@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.
- package/README.md +18 -0
- package/dist/renderer/assets/AutomationList-BgJgAdVO.js +16 -0
- package/dist/renderer/assets/{Badge-BfY1VaHS.js → Badge-Bq2hPVcR.js} +1 -1
- package/dist/renderer/assets/{Card-DWA_Tww_.js → Card-BPJNSZce.js} +1 -1
- package/dist/renderer/assets/{Config-nHj3g1U5.js → Config-aSFqlI88.js} +2 -2
- package/dist/renderer/assets/Dashboard-D3ezFmb7.js +36 -0
- package/dist/renderer/assets/{EmptyState-zatDjLj_.js → EmptyState-DK496gBq.js} +1 -1
- package/dist/renderer/assets/{HighlightedMarkdown-CsM5n70d.js → HighlightedMarkdown-Ckre5jNv.js} +1 -1
- package/dist/renderer/assets/{Input-R1XdFK1K.js → Input-C5C1d0ba.js} +1 -1
- package/dist/renderer/assets/{KnowledgeDetail-Co5sxeGd.js → KnowledgeDetail-N8Ey1ikc.js} +2 -2
- package/dist/renderer/assets/{KnowledgeHome-DwziPw5m.js → KnowledgeHome-9aF8nJEf.js} +2 -2
- package/dist/renderer/assets/{Logs-NwZUtR_G.js → Logs-sgAdwEeS.js} +2 -2
- package/dist/renderer/assets/{Modal-CrcIwIFL.js → Modal-BB10hTRc.js} +3 -3
- package/dist/renderer/assets/{Page-Dp0obeSw.js → Page-DfC319Ck.js} +1 -1
- package/dist/renderer/assets/{Select-6mVQaf84.js → Select-RHJBmNER.js} +1 -1
- package/dist/renderer/assets/{ThreadChat-BFzbn3L6.js → ThreadChat-BU1zCdr4.js} +3 -3
- package/dist/renderer/assets/{Workspace-DCmo8BMm.js → Workspace-BoZCz7S8.js} +1 -1
- package/dist/renderer/assets/{arrow-left-9rjg_Hp4.js → arrow-left-D2V1eZSD.js} +1 -1
- package/dist/renderer/assets/{book-open-Bu8GCHnG.js → book-open-DUcBn7Bb.js} +1 -1
- package/dist/renderer/assets/{channels-BxVbG-6j.js → channels-DMhDSlf2.js} +2 -2
- package/dist/renderer/assets/{chevron-down-BE8uQmWM.js → chevron-down-Ca2FQDB7.js} +1 -1
- package/dist/renderer/assets/en-Bnr4nMzg.js +1 -0
- package/dist/renderer/assets/es-B1GIFh3i.js +1 -0
- package/dist/renderer/assets/{index-BIKL3fTP.css → index-B6WcpkJF.css} +1 -1
- package/dist/renderer/assets/{index-B7TeF6Es.js → index-BlnvlekJ.js} +1 -1
- package/dist/renderer/assets/index-DRJ5wgw_.js +162 -0
- package/dist/renderer/assets/ja-CDWPWqEW.js +1 -0
- package/dist/renderer/assets/{knowledge-Kl19SqGS.js → knowledge-D1-k56eY.js} +1 -1
- package/dist/renderer/assets/{pencil-CPB7SK1m.js → pencil-BhWdv_86.js} +1 -1
- package/dist/renderer/assets/{plus-Bt26z-4q.js → plus-BacLMlK3.js} +1 -1
- package/dist/renderer/assets/{save-Une7GMxT.js → save-Bpoxbzau.js} +1 -1
- package/dist/renderer/assets/{search-DE0agJjJ.js → search-BVJcpdFX.js} +1 -1
- package/dist/renderer/assets/{shield-check-De7xaXZh.js → shield-check-C2cw_Ip6.js} +1 -1
- package/dist/renderer/assets/{threads-BnsNL5ei.js → threads-DOsBQBg_.js} +1 -1
- package/dist/renderer/assets/{trash-2-6iGF59Ac.js → trash-2-Bs3rEm3g.js} +1 -1
- package/dist/renderer/assets/zh-MSddxMDX.js +1 -0
- package/dist/renderer/assets/zh-TW-C_LNWX0o.js +1 -0
- package/dist/renderer/index.html +2 -2
- package/dist-electron/electron/managed-skills/agent-browser/SKILL.md +41 -0
- package/dist-electron/electron/managed-skills/agent-browser/references/authentication.md +83 -0
- package/dist-electron/electron/managed-skills/agent-browser/references/commands.md +50 -0
- package/dist-electron/electron/managed-skills/agent-browser/references/profiling.md +20 -0
- package/dist-electron/electron/managed-skills/agent-browser/references/proxy-support.md +18 -0
- package/dist-electron/electron/managed-skills/agent-browser/references/session-management.md +17 -0
- package/dist-electron/electron/managed-skills/agent-browser/references/snapshot-refs.md +17 -0
- package/dist-electron/electron/managed-skills/agent-browser/references/video-recording.md +21 -0
- package/dist-electron/electron/managed-skills/agent-browser/templates/authenticated-session.sh +11 -0
- package/dist-electron/electron/managed-skills/agent-browser/templates/capture-workflow.sh +12 -0
- package/dist-electron/electron/managed-skills/agent-browser/templates/form-automation.sh +10 -0
- package/dist-electron/electron/managed-skills/condition-trigger/SKILL.md +21 -0
- package/dist-electron/electron/managed-skills/condition-trigger/scripts/register-condition-trigger.sh +106 -0
- package/dist-electron/electron/managed-skills/knowledge-base/SKILL.md +13 -0
- package/dist-electron/electron/managed-skills/knowledge-base/scripts/search-knowledge.sh +80 -0
- package/dist-electron/packages/contracts/src/automations.js +211 -0
- package/dist-electron/packages/contracts/src/index.js +1 -0
- package/dist-electron/packages/core-sdk/src/automations.js +87 -0
- package/dist-electron/packages/core-sdk/src/client.js +101 -0
- package/dist-electron/packages/core-sdk/src/index.js +2 -1
- package/dist-electron/services/local-ai-core/src/acp/store/automation-script-store.js +415 -0
- package/dist-electron/services/local-ai-core/src/acp/store/automation-store.js +829 -0
- package/dist-electron/services/local-ai-core/src/acp/store/local-core-acp-store.js +158 -0
- package/dist-electron/services/local-ai-core/src/acp/store/schema.js +81 -0
- package/dist-electron/services/local-ai-core/src/automation/automation-action-executor.js +144 -0
- package/dist-electron/services/local-ai-core/src/automation/automation-condition-engine.js +79 -0
- package/dist-electron/services/local-ai-core/src/automation/automation-conversation-executor.js +39 -73
- package/dist-electron/services/local-ai-core/src/automation/automation-monitor-service.js +695 -260
- package/dist-electron/services/local-ai-core/src/automation/automation-script-service.js +507 -0
- package/dist-electron/services/local-ai-core/src/automation/automation-service.js +1019 -0
- package/dist-electron/services/local-ai-core/src/automation/automation-trigger-engine.js +141 -0
- package/dist-electron/services/local-ai-core/src/automation/condition-evaluator.js +60 -17
- package/dist-electron/services/local-ai-core/src/automation/legacy-automation-mappers.js +286 -0
- package/dist-electron/services/local-ai-core/src/automation/scripts/anthropic-sandbox-runner.js +792 -0
- package/dist-electron/services/local-ai-core/src/automation/scripts/sandbox-runner.js +2 -0
- package/dist-electron/services/local-ai-core/src/automation/scripts/script-package.js +475 -0
- package/dist-electron/services/local-ai-core/src/automation/scripts/script-protocol-runner.js +266 -0
- package/dist-electron/services/local-ai-core/src/automation/scripts/secret-resolver.js +37 -0
- package/dist-electron/services/local-ai-core/src/channel/weixin/inbound-poller.js +1 -2
- package/dist-electron/services/local-ai-core/src/channel/weixin/transport.js +9 -8
- package/dist-electron/services/local-ai-core/src/cli/lac.js +229 -0
- package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +21 -0
- package/dist-electron/services/local-ai-core/src/runtime/deployment-diagnostics.js +76 -0
- package/dist-electron/services/local-ai-core/src/runtime/handlers/automations-handler.js +322 -0
- package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +20 -0
- package/dist-electron/services/local-ai-core/src/runtime/managed-skill-catalog.js +37 -0
- package/dist-electron/services/local-ai-core/src/runtime/server-helpers.js +14 -4
- package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +75 -0
- package/dist-electron/services/local-ai-core/src/runtime/server.js +21 -0
- package/dist-electron/services/local-ai-core/src/runtime/standalone.js +1 -0
- package/dist-electron/services/local-ai-core/src/scheduler/cron.js +151 -39
- package/dist-electron/services/local-ai-core/src/scheduler/scheduled-conversation-executor.js +2 -4
- package/dist-electron/services/local-ai-core/src/scheduler/scheduled-job-application-service.js +64 -12
- package/dist-electron/services/local-ai-core/src/scheduler/scheduler-service.js +71 -4
- package/dist-electron/services/local-ai-core/src/scheduler/thread-resolution.js +10 -0
- package/dist-electron/services/local-ai-core/src/thread/agent-message-policy.js +16 -1
- package/dist-electron/src/pages/Automation/automation-page-model.js +76 -0
- package/dist-electron/tests/contracts/agent-message-policy.test.js +32 -0
- package/dist-electron/tests/contracts/architecture-docs.test.js +30 -0
- package/dist-electron/tests/contracts/automation-contracts.test.js +154 -0
- package/dist-electron/tests/contracts/automation-renderer-model.test.js +47 -0
- package/dist-electron/tests/electron/automation-action-executor.test.js +75 -0
- package/dist-electron/tests/electron/automation-compatibility.test.js +463 -0
- package/dist-electron/tests/electron/automation-deployment.test.js +66 -0
- package/dist-electron/tests/electron/automation-engine.test.js +264 -0
- package/dist-electron/tests/electron/automation-monitor.test.js +1333 -0
- package/dist-electron/tests/electron/automation-sandbox-runner.test.js +333 -0
- package/dist-electron/tests/electron/automation-script-approval.test.js +553 -0
- package/dist-electron/tests/electron/automation-script-package.test.js +499 -0
- package/dist-electron/tests/electron/automation-script-runner.test.js +120 -0
- package/dist-electron/tests/electron/automation-service.test.js +1078 -0
- package/dist-electron/tests/electron/automation-store.test.js +593 -0
- package/dist-electron/tests/electron/condition-trigger-skill.test.js +93 -0
- package/dist-electron/tests/electron/core-client.test.js +99 -0
- package/dist-electron/tests/electron/lac-cli.test.js +110 -0
- package/dist-electron/tests/electron/workspace-task-store.test.js +25 -0
- package/dist-electron/tests/integration/automation-routes.test.js +186 -0
- package/dist-electron/tests/integration/automation-sandbox-runtime.test.js +92 -0
- package/package.json +5 -6
- package/dist/renderer/assets/CronList-BwkA_I7v.js +0 -1
- package/dist/renderer/assets/Dashboard-D0_A7k02.js +0 -41
- package/dist/renderer/assets/MonitorList-CS1uLS5A.js +0 -1
- package/dist/renderer/assets/en-CmAr_0e6.js +0 -1
- package/dist/renderer/assets/es-Cs9x52ca.js +0 -1
- package/dist/renderer/assets/index-BkI9POxj.js +0 -167
- package/dist/renderer/assets/ja-DtvtBRTW.js +0 -1
- package/dist/renderer/assets/play-DGAO4OCu.js +0 -6
- package/dist/renderer/assets/zh-CcJ9vBZj.js +0 -1
- 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
|
|
6
|
-
const
|
|
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
|
-
|
|
13
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
38
|
-
if (this.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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.
|
|
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.
|
|
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
|
|
52
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
65
|
-
const existing = this.
|
|
66
|
-
if (
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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.
|
|
97
|
-
return this.
|
|
98
|
-
.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
261
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
416
|
+
if (!this.canAdmitProviderWork(generation))
|
|
417
|
+
return false;
|
|
418
|
+
this.providerEventsInFlight += 1;
|
|
419
|
+
return true;
|
|
300
420
|
}
|
|
301
|
-
|
|
302
|
-
this.
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
if (!
|
|
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
|
-
|
|
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
|
|
441
|
+
for (const monitorId of this.subscriptionHandles.keys()) {
|
|
314
442
|
if (!activeIds.has(monitorId)) {
|
|
315
|
-
|
|
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
|
-
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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.
|
|
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.
|
|
353
|
-
|
|
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;
|