第 7 章:外部入口——两个产品表面与三个协议入口¶
对应 dsh 真实源码:
apps/cli+packages/boot/app-boot+packages/bundle/{headless,web-app,acp-app,sdk-app,sdk-minimal}+packages/{acp,sdk,hooks}。 前置:第 4~6 章。本章产出:miniharness/cli/headless.py、miniharness/web/、miniharness/protocol/{sdk,acp,hooks}.py。
在 system-map 中的位置¶
本章对应 system-map Fig S1 的 Surface_07(体裁:解读)。不要求从零敲内核;对照已实现代码与上游 profile 语义。总览图:Fig S5 · Wire:wire-tour.md · 走查:T3、T4。
7.0 导读¶
- 读法:上游「入口 = 组合树差异」→ mini
cli/main.py+boot/+web/映射 → 用 web 测试与 trace 验证。 - 篇幅:§7.5 wire 细节以 interface-wire.md 为字段单源;本章拆为多文件,下表跳转各节。
| 节 | 主题 | mini 主路径 |
|---|---|---|
| §7.2 | headless 一次性任务 | cli/headless.py、cli/main.py |
| §7.3 | web 表面概览 | web/launcher.py、webui/ |
| §7.4 | SDK / ACP / hooks 入口 | protocol/ |
| §7.5 | Gateway、mux、streams、审批桥 | web/*.py |
| §7.6–§7.8 | 三协议最小子集 | protocol/{sdk,acp,hooks}.py(ACP、hooks) |
| §7.9 | 入口对照总表 | — |
| 走查 | unary / 审批 / SDK | T3、T4、T5 |
7.1 这一章要做什么:一切入口都是 profile¶
前六章讲内核:会话、总线、工具、loop、持久化、扩展口。这一章讲外壳——所有能"启动一个 dsh 进程"的路径,以及它们各自把什么约定暴露给外部。
dsh 没有"web 模式"和"headless 模式"两个程序。它只有一个启动器 dsh(apps/cli/src/args.ts),启动器只解析自己的标志,然后把剩余参数原样交给"被启动的 profile"。profile 是 $DSH_HOME/profiles/<name> 下的一个目录,里面有一个 package.json(声明有序的 bundle 层列表)和一个用户自己的 cordis.patch.yml。每种入口 = 一份不同的插件组合树,入口之间的差异全在组合层,内核一行不改。
启动器自己拥有的东西只有四类(apps/cli/src/args.ts):
--profile <name>:启动哪个 profile(必填)。web:--profile web的硬编码别名。plugin --profile <name> <pnpm args>:把 pnpm 转发进 profile 目录装插件。--dump-config/--dump-default-config:打印组合树并退出,不启动。
被启动的应用自己解析自己的标志(--host、--port、任务文本、--help),所以 dsh --profile web --help 打印的是 web 应用的帮助,不是启动器的。这个"启动器只做薄壳"的分层是理解一切入口的前提。
出厂自带的 profile 模板有五个(packages/boot/app-boot/src/profile.ts):
| profile | bundle 层 | 形态 |
|---|---|---|
web |
dsh-base + dsh-web-app |
浏览器表面,有 Host/HTTP/浏览器插件 |
headless |
dsh-base + dsh-headless |
一次性任务,无任何 Host 层 |
acp |
dsh-base + dsh-acp-app |
ACP stdio 服务器 |
sdk |
dsh-base + dsh-sdk-app |
SDK JSON-RPC 服务器 |
sdk-minimal |
dsh-sdk-minimal |
最小 SDK 组合(不叠 base) |
前四个都叠在 dsh-base 上;sdk-minimal 只带自己的 bundle。dsh-base 是共享的内核底座(persona、工具模式、PTC——programmatic tool calls,程序化工具调用——worker 等)。其它名字的 profile 首次使用不会自动初始化,必须先经 dsh plugin 路径显式创建(initProfile),否则 fail loud。
mini 启动器(miniharness/cli/main.py)¶
miniharness/cli/main.py 复现了启动器的选项语义(同 apps/cli/src/args.ts):
--profile headless "task":一次性任务(§7.2 全部语义);--profile web:启动 FastAPI 服务表层(§7.5,需 fastapi/uvicorn 的[web]extra);未知 profile fail loud。- 无任何参数(无
--profile/--config/--patch)时回退运行demo_main(无 key 端到端演示,main.py)。 --patch <path>(可重复):YAML/JSON overlay 补丁,参与组合层叠与 dump。--dump-config:只读打印最终组合(boot-free,不启动任何应用);--dump-default-config:只打印内置默认组合。两者互斥(program.error同语义);dump 不接受任务参数;--dump-default-config不接受--patch/--config。输出带行级# == <label>来源注释、!!js表达式原样未求值、skipped patch warn 不失败、单文档可再加载(同renderConfigDump)。- 教学扩展(上游没有):
--config <path>指定组合文件(上游用 profile 目录);miniharness sessions子命令(会话列表 / 恢复 / 删除 —— 上游会话管理在 web 表层,见miniharness/cli/session_cmds.py)。 - headless 路径的内置默认组合为空(
cli/headless.py有简化标注);组合层与 headless 运行时解耦:带--config/--patch跑任务时先 boot 验证(fail loud),headless 运行时仍为内置 adapter。