@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
@@ -2,355 +2,790 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AutomationMonitorService = void 0;
4
4
  const scheduled_job_route_js_1 = require("../scheduler/scheduled-job-route.js");
5
- const condition_evaluator_js_1 = require("./condition-evaluator.js");
6
- const automation_conversation_executor_js_1 = require("./automation-conversation-executor.js");
7
- const automation_monitor_repository_js_1 = require("./automation-monitor-repository.js");
5
+ const legacy_automation_mappers_js_1 = require("./legacy-automation-mappers.js");
6
+ const automation_service_js_1 = require("./automation-service.js");
8
7
  const monitor_id_js_1 = require("./monitor-id.js");
8
+ const PROVIDER_EVENT_CONCURRENCY = 4;
9
9
  class AutomationMonitorService {
10
10
  options;
11
11
  timer = null;
12
- pollInFlight = false;
13
- runningMonitors = new Set();
12
+ startPromise;
13
+ stopPromise;
14
+ tickPromise;
15
+ lifecycleState = 'stopped';
16
+ lifecycleGeneration = 0;
17
+ providerEventsInFlight = 0;
18
+ providerEventWaiters = [];
19
+ inFlight = new Set();
14
20
  subscriptionHandles = new Map();
21
+ handleLifecycleGenerations = new Map();
22
+ subscriptionGenerations = new Map();
15
23
  providers = new Map();
16
- executor;
17
- repository;
18
24
  constructor(options) {
19
25
  this.options = options;
20
- for (const provider of options.providers) {
26
+ for (const provider of options.providers)
21
27
  this.providers.set(provider.sourceType, provider);
28
+ }
29
+ start() {
30
+ if (this.lifecycleState === 'stopping') {
31
+ return (this.stopPromise || Promise.resolve()).then(() => this.start());
22
32
  }
23
- this.repository = new automation_monitor_repository_js_1.AutomationMonitorRepository(options.store);
24
- this.executor = new automation_conversation_executor_js_1.AutomationConversationExecutor({
25
- store: options.store,
26
- getWorkspaceRouter: options.getWorkspaceRouter,
27
- getChannelRuntime: options.getChannelRuntime,
33
+ if (this.startPromise)
34
+ return this.startPromise;
35
+ if (this.lifecycleState === 'running')
36
+ return Promise.resolve();
37
+ const generation = ++this.lifecycleGeneration;
38
+ this.lifecycleState = 'starting';
39
+ const work = this.startInternal(generation).catch((error) => {
40
+ if (this.lifecycleGeneration === generation) {
41
+ if (this.timer)
42
+ (this.options.clearInterval || clearInterval)(this.timer);
43
+ this.timer = null;
44
+ this.lifecycleState = 'stopped';
45
+ }
46
+ throw error;
47
+ });
48
+ let shared;
49
+ shared = work.finally(() => {
50
+ if (this.startPromise === shared)
51
+ this.startPromise = undefined;
52
+ if (this.lifecycleGeneration === generation && this.lifecycleState === 'starting') {
53
+ this.lifecycleState = 'stopped';
54
+ }
28
55
  });
56
+ this.startPromise = shared;
57
+ return shared;
29
58
  }
30
- async start() {
31
- await this.refreshSubscriptions();
32
- await this.tick();
33
- this.timer = setInterval(() => {
34
- void this.tick();
59
+ async startInternal(generation) {
60
+ if (this.options.automations.getRuntimeStatus().status === 'degraded') {
61
+ if (this.isCurrentLifecycle(generation, 'starting'))
62
+ this.lifecycleState = 'stopped';
63
+ this.reportProviderError('start', new Error('Unified automation migration is unavailable.'));
64
+ return;
65
+ }
66
+ await this.refreshSubscriptions(generation);
67
+ if (!this.isCurrentLifecycle(generation, 'starting'))
68
+ return;
69
+ this.lifecycleState = 'running';
70
+ await this.tick(false, generation);
71
+ if (!this.isCurrentLifecycle(generation, 'running'))
72
+ return;
73
+ this.timer = (this.options.setInterval || setInterval)(() => {
74
+ if (!this.isCurrentLifecycle(generation, 'running'))
75
+ return;
76
+ void this.tick(true, generation).catch((error) => this.reportProviderError('interval', error));
35
77
  }, 30_000);
36
78
  }
37
- async stop() {
38
- if (this.timer) {
39
- clearInterval(this.timer);
40
- this.timer = null;
41
- }
42
- for (const handle of this.subscriptionHandles.values()) {
43
- await handle.stop();
79
+ stop() {
80
+ if (this.stopPromise)
81
+ return this.stopPromise;
82
+ if (this.lifecycleState === 'stopped'
83
+ && this.subscriptionHandles.size === 0
84
+ && this.inFlight.size === 0
85
+ && !this.startPromise)
86
+ return Promise.resolve();
87
+ const starting = this.startPromise;
88
+ const generation = ++this.lifecycleGeneration;
89
+ this.lifecycleState = 'stopping';
90
+ if (this.timer)
91
+ (this.options.clearInterval || clearInterval)(this.timer);
92
+ this.timer = null;
93
+ this.handleLifecycleGenerations.clear();
94
+ for (const monitorId of this.subscriptionHandles.keys()) {
95
+ this.subscriptionGenerations.set(monitorId, (this.subscriptionGenerations.get(monitorId) || 0) + 1);
44
96
  }
45
- this.subscriptionHandles.clear();
97
+ for (const waiter of this.providerEventWaiters.splice(0))
98
+ waiter(false);
99
+ const work = this.stopInternal(starting);
100
+ let shared;
101
+ shared = work.finally(() => {
102
+ if (this.stopPromise === shared)
103
+ this.stopPromise = undefined;
104
+ if (this.lifecycleGeneration === generation && this.lifecycleState === 'stopping') {
105
+ this.lifecycleState = 'stopped';
106
+ }
107
+ });
108
+ this.stopPromise = shared;
109
+ return shared;
110
+ }
111
+ async stopInternal(starting) {
112
+ if (starting)
113
+ await Promise.allSettled([starting]);
114
+ await this.settleInFlight();
115
+ const handles = [...this.subscriptionHandles.entries()];
116
+ const results = await Promise.allSettled(handles.map(([, handle]) => this.observeProviderWork(Promise.resolve().then(() => handle.stop()))));
117
+ results.forEach((result, index) => {
118
+ const [monitorId, handle] = handles[index] || [];
119
+ if (!monitorId || !handle)
120
+ return;
121
+ if (result.status === 'fulfilled') {
122
+ if (this.subscriptionHandles.get(monitorId) === handle) {
123
+ this.subscriptionHandles.delete(monitorId);
124
+ this.handleLifecycleGenerations.delete(monitorId);
125
+ }
126
+ }
127
+ else {
128
+ this.reportProviderError(`stop:${monitorId}`, result.reason);
129
+ }
130
+ });
131
+ await this.settleInFlight();
46
132
  }
47
133
  listMonitors(workspaceId) {
48
- return this.repository.list(workspaceId);
134
+ return this.options.automations.list(workspaceId)
135
+ .filter((automation) => automation.originKind === 'automation-monitor')
136
+ .map((automation) => (0, legacy_automation_mappers_js_1.automationToMonitor)(automation, (0, legacy_automation_mappers_js_1.latestFinishedEvaluation)(this.options.automations.listEvaluations(automation.id)), (0, legacy_automation_mappers_js_1.latestAutomationRun)(this.options.automations.listRuns(automation.id)), this.options.automations.getLatestEvaluationWithState(automation.id)));
49
137
  }
50
138
  getMonitor(monitorId) {
51
- const resolvedMonitorId = this.resolveMonitorId(monitorId);
52
- return resolvedMonitorId ? this.repository.get(resolvedMonitorId) : undefined;
139
+ const resolved = this.resolveMonitorId(monitorId);
140
+ const automation = resolved ? this.options.automations.get(resolved) : undefined;
141
+ return automation?.originKind === 'automation-monitor'
142
+ ? (0, legacy_automation_mappers_js_1.automationToMonitor)(automation, (0, legacy_automation_mappers_js_1.latestFinishedEvaluation)(this.options.automations.listEvaluations(automation.id)), (0, legacy_automation_mappers_js_1.latestAutomationRun)(this.options.automations.listRuns(automation.id)), this.options.automations.getLatestEvaluationWithState(automation.id))
143
+ : undefined;
53
144
  }
54
- createMonitor(input) {
145
+ async createMonitor(input) {
55
146
  const resolved = this.resolveCreateInput(input);
56
- const provider = this.providers.get(resolved.sourceType);
57
- provider?.validateConfig?.(resolved.sourceConfig || {});
58
- const monitor = this.repository.create(resolved);
59
- this.emitMonitor(monitor);
60
- void this.ensureSubscription(monitor);
61
- return monitor;
147
+ this.providers.get(resolved.sourceType)?.validateConfig?.(resolved.sourceConfig || {});
148
+ const mapped = (0, legacy_automation_mappers_js_1.monitorToAutomationInput)(resolved);
149
+ let monitor;
150
+ let persistedId;
151
+ try {
152
+ const persisted = this.options.automations.createFromLegacy(mapped);
153
+ persistedId = persisted.id;
154
+ monitor = (0, legacy_automation_mappers_js_1.automationToMonitor)(persisted);
155
+ await this.ensureSubscription(monitor);
156
+ return monitor;
157
+ }
158
+ catch (error) {
159
+ if (!persistedId)
160
+ throw error;
161
+ const message = this.lifecycleError(`create:${persistedId}`, error);
162
+ if (this.subscriptionHandles.has(persistedId))
163
+ throw new Error(message);
164
+ try {
165
+ this.options.automations.delete(persistedId);
166
+ }
167
+ catch (cleanupError) {
168
+ const cleanupMessage = this.lifecycleError(`create-cleanup:${persistedId}`, cleanupError);
169
+ this.options.automations.failClosedLegacyAutomation(persistedId, `${message}; ${cleanupMessage}`);
170
+ }
171
+ throw new Error(message);
172
+ }
62
173
  }
63
- updateMonitor(monitorId, input) {
64
- const resolvedMonitorId = this.resolveRequiredMonitorId(monitorId);
65
- const existing = this.repository.get(resolvedMonitorId);
66
- if (existing && input.sourceConfig) {
174
+ async updateMonitor(monitorId, input) {
175
+ this.options.automations.assertLegacyFacadesAvailable();
176
+ const existing = this.getRequiredMonitor(monitorId);
177
+ if (input.sourceConfig)
67
178
  this.providers.get(existing.sourceType)?.validateConfig?.(input.sourceConfig);
68
- }
69
- const monitor = this.repository.update(resolvedMonitorId, {
70
- ...input,
71
- ...(input.route ? { route: (0, scheduled_job_route_js_1.withoutThreadRoute)(input.route) } : {}),
179
+ const resolved = this.resolveCreateInput({
180
+ workspaceId: existing.workspaceId,
181
+ title: input.title ?? existing.title,
182
+ sourceType: existing.sourceType,
183
+ sourceConfig: input.sourceConfig ?? existing.sourceConfig,
184
+ condition: input.condition ?? existing.condition,
185
+ promptTemplate: input.promptTemplate ?? existing.promptTemplate,
186
+ platform: existing.platform,
187
+ route: input.route ? (0, scheduled_job_route_js_1.withoutThreadRoute)(input.route) : existing.route,
188
+ executionMode: input.executionMode ?? existing.executionMode,
189
+ enabled: input.enabled ?? existing.enabled,
190
+ cooldownMs: input.cooldownMs ?? existing.cooldownMs,
72
191
  });
73
- this.emitMonitor(monitor);
74
- void this.ensureSubscription(monitor);
192
+ const mapped = (0, legacy_automation_mappers_js_1.monitorToAutomationInput)(resolved);
193
+ const { workspaceId: _workspaceId, originKind: _originKind, ...update } = mapped;
194
+ if (input.sourceConfig) {
195
+ return this.updateSourceConfigTransaction(existing, update, resolved.sourceConfig || {});
196
+ }
197
+ if (!existing.enabled
198
+ && resolved.enabled
199
+ && this.providers.get(existing.sourceType)?.startMonitor) {
200
+ return this.enableSubscriptionTransaction(existing, update, resolved.sourceConfig || {});
201
+ }
202
+ if (existing.enabled
203
+ && !resolved.enabled
204
+ && this.providers.get(existing.sourceType)?.startMonitor) {
205
+ return this.disableSubscriptionTransaction(existing, update);
206
+ }
207
+ const monitor = (0, legacy_automation_mappers_js_1.automationToMonitor)(this.options.automations.updateFromLegacy(this.resolveRequiredMonitorId(monitorId), update));
208
+ await this.ensureSubscription(monitor);
75
209
  return monitor;
76
210
  }
77
- deleteMonitor(monitorId) {
78
- const resolvedMonitorId = this.resolveRequiredMonitorId(monitorId);
79
- void this.stopSubscription(resolvedMonitorId);
80
- return this.repository.delete(resolvedMonitorId);
211
+ async deleteMonitor(monitorId) {
212
+ this.options.automations.assertLegacyFacadesAvailable();
213
+ const resolved = this.resolveRequiredMonitorId(monitorId);
214
+ const existing = this.getRequiredMonitor(resolved);
215
+ await this.stopSubscription(resolved);
216
+ try {
217
+ return this.options.automations.delete(resolved);
218
+ }
219
+ catch (error) {
220
+ try {
221
+ const restored = await this.startSubscriptionHandle(existing);
222
+ if (restored) {
223
+ this.installSubscriptionHandle(existing.id, restored);
224
+ this.recoverSubscriptionHealth(existing.id);
225
+ }
226
+ }
227
+ catch (restoreError) {
228
+ const message = `${this.lifecycleError(`delete:${existing.id}`, error)}; ${this.lifecycleError(`delete-restore:${existing.id}`, restoreError)}`;
229
+ this.options.automations.failClosedLegacyAutomation(existing.id, message);
230
+ }
231
+ throw new Error(this.lifecycleError(`delete:${existing.id}`, error));
232
+ }
81
233
  }
82
234
  listRuns(monitorId) {
83
- return this.repository.listRuns(this.resolveRequiredMonitorId(monitorId));
235
+ const resolved = this.resolveRequiredMonitorId(monitorId);
236
+ const runs = this.options.automations.listRuns(resolved);
237
+ const runsByEvaluationId = new Map(runs.map((run) => [run.evaluationId, run]));
238
+ return this.options.automations.listEvaluations(resolved).map((evaluation) => (0, legacy_automation_mappers_js_1.automationToMonitorRun)(evaluation, runsByEvaluationId.get(evaluation.id)));
84
239
  }
85
240
  async runMonitorNow(monitorId, event) {
241
+ this.options.automations.assertLegacyFacadesAvailable();
86
242
  const monitor = this.getRequiredMonitor(monitorId);
87
- const snapshot = event || await this.pollMonitor(monitor, true);
88
- if (!snapshot) {
89
- const skipped = this.markSkipped(monitor, new Date().toISOString(), 'No event snapshot is available for this monitor.');
90
- this.emitRun(skipped);
91
- return skipped;
243
+ const hasExplicitEvent = event !== undefined;
244
+ const polled = hasExplicitEvent ? undefined : await this.pollMonitor(monitor, true);
245
+ if (!hasExplicitEvent && !polled)
246
+ throw new Error('Automation monitor changed while provider polling was in progress.');
247
+ const snapshot = hasExplicitEvent ? event : polled?.event;
248
+ this.options.automations.assertLegacyFacadesAvailable();
249
+ if (!hasExplicitEvent && !snapshot) {
250
+ const evaluation = this.options.automations.recordUnavailableProviderEvent(monitor.id, 'No event snapshot is available for this monitor.');
251
+ return (0, legacy_automation_mappers_js_1.automationToMonitorRun)(evaluation);
92
252
  }
93
- return this.executeMonitor(monitor, snapshot, true);
253
+ const evaluated = await this.evaluateEvent(monitor.id, snapshot, {
254
+ kind: 'manual',
255
+ ...(polled ? { token: polled.token } : {}),
256
+ });
257
+ if (!evaluated)
258
+ throw new Error('Automation monitor changed before the provider event could be evaluated.');
259
+ return evaluated;
94
260
  }
95
261
  listMonitorsForThread(threadId) {
96
- const binding = this.repository.getPlatformThreadBindingByThreadId(threadId);
97
- return this.repository
98
- .list()
99
- .filter((monitor) => monitor.route.threadId === threadId ||
100
- (binding ? (0, scheduled_job_route_js_1.scheduledJobMatchesPlatformBinding)(this.repository.toScheduledLike(monitor), binding) : false));
101
- }
102
- async tick() {
103
- if (this.pollInFlight) {
104
- return;
105
- }
106
- this.pollInFlight = true;
262
+ const binding = this.options.store.getPlatformThreadBindingByThreadId(threadId);
263
+ return this.listMonitors().filter((monitor) => monitor.route.threadId === threadId || (binding ? (0, scheduled_job_route_js_1.scheduledJobMatchesPlatformBinding)({
264
+ id: monitor.id,
265
+ workspaceId: monitor.workspaceId,
266
+ platform: monitor.platform,
267
+ route: monitor.route,
268
+ executionMode: monitor.executionMode,
269
+ triggerType: 'once',
270
+ promptTemplate: monitor.promptTemplate,
271
+ description: monitor.title,
272
+ enabled: monitor.enabled,
273
+ concurrencyPolicy: 'skip_if_running',
274
+ createdAt: monitor.createdAt,
275
+ updatedAt: monitor.updatedAt,
276
+ }, binding) : false));
277
+ }
278
+ tick(refreshSubscriptions = true, generation) {
279
+ if (generation !== undefined && !this.isCurrentLifecycle(generation, 'running'))
280
+ return Promise.resolve();
281
+ if (this.tickPromise)
282
+ return this.tickPromise;
283
+ const work = this.runTick(refreshSubscriptions, generation);
284
+ let shared;
285
+ shared = this.observeProviderWork(work).finally(() => {
286
+ if (this.tickPromise === shared)
287
+ this.tickPromise = undefined;
288
+ });
289
+ this.tickPromise = shared;
290
+ return shared;
291
+ }
292
+ async runTick(refreshSubscriptions, generation) {
107
293
  try {
108
- for (const monitor of this.repository.list().filter((candidate) => candidate.enabled)) {
109
- const provider = this.providers.get(monitor.sourceType);
110
- if (provider?.startMonitor) {
111
- continue;
112
- }
113
- const event = await this.pollMonitor(monitor, false);
114
- if (!event) {
115
- continue;
116
- }
117
- if (!(0, condition_evaluator_js_1.evaluateMonitorCondition)(monitor.condition, event)) {
118
- this.repository.updateState(monitor.id, {
119
- lastState: this.stateFromEvent(event, monitor.lastState),
120
- });
121
- continue;
294
+ if (refreshSubscriptions)
295
+ await this.refreshSubscriptions(generation);
296
+ if (generation !== undefined && !this.isCurrentLifecycle(generation, 'running'))
297
+ return;
298
+ const monitors = this.listMonitors().filter((candidate) => candidate.enabled && !this.providers.get(candidate.sourceType)?.startMonitor);
299
+ let index = 0;
300
+ const worker = async () => {
301
+ while (true) {
302
+ const monitor = monitors[index++];
303
+ if (!monitor)
304
+ return;
305
+ if (generation !== undefined && !this.isCurrentLifecycle(generation, 'running'))
306
+ return;
307
+ try {
308
+ const polled = await this.pollMonitor(monitor, false, generation);
309
+ if (generation !== undefined && !this.isCurrentLifecycle(generation, 'running'))
310
+ return;
311
+ if (polled?.event)
312
+ await this.evaluateEvent(monitor.id, polled.event, {
313
+ kind: 'poll', token: polled.token, lifecycleGeneration: generation,
314
+ });
315
+ if (generation !== undefined && !this.isCurrentLifecycle(generation, 'running'))
316
+ return;
317
+ }
318
+ catch (error) {
319
+ this.reportProviderError(`poll:${monitor.id}`, error);
320
+ }
122
321
  }
123
- if (this.isCoolingDown(monitor, event.occurredAt)) {
124
- continue;
125
- }
126
- void this.executeMonitor(monitor, event, false);
127
- }
322
+ };
323
+ await Promise.allSettled(Array.from({ length: Math.min(PROVIDER_EVENT_CONCURRENCY, monitors.length) }, () => worker()));
128
324
  }
129
325
  finally {
130
- this.pollInFlight = false;
326
+ // tickPromise owns overlap and settlement state.
131
327
  }
132
328
  }
133
- async pollMonitor(monitor, manual) {
329
+ async pollMonitor(monitor, manual, generation) {
330
+ if (this.lifecycleState === 'stopping')
331
+ return undefined;
332
+ const operationGeneration = this.lifecycleGeneration;
333
+ if (generation !== undefined && !this.isCurrentLifecycle(generation, 'running'))
334
+ return undefined;
134
335
  const provider = this.providers.get(monitor.sourceType);
135
336
  if (!provider?.poll) {
136
- if (manual) {
337
+ if (manual)
137
338
  throw new Error(`No polling provider is available for monitor source "${monitor.sourceType}"`);
138
- }
139
- return null;
339
+ return undefined;
140
340
  }
141
- return provider.poll({
341
+ const token = this.providerDefinitionToken(monitor.id);
342
+ const event = await this.observeProviderWork(Promise.resolve(provider.poll({
142
343
  monitorId: monitor.id,
143
344
  workspaceId: monitor.workspaceId,
144
345
  sourceConfig: monitor.sourceConfig,
145
346
  lastState: monitor.lastState,
146
- });
347
+ })));
348
+ if (operationGeneration !== this.lifecycleGeneration || this.isLifecycleStopping())
349
+ return undefined;
350
+ if (generation !== undefined && !this.isCurrentLifecycle(generation, 'running'))
351
+ return undefined;
352
+ this.options.automations.assertLegacyFacadesAvailable();
353
+ if (!this.isProviderDefinitionCurrent(monitor.id, token))
354
+ return undefined;
355
+ return { event, token };
147
356
  }
148
- async executeMonitor(monitor, event, manual) {
149
- if (this.runningMonitors.has(monitor.id)) {
150
- const skipped = this.markSkipped(monitor, event.occurredAt, 'Skipped because the previous monitor run is still active.', event);
151
- this.emitRun(skipped);
152
- return skipped;
153
- }
154
- this.runningMonitors.add(monitor.id);
155
- const run = this.repository.createRun(monitor.id, 'queued', {
156
- triggeredAt: event.occurredAt,
157
- eventSnapshot: event,
158
- deliveryStatus: 'pending',
159
- });
160
- this.emitRun(run);
357
+ evaluateEvent(monitorId, event, admission) {
358
+ let snapshot;
161
359
  try {
162
- const running = this.repository.updateRun(run.id, {
163
- status: 'running',
164
- startedAt: new Date().toISOString(),
165
- });
166
- this.emitRun(running);
167
- const result = await this.executor.execute(monitor, event);
168
- const succeeded = this.repository.updateRun(run.id, {
169
- status: 'succeeded',
170
- finishedAt: new Date().toISOString(),
171
- threadId: result.threadId,
172
- runId: result.runId,
173
- deliveryMode: result.deliveryMode,
174
- deliveryStatus: result.deliveryStatus || 'succeeded',
175
- deliveryError: result.deliveryError || '',
176
- lastBridgeEventAt: result.lastBridgeEventAt,
177
- error: '',
178
- });
179
- this.repository.updateState(monitor.id, {
180
- lastState: this.stateFromEvent(event, monitor.lastState),
181
- lastTriggeredAt: event.occurredAt,
182
- lastStatus: 'succeeded',
183
- lastError: '',
184
- });
185
- this.emitRun(succeeded);
186
- const nextMonitor = this.repository.get(monitor.id);
187
- if (nextMonitor)
188
- this.emitMonitor(nextMonitor);
189
- return succeeded;
360
+ snapshot = (0, automation_service_js_1.normalizeProviderEventSnapshot)(event);
190
361
  }
191
362
  catch (error) {
192
- const message = error instanceof Error ? error.message : String(error);
193
- const failed = this.repository.updateRun(run.id, {
194
- status: 'failed',
195
- finishedAt: new Date().toISOString(),
196
- error: message,
197
- deliveryStatus: 'failed',
198
- deliveryError: message,
199
- });
200
- this.repository.updateState(monitor.id, {
201
- lastState: this.stateFromEvent(event, monitor.lastState),
202
- lastTriggeredAt: event.occurredAt,
203
- lastStatus: 'failed',
204
- lastError: message,
205
- });
206
- this.emitRun(failed);
207
- const nextMonitor = this.repository.get(monitor.id);
208
- if (nextMonitor)
209
- this.emitMonitor(nextMonitor);
210
- this.options.log?.(`automation monitor failed ${monitor.id}: ${message}`);
211
- return failed;
363
+ return Promise.reject(error);
212
364
  }
213
- finally {
214
- this.runningMonitors.delete(monitor.id);
215
- }
216
- }
217
- markSkipped(monitor, triggeredAt, error, eventSnapshot) {
218
- return this.repository.createRun(monitor.id, 'skipped', {
219
- triggeredAt,
220
- error,
221
- eventSnapshot,
222
- deliveryStatus: 'skipped',
223
- deliveryError: error,
224
- });
365
+ return this.observeProviderWork(this.evaluateNormalizedEvent(monitorId, snapshot, admission));
225
366
  }
226
- stateFromEvent(event, previous) {
227
- return {
228
- ...(previous || {}),
229
- lastEventId: event.id,
230
- lastEventAt: event.occurredAt,
231
- latestPrice: event.payload.latestPrice ?? previous?.latestPrice,
232
- previousPrice: event.payload.previousPrice ?? previous?.previousPrice,
233
- payload: event.payload,
234
- };
235
- }
236
- isCoolingDown(monitor, occurredAt) {
237
- if (!monitor.lastTriggeredAt || monitor.cooldownMs <= 0) {
238
- return false;
239
- }
240
- return Date.parse(occurredAt) - Date.parse(monitor.lastTriggeredAt) < monitor.cooldownMs;
241
- }
242
- resolveCreateInput(input) {
243
- if (input.platform && input.route) {
244
- return {
245
- ...input,
246
- route: (0, scheduled_job_route_js_1.routeWithPlatformInstance)((0, scheduled_job_route_js_1.withoutThreadRoute)(input.route), input.platform),
247
- };
248
- }
249
- const threadId = String(input.threadId || input.route?.threadId || '').trim();
250
- if (threadId) {
251
- const binding = this.repository.getPlatformThreadBindingByThreadId(threadId);
252
- if (binding && binding.workspace_id === input.workspaceId) {
253
- return {
254
- ...input,
255
- platform: binding.platform,
256
- route: (0, scheduled_job_route_js_1.routeFromPlatformThreadBinding)(binding),
257
- };
367
+ async evaluateNormalizedEvent(monitorId, event, admission) {
368
+ const admitted = await this.acquireProviderEventPermit(admission.kind === 'manual'
369
+ ? undefined
370
+ : admission.lifecycleGeneration);
371
+ if (!admitted)
372
+ return undefined;
373
+ try {
374
+ if (admission.kind !== 'manual'
375
+ && admission.lifecycleGeneration !== undefined
376
+ && !this.isCurrentLifecycle(admission.lifecycleGeneration, 'running'))
377
+ return undefined;
378
+ this.options.automations.assertLegacyFacadesAvailable();
379
+ const definition = this.options.automations.get(monitorId);
380
+ if (!definition || definition.activation.kind !== 'provider-event')
381
+ return undefined;
382
+ if (admission.kind === 'manual') {
383
+ if (admission.token && !this.isProviderDefinitionCurrent(monitorId, admission.token))
384
+ return undefined;
258
385
  }
386
+ else {
387
+ if (!definition.enabled || definition.health === 'blocked')
388
+ return undefined;
389
+ if (admission.kind === 'subscription'
390
+ && (this.subscriptionGenerations.get(monitorId) !== admission.generation
391
+ || !this.subscriptionHandles.has(monitorId)))
392
+ return undefined;
393
+ if (admission.kind === 'poll' && !this.isProviderDefinitionCurrent(monitorId, admission.token))
394
+ return undefined;
395
+ }
396
+ const evaluation = await this.observeProviderWork(this.options.automations.evaluateProviderEvent(monitorId, event));
397
+ if (admission.kind !== 'manual'
398
+ && admission.lifecycleGeneration !== undefined
399
+ && !this.isCurrentLifecycle(admission.lifecycleGeneration, 'running'))
400
+ return undefined;
401
+ const run = this.options.automations.listRuns(monitorId).find((candidate) => candidate.evaluationId === evaluation.id);
402
+ return (0, legacy_automation_mappers_js_1.automationToMonitorRun)(evaluation, run);
259
403
  }
260
- return {
261
- ...input,
262
- platform: 'local',
263
- route: {
264
- type: 'local.thread',
265
- channelId: input.workspaceId,
266
- },
267
- };
268
- }
269
- getRequiredMonitor(monitorId) {
270
- const monitor = this.getMonitor(monitorId);
271
- if (!monitor) {
272
- throw new Error(`Automation monitor not found: ${monitorId}`);
273
- }
274
- return monitor;
275
- }
276
- resolveMonitorId(monitorId) {
277
- if (this.repository.get(monitorId)) {
278
- return monitorId;
279
- }
280
- const matches = this.repository
281
- .list()
282
- .filter((monitor) => (0, monitor_id_js_1.toPublicAutomationMonitorId)(monitor.id) === monitorId);
283
- if (matches.length === 0) {
284
- return '';
285
- }
286
- if (matches.length > 1) {
287
- throw new Error(`Automation monitor id is ambiguous: ${monitorId}`);
404
+ finally {
405
+ this.releaseProviderEventPermit();
288
406
  }
289
- return matches[0].id;
290
407
  }
291
- resolveRequiredMonitorId(monitorId) {
292
- const resolved = this.resolveMonitorId(monitorId);
293
- if (!resolved) {
294
- throw new Error(`Automation monitor not found: ${monitorId}`);
408
+ async acquireProviderEventPermit(generation) {
409
+ if (!this.canAdmitProviderWork(generation))
410
+ return false;
411
+ if (this.providerEventsInFlight >= PROVIDER_EVENT_CONCURRENCY) {
412
+ const admitted = await new Promise((resolve) => this.providerEventWaiters.push(resolve));
413
+ if (!admitted)
414
+ return false;
295
415
  }
296
- return resolved;
297
- }
298
- emitMonitor(monitor) {
299
- this.options.eventBus.emit({ type: 'automation.monitor.updated', payload: monitor });
416
+ if (!this.canAdmitProviderWork(generation))
417
+ return false;
418
+ this.providerEventsInFlight += 1;
419
+ return true;
300
420
  }
301
- emitRun(run) {
302
- this.options.eventBus.emit({ type: 'automation.monitor.run.updated', payload: run });
421
+ releaseProviderEventPermit() {
422
+ this.providerEventsInFlight -= 1;
423
+ this.providerEventWaiters.shift()?.(true);
303
424
  }
304
- async refreshSubscriptions() {
425
+ async refreshSubscriptions(generation) {
426
+ if (generation !== undefined && !this.isActiveLifecycle(generation))
427
+ return;
305
428
  const activeIds = new Set();
306
- for (const monitor of this.repository.list().filter((candidate) => candidate.enabled)) {
307
- const provider = this.providers.get(monitor.sourceType);
308
- if (!provider?.startMonitor)
429
+ const work = [];
430
+ for (const monitor of this.listMonitors().filter((candidate) => candidate.enabled)) {
431
+ if (!this.providers.get(monitor.sourceType)?.startMonitor)
309
432
  continue;
310
433
  activeIds.add(monitor.id);
311
- await this.ensureSubscription(monitor);
434
+ if (!this.subscriptionHandles.has(monitor.id)) {
435
+ work.push({ monitorId: monitor.id, kind: 'start', run: () => this.ensureSubscription(monitor, generation) });
436
+ }
437
+ else if (generation !== undefined) {
438
+ this.handleLifecycleGenerations.set(monitor.id, generation);
439
+ }
312
440
  }
313
- for (const monitorId of [...this.subscriptionHandles.keys()]) {
441
+ for (const monitorId of this.subscriptionHandles.keys()) {
314
442
  if (!activeIds.has(monitorId)) {
315
- await this.stopSubscription(monitorId);
443
+ work.push({ monitorId, kind: 'stop', run: () => this.stopSubscription(monitorId) });
316
444
  }
317
445
  }
446
+ let index = 0;
447
+ const worker = async () => {
448
+ while (true) {
449
+ const entry = work[index++];
450
+ if (!entry)
451
+ return;
452
+ if (generation !== undefined && !this.isActiveLifecycle(generation))
453
+ return;
454
+ try {
455
+ await entry.run();
456
+ if (generation !== undefined && !this.isActiveLifecycle(generation))
457
+ return;
458
+ }
459
+ catch (error) {
460
+ if (generation !== undefined && !this.isActiveLifecycle(generation))
461
+ return;
462
+ if (entry.kind === 'start')
463
+ this.blockSubscription(entry.monitorId, error);
464
+ this.reportProviderError(`subscription:${entry.monitorId}`, error);
465
+ }
466
+ }
467
+ };
468
+ await Promise.all(Array.from({ length: Math.min(PROVIDER_EVENT_CONCURRENCY, work.length) }, () => worker()));
469
+ if (generation !== undefined && !this.isActiveLifecycle(generation))
470
+ return;
318
471
  }
319
- async ensureSubscription(monitor) {
472
+ async ensureSubscription(monitor, lifecycleGeneration) {
473
+ if (this.lifecycleState === 'stopping')
474
+ return;
475
+ if (lifecycleGeneration !== undefined && !this.isActiveLifecycle(lifecycleGeneration))
476
+ return;
477
+ this.options.automations.assertLegacyFacadesAvailable();
320
478
  const provider = this.providers.get(monitor.sourceType);
321
479
  if (!monitor.enabled || !provider?.startMonitor) {
322
480
  await this.stopSubscription(monitor.id);
481
+ if (lifecycleGeneration !== undefined && !this.isActiveLifecycle(lifecycleGeneration))
482
+ return;
323
483
  return;
324
484
  }
325
485
  if (this.subscriptionHandles.has(monitor.id)) {
486
+ if (lifecycleGeneration !== undefined)
487
+ this.handleLifecycleGenerations.set(monitor.id, lifecycleGeneration);
488
+ this.recoverSubscriptionHealth(monitor.id);
489
+ return;
490
+ }
491
+ const handle = await this.startSubscriptionHandle(monitor, lifecycleGeneration);
492
+ if (lifecycleGeneration !== undefined && !this.isActiveLifecycle(lifecycleGeneration)) {
493
+ if (handle)
494
+ await this.stopProviderHandle(monitor.id, handle, 'stale-ensure');
326
495
  return;
327
496
  }
328
- const handle = await provider.startMonitor({
497
+ if (handle) {
498
+ this.installSubscriptionHandle(monitor.id, handle, lifecycleGeneration);
499
+ this.recoverSubscriptionHealth(monitor.id);
500
+ }
501
+ }
502
+ async startSubscriptionHandle(monitor, lifecycleGeneration) {
503
+ if (this.lifecycleState === 'stopping')
504
+ return undefined;
505
+ const operationGeneration = this.lifecycleGeneration;
506
+ if (lifecycleGeneration !== undefined && !this.isActiveLifecycle(lifecycleGeneration))
507
+ return undefined;
508
+ const provider = this.providers.get(monitor.sourceType);
509
+ if (!monitor.enabled || !provider?.startMonitor)
510
+ return undefined;
511
+ const generation = (this.subscriptionGenerations.get(monitor.id) || 0) + 1;
512
+ this.subscriptionGenerations.set(monitor.id, generation);
513
+ const handle = await this.observeProviderWork(Promise.resolve(provider.startMonitor({
329
514
  monitorId: monitor.id,
330
515
  workspaceId: monitor.workspaceId,
331
516
  sourceConfig: monitor.sourceConfig,
332
517
  lastState: monitor.lastState,
333
518
  emit: async (event) => {
334
- const latest = this.repository.get(monitor.id);
335
- if (!latest || !latest.enabled)
336
- return;
337
- if (!(0, condition_evaluator_js_1.evaluateMonitorCondition)(latest.condition, event)) {
338
- this.repository.updateState(latest.id, { lastState: this.stateFromEvent(event, latest.lastState) });
339
- return;
519
+ try {
520
+ const callbackLifecycleGeneration = this.handleLifecycleGenerations.get(monitor.id);
521
+ if (callbackLifecycleGeneration === undefined
522
+ || !this.isCurrentLifecycle(callbackLifecycleGeneration, 'running'))
523
+ return;
524
+ const latest = this.getMonitor(monitor.id);
525
+ const definition = this.options.automations.get(monitor.id);
526
+ if (latest?.enabled
527
+ && definition?.health !== 'blocked'
528
+ && this.subscriptionHandles.has(monitor.id)) {
529
+ await this.evaluateEvent(latest.id, event, {
530
+ kind: 'subscription', generation, lifecycleGeneration: callbackLifecycleGeneration,
531
+ });
532
+ }
533
+ }
534
+ catch (error) {
535
+ this.reportProviderError(`event:${monitor.id}`, error);
340
536
  }
341
- if (this.isCoolingDown(latest, event.occurredAt))
342
- return;
343
- await this.executeMonitor(latest, event, false);
344
537
  },
345
- });
346
- this.subscriptionHandles.set(monitor.id, handle);
538
+ })));
539
+ if (operationGeneration !== this.lifecycleGeneration || this.isLifecycleStopping()) {
540
+ await this.stopProviderHandle(monitor.id, handle, 'stale-start');
541
+ return undefined;
542
+ }
543
+ if (lifecycleGeneration !== undefined && !this.isActiveLifecycle(lifecycleGeneration)) {
544
+ await this.stopProviderHandle(monitor.id, handle, 'stale-start');
545
+ return undefined;
546
+ }
547
+ try {
548
+ this.options.automations.assertLegacyFacadesAvailable();
549
+ }
550
+ catch (error) {
551
+ try {
552
+ await this.observeProviderWork(Promise.resolve().then(() => handle.stop()));
553
+ }
554
+ catch (stopError) {
555
+ this.installSubscriptionHandle(monitor.id, handle, lifecycleGeneration);
556
+ const message = `${this.lifecycleError(`degraded-start:${monitor.id}`, error)}; ${this.lifecycleError(`degraded-stop:${monitor.id}`, stopError)}`;
557
+ this.options.automations.failClosedLegacyAutomation(monitor.id, message);
558
+ this.reportProviderError(`degraded-stop:${monitor.id}`, stopError);
559
+ }
560
+ throw error;
561
+ }
562
+ return handle;
347
563
  }
348
564
  async stopSubscription(monitorId) {
349
565
  const handle = this.subscriptionHandles.get(monitorId);
350
566
  if (!handle)
351
567
  return;
352
- this.subscriptionHandles.delete(monitorId);
353
- await handle.stop();
568
+ await this.observeProviderWork(Promise.resolve().then(() => handle.stop()));
569
+ if (this.subscriptionHandles.get(monitorId) === handle) {
570
+ this.subscriptionHandles.delete(monitorId);
571
+ this.handleLifecycleGenerations.delete(monitorId);
572
+ this.subscriptionGenerations.set(monitorId, (this.subscriptionGenerations.get(monitorId) || 0) + 1);
573
+ }
574
+ }
575
+ async updateSourceConfigTransaction(existing, update, sourceConfig) {
576
+ await this.stopSubscription(existing.id);
577
+ const prospective = { ...existing, ...('enabled' in update ? { enabled: update.enabled ?? existing.enabled } : {}), sourceConfig };
578
+ let nextHandle;
579
+ try {
580
+ nextHandle = await this.startSubscriptionHandle(prospective);
581
+ }
582
+ catch (error) {
583
+ if (!this.subscriptionHandles.has(existing.id)) {
584
+ await this.restoreSubscriptionOrFailClosed(existing, error);
585
+ }
586
+ throw new Error(this.lifecycleError(`update-start:${existing.id}`, error));
587
+ }
588
+ try {
589
+ if (nextHandle)
590
+ this.installSubscriptionHandle(existing.id, nextHandle);
591
+ const monitor = (0, legacy_automation_mappers_js_1.automationToMonitor)(this.options.automations.updateFromLegacy(existing.id, update));
592
+ this.recoverSubscriptionHealth(existing.id);
593
+ return monitor;
594
+ }
595
+ catch (error) {
596
+ if (nextHandle) {
597
+ try {
598
+ await this.stopSubscription(existing.id);
599
+ }
600
+ catch (stopError) {
601
+ const message = `${this.lifecycleError(`update-persist:${existing.id}`, error)}; ${this.lifecycleError(`update-new-stop:${existing.id}`, stopError)}`;
602
+ this.options.automations.failClosedLegacyAutomation(existing.id, message);
603
+ throw new Error(message);
604
+ }
605
+ }
606
+ await this.restoreSubscriptionOrFailClosed(existing, error);
607
+ throw new Error(this.lifecycleError(`update-persist:${existing.id}`, error));
608
+ }
609
+ }
610
+ async enableSubscriptionTransaction(existing, update, sourceConfig) {
611
+ let handle = this.subscriptionHandles.get(existing.id);
612
+ if (!handle) {
613
+ try {
614
+ handle = await this.startSubscriptionHandle({ ...existing, enabled: true, sourceConfig });
615
+ }
616
+ catch (error) {
617
+ throw new Error(this.lifecycleError(`enable-start:${existing.id}`, error));
618
+ }
619
+ if (handle)
620
+ this.installSubscriptionHandle(existing.id, handle);
621
+ }
622
+ try {
623
+ const monitor = (0, legacy_automation_mappers_js_1.automationToMonitor)(this.options.automations.updateFromLegacy(existing.id, update));
624
+ this.recoverSubscriptionHealth(existing.id);
625
+ return monitor;
626
+ }
627
+ catch (error) {
628
+ if (handle) {
629
+ try {
630
+ await this.stopSubscription(existing.id);
631
+ }
632
+ catch (stopError) {
633
+ const message = `${this.lifecycleError(`enable-persist:${existing.id}`, error)}; ${this.lifecycleError(`enable-stop:${existing.id}`, stopError)}`;
634
+ this.options.automations.failClosedLegacyAutomation(existing.id, message);
635
+ throw new Error(message);
636
+ }
637
+ }
638
+ this.options.automations.failClosedLegacyAutomation(existing.id, this.lifecycleError(`enable-persist:${existing.id}`, error));
639
+ throw new Error(this.lifecycleError(`enable-persist:${existing.id}`, error));
640
+ }
641
+ }
642
+ async disableSubscriptionTransaction(existing, update) {
643
+ try {
644
+ await this.stopSubscription(existing.id);
645
+ }
646
+ catch (error) {
647
+ throw new Error(this.lifecycleError(`disable-stop:${existing.id}`, error));
648
+ }
649
+ try {
650
+ return (0, legacy_automation_mappers_js_1.automationToMonitor)(this.options.automations.updateFromLegacy(existing.id, update));
651
+ }
652
+ catch (error) {
653
+ await this.restoreSubscriptionOrFailClosed(existing, error);
654
+ throw new Error(this.lifecycleError(`disable-persist:${existing.id}`, error));
655
+ }
656
+ }
657
+ async restoreSubscriptionOrFailClosed(existing, cause) {
658
+ try {
659
+ const restored = await this.startSubscriptionHandle(existing);
660
+ if (restored) {
661
+ this.installSubscriptionHandle(existing.id, restored);
662
+ this.recoverSubscriptionHealth(existing.id);
663
+ }
664
+ }
665
+ catch (restoreError) {
666
+ const message = `${this.lifecycleError(`subscription-transition:${existing.id}`, cause)}; ${this.lifecycleError(`subscription-restore:${existing.id}`, restoreError)}`;
667
+ this.options.automations.failClosedLegacyAutomation(existing.id, message);
668
+ }
669
+ }
670
+ reportProviderError(scope, error) {
671
+ const message = this.lifecycleError(scope, error);
672
+ try {
673
+ this.options.log?.(`automation monitor provider ${scope} failed: ${message}`);
674
+ }
675
+ catch {
676
+ // Provider diagnostics must not destabilize lifecycle isolation.
677
+ }
678
+ }
679
+ lifecycleError(scope, error) {
680
+ return (0, automation_service_js_1.normalizeAutomationError)(error, `automation monitor provider ${scope} failed: `);
681
+ }
682
+ blockSubscription(monitorId, error) {
683
+ const reason = (0, automation_service_js_1.providerLifecycleBlockReason)(error);
684
+ try {
685
+ this.options.automations.assertLegacyFacadesAvailable();
686
+ this.options.automations.markLegacyProviderLifecycleBlocked(monitorId, reason);
687
+ }
688
+ catch (healthError) {
689
+ this.reportProviderError(`subscription-health:${monitorId}`, healthError);
690
+ }
691
+ }
692
+ recoverSubscriptionHealth(monitorId) {
693
+ const definition = this.options.automations.get(monitorId);
694
+ const reason = definition?.health === 'blocked' ? definition.blockedReason : undefined;
695
+ if (reason?.startsWith(automation_service_js_1.PROVIDER_LIFECYCLE_BLOCK_PREFIX)) {
696
+ this.options.automations.clearLegacyProviderLifecycleBlocked(monitorId, reason);
697
+ }
698
+ }
699
+ providerDefinitionToken(monitorId) {
700
+ const definition = this.options.automations.get(monitorId);
701
+ if (!definition || definition.activation.kind !== 'provider-event')
702
+ return '';
703
+ return `${definition.updatedAt}\n${JSON.stringify(definition.activation.sourceConfig || {})}`;
704
+ }
705
+ isProviderDefinitionCurrent(monitorId, token) {
706
+ return token !== '' && this.providerDefinitionToken(monitorId) === token;
707
+ }
708
+ isCurrentLifecycle(generation, state) {
709
+ return this.lifecycleGeneration === generation && this.lifecycleState === state;
710
+ }
711
+ isActiveLifecycle(generation) {
712
+ return this.lifecycleGeneration === generation
713
+ && (this.lifecycleState === 'starting' || this.lifecycleState === 'running');
714
+ }
715
+ canAdmitProviderWork(generation) {
716
+ return this.lifecycleState !== 'stopping'
717
+ && (generation === undefined || this.isCurrentLifecycle(generation, 'running'));
718
+ }
719
+ isLifecycleStopping() {
720
+ return this.lifecycleState === 'stopping';
721
+ }
722
+ installSubscriptionHandle(monitorId, handle, lifecycleGeneration = this.lifecycleGeneration) {
723
+ this.subscriptionHandles.set(monitorId, handle);
724
+ if (this.lifecycleState === 'running' && lifecycleGeneration === this.lifecycleGeneration) {
725
+ this.handleLifecycleGenerations.set(monitorId, lifecycleGeneration);
726
+ }
727
+ else {
728
+ this.handleLifecycleGenerations.delete(monitorId);
729
+ }
730
+ }
731
+ async stopProviderHandle(monitorId, handle, scope) {
732
+ try {
733
+ await this.observeProviderWork(Promise.resolve().then(() => handle.stop()));
734
+ }
735
+ catch (error) {
736
+ if (!this.subscriptionHandles.has(monitorId))
737
+ this.installSubscriptionHandle(monitorId, handle);
738
+ this.reportProviderError(`${scope}:${monitorId}`, error);
739
+ }
740
+ }
741
+ observeProviderWork(work) {
742
+ let observed;
743
+ observed = work.finally(() => this.inFlight.delete(observed));
744
+ this.inFlight.add(observed);
745
+ return observed;
746
+ }
747
+ async settleInFlight() {
748
+ while (this.inFlight.size > 0) {
749
+ await Promise.allSettled([...this.inFlight]);
750
+ }
751
+ }
752
+ resolveCreateInput(input) {
753
+ if (input.platform && input.route) {
754
+ return { ...input, route: (0, scheduled_job_route_js_1.routeWithPlatformInstance)((0, scheduled_job_route_js_1.withoutThreadRoute)(input.route), input.platform) };
755
+ }
756
+ const threadId = String(input.threadId || input.route?.threadId || '').trim();
757
+ if (threadId) {
758
+ const binding = this.options.store.getPlatformThreadBindingByThreadId(threadId);
759
+ if (binding && binding.workspace_id === input.workspaceId) {
760
+ return { ...input, platform: binding.platform, route: (0, scheduled_job_route_js_1.routeFromPlatformThreadBinding)(binding) };
761
+ }
762
+ }
763
+ return {
764
+ ...input,
765
+ platform: 'local',
766
+ route: { type: 'local.thread', channelId: input.workspaceId },
767
+ };
768
+ }
769
+ getRequiredMonitor(monitorId) {
770
+ const monitor = this.getMonitor(monitorId);
771
+ if (!monitor)
772
+ throw new Error(`Automation monitor not found: ${monitorId}`);
773
+ return monitor;
774
+ }
775
+ resolveMonitorId(monitorId) {
776
+ const direct = this.options.automations.get(monitorId);
777
+ if (direct?.originKind === 'automation-monitor')
778
+ return direct.id;
779
+ const matches = this.options.automations.list().filter((automation) => automation.originKind === 'automation-monitor' && (0, monitor_id_js_1.toPublicAutomationMonitorId)(automation.id) === monitorId);
780
+ if (matches.length > 1)
781
+ throw new Error(`Automation monitor id is ambiguous: ${monitorId}`);
782
+ return matches[0]?.id || '';
783
+ }
784
+ resolveRequiredMonitorId(monitorId) {
785
+ const resolved = this.resolveMonitorId(monitorId);
786
+ if (!resolved)
787
+ throw new Error(`Automation monitor not found: ${monitorId}`);
788
+ return resolved;
354
789
  }
355
790
  }
356
791
  exports.AutomationMonitorService = AutomationMonitorService;