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
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseAcpRunner: shared turn machinery for the kimi / opencode ACP runners.
|
|
3
|
+
*
|
|
4
|
+
* Both runners extend ConnectionBasedRunner and were structurally aligned
|
|
5
|
+
* (kimi's header even said so) — but session/resume→set_config→turn_started→
|
|
6
|
+
* prompt orchestration, compact orchestration, approval response dispatch,
|
|
7
|
+
* cancel/clear/release plumbing and the pendingApprovals registry were
|
|
8
|
+
* line-for-line duplicated with only constants/field names differing
|
|
9
|
+
* (2026-09 round-2 simplification). This base absorbs them; what stays in
|
|
10
|
+
* subclasses:
|
|
11
|
+
* - applyTurnSettings: mode/model下发 semantics differ (kimi unconditional,
|
|
12
|
+
* opencode conditional on cached values)
|
|
13
|
+
* - onCompactPromptFired: kimi polls wire.jsonl compaction records (§5.2,
|
|
14
|
+
* background compaction), opencode settles synchronously
|
|
15
|
+
* - shouldDeferStop: kimi defers while prompt unsettled, opencode defers
|
|
16
|
+
* until the prompt has been SENT (deliberately narrower window)
|
|
17
|
+
* - handleNotification/handleServerRequest: kimi filters terminal-backed
|
|
18
|
+
* Bash notifications and serves terminal/* reverse RPC; opencode is plain
|
|
19
|
+
* - getStatusInfo / updateApprovalMode: agent-specific extras & mode vocab
|
|
20
|
+
*/
|
|
21
|
+
import type { AgentEvent, AgentKind, AgentSessionReader, SpawnOptions } from '../../types.js';
|
|
22
|
+
import { ConnectionManager } from '../jsonrpc/connection-manager.js';
|
|
23
|
+
import { JsonRpcClient } from '../jsonrpc/client.js';
|
|
24
|
+
import { type SessionNewResult, type SessionResumeResult, type SessionPromptResult, type RequestPermissionParams } from './protocol-types.js';
|
|
25
|
+
import { type AcpPendingApproval } from './protocol-helpers.js';
|
|
26
|
+
import { ConnectionBasedRunner } from '../connection-based-runner.js';
|
|
27
|
+
import { BaseAcpTranslator, type AcpTranslatorEvent } from './base-translator.js';
|
|
28
|
+
export type { AcpTranslatorEvent } from './base-translator.js';
|
|
29
|
+
export declare abstract class BaseAcpRunner<TTranslator extends BaseAcpTranslator> extends ConnectionBasedRunner<JsonRpcClient, AcpTranslatorEvent> {
|
|
30
|
+
protected readonly connectionManager: ConnectionManager;
|
|
31
|
+
protected currentTranslator: TTranslator | null;
|
|
32
|
+
protected activeSessionId: string | null;
|
|
33
|
+
protected model?: string;
|
|
34
|
+
/** Pending approval requests: requestId → kind + view + options. */
|
|
35
|
+
protected pendingApprovals: Map<string | number, AcpPendingApproval>;
|
|
36
|
+
/** Tracker for the in-flight prompt request — needed for cancellation. */
|
|
37
|
+
protected promptSettled: boolean;
|
|
38
|
+
/** Whether the prompt request has been fired (opencode defer-stop window). */
|
|
39
|
+
protected promptSent: boolean;
|
|
40
|
+
protected constructor(opts: {
|
|
41
|
+
kind: AgentKind;
|
|
42
|
+
sessionReader: AgentSessionReader;
|
|
43
|
+
turnIdleTimeoutMs?: number;
|
|
44
|
+
}, connectionManager: ConnectionManager);
|
|
45
|
+
/** failTurn message when the ACP connection drops mid-turn. */
|
|
46
|
+
protected abstract get connectionClosedMessage(): string;
|
|
47
|
+
/** Create the per-turn translator instance. */
|
|
48
|
+
protected abstract createTranslator(): TTranslator;
|
|
49
|
+
/**
|
|
50
|
+
* Apply turn-scoped settings on a fresh session: configured model and
|
|
51
|
+
* permission/mode. kimi sends both unconditionally (fresh ACP sessions
|
|
52
|
+
* always start in 'default'), opencode skips when the cached value already
|
|
53
|
+
* matches. Failures are non-fatal (warn only).
|
|
54
|
+
*/
|
|
55
|
+
protected abstract applyTurnSettings(client: JsonRpcClient, sessionId: string): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Dispatch the compact prompt result once fired. kimi: background
|
|
58
|
+
* compaction — poll wire.jsonl for a NEW terminal record before translating
|
|
59
|
+
* (§5.2). opencode: synchronous — standard prompt-settle translation.
|
|
60
|
+
*/
|
|
61
|
+
protected abstract onCompactPromptFired(promptPromise: Promise<SessionPromptResult>, translator: TTranslator, sessionId: string, opts: SpawnOptions, baselineTotalCount: number): void;
|
|
62
|
+
protected currentSessionId(): string | null;
|
|
63
|
+
protected cancelCurrentTurn(): Promise<void>;
|
|
64
|
+
protected clearTurnState(): void;
|
|
65
|
+
protected releaseConnection(cwd: string): Promise<void>;
|
|
66
|
+
protected notifyIdle(cwd: string): void;
|
|
67
|
+
protected disposeConnections(): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Respond to an approval server request. `response` is the bridge
|
|
70
|
+
* ApprovalAction (`{ action: 'accept' | 'decline' | 'cancel' }`).
|
|
71
|
+
*/
|
|
72
|
+
respondApproval(requestId: number | string, response: unknown): Promise<void>;
|
|
73
|
+
/** Agent-specific outcome encoding (kimi: question bridge; opencode: permission). */
|
|
74
|
+
protected abstract buildApprovalOutcome(action: string, pending: AcpPendingApproval, response: unknown): unknown;
|
|
75
|
+
/** Shared tail of updateApprovalMode: re-send session/set_mode to the live session. */
|
|
76
|
+
protected sendApprovalModeUpdate(modeId: string): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Register approval_requested events yielded by the translator into the
|
|
79
|
+
* pendingApprovals map (shared tail of handleServerRequest).
|
|
80
|
+
*/
|
|
81
|
+
protected registerApprovalEvents(events: AcpTranslatorEvent[], options: RequestPermissionParams['options'], proto?: AcpPendingApproval['proto']): void;
|
|
82
|
+
/**
|
|
83
|
+
* Standard prompt-settle translation: on resolve → result event (unless
|
|
84
|
+
* stopRequested); on reject → error result (or warn when already stopped).
|
|
85
|
+
* `context` only decorates the stopRequested warn line.
|
|
86
|
+
*/
|
|
87
|
+
protected handlePromptRejected(err: unknown, translator: TTranslator, sessionId: string, context: string): void;
|
|
88
|
+
protected handlePromptResolved(result: SessionPromptResult, translator: TTranslator, sessionId: string): void;
|
|
89
|
+
/**
|
|
90
|
+
* Acquire the connection and set up the turn: session/new (new session) or
|
|
91
|
+
* session/resume (existing sessionId) → applyTurnSettings → session/prompt.
|
|
92
|
+
*/
|
|
93
|
+
protected setupTurn(message: string, opts: SpawnOptions): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Run a compact operation on the current session: resume → '/compact'
|
|
96
|
+
* prompt text (both servers intercept it) → agent-specific result dispatch.
|
|
97
|
+
*/
|
|
98
|
+
runCompact(_message: string, opts: SpawnOptions): AsyncGenerator<AgentEvent>;
|
|
99
|
+
/** After the connection is acquired per turn (kimi: record active cwd). */
|
|
100
|
+
protected onConnectionAcquired(_opts: SpawnOptions): void;
|
|
101
|
+
/** When the ACP connection drops (kimi: kill live terminals). */
|
|
102
|
+
protected onConnectionClosed(): void;
|
|
103
|
+
/** After the turn's session id is known (opencode: start log error monitor). */
|
|
104
|
+
protected onTurnSessionReady(_sessionId: string, _opts: SpawnOptions): void;
|
|
105
|
+
/** After session/new|resume resolves (opencode: track configOptions). */
|
|
106
|
+
protected onSessionEstablished(_result: SessionResumeResult | SessionNewResult): void;
|
|
107
|
+
/** Compaction baseline record count, captured before the compact prompt (kimi). */
|
|
108
|
+
protected compactBaseline(_sessionId: string, _opts: SpawnOptions): number;
|
|
109
|
+
/** Wire the active client's JSON-RPC hooks into the subclass dispatcher. */
|
|
110
|
+
protected abstract handleNotification(method: string, params: unknown): void;
|
|
111
|
+
protected abstract handleServerRequest(id: number | string, method: string, params: unknown): void;
|
|
112
|
+
/** Extra per-turn state cleanup (kimi: active cwd; opencode: error monitor). */
|
|
113
|
+
protected onClearTurnState(): void;
|
|
114
|
+
/** Extra dispose-time cleanup before connections are disposed (kimi: terminals). */
|
|
115
|
+
protected beforeDisposeConnections(): Promise<void>;
|
|
116
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { respondAcpApproval, sendAcpSetMode } from './protocol-helpers.js';
|
|
2
|
+
import { getLogger } from '../../../logger/index.js';
|
|
3
|
+
import { ConnectionBasedRunner } from '../connection-based-runner.js';
|
|
4
|
+
export class BaseAcpRunner extends ConnectionBasedRunner {
|
|
5
|
+
connectionManager;
|
|
6
|
+
currentTranslator = null;
|
|
7
|
+
activeSessionId = null;
|
|
8
|
+
model;
|
|
9
|
+
/** Pending approval requests: requestId → kind + view + options. */
|
|
10
|
+
pendingApprovals = new Map();
|
|
11
|
+
/** Tracker for the in-flight prompt request — needed for cancellation. */
|
|
12
|
+
promptSettled = false;
|
|
13
|
+
/** Whether the prompt request has been fired (opencode defer-stop window). */
|
|
14
|
+
promptSent = false;
|
|
15
|
+
constructor(opts, connectionManager) {
|
|
16
|
+
super(opts);
|
|
17
|
+
this.connectionManager = connectionManager;
|
|
18
|
+
}
|
|
19
|
+
// =========================================================================
|
|
20
|
+
// Shared plumbing
|
|
21
|
+
// =========================================================================
|
|
22
|
+
currentSessionId() {
|
|
23
|
+
return this.activeSessionId;
|
|
24
|
+
}
|
|
25
|
+
async cancelCurrentTurn() {
|
|
26
|
+
if (!this.currentClient || !this.activeSessionId)
|
|
27
|
+
return;
|
|
28
|
+
try {
|
|
29
|
+
// session/cancel is a NOTIFICATION (no id, no response) per ACP spec.
|
|
30
|
+
const cancelParams = { sessionId: this.activeSessionId };
|
|
31
|
+
this.currentClient.notify('session/cancel', cancelParams);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
getLogger().warn(`[${this.logTag}] session/cancel failed: ${err.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
clearTurnState() {
|
|
38
|
+
this.onClearTurnState();
|
|
39
|
+
this.currentTranslator = null;
|
|
40
|
+
this.promptSettled = false;
|
|
41
|
+
this.promptSent = false;
|
|
42
|
+
this.activeSessionId = null;
|
|
43
|
+
}
|
|
44
|
+
async releaseConnection(cwd) {
|
|
45
|
+
await this.connectionManager.release(cwd);
|
|
46
|
+
}
|
|
47
|
+
notifyIdle(cwd) {
|
|
48
|
+
this.connectionManager.notifyIdle(cwd);
|
|
49
|
+
}
|
|
50
|
+
async disposeConnections() {
|
|
51
|
+
await this.beforeDisposeConnections();
|
|
52
|
+
await this.connectionManager.disposeAll();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Respond to an approval server request. `response` is the bridge
|
|
56
|
+
* ApprovalAction (`{ action: 'accept' | 'decline' | 'cancel' }`).
|
|
57
|
+
*/
|
|
58
|
+
async respondApproval(requestId, response) {
|
|
59
|
+
respondAcpApproval({
|
|
60
|
+
client: this.currentClient,
|
|
61
|
+
pendingApprovals: this.pendingApprovals,
|
|
62
|
+
requestId,
|
|
63
|
+
response,
|
|
64
|
+
logTag: this.logTag,
|
|
65
|
+
buildOutcome: (action, pending, resp) => this.buildApprovalOutcome(action, pending, resp),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** Shared tail of updateApprovalMode: re-send session/set_mode to the live session. */
|
|
69
|
+
async sendApprovalModeUpdate(modeId) {
|
|
70
|
+
await sendAcpSetMode({
|
|
71
|
+
client: this.currentClient,
|
|
72
|
+
activeSessionId: this.activeSessionId,
|
|
73
|
+
modeId,
|
|
74
|
+
logTag: this.logTag,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Register approval_requested events yielded by the translator into the
|
|
79
|
+
* pendingApprovals map (shared tail of handleServerRequest).
|
|
80
|
+
*/
|
|
81
|
+
registerApprovalEvents(events, options, proto) {
|
|
82
|
+
for (const ev of events) {
|
|
83
|
+
if (ev.type === 'approval_requested') {
|
|
84
|
+
this.pendingApprovals.set(ev.requestId, {
|
|
85
|
+
kind: ev.kind,
|
|
86
|
+
view: ev.view,
|
|
87
|
+
options,
|
|
88
|
+
...(proto ? { proto } : {}),
|
|
89
|
+
});
|
|
90
|
+
getLogger().info(`[${this.logTag}] approval requested requestId=${ev.requestId} kind=${ev.kind}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Standard prompt-settle translation: on resolve → result event (unless
|
|
96
|
+
* stopRequested); on reject → error result (or warn when already stopped).
|
|
97
|
+
* `context` only decorates the stopRequested warn line.
|
|
98
|
+
*/
|
|
99
|
+
handlePromptRejected(err, translator, sessionId, context) {
|
|
100
|
+
this.promptSettled = true;
|
|
101
|
+
if (this.stopRequested) {
|
|
102
|
+
getLogger().warn(`[${this.logTag}] ${context} prompt rejected after stopRequested: ${err.message}`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const resultEvent = translator.produceErrorResult(sessionId, err.message);
|
|
106
|
+
this.pushEvents([resultEvent]);
|
|
107
|
+
}
|
|
108
|
+
handlePromptResolved(result, translator, sessionId) {
|
|
109
|
+
this.promptSettled = true;
|
|
110
|
+
if (this.stopRequested)
|
|
111
|
+
return; // already cancelled
|
|
112
|
+
const resultEvent = translator.handlePromptResponse(sessionId, result);
|
|
113
|
+
this.pushEvents([resultEvent]);
|
|
114
|
+
}
|
|
115
|
+
// =========================================================================
|
|
116
|
+
// Turn orchestration
|
|
117
|
+
// =========================================================================
|
|
118
|
+
/**
|
|
119
|
+
* Acquire the connection and set up the turn: session/new (new session) or
|
|
120
|
+
* session/resume (existing sessionId) → applyTurnSettings → session/prompt.
|
|
121
|
+
*/
|
|
122
|
+
async setupTurn(message, opts) {
|
|
123
|
+
const client = await this.connectionManager.acquire(opts.cwd);
|
|
124
|
+
this.connectionManager.notifyActivity(opts.cwd);
|
|
125
|
+
this.currentClient = client;
|
|
126
|
+
this.onConnectionAcquired(opts);
|
|
127
|
+
client.setHooks({
|
|
128
|
+
onNotification: (method, params) => this.handleNotification(method, params),
|
|
129
|
+
onServerRequest: (id, method, params) => this.handleServerRequest(id, method, params),
|
|
130
|
+
onClose: () => {
|
|
131
|
+
getLogger().warn(`[${this.logTag}] client connection closed`);
|
|
132
|
+
this.onConnectionClosed();
|
|
133
|
+
this.failTurn(this.connectionClosedMessage);
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
let sessionId;
|
|
137
|
+
if (opts.sessionId) {
|
|
138
|
+
const resumeParams = {
|
|
139
|
+
sessionId: opts.sessionId,
|
|
140
|
+
cwd: opts.cwd,
|
|
141
|
+
};
|
|
142
|
+
const resumeResult = await client.request('session/resume', resumeParams);
|
|
143
|
+
this.onSessionEstablished(resumeResult);
|
|
144
|
+
sessionId = opts.sessionId;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
const newParams = { cwd: opts.cwd, mcpServers: [] };
|
|
148
|
+
const newResult = await client.request('session/new', newParams);
|
|
149
|
+
this.onSessionEstablished(newResult);
|
|
150
|
+
sessionId = newResult.sessionId;
|
|
151
|
+
}
|
|
152
|
+
this.activeSessionId = sessionId;
|
|
153
|
+
this.onTurnSessionReady(sessionId, opts);
|
|
154
|
+
await this.applyTurnSettings(client, sessionId);
|
|
155
|
+
const translator = this.createTranslator();
|
|
156
|
+
this.currentTranslator = translator;
|
|
157
|
+
// Emit turn_started before prompt
|
|
158
|
+
const turnId = `turn-${Date.now()}`;
|
|
159
|
+
const turnStarted = translator.produceTurnStarted(sessionId, turnId);
|
|
160
|
+
this.currentTurnId = turnId;
|
|
161
|
+
this.pushEvents([turnStarted]);
|
|
162
|
+
// Send the prompt. The prompt request races with notifications (the
|
|
163
|
+
// server streams session/update before the prompt response); the
|
|
164
|
+
// response settles the turn and is translated into the result event.
|
|
165
|
+
const promptParams = {
|
|
166
|
+
sessionId,
|
|
167
|
+
prompt: [{ type: 'text', text: message }],
|
|
168
|
+
};
|
|
169
|
+
this.promptSettled = false;
|
|
170
|
+
const promptPromise = client.request('session/prompt', promptParams, Number.POSITIVE_INFINITY);
|
|
171
|
+
this.promptSent = true;
|
|
172
|
+
promptPromise.then((result) => this.handlePromptResolved(result, translator, sessionId), (err) => this.handlePromptRejected(err, translator, sessionId, ''));
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Run a compact operation on the current session: resume → '/compact'
|
|
176
|
+
* prompt text (both servers intercept it) → agent-specific result dispatch.
|
|
177
|
+
*/
|
|
178
|
+
async *runCompact(_message, opts) {
|
|
179
|
+
yield* this.executeTurn(opts, async () => {
|
|
180
|
+
const client = await this.connectionManager.acquire(opts.cwd);
|
|
181
|
+
this.connectionManager.notifyActivity(opts.cwd);
|
|
182
|
+
this.currentClient = client;
|
|
183
|
+
this.onConnectionAcquired(opts);
|
|
184
|
+
client.setHooks({
|
|
185
|
+
onNotification: (method, params) => this.handleNotification(method, params),
|
|
186
|
+
onServerRequest: (id, method, params) => this.handleServerRequest(id, method, params),
|
|
187
|
+
onClose: () => {
|
|
188
|
+
this.onConnectionClosed();
|
|
189
|
+
this.failTurn(this.connectionClosedMessage);
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
const sessionId = opts.sessionId;
|
|
193
|
+
if (!sessionId) {
|
|
194
|
+
throw new Error('compact requires a sessionId');
|
|
195
|
+
}
|
|
196
|
+
this.activeSessionId = sessionId;
|
|
197
|
+
this.onTurnSessionReady(sessionId, opts);
|
|
198
|
+
// Cold-connection fallback: session/resume loads the session into
|
|
199
|
+
// memory. On a reused connection the session is already loaded —
|
|
200
|
+
// resume is idempotent and harmless.
|
|
201
|
+
const resumeParams = { sessionId, cwd: opts.cwd };
|
|
202
|
+
const resumeResult = await client.request('session/resume', resumeParams);
|
|
203
|
+
this.onSessionEstablished(resumeResult);
|
|
204
|
+
const translator = this.createTranslator();
|
|
205
|
+
translator.setOperationKind('compact');
|
|
206
|
+
this.currentTranslator = translator;
|
|
207
|
+
// Emit turn_started with operationKind='compaction' before the prompt.
|
|
208
|
+
const turnId = `compact-${Date.now()}`;
|
|
209
|
+
const turnStarted = translator.produceTurnStarted(sessionId, turnId);
|
|
210
|
+
this.currentTurnId = turnId;
|
|
211
|
+
this.pushEvents([turnStarted]);
|
|
212
|
+
const promptParams = {
|
|
213
|
+
sessionId,
|
|
214
|
+
prompt: [{ type: 'text', text: '/compact' }],
|
|
215
|
+
};
|
|
216
|
+
// Baseline is captured BEFORE the prompt fires (kimi §5.2: a begin
|
|
217
|
+
// record landing after the fire must count as "new activity").
|
|
218
|
+
const baselineTotalCount = this.compactBaseline(sessionId, opts);
|
|
219
|
+
this.promptSettled = false;
|
|
220
|
+
// ACP holds the session/prompt response for the ENTIRE turn, so the
|
|
221
|
+
// RPC-level timeout must not apply — turn liveness is guarded by the
|
|
222
|
+
// rolling idle watchdog instead.
|
|
223
|
+
const promptPromise = client.request('session/prompt', promptParams, Number.POSITIVE_INFINITY);
|
|
224
|
+
this.promptSent = true;
|
|
225
|
+
this.onCompactPromptFired(promptPromise, translator, sessionId, opts, baselineTotalCount);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
// =========================================================================
|
|
229
|
+
// Optional hooks (defaults: no-op)
|
|
230
|
+
// =========================================================================
|
|
231
|
+
/** After the connection is acquired per turn (kimi: record active cwd). */
|
|
232
|
+
onConnectionAcquired(_opts) { }
|
|
233
|
+
/** When the ACP connection drops (kimi: kill live terminals). */
|
|
234
|
+
onConnectionClosed() { }
|
|
235
|
+
/** After the turn's session id is known (opencode: start log error monitor). */
|
|
236
|
+
onTurnSessionReady(_sessionId, _opts) { }
|
|
237
|
+
/** After session/new|resume resolves (opencode: track configOptions). */
|
|
238
|
+
onSessionEstablished(_result) { }
|
|
239
|
+
/** Compaction baseline record count, captured before the compact prompt (kimi). */
|
|
240
|
+
compactBaseline(_sessionId, _opts) {
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
/** Extra per-turn state cleanup (kimi: active cwd; opencode: error monitor). */
|
|
244
|
+
onClearTurnState() { }
|
|
245
|
+
/** Extra dispose-time cleanup before connections are disposed (kimi: terminals). */
|
|
246
|
+
async beforeDisposeConnections() { }
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=base-acp-runner.js.map
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseAcpTranslator: shared skeleton for the kimi / opencode ACP translators.
|
|
3
|
+
*
|
|
4
|
+
* Both agents' session/update notification envelopes, prompt-response
|
|
5
|
+
* stopReason mapping, tool_call/tool_call_update normalization, usage_update
|
|
6
|
+
* occupancy bookkeeping and self-produced turn_started events are
|
|
7
|
+
* line-for-line identical — extracted here in the 2026-09 round-2
|
|
8
|
+
* simplification. What genuinely diverges is the content channel:
|
|
9
|
+
* kimi accumulates wire deltas into turn_diff snapshots (snapshot semantics,
|
|
10
|
+
* see KimiAcpTranslator header), opencode forwards `assistant` deltas
|
|
11
|
+
* directly (delta semantics) — those chunk handlers stay abstract.
|
|
12
|
+
*
|
|
13
|
+
* Extension points (all no-op or passthrough by default):
|
|
14
|
+
* handleAgentMessageChunk / handleAgentThoughtChunk — content channel (abstract)
|
|
15
|
+
* handleRequestPermission — approval rendering (abstract)
|
|
16
|
+
* handleOtherServerRequest — extra reverse RPC (kimi: elicitation)
|
|
17
|
+
* extractToolResultContent — tool_result content (default: rawOutput)
|
|
18
|
+
* onUsageSample — extra usage bookkeeping (opencode: cost)
|
|
19
|
+
*/
|
|
20
|
+
import type { AgentEvent, ApprovalRequestedEvent, ResultEvent } from '../../types.js';
|
|
21
|
+
import { type AgentMessageChunkEvent, type AgentThoughtChunkEvent, type ToolCallEvent, type ToolCallUpdateEvent, type UsageUpdateEvent, type RequestPermissionParams, type AcpPlanEvent } from './protocol-types.js';
|
|
22
|
+
/** Self-produced turn_started event (no wire equivalent — produced on prompt start). */
|
|
23
|
+
export interface AcpTurnStartedEvent {
|
|
24
|
+
type: 'turn_started';
|
|
25
|
+
threadId: string;
|
|
26
|
+
turnId: string;
|
|
27
|
+
operationKind: 'turn' | 'compaction';
|
|
28
|
+
timestamp?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Live usage snapshot from usage_update. The ACP wire carries context
|
|
32
|
+
* occupancy (`used`) and the model context window (`size`) — there is NO
|
|
33
|
+
* input/output token split. input_tokens/output_tokens stay undefined so the
|
|
34
|
+
* bridge falls back to per-agent transcript readback for cumulative stats.
|
|
35
|
+
*/
|
|
36
|
+
export interface AcpLiveUsage {
|
|
37
|
+
total_tokens?: number;
|
|
38
|
+
context_limit?: number;
|
|
39
|
+
input_tokens?: number;
|
|
40
|
+
output_tokens?: number;
|
|
41
|
+
}
|
|
42
|
+
/** Usage update event for live token display. */
|
|
43
|
+
export interface AcpUsageEvent {
|
|
44
|
+
type: 'usage';
|
|
45
|
+
usage: AcpLiveUsage;
|
|
46
|
+
timestamp?: string;
|
|
47
|
+
}
|
|
48
|
+
export type AcpTranslatorEvent = AgentEvent | AcpTurnStartedEvent | AcpUsageEvent | ApprovalRequestedEvent;
|
|
49
|
+
export declare abstract class BaseAcpTranslator {
|
|
50
|
+
/** Logger tag, e.g. 'kimi-acp-translator'. */
|
|
51
|
+
protected abstract readonly logTag: string;
|
|
52
|
+
/** Current operation kind (turn vs compact). */
|
|
53
|
+
private operationKind;
|
|
54
|
+
/** Live usage snapshot from usage_update events (context occupancy only). */
|
|
55
|
+
protected liveUsage: AcpLiveUsage;
|
|
56
|
+
/** Whether any usage_update has been seen this turn (no update → no usage). */
|
|
57
|
+
protected hasLiveUsage: boolean;
|
|
58
|
+
/** Session cost from usage_update cost.amount (USD). Written by opencode only. */
|
|
59
|
+
protected liveCostUsd?: number;
|
|
60
|
+
/** Current turn id, set by produceTurnStarted. */
|
|
61
|
+
protected currentTurnId: string;
|
|
62
|
+
/**
|
|
63
|
+
* Handle a notification from the ACP server and return translated events.
|
|
64
|
+
* Wire envelope: {sessionId, update: {sessionUpdate: '<kind>', ...}} — the
|
|
65
|
+
* discriminator is `update.sessionUpdate`.
|
|
66
|
+
*/
|
|
67
|
+
handleNotification(method: string, params: unknown): AcpTranslatorEvent[];
|
|
68
|
+
/**
|
|
69
|
+
* Handle a server request (reverse RPC from the ACP server). Subclasses
|
|
70
|
+
* hook additional methods via handleOtherServerRequest.
|
|
71
|
+
*/
|
|
72
|
+
handleServerRequest(id: number | string, method: string, params: unknown): AcpTranslatorEvent[];
|
|
73
|
+
/** Hook for server requests other than session/request_permission. */
|
|
74
|
+
protected handleOtherServerRequest(_id: number | string, _method: string, _params: unknown): AcpTranslatorEvent[];
|
|
75
|
+
/**
|
|
76
|
+
* Produce a turn_started event. Called by the runner when session/prompt
|
|
77
|
+
* is sent (no wire notification for this — self-produced).
|
|
78
|
+
*/
|
|
79
|
+
produceTurnStarted(sessionId: string, turnId: string): AcpTurnStartedEvent;
|
|
80
|
+
/**
|
|
81
|
+
* Translate a prompt response into a result event.
|
|
82
|
+
* stopReason: end_turn → success; cancelled → interrupted (independent
|
|
83
|
+
* terminal state, must NOT merge into error); anything else
|
|
84
|
+
* (max_tokens / refusal / …) → error.
|
|
85
|
+
*/
|
|
86
|
+
handlePromptResponse(sessionId: string, result: {
|
|
87
|
+
stopReason: string;
|
|
88
|
+
}): AgentEvent;
|
|
89
|
+
/**
|
|
90
|
+
* Produce an error result event (for JSON-RPC errors during prompt).
|
|
91
|
+
*/
|
|
92
|
+
produceErrorResult(sessionId: string, errorMessage: string): AgentEvent;
|
|
93
|
+
/**
|
|
94
|
+
* Set the operation kind (turn vs compact).
|
|
95
|
+
*/
|
|
96
|
+
setOperationKind(kind: 'turn' | 'compact'): void;
|
|
97
|
+
/**
|
|
98
|
+
* plan update → PlanEvent(信息保真 C3.2:不再显式丢弃)。
|
|
99
|
+
* ACP PlanEntry: { content, priority, status }(status: pending/in_progress/
|
|
100
|
+
* completed)。基类具体方法:kimi/opencode wire 结构相同,直接复用。
|
|
101
|
+
*/
|
|
102
|
+
protected handlePlan(event: AcpPlanEvent): AcpTranslatorEvent[];
|
|
103
|
+
/**
|
|
104
|
+
* tool_call → assistant/tool_use. rawInput normalization is contractual:
|
|
105
|
+
* missing → {} (lazy-create tool_call carries no rawInput field), string →
|
|
106
|
+
* JSON.parse (older agents send JSON strings) — keeps the card from
|
|
107
|
+
* crashing on undefined input.
|
|
108
|
+
*
|
|
109
|
+
* 信息保真 C3.1:kind 命中 KIND_TO_TOOL 时用映射名渲染结构化面板;
|
|
110
|
+
* title 与 name 不同时把 title 存到 content block 的 summary 字段
|
|
111
|
+
* (渲染层做面板副标题)。
|
|
112
|
+
*/
|
|
113
|
+
protected handleToolCall(event: ToolCallEvent, _sessionId: string): AcpTranslatorEvent[];
|
|
114
|
+
/**
|
|
115
|
+
* tool_call_update → user/tool_result. status:'failed' → is_error:true.
|
|
116
|
+
*/
|
|
117
|
+
protected handleToolCallUpdate(event: ToolCallUpdateEvent, _sessionId: string): AcpTranslatorEvent[];
|
|
118
|
+
/**
|
|
119
|
+
* tool_result content extraction. Default: rawOutput passthrough (kimi
|
|
120
|
+
* semantics). opencode overrides — its rawOutput is an OBJECT
|
|
121
|
+
* ({output} / {error}).
|
|
122
|
+
*/
|
|
123
|
+
protected extractToolResultContent(event: ToolCallUpdateEvent): unknown;
|
|
124
|
+
/**
|
|
125
|
+
* usage_update → context occupancy bookkeeping. The wire shape is
|
|
126
|
+
* {used, size} (+ opencode: cost:{amount}) — context occupancy, not
|
|
127
|
+
* cumulative tokens.
|
|
128
|
+
*/
|
|
129
|
+
protected handleUsageUpdate(event: UsageUpdateEvent): AcpTranslatorEvent[];
|
|
130
|
+
/** Extra per-sample usage bookkeeping (opencode: session cost amount). */
|
|
131
|
+
protected onUsageSample(_event: UsageUpdateEvent): void;
|
|
132
|
+
/**
|
|
133
|
+
* ResultEvent.usage requires input_tokens/output_tokens: number, but the
|
|
134
|
+
* ACP wire has no such split — the live snapshot is context occupancy only.
|
|
135
|
+
* Cast is deliberate: consumers must tolerate undefined input/output.
|
|
136
|
+
*/
|
|
137
|
+
protected liveUsageSnapshot(): ResultEvent['usage'];
|
|
138
|
+
protected abstract handleAgentMessageChunk(event: AgentMessageChunkEvent, sessionId: string): AcpTranslatorEvent[];
|
|
139
|
+
protected abstract handleAgentThoughtChunk(event: AgentThoughtChunkEvent, sessionId: string): AcpTranslatorEvent[];
|
|
140
|
+
protected abstract handleRequestPermission(requestId: number | string, params: RequestPermissionParams): AcpTranslatorEvent[];
|
|
141
|
+
}
|