@assemblyline-agents/runtime 0.1.0
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/LICENSE +21 -0
- package/README.md +19 -0
- package/dist/agent-harness.d.ts +108 -0
- package/dist/agent-harness.d.ts.map +1 -0
- package/dist/agent-harness.js +2 -0
- package/dist/agent-harness.js.map +1 -0
- package/dist/attachments-legacy.d.ts +17 -0
- package/dist/attachments-legacy.d.ts.map +1 -0
- package/dist/attachments-legacy.js +273 -0
- package/dist/attachments-legacy.js.map +1 -0
- package/dist/attachments.d.ts +49 -0
- package/dist/attachments.d.ts.map +1 -0
- package/dist/attachments.js +832 -0
- package/dist/attachments.js.map +1 -0
- package/dist/builtin-tool-executors.d.ts +96 -0
- package/dist/builtin-tool-executors.d.ts.map +1 -0
- package/dist/builtin-tool-executors.js +501 -0
- package/dist/builtin-tool-executors.js.map +1 -0
- package/dist/builtin-tools.d.ts +30 -0
- package/dist/builtin-tools.d.ts.map +1 -0
- package/dist/builtin-tools.js +690 -0
- package/dist/builtin-tools.js.map +1 -0
- package/dist/capability-catalog.d.ts +27 -0
- package/dist/capability-catalog.d.ts.map +1 -0
- package/dist/capability-catalog.js +193 -0
- package/dist/capability-catalog.js.map +1 -0
- package/dist/channel-http-util.d.ts +34 -0
- package/dist/channel-http-util.d.ts.map +1 -0
- package/dist/channel-http-util.js +93 -0
- package/dist/channel-http-util.js.map +1 -0
- package/dist/channel-ingress.d.ts +56 -0
- package/dist/channel-ingress.d.ts.map +1 -0
- package/dist/channel-ingress.js +273 -0
- package/dist/channel-ingress.js.map +1 -0
- package/dist/channel.d.ts +146 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +2 -0
- package/dist/channel.js.map +1 -0
- package/dist/checkpoints.d.ts +34 -0
- package/dist/checkpoints.d.ts.map +1 -0
- package/dist/checkpoints.js +86 -0
- package/dist/checkpoints.js.map +1 -0
- package/dist/code-mode.d.ts +20 -0
- package/dist/code-mode.d.ts.map +1 -0
- package/dist/code-mode.js +658 -0
- package/dist/code-mode.js.map +1 -0
- package/dist/computer-relay-protocol.d.ts +36 -0
- package/dist/computer-relay-protocol.d.ts.map +1 -0
- package/dist/computer-relay-protocol.js +127 -0
- package/dist/computer-relay-protocol.js.map +1 -0
- package/dist/concurrency.d.ts +68 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +193 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/connection-auth-util.d.ts +56 -0
- package/dist/connection-auth-util.d.ts.map +1 -0
- package/dist/connection-auth-util.js +154 -0
- package/dist/connection-auth-util.js.map +1 -0
- package/dist/connection-clients.d.ts +50 -0
- package/dist/connection-clients.d.ts.map +1 -0
- package/dist/connection-clients.js +402 -0
- package/dist/connection-clients.js.map +1 -0
- package/dist/connection-stores.d.ts +108 -0
- package/dist/connection-stores.d.ts.map +1 -0
- package/dist/connection-stores.js +127 -0
- package/dist/connection-stores.js.map +1 -0
- package/dist/connection-util.d.ts +27 -0
- package/dist/connection-util.d.ts.map +1 -0
- package/dist/connection-util.js +118 -0
- package/dist/connection-util.js.map +1 -0
- package/dist/connections.d.ts +152 -0
- package/dist/connections.d.ts.map +1 -0
- package/dist/connections.js +838 -0
- package/dist/connections.js.map +1 -0
- package/dist/content-capture.d.ts +32 -0
- package/dist/content-capture.d.ts.map +1 -0
- package/dist/content-capture.js +77 -0
- package/dist/content-capture.js.map +1 -0
- package/dist/context-policy-util.d.ts +45 -0
- package/dist/context-policy-util.d.ts.map +1 -0
- package/dist/context-policy-util.js +120 -0
- package/dist/context-policy-util.js.map +1 -0
- package/dist/conversation-context.d.ts +55 -0
- package/dist/conversation-context.d.ts.map +1 -0
- package/dist/conversation-context.js +179 -0
- package/dist/conversation-context.js.map +1 -0
- package/dist/conversation-transcript.d.ts +49 -0
- package/dist/conversation-transcript.d.ts.map +1 -0
- package/dist/conversation-transcript.js +77 -0
- package/dist/conversation-transcript.js.map +1 -0
- package/dist/conversation-turn-dispatch.d.ts +46 -0
- package/dist/conversation-turn-dispatch.d.ts.map +1 -0
- package/dist/conversation-turn-dispatch.js +201 -0
- package/dist/conversation-turn-dispatch.js.map +1 -0
- package/dist/delivery-queue.d.ts +89 -0
- package/dist/delivery-queue.d.ts.map +1 -0
- package/dist/delivery-queue.js +244 -0
- package/dist/delivery-queue.js.map +1 -0
- package/dist/delivery-send.d.ts +44 -0
- package/dist/delivery-send.d.ts.map +1 -0
- package/dist/delivery-send.js +494 -0
- package/dist/delivery-send.js.map +1 -0
- package/dist/env-config.d.ts +6 -0
- package/dist/env-config.d.ts.map +1 -0
- package/dist/env-config.js +27 -0
- package/dist/env-config.js.map +1 -0
- package/dist/eval-run-options.d.ts +37 -0
- package/dist/eval-run-options.d.ts.map +1 -0
- package/dist/eval-run-options.js +106 -0
- package/dist/eval-run-options.js.map +1 -0
- package/dist/harness-continuation.d.ts +54 -0
- package/dist/harness-continuation.d.ts.map +1 -0
- package/dist/harness-continuation.js +58 -0
- package/dist/harness-continuation.js.map +1 -0
- package/dist/harness-loop.d.ts +110 -0
- package/dist/harness-loop.d.ts.map +1 -0
- package/dist/harness-loop.js +575 -0
- package/dist/harness-loop.js.map +1 -0
- package/dist/hook-dispatch.d.ts +19 -0
- package/dist/hook-dispatch.d.ts.map +1 -0
- package/dist/hook-dispatch.js +37 -0
- package/dist/hook-dispatch.js.map +1 -0
- package/dist/http.d.ts +26 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +114 -0
- package/dist/http.js.map +1 -0
- package/dist/in-memory-conversation-turn-store.d.ts +29 -0
- package/dist/in-memory-conversation-turn-store.d.ts.map +1 -0
- package/dist/in-memory-conversation-turn-store.js +166 -0
- package/dist/in-memory-conversation-turn-store.js.map +1 -0
- package/dist/in-memory-run-store.d.ts +39 -0
- package/dist/in-memory-run-store.d.ts.map +1 -0
- package/dist/in-memory-run-store.js +332 -0
- package/dist/in-memory-run-store.js.map +1 -0
- package/dist/in-memory-sandbox-session-store.d.ts +33 -0
- package/dist/in-memory-sandbox-session-store.d.ts.map +1 -0
- package/dist/in-memory-sandbox-session-store.js +186 -0
- package/dist/in-memory-sandbox-session-store.js.map +1 -0
- package/dist/in-memory-small-stores.d.ts +117 -0
- package/dist/in-memory-small-stores.d.ts.map +1 -0
- package/dist/in-memory-small-stores.js +340 -0
- package/dist/in-memory-small-stores.js.map +1 -0
- package/dist/in-memory-usage-store.d.ts +15 -0
- package/dist/in-memory-usage-store.d.ts.map +1 -0
- package/dist/in-memory-usage-store.js +49 -0
- package/dist/in-memory-usage-store.js.map +1 -0
- package/dist/index.d.ts +467 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2046 -0
- package/dist/index.js.map +1 -0
- package/dist/json-util.d.ts +19 -0
- package/dist/json-util.d.ts.map +1 -0
- package/dist/json-util.js +59 -0
- package/dist/json-util.js.map +1 -0
- package/dist/local-sandbox.d.ts +16 -0
- package/dist/local-sandbox.d.ts.map +1 -0
- package/dist/local-sandbox.js +327 -0
- package/dist/local-sandbox.js.map +1 -0
- package/dist/logging.d.ts +16 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +41 -0
- package/dist/logging.js.map +1 -0
- package/dist/mcp-client.d.ts +27 -0
- package/dist/mcp-client.d.ts.map +1 -0
- package/dist/mcp-client.js +169 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/model-harnesses.d.ts +4 -0
- package/dist/model-harnesses.d.ts.map +1 -0
- package/dist/model-harnesses.js +6 -0
- package/dist/model-harnesses.js.map +1 -0
- package/dist/model-media.d.ts +36 -0
- package/dist/model-media.d.ts.map +1 -0
- package/dist/model-media.js +234 -0
- package/dist/model-media.js.map +1 -0
- package/dist/operator-controls.d.ts +31 -0
- package/dist/operator-controls.d.ts.map +1 -0
- package/dist/operator-controls.js +71 -0
- package/dist/operator-controls.js.map +1 -0
- package/dist/pi-agent-harness.d.ts +40 -0
- package/dist/pi-agent-harness.d.ts.map +1 -0
- package/dist/pi-agent-harness.js +222 -0
- package/dist/pi-agent-harness.js.map +1 -0
- package/dist/pi-harness.d.ts +42 -0
- package/dist/pi-harness.d.ts.map +1 -0
- package/dist/pi-harness.js +142 -0
- package/dist/pi-harness.js.map +1 -0
- package/dist/relay-mcp-client.d.ts +17 -0
- package/dist/relay-mcp-client.d.ts.map +1 -0
- package/dist/relay-mcp-client.js +119 -0
- package/dist/relay-mcp-client.js.map +1 -0
- package/dist/replay-resume.d.ts +107 -0
- package/dist/replay-resume.d.ts.map +1 -0
- package/dist/replay-resume.js +495 -0
- package/dist/replay-resume.js.map +1 -0
- package/dist/resource-projection-util.d.ts +58 -0
- package/dist/resource-projection-util.d.ts.map +1 -0
- package/dist/resource-projection-util.js +298 -0
- package/dist/resource-projection-util.js.map +1 -0
- package/dist/resource-projection.d.ts +45 -0
- package/dist/resource-projection.d.ts.map +1 -0
- package/dist/resource-projection.js +481 -0
- package/dist/resource-projection.js.map +1 -0
- package/dist/run-heartbeat.d.ts +34 -0
- package/dist/run-heartbeat.d.ts.map +1 -0
- package/dist/run-heartbeat.js +62 -0
- package/dist/run-heartbeat.js.map +1 -0
- package/dist/run-outcome.d.ts +47 -0
- package/dist/run-outcome.d.ts.map +1 -0
- package/dist/run-outcome.js +92 -0
- package/dist/run-outcome.js.map +1 -0
- package/dist/run-recovery.d.ts +117 -0
- package/dist/run-recovery.d.ts.map +1 -0
- package/dist/run-recovery.js +466 -0
- package/dist/run-recovery.js.map +1 -0
- package/dist/run-resolver.d.ts +35 -0
- package/dist/run-resolver.d.ts.map +1 -0
- package/dist/run-resolver.js +84 -0
- package/dist/run-resolver.js.map +1 -0
- package/dist/run-stream.d.ts +37 -0
- package/dist/run-stream.d.ts.map +1 -0
- package/dist/run-stream.js +78 -0
- package/dist/run-stream.js.map +1 -0
- package/dist/runs-query.d.ts +29 -0
- package/dist/runs-query.d.ts.map +1 -0
- package/dist/runs-query.js +142 -0
- package/dist/runs-query.js.map +1 -0
- package/dist/runtime-bundle.d.ts +3 -0
- package/dist/runtime-bundle.d.ts.map +1 -0
- package/dist/runtime-bundle.js +18 -0
- package/dist/runtime-bundle.js.map +1 -0
- package/dist/runtime-types.d.ts +397 -0
- package/dist/runtime-types.d.ts.map +1 -0
- package/dist/runtime-types.js +2 -0
- package/dist/runtime-types.js.map +1 -0
- package/dist/runtime-util.d.ts +45 -0
- package/dist/runtime-util.d.ts.map +1 -0
- package/dist/runtime-util.js +116 -0
- package/dist/runtime-util.js.map +1 -0
- package/dist/sandbox-cli-client.d.ts +20 -0
- package/dist/sandbox-cli-client.d.ts.map +1 -0
- package/dist/sandbox-cli-client.js +97 -0
- package/dist/sandbox-cli-client.js.map +1 -0
- package/dist/sandbox-fs.d.ts +56 -0
- package/dist/sandbox-fs.d.ts.map +1 -0
- package/dist/sandbox-fs.js +276 -0
- package/dist/sandbox-fs.js.map +1 -0
- package/dist/sandbox-session-key.d.ts +8 -0
- package/dist/sandbox-session-key.d.ts.map +1 -0
- package/dist/sandbox-session-key.js +32 -0
- package/dist/sandbox-session-key.js.map +1 -0
- package/dist/sandbox-snapshot-policy.d.ts +27 -0
- package/dist/sandbox-snapshot-policy.d.ts.map +1 -0
- package/dist/sandbox-snapshot-policy.js +94 -0
- package/dist/sandbox-snapshot-policy.js.map +1 -0
- package/dist/sandbox-sync-worker.d.ts +63 -0
- package/dist/sandbox-sync-worker.d.ts.map +1 -0
- package/dist/sandbox-sync-worker.js +448 -0
- package/dist/sandbox-sync-worker.js.map +1 -0
- package/dist/schedule-execution.d.ts +59 -0
- package/dist/schedule-execution.d.ts.map +1 -0
- package/dist/schedule-execution.js +951 -0
- package/dist/schedule-execution.js.map +1 -0
- package/dist/scheduler.d.ts +19 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +38 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/security.d.ts +20 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +145 -0
- package/dist/security.js.map +1 -0
- package/dist/self-improvement.d.ts +245 -0
- package/dist/self-improvement.d.ts.map +1 -0
- package/dist/self-improvement.js +554 -0
- package/dist/self-improvement.js.map +1 -0
- package/dist/source-loader.d.ts +19 -0
- package/dist/source-loader.d.ts.map +1 -0
- package/dist/source-loader.js +160 -0
- package/dist/source-loader.js.map +1 -0
- package/dist/state-store-helpers.d.ts +28 -0
- package/dist/state-store-helpers.d.ts.map +1 -0
- package/dist/state-store-helpers.js +164 -0
- package/dist/state-store-helpers.js.map +1 -0
- package/dist/state-stores.d.ts +25 -0
- package/dist/state-stores.d.ts.map +1 -0
- package/dist/state-stores.js +157 -0
- package/dist/state-stores.js.map +1 -0
- package/dist/state.d.ts +736 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +46 -0
- package/dist/state.js.map +1 -0
- package/dist/storage.d.ts +188 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +752 -0
- package/dist/storage.js.map +1 -0
- package/dist/subagent-execution.d.ts +80 -0
- package/dist/subagent-execution.d.ts.map +1 -0
- package/dist/subagent-execution.js +135 -0
- package/dist/subagent-execution.js.map +1 -0
- package/dist/telemetry.d.ts +49 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +60 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/tool-apis.d.ts +22 -0
- package/dist/tool-apis.d.ts.map +1 -0
- package/dist/tool-apis.js +344 -0
- package/dist/tool-apis.js.map +1 -0
- package/dist/tool-execution.d.ts +143 -0
- package/dist/tool-execution.d.ts.map +1 -0
- package/dist/tool-execution.js +573 -0
- package/dist/tool-execution.js.map +1 -0
- package/dist/trusted-attachments.d.ts +20 -0
- package/dist/trusted-attachments.d.ts.map +1 -0
- package/dist/trusted-attachments.js +276 -0
- package/dist/trusted-attachments.js.map +1 -0
- package/dist/usage-accounting.d.ts +37 -0
- package/dist/usage-accounting.d.ts.map +1 -0
- package/dist/usage-accounting.js +181 -0
- package/dist/usage-accounting.js.map +1 -0
- package/dist/usage-budget.d.ts +29 -0
- package/dist/usage-budget.d.ts.map +1 -0
- package/dist/usage-budget.js +145 -0
- package/dist/usage-budget.js.map +1 -0
- package/dist/usage-reconciliation.d.ts +76 -0
- package/dist/usage-reconciliation.d.ts.map +1 -0
- package/dist/usage-reconciliation.js +463 -0
- package/dist/usage-reconciliation.js.map +1 -0
- package/dist/usage-records.d.ts +24 -0
- package/dist/usage-records.d.ts.map +1 -0
- package/dist/usage-records.js +445 -0
- package/dist/usage-records.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,951 @@
|
|
|
1
|
+
import { isTriggerHandlerDefinition, normalizeRoutineRunRecord, normalizeTriggerPrepareResult, triggerPrepareContext } from "./channel-http-util.js";
|
|
2
|
+
import { RunCapacityError } from "./concurrency.js";
|
|
3
|
+
import { dueTimeForSchedule, dynamicScheduleMessage, stringValue, timestamp } from "./runtime-util.js";
|
|
4
|
+
import { buildDefaultContextBundle } from "./sandbox-fs.js";
|
|
5
|
+
import { isJsonObject, safeJson } from "./json-util.js";
|
|
6
|
+
import { failRun } from "./run-outcome.js";
|
|
7
|
+
import { isRuntimeSettingsStore } from "./state.js";
|
|
8
|
+
import { memoryBodyText, memoryDocumentSummary, positiveLimit } from "./storage.js";
|
|
9
|
+
export async function registerManifestSchedulesImpl(host) {
|
|
10
|
+
const records = [];
|
|
11
|
+
for (const schedule of manifestScheduleRegistrations(host)) {
|
|
12
|
+
records.push(await host.state.registerSchedule({
|
|
13
|
+
id: schedule.schedule,
|
|
14
|
+
name: `schedule:${schedule.schedule}`,
|
|
15
|
+
cron: schedule.cron,
|
|
16
|
+
...(schedule.timezone ? { timezone: schedule.timezone } : {}),
|
|
17
|
+
metadata: {
|
|
18
|
+
agentRevision: host.manifest.agentRevision,
|
|
19
|
+
message: schedule.message ?? compiledSchedule(host, schedule.schedule)?.message ?? null,
|
|
20
|
+
trigger: schedule.trigger ? safeJson(schedule.trigger) : null
|
|
21
|
+
}
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
return records;
|
|
25
|
+
}
|
|
26
|
+
export async function runDueSchedulesImpl(host, options = {}) {
|
|
27
|
+
host.boot();
|
|
28
|
+
if (!(await host.agentEnabled()))
|
|
29
|
+
return [];
|
|
30
|
+
await registerManifestSchedulesImpl(host);
|
|
31
|
+
const now = options.now ?? host.now();
|
|
32
|
+
const lookbackMs = options.lookbackMs ?? host.schedulerTickMs;
|
|
33
|
+
const results = [];
|
|
34
|
+
for (const schedule of manifestScheduleRegistrations(host)) {
|
|
35
|
+
const dueAt = dueTimeForSchedule(schedule, now, lookbackMs);
|
|
36
|
+
if (!dueAt)
|
|
37
|
+
continue;
|
|
38
|
+
const dueAtIso = dueAt.toISOString();
|
|
39
|
+
const idempotencyKey = `${schedule.idempotencyKey}:${dueAtIso}`;
|
|
40
|
+
// Message schedules run through the bounded semaphore; claim capacity
|
|
41
|
+
// BEFORE reserving the idempotency key so a saturated runtime skips the
|
|
42
|
+
// tick without burning the key (the next sweep retries). Triggered
|
|
43
|
+
// schedules use an existingRun and bypass admission (enter()).
|
|
44
|
+
let admission;
|
|
45
|
+
if (!schedule.trigger) {
|
|
46
|
+
const admitted = host.admitRunOrReject();
|
|
47
|
+
if (admitted instanceof RunCapacityError) {
|
|
48
|
+
results.push({ schedule: schedule.schedule, dueAt: dueAtIso, idempotencyKey, status: "skipped", error: "run capacity saturated" });
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
admission = admitted;
|
|
52
|
+
}
|
|
53
|
+
const reserved = await host.state.reserveIdempotencyKey("schedule", idempotencyKey, host.manifest.agentRevision).catch((error) => {
|
|
54
|
+
admission?.release();
|
|
55
|
+
throw error;
|
|
56
|
+
});
|
|
57
|
+
if (!reserved) {
|
|
58
|
+
admission?.release();
|
|
59
|
+
results.push({ schedule: schedule.schedule, dueAt: dueAtIso, idempotencyKey, status: "skipped" });
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
// Durable claim evidence, written immediately after the key burn: a crash
|
|
63
|
+
// before the run exists leaves a "preparing" record instead of a silently
|
|
64
|
+
// lost occurrence. (The residual window is the single adjacent statement.)
|
|
65
|
+
await host.state.recordScheduleRun({
|
|
66
|
+
scheduleId: schedule.schedule,
|
|
67
|
+
runAt: dueAtIso,
|
|
68
|
+
idempotencyKey,
|
|
69
|
+
status: "preparing"
|
|
70
|
+
}).catch((error) => {
|
|
71
|
+
admission?.release();
|
|
72
|
+
throw error;
|
|
73
|
+
});
|
|
74
|
+
const job = manifestScheduleExecution(host, schedule, dueAtIso, idempotencyKey);
|
|
75
|
+
try {
|
|
76
|
+
results.push(await runScheduleExecution(host, job, admission));
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
80
|
+
await host.state.recordScheduleRun({
|
|
81
|
+
scheduleId: schedule.schedule,
|
|
82
|
+
runAt: dueAtIso,
|
|
83
|
+
idempotencyKey,
|
|
84
|
+
status: "failed"
|
|
85
|
+
});
|
|
86
|
+
results.push({ schedule: schedule.schedule, dueAt: dueAtIso, idempotencyKey, status: "failed", error: message });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
results.push(...(await runDueDynamicSchedules(host, now)));
|
|
90
|
+
return results;
|
|
91
|
+
}
|
|
92
|
+
export async function dispatchAutomationEventImpl(host, event) {
|
|
93
|
+
host.boot();
|
|
94
|
+
if (!(await host.agentEnabled()))
|
|
95
|
+
return [];
|
|
96
|
+
if (!event.source.trim() || !event.event.trim() || !event.eventId.trim()) {
|
|
97
|
+
throw new Error("Automation events require non-empty source, event, and eventId values.");
|
|
98
|
+
}
|
|
99
|
+
if (event.occurredAt && Number.isNaN(new Date(event.occurredAt).getTime())) {
|
|
100
|
+
throw new Error("Automation event occurredAt must be a valid ISO timestamp.");
|
|
101
|
+
}
|
|
102
|
+
const results = [];
|
|
103
|
+
for (const automation of host.manifest.automationRegistrations ?? []) {
|
|
104
|
+
if (automation.trigger.type !== "event")
|
|
105
|
+
continue;
|
|
106
|
+
if (automation.trigger.source !== event.source || automation.trigger.event !== event.event)
|
|
107
|
+
continue;
|
|
108
|
+
if (automation.trigger.filter && !jsonSubsetMatches(automation.trigger.filter, event.payload))
|
|
109
|
+
continue;
|
|
110
|
+
const idempotencyKey = `${automation.idempotencyKey}:${event.eventId}`;
|
|
111
|
+
let admission;
|
|
112
|
+
if (!automation.lifecycle) {
|
|
113
|
+
const admitted = host.admitRunOrReject();
|
|
114
|
+
if (admitted instanceof RunCapacityError) {
|
|
115
|
+
results.push({
|
|
116
|
+
automation: automation.automation,
|
|
117
|
+
eventId: event.eventId,
|
|
118
|
+
idempotencyKey,
|
|
119
|
+
status: "skipped",
|
|
120
|
+
error: "run capacity saturated"
|
|
121
|
+
});
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
admission = admitted;
|
|
125
|
+
}
|
|
126
|
+
const reserved = await host.state.reserveIdempotencyKey(`automation:${automation.automation}:event`, idempotencyKey, host.manifest.agentRevision).catch((error) => {
|
|
127
|
+
admission?.release();
|
|
128
|
+
throw error;
|
|
129
|
+
});
|
|
130
|
+
if (!reserved) {
|
|
131
|
+
admission?.release();
|
|
132
|
+
results.push({
|
|
133
|
+
automation: automation.automation,
|
|
134
|
+
eventId: event.eventId,
|
|
135
|
+
idempotencyKey,
|
|
136
|
+
status: "skipped"
|
|
137
|
+
});
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const execution = eventAutomationExecution(host, automation, event, idempotencyKey);
|
|
141
|
+
try {
|
|
142
|
+
const result = await runScheduleExecution(host, execution, admission);
|
|
143
|
+
results.push({
|
|
144
|
+
automation: automation.automation,
|
|
145
|
+
eventId: event.eventId,
|
|
146
|
+
idempotencyKey,
|
|
147
|
+
status: result.status,
|
|
148
|
+
...(result.runId ? { runId: result.runId } : {}),
|
|
149
|
+
...(result.error ? { error: result.error } : {})
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
results.push({
|
|
154
|
+
automation: automation.automation,
|
|
155
|
+
eventId: event.eventId,
|
|
156
|
+
idempotencyKey,
|
|
157
|
+
status: "failed",
|
|
158
|
+
error: error instanceof Error ? error.message : String(error)
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return results;
|
|
163
|
+
}
|
|
164
|
+
function eventAutomationExecution(host, automation, event, idempotencyKey) {
|
|
165
|
+
const eventAt = event.occurredAt ?? timestamp();
|
|
166
|
+
const compiled = host.manifest.automations?.find((candidate) => candidate.name === automation.automation);
|
|
167
|
+
const trigger = automation.lifecycle
|
|
168
|
+
? {
|
|
169
|
+
handler: automation.lifecycle.handler,
|
|
170
|
+
...(automation.target ? { target: automation.target } : {}),
|
|
171
|
+
...(automation.metadata ? { metadata: automation.metadata } : {})
|
|
172
|
+
}
|
|
173
|
+
: undefined;
|
|
174
|
+
return {
|
|
175
|
+
source: "manifest",
|
|
176
|
+
scheduleId: automation.automation,
|
|
177
|
+
resultSchedule: automation.automation,
|
|
178
|
+
dueAtIso: eventAt,
|
|
179
|
+
idempotencyKey,
|
|
180
|
+
message: automation.message ?? `Handle ${event.event} from ${event.source}.`,
|
|
181
|
+
channel: "automation",
|
|
182
|
+
...(trigger ? { trigger } : {}),
|
|
183
|
+
...(automation.target ? { target: automation.target } : {}),
|
|
184
|
+
automationEvent: event,
|
|
185
|
+
metadata: {
|
|
186
|
+
automation: automation.automation,
|
|
187
|
+
event: safeJson(event),
|
|
188
|
+
...(automation.metadata ?? {})
|
|
189
|
+
},
|
|
190
|
+
definition: safeJson(compiled ?? automation),
|
|
191
|
+
...(event.userId ? { userId: event.userId } : {}),
|
|
192
|
+
...(event.conversationId ? { conversationId: event.conversationId } : {}),
|
|
193
|
+
...(event.projectId ? { projectId: event.projectId } : {}),
|
|
194
|
+
...(event.delivery ? { delivery: event.delivery } : {}),
|
|
195
|
+
turnMetadata: {
|
|
196
|
+
automation: automation.automation,
|
|
197
|
+
source: event.source,
|
|
198
|
+
event: event.event,
|
|
199
|
+
payload: event.payload,
|
|
200
|
+
...(event.metadata ?? {})
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function jsonSubsetMatches(expected, actual) {
|
|
205
|
+
return Object.entries(expected).every(([key, expectedValue]) => {
|
|
206
|
+
if (!Object.prototype.hasOwnProperty.call(actual, key))
|
|
207
|
+
return false;
|
|
208
|
+
const actualValue = actual[key];
|
|
209
|
+
if (isJsonObject(expectedValue)) {
|
|
210
|
+
return isJsonObject(actualValue) && jsonSubsetMatches(expectedValue, actualValue);
|
|
211
|
+
}
|
|
212
|
+
return JSON.stringify(expectedValue) === JSON.stringify(actualValue);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
function manifestScheduleExecution(host, schedule, dueAtIso, idempotencyKey) {
|
|
216
|
+
return {
|
|
217
|
+
source: "manifest",
|
|
218
|
+
scheduleId: schedule.schedule,
|
|
219
|
+
resultSchedule: schedule.schedule,
|
|
220
|
+
dueAtIso,
|
|
221
|
+
idempotencyKey,
|
|
222
|
+
message: scheduleMessage(host, schedule.schedule),
|
|
223
|
+
channel: "schedule",
|
|
224
|
+
...(schedule.trigger ? { trigger: schedule.trigger } : {}),
|
|
225
|
+
...(schedule.target ? { target: schedule.target } : {}),
|
|
226
|
+
metadata: {
|
|
227
|
+
schedule: schedule.schedule,
|
|
228
|
+
dueAt: dueAtIso,
|
|
229
|
+
idempotencyKey
|
|
230
|
+
},
|
|
231
|
+
definition: safeJson(compiledSchedule(host, schedule.schedule) ?? schedule)
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
async function runScheduleExecution(host, schedule, admission) {
|
|
235
|
+
return schedule.trigger ? runTriggeredSchedule(host, schedule) : runMessageSchedule(host, schedule, admission);
|
|
236
|
+
}
|
|
237
|
+
async function runMessageSchedule(host, schedule, admission) {
|
|
238
|
+
const overrides = admission ? { admission } : {};
|
|
239
|
+
if (schedule.target)
|
|
240
|
+
overrides.target = schedule.target;
|
|
241
|
+
const result = await host.run(scheduleRunOptions(host, schedule, schedule.message, schedule.metadata, overrides));
|
|
242
|
+
await host.state.recordScheduleRun({
|
|
243
|
+
scheduleId: schedule.resultSchedule,
|
|
244
|
+
runId: result.run.id,
|
|
245
|
+
runAt: schedule.dueAtIso,
|
|
246
|
+
idempotencyKey: schedule.idempotencyKey,
|
|
247
|
+
status: result.run.status
|
|
248
|
+
});
|
|
249
|
+
return {
|
|
250
|
+
schedule: schedule.resultSchedule,
|
|
251
|
+
dueAt: schedule.dueAtIso,
|
|
252
|
+
idempotencyKey: schedule.idempotencyKey,
|
|
253
|
+
status: result.run.status === "failed" ? "failed" : "completed",
|
|
254
|
+
runId: result.run.id
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function scheduleRunOptions(host, schedule, message, metadata, overrides = {}) {
|
|
258
|
+
const options = {
|
|
259
|
+
message,
|
|
260
|
+
channel: schedule.channel,
|
|
261
|
+
eventId: schedule.idempotencyKey,
|
|
262
|
+
metadata,
|
|
263
|
+
...overrides
|
|
264
|
+
};
|
|
265
|
+
if (schedule.userId)
|
|
266
|
+
options.userId = schedule.userId;
|
|
267
|
+
if (schedule.conversationId)
|
|
268
|
+
options.conversationId = schedule.conversationId;
|
|
269
|
+
if (schedule.projectId)
|
|
270
|
+
options.projectId = schedule.projectId;
|
|
271
|
+
if (schedule.delivery)
|
|
272
|
+
options.delivery = schedule.delivery;
|
|
273
|
+
if (schedule.conversationId) {
|
|
274
|
+
options.turn = {
|
|
275
|
+
eventId: schedule.idempotencyKey,
|
|
276
|
+
channel: schedule.channel,
|
|
277
|
+
conversationId: schedule.conversationId,
|
|
278
|
+
message,
|
|
279
|
+
...(schedule.userId ? { userId: schedule.userId } : {}),
|
|
280
|
+
...(schedule.delivery ? { delivery: schedule.delivery } : {}),
|
|
281
|
+
...(schedule.turnMetadata ? { metadata: schedule.turnMetadata } : {})
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
return options;
|
|
285
|
+
}
|
|
286
|
+
async function runTriggeredSchedule(host, schedule) {
|
|
287
|
+
const trigger = schedule.trigger;
|
|
288
|
+
if (!trigger)
|
|
289
|
+
throw new Error(`Schedule ${schedule.resultSchedule} has no trigger definition.`);
|
|
290
|
+
const handler = automationHandlers(host).find((candidate) => candidate.name === trigger.handler);
|
|
291
|
+
if (!handler)
|
|
292
|
+
throw new Error(`Trigger handler not found: ${trigger.handler}`);
|
|
293
|
+
const invocation = {
|
|
294
|
+
kind: schedule.automationEvent ? "event" : "schedule",
|
|
295
|
+
source: schedule.source,
|
|
296
|
+
...(schedule.automationEvent
|
|
297
|
+
? { automation: schedule.resultSchedule, event: schedule.automationEvent }
|
|
298
|
+
: { schedule: schedule.resultSchedule, dueAt: schedule.dueAtIso }),
|
|
299
|
+
handler: handler.name,
|
|
300
|
+
idempotencyKey: schedule.idempotencyKey
|
|
301
|
+
};
|
|
302
|
+
if (trigger.metadata)
|
|
303
|
+
invocation.metadata = trigger.metadata;
|
|
304
|
+
let run = await host.state.createRun({
|
|
305
|
+
agentRevision: host.manifest.agentRevision,
|
|
306
|
+
status: "created",
|
|
307
|
+
input: {
|
|
308
|
+
message: schedule.message,
|
|
309
|
+
model: host.manifest.agent.model,
|
|
310
|
+
channel: schedule.channel,
|
|
311
|
+
eventId: schedule.idempotencyKey,
|
|
312
|
+
conversationId: schedule.conversationId ?? null,
|
|
313
|
+
userId: schedule.userId ?? null,
|
|
314
|
+
projectId: schedule.projectId ?? null,
|
|
315
|
+
delivery: schedule.delivery ?? null,
|
|
316
|
+
metadata: {
|
|
317
|
+
...schedule.metadata,
|
|
318
|
+
trigger: safeJson(invocation)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
await host.state.recordScheduleRun({
|
|
323
|
+
scheduleId: schedule.resultSchedule,
|
|
324
|
+
runId: run.id,
|
|
325
|
+
runAt: schedule.dueAtIso,
|
|
326
|
+
idempotencyKey: schedule.idempotencyKey,
|
|
327
|
+
status: "preparing"
|
|
328
|
+
});
|
|
329
|
+
const definition = await loadTriggerHandler(host, handler);
|
|
330
|
+
const prepareStartedAt = timestamp();
|
|
331
|
+
let prepareResult = {};
|
|
332
|
+
let prepareContext = { runId: run.id };
|
|
333
|
+
try {
|
|
334
|
+
await host.state.appendEvent(run.id, "trigger.prepare_started", {
|
|
335
|
+
handler: handler.name,
|
|
336
|
+
schedule: schedule.resultSchedule,
|
|
337
|
+
source: schedule.source,
|
|
338
|
+
dueAt: schedule.dueAtIso,
|
|
339
|
+
idempotencyKey: schedule.idempotencyKey
|
|
340
|
+
});
|
|
341
|
+
const prepared = await definition.prepare?.(createTriggerHandlerContext(host, run, schedule, invocation, {}));
|
|
342
|
+
prepareResult = normalizeTriggerPrepareResult(prepared);
|
|
343
|
+
prepareContext = triggerPrepareContext(run.id, prepareResult);
|
|
344
|
+
await host.state.appendEvent(run.id, "trigger.prepare_completed", {
|
|
345
|
+
handler: handler.name,
|
|
346
|
+
target: safeJson(prepareResult.target ?? trigger.target ?? null),
|
|
347
|
+
promptContext: safeJson(prepareResult.promptContext ?? {})
|
|
348
|
+
});
|
|
349
|
+
await host.emitSpan("ai.assembly-line.trigger.prepare", prepareStartedAt, host.runSpanAttributes(run, {
|
|
350
|
+
triggerHandler: handler.name,
|
|
351
|
+
schedule: schedule.resultSchedule,
|
|
352
|
+
scheduleSource: schedule.source,
|
|
353
|
+
status: "completed"
|
|
354
|
+
}), {
|
|
355
|
+
parentSpanId: host.turnSpanId(run.id),
|
|
356
|
+
status: "ok"
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
361
|
+
await host.state.appendEvent(run.id, "trigger.prepare_failed", { handler: handler.name, error: message });
|
|
362
|
+
run = await failRun({ state: host.state }, run.id, { reason: "trigger.prepare_failed", error: message });
|
|
363
|
+
await host.state.recordScheduleRun({
|
|
364
|
+
scheduleId: schedule.resultSchedule,
|
|
365
|
+
runId: run.id,
|
|
366
|
+
runAt: schedule.dueAtIso,
|
|
367
|
+
idempotencyKey: schedule.idempotencyKey,
|
|
368
|
+
status: "failed"
|
|
369
|
+
});
|
|
370
|
+
await host.emitSpan("ai.assembly-line.trigger.prepare", prepareStartedAt, host.runSpanAttributes(run, {
|
|
371
|
+
triggerHandler: handler.name,
|
|
372
|
+
schedule: schedule.resultSchedule,
|
|
373
|
+
scheduleSource: schedule.source,
|
|
374
|
+
status: "failed",
|
|
375
|
+
error: message
|
|
376
|
+
}), {
|
|
377
|
+
parentSpanId: host.turnSpanId(run.id),
|
|
378
|
+
status: "error"
|
|
379
|
+
});
|
|
380
|
+
return { schedule: schedule.resultSchedule, dueAt: schedule.dueAtIso, idempotencyKey: schedule.idempotencyKey, status: "failed", runId: run.id, error: message };
|
|
381
|
+
}
|
|
382
|
+
const target = prepareResult.target ?? trigger.target ?? schedule.target;
|
|
383
|
+
const promptContext = {
|
|
384
|
+
triggerKind: invocation.kind,
|
|
385
|
+
triggerSource: invocation.source ?? "manifest",
|
|
386
|
+
triggerKey: invocation.idempotencyKey,
|
|
387
|
+
...(schedule.automationEvent
|
|
388
|
+
? {
|
|
389
|
+
automation: schedule.resultSchedule,
|
|
390
|
+
event: safeJson(schedule.automationEvent)
|
|
391
|
+
}
|
|
392
|
+
: {
|
|
393
|
+
schedule: schedule.resultSchedule,
|
|
394
|
+
dueAt: schedule.dueAtIso
|
|
395
|
+
}),
|
|
396
|
+
runId: run.id,
|
|
397
|
+
...(prepareResult.promptContext ?? {})
|
|
398
|
+
};
|
|
399
|
+
const metadata = {
|
|
400
|
+
...schedule.metadata,
|
|
401
|
+
trigger: safeJson(invocation),
|
|
402
|
+
promptContext,
|
|
403
|
+
...(prepareResult.metadata ?? {})
|
|
404
|
+
};
|
|
405
|
+
const message = prepareResult.message ?? prepareResult.prompt ?? schedule.message ?? `Run scheduled trigger ${handler.name}.`;
|
|
406
|
+
let runResult;
|
|
407
|
+
let finalizeResult;
|
|
408
|
+
try {
|
|
409
|
+
await host.state.recordScheduleRun({
|
|
410
|
+
scheduleId: schedule.resultSchedule,
|
|
411
|
+
runId: run.id,
|
|
412
|
+
runAt: schedule.dueAtIso,
|
|
413
|
+
idempotencyKey: schedule.idempotencyKey,
|
|
414
|
+
status: "running"
|
|
415
|
+
});
|
|
416
|
+
const runOptions = scheduleRunOptions(host, schedule, message, metadata, {
|
|
417
|
+
existingRun: run,
|
|
418
|
+
promptContext
|
|
419
|
+
});
|
|
420
|
+
if (target)
|
|
421
|
+
runOptions.target = target;
|
|
422
|
+
runResult = await host.run(runOptions);
|
|
423
|
+
run = runResult.run;
|
|
424
|
+
const failureMessage = run.status === "failed" ? await host.latestRunFailureMessage(run.id) : undefined;
|
|
425
|
+
finalizeResult = {
|
|
426
|
+
ok: run.status !== "failed",
|
|
427
|
+
runId: run.id,
|
|
428
|
+
status: run.status,
|
|
429
|
+
summary: runResult.response ?? "",
|
|
430
|
+
response: runResult.response ?? "",
|
|
431
|
+
metadata: {
|
|
432
|
+
eventCount: runResult.events.length,
|
|
433
|
+
toolCallCount: runResult.toolCalls.length
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
if (failureMessage)
|
|
437
|
+
finalizeResult.error = failureMessage;
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
const messageText = error instanceof Error ? error.message : String(error);
|
|
441
|
+
await host.state.appendEvent(run.id, "trigger.model_failed", { handler: handler.name, error: messageText });
|
|
442
|
+
run = await failRun({ state: host.state }, run.id, { reason: "trigger.model_failed", error: messageText });
|
|
443
|
+
finalizeResult = {
|
|
444
|
+
ok: false,
|
|
445
|
+
runId: run.id,
|
|
446
|
+
status: "failed",
|
|
447
|
+
summary: messageText,
|
|
448
|
+
error: messageText
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
const finalizeStartedAt = timestamp();
|
|
452
|
+
try {
|
|
453
|
+
await host.state.appendEvent(run.id, "trigger.finalize_started", {
|
|
454
|
+
handler: handler.name,
|
|
455
|
+
ok: finalizeResult.ok,
|
|
456
|
+
status: finalizeResult.status ?? null
|
|
457
|
+
});
|
|
458
|
+
await definition.finalize?.(createTriggerHandlerContext(host, run, schedule, invocation, prepareContext), finalizeResult);
|
|
459
|
+
await host.state.appendEvent(run.id, "trigger.finalize_completed", {
|
|
460
|
+
handler: handler.name,
|
|
461
|
+
ok: finalizeResult.ok,
|
|
462
|
+
status: finalizeResult.status ?? null
|
|
463
|
+
});
|
|
464
|
+
await host.emitSpan("ai.assembly-line.trigger.finalize", finalizeStartedAt, host.runSpanAttributes(run, {
|
|
465
|
+
triggerHandler: handler.name,
|
|
466
|
+
schedule: schedule.resultSchedule,
|
|
467
|
+
scheduleSource: schedule.source,
|
|
468
|
+
status: "completed",
|
|
469
|
+
resultOk: finalizeResult.ok
|
|
470
|
+
}), {
|
|
471
|
+
parentSpanId: host.turnSpanId(run.id),
|
|
472
|
+
status: "ok"
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
const messageText = error instanceof Error ? error.message : String(error);
|
|
477
|
+
await host.state.appendEvent(run.id, "trigger.finalize_failed", { handler: handler.name, error: messageText });
|
|
478
|
+
run = await failRun({ state: host.state }, run.id, { reason: "trigger.finalize_failed", error: messageText });
|
|
479
|
+
await host.emitSpan("ai.assembly-line.trigger.finalize", finalizeStartedAt, host.runSpanAttributes(run, {
|
|
480
|
+
triggerHandler: handler.name,
|
|
481
|
+
schedule: schedule.resultSchedule,
|
|
482
|
+
scheduleSource: schedule.source,
|
|
483
|
+
status: "failed",
|
|
484
|
+
error: messageText
|
|
485
|
+
}), {
|
|
486
|
+
parentSpanId: host.turnSpanId(run.id),
|
|
487
|
+
status: "error"
|
|
488
|
+
});
|
|
489
|
+
finalizeResult = {
|
|
490
|
+
...finalizeResult,
|
|
491
|
+
ok: false,
|
|
492
|
+
status: "failed",
|
|
493
|
+
error: messageText,
|
|
494
|
+
summary: messageText
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
const finalRun = await host.state.getRun(run.id) ?? run;
|
|
498
|
+
await host.state.recordScheduleRun({
|
|
499
|
+
scheduleId: schedule.resultSchedule,
|
|
500
|
+
runId: finalRun.id,
|
|
501
|
+
runAt: schedule.dueAtIso,
|
|
502
|
+
idempotencyKey: schedule.idempotencyKey,
|
|
503
|
+
status: finalRun.status
|
|
504
|
+
});
|
|
505
|
+
return {
|
|
506
|
+
schedule: schedule.resultSchedule,
|
|
507
|
+
dueAt: schedule.dueAtIso,
|
|
508
|
+
idempotencyKey: schedule.idempotencyKey,
|
|
509
|
+
status: finalRun.status === "failed" || !finalizeResult.ok ? "failed" : "completed",
|
|
510
|
+
runId: finalRun.id,
|
|
511
|
+
...(finalizeResult.error ? { error: finalizeResult.error } : {})
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
async function loadTriggerHandler(host, handler) {
|
|
515
|
+
const module = await host.sourceLoader.load(handler.source);
|
|
516
|
+
const candidates = [module.default, module[handler.name], ...Object.values(module)];
|
|
517
|
+
const definition = candidates.find(isTriggerHandlerDefinition);
|
|
518
|
+
if (!definition)
|
|
519
|
+
throw new Error(`Trigger handler ${handler.name} did not export a trigger handler definition.`);
|
|
520
|
+
return definition;
|
|
521
|
+
}
|
|
522
|
+
export function normalizeScheduleTriggerDefinitionImpl(host, value) {
|
|
523
|
+
if (!isJsonObject(value))
|
|
524
|
+
throw new Error("Schedule trigger must be an object.");
|
|
525
|
+
const handler = stringValue(value.handler);
|
|
526
|
+
if (!handler)
|
|
527
|
+
throw new Error("Schedule trigger requires handler.");
|
|
528
|
+
if (!automationHandlers(host).some((candidate) => candidate.name === handler)) {
|
|
529
|
+
throw new Error(`Unknown trigger handler: ${handler}`);
|
|
530
|
+
}
|
|
531
|
+
const trigger = { handler };
|
|
532
|
+
if (value.target !== undefined && value.target !== null) {
|
|
533
|
+
trigger.target = normalizeTriggerTarget(value.target);
|
|
534
|
+
}
|
|
535
|
+
if (value.metadata !== undefined) {
|
|
536
|
+
if (!isJsonObject(value.metadata))
|
|
537
|
+
throw new Error("Schedule trigger metadata must be an object.");
|
|
538
|
+
trigger.metadata = value.metadata;
|
|
539
|
+
}
|
|
540
|
+
return trigger;
|
|
541
|
+
}
|
|
542
|
+
function normalizeTriggerTarget(value) {
|
|
543
|
+
if (!isJsonObject(value))
|
|
544
|
+
throw new Error("Schedule trigger target must be an object.");
|
|
545
|
+
const type = stringValue(value.type);
|
|
546
|
+
if (type === "skill") {
|
|
547
|
+
const name = stringValue(value.name);
|
|
548
|
+
if (!name)
|
|
549
|
+
throw new Error("Skill trigger targets require name.");
|
|
550
|
+
return { type, name, ...(isJsonObject(value.metadata) ? { metadata: value.metadata } : {}) };
|
|
551
|
+
}
|
|
552
|
+
if (type === "agent") {
|
|
553
|
+
const name = stringValue(value.name);
|
|
554
|
+
const model = stringValue(value.model);
|
|
555
|
+
const target = { type };
|
|
556
|
+
if (name)
|
|
557
|
+
target.name = name;
|
|
558
|
+
if (model)
|
|
559
|
+
target.model = model;
|
|
560
|
+
if (isJsonObject(value.metadata))
|
|
561
|
+
target.metadata = value.metadata;
|
|
562
|
+
return target;
|
|
563
|
+
}
|
|
564
|
+
if (type === "playbook") {
|
|
565
|
+
const name = stringValue(value.name);
|
|
566
|
+
if (!name)
|
|
567
|
+
throw new Error("Playbook trigger targets require name.");
|
|
568
|
+
return { type, name, ...(isJsonObject(value.metadata) ? { metadata: value.metadata } : {}) };
|
|
569
|
+
}
|
|
570
|
+
throw new Error(`Unsupported schedule trigger target type: ${type || "missing"}`);
|
|
571
|
+
}
|
|
572
|
+
function createTriggerHandlerContext(host, run, schedule, trigger, prepare) {
|
|
573
|
+
const sandboxHandle = host.createSandboxHandle(() => run, () => undefined, () => buildDefaultContextBundle(host.manifest, {
|
|
574
|
+
message: typeof run.input.message === "string" ? run.input.message : schedule.message,
|
|
575
|
+
channel: schedule.channel
|
|
576
|
+
}));
|
|
577
|
+
return {
|
|
578
|
+
runId: run.id,
|
|
579
|
+
agentRevision: host.manifest.agentRevision,
|
|
580
|
+
trigger,
|
|
581
|
+
state: createTriggerStateApi(host, run),
|
|
582
|
+
memory: host.createMemoryToolApi(run),
|
|
583
|
+
resources: createTriggerResourceApi(host, run, sandboxHandle),
|
|
584
|
+
blob: {
|
|
585
|
+
put: async (key, value, contentTypeOrOptions) => host.blob.put(key, value, contentTypeOrOptions),
|
|
586
|
+
get: async (key) => host.blob.get(key)
|
|
587
|
+
},
|
|
588
|
+
schedule: {
|
|
589
|
+
definition: schedule.definition,
|
|
590
|
+
run: {
|
|
591
|
+
scheduleId: schedule.resultSchedule,
|
|
592
|
+
runId: run.id,
|
|
593
|
+
runAt: trigger.dueAt ?? trigger.event?.occurredAt ?? schedule.dueAtIso,
|
|
594
|
+
idempotencyKey: trigger.idempotencyKey
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
env: host.env,
|
|
598
|
+
fetch: host.fetch,
|
|
599
|
+
prepare,
|
|
600
|
+
emit: async (eventType, data = {}) => {
|
|
601
|
+
await host.state.appendEvent(run.id, eventType, data);
|
|
602
|
+
},
|
|
603
|
+
idempotencyKey: (scope) => `${trigger.idempotencyKey}:${scope}`,
|
|
604
|
+
step: (key, fn, options) => host.durableStep(run.id, key, fn, options)
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
function createTriggerStateApi(host, run) {
|
|
608
|
+
const runtime = host;
|
|
609
|
+
return {
|
|
610
|
+
async upsertRoutineRun(input) {
|
|
611
|
+
const current = await runtime.state.getRun(run.id) ?? run;
|
|
612
|
+
const metadata = isJsonObject(current.input.metadata) ? current.input.metadata : {};
|
|
613
|
+
const existing = isJsonObject(metadata.routineRun) ? metadata.routineRun : {};
|
|
614
|
+
const record = normalizeRoutineRunRecord({
|
|
615
|
+
existing,
|
|
616
|
+
runId: run.id,
|
|
617
|
+
kind: input.kind,
|
|
618
|
+
triggerKey: input.triggerKey,
|
|
619
|
+
status: input.status ?? (stringValue(existing.status) || current.status),
|
|
620
|
+
metadata: input.metadata ?? (isJsonObject(existing.metadata) ? existing.metadata : {})
|
|
621
|
+
});
|
|
622
|
+
await runtime.state.updateRun(run.id, {
|
|
623
|
+
input: {
|
|
624
|
+
...current.input,
|
|
625
|
+
metadata: {
|
|
626
|
+
...metadata,
|
|
627
|
+
routineRun: safeJson(record)
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
await runtime.state.appendEvent(run.id, "routine.run_upserted", safeJson(record));
|
|
632
|
+
return record;
|
|
633
|
+
},
|
|
634
|
+
async completeRoutineRun(input) {
|
|
635
|
+
const current = await runtime.state.getRun(run.id) ?? run;
|
|
636
|
+
const metadata = isJsonObject(current.input.metadata) ? current.input.metadata : {};
|
|
637
|
+
const existing = isJsonObject(metadata.routineRun) ? metadata.routineRun : {};
|
|
638
|
+
const completeInput = {
|
|
639
|
+
existing,
|
|
640
|
+
runId: input.runId || run.id,
|
|
641
|
+
kind: stringValue(existing.kind) || "routine",
|
|
642
|
+
triggerKey: stringValue(existing.triggerKey) || "",
|
|
643
|
+
status: input.status,
|
|
644
|
+
metadata: input.metadata ?? (isJsonObject(existing.metadata) ? existing.metadata : {})
|
|
645
|
+
};
|
|
646
|
+
if (input.summary)
|
|
647
|
+
completeInput.summary = input.summary;
|
|
648
|
+
const record = normalizeRoutineRunRecord(completeInput);
|
|
649
|
+
await runtime.state.updateRun(run.id, {
|
|
650
|
+
input: {
|
|
651
|
+
...current.input,
|
|
652
|
+
metadata: {
|
|
653
|
+
...metadata,
|
|
654
|
+
routineRun: safeJson(record)
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
await runtime.state.appendEvent(run.id, "routine.run_completed", safeJson(record));
|
|
659
|
+
return record;
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
function createTriggerResourceApi(host, run, sandboxHandle) {
|
|
664
|
+
const base = host.createResourceToolApi(run, sandboxHandle);
|
|
665
|
+
return {
|
|
666
|
+
...base,
|
|
667
|
+
collect: async (input = {}) => collectTriggerResources(host, run, input)
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
async function collectTriggerResources(host, run, input = {}) {
|
|
671
|
+
const requested = new Set(input.sources ?? ["memory", "history", "connections"]);
|
|
672
|
+
const limit = positiveLimit(input.limit, 50);
|
|
673
|
+
const resources = [];
|
|
674
|
+
const sections = [];
|
|
675
|
+
if (requested.has("memory")) {
|
|
676
|
+
const documents = await host.memoryStore.list(host.memoryScopeForRun(run), { limit });
|
|
677
|
+
resources.push(...documents.map((document) => memoryDocumentSummary(document)));
|
|
678
|
+
if (documents.length > 0) {
|
|
679
|
+
sections.push([
|
|
680
|
+
"# Memory",
|
|
681
|
+
...documents.map((document) => [
|
|
682
|
+
`## ${document.path}`,
|
|
683
|
+
memoryBodyText(document)
|
|
684
|
+
].join("\n"))
|
|
685
|
+
].join("\n\n"));
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (requested.has("history")) {
|
|
689
|
+
const conversationId = typeof run.input.conversationId === "string" ? run.input.conversationId : undefined;
|
|
690
|
+
const messages = conversationId ? await host.state.listRecentMessages(conversationId, limit) : [];
|
|
691
|
+
resources.push(...messages.map((message) => ({
|
|
692
|
+
kind: "history",
|
|
693
|
+
role: message.role,
|
|
694
|
+
createdAt: message.createdAt,
|
|
695
|
+
runId: message.runId ?? null
|
|
696
|
+
})));
|
|
697
|
+
if (messages.length > 0) {
|
|
698
|
+
sections.push([
|
|
699
|
+
"# History",
|
|
700
|
+
...messages.map((message) => {
|
|
701
|
+
const text = typeof message.content.text === "string" ? message.content.text : JSON.stringify(message.content);
|
|
702
|
+
return `- ${message.role}: ${text}`;
|
|
703
|
+
})
|
|
704
|
+
].join("\n"));
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (requested.has("connections")) {
|
|
708
|
+
const connections = host.mergedConnections().slice(0, limit);
|
|
709
|
+
resources.push(...connections.map((connection) => ({
|
|
710
|
+
kind: "connection",
|
|
711
|
+
name: connection.name,
|
|
712
|
+
protocol: connection.protocol,
|
|
713
|
+
provider: connection.provider,
|
|
714
|
+
required: connection.required
|
|
715
|
+
})));
|
|
716
|
+
if (connections.length > 0) {
|
|
717
|
+
sections.push([
|
|
718
|
+
"# Connections",
|
|
719
|
+
...connections.map((connection) => `- ${connection.name} (${connection.protocol}, ${connection.provider})`)
|
|
720
|
+
].join("\n"));
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
if (requested.has("skills")) {
|
|
724
|
+
const skills = (await host.skillStore.list(host.skillScope())).filter((skill) => !skill.pending).slice(0, limit);
|
|
725
|
+
resources.push(...skills.map((skill) => ({
|
|
726
|
+
kind: "skill",
|
|
727
|
+
name: skill.name,
|
|
728
|
+
origin: skill.origin,
|
|
729
|
+
updatedAt: skill.updatedAt
|
|
730
|
+
})));
|
|
731
|
+
if (skills.length > 0) {
|
|
732
|
+
sections.push([
|
|
733
|
+
"# Skills",
|
|
734
|
+
...skills.map((skill) => `- ${skill.name} (${skill.origin})`)
|
|
735
|
+
].join("\n"));
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
await host.state.appendEvent(run.id, "trigger.resources_collected", {
|
|
739
|
+
sources: [...requested],
|
|
740
|
+
count: resources.length,
|
|
741
|
+
since: input.since ?? null
|
|
742
|
+
});
|
|
743
|
+
return {
|
|
744
|
+
markdown: sections.join("\n\n").trim(),
|
|
745
|
+
resources
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Dispatch application-managed dynamic schedules: atomically lease due rows
|
|
750
|
+
* and start one durable run per row. At-least-once; tasks should be idempotent.
|
|
751
|
+
*/
|
|
752
|
+
async function runDueDynamicSchedules(host, now) {
|
|
753
|
+
if (!host.schedulesConfig.dynamic)
|
|
754
|
+
return [];
|
|
755
|
+
const results = [];
|
|
756
|
+
const due = await host.scheduleStore.claimDue({ now, limit: 25, leaseForMs: 5 * 60_000 });
|
|
757
|
+
for (const job of due) {
|
|
758
|
+
const dueAtIso = job.nextRunAt;
|
|
759
|
+
const idempotencyKey = `dynamic:${job.id}:${dueAtIso}`;
|
|
760
|
+
const leaseToken = job.leaseToken ?? "";
|
|
761
|
+
try {
|
|
762
|
+
// openeve_schedule_runs rows reference openeve_schedules; dynamic jobs
|
|
763
|
+
// live in the schedule store, so upsert a parent row before recording runs.
|
|
764
|
+
await host.state.registerSchedule({
|
|
765
|
+
id: `dynamic:${job.id}`,
|
|
766
|
+
name: `dynamic:${job.id}`,
|
|
767
|
+
cron: job.cron ?? (job.everyMinutes ? `@every ${job.everyMinutes}m` : `@at ${dueAtIso}`),
|
|
768
|
+
...(job.timezone ? { timezone: job.timezone } : {}),
|
|
769
|
+
metadata: { source: "dynamic", scope: job.scope }
|
|
770
|
+
});
|
|
771
|
+
const schedule = dynamicScheduleExecution(host, job, dueAtIso, idempotencyKey);
|
|
772
|
+
const result = await runScheduleExecution(host, schedule);
|
|
773
|
+
await host.scheduleStore.complete(job.id, leaseToken, { ranAt: dueAtIso });
|
|
774
|
+
if (result.runId) {
|
|
775
|
+
await host.state.appendEvent(result.runId, "schedule.dynamic_dispatched", {
|
|
776
|
+
dynamicScheduleId: job.id,
|
|
777
|
+
dueAt: dueAtIso,
|
|
778
|
+
trigger: job.trigger ? safeJson(job.trigger) : null
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
results.push(result);
|
|
782
|
+
}
|
|
783
|
+
catch (error) {
|
|
784
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
785
|
+
try {
|
|
786
|
+
await host.state.recordScheduleRun({
|
|
787
|
+
scheduleId: `dynamic:${job.id}`,
|
|
788
|
+
runAt: dueAtIso,
|
|
789
|
+
idempotencyKey,
|
|
790
|
+
status: "failed"
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
catch (recordError) {
|
|
794
|
+
host.logger.warn("dynamic schedule failure bookkeeping failed", {
|
|
795
|
+
dynamicScheduleId: job.id,
|
|
796
|
+
error: recordError instanceof Error ? recordError.message : String(recordError)
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
// Escalating backoff on the failure streak (5min base, 2x, capped 6h);
|
|
800
|
+
// the store disables the schedule atomically once the streak hits the
|
|
801
|
+
// cap so a poison schedule cannot burn a model run every cycle forever.
|
|
802
|
+
const failureStreak = (job.consecutiveFailures ?? 0) + 1;
|
|
803
|
+
const retryDelayMs = Math.min(300_000 * 2 ** (failureStreak - 1), 6 * 3_600_000);
|
|
804
|
+
const disabled = failureStreak >= host.scheduleMaxConsecutiveFailures;
|
|
805
|
+
await host.scheduleStore.release(job.id, leaseToken, new Date(now.getTime() + retryDelayMs), {
|
|
806
|
+
maxConsecutiveFailures: host.scheduleMaxConsecutiveFailures
|
|
807
|
+
});
|
|
808
|
+
if (disabled) {
|
|
809
|
+
host.logger.warn("dynamic schedule disabled after consecutive failures", {
|
|
810
|
+
dynamicScheduleId: job.id,
|
|
811
|
+
scope: job.scope,
|
|
812
|
+
consecutiveFailures: failureStreak,
|
|
813
|
+
error: message
|
|
814
|
+
});
|
|
815
|
+
if (isRuntimeSettingsStore(host.state)) {
|
|
816
|
+
await host.state.appendRuntimeControlEvent(job.scope, "schedule.disabled_after_failures", {
|
|
817
|
+
dynamicScheduleId: job.id,
|
|
818
|
+
consecutiveFailures: failureStreak,
|
|
819
|
+
error: message
|
|
820
|
+
}).catch(() => undefined);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
results.push({ schedule: `dynamic:${job.id}`, dueAt: dueAtIso, idempotencyKey, status: "failed", error: message });
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return results;
|
|
827
|
+
}
|
|
828
|
+
function dynamicScheduleExecution(host, job, dueAtIso, idempotencyKey) {
|
|
829
|
+
const target = isJsonObject(job.target) ? job.target : {};
|
|
830
|
+
const targetConversation = typeof target.conversationId === "string" ? target.conversationId : undefined;
|
|
831
|
+
const targetProject = typeof target.projectId === "string" ? target.projectId : undefined;
|
|
832
|
+
const conversationId = job.conversationId ?? targetConversation;
|
|
833
|
+
const projectId = job.projectId ?? targetProject;
|
|
834
|
+
const delivery = isJsonObject(job.target) ? job.target : undefined;
|
|
835
|
+
const message = dynamicScheduleMessage(job);
|
|
836
|
+
const trigger = job.trigger ? normalizeScheduleTriggerDefinitionImpl(host, job.trigger) : undefined;
|
|
837
|
+
const metadata = {
|
|
838
|
+
...(job.metadata ?? {}),
|
|
839
|
+
dynamicScheduleId: job.id,
|
|
840
|
+
scope: job.scope,
|
|
841
|
+
source: "dynamic",
|
|
842
|
+
schedule: `dynamic:${job.id}`,
|
|
843
|
+
dueAt: dueAtIso,
|
|
844
|
+
idempotencyKey
|
|
845
|
+
};
|
|
846
|
+
if (job.target)
|
|
847
|
+
metadata.target = safeJson(job.target);
|
|
848
|
+
const definition = { ...job };
|
|
849
|
+
if (trigger)
|
|
850
|
+
definition.trigger = trigger;
|
|
851
|
+
return {
|
|
852
|
+
source: "dynamic",
|
|
853
|
+
scheduleId: job.id,
|
|
854
|
+
resultSchedule: `dynamic:${job.id}`,
|
|
855
|
+
dueAtIso,
|
|
856
|
+
idempotencyKey,
|
|
857
|
+
message,
|
|
858
|
+
channel: job.channel,
|
|
859
|
+
...(trigger ? { trigger } : {}),
|
|
860
|
+
metadata,
|
|
861
|
+
definition: safeJson(definition),
|
|
862
|
+
...(job.userId ? { userId: job.userId } : {}),
|
|
863
|
+
...(conversationId ? { conversationId } : {}),
|
|
864
|
+
...(projectId ? { projectId } : {}),
|
|
865
|
+
...(delivery ? { delivery } : {})
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
export async function startSchedulerPollingLoopImpl(host) {
|
|
869
|
+
if (!hasScheduledWork(host))
|
|
870
|
+
return undefined;
|
|
871
|
+
await registerManifestSchedulesImpl(host);
|
|
872
|
+
let inFlight = false;
|
|
873
|
+
const tick = async () => {
|
|
874
|
+
if (inFlight)
|
|
875
|
+
return [];
|
|
876
|
+
inFlight = true;
|
|
877
|
+
try {
|
|
878
|
+
return await runDueSchedulesImpl(host, { now: host.now(), lookbackMs: host.schedulerTickMs });
|
|
879
|
+
}
|
|
880
|
+
catch (error) {
|
|
881
|
+
// The interval invokes tick as a floating promise; a throw here would
|
|
882
|
+
// become an unhandled rejection and take down the whole process.
|
|
883
|
+
host.logger.error("scheduler tick failed", {
|
|
884
|
+
error: error instanceof Error ? error.message : String(error)
|
|
885
|
+
});
|
|
886
|
+
return [];
|
|
887
|
+
}
|
|
888
|
+
finally {
|
|
889
|
+
inFlight = false;
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
const timer = setInterval(() => {
|
|
893
|
+
void tick();
|
|
894
|
+
}, host.schedulerTickMs);
|
|
895
|
+
timer.unref?.();
|
|
896
|
+
void tick();
|
|
897
|
+
return {
|
|
898
|
+
stop() {
|
|
899
|
+
clearInterval(timer);
|
|
900
|
+
},
|
|
901
|
+
tick
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
function hasScheduledWork(host) {
|
|
905
|
+
if (host.schedulesConfig.dynamic)
|
|
906
|
+
return true;
|
|
907
|
+
return manifestScheduleRegistrations(host).length > 0;
|
|
908
|
+
}
|
|
909
|
+
function compiledSchedule(host, name) {
|
|
910
|
+
return host.manifest.automations?.find((automation) => automation.name === name)
|
|
911
|
+
?? host.manifest.schedules.find((schedule) => schedule.name === name);
|
|
912
|
+
}
|
|
913
|
+
function scheduleMessage(host, name) {
|
|
914
|
+
const registration = manifestScheduleRegistrations(host).find((schedule) => schedule.schedule === name);
|
|
915
|
+
const compiled = compiledSchedule(host, name);
|
|
916
|
+
return registration?.message ?? compiled?.message ?? compiled?.description ?? `Run schedule ${name}.`;
|
|
917
|
+
}
|
|
918
|
+
function automationHandlers(host) {
|
|
919
|
+
return host.manifest.automationHandlers?.length
|
|
920
|
+
? host.manifest.automationHandlers
|
|
921
|
+
: host.manifest.triggerHandlers ?? [];
|
|
922
|
+
}
|
|
923
|
+
function manifestScheduleRegistrations(host) {
|
|
924
|
+
if (Array.isArray(host.manifest.automationRegistrations)) {
|
|
925
|
+
return host.manifest.automationRegistrations.flatMap((automation) => {
|
|
926
|
+
if (automation.trigger.type !== "schedule")
|
|
927
|
+
return [];
|
|
928
|
+
const registration = {
|
|
929
|
+
schedule: automation.automation,
|
|
930
|
+
cron: automation.trigger.cron,
|
|
931
|
+
idempotencyKey: automation.idempotencyKey
|
|
932
|
+
};
|
|
933
|
+
if (automation.trigger.timezone)
|
|
934
|
+
registration.timezone = automation.trigger.timezone;
|
|
935
|
+
if (automation.message)
|
|
936
|
+
registration.message = automation.message;
|
|
937
|
+
if (automation.target)
|
|
938
|
+
registration.target = automation.target;
|
|
939
|
+
if (automation.lifecycle) {
|
|
940
|
+
registration.trigger = {
|
|
941
|
+
handler: automation.lifecycle.handler,
|
|
942
|
+
...(automation.target ? { target: automation.target } : {}),
|
|
943
|
+
...(automation.metadata ? { metadata: automation.metadata } : {})
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
return [registration];
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
return host.manifest.scheduleRegistrations ?? [];
|
|
950
|
+
}
|
|
951
|
+
//# sourceMappingURL=schedule-execution.js.map
|