@kafca/agentdock 0.1.55 → 0.1.57

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 (185) hide show
  1. package/README.md +14 -0
  2. package/dist/renderer/assets/{Badge-Bg_nNvBL.js → Badge-CDTbmw1h.js} +1 -1
  3. package/dist/renderer/assets/Card-DayUo35w.js +1 -0
  4. package/dist/renderer/assets/Config-ByGMQuzR.js +21 -0
  5. package/dist/renderer/assets/CronList-XqHdVlgu.js +1 -0
  6. package/dist/renderer/assets/Dashboard-CZj-oANw.js +41 -0
  7. package/dist/renderer/assets/{EmptyState-C60g3RMj.js → EmptyState-EFMkOjZO.js} +1 -1
  8. package/dist/renderer/assets/HighlightedMarkdown-DyvF3DHK.js +12 -0
  9. package/dist/renderer/assets/Input-DxoWVc9d.js +1 -0
  10. package/dist/renderer/assets/{KnowledgeDetail-CC5C7JYe.js → KnowledgeDetail-CbUb2tG7.js} +2 -2
  11. package/dist/renderer/assets/{KnowledgeHome-3rlvxgWf.js → KnowledgeHome-BWV1gz4f.js} +3 -3
  12. package/dist/renderer/assets/Logs-DqnanQTc.js +6 -0
  13. package/dist/renderer/assets/{Modal-BQUW9LSf.js → Modal-yY7TzR0x.js} +3 -3
  14. package/dist/renderer/assets/MonitorList-CIjGQkFs.js +1 -0
  15. package/dist/renderer/assets/Page-vwHNUdoj.js +1 -0
  16. package/dist/renderer/assets/{Select-CfRkos3z.js → Select-CmbK36pN.js} +2 -2
  17. package/dist/renderer/assets/ThreadChat-4OceMgM4.js +86 -0
  18. package/dist/renderer/assets/Workspace-DplPJOjJ.js +19 -0
  19. package/dist/renderer/assets/{arrow-left-D1DbXrLA.js → arrow-left-C8HfBt2K.js} +1 -1
  20. package/dist/renderer/assets/{book-open-BxC1z1C1.js → book-open-BCdZWncQ.js} +1 -1
  21. package/dist/renderer/assets/channels-hgO3cAIx.js +6 -0
  22. package/dist/renderer/assets/chevron-down-BPk77tqY.js +11 -0
  23. package/dist/renderer/assets/index-BIKL3fTP.css +11 -0
  24. package/dist/renderer/assets/index-BlA4xPdd.js +41 -0
  25. package/dist/renderer/assets/index-wYpjqVqC.js +167 -0
  26. package/dist/renderer/assets/knowledge-7pl06KuH.js +1 -0
  27. package/dist/renderer/assets/{pencil-X44UaxwR.js → pencil-CB1SWZ9A.js} +1 -1
  28. package/dist/renderer/assets/{play-ujo-De38.js → play-BvsoXUrM.js} +1 -1
  29. package/dist/renderer/assets/{plus-BHgVdG_A.js → plus-CokBDnaL.js} +1 -1
  30. package/dist/renderer/assets/{save-Cz6giKrE.js → save-D-w4kATO.js} +1 -1
  31. package/dist/renderer/assets/{search-DKWvTqi5.js → search-Cnsn6i4Q.js} +1 -1
  32. package/dist/renderer/assets/{shield-check-V82O8AC1.js → shield-check-Cjja2Gh6.js} +1 -1
  33. package/dist/renderer/assets/threads-BCm6j4eI.js +1 -0
  34. package/dist/renderer/assets/{trash-2-_Fgwk8mc.js → trash-2-CIxz9qYY.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 +6 -9
  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/channel/lark/cards.js +33 -18
  80. package/dist-electron/services/local-ai-core/src/channel/lark/config.js +2 -2
  81. package/dist-electron/services/local-ai-core/src/channel/lark/gateway-utils.js +91 -0
  82. package/dist-electron/services/local-ai-core/src/channel/lark/inbound-handler.js +180 -0
  83. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +52 -456
  84. package/dist-electron/services/local-ai-core/src/channel/lark/outbound-transport.js +124 -0
  85. package/dist-electron/services/local-ai-core/src/channel/shared/inbound-attachment-store.js +93 -0
  86. package/dist-electron/services/local-ai-core/src/channel/shared/inbound-authorization.js +54 -0
  87. package/dist-electron/services/local-ai-core/src/channel/weixin/config.js +2 -2
  88. package/dist-electron/services/local-ai-core/src/channel/weixin/inbound-media.js +21 -0
  89. package/dist-electron/services/local-ai-core/src/channel/weixin/inbound-poller.js +167 -0
  90. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +98 -483
  91. package/dist-electron/services/local-ai-core/src/channel/weixin/runtime-state.js +156 -0
  92. package/dist-electron/services/local-ai-core/src/channel/weixin/text-utils.js +100 -0
  93. package/dist-electron/services/local-ai-core/src/cli/lac.js +6 -6
  94. package/dist-electron/services/local-ai-core/src/cli/monitor-cli-parsers.js +2 -2
  95. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +28 -70
  96. package/dist-electron/services/local-ai-core/src/kernel/local-core-errors.js +33 -5
  97. package/dist-electron/services/local-ai-core/src/plugins/builtin/knowledge-ai-vector-plugin.js +2 -2
  98. package/dist-electron/services/local-ai-core/src/plugins/builtin/knowledge-noop-plugin.js +2 -2
  99. package/dist-electron/services/local-ai-core/src/router/workspace-bridge-event-stream.js +29 -0
  100. package/dist-electron/services/local-ai-core/src/router/workspace-route-config.js +5 -5
  101. package/dist-electron/services/local-ai-core/src/router/workspace-router.js +47 -60
  102. package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +5 -5
  103. package/dist-electron/services/local-ai-core/src/runtime/config-migration.js +6 -6
  104. package/dist-electron/services/local-ai-core/src/runtime/deployment-diagnostics.js +3 -3
  105. package/dist-electron/services/local-ai-core/src/runtime/external-service.js +3 -1
  106. package/dist-electron/services/local-ai-core/src/runtime/handlers/automation-handler.js +11 -2
  107. package/dist-electron/services/local-ai-core/src/runtime/handlers/channel-handler.js +12 -6
  108. package/dist-electron/services/local-ai-core/src/runtime/handlers/external-handler.js +10 -2
  109. package/dist-electron/services/local-ai-core/src/runtime/handlers/knowledge-handler.js +18 -6
  110. package/dist-electron/services/local-ai-core/src/runtime/handlers/provider-handler.js +7 -2
  111. package/dist-electron/services/local-ai-core/src/runtime/handlers/runtime-handler.js +28 -3
  112. package/dist-electron/services/local-ai-core/src/runtime/handlers/scheduler-handler.js +10 -2
  113. package/dist-electron/services/local-ai-core/src/runtime/handlers/security-handler.js +18 -5
  114. package/dist-electron/services/local-ai-core/src/runtime/handlers/task-handler.js +14 -2
  115. package/dist-electron/services/local-ai-core/src/runtime/handlers/thread-handler.js +18 -15
  116. package/dist-electron/services/local-ai-core/src/runtime/handlers/workspace-handler.js +13 -2
  117. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +35 -8
  118. package/dist-electron/services/local-ai-core/src/runtime/request-validation.js +79 -0
  119. package/dist-electron/services/local-ai-core/src/runtime/runtime-detection-service.js +9 -9
  120. package/dist-electron/services/local-ai-core/src/runtime/server-helpers.js +10 -1
  121. package/dist-electron/services/local-ai-core/src/runtime/server.js +2 -1
  122. package/dist-electron/services/local-ai-core/src/runtime/workspace-project-registry.js +76 -0
  123. package/dist-electron/services/local-ai-core/src/sandbox/sandbox-config.js +8 -8
  124. package/dist-electron/services/local-ai-core/src/thread/agent-message-policy.js +75 -0
  125. package/dist-electron/shared/desktop.js +1 -64
  126. package/dist-electron/src/components/chat/chat-controller-state.js +60 -0
  127. package/dist-electron/src/components/chat/chat-event-gate.js +146 -0
  128. package/dist-electron/src/components/chat/markdown-code-block.js +8 -0
  129. package/dist-electron/src/pages/Desktop/workspace-model.js +212 -0
  130. package/dist-electron/src/pages/Threads/thread-chat-model.js +50 -51
  131. package/dist-electron/tests/contracts/agent-message-policy.test.js +21 -0
  132. package/dist-electron/tests/contracts/architecture-docs.test.js +56 -33
  133. package/dist-electron/tests/contracts/chat-event-gate.test.js +114 -0
  134. package/dist-electron/tests/contracts/chat-markdown.test.js +16 -0
  135. package/dist-electron/tests/contracts/thread-task-state.test.js +32 -0
  136. package/dist-electron/tests/contracts/workspace-identity.test.js +23 -0
  137. package/dist-electron/tests/electron/core-client.test.js +284 -0
  138. package/dist-electron/tests/electron/plugin-kernel.test.js +54 -0
  139. package/dist-electron/tests/electron/renderer-packaging.test.js +9 -0
  140. package/dist-electron/tests/electron/workspace-task-store.test.js +31 -0
  141. package/dist-electron/tests/integration/channel-inbound-attachment-store.test.js +117 -0
  142. package/dist-electron/tests/integration/channel-inbound-authorization.test.js +80 -0
  143. package/dist-electron/tests/integration/lark-gateway-utils.test.js +13 -0
  144. package/dist-electron/tests/integration/lark-gateway.test.js +58 -4
  145. package/dist-electron/tests/integration/local-core-errors.test.js +41 -0
  146. package/dist-electron/tests/integration/weixin-gateway.test.js +36 -0
  147. package/dist-electron/tests/integration/weixin-runtime-state.test.js +32 -0
  148. package/dist-electron/tests/integration/weixin-text-utils.test.js +15 -0
  149. package/package.json +9 -4
  150. package/dist/renderer/assets/Chat-BJveQQKu.js +0 -2
  151. package/dist/renderer/assets/ChatMarkdown-Bqn2HJ6f.js +0 -45
  152. package/dist/renderer/assets/Config-CMtZDRAE.js +0 -21
  153. package/dist/renderer/assets/CronList-C-ILB_Ae.js +0 -1
  154. package/dist/renderer/assets/Dashboard-CsKBSQIe.js +0 -26
  155. package/dist/renderer/assets/Logs-CktemGN0.js +0 -1
  156. package/dist/renderer/assets/MonitorList-B0hxjuOZ.js +0 -1
  157. package/dist/renderer/assets/Page-DL_Nv2i5.js +0 -1
  158. package/dist/renderer/assets/ProjectDetail-Djr9-MyT.js +0 -11
  159. package/dist/renderer/assets/ProjectList-BKl4eW7q.js +0 -1
  160. package/dist/renderer/assets/SessionChat-Dxv8D5DW.js +0 -1
  161. package/dist/renderer/assets/SessionList-BJ2GjXvR.js +0 -1
  162. package/dist/renderer/assets/ThreadChat-CAsIl9qf.js +0 -28
  163. package/dist/renderer/assets/Workspace-CaUhiGBX.js +0 -19
  164. package/dist/renderer/assets/bot-Fw3IzhJA.js +0 -6
  165. package/dist/renderer/assets/check-5zCTgp3A.js +0 -6
  166. package/dist/renderer/assets/chevron-down-N3PxTNxH.js +0 -6
  167. package/dist/renderer/assets/circle-C_chUflo.js +0 -6
  168. package/dist/renderer/assets/desktop-DkQTZNhe.js +0 -1
  169. package/dist/renderer/assets/desktop-JQkhNOF4.js +0 -5
  170. package/dist/renderer/assets/funnel-B8ZAv6I7.js +0 -6
  171. package/dist/renderer/assets/heart-M9A3E4Ed.js +0 -6
  172. package/dist/renderer/assets/index-Bwv855ID.js +0 -41
  173. package/dist/renderer/assets/index-CjCuR6DI.css +0 -11
  174. package/dist/renderer/assets/index-DJ8w4e0l.js +0 -182
  175. package/dist/renderer/assets/projects-DB1FLZ3A.js +0 -1
  176. package/dist/renderer/assets/server-BsuHcYl4.js +0 -11
  177. package/dist/renderer/assets/session-utils-DnAWL3eo.js +0 -6
  178. package/dist/renderer/assets/sessions-DptxO_BE.js +0 -1
  179. package/dist/renderer/assets/user-DQKTaUcR.js +0 -6
  180. package/dist/renderer/assets/wifi-off-vjH-MD2w.js +0 -11
  181. package/dist-electron/src/api/client.js +0 -73
  182. package/dist-electron/src/api/sessions.js +0 -21
  183. package/dist-electron/src/lib/session-utils.js +0 -51
  184. package/dist-electron/src/pages/Projects/project-provider-config.js +0 -75
  185. package/dist-electron/tests/electron/project-provider-config.test.js +0 -71
@@ -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
+ });
@@ -1021,6 +1021,60 @@ model = "deepseek-v4-flash"
1021
1021
  strict_1.default.match(response.body.error, /Unknown route/);
1022
1022
  strict_1.default.equal(response.body.errorInfo?.severity, 'error');
1023
1023
  });
1024
+ (0, node_test_1.default)('server rejects malformed endpoint input with a 400 response before domain dispatch', async () => {
1025
+ let dispatched = false;
1026
+ const controller = new node_events_1.EventEmitter();
1027
+ const workspaceRouter = {
1028
+ createThread: async () => {
1029
+ dispatched = true;
1030
+ return {};
1031
+ },
1032
+ };
1033
+ const server = new server_js_1.LocalAiCoreServer({ controller, workspaceRouter }, { port: 0 });
1034
+ const response = await invokeServer(server, 'POST', '/api/local/v1/threads', { workspaceId: 42 });
1035
+ strict_1.default.equal(response.statusCode, 400);
1036
+ strict_1.default.equal(response.body.ok, false);
1037
+ strict_1.default.match(response.body.error, /workspaceId/);
1038
+ strict_1.default.equal(dispatched, false);
1039
+ const emptyResponse = await invokeServer(server, 'POST', '/api/local/v1/threads', { workspaceId: ' ' });
1040
+ strict_1.default.equal(emptyResponse.statusCode, 400);
1041
+ strict_1.default.match(emptyResponse.body.error, /workspaceId/);
1042
+ strict_1.default.equal(dispatched, false);
1043
+ });
1044
+ (0, node_test_1.default)('server validates the actual desktop settings contract before dispatch', async () => {
1045
+ let dispatched = false;
1046
+ const controller = new node_events_1.EventEmitter();
1047
+ controller.saveSettings = async () => {
1048
+ dispatched = true;
1049
+ return {};
1050
+ };
1051
+ const server = new server_js_1.LocalAiCoreServer({ controller }, { port: 0 });
1052
+ const invalidKnowledge = await invokeServer(server, 'POST', '/api/local/v1/runtime/settings', { knowledge: 42 });
1053
+ strict_1.default.equal(invalidKnowledge.statusCode, 400);
1054
+ strict_1.default.match(invalidKnowledge.body.error, /knowledge/);
1055
+ const invalidPlugins = await invokeServer(server, 'POST', '/api/local/v1/runtime/settings', { plugins: [] });
1056
+ strict_1.default.equal(invalidPlugins.statusCode, 400);
1057
+ strict_1.default.match(invalidPlugins.body.error, /plugins/);
1058
+ const invalidPluginSettings = await invokeServer(server, 'POST', '/api/local/v1/runtime/settings', {
1059
+ plugins: { 'channel.lark': { enabled: 'yes' } },
1060
+ });
1061
+ strict_1.default.equal(invalidPluginSettings.statusCode, 400);
1062
+ strict_1.default.match(invalidPluginSettings.body.error, /plugins\.channel\.lark\.enabled/);
1063
+ const invalidAuthMode = await invokeServer(server, 'POST', '/api/local/v1/runtime/settings', {
1064
+ knowledge: { authMode: 'cookie' },
1065
+ });
1066
+ strict_1.default.equal(invalidAuthMode.statusCode, 400);
1067
+ strict_1.default.match(invalidAuthMode.body.error, /knowledge\.authMode/);
1068
+ strict_1.default.equal(dispatched, false);
1069
+ const valid = await invokeServer(server, 'POST', '/api/local/v1/runtime/settings', {
1070
+ autoStartService: true,
1071
+ defaultProject: 'workspace-stable',
1072
+ knowledge: { authMode: 'bearer', token: 'secret' },
1073
+ plugins: { 'channel.lark': { enabled: true, config: { appId: 'app-id' } } },
1074
+ });
1075
+ strict_1.default.equal(valid.statusCode, 200);
1076
+ strict_1.default.equal(dispatched, true);
1077
+ });
1024
1078
  (0, node_test_1.default)('server diagnostics routes dispatch through generic bindings', async () => {
1025
1079
  const calls = [];
1026
1080
  const controller = Object.assign(new node_events_1.EventEmitter(), {
@@ -31,6 +31,15 @@ const rootDir = node_process_1.default.cwd();
31
31
  strict_1.default.ok(buildIndex >= 0, 'pnpm e2e:smoke must start from a production build');
32
32
  strict_1.default.ok(smokeIndex > buildIndex, 'pnpm e2e:smoke must run smoke checks after the production build');
33
33
  });
34
+ (0, node_test_1.default)('development startup rewrites workspace aliases before launching Electron', () => {
35
+ const devScript = (0, node_fs_1.readFileSync)((0, node_path_1.join)(rootDir, 'scripts', 'dev.mjs'), 'utf8');
36
+ const aliasCommandIndex = devScript.indexOf("['exec', 'tsc-alias', '-p', 'tsconfig.electron.json']");
37
+ const readyAssignmentIndex = devScript.indexOf('electronReady = true', aliasCommandIndex);
38
+ const launchIndex = devScript.indexOf('maybeLaunchElectron()', readyAssignmentIndex);
39
+ strict_1.default.ok(aliasCommandIndex >= 0, 'pnpm dev must rewrite emitted @cc/* aliases');
40
+ strict_1.default.ok(readyAssignmentIndex > aliasCommandIndex, 'Electron must become ready only after alias rewriting is configured');
41
+ strict_1.default.ok(launchIndex > readyAssignmentIndex, 'Electron must launch only after alias rewriting completes');
42
+ });
34
43
  (0, node_test_1.default)('production renderer build has a loadable entry document and assets', () => {
35
44
  const rendererDir = (0, node_path_1.join)(rootDir, 'dist', 'renderer');
36
45
  const indexPath = (0, node_path_1.join)(rendererDir, 'index.html');
@@ -42,6 +42,37 @@ const env_utils_js_1 = require("../../services/local-ai-core/src/runtime/env-uti
42
42
  (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
43
43
  }
44
44
  });
45
+ (0, node_test_1.default)('runtime project migration makes workspace registry authoritative and preserves identity across rename', async () => {
46
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'workspace-project-migration-'));
47
+ try {
48
+ const runtime = (0, bootstrap_js_1.bootstrapLocalCoreRuntime)({ userDataPath, enableKnowledge: false, log: () => { } });
49
+ const controller = new local_core_controller_js_1.LocalCoreController(userDataPath, runtime);
50
+ await controller.saveRuntimeConfig({
51
+ projects: [{
52
+ name: 'Workspace A',
53
+ agent: { type: 'pi', options: { work_dir: '/tmp/workspace-a' } },
54
+ platforms: [],
55
+ }],
56
+ });
57
+ const first = await controller.readRuntimeConfig();
58
+ const workspaceId = first.config.projects?.[0]?.workspace_id;
59
+ strict_1.default.ok(workspaceId);
60
+ strict_1.default.equal(controller.store.getWorkspaceRegistryEntry(workspaceId)?.displayName, 'Workspace A');
61
+ strict_1.default.equal(controller.store.readRuntimeConfig().config.projects, undefined);
62
+ await controller.saveRuntimeConfig({
63
+ ...first.config,
64
+ projects: [{ ...first.config.projects[0], name: 'Workspace B' }],
65
+ });
66
+ const renamed = await controller.readRuntimeConfig();
67
+ strict_1.default.equal(renamed.config.projects?.[0]?.workspace_id, workspaceId);
68
+ strict_1.default.equal(controller.store.getWorkspaceRegistryEntry(workspaceId)?.displayName, 'Workspace B');
69
+ strict_1.default.equal((await controller.workspaceRouter.listWorkspaces())[0]?.id, workspaceId);
70
+ await controller.close();
71
+ }
72
+ finally {
73
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
74
+ }
75
+ });
45
76
  (0, node_test_1.default)('model providers persist independently from workspace config', () => {
46
77
  const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'model-provider-store-'));
47
78
  try {
@@ -0,0 +1,117 @@
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 node_fs_1 = require("node:fs");
9
+ const node_os_1 = require("node:os");
10
+ const node_path_1 = require("node:path");
11
+ const node_stream_1 = require("node:stream");
12
+ const inbound_attachment_store_js_1 = require("../../services/local-ai-core/src/channel/shared/inbound-attachment-store.js");
13
+ (0, node_test_1.default)('inbound attachment store streams a platform source into an atomic local file', async () => {
14
+ const tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'channel-inbound-store-'));
15
+ try {
16
+ const store = new inbound_attachment_store_js_1.FileSystemInboundAttachmentStore();
17
+ const stored = await store.save({
18
+ directory: tempDir,
19
+ storedFileName: 'message/one-report.pdf',
20
+ displayFileName: 'report.pdf',
21
+ source: {
22
+ open: async () => ({
23
+ stream: node_stream_1.Readable.from([Buffer.from('hello '), Buffer.from('world')]),
24
+ mimeType: 'application/pdf',
25
+ }),
26
+ },
27
+ });
28
+ strict_1.default.equal(stored.path, (0, node_path_1.join)(tempDir, 'message_one-report.pdf'));
29
+ strict_1.default.equal(stored.fileName, 'report.pdf');
30
+ strict_1.default.equal(stored.mimeType, 'application/pdf');
31
+ strict_1.default.equal(stored.size, 11);
32
+ strict_1.default.equal((0, node_fs_1.readFileSync)(stored.path, 'utf8'), 'hello world');
33
+ strict_1.default.deepEqual((0, node_fs_1.readdirSync)(tempDir), ['message_one-report.pdf']);
34
+ }
35
+ finally {
36
+ (0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
37
+ }
38
+ });
39
+ (0, node_test_1.default)('inbound attachment store removes partial files when the size limit is exceeded', async () => {
40
+ const tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'channel-inbound-limit-'));
41
+ try {
42
+ const store = new inbound_attachment_store_js_1.FileSystemInboundAttachmentStore();
43
+ await strict_1.default.rejects(() => store.save({
44
+ directory: tempDir,
45
+ storedFileName: 'large.bin',
46
+ displayFileName: 'large.bin',
47
+ maxBytes: 4,
48
+ source: {
49
+ open: async () => ({ stream: node_stream_1.Readable.from([Buffer.from('12345')]) }),
50
+ },
51
+ }), /exceeds 4 bytes/);
52
+ strict_1.default.deepEqual((0, node_fs_1.readdirSync)(tempDir), []);
53
+ }
54
+ finally {
55
+ (0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
56
+ }
57
+ });
58
+ (0, node_test_1.default)('inbound attachment store finalizes the filename before commit and can return base64', async () => {
59
+ const tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'channel-inbound-finalize-'));
60
+ try {
61
+ const store = new inbound_attachment_store_js_1.FileSystemInboundAttachmentStore();
62
+ const stored = await store.save({
63
+ directory: tempDir,
64
+ storedFileName: 'image-1',
65
+ displayFileName: 'image.png',
66
+ includeBase64: true,
67
+ finalizeStoredFileName: ({ storedFileName, prefix }) => {
68
+ strict_1.default.deepEqual(prefix.subarray(0, 4), Buffer.from([0x89, 0x50, 0x4e, 0x47]));
69
+ return `${storedFileName}.png`;
70
+ },
71
+ source: {
72
+ open: async () => ({ stream: node_stream_1.Readable.from([Buffer.from([0x89, 0x50, 0x4e, 0x47, 1, 2])]) }),
73
+ },
74
+ });
75
+ strict_1.default.equal(stored.path, (0, node_path_1.join)(tempDir, 'image-1.png'));
76
+ strict_1.default.equal(stored.data, 'iVBORwEC');
77
+ strict_1.default.deepEqual((0, node_fs_1.readdirSync)(tempDir), ['image-1.png']);
78
+ }
79
+ finally {
80
+ (0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
81
+ }
82
+ });
83
+ (0, node_test_1.default)('inbound attachment store removes temporary data when filename finalization fails', async () => {
84
+ const tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'channel-inbound-finalize-error-'));
85
+ try {
86
+ const store = new inbound_attachment_store_js_1.FileSystemInboundAttachmentStore();
87
+ await strict_1.default.rejects(() => store.save({
88
+ directory: tempDir,
89
+ storedFileName: 'image-1',
90
+ displayFileName: 'image.png',
91
+ finalizeStoredFileName: () => {
92
+ throw new Error('cannot identify image');
93
+ },
94
+ source: {
95
+ open: async () => ({ stream: node_stream_1.Readable.from([Buffer.from('image')]) }),
96
+ },
97
+ }), /cannot identify image/);
98
+ strict_1.default.deepEqual((0, node_fs_1.readdirSync)(tempDir), []);
99
+ }
100
+ finally {
101
+ (0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
102
+ }
103
+ });
104
+ (0, node_test_1.default)('inbound attachment URI maps workspace files into sandbox and hides external files', () => {
105
+ strict_1.default.equal((0, inbound_attachment_store_js_1.resolveInboundAttachmentUri)({
106
+ filePath: '/host/project/.agentdock/image.png',
107
+ workspacePath: '/host/project',
108
+ sandboxEnabled: true,
109
+ sandboxWorkspacePath: '/workspace',
110
+ }), 'file:///workspace/.agentdock/image.png');
111
+ strict_1.default.equal((0, inbound_attachment_store_js_1.resolveInboundAttachmentUri)({
112
+ filePath: '/host/state/image.png',
113
+ workspacePath: '/host/project',
114
+ sandboxEnabled: true,
115
+ sandboxWorkspacePath: '/workspace',
116
+ }), undefined);
117
+ });
@@ -0,0 +1,80 @@
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 inbound_authorization_js_1 = require("../../services/local-ai-core/src/channel/shared/inbound-authorization.js");
9
+ function createStore() {
10
+ const authorizedUsers = [];
11
+ const pairings = [];
12
+ return {
13
+ authorizedUsers,
14
+ pairings,
15
+ store: {
16
+ expirePendingPairings() { },
17
+ getAuthorizedUser(workspaceId, platformUserId, platform) {
18
+ return authorizedUsers.find((row) => row.workspace_id === workspaceId &&
19
+ row.platform_user_id === platformUserId &&
20
+ row.platform === platform);
21
+ },
22
+ createAuthorizedUser(input) {
23
+ authorizedUsers.push(input);
24
+ },
25
+ listPendingPairings() {
26
+ return pairings;
27
+ },
28
+ createPairingRequest(input) {
29
+ pairings.push(input);
30
+ },
31
+ },
32
+ };
33
+ }
34
+ const identity = {
35
+ workspaceId: 'workspace-1',
36
+ platformKey: 'lark:default',
37
+ platformUserId: 'user-1',
38
+ chatId: 'chat-1',
39
+ displayName: 'User One',
40
+ };
41
+ (0, node_test_1.default)('shared inbound authorization auto-approves and returns the stored user', () => {
42
+ const fixture = createStore();
43
+ let changes = 0;
44
+ const result = (0, inbound_authorization_js_1.resolveInboundChannelAuthorization)({
45
+ store: fixture.store,
46
+ identity,
47
+ autoApprove: true,
48
+ authorizedUserIdPrefix: 'lark-user',
49
+ generatePairingCode: () => '123456',
50
+ onStateChanged: () => { changes += 1; },
51
+ });
52
+ strict_1.default.equal(result.status, 'authorized');
53
+ strict_1.default.equal(fixture.authorizedUsers.length, 1);
54
+ strict_1.default.equal(changes, 1);
55
+ });
56
+ (0, node_test_1.default)('shared inbound authorization reuses an existing pending pairing', () => {
57
+ const fixture = createStore();
58
+ fixture.pairings.push({
59
+ code: '654321',
60
+ workspace_id: 'workspace-1',
61
+ platform: 'lark:default',
62
+ platform_user_id: 'user-1',
63
+ chat_id: 'chat-1',
64
+ status: 'pending',
65
+ });
66
+ let generated = 0;
67
+ const result = (0, inbound_authorization_js_1.resolveInboundChannelAuthorization)({
68
+ store: fixture.store,
69
+ identity,
70
+ autoApprove: false,
71
+ authorizedUserIdPrefix: 'lark-user',
72
+ generatePairingCode: () => {
73
+ generated += 1;
74
+ return '123456';
75
+ },
76
+ });
77
+ strict_1.default.deepEqual(result, { status: 'pending', pairingCode: '654321' });
78
+ strict_1.default.equal(generated, 0);
79
+ strict_1.default.equal(fixture.pairings.length, 1);
80
+ });
@@ -0,0 +1,13 @@
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 gateway_utils_js_1 = require("../../services/local-ai-core/src/channel/lark/gateway-utils.js");
9
+ (0, node_test_1.default)('lark gateway attachment utilities preserve content summaries and mime detection', () => {
10
+ strict_1.default.equal((0, gateway_utils_js_1.summarizeLarkInboundContentParts)([{ type: 'file', fileName: 'report.pdf' }]), '[File: report.pdf]');
11
+ strict_1.default.equal((0, gateway_utils_js_1.extractLarkHeaderMimeType)({ 'Content-Type': 'image/png; charset=binary' }), 'image/png');
12
+ strict_1.default.equal((0, gateway_utils_js_1.sniffLarkImageExtension)(Buffer.from([0x89, 0x50, 0x4e, 0x47])), 'png');
13
+ });