@kafca/agentdock 0.1.29 → 0.1.31

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 (97) hide show
  1. package/README.md +4 -0
  2. package/dist/renderer/assets/{Badge-ngf0eCzI.js → Badge-DaHt-aUT.js} +1 -1
  3. package/dist/renderer/assets/{Chat-tCGY5odh.js → Chat-Dzeq3kiD.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-vqDWgVNb.js → ChatMarkdown-B-L5Roai.js} +1 -1
  5. package/dist/renderer/assets/{Config-YsVDQmIs.js → Config-BI4LJ7Fw.js} +1 -1
  6. package/dist/renderer/assets/{CronList-DQN0T7Yc.js → CronList-Dic7pblj.js} +1 -1
  7. package/dist/renderer/assets/{Dashboard-C2O6Yxq3.js → Dashboard-C0_ATyGe.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-CTkA-fBQ.js → EmptyState-SQsG1DZP.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-DjLZDItQ.js → KnowledgeDetail-D4yUJzp7.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeHome-2NHeRZxW.js → KnowledgeHome-L0tsg-oo.js} +1 -1
  11. package/dist/renderer/assets/{Logs-CQ6j-Sct.js → Logs-C2Jdr3uQ.js} +1 -1
  12. package/dist/renderer/assets/{Modal-9g1uWJfC.js → Modal-GUH3iPXo.js} +1 -1
  13. package/dist/renderer/assets/{Page-B4mPLn6n.js → Page-m42FBy97.js} +1 -1
  14. package/dist/renderer/assets/{ProjectDetail-pBXOAeL4.js → ProjectDetail-RF_sagPO.js} +1 -1
  15. package/dist/renderer/assets/{ProjectList-B9PYfZaS.js → ProjectList-Cyr8RDK3.js} +1 -1
  16. package/dist/renderer/assets/{Select-BWFQJ2R8.js → Select-CRlDgI5s.js} +1 -1
  17. package/dist/renderer/assets/{SessionChat-fBrzJs3A.js → SessionChat-_HhzMwEO.js} +1 -1
  18. package/dist/renderer/assets/{SessionList-DjHOsk7X.js → SessionList-DNwZjGTv.js} +1 -1
  19. package/dist/renderer/assets/ThreadChat-DxBmdsAm.js +23 -0
  20. package/dist/renderer/assets/{Workspace-nfTcxlhw.js → Workspace-DFphG85O.js} +1 -1
  21. package/dist/renderer/assets/{arrow-left-CIWZ6JEB.js → arrow-left-bILMzse7.js} +1 -1
  22. package/dist/renderer/assets/{book-open-Ck6BgBhN.js → book-open-BNBWEEve.js} +1 -1
  23. package/dist/renderer/assets/{check-CtB2ZLVE.js → check-MGp5kvnb.js} +1 -1
  24. package/dist/renderer/assets/{chevron-down--8lODnN0.js → chevron-down-zsfoTlh7.js} +1 -1
  25. package/dist/renderer/assets/{circle-CKQOJcbh.js → circle-BX_xUSg-.js} +1 -1
  26. package/dist/renderer/assets/{desktop-Do2I28CT.js → desktop-CgrO_cdQ.js} +1 -1
  27. package/dist/renderer/assets/{desktop-BkMW-h5p.js → desktop-O8_6ZCVZ.js} +2 -2
  28. package/dist/renderer/assets/{funnel-CkZuFIwa.js → funnel-7442U1wq.js} +1 -1
  29. package/dist/renderer/assets/{heart-CAX_CqFQ.js → heart-B3d8bnSH.js} +1 -1
  30. package/dist/renderer/assets/{index--_L0Y77h.js → index-5YUqCvBf.js} +1 -1
  31. package/dist/renderer/assets/{index-DvAYflFj.js → index-BBaYsVjU.js} +2 -2
  32. package/dist/renderer/assets/{pencil-sIRhtchu.js → pencil-QzWgkmoH.js} +1 -1
  33. package/dist/renderer/assets/{play-CJNRbCL5.js → play-Bsc8hSI7.js} +1 -1
  34. package/dist/renderer/assets/{plug-DsFe9GBx.js → plug-CRY7sjvB.js} +1 -1
  35. package/dist/renderer/assets/{plus-KDwEI-Rs.js → plus-Dr5Ei8Ud.js} +1 -1
  36. package/dist/renderer/assets/projects-BDYSOFIJ.js +1 -0
  37. package/dist/renderer/assets/{save-C-p5r3K-.js → save-giJnp2Ki.js} +1 -1
  38. package/dist/renderer/assets/{search-BZxQJb2k.js → search-DcvHLmKs.js} +1 -1
  39. package/dist/renderer/assets/{server-BV-K022I.js → server-D_Qu8If5.js} +1 -1
  40. package/dist/renderer/assets/{session-utils-ClQTa8yS.js → session-utils-CsASahfU.js} +1 -1
  41. package/dist/renderer/assets/{sessions-B0FSCA9O.js → sessions-eMpPqp4P.js} +1 -1
  42. package/dist/renderer/assets/{shield-check-B6AjkXUd.js → shield-check-BpF0uIw-.js} +1 -1
  43. package/dist/renderer/assets/{trash-2-41Pv4qRx.js → trash-2-OJFmx3xk.js} +1 -1
  44. package/dist/renderer/assets/{user-NKKHCKm9.js → user-GL_ivaV6.js} +1 -1
  45. package/dist/renderer/assets/{wifi-off-E1ZNhLG5.js → wifi-off-CQT5VNQl.js} +1 -1
  46. package/dist/renderer/index.html +1 -1
  47. package/dist-electron/electron/agent-runtime-detector.test.js +30 -0
  48. package/dist-electron/electron/local-core-refactor.test.js +749 -44
  49. package/dist-electron/electron/plugin-kernel.test.js +81 -1
  50. package/dist-electron/electron/workspace-task-store.test.js +7 -1
  51. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +34 -4
  52. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-progress.js +239 -7
  53. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +10 -6
  54. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-transport.js +1 -0
  55. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +169 -5
  56. package/dist-electron/services/local-ai-core/src/agents/claudecode/behavior.js +5 -0
  57. package/dist-electron/services/local-ai-core/src/agents/claudecode/definition.js +36 -0
  58. package/dist-electron/services/local-ai-core/src/agents/claudecode/index.js +15 -0
  59. package/dist-electron/services/local-ai-core/src/agents/codex/behavior.js +5 -0
  60. package/dist-electron/services/local-ai-core/src/agents/codex/definition.js +24 -0
  61. package/dist-electron/services/local-ai-core/src/agents/codex/index.js +15 -0
  62. package/dist-electron/services/local-ai-core/src/agents/hermes/behavior.js +29 -0
  63. package/dist-electron/services/local-ai-core/src/agents/hermes/definition.js +22 -0
  64. package/dist-electron/services/local-ai-core/src/agents/hermes/index.js +15 -0
  65. package/dist-electron/services/local-ai-core/src/agents/index.js +98 -0
  66. package/dist-electron/services/local-ai-core/src/agents/localcore-acp/behavior.js +5 -0
  67. package/dist-electron/services/local-ai-core/src/agents/localcore-acp/definition.js +14 -0
  68. package/dist-electron/services/local-ai-core/src/agents/localcore-acp/index.js +27 -0
  69. package/dist-electron/services/local-ai-core/src/agents/opencode/behavior.js +5 -0
  70. package/dist-electron/services/local-ai-core/src/agents/opencode/definition.js +18 -0
  71. package/dist-electron/services/local-ai-core/src/agents/opencode/index.js +15 -0
  72. package/dist-electron/services/local-ai-core/src/agents/opencode/launch.js +119 -0
  73. package/dist-electron/services/local-ai-core/src/agents/pi/behavior.js +5 -0
  74. package/dist-electron/services/local-ai-core/src/agents/pi/definition.js +23 -0
  75. package/dist-electron/services/local-ai-core/src/agents/pi/index.js +15 -0
  76. package/dist-electron/services/local-ai-core/src/agents/pi/launch.js +139 -0
  77. package/dist-electron/services/local-ai-core/src/agents/shared/acp-behavior.js +11 -0
  78. package/dist-electron/services/local-ai-core/src/agents/shared/agent-plugin.js +60 -0
  79. package/dist-electron/services/local-ai-core/src/agents/shared/definition.js +12 -0
  80. package/dist-electron/services/local-ai-core/src/agents/shared/launch-utils.js +122 -0
  81. package/dist-electron/services/local-ai-core/src/agents/shared/progress-replay.js +19 -0
  82. package/dist-electron/services/local-ai-core/src/agents/shared/replay-prefix.js +68 -0
  83. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +0 -2
  84. package/dist-electron/services/local-ai-core/src/channel/lark/runtime-state.js +1 -1
  85. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +13 -2
  86. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +8 -7
  87. package/dist-electron/services/local-ai-core/src/plugins/builtin/agent-localcore-acp-plugin.js +9 -130
  88. package/dist-electron/services/local-ai-core/src/router/workspace-route-config.js +22 -397
  89. package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +9 -45
  90. package/dist-electron/services/local-ai-core/src/runtime/local-core-runtime-state.js +62 -1
  91. package/dist-electron/services/local-ai-core/src/runtime/runtime-detection-service.js +2 -12
  92. package/dist-electron/shared/desktop.js +3 -1
  93. package/dist-electron/src/pages/Threads/thread-chat-model.js +27 -18
  94. package/dist-electron/src/pages/Threads/thread-chat-permission.test.js +57 -18
  95. package/package.json +1 -1
  96. package/dist/renderer/assets/ThreadChat-MagPeJD0.js +0 -23
  97. package/dist/renderer/assets/projects-CJUs2vaq.js +0 -1
@@ -16,6 +16,7 @@ const event_bus_js_1 = require("../services/local-ai-core/src/kernel/event-bus.j
16
16
  const lifecycle_manager_js_1 = require("../services/local-ai-core/src/kernel/lifecycle-manager.js");
17
17
  const plugin_registry_js_1 = require("../services/local-ai-core/src/kernel/plugin-registry.js");
18
18
  const local_core_controller_js_1 = require("../services/local-ai-core/src/runtime/local-core-controller.js");
19
+ const local_core_runtime_state_js_1 = require("../services/local-ai-core/src/runtime/local-core-runtime-state.js");
19
20
  const server_js_1 = require("../services/local-ai-core/src/runtime/server.js");
20
21
  function plugin(id, dependsOn = []) {
21
22
  return {
@@ -159,6 +160,7 @@ function plugin(id, dependsOn = []) {
159
160
  'opencode',
160
161
  'codex',
161
162
  'claudecode',
163
+ 'hermes',
162
164
  ]);
163
165
  strict_1.default.deepEqual(runtime.kernel.getCapabilitySnapshot().adapters.channels, ['localcore-acp', 'lark', 'weixin']);
164
166
  strict_1.default.deepEqual(runtime.kernel.getCapabilitySnapshot().adapters.knowledgeProviders, ['ai-vector']);
@@ -193,6 +195,7 @@ function plugin(id, dependsOn = []) {
193
195
  'opencode',
194
196
  'codex',
195
197
  'claudecode',
198
+ 'hermes',
196
199
  ]);
197
200
  strict_1.default.deepEqual(runtime.kernel.getCapabilitySnapshot().adapters.channels, ['localcore-acp', 'lark', 'weixin']);
198
201
  strict_1.default.deepEqual(runtime.kernel.getCapabilitySnapshot().adapters.knowledgeProviders, []);
@@ -245,6 +248,44 @@ type = "codex"
245
248
  (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
246
249
  }
247
250
  });
251
+ (0, node_test_1.default)('hermes agent runtime routes projects through hermes ACP command', async () => {
252
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-kernel-'));
253
+ try {
254
+ const runtime = (0, bootstrap_js_2.bootstrapLocalCoreRuntime)({
255
+ userDataPath,
256
+ enableKnowledge: false,
257
+ });
258
+ await runtime.state.saveRawConfigFile(`
259
+ [[projects]]
260
+ name = "hermes-workspace"
261
+
262
+ [projects.agent]
263
+ type = "hermes"
264
+ `);
265
+ const configState = await runtime.state.readConfigFile();
266
+ const project = configState.parsed?.projects?.find((entry) => entry.name === 'hermes-workspace');
267
+ const hermesRuntime = runtime.agentRuntimes.find((entry) => entry.agentType === 'hermes');
268
+ const route = project ? hermesRuntime?.createRoute(configState, project) : null;
269
+ strict_1.default.equal(route?.agentType, 'hermes');
270
+ strict_1.default.equal(route?.config.command, 'hermes');
271
+ strict_1.default.deepEqual(route?.config.args, ['acp']);
272
+ strict_1.default.equal(route?.config.env?.HERMES_YOLO_MODE, '1');
273
+ strict_1.default.deepEqual(await runtime.workspaceRouter.listWorkspaces(), [
274
+ {
275
+ id: 'hermes-workspace',
276
+ name: 'hermes-workspace',
277
+ agentType: 'hermes',
278
+ platforms: [],
279
+ sessionsCount: 0,
280
+ heartbeatEnabled: false,
281
+ },
282
+ ]);
283
+ await runtime.stop();
284
+ }
285
+ finally {
286
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
287
+ }
288
+ });
248
289
  (0, node_test_1.default)('pi agent runtime routes projects through bundled pi ACP and coding agent', async () => {
249
290
  const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-kernel-'));
250
291
  try {
@@ -565,6 +606,45 @@ model = "deepseek-v4-flash"
565
606
  (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
566
607
  }
567
608
  });
609
+ (0, node_test_1.default)('runtime getLogs reads from disk after state is recreated', () => {
610
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-disk-logs-'));
611
+ try {
612
+ const state = (0, local_core_runtime_state_js_1.createLocalCoreRuntimeState)({ userDataPath });
613
+ state.pushLog('first persisted line');
614
+ state.pushLog('second persisted line');
615
+ const recreated = (0, local_core_runtime_state_js_1.createLocalCoreRuntimeState)({ userDataPath });
616
+ strict_1.default.deepEqual(recreated.getLogs(2).map((line) => line.replace(/^\S+\s+/, '')), [
617
+ 'first persisted line',
618
+ 'second persisted line',
619
+ ]);
620
+ }
621
+ finally {
622
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
623
+ }
624
+ });
625
+ (0, node_test_1.default)('runtime getLogs fills from rotated log when current log has fewer lines', () => {
626
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-rotated-logs-'));
627
+ try {
628
+ const state = (0, local_core_runtime_state_js_1.createLocalCoreRuntimeState)({ userDataPath });
629
+ (0, node_fs_1.writeFileSync)(`${state.logPath}.1`, 'old-1\nold-2\nold-3\n', 'utf8');
630
+ (0, node_fs_1.writeFileSync)(state.logPath, 'new-1\n', 'utf8');
631
+ strict_1.default.deepEqual(state.getLogs(3), ['old-2', 'old-3', 'new-1']);
632
+ }
633
+ finally {
634
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
635
+ }
636
+ });
637
+ (0, node_test_1.default)('runtime getLogs returns only the requested tail lines from disk', () => {
638
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-tail-logs-'));
639
+ try {
640
+ const state = (0, local_core_runtime_state_js_1.createLocalCoreRuntimeState)({ userDataPath });
641
+ (0, node_fs_1.writeFileSync)(state.logPath, Array.from({ length: 1000 }, (_, index) => `line-${index + 1}`).join('\n') + '\n', 'utf8');
642
+ strict_1.default.deepEqual(state.getLogs(4), ['line-997', 'line-998', 'line-999', 'line-1000']);
643
+ }
644
+ finally {
645
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
646
+ }
647
+ });
568
648
  (0, node_test_1.default)('controller logs are persisted to local-core.log', () => {
569
649
  const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-controller-logs-'));
570
650
  try {
@@ -739,7 +819,7 @@ name = "pi-workspace"
739
819
  [projects.agent]
740
820
  type = "pi"
741
821
  `);
742
- strict_1.default.deepEqual(runtime.agentRuntimes.map((entry) => entry.agentType), ['localcore-acp', 'opencode', 'codex']);
822
+ strict_1.default.deepEqual(runtime.agentRuntimes.map((entry) => entry.agentType), ['localcore-acp', 'opencode', 'codex', 'hermes']);
743
823
  strict_1.default.equal(runtime.kernel.getCapabilitySnapshot().snapshot.agents.some((capability) => capability.agentType === 'claudecode'), false);
744
824
  strict_1.default.equal(runtime.kernel.getCapabilitySnapshot().snapshot.agents.some((capability) => capability.agentType === 'pi'), false);
745
825
  strict_1.default.deepEqual(await runtime.workspaceRouter.listWorkspaces(), []);
@@ -198,7 +198,13 @@ const local_core_controller_js_1 = require("../services/local-ai-core/src/runtim
198
198
  model: '',
199
199
  });
200
200
  strict_1.default.equal(closeCount, 1);
201
- strict_1.default.equal(capturedSessionNewParams[1]?._meta, undefined);
201
+ strict_1.default.deepEqual(capturedSessionNewParams[1]?._meta, {
202
+ claudeCode: {
203
+ emitRawSDKMessages: [
204
+ { type: 'system', subtype: 'local_command_output' },
205
+ ],
206
+ },
207
+ });
202
208
  }
203
209
  finally {
204
210
  (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
@@ -10,6 +10,8 @@ const local_core_acp_response_processor_js_1 = require("./local-core-acp-respons
10
10
  const local_core_acp_content_js_1 = require("./local-core-acp-content.js");
11
11
  const command_risk_js_1 = require("../security/command-risk.js");
12
12
  const local_core_slash_commands_js_1 = require("./local-core-slash-commands.js");
13
+ const local_core_acp_progress_js_1 = require("./local-core-acp-progress.js");
14
+ const index_js_1 = require("../agents/index.js");
13
15
  const ACP_PROMPT_TIMEOUT_MS = 15 * 60 * 1000;
14
16
  class LocalCoreAcpBackend {
15
17
  options;
@@ -199,8 +201,10 @@ class LocalCoreAcpBackend {
199
201
  sessionKey: row.bridge_session_key,
200
202
  },
201
203
  });
202
- void this.runPrompt(threadId, runId, row.bridge_session_key, config, message, options).catch((error) => {
203
- this.options.log?.(`localcore-acp prompt failed for ${threadId}: ${error instanceof Error ? error.message : String(error)}`);
204
+ setImmediate(() => {
205
+ void this.runPrompt(threadId, runId, row.bridge_session_key, config, message, options).catch((error) => {
206
+ this.options.log?.(`localcore-acp prompt failed for ${threadId}: ${error instanceof Error ? error.message : String(error)}`);
207
+ });
204
208
  });
205
209
  return { runId };
206
210
  }
@@ -300,15 +304,33 @@ class LocalCoreAcpBackend {
300
304
  session = await this.sessionCoordinator.ensureSession(threadId, bridgeSessionKey, config, {
301
305
  permissionMode: options.permissionMode,
302
306
  });
307
+ const priorAssistantFinalMessages = this.options.store
308
+ .getThread(threadId, [])
309
+ .messages
310
+ .filter((entry) => entry.role === 'assistant' && entry.kind === 'final')
311
+ .map((entry) => entry.content);
312
+ const priorAssistantProgressMessages = this.options.store
313
+ .getThread(threadId, [])
314
+ .messages
315
+ .filter((entry) => entry.role === 'assistant' && entry.kind === 'progress')
316
+ .map((entry) => ({
317
+ kind: entry.bridgeKind,
318
+ content: entry.content,
319
+ }));
303
320
  session.currentRunId = runId;
304
321
  session.currentTurn = {
305
322
  runId,
306
323
  replyCtx: runId,
307
324
  previewHandle: (0, node_crypto_1.randomUUID)(),
308
325
  thoughtPreviewHandle: (0, node_crypto_1.randomUUID)(),
309
- thoughtMessageId: `${runId}-thought`,
326
+ thoughtMessageId: `${runId}-thought-1`,
327
+ agentType: row.agent_type,
310
328
  assistantText: '',
329
+ rawAssistantText: '',
330
+ priorAssistantFinalMessages,
331
+ priorAssistantProgressMessages,
311
332
  thoughtText: '',
333
+ thoughtSequence: 1,
312
334
  typingStarted: true,
313
335
  previewStarted: false,
314
336
  thoughtPreviewStarted: false,
@@ -319,6 +341,7 @@ class LocalCoreAcpBackend {
319
341
  pendingToolCalls: {},
320
342
  pendingToolCallOrder: [],
321
343
  toolCallSequence: 0,
344
+ toolObservations: [],
322
345
  permission: null,
323
346
  };
324
347
  const promptPromise = this.transport.request(session, 'session/prompt', {
@@ -332,9 +355,16 @@ class LocalCoreAcpBackend {
332
355
  if (!currentTurn || currentTurn.runId !== runId) {
333
356
  return;
334
357
  }
358
+ this.turnCoordinator.closePendingThoughtSegment(session);
335
359
  this.turnCoordinator.flushPendingToolCall(session);
336
360
  if (currentTurn.assistantText) {
337
- const processed = await this.responseProcessor.processAssistantResponse(threadId, currentTurn.assistantText);
361
+ const behavior = (0, index_js_1.resolveAgentAcpBehavior)(currentTurn.agentType);
362
+ const normalizedFinalAssistantText = behavior.normalizeFinalAssistantText({
363
+ rawText: currentTurn.rawAssistantText || currentTurn.assistantText,
364
+ priorAssistantMessages: currentTurn.priorAssistantFinalMessages || [],
365
+ });
366
+ const assistantText = (0, local_core_acp_progress_js_1.stripObservedToolTranscriptsFromAssistantText)(normalizedFinalAssistantText, currentTurn.toolObservations);
367
+ const processed = await this.responseProcessor.processAssistantResponse(threadId, assistantText);
338
368
  if (processed.displayContent) {
339
369
  this.options.store.appendMessage(threadId, 'assistant', processed.displayContent, 'final');
340
370
  this.options.eventBus.emit({
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.applyAssistantMessageChunk = applyAssistantMessageChunk;
4
4
  exports.applyThoughtChunk = applyThoughtChunk;
5
+ exports.closeThoughtSegment = closeThoughtSegment;
5
6
  exports.registerPendingToolCall = registerPendingToolCall;
6
7
  exports.resolveToolCallForUpdate = resolveToolCallForUpdate;
7
8
  exports.resolveFallbackToolCall = resolveFallbackToolCall;
@@ -16,6 +17,8 @@ exports.isTerminalToolStatus = isTerminalToolStatus;
16
17
  exports.formatPlanProgress = formatPlanProgress;
17
18
  exports.extractToolCallKey = extractToolCallKey;
18
19
  exports.extractToolCallInput = extractToolCallInput;
20
+ exports.recordToolObservation = recordToolObservation;
21
+ exports.stripObservedToolTranscriptsFromAssistantText = stripObservedToolTranscriptsFromAssistantText;
19
22
  function applyAssistantMessageChunk(currentTurn, text) {
20
23
  if (!text) {
21
24
  return null;
@@ -41,8 +44,12 @@ function applyThoughtChunk(currentTurn, text) {
41
44
  if (!text) {
42
45
  return null;
43
46
  }
44
- currentTurn.thoughtText = mergeStreamingText(currentTurn.thoughtText, text);
47
+ const projection = mergeThoughtSegment(currentTurn.thoughtText, text);
48
+ currentTurn.thoughtText = projection.fullText;
45
49
  const content = currentTurn.thoughtText.trim();
50
+ if (!content) {
51
+ return null;
52
+ }
46
53
  if (!currentTurn.thoughtPreviewStarted) {
47
54
  currentTurn.thoughtPreviewStarted = true;
48
55
  return {
@@ -61,23 +68,67 @@ function applyThoughtChunk(currentTurn, text) {
61
68
  bridgeKind: 'thought',
62
69
  };
63
70
  }
64
- function mergeStreamingText(current, next) {
71
+ function closeThoughtSegment(currentTurn) {
72
+ const thoughtText = currentTurn.thoughtText || '';
73
+ if (!thoughtText.trim()) {
74
+ return;
75
+ }
76
+ const nextSequence = (currentTurn.thoughtSequence || 1) + 1;
77
+ const runId = currentTurn.runId || 'thought';
78
+ currentTurn.thoughtSequence = nextSequence;
79
+ currentTurn.thoughtText = '';
80
+ currentTurn.thoughtPreviewStarted = false;
81
+ currentTurn.thoughtMessageId = `${runId}-thought-${nextSequence}`;
82
+ currentTurn.thoughtPreviewHandle = `${runId}-thought-preview-${nextSequence}`;
83
+ }
84
+ function mergeThoughtSegment(current, next) {
65
85
  if (!current) {
66
- return next;
86
+ return { fullText: next, segmentText: next };
67
87
  }
68
88
  if (next.startsWith(current)) {
69
- return next;
89
+ return {
90
+ fullText: next,
91
+ segmentText: next.slice(current.length),
92
+ };
70
93
  }
71
94
  if (current.endsWith(next)) {
72
- return current;
95
+ return {
96
+ fullText: current,
97
+ segmentText: '',
98
+ };
73
99
  }
74
100
  const maxOverlap = Math.min(current.length, next.length);
75
101
  for (let size = maxOverlap; size > 0; size -= 1) {
76
102
  if (current.endsWith(next.slice(0, size))) {
77
- return current + next.slice(size);
103
+ const suffix = next.slice(size);
104
+ return {
105
+ fullText: current + suffix,
106
+ segmentText: suffix,
107
+ };
78
108
  }
79
109
  }
80
- return current + next;
110
+ return {
111
+ fullText: appendThoughtPiece(current, next),
112
+ segmentText: next,
113
+ };
114
+ }
115
+ function appendThoughtPiece(current, next) {
116
+ if (!current.trim()) {
117
+ return next;
118
+ }
119
+ if (!next.trim()) {
120
+ return current;
121
+ }
122
+ if (/^[,.;:!?,。;:!?、)】》]/.test(next.trimStart())) {
123
+ return current.trimEnd() + next.trimStart();
124
+ }
125
+ if (/^[-/]/.test(next.trimStart()) || /[-/][A-Za-z0-9]*$/.test(current.trimEnd())) {
126
+ return current.trimEnd() + next.trimStart();
127
+ }
128
+ if (/\s$/.test(current) || /^\s/.test(next)) {
129
+ return current + next;
130
+ }
131
+ return `${current.trimEnd()} ${next.trimStart()}`;
81
132
  }
82
133
  function registerPendingToolCall(input) {
83
134
  const title = String(input.update.title || 'Running tool').trim();
@@ -203,3 +254,184 @@ function extractToolCallInput(update) {
203
254
  }
204
255
  return undefined;
205
256
  }
257
+ function recordToolObservation(currentTurn, observation) {
258
+ const normalized = {
259
+ ...observation,
260
+ name: observation.name?.trim() || undefined,
261
+ title: observation.title?.trim() || undefined,
262
+ status: observation.status?.trim() || undefined,
263
+ outputText: observation.outputText?.trim() || undefined,
264
+ };
265
+ if (!normalized.name && !normalized.title && normalized.input === undefined && !normalized.outputText) {
266
+ return;
267
+ }
268
+ currentTurn.toolObservations = [
269
+ ...(currentTurn.toolObservations || []),
270
+ normalized,
271
+ ];
272
+ }
273
+ function stripObservedToolTranscriptsFromAssistantText(text, observations) {
274
+ let remaining = String(text || '');
275
+ const knownObservations = observations || [];
276
+ for (let guard = 0; guard < knownObservations.length; guard += 1) {
277
+ const stripped = stripOneObservedToolTranscriptPrefix(remaining, knownObservations);
278
+ if (stripped === remaining) {
279
+ break;
280
+ }
281
+ remaining = stripped;
282
+ }
283
+ return remaining.trim();
284
+ }
285
+ function stripOneObservedToolTranscriptPrefix(text, observations) {
286
+ const trimmedStart = text.replace(/^\s+/, '');
287
+ if (!trimmedStart) {
288
+ return text;
289
+ }
290
+ const matchingEvidence = observations
291
+ .map((observation) => createToolObservationEvidence(observation))
292
+ .find((evidence) => matchesObservedToolTranscriptPrefix(trimmedStart, evidence));
293
+ if (!matchingEvidence) {
294
+ return text;
295
+ }
296
+ const boundary = findToolTranscriptPrefixBoundary(trimmedStart, matchingEvidence);
297
+ if (boundary <= 0) {
298
+ return text;
299
+ }
300
+ return trimmedStart.slice(boundary);
301
+ }
302
+ function createToolObservationEvidence(observation) {
303
+ const serializedInput = serializeObservationValue(observation.input);
304
+ const serializedOutput = observation.outputText || '';
305
+ const structuredValues = collectStructuredStringValues(serializedOutput);
306
+ return {
307
+ toolNames: uniqueNonEmpty([
308
+ observation.name,
309
+ observation.title,
310
+ ].map((value) => normalizeEvidencePhrase(value || ''))),
311
+ urls: uniqueNonEmpty([
312
+ ...extractUrls(serializedInput),
313
+ ...extractUrls(serializedOutput),
314
+ ]),
315
+ phrases: uniqueNonEmpty([
316
+ ...structuredValues,
317
+ observation.title,
318
+ serializedInput.length <= 300 ? serializedInput : '',
319
+ ].map((value) => normalizeEvidencePhrase(value || '')).filter((value) => value.length >= 12)),
320
+ };
321
+ }
322
+ function matchesObservedToolTranscriptPrefix(text, evidence) {
323
+ const prefix = text.slice(0, 3000);
324
+ const normalizedPrefix = normalizeEvidencePhrase(prefix);
325
+ const hasToolName = evidence.toolNames.some((name) => name && normalizedPrefix.includes(name));
326
+ const urlMatches = evidence.urls.filter((url) => prefix.includes(url)).length;
327
+ const phraseMatches = evidence.phrases.filter((phrase) => normalizedPrefix.includes(phrase)).length;
328
+ if (hasToolName && (urlMatches > 0 || phraseMatches > 0)) {
329
+ return true;
330
+ }
331
+ return urlMatches > 0 && phraseMatches > 0;
332
+ }
333
+ function findToolTranscriptPrefixBoundary(text, evidence) {
334
+ const lines = text.split(/\n/);
335
+ const firstContentLine = lines.find((line) => line.trim());
336
+ if (!firstContentLine || !isTranscriptStartLine(firstContentLine, evidence)) {
337
+ return 0;
338
+ }
339
+ let offset = 0;
340
+ let seenEvidence = false;
341
+ for (let index = 0; index < lines.length; index += 1) {
342
+ const line = lines[index] || '';
343
+ const lineWithNewlineLength = line.length + (index < lines.length - 1 ? 1 : 0);
344
+ if (lineContainsObservationEvidence(line, evidence)) {
345
+ seenEvidence = true;
346
+ }
347
+ if (seenEvidence && index > 0 && line.trim() && !isStructuredTranscriptLine(line, evidence)) {
348
+ return offset;
349
+ }
350
+ offset += lineWithNewlineLength;
351
+ }
352
+ return 0;
353
+ }
354
+ function isTranscriptStartLine(line, evidence) {
355
+ const trimmed = line.trim();
356
+ return lineContainsObservationEvidence(line, evidence) &&
357
+ (/^(```|\{|\[|\*\*[^*]+\*\*|#+\s)/.test(trimmed) || /^[A-Za-z0-9_.-]+:/.test(trimmed));
358
+ }
359
+ function isStructuredTranscriptLine(line, evidence) {
360
+ const trimmed = line.trim();
361
+ if (!trimmed) {
362
+ return true;
363
+ }
364
+ if (lineContainsObservationEvidence(line, evidence)) {
365
+ return true;
366
+ }
367
+ return /^(```|\{|\}|\[|\]|".+":|\*\*[^*]+:\*\*|\*[^*]+\*|#+\s)/.test(trimmed) ||
368
+ /[::]\s*$/.test(trimmed) ||
369
+ /[{}\[\]"]/g.test(trimmed) ||
370
+ /^[A-Za-z0-9_.-]+:/.test(trimmed);
371
+ }
372
+ function lineContainsObservationEvidence(line, evidence) {
373
+ const normalizedLine = normalizeEvidencePhrase(line);
374
+ return evidence.toolNames.some((name) => name && normalizedLine.includes(name)) ||
375
+ evidence.urls.some((url) => line.includes(url)) ||
376
+ evidence.phrases.some((phrase) => phrase && normalizedLine.includes(phrase));
377
+ }
378
+ function serializeObservationValue(value) {
379
+ if (value == null) {
380
+ return '';
381
+ }
382
+ if (typeof value === 'string') {
383
+ return value;
384
+ }
385
+ try {
386
+ return JSON.stringify(value);
387
+ }
388
+ catch {
389
+ return String(value);
390
+ }
391
+ }
392
+ function collectStructuredStringValues(text) {
393
+ const values = [];
394
+ try {
395
+ collectStringValues(JSON.parse(text), values);
396
+ }
397
+ catch {
398
+ const matches = text.matchAll(/"(title|description|url|name|summary)"\s*:\s*"([^"]{12,})"/gi);
399
+ for (const match of matches) {
400
+ values.push(match[2] || '');
401
+ }
402
+ }
403
+ return values;
404
+ }
405
+ function collectStringValues(value, values) {
406
+ if (typeof value === 'string') {
407
+ values.push(value);
408
+ return;
409
+ }
410
+ if (Array.isArray(value)) {
411
+ for (const item of value) {
412
+ collectStringValues(item, values);
413
+ }
414
+ return;
415
+ }
416
+ if (!value || typeof value !== 'object') {
417
+ return;
418
+ }
419
+ for (const entry of Object.values(value)) {
420
+ collectStringValues(entry, values);
421
+ }
422
+ }
423
+ function extractUrls(text) {
424
+ return [...text.matchAll(/https?:\/\/[^\s"'`<>),\]}]+/g)]
425
+ .map((match) => match[0])
426
+ .filter(Boolean);
427
+ }
428
+ function normalizeEvidencePhrase(value) {
429
+ return String(value || '')
430
+ .replace(/\\[nrt]/g, ' ')
431
+ .replace(/\s+/g, ' ')
432
+ .trim()
433
+ .toLowerCase();
434
+ }
435
+ function uniqueNonEmpty(values) {
436
+ return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
437
+ }
@@ -194,14 +194,18 @@ class LocalCoreAcpSessionCoordinator {
194
194
  }
195
195
  buildSessionMeta(threadId, permissionModeOverride = '') {
196
196
  const mode = this.resolveLaunchPermissionMode(threadId, permissionModeOverride);
197
- if (!mode) {
198
- return undefined;
199
- }
200
197
  return {
201
198
  claudeCode: {
202
- options: {
203
- permissionMode: mode,
204
- },
199
+ emitRawSDKMessages: [
200
+ { type: 'system', subtype: 'local_command_output' },
201
+ ],
202
+ ...(mode
203
+ ? {
204
+ options: {
205
+ permissionMode: mode,
206
+ },
207
+ }
208
+ : {}),
205
209
  },
206
210
  };
207
211
  }
@@ -35,6 +35,7 @@ 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,