lark-remote 0.4.5 → 0.5.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 +18 -0
- package/dist/bridge/approval-coordinator.d.ts +1 -1
- package/dist/bridge/approval-coordinator.js +3 -3
- package/dist/bridge/inbound-media.js +10 -7
- package/dist/bridge/index.d.ts +9 -9
- package/dist/bridge/index.js +41 -41
- package/dist/bridge/queue-manager.d.ts +12 -12
- package/dist/bridge/queue-manager.js +19 -19
- package/dist/card/approval-render.d.ts +1 -1
- package/dist/card/approval-render.js +6 -6
- package/dist/card/bash-card-session.d.ts +3 -3
- package/dist/card/bash-card-session.js +3 -3
- package/dist/card/bash-renderer.js +1 -1
- package/dist/card/card-session.d.ts +4 -4
- package/dist/card/card-session.js +6 -6
- package/dist/card/run-card-session.js +2 -2
- package/dist/card/run-renderer.d.ts +1 -1
- package/dist/card/run-renderer.js +14 -14
- package/dist/card/run-state.d.ts +2 -2
- package/dist/card/run-state.js +3 -3
- package/dist/card/tool-render.d.ts +1 -1
- package/dist/card/tool-render.js +6 -6
- package/dist/clone.d.ts +5 -3
- package/dist/clone.js +8 -4
- package/dist/common/truncate.d.ts +1 -1
- package/dist/common/truncate.js +2 -2
- package/dist/common/ttl-cache.d.ts +2 -2
- package/dist/common/ttl-cache.js +2 -2
- package/dist/config/codex-config.d.ts +9 -9
- package/dist/config/codex-config.js +18 -18
- package/dist/config/dir.js +5 -2
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.js +6 -6
- package/dist/config/opencode-config.js +3 -3
- package/dist/connector/index.d.ts +5 -5
- package/dist/connector/index.js +24 -11
- package/dist/inbound/event.d.ts +1 -1
- package/dist/inbound/event.js +1 -1
- package/dist/inbound/placeholder.d.ts +2 -2
- package/dist/inbound/placeholder.js +2 -2
- package/dist/index.js +9 -9
- package/dist/instance-lock.js +4 -3
- package/dist/logger/index.d.ts +1 -1
- package/dist/logger/index.js +4 -4
- package/dist/persistence/atomic-write.d.ts +1 -1
- package/dist/persistence/atomic-write.js +6 -2
- package/dist/platform/agent-stopper.d.ts +1 -1
- package/dist/platform/identity.d.ts +7 -1
- package/dist/platform/identity.js +2 -2
- package/dist/platform/path.d.ts +6 -0
- package/dist/platform/path.js +14 -8
- package/dist/platform/select.d.ts +1 -1
- package/dist/platform/select.js +1 -1
- package/dist/platform/shell.d.ts +2 -3
- package/dist/platform/spawn.d.ts +2 -2
- package/dist/platform/spawn.js +2 -2
- package/dist/platform/terminator-posix.js +1 -1
- package/dist/platform/terminator-win32.js +2 -2
- package/dist/platform/terminator.d.ts +3 -3
- package/dist/platform/terminator.js +2 -2
- package/dist/platform/types.d.ts +1 -1
- package/dist/restart.d.ts +1 -1
- package/dist/restart.js +5 -3
- package/dist/router/config/codex.d.ts +1 -1
- package/dist/router/config/codex.js +2 -2
- package/dist/router/config/opencode.js +1 -1
- package/dist/router/index.d.ts +9 -9
- package/dist/router/index.js +34 -34
- package/dist/router/order-exec-dispatch.js +2 -2
- package/dist/runner/bash/runner.d.ts +2 -2
- package/dist/runner/bash/runner.js +6 -6
- package/dist/runner/claude/runner.js +1 -1
- package/dist/runner/claude/session.d.ts +2 -2
- package/dist/runner/claude/session.js +14 -14
- package/dist/runner/codex/app-server/runner.js +3 -3
- package/dist/runner/codex/app-server/translator.d.ts +2 -2
- package/dist/runner/codex/app-server/translator.js +5 -5
- package/dist/runner/common/acp/acp-approval.d.ts +2 -2
- package/dist/runner/common/acp/acp-approval.js +3 -3
- package/dist/runner/common/acp/base-acp-runner.js +1 -1
- package/dist/runner/common/acp/base-translator.d.ts +3 -3
- package/dist/runner/common/acp/base-translator.js +4 -4
- package/dist/runner/common/acp/protocol-types.js +1 -1
- package/dist/runner/common/connection-based-runner.d.ts +1 -1
- package/dist/runner/common/connection-based-runner.js +2 -2
- package/dist/runner/common/jsonl-stream.d.ts +2 -2
- package/dist/runner/common/jsonl-stream.js +12 -12
- package/dist/runner/common/jsonrpc/client.d.ts +1 -1
- package/dist/runner/common/jsonrpc/client.js +1 -1
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +1 -1
- package/dist/runner/common/jsonrpc/connection-manager.js +1 -1
- package/dist/runner/common/jsonrpc/transport.d.ts +1 -1
- package/dist/runner/common/jsonrpc/transport.js +2 -2
- package/dist/runner/common/spawning-runner.d.ts +21 -8
- package/dist/runner/common/spawning-runner.js +41 -15
- package/dist/runner/dsh/runner.js +2 -2
- package/dist/runner/kimi/acp/runner.d.ts +3 -3
- package/dist/runner/kimi/acp/runner.js +9 -9
- package/dist/runner/kimi/acp/translator.d.ts +2 -2
- package/dist/runner/kimi/acp/translator.js +4 -4
- package/dist/runner/opencode/acp/runner.d.ts +2 -2
- package/dist/runner/opencode/acp/runner.js +3 -3
- package/dist/runner/opencode/acp/translator.js +1 -1
- package/dist/runner/pi/rpc/runner.js +2 -2
- package/dist/runner/pi/rpc/translator.d.ts +1 -1
- package/dist/runner/pi/rpc/translator.js +4 -4
- package/dist/runner/types.d.ts +1 -1
- package/dist/session/claude/session-index.d.ts +12 -0
- package/dist/session/claude/session-index.js +20 -5
- package/dist/session/claude/sessions.js +10 -10
- package/dist/session/codex/rollout-reader.d.ts +4 -4
- package/dist/session/codex/rollout-reader.js +12 -12
- package/dist/session/common/two-pass.js +2 -2
- package/dist/session/common/usage-accumulator.d.ts +2 -2
- package/dist/session/common/usage-accumulator.js +2 -2
- package/dist/session/dsh/sessions.js +2 -2
- package/dist/session/kimi/sessions.d.ts +2 -2
- package/dist/session/kimi/sessions.js +11 -11
- package/dist/session/opencode/sessions.d.ts +3 -3
- package/dist/session/opencode/sessions.js +12 -10
- package/dist/session/pi/sessions.js +13 -12
- package/dist/session/session-store.d.ts +1 -1
- package/dist/session/session-store.js +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ACP approval/permission mapping and lifecycle shared by the kimi and
|
|
3
3
|
* opencode ACP runners.
|
|
4
4
|
*
|
|
5
|
-
* Extracted from runner.ts and translator.ts to avoid duplication
|
|
5
|
+
* Extracted from runner.ts and translator.ts to avoid duplication.
|
|
6
6
|
* Shape source: approval.ts:28-29 — optionId is opaque, echo back as-is.
|
|
7
7
|
*/
|
|
8
8
|
import type { ApprovalView } from '../../types.js';
|
|
@@ -33,7 +33,7 @@ export declare function buildAcpPermissionOutcome(action: string, options: Permi
|
|
|
33
33
|
/**
|
|
34
34
|
* 从服务端 options kind 派生审批决定列表:accept/decline/cancel 恒有;
|
|
35
35
|
* 带 always 类 option(kimi approve_always / opencode allow_always)才提供
|
|
36
|
-
* acceptForSession
|
|
36
|
+
* acceptForSession(「本会话总是允许」)。kimi/opencode 两侧共用。
|
|
37
37
|
*/
|
|
38
38
|
export declare function deriveAcpAvailableDecisions(options: PermissionOption[]): string[];
|
|
39
39
|
/** 截断长文本并追加省略号(translator 审批 reason 用,原先两份相同拷贝)。 */
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ACP approval/permission mapping and lifecycle shared by the kimi and
|
|
3
3
|
* opencode ACP runners.
|
|
4
4
|
*
|
|
5
|
-
* Extracted from runner.ts and translator.ts to avoid duplication
|
|
5
|
+
* Extracted from runner.ts and translator.ts to avoid duplication.
|
|
6
6
|
* Shape source: approval.ts:28-29 — optionId is opaque, echo back as-is.
|
|
7
7
|
*/
|
|
8
8
|
import { getLogger } from '../../../logger/index.js';
|
|
@@ -10,7 +10,7 @@ import { getLogger } from '../../../logger/index.js';
|
|
|
10
10
|
* Find an option by matching its `kind` against a list of candidate values,
|
|
11
11
|
* and return the `optionId` to echo back in the approval response.
|
|
12
12
|
* ACP option kind values vary between kimi versions; this handles both
|
|
13
|
-
* the documented names and the observed real names
|
|
13
|
+
* the documented names and the observed real names.
|
|
14
14
|
*/
|
|
15
15
|
function findOptionIdByKind(options, candidateKinds) {
|
|
16
16
|
for (const kind of candidateKinds) {
|
|
@@ -54,7 +54,7 @@ export function buildAcpPermissionOutcome(action, options, kinds) {
|
|
|
54
54
|
/**
|
|
55
55
|
* 从服务端 options kind 派生审批决定列表:accept/decline/cancel 恒有;
|
|
56
56
|
* 带 always 类 option(kimi approve_always / opencode allow_always)才提供
|
|
57
|
-
* acceptForSession
|
|
57
|
+
* acceptForSession(「本会话总是允许」)。kimi/opencode 两侧共用。
|
|
58
58
|
*/
|
|
59
59
|
export function deriveAcpAvailableDecisions(options) {
|
|
60
60
|
const decisions = ['accept', 'decline', 'cancel'];
|
|
@@ -15,7 +15,7 @@ export class BaseAcpRunner extends ConnectionBasedRunner {
|
|
|
15
15
|
constructor(opts, connectionManager) {
|
|
16
16
|
super(opts);
|
|
17
17
|
this.connectionManager = connectionManager;
|
|
18
|
-
//
|
|
18
|
+
// 协议停止通道:ACP 的 cancel 就是 `session/cancel`,已由
|
|
19
19
|
// cancelCurrentTurn 实现。这里只做登记接线——manager 建在 super() 实参里,
|
|
20
20
|
// 那时 this 不可用,所以只能等 super() 返回后再赋值。
|
|
21
21
|
connectionManager.stopper = ({ client }) => this.buildCooperativeStop(client);
|
|
@@ -114,7 +114,7 @@ export declare abstract class BaseAcpTranslator {
|
|
|
114
114
|
*/
|
|
115
115
|
setOperationKind(kind: 'turn' | 'compact'): void;
|
|
116
116
|
/**
|
|
117
|
-
* plan update → PlanEvent(信息保真
|
|
117
|
+
* plan update → PlanEvent(信息保真 不再显式丢弃)。
|
|
118
118
|
* ACP PlanEntry: { content, priority, status }(status: pending/in_progress/
|
|
119
119
|
* completed)。基类具体方法:kimi/opencode wire 结构相同,直接复用。
|
|
120
120
|
*/
|
|
@@ -125,7 +125,7 @@ export declare abstract class BaseAcpTranslator {
|
|
|
125
125
|
* JSON.parse (older agents send JSON strings) — keeps the card from
|
|
126
126
|
* crashing on undefined input.
|
|
127
127
|
*
|
|
128
|
-
* 信息保真
|
|
128
|
+
* 信息保真 kind 命中 KIND_TO_TOOL 时用映射名渲染结构化面板;
|
|
129
129
|
* title 与 name 不同时把 title 存到 content block 的 summary 字段
|
|
130
130
|
* (渲染层做面板副标题)。
|
|
131
131
|
*/
|
|
@@ -144,7 +144,7 @@ export declare abstract class BaseAcpTranslator {
|
|
|
144
144
|
/** tool_use name recorded at tool_call time (kind mapping wins, else title). */
|
|
145
145
|
protected getToolCallName(toolCallId: string): string | undefined;
|
|
146
146
|
/**
|
|
147
|
-
* kind → canonical tool name (信息保真
|
|
147
|
+
* kind → canonical tool name (信息保真). Shared by the initial tool_call
|
|
148
148
|
* handler and the tool_call_update patch builder so both agree on the name.
|
|
149
149
|
*/
|
|
150
150
|
protected toolNameFromKind(kind: string | undefined): string | undefined;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NotificationMethod, ServerRequestMethod, SessionEventType, } from './protocol-types.js';
|
|
2
2
|
/**
|
|
3
|
-
* ACP tool kind →
|
|
3
|
+
* ACP tool kind → 常用工具名映射(信息保真,数据驱动无 agent 分支)。
|
|
4
4
|
* kind 命中映射时用映射名渲染结构化面板;否则回落 title(维持现状)。
|
|
5
5
|
*/
|
|
6
6
|
const KIND_TO_TOOL = {
|
|
@@ -199,7 +199,7 @@ export class BaseAcpTranslator {
|
|
|
199
199
|
// Shared notification handlers
|
|
200
200
|
// =========================================================================
|
|
201
201
|
/**
|
|
202
|
-
* plan update → PlanEvent(信息保真
|
|
202
|
+
* plan update → PlanEvent(信息保真 不再显式丢弃)。
|
|
203
203
|
* ACP PlanEntry: { content, priority, status }(status: pending/in_progress/
|
|
204
204
|
* completed)。基类具体方法:kimi/opencode wire 结构相同,直接复用。
|
|
205
205
|
*/
|
|
@@ -224,7 +224,7 @@ export class BaseAcpTranslator {
|
|
|
224
224
|
* JSON.parse (older agents send JSON strings) — keeps the card from
|
|
225
225
|
* crashing on undefined input.
|
|
226
226
|
*
|
|
227
|
-
* 信息保真
|
|
227
|
+
* 信息保真 kind 命中 KIND_TO_TOOL 时用映射名渲染结构化面板;
|
|
228
228
|
* title 与 name 不同时把 title 存到 content block 的 summary 字段
|
|
229
229
|
* (渲染层做面板副标题)。
|
|
230
230
|
*/
|
|
@@ -327,7 +327,7 @@ export class BaseAcpTranslator {
|
|
|
327
327
|
return this.toolCallNameByCallId.get(toolCallId);
|
|
328
328
|
}
|
|
329
329
|
/**
|
|
330
|
-
* kind → canonical tool name (信息保真
|
|
330
|
+
* kind → canonical tool name (信息保真). Shared by the initial tool_call
|
|
331
331
|
* handler and the tool_call_update patch builder so both agree on the name.
|
|
332
332
|
*/
|
|
333
333
|
toolNameFromKind(kind) {
|
|
@@ -45,7 +45,7 @@ export const RpcErrorCode = {
|
|
|
45
45
|
//
|
|
46
46
|
// REAL envelope: {sessionId, update: {sessionUpdate: '<kind>', ...}}.
|
|
47
47
|
// The discriminator field is `update.sessionUpdate` — there is NO nested
|
|
48
|
-
// `event.type` / `event.delta` (
|
|
48
|
+
// `event.type` / `event.delta` (旧实现读 params.event,
|
|
49
49
|
// 真实通知全部静默丢弃)。
|
|
50
50
|
// =============================================================================
|
|
51
51
|
/** Notification method names for ACP protocol. */
|
|
@@ -51,7 +51,7 @@ export declare abstract class ConnectionBasedRunner<TClient, TEvent = AgentEvent
|
|
|
51
51
|
/** Set up the turn: acquire + thread|session new/resume + prompt. */
|
|
52
52
|
protected abstract setupTurn(message: string, opts: SpawnOptions): Promise<void>;
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
54
|
+
* 本连接的协议停止通道。
|
|
55
55
|
*
|
|
56
56
|
* win32 上「优雅停止」没有可拦截的跨进程 SIGTERM,只能经 agent 自有协议通道
|
|
57
57
|
* 请求对方收摊。子类把这个方法的结果交给 ConnectionManager.stopper,由后者按
|
|
@@ -28,7 +28,7 @@ export class ConnectionBasedRunner {
|
|
|
28
28
|
this.turnIdleTimeoutMs = opts.turnIdleTimeoutMs ?? defaultTurnIdleTimeoutMs;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
31
|
+
* 本连接的协议停止通道。
|
|
32
32
|
*
|
|
33
33
|
* win32 上「优雅停止」没有可拦截的跨进程 SIGTERM,只能经 agent 自有协议通道
|
|
34
34
|
* 请求对方收摊。子类把这个方法的结果交给 ConnectionManager.stopper,由后者按
|
|
@@ -141,7 +141,7 @@ export class ConnectionBasedRunner {
|
|
|
141
141
|
async stop(_opts) {
|
|
142
142
|
if (!this._isRunning)
|
|
143
143
|
return;
|
|
144
|
-
//
|
|
144
|
+
// 启动阶段(client/session 未建立)也必须记录停止意图,否则 /stop 被吞、
|
|
145
145
|
// turn 照常执行。executeTurn 在 setup 完成后检查 stopRequested 补发 cancel。
|
|
146
146
|
this.stopRequested = true;
|
|
147
147
|
this.forceFinish = true;
|
|
@@ -4,7 +4,7 @@ import type { Readable } from 'node:stream';
|
|
|
4
4
|
interface JSONLStreamOptions {
|
|
5
5
|
onParseError?: (line: string) => void;
|
|
6
6
|
/**
|
|
7
|
-
* Pause stdout once `lineQueue` exceeds this depth (
|
|
7
|
+
* Pause stdout once `lineQueue` exceeds this depth (backpressure).
|
|
8
8
|
* Default 100 (matches production; tests may pass a smaller value).
|
|
9
9
|
*/
|
|
10
10
|
pauseThreshold?: number;
|
|
@@ -23,7 +23,7 @@ interface JSONLStreamOptions {
|
|
|
23
23
|
* - JSON parse errors: when `options.onParseError` is provided, bad lines are
|
|
24
24
|
* forwarded to the callback; otherwise a warning is logged via getLogger().warn
|
|
25
25
|
* and the line is skipped.
|
|
26
|
-
* - Optional backpressure
|
|
26
|
+
* - Optional backpressure: when `pauseThreshold` is set, stdout is
|
|
27
27
|
* paused once the parsed-line queue exceeds the threshold and resumed once
|
|
28
28
|
* the consumer drains it back below `resumeThreshold`, bounding memory in
|
|
29
29
|
* long runs where the producer (agent stdout) outpaces the consumer (card
|
|
@@ -8,7 +8,7 @@ import { getLogger } from '../../logger/index.js';
|
|
|
8
8
|
* - JSON parse errors: when `options.onParseError` is provided, bad lines are
|
|
9
9
|
* forwarded to the callback; otherwise a warning is logged via getLogger().warn
|
|
10
10
|
* and the line is skipped.
|
|
11
|
-
* - Optional backpressure
|
|
11
|
+
* - Optional backpressure: when `pauseThreshold` is set, stdout is
|
|
12
12
|
* paused once the parsed-line queue exceeds the threshold and resumed once
|
|
13
13
|
* the consumer drains it back below `resumeThreshold`, bounding memory in
|
|
14
14
|
* long runs where the producer (agent stdout) outpaces the consumer (card
|
|
@@ -22,7 +22,7 @@ import { getLogger } from '../../logger/index.js';
|
|
|
22
22
|
*/
|
|
23
23
|
export function createJSONLStream(stdout, options) {
|
|
24
24
|
const lineQueue = [];
|
|
25
|
-
//
|
|
25
|
+
// accumulate partial-line fragments in an array instead of repeatedly
|
|
26
26
|
// string-concatenating a growing `partialLine`. A huge single JSON line
|
|
27
27
|
// (large tool_result / text_end) spanning many chunks caused O(n²) copies:
|
|
28
28
|
// each chunk rebuilt `partialLine + text` over the whole accumulated prefix.
|
|
@@ -30,7 +30,7 @@ export function createJSONLStream(stdout, options) {
|
|
|
30
30
|
// and push complete lines; the residual tail stays as array fragments until
|
|
31
31
|
// the next newline or stream end. Net allocation: O(n) instead of O(n²).
|
|
32
32
|
const partialFragments = [];
|
|
33
|
-
//
|
|
33
|
+
// accumulated byte length of partialFragments. Backpressure (above)
|
|
34
34
|
// only counts COMPLETE lines in lineQueue; a never-newline stream (malformed
|
|
35
35
|
// output, binary garbage) accumulates fragments without ever completing a
|
|
36
36
|
// line, so pause never fires and memory grows unbounded. Cap the partial
|
|
@@ -41,13 +41,13 @@ export function createJSONLStream(stdout, options) {
|
|
|
41
41
|
const completeLines = [];
|
|
42
42
|
let done = false;
|
|
43
43
|
let resolveNext = null;
|
|
44
|
-
//
|
|
44
|
+
// backpressure thresholds. Default matches production (100).
|
|
45
45
|
const pauseThreshold = options?.pauseThreshold ?? 100;
|
|
46
46
|
// Default low-water mark is half the high-water mark for meaningful hysteresis
|
|
47
47
|
// (avoids per-line pause/resume thrashing when callers don't set it explicitly).
|
|
48
48
|
let resumeThreshold = options?.resumeThreshold ?? Math.max(0, Math.floor(pauseThreshold / 2));
|
|
49
49
|
// Clamp misconfiguration: resumeThreshold > pauseThreshold would resume at queue
|
|
50
|
-
// high water then immediately re-pause on the next line → thrash (
|
|
50
|
+
// high water then immediately re-pause on the next line → thrash (nit).
|
|
51
51
|
if (resumeThreshold > pauseThreshold)
|
|
52
52
|
resumeThreshold = pauseThreshold;
|
|
53
53
|
let paused = false;
|
|
@@ -58,7 +58,7 @@ export function createJSONLStream(stdout, options) {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
// Flush any trailing partial line and mark the stream finished. Shared by the
|
|
61
|
-
// 'end'/'close'/'error' handlers so flush logic lives in one place (
|
|
61
|
+
// 'end'/'close'/'error' handlers so flush logic lives in one place (nit).
|
|
62
62
|
function flushAndFinish() {
|
|
63
63
|
if (partialFragments.length > 0) {
|
|
64
64
|
const trimmed = partialFragments.join('').trim();
|
|
@@ -70,7 +70,7 @@ export function createJSONLStream(stdout, options) {
|
|
|
70
70
|
done = true;
|
|
71
71
|
notify();
|
|
72
72
|
}
|
|
73
|
-
//
|
|
73
|
+
// flush an oversize partial line as a bad line. When partialFragments
|
|
74
74
|
// exceeds the byte cap, the partial line is treated as malformed (a single
|
|
75
75
|
// JSON line should never reach 10MB) and reported via onParseError rather
|
|
76
76
|
// than accumulated indefinitely. Resets the buffer so a pathological stream
|
|
@@ -93,7 +93,7 @@ export function createJSONLStream(stdout, options) {
|
|
|
93
93
|
}
|
|
94
94
|
// Apply backpressure after pushing new lines: pause stdout when the queue
|
|
95
95
|
// exceeds the high-water mark so the producer stops feeding until the
|
|
96
|
-
// consumer drains it.
|
|
96
|
+
// consumer drains it.bounds memory in long runs.
|
|
97
97
|
function maybePause() {
|
|
98
98
|
if (pauseThreshold > 0 && !paused && lineQueue.length > pauseThreshold) {
|
|
99
99
|
paused = true;
|
|
@@ -110,7 +110,7 @@ export function createJSONLStream(stdout, options) {
|
|
|
110
110
|
}
|
|
111
111
|
stdout.on('data', (chunk) => {
|
|
112
112
|
const text = chunk.toString('utf-8');
|
|
113
|
-
//
|
|
113
|
+
// split on newlines. The first segment continues the pending partial
|
|
114
114
|
// line (joined with accumulated fragments — one join per chunk, not per
|
|
115
115
|
// byte); subsequent segments are complete lines. The last segment is the
|
|
116
116
|
// new partial tail (kept as a single fragment).
|
|
@@ -119,7 +119,7 @@ export function createJSONLStream(stdout, options) {
|
|
|
119
119
|
// No newline in this chunk: the whole chunk is a partial fragment.
|
|
120
120
|
partialFragments.push(segments[0]);
|
|
121
121
|
partialBytes += segments[0].length;
|
|
122
|
-
//
|
|
122
|
+
// cap the partial line. A never-newline stream would otherwise
|
|
123
123
|
// accumulate fragments without bound (backpressure only counts complete
|
|
124
124
|
// lines). Flush as a bad line once the byte cap is exceeded.
|
|
125
125
|
if (partialBytes > PARTIAL_BYTE_LIMIT) {
|
|
@@ -140,7 +140,7 @@ export function createJSONLStream(stdout, options) {
|
|
|
140
140
|
// Last segment is the new partial tail.
|
|
141
141
|
partialFragments.push(segments[segments.length - 1]);
|
|
142
142
|
partialBytes = segments[segments.length - 1].length;
|
|
143
|
-
//
|
|
143
|
+
// the trailing partial tail can itself exceed the cap if a single
|
|
144
144
|
// chunk is larger than 10MB with no internal newline.
|
|
145
145
|
if (partialBytes > PARTIAL_BYTE_LIMIT) {
|
|
146
146
|
flushOversizePartial();
|
|
@@ -162,7 +162,7 @@ export function createJSONLStream(stdout, options) {
|
|
|
162
162
|
stdout.on('close', () => {
|
|
163
163
|
flushAndFinish();
|
|
164
164
|
});
|
|
165
|
-
//
|
|
165
|
+
// handle stdout 'error' so the generator terminates instead of
|
|
166
166
|
// hanging forever on the `await new Promise` below. Without this, a stdout
|
|
167
167
|
// error (e.g. broken pipe, runner crash) leaves the consumer awaiting a
|
|
168
168
|
// notify that never comes → bridge serial queue blocks permanently. We do
|
|
@@ -44,7 +44,7 @@ export declare class JsonRpcClient<InitializeResult = unknown> {
|
|
|
44
44
|
* Replace the per-run hooks (used by the runner to bridge
|
|
45
45
|
* notifications/server requests into the active turn's translator).
|
|
46
46
|
* Connection-layer hooks (slot cleanup / onConnectionLost) are preserved:
|
|
47
|
-
*
|
|
47
|
+
* an overwrite here previously silenced onClose slot cleanup,
|
|
48
48
|
* leaving dead slots behind after the server process was killed.
|
|
49
49
|
*/
|
|
50
50
|
setHooks(hooks: ClientHooks): void;
|
|
@@ -61,7 +61,7 @@ export class JsonRpcClient {
|
|
|
61
61
|
* Replace the per-run hooks (used by the runner to bridge
|
|
62
62
|
* notifications/server requests into the active turn's translator).
|
|
63
63
|
* Connection-layer hooks (slot cleanup / onConnectionLost) are preserved:
|
|
64
|
-
*
|
|
64
|
+
* an overwrite here previously silenced onClose slot cleanup,
|
|
65
65
|
* leaving dead slots behind after the server process was killed.
|
|
66
66
|
*/
|
|
67
67
|
setHooks(hooks) {
|
|
@@ -94,7 +94,7 @@ export declare class ConnectionManager<TClient extends ConnectionClient = JsonRp
|
|
|
94
94
|
/** Callback when a connection is lost — cleared from slot map. */
|
|
95
95
|
onConnectionLost?: (cwd: string) => void;
|
|
96
96
|
/**
|
|
97
|
-
*
|
|
97
|
+
* 协议停止通道工厂:连接建立后按 pid 登记到 AgentStopperRegistry,
|
|
98
98
|
* 供 Terminator 在**优雅**停止时回调(win32 上没有可拦截的 SIGTERM,只能靠
|
|
99
99
|
* 协议通道让 agent 自己收摊)。返回 undefined = 该协议无通道。
|
|
100
100
|
*
|
|
@@ -34,7 +34,7 @@ export class ConnectionManager {
|
|
|
34
34
|
/** Callback when a connection is lost — cleared from slot map. */
|
|
35
35
|
onConnectionLost;
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
37
|
+
* 协议停止通道工厂:连接建立后按 pid 登记到 AgentStopperRegistry,
|
|
38
38
|
* 供 Terminator 在**优雅**停止时回调(win32 上没有可拦截的 SIGTERM,只能靠
|
|
39
39
|
* 协议通道让 agent 自己收摊)。返回 undefined = 该协议无通道。
|
|
40
40
|
*
|
|
@@ -42,7 +42,7 @@ export declare class JsonlRpcTransport {
|
|
|
42
42
|
/** 终止器注入(测试用);默认按平台建实现 */
|
|
43
43
|
terminator?: Terminator;
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
45
|
+
* 协议停止通道工厂:spawn 成功后按 pid 建通道并登记到
|
|
46
46
|
* AgentStopperRegistry,进程退出/关闭时注销。返回 undefined = 无通道。
|
|
47
47
|
*/
|
|
48
48
|
stopper?: (pid: number) => AgentStopper | undefined;
|
|
@@ -70,7 +70,7 @@ export class JsonlRpcTransport {
|
|
|
70
70
|
// agent 是用户自己的可信二进制,provider 认证依赖 API key /
|
|
71
71
|
// 自定义 provider 的 env_key,代理环境依赖 HTTP(S)_PROXY、TMPDIR 等;
|
|
72
72
|
// 任何白名单收窄都会打断认证或网络。调用方 this.env 覆盖 process.env 同名键。
|
|
73
|
-
// env 覆盖必须大小写不敏感合并(win32 PATH/Path
|
|
73
|
+
// env 覆盖必须大小写不敏感合并(win32 PATH/Path 双键防护)
|
|
74
74
|
const childEnv = mergeProcessEnv(process.env, this.env);
|
|
75
75
|
const proc = spawnProcess(this.binary, this.args, {
|
|
76
76
|
cwd: this.cwd,
|
|
@@ -195,7 +195,7 @@ export class JsonlRpcTransport {
|
|
|
195
195
|
}
|
|
196
196
|
try {
|
|
197
197
|
// 入队后顺序 flush;write() 返回 false(高水位)时暂停直到 drain,
|
|
198
|
-
// 避免大消息/突发消息在 Node
|
|
198
|
+
// 避免大消息/突发消息在 Node 内部无限缓冲。
|
|
199
199
|
this.writeQueue.push(Buffer.from(JSON.stringify(msg) + '\n', 'utf8'));
|
|
200
200
|
this.flush();
|
|
201
201
|
}
|
|
@@ -4,7 +4,7 @@ import { type Terminator } from '../../platform/terminator.js';
|
|
|
4
4
|
import { SpawnHeartbeat } from './spawn-heartbeat.js';
|
|
5
5
|
import type { SpawnOptions } from '../types.js';
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* 进程级退出监听单例分发(修复,2026-08-02)。
|
|
8
8
|
*
|
|
9
9
|
* 背景:各 agent runner 的 registerExitHandlers 曾是同构复制,每个实例
|
|
10
10
|
* process.on('exit'|'SIGINT'|'SIGTERM') 注册 3 个永不移除的闭包(捕获整个
|
|
@@ -20,13 +20,26 @@ import type { SpawnOptions } from '../types.js';
|
|
|
20
20
|
/**
|
|
21
21
|
* Exit-cleanup contract: anything that spawns a child process group and wants
|
|
22
22
|
* process-level cleanup (agent runners via SpawningRunner, and BashProcessRunner
|
|
23
|
-
* for `!` commands
|
|
23
|
+
* for `!` commands) registers with the singleton dispatcher.
|
|
24
24
|
*/
|
|
25
25
|
interface ExitCleanupHandler {
|
|
26
26
|
cleanupOnExit(): void;
|
|
27
27
|
}
|
|
28
28
|
/** Register a process-exit cleanup handler (installs listeners at most once). */
|
|
29
29
|
export declare function registerExitCleanup(handler: ExitCleanupHandler): void;
|
|
30
|
+
/**
|
|
31
|
+
* pid 文件名的 workspace 后缀:`-<消毒 cwd>-<cwd hash>`。
|
|
32
|
+
*
|
|
33
|
+
* 消毒段(非字母数字 → `_`)只是可读性;不同 cwd 可能消毒出同一段
|
|
34
|
+
* (`/w/a+b` 与 `/w/a_b`),而 killOrphan 的身份校验只比对二进制名——
|
|
35
|
+
* 不掺 hash 时两个 workspace 会共用同一个 pid 文件,启动时的孤儿回收
|
|
36
|
+
* 可能误杀对方 workspace 存活的同名 agent。hash 段按**原始 cwd** 计算,
|
|
37
|
+
* 与消毒无关,消除碰撞。消毒段截到 96 字符,深路径下给 MAX_PATH 留余量。
|
|
38
|
+
*
|
|
39
|
+
* 导出给测试/anchor 复用:任何「手工构造 runner 会写出的 pid 文件路径」的
|
|
40
|
+
* 代码都必须走这里,自己拼一份就是自造半迁移。
|
|
41
|
+
*/
|
|
42
|
+
export declare function pidFileSuffix(cwd: string): string;
|
|
30
43
|
/** Remove a handler so it can be GC'd (agent slots / finished bash runs). */
|
|
31
44
|
export declare function unregisterExitCleanup(handler: ExitCleanupHandler): void;
|
|
32
45
|
/**
|
|
@@ -119,12 +132,12 @@ export declare abstract class SpawningRunner {
|
|
|
119
132
|
protected getStdio(): ('ignore' | 'pipe')[];
|
|
120
133
|
/**
|
|
121
134
|
* Create an async iterator over the child's stdout. Default: createJSONLStream
|
|
122
|
-
*
|
|
135
|
+
* withbackpressure enabled (pauseThreshold=100).
|
|
123
136
|
*/
|
|
124
137
|
protected createStreamReader(stdout: Readable): AsyncGenerator<unknown>;
|
|
125
138
|
/**
|
|
126
139
|
* Await the spawn 'error' event when proc.pid === undefined. Races a
|
|
127
|
-
* finite timeout
|
|
140
|
+
* finite timeout against the 'error' event: Node guarantees
|
|
128
141
|
* 'error' for ENOENT/EACCES, but some binaries fail silently without ever
|
|
129
142
|
* emitting it. Without the race, the spawn lead-in hangs forever → the
|
|
130
143
|
* cwd serial queue never settles → permanent deadlock, and /stop
|
|
@@ -143,7 +156,7 @@ export declare abstract class SpawningRunner {
|
|
|
143
156
|
*/
|
|
144
157
|
protected spawnChild(opts: SpawnOptions): Promise<ChildProcess>;
|
|
145
158
|
/**
|
|
146
|
-
*
|
|
159
|
+
* 登记本进程的协议停止通道:win32 上没有可拦截的跨进程
|
|
147
160
|
* SIGTERM,「优雅停止」只能经 agent 自有通道请求对方自行退出。stdio 型 agent
|
|
148
161
|
* CLI 的通道就是关 stdin(cc-connect 已验证 claude:关 stdin → 干净退出并跑
|
|
149
162
|
* Stop hooks)。
|
|
@@ -166,12 +179,12 @@ export declare abstract class SpawningRunner {
|
|
|
166
179
|
immediate?: boolean;
|
|
167
180
|
}): Promise<void>;
|
|
168
181
|
/**
|
|
169
|
-
*
|
|
182
|
+
* 杀掉上一次崩溃留下的孤儿 agent 进程。**身份校验是动手的前置条件**:
|
|
170
183
|
* pid 文件里的 pid 可能已被系统回收给无关进程,`kill(pid, 0)` 存活探测分不出来。
|
|
171
184
|
*
|
|
172
185
|
* 判定单源 = `platform/identity`(posix `ps -o command=` / win32 CIM),三态:
|
|
173
186
|
* - match → 杀整个进程组(detached:true 下 agent 是组长,与 Terminator
|
|
174
|
-
* posix 路径的 kill(-pid)
|
|
187
|
+
* posix 路径的 kill(-pid) 同语义,子进程不孤儿化);
|
|
175
188
|
* - mismatch → pid 已被无关进程占用:绝不杀,文件是陈旧垃圾,清除自愈;
|
|
176
189
|
* - unknown → 查不到身份(ps 不可用/超时/CIM 被拒):同样不杀,清文件。
|
|
177
190
|
* 统一原则:只有身份匹配才杀,验证失败一律不杀(fail-closed)。
|
|
@@ -198,7 +211,7 @@ export declare abstract class SpawningRunner {
|
|
|
198
211
|
unregisterExitHandlers(): void;
|
|
199
212
|
/**
|
|
200
213
|
* Current number of runners registered with the process-level exit
|
|
201
|
-
* dispatcher. Diagnostics / test-support introspection (
|
|
214
|
+
* dispatcher. Diagnostics / test-support introspection (asserts
|
|
202
215
|
* the count returns to baseline after bridge eviction).
|
|
203
216
|
*/
|
|
204
217
|
static getRegisteredExitHandlerCount(): number;
|
|
@@ -11,7 +11,7 @@ import { SpawnHeartbeat } from './spawn-heartbeat.js';
|
|
|
11
11
|
import { createJSONLStream } from './jsonl-stream.js';
|
|
12
12
|
import { DEFAULT_STOP_GRACE_MS } from '../../config/index.js';
|
|
13
13
|
/**
|
|
14
|
-
* Magic-number constants (Clean Code
|
|
14
|
+
* Magic-number constants (Clean Code G25 Replace Magic Numbers with
|
|
15
15
|
* Named Constants). Centralized here so the truncation budgets are visible
|
|
16
16
|
* and adjustable in one place rather than scattered as raw literals.
|
|
17
17
|
*/
|
|
@@ -22,7 +22,7 @@ class SpawnChildError extends Error {
|
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Timeout (ms) for awaiting the spawn 'error' event when proc.pid === undefined
|
|
25
|
-
*
|
|
25
|
+
* Node guarantees 'error' for ENOENT/EACCES, but some binaries
|
|
26
26
|
* fail silently; the race keeps a silent failure from hanging the spawn lead-in
|
|
27
27
|
* forever. Matches the 5s race kimi's override used before it was hoisted to
|
|
28
28
|
* the base.
|
|
@@ -54,6 +54,33 @@ export function registerExitCleanup(handler) {
|
|
|
54
54
|
installExitListenersOnce();
|
|
55
55
|
registeredRunners.add(handler);
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* FNV-1a 32 位哈希(hex,平台无关确定性):pid 文件后缀的消歧段。
|
|
59
|
+
*/
|
|
60
|
+
function fnv1aHex(text) {
|
|
61
|
+
let hash = 0x811c9dc5;
|
|
62
|
+
for (let i = 0; i < text.length; i++) {
|
|
63
|
+
hash ^= text.charCodeAt(i);
|
|
64
|
+
hash = Math.imul(hash, 0x01000193);
|
|
65
|
+
}
|
|
66
|
+
return (hash >>> 0).toString(16).padStart(8, '0');
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* pid 文件名的 workspace 后缀:`-<消毒 cwd>-<cwd hash>`。
|
|
70
|
+
*
|
|
71
|
+
* 消毒段(非字母数字 → `_`)只是可读性;不同 cwd 可能消毒出同一段
|
|
72
|
+
* (`/w/a+b` 与 `/w/a_b`),而 killOrphan 的身份校验只比对二进制名——
|
|
73
|
+
* 不掺 hash 时两个 workspace 会共用同一个 pid 文件,启动时的孤儿回收
|
|
74
|
+
* 可能误杀对方 workspace 存活的同名 agent。hash 段按**原始 cwd** 计算,
|
|
75
|
+
* 与消毒无关,消除碰撞。消毒段截到 96 字符,深路径下给 MAX_PATH 留余量。
|
|
76
|
+
*
|
|
77
|
+
* 导出给测试/anchor 复用:任何「手工构造 runner 会写出的 pid 文件路径」的
|
|
78
|
+
* 代码都必须走这里,自己拼一份就是自造半迁移。
|
|
79
|
+
*/
|
|
80
|
+
export function pidFileSuffix(cwd) {
|
|
81
|
+
const sanitized = cwd.replace(/[^a-zA-Z0-9]/g, '_').slice(0, 96);
|
|
82
|
+
return `-${sanitized}-${fnv1aHex(cwd)}`;
|
|
83
|
+
}
|
|
57
84
|
/** Remove a handler so it can be GC'd (agent slots / finished bash runs). */
|
|
58
85
|
export function unregisterExitCleanup(handler) {
|
|
59
86
|
registeredRunners.delete(handler);
|
|
@@ -120,9 +147,8 @@ export class SpawningRunner {
|
|
|
120
147
|
this.logTag = opts.logTag ?? 'spawning-runner';
|
|
121
148
|
this.agent = opts.agent;
|
|
122
149
|
const pidDir = opts.pidDir ?? path.join(os.homedir(), '.lark-remote');
|
|
123
|
-
const workspaceSuffix = `-${opts.cwd.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
|
124
150
|
const pidFilePrefix = opts.pidFilePrefix ?? 'spawning';
|
|
125
|
-
this.pidFilePath = path.join(pidDir, `${pidFilePrefix}${
|
|
151
|
+
this.pidFilePath = path.join(pidDir, `${pidFilePrefix}${pidFileSuffix(opts.cwd)}.pid`);
|
|
126
152
|
this.terminator =
|
|
127
153
|
opts.terminator ?? createTerminator({ graceMs: this.stopGraceMs, agent: opts.agent });
|
|
128
154
|
this.spawnHeartbeat = new SpawnHeartbeat(opts.spawnHeartbeatMs ?? 30_000, this.logTag);
|
|
@@ -142,7 +168,7 @@ export class SpawningRunner {
|
|
|
142
168
|
}
|
|
143
169
|
/**
|
|
144
170
|
* Create an async iterator over the child's stdout. Default: createJSONLStream
|
|
145
|
-
*
|
|
171
|
+
* withbackpressure enabled (pauseThreshold=100).
|
|
146
172
|
*/
|
|
147
173
|
createStreamReader(stdout) {
|
|
148
174
|
return createJSONLStream(stdout, {
|
|
@@ -155,7 +181,7 @@ export class SpawningRunner {
|
|
|
155
181
|
}
|
|
156
182
|
/**
|
|
157
183
|
* Await the spawn 'error' event when proc.pid === undefined. Races a
|
|
158
|
-
* finite timeout
|
|
184
|
+
* finite timeout against the 'error' event: Node guarantees
|
|
159
185
|
* 'error' for ENOENT/EACCES, but some binaries fail silently without ever
|
|
160
186
|
* emitting it. Without the race, the spawn lead-in hangs forever → the
|
|
161
187
|
* cwd serial queue never settles → permanent deadlock, and /stop
|
|
@@ -203,7 +229,7 @@ export class SpawningRunner {
|
|
|
203
229
|
}
|
|
204
230
|
this.currentProcess = null;
|
|
205
231
|
this.spawnHeartbeat.clear();
|
|
206
|
-
//
|
|
232
|
+
// surface the real spawn error cause instead of a fixed "binary
|
|
207
233
|
// not found" message. ENOENT (binary missing) keeps the friendly hint,
|
|
208
234
|
// but EMFILE/EACCES/ENOMEM/bad-cwd failures must name the actual reason
|
|
209
235
|
// so the user is not misdiagnosed into reinstalling the binary.
|
|
@@ -219,7 +245,7 @@ export class SpawningRunner {
|
|
|
219
245
|
proc.stdout?.once('data', () => {
|
|
220
246
|
this.spawnHeartbeat.notifyStdout();
|
|
221
247
|
});
|
|
222
|
-
//
|
|
248
|
+
// 协议停止通道登记:子进程起来后立刻登记,win32 上
|
|
223
249
|
// Terminator 停这个 pid 时才能取到通道;进程退出/被停后注销。
|
|
224
250
|
this.registerStopper(proc);
|
|
225
251
|
this.spawnStderr = '';
|
|
@@ -235,7 +261,7 @@ export class SpawningRunner {
|
|
|
235
261
|
this.commandNotFoundSeen = true;
|
|
236
262
|
getLogger().error(`[${this.logTag}] command not found suspected (win32 shim): ${text}`);
|
|
237
263
|
}
|
|
238
|
-
//
|
|
264
|
+
// most agent CLIs emit progress/warnings/deprecation notices on
|
|
239
265
|
// stderr, not real errors. Logging each chunk at error level drowns out
|
|
240
266
|
// genuine errors. Downgrade to warn — the accumulated stderr is already
|
|
241
267
|
// surfaced at error level in the non-zero-exit result path.
|
|
@@ -245,7 +271,7 @@ export class SpawningRunner {
|
|
|
245
271
|
return proc;
|
|
246
272
|
}
|
|
247
273
|
/**
|
|
248
|
-
*
|
|
274
|
+
* 登记本进程的协议停止通道:win32 上没有可拦截的跨进程
|
|
249
275
|
* SIGTERM,「优雅停止」只能经 agent 自有通道请求对方自行退出。stdio 型 agent
|
|
250
276
|
* CLI 的通道就是关 stdin(cc-connect 已验证 claude:关 stdin → 干净退出并跑
|
|
251
277
|
* Stop hooks)。
|
|
@@ -302,12 +328,12 @@ export class SpawningRunner {
|
|
|
302
328
|
getLogger().debug(`[${this.logTag}] cleaned pid file ${this.pidFilePath}`);
|
|
303
329
|
}
|
|
304
330
|
/**
|
|
305
|
-
*
|
|
331
|
+
* 杀掉上一次崩溃留下的孤儿 agent 进程。**身份校验是动手的前置条件**:
|
|
306
332
|
* pid 文件里的 pid 可能已被系统回收给无关进程,`kill(pid, 0)` 存活探测分不出来。
|
|
307
333
|
*
|
|
308
334
|
* 判定单源 = `platform/identity`(posix `ps -o command=` / win32 CIM),三态:
|
|
309
335
|
* - match → 杀整个进程组(detached:true 下 agent 是组长,与 Terminator
|
|
310
|
-
* posix 路径的 kill(-pid)
|
|
336
|
+
* posix 路径的 kill(-pid) 同语义,子进程不孤儿化);
|
|
311
337
|
* - mismatch → pid 已被无关进程占用:绝不杀,文件是陈旧垃圾,清除自愈;
|
|
312
338
|
* - unknown → 查不到身份(ps 不可用/超时/CIM 被拒):同样不杀,清文件。
|
|
313
339
|
* 统一原则:只有身份匹配才杀,验证失败一律不杀(fail-closed)。
|
|
@@ -343,7 +369,7 @@ export class SpawningRunner {
|
|
|
343
369
|
return;
|
|
344
370
|
}
|
|
345
371
|
// 身份匹配:杀整个进程组(detached:true 下 agent 是组长),与
|
|
346
|
-
// Terminator 的 kill(-pid)
|
|
372
|
+
// Terminator 的 kill(-pid) 语义对齐,子进程不会孤儿化。
|
|
347
373
|
getLogger().info(`[${this.logTag}] killing orphan process group ${-pid}`);
|
|
348
374
|
try {
|
|
349
375
|
process.kill(-pid, 'SIGTERM');
|
|
@@ -377,7 +403,7 @@ export class SpawningRunner {
|
|
|
377
403
|
}
|
|
378
404
|
/**
|
|
379
405
|
* Current number of runners registered with the process-level exit
|
|
380
|
-
* dispatcher. Diagnostics / test-support introspection (
|
|
406
|
+
* dispatcher. Diagnostics / test-support introspection (asserts
|
|
381
407
|
* the count returns to baseline after bridge eviction).
|
|
382
408
|
*/
|
|
383
409
|
static getRegisteredExitHandlerCount() {
|
|
@@ -392,7 +418,7 @@ export class SpawningRunner {
|
|
|
392
418
|
cleanupOnExit() {
|
|
393
419
|
if (this.currentProcess && this.currentProcess.exitCode === null) {
|
|
394
420
|
try {
|
|
395
|
-
//
|
|
421
|
+
// 只杀组长会让组内子进程(工具调用起的后台进程)reparent 成孤儿。
|
|
396
422
|
// Terminator.cleanupOnExit 在两种平台上都是「fire-and-forget 强杀整个
|
|
397
423
|
// 进程组/进程树」(posix kill(-pgid, SIGKILL) / win32 taskkill /T /F),
|
|
398
424
|
// 退出路径上无法等待,也不需要优雅段。
|
|
@@ -67,7 +67,7 @@ export class DshRunner {
|
|
|
67
67
|
let sessionId = opts.sessionId;
|
|
68
68
|
let terminalEmitted = false;
|
|
69
69
|
try {
|
|
70
|
-
//
|
|
70
|
+
// session.create 必须包进同一 try/finally,否则创建失败(服务端不可达/报错)
|
|
71
71
|
// 会在进入旧 try 之前抛异常,finally 不执行 → running 恒 true、activeAbort 残留。
|
|
72
72
|
if (!sessionId) {
|
|
73
73
|
const created = await this.client.createSession({
|
|
@@ -188,7 +188,7 @@ export class DshRunner {
|
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
catch (err) {
|
|
191
|
-
//
|
|
191
|
+
// createSession 等启动期失败也产出 error result(而非抛异常),
|
|
192
192
|
// 由本 catch 统一转 error result;state 清理在 finally。
|
|
193
193
|
if (!abort.signal.aborted) {
|
|
194
194
|
yield {
|
|
@@ -83,7 +83,7 @@ export declare class KimiAcpRunner extends BaseAcpRunner<KimiAcpTranslator> {
|
|
|
83
83
|
*/
|
|
84
84
|
private readonly terminator;
|
|
85
85
|
/**
|
|
86
|
-
* shell seam:terminal/create
|
|
86
|
+
* shell seam:terminal/create 的可执行解析走这里。kimi 的
|
|
87
87
|
* ACP 服务端固定要 `bash -c`,win32 上没有 Git Bash 时必须在 seam 里变成
|
|
88
88
|
* 明确错误,而不是一个语焉不详的 spawn ENOENT。
|
|
89
89
|
*/
|
|
@@ -105,7 +105,7 @@ export declare class KimiAcpRunner extends BaseAcpRunner<KimiAcpTranslator> {
|
|
|
105
105
|
* before producing the result. There is NO total timeout — silence beyond
|
|
106
106
|
* compactIdleTimeoutMs yields 'unknown' (never a fake success). Keeps the
|
|
107
107
|
* connection alive while the background compaction finishes — otherwise
|
|
108
|
-
* dispose/exit kills it and the record never lands (
|
|
108
|
+
* dispose/exit kills it and the record never lands (打回复现 /
|
|
109
109
|
* 2026-08-31 事故).
|
|
110
110
|
*/
|
|
111
111
|
protected compactBaseline(sessionId: string, opts: SpawnOptions): number;
|
|
@@ -113,7 +113,7 @@ export declare class KimiAcpRunner extends BaseAcpRunner<KimiAcpTranslator> {
|
|
|
113
113
|
protected buildApprovalOutcome(action: string, pending: AcpPendingApproval, response: unknown): unknown;
|
|
114
114
|
getStatusInfo(): AgentStatusInfo;
|
|
115
115
|
/**
|
|
116
|
-
* Hot-apply a permission-mode change to the live ACP session
|
|
116
|
+
* Hot-apply a permission-mode change to the live ACP session.
|
|
117
117
|
*
|
|
118
118
|
* The local cache is always updated so `getStatusInfo()` (and therefore
|
|
119
119
|
* `/s`) reflects the new mode immediately. When a session is connected,
|