跳至内容

核心文件精读

这一页由人工撰写,导读 DSH 主链路中的 27 个文件;自动生成的只有文件索引。每一条都回答四个问题:它有什么用,为什么单独放在这里,和哪些文件配合,怎样验证。路径链接固定到官方提交 aa6c361a972c8369148dea7380bb5c21c24e07ec

先看总链路

text
Cordis Context
  -> Profile 读取 Bundle 和补丁
  -> Bundle 挂载 Session、Agent、Tools、LLM 等插件
  -> Agent Loop 打开 Turn 和 Step
  -> Session 记录输入、模型片段、工具调用和结果
  -> Prompt / Tool schema 组装模型请求
  -> LLM Adapter 产生流式片段
  -> Tool Registry 执行工具并把结果写回 Session

下面的文件按照这条链路从底到上排列。不要把“先读”理解成必须一次读完;每看完一个文件,就用它的测试和协作者检查自己的理解。

委派是这条链上的一条支线:父 Agent 把任务交给子 Agent,子深度恒为父深度加一,默认上限 3,超了就在边界抛 SubagentDepthError、子会话根本不创建;报错原文是 subagent depth <attempted> exceeds maxDepth <max>(child-agent.ts 第 33 行)。深度记在 Session header 上单调取最大,恢复出来的子代理没法把自己算回顶层。subagent 委派实验把「发起 → 边界 → 子执行 → 回报」摆成时间线,切父深度看这条报错怎么出现。

互动组件subagent 委派实验单独打开

正在载入互动组件……

不看组件也能读的说明

时间线是固定教学模型:一次委派、两种结局(回报或失败都算完整结算)、一条被拒路径。它不启动真实子进程或 worker;真实调度细节以源码和测试为准。

Cordis 基础

vendor/cordis/src/context.ts

  • 有什么用:它定义插件共享的 Context 和具体的 Context 类。插件通过上下文找到服务、注册事件、挂载子插件,也可以创建不会修改父上下文的子上下文。
  • 为什么这样设计:上下文采用代理和原型继承,普通读取像读属性,extend()isolate()intercept() 又能产生新的作用范围。这样同一个服务可以在不同 agent 或不同插件树里使用不同实现,而不会污染父树。
  • 和谁配合fiber.ts 负责上下文对应的插件生命周期;service.ts 把服务放进上下文;events.ts 提供事件总线;reflect.tsregistry.ts 负责查找与插件注册。
  • 怎么验证:先看官方 Cordis primer,再沿 DSH 的包测试观察 ctx.isolate()ctx.intercept();vendor 本身没有单独的 DSH 测试目录。

vendor/cordis/src/fiber.ts

  • 有什么用Fiber 是一个插件实例的运行记录。它保存插件状态、已校验配置、依赖服务、效果清理函数和卸载过程。
  • 为什么这样设计:插件不只是执行一次函数,它可能注册监听器、服务、定时器和子插件。把所有可撤销效果收集在 fiber 中,插件卸载时就能按相反顺序清理,避免“拔掉插件后监听器还活着”。PENDINGLOADINGACTIVEFAILEDUNLOADINGDISPOSED 这些状态也让异步启动和失败可以被观察。
  • 和谁配合context.ts 创建 fiber;service.ts 的注册效果归它管理;registry.ts 决定何时加载插件;events.ts 在生命周期事件上通知观察者。
  • 怎么验证:对照 packages/boot/app-boot/tests/app-boot.spec.tsconfig-reload.spec.tspackages/core/agent-loop/tests/scope-lifecycle.spec.ts,重点看加载、失败、重载和清理。

vendor/cordis/src/service.ts

  • 有什么用Service 是把一项命名能力注册到 ctx 的基础类。子类调用 super(ctx, name) 后,其他插件就能通过服务名找到它。
  • 为什么这样设计:服务的接口和具体提供方可以分开。Service 还负责服务过滤、子上下文扩展和拦截配置合并,所以业务包不用各自发明注册与清理规则。
  • 和谁配合:它调用 ctx.reflect.provide()context.ts 提供上下文;fiber.ts 负责注册效果的撤销;具体 DSH 服务类继承它,例如 Session、LLM 和 Tools 服务。
  • 怎么验证:看同包的 packages/llm/llm/tests/service.spec.tspackages/core/agent/tests/invariant.spec.tspackages/core/tools/tests/scoped.spec.ts,分别观察服务注册、范围和卸载。

vendor/cordis/src/events.ts

  • 有什么用:它定义事件的类型、监听器和分发方式,包括普通广播、并行、串行、bail 和 waterfall。DSH 的许多扩展点都从这里开始。
  • 为什么这样设计:不是所有事件都需要同一种顺序。普通通知可以广播;waterfall 需要监听器调用 next() 才继续;bail 可以在得到足够结果时停止。把分发模式放在底层,DSH 的插件只声明自己需要哪种协作方式。
  • 和谁配合context.ts 暴露事件服务;fiber.ts 保证监听器随插件卸载;Agent、Session 和 Tools 包分别声明自己的事件名。
  • 怎么验证:读 DSH 官方架构中的 Events,再看 packages/core/agent-loop/tests/tool-order.spec.ts 和工具/LLM 的 waterfall 测试。

启动、Profile 与 Bundle

packages/boot/app-boot/src/index.ts

  • 有什么用:它是应用启动公共入口,负责把 profile、bundle、补丁和 Cordis Loader 组合成一棵可以挂载的插件树,并提供启动失败时的收束逻辑。
  • 为什么这样设计:Profile 组合是 CLI、Web、headless 都会用到的基础能力,所以不能写在某一个应用里。把它放在 app-boot,不同宿主可以共享“怎样装配”而各自决定“装配后做什么”。
  • 和谁配合profile.ts 读取 profile;Cordis Loader 挂载条目;apps/cli/src/profile-boot.ts 提供 CLI 的环境和进程生命周期;Bundle 的 package.jsoncordis.patch.yml 提供实际层。
  • 怎么验证:看 packages/boot/app-boot/tests/app-boot.spec.tsconfig-dump.spec.tsconfig-reload.spec.tsuser-patches.spec.ts

packages/boot/app-boot/src/profile.ts

  • 有什么用:它把 $DSH_HOME/profiles/<name> 解释成一个 Profile,读取 dsh.profile.bundles,找到每个 Bundle 的补丁,再加上 profile 自己的用户补丁。
  • 为什么这样设计:补丁按层叠加:Bundle 层先来,profile 层覆盖它,home 层和命令行 --patch 继续覆盖。每层只改自己拥有的配置行,用户就能换模型、工具或策略而不必复制整个 Bundle。
  • 和谁配合packages/boot/app-boot/src/index.ts 调用它完成启动;Bundle 的 manifest 声明补丁位置;CLI 的 apps/cli/src/profile-boot.ts 继续添加 home、overlay 和 telemetry 选择。
  • 怎么验证:看 packages/boot/app-boot/tests/profile.spec.tsuser-patches.spec.tshmr-config.spec.tsconfig-reload.spec.ts,重点检查顺序、缺文件和重载。

packages/bundle/base/src/index.ts

  • 有什么用:它是多数 profile 的基础功能层,把模型、Session、Tools、文件系统、shell、权限、设置、凭据和遥测等基础插件放进树里。
  • 为什么这样设计:把“常用但可替换”的能力放进一个 Bundle,用户可以用同一套骨架启动 Web 或 headless;真正的配置仍在 patch 文件中,因此上层还能替换单行。
  • 和谁配合packages/boot/app-boot/src/profile.ts 读取它的 Bundle manifest;packages/bundle/headless/src/index.tspackages/bundle/web-app/src/index.ts 在它之上添加宿主差异;各功能包提供被挂载的插件。
  • 怎么验证:看官方 Bundle 说明,再运行 CLI 的 dump-config 相关测试,观察基础行是否按预期出现。

packages/bundle/headless/src/index.ts

  • 有什么用:它把共享核心组合成一次性、没有 Web 服务器的运行器。它取得 Agent、默认模型和 Session,送入一次输入,然后从事件结果中总结退出状态。
  • 为什么这样设计:批处理、脚本和自动化不需要浏览器。把 headless 作为 Bundle,而不是把 if (headless) 散落在每个核心包里,可以复用同一套 Agent Loop,同时保持宿主退出规则清楚。
  • 和谁配合:依赖 core/agentcore/sessionllmapps/cli/src/profile-boot.ts 提供 ctx.appExitexamples/headless-agent 展示如何启动它。
  • 怎么验证:看 apps/cli/tests/headless-shutdown.e2e.tssource-launch.compat.spec.tsexamples/headless-agent 的运行入口,重点看成功、错误和中断是否都结束进程。

packages/bundle/web-app/src/index.ts

  • 有什么用:它把 Web Server、静态前端、系统提示词里的 Web 地址和浏览器运行时接到共享插件树上,并处理局域网信任主机等启动选项。
  • 为什么这样设计:浏览器只是一个宿主,不应改变 Session、Agent 或 Tools 的核心协议。Web-specific 逻辑集中在 Bundle 里,headless 可以完全不加载它。
  • 和谁配合:依赖 host/webserverhost/frontend-static;前端由 packages/client 提供;CLI 的 profile 启动器负责等待 Loader、打印 URL 和处理退出。
  • 怎么验证:看 Web 应用的启动测试、apps/cli/tests/web-agent-presets.e2e.ts 和官方 Web UI 指南;真实浏览器和真实模型属于额外运行验证,不由索引生成证明。

Session:把过程变成可重放的日志

packages/core/session/src/types.ts

  • 有什么用:它定义 SessionId、Session header、SessionEventMapSessionEvent、Turn 结束原因和模型可见事件。它是“会话里究竟记录什么”的类型地图。
  • 为什么这样设计:会话是追加式日志,不是只保存最后一段文本。用户消息、assistant chunk、完整 assistant message、tool call、tool result、请求头和 todo 都有自己的事件类型;这样回放、恢复、UI 和持久化可以从同一事实源工作。类型还区分可忽略事件和必须理解的事件,防止旧读者静默读错。
  • 和谁配合packages/core/session/src/index.ts 实现存储和追加;packages/core/session/src/preparation.ts 负责未发布对象的所有权;packages/core/session/src/surface.ts 派生模型可见表面;Agent Loop 产生事件;持久化包写 JSONL 或 SQLite。
  • 怎么验证:看 packages/core/session/tests/session.spec.tsinvariant.spec.tssurface.spec.tsrequest-header.spec.tsfork.spec.tsrepair.spec.ts

packages/core/session/src/preparation.ts

  • 有什么用:它只负责持有一个尚未公开的精确 Session,以及可选的 provider-owned release();调用 dispose 时幂等地释放这部分外部状态。seed、header、事件连续性和恢复数据校验由 packages/core/session/src/index.ts 及持久化准备层完成。
  • 为什么这样设计:Session 在完成 setup 以前不应该进入公开的 Store;把“未发布对象的所有权”和“provider 资源的释放”放进一个小包装器,可以让 publish 前后边界清楚,并让重复 dispose 变成安全的 no-op。
  • 和谁配合packages/core/session/src/index.tsSession.createSession.fromRestoreSessionStore.prepare 负责创建或恢复;packages/session/session-persistence/src/preparations.ts 协调持久化准备;Agent Loop 在创建或 resume agent 时消费准备结果。
  • 怎么验证:看 packages/core/session/tests/session.spec.tsfork.spec.tsinvariant.spec.tsproperties.spec.ts

packages/core/session/src/index.ts

  • 有什么用:这是 Session 包的公开入口和主要实现,提供 Session、SessionStore、事件追加、fork、空闲等待、恢复和事件投影相关 API。
  • 为什么这样设计:让“追加事实”和“读取投影”有清楚的边界。原始事件保持不可变并按序排列,deriveMessages() 等消费者再从它投影模型历史;这样新增 UI、回放或持久化后端不会各自保存一份互相矛盾的历史。
  • 和谁配合packages/core/session/src/types.ts 提供事件类型;packages/core/session/src/preparation.ts 负责发布前准备;packages/core/session/src/surface.ts 计算可见消息;packages/core/session/src/json.ts 保证事件数据可安全序列化;Session persistence 包负责落盘。
  • 怎么验证:看 session.spec.tsfork.spec.tssurface.spec.tsrepair.spec.tsscoped.spec.tstypert.spec.ts

Agent 与 Agent Loop

packages/core/agent/src/types.ts

  • 有什么用:它向类型消费者公开 Agent 的输入队列约定,并把 agent/inbox/spliced 合并进 Session 事件地图。这里说明消息放到 next-turn 还是 next-step,以及一次队列改动怎样被记录。
  • 为什么这样设计:Agent 的 inbox 是会影响下一次模型请求的状态。把队列变动变成 durable event,恢复和调试时才知道消息为什么出现在某一步;把类型放在独立文件也让只需要类型的包不必加载完整运行时。
  • 和谁配合packages/core/agent/src/index.ts 使用这些类型;packages/core/agent-loop 负责真正 splicing 和发事件;Session types.ts 接收事件扩展;Agent Loop 测试检查队列和取消。
  • 怎么验证:看 packages/core/agent/tests/agent.spec.tsagent-initiator.spec.tsconsumed-work.spec.tsinvariant.spec.ts

packages/core/agent/src/index.ts

  • 有什么用:它定义 Agent 服务、Agent Handle、Agent 注册表和 agent/* 事件,给其他插件一个观察或驱动 agent 的公共接口。
  • 为什么这样设计:调用者不应该直接抓住某个 ReactLoopAgent 的内部字段。公共 Agent 契约只描述发送输入、注入上下文、取消、状态和事件;具体循环可以替换,UI、subagent 和工具仍能工作。
  • 和谁配合packages/core/agent-loop/src/agent.ts 实现具体驱动;Session 提供持久身份和事件;Tools、Prompt 和 UI 通过 ctx.agents 或 agent-scoped context 与它协作。
  • 怎么验证:看 packages/core/agent/tests/agent.spec.tsagent-initiator.spec.tsmodel-selection.spec.tsverify-export-jsdoc.spec.ts

packages/core/agent-loop/src/index.ts

  • 有什么用:它是 Agent Loop 插件入口,创建 Agent 工厂、恢复或新建 Session、发布 live agent,并负责配置和整体卸载。
  • 为什么这样设计:工厂拥有多个 agent 和共享启动任务,所以必须有一个比单个 agent 更高的生命周期所有者。文件中的 FactoryOwnership 让卸载可以同时中止等待中的创建、等待启动工作并清理活跃 agent。
  • 和谁配合agent.ts 承担一个 agent 的状态机;runtime-context.ts 维护动态上下文投影;tool-calls.ts 处理模型请求的工具调用;core/agent 提供公共契约;session 提供日志。
  • 怎么验证:看 packages/core/agent-loop/tests/agent.spec.tscancel.spec.tsloop.spec.tsresume.spec.tsscope-lifecycle.spec.tscontract-regressions.spec.ts

packages/core/agent-loop/src/agent.ts

  • 有什么用:它实现具体的 ReactLoopAgent,维护 inbox、Turn/Step 状态、请求模型、接收流、调工具和关闭轮次。
  • 为什么这样设计:模型请求和工具调用可能循环多次,而且每个步骤都可能取消、失败或继续。把状态机放在一个明确的 agent runtime 中,才能保证 turn/startstep/startstep/endturn/end 顺序正确,而不是把状态散落到插件回调里。
  • 和谁配合runtime-context.ts 产生模型可见的动态上下文;tool-calls.ts 把 assistant 的工具块交给 Tools;Session 记录事件;LLM BlockAssembler 组装流;Agent 公共接口向外发布状态。
  • 怎么验证:看 packages/core/agent-loop/tests/loop.spec.tsagent.spec.tstool-order.spec.tsrequest-reconstruction.spec.tsrequest-error.spec.tsresume.spec.tscancel.spec.ts

packages/core/agent-loop/src/runtime-context.ts

  • 有什么用:它跟踪由系统提示词或插件产生的动态运行时上下文,并把变化投影成带来源的 user message。
  • 为什么这样设计:模型看到的上下文必须同时满足“只在变化时写入”和“能从 Session 还原”。因此它不直接改 prompt 字符串,而是观察 Session 表面,比较上一次保留的快照,必要时生成新的消息;被 compaction 替换时也会清掉旧快照。
  • 和谁配合:使用 dsh-llm 的 user message;读取 dsh-session 的 surface 和 replacement event;由 agent.ts 在请求前把投影放进会话。
  • 怎么验证:看 packages/core/agent-loop/tests/runtime-context.spec.tsrequest-reconstruction.spec.tsresume.spec.ts

packages/core/agent-loop/src/tool-calls.ts

  • 有什么用:它把一次 assistant 输出中的 tool-call block 转成 Tool Registry 能执行的输入,处理调用顺序、取消、结果和是否需要下一步请求。
  • 为什么这样设计:模型流和工具执行是两个不同的协议。单独的桥接文件可以集中处理 call id、参数字符串、失败结果和多工具调用,Agent 主循环只关心“本步骤是否还有工作”。
  • 和谁配合:读取 dsh-llm 的 assistant message;调用 dsh-tools;把 tool/calltool/result 写入 Session;agent.ts 根据返回状态决定继续或结束。
  • 怎么验证:看 packages/core/agent-loop/tests/tool-calls.spec.tstool-order.spec.tsrequest-error.spec.tsinvariant.spec.ts

工具注册、schema 与展示

packages/core/tools/src/index.ts

  • 有什么用:它实现工具注册表和执行流水线:按作用域查找工具、生成模型 schema、检查并发、请求审批、执行、处理取消、规范化结果并发出 tools/* 事件。
  • 为什么这样设计:工具不只是一个函数。一次调用需要权限、参数、并发、超时、模型可见文本和 UI 展示。把这些阶段写成统一流水线,新的工具只实现自己的能力,通用的安全和生命周期规则由注册表保证。
  • 和谁配合packages/core/tools/src/schema.ts 定义参数和输出校验;packages/core/tools/src/presentation.ts 定义 provider-neutral 的 UI 意图;Agent Loop 提交执行;Session 保存 tool/calltool/result;审批插件监听相应事件。
  • 怎么验证:看 packages/core/tools/tests/tools.spec.tsexecution-mode.spec.tsexecution-signal-types.spec.tsscoped.spec.tsinvariant.spec.tsproperties.spec.ts

packages/core/tools/src/schema.ts

  • 有什么用:它提供工具参数和输出使用的 JSON schema DSL,把作者写的类型化 schema 编译成模型能理解、运行时能验证的 JSON Schema。
  • 为什么这样设计:如果每个工具手写一份类型、一份 JSON Schema 和一份验证代码,三份内容很容易不一致。集中编译还能明确对象是否开放、required 怎样表达、递归 schema 怎样处理,并拒绝不支持的形状。
  • 和谁配合packages/core/tools/src/index.ts 把 schema 放进工具定义和 prompt;packages/core/tools/src/json-schema.ts 执行验证;LLM 类型定义提供模型侧的 schema 形状;工具测试验证输入和输出错误。
  • 怎么验证:看 packages/core/tools/tests/schema.spec.tsjson-schema.spec.tsts-types.spec.tspy-types.spec.tsproperties.spec.ts

packages/core/tools/src/presentation.ts

  • 有什么用:它定义工具调用和结果在 UI 或 CLI 中怎样展示的中立数据,例如普通卡片、终端卡片、文件 diff、搜索结果和读取行。
  • 为什么这样设计:工具执行结果给模型看的文本,和给人看的卡片不是一回事。把展示意图抽成 provider-neutral 类型,工具不用依赖某一个前端;Web、CLI 或其他桥接器可以各自渲染同一意图。
  • 和谁配合packages/core/tools/src/index.ts 在执行前后调用 presenter;packages/client 的 UI 插件和 CLI 把 ToolCallViewToolResultView 渲染出来;文件工具、shell 工具和 web 工具提供具体视图。
  • 怎么验证:看 packages/core/tools/tests/tools.spec.ts,以及固定版本中确实存在的 packages/client/ui-tool/tests/diff-card.client.spec.tsxpackages/client/ui-tool/tests/read-card.client.spec.tsxpackages/client/ui-tool/tests/search-card.client.spec.tsxpackages/client/ui-tool/tests/terminal-card.client.spec.tsxpackages/client/ui-tool/tests/web-card.client.spec.tsx;它们分别帮助核对文件差异、读取、搜索、终端和 Web 结果的呈现。还可以对照各工具包自己的 presentation 测试。

LLM 流和 DeepSeek 适配器

packages/llm/llm/src/index.ts

  • 有什么用:它提供 ctx.llm、抽象 LlmAdapter、模型注册和 llm/stream waterfall,并导出消息、错误、调用配置和 BlockAssembler 等公共词汇。
  • 为什么这样设计:Agent 只需要统一的消息与流接口,不应该知道某个供应商的 HTTP 字段。抽象适配器让 DeepSeek、pi-ai、回放或测试 mock 都可以挂到同一服务上;waterfall 又给重试、路由和回放留下扩展位置。
  • 和谁配合packages/llm/llm/src/assembler.ts 把流片段组装成消息;packages/llm/llm-deepseek/src/adapter.ts 实现 HTTP 适配;Session 保存 request header、chunk 和完整 assistant message;Agent Loop 消费 stream。
  • 怎么验证:看 packages/llm/llm/tests/service.spec.tsassembler.spec.tsadapter-failure.spec.tsretry-policy.spec.tsinvariant.spec.ts;这些文件都位于固定版本的 packages/llm/llm/tests/,验证通用 LLM 服务、组装器和错误策略。

packages/llm/llm/src/assembler.ts

  • 有什么用BlockAssembler 按流的顺序收集 text、reasoning、tool-call、usage 和 finish chunk,最后生成完整 assistant message。
  • 为什么这样设计:流式协议只会不断送增量,Agent 同时又要实时显示和在结束时得到完整消息。这个类保留原始 chunk 的回放价值,再提供稳定的 blocks、usage、finish 和 message 视图;已被 block-end 关闭的 block 收到迟到增量时会忽略(畸形流容错),避免结果被污染——至于 finish 之后的 chunk,那不是本类的职责,流校验层会直接报错。
  • 和谁配合:LLM 适配器产生 StreamChunk;Agent Loop 每收到一块就 push();Session 记录原始 assistant/chunk,结束时记录 assistant/message;工具桥从最终 tool-call block 继续执行。
  • 怎么验证:看 packages/llm/llm/tests/assembler.spec.tsproperties.spec.ts 和 Agent Loop 的 request-reconstruction.spec.ts

packages/llm/llm-deepseek/src/adapter.ts

  • 有什么用:它把 DSH 的统一 LLM 请求转换成 DeepSeek 兼容的 HTTP chat-completions 请求,再把网络、认证、HTTP 错误和流式响应转成 Harness 的 StreamChunkLlmError
  • 为什么这样设计:供应商差异应该停在适配器边界。适配器在一次 stream 开始时冻结连接和凭据,处理 idle timeout、取消、重试信息和 request id,避免请求进行到一半时配置变化导致一条请求前后不一致。
  • 和谁配合:继承 packages/llm/llm/src/index.tsLlmAdapterpackages/llm/llm-deepseek/src/sse.ts 解帧;packages/llm/llm 的错误和消息类型统一故障;packages/llm/llm-deepseek 的测试服务器和翻译测试验证 wire 形状。
  • 怎么验证:主要看固定版本中 packages/llm/llm-deepseek/tests/adapter.spec.tsdynamic-config.spec.tssse.spec.tstranslate.spec.tsadapter.e2e.ts;其中 adapter-failure.spec.tsapi-key.spec.ts 不在 DeepSeek 子包,而是在通用 LLM 测试层的 packages/llm/llm/tests/adapter-failure.spec.tspackages/llm/llm/tests/api-key.spec.ts

packages/llm/llm-deepseek/src/sse.ts

  • 有什么用:它把 DeepSeek 返回的 Server-Sent Events 字节流解析成一个个 data 字符串,并把 [DONE] 作为最后的结束标记交给上层。
  • 为什么这样设计:网络读取可能在任意字节处分段,甚至把一个 UTF-8 字符拆开;SSE 还要处理空行、CRLF、注释和多行 data。把 framing 单独放在这里,适配器只处理 JSON 协议;如果 EOF 没有 [DONE],它会报告截断而不是假装成功。
  • 和谁配合packages/llm/llm-deepseek/src/adapter.ts 读取每个 payload 并转换成模型 chunk;eventsource-parser 负责标准 framing;LlmError 提供 STREAM_CLOSED 等机器可读错误。
  • 怎么验证:看 packages/llm/llm-deepseek/tests/sse.spec.tsadapter.spec.tsmock-server.ts;通用流关闭或错误语义再对照 packages/llm/llm/tests/adapter-failure.spec.ts

CLI 入口

apps/cli/src/bin.ts

  • 有什么用:这是 dsh 命令的最外层入口,读取版本、解析参数,然后按 profileplugindump-config 动态导入对应模块。
  • 为什么这样设计:动态导入让 --help--version 和某个运行模式不必加载所有 Web、headless 和插件代码;入口保持薄,真正的启动逻辑放在对应模块中,也更容易测试。
  • 和谁配合args.ts 负责参数解析;profile-boot.ts 负责启动;dump-config.ts 负责配置输出;plugin.ts 负责插件命令;app-boot 提供分层环境。
  • 怎么验证:看 apps/cli/tests/args.spec.tsbuilt-bin.e2e.tssource-launch.compat.spec.tsheadless-shutdown.e2e.tstelemetry-switch.spec.ts

apps/cli/src/profile-boot.ts

  • 有什么用:它把 CLI 的 profile 名称、--patch 文件、环境快照和进程信号交给 app-boot,再维护 fail-loud、动态补丁和有界 shutdown。
  • 为什么这样设计:CLI 负责进程边界,App Boot 负责通用组合,具体 Bundle 负责业务能力。这样同一套 profile 机制能被不同宿主复用;把 SIGINT、SIGTERM、快速退出和 watcher 收束放在这里,也不会污染 Agent Loop。
  • 和谁配合:调用 app-bootloadProfilecomposeEntriesboot;向树中提供 ctx.cmdlineArgs 和 configured identities;headless Bundle 通过 ctx.appExit 请求结束。
  • 怎么验证:看 apps/cli/tests/source-launch.compat.spec.tsbuilt-bin.e2e.tsheadless-shutdown.e2e.tswindows-shell.spec.tsweb-agent-presets.e2e.ts,以及 packages/boot/app-boot/tests/config-reload.spec.ts

读完这些文件后检查自己

  • 你能否说出一个插件怎样从 Context 取得服务、注册事件,并在卸载时清理?
  • 你能否解释 Profile 如何由 Bundle 补丁有序叠加而成?
  • 你能否指出“模型看到的内容”从哪里来,以及为什么它必须在 Session 日志中可重建?
  • 你能否区分 Agent 的公共接口、具体 Loop、LLM Adapter 和 Tool Provider?
  • 你能否从一个测试找到它保护的运行时不变量,而不是只看测试名字?

如果还不能,先回到Agent 与 Turn 流程Session 日志与恢复,再沿本页的协作者链接回源码。