一. 文件锁避免重复实例

    let _pid_lock = match ironclaw::bootstrap::PidLock::acquire() {//尝试获取
        Ok(lock) => Some(lock),
        Err(ironclaw::bootstrap::PidLockError::AlreadyRunning { pid }) => {//已有运行,则取消本次运行
            anyhow::bail!(
                "Another IronClaw instance is already running (PID {}). \
                 If this is incorrect, remove the stale PID file: {}",
                pid,
                ironclaw::bootstrap::pid_lock_path().display()
            );
        }
        Err(e) => {//无权限等错误到这
            eprintln!("Warning: Could not acquire PID lock: {}", e);
            eprintln!("Continuing without PID lock protection.");
            None
        }
    };

二. Agent startup

1. Enhanced first-run detection

  • 避免没有必须的配置的情况,去运行agent

    if !cli.no_onboard
        && let Some(reason) = ironclaw::setup::check_onboard_needed()
    {
          wizard.run().await?;//同直接ironclaw onboard,走一遍引导

2. 最终Agent参数获取


     let toml_path = cli.config.as_deref();//同Onboard Setting获取的文件
    let runtime_overrides = ironclaw::config::RuntimeConfigOverrides {//这里是运行时安全配置重写
        deployment: cli.deployment_mode,//包括部署模式
        profile: cli.runtime_profile,//运行画像 ,这些都会影响后面agent的行为决策
        yolo_disclosure_acknowledged: if cli.yolo_disclosure {
            Some(true)
        } else {
            None
        },
    };
    let config = match Config::from_env_with_toml(toml_path)//将用从toml读取构造的setting给config配置
        .await
        .and_then(|c| c.with_runtime_overrides(&runtime_overrides))
  1. Settings::default()
  2. Profile preset
  3. TOML 文件
  4. 从 OS 凭据存储注入(Keychain / Linux credentials)
  5. 从加密 DB 注入的 LLM 密钥
  6. 用户显式设置的环境变量(.env / IRONCLAW_* / shell)— 最高

3. 日志广播初始化

  生产者(产生日志方):业务代码直接调 tracing::info!() / error!() 等宏,不需要感知 web、订阅者、SSE 这些东西。tracing-subscriber 框架会把日志路由到 Layer,Layer 再推给 broadcaster。

  消费者(需要日志方):SSE/WebSocket handler 调用 subscribe() 建立订阅,broadcaster 维护一个活跃连接列表,每次收到新日志就遍历推送。

  简单说就是:

  业务代码 (tracing!宏)
      ↓
  tracing Layer (log_layer.rs 的 on_event)
      ↓
  Broadcaster::send()  ← 所有在线订阅者收到
      ↓
  SSE handler / WebSocket handler (subscribe → receiver)
      ↓
  客户端浏览器

  Layer 本身充当了桥接层——一边接 tracing 框架的被动回调,一边接主动的订阅/广播机制,业务代码完全不用改。

    let log_broadcaster = Arc::new(LogBroadcaster::new());

三. AppBuilder(构建核心组件)

buildAll()
  init_database//连接数据库,获取句柄;清理"残留 sandbox 任务"——上次进程崩溃可能留下未结束的 sandbox job 记录
init_secrets(&mut self)
  拿master key:环境变量、Keychain (macOS) / Credentials file (Linux)

  拿到后let crypto = Arc::new(SecretsCrypto::new(master_key.clone())?);构造加密引擎

   构造真正的持久化 secrets storelet store = create_secrets_store(crypto, handles); 

  如果本进程自动生成了新 key,但 DB 里已有历史密钥行,新 key 解不开旧行——继续往下走会静默遮盖无法恢复的数据。一旦发现,回滚 key 持久化(让下次启动重新触发检测),并 fail-
  closed。
  
crate::config::inject_llm_keys_from_secrets(secrets.as_ref(), &self.config.owner_id)注入配置将加密llm key 
  除了固定读的,还有动态发现的(provider.json文件里,有配置什么模型需要什么变量)
  DB 里是加密的,内存里是明文的——明文只存在于进程运行时的 INJECTED_VARS,进程退出就清掉。

 若缺失补上config.llmconfig的apikey