@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
@@ -458,6 +458,7 @@ const server_routes_js_1 = require("../services/local-ai-core/src/runtime/server
458
458
  });
459
459
  (0, node_test_1.default)('ACP progress projection applies assistant and thought chunks with bridge metadata', () => {
460
460
  const currentTurn = {
461
+ runId: 'run-1',
461
462
  previewHandle: 'preview-1',
462
463
  thoughtPreviewHandle: 'thought-preview-1',
463
464
  thoughtMessageId: 'run-1-thought',
@@ -492,9 +493,11 @@ const server_routes_js_1 = require("../services/local-ai-core/src/runtime/server
492
493
  content: '先理解,再修改',
493
494
  bridgeKind: 'thought',
494
495
  });
496
+ strict_1.default.equal(currentTurn.thoughtText, '先理解,再修改');
495
497
  });
496
498
  (0, node_test_1.default)('ACP thought chunks merge provider snapshots without duplicating text', () => {
497
499
  const currentTurn = {
500
+ runId: 'run-1',
498
501
  thoughtPreviewHandle: 'thought-preview-1',
499
502
  thoughtMessageId: 'run-1-thought',
500
503
  thoughtText: '',
@@ -516,6 +519,35 @@ const server_routes_js_1 = require("../services/local-ai-core/src/runtime/server
516
519
  });
517
520
  strict_1.default.equal(currentTurn.thoughtText, 'The user wants to see their desktop files. Let me show them.');
518
521
  });
522
+ (0, node_test_1.default)('ACP thought segment close starts a new streaming preview for web and app', () => {
523
+ const currentTurn = {
524
+ runId: 'run-1',
525
+ thoughtPreviewHandle: 'thought-preview-1',
526
+ thoughtMessageId: 'run-1-thought-1',
527
+ thoughtText: '',
528
+ thoughtSequence: 1,
529
+ thoughtPreviewStarted: false,
530
+ };
531
+ strict_1.default.deepEqual((0, local_core_acp_progress_js_1.applyThoughtChunk)(currentTurn, 'first thought'), {
532
+ bridgeType: 'preview_start',
533
+ previewHandle: 'thought-preview-1',
534
+ messageId: 'run-1-thought-1',
535
+ content: 'first thought',
536
+ bridgeKind: 'thought',
537
+ });
538
+ (0, local_core_acp_progress_js_1.closeThoughtSegment)(currentTurn);
539
+ strict_1.default.equal(currentTurn.thoughtText, '');
540
+ strict_1.default.equal(currentTurn.thoughtPreviewStarted, false);
541
+ strict_1.default.equal(currentTurn.thoughtMessageId, 'run-1-thought-2');
542
+ strict_1.default.equal(currentTurn.thoughtPreviewHandle, 'run-1-thought-preview-2');
543
+ strict_1.default.deepEqual((0, local_core_acp_progress_js_1.applyThoughtChunk)(currentTurn, 'second thought'), {
544
+ bridgeType: 'preview_start',
545
+ previewHandle: 'run-1-thought-preview-2',
546
+ messageId: 'run-1-thought-2',
547
+ content: 'second thought',
548
+ bridgeKind: 'thought',
549
+ });
550
+ });
519
551
  (0, node_test_1.default)('ACP progress projection registers pending tool calls in order', () => {
520
552
  const currentTurn = {
521
553
  toolCallSequence: 0,
@@ -923,6 +955,56 @@ const server_routes_js_1 = require("../services/local-ai-core/src/runtime/server
923
955
  ]]);
924
956
  strict_1.default.equal(session.pendingPermissionByRun.get('run-1')?.options[1]?.optionId, 'approve-always');
925
957
  });
958
+ (0, node_test_1.default)('Hermes ACP permission options respect allow_permanent false', () => {
959
+ const emitted = [];
960
+ const coordinator = new local_core_acp_turn_coordinator_js_1.LocalCoreAcpTurnCoordinator({
961
+ appendMessage: () => { },
962
+ emitBridge: (event) => emitted.push(event),
963
+ updateRunStatus: () => { },
964
+ sendRaw: () => true,
965
+ });
966
+ const session = {
967
+ threadId: 'thread-1',
968
+ bridgeSessionKey: 'session:thread-1',
969
+ currentRunId: 'run-1',
970
+ currentTurn: {
971
+ runId: 'run-1',
972
+ replyCtx: 'run-1',
973
+ previewHandle: 'preview-1',
974
+ assistantText: '',
975
+ typingStarted: true,
976
+ previewStarted: false,
977
+ permission: null,
978
+ agentType: 'hermes',
979
+ },
980
+ pendingPermissionByRun: new Map(),
981
+ loadReplayMode: false,
982
+ schedulerJobCreatedByRun: new Map(),
983
+ };
984
+ coordinator.handleAgentRequest(session, {
985
+ method: 'session/request_permission',
986
+ id: 42,
987
+ params: {
988
+ allow_permanent: false,
989
+ toolCall: {
990
+ title: 'Terminal',
991
+ parameters: {
992
+ command: 'system_profiler SPHardwareDataType',
993
+ },
994
+ },
995
+ options: [
996
+ { optionId: 'approve-once', name: 'Allow once', kind: 'allow' },
997
+ { optionId: 'approve-always', name: 'Always allow', kind: 'allow' },
998
+ { optionId: 'reject', name: 'Reject', kind: 'reject' },
999
+ ],
1000
+ },
1001
+ });
1002
+ strict_1.default.deepEqual(emitted[0]?.buttonRows, [[
1003
+ { text: 'allow', data: 'allow' },
1004
+ { text: 'deny', data: 'deny' },
1005
+ ]]);
1006
+ strict_1.default.deepEqual(session.pendingPermissionByRun.get('run-1')?.options.map((option) => option.optionId), ['approve-once', 'reject']);
1007
+ });
926
1008
  (0, node_test_1.default)('ACP yolo mode auto-selects permission requests without rendering cards', () => {
927
1009
  const emitted = [];
928
1010
  const appended = [];
@@ -1025,11 +1107,11 @@ const server_routes_js_1 = require("../services/local-ai-core/src/runtime/server
1025
1107
  strict_1.default.equal(emitted[0]?.content, '🔧 Terminal');
1026
1108
  strict_1.default.equal(session.currentTurn.assistantText, 'done');
1027
1109
  });
1028
- (0, node_test_1.default)('ACP plan updates are persisted and emitted as thinking progress', () => {
1110
+ (0, node_test_1.default)('ACP tool-scoped assistant chunks stay out of final assistant buffer', () => {
1029
1111
  const appended = [];
1030
1112
  const emitted = [];
1031
1113
  const coordinator = new local_core_acp_turn_coordinator_js_1.LocalCoreAcpTurnCoordinator({
1032
- appendMessage: (_threadId, _role, content, kind) => appended.push({ content, kind }),
1114
+ appendMessage: (_threadId, _role, content, _kind, _toolCall, bridgeKind) => appended.push({ content, bridgeKind }),
1033
1115
  emitBridge: (event) => emitted.push(event),
1034
1116
  updateRunStatus: () => { },
1035
1117
  sendRaw: () => true,
@@ -1054,32 +1136,175 @@ const server_routes_js_1 = require("../services/local-ai-core/src/runtime/server
1054
1136
  method: 'session/update',
1055
1137
  params: {
1056
1138
  update: {
1057
- sessionUpdate: 'plan',
1058
- entries: [
1059
- { content: '检查消息流' },
1060
- { content: '修复持久化' },
1061
- ],
1139
+ sessionUpdate: 'agent_message_chunk',
1140
+ _meta: {
1141
+ claudeCode: {
1142
+ parentToolUseId: 'tool-1',
1143
+ },
1144
+ },
1145
+ content: {
1146
+ type: 'text',
1147
+ text: 'tool scoped transcript',
1148
+ },
1062
1149
  },
1063
1150
  },
1064
1151
  });
1065
- strict_1.default.deepEqual(appended, [
1066
- {
1067
- content: '检查消息流 | 修复持久化',
1068
- kind: 'progress',
1152
+ coordinator.handleAgentNotification(session, {
1153
+ method: 'session/update',
1154
+ params: {
1155
+ update: {
1156
+ sessionUpdate: 'agent_message_chunk',
1157
+ content: { type: 'text', text: 'final answer' },
1158
+ },
1069
1159
  },
1070
- ]);
1071
- strict_1.default.equal(emitted.length, 1);
1072
- strict_1.default.equal(emitted[0]?.type, 'reply');
1073
- strict_1.default.equal(emitted[0]?.bridgeKind, 'plan');
1074
- strict_1.default.equal(emitted[0]?.content, appended[0]?.content);
1160
+ });
1161
+ strict_1.default.deepEqual(appended, [{ content: 'tool scoped transcript', bridgeKind: 'tool' }]);
1162
+ strict_1.default.equal(emitted[0]?.bridgeKind, 'tool');
1163
+ strict_1.default.equal(emitted[0]?.content, 'tool scoped transcript');
1164
+ strict_1.default.equal(session.currentTurn.assistantText, 'final answer');
1075
1165
  });
1076
- (0, node_test_1.default)('ACP thought chunks are streamed as thinking preview updates', () => {
1077
- const appended = [];
1166
+ (0, node_test_1.default)('Hermes ACP assistant chunks strip restored history replay inside its own behavior', () => {
1167
+ const emitted = [];
1168
+ const coordinator = new local_core_acp_turn_coordinator_js_1.LocalCoreAcpTurnCoordinator({
1169
+ appendMessage: () => { },
1170
+ emitBridge: (event) => emitted.push(event),
1171
+ updateRunStatus: () => { },
1172
+ sendRaw: () => true,
1173
+ });
1174
+ const session = {
1175
+ threadId: 'thread-1',
1176
+ bridgeSessionKey: 'session:thread-1',
1177
+ currentRunId: 'run-1',
1178
+ currentTurn: {
1179
+ runId: 'run-1',
1180
+ replyCtx: 'run-1',
1181
+ previewHandle: 'preview-1',
1182
+ agentType: 'hermes',
1183
+ assistantText: '',
1184
+ rawAssistantText: '',
1185
+ priorAssistantFinalMessages: ['Hi! 😊 How can I help you today?', '我是 Hermes Agent。'],
1186
+ typingStarted: true,
1187
+ previewStarted: false,
1188
+ permission: null,
1189
+ },
1190
+ loadReplayMode: false,
1191
+ schedulerJobCreatedByRun: new Map(),
1192
+ };
1193
+ for (const text of ['Hi! 😊 How can I help you today?', '我是 Hermes Agent。', '确认删除前需要你确认。']) {
1194
+ coordinator.handleAgentNotification(session, {
1195
+ method: 'session/update',
1196
+ params: {
1197
+ update: {
1198
+ sessionUpdate: 'agent_message_chunk',
1199
+ content: { type: 'text', text },
1200
+ },
1201
+ },
1202
+ });
1203
+ }
1204
+ strict_1.default.equal(session.currentTurn.assistantText, '确认删除前需要你确认。');
1205
+ strict_1.default.deepEqual(emitted.map((event) => event.content), ['确认删除前需要你确认。']);
1206
+ });
1207
+ (0, node_test_1.default)('Hermes ACP assistant chunks strip replay even when stored prior final is already polluted', () => {
1208
+ const emitted = [];
1209
+ const coordinator = new local_core_acp_turn_coordinator_js_1.LocalCoreAcpTurnCoordinator({
1210
+ appendMessage: () => { },
1211
+ emitBridge: (event) => emitted.push(event),
1212
+ updateRunStatus: () => { },
1213
+ sendRaw: () => true,
1214
+ });
1215
+ const previousCleanFinal = '你确定要删除 **Sisyphus_介绍.txt** 这个文件吗?删除后无法恢复。确认的话我马上执行。';
1216
+ const pollutedStoredFinal = [
1217
+ 'Hi! How can I help you today?',
1218
+ '我是 Hermes Agent,你的 AI 助手。',
1219
+ '好的,让我看看你的桌面文件。',
1220
+ previousCleanFinal,
1221
+ ].join('');
1222
+ const session = {
1223
+ threadId: 'thread-1',
1224
+ bridgeSessionKey: 'session:thread-1',
1225
+ currentRunId: 'run-1',
1226
+ currentTurn: {
1227
+ runId: 'run-1',
1228
+ replyCtx: 'run-1',
1229
+ previewHandle: 'preview-1',
1230
+ agentType: 'hermes',
1231
+ assistantText: '',
1232
+ rawAssistantText: '',
1233
+ priorAssistantFinalMessages: [pollutedStoredFinal],
1234
+ typingStarted: true,
1235
+ previewStarted: false,
1236
+ permission: null,
1237
+ },
1238
+ loadReplayMode: false,
1239
+ schedulerJobCreatedByRun: new Map(),
1240
+ };
1241
+ for (const text of [
1242
+ 'Hi! How can I help you today?',
1243
+ '我是 Hermes Agent,你的 AI 助手。',
1244
+ '好的,让我看看你的桌面文件。',
1245
+ previousCleanFinal,
1246
+ '已删除 **Sisyphus_介绍.txt**,现在 Text 文件夹是空的了。',
1247
+ ]) {
1248
+ coordinator.handleAgentNotification(session, {
1249
+ method: 'session/update',
1250
+ params: {
1251
+ update: {
1252
+ sessionUpdate: 'agent_message_chunk',
1253
+ content: { type: 'text', text },
1254
+ },
1255
+ },
1256
+ });
1257
+ }
1258
+ strict_1.default.equal(session.currentTurn.assistantText, '已删除 **Sisyphus_介绍.txt**,现在 Text 文件夹是空的了。');
1259
+ strict_1.default.deepEqual(emitted.map((event) => event.content), ['已删除 **Sisyphus_介绍.txt**,现在 Text 文件夹是空的了。']);
1260
+ });
1261
+ (0, node_test_1.default)('Hermes ACP assistant chunks keep a fresh answer when no replay anchor is present', () => {
1262
+ const emitted = [];
1263
+ const coordinator = new local_core_acp_turn_coordinator_js_1.LocalCoreAcpTurnCoordinator({
1264
+ appendMessage: () => { },
1265
+ emitBridge: (event) => emitted.push(event),
1266
+ updateRunStatus: () => { },
1267
+ sendRaw: () => true,
1268
+ });
1269
+ const session = {
1270
+ threadId: 'thread-1',
1271
+ bridgeSessionKey: 'session:thread-1',
1272
+ currentRunId: 'run-1',
1273
+ currentTurn: {
1274
+ runId: 'run-1',
1275
+ replyCtx: 'run-1',
1276
+ previewHandle: 'preview-1',
1277
+ agentType: 'hermes',
1278
+ assistantText: '',
1279
+ rawAssistantText: '',
1280
+ priorAssistantFinalMessages: ['上一轮已经污染的历史,但是它不会出现在这次新回复里。'],
1281
+ typingStarted: true,
1282
+ previewStarted: false,
1283
+ permission: null,
1284
+ },
1285
+ loadReplayMode: false,
1286
+ schedulerJobCreatedByRun: new Map(),
1287
+ };
1288
+ coordinator.handleAgentNotification(session, {
1289
+ method: 'session/update',
1290
+ params: {
1291
+ update: {
1292
+ sessionUpdate: 'agent_message_chunk',
1293
+ content: { type: 'text', text: '已删除文件。' },
1294
+ },
1295
+ },
1296
+ });
1297
+ strict_1.default.equal(session.currentTurn.assistantText, '已删除文件。');
1298
+ strict_1.default.deepEqual(emitted.map((event) => event.content), ['已删除文件。']);
1299
+ });
1300
+ (0, node_test_1.default)('Hermes ACP progress updates suppress restored tool and thought replay only', () => {
1078
1301
  const upserted = [];
1079
1302
  const emitted = [];
1080
1303
  const coordinator = new local_core_acp_turn_coordinator_js_1.LocalCoreAcpTurnCoordinator({
1081
- appendMessage: (_threadId, _role, content, kind) => appended.push({ content, kind }),
1082
- upsertMessage: (_threadId, id, _role, content, kind) => upserted.push({ id, content, kind }),
1304
+ appendMessage: () => strict_1.default.fail('tool updates should be upserted'),
1305
+ upsertMessage: (_threadId, id, _role, content, _kind, _toolCall, bridgeKind) => {
1306
+ upserted.push({ id, content, bridgeKind });
1307
+ },
1083
1308
  emitBridge: (event) => emitted.push(event),
1084
1309
  updateRunStatus: () => { },
1085
1310
  sendRaw: () => true,
@@ -1093,12 +1318,24 @@ const server_routes_js_1 = require("../services/local-ai-core/src/runtime/server
1093
1318
  replyCtx: 'run-1',
1094
1319
  previewHandle: 'preview-1',
1095
1320
  thoughtPreviewHandle: 'thought-preview-1',
1096
- thoughtMessageId: 'run-1-thought',
1321
+ thoughtMessageId: 'run-1-thought-1',
1322
+ agentType: 'hermes',
1097
1323
  assistantText: '',
1098
1324
  thoughtText: '',
1325
+ thoughtSequence: 1,
1099
1326
  typingStarted: true,
1100
1327
  previewStarted: false,
1101
1328
  thoughtPreviewStarted: false,
1329
+ priorAssistantProgressMessages: [
1330
+ {
1331
+ kind: 'tool',
1332
+ content: '🔧 terminal: rm ~/Desktop/Text/Sisyphus_介绍.txt: completed - terminal result\n- **exit_code:** 0',
1333
+ },
1334
+ {
1335
+ kind: 'thought',
1336
+ content: 'The user confirmed they want to delete the file. Let me delete it.',
1337
+ },
1338
+ ],
1102
1339
  permission: null,
1103
1340
  },
1104
1341
  loadReplayMode: false,
@@ -1108,8 +1345,30 @@ const server_routes_js_1 = require("../services/local-ai-core/src/runtime/server
1108
1345
  method: 'session/update',
1109
1346
  params: {
1110
1347
  update: {
1111
- sessionUpdate: 'agent_thought_chunk',
1112
- content: { type: 'text', text: '先理解问题' },
1348
+ sessionUpdate: 'tool_call',
1349
+ id: 'old-tool',
1350
+ title: 'terminal',
1351
+ rawInput: { command: 'rm ~/Desktop/Text/Sisyphus_介绍.txt' },
1352
+ },
1353
+ },
1354
+ });
1355
+ coordinator.handleAgentNotification(session, {
1356
+ method: 'session/update',
1357
+ params: {
1358
+ update: {
1359
+ sessionUpdate: 'tool_call_update',
1360
+ id: 'old-tool',
1361
+ title: 'tool update',
1362
+ status: 'completed',
1363
+ content: [
1364
+ {
1365
+ type: 'content',
1366
+ content: {
1367
+ type: 'text',
1368
+ text: 'terminal result\n- **exit_code:** 0',
1369
+ },
1370
+ },
1371
+ ],
1113
1372
  },
1114
1373
  },
1115
1374
  });
@@ -1118,34 +1377,386 @@ const server_routes_js_1 = require("../services/local-ai-core/src/runtime/server
1118
1377
  params: {
1119
1378
  update: {
1120
1379
  sessionUpdate: 'agent_thought_chunk',
1121
- content: { type: 'text', text: ',再检查代码' },
1380
+ content: {
1381
+ type: 'text',
1382
+ text: 'The user confirmed they want to delete the file. Let me delete it.',
1383
+ },
1122
1384
  },
1123
1385
  },
1124
1386
  });
1125
- strict_1.default.deepEqual(appended, []);
1126
- strict_1.default.deepEqual(upserted, [
1127
- {
1128
- id: 'run-1-thought',
1129
- content: '先理解问题',
1130
- kind: 'progress',
1387
+ coordinator.handleAgentNotification(session, {
1388
+ method: 'session/update',
1389
+ params: {
1390
+ update: {
1391
+ sessionUpdate: 'tool_call',
1392
+ id: 'new-tool',
1393
+ title: 'terminal',
1394
+ rawInput: { command: 'ls -la ~/Desktop/PDF/' },
1395
+ },
1131
1396
  },
1132
- {
1133
- id: 'run-1-thought',
1134
- content: '先理解问题,再检查代码',
1135
- kind: 'progress',
1397
+ });
1398
+ coordinator.handleAgentNotification(session, {
1399
+ method: 'session/update',
1400
+ params: {
1401
+ update: {
1402
+ sessionUpdate: 'tool_call_update',
1403
+ id: 'new-tool',
1404
+ title: 'tool update',
1405
+ status: 'completed',
1406
+ content: [
1407
+ {
1408
+ type: 'content',
1409
+ content: {
1410
+ type: 'text',
1411
+ text: 'terminal result\n- **output:** pdf files\n- **exit_code:** 0',
1412
+ },
1413
+ },
1414
+ ],
1415
+ },
1416
+ },
1417
+ });
1418
+ coordinator.handleAgentNotification(session, {
1419
+ method: 'session/update',
1420
+ params: {
1421
+ update: {
1422
+ sessionUpdate: 'agent_thought_chunk',
1423
+ content: {
1424
+ type: 'text',
1425
+ text: 'Let me present the PDF files to the user.',
1426
+ },
1427
+ },
1136
1428
  },
1429
+ });
1430
+ strict_1.default.deepEqual(upserted.map((entry) => entry.content), [
1431
+ '🔧 terminal: completed - terminal result\n- **output:** pdf files\n- **exit_code:** 0',
1432
+ 'Let me present the PDF files to the user.',
1433
+ ]);
1434
+ strict_1.default.deepEqual(emitted.map((event) => event.content), [
1435
+ '🔧 terminal: completed - terminal result\n- **output:** pdf files\n- **exit_code:** 0',
1436
+ 'Let me present the PDF files to the user.',
1137
1437
  ]);
1138
- strict_1.default.deepEqual(emitted.map((event) => event.type), ['preview_start', 'update_message']);
1139
- strict_1.default.equal(emitted[0]?.previewHandle, 'thought-preview-1');
1140
- strict_1.default.equal(emitted[0]?.bridgeKind, 'thought');
1141
- strict_1.default.equal(emitted[0]?.content, '先理解问题');
1142
- strict_1.default.equal(emitted[1]?.content, '先理解问题,再检查代码');
1143
- strict_1.default.equal(session.currentTurn.thoughtText, '先理解问题,再检查代码');
1144
1438
  });
1145
- (0, node_test_1.default)('ACP store preserves structured progress metadata', () => {
1146
- const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-thought-store-'));
1147
- const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
1148
- try {
1439
+ (0, node_test_1.default)('Hermes ACP progress updates suppress restored plan and tool-scoped assistant chunks', () => {
1440
+ const appended = [];
1441
+ const emitted = [];
1442
+ const coordinator = new local_core_acp_turn_coordinator_js_1.LocalCoreAcpTurnCoordinator({
1443
+ appendMessage: (_threadId, _role, content, _kind, _toolCall, bridgeKind) => appended.push({ content, bridgeKind }),
1444
+ emitBridge: (event) => emitted.push(event),
1445
+ updateRunStatus: () => { },
1446
+ sendRaw: () => true,
1447
+ });
1448
+ const session = {
1449
+ threadId: 'thread-1',
1450
+ bridgeSessionKey: 'session:thread-1',
1451
+ currentRunId: 'run-1',
1452
+ currentTurn: {
1453
+ runId: 'run-1',
1454
+ replyCtx: 'run-1',
1455
+ previewHandle: 'preview-1',
1456
+ agentType: 'hermes',
1457
+ assistantText: '',
1458
+ priorAssistantProgressMessages: [
1459
+ { kind: 'plan', content: '旧计划 | 已完成' },
1460
+ { kind: 'tool', content: 'raw restored command output' },
1461
+ ],
1462
+ typingStarted: true,
1463
+ previewStarted: false,
1464
+ permission: null,
1465
+ },
1466
+ loadReplayMode: false,
1467
+ schedulerJobCreatedByRun: new Map(),
1468
+ pendingRawAssistantProgressChunks: ['raw restored command output', 'fresh command output'],
1469
+ };
1470
+ coordinator.handleAgentNotification(session, {
1471
+ method: 'session/update',
1472
+ params: {
1473
+ update: {
1474
+ sessionUpdate: 'plan',
1475
+ entries: [{ content: '旧计划' }, { content: '已完成' }],
1476
+ },
1477
+ },
1478
+ });
1479
+ coordinator.handleAgentNotification(session, {
1480
+ method: 'session/update',
1481
+ params: {
1482
+ update: {
1483
+ sessionUpdate: 'agent_message_chunk',
1484
+ content: { type: 'text', text: 'raw restored command output' },
1485
+ },
1486
+ },
1487
+ });
1488
+ coordinator.handleAgentNotification(session, {
1489
+ method: 'session/update',
1490
+ params: {
1491
+ update: {
1492
+ sessionUpdate: 'plan',
1493
+ entries: [{ content: '新计划' }, { content: '进行中' }],
1494
+ },
1495
+ },
1496
+ });
1497
+ coordinator.handleAgentNotification(session, {
1498
+ method: 'session/update',
1499
+ params: {
1500
+ update: {
1501
+ sessionUpdate: 'agent_message_chunk',
1502
+ content: { type: 'text', text: 'fresh command output' },
1503
+ },
1504
+ },
1505
+ });
1506
+ strict_1.default.deepEqual(appended, [
1507
+ { content: '新计划 | 进行中', bridgeKind: 'plan' },
1508
+ { content: 'fresh command output', bridgeKind: 'tool' },
1509
+ ]);
1510
+ strict_1.default.deepEqual(emitted.map((event) => ({ content: event.content, bridgeKind: event.bridgeKind })), [
1511
+ { content: '新计划 | 进行中', bridgeKind: 'plan' },
1512
+ { content: 'fresh command output', bridgeKind: 'tool' },
1513
+ ]);
1514
+ });
1515
+ (0, node_test_1.default)('ACP raw local command output assistant chunks stay out of final assistant buffer', () => {
1516
+ const appended = [];
1517
+ const coordinator = new local_core_acp_turn_coordinator_js_1.LocalCoreAcpTurnCoordinator({
1518
+ appendMessage: (_threadId, _role, content, _kind, _toolCall, bridgeKind) => appended.push({ content, bridgeKind }),
1519
+ emitBridge: () => { },
1520
+ updateRunStatus: () => { },
1521
+ sendRaw: () => true,
1522
+ });
1523
+ const session = {
1524
+ threadId: 'thread-1',
1525
+ bridgeSessionKey: 'session:thread-1',
1526
+ currentRunId: 'run-1',
1527
+ currentTurn: {
1528
+ runId: 'run-1',
1529
+ replyCtx: 'run-1',
1530
+ previewHandle: 'preview-1',
1531
+ assistantText: '',
1532
+ typingStarted: true,
1533
+ previewStarted: false,
1534
+ permission: null,
1535
+ },
1536
+ loadReplayMode: false,
1537
+ schedulerJobCreatedByRun: new Map(),
1538
+ pendingRawAssistantProgressChunks: [],
1539
+ };
1540
+ coordinator.handleAgentNotification(session, {
1541
+ method: '_claude/sdkMessage',
1542
+ params: {
1543
+ message: {
1544
+ type: 'system',
1545
+ subtype: 'local_command_output',
1546
+ content: 'raw command output',
1547
+ },
1548
+ },
1549
+ });
1550
+ coordinator.handleAgentNotification(session, {
1551
+ method: 'session/update',
1552
+ params: {
1553
+ update: {
1554
+ sessionUpdate: 'agent_message_chunk',
1555
+ content: { type: 'text', text: 'raw command output' },
1556
+ },
1557
+ },
1558
+ });
1559
+ coordinator.handleAgentNotification(session, {
1560
+ method: 'session/update',
1561
+ params: {
1562
+ update: {
1563
+ sessionUpdate: 'agent_message_chunk',
1564
+ content: { type: 'text', text: 'final answer' },
1565
+ },
1566
+ },
1567
+ });
1568
+ strict_1.default.deepEqual(appended, [{ content: 'raw command output', bridgeKind: 'tool' }]);
1569
+ strict_1.default.equal(session.currentTurn.assistantText, 'final answer');
1570
+ });
1571
+ (0, node_test_1.default)('ACP final text strips observed provider tool transcript prefix', () => {
1572
+ const currentTurn = {
1573
+ runId: 'run-1',
1574
+ replyCtx: 'run-1',
1575
+ previewHandle: 'preview-1',
1576
+ assistantText: '',
1577
+ thoughtText: '',
1578
+ typingStarted: true,
1579
+ previewStarted: false,
1580
+ thoughtPreviewStarted: false,
1581
+ permission: null,
1582
+ toolObservations: [],
1583
+ };
1584
+ (0, local_core_acp_progress_js_1.recordToolObservation)(currentTurn, {
1585
+ name: 'webReader',
1586
+ title: 'webReader',
1587
+ input: {
1588
+ url: 'https://github.com/Thysrael/Horizon',
1589
+ return_format: 'markdown',
1590
+ retain_images: false,
1591
+ },
1592
+ status: 'completed',
1593
+ outputText: JSON.stringify([{
1594
+ title: 'GitHub - Thysrael/Horizon: Your own AI-powered news radar',
1595
+ description: 'Your own AI-powered news radar. Generates daily briefings in English & Chinese.',
1596
+ url: 'https://github.com/Thysrael/Horizon',
1597
+ }]),
1598
+ });
1599
+ const polluted = [
1600
+ '**🌐 Z.ai Built-in Tool: webReader**',
1601
+ '',
1602
+ '**Input:**',
1603
+ '```json',
1604
+ '{"url":"https://github.com/Thysrael/Horizon","return_format":"markdown","retain_images":false}',
1605
+ '```',
1606
+ '',
1607
+ '*Executing on server...*',
1608
+ ' **Output:**',
1609
+ '**webReader_result_summary:** [{"text": {"title": "GitHub - Thysrael/Horizon: Your own AI-powered news radar", "description": "Your own AI-powered news radar. Generates daily briefings in English & Chinese.", "url": "https...',
1610
+ ' 已放入 `00-Inbox/Horizon.md`。这个项目和咱们的 AI 早报 skill 功能高度重叠,架构可以作为参考。',
1611
+ ].join('\n');
1612
+ strict_1.default.equal((0, local_core_acp_progress_js_1.stripObservedToolTranscriptsFromAssistantText)(polluted, currentTurn.toolObservations), '已放入 `00-Inbox/Horizon.md`。这个项目和咱们的 AI 早报 skill 功能高度重叠,架构可以作为参考。');
1613
+ strict_1.default.equal((0, local_core_acp_progress_js_1.stripObservedToolTranscriptsFromAssistantText)(polluted, []), polluted.trim());
1614
+ });
1615
+ (0, node_test_1.default)('ACP final text keeps normal answers that mention observed tool evidence', () => {
1616
+ const observations = [{
1617
+ name: 'webReader',
1618
+ input: { url: 'https://github.com/Thysrael/Horizon' },
1619
+ outputText: JSON.stringify([{
1620
+ title: 'GitHub - Thysrael/Horizon: Your own AI-powered news radar',
1621
+ url: 'https://github.com/Thysrael/Horizon',
1622
+ }]),
1623
+ }];
1624
+ const answer = [
1625
+ '我看了 https://github.com/Thysrael/Horizon,它是一个 AI 新闻雷达项目。',
1626
+ '结论:可以作为早报 skill 的参考,但不需要直接照搬。',
1627
+ ].join('\n');
1628
+ strict_1.default.equal((0, local_core_acp_progress_js_1.stripObservedToolTranscriptsFromAssistantText)(answer, observations), answer);
1629
+ });
1630
+ (0, node_test_1.default)('ACP plan updates are persisted and emitted as thinking progress', () => {
1631
+ const appended = [];
1632
+ const emitted = [];
1633
+ const coordinator = new local_core_acp_turn_coordinator_js_1.LocalCoreAcpTurnCoordinator({
1634
+ appendMessage: (_threadId, _role, content, kind) => appended.push({ content, kind }),
1635
+ emitBridge: (event) => emitted.push(event),
1636
+ updateRunStatus: () => { },
1637
+ sendRaw: () => true,
1638
+ });
1639
+ const session = {
1640
+ threadId: 'thread-1',
1641
+ bridgeSessionKey: 'session:thread-1',
1642
+ currentRunId: 'run-1',
1643
+ currentTurn: {
1644
+ runId: 'run-1',
1645
+ replyCtx: 'run-1',
1646
+ previewHandle: 'preview-1',
1647
+ assistantText: '',
1648
+ typingStarted: true,
1649
+ previewStarted: false,
1650
+ permission: null,
1651
+ },
1652
+ loadReplayMode: false,
1653
+ schedulerJobCreatedByRun: new Map(),
1654
+ };
1655
+ coordinator.handleAgentNotification(session, {
1656
+ method: 'session/update',
1657
+ params: {
1658
+ update: {
1659
+ sessionUpdate: 'plan',
1660
+ entries: [
1661
+ { content: '检查消息流' },
1662
+ { content: '修复持久化' },
1663
+ ],
1664
+ },
1665
+ },
1666
+ });
1667
+ strict_1.default.deepEqual(appended, [
1668
+ {
1669
+ content: '检查消息流 | 修复持久化',
1670
+ kind: 'progress',
1671
+ },
1672
+ ]);
1673
+ strict_1.default.equal(emitted.length, 1);
1674
+ strict_1.default.equal(emitted[0]?.type, 'reply');
1675
+ strict_1.default.equal(emitted[0]?.bridgeKind, 'plan');
1676
+ strict_1.default.equal(emitted[0]?.content, appended[0]?.content);
1677
+ });
1678
+ (0, node_test_1.default)('ACP thought chunks stream for web/app and start a fresh segment at tool boundaries', () => {
1679
+ const appended = [];
1680
+ const upserted = [];
1681
+ const emitted = [];
1682
+ const coordinator = new local_core_acp_turn_coordinator_js_1.LocalCoreAcpTurnCoordinator({
1683
+ appendMessage: (_threadId, _role, content, kind) => appended.push({ content, kind }),
1684
+ upsertMessage: (_threadId, id, _role, content, kind) => upserted.push({ id, content, kind }),
1685
+ emitBridge: (event) => emitted.push(event),
1686
+ updateRunStatus: () => { },
1687
+ sendRaw: () => true,
1688
+ });
1689
+ const session = {
1690
+ threadId: 'thread-1',
1691
+ bridgeSessionKey: 'session:thread-1',
1692
+ currentRunId: 'run-1',
1693
+ currentTurn: {
1694
+ runId: 'run-1',
1695
+ replyCtx: 'run-1',
1696
+ previewHandle: 'preview-1',
1697
+ thoughtPreviewHandle: 'thought-preview-1',
1698
+ thoughtMessageId: 'run-1-thought-1',
1699
+ assistantText: '',
1700
+ thoughtText: '',
1701
+ thoughtSequence: 1,
1702
+ typingStarted: true,
1703
+ previewStarted: false,
1704
+ thoughtPreviewStarted: false,
1705
+ permission: null,
1706
+ },
1707
+ loadReplayMode: false,
1708
+ schedulerJobCreatedByRun: new Map(),
1709
+ };
1710
+ coordinator.handleAgentNotification(session, {
1711
+ method: 'session/update',
1712
+ params: {
1713
+ update: {
1714
+ sessionUpdate: 'agent_thought_chunk',
1715
+ content: { type: 'text', text: '先理解问题' },
1716
+ },
1717
+ },
1718
+ });
1719
+ strict_1.default.deepEqual(appended, []);
1720
+ strict_1.default.deepEqual(upserted, [
1721
+ {
1722
+ id: 'run-1-thought-1',
1723
+ content: '先理解问题',
1724
+ kind: 'progress',
1725
+ },
1726
+ ]);
1727
+ strict_1.default.deepEqual(emitted.map((event) => event.type), ['preview_start']);
1728
+ strict_1.default.equal(emitted[0]?.content, '先理解问题');
1729
+ coordinator.handleAgentNotification(session, {
1730
+ method: 'session/update',
1731
+ params: {
1732
+ update: {
1733
+ sessionUpdate: 'agent_thought_chunk',
1734
+ content: { type: 'text', text: ',再检查代码' },
1735
+ },
1736
+ },
1737
+ });
1738
+ strict_1.default.deepEqual(appended, []);
1739
+ strict_1.default.deepEqual(upserted.map((entry) => entry.content), ['先理解问题', '先理解问题,再检查代码']);
1740
+ strict_1.default.deepEqual(emitted.map((event) => event.type), ['preview_start', 'update_message']);
1741
+ strict_1.default.equal(emitted[1]?.content, '先理解问题,再检查代码');
1742
+ coordinator.handleAgentNotification(session, {
1743
+ method: 'session/update',
1744
+ params: {
1745
+ update: {
1746
+ sessionUpdate: 'tool_call',
1747
+ title: 'webReader',
1748
+ },
1749
+ },
1750
+ });
1751
+ strict_1.default.deepEqual(appended, []);
1752
+ strict_1.default.equal(session.currentTurn.thoughtText, '');
1753
+ strict_1.default.equal(session.currentTurn.thoughtMessageId, 'run-1-thought-2');
1754
+ strict_1.default.equal(session.currentTurn.thoughtPreviewHandle, 'run-1-thought-preview-2');
1755
+ });
1756
+ (0, node_test_1.default)('ACP store preserves structured progress metadata', () => {
1757
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-thought-store-'));
1758
+ const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
1759
+ try {
1149
1760
  const thread = store.createThread('project-1', 'Thread');
1150
1761
  store.upsertMessage(thread.id, 'run-1-thought', 'assistant', '先理解问题', 'progress', undefined, 'thought');
1151
1762
  store.upsertMessage(thread.id, 'run-1-thought', 'assistant', '先理解问题,再检查代码', 'progress', undefined, 'thought');
@@ -1193,6 +1804,29 @@ const server_routes_js_1 = require("../services/local-ai-core/src/runtime/server
1193
1804
  (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
1194
1805
  }
1195
1806
  });
1807
+ (0, node_test_1.default)('channel gateways ignore unowned bridge events without route miss log noise', async () => {
1808
+ const logs = [];
1809
+ const commonOptions = {
1810
+ store: {},
1811
+ readConfig: async () => null,
1812
+ getWorkspaceRouter: () => ({}),
1813
+ eventBus: { emit: () => { }, on: () => () => { } },
1814
+ log: (line) => logs.push(line),
1815
+ };
1816
+ const larkGateway = new local_core_lark_gateway_js_1.LocalCoreLarkGateway(commonOptions);
1817
+ const weixinGateway = new local_core_weixin_gateway_js_1.LocalCoreWeixinGateway(commonOptions);
1818
+ await larkGateway.onBridgeEvent({
1819
+ type: 'update_message',
1820
+ sessionKey: 'localcore-acp:project-1:thread-1',
1821
+ content: 'stream chunk',
1822
+ });
1823
+ await weixinGateway.onBridgeEvent({
1824
+ type: 'update_message',
1825
+ sessionKey: 'localcore-acp:project-1:thread-1',
1826
+ content: 'stream chunk',
1827
+ });
1828
+ strict_1.default.deepEqual(logs, []);
1829
+ });
1196
1830
  (0, node_test_1.default)('lark bridge sends completed thought once before final answer', async () => {
1197
1831
  const createdCards = [];
1198
1832
  const patchedCards = [];
@@ -1355,6 +1989,77 @@ const server_routes_js_1 = require("../services/local-ai-core/src/runtime/server
1355
1989
  strict_1.default.deepEqual(patchedCards, []);
1356
1990
  strict_1.default.deepEqual(storedMessageIds, ['lark-msg-1']);
1357
1991
  });
1992
+ (0, node_test_1.default)('lark bridge creates final replies without patching prior final cards', async () => {
1993
+ const createdCards = [];
1994
+ const patchedCards = [];
1995
+ const client = {
1996
+ im: {
1997
+ message: {
1998
+ create: async (request) => {
1999
+ const messageId = `lark-msg-${createdCards.length + 1}`;
2000
+ const card = JSON.parse(String(request.data.content || '{}'));
2001
+ createdCards.push({
2002
+ messageId,
2003
+ text: String(card.elements?.[0]?.content || ''),
2004
+ });
2005
+ return { data: { message_id: messageId } };
2006
+ },
2007
+ patch: async (request) => {
2008
+ const card = JSON.parse(String(request.data.content || '{}'));
2009
+ patchedCards.push({
2010
+ messageId: String(request.path.message_id || ''),
2011
+ text: String(card.elements?.[0]?.content || ''),
2012
+ });
2013
+ },
2014
+ },
2015
+ },
2016
+ };
2017
+ const gateway = new local_core_lark_gateway_js_1.LocalCoreLarkGateway({
2018
+ store: {
2019
+ getPlatformThreadBinding: () => ({
2020
+ workspace_id: 'default',
2021
+ platform: 'lark',
2022
+ chat_id: 'chat-1',
2023
+ platform_user_id: 'user-1',
2024
+ thread_id: 'thread-1',
2025
+ last_platform_message_id: null,
2026
+ }),
2027
+ updatePlatformThreadMessageId: () => { },
2028
+ },
2029
+ readConfig: async () => null,
2030
+ getWorkspaceRouter: () => ({}),
2031
+ eventBus: { emit: () => { }, on: () => () => { } },
2032
+ });
2033
+ const internals = gateway;
2034
+ internals.runtime.set('default', {
2035
+ workspaceId: 'default',
2036
+ enabled: true,
2037
+ status: 'running',
2038
+ connected: true,
2039
+ appId: 'app-1',
2040
+ client,
2041
+ });
2042
+ internals.threadRouting.set('session:thread-1', {
2043
+ workspaceId: 'default',
2044
+ platformUserId: 'user-1',
2045
+ chatId: 'chat-1',
2046
+ threadId: 'thread-1',
2047
+ });
2048
+ await gateway.onBridgeEvent({
2049
+ type: 'reply',
2050
+ sessionKey: 'session:thread-1',
2051
+ replyCtx: 'run-1',
2052
+ content: 'first final',
2053
+ });
2054
+ await gateway.onBridgeEvent({
2055
+ type: 'reply',
2056
+ sessionKey: 'session:thread-1',
2057
+ replyCtx: 'run-2',
2058
+ content: 'second final',
2059
+ });
2060
+ strict_1.default.deepEqual(createdCards.map((card) => card.text), ['first final', 'second final']);
2061
+ strict_1.default.deepEqual(patchedCards, []);
2062
+ });
1358
2063
  (0, node_test_1.default)('lark bridge sends tool and final as separate cards without patching tool progress', async () => {
1359
2064
  const createdCards = [];
1360
2065
  const patchedCards = [];