@deepseek-ai/dsh-subprocess-local 0.1.2-alpha.5 → 0.1.3-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 +27 -18
- package/README.zh.md +28 -19
- package/lib/index.js +673 -961
- package/lib/runner-launch-COYGu0Dl.js +1623 -0
- package/lib/runner.js +425 -0
- package/lib/types/bin.d.ts +7 -0
- package/lib/types/index.d.ts +17 -11
- package/lib/types/linux-execve.d.ts +9 -0
- package/lib/types/linux-scope.d.ts +81 -0
- package/lib/types/managed-owner.d.ts +30 -0
- package/lib/types/runner-launch.d.ts +66 -0
- package/lib/types/runner-protocol.d.ts +109 -0
- package/lib/types/spawn-runner.d.ts +36 -0
- package/lib/types/spawn.d.ts +41 -15
- package/lib/types/terminal.d.ts +11 -2
- package/lib/types/windows-job.d.ts +30 -0
- package/package.json +15 -8
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: 63d4d5d403d3559778dcad76a8c96cf677f1a977
|
|
6
|
+
README.zh.md: f0a8b629b2ed17b6a6e9385a6eb565443c21ab26
|
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "The local host provider for the subprocess service: run managed
|
|
2
|
+
description: "The local host provider for the subprocess service: run OS-owned managed ranges and real terminal sessions on the host machine, with explicit weaker fallbacks."
|
|
3
3
|
kind: "package-reference"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
|
|
9
9
|
|
|
10
10
|
## Summary
|
|
11
11
|
|
|
12
|
-
Mount `dsh-subprocess-local` in any composition that runs child processes on the host
|
|
12
|
+
Mount `dsh-subprocess-local` in any composition that runs child processes on the host. It resolves local executables, gives ordinary Linux and Windows commands plus supported Linux terminal sessions an OS-owned managed range, and provides real terminal sessions through `node-pty`; unsupported hosts use an explicit weaker fallback. It has no configuration, so every disposition, limit, terminal size, and grace arrives on the spawn request from the calling capability seam. Output collection keeps a bounded in-memory tail with optional spill files for full-stream recovery, children start from a scrubbed environment, and disposal terminates and joins every selected range or session.
|
|
13
13
|
|
|
14
14
|
## Table of Contents
|
|
15
15
|
|
|
@@ -25,7 +25,7 @@ Mount `dsh-subprocess-local` in any composition that runs child processes on the
|
|
|
25
25
|
<a id="use-this-package"></a>
|
|
26
26
|
## Use this package
|
|
27
27
|
|
|
28
|
-
Mount the provider beside its consumers and start processes exactly as the subprocess service specifies; this package decides only how those processes run on the host.
|
|
28
|
+
Mount the provider beside its consumers and start processes exactly as the subprocess service specifies; this package decides only how those processes run on the host. On Windows, non-terminal children and `taskkill` helpers start with their windows hidden so background operations do not take focus. This also hides GUI windows that honor the process startup visibility setting.
|
|
29
29
|
|
|
30
30
|
### Mounting the provider
|
|
31
31
|
|
|
@@ -46,15 +46,15 @@ Collect mode keeps the last `maxBytes` of a stream in memory — errors and fina
|
|
|
46
46
|
|
|
47
47
|
### Running terminal sessions
|
|
48
48
|
|
|
49
|
-
`spawnTerminal` allocates a real PTY and bridges UTF-8 text; you can inspect and signal the current foreground process group and await
|
|
49
|
+
`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
50
|
|
|
51
51
|
### Shutdown behavior
|
|
52
52
|
|
|
53
|
-
Normal disposal terminates every running
|
|
53
|
+
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
54
|
|
|
55
55
|
### What can go wrong
|
|
56
56
|
|
|
57
|
-
An executable that cannot be resolved fails loud with a stable error
|
|
57
|
+
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.
|
|
58
58
|
|
|
59
59
|
-----
|
|
60
60
|
|
|
@@ -68,26 +68,33 @@ This section explains the design decisions behind the provider and points at the
|
|
|
68
68
|
|
|
69
69
|
### Design concept
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
Each spawn selects one owner for both signalling and quiescence. Supported Linux ordinary and terminal launches use transient user-systemd scopes, while supported Windows ordinary launches use a helper-owned kill-on-close Job. macOS, older or unavailable user-systemd, and unavailable Windows native support use the existing detached process-group, `taskkill`, or terminal-session observations with one warning. The provider never replays a command through fallback after a native path may have started it.
|
|
72
72
|
|
|
73
73
|
### Source map
|
|
74
74
|
|
|
75
75
|
| File | Role |
|
|
76
76
|
|---|---|
|
|
77
77
|
| [`src/index.ts`](src/index.ts) | Service wiring: live-handle sets, disposal, host-exit finalization, executable lookup |
|
|
78
|
-
| [`src/spawn.ts`](src/spawn.ts) |
|
|
79
|
-
| [`src/
|
|
78
|
+
| [`src/spawn.ts`](src/spawn.ts) | Shared process plumbing: direct outcomes, tail-keep collection, spill files, and fallback spawning |
|
|
79
|
+
| [`src/managed-owner.ts`](src/managed-owner.ts) | Private signal-and-wait owner used by each ordinary handle |
|
|
80
|
+
| [`src/linux-scope.ts`](src/linux-scope.ts) | Linux user-systemd capability checks, scope launch, signalling, and quiescence |
|
|
81
|
+
| [`src/linux-execve.ts`](src/linux-execve.ts) | Linux libc image replacement and inherited-standard-descriptor preservation |
|
|
82
|
+
| [`src/windows-job.ts`](src/windows-job.ts) | Windows Job capability checks and helper launch |
|
|
83
|
+
| [`src/runner-launch.ts`](src/runner-launch.ts) | Source, built, and packaged private-runner selection |
|
|
84
|
+
| [`src/spawn-runner.ts`](src/spawn-runner.ts) | Linux one-shot exec bootstrap and Windows Job runner |
|
|
85
|
+
| [`src/runner-protocol.ts`](src/runner-protocol.ts) | Strict Linux launch/startup files and Windows IPC messages |
|
|
86
|
+
| [`src/terminal.ts`](src/terminal.ts) | `node-pty` handle: Linux scope attachment, foreground inspection, and fallback cleanup |
|
|
80
87
|
| [`src/process-inspector.ts`](src/process-inspector.ts) | POSIX process-tree and session inspection |
|
|
81
88
|
| [`src/windows-inspector.ts`](src/windows-inspector.ts) | Windows Toolhelp32 process-table inspection via koffi |
|
|
82
89
|
| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. |
|
|
83
90
|
|
|
84
91
|
### Main flow
|
|
85
92
|
|
|
86
|
-
A spawn
|
|
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 only the carrier descriptors. `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.
|
|
87
94
|
|
|
88
95
|
### Safety invariants
|
|
89
96
|
|
|
90
|
-
Spill files are opened `0600` with `O_EXCL` and random names under a `0700` per-process directory, defeating symlink planting in shared temp dirs; a failed final close withholds the spill path.
|
|
97
|
+
Spill files are opened `0600` with `O_EXCL` and random names under a `0700` per-process directory, defeating symlink planting in shared temp dirs; a failed final close withholds the spill path. Fallback process identities carry start times, so cleanup never follows PID reuse. A selected native failure is reported instead of replaying argv through fallback, and a range is removed from the live set only after cleanup completes or the failure remains observable. Host-exit finalization creates no promises or timers, preserves the host exit code and diagnostic, contains each target's failure, and does not claim quiescence.
|
|
91
98
|
|
|
92
99
|
</details>
|
|
93
100
|
|
|
@@ -101,8 +108,8 @@ Read these pages when the provider-level contract is not enough. They move from
|
|
|
101
108
|
- [Subprocess subsystem](../../../docs/subsystems/subprocess.md) — spawn specs, output readers, outcomes, and the `DSH_*` environment in full.
|
|
102
109
|
- [dsh-subprocess](../subprocess/README.md) — the abstract contract this provider implements.
|
|
103
110
|
- [dsh-bash-local](../../shell/bash-local/README.md) — the largest consumer and the concrete stdio shapes it asks for.
|
|
104
|
-
- [Subprocess seam Agent Note](../../../.agents/notes/
|
|
105
|
-
- [Synchronous subprocess exit cleanup](../../../.agents/notes/
|
|
111
|
+
- [Subprocess seam Agent Note](../../../.agents/notes/archived/architecture/2026-07-26-subprocess-seam.md) — why the process half became its own seam.
|
|
112
|
+
- [Synchronous subprocess exit cleanup](../../../.agents/notes/archived/bug-fix/2026-08-11-synchronous-subprocess-exit-cleanup.md) — the host-exit finalization decision and its failure modes.
|
|
106
113
|
|
|
107
114
|
-----
|
|
108
115
|
|
|
@@ -122,12 +129,14 @@ No direct invalidation; the named consumers own any request-prefix changes.
|
|
|
122
129
|
|
|
123
130
|
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.
|
|
124
131
|
|
|
125
|
-
- **
|
|
126
|
-
- **
|
|
127
|
-
- **
|
|
128
|
-
- **
|
|
132
|
+
- **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
|
+
- **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
|
+
- **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.
|
|
135
|
+
- **Windows terminal signalling is console-wide** — SIGINT is delivered as a `\x03` Ctrl-C input write that conhost turns into a console-wide CTRL_C event; SIGTSTP and SIGHUP are rejected as unavailable; a `taskkill` without `/F` does not terminate console processes, so the teardown TERM tier is a grace wait before the `/F` escalation. Windows readiness has no exact stdin-wait tier: the prompt-marker fast path compares the shell pid as the pseudo foreground group, and silence/timing tiers cover the rest.
|
|
136
|
+
- **Fallback terminal ownership remains observational** — on macOS or Linux without usable user-systemd, a child that reparents before any foreground-inspection snapshot or leaves the owned terminal session can escape the process-table scan. The local provider does not add a continuous process-table monitor; supported Linux native mode instead retains these descendants through scope membership.
|
|
137
|
+
- **In-process cleanup requires a JavaScript-observable exit** — direct `process.exit()`, default uncaught exceptions, and default unhandled rejections emit Node's synchronous `exit` event. The default OS disposition for an unhandled `SIGTERM`, `SIGINT`, or `SIGHUP` bypasses that event; an application covers those signals only by installing a handler that performs normal disposal or calls `process.exit()`. `SIGKILL`, fatal OOM, `process.abort()`, native crashes, power loss, and any failure that cannot run JavaScript require an external supervisor, container init, or equivalent OS owner.
|
|
129
138
|
- **The credential scrub is a name heuristic** — `*KEY*`/`*PASSWORD*`/`*SECRET*`/`*TOKEN*` only; differently named secrets (for example `*PASSPHRASE*`) pass through, and a whitelist for over-scrubbed variables is noted future work.
|
|
130
|
-
- **Completed spill files are not deleted** — bounded full-output recovery files
|
|
139
|
+
- **Completed spill files are not deleted** — bounded full-output recovery files accumulate under the OS tmpdir until something external cleans them; the private per-process spill directory is removed at a JavaScript-observable exit only when it holds no completed spill file.
|
|
131
140
|
|
|
132
141
|
<a id="dev-note"></a>
|
|
133
142
|
### Dev Note
|
package/README.zh.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "
|
|
2
|
+
description: "子进程服务的本地宿主提供方:在宿主机器上运行由 OS 所有的受管范围与真实终端会话,并明确披露较弱的 fallback。"
|
|
3
3
|
kind: "package-reference"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -9,7 +9,7 @@ kind: "package-reference"
|
|
|
9
9
|
|
|
10
10
|
## 概述
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
在任何于宿主机上运行子进程的组合中挂载 `dsh-subprocess-local`。它解析本地可执行文件,为普通 Linux 与 Windows 命令以及受支持的 Linux 终端会话提供由 OS 所有的受管范围,并通过 `node-pty` 提供真实终端会话;不受支持的宿主使用明确披露的较弱 fallback。它没有任何配置,因此每项处置方式、限制、终端尺寸与宽限期都随 spawn 请求来自调用方能力 seam。输出收集在内存中保留一段有界尾部,并可选地用 spill 文件恢复完整流;子进程从清理后的环境起步;dispose(资源释放)会终止并等待每个选定范围或会话完全停稳。
|
|
13
13
|
|
|
14
14
|
## 目录
|
|
15
15
|
|
|
@@ -25,7 +25,7 @@ kind: "package-reference"
|
|
|
25
25
|
<a id="use-this-package"></a>
|
|
26
26
|
## 使用本包
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
把提供方与它的消费方挂载在同一组合中,并完全按子进程服务的规定启动进程;本包只决定这些进程在宿主机上如何运行。在 Windows 上,非终端子进程与 `taskkill` 辅助进程会隐藏窗口,因此后台操作不会抢占焦点。遵循进程启动可见性设置的 GUI 窗口也会被隐藏。
|
|
29
29
|
|
|
30
30
|
### 挂载提供方
|
|
31
31
|
|
|
@@ -46,15 +46,15 @@ kind: "package-reference"
|
|
|
46
46
|
|
|
47
47
|
### 运行终端会话
|
|
48
48
|
|
|
49
|
-
`spawnTerminal` 分配真实 PTY 并桥接 UTF-8 文本;你可以检查当前前台进程组并向其发送信号,还可以等待一次 `terminate()
|
|
49
|
+
`spawnTerminal` 分配真实 PTY 并桥接 UTF-8 文本;你可以检查当前前台进程组并向其发送信号,还可以等待一次 `terminate()` 操作。在受支持的 Linux 宿主上,原始终端 argv 直接在 user-systemd scope 内运行;node-pty PID、session leader、控制终端、前台 `inputWaiting` 与就绪状态保持不变,而 scope 会拥有已重新设定父进程或调用 `setsid` 的后代。在 fallback 宿主上,清理会保留根进程树和可观察 session 中的精确身份,但无法重新发现每个已经逃逸的后代。Linux 的精确输入等待要求前台线程的 fd 0 标识 shell 的控制终端,且线程当前的 syscall 正在等待该 fd;如果内核拒绝 syscall 探测,上层 PTY 后端会改用空闲推断。在 Windows 上,SIGINT 以 Ctrl-C 输入写入投递,SIGTSTP 与 SIGHUP 不受支持,拆卸会通过进程表验证 shell 已终止,因为被外部终止的 shell 可能永远不会触发 PTY 退出通知。
|
|
50
50
|
|
|
51
51
|
### 关闭行为
|
|
52
52
|
|
|
53
|
-
正常 dispose
|
|
53
|
+
正常 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
54
|
|
|
55
55
|
### 可能出错的地方
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
无法解析的可执行文件会以稳定错误快速失败。当 spawn 或 provider failure 使 direct outcome 无法产生时,`done` 会 reject;该 rejection 不能证明 target 是否已经开始执行。若所选 owner 无法再证明其范围为空,`waitForExit()` 会 reject,清理仍会尝试终止。越过保留尾部的读取是 `lossy` 的,并在 spill 文件存在时指向它。fallback 进程组或已观察终端 session 可能遗漏在观察前逃逸的后代——见下文限制。
|
|
58
58
|
|
|
59
59
|
-----
|
|
60
60
|
|
|
@@ -68,26 +68,33 @@ kind: "package-reference"
|
|
|
68
68
|
|
|
69
69
|
### 设计理念
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
每次 spawn 都为信号发送与完全停稳选择同一个 owner。受支持的 Linux 普通命令与终端启动使用临时 user-systemd scope,受支持的 Windows 普通命令使用由 helper 持有、关闭时终止成员的 Job。macOS、旧版或不可用的 user-systemd,以及不可用的 Windows 原生支持使用既有 detached 进程组、`taskkill` 或终端 session 观察,并只告警一次。native 路径可能已经启动命令后,本提供方绝不会通过 fallback 重放该命令。
|
|
72
72
|
|
|
73
73
|
### 源码地图
|
|
74
74
|
|
|
75
75
|
| 文件 | 职责 |
|
|
76
76
|
|---|---|
|
|
77
77
|
| [`src/index.ts`](src/index.ts) | 服务接线:存活句柄集合、dispose、宿主退出最终清理、可执行文件查找 |
|
|
78
|
-
| [`src/spawn.ts`](src/spawn.ts) |
|
|
79
|
-
| [`src/
|
|
78
|
+
| [`src/spawn.ts`](src/spawn.ts) | 共享进程管道:直接结果、保尾收集、spill 文件与 fallback spawn |
|
|
79
|
+
| [`src/managed-owner.ts`](src/managed-owner.ts) | 每个普通句柄使用的私有信号与等待 owner |
|
|
80
|
+
| [`src/linux-scope.ts`](src/linux-scope.ts) | Linux user-systemd 能力检查、scope 启动、信号发送与完全停稳 |
|
|
81
|
+
| [`src/linux-execve.ts`](src/linux-execve.ts) | Linux libc 进程映像替换与继承标准文件描述符保留 |
|
|
82
|
+
| [`src/windows-job.ts`](src/windows-job.ts) | Windows Job 能力检查与 helper 启动 |
|
|
83
|
+
| [`src/runner-launch.ts`](src/runner-launch.ts) | source、built 与 packaged 私有 runner 选择 |
|
|
84
|
+
| [`src/spawn-runner.ts`](src/spawn-runner.ts) | Linux 一次性 exec bootstrap 与 Windows Job runner |
|
|
85
|
+
| [`src/runner-protocol.ts`](src/runner-protocol.ts) | 严格的 Linux 启动/错误文件与 Windows IPC 消息 |
|
|
86
|
+
| [`src/terminal.ts`](src/terminal.ts) | `node-pty` 终端句柄:Linux scope 绑定、前台检查与 fallback 清理 |
|
|
80
87
|
| [`src/process-inspector.ts`](src/process-inspector.ts) | POSIX 进程树与会话检查 |
|
|
81
88
|
| [`src/windows-inspector.ts`](src/windows-inspector.ts) | 经 koffi 的 Windows Toolhelp32 进程表检查 |
|
|
82
89
|
| — | 不发布运行时不变式伴生入口;约定归 seam 所有。 |
|
|
83
90
|
|
|
84
91
|
### 主流程
|
|
85
92
|
|
|
86
|
-
一次 spawn
|
|
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、恢复运行,再只关闭 carrier 描述符。`done` 会在 direct command 及其 stdio 屏障结算后完成,`waitForExit()` 则分别等待所选 scope、Job、进程组或已观察 session 变空。
|
|
87
94
|
|
|
88
95
|
### 安全不变式
|
|
89
96
|
|
|
90
|
-
spill 文件以 `0600` 权限、`O_EXCL` 与随机名称在 `0700` 每进程目录下创建,可抵御共享临时目录中的符号链接植入;最终关闭失败时不公布 spill
|
|
97
|
+
spill 文件以 `0600` 权限、`O_EXCL` 与随机名称在 `0700` 每进程目录下创建,可抵御共享临时目录中的符号链接植入;最终关闭失败时不公布 spill 路径。fallback 进程身份携带启动时间,因此清理绝不会跟随 PID 复用。选定的 native 路径失败时会报告错误,而不会通过 fallback 重放 argv;受管范围只有在清理完成后才从存活集合移除,否则失败仍保持可观察。宿主退出最终清理不创建 Promise 或定时器,保留宿主退出码与诊断,分别包含每个目标的失败,也不会声称已经完全停稳。
|
|
91
98
|
|
|
92
99
|
</details>
|
|
93
100
|
|
|
@@ -101,8 +108,8 @@ spill 文件以 `0600` 权限、`O_EXCL` 与随机名称在 `0700` 每进程目
|
|
|
101
108
|
- [子进程子系统](../../../docs/subsystems/subprocess.zh.md)——spawn spec、输出读取器、结果与完整的 `DSH_*` 环境。
|
|
102
109
|
- [dsh-subprocess](../subprocess/README.zh.md)——本提供方实现的抽象约定。
|
|
103
110
|
- [dsh-bash-local](../../shell/bash-local/README.zh.md)——最大的消费方及其请求的具体 stdio 形态。
|
|
104
|
-
- [subprocess seam Agent Note](../../../.agents/notes/
|
|
105
|
-
- [同步子进程退出清理](../../../.agents/notes/
|
|
111
|
+
- [subprocess seam Agent Note](../../../.agents/notes/archived/architecture/2026-07-26-subprocess-seam.md)——进程部分为何成为独立的 seam。
|
|
112
|
+
- [同步子进程退出清理](../../../.agents/notes/archived/bug-fix/2026-08-11-synchronous-subprocess-exit-cleanup.md)——宿主退出最终清理决策及其失败模式。
|
|
106
113
|
|
|
107
114
|
-----
|
|
108
115
|
|
|
@@ -122,12 +129,14 @@ spill 文件以 `0600` 权限、`O_EXCL` 与随机名称在 `0700` 每进程目
|
|
|
122
129
|
|
|
123
130
|
这些限制说明本提供方何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是通用平台对比或任务积压。
|
|
124
131
|
|
|
125
|
-
- **
|
|
126
|
-
- **
|
|
127
|
-
-
|
|
128
|
-
-
|
|
129
|
-
-
|
|
130
|
-
-
|
|
132
|
+
- **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 告警去重会在 provider 生命周期内持续保留。所有探测都会在用户命令可能运行前完成,子进程探测的超时为 5 秒。每次 Linux 启动都会创建私有请求目录,以 50 毫秒间隔检查尚未确定的 scope 建立状态;scope 已建立且仍 active 后,查询间隔按指数增长,最多为 5 秒。Windows 普通命令会保留一个 runner 与一条 IPC 通道,直到 Job 报告活动进程数为零。目标会直接继承标准句柄,不使用 named-pipe stdio 或结果文件。
|
|
134
|
+
- **Windows Job inheritance 有明确排除项**——普通后代默认继承 Job,但 breakaway 进程不在保证范围。目标只在 Job 分配后启动;runner 若在 create-to-assignment 极窄区间遭外力终止,可能留下 suspended target。
|
|
135
|
+
- **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 上,子进程如果在任何前台检查快照之前重新设定父进程,或离开自有终端 session,就可能逃出进程表扫描。本地提供方不会新增持续进程表监视器;受支持的 Linux native 模式改由 scope membership 持有这些后代。
|
|
137
|
+
- **进程内清理要求退出阶段仍能执行 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
|
+
- **凭据清除依赖名称启发式规则**——只匹配 `*KEY*`/`*PASSWORD*`/`*SECRET*`/`*TOKEN*`;名称不同的 secret(例如 `*PASSPHRASE*`)会继续传递,对误删变量引入白名单属于已记录的后续工作。
|
|
139
|
+
- **不会删除已完成的 spill 文件**——有界的完整输出恢复文件会在 OS tmpdir 下累积,直到外部机制进行清理;每进程私有 spill 目录仅在未持有任何已完成 spill 文件时于 JavaScript 可观察的退出阶段删除。
|
|
131
140
|
|
|
132
141
|
<a id="dev-note"></a>
|
|
133
142
|
### 开发备注
|