lark-remote 0.1.13 → 0.2.1
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 +36 -3
- package/README.en.md +7 -11
- package/README.md +7 -11
- package/dist/bridge/final-usage.d.ts +24 -0
- package/dist/bridge/final-usage.js +55 -0
- package/dist/bridge/index.d.ts +0 -7
- package/dist/bridge/index.js +22 -64
- 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 +13 -1
- package/dist/card/card-shared.js +21 -30
- 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/config/codex-config.js +2 -0
- package/dist/config/index.d.ts +0 -5
- package/dist/config/index.js +0 -11
- package/dist/config/kimi-config.js +1 -0
- package/dist/config/opencode-config.js +13 -2
- 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 -4
- package/dist/index.js +15 -42
- 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 +66 -6
- package/dist/router/index.js +451 -316
- 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 -1
- package/dist/runner/claude/session.js +18 -4
- package/dist/runner/codex/app-server/protocol-types.d.ts +35 -1
- 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 +39 -0
- package/dist/runner/common/acp/protocol-helpers.js +34 -0
- 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/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +45 -42
- package/dist/runner/kimi/acp/runner.js +140 -293
- package/dist/runner/kimi/acp/translator.d.ts +18 -100
- package/dist/runner/kimi/acp/translator.js +56 -242
- package/dist/runner/opencode/acp/runner.d.ts +22 -46
- package/dist/runner/opencode/acp/runner.js +73 -256
- package/dist/runner/opencode/acp/translator.d.ts +28 -103
- package/dist/runner/opencode/acp/translator.js +51 -236
- 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/types.d.ts +38 -1
- package/dist/session/claude/sessions.js +35 -58
- package/dist/session/codex/rollout-reader.d.ts +10 -0
- package/dist/session/codex/rollout-reader.js +123 -41
- package/dist/session/common/constants.d.ts +7 -0
- package/dist/session/common/constants.js +9 -0
- package/dist/session/common/two-pass.d.ts +22 -0
- package/dist/session/common/two-pass.js +70 -0
- package/dist/session/dsh/sessions.js +7 -6
- package/dist/session/kimi/sessions.d.ts +6 -0
- package/dist/session/kimi/sessions.js +50 -73
- package/dist/session/opencode/sessions.js +26 -8
- package/dist/session/pi/sessions.js +54 -73
- package/dist/update/index.d.ts +1 -2
- 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 -6
- package/dist/update/startup-hint.d.ts +0 -5
- package/dist/update/startup-hint.js +0 -11
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { spawnProcess, isWindowsCommandNotFoundLine, useDetachedProcessGroup, } from '../../platform/spawn.js';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import os from 'node:os';
|
|
4
5
|
import fs from 'node:fs';
|
|
@@ -7,7 +8,6 @@ import { getLogger } from '../../logger/index.js';
|
|
|
7
8
|
import { ProcessStopper } from './process-stopper.js';
|
|
8
9
|
import { SpawnHeartbeat } from './spawn-heartbeat.js';
|
|
9
10
|
import { createJSONLStream } from './jsonl-stream.js';
|
|
10
|
-
import { authErrorEvent, syntheticInitEvent } from './runner-utils.js';
|
|
11
11
|
import { DEFAULT_STOP_GRACE_MS } from '../../config/index.js';
|
|
12
12
|
/**
|
|
13
13
|
* Magic-number constants (Clean Code P3-1, G25 Replace Magic Numbers with
|
|
@@ -16,16 +16,15 @@ import { DEFAULT_STOP_GRACE_MS } from '../../config/index.js';
|
|
|
16
16
|
*/
|
|
17
17
|
/** Max bytes of stderr retained for the result-event error message. */
|
|
18
18
|
const STDERR_TAIL_BYTES = 4000;
|
|
19
|
-
/** Max bytes of stderr surfaced in the non-zero-exit log line. */
|
|
20
|
-
const STDERR_LOG_TAIL = 500;
|
|
21
19
|
/** Error thrown by `spawnChild` when the child process fails to spawn. */
|
|
22
20
|
class SpawnChildError extends Error {
|
|
23
21
|
}
|
|
24
22
|
/**
|
|
25
23
|
* Timeout (ms) for awaiting the spawn 'error' event when proc.pid === undefined
|
|
26
24
|
* (review P2-11). Node guarantees 'error' for ENOENT/EACCES, but some binaries
|
|
27
|
-
* fail silently; the race keeps a silent failure from hanging
|
|
28
|
-
* Matches the 5s race kimi's override used before it was hoisted to
|
|
25
|
+
* fail silently; the race keeps a silent failure from hanging the spawn lead-in
|
|
26
|
+
* forever. Matches the 5s race kimi's override used before it was hoisted to
|
|
27
|
+
* the base.
|
|
29
28
|
*/
|
|
30
29
|
const SPAWN_ERROR_TIMEOUT_MS = 5000;
|
|
31
30
|
const registeredRunners = new Set();
|
|
@@ -59,24 +58,22 @@ export function unregisterExitCleanup(handler) {
|
|
|
59
58
|
registeredRunners.delete(handler);
|
|
60
59
|
}
|
|
61
60
|
/**
|
|
62
|
-
* Abstract base class
|
|
61
|
+
* Abstract base class encapsulating the spawn lifecycle shared by agent
|
|
62
|
+
* runners: spawn lead-in (pid-undefined check, pid file write, heartbeat,
|
|
63
|
+
* stderr tail accumulation), user-initiated stop, orphan kill with pid
|
|
64
|
+
* identity verification, and process-exit cleanup registration.
|
|
63
65
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* OR translate(raw, ctx) → stateless passthrough (claude)
|
|
66
|
+
* The sole production subclass is ClaudeSession (src/runner/claude/session.ts),
|
|
67
|
+
* a long-lived interactive session that overrides `run()` with its own
|
|
68
|
+
* per-turn consumption loop and reads `spawnStderr` / `stoppedByUser` /
|
|
69
|
+
* `commandNotFoundSeen` to build its terminal result events.
|
|
69
70
|
*
|
|
70
|
-
*
|
|
71
|
-
* -
|
|
72
|
-
* - createStreamReader(stdout) → JSONL or readline parser (default createJSONLStream)
|
|
73
|
-
* - awaitSpawnError(proc) → wait for spawn error (default once('error'))
|
|
71
|
+
* Subclasses override:
|
|
72
|
+
* - buildArgv(opts) → agent-specific CLI flags (required)
|
|
74
73
|
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* `runAgentStreamToEnd` error handling (which accepts both thrown errors and
|
|
79
|
-
* yielded error-result events).
|
|
74
|
+
* Optional hooks (with sensible defaults):
|
|
75
|
+
* - getStdio() → stdio config (default ['ignore','pipe','pipe'])
|
|
76
|
+
* - createStreamReader(stdout) → JSONL or readline parser (default createJSONLStream)
|
|
80
77
|
*/
|
|
81
78
|
export class SpawningRunner {
|
|
82
79
|
currentProcess = null;
|
|
@@ -91,26 +88,23 @@ export class SpawningRunner {
|
|
|
91
88
|
* Log prefix used in all operational log lines emitted by this runner
|
|
92
89
|
* (spawn, pid file, non-zero exit, stderr, killOrphan, stop cleanup) and
|
|
93
90
|
* as the SpawnHeartbeat label. Subclasses pass their own tag (e.g.
|
|
94
|
-
* 'claude-runner') so operators can grep agent-specific logs
|
|
95
|
-
* 'spawning-runner' keeps the base-class anchor tests' neutral identity.
|
|
91
|
+
* 'claude-runner') so operators can grep agent-specific logs.
|
|
96
92
|
*/
|
|
97
93
|
logTag;
|
|
98
|
-
/**
|
|
99
|
-
* The `message` argument from the most recent `run(message, opts)` call.
|
|
100
|
-
* Set in `run()` before `buildArgv(opts)` is invoked so subclasses can
|
|
101
|
-
* embed the message in their agent-specific argv (e.g. claude's `-p
|
|
102
|
-
* <message>`). This avoids changing the `buildArgv(opts)` signature,
|
|
103
|
-
* which would break existing anchor subclasses.
|
|
104
|
-
*/
|
|
105
|
-
currentMessage = '';
|
|
106
94
|
/**
|
|
107
95
|
* Whether the current run was interrupted by user-initiated stop().
|
|
108
96
|
* Set to true by stop() when a running process is being terminated;
|
|
109
|
-
* reset
|
|
110
|
-
*
|
|
111
|
-
* (error vs success) and the error message precedence.
|
|
97
|
+
* subclasses reset it at the start of a turn and read it when building
|
|
98
|
+
* the terminal result event (interrupted vs error precedence).
|
|
112
99
|
*/
|
|
113
100
|
stoppedByUser = false;
|
|
101
|
+
/**
|
|
102
|
+
* §4.4 win32 command-not-found 嗅探嫌疑标记:stderr 命中特征行时置位,
|
|
103
|
+
* 但**不**立即杀进程——agent 正常输出可能引用该错误文本(调试 Windows
|
|
104
|
+
* 报错场景),见行即杀会误杀 run。定性走双条件:子类收尾时「标记在 +
|
|
105
|
+
* 非零退出」才判 command-not-found;真挂起由既有 grace/超时兜底。
|
|
106
|
+
*/
|
|
107
|
+
commandNotFoundSeen = false;
|
|
114
108
|
constructor(opts) {
|
|
115
109
|
// Subclasses set this.binary after super()
|
|
116
110
|
this.binary = '';
|
|
@@ -130,15 +124,15 @@ export class SpawningRunner {
|
|
|
130
124
|
}
|
|
131
125
|
// --- Hooks (subclasses override as needed) ---
|
|
132
126
|
/**
|
|
133
|
-
* stdio config for spawn. Default: stdin ignored (
|
|
134
|
-
*
|
|
127
|
+
* stdio config for spawn. Default: stdin ignored (agents pass the prompt
|
|
128
|
+
* via argv).
|
|
135
129
|
*/
|
|
136
130
|
getStdio() {
|
|
137
131
|
return ['ignore', 'pipe', 'pipe'];
|
|
138
132
|
}
|
|
139
133
|
/**
|
|
140
134
|
* Create an async iterator over the child's stdout. Default: createJSONLStream
|
|
141
|
-
*
|
|
135
|
+
* with P1-4 backpressure enabled (pauseThreshold=100).
|
|
142
136
|
*/
|
|
143
137
|
createStreamReader(stdout) {
|
|
144
138
|
return createJSONLStream(stdout, {
|
|
@@ -149,21 +143,14 @@ export class SpawningRunner {
|
|
|
149
143
|
resumeThreshold: 50,
|
|
150
144
|
});
|
|
151
145
|
}
|
|
152
|
-
/**
|
|
153
|
-
* Create a stateful translator for this run. Default: null (uses the
|
|
154
|
-
* stateless passthrough). pi overrides to return its translator instance.
|
|
155
|
-
*/
|
|
156
|
-
createTranslator(_opts) {
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
159
146
|
/**
|
|
160
147
|
* Await the spawn 'error' event when proc.pid === undefined. Races a
|
|
161
148
|
* finite timeout (review P2-11) against the 'error' event: Node guarantees
|
|
162
149
|
* 'error' for ENOENT/EACCES, but some binaries fail silently without ever
|
|
163
|
-
* emitting it
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
150
|
+
* emitting it. Without the race, the spawn lead-in hangs forever → the
|
|
151
|
+
* workspace serial queue never settles → permanent deadlock, and /stop
|
|
152
|
+
* can't recover (ProcessStopper returns early when pid === undefined).
|
|
153
|
+
* The timeout keeps the deadlock bounded.
|
|
167
154
|
*/
|
|
168
155
|
awaitSpawnError(proc) {
|
|
169
156
|
return Promise.race([
|
|
@@ -180,22 +167,18 @@ export class SpawningRunner {
|
|
|
180
167
|
*
|
|
181
168
|
* Throws a `SpawnChildError` (carrying the user-facing spawn failure
|
|
182
169
|
* message) when the process fails to spawn (binary missing / pid undefined).
|
|
183
|
-
* On success returns the spawned child
|
|
184
|
-
*
|
|
185
|
-
* Shared by the base `run()` (yields an authErrorEvent on failure) and
|
|
186
|
-
* ClaudeSession's `doStartProcess` (returns the error message), which would
|
|
187
|
-
* otherwise duplicate ~60 lines of spawn lead-in with two copies of the
|
|
188
|
-
* STDERR_TAIL_BYTES cap.
|
|
170
|
+
* On success returns the spawned child.
|
|
189
171
|
*/
|
|
190
172
|
async spawnChild(opts) {
|
|
191
|
-
const proc =
|
|
173
|
+
const proc = spawnProcess(this.binary, this.buildArgv(opts), {
|
|
192
174
|
cwd: opts.cwd,
|
|
193
175
|
stdio: this.getStdio(),
|
|
194
176
|
env: process.env,
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
detached:
|
|
177
|
+
// posix 建新进程组以便负 PID 组杀(壳包装/子进程一并终止);win32 不
|
|
178
|
+
// detached —— `.cmd` 垫片在 DETACHED_PROCESS 下丢 stdio(见
|
|
179
|
+
// useDetachedProcessGroup),树杀由 taskkill /T 负责。
|
|
180
|
+
detached: useDetachedProcessGroup(),
|
|
181
|
+
windowsHide: true,
|
|
199
182
|
});
|
|
200
183
|
this.currentProcess = proc;
|
|
201
184
|
getLogger().info(`[${this.logTag}] spawn pid=${proc.pid ?? '(none)'} binary=${this.binary} ` +
|
|
@@ -231,6 +214,14 @@ export class SpawningRunner {
|
|
|
231
214
|
const text = chunk.toString('utf-8').trim();
|
|
232
215
|
if (text) {
|
|
233
216
|
this.spawnStderr = (this.spawnStderr + '\n' + text).trim().slice(-STDERR_TAIL_BYTES);
|
|
217
|
+
// §4.4 win32 command-not-found 嗅探:经 cmd 垫片启动失败不是 spawn 的
|
|
218
|
+
// ENOENT,而是 stderr 的 "is not recognized..." 行。命中只记嫌疑标记
|
|
219
|
+
// (双条件之一),不立即杀——定性由子类收尾的「标记 + 非零退出」
|
|
220
|
+
// 双条件完成;子进程真挂起由既有 grace/超时看门狗兜底。
|
|
221
|
+
if (isWindowsCommandNotFoundLine(text)) {
|
|
222
|
+
this.commandNotFoundSeen = true;
|
|
223
|
+
getLogger().error(`[${this.logTag}] command not found suspected (win32 shim): ${text}`);
|
|
224
|
+
}
|
|
234
225
|
// P2-16: most agent CLIs emit progress/warnings/deprecation notices on
|
|
235
226
|
// stderr, not real errors. Logging each chunk at error level drowns out
|
|
236
227
|
// genuine errors. Downgrade to warn — the accumulated stderr is already
|
|
@@ -240,151 +231,11 @@ export class SpawningRunner {
|
|
|
240
231
|
});
|
|
241
232
|
return proc;
|
|
242
233
|
}
|
|
243
|
-
/**
|
|
244
|
-
* Spawn the agent binary with argv built by buildArgv(opts), iterate its
|
|
245
|
-
* stdout stream, and yield translated events. Always emits a unified
|
|
246
|
-
* result event at the end via `buildResultEvent(...)` — never throws on
|
|
247
|
-
* non-zero exit (the bridge accepts both thrown errors and yielded
|
|
248
|
-
* error-result events, and the latter carries richer diagnostic info).
|
|
249
|
-
*
|
|
250
|
-
* Spawn failures (binary missing, pid undefined) yield an `authErrorEvent`
|
|
251
|
-
* instead of throwing, so the user sees a friendly error card.
|
|
252
|
-
*/
|
|
253
|
-
async *run(message, opts) {
|
|
254
|
-
if (this.isRunning) {
|
|
255
|
-
getLogger().warn(`[${this.logTag}] run() called while already running, refusing`);
|
|
256
|
-
throw new Error(`${this.binary} process already running`);
|
|
257
|
-
}
|
|
258
|
-
this.stoppedByUser = false;
|
|
259
|
-
this.currentMessage = message;
|
|
260
|
-
let proc;
|
|
261
|
-
try {
|
|
262
|
-
proc = await this.spawnChild(opts);
|
|
263
|
-
}
|
|
264
|
-
catch (err) {
|
|
265
|
-
// §9.22: yield synthetic init before the error result so bridge/run-state
|
|
266
|
-
// guards don't silently drop it.
|
|
267
|
-
yield syntheticInitEvent(opts.sessionId);
|
|
268
|
-
yield authErrorEvent(err.message);
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
const completion = new Promise((resolve) => {
|
|
272
|
-
proc.once('error', (err) => {
|
|
273
|
-
this.spawnHeartbeat.clear();
|
|
274
|
-
getLogger().error(`[${this.logTag}] spawn failed: ${err.message} binary=${this.binary} cwd=${opts.cwd}`);
|
|
275
|
-
// Resolve (not reject) so the unified result event is emitted
|
|
276
|
-
// via buildResultEvent instead of throwing past the finally
|
|
277
|
-
// into the bridge's catch (which would leak the pid file).
|
|
278
|
-
// P2-12: a post-spawn 'error' (e.g. kill failure, broken pipe)
|
|
279
|
-
// MUST surface as an error result, not success. Previously
|
|
280
|
-
// {code:null,signal:null} was classified as success by
|
|
281
|
-
// buildResultEvent; code=1 forces the error branch so the
|
|
282
|
-
// failure is never silently swallowed as a successful run.
|
|
283
|
-
resolve({ code: 1, signal: null });
|
|
284
|
-
});
|
|
285
|
-
proc.once('close', (code, signal) => {
|
|
286
|
-
this.spawnHeartbeat.clear();
|
|
287
|
-
resolve({ code, signal });
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
// Create translator (per-run state) — null for stateless passthrough
|
|
291
|
-
const translator = this.createTranslator(opts);
|
|
292
|
-
// Create stream reader (jsonl or readline)
|
|
293
|
-
const stream = this.createStreamReader(proc.stdout);
|
|
294
|
-
try {
|
|
295
|
-
// Stream loop wrapped in try/catch so a stream error (e.g. stdout
|
|
296
|
-
// 'error' event propagated through the async generator) does not
|
|
297
|
-
// bypass the completion await and result event emission. Matches
|
|
298
|
-
// kimi's long-standing behavior; safe for all runners because the
|
|
299
|
-
// completion promise still resolves via proc 'close'.
|
|
300
|
-
try {
|
|
301
|
-
for await (const rawEvent of stream) {
|
|
302
|
-
let translated;
|
|
303
|
-
if (translator) {
|
|
304
|
-
// Per-event error isolation: a translator throw must not kill
|
|
305
|
-
// the stream (pi's accumulator could throw on malformed input).
|
|
306
|
-
try {
|
|
307
|
-
translated = translator.translate(rawEvent);
|
|
308
|
-
}
|
|
309
|
-
catch (err) {
|
|
310
|
-
getLogger().warn(`[${this.logTag}] translator error: ${err.message}, raw: ${JSON.stringify(rawEvent).slice(0, 100)}`);
|
|
311
|
-
continue;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
else {
|
|
315
|
-
translated = this.translate(rawEvent, { message, opts });
|
|
316
|
-
}
|
|
317
|
-
if (translated === null)
|
|
318
|
-
continue;
|
|
319
|
-
if (Array.isArray(translated)) {
|
|
320
|
-
for (const e of translated)
|
|
321
|
-
yield e;
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
yield translated;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
catch (error) {
|
|
329
|
-
getLogger().error(`[${this.logTag}] stream error: ${error}`);
|
|
330
|
-
}
|
|
331
|
-
const { code, signal } = await completion;
|
|
332
|
-
// If the stream ended before a terminal event, let the translator
|
|
333
|
-
// record its terminal state (e.g. codex `finish('failed')` stashes
|
|
334
|
-
// "stream ended before a terminal event"). The runner folds this
|
|
335
|
-
// into buildResultEvent below.
|
|
336
|
-
if (translator && !translator.isTerminal()) {
|
|
337
|
-
const reason = this.stoppedByUser ? 'interrupted' : 'failed';
|
|
338
|
-
translator.finish(reason);
|
|
339
|
-
}
|
|
340
|
-
// Resolve translatorError precedence (most-specific first):
|
|
341
|
-
// - agent-reported terminal error always wins
|
|
342
|
-
// - stream-ended-early symptom only surfaces with no external cause
|
|
343
|
-
let translatorError;
|
|
344
|
-
if (translator) {
|
|
345
|
-
const agentError = translator.hasAgentTerminalError();
|
|
346
|
-
const hasExternalCause = this.stoppedByUser || signal !== null || (code !== null && code !== 0);
|
|
347
|
-
translatorError =
|
|
348
|
-
agentError || !hasExternalCause ? translator.getTerminalError() : undefined;
|
|
349
|
-
}
|
|
350
|
-
const nonUserError = !this.stoppedByUser && ((code !== null && code !== 0) || signal !== null);
|
|
351
|
-
yield this.buildResultEvent({
|
|
352
|
-
code,
|
|
353
|
-
signal,
|
|
354
|
-
stderr: this.spawnStderr,
|
|
355
|
-
sessionId: translator?.getSessionId?.() ?? '',
|
|
356
|
-
usage: translator?.getLastUsage?.(),
|
|
357
|
-
translatorError,
|
|
358
|
-
});
|
|
359
|
-
if (nonUserError) {
|
|
360
|
-
getLogger().error(`[${this.logTag}] non-zero exit code=${code} signal=${signal} stderr=${this.spawnStderr.slice(-STDERR_LOG_TAIL)}`);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
finally {
|
|
364
|
-
this.spawnHeartbeat.clear();
|
|
365
|
-
// P1-11: 消费者提前关闭生成器(for-await 循环体抛错/break 触发的 .return())
|
|
366
|
-
// 会跳过 completion await 直接进 finally——此时子进程可能仍在运行。若不杀,
|
|
367
|
-
// 它会成为 stop()(currentProcess 已置 null)、killOrphan()(pid 文件已删)、
|
|
368
|
-
// exit handler(同一字段)都够不到的孤儿黑洞,且 stdout 管道背压会把它永久
|
|
369
|
-
// 阻塞。正常完成路径进程已退出,stop 是 no-op,零成本。
|
|
370
|
-
const proc = this.currentProcess;
|
|
371
|
-
if (proc && proc.exitCode === null && proc.signalCode === null) {
|
|
372
|
-
try {
|
|
373
|
-
await this.stopper.stop(proc, { immediate: true });
|
|
374
|
-
}
|
|
375
|
-
catch {
|
|
376
|
-
/* ignore */
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
this.currentProcess = null;
|
|
380
|
-
silentlyUnlink(this.pidFilePath);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
234
|
/**
|
|
384
235
|
* Stop the current process if one is running. Delegates the actual
|
|
385
236
|
* SIGTERM → grace → SIGKILL sequence to `this.stopper.stop(proc, opts)`
|
|
386
237
|
* (see `src/runner/common/process-stopper.ts`), forwarding the `immediate`
|
|
387
|
-
* flag verbatim so all
|
|
238
|
+
* flag verbatim so all subclasses inherit identical stop semantics.
|
|
388
239
|
*/
|
|
389
240
|
async stop(opts) {
|
|
390
241
|
const proc = this.currentProcess;
|
|
@@ -497,7 +348,7 @@ export class SpawningRunner {
|
|
|
497
348
|
/**
|
|
498
349
|
* Shared process-exit cleanup: SIGTERM the still-running child process and
|
|
499
350
|
* remove the pid file. Called by the singleton dispatcher for every
|
|
500
|
-
* registered runner; previously duplicated verbatim in
|
|
351
|
+
* registered runner; previously duplicated verbatim in the runner
|
|
501
352
|
* subclasses.
|
|
502
353
|
*/
|
|
503
354
|
cleanupOnExit() {
|
|
@@ -512,75 +363,5 @@ export class SpawningRunner {
|
|
|
512
363
|
}
|
|
513
364
|
silentlyUnlink(this.pidFilePath);
|
|
514
365
|
}
|
|
515
|
-
/**
|
|
516
|
-
* Build a unified result event from process exit state. Centralizes the
|
|
517
|
-
* result-event semantics specified in Candidate 2:
|
|
518
|
-
* - stoppedByUser → error ("interrupted by user")
|
|
519
|
-
* - translatorError set → error (translator-supplied message, e.g. codex
|
|
520
|
-
* `turn.failed` or "stream ended before a terminal event")
|
|
521
|
-
* - code !== 0 && code !== null → error (exit code + stderr tail)
|
|
522
|
-
* - signal !== null → error (killed by signal)
|
|
523
|
-
* - otherwise → success (with optional usage)
|
|
524
|
-
*
|
|
525
|
-
* Error message precedence (most-specific first):
|
|
526
|
-
* - stoppedByUser: "{binary} interrupted by user"
|
|
527
|
-
* - translatorError: verbatim translator-supplied message
|
|
528
|
-
* - signal: "{binary} killed by signal {signal}{stderr tail}"
|
|
529
|
-
* - non-zero code: "{binary} exited code={code}{stderr tail}"
|
|
530
|
-
*
|
|
531
|
-
* @param opts.code - Process exit code (null if killed by signal)
|
|
532
|
-
* @param opts.signal - Process signal code (null if exited normally)
|
|
533
|
-
* @param opts.stderr - Accumulated stderr (tail included in code/signal errors)
|
|
534
|
-
* @param opts.sessionId - Session ID to include in the result event
|
|
535
|
-
* @param opts.usage - Token usage to include on success
|
|
536
|
-
* @param opts.translatorError - Agent-specific terminal error message (e.g.
|
|
537
|
-
* codex turn.failed, or "stream ended early")
|
|
538
|
-
* @returns AgentEvent with type='result'
|
|
539
|
-
*/
|
|
540
|
-
buildResultEvent(opts) {
|
|
541
|
-
const isError = this.stoppedByUser ||
|
|
542
|
-
opts.translatorError !== undefined ||
|
|
543
|
-
(opts.code !== null && opts.code !== 0) ||
|
|
544
|
-
opts.signal !== null;
|
|
545
|
-
const sessionId = opts.sessionId ?? '';
|
|
546
|
-
if (isError) {
|
|
547
|
-
let errorMessage;
|
|
548
|
-
const stderrTail = opts.stderr ? opts.stderr.slice(-STDERR_LOG_TAIL) : '';
|
|
549
|
-
if (this.stoppedByUser) {
|
|
550
|
-
errorMessage = `${this.binary} interrupted by user`;
|
|
551
|
-
}
|
|
552
|
-
else if (opts.translatorError !== undefined) {
|
|
553
|
-
errorMessage = opts.translatorError;
|
|
554
|
-
}
|
|
555
|
-
else if (opts.signal !== null) {
|
|
556
|
-
errorMessage = `${this.binary} killed by signal ${opts.signal}${stderrTail ? `: ${stderrTail}` : ''}`;
|
|
557
|
-
}
|
|
558
|
-
else {
|
|
559
|
-
errorMessage = `${this.binary} exited code=${opts.code}${stderrTail ? `: ${stderrTail}` : ''}`;
|
|
560
|
-
}
|
|
561
|
-
return {
|
|
562
|
-
type: 'result',
|
|
563
|
-
subtype: 'error',
|
|
564
|
-
session_id: sessionId,
|
|
565
|
-
errorMessage,
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
return {
|
|
569
|
-
type: 'result',
|
|
570
|
-
subtype: 'success',
|
|
571
|
-
session_id: sessionId,
|
|
572
|
-
...(opts.usage ? { usage: opts.usage } : {}),
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
/**
|
|
576
|
-
* Stateless passthrough translator hook. Used by ClaudeRunner
|
|
577
|
-
* which does not need per-run translation state. Subclasses that return
|
|
578
|
-
* a translator from `createTranslator(opts)` leave this as the default
|
|
579
|
-
* no-op — the base `run()` loop only calls `translate()` when
|
|
580
|
-
* `createTranslator()` returns null.
|
|
581
|
-
*/
|
|
582
|
-
translate(_rawEvent, _ctx) {
|
|
583
|
-
return null;
|
|
584
|
-
}
|
|
585
366
|
}
|
|
586
367
|
//# sourceMappingURL=spawning-runner.js.map
|
|
@@ -25,6 +25,8 @@ interface NormalizedResultUsage {
|
|
|
25
25
|
totalTokens?: number;
|
|
26
26
|
/** Model context window limit (codex app-server `modelContextWindow` passthrough). */
|
|
27
27
|
contextLimit?: number;
|
|
28
|
+
/** Reasoning tokens (pi usage.reasoning). Undefined when not provided. */
|
|
29
|
+
reasoningTokens?: number;
|
|
28
30
|
/** Reconstructed or agent-declared context length (see JSDoc above). */
|
|
29
31
|
contextLength: number;
|
|
30
32
|
}
|
|
@@ -15,6 +15,7 @@ export function normalizeResultUsage(usage) {
|
|
|
15
15
|
const cacheCreation = usage.cache_creation_tokens ?? usage.cache_creation_input_tokens;
|
|
16
16
|
const totalTokens = usage.total_tokens;
|
|
17
17
|
const contextLimit = usage.context_limit;
|
|
18
|
+
const reasoningTokens = usage.reasoning_tokens;
|
|
18
19
|
// total_tokens (agent-declared total) takes priority; otherwise reconstruct
|
|
19
20
|
// from the parts: input + cacheRead + cacheCreation + output.
|
|
20
21
|
const contextLength = totalTokens ?? inputTokens + (cacheRead ?? 0) + (cacheCreation ?? 0) + outputTokens;
|
|
@@ -25,6 +26,7 @@ export function normalizeResultUsage(usage) {
|
|
|
25
26
|
cacheCreationTokens: cacheCreation,
|
|
26
27
|
totalTokens,
|
|
27
28
|
contextLimit,
|
|
29
|
+
reasoningTokens,
|
|
28
30
|
contextLength,
|
|
29
31
|
};
|
|
30
32
|
}
|
package/dist/runner/index.d.ts
CHANGED
|
@@ -15,6 +15,6 @@ export { KimiAcpRunner } from './kimi/index.js';
|
|
|
15
15
|
export { DshRunner } from './dsh/index.js';
|
|
16
16
|
export { BashProcessRunner, type BashRunner } from './bash/index.js';
|
|
17
17
|
export { AgentRegistry } from './registry.js';
|
|
18
|
-
export type { ResultEvent, PlanEvent, FileChangeEvent, AgentEvent, Runner, RunnerLifetime, AgentKind, AgentSession, AgentSessionContentEvent, AgentSessionUsage, SessionContent, SessionSummary, AgentSessionReader, AgentRunner, TurnStartedEvent, TurnDiffEvent, ApprovalView, ApprovalRequestedEvent, ApprovalResolvedEvent, } from './types.js';
|
|
18
|
+
export type { ResultEvent, PlanEvent, FileChangeEvent, AgentEvent, Runner, RunnerLifetime, AgentKind, AgentSession, AgentSessionContentEvent, AgentSessionUsage, SessionContent, SessionSummary, AgentSessionReader, AgentRunner, TurnStartedEvent, TurnDiffEvent, NoticeEvent, SessionInfoEvent, ToolHint, ApprovalView, ApprovalRequestedEvent, ApprovalResolvedEvent, } from './types.js';
|
|
19
19
|
export { resolveAgentChoices } from './resolve-agent-choices.js';
|
|
20
20
|
export { syncAgentChoices } from './sync-agent-choices.js';
|
|
@@ -6,15 +6,23 @@
|
|
|
6
6
|
* → synthetic init → session/prompt → consume session/update notifications
|
|
7
7
|
* until prompt settles → result event.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* Turn/connection/compact/approval orchestration lives in BaseAcpRunner.
|
|
10
|
+
* Kimi-specific parts:
|
|
11
|
+
* - compaction is a BACKGROUND task: after the compact prompt settles, poll
|
|
12
|
+
* wire.jsonl for a NEW terminal compaction record (§5.2, never fake success)
|
|
13
|
+
* - terminal/* reverse RPC: kimi delegates Bash execution to the client —
|
|
14
|
+
* this runner spawns one-shot local bash processes (handleTerminalRequest)
|
|
15
|
+
* - terminal-backed Bash tool notifications are filtered (bashToolCallIds) so
|
|
16
|
+
* the run card doesn't get duplicate output-less Bash panels
|
|
17
|
+
* - AskUserQuestion flows through elicitation forms + a request_permission
|
|
18
|
+
* question bridge (buildQuestionResponse)
|
|
13
19
|
*/
|
|
14
|
-
import type { AgentKind, AgentSessionReader,
|
|
15
|
-
import { JsonRpcClient
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
20
|
+
import type { AgentKind, AgentSessionReader, AgentStatusInfo, SpawnOptions } from '../../types.js';
|
|
21
|
+
import { JsonRpcClient } from '../../common/jsonrpc/client.js';
|
|
22
|
+
import { KimiAcpTranslator } from './translator.js';
|
|
23
|
+
import { type SessionPromptResult } from '../../common/acp/protocol-types.js';
|
|
24
|
+
import { type AcpPendingApproval } from '../../common/acp/protocol-helpers.js';
|
|
25
|
+
import { BaseAcpRunner } from '../../common/acp/base-acp-runner.js';
|
|
18
26
|
export interface KimiAcpRunnerOptions {
|
|
19
27
|
kind: AgentKind;
|
|
20
28
|
sessionReader: AgentSessionReader;
|
|
@@ -39,15 +47,15 @@ export interface KimiAcpRunnerOptions {
|
|
|
39
47
|
/** Kimi permission mode (user-facing: manual/auto/yolo). */
|
|
40
48
|
permissionMode?: 'manual' | 'auto' | 'yolo';
|
|
41
49
|
}
|
|
42
|
-
export declare class KimiAcpRunner extends
|
|
43
|
-
private connectionManager;
|
|
44
|
-
private currentTranslator;
|
|
45
|
-
private activeSessionId;
|
|
46
|
-
private model?;
|
|
50
|
+
export declare class KimiAcpRunner extends BaseAcpRunner<KimiAcpTranslator> {
|
|
47
51
|
private permissionMode;
|
|
48
52
|
private readonly compactIdleTimeoutMs;
|
|
49
|
-
/**
|
|
50
|
-
|
|
53
|
+
/**
|
|
54
|
+
* §5.2 compaction baseline: wire.jsonl records sampled by compactBaseline()
|
|
55
|
+
* BEFORE the compact prompt fires (reset per compact run). onCompactPromptFired
|
|
56
|
+
* derives both the terminal-count baseline and the total-count probe from it.
|
|
57
|
+
*/
|
|
58
|
+
private compactBaselineRecords;
|
|
51
59
|
/** 存活 terminal(kimi 把 Bash 执行下放客户端):terminalId → 本地句柄。 */
|
|
52
60
|
private terminals;
|
|
53
61
|
/** terminalId 自增序号(term-1, term-2, …)。 */
|
|
@@ -63,32 +71,29 @@ export declare class KimiAcpRunner extends ConnectionBasedRunner<KimiAcpClient,
|
|
|
63
71
|
/** 当前 run 的 session cwd(terminal/create 未给 cwd 时回退用)。 */
|
|
64
72
|
private activeCwd;
|
|
65
73
|
private readonly processStopper;
|
|
66
|
-
/** Tracker for the in-flight prompt request — needed for cancellation. */
|
|
67
|
-
private promptSettled;
|
|
68
74
|
constructor(opts: KimiAcpRunnerOptions);
|
|
69
75
|
protected get logTag(): string;
|
|
70
76
|
protected get turnTimeoutErrorMessage(): string;
|
|
71
77
|
protected get turnInterruptedErrorMessage(): string;
|
|
72
|
-
protected
|
|
78
|
+
protected get connectionClosedMessage(): string;
|
|
73
79
|
protected shouldDeferStop(): boolean;
|
|
74
|
-
protected
|
|
75
|
-
protected
|
|
76
|
-
protected releaseConnection(cwd: string): Promise<void>;
|
|
77
|
-
protected notifyIdle(cwd: string): void;
|
|
78
|
-
protected disposeConnections(): Promise<void>;
|
|
80
|
+
protected createTranslator(): KimiAcpTranslator;
|
|
81
|
+
protected applyTurnSettings(client: JsonRpcClient, sessionId: string): Promise<void>;
|
|
79
82
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
83
|
+
* §5.2: compaction is a BACKGROUND task — the prompt settles before the
|
|
84
|
+
* wire.jsonl terminal record lands. Baselines are captured BEFORE the
|
|
85
|
+
* compact prompt fires (see compactBaseline override — a begin record
|
|
86
|
+
* landing after the fire must count as new activity, not baseline), then
|
|
87
|
+
* wait for a NEW terminal (complete/apply → completed; cancel → cancelled)
|
|
88
|
+
* before producing the result. There is NO total timeout — silence beyond
|
|
89
|
+
* compactIdleTimeoutMs yields 'unknown' (never a fake success). Keeps the
|
|
90
|
+
* connection alive while the background compaction finishes — otherwise
|
|
91
|
+
* dispose/exit kills it and the record never lands (S5/S6 打回复现 /
|
|
92
|
+
* 2026-08-31 事故).
|
|
85
93
|
*/
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
* ApprovalAction (`{ action: 'accept' | 'decline' | 'cancel' }`).
|
|
90
|
-
*/
|
|
91
|
-
respondApproval(requestId: number | string, response: unknown): Promise<void>;
|
|
94
|
+
protected compactBaseline(sessionId: string, opts: SpawnOptions): number;
|
|
95
|
+
protected onCompactPromptFired(promptPromise: Promise<SessionPromptResult>, translator: KimiAcpTranslator, sessionId: string, opts: SpawnOptions, baselineTotalCount: number): void;
|
|
96
|
+
protected buildApprovalOutcome(action: string, pending: AcpPendingApproval, response: unknown): unknown;
|
|
92
97
|
getStatusInfo(): AgentStatusInfo;
|
|
93
98
|
/**
|
|
94
99
|
* Hot-apply a permission-mode change to the live ACP session (§P5).
|
|
@@ -102,12 +107,6 @@ export declare class KimiAcpRunner extends ConnectionBasedRunner<KimiAcpClient,
|
|
|
102
107
|
updateApprovalMode(settings: {
|
|
103
108
|
permissionMode?: 'manual' | 'auto' | 'yolo';
|
|
104
109
|
}): Promise<void>;
|
|
105
|
-
/**
|
|
106
|
-
* Acquire the connection and set up the turn:
|
|
107
|
-
* session/new (new session) or session/resume (existing sessionId)
|
|
108
|
-
* → set permission mode if needed → session/prompt.
|
|
109
|
-
*/
|
|
110
|
-
protected setupTurn(message: string, opts: SpawnOptions): Promise<void>;
|
|
111
110
|
/**
|
|
112
111
|
* §5.2: 等 wire.jsonl 出现 NEW 压缩终态记录,返回判别结果而非 void。
|
|
113
112
|
*
|
|
@@ -123,7 +122,7 @@ export declare class KimiAcpRunner extends ConnectionBasedRunner<KimiAcpClient,
|
|
|
123
122
|
* wakeWaiters),避免数分钟的压缩被 consumeTurn 滚动空闲看门狗误杀。
|
|
124
123
|
*/
|
|
125
124
|
private waitForCompactionTerminal;
|
|
126
|
-
|
|
125
|
+
protected handleNotification(method: string, params: unknown): void;
|
|
127
126
|
/**
|
|
128
127
|
* Filter kimi's own Bash tool notifications so the run card doesn't get a
|
|
129
128
|
* duplicate, output-less Bash panel alongside the runner-produced one.
|
|
@@ -135,7 +134,7 @@ export declare class KimiAcpRunner extends ConnectionBasedRunner<KimiAcpClient,
|
|
|
135
134
|
* Returns true when the notification should be swallowed.
|
|
136
135
|
*/
|
|
137
136
|
private filterBashNotifications;
|
|
138
|
-
|
|
137
|
+
protected handleServerRequest(id: number | string, method: string, params: unknown): void;
|
|
139
138
|
/**
|
|
140
139
|
* Handle a kimi terminal/* reverse RPC by spawning a one-shot local process.
|
|
141
140
|
*
|
|
@@ -161,4 +160,8 @@ export declare class KimiAcpRunner extends ConnectionBasedRunner<KimiAcpClient,
|
|
|
161
160
|
private handleTerminalRelease;
|
|
162
161
|
/** Kill every live terminal (dispose/connection-close 清理钩子)。 */
|
|
163
162
|
private cleanupTerminals;
|
|
163
|
+
protected onConnectionAcquired(opts: SpawnOptions): void;
|
|
164
|
+
protected onConnectionClosed(): void;
|
|
165
|
+
protected onClearTurnState(): void;
|
|
166
|
+
protected beforeDisposeConnections(): Promise<void>;
|
|
164
167
|
}
|