/** * design/103 — 后台 shell seam(detached process)。「手」腿(`ExecutionEnv`)的 long-running 进程能力:启动一个不会 * 立刻退出的命令、轮询它的输出、需要时杀掉它。让引擎能跑真实开发循环(起 dev server / build watcher → 轮询日志 → * 迭代),用于自足 make-real。 * * 这是一个 **env 上的可选能力**(非一种独立 env 类型),所以不 `extends ExecutionEnv` —— 用交叉类型挂到具体实现上,并经 * {@link hasBackgroundShell} 运行时检测(对齐 remote-env.ts 的 `hasDestroy`/`isRemoteExecutionEnv` 模式,零 vendor 接口改动)。 * * 设计裁定(经 codex + workflow 5-lens 双轨对抗复审收敛,design/103 v2): * - **不跨 durable suspend**:后台进程在每条退出路径被 dispose;Runner 必须在 suspendVM **之前** 调 * {@link BackgroundShellCapability.disposeBackgroundShells}(detached job 不在 suspendVM 的 in-flight 契约射程内)。 * - **越权隔离**:`pollBackground`/`killBackground` 的 shellId MUST 被校验为本 env 自己 spawn 过的;非本 env 走 `not_found`。 * - **按-id-可重读**:实现 MUST 保证按 shellId 跨多次独立调用可重复读取累积/增量输出(execStream 的 consume-once 不满足)。 */ import type { ExecutionEnv, ExecutionEnvExecOptions, Result } from "../internal/harness.js"; /** * 一个 long-running / detached 进程的句柄。**env-local、非 durable、对调用方不透明** —— adapter 内部把它映射到真实进程/ * provider job,**绝不**把可猜的 raw provider job id 暴露成 shellId(否则跨租户可枚举,design/103 §3.8 越权红线)。 */ export type BackgroundShellId = string & { readonly __brand: "BackgroundShellId"; }; /** 一个后台进程的生命周期状态。`exited` 携带 exitCode;`failed` = 启动后异常终止(非被 kill)。 */ export type BackgroundShellStatus = "running" | "exited" | "killed" | "failed"; /** * 类型化错误码(对齐 `RemoteExecutionErrorCode` 风格)。`timeout`/`io` 覆盖 TOB 下的有界 liveness/传输失败 —— 接口规定 * 方法 MUST NOT throw(返回 {@link Result}),所以挂掉的 provider RPC 必须有码可表达、而非违约抛异常。 */ export type BackgroundShellErrorCode = "unsupported" | "not_found" | "spawn_failed" | "limit_exceeded" | "timeout" | "io"; /** 后台 shell 操作的类型化错误。`code` 给程序分类,`message` 给模型自纠(design/103 错误文案纪律)。 */ export declare class BackgroundShellError extends Error { readonly code: BackgroundShellErrorCode; readonly cause?: unknown | undefined; constructor(code: BackgroundShellErrorCode, message: string, cause?: unknown | undefined); } /** * `spawnBackground` 的 options。后台进程只用 `cwd`/`env`/`timeout`;流式回调(`onStdout`/`onStderr`)和 `abortSignal` * 都对「启动后轮询」语义无意义(输出进 tail buffer 等 poll;后台进程的生命周期由 kill/timeout/dispose 管,不绑启动它 * 的工具调用的 signal),故显式 `Omit` 以免实现者误以为传 abortSignal 能约束进程。`timeout` 在后台是**硬墙** * (到点自杀 → status `killed`,design/103 §3.6)。 */ export type BackgroundSpawnOptions = Omit; /** 一次 {@link BackgroundShellCapability.pollBackground} 的返回:自上次 poll 以来的增量 + 当前状态 + cursor 元数据。 */ export interface BackgroundPoll { /** 自上次 poll 后的新增 stdout(已应用 `filter`,且 filter 先于截断 —— design/103 §3.4)。 */ stdout: string; /** 自上次 poll 后的新增 stderr。 */ stderr: string; /** 当前生命周期状态。 */ status: BackgroundShellStatus; /** `status==="exited"` 时的退出码。 */ exitCode?: number; /** * 本 cursor 之前有字节因 **8MB tail buffer 的 head-evict 永久丢失**(配合 {@link bytesDroppedBeforeCursor})。 * 🔴 注意:这是**不可恢复**的丢失,不是「可再 poll 拉取」——进程产出超过 8MB tail 时最早段被挤掉。(单次 poll 增量 * 超 per-poll 显示上限的 head+tail 截断发生在**工具层**,那是另一回事,由工具的截断 marker 诚实标注。) */ truncated?: boolean; /** 从进程启动至今的总输出字节(cursor 元数据,供模型判断是否错过早期日志)。 */ bytesFromStart?: number; /** 因 8MB tail buffer head-evict 在本 cursor 之前**永久丢失**的字节数(诚实标注,不谎称「全部」)。 */ bytesDroppedBeforeCursor?: number; } /** * 「手」腿的后台进程能力。一个具体 `ExecutionEnv` 实现可选地附加它(交叉类型),经 {@link hasBackgroundShell} 检测。 * 不支持的 env(StubExecutionEnv / SSH / ADB)= 不实现(或 `supported:false`),三个后台工具自动不挂(INERT)。 */ export interface BackgroundShellCapability { /** 显式声明能力(像 `RemoteExecutionEnv.capabilities` 一样不猜)。per-env / per-adapter。 */ readonly backgroundCapabilities: { /** 是否真支持后台 spawn;`false` → 三工具不挂(design/103 §3.8)。 */ readonly supported: boolean; /** 单 env 同时存活的后台进程上限(防 fork bomb;超限 spawn 返回 `limit_exceeded`)。 */ readonly maxConcurrent: number; /** 后台默认 timeout(秒)。**per-端下沉**(E2B 沙箱寿命 ≠ TOC 宿主),非单一 core 常量。 */ readonly defaultBgTimeoutSec: number; /** 后台 timeout **硬上限**(秒)。fail-closed 到有限值 —— **绝不允许无界**(design/103 §3.6)。 */ readonly maxBgTimeoutSec: number; /** design/116 detach(飞轮 [C]):env 是否支持把前台 exec 的运行中子进程「领养」为后台(exec options 的 * `detachSignal`)。缺省/false ⇒ detach 请求被忽略(exec 继续前台跑完)。 */ readonly supportsDetach?: boolean; /** design/128 T1-1 留驻声明(TB 2026-07-08 翻红回归修):env 声明后台进程 **outlive the run** —— * 一切**自动**收割路径(runner 每退出路径的 `disposeBackgroundShells`、registry 的 run-teardown settle * 清扫、session release 的 reap)MUST 跳过 kill,把进程留作孤儿(宿主退出后由部署收尸,e.g. TB 容器)。 * **显式** TaskStop/KillShell 与 per-shell timeout 硬墙不受影响(host 在世期间照常工作)。 * 实现侧 `disposeBackgroundShells` 自守 no-op 只护住了 dispose 一条路径;registry 直调 `killBackground` * 的清扫(settleKilledForOwner)在 1.257.3 绕穿了它把 TB 留驻服务全数击杀 —— 声明上浮到能力面,让每个 * 自动收割调用方都看得见。缺省 false = 常规回收行为。 */ readonly retainBackgroundProcesses?: boolean; }; /** * 启动一条命令为后台进程,不等退出,返回 env-local 不透明句柄。MUST NOT block on 进程退出。`timeout` 是硬墙(到点自杀)。 * 后台子进程的 env MUST 经与前台 `exec` **完全相同**的 secret-scrub(design/103 §3.1 红线),cwd 默认 = 追踪的逻辑 cwd。 */ spawnBackground(command: string, options?: BackgroundSpawnOptions): Promise>; /** * 读一个后台进程**自上次 poll 以来的新增**输出 + 当前状态(cursor 语义)。退出后仍可读残余 + exitCode,直到被 dispose/reap。 * * 🔴 越权契约:`shellId` MUST 被校验为**本 env 自己 spawnBackground 返回过**的;非本 env 一律 `not_found`,绝不解析外部 job id。 * 🔴 重读契约:实现 MUST 保证「按 shellId 跨多次独立调用可重复读取累积/增量」(remote 的 execStream consume-once 不满足 —— 见 design/103 §5.2 两条路径)。 */ pollBackground(shellId: BackgroundShellId): Promise>; /** 杀一个后台进程(幂等:杀已死的是 no-op,返回 ok)。`shellId` 同 {@link pollBackground} 的越权校验。 */ killBackground(shellId: BackgroundShellId): Promise>; /** * 杀掉并清理**本 env 的所有**后台进程。Runner 在每条退出路径调:finish/abort/throw 在 run-loop tail finally,**suspend/ * review 必须在 `suspendVM` 之前**(design/103 §3.7;detached job 不在 suspendVM in-flight 契约内,不能指望 adapter 隐式处理)。 * Best-effort,MUST NOT throw(像 `cleanup`/`destroy`)。幂等。 * * 飞轮 [492]② `except`(可选):这些 shellId **留活**(session 驻留 persistent Monitor 的进程 —— 它的全部意义 * 就是跨 turn 存活;run-end 全灭会留下「registry handle 活着、进程死了」的孤儿 watch)。不认识此参数的旧 * 实现照旧全灭 = 今天的行为(诚实降级,not silent corruption:watcher 会打出 env-death 终态通知)。 * suspend/review 前的 dispose **不带** except(挂起整个 VM,进程死亡是既有契约)。 */ disposeBackgroundShells(opts?: { except?: readonly BackgroundShellId[]; }): Promise; } /** * 结构 + 语义检测:`env` 是否暴露后台 shell 能力且声明 `supported:true`。挂载门控用此(对齐 `hasDestroy`)。 * * 单谓词足够:remote-env 刻意拆 `isRemoteExecutionEnv`(结构)/`isSuspendable`(语义)是因为 isolation/suspendable 两轴 * 彼此独立;这里 `supported` 是唯一语义轴,合进一个谓词不会丢信息。 */ export declare function hasBackgroundShell(env: ExecutionEnv): env is ExecutionEnv & BackgroundShellCapability; //# sourceMappingURL=background-shell.d.ts.map