Module Structure
CLI 层先建立进程级 cwd、日志和可信资源覆盖,再按参数选择专用运行器;Headless 与 Print 都直接拥有 SessionRuntime,但面向不同的机器消费与一次性文本消费场景。
Directory Layout
packages/cli/src/blade.tsx— 进程预解析、工作区初始化、模式分流和默认 Ink 启动packages/cli/src/cli/— 全局选项、设置文件校验、运行时配置合并和中间件packages/cli/src/commands/headless.ts— 完整 Agent loop 的非交互运行器packages/cli/src/commands/headlessEvents.ts— 版本化 snake_case JSONL wire schemapackages/cli/src/commands/HeadlessOutputEgress.ts— stdout/stderr 有界串行写入packages/cli/src/commands/print.ts— 单一最终响应运行器packages/cli/src/commands/shared/— 输入规范化、输出 schema 和非交互 Session 解析packages/cli/src/commands/— Serve、Web、Browser、Doctor、MCP、Projects、Schedule 等管理命令
Key Entry Points
main()inpackages/cli/src/blade.tsx— 选择 Headless、Print、ACP、yargs 子命令或 TUIloadConfiguration()inpackages/cli/src/cli/middleware.ts— 合并 settings、CLI 和持久配置runHeadless()inpackages/cli/src/commands/headless.ts— 执行完整事件化 Agent looprunPrint()inpackages/cli/src/commands/print.ts— 聚合一次最终响应并退出resolveNonInteractiveSession()inpackages/cli/src/commands/shared/sessionContext.ts— 统一 resume、continue 与 fork
Gotchas
--debug必须在导入 yargs 和创建其他 logger 前从原始 argv 预解析,否则早期模块日志不会继承过滤设置 (packages/cli/src/blade.tsx)- 工作区根与命令调用目录不是同一概念:项目探测会更新全局 cwd,但
--plugin-dir和--settings的相对路径仍按原始 invocation cwd 解析 (packages/cli/src/blade.tsx,packages/cli/src/cli/settings.ts) --headless在全局GracefulShutdown初始化前分流并独占 SIGINT/SIGTERM;把它移到普通 yargs 路径会让活动 turn 来不及 settle 就退出 (packages/cli/src/blade.tsx,packages/cli/src/commands/headless.ts,git:3d7ec137)- Headless 新 Session 默认
yolo,Print 则回退到 Runtime 配置的权限模式;resume 时两者都优先采用显式参数,再采用 Session 持久模式 (packages/cli/src/commands/headless.ts,packages/cli/src/commands/print.ts) - 无参数
--resume只在 TUI 中打开选择器,Print/Headless 必须获得具体 Session ID;--session-id与 resume/continue 同用时还必须显式 fork (packages/cli/src/cli/middleware.ts,packages/cli/src/commands/shared/sessionContext.ts) - Headless 的
jsonl是版本化、snake_case 且经 TypeBox 定义的外部契约,不应直接序列化内部 camelCaseLoopEvent(packages/cli/src/commands/headlessEvents.ts) - Print 的
stream-json名称不表示逐 delta 输出;实现会先drainLoop(),最后只写一个 response 或 structured_output 对象 (packages/cli/src/commands/print.ts) ! <command>不能与 output schema 组合,Headless 中还不能与--task-isolation组合;这些路径跳过模型并使用 Shell 自己的退出码 (packages/cli/src/commands/headless.ts,packages/cli/src/commands/print.ts)- Headless 的 stdout 与 stderr 是两个独立容量队列;任一 writer 的 EPIPE、超时、overflow 或无法观察的
drain都会中止当前运行并返回失败 (packages/cli/src/commands/HeadlessOutputEgress.ts,git:1af43232) - Headless 对 MCP Elicitation 和 Sampling 默认拒绝,但普通工具确认默认 Session 级批准;这不是 TUI 的交互权限行为 (
packages/cli/src/commands/headless.ts) globalOptions的 yargs 总体为strict: false以保留兼容性,Headless 仍在内部用HeadlessOptionsSchemafail closed;新增选项必须同步这两层,否则可能被 yargs 接收后在运行器拒绝 (packages/cli/src/cli/config.ts,packages/cli/src/commands/headless.ts)
Architecture
- 启动优先级固定为纯版本请求 → 工作区/信任预处理 → Headless → 全局关闭处理器 → Print → ACP → yargs 管理命令或 TUI,前面的专用模式不会加载后面的重型 UI 路径 (
packages/cli/src/blade.tsx) --settings先通过封闭字段集合与 TypeBox schema 校验,再只填补 argv 中未显式提供的值,最后由mergeRuntimeConfig()与持久配置合并 (packages/cli/src/cli/settings.ts,packages/cli/src/cli/middleware.ts)- Print 与 Headless 共用输入规范化、插件资源加载、输出 schema 和非交互 Session 解析;Slash command 可直接产生输出,也可转换为 Skill/自定义命令提示后继续走 Agent (
packages/cli/src/commands/shared/commandInput.ts) - Headless 的事件 writer 显式映射每一种 LoopEvent,并在每个事件后 flush;结构化输出工具本身从普通 tool 事件中隐藏,只暴露专用
structured_output(packages/cli/src/commands/headless.ts,packages/cli/src/commands/headlessEvents.ts) --task-isolation不是普通 Session 标志,它先通过SessionTaskService创建 durable Task 与可选 worktree,再禁止 Agent 重复调用 Enter/ExitWorktree (packages/cli/src/commands/headless.ts)
Decisions
- Headless 与 Print 分成两个运行器:前者为自动化保留完整事件、阶段和工具状态,后者为 shell pipeline 保留单个最终值与简单退出码 (
packages/cli/src/commands/headless.ts,packages/cli/src/commands/print.ts) - JSONL 使用固定
event_version和运行时 schema,是为了允许外部消费者独立于内部 LoopEvent 演进;新增事件必须先扩充 union 再从 writer 发出 (packages/cli/src/commands/headlessEvents.ts) - TUI、yargs 和版本检查采用动态导入,避免
--help、--version与非交互命令承担 Ink/React 启动成本 (packages/cli/src/blade.tsx)
Patterns
- 非交互 resume 可以无新输入地继续 pending steering 或 active Goal;两者都不存在时只允许返回已恢复的最终响应,否则报“无未完成 turn” (
packages/cli/src/commands/headless.ts,packages/cli/src/commands/print.ts) - Session 权限模式在 Runtime 创建和 input preparation 前持久化,避免 UI/输出已宣称新模式但崩溃恢复仍使用旧模式 (
packages/cli/src/commands/headless.ts,packages/cli/src/commands/print.ts) - Headless 清理顺序是停止 Bus 订阅、flush 已接纳输出、dispose Runtime、关闭 egress、移除信号监听;新增早退路径必须仍经过统一
finally(packages/cli/src/commands/headless.ts) serve与web都注册同一幂等 server stop 清理,区别仅是 Web 命令打开浏览器并展示地址;不要在两个命令中复制服务端资源所有权 (packages/cli/src/commands/serve.ts,packages/cli/src/commands/web.ts)
Dependencies
- CLI 参数解析依赖 yargs,运行时校验依赖项目 TypeBox wrapper,TUI 启动依赖 Ink/React;这些依赖均在对应分支按需加载 (
packages/cli/src/blade.tsx,packages/cli/src/commands/headless.ts)