lark-remote 0.1.12 → 0.2.0
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/CHANGELOG.md +37 -0
- package/README.en.md +2 -6
- package/README.md +3 -7
- package/dist/bridge/final-usage.d.ts +24 -0
- package/dist/bridge/final-usage.js +55 -0
- package/dist/bridge/inbound-media.d.ts +0 -3
- package/dist/bridge/inbound-media.js +0 -1
- package/dist/bridge/index.d.ts +7 -7
- package/dist/bridge/index.js +100 -133
- package/dist/bridge/queue-manager.js +4 -3
- package/dist/card/approval-render.js +39 -94
- package/dist/card/bash-renderer.js +12 -21
- package/dist/card/card-shared.d.ts +14 -5
- package/dist/card/card-shared.js +23 -34
- package/dist/card/run-renderer.d.ts +0 -22
- package/dist/card/run-renderer.js +206 -153
- package/dist/card/run-state.d.ts +29 -1
- package/dist/card/run-state.js +172 -84
- package/dist/card/text-truncate.d.ts +0 -9
- package/dist/card/text-truncate.js +0 -9
- package/dist/card/tool-render.d.ts +4 -1
- package/dist/card/tool-render.js +76 -32
- package/dist/cli.js +2 -2
- package/dist/common/fs.d.ts +14 -0
- package/dist/common/fs.js +57 -3
- package/dist/common/truncate.d.ts +0 -6
- package/dist/common/truncate.js +1 -1
- package/dist/common/ttl-cache.d.ts +24 -0
- package/dist/common/ttl-cache.js +53 -0
- package/dist/config/codex-config.js +14 -11
- package/dist/config/index.d.ts +0 -8
- package/dist/config/index.js +0 -16
- package/dist/config/kimi-config.d.ts +1 -1
- package/dist/config/kimi-config.js +2 -1
- package/dist/config/opencode-config.d.ts +1 -1
- package/dist/config/opencode-config.js +16 -10
- package/dist/config/qr.d.ts +36 -0
- package/dist/config/qr.js +124 -0
- package/dist/config/wizard.js +69 -27
- package/dist/connector/index.d.ts +3 -2
- package/dist/connector/index.js +5 -3
- package/dist/index.js +14 -41
- package/dist/instance-lock.js +7 -0
- package/dist/persistence/atomic-write.d.ts +2 -0
- package/dist/persistence/atomic-write.js +20 -5
- package/dist/persistence/load-json-file.d.ts +1 -2
- package/dist/persistence/load-json-file.js +2 -3
- package/dist/platform/agent-stopper.d.ts +22 -0
- package/dist/platform/agent-stopper.js +19 -0
- package/dist/platform/command.d.ts +11 -0
- package/dist/platform/command.js +107 -0
- package/dist/platform/identity.d.ts +33 -0
- package/dist/platform/identity.js +187 -0
- package/dist/platform/path.d.ts +24 -0
- package/dist/platform/path.js +110 -0
- package/dist/platform/select.d.ts +9 -0
- package/dist/platform/select.js +12 -0
- package/dist/platform/shell.d.ts +51 -0
- package/dist/platform/shell.js +75 -0
- package/dist/platform/spawn.d.ts +41 -0
- package/dist/platform/spawn.js +77 -0
- package/dist/platform/terminator-posix.d.ts +38 -0
- package/dist/platform/terminator-posix.js +103 -0
- package/dist/platform/terminator-win32.d.ts +21 -0
- package/dist/platform/terminator-win32.js +110 -0
- package/dist/platform/terminator.d.ts +44 -0
- package/dist/platform/terminator.js +24 -0
- package/dist/platform/types.d.ts +23 -0
- package/dist/platform/types.js +5 -0
- package/dist/restart.js +4 -2
- package/dist/router/card-helpers.d.ts +33 -6
- package/dist/router/card-helpers.js +67 -11
- package/dist/router/config/common/render.d.ts +1 -1
- package/dist/router/config/common/render.js +1 -1
- package/dist/router/config-diff.d.ts +4 -0
- package/dist/router/config-diff.js +70 -0
- package/dist/router/index.d.ts +73 -6
- package/dist/router/index.js +663 -590
- package/dist/router/utils.d.ts +6 -0
- package/dist/router/utils.js +12 -0
- package/dist/runner/bash/runner.d.ts +9 -2
- package/dist/runner/bash/runner.js +45 -20
- package/dist/runner/claude/session.d.ts +4 -4
- package/dist/runner/claude/session.js +18 -9
- package/dist/runner/codex/app-server/protocol-types.d.ts +35 -1
- package/dist/runner/codex/app-server/runner.d.ts +1 -16
- package/dist/runner/codex/app-server/runner.js +2 -2
- package/dist/runner/codex/app-server/translator.d.ts +18 -0
- package/dist/runner/codex/app-server/translator.js +146 -0
- package/dist/runner/common/acp/base-acp-runner.d.ts +116 -0
- package/dist/runner/common/acp/base-acp-runner.js +248 -0
- package/dist/runner/common/acp/base-translator.d.ts +141 -0
- package/dist/runner/common/acp/base-translator.js +271 -0
- package/dist/runner/common/acp/protocol-helpers.d.ts +70 -0
- package/dist/runner/common/acp/protocol-helpers.js +82 -0
- package/dist/runner/common/connection-based-runner.d.ts +0 -2
- package/dist/runner/common/connection-based-runner.js +1 -1
- package/dist/runner/common/jsonrpc/transport.d.ts +2 -0
- package/dist/runner/common/jsonrpc/transport.js +25 -5
- package/dist/runner/common/process-stopper.d.ts +4 -26
- package/dist/runner/common/process-stopper.js +4 -76
- package/dist/runner/common/runner-utils.d.ts +0 -5
- package/dist/runner/common/runner-utils.js +0 -7
- package/dist/runner/common/spawning-runner.d.ts +42 -132
- package/dist/runner/common/spawning-runner.js +52 -271
- package/dist/runner/common/usage.d.ts +2 -0
- package/dist/runner/common/usage.js +2 -0
- package/dist/runner/dsh/index.d.ts +0 -2
- package/dist/runner/dsh/index.js +0 -2
- package/dist/runner/dsh/runner.d.ts +0 -2
- package/dist/runner/dsh/runner.js +1 -1
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +63 -53
- package/dist/runner/kimi/acp/runner.js +189 -334
- package/dist/runner/kimi/acp/translator.d.ts +18 -101
- package/dist/runner/kimi/acp/translator.js +61 -255
- package/dist/runner/opencode/acp/runner.d.ts +22 -46
- package/dist/runner/opencode/acp/runner.js +74 -297
- package/dist/runner/opencode/acp/translator.d.ts +28 -104
- package/dist/runner/opencode/acp/translator.js +54 -249
- package/dist/runner/pi/rpc/index.d.ts +0 -2
- package/dist/runner/pi/rpc/index.js +0 -2
- package/dist/runner/pi/rpc/translator.d.ts +3 -0
- package/dist/runner/pi/rpc/translator.js +47 -9
- package/dist/runner/probe.d.ts +8 -11
- package/dist/runner/probe.js +17 -21
- package/dist/runner/registry.d.ts +4 -4
- package/dist/runner/registry.js +4 -4
- package/dist/runner/resolve-agent-choices.js +6 -2
- package/dist/runner/sync-agent-choices.js +1 -1
- package/dist/runner/types.d.ts +38 -1
- package/dist/session/claude/session-index.js +3 -1
- package/dist/session/claude/sessions.js +37 -64
- package/dist/session/codex/rollout-reader.d.ts +10 -14
- package/dist/session/codex/rollout-reader.js +126 -48
- package/dist/session/common/constants.d.ts +7 -0
- package/dist/session/common/constants.js +9 -0
- package/dist/session/common/pagination.d.ts +0 -2
- package/dist/session/common/pagination.js +1 -1
- package/dist/session/common/recency.d.ts +7 -0
- package/dist/session/common/recency.js +10 -0
- package/dist/session/common/two-pass.d.ts +22 -0
- package/dist/session/common/two-pass.js +70 -0
- package/dist/session/common/usage-accumulator.d.ts +14 -0
- package/dist/session/common/usage-accumulator.js +15 -0
- package/dist/session/dsh/sessions.js +15 -11
- package/dist/session/kimi/sessions.d.ts +24 -4
- package/dist/session/kimi/sessions.js +99 -103
- package/dist/session/opencode/sessions.d.ts +2 -14
- package/dist/session/opencode/sessions.js +35 -15
- package/dist/session/pi/sessions.js +64 -95
- package/dist/update/index.d.ts +2 -3
- package/dist/update/index.js +1 -2
- package/dist/update/install.d.ts +2 -1
- package/dist/update/install.js +58 -9
- package/dist/update/version-check.d.ts +5 -0
- package/dist/update/version-check.js +9 -0
- package/package.json +9 -7
- package/dist/update/startup-hint.d.ts +0 -5
- package/dist/update/startup-hint.js +0 -11
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pid 身份验证(design.md §3.4 / §3.5)。
|
|
3
|
+
*
|
|
4
|
+
* POSIX 现状用 `ps -o command=` 读命令行,Windows 没有 ps,等价物是 CIM:
|
|
5
|
+
* PowerShell `Get-CimInstance Win32_Process` 取 CommandLine,并额外取
|
|
6
|
+
* CreationDate 做 pid 复用加固(Windows pid 复用快)。
|
|
7
|
+
*
|
|
8
|
+
* 纪律:
|
|
9
|
+
* - 只在查询失败/身份不匹配时返回 null / false,绝不抛给调用方——调用方
|
|
10
|
+
* (killOrphan、实例锁陈旧判定)一律「只有身份匹配才动手」;
|
|
11
|
+
* - win32 每次查询要拉起 PowerShell(冷启动约 1s),批量扫 pid 文件的场景
|
|
12
|
+
* (killOrphan)应按需调用,真机矩阵(M0)需实测整体耗时;
|
|
13
|
+
* - platform 显式注入,win32 语义可在任意宿主上单测。
|
|
14
|
+
*/
|
|
15
|
+
import { spawn } from 'node:child_process';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { currentPlatform, isWin32 } from './select.js';
|
|
18
|
+
const DEFAULT_TIMEOUT_MS = 3000;
|
|
19
|
+
/** 拉起一次性查询进程并收全 stdout;超时/拉起失败/进程报错一律 null。 */
|
|
20
|
+
function collect(file, args, timeoutMs) {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
let proc;
|
|
23
|
+
try {
|
|
24
|
+
// stderr 用 ignore:查询进程的错误输出我们不需要,pipe 而不消费在极端
|
|
25
|
+
// 情况下(管道缓冲满)会阻塞子进程,把一次性查询拖成挂起。
|
|
26
|
+
// windowsHide:避免查询进程在桌面闪控制台窗口
|
|
27
|
+
proc = spawn(file, args, { stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
resolve(null);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
let stdout = '';
|
|
34
|
+
let settled = false;
|
|
35
|
+
const finish = (result) => {
|
|
36
|
+
if (settled)
|
|
37
|
+
return;
|
|
38
|
+
settled = true;
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
resolve(result);
|
|
41
|
+
};
|
|
42
|
+
proc.stdout?.setEncoding('utf8');
|
|
43
|
+
proc.stdout?.on('data', (chunk) => {
|
|
44
|
+
stdout += chunk;
|
|
45
|
+
});
|
|
46
|
+
proc.on('error', () => finish(null));
|
|
47
|
+
proc.on('exit', (code) => finish({ code, stdout }));
|
|
48
|
+
const timer = setTimeout(() => {
|
|
49
|
+
proc.kill();
|
|
50
|
+
finish(null);
|
|
51
|
+
}, timeoutMs);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** 解析 CIM 输出(ConvertTo-Json 形态):单对象或数组,取首条。 */
|
|
55
|
+
function parseCimIdentity(stdout) {
|
|
56
|
+
const text = stdout.trim();
|
|
57
|
+
if (!text)
|
|
58
|
+
return null;
|
|
59
|
+
let parsed;
|
|
60
|
+
try {
|
|
61
|
+
parsed = JSON.parse(text);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const first = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
67
|
+
if (!first || typeof first !== 'object')
|
|
68
|
+
return null;
|
|
69
|
+
const record = first;
|
|
70
|
+
// 权限不足时 CIM 会给出 null 字段:命令行拿不到就没有身份可言
|
|
71
|
+
const commandLine = typeof record.CommandLine === 'string' ? record.CommandLine.trim() : '';
|
|
72
|
+
if (!commandLine)
|
|
73
|
+
return null;
|
|
74
|
+
const rawDate = typeof record.CreationDate === 'string' ? record.CreationDate.trim() : '';
|
|
75
|
+
return rawDate ? { commandLine, creationDate: rawDate } : { commandLine };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 查询进程身份;进程不存在或查询失败返回 null。
|
|
79
|
+
*/
|
|
80
|
+
export async function queryProcessIdentity(pid, opts) {
|
|
81
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
82
|
+
return null;
|
|
83
|
+
const platform = opts?.platform ?? currentPlatform;
|
|
84
|
+
const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
85
|
+
if (isWin32(platform)) {
|
|
86
|
+
const result = await collect('powershell.exe', [
|
|
87
|
+
'-NoProfile',
|
|
88
|
+
'-NonInteractive',
|
|
89
|
+
'-Command',
|
|
90
|
+
`Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}" ` +
|
|
91
|
+
// -Compress:单行输出,避免 PowerShell 按控制台宽度换行给解析添变数
|
|
92
|
+
'| Select-Object -Property CommandLine,CreationDate | ConvertTo-Json -Compress',
|
|
93
|
+
], timeoutMs);
|
|
94
|
+
return result ? parseCimIdentity(result.stdout) : null;
|
|
95
|
+
}
|
|
96
|
+
const result = await collect('ps', ['-o', 'command=', '-p', String(pid)], timeoutMs);
|
|
97
|
+
if (!result || result.code !== 0)
|
|
98
|
+
return null;
|
|
99
|
+
const commandLine = result.stdout.split('\n')[0]?.trim() ?? '';
|
|
100
|
+
return commandLine ? { commandLine } : null;
|
|
101
|
+
}
|
|
102
|
+
function caseInsensitive(platform) {
|
|
103
|
+
// 与 PathKit.samePath 同口径:win32/darwin 大小写不敏感,linux 严格
|
|
104
|
+
return isWin32(platform) || platform === 'darwin';
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 引号感知的命令行 tokenizer:双引号段不按空白拆分。CIM 的 CommandLine 里
|
|
108
|
+
* "C:\Program Files\nodejs\node.exe" 这类含空格路径必须整段保留,朴素
|
|
109
|
+
* split(/\s+/) 会切碎 token 导致身份假阴性。
|
|
110
|
+
*/
|
|
111
|
+
export function tokenizeCommandLine(commandLine) {
|
|
112
|
+
const tokens = [];
|
|
113
|
+
let current = '';
|
|
114
|
+
let hasContent = false;
|
|
115
|
+
let inQuotes = false;
|
|
116
|
+
for (const ch of commandLine) {
|
|
117
|
+
if (ch === '"') {
|
|
118
|
+
inQuotes = !inQuotes;
|
|
119
|
+
// 空引号段也是 token 内容("" x 与 x 不同),与 cmd 的近似语义一致
|
|
120
|
+
hasContent = true;
|
|
121
|
+
}
|
|
122
|
+
else if (!inQuotes && /\s/.test(ch)) {
|
|
123
|
+
if (hasContent)
|
|
124
|
+
tokens.push(current);
|
|
125
|
+
current = '';
|
|
126
|
+
hasContent = false;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
current += ch;
|
|
130
|
+
hasContent = true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (hasContent)
|
|
134
|
+
tokens.push(current);
|
|
135
|
+
return tokens;
|
|
136
|
+
}
|
|
137
|
+
/** 取命令行 token 的「去掉扩展名的 basename」,用于与期望二进制名比较。 */
|
|
138
|
+
function tokenBinaryName(token, platform) {
|
|
139
|
+
let text = token.trim();
|
|
140
|
+
if (text.length >= 2 &&
|
|
141
|
+
((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'")))) {
|
|
142
|
+
text = text.slice(1, -1);
|
|
143
|
+
}
|
|
144
|
+
// CIM 的 CommandLine 用 Windows 分隔符;posix 用 unix 分隔符
|
|
145
|
+
const p = isWin32(platform) ? path.win32 : path.posix;
|
|
146
|
+
const base = p.basename(text);
|
|
147
|
+
const ext = p.extname(base);
|
|
148
|
+
const name = ext ? base.slice(0, -ext.length) : base;
|
|
149
|
+
return caseInsensitive(platform) ? name.toLowerCase() : name;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* 命令行与期望二进制是否属于同一程序。收紧规则(review 2026-09-05):
|
|
153
|
+
* 1. 首 token(可执行位)匹配——`/usr/local/bin/claude --verbose` 匹配 claude;
|
|
154
|
+
* 2. 或后续 token 是**带路径分隔符的脚本路径**且 basename 匹配——
|
|
155
|
+
* `node /home/user/.local/bin/claude` 匹配 claude。
|
|
156
|
+
* 任意位置裸参数不再参与匹配:`bash claude`(脚本恰好叫 claude)、
|
|
157
|
+
* `grep claude foo.log` 这类不再假阳性(killOrphan 场景会误杀无辜进程)。
|
|
158
|
+
*/
|
|
159
|
+
function commandLineMatches(commandLine, want, platform) {
|
|
160
|
+
const tokens = tokenizeCommandLine(commandLine);
|
|
161
|
+
const first = tokens[0];
|
|
162
|
+
if (first !== undefined && tokenBinaryName(first, platform) === want)
|
|
163
|
+
return true;
|
|
164
|
+
return tokens
|
|
165
|
+
.slice(1)
|
|
166
|
+
.some((token) => /[/\\]/.test(token) && tokenBinaryName(token, platform) === want);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* pid 身份验证:进程存活且命令行确实属于期望二进制(+ CreationDate 一致)才 true。
|
|
170
|
+
*
|
|
171
|
+
* 用于 killOrphan 与实例锁陈旧判定——只有这里返回 true 才允许动手。
|
|
172
|
+
*/
|
|
173
|
+
export async function verifyPidIdentity(pid, opts) {
|
|
174
|
+
const platform = opts.platform ?? currentPlatform;
|
|
175
|
+
const identity = await queryProcessIdentity(pid, opts);
|
|
176
|
+
if (!identity)
|
|
177
|
+
return false;
|
|
178
|
+
const want = caseInsensitive(platform) ? opts.expectedBinary.toLowerCase() : opts.expectedBinary;
|
|
179
|
+
if (!commandLineMatches(identity.commandLine, want, platform))
|
|
180
|
+
return false;
|
|
181
|
+
// CreationDate 仅在 pid 文件带了第三行时才比对(旧文件缺字段 → 退化为命令行匹配)
|
|
182
|
+
const expected = opts.expectedCreationDate?.trim();
|
|
183
|
+
if (expected)
|
|
184
|
+
return identity.creationDate === expected;
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface PathOptions {
|
|
2
|
+
platform?: NodeJS.Platform;
|
|
3
|
+
/** 环境变量注入(测试用);默认 process.env */
|
|
4
|
+
env?: Record<string, string | undefined>;
|
|
5
|
+
/** 家目录注入(测试用);默认 os.homedir() */
|
|
6
|
+
home?: string;
|
|
7
|
+
/** 是否执行 realpath(默认 true);不存在的路径自动跳过 */
|
|
8
|
+
realpath?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function canonicalPath(p: string, opts?: PathOptions): string;
|
|
11
|
+
export declare function samePath(a: string, b: string, opts?: PathOptions): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* cwd → 会话项目目录名(Claude/pi 的 on-disk 编码,lossy N-to-N)。
|
|
14
|
+
*
|
|
15
|
+
* 只可用于**定位**目录,绝不可反解回 cwd——cwd 必须从 JSONL 内容读取
|
|
16
|
+
* (回归 2026-06-21 /resume & /cd 路径错乱)。
|
|
17
|
+
*
|
|
18
|
+
* posix:`/` → `-`(`_` 同样归一,与 Claude Code 一致)。
|
|
19
|
+
* win32:`\` 与 `/` **都是**分隔符、`:` 是非法文件名字符,三者都必须归一,
|
|
20
|
+
* 否则 `C:\Users\x\proj` 会原样拼进目录名,mkdir/stat 必然失败。
|
|
21
|
+
* 反过来,posix 上 `:` 是**合法**文件名字符,不能动——否则既有目录定位不到。
|
|
22
|
+
*/
|
|
23
|
+
export declare function encodeProjectDirName(cwd: string, opts?: PathOptions): string;
|
|
24
|
+
export declare function displayName(p: string, opts?: PathOptions): string;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PathKit — 跨平台路径语义(design.md §5.1)。
|
|
3
|
+
*
|
|
4
|
+
* 所有 cwd 相等性判断、会话目录编码、pid 文件名后缀、spawn cwd 前统一走本模块。
|
|
5
|
+
* 语义映射:
|
|
6
|
+
* canonicalPath(p): realpath(expandEnv(expandTilde(normalizeSeparators(p))))
|
|
7
|
+
* samePath(a, b): win32/darwin 默认大小写不敏感 + 分隔符不敏感;linux 严格相等
|
|
8
|
+
* displayName(p): path.basename(win32 原生认识 \ 与 /)
|
|
9
|
+
*
|
|
10
|
+
* 全部函数接受显式 `platform` 参数(默认 currentPlatform),使 win32 语义
|
|
11
|
+
* 可在任意宿主的单元测试中验证。
|
|
12
|
+
*/
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import os from 'node:os';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import { currentPlatform, isWin32 } from './select.js';
|
|
17
|
+
/** 展开开头的 ~ 或 ~/(win32 含 ~\)为家目录;其余原样返回。 */
|
|
18
|
+
function expandTilde(p, home, platform) {
|
|
19
|
+
if (p === '~')
|
|
20
|
+
return home;
|
|
21
|
+
if (p.startsWith('~/') || (isWin32(platform) && p.startsWith('~\\'))) {
|
|
22
|
+
return path.join(home, p.slice(2));
|
|
23
|
+
}
|
|
24
|
+
return p;
|
|
25
|
+
}
|
|
26
|
+
/** 展开 %VAR%(win32)或 $VAR / ${VAR}(posix);未定义的变量保留字面量。 */
|
|
27
|
+
function expandEnv(p, env, platform) {
|
|
28
|
+
if (isWin32(platform)) {
|
|
29
|
+
return p.replace(/%([A-Za-z0-9_]+)%/g, (match, name) => env[name] ?? match);
|
|
30
|
+
}
|
|
31
|
+
return p.replace(/\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/g, (match, braced, plain) => {
|
|
32
|
+
const name = braced ?? plain;
|
|
33
|
+
if (name === undefined)
|
|
34
|
+
return match;
|
|
35
|
+
const value = env[name];
|
|
36
|
+
return value === undefined ? match : value;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/** win32 把反斜杠归一为正斜杠(Node win32 API 两者皆收);posix 反斜杠是合法文件名字符,不动。 */
|
|
40
|
+
function normalizeSeparators(p, platform) {
|
|
41
|
+
return isWin32(platform) ? p.replaceAll('\\', '/') : p;
|
|
42
|
+
}
|
|
43
|
+
function resolveOptions(opts) {
|
|
44
|
+
const platform = opts?.platform ?? currentPlatform;
|
|
45
|
+
return {
|
|
46
|
+
platform,
|
|
47
|
+
env: opts?.env ?? process.env,
|
|
48
|
+
home: opts?.home ?? os.homedir(),
|
|
49
|
+
realpath: opts?.realpath ?? true,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function canonicalPath(p, opts) {
|
|
53
|
+
const { platform, env, home, realpath } = resolveOptions(opts);
|
|
54
|
+
let out = expandTilde(p, home, platform);
|
|
55
|
+
out = expandEnv(out, env, platform);
|
|
56
|
+
out = normalizeSeparators(out, platform);
|
|
57
|
+
if (!realpath)
|
|
58
|
+
return out;
|
|
59
|
+
try {
|
|
60
|
+
return fs.realpathSync(out);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// 路径尚不存在(如待创建的目录):返回展开+归一化形态,不抛
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export function samePath(a, b, opts) {
|
|
68
|
+
const { platform } = resolveOptions(opts);
|
|
69
|
+
if (platform === 'linux')
|
|
70
|
+
return a === b;
|
|
71
|
+
if (isWin32(platform)) {
|
|
72
|
+
// win32:\ 与 / 都是分隔符,且路径大小写不敏感
|
|
73
|
+
const norm = (p) => p.replaceAll('\\', '/').replace(/\/+$/, '');
|
|
74
|
+
return norm(a).toLowerCase() === norm(b).toLowerCase();
|
|
75
|
+
}
|
|
76
|
+
// darwin:默认按大小写不敏感处理(macOS 默认文件系统 APFS 不敏感);
|
|
77
|
+
// 反斜杠是合法文件名字符,不动。分隔符只有 /。
|
|
78
|
+
// 假设标注:大小写敏感 APFS/HFS+ 卷上 /Users/A 与 /Users/a 实为两个目录,
|
|
79
|
+
// 此处会判等(cwd 守卫假阳性)——影响面小(默认卷不敏感),如需精确需
|
|
80
|
+
// 逐目录 stat 探测卷大小写属性,暂不做。
|
|
81
|
+
// 尾部分隔符必须与 win32 同口径忽略,否则 `/cd ~/repo/` 与 `/cd ~/repo`
|
|
82
|
+
// 会被判成两个目录,M2 迁移后直接表现为会话目录错配。
|
|
83
|
+
const norm = (p) => p.replace(/\/+$/, '');
|
|
84
|
+
return norm(a).toLowerCase() === norm(b).toLowerCase();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* cwd → 会话项目目录名(Claude/pi 的 on-disk 编码,lossy N-to-N)。
|
|
88
|
+
*
|
|
89
|
+
* 只可用于**定位**目录,绝不可反解回 cwd——cwd 必须从 JSONL 内容读取
|
|
90
|
+
* (回归 2026-06-21 /resume & /cd 路径错乱)。
|
|
91
|
+
*
|
|
92
|
+
* posix:`/` → `-`(`_` 同样归一,与 Claude Code 一致)。
|
|
93
|
+
* win32:`\` 与 `/` **都是**分隔符、`:` 是非法文件名字符,三者都必须归一,
|
|
94
|
+
* 否则 `C:\Users\x\proj` 会原样拼进目录名,mkdir/stat 必然失败。
|
|
95
|
+
* 反过来,posix 上 `:` 是**合法**文件名字符,不能动——否则既有目录定位不到。
|
|
96
|
+
*/
|
|
97
|
+
export function encodeProjectDirName(cwd, opts) {
|
|
98
|
+
const { platform } = resolveOptions(opts);
|
|
99
|
+
return isWin32(platform)
|
|
100
|
+
? cwd.replace(/[/\\:]/g, '-').replace(/_/g, '-')
|
|
101
|
+
: cwd.replace(/\//g, '-').replace(/_/g, '-');
|
|
102
|
+
}
|
|
103
|
+
export function displayName(p, opts) {
|
|
104
|
+
const { platform } = resolveOptions(opts);
|
|
105
|
+
// 必须用与注入 platform 对应的 path 实现,而非宿主 `path`:宿主为 win32 时
|
|
106
|
+
// `path.basename` 会把 `\` 当分隔符,posix 语义(反斜杠是合法文件名字符)
|
|
107
|
+
// 就被宿主机悄悄改写。win32/posix 各用各的实现后,结果与宿主无关。
|
|
108
|
+
return isWin32(platform) ? path.win32.basename(p) : path.posix.basename(p);
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=path.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 平台选择的唯一入口(design.md §2.2 纪律)。
|
|
3
|
+
*
|
|
4
|
+
* `process.platform` 的读取只允许发生在本文件;其余模块一律导入
|
|
5
|
+
* `currentPlatform` / `isWin32`,或接受显式 platform 参数(便于在任意
|
|
6
|
+
* 宿主上单测 win32 语义)。
|
|
7
|
+
*/
|
|
8
|
+
export declare const currentPlatform: NodeJS.Platform;
|
|
9
|
+
export declare function isWin32(platform?: NodeJS.Platform): boolean;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 平台选择的唯一入口(design.md §2.2 纪律)。
|
|
3
|
+
*
|
|
4
|
+
* `process.platform` 的读取只允许发生在本文件;其余模块一律导入
|
|
5
|
+
* `currentPlatform` / `isWin32`,或接受显式 platform 参数(便于在任意
|
|
6
|
+
* 宿主上单测 win32 语义)。
|
|
7
|
+
*/
|
|
8
|
+
export const currentPlatform = process.platform;
|
|
9
|
+
export function isWin32(platform = currentPlatform) {
|
|
10
|
+
return platform === 'win32';
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=select.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShellBackend:把 `!` 命令与 kimi ACP terminal 的 shell 依赖收进 seam
|
|
3
|
+
* (design.md §7.2)。
|
|
4
|
+
*
|
|
5
|
+
* - posix:`bash -c <command>`(与现状 BashProcessRunner 同语义);
|
|
6
|
+
* - win32:默认 Git Bash(`bash.exe -c <command>`),powershell / cmd 仅作为
|
|
7
|
+
* 显式用户选择 §7.3;
|
|
8
|
+
* - 首版不做命令翻译器:Git Bash 缺失时抛 {@link ShellUnavailableError},
|
|
9
|
+
* 由调用方转成明确错误卡(提示需要 Git Bash)。
|
|
10
|
+
*
|
|
11
|
+
* kimi ACP 的 terminal/create 固定 `bash -c`,因此 Git Bash 不是 `!` 的专属
|
|
12
|
+
* 需求,而是 kimi agent 在 Windows 上可用的前置条件(§4.4)。
|
|
13
|
+
*/
|
|
14
|
+
import type { ChildProcess, SpawnOptions } from 'node:child_process';
|
|
15
|
+
import { type ResolveOptions, type LaunchSpec } from './command.js';
|
|
16
|
+
export type ShellKind = 'bash' | 'powershell' | 'cmd';
|
|
17
|
+
export interface ShellBackend {
|
|
18
|
+
readonly kind: ShellKind;
|
|
19
|
+
/**
|
|
20
|
+
* 执行 command。`options` 透传给底层 spawn(如 stdio/detached);
|
|
21
|
+
* win32 会强制附加 `windowsHide: true`,调用方无法覆盖。
|
|
22
|
+
*/
|
|
23
|
+
spawn(command: string, opts: {
|
|
24
|
+
cwd: string;
|
|
25
|
+
options?: SpawnOptions;
|
|
26
|
+
}): ChildProcess;
|
|
27
|
+
}
|
|
28
|
+
/** shell 不可用(如 win32 上没装 Git Bash):调用方应转成明确错误卡。 */
|
|
29
|
+
export declare class ShellUnavailableError extends Error {
|
|
30
|
+
readonly kind: ShellKind;
|
|
31
|
+
constructor(kind: ShellKind, message: string);
|
|
32
|
+
}
|
|
33
|
+
export type SpawnLike = (file: string, args: readonly string[], opts?: SpawnOptions) => ChildProcess;
|
|
34
|
+
export interface ShellBackendDeps {
|
|
35
|
+
/** 平台注入(测试用);默认当前宿主平台 */
|
|
36
|
+
platform?: NodeJS.Platform;
|
|
37
|
+
/** win32 下的后端选择:默认 bash(Git Bash) */
|
|
38
|
+
kind?: ShellKind;
|
|
39
|
+
/** spawn 注入(测试用);默认 platform/spawn 的 cross-spawn 包装 */
|
|
40
|
+
spawn?: SpawnLike;
|
|
41
|
+
/** PATH 注入(测试 / 守护 env 用);win32 解析 bash.exe 时透传 */
|
|
42
|
+
pathEnv?: string;
|
|
43
|
+
/** 命令解析注入(测试用);默认 resolveExecutable */
|
|
44
|
+
resolve?: (name: string, opts?: ResolveOptions) => LaunchSpec | null;
|
|
45
|
+
}
|
|
46
|
+
/** posix bash 后端:`bash -c <command>`。 */
|
|
47
|
+
export declare function createBashShellBackend(deps?: ShellBackendDeps): ShellBackend;
|
|
48
|
+
/** win32 后端:默认 Git Bash,powershell/cmd 为显式用户选择。 */
|
|
49
|
+
export declare function createWin32ShellBackend(deps?: ShellBackendDeps): ShellBackend;
|
|
50
|
+
/** 平台分发的唯一入口:posix → bash,win32 → 默认 Git Bash。 */
|
|
51
|
+
export declare function createShellBackend(deps?: ShellBackendDeps): ShellBackend;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { currentPlatform, isWin32 } from './select.js';
|
|
2
|
+
import { resolveExecutable } from './command.js';
|
|
3
|
+
import { spawnProcess } from './spawn.js';
|
|
4
|
+
/** shell 不可用(如 win32 上没装 Git Bash):调用方应转成明确错误卡。 */
|
|
5
|
+
export class ShellUnavailableError extends Error {
|
|
6
|
+
kind;
|
|
7
|
+
constructor(kind, message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'ShellUnavailableError';
|
|
10
|
+
this.kind = kind;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** posix bash 后端:`bash -c <command>`。 */
|
|
14
|
+
export function createBashShellBackend(deps = {}) {
|
|
15
|
+
const spawnFn = deps.spawn ?? spawnProcess;
|
|
16
|
+
return {
|
|
17
|
+
kind: 'bash',
|
|
18
|
+
spawn(command, opts) {
|
|
19
|
+
return spawnFn('bash', ['-c', command], { cwd: opts.cwd, ...opts.options });
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** win32 后端:默认 Git Bash,powershell/cmd 为显式用户选择。 */
|
|
24
|
+
export function createWin32ShellBackend(deps = {}) {
|
|
25
|
+
const spawnFn = deps.spawn ?? spawnProcess;
|
|
26
|
+
const kind = deps.kind ?? 'bash';
|
|
27
|
+
if (kind === 'powershell') {
|
|
28
|
+
return {
|
|
29
|
+
kind,
|
|
30
|
+
spawn(command, opts) {
|
|
31
|
+
return spawnFn('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', command], {
|
|
32
|
+
cwd: opts.cwd,
|
|
33
|
+
...opts.options,
|
|
34
|
+
windowsHide: true,
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (kind === 'cmd') {
|
|
40
|
+
return {
|
|
41
|
+
kind,
|
|
42
|
+
spawn(command, opts) {
|
|
43
|
+
return spawnFn('cmd.exe', ['/d', '/s', '/c', command], {
|
|
44
|
+
cwd: opts.cwd,
|
|
45
|
+
...opts.options,
|
|
46
|
+
windowsHide: true,
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
kind: 'bash',
|
|
53
|
+
spawn(command, opts) {
|
|
54
|
+
const resolve = deps.resolve ?? resolveExecutable;
|
|
55
|
+
const spec = resolve('bash', { platform: 'win32', pathEnv: deps.pathEnv });
|
|
56
|
+
if (!spec) {
|
|
57
|
+
throw new ShellUnavailableError('bash', 'Windows 上执行 bash 命令需要 Git Bash(未在 PATH 中找到 bash.exe);' +
|
|
58
|
+
'请安装 Git for Windows 后重试');
|
|
59
|
+
}
|
|
60
|
+
// 执行语义由 cross-spawn 负责(垫片 cmd.exe 受控执行);这里只保证
|
|
61
|
+
// bash 存在性并给出明确的「需要 Git Bash」错误
|
|
62
|
+
return spawnFn(spec.file, ['-c', command], {
|
|
63
|
+
cwd: opts.cwd,
|
|
64
|
+
...opts.options,
|
|
65
|
+
windowsHide: true,
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** 平台分发的唯一入口:posix → bash,win32 → 默认 Git Bash。 */
|
|
71
|
+
export function createShellBackend(deps = {}) {
|
|
72
|
+
const platform = deps.platform ?? currentPlatform;
|
|
73
|
+
return isWin32(platform) ? createWin32ShellBackend(deps) : createBashShellBackend(deps);
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=shell.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ChildProcess, SpawnOptions, SpawnSyncOptions } from 'node:child_process';
|
|
2
|
+
import type { SpawnSyncReturns } from 'child_process';
|
|
3
|
+
/**
|
|
4
|
+
* cross-spawn 包装:posix 直通,win32 PATHEXT + 垫片 cmd.exe 受控执行。
|
|
5
|
+
*
|
|
6
|
+
* 默认注入 `windowsHide: true`:win32 上 .cmd/.bat 垫片经 cmd.exe 执行时,
|
|
7
|
+
* 不加该选项会闪控制台窗口(测试批量拉起 mock agent 时尤其明显)。该选项
|
|
8
|
+
* 在 posix 上被 Node 直接忽略(Windows-only),因此默认值零跨平台影响;
|
|
9
|
+
* 调用方显式传 `windowsHide: false` 仍可覆盖。
|
|
10
|
+
*/
|
|
11
|
+
export declare function spawnProcess(command: string, args?: readonly string[], options?: SpawnOptions): ChildProcess;
|
|
12
|
+
/** cross-spawn 同步版(一次性命令探测/目录查询用)。默认注入同 {@link spawnProcess}。 */
|
|
13
|
+
export declare function spawnProcessSync(command: string, args?: readonly string[], options?: SpawnSyncOptions): SpawnSyncReturns<string | Buffer>;
|
|
14
|
+
/**
|
|
15
|
+
* env 覆盖合并:先按不区分大小写删除 base 中的同名旧键,再设新值。
|
|
16
|
+
* Windows env 键大小写不敏感,`{...process.env, PATH:x}` 会产生 `PATH`/`Path`
|
|
17
|
+
* 双键(§8.3)。注意 posix 上并非普通 spread:base 中大小写不同名的键(如
|
|
18
|
+
* `Path`)也会被删除,而非与 `PATH` 并存——可接受(posix 语义键为全大写
|
|
19
|
+
* `PATH`,大小写变体本身就不是语义键),但语义与 spread 不同,勿混淆。
|
|
20
|
+
*/
|
|
21
|
+
export declare function mergeProcessEnv(base?: NodeJS.ProcessEnv, overrides?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
22
|
+
/**
|
|
23
|
+
* 是否需要为子进程建独立进程组(`spawn` 的 `detached: true`)。
|
|
24
|
+
*
|
|
25
|
+
* posix:ProcessStopper 的组杀用负 PID(`kill(-pgid)`),子进程必须是组长,
|
|
26
|
+
* 否则 kill 抛 ESRCH 被吞,停止/清理链路全失效。
|
|
27
|
+
*
|
|
28
|
+
* win32:组杀已由 terminator 的 `taskkill /PID <pid> /T /F` 树杀替代,不需要
|
|
29
|
+
* 进程组;而 `detached: true` 会让 **`.cmd` 垫片丢掉父进程的 stdio 管道** ——
|
|
30
|
+
* Windows 上 `DETACHED_PROCESS` 下 cmd.exe 另建控制台并重绑标准句柄,于是
|
|
31
|
+
* 子进程(npm 安装的 agent 全是 `.cmd` 垫片)的 stdout/stderr 永不抵达父进程,
|
|
32
|
+
* 表现为 agent 全程静默、turn 超时。实测 `windowsHide: true` 不能改善,
|
|
33
|
+
* 因此 v1 §3.3「保留 detached + windowsHide」的假设据此修正为「win32 不
|
|
34
|
+
* detached」。调用方应同时传 `windowsHide: true`,避免 cmd.exe 闪控制台窗口。
|
|
35
|
+
*/
|
|
36
|
+
export declare function useDetachedProcessGroup(platform?: NodeJS.Platform): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* win32 command-not-found 行嗅探(§4.4):命中即视为「命令缺失」失败而非
|
|
39
|
+
* 普通非零退出。platform 注入使其在任意宿主可测。
|
|
40
|
+
*/
|
|
41
|
+
export declare function isWindowsCommandNotFoundLine(line: string, platform?: NodeJS.Platform): boolean;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* spawn 统一收口。
|
|
3
|
+
*
|
|
4
|
+
* 全部子进程拉起必须走本模块,禁止直接 child_process.spawn:
|
|
5
|
+
* - cross-spawn 在 posix 是 child_process.spawn 直通(零行为差异);win32 上
|
|
6
|
+
* 做 PATHEXT 解析,.cmd/.bat 经 cmd.exe 以正确的引号转义执行——取代 v1 的
|
|
7
|
+
* 手写垫片解析层;
|
|
8
|
+
* - mergeProcessEnv:Windows env 键大小写不敏感,直接 `{...process.env, PATH:x}`
|
|
9
|
+
* 在键为 `Path` 的机器上产生双键、子进程行为未定义——任何 env 覆盖注入点
|
|
10
|
+
* 必须经它合并(先按不区分大小写删旧键再设新值);
|
|
11
|
+
* - isWindowsCommandNotFoundLine:win32 经 cmd 垫片启动失败不是 spawn 的
|
|
12
|
+
* error 事件 ENOENT,而是子进程往 stderr 打
|
|
13
|
+
* "'xxx' is not recognized as an internal or external command"(§4.4)。
|
|
14
|
+
*/
|
|
15
|
+
import crossSpawn from 'cross-spawn';
|
|
16
|
+
import { currentPlatform, isWin32 } from './select.js';
|
|
17
|
+
/**
|
|
18
|
+
* cross-spawn 包装:posix 直通,win32 PATHEXT + 垫片 cmd.exe 受控执行。
|
|
19
|
+
*
|
|
20
|
+
* 默认注入 `windowsHide: true`:win32 上 .cmd/.bat 垫片经 cmd.exe 执行时,
|
|
21
|
+
* 不加该选项会闪控制台窗口(测试批量拉起 mock agent 时尤其明显)。该选项
|
|
22
|
+
* 在 posix 上被 Node 直接忽略(Windows-only),因此默认值零跨平台影响;
|
|
23
|
+
* 调用方显式传 `windowsHide: false` 仍可覆盖。
|
|
24
|
+
*/
|
|
25
|
+
export function spawnProcess(command, args = [], options = {}) {
|
|
26
|
+
return crossSpawn(command, [...args], { windowsHide: true, ...options });
|
|
27
|
+
}
|
|
28
|
+
/** cross-spawn 同步版(一次性命令探测/目录查询用)。默认注入同 {@link spawnProcess}。 */
|
|
29
|
+
export function spawnProcessSync(command, args = [], options = {}) {
|
|
30
|
+
return crossSpawn.sync(command, [...args], { windowsHide: true, ...options });
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* env 覆盖合并:先按不区分大小写删除 base 中的同名旧键,再设新值。
|
|
34
|
+
* Windows env 键大小写不敏感,`{...process.env, PATH:x}` 会产生 `PATH`/`Path`
|
|
35
|
+
* 双键(§8.3)。注意 posix 上并非普通 spread:base 中大小写不同名的键(如
|
|
36
|
+
* `Path`)也会被删除,而非与 `PATH` 并存——可接受(posix 语义键为全大写
|
|
37
|
+
* `PATH`,大小写变体本身就不是语义键),但语义与 spread 不同,勿混淆。
|
|
38
|
+
*/
|
|
39
|
+
export function mergeProcessEnv(base = process.env, overrides = {}) {
|
|
40
|
+
const out = { ...base };
|
|
41
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
42
|
+
for (const existing of Object.keys(out)) {
|
|
43
|
+
if (existing.toLowerCase() === key.toLowerCase()) {
|
|
44
|
+
delete out[existing];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (value !== undefined)
|
|
48
|
+
out[key] = value;
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
const COMMAND_NOT_FOUND_PATTERN = /is not recognized as an internal or external command|operable program or batch file/i;
|
|
53
|
+
/**
|
|
54
|
+
* 是否需要为子进程建独立进程组(`spawn` 的 `detached: true`)。
|
|
55
|
+
*
|
|
56
|
+
* posix:ProcessStopper 的组杀用负 PID(`kill(-pgid)`),子进程必须是组长,
|
|
57
|
+
* 否则 kill 抛 ESRCH 被吞,停止/清理链路全失效。
|
|
58
|
+
*
|
|
59
|
+
* win32:组杀已由 terminator 的 `taskkill /PID <pid> /T /F` 树杀替代,不需要
|
|
60
|
+
* 进程组;而 `detached: true` 会让 **`.cmd` 垫片丢掉父进程的 stdio 管道** ——
|
|
61
|
+
* Windows 上 `DETACHED_PROCESS` 下 cmd.exe 另建控制台并重绑标准句柄,于是
|
|
62
|
+
* 子进程(npm 安装的 agent 全是 `.cmd` 垫片)的 stdout/stderr 永不抵达父进程,
|
|
63
|
+
* 表现为 agent 全程静默、turn 超时。实测 `windowsHide: true` 不能改善,
|
|
64
|
+
* 因此 v1 §3.3「保留 detached + windowsHide」的假设据此修正为「win32 不
|
|
65
|
+
* detached」。调用方应同时传 `windowsHide: true`,避免 cmd.exe 闪控制台窗口。
|
|
66
|
+
*/
|
|
67
|
+
export function useDetachedProcessGroup(platform = currentPlatform) {
|
|
68
|
+
return !isWin32(platform);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* win32 command-not-found 行嗅探(§4.4):命中即视为「命令缺失」失败而非
|
|
72
|
+
* 普通非零退出。platform 注入使其在任意宿主可测。
|
|
73
|
+
*/
|
|
74
|
+
export function isWindowsCommandNotFoundLine(line, platform = currentPlatform) {
|
|
75
|
+
return platform === 'win32' && COMMAND_NOT_FOUND_PATTERN.test(line);
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=spawn.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* posix 终止器:`src/runner/common/process-stopper.ts` 的原样迁移(design.md §3.2)。
|
|
3
|
+
*
|
|
4
|
+
* 语义与迁移前逐条对齐:SIGTERM 打负 PID 进程组 → 等 graceMs → SIGKILL;
|
|
5
|
+
* immediate 则两个信号连发。本次只新增两样东西:
|
|
6
|
+
* - 返回 {@link TerminateResult} 让终止途经可观测(与 win32 同构);
|
|
7
|
+
* - `cleanupOnExit`(Terminator 接口要求的进程级退出清理)。
|
|
8
|
+
*
|
|
9
|
+
* `ProcessStopper` 保留为兼容壳:行为(含日志文案)零变化,既有调用方与
|
|
10
|
+
* 测试无需改动;M2 调用点迁移时逐个换成 Terminator 后即可删除。
|
|
11
|
+
*/
|
|
12
|
+
import type { ChildProcess } from 'node:child_process';
|
|
13
|
+
import type { Terminator } from './terminator.js';
|
|
14
|
+
export type LogLevel = 'debug' | 'info';
|
|
15
|
+
export type TerminatorLogger = (level: LogLevel, message: string) => void;
|
|
16
|
+
export interface PosixTerminatorDeps {
|
|
17
|
+
/** 优雅等待窗口(毫秒),对应现状 stopGraceMs */
|
|
18
|
+
graceMs: number;
|
|
19
|
+
/** 日志注入(测试用);默认 logger 单例 */
|
|
20
|
+
log?: TerminatorLogger;
|
|
21
|
+
}
|
|
22
|
+
export declare function createPosixTerminator(deps: PosixTerminatorDeps): Terminator;
|
|
23
|
+
interface ProcessStopperOptions {
|
|
24
|
+
graceMs: number;
|
|
25
|
+
}
|
|
26
|
+
interface StopOptions {
|
|
27
|
+
immediate?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 迁移前的类名(兼容壳):返回 void、接受 null proc。
|
|
31
|
+
* 新代码请用 {@link createPosixTerminator} / `createTerminator`。
|
|
32
|
+
*/
|
|
33
|
+
export declare class ProcessStopper {
|
|
34
|
+
private graceMs;
|
|
35
|
+
constructor(opts: ProcessStopperOptions);
|
|
36
|
+
stop(proc: ChildProcess | null, opts?: StopOptions): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
export {};
|