@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.
Files changed (48) hide show
  1. package/README.md +4 -0
  2. package/dist/renderer/assets/{AutomationList-DR-micIT.js → AutomationList-fCves7JJ.js} +1 -1
  3. package/dist/renderer/assets/{Badge-COE1Zm2i.js → Badge-03CgzC3O.js} +1 -1
  4. package/dist/renderer/assets/{Card-F6UNFxnQ.js → Card-BG4attR1.js} +1 -1
  5. package/dist/renderer/assets/{Config-BKqxfhZi.js → Config-DLmfH4Fs.js} +1 -1
  6. package/dist/renderer/assets/{Dashboard-Bvp7-_2z.js → Dashboard-tmYu-275.js} +2 -2
  7. package/dist/renderer/assets/{EmptyState-BwV_J0Og.js → EmptyState-D3nq3XaN.js} +1 -1
  8. package/dist/renderer/assets/{HighlightedMarkdown-CBIBaWyM.js → HighlightedMarkdown-DFiXk_8l.js} +1 -1
  9. package/dist/renderer/assets/{Input-C8LZd4Bi.js → Input-BuQ3SusB.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeDetail-DjXYNMIs.js → KnowledgeDetail-2k6D-FBu.js} +1 -1
  11. package/dist/renderer/assets/{KnowledgeHome-Cqk5_nJh.js → KnowledgeHome-Bv3_riqS.js} +1 -1
  12. package/dist/renderer/assets/{Logs-B6cXFvW4.js → Logs-DdDCHMXT.js} +1 -1
  13. package/dist/renderer/assets/{Modal-CAj7soll.js → Modal-BOpQurFi.js} +1 -1
  14. package/dist/renderer/assets/{Page-DlnMnu4F.js → Page-DAZBFNqx.js} +1 -1
  15. package/dist/renderer/assets/{Select-CVay_LUX.js → Select-CjT5-m7R.js} +1 -1
  16. package/dist/renderer/assets/{ThreadChat-BxXRpYa1.js → ThreadChat-Dd1d35z9.js} +3 -3
  17. package/dist/renderer/assets/{Workspace-YC0pC7cM.js → Workspace-CMHdpXa7.js} +1 -1
  18. package/dist/renderer/assets/{arrow-left-Cwm09dxx.js → arrow-left-DTisCSrr.js} +1 -1
  19. package/dist/renderer/assets/{book-open-BwcGC_im.js → book-open-DGOtHxDK.js} +1 -1
  20. package/dist/renderer/assets/{channels-BPytmVCd.js → channels-DTwVsSq8.js} +1 -1
  21. package/dist/renderer/assets/{chevron-down-2LP9oGHG.js → chevron-down-Dw5IfnsR.js} +1 -1
  22. package/dist/renderer/assets/{index-llCPCiYk.js → index-CDPdiGc6.js} +1 -1
  23. package/dist/renderer/assets/{index-eoVERL5K.js → index-jESlfFTY.js} +2 -2
  24. package/dist/renderer/assets/{knowledge-QujAv-Zh.js → knowledge-DnebW2tM.js} +1 -1
  25. package/dist/renderer/assets/{pencil-0P2INETc.js → pencil-BBgjg0RK.js} +1 -1
  26. package/dist/renderer/assets/{plus-tiGRgE2v.js → plus-C5OXMCrc.js} +1 -1
  27. package/dist/renderer/assets/{save-D8U93DNa.js → save-B9-o4ceK.js} +1 -1
  28. package/dist/renderer/assets/{search-C-9J9J3f.js → search-CetWQ9yl.js} +1 -1
  29. package/dist/renderer/assets/{shield-check-BCKkPPTJ.js → shield-check-Dts9cJZL.js} +1 -1
  30. package/dist/renderer/assets/{threads-Bsrwwnpm.js → threads-Co4JiiXY.js} +1 -1
  31. package/dist/renderer/assets/{trash-2-CP4sn15n.js → trash-2-CcjYdBI8.js} +1 -1
  32. package/dist/renderer/index.html +1 -1
  33. package/dist-electron/services/local-ai-core/src/acp/store/automation-store.js +69 -3
  34. package/dist-electron/services/local-ai-core/src/acp/store/local-core-acp-store.js +14 -2
  35. package/dist-electron/services/local-ai-core/src/acp/store/runtime-config-store.js +20 -6
  36. package/dist-electron/services/local-ai-core/src/automation/automation-monitor-service.js +5 -3
  37. package/dist-electron/services/local-ai-core/src/automation/automation-service.js +13 -4
  38. package/dist-electron/services/local-ai-core/src/automation/automation-trigger-engine.js +19 -18
  39. package/dist-electron/services/local-ai-core/src/automation/legacy-automation-mappers.js +25 -1
  40. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +15 -0
  41. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +17 -3
  42. package/dist-electron/services/local-ai-core/src/scheduler/cron.js +197 -0
  43. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-job-application-service.js +4 -3
  44. package/dist-electron/services/local-ai-core/src/scheduler/scheduler-service.js +3 -3
  45. package/dist-electron/tests/electron/automation-compatibility.test.js +6 -1
  46. package/dist-electron/tests/electron/automation-engine.test.js +67 -3
  47. package/dist-electron/tests/electron/automation-store.test.js +189 -0
  48. package/package.json +1 -1
@@ -1 +1 @@
1
- import{z as n,Z as l}from"./index-eoVERL5K.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
+ 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,4 +1,4 @@
1
- import{c as a}from"./index-eoVERL5K.js";/**
1
+ import{c as a}from"./index-jESlfFTY.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as e}from"./index-eoVERL5K.js";/**
1
+ import{c as e}from"./index-jESlfFTY.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-eoVERL5K.js";/**
1
+ import{c as a}from"./index-jESlfFTY.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c}from"./index-eoVERL5K.js";/**
1
+ import{c}from"./index-jESlfFTY.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c}from"./index-eoVERL5K.js";/**
1
+ import{c}from"./index-jESlfFTY.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1 +1 @@
1
- import{z as t,D as r}from"./index-eoVERL5K.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};
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};
@@ -1,4 +1,4 @@
1
- import{c as e}from"./index-eoVERL5K.js";/**
1
+ import{c as e}from"./index-jESlfFTY.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -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-eoVERL5K.js"></script>
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
- ${workspaceId ? 'WHERE workspace_id = ?' : ''}
34
+ ${conditions.length ? `WHERE ${conditions.join(' AND ')}` : ''}
25
35
  ORDER BY updated_at DESC
26
- `).all(...(workspaceId ? [workspaceId] : []));
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
- const migrated = (0, config_migration_js_1.migrateDesktopConnectConfig)(config);
76
- if (migrated.changed) {
77
- return this.writeRow(migrated.config, {
78
- migratedFromPath: row.migrated_from_path || undefined,
79
- warnings: migrated.warnings,
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
- return this.toState(row, config, migrated.warnings);
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
- return this.options.automations.list(workspaceId)
135
- .filter((automation) => automation.originKind === 'automation-monitor')
136
- .map((automation) => (0, legacy_automation_mappers_js_1.automationToMonitor)(automation, (0, legacy_automation_mappers_js_1.latestFinishedEvaluation)(this.options.automations.listEvaluations(automation.id)), (0, legacy_automation_mappers_js_1.latestAutomationRun)(this.options.automations.listRuns(automation.id)), this.options.automations.getLatestEvaluationWithState(automation.id)));
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
- const nextCheckAt = this.options.store.getAutomationNextCheckAt(automation.id);
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 result = (0, cron_js_1.findNextCronMatchUtc)(requireCompiledCron(compiledCron), after);
24
- if (result.date)
25
- return result.date;
26
- throw new Error(`No UTC cron activation exists for expression: ${activation.expression}`);
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
- return nowMs === floorUtcMinute(nowMs) && (0, cron_js_1.cronMatchesFields)(requireCompiledCron(compiledCron), {
45
- minute: now.getUTCMinutes(),
46
- hour: now.getUTCHours(),
47
- dayOfMonth: now.getUTCDate(),
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
- return (0, cron_js_1.findPreviousCronMatchUtc)(requireCompiledCron(compiledCron), now, new Date(lastCheckedMs)).date;
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
- assertUtcTimezone(activation.timezone);
89
+ (0, cron_js_1.assertSupportedTimezone)(activation.timezone);
87
90
  return (0, cron_js_1.compileCronExpression)(activation.expression);
88
91
  }
89
92
  }
90
- function assertUtcTimezone(timezone) {
91
- if (timezone !== 'UTC' && timezone !== 'Etc/UTC') {
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: 'UTC' }
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
- (0, workspace_project_registry_js_1.persistProjectsInRegistry)(this.store, legacyProjects, { preserveLegacyIds: true });
324
- const saved = this.store.saveRuntimeConfig((0, workspace_project_registry_js_1.withoutRuntimeProjects)(migrated.config));
325
- warnings = [...warnings, ...(saved.warnings || []), 'Migrated runtime projects into the workspace registry.'];
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(),