@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.
- package/README.md +5 -0
- package/dist/renderer/assets/{Badge-DUa4J-09.js → Badge-CjzZp9Tf.js} +1 -1
- package/dist/renderer/assets/{Chat-B1FWuTWV.js → Chat-D4_5K_Ww.js} +1 -1
- package/dist/renderer/assets/{ChatMarkdown-DqOgFSgI.js → ChatMarkdown-DkI29dOw.js} +1 -1
- package/dist/renderer/assets/{Config-CM5bqUrV.js → Config-CnrlmSEQ.js} +1 -1
- package/dist/renderer/assets/{CronList-CfXE7S0M.js → CronList-DVvInMvr.js} +1 -1
- package/dist/renderer/assets/{Dashboard-D5Dwbotu.js → Dashboard-CY4YAmVj.js} +2 -2
- package/dist/renderer/assets/{EmptyState-Bs_6ISc0.js → EmptyState-CmR947i4.js} +1 -1
- package/dist/renderer/assets/{KnowledgeDetail-0kdWnKyy.js → KnowledgeDetail-BvNG0inb.js} +1 -1
- package/dist/renderer/assets/{KnowledgeHome-Bg8xbRe2.js → KnowledgeHome-BB0Z8VrS.js} +1 -1
- package/dist/renderer/assets/{Logs-BfgvW7qZ.js → Logs-IORTjnGM.js} +1 -1
- package/dist/renderer/assets/{Modal-BkuKCDDx.js → Modal-D9NZ6cvF.js} +1 -1
- package/dist/renderer/assets/{Page-ddaAS_jP.js → Page-DlvElfTz.js} +1 -1
- package/dist/renderer/assets/{ProjectDetail-C9x-Ib5p.js → ProjectDetail-Y1ZFL8W9.js} +1 -1
- package/dist/renderer/assets/{ProjectList-XIIikxrj.js → ProjectList-DAI-4HU7.js} +1 -1
- package/dist/renderer/assets/{Select-BBL_5aa5.js → Select-LMEt9yJy.js} +1 -1
- package/dist/renderer/assets/{SessionChat-BFsTM0c7.js → SessionChat-BWRAS_H-.js} +1 -1
- package/dist/renderer/assets/{SessionList-DWh6n10K.js → SessionList-DQsxmC1n.js} +1 -1
- package/dist/renderer/assets/{ThreadChat-CzPiDA92.js → ThreadChat-D9j78cdm.js} +1 -1
- package/dist/renderer/assets/{Workspace-BW8v3Nia.js → Workspace-C-RTj4uI.js} +1 -1
- package/dist/renderer/assets/{arrow-left-CcoHvwbx.js → arrow-left-C_ZIHUlD.js} +1 -1
- package/dist/renderer/assets/{book-open-RweyWxPq.js → book-open-BuI3webp.js} +1 -1
- package/dist/renderer/assets/{check-B5YOrBNT.js → check-FA1rU94Q.js} +1 -1
- package/dist/renderer/assets/{chevron-down-HjaPrxdd.js → chevron-down-DvQ7Hq2s.js} +1 -1
- package/dist/renderer/assets/{circle-C-aaO7LR.js → circle-DZYvZ-Iq.js} +1 -1
- package/dist/renderer/assets/{desktop-DUWkNnbA.js → desktop-BSoKBjFu.js} +1 -1
- package/dist/renderer/assets/{funnel-IWZmi-_V.js → funnel-BoW8A8Fi.js} +1 -1
- package/dist/renderer/assets/{heart-DM5_F2nX.js → heart-DaIdiPUs.js} +1 -1
- package/dist/renderer/assets/{index-Bin2xyBS.js → index-Cc83IKng.js} +2 -2
- package/dist/renderer/assets/{index-DvIUdsAm.js → index-pdvxKnBr.js} +1 -1
- package/dist/renderer/assets/{pencil-DcxwCYPi.js → pencil-CMs10l_8.js} +1 -1
- package/dist/renderer/assets/{play-DUdnnxLC.js → play-DtT_FrBZ.js} +1 -1
- package/dist/renderer/assets/{plug-B-rDbk24.js → plug-vKGMXapD.js} +1 -1
- package/dist/renderer/assets/{plus-DslMGvrO.js → plus-DZV73wzr.js} +1 -1
- package/dist/renderer/assets/projects-BUda9SpJ.js +1 -0
- package/dist/renderer/assets/{save-DlkR3jLs.js → save-Bus9w20l.js} +1 -1
- package/dist/renderer/assets/{search-ULTp3p_0.js → search-ChEC20LW.js} +1 -1
- package/dist/renderer/assets/{server-DBdiKfv9.js → server-B9HgN6CF.js} +1 -1
- package/dist/renderer/assets/{session-utils-BPyVVONo.js → session-utils-Bn9-xCic.js} +1 -1
- package/dist/renderer/assets/{sessions-B0NecMYO.js → sessions-RUHmhxT5.js} +1 -1
- package/dist/renderer/assets/{shield-check-BNSk8nza.js → shield-check-B97md1a5.js} +1 -1
- package/dist/renderer/assets/{trash-2-CGydl763.js → trash-2-31WOGl8I.js} +1 -1
- package/dist/renderer/assets/{user-D1xkQo35.js → user-CLiiHYE_.js} +1 -1
- package/dist/renderer/assets/{wifi-off-Iy1XPeZ3.js → wifi-off-BKh21npc.js} +1 -1
- package/dist/renderer/index.html +1 -1
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +22 -7
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +2 -1
- package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +37 -7
- package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +31 -1
- package/dist-electron/services/local-ai-core/src/scheduler/channel-execution-policy.js +79 -0
- package/dist-electron/services/local-ai-core/src/scheduler/lark-execution-policies.js +7 -61
- package/dist-electron/services/local-ai-core/src/scheduler/lark-schedule-adapter.js +3 -12
- package/dist-electron/services/local-ai-core/src/scheduler/local-schedule-adapter.js +23 -30
- package/dist-electron/services/local-ai-core/src/scheduler/scheduled-bridge-session.js +27 -0
- package/dist-electron/services/local-ai-core/src/scheduler/scheduler-run-lifecycle.js +13 -1
- package/dist-electron/services/local-ai-core/src/scheduler/scheduler-service.js +1 -1
- package/dist-electron/services/local-ai-core/src/scheduler/weixin-execution-policies.js +7 -61
- package/dist-electron/services/local-ai-core/src/scheduler/weixin-schedule-adapter.js +3 -12
- package/dist-electron/tests/electron/workspace-task-store.test.js +178 -28
- package/dist-electron/tests/integration/local-core-refactor.test.js +58 -5
- package/package.json +1 -1
- 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
|
|
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
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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', {
|
|
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);
|
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
206
|
-
strict_1.default.deepEqual(
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
|
354
|
-
strict_1.default.deepEqual(
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
}
|