@kafca/agentdock 0.1.61 → 0.1.63

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-DR-micIT.js +16 -0
  3. package/dist/renderer/assets/{Badge-BLI4LHse.js → Badge-COE1Zm2i.js} +1 -1
  4. package/dist/renderer/assets/{Card-DJeLDdL3.js → Card-F6UNFxnQ.js} +1 -1
  5. package/dist/renderer/assets/{Config-D1awc81A.js → Config-BKqxfhZi.js} +2 -2
  6. package/dist/renderer/assets/Dashboard-Bvp7-_2z.js +36 -0
  7. package/dist/renderer/assets/{EmptyState-CkI1RwPX.js → EmptyState-BwV_J0Og.js} +1 -1
  8. package/dist/renderer/assets/{HighlightedMarkdown-BUhDlhgW.js → HighlightedMarkdown-CBIBaWyM.js} +1 -1
  9. package/dist/renderer/assets/{Input-DHYrn-Ne.js → Input-C8LZd4Bi.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeDetail-Bij-Xo90.js → KnowledgeDetail-DjXYNMIs.js} +2 -2
  11. package/dist/renderer/assets/{KnowledgeHome-DmPZ2IlI.js → KnowledgeHome-Cqk5_nJh.js} +2 -2
  12. package/dist/renderer/assets/{Logs-ISY99heA.js → Logs-B6cXFvW4.js} +2 -2
  13. package/dist/renderer/assets/{Modal-DAQfz0ot.js → Modal-CAj7soll.js} +3 -3
  14. package/dist/renderer/assets/{Page-Dw43UYDT.js → Page-DlnMnu4F.js} +1 -1
  15. package/dist/renderer/assets/{Select-DzmN5OGQ.js → Select-CVay_LUX.js} +1 -1
  16. package/dist/renderer/assets/{ThreadChat-B5CWqqMK.js → ThreadChat-BxXRpYa1.js} +3 -3
  17. package/dist/renderer/assets/{Workspace-C5g9LBj9.js → Workspace-YC0pC7cM.js} +1 -1
  18. package/dist/renderer/assets/{arrow-left-BLXh56Ip.js → arrow-left-Cwm09dxx.js} +1 -1
  19. package/dist/renderer/assets/{book-open-DhPGbc-7.js → book-open-BwcGC_im.js} +1 -1
  20. package/dist/renderer/assets/{channels-DfQrmuYO.js → channels-BPytmVCd.js} +2 -2
  21. package/dist/renderer/assets/{chevron-down-CEFtpPDc.js → chevron-down-2LP9oGHG.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-eoVERL5K.js +162 -0
  26. package/dist/renderer/assets/{index-CYhDUAR_.js → index-llCPCiYk.js} +1 -1
  27. package/dist/renderer/assets/ja-CDWPWqEW.js +1 -0
  28. package/dist/renderer/assets/{knowledge-uHRQ80sR.js → knowledge-QujAv-Zh.js} +1 -1
  29. package/dist/renderer/assets/{pencil-BD2Y31YC.js → pencil-0P2INETc.js} +1 -1
  30. package/dist/renderer/assets/{plus-DDcldp_c.js → plus-tiGRgE2v.js} +1 -1
  31. package/dist/renderer/assets/{save-CMW_cZI9.js → save-D8U93DNa.js} +1 -1
  32. package/dist/renderer/assets/{search-BM-0CvJr.js → search-C-9J9J3f.js} +1 -1
  33. package/dist/renderer/assets/{shield-check-DaMB0FKG.js → shield-check-BCKkPPTJ.js} +1 -1
  34. package/dist/renderer/assets/{threads-DZgt9tWj.js → threads-Bsrwwnpm.js} +1 -1
  35. package/dist/renderer/assets/{trash-2-DTXVbC5y.js → trash-2-CP4sn15n.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 +344 -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 +4 -3
  118. package/dist/renderer/assets/CronList-CupZoNwW.js +0 -1
  119. package/dist/renderer/assets/Dashboard-nLZnOtcs.js +0 -41
  120. package/dist/renderer/assets/MonitorList-BlX82rXF.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-Dd00oqgY.js +0 -167
  124. package/dist/renderer/assets/ja-DtvtBRTW.js +0 -1
  125. package/dist/renderer/assets/play-DBJ8wsSy.js +0 -6
  126. package/dist/renderer/assets/zh-CcJ9vBZj.js +0 -1
  127. package/dist/renderer/assets/zh-TW-elI72AC1.js +0 -1
@@ -5,7 +5,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const node_test_1 = __importDefault(require("node:test"));
7
7
  const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const node_fs_1 = require("node:fs");
9
+ const node_os_1 = require("node:os");
10
+ const node_path_1 = require("node:path");
8
11
  const condition_evaluator_js_1 = require("../../services/local-ai-core/src/automation/condition-evaluator.js");
12
+ const automation_monitor_service_js_1 = require("../../services/local-ai-core/src/automation/automation-monitor-service.js");
13
+ const automation_service_js_1 = require("../../services/local-ai-core/src/automation/automation-service.js");
14
+ const legacy_automation_mappers_js_1 = require("../../services/local-ai-core/src/automation/legacy-automation-mappers.js");
15
+ const local_core_acp_store_js_1 = require("../../services/local-ai-core/src/acp/local-core-acp-store.js");
16
+ const event_bus_js_1 = require("../../services/local-ai-core/src/kernel/event-bus.js");
9
17
  const event = {
10
18
  id: 'event-1',
11
19
  sourceType: 'stock.quote',
@@ -36,3 +44,1328 @@ const event = {
36
44
  expression: 'latestPrice < 100 || volumeRatio >= 2',
37
45
  }, event), true);
38
46
  });
47
+ (0, node_test_1.default)('monitor expressions preserve payload nesting and top-level metric semantics', () => {
48
+ const nestedEvent = {
49
+ ...event,
50
+ subject: 'AAPL',
51
+ sourceType: 'stock.quote',
52
+ payload: {
53
+ subject: { market: 'NASDAQ' },
54
+ sourceType: { vendor: 'IEX' },
55
+ },
56
+ };
57
+ strict_1.default.equal((0, condition_evaluator_js_1.evaluateMonitorCondition)({
58
+ metric: 'expression',
59
+ operator: '==',
60
+ value: true,
61
+ expression: 'subject.market == NASDAQ && sourceType.vendor == IEX',
62
+ }, nestedEvent), true);
63
+ strict_1.default.equal((0, condition_evaluator_js_1.evaluateMonitorCondition)({
64
+ metric: 'expression',
65
+ operator: '==',
66
+ value: true,
67
+ expression: 'subject == AAPL && sourceType == stock.quote',
68
+ }, nestedEvent), true);
69
+ });
70
+ (0, node_test_1.default)('restricted expression validation rejects malformed grammar without evaluating data', () => {
71
+ for (const expression of ['not a comparison', 'latestPrice > 1 &&', '|| latestPrice > 1', 'latestPrice === 1']) {
72
+ strict_1.default.throws(() => (0, condition_evaluator_js_1.validateRestrictedExpression)(expression), /Unsupported monitor condition expression/);
73
+ }
74
+ strict_1.default.doesNotThrow(() => (0, condition_evaluator_js_1.validateRestrictedExpression)('latestPrice >= 1 && sourceType == stock.quote'));
75
+ });
76
+ (0, node_test_1.default)('malformed legacy expressions are rejected before persistence, handles, or events', async () => {
77
+ const context = monitorFixture();
78
+ let starts = 0;
79
+ let events = 0;
80
+ context.eventBus.on('automation.definition.updated', () => { events += 1; });
81
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
82
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
83
+ providers: [{ sourceType: 'stock.quote', modes: ['subscribe'], async startMonitor() {
84
+ starts += 1;
85
+ return { stop() { } };
86
+ } }],
87
+ });
88
+ try {
89
+ await strict_1.default.rejects(() => monitors.createMonitor({
90
+ workspaceId: 'workspace', title: 'bad', sourceType: 'stock.quote',
91
+ condition: { metric: 'expression', operator: '==', value: true, expression: 'not a comparison' },
92
+ promptTemplate: 'quote',
93
+ }), /Unsupported monitor condition expression/);
94
+ strict_1.default.equal(context.automations.list().length, 0);
95
+ strict_1.default.equal(starts, 0);
96
+ strict_1.default.equal(events, 0);
97
+ }
98
+ finally {
99
+ await monitors.stop();
100
+ context.close();
101
+ }
102
+ });
103
+ (0, node_test_1.default)('persisted malformed legacy expressions project explicitly without crashing list', () => {
104
+ const context = monitorFixture();
105
+ try {
106
+ context.store.createTrustedAutomation({
107
+ workspaceId: 'workspace', title: 'old expression', enabled: true,
108
+ activation: { kind: 'provider-event', sourceType: 'stock.quote', sourceConfig: {} },
109
+ condition: { kind: 'expression', expression: 'not a comparison' },
110
+ action: { kind: 'agent-prompt', promptTemplate: 'quote', executionMode: 'side-thread' },
111
+ delivery: { platform: 'local', route: { type: 'local.thread', channelId: 'workspace' } },
112
+ policies: { concurrency: 'skip-if-running', cooldownMs: 0 },
113
+ originKind: 'automation-monitor',
114
+ });
115
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
116
+ store: context.store, automations: context.automations, eventBus: context.eventBus, providers: [],
117
+ });
118
+ strict_1.default.deepEqual(monitors.listMonitors()[0]?.condition, {
119
+ metric: 'expression', operator: '==', value: true, expression: 'not a comparison',
120
+ });
121
+ }
122
+ finally {
123
+ context.close();
124
+ }
125
+ });
126
+ function monitorFixture(execute) {
127
+ const path = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'monitor-lifecycle-'));
128
+ const store = new local_core_acp_store_js_1.LocalCoreAcpStore(path);
129
+ const eventBus = new event_bus_js_1.LocalCoreEventBus();
130
+ const actions = [];
131
+ const automations = new automation_service_js_1.AutomationService({
132
+ store,
133
+ eventBus,
134
+ actionExecutor: {
135
+ async execute({ automation }) {
136
+ actions.push(automation.id);
137
+ await execute?.(automation.id);
138
+ return { threadId: 'thread', acpRunId: `run:${automation.id}`, deliveryStatus: 'succeeded' };
139
+ },
140
+ },
141
+ ownershipPolicy: { executes: () => true },
142
+ });
143
+ return { path, store, eventBus, automations, actions, close() { store.close(); (0, node_fs_1.rmSync)(path, { recursive: true, force: true }); } };
144
+ }
145
+ (0, node_test_1.default)('subscription source reconfiguration stops the old handle before starting the new one', async () => {
146
+ const context = monitorFixture();
147
+ const transitions = [];
148
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
149
+ store: context.store,
150
+ automations: context.automations,
151
+ eventBus: context.eventBus,
152
+ providers: [{
153
+ sourceType: 'stock.quote', modes: ['subscribe'],
154
+ async startMonitor({ sourceConfig }) {
155
+ const symbol = String(sourceConfig.symbol);
156
+ transitions.push(`start:${symbol}`);
157
+ return { async stop() { transitions.push(`stop:${symbol}`); } };
158
+ },
159
+ }],
160
+ });
161
+ try {
162
+ const monitor = await monitors.createMonitor({
163
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote', sourceConfig: { symbol: 'AAPL' },
164
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
165
+ });
166
+ await monitors.updateMonitor(monitor.id, { sourceConfig: { symbol: 'MSFT' } });
167
+ strict_1.default.deepEqual(transitions, ['start:AAPL', 'stop:AAPL', 'start:MSFT']);
168
+ }
169
+ finally {
170
+ await monitors.stop();
171
+ context.close();
172
+ }
173
+ });
174
+ (0, node_test_1.default)('subscription lifecycle isolates rejecting starts and stops', async () => {
175
+ const context = monitorFixture();
176
+ const logs = [];
177
+ const stopped = [];
178
+ for (const kind of ['bad-start', 'bad-stop', 'good']) {
179
+ context.automations.createFromLegacy((0, legacy_automation_mappers_js_1.monitorToAutomationInput)({
180
+ workspaceId: 'workspace', title: kind, sourceType: 'stock.quote', sourceConfig: { kind },
181
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
182
+ platform: 'local', route: { type: 'local.thread', channelId: 'workspace' },
183
+ }));
184
+ }
185
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
186
+ store: context.store, automations: context.automations, eventBus: context.eventBus, log: (message) => logs.push(message),
187
+ providers: [{
188
+ sourceType: 'stock.quote', modes: ['subscribe'],
189
+ async startMonitor({ sourceConfig }) {
190
+ const kind = String(sourceConfig.kind);
191
+ if (kind === 'bad-start')
192
+ throw new Error('start rejected');
193
+ return { async stop() { stopped.push(kind); if (kind === 'bad-stop')
194
+ throw new Error('stop rejected'); } };
195
+ },
196
+ }],
197
+ });
198
+ try {
199
+ await Promise.all([monitors.start(), monitors.start()]);
200
+ await monitors.stop();
201
+ strict_1.default.deepEqual(stopped.sort(), ['bad-stop', 'good']);
202
+ strict_1.default.ok(logs.some((message) => message.includes('start rejected')));
203
+ strict_1.default.ok(logs.some((message) => message.includes('stop rejected')));
204
+ }
205
+ finally {
206
+ await monitors.stop();
207
+ context.close();
208
+ }
209
+ });
210
+ (0, node_test_1.default)('rejected subscription create removes the definition and retry creates exactly one monitor', async () => {
211
+ const context = monitorFixture();
212
+ let starts = 0;
213
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
214
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
215
+ providers: [{
216
+ sourceType: 'stock.quote', modes: ['subscribe'],
217
+ async startMonitor() {
218
+ starts += 1;
219
+ if (starts === 1)
220
+ throw new Error('TOKEN=subscription-secret\u001b[31m start rejected');
221
+ return { stop() { } };
222
+ },
223
+ }],
224
+ });
225
+ const input = {
226
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote', sourceConfig: { symbol: 'AAPL' },
227
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
228
+ };
229
+ try {
230
+ await strict_1.default.rejects(() => monitors.createMonitor(input), (error) => {
231
+ strict_1.default.match(error.message, /REDACTED_SECRET/);
232
+ strict_1.default.doesNotMatch(error.message, /subscription-secret|\u001b/);
233
+ return true;
234
+ });
235
+ strict_1.default.equal(context.automations.list().length, 0);
236
+ const created = await monitors.createMonitor(input);
237
+ strict_1.default.equal(starts, 2);
238
+ strict_1.default.deepEqual(context.automations.list().map((automation) => automation.id), [created.id]);
239
+ }
240
+ finally {
241
+ await monitors.stop();
242
+ context.close();
243
+ }
244
+ });
245
+ (0, node_test_1.default)('create fails closed and retains a returned handle when degradation compensation cannot stop it', async () => {
246
+ const context = monitorFixture();
247
+ let releaseStart;
248
+ const startBlocked = new Promise((resolve) => { releaseStart = resolve; });
249
+ let stopAttempts = 0;
250
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
251
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
252
+ providers: [{
253
+ sourceType: 'stock.quote', modes: ['subscribe'],
254
+ async startMonitor() {
255
+ await startBlocked;
256
+ return { async stop() { if (++stopAttempts === 1)
257
+ throw new Error('compensation stop rejected'); } };
258
+ },
259
+ }],
260
+ });
261
+ try {
262
+ const creating = monitors.createMonitor({
263
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote',
264
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
265
+ });
266
+ await new Promise((resolve) => setImmediate(resolve));
267
+ context.store.importLegacyAutomations = () => { throw new Error('migration blocked'); };
268
+ await context.automations.start();
269
+ releaseStart();
270
+ await strict_1.default.rejects(() => creating, /Unified automation migration is unavailable/);
271
+ const [definition] = context.automations.list();
272
+ strict_1.default.equal(definition?.enabled, false);
273
+ strict_1.default.equal(definition?.health, 'blocked');
274
+ await monitors.stop();
275
+ strict_1.default.equal(stopAttempts, 2);
276
+ }
277
+ finally {
278
+ releaseStart();
279
+ await context.automations.stop();
280
+ await monitors.stop();
281
+ context.close();
282
+ }
283
+ });
284
+ (0, node_test_1.default)('source update keeps the old config and tracked handle when stop rejects, then retries cleanly', async () => {
285
+ const context = monitorFixture();
286
+ const transitions = [];
287
+ let oldStopAttempts = 0;
288
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
289
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
290
+ providers: [{
291
+ sourceType: 'stock.quote', modes: ['subscribe'],
292
+ async startMonitor({ sourceConfig }) {
293
+ const symbol = String(sourceConfig.symbol);
294
+ transitions.push(`start:${symbol}`);
295
+ return {
296
+ async stop() {
297
+ transitions.push(`stop:${symbol}`);
298
+ if (symbol === 'AAPL' && ++oldStopAttempts === 1)
299
+ throw new Error('temporary stop rejection');
300
+ },
301
+ };
302
+ },
303
+ }],
304
+ });
305
+ try {
306
+ const monitor = await monitors.createMonitor({
307
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote', sourceConfig: { symbol: 'AAPL' },
308
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
309
+ });
310
+ await strict_1.default.rejects(() => monitors.updateMonitor(monitor.id, { sourceConfig: { symbol: 'MSFT' } }), /temporary stop rejection/);
311
+ strict_1.default.deepEqual(monitors.getMonitor(monitor.id)?.sourceConfig, { symbol: 'AAPL' });
312
+ const updated = await monitors.updateMonitor(monitor.id, { sourceConfig: { symbol: 'MSFT' } });
313
+ strict_1.default.deepEqual(updated.sourceConfig, { symbol: 'MSFT' });
314
+ strict_1.default.deepEqual(transitions.slice(0, 4), ['start:AAPL', 'stop:AAPL', 'stop:AAPL', 'start:MSFT']);
315
+ }
316
+ finally {
317
+ await monitors.stop();
318
+ context.close();
319
+ }
320
+ });
321
+ (0, node_test_1.default)('enabling a disabled subscription starts before persistence and leaves it disabled on rejection', async () => {
322
+ const context = monitorFixture();
323
+ let starts = 0;
324
+ let stops = 0;
325
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
326
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
327
+ providers: [{
328
+ sourceType: 'stock.quote', modes: ['subscribe'],
329
+ async startMonitor() {
330
+ starts += 1;
331
+ if (starts === 1)
332
+ throw new Error('enable start rejected');
333
+ return { async stop() { stops += 1; } };
334
+ },
335
+ }],
336
+ });
337
+ try {
338
+ const monitor = await monitors.createMonitor({
339
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote', enabled: false,
340
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
341
+ });
342
+ await strict_1.default.rejects(() => monitors.updateMonitor(monitor.id, { enabled: true }), /enable start rejected/);
343
+ strict_1.default.equal(context.automations.get(monitor.id)?.enabled, false);
344
+ const enabled = await monitors.updateMonitor(monitor.id, { enabled: true });
345
+ strict_1.default.equal(enabled.enabled, true);
346
+ strict_1.default.equal(starts, 2);
347
+ await monitors.stop();
348
+ strict_1.default.equal(stops, 1);
349
+ }
350
+ finally {
351
+ await monitors.stop();
352
+ context.close();
353
+ }
354
+ });
355
+ (0, node_test_1.default)('disabling a subscription restores the old handle and definition when persistence rejects', async () => {
356
+ const context = monitorFixture();
357
+ let starts = 0;
358
+ let stops = 0;
359
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
360
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
361
+ providers: [{ sourceType: 'stock.quote', modes: ['subscribe'], async startMonitor() {
362
+ starts += 1;
363
+ return { async stop() { stops += 1; } };
364
+ } }],
365
+ });
366
+ try {
367
+ const monitor = await monitors.createMonitor({
368
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote',
369
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
370
+ });
371
+ const updateFromLegacy = context.automations.updateFromLegacy.bind(context.automations);
372
+ context.automations.updateFromLegacy = () => { throw new Error('database update rejected'); };
373
+ await strict_1.default.rejects(() => monitors.updateMonitor(monitor.id, { enabled: false }), /database update rejected/);
374
+ strict_1.default.equal(context.automations.get(monitor.id)?.enabled, true);
375
+ strict_1.default.equal(starts, 2);
376
+ strict_1.default.equal(stops, 1);
377
+ context.automations.updateFromLegacy = updateFromLegacy;
378
+ await monitors.updateMonitor(monitor.id, { enabled: false });
379
+ strict_1.default.equal(stops, 2);
380
+ }
381
+ finally {
382
+ await monitors.stop();
383
+ context.close();
384
+ }
385
+ });
386
+ (0, node_test_1.default)('enabling a disabled subscription stops the new handle and remains disabled when persistence rejects', async () => {
387
+ const context = monitorFixture();
388
+ let stops = 0;
389
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
390
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
391
+ providers: [{
392
+ sourceType: 'stock.quote', modes: ['subscribe'],
393
+ async startMonitor() { return { async stop() { stops += 1; } }; },
394
+ }],
395
+ });
396
+ try {
397
+ const monitor = await monitors.createMonitor({
398
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote', enabled: false,
399
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
400
+ });
401
+ const updateFromLegacy = context.automations.updateFromLegacy.bind(context.automations);
402
+ context.automations.updateFromLegacy = (automationId, input) => {
403
+ updateFromLegacy(automationId, input);
404
+ throw new Error('enable persistence rejected');
405
+ };
406
+ await strict_1.default.rejects(() => monitors.updateMonitor(monitor.id, { enabled: true }), /enable persistence rejected/);
407
+ strict_1.default.equal(context.automations.get(monitor.id)?.enabled, false);
408
+ strict_1.default.equal(stops, 1);
409
+ await monitors.stop();
410
+ strict_1.default.equal(stops, 1);
411
+ }
412
+ finally {
413
+ await monitors.stop();
414
+ context.close();
415
+ }
416
+ });
417
+ (0, node_test_1.default)('source update restores the old subscription and config when the new start rejects', async () => {
418
+ const context = monitorFixture();
419
+ const transitions = [];
420
+ let oldStarts = 0;
421
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
422
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
423
+ providers: [{
424
+ sourceType: 'stock.quote', modes: ['subscribe'],
425
+ async startMonitor({ sourceConfig }) {
426
+ const symbol = String(sourceConfig.symbol);
427
+ transitions.push(`start:${symbol}`);
428
+ if (symbol === 'MSFT')
429
+ throw new Error('new subscription rejected');
430
+ oldStarts += 1;
431
+ return { async stop() { transitions.push(`stop:${symbol}:${oldStarts}`); } };
432
+ },
433
+ }],
434
+ });
435
+ try {
436
+ const monitor = await monitors.createMonitor({
437
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote', sourceConfig: { symbol: 'AAPL' },
438
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
439
+ });
440
+ await strict_1.default.rejects(() => monitors.updateMonitor(monitor.id, { sourceConfig: { symbol: 'MSFT' } }), /new subscription rejected/);
441
+ strict_1.default.deepEqual(monitors.getMonitor(monitor.id)?.sourceConfig, { symbol: 'AAPL' });
442
+ strict_1.default.equal(monitors.getMonitor(monitor.id)?.enabled, true);
443
+ strict_1.default.equal(oldStarts, 2);
444
+ strict_1.default.deepEqual(transitions.slice(0, 4), ['start:AAPL', 'stop:AAPL:1', 'start:MSFT', 'start:AAPL']);
445
+ }
446
+ finally {
447
+ await monitors.stop();
448
+ context.close();
449
+ }
450
+ });
451
+ (0, node_test_1.default)('source update is disabled and blocked when both the new start and old restoration reject', async () => {
452
+ const context = monitorFixture();
453
+ let oldStarts = 0;
454
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
455
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
456
+ providers: [{
457
+ sourceType: 'stock.quote', modes: ['subscribe'],
458
+ async startMonitor({ sourceConfig }) {
459
+ const symbol = String(sourceConfig.symbol);
460
+ if (symbol === 'MSFT')
461
+ throw new Error('new start rejected');
462
+ oldStarts += 1;
463
+ if (oldStarts > 1)
464
+ throw new Error('old restore rejected');
465
+ return { stop() { } };
466
+ },
467
+ }],
468
+ });
469
+ try {
470
+ const monitor = await monitors.createMonitor({
471
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote', sourceConfig: { symbol: 'AAPL' },
472
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
473
+ });
474
+ await strict_1.default.rejects(() => monitors.updateMonitor(monitor.id, { sourceConfig: { symbol: 'MSFT' } }), /new start rejected/);
475
+ const definition = context.automations.get(monitor.id);
476
+ strict_1.default.equal(definition?.enabled, false);
477
+ strict_1.default.equal(definition?.health, 'blocked');
478
+ strict_1.default.match(definition?.blockedReason || '', /new start rejected/);
479
+ strict_1.default.match(definition?.blockedReason || '', /old restore rejected/);
480
+ strict_1.default.deepEqual(monitors.getMonitor(monitor.id)?.sourceConfig, { symbol: 'AAPL' });
481
+ }
482
+ finally {
483
+ await monitors.stop();
484
+ context.close();
485
+ }
486
+ });
487
+ (0, node_test_1.default)('successful enable recovers a provider-owned fail-closed block but preserves unrelated blocks', async () => {
488
+ const context = monitorFixture();
489
+ let emit;
490
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
491
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
492
+ providers: [{ sourceType: 'stock.quote', modes: ['subscribe'], async startMonitor(input) {
493
+ emit = input.emit;
494
+ return { stop() { } };
495
+ } }],
496
+ });
497
+ try {
498
+ const monitor = await monitors.createMonitor({
499
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote', enabled: false,
500
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
501
+ });
502
+ context.automations.failClosedLegacyAutomation(monitor.id, 'provider transition failed');
503
+ await monitors.updateMonitor(monitor.id, { enabled: true });
504
+ strict_1.default.equal(context.automations.get(monitor.id)?.health, 'healthy');
505
+ await monitors.start();
506
+ await emit(event);
507
+ strict_1.default.equal(context.automations.listEvaluations(monitor.id).length, 1);
508
+ await monitors.updateMonitor(monitor.id, { enabled: false });
509
+ context.store.updateAutomationState(monitor.id, { health: 'blocked', blockedReason: 'Approved script revoked' });
510
+ await monitors.updateMonitor(monitor.id, { enabled: true });
511
+ strict_1.default.equal(context.automations.get(monitor.id)?.blockedReason, 'Approved script revoked');
512
+ await emit({ ...event, id: 'blocked-event' });
513
+ strict_1.default.equal(context.automations.listEvaluations(monitor.id).length, 1);
514
+ }
515
+ finally {
516
+ await monitors.stop();
517
+ context.close();
518
+ }
519
+ });
520
+ (0, node_test_1.default)('source update fails closed and tracks the new handle when persistence and compensation stop reject', async () => {
521
+ const context = monitorFixture();
522
+ const starts = new Map();
523
+ const stops = new Map();
524
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
525
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
526
+ providers: [{
527
+ sourceType: 'stock.quote', modes: ['subscribe'],
528
+ async startMonitor({ sourceConfig }) {
529
+ const symbol = String(sourceConfig.symbol);
530
+ starts.set(symbol, (starts.get(symbol) || 0) + 1);
531
+ return {
532
+ async stop() {
533
+ const count = (stops.get(symbol) || 0) + 1;
534
+ stops.set(symbol, count);
535
+ if (symbol === 'MSFT' && count === 1)
536
+ throw new Error('new handle stop rejected');
537
+ },
538
+ };
539
+ },
540
+ }],
541
+ });
542
+ try {
543
+ const monitor = await monitors.createMonitor({
544
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote', sourceConfig: { symbol: 'AAPL' },
545
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
546
+ });
547
+ context.automations.updateFromLegacy = () => { throw new Error('persistence rejected'); };
548
+ await strict_1.default.rejects(() => monitors.updateMonitor(monitor.id, { sourceConfig: { symbol: 'MSFT' } }), /persistence rejected/);
549
+ strict_1.default.equal(context.automations.get(monitor.id)?.enabled, false);
550
+ strict_1.default.equal(context.automations.get(monitor.id)?.health, 'blocked');
551
+ strict_1.default.deepEqual(Object.fromEntries(starts), { AAPL: 1, MSFT: 1 });
552
+ await monitors.stop();
553
+ strict_1.default.deepEqual(Object.fromEntries(stops), { AAPL: 1, MSFT: 2 });
554
+ }
555
+ finally {
556
+ await monitors.stop();
557
+ context.close();
558
+ }
559
+ });
560
+ (0, node_test_1.default)('delete retains the definition and subscription after a rejected stop, then retries', async () => {
561
+ const context = monitorFixture();
562
+ let stopAttempts = 0;
563
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
564
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
565
+ providers: [{
566
+ sourceType: 'stock.quote', modes: ['subscribe'],
567
+ async startMonitor() {
568
+ return { async stop() { if (++stopAttempts === 1)
569
+ throw new Error('stop rejected'); } };
570
+ },
571
+ }],
572
+ });
573
+ try {
574
+ const monitor = await monitors.createMonitor({
575
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote',
576
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
577
+ });
578
+ await strict_1.default.rejects(() => monitors.deleteMonitor(monitor.id), /stop rejected/);
579
+ strict_1.default.equal(monitors.getMonitor(monitor.id)?.id, monitor.id);
580
+ strict_1.default.deepEqual(await monitors.deleteMonitor(monitor.id), { deleted: true });
581
+ strict_1.default.equal(stopAttempts, 2);
582
+ strict_1.default.equal(monitors.getMonitor(monitor.id), undefined);
583
+ }
584
+ finally {
585
+ await monitors.stop();
586
+ context.close();
587
+ }
588
+ });
589
+ (0, node_test_1.default)('delete restarts the old subscription when database deletion rejects', async () => {
590
+ const context = monitorFixture();
591
+ let starts = 0;
592
+ let stops = 0;
593
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
594
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
595
+ providers: [{ sourceType: 'stock.quote', modes: ['subscribe'], async startMonitor() {
596
+ starts += 1;
597
+ return { async stop() { stops += 1; } };
598
+ } }],
599
+ });
600
+ try {
601
+ const monitor = await monitors.createMonitor({
602
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote',
603
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
604
+ });
605
+ const deleteAutomation = context.store.deleteAutomation.bind(context.store);
606
+ context.store.deleteAutomation = () => { throw new Error('database delete rejected'); };
607
+ await strict_1.default.rejects(() => monitors.deleteMonitor(monitor.id), /database delete rejected/);
608
+ strict_1.default.equal(context.automations.get(monitor.id)?.enabled, true);
609
+ strict_1.default.equal(starts, 2);
610
+ context.store.deleteAutomation = deleteAutomation;
611
+ await monitors.deleteMonitor(monitor.id);
612
+ strict_1.default.equal(stops, 2);
613
+ }
614
+ finally {
615
+ await monitors.stop();
616
+ context.close();
617
+ }
618
+ });
619
+ (0, node_test_1.default)('service stop attempts every subscription and retains a failed handle for retry', async () => {
620
+ const context = monitorFixture();
621
+ const attempts = new Map();
622
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
623
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
624
+ providers: [{
625
+ sourceType: 'stock.quote', modes: ['subscribe'],
626
+ async startMonitor({ sourceConfig }) {
627
+ const symbol = String(sourceConfig.symbol);
628
+ return {
629
+ async stop() {
630
+ const count = (attempts.get(symbol) || 0) + 1;
631
+ attempts.set(symbol, count);
632
+ if (symbol === 'AAPL' && count === 1)
633
+ throw new Error('first stop rejected');
634
+ },
635
+ };
636
+ },
637
+ }],
638
+ });
639
+ try {
640
+ for (const symbol of ['AAPL', 'MSFT']) {
641
+ await monitors.createMonitor({
642
+ workspaceId: 'workspace', title: symbol, sourceType: 'stock.quote', sourceConfig: { symbol },
643
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
644
+ });
645
+ }
646
+ await monitors.stop();
647
+ strict_1.default.deepEqual(Object.fromEntries(attempts), { AAPL: 1, MSFT: 1 });
648
+ await monitors.stop();
649
+ strict_1.default.deepEqual(Object.fromEntries(attempts), { AAPL: 2, MSFT: 1 });
650
+ }
651
+ finally {
652
+ await monitors.stop();
653
+ context.close();
654
+ }
655
+ });
656
+ (0, node_test_1.default)('stop drains a blocked provider start and prevents stale handle or timer installation', async () => {
657
+ const context = monitorFixture();
658
+ let release;
659
+ const blocked = new Promise((resolve) => { release = resolve; });
660
+ let stops = 0;
661
+ let timers = 0;
662
+ context.automations.createFromLegacy((0, legacy_automation_mappers_js_1.monitorToAutomationInput)({
663
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote',
664
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
665
+ platform: 'local', route: { type: 'local.thread', channelId: 'workspace' },
666
+ }));
667
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
668
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
669
+ providers: [{ sourceType: 'stock.quote', modes: ['subscribe'], async startMonitor() {
670
+ await blocked;
671
+ return { async stop() { stops += 1; } };
672
+ } }],
673
+ setInterval() { timers += 1; return {}; },
674
+ });
675
+ try {
676
+ const starting = monitors.start();
677
+ await new Promise((resolve) => setImmediate(resolve));
678
+ const stopping = monitors.stop();
679
+ release();
680
+ await Promise.all([starting, stopping]);
681
+ const internals = monitors;
682
+ strict_1.default.equal(timers, 0);
683
+ strict_1.default.equal(stops, 1);
684
+ strict_1.default.equal(internals.timer, null);
685
+ strict_1.default.equal(internals.subscriptionHandles.size, 0);
686
+ await monitors.stop();
687
+ strict_1.default.equal(stops, 1);
688
+ }
689
+ finally {
690
+ release();
691
+ await monitors.stop();
692
+ context.close();
693
+ }
694
+ });
695
+ (0, node_test_1.default)('start-stop-start prevents stale provider work from contaminating the new generation', async () => {
696
+ const context = monitorFixture();
697
+ let release;
698
+ const blocked = new Promise((resolve) => { release = resolve; });
699
+ let starts = 0;
700
+ let stops = 0;
701
+ let timers = 0;
702
+ context.automations.createFromLegacy((0, legacy_automation_mappers_js_1.monitorToAutomationInput)({
703
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote',
704
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
705
+ platform: 'local', route: { type: 'local.thread', channelId: 'workspace' },
706
+ }));
707
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
708
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
709
+ providers: [{ sourceType: 'stock.quote', modes: ['subscribe'], async startMonitor() {
710
+ starts += 1;
711
+ if (starts === 1)
712
+ await blocked;
713
+ return { async stop() { stops += 1; } };
714
+ } }],
715
+ setInterval() { timers += 1; return {}; }, clearInterval() { },
716
+ });
717
+ try {
718
+ const firstStart = monitors.start();
719
+ await new Promise((resolve) => setImmediate(resolve));
720
+ const stopping = monitors.stop();
721
+ const restarting = monitors.start();
722
+ release();
723
+ await Promise.all([firstStart, stopping, restarting]);
724
+ const internals = monitors;
725
+ strict_1.default.equal(starts, 2);
726
+ strict_1.default.equal(stops, 1);
727
+ strict_1.default.equal(timers, 1);
728
+ strict_1.default.equal(internals.subscriptionHandles.size, 1);
729
+ await monitors.stop();
730
+ strict_1.default.equal(stops, 2);
731
+ }
732
+ finally {
733
+ release();
734
+ await monitors.stop();
735
+ context.close();
736
+ }
737
+ });
738
+ (0, node_test_1.default)('stop drains a blocked poll and persists no evaluation or run after release', async () => {
739
+ const context = monitorFixture();
740
+ let release;
741
+ let started;
742
+ const blocked = new Promise((resolve) => { release = resolve; });
743
+ const pollStarted = new Promise((resolve) => { started = resolve; });
744
+ const monitor = context.automations.createFromLegacy((0, legacy_automation_mappers_js_1.monitorToAutomationInput)({
745
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote',
746
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
747
+ platform: 'local', route: { type: 'local.thread', channelId: 'workspace' },
748
+ }));
749
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
750
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
751
+ providers: [{ sourceType: 'stock.quote', modes: ['poll'], async poll() {
752
+ started();
753
+ await blocked;
754
+ return event;
755
+ } }],
756
+ });
757
+ try {
758
+ const starting = monitors.start();
759
+ await pollStarted;
760
+ const stopping = monitors.stop();
761
+ release();
762
+ await Promise.all([starting, stopping]);
763
+ strict_1.default.equal(context.automations.listEvaluations(monitor.id).length, 0);
764
+ strict_1.default.equal(context.automations.listRuns(monitor.id).length, 0);
765
+ }
766
+ finally {
767
+ release();
768
+ await monitors.stop();
769
+ context.close();
770
+ }
771
+ });
772
+ (0, node_test_1.default)('stop cancels a callback queued for a provider permit', async () => {
773
+ const context = monitorFixture();
774
+ let emit;
775
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
776
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
777
+ providers: [{ sourceType: 'stock.quote', modes: ['subscribe'], async startMonitor(input) {
778
+ emit = input.emit;
779
+ return { stop() { } };
780
+ } }],
781
+ });
782
+ try {
783
+ const monitor = await monitors.createMonitor({
784
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote',
785
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
786
+ });
787
+ await monitors.start();
788
+ const internals = monitors;
789
+ internals.providerEventsInFlight = 4;
790
+ const queued = emit(event);
791
+ await new Promise((resolve) => setImmediate(resolve));
792
+ await monitors.stop();
793
+ await queued;
794
+ strict_1.default.equal(context.automations.listEvaluations(monitor.id).length, 0);
795
+ strict_1.default.equal(context.automations.listRuns(monitor.id).length, 0);
796
+ }
797
+ finally {
798
+ await monitors.stop();
799
+ context.close();
800
+ }
801
+ });
802
+ (0, node_test_1.default)('startup blocks a rejected subscription and the next timer tick retries it to healthy', async () => {
803
+ const context = monitorFixture();
804
+ let timerHandler;
805
+ let starts = 0;
806
+ let stops = 0;
807
+ let emit;
808
+ const definition = context.automations.createFromLegacy((0, legacy_automation_mappers_js_1.monitorToAutomationInput)({
809
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote',
810
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
811
+ platform: 'local', route: { type: 'local.thread', channelId: 'workspace' },
812
+ }));
813
+ const options = {
814
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
815
+ providers: [{
816
+ sourceType: 'stock.quote', modes: ['subscribe'],
817
+ async startMonitor(input) {
818
+ emit = input.emit;
819
+ starts += 1;
820
+ if (starts === 1)
821
+ throw new Error('TOKEN=retry-secret\u001b[31m start rejected');
822
+ return { async stop() { stops += 1; } };
823
+ },
824
+ }],
825
+ setInterval(handler, delayMs) {
826
+ strict_1.default.equal(delayMs, 30_000);
827
+ timerHandler = handler;
828
+ return {};
829
+ },
830
+ clearInterval() { },
831
+ };
832
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService(options);
833
+ try {
834
+ await monitors.start();
835
+ const blocked = context.automations.get(definition.id);
836
+ strict_1.default.equal(blocked?.enabled, true);
837
+ strict_1.default.equal(blocked?.health, 'blocked');
838
+ strict_1.default.match(blocked?.blockedReason || '', /REDACTED_SECRET/);
839
+ strict_1.default.doesNotMatch(blocked?.blockedReason || '', /retry-secret|\u001b/);
840
+ strict_1.default.equal(starts, 1);
841
+ await monitors.start();
842
+ strict_1.default.equal(starts, 1);
843
+ await emit(event);
844
+ strict_1.default.equal(context.automations.listEvaluations(definition.id).length, 0);
845
+ strict_1.default.ok(timerHandler);
846
+ timerHandler();
847
+ await new Promise((resolve) => setImmediate(resolve));
848
+ await new Promise((resolve) => setImmediate(resolve));
849
+ const recovered = context.automations.get(definition.id);
850
+ strict_1.default.equal(recovered?.health, 'healthy');
851
+ strict_1.default.equal(recovered?.blockedReason, undefined);
852
+ strict_1.default.equal(starts, 2);
853
+ await monitors.stop();
854
+ strict_1.default.equal(stops, 1);
855
+ }
856
+ finally {
857
+ await monitors.stop();
858
+ context.close();
859
+ }
860
+ });
861
+ (0, node_test_1.default)('repeated subscription start failures stay isolated while other monitors start', async () => {
862
+ const context = monitorFixture();
863
+ const definitions = ['bad', 'good'].map((kind) => context.automations.createFromLegacy((0, legacy_automation_mappers_js_1.monitorToAutomationInput)({
864
+ workspaceId: 'workspace', title: kind, sourceType: 'stock.quote', sourceConfig: { kind },
865
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
866
+ platform: 'local', route: { type: 'local.thread', channelId: 'workspace' },
867
+ })));
868
+ let goodStops = 0;
869
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
870
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
871
+ providers: [{
872
+ sourceType: 'stock.quote', modes: ['subscribe'],
873
+ async startMonitor({ sourceConfig }) {
874
+ if (sourceConfig.kind === 'bad')
875
+ throw new Error('API_KEY=provider-secret start rejected');
876
+ return { async stop() { goodStops += 1; } };
877
+ },
878
+ }],
879
+ });
880
+ try {
881
+ await monitors.start();
882
+ const bad = context.automations.get(definitions[0].id);
883
+ const good = context.automations.get(definitions[1].id);
884
+ strict_1.default.equal(bad?.enabled, true);
885
+ strict_1.default.equal(bad?.health, 'blocked');
886
+ strict_1.default.match(bad?.blockedReason || '', /REDACTED_SECRET/);
887
+ strict_1.default.doesNotMatch(bad?.blockedReason || '', /provider-secret/);
888
+ strict_1.default.equal(good?.health, 'healthy');
889
+ await monitors.stop();
890
+ strict_1.default.equal(goodStops, 1);
891
+ }
892
+ finally {
893
+ await monitors.stop();
894
+ context.close();
895
+ }
896
+ });
897
+ (0, node_test_1.default)('polling admits a later monitor while the first action is slow and isolates poll failures', async () => {
898
+ let release;
899
+ const blocked = new Promise((resolve) => { release = resolve; });
900
+ let firstId = '';
901
+ const context = monitorFixture(async (automationId) => { if (automationId === firstId)
902
+ await blocked; });
903
+ const logs = [];
904
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
905
+ store: context.store, automations: context.automations, eventBus: context.eventBus, log: (message) => logs.push(message),
906
+ providers: [{
907
+ sourceType: 'stock.quote', modes: ['poll'],
908
+ async poll({ sourceConfig }) {
909
+ if (sourceConfig.fail)
910
+ throw new Error('poll rejected');
911
+ return { id: String(sourceConfig.id), sourceType: 'stock.quote', occurredAt: '2026-07-05T01:00:00.000Z', subject: 'quote', payload: { latestPrice: 2 } };
912
+ },
913
+ }],
914
+ });
915
+ try {
916
+ const first = await monitors.createMonitor({
917
+ workspaceId: 'workspace', title: 'first', sourceType: 'stock.quote', sourceConfig: { id: 'first' },
918
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
919
+ });
920
+ firstId = first.id;
921
+ await monitors.createMonitor({
922
+ workspaceId: 'workspace', title: 'bad', sourceType: 'stock.quote', sourceConfig: { fail: true },
923
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
924
+ });
925
+ const second = await monitors.createMonitor({
926
+ workspaceId: 'workspace', title: 'second', sourceType: 'stock.quote', sourceConfig: { id: 'second' },
927
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
928
+ });
929
+ const starting = monitors.start();
930
+ await new Promise((resolve) => setImmediate(resolve));
931
+ strict_1.default.ok(context.actions.includes(first.id));
932
+ strict_1.default.ok(context.actions.includes(second.id));
933
+ strict_1.default.ok(logs.some((message) => message.includes('poll rejected')));
934
+ release();
935
+ await starting;
936
+ }
937
+ finally {
938
+ release();
939
+ await monitors.stop();
940
+ context.close();
941
+ }
942
+ });
943
+ (0, node_test_1.default)('provider callback fails closed after automation migration degrades', async () => {
944
+ const context = monitorFixture();
945
+ let emit;
946
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
947
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
948
+ providers: [{
949
+ sourceType: 'stock.quote', modes: ['subscribe'],
950
+ async startMonitor(input) { emit = input.emit; return { stop() { } }; },
951
+ }],
952
+ });
953
+ try {
954
+ const monitor = await monitors.createMonitor({
955
+ workspaceId: 'workspace', title: 'quote', sourceType: 'stock.quote',
956
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
957
+ });
958
+ context.store.importLegacyAutomations = () => { throw new Error('migration blocked'); };
959
+ await context.automations.start();
960
+ await emit(event);
961
+ strict_1.default.equal(context.automations.listEvaluations(monitor.id).length, 0);
962
+ strict_1.default.equal(context.actions.length, 0);
963
+ }
964
+ finally {
965
+ await context.automations.stop();
966
+ await monitors.stop();
967
+ context.close();
968
+ }
969
+ });
970
+ (0, node_test_1.default)('poll returning null after migration degrades persists no compatibility evaluation', async () => {
971
+ const context = monitorFixture();
972
+ let releasePoll;
973
+ const pollBlocked = new Promise((resolve) => { releasePoll = resolve; });
974
+ const compatibilityRuns = [];
975
+ context.eventBus.on('automation.monitor.run.updated', (run) => compatibilityRuns.push(run.id));
976
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
977
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
978
+ providers: [{
979
+ sourceType: 'stock.quote', modes: ['poll'],
980
+ async poll() { await pollBlocked; return null; },
981
+ }],
982
+ });
983
+ try {
984
+ const monitor = await monitors.createMonitor({
985
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote',
986
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
987
+ });
988
+ const polling = monitors.runMonitorNow(monitor.id);
989
+ await new Promise((resolve) => setImmediate(resolve));
990
+ context.store.importLegacyAutomations = () => { throw new Error('migration blocked'); };
991
+ await context.automations.start();
992
+ releasePoll();
993
+ await strict_1.default.rejects(() => polling, /Unified automation migration is unavailable/);
994
+ strict_1.default.equal(context.automations.listEvaluations(monitor.id).length, 0);
995
+ strict_1.default.equal(context.automations.listRuns(monitor.id).length, 0);
996
+ strict_1.default.deepEqual(compatibilityRuns, []);
997
+ }
998
+ finally {
999
+ releasePoll();
1000
+ await context.automations.stop();
1001
+ await monitors.stop();
1002
+ context.close();
1003
+ }
1004
+ });
1005
+ (0, node_test_1.default)('poll returning an event after migration degrades persists no evaluation or run', async () => {
1006
+ const context = monitorFixture();
1007
+ let releasePoll;
1008
+ const pollBlocked = new Promise((resolve) => { releasePoll = resolve; });
1009
+ const compatibilityRuns = [];
1010
+ context.eventBus.on('automation.monitor.run.updated', (run) => compatibilityRuns.push(run.id));
1011
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
1012
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
1013
+ providers: [{
1014
+ sourceType: 'stock.quote', modes: ['poll'],
1015
+ async poll() { await pollBlocked; return event; },
1016
+ }],
1017
+ });
1018
+ try {
1019
+ const monitor = await monitors.createMonitor({
1020
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote',
1021
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
1022
+ });
1023
+ const polling = monitors.runMonitorNow(monitor.id);
1024
+ await new Promise((resolve) => setImmediate(resolve));
1025
+ context.store.importLegacyAutomations = () => { throw new Error('migration blocked'); };
1026
+ await context.automations.start();
1027
+ releasePoll();
1028
+ await strict_1.default.rejects(() => polling, /Unified automation migration is unavailable/);
1029
+ strict_1.default.equal(context.automations.listEvaluations(monitor.id).length, 0);
1030
+ strict_1.default.equal(context.automations.listRuns(monitor.id).length, 0);
1031
+ strict_1.default.deepEqual(compatibilityRuns, []);
1032
+ }
1033
+ finally {
1034
+ releasePoll();
1035
+ await context.automations.stop();
1036
+ await monitors.stop();
1037
+ context.close();
1038
+ }
1039
+ });
1040
+ (0, node_test_1.default)('old subscription callbacks are ignored after source reconfiguration', async () => {
1041
+ const context = monitorFixture();
1042
+ const emits = [];
1043
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
1044
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
1045
+ providers: [{ sourceType: 'stock.quote', modes: ['subscribe'], async startMonitor(input) {
1046
+ emits.push(input.emit);
1047
+ return { stop() { } };
1048
+ } }],
1049
+ });
1050
+ try {
1051
+ const monitor = await monitors.createMonitor({
1052
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote', sourceConfig: { symbol: 'AAPL' },
1053
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
1054
+ });
1055
+ await monitors.updateMonitor(monitor.id, { sourceConfig: { symbol: 'MSFT' } });
1056
+ await monitors.start();
1057
+ await emits[0](event);
1058
+ strict_1.default.equal(context.automations.listEvaluations(monitor.id).length, 0);
1059
+ await emits[1](event);
1060
+ strict_1.default.equal(context.automations.listEvaluations(monitor.id).length, 1);
1061
+ }
1062
+ finally {
1063
+ await monitors.stop();
1064
+ context.close();
1065
+ }
1066
+ });
1067
+ (0, node_test_1.default)('queued subscription event is ignored after the monitor is disabled', async () => {
1068
+ const context = monitorFixture();
1069
+ let emit;
1070
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
1071
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
1072
+ providers: [{ sourceType: 'stock.quote', modes: ['subscribe'], async startMonitor(input) {
1073
+ emit = input.emit;
1074
+ return { stop() { } };
1075
+ } }],
1076
+ });
1077
+ try {
1078
+ const monitor = await monitors.createMonitor({
1079
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote',
1080
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
1081
+ });
1082
+ const internals = monitors;
1083
+ internals.providerEventsInFlight = 4;
1084
+ const queued = emit(event);
1085
+ await new Promise((resolve) => setImmediate(resolve));
1086
+ await monitors.updateMonitor(monitor.id, { enabled: false });
1087
+ internals.releaseProviderEventPermit();
1088
+ await queued;
1089
+ strict_1.default.equal(context.automations.listEvaluations(monitor.id).length, 0);
1090
+ }
1091
+ finally {
1092
+ await monitors.stop();
1093
+ context.close();
1094
+ }
1095
+ });
1096
+ (0, node_test_1.default)('queued subscription events are ignored after reconfiguration or deletion', async () => {
1097
+ for (const transition of ['reconfigure', 'delete']) {
1098
+ const context = monitorFixture();
1099
+ let emit;
1100
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
1101
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
1102
+ providers: [{ sourceType: 'stock.quote', modes: ['subscribe'], async startMonitor(input) {
1103
+ emit ||= input.emit;
1104
+ return { stop() { } };
1105
+ } }],
1106
+ });
1107
+ try {
1108
+ const monitor = await monitors.createMonitor({
1109
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote', sourceConfig: { symbol: 'AAPL' },
1110
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
1111
+ });
1112
+ const internals = monitors;
1113
+ internals.providerEventsInFlight = 4;
1114
+ const queued = emit(event);
1115
+ await new Promise((resolve) => setImmediate(resolve));
1116
+ if (transition === 'reconfigure') {
1117
+ await monitors.updateMonitor(monitor.id, { sourceConfig: { symbol: 'MSFT' } });
1118
+ }
1119
+ else {
1120
+ await monitors.deleteMonitor(monitor.id);
1121
+ }
1122
+ internals.releaseProviderEventPermit();
1123
+ await queued;
1124
+ strict_1.default.equal(context.automations.listEvaluations(monitor.id).length, 0);
1125
+ }
1126
+ finally {
1127
+ await monitors.stop();
1128
+ context.close();
1129
+ }
1130
+ }
1131
+ });
1132
+ (0, node_test_1.default)('poll result is ignored when source config changes while polling', async () => {
1133
+ const context = monitorFixture();
1134
+ let release;
1135
+ const blocked = new Promise((resolve) => { release = resolve; });
1136
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
1137
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
1138
+ providers: [{ sourceType: 'stock.quote', modes: ['poll'], async poll() { await blocked; return event; } }],
1139
+ });
1140
+ try {
1141
+ const monitor = await monitors.createMonitor({
1142
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote', sourceConfig: { symbol: 'AAPL' },
1143
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
1144
+ });
1145
+ const polling = monitors.runMonitorNow(monitor.id);
1146
+ await new Promise((resolve) => setImmediate(resolve));
1147
+ await monitors.updateMonitor(monitor.id, { sourceConfig: { symbol: 'MSFT' } });
1148
+ release();
1149
+ await strict_1.default.rejects(() => polling, /changed while provider polling/);
1150
+ strict_1.default.equal(context.automations.listEvaluations(monitor.id).length, 0);
1151
+ }
1152
+ finally {
1153
+ release();
1154
+ await monitors.stop();
1155
+ context.close();
1156
+ }
1157
+ });
1158
+ (0, node_test_1.default)('queued automatic poll is ignored after disable once it receives a global permit', async () => {
1159
+ const context = monitorFixture();
1160
+ let polled;
1161
+ const pollStarted = new Promise((resolve) => { polled = resolve; });
1162
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
1163
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
1164
+ providers: [{ sourceType: 'stock.quote', modes: ['poll'], async poll() { polled(); return event; } }],
1165
+ });
1166
+ try {
1167
+ const monitor = await monitors.createMonitor({
1168
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote',
1169
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
1170
+ });
1171
+ const internals = monitors;
1172
+ internals.providerEventsInFlight = 4;
1173
+ const ticking = internals.tick(false);
1174
+ await pollStarted;
1175
+ await new Promise((resolve) => setImmediate(resolve));
1176
+ await monitors.updateMonitor(monitor.id, { enabled: false });
1177
+ internals.releaseProviderEventPermit();
1178
+ await ticking;
1179
+ strict_1.default.equal(context.automations.listEvaluations(monitor.id).length, 0);
1180
+ }
1181
+ finally {
1182
+ await monitors.stop();
1183
+ context.close();
1184
+ }
1185
+ });
1186
+ (0, node_test_1.default)('manual polling remains available while a monitor is disabled', async () => {
1187
+ const context = monitorFixture();
1188
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
1189
+ store: context.store, automations: context.automations, eventBus: context.eventBus,
1190
+ providers: [{ sourceType: 'stock.quote', modes: ['poll'], async poll() { return event; } }],
1191
+ });
1192
+ try {
1193
+ const monitor = await monitors.createMonitor({
1194
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote', enabled: false,
1195
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
1196
+ });
1197
+ const run = await monitors.runMonitorNow(monitor.id);
1198
+ strict_1.default.equal(run.status, 'succeeded');
1199
+ strict_1.default.equal(context.automations.listEvaluations(monitor.id).length, 1);
1200
+ }
1201
+ finally {
1202
+ await monitors.stop();
1203
+ context.close();
1204
+ }
1205
+ });
1206
+ (0, node_test_1.default)('provider snapshots are fully validated before evaluation persistence', async () => {
1207
+ const context = monitorFixture();
1208
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
1209
+ store: context.store, automations: context.automations, eventBus: context.eventBus, providers: [],
1210
+ });
1211
+ try {
1212
+ const monitor = await monitors.createMonitor({
1213
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote',
1214
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
1215
+ });
1216
+ let compatibilityEvents = 0;
1217
+ context.eventBus.on('automation.evaluation.updated', () => { compatibilityEvents += 1; });
1218
+ context.eventBus.on('automation.monitor.run.updated', () => { compatibilityEvents += 1; });
1219
+ context.eventBus.on('automation.run.updated', () => { compatibilityEvents += 1; });
1220
+ const cycle = {};
1221
+ cycle.self = cycle;
1222
+ const cyclicSnapshot = { ...event, payload: cycle };
1223
+ const symbolPayload = { latestPrice: 188 };
1224
+ symbolPayload[Symbol('hidden')] = true;
1225
+ let accessorReads = 0;
1226
+ const accessorPayload = Object.defineProperty({}, 'latestPrice', {
1227
+ enumerable: true,
1228
+ get() { accessorReads += 1; return 188; },
1229
+ });
1230
+ let tooDeep = {};
1231
+ for (let depth = 0; depth < 66; depth += 1)
1232
+ tooDeep = { nested: tooDeep };
1233
+ class ProviderValue {
1234
+ value = 1;
1235
+ }
1236
+ const malformed = [
1237
+ null,
1238
+ [],
1239
+ Object.assign(Object.create({ inherited: true }), event),
1240
+ (({ id: _id, ...rest }) => rest)(event),
1241
+ (({ sourceType: _sourceType, ...rest }) => rest)(event),
1242
+ (({ subject: _subject, ...rest }) => rest)(event),
1243
+ { ...event, id: '' },
1244
+ { ...event, id: ' ' },
1245
+ { ...event, sourceType: ' ' },
1246
+ { ...event, subject: ' ' },
1247
+ { ...event, subject: 1 },
1248
+ { ...event, summary: 1 },
1249
+ { ...event, summary: null },
1250
+ { ...event, payload: null },
1251
+ { ...event, payload: [] },
1252
+ { ...event, payload: new Date() },
1253
+ { ...event, payload: { value: 1n } },
1254
+ { ...event, payload: { value: undefined } },
1255
+ { ...event, payload: { value: () => true } },
1256
+ { ...event, payload: { value: Symbol('nested') } },
1257
+ { ...event, payload: { value: Number.NaN } },
1258
+ { ...event, payload: { value: Number.POSITIVE_INFINITY } },
1259
+ { ...event, payload: { nested: new Map() } },
1260
+ { ...event, payload: { nested: new Set() } },
1261
+ { ...event, payload: { nested: new ProviderValue() } },
1262
+ cyclicSnapshot,
1263
+ { ...event, payload: tooDeep },
1264
+ { ...event, payload: symbolPayload },
1265
+ { ...event, payload: accessorPayload },
1266
+ { ...event, occurredAt: '2026-01-01T00:00:00' },
1267
+ { ...event, occurredAt: '2026-13-01T00:00:00Z' },
1268
+ { ...event, occurredAt: '2026-02-30T00:00:00Z' },
1269
+ { ...event, occurredAt: '2026-01-01T24:00:00Z' },
1270
+ { ...event, occurredAt: '2026-01-01T00:60:00Z' },
1271
+ { ...event, occurredAt: '2026-01-01T00:00:60Z' },
1272
+ { ...event, occurredAt: '2026-01-01T00:00:00+24:00' },
1273
+ { ...event, sourceType: 'weather.alert' },
1274
+ { ...event, subject: 'x'.repeat(100_001) },
1275
+ { ...event, summary: 'x'.repeat(100_001) },
1276
+ ];
1277
+ for (const [index, snapshot] of malformed.entries()) {
1278
+ await strict_1.default.rejects(() => monitors.runMonitorNow(monitor.id, snapshot), (error) => {
1279
+ if (snapshot === cyclicSnapshot) {
1280
+ const message = error instanceof Error ? error.message : String(error);
1281
+ strict_1.default.match(message, /^Invalid provider event payload: .*cycle/);
1282
+ strict_1.default.ok(message.length <= 2_000);
1283
+ strict_1.default.doesNotMatch(message, /[\u0000-\u001f\u007f-\u009f]/);
1284
+ }
1285
+ return true;
1286
+ }, `malformed snapshot ${index}`);
1287
+ }
1288
+ strict_1.default.equal(context.automations.listEvaluations(monitor.id).length, 0);
1289
+ strict_1.default.equal(context.automations.listRuns(monitor.id).length, 0);
1290
+ strict_1.default.equal(compatibilityEvents, 0);
1291
+ strict_1.default.equal(accessorReads, 0);
1292
+ }
1293
+ finally {
1294
+ await monitors.stop();
1295
+ context.close();
1296
+ }
1297
+ });
1298
+ (0, node_test_1.default)('provider snapshot payload is detached at call time and preserves valid nested JSON data', async () => {
1299
+ const context = monitorFixture();
1300
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
1301
+ store: context.store, automations: context.automations, eventBus: context.eventBus, providers: [],
1302
+ });
1303
+ try {
1304
+ const monitor = await monitors.createMonitor({
1305
+ workspaceId: 'workspace', title: 'Quote', sourceType: 'stock.quote',
1306
+ condition: { metric: 'latestPrice', operator: '>', value: 1 }, promptTemplate: 'quote',
1307
+ });
1308
+ const payload = {
1309
+ latestPrice: 188,
1310
+ nested: { label: 'original', values: [true, null, { count: 2 }] },
1311
+ };
1312
+ const evaluating = monitors.runMonitorNow(monitor.id, { ...event, payload });
1313
+ payload.nested.label = 'mutated';
1314
+ payload.nested.values[2] = { count: 99 };
1315
+ await evaluating;
1316
+ const evaluation = context.automations.getLatestEvaluationWithState(monitor.id);
1317
+ strict_1.default.equal(evaluation?.status, 'finished');
1318
+ if (!evaluation || evaluation.status !== 'finished')
1319
+ throw new Error('Expected a finished evaluation');
1320
+ strict_1.default.deepEqual(evaluation.nextState?.payload, {
1321
+ latestPrice: 188,
1322
+ nested: { label: 'original', values: [true, null, { count: 2 }] },
1323
+ });
1324
+ strict_1.default.deepEqual(evaluation.payload?.eventSnapshot, {
1325
+ ...event,
1326
+ payload: {
1327
+ latestPrice: 188,
1328
+ nested: { label: 'original', values: [true, null, { count: 2 }] },
1329
+ },
1330
+ });
1331
+ }
1332
+ finally {
1333
+ await monitors.stop();
1334
+ context.close();
1335
+ }
1336
+ });
1337
+ (0, node_test_1.default)('unified monitor evaluation preserves nested legacy metrics and trusted state checkpoints', async () => {
1338
+ const context = monitorFixture();
1339
+ const monitors = new automation_monitor_service_js_1.AutomationMonitorService({
1340
+ store: context.store, automations: context.automations, eventBus: context.eventBus, providers: [],
1341
+ });
1342
+ try {
1343
+ const monitor = await monitors.createMonitor({
1344
+ workspaceId: 'workspace', title: 'Nested', sourceType: 'stock.quote',
1345
+ condition: {
1346
+ metric: 'expression', operator: '==', value: true,
1347
+ expression: 'subject == AAPL && sourceType == stock.quote && subject.market == NASDAQ && sourceType.vendor == IEX',
1348
+ },
1349
+ promptTemplate: 'quote',
1350
+ });
1351
+ await monitors.runMonitorNow(monitor.id, {
1352
+ id: 'trusted-id', sourceType: 'stock.quote', occurredAt: '2026-07-05T09:00:00+08:00', subject: 'AAPL',
1353
+ payload: {
1354
+ subject: { market: 'NASDAQ' }, sourceType: { vendor: 'IEX' },
1355
+ lastEventId: 'spoofed', lastEventAt: 'spoofed', payload: { spoofed: true },
1356
+ },
1357
+ });
1358
+ const evaluation = context.automations.getLatestEvaluationWithState(monitor.id);
1359
+ strict_1.default.equal(evaluation?.conditionOutcome, 'matched');
1360
+ strict_1.default.equal(evaluation?.nextState?.lastEventId, 'trusted-id');
1361
+ strict_1.default.equal(evaluation?.nextState?.lastEventAt, '2026-07-05T01:00:00.000Z');
1362
+ strict_1.default.deepEqual(evaluation?.nextState?.payload, {
1363
+ subject: { market: 'NASDAQ' }, sourceType: { vendor: 'IEX' },
1364
+ lastEventId: 'spoofed', lastEventAt: 'spoofed', payload: { spoofed: true },
1365
+ });
1366
+ }
1367
+ finally {
1368
+ await monitors.stop();
1369
+ context.close();
1370
+ }
1371
+ });