@kafca/agentdock 0.1.30 → 0.1.33

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 (110) hide show
  1. package/LICENSE.md +73 -0
  2. package/README.md +15 -0
  3. package/dist/renderer/assets/{Badge-DlVJI1Ys.js → Badge-VeSLCPPV.js} +1 -1
  4. package/dist/renderer/assets/{Chat-oa8OjaHQ.js → Chat-DWTQeTgn.js} +1 -1
  5. package/dist/renderer/assets/{ChatMarkdown-C-N8KS5C.js → ChatMarkdown-C9gSQIVb.js} +1 -1
  6. package/dist/renderer/assets/{Config-D9sL_d4D.js → Config-CTA-pZNR.js} +1 -1
  7. package/dist/renderer/assets/{CronList-Z5JU3DHk.js → CronList-BngBCnFY.js} +1 -1
  8. package/dist/renderer/assets/{Dashboard-D3BwbG43.js → Dashboard-D4pGvDpc.js} +2 -2
  9. package/dist/renderer/assets/{EmptyState-QOj765Ul.js → EmptyState-DMf8rzDs.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeDetail-J7n01mPu.js → KnowledgeDetail-C1Ym_cHk.js} +1 -1
  11. package/dist/renderer/assets/{KnowledgeHome-BFnH0NVk.js → KnowledgeHome-DPRiR72G.js} +1 -1
  12. package/dist/renderer/assets/{Logs-DfSUifXc.js → Logs-CRHecmzO.js} +1 -1
  13. package/dist/renderer/assets/{Modal-QjEGhyru.js → Modal-BDhhPv2l.js} +1 -1
  14. package/dist/renderer/assets/{Page-qdHFSw7x.js → Page-DdDZN7AE.js} +1 -1
  15. package/dist/renderer/assets/{ProjectDetail-DS_rfvhh.js → ProjectDetail-DCsr3LkW.js} +1 -1
  16. package/dist/renderer/assets/{ProjectList-COtyZd7H.js → ProjectList-Cnm-iLvg.js} +1 -1
  17. package/dist/renderer/assets/{Select-BQ8mncVu.js → Select-Dw4FCXhJ.js} +1 -1
  18. package/dist/renderer/assets/{SessionChat-DjVnjuQy.js → SessionChat-O-6abK8x.js} +1 -1
  19. package/dist/renderer/assets/{SessionList-88dgGZJ8.js → SessionList-5yptvVL9.js} +1 -1
  20. package/dist/renderer/assets/ThreadChat-CIM_o2HO.js +23 -0
  21. package/dist/renderer/assets/{Workspace-D_hzSJjX.js → Workspace-D9UeUCNr.js} +1 -1
  22. package/dist/renderer/assets/{arrow-left-BMpknulW.js → arrow-left-CKfxNOXE.js} +1 -1
  23. package/dist/renderer/assets/{book-open-CiaOeQbU.js → book-open-CQddlENV.js} +1 -1
  24. package/dist/renderer/assets/{check-CgwYH4GF.js → check-CZYvOmIS.js} +1 -1
  25. package/dist/renderer/assets/{chevron-down-BzD2S1OO.js → chevron-down-CVIWFoXF.js} +1 -1
  26. package/dist/renderer/assets/{circle-BDHD7g7l.js → circle-DSfdQM7k.js} +1 -1
  27. package/dist/renderer/assets/{desktop-BiR5LPL5.js → desktop-B9KUgeOV.js} +1 -1
  28. package/dist/renderer/assets/{desktop-BkMW-h5p.js → desktop-O8_6ZCVZ.js} +2 -2
  29. package/dist/renderer/assets/{funnel-Bruf5FYA.js → funnel-pB5NegZB.js} +1 -1
  30. package/dist/renderer/assets/{heart-D7Mz04B0.js → heart-BT5laZ_b.js} +1 -1
  31. package/dist/renderer/assets/{index-CL1e8m3U.js → index-BYyDk28K.js} +2 -2
  32. package/dist/renderer/assets/{index-gZIRr4_o.js → index-zTUUYax3.js} +1 -1
  33. package/dist/renderer/assets/{pencil-C6gzDMse.js → pencil-C0thxLi6.js} +1 -1
  34. package/dist/renderer/assets/{play-DqTXX-Pr.js → play-CmZ0kWtt.js} +1 -1
  35. package/dist/renderer/assets/{plug-ykmbeaO3.js → plug-CeDQatBe.js} +1 -1
  36. package/dist/renderer/assets/{plus-DHE60vMX.js → plus-Di__xFOZ.js} +1 -1
  37. package/dist/renderer/assets/projects-B-pbhhtH.js +1 -0
  38. package/dist/renderer/assets/{save-CGIvrdQI.js → save-BDatCjfO.js} +1 -1
  39. package/dist/renderer/assets/{search-D7IFAwyE.js → search-DnTOjP0C.js} +1 -1
  40. package/dist/renderer/assets/{server-aOsirp7I.js → server-Ey_iHY6m.js} +1 -1
  41. package/dist/renderer/assets/{session-utils-Dxfp4ohQ.js → session-utils-DWaM7Rkr.js} +1 -1
  42. package/dist/renderer/assets/{sessions-Db1XPbG2.js → sessions-CYYjLD5I.js} +1 -1
  43. package/dist/renderer/assets/{shield-check-ChXeQK-d.js → shield-check-Br8BQzcn.js} +1 -1
  44. package/dist/renderer/assets/{trash-2-CfN-c1zw.js → trash-2-7YD616fw.js} +1 -1
  45. package/dist/renderer/assets/{user-C7OHaBp4.js → user-BR_5jmr9.js} +1 -1
  46. package/dist/renderer/assets/{wifi-off-CpaJigye.js → wifi-off-By9QdREz.js} +1 -1
  47. package/dist/renderer/index.html +1 -1
  48. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +28 -4
  49. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-response-processor.js +2 -4
  50. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +102 -15
  51. package/dist-electron/services/local-ai-core/src/agents/claudecode/behavior.js +5 -0
  52. package/dist-electron/services/local-ai-core/src/agents/claudecode/definition.js +36 -0
  53. package/dist-electron/services/local-ai-core/src/agents/claudecode/index.js +15 -0
  54. package/dist-electron/services/local-ai-core/src/agents/codex/behavior.js +5 -0
  55. package/dist-electron/services/local-ai-core/src/agents/codex/definition.js +24 -0
  56. package/dist-electron/services/local-ai-core/src/agents/codex/index.js +15 -0
  57. package/dist-electron/services/local-ai-core/src/agents/hermes/behavior.js +29 -0
  58. package/dist-electron/services/local-ai-core/src/agents/hermes/definition.js +22 -0
  59. package/dist-electron/services/local-ai-core/src/agents/hermes/index.js +15 -0
  60. package/dist-electron/services/local-ai-core/src/agents/index.js +98 -0
  61. package/dist-electron/services/local-ai-core/src/agents/localcore-acp/behavior.js +5 -0
  62. package/dist-electron/services/local-ai-core/src/agents/localcore-acp/definition.js +14 -0
  63. package/dist-electron/services/local-ai-core/src/agents/localcore-acp/index.js +27 -0
  64. package/dist-electron/services/local-ai-core/src/agents/opencode/behavior.js +5 -0
  65. package/dist-electron/services/local-ai-core/src/agents/opencode/definition.js +18 -0
  66. package/dist-electron/services/local-ai-core/src/agents/opencode/index.js +15 -0
  67. package/dist-electron/services/local-ai-core/src/agents/opencode/launch.js +119 -0
  68. package/dist-electron/services/local-ai-core/src/agents/pi/behavior.js +5 -0
  69. package/dist-electron/services/local-ai-core/src/agents/pi/definition.js +23 -0
  70. package/dist-electron/services/local-ai-core/src/agents/pi/index.js +15 -0
  71. package/dist-electron/services/local-ai-core/src/agents/pi/launch.js +139 -0
  72. package/dist-electron/services/local-ai-core/src/agents/shared/acp-behavior.js +11 -0
  73. package/dist-electron/services/local-ai-core/src/agents/shared/agent-plugin.js +60 -0
  74. package/dist-electron/services/local-ai-core/src/agents/shared/definition.js +12 -0
  75. package/dist-electron/services/local-ai-core/src/agents/shared/launch-utils.js +122 -0
  76. package/dist-electron/services/local-ai-core/src/agents/shared/progress-replay.js +19 -0
  77. package/dist-electron/services/local-ai-core/src/agents/shared/replay-prefix.js +68 -0
  78. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +0 -2
  79. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +0 -1
  80. package/dist-electron/services/local-ai-core/src/cli/lac.js +2 -1
  81. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +17 -11
  82. package/dist-electron/services/local-ai-core/src/plugins/builtin/agent-localcore-acp-plugin.js +9 -130
  83. package/dist-electron/services/local-ai-core/src/router/workspace-route-config.js +22 -397
  84. package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +9 -45
  85. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +9 -4
  86. package/dist-electron/services/local-ai-core/src/runtime/local-core-runtime-state.js +62 -1
  87. package/dist-electron/services/local-ai-core/src/runtime/runtime-detection-service.js +2 -12
  88. package/dist-electron/services/local-ai-core/src/scheduler/lark-schedule-adapter.js +16 -7
  89. package/dist-electron/services/local-ai-core/src/scheduler/local-schedule-adapter.js +10 -2
  90. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-job-route.js +24 -0
  91. package/dist-electron/services/local-ai-core/src/scheduler/weixin-schedule-adapter.js +15 -6
  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/dist-electron/{electron → tests/contracts}/local-core-contracts.test.js +1 -1
  96. package/dist-electron/{electron → tests/electron}/agent-runtime-detector.test.js +31 -1
  97. package/dist-electron/{electron → tests/electron}/lac-cli.test.js +6 -3
  98. package/dist-electron/{electron → tests/electron}/plugin-kernel.test.js +93 -13
  99. package/dist-electron/{electron → tests/electron}/renderer-packaging.test.js +11 -6
  100. package/dist-electron/{electron → tests/electron}/runtime-detection-service.test.js +1 -1
  101. package/dist-electron/{electron → tests/electron}/security-approval-store.test.js +2 -2
  102. package/dist-electron/{electron → tests/electron}/workspace-task-store.test.js +95 -7
  103. package/dist-electron/{electron → tests/integration}/channel-content.test.js +2 -2
  104. package/dist-electron/{electron → tests/integration}/local-core-acp-content.test.js +1 -1
  105. package/dist-electron/{electron → tests/integration}/local-core-refactor.test.js +437 -15
  106. package/package.json +3 -2
  107. package/dist/renderer/assets/ThreadChat-BCdbTxBo.js +0 -23
  108. package/dist/renderer/assets/projects-Bx5p10th.js +0 -1
  109. /package/dist-electron/{electron → tests/contracts}/architecture-docs.test.js +0 -0
  110. /package/dist-electron/{electron → tests/electron}/knowledge-skill-script.test.js +0 -0
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RuntimeDetectionService = void 0;
4
4
  const desktop_js_1 = require("../../../../shared/desktop.js");
5
+ const index_js_1 = require("../agents/index.js");
5
6
  const agent_runtime_detector_js_1 = require("./agent-runtime-detector.js");
6
7
  const runtime_detection_store_js_1 = require("./runtime-detection-store.js");
7
8
  class RuntimeDetectionService {
@@ -124,16 +125,5 @@ function changedRuntimes(previous, next) {
124
125
  });
125
126
  }
126
127
  function displayName(agentType) {
127
- const names = {
128
- pi: 'Pi',
129
- opencode: 'OpenCode',
130
- codex: 'Codex',
131
- claudecode: 'Claude Code',
132
- cursor: 'Cursor',
133
- gemini: 'Gemini',
134
- qoder: 'Qoder',
135
- iflow: 'iFlow',
136
- [desktop_js_1.LOCALCORE_ACP_AGENT_TYPE]: 'LocalCore ACP',
137
- };
138
- return names[agentType] || agentType;
128
+ return (0, index_js_1.resolveAgentRuntimeDefinition)(agentType)?.displayName || agentType;
139
129
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LarkScheduleAdapter = void 0;
4
4
  const scheduled_conversation_executor_js_1 = require("./scheduled-conversation-executor.js");
5
5
  const lark_execution_policies_js_1 = require("./lark-execution-policies.js");
6
+ const scheduled_job_route_js_1 = require("./scheduled-job-route.js");
6
7
  class LarkScheduleAdapter {
7
8
  options;
8
9
  executor;
@@ -31,7 +32,7 @@ class LarkScheduleAdapter {
31
32
  if (!channelRuntime.sendScheduledMessage) {
32
33
  throw new Error('Lark channel runtime does not support scheduled delivery.');
33
34
  }
34
- platformMessageId = await channelRuntime.sendScheduledMessage(job.workspaceId, job.route, execution.replyText);
35
+ platformMessageId = await channelRuntime.sendScheduledMessage(job.workspaceId, (0, scheduled_job_route_js_1.withoutThreadRoute)(job.route), execution.replyText);
35
36
  if (!platformMessageId) {
36
37
  throw new Error('Lark gateway did not return a message id for scheduled delivery.');
37
38
  }
@@ -46,16 +47,15 @@ class LarkScheduleAdapter {
46
47
  async resolveThread(job) {
47
48
  const workspaceRouter = this.options.getWorkspaceRouter();
48
49
  const route = job.route;
49
- if (route.threadId) {
50
- await workspaceRouter.getThread(route.threadId);
51
- return route.threadId;
52
- }
53
50
  const channelId = route.channelId;
54
51
  const participantId = route.participantId || '';
55
- const binding = this.options.store.getPlatformThreadBinding(job.workspaceId, channelId, participantId);
56
- if (binding?.thread_id) {
52
+ const binding = this.options.store.getPlatformThreadBinding(job.workspaceId, channelId, participantId, 'lark');
53
+ if (binding?.thread_id && await this.threadExists(binding.thread_id)) {
57
54
  return binding.thread_id;
58
55
  }
56
+ if (route.threadId && await this.threadExists(route.threadId)) {
57
+ return route.threadId;
58
+ }
59
59
  const thread = await workspaceRouter.createThread(job.workspaceId, job.description || `Scheduled ${job.platform} task`);
60
60
  const now = new Date().toISOString();
61
61
  this.options.store.upsertPlatformThreadBinding({
@@ -73,5 +73,14 @@ class LarkScheduleAdapter {
73
73
  }
74
74
  return thread.id;
75
75
  }
76
+ async threadExists(threadId) {
77
+ try {
78
+ await this.options.getWorkspaceRouter().getThread(threadId);
79
+ return true;
80
+ }
81
+ catch {
82
+ return false;
83
+ }
84
+ }
76
85
  }
77
86
  exports.LarkScheduleAdapter = LarkScheduleAdapter;
@@ -33,8 +33,7 @@ class LocalScheduleAdapter {
33
33
  }
34
34
  async resolveThread(job) {
35
35
  const workspaceRouter = this.options.getWorkspaceRouter();
36
- if (job.route.threadId) {
37
- await workspaceRouter.getThread(job.route.threadId);
36
+ if (job.route.threadId && await this.threadExists(job.route.threadId)) {
38
37
  return job.route.threadId;
39
38
  }
40
39
  const title = `[Scheduled] ${job.description || job.id}`;
@@ -46,6 +45,15 @@ class LocalScheduleAdapter {
46
45
  const created = await workspaceRouter.createThread(job.workspaceId, title);
47
46
  return created.id;
48
47
  }
48
+ async threadExists(threadId) {
49
+ try {
50
+ await this.options.getWorkspaceRouter().getThread(threadId);
51
+ return true;
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ }
49
57
  async waitForRun(runId, timeoutMs = 15 * 60 * 1000) {
50
58
  const startedAt = Date.now();
51
59
  while (Date.now() - startedAt < timeoutMs) {
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withoutThreadRoute = withoutThreadRoute;
4
+ exports.scheduledJobMatchesPlatformBinding = scheduledJobMatchesPlatformBinding;
5
+ exports.scheduledJobMatchesCliContext = scheduledJobMatchesCliContext;
6
+ function withoutThreadRoute(route) {
7
+ const { threadId: _threadId, ...deliveryRoute } = route;
8
+ return deliveryRoute;
9
+ }
10
+ function scheduledJobMatchesPlatformBinding(job, binding) {
11
+ return (job.workspaceId === binding.workspace_id &&
12
+ job.platform === binding.platform &&
13
+ job.route.channelId === binding.chat_id &&
14
+ String(job.route.participantId || '') === String(binding.platform_user_id || ''));
15
+ }
16
+ function scheduledJobMatchesCliContext(job, context) {
17
+ if (!context.workspaceId || !context.platform || !context.chatId) {
18
+ return false;
19
+ }
20
+ return (job.workspaceId === context.workspaceId &&
21
+ job.platform === context.platform &&
22
+ job.route.channelId === context.chatId &&
23
+ String(job.route.participantId || '') === String(context.platformUserId || ''));
24
+ }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WeixinScheduleAdapter = void 0;
4
4
  const scheduled_conversation_executor_js_1 = require("./scheduled-conversation-executor.js");
5
5
  const weixin_execution_policies_js_1 = require("./weixin-execution-policies.js");
6
+ const scheduled_job_route_js_1 = require("./scheduled-job-route.js");
6
7
  class WeixinScheduleAdapter {
7
8
  options;
8
9
  executor;
@@ -31,7 +32,7 @@ class WeixinScheduleAdapter {
31
32
  if (!channelRuntime.sendScheduledMessage) {
32
33
  throw new Error('WeChat channel runtime does not support scheduled delivery.');
33
34
  }
34
- platformMessageId = await channelRuntime.sendScheduledMessage(job.workspaceId, job.route, execution.replyText);
35
+ platformMessageId = await channelRuntime.sendScheduledMessage(job.workspaceId, (0, scheduled_job_route_js_1.withoutThreadRoute)(job.route), execution.replyText);
35
36
  if (!platformMessageId) {
36
37
  throw new Error('WeChat gateway did not return a message id for scheduled delivery.');
37
38
  }
@@ -46,16 +47,15 @@ class WeixinScheduleAdapter {
46
47
  async resolveThread(job) {
47
48
  const workspaceRouter = this.options.getWorkspaceRouter();
48
49
  const route = job.route;
49
- if (route.threadId) {
50
- await workspaceRouter.getThread(route.threadId);
51
- return route.threadId;
52
- }
53
50
  const channelId = route.channelId;
54
51
  const participantId = route.participantId || '';
55
52
  const binding = this.options.store.getPlatformThreadBinding(job.workspaceId, channelId, participantId, 'weixin');
56
- if (binding?.thread_id) {
53
+ if (binding?.thread_id && await this.threadExists(binding.thread_id)) {
57
54
  return binding.thread_id;
58
55
  }
56
+ if (route.threadId && await this.threadExists(route.threadId)) {
57
+ return route.threadId;
58
+ }
59
59
  const thread = await workspaceRouter.createThread(job.workspaceId, job.description || `Scheduled ${job.platform} task`);
60
60
  const now = new Date().toISOString();
61
61
  this.options.store.upsertPlatformThreadBinding({
@@ -74,5 +74,14 @@ class WeixinScheduleAdapter {
74
74
  }
75
75
  return thread.id;
76
76
  }
77
+ async threadExists(threadId) {
78
+ try {
79
+ await this.options.getWorkspaceRouter().getThread(threadId);
80
+ return true;
81
+ }
82
+ catch {
83
+ return false;
84
+ }
85
+ }
77
86
  }
78
87
  exports.WeixinScheduleAdapter = WeixinScheduleAdapter;
@@ -29,6 +29,7 @@ exports.DESKTOP_AGENT_TYPE_OPTIONS = [
29
29
  'gemini',
30
30
  'qoder',
31
31
  'iflow',
32
+ 'hermes',
32
33
  'localcore-acp',
33
34
  ];
34
35
  exports.DESKTOP_PLATFORM_TYPE_OPTIONS = [
@@ -56,7 +57,7 @@ exports.DESKTOP_PROVIDER_PRESET_OPTIONS = [
56
57
  'ollama',
57
58
  ];
58
59
  exports.DESKTOP_PROVIDER_THINKING_OPTIONS = ['', 'enabled', 'disabled'];
59
- exports.DESKTOP_INTERACTIVE_PERMISSION_AGENT_TYPES = ['pi', 'opencode', 'codex', 'claudecode', 'acp', 'localcore-acp'];
60
+ exports.DESKTOP_INTERACTIVE_PERMISSION_AGENT_TYPES = ['pi', 'opencode', 'codex', 'claudecode', 'hermes', 'acp', 'localcore-acp'];
60
61
  exports.LOCALCORE_ACP_AGENT_TYPE = 'localcore-acp';
61
62
  exports.SCHEDULER_PROTOCOL_INSTRUCTION = [
62
63
  '[Scheduler Tools]',
@@ -242,5 +243,6 @@ function isAcpAgentType(agentType) {
242
243
  || normalized === 'opencode'
243
244
  || normalized === 'codex'
244
245
  || normalized === 'claudecode'
246
+ || normalized === 'hermes'
245
247
  || normalized === exports.LOCALCORE_ACP_AGENT_TYPE;
246
248
  }
@@ -5,7 +5,7 @@ exports.isInternalProgressBridgeKind = isInternalProgressBridgeKind;
5
5
  exports.isAwaitingInputBridgeStatus = isAwaitingInputBridgeStatus;
6
6
  exports.findStreamingPreviewMessage = findStreamingPreviewMessage;
7
7
  exports.shouldReplacePreviewWithReply = shouldReplacePreviewWithReply;
8
- exports.advancePreviewContent = advancePreviewContent;
8
+ exports.shouldAcceptLiveBridgeEvent = shouldAcceptLiveBridgeEvent;
9
9
  exports.settlePreviewMessages = settlePreviewMessages;
10
10
  exports.finalizeTurnMessageKinds = finalizeTurnMessageKinds;
11
11
  exports.extractVisibleMessageContent = extractVisibleMessageContent;
@@ -50,26 +50,37 @@ function findStreamingPreviewMessage(messages, previewHandle, replyCtx) {
50
50
  }
51
51
  return messages.find((message) => message.preview && message.turnKey === replyCtx);
52
52
  }
53
- function shouldReplacePreviewWithReply(message, replyContent, replyCtx) {
54
- if (!message.preview || !replyCtx || message.turnKey !== replyCtx) {
53
+ function shouldReplacePreviewWithReply(message, replyContent, replyCtx, replyBridgeKind) {
54
+ if (!message.preview ||
55
+ message.role !== 'assistant' ||
56
+ !replyCtx ||
57
+ message.turnKey !== replyCtx ||
58
+ isInternalProgressBridgeKind(replyBridgeKind) ||
59
+ isInternalProgressBridgeKind(message.bridgeKind)) {
55
60
  return false;
56
61
  }
57
- const content = message.streamTargetContent ?? message.content;
58
- return Boolean(replyContent && content === replyContent && !isInternalProgressBridgeKind(message.bridgeKind));
62
+ return Boolean(replyContent && (message.content === replyContent ||
63
+ !message.bridgeKind ||
64
+ message.bridgeKind === 'assistant'));
59
65
  }
60
- function advancePreviewContent(content, target) {
61
- if (!target) {
62
- return '';
63
- }
64
- if (!content || !target.startsWith(content)) {
65
- return target.slice(0, Math.min(target.length, Math.max(1, Math.ceil(target.length / 24))));
66
+ function shouldAcceptLiveBridgeEvent(input) {
67
+ const replyCtx = String(input.event.replyCtx || '').trim();
68
+ if (!replyCtx) {
69
+ return true;
66
70
  }
67
- if (content === target) {
68
- return target;
71
+ const activeRunId = String(input.activeRunId || '').trim();
72
+ const pendingTurn = input.pendingTurn;
73
+ if (pendingTurn && pendingTurn.sessionKey === input.event.sessionKey) {
74
+ const pendingRunId = String(pendingTurn.runId || '').trim();
75
+ if (pendingRunId) {
76
+ return replyCtx === pendingRunId;
77
+ }
78
+ const supersededRunId = String(pendingTurn.supersededRunId || '').trim();
79
+ if (supersededRunId && replyCtx === supersededRunId) {
80
+ return false;
81
+ }
69
82
  }
70
- const remaining = target.length - content.length;
71
- const step = remaining > 160 ? 14 : remaining > 80 ? 8 : remaining > 24 ? 4 : 2;
72
- return target.slice(0, Math.min(target.length, content.length + step));
83
+ return !activeRunId || replyCtx === activeRunId;
73
84
  }
74
85
  function settlePreviewMessages(messages, turnKey) {
75
86
  let changed = false;
@@ -83,8 +94,6 @@ function settlePreviewMessages(messages, turnKey) {
83
94
  changed = true;
84
95
  return {
85
96
  ...message,
86
- content: message.streamTargetContent ?? message.content,
87
- streamTargetContent: undefined,
88
97
  preview: false,
89
98
  previewPlainText: false,
90
99
  };
@@ -170,8 +170,7 @@ function createKnowledgeBase(overrides) {
170
170
  {
171
171
  id: 'thought-preview',
172
172
  role: 'assistant',
173
- content: 'checking',
174
- streamTargetContent: 'checking the request',
173
+ content: 'checking the request',
175
174
  kind: 'progress',
176
175
  bridgeKind: 'thought',
177
176
  order: 1,
@@ -182,8 +181,7 @@ function createKnowledgeBase(overrides) {
182
181
  {
183
182
  id: 'answer-preview',
184
183
  role: 'assistant',
185
- content: 'Hi',
186
- streamTargetContent: 'Hi! How can I help you today?',
184
+ content: 'Hi! How can I help you today?',
187
185
  kind: 'progress',
188
186
  order: 2,
189
187
  turnKey: 'run-1',
@@ -216,8 +214,7 @@ function createKnowledgeBase(overrides) {
216
214
  const thoughtPreview = {
217
215
  id: 'thought-preview',
218
216
  role: 'assistant',
219
- content: 'checking',
220
- streamTargetContent: 'checking the request',
217
+ content: 'checking the request',
221
218
  kind: 'progress',
222
219
  bridgeKind: 'thought',
223
220
  order: 0,
@@ -228,8 +225,7 @@ function createKnowledgeBase(overrides) {
228
225
  const answerPreview = {
229
226
  id: 'answer-preview',
230
227
  role: 'assistant',
231
- content: 'Hi',
232
- streamTargetContent: 'Hi! How can I help you today?',
228
+ content: 'Hi! How can I help you today?',
233
229
  kind: 'progress',
234
230
  order: 1,
235
231
  turnKey: 'run-1',
@@ -238,6 +234,56 @@ function createKnowledgeBase(overrides) {
238
234
  };
239
235
  strict_1.default.equal((0, thread_chat_model_1.shouldReplacePreviewWithReply)(thoughtPreview, 'Hi! How can I help you today?', 'run-1'), false);
240
236
  strict_1.default.equal((0, thread_chat_model_1.shouldReplacePreviewWithReply)(answerPreview, 'Hi! How can I help you today?', 'run-1'), true);
237
+ strict_1.default.equal((0, thread_chat_model_1.shouldReplacePreviewWithReply)(answerPreview, 'Hi! Final answer.', 'run-1'), true);
238
+ strict_1.default.equal((0, thread_chat_model_1.shouldReplacePreviewWithReply)(answerPreview, 'tool result', 'run-1', 'tool'), false);
239
+ });
240
+ (0, node_test_1.default)('shouldAcceptLiveBridgeEvent rejects stale events from the superseded run while a new turn is pending', () => {
241
+ const pendingTurn = {
242
+ sessionKey: 'session-1',
243
+ userOrder: 3,
244
+ supersededRunId: 'run-old',
245
+ };
246
+ strict_1.default.equal((0, thread_chat_model_1.shouldAcceptLiveBridgeEvent)({
247
+ event: { sessionKey: 'session-1', replyCtx: 'run-old' },
248
+ activeRunId: 'run-old',
249
+ pendingTurn,
250
+ }), false);
251
+ strict_1.default.equal((0, thread_chat_model_1.shouldAcceptLiveBridgeEvent)({
252
+ event: { sessionKey: 'session-1', replyCtx: 'run-new' },
253
+ activeRunId: 'run-old',
254
+ pendingTurn,
255
+ }), false);
256
+ strict_1.default.equal((0, thread_chat_model_1.shouldAcceptLiveBridgeEvent)({
257
+ event: { sessionKey: 'session-1', replyCtx: 'run-new' },
258
+ activeRunId: '',
259
+ pendingTurn,
260
+ }), true);
261
+ });
262
+ (0, node_test_1.default)('shouldAcceptLiveBridgeEvent pins live events to the current run once known', () => {
263
+ strict_1.default.equal((0, thread_chat_model_1.shouldAcceptLiveBridgeEvent)({
264
+ event: { sessionKey: 'session-1', replyCtx: 'run-current' },
265
+ activeRunId: 'run-current',
266
+ pendingTurn: null,
267
+ }), true);
268
+ strict_1.default.equal((0, thread_chat_model_1.shouldAcceptLiveBridgeEvent)({
269
+ event: { sessionKey: 'session-1', replyCtx: 'run-stale' },
270
+ activeRunId: 'run-current',
271
+ pendingTurn: null,
272
+ }), false);
273
+ strict_1.default.equal((0, thread_chat_model_1.shouldAcceptLiveBridgeEvent)({
274
+ event: { sessionKey: 'session-1', replyCtx: 'run-current' },
275
+ activeRunId: 'run-current',
276
+ pendingTurn: {
277
+ sessionKey: 'session-1',
278
+ userOrder: 1,
279
+ runId: 'run-current',
280
+ },
281
+ }), true);
282
+ strict_1.default.equal((0, thread_chat_model_1.shouldAcceptLiveBridgeEvent)({
283
+ event: { sessionKey: 'session-1' },
284
+ activeRunId: 'run-current',
285
+ pendingTurn: null,
286
+ }), true);
241
287
  });
242
288
  (0, node_test_1.default)('finalizeTurnMessageKinds marks only the last non-progress turn message as final', () => {
243
289
  const messages = [
@@ -350,8 +396,7 @@ function createKnowledgeBase(overrides) {
350
396
  {
351
397
  id: 'preview-1',
352
398
  role: 'assistant',
353
- content: 'Hel',
354
- streamTargetContent: 'Hello',
399
+ content: 'Hello',
355
400
  kind: 'progress',
356
401
  order: 1,
357
402
  turnKey: 'run-1',
@@ -361,8 +406,7 @@ function createKnowledgeBase(overrides) {
361
406
  {
362
407
  id: 'preview-2',
363
408
  role: 'assistant',
364
- content: 'Wor',
365
- streamTargetContent: 'World',
409
+ content: 'World',
366
410
  kind: 'progress',
367
411
  order: 2,
368
412
  turnKey: 'run-2',
@@ -373,14 +417,9 @@ function createKnowledgeBase(overrides) {
373
417
  const settled = (0, thread_chat_model_1.settlePreviewMessages)(messages, 'run-1');
374
418
  strict_1.default.equal(settled[0]?.content, 'Hello');
375
419
  strict_1.default.equal(settled[0]?.preview, false);
376
- strict_1.default.equal(settled[1]?.content, 'Wor');
420
+ strict_1.default.equal(settled[1]?.content, 'World');
377
421
  strict_1.default.equal(settled[1]?.preview, true);
378
422
  });
379
- (0, node_test_1.default)('advancePreviewContent moves monotonically toward the target content', () => {
380
- strict_1.default.equal((0, thread_chat_model_1.advancePreviewContent)('', 'Hello'), 'H');
381
- strict_1.default.equal((0, thread_chat_model_1.advancePreviewContent)('Hel', 'Hello'), 'Hello');
382
- strict_1.default.equal((0, thread_chat_model_1.advancePreviewContent)('Mismatch', 'Hello'), 'H');
383
- });
384
423
  (0, node_test_1.default)('tool result card prefers structured tool call fields', () => {
385
424
  const card = (0, thread_chat_message_blocks_1.toolCallToResultCard)({
386
425
  name: 'bash',
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const node_test_1 = __importDefault(require("node:test"));
7
7
  const strict_1 = __importDefault(require("node:assert/strict"));
8
- const index_js_1 = require("../packages/contracts/src/index.js");
8
+ const index_js_1 = require("../../packages/contracts/src/index.js");
9
9
  (0, node_test_1.default)('scheduled job execution mode normalization accepts canonical aliases', () => {
10
10
  strict_1.default.equal((0, index_js_1.normalizeScheduledJobExecutionMode)(undefined), 'same-thread');
11
11
  strict_1.default.equal((0, index_js_1.normalizeScheduledJobExecutionMode)('same_thread'), 'same-thread');
@@ -8,7 +8,16 @@ const strict_1 = __importDefault(require("node:assert/strict"));
8
8
  const node_fs_1 = require("node:fs");
9
9
  const node_path_1 = require("node:path");
10
10
  const node_os_1 = require("node:os");
11
- const agent_runtime_detector_js_1 = require("../services/local-ai-core/src/runtime/agent-runtime-detector.js");
11
+ const agent_runtime_detector_js_1 = require("../../services/local-ai-core/src/runtime/agent-runtime-detector.js");
12
+ const index_js_1 = require("../../services/local-ai-core/src/agents/index.js");
13
+ (0, node_test_1.default)('agent runtime definitions own runtime detection metadata', () => {
14
+ strict_1.default.deepEqual((0, index_js_1.resolveAgentRuntimeDefinition)('hermes')?.detection?.commandCandidates, ['hermes']);
15
+ strict_1.default.deepEqual((0, index_js_1.resolveAgentRuntimeDefinition)('codex')?.detection?.bundledRuntimes?.[0], {
16
+ packageName: '@zed-industries/codex-acp',
17
+ candidates: ['bin/codex-acp.js'],
18
+ });
19
+ strict_1.default.equal((0, index_js_1.resolveAgentRuntimeDefinition)('localcore-acp')?.detection?.builtin, true);
20
+ });
12
21
  (0, node_test_1.default)('agent runtime detector only marks commands present on PATH as installed', () => {
13
22
  const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agent-runtime-detector-'));
14
23
  try {
@@ -30,6 +39,8 @@ const agent_runtime_detector_js_1 = require("../services/local-ai-core/src/runti
30
39
  strict_1.default.equal(byType.get('pi')?.status, 'not_installed');
31
40
  strict_1.default.equal(byType.get('codex')?.installed, false);
32
41
  strict_1.default.equal(byType.get('codex')?.status, 'not_installed');
42
+ strict_1.default.equal(byType.get('hermes')?.installed, false);
43
+ strict_1.default.equal(byType.get('hermes')?.status, 'not_installed');
33
44
  strict_1.default.equal(byType.get('localcore-acp')?.installed, true);
34
45
  strict_1.default.equal(byType.get('localcore-acp')?.status, 'installed');
35
46
  strict_1.default.equal(byType.get('localcore-acp')?.source, 'builtin');
@@ -179,6 +190,25 @@ const agent_runtime_detector_js_1 = require("../services/local-ai-core/src/runti
179
190
  (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
180
191
  }
181
192
  });
193
+ (0, node_test_1.default)('agent runtime detector reports Hermes when hermes command is available', () => {
194
+ const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agent-runtime-hermes-version-'));
195
+ try {
196
+ const hermes = (0, node_path_1.join)(dir, 'hermes');
197
+ (0, node_fs_1.writeFileSync)(hermes, '#!/bin/sh\necho "hermes 0.9.1"\n', 'utf8');
198
+ (0, node_fs_1.chmodSync)(hermes, 0o755);
199
+ const hermesRuntime = (0, agent_runtime_detector_js_1.detectInstalledAgentRuntimes)({
200
+ env: { PATH: dir },
201
+ }).find((runtime) => runtime.agentType === 'hermes');
202
+ strict_1.default.equal(hermesRuntime?.status, 'installed');
203
+ strict_1.default.equal(hermesRuntime?.source, 'path');
204
+ strict_1.default.equal(hermesRuntime?.command, hermes);
205
+ strict_1.default.equal(hermesRuntime?.version, '0.9.1');
206
+ strict_1.default.equal(hermesRuntime?.issues.length, 0);
207
+ }
208
+ finally {
209
+ (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
210
+ }
211
+ });
182
212
  (0, node_test_1.default)('agent runtime detector keeps runtime installed when version command fails', () => {
183
213
  const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agent-runtime-version-fail-'));
184
214
  try {
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const node_test_1 = __importDefault(require("node:test"));
7
7
  const strict_1 = __importDefault(require("node:assert/strict"));
8
8
  const node_http_1 = require("node:http");
9
- const lac_js_1 = require("../services/local-ai-core/src/cli/lac.js");
9
+ const lac_js_1 = require("../../services/local-ai-core/src/cli/lac.js");
10
10
  function createIo() {
11
11
  let stdout = '';
12
12
  let stderr = '';
@@ -516,7 +516,7 @@ function createIo() {
516
516
  id: 'job-1',
517
517
  workspaceId: '知识库',
518
518
  platform: 'lark',
519
- route: { type: 'channel.chat', channelId: 'chat-1', participantId: 'user-1', threadId: 'thread-1' },
519
+ route: { type: 'channel.chat', channelId: 'chat-1', participantId: 'user-1' },
520
520
  executionMode: 'same-thread',
521
521
  triggerType: 'cron',
522
522
  cronExpr: '*/2 * * * *',
@@ -531,7 +531,7 @@ function createIo() {
531
531
  id: 'job-2',
532
532
  workspaceId: '知识库',
533
533
  platform: 'lark',
534
- route: { type: 'channel.chat', channelId: 'chat-2', participantId: 'user-2', threadId: 'thread-2' },
534
+ route: { type: 'channel.chat', channelId: 'chat-2', participantId: 'user-2' },
535
535
  executionMode: 'side-thread',
536
536
  triggerType: 'cron',
537
537
  cronExpr: '0 9 * * *',
@@ -558,6 +558,9 @@ function createIo() {
558
558
  LOCAL_AI_CORE_BASE: `http://127.0.0.1:${address.port}/api/local/v1`,
559
559
  LOCAL_AI_WORKSPACE_ID: '知识库',
560
560
  LOCAL_AI_THREAD_ID: 'thread-1',
561
+ LOCAL_AI_PLATFORM: 'lark',
562
+ LOCAL_AI_CHAT_ID: 'chat-1',
563
+ LOCAL_AI_PLATFORM_USER_ID: 'user-1',
561
564
  }, io);
562
565
  server.close();
563
566
  strict_1.default.equal(exitCode, 0);