From fb0d4ba56434f01cd71efb1e9685702fcd3ae708 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 26 Jul 2026 07:54:42 +0800 Subject: [PATCH] fix(process): close review gaps from Codex round 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Mount LocalProcessManager in the sandbox e2e compositions (bwrap/landlock/ seatbelt + the spine multi-project e2e) and add the spine demo's dsh-process-local devDependency, so SandboxBashExecutor's new inject resolves when those suites are enabled. - Extend the Windows test/coverage skip to packages/process/* — the POSIX process-group suite moved there from packages/bash. - Update the stale disposal contract: the bash seam JSDoc, BashProcess JSDoc, and core bash doc (en+zh) now state that composition teardown (the process manager's disposal) owns kill-and-await, and an executor-only reload leaves background processes running. - Record ctx.processes in the architecture capability table and extension map (en+zh) and the root AGENTS.md layout tree; reword the timeout-library note so it describes where the plumbing and classification live today. --- AGENTS.md | 1 + docs/architecture.i18n.yaml | 4 ++-- docs/architecture.md | 3 ++- docs/architecture.zh.md | 3 ++- docs/cordis-catalog/services.md | 4 ++-- docs/core-data-structures/bash.i18n.yaml | 4 ++-- docs/core-data-structures/bash.md | 7 ++++--- docs/core-data-structures/bash.zh.md | 7 ++++--- packages/bash/bash-sandbox/tests/bwrap.e2e.ts | 2 ++ packages/bash/bash-sandbox/tests/landlock.e2e.ts | 2 ++ packages/bash/bash-sandbox/tests/seatbelt.e2e.ts | 2 ++ packages/bash/bash/src/index.ts | 5 ++++- packages/bash/bash/src/types.ts | 5 +++-- packages/examples/agent-spine-demo/package.json | 1 + .../agent-spine-demo/tests/multi-project-sandbox.e2e.ts | 2 ++ pnpm-lock.yaml | 3 +++ vitest.config.ts | 1 + 17 files changed, 39 insertions(+), 17 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 64259784fb..abcd3e8709 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,6 +15,7 @@ packages/ @deepseek-ai/dsh- workspaces at packages/// prompt/ workspace instructions llm/ LLM seam + the DeepSeek adapters (hand-rolled + pi-ai design twin) bash/ bash executor seam + local impl + model-facing bash tools + process/ child-process manager seam + local process-group impl pty/ persistent PTY seam/backend/tools fs/ filesystem seam + local impl + policy gate + read/write/edit tools lsp/ language-server seam + local stdio provider + model-facing lsp tool diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index 1798b46908..5293177942 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -architecture.md: b426891c0483f42a64b597632cf1871aff79ca2d -architecture.zh.md: 13feefb6854e79ddee38602902d325a789fd7744 +architecture.md: ca2d1c70342fcef35deca7e247863511fb7c35b8 +architecture.zh.md: c3107b23f96497a5ab2184d660d085f3e8a5e527 diff --git a/docs/architecture.md b/docs/architecture.md index b426891c04..ca2d1c7034 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -28,6 +28,7 @@ Harnesses are [Cordis](cordis-primer.md) contexts with package-contributed servi | `ctx.llm` | [`llm/`](../packages/llm/README.md) | adapter registry and streaming model calls | | `ctx.tokenMeter` | [`llm/token-meter`](../packages/llm/token-meter/README.md) | singleton replay-aware request/surface pressure | | `ctx.bash` | [`bash/`](../packages/bash/README.md) | foreground/background command execution | +| `ctx.processes` | [`process/`](../packages/process/README.md) | managed child-process groups under the bash executors | | `ctx.pty` | [`pty/`](../packages/pty/README.md) | owner-scoped persistent terminal sessions | | `ctx.sandbox` | [`sandbox/`](../packages/sandbox/README.md) | same-world process confinement (argv wrapping, per-call policy) | | `ctx.sandboxPolicy` | [`sandbox/`](../packages/sandbox/README.md) | shared sandbox policy home | @@ -177,7 +178,7 @@ New behavior attaches to a documented extension point; a loop change updates thi |---|---| | Add a model provider | register an adapter on `ctx.llm` | | Add a model-facing capability | register on `ctx.tools`; schemas enter prompt assembly | -| Add shell execution | implement and register a `ctx.bash` backend | +| Add shell execution | implement and register a `ctx.bash` backend (the local one spawns through `ctx.processes`) | | Add persistent terminal execution | register a `ctx.pty` backend and `dsh-tool-pty` | | Add a human command | register on `ctx.commands`; adapters discover and dispatch it without a model turn | | Add background work | register on `ctx.tasks`; generic `task_*` tools collect or stop it | diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index 13feefb685..c3107b23f9 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -28,6 +28,7 @@ | `ctx.llm` | [`llm/`](../packages/llm/README.md) | 适配器注册表和模型流式调用 | | `ctx.tokenMeter` | [`llm/token-meter`](../packages/llm/token-meter/README.md) | 感知回放的单实例请求压力和会话表面压力 | | `ctx.bash` | [`bash/`](../packages/bash/README.md) | 前台和后台命令执行 | +| `ctx.processes` | [`process/`](../packages/process/README.md) | bash 执行器之下受管理的子进程组 | | `ctx.pty` | [`pty/`](../packages/pty/README.md) | 按 owner 隔离的持久化终端会话 | | `ctx.sandbox` | [`sandbox/`](../packages/sandbox/README.md) | 同一执行环境内的进程限制(argv 包装、逐调用策略) | | `ctx.sandboxPolicy` | [`sandbox/`](../packages/sandbox/README.md) | 共享沙箱策略归属点 | @@ -177,7 +178,7 @@ forever: |---|---| | 添加模型提供方 | 在 `ctx.llm` 上注册适配器 | | 添加面向模型的功能 | 在 `ctx.tools` 上注册;schema 进入提示词组装流程 | -| 添加 shell 执行 | 实现并注册 `ctx.bash` 后端 | +| 添加 shell 执行 | 实现并注册 `ctx.bash` 后端(本地后端通过 `ctx.processes` 生成进程) | | 添加持久化终端执行 | 注册 `ctx.pty` 后端和 `dsh-tool-pty` | | 添加用户命令 | 在 `ctx.commands` 上注册;适配器无需模型轮次即可发现并分派该命令 | | 添加后台工作 | 在 `ctx.tasks` 上注册;通用 `task_*` 工具负责收集或停止 | diff --git a/docs/cordis-catalog/services.md b/docs/cordis-catalog/services.md index 6ea9919713..b2229130bc 100644 --- a/docs/cordis-catalog/services.md +++ b/docs/cordis-catalog/services.md @@ -257,7 +257,7 @@ Implementations must honor these semantics: - run rejects only for infrastructure failures. Nonzero exits, timeout kills, and abort kills resolve with a BashRunResult. - start returns immediately; no timeout applies to background processes. `done` settles at process close and never rejects; spawn failures settle as `killed` with the error on stderr. - BashProcess.readOutput is incremental: consecutive reads never repeat output. Lossy reads report truncation and available spill files. -- Disposal kills all running background processes and awaits their exit. +- A still-running background process is stopped and awaited when its owning composition tears down. With the process-manager seam that boundary is `ctx.processes` disposal, so a background process survives an executor-only reload. ```ts cordis-catalog /** @@ -286,7 +286,7 @@ abstract start(spec: BashExecSpec): BashProcess Types: [BashExecRequest](../core-data-structures/bash.md) · [BashExecSpec](../core-data-structures/bash.md) · [BashProcess](../core-data-structures/bash.md) · [BashRunResult](../core-data-structures/bash.md) -Source: [`packages/bash/bash/src/index.ts:48`](../../packages/bash/bash/src/index.ts) +Source: [`packages/bash/bash/src/index.ts:51`](../../packages/bash/bash/src/index.ts) ## `ctx.bashEnv` — `BashEnvRegistry` diff --git a/docs/core-data-structures/bash.i18n.yaml b/docs/core-data-structures/bash.i18n.yaml index 0111aa680d..00a77885e8 100644 --- a/docs/core-data-structures/bash.i18n.yaml +++ b/docs/core-data-structures/bash.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -bash.md: e092bfb1bf1aa23f7eeeda41a16a1625d1698201 -bash.zh.md: 7ae60b9f38e6ca5c72e349e141132ae6ec72913d +bash.md: 0c92addf4778fa780cd432444549bfc36eeb763c +bash.zh.md: cd9d94055627937d03107ead5a4ffaeaa945d187 diff --git a/docs/core-data-structures/bash.md b/docs/core-data-structures/bash.md index e092bfb1bf..0c92addf47 100644 --- a/docs/core-data-structures/bash.md +++ b/docs/core-data-structures/bash.md @@ -170,8 +170,9 @@ One more piece completes the vocabulary: the `SANDBOX_UNAVAILABLE` error code (o ```ts type-equiv /** * A background process handle returned by {@link BashExecutor.start}. It is the - * only access path; buffered output remains readable after exit. Executor - * disposal kills running processes and awaits {@link done}. + * only access path; buffered output remains readable after exit. Composition + * teardown (the process manager's disposal) kills running processes and + * awaits {@link done}; an executor-only reload leaves them running. */ interface BashProcess { /** Process lifecycle state (settled exactly once). */ @@ -216,4 +217,4 @@ interface BashProcessRead { ## The service -`BashExecutor` owns `resolve`, foreground `run`, background-process `start`, and the `sandboxMode` capability fact. `dsh-bash-local` owns process groups, timeout/abort handling, bounded collectors, spill files, credential scrubbing, and disposal quiescence. `dsh-tool-bash` owns model-facing rendering and adapts background handles into the [generic task runtime](tasks.md). +`BashExecutor` owns `resolve`, foreground `run`, background-process `start`, and the `sandboxMode` capability fact. `dsh-bash-local` owns command defaulting, timeout/abort classification, the terminal environment, and the background read merge; process groups, bounded collectors, spill files, credential scrubbing, and disposal quiescence are the [process manager](process.md)'s. `dsh-tool-bash` owns model-facing rendering and adapts background handles into the [generic task runtime](tasks.md). diff --git a/docs/core-data-structures/bash.zh.md b/docs/core-data-structures/bash.zh.md index 7ae60b9f38..cd9d940556 100644 --- a/docs/core-data-structures/bash.zh.md +++ b/docs/core-data-structures/bash.zh.md @@ -170,8 +170,9 @@ interface BashSandboxInfo { ```ts type-equiv /** * A background process handle returned by {@link BashExecutor.start}. It is the - * only access path; buffered output remains readable after exit. Executor - * disposal kills running processes and awaits {@link done}. + * only access path; buffered output remains readable after exit. Composition + * teardown (the process manager's disposal) kills running processes and + * awaits {@link done}; an executor-only reload leaves them running. */ interface BashProcess { /** Process lifecycle state (settled exactly once). */ @@ -216,4 +217,4 @@ interface BashProcessRead { ## 服务 -`BashExecutor` 拥有 `resolve`、前台 `run`、后台进程 `start` 以及 `sandboxMode` 能力事实。`dsh-bash-local` 拥有进程组、超时/中止处理、有界收集器、spill 文件、凭据清除以及 dispose(资源释放)后完全停稳。`dsh-tool-bash` 拥有面向模型的渲染,并将后台句柄适配到[通用任务运行时](tasks.md)。 +`BashExecutor` 拥有 `resolve`、前台 `run`、后台进程 `start` 以及 `sandboxMode` 能力事实。`dsh-bash-local` 拥有命令默认值补全、超时/中止分类、终端环境以及后台读取合并;进程组、有界收集器、spill 文件、凭据清除与 dispose(资源释放)后完全停稳归[进程管理器](process.md)所有。`dsh-tool-bash` 拥有面向模型的渲染,并将后台句柄适配到[通用任务运行时](tasks.md)。 diff --git a/packages/bash/bash-sandbox/tests/bwrap.e2e.ts b/packages/bash/bash-sandbox/tests/bwrap.e2e.ts index 87bcffe9df..ad6ecab6f1 100644 --- a/packages/bash/bash-sandbox/tests/bwrap.e2e.ts +++ b/packages/bash/bash-sandbox/tests/bwrap.e2e.ts @@ -9,6 +9,7 @@ import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local' import { SandboxPolicyService } from '@deepseek-ai/dsh-sandbox-policy' import { bwrapProfileArgs } from '@deepseek-ai/dsh-sandbox-local/src/profiles.ts' import { SandboxBashExecutor } from '@deepseek-ai/dsh-bash-sandbox' +import LocalProcessManager from '@deepseek-ai/dsh-process-local' /** * Keyless integration of the real provider and executor through public run/start paths. With @@ -42,6 +43,7 @@ async function sandboxedBash(workspace: string, mode: 'read-only' | 'workspace-w ctx = new Context() await ctx.plugin(LocalSandboxProvider, {}) await ctx.plugin(SandboxPolicyService, { mode, workspaceRoot: workspace }) + await ctx.plugin(LocalProcessManager) await ctx.plugin(SandboxBashExecutor, { cwd: workspace, timeoutMs: 30_000 }) return ctx.bash as SandboxBashExecutor } diff --git a/packages/bash/bash-sandbox/tests/landlock.e2e.ts b/packages/bash/bash-sandbox/tests/landlock.e2e.ts index 3ce944b07c..aac9b766e8 100644 --- a/packages/bash/bash-sandbox/tests/landlock.e2e.ts +++ b/packages/bash/bash-sandbox/tests/landlock.e2e.ts @@ -9,6 +9,7 @@ import { launcherPath } from 'node-addon-landlock-run' import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local' import { SandboxPolicyService } from '@deepseek-ai/dsh-sandbox-policy' import { SandboxBashExecutor } from '@deepseek-ai/dsh-bash-sandbox' +import LocalProcessManager from '@deepseek-ai/dsh-process-local' /** * KEYLESS consumer-integration proof: the REAL `LocalSandboxProvider` (bwrap @@ -47,6 +48,7 @@ async function sandboxedBash(workspace: string, mode: 'read-only' | 'workspace-w await ctx.plugin(LocalSandboxProvider, {}) ;(ctx.sandbox as LocalSandboxProvider).internals = { probeBwrap: () => false } await ctx.plugin(SandboxPolicyService, { mode, workspaceRoot: workspace }) + await ctx.plugin(LocalProcessManager) await ctx.plugin(SandboxBashExecutor, { cwd: workspace, timeoutMs: 30_000 }) return ctx.bash as SandboxBashExecutor } diff --git a/packages/bash/bash-sandbox/tests/seatbelt.e2e.ts b/packages/bash/bash-sandbox/tests/seatbelt.e2e.ts index 6c212ee546..87e76d95e1 100644 --- a/packages/bash/bash-sandbox/tests/seatbelt.e2e.ts +++ b/packages/bash/bash-sandbox/tests/seatbelt.e2e.ts @@ -9,6 +9,7 @@ import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local' import { SandboxPolicyService } from '@deepseek-ai/dsh-sandbox-policy' import { seatbeltProfileArgs } from '@deepseek-ai/dsh-sandbox-local/src/profiles.ts' import { SandboxBashExecutor } from '@deepseek-ai/dsh-bash-sandbox' +import LocalProcessManager from '@deepseek-ai/dsh-process-local' /** * Keyless macOS integration of the real provider and executor through public run/start paths. @@ -41,6 +42,7 @@ async function sandboxedBash(workspace: string, mode: 'read-only' | 'workspace-w await ctx.plugin(LocalSandboxProvider, {}) ;(ctx.sandbox as LocalSandboxProvider).internals = { probeBwrap: () => false, probeLandlock: () => 'unusable' } await ctx.plugin(SandboxPolicyService, { mode, workspaceRoot: workspace }) + await ctx.plugin(LocalProcessManager) await ctx.plugin(SandboxBashExecutor, { cwd: workspace, timeoutMs: 30_000 }) return ctx.bash as SandboxBashExecutor } diff --git a/packages/bash/bash/src/index.ts b/packages/bash/bash/src/index.ts index d9eedee052..ac03e73a91 100644 --- a/packages/bash/bash/src/index.ts +++ b/packages/bash/bash/src/index.ts @@ -43,7 +43,10 @@ declare module 'cordis' { * failures settle as `killed` with the error on stderr. * - {@link BashProcess.readOutput} is incremental: consecutive reads never * repeat output. Lossy reads report truncation and available spill files. - * - Disposal kills all running background processes and awaits their exit. + * - A still-running background process is stopped and awaited when its + * owning composition tears down. With the process-manager seam that + * boundary is `ctx.processes` disposal, so a background process survives + * an executor-only reload. */ export abstract class BashExecutor extends Service { constructor(ctx: Context) { diff --git a/packages/bash/bash/src/types.ts b/packages/bash/bash/src/types.ts index 5928687e93..4fdb11288e 100644 --- a/packages/bash/bash/src/types.ts +++ b/packages/bash/bash/src/types.ts @@ -153,8 +153,9 @@ export interface BashProcessRead { /** * A background process handle returned by {@link BashExecutor.start}. It is the - * only access path; buffered output remains readable after exit. Executor - * disposal kills running processes and awaits {@link done}. + * only access path; buffered output remains readable after exit. Composition + * teardown (the process manager's disposal) kills running processes and + * awaits {@link done}; an executor-only reload leaves them running. */ export interface BashProcess { /** Process lifecycle state (settled exactly once). */ diff --git a/packages/examples/agent-spine-demo/package.json b/packages/examples/agent-spine-demo/package.json index 923a9aace6..6ecca27aed 100644 --- a/packages/examples/agent-spine-demo/package.json +++ b/packages/examples/agent-spine-demo/package.json @@ -65,6 +65,7 @@ "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-paths": "workspace:^", "@deepseek-ai/dsh-llm-retry": "workspace:^", + "@deepseek-ai/dsh-process-local": "workspace:^", "@deepseek-ai/dsh-sandbox-local": "workspace:^", "@deepseek-ai/dsh-sandbox-policy": "workspace:^", "@deepseek-ai/dsh-scope": "workspace:^", diff --git a/packages/examples/agent-spine-demo/tests/multi-project-sandbox.e2e.ts b/packages/examples/agent-spine-demo/tests/multi-project-sandbox.e2e.ts index 11f5421bf9..f22947eb3b 100644 --- a/packages/examples/agent-spine-demo/tests/multi-project-sandbox.e2e.ts +++ b/packages/examples/agent-spine-demo/tests/multi-project-sandbox.e2e.ts @@ -5,6 +5,7 @@ import { basename, join } from 'node:path' import { afterEach, beforeEach, describe, expect, it } from 'vitest' import { Context } from 'cordis' import { SandboxBashExecutor } from '@deepseek-ai/dsh-bash-sandbox' +import LocalProcessManager from '@deepseek-ai/dsh-process-local' import * as FsPolicy from '@deepseek-ai/dsh-fs-policy' import SandboxedFileSystem from '@deepseek-ai/dsh-fs-sandbox' import { CallId } from '@deepseek-ai/dsh-llm' @@ -52,6 +53,7 @@ beforeEach(async () => { ctx = new Context() await ctx.plugin(LocalSandboxProvider, {}) await ctx.plugin(SandboxPolicyService, { mode: 'workspace-write', workspaceRoot: fallbackRoot }) + await ctx.plugin(LocalProcessManager) await ctx.plugin(SandboxBashExecutor, { cwd: fallbackRoot, timeoutMs: 30_000 }) await ctx.plugin(SandboxedFileSystem, { cwd: fallbackRoot }) await ctx.plugin(agentSpine, { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c0471ffc91..340d47c7fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1658,6 +1658,9 @@ importers: '@deepseek-ai/dsh-paths': specifier: workspace:^ version: link:../../util/paths + '@deepseek-ai/dsh-process-local': + specifier: workspace:^ + version: link:../../process/process-local '@deepseek-ai/dsh-sandbox-local': specifier: workspace:^ version: link:../../sandbox/sandbox-local diff --git a/vitest.config.ts b/vitest.config.ts index 0f2b25f627..a0379a325b 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -11,6 +11,7 @@ const windowsUnsupportedPackages = process.platform === 'win32' ? [ 'packages/bash/*', 'packages/hooks/*', + 'packages/process/*', 'packages/pty/pty-local', 'packages/sandbox/sandbox-local', 'packages/sdk/create-sdk',