lark-remote 0.1.13 → 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 +25 -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/index.d.ts +0 -7
- package/dist/bridge/index.js +21 -63
- 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 -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 +66 -6
- package/dist/router/index.js +450 -315
- 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 +34 -57
- 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 +6 -5
- 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
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { NotificationMethod, ServerRequestMethod, SessionEventType, } from './protocol-types.js';
|
|
2
|
+
/**
|
|
3
|
+
* ACP tool kind → 常用工具名映射(信息保真 C3.1,数据驱动无 agent 分支)。
|
|
4
|
+
* kind 命中映射时用映射名渲染结构化面板;否则回落 title(维持现状)。
|
|
5
|
+
*/
|
|
6
|
+
const KIND_TO_TOOL = {
|
|
7
|
+
execute: 'Bash',
|
|
8
|
+
read: 'Read',
|
|
9
|
+
edit: 'Edit',
|
|
10
|
+
write: 'Write',
|
|
11
|
+
search: 'Grep',
|
|
12
|
+
fetch: 'WebFetch',
|
|
13
|
+
};
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// Translator base class
|
|
16
|
+
// =============================================================================
|
|
17
|
+
export class BaseAcpTranslator {
|
|
18
|
+
/** Current operation kind (turn vs compact). */
|
|
19
|
+
operationKind = 'turn';
|
|
20
|
+
/** Live usage snapshot from usage_update events (context occupancy only). */
|
|
21
|
+
liveUsage = {};
|
|
22
|
+
/** Whether any usage_update has been seen this turn (no update → no usage). */
|
|
23
|
+
hasLiveUsage = false;
|
|
24
|
+
/** Session cost from usage_update cost.amount (USD). Written by opencode only. */
|
|
25
|
+
liveCostUsd;
|
|
26
|
+
/** Current turn id, set by produceTurnStarted. */
|
|
27
|
+
currentTurnId = '';
|
|
28
|
+
/**
|
|
29
|
+
* Handle a notification from the ACP server and return translated events.
|
|
30
|
+
* Wire envelope: {sessionId, update: {sessionUpdate: '<kind>', ...}} — the
|
|
31
|
+
* discriminator is `update.sessionUpdate`.
|
|
32
|
+
*/
|
|
33
|
+
handleNotification(method, params) {
|
|
34
|
+
if (method !== NotificationMethod.SESSION_UPDATE) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
const notif = params;
|
|
38
|
+
const update = notif.update;
|
|
39
|
+
switch (update.sessionUpdate) {
|
|
40
|
+
case SessionEventType.AGENT_MESSAGE_CHUNK:
|
|
41
|
+
return this.handleAgentMessageChunk(update, notif.sessionId);
|
|
42
|
+
case SessionEventType.AGENT_THOUGHT_CHUNK:
|
|
43
|
+
return this.handleAgentThoughtChunk(update, notif.sessionId);
|
|
44
|
+
case SessionEventType.TOOL_CALL:
|
|
45
|
+
return this.handleToolCall(update, notif.sessionId);
|
|
46
|
+
case SessionEventType.TOOL_CALL_UPDATE:
|
|
47
|
+
return this.handleToolCallUpdate(update, notif.sessionId);
|
|
48
|
+
case SessionEventType.USAGE_UPDATE:
|
|
49
|
+
return this.handleUsageUpdate(update);
|
|
50
|
+
case SessionEventType.PLAN:
|
|
51
|
+
return this.handlePlan(update);
|
|
52
|
+
case SessionEventType.SESSION_INFO_UPDATE: {
|
|
53
|
+
// 会话标题更新 → session_info 事件(title 缺省时不发,避免空事件噪声)。
|
|
54
|
+
const info = update;
|
|
55
|
+
if (!info.title)
|
|
56
|
+
return [];
|
|
57
|
+
const event = {
|
|
58
|
+
type: 'session_info',
|
|
59
|
+
title: info.title,
|
|
60
|
+
timestamp: new Date().toISOString(),
|
|
61
|
+
};
|
|
62
|
+
return [event];
|
|
63
|
+
}
|
|
64
|
+
case SessionEventType.CURRENT_MODE_UPDATE: {
|
|
65
|
+
// 模式切换 → session_info 事件(mode 字段)。
|
|
66
|
+
const mode = update;
|
|
67
|
+
const event = {
|
|
68
|
+
type: 'session_info',
|
|
69
|
+
mode: mode.currentModeId,
|
|
70
|
+
timestamp: new Date().toISOString(),
|
|
71
|
+
};
|
|
72
|
+
return [event];
|
|
73
|
+
}
|
|
74
|
+
default:
|
|
75
|
+
// Control-plane noise (config echoes, user replay) — not content.
|
|
76
|
+
// available_commands_update / config_option_update 维持丢弃:
|
|
77
|
+
// 控制面回显,无用户价值。
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Handle a server request (reverse RPC from the ACP server). Subclasses
|
|
83
|
+
* hook additional methods via handleOtherServerRequest.
|
|
84
|
+
*/
|
|
85
|
+
handleServerRequest(id, method, params) {
|
|
86
|
+
if (method === ServerRequestMethod.REQUEST_PERMISSION) {
|
|
87
|
+
return this.handleRequestPermission(id, params);
|
|
88
|
+
}
|
|
89
|
+
return this.handleOtherServerRequest(id, method, params);
|
|
90
|
+
}
|
|
91
|
+
/** Hook for server requests other than session/request_permission. */
|
|
92
|
+
handleOtherServerRequest(_id, _method, _params) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Produce a turn_started event. Called by the runner when session/prompt
|
|
97
|
+
* is sent (no wire notification for this — self-produced).
|
|
98
|
+
*/
|
|
99
|
+
produceTurnStarted(sessionId, turnId) {
|
|
100
|
+
this.currentTurnId = turnId;
|
|
101
|
+
return {
|
|
102
|
+
type: 'turn_started',
|
|
103
|
+
threadId: sessionId,
|
|
104
|
+
turnId,
|
|
105
|
+
operationKind: this.operationKind === 'compact' ? 'compaction' : 'turn',
|
|
106
|
+
timestamp: new Date().toISOString(),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Translate a prompt response into a result event.
|
|
111
|
+
* stopReason: end_turn → success; cancelled → interrupted (independent
|
|
112
|
+
* terminal state, must NOT merge into error); anything else
|
|
113
|
+
* (max_tokens / refusal / …) → error.
|
|
114
|
+
*/
|
|
115
|
+
handlePromptResponse(sessionId, result) {
|
|
116
|
+
const subtype = result.stopReason === 'end_turn'
|
|
117
|
+
? 'success'
|
|
118
|
+
: result.stopReason === 'cancelled'
|
|
119
|
+
? 'interrupted'
|
|
120
|
+
: 'error';
|
|
121
|
+
return {
|
|
122
|
+
type: 'result',
|
|
123
|
+
subtype,
|
|
124
|
+
session_id: sessionId,
|
|
125
|
+
...(this.hasLiveUsage ? { usage: this.liveUsageSnapshot() } : {}),
|
|
126
|
+
...(this.liveCostUsd !== undefined ? { total_cost_usd: this.liveCostUsd } : {}),
|
|
127
|
+
...(subtype === 'error'
|
|
128
|
+
? { errorMessage: `Prompt ended with stopReason: ${result.stopReason}` }
|
|
129
|
+
: {}),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Produce an error result event (for JSON-RPC errors during prompt).
|
|
134
|
+
*/
|
|
135
|
+
produceErrorResult(sessionId, errorMessage) {
|
|
136
|
+
return {
|
|
137
|
+
type: 'result',
|
|
138
|
+
subtype: 'error',
|
|
139
|
+
session_id: sessionId,
|
|
140
|
+
errorMessage,
|
|
141
|
+
...(this.hasLiveUsage ? { usage: this.liveUsageSnapshot() } : {}),
|
|
142
|
+
...(this.liveCostUsd !== undefined ? { total_cost_usd: this.liveCostUsd } : {}),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Set the operation kind (turn vs compact).
|
|
147
|
+
*/
|
|
148
|
+
setOperationKind(kind) {
|
|
149
|
+
this.operationKind = kind;
|
|
150
|
+
}
|
|
151
|
+
// =========================================================================
|
|
152
|
+
// Shared notification handlers
|
|
153
|
+
// =========================================================================
|
|
154
|
+
/**
|
|
155
|
+
* plan update → PlanEvent(信息保真 C3.2:不再显式丢弃)。
|
|
156
|
+
* ACP PlanEntry: { content, priority, status }(status: pending/in_progress/
|
|
157
|
+
* completed)。基类具体方法:kimi/opencode wire 结构相同,直接复用。
|
|
158
|
+
*/
|
|
159
|
+
handlePlan(event) {
|
|
160
|
+
const ICON = { completed: '✅', in_progress: '🔄', pending: '⬜', error: '❌' };
|
|
161
|
+
const entries = Array.isArray(event.entries) ? event.entries : [];
|
|
162
|
+
const markdown = entries
|
|
163
|
+
.map((e) => {
|
|
164
|
+
const entry = e;
|
|
165
|
+
const status = entry.status && entry.status in ICON ? entry.status : 'pending';
|
|
166
|
+
return `${ICON[status]} ${entry.content ?? ''}`;
|
|
167
|
+
})
|
|
168
|
+
.join('\n')
|
|
169
|
+
.trim();
|
|
170
|
+
if (!markdown)
|
|
171
|
+
return [];
|
|
172
|
+
return [{ type: 'plan', plan: markdown, timestamp: new Date().toISOString() }];
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* tool_call → assistant/tool_use. rawInput normalization is contractual:
|
|
176
|
+
* missing → {} (lazy-create tool_call carries no rawInput field), string →
|
|
177
|
+
* JSON.parse (older agents send JSON strings) — keeps the card from
|
|
178
|
+
* crashing on undefined input.
|
|
179
|
+
*
|
|
180
|
+
* 信息保真 C3.1:kind 命中 KIND_TO_TOOL 时用映射名渲染结构化面板;
|
|
181
|
+
* title 与 name 不同时把 title 存到 content block 的 summary 字段
|
|
182
|
+
* (渲染层做面板副标题)。
|
|
183
|
+
*/
|
|
184
|
+
handleToolCall(event, _sessionId) {
|
|
185
|
+
let input = event.rawInput ?? {};
|
|
186
|
+
if (typeof event.rawInput === 'string') {
|
|
187
|
+
try {
|
|
188
|
+
input = JSON.parse(event.rawInput);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// Keep raw string as-is
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const mapped = event.kind ? KIND_TO_TOOL[event.kind] : undefined;
|
|
195
|
+
const name = mapped ?? event.title;
|
|
196
|
+
const summary = mapped && event.title && event.title !== name ? event.title : undefined;
|
|
197
|
+
return [
|
|
198
|
+
{
|
|
199
|
+
type: 'assistant',
|
|
200
|
+
message: {
|
|
201
|
+
content: [
|
|
202
|
+
{
|
|
203
|
+
type: 'tool_use',
|
|
204
|
+
id: event.toolCallId,
|
|
205
|
+
name,
|
|
206
|
+
input,
|
|
207
|
+
...(summary !== undefined ? { summary } : {}),
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
},
|
|
211
|
+
timestamp: new Date().toISOString(),
|
|
212
|
+
},
|
|
213
|
+
];
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* tool_call_update → user/tool_result. status:'failed' → is_error:true.
|
|
217
|
+
*/
|
|
218
|
+
handleToolCallUpdate(event, _sessionId) {
|
|
219
|
+
const isError = event.status === 'failed';
|
|
220
|
+
const content = this.extractToolResultContent(event);
|
|
221
|
+
return [
|
|
222
|
+
{
|
|
223
|
+
type: 'user',
|
|
224
|
+
message: {
|
|
225
|
+
content: [
|
|
226
|
+
{
|
|
227
|
+
type: 'tool_result',
|
|
228
|
+
tool_use_id: event.toolCallId,
|
|
229
|
+
content,
|
|
230
|
+
is_error: isError,
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
timestamp: new Date().toISOString(),
|
|
235
|
+
},
|
|
236
|
+
];
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* tool_result content extraction. Default: rawOutput passthrough (kimi
|
|
240
|
+
* semantics). opencode overrides — its rawOutput is an OBJECT
|
|
241
|
+
* ({output} / {error}).
|
|
242
|
+
*/
|
|
243
|
+
extractToolResultContent(event) {
|
|
244
|
+
return event.rawOutput ?? '';
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* usage_update → context occupancy bookkeeping. The wire shape is
|
|
248
|
+
* {used, size} (+ opencode: cost:{amount}) — context occupancy, not
|
|
249
|
+
* cumulative tokens.
|
|
250
|
+
*/
|
|
251
|
+
handleUsageUpdate(event) {
|
|
252
|
+
this.liveUsage = {
|
|
253
|
+
total_tokens: event.used,
|
|
254
|
+
context_limit: event.size,
|
|
255
|
+
};
|
|
256
|
+
this.hasLiveUsage = true;
|
|
257
|
+
this.onUsageSample(event);
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
260
|
+
/** Extra per-sample usage bookkeeping (opencode: session cost amount). */
|
|
261
|
+
onUsageSample(_event) { }
|
|
262
|
+
/**
|
|
263
|
+
* ResultEvent.usage requires input_tokens/output_tokens: number, but the
|
|
264
|
+
* ACP wire has no such split — the live snapshot is context occupancy only.
|
|
265
|
+
* Cast is deliberate: consumers must tolerate undefined input/output.
|
|
266
|
+
*/
|
|
267
|
+
liveUsageSnapshot() {
|
|
268
|
+
return { ...this.liveUsage };
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=base-translator.js.map
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Extracted from runner.ts and translator.ts to avoid duplication (G5).
|
|
5
5
|
* Shape source: approval.ts:28-29 — optionId is opaque, echo back as-is.
|
|
6
6
|
*/
|
|
7
|
+
import type { ApprovalView } from '../../types.js';
|
|
7
8
|
import type { PermissionOption } from './protocol-types.js';
|
|
8
9
|
/**
|
|
9
10
|
* Find an option by matching its `kind` against a list of candidate values,
|
|
@@ -43,3 +44,41 @@ export declare function buildAcpPermissionOutcome(action: string, options: Permi
|
|
|
43
44
|
export declare function deriveAcpAvailableDecisions(options: PermissionOption[]): string[];
|
|
44
45
|
/** 截断长文本并追加省略号(translator 审批 reason 用,原先两份相同拷贝)。 */
|
|
45
46
|
export declare function truncateWithEllipsis(text: string, maxLen: number): string;
|
|
47
|
+
/**
|
|
48
|
+
* 待审批请求登记(requestId → 登记,runner 持 Map)。
|
|
49
|
+
* kimi 的提问分叉额外带 proto 来源标记。
|
|
50
|
+
*/
|
|
51
|
+
export interface AcpPendingApproval {
|
|
52
|
+
kind: 'command' | 'file' | 'permissions' | 'question' | 'tool';
|
|
53
|
+
view: ApprovalView;
|
|
54
|
+
options: PermissionOption[];
|
|
55
|
+
/** 提问来源(kimi elicitation):elicitation form / request_permission 兜底桥。 */
|
|
56
|
+
proto?: 'elicitation' | 'permission';
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* respondApproval 共用骨架:取 pending → 构建 ACP 响应 → respond + 清除 + 日志。
|
|
60
|
+
* buildOutcome 由调用方注入(kimi question 走 elicitation 回编,其余走
|
|
61
|
+
* buildAcpPermissionOutcome)。client 缺失或 requestId 未知时静默丢弃。
|
|
62
|
+
*/
|
|
63
|
+
export declare function respondAcpApproval(opts: {
|
|
64
|
+
client: {
|
|
65
|
+
respond(requestId: number | string, response: unknown): void;
|
|
66
|
+
} | null | undefined;
|
|
67
|
+
pendingApprovals: Map<number | string, AcpPendingApproval>;
|
|
68
|
+
requestId: number | string;
|
|
69
|
+
response: unknown;
|
|
70
|
+
logTag: string;
|
|
71
|
+
buildOutcome: (action: string, pending: AcpPendingApproval, response: unknown) => unknown;
|
|
72
|
+
}): void;
|
|
73
|
+
/**
|
|
74
|
+
* updateApprovalMode 共用尾段:本地缓存已由调用方按各自语义更新,会话在连时
|
|
75
|
+
* 重发 session/set_mode。失败非致命(下一次 setupTurn 会重新套用缓存模式)。
|
|
76
|
+
*/
|
|
77
|
+
export declare function sendAcpSetMode(opts: {
|
|
78
|
+
client: {
|
|
79
|
+
request(method: string, params: unknown): Promise<unknown>;
|
|
80
|
+
} | null | undefined;
|
|
81
|
+
activeSessionId: string | null | undefined;
|
|
82
|
+
modeId: string;
|
|
83
|
+
logTag: string;
|
|
84
|
+
}): Promise<void>;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Extracted from runner.ts and translator.ts to avoid duplication (G5).
|
|
5
5
|
* Shape source: approval.ts:28-29 — optionId is opaque, echo back as-is.
|
|
6
6
|
*/
|
|
7
|
+
import { getLogger } from '../../../logger/index.js';
|
|
7
8
|
/**
|
|
8
9
|
* Find an option by matching its `kind` against a list of candidate values,
|
|
9
10
|
* and return the `optionId` to echo back in the approval response.
|
|
@@ -66,4 +67,37 @@ export function deriveAcpAvailableDecisions(options) {
|
|
|
66
67
|
export function truncateWithEllipsis(text, maxLen) {
|
|
67
68
|
return text.length <= maxLen ? text : text.slice(0, maxLen) + '…';
|
|
68
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* respondApproval 共用骨架:取 pending → 构建 ACP 响应 → respond + 清除 + 日志。
|
|
72
|
+
* buildOutcome 由调用方注入(kimi question 走 elicitation 回编,其余走
|
|
73
|
+
* buildAcpPermissionOutcome)。client 缺失或 requestId 未知时静默丢弃。
|
|
74
|
+
*/
|
|
75
|
+
export function respondAcpApproval(opts) {
|
|
76
|
+
const pending = opts.pendingApprovals.get(opts.requestId);
|
|
77
|
+
if (!opts.client || !pending)
|
|
78
|
+
return;
|
|
79
|
+
const action = opts.response?.action ?? 'decline';
|
|
80
|
+
const acpResponse = opts.buildOutcome(action, pending, opts.response);
|
|
81
|
+
opts.client.respond(opts.requestId, acpResponse);
|
|
82
|
+
opts.pendingApprovals.delete(opts.requestId);
|
|
83
|
+
getLogger().info(`[${opts.logTag}] approval responded requestId=${opts.requestId} action=${action}`);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* updateApprovalMode 共用尾段:本地缓存已由调用方按各自语义更新,会话在连时
|
|
87
|
+
* 重发 session/set_mode。失败非致命(下一次 setupTurn 会重新套用缓存模式)。
|
|
88
|
+
*/
|
|
89
|
+
export async function sendAcpSetMode(opts) {
|
|
90
|
+
if (!opts.client || !opts.activeSessionId)
|
|
91
|
+
return;
|
|
92
|
+
try {
|
|
93
|
+
await opts.client.request('session/set_mode', {
|
|
94
|
+
sessionId: opts.activeSessionId,
|
|
95
|
+
modeId: opts.modeId,
|
|
96
|
+
});
|
|
97
|
+
getLogger().info(`[${opts.logTag}] session/set_mode hot-applied session=${opts.activeSessionId} modeId=${opts.modeId}`);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
getLogger().warn(`[${opts.logTag}] session/set_mode failed (non-fatal): ${err.message}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
69
103
|
//# sourceMappingURL=protocol-helpers.js.map
|
|
@@ -22,6 +22,8 @@ export declare class JsonlRpcTransport {
|
|
|
22
22
|
/** JSON lines buffered while stdin is under backpressure (bounded by callers). */
|
|
23
23
|
private writeQueue;
|
|
24
24
|
private flushing;
|
|
25
|
+
/** §4.4 win32 嗅探嫌疑标记(双条件定性见下方 onExit)。 */
|
|
26
|
+
private commandNotFoundSeen;
|
|
25
27
|
constructor(opts: {
|
|
26
28
|
binary: string;
|
|
27
29
|
args: string[];
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* each line on stdout is a JSON message from the server; each `write()`
|
|
7
7
|
* sends a JSON line to stdin.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import { spawnProcess, mergeProcessEnv, isWindowsCommandNotFoundLine, useDetachedProcessGroup, } from '../../../platform/spawn.js';
|
|
10
10
|
import { ProcessStopper } from '../process-stopper.js';
|
|
11
11
|
import { getLogger } from '../../../logger/index.js';
|
|
12
12
|
/** Max bytes for a single line before we disconnect (10MB). */
|
|
@@ -25,6 +25,8 @@ export class JsonlRpcTransport {
|
|
|
25
25
|
/** JSON lines buffered while stdin is under backpressure (bounded by callers). */
|
|
26
26
|
writeQueue = [];
|
|
27
27
|
flushing = false;
|
|
28
|
+
/** §4.4 win32 嗅探嫌疑标记(双条件定性见下方 onExit)。 */
|
|
29
|
+
commandNotFoundSeen = false;
|
|
28
30
|
constructor(opts) {
|
|
29
31
|
this.binary = opts.binary;
|
|
30
32
|
this.args = opts.args;
|
|
@@ -41,14 +43,18 @@ export class JsonlRpcTransport {
|
|
|
41
43
|
*/
|
|
42
44
|
async start(events) {
|
|
43
45
|
this.events = events;
|
|
46
|
+
this.commandNotFoundSeen = false;
|
|
44
47
|
// agent 是用户自己的可信二进制,provider 认证依赖 API key /
|
|
45
48
|
// 自定义 provider 的 env_key,代理环境依赖 HTTP(S)_PROXY、TMPDIR 等;
|
|
46
49
|
// 任何白名单收窄都会打断认证或网络。调用方 this.env 覆盖 process.env 同名键。
|
|
47
|
-
|
|
48
|
-
const
|
|
50
|
+
// env 覆盖必须大小写不敏感合并(win32 PATH/Path 双键防护,v2 §8.3)
|
|
51
|
+
const childEnv = mergeProcessEnv(process.env, this.env);
|
|
52
|
+
const proc = spawnProcess(this.binary, this.args, {
|
|
49
53
|
cwd: this.cwd,
|
|
50
54
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
51
|
-
detached
|
|
55
|
+
// posix 建新进程组(负 PID 组杀);win32 不 detached(`.cmd` 垫片丢 stdio)
|
|
56
|
+
detached: useDetachedProcessGroup(),
|
|
57
|
+
windowsHide: true,
|
|
52
58
|
env: childEnv,
|
|
53
59
|
});
|
|
54
60
|
this.proc = proc;
|
|
@@ -74,6 +80,14 @@ export class JsonlRpcTransport {
|
|
|
74
80
|
const text = chunk.toString('utf-8');
|
|
75
81
|
stderrBuf = (stderrBuf + text).slice(-MAX_STDERR_BYTES);
|
|
76
82
|
getLogger().debug(`[jsonrpc-transport] stderr: ${text.trimEnd()}`);
|
|
83
|
+
// §4.4 win32 command-not-found 嗅探:命中只记嫌疑标记,不立即杀——
|
|
84
|
+
// agent 正常输出可能引用该错误文本(调试 Windows 报错场景),见行即杀
|
|
85
|
+
// 会误杀 run;定性由 onExit 的「标记 + 非零退出」双条件完成,真挂起由
|
|
86
|
+
// 上层超时兜底
|
|
87
|
+
if (isWindowsCommandNotFoundLine(text)) {
|
|
88
|
+
this.commandNotFoundSeen = true;
|
|
89
|
+
getLogger().error(`[jsonrpc-transport] command not found suspected (win32 shim): ${text.trimEnd()}`);
|
|
90
|
+
}
|
|
77
91
|
});
|
|
78
92
|
// Stdout: line-split with 10MB line limit
|
|
79
93
|
let remainder = '';
|
|
@@ -113,7 +127,13 @@ export class JsonlRpcTransport {
|
|
|
113
127
|
// ignore trailing incomplete line
|
|
114
128
|
}
|
|
115
129
|
}
|
|
116
|
-
|
|
130
|
+
// §4.4 双条件定性:嫌疑标记 + 非零正常退出 → command-not-found。
|
|
131
|
+
// reason 仅供日志/诊断,消费方不解析该字符串(baseOnClose 无参)。
|
|
132
|
+
let reason = signal ? `signal:${signal}` : `exit:${code}`;
|
|
133
|
+
if (this.commandNotFoundSeen && signal === null && code !== null && code !== 0) {
|
|
134
|
+
getLogger().error(`[jsonrpc-transport] command not found confirmed (win32 shim): exit=${code} stderr=${stderrBuf.slice(-512)}`);
|
|
135
|
+
reason = `command_not_found:${reason}`;
|
|
136
|
+
}
|
|
117
137
|
events.onClose(reason);
|
|
118
138
|
getLogger().info(`[jsonrpc-transport] process exited pid=${proc.pid} code=${code} signal=${signal}`);
|
|
119
139
|
};
|
|
@@ -1,29 +1,7 @@
|
|
|
1
|
-
import type { ChildProcess } from 'node:child_process';
|
|
2
|
-
interface ProcessStopperOptions {
|
|
3
|
-
graceMs: number;
|
|
4
|
-
}
|
|
5
|
-
interface StopOptions {
|
|
6
|
-
immediate?: boolean;
|
|
7
|
-
}
|
|
8
1
|
/**
|
|
9
|
-
*
|
|
2
|
+
* 已迁移到平台 seam:`src/platform/terminator-posix.ts`(design.md §3.2)。
|
|
10
3
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
4
|
+
* 本文件只保留 re-export,使既有调用方(bash runner、jsonrpc transport 等)
|
|
5
|
+
* 与既有测试不受影响;M2 调用点迁移时逐个换成 `createTerminator()` 后删除。
|
|
13
6
|
*/
|
|
14
|
-
export
|
|
15
|
-
private graceMs;
|
|
16
|
-
constructor(opts: ProcessStopperOptions);
|
|
17
|
-
/**
|
|
18
|
-
* Stop a child process: SIGTERM → wait grace → SIGKILL.
|
|
19
|
-
*
|
|
20
|
-
* Uses negative PID to kill the entire process group, ensuring child
|
|
21
|
-
* processes (shell wrappers, sub-processes) are also terminated.
|
|
22
|
-
*
|
|
23
|
-
* Pass `immediate: true` to skip the grace window — SIGTERM and SIGKILL
|
|
24
|
-
* are sent back-to-back. Safe to call on an already-exited process.
|
|
25
|
-
*/
|
|
26
|
-
/** proc 可为 null(调用方已清理 currentProcess);nullish 直接返回。 */
|
|
27
|
-
stop(proc: ChildProcess | null, opts?: StopOptions): Promise<void>;
|
|
28
|
-
}
|
|
29
|
-
export {};
|
|
7
|
+
export { ProcessStopper } from '../../platform/terminator-posix.js';
|
|
@@ -1,80 +1,8 @@
|
|
|
1
|
-
import { getLogger } from '../../logger/index.js';
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
2
|
+
* 已迁移到平台 seam:`src/platform/terminator-posix.ts`(design.md §3.2)。
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* 本文件只保留 re-export,使既有调用方(bash runner、jsonrpc transport 等)
|
|
5
|
+
* 与既有测试不受影响;M2 调用点迁移时逐个换成 `createTerminator()` 后删除。
|
|
7
6
|
*/
|
|
8
|
-
export
|
|
9
|
-
graceMs;
|
|
10
|
-
constructor(opts) {
|
|
11
|
-
this.graceMs = opts.graceMs;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Stop a child process: SIGTERM → wait grace → SIGKILL.
|
|
15
|
-
*
|
|
16
|
-
* Uses negative PID to kill the entire process group, ensuring child
|
|
17
|
-
* processes (shell wrappers, sub-processes) are also terminated.
|
|
18
|
-
*
|
|
19
|
-
* Pass `immediate: true` to skip the grace window — SIGTERM and SIGKILL
|
|
20
|
-
* are sent back-to-back. Safe to call on an already-exited process.
|
|
21
|
-
*/
|
|
22
|
-
/** proc 可为 null(调用方已清理 currentProcess);nullish 直接返回。 */
|
|
23
|
-
async stop(proc, opts) {
|
|
24
|
-
if (!proc || proc.exitCode !== null || proc.signalCode !== null) {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
const pid = proc.pid;
|
|
28
|
-
if (pid === undefined) {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
// Use negative PID to kill the entire process group.
|
|
32
|
-
// This ensures child processes (shell wrappers, sub-processes) are also killed.
|
|
33
|
-
// If the process is not a group leader, kill(-pid) behaves same as kill(pid).
|
|
34
|
-
const pgid = -pid;
|
|
35
|
-
const immediate = opts?.immediate === true;
|
|
36
|
-
getLogger().debug(`[process-stopper] sending SIGTERM to pgid=${pgid} immediate=${immediate}`);
|
|
37
|
-
try {
|
|
38
|
-
process.kill(pgid, 'SIGTERM');
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
/* process may have exited */
|
|
42
|
-
}
|
|
43
|
-
if (immediate) {
|
|
44
|
-
// Don't wait — send SIGKILL back-to-back. Even if proc just exited
|
|
45
|
-
// from SIGTERM, kill(SIGKILL) on a dead pid throws ESRCH which we ignore.
|
|
46
|
-
try {
|
|
47
|
-
process.kill(pgid, 'SIGKILL');
|
|
48
|
-
}
|
|
49
|
-
catch { }
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
// Wait for exit with grace period. Use `once` so repeated stop() calls
|
|
53
|
-
// don't accumulate listeners on the proc. P2-15: save the grace timer id
|
|
54
|
-
// and clear it once the race settles — otherwise the timer lingers after
|
|
55
|
-
// an early exit, keeping the event loop alive and accumulating across
|
|
56
|
-
// frequent stop() calls.
|
|
57
|
-
let graceTimer;
|
|
58
|
-
const exited = await Promise.race([
|
|
59
|
-
new Promise((resolve) => {
|
|
60
|
-
if (proc.exitCode !== null || proc.signalCode !== null)
|
|
61
|
-
return resolve(true);
|
|
62
|
-
proc.once('exit', () => resolve(true));
|
|
63
|
-
}),
|
|
64
|
-
new Promise((resolve) => {
|
|
65
|
-
graceTimer = setTimeout(() => resolve(false), this.graceMs);
|
|
66
|
-
}),
|
|
67
|
-
]);
|
|
68
|
-
if (graceTimer)
|
|
69
|
-
clearTimeout(graceTimer);
|
|
70
|
-
if (!exited) {
|
|
71
|
-
getLogger().info(`[process-stopper] process group ${pgid} did not exit within grace period, sending SIGKILL`);
|
|
72
|
-
try {
|
|
73
|
-
process.kill(pgid, 'SIGKILL');
|
|
74
|
-
}
|
|
75
|
-
catch { }
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
7
|
+
export { ProcessStopper } from '../../platform/terminator-posix.js';
|
|
80
8
|
//# sourceMappingURL=process-stopper.js.map
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import type { AgentEvent } from '../types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Return the stdio config ['pipe','pipe','pipe'] used by the runner that
|
|
4
|
-
* writes the prompt to the child's stdin (claude).
|
|
5
|
-
*/
|
|
6
|
-
export declare function pipeAllStdio(): ('ignore' | 'pipe')[];
|
|
7
2
|
/**
|
|
8
3
|
* Build a synthetic system.init event for pre-spawn failure paths.
|
|
9
4
|
*
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Return the stdio config ['pipe','pipe','pipe'] used by the runner that
|
|
3
|
-
* writes the prompt to the child's stdin (claude).
|
|
4
|
-
*/
|
|
5
|
-
export function pipeAllStdio() {
|
|
6
|
-
return ['pipe', 'pipe', 'pipe'];
|
|
7
|
-
}
|
|
8
1
|
/**
|
|
9
2
|
* Build a synthetic system.init event for pre-spawn failure paths.
|
|
10
3
|
*
|