lark-remote 0.1.13 → 0.2.1

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.
Files changed (132) hide show
  1. package/CHANGELOG.md +36 -3
  2. package/README.en.md +7 -11
  3. package/README.md +7 -11
  4. package/dist/bridge/final-usage.d.ts +24 -0
  5. package/dist/bridge/final-usage.js +55 -0
  6. package/dist/bridge/index.d.ts +0 -7
  7. package/dist/bridge/index.js +22 -64
  8. package/dist/bridge/queue-manager.js +4 -3
  9. package/dist/card/approval-render.js +39 -94
  10. package/dist/card/bash-renderer.js +12 -21
  11. package/dist/card/card-shared.d.ts +13 -1
  12. package/dist/card/card-shared.js +21 -30
  13. package/dist/card/run-renderer.d.ts +0 -22
  14. package/dist/card/run-renderer.js +206 -153
  15. package/dist/card/run-state.d.ts +29 -1
  16. package/dist/card/run-state.js +172 -84
  17. package/dist/card/text-truncate.d.ts +0 -9
  18. package/dist/card/text-truncate.js +0 -9
  19. package/dist/card/tool-render.d.ts +4 -1
  20. package/dist/card/tool-render.js +76 -32
  21. package/dist/cli.js +2 -2
  22. package/dist/common/fs.d.ts +14 -0
  23. package/dist/common/fs.js +57 -3
  24. package/dist/config/codex-config.js +2 -0
  25. package/dist/config/index.d.ts +0 -5
  26. package/dist/config/index.js +0 -11
  27. package/dist/config/kimi-config.js +1 -0
  28. package/dist/config/opencode-config.js +13 -2
  29. package/dist/config/qr.d.ts +36 -0
  30. package/dist/config/qr.js +124 -0
  31. package/dist/config/wizard.js +69 -27
  32. package/dist/connector/index.d.ts +3 -2
  33. package/dist/connector/index.js +5 -4
  34. package/dist/index.js +15 -42
  35. package/dist/instance-lock.js +7 -0
  36. package/dist/persistence/atomic-write.d.ts +2 -0
  37. package/dist/persistence/atomic-write.js +20 -5
  38. package/dist/persistence/load-json-file.d.ts +1 -2
  39. package/dist/persistence/load-json-file.js +2 -3
  40. package/dist/platform/agent-stopper.d.ts +22 -0
  41. package/dist/platform/agent-stopper.js +19 -0
  42. package/dist/platform/command.d.ts +11 -0
  43. package/dist/platform/command.js +107 -0
  44. package/dist/platform/identity.d.ts +33 -0
  45. package/dist/platform/identity.js +187 -0
  46. package/dist/platform/path.d.ts +24 -0
  47. package/dist/platform/path.js +110 -0
  48. package/dist/platform/select.d.ts +9 -0
  49. package/dist/platform/select.js +12 -0
  50. package/dist/platform/shell.d.ts +51 -0
  51. package/dist/platform/shell.js +75 -0
  52. package/dist/platform/spawn.d.ts +41 -0
  53. package/dist/platform/spawn.js +77 -0
  54. package/dist/platform/terminator-posix.d.ts +38 -0
  55. package/dist/platform/terminator-posix.js +103 -0
  56. package/dist/platform/terminator-win32.d.ts +21 -0
  57. package/dist/platform/terminator-win32.js +110 -0
  58. package/dist/platform/terminator.d.ts +44 -0
  59. package/dist/platform/terminator.js +24 -0
  60. package/dist/platform/types.d.ts +23 -0
  61. package/dist/platform/types.js +5 -0
  62. package/dist/restart.js +4 -2
  63. package/dist/router/card-helpers.d.ts +33 -6
  64. package/dist/router/card-helpers.js +67 -11
  65. package/dist/router/config/common/render.d.ts +1 -1
  66. package/dist/router/config/common/render.js +1 -1
  67. package/dist/router/config-diff.d.ts +4 -0
  68. package/dist/router/config-diff.js +70 -0
  69. package/dist/router/index.d.ts +66 -6
  70. package/dist/router/index.js +451 -316
  71. package/dist/router/utils.d.ts +6 -0
  72. package/dist/router/utils.js +12 -0
  73. package/dist/runner/bash/runner.d.ts +9 -2
  74. package/dist/runner/bash/runner.js +45 -20
  75. package/dist/runner/claude/session.d.ts +4 -1
  76. package/dist/runner/claude/session.js +18 -4
  77. package/dist/runner/codex/app-server/protocol-types.d.ts +35 -1
  78. package/dist/runner/codex/app-server/translator.d.ts +18 -0
  79. package/dist/runner/codex/app-server/translator.js +146 -0
  80. package/dist/runner/common/acp/base-acp-runner.d.ts +116 -0
  81. package/dist/runner/common/acp/base-acp-runner.js +248 -0
  82. package/dist/runner/common/acp/base-translator.d.ts +141 -0
  83. package/dist/runner/common/acp/base-translator.js +271 -0
  84. package/dist/runner/common/acp/protocol-helpers.d.ts +39 -0
  85. package/dist/runner/common/acp/protocol-helpers.js +34 -0
  86. package/dist/runner/common/jsonrpc/transport.d.ts +2 -0
  87. package/dist/runner/common/jsonrpc/transport.js +25 -5
  88. package/dist/runner/common/process-stopper.d.ts +4 -26
  89. package/dist/runner/common/process-stopper.js +4 -76
  90. package/dist/runner/common/runner-utils.d.ts +0 -5
  91. package/dist/runner/common/runner-utils.js +0 -7
  92. package/dist/runner/common/spawning-runner.d.ts +42 -132
  93. package/dist/runner/common/spawning-runner.js +52 -271
  94. package/dist/runner/common/usage.d.ts +2 -0
  95. package/dist/runner/common/usage.js +2 -0
  96. package/dist/runner/index.d.ts +1 -1
  97. package/dist/runner/kimi/acp/runner.d.ts +45 -42
  98. package/dist/runner/kimi/acp/runner.js +140 -293
  99. package/dist/runner/kimi/acp/translator.d.ts +18 -100
  100. package/dist/runner/kimi/acp/translator.js +56 -242
  101. package/dist/runner/opencode/acp/runner.d.ts +22 -46
  102. package/dist/runner/opencode/acp/runner.js +73 -256
  103. package/dist/runner/opencode/acp/translator.d.ts +28 -103
  104. package/dist/runner/opencode/acp/translator.js +51 -236
  105. package/dist/runner/pi/rpc/translator.d.ts +3 -0
  106. package/dist/runner/pi/rpc/translator.js +47 -9
  107. package/dist/runner/probe.d.ts +8 -11
  108. package/dist/runner/probe.js +17 -21
  109. package/dist/runner/registry.d.ts +4 -4
  110. package/dist/runner/registry.js +4 -4
  111. package/dist/runner/types.d.ts +38 -1
  112. package/dist/session/claude/sessions.js +35 -58
  113. package/dist/session/codex/rollout-reader.d.ts +10 -0
  114. package/dist/session/codex/rollout-reader.js +123 -41
  115. package/dist/session/common/constants.d.ts +7 -0
  116. package/dist/session/common/constants.js +9 -0
  117. package/dist/session/common/two-pass.d.ts +22 -0
  118. package/dist/session/common/two-pass.js +70 -0
  119. package/dist/session/dsh/sessions.js +7 -6
  120. package/dist/session/kimi/sessions.d.ts +6 -0
  121. package/dist/session/kimi/sessions.js +50 -73
  122. package/dist/session/opencode/sessions.js +26 -8
  123. package/dist/session/pi/sessions.js +54 -73
  124. package/dist/update/index.d.ts +1 -2
  125. package/dist/update/index.js +1 -2
  126. package/dist/update/install.d.ts +2 -1
  127. package/dist/update/install.js +58 -9
  128. package/dist/update/version-check.d.ts +5 -0
  129. package/dist/update/version-check.js +9 -0
  130. package/package.json +9 -6
  131. package/dist/update/startup-hint.d.ts +0 -5
  132. package/dist/update/startup-hint.js +0 -11
@@ -2,8 +2,11 @@
2
2
  * KimiAcpTranslator: translates ACP session/update notifications and
3
3
  * session/request_permission server requests into AgentEvents.
4
4
  *
5
- * Per-turn stateful: accumulates text/thinking deltas, tracks tool calls,
6
- * and produces turn_started events with operationKind.
5
+ * Extends BaseAcpTranslator (shared envelope dispatch, prompt-response
6
+ * stopReason mapping, tool_call normalization, usage occupancy). Kimi's
7
+ * divergence from opencode is the content channel: per-turn stateful —
8
+ * accumulates text/thinking deltas and emits turn_diff snapshots (NOT
9
+ * assistant deltas — see handleAgentMessageChunk), plus question elicitation.
7
10
  *
8
11
  * Wire envelope (R1, source: kimi-code packages/acp-server/src/events-map.ts,
9
12
  * verified live 2026-08-15/16 against kimi 0.36.0):
@@ -13,74 +16,28 @@
13
16
  * Event mapping (design doc §4.2):
14
17
  * agent_message_chunk → turn_diff text snapshot (content.text accumulation)
15
18
  * agent_thought_chunk → turn_diff reasoning snapshot (content.text accumulation)
16
- * tool_call → assistant/tool_use (rawInput object passthrough,
17
- * string → JSON.parse fallback)
18
- * tool_call_update → user/tool_result (status:'failed' → is_error:true)
19
- * plan / available_commands_update / session_info_update /
20
- * current_mode_update / config_option_update → discard
21
- * usage_update → live context occupancy {used, size} →
22
- * {total_tokens, context_limit}; NO input/output
23
- * split exists on the wire — cumulative token stats
24
- * fall back to wire.jsonl usage.record (dual path)
19
+ * tool_call / tool_call_update / usage_update / plan / control-plane noise →
20
+ * base class (BaseAcpTranslator)
25
21
  * prompt stopReason:'end_turn' → result success
26
22
  * prompt stopReason:'cancelled' → result interrupted (独立终态)
27
23
  * prompt other stopReason/error → result error
28
24
  * turn_started (self-produced) → operationKind 'turn'/'compaction'
29
- * session/request_permission → approval_requested
25
+ * session/request_permission → approval_requested(含 question 桥)
26
+ * elicitation/create → question approval event
30
27
  */
31
- import type { AgentEvent } from '../../types.js';
32
- import type { ApprovalRequestedEvent } from '../../types.js';
33
- /** Self-produced turn_started event (no wire equivalent — produced on prompt start). */
34
- export interface AcpTurnStartedEvent {
35
- type: 'turn_started';
36
- threadId: string;
37
- turnId: string;
38
- operationKind: 'turn' | 'compaction';
39
- timestamp?: string;
40
- }
41
- /**
42
- * Live usage snapshot from usage_update. The wire only carries context
43
- * occupancy (`used`) and the model context window (`size`) — there is NO
44
- * input/output token split (events-map.ts:505-516). input_tokens/output_tokens
45
- * stay undefined so the bridge falls back to wire.jsonl usage.record for
46
- * cumulative token stats (R1 dual path).
47
- */
48
- export interface AcpLiveUsage {
49
- total_tokens?: number;
50
- context_limit?: number;
51
- input_tokens?: number;
52
- output_tokens?: number;
53
- }
54
- /** Usage update event for live token display. */
55
- export interface AcpUsageEvent {
56
- type: 'usage';
57
- usage: AcpLiveUsage;
58
- timestamp?: string;
59
- }
60
- export type AcpTranslatorEvent = AgentEvent | AcpTurnStartedEvent | AcpUsageEvent | ApprovalRequestedEvent;
61
- export declare class KimiAcpTranslator {
28
+ import { type RequestPermissionParams, type AgentMessageChunkEvent, type AgentThoughtChunkEvent } from '../../common/acp/protocol-types.js';
29
+ import { BaseAcpTranslator, type AcpTranslatorEvent } from '../../common/acp/base-translator.js';
30
+ export type { AcpTurnStartedEvent, AcpLiveUsage, AcpUsageEvent, AcpTranslatorEvent, } from '../../common/acp/base-translator.js';
31
+ export declare class KimiAcpTranslator extends BaseAcpTranslator {
32
+ protected readonly logTag = "kimi-acp-translator";
62
33
  /** Accumulated text per tool-call or message (keyed by a logical item id). */
63
34
  private textByItem;
64
35
  /** Accumulated thinking per item. */
65
36
  private thinkingByItem;
66
- /** Pending tool calls keyed by toolCallId (for matching tool_call_update). */
67
- private pendingToolCalls;
68
- /** Current operation kind (turn vs compact). */
69
- private operationKind;
70
- /** Live usage snapshot from usage_update events (context occupancy only). */
71
- private liveUsage;
72
- /** Whether any usage_update has been seen this turn (no update → no usage). */
73
- private hasLiveUsage;
74
- /** Current turn id, set by produceTurnStarted; carried on turn_diff events. */
75
- private currentTurnId;
76
- /**
77
- * Handle a notification from the ACP server and return translated events.
78
- */
79
- handleNotification(method: string, params: unknown): AcpTranslatorEvent[];
80
- /**
81
- * Handle a server request (reverse RPC from the ACP server).
82
- */
83
- handleServerRequest(id: number | string, method: string, params: unknown): AcpTranslatorEvent[];
37
+ protected handleAgentMessageChunk(event: AgentMessageChunkEvent, sessionId: string): AcpTranslatorEvent[];
38
+ protected handleAgentThoughtChunk(event: AgentThoughtChunkEvent, sessionId: string): AcpTranslatorEvent[];
39
+ protected handleRequestPermission(requestId: number | string, params: RequestPermissionParams): AcpTranslatorEvent[];
40
+ protected handleOtherServerRequest(requestId: number | string, method: string, params: unknown): AcpTranslatorEvent[];
84
41
  /**
85
42
  * Kimi elicitation form:requestedSchema.properties 按顺序解析为 UserQuestion[]
86
43
  * (type:array → 多选;oneOf/anyOf const → 选项 label;title → header??question)。
@@ -89,49 +46,10 @@ export declare class KimiAcpTranslator {
89
46
  * form 会丢弃非声明选项值 → isOther=false(卡片隐藏自定义答案输入)。
90
47
  */
91
48
  private handleElicitationCreate;
92
- /**
93
- * Produce a turn_started event. Called by the runner when session/prompt
94
- * is sent (no wire notification for this — self-produced).
95
- */
96
- produceTurnStarted(sessionId: string, turnId: string): AcpTurnStartedEvent;
97
- /**
98
- * Translate a prompt response into a result event. Carries the live usage
99
- * snapshot only when a usage_update was seen (otherwise undefined, so the
100
- * bridge falls back to wire.jsonl usage.record readback).
101
- */
102
- handlePromptResponse(sessionId: string, result: {
103
- stopReason: string;
104
- }): AgentEvent;
105
- /**
106
- * Produce an error result event (for JSON-RPC errors during prompt).
107
- */
108
- produceErrorResult(sessionId: string, errorMessage: string): AgentEvent;
109
- /**
110
- * Set the operation kind (turn vs compact).
111
- */
112
- setOperationKind(kind: 'turn' | 'compact'): void;
113
- private handleAgentMessageChunk;
114
- private handleAgentThoughtChunk;
115
- private handleToolCall;
116
- private handleToolCallUpdate;
117
- private handleUsageUpdate;
118
- private handleRequestPermission;
119
49
  /**
120
50
  * request_permission 兜底桥(elicitation/create 失败或旧版 kimi):
121
51
  * 选项为 q{n}_opt_{i}(allow_once,label 直取 name)+ q{n}_skip(reject_once,
122
52
  * 跳过语义)。单题单选、无 Other(kimi 桥丢弃非声明值)。
123
53
  */
124
54
  private handlePermissionQuestion;
125
- /**
126
- * Derive the approval decision list from the server's offered options.
127
- * accept/decline/cancel are universal; acceptForSession requires an
128
- * always-class option (kimi `approve_always`, opencode `allow_always`).
129
- */
130
- /**
131
- * ResultEvent.usage requires input_tokens/output_tokens: number, but the
132
- * ACP wire has no such split — the live snapshot is context occupancy only.
133
- * Cast is deliberate: consumers must tolerate undefined input/output (the
134
- * bridge's kimi path already falls back to wire.jsonl readback).
135
- */
136
- private liveUsageSnapshot;
137
55
  }
@@ -2,8 +2,11 @@
2
2
  * KimiAcpTranslator: translates ACP session/update notifications and
3
3
  * session/request_permission server requests into AgentEvents.
4
4
  *
5
- * Per-turn stateful: accumulates text/thinking deltas, tracks tool calls,
6
- * and produces turn_started events with operationKind.
5
+ * Extends BaseAcpTranslator (shared envelope dispatch, prompt-response
6
+ * stopReason mapping, tool_call normalization, usage occupancy). Kimi's
7
+ * divergence from opencode is the content channel: per-turn stateful —
8
+ * accumulates text/thinking deltas and emits turn_diff snapshots (NOT
9
+ * assistant deltas — see handleAgentMessageChunk), plus question elicitation.
7
10
  *
8
11
  * Wire envelope (R1, source: kimi-code packages/acp-server/src/events-map.ts,
9
12
  * verified live 2026-08-15/16 against kimi 0.36.0):
@@ -13,25 +16,20 @@
13
16
  * Event mapping (design doc §4.2):
14
17
  * agent_message_chunk → turn_diff text snapshot (content.text accumulation)
15
18
  * agent_thought_chunk → turn_diff reasoning snapshot (content.text accumulation)
16
- * tool_call → assistant/tool_use (rawInput object passthrough,
17
- * string → JSON.parse fallback)
18
- * tool_call_update → user/tool_result (status:'failed' → is_error:true)
19
- * plan / available_commands_update / session_info_update /
20
- * current_mode_update / config_option_update → discard
21
- * usage_update → live context occupancy {used, size} →
22
- * {total_tokens, context_limit}; NO input/output
23
- * split exists on the wire — cumulative token stats
24
- * fall back to wire.jsonl usage.record (dual path)
19
+ * tool_call / tool_call_update / usage_update / plan / control-plane noise →
20
+ * base class (BaseAcpTranslator)
25
21
  * prompt stopReason:'end_turn' → result success
26
22
  * prompt stopReason:'cancelled' → result interrupted (独立终态)
27
23
  * prompt other stopReason/error → result error
28
24
  * turn_started (self-produced) → operationKind 'turn'/'compaction'
29
- * session/request_permission → approval_requested
25
+ * session/request_permission → approval_requested(含 question 桥)
26
+ * elicitation/create → question approval event
30
27
  */
31
28
  import { makeQuestionApprovalEvent } from '../../question-common.js';
32
- import { NotificationMethod, ServerRequestMethod, SessionEventType, } from '../../common/acp/protocol-types.js';
29
+ import { ServerRequestMethod, } from '../../common/acp/protocol-types.js';
33
30
  import { deriveAcpAvailableDecisions, truncateWithEllipsis, } from '../../common/acp/protocol-helpers.js';
34
31
  import { getLogger } from '../../../logger/index.js';
32
+ import { BaseAcpTranslator } from '../../common/acp/base-translator.js';
35
33
  /**
36
34
  * Fixed snapshot item ids: ACP has exactly one text stream and one thinking
37
35
  * stream per turn (no itemId on the wire), so turn_diff snapshots use these
@@ -42,153 +40,14 @@ const THINKING_ITEM_ID = 'thinking';
42
40
  // =============================================================================
43
41
  // Translator
44
42
  // =============================================================================
45
- export class KimiAcpTranslator {
43
+ export class KimiAcpTranslator extends BaseAcpTranslator {
44
+ logTag = 'kimi-acp-translator';
46
45
  /** Accumulated text per tool-call or message (keyed by a logical item id). */
47
46
  textByItem = new Map();
48
47
  /** Accumulated thinking per item. */
49
48
  thinkingByItem = new Map();
50
- /** Pending tool calls keyed by toolCallId (for matching tool_call_update). */
51
- pendingToolCalls = new Map();
52
- /** Current operation kind (turn vs compact). */
53
- operationKind = 'turn';
54
- /** Live usage snapshot from usage_update events (context occupancy only). */
55
- liveUsage = {};
56
- /** Whether any usage_update has been seen this turn (no update → no usage). */
57
- hasLiveUsage = false;
58
- /** Current turn id, set by produceTurnStarted; carried on turn_diff events. */
59
- currentTurnId = '';
60
- /**
61
- * Handle a notification from the ACP server and return translated events.
62
- */
63
- handleNotification(method, params) {
64
- if (method !== NotificationMethod.SESSION_UPDATE) {
65
- return [];
66
- }
67
- // R1: real envelope is {sessionId, update: {sessionUpdate: ...}}.
68
- const notif = params;
69
- const update = notif.update;
70
- switch (update.sessionUpdate) {
71
- case SessionEventType.AGENT_MESSAGE_CHUNK:
72
- return this.handleAgentMessageChunk(update, notif.sessionId);
73
- case SessionEventType.AGENT_THOUGHT_CHUNK:
74
- return this.handleAgentThoughtChunk(update, notif.sessionId);
75
- case SessionEventType.TOOL_CALL:
76
- return this.handleToolCall(update, notif.sessionId);
77
- case SessionEventType.TOOL_CALL_UPDATE:
78
- return this.handleToolCallUpdate(update, notif.sessionId);
79
- case SessionEventType.PLAN:
80
- getLogger().debug(`[kimi-acp-translator] discarding plan event`);
81
- return [];
82
- case SessionEventType.AVAILABLE_COMMANDS_UPDATE:
83
- case SessionEventType.SESSION_INFO_UPDATE:
84
- case SessionEventType.CURRENT_MODE_UPDATE:
85
- case SessionEventType.CONFIG_OPTION_UPDATE:
86
- // Control-plane noise (command list, mode/config echoes) — not content.
87
- return [];
88
- case SessionEventType.USAGE_UPDATE:
89
- return this.handleUsageUpdate(update);
90
- default:
91
- return [];
92
- }
93
- }
94
- /**
95
- * Handle a server request (reverse RPC from the ACP server).
96
- */
97
- handleServerRequest(id, method, params) {
98
- if (method === ServerRequestMethod.REQUEST_PERMISSION) {
99
- return this.handleRequestPermission(id, params);
100
- }
101
- if (method === ServerRequestMethod.ELICITATION_CREATE) {
102
- return this.handleElicitationCreate(id, params);
103
- }
104
- return [];
105
- }
106
- /**
107
- * Kimi elicitation form:requestedSchema.properties 按顺序解析为 UserQuestion[]
108
- * (type:array → 多选;oneOf/anyOf const → 选项 label;title → header??question)。
109
- * 题面完整文本只在 form 级 message 里合并出现(逐字段 title 可能只是短 header),
110
- * 因此 message 作为卡片概要行(view.intro)透传。
111
- * form 会丢弃非声明选项值 → isOther=false(卡片隐藏自定义答案输入)。
112
- */
113
- handleElicitationCreate(requestId, params) {
114
- const properties = params.requestedSchema?.properties ?? {};
115
- const questions = [];
116
- for (const [key, prop] of Object.entries(properties)) {
117
- const q = elicitationPropertyToQuestion(prop);
118
- if (!q) {
119
- getLogger().warn(`[kimi-acp-translator] elicitation property skipped: key=${key}`);
120
- continue;
121
- }
122
- questions.push(q);
123
- }
124
- if (questions.length === 0) {
125
- // 无法解析 → 交还 runner 自动响应(不悬挂服务端)。
126
- return [];
127
- }
128
- const message = params.message?.trim();
129
- const single = questions.length === 1;
130
- const intro = message && (!single || message !== questions[0].question) ? message : undefined;
131
- const event = makeQuestionApprovalEvent(requestId, questions, params.sessionId);
132
- if (intro)
133
- event.view.intro = intro;
134
- return [event];
135
- }
136
- /**
137
- * Produce a turn_started event. Called by the runner when session/prompt
138
- * is sent (no wire notification for this — self-produced).
139
- */
140
- produceTurnStarted(sessionId, turnId) {
141
- this.currentTurnId = turnId;
142
- return {
143
- type: 'turn_started',
144
- threadId: sessionId,
145
- turnId,
146
- operationKind: this.operationKind === 'compact' ? 'compaction' : 'turn',
147
- timestamp: new Date().toISOString(),
148
- };
149
- }
150
- /**
151
- * Translate a prompt response into a result event. Carries the live usage
152
- * snapshot only when a usage_update was seen (otherwise undefined, so the
153
- * bridge falls back to wire.jsonl usage.record readback).
154
- */
155
- handlePromptResponse(sessionId, result) {
156
- // §4.2: cancelled is independent terminal state, must NOT merge into error
157
- const subtype = result.stopReason === 'end_turn'
158
- ? 'success'
159
- : result.stopReason === 'cancelled'
160
- ? 'interrupted'
161
- : 'error';
162
- return {
163
- type: 'result',
164
- subtype,
165
- session_id: sessionId,
166
- ...(this.hasLiveUsage ? { usage: this.liveUsageSnapshot() } : {}),
167
- ...(subtype === 'error'
168
- ? { errorMessage: `Prompt ended with stopReason: ${result.stopReason}` }
169
- : {}),
170
- };
171
- }
172
- /**
173
- * Produce an error result event (for JSON-RPC errors during prompt).
174
- */
175
- produceErrorResult(sessionId, errorMessage) {
176
- return {
177
- type: 'result',
178
- subtype: 'error',
179
- session_id: sessionId,
180
- errorMessage,
181
- ...(this.hasLiveUsage ? { usage: this.liveUsageSnapshot() } : {}),
182
- };
183
- }
184
- /**
185
- * Set the operation kind (turn vs compact).
186
- */
187
- setOperationKind(kind) {
188
- this.operationKind = kind;
189
- }
190
49
  // =========================================================================
191
- // Private notification handlers
50
+ // Content channel: wire deltas → turn_diff snapshots
192
51
  // =========================================================================
193
52
  handleAgentMessageChunk(event, sessionId) {
194
53
  // Use a stable key for the current text stream. Kimi ACP doesn't
@@ -214,6 +73,10 @@ export class KimiAcpTranslator {
214
73
  threadId: sessionId,
215
74
  turnId: this.currentTurnId,
216
75
  timestamp: new Date().toISOString(),
76
+ // 卡片块锚点时间刷新为本次 diff 时间:'text' 是整轮累积流,工具间隙
77
+ // 仍会续写增量;首见时间(如 08:39)会让标题与 move-to-end 后的位置
78
+ // 看起来“乱序”。这里显式声明 diff 时间即块的最新写入时间。
79
+ refreshTimestamp: true,
217
80
  },
218
81
  ];
219
82
  }
@@ -233,80 +96,12 @@ export class KimiAcpTranslator {
233
96
  threadId: sessionId,
234
97
  turnId: this.currentTurnId,
235
98
  timestamp: new Date().toISOString(),
99
+ refreshTimestamp: true,
236
100
  },
237
101
  ];
238
102
  }
239
- handleToolCall(event, _sessionId) {
240
- // rawInput arrives as an object on the wire; older kimi versions sent a
241
- // JSON string — parse defensively in that case. The acp-server's
242
- // lazy-create tool_call (first args delta, events-map.ts
243
- // toolCallLazyCreateToSessionUpdate) carries NO rawInput field at all —
244
- // normalize to an empty object so the card renders an empty args summary
245
- // instead of crashing on undefined input (2026-08-17 live TypeError).
246
- let input = event.rawInput ?? {};
247
- if (typeof event.rawInput === 'string') {
248
- try {
249
- input = JSON.parse(event.rawInput);
250
- }
251
- catch {
252
- // Keep raw string as-is
253
- }
254
- }
255
- this.pendingToolCalls.set(event.toolCallId, {
256
- id: event.toolCallId,
257
- name: event.title,
258
- input,
259
- });
260
- return [
261
- {
262
- type: 'assistant',
263
- message: {
264
- content: [
265
- {
266
- type: 'tool_use',
267
- id: event.toolCallId,
268
- name: event.title,
269
- input,
270
- },
271
- ],
272
- },
273
- timestamp: new Date().toISOString(),
274
- },
275
- ];
276
- }
277
- handleToolCallUpdate(event, _sessionId) {
278
- // §4.2: status:'failed' → is_error:true (fixes CLI mode's is_error always false)
279
- const isError = event.status === 'failed';
280
- const content = event.rawOutput ?? '';
281
- return [
282
- {
283
- type: 'user',
284
- message: {
285
- content: [
286
- {
287
- type: 'tool_result',
288
- tool_use_id: event.toolCallId,
289
- content,
290
- is_error: isError,
291
- },
292
- ],
293
- },
294
- timestamp: new Date().toISOString(),
295
- },
296
- ];
297
- }
298
- handleUsageUpdate(event) {
299
- // R1: usage_update is {sessionUpdate:'usage_update', used, size}
300
- // (events-map.ts:505-516) — context occupancy, not cumulative tokens.
301
- this.liveUsage = {
302
- total_tokens: event.used,
303
- context_limit: event.size,
304
- };
305
- this.hasLiveUsage = true;
306
- return [];
307
- }
308
103
  // =========================================================================
309
- // Private approval handlers
104
+ // Approval + question elicitation
310
105
  // =========================================================================
311
106
  handleRequestPermission(requestId, params) {
312
107
  // §5.4 升级:question elicitation 识别(isQuestion 标记 / AskUserQuestion
@@ -349,6 +144,42 @@ export class KimiAcpTranslator {
349
144
  },
350
145
  ];
351
146
  }
147
+ handleOtherServerRequest(requestId, method, params) {
148
+ if (method !== ServerRequestMethod.ELICITATION_CREATE) {
149
+ return [];
150
+ }
151
+ return this.handleElicitationCreate(requestId, params);
152
+ }
153
+ /**
154
+ * Kimi elicitation form:requestedSchema.properties 按顺序解析为 UserQuestion[]
155
+ * (type:array → 多选;oneOf/anyOf const → 选项 label;title → header??question)。
156
+ * 题面完整文本只在 form 级 message 里合并出现(逐字段 title 可能只是短 header),
157
+ * 因此 message 作为卡片概要行(view.intro)透传。
158
+ * form 会丢弃非声明选项值 → isOther=false(卡片隐藏自定义答案输入)。
159
+ */
160
+ handleElicitationCreate(requestId, params) {
161
+ const properties = params.requestedSchema?.properties ?? {};
162
+ const questions = [];
163
+ for (const [key, prop] of Object.entries(properties)) {
164
+ const q = elicitationPropertyToQuestion(prop);
165
+ if (!q) {
166
+ getLogger().warn(`[kimi-acp-translator] elicitation property skipped: key=${key}`);
167
+ continue;
168
+ }
169
+ questions.push(q);
170
+ }
171
+ if (questions.length === 0) {
172
+ // 无法解析 → 交还 runner 自动响应(不悬挂服务端)。
173
+ return [];
174
+ }
175
+ const message = params.message?.trim();
176
+ const single = questions.length === 1;
177
+ const intro = message && (!single || message !== questions[0].question) ? message : undefined;
178
+ const event = makeQuestionApprovalEvent(requestId, questions, params.sessionId);
179
+ if (intro)
180
+ event.view.intro = intro;
181
+ return [event];
182
+ }
352
183
  /**
353
184
  * request_permission 兜底桥(elicitation/create 失败或旧版 kimi):
354
185
  * 选项为 q{n}_opt_{i}(allow_once,label 直取 name)+ q{n}_skip(reject_once,
@@ -370,23 +201,6 @@ export class KimiAcpTranslator {
370
201
  ];
371
202
  return [makeQuestionApprovalEvent(requestId, questions, params.sessionId)];
372
203
  }
373
- /**
374
- * Derive the approval decision list from the server's offered options.
375
- * accept/decline/cancel are universal; acceptForSession requires an
376
- * always-class option (kimi `approve_always`, opencode `allow_always`).
377
- */
378
- // =========================================================================
379
- // Private helpers
380
- // =========================================================================
381
- /**
382
- * ResultEvent.usage requires input_tokens/output_tokens: number, but the
383
- * ACP wire has no such split — the live snapshot is context occupancy only.
384
- * Cast is deliberate: consumers must tolerate undefined input/output (the
385
- * bridge's kimi path already falls back to wire.jsonl readback).
386
- */
387
- liveUsageSnapshot() {
388
- return { ...this.liveUsage };
389
- }
390
204
  }
391
205
  // =============================================================================
392
206
  // Helpers
@@ -7,10 +7,8 @@
7
7
  * → synthetic init → session/prompt → consume session/update notifications
8
8
  * until prompt settles → result event.
9
9
  *
10
- * Structurally aligned with KimiAcpRunner via the shared ConnectionBasedRunner
11
- * base (same notification queue + waitResolve pattern, same forceFinish/
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,11 +16,15 @@
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
- import type { AgentKind, AgentSessionReader, AgentEvent, AgentStatusInfo, SpawnOptions } from '../../types.js';
22
+ import type { AgentKind, AgentSessionReader, AgentStatusInfo } from '../../types.js';
23
23
  import { JsonRpcClient } from '../../common/jsonrpc/client.js';
24
- import { type OpencodeAcpTranslatorEvent } from './translator.js';
25
- import { ConnectionBasedRunner } from '../../common/connection-based-runner.js';
24
+ import { OpencodeAcpTranslator } from './translator.js';
25
+ import { type SessionNewResult, type SessionResumeResult, type SessionPromptResult } from '../../common/acp/protocol-types.js';
26
+ import { type AcpPendingApproval } from '../../common/acp/protocol-helpers.js';
27
+ import { BaseAcpRunner } from '../../common/acp/base-acp-runner.js';
26
28
  export interface OpencodeAcpRunnerOptions {
27
29
  kind: AgentKind;
28
30
  sessionReader: AgentSessionReader;
@@ -48,11 +50,7 @@ export interface OpencodeAcpRunnerOptions {
48
50
  /** Tail poll interval for the opencode log error monitor. Defaults to 2000ms. */
49
51
  errorMonitorPollIntervalMs?: number;
50
52
  }
51
- export declare class OpencodeAcpRunner extends ConnectionBasedRunner<JsonRpcClient, OpencodeAcpTranslatorEvent> {
52
- private connectionManager;
53
- private currentTranslator;
54
- private activeSessionId;
55
- private model?;
53
+ export declare class OpencodeAcpRunner extends BaseAcpRunner<OpencodeAcpTranslator> {
56
54
  /** Configured session mode applied via session/set_mode (§P5). */
57
55
  private configuredMode;
58
56
  /** Current session mode id, parsed from session/new|resume configOptions. */
@@ -62,22 +60,12 @@ export declare class OpencodeAcpRunner extends ConnectionBasedRunner<JsonRpcClie
62
60
  /** Tails opencode's own log to surface LLM stream errors the ACP protocol
63
61
  * never forwards (quota/limit errors back off for ~1h → turn would hang). */
64
62
  private readonly errorMonitor;
65
- /** Pending approval requests: requestId → kind + view + options. */
66
- private pendingApprovals;
67
- /** Tracker for the in-flight prompt request — needed for cancellation. */
68
- private promptSettled;
69
- /** Whether the prompt request has been fired (distinguishes "stop before
70
- * prompt sent" from "stop while prompt in-flight" — the latter sends
71
- * session/cancel immediately). */
72
- private promptSent;
73
63
  constructor(opts: OpencodeAcpRunnerOptions);
74
64
  protected get logTag(): string;
75
65
  protected get turnTimeoutErrorMessage(): string;
76
66
  protected get turnInterruptedErrorMessage(): string;
77
- protected currentSessionId(): string | null;
67
+ protected get connectionClosedMessage(): string;
78
68
  protected shouldDeferStop(): boolean;
79
- protected cancelCurrentTurn(): Promise<void>;
80
- protected clearTurnState(): void;
81
69
  /**
82
70
  * Start watching opencode's own log for LLM stream errors of the active
83
71
  * session. opencode ACP does not surface retry/error state to the client
@@ -88,23 +76,14 @@ export declare class OpencodeAcpRunner extends ConnectionBasedRunner<JsonRpcClie
88
76
  * until the idle watchdog.
89
77
  */
90
78
  private startErrorMonitor;
91
- protected releaseConnection(cwd: string): Promise<void>;
92
- protected notifyIdle(cwd: string): void;
93
- protected disposeConnections(): Promise<void>;
79
+ protected createTranslator(): OpencodeAcpTranslator;
80
+ protected applyTurnSettings(client: JsonRpcClient, sessionId: string): Promise<void>;
94
81
  /**
95
- * Run a compact operation on the current session.
96
- *
97
- * opencode special-cases the `/compact` text prompt server-side
98
- * (service.ts:555-571): it runs session.summarize synchronously and the
99
- * prompt response settles only after compaction completes — no background
100
- * record polling needed (unlike kimi).
101
- */
102
- runCompact(_message: string, opts: SpawnOptions): AsyncGenerator<AgentEvent>;
103
- /**
104
- * Respond to an approval server request. `response` is the bridge
105
- * ApprovalAction (`{ action: 'accept' | 'decline' | 'cancel' }`).
82
+ * opencode /compact is synchronous server-side — standard prompt-settle
83
+ * translation (no wire.jsonl polling, unlike kimi).
106
84
  */
107
- respondApproval(requestId: number | string, response: unknown): Promise<void>;
85
+ protected onCompactPromptFired(promptPromise: Promise<SessionPromptResult>, translator: OpencodeAcpTranslator, sessionId: string, _opts: unknown, _baselineTotalCount: number): void;
86
+ protected buildApprovalOutcome(action: string, pending: AcpPendingApproval, _response: unknown): unknown;
108
87
  getStatusInfo(): AgentStatusInfo;
109
88
  /**
110
89
  * Hot-apply a mode change to the live ACP session (§P5).
@@ -121,18 +100,15 @@ export declare class OpencodeAcpRunner extends ConnectionBasedRunner<JsonRpcClie
121
100
  updateApprovalMode(settings: {
122
101
  mode?: 'build' | 'plan';
123
102
  }): Promise<void>;
124
- /**
125
- * Acquire the connection and set up the turn:
126
- * session/new (new session) or session/resume (existing sessionId)
127
- * → session/prompt.
128
- */
129
- protected setupTurn(message: string, opts: SpawnOptions): Promise<void>;
130
103
  /**
131
104
  * Track the current session mode and model from a session/new|resume
132
105
  * result's configOptions (opencode buildModeSelectOption/buildModelSelectOption:
133
106
  * {id, category, currentValue}).
134
107
  */
135
108
  private trackConfigOptions;
136
- private handleNotification;
137
- private handleServerRequest;
109
+ protected onTurnSessionReady(sessionId: string): void;
110
+ protected onSessionEstablished(result: SessionResumeResult | SessionNewResult): void;
111
+ protected onClearTurnState(): void;
112
+ protected handleNotification(method: string, params: unknown): void;
113
+ protected handleServerRequest(id: number | string, method: string, params: unknown): void;
138
114
  }