lark-remote 0.1.12 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/README.en.md +2 -6
- package/README.md +3 -7
- package/dist/bridge/final-usage.d.ts +24 -0
- package/dist/bridge/final-usage.js +55 -0
- package/dist/bridge/inbound-media.d.ts +0 -3
- package/dist/bridge/inbound-media.js +0 -1
- package/dist/bridge/index.d.ts +7 -7
- package/dist/bridge/index.js +100 -133
- package/dist/bridge/queue-manager.js +4 -3
- package/dist/card/approval-render.js +39 -94
- package/dist/card/bash-renderer.js +12 -21
- package/dist/card/card-shared.d.ts +14 -5
- package/dist/card/card-shared.js +23 -34
- package/dist/card/run-renderer.d.ts +0 -22
- package/dist/card/run-renderer.js +206 -153
- package/dist/card/run-state.d.ts +29 -1
- package/dist/card/run-state.js +172 -84
- package/dist/card/text-truncate.d.ts +0 -9
- package/dist/card/text-truncate.js +0 -9
- package/dist/card/tool-render.d.ts +4 -1
- package/dist/card/tool-render.js +76 -32
- package/dist/cli.js +2 -2
- package/dist/common/fs.d.ts +14 -0
- package/dist/common/fs.js +57 -3
- package/dist/common/truncate.d.ts +0 -6
- package/dist/common/truncate.js +1 -1
- package/dist/common/ttl-cache.d.ts +24 -0
- package/dist/common/ttl-cache.js +53 -0
- package/dist/config/codex-config.js +14 -11
- package/dist/config/index.d.ts +0 -8
- package/dist/config/index.js +0 -16
- package/dist/config/kimi-config.d.ts +1 -1
- package/dist/config/kimi-config.js +2 -1
- package/dist/config/opencode-config.d.ts +1 -1
- package/dist/config/opencode-config.js +16 -10
- package/dist/config/qr.d.ts +36 -0
- package/dist/config/qr.js +124 -0
- package/dist/config/wizard.js +69 -27
- package/dist/connector/index.d.ts +3 -2
- package/dist/connector/index.js +5 -3
- package/dist/index.js +14 -41
- package/dist/instance-lock.js +7 -0
- package/dist/persistence/atomic-write.d.ts +2 -0
- package/dist/persistence/atomic-write.js +20 -5
- package/dist/persistence/load-json-file.d.ts +1 -2
- package/dist/persistence/load-json-file.js +2 -3
- package/dist/platform/agent-stopper.d.ts +22 -0
- package/dist/platform/agent-stopper.js +19 -0
- package/dist/platform/command.d.ts +11 -0
- package/dist/platform/command.js +107 -0
- package/dist/platform/identity.d.ts +33 -0
- package/dist/platform/identity.js +187 -0
- package/dist/platform/path.d.ts +24 -0
- package/dist/platform/path.js +110 -0
- package/dist/platform/select.d.ts +9 -0
- package/dist/platform/select.js +12 -0
- package/dist/platform/shell.d.ts +51 -0
- package/dist/platform/shell.js +75 -0
- package/dist/platform/spawn.d.ts +41 -0
- package/dist/platform/spawn.js +77 -0
- package/dist/platform/terminator-posix.d.ts +38 -0
- package/dist/platform/terminator-posix.js +103 -0
- package/dist/platform/terminator-win32.d.ts +21 -0
- package/dist/platform/terminator-win32.js +110 -0
- package/dist/platform/terminator.d.ts +44 -0
- package/dist/platform/terminator.js +24 -0
- package/dist/platform/types.d.ts +23 -0
- package/dist/platform/types.js +5 -0
- package/dist/restart.js +4 -2
- package/dist/router/card-helpers.d.ts +33 -6
- package/dist/router/card-helpers.js +67 -11
- package/dist/router/config/common/render.d.ts +1 -1
- package/dist/router/config/common/render.js +1 -1
- package/dist/router/config-diff.d.ts +4 -0
- package/dist/router/config-diff.js +70 -0
- package/dist/router/index.d.ts +73 -6
- package/dist/router/index.js +663 -590
- package/dist/router/utils.d.ts +6 -0
- package/dist/router/utils.js +12 -0
- package/dist/runner/bash/runner.d.ts +9 -2
- package/dist/runner/bash/runner.js +45 -20
- package/dist/runner/claude/session.d.ts +4 -4
- package/dist/runner/claude/session.js +18 -9
- package/dist/runner/codex/app-server/protocol-types.d.ts +35 -1
- package/dist/runner/codex/app-server/runner.d.ts +1 -16
- package/dist/runner/codex/app-server/runner.js +2 -2
- package/dist/runner/codex/app-server/translator.d.ts +18 -0
- package/dist/runner/codex/app-server/translator.js +146 -0
- package/dist/runner/common/acp/base-acp-runner.d.ts +116 -0
- package/dist/runner/common/acp/base-acp-runner.js +248 -0
- package/dist/runner/common/acp/base-translator.d.ts +141 -0
- package/dist/runner/common/acp/base-translator.js +271 -0
- package/dist/runner/common/acp/protocol-helpers.d.ts +70 -0
- package/dist/runner/common/acp/protocol-helpers.js +82 -0
- package/dist/runner/common/connection-based-runner.d.ts +0 -2
- package/dist/runner/common/connection-based-runner.js +1 -1
- package/dist/runner/common/jsonrpc/transport.d.ts +2 -0
- package/dist/runner/common/jsonrpc/transport.js +25 -5
- package/dist/runner/common/process-stopper.d.ts +4 -26
- package/dist/runner/common/process-stopper.js +4 -76
- package/dist/runner/common/runner-utils.d.ts +0 -5
- package/dist/runner/common/runner-utils.js +0 -7
- package/dist/runner/common/spawning-runner.d.ts +42 -132
- package/dist/runner/common/spawning-runner.js +52 -271
- package/dist/runner/common/usage.d.ts +2 -0
- package/dist/runner/common/usage.js +2 -0
- package/dist/runner/dsh/index.d.ts +0 -2
- package/dist/runner/dsh/index.js +0 -2
- package/dist/runner/dsh/runner.d.ts +0 -2
- package/dist/runner/dsh/runner.js +1 -1
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +63 -53
- package/dist/runner/kimi/acp/runner.js +189 -334
- package/dist/runner/kimi/acp/translator.d.ts +18 -101
- package/dist/runner/kimi/acp/translator.js +61 -255
- package/dist/runner/opencode/acp/runner.d.ts +22 -46
- package/dist/runner/opencode/acp/runner.js +74 -297
- package/dist/runner/opencode/acp/translator.d.ts +28 -104
- package/dist/runner/opencode/acp/translator.js +54 -249
- package/dist/runner/pi/rpc/index.d.ts +0 -2
- package/dist/runner/pi/rpc/index.js +0 -2
- package/dist/runner/pi/rpc/translator.d.ts +3 -0
- package/dist/runner/pi/rpc/translator.js +47 -9
- package/dist/runner/probe.d.ts +8 -11
- package/dist/runner/probe.js +17 -21
- package/dist/runner/registry.d.ts +4 -4
- package/dist/runner/registry.js +4 -4
- package/dist/runner/resolve-agent-choices.js +6 -2
- package/dist/runner/sync-agent-choices.js +1 -1
- package/dist/runner/types.d.ts +38 -1
- package/dist/session/claude/session-index.js +3 -1
- package/dist/session/claude/sessions.js +37 -64
- package/dist/session/codex/rollout-reader.d.ts +10 -14
- package/dist/session/codex/rollout-reader.js +126 -48
- package/dist/session/common/constants.d.ts +7 -0
- package/dist/session/common/constants.js +9 -0
- package/dist/session/common/pagination.d.ts +0 -2
- package/dist/session/common/pagination.js +1 -1
- package/dist/session/common/recency.d.ts +7 -0
- package/dist/session/common/recency.js +10 -0
- package/dist/session/common/two-pass.d.ts +22 -0
- package/dist/session/common/two-pass.js +70 -0
- package/dist/session/common/usage-accumulator.d.ts +14 -0
- package/dist/session/common/usage-accumulator.js +15 -0
- package/dist/session/dsh/sessions.js +15 -11
- package/dist/session/kimi/sessions.d.ts +24 -4
- package/dist/session/kimi/sessions.js +99 -103
- package/dist/session/opencode/sessions.d.ts +2 -14
- package/dist/session/opencode/sessions.js +35 -15
- package/dist/session/pi/sessions.js +64 -95
- package/dist/update/index.d.ts +2 -3
- package/dist/update/index.js +1 -2
- package/dist/update/install.d.ts +2 -1
- package/dist/update/install.js +58 -9
- package/dist/update/version-check.d.ts +5 -0
- package/dist/update/version-check.js +9 -0
- package/package.json +9 -7
- package/dist/update/startup-hint.d.ts +0 -5
- package/dist/update/startup-hint.js +0 -11
|
@@ -2,148 +2,40 @@
|
|
|
2
2
|
* OpencodeAcpTranslator: translates opencode ACP session/update notifications
|
|
3
3
|
* and session/request_permission server requests into AgentEvents.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
5
|
+
* Extends BaseAcpTranslator (shared envelope dispatch, prompt-response
|
|
6
|
+
* stopReason mapping, tool_call normalization, usage occupancy). opencode's
|
|
7
|
+
* divergence from kimi is the content channel: wire `content.text` IS an
|
|
8
|
+
* incremental delta (opencode acp/event.ts:231-258 handlePartDelta sends
|
|
9
|
+
* `props.delta` verbatim), so text/thinking chunks go straight onto the
|
|
10
|
+
* `assistant` incremental channel — NO accumulation, NO turn_diff snapshot.
|
|
11
|
+
* (Contrast kimi: same field is also a delta but kimi's contract accumulates
|
|
12
|
+
* into turn_diff snapshots; both end with exactly one concatenation in
|
|
13
|
+
* run-state, enforced by each side's seam test.)
|
|
13
14
|
*
|
|
14
15
|
* Event mapping (source: opencode acp/service.ts + event.ts + tool.ts,
|
|
15
16
|
* dev@1c965451b5):
|
|
16
17
|
* agent_message_chunk → assistant/text delta (content.text passthrough)
|
|
17
18
|
* agent_thought_chunk → assistant/thinking delta (content.text passthrough)
|
|
18
|
-
* tool_call
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* rawOutput is an OBJECT: {output} / {error})
|
|
22
|
-
* usage_update → live context occupancy {used, size} + cost →
|
|
23
|
-
* {total_tokens, context_limit, total_cost_usd}
|
|
24
|
-
* available_commands_update / plan / session_info_update /
|
|
25
|
-
* current_mode_update / config_option_update / user_message_chunk → discard
|
|
19
|
+
* tool_call / tool_call_update / usage_update / plan / control-plane noise →
|
|
20
|
+
* base class (BaseAcpTranslator; opencode overrides
|
|
21
|
+
* tool_result content extraction + session cost)
|
|
26
22
|
* prompt stopReason:'end_turn' → result success
|
|
27
23
|
* prompt stopReason:'cancelled' → result interrupted (独立终态)
|
|
28
24
|
* prompt other stopReason/error → result error
|
|
29
25
|
* session/request_permission → approval_requested (optionId echo)
|
|
30
26
|
*/
|
|
31
|
-
import {
|
|
27
|
+
import { deriveAcpAvailableDecisions, truncateWithEllipsis, } from '../../common/acp/protocol-helpers.js';
|
|
32
28
|
import { getLogger } from '../../../logger/index.js';
|
|
29
|
+
import { BaseAcpTranslator } from '../../common/acp/base-translator.js';
|
|
33
30
|
// =============================================================================
|
|
34
31
|
// Translator
|
|
35
32
|
// =============================================================================
|
|
36
|
-
export class OpencodeAcpTranslator {
|
|
37
|
-
|
|
38
|
-
pendingToolCalls = new Map();
|
|
39
|
-
/** Current operation kind (turn vs compact). */
|
|
40
|
-
operationKind = 'turn';
|
|
41
|
-
/** Live usage snapshot from usage_update events (context occupancy only). */
|
|
42
|
-
liveUsage = {};
|
|
43
|
-
/** Session cost from usage_update cost.amount (USD). */
|
|
44
|
-
liveCostUsd;
|
|
45
|
-
/** Whether any usage_update has been seen this turn (no update → no usage). */
|
|
46
|
-
hasLiveUsage = false;
|
|
47
|
-
/** Current turn id, set by produceTurnStarted. */
|
|
48
|
-
currentTurnId = '';
|
|
49
|
-
/**
|
|
50
|
-
* Handle a notification from the ACP server and return translated events.
|
|
51
|
-
*/
|
|
52
|
-
handleNotification(method, params) {
|
|
53
|
-
if (method !== NotificationMethod.SESSION_UPDATE) {
|
|
54
|
-
return [];
|
|
55
|
-
}
|
|
56
|
-
// Envelope: {sessionId, update: {sessionUpdate: ...}} (same as kimi).
|
|
57
|
-
const notif = params;
|
|
58
|
-
const update = notif.update;
|
|
59
|
-
switch (update.sessionUpdate) {
|
|
60
|
-
case SessionEventType.AGENT_MESSAGE_CHUNK:
|
|
61
|
-
return this.handleAgentMessageChunk(update);
|
|
62
|
-
case SessionEventType.AGENT_THOUGHT_CHUNK:
|
|
63
|
-
return this.handleAgentThoughtChunk(update);
|
|
64
|
-
case SessionEventType.TOOL_CALL:
|
|
65
|
-
return this.handleToolCall(update);
|
|
66
|
-
case SessionEventType.TOOL_CALL_UPDATE:
|
|
67
|
-
return this.handleToolCallUpdate(update);
|
|
68
|
-
case SessionEventType.USAGE_UPDATE:
|
|
69
|
-
return this.handleUsageUpdate(update);
|
|
70
|
-
case SessionEventType.PLAN:
|
|
71
|
-
getLogger().debug(`[opencode-acp-translator] discarding plan event`);
|
|
72
|
-
return [];
|
|
73
|
-
default:
|
|
74
|
-
// Control-plane noise (command list, mode/config echoes, user replay)
|
|
75
|
-
// — not content.
|
|
76
|
-
return [];
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Handle a server request (reverse RPC from the ACP server).
|
|
81
|
-
*/
|
|
82
|
-
handleServerRequest(id, method, params) {
|
|
83
|
-
if (method === ServerRequestMethod.REQUEST_PERMISSION) {
|
|
84
|
-
return this.handleRequestPermission(id, params);
|
|
85
|
-
}
|
|
86
|
-
return [];
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Produce a turn_started event. Called by the runner when session/prompt
|
|
90
|
-
* is sent (no wire notification for this — self-produced).
|
|
91
|
-
*/
|
|
92
|
-
produceTurnStarted(sessionId, turnId) {
|
|
93
|
-
this.currentTurnId = turnId;
|
|
94
|
-
return {
|
|
95
|
-
type: 'turn_started',
|
|
96
|
-
threadId: sessionId,
|
|
97
|
-
turnId,
|
|
98
|
-
operationKind: this.operationKind === 'compact' ? 'compaction' : 'turn',
|
|
99
|
-
timestamp: new Date().toISOString(),
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Translate a prompt response into a result event.
|
|
104
|
-
* stopReason: end_turn → success; cancelled → interrupted; anything else
|
|
105
|
-
* (max_tokens / refusal / …) → error.
|
|
106
|
-
*/
|
|
107
|
-
handlePromptResponse(sessionId, result) {
|
|
108
|
-
const subtype = result.stopReason === 'end_turn'
|
|
109
|
-
? 'success'
|
|
110
|
-
: result.stopReason === 'cancelled'
|
|
111
|
-
? 'interrupted'
|
|
112
|
-
: 'error';
|
|
113
|
-
return {
|
|
114
|
-
type: 'result',
|
|
115
|
-
subtype,
|
|
116
|
-
session_id: sessionId,
|
|
117
|
-
...(this.hasLiveUsage ? { usage: this.liveUsageSnapshot() } : {}),
|
|
118
|
-
...(this.liveCostUsd !== undefined ? { total_cost_usd: this.liveCostUsd } : {}),
|
|
119
|
-
...(subtype === 'error'
|
|
120
|
-
? { errorMessage: `Prompt ended with stopReason: ${result.stopReason}` }
|
|
121
|
-
: {}),
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Produce an error result event (for JSON-RPC errors during prompt).
|
|
126
|
-
*/
|
|
127
|
-
produceErrorResult(sessionId, errorMessage) {
|
|
128
|
-
return {
|
|
129
|
-
type: 'result',
|
|
130
|
-
subtype: 'error',
|
|
131
|
-
session_id: sessionId,
|
|
132
|
-
errorMessage,
|
|
133
|
-
...(this.hasLiveUsage ? { usage: this.liveUsageSnapshot() } : {}),
|
|
134
|
-
...(this.liveCostUsd !== undefined ? { total_cost_usd: this.liveCostUsd } : {}),
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Set the operation kind (turn vs compact).
|
|
139
|
-
*/
|
|
140
|
-
setOperationKind(kind) {
|
|
141
|
-
this.operationKind = kind;
|
|
142
|
-
}
|
|
33
|
+
export class OpencodeAcpTranslator extends BaseAcpTranslator {
|
|
34
|
+
logTag = 'opencode-acp-translator';
|
|
143
35
|
// =========================================================================
|
|
144
|
-
//
|
|
36
|
+
// Content channel: wire deltas → assistant incremental channel
|
|
145
37
|
// =========================================================================
|
|
146
|
-
handleAgentMessageChunk(event) {
|
|
38
|
+
handleAgentMessageChunk(event, _sessionId) {
|
|
147
39
|
// opencode wire delta → assistant incremental channel directly
|
|
148
40
|
// (event.ts handlePartDelta: content.text = props.delta). The reducer
|
|
149
41
|
// appends assistant text — no accumulation here, no turn_diff.
|
|
@@ -157,7 +49,7 @@ export class OpencodeAcpTranslator {
|
|
|
157
49
|
},
|
|
158
50
|
];
|
|
159
51
|
}
|
|
160
|
-
handleAgentThoughtChunk(event) {
|
|
52
|
+
handleAgentThoughtChunk(event, _sessionId) {
|
|
161
53
|
return [
|
|
162
54
|
{
|
|
163
55
|
type: 'assistant',
|
|
@@ -168,78 +60,47 @@ export class OpencodeAcpTranslator {
|
|
|
168
60
|
},
|
|
169
61
|
];
|
|
170
62
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
63
|
+
// =========================================================================
|
|
64
|
+
// opencode-specific hooks
|
|
65
|
+
// =========================================================================
|
|
66
|
+
/**
|
|
67
|
+
* opencode rawOutput is an OBJECT (tool.ts): completed →
|
|
68
|
+
* {output, metadata?}; failed → {error, metadata}; in_progress →
|
|
69
|
+
* content[] with shell output snapshot.
|
|
70
|
+
*/
|
|
71
|
+
extractToolResultContent(event) {
|
|
72
|
+
const rawOutput = event.rawOutput;
|
|
73
|
+
if (rawOutput && typeof rawOutput === 'object') {
|
|
74
|
+
const rec = rawOutput;
|
|
75
|
+
if (typeof rec.output === 'string')
|
|
76
|
+
return rec.output;
|
|
77
|
+
if (typeof rec.error === 'string')
|
|
78
|
+
return rec.error;
|
|
183
79
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
type: '
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
input,
|
|
199
|
-
},
|
|
200
|
-
],
|
|
201
|
-
},
|
|
202
|
-
timestamp: new Date().toISOString(),
|
|
203
|
-
},
|
|
204
|
-
];
|
|
205
|
-
}
|
|
206
|
-
handleToolCallUpdate(event) {
|
|
207
|
-
const isError = event.status === 'failed';
|
|
208
|
-
const content = extractToolResultContent(event);
|
|
209
|
-
return [
|
|
210
|
-
{
|
|
211
|
-
type: 'user',
|
|
212
|
-
message: {
|
|
213
|
-
content: [
|
|
214
|
-
{
|
|
215
|
-
type: 'tool_result',
|
|
216
|
-
tool_use_id: event.toolCallId,
|
|
217
|
-
content,
|
|
218
|
-
is_error: isError,
|
|
219
|
-
},
|
|
220
|
-
],
|
|
221
|
-
},
|
|
222
|
-
timestamp: new Date().toISOString(),
|
|
223
|
-
},
|
|
224
|
-
];
|
|
80
|
+
if (typeof rawOutput === 'string')
|
|
81
|
+
return rawOutput;
|
|
82
|
+
const content = event.content;
|
|
83
|
+
if (Array.isArray(content)) {
|
|
84
|
+
const texts = content
|
|
85
|
+
.map((item) => {
|
|
86
|
+
const c = item?.content;
|
|
87
|
+
return c?.type === 'text' && typeof c.text === 'string' ? c.text : '';
|
|
88
|
+
})
|
|
89
|
+
.filter((t) => t.length > 0);
|
|
90
|
+
if (texts.length > 0)
|
|
91
|
+
return texts.join('\n');
|
|
92
|
+
}
|
|
93
|
+
return '';
|
|
225
94
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
// (service.ts:653-663) — context occupancy + session cost, no
|
|
229
|
-
// input/output split.
|
|
230
|
-
this.liveUsage = {
|
|
231
|
-
total_tokens: event.used,
|
|
232
|
-
context_limit: event.size,
|
|
233
|
-
};
|
|
95
|
+
/** opencode usage_update also carries session cost: {cost:{amount}} (USD). */
|
|
96
|
+
onUsageSample(event) {
|
|
234
97
|
const cost = event.cost;
|
|
235
98
|
if (typeof cost?.amount === 'number') {
|
|
236
99
|
this.liveCostUsd = cost.amount;
|
|
237
100
|
}
|
|
238
|
-
this.hasLiveUsage = true;
|
|
239
|
-
return [];
|
|
240
101
|
}
|
|
241
102
|
// =========================================================================
|
|
242
|
-
//
|
|
103
|
+
// Approval
|
|
243
104
|
// =========================================================================
|
|
244
105
|
handleRequestPermission(requestId, params) {
|
|
245
106
|
// opencode always sends a toolCall (permission.ts:62-70 — built via
|
|
@@ -254,10 +115,10 @@ export class OpencodeAcpTranslator {
|
|
|
254
115
|
requestId,
|
|
255
116
|
kind: 'command',
|
|
256
117
|
command: params.toolCall.title ?? undefined,
|
|
257
|
-
reason: rawInput ?
|
|
118
|
+
reason: rawInput ? truncateWithEllipsis(stringify(rawInput), 200) : undefined,
|
|
258
119
|
// §P4: 从服务端 options kind 派生——带 allow_always(或 approve_always)
|
|
259
120
|
// 才提供「本会话总是允许」(acceptForSession)。
|
|
260
|
-
availableDecisions:
|
|
121
|
+
availableDecisions: deriveAcpAvailableDecisions(params.options ?? []),
|
|
261
122
|
};
|
|
262
123
|
return [
|
|
263
124
|
{
|
|
@@ -272,66 +133,10 @@ export class OpencodeAcpTranslator {
|
|
|
272
133
|
},
|
|
273
134
|
];
|
|
274
135
|
}
|
|
275
|
-
/**
|
|
276
|
-
* Derive the approval decision list from the server's offered options.
|
|
277
|
-
* accept/decline/cancel are universal; acceptForSession requires an
|
|
278
|
-
* always-class option (opencode `allow_always`, kimi `approve_always`).
|
|
279
|
-
*/
|
|
280
|
-
deriveAvailableDecisions(params) {
|
|
281
|
-
const decisions = ['accept', 'decline', 'cancel'];
|
|
282
|
-
const hasAlwaysOption = (params.options ?? []).some((opt) => opt.kind === 'allow_always' || opt.kind === 'approve_always');
|
|
283
|
-
if (hasAlwaysOption) {
|
|
284
|
-
decisions.push('acceptForSession');
|
|
285
|
-
}
|
|
286
|
-
return decisions;
|
|
287
|
-
}
|
|
288
|
-
// =========================================================================
|
|
289
|
-
// Private helpers
|
|
290
|
-
// =========================================================================
|
|
291
|
-
/**
|
|
292
|
-
* ResultEvent.usage nominally requires input_tokens/output_tokens, but the
|
|
293
|
-
* ACP wire has no such split — the live snapshot is context occupancy only.
|
|
294
|
-
* Cast is deliberate: consumers must tolerate undefined input/output.
|
|
295
|
-
*/
|
|
296
|
-
liveUsageSnapshot() {
|
|
297
|
-
return { ...this.liveUsage };
|
|
298
|
-
}
|
|
299
136
|
}
|
|
300
137
|
// =============================================================================
|
|
301
138
|
// Helpers
|
|
302
139
|
// =============================================================================
|
|
303
|
-
/**
|
|
304
|
-
* Extract a display string from a tool_call_update. opencode shapes
|
|
305
|
-
* (tool.ts): completed → rawOutput {output, metadata?}; failed → rawOutput
|
|
306
|
-
* {error, metadata}; in_progress → content[] with shell output snapshot.
|
|
307
|
-
*/
|
|
308
|
-
function extractToolResultContent(event) {
|
|
309
|
-
const rawOutput = event.rawOutput;
|
|
310
|
-
if (rawOutput && typeof rawOutput === 'object') {
|
|
311
|
-
const rec = rawOutput;
|
|
312
|
-
if (typeof rec.output === 'string')
|
|
313
|
-
return rec.output;
|
|
314
|
-
if (typeof rec.error === 'string')
|
|
315
|
-
return rec.error;
|
|
316
|
-
}
|
|
317
|
-
if (typeof rawOutput === 'string')
|
|
318
|
-
return rawOutput;
|
|
319
|
-
const content = event.content;
|
|
320
|
-
if (Array.isArray(content)) {
|
|
321
|
-
const texts = content
|
|
322
|
-
.map((item) => {
|
|
323
|
-
const c = item?.content;
|
|
324
|
-
return c?.type === 'text' && typeof c.text === 'string' ? c.text : '';
|
|
325
|
-
})
|
|
326
|
-
.filter((t) => t.length > 0);
|
|
327
|
-
if (texts.length > 0)
|
|
328
|
-
return texts.join('\n');
|
|
329
|
-
}
|
|
330
|
-
return '';
|
|
331
|
-
}
|
|
332
|
-
function truncate(text, maxLen) {
|
|
333
|
-
return text.length <= maxLen ? text : text.slice(0, maxLen) + '…';
|
|
334
|
-
}
|
|
335
140
|
function stringify(value) {
|
|
336
141
|
if (typeof value === 'string')
|
|
337
142
|
return value;
|
|
@@ -30,8 +30,11 @@ export declare class PiRpcTranslator {
|
|
|
30
30
|
private accOutput;
|
|
31
31
|
private accCacheRead;
|
|
32
32
|
private accCacheCreation;
|
|
33
|
+
private accReasoning;
|
|
33
34
|
private lastTotalTokens;
|
|
34
35
|
private hasUsage;
|
|
36
|
+
/** toolcall_start bookkeeping:占位不发事件,end 时补发完整 tool_use(C3.4)。 */
|
|
37
|
+
private pendingToolCall;
|
|
35
38
|
/** Provider failure (stopReason="error") on the final assistant message. */
|
|
36
39
|
private lastAssistantStopReason;
|
|
37
40
|
private lastAssistantErrorMessage;
|
|
@@ -21,8 +21,11 @@ export class PiRpcTranslator {
|
|
|
21
21
|
accOutput = 0;
|
|
22
22
|
accCacheRead = 0;
|
|
23
23
|
accCacheCreation = 0;
|
|
24
|
+
accReasoning = 0;
|
|
24
25
|
lastTotalTokens;
|
|
25
26
|
hasUsage = false;
|
|
27
|
+
/** toolcall_start bookkeeping:占位不发事件,end 时补发完整 tool_use(C3.4)。 */
|
|
28
|
+
pendingToolCall = false;
|
|
26
29
|
/** Provider failure (stopReason="error") on the final assistant message. */
|
|
27
30
|
lastAssistantStopReason;
|
|
28
31
|
lastAssistantErrorMessage;
|
|
@@ -41,6 +44,7 @@ export class PiRpcTranslator {
|
|
|
41
44
|
this.accOutput === 0 &&
|
|
42
45
|
this.accCacheRead === 0 &&
|
|
43
46
|
this.accCacheCreation === 0 &&
|
|
47
|
+
this.accReasoning === 0 &&
|
|
44
48
|
this.lastTotalTokens === undefined) {
|
|
45
49
|
return undefined;
|
|
46
50
|
}
|
|
@@ -49,6 +53,8 @@ export class PiRpcTranslator {
|
|
|
49
53
|
output_tokens: this.accOutput,
|
|
50
54
|
cache_read_tokens: this.accCacheRead,
|
|
51
55
|
cache_creation_tokens: this.accCacheCreation,
|
|
56
|
+
// 推理 token(pi usage.reasoning 累加):> 0 才携带,缺省不展示。
|
|
57
|
+
...(this.accReasoning > 0 ? { reasoning_tokens: this.accReasoning } : {}),
|
|
52
58
|
...(this.lastTotalTokens != null ? { total_tokens: this.lastTotalTokens } : {}),
|
|
53
59
|
};
|
|
54
60
|
}
|
|
@@ -92,9 +98,36 @@ export class PiRpcTranslator {
|
|
|
92
98
|
}
|
|
93
99
|
return [];
|
|
94
100
|
}
|
|
101
|
+
case 'compaction_start': {
|
|
102
|
+
// 信息保真 C3.4:压缩过程用户可见(info 通知),不再静默。
|
|
103
|
+
const c = evt;
|
|
104
|
+
return [
|
|
105
|
+
{
|
|
106
|
+
type: 'notice',
|
|
107
|
+
level: 'info',
|
|
108
|
+
code: 'compaction',
|
|
109
|
+
text: `上下文压缩中${c.reason ? `(${c.reason})` : ''}`,
|
|
110
|
+
timestamp: new Date().toISOString(),
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
case 'compaction_end': {
|
|
115
|
+
// 压缩中止(aborted)是 warn:用户需要知道压缩未完成。
|
|
116
|
+
const c = evt;
|
|
117
|
+
return [
|
|
118
|
+
{
|
|
119
|
+
type: 'notice',
|
|
120
|
+
level: c.aborted ? 'warn' : 'info',
|
|
121
|
+
code: 'compaction',
|
|
122
|
+
text: c.aborted ? `上下文压缩未完成:${c.errorMessage ?? '已中止'}` : '上下文压缩完成',
|
|
123
|
+
timestamp: new Date().toISOString(),
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
}
|
|
95
127
|
default:
|
|
96
|
-
// turn_start/turn_end, agent_start/agent_end,
|
|
97
|
-
//
|
|
128
|
+
// turn_start/turn_end, agent_start/agent_end, tool_execution_*, and
|
|
129
|
+
// unknown types are ignored. (compaction_start/end 自 C3.4 起映射为
|
|
130
|
+
// notice 事件,不再静默。)
|
|
98
131
|
return [];
|
|
99
132
|
}
|
|
100
133
|
}
|
|
@@ -154,6 +187,9 @@ export class PiRpcTranslator {
|
|
|
154
187
|
handleMessageEnd(evt) {
|
|
155
188
|
const m = evt.message;
|
|
156
189
|
if (m.role === 'assistant') {
|
|
190
|
+
// pendingToolCall 复位:消息边界后 start/end 配对作废(防御 start 后
|
|
191
|
+
// 永无 end 的异常流,避免跨消息串扰到下一条 assistant 消息)。
|
|
192
|
+
this.pendingToolCall = false;
|
|
157
193
|
this.lastAssistantStopReason = m.stopReason;
|
|
158
194
|
this.lastAssistantErrorMessage = m.stopReason === 'error' ? m.errorMessage : undefined;
|
|
159
195
|
if (m.usage)
|
|
@@ -193,6 +229,7 @@ export class PiRpcTranslator {
|
|
|
193
229
|
this.accOutput += u.output ?? 0;
|
|
194
230
|
this.accCacheRead += u.cacheRead ?? 0;
|
|
195
231
|
this.accCacheCreation += u.cacheWrite ?? 0;
|
|
232
|
+
this.accReasoning += u.reasoning ?? 0;
|
|
196
233
|
if (u.totalTokens != null)
|
|
197
234
|
this.lastTotalTokens = u.totalTokens;
|
|
198
235
|
}
|
|
@@ -234,19 +271,20 @@ export class PiRpcTranslator {
|
|
|
234
271
|
}
|
|
235
272
|
break;
|
|
236
273
|
case 'toolcall_start':
|
|
237
|
-
|
|
238
|
-
|
|
274
|
+
// 信息保真 C3.4:不再 push 空壳 tool_use(空 id/name 会被渲染成
|
|
275
|
+
// 无意义工具块)。只保留 bookkeeping,toolcall_end 补发完整事件。
|
|
276
|
+
this.pendingToolCall = true;
|
|
239
277
|
break;
|
|
240
278
|
case 'toolcall_end':
|
|
241
|
-
if (this.
|
|
242
|
-
this.content
|
|
243
|
-
evt.toolCall) {
|
|
244
|
-
this.content[this.currentContentIndex] = {
|
|
279
|
+
if (this.pendingToolCall && evt.toolCall) {
|
|
280
|
+
this.content.push({
|
|
245
281
|
type: 'tool_use',
|
|
246
282
|
id: evt.toolCall.id,
|
|
247
283
|
name: evt.toolCall.name,
|
|
248
284
|
input: evt.toolCall.arguments,
|
|
249
|
-
};
|
|
285
|
+
});
|
|
286
|
+
this.currentContentIndex = this.content.length - 1;
|
|
287
|
+
this.pendingToolCall = false;
|
|
250
288
|
}
|
|
251
289
|
break;
|
|
252
290
|
default:
|
package/dist/runner/probe.d.ts
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent availability probe — detect whether each CLI binary is installed
|
|
3
|
-
*
|
|
3
|
+
* via a pure-Node PATH lookup (platform seam `resolveExecutable`).
|
|
4
4
|
*
|
|
5
|
-
* Why
|
|
5
|
+
* Why a PATH lookup over `<binary> --help`:
|
|
6
6
|
* - `--help` starts a full Node.js process per agent (2–8 s on
|
|
7
7
|
* resource-constrained devices like Raspberry Pi). Under startup load
|
|
8
8
|
* (auto-resume spawns, CPU contention) probes time out and installed
|
|
9
9
|
* agents are falsely reported as unavailable.
|
|
10
|
-
* -
|
|
10
|
+
* - A PATH lookup is lightweight (<10 ms), immune to CPU contention.
|
|
11
11
|
* - "Installed but broken" cases surface at run time via SpawningRunner's
|
|
12
12
|
* ENOENT/error handling (src/runner/common/spawning-runner.ts), which
|
|
13
13
|
* shows a friendly error card instead of a startup-time mislabel.
|
|
14
14
|
*
|
|
15
|
-
* Why
|
|
16
|
-
* - `which` is
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* other Debian-based distributions ship it by default; if Windows or
|
|
20
|
-
* minimal Alpine containers become supported, prefer a pure-Node
|
|
21
|
-
* PATH + X_OK lookup instead.
|
|
15
|
+
* Why the platform seam over spawning `which`:
|
|
16
|
+
* - `which` is not POSIX-guaranteed and does not exist on Windows; the seam
|
|
17
|
+
* does PATH + X_OK on posix (same semantics) and PATH × PATHEXT on win32,
|
|
18
|
+
* with no child process at all.
|
|
22
19
|
*/
|
|
23
20
|
import type { AgentKind } from './types.js';
|
|
24
21
|
/**
|
|
@@ -29,7 +26,7 @@ export declare function probeAgentAvailability(kind: AgentKind): Promise<boolean
|
|
|
29
26
|
/**
|
|
30
27
|
* Probe all agents concurrently.
|
|
31
28
|
*
|
|
32
|
-
* Concurrent (not sequential) because
|
|
29
|
+
* Concurrent (not sequential) because the lookup is a lightweight PATH scan
|
|
33
30
|
* (<10 ms) with no CPU contention risk — unlike the previous `--help`
|
|
34
31
|
* probes, which needed to run one-at-a-time to avoid starving each other
|
|
35
32
|
* on resource-constrained devices.
|
package/dist/runner/probe.js
CHANGED
|
@@ -1,26 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent availability probe — detect whether each CLI binary is installed
|
|
3
|
-
*
|
|
3
|
+
* via a pure-Node PATH lookup (platform seam `resolveExecutable`).
|
|
4
4
|
*
|
|
5
|
-
* Why
|
|
5
|
+
* Why a PATH lookup over `<binary> --help`:
|
|
6
6
|
* - `--help` starts a full Node.js process per agent (2–8 s on
|
|
7
7
|
* resource-constrained devices like Raspberry Pi). Under startup load
|
|
8
8
|
* (auto-resume spawns, CPU contention) probes time out and installed
|
|
9
9
|
* agents are falsely reported as unavailable.
|
|
10
|
-
* -
|
|
10
|
+
* - A PATH lookup is lightweight (<10 ms), immune to CPU contention.
|
|
11
11
|
* - "Installed but broken" cases surface at run time via SpawningRunner's
|
|
12
12
|
* ENOENT/error handling (src/runner/common/spawning-runner.ts), which
|
|
13
13
|
* shows a friendly error card instead of a startup-time mislabel.
|
|
14
14
|
*
|
|
15
|
-
* Why
|
|
16
|
-
* - `which` is
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* other Debian-based distributions ship it by default; if Windows or
|
|
20
|
-
* minimal Alpine containers become supported, prefer a pure-Node
|
|
21
|
-
* PATH + X_OK lookup instead.
|
|
15
|
+
* Why the platform seam over spawning `which`:
|
|
16
|
+
* - `which` is not POSIX-guaranteed and does not exist on Windows; the seam
|
|
17
|
+
* does PATH + X_OK on posix (same semantics) and PATH × PATHEXT on win32,
|
|
18
|
+
* with no child process at all.
|
|
22
19
|
*/
|
|
23
|
-
import {
|
|
20
|
+
import { resolveExecutable } from '../platform/command.js';
|
|
24
21
|
/** Map from AgentKind to its CLI binary name. */
|
|
25
22
|
const BINARY_MAP = {
|
|
26
23
|
claude: 'claude',
|
|
@@ -28,24 +25,23 @@ const BINARY_MAP = {
|
|
|
28
25
|
opencode: 'opencode',
|
|
29
26
|
pi: 'pi',
|
|
30
27
|
kimi: 'kimi',
|
|
31
|
-
// DSH is an HTTP-only agent (no CLI)
|
|
32
|
-
//
|
|
28
|
+
// DSH is an HTTP-only agent (no CLI) → reported unavailable, which is
|
|
29
|
+
// semantically correct for the /config availability display.
|
|
33
30
|
dsh: '',
|
|
34
31
|
};
|
|
35
32
|
const CACHE_TTL_MS = 5 * 60_000;
|
|
36
33
|
/** Module-level cache, survives across calls within the process. */
|
|
37
34
|
const cache = new Map();
|
|
38
35
|
/**
|
|
39
|
-
* Probe a single agent's availability
|
|
40
|
-
*
|
|
36
|
+
* Probe a single agent's availability (pure PATH lookup; dsh has no CLI
|
|
37
|
+
* binary and is always unavailable here).
|
|
41
38
|
*/
|
|
42
39
|
async function probeOne(kind) {
|
|
43
40
|
const binary = BINARY_MAP[kind];
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
});
|
|
41
|
+
if (!binary)
|
|
42
|
+
return false;
|
|
43
|
+
// resolveExecutable 纯 Node PATH 查找(win32 PATH×PATHEXT),命中即可用。
|
|
44
|
+
return resolveExecutable(binary) !== null;
|
|
49
45
|
}
|
|
50
46
|
/**
|
|
51
47
|
* Probe a single agent, using cache when fresh.
|
|
@@ -62,7 +58,7 @@ export async function probeAgentAvailability(kind) {
|
|
|
62
58
|
/**
|
|
63
59
|
* Probe all agents concurrently.
|
|
64
60
|
*
|
|
65
|
-
* Concurrent (not sequential) because
|
|
61
|
+
* Concurrent (not sequential) because the lookup is a lightweight PATH scan
|
|
66
62
|
* (<10 ms) with no CPU contention risk — unlike the previous `--help`
|
|
67
63
|
* probes, which needed to run one-at-a-time to avoid starving each other
|
|
68
64
|
* on resource-constrained devices.
|
|
@@ -4,10 +4,10 @@ import type { AgentKind, Runner } from './index.js';
|
|
|
4
4
|
* factory per available agent at startup; `Bridge.getRunner(workspace)` looks
|
|
5
5
|
* up `config.defaultAgent` here to pick the concrete runner.
|
|
6
6
|
*
|
|
7
|
-
* Singleton caching: the factory MAY cache its instance internally.
|
|
8
|
-
*
|
|
9
|
-
* workspace-lifetime (
|
|
10
|
-
* subprocess). Each factory returns a runner per call.
|
|
7
|
+
* Singleton caching: the factory MAY cache its instance internally. All
|
|
8
|
+
* local-subprocess agents (claude/codex/opencode/pi/kimi) are
|
|
9
|
+
* workspace-lifetime (long-lived session); dsh is HTTP+WS direct connection
|
|
10
|
+
* (no local subprocess). Each factory returns a runner per call.
|
|
11
11
|
*
|
|
12
12
|
* Dynamic config reload: factory can read latest config via registry's
|
|
13
13
|
* configContainer, enabling runtime config changes to take effect.
|
package/dist/runner/registry.js
CHANGED
|
@@ -8,10 +8,10 @@ let globalRegistry;
|
|
|
8
8
|
* factory per available agent at startup; `Bridge.getRunner(workspace)` looks
|
|
9
9
|
* up `config.defaultAgent` here to pick the concrete runner.
|
|
10
10
|
*
|
|
11
|
-
* Singleton caching: the factory MAY cache its instance internally.
|
|
12
|
-
*
|
|
13
|
-
* workspace-lifetime (
|
|
14
|
-
* subprocess). Each factory returns a runner per call.
|
|
11
|
+
* Singleton caching: the factory MAY cache its instance internally. All
|
|
12
|
+
* local-subprocess agents (claude/codex/opencode/pi/kimi) are
|
|
13
|
+
* workspace-lifetime (long-lived session); dsh is HTTP+WS direct connection
|
|
14
|
+
* (no local subprocess). Each factory returns a runner per call.
|
|
15
15
|
*
|
|
16
16
|
* Dynamic config reload: factory can read latest config via registry's
|
|
17
17
|
* configContainer, enabling runtime config changes to take effect.
|