refactor(subagent-subprocess): shared provider skeleton for out-of-process backends

The duplication gate flagged three ACP/SDK clones; the shared halves move
into dsh-subagent-subprocess as provider.ts: NO_START_CAPABILITIES (frozen
all-false advertisement), assertPositiveFinite (prefix-parameterized timing
validation), settleRunResult (never-reject result settlement with contained
onError sink and listener hygiene), and subprocessRunHandle (idempotent
dispose publication). Both backends now compose these; the previously
unreachable cancelled-rejection branch is directly unit-tested at the
library level instead of v8-ignored in each backend. knip learns the
subagent-sdk workspace (e2e entry outside vitest unit includes).
This commit is contained in:
Tianyi Cui
2026-07-27 04:58:11 +08:00
parent 34aabc4183
commit 441110f6da
14 changed files with 340 additions and 96 deletions
@@ -2,7 +2,7 @@
[English](README.md) | 中文
用于**进程外 subagent 后端** 的共享机制:这类提供方会把外部 agent(智能体)作为子进程派生,例如 [ACP 后端](../subagent-acp/README.md)。这是纯库(无提供方、无注册、无 Config),提供所有「派生 CLI 子进程」后端都需要的机制:阻止父级部署凭据进入子进程、把子进程清理至完全停稳,以及将子进程与宿主用户的磁盘 CLI 状态隔离。设计理由见 [Claude Code / Codex subagent 后端 Agent Note](../../../.agents/notes/proposed/feature/2026-07-07-claude-code-and-codex-subagent-backends.md)。
用于**进程外 subagent 后端** 的共享机制:这类提供方会把外部 agent(智能体)作为子进程派生,例如 [ACP 后端](../subagent-acp/README.md)和 [SDK 后端](../subagent-sdk/README.md)。这是纯库(无提供方、无注册、无 Config),提供所有「派生 CLI 子进程」后端都需要的机制:阻止父级部署凭据进入子进程、把子进程清理至完全停稳、解析子进程工作目录、发布接缝 run 句柄,以及将子进程与宿主用户的磁盘 CLI 状态隔离。设计理由见 [Claude Code / Codex subagent 后端 Agent Note](../../../.agents/notes/proposed/feature/2026-07-07-claude-code-and-codex-subagent-backends.md)。
每个可调项都是**参数**:dispose(资源释放)阶梯每次调用时接收宽限时间,配置目录辅助函数接收可选的固定路径。默认值位于各个消费插件的 Config 中(带默认值且经过校验的字段,可从 `cordis.yml` 修改),绝不位于本库。
@@ -28,6 +28,14 @@
退出等待逻辑位于该阶梯内部。无论结算结果如何,它们都会清理自己的 timer 和监听器,因此升级过程不会在子进程上累积监听器。
### `assertUsableCwd` / `validateConfiguredCwd` / `resolveChildCwd`
子进程工作目录解析,被 ACP 与 SDK 后端逐字共享:配置的 `cwd` 覆盖在加载时校验一次(`validateConfiguredCwd`——拒绝空字符串、把相对路径按 harness 启动目录解析、要求可进入的目录),`resolveChildCwd` 在每次 start 应用它,否则校验发起委托的父会话 cwd——绝不用服务器进程自己的 cwd,因为一个服务器进程服务多个会话。`assertUsableCwd` 是底层探针:绝对、存在且可搜索(`X_OK`——子进程 cwd 真正需要的权限;mode-600 目录能过 `isDirectory()` 却让 spawn 以 EACCES 失败)。所有诊断都带消费插件名前缀。
### `NO_START_CAPABILITIES` / `settleRunResult` / `subprocessRunHandle`
每个进程外后端共享的 provider 侧骨架。`NO_START_CAPABILITIES` 是冻结的全 false 能力宣告(进程外子进程无法执行父方强制的启动期特性,服务会在 `start` 之前拒绝此类请求)。`settleRunResult` 在接缝的绝不拒绝契约下定格 run 结果:尝试的拒绝在本地取消已定格时读作 `aborted`,否则经吞掉自身异常的诊断汇压平为 `stopReason: 'error'`,并总是移除 abort 监听器。`subprocessRunHandle` 发布幂等 dispose 的接缝句柄:移除监听器、定格本地取消,然后等待后端的拆除直至真正退出。
### `createIsolatedConfigDir(prefix, pinnedPath?)`
为外部 CLI 子进程创建每次运行独立的隔离配置目录(`CLAUDE_CONFIG_DIR` / `CODEX_HOME` 式重定向的目标),使子进程行为只取决于部署配置,绝不取决于宿主上任何 `~/.claude` / `~/.codex` 式状态。返回一个 `IsolatedConfigDir` 句柄:`path` 写入子进程环境,`remove()` 在 dispose 时运行。