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
package/runner/src/config.ts
CHANGED
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { homedir, hostname } from "node:os";
|
|
4
|
-
import { join, resolve } from "node:path";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { DEFAULT_RELAY_URL, RELAY_TOKEN_HEADER, errMessage, stringValue } from "agent-relay-sdk";
|
|
6
6
|
import type { SettingEntry, WorkspaceMetadata } from "agent-relay-sdk";
|
|
7
7
|
import { sanitizeFsName } from "agent-relay-sdk/fs-name";
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
interface ProviderConfig {
|
|
10
|
+
command: string;
|
|
11
|
+
defaultArgs: string[];
|
|
12
|
+
env: Record<string, string>;
|
|
13
|
+
pluginDirs: string[];
|
|
14
|
+
defaultCapabilities: string[];
|
|
15
|
+
defaultApprovalMode: string;
|
|
16
|
+
defaultTags: string[];
|
|
17
|
+
chatCaptureMode: "final" | "full";
|
|
18
|
+
reasoningCapture?: boolean;
|
|
19
|
+
headless: {
|
|
20
|
+
tmuxPrefix: string;
|
|
21
|
+
shutdownTimeoutMs: number;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
9
24
|
|
|
10
25
|
interface GlobalRunnerConfig {
|
|
11
26
|
relayUrl: string;
|
|
@@ -17,7 +32,7 @@ interface LoadedProviderConfig extends ProviderConfig {
|
|
|
17
32
|
path: string;
|
|
18
33
|
}
|
|
19
34
|
|
|
20
|
-
function agentRelayHome(): string {
|
|
35
|
+
export function agentRelayHome(): string {
|
|
21
36
|
return process.env.AGENT_RELAY_HOME || join(homedir(), ".agent-relay");
|
|
22
37
|
}
|
|
23
38
|
|
|
@@ -48,12 +63,135 @@ export function loadGlobalConfig(home = agentRelayHome()): GlobalRunnerConfig {
|
|
|
48
63
|
const path = join(home, "config.json");
|
|
49
64
|
const parsed = readJson(path);
|
|
50
65
|
return {
|
|
51
|
-
relayUrl: stringValue(parsed.relayUrl) ??
|
|
52
|
-
token: stringValue(parsed.token) ??
|
|
66
|
+
relayUrl: stringValue(parsed.relayUrl) ?? relayUrlFromEnv() ?? DEFAULT_RELAY_URL,
|
|
67
|
+
token: stringValue(parsed.token) ?? relayTokenFromEnv(),
|
|
53
68
|
defaultCwd: stringValue(parsed.defaultCwd) ?? process.cwd(),
|
|
54
69
|
};
|
|
55
70
|
}
|
|
56
71
|
|
|
72
|
+
function relayUrlFromEnv(): string | undefined {
|
|
73
|
+
return process.env.AGENT_RELAY_URL;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function relayTokenFromEnv(): string | undefined {
|
|
77
|
+
return process.env.AGENT_RELAY_TOKEN;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function runtimeTokenProfileFromEnv(): string | undefined {
|
|
81
|
+
return process.env.AGENT_RELAY_TOKEN_PROFILE;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function runtimeTokenJtiFromEnv(): string | undefined {
|
|
85
|
+
return process.env.AGENT_RELAY_TOKEN_JTI;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function runtimeTokenExpiresAtFromEnv(): string | undefined {
|
|
89
|
+
return process.env.AGENT_RELAY_TOKEN_EXPIRES_AT;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function agentProfileNameFromEnv(): string | undefined {
|
|
93
|
+
return process.env.AGENT_RELAY_AGENT_PROFILE;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function agentProfileJsonFromEnv(): string | undefined {
|
|
97
|
+
return process.env.AGENT_RELAY_AGENT_PROFILE_JSON;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function workspaceJsonFromEnv(): string | undefined {
|
|
101
|
+
return process.env.AGENT_RELAY_WORKSPACE_JSON;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function tmuxSessionFromEnv(): string | undefined {
|
|
105
|
+
return process.env.AGENT_RELAY_TMUX_SESSION;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function policyNameFromEnv(): string | undefined {
|
|
109
|
+
return process.env.AGENT_RELAY_POLICY;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function spawnRequestIdFromEnv(): string | undefined {
|
|
113
|
+
return process.env.AGENT_RELAY_SPAWN_REQUEST_ID;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function automationIdFromEnv(): string | undefined {
|
|
117
|
+
return process.env.AGENT_RELAY_AUTOMATION_ID;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function automationRunIdFromEnv(): string | undefined {
|
|
121
|
+
return process.env.AGENT_RELAY_AUTOMATION_RUN_ID;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function lifecycleFromEnv(): string | undefined {
|
|
125
|
+
return process.env.AGENT_RELAY_LIFECYCLE;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function sessionDebugEnabled(): boolean {
|
|
129
|
+
return process.env.AGENT_RELAY_SESSION_DEBUG === "1";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function logLevelFromEnv(): string | undefined {
|
|
133
|
+
return process.env.AGENT_RELAY_LOG_LEVEL;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function mcpProxyEnabledFromEnv(): boolean {
|
|
137
|
+
return !["0", "false", "off"].includes((process.env.AGENT_RELAY_MCP_PROXY ?? "").trim().toLowerCase());
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function runnerInfoFileFromEnv(): string | undefined {
|
|
141
|
+
return process.env.AGENT_RELAY_RUNNER_INFO_FILE;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function runnerOutboxDirFromEnv(): string | undefined {
|
|
145
|
+
return process.env.AGENT_RELAY_RUNNER_OUTBOX_DIR;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function runnerOutboxDirWithInfoFallback(): string | undefined {
|
|
149
|
+
const dir = runnerOutboxDirFromEnv();
|
|
150
|
+
if (dir) return dir;
|
|
151
|
+
const infoFile = runnerInfoFileFromEnv();
|
|
152
|
+
return infoFile ? join(dirname(infoFile), "outbox") : undefined;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function capsFromEnv(): string | undefined {
|
|
156
|
+
return process.env.AGENT_RELAY_CAPS;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function tagsFromEnv(): string | undefined {
|
|
160
|
+
return process.env.AGENT_RELAY_TAGS;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function workspaceModeFromEnv(): string | undefined {
|
|
164
|
+
return process.env.AGENT_RELAY_WORKSPACE_MODE;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function orchestratorBaseDirFromEnv(): string | undefined {
|
|
168
|
+
return process.env.AGENT_RELAY_ORCHESTRATOR_BASE_DIR;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function runnerLogFileFromEnv(): string | undefined {
|
|
172
|
+
return typeof process.env.AGENT_RELAY_LOG_FILE === "string" ? process.env.AGENT_RELAY_LOG_FILE : undefined;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function orchestratorUrlFromEnv(): string | undefined {
|
|
176
|
+
return process.env.AGENT_RELAY_ORCHESTRATOR_URL;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function contextStateDirFromEnv(): string | undefined {
|
|
180
|
+
return process.env.AGENT_RELAY_CONTEXT_STATE_DIR;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function attachmentCacheDirFromEnv(): string | undefined {
|
|
184
|
+
return process.env.AGENT_RELAY_ATTACHMENT_CACHE_DIR;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function providerHomeRootFromEnv(): string {
|
|
188
|
+
return process.env.AGENT_RELAY_PROVIDER_HOME_ROOT || join(homedir(), ".agent-relay", "provider-homes");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function messageBodyMaxCharsFromEnv(): string | undefined {
|
|
192
|
+
return process.env.AGENT_RELAY_MESSAGE_BODY_MAX_CHARS;
|
|
193
|
+
}
|
|
194
|
+
|
|
57
195
|
export function loadProviderConfig(provider: string, home = agentRelayHome()): LoadedProviderConfig {
|
|
58
196
|
const path = join(providersDir(home), `${provider}.json`);
|
|
59
197
|
const raw = readJson(path);
|
package/src/agent-ref.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { STALE_TTL_MS } from "./config";
|
|
15
15
|
import type { AgentCard, Message, TokenConstraints } from "./types";
|
|
16
|
+
import { targetMatchesAgentIdentity } from "agent-relay-sdk/agent-target";
|
|
16
17
|
|
|
17
18
|
interface ResolveOptions {
|
|
18
19
|
/** Exclude this agent id from matches (e.g. the requester, when pairing). */
|
|
@@ -150,12 +151,13 @@ export function resolveAgentRef(ref: string, agents: AgentCard[], opts: ResolveO
|
|
|
150
151
|
|
|
151
152
|
export function targetMatchesAgent(target: string, agentId: string, agent?: AgentCard | null): boolean {
|
|
152
153
|
if (!agent) return false;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
154
|
+
return targetMatchesAgentIdentity(target, {
|
|
155
|
+
id: agentId,
|
|
156
|
+
label: agent.label,
|
|
157
|
+
tags: agent.tags,
|
|
158
|
+
capabilities: agent.capabilities,
|
|
159
|
+
teamId: agent.teamId,
|
|
160
|
+
});
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
export function messageMatchesAgent(msg: Message, agentId: string, agent?: AgentCard | null): boolean {
|
package/src/artifact-storage.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { dirname, join, relative, resolve } from "node:path";
|
|
5
5
|
import { Readable } from "node:stream";
|
|
6
6
|
import { expandTilde } from "./utils";
|
|
7
|
+
import { artifactsDir, maxArtifactBytesFromEnv } from "./config";
|
|
7
8
|
|
|
8
9
|
interface ArtifactStorageBackend {
|
|
9
10
|
readonly scheme: string;
|
|
@@ -20,14 +21,11 @@ interface ArtifactStorageBackend {
|
|
|
20
21
|
const DEFAULT_MAX_ARTIFACT_BYTES = 50 * 1024 * 1024;
|
|
21
22
|
|
|
22
23
|
function artifactRoot(): string {
|
|
23
|
-
return expandTilde(
|
|
24
|
+
return expandTilde(artifactsDir());
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export function maxArtifactBytes(): number {
|
|
27
|
-
|
|
28
|
-
if (!raw) return DEFAULT_MAX_ARTIFACT_BYTES;
|
|
29
|
-
const parsed = Number(raw);
|
|
30
|
-
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_ARTIFACT_BYTES;
|
|
28
|
+
return maxArtifactBytesFromEnv(DEFAULT_MAX_ARTIFACT_BYTES);
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
export function normalizeDigest(digest: string): string {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AutomationCatchUpPolicy, AutomationConcurrencyPolicy, AutomationRunStatus } from "../types";
|
|
2
|
+
|
|
3
|
+
export const AUTOMATION_KIND = "scheduled_task";
|
|
4
|
+
export const DEFAULT_TIMEZONE = "UTC";
|
|
5
|
+
export const DEFAULT_CATCH_UP: AutomationCatchUpPolicy = "skip";
|
|
6
|
+
export const DEFAULT_CONCURRENCY: AutomationConcurrencyPolicy = "skip";
|
|
7
|
+
export const BLOCKING_RUN_STATUSES = new Set<AutomationRunStatus>(["dispatching", "waiting_agent", "running"]);
|
|
8
|
+
export const OPEN_RUN_STATUSES = new Set<AutomationRunStatus>(["scheduled", "dispatching", "waiting_agent", "running"]);
|
|
9
|
+
export const CLOSED_TASK_STATUS = new Set(["done", "failed", "canceled"]);
|
|
10
|
+
export const MAX_CRON_SCAN_MINUTES = 366 * 24 * 60;
|
|
11
|
+
export const MIN_RUNTIME_BUDGET_MS = 60_000;
|
|
12
|
+
export const MAX_RUNTIME_BUDGET_MS = 24 * 60 * 60 * 1000;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { ValidationError } from "../db";
|
|
2
|
+
import { MAX_CRON_SCAN_MINUTES } from "./constants";
|
|
3
|
+
|
|
4
|
+
export function validateCron(expression: string, timezone: string): void {
|
|
5
|
+
parseCron(expression);
|
|
6
|
+
localParts(Date.now(), timezone);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function nextScheduledAt(expression: string, timezone: string, afterMs: number): number {
|
|
10
|
+
const cron = parseCron(expression);
|
|
11
|
+
let cursor = Math.floor(afterMs / 60_000) * 60_000 + 60_000;
|
|
12
|
+
for (let i = 0; i < MAX_CRON_SCAN_MINUTES; i += 1) {
|
|
13
|
+
const parts = localParts(cursor, timezone);
|
|
14
|
+
if (cronMatches(cron, parts)) return cursor;
|
|
15
|
+
cursor += 60_000;
|
|
16
|
+
}
|
|
17
|
+
throw new ValidationError("schedule has no matching time in the next year");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface CronSpec {
|
|
21
|
+
minute: Set<number>;
|
|
22
|
+
hour: Set<number>;
|
|
23
|
+
dayOfMonth: Set<number>;
|
|
24
|
+
month: Set<number>;
|
|
25
|
+
dayOfWeek: Set<number>;
|
|
26
|
+
domAny: boolean;
|
|
27
|
+
dowAny: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface LocalTimeParts {
|
|
31
|
+
minute: number;
|
|
32
|
+
hour: number;
|
|
33
|
+
dayOfMonth: number;
|
|
34
|
+
month: number;
|
|
35
|
+
dayOfWeek: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseCron(expression: string): CronSpec {
|
|
39
|
+
const parts = expression.trim().split(/\s+/);
|
|
40
|
+
if (parts.length !== 5) throw new ValidationError("schedule must be a 5-field cron expression");
|
|
41
|
+
const [minute, hour, dom, month, dow] = parts as [string, string, string, string, string];
|
|
42
|
+
return {
|
|
43
|
+
minute: parseCronField(minute, 0, 59, "minute"),
|
|
44
|
+
hour: parseCronField(hour, 0, 23, "hour"),
|
|
45
|
+
dayOfMonth: parseCronField(dom, 1, 31, "dayOfMonth"),
|
|
46
|
+
month: parseCronField(month, 1, 12, "month"),
|
|
47
|
+
dayOfWeek: parseCronField(dow.replace(/\b7\b/g, "0"), 0, 6, "dayOfWeek"),
|
|
48
|
+
domAny: dom === "*",
|
|
49
|
+
dowAny: dow === "*",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseCronField(field: string, min: number, max: number, name: string): Set<number> {
|
|
54
|
+
const values = new Set<number>();
|
|
55
|
+
for (const rawPart of field.split(",")) {
|
|
56
|
+
const part = rawPart.trim();
|
|
57
|
+
if (!part) throw new ValidationError(`${name} cron field is empty`);
|
|
58
|
+
const [rangePart, stepRaw] = part.split("/");
|
|
59
|
+
const step = stepRaw === undefined ? 1 : Number(stepRaw);
|
|
60
|
+
if (!Number.isInteger(step) || step <= 0) throw new ValidationError(`${name} cron step must be a positive integer`);
|
|
61
|
+
let start: number;
|
|
62
|
+
let end: number;
|
|
63
|
+
if (rangePart === "*") {
|
|
64
|
+
start = min;
|
|
65
|
+
end = max;
|
|
66
|
+
} else if (rangePart?.includes("-")) {
|
|
67
|
+
const [startRaw, endRaw] = rangePart.split("-");
|
|
68
|
+
start = Number(startRaw);
|
|
69
|
+
end = Number(endRaw);
|
|
70
|
+
} else {
|
|
71
|
+
start = Number(rangePart);
|
|
72
|
+
end = start;
|
|
73
|
+
}
|
|
74
|
+
if (!Number.isInteger(start) || !Number.isInteger(end) || start < min || end > max || start > end) {
|
|
75
|
+
throw new ValidationError(`${name} cron field is out of range`);
|
|
76
|
+
}
|
|
77
|
+
for (let value = start; value <= end; value += step) values.add(value);
|
|
78
|
+
}
|
|
79
|
+
return values;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Authoritative localized run date for a dispatched automation task (#480). Formats the
|
|
83
|
+
// scheduled instant in the automation's timezone via the same Intl machinery as cron eval, so
|
|
84
|
+
// scheduled output ("today is ...") never depends on a long-lived agent's stale session context.
|
|
85
|
+
export function formatRunDate(timestamp: number, timezone: string): { iso: string; weekday: string; line: string } {
|
|
86
|
+
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
87
|
+
timeZone: timezone,
|
|
88
|
+
year: "numeric",
|
|
89
|
+
month: "2-digit",
|
|
90
|
+
day: "2-digit",
|
|
91
|
+
weekday: "long",
|
|
92
|
+
});
|
|
93
|
+
const parts = Object.fromEntries(dtf.formatToParts(new Date(timestamp)).map((part) => [part.type, part.value]));
|
|
94
|
+
const iso = `${parts.year}-${parts.month}-${parts.day}`;
|
|
95
|
+
const weekday = parts.weekday ?? "";
|
|
96
|
+
return { iso, weekday, line: `${weekday}, ${iso} (${timezone})` };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function localParts(timestamp: number, timezone: string): LocalTimeParts {
|
|
100
|
+
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
101
|
+
timeZone: timezone,
|
|
102
|
+
minute: "numeric",
|
|
103
|
+
hour: "numeric",
|
|
104
|
+
hourCycle: "h23",
|
|
105
|
+
day: "numeric",
|
|
106
|
+
month: "numeric",
|
|
107
|
+
weekday: "short",
|
|
108
|
+
});
|
|
109
|
+
const parts = Object.fromEntries(dtf.formatToParts(new Date(timestamp)).map((part) => [part.type, part.value]));
|
|
110
|
+
const dow = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].indexOf(parts.weekday ?? "");
|
|
111
|
+
if (dow < 0) throw new ValidationError(`invalid timezone: ${timezone}`);
|
|
112
|
+
return {
|
|
113
|
+
minute: Number(parts.minute),
|
|
114
|
+
hour: Number(parts.hour),
|
|
115
|
+
dayOfMonth: Number(parts.day),
|
|
116
|
+
month: Number(parts.month),
|
|
117
|
+
dayOfWeek: dow,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function cronMatches(cron: CronSpec, parts: LocalTimeParts): boolean {
|
|
122
|
+
if (!cron.minute.has(parts.minute) || !cron.hour.has(parts.hour) || !cron.month.has(parts.month)) return false;
|
|
123
|
+
const domMatch = cron.dayOfMonth.has(parts.dayOfMonth);
|
|
124
|
+
const dowMatch = cron.dayOfWeek.has(parts.dayOfWeek);
|
|
125
|
+
if (cron.domAny && cron.dowAny) return true;
|
|
126
|
+
if (cron.domAny) return dowMatch;
|
|
127
|
+
if (cron.dowAny) return domMatch;
|
|
128
|
+
return domMatch || dowMatch;
|
|
129
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { getOrchestrator, ValidationError } from "../db";
|
|
3
|
+
import { AUTOMATION_KIND } from "./constants";
|
|
4
|
+
import { nextScheduledAt } from "./cron";
|
|
5
|
+
import { db, ensureAutomationTables, getAutomation } from "./storage";
|
|
6
|
+
import { normalizeCreateInput } from "./validation";
|
|
7
|
+
import type { Automation, CreateAutomationInput, UpdateAutomationInput } from "../types";
|
|
8
|
+
|
|
9
|
+
export function createAutomation(input: CreateAutomationInput, now = Date.now()): Automation {
|
|
10
|
+
ensureAutomationTables();
|
|
11
|
+
const normalized = normalizeCreateInput(input);
|
|
12
|
+
if (!getOrchestrator(normalized.orchestratorId)) throw new ValidationError(`orchestrator ${normalized.orchestratorId} not found`);
|
|
13
|
+
const id = randomUUID();
|
|
14
|
+
const nextRunAt = normalized.enabled ? nextScheduledAt(normalized.schedule, normalized.timezone, now) : undefined;
|
|
15
|
+
db().query(`
|
|
16
|
+
INSERT INTO automations (id, kind, name, description, enabled, schedule, timezone, next_run_at, catch_up_policy, concurrency_policy, orchestrator_id, target_policy, task_template, created_at, updated_at)
|
|
17
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
18
|
+
`).run(
|
|
19
|
+
id,
|
|
20
|
+
AUTOMATION_KIND,
|
|
21
|
+
normalized.name,
|
|
22
|
+
normalized.description ?? null,
|
|
23
|
+
normalized.enabled ? 1 : 0,
|
|
24
|
+
normalized.schedule,
|
|
25
|
+
normalized.timezone,
|
|
26
|
+
nextRunAt ?? null,
|
|
27
|
+
normalized.catchUpPolicy,
|
|
28
|
+
normalized.concurrencyPolicy,
|
|
29
|
+
normalized.orchestratorId,
|
|
30
|
+
JSON.stringify(normalized.targetPolicy),
|
|
31
|
+
JSON.stringify(normalized.taskTemplate),
|
|
32
|
+
now,
|
|
33
|
+
now,
|
|
34
|
+
);
|
|
35
|
+
return getAutomation(id)!;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function updateAutomation(id: string, input: UpdateAutomationInput, now = Date.now()): Automation | null {
|
|
39
|
+
ensureAutomationTables();
|
|
40
|
+
const existing = getAutomation(id);
|
|
41
|
+
if (!existing) return null;
|
|
42
|
+
const next: CreateAutomationInput = {
|
|
43
|
+
kind: AUTOMATION_KIND,
|
|
44
|
+
name: input.name ?? existing.name,
|
|
45
|
+
description: input.description === null ? undefined : input.description ?? existing.description,
|
|
46
|
+
enabled: input.enabled ?? existing.enabled,
|
|
47
|
+
schedule: input.schedule ?? existing.schedule,
|
|
48
|
+
timezone: input.timezone ?? existing.timezone,
|
|
49
|
+
catchUpPolicy: input.catchUpPolicy ?? existing.catchUpPolicy,
|
|
50
|
+
concurrencyPolicy: input.concurrencyPolicy ?? existing.concurrencyPolicy,
|
|
51
|
+
orchestratorId: input.orchestratorId ?? existing.orchestratorId,
|
|
52
|
+
targetPolicy: input.targetPolicy ?? existing.targetPolicy,
|
|
53
|
+
taskTemplate: input.taskTemplate ?? existing.taskTemplate,
|
|
54
|
+
};
|
|
55
|
+
const normalized = normalizeCreateInput(next);
|
|
56
|
+
if (!getOrchestrator(normalized.orchestratorId)) throw new ValidationError(`orchestrator ${normalized.orchestratorId} not found`);
|
|
57
|
+
const nextRunAt = normalized.enabled ? nextScheduledAt(normalized.schedule, normalized.timezone, now) : undefined;
|
|
58
|
+
db().query(`
|
|
59
|
+
UPDATE automations
|
|
60
|
+
SET name = ?, description = ?, enabled = ?, schedule = ?, timezone = ?, next_run_at = ?,
|
|
61
|
+
catch_up_policy = ?, concurrency_policy = ?, orchestrator_id = ?, target_policy = ?, task_template = ?, updated_at = ?
|
|
62
|
+
WHERE id = ?
|
|
63
|
+
`).run(
|
|
64
|
+
normalized.name,
|
|
65
|
+
normalized.description ?? null,
|
|
66
|
+
normalized.enabled ? 1 : 0,
|
|
67
|
+
normalized.schedule,
|
|
68
|
+
normalized.timezone,
|
|
69
|
+
nextRunAt ?? null,
|
|
70
|
+
normalized.catchUpPolicy,
|
|
71
|
+
normalized.concurrencyPolicy,
|
|
72
|
+
normalized.orchestratorId,
|
|
73
|
+
JSON.stringify(normalized.targetPolicy),
|
|
74
|
+
JSON.stringify(normalized.taskTemplate),
|
|
75
|
+
now,
|
|
76
|
+
id,
|
|
77
|
+
);
|
|
78
|
+
return getAutomation(id)!;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function deleteAutomation(id: string): boolean {
|
|
82
|
+
ensureAutomationTables();
|
|
83
|
+
return db().query("DELETE FROM automations WHERE id = ?").run(id).changes > 0;
|
|
84
|
+
}
|