lark-remote 0.5.10 → 0.5.13
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 +51 -2
- package/README.en.md +1 -14
- package/README.md +1 -14
- package/dist/binder.js +7 -1
- package/dist/bridge/approval-coordinator.d.ts +1 -0
- package/dist/bridge/approval-coordinator.js +12 -24
- package/dist/bridge/index.d.ts +7 -0
- package/dist/bridge/index.js +89 -230
- package/dist/bridge/run-approval.d.ts +15 -0
- package/dist/bridge/run-approval.js +41 -0
- package/dist/bridge/run-usage.d.ts +46 -0
- package/dist/bridge/run-usage.js +96 -0
- package/dist/bridge/session-writeback.d.ts +18 -0
- package/dist/bridge/session-writeback.js +36 -0
- package/dist/card/approval-render.d.ts +14 -0
- package/dist/card/approval-render.js +155 -29
- package/dist/card/bash-card-session.js +10 -5
- package/dist/card/bash-renderer.js +11 -23
- package/dist/card/card-budget.js +24 -4
- package/dist/card/card-session.d.ts +2 -0
- package/dist/card/card-session.js +20 -22
- package/dist/card/card-shared.d.ts +4 -4
- package/dist/card/card-shared.js +3 -9
- package/dist/card/collapsible.js +3 -2
- package/dist/card/final-answer-overflow.d.ts +7 -0
- package/dist/card/final-answer-overflow.js +46 -0
- package/dist/card/run-card-session.js +2 -7
- package/dist/card/run-renderer.d.ts +14 -0
- package/dist/card/run-renderer.js +150 -10
- package/dist/card/run-state.d.ts +12 -1
- package/dist/card/run-state.js +73 -34
- package/dist/common/text-width.js +5 -0
- package/dist/common/tool-summary.js +4 -2
- package/dist/config/codex-config.js +5 -3
- package/dist/config/index.d.ts +4 -2
- package/dist/config/index.js +19 -5
- package/dist/inbound/turn-assembler.js +6 -0
- package/dist/index.js +15 -391
- package/dist/instance-lock.d.ts +9 -1
- package/dist/instance-lock.js +42 -7
- package/dist/message-handlers.d.ts +26 -0
- package/dist/message-handlers.js +275 -0
- package/dist/platform/identity.js +4 -3
- package/dist/platform/path.js +3 -1
- package/dist/platform/terminator-win32.d.ts +13 -1
- package/dist/platform/terminator-win32.js +4 -14
- package/dist/router/card-action-payload.d.ts +1 -1
- package/dist/router/card-action-payload.js +1 -1
- package/dist/router/commands/ls.d.ts +9 -0
- package/dist/router/commands/ls.js +243 -0
- package/dist/router/commands/order.d.ts +12 -0
- package/dist/router/commands/order.js +178 -0
- package/dist/router/commands/resume.d.ts +47 -0
- package/dist/router/commands/resume.js +246 -0
- package/dist/router/commands/ws.d.ts +15 -0
- package/dist/router/commands/ws.js +189 -0
- package/dist/router/config/claude.js +3 -3
- package/dist/router/config/codex.js +2 -6
- package/dist/router/config/common/turn-idle.d.ts +2 -0
- package/dist/router/config/common/turn-idle.js +9 -0
- package/dist/router/config/kimi.js +2 -6
- package/dist/router/config/opencode.js +2 -6
- package/dist/router/index.d.ts +14 -22
- package/dist/router/index.js +93 -1133
- package/dist/router/paging.d.ts +13 -0
- package/dist/router/paging.js +20 -0
- package/dist/router/path-validation.d.ts +27 -0
- package/dist/router/path-validation.js +45 -0
- package/dist/runner/claude/runner.d.ts +3 -4
- package/dist/runner/claude/runner.js +4 -5
- package/dist/runner/claude/session.js +1 -1
- package/dist/runner/codex/app-server/runner.js +12 -0
- package/dist/runner/codex/app-server/translator.d.ts +2 -0
- package/dist/runner/codex/app-server/translator.js +65 -9
- package/dist/runner/common/acp/base-translator.d.ts +15 -8
- package/dist/runner/common/acp/base-translator.js +38 -7
- package/dist/runner/common/jsonl-stream.js +12 -1
- package/dist/runner/common/jsonrpc/connection-manager.js +11 -1
- package/dist/runner/common/jsonrpc/transport.d.ts +2 -1
- package/dist/runner/common/jsonrpc/transport.js +7 -1
- package/dist/runner/common/spawning-runner.js +10 -4
- package/dist/runner/common/turn-started.d.ts +2 -0
- package/dist/runner/common/turn-started.js +10 -0
- package/dist/runner/dsh/approval.js +16 -3
- package/dist/runner/dsh/auth.js +2 -3
- package/dist/runner/dsh/client.d.ts +32 -7
- package/dist/runner/dsh/client.js +115 -149
- package/dist/runner/dsh/daemon.d.ts +0 -2
- package/dist/runner/dsh/daemon.js +1 -6
- package/dist/runner/dsh/errors.d.ts +5 -0
- package/dist/runner/dsh/errors.js +11 -0
- package/dist/runner/dsh/exit-cancel.d.ts +0 -2
- package/dist/runner/dsh/exit-cancel.js +63 -24
- package/dist/runner/dsh/home.d.ts +2 -0
- package/dist/runner/dsh/home.js +8 -0
- package/dist/runner/dsh/inflight.d.ts +0 -2
- package/dist/runner/dsh/inflight.js +1 -1
- package/dist/runner/dsh/mux-stream.d.ts +26 -0
- package/dist/runner/dsh/mux-stream.js +157 -0
- package/dist/runner/dsh/runner.d.ts +7 -0
- package/dist/runner/dsh/runner.js +42 -5
- package/dist/runner/dsh/translator.d.ts +0 -10
- package/dist/runner/dsh/translator.js +1 -1
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.js +42 -5
- package/dist/runner/kimi/acp/translator.d.ts +8 -0
- package/dist/runner/kimi/acp/translator.js +11 -12
- package/dist/runner/opencode/acp/translator.js +1 -12
- package/dist/runner/pi/rpc/runner.js +33 -5
- package/dist/runner/pi/rpc/translator.d.ts +2 -8
- package/dist/runner/pi/rpc/translator.js +2 -7
- package/dist/runner/registry.d.ts +0 -19
- package/dist/runner/registry.js +0 -32
- package/dist/runner/resolve-agent-choices.js +5 -1
- package/dist/runner/sync-agent-choices.js +4 -1
- package/dist/runner/types.d.ts +17 -1
- package/dist/session/claude/session-index.js +13 -4
- package/dist/session/codex/rollout-reader.js +51 -26
- package/dist/session/common/usage-accumulator.d.ts +10 -1
- package/dist/session/common/usage-accumulator.js +10 -2
- package/dist/session/dsh/sessions.js +29 -9
- package/dist/session/kimi/sessions.js +11 -7
- package/dist/session/opencode/sessions.js +15 -7
- package/dist/session/pi/sessions.js +3 -2
- package/dist/session/session-store.js +4 -3
- package/package.json +1 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { getLogger } from '../../logger/index.js';
|
|
2
|
+
import { DshError } from './errors.js';
|
|
3
|
+
export async function* openMuxStream(opts) {
|
|
4
|
+
const cookie = await opts.authCookie();
|
|
5
|
+
const socket = opts.webSocketFactory(`${opts.baseUrl.replace(/^http/, 'ws')}/api/remote.mux`, cookie ? { headers: { cookie } } : undefined);
|
|
6
|
+
const streamId = `lr-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
7
|
+
const queue = [];
|
|
8
|
+
let waiter = null;
|
|
9
|
+
let ended = false;
|
|
10
|
+
let failure;
|
|
11
|
+
let opened = false;
|
|
12
|
+
const settle = (frame) => {
|
|
13
|
+
if (waiter) {
|
|
14
|
+
const resolve = waiter;
|
|
15
|
+
waiter = null;
|
|
16
|
+
resolve(frame);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (frame)
|
|
20
|
+
queue.push(frame);
|
|
21
|
+
else
|
|
22
|
+
ended = true;
|
|
23
|
+
};
|
|
24
|
+
const connectTimer = setTimeout(() => {
|
|
25
|
+
if (opened || ended)
|
|
26
|
+
return;
|
|
27
|
+
failure = new DshError('transport', 'DSH remote mux WebSocket connect timeout');
|
|
28
|
+
ended = true;
|
|
29
|
+
settle(undefined);
|
|
30
|
+
try {
|
|
31
|
+
socket.close();
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
/* ignore close error */
|
|
35
|
+
}
|
|
36
|
+
}, opts.requestTimeoutMs);
|
|
37
|
+
connectTimer.unref?.();
|
|
38
|
+
socket.onopen = () => {
|
|
39
|
+
opened = true;
|
|
40
|
+
clearTimeout(connectTimer);
|
|
41
|
+
if (ended)
|
|
42
|
+
return;
|
|
43
|
+
try {
|
|
44
|
+
socket.send(JSON.stringify({
|
|
45
|
+
type: 'open',
|
|
46
|
+
streamId,
|
|
47
|
+
endpoint: opts.endpoint,
|
|
48
|
+
payload: opts.payload,
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
failure = new DshError('transport', `DSH remote mux send error: ${err.message}`);
|
|
53
|
+
ended = true;
|
|
54
|
+
settle(undefined);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
socket.onmessage = (event) => {
|
|
58
|
+
if (ended)
|
|
59
|
+
return;
|
|
60
|
+
const text = typeof event.data === 'string'
|
|
61
|
+
? event.data
|
|
62
|
+
: Buffer.isBuffer(event.data)
|
|
63
|
+
? event.data.toString('utf8')
|
|
64
|
+
: String(event.data);
|
|
65
|
+
let message;
|
|
66
|
+
try {
|
|
67
|
+
message = JSON.parse(text);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
getLogger().warn('[dsh] dropped malformed remote mux frame');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (message.streamId !== streamId)
|
|
74
|
+
return;
|
|
75
|
+
if (message.type === 'item' && message.value) {
|
|
76
|
+
settle(message.value);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (message.type === 'end') {
|
|
80
|
+
ended = true;
|
|
81
|
+
settle(undefined);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (message.type === 'error') {
|
|
85
|
+
failure = new DshError(message.error?.code ?? 'stream', message.error?.message ?? 'DSH remote stream failed', message.error?.details ?? {});
|
|
86
|
+
ended = true;
|
|
87
|
+
settle(undefined);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
socket.onerror = () => {
|
|
91
|
+
if (ended)
|
|
92
|
+
return;
|
|
93
|
+
failure = new DshError('transport', 'DSH remote mux WebSocket error');
|
|
94
|
+
ended = true;
|
|
95
|
+
settle(undefined);
|
|
96
|
+
};
|
|
97
|
+
socket.onunexpectedresponse = (event) => {
|
|
98
|
+
if (ended)
|
|
99
|
+
return;
|
|
100
|
+
const status = event.statusCode;
|
|
101
|
+
failure = new DshError('http', `DSH remote mux WebSocket HTTP ${status === undefined ? 'error' : status}`, status === undefined ? {} : { status });
|
|
102
|
+
ended = true;
|
|
103
|
+
settle(undefined);
|
|
104
|
+
};
|
|
105
|
+
socket.onclose = () => {
|
|
106
|
+
if (ended)
|
|
107
|
+
return;
|
|
108
|
+
ended = true;
|
|
109
|
+
settle(undefined);
|
|
110
|
+
};
|
|
111
|
+
const onAbort = () => {
|
|
112
|
+
if (ended)
|
|
113
|
+
return;
|
|
114
|
+
ended = true;
|
|
115
|
+
settle(undefined);
|
|
116
|
+
try {
|
|
117
|
+
socket.close();
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
/* ignore close error */
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
opts.signal.addEventListener('abort', onAbort, { once: true });
|
|
124
|
+
const next = () => new Promise((resolve) => {
|
|
125
|
+
if (queue.length > 0) {
|
|
126
|
+
resolve(queue.shift());
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (ended) {
|
|
130
|
+
resolve(undefined);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
waiter = resolve;
|
|
134
|
+
});
|
|
135
|
+
try {
|
|
136
|
+
while (!opts.signal.aborted) {
|
|
137
|
+
const frame = await next();
|
|
138
|
+
if (frame === undefined) {
|
|
139
|
+
if (failure && !opts.signal.aborted)
|
|
140
|
+
throw failure;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
yield frame;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
clearTimeout(connectTimer);
|
|
148
|
+
opts.signal.removeEventListener('abort', onAbort);
|
|
149
|
+
try {
|
|
150
|
+
socket.close();
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
/* ignore close error */
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=mux-stream.js.map
|
|
@@ -230,6 +230,13 @@ export declare class DshRunner implements AgentRunner {
|
|
|
230
230
|
* that is the only client-side protection against a late orphan turn.
|
|
231
231
|
*/
|
|
232
232
|
private findPendingPromptWithin;
|
|
233
|
+
/**
|
|
234
|
+
* cancel 确认 closed 后的收尾:cancel 是 keepInbox,prompt 可能仍在 inbox。
|
|
235
|
+
* 在则撤回(防被下一个 turn 隐形认领),确认不在(已被本 turn 消费)或
|
|
236
|
+
* 撤回成功才清 inflight 标记;探测/撤回失败保留标记,交给下次启动的
|
|
237
|
+
* reconcile(findPendingPrompt 落空即自然清除,无副作用)。
|
|
238
|
+
*/
|
|
239
|
+
private withdrawPendingPromptAfterCancel;
|
|
233
240
|
private removePendingPrompt;
|
|
234
241
|
/** One cancel attempt, then one retry (the request is idempotent when idle). */
|
|
235
242
|
private cancelWithRetry;
|
|
@@ -1216,8 +1216,14 @@ export class DshRunner {
|
|
|
1216
1216
|
const state = await this.waitForClosed(sessionId, deadline);
|
|
1217
1217
|
if (state === 'closed') {
|
|
1218
1218
|
releaseGate();
|
|
1219
|
-
|
|
1220
|
-
|
|
1219
|
+
// cancel 是 keepInbox:prompt 可能仍在 inbox 里,等下一个无人订阅的
|
|
1220
|
+
// turn 认领(探测超时/迟到 admit 退到 cancel 分支时,
|
|
1221
|
+
// 旧实现直接清 inflight 标记——被停的 prompt 会被后续 turn 隐形执行
|
|
1222
|
+
//(耗 token、改工作区,卡片不可见),启动对账安全网也被解除)。
|
|
1223
|
+
// closed 后补一次撤回:在 inbox 就撤走,确认不在才清标记。
|
|
1224
|
+
if (promptRequestId) {
|
|
1225
|
+
await this.withdrawPendingPromptAfterCancel(sessionId, promptRequestId, deadline);
|
|
1226
|
+
}
|
|
1221
1227
|
getLogger().info(`[dsh] cancelled session ${sessionId}`);
|
|
1222
1228
|
return { status: 'cancelled', sessionId };
|
|
1223
1229
|
}
|
|
@@ -1233,8 +1239,9 @@ export class DshRunner {
|
|
|
1233
1239
|
const retryState = await this.waitForClosed(sessionId, deadline);
|
|
1234
1240
|
releaseGate();
|
|
1235
1241
|
if (retryState === 'closed') {
|
|
1236
|
-
if (promptRequestId)
|
|
1237
|
-
this.
|
|
1242
|
+
if (promptRequestId) {
|
|
1243
|
+
await this.withdrawPendingPromptAfterCancel(sessionId, promptRequestId, deadline);
|
|
1244
|
+
}
|
|
1238
1245
|
return { status: 'cancelled', sessionId };
|
|
1239
1246
|
}
|
|
1240
1247
|
getLogger().warn(`[dsh] stop unconfirmed session=${sessionId}: turn still open`);
|
|
@@ -1268,12 +1275,42 @@ export class DshRunner {
|
|
|
1268
1275
|
return undefined;
|
|
1269
1276
|
}
|
|
1270
1277
|
catch (err) {
|
|
1278
|
+
// 单次探测错误只消耗一次尝试(不提前放弃):探测失败就 return 会把
|
|
1279
|
+
// 仍在 inbox 的 prompt 交给 cancel 分支处理——cancel 是 keepInbox,
|
|
1280
|
+
// prompt 会留到下一个无人订阅的 turn 被隐形执行。
|
|
1271
1281
|
getLogger().warn(`[dsh] stop inbox probe failed session=${sessionId}: ${err.message}`);
|
|
1272
|
-
|
|
1282
|
+
continue;
|
|
1273
1283
|
}
|
|
1274
1284
|
}
|
|
1275
1285
|
return undefined;
|
|
1276
1286
|
}
|
|
1287
|
+
/**
|
|
1288
|
+
* cancel 确认 closed 后的收尾:cancel 是 keepInbox,prompt 可能仍在 inbox。
|
|
1289
|
+
* 在则撤回(防被下一个 turn 隐形认领),确认不在(已被本 turn 消费)或
|
|
1290
|
+
* 撤回成功才清 inflight 标记;探测/撤回失败保留标记,交给下次启动的
|
|
1291
|
+
* reconcile(findPendingPrompt 落空即自然清除,无副作用)。
|
|
1292
|
+
*/
|
|
1293
|
+
async withdrawPendingPromptAfterCancel(sessionId, promptRequestId, deadline) {
|
|
1294
|
+
try {
|
|
1295
|
+
const itemId = await this.client.findPendingPrompt(sessionId, promptRequestId, {
|
|
1296
|
+
timeoutMs: this.stopProbeTimeout(deadline),
|
|
1297
|
+
});
|
|
1298
|
+
if (itemId) {
|
|
1299
|
+
const removed = await this.client.removeQueuedPrompt(sessionId, itemId, {
|
|
1300
|
+
timeoutMs: this.stopProbeTimeout(deadline),
|
|
1301
|
+
});
|
|
1302
|
+
if (removed) {
|
|
1303
|
+
getLogger().info(`[dsh] stop withdrew inbox prompt left behind by cancel session=${sessionId}`);
|
|
1304
|
+
this.inflight?.clear(sessionId, promptRequestId);
|
|
1305
|
+
}
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
this.inflight?.clear(sessionId, promptRequestId);
|
|
1309
|
+
}
|
|
1310
|
+
catch (err) {
|
|
1311
|
+
getLogger().warn(`[dsh] post-cancel withdraw probe failed session=${sessionId}: ${err.message}`);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1277
1314
|
async removePendingPrompt(sessionId, itemId, deadline) {
|
|
1278
1315
|
try {
|
|
1279
1316
|
return await this.client.removeQueuedPrompt(sessionId, itemId, {
|
|
@@ -25,13 +25,6 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import type { AgentEvent, ResultEvent } from '../types.js';
|
|
27
27
|
import type { DshAssistantStreamFrame, DshSessionEvent, DshTokenUsage } from './types.js';
|
|
28
|
-
/** 本 turn 各采样用量之和(input 是非缓存输入,与 ccusage/ResultEvent 口径一致)。 */
|
|
29
|
-
interface UsageTotals {
|
|
30
|
-
input: number;
|
|
31
|
-
output: number;
|
|
32
|
-
cacheRead: number;
|
|
33
|
-
cacheWrite: number;
|
|
34
|
-
}
|
|
35
28
|
export declare class DshTranslator {
|
|
36
29
|
/** 本 turn 累计用量:DSH 每次模型调用(step)一条 usage,工具往返 = 多条。 */
|
|
37
30
|
private turnUsage;
|
|
@@ -63,6 +56,3 @@ export declare class DshTranslator {
|
|
|
63
56
|
}
|
|
64
57
|
/** Map DSH TokenUsage → ResultEvent.usage (cacheWrite = cache creation). */
|
|
65
58
|
export declare function mapUsage(u: DshTokenUsage): NonNullable<ResultEvent['usage']>;
|
|
66
|
-
/** Map a turn's summed usage → ResultEvent.usage (cacheWrite = cache creation). */
|
|
67
|
-
export declare function mapAggregatedUsage(t: UsageTotals): NonNullable<ResultEvent['usage']>;
|
|
68
|
-
export {};
|
|
@@ -265,7 +265,7 @@ export function mapUsage(u) {
|
|
|
265
265
|
});
|
|
266
266
|
}
|
|
267
267
|
/** Map a turn's summed usage → ResultEvent.usage (cacheWrite = cache creation). */
|
|
268
|
-
|
|
268
|
+
function mapAggregatedUsage(t) {
|
|
269
269
|
return {
|
|
270
270
|
input_tokens: t.input,
|
|
271
271
|
output_tokens: t.output,
|
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, NoticeEvent, SessionInfoEvent, ToolHint, ApprovalView, ApprovalRequestedEvent, ApprovalResolvedEvent, GoalView, GoalStateEvent, } from './types.js';
|
|
18
|
+
export type { ResultEvent, PlanEvent, FileChangeEvent, AgentEvent, Runner, RunnerLifetime, AgentKind, AgentSession, AgentSessionContentEvent, AgentSessionUsage, SessionContent, SessionSummary, AgentSessionReader, AgentRunner, TurnStartedEvent, TurnDiffEvent, FinalAnswerEvent, NoticeEvent, SessionInfoEvent, ToolHint, ApprovalView, ApprovalRequestedEvent, ApprovalResolvedEvent, GoalView, GoalStateEvent, } from './types.js';
|
|
19
19
|
export { resolveAgentChoices } from './resolve-agent-choices.js';
|
|
20
20
|
export { syncAgentChoices } from './sync-agent-choices.js';
|
|
@@ -144,6 +144,11 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
144
144
|
*/
|
|
145
145
|
compactBaselineRecords = [];
|
|
146
146
|
/** 存活 terminal(kimi 把 Bash 执行下放客户端):terminalId → 本地句柄。 */
|
|
147
|
+
// 已知限制(待验证 kimi 0.4x 契约):句柄只在
|
|
148
|
+
// terminal/release 与连接关闭(cleanupTerminals)时删除。若服务端只 kill
|
|
149
|
+
// 不 release(中断 turn 的场景),已退出进程的句柄(含 ≤4MB output 缓冲)
|
|
150
|
+
// 会滞留到连接释放。kimi 契约上工具结束会发 release(mock 测试同驱动);
|
|
151
|
+
// 若真机确认存在只 kill 不 release 的路径,再补 exit 后延迟回收。
|
|
147
152
|
terminals = new Map();
|
|
148
153
|
/** terminalId 自增序号(term-1, term-2, …)。 */
|
|
149
154
|
terminalSeq = 0;
|
|
@@ -422,6 +427,11 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
422
427
|
while (true) {
|
|
423
428
|
if (this.forceFinish || this.stopRequested)
|
|
424
429
|
return 'stopped';
|
|
430
|
+
// turn 已死亡(failTurn,如连接关闭)时不再轮询:forceFinish 与
|
|
431
|
+
// stopRequested 均为 false 的死亡路径会让循环空转到 10 分钟沉默超时,
|
|
432
|
+
// 结果还被 isCurrent() 丢弃——纯浪费的每秒文件读。
|
|
433
|
+
if (!this._isRunning)
|
|
434
|
+
return 'unknown';
|
|
425
435
|
const records = reader.call(this.sessionReader, sessionId, opts.cwd);
|
|
426
436
|
if (records.length !== lastTotalCount) {
|
|
427
437
|
lastTotalCount = records.length;
|
|
@@ -506,8 +516,15 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
506
516
|
// (error = "rejected"), otherwise the server hangs waiting for a
|
|
507
517
|
// response. For question elicitation (§5.4), auto-respond cancelled.
|
|
508
518
|
const rpParams = params;
|
|
509
|
-
|
|
510
|
-
|
|
519
|
+
// elicitation/create 的表单属性全不可解析时(translator 返回 [])同样回
|
|
520
|
+
// cancelled 而不是 METHOD_NOT_FOUND:§5.4 的不变量是「识别到非工具审批
|
|
521
|
+
// 形态 → 自动回应 cancelled,防止服务端挂起」;错误响应虽也能解悬挂,
|
|
522
|
+
// 但 kimi 侧可能把 JSON-RPC error 当工具级失败让整个 turn 报错,而不是
|
|
523
|
+
// 跳过提问继续。
|
|
524
|
+
const isQuestionElicitation = method === ServerRequestMethod.REQUEST_PERMISSION &&
|
|
525
|
+
(rpParams.isQuestion || !rpParams.toolCall);
|
|
526
|
+
const isUnparseableElicitationForm = method === ServerRequestMethod.ELICITATION_CREATE;
|
|
527
|
+
if (isQuestionElicitation || isUnparseableElicitationForm) {
|
|
511
528
|
this.currentClient?.respond(id, { outcome: { outcome: 'cancelled' } });
|
|
512
529
|
getLogger().info(`[${this.logTag}] auto-responded cancelled to question elicitation requestId=${id}`);
|
|
513
530
|
}
|
|
@@ -633,8 +650,27 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
633
650
|
proc.once('exit', (code, signal) => {
|
|
634
651
|
handle.exitCode = code;
|
|
635
652
|
handle.signal = signal;
|
|
636
|
-
|
|
637
|
-
|
|
653
|
+
// 'exit' 可先于 stdout 缓冲派发:立刻 emit 会按旧快照推送 tool_result,
|
|
654
|
+
// 卡片面板丢尾部输出(terminal/output 反向 RPC 读到的是补齐后的全量,
|
|
655
|
+
// 只影响卡片)。等流 end 或 200ms 宽限——不等 'close':它要等所有共享
|
|
656
|
+
// 管道的子孙进程退出,`nohup &` 类后台任务会把它拖到永远。
|
|
657
|
+
//
|
|
658
|
+
// wait_for_exit 必须在 tool_result 入队之后才 resolve:服务端拿到响应
|
|
659
|
+
// 后会立刻 release + end_turn,先 resolve 会让回合结果早于延迟推送的
|
|
660
|
+
// tool_result 到达,Bash 输出被丢弃(卡片面板空输出)。
|
|
661
|
+
const flushThenEmit = () => {
|
|
662
|
+
clearTimeout(emitTimer);
|
|
663
|
+
emitResult();
|
|
664
|
+
resolveExit?.();
|
|
665
|
+
};
|
|
666
|
+
const emitTimer = setTimeout(flushThenEmit, 200);
|
|
667
|
+
emitTimer.unref?.();
|
|
668
|
+
if (proc.stdout && !proc.stdout.destroyed) {
|
|
669
|
+
proc.stdout.once('end', flushThenEmit);
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
flushThenEmit();
|
|
673
|
+
}
|
|
638
674
|
});
|
|
639
675
|
proc.once('error', (err) => {
|
|
640
676
|
getLogger().warn(`[${this.logTag}] terminal process error: ${err.message}`);
|
|
@@ -642,8 +678,9 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
642
678
|
handle.exitCode = 1;
|
|
643
679
|
handle.signal = null;
|
|
644
680
|
}
|
|
645
|
-
|
|
681
|
+
// 同 exit:tool_result 先入队,再放行 wait_for_exit。
|
|
646
682
|
emitResult();
|
|
683
|
+
resolveExit?.();
|
|
647
684
|
});
|
|
648
685
|
const terminalId = `term-${++this.terminalSeq}`;
|
|
649
686
|
this.terminals.set(terminalId, handle);
|
|
@@ -29,6 +29,14 @@ import { type RequestPermissionParams, type AgentMessageChunkEvent, type AgentTh
|
|
|
29
29
|
import { BaseAcpTranslator, type AcpTranslatorEvent } from '../../common/acp/base-translator.js';
|
|
30
30
|
export declare class KimiAcpTranslator extends BaseAcpTranslator {
|
|
31
31
|
protected readonly logTag = "kimi-acp-translator";
|
|
32
|
+
/**
|
|
33
|
+
* 非终态 tool_call_update 不附带空 tool_result:kimi 非 Bash 工具的
|
|
34
|
+
* in-progress 只带流式 args(rawOutput 缺省 → content ''),空 tool_result
|
|
35
|
+
* 会让 reducer 把运行中的工具提前置 ✅ + 空输出(终态到达后自愈,但终态
|
|
36
|
+
* 丢失时面板停在错误的「已完成」)。opencode 走基类默认 true(其
|
|
37
|
+
* in-progress 带输出快照,seam 契约钉死要发)。
|
|
38
|
+
*/
|
|
39
|
+
protected emitToolResultForNonTerminalUpdate(content: unknown): boolean;
|
|
32
40
|
/** Accumulated text per tool-call or message (keyed by a logical item id). */
|
|
33
41
|
private textByItem;
|
|
34
42
|
/** Accumulated thinking per item. */
|
|
@@ -42,6 +42,16 @@ const THINKING_ITEM_ID = 'thinking';
|
|
|
42
42
|
// =============================================================================
|
|
43
43
|
export class KimiAcpTranslator extends BaseAcpTranslator {
|
|
44
44
|
logTag = 'kimi-acp-translator';
|
|
45
|
+
/**
|
|
46
|
+
* 非终态 tool_call_update 不附带空 tool_result:kimi 非 Bash 工具的
|
|
47
|
+
* in-progress 只带流式 args(rawOutput 缺省 → content ''),空 tool_result
|
|
48
|
+
* 会让 reducer 把运行中的工具提前置 ✅ + 空输出(终态到达后自愈,但终态
|
|
49
|
+
* 丢失时面板停在错误的「已完成」)。opencode 走基类默认 true(其
|
|
50
|
+
* in-progress 带输出快照,seam 契约钉死要发)。
|
|
51
|
+
*/
|
|
52
|
+
emitToolResultForNonTerminalUpdate(content) {
|
|
53
|
+
return !(content === undefined || content === null || content === '');
|
|
54
|
+
}
|
|
45
55
|
/** Accumulated text per tool-call or message (keyed by a logical item id). */
|
|
46
56
|
textByItem = new Map();
|
|
47
57
|
/** Accumulated thinking per item. */
|
|
@@ -149,18 +159,7 @@ export class KimiAcpTranslator extends BaseAcpTranslator {
|
|
|
149
159
|
// 才提供「本会话总是允许」(acceptForSession);否则与旧行为一致。
|
|
150
160
|
availableDecisions: deriveAcpAvailableDecisions(params.options ?? []),
|
|
151
161
|
};
|
|
152
|
-
return
|
|
153
|
-
{
|
|
154
|
-
type: 'approval_requested',
|
|
155
|
-
requestId,
|
|
156
|
-
kind: 'command',
|
|
157
|
-
threadId: params.sessionId,
|
|
158
|
-
turnId: '',
|
|
159
|
-
itemId: '',
|
|
160
|
-
view,
|
|
161
|
-
timestamp: new Date().toISOString(),
|
|
162
|
-
},
|
|
163
|
-
];
|
|
162
|
+
return this.approvalRequestedEvents(requestId, params.sessionId, view);
|
|
164
163
|
}
|
|
165
164
|
handleOtherServerRequest(requestId, method, params) {
|
|
166
165
|
if (method !== ServerRequestMethod.ELICITATION_CREATE) {
|
|
@@ -189,18 +189,7 @@ export class OpencodeAcpTranslator extends BaseAcpTranslator {
|
|
|
189
189
|
// 才提供「本会话总是允许」(acceptForSession)。
|
|
190
190
|
availableDecisions: deriveAcpAvailableDecisions(params.options ?? []),
|
|
191
191
|
};
|
|
192
|
-
return
|
|
193
|
-
{
|
|
194
|
-
type: 'approval_requested',
|
|
195
|
-
requestId,
|
|
196
|
-
kind: 'command',
|
|
197
|
-
threadId: params.sessionId,
|
|
198
|
-
turnId: '',
|
|
199
|
-
itemId: '',
|
|
200
|
-
view,
|
|
201
|
-
timestamp: new Date().toISOString(),
|
|
202
|
-
},
|
|
203
|
-
];
|
|
192
|
+
return this.approvalRequestedEvents(requestId, params.sessionId, view);
|
|
204
193
|
}
|
|
205
194
|
}
|
|
206
195
|
// =============================================================================
|
|
@@ -113,9 +113,24 @@ export class PiRpcRunner extends ConnectionBasedRunner {
|
|
|
113
113
|
const client = await this.manager.acquire(opts.cwd, { sessionId: opts.sessionId });
|
|
114
114
|
this.manager.notifyActivity(opts.cwd);
|
|
115
115
|
this.currentClient = client;
|
|
116
|
+
// 轮次代际锚点(与 BaseAcpRunner 同口径):pi 的 shouldReuse 要求
|
|
117
|
+
// sessionId 匹配,/new、/resume 切换后的首条消息会让 acquire **同步
|
|
118
|
+
// dispose** 旧连接;terminator 等到进程退出后,旧 client 上残留的上一轮
|
|
119
|
+
// onClose 闭包会把假错误推进新 turn 的共享队列(用户消息未发出即丢失)。
|
|
120
|
+
// 旧轮迟到的 agent_settled 同理(新 turn 空转立即"成功")。所有 hook
|
|
121
|
+
// 落地前必须过 isCurrentTurn。
|
|
122
|
+
const generation = this.turnGeneration;
|
|
116
123
|
client.setHooks({
|
|
117
|
-
onEvent: (evt) =>
|
|
118
|
-
|
|
124
|
+
onEvent: (evt) => {
|
|
125
|
+
if (!this.isCurrentTurn(generation, client))
|
|
126
|
+
return;
|
|
127
|
+
this.handleEvent(evt);
|
|
128
|
+
},
|
|
129
|
+
onClose: () => {
|
|
130
|
+
if (!this.isCurrentTurn(generation, client))
|
|
131
|
+
return;
|
|
132
|
+
this.failTurn('Pi RPC connection closed');
|
|
133
|
+
},
|
|
119
134
|
});
|
|
120
135
|
let sessionId = opts.sessionId;
|
|
121
136
|
if (!sessionId) {
|
|
@@ -157,9 +172,19 @@ export class PiRpcRunner extends ConnectionBasedRunner {
|
|
|
157
172
|
const client = await this.manager.acquire(opts.cwd, { sessionId: opts.sessionId });
|
|
158
173
|
this.manager.notifyActivity(opts.cwd);
|
|
159
174
|
this.currentClient = client;
|
|
175
|
+
// 同 setupTurn:hook 带轮次代际锚点,旧连接的迟到回调不得进入本轮。
|
|
176
|
+
const generation = this.turnGeneration;
|
|
160
177
|
client.setHooks({
|
|
161
|
-
onEvent: (evt) =>
|
|
162
|
-
|
|
178
|
+
onEvent: (evt) => {
|
|
179
|
+
if (!this.isCurrentTurn(generation, client))
|
|
180
|
+
return;
|
|
181
|
+
this.handleEvent(evt);
|
|
182
|
+
},
|
|
183
|
+
onClose: () => {
|
|
184
|
+
if (!this.isCurrentTurn(generation, client))
|
|
185
|
+
return;
|
|
186
|
+
this.failTurn('Pi RPC connection closed');
|
|
187
|
+
},
|
|
163
188
|
});
|
|
164
189
|
this.activeSessionId = opts.sessionId;
|
|
165
190
|
const translator = new PiRpcTranslator();
|
|
@@ -170,7 +195,10 @@ export class PiRpcRunner extends ConnectionBasedRunner {
|
|
|
170
195
|
this.currentTurnId = turnId;
|
|
171
196
|
this.pushEvents([translator.produceTurnStarted(opts.sessionId, turnId)]);
|
|
172
197
|
this.promptSettled = false;
|
|
173
|
-
|
|
198
|
+
// compact 是完整的 LLM 请求(同步跑完才回响应,协议文档自述),大上下文
|
|
199
|
+
// 压缩轻松超过默认 60s 请求超时——超时只会出假错误卡,压缩仍在后台完成。
|
|
200
|
+
// 活性交给 turn idle 看门狗与 /stop(与 ACP 侧 turn 级请求同口径)。
|
|
201
|
+
const response = await client.request({ type: 'compact' }, Number.POSITIVE_INFINITY);
|
|
174
202
|
this.promptSettled = true;
|
|
175
203
|
// Guard: a stop may have already ended this turn (deferred-stop during
|
|
176
204
|
// compaction) — don't push a stale result into a (possibly reused) queue.
|
|
@@ -10,15 +10,9 @@
|
|
|
10
10
|
* `result` from that response.
|
|
11
11
|
* - `agent_settled` is ignored for compaction turns (no double result).
|
|
12
12
|
*/
|
|
13
|
-
import type { AgentEvent, ResultEvent } from '../../types.js';
|
|
13
|
+
import type { AgentEvent, ResultEvent, TurnStartedEvent } from '../../types.js';
|
|
14
14
|
import { type PiRpcEvent, type PiRpcResponse } from './protocol-types.js';
|
|
15
|
-
export
|
|
16
|
-
type: 'turn_started';
|
|
17
|
-
threadId: string;
|
|
18
|
-
turnId: string;
|
|
19
|
-
operationKind: 'turn' | 'compaction';
|
|
20
|
-
timestamp?: string;
|
|
21
|
-
}
|
|
15
|
+
export type PiRpcTurnStartedEvent = TurnStartedEvent;
|
|
22
16
|
export type PiRpcTranslatorEvent = AgentEvent | PiRpcTurnStartedEvent;
|
|
23
17
|
export declare class PiRpcTranslator {
|
|
24
18
|
private content;
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* `result` from that response.
|
|
11
11
|
* - `agent_settled` is ignored for compaction turns (no double result).
|
|
12
12
|
*/
|
|
13
|
+
import { makeTurnStartedEvent } from '../../common/turn-started.js';
|
|
13
14
|
export class PiRpcTranslator {
|
|
14
15
|
content = [];
|
|
15
16
|
currentContentIndex = -1;
|
|
@@ -135,13 +136,7 @@ export class PiRpcTranslator {
|
|
|
135
136
|
/** Self-produced turn_started (no wire equivalent). */
|
|
136
137
|
produceTurnStarted(sessionId, turnId) {
|
|
137
138
|
this.sessionId = sessionId;
|
|
138
|
-
return
|
|
139
|
-
type: 'turn_started',
|
|
140
|
-
threadId: sessionId,
|
|
141
|
-
turnId,
|
|
142
|
-
operationKind: this.operationKind === 'compact' ? 'compaction' : 'turn',
|
|
143
|
-
timestamp: new Date().toISOString(),
|
|
144
|
-
};
|
|
139
|
+
return makeTurnStartedEvent(sessionId, turnId, this.operationKind);
|
|
145
140
|
}
|
|
146
141
|
/** Result for a completed normal turn (agent_settled). */
|
|
147
142
|
produceResultFromSettled() {
|
|
@@ -14,28 +14,9 @@ import type { AgentKind, Runner } from './index.js';
|
|
|
14
14
|
*/
|
|
15
15
|
export declare class AgentRegistry {
|
|
16
16
|
private readonly factories;
|
|
17
|
-
private readonly displayNames;
|
|
18
17
|
/** Container for runtime config updates (pi provider dynamic reload). */
|
|
19
18
|
private configContainer?;
|
|
20
19
|
register(kind: AgentKind, factory: (cwd: string) => Runner): void;
|
|
21
|
-
/**
|
|
22
|
-
* 注册 agent 的显示名。应在 register(kind, factory) 后调用。
|
|
23
|
-
* 用于 agentDisplayName 等全局查询,实现单点真相。
|
|
24
|
-
*/
|
|
25
|
-
registerDisplayName(kind: AgentKind, displayName: string): void;
|
|
26
|
-
/**
|
|
27
|
-
* 获取 agent 显示名。未注册时返回原始 kind。
|
|
28
|
-
*/
|
|
29
|
-
getDisplayName(kind: AgentKind): string;
|
|
30
|
-
/**
|
|
31
|
-
* 设置全局 registry 实例,供 agentDisplayName 等全局函数使用。
|
|
32
|
-
* 在 index.ts 初始化所有 agents 后调用。
|
|
33
|
-
*/
|
|
34
|
-
static setGlobalInstance(reg: AgentRegistry | undefined): void;
|
|
35
|
-
/**
|
|
36
|
-
* 获取全局 registry 实例。
|
|
37
|
-
*/
|
|
38
|
-
static getGlobalInstance(): AgentRegistry | undefined;
|
|
39
20
|
/**
|
|
40
21
|
* Set config container reference. Call after bridge config is updated.
|
|
41
22
|
* Factory functions can read latest config from here.
|
package/dist/runner/registry.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 单例 registry 实例,供 agentDisplayName 等全局查询使用。
|
|
3
|
-
* 在 index.ts 初始化后设置。
|
|
4
|
-
*/
|
|
5
|
-
let globalRegistry;
|
|
6
1
|
/**
|
|
7
2
|
* Registry of agent factories, keyed by `AgentKind`. `index.ts` registers one
|
|
8
3
|
* factory per available agent at startup; `Bridge.getRunner(cwd)` looks
|
|
@@ -18,38 +13,11 @@ let globalRegistry;
|
|
|
18
13
|
*/
|
|
19
14
|
export class AgentRegistry {
|
|
20
15
|
factories = new Map();
|
|
21
|
-
displayNames = new Map();
|
|
22
16
|
/** Container for runtime config updates (pi provider dynamic reload). */
|
|
23
17
|
configContainer;
|
|
24
18
|
register(kind, factory) {
|
|
25
19
|
this.factories.set(kind, factory);
|
|
26
20
|
}
|
|
27
|
-
/**
|
|
28
|
-
* 注册 agent 的显示名。应在 register(kind, factory) 后调用。
|
|
29
|
-
* 用于 agentDisplayName 等全局查询,实现单点真相。
|
|
30
|
-
*/
|
|
31
|
-
registerDisplayName(kind, displayName) {
|
|
32
|
-
this.displayNames.set(kind, displayName);
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* 获取 agent 显示名。未注册时返回原始 kind。
|
|
36
|
-
*/
|
|
37
|
-
getDisplayName(kind) {
|
|
38
|
-
return this.displayNames.get(kind) ?? kind;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* 设置全局 registry 实例,供 agentDisplayName 等全局函数使用。
|
|
42
|
-
* 在 index.ts 初始化所有 agents 后调用。
|
|
43
|
-
*/
|
|
44
|
-
static setGlobalInstance(reg) {
|
|
45
|
-
globalRegistry = reg;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* 获取全局 registry 实例。
|
|
49
|
-
*/
|
|
50
|
-
static getGlobalInstance() {
|
|
51
|
-
return globalRegistry;
|
|
52
|
-
}
|
|
53
21
|
/**
|
|
54
22
|
* Set config container reference. Call after bridge config is updated.
|
|
55
23
|
* Factory functions can read latest config from here.
|
|
@@ -25,7 +25,11 @@ export function resolveAgentChoices(config) {
|
|
|
25
25
|
const source = agentChoices;
|
|
26
26
|
for (const { configKey, choicesKey } of fields) {
|
|
27
27
|
const value = source[choicesKey];
|
|
28
|
-
|
|
28
|
+
// 只填「配置里根本没写」的字段(undefined):显式清空(config.yaml 写了
|
|
29
|
+
// `model: ""`)是用户意志,旧真值判断 `!target[configKey]` 会把空串当
|
|
30
|
+
// 「未配置」,用残留 choices 把清空值填回去——重启后永远回不到默认。
|
|
31
|
+
// value 为空串同样不填(choices 里记录的「跟随默认」)。
|
|
32
|
+
if (target[configKey] === undefined && value) {
|
|
29
33
|
target[configKey] = value;
|
|
30
34
|
}
|
|
31
35
|
}
|
|
@@ -14,7 +14,10 @@ export function syncAgentChoices(config, agent) {
|
|
|
14
14
|
const source = agentCfg;
|
|
15
15
|
for (const { configKey, choicesKey } of fields) {
|
|
16
16
|
const value = source[configKey];
|
|
17
|
-
|
|
17
|
+
// 显式空串也要同步(用户清空 = 回到默认):旧真值判断不同步空串,
|
|
18
|
+
// agentChoices 里残留上一次的选择——/config 卡片显示的与实际生效的
|
|
19
|
+
// 配置从此分叉,重启后 resolve 还会把残留填回 config。
|
|
20
|
+
if (value !== undefined)
|
|
18
21
|
agentChoices[choicesKey] = value;
|
|
19
22
|
}
|
|
20
23
|
return updated;
|