@kafca/agentdock 0.1.35 → 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 +7 -0
- package/dist/renderer/assets/{Badge-BM4WCcua.js → Badge-CjzZp9Tf.js} +1 -1
- package/dist/renderer/assets/{Chat-DoyBHOsx.js → Chat-D4_5K_Ww.js} +1 -1
- package/dist/renderer/assets/{ChatMarkdown-BWj04kgW.js → ChatMarkdown-DkI29dOw.js} +1 -1
- package/dist/renderer/assets/{Config-A95OuazQ.js → Config-CnrlmSEQ.js} +1 -1
- package/dist/renderer/assets/{CronList-DsPU4zGT.js → CronList-DVvInMvr.js} +1 -1
- package/dist/renderer/assets/{Dashboard-D8Vpj97x.js → Dashboard-CY4YAmVj.js} +2 -2
- package/dist/renderer/assets/{EmptyState-x1uVoSxZ.js → EmptyState-CmR947i4.js} +1 -1
- package/dist/renderer/assets/{KnowledgeDetail-C9TbcyNJ.js → KnowledgeDetail-BvNG0inb.js} +1 -1
- package/dist/renderer/assets/{KnowledgeHome-Cg9Fb41Q.js → KnowledgeHome-BB0Z8VrS.js} +1 -1
- package/dist/renderer/assets/{Logs-oWaKA60u.js → Logs-IORTjnGM.js} +1 -1
- package/dist/renderer/assets/{Modal-24hTXXHK.js → Modal-D9NZ6cvF.js} +1 -1
- package/dist/renderer/assets/{Page-YRtQonKZ.js → Page-DlvElfTz.js} +1 -1
- package/dist/renderer/assets/{ProjectDetail-DScsxJO-.js → ProjectDetail-Y1ZFL8W9.js} +1 -1
- package/dist/renderer/assets/{ProjectList-8L5GD7PN.js → ProjectList-DAI-4HU7.js} +1 -1
- package/dist/renderer/assets/{Select-BI8Uw_DF.js → Select-LMEt9yJy.js} +1 -1
- package/dist/renderer/assets/{SessionChat-BIoCWdDD.js → SessionChat-BWRAS_H-.js} +1 -1
- package/dist/renderer/assets/{SessionList-BsEJeDxI.js → SessionList-DQsxmC1n.js} +1 -1
- package/dist/renderer/assets/{ThreadChat-DJi0UBta.js → ThreadChat-D9j78cdm.js} +1 -1
- package/dist/renderer/assets/{Workspace-tlJxrgC5.js → Workspace-C-RTj4uI.js} +1 -1
- package/dist/renderer/assets/{arrow-left-pzaBhi9y.js → arrow-left-C_ZIHUlD.js} +1 -1
- package/dist/renderer/assets/{book-open-CNhlVyqh.js → book-open-BuI3webp.js} +1 -1
- package/dist/renderer/assets/{check-Cg2LLCdl.js → check-FA1rU94Q.js} +1 -1
- package/dist/renderer/assets/{chevron-down-Dgqg07wf.js → chevron-down-DvQ7Hq2s.js} +1 -1
- package/dist/renderer/assets/{circle-BBd_jIS2.js → circle-DZYvZ-Iq.js} +1 -1
- package/dist/renderer/assets/{desktop-C0S-Yv8G.js → desktop-BSoKBjFu.js} +1 -1
- package/dist/renderer/assets/{funnel-DwSh-SrX.js → funnel-BoW8A8Fi.js} +1 -1
- package/dist/renderer/assets/{heart-CWVc-KhZ.js → heart-DaIdiPUs.js} +1 -1
- package/dist/renderer/assets/{index-DZbeAYVk.js → index-Cc83IKng.js} +2 -2
- package/dist/renderer/assets/{index-HYmDNQ9k.js → index-pdvxKnBr.js} +1 -1
- package/dist/renderer/assets/{pencil-tI4tCLz0.js → pencil-CMs10l_8.js} +1 -1
- package/dist/renderer/assets/{play-Coi2HA1w.js → play-DtT_FrBZ.js} +1 -1
- package/dist/renderer/assets/{plug-D4TCyW9b.js → plug-vKGMXapD.js} +1 -1
- package/dist/renderer/assets/{plus-p62AgpoB.js → plus-DZV73wzr.js} +1 -1
- package/dist/renderer/assets/projects-BUda9SpJ.js +1 -0
- package/dist/renderer/assets/{save-fpMhkVV0.js → save-Bus9w20l.js} +1 -1
- package/dist/renderer/assets/{search-BHKIStdA.js → search-ChEC20LW.js} +1 -1
- package/dist/renderer/assets/{server-0oTWed8U.js → server-B9HgN6CF.js} +1 -1
- package/dist/renderer/assets/{session-utils-D0Qv0I0-.js → session-utils-Bn9-xCic.js} +1 -1
- package/dist/renderer/assets/{sessions-C8ApKQgC.js → sessions-RUHmhxT5.js} +1 -1
- package/dist/renderer/assets/{shield-check-BAJI9jzC.js → shield-check-B97md1a5.js} +1 -1
- package/dist/renderer/assets/{trash-2-B-Ygm-x6.js → trash-2-31WOGl8I.js} +1 -1
- package/dist/renderer/assets/{user-Cv8rzw8l.js → user-CLiiHYE_.js} +1 -1
- package/dist/renderer/assets/{wifi-off-BImS3nkm.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 +4 -13
- 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/scheduled-conversation-executor.js +3 -2
- 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 +4 -13
- package/dist-electron/tests/electron/workspace-task-store.test.js +244 -24
- package/dist-electron/tests/integration/local-core-refactor.test.js +60 -7
- package/package.json +1 -1
- package/dist/renderer/assets/projects-CTDuVxeQ.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;
|
package/dist-electron/services/local-ai-core/src/scheduler/scheduled-conversation-executor.js
CHANGED
|
@@ -13,12 +13,13 @@ class ScheduledConversationExecutor {
|
|
|
13
13
|
const target = await policy.resolveTarget(job);
|
|
14
14
|
await policy.beforeExecute?.(target, job);
|
|
15
15
|
try {
|
|
16
|
-
const
|
|
16
|
+
const workspaceRouter = this.options.getWorkspaceRouter();
|
|
17
|
+
const sendResult = await workspaceRouter.sendThreadMessage(target.threadId, prompt, {
|
|
17
18
|
permissionMode: SCHEDULED_RUN_PERMISSION_MODE,
|
|
18
19
|
runtimeEnv: this.buildScheduledRuntimeEnv(target),
|
|
19
20
|
});
|
|
20
21
|
await this.waitForRun(sendResult.runId, timeoutMs);
|
|
21
|
-
const thread = await
|
|
22
|
+
const thread = await workspaceRouter.getThread(target.threadId);
|
|
22
23
|
const replyText = [...thread.messages]
|
|
23
24
|
.reverse()
|
|
24
25
|
.find((message) => message.role === 'assistant' && message.kind === 'final')
|
|
@@ -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
|
}
|
|
@@ -12,7 +12,7 @@ class WeixinScheduleAdapter {
|
|
|
12
12
|
this.options = options;
|
|
13
13
|
this.executor = new scheduled_conversation_executor_js_1.ScheduledConversationExecutor({
|
|
14
14
|
store: options.store,
|
|
15
|
-
|
|
15
|
+
getWorkspaceRouter: options.getWorkspaceRouter,
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
supports(job) {
|
|
@@ -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,101 @@ 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);
|
|
220
|
+
store.close();
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
(0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
(0, node_test_1.default)('Lark scheduled execution resolves workspace router at execution time', async () => {
|
|
227
|
+
const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'scheduler-lark-lazy-router-'));
|
|
228
|
+
try {
|
|
229
|
+
const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
|
|
230
|
+
const thread = store.createThread('workspace-a', 'Lark thread');
|
|
231
|
+
const now = new Date().toISOString();
|
|
232
|
+
store.upsertPlatformThreadBinding({
|
|
233
|
+
workspace_id: 'workspace-a',
|
|
234
|
+
platform: 'lark:lark-1',
|
|
235
|
+
chat_id: 'chat-1',
|
|
236
|
+
platform_user_id: 'user-1',
|
|
237
|
+
thread_id: thread.id,
|
|
238
|
+
last_platform_message_id: null,
|
|
239
|
+
created_at: now,
|
|
240
|
+
updated_at: now,
|
|
241
|
+
});
|
|
242
|
+
const job = store.createScheduledJob({
|
|
243
|
+
workspaceId: 'workspace-a',
|
|
244
|
+
platform: 'lark:lark-1',
|
|
245
|
+
route: { type: 'channel.chat', channelId: 'chat-1', participantId: 'user-1' },
|
|
246
|
+
executionMode: 'same-thread',
|
|
247
|
+
triggerType: 'cron',
|
|
248
|
+
cronExpr: '30 18 * * *',
|
|
249
|
+
promptTemplate: 'ping',
|
|
250
|
+
description: 'bound lark task',
|
|
251
|
+
enabled: true,
|
|
252
|
+
});
|
|
253
|
+
let sentThreadId = '';
|
|
254
|
+
let workspaceRouter;
|
|
255
|
+
const adapter = new lark_schedule_adapter_js_1.LarkScheduleAdapter({
|
|
256
|
+
store,
|
|
257
|
+
getWorkspaceRouter: () => workspaceRouter,
|
|
258
|
+
getChannelRuntime: () => ({
|
|
259
|
+
registerScheduledThreadBridge: () => () => { },
|
|
260
|
+
sendScheduledMessage: async () => {
|
|
261
|
+
throw new Error('scheduled Lark replies should be delivered through bridge events');
|
|
262
|
+
},
|
|
263
|
+
}),
|
|
264
|
+
});
|
|
265
|
+
workspaceRouter = {
|
|
266
|
+
getThreadSessionKey: (threadId) => `session:${threadId}`,
|
|
267
|
+
getThread: async (threadId) => ({
|
|
268
|
+
id: threadId,
|
|
269
|
+
workspaceId: 'workspace-a',
|
|
270
|
+
title: 'Thread',
|
|
271
|
+
live: false,
|
|
272
|
+
updatedAt: now,
|
|
273
|
+
createdAt: now,
|
|
274
|
+
historyCount: 1,
|
|
275
|
+
excerpt: '',
|
|
276
|
+
bridgeSessionKey: '',
|
|
277
|
+
agentType: 'localcore-acp',
|
|
278
|
+
selectedKnowledgeBaseIds: [],
|
|
279
|
+
pendingPermissionRequest: null,
|
|
280
|
+
messages: [{ id: 'message-1', role: 'assistant', kind: 'final', content: 'pong', timestamp: now }],
|
|
281
|
+
}),
|
|
282
|
+
sendThreadMessage: async (threadId) => {
|
|
283
|
+
sentThreadId = threadId;
|
|
284
|
+
store.updateRun('run-1', threadId, 'completed');
|
|
285
|
+
return { runId: 'run-1' };
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
const result = await adapter.execute({ job, triggeredAt: now });
|
|
289
|
+
strict_1.default.equal(sentThreadId, thread.id);
|
|
290
|
+
strict_1.default.equal(result.threadId, thread.id);
|
|
291
|
+
strict_1.default.equal('platformMessageId' in result, false);
|
|
211
292
|
store.close();
|
|
212
293
|
}
|
|
213
294
|
finally {
|
|
@@ -241,10 +322,11 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
|
|
|
241
322
|
description: 'bound lark task',
|
|
242
323
|
enabled: true,
|
|
243
324
|
});
|
|
244
|
-
let
|
|
325
|
+
let registeredBridge;
|
|
245
326
|
const adapter = new lark_schedule_adapter_js_1.LarkScheduleAdapter({
|
|
246
327
|
store,
|
|
247
328
|
getWorkspaceRouter: () => ({
|
|
329
|
+
getThreadSessionKey: (threadId) => `session:${threadId}`,
|
|
248
330
|
getThread: async (threadId) => ({
|
|
249
331
|
id: threadId,
|
|
250
332
|
workspaceId: 'workspace-a',
|
|
@@ -270,22 +352,24 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
|
|
|
270
352
|
},
|
|
271
353
|
}),
|
|
272
354
|
getChannelRuntime: () => ({
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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');
|
|
278
361
|
},
|
|
279
362
|
}),
|
|
280
363
|
});
|
|
281
364
|
const result = await adapter.execute({ job, triggeredAt: now });
|
|
282
365
|
strict_1.default.equal(result.threadId, thread.id);
|
|
283
|
-
strict_1.default.equal(result
|
|
284
|
-
strict_1.default.deepEqual(
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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}`,
|
|
289
373
|
});
|
|
290
374
|
store.close();
|
|
291
375
|
}
|
|
@@ -421,6 +505,59 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
|
|
|
421
505
|
(0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
|
|
422
506
|
}
|
|
423
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
|
+
});
|
|
424
561
|
(0, node_test_1.default)('scheduler uses short ids for new jobs and resolves legacy full ids by short id', () => {
|
|
425
562
|
const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'scheduler-id-store-'));
|
|
426
563
|
try {
|
|
@@ -467,6 +604,78 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
|
|
|
467
604
|
(0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
|
|
468
605
|
}
|
|
469
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
|
+
});
|
|
470
679
|
(0, node_test_1.default)('scheduled jobs normalize enum-like input before persistence', () => {
|
|
471
680
|
const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'scheduler-enum-store-'));
|
|
472
681
|
try {
|
|
@@ -563,10 +772,21 @@ const local_core_controller_js_1 = require("../../services/local-ai-core/src/run
|
|
|
563
772
|
description: 'daily ping',
|
|
564
773
|
enabled: true,
|
|
565
774
|
});
|
|
566
|
-
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
|
+
});
|
|
567
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');
|
|
568
786
|
const skipped = store.updateScheduledJobRun(run.id, { status: 'cancelled' });
|
|
569
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']);
|
|
570
790
|
strict_1.default.equal(store.getScheduledJob(job.id)?.lastStatus, 'skipped');
|
|
571
791
|
store.close();
|
|
572
792
|
}
|