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