@kafca/agentdock 0.1.56 → 0.1.58

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 (200) hide show
  1. package/README.md +7 -0
  2. package/dist/renderer/assets/{Badge-DjcoXx2J.js → Badge-BfY1VaHS.js} +1 -1
  3. package/dist/renderer/assets/Card-DWA_Tww_.js +1 -0
  4. package/dist/renderer/assets/Config-nHj3g1U5.js +21 -0
  5. package/dist/renderer/assets/CronList-BwkA_I7v.js +1 -0
  6. package/dist/renderer/assets/Dashboard-D0_A7k02.js +41 -0
  7. package/dist/renderer/assets/{EmptyState-DIZLiccW.js → EmptyState-zatDjLj_.js} +1 -1
  8. package/dist/renderer/assets/HighlightedMarkdown-CsM5n70d.js +12 -0
  9. package/dist/renderer/assets/Input-R1XdFK1K.js +1 -0
  10. package/dist/renderer/assets/{KnowledgeDetail-CJ9IYOsa.js → KnowledgeDetail-Co5sxeGd.js} +2 -2
  11. package/dist/renderer/assets/{KnowledgeHome-BLGPwnBO.js → KnowledgeHome-DwziPw5m.js} +3 -3
  12. package/dist/renderer/assets/Logs-NwZUtR_G.js +6 -0
  13. package/dist/renderer/assets/{Modal-CQiUpfl4.js → Modal-CrcIwIFL.js} +3 -3
  14. package/dist/renderer/assets/MonitorList-CS1uLS5A.js +1 -0
  15. package/dist/renderer/assets/Page-Dp0obeSw.js +1 -0
  16. package/dist/renderer/assets/{Select-D6z65IT3.js → Select-6mVQaf84.js} +2 -2
  17. package/dist/renderer/assets/ThreadChat-BFzbn3L6.js +86 -0
  18. package/dist/renderer/assets/Workspace-DCmo8BMm.js +19 -0
  19. package/dist/renderer/assets/{arrow-left-CV5cL3f4.js → arrow-left-9rjg_Hp4.js} +1 -1
  20. package/dist/renderer/assets/{book-open-DThvB7pj.js → book-open-Bu8GCHnG.js} +1 -1
  21. package/dist/renderer/assets/channels-BxVbG-6j.js +6 -0
  22. package/dist/renderer/assets/chevron-down-BE8uQmWM.js +11 -0
  23. package/dist/renderer/assets/index-B7TeF6Es.js +41 -0
  24. package/dist/renderer/assets/index-BIKL3fTP.css +11 -0
  25. package/dist/renderer/assets/index-BkI9POxj.js +167 -0
  26. package/dist/renderer/assets/knowledge-Kl19SqGS.js +1 -0
  27. package/dist/renderer/assets/{pencil-Dv5WxhdC.js → pencil-CPB7SK1m.js} +1 -1
  28. package/dist/renderer/assets/{play-DnCnft83.js → play-DGAO4OCu.js} +1 -1
  29. package/dist/renderer/assets/{plus-DsxIagzS.js → plus-Bt26z-4q.js} +1 -1
  30. package/dist/renderer/assets/{save-CST01lIc.js → save-Une7GMxT.js} +1 -1
  31. package/dist/renderer/assets/{search-B_bqy6Wd.js → search-DE0agJjJ.js} +1 -1
  32. package/dist/renderer/assets/{shield-check-D6lG5Aoc.js → shield-check-De7xaXZh.js} +1 -1
  33. package/dist/renderer/assets/threads-BnsNL5ei.js +1 -0
  34. package/dist/renderer/assets/{trash-2-Cefngb6Q.js → trash-2-6iGF59Ac.js} +1 -1
  35. package/dist/renderer/index.html +2 -2
  36. package/dist-electron/packages/contracts/src/automation.js +2 -0
  37. package/dist-electron/packages/contracts/src/channels.js +2 -0
  38. package/dist-electron/packages/contracts/src/knowledge.js +2 -0
  39. package/dist-electron/packages/contracts/src/local-core.js +15 -0
  40. package/dist-electron/packages/contracts/src/runtime.js +2 -0
  41. package/dist-electron/packages/contracts/src/scheduler.js +2 -0
  42. package/dist-electron/packages/contracts/src/threads.js +2 -0
  43. package/dist-electron/packages/core-sdk/src/automation.js +32 -0
  44. package/dist-electron/packages/core-sdk/src/channels.js +121 -0
  45. package/dist-electron/packages/core-sdk/src/client.js +455 -0
  46. package/dist-electron/packages/core-sdk/src/index.js +43 -607
  47. package/dist-electron/packages/core-sdk/src/knowledge.js +85 -0
  48. package/dist-electron/packages/core-sdk/src/request.js +22 -0
  49. package/dist-electron/packages/core-sdk/src/runtime.js +214 -0
  50. package/dist-electron/packages/core-sdk/src/scheduler.js +32 -0
  51. package/dist-electron/packages/core-sdk/src/threads.js +67 -0
  52. package/dist-electron/packages/plugin-sdk/src/agents.js +2 -0
  53. package/dist-electron/packages/plugin-sdk/src/automation.js +2 -0
  54. package/dist-electron/packages/plugin-sdk/src/channels.js +2 -0
  55. package/dist-electron/packages/plugin-sdk/src/index.js +21 -0
  56. package/dist-electron/packages/plugin-sdk/src/knowledge.js +2 -0
  57. package/dist-electron/packages/plugin-sdk/src/runtime.js +2 -0
  58. package/dist-electron/packages/plugin-sdk/src/scheduler.js +2 -0
  59. package/dist-electron/packages/plugin-sdk/src/threads.js +2 -0
  60. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +5 -8
  61. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-content.js +2 -2
  62. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +3 -3
  63. package/dist-electron/services/local-ai-core/src/acp/store/agent-task-store.js +5 -5
  64. package/dist-electron/services/local-ai-core/src/acp/store/automation-monitor-store.js +12 -12
  65. package/dist-electron/services/local-ai-core/src/acp/store/local-core-acp-store.js +2 -2
  66. package/dist-electron/services/local-ai-core/src/acp/store/scheduler-store.js +11 -11
  67. package/dist-electron/services/local-ai-core/src/acp/store/security-store.js +3 -3
  68. package/dist-electron/services/local-ai-core/src/acp/store/thread-store.js +7 -7
  69. package/dist-electron/services/local-ai-core/src/acp/workspace-acp-permissions.js +2 -2
  70. package/dist-electron/services/local-ai-core/src/agents/claudecode/definition.js +3 -3
  71. package/dist-electron/services/local-ai-core/src/agents/codex/definition.js +3 -3
  72. package/dist-electron/services/local-ai-core/src/agents/localcore-acp/definition.js +2 -2
  73. package/dist-electron/services/local-ai-core/src/agents/localcore-acp/index.js +4 -4
  74. package/dist-electron/services/local-ai-core/src/agents/opencode/launch.js +2 -2
  75. package/dist-electron/services/local-ai-core/src/agents/pi/definition.js +2 -2
  76. package/dist-electron/services/local-ai-core/src/agents/pi/launch.js +3 -3
  77. package/dist-electron/services/local-ai-core/src/agents/pi/provider-materializer.js +1 -1
  78. package/dist-electron/services/local-ai-core/src/agents/shared/agent-plugin.js +4 -4
  79. package/dist-electron/services/local-ai-core/src/automation/automation-conversation-executor.js +6 -47
  80. package/dist-electron/services/local-ai-core/src/automation/automation-monitor-service.js +2 -5
  81. package/dist-electron/services/local-ai-core/src/channel/lark/cards.js +33 -18
  82. package/dist-electron/services/local-ai-core/src/channel/lark/config.js +2 -2
  83. package/dist-electron/services/local-ai-core/src/channel/lark/gateway-utils.js +91 -0
  84. package/dist-electron/services/local-ai-core/src/channel/lark/inbound-handler.js +180 -0
  85. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +58 -539
  86. package/dist-electron/services/local-ai-core/src/channel/lark/outbound-transport.js +112 -0
  87. package/dist-electron/services/local-ai-core/src/channel/lark/runtime-state.js +5 -20
  88. package/dist-electron/services/local-ai-core/src/channel/shared/base-channel-gateway.js +37 -0
  89. package/dist-electron/services/local-ai-core/src/channel/shared/bridge-event-helpers.js +23 -0
  90. package/dist-electron/services/local-ai-core/src/channel/shared/channel-keys.js +6 -0
  91. package/dist-electron/services/local-ai-core/src/channel/shared/inbound-authorization.js +54 -0
  92. package/dist-electron/services/local-ai-core/src/channel/weixin/config.js +2 -2
  93. package/dist-electron/services/local-ai-core/src/channel/weixin/inbound-media.js +21 -0
  94. package/dist-electron/services/local-ai-core/src/channel/weixin/inbound-poller.js +167 -0
  95. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +55 -512
  96. package/dist-electron/services/local-ai-core/src/channel/weixin/runtime-state.js +149 -0
  97. package/dist-electron/services/local-ai-core/src/channel/weixin/text-utils.js +92 -0
  98. package/dist-electron/services/local-ai-core/src/cli/lac.js +9 -11
  99. package/dist-electron/services/local-ai-core/src/cli/monitor-cli-parsers.js +2 -2
  100. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +28 -70
  101. package/dist-electron/services/local-ai-core/src/kernel/local-core-errors.js +8 -0
  102. package/dist-electron/services/local-ai-core/src/plugins/builtin/knowledge-ai-vector-plugin.js +2 -2
  103. package/dist-electron/services/local-ai-core/src/plugins/builtin/knowledge-noop-plugin.js +2 -2
  104. package/dist-electron/services/local-ai-core/src/router/workspace-bridge-event-stream.js +29 -0
  105. package/dist-electron/services/local-ai-core/src/router/workspace-route-config.js +5 -5
  106. package/dist-electron/services/local-ai-core/src/router/workspace-router.js +47 -60
  107. package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +5 -5
  108. package/dist-electron/services/local-ai-core/src/runtime/config-migration.js +6 -6
  109. package/dist-electron/services/local-ai-core/src/runtime/deployment-diagnostics.js +3 -3
  110. package/dist-electron/services/local-ai-core/src/runtime/external-service.js +3 -1
  111. package/dist-electron/services/local-ai-core/src/runtime/handlers/automation-handler.js +11 -2
  112. package/dist-electron/services/local-ai-core/src/runtime/handlers/channel-handler.js +12 -6
  113. package/dist-electron/services/local-ai-core/src/runtime/handlers/external-handler.js +10 -2
  114. package/dist-electron/services/local-ai-core/src/runtime/handlers/knowledge-handler.js +18 -6
  115. package/dist-electron/services/local-ai-core/src/runtime/handlers/provider-handler.js +7 -2
  116. package/dist-electron/services/local-ai-core/src/runtime/handlers/runtime-handler.js +28 -3
  117. package/dist-electron/services/local-ai-core/src/runtime/handlers/scheduler-handler.js +10 -2
  118. package/dist-electron/services/local-ai-core/src/runtime/handlers/security-handler.js +18 -5
  119. package/dist-electron/services/local-ai-core/src/runtime/handlers/task-handler.js +14 -2
  120. package/dist-electron/services/local-ai-core/src/runtime/handlers/thread-handler.js +18 -15
  121. package/dist-electron/services/local-ai-core/src/runtime/handlers/workspace-handler.js +13 -2
  122. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +35 -8
  123. package/dist-electron/services/local-ai-core/src/runtime/request-validation.js +79 -0
  124. package/dist-electron/services/local-ai-core/src/runtime/runtime-detection-service.js +9 -9
  125. package/dist-electron/services/local-ai-core/src/runtime/server-helpers.js +10 -1
  126. package/dist-electron/services/local-ai-core/src/runtime/server.js +2 -1
  127. package/dist-electron/services/local-ai-core/src/runtime/workspace-project-registry.js +76 -0
  128. package/dist-electron/services/local-ai-core/src/sandbox/opensandbox-client.js +3 -2
  129. package/dist-electron/services/local-ai-core/src/sandbox/sandbox-config.js +8 -8
  130. package/dist-electron/services/local-ai-core/src/scheduler/base-channel-schedule-adapter.js +77 -0
  131. package/dist-electron/services/local-ai-core/src/scheduler/lark-schedule-adapter.js +13 -79
  132. package/dist-electron/services/local-ai-core/src/scheduler/local-schedule-adapter.js +2 -10
  133. package/dist-electron/services/local-ai-core/src/scheduler/run-polling.js +19 -0
  134. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-conversation-executor.js +3 -37
  135. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-job-route.js +20 -0
  136. package/dist-electron/services/local-ai-core/src/scheduler/scheduler-service.js +15 -28
  137. package/dist-electron/services/local-ai-core/src/scheduler/thread-resolution.js +12 -0
  138. package/dist-electron/services/local-ai-core/src/scheduler/weixin-schedule-adapter.js +13 -79
  139. package/dist-electron/services/local-ai-core/src/thread/agent-message-policy.js +75 -0
  140. package/dist-electron/shared/desktop.js +1 -64
  141. package/dist-electron/src/components/chat/chat-controller-state.js +60 -0
  142. package/dist-electron/src/components/chat/chat-event-gate.js +146 -0
  143. package/dist-electron/src/components/chat/markdown-code-block.js +8 -0
  144. package/dist-electron/src/pages/Desktop/workspace-model.js +212 -0
  145. package/dist-electron/src/pages/Threads/thread-chat-model.js +50 -51
  146. package/dist-electron/tests/contracts/agent-message-policy.test.js +21 -0
  147. package/dist-electron/tests/contracts/architecture-docs.test.js +56 -33
  148. package/dist-electron/tests/contracts/chat-event-gate.test.js +114 -0
  149. package/dist-electron/tests/contracts/chat-markdown.test.js +16 -0
  150. package/dist-electron/tests/contracts/thread-task-state.test.js +32 -0
  151. package/dist-electron/tests/contracts/workspace-identity.test.js +23 -0
  152. package/dist-electron/tests/electron/core-client.test.js +284 -0
  153. package/dist-electron/tests/electron/plugin-kernel.test.js +54 -0
  154. package/dist-electron/tests/electron/renderer-packaging.test.js +9 -0
  155. package/dist-electron/tests/electron/workspace-task-store.test.js +31 -0
  156. package/dist-electron/tests/integration/channel-inbound-authorization.test.js +80 -0
  157. package/dist-electron/tests/integration/lark-gateway-utils.test.js +13 -0
  158. package/dist-electron/tests/integration/lark-gateway.test.js +4 -4
  159. package/dist-electron/tests/integration/local-core-refactor.test.js +15 -8
  160. package/dist-electron/tests/integration/weixin-runtime-state.test.js +32 -0
  161. package/dist-electron/tests/integration/weixin-text-utils.test.js +15 -0
  162. package/package.json +9 -4
  163. package/dist/renderer/assets/Chat-C5Bkm9-M.js +0 -2
  164. package/dist/renderer/assets/ChatMarkdown-uc36EzyD.js +0 -45
  165. package/dist/renderer/assets/Config-DHtQuuLy.js +0 -21
  166. package/dist/renderer/assets/CronList-BYUnhzSC.js +0 -1
  167. package/dist/renderer/assets/Dashboard-BPnGMDg3.js +0 -26
  168. package/dist/renderer/assets/Logs-Dyd2Uwlt.js +0 -1
  169. package/dist/renderer/assets/MonitorList-Byg3E3Gt.js +0 -1
  170. package/dist/renderer/assets/Page-7DxXvTMm.js +0 -1
  171. package/dist/renderer/assets/ProjectDetail-Dfr5s0Ja.js +0 -11
  172. package/dist/renderer/assets/ProjectList-CDDQjAQ_.js +0 -1
  173. package/dist/renderer/assets/SessionChat-s7CAaCPL.js +0 -1
  174. package/dist/renderer/assets/SessionList-D-OycKKS.js +0 -1
  175. package/dist/renderer/assets/ThreadChat-CLdFynB5.js +0 -28
  176. package/dist/renderer/assets/Workspace-xfT5z4j_.js +0 -19
  177. package/dist/renderer/assets/bot-CWQ5-ovl.js +0 -6
  178. package/dist/renderer/assets/check-DKM7zfWG.js +0 -6
  179. package/dist/renderer/assets/chevron-down-Dfw2KU87.js +0 -6
  180. package/dist/renderer/assets/circle-CoP4RA4s.js +0 -6
  181. package/dist/renderer/assets/desktop-CsDYrOOU.js +0 -1
  182. package/dist/renderer/assets/desktop-JQkhNOF4.js +0 -5
  183. package/dist/renderer/assets/funnel-DpVLlG3g.js +0 -6
  184. package/dist/renderer/assets/heart-BMD36ejP.js +0 -6
  185. package/dist/renderer/assets/index-COxSYBjE.js +0 -182
  186. package/dist/renderer/assets/index-CTGTI-cm.js +0 -41
  187. package/dist/renderer/assets/index-CjCuR6DI.css +0 -11
  188. package/dist/renderer/assets/projects-DgjHcNgt.js +0 -1
  189. package/dist/renderer/assets/server-ecH4GNwa.js +0 -11
  190. package/dist/renderer/assets/session-utils-Dd_WKMYW.js +0 -6
  191. package/dist/renderer/assets/sessions-BQ33Mv4k.js +0 -1
  192. package/dist/renderer/assets/user-D_8RSH-T.js +0 -6
  193. package/dist/renderer/assets/wifi-off-DB9D-pOu.js +0 -11
  194. package/dist-electron/services/local-ai-core/src/scheduler/lark-execution-policies.js +0 -13
  195. package/dist-electron/services/local-ai-core/src/scheduler/weixin-execution-policies.js +0 -13
  196. package/dist-electron/src/api/client.js +0 -73
  197. package/dist-electron/src/api/sessions.js +0 -21
  198. package/dist-electron/src/lib/session-utils.js +0 -51
  199. package/dist-electron/src/pages/Projects/project-provider-config.js +0 -75
  200. package/dist-electron/tests/electron/project-provider-config.test.js +0 -71
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_test_1 = __importDefault(require("node:test"));
7
+ const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const agent_message_policy_js_1 = require("../../services/local-ai-core/src/thread/agent-message-policy.js");
9
+ (0, node_test_1.default)('agent message policy is owned by Local Core and includes selected knowledge context', () => {
10
+ const message = (0, agent_message_policy_js_1.composeAgentMessage)('Summarize the design', [
11
+ { id: 'kb-1', name: 'Architecture notes' },
12
+ ]);
13
+ strict_1.default.match(message, /\[Scheduler Tools\]/);
14
+ strict_1.default.match(message, /\[Monitor Tools\]/);
15
+ strict_1.default.match(message, /\[Channel Tools\]/);
16
+ strict_1.default.match(message, /id: kb-1 \| name: Architecture notes/);
17
+ strict_1.default.match(message, /\[User Message\]\nSummarize the design\n\[\/User Message\]/);
18
+ });
19
+ (0, node_test_1.default)('agent message policy leaves slash commands untouched', () => {
20
+ strict_1.default.equal((0, agent_message_policy_js_1.composeAgentMessage)('/agent use pi'), '/agent use pi');
21
+ });
@@ -39,33 +39,6 @@ function readSourceFiles(scope) {
39
39
  content: (0, node_fs_1.readFileSync)(path, 'utf8'),
40
40
  }));
41
41
  }
42
- const rendererPackageSourceImportAllowlist = new Set([
43
- 'src/api/cron.ts',
44
- 'src/api/desktop.ts',
45
- 'src/api/knowledge.ts',
46
- 'src/api/monitors.ts',
47
- 'src/api/runtime-bootstrap.ts',
48
- 'src/app/runtime.ts',
49
- 'src/pages/Automation/MonitorList.tsx',
50
- 'src/pages/Cron/CronList.tsx',
51
- 'src/pages/Dashboard.tsx',
52
- 'src/pages/Knowledge/KnowledgeDetail.tsx',
53
- 'src/pages/Knowledge/KnowledgeHome.tsx',
54
- 'src/pages/System/Config.tsx',
55
- 'src/pages/Threads/thread-chat-action-types.ts',
56
- 'src/pages/Threads/thread-chat-model.ts',
57
- 'src/pages/Threads/thread-chat-page-state.ts',
58
- 'src/pages/Threads/thread-chat-permission.test.ts',
59
- 'src/pages/Threads/thread-chat-permission.ts',
60
- 'src/pages/Threads/useThreadChatActions.ts',
61
- 'src/pages/Threads/useThreadChatController.ts',
62
- 'src/pages/Threads/useThreadChatConversationState.ts',
63
- 'src/pages/Threads/useThreadChatSendingActions.ts',
64
- 'src/pages/Threads/useThreadChatSessionBrowser.ts',
65
- 'src/pages/Threads/ThreadChatComposer.tsx',
66
- 'src/pages/Threads/useThreadChatThreadActions.ts',
67
- 'src/types/window.d.ts',
68
- ]);
69
42
  (0, node_test_1.default)('state ownership docs identify Local AI Core owners for durable chat and channel state', () => {
70
43
  const content = (0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, 'docs', 'architecture', 'state-ownership.md'), 'utf8');
71
44
  for (const state of [
@@ -111,12 +84,13 @@ const rendererPackageSourceImportAllowlist = new Set([
111
84
  strict_1.default.match(permissionSource, /export type PendingPermissionRequest = ThreadPendingPermissionRequest;/, 'renderer pending permission state must alias the shared thread permission contract');
112
85
  strict_1.default.doesNotMatch(permissionSource, /type PermissionTaskState\s*=/, 'permission helpers must not carry a second task-state union');
113
86
  });
114
- (0, node_test_1.default)('renderer does not add new direct imports of package source internals', () => {
115
- const offenders = readSourceFiles('src')
116
- .filter(({ content }) => /from ['"][^'"]*packages\/(?:contracts|core-sdk|plugin-sdk)\/src/.test(content))
117
- .map(({ relativePath }) => relativePath)
118
- .filter((relativePath) => !rendererPackageSourceImportAllowlist.has(relativePath));
119
- strict_1.default.deepEqual(offenders, [], 'renderer code should import package APIs through stable public entrypoints; add migrations instead of new packages/*/src imports');
87
+ (0, node_test_1.default)('production code imports workspace packages through public package names', () => {
88
+ const offenders = ['src', 'services', 'packages']
89
+ .flatMap(readSourceFiles)
90
+ .filter(({ relativePath }) => !relativePath.startsWith('packages/contracts/src/'))
91
+ .filter(({ content }) => /(?:packages\/(?:contracts|core-sdk|plugin-sdk|knowledge-api)|(?:contracts|core-sdk|plugin-sdk))\/src/.test(content))
92
+ .map(({ relativePath }) => relativePath);
93
+ strict_1.default.deepEqual(offenders, [], 'production code must import workspace dependencies through @cc/* public entrypoints');
120
94
  });
121
95
  (0, node_test_1.default)('renderer does not import Electron or Local AI Core internals', () => {
122
96
  const offenders = readSourceFiles('src')
@@ -137,4 +111,53 @@ const rendererPackageSourceImportAllowlist = new Set([
137
111
  const packageJson = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, packagePath), 'utf8'));
138
112
  strict_1.default.equal(packageJson.exports?.['.'], './src/index.ts', `${packagePath} must expose its public source entrypoint for workspace consumers`);
139
113
  }
114
+ const contracts = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, 'packages/contracts/package.json'), 'utf8'));
115
+ const domains = ['runtime', 'threads', 'channels', 'scheduler', 'automation', 'knowledge'];
116
+ for (const domain of domains) {
117
+ strict_1.default.ok(contracts.exports?.[`./${domain}`], `contracts must expose the ${domain} domain entrypoint`);
118
+ }
119
+ for (const packageName of ['contracts', 'core-sdk', 'plugin-sdk']) {
120
+ const packageJson = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, `packages/${packageName}/package.json`), 'utf8'));
121
+ for (const domain of domains) {
122
+ strict_1.default.equal(packageJson.exports?.[`./${domain}`], `./src/${domain}.ts`, `${packageName} must expose the ${domain} domain through its dedicated entrypoint`);
123
+ }
124
+ }
125
+ });
126
+ (0, node_test_1.default)('core sdk domain entrypoints do not re-export the top-level sdk', () => {
127
+ for (const domain of ['runtime', 'threads', 'channels', 'scheduler', 'automation', 'knowledge']) {
128
+ const source = (0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, `packages/core-sdk/src/${domain}.ts`), 'utf8');
129
+ strict_1.default.doesNotMatch(source, /(?:from|import\s*\()\s*['"]\.\/index\.js['"]/, `core-sdk/${domain} must own its implementation without depending on the aggregate entrypoint`);
130
+ strict_1.default.match(source, /coreRequest|coreClient/, `core-sdk/${domain} must own executable domain behavior`);
131
+ }
132
+ });
133
+ (0, node_test_1.default)('package domain modules do not depend on aggregate entrypoints', () => {
134
+ for (const packageName of ['contracts', 'core-sdk', 'plugin-sdk']) {
135
+ for (const domain of ['runtime', 'threads', 'channels', 'scheduler', 'automation', 'knowledge']) {
136
+ const source = (0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, `packages/${packageName}/src/${domain}.ts`), 'utf8');
137
+ strict_1.default.doesNotMatch(source, /(?:from|import\s*\()\s*['"]\.\/index(?:\.js)?['"]/, `${packageName}/${domain} must not reach back through its aggregate entrypoint`);
138
+ }
139
+ }
140
+ });
141
+ (0, node_test_1.default)('desktop contracts are consumed through the contracts package outside their owner', () => {
142
+ const offenders = ['src', 'services', 'packages']
143
+ .flatMap(readSourceFiles)
144
+ .filter(({ relativePath }) => !relativePath.startsWith('packages/contracts/src/'))
145
+ .filter(({ content }) => /shared\/desktop/.test(content))
146
+ .map(({ relativePath }) => relativePath);
147
+ strict_1.default.deepEqual(offenders, [], 'business code must consume shared desktop contracts through @cc/superai-contracts');
148
+ });
149
+ (0, node_test_1.default)('renderer does not branch features through the retired runtime provider string', () => {
150
+ const offenders = readSourceFiles('src')
151
+ .filter(({ content }) => /RuntimeProvider|getRuntimeProvider|runtimeProvider\s*(?:===|!==)/.test(content))
152
+ .map(({ relativePath }) => relativePath);
153
+ strict_1.default.deepEqual(offenders, [], 'renderer features must use capabilities or the Local Core contract directly');
154
+ });
155
+ (0, node_test_1.default)('chat syntax highlighting stays behind a lazy code-block boundary', () => {
156
+ const markdown = (0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, 'src/components/chat/ChatMarkdown.tsx'), 'utf8');
157
+ const highlighted = (0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, 'src/components/chat/HighlightedMarkdown.tsx'), 'utf8');
158
+ strict_1.default.doesNotMatch(markdown, /from ['"]rehype-highlight['"]/, 'base chat markdown must not eagerly load highlighting');
159
+ strict_1.default.match(markdown, /lazy\(\(\) =>\s*import\('\.\/HighlightedMarkdown'\)/, 'highlighting must use a lazy chunk');
160
+ for (const language of ['typescript', 'javascript', 'json', 'bash', 'python', 'xml', 'css', 'sql', 'yaml']) {
161
+ strict_1.default.match(highlighted, new RegExp(`highlight\\.js/lib/languages/${language}`), `${language} must remain in the supported subset`);
162
+ }
140
163
  });
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_test_1 = __importDefault(require("node:test"));
8
+ const chat_event_gate_js_1 = require("../../src/components/chat/chat-event-gate.js");
9
+ (0, node_test_1.default)('chat event gate rejects duplicate bridge events', () => {
10
+ const gate = (0, chat_event_gate_js_1.createChatEventGate)();
11
+ const event = {
12
+ type: 'update_message',
13
+ sessionKey: 'workspace:thread-1',
14
+ replyCtx: 'run-1',
15
+ previewHandle: 'preview-1',
16
+ content: 'working',
17
+ };
18
+ strict_1.default.equal(gate.acceptBridgeEvent(event, { activeRunId: 'run-1' }), true);
19
+ strict_1.default.equal(gate.acceptBridgeEvent(event, { activeRunId: 'run-1' }), false);
20
+ });
21
+ (0, node_test_1.default)('chat event gate rejects late streaming updates after a turn settles', () => {
22
+ const gate = (0, chat_event_gate_js_1.createChatEventGate)();
23
+ const context = { activeRunId: 'run-1' };
24
+ strict_1.default.equal(gate.acceptBridgeEvent({
25
+ type: 'typing_stop',
26
+ sessionKey: 'workspace:thread-1',
27
+ replyCtx: 'run-1',
28
+ }, context), true);
29
+ strict_1.default.equal(gate.acceptBridgeEvent({
30
+ type: 'update_message',
31
+ sessionKey: 'workspace:thread-1',
32
+ replyCtx: 'run-1',
33
+ content: 'late preview',
34
+ }, context), false);
35
+ });
36
+ (0, node_test_1.default)('chat event gate rejects events from a superseded run', () => {
37
+ const gate = (0, chat_event_gate_js_1.createChatEventGate)();
38
+ strict_1.default.equal(gate.acceptBridgeEvent({
39
+ type: 'reply',
40
+ sessionKey: 'workspace:thread-1',
41
+ replyCtx: 'run-old',
42
+ content: 'late answer',
43
+ }, {
44
+ activeRunId: 'run-old',
45
+ pendingTurn: {
46
+ sessionKey: 'workspace:thread-1',
47
+ supersededRunId: 'run-old',
48
+ },
49
+ }), false);
50
+ });
51
+ (0, node_test_1.default)('chat event gate deduplicates core refresh events while allowing changed payloads', () => {
52
+ const gate = (0, chat_event_gate_js_1.createChatEventGate)();
53
+ const first = {
54
+ type: 'message.updated',
55
+ threadId: 'thread-1',
56
+ message: { id: 'message-1', content: 'one' },
57
+ };
58
+ const second = {
59
+ ...first,
60
+ message: { id: 'message-1', content: 'two' },
61
+ };
62
+ strict_1.default.equal(gate.acceptCoreEvent(first), true);
63
+ strict_1.default.equal(gate.acceptCoreEvent(first), false);
64
+ strict_1.default.equal(gate.acceptCoreEvent(second), true);
65
+ });
66
+ (0, node_test_1.default)('chat event gate rejects late Core stream updates after the run settles', () => {
67
+ const gate = (0, chat_event_gate_js_1.createChatEventGate)();
68
+ const terminal = {
69
+ type: 'stream.updated',
70
+ stream: {
71
+ type: 'typing_stop',
72
+ sessionKey: 'workspace:thread-1',
73
+ replyCtx: 'run-1',
74
+ },
75
+ };
76
+ const lateUpdate = {
77
+ type: 'presence.updated',
78
+ live: true,
79
+ stream: {
80
+ type: 'update_message',
81
+ sessionKey: 'workspace:thread-1',
82
+ replyCtx: 'run-1',
83
+ content: 'late preview',
84
+ },
85
+ };
86
+ strict_1.default.equal(gate.acceptCoreEvent(terminal), true);
87
+ strict_1.default.equal(gate.acceptCoreEvent(lateUpdate), false);
88
+ strict_1.default.equal(gate.acceptCoreEvent({
89
+ type: 'stream.updated',
90
+ stream: {
91
+ type: 'reply',
92
+ sessionKey: 'workspace:thread-1',
93
+ replyCtx: 'run-1',
94
+ content: 'late final',
95
+ },
96
+ }), false);
97
+ });
98
+ (0, node_test_1.default)('chat event gate rejects late running state after a Core run completes', () => {
99
+ const gate = (0, chat_event_gate_js_1.createChatEventGate)();
100
+ const run = {
101
+ id: 'run-1',
102
+ threadId: 'thread-1',
103
+ startedAt: '',
104
+ updatedAt: '',
105
+ };
106
+ strict_1.default.equal(gate.acceptCoreEvent({
107
+ type: 'run.updated',
108
+ run: { ...run, status: 'completed' },
109
+ }), true);
110
+ strict_1.default.equal(gate.acceptCoreEvent({
111
+ type: 'run.updated',
112
+ run: { ...run, status: 'running' },
113
+ }), false);
114
+ });
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_test_1 = __importDefault(require("node:test"));
8
+ const markdown_code_block_js_1 = require("../../src/components/chat/markdown-code-block.js");
9
+ (0, node_test_1.default)('markdown code block detection covers fenced and indented code blocks', () => {
10
+ strict_1.default.equal((0, markdown_code_block_js_1.hasMarkdownCodeBlock)('```ts\nconst value = 1;\n```'), true);
11
+ strict_1.default.equal((0, markdown_code_block_js_1.hasMarkdownCodeBlock)('~~~python\nprint("hi")\n~~~'), true);
12
+ strict_1.default.equal((0, markdown_code_block_js_1.hasMarkdownCodeBlock)(' ````ts\nconst value = 1;\n`````'), true);
13
+ strict_1.default.equal((0, markdown_code_block_js_1.hasMarkdownCodeBlock)('```ts\nconst unfinished = true;'), true);
14
+ strict_1.default.equal((0, markdown_code_block_js_1.hasMarkdownCodeBlock)('Paragraph\n\n pnpm test'), true);
15
+ strict_1.default.equal((0, markdown_code_block_js_1.hasMarkdownCodeBlock)('Use `inline code` only'), false);
16
+ });
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_test_1 = __importDefault(require("node:test"));
8
+ const thread_chat_model_js_1 = require("../../src/pages/Threads/thread-chat-model.js");
9
+ (0, node_test_1.default)('chatControllerActionForTaskState maps each chat task state to a named controller action', () => {
10
+ strict_1.default.deepEqual((0, thread_chat_model_js_1.chatControllerActionForTaskState)('idle'), { type: 'settled' });
11
+ strict_1.default.deepEqual((0, thread_chat_model_js_1.chatControllerActionForTaskState)('running'), { type: 'stream_started' });
12
+ strict_1.default.deepEqual((0, thread_chat_model_js_1.chatControllerActionForTaskState)('awaiting_input'), { type: 'input_requested' });
13
+ strict_1.default.deepEqual((0, thread_chat_model_js_1.chatControllerActionForTaskState)('awaiting_permission'), { type: 'permission_requested' });
14
+ strict_1.default.deepEqual((0, thread_chat_model_js_1.chatControllerActionForTaskState)('permission_submitted'), { type: 'permission_submitted' });
15
+ strict_1.default.deepEqual((0, thread_chat_model_js_1.chatControllerActionForTaskState)('stopping'), { type: 'stop_started' });
16
+ strict_1.default.deepEqual((0, thread_chat_model_js_1.chatControllerActionForTaskState)('error'), { type: 'failed' });
17
+ });
18
+ (0, node_test_1.default)('taskStateFromControllerStatus narrows the controller status without a cast', () => {
19
+ strict_1.default.equal((0, thread_chat_model_js_1.taskStateFromControllerStatus)('idle'), 'idle');
20
+ strict_1.default.equal((0, thread_chat_model_js_1.taskStateFromControllerStatus)('running'), 'running');
21
+ strict_1.default.equal((0, thread_chat_model_js_1.taskStateFromControllerStatus)('awaiting_input'), 'awaiting_input');
22
+ strict_1.default.equal((0, thread_chat_model_js_1.taskStateFromControllerStatus)('error'), 'error');
23
+ strict_1.default.equal((0, thread_chat_model_js_1.taskStateFromControllerStatus)('stopping'), 'stopping');
24
+ // Terminal controller statuses Threads never dispatches map back to 'error'.
25
+ strict_1.default.equal((0, thread_chat_model_js_1.taskStateFromControllerStatus)('failed'), 'error');
26
+ strict_1.default.equal((0, thread_chat_model_js_1.taskStateFromControllerStatus)('timed_out'), 'error');
27
+ // Session/Web-only statuses map defensively to 'running'.
28
+ strict_1.default.equal((0, thread_chat_model_js_1.taskStateFromControllerStatus)('sending'), 'running');
29
+ strict_1.default.equal((0, thread_chat_model_js_1.taskStateFromControllerStatus)('waiting'), 'running');
30
+ strict_1.default.equal((0, thread_chat_model_js_1.taskStateFromControllerStatus)('polling'), 'running');
31
+ strict_1.default.equal((0, thread_chat_model_js_1.taskStateFromControllerStatus)('activating'), 'running');
32
+ });
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_test_1 = __importDefault(require("node:test"));
7
+ const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const workspace_model_js_1 = require("../../src/pages/Desktop/workspace-model.js");
9
+ (0, node_test_1.default)('desktop workspace operations keep using stable identity after a display-name change', () => {
10
+ strict_1.default.equal((0, workspace_model_js_1.desktopProjectWorkspaceId)({
11
+ workspace_id: 'workspace-stable',
12
+ name: 'Renamed workspace',
13
+ agent: { type: 'pi' },
14
+ platforms: [],
15
+ }), 'workspace-stable');
16
+ });
17
+ (0, node_test_1.default)('desktop workspace identity falls back to legacy project names', () => {
18
+ strict_1.default.equal((0, workspace_model_js_1.desktopProjectWorkspaceId)({
19
+ name: 'Legacy workspace',
20
+ agent: { type: 'pi' },
21
+ platforms: [],
22
+ }), 'Legacy workspace');
23
+ });
@@ -0,0 +1,284 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_test_1 = __importDefault(require("node:test"));
8
+ const client_js_1 = require("../../packages/core-sdk/src/client.js");
9
+ class FakeEventSource {
10
+ listeners = new Map();
11
+ onerror = null;
12
+ onopen = null;
13
+ closed = false;
14
+ addEventListener(type, listener) {
15
+ const listeners = this.listeners.get(type) || new Set();
16
+ listeners.add(listener);
17
+ this.listeners.set(type, listeners);
18
+ }
19
+ emit(type, payload) {
20
+ for (const listener of this.listeners.get(type) || []) {
21
+ listener({ data: typeof payload === 'string' ? payload : JSON.stringify(payload) });
22
+ }
23
+ }
24
+ close() {
25
+ this.closed = true;
26
+ }
27
+ }
28
+ (0, node_test_1.default)('core client shares one event source across subscribers and closes it after the last unsubscribe', () => {
29
+ const sources = [];
30
+ const client = (0, client_js_1.createCoreClient)({
31
+ baseUrl: 'http://127.0.0.1:9831/api/local/v1/',
32
+ eventSourceFactory: () => {
33
+ const source = new FakeEventSource();
34
+ sources.push(source);
35
+ return source;
36
+ },
37
+ });
38
+ const first = client.events.subscribe(() => { });
39
+ const second = client.events.subscribe(() => { });
40
+ strict_1.default.equal(sources.length, 1);
41
+ strict_1.default.equal(client.baseUrl, 'http://127.0.0.1:9831/api/local/v1');
42
+ first();
43
+ strict_1.default.equal(sources[0].closed, false);
44
+ second();
45
+ strict_1.default.equal(sources[0].closed, true);
46
+ });
47
+ (0, node_test_1.default)('core clients with the same base URL share one event source across instances', () => {
48
+ const sources = [];
49
+ const firstClient = (0, client_js_1.createCoreClient)({
50
+ baseUrl: 'http://127.0.0.1:9831/api/local/v1/',
51
+ eventSourceFactory: () => {
52
+ const source = new FakeEventSource();
53
+ sources.push(source);
54
+ return source;
55
+ },
56
+ });
57
+ const secondClient = (0, client_js_1.createCoreClient)({
58
+ baseUrl: 'http://127.0.0.1:9831/api/local/v1',
59
+ eventSourceFactory: () => {
60
+ const source = new FakeEventSource();
61
+ sources.push(source);
62
+ return source;
63
+ },
64
+ });
65
+ const firstEventTypes = [];
66
+ const secondEventTypes = [];
67
+ const stopFirst = firstClient.events.subscribe((event) => firstEventTypes.push(event.type));
68
+ const stopSecond = secondClient.events.subscribe((event) => secondEventTypes.push(event.type));
69
+ strict_1.default.equal(sources.length, 1);
70
+ sources[0].emit('presence.updated', { type: 'presence.updated', live: true });
71
+ strict_1.default.deepEqual(firstEventTypes, ['presence.updated']);
72
+ strict_1.default.deepEqual(secondEventTypes, ['presence.updated']);
73
+ stopFirst();
74
+ strict_1.default.equal(sources[0].closed, false);
75
+ stopSecond();
76
+ strict_1.default.equal(sources[0].closed, true);
77
+ });
78
+ (0, node_test_1.default)('core client ignores malformed events and maps stream events to bridge subscribers', () => {
79
+ const source = new FakeEventSource();
80
+ const events = [];
81
+ const bridgeContents = [];
82
+ const client = (0, client_js_1.createCoreClient)({
83
+ baseUrl: 'http://127.0.0.1:9831/api/local/v1',
84
+ eventSourceFactory: () => source,
85
+ });
86
+ const stopEvents = client.events.subscribe((event) => events.push(event.type));
87
+ const stopBridge = client.events.subscribeBridge((event) => bridgeContents.push(event.content || ''));
88
+ source.emit('stream.updated', '{not-json');
89
+ source.emit('stream.updated', {
90
+ type: 'stream.updated',
91
+ stream: { type: 'reply', sessionKey: 'workspace:thread', content: 'done' },
92
+ });
93
+ strict_1.default.deepEqual(events, ['stream.updated']);
94
+ strict_1.default.deepEqual(bridgeContents, ['done']);
95
+ stopBridge();
96
+ stopEvents();
97
+ });
98
+ (0, node_test_1.default)('core client ignores valid JSON events that do not match the event contract', () => {
99
+ const source = new FakeEventSource();
100
+ const events = [];
101
+ const client = (0, client_js_1.createCoreClient)({
102
+ baseUrl: 'http://127.0.0.1:9841/api/local/v1',
103
+ eventSourceFactory: () => source,
104
+ });
105
+ const stop = client.events.subscribe((event) => events.push(event.type));
106
+ source.emit('runtime.updated', { type: 'runtime.updated' });
107
+ source.emit('presence.updated', { type: 'presence.updated', live: 'yes' });
108
+ source.emit('stream.updated', { type: 'stream.updated', stream: { content: 'missing bridge type' } });
109
+ source.emit('presence.updated', { type: 'presence.updated', live: false });
110
+ strict_1.default.deepEqual(events, ['presence.updated']);
111
+ stop();
112
+ });
113
+ (0, node_test_1.default)('core client rejects events whose nested payload misses required contract fields', () => {
114
+ const source = new FakeEventSource();
115
+ const events = [];
116
+ const client = (0, client_js_1.createCoreClient)({
117
+ baseUrl: 'http://127.0.0.1:9831/api/local/v1',
118
+ eventSourceFactory: () => source,
119
+ });
120
+ const stop = client.events.subscribe((event) => events.push(event.type));
121
+ source.emit('run.updated', { type: 'run.updated', run: {} });
122
+ source.emit('message.created', { type: 'message.created', threadId: 'thread-1', message: {} });
123
+ source.emit('scheduler.run.updated', { type: 'scheduler.run.updated', run: { id: 'run-1' } });
124
+ source.emit('run.updated', {
125
+ type: 'run.updated',
126
+ run: {
127
+ id: 'run-1',
128
+ threadId: 'thread-1',
129
+ status: 'running',
130
+ startedAt: '2026-06-21T00:00:00.000Z',
131
+ updatedAt: '2026-06-21T00:00:01.000Z',
132
+ },
133
+ });
134
+ strict_1.default.deepEqual(events, ['run.updated']);
135
+ stop();
136
+ });
137
+ (0, node_test_1.default)('identical listener functions retain independent subscriptions', () => {
138
+ const source = new FakeEventSource();
139
+ let calls = 0;
140
+ const listener = () => {
141
+ calls += 1;
142
+ };
143
+ const client = (0, client_js_1.createCoreClient)({
144
+ baseUrl: 'http://127.0.0.1:9831/api/local/v1',
145
+ eventSourceFactory: () => source,
146
+ });
147
+ const stopFirst = client.events.subscribe(listener);
148
+ const stopSecond = client.events.subscribe(listener);
149
+ stopFirst();
150
+ strict_1.default.equal(source.closed, false);
151
+ source.emit('presence.updated', { type: 'presence.updated', live: true });
152
+ strict_1.default.equal(calls, 1);
153
+ stopSecond();
154
+ strict_1.default.equal(source.closed, true);
155
+ });
156
+ (0, node_test_1.default)('one failing event listener does not prevent other subscribers from receiving the event', () => {
157
+ const source = new FakeEventSource();
158
+ const events = [];
159
+ const client = (0, client_js_1.createCoreClient)({
160
+ baseUrl: 'http://127.0.0.1:9844/api/local/v1',
161
+ eventSourceFactory: () => source,
162
+ });
163
+ const stopFailing = client.events.subscribe(() => {
164
+ throw new Error('listener failed');
165
+ });
166
+ const stopHealthy = client.events.subscribe((event) => events.push(event.type));
167
+ source.emit('presence.updated', { type: 'presence.updated', live: true });
168
+ strict_1.default.deepEqual(events, ['presence.updated']);
169
+ stopFailing();
170
+ stopHealthy();
171
+ });
172
+ (0, node_test_1.default)('core client schedules only one reconnect while an error is outstanding', () => {
173
+ const sources = [];
174
+ const reconnects = [];
175
+ const client = (0, client_js_1.createCoreClient)({
176
+ baseUrl: 'http://127.0.0.1:9831/api/local/v1',
177
+ eventSourceFactory: () => {
178
+ const source = new FakeEventSource();
179
+ sources.push(source);
180
+ return source;
181
+ },
182
+ scheduleReconnect: (callback) => {
183
+ reconnects.push(callback);
184
+ return reconnects.length;
185
+ },
186
+ cancelReconnect: () => { },
187
+ });
188
+ const stop = client.events.subscribe(() => { });
189
+ sources[0].onerror?.();
190
+ sources[0].onerror?.();
191
+ strict_1.default.equal(reconnects.length, 1);
192
+ reconnects[0]();
193
+ strict_1.default.equal(sources.length, 2);
194
+ stop();
195
+ });
196
+ (0, node_test_1.default)('an obsolete event source cannot schedule another reconnect after replacement', () => {
197
+ const sources = [];
198
+ const reconnects = [];
199
+ const client = (0, client_js_1.createCoreClient)({
200
+ baseUrl: 'http://127.0.0.1:9842/api/local/v1',
201
+ eventSourceFactory: () => {
202
+ const source = new FakeEventSource();
203
+ sources.push(source);
204
+ return source;
205
+ },
206
+ scheduleReconnect: (callback) => {
207
+ reconnects.push(callback);
208
+ return reconnects.length;
209
+ },
210
+ cancelReconnect: () => { },
211
+ });
212
+ const stop = client.events.subscribe(() => { });
213
+ sources[0].onerror?.();
214
+ reconnects[0]();
215
+ sources[0].onerror?.();
216
+ strict_1.default.equal(reconnects.length, 1);
217
+ stop();
218
+ });
219
+ (0, node_test_1.default)('core client reports reconnect so consumers can recover missed state', () => {
220
+ const sources = [];
221
+ const reconnects = [];
222
+ const states = [];
223
+ const client = (0, client_js_1.createCoreClient)({
224
+ baseUrl: 'http://127.0.0.1:9845/api/local/v1',
225
+ eventSourceFactory: () => {
226
+ const source = new FakeEventSource();
227
+ sources.push(source);
228
+ return source;
229
+ },
230
+ scheduleReconnect: (callback) => {
231
+ reconnects.push(callback);
232
+ return reconnects.length;
233
+ },
234
+ cancelReconnect: () => { },
235
+ });
236
+ const stop = client.events.subscribeConnectionState((state) => states.push(state));
237
+ sources[0].onopen?.();
238
+ sources[0].onerror?.();
239
+ reconnects[0]();
240
+ sources[1].onopen?.();
241
+ strict_1.default.deepEqual(states, ['connected', 'disconnected', 'connected']);
242
+ stop();
243
+ strict_1.default.equal(sources[1].closed, true);
244
+ });
245
+ (0, node_test_1.default)('core client rejects partially populated scheduler and automation payloads', () => {
246
+ const source = new FakeEventSource();
247
+ const events = [];
248
+ const client = (0, client_js_1.createCoreClient)({
249
+ baseUrl: 'http://127.0.0.1:9843/api/local/v1',
250
+ eventSourceFactory: () => source,
251
+ });
252
+ const stop = client.events.subscribe((event) => events.push(event.type));
253
+ source.emit('scheduler.job.updated', {
254
+ type: 'scheduler.job.updated',
255
+ job: {
256
+ id: 'job-1',
257
+ workspaceId: 'workspace-1',
258
+ platform: 'local',
259
+ triggerType: 'cron',
260
+ promptTemplate: 'run',
261
+ enabled: true,
262
+ createdAt: '',
263
+ updatedAt: '',
264
+ },
265
+ });
266
+ source.emit('automation.monitor.updated', {
267
+ type: 'automation.monitor.updated',
268
+ monitor: {
269
+ id: 'monitor-1',
270
+ workspaceId: 'workspace-1',
271
+ title: 'price',
272
+ sourceType: 'stock.quote',
273
+ sourceConfig: {},
274
+ condition: {},
275
+ promptTemplate: 'analyze',
276
+ enabled: true,
277
+ cooldownMs: 1000,
278
+ createdAt: '',
279
+ updatedAt: '',
280
+ },
281
+ });
282
+ strict_1.default.deepEqual(events, []);
283
+ stop();
284
+ });