@@ -6,80 +6,80 @@ Status: implemented
## 问题
DeepSeek Harness 需要为 Python 库专门提供一个免 安装 Node、可直接在目标平台运行的 SDK 分发形态:一个单文件可执行程序(下称 exe) , 对外提供 stdio JSON-RPC 服务面 ( `HarnessSdkServer` , Python SDK 的对端),且实际启动的插件与配置完全由 exe 外部输入的 `cordis.yml` 决定。
DeepSeek Harness 需要为 Python 库专门提供一种无需 安装 Node、可直接在目标平台运行的 SDK 分发形态:一个单文件可执行程序(下称 exe) , 通过 stdio 提供 JSON-RPC 对外服务接口 ( `HarnessSdkServer` , Python SDK 的对端),且实际启动的插件与配置完全由 exe 外部输入的 `cordis.yml` 决定。
- 与 Python SDK 通信的 JSONRPC 协议已经过验证
- 需要提供标准化的 cordis.yml 加载所有插件( ESModule )的能力
- 与 Python SDK 通信的 JSON- RPC 协议已经过验证
- 需要提供通过 标准化 ` cordis.yml` 加载所有插件( ES 模块 )的能力
- 分发物要自带 Node 运行时,并支持本地源码链接的调试模式
## 决策
### 打包路线:@yao-pkg/pkg 的 `--sea` 模式
exe 用 [@yao-pkg/pkg ](https://github.com/yao-pkg/pkg )( vercel/pkg 归档后的活跃维护 fork) 的 * * `--sea` ( enhanced SEA) 模式**打包。相比 Node 原生 SEA, pkg 在其上加装 `/snapshot` VFS 与运行时模块钩子, ESM 入口原样交给 Node 默认 ESM loader, 不依赖任何 ESM→CJS 转译。
> 实测( macos-arm64、node24 target 、pkg 6.21.0) : VFS 内裸包名 ESM 动态 import( 含顶层 await) 、CJS 互操作、`node:sqlite`、集合外包名 fail loud 、VFS 外磁盘 ESM import 全部通过,`import.meta.url` 原样为 `file:///snapshot/...`。
exe 使 用 [@yao-pkg/pkg ](https://github.com/yao-pkg/pkg )( vercel/pkg 归档后的活跃维护 fork) 的 * * `--sea` ( enhanced SEA) 模式**打包。相比 Node 原生 SEA, pkg 在其上增 加 `/snapshot` 虚拟文件系统( VFS) 与运行时模块钩子,将 ESM 入口原样交给 Node 默认的 ESM loader, 不依赖任何 ESM→CJS 转译。
> 实测( macos-arm64、node24 构建目标 、pkg 6.21.0) : VFS 内裸包名 ESM 动态 ` import()` (含顶层 ` await` ) 、CJS 互操作、`node:sqlite`、集合外包名明确报错 、VFS 外磁盘 ESM ` import()` 全部通过,`import.meta.url` 原样为 `file:///snapshot/...`。
`--sea` 要求 target ≥ node22, exe 统一以 node24 为 target; 单 次 pkg 调用只打一个 target ,多平台各调一次。
`--sea` 要求构建目标 ≥ node22, exe 统一以 node24 为构建目标;每 次 pkg 调用只打包 一个构建目标 ,多平台各调用 一次。
术语提醒: pkg 的 `/snapshot` VFS 与本仓库测试体系的「snapshot」( ACP replay goldens 、`$DSH_SNAPSHOT` )无关,本文用「 VFS」 指前者。
术语提醒: pkg 的 `/snapshot` VFS 与本仓库测试体系的“快照”( ACP 回放 golden、`$DSH_SNAPSHOT` )无关,本文用“ VFS” 指前者。
### serving 面 是插件: ui/jsonrpc + ui/jsonrpc-agent 两包
### 对外服务接口也 是插件: ui/jsonrpc + ui/jsonrpc-agent 两包
确定性协议实现(`server.ts` / `transport.ts` )按 `ui/acp` + `ui/acp-agent` 的既有模式落为两包——serving 面 本身也是插件:
确定性协议实现(`server.ts` / `transport.ts` )按 `ui/acp` + `ui/acp-agent` 的既有模式落为两包——对外服务接口 本身也是插件:
- [`packages/ui/jsonrpc` ](../../../../packages/ui/jsonrpc/README.md )( `@deepseek-ai/dsh-jsonrpc` ):纯协议插件, apply 时在进程 stdio 上挂 `HarnessSdkServer` + 行式 JSON-RPC transport, disposal 走 `ctx.effect()` 。是否服务由 `cordis.yml` 决定;一份 yml 没挂它就是一个不 serve 的合法进程。协议级退出归插件( `shutdown` 请求应答 后 dispose 自身 fiber 再 `exit(0)` ; HMR 式卸载只停服务不退进程)。
- [`packages/ui/jsonrpc-agent` ](../../../../packages/ui/jsonrpc-agent/README.md )( `@deepseek-ai/dsh-jsonrpc-agent` ) : 薄 app bin ——`installFailLoud` + `loadEnv` + 配置发现 + [`dsh-app-boot` ](../../../../packages/ui/app-boot/src/index.ts ) 的 `boot()` , boot 完即毕, server 由 yml 里 的 `dsh-jsonrpc` 条目带起。依赖只有 app-boot。进程级退出归 bin( stdin EOF/SIGTERM → dispose 后 0, SIGINT → 130) 。
- [`packages/ui/jsonrpc` ](../../../../packages/ui/jsonrpc/README.md )( `@deepseek-ai/dsh-jsonrpc` ):纯协议插件;执行 ` apply` 时, 在进程 stdio 上挂载 `HarnessSdkServer` 与按行传输的 JSON-RPC 层,资源释放 走 `ctx.effect()` 。是否提供 服务由 `cordis.yml` 决定;未挂载该插件的配置会启动一个不提供此服务 的合法进程。协议级退出归插件所有(应答 `shutdown` 请求后 dispose 自身 fiber,再调用 `exit(0)` ; HMR 式卸载只停止 服务, 不退出 进程)。
- [`packages/ui/jsonrpc-agent` ](../../../../packages/ui/jsonrpc-agent/README.md )( `@deepseek-ai/dsh-jsonrpc-agent` ) : 轻量应用入口 ——`installFailLoud` + `loadEnv` + 配置发现 + [`dsh-app-boot` ](../../../../packages/ui/app-boot/src/index.ts ) 的 `boot()` ; ` boot()` 完成后入口即完成,服务器由 `cordis.yml` 中 的 `dsh-jsonrpc` 条目启动。它只依赖 ` app-boot` 。进程级退出归 ` bin` 所有( stdin EOF/SIGTERM → dispose 后返回 0, SIGINT → 130) 。
配置发现两 通道,缺失即报错:`DSH_CORDIS_CONFIG` 环境变量优先 ( SDK 客户端约定) , argv 位置参数次之;无任何 默认路径或内置回退——「 实际启动的插件由外部 cordis.yml 决定」 是硬语义。
配置发现有两个 通道,均 缺失时立 即报错:优先使用 `DSH_CORDIS_CONFIG` 环境变量( SDK 客户端约定),其次使用 argv 位置参数;没有 默认路径或内置回退——“ 实际启动的插件由外部 ` cordis.yml` 决定” 是硬语义。
### 插件解析: VFS 装真实包树,闭包清单即 deploy root
### 插件解析: VFS 装载 真实包树,闭包清单就是部署根目录
exe 的 VFS 内是**构建产物形态的真实包树**(各包 `lib/` + 真实 `node_modules` ) , L oader 解析插件名走 标准动态 `import()` :裸包名从 VFS 内 L oader 位置沿 `node_modules` 向上解析,天 然落在 VFS 内。封闭集不需要白名单代码——集合就是 VFS 里 装了什么,引用集合外的名字 import 失败。
exe 的 VFS 内是**构建产物形态的真实包树**(各包的 `lib/` + 真实 `node_modules` ) 。l oader 通过 标准动态 `import()` 解析插件名 :裸包名从 VFS 内 l oader 所在 位置沿 `node_modules` 向上解析,自 然落在 VFS 内。封闭集不需要白名单代码——VFS 中安 装了什么,集合中就有什么;`import()` 集合外的名称会 失败。
deploy root 是 [`python/sdk-runtime/package.json` ](../../../../python/sdk-runtime/package.json )( `dsh-jsonrpc-agent-pkg` , pnpm workspace 成员、零代码纯依赖清单)——「exe 装什么插件」与「Python runtime 分发什么」的合 一事实源。往 exe 加插件 = 清单 加一行依赖再重 打包。[`scripts/verify-runtime-closure.ts` ](../../../../scripts/verify-runtime-closure.ts ) 遍历该清单覆盖的全部 workspace 包,要求每个非 optional workspace peer 显式列在 runtime root ,并报告「 引用包 → 缺失 peer」 的完整链路; CI static 、pre-push 与 single-exe 构建都会在打包前运行该门禁。deploy 还会按 各包 `files` 打包,因此 tsdown 拆出的共享 chunk 必须被 `files` 覆盖。
部署根目录 是 [`python/sdk-runtime/package.json` ](../../../../python/sdk-runtime/package.json )( `dsh-jsonrpc-agent-pkg` , pnpm 工作区 成员、零代码纯依赖清单), 也是“exe 安装哪些插件”与“Python 运行时 分发什么”的统 一事实源。向 exe 添 加插件,就是在清单中增 加一行依赖后重新 打包。[`scripts/verify-runtime-closure.ts` ](../../../../scripts/verify-runtime-closure.ts ) 遍历该清单覆盖的全部工作区包, 要求每个非可选的工作区对等依赖( peer dependency) 都显式列在运行时根目录 ,并报告“ 引用包 → 缺失对等依赖” 的完整链路; CI 静态检查 、pre-push 与 single-exe 构建都会在打包前运行该门禁。部署还会依据 各包的 `files` 字段 打包,因此 tsdown 拆出的共享分片 必须被 `files` 覆盖。
### 构建管线与产物
[`scripts/build-exe-for-python-sdk.ts` ](../../../../scripts/build-exe-for-python-sdk.ts ): runtime 闭包校验 → `pnpm run build` →(清空后)`pnpm --filter dsh-jsonrpc-agent-pkg deploy --legacy --prod --config.node-linker=hoisted --config.auto-install-peers=false --config.link-workspace-packages=true` **直落 ** `python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/` → 注入 pkg 配置(`bin` 指闭包内 `node_modules/@deepseek-ai/dsh-jsonrpc-agent/lib/bin.js` , `assets` 全量 glob—— 动态 import 对 pkg 静态分析不可见,必须显式全量打入)→ 每 target 一次 `pkg --sea` → 可执行文件 `dsh-jsonrpc-agent-pkg-<platform>-<arch>` 落 `dist-exe/` 并拷回 runtime 目录。CI 把它们 作为测试中间输入,只保留对应的 平台 wheel。deploy 四 flag 均有实测依据:`--legacy` 是未开 inject-workspace-packages 时的必选路径; hoisted 产出零 符号链接文件树( pkg VFS 最稳、 物理保证 c ordis 单 实例);关 peer 自动安装避免未发布包名触发 registry 解析; link-workspace-packages 让闭包指向 workspace /vendor 源。
[`scripts/build-exe-for-python-sdk.ts` ](../../../../scripts/build-exe-for-python-sdk.ts ): 运行时 闭包校验 → `pnpm run build` →(清空后)`pnpm --filter dsh-jsonrpc-agent-pkg deploy --legacy --prod --config.node-linker=hoisted --config.auto-install-peers=false --config.link-workspace-packages=true` **直接写入 ** `python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/` → 注入 pkg 配置(`bin` 指向 闭包内的 `node_modules/@deepseek-ai/dsh-jsonrpc-agent/lib/bin.js` ; `assets` 使用 全量 glob,因为 动态 ` import()` 对 pkg 静态分析不可见,必须显式打入全部内容)→ 每个构建目标调用 一次 `pkg --sea` → 可执行文件 `dsh-jsonrpc-agent-pkg-<platform>-<arch>` 写入 `dist-exe/` , 并拷回运行时 目录。CI 将这些文件 作为测试中间输入,只保留对应平台的 wheel 包。四个部署标志都有实测依据:未启用 ` inject-workspace-packages` 时必须使用 `--legacy` ; ` hoisted` 产出无 符号链接的 文件树(对 pkg VFS 最稳定,并从 物理上 保证只有一个 C ordis 实例);关闭对等依赖 自动安装可 避免未发布包名触发注册表 解析;` link-workspace-packages` 让闭包指向工作区 /vendor 源码 。
CI: [`.github/workflows/build-exe-for-python-sdk.yml` ](../../../../.github/workflows/build-exe-for-python-sdk.yml ), 仅显式触发—— `workflow_dispatch` 手动派发 ,或给 PR 打 `build-exe` 标签; linux-x64 / linux-arm64( `ubuntu-24.04-arm` ) / macos-arm64 三平台原生构建,并缓存 `~/.pkg-cache` ; macOS ad-hoc 签名由 pkg 处理。每个平台都以 mock SSE 模型分别通过默认配置和自定义 `cordis.yml` 驱动 SDK, 再以 NDJSON JSON-RPC 直接驱动 exe, 校验 JSONL 与最终响应, 最后把 release 形态的 wheel 安装到干净 venv 中并在不传 `runtime_bin` 的情况下运行; Linux 还检查 GLIBC 依赖并在 manylinux 2.28 容器中运行。整次运行只保留 4 个产物,每个只含一个发布文件:平台无关的 SDK wheel 与 3 个原生 runtime wheel; 裸 exe 和 源码 bundle 只作为测试中间输入。[`.gitlab-ci.yml` ](../../../../.gitlab-ci.yml ) 只接受版本与根目录 `package.json` 匹配的 `python-vX.Y.Z` tag 流水线,构建一个 SDK wheel 与 3 个原生 runtime wheel, 再由单个串行 job 校验并发布 这 4 个文件到项目 PyPI 注册表。Windows 是非目标 。
CI 使用 [`.github/workflows/build-exe-for-python-sdk.yml` ](../../../../.github/workflows/build-exe-for-python-sdk.yml ), 且只允许显式触发:手动派发 `workflow_dispatch` ,或给 PR 添加 `build-exe` 标签。 linux-x64、 linux-arm64( `ubuntu-24.04-arm` ) 和 macos-arm64 三个 平台分别进行 原生构建,并缓存 `~/.pkg-cache` ; macOS 的 ad-hoc 签名由 pkg 处理。每个平台都使用模拟 SSE 模型, 分别通过默认配置和自定义 `cordis.yml` 驱动 SDK, 再通过 NDJSON JSON-RPC 直接驱动 exe, 校验 JSONL 与最终响应; 最后把发布 形态的 wheel 包 安装到干净的 venv 中, 并在不传 `runtime_bin` 的情况下运行。 Linux 还会 检查 GLIBC 依赖, 并在 manylinux 2.28 容器中运行。整次运行只保留 4 个产物,每个产物 只含一个发布文件:平台无关的 SDK wheel 包和 3 个原生运行时 wheel 包 ;裸 exe 与 源码包 只作为测试中间输入。[`.gitlab-ci.yml` ](../../../../.gitlab-ci.yml ) 只接受版本与根目录 `package.json` 匹配的 `python-vX.Y.Z` 标签 流水线,构建一个 SDK wheel 包和 3 个原生运行时 wheel 包 ,再由单个串行任务 校验并将 这 4 个文件发布 到项目的 PyPI 注册表。Windows 不在目标范围内 。
### Python SDK 分发: 双载体, exe 为生产、node 为 开发
### Python SDK 分发: 双载体, exe 用于生产,`node` 用于 开发
Python SDK 位于 [`python/` ](../../../../python/README.md ): `python/sdk` ( 客户端) + `python/sdk-runtime` ( 运行时载体包) 。runtime 包 数据目录三类内容:检入的默认 `runtime/cordis.yml` 、构建注入的平台 exe、 构建注入的 `runtime/node/` 闭包树。`resolve_bundled_launch_args()` 自动解析**只找 exe**; node 载体仅显式 `DSH_RUNTIME_MODE=node` 启用(跑 `runtime/node/node_modules/@deepseek-ai/dsh-jsonrpc-agent/lib/bin.js` ,需系统 n ode ≥22.19),定位是 本仓库成员的开发验证通道,不进 wheel 分发物 。
Python SDK 位于 [`python/` ](../../../../python/README.md ): `python/sdk` 是 客户端, `python/sdk-runtime` 是 运行时载体包。运行时包的 数据目录包含 三类内容:检入的默认 `runtime/cordis.yml` 、构建注入的平台 exe,以及 构建注入的 `runtime/node/` 闭包树。`resolve_bundled_launch_args()` 的 自动解析**只查 找 exe**; ` node` 载体仅在 显式设置 `DSH_RUNTIME_MODE=node` 时 启用(运行 `runtime/node/node_modules/@deepseek-ai/dsh-jsonrpc-agent/lib/bin.js` ,需要 系统 N ode ≥22.19),定位为 本仓库成员的开发验证通道,不随 wheel 包 分发。
[`scripts/build-python-release.py` ](../../../../scripts/build-python-release.py ) 从仓库根目录 `package.json` 读取权威的稳定 `X.Y.Z` , 并 以该版本暂存两个包,同时 让 SDK 精确依赖 `deepseek-harness-runtime-bin==X.Y.Z` 。可选的 `python-vX.Y.Z` 发布 tag 只是一项一致性断言,与仓库版本不同时会被拒绝;源码 `pyproject.toml` 中的开发占位版本从不决定发布版本。SDK 是 `py3-none-any` wheel; 只提供 wheel 的 runtime 包恰好包含一个 exe, tag 为 `py3-none-manylinux_2_28_x86_64` 、`py3-none-manylinux_2_28_aarch64` 或 `py3-none-macosx_11_0_arm64` 。其 Hatch 钩子拒绝 sdist、通用 tag 、混合可执行载荷以及不支持的平台。
[`scripts/build-python-release.py` ](../../../../scripts/build-python-release.py ) 从仓库根目录的 `package.json` 读取权威的稳定版本 `X.Y.Z` ,以该版本暂存两个包,并 让 SDK 精确依赖 `deepseek-harness-runtime-bin==X.Y.Z` 。可选的 `python-vX.Y.Z` 发布标签 只是一项一致性断言,与仓库版本不同时会被拒绝;源码 `pyproject.toml` 中的开发占位版本从不决定发布版本。SDK 是 `py3-none-any` wheel 包 ;只提供 wheel 包的运行时 包恰好包含一个 exe, 标签 为 `py3-none-manylinux_2_28_x86_64` 、`py3-none-manylinux_2_28_aarch64` 或 `py3-none-macosx_11_0_arm64` 。其 Hatch 钩子拒绝 sdist、通用标签 、混合可执行载荷以及不支持的平台。
exe「 必须显式配置」 的硬语义不变;零配置体验由 wrapper 恢复:调用方没给 `cordis` 、没显式指定 runtime、环境无 `DSH_CORDIS_CONFIG` 时,客户端把 检入的默认 `cordis.yml` ( agent-core + 预载 llm-deepseek + JSONL 持久化 + bash-local + `dsh-jsonrpc` serving 条目, `!!js` 环境变量兜底)显式注入 `DSH_CORDIS_CONFIG` 。
exe“ 必须显式配置” 的硬语义不变;零配置体验由包装层 恢复:调用方没有提供 `cordis` 、没有 显式指定运行时,且环境中没有 `DSH_CORDIS_CONFIG` 时,客户端将 检入的默认 `cordis.yml` ( ` agent-core` + 预载的 ` llm-deepseek` + JSONL 持久化 + ` bash-local` + `dsh-jsonrpc` 对外服务条目,并通过 `!!js` 使用 环境变量兜底)显式注入 `DSH_CORDIS_CONFIG` 。
### 命名血统
`@deepseek-ai/dsh-jsonrpc-agent` (包)→ `dsh-jsonrpc-agent` ( bin) → `dsh-jsonrpc-agent-pkg` (闭包清单;无 scope 前缀,刻意避开 constraints 对 `@deepseek-ai/dsh-*` 的包形状规则)→ `dsh-jsonrpc-agent-pkg-<platform>-<arch>` ( exe 产物)。wire `serverInfo.name` 保持 `deepseek-harness-sdk-runtime` ( 协议稳定值) ; Python dist 名为 `deepseek-harness` / `deepseek-harness-runtime-bin` 。
`@deepseek-ai/dsh-jsonrpc-agent` (包)→ `dsh-jsonrpc-agent` ( ` bin` )→ `dsh-jsonrpc-agent-pkg` (闭包清单;没有作用域 前缀,刻意避开 ` constraints` 对 `@deepseek-ai/dsh-*` 的包形状规则)→ `dsh-jsonrpc-agent-pkg-<platform>-<arch>` ( exe 产物)。协议字段 `serverInfo.name` 保持为 `deepseek-harness-sdk-runtime` ( 协议稳定值) ; Python 分发 名为 `deepseek-harness` / `deepseek-harness-runtime-bin` 。
## 工作线程插件
exe 内支持 `dsh-workflow-workerthread` 与 `dsh-code-runtime-worker` 。两个后端的 构建入口 都通过 `fileURLToPath()` 转换相邻 worker 的 URL, 再将所得文件系统字符串传给 `Worker` ; pkg 的 Worker 钩子可以用这种形式解析 VFS 内文件。工作流引擎在未构建的源码执行中仍保留 data URL 引导程序,只有构建后的相邻入口使用文件系统字符串。自定义配置的可执行文件冒烟测试会加载两个后端,实际调用 `run_code` 与不启动 agent 的 `workflow` ,并要求两个 worker 都从 pkg 的 VFS 内返回 `42` 。
exe 内支持 `dsh-workflow-workerthread` 与 `dsh-code-runtime-worker` 。两个后端构建后的宿主 都通过 `fileURLToPath()` 转换相邻 `lib/worker.cjs` 的 URL, 再将所得文件系统字符串传给 `Worker` ; pkg 的 Worker 钩子可以用这种形式解析 VFS 内文件。该钩子会把 VFS 内的工作线程文件作为 CommonJS 编译,所以工作线程入口采用 CommonJS。 工作流引擎在未构建的源码执行中仍保留 ` data:` URL 引导程序,只有构建后的相邻入口使用文件系统字符串。自定义配置的可执行文件冒烟测试会加载两个后端,实际调用 `run_code` 与不启动 agent 的 `workflow` ,并要求两个工作线程 都从 pkg 的 VFS 内返回 `42` 。
## 测试
验证面分三层。机制层:`--sea` 链路的实测结论内嵌在「 决策」 各节( VFS 内 ESM 动态 import、c ordis 单 实例、fail-loud 配置链路、`node:sqlite` 、macOS ad-hoc 签名可运行) 。SDK 层:完整的 keyless pytest 套件以假运行时对端覆盖客户端协议、子进程清理、绝对 cwd 传递、双载体启动与载体解析;根 CI 在 Python 3.10 上运行全部用例。端到端层:每个平台构建都通过默认 SDK 路径、自定义配置和直接二进制协议对着 mock 端点完成一个轮次,并校验最终文本与 JSONL。自定义配置还会通过打包进 VFS 的真实 worker 文件执行 `run_code` 和不启动 agent 的 `workflow` 。随后把平台 wheel 安装进干净 venv, 在不传 `runtime_bin` 的情况下运行。JSON-RPC 协议不在 ACP snapshot 体系内,无 snapshot 层(点名后的明确 空缺,非遗漏)。
验证面分三层。机制层:`--sea` 链路的实测结论内嵌在“ 决策” 各节( VFS 内 ESM 动态 ` import()` 、单一 C ordis 实例、明确报错的 配置链路、`node:sqlite` 、macOS ad-hoc 签名可运行) 。SDK 层:完整的无密钥 pytest 套件以假运行时对端覆盖客户端协议、子进程清理、绝对 ` cwd` 传递、双载体启动与载体解析;根 CI 在 Python 3.10 上运行全部用例。端到端层:每个平台构建都通过默认 SDK 路径、自定义配置和直接二进制协议,对模拟 端点完成一个轮次,并校验最终文本与 JSONL。自定义配置还会通过打包进 VFS 的真实工作线程 文件执行 `run_code` 和不启动 agent 的 `workflow` 。随后把平台 wheel 包 安装进干净的 venv, 并 在不传 `runtime_bin` 的情况下运行。JSON-RPC 协议不在 ACP 快照体系内,因此没有快照层(这是明确指出的 空缺,并 非遗漏)。
手工驱动注意: bin 视 stdin EOF 为「 客户端已走」 并立即 dispose, 短命管道会中止在飞回合 ——管道驱动必须保持 stdin 打开到回合 结束。
手工驱动注意:` bin` 将 stdin EOF 视为“ 客户端已离开” 并立即 dispose, 短命管道会中止进行中的轮次 ——管道驱动必须保持 stdin 打开,直到轮次 结束。
## 曾考虑的替代方案
**Node 原生 SEA 裸用 。 ** 注入主脚本必须是 CJS 单文件、 blob 内无 文件系统与模块解析,动态 import 裸包名无从解析, 只能把插件静态编译进主脚本并手工注册—— 绕过标准模块解析、插件集合被硬编码 ,与「 配置决定一切」 相悖。最终路线实为「 官方 SEA 地 基 + pkg 的 VFS/模块钩子层」,否掉的是裸用而非 SEA 本身。
**裸用 Node 原生 SEA。 ** 注入的 主脚本必须是 CJS 单文件, blob 内没有 文件系统与模块解析,因此 动态 ` import()` 无法解析裸包名; 只能把插件静态编译进主脚本并手工注册。这会 绕过标准模块解析并硬编码插件集合 ,与“ 配置决定一切” 相悖。最终路线实际是“ 官方 SEA 基础 + pkg 的 VFS/模块钩子层”;否决的是裸用方式,而不是 SEA 本身。
**pkg standard 模式。 ** PoC 判死,非取舍:它把 ESM 经 esbuild 转 CJS + V8 字节码,运行时 vm 编译未接 动态 import 回调,任何 `import()` 一律抛 `ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING` , `--options experimental-require-module` 无效;且 依赖社区补丁 Node 二进制( macos-arm64 无 预编译,现场源码编译约 10 分钟)。对 本仓库架构零可行性 。
**pkg 标准 模式。 ** PoC 证明该模式不可行,而非权衡后放弃:它通过 esbuild 将 ESM 转为 CJS + V8 字节码,但 运行时 VM 编译没有接入 动态 ` import()` 回调,任何 `import()` 都会抛出 `ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING` , `--options experimental-require-module` 也 无效;此外,它 依赖社区补丁版 Node 二进制( macos-arm64 没有 预编译版本 ,现场从 源码编译约需 10 分钟)。该模式不适用于 本仓库架构。
**每包 ESM→CJS 预打包进 VFS。 ** 保持真实解析语义、只降级模块格式的折中;`--sea` 直接通过实测,这层构建复杂度无需引入。
**jsonrpc-agent 背全量 闭包依赖。 ** app bin 声明 53+ 个它不 import 的依赖,「 打包清单」冒充 真实依赖关系,且 迫使 constraints 为其开 cordis-in-dependencies 与 files- 通配两个例外。闭包清单落 在 p ython 侧的清单包上, constraints 无需 任何例外, bin 保持与 acp-agent 同构的正常包形状。
**让 jsonrpc-agent 承担完整 闭包依赖。 ** 应用入口将 声明 53 个以上自身并不 ` import()` 的依赖,使“ 打包清单”伪装成 真实依赖关系,还会 迫使 ` constraints` 为其增加 ` cordis-in-dependencies` 与 ` files` 通配符 两个例外。将 闭包清单放 在 P ython 侧的清单包后,` constraints` 不需要 任何例外,` bin` 也能 保持与 acp-agent 同构的正常包形状。
**开放插件集(磁盘加载用户插件)。 ** 本期封闭集; PoC 顺带证实 VFS 外磁盘 ESM import 可行(经 `ctx.baseUrl` 相对路径通道), 列为后续演进,需另解 外部插件与 exe 内 c ordis 实例的共享问题。
**开放插件集(从 磁盘加载用户插件)。 ** 本期采用 封闭集; PoC 同时证实,可以通过 `ctx.baseUrl` 相对路径通道从 VFS 外的磁盘 `import()` ESM。该能力 列为后续演进,届时还需解决 外部插件与 exe 内 C ordis 实例的共享问题。
## 后果
**买到的 ** :目标平台零依赖单文件分发;插件语义与源码运行严格一致(同一棵真实包树,无转译无注册表);serving 面 、插件集、 配置三者 全部收敛到 `cordis.yml` + 一份依赖清单两个事实源; exe 与 node 双载体同树 同语义,开发验证不必等 打包;官方 Node 二进制消除了补丁二进制供应链顾虑。
**买到的 ** :目标平台零依赖的 单文件分发;插件语义与源码运行严格一致(同一棵真实包树,无转译、 无注册表);对外服务接口 、插件集与 配置全部收敛到 `cordis.yml` 和 一份依赖清单这 两个事实源; exe 与 ` node` 双载体使用同一棵树和相 同语义,开发验证无需等待 打包;官方 Node 二进制消除了补丁版 二进制的 供应链顾虑。
**付出的 ** :产物 174MB 级 且源码原样进 blob( 无 字节码混淆, 闭源分发诉求需另行评估) ; pkg 的 VFS/模块钩子层仍是 社区维护(构建脚本钉死 `@yao-pkg/pkg@6.21.0` ,升级走 显式改动);`--sea` 每个 target 调用一次(与 CI 每平台一腿 匹配,本地多平台构建串行)。
**付出的 ** :产物约 174MB, 且源码原样进入 blob( 没有 字节码混淆; 闭源分发诉求需要 另行评估) ; pkg 的 VFS/模块钩子层仍由 社区维护(构建脚本钉死 `@yao-pkg/pkg@6.21.0` ,升级需要 显式改动);`--sea` 每个构建目标 调用一次(与 CI 每个 平台一个任务相 匹配,本地多平台构建串行执行 )。