@kafca/agentdock 0.1.63 → 0.1.65
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 +4 -0
- package/dist/renderer/assets/{AutomationList-DR-micIT.js → AutomationList-fCves7JJ.js} +1 -1
- package/dist/renderer/assets/{Badge-COE1Zm2i.js → Badge-03CgzC3O.js} +1 -1
- package/dist/renderer/assets/{Card-F6UNFxnQ.js → Card-BG4attR1.js} +1 -1
- package/dist/renderer/assets/{Config-BKqxfhZi.js → Config-DLmfH4Fs.js} +1 -1
- package/dist/renderer/assets/{Dashboard-Bvp7-_2z.js → Dashboard-tmYu-275.js} +2 -2
- package/dist/renderer/assets/{EmptyState-BwV_J0Og.js → EmptyState-D3nq3XaN.js} +1 -1
- package/dist/renderer/assets/{HighlightedMarkdown-CBIBaWyM.js → HighlightedMarkdown-DFiXk_8l.js} +1 -1
- package/dist/renderer/assets/{Input-C8LZd4Bi.js → Input-BuQ3SusB.js} +1 -1
- package/dist/renderer/assets/{KnowledgeDetail-DjXYNMIs.js → KnowledgeDetail-2k6D-FBu.js} +1 -1
- package/dist/renderer/assets/{KnowledgeHome-Cqk5_nJh.js → KnowledgeHome-Bv3_riqS.js} +1 -1
- package/dist/renderer/assets/{Logs-B6cXFvW4.js → Logs-DdDCHMXT.js} +1 -1
- package/dist/renderer/assets/{Modal-CAj7soll.js → Modal-BOpQurFi.js} +1 -1
- package/dist/renderer/assets/{Page-DlnMnu4F.js → Page-DAZBFNqx.js} +1 -1
- package/dist/renderer/assets/{Select-CVay_LUX.js → Select-CjT5-m7R.js} +1 -1
- package/dist/renderer/assets/{ThreadChat-BxXRpYa1.js → ThreadChat-Dd1d35z9.js} +3 -3
- package/dist/renderer/assets/{Workspace-YC0pC7cM.js → Workspace-CMHdpXa7.js} +1 -1
- package/dist/renderer/assets/{arrow-left-Cwm09dxx.js → arrow-left-DTisCSrr.js} +1 -1
- package/dist/renderer/assets/{book-open-BwcGC_im.js → book-open-DGOtHxDK.js} +1 -1
- package/dist/renderer/assets/{channels-BPytmVCd.js → channels-DTwVsSq8.js} +1 -1
- package/dist/renderer/assets/{chevron-down-2LP9oGHG.js → chevron-down-Dw5IfnsR.js} +1 -1
- package/dist/renderer/assets/{index-llCPCiYk.js → index-CDPdiGc6.js} +1 -1
- package/dist/renderer/assets/{index-eoVERL5K.js → index-jESlfFTY.js} +2 -2
- package/dist/renderer/assets/{knowledge-QujAv-Zh.js → knowledge-DnebW2tM.js} +1 -1
- package/dist/renderer/assets/{pencil-0P2INETc.js → pencil-BBgjg0RK.js} +1 -1
- package/dist/renderer/assets/{plus-tiGRgE2v.js → plus-C5OXMCrc.js} +1 -1
- package/dist/renderer/assets/{save-D8U93DNa.js → save-B9-o4ceK.js} +1 -1
- package/dist/renderer/assets/{search-C-9J9J3f.js → search-CetWQ9yl.js} +1 -1
- package/dist/renderer/assets/{shield-check-BCKkPPTJ.js → shield-check-Dts9cJZL.js} +1 -1
- package/dist/renderer/assets/{threads-Bsrwwnpm.js → threads-Co4JiiXY.js} +1 -1
- package/dist/renderer/assets/{trash-2-CP4sn15n.js → trash-2-CcjYdBI8.js} +1 -1
- package/dist/renderer/index.html +1 -1
- package/dist-electron/services/local-ai-core/src/acp/store/automation-store.js +69 -3
- package/dist-electron/services/local-ai-core/src/acp/store/local-core-acp-store.js +14 -2
- package/dist-electron/services/local-ai-core/src/acp/store/runtime-config-store.js +20 -6
- package/dist-electron/services/local-ai-core/src/automation/automation-monitor-service.js +5 -3
- package/dist-electron/services/local-ai-core/src/automation/automation-service.js +13 -4
- package/dist-electron/services/local-ai-core/src/automation/automation-trigger-engine.js +19 -18
- package/dist-electron/services/local-ai-core/src/automation/legacy-automation-mappers.js +25 -1
- package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +15 -0
- package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +17 -3
- package/dist-electron/services/local-ai-core/src/scheduler/cron.js +197 -0
- package/dist-electron/services/local-ai-core/src/scheduler/scheduled-job-application-service.js +4 -3
- package/dist-electron/services/local-ai-core/src/scheduler/scheduler-service.js +3 -3
- package/dist-electron/tests/electron/automation-compatibility.test.js +6 -1
- package/dist-electron/tests/electron/automation-engine.test.js +67 -3
- package/dist-electron/tests/electron/automation-store.test.js +189 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{z as n,Z as l}from"./index-
|
|
1
|
+
import{z as n,Z as l}from"./index-jESlfFTY.js";function c(){return n("GET","/knowledge/config")}function f(){return n("GET","/knowledge/folders")}function g(e){return n("POST","/knowledge/folders",e)}function i(e,o){return n("PATCH",`/knowledge/folders/${encodeURIComponent(e)}`,o)}function w(e){return n("DELETE",`/knowledge/folders/${encodeURIComponent(e)}`)}function u(){return n("GET","/knowledge/bases")}function p(e){return n("POST","/knowledge/bases",e)}function E(e){return n("GET",`/knowledge/bases/${encodeURIComponent(e)}`)}function k(e,o){return n("PATCH",`/knowledge/bases/${encodeURIComponent(e)}`,o)}function m(e){return n("DELETE",`/knowledge/bases/${encodeURIComponent(e)}`)}function C(e){return n("GET",`/knowledge/bases/${encodeURIComponent(e)}/files`)}async function K(e,o){const s=new FormData;s.append("collection",o.collection),s.append("knowledgebase_id",e),o.folder&&s.append("folder",o.folder),o.files.forEach(a=>{s.append("files",a,a.name)});const d=await fetch(`${l}/knowledge/bases/${encodeURIComponent(e)}/files`,{method:"POST",body:s}),t=await d.json();if(!d.ok||!t.ok)throw new Error(t.error||`Local AI Core upload failed: ${d.status}`);return t.data}function T(e,o){return n("DELETE",`/knowledge/bases/${encodeURIComponent(e)}/files/${encodeURIComponent(o)}`)}function b(e,o){return n("POST",`/knowledge/bases/${encodeURIComponent(e)}/search`,o)}export{f as a,p as b,g as c,w as d,k as e,m as f,c as g,E as h,C as i,K as j,T as k,u as l,b as s,i as u};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{z as t,D as r}from"./index-
|
|
1
|
+
import{z as t,D as r}from"./index-jESlfFTY.js";function s(){return t("GET","/workspaces")}function a(e){return t("GET",`/threads${r({workspace_id:e})}`)}function d(e,n){return t("POST","/threads",{workspaceId:e,title:n})}function u(e){return t("GET",`/threads/${encodeURIComponent(e)}`)}function c(e,n){return t("PATCH",`/threads/${encodeURIComponent(e)}`,{title:n})}function i(e,n){return t("PATCH",`/threads/${encodeURIComponent(e)}/knowledge-bases`,{knowledgeBaseIds:n})}function T(e,n){return t("PATCH",`/threads/${encodeURIComponent(e)}/mode`,{mode:n})}function p(e){return t("DELETE",`/threads/${encodeURIComponent(e)}`)}function h(e,n){return t("POST",`/threads/${encodeURIComponent(e)}/messages`,{content:n})}function m(e,n){return t("POST",`/threads/${encodeURIComponent(e)}/actions`,{content:n})}function f(e){return t("POST",`/runs/${encodeURIComponent(e)}/interrupt`)}export{a,i as b,d as c,h as d,p as e,u as g,f as i,s as l,c as r,m as s,T as u};
|
package/dist/renderer/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="./favicon.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>AgentDock</title>
|
|
8
|
-
<script type="module" crossorigin src="./assets/index-
|
|
8
|
+
<script type="module" crossorigin src="./assets/index-jESlfFTY.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="./assets/index-B6WcpkJF.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body class="bg-[#f5f5f7] dark:bg-[#151516]">
|
|
@@ -17,13 +17,23 @@ class LocalAutomationStore {
|
|
|
17
17
|
constructor(db) {
|
|
18
18
|
this.db = db;
|
|
19
19
|
}
|
|
20
|
-
list(workspaceId) {
|
|
20
|
+
list(workspaceId, originKind) {
|
|
21
|
+
const conditions = [];
|
|
22
|
+
const params = [];
|
|
23
|
+
if (workspaceId) {
|
|
24
|
+
conditions.push('workspace_id = ?');
|
|
25
|
+
params.push(workspaceId);
|
|
26
|
+
}
|
|
27
|
+
if (originKind) {
|
|
28
|
+
conditions.push('origin_kind = ?');
|
|
29
|
+
params.push(originKind);
|
|
30
|
+
}
|
|
21
31
|
const rows = this.db.prepare(`
|
|
22
32
|
SELECT ${AUTOMATION_COLUMNS}
|
|
23
33
|
FROM automations
|
|
24
|
-
${
|
|
34
|
+
${conditions.length ? `WHERE ${conditions.join(' AND ')}` : ''}
|
|
25
35
|
ORDER BY updated_at DESC
|
|
26
|
-
`).all(...
|
|
36
|
+
`).all(...params);
|
|
27
37
|
return rows.map((row) => this.toDefinition(row));
|
|
28
38
|
}
|
|
29
39
|
get(id) {
|
|
@@ -232,6 +242,57 @@ class LocalAutomationStore {
|
|
|
232
242
|
`).all(automationId);
|
|
233
243
|
return rows.map((row) => this.toRun(row));
|
|
234
244
|
}
|
|
245
|
+
listLatestFinishedEvaluationByOrigin(originKind, workspaceId) {
|
|
246
|
+
return this.listLatestEvaluationByOrigin(originKind, workspaceId, '');
|
|
247
|
+
}
|
|
248
|
+
listLatestEvaluationWithStateByOrigin(originKind, workspaceId) {
|
|
249
|
+
return this.listLatestEvaluationByOrigin(originKind, workspaceId, "AND json_type(e.evaluation_json, '$.nextState') = 'object'");
|
|
250
|
+
}
|
|
251
|
+
listLatestEvaluationByOrigin(originKind, workspaceId, extraPredicate) {
|
|
252
|
+
const rows = this.db.prepare(`
|
|
253
|
+
WITH ranked AS (
|
|
254
|
+
SELECT
|
|
255
|
+
e.id, e.automation_id, e.status, e.activation_kind, e.script_version_id,
|
|
256
|
+
e.started_at, e.finished_at, e.evaluation_json,
|
|
257
|
+
ROW_NUMBER() OVER (PARTITION BY e.automation_id ORDER BY e.started_at DESC, e.id DESC) AS position
|
|
258
|
+
FROM automation_evaluations e
|
|
259
|
+
JOIN automations a ON a.id = e.automation_id
|
|
260
|
+
WHERE e.status = 'finished'
|
|
261
|
+
${extraPredicate}
|
|
262
|
+
AND a.origin_kind = ?${workspaceId ? ' AND a.workspace_id = ?' : ''}
|
|
263
|
+
)
|
|
264
|
+
SELECT ${EVALUATION_COLUMNS}
|
|
265
|
+
FROM ranked
|
|
266
|
+
WHERE position = 1
|
|
267
|
+
`).all(...(workspaceId ? [originKind, workspaceId] : [originKind]));
|
|
268
|
+
const result = new Map();
|
|
269
|
+
for (const row of rows) {
|
|
270
|
+
const evaluation = this.toEvaluation(row);
|
|
271
|
+
result.set(evaluation.automationId, evaluation);
|
|
272
|
+
}
|
|
273
|
+
return result;
|
|
274
|
+
}
|
|
275
|
+
listLatestRunByOrigin(originKind, workspaceId) {
|
|
276
|
+
const rows = this.db.prepare(`
|
|
277
|
+
WITH ranked AS (
|
|
278
|
+
SELECT
|
|
279
|
+
r.id, r.automation_id, r.evaluation_id, r.status, r.created_at, r.run_json,
|
|
280
|
+
ROW_NUMBER() OVER (PARTITION BY r.automation_id ORDER BY r.created_at DESC, r.id DESC) AS position
|
|
281
|
+
FROM automation_runs r
|
|
282
|
+
JOIN automations a ON a.id = r.automation_id
|
|
283
|
+
WHERE a.origin_kind = ?${workspaceId ? ' AND a.workspace_id = ?' : ''}
|
|
284
|
+
)
|
|
285
|
+
SELECT ${RUN_COLUMNS}
|
|
286
|
+
FROM ranked
|
|
287
|
+
WHERE position = 1
|
|
288
|
+
`).all(...(workspaceId ? [originKind, workspaceId] : [originKind]));
|
|
289
|
+
const result = new Map();
|
|
290
|
+
for (const row of rows) {
|
|
291
|
+
const run = this.toRun(row);
|
|
292
|
+
result.set(run.automationId, run);
|
|
293
|
+
}
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
235
296
|
reconcileInterruptedRuns(reason, finishedAt) {
|
|
236
297
|
const interruptedAt = assertIsoTimestamp(finishedAt, 'Automation interrupted run finishedAt');
|
|
237
298
|
const rows = this.db.prepare(`
|
|
@@ -415,6 +476,11 @@ class LocalAutomationStore {
|
|
|
415
476
|
const row = this.db.prepare('SELECT next_check_at FROM automations WHERE id = ?').get(id);
|
|
416
477
|
return row?.next_check_at ?? null;
|
|
417
478
|
}
|
|
479
|
+
listDueAutomationIds(now) {
|
|
480
|
+
const rows = this.db.prepare(`SELECT id FROM automations
|
|
481
|
+
WHERE enabled = 1 AND health = 'healthy' AND next_check_at IS NOT NULL AND next_check_at <= ?`).all(now.toISOString());
|
|
482
|
+
return new Set(rows.map((row) => row.id));
|
|
483
|
+
}
|
|
418
484
|
toDefinition(row) {
|
|
419
485
|
try {
|
|
420
486
|
return normalizeDefinition({
|
|
@@ -246,8 +246,8 @@ class LocalCoreAcpStore {
|
|
|
246
246
|
updateAutomationMonitorRun(runId, input) {
|
|
247
247
|
return this.automationMonitors.updateRun(runId, input);
|
|
248
248
|
}
|
|
249
|
-
listAutomations(workspaceId) {
|
|
250
|
-
return this.automations.list(workspaceId);
|
|
249
|
+
listAutomations(workspaceId, originKind) {
|
|
250
|
+
return this.automations.list(workspaceId, originKind);
|
|
251
251
|
}
|
|
252
252
|
getAutomation(automationId) {
|
|
253
253
|
return this.automations.get(automationId);
|
|
@@ -255,6 +255,9 @@ class LocalCoreAcpStore {
|
|
|
255
255
|
getAutomationNextCheckAt(automationId) {
|
|
256
256
|
return this.automations.getNextCheckAt(automationId);
|
|
257
257
|
}
|
|
258
|
+
listDueAutomationIds(now) {
|
|
259
|
+
return this.automations.listDueAutomationIds(now);
|
|
260
|
+
}
|
|
258
261
|
createAutomation(input) {
|
|
259
262
|
return this.automations.create(input);
|
|
260
263
|
}
|
|
@@ -365,6 +368,15 @@ class LocalCoreAcpStore {
|
|
|
365
368
|
getLatestAutomationEvaluationWithState(automationId) {
|
|
366
369
|
return this.automations.getLatestEvaluationWithState(automationId);
|
|
367
370
|
}
|
|
371
|
+
listLatestFinishedAutomationEvaluationByOrigin(originKind, workspaceId) {
|
|
372
|
+
return this.automations.listLatestFinishedEvaluationByOrigin(originKind, workspaceId);
|
|
373
|
+
}
|
|
374
|
+
listLatestAutomationEvaluationWithStateByOrigin(originKind, workspaceId) {
|
|
375
|
+
return this.automations.listLatestEvaluationWithStateByOrigin(originKind, workspaceId);
|
|
376
|
+
}
|
|
377
|
+
listLatestAutomationRunByOrigin(originKind, workspaceId) {
|
|
378
|
+
return this.automations.listLatestRunByOrigin(originKind, workspaceId);
|
|
379
|
+
}
|
|
368
380
|
createAutomationRun(automationId, evaluationId, input = {}) {
|
|
369
381
|
return this.automations.createRun(automationId, evaluationId, input);
|
|
370
382
|
}
|
|
@@ -72,14 +72,28 @@ class LocalRuntimeConfigStore {
|
|
|
72
72
|
const row = this.selectStmt.get(RUNTIME_CONFIG_ID);
|
|
73
73
|
if (row) {
|
|
74
74
|
const config = (0, utils_js_1.parseJson)(row.config_json, {});
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
// If SQLite lost its projects (e.g. a partial save wrote {"config_version":2}),
|
|
76
|
+
// fall back to importing from config.toml rather than silently running empty.
|
|
77
|
+
const sqliteLostProjects = !Array.isArray(config.projects) || config.projects.length === 0;
|
|
78
|
+
if (!sqliteLostProjects) {
|
|
79
|
+
const migrated = (0, config_migration_js_1.migrateDesktopConnectConfig)(config);
|
|
80
|
+
if (migrated.changed) {
|
|
81
|
+
return this.writeRow(migrated.config, {
|
|
82
|
+
migratedFromPath: row.migrated_from_path || undefined,
|
|
83
|
+
warnings: migrated.warnings,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return this.toState(row, config, migrated.warnings);
|
|
87
|
+
}
|
|
88
|
+
// projects missing in SQLite — try to recover from the on-disk toml.
|
|
89
|
+
const legacy = this.readLegacyConfig();
|
|
90
|
+
if (legacy && !('error' in legacy)) {
|
|
91
|
+
return this.writeRow(legacy.config, {
|
|
92
|
+
migratedFromPath: legacy.path,
|
|
93
|
+
warnings: ['Recovered projects from disk after SQLite config lost them.', ...legacy.warnings],
|
|
80
94
|
});
|
|
81
95
|
}
|
|
82
|
-
|
|
96
|
+
// Fall through to the standard legacy path (which also handles the no-row case below).
|
|
83
97
|
}
|
|
84
98
|
const legacy = this.readLegacyConfig();
|
|
85
99
|
if (legacy) {
|
|
@@ -131,9 +131,11 @@ class AutomationMonitorService {
|
|
|
131
131
|
await this.settleInFlight();
|
|
132
132
|
}
|
|
133
133
|
listMonitors(workspaceId) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
const automations = this.options.automations.list(workspaceId, 'automation-monitor');
|
|
135
|
+
const latestEvaluationById = this.options.automations.listLatestFinishedEvaluationByOrigin('automation-monitor', workspaceId);
|
|
136
|
+
const latestRunById = this.options.automations.listLatestRunByOrigin('automation-monitor', workspaceId);
|
|
137
|
+
const latestEvaluationWithStateById = this.options.automations.listLatestEvaluationWithStateByOrigin('automation-monitor', workspaceId);
|
|
138
|
+
return automations.map((automation) => (0, legacy_automation_mappers_js_1.automationToMonitor)(automation, latestEvaluationById.get(automation.id), latestRunById.get(automation.id), latestEvaluationWithStateById.get(automation.id)));
|
|
137
139
|
}
|
|
138
140
|
getMonitor(monitorId) {
|
|
139
141
|
const resolved = this.resolveMonitorId(monitorId);
|
|
@@ -41,8 +41,8 @@ class AutomationService {
|
|
|
41
41
|
}
|
|
42
42
|
this.scriptProtocolRunner = options.scriptProtocolRunner;
|
|
43
43
|
}
|
|
44
|
-
list(workspaceId) {
|
|
45
|
-
return this.options.store.listAutomations(workspaceId);
|
|
44
|
+
list(workspaceId, originKind) {
|
|
45
|
+
return this.options.store.listAutomations(workspaceId, originKind);
|
|
46
46
|
}
|
|
47
47
|
get(automationId) {
|
|
48
48
|
return this.options.store.getAutomation(automationId);
|
|
@@ -105,6 +105,15 @@ class AutomationService {
|
|
|
105
105
|
listRuns(automationId) {
|
|
106
106
|
return this.options.store.listAutomationRuns(automationId);
|
|
107
107
|
}
|
|
108
|
+
listLatestFinishedEvaluationByOrigin(originKind, workspaceId) {
|
|
109
|
+
return this.options.store.listLatestFinishedAutomationEvaluationByOrigin(originKind, workspaceId);
|
|
110
|
+
}
|
|
111
|
+
listLatestEvaluationWithStateByOrigin(originKind, workspaceId) {
|
|
112
|
+
return this.options.store.listLatestAutomationEvaluationWithStateByOrigin(originKind, workspaceId);
|
|
113
|
+
}
|
|
114
|
+
listLatestRunByOrigin(originKind, workspaceId) {
|
|
115
|
+
return this.options.store.listLatestAutomationRunByOrigin(originKind, workspaceId);
|
|
116
|
+
}
|
|
108
117
|
getRuntimeStatus() {
|
|
109
118
|
return this.runtimeStatus;
|
|
110
119
|
}
|
|
@@ -300,11 +309,11 @@ class AutomationService {
|
|
|
300
309
|
async runTick(generation) {
|
|
301
310
|
const now = this.now();
|
|
302
311
|
const due = [];
|
|
312
|
+
const dueIds = this.options.store.listDueAutomationIds(now);
|
|
303
313
|
for (const automation of this.list()) {
|
|
304
314
|
if (!this.shouldPoll(automation))
|
|
305
315
|
continue;
|
|
306
|
-
|
|
307
|
-
if (nextCheckAt !== null && Date.parse(nextCheckAt) <= now.getTime()) {
|
|
316
|
+
if (dueIds.has(automation.id)) {
|
|
308
317
|
due.push(automation);
|
|
309
318
|
}
|
|
310
319
|
}
|
|
@@ -20,10 +20,13 @@ function nextActivationAt(activation, after) {
|
|
|
20
20
|
return validResultDate(nextMs);
|
|
21
21
|
}
|
|
22
22
|
case 'cron': {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const compiled = requireCompiledCron(compiledCron);
|
|
24
|
+
const search = isUtcTimezone(activation.timezone)
|
|
25
|
+
? (0, cron_js_1.findNextCronMatchUtc)(compiled, after)
|
|
26
|
+
: (0, cron_js_1.findNextCronMatchInTimezone)(compiled, after, activation.timezone);
|
|
27
|
+
if (search.date)
|
|
28
|
+
return search.date;
|
|
29
|
+
throw new Error(`No cron activation exists for expression: ${activation.expression} (${activation.timezone})`);
|
|
27
30
|
}
|
|
28
31
|
}
|
|
29
32
|
}
|
|
@@ -40,14 +43,11 @@ function isActivationDue(activation, now, nextCheckAt) {
|
|
|
40
43
|
return parseIsoTimestamp(activation.runAt, 'once runAt').getTime() <= nowMs;
|
|
41
44
|
case 'interval':
|
|
42
45
|
return nowMs % activation.intervalMs === 0;
|
|
43
|
-
case 'cron':
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
month: now.getUTCMonth() + 1,
|
|
49
|
-
dayOfWeek: now.getUTCDay(),
|
|
50
|
-
});
|
|
46
|
+
case 'cron': {
|
|
47
|
+
if (nowMs !== floorUtcMinute(nowMs))
|
|
48
|
+
return false;
|
|
49
|
+
return (0, cron_js_1.cronMatchesFields)(requireCompiledCron(compiledCron), (0, cron_js_1.extractFieldsInTimezone)(now, activation.timezone));
|
|
50
|
+
}
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
function missedActivationAt(activation, lastCheckedAt, now) {
|
|
@@ -68,7 +68,10 @@ function missedActivationAt(activation, lastCheckedAt, now) {
|
|
|
68
68
|
return latestMs > lastCheckedMs ? validResultDate(latestMs) : null;
|
|
69
69
|
}
|
|
70
70
|
case 'cron': {
|
|
71
|
-
|
|
71
|
+
const compiled = requireCompiledCron(compiledCron);
|
|
72
|
+
return (isUtcTimezone(activation.timezone)
|
|
73
|
+
? (0, cron_js_1.findPreviousCronMatchUtc)(compiled, now, new Date(lastCheckedMs))
|
|
74
|
+
: (0, cron_js_1.findPreviousCronMatchInTimezone)(compiled, now, activation.timezone, new Date(lastCheckedMs))).date;
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
77
|
}
|
|
@@ -83,14 +86,12 @@ function validateActivation(activation) {
|
|
|
83
86
|
assertInterval(activation.intervalMs);
|
|
84
87
|
return undefined;
|
|
85
88
|
case 'cron':
|
|
86
|
-
|
|
89
|
+
(0, cron_js_1.assertSupportedTimezone)(activation.timezone);
|
|
87
90
|
return (0, cron_js_1.compileCronExpression)(activation.expression);
|
|
88
91
|
}
|
|
89
92
|
}
|
|
90
|
-
function
|
|
91
|
-
|
|
92
|
-
throw new Error(`Automation scheduling currently supports only UTC cron timezones; received: ${timezone}`);
|
|
93
|
-
}
|
|
93
|
+
function isUtcTimezone(timezone) {
|
|
94
|
+
return timezone === 'UTC' || timezone === 'Etc/UTC' || timezone === 'Z';
|
|
94
95
|
}
|
|
95
96
|
function assertInterval(intervalMs) {
|
|
96
97
|
if (!Number.isSafeInteger(intervalMs) || intervalMs <= 0) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setDefaultTimezone = setDefaultTimezone;
|
|
3
4
|
exports.scheduledJobToAutomationInput = scheduledJobToAutomationInput;
|
|
4
5
|
exports.automationToScheduledJob = automationToScheduledJob;
|
|
5
6
|
exports.monitorToAutomationInput = monitorToAutomationInput;
|
|
@@ -9,13 +10,36 @@ exports.automationToMonitorRun = automationToMonitorRun;
|
|
|
9
10
|
exports.latestFinishedEvaluation = latestFinishedEvaluation;
|
|
10
11
|
exports.latestAutomationRun = latestAutomationRun;
|
|
11
12
|
const superai_contracts_1 = require("../../../../packages/contracts/src/index.js");
|
|
13
|
+
const cron_js_1 = require("../scheduler/cron.js");
|
|
12
14
|
const condition_evaluator_js_1 = require("./condition-evaluator.js");
|
|
15
|
+
// The timezone used for cron expressions created through the legacy scheduler facade.
|
|
16
|
+
// The old SchedulerService interpreted cron in the server's local wall clock, so we
|
|
17
|
+
// default to the host's IANA timezone (falling back to UTC) to preserve that behavior.
|
|
18
|
+
// Resolves lazily so environments without full IANA data still boot.
|
|
19
|
+
let defaultTimezone;
|
|
20
|
+
function resolveDefaultTimezone() {
|
|
21
|
+
if (defaultTimezone)
|
|
22
|
+
return defaultTimezone;
|
|
23
|
+
try {
|
|
24
|
+
const detected = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
25
|
+
defaultTimezone = detected && (0, cron_js_1.isValidTimezone)(detected) ? detected : 'UTC';
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
defaultTimezone = 'UTC';
|
|
29
|
+
}
|
|
30
|
+
return defaultTimezone;
|
|
31
|
+
}
|
|
32
|
+
/** Override the default timezone used for legacy cron jobs (e.g. from runtime config). */
|
|
33
|
+
function setDefaultTimezone(timezone) {
|
|
34
|
+
(0, cron_js_1.assertSupportedTimezone)(timezone);
|
|
35
|
+
defaultTimezone = timezone;
|
|
36
|
+
}
|
|
13
37
|
function scheduledJobToAutomationInput(input) {
|
|
14
38
|
const triggerType = (0, superai_contracts_1.normalizeScheduledJobTriggerType)(input.triggerType);
|
|
15
39
|
const activation = triggerType === 'once'
|
|
16
40
|
? { kind: 'once', runAt: requireText(input.runAt, 'Scheduled job runAt') }
|
|
17
41
|
: triggerType === 'cron'
|
|
18
|
-
? { kind: 'cron', expression: requireText(input.cronExpr, 'Scheduled job cronExpr'), timezone:
|
|
42
|
+
? { kind: 'cron', expression: requireText(input.cronExpr, 'Scheduled job cronExpr'), timezone: resolveDefaultTimezone() }
|
|
19
43
|
: fail(`Unsupported scheduled job trigger type: ${triggerType}`);
|
|
20
44
|
return {
|
|
21
45
|
workspaceId: requireText(input.workspaceId, 'Scheduled job workspaceId'),
|
|
@@ -16,6 +16,7 @@ const scheduler_service_js_1 = require("../scheduler/scheduler-service.js");
|
|
|
16
16
|
const automation_monitor_service_js_1 = require("../automation/automation-monitor-service.js");
|
|
17
17
|
const automation_action_executor_js_1 = require("../automation/automation-action-executor.js");
|
|
18
18
|
const automation_service_js_1 = require("../automation/automation-service.js");
|
|
19
|
+
const legacy_automation_mappers_js_1 = require("../automation/legacy-automation-mappers.js");
|
|
19
20
|
function bootstrapLocalCoreKernel(options) {
|
|
20
21
|
const capabilities = new capability_registry_js_1.LocalCoreCapabilityRegistry();
|
|
21
22
|
const plugins = new plugin_registry_js_1.LocalCorePluginRegistry(options?.disabledPluginIds);
|
|
@@ -213,6 +214,10 @@ function bootstrapLocalCoreRuntime(options) {
|
|
|
213
214
|
getWorkspaceRouter: () => workspaceRouter,
|
|
214
215
|
getChannelRuntime: (platform) => channelRuntimes.find((runtime) => runtime.platform === platform || platform.startsWith(`${runtime.platform}:`)),
|
|
215
216
|
});
|
|
217
|
+
// Default legacy cron jobs to the host's local timezone so they fire at the wall
|
|
218
|
+
// clock the user wrote (e.g. a job written as "0 1 * * *" runs at 1 AM server time).
|
|
219
|
+
// Matches the behavior of the old SchedulerService, which matched in local time.
|
|
220
|
+
(0, legacy_automation_mappers_js_1.setDefaultTimezone)(resolveHostTimezone());
|
|
216
221
|
const automations = new automation_service_js_1.AutomationService({
|
|
217
222
|
store,
|
|
218
223
|
actionExecutor: automationActionExecutor,
|
|
@@ -280,3 +285,13 @@ function bootstrapLocalCoreRuntime(options) {
|
|
|
280
285
|
},
|
|
281
286
|
};
|
|
282
287
|
}
|
|
288
|
+
// The host machine's IANA timezone. Falls back to UTC if the runtime cannot determine it.
|
|
289
|
+
function resolveHostTimezone() {
|
|
290
|
+
try {
|
|
291
|
+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
292
|
+
return tz && tz.length > 0 ? tz : 'UTC';
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return 'UTC';
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -110,6 +110,10 @@ class LocalCoreController extends node_events_1.EventEmitter {
|
|
|
110
110
|
}
|
|
111
111
|
async init() {
|
|
112
112
|
await this.runtime.start();
|
|
113
|
+
// Bind and start channel gateways (e.g. Lark WS) now that plugins are registered.
|
|
114
|
+
// Without this, channels only (re)connect when runtime_config is later re-saved,
|
|
115
|
+
// so a fresh process boot silently leaves every channel disconnected.
|
|
116
|
+
await this.channelService.refreshBindings();
|
|
113
117
|
await this.emitRuntime();
|
|
114
118
|
setTimeout(() => {
|
|
115
119
|
void this.runtimeDetection.refreshOnStartup();
|
|
@@ -320,9 +324,19 @@ class LocalCoreController extends node_events_1.EventEmitter {
|
|
|
320
324
|
const legacyProjects = migrated.config.projects || [];
|
|
321
325
|
let warnings = [...(current.warnings || []), ...migrated.warnings];
|
|
322
326
|
if (legacyProjects.length > 0) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
327
|
+
// Persist a copy of the projects into the workspace registry as a
|
|
328
|
+
// crash-recovery backup, but do NOT strip them from runtime_config:
|
|
329
|
+
// channel bindings still read runtime_config directly, so removing
|
|
330
|
+
// projects here would silently disable every channel until the next
|
|
331
|
+
// explicit save.
|
|
332
|
+
const registryNames = (this.store.listWorkspaceRegistry?.() ?? [])
|
|
333
|
+
.filter((e) => e.metadata?.source === 'runtime-project')
|
|
334
|
+
.map((e) => e.displayName);
|
|
335
|
+
const alreadyBackedUp = legacyProjects.every((p) => registryNames.includes(p.name));
|
|
336
|
+
if (!alreadyBackedUp) {
|
|
337
|
+
(0, workspace_project_registry_js_1.persistProjectsInRegistry)(this.store, legacyProjects, { preserveLegacyIds: true });
|
|
338
|
+
warnings = [...warnings, 'Backed up runtime projects into the workspace registry.'];
|
|
339
|
+
}
|
|
326
340
|
}
|
|
327
341
|
return {
|
|
328
342
|
...this.store.readRuntimeConfig(),
|