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
|
@@ -7,10 +7,8 @@
|
|
|
7
7
|
* → synthetic init → session/prompt → consume session/update notifications
|
|
8
8
|
* until prompt settles → result event.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* stopRequested semantics, same turn idle timeout, same ConnectionLostError
|
|
13
|
-
* retry). Differences from kimi:
|
|
10
|
+
* Turn/connection/compact/approval orchestration lives in BaseAcpRunner;
|
|
11
|
+
* opencode-specific parts:
|
|
14
12
|
* - session/set_mode applies the configured mode (build/plan; default 'build'
|
|
15
13
|
* keeps manual approvals flowing); opencode sends no set_mode notification,
|
|
16
14
|
* so the client refreshes the local mode view itself
|
|
@@ -18,22 +16,20 @@
|
|
|
18
16
|
* service.ts:555-571 summarize completes before the prompt response)
|
|
19
17
|
* - text/thinking go to the assistant incremental channel (opencode wire
|
|
20
18
|
* deltas), not turn_diff snapshots
|
|
19
|
+
* - OpencodeLogErrorMonitor tails opencode's own log for LLM stream errors
|
|
20
|
+
* the ACP protocol never forwards
|
|
21
21
|
*/
|
|
22
22
|
import { ConnectionManager, } from '../../common/jsonrpc/connection-manager.js';
|
|
23
23
|
import { OpencodeAcpTranslator } from './translator.js';
|
|
24
24
|
import { RpcErrorCode, } from '../../common/acp/protocol-types.js';
|
|
25
25
|
import { OPENCODE_APPROVAL_KINDS, buildAcpPermissionOutcome, } from '../../common/acp/protocol-helpers.js';
|
|
26
26
|
import { getLogger } from '../../../logger/index.js';
|
|
27
|
-
import {
|
|
27
|
+
import { BaseAcpRunner } from '../../common/acp/base-acp-runner.js';
|
|
28
28
|
import { OpencodeLogErrorMonitor, resolveOpencodeLogPath } from './error-monitor.js';
|
|
29
29
|
// =============================================================================
|
|
30
30
|
// Runner
|
|
31
31
|
// =============================================================================
|
|
32
|
-
export class OpencodeAcpRunner extends
|
|
33
|
-
connectionManager;
|
|
34
|
-
currentTranslator = null;
|
|
35
|
-
activeSessionId = null;
|
|
36
|
-
model;
|
|
32
|
+
export class OpencodeAcpRunner extends BaseAcpRunner {
|
|
37
33
|
/** Configured session mode applied via session/set_mode (§P5). */
|
|
38
34
|
configuredMode;
|
|
39
35
|
/** Current session mode id, parsed from session/new|resume configOptions. */
|
|
@@ -43,22 +39,7 @@ export class OpencodeAcpRunner extends ConnectionBasedRunner {
|
|
|
43
39
|
/** Tails opencode's own log to surface LLM stream errors the ACP protocol
|
|
44
40
|
* never forwards (quota/limit errors back off for ~1h → turn would hang). */
|
|
45
41
|
errorMonitor;
|
|
46
|
-
/** Pending approval requests: requestId → kind + view + options. */
|
|
47
|
-
pendingApprovals = new Map();
|
|
48
|
-
/** Tracker for the in-flight prompt request — needed for cancellation. */
|
|
49
|
-
promptSettled = false;
|
|
50
|
-
/** Whether the prompt request has been fired (distinguishes "stop before
|
|
51
|
-
* prompt sent" from "stop while prompt in-flight" — the latter sends
|
|
52
|
-
* session/cancel immediately). */
|
|
53
|
-
promptSent = false;
|
|
54
42
|
constructor(opts) {
|
|
55
|
-
super({ kind: opts.kind, sessionReader: opts.sessionReader }, opts.turnIdleTimeoutMs);
|
|
56
|
-
this.model = opts.model;
|
|
57
|
-
this.configuredMode = opts.mode ?? 'build';
|
|
58
|
-
this.errorMonitor = new OpencodeLogErrorMonitor({
|
|
59
|
-
logPath: opts.errorMonitorLogPath ?? resolveOpencodeLogPath(),
|
|
60
|
-
pollIntervalMs: opts.errorMonitorPollIntervalMs,
|
|
61
|
-
});
|
|
62
43
|
const managerOpts = {
|
|
63
44
|
binary: opts.binary ?? 'opencode',
|
|
64
45
|
args: opts.acpArgs ?? ['acp'],
|
|
@@ -76,7 +57,17 @@ export class OpencodeAcpRunner extends ConnectionBasedRunner {
|
|
|
76
57
|
},
|
|
77
58
|
},
|
|
78
59
|
};
|
|
79
|
-
|
|
60
|
+
super({
|
|
61
|
+
kind: opts.kind,
|
|
62
|
+
sessionReader: opts.sessionReader,
|
|
63
|
+
turnIdleTimeoutMs: opts.turnIdleTimeoutMs,
|
|
64
|
+
}, new ConnectionManager(managerOpts));
|
|
65
|
+
this.model = opts.model;
|
|
66
|
+
this.configuredMode = opts.mode ?? 'build';
|
|
67
|
+
this.errorMonitor = new OpencodeLogErrorMonitor({
|
|
68
|
+
logPath: opts.errorMonitorLogPath ?? resolveOpencodeLogPath(),
|
|
69
|
+
pollIntervalMs: opts.errorMonitorPollIntervalMs,
|
|
70
|
+
});
|
|
80
71
|
}
|
|
81
72
|
get logTag() {
|
|
82
73
|
return 'opencode-acp-runner';
|
|
@@ -87,31 +78,12 @@ export class OpencodeAcpRunner extends ConnectionBasedRunner {
|
|
|
87
78
|
get turnInterruptedErrorMessage() {
|
|
88
79
|
return 'opencode ACP turn interrupted';
|
|
89
80
|
}
|
|
90
|
-
|
|
91
|
-
return
|
|
81
|
+
get connectionClosedMessage() {
|
|
82
|
+
return 'opencode ACP connection closed';
|
|
92
83
|
}
|
|
93
84
|
shouldDeferStop() {
|
|
94
85
|
return !this.promptSent;
|
|
95
86
|
}
|
|
96
|
-
async cancelCurrentTurn() {
|
|
97
|
-
if (!this.currentClient || !this.activeSessionId)
|
|
98
|
-
return;
|
|
99
|
-
try {
|
|
100
|
-
// session/cancel is a NOTIFICATION (no id, no response) per ACP spec.
|
|
101
|
-
const cancelParams = { sessionId: this.activeSessionId };
|
|
102
|
-
this.currentClient.notify('session/cancel', cancelParams);
|
|
103
|
-
}
|
|
104
|
-
catch (err) {
|
|
105
|
-
getLogger().warn(`[${this.logTag}] session/cancel failed: ${err.message}`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
clearTurnState() {
|
|
109
|
-
this.errorMonitor.stop();
|
|
110
|
-
this.currentTranslator = null;
|
|
111
|
-
this.promptSettled = false;
|
|
112
|
-
this.promptSent = false;
|
|
113
|
-
this.activeSessionId = null;
|
|
114
|
-
}
|
|
115
87
|
/**
|
|
116
88
|
* Start watching opencode's own log for LLM stream errors of the active
|
|
117
89
|
* session. opencode ACP does not surface retry/error state to the client
|
|
@@ -127,175 +99,10 @@ export class OpencodeAcpRunner extends ConnectionBasedRunner {
|
|
|
127
99
|
this.failTurn(`opencode LLM stream error: ${message}`);
|
|
128
100
|
});
|
|
129
101
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
notifyIdle(cwd) {
|
|
134
|
-
this.connectionManager.notifyIdle(cwd);
|
|
135
|
-
}
|
|
136
|
-
async disposeConnections() {
|
|
137
|
-
await this.connectionManager.disposeAll();
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Run a compact operation on the current session.
|
|
141
|
-
*
|
|
142
|
-
* opencode special-cases the `/compact` text prompt server-side
|
|
143
|
-
* (service.ts:555-571): it runs session.summarize synchronously and the
|
|
144
|
-
* prompt response settles only after compaction completes — no background
|
|
145
|
-
* record polling needed (unlike kimi).
|
|
146
|
-
*/
|
|
147
|
-
async *runCompact(_message, opts) {
|
|
148
|
-
yield* this.executeTurn(opts, async () => {
|
|
149
|
-
const client = await this.connectionManager.acquire(opts.cwd);
|
|
150
|
-
this.connectionManager.notifyActivity(opts.cwd);
|
|
151
|
-
this.currentClient = client;
|
|
152
|
-
client.setHooks({
|
|
153
|
-
onNotification: (method, params) => this.handleNotification(method, params),
|
|
154
|
-
onServerRequest: (id, method, params) => this.handleServerRequest(id, method, params),
|
|
155
|
-
onClose: () => this.failTurn('opencode ACP connection closed'),
|
|
156
|
-
});
|
|
157
|
-
const sessionId = opts.sessionId;
|
|
158
|
-
if (!sessionId) {
|
|
159
|
-
throw new Error('compact requires a sessionId');
|
|
160
|
-
}
|
|
161
|
-
this.activeSessionId = sessionId;
|
|
162
|
-
this.startErrorMonitor(sessionId);
|
|
163
|
-
// Cold-connection fallback: session/resume loads the session into
|
|
164
|
-
// memory. On a reused connection the session is already loaded —
|
|
165
|
-
// resume is idempotent and harmless.
|
|
166
|
-
const resumeParams = { sessionId, cwd: opts.cwd };
|
|
167
|
-
const resumeResult = await client.request('session/resume', resumeParams);
|
|
168
|
-
this.trackConfigOptions(resumeResult);
|
|
169
|
-
const translator = new OpencodeAcpTranslator();
|
|
170
|
-
translator.setOperationKind('compact');
|
|
171
|
-
this.currentTranslator = translator;
|
|
172
|
-
// Emit turn_started with operationKind='compaction' before the prompt.
|
|
173
|
-
const turnId = `compact-${Date.now()}`;
|
|
174
|
-
const turnStarted = translator.produceTurnStarted(sessionId, turnId);
|
|
175
|
-
this.currentTurnId = turnId;
|
|
176
|
-
this.pushEvents([turnStarted]);
|
|
177
|
-
// Send /compact as prompt text (opencode intercepts it server-side).
|
|
178
|
-
const promptParams = {
|
|
179
|
-
sessionId,
|
|
180
|
-
prompt: [{ type: 'text', text: '/compact' }],
|
|
181
|
-
};
|
|
182
|
-
this.promptSettled = false;
|
|
183
|
-
// ACP holds the session/prompt response for the ENTIRE turn, so the
|
|
184
|
-
// RPC-level timeout must not apply — turn liveness is guarded by the
|
|
185
|
-
// rolling idle watchdog instead.
|
|
186
|
-
const promptPromise = client.request('session/prompt', promptParams, Number.POSITIVE_INFINITY);
|
|
187
|
-
this.promptSent = true;
|
|
188
|
-
promptPromise.then((result) => {
|
|
189
|
-
this.promptSettled = true;
|
|
190
|
-
if (this.stopRequested)
|
|
191
|
-
return; // already cancelled
|
|
192
|
-
const resultEvent = translator.handlePromptResponse(sessionId, result);
|
|
193
|
-
this.pushEvents([resultEvent]);
|
|
194
|
-
}, (err) => {
|
|
195
|
-
this.promptSettled = true;
|
|
196
|
-
if (this.stopRequested) {
|
|
197
|
-
getLogger().warn(`[${this.logTag}] compact prompt rejected after stopRequested: ${err.message}`);
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
const resultEvent = translator.produceErrorResult(sessionId, err.message);
|
|
201
|
-
this.pushEvents([resultEvent]);
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Respond to an approval server request. `response` is the bridge
|
|
207
|
-
* ApprovalAction (`{ action: 'accept' | 'decline' | 'cancel' }`).
|
|
208
|
-
*/
|
|
209
|
-
async respondApproval(requestId, response) {
|
|
210
|
-
const client = this.currentClient;
|
|
211
|
-
const pending = this.pendingApprovals.get(requestId);
|
|
212
|
-
if (!client || !pending)
|
|
213
|
-
return;
|
|
214
|
-
const action = response?.action ?? 'decline';
|
|
215
|
-
const acpResponse = buildAcpPermissionOutcome(action, pending.options, OPENCODE_APPROVAL_KINDS);
|
|
216
|
-
client.respond(requestId, acpResponse);
|
|
217
|
-
this.pendingApprovals.delete(requestId);
|
|
218
|
-
getLogger().info(`[${this.logTag}] approval responded requestId=${requestId} action=${action}`);
|
|
219
|
-
}
|
|
220
|
-
getStatusInfo() {
|
|
221
|
-
return {
|
|
222
|
-
kind: this.kind,
|
|
223
|
-
model: this.model ?? '(opencode-acp)',
|
|
224
|
-
extras: {
|
|
225
|
-
mode: 'acp',
|
|
226
|
-
...(this.currentModeId ? { sessionMode: this.currentModeId } : {}),
|
|
227
|
-
},
|
|
228
|
-
};
|
|
102
|
+
createTranslator() {
|
|
103
|
+
return new OpencodeAcpTranslator();
|
|
229
104
|
}
|
|
230
|
-
|
|
231
|
-
* Hot-apply a mode change to the live ACP session (§P5).
|
|
232
|
-
*
|
|
233
|
-
* opencode's session/set_mode sends no notification — the client refreshes
|
|
234
|
-
* the local mode view itself. The local mode is always updated so
|
|
235
|
-
* `getStatusInfo()` (and therefore `/s`) reflects the new value
|
|
236
|
-
* immediately; when a session is connected, also re-sends session/set_mode
|
|
237
|
-
* so the running session picks up the new mode without waiting for the
|
|
238
|
-
* runner to be evicted/recreated. Failure is non-fatal: the next setupTurn
|
|
239
|
-
* re-applies the cached mode when it differs from the session's current
|
|
240
|
-
* mode.
|
|
241
|
-
*/
|
|
242
|
-
async updateApprovalMode(settings) {
|
|
243
|
-
if (settings.mode !== undefined) {
|
|
244
|
-
this.configuredMode = settings.mode;
|
|
245
|
-
this.currentModeId = settings.mode;
|
|
246
|
-
}
|
|
247
|
-
if (!this.currentClient || !this.activeSessionId)
|
|
248
|
-
return;
|
|
249
|
-
const modeParams = {
|
|
250
|
-
sessionId: this.activeSessionId,
|
|
251
|
-
modeId: this.configuredMode,
|
|
252
|
-
};
|
|
253
|
-
try {
|
|
254
|
-
await this.currentClient.request('session/set_mode', modeParams);
|
|
255
|
-
getLogger().info(`[${this.logTag}] session/set_mode hot-applied session=${this.activeSessionId} modeId=${modeParams.modeId}`);
|
|
256
|
-
}
|
|
257
|
-
catch (err) {
|
|
258
|
-
getLogger().warn(`[${this.logTag}] session/set_mode failed (non-fatal): ${err.message}`);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
// =========================================================================
|
|
262
|
-
// Internal
|
|
263
|
-
// =========================================================================
|
|
264
|
-
/**
|
|
265
|
-
* Acquire the connection and set up the turn:
|
|
266
|
-
* session/new (new session) or session/resume (existing sessionId)
|
|
267
|
-
* → session/prompt.
|
|
268
|
-
*/
|
|
269
|
-
async setupTurn(message, opts) {
|
|
270
|
-
const client = await this.connectionManager.acquire(opts.cwd);
|
|
271
|
-
this.connectionManager.notifyActivity(opts.cwd);
|
|
272
|
-
this.currentClient = client;
|
|
273
|
-
client.setHooks({
|
|
274
|
-
onNotification: (method, params) => this.handleNotification(method, params),
|
|
275
|
-
onServerRequest: (id, method, params) => this.handleServerRequest(id, method, params),
|
|
276
|
-
onClose: () => {
|
|
277
|
-
getLogger().warn(`[${this.logTag}] client connection closed`);
|
|
278
|
-
this.failTurn('opencode ACP connection closed');
|
|
279
|
-
},
|
|
280
|
-
});
|
|
281
|
-
let sessionId;
|
|
282
|
-
if (opts.sessionId) {
|
|
283
|
-
const resumeParams = {
|
|
284
|
-
sessionId: opts.sessionId,
|
|
285
|
-
cwd: opts.cwd,
|
|
286
|
-
};
|
|
287
|
-
const resumeResult = await client.request('session/resume', resumeParams);
|
|
288
|
-
this.trackConfigOptions(resumeResult);
|
|
289
|
-
sessionId = opts.sessionId;
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
const newParams = { cwd: opts.cwd, mcpServers: [] };
|
|
293
|
-
const newResult = await client.request('session/new', newParams);
|
|
294
|
-
this.trackConfigOptions(newResult);
|
|
295
|
-
sessionId = newResult.sessionId;
|
|
296
|
-
}
|
|
297
|
-
this.activeSessionId = sessionId;
|
|
298
|
-
this.startErrorMonitor(sessionId);
|
|
105
|
+
async applyTurnSettings(client, sessionId) {
|
|
299
106
|
// §P5: apply the configured mode (build/plan). If the session already
|
|
300
107
|
// runs it (from session/new|resume configOptions), skip the wire call;
|
|
301
108
|
// otherwise send session/set_mode and refresh the local view (opencode
|
|
@@ -331,39 +138,49 @@ export class OpencodeAcpRunner extends ConnectionBasedRunner {
|
|
|
331
138
|
getLogger().warn(`[${this.logTag}] set_config_option model failed (non-fatal): ${err.message}`);
|
|
332
139
|
}
|
|
333
140
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
this.
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* opencode /compact is synchronous server-side — standard prompt-settle
|
|
144
|
+
* translation (no wire.jsonl polling, unlike kimi).
|
|
145
|
+
*/
|
|
146
|
+
onCompactPromptFired(promptPromise, translator, sessionId, _opts, _baselineTotalCount) {
|
|
147
|
+
promptPromise.then((result) => this.handlePromptResolved(result, translator, sessionId), (err) => this.handlePromptRejected(err, translator, sessionId, 'compact'));
|
|
148
|
+
}
|
|
149
|
+
buildApprovalOutcome(action, pending, _response) {
|
|
150
|
+
return buildAcpPermissionOutcome(action, pending.options, OPENCODE_APPROVAL_KINDS);
|
|
151
|
+
}
|
|
152
|
+
getStatusInfo() {
|
|
153
|
+
return {
|
|
154
|
+
kind: this.kind,
|
|
155
|
+
model: this.model ?? '(opencode-acp)',
|
|
156
|
+
extras: {
|
|
157
|
+
mode: 'acp',
|
|
158
|
+
...(this.currentModeId ? { sessionMode: this.currentModeId } : {}),
|
|
159
|
+
},
|
|
347
160
|
};
|
|
348
|
-
this.promptSettled = false;
|
|
349
|
-
const promptPromise = client.request('session/prompt', promptParams, Number.POSITIVE_INFINITY);
|
|
350
|
-
this.promptSent = true;
|
|
351
|
-
promptPromise.then((result) => {
|
|
352
|
-
this.promptSettled = true;
|
|
353
|
-
if (this.stopRequested)
|
|
354
|
-
return; // already cancelled
|
|
355
|
-
const resultEvent = translator.handlePromptResponse(sessionId, result);
|
|
356
|
-
this.pushEvents([resultEvent]);
|
|
357
|
-
}, (err) => {
|
|
358
|
-
this.promptSettled = true;
|
|
359
|
-
if (this.stopRequested) {
|
|
360
|
-
getLogger().warn(`[${this.logTag}] prompt rejected after stopRequested: ${err.message}`);
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
const resultEvent = translator.produceErrorResult(sessionId, err.message);
|
|
364
|
-
this.pushEvents([resultEvent]);
|
|
365
|
-
});
|
|
366
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Hot-apply a mode change to the live ACP session (§P5).
|
|
164
|
+
*
|
|
165
|
+
* opencode's session/set_mode sends no notification — the client refreshes
|
|
166
|
+
* the local mode view itself. The local mode is always updated so
|
|
167
|
+
* `getStatusInfo()` (and therefore `/s`) reflects the new value
|
|
168
|
+
* immediately; when a session is connected, also re-sends session/set_mode
|
|
169
|
+
* so the running session picks up the new mode without waiting for the
|
|
170
|
+
* runner to be evicted/recreated. Failure is non-fatal: the next setupTurn
|
|
171
|
+
* re-applies the cached mode when it differs from the session's current
|
|
172
|
+
* mode.
|
|
173
|
+
*/
|
|
174
|
+
async updateApprovalMode(settings) {
|
|
175
|
+
if (settings.mode !== undefined) {
|
|
176
|
+
this.configuredMode = settings.mode;
|
|
177
|
+
this.currentModeId = settings.mode;
|
|
178
|
+
}
|
|
179
|
+
await this.sendApprovalModeUpdate(this.configuredMode);
|
|
180
|
+
}
|
|
181
|
+
// =========================================================================
|
|
182
|
+
// Internal
|
|
183
|
+
// =========================================================================
|
|
367
184
|
/**
|
|
368
185
|
* Track the current session mode and model from a session/new|resume
|
|
369
186
|
* result's configOptions (opencode buildModeSelectOption/buildModelSelectOption:
|
|
@@ -382,6 +199,15 @@ export class OpencodeAcpRunner extends ConnectionBasedRunner {
|
|
|
382
199
|
this.currentModelValue = model.currentValue;
|
|
383
200
|
}
|
|
384
201
|
}
|
|
202
|
+
onTurnSessionReady(sessionId) {
|
|
203
|
+
this.startErrorMonitor(sessionId);
|
|
204
|
+
}
|
|
205
|
+
onSessionEstablished(result) {
|
|
206
|
+
this.trackConfigOptions(result);
|
|
207
|
+
}
|
|
208
|
+
onClearTurnState() {
|
|
209
|
+
this.errorMonitor.stop();
|
|
210
|
+
}
|
|
385
211
|
handleNotification(method, params) {
|
|
386
212
|
const events = this.currentTranslator?.handleNotification(method, params) ?? [];
|
|
387
213
|
this.pushEvents(events);
|
|
@@ -399,16 +225,7 @@ export class OpencodeAcpRunner extends ConnectionBasedRunner {
|
|
|
399
225
|
getLogger().info(`[${this.logTag}] rejected unsupported server request method=${method} id=${id}`);
|
|
400
226
|
return;
|
|
401
227
|
}
|
|
402
|
-
|
|
403
|
-
if (ev.type === 'approval_requested') {
|
|
404
|
-
this.pendingApprovals.set(ev.requestId, {
|
|
405
|
-
kind: ev.kind,
|
|
406
|
-
view: ev.view,
|
|
407
|
-
options: params.options,
|
|
408
|
-
});
|
|
409
|
-
getLogger().info(`[${this.logTag}] approval requested requestId=${ev.requestId} kind=${ev.kind}`);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
228
|
+
this.registerApprovalEvents(events, params.options);
|
|
412
229
|
this.pushEvents(events);
|
|
413
230
|
}
|
|
414
231
|
}
|
|
@@ -2,117 +2,42 @@
|
|
|
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 type {
|
|
32
|
-
import type
|
|
33
|
-
|
|
34
|
-
export
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
total_tokens?: number;
|
|
48
|
-
context_limit?: number;
|
|
49
|
-
input_tokens?: number;
|
|
50
|
-
output_tokens?: number;
|
|
51
|
-
}
|
|
52
|
-
/** Usage update event for live token display. */
|
|
53
|
-
export interface OpencodeAcpUsageEvent {
|
|
54
|
-
type: 'usage';
|
|
55
|
-
usage: OpencodeAcpLiveUsage;
|
|
56
|
-
timestamp?: string;
|
|
57
|
-
}
|
|
58
|
-
export type OpencodeAcpTranslatorEvent = AgentEvent | OpencodeAcpTurnStartedEvent | OpencodeAcpUsageEvent | ApprovalRequestedEvent;
|
|
59
|
-
export declare class OpencodeAcpTranslator {
|
|
60
|
-
/** Pending tool calls keyed by toolCallId (for matching tool_call_update). */
|
|
61
|
-
private pendingToolCalls;
|
|
62
|
-
/** Current operation kind (turn vs compact). */
|
|
63
|
-
private operationKind;
|
|
64
|
-
/** Live usage snapshot from usage_update events (context occupancy only). */
|
|
65
|
-
private liveUsage;
|
|
66
|
-
/** Session cost from usage_update cost.amount (USD). */
|
|
67
|
-
private liveCostUsd?;
|
|
68
|
-
/** Whether any usage_update has been seen this turn (no update → no usage). */
|
|
69
|
-
private hasLiveUsage;
|
|
70
|
-
/** Current turn id, set by produceTurnStarted. */
|
|
71
|
-
private currentTurnId;
|
|
72
|
-
/**
|
|
73
|
-
* Handle a notification from the ACP server and return translated events.
|
|
74
|
-
*/
|
|
75
|
-
handleNotification(method: string, params: unknown): OpencodeAcpTranslatorEvent[];
|
|
76
|
-
/**
|
|
77
|
-
* Handle a server request (reverse RPC from the ACP server).
|
|
78
|
-
*/
|
|
79
|
-
handleServerRequest(id: number | string, method: string, params: unknown): OpencodeAcpTranslatorEvent[];
|
|
80
|
-
/**
|
|
81
|
-
* Produce a turn_started event. Called by the runner when session/prompt
|
|
82
|
-
* is sent (no wire notification for this — self-produced).
|
|
83
|
-
*/
|
|
84
|
-
produceTurnStarted(sessionId: string, turnId: string): OpencodeAcpTurnStartedEvent;
|
|
85
|
-
/**
|
|
86
|
-
* Translate a prompt response into a result event.
|
|
87
|
-
* stopReason: end_turn → success; cancelled → interrupted; anything else
|
|
88
|
-
* (max_tokens / refusal / …) → error.
|
|
89
|
-
*/
|
|
90
|
-
handlePromptResponse(sessionId: string, result: {
|
|
91
|
-
stopReason: string;
|
|
92
|
-
}): AgentEvent;
|
|
93
|
-
/**
|
|
94
|
-
* Produce an error result event (for JSON-RPC errors during prompt).
|
|
95
|
-
*/
|
|
96
|
-
produceErrorResult(sessionId: string, errorMessage: string): AgentEvent;
|
|
97
|
-
/**
|
|
98
|
-
* Set the operation kind (turn vs compact).
|
|
99
|
-
*/
|
|
100
|
-
setOperationKind(kind: 'turn' | 'compact'): void;
|
|
101
|
-
private handleAgentMessageChunk;
|
|
102
|
-
private handleAgentThoughtChunk;
|
|
103
|
-
private handleToolCall;
|
|
104
|
-
private handleToolCallUpdate;
|
|
105
|
-
private handleUsageUpdate;
|
|
106
|
-
private handleRequestPermission;
|
|
107
|
-
/**
|
|
108
|
-
* Derive the approval decision list from the server's offered options.
|
|
109
|
-
* accept/decline/cancel are universal; acceptForSession requires an
|
|
110
|
-
* always-class option (opencode `allow_always`, kimi `approve_always`).
|
|
111
|
-
*/
|
|
112
|
-
/**
|
|
113
|
-
* ResultEvent.usage nominally requires input_tokens/output_tokens, but the
|
|
114
|
-
* ACP wire has no such split — the live snapshot is context occupancy only.
|
|
115
|
-
* Cast is deliberate: consumers must tolerate undefined input/output.
|
|
116
|
-
*/
|
|
117
|
-
private liveUsageSnapshot;
|
|
27
|
+
import type { RequestPermissionParams, AgentMessageChunkEvent, AgentThoughtChunkEvent, ToolCallUpdateEvent, UsageUpdateEvent } from '../../common/acp/protocol-types.js';
|
|
28
|
+
import { BaseAcpTranslator, type AcpTranslatorEvent } from '../../common/acp/base-translator.js';
|
|
29
|
+
export type { AcpTurnStartedEvent as OpencodeAcpTurnStartedEvent, AcpLiveUsage as OpencodeAcpLiveUsage, AcpUsageEvent as OpencodeAcpUsageEvent, AcpTranslatorEvent as OpencodeAcpTranslatorEvent, } from '../../common/acp/base-translator.js';
|
|
30
|
+
export declare class OpencodeAcpTranslator extends BaseAcpTranslator {
|
|
31
|
+
protected readonly logTag = "opencode-acp-translator";
|
|
32
|
+
protected handleAgentMessageChunk(event: AgentMessageChunkEvent, _sessionId: string): AcpTranslatorEvent[];
|
|
33
|
+
protected handleAgentThoughtChunk(event: AgentThoughtChunkEvent, _sessionId: string): AcpTranslatorEvent[];
|
|
34
|
+
/**
|
|
35
|
+
* opencode rawOutput is an OBJECT (tool.ts): completed →
|
|
36
|
+
* {output, metadata?}; failed → {error, metadata}; in_progress →
|
|
37
|
+
* content[] with shell output snapshot.
|
|
38
|
+
*/
|
|
39
|
+
protected extractToolResultContent(event: ToolCallUpdateEvent): unknown;
|
|
40
|
+
/** opencode usage_update also carries session cost: {cost:{amount}} (USD). */
|
|
41
|
+
protected onUsageSample(event: UsageUpdateEvent): void;
|
|
42
|
+
protected handleRequestPermission(requestId: number | string, params: RequestPermissionParams): AcpTranslatorEvent[];
|
|
118
43
|
}
|