agent-relay-server 0.62.3 → 0.63.1
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/docs/openapi.json +1 -1
- package/package.json +4 -2
- package/public/assets/{MessageBubble-DRrxfjyT.js → MessageBubble-Cl6ufPHw.js} +2 -2
- package/public/assets/MessageBubble-Cl6ufPHw.js.map +1 -0
- package/public/assets/{PlanGraphPanel-Dp2eYazz.js → PlanGraphPanel-BnfjmkQ8.js} +2 -2
- package/public/assets/{PlanGraphPanel-Dp2eYazz.js.map → PlanGraphPanel-BnfjmkQ8.js.map} +1 -1
- package/public/assets/{activity--t0U2yzq.js → activity-aFxvGLtd.js} +2 -2
- package/public/assets/{activity--t0U2yzq.js.map → activity-aFxvGLtd.js.map} +1 -1
- package/public/assets/{agent-profiles-BMtJYzxn.js → agent-profiles-B4mleUDz.js} +2 -2
- package/public/assets/{agent-profiles-BMtJYzxn.js.map → agent-profiles-B4mleUDz.js.map} +1 -1
- package/public/assets/{agents-Ck9AePlc.js → agents-DW5djToQ.js} +2 -2
- package/public/assets/{agents-Ck9AePlc.js.map → agents-DW5djToQ.js.map} +1 -1
- package/public/assets/{analytics-CzfK387L.js → analytics-CRi6thtg.js} +2 -2
- package/public/assets/{analytics-CzfK387L.js.map → analytics-CRi6thtg.js.map} +1 -1
- package/public/assets/{automation-Cgw8vtE8.js → automation-Do4OpauI.js} +2 -2
- package/public/assets/{automation-Cgw8vtE8.js.map → automation-Do4OpauI.js.map} +1 -1
- package/public/assets/{branch-state-badge-DnEEPtOP.js → branch-state-badge-D3W3w7zz.js} +2 -2
- package/public/assets/{branch-state-badge-DnEEPtOP.js.map → branch-state-badge-D3W3w7zz.js.map} +1 -1
- package/public/assets/{channels-Dc3L2MQs.js → channels-BytdR9NS.js} +2 -2
- package/public/assets/{channels-Dc3L2MQs.js.map → channels-BytdR9NS.js.map} +1 -1
- package/public/assets/{chat-Fc6iJ7sa.js → chat-CvaQ2czL.js} +2 -2
- package/public/assets/{chat-Fc6iJ7sa.js.map → chat-CvaQ2czL.js.map} +1 -1
- package/public/assets/{connectors-Dc2ttGWs.js → connectors-BLQDp8Bc.js} +2 -2
- package/public/assets/{connectors-Dc2ttGWs.js.map → connectors-BLQDp8Bc.js.map} +1 -1
- package/public/assets/{display-DzNvOu1j.js → display-B06I5sqa.js} +3 -3
- package/public/assets/display-B06I5sqa.js.map +1 -0
- package/public/assets/{formatted-body-impl-Bg-gx7rG.js → formatted-body-impl-0Lf8e1Ic.js} +2 -2
- package/public/assets/{formatted-body-impl-Bg-gx7rG.js.map → formatted-body-impl-0Lf8e1Ic.js.map} +1 -1
- package/public/assets/{index-BeDncjN4.js → index-C3su8ezq.js} +6 -6
- package/public/assets/{index-BeDncjN4.js.map → index-C3su8ezq.js.map} +1 -1
- package/public/assets/{insights-c08aW-7n.js → insights-64uExH1E.js} +2 -2
- package/public/assets/{insights-c08aW-7n.js.map → insights-64uExH1E.js.map} +1 -1
- package/public/assets/{integrations-BwpgcqnF.js → integrations-HyC1EaIp.js} +2 -2
- package/public/assets/{integrations-BwpgcqnF.js.map → integrations-HyC1EaIp.js.map} +1 -1
- package/public/assets/{maintenance-CTKP0kWB.js → maintenance-EVBpKH07.js} +2 -2
- package/public/assets/{maintenance-CTKP0kWB.js.map → maintenance-EVBpKH07.js.map} +1 -1
- package/public/assets/{managed-agents-BofCxo8d.js → managed-agents-BLZPxGuw.js} +2 -2
- package/public/assets/{managed-agents-BofCxo8d.js.map → managed-agents-BLZPxGuw.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-CQ-6BIb8.js → markdown-preview-impl-Bg6rCWYN.js} +2 -2
- package/public/assets/{markdown-preview-impl-CQ-6BIb8.js.map → markdown-preview-impl-Bg6rCWYN.js.map} +1 -1
- package/public/assets/{memory-DvfXjMoi.js → memory-Bjr8bgjj.js} +2 -2
- package/public/assets/{memory-DvfXjMoi.js.map → memory-Bjr8bgjj.js.map} +1 -1
- package/public/assets/{messages-C9VsPzZ_.js → messages-CRYw1F4z.js} +2 -2
- package/public/assets/{messages-C9VsPzZ_.js.map → messages-CRYw1F4z.js.map} +1 -1
- package/public/assets/{orchestrators-MffVueeE.js → orchestrators-D-co1vdp.js} +2 -2
- package/public/assets/{orchestrators-MffVueeE.js.map → orchestrators-D-co1vdp.js.map} +1 -1
- package/public/assets/{overview-BSVajefM.js → overview-Ddy5czri.js} +2 -2
- package/public/assets/{overview-BSVajefM.js.map → overview-Ddy5czri.js.map} +1 -1
- package/public/assets/{pairs-B5711-Cs.js → pairs-CFVM8uHL.js} +2 -2
- package/public/assets/{pairs-B5711-Cs.js.map → pairs-CFVM8uHL.js.map} +1 -1
- package/public/assets/{security-DkUwK9Vj.js → security-Df4_76bh.js} +2 -2
- package/public/assets/{security-DkUwK9Vj.js.map → security-Df4_76bh.js.map} +1 -1
- package/public/assets/{select-DrM2RRiT.js → select-Cq3BBnL9.js} +2 -2
- package/public/assets/{select-DrM2RRiT.js.map → select-Cq3BBnL9.js.map} +1 -1
- package/public/assets/{settings-DdKCb-vy.js → settings-DOLEYXZL.js} +2 -2
- package/public/assets/{settings-DdKCb-vy.js.map → settings-DOLEYXZL.js.map} +1 -1
- package/public/assets/{store-iTiheuTD.js → store-C1E8jmNE.js} +4 -4
- package/public/assets/store-C1E8jmNE.js.map +1 -0
- package/public/assets/{tasks-e1y1PFfO.js → tasks-BcsmPi9_.js} +2 -2
- package/public/assets/{tasks-e1y1PFfO.js.map → tasks-BcsmPi9_.js.map} +1 -1
- package/public/assets/{teams-BUcxoGC_.js → teams-BQrV3eKI.js} +2 -2
- package/public/assets/{teams-BUcxoGC_.js.map → teams-BQrV3eKI.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-CmZjQOB2.js → terminal-viewer-impl-Dle04qpH.js} +2 -2
- package/public/assets/{terminal-viewer-impl-CmZjQOB2.js.map → terminal-viewer-impl-Dle04qpH.js.map} +1 -1
- package/public/assets/{work-queue-CXBQx43n.js → work-queue-BLhTLg4o.js} +2 -2
- package/public/assets/{work-queue-CXBQx43n.js.map → work-queue-BLhTLg4o.js.map} +1 -1
- package/public/assets/{workspaces-D2EfZh9a.js → workspaces-4NCNTmRQ.js} +3 -3
- package/public/assets/{workspaces-D2EfZh9a.js.map → workspaces-4NCNTmRQ.js.map} +1 -1
- package/public/index.html +3 -3
- package/runner/src/adapter.ts +2 -1
- package/runner/src/config.ts +143 -5
- package/src/agent-ref.ts +8 -6
- package/src/artifact-storage.ts +3 -5
- package/src/automations/constants.ts +12 -0
- package/src/automations/cron.ts +129 -0
- package/src/automations/crud.ts +84 -0
- package/src/automations/dispatch.ts +275 -0
- package/src/automations/index.ts +5 -0
- package/src/automations/reconcile.ts +245 -0
- package/src/automations/storage.ts +181 -0
- package/src/automations/types.ts +16 -0
- package/src/automations/validation.ts +140 -0
- package/src/automations.ts +1 -1045
- package/src/bus.ts +3 -4
- package/src/cli/_shared.ts +5 -4
- package/src/cli/agent-detect.ts +6 -4
- package/src/cli/introspect.ts +3 -2
- package/src/cli/token.ts +2 -1
- package/src/cli/workspace.ts +2 -1
- package/src/config.ts +176 -1
- package/src/connectors.ts +3 -3
- package/src/db/activity.ts +1 -1
- package/src/db/agent-predicates.ts +9 -0
- package/src/db/agent-search.ts +2 -2
- package/src/db/agents.ts +13 -41
- package/src/db/artifacts.ts +8 -8
- package/src/db/channel-identity.ts +38 -0
- package/src/db/channels.ts +37 -61
- package/src/db/connection.ts +2 -1
- package/src/db/continuation-archives.ts +7 -7
- package/src/db/delivery.ts +7 -12
- package/src/db/inbox.ts +1 -1
- package/src/db/integrations.ts +4 -4
- package/src/db/issue-lifecycle.ts +6 -6
- package/src/db/mappers.ts +1 -2
- package/src/db/merge-lease.ts +3 -3
- package/src/db/message-claims.ts +16 -0
- package/src/db/message-queries.ts +8 -0
- package/src/db/message-reads.ts +26 -9
- package/src/db/messages.ts +17 -41
- package/src/db/notification-subscriptions.ts +2 -2
- package/src/db/orchestrators.ts +4 -4
- package/src/db/pair-maintenance.ts +9 -0
- package/src/db/pairs.ts +10 -18
- package/src/db/plans.ts +1 -1
- package/src/db/policy-runtime.ts +14 -0
- package/src/db/project-entries.ts +1 -1
- package/src/db/projects.ts +2 -2
- package/src/db/shared.ts +7 -0
- package/src/db/stats.ts +2 -2
- package/src/db/task-core.ts +18 -0
- package/src/db/task-maintenance.ts +24 -0
- package/src/db/tasks.ts +5 -17
- package/src/db/teams.ts +3 -3
- package/src/db/workspaces.ts +6 -6
- package/src/index.ts +6 -5
- package/src/issue-tracker/github.ts +3 -3
- package/src/maintenance/constants.ts +52 -0
- package/src/maintenance/events.ts +32 -0
- package/src/maintenance/index.ts +7 -0
- package/src/maintenance/jobs.ts +340 -0
- package/src/maintenance/orphaned-sessions.ts +86 -0
- package/src/maintenance/scheduler.ts +231 -0
- package/src/maintenance/teams.ts +65 -0
- package/src/maintenance/types.ts +31 -0
- package/src/maintenance/workspaces.ts +251 -0
- package/src/maintenance.ts +1 -1099
- package/src/mcp/identity.ts +131 -0
- package/src/mcp/index.ts +208 -0
- package/src/mcp/tools-messaging.ts +459 -0
- package/src/mcp/tools-spawn.ts +145 -0
- package/src/mcp/tools-workspace.ts +227 -0
- package/src/mcp/types.ts +24 -0
- package/src/mcp/validation.ts +104 -0
- package/src/mcp.ts +1 -1319
- package/src/notification-router.ts +2 -2
- package/src/notification-rules.ts +2 -2
- package/src/project-ingest.ts +3 -3
- package/src/project-instructions.ts +1 -1
- package/src/provider-config-store.ts +5 -4
- package/src/provider-state.ts +2 -4
- package/src/reviewer-pipeline.ts +1 -1
- package/src/routes/agent-sessions.ts +5 -4
- package/src/routes/orchestrator-proxy.ts +5 -4
- package/src/routes/workspaces.ts +5 -4
- package/src/security.ts +13 -7
- package/src/self-resume.ts +3 -7
- package/src/services/agent-action.ts +2 -2
- package/src/services/issue-activity.ts +0 -4
- package/src/services/issue-associations.ts +2 -2
- package/src/services/issue-lifecycle.ts +8 -11
- package/src/services/managed-running.ts +1 -1
- package/src/services/parity-harness.ts +3 -3
- package/src/services/plan-graph.ts +4 -7
- package/src/services/send-message.ts +16 -12
- package/src/services/shutdown-agent.ts +2 -2
- package/src/services/team-templates.ts +2 -2
- package/src/services/team.ts +2 -2
- package/src/settings/modules.ts +1 -1
- package/src/settings/registry.ts +3 -3
- package/src/spawn-command.ts +1 -1
- package/src/sse.ts +0 -4
- package/src/upgrade.ts +7 -9
- package/src/workspace-actions.ts +1 -1
- package/src/workspace-auto-merge.ts +4 -5
- package/src/workspace-claim.ts +1 -2
- package/src/workspace-orphans.ts +2 -7
- package/src/workspace-probe.ts +5 -8
- package/public/assets/MessageBubble-DRrxfjyT.js.map +0 -1
- package/public/assets/display-DzNvOu1j.js.map +0 -1
- package/public/assets/store-iTiheuTD.js.map +0 -1
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { parseJson } from "../utils";
|
|
3
|
+
import { getDb } from "../db";
|
|
4
|
+
import { BLOCKING_RUN_STATUSES, OPEN_RUN_STATUSES } from "./constants";
|
|
5
|
+
import type { Automation, AutomationRun, AutomationRunStatus, AutomationTargetPolicy, AutomationTaskTemplate } from "../types";
|
|
6
|
+
|
|
7
|
+
let initializedDb: unknown = null;
|
|
8
|
+
|
|
9
|
+
export function db() {
|
|
10
|
+
return getDb();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function ensureAutomationTables(): void {
|
|
14
|
+
const current = db();
|
|
15
|
+
if (initializedDb === current) return;
|
|
16
|
+
initializedDb = current;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function rowToAutomation(row: any): Automation {
|
|
20
|
+
return {
|
|
21
|
+
id: row.id,
|
|
22
|
+
kind: row.kind,
|
|
23
|
+
name: row.name,
|
|
24
|
+
description: row.description ?? undefined,
|
|
25
|
+
enabled: row.enabled === 1,
|
|
26
|
+
schedule: row.schedule,
|
|
27
|
+
timezone: row.timezone,
|
|
28
|
+
nextRunAt: row.next_run_at ?? undefined,
|
|
29
|
+
catchUpPolicy: row.catch_up_policy,
|
|
30
|
+
concurrencyPolicy: row.concurrency_policy,
|
|
31
|
+
orchestratorId: row.orchestrator_id,
|
|
32
|
+
targetPolicy: parseJson<AutomationTargetPolicy>(row.target_policy, { mode: "existing_agent", selector: {}, ifNoMatch: "fail" }),
|
|
33
|
+
taskTemplate: parseJson<AutomationTaskTemplate>(row.task_template, { title: row.name, body: "" }),
|
|
34
|
+
createdAt: row.created_at,
|
|
35
|
+
updatedAt: row.updated_at,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function rowToAutomationRun(row: any): AutomationRun {
|
|
40
|
+
return {
|
|
41
|
+
id: row.id,
|
|
42
|
+
automationId: row.automation_id,
|
|
43
|
+
status: row.status,
|
|
44
|
+
scheduledFor: row.scheduled_for,
|
|
45
|
+
startedAt: row.started_at ?? undefined,
|
|
46
|
+
finishedAt: row.finished_at ?? undefined,
|
|
47
|
+
orchestratorId: row.orchestrator_id,
|
|
48
|
+
targetAgentId: row.target_agent_id ?? undefined,
|
|
49
|
+
spawnedAgentId: row.spawned_agent_id ?? undefined,
|
|
50
|
+
taskId: row.task_id ?? undefined,
|
|
51
|
+
messageId: row.message_id ?? undefined,
|
|
52
|
+
error: row.error ?? undefined,
|
|
53
|
+
result: parseJson<Record<string, unknown> | undefined>(row.result, undefined),
|
|
54
|
+
meta: parseJson<Record<string, unknown>>(row.meta, {}),
|
|
55
|
+
createdAt: row.created_at,
|
|
56
|
+
updatedAt: row.updated_at,
|
|
57
|
+
shutdownRequestedAt: row.shutdown_requested_at ?? undefined,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function getAutomation(id: string): Automation | null {
|
|
62
|
+
ensureAutomationTables();
|
|
63
|
+
const row = db().query("SELECT * FROM automations WHERE id = ?").get(id) as any;
|
|
64
|
+
return row ? rowToAutomation(row) : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function listAutomations(): Automation[] {
|
|
68
|
+
ensureAutomationTables();
|
|
69
|
+
return (db().query("SELECT * FROM automations ORDER BY enabled DESC, next_run_at IS NULL, next_run_at ASC, name COLLATE NOCASE").all() as any[])
|
|
70
|
+
.map(rowToAutomation);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function listAutomationRuns(filter: { automationId?: string; status?: string; limit?: number } = {}): AutomationRun[] {
|
|
74
|
+
ensureAutomationTables();
|
|
75
|
+
const clauses: string[] = [];
|
|
76
|
+
const params: (string | number)[] = [];
|
|
77
|
+
if (filter.automationId) {
|
|
78
|
+
clauses.push("automation_id = ?");
|
|
79
|
+
params.push(filter.automationId);
|
|
80
|
+
}
|
|
81
|
+
if (filter.status) {
|
|
82
|
+
clauses.push("status = ?");
|
|
83
|
+
params.push(filter.status);
|
|
84
|
+
}
|
|
85
|
+
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
86
|
+
params.push(filter.limit ?? 100);
|
|
87
|
+
return (db().query(`SELECT * FROM automation_runs ${where} ORDER BY created_at DESC LIMIT ?`).all(...params) as any[])
|
|
88
|
+
.map(rowToAutomationRun);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function getAutomationRun(id: string): AutomationRun | null {
|
|
92
|
+
ensureAutomationTables();
|
|
93
|
+
const row = db().query("SELECT * FROM automation_runs WHERE id = ?").get(id) as any;
|
|
94
|
+
return row ? rowToAutomationRun(row) : null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function insertRun(automation: Automation, scheduledFor: number, now: number): AutomationRun {
|
|
98
|
+
const id = randomUUID();
|
|
99
|
+
db().query(`
|
|
100
|
+
INSERT INTO automation_runs (id, automation_id, status, scheduled_for, orchestrator_id, meta, created_at, updated_at)
|
|
101
|
+
VALUES (?, ?, 'dispatching', ?, ?, '{}', ?, ?)
|
|
102
|
+
`).run(id, automation.id, scheduledFor, automation.orchestratorId, now, now);
|
|
103
|
+
return getAutomationRun(id)!;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function enqueueAutomationRun(automation: Automation, scheduledFor: number, now: number): AutomationRun {
|
|
107
|
+
const existing = db().query("SELECT * FROM automation_runs WHERE automation_id = ? AND status = 'scheduled' AND scheduled_for = ?")
|
|
108
|
+
.get(automation.id, scheduledFor) as any;
|
|
109
|
+
if (existing) return rowToAutomationRun(existing);
|
|
110
|
+
const id = randomUUID();
|
|
111
|
+
db().query(`
|
|
112
|
+
INSERT INTO automation_runs (id, automation_id, status, scheduled_for, orchestrator_id, meta, created_at, updated_at)
|
|
113
|
+
VALUES (?, ?, 'scheduled', ?, ?, '{}', ?, ?)
|
|
114
|
+
`).run(id, automation.id, scheduledFor, automation.orchestratorId, now, now);
|
|
115
|
+
return getAutomationRun(id)!;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function updateRun(id: string, input: {
|
|
119
|
+
status?: AutomationRunStatus;
|
|
120
|
+
startedAt?: number;
|
|
121
|
+
finishedAt?: number;
|
|
122
|
+
targetAgentId?: string;
|
|
123
|
+
spawnedAgentId?: string;
|
|
124
|
+
taskId?: number;
|
|
125
|
+
messageId?: number;
|
|
126
|
+
error?: string;
|
|
127
|
+
result?: Record<string, unknown>;
|
|
128
|
+
meta?: Record<string, unknown>;
|
|
129
|
+
shutdownRequestedAt?: number;
|
|
130
|
+
}, now: number): void {
|
|
131
|
+
const current = getAutomationRun(id);
|
|
132
|
+
const meta = input.meta !== undefined ? input.meta : current?.meta;
|
|
133
|
+
db().query(`
|
|
134
|
+
UPDATE automation_runs
|
|
135
|
+
SET status = COALESCE(?, status),
|
|
136
|
+
started_at = COALESCE(?, started_at),
|
|
137
|
+
finished_at = COALESCE(?, finished_at),
|
|
138
|
+
target_agent_id = COALESCE(?, target_agent_id),
|
|
139
|
+
spawned_agent_id = COALESCE(?, spawned_agent_id),
|
|
140
|
+
task_id = COALESCE(?, task_id),
|
|
141
|
+
message_id = COALESCE(?, message_id),
|
|
142
|
+
control_message_id = control_message_id,
|
|
143
|
+
error = COALESCE(?, error),
|
|
144
|
+
result = COALESCE(?, result),
|
|
145
|
+
meta = COALESCE(?, meta),
|
|
146
|
+
shutdown_requested_at = COALESCE(?, shutdown_requested_at),
|
|
147
|
+
updated_at = ?
|
|
148
|
+
WHERE id = ?
|
|
149
|
+
`).run(
|
|
150
|
+
input.status ?? null,
|
|
151
|
+
input.startedAt ?? null,
|
|
152
|
+
input.finishedAt ?? null,
|
|
153
|
+
input.targetAgentId ?? null,
|
|
154
|
+
input.spawnedAgentId ?? null,
|
|
155
|
+
input.taskId ?? null,
|
|
156
|
+
input.messageId ?? null,
|
|
157
|
+
input.error ?? null,
|
|
158
|
+
input.result ? JSON.stringify(input.result) : null,
|
|
159
|
+
meta ? JSON.stringify(meta) : null,
|
|
160
|
+
input.shutdownRequestedAt ?? null,
|
|
161
|
+
now,
|
|
162
|
+
id,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function hasActiveRun(automationId: string): boolean {
|
|
167
|
+
const placeholders = [...BLOCKING_RUN_STATUSES].map(() => "?").join(",");
|
|
168
|
+
const row = db().query(`SELECT id FROM automation_runs WHERE automation_id = ? AND status IN (${placeholders}) LIMIT 1`)
|
|
169
|
+
.get(automationId, ...BLOCKING_RUN_STATUSES) as any;
|
|
170
|
+
return Boolean(row);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function cancelActiveRuns(automationId: string, now: number, reason: string): void {
|
|
174
|
+
const placeholders = [...OPEN_RUN_STATUSES].map(() => "?").join(",");
|
|
175
|
+
db().query(`UPDATE automation_runs SET status = 'canceled', finished_at = ?, error = ?, updated_at = ? WHERE automation_id = ? AND status IN (${placeholders})`)
|
|
176
|
+
.run(now, reason, now, automationId, ...OPEN_RUN_STATUSES);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function rescheduleAutomation(automation: Automation, nextRunAt: number | undefined, now: number): void {
|
|
180
|
+
db().query("UPDATE automations SET next_run_at = ?, updated_at = ? WHERE id = ?").run(nextRunAt ?? null, now, automation.id);
|
|
181
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Automation, AutomationRun, Command, Message, Task } from "../types";
|
|
2
|
+
|
|
3
|
+
export interface AutomationDispatchResult {
|
|
4
|
+
automation: Automation;
|
|
5
|
+
run: AutomationRun;
|
|
6
|
+
task?: Task;
|
|
7
|
+
message?: Message;
|
|
8
|
+
command?: Command;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface AutomationReconcileResult {
|
|
12
|
+
run: AutomationRun;
|
|
13
|
+
task?: Task;
|
|
14
|
+
message?: Message;
|
|
15
|
+
command?: Command;
|
|
16
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { errMessage, VALID_WORKSPACE_MODES } from "agent-relay-sdk";
|
|
2
|
+
import { resolveProviderSelection, type ProviderEffort } from "agent-relay-sdk/provider-catalog";
|
|
3
|
+
import { getAgentProfile } from "../config-store";
|
|
4
|
+
import { ValidationError } from "../db";
|
|
5
|
+
import { effectiveProviderCatalogList } from "../provider-catalog-store";
|
|
6
|
+
import { cleanEnum, cleanString, cleanStringArray, optionalEnum } from "../validation";
|
|
7
|
+
import { DEFAULT_CATCH_UP, DEFAULT_CONCURRENCY, DEFAULT_TIMEZONE, MAX_RUNTIME_BUDGET_MS, MIN_RUNTIME_BUDGET_MS, AUTOMATION_KIND } from "./constants";
|
|
8
|
+
import { validateCron } from "./cron";
|
|
9
|
+
import type {
|
|
10
|
+
AutomationRuntimeBudget,
|
|
11
|
+
AutomationTargetPolicy,
|
|
12
|
+
AutomationTaskTemplate,
|
|
13
|
+
CreateAutomationInput,
|
|
14
|
+
TaskSeverity,
|
|
15
|
+
} from "../types";
|
|
16
|
+
|
|
17
|
+
function cleanBool(value: unknown, field: string): boolean | undefined {
|
|
18
|
+
if (value === undefined || value === null) return undefined;
|
|
19
|
+
if (typeof value !== "boolean") throw new ValidationError(`${field} must be a boolean`);
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function cleanMeta(value: unknown, field = "metadata"): Record<string, unknown> | undefined {
|
|
24
|
+
if (value === undefined || value === null) return undefined;
|
|
25
|
+
if (typeof value !== "object" || Array.isArray(value)) throw new ValidationError(`${field} must be an object`);
|
|
26
|
+
if (JSON.stringify(value).length > 65_536) throw new ValidationError(`${field} is too large`);
|
|
27
|
+
return value as Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeTaskTemplate(value: unknown): AutomationTaskTemplate {
|
|
31
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new ValidationError("taskTemplate must be an object");
|
|
32
|
+
const input = value as Record<string, unknown>;
|
|
33
|
+
return {
|
|
34
|
+
title: cleanString(input.title, "taskTemplate.title", { required: true, max: 240 })!,
|
|
35
|
+
body: cleanString(input.body, "taskTemplate.body", { required: true, max: 200_000 })!,
|
|
36
|
+
severity: optionalEnum(input.severity, "taskTemplate.severity", ["info", "warning", "critical"] as const, "info") as TaskSeverity,
|
|
37
|
+
dedupeKey: cleanString(input.dedupeKey, "taskTemplate.dedupeKey", { max: 240 }),
|
|
38
|
+
externalUrl: cleanString(input.externalUrl, "taskTemplate.externalUrl", { max: 1000 }),
|
|
39
|
+
metadata: cleanMeta(input.metadata, "taskTemplate.metadata"),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function normalizeRuntimeBudget(input: Record<string, unknown>): AutomationRuntimeBudget | undefined {
|
|
44
|
+
const rawBudget = input.runtimeBudget;
|
|
45
|
+
let budgetInput: Record<string, unknown> | undefined;
|
|
46
|
+
if (rawBudget !== undefined && rawBudget !== null) {
|
|
47
|
+
if (typeof rawBudget !== "object" || Array.isArray(rawBudget)) throw new ValidationError("targetPolicy.runtimeBudget must be an object");
|
|
48
|
+
budgetInput = rawBudget as Record<string, unknown>;
|
|
49
|
+
} else if (typeof input.shutdownAfterMs === "number" && input.shutdownAfterMs > 0) {
|
|
50
|
+
budgetInput = { maxRuntimeMs: input.shutdownAfterMs };
|
|
51
|
+
}
|
|
52
|
+
if (!budgetInput) return undefined;
|
|
53
|
+
|
|
54
|
+
const maxRuntimeMsRaw = budgetInput.maxRuntimeMs;
|
|
55
|
+
if (!Number.isSafeInteger(maxRuntimeMsRaw)) {
|
|
56
|
+
throw new ValidationError("targetPolicy.runtimeBudget.maxRuntimeMs must be between 60000 and 86400000");
|
|
57
|
+
}
|
|
58
|
+
const maxRuntimeMs = maxRuntimeMsRaw as number;
|
|
59
|
+
if (maxRuntimeMs < MIN_RUNTIME_BUDGET_MS || maxRuntimeMs > MAX_RUNTIME_BUDGET_MS) {
|
|
60
|
+
throw new ValidationError("targetPolicy.runtimeBudget.maxRuntimeMs must be between 60000 and 86400000");
|
|
61
|
+
}
|
|
62
|
+
const warnAtMsRaw = budgetInput.warnAtMs;
|
|
63
|
+
if (warnAtMsRaw !== undefined && !Number.isSafeInteger(warnAtMsRaw)) {
|
|
64
|
+
throw new ValidationError("targetPolicy.runtimeBudget.warnAtMs must be less than maxRuntimeMs");
|
|
65
|
+
}
|
|
66
|
+
const warnAtMs = warnAtMsRaw as number | undefined;
|
|
67
|
+
if (warnAtMs !== undefined && (warnAtMs < 0 || warnAtMs >= maxRuntimeMs)) {
|
|
68
|
+
throw new ValidationError("targetPolicy.runtimeBudget.warnAtMs must be less than maxRuntimeMs");
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
maxRuntimeMs,
|
|
72
|
+
warnAtMs,
|
|
73
|
+
warningMessage: cleanString(budgetInput.warningMessage, "targetPolicy.runtimeBudget.warningMessage", { max: 4000 }),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizeTargetPolicy(value: unknown): AutomationTargetPolicy {
|
|
78
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new ValidationError("targetPolicy must be an object");
|
|
79
|
+
const input = value as Record<string, unknown>;
|
|
80
|
+
const mode = cleanEnum(input.mode, "targetPolicy.mode", ["existing_agent", "on_demand_agent"] as const);
|
|
81
|
+
if (mode === "existing_agent") {
|
|
82
|
+
const selectorInput = (typeof input.selector === "object" && input.selector !== null && !Array.isArray(input.selector))
|
|
83
|
+
? input.selector as Record<string, unknown>
|
|
84
|
+
: {};
|
|
85
|
+
return {
|
|
86
|
+
mode,
|
|
87
|
+
selector: {
|
|
88
|
+
provider: cleanString(selectorInput.provider, "targetPolicy.selector.provider", { max: 40 }) as "claude" | "codex" | undefined,
|
|
89
|
+
label: cleanString(selectorInput.label, "targetPolicy.selector.label", { max: 120 }),
|
|
90
|
+
tags: cleanStringArray(selectorInput.tags, "targetPolicy.selector.tags", { itemMax: 100 }),
|
|
91
|
+
capabilities: cleanStringArray(selectorInput.capabilities, "targetPolicy.selector.capabilities", { itemMax: 100 }),
|
|
92
|
+
},
|
|
93
|
+
ifNoMatch: optionalEnum(input.ifNoMatch, "targetPolicy.ifNoMatch", ["fail", "spawn"] as const, "fail"),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const provider = optionalEnum(input.provider, "targetPolicy.provider", ["claude", "codex"] as const, "codex");
|
|
97
|
+
const model = cleanString(input.model, "targetPolicy.model", { max: 120 });
|
|
98
|
+
const effort = cleanString(input.effort, "targetPolicy.effort", { max: 120 }) as ProviderEffort | undefined;
|
|
99
|
+
const profile = cleanString(input.profile, "targetPolicy.profile", { max: 120 });
|
|
100
|
+
if (profile && !getAgentProfile(profile)) throw new ValidationError("agent profile not found");
|
|
101
|
+
try {
|
|
102
|
+
resolveProviderSelection({ provider, model, effort }, effectiveProviderCatalogList());
|
|
103
|
+
} catch (error) {
|
|
104
|
+
throw new ValidationError(errMessage(error));
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
mode,
|
|
108
|
+
provider,
|
|
109
|
+
model,
|
|
110
|
+
effort,
|
|
111
|
+
cwd: cleanString(input.cwd, "targetPolicy.cwd", { max: 500 }),
|
|
112
|
+
workspaceMode: optionalEnum(input.workspaceMode, "targetPolicy.workspaceMode", VALID_WORKSPACE_MODES, "inherit"),
|
|
113
|
+
profile,
|
|
114
|
+
approvalMode: optionalEnum(input.approvalMode, "targetPolicy.approvalMode", ["open", "guarded", "read-only"] as const, "guarded"),
|
|
115
|
+
keepAlive: cleanBool(input.keepAlive, "targetPolicy.keepAlive") ?? false,
|
|
116
|
+
runtimeBudget: normalizeRuntimeBudget(input),
|
|
117
|
+
shutdownAfterMs: typeof input.shutdownAfterMs === "number" && Number.isSafeInteger(input.shutdownAfterMs) && input.shutdownAfterMs >= 0
|
|
118
|
+
? input.shutdownAfterMs
|
|
119
|
+
: undefined,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function normalizeCreateInput(input: CreateAutomationInput): Required<Omit<CreateAutomationInput, "description">> & { description?: string } {
|
|
124
|
+
const schedule = cleanString(input.schedule, "schedule", { required: true, max: 120 })!;
|
|
125
|
+
const timezone = cleanString(input.timezone, "timezone", { max: 80 }) ?? DEFAULT_TIMEZONE;
|
|
126
|
+
validateCron(schedule, timezone);
|
|
127
|
+
return {
|
|
128
|
+
kind: AUTOMATION_KIND,
|
|
129
|
+
name: cleanString(input.name, "name", { required: true, max: 160 })!,
|
|
130
|
+
description: cleanString(input.description, "description", { max: 2000 }),
|
|
131
|
+
enabled: cleanBool(input.enabled, "enabled") ?? true,
|
|
132
|
+
schedule,
|
|
133
|
+
timezone,
|
|
134
|
+
catchUpPolicy: optionalEnum(input.catchUpPolicy, "catchUpPolicy", ["skip", "run_once", "run_all"] as const, DEFAULT_CATCH_UP),
|
|
135
|
+
concurrencyPolicy: optionalEnum(input.concurrencyPolicy, "concurrencyPolicy", ["skip", "queue", "replace"] as const, DEFAULT_CONCURRENCY),
|
|
136
|
+
orchestratorId: cleanString(input.orchestratorId, "orchestratorId", { required: true, max: 160 })!,
|
|
137
|
+
targetPolicy: normalizeTargetPolicy(input.targetPolicy),
|
|
138
|
+
taskTemplate: normalizeTaskTemplate(input.taskTemplate),
|
|
139
|
+
};
|
|
140
|
+
}
|