@kafca/agentdock 0.1.28 → 0.1.30

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 (70) hide show
  1. package/README.md +5 -0
  2. package/dist/renderer/assets/{Badge-B-Ayib4h.js → Badge-DlVJI1Ys.js} +1 -1
  3. package/dist/renderer/assets/{Chat-CNEOxtyz.js → Chat-oa8OjaHQ.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-BTd1QBj9.js → ChatMarkdown-C-N8KS5C.js} +1 -1
  5. package/dist/renderer/assets/{Config-ByKhcLa6.js → Config-D9sL_d4D.js} +1 -1
  6. package/dist/renderer/assets/{CronList-0mi8dcC5.js → CronList-Z5JU3DHk.js} +1 -1
  7. package/dist/renderer/assets/{Dashboard-C8-tB_qg.js → Dashboard-D3BwbG43.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-TR-9nr5e.js → EmptyState-QOj765Ul.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-DKAxFOXc.js → KnowledgeDetail-J7n01mPu.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeHome-CM-1IDr4.js → KnowledgeHome-BFnH0NVk.js} +1 -1
  11. package/dist/renderer/assets/{Logs-DI5RFPri.js → Logs-DfSUifXc.js} +1 -1
  12. package/dist/renderer/assets/{Modal-CQpa4NPP.js → Modal-QjEGhyru.js} +1 -1
  13. package/dist/renderer/assets/{Page-BHXEAwzQ.js → Page-qdHFSw7x.js} +1 -1
  14. package/dist/renderer/assets/{ProjectDetail-Dn5elnHx.js → ProjectDetail-DS_rfvhh.js} +1 -1
  15. package/dist/renderer/assets/{ProjectList-D6yvEgb5.js → ProjectList-COtyZd7H.js} +1 -1
  16. package/dist/renderer/assets/{Select-BbkcXnXL.js → Select-BQ8mncVu.js} +1 -1
  17. package/dist/renderer/assets/{SessionChat-Vkf-7gvX.js → SessionChat-DjVnjuQy.js} +1 -1
  18. package/dist/renderer/assets/{SessionList-c5encjWd.js → SessionList-88dgGZJ8.js} +1 -1
  19. package/dist/renderer/assets/ThreadChat-BCdbTxBo.js +23 -0
  20. package/dist/renderer/assets/{Workspace-BC7hWqhP.js → Workspace-D_hzSJjX.js} +1 -1
  21. package/dist/renderer/assets/{arrow-left-I_Y1kdN9.js → arrow-left-BMpknulW.js} +1 -1
  22. package/dist/renderer/assets/{book-open-B3anUYvp.js → book-open-CiaOeQbU.js} +1 -1
  23. package/dist/renderer/assets/{check-BPNCDKgS.js → check-CgwYH4GF.js} +1 -1
  24. package/dist/renderer/assets/{chevron-down-BaxUcOIV.js → chevron-down-BzD2S1OO.js} +1 -1
  25. package/dist/renderer/assets/{circle-DJQiypFS.js → circle-BDHD7g7l.js} +1 -1
  26. package/dist/renderer/assets/{desktop-Cv51clcN.js → desktop-BiR5LPL5.js} +1 -1
  27. package/dist/renderer/assets/{funnel-DqKBtiqE.js → funnel-Bruf5FYA.js} +1 -1
  28. package/dist/renderer/assets/{heart-CmoNjvMO.js → heart-D7Mz04B0.js} +1 -1
  29. package/dist/renderer/assets/{index-DXJl2HOK.js → index-CL1e8m3U.js} +2 -2
  30. package/dist/renderer/assets/{index-BfXqhQ1Z.js → index-gZIRr4_o.js} +1 -1
  31. package/dist/renderer/assets/{pencil-CLUBszbw.js → pencil-C6gzDMse.js} +1 -1
  32. package/dist/renderer/assets/{play-l2RPZuAi.js → play-DqTXX-Pr.js} +1 -1
  33. package/dist/renderer/assets/{plug-CeUPIjRt.js → plug-ykmbeaO3.js} +1 -1
  34. package/dist/renderer/assets/{plus-BjkefS2P.js → plus-DHE60vMX.js} +1 -1
  35. package/dist/renderer/assets/projects-Bx5p10th.js +1 -0
  36. package/dist/renderer/assets/{save-D0yYt9tz.js → save-CGIvrdQI.js} +1 -1
  37. package/dist/renderer/assets/{search-B7s473Y4.js → search-D7IFAwyE.js} +1 -1
  38. package/dist/renderer/assets/{server-Dz_h8X_8.js → server-aOsirp7I.js} +1 -1
  39. package/dist/renderer/assets/{session-utils-BvNRTRXA.js → session-utils-Dxfp4ohQ.js} +1 -1
  40. package/dist/renderer/assets/{sessions-KPJsgKbQ.js → sessions-Db1XPbG2.js} +1 -1
  41. package/dist/renderer/assets/{shield-check-Wc5wfUUs.js → shield-check-ChXeQK-d.js} +1 -1
  42. package/dist/renderer/assets/{trash-2-7INdaB9w.js → trash-2-CfN-c1zw.js} +1 -1
  43. package/dist/renderer/assets/{user-CLbCgWNR.js → user-C7OHaBp4.js} +1 -1
  44. package/dist/renderer/assets/{wifi-off-BfDVPXbO.js → wifi-off-CpaJigye.js} +1 -1
  45. package/dist/renderer/index.html +1 -1
  46. package/dist-electron/electron/agent-runtime-detector.test.js +20 -0
  47. package/dist-electron/electron/lac-cli.test.js +2 -13
  48. package/dist-electron/electron/local-core-refactor.test.js +424 -64
  49. package/dist-electron/electron/workspace-task-store.test.js +118 -0
  50. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +17 -10
  51. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-progress.js +245 -9
  52. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +24 -11
  53. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +36 -11
  54. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-transport.js +2 -0
  55. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +90 -7
  56. package/dist-electron/services/local-ai-core/src/channel/lark/runtime-state.js +35 -40
  57. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +43 -18
  58. package/dist-electron/services/local-ai-core/src/cli/lac.js +1 -17
  59. package/dist-electron/services/local-ai-core/src/router/workspace-router.js +2 -2
  60. package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +1 -1
  61. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +31 -1
  62. package/dist-electron/services/local-ai-core/src/scheduler/local-schedule-adapter.js +4 -1
  63. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-conversation-executor.js +4 -1
  64. package/dist-electron/src/pages/Threads/thread-chat-message-blocks.js +4 -114
  65. package/dist-electron/src/pages/Threads/thread-chat-model.js +14 -23
  66. package/dist-electron/src/pages/Threads/thread-chat-page-state.js +1 -1
  67. package/dist-electron/src/pages/Threads/thread-chat-permission.test.js +58 -31
  68. package/package.json +1 -1
  69. package/dist/renderer/assets/ThreadChat-DY4wzomi.js +0 -23
  70. package/dist/renderer/assets/projects-De73QY_o.js +0 -1
@@ -41,6 +41,8 @@ class LocalCoreAcpStore {
41
41
  role TEXT NOT NULL,
42
42
  content TEXT NOT NULL,
43
43
  tool_call_json TEXT,
44
+ bridge_kind TEXT,
45
+ bridge_status TEXT,
44
46
  timestamp TEXT NOT NULL,
45
47
  kind TEXT NOT NULL,
46
48
  seq INTEGER NOT NULL,
@@ -222,6 +224,8 @@ class LocalCoreAcpStore {
222
224
  CREATE INDEX IF NOT EXISTS idx_audit_events_type_created ON audit_events (type, created_at DESC);
223
225
  `);
224
226
  this.ensureColumn('messages', 'tool_call_json', 'TEXT');
227
+ this.ensureColumn('messages', 'bridge_kind', 'TEXT');
228
+ this.ensureColumn('messages', 'bridge_status', 'TEXT');
225
229
  this.ensureColumn('scheduled_jobs', 'execution_mode', "TEXT NOT NULL DEFAULT 'same-thread'");
226
230
  this.ensureColumn('threads', 'agent_mode', "TEXT NOT NULL DEFAULT 'default'");
227
231
  }
@@ -284,7 +288,7 @@ class LocalCoreAcpStore {
284
288
  throw new Error(`Thread not found: ${threadId}`);
285
289
  }
286
290
  const messages = this.db.prepare(`
287
- SELECT id, thread_id, role, content, tool_call_json, timestamp, kind, seq
291
+ SELECT id, thread_id, role, content, tool_call_json, bridge_kind, bridge_status, timestamp, kind, seq
288
292
  FROM messages
289
293
  WHERE thread_id = ?
290
294
  ORDER BY seq ASC
@@ -305,6 +309,8 @@ class LocalCoreAcpStore {
305
309
  role: message.role,
306
310
  content: message.content,
307
311
  toolCall: parseJson(message.tool_call_json || 'null', null) || undefined,
312
+ bridgeKind: normalizeBridgeKind(message.bridge_kind),
313
+ bridgeStatus: normalizeBridgeStatus(message.bridge_status),
308
314
  timestamp: message.timestamp,
309
315
  kind: message.kind,
310
316
  })),
@@ -319,7 +325,7 @@ class LocalCoreAcpStore {
319
325
  deleteThread(threadId) {
320
326
  this.db.prepare('DELETE FROM threads WHERE id = ?').run(threadId);
321
327
  }
322
- appendMessage(threadId, role, content, kind, toolCall) {
328
+ appendMessage(threadId, role, content, kind, toolCall, bridgeKind, bridgeStatus) {
323
329
  const timestamp = new Date().toISOString();
324
330
  const nextSequenceRow = this.db.prepare('SELECT COALESCE(MAX(seq), -1) + 1 AS next_seq FROM messages WHERE thread_id = ?').get(threadId);
325
331
  const nextSeq = Number(nextSequenceRow?.next_seq || 0);
@@ -328,9 +334,9 @@ class LocalCoreAcpStore {
328
334
  this.db.exec('BEGIN IMMEDIATE');
329
335
  try {
330
336
  this.db.prepare(`
331
- INSERT INTO messages (id, thread_id, role, content, tool_call_json, timestamp, kind, seq)
332
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
333
- `).run(id, threadId, role, content, toolCall ? JSON.stringify(toolCall) : null, timestamp, kind, nextSeq);
337
+ INSERT INTO messages (id, thread_id, role, content, tool_call_json, bridge_kind, bridge_status, timestamp, kind, seq)
338
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
339
+ `).run(id, threadId, role, content, toolCall ? JSON.stringify(toolCall) : null, bridgeKind || null, bridgeStatus || null, timestamp, kind, nextSeq);
334
340
  this.db.prepare(`
335
341
  UPDATE threads
336
342
  SET updated_at = ?, history_count = history_count + 1, excerpt = ?
@@ -344,7 +350,7 @@ class LocalCoreAcpStore {
344
350
  }
345
351
  return { id, timestamp };
346
352
  }
347
- upsertMessage(threadId, id, role, content, kind, toolCall) {
353
+ upsertMessage(threadId, id, role, content, kind, toolCall, bridgeKind, bridgeStatus) {
348
354
  const timestamp = new Date().toISOString();
349
355
  const excerpt = (0, workspace_thread_mappers_js_1.normalizeMessageContent)(content);
350
356
  const existing = this.db.prepare('SELECT id FROM messages WHERE id = ? AND thread_id = ?').get(id, threadId);
@@ -353,17 +359,17 @@ class LocalCoreAcpStore {
353
359
  if (existing) {
354
360
  this.db.prepare(`
355
361
  UPDATE messages
356
- SET content = ?, tool_call_json = ?, timestamp = ?, kind = ?
362
+ SET content = ?, tool_call_json = ?, bridge_kind = ?, bridge_status = ?, timestamp = ?, kind = ?
357
363
  WHERE id = ? AND thread_id = ?
358
- `).run(content, toolCall ? JSON.stringify(toolCall) : null, timestamp, kind, id, threadId);
364
+ `).run(content, toolCall ? JSON.stringify(toolCall) : null, bridgeKind || null, bridgeStatus || null, timestamp, kind, id, threadId);
359
365
  }
360
366
  else {
361
367
  const nextSequenceRow = this.db.prepare('SELECT COALESCE(MAX(seq), -1) + 1 AS next_seq FROM messages WHERE thread_id = ?').get(threadId);
362
368
  const nextSeq = Number(nextSequenceRow?.next_seq || 0);
363
369
  this.db.prepare(`
364
- INSERT INTO messages (id, thread_id, role, content, tool_call_json, timestamp, kind, seq)
365
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
366
- `).run(id, threadId, role, content, toolCall ? JSON.stringify(toolCall) : null, timestamp, kind, nextSeq);
370
+ INSERT INTO messages (id, thread_id, role, content, tool_call_json, bridge_kind, bridge_status, timestamp, kind, seq)
371
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
372
+ `).run(id, threadId, role, content, toolCall ? JSON.stringify(toolCall) : null, bridgeKind || null, bridgeStatus || null, timestamp, kind, nextSeq);
367
373
  this.db.prepare(`
368
374
  UPDATE threads
369
375
  SET history_count = history_count + 1
@@ -830,6 +836,9 @@ class LocalCoreAcpStore {
830
836
  return row ? this.toScheduledJob(row) : undefined;
831
837
  }
832
838
  createScheduledJob(input) {
839
+ if (!input.platform || !input.route) {
840
+ throw new Error('Scheduled job creation requires a resolved platform and route.');
841
+ }
833
842
  let id = (0, job_id_js_1.createScheduledJobId)();
834
843
  for (let attempt = 0; attempt < 5 && this.getScheduledJob(id); attempt += 1) {
835
844
  id = (0, job_id_js_1.createScheduledJobId)();
@@ -1301,6 +1310,22 @@ function parseJson(value, fallback) {
1301
1310
  return fallback;
1302
1311
  }
1303
1312
  }
1313
+ function normalizeBridgeKind(value) {
1314
+ switch (value) {
1315
+ case 'assistant':
1316
+ case 'thought':
1317
+ case 'plan':
1318
+ case 'tool':
1319
+ case 'status':
1320
+ case 'permission':
1321
+ return value;
1322
+ default:
1323
+ return undefined;
1324
+ }
1325
+ }
1326
+ function normalizeBridgeStatus(value) {
1327
+ return value === 'awaiting_input' ? value : undefined;
1328
+ }
1304
1329
  function defaultPermissions() {
1305
1330
  return {
1306
1331
  'workspace.read': 'allow',
@@ -35,9 +35,11 @@ class LocalCoreAcpTransport {
35
35
  loadReplayMode: false,
36
36
  pendingPermissionByRun: new Map(),
37
37
  schedulerJobCreatedByRun: new Map(),
38
+ pendingRawAssistantProgressChunks: [],
38
39
  closed: false,
39
40
  closeReason: null,
40
41
  promptPromise: null,
42
+ launchPermissionMode: '',
41
43
  };
42
44
  child.stdout.setEncoding('utf8');
43
45
  child.stdout.on('data', (chunk) => this.handleStdout(session, chunk));
@@ -11,6 +11,11 @@ class LocalCoreAcpTurnCoordinator {
11
11
  constructor(options) {
12
12
  this.options = options;
13
13
  }
14
+ closePendingThoughtSegment(session) {
15
+ const currentTurn = session.currentTurn;
16
+ if (currentTurn)
17
+ (0, local_core_acp_progress_js_1.closeThoughtSegment)(currentTurn);
18
+ }
14
19
  flushPendingToolCall(session) {
15
20
  const currentTurn = session.currentTurn;
16
21
  const currentRunId = session.currentRunId;
@@ -97,6 +102,7 @@ class LocalCoreAcpTurnCoordinator {
97
102
  }
98
103
  const options = (0, local_core_acp_permission_lifecycle_js_1.parsePermissionOptions)(payload.params?.options);
99
104
  const toolTitle = (0, workspace_acp_permissions_js_1.formatToolCallContent)(payload.params?.toolCall);
105
+ this.closePendingThoughtSegment(session);
100
106
  if ((this.options.getThreadAgentMode?.(session.threadId) || local_core_slash_commands_js_1.DEFAULT_AGENT_MODE) === 'bypassPermissions') {
101
107
  const selected = options.find((option) => option.normalizedAction === 'allow all')
102
108
  || options.find((option) => option.normalizedAction === 'allow')
@@ -160,17 +166,26 @@ class LocalCoreAcpTurnCoordinator {
160
166
  });
161
167
  this.options.updateRunStatus(currentRunId, session.threadId, 'awaiting_input');
162
168
  const permissionPrompt = (0, local_core_acp_permission_lifecycle_js_1.createPermissionPrompt)(toolTitle);
163
- this.options.appendMessage(session.threadId, 'assistant', permissionPrompt, 'progress');
169
+ this.options.appendMessage(session.threadId, 'assistant', permissionPrompt, 'progress', undefined, 'permission', 'awaiting_input');
164
170
  this.options.emitBridge({
165
171
  type: 'buttons',
166
172
  sessionKey: session.bridgeSessionKey,
167
173
  replyCtx: currentRunId,
174
+ bridgeKind: 'permission',
175
+ bridgeStatus: 'awaiting_input',
168
176
  content: permissionPrompt,
169
177
  buttonRows,
170
178
  });
171
179
  }
172
180
  handleAgentNotification(session, payload) {
173
- if (session.loadReplayMode || payload.method !== 'session/update') {
181
+ if (session.loadReplayMode) {
182
+ return;
183
+ }
184
+ if (payload.method === '_claude/sdkMessage') {
185
+ this.handleClaudeSdkMessage(session, payload.params?.message);
186
+ return;
187
+ }
188
+ if (payload.method !== 'session/update') {
174
189
  return;
175
190
  }
176
191
  const update = payload.params?.update;
@@ -181,10 +196,25 @@ class LocalCoreAcpTurnCoordinator {
181
196
  }
182
197
  switch (String(update.sessionUpdate || '')) {
183
198
  case 'agent_message_chunk': {
199
+ this.closePendingThoughtSegment(session);
184
200
  this.flushPendingToolCall(session);
185
201
  if (update.content?.type !== 'text') {
186
202
  return;
187
203
  }
204
+ if (isToolScopedAssistantUpdate(update) || consumeRawAssistantProgressChunk(session, String(update.content.text || ''))) {
205
+ const content = String(update.content.text || '').trim();
206
+ if (content) {
207
+ this.options.appendMessage(session.threadId, 'assistant', content, 'progress', undefined, 'tool');
208
+ this.options.emitBridge({
209
+ type: 'reply',
210
+ sessionKey: session.bridgeSessionKey,
211
+ replyCtx: currentRunId,
212
+ bridgeKind: 'tool',
213
+ content,
214
+ });
215
+ }
216
+ return;
217
+ }
188
218
  const projection = (0, local_core_acp_progress_js_1.applyAssistantMessageChunk)(currentTurn, String(update.content.text || ''));
189
219
  if (!projection) {
190
220
  return;
@@ -194,12 +224,12 @@ class LocalCoreAcpTurnCoordinator {
194
224
  sessionKey: session.bridgeSessionKey,
195
225
  replyCtx: currentRunId,
196
226
  previewHandle: projection.previewHandle,
227
+ bridgeKind: projection.bridgeKind,
197
228
  content: projection.content,
198
229
  });
199
230
  return;
200
231
  }
201
232
  case 'agent_thought_chunk': {
202
- this.flushPendingToolCall(session);
203
233
  if (update.content?.type !== 'text') {
204
234
  return;
205
235
  }
@@ -208,23 +238,31 @@ class LocalCoreAcpTurnCoordinator {
208
238
  return;
209
239
  }
210
240
  if (this.options.upsertMessage) {
211
- this.options.upsertMessage(session.threadId, projection.messageId, 'assistant', projection.content, 'progress');
241
+ this.options.upsertMessage(session.threadId, projection.messageId, 'assistant', projection.content, 'progress', undefined, projection.bridgeKind);
212
242
  }
213
243
  this.options.emitBridge({
214
244
  type: projection.bridgeType,
215
245
  sessionKey: session.bridgeSessionKey,
216
246
  replyCtx: currentRunId,
217
247
  previewHandle: projection.previewHandle,
248
+ bridgeKind: projection.bridgeKind,
218
249
  content: projection.content,
219
250
  });
220
251
  return;
221
252
  }
222
253
  case 'tool_call': {
254
+ this.closePendingThoughtSegment(session);
223
255
  const toolCall = (0, local_core_acp_progress_js_1.registerPendingToolCall)({ currentTurn, runId: currentRunId, update });
256
+ (0, local_core_acp_progress_js_1.recordToolObservation)(currentTurn, {
257
+ name: toolCall.title,
258
+ title: toolCall.title,
259
+ input: toolCall.input,
260
+ });
224
261
  (0, local_core_acp_progress_js_1.syncLegacyPendingToolCall)(currentTurn, toolCall);
225
262
  return;
226
263
  }
227
264
  case 'tool_call_update': {
265
+ this.closePendingThoughtSegment(session);
228
266
  const title = String(update.title || 'Tool update').trim();
229
267
  const status = String(update.status || '').trim();
230
268
  const content = (0, local_core_acp_progress_js_1.extractToolUpdateContent)(update.content);
@@ -276,11 +314,19 @@ class LocalCoreAcpTurnCoordinator {
276
314
  detail: displayTitle,
277
315
  content,
278
316
  });
317
+ (0, local_core_acp_progress_js_1.recordToolObservation)(currentTurn, {
318
+ name: toolName || displayTitle || title,
319
+ title: displayTitle || title,
320
+ status,
321
+ input: updateInput === undefined ? toolCall?.input : updateInput,
322
+ outputText: content,
323
+ });
279
324
  this.emitProgress(session, currentRunId, (0, local_core_acp_progress_js_1.formatToolProgressMessage)({ toolName, title: displayTitle, status, content }), messageId, toolCallPayload);
280
325
  (0, local_core_acp_progress_js_1.syncLegacyPendingToolCall)(currentTurn, (0, local_core_acp_progress_js_1.resolveFallbackToolCall)(currentTurn));
281
326
  return;
282
327
  }
283
328
  case 'plan': {
329
+ this.closePendingThoughtSegment(session);
284
330
  this.flushPendingToolCall(session);
285
331
  const entries = Array.isArray(update.entries) ? update.entries : [];
286
332
  if (entries.length === 0) {
@@ -290,11 +336,12 @@ class LocalCoreAcpTurnCoordinator {
290
336
  if (!content) {
291
337
  return;
292
338
  }
293
- this.options.appendMessage(session.threadId, 'assistant', content, 'progress');
339
+ this.options.appendMessage(session.threadId, 'assistant', content, 'progress', undefined, 'plan');
294
340
  this.options.emitBridge({
295
341
  type: 'reply',
296
342
  sessionKey: session.bridgeSessionKey,
297
343
  replyCtx: currentRunId,
344
+ bridgeKind: 'plan',
298
345
  content,
299
346
  });
300
347
  return;
@@ -303,24 +350,60 @@ class LocalCoreAcpTurnCoordinator {
303
350
  return;
304
351
  }
305
352
  }
353
+ handleClaudeSdkMessage(session, message) {
354
+ if (!session.currentTurn || message?.type !== 'system' || message?.subtype !== 'local_command_output') {
355
+ return;
356
+ }
357
+ const content = typeof message.content === 'string' ? message.content : '';
358
+ if (!content) {
359
+ return;
360
+ }
361
+ session.pendingRawAssistantProgressChunks = [
362
+ ...(session.pendingRawAssistantProgressChunks || []),
363
+ content,
364
+ ];
365
+ }
306
366
  emitProgress(session, currentRunId, content, messageId, toolCall) {
307
367
  if (messageId && this.options.upsertMessage) {
308
- this.options.upsertMessage(session.threadId, messageId, 'assistant', content, 'progress', toolCall);
368
+ this.options.upsertMessage(session.threadId, messageId, 'assistant', content, 'progress', toolCall, toolCall ? 'tool' : undefined);
309
369
  }
310
370
  else {
311
- this.options.appendMessage(session.threadId, 'assistant', content, 'progress', toolCall);
371
+ this.options.appendMessage(session.threadId, 'assistant', content, 'progress', toolCall, toolCall ? 'tool' : undefined);
312
372
  }
313
373
  this.options.emitBridge({
314
374
  type: 'reply',
315
375
  sessionKey: session.bridgeSessionKey,
316
376
  replyCtx: currentRunId,
317
377
  messageId,
378
+ bridgeKind: toolCall ? 'tool' : undefined,
318
379
  content,
319
380
  toolCall,
320
381
  });
321
382
  }
322
383
  }
323
384
  exports.LocalCoreAcpTurnCoordinator = LocalCoreAcpTurnCoordinator;
385
+ function isToolScopedAssistantUpdate(update) {
386
+ const meta = update._meta;
387
+ if (!meta || typeof meta !== 'object') {
388
+ return false;
389
+ }
390
+ const claudeCode = meta.claudeCode;
391
+ if (!claudeCode || typeof claudeCode !== 'object') {
392
+ return false;
393
+ }
394
+ const parentToolUseId = claudeCode.parentToolUseId;
395
+ return typeof parentToolUseId === 'string' && parentToolUseId.trim().length > 0;
396
+ }
397
+ function consumeRawAssistantProgressChunk(session, text) {
398
+ const pending = session.pendingRawAssistantProgressChunks || [];
399
+ const index = pending.findIndex((candidate) => candidate === text);
400
+ if (index < 0) {
401
+ return false;
402
+ }
403
+ pending.splice(index, 1);
404
+ session.pendingRawAssistantProgressChunks = pending;
405
+ return true;
406
+ }
324
407
  function createToolCallPayload(input) {
325
408
  const status = input.status.trim() || 'running';
326
409
  return {
@@ -31,31 +31,25 @@ function createLarkTurnState(sessionKey, sourceMessageId) {
31
31
  return turn;
32
32
  }
33
33
  function foldToolResultForLark(content) {
34
- const normalized = String(content || '').trim();
35
- if (!normalized.startsWith('🔧 '))
36
- return normalized;
37
- const parts = normalized.split(' - ');
38
- if (parts.length <= 2)
39
- return normalized;
40
- return parts.slice(0, 2).join(' - ');
34
+ return String(content || '').trim();
41
35
  }
42
36
  function summarizeToolCallForLark(event, content) {
43
37
  const toolCall = event.toolCall;
44
38
  if (toolCall) {
45
39
  const name = String(toolCall.name || '').trim() || summarizeToolContentForLark(content);
46
40
  const input = formatToolInputForLark(toolCall.input);
47
- return [name.startsWith('🔧 ') ? name : `🔧 ${name}`, input ? `参数:${input}` : '']
41
+ return [`🔧 ${name}`, input ? `参数:${input}` : '']
48
42
  .filter(Boolean)
49
43
  .join('\n\n');
50
44
  }
51
45
  return summarizeToolContentForLark(content);
52
46
  }
53
- function isPendingToolProgressForLark(content) {
54
- const normalized = foldToolResultForLark(content).toLowerCase();
55
- return normalized.startsWith('🔧 ') && normalized.includes(' - pending');
47
+ function isPendingToolProgressForLark(event) {
48
+ return event.toolCall?.status.trim().toLowerCase() === 'pending';
56
49
  }
57
50
  function consumeLarkBridgeEvent(turn, event, options) {
58
51
  const content = foldToolResultForLark(String(event.content || '').trim());
52
+ const bridgeKind = resolveBridgeEventKind(event);
59
53
  if (event.type === 'typing_start') {
60
54
  turn.messageId = undefined;
61
55
  turn.finalMessageId = undefined;
@@ -80,10 +74,10 @@ function consumeLarkBridgeEvent(turn, event, options) {
80
74
  return;
81
75
  }
82
76
  if (event.type === 'preview_start' || event.type === 'update_message') {
83
- if (content.startsWith('💭 ')) {
77
+ if (bridgeKind === 'thought') {
84
78
  turn.pendingThoughtKey = event.previewHandle || 'thinking-preview';
85
79
  turn.pendingThoughtText = content;
86
- pushUniqueLarkTurnLine(turn.thinkingSteps, content.slice(3).trim());
80
+ pushUniqueLarkTurnLine(turn.thinkingSteps, content);
87
81
  return;
88
82
  }
89
83
  turn.previewText = content;
@@ -106,17 +100,17 @@ function consumeLarkBridgeEvent(turn, event, options) {
106
100
  if (!content) {
107
101
  return;
108
102
  }
109
- if (content.startsWith('💭 ')) {
103
+ if (bridgeKind === 'thought' || bridgeKind === 'plan') {
110
104
  turn.pendingThoughtKey = progressKey('thinking', event);
111
105
  turn.pendingThoughtText = content;
112
- pushUniqueLarkTurnLine(turn.thinkingSteps, content.slice(3).trim());
106
+ pushUniqueLarkTurnLine(turn.thinkingSteps, content);
113
107
  return;
114
108
  }
115
- if (content.startsWith('🔧 ')) {
109
+ if (bridgeKind === 'tool') {
116
110
  pushUniqueLarkTurnLine(turn.toolCalls, content.slice(3).trim());
117
111
  return;
118
112
  }
119
- if (content.startsWith('⏳ ') || content.startsWith('📤 ')) {
113
+ if (bridgeKind === 'status') {
120
114
  pushUniqueLarkTurnLine(turn.statusLines, content.slice(3).trim());
121
115
  return;
122
116
  }
@@ -125,12 +119,13 @@ function consumeLarkBridgeEvent(turn, event, options) {
125
119
  }
126
120
  function renderLarkBridgeEventMessage(turn, event) {
127
121
  const content = foldToolResultForLark(String(event.content || '').trim());
122
+ const bridgeKind = resolveBridgeEventKind(event);
128
123
  if (event.type === 'preview_start' || event.type === 'update_message') {
129
- if (content.startsWith('💭 ')) {
124
+ if (bridgeKind === 'thought') {
130
125
  return { key: 'noop', text: '', buttonRows: [], isFinal: false };
131
126
  }
132
- if (content.startsWith('🔧 ')) {
133
- if (isPendingToolProgressForLark(content)) {
127
+ if (bridgeKind === 'tool') {
128
+ if (isPendingToolProgressForLark(event)) {
134
129
  return { key: 'noop', text: '', buttonRows: [], isFinal: false };
135
130
  }
136
131
  return renderToolMessage(progressKey('tool', event), summarizeToolCallForLark(event, content));
@@ -138,19 +133,19 @@ function renderLarkBridgeEventMessage(turn, event) {
138
133
  return { key: 'noop', text: '', buttonRows: [], isFinal: false };
139
134
  }
140
135
  if (event.type === 'status') {
141
- return renderProgressMessage(progressKey('status', event), content.startsWith('⏳ ') ? content : `⏳ ${content}`);
136
+ return renderProgressMessage(progressKey('status', event), `⏳ ${content}`);
142
137
  }
143
138
  if (event.type === 'reply') {
144
- if (content.startsWith('💭 ')) {
145
- return { key: 'noop', text: '', buttonRows: [], isFinal: false };
139
+ if (bridgeKind === 'thought' || bridgeKind === 'plan') {
140
+ return renderProgressMessage(progressKey(bridgeKind, event), renderProcessText(bridgeKind, content));
146
141
  }
147
- if (content.startsWith('🔧 ')) {
148
- if (isPendingToolProgressForLark(content)) {
142
+ if (bridgeKind === 'tool') {
143
+ if (isPendingToolProgressForLark(event)) {
149
144
  return { key: 'noop', text: '', buttonRows: [], isFinal: false };
150
145
  }
151
146
  return renderToolMessage(progressKey('tool', event), summarizeToolCallForLark(event, content));
152
147
  }
153
- if (content.startsWith('⏳ ') || content.startsWith('📤 ')) {
148
+ if (bridgeKind === 'status') {
154
149
  return renderProgressMessage(progressKey('status', event), content);
155
150
  }
156
151
  return {
@@ -186,14 +181,14 @@ function takePendingLarkThoughtRender(turn) {
186
181
  }
187
182
  turn.thoughtSegmentSequence += 1;
188
183
  const baseKey = turn.pendingThoughtKey || 'thinking-preview';
189
- const rendered = renderProgressMessage(`thinking:${baseKey}:${turn.thoughtSegmentSequence}`, text);
184
+ const rendered = renderProgressMessage(`thinking:${baseKey}:${turn.thoughtSegmentSequence}`, renderProcessText('thought', text));
190
185
  turn.pendingThoughtKey = undefined;
191
186
  turn.pendingThoughtText = undefined;
192
187
  return rendered;
193
188
  }
194
189
  function getLarkRenderedMessageId(turn, rendered) {
195
190
  if (rendered.isFinal) {
196
- return turn.finalMessageId || turn.replyMessageId;
191
+ return undefined;
197
192
  }
198
193
  return turn.progressMessageIds[rendered.key];
199
194
  }
@@ -229,21 +224,21 @@ function progressKey(prefix, event) {
229
224
  return `${prefix}:${content.slice(0, 120)}`;
230
225
  }
231
226
  function isThoughtBridgeEvent(event) {
232
- if (event.type !== 'preview_start' && event.type !== 'update_message' && event.type !== 'reply') {
233
- return false;
227
+ const kind = resolveBridgeEventKind(event);
228
+ return kind === 'thought' || kind === 'plan';
229
+ }
230
+ function resolveBridgeEventKind(event) {
231
+ if (event.bridgeKind) {
232
+ return event.bridgeKind;
234
233
  }
235
- return String(event.content || '').trim().startsWith('💭 ');
234
+ return event.type === 'status' ? 'status' : 'assistant';
235
+ }
236
+ function renderProcessText(kind, content) {
237
+ const text = content.trim();
238
+ return kind === 'plan' ? `计划\n${text}` : text;
236
239
  }
237
240
  function summarizeToolContentForLark(content) {
238
- const normalized = String(content || '').trim();
239
- if (!normalized.startsWith('🔧 ')) {
240
- return normalized;
241
- }
242
- const withoutOutput = normalized.split(' - ')[0]?.trim() || normalized;
243
- return withoutOutput
244
- .replace(/\s+-\s*(running|pending|completed|failed|error|cancelled|canceled)\s*$/i, '')
245
- .replace(/:\s*(running|pending|completed|failed|error|cancelled|canceled)\s*$/i, '')
246
- .trim();
241
+ return String(content || '').trim();
247
242
  }
248
243
  function formatToolInputForLark(input) {
249
244
  if (input == null) {
@@ -130,16 +130,16 @@ function truncateTextByUtf8Bytes(text, maxBytes) {
130
130
  }
131
131
  return `${result.trim()}${suffix}`;
132
132
  }
133
- function stripToolResultForWeixin(content) {
134
- const normalized = String(content || '').trim();
135
- if (!normalized.startsWith('🔧 '))
136
- return normalized;
137
- const parts = normalized.split(' - ');
138
- if (parts[0] === '🔧 Tool update' && parts[1] === 'completed')
133
+ function renderBridgeContentForWeixin(event) {
134
+ const toolCall = event.toolCall;
135
+ if (!toolCall) {
136
+ return String(event.content || '').trim();
137
+ }
138
+ const name = String(toolCall.name || '').trim() || 'Tool update';
139
+ const status = String(toolCall.status || '').trim();
140
+ if (name === 'Tool update' && status === 'completed')
139
141
  return '';
140
- if (parts.length <= 2)
141
- return normalized;
142
- return parts.slice(0, 2).join(' - ');
142
+ return [name, status].filter(Boolean).join(' - ');
143
143
  }
144
144
  // ==================== Gateway Class ====================
145
145
  class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
@@ -1058,6 +1058,7 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
1058
1058
  previewText: '',
1059
1059
  finalText: '',
1060
1060
  thinkingSteps: [],
1061
+ pendingThoughtText: undefined,
1061
1062
  statusLines: [],
1062
1063
  buttonRows: [],
1063
1064
  lastSentAt: 0,
@@ -1070,12 +1071,14 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
1070
1071
  return this.outboundTurns.get(sessionKey) || this.createTurnState(sessionKey);
1071
1072
  }
1072
1073
  consumeBridgeEvent(turn, event) {
1073
- const content = stripToolResultForWeixin(String(event.content || '').trim());
1074
+ const content = renderBridgeContentForWeixin(event);
1075
+ const bridgeKind = this.resolveBridgeEventKind(event);
1074
1076
  if (event.type === 'typing_start') {
1075
1077
  turn.processing = true;
1076
1078
  turn.previewText = '';
1077
1079
  turn.finalText = '';
1078
1080
  turn.thinkingSteps = [];
1081
+ turn.pendingThoughtText = undefined;
1079
1082
  turn.statusLines = [];
1080
1083
  turn.buttonRows = [];
1081
1084
  return;
@@ -1085,9 +1088,16 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
1085
1088
  return;
1086
1089
  }
1087
1090
  if (event.type === 'preview_start' || event.type === 'update_message') {
1091
+ if (bridgeKind === 'thought') {
1092
+ turn.pendingThoughtText = content;
1093
+ return;
1094
+ }
1088
1095
  turn.previewText = content;
1089
1096
  return;
1090
1097
  }
1098
+ if (bridgeKind !== 'thought') {
1099
+ this.flushPendingThought(turn);
1100
+ }
1091
1101
  if (event.type === 'status') {
1092
1102
  if (content) {
1093
1103
  this.pushUnique(turn.statusLines, content);
@@ -1110,8 +1120,12 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
1110
1120
  }
1111
1121
  if (!content)
1112
1122
  return;
1113
- if (content.startsWith('💭 ')) {
1114
- this.pushUnique(turn.thinkingSteps, content.slice(3).trim());
1123
+ if (bridgeKind === 'thought' || bridgeKind === 'plan') {
1124
+ this.pushUnique(turn.thinkingSteps, content);
1125
+ return;
1126
+ }
1127
+ if (bridgeKind === 'tool' || bridgeKind === 'status') {
1128
+ this.pushUnique(turn.statusLines, content);
1115
1129
  return;
1116
1130
  }
1117
1131
  turn.finalText = content;
@@ -1120,7 +1134,7 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
1120
1134
  renderTurnText(turn) {
1121
1135
  const sections = [];
1122
1136
  if (turn.thinkingSteps.length > 0) {
1123
- sections.push(`**思考过程**\n${turn.thinkingSteps.map((step) => `• ${step.replace(/\s+/g, ' ').trim()}`).join('\n')}`);
1137
+ sections.push(`**中间过程**\n${turn.thinkingSteps.map((step) => `• ${step.replace(/\s+/g, ' ').trim()}`).join('\n')}`);
1124
1138
  }
1125
1139
  if (turn.finalText) {
1126
1140
  sections.push(turn.finalText);
@@ -1128,7 +1142,7 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
1128
1142
  else if (turn.previewText) {
1129
1143
  sections.push(turn.previewText);
1130
1144
  }
1131
- else if (turn.processing && turn.statusLines.length > 0) {
1145
+ else if (turn.statusLines.length > 0) {
1132
1146
  sections.push(`**处理中**\n${turn.statusLines.slice(-3).map((l) => `• ${l.replace(/\s+/g, ' ').trim()}`).join('\n')}`);
1133
1147
  }
1134
1148
  else if (turn.processing) {
@@ -1139,21 +1153,32 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
1139
1153
  }
1140
1154
  return sections.join('\n\n').trim();
1141
1155
  }
1156
+ flushPendingThought(turn) {
1157
+ const text = String(turn.pendingThoughtText || '').trim();
1158
+ if (!text)
1159
+ return;
1160
+ this.pushUnique(turn.thinkingSteps, text);
1161
+ turn.pendingThoughtText = undefined;
1162
+ }
1142
1163
  isTerminalBridgeMessage(event, rendered) {
1143
1164
  if (event.type === 'buttons')
1144
1165
  return true;
1145
1166
  if (event.type !== 'reply')
1146
1167
  return false;
1147
- const eventContent = String(event.content || '').trim();
1148
- if (eventContent.startsWith('🔧 ') || eventContent.startsWith('💭 '))
1168
+ const bridgeKind = this.resolveBridgeEventKind(event);
1169
+ if (bridgeKind === 'tool' || bridgeKind === 'thought' || bridgeKind === 'plan' || bridgeKind === 'status')
1149
1170
  return false;
1150
1171
  const normalized = rendered.trim();
1151
1172
  if (!normalized)
1152
1173
  return false;
1153
- if (normalized.startsWith('🔧 ') || normalized.startsWith('💭 '))
1154
- return false;
1155
1174
  return true;
1156
1175
  }
1176
+ resolveBridgeEventKind(event) {
1177
+ if (event.bridgeKind) {
1178
+ return event.bridgeKind;
1179
+ }
1180
+ return event.type === 'status' ? 'status' : 'assistant';
1181
+ }
1157
1182
  // ==================== Private: Helpers ====================
1158
1183
  pushUnique(target, value) {
1159
1184
  const normalized = value.trim();