@kafca/agentdock 0.1.36 → 0.1.37

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 (62) hide show
  1. package/README.md +5 -0
  2. package/dist/renderer/assets/{Badge-DUa4J-09.js → Badge-CjzZp9Tf.js} +1 -1
  3. package/dist/renderer/assets/{Chat-B1FWuTWV.js → Chat-D4_5K_Ww.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-DqOgFSgI.js → ChatMarkdown-DkI29dOw.js} +1 -1
  5. package/dist/renderer/assets/{Config-CM5bqUrV.js → Config-CnrlmSEQ.js} +1 -1
  6. package/dist/renderer/assets/{CronList-CfXE7S0M.js → CronList-DVvInMvr.js} +1 -1
  7. package/dist/renderer/assets/{Dashboard-D5Dwbotu.js → Dashboard-CY4YAmVj.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-Bs_6ISc0.js → EmptyState-CmR947i4.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-0kdWnKyy.js → KnowledgeDetail-BvNG0inb.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeHome-Bg8xbRe2.js → KnowledgeHome-BB0Z8VrS.js} +1 -1
  11. package/dist/renderer/assets/{Logs-BfgvW7qZ.js → Logs-IORTjnGM.js} +1 -1
  12. package/dist/renderer/assets/{Modal-BkuKCDDx.js → Modal-D9NZ6cvF.js} +1 -1
  13. package/dist/renderer/assets/{Page-ddaAS_jP.js → Page-DlvElfTz.js} +1 -1
  14. package/dist/renderer/assets/{ProjectDetail-C9x-Ib5p.js → ProjectDetail-Y1ZFL8W9.js} +1 -1
  15. package/dist/renderer/assets/{ProjectList-XIIikxrj.js → ProjectList-DAI-4HU7.js} +1 -1
  16. package/dist/renderer/assets/{Select-BBL_5aa5.js → Select-LMEt9yJy.js} +1 -1
  17. package/dist/renderer/assets/{SessionChat-BFsTM0c7.js → SessionChat-BWRAS_H-.js} +1 -1
  18. package/dist/renderer/assets/{SessionList-DWh6n10K.js → SessionList-DQsxmC1n.js} +1 -1
  19. package/dist/renderer/assets/{ThreadChat-CzPiDA92.js → ThreadChat-D9j78cdm.js} +1 -1
  20. package/dist/renderer/assets/{Workspace-BW8v3Nia.js → Workspace-C-RTj4uI.js} +1 -1
  21. package/dist/renderer/assets/{arrow-left-CcoHvwbx.js → arrow-left-C_ZIHUlD.js} +1 -1
  22. package/dist/renderer/assets/{book-open-RweyWxPq.js → book-open-BuI3webp.js} +1 -1
  23. package/dist/renderer/assets/{check-B5YOrBNT.js → check-FA1rU94Q.js} +1 -1
  24. package/dist/renderer/assets/{chevron-down-HjaPrxdd.js → chevron-down-DvQ7Hq2s.js} +1 -1
  25. package/dist/renderer/assets/{circle-C-aaO7LR.js → circle-DZYvZ-Iq.js} +1 -1
  26. package/dist/renderer/assets/{desktop-DUWkNnbA.js → desktop-BSoKBjFu.js} +1 -1
  27. package/dist/renderer/assets/{funnel-IWZmi-_V.js → funnel-BoW8A8Fi.js} +1 -1
  28. package/dist/renderer/assets/{heart-DM5_F2nX.js → heart-DaIdiPUs.js} +1 -1
  29. package/dist/renderer/assets/{index-Bin2xyBS.js → index-Cc83IKng.js} +2 -2
  30. package/dist/renderer/assets/{index-DvIUdsAm.js → index-pdvxKnBr.js} +1 -1
  31. package/dist/renderer/assets/{pencil-DcxwCYPi.js → pencil-CMs10l_8.js} +1 -1
  32. package/dist/renderer/assets/{play-DUdnnxLC.js → play-DtT_FrBZ.js} +1 -1
  33. package/dist/renderer/assets/{plug-B-rDbk24.js → plug-vKGMXapD.js} +1 -1
  34. package/dist/renderer/assets/{plus-DslMGvrO.js → plus-DZV73wzr.js} +1 -1
  35. package/dist/renderer/assets/projects-BUda9SpJ.js +1 -0
  36. package/dist/renderer/assets/{save-DlkR3jLs.js → save-Bus9w20l.js} +1 -1
  37. package/dist/renderer/assets/{search-ULTp3p_0.js → search-ChEC20LW.js} +1 -1
  38. package/dist/renderer/assets/{server-DBdiKfv9.js → server-B9HgN6CF.js} +1 -1
  39. package/dist/renderer/assets/{session-utils-BPyVVONo.js → session-utils-Bn9-xCic.js} +1 -1
  40. package/dist/renderer/assets/{sessions-B0NecMYO.js → sessions-RUHmhxT5.js} +1 -1
  41. package/dist/renderer/assets/{shield-check-BNSk8nza.js → shield-check-B97md1a5.js} +1 -1
  42. package/dist/renderer/assets/{trash-2-CGydl763.js → trash-2-31WOGl8I.js} +1 -1
  43. package/dist/renderer/assets/{user-D1xkQo35.js → user-CLiiHYE_.js} +1 -1
  44. package/dist/renderer/assets/{wifi-off-Iy1XPeZ3.js → wifi-off-BKh21npc.js} +1 -1
  45. package/dist/renderer/index.html +1 -1
  46. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +22 -7
  47. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +2 -1
  48. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +37 -7
  49. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +31 -1
  50. package/dist-electron/services/local-ai-core/src/scheduler/channel-execution-policy.js +79 -0
  51. package/dist-electron/services/local-ai-core/src/scheduler/lark-execution-policies.js +7 -61
  52. package/dist-electron/services/local-ai-core/src/scheduler/lark-schedule-adapter.js +3 -12
  53. package/dist-electron/services/local-ai-core/src/scheduler/local-schedule-adapter.js +23 -30
  54. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-bridge-session.js +27 -0
  55. package/dist-electron/services/local-ai-core/src/scheduler/scheduler-run-lifecycle.js +13 -1
  56. package/dist-electron/services/local-ai-core/src/scheduler/scheduler-service.js +1 -1
  57. package/dist-electron/services/local-ai-core/src/scheduler/weixin-execution-policies.js +7 -61
  58. package/dist-electron/services/local-ai-core/src/scheduler/weixin-schedule-adapter.js +3 -12
  59. package/dist-electron/tests/electron/workspace-task-store.test.js +178 -28
  60. package/dist-electron/tests/integration/local-core-refactor.test.js +58 -5
  61. package/package.json +1 -1
  62. package/dist/renderer/assets/projects-JFuOB4DM.js +0 -1
@@ -1,34 +1,41 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LocalScheduleAdapter = void 0;
4
- const TERMINAL_RUN_STATES = new Set(['completed', 'failed', 'interrupted']);
5
- const SCHEDULED_RUN_PERMISSION_MODE = 'bypassPermissions';
4
+ const scheduled_conversation_executor_js_1 = require("./scheduled-conversation-executor.js");
6
5
  class LocalScheduleAdapter {
7
6
  options;
7
+ executor;
8
8
  deliveryTargets = ['local'];
9
9
  constructor(options) {
10
10
  this.options = options;
11
+ this.executor = new scheduled_conversation_executor_js_1.ScheduledConversationExecutor({
12
+ store: options.store,
13
+ getWorkspaceRouter: options.getWorkspaceRouter,
14
+ });
11
15
  }
12
16
  supports(job) {
13
17
  return job.platform === 'local' && (job.route.type === 'local.thread' || job.route.type === 'thread');
14
18
  }
15
19
  async execute(context) {
16
20
  const { job } = context;
17
- const workspaceRouter = this.options.getWorkspaceRouter();
18
- const threadId = await this.resolveThread(job);
19
- const sendResult = await workspaceRouter.sendThreadMessage(threadId, job.promptTemplate, {
20
- permissionMode: SCHEDULED_RUN_PERMISSION_MODE,
21
- });
22
- await this.waitForRun(sendResult.runId);
23
- const thread = await workspaceRouter.getThread(threadId);
24
- const replyText = [...thread.messages]
25
- .reverse()
26
- .find((message) => message.role === 'assistant' && message.kind === 'final')
27
- ?.content;
21
+ const execution = await this.executor.execute(job, job.promptTemplate, this.createExecutionPolicy());
22
+ return {
23
+ threadId: execution.threadId,
24
+ runId: execution.runId,
25
+ replyText: execution.replyText,
26
+ deliveryMode: 'thread-only',
27
+ deliveryStatus: 'succeeded',
28
+ };
29
+ }
30
+ createExecutionPolicy() {
28
31
  return {
29
- threadId,
30
- runId: sendResult.runId,
31
- replyText,
32
+ resolveTarget: async (job) => ({
33
+ kind: 'local:thread',
34
+ threadId: await this.resolveThread(job),
35
+ workspaceId: job.workspaceId,
36
+ platform: job.platform,
37
+ route: job.route,
38
+ }),
32
39
  };
33
40
  }
34
41
  async resolveThread(job) {
@@ -54,19 +61,5 @@ class LocalScheduleAdapter {
54
61
  return false;
55
62
  }
56
63
  }
57
- async waitForRun(runId, timeoutMs = 15 * 60 * 1000) {
58
- const startedAt = Date.now();
59
- while (Date.now() - startedAt < timeoutMs) {
60
- const run = this.options.store.getRun(runId);
61
- if (run && TERMINAL_RUN_STATES.has(run.status)) {
62
- if (run.status !== 'completed') {
63
- throw new Error(`Scheduled run finished with status ${run.status}`);
64
- }
65
- return;
66
- }
67
- await new Promise((resolve) => setTimeout(resolve, 300));
68
- }
69
- throw new Error(`Timed out waiting for scheduled run ${runId}`);
70
- }
71
64
  }
72
65
  exports.LocalScheduleAdapter = LocalScheduleAdapter;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ScheduledBridgeSession = void 0;
4
+ class ScheduledBridgeSession {
5
+ static async open(input) {
6
+ const sessionKey = input.workspaceRouter.getThreadSessionKey(input.threadId);
7
+ const unregister = await input.getChannelRuntime().registerScheduledThreadBridge?.({
8
+ workspaceId: input.job.workspaceId,
9
+ platform: input.job.platform,
10
+ route: input.job.route,
11
+ threadId: input.threadId,
12
+ sessionKey,
13
+ });
14
+ return {
15
+ mode: 'bridge-stream',
16
+ workspaceId: input.job.workspaceId,
17
+ platform: input.job.platform,
18
+ route: input.job.route,
19
+ threadId: input.threadId,
20
+ sessionKey,
21
+ close: async () => {
22
+ await unregister?.();
23
+ },
24
+ };
25
+ }
26
+ }
27
+ exports.ScheduledBridgeSession = ScheduledBridgeSession;
@@ -10,13 +10,18 @@ class SchedulerRunLifecycle {
10
10
  const skipped = this.options.store.createScheduledJobRun(job.id, 'skipped', {
11
11
  triggeredAt,
12
12
  error,
13
+ deliveryStatus: 'skipped',
14
+ deliveryError: error,
13
15
  });
14
16
  this.options.emitRun(skipped);
15
17
  this.emitCurrentJob(job.id);
16
18
  return skipped;
17
19
  }
18
20
  markQueued(job, triggeredAt) {
19
- const run = this.options.store.createScheduledJobRun(job.id, 'queued', { triggeredAt });
21
+ const run = this.options.store.createScheduledJobRun(job.id, 'queued', {
22
+ triggeredAt,
23
+ deliveryStatus: 'pending',
24
+ });
20
25
  this.options.emitRun(run);
21
26
  return run;
22
27
  }
@@ -35,6 +40,11 @@ class SchedulerRunLifecycle {
35
40
  threadId: result.threadId,
36
41
  runId: result.runId,
37
42
  platformMessageId: result.platformMessageId,
43
+ platformMessageIds: result.platformMessageIds,
44
+ deliveryMode: result.deliveryMode,
45
+ deliveryStatus: result.deliveryStatus || 'succeeded',
46
+ deliveryError: result.deliveryError || '',
47
+ lastBridgeEventAt: result.lastBridgeEventAt,
38
48
  error: '',
39
49
  });
40
50
  if (disableOnceJob) {
@@ -49,6 +59,8 @@ class SchedulerRunLifecycle {
49
59
  status: 'failed',
50
60
  finishedAt: new Date().toISOString(),
51
61
  error,
62
+ deliveryStatus: 'failed',
63
+ deliveryError: error,
52
64
  });
53
65
  this.options.emitRun(failed);
54
66
  this.emitCurrentJob(jobId);
@@ -98,7 +98,7 @@ class SchedulerService extends node_events_1.EventEmitter {
98
98
  if (!this.isDue(job, now)) {
99
99
  continue;
100
100
  }
101
- await this.executeJob(job, now.toISOString(), false);
101
+ void this.executeJob(job, now.toISOString(), false);
102
102
  }
103
103
  }
104
104
  finally {
@@ -1,66 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createWeixinExecutionPolicy = createWeixinExecutionPolicy;
4
+ const channel_execution_policy_js_1 = require("./channel-execution-policy.js");
4
5
  function createWeixinExecutionPolicy(job, options, resolveSameThread) {
5
- if (job.executionMode === 'side-thread') {
6
- return new WeixinSideThreadExecutionPolicy(options);
7
- }
8
- return new WeixinSameThreadExecutionPolicy(options, resolveSameThread);
9
- }
10
- class WeixinSameThreadExecutionPolicy {
11
- options;
12
- resolveSameThread;
13
- constructor(options, resolveSameThread) {
14
- this.options = options;
15
- this.resolveSameThread = resolveSameThread;
16
- }
17
- async resolveTarget(job) {
18
- return {
19
- kind: 'thread',
20
- threadId: await this.resolveSameThread(job),
21
- workspaceId: job.workspaceId,
22
- platform: job.platform,
23
- route: job.route,
24
- };
25
- }
26
- beforeExecute(target) {
27
- this.options.getChannelRuntime().muteThreadBridge?.(target.threadId);
28
- }
29
- afterExecute(target) {
30
- this.options.getChannelRuntime().unmuteThreadBridge?.(target.threadId);
31
- }
32
- }
33
- class WeixinSideThreadExecutionPolicy {
34
- options;
35
- constructor(options) {
36
- this.options = options;
37
- }
38
- async resolveTarget(job) {
39
- const title = `[Scheduled] ${job.description || job.id}`;
40
- const existing = (await this.options.workspaceRouter.listThreads(job.workspaceId))
41
- .find((thread) => thread.title === title);
42
- if (existing) {
43
- return {
44
- kind: 'thread',
45
- threadId: existing.id,
46
- workspaceId: job.workspaceId,
47
- platform: job.platform,
48
- route: job.route,
49
- };
50
- }
51
- const created = await this.options.workspaceRouter.createThread(job.workspaceId, title);
52
- return {
53
- kind: 'thread',
54
- threadId: created.id,
55
- workspaceId: job.workspaceId,
56
- platform: job.platform,
57
- route: job.route,
58
- };
59
- }
60
- beforeExecute(target) {
61
- this.options.getChannelRuntime().muteThreadBridge?.(target.threadId);
62
- }
63
- afterExecute(target) {
64
- this.options.getChannelRuntime().unmuteThreadBridge?.(target.threadId);
65
- }
6
+ return (0, channel_execution_policy_js_1.createChannelExecutionPolicy)(job, options, {
7
+ platformBase: 'weixin',
8
+ resolveSameThread,
9
+ sideThreadTitle: (nextJob) => `[Scheduled:Weixin] ${nextJob.description || nextJob.id}`,
10
+ legacySideThreadTitles: (nextJob) => [`[Scheduled] ${nextJob.description || nextJob.id}`],
11
+ });
66
12
  }
@@ -26,22 +26,13 @@ class WeixinScheduleAdapter {
26
26
  getChannelRuntime: this.options.getChannelRuntime,
27
27
  }, (nextJob) => this.resolveThread(nextJob));
28
28
  const execution = await this.executor.execute(job, job.promptTemplate, executionPolicy);
29
- let platformMessageId = '';
30
- if (execution.replyText) {
31
- const channelRuntime = this.options.getChannelRuntime();
32
- if (!channelRuntime.sendScheduledMessage) {
33
- throw new Error('WeChat channel runtime does not support scheduled delivery.');
34
- }
35
- platformMessageId = await channelRuntime.sendScheduledMessage(job.workspaceId, (0, scheduled_job_route_js_1.routeWithPlatformInstance)((0, scheduled_job_route_js_1.withoutThreadRoute)(job.route), job.platform), execution.replyText);
36
- if (!platformMessageId) {
37
- throw new Error('WeChat gateway did not return a message id for scheduled delivery.');
38
- }
39
- }
40
29
  return {
41
30
  threadId: execution.threadId,
42
31
  runId: execution.runId,
43
32
  replyText: execution.replyText,
44
- platformMessageId: platformMessageId || undefined,
33
+ deliveryMode: 'bridge-stream',
34
+ deliveryStatus: 'succeeded',
35
+ lastBridgeEventAt: new Date().toISOString(),
45
36
  };
46
37
  }
47
38
  async resolveThread(job) {
@@ -10,6 +10,7 @@ const node_path_1 = require("node:path");
10
10
  const node_os_1 = require("node:os");
11
11
  const local_core_acp_store_js_1 = require("../../services/local-ai-core/src/acp/local-core-acp-store.js");
12
12
  const local_core_acp_session_coordinator_js_1 = require("../../services/local-ai-core/src/acp/local-core-acp-session-coordinator.js");
13
+ const local_core_acp_turn_coordinator_js_1 = require("../../services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js");
13
14
  const scheduler_service_js_1 = require("../../services/local-ai-core/src/scheduler/scheduler-service.js");
14
15
  const lark_schedule_adapter_js_1 = require("../../services/local-ai-core/src/scheduler/lark-schedule-adapter.js");
15
16
  const bootstrap_js_1 = require("../../services/local-ai-core/src/kernel/bootstrap.js");
@@ -165,10 +166,12 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
165
166
  enabled: true,
166
167
  });
167
168
  let sentThreadId = '';
168
- let deliveredRoute;
169
+ let registeredBridge;
170
+ let unregisteredBridge = false;
169
171
  const adapter = new lark_schedule_adapter_js_1.LarkScheduleAdapter({
170
172
  store,
171
173
  getWorkspaceRouter: () => ({
174
+ getThreadSessionKey: (threadId) => `session:${threadId}`,
172
175
  getThread: async (threadId) => ({
173
176
  id: threadId,
174
177
  workspaceId: 'workspace-a',
@@ -191,23 +194,29 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
191
194
  },
192
195
  }),
193
196
  getChannelRuntime: () => ({
194
- muteThreadBridge: () => { },
195
- unmuteThreadBridge: () => { },
196
- sendScheduledMessage: async (_workspaceId, route) => {
197
- deliveredRoute = route;
198
- return 'platform-message-1';
197
+ registerScheduledThreadBridge: (input) => {
198
+ registeredBridge = input;
199
+ return () => {
200
+ unregisteredBridge = true;
201
+ };
202
+ },
203
+ sendScheduledMessage: async () => {
204
+ throw new Error('scheduled Lark replies should be delivered through bridge events');
199
205
  },
200
206
  }),
201
207
  });
202
208
  const result = await adapter.execute({ job, triggeredAt: now });
203
209
  strict_1.default.equal(sentThreadId, latestThread.id);
204
210
  strict_1.default.equal(result.threadId, latestThread.id);
205
- strict_1.default.equal(result.platformMessageId, 'platform-message-1');
206
- strict_1.default.deepEqual(deliveredRoute, {
207
- type: 'channel.chat',
208
- channelId: 'chat-1',
209
- participantId: 'user-1',
211
+ strict_1.default.equal('platformMessageId' in result, false);
212
+ strict_1.default.deepEqual(registeredBridge, {
213
+ workspaceId: 'workspace-a',
214
+ platform: 'lark',
215
+ route: { type: 'channel.chat', channelId: 'chat-1', participantId: 'user-1', threadId: oldThread.id },
216
+ threadId: latestThread.id,
217
+ sessionKey: `session:${latestThread.id}`,
210
218
  });
219
+ strict_1.default.equal(unregisteredBridge, true);
211
220
  store.close();
212
221
  }
213
222
  finally {
@@ -247,12 +256,14 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
247
256
  store,
248
257
  getWorkspaceRouter: () => workspaceRouter,
249
258
  getChannelRuntime: () => ({
250
- muteThreadBridge: () => { },
251
- unmuteThreadBridge: () => { },
252
- sendScheduledMessage: async () => 'platform-message-1',
259
+ registerScheduledThreadBridge: () => () => { },
260
+ sendScheduledMessage: async () => {
261
+ throw new Error('scheduled Lark replies should be delivered through bridge events');
262
+ },
253
263
  }),
254
264
  });
255
265
  workspaceRouter = {
266
+ getThreadSessionKey: (threadId) => `session:${threadId}`,
256
267
  getThread: async (threadId) => ({
257
268
  id: threadId,
258
269
  workspaceId: 'workspace-a',
@@ -277,7 +288,7 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
277
288
  const result = await adapter.execute({ job, triggeredAt: now });
278
289
  strict_1.default.equal(sentThreadId, thread.id);
279
290
  strict_1.default.equal(result.threadId, thread.id);
280
- strict_1.default.equal(result.platformMessageId, 'platform-message-1');
291
+ strict_1.default.equal('platformMessageId' in result, false);
281
292
  store.close();
282
293
  }
283
294
  finally {
@@ -311,10 +322,11 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
311
322
  description: 'bound lark task',
312
323
  enabled: true,
313
324
  });
314
- let deliveredRoute;
325
+ let registeredBridge;
315
326
  const adapter = new lark_schedule_adapter_js_1.LarkScheduleAdapter({
316
327
  store,
317
328
  getWorkspaceRouter: () => ({
329
+ getThreadSessionKey: (threadId) => `session:${threadId}`,
318
330
  getThread: async (threadId) => ({
319
331
  id: threadId,
320
332
  workspaceId: 'workspace-a',
@@ -340,22 +352,24 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
340
352
  },
341
353
  }),
342
354
  getChannelRuntime: () => ({
343
- muteThreadBridge: () => { },
344
- unmuteThreadBridge: () => { },
345
- sendScheduledMessage: async (_workspaceId, route) => {
346
- deliveredRoute = route;
347
- return 'platform-message-1';
355
+ registerScheduledThreadBridge: (input) => {
356
+ registeredBridge = input;
357
+ return () => { };
358
+ },
359
+ sendScheduledMessage: async () => {
360
+ throw new Error('scheduled Lark replies should be delivered through bridge events');
348
361
  },
349
362
  }),
350
363
  });
351
364
  const result = await adapter.execute({ job, triggeredAt: now });
352
365
  strict_1.default.equal(result.threadId, thread.id);
353
- strict_1.default.equal(result.platformMessageId, 'platform-message-1');
354
- strict_1.default.deepEqual(deliveredRoute, {
355
- type: 'channel.chat',
356
- channelId: 'chat-1',
357
- instanceId: 'lark-1',
358
- participantId: 'user-1',
366
+ strict_1.default.equal('platformMessageId' in result, false);
367
+ strict_1.default.deepEqual(registeredBridge, {
368
+ workspaceId: 'workspace-a',
369
+ platform: 'lark:lark-1',
370
+ route: { type: 'channel.chat', channelId: 'chat-1', participantId: 'user-1' },
371
+ threadId: thread.id,
372
+ sessionKey: `session:${thread.id}`,
359
373
  });
360
374
  store.close();
361
375
  }
@@ -491,6 +505,59 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
491
505
  (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
492
506
  }
493
507
  });
508
+ (0, node_test_1.default)('ACP permission requests honor scheduled session permission override', () => {
509
+ const sentPayloads = [];
510
+ let approvalCreated = false;
511
+ let awaitingInput = false;
512
+ const coordinator = new local_core_acp_turn_coordinator_js_1.LocalCoreAcpTurnCoordinator({
513
+ emitBridge: () => { },
514
+ appendMessage: () => { },
515
+ updateRunStatus: () => {
516
+ awaitingInput = true;
517
+ },
518
+ createApprovalRequest: () => {
519
+ approvalCreated = true;
520
+ return 'approval-1';
521
+ },
522
+ getThreadAgentMode: () => 'default',
523
+ sendRaw: (_session, payload) => {
524
+ sentPayloads.push(payload);
525
+ return true;
526
+ },
527
+ });
528
+ coordinator.handleAgentRequest({
529
+ threadId: 'thread-1',
530
+ bridgeSessionKey: 'bridge-1',
531
+ currentRunId: 'run-1',
532
+ currentTurn: null,
533
+ pendingPermissionByRun: new Map(),
534
+ schedulerJobCreatedByRun: new Map(),
535
+ launchPermissionMode: 'bypassPermissions',
536
+ }, {
537
+ jsonrpc: '2.0',
538
+ id: 42,
539
+ method: 'session/request_permission',
540
+ params: {
541
+ toolCall: { title: 'Write AI早报/AI早报-2026-05-08.md' },
542
+ options: [
543
+ { optionId: 'deny', name: 'Deny', kind: 'deny' },
544
+ { optionId: 'allow', name: 'Allow', kind: 'allow' },
545
+ ],
546
+ },
547
+ });
548
+ strict_1.default.equal(approvalCreated, false);
549
+ strict_1.default.equal(awaitingInput, false);
550
+ strict_1.default.deepEqual(sentPayloads[0], {
551
+ jsonrpc: '2.0',
552
+ id: 42,
553
+ result: {
554
+ outcome: {
555
+ outcome: 'selected',
556
+ optionId: 'allow',
557
+ },
558
+ },
559
+ });
560
+ });
494
561
  (0, node_test_1.default)('scheduler uses short ids for new jobs and resolves legacy full ids by short id', () => {
495
562
  const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'scheduler-id-store-'));
496
563
  try {
@@ -537,6 +604,78 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
537
604
  (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
538
605
  }
539
606
  });
607
+ (0, node_test_1.default)('scheduler dispatches due jobs without waiting for long-running jobs', async () => {
608
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'scheduler-dispatch-'));
609
+ try {
610
+ const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
611
+ store.createScheduledJob({
612
+ workspaceId: 'workspace-a',
613
+ platform: 'local',
614
+ route: { type: 'local.thread', channelId: 'workspace-a', threadId: 'thread-1' },
615
+ executionMode: 'side-thread',
616
+ triggerType: 'cron',
617
+ cronExpr: '* * * * *',
618
+ promptTemplate: 'slow',
619
+ description: 'slow job',
620
+ enabled: true,
621
+ });
622
+ store.createScheduledJob({
623
+ workspaceId: 'workspace-a',
624
+ platform: 'local',
625
+ route: { type: 'local.thread', channelId: 'workspace-a', threadId: 'thread-2' },
626
+ executionMode: 'side-thread',
627
+ triggerType: 'cron',
628
+ cronExpr: '* * * * *',
629
+ promptTemplate: 'fast',
630
+ description: 'fast job',
631
+ enabled: true,
632
+ });
633
+ let releaseSlow;
634
+ const slowDone = new Promise((resolve) => {
635
+ releaseSlow = resolve;
636
+ });
637
+ const started = [];
638
+ let slowJobId = '';
639
+ const scheduler = new scheduler_service_js_1.SchedulerService({
640
+ store,
641
+ triggers: [{
642
+ triggerTypes: ['cron'],
643
+ supports: () => true,
644
+ isDue: () => true,
645
+ }],
646
+ executors: [{
647
+ deliveryTargets: ['local'],
648
+ supports: () => true,
649
+ execute: async ({ job }) => {
650
+ const isFirstStartedJob = started.length === 0;
651
+ started.push(job.id);
652
+ if (isFirstStartedJob) {
653
+ slowJobId = job.id;
654
+ await slowDone;
655
+ }
656
+ return {
657
+ threadId: String(job.route.threadId || job.id),
658
+ runId: `run:${job.id}`,
659
+ };
660
+ },
661
+ }],
662
+ eventBus: { emit: () => { }, on: () => () => { } },
663
+ });
664
+ await scheduler.tick();
665
+ await new Promise((resolve) => setImmediate(resolve));
666
+ strict_1.default.equal(started.length, 2);
667
+ const fastJobId = started.find((id) => id !== slowJobId);
668
+ strict_1.default.equal(store.listScheduledJobRuns(slowJobId)[0]?.status, 'running');
669
+ strict_1.default.equal(store.listScheduledJobRuns(fastJobId)[0]?.status, 'succeeded');
670
+ releaseSlow();
671
+ await new Promise((resolve) => setImmediate(resolve));
672
+ strict_1.default.equal(store.listScheduledJobRuns(slowJobId)[0]?.status, 'succeeded');
673
+ store.close();
674
+ }
675
+ finally {
676
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
677
+ }
678
+ });
540
679
  (0, node_test_1.default)('scheduled jobs normalize enum-like input before persistence', () => {
541
680
  const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'scheduler-enum-store-'));
542
681
  try {
@@ -633,10 +772,21 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
633
772
  description: 'daily ping',
634
773
  enabled: true,
635
774
  });
636
- const run = store.createScheduledJobRun(job.id, 'complete');
775
+ const run = store.createScheduledJobRun(job.id, 'complete', {
776
+ deliveryMode: 'bridge-stream',
777
+ deliveryStatus: 'streaming',
778
+ platformMessageIds: ['msg-1', 'msg-2'],
779
+ lastBridgeEventAt: '2026-04-22T06:00:03.000Z',
780
+ });
637
781
  strict_1.default.equal(run.status, 'succeeded');
782
+ strict_1.default.equal(run.deliveryMode, 'bridge-stream');
783
+ strict_1.default.equal(run.deliveryStatus, 'streaming');
784
+ strict_1.default.deepEqual(run.platformMessageIds, ['msg-1', 'msg-2']);
785
+ strict_1.default.equal(run.lastBridgeEventAt, '2026-04-22T06:00:03.000Z');
638
786
  const skipped = store.updateScheduledJobRun(run.id, { status: 'cancelled' });
639
787
  strict_1.default.equal(skipped.status, 'skipped');
788
+ strict_1.default.equal(skipped.deliveryMode, 'bridge-stream');
789
+ strict_1.default.deepEqual(skipped.platformMessageIds, ['msg-1', 'msg-2']);
640
790
  strict_1.default.equal(store.getScheduledJob(job.id)?.lastStatus, 'skipped');
641
791
  store.close();
642
792
  }