@deepseek-ai/dsh-subprocess-local 0.1.5-rc.2 → 0.1.6-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +20 -2
- package/README.zh.md +27 -9
- package/lib/index.js +483 -88
- package/lib/output.js +181 -0
- package/lib/{runner-launch-COYGu0Dl.js → runner-launch-DGV26RBf.js} +108 -206
- package/lib/runner.js +21 -10
- package/lib/types/control-spawn.d.ts +19 -0
- package/lib/types/index.d.ts +5 -1
- package/lib/types/linux-execve.d.ts +1 -1
- package/lib/types/linux-scope.d.ts +13 -3
- package/lib/types/managed-owner.d.ts +7 -1
- package/lib/types/output.d.ts +85 -0
- package/lib/types/process-inspector.d.ts +3 -0
- package/lib/types/runner-launch.d.ts +4 -2
- package/lib/types/runner-protocol.d.ts +2 -0
- package/lib/types/shell-activity.d.ts +37 -0
- package/lib/types/spawn-runner.d.ts +1 -1
- package/lib/types/spawn.d.ts +1 -74
- package/lib/types/terminal.d.ts +13 -2
- package/package.json +15 -9
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/subprocess/subprocess-local/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 24480565de2f324a451dfdb40101eb50703b30b6
|
|
6
|
+
README.zh.md: c86a7eb930834d960fa25dd7b5d175af63d0b31a
|
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ Mount the provider beside its consumers and start processes exactly as the subpr
|
|
|
29
29
|
|
|
30
30
|
### Mounting the provider
|
|
31
31
|
|
|
32
|
-
Load the provider in the same composition as its consumers. It has no config fields: every choice arrives on the spawn request, so deployment-varying decisions stay with the caller's configuration.
|
|
32
|
+
Load the provider in the same composition as its consumers. It has no config fields: every choice arrives on the spawn request, so deployment-varying decisions stay with the caller's configuration. `terminalEnvironment()` reads a nonempty `SHELL` on POSIX, falling back to the account login shell, or a nonempty `ComSpec` on Windows. Empty values are omitted so the consumer can choose its platform fallback.
|
|
33
33
|
|
|
34
34
|
```yaml
|
|
35
35
|
- name: '@deepseek-ai/dsh-subprocess-local'
|
|
@@ -40,18 +40,33 @@ Load the provider in the same composition as its consumers. It has no config fie
|
|
|
40
40
|
|
|
41
41
|
Absolute executable paths are verified; bare names resolve against the scrubbed PATH with platform-aware executable extensions (`.COM`/`.EXE`/`.BAT`/`.CMD` on Windows). Relative paths containing separators are rejected — provide an absolute path or a bare PATH name — and relative PATH entries resolve from the host process cwd.
|
|
42
42
|
|
|
43
|
+
Windows ordinary subprocesses start the private Job runner with `windowsHide` and request hidden initial windows for native targets. Standard streams and Job ownership remain independent of window visibility; commands that explicitly create their own windows are outside this guarantee.
|
|
44
|
+
|
|
43
45
|
### Collecting output
|
|
44
46
|
|
|
45
47
|
Collect mode keeps the last `maxBytes` of a stream in memory — errors and final results cluster at the end — and, when a `spill` cap is configured, appends the complete stream to a private file under a per-process directory in the OS temp dir (a `0700` directory, `0600` random-named files). A stream larger than the spill cap discards its incomplete spill and returns only the marked truncated tail. Reads are offset-based and non-consuming, so background and batch readers coexist before and after exit.
|
|
46
48
|
|
|
49
|
+
The `./output` export shares this collector and retained-spill storage with process adapters. `snapshot()` returns the retained raw bytes and total byte count, allowing remote adapters to preserve offsets without forwarding the complete stream.
|
|
50
|
+
|
|
51
|
+
### Control transport
|
|
52
|
+
|
|
53
|
+
An ordinary spawn can request the [subprocess control pipe](../subprocess/README.md#using-a-control-pipe). A Node target receives fd 7 on every supported host; Windows descriptor numbering requires CRT initialization. POSIX runners preserve that descriptor across `execve`; Windows Job and ACL runners establish it in the child's CRT startup table before Node initializes and close their own carrier copies after spawning. Standard streams and the runner's private management channel remain independent.
|
|
54
|
+
|
|
55
|
+
<a id="running-terminal-sessions"></a>
|
|
47
56
|
### Running terminal sessions
|
|
48
57
|
|
|
49
58
|
`spawnTerminal` allocates a real PTY and bridges UTF-8 text; you can inspect and signal the current foreground process group and await one `terminate()` operation. On supported Linux hosts, the original terminal argv runs directly inside a user-systemd scope, preserving the node-pty PID, session leader, controlling terminal, foreground `inputWaiting`, and readiness while the scope owns reparented or `setsid` descendants. On fallback hosts, cleanup retains exact identities from the rooted tree and observable session but cannot recover every escaped descendant. An exact Linux input wait requires a foreground thread whose fd 0 identifies the shell's controlling terminal and whose current syscall waits on that fd; if the kernel denies the syscall probe, the higher PTY backend uses its idle inference instead. On Windows, SIGINT is delivered as a Ctrl-C input write, SIGTSTP and SIGHUP are unsupported, and teardown verifies the shell's termination through the process table because an externally killed shell may never fire the PTY exit notification.
|
|
50
59
|
|
|
60
|
+
With `shellActivity: true`, plain non-login `bash -i` and `zsh -i` launches install private lifecycle records while retaining user startup files and prompt configuration. Bash requires version 4.4 or later and writable prompt hooks; Zsh observes an empty top-level ZLE prompt, excluding `vared`, selection and continuation prompts. Input, shell transitions and changed process observations advance activity revisions. Foreground, background and stopped descendants block idle; native Linux also requires exactly one task in the systemd scope, including ownership beyond the process tree; incomplete process-table scans, custom traps and Zsh asynchronous descriptor handlers yield unknown. A failure to enumerate the process table rejects the observation; activity remains unknown and cleanup retains ownership until a readable table permits verification. Private files are removed after successful process cleanup. Other shells, Windows, custom arguments and sandbox-wrapped executables remain usable with unknown activity.
|
|
61
|
+
|
|
62
|
+
Opted-in root exit does not terminate surviving descendants. A confirmed empty Linux managed range or complete empty Linux session can authorize reclamation of its retained record; macOS cannot confirm an unobserved process range after root exit and keeps that record unknown. The existing fallback visibility limits still apply: shell lifecycle records do not make escaped, unobserved descendants discoverable. Lifecycle records coordinate ordinary shell behavior, not hostile same-user processes.
|
|
63
|
+
|
|
51
64
|
### Shutdown behavior
|
|
52
65
|
|
|
53
66
|
Normal disposal terminates every running managed range and terminal session and awaits quiescence. During a JavaScript-observable host exit — direct `process.exit()`, default uncaught exceptions, default unhandled rejections — synchronous finalization asks a Linux scope to kill its members, kills each Windows runner so its sole Job handle closes, and uses the existing PGID, `taskkill`, or captured-identity operation for fallbacks. It creates no promises or timers and does not claim quiescence. The same exit removes the private per-process spill directory when it holds no completed spill file; completed spill files remain as full-output recovery artifacts until an external cleanup. Unhandled `SIGTERM`/`SIGINT`/`SIGHUP`, `SIGKILL`, fatal OOM, native crashes, and power loss need an external supervisor.
|
|
54
67
|
|
|
68
|
+
Linux ordinary and terminal cancellation preserves the observed termination signal even before the bootstrap consumes its launch request. An unconsumed request still reports startup failure when no matching termination was requested; a recorded pre-exec error always takes precedence. `waitForExit()` independently proves the scope empty, including a scope the manager leaves active with no processes after a payload dies before it enters that scope's cgroup. State queries interrupted by a termination signal are repeated before deciding whether cleanup succeeded. After termination, a consumed launch request and zero scope processes prove quiescence even before the direct-process exit notification. If the final scope signal fails, an accepted direct `SIGKILL` or verified direct-process absence permits one wait for the pending direct-process settlement before rechecking an active range not yet proven empty. This wait is independent of output draining and whole-range settlement. The fresh scope observation must prove the range empty; surviving processes or an unknown process count retain the signal failure.
|
|
69
|
+
|
|
55
70
|
### What can go wrong
|
|
56
71
|
|
|
57
72
|
An executable that cannot be resolved fails loud with a stable error. `done` rejects when spawn or provider failure prevents a direct outcome, and that rejection does not prove whether target execution began. `waitForExit()` rejects if the selected owner can no longer prove its range empty, and cleanup still attempts termination. A read past the retained tail is `lossy` and points at the spill file when one exists. A fallback process group or observed terminal session can miss a descendant that escapes before observation — see the limitations below.
|
|
@@ -90,7 +105,7 @@ Each spawn selects one owner for both signalling and quiescence. Supported Linux
|
|
|
90
105
|
|
|
91
106
|
### Main flow
|
|
92
107
|
|
|
93
|
-
A spawn synchronously validates the final argv, cwd, and environment, selects containment before the user command can run, and returns a handle while target identity remains private. Linux ordinary and terminal launches use a private one-shot request whose scoped bootstrap restores the target cwd and environment, resolves the executable, clears close-on-exec on fd 0 through fd 2, and enters libc `execve()` with the original argv. Windows ordinary launches isolate runner fd 0 through fd 2, reserve fd 3 for IPC, and carry target stdio on fd 4 through fd 6; the runner resolves those CRT descriptors to OS handles, creates the target suspended, assigns it to the Job, resumes it, and closes
|
|
108
|
+
A spawn synchronously validates the final argv, cwd, and environment, selects containment before the user command can run, and returns a handle while target identity remains private. Linux ordinary and terminal launches use a private one-shot request whose scoped bootstrap restores the target cwd and environment, resolves the executable, clears close-on-exec on fd 0 through fd 2 and optional control fd 7, and enters libc `execve()` with the original argv. Windows ordinary launches isolate runner fd 0 through fd 2, reserve fd 3 for IPC, and carry target stdio on fd 4 through fd 6 plus fd 7 when control is requested; the runner resolves those CRT descriptors to OS handles, creates the target suspended, assigns it to the Job, resumes it, and closes its standard-stream carriers and optional fd-7 carrier. `done` settles the direct command after its stdio barrier, while `waitForExit()` separately waits for the selected scope, Job, process group, or observed session to become empty.
|
|
94
109
|
|
|
95
110
|
### Safety invariants
|
|
96
111
|
|
|
@@ -113,6 +128,8 @@ Read these pages when the provider-level contract is not enough. They move from
|
|
|
113
128
|
|
|
114
129
|
-----
|
|
115
130
|
|
|
131
|
+
Terminal allocation advertises the caller-provided `terminalType` through TERM and node-pty. Dynamic resize updates the existing PTY. Output backpressure pauses native reads until the consumer drains; explicit termination resumes a paused reader to receive the exit notification.
|
|
132
|
+
|
|
116
133
|
<a id="model-experience"></a>
|
|
117
134
|
## Model Experience
|
|
118
135
|
|
|
@@ -129,6 +146,7 @@ No direct invalidation; the named consumers own any request-prefix changes.
|
|
|
129
146
|
|
|
130
147
|
These limits define when the provider is a poor fit or needs special operational care. They are current package constraints, not a general platform comparison or a task backlog.
|
|
131
148
|
|
|
149
|
+
- **Linux direct exit has no independent deadline** — after direct `SIGKILL` is acknowledged, uninterruptible kernel I/O can keep disposal pending indefinitely; `graceMs` and scope-query polling budgets do not bound this wait.
|
|
132
150
|
- **Native ownership has explicit host requirements** — Linux needs a readable user manager and `systemd-run --expand-environment=no`; older systemd versions use the warned PGID fallback. macOS always uses that fallback because no supported public persistent owner exists.
|
|
133
151
|
- **Native selection has bounded per-spawn costs** — Linux repeats the bootstrap entry, libc `execve`/`fcntl` bindings, live user manager, and literal-argv scope probe until it first succeeds; later eligible ordinary or terminal spawns recheck only the live user manager. Windows rechecks the runner entry, bindings, and current Job support before every ordinary spawn. Successful Linux deep-probe state and fallback-warning de-duplication persist for the provider lifetime. All probes finish before the user command can run, and child-process probes have a 5-second timeout. Each Linux launch creates a private request directory, checks unresolved scope establishment every 50 milliseconds, then exponentially backs off an established active scope to at most 5 seconds between queries; a Windows ordinary launch keeps one runner and IPC channel until the Job reports zero active processes. Target standard handles are inherited directly, with no named-pipe stdio or result files.
|
|
134
152
|
- **Windows Job inheritance has defined exclusions** — ordinary descendants inherit the Job by default, but breakaway processes are outside the guarantee. The target starts only after Job assignment; external termination of the runner in the narrow create-to-assignment interval can leave a suspended target behind.
|
package/README.zh.md
CHANGED
|
@@ -29,7 +29,7 @@ kind: "package-reference"
|
|
|
29
29
|
|
|
30
30
|
### 挂载提供方
|
|
31
31
|
|
|
32
|
-
在与消费方相同的组合中加载本提供方。它没有任何配置字段:每项选择都随 spawn 请求到达,因此随部署变化的决策留在调用方的配置里。
|
|
32
|
+
在与消费方相同的组合中加载本提供方。它没有任何配置字段:每项选择都随 spawn 请求到达,因此随部署变化的决策留在调用方的配置里。 `terminalEnvironment()` 在 POSIX 读取非空的 `SHELL`,缺失时使用账户登录 shell;在 Windows 读取非空的 `ComSpec`。空值会被省略,由消费者选择平台回退。
|
|
33
33
|
|
|
34
34
|
```yaml
|
|
35
35
|
- name: '@deepseek-ai/dsh-subprocess-local'
|
|
@@ -40,21 +40,36 @@ kind: "package-reference"
|
|
|
40
40
|
|
|
41
41
|
绝对可执行文件路径会被验证;裸名称根据清理后的 PATH 并以平台感知的可执行文件扩展名(Windows 上为 `.COM`/`.EXE`/`.BAT`/`.CMD`)解析。含分隔符的相对路径会被拒绝——请提供绝对路径或裸 PATH 名称——相对 PATH 条目从宿主进程 cwd 解析。
|
|
42
42
|
|
|
43
|
+
Windows 普通子进程通过 `windowsHide` 启动私有 Job runner,并为原生目标请求隐藏初始窗口。标准流和 Job 归属不依赖窗口可见性;显式创建自身窗口的命令不在此保证范围内。
|
|
44
|
+
|
|
43
45
|
### 收集输出
|
|
44
46
|
|
|
45
47
|
收集模式在内存中保留一条流的最后 `maxBytes`——错误与最终结果通常聚集在末尾——并在配置了 `spill` 上限时把完整流追加到 OS 临时目录下每进程目录中的私有文件(`0700` 目录、`0600` 随机命名文件)。某条流大于 spill 上限时,会丢弃不完整的 spill,只返回带截断标记的尾部。读取基于偏移量且从不消费,因此后台读取与批量读取在退出前后都可以共存。
|
|
46
48
|
|
|
49
|
+
`./output` 导出向进程适配器共享该收集器与保留 spill 的存储。`snapshot()` 返回保留的原始字节及总字节数,使远程适配器能够保留偏移量,而无需转发完整的流。
|
|
50
|
+
|
|
51
|
+
### 控制传输
|
|
52
|
+
|
|
53
|
+
普通 spawn 可以请求 [subprocess 控制管道](../subprocess/README.zh.md#using-a-control-pipe)。Node 目标在所有受支持的宿主上均收到 fd 7;Windows 描述符编号依赖 CRT 初始化。POSIX runner 在 `execve` 时保留该描述符;Windows Job 和 ACL runner 在 Node 初始化前通过子进程的 CRT 启动表建立它,并在 spawn 后关闭自身的承载副本。标准流与 runner 的私有管理通道保持独立。
|
|
54
|
+
|
|
55
|
+
<a id="running-terminal-sessions"></a>
|
|
47
56
|
### 运行终端会话
|
|
48
57
|
|
|
49
|
-
`spawnTerminal` 分配真实 PTY 并桥接 UTF-8 文本;你可以检查当前前台进程组并向其发送信号,还可以等待一次 `terminate()` 操作。在受支持的 Linux 宿主上,原始终端 argv 直接在 user-systemd scope 内运行;node-pty PID
|
|
58
|
+
`spawnTerminal` 分配真实 PTY 并桥接 UTF-8 文本;你可以检查当前前台进程组并向其发送信号,还可以等待一次 `terminate()` 操作。在受支持的 Linux 宿主上,原始终端 argv 直接在 user-systemd scope 内运行;node-pty PID、会话 leader、控制终端、前台 `inputWaiting` 与就绪状态保持不变,而 scope 会拥有已重新设定父进程或调用 `setsid` 的后代。在 fallback 宿主上,清理会保留根进程树和可观察会话中的精确身份,但无法重新发现每个已经逃逸的后代。Linux 的精确输入等待要求前台线程的 fd 0 标识 shell 的控制终端,且线程当前的 syscall 正在等待该 fd;如果内核拒绝 syscall 探测,上层 PTY 后端会改用空闲推断。在 Windows 上,SIGINT 以 Ctrl-C 输入写入投递,SIGTSTP 与 SIGHUP 不受支持,拆卸会通过进程表验证 shell 已终止,因为被外部终止的 shell 可能永远不会触发 PTY 退出通知。
|
|
59
|
+
|
|
60
|
+
使用 `shellActivity: true` 时,普通非登录的 `bash -i` 与 `zsh -i` 会安装私有生命周期记录,同时保留用户启动文件和提示符配置。Bash 需要 4.4 或更高版本,并允许写入提示符 hook;Zsh 观察空的顶层 ZLE 提示符,排除 `vared`、选择和续行提示符。输入、shell 状态迁移和进程观察变化都会推进活动 revision。前台、后台及停止的后代任务阻止空闲判断;原生 Linux 还要求 systemd scope 中恰好只有一个 task,覆盖进程树以外仍归其所有的工作;进程表扫描不完整、自定义 trap 和 Zsh 异步文件描述符 handler 会返回 unknown。进程表枚举失败会拒绝本次观察;活动保持 unknown,清理保留所有权,直到进程表可读并能完成验证。进程清理成功后删除私有文件。其他 shell、Windows、自定义参数以及经 sandbox 包装的可执行文件仍可使用,但活动为 unknown。
|
|
61
|
+
|
|
62
|
+
启用后,根进程退出不会终止仍在运行的后代。明确为空的 Linux 受管范围或完整且为空的 Linux 会话可允许回收保留的记录;macOS 无法在根进程退出后确认未观察到的进程范围,会将该记录保持为 unknown。原有 fallback 可见性限制仍然适用:shell 生命周期记录不能让已逃逸且未观察到的后代变得可发现。这些记录协调普通 shell 行为,不用于防御同一用户的恶意进程。
|
|
50
63
|
|
|
51
64
|
### 关闭行为
|
|
52
65
|
|
|
53
66
|
正常 dispose 会终止每个仍在运行的受管范围与终端会话并等待其完全停稳。在 JavaScript 可观察的宿主退出期间——直接 `process.exit()`、默认未捕获异常、默认未处理 rejection——同步最终清理会请求 Linux scope 终止其成员,同步终止每个 Windows runner 以关闭其唯一 Job handle,并为 fallback 使用既有 PGID、`taskkill` 或已捕获身份操作。它不创建 Promise 或定时器,也不声称已经完全停稳。同一退出阶段会删除未持有任何已完成 spill 文件的每进程私有 spill 目录;已完成的 spill 文件作为完整输出恢复产物保留,直到外部机制清理。未处理的 `SIGTERM`/`SIGINT`/`SIGHUP`、`SIGKILL`、fatal OOM、native crash 与断电需要外部 supervisor。
|
|
54
67
|
|
|
68
|
+
Linux 普通进程和终端进程即使在 bootstrap 消费启动请求前被取消,也会保留实际观察到的终止信号。如果没有请求对应的终止信号,未消费的请求仍会报启动失败;已记录的 pre-exec 错误始终优先。`waitForExit()` 独立证明 scope 已为空,其中也包括 payload 在进入该 scope 的 cgroup 前就被杀死、manager 因此让它保持 active 却没有任何进程的 scope。状态查询期间若发出终止信号,会重新查询后再判定清理是否成功。请求终止后,启动请求已消费且 scope 进程数为零即可证明完全停稳,无需等待直接进程的退出通知。如果最终 scope 信号发送失败,直接进程接受 `SIGKILL` 或被独立确认已不存在,owner 才可以等待一次尚未送达的直接进程停稳通知,再重新查询尚未证明为空的 active 范围。此等待独立于输出排空和整个范围的停稳。新取得的 scope 状态必须证明范围已为空;仍有进程存活或进程数未知时,仍报告信号失败。
|
|
69
|
+
|
|
55
70
|
### 可能出错的地方
|
|
56
71
|
|
|
57
|
-
|
|
72
|
+
无法解析的可执行文件会明确报出稳定错误。当 spawn 或提供方故障使 direct outcome 无法产生时,`done` 会 reject;该 rejection 不能证明 target 是否已经开始执行。若所选 owner 无法再证明其范围为空,`waitForExit()` 会 reject,清理仍会尝试终止。越过保留尾部的读取是 `lossy` 的,并在 spill 文件存在时指向它。fallback 进程组或已观察终端 session 可能遗漏在观察前逃逸的后代——见下文限制。
|
|
58
73
|
|
|
59
74
|
-----
|
|
60
75
|
|
|
@@ -68,7 +83,7 @@ kind: "package-reference"
|
|
|
68
83
|
|
|
69
84
|
### 设计理念
|
|
70
85
|
|
|
71
|
-
每次 spawn 都为信号发送与完全停稳选择同一个 owner。受支持的 Linux 普通命令与终端启动使用临时 user-systemd scope,受支持的 Windows 普通命令使用由 helper 持有、关闭时终止成员的 Job。macOS、旧版或不可用的 user-systemd,以及不可用的 Windows 原生支持使用既有 detached 进程组、`taskkill`
|
|
86
|
+
每次 spawn 都为信号发送与完全停稳选择同一个 owner。受支持的 Linux 普通命令与终端启动使用临时 user-systemd scope,受支持的 Windows 普通命令使用由 helper 持有、关闭时终止成员的 Job。macOS、旧版或不可用的 user-systemd,以及不可用的 Windows 原生支持使用既有 detached 进程组、`taskkill` 或终端会话观察,并只告警一次。native 路径可能已经启动命令后,本提供方绝不会通过 fallback 重放该命令。
|
|
72
87
|
|
|
73
88
|
### 源码地图
|
|
74
89
|
|
|
@@ -82,15 +97,15 @@ kind: "package-reference"
|
|
|
82
97
|
| [`src/windows-job.ts`](src/windows-job.ts) | Windows Job 能力检查与 helper 启动 |
|
|
83
98
|
| [`src/runner-launch.ts`](src/runner-launch.ts) | source、built 与 packaged 私有 runner 选择 |
|
|
84
99
|
| [`src/spawn-runner.ts`](src/spawn-runner.ts) | Linux 一次性 exec bootstrap 与 Windows Job runner |
|
|
85
|
-
| [`src/runner-protocol.ts`](src/runner-protocol.ts) |
|
|
100
|
+
| [`src/runner-protocol.ts`](src/runner-protocol.ts) | 严格定义的 Linux launch/startup 文件与 Windows IPC 消息 |
|
|
86
101
|
| [`src/terminal.ts`](src/terminal.ts) | `node-pty` 终端句柄:Linux scope 绑定、前台检查与 fallback 清理 |
|
|
87
102
|
| [`src/process-inspector.ts`](src/process-inspector.ts) | POSIX 进程树与会话检查 |
|
|
88
103
|
| [`src/windows-inspector.ts`](src/windows-inspector.ts) | 经 koffi 的 Windows Toolhelp32 进程表检查 |
|
|
89
|
-
| — |
|
|
104
|
+
| — | 不发布运行时不变式伴生入口;除所属 seam 强制执行的约定外,本包不公开独立的事件序列或可变数据关系。 |
|
|
90
105
|
|
|
91
106
|
### 主流程
|
|
92
107
|
|
|
93
|
-
一次 spawn 会同步校验最终 argv、cwd 与环境,在用户命令可能运行前选择 containment,并在目标身份保持私有的情况下返回句柄。Linux 普通命令与终端启动使用私有的一次性请求;scope 内的 bootstrap 会恢复目标 cwd 与环境、解析可执行文件、清除 fd 0 至 fd 2 的 close-on-exec 标记,再以原始 argv 进入 libc `execve()`。Windows 普通命令会隔离 runner 的 fd 0 至 fd 2、把 fd 3 留给 IPC,并用 fd 4 至 fd 6 承载 target stdio;runner 把这些 CRT 描述符解析成 OS handle,以 suspended 状态创建 target,将其加入 Job
|
|
108
|
+
一次 spawn 会同步校验最终 argv、cwd 与环境,在用户命令可能运行前选择 containment,并在目标身份保持私有的情况下返回句柄。Linux 普通命令与终端启动使用私有的一次性请求;scope 内的 bootstrap 会恢复目标 cwd 与环境、解析可执行文件、清除 fd 0 至 fd 2 及可选控制 fd 7 的 close-on-exec 标记,再以原始 argv 进入 libc `execve()`。Windows 普通命令会隔离 runner 的 fd 0 至 fd 2、把 fd 3 留给 IPC,并用 fd 4 至 fd 6 承载 target stdio,在请求控制时还使用 fd 7;runner 把这些 CRT 描述符解析成 OS handle,以 suspended 状态创建 target,将其加入 Job、恢复运行,再关闭自身的标准流载体及可选 fd-7 载体。`done` 会在 direct command 及其 stdio 屏障结算后完成,`waitForExit()` 则分别等待所选 scope、Job、进程组或已观察会话变空。
|
|
94
109
|
|
|
95
110
|
### 安全不变式
|
|
96
111
|
|
|
@@ -113,6 +128,8 @@ spill 文件以 `0600` 权限、`O_EXCL` 与随机名称在 `0700` 每进程目
|
|
|
113
128
|
|
|
114
129
|
-----
|
|
115
130
|
|
|
131
|
+
终端分配通过 TERM 和 node-pty 使用调用者指定的 `terminalType`。动态 resize 更新已有 PTY。输出背压会暂停原生读取,待消费者排空后恢复;显式终止会恢复暂停的读取,以接收退出通知。
|
|
132
|
+
|
|
116
133
|
<a id="model-experience"></a>
|
|
117
134
|
## 模型体验
|
|
118
135
|
|
|
@@ -129,11 +146,12 @@ spill 文件以 `0600` 权限、`O_EXCL` 与随机名称在 `0700` 每进程目
|
|
|
129
146
|
|
|
130
147
|
这些限制说明本提供方何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是通用平台对比或任务积压。
|
|
131
148
|
|
|
149
|
+
- **Linux 直接进程退出没有独立截止时间**——直接 `SIGKILL` 获得确认后,不可中断的内核 I/O 可能让 dispose 无限期等待;`graceMs` 和 scope 查询的轮询预算不限制此等待。
|
|
132
150
|
- **native ownership 有明确宿主要求**——Linux 需要可读的 user manager 与 `systemd-run --expand-environment=no`;旧版 systemd 使用带告警的 PGID fallback。macOS 因没有受支持的公开 persistent owner,始终使用该 fallback。
|
|
133
|
-
- **native 选择具有有界的每次 spawn 成本**——Linux 会重复检查 bootstrap 入口、libc `execve`/`fcntl` bindings、存活的 user manager 与 literal-argv scope 支持,直到这套完整探测首次成功;后续符合条件的普通命令或终端 spawn 只重新检查存活的 user manager。Windows 会在每次普通 spawn 前重新检查 runner 入口、bindings 与当前 Job 支持。Linux 深度探测的成功状态与 fallback
|
|
151
|
+
- **native 选择具有有界的每次 spawn 成本**——Linux 会重复检查 bootstrap 入口、libc `execve`/`fcntl` bindings、存活的 user manager 与 literal-argv scope 支持,直到这套完整探测首次成功;后续符合条件的普通命令或终端 spawn 只重新检查存活的 user manager。Windows 会在每次普通 spawn 前重新检查 runner 入口、bindings 与当前 Job 支持。Linux 深度探测的成功状态与 fallback 告警去重会在提供方生命周期内持续保留。所有探测都会在用户命令可能运行前完成,子进程探测的超时为 5 秒。每次 Linux 启动都会创建私有请求目录,以 50 毫秒间隔检查尚未确定的 scope 建立状态;scope 已建立且仍 active 后,查询间隔按指数增长,最多为 5 秒。Windows 普通命令会保留一个 runner 与一条 IPC 通道,直到 Job 报告活动进程数为零。目标会直接继承标准句柄,不使用 named-pipe stdio 或结果文件。
|
|
134
152
|
- **Windows Job inheritance 有明确排除项**——普通后代默认继承 Job,但 breakaway 进程不在保证范围。目标只在 Job 分配后启动;runner 若在 create-to-assignment 极窄区间遭外力终止,可能留下 suspended target。
|
|
135
153
|
- **Windows 终端信号是控制台级的**——SIGINT 以 `\x03` Ctrl-C 输入写入投递,由 conhost 转为控制台级 CTRL_C 事件;SIGTSTP 与 SIGHUP 被拒绝(不可用);不带 `/F` 的 `taskkill` 无法终止控制台进程,因此拆卸的 TERM 档是 `/F` 升级前的宽限等待。Windows 就绪没有精确的 stdin-wait 档:prompt-marker 快路径把 shell pid 作为伪前台进程组比较,其余由静默与计时档覆盖。
|
|
136
|
-
- **fallback 终端 ownership 仍依赖观察**——在 macOS 或缺少可用 user-systemd 的 Linux
|
|
154
|
+
- **fallback 终端 ownership 仍依赖观察**——在 macOS 或缺少可用 user-systemd 的 Linux 上,子进程如果在任何前台检查快照之前重新设定父进程,或离开自有终端会话,就可能逃出进程表扫描。本地提供方不会新增持续进程表监视器;受支持的 Linux native 模式改由 scope membership 持有这些后代。
|
|
137
155
|
- **进程内清理要求退出阶段仍能执行 JavaScript**——直接 `process.exit()`、默认未捕获异常和默认未处理 rejection 会发出 Node 同步 `exit` 事件。未安装 handler 时,`SIGTERM`、`SIGINT` 或 `SIGHUP` 的默认 OS 处置不会发出该事件;应用只有安装执行正常 dispose 或调用 `process.exit()` 的 handler 才能覆盖这些信号。`SIGKILL`、fatal OOM、`process.abort()`、native crash、断电,以及任何无法运行 JavaScript 的故障,都需要外部 supervisor、容器 init 或等价的 OS owner 负责。
|
|
138
156
|
- **凭据清除依赖名称启发式规则**——只匹配 `*KEY*`/`*PASSWORD*`/`*SECRET*`/`*TOKEN*`;名称不同的 secret(例如 `*PASSPHRASE*`)会继续传递,对误删变量引入白名单属于已记录的后续工作。
|
|
139
157
|
- **不会删除已完成的 spill 文件**——有界的完整输出恢复文件会在 OS tmpdir 下累积,直到外部机制进行清理;每进程私有 spill 目录仅在未持有任何已完成 spill 文件时于 JavaScript 可观察的退出阶段删除。
|