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,103 @@
|
|
|
1
|
+
import { getLogger } from '../logger/index.js';
|
|
2
|
+
/** 默认日志出口:与迁移前同级别(debug 记信号,info 记 grace 超时)。 */
|
|
3
|
+
function defaultLog(level, message) {
|
|
4
|
+
const logger = getLogger();
|
|
5
|
+
if (level === 'debug')
|
|
6
|
+
logger.debug(message);
|
|
7
|
+
else
|
|
8
|
+
logger.info(message);
|
|
9
|
+
}
|
|
10
|
+
/** 退出判定统一口径(CLAUDE.md 红线):exitCode !== null || signalCode !== null。 */
|
|
11
|
+
function isAlive(proc) {
|
|
12
|
+
return proc.exitCode === null && proc.signalCode === null;
|
|
13
|
+
}
|
|
14
|
+
async function stopPosix(proc, opts, log) {
|
|
15
|
+
if (!isAlive(proc)) {
|
|
16
|
+
return { requested: false, via: 'already-exited' };
|
|
17
|
+
}
|
|
18
|
+
const pid = proc.pid;
|
|
19
|
+
if (pid === undefined) {
|
|
20
|
+
return { requested: false, via: 'already-exited' };
|
|
21
|
+
}
|
|
22
|
+
// 负 PID = 整个进程组:shell 包装层与孙进程一并带走
|
|
23
|
+
const pgid = -pid;
|
|
24
|
+
log('debug', `[process-stopper] sending SIGTERM to pgid=${pgid} immediate=${opts.immediate}`);
|
|
25
|
+
try {
|
|
26
|
+
process.kill(pgid, 'SIGTERM');
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
/* process may have exited */
|
|
30
|
+
}
|
|
31
|
+
if (opts.immediate) {
|
|
32
|
+
// 不等待:SIGKILL 紧随其后;进程已死时 kill 抛 ESRCH,忽略即可
|
|
33
|
+
try {
|
|
34
|
+
process.kill(pgid, 'SIGKILL');
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
/* process already gone */
|
|
38
|
+
}
|
|
39
|
+
return { requested: true, via: 'taskkill' };
|
|
40
|
+
}
|
|
41
|
+
// 与 exit 事件竞速等 grace;用 once 避免重复 stop() 累积监听器(P2-15)
|
|
42
|
+
let graceTimer;
|
|
43
|
+
const exited = await Promise.race([
|
|
44
|
+
new Promise((resolve) => {
|
|
45
|
+
if (!isAlive(proc))
|
|
46
|
+
return resolve(true);
|
|
47
|
+
proc.once('exit', () => resolve(true));
|
|
48
|
+
}),
|
|
49
|
+
new Promise((resolve) => {
|
|
50
|
+
graceTimer = setTimeout(() => resolve(false), opts.graceMs);
|
|
51
|
+
}),
|
|
52
|
+
]);
|
|
53
|
+
if (graceTimer)
|
|
54
|
+
clearTimeout(graceTimer);
|
|
55
|
+
if (!exited) {
|
|
56
|
+
log('info', `[process-stopper] process group ${pgid} did not exit within grace period, sending SIGKILL`);
|
|
57
|
+
try {
|
|
58
|
+
process.kill(pgid, 'SIGKILL');
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
/* process already gone */
|
|
62
|
+
}
|
|
63
|
+
return { requested: true, via: 'taskkill' };
|
|
64
|
+
}
|
|
65
|
+
return { requested: true, via: 'cooperative' };
|
|
66
|
+
}
|
|
67
|
+
export function createPosixTerminator(deps) {
|
|
68
|
+
const log = deps.log ?? defaultLog;
|
|
69
|
+
return {
|
|
70
|
+
stop(proc, opts) {
|
|
71
|
+
return stopPosix(proc, { graceMs: deps.graceMs, immediate: opts.immediate }, log);
|
|
72
|
+
},
|
|
73
|
+
cleanupOnExit(proc) {
|
|
74
|
+
if (!isAlive(proc))
|
|
75
|
+
return;
|
|
76
|
+
const pid = proc.pid;
|
|
77
|
+
if (pid === undefined)
|
|
78
|
+
return;
|
|
79
|
+
try {
|
|
80
|
+
process.kill(-pid, 'SIGKILL');
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
/* process already gone */
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 迁移前的类名(兼容壳):返回 void、接受 null proc。
|
|
90
|
+
* 新代码请用 {@link createPosixTerminator} / `createTerminator`。
|
|
91
|
+
*/
|
|
92
|
+
export class ProcessStopper {
|
|
93
|
+
graceMs;
|
|
94
|
+
constructor(opts) {
|
|
95
|
+
this.graceMs = opts.graceMs;
|
|
96
|
+
}
|
|
97
|
+
async stop(proc, opts) {
|
|
98
|
+
if (!proc)
|
|
99
|
+
return;
|
|
100
|
+
await stopPosix(proc, { graceMs: this.graceMs, immediate: opts?.immediate === true }, defaultLog);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=terminator-posix.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ChildProcess, SpawnOptions } from 'node:child_process';
|
|
2
|
+
import type { Terminator } from './terminator.js';
|
|
3
|
+
import { AgentStopperRegistry } from './agent-stopper.js';
|
|
4
|
+
export type SpawnLike = (file: string, args: string[], opts?: SpawnOptions) => ChildProcess;
|
|
5
|
+
/** 等待 graceMs 与 exit 事件竞速;返回 true 表示等待结束后进程仍存活。 */
|
|
6
|
+
export type WaitForExit = (proc: ChildProcess, ms: number) => Promise<boolean>;
|
|
7
|
+
export interface Win32TerminatorDeps {
|
|
8
|
+
/** 协议停止后的优雅等待窗口(毫秒),对应 posix 的 stopGraceMs */
|
|
9
|
+
graceMs: number;
|
|
10
|
+
/** 当前 run 所属 agent:用于查协议停止通道;缺省视为无通道 */
|
|
11
|
+
agent?: string;
|
|
12
|
+
/** 协议通道注册表注入(测试隔离);默认进程级单例 */
|
|
13
|
+
stoppers?: AgentStopperRegistry;
|
|
14
|
+
/** spawn 注入(测试用);默认 node:child_process */
|
|
15
|
+
spawn?: SpawnLike;
|
|
16
|
+
/** grace 等待器注入(测试用);默认与 exit 事件竞速 */
|
|
17
|
+
waitForExit?: WaitForExit;
|
|
18
|
+
/** 日志注入:本模块处于 seam 底层,不反向依赖 logger 单例 */
|
|
19
|
+
log?: (message: string) => void;
|
|
20
|
+
}
|
|
21
|
+
export declare function createWin32Terminator(deps: Win32TerminatorDeps): Terminator;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* win32 终止器:协议停止通道 → grace 等待 → taskkill 树杀(design.md §3.2)。
|
|
3
|
+
*
|
|
4
|
+
* 状态机:
|
|
5
|
+
* 1. 进程已退出 → nothing(via='already-exited')
|
|
6
|
+
* 2. immediate → taskkill /PID <pid> /T /F
|
|
7
|
+
* 3. 优雅 → AgentStopperRegistry 查协议通道
|
|
8
|
+
* - 无通道:打日志显式跳过优雅段(不等一个不会来的退出事件)→ 直接树杀
|
|
9
|
+
* - 有通道:调用后与 exit 事件竞速等 graceMs,仍存活 → 树杀
|
|
10
|
+
*
|
|
11
|
+
* 依赖(spawn / 等待器 / 日志)全部构造注入,win32 语义可在任意宿主上单测。
|
|
12
|
+
*/
|
|
13
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
14
|
+
import { agentStopperRegistry } from './agent-stopper.js';
|
|
15
|
+
/** 退出判定统一口径(CLAUDE.md 红线):exitCode !== null || signalCode !== null。 */
|
|
16
|
+
function isAlive(proc) {
|
|
17
|
+
return proc.exitCode === null && proc.signalCode === null;
|
|
18
|
+
}
|
|
19
|
+
/** 默认等待器:exit 事件与定时器竞速,谁先到算谁。 */
|
|
20
|
+
async function defaultWaitForExit(proc, ms) {
|
|
21
|
+
if (!isAlive(proc))
|
|
22
|
+
return false;
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
let settled = false;
|
|
25
|
+
const onExit = () => finish(false);
|
|
26
|
+
const finish = (alive) => {
|
|
27
|
+
if (settled)
|
|
28
|
+
return;
|
|
29
|
+
settled = true;
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
proc.removeListener('exit', onExit);
|
|
32
|
+
resolve(alive);
|
|
33
|
+
};
|
|
34
|
+
proc.on('exit', onExit);
|
|
35
|
+
const timer = setTimeout(() => finish(isAlive(proc)), ms);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export function createWin32Terminator(deps) {
|
|
39
|
+
const spawnFn = deps.spawn ?? nodeSpawn;
|
|
40
|
+
const stoppers = deps.stoppers ?? agentStopperRegistry;
|
|
41
|
+
const waitForExit = deps.waitForExit ?? defaultWaitForExit;
|
|
42
|
+
const log = deps.log;
|
|
43
|
+
/** 树杀:taskkill /PID <pid> /T /F —— posix 负 PID 组杀的等价物。 */
|
|
44
|
+
function killTree(pid) {
|
|
45
|
+
if (pid === undefined) {
|
|
46
|
+
log?.(`win32 树杀跳过:子进程没有 pid(spawn 未成功)`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
// fire-and-forget:不等 taskkill 自身返回,退出由 proc 的 exit 事件驱动
|
|
51
|
+
const killer = spawnFn('taskkill', ['/PID', String(pid), '/T', '/F'], {
|
|
52
|
+
stdio: 'ignore',
|
|
53
|
+
windowsHide: true,
|
|
54
|
+
});
|
|
55
|
+
killer?.on?.('error', () => {
|
|
56
|
+
// taskkill 拉起失败不致命:调用方仍可走 killOrphan / 陈旧锁自愈
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
log?.(`win32 树杀失败:taskkill 拉起异常 ${String(error)}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
async stop(proc, opts) {
|
|
65
|
+
if (!isAlive(proc))
|
|
66
|
+
return { requested: false, via: 'already-exited' };
|
|
67
|
+
if (proc.pid === undefined) {
|
|
68
|
+
// 与 posix 同口径:拿不到 pid 就没有可请求的终止目标,不能谎报 requested
|
|
69
|
+
log?.('win32 停止跳过:子进程没有 pid(spawn 未成功),按已退出处理');
|
|
70
|
+
return { requested: false, via: 'already-exited' };
|
|
71
|
+
}
|
|
72
|
+
if (opts.immediate) {
|
|
73
|
+
killTree(proc.pid);
|
|
74
|
+
return { requested: true, via: 'taskkill' };
|
|
75
|
+
}
|
|
76
|
+
const agent = deps.agent;
|
|
77
|
+
const stopper = agent === undefined ? undefined : stoppers.get(agent);
|
|
78
|
+
if (!stopper) {
|
|
79
|
+
// 无通道是合法状态:显式记录后走树杀,不做无意义的优雅空转
|
|
80
|
+
log?.(`win32 优雅停止跳过:agent=${agent ?? '(unknown)'} 无协议停止通道,直接树杀`);
|
|
81
|
+
killTree(proc.pid);
|
|
82
|
+
return { requested: true, via: 'skipped-no-channel' };
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
await stopper(proc);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
// 通道异常不能吞掉终止流程:记录后继续走树杀兜底
|
|
89
|
+
log?.(`win32 协议停止通道异常,转入树杀:${String(error)}`);
|
|
90
|
+
}
|
|
91
|
+
if (!isAlive(proc))
|
|
92
|
+
return { requested: true, via: 'cooperative' };
|
|
93
|
+
const stillAlive = await waitForExit(proc, deps.graceMs);
|
|
94
|
+
if (!stillAlive)
|
|
95
|
+
return { requested: true, via: 'cooperative' };
|
|
96
|
+
killTree(proc.pid);
|
|
97
|
+
return { requested: true, via: 'taskkill' };
|
|
98
|
+
},
|
|
99
|
+
cleanupOnExit(proc) {
|
|
100
|
+
// M0 真机验证项(§9.2 矩阵):本方法在 process.on('exit') 里调用,而
|
|
101
|
+
// killTree 依赖 libuv uv_spawn 在 exit handler 内同步发起 taskkill——
|
|
102
|
+
// 大概率可用(uv_spawn 本身同步),但与 posix 路径(同步 process.kill)
|
|
103
|
+
// 可靠性不等价;失败表现为 win32 上 bridge 退出后 agent 变孤儿。
|
|
104
|
+
if (!isAlive(proc))
|
|
105
|
+
return;
|
|
106
|
+
killTree(proc.pid);
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=terminator-win32.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 平台终止器 seam 的接口层(design.md §3.2 / §3.3)。
|
|
3
|
+
*
|
|
4
|
+
* Windows 没有可拦截的跨进程 SIGTERM、负 PID 无效,因此把「进程组 + 信号」
|
|
5
|
+
* 换成「协议停止通道 + 进程树杀」。本文件只放接口与平台分发,实现见
|
|
6
|
+
* terminator-posix.ts / terminator-win32.ts。
|
|
7
|
+
*/
|
|
8
|
+
import type { ChildProcess } from 'node:child_process';
|
|
9
|
+
import type { AgentStopperRegistry } from './agent-stopper.js';
|
|
10
|
+
import type { TerminateResult } from './types.js';
|
|
11
|
+
export { AgentStopperRegistry, agentStopperRegistry } from './agent-stopper.js';
|
|
12
|
+
export type { AgentStopper } from './agent-stopper.js';
|
|
13
|
+
/**
|
|
14
|
+
* 进程终止器。
|
|
15
|
+
*
|
|
16
|
+
* `stop` 的 `immediate` 语义与现状 `process-stopper` 的 `stop({immediate})` 对齐。
|
|
17
|
+
* 设计文档声明返回 `Promise<void>`,这里收窄为 {@link TerminateResult}:降级
|
|
18
|
+
* 必须可观测(优雅段被跳过、走了树杀、进程早已退出),否则 win32 上「等一个
|
|
19
|
+
* 不会来的退出事件」这类退化无法被发现。
|
|
20
|
+
*/
|
|
21
|
+
export interface Terminator {
|
|
22
|
+
stop(proc: ChildProcess, opts: {
|
|
23
|
+
immediate: boolean;
|
|
24
|
+
}): Promise<TerminateResult>;
|
|
25
|
+
/** 进程级退出清理:fire-and-forget 强杀 */
|
|
26
|
+
cleanupOnExit(proc: ChildProcess): void;
|
|
27
|
+
}
|
|
28
|
+
export interface TerminatorDeps {
|
|
29
|
+
/** 优雅等待窗口(毫秒);win32 为协议停止后的等待,posix 为 SIGTERM→SIGKILL 的 grace */
|
|
30
|
+
graceMs: number;
|
|
31
|
+
/** 平台注入(测试用);默认当前宿主平台 */
|
|
32
|
+
platform?: NodeJS.Platform;
|
|
33
|
+
/** 当前 run 所属 agent(win32 查协议通道用;posix 忽略) */
|
|
34
|
+
agent?: string;
|
|
35
|
+
/** 协议通道注册表注入(测试隔离);默认进程级单例 */
|
|
36
|
+
stoppers?: AgentStopperRegistry;
|
|
37
|
+
/** 日志注入(测试用);默认走 logger 单例 */
|
|
38
|
+
log?: (level: 'debug' | 'info', message: string) => void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 唯一的终止器选择入口:posix 走组杀,win32 走「协议通道 + taskkill 树杀」。
|
|
42
|
+
* 其余模块一律拿这里的 Terminator,禁止散落平台 if(§2.2)。
|
|
43
|
+
*/
|
|
44
|
+
export declare function createTerminator(deps: TerminatorDeps): Terminator;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { currentPlatform, isWin32 } from './select.js';
|
|
2
|
+
import { createPosixTerminator } from './terminator-posix.js';
|
|
3
|
+
import { createWin32Terminator } from './terminator-win32.js';
|
|
4
|
+
// 注册表实现在 agent-stopper.ts(避免与 terminator-win32.ts 循环依赖),
|
|
5
|
+
// 这里 re-export 以维持调用方的单一入口。
|
|
6
|
+
export { AgentStopperRegistry, agentStopperRegistry } from './agent-stopper.js';
|
|
7
|
+
/**
|
|
8
|
+
* 唯一的终止器选择入口:posix 走组杀,win32 走「协议通道 + taskkill 树杀」。
|
|
9
|
+
* 其余模块一律拿这里的 Terminator,禁止散落平台 if(§2.2)。
|
|
10
|
+
*/
|
|
11
|
+
export function createTerminator(deps) {
|
|
12
|
+
const platform = deps.platform ?? currentPlatform;
|
|
13
|
+
if (isWin32(platform)) {
|
|
14
|
+
return createWin32Terminator({
|
|
15
|
+
graceMs: deps.graceMs,
|
|
16
|
+
agent: deps.agent,
|
|
17
|
+
stoppers: deps.stoppers,
|
|
18
|
+
// win32 侧日志不带级别(无 debug 级输出),统一按 info 透传
|
|
19
|
+
log: deps.log ? (message) => deps.log?.('info', message) : undefined,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return createPosixTerminator({ graceMs: deps.graceMs, log: deps.log });
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=terminator.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 平台 seam 纯类型。
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* spawn 发现规格 —— resolveExecutable 的产出(v2 §4.3,仅探测/诊断用)。
|
|
6
|
+
* v2 收窄:.cmd 垫片命中即报告垫片路径本身,不再解析内容定位入口;
|
|
7
|
+
* 执行统一走 platform/spawn.ts 的 cross-spawn 包装。
|
|
8
|
+
*/
|
|
9
|
+
export interface LaunchSpec {
|
|
10
|
+
kind: 'direct';
|
|
11
|
+
/** 可执行文件路径(posix 绝对路径 / win32 含 .cmd 垫片路径) */
|
|
12
|
+
file: string;
|
|
13
|
+
}
|
|
14
|
+
/** 终止结果(§3.2 win32 状态机产出;可观测降级用)。 */
|
|
15
|
+
export interface TerminateResult {
|
|
16
|
+
/** 进程是否已确认被请求终止(不代表已退出,退出由 exit 事件驱动) */
|
|
17
|
+
requested: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* 终止途经:协议停止 / 强制终止 / 进程已退出 / 无通道跳过优雅段。
|
|
20
|
+
* 强制终止在 posix 下是 SIGKILL 组杀、win32 下是 taskkill 树杀,同枚举值。
|
|
21
|
+
*/
|
|
22
|
+
via: 'cooperative' | 'taskkill' | 'already-exited' | 'skipped-no-channel';
|
|
23
|
+
}
|
package/dist/restart.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawnProcess } from './platform/spawn.js';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { getLogger } from './logger/index.js';
|
|
@@ -24,10 +24,12 @@ const POLL_MS = 100;
|
|
|
24
24
|
export function spawnReplacementBridge(logsDir) {
|
|
25
25
|
fs.mkdirSync(logsDir, { recursive: true });
|
|
26
26
|
const out = fs.openSync(path.join(logsDir, 'restart-child.log'), 'a');
|
|
27
|
-
const child =
|
|
27
|
+
const child = spawnProcess(process.execPath, process.argv.slice(1), {
|
|
28
28
|
cwd: process.cwd(),
|
|
29
29
|
env: { ...process.env, [RESTART_WAIT_PID_ENV]: String(process.pid) },
|
|
30
30
|
detached: true,
|
|
31
|
+
// win32:detached 继任者默认会闪控制台窗口(§3.7)
|
|
32
|
+
windowsHide: true,
|
|
31
33
|
stdio: ['ignore', out, out],
|
|
32
34
|
});
|
|
33
35
|
// Late spawn errors (e.g. binary removed mid-run) must not crash the
|
|
@@ -28,13 +28,40 @@ type SessionUsageLike = Parameters<typeof formatUsageStats>[0];
|
|
|
28
28
|
*/
|
|
29
29
|
export declare function sessionEventPanel(ev: AgentSessionContentEvent, index: number, totalEvents: number, tailExpandedCount?: number, agentKind?: string): object;
|
|
30
30
|
export { markdownDiv };
|
|
31
|
+
/** 分页卡「跳转页码」输入框的组件 name(仅用于结构断言与可读性)。 */
|
|
32
|
+
export declare const PAGE_JUMP_INPUT_NAME = "pageInput";
|
|
33
|
+
/** 页码输入非法时的统一提示文案(/ls /ws /resume /active /order 共用)。 */
|
|
34
|
+
export declare const PAGE_JUMP_INVALID_HINT = "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u9875\u7801\uFF08\u6B63\u6574\u6570\uFF09";
|
|
31
35
|
/**
|
|
32
|
-
*
|
|
33
|
-
* columns). Shared by /ls, /ws, /resume, /active, /order.
|
|
36
|
+
* 解析分页卡「跳转页码」输入框的提交值。
|
|
34
37
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
+
* 三态返回:
|
|
39
|
+
* - `undefined` — 未输入(空串/非字符串),调用方应回退到 `offset`(上一页/下一页)
|
|
40
|
+
* - `null` — 输入非法(非正整数),调用方应回错误 toast 且不刷新卡片
|
|
41
|
+
* - `number` — 页码换算出的 offset(页码从 1 开始)
|
|
42
|
+
*/
|
|
43
|
+
export declare function parseJumpOffset(inputValue: unknown, pageSize: number): number | null | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Build the CardKit 2.0 pagination block shared by /ls, /ws, /resume, /active,
|
|
46
|
+
* /order.
|
|
47
|
+
*
|
|
48
|
+
* 第一性原理(2026-09-10 窄屏重设计):分页栏要同时承载「页码信息」和「翻页
|
|
49
|
+
* 控件」两类需求。旧布局把两者塞进同一行 `column_set`(文案 weighted 4 : 输入
|
|
50
|
+
* weighted 2),手机窄屏下 auto 按钮先占走固有宽度,剩余宽度按 4:2 切分后文案
|
|
51
|
+
* 列只有百来像素、输入框只有几十像素——文案折行、输入框窄到没法点。
|
|
52
|
+
* 拆成两行后每一类都有足够的横向空间:
|
|
53
|
+
*
|
|
54
|
+
* 1. 页码文案独占整行(顶层 `div`),窄屏不折行;
|
|
55
|
+
* 2. 控件行 `column_set` = [上一页 auto] [跳转页码 input weighted] [下一页 auto],
|
|
56
|
+
* input 吃掉 auto 按钮之外的剩余宽度(窄屏约百来像素,桌面端更宽)。
|
|
57
|
+
*
|
|
58
|
+
* Returns an array of body elements: `[page-label div, controls column_set]`;
|
|
59
|
+
* 调用方展开进 `body.elements`(+ 自己的 `hr`)。input 仍留在控件 column_set
|
|
60
|
+
* 内部,不用 form 容器(form 触发 300123 / 200621 整卡不可用)。CardKit 2.0
|
|
61
|
+
* input 自带 ✓ 提交图标,提交值经 raw `action.input_value` 回传,见 connector
|
|
62
|
+
* includeRawEvent + router/card-action-payload.ts。
|
|
63
|
+
*
|
|
64
|
+
* 每个调用方自带 callback cmd / extra value / 文案(语义差异保留,不强行统一)。
|
|
38
65
|
*
|
|
39
66
|
* @param opts.cmd The pagination callback command (e.g. 'ls.page').
|
|
40
67
|
* @param opts.offset Current page offset.
|
|
@@ -53,7 +80,7 @@ export declare function paginationBar(opts: {
|
|
|
53
80
|
label: string;
|
|
54
81
|
prevText?: string;
|
|
55
82
|
nextText?: string;
|
|
56
|
-
}): object;
|
|
83
|
+
}): object[];
|
|
57
84
|
/**
|
|
58
85
|
* Build a session-history resume card (shared by auto-restore, /resume <id>,
|
|
59
86
|
* config-switch resume, and run-completion notification).
|
|
@@ -63,13 +63,51 @@ function eventLabel(ev, agentKind) {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
export { markdownDiv };
|
|
66
|
+
/** 分页卡「跳转页码」输入框的组件 name(仅用于结构断言与可读性)。 */
|
|
67
|
+
export const PAGE_JUMP_INPUT_NAME = 'pageInput';
|
|
68
|
+
/** 页码输入非法时的统一提示文案(/ls /ws /resume /active /order 共用)。 */
|
|
69
|
+
export const PAGE_JUMP_INVALID_HINT = '请输入有效的页码(正整数)';
|
|
66
70
|
/**
|
|
67
|
-
*
|
|
68
|
-
* columns). Shared by /ls, /ws, /resume, /active, /order.
|
|
71
|
+
* 解析分页卡「跳转页码」输入框的提交值。
|
|
69
72
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
+
* 三态返回:
|
|
74
|
+
* - `undefined` — 未输入(空串/非字符串),调用方应回退到 `offset`(上一页/下一页)
|
|
75
|
+
* - `null` — 输入非法(非正整数),调用方应回错误 toast 且不刷新卡片
|
|
76
|
+
* - `number` — 页码换算出的 offset(页码从 1 开始)
|
|
77
|
+
*/
|
|
78
|
+
export function parseJumpOffset(inputValue, pageSize) {
|
|
79
|
+
if (typeof inputValue !== 'string' || inputValue.trim() === '')
|
|
80
|
+
return undefined;
|
|
81
|
+
const raw = inputValue.trim();
|
|
82
|
+
if (!/^[0-9]+$/.test(raw))
|
|
83
|
+
return null;
|
|
84
|
+
const page = Number(raw);
|
|
85
|
+
if (!Number.isFinite(page) || page < 1)
|
|
86
|
+
return null;
|
|
87
|
+
const size = Number.isFinite(pageSize) && pageSize > 0 ? Math.trunc(pageSize) : 1;
|
|
88
|
+
return (page - 1) * size;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Build the CardKit 2.0 pagination block shared by /ls, /ws, /resume, /active,
|
|
92
|
+
* /order.
|
|
93
|
+
*
|
|
94
|
+
* 第一性原理(2026-09-10 窄屏重设计):分页栏要同时承载「页码信息」和「翻页
|
|
95
|
+
* 控件」两类需求。旧布局把两者塞进同一行 `column_set`(文案 weighted 4 : 输入
|
|
96
|
+
* weighted 2),手机窄屏下 auto 按钮先占走固有宽度,剩余宽度按 4:2 切分后文案
|
|
97
|
+
* 列只有百来像素、输入框只有几十像素——文案折行、输入框窄到没法点。
|
|
98
|
+
* 拆成两行后每一类都有足够的横向空间:
|
|
99
|
+
*
|
|
100
|
+
* 1. 页码文案独占整行(顶层 `div`),窄屏不折行;
|
|
101
|
+
* 2. 控件行 `column_set` = [上一页 auto] [跳转页码 input weighted] [下一页 auto],
|
|
102
|
+
* input 吃掉 auto 按钮之外的剩余宽度(窄屏约百来像素,桌面端更宽)。
|
|
103
|
+
*
|
|
104
|
+
* Returns an array of body elements: `[page-label div, controls column_set]`;
|
|
105
|
+
* 调用方展开进 `body.elements`(+ 自己的 `hr`)。input 仍留在控件 column_set
|
|
106
|
+
* 内部,不用 form 容器(form 触发 300123 / 200621 整卡不可用)。CardKit 2.0
|
|
107
|
+
* input 自带 ✓ 提交图标,提交值经 raw `action.input_value` 回传,见 connector
|
|
108
|
+
* includeRawEvent + router/card-action-payload.ts。
|
|
109
|
+
*
|
|
110
|
+
* 每个调用方自带 callback cmd / extra value / 文案(语义差异保留,不强行统一)。
|
|
73
111
|
*
|
|
74
112
|
* @param opts.cmd The pagination callback command (e.g. 'ls.page').
|
|
75
113
|
* @param opts.offset Current page offset.
|
|
@@ -84,9 +122,9 @@ export function paginationBar(opts) {
|
|
|
84
122
|
const hasNext = opts.offset + opts.pageSize < opts.total;
|
|
85
123
|
const prevText = opts.prevText ?? '⬅ 上一页';
|
|
86
124
|
const nextText = opts.nextText ?? '下一页 ➡';
|
|
87
|
-
const
|
|
125
|
+
const controls = [];
|
|
88
126
|
if (hasPrev) {
|
|
89
|
-
|
|
127
|
+
controls.push({
|
|
90
128
|
tag: 'column',
|
|
91
129
|
width: 'auto',
|
|
92
130
|
vertical_align: 'center',
|
|
@@ -106,15 +144,30 @@ export function paginationBar(opts) {
|
|
|
106
144
|
],
|
|
107
145
|
});
|
|
108
146
|
}
|
|
109
|
-
|
|
147
|
+
// 跳转页码输入:占满 auto 按钮之外的剩余宽度(窄屏可点、桌面不局促)。
|
|
148
|
+
// 提交后值经 raw.action.input_value 回传,见 connector includeRawEvent +
|
|
149
|
+
// router/card-action-payload.ts。
|
|
150
|
+
controls.push({
|
|
110
151
|
tag: 'column',
|
|
111
152
|
width: 'weighted',
|
|
112
153
|
weight: 1,
|
|
113
154
|
vertical_align: 'center',
|
|
114
|
-
elements: [
|
|
155
|
+
elements: [
|
|
156
|
+
{
|
|
157
|
+
tag: 'input',
|
|
158
|
+
name: PAGE_JUMP_INPUT_NAME,
|
|
159
|
+
placeholder: { tag: 'plain_text', content: '跳转页码' },
|
|
160
|
+
behaviors: [
|
|
161
|
+
{
|
|
162
|
+
type: 'callback',
|
|
163
|
+
value: { cmd: opts.cmd, pageSize: opts.pageSize, ...opts.extra },
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
],
|
|
115
168
|
});
|
|
116
169
|
if (hasNext) {
|
|
117
|
-
|
|
170
|
+
controls.push({
|
|
118
171
|
tag: 'column',
|
|
119
172
|
width: 'auto',
|
|
120
173
|
vertical_align: 'center',
|
|
@@ -134,7 +187,10 @@ export function paginationBar(opts) {
|
|
|
134
187
|
],
|
|
135
188
|
});
|
|
136
189
|
}
|
|
137
|
-
return
|
|
190
|
+
return [
|
|
191
|
+
{ tag: 'div', text: { tag: 'lark_md', content: opts.label } },
|
|
192
|
+
{ tag: 'column_set', columns: controls },
|
|
193
|
+
];
|
|
138
194
|
}
|
|
139
195
|
/**
|
|
140
196
|
* Build a session-history resume card (shared by auto-restore, /resume <id>,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Shared config card rendering logic.
|
|
3
3
|
*
|
|
4
4
|
* Builds CardKit 2.0 elements for config fields (boolean toggle, select dropdown, input).
|
|
5
|
-
* Used by all agent config builders and the shared tabs (idle,
|
|
5
|
+
* Used by all agent config builders and the shared tabs (idle, logging).
|
|
6
6
|
*/
|
|
7
7
|
import { type AppConfig } from '../../../config/index.js';
|
|
8
8
|
import type { ConfigField } from '../types.js';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Shared config card rendering logic.
|
|
3
3
|
*
|
|
4
4
|
* Builds CardKit 2.0 elements for config fields (boolean toggle, select dropdown, input).
|
|
5
|
-
* Used by all agent config builders and the shared tabs (idle,
|
|
5
|
+
* Used by all agent config builders and the shared tabs (idle, logging).
|
|
6
6
|
*/
|
|
7
7
|
import { getConfigValue } from '../../../config/index.js';
|
|
8
8
|
/** Build CardKit 2.0 elements for a list of config fields. */
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AppConfig } from '../config/index.js';
|
|
2
|
+
/** 对比原始 config 与 pendingConfig,返回变化的 key→string 映射 */
|
|
3
|
+
export declare function diffConfig(original: AppConfig, pending: AppConfig): Record<string, string | undefined>;
|
|
4
|
+
export declare function setNestedValue(target: AppConfig, key: string, value: unknown): void;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config diff / nested-value helpers extracted from CommandRouter
|
|
3
|
+
* (2026-09 round-2 simplification). Pure functions — no router state —
|
|
4
|
+
* previously inlined as methods (collectDiff recursed only into itself).
|
|
5
|
+
*/
|
|
6
|
+
import { mapAgentKey, walkNestedContainer, assertSafeKeyPart } from '../config/index.js';
|
|
7
|
+
/** 对比原始 config 与 pendingConfig,返回变化的 key→string 映射 */
|
|
8
|
+
export function diffConfig(original, pending) {
|
|
9
|
+
const updates = {};
|
|
10
|
+
collectDiff('', original, pending, updates);
|
|
11
|
+
return updates;
|
|
12
|
+
}
|
|
13
|
+
/** 递归收集差异 */
|
|
14
|
+
function collectDiff(prefix, original, pending, result) {
|
|
15
|
+
if (original === pending)
|
|
16
|
+
return;
|
|
17
|
+
// 如果 pending 是对象(非 null)但 original 不是对象,
|
|
18
|
+
// 把 original 当作空对象递归进入 pending 内部,
|
|
19
|
+
// 避免把整个对象转成 "[object Object]" 字符串
|
|
20
|
+
if (typeof pending === 'object' &&
|
|
21
|
+
pending !== null &&
|
|
22
|
+
(typeof original !== 'object' || original === null)) {
|
|
23
|
+
const pendObj = pending;
|
|
24
|
+
for (const k of Object.keys(pendObj)) {
|
|
25
|
+
const newKey = prefix ? `${prefix}.${k}` : k;
|
|
26
|
+
collectDiff(newKey, undefined, pendObj[k], result);
|
|
27
|
+
}
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (typeof original !== 'object' ||
|
|
31
|
+
typeof pending !== 'object' ||
|
|
32
|
+
original === null ||
|
|
33
|
+
pending === null) {
|
|
34
|
+
// 叶子节点,记录差异
|
|
35
|
+
const key = prefix || 'root';
|
|
36
|
+
// pending 为 undefined 表示键被删除,必须保留 undefined 语义,
|
|
37
|
+
// 不能 String(pending) 成 "undefined"
|
|
38
|
+
result[key] = pending === undefined ? undefined : String(pending);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// 两者都是对象,递归比较
|
|
42
|
+
const origObj = original;
|
|
43
|
+
const pendObj = pending;
|
|
44
|
+
const allKeys = new Set([...Object.keys(origObj), ...Object.keys(pendObj)]);
|
|
45
|
+
for (const k of allKeys) {
|
|
46
|
+
const newKey = prefix ? `${prefix}.${k}` : k;
|
|
47
|
+
collectDiff(newKey, origObj[k], pendObj[k], result);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function setNestedValue(target, key, value) {
|
|
51
|
+
// Path mapping 共用 config 模块的 mapAgentKey(G11 Inconsistency 修复):
|
|
52
|
+
// pi.xxx/codex.xxx/opencode.xxx → agents.xxx,claude.xxx 保持顶层
|
|
53
|
+
const mappedKey = mapAgentKey(key);
|
|
54
|
+
const parts = mappedKey.split('.');
|
|
55
|
+
// 复用 config 模块的 walkNestedContainer(含 __proto__/prototype/constructor
|
|
56
|
+
// 守卫与中间段补 {}),router 只保留 value=undefined → delete 分支。
|
|
57
|
+
const container = walkNestedContainer(target, parts, true);
|
|
58
|
+
const lastPart = parts[parts.length - 1];
|
|
59
|
+
assertSafeKeyPart(lastPart);
|
|
60
|
+
if (value === undefined) {
|
|
61
|
+
// value=undefined 表示"删除键"(如清空 reasoningEffort),
|
|
62
|
+
// 不能写成 undefined 值——diffConfig 会把 undefined 转成字面量 "undefined"
|
|
63
|
+
// 写入 config.yaml 并透传给 codex(ReasoningEffort::Custom("undefined"))。
|
|
64
|
+
delete container[lastPart];
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
container[lastPart] = value;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=config-diff.js.map
|