lark-remote 0.1.5 → 0.1.7

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 (190) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.en.md +5 -1
  3. package/README.md +10 -2
  4. package/dist/bridge/approval-coordinator.d.ts +131 -0
  5. package/dist/bridge/approval-coordinator.js +409 -0
  6. package/dist/bridge/inbound-media.d.ts +62 -0
  7. package/dist/bridge/inbound-media.js +282 -0
  8. package/dist/bridge/index.d.ts +138 -5
  9. package/dist/bridge/index.js +569 -65
  10. package/dist/bridge/queue-manager.d.ts +11 -1
  11. package/dist/bridge/queue-manager.js +20 -11
  12. package/dist/card/approval-render.d.ts +16 -0
  13. package/dist/card/approval-render.js +363 -0
  14. package/dist/card/bash-card-session.d.ts +2 -24
  15. package/dist/card/bash-card-session.js +6 -73
  16. package/dist/card/bash-renderer.js +2 -3
  17. package/dist/card/card-budget.js +29 -75
  18. package/dist/card/card-session.d.ts +48 -0
  19. package/dist/card/card-session.js +86 -0
  20. package/dist/card/card-shared.d.ts +13 -10
  21. package/dist/card/card-shared.js +27 -24
  22. package/dist/card/run-card-session.d.ts +2 -55
  23. package/dist/card/run-card-session.js +6 -104
  24. package/dist/card/run-renderer.js +140 -202
  25. package/dist/card/run-state.d.ts +38 -3
  26. package/dist/card/run-state.js +221 -11
  27. package/dist/card/text-truncate.d.ts +13 -12
  28. package/dist/card/text-truncate.js +13 -63
  29. package/dist/common/fs.d.ts +8 -0
  30. package/dist/common/fs.js +17 -0
  31. package/dist/common/guards.d.ts +0 -20
  32. package/dist/common/guards.js +0 -43
  33. package/dist/common/sleep.d.ts +2 -0
  34. package/dist/common/sleep.js +5 -0
  35. package/dist/common/truncate.d.ts +39 -0
  36. package/dist/common/truncate.js +92 -0
  37. package/dist/config/codex-config.d.ts +1 -1
  38. package/dist/config/codex-config.js +1 -1
  39. package/dist/config/dir.js +1 -1
  40. package/dist/config/index.d.ts +116 -9
  41. package/dist/config/index.js +122 -29
  42. package/dist/connector/index.d.ts +74 -2
  43. package/dist/connector/index.js +119 -1
  44. package/dist/index.js +139 -99
  45. package/dist/instance-lock.js +4 -12
  46. package/dist/order/alias-resolve.d.ts +13 -0
  47. package/dist/order/alias-resolve.js +22 -0
  48. package/dist/order/alias-store.d.ts +38 -0
  49. package/dist/order/alias-store.js +106 -0
  50. package/dist/persistence/atomic-write.d.ts +8 -2
  51. package/dist/persistence/atomic-write.js +23 -7
  52. package/dist/restart.js +2 -1
  53. package/dist/router/card-helpers.d.ts +63 -0
  54. package/dist/router/card-helpers.js +146 -0
  55. package/dist/router/config/claude.js +25 -1
  56. package/dist/router/config/codex.d.ts +1 -1
  57. package/dist/router/config/codex.js +33 -5
  58. package/dist/router/config/common/model-patch.d.ts +22 -0
  59. package/dist/router/config/common/model-patch.js +26 -0
  60. package/dist/router/config/common/render.js +9 -4
  61. package/dist/router/config/index.d.ts +12 -0
  62. package/dist/router/config/index.js +25 -1
  63. package/dist/router/config/kimi.d.ts +4 -1
  64. package/dist/router/config/kimi.js +27 -0
  65. package/dist/router/config/opencode.js +18 -5
  66. package/dist/router/config/pi.js +4 -5
  67. package/dist/router/config/types.d.ts +12 -3
  68. package/dist/router/index.d.ts +77 -17
  69. package/dist/router/index.js +455 -549
  70. package/dist/router/utils.d.ts +4 -0
  71. package/dist/router/utils.js +9 -2
  72. package/dist/runner/agent-choices-common.d.ts +16 -0
  73. package/dist/runner/agent-choices-common.js +31 -0
  74. package/dist/runner/claude/runner.d.ts +47 -10
  75. package/dist/runner/claude/runner.js +117 -46
  76. package/dist/runner/claude/session.d.ts +166 -0
  77. package/dist/runner/claude/session.js +749 -0
  78. package/dist/runner/codex/app-server/protocol-types.d.ts +519 -0
  79. package/dist/runner/codex/app-server/protocol-types.js +56 -0
  80. package/dist/runner/codex/app-server/runner.d.ts +104 -0
  81. package/dist/runner/codex/app-server/runner.js +377 -0
  82. package/dist/runner/codex/app-server/translator.d.ts +89 -0
  83. package/dist/runner/codex/app-server/translator.js +716 -0
  84. package/dist/runner/codex/index.d.ts +1 -1
  85. package/dist/runner/codex/index.js +1 -1
  86. package/dist/runner/common/acp/protocol-helpers.d.ts +14 -0
  87. package/dist/runner/common/acp/protocol-helpers.js +21 -0
  88. package/dist/runner/common/acp/protocol-types.d.ts +238 -0
  89. package/dist/runner/common/acp/protocol-types.js +75 -0
  90. package/dist/runner/common/connection-based-runner.d.ts +111 -0
  91. package/dist/runner/common/connection-based-runner.js +261 -0
  92. package/dist/runner/common/jsonrpc/client.d.ts +89 -0
  93. package/dist/runner/common/jsonrpc/client.js +243 -0
  94. package/dist/runner/common/jsonrpc/connection-manager.d.ts +125 -0
  95. package/dist/runner/common/jsonrpc/connection-manager.js +205 -0
  96. package/dist/runner/common/jsonrpc/transport.d.ts +50 -0
  97. package/dist/runner/common/jsonrpc/transport.js +230 -0
  98. package/dist/runner/common/jsonrpc/types.d.ts +32 -0
  99. package/dist/runner/common/jsonrpc/types.js +9 -0
  100. package/dist/runner/common/process-stopper.d.ts +2 -1
  101. package/dist/runner/common/process-stopper.js +1 -0
  102. package/dist/runner/common/runner-utils.d.ts +6 -13
  103. package/dist/runner/common/runner-utils.js +6 -47
  104. package/dist/runner/common/spawning-runner.d.ts +24 -70
  105. package/dist/runner/common/spawning-runner.js +98 -152
  106. package/dist/runner/common/usage.d.ts +2 -0
  107. package/dist/runner/common/usage.js +2 -0
  108. package/dist/runner/index.d.ts +5 -5
  109. package/dist/runner/index.js +4 -4
  110. package/dist/runner/kimi/acp/runner.d.ts +106 -0
  111. package/dist/runner/kimi/acp/runner.js +448 -0
  112. package/dist/runner/kimi/acp/translator.d.ts +124 -0
  113. package/dist/runner/kimi/acp/translator.js +336 -0
  114. package/dist/runner/kimi/index.d.ts +1 -1
  115. package/dist/runner/kimi/index.js +1 -1
  116. package/dist/runner/opencode/acp/index.d.ts +1 -0
  117. package/dist/runner/opencode/acp/index.js +2 -0
  118. package/dist/runner/opencode/acp/runner.d.ts +121 -0
  119. package/dist/runner/opencode/acp/runner.js +429 -0
  120. package/dist/runner/opencode/acp/translator.d.ts +119 -0
  121. package/dist/runner/opencode/acp/translator.js +345 -0
  122. package/dist/runner/opencode/index.d.ts +1 -1
  123. package/dist/runner/opencode/index.js +1 -1
  124. package/dist/runner/pi/index.d.ts +1 -1
  125. package/dist/runner/pi/index.js +1 -1
  126. package/dist/runner/pi/rpc/client.d.ts +52 -0
  127. package/dist/runner/pi/rpc/client.js +114 -0
  128. package/dist/runner/pi/rpc/index.d.ts +4 -0
  129. package/dist/runner/pi/rpc/index.js +4 -0
  130. package/dist/runner/pi/rpc/protocol-types.d.ts +127 -0
  131. package/dist/runner/pi/rpc/protocol-types.js +21 -0
  132. package/dist/runner/pi/rpc/runner.d.ts +80 -0
  133. package/dist/runner/pi/rpc/runner.js +199 -0
  134. package/dist/runner/pi/rpc/translator.d.ts +55 -0
  135. package/dist/runner/pi/rpc/translator.js +258 -0
  136. package/dist/runner/registry.d.ts +7 -8
  137. package/dist/runner/registry.js +3 -4
  138. package/dist/runner/resolve-agent-choices.js +18 -59
  139. package/dist/runner/sync-agent-choices.js +16 -67
  140. package/dist/runner/types.d.ts +153 -2
  141. package/dist/session/claude/session-index.js +2 -3
  142. package/dist/session/claude/session-reader.js +3 -6
  143. package/dist/session/claude/sessions.js +6 -13
  144. package/dist/session/codex/rollout-reader.d.ts +2 -2
  145. package/dist/session/codex/rollout-reader.js +57 -17
  146. package/dist/session/codex/sessions.d.ts +1 -1
  147. package/dist/session/codex/sessions.js +2 -2
  148. package/dist/session/common/content-blocks.d.ts +7 -0
  149. package/dist/session/common/content-blocks.js +13 -9
  150. package/dist/session/common/jsonl.d.ts +8 -25
  151. package/dist/session/common/jsonl.js +126 -261
  152. package/dist/session/common/pagination.d.ts +28 -0
  153. package/dist/session/common/pagination.js +33 -0
  154. package/dist/session/kimi/sessions.d.ts +34 -1
  155. package/dist/session/kimi/sessions.js +208 -98
  156. package/dist/session/opencode/sessions.d.ts +17 -3
  157. package/dist/session/opencode/sessions.js +29 -29
  158. package/dist/session/pi/sessions.d.ts +5 -1
  159. package/dist/session/pi/sessions.js +12 -16
  160. package/dist/session/session-store.d.ts +11 -1
  161. package/dist/session/session-store.js +75 -178
  162. package/dist/update/index.d.ts +2 -2
  163. package/dist/update/index.js +2 -2
  164. package/dist/update/install.d.ts +20 -2
  165. package/dist/update/install.js +51 -7
  166. package/package.json +1 -1
  167. package/dist/runner/codex/argv.d.ts +0 -33
  168. package/dist/runner/codex/argv.js +0 -40
  169. package/dist/runner/codex/jsonl.d.ts +0 -37
  170. package/dist/runner/codex/jsonl.js +0 -221
  171. package/dist/runner/codex/runner.d.ts +0 -63
  172. package/dist/runner/codex/runner.js +0 -124
  173. package/dist/runner/common/exec-translator.d.ts +0 -73
  174. package/dist/runner/common/exec-translator.js +0 -105
  175. package/dist/runner/kimi/jsonl.d.ts +0 -28
  176. package/dist/runner/kimi/jsonl.js +0 -143
  177. package/dist/runner/kimi/runner.d.ts +0 -49
  178. package/dist/runner/kimi/runner.js +0 -105
  179. package/dist/runner/opencode/argv.d.ts +0 -32
  180. package/dist/runner/opencode/argv.js +0 -35
  181. package/dist/runner/opencode/jsonl.d.ts +0 -68
  182. package/dist/runner/opencode/jsonl.js +0 -295
  183. package/dist/runner/opencode/runner.d.ts +0 -61
  184. package/dist/runner/opencode/runner.js +0 -93
  185. package/dist/runner/pi/jsonl.d.ts +0 -128
  186. package/dist/runner/pi/jsonl.js +0 -249
  187. package/dist/runner/pi/runner.d.ts +0 -36
  188. package/dist/runner/pi/runner.js +0 -76
  189. package/dist/session/common/catch-up.d.ts +0 -12
  190. package/dist/session/common/catch-up.js +0 -31
@@ -0,0 +1,124 @@
1
+ /**
2
+ * KimiAcpTranslator: translates ACP session/update notifications and
3
+ * session/request_permission server requests into AgentEvents.
4
+ *
5
+ * Per-turn stateful: accumulates text/thinking deltas, tracks tool calls,
6
+ * and produces turn_started events with operationKind.
7
+ *
8
+ * Wire envelope (R1, source: kimi-code packages/acp-server/src/events-map.ts,
9
+ * verified live 2026-08-15/16 against kimi 0.36.0):
10
+ * session/update params = {sessionId, update: {sessionUpdate: '<kind>', ...}}
11
+ * — the discriminator is `update.sessionUpdate`, NOT a nested `event.type`.
12
+ *
13
+ * Event mapping (design doc §4.2):
14
+ * agent_message_chunk → turn_diff text snapshot (content.text accumulation)
15
+ * 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)
25
+ * prompt stopReason:'end_turn' → result success
26
+ * prompt stopReason:'cancelled' → result interrupted (独立终态)
27
+ * prompt other stopReason/error → result error
28
+ * turn_started (self-produced) → operationKind 'turn'/'compaction'
29
+ * session/request_permission → approval_requested
30
+ */
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 {
62
+ /** Accumulated text per tool-call or message (keyed by a logical item id). */
63
+ private textByItem;
64
+ /** Accumulated thinking per item. */
65
+ 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[];
84
+ /**
85
+ * Produce a turn_started event. Called by the runner when session/prompt
86
+ * is sent (no wire notification for this — self-produced).
87
+ */
88
+ produceTurnStarted(sessionId: string, turnId: string): AcpTurnStartedEvent;
89
+ /**
90
+ * Translate a prompt response into a result event. Carries the live usage
91
+ * snapshot only when a usage_update was seen (otherwise undefined, so the
92
+ * bridge falls back to wire.jsonl usage.record readback).
93
+ */
94
+ handlePromptResponse(sessionId: string, result: {
95
+ stopReason: string;
96
+ }): AgentEvent;
97
+ /**
98
+ * Produce an error result event (for JSON-RPC errors during prompt).
99
+ */
100
+ produceErrorResult(sessionId: string, errorMessage: string): AgentEvent;
101
+ /**
102
+ * Set the operation kind (turn vs compact).
103
+ */
104
+ setOperationKind(kind: 'turn' | 'compact'): void;
105
+ private handleAgentMessageChunk;
106
+ private handleAgentThoughtChunk;
107
+ private handleToolCall;
108
+ private handleToolCallUpdate;
109
+ private handleUsageUpdate;
110
+ private handleRequestPermission;
111
+ /**
112
+ * Derive the approval decision list from the server's offered options.
113
+ * accept/decline/cancel are universal; acceptForSession requires an
114
+ * always-class option (kimi `approve_always`, opencode `allow_always`).
115
+ */
116
+ private deriveAvailableDecisions;
117
+ /**
118
+ * ResultEvent.usage requires input_tokens/output_tokens: number, but the
119
+ * ACP wire has no such split — the live snapshot is context occupancy only.
120
+ * Cast is deliberate: consumers must tolerate undefined input/output (the
121
+ * bridge's kimi path already falls back to wire.jsonl readback).
122
+ */
123
+ private liveUsageSnapshot;
124
+ }
@@ -0,0 +1,336 @@
1
+ /**
2
+ * KimiAcpTranslator: translates ACP session/update notifications and
3
+ * session/request_permission server requests into AgentEvents.
4
+ *
5
+ * Per-turn stateful: accumulates text/thinking deltas, tracks tool calls,
6
+ * and produces turn_started events with operationKind.
7
+ *
8
+ * Wire envelope (R1, source: kimi-code packages/acp-server/src/events-map.ts,
9
+ * verified live 2026-08-15/16 against kimi 0.36.0):
10
+ * session/update params = {sessionId, update: {sessionUpdate: '<kind>', ...}}
11
+ * — the discriminator is `update.sessionUpdate`, NOT a nested `event.type`.
12
+ *
13
+ * Event mapping (design doc §4.2):
14
+ * agent_message_chunk → turn_diff text snapshot (content.text accumulation)
15
+ * 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)
25
+ * prompt stopReason:'end_turn' → result success
26
+ * prompt stopReason:'cancelled' → result interrupted (独立终态)
27
+ * prompt other stopReason/error → result error
28
+ * turn_started (self-produced) → operationKind 'turn'/'compaction'
29
+ * session/request_permission → approval_requested
30
+ */
31
+ import { NotificationMethod, ServerRequestMethod, SessionEventType, } from '../../common/acp/protocol-types.js';
32
+ import { getLogger } from '../../../logger/index.js';
33
+ /**
34
+ * Fixed snapshot item ids: ACP has exactly one text stream and one thinking
35
+ * stream per turn (no itemId on the wire), so turn_diff snapshots use these
36
+ * stable ids for in-place block replacement.
37
+ */
38
+ const TEXT_ITEM_ID = 'text';
39
+ const THINKING_ITEM_ID = 'thinking';
40
+ // =============================================================================
41
+ // Translator
42
+ // =============================================================================
43
+ export class KimiAcpTranslator {
44
+ /** Accumulated text per tool-call or message (keyed by a logical item id). */
45
+ textByItem = new Map();
46
+ /** Accumulated thinking per item. */
47
+ thinkingByItem = new Map();
48
+ /** Pending tool calls keyed by toolCallId (for matching tool_call_update). */
49
+ pendingToolCalls = new Map();
50
+ /** Current operation kind (turn vs compact). */
51
+ operationKind = 'turn';
52
+ /** Live usage snapshot from usage_update events (context occupancy only). */
53
+ liveUsage = {};
54
+ /** Whether any usage_update has been seen this turn (no update → no usage). */
55
+ hasLiveUsage = false;
56
+ /** Current turn id, set by produceTurnStarted; carried on turn_diff events. */
57
+ currentTurnId = '';
58
+ /**
59
+ * Handle a notification from the ACP server and return translated events.
60
+ */
61
+ handleNotification(method, params) {
62
+ if (method !== NotificationMethod.SESSION_UPDATE) {
63
+ return [];
64
+ }
65
+ // R1: real envelope is {sessionId, update: {sessionUpdate: ...}}.
66
+ const notif = params;
67
+ const update = notif.update;
68
+ switch (update.sessionUpdate) {
69
+ case SessionEventType.AGENT_MESSAGE_CHUNK:
70
+ return this.handleAgentMessageChunk(update, notif.sessionId);
71
+ case SessionEventType.AGENT_THOUGHT_CHUNK:
72
+ return this.handleAgentThoughtChunk(update, notif.sessionId);
73
+ case SessionEventType.TOOL_CALL:
74
+ return this.handleToolCall(update, notif.sessionId);
75
+ case SessionEventType.TOOL_CALL_UPDATE:
76
+ return this.handleToolCallUpdate(update, notif.sessionId);
77
+ case SessionEventType.PLAN:
78
+ getLogger().debug(`[kimi-acp-translator] discarding plan event`);
79
+ return [];
80
+ case SessionEventType.AVAILABLE_COMMANDS_UPDATE:
81
+ case SessionEventType.SESSION_INFO_UPDATE:
82
+ case SessionEventType.CURRENT_MODE_UPDATE:
83
+ case SessionEventType.CONFIG_OPTION_UPDATE:
84
+ // Control-plane noise (command list, mode/config echoes) — not content.
85
+ return [];
86
+ case SessionEventType.USAGE_UPDATE:
87
+ return this.handleUsageUpdate(update);
88
+ default:
89
+ return [];
90
+ }
91
+ }
92
+ /**
93
+ * Handle a server request (reverse RPC from the ACP server).
94
+ */
95
+ handleServerRequest(id, method, params) {
96
+ if (method === ServerRequestMethod.REQUEST_PERMISSION) {
97
+ return this.handleRequestPermission(id, params);
98
+ }
99
+ return [];
100
+ }
101
+ /**
102
+ * Produce a turn_started event. Called by the runner when session/prompt
103
+ * is sent (no wire notification for this — self-produced).
104
+ */
105
+ produceTurnStarted(sessionId, turnId) {
106
+ this.currentTurnId = turnId;
107
+ return {
108
+ type: 'turn_started',
109
+ threadId: sessionId,
110
+ turnId,
111
+ operationKind: this.operationKind === 'compact' ? 'compaction' : 'turn',
112
+ timestamp: new Date().toISOString(),
113
+ };
114
+ }
115
+ /**
116
+ * Translate a prompt response into a result event. Carries the live usage
117
+ * snapshot only when a usage_update was seen (otherwise undefined, so the
118
+ * bridge falls back to wire.jsonl usage.record readback).
119
+ */
120
+ handlePromptResponse(sessionId, result) {
121
+ // §4.2: cancelled is independent terminal state, must NOT merge into error
122
+ const subtype = result.stopReason === 'end_turn'
123
+ ? 'success'
124
+ : result.stopReason === 'cancelled'
125
+ ? 'interrupted'
126
+ : 'error';
127
+ return {
128
+ type: 'result',
129
+ subtype,
130
+ session_id: sessionId,
131
+ ...(this.hasLiveUsage ? { usage: this.liveUsageSnapshot() } : {}),
132
+ ...(subtype === 'error'
133
+ ? { errorMessage: `Prompt ended with stopReason: ${result.stopReason}` }
134
+ : {}),
135
+ };
136
+ }
137
+ /**
138
+ * Produce an error result event (for JSON-RPC errors during prompt).
139
+ */
140
+ produceErrorResult(sessionId, errorMessage) {
141
+ return {
142
+ type: 'result',
143
+ subtype: 'error',
144
+ session_id: sessionId,
145
+ errorMessage,
146
+ ...(this.hasLiveUsage ? { usage: this.liveUsageSnapshot() } : {}),
147
+ };
148
+ }
149
+ /**
150
+ * Set the operation kind (turn vs compact).
151
+ */
152
+ setOperationKind(kind) {
153
+ this.operationKind = kind;
154
+ }
155
+ // =========================================================================
156
+ // Private notification handlers
157
+ // =========================================================================
158
+ handleAgentMessageChunk(event, sessionId) {
159
+ // Use a stable key for the current text stream. Kimi ACP doesn't
160
+ // provide an itemId in agent_message_chunk; we use 'text' as the
161
+ // single logical text accumulator (one text stream per turn).
162
+ // R1: wire chunk text lives at content.text and is a DELTA
163
+ // (kimi-code events-map.ts assistantDeltaToSessionUpdate:
164
+ // content.text = event.delta), so we accumulate here into a full-text
165
+ // snapshot and emit it as turn_diff. The card reducer's turn_diff path
166
+ // REPLACES the block in place (snapshot semantics). Emitting the full
167
+ // text as assistant/text (delta semantics) made the reducer append the
168
+ // full text to itself on every chunk — duplicated card output
169
+ // (2026-08-17 live regression, reproduced with 3 chunks).
170
+ const key = 'text';
171
+ const prev = this.textByItem.get(key) ?? '';
172
+ const next = prev + event.content.text;
173
+ this.textByItem.set(key, next);
174
+ return [
175
+ {
176
+ type: 'turn_diff',
177
+ itemId: TEXT_ITEM_ID,
178
+ text: next,
179
+ threadId: sessionId,
180
+ turnId: this.currentTurnId,
181
+ timestamp: new Date().toISOString(),
182
+ },
183
+ ];
184
+ }
185
+ handleAgentThoughtChunk(event, sessionId) {
186
+ // Same delta→snapshot contract as handleAgentMessageChunk: accumulate
187
+ // wire deltas and emit a full reasoning snapshot via turn_diff so the
188
+ // reducer replaces the thinking block instead of appending.
189
+ const key = THINKING_ITEM_ID;
190
+ const prev = this.thinkingByItem.get(key) ?? '';
191
+ const next = prev + event.content.text;
192
+ this.thinkingByItem.set(key, next);
193
+ return [
194
+ {
195
+ type: 'turn_diff',
196
+ itemId: THINKING_ITEM_ID,
197
+ reasoning: next,
198
+ threadId: sessionId,
199
+ turnId: this.currentTurnId,
200
+ timestamp: new Date().toISOString(),
201
+ },
202
+ ];
203
+ }
204
+ handleToolCall(event, _sessionId) {
205
+ // rawInput arrives as an object on the wire; older kimi versions sent a
206
+ // JSON string — parse defensively in that case. The acp-server's
207
+ // lazy-create tool_call (first args delta, events-map.ts
208
+ // toolCallLazyCreateToSessionUpdate) carries NO rawInput field at all —
209
+ // normalize to an empty object so the card renders an empty args summary
210
+ // instead of crashing on undefined input (2026-08-17 live TypeError).
211
+ let input = event.rawInput ?? {};
212
+ if (typeof event.rawInput === 'string') {
213
+ try {
214
+ input = JSON.parse(event.rawInput);
215
+ }
216
+ catch {
217
+ // Keep raw string as-is
218
+ }
219
+ }
220
+ this.pendingToolCalls.set(event.toolCallId, {
221
+ id: event.toolCallId,
222
+ name: event.title,
223
+ input,
224
+ });
225
+ return [
226
+ {
227
+ type: 'assistant',
228
+ message: {
229
+ content: [
230
+ {
231
+ type: 'tool_use',
232
+ id: event.toolCallId,
233
+ name: event.title,
234
+ input,
235
+ },
236
+ ],
237
+ },
238
+ timestamp: new Date().toISOString(),
239
+ },
240
+ ];
241
+ }
242
+ handleToolCallUpdate(event, _sessionId) {
243
+ // §4.2: status:'failed' → is_error:true (fixes CLI mode's is_error always false)
244
+ const isError = event.status === 'failed';
245
+ const content = event.rawOutput ?? '';
246
+ return [
247
+ {
248
+ type: 'user',
249
+ message: {
250
+ content: [
251
+ {
252
+ type: 'tool_result',
253
+ tool_use_id: event.toolCallId,
254
+ content,
255
+ is_error: isError,
256
+ },
257
+ ],
258
+ },
259
+ timestamp: new Date().toISOString(),
260
+ },
261
+ ];
262
+ }
263
+ handleUsageUpdate(event) {
264
+ // R1: usage_update is {sessionUpdate:'usage_update', used, size}
265
+ // (events-map.ts:505-516) — context occupancy, not cumulative tokens.
266
+ this.liveUsage = {
267
+ total_tokens: event.used,
268
+ context_limit: event.size,
269
+ };
270
+ this.hasLiveUsage = true;
271
+ return [];
272
+ }
273
+ // =========================================================================
274
+ // Private approval handlers
275
+ // =========================================================================
276
+ handleRequestPermission(requestId, params) {
277
+ // §5.4: Question elicitation auto-respond cancelled
278
+ if (params.isQuestion || !params.toolCall) {
279
+ getLogger().info(`[kimi-acp-translator] question elicitation detected, auto-responding cancelled (requestId=${requestId})`);
280
+ return [];
281
+ }
282
+ const view = {
283
+ requestId,
284
+ kind: 'command',
285
+ command: params.toolCall?.title ?? undefined,
286
+ reason: params.toolCall?.rawInput ? truncate(params.toolCall.rawInput, 200) : undefined,
287
+ // §P4: 从服务端 options kind 派生——带 approve_always(或 allow_always)
288
+ // 才提供「本会话总是允许」(acceptForSession);否则与旧行为一致。
289
+ availableDecisions: this.deriveAvailableDecisions(params),
290
+ };
291
+ return [
292
+ {
293
+ type: 'approval_requested',
294
+ requestId,
295
+ kind: 'command',
296
+ threadId: params.sessionId,
297
+ turnId: '',
298
+ itemId: '',
299
+ view,
300
+ timestamp: new Date().toISOString(),
301
+ },
302
+ ];
303
+ }
304
+ /**
305
+ * Derive the approval decision list from the server's offered options.
306
+ * accept/decline/cancel are universal; acceptForSession requires an
307
+ * always-class option (kimi `approve_always`, opencode `allow_always`).
308
+ */
309
+ deriveAvailableDecisions(params) {
310
+ const decisions = ['accept', 'decline', 'cancel'];
311
+ const hasAlwaysOption = (params.options ?? []).some((opt) => opt.kind === 'approve_always' || opt.kind === 'allow_always');
312
+ if (hasAlwaysOption) {
313
+ decisions.push('acceptForSession');
314
+ }
315
+ return decisions;
316
+ }
317
+ // =========================================================================
318
+ // Private helpers
319
+ // =========================================================================
320
+ /**
321
+ * ResultEvent.usage requires input_tokens/output_tokens: number, but the
322
+ * ACP wire has no such split — the live snapshot is context occupancy only.
323
+ * Cast is deliberate: consumers must tolerate undefined input/output (the
324
+ * bridge's kimi path already falls back to wire.jsonl readback).
325
+ */
326
+ liveUsageSnapshot() {
327
+ return { ...this.liveUsage };
328
+ }
329
+ }
330
+ // =============================================================================
331
+ // Helpers
332
+ // =============================================================================
333
+ function truncate(text, maxLen) {
334
+ return text.length <= maxLen ? text : text.slice(0, maxLen) + '…';
335
+ }
336
+ //# sourceMappingURL=translator.js.map
@@ -1 +1 @@
1
- export { KimiRunner } from './runner.js';
1
+ export { KimiAcpRunner } from './acp/runner.js';
@@ -1,2 +1,2 @@
1
- export { KimiRunner } from './runner.js';
1
+ export { KimiAcpRunner } from './acp/runner.js';
2
2
  //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ export { OpencodeAcpRunner, type OpencodeAcpRunnerOptions } from './runner.js';
@@ -0,0 +1,2 @@
1
+ export { OpencodeAcpRunner } from './runner.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,121 @@
1
+ /**
2
+ * OpencodeAcpRunner: workspace-lifetime runner using opencode's ACP mode
3
+ * (`opencode acp`, JSON-RPC over stdio). Registered as the opencode agent's
4
+ * runner.
5
+ *
6
+ * Flow per run: acquire persistent connection → session/new or session/resume
7
+ * → synthetic init → session/prompt → consume session/update notifications
8
+ * until prompt settles → result event.
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:
14
+ * - session/set_mode applies the configured mode (build/plan; default 'build'
15
+ * keeps manual approvals flowing); opencode sends no set_mode notification,
16
+ * so the client refreshes the local mode view itself
17
+ * - no wire.jsonl compaction polling (opencode /compact is synchronous:
18
+ * service.ts:555-571 summarize completes before the prompt response)
19
+ * - text/thinking go to the assistant incremental channel (opencode wire
20
+ * deltas), not turn_diff snapshots
21
+ */
22
+ import type { AgentKind, AgentSessionReader, AgentEvent, AgentStatusInfo, SpawnOptions } from '../../types.js';
23
+ import { JsonRpcClient } from '../../common/jsonrpc/client.js';
24
+ import { type OpencodeAcpTranslatorEvent } from './translator.js';
25
+ import { ConnectionBasedRunner } from '../../common/connection-based-runner.js';
26
+ export interface OpencodeAcpRunnerOptions {
27
+ kind: AgentKind;
28
+ sessionReader: AgentSessionReader;
29
+ /** Path to the opencode binary. Defaults to `opencode`. */
30
+ binary?: string;
31
+ /** Environment variables. */
32
+ env?: Record<string, string | undefined>;
33
+ /** Args to spawn the ACP server with. Defaults to `['acp']`. Note: no
34
+ * explicit `--cwd` — opencode acp defaults --cwd to the process cwd, which
35
+ * the transport already sets to the workspace (cli/cmd/acp.ts:13-17). */
36
+ acpArgs?: string[];
37
+ /** Request timeout in milliseconds. */
38
+ requestTimeoutMs?: number;
39
+ /** Idle TTL for connection manager. */
40
+ idleTtlMs?: number;
41
+ /** How long to wait for turn output before failing. Defaults to 10 min. */
42
+ turnIdleTimeoutMs?: number;
43
+ model?: string;
44
+ /** Configured session mode: 'build' (default) or 'plan' (opencode agent names). */
45
+ mode?: 'build' | 'plan';
46
+ }
47
+ export declare class OpencodeAcpRunner extends ConnectionBasedRunner<JsonRpcClient, OpencodeAcpTranslatorEvent> {
48
+ private connectionManager;
49
+ private currentTranslator;
50
+ private activeSessionId;
51
+ private model?;
52
+ /** Configured session mode applied via session/set_mode (§P5). */
53
+ private configuredMode;
54
+ /** Current session mode id, parsed from session/new|resume configOptions. */
55
+ private currentModeId?;
56
+ /** Current model value (`provider/model`), parsed from configOptions. */
57
+ private currentModelValue?;
58
+ /** Pending approval requests: requestId → kind + view + options. */
59
+ private pendingApprovals;
60
+ /** Tracker for the in-flight prompt request — needed for cancellation. */
61
+ private promptSettled;
62
+ /** Whether the prompt request has been fired (distinguishes "stop before
63
+ * prompt sent" from "stop while prompt in-flight" — the latter sends
64
+ * session/cancel immediately). */
65
+ private promptSent;
66
+ constructor(opts: OpencodeAcpRunnerOptions);
67
+ protected get logTag(): string;
68
+ protected get turnTimeoutErrorMessage(): string;
69
+ protected get turnInterruptedErrorMessage(): string;
70
+ protected currentSessionId(): string | null;
71
+ protected shouldDeferStop(): boolean;
72
+ protected cancelCurrentTurn(): Promise<void>;
73
+ protected clearTurnState(): void;
74
+ protected releaseConnection(cwd: string): Promise<void>;
75
+ protected notifyIdle(cwd: string): void;
76
+ protected disposeConnections(): Promise<void>;
77
+ /**
78
+ * Run a compact operation on the current session.
79
+ *
80
+ * opencode special-cases the `/compact` text prompt server-side
81
+ * (service.ts:555-571): it runs session.summarize synchronously and the
82
+ * prompt response settles only after compaction completes — no background
83
+ * record polling needed (unlike kimi).
84
+ */
85
+ runCompact(_message: string, opts: SpawnOptions): AsyncGenerator<AgentEvent>;
86
+ /**
87
+ * Respond to an approval server request. `response` is the bridge
88
+ * ApprovalAction (`{ action: 'accept' | 'decline' | 'cancel' }`).
89
+ */
90
+ respondApproval(requestId: number | string, response: unknown): Promise<void>;
91
+ getStatusInfo(): AgentStatusInfo;
92
+ /**
93
+ * Hot-apply a mode change to the live ACP session (§P5).
94
+ *
95
+ * opencode's session/set_mode sends no notification — the client refreshes
96
+ * the local mode view itself. The local mode is always updated so
97
+ * `getStatusInfo()` (and therefore `/s`) reflects the new value
98
+ * immediately; when a session is connected, also re-sends session/set_mode
99
+ * so the running session picks up the new mode without waiting for the
100
+ * runner to be evicted/recreated. Failure is non-fatal: the next setupTurn
101
+ * re-applies the cached mode when it differs from the session's current
102
+ * mode.
103
+ */
104
+ updateApprovalMode(settings: {
105
+ mode?: 'build' | 'plan';
106
+ }): Promise<void>;
107
+ /**
108
+ * Acquire the connection and set up the turn:
109
+ * session/new (new session) or session/resume (existing sessionId)
110
+ * → session/prompt.
111
+ */
112
+ protected setupTurn(message: string, opts: SpawnOptions): Promise<void>;
113
+ /**
114
+ * Track the current session mode and model from a session/new|resume
115
+ * result's configOptions (opencode buildModeSelectOption/buildModelSelectOption:
116
+ * {id, category, currentValue}).
117
+ */
118
+ private trackConfigOptions;
119
+ private handleNotification;
120
+ private handleServerRequest;
121
+ }