@kafca/agentdock 0.1.58 → 0.1.62

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 (127) hide show
  1. package/README.md +18 -0
  2. package/dist/renderer/assets/AutomationList-BgJgAdVO.js +16 -0
  3. package/dist/renderer/assets/{Badge-BfY1VaHS.js → Badge-Bq2hPVcR.js} +1 -1
  4. package/dist/renderer/assets/{Card-DWA_Tww_.js → Card-BPJNSZce.js} +1 -1
  5. package/dist/renderer/assets/{Config-nHj3g1U5.js → Config-aSFqlI88.js} +2 -2
  6. package/dist/renderer/assets/Dashboard-D3ezFmb7.js +36 -0
  7. package/dist/renderer/assets/{EmptyState-zatDjLj_.js → EmptyState-DK496gBq.js} +1 -1
  8. package/dist/renderer/assets/{HighlightedMarkdown-CsM5n70d.js → HighlightedMarkdown-Ckre5jNv.js} +1 -1
  9. package/dist/renderer/assets/{Input-R1XdFK1K.js → Input-C5C1d0ba.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeDetail-Co5sxeGd.js → KnowledgeDetail-N8Ey1ikc.js} +2 -2
  11. package/dist/renderer/assets/{KnowledgeHome-DwziPw5m.js → KnowledgeHome-9aF8nJEf.js} +2 -2
  12. package/dist/renderer/assets/{Logs-NwZUtR_G.js → Logs-sgAdwEeS.js} +2 -2
  13. package/dist/renderer/assets/{Modal-CrcIwIFL.js → Modal-BB10hTRc.js} +3 -3
  14. package/dist/renderer/assets/{Page-Dp0obeSw.js → Page-DfC319Ck.js} +1 -1
  15. package/dist/renderer/assets/{Select-6mVQaf84.js → Select-RHJBmNER.js} +1 -1
  16. package/dist/renderer/assets/{ThreadChat-BFzbn3L6.js → ThreadChat-BU1zCdr4.js} +3 -3
  17. package/dist/renderer/assets/{Workspace-DCmo8BMm.js → Workspace-BoZCz7S8.js} +1 -1
  18. package/dist/renderer/assets/{arrow-left-9rjg_Hp4.js → arrow-left-D2V1eZSD.js} +1 -1
  19. package/dist/renderer/assets/{book-open-Bu8GCHnG.js → book-open-DUcBn7Bb.js} +1 -1
  20. package/dist/renderer/assets/{channels-BxVbG-6j.js → channels-DMhDSlf2.js} +2 -2
  21. package/dist/renderer/assets/{chevron-down-BE8uQmWM.js → chevron-down-Ca2FQDB7.js} +1 -1
  22. package/dist/renderer/assets/en-Bnr4nMzg.js +1 -0
  23. package/dist/renderer/assets/es-B1GIFh3i.js +1 -0
  24. package/dist/renderer/assets/{index-BIKL3fTP.css → index-B6WcpkJF.css} +1 -1
  25. package/dist/renderer/assets/{index-B7TeF6Es.js → index-BlnvlekJ.js} +1 -1
  26. package/dist/renderer/assets/index-DRJ5wgw_.js +162 -0
  27. package/dist/renderer/assets/ja-CDWPWqEW.js +1 -0
  28. package/dist/renderer/assets/{knowledge-Kl19SqGS.js → knowledge-D1-k56eY.js} +1 -1
  29. package/dist/renderer/assets/{pencil-CPB7SK1m.js → pencil-BhWdv_86.js} +1 -1
  30. package/dist/renderer/assets/{plus-Bt26z-4q.js → plus-BacLMlK3.js} +1 -1
  31. package/dist/renderer/assets/{save-Une7GMxT.js → save-Bpoxbzau.js} +1 -1
  32. package/dist/renderer/assets/{search-DE0agJjJ.js → search-BVJcpdFX.js} +1 -1
  33. package/dist/renderer/assets/{shield-check-De7xaXZh.js → shield-check-C2cw_Ip6.js} +1 -1
  34. package/dist/renderer/assets/{threads-BnsNL5ei.js → threads-DOsBQBg_.js} +1 -1
  35. package/dist/renderer/assets/{trash-2-6iGF59Ac.js → trash-2-Bs3rEm3g.js} +1 -1
  36. package/dist/renderer/assets/zh-MSddxMDX.js +1 -0
  37. package/dist/renderer/assets/zh-TW-C_LNWX0o.js +1 -0
  38. package/dist/renderer/index.html +2 -2
  39. package/dist-electron/electron/managed-skills/agent-browser/SKILL.md +41 -0
  40. package/dist-electron/electron/managed-skills/agent-browser/references/authentication.md +83 -0
  41. package/dist-electron/electron/managed-skills/agent-browser/references/commands.md +50 -0
  42. package/dist-electron/electron/managed-skills/agent-browser/references/profiling.md +20 -0
  43. package/dist-electron/electron/managed-skills/agent-browser/references/proxy-support.md +18 -0
  44. package/dist-electron/electron/managed-skills/agent-browser/references/session-management.md +17 -0
  45. package/dist-electron/electron/managed-skills/agent-browser/references/snapshot-refs.md +17 -0
  46. package/dist-electron/electron/managed-skills/agent-browser/references/video-recording.md +21 -0
  47. package/dist-electron/electron/managed-skills/agent-browser/templates/authenticated-session.sh +11 -0
  48. package/dist-electron/electron/managed-skills/agent-browser/templates/capture-workflow.sh +12 -0
  49. package/dist-electron/electron/managed-skills/agent-browser/templates/form-automation.sh +10 -0
  50. package/dist-electron/electron/managed-skills/condition-trigger/SKILL.md +21 -0
  51. package/dist-electron/electron/managed-skills/condition-trigger/scripts/register-condition-trigger.sh +106 -0
  52. package/dist-electron/electron/managed-skills/knowledge-base/SKILL.md +13 -0
  53. package/dist-electron/electron/managed-skills/knowledge-base/scripts/search-knowledge.sh +80 -0
  54. package/dist-electron/packages/contracts/src/automations.js +211 -0
  55. package/dist-electron/packages/contracts/src/index.js +1 -0
  56. package/dist-electron/packages/core-sdk/src/automations.js +87 -0
  57. package/dist-electron/packages/core-sdk/src/client.js +101 -0
  58. package/dist-electron/packages/core-sdk/src/index.js +2 -1
  59. package/dist-electron/services/local-ai-core/src/acp/store/automation-script-store.js +415 -0
  60. package/dist-electron/services/local-ai-core/src/acp/store/automation-store.js +829 -0
  61. package/dist-electron/services/local-ai-core/src/acp/store/local-core-acp-store.js +158 -0
  62. package/dist-electron/services/local-ai-core/src/acp/store/schema.js +81 -0
  63. package/dist-electron/services/local-ai-core/src/automation/automation-action-executor.js +144 -0
  64. package/dist-electron/services/local-ai-core/src/automation/automation-condition-engine.js +79 -0
  65. package/dist-electron/services/local-ai-core/src/automation/automation-conversation-executor.js +39 -73
  66. package/dist-electron/services/local-ai-core/src/automation/automation-monitor-service.js +695 -260
  67. package/dist-electron/services/local-ai-core/src/automation/automation-script-service.js +507 -0
  68. package/dist-electron/services/local-ai-core/src/automation/automation-service.js +1019 -0
  69. package/dist-electron/services/local-ai-core/src/automation/automation-trigger-engine.js +141 -0
  70. package/dist-electron/services/local-ai-core/src/automation/condition-evaluator.js +60 -17
  71. package/dist-electron/services/local-ai-core/src/automation/legacy-automation-mappers.js +286 -0
  72. package/dist-electron/services/local-ai-core/src/automation/scripts/anthropic-sandbox-runner.js +792 -0
  73. package/dist-electron/services/local-ai-core/src/automation/scripts/sandbox-runner.js +2 -0
  74. package/dist-electron/services/local-ai-core/src/automation/scripts/script-package.js +475 -0
  75. package/dist-electron/services/local-ai-core/src/automation/scripts/script-protocol-runner.js +266 -0
  76. package/dist-electron/services/local-ai-core/src/automation/scripts/secret-resolver.js +37 -0
  77. package/dist-electron/services/local-ai-core/src/channel/weixin/inbound-poller.js +1 -2
  78. package/dist-electron/services/local-ai-core/src/channel/weixin/transport.js +9 -8
  79. package/dist-electron/services/local-ai-core/src/cli/lac.js +229 -0
  80. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +21 -0
  81. package/dist-electron/services/local-ai-core/src/runtime/deployment-diagnostics.js +76 -0
  82. package/dist-electron/services/local-ai-core/src/runtime/handlers/automations-handler.js +322 -0
  83. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +20 -0
  84. package/dist-electron/services/local-ai-core/src/runtime/managed-skill-catalog.js +37 -0
  85. package/dist-electron/services/local-ai-core/src/runtime/server-helpers.js +14 -4
  86. package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +75 -0
  87. package/dist-electron/services/local-ai-core/src/runtime/server.js +21 -0
  88. package/dist-electron/services/local-ai-core/src/runtime/standalone.js +1 -0
  89. package/dist-electron/services/local-ai-core/src/scheduler/cron.js +151 -39
  90. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-conversation-executor.js +2 -4
  91. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-job-application-service.js +64 -12
  92. package/dist-electron/services/local-ai-core/src/scheduler/scheduler-service.js +71 -4
  93. package/dist-electron/services/local-ai-core/src/scheduler/thread-resolution.js +10 -0
  94. package/dist-electron/services/local-ai-core/src/thread/agent-message-policy.js +16 -1
  95. package/dist-electron/src/pages/Automation/automation-page-model.js +76 -0
  96. package/dist-electron/tests/contracts/agent-message-policy.test.js +32 -0
  97. package/dist-electron/tests/contracts/architecture-docs.test.js +30 -0
  98. package/dist-electron/tests/contracts/automation-contracts.test.js +154 -0
  99. package/dist-electron/tests/contracts/automation-renderer-model.test.js +47 -0
  100. package/dist-electron/tests/electron/automation-action-executor.test.js +75 -0
  101. package/dist-electron/tests/electron/automation-compatibility.test.js +463 -0
  102. package/dist-electron/tests/electron/automation-deployment.test.js +66 -0
  103. package/dist-electron/tests/electron/automation-engine.test.js +264 -0
  104. package/dist-electron/tests/electron/automation-monitor.test.js +1333 -0
  105. package/dist-electron/tests/electron/automation-sandbox-runner.test.js +333 -0
  106. package/dist-electron/tests/electron/automation-script-approval.test.js +553 -0
  107. package/dist-electron/tests/electron/automation-script-package.test.js +499 -0
  108. package/dist-electron/tests/electron/automation-script-runner.test.js +120 -0
  109. package/dist-electron/tests/electron/automation-service.test.js +1078 -0
  110. package/dist-electron/tests/electron/automation-store.test.js +593 -0
  111. package/dist-electron/tests/electron/condition-trigger-skill.test.js +93 -0
  112. package/dist-electron/tests/electron/core-client.test.js +99 -0
  113. package/dist-electron/tests/electron/lac-cli.test.js +110 -0
  114. package/dist-electron/tests/electron/workspace-task-store.test.js +25 -0
  115. package/dist-electron/tests/integration/automation-routes.test.js +186 -0
  116. package/dist-electron/tests/integration/automation-sandbox-runtime.test.js +92 -0
  117. package/package.json +5 -6
  118. package/dist/renderer/assets/CronList-BwkA_I7v.js +0 -1
  119. package/dist/renderer/assets/Dashboard-D0_A7k02.js +0 -41
  120. package/dist/renderer/assets/MonitorList-CS1uLS5A.js +0 -1
  121. package/dist/renderer/assets/en-CmAr_0e6.js +0 -1
  122. package/dist/renderer/assets/es-Cs9x52ca.js +0 -1
  123. package/dist/renderer/assets/index-BkI9POxj.js +0 -167
  124. package/dist/renderer/assets/ja-DtvtBRTW.js +0 -1
  125. package/dist/renderer/assets/play-DGAO4OCu.js +0 -6
  126. package/dist/renderer/assets/zh-CcJ9vBZj.js +0 -1
  127. package/dist/renderer/assets/zh-TW-elI72AC1.js +0 -1
@@ -0,0 +1,829 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalAutomationStore = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const superai_contracts_1 = require("../../../../../packages/contracts/src/index.js");
6
+ const AUTOMATION_COLUMNS = `
7
+ id, workspace_id, title, enabled, health, blocked_reason, activation_json, condition_json, action_json,
8
+ delivery_json, policies_json, last_successful_match, last_evaluation_at, last_triggered_at,
9
+ consecutive_evaluation_failures, next_check_at, origin_kind, created_at, updated_at, legacy_metadata_json
10
+ `;
11
+ const EVALUATION_COLUMNS = `
12
+ id, automation_id, status, activation_kind, script_version_id, started_at, finished_at, evaluation_json
13
+ `;
14
+ const RUN_COLUMNS = 'id, automation_id, evaluation_id, status, created_at, run_json';
15
+ class LocalAutomationStore {
16
+ db;
17
+ constructor(db) {
18
+ this.db = db;
19
+ }
20
+ list(workspaceId) {
21
+ const rows = this.db.prepare(`
22
+ SELECT ${AUTOMATION_COLUMNS}
23
+ FROM automations
24
+ ${workspaceId ? 'WHERE workspace_id = ?' : ''}
25
+ ORDER BY updated_at DESC
26
+ `).all(...(workspaceId ? [workspaceId] : []));
27
+ return rows.map((row) => this.toDefinition(row));
28
+ }
29
+ get(id) {
30
+ const row = this.db.prepare(`SELECT ${AUTOMATION_COLUMNS} FROM automations WHERE id = ?`)
31
+ .get(id);
32
+ return row ? this.toDefinition(row) : undefined;
33
+ }
34
+ create(input) {
35
+ return this.createWithOrigin(input, 'native');
36
+ }
37
+ createTrusted(input) {
38
+ return this.createWithOrigin(input, input.originKind, input.legacyMetadata);
39
+ }
40
+ createWithOrigin(input, originKind, legacyMetadata) {
41
+ const now = new Date().toISOString();
42
+ const uuid = (0, node_crypto_1.randomUUID)();
43
+ const id = originKind === 'scheduled-job'
44
+ ? uuid.split('-')[0] || uuid
45
+ : originKind === 'automation-monitor'
46
+ ? `monitor:${uuid}`
47
+ : `automation:${uuid}`;
48
+ const definition = normalizeDefinition({
49
+ id,
50
+ workspaceId: input.workspaceId,
51
+ title: input.title,
52
+ enabled: input.enabled,
53
+ health: 'healthy',
54
+ activation: input.activation,
55
+ condition: input.condition,
56
+ action: input.action,
57
+ delivery: input.delivery,
58
+ policies: input.policies,
59
+ consecutiveEvaluationFailures: 0,
60
+ originKind,
61
+ ...(legacyMetadata ? { legacyMetadata } : {}),
62
+ createdAt: now,
63
+ updatedAt: now,
64
+ });
65
+ this.insertDefinition(definition);
66
+ return this.get(definition.id);
67
+ }
68
+ update(id, input) {
69
+ return this.updateWithMetadata(id, input);
70
+ }
71
+ updateTrusted(id, input) {
72
+ return this.updateWithMetadata(id, input, input.legacyMetadata);
73
+ }
74
+ updateWithMetadata(id, input, legacyMetadata) {
75
+ const existing = this.requireDefinition(id);
76
+ const candidate = { ...existing };
77
+ if (input.title !== undefined)
78
+ candidate.title = input.title;
79
+ if (input.enabled !== undefined)
80
+ candidate.enabled = input.enabled;
81
+ if (input.activation !== undefined)
82
+ candidate.activation = input.activation;
83
+ if (input.condition !== undefined)
84
+ candidate.condition = input.condition;
85
+ if (input.action !== undefined)
86
+ candidate.action = input.action;
87
+ if (input.delivery !== undefined)
88
+ candidate.delivery = input.delivery;
89
+ if (input.policies !== undefined)
90
+ candidate.policies = input.policies;
91
+ if (legacyMetadata !== undefined)
92
+ candidate.legacyMetadata = legacyMetadata;
93
+ candidate.updatedAt = new Date().toISOString();
94
+ const definition = normalizeDefinition(candidate);
95
+ this.writeDefinition(definition);
96
+ return this.get(id);
97
+ }
98
+ updateState(id, input) {
99
+ const existing = this.requireDefinition(id);
100
+ const candidate = { ...existing, updatedAt: new Date().toISOString() };
101
+ this.applyNullable(candidate, input, 'blockedReason');
102
+ this.applyNullable(candidate, input, 'lastSuccessfulMatch');
103
+ this.applyNullable(candidate, input, 'lastEvaluationAt');
104
+ this.applyNullable(candidate, input, 'lastTriggeredAt');
105
+ if (input.health !== undefined)
106
+ candidate.health = input.health;
107
+ if (input.consecutiveEvaluationFailures !== undefined) {
108
+ candidate.consecutiveEvaluationFailures = input.consecutiveEvaluationFailures;
109
+ }
110
+ if (candidate.health === 'healthy') {
111
+ delete candidate.blockedReason;
112
+ }
113
+ else if (candidate.health === 'blocked' &&
114
+ (typeof candidate.blockedReason !== 'string' || !candidate.blockedReason.trim())) {
115
+ throw new Error('Blocked automation health requires a non-empty blocked reason.');
116
+ }
117
+ const definition = normalizeDefinition(candidate);
118
+ const nextCheckAt = Object.prototype.hasOwnProperty.call(input, 'nextCheckAt')
119
+ ? input.nextCheckAt ?? null
120
+ : this.getNextCheckAt(id);
121
+ const canonicalNextCheckAt = nextCheckAt === null
122
+ ? null
123
+ : assertIsoTimestamp(nextCheckAt, 'Automation nextCheckAt');
124
+ this.writeDefinition(definition, canonicalNextCheckAt);
125
+ return this.get(id);
126
+ }
127
+ delete(id) {
128
+ const result = this.db.prepare('DELETE FROM automations WHERE id = ?').run(id);
129
+ if (result.changes === 0)
130
+ throw new Error(`Automation not found: ${id}`);
131
+ return { deleted: true };
132
+ }
133
+ createEvaluation(automationId, input) {
134
+ this.requireDefinition(automationId);
135
+ const evaluation = validateEvaluation({
136
+ id: `automation-evaluation:${(0, node_crypto_1.randomUUID)()}`,
137
+ automationId,
138
+ status: 'running',
139
+ activationKind: input.activationKind,
140
+ ...(input.scriptVersionId === undefined ? {} : { scriptVersionId: input.scriptVersionId }),
141
+ startedAt: input.startedAt,
142
+ });
143
+ this.insertEvaluation(evaluation);
144
+ return this.getEvaluation(evaluation.id);
145
+ }
146
+ finishEvaluation(evaluationId, input) {
147
+ const existing = this.requireEvaluation(evaluationId);
148
+ if (existing.status !== 'running')
149
+ throw new Error(`Automation evaluation already finished: ${evaluationId}`);
150
+ const evaluation = validateEvaluation({
151
+ id: existing.id,
152
+ automationId: existing.automationId,
153
+ activationKind: existing.activationKind,
154
+ ...(existing.scriptVersionId === undefined ? {} : { scriptVersionId: existing.scriptVersionId }),
155
+ startedAt: existing.startedAt,
156
+ status: 'finished',
157
+ ...pickEvaluationFinishFields(input),
158
+ });
159
+ if (evaluation.status !== 'finished')
160
+ throw new Error('Finished automation evaluation validation failed.');
161
+ this.db.prepare(`
162
+ UPDATE automation_evaluations
163
+ SET status = ?, finished_at = ?, evaluation_json = ?
164
+ WHERE id = ?
165
+ `).run(evaluation.status, evaluation.finishedAt, JSON.stringify(evaluation), evaluationId);
166
+ return this.getEvaluation(evaluationId);
167
+ }
168
+ listEvaluations(automationId) {
169
+ const rows = this.db.prepare(`
170
+ SELECT ${EVALUATION_COLUMNS}
171
+ FROM automation_evaluations
172
+ WHERE automation_id = ?
173
+ ORDER BY started_at DESC, id DESC
174
+ `).all(automationId);
175
+ return rows.map((row) => this.toEvaluation(row));
176
+ }
177
+ getLatestEvaluationWithState(automationId) {
178
+ const row = this.db.prepare(`
179
+ SELECT ${EVALUATION_COLUMNS}
180
+ FROM automation_evaluations
181
+ WHERE automation_id = ?
182
+ AND status = 'finished'
183
+ AND json_type(evaluation_json, '$.nextState') = 'object'
184
+ ORDER BY started_at DESC, id DESC
185
+ LIMIT 1
186
+ `).get(automationId);
187
+ return row ? this.toEvaluation(row) : undefined;
188
+ }
189
+ createRun(automationId, evaluationId, input = {}) {
190
+ const automation = this.requireDefinition(automationId);
191
+ const evaluation = this.requireEvaluation(evaluationId);
192
+ if (evaluation.automationId !== automationId) {
193
+ throw new Error(`Automation evaluation ${evaluationId} does not belong to automation ${automationId}.`);
194
+ }
195
+ if (evaluation.status !== 'finished' ||
196
+ evaluation.conditionOutcome !== 'matched' ||
197
+ evaluation.triggerDecision !== 'triggered') {
198
+ throw new Error('Automation action runs require a finished evaluation with matched outcome and triggered decision.');
199
+ }
200
+ if (this.getRunByEvaluation(evaluationId)) {
201
+ throw new Error(`Automation evaluation already has an action run: ${evaluationId}`);
202
+ }
203
+ const mutableFields = pickRunMutableFields(input);
204
+ const run = validateRun({
205
+ id: `automation-run:${(0, node_crypto_1.randomUUID)()}`,
206
+ automationId,
207
+ evaluationId,
208
+ executionMode: automation.action.executionMode,
209
+ createdAt: new Date().toISOString(),
210
+ status: 'queued',
211
+ ...mutableFields,
212
+ });
213
+ this.db.prepare(`
214
+ INSERT INTO automation_runs (id, automation_id, evaluation_id, status, created_at, run_json)
215
+ VALUES (?, ?, ?, ?, ?, ?)
216
+ `).run(run.id, run.automationId, run.evaluationId, run.status, run.createdAt, JSON.stringify(run));
217
+ return this.getRun(run.id);
218
+ }
219
+ updateRun(runId, input) {
220
+ const existing = this.requireRun(runId);
221
+ const run = validateRun({ ...existing, ...pickRunMutableFields(input) });
222
+ this.db.prepare('UPDATE automation_runs SET status = ?, run_json = ? WHERE id = ?')
223
+ .run(run.status, JSON.stringify(run), runId);
224
+ return this.getRun(runId);
225
+ }
226
+ listRuns(automationId) {
227
+ const rows = this.db.prepare(`
228
+ SELECT ${RUN_COLUMNS}
229
+ FROM automation_runs
230
+ WHERE automation_id = ?
231
+ ORDER BY created_at DESC, id DESC
232
+ `).all(automationId);
233
+ return rows.map((row) => this.toRun(row));
234
+ }
235
+ reconcileInterruptedRuns(reason, finishedAt) {
236
+ const interruptedAt = assertIsoTimestamp(finishedAt, 'Automation interrupted run finishedAt');
237
+ const rows = this.db.prepare(`
238
+ SELECT ${RUN_COLUMNS}
239
+ FROM automation_runs
240
+ WHERE status IN ('queued', 'running')
241
+ ORDER BY created_at, id
242
+ `).all();
243
+ return rows.map((row) => this.updateRun(row.id, {
244
+ status: 'failed',
245
+ deliveryStatus: 'failed',
246
+ error: reason,
247
+ finishedAt: interruptedAt,
248
+ }));
249
+ }
250
+ importLegacyRecords() {
251
+ this.db.exec('BEGIN IMMEDIATE');
252
+ try {
253
+ let scheduled = 0;
254
+ let monitors = 0;
255
+ const jobs = this.db.prepare(`
256
+ SELECT id, workspace_id, platform, route_type, route_config, execution_mode, trigger_type, cron_expr, run_at,
257
+ prompt_template, description, enabled, concurrency_policy, created_at, updated_at, last_run_at,
258
+ last_status, last_error
259
+ FROM scheduled_jobs
260
+ `).all();
261
+ for (const row of jobs) {
262
+ try {
263
+ scheduled += this.importScheduledJob(row);
264
+ }
265
+ catch (error) {
266
+ throw withContext(`Scheduled job ${row.id} failed legacy import`, error);
267
+ }
268
+ }
269
+ const legacyMonitors = this.db.prepare(`
270
+ SELECT id, workspace_id, title, source_type, source_config_json, condition_json, prompt_template, platform,
271
+ route_type, route_config, execution_mode, enabled, cooldown_ms, concurrency_policy, last_state_json,
272
+ created_at, updated_at, last_triggered_at, last_status, last_error
273
+ FROM automation_monitors
274
+ `).all();
275
+ for (const row of legacyMonitors) {
276
+ try {
277
+ monitors += this.importMonitor(row);
278
+ }
279
+ catch (error) {
280
+ throw withContext(`Automation monitor ${row.id} failed legacy import`, error);
281
+ }
282
+ }
283
+ this.db.exec('COMMIT');
284
+ return { scheduled, monitors };
285
+ }
286
+ catch (error) {
287
+ // Legacy migration is intentionally atomic and fail-fast; Task 4 reports this as degraded startup.
288
+ this.db.exec('ROLLBACK');
289
+ throw error;
290
+ }
291
+ }
292
+ pruneEvaluations(now) {
293
+ if (Number.isNaN(now.getTime()))
294
+ throw new Error('Evaluation retention time must be a valid date.');
295
+ const cutoff = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1_000).toISOString();
296
+ const result = this.db.prepare(`
297
+ WITH ranked AS (
298
+ SELECT id, automation_id, started_at,
299
+ ROW_NUMBER() OVER (PARTITION BY automation_id ORDER BY started_at DESC, id DESC) AS position
300
+ FROM automation_evaluations
301
+ ), state_ranked AS (
302
+ SELECT id,
303
+ ROW_NUMBER() OVER (PARTITION BY automation_id ORDER BY started_at DESC, id DESC) AS position
304
+ FROM automation_evaluations
305
+ WHERE json_type(evaluation_json, '$.nextState') IS NOT NULL
306
+ ), latest_state AS (
307
+ SELECT id FROM state_ranked WHERE position = 1
308
+ )
309
+ DELETE FROM automation_evaluations
310
+ WHERE id IN (SELECT id FROM ranked WHERE started_at < ? OR position > 1000)
311
+ AND id NOT IN (SELECT id FROM latest_state)
312
+ AND NOT EXISTS (
313
+ SELECT 1 FROM automation_runs WHERE automation_runs.evaluation_id = automation_evaluations.id
314
+ )
315
+ `).run(cutoff);
316
+ return Number(result.changes);
317
+ }
318
+ getEvaluation(id) {
319
+ const row = this.db.prepare(`SELECT ${EVALUATION_COLUMNS} FROM automation_evaluations WHERE id = ?`)
320
+ .get(id);
321
+ return row ? this.toEvaluation(row) : undefined;
322
+ }
323
+ getRun(id) {
324
+ const row = this.db.prepare(`SELECT ${RUN_COLUMNS} FROM automation_runs WHERE id = ?`)
325
+ .get(id);
326
+ return row ? this.toRun(row) : undefined;
327
+ }
328
+ getRunByEvaluation(evaluationId) {
329
+ const row = this.db.prepare(`SELECT ${RUN_COLUMNS} FROM automation_runs WHERE evaluation_id = ?`)
330
+ .get(evaluationId);
331
+ return row ? this.toRun(row) : undefined;
332
+ }
333
+ requireDefinition(id) {
334
+ const definition = this.get(id);
335
+ if (!definition)
336
+ throw new Error(`Automation not found: ${id}`);
337
+ return definition;
338
+ }
339
+ requireEvaluation(id) {
340
+ const evaluation = this.getEvaluation(id);
341
+ if (!evaluation)
342
+ throw new Error(`Automation evaluation not found: ${id}`);
343
+ return evaluation;
344
+ }
345
+ requireRun(id) {
346
+ const run = this.getRun(id);
347
+ if (!run)
348
+ throw new Error(`Automation run not found: ${id}`);
349
+ return run;
350
+ }
351
+ insertDefinition(definition, nextCheckAt = null, ignoreConflict = false) {
352
+ const canonicalDefinition = normalizeDefinition(definition);
353
+ const canonicalNextCheckAt = nextCheckAt === null
354
+ ? null
355
+ : assertIsoTimestamp(nextCheckAt, 'Automation nextCheckAt');
356
+ const result = this.db.prepare(`
357
+ INSERT INTO automations (
358
+ id, workspace_id, title, enabled, health, blocked_reason, activation_json, condition_json, action_json,
359
+ delivery_json, policies_json, last_successful_match, last_evaluation_at, last_triggered_at,
360
+ consecutive_evaluation_failures, next_check_at, origin_kind, created_at, updated_at, legacy_metadata_json
361
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
362
+ ${ignoreConflict ? 'ON CONFLICT(id) DO NOTHING' : ''}
363
+ `).run(...this.definitionValues(canonicalDefinition, canonicalNextCheckAt));
364
+ return Number(result.changes);
365
+ }
366
+ insertEvaluation(evaluation, ignoreConflict = false) {
367
+ const finishedAt = evaluation.status === 'finished' ? evaluation.finishedAt : null;
368
+ const result = this.db.prepare(`
369
+ INSERT INTO automation_evaluations (
370
+ id, automation_id, status, activation_kind, script_version_id, started_at, finished_at, evaluation_json
371
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
372
+ ${ignoreConflict ? 'ON CONFLICT(id) DO NOTHING' : ''}
373
+ `).run(evaluation.id, evaluation.automationId, evaluation.status, evaluation.activationKind, evaluation.scriptVersionId ?? null, evaluation.startedAt, finishedAt, JSON.stringify(evaluation));
374
+ return Number(result.changes);
375
+ }
376
+ writeDefinition(definition, nextCheckAt = this.getNextCheckAt(definition.id)) {
377
+ const canonicalDefinition = normalizeDefinition(definition);
378
+ const canonicalNextCheckAt = nextCheckAt === null
379
+ ? null
380
+ : assertIsoTimestamp(nextCheckAt, 'Automation nextCheckAt');
381
+ this.db.prepare(`
382
+ UPDATE automations SET
383
+ workspace_id = ?, title = ?, enabled = ?, health = ?, blocked_reason = ?, activation_json = ?,
384
+ condition_json = ?, action_json = ?, delivery_json = ?, policies_json = ?, last_successful_match = ?,
385
+ last_evaluation_at = ?, last_triggered_at = ?, consecutive_evaluation_failures = ?, next_check_at = ?,
386
+ origin_kind = ?, created_at = ?, updated_at = ?, legacy_metadata_json = ?
387
+ WHERE id = ?
388
+ `).run(...this.definitionValues(canonicalDefinition, canonicalNextCheckAt).slice(1), canonicalDefinition.id);
389
+ }
390
+ definitionValues(definition, nextCheckAt) {
391
+ return [
392
+ definition.id,
393
+ definition.workspaceId,
394
+ definition.title,
395
+ definition.enabled ? 1 : 0,
396
+ definition.health,
397
+ definition.blockedReason ?? null,
398
+ JSON.stringify(definition.activation),
399
+ JSON.stringify(definition.condition),
400
+ JSON.stringify(definition.action),
401
+ JSON.stringify(definition.delivery),
402
+ JSON.stringify(definition.policies),
403
+ definition.lastSuccessfulMatch === undefined ? null : definition.lastSuccessfulMatch ? 1 : 0,
404
+ definition.lastEvaluationAt ?? null,
405
+ definition.lastTriggeredAt ?? null,
406
+ definition.consecutiveEvaluationFailures,
407
+ nextCheckAt,
408
+ definition.originKind ?? 'native',
409
+ definition.createdAt,
410
+ definition.updatedAt,
411
+ definition.legacyMetadata ? JSON.stringify(definition.legacyMetadata) : null,
412
+ ];
413
+ }
414
+ getNextCheckAt(id) {
415
+ const row = this.db.prepare('SELECT next_check_at FROM automations WHERE id = ?').get(id);
416
+ return row?.next_check_at ?? null;
417
+ }
418
+ toDefinition(row) {
419
+ try {
420
+ return normalizeDefinition({
421
+ id: row.id,
422
+ workspaceId: row.workspace_id,
423
+ title: row.title,
424
+ enabled: row.enabled === 1,
425
+ health: row.health,
426
+ ...(row.blocked_reason ? { blockedReason: row.blocked_reason } : {}),
427
+ activation: parseStoredJson(row.activation_json, `Automation ${row.id} activation`),
428
+ condition: parseStoredJson(row.condition_json, `Automation ${row.id} condition`),
429
+ action: parseStoredJson(row.action_json, `Automation ${row.id} action`),
430
+ delivery: parseStoredJson(row.delivery_json, `Automation ${row.id} delivery`),
431
+ policies: parseStoredJson(row.policies_json, `Automation ${row.id} policies`),
432
+ ...(row.last_successful_match === null ? {} : { lastSuccessfulMatch: row.last_successful_match === 1 }),
433
+ ...(row.last_evaluation_at ? { lastEvaluationAt: row.last_evaluation_at } : {}),
434
+ ...(row.last_triggered_at ? { lastTriggeredAt: row.last_triggered_at } : {}),
435
+ consecutiveEvaluationFailures: row.consecutive_evaluation_failures,
436
+ originKind: row.origin_kind,
437
+ ...(row.legacy_metadata_json
438
+ ? { legacyMetadata: parseStoredJson(row.legacy_metadata_json, `Automation ${row.id} legacy metadata`) }
439
+ : {}),
440
+ createdAt: row.created_at,
441
+ updatedAt: row.updated_at,
442
+ });
443
+ }
444
+ catch (error) {
445
+ throw withContext(`Automation ${row.id} contains invalid persisted data`, error);
446
+ }
447
+ }
448
+ toEvaluation(row) {
449
+ try {
450
+ const evaluation = validateEvaluation(parseStoredJson(row.evaluation_json, `Automation evaluation ${row.id}`));
451
+ const finishedAt = evaluation.status === 'finished' ? evaluation.finishedAt : null;
452
+ assertDuplicatedField('id', row.id, evaluation.id);
453
+ assertDuplicatedField('automationId', row.automation_id, evaluation.automationId);
454
+ assertDuplicatedField('status', row.status, evaluation.status);
455
+ assertDuplicatedField('activationKind', row.activation_kind, evaluation.activationKind);
456
+ assertDuplicatedField('scriptVersionId', row.script_version_id, evaluation.scriptVersionId ?? null);
457
+ assertDuplicatedField('startedAt', row.started_at, evaluation.startedAt);
458
+ assertDuplicatedField('finishedAt', row.finished_at, finishedAt);
459
+ return evaluation;
460
+ }
461
+ catch (error) {
462
+ throw withContext(`Automation evaluation ${row.id} contains invalid persisted data`, error);
463
+ }
464
+ }
465
+ toRun(row) {
466
+ try {
467
+ const run = validateRun(parseStoredJson(row.run_json, `Automation run ${row.id}`));
468
+ assertDuplicatedField('id', row.id, run.id);
469
+ assertDuplicatedField('automationId', row.automation_id, run.automationId);
470
+ assertDuplicatedField('evaluationId', row.evaluation_id, run.evaluationId);
471
+ assertDuplicatedField('status', row.status, run.status);
472
+ assertDuplicatedField('createdAt', row.created_at, run.createdAt);
473
+ return run;
474
+ }
475
+ catch (error) {
476
+ throw withContext(`Automation run ${row.id} contains invalid persisted data`, error);
477
+ }
478
+ }
479
+ importScheduledJob(row) {
480
+ const consumedOnce = row.trigger_type === 'once' && Boolean(row.last_run_at || row.last_status);
481
+ const activation = row.trigger_type === 'once'
482
+ ? { kind: 'once', runAt: row.run_at }
483
+ : { kind: 'cron', expression: row.cron_expr, timezone: 'UTC' };
484
+ const definition = normalizeDefinition({
485
+ id: row.id,
486
+ workspaceId: row.workspace_id,
487
+ title: row.description.trim() || row.prompt_template.trim(),
488
+ enabled: consumedOnce ? false : row.enabled === 1,
489
+ health: 'healthy',
490
+ activation,
491
+ condition: { kind: 'always' },
492
+ action: { kind: 'agent-prompt', promptTemplate: row.prompt_template, executionMode: row.execution_mode },
493
+ delivery: {
494
+ platform: row.platform,
495
+ route: parseStoredJson(row.route_config, `Scheduled job ${row.id} route`),
496
+ },
497
+ policies: { concurrency: 'skip-if-running', cooldownMs: 0 },
498
+ ...(row.last_status ? { lastSuccessfulMatch: row.last_status === 'succeeded' } : {}),
499
+ ...(row.last_run_at || row.last_status
500
+ ? { lastEvaluationAt: row.last_run_at || row.updated_at }
501
+ : {}),
502
+ ...(row.last_run_at ? { lastTriggeredAt: row.last_run_at } : {}),
503
+ consecutiveEvaluationFailures: row.last_status === 'failed' ? 1 : 0,
504
+ originKind: 'scheduled-job',
505
+ legacyMetadata: { scheduledDescription: row.description },
506
+ createdAt: row.created_at,
507
+ updatedAt: row.updated_at,
508
+ });
509
+ return this.insertDefinition(definition, row.trigger_type === 'once' && !consumedOnce ? row.run_at : null, true);
510
+ }
511
+ importMonitor(row) {
512
+ const sourceConfig = parseStoredJson(row.source_config_json, `Automation monitor ${row.id} source config`);
513
+ const legacyCondition = asRecord(parseStoredJson(row.condition_json, `Automation monitor ${row.id} condition`), `Automation monitor ${row.id} condition`);
514
+ const lastState = row.last_state_json
515
+ ? asRecord(parseStoredJson(row.last_state_json, `Automation monitor ${row.id} state`), `Automation monitor ${row.id} state`)
516
+ : undefined;
517
+ const expression = typeof legacyCondition.expression === 'string' && legacyCondition.expression.trim()
518
+ ? legacyCondition.expression.trim()
519
+ : `${String(legacyCondition.metric || '').trim()} ${String(legacyCondition.operator || '').trim()} ${JSON.stringify(legacyCondition.value)}`;
520
+ const definition = normalizeDefinition({
521
+ id: row.id,
522
+ workspaceId: row.workspace_id,
523
+ title: row.title,
524
+ enabled: row.enabled === 1,
525
+ health: 'healthy',
526
+ activation: { kind: 'provider-event', sourceType: row.source_type, sourceConfig },
527
+ condition: { kind: 'expression', expression },
528
+ action: { kind: 'agent-prompt', promptTemplate: row.prompt_template, executionMode: row.execution_mode },
529
+ delivery: {
530
+ platform: row.platform,
531
+ route: parseStoredJson(row.route_config, `Automation monitor ${row.id} route`),
532
+ },
533
+ policies: { concurrency: 'skip-if-running', cooldownMs: row.cooldown_ms },
534
+ ...(typeof lastState?.lastSuccessfulMatch === 'boolean'
535
+ ? { lastSuccessfulMatch: lastState.lastSuccessfulMatch }
536
+ : {}),
537
+ ...(row.last_triggered_at ? { lastEvaluationAt: row.last_triggered_at, lastTriggeredAt: row.last_triggered_at } : {}),
538
+ consecutiveEvaluationFailures: row.last_status === 'failed' ? 1 : 0,
539
+ originKind: 'automation-monitor',
540
+ createdAt: row.created_at,
541
+ updatedAt: row.updated_at,
542
+ });
543
+ const imported = this.insertDefinition(definition, null, true);
544
+ if (lastState && this.get(row.id)?.originKind === 'automation-monitor') {
545
+ const stateTimestamp = row.last_triggered_at ?? row.updated_at;
546
+ const stateEvaluation = validateEvaluation({
547
+ id: `automation-evaluation:legacy-monitor-state:${row.id}`,
548
+ automationId: row.id,
549
+ status: 'finished',
550
+ activationKind: 'provider-event',
551
+ startedAt: stateTimestamp,
552
+ conditionOutcome: 'skipped',
553
+ triggerDecision: 'not_evaluated',
554
+ finishedAt: stateTimestamp,
555
+ resultSummary: 'Imported legacy monitor state.',
556
+ nextState: lastState,
557
+ });
558
+ this.insertEvaluation(stateEvaluation, true);
559
+ }
560
+ return imported;
561
+ }
562
+ applyNullable(candidate, input, key) {
563
+ if (!Object.prototype.hasOwnProperty.call(input, key))
564
+ return;
565
+ const value = input[key];
566
+ if (value === null || value === undefined)
567
+ delete candidate[key];
568
+ else
569
+ candidate[key] = value;
570
+ }
571
+ }
572
+ exports.LocalAutomationStore = LocalAutomationStore;
573
+ function pickEvaluationFinishFields(input) {
574
+ const fields = {
575
+ conditionOutcome: input.conditionOutcome,
576
+ triggerDecision: input.triggerDecision,
577
+ finishedAt: input.finishedAt,
578
+ };
579
+ for (const key of [
580
+ 'triggeredAt',
581
+ 'durationMs',
582
+ 'exitCode',
583
+ 'errorCategory',
584
+ 'stdout',
585
+ 'stderr',
586
+ 'outputTruncated',
587
+ 'resultSummary',
588
+ 'payload',
589
+ 'nextState',
590
+ 'sandboxViolations',
591
+ 'networkAudit',
592
+ ]) {
593
+ if (input[key] !== undefined)
594
+ fields[key] = input[key];
595
+ }
596
+ return fields;
597
+ }
598
+ function pickRunMutableFields(input) {
599
+ const fields = {};
600
+ for (const key of [
601
+ 'status',
602
+ 'threadId',
603
+ 'acpRunId',
604
+ 'deliveryStatus',
605
+ 'bridgeActivity',
606
+ 'error',
607
+ 'startedAt',
608
+ 'finishedAt',
609
+ ]) {
610
+ if (input[key] !== undefined)
611
+ fields[key] = input[key];
612
+ }
613
+ return fields;
614
+ }
615
+ function normalizeDefinition(value) {
616
+ const definition = { ...(0, superai_contracts_1.normalizeAutomationDefinition)(value) };
617
+ definition.createdAt = assertIsoTimestamp(definition.createdAt, 'Automation createdAt');
618
+ definition.updatedAt = assertIsoTimestamp(definition.updatedAt, 'Automation updatedAt');
619
+ if (definition.lastEvaluationAt !== undefined) {
620
+ definition.lastEvaluationAt = assertIsoTimestamp(definition.lastEvaluationAt, 'Automation lastEvaluationAt');
621
+ }
622
+ if (definition.lastTriggeredAt !== undefined) {
623
+ definition.lastTriggeredAt = assertIsoTimestamp(definition.lastTriggeredAt, 'Automation lastTriggeredAt');
624
+ }
625
+ if (definition.activation.kind === 'once') {
626
+ definition.activation = {
627
+ ...definition.activation,
628
+ runAt: assertIsoTimestamp(definition.activation.runAt, 'Automation runAt'),
629
+ };
630
+ }
631
+ return definition;
632
+ }
633
+ function assertDuplicatedField(label, indexedValue, jsonValue) {
634
+ if (!Object.is(indexedValue, jsonValue)) {
635
+ throw new Error(`stored ${label} does not match its indexed column.`);
636
+ }
637
+ }
638
+ function validateEvaluation(value) {
639
+ const input = asRecord(value, 'Automation evaluation');
640
+ const id = requiredString(input.id, 'Automation evaluation id');
641
+ const automationId = requiredString(input.automationId, 'Automation evaluation automationId');
642
+ const activationKind = input.activationKind;
643
+ if (activationKind !== 'cron' && activationKind !== 'once' && activationKind !== 'interval' && activationKind !== 'provider-event') {
644
+ throw new Error('Automation evaluation activationKind is invalid.');
645
+ }
646
+ const startedAt = assertIsoTimestamp(input.startedAt, 'Automation evaluation startedAt');
647
+ const scriptVersionId = optionalString(input.scriptVersionId, 'Automation evaluation scriptVersionId');
648
+ if (input.status === 'running') {
649
+ if (input.conditionOutcome !== undefined || input.triggerDecision !== undefined || input.finishedAt !== undefined) {
650
+ throw new Error('Running automation evaluations cannot contain finish fields.');
651
+ }
652
+ return { id, automationId, status: 'running', activationKind, ...(scriptVersionId ? { scriptVersionId } : {}), startedAt };
653
+ }
654
+ if (input.status !== 'finished')
655
+ throw new Error('Automation evaluation status must be running or finished.');
656
+ const conditionOutcome = input.conditionOutcome;
657
+ const triggerDecision = input.triggerDecision;
658
+ const allowed = (conditionOutcome === 'matched' && ['triggered', 'not_rising', 'skipped_cooldown', 'skipped_action_running'].includes(String(triggerDecision))) ||
659
+ (conditionOutcome === 'not_matched' && triggerDecision === 'not_rising') ||
660
+ (conditionOutcome === 'error' && triggerDecision === 'not_evaluated') ||
661
+ (conditionOutcome === 'skipped' && ['not_evaluated', 'skipped_concurrent', 'skipped_cooldown', 'skipped_action_running'].includes(String(triggerDecision)));
662
+ if (!allowed)
663
+ throw new Error('Automation evaluation condition outcome and trigger decision are incompatible.');
664
+ const result = {
665
+ id,
666
+ automationId,
667
+ status: 'finished',
668
+ activationKind,
669
+ ...(scriptVersionId ? { scriptVersionId } : {}),
670
+ startedAt,
671
+ conditionOutcome,
672
+ triggerDecision,
673
+ finishedAt: assertIsoTimestamp(input.finishedAt, 'Automation evaluation finishedAt'),
674
+ };
675
+ for (const key of ['triggeredAt']) {
676
+ if (input[key] !== undefined)
677
+ result[key] = assertIsoTimestamp(input[key], `Automation evaluation ${key}`);
678
+ }
679
+ for (const key of ['durationMs', 'exitCode']) {
680
+ if (input[key] !== undefined)
681
+ result[key] = assertInteger(input[key], `Automation evaluation ${key}`, key === 'durationMs');
682
+ }
683
+ for (const key of ['errorCategory', 'stdout', 'stderr', 'resultSummary']) {
684
+ if (input[key] !== undefined)
685
+ result[key] = assertString(input[key], `Automation evaluation ${key}`);
686
+ }
687
+ if (input.outputTruncated !== undefined) {
688
+ if (typeof input.outputTruncated !== 'boolean')
689
+ throw new Error('Automation evaluation outputTruncated must be a boolean.');
690
+ result.outputTruncated = input.outputTruncated;
691
+ }
692
+ for (const key of ['payload', 'nextState']) {
693
+ if (input[key] !== undefined)
694
+ result[key] = asRecord(input[key], `Automation evaluation ${key}`);
695
+ }
696
+ for (const key of ['sandboxViolations']) {
697
+ if (input[key] !== undefined)
698
+ result[key] = stringArray(input[key], `Automation evaluation ${key}`);
699
+ }
700
+ if (input.networkAudit !== undefined) {
701
+ if (!Array.isArray(input.networkAudit))
702
+ throw new Error('Automation evaluation networkAudit must be an array.');
703
+ result.networkAudit = input.networkAudit.map((entry, index) => {
704
+ const record = asRecord(entry, `Automation evaluation networkAudit[${index}]`);
705
+ if (typeof record.allowed !== 'boolean')
706
+ throw new Error('Automation evaluation networkAudit allowed must be a boolean.');
707
+ if (record.host === undefined) {
708
+ const legacy = requiredString(record.target, 'Automation evaluation networkAudit target');
709
+ const parsed = safeLegacyNetworkTarget(legacy);
710
+ return { host: parsed.host, ...(parsed.port === undefined ? {} : { port: parsed.port }), allowed: record.allowed };
711
+ }
712
+ const port = record.port === undefined ? undefined : assertInteger(record.port, 'Automation evaluation networkAudit port', true);
713
+ return {
714
+ host: requiredString(record.host, 'Automation evaluation networkAudit host').toLowerCase(),
715
+ ...(port === undefined ? {} : { port }), allowed: record.allowed,
716
+ ...(record.timestamp === undefined ? {} : { timestamp: requiredString(record.timestamp, 'Automation evaluation networkAudit timestamp') }),
717
+ };
718
+ });
719
+ }
720
+ return result;
721
+ }
722
+ function safeLegacyNetworkTarget(target) {
723
+ try {
724
+ const url = new URL(target.includes('://') ? target : `https://${target}`);
725
+ const port = url.port ? Number(url.port) : undefined;
726
+ return { host: url.hostname.toLowerCase(), ...(Number.isSafeInteger(port) && port >= 0 ? { port } : {}) };
727
+ }
728
+ catch {
729
+ return { host: 'legacy-invalid-target' };
730
+ }
731
+ }
732
+ function validateRun(value) {
733
+ const input = asRecord(value, 'Automation run');
734
+ const status = input.status;
735
+ if (status !== 'queued' && status !== 'running' && status !== 'succeeded' && status !== 'failed' && status !== 'skipped') {
736
+ throw new Error('Automation run status is invalid.');
737
+ }
738
+ const executionMode = input.executionMode;
739
+ if (executionMode !== 'same-thread' && executionMode !== 'side-thread') {
740
+ throw new Error('Automation run executionMode is invalid.');
741
+ }
742
+ const run = {
743
+ id: requiredString(input.id, 'Automation run id'),
744
+ automationId: requiredString(input.automationId, 'Automation run automationId'),
745
+ evaluationId: requiredString(input.evaluationId, 'Automation run evaluationId'),
746
+ status,
747
+ executionMode,
748
+ createdAt: assertIsoTimestamp(input.createdAt, 'Automation run createdAt'),
749
+ };
750
+ for (const key of ['threadId', 'acpRunId', 'error']) {
751
+ if (input[key] !== undefined)
752
+ run[key] = assertString(input[key], `Automation run ${key}`);
753
+ }
754
+ for (const key of ['startedAt', 'finishedAt']) {
755
+ if (input[key] !== undefined)
756
+ run[key] = assertIsoTimestamp(input[key], `Automation run ${key}`);
757
+ }
758
+ if (input.deliveryStatus !== undefined) {
759
+ if (!['pending', 'delivering', 'delivered', 'failed'].includes(String(input.deliveryStatus))) {
760
+ throw new Error('Automation run deliveryStatus is invalid.');
761
+ }
762
+ run.deliveryStatus = input.deliveryStatus;
763
+ }
764
+ if (input.bridgeActivity !== undefined)
765
+ run.bridgeActivity = asRecord(input.bridgeActivity, 'Automation run bridgeActivity');
766
+ return run;
767
+ }
768
+ function parseStoredJson(value, label) {
769
+ try {
770
+ return JSON.parse(value);
771
+ }
772
+ catch (error) {
773
+ throw withContext(`${label} is malformed JSON`, error);
774
+ }
775
+ }
776
+ function asRecord(value, label) {
777
+ if (!value || typeof value !== 'object' || Array.isArray(value))
778
+ throw new Error(`${label} must be an object.`);
779
+ return value;
780
+ }
781
+ function requiredString(value, label) {
782
+ if (typeof value !== 'string' || !value.trim())
783
+ throw new Error(`${label} is required.`);
784
+ return value.trim();
785
+ }
786
+ function optionalString(value, label) {
787
+ return value === undefined ? undefined : requiredString(value, label);
788
+ }
789
+ function assertString(value, label) {
790
+ if (typeof value !== 'string')
791
+ throw new Error(`${label} must be a string.`);
792
+ return value;
793
+ }
794
+ function assertIsoTimestamp(value, label) {
795
+ const timestamp = requiredString(value, label);
796
+ const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/.exec(timestamp);
797
+ if (!match || Number.isNaN(Date.parse(timestamp))) {
798
+ throw new Error(`${label} must be a valid ISO timestamp.`);
799
+ }
800
+ const [, yearText, monthText, dayText, hourText, minuteText, secondText] = match;
801
+ const year = Number(yearText);
802
+ const month = Number(monthText);
803
+ const day = Number(dayText);
804
+ const calendarDate = new Date(Date.UTC(year, month - 1, day));
805
+ if (calendarDate.getUTCFullYear() !== year ||
806
+ calendarDate.getUTCMonth() !== month - 1 ||
807
+ calendarDate.getUTCDate() !== day ||
808
+ Number(hourText) > 23 ||
809
+ Number(minuteText) > 59 ||
810
+ Number(secondText) > 59) {
811
+ throw new Error(`${label} must be a valid ISO timestamp.`);
812
+ }
813
+ return new Date(timestamp).toISOString();
814
+ }
815
+ function assertInteger(value, label, nonNegative = false) {
816
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || (nonNegative && value < 0)) {
817
+ throw new Error(`${label} must be ${nonNegative ? 'a non-negative ' : 'an '}integer.`);
818
+ }
819
+ return value;
820
+ }
821
+ function stringArray(value, label) {
822
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== 'string')) {
823
+ throw new Error(`${label} must be an array of strings.`);
824
+ }
825
+ return [...value];
826
+ }
827
+ function withContext(label, error) {
828
+ return new Error(`${label}: ${error instanceof Error ? error.message : String(error)}`);
829
+ }