核心文件精读
这一页由人工撰写,导读 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 委派实验把「发起 → 边界 → 子执行 → 回报」摆成时间线,切父深度看这条报错怎么出现。
Cordis 基础
vendor/cordis/src/context.ts
- 有什么用:它定义插件共享的
Context和具体的Context类。插件通过上下文找到服务、注册事件、挂载子插件,也可以创建不会修改父上下文的子上下文。 - 为什么这样设计:上下文采用代理和原型继承,普通读取像读属性,
extend()、isolate()、intercept()又能产生新的作用范围。这样同一个服务可以在不同 agent 或不同插件树里使用不同实现,而不会污染父树。 - 和谁配合:
fiber.ts负责上下文对应的插件生命周期;service.ts把服务放进上下文;events.ts提供事件总线;reflect.ts和registry.ts负责查找与插件注册。 - 怎么验证:先看官方 Cordis primer,再沿 DSH 的包测试观察
ctx.isolate()和ctx.intercept();vendor 本身没有单独的 DSH 测试目录。
vendor/cordis/src/fiber.ts
- 有什么用:
Fiber是一个插件实例的运行记录。它保存插件状态、已校验配置、依赖服务、效果清理函数和卸载过程。 - 为什么这样设计:插件不只是执行一次函数,它可能注册监听器、服务、定时器和子插件。把所有可撤销效果收集在 fiber 中,插件卸载时就能按相反顺序清理,避免“拔掉插件后监听器还活着”。
PENDING、LOADING、ACTIVE、FAILED、UNLOADING、DISPOSED这些状态也让异步启动和失败可以被观察。 - 和谁配合:
context.ts创建 fiber;service.ts的注册效果归它管理;registry.ts决定何时加载插件;events.ts在生命周期事件上通知观察者。 - 怎么验证:对照
packages/boot/app-boot/tests/app-boot.spec.ts、config-reload.spec.ts和packages/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.ts、packages/core/agent/tests/invariant.spec.ts和packages/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.json与cordis.patch.yml提供实际层。 - 怎么验证:看
packages/boot/app-boot/tests/app-boot.spec.ts、config-dump.spec.ts、config-reload.spec.ts和user-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.ts、user-patches.spec.ts、hmr-config.spec.ts和config-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.ts和packages/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/agent、core/session和llm;apps/cli/src/profile-boot.ts提供ctx.appExit;examples/headless-agent展示如何启动它。 - 怎么验证:看
apps/cli/tests/headless-shutdown.e2e.ts、source-launch.compat.spec.ts和examples/headless-agent的运行入口,重点看成功、错误和中断是否都结束进程。
packages/bundle/web-app/src/index.ts
- 有什么用:它把 Web Server、静态前端、系统提示词里的 Web 地址和浏览器运行时接到共享插件树上,并处理局域网信任主机等启动选项。
- 为什么这样设计:浏览器只是一个宿主,不应改变 Session、Agent 或 Tools 的核心协议。Web-specific 逻辑集中在 Bundle 里,headless 可以完全不加载它。
- 和谁配合:依赖
host/webserver和host/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、SessionEventMap、SessionEvent、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.ts、invariant.spec.ts、surface.spec.ts、request-header.spec.ts、fork.spec.ts和repair.spec.ts。
packages/core/session/src/preparation.ts
- 有什么用:它只负责持有一个尚未公开的精确
Session,以及可选的 provider-ownedrelease();调用dispose时幂等地释放这部分外部状态。seed、header、事件连续性和恢复数据校验由packages/core/session/src/index.ts及持久化准备层完成。 - 为什么这样设计:Session 在完成 setup 以前不应该进入公开的 Store;把“未发布对象的所有权”和“provider 资源的释放”放进一个小包装器,可以让 publish 前后边界清楚,并让重复 dispose 变成安全的 no-op。
- 和谁配合:
packages/core/session/src/index.ts的Session.create、Session.fromRestore和SessionStore.prepare负责创建或恢复;packages/session/session-persistence/src/preparations.ts协调持久化准备;Agent Loop 在创建或 resume agent 时消费准备结果。 - 怎么验证:看
packages/core/session/tests/session.spec.ts、fork.spec.ts、invariant.spec.ts和properties.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.ts、fork.spec.ts、surface.spec.ts、repair.spec.ts、scoped.spec.ts和typert.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 和发事件;Sessiontypes.ts接收事件扩展;Agent Loop 测试检查队列和取消。 - 怎么验证:看
packages/core/agent/tests/agent.spec.ts、agent-initiator.spec.ts、consumed-work.spec.ts和invariant.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.ts、agent-initiator.spec.ts、model-selection.spec.ts和verify-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.ts、cancel.spec.ts、loop.spec.ts、resume.spec.ts、scope-lifecycle.spec.ts和contract-regressions.spec.ts。
packages/core/agent-loop/src/agent.ts
- 有什么用:它实现具体的
ReactLoopAgent,维护 inbox、Turn/Step 状态、请求模型、接收流、调工具和关闭轮次。 - 为什么这样设计:模型请求和工具调用可能循环多次,而且每个步骤都可能取消、失败或继续。把状态机放在一个明确的 agent runtime 中,才能保证
turn/start、step/start、step/end、turn/end顺序正确,而不是把状态散落到插件回调里。 - 和谁配合:
runtime-context.ts产生模型可见的动态上下文;tool-calls.ts把 assistant 的工具块交给 Tools;Session 记录事件;LLMBlockAssembler组装流;Agent 公共接口向外发布状态。 - 怎么验证:看
packages/core/agent-loop/tests/loop.spec.ts、agent.spec.ts、tool-order.spec.ts、request-reconstruction.spec.ts、request-error.spec.ts、resume.spec.ts和cancel.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.ts、request-reconstruction.spec.ts和resume.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/call和tool/result写入 Session;agent.ts根据返回状态决定继续或结束。 - 怎么验证:看
packages/core/agent-loop/tests/tool-calls.spec.ts、tool-order.spec.ts、request-error.spec.ts和invariant.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/call和tool/result;审批插件监听相应事件。 - 怎么验证:看
packages/core/tools/tests/tools.spec.ts、execution-mode.spec.ts、execution-signal-types.spec.ts、scoped.spec.ts、invariant.spec.ts和properties.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.ts、json-schema.spec.ts、ts-types.spec.ts、py-types.spec.ts和properties.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 把ToolCallView/ToolResultView渲染出来;文件工具、shell 工具和 web 工具提供具体视图。 - 怎么验证:看
packages/core/tools/tests/tools.spec.ts,以及固定版本中确实存在的packages/client/ui-tool/tests/diff-card.client.spec.tsx、packages/client/ui-tool/tests/read-card.client.spec.tsx、packages/client/ui-tool/tests/search-card.client.spec.tsx、packages/client/ui-tool/tests/terminal-card.client.spec.tsx和packages/client/ui-tool/tests/web-card.client.spec.tsx;它们分别帮助核对文件差异、读取、搜索、终端和 Web 结果的呈现。还可以对照各工具包自己的 presentation 测试。
LLM 流和 DeepSeek 适配器
packages/llm/llm/src/index.ts
- 有什么用:它提供
ctx.llm、抽象LlmAdapter、模型注册和llm/streamwaterfall,并导出消息、错误、调用配置和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.ts、assembler.spec.ts、adapter-failure.spec.ts、retry-policy.spec.ts和invariant.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.ts、properties.spec.ts和 Agent Loop 的request-reconstruction.spec.ts。
packages/llm/llm-deepseek/src/adapter.ts
- 有什么用:它把 DSH 的统一 LLM 请求转换成 DeepSeek 兼容的 HTTP chat-completions 请求,再把网络、认证、HTTP 错误和流式响应转成 Harness 的
StreamChunk和LlmError。 - 为什么这样设计:供应商差异应该停在适配器边界。适配器在一次 stream 开始时冻结连接和凭据,处理 idle timeout、取消、重试信息和 request id,避免请求进行到一半时配置变化导致一条请求前后不一致。
- 和谁配合:继承
packages/llm/llm/src/index.ts的LlmAdapter;packages/llm/llm-deepseek/src/sse.ts解帧;packages/llm/llm的错误和消息类型统一故障;packages/llm/llm-deepseek的测试服务器和翻译测试验证 wire 形状。 - 怎么验证:主要看固定版本中
packages/llm/llm-deepseek/tests/adapter.spec.ts、dynamic-config.spec.ts、sse.spec.ts、translate.spec.ts和adapter.e2e.ts;其中adapter-failure.spec.ts与api-key.spec.ts不在 DeepSeek 子包,而是在通用 LLM 测试层的packages/llm/llm/tests/adapter-failure.spec.ts、packages/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.ts、adapter.spec.ts和mock-server.ts;通用流关闭或错误语义再对照packages/llm/llm/tests/adapter-failure.spec.ts。
CLI 入口
apps/cli/src/bin.ts
- 有什么用:这是
dsh命令的最外层入口,读取版本、解析参数,然后按profile、plugin或dump-config动态导入对应模块。 - 为什么这样设计:动态导入让
--help、--version和某个运行模式不必加载所有 Web、headless 和插件代码;入口保持薄,真正的启动逻辑放在对应模块中,也更容易测试。 - 和谁配合:
args.ts负责参数解析;profile-boot.ts负责启动;dump-config.ts负责配置输出;plugin.ts负责插件命令;app-boot提供分层环境。 - 怎么验证:看
apps/cli/tests/args.spec.ts、built-bin.e2e.ts、source-launch.compat.spec.ts、headless-shutdown.e2e.ts和telemetry-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-boot的loadProfile、composeEntries、boot;向树中提供ctx.cmdlineArgs和 configured identities;headless Bundle 通过ctx.appExit请求结束。 - 怎么验证:看
apps/cli/tests/source-launch.compat.spec.ts、built-bin.e2e.ts、headless-shutdown.e2e.ts、windows-shell.spec.ts、web-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 日志与恢复,再沿本页的协作者链接回源码。