agent-relay-server 0.102.2 → 0.103.4
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-BpiTbbnp.js +4 -0
- package/public/assets/{MessageBubble-Dppo7pil.js.map → MessageBubble-BpiTbbnp.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-DO5OY2qa.js → PlanGraphPanel-k8Aa98g5.js} +2 -2
- package/public/assets/{PlanGraphPanel-DO5OY2qa.js.map → PlanGraphPanel-k8Aa98g5.js.map} +1 -1
- package/public/assets/{activity-BhuOs5ha.js → activity-CxKi5Erb.js} +2 -2
- package/public/assets/{activity-BhuOs5ha.js.map → activity-CxKi5Erb.js.map} +1 -1
- package/public/assets/agent-profiles-DMZXdN6K.js +2 -0
- package/public/assets/agent-profiles-DMZXdN6K.js.map +1 -0
- package/public/assets/agents-BrvpH3gn.js +2 -0
- package/public/assets/agents-BrvpH3gn.js.map +1 -0
- package/public/assets/{analytics-BxGRljQc.js → analytics-1fvh-9ok.js} +2 -2
- package/public/assets/{analytics-BxGRljQc.js.map → analytics-1fvh-9ok.js.map} +1 -1
- package/public/assets/automation-BCgX5WOH.js +2 -0
- package/public/assets/automation-BCgX5WOH.js.map +1 -0
- package/public/assets/{branch-state-badge-olhanssg.js → branch-state-badge-DYA7bU0i.js} +2 -2
- package/public/assets/{branch-state-badge-olhanssg.js.map → branch-state-badge-DYA7bU0i.js.map} +1 -1
- package/public/assets/{channels-yHkm1dux.js → channels-CK80oJYG.js} +2 -2
- package/public/assets/{channels-yHkm1dux.js.map → channels-CK80oJYG.js.map} +1 -1
- package/public/assets/chat-D_2PEz06.js +2 -0
- package/public/assets/chat-D_2PEz06.js.map +1 -0
- package/public/assets/{connectors-DpR0V2hy.js → connectors-ChGyht5m.js} +2 -2
- package/public/assets/{connectors-DpR0V2hy.js.map → connectors-ChGyht5m.js.map} +1 -1
- package/public/assets/display-D1xB8NJZ.js +3 -0
- package/public/assets/display-D1xB8NJZ.js.map +1 -0
- package/public/assets/{formatted-body-impl-BTeCfK4I.js → formatted-body-impl-BJ4WCF5J.js} +2 -2
- package/public/assets/{formatted-body-impl-BTeCfK4I.js.map → formatted-body-impl-BJ4WCF5J.js.map} +1 -1
- package/public/assets/index-BMO_Sblt.css +2 -0
- package/public/assets/index-CN7QZlMb.js +23 -0
- package/public/assets/index-CN7QZlMb.js.map +1 -0
- package/public/assets/{insights-C4aYlgsY.js → insights-BCZRV0hX.js} +2 -2
- package/public/assets/{insights-C4aYlgsY.js.map → insights-BCZRV0hX.js.map} +1 -1
- package/public/assets/{integrations-CEmYa5mW.js → integrations-C7UGs00U.js} +2 -2
- package/public/assets/{integrations-CEmYa5mW.js.map → integrations-C7UGs00U.js.map} +1 -1
- package/public/assets/maintenance-ChYno3Fm.js +2 -0
- package/public/assets/{maintenance-DVx3SiC_.js.map → maintenance-ChYno3Fm.js.map} +1 -1
- package/public/assets/{managed-agents-DUyOTdz1.js → managed-agents-DjxZQNIR.js} +4 -4
- package/public/assets/managed-agents-DjxZQNIR.js.map +1 -0
- package/public/assets/{markdown-preview-impl-D3hYfzGD.js → markdown-preview-impl-DFa5HF1H.js} +2 -2
- package/public/assets/{markdown-preview-impl-D3hYfzGD.js.map → markdown-preview-impl-DFa5HF1H.js.map} +1 -1
- package/public/assets/{memory-2IfPgcnN.js → memory-CTw6kadT.js} +2 -2
- package/public/assets/{memory-2IfPgcnN.js.map → memory-CTw6kadT.js.map} +1 -1
- package/public/assets/{messages-CkOwABhw.js → messages-Cjr816D_.js} +2 -2
- package/public/assets/{messages-CkOwABhw.js.map → messages-Cjr816D_.js.map} +1 -1
- package/public/assets/{orchestrators-CEKO3eQ1.js → orchestrators-CdmFKA7K.js} +2 -2
- package/public/assets/{orchestrators-CEKO3eQ1.js.map → orchestrators-CdmFKA7K.js.map} +1 -1
- package/public/assets/overview-DBOnjfpP.js +2 -0
- package/public/assets/{overview-D9BxevAq.js.map → overview-DBOnjfpP.js.map} +1 -1
- package/public/assets/{pairs-CqT4D1uL.js → pairs-BkcdKdF7.js} +2 -2
- package/public/assets/{pairs-CqT4D1uL.js.map → pairs-BkcdKdF7.js.map} +1 -1
- package/public/assets/{projects-C_zwR-Fn.js → projects-ClKbDQB1.js} +2 -2
- package/public/assets/{projects-C_zwR-Fn.js.map → projects-ClKbDQB1.js.map} +1 -1
- package/public/assets/registry-Bsf3RtOO.js +2 -0
- package/public/assets/registry-Bsf3RtOO.js.map +1 -0
- package/public/assets/{security-8a5_4U3S.js → security-Iyu5bhJw.js} +2 -2
- package/public/assets/{security-8a5_4U3S.js.map → security-Iyu5bhJw.js.map} +1 -1
- package/public/assets/{select-s4BE-T62.js → select-Bi6tNJDx.js} +2 -2
- package/public/assets/{select-s4BE-T62.js.map → select-Bi6tNJDx.js.map} +1 -1
- package/public/assets/settings-CbHp2F9r.js +6 -0
- package/public/assets/settings-CbHp2F9r.js.map +1 -0
- package/public/assets/store-zeyDiSKE.js +9 -0
- package/public/assets/store-zeyDiSKE.js.map +1 -0
- package/public/assets/{tasks-Bcr17zR3.js → tasks-CPlf-5Tr.js} +2 -2
- package/public/assets/{tasks-Bcr17zR3.js.map → tasks-CPlf-5Tr.js.map} +1 -1
- package/public/assets/teams-D2uUNhlY.js +2 -0
- package/public/assets/{teams-B_HSkU9T.js.map → teams-D2uUNhlY.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-Ctp4csJ3.js → terminal-viewer-impl-uRzbrlXy.js} +2 -2
- package/public/assets/{terminal-viewer-impl-Ctp4csJ3.js.map → terminal-viewer-impl-uRzbrlXy.js.map} +1 -1
- package/public/assets/types-DorDDvEb.js +2 -0
- package/public/assets/types-DorDDvEb.js.map +1 -0
- package/public/assets/{user-avatar-RliaJcaG.js → user-avatar-GFWMuAGK.js} +2 -2
- package/public/assets/{user-avatar-RliaJcaG.js.map → user-avatar-GFWMuAGK.js.map} +1 -1
- package/public/assets/{work-queue-C0hpmlv8.js → work-queue-B30SNmuq.js} +2 -2
- package/public/assets/{work-queue-C0hpmlv8.js.map → work-queue-B30SNmuq.js.map} +1 -1
- package/public/assets/{workspaces-QXbQx_AG.js → workspaces-CSpmgbPT.js} +3 -3
- package/public/assets/{workspaces-QXbQx_AG.js.map → workspaces-CSpmgbPT.js.map} +1 -1
- package/public/index.html +5 -5
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/runner/plugins/claude/monitors/relay-monitor.provisioned.mjs +238 -16
- package/runner/src/config.ts +8 -3
- package/src/automations/validation.ts +3 -3
- package/src/cli/orchestrator.ts +7 -5
- package/src/cli/provider.ts +26 -10
- package/src/cli/upgrade.ts +5 -4
- package/src/commands-db.ts +1 -1
- package/src/config-store.ts +3 -3
- package/src/config.ts +3 -3
- package/src/context-advisory.ts +3 -3
- package/src/db/provider-quotas.ts +11 -2
- package/src/db/provisioning-registry.ts +49 -61
- package/src/managed-policy.ts +7 -2
- package/src/mcp/tools-spawn.ts +2 -2
- package/src/mcp-team-template-tools.ts +2 -2
- package/src/provider-catalog-store.ts +1 -1
- package/src/recipe-validator.ts +4 -2
- package/src/routes/agent-profiles.ts +2 -2
- package/src/routes/agent-sessions.ts +18 -23
- package/src/routes/provider-config.ts +1 -1
- package/src/spawn-targets.ts +2 -2
- package/src/upgrade.ts +120 -44
- package/src/workspace-phase.ts +1 -0
- package/public/assets/MessageBubble-Dppo7pil.js +0 -4
- package/public/assets/agent-profiles-CT-vbfzd.js +0 -2
- package/public/assets/agent-profiles-CT-vbfzd.js.map +0 -1
- package/public/assets/agents-CLcgi8AD.js +0 -2
- package/public/assets/agents-CLcgi8AD.js.map +0 -1
- package/public/assets/automation-CE1i1zpw.js +0 -2
- package/public/assets/automation-CE1i1zpw.js.map +0 -1
- package/public/assets/chat-DHdFZvAg.js +0 -2
- package/public/assets/chat-DHdFZvAg.js.map +0 -1
- package/public/assets/display-DRiGi1zj.js +0 -3
- package/public/assets/display-DRiGi1zj.js.map +0 -1
- package/public/assets/index-CKhh9XRZ.css +0 -2
- package/public/assets/index-DeDosfVE.js +0 -23
- package/public/assets/index-DeDosfVE.js.map +0 -1
- package/public/assets/maintenance-DVx3SiC_.js +0 -2
- package/public/assets/managed-agents-DUyOTdz1.js.map +0 -1
- package/public/assets/overview-D9BxevAq.js +0 -2
- package/public/assets/registry-BLjwVXCW.js +0 -2
- package/public/assets/registry-BLjwVXCW.js.map +0 -1
- package/public/assets/settings-BR30UpbJ.js +0 -6
- package/public/assets/settings-BR30UpbJ.js.map +0 -1
- package/public/assets/store-DNbkrqbU.js +0 -9
- package/public/assets/store-DNbkrqbU.js.map +0 -1
- package/public/assets/teams-B_HSkU9T.js +0 -2
- package/public/assets/types-Tv67voFu.js +0 -2
- package/public/assets/types-Tv67voFu.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { errMessage, VALID_WORKSPACE_MODES } from "agent-relay-sdk";
|
|
1
|
+
import { errMessage, SPAWN_PROVIDERS, VALID_WORKSPACE_MODES, type SpawnProvider } from "agent-relay-sdk";
|
|
2
2
|
import { resolveProviderSelection, type ProviderEffort } from "agent-relay-sdk/provider-catalog";
|
|
3
3
|
import { getAgentProfile } from "../config-store";
|
|
4
4
|
import { ValidationError } from "../db";
|
|
@@ -85,7 +85,7 @@ function normalizeTargetPolicy(value: unknown): AutomationTargetPolicy {
|
|
|
85
85
|
return {
|
|
86
86
|
mode,
|
|
87
87
|
selector: {
|
|
88
|
-
provider: cleanString(selectorInput.provider, "targetPolicy.selector.provider", { max: 40 }) as
|
|
88
|
+
provider: cleanString(selectorInput.provider, "targetPolicy.selector.provider", { max: 40 }) as SpawnProvider | undefined,
|
|
89
89
|
label: cleanString(selectorInput.label, "targetPolicy.selector.label", { max: 120 }),
|
|
90
90
|
tags: cleanStringArray(selectorInput.tags, "targetPolicy.selector.tags", { itemMax: 100 }),
|
|
91
91
|
capabilities: cleanStringArray(selectorInput.capabilities, "targetPolicy.selector.capabilities", { itemMax: 100 }),
|
|
@@ -93,7 +93,7 @@ function normalizeTargetPolicy(value: unknown): AutomationTargetPolicy {
|
|
|
93
93
|
ifNoMatch: optionalEnum(input.ifNoMatch, "targetPolicy.ifNoMatch", ["fail", "spawn"] as const, "fail"),
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
|
-
const provider = optionalEnum(input.provider, "targetPolicy.provider",
|
|
96
|
+
const provider = optionalEnum(input.provider, "targetPolicy.provider", SPAWN_PROVIDERS, "codex");
|
|
97
97
|
const model = cleanString(input.model, "targetPolicy.model", { max: 120 });
|
|
98
98
|
const effort = cleanString(input.effort, "targetPolicy.effort", { max: 120 }) as ProviderEffort | undefined;
|
|
99
99
|
const profile = cleanString(input.profile, "targetPolicy.profile", { max: 120 });
|
package/src/cli/orchestrator.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { chmodSync, existsSync, mkdirSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { hostname as osHostname, homedir } from "node:os";
|
|
5
5
|
import { dirname, join, resolve } from "node:path";
|
|
6
|
+
import { getAllManifests, getManifest } from "agent-relay-providers";
|
|
6
7
|
import {
|
|
7
8
|
createDaemonPlan,
|
|
8
9
|
detectDaemonEnvironment,
|
|
@@ -28,7 +29,7 @@ export async function handleOrchestratorCommand(args: string[]): Promise<void> {
|
|
|
28
29
|
let bootstrapToken = "";
|
|
29
30
|
let id = sanitizeOrchestratorId(osHostname());
|
|
30
31
|
let baseDir = join(homedir(), "projects");
|
|
31
|
-
let providers = "
|
|
32
|
+
let providers = getAllManifests().map((manifest) => manifest.id).join(",");
|
|
32
33
|
let apiPort = 4860;
|
|
33
34
|
let runtimePrefix = defaultRuntimePrefix();
|
|
34
35
|
let serviceName = "agent-relay-orchestrator";
|
|
@@ -65,9 +66,9 @@ export async function handleOrchestratorCommand(args: string[]): Promise<void> {
|
|
|
65
66
|
|
|
66
67
|
const providerList = providers.split(",").map((p) => p.trim()).filter(Boolean);
|
|
67
68
|
for (const provider of providerList) {
|
|
68
|
-
if (provider
|
|
69
|
+
if (!getManifest(provider)) throw new Error(`Unknown provider "${provider}". Expected one of ${getAllManifests().map((manifest) => manifest.id).join(", ")}.`);
|
|
69
70
|
}
|
|
70
|
-
const detectedPathPrefix = [...pathPrefix, ...detectProviderPathPrefixes(providerList
|
|
71
|
+
const detectedPathPrefix = [...pathPrefix, ...detectProviderPathPrefixes(providerList)];
|
|
71
72
|
const uniquePathPrefix = [...new Set(detectedPathPrefix)];
|
|
72
73
|
const envToken = token || (dryRun || json ? "<runtime-token-from-bootstrap>" : await exchangeOrchestratorBootstrapToken(relayUrl, bootstrapToken, id, baseDir));
|
|
73
74
|
const envFile = defaultOrchestratorEnvFile();
|
|
@@ -165,10 +166,11 @@ function sanitizeOrchestratorId(value: string): string {
|
|
|
165
166
|
return clean.slice(0, 120);
|
|
166
167
|
}
|
|
167
168
|
|
|
168
|
-
function detectProviderPathPrefixes(providers:
|
|
169
|
+
function detectProviderPathPrefixes(providers: string[]): string[] {
|
|
169
170
|
const prefixes: string[] = [];
|
|
170
171
|
for (const provider of providers) {
|
|
171
|
-
const
|
|
172
|
+
const command = getManifest(provider)?.probe?.command ?? provider;
|
|
173
|
+
const path = resolveExecutableOnPath(command);
|
|
172
174
|
if (path) prefixes.push(dirname(path));
|
|
173
175
|
}
|
|
174
176
|
return prefixes;
|
package/src/cli/provider.ts
CHANGED
|
@@ -5,16 +5,19 @@
|
|
|
5
5
|
import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { dirname, join } from "node:path";
|
|
8
|
+
import { getAllManifests, getManifest } from "agent-relay-providers";
|
|
8
9
|
|
|
9
10
|
export async function handleProviderCommand(args: string[]): Promise<void> {
|
|
10
11
|
const action = args[0];
|
|
11
12
|
const provider = args[1];
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
const providerList = shimmableProviderIds();
|
|
14
|
+
const manifest = typeof provider === "string" ? getManifest(provider) : undefined;
|
|
15
|
+
if ((action !== "wrap" && action !== "unwrap") || !provider || !manifest?.shim) {
|
|
16
|
+
throw new Error(`Usage: agent-relay provider <wrap|unwrap> <${providerList.join("|")}>`);
|
|
14
17
|
}
|
|
15
18
|
const dir = join(process.env.HOME || homedir(), ".agent-relay", "bin");
|
|
16
19
|
const shims = providerShimPaths(provider);
|
|
17
|
-
if (
|
|
20
|
+
if (manifest.shim.cleanLegacyHooks) cleanLegacyCodexSessionStartHook();
|
|
18
21
|
if (action === "wrap") {
|
|
19
22
|
for (const shim of shims) {
|
|
20
23
|
mkdirSync(dirname(shim), { recursive: true });
|
|
@@ -31,6 +34,12 @@ export async function handleProviderCommand(args: string[]): Promise<void> {
|
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
36
|
|
|
37
|
+
function shimmableProviderIds(): string[] {
|
|
38
|
+
return getAllManifests()
|
|
39
|
+
.filter((manifest) => manifest.shim)
|
|
40
|
+
.map((manifest) => manifest.id);
|
|
41
|
+
}
|
|
42
|
+
|
|
34
43
|
function cleanLegacyCodexSessionStartHook(): void {
|
|
35
44
|
const configPath = join(process.env.HOME || homedir(), ".codex", "config.toml");
|
|
36
45
|
if (!existsSync(configPath)) return;
|
|
@@ -111,16 +120,23 @@ function isLegacyCodexSessionStartHook(text: string): boolean {
|
|
|
111
120
|
/agent-relay.*(SessionStart|UserPromptSubmit|Stop).*hook/i.test(text);
|
|
112
121
|
}
|
|
113
122
|
|
|
114
|
-
function providerShimPaths(provider:
|
|
123
|
+
function providerShimPaths(provider: string): string[] {
|
|
124
|
+
const manifest = getManifest(provider);
|
|
125
|
+
if (!manifest?.shim) throw new Error(`Provider "${provider}" does not support PATH shims.`);
|
|
115
126
|
const root = join(process.env.HOME || homedir(), ".agent-relay");
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
127
|
+
return [
|
|
128
|
+
join(root, "bin", provider),
|
|
129
|
+
...(manifest.shim.extraBinPaths ?? []).map((path: string) => join(root, path)),
|
|
130
|
+
];
|
|
119
131
|
}
|
|
120
132
|
|
|
121
|
-
function providerShimContent(provider:
|
|
122
|
-
|
|
123
|
-
|
|
133
|
+
function providerShimContent(provider: string): string {
|
|
134
|
+
const shimKind = getManifest(provider)?.shim?.kind;
|
|
135
|
+
if (shimKind === "claude-relay") {
|
|
136
|
+
return `#!/usr/bin/env bash\nexec claude-relay ${provider} -- "$@"\n`;
|
|
137
|
+
}
|
|
138
|
+
if (shimKind !== "codex-relay") {
|
|
139
|
+
throw new Error(`Provider "${provider}" does not define a shim template.`);
|
|
124
140
|
}
|
|
125
141
|
return `#!/usr/bin/env bash
|
|
126
142
|
set -e
|
package/src/cli/upgrade.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Upgrade commands — auto-split from cli.ts (#294). Local runtime upgrade plus
|
|
2
2
|
// remote-host fan-out over the relay command bus (#210).
|
|
3
|
+
import { getAllManifests, getManifest } from "agent-relay-providers";
|
|
3
4
|
import {
|
|
4
5
|
createUpgradePlan,
|
|
5
6
|
detectUpgradeSnapshot,
|
|
@@ -27,6 +28,7 @@ export async function handleUpgradeCommand(args: string[]): Promise<void> {
|
|
|
27
28
|
|
|
28
29
|
for (let i = 0; i < args.length; i++) {
|
|
29
30
|
const arg = args[i];
|
|
31
|
+
const providerFlag = arg?.startsWith("--") ? arg.slice(2) : undefined;
|
|
30
32
|
if (arg === "--version" && i + 1 < args.length) targetVersion = args[++i];
|
|
31
33
|
else if (arg === "--runtime-prefix" && i + 1 < args.length) runtimePrefix = args[++i];
|
|
32
34
|
else if (arg === "--providers" && i + 1 < args.length) {
|
|
@@ -34,8 +36,7 @@ export async function handleUpgradeCommand(args: string[]): Promise<void> {
|
|
|
34
36
|
} else if (arg === "--provider" && i + 1 < args.length) providers.push(parseUpgradeProvider(args[++i]!));
|
|
35
37
|
else if (arg === "--host" && i + 1 < args.length) hosts.push(args[++i]!);
|
|
36
38
|
else if (arg === "--all-hosts") allHosts = true;
|
|
37
|
-
else if (
|
|
38
|
-
else if (arg === "--claude") providers.push("claude");
|
|
39
|
+
else if (providerFlag && getManifest(providerFlag)) providers.push(parseUpgradeProvider(providerFlag));
|
|
39
40
|
else if (arg === "--orchestrator") providers.push("orchestrator");
|
|
40
41
|
else if (arg === "--all") providers.push("all");
|
|
41
42
|
else if (arg === "--dry-run") dryRun = true;
|
|
@@ -188,6 +189,6 @@ async function runRemoteOrchestratorUpgrades(opts: {
|
|
|
188
189
|
}
|
|
189
190
|
|
|
190
191
|
function parseUpgradeProvider(value: string): UpgradeProvider {
|
|
191
|
-
if (value === "auto" || value === "all" || value === "
|
|
192
|
-
throw new Error(`Unknown upgrade provider "${value}". Expected auto, all,
|
|
192
|
+
if (value === "auto" || value === "all" || value === "orchestrator" || getManifest(value)) return value;
|
|
193
|
+
throw new Error(`Unknown upgrade provider "${value}". Expected auto, all, orchestrator, or one of ${getAllManifests().map((manifest) => manifest.id).join(", ")}.`);
|
|
193
194
|
}
|
package/src/commands-db.ts
CHANGED
|
@@ -131,7 +131,7 @@ export function expireCommands(now: number = Date.now()): Command[] {
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
function defaultExpiresAt(type: string, now: number): number | undefined {
|
|
134
|
-
if (type === "agent.spawn") return now +
|
|
134
|
+
if (type === "agent.spawn") return now + 180_000;
|
|
135
135
|
if (type === "agent.shutdown" || type === "agent.restart" || type === "agent.reconnect" || type === "agent.kill" || type === "agent.compact" || type === "agent.clearContext" || type === "agent.injectContext") return now + 30_000;
|
|
136
136
|
return undefined;
|
|
137
137
|
}
|
package/src/config-store.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { PROJECT_SETTINGS_POLICY_NAMES } from "./project-settings";
|
|
|
16
16
|
import { PROJECT_SKILLS_POLICY_NAMES } from "./project-skills";
|
|
17
17
|
import { SHARED_MCP_POLICY_NAMES } from "./shared-mcp";
|
|
18
18
|
import { resolveProviderSelection, type ProviderEffort } from "agent-relay-sdk/provider-catalog";
|
|
19
|
-
import { errMessage, isRecord, SPAWN_PROVIDERS, VALID_WORKSPACE_MODES } from "agent-relay-sdk"; import { effectiveProviderCatalogList } from "./provider-catalog-store";
|
|
19
|
+
import { errMessage, isRecord, isSpawnProvider, SPAWN_PROVIDERS, VALID_WORKSPACE_MODES } from "agent-relay-sdk"; import { effectiveProviderCatalogList } from "./provider-catalog-store";
|
|
20
20
|
import type {
|
|
21
21
|
AgentProfile,
|
|
22
22
|
AgentProfileBase,
|
|
@@ -50,7 +50,7 @@ const WORKSPACE_KEY = "default";
|
|
|
50
50
|
const LANDING_NAMESPACE = "landing";
|
|
51
51
|
const LANDING_KEY = "default";
|
|
52
52
|
const VALID_LANDING_STRATEGIES = ["direct", "pr"] as const;
|
|
53
|
-
const VALID_PROFILE_PROVIDERS = ["any",
|
|
53
|
+
const VALID_PROFILE_PROVIDERS = ["any", ...SPAWN_PROVIDERS] as const;
|
|
54
54
|
const VALID_PROFILE_BASES = ["host", "minimal", "isolated", "vanilla"] as const;
|
|
55
55
|
const VALID_PROFILE_INSTRUCTION_POLICIES = ["allow", "ignore"] as const;
|
|
56
56
|
const VALID_PROFILE_CATEGORY_MODES = ["host", "profile", "repo", "none"] as const;
|
|
@@ -957,7 +957,7 @@ function listManagedAgentStates(): ManagedAgentState[] {
|
|
|
957
957
|
|
|
958
958
|
export function upsertManagedAgentState(input: ManagedAgentStateInput): ManagedAgentState {
|
|
959
959
|
if (!VALID_MANAGED_STATUSES.includes(input.status)) throw new ValidationError("status must be a managed-agent status");
|
|
960
|
-
if (!
|
|
960
|
+
if (!isSpawnProvider(input.provider)) throw new ValidationError(`provider must be one of: ${SPAWN_PROVIDERS.join(", ")}`);
|
|
961
961
|
const now = input.updatedAt ?? Date.now();
|
|
962
962
|
getDb().query(`
|
|
963
963
|
INSERT INTO managed_agent_state (
|
package/src/config.ts
CHANGED
|
@@ -48,9 +48,9 @@ function envTrimmedOrUndefined(name: string): string | undefined {
|
|
|
48
48
|
|
|
49
49
|
export const STALE_TTL_MS = envPositiveInt("STALE_TTL_MS", 120_000); // 2min without heartbeat → offline
|
|
50
50
|
// Backstop for agent-initiated spawns whose orchestrator command succeeded but whose runner never
|
|
51
|
-
// registered with Relay. Keep this above the
|
|
52
|
-
//
|
|
53
|
-
export const SPAWN_REGISTRATION_DEADLINE_MS = envPositiveInt("AGENT_RELAY_SPAWN_REGISTRATION_DEADLINE_MS",
|
|
51
|
+
// registered with Relay. Keep this above the command TTL (180s) so the command's direct failure
|
|
52
|
+
// path wins first; this is the relay-side dead-man switch.
|
|
53
|
+
export const SPAWN_REGISTRATION_DEADLINE_MS = envPositiveInt("AGENT_RELAY_SPAWN_REGISTRATION_DEADLINE_MS", 300_000);
|
|
54
54
|
export const SPAWN_REGISTRATION_NOTIFY_MAX_AGE_MS = envPositiveInt("AGENT_RELAY_SPAWN_REGISTRATION_NOTIFY_MAX_AGE_MS", 15 * 60_000);
|
|
55
55
|
// #451 — default TTL for fire-and-forget FAN-OUT messages (broadcast/tag/cap/label/team) that the
|
|
56
56
|
// sender didn't bound. A stale fan-out push must not wake an agent that matches the target hours
|
package/src/context-advisory.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { deriveContextThresholdFromWindowTokens, findProviderCatalogModel, resolveProviderContextThreshold, type ProviderModelCatalogEntry, type ResolvedProviderContextThreshold } from "agent-relay-sdk/provider-catalog";
|
|
2
|
-
import { isRecord,
|
|
2
|
+
import { isRecord, isSpawnProvider } from "agent-relay-sdk";
|
|
3
3
|
import { getConfig } from "./config-store";
|
|
4
4
|
import { validateContextThresholdConfig } from "./context-threshold-config";
|
|
5
5
|
import { getResolvedNotificationsConfig } from "./notification-settings-store";
|
|
@@ -170,8 +170,8 @@ function hasExplicitContextThreshold(agent: AgentCard): boolean {
|
|
|
170
170
|
|
|
171
171
|
function findAgentCatalogModel(agent: AgentCard, observedWindowTokens: number | undefined): ProviderModelCatalogEntry | undefined {
|
|
172
172
|
const runtime = agent.providerCapabilities?.model;
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
if (!runtime || !isSpawnProvider(runtime.provider)) return undefined;
|
|
174
|
+
const provider = runtime.provider;
|
|
175
175
|
const catalog = effectiveProviderCatalogList().find((entry) => entry.provider === provider);
|
|
176
176
|
if (!catalog) return undefined;
|
|
177
177
|
const ids = [runtime.alias, runtime.id, runtime.providerModel].filter((value): value is string => Boolean(value));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { DAY_MS, QUOTA_HISTORY_RETENTION_MS } from "../config";
|
|
2
|
-
import {
|
|
3
|
+
import { quotaAccountKeyAliases, registerAccountKeyAliases } from "agent-relay-providers";
|
|
3
4
|
import { parseJson } from "../utils";
|
|
4
5
|
import { getDb } from "./connection.ts";
|
|
5
6
|
import { deriveProviderQuotaBurn } from "./provider-quota-burn.ts";
|
|
@@ -12,6 +13,14 @@ const QUOTA_RESET_BOUNDARY_ADVANCE_MS = 5_000;
|
|
|
12
13
|
const QUOTA_RESET_UTILIZATION_MAX = 0.01;
|
|
13
14
|
const FIVE_HOUR_MS = 5 * 60 * 60_000;
|
|
14
15
|
|
|
16
|
+
registerAccountKeyAliases("codex", (accountKey) => {
|
|
17
|
+
if (accountKey.startsWith("account:")) {
|
|
18
|
+
const hash = createHash("sha256").update(accountKey.slice("account:".length)).digest("hex").slice(0, 16);
|
|
19
|
+
return [`credential:${hash}`];
|
|
20
|
+
}
|
|
21
|
+
return [];
|
|
22
|
+
});
|
|
23
|
+
|
|
15
24
|
export interface ProviderQuotaAdvisoryInput {
|
|
16
25
|
provider: string;
|
|
17
26
|
accountKey: string;
|
|
@@ -101,7 +110,7 @@ function quotaRowsForProvider(provider: string): ProviderQuotaRow[] {
|
|
|
101
110
|
function supersededAccountKeys(provider: string, accountKey: string, rawAccountKey: string, sourceAgentId: string | undefined): string[] {
|
|
102
111
|
const candidates = new Set<string>();
|
|
103
112
|
if (rawAccountKey && rawAccountKey !== accountKey) candidates.add(rawAccountKey);
|
|
104
|
-
|
|
113
|
+
for (const alias of quotaAccountKeyAliases(provider, accountKey)) candidates.add(alias);
|
|
105
114
|
const host = hostFromAccountKey(accountKey) ?? sourceAgentHost(sourceAgentId);
|
|
106
115
|
if (host) {
|
|
107
116
|
candidates.add(`host:${host}:${provider}`);
|
|
@@ -2,9 +2,12 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { Buffer } from "node:buffer";
|
|
3
3
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
4
4
|
import { basename, join, posix, resolve } from "node:path";
|
|
5
|
+
import "agent-relay-providers";
|
|
6
|
+
import { getManifest } from "agent-relay-providers/registry";
|
|
7
|
+
import type { ProviderManifest } from "agent-relay-providers/types";
|
|
5
8
|
import { getDb, ValidationError } from "./connection.ts";
|
|
6
9
|
import { parseJson } from "../utils";
|
|
7
|
-
import { errMessage, isRecord } from "agent-relay-sdk";
|
|
10
|
+
import { errMessage, isRecord, SPAWN_PROVIDERS } from "agent-relay-sdk";
|
|
8
11
|
import type {
|
|
9
12
|
AgentProfile,
|
|
10
13
|
AgentProfileAssetRef,
|
|
@@ -30,16 +33,14 @@ import type {
|
|
|
30
33
|
// this stays the single home for asset definition + resolution — not three modules.
|
|
31
34
|
|
|
32
35
|
const PROVISIONING_ASSET_KINDS = ["skill", "mcp", "plugin"] as const;
|
|
33
|
-
const PROVISIONING_PROVIDERS = [
|
|
36
|
+
const PROVISIONING_PROVIDERS = [...SPAWN_PROVIDERS, "any"];
|
|
34
37
|
const GITHUB_API = "https://api.github.com";
|
|
35
38
|
const DEFAULT_MAX_FILE_BYTES = 256 * 1024;
|
|
36
39
|
const DEFAULT_MAX_TOTAL_BYTES = 2 * 1024 * 1024;
|
|
37
40
|
const ENV_PLACEHOLDER = /^\$?\{?[A-Z_][A-Z0-9_]*\}?$/;
|
|
38
41
|
const BUNDLED_SOURCE_TYPE = "bundled";
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const BUNDLED_CLAUDE_PLUGIN_DIR = resolve(import.meta.dir, "../../runner/plugins/claude");
|
|
42
|
-
const BUNDLED_CODEX_SKILLS_DIR = resolve(import.meta.dir, "../../runner/plugins/codex/skills");
|
|
42
|
+
|
|
43
|
+
type ProviderBundledSkillSetDescriptor = NonNullable<NonNullable<ProviderManifest["provisioning"]>["bundledSkills"]>;
|
|
43
44
|
|
|
44
45
|
interface ProvisioningAssetRow {
|
|
45
46
|
id: string;
|
|
@@ -150,6 +151,8 @@ interface BundledFile {
|
|
|
150
151
|
size: number;
|
|
151
152
|
}
|
|
152
153
|
|
|
154
|
+
type BundledFileReadOptions = { omitFiles?: string[]; fileOverrides?: Array<{ path: string; sourcePath: string }> };
|
|
155
|
+
|
|
153
156
|
export interface ProvisioningCapabilityWithVariants {
|
|
154
157
|
capability: ProvisioningCapability;
|
|
155
158
|
variants: ProvisioningAsset[];
|
|
@@ -345,11 +348,11 @@ function parseJsonFile(file: VendoredFile, label: string): Record<string, unknow
|
|
|
345
348
|
}
|
|
346
349
|
|
|
347
350
|
function assertPlugin(files: VendoredFile[], provider: AgentProfileProvider): void {
|
|
348
|
-
const manifests =
|
|
349
|
-
{ provider:
|
|
350
|
-
|
|
351
|
-
];
|
|
351
|
+
const manifests = SPAWN_PROVIDERS.flatMap((manifestProvider) => getManifest(manifestProvider)?.provisioning?.pluginManifestPath
|
|
352
|
+
? [{ provider: manifestProvider, path: getManifest(manifestProvider)!.provisioning!.pluginManifestPath! }]
|
|
353
|
+
: []);
|
|
352
354
|
const expected = provider === "any" ? manifests : manifests.filter((m) => m.provider === provider);
|
|
355
|
+
if (!expected.length) throw new ValidationError(`plugin import has no manifest descriptor configured for provider ${provider}`);
|
|
353
356
|
const present = expected.filter((manifest) => files.some((file) => file.path === manifest.path));
|
|
354
357
|
if (!present.length) throw new ValidationError(`plugin import requires ${expected.map((m) => m.path).join(" or ")}`);
|
|
355
358
|
for (const manifest of present) {
|
|
@@ -455,22 +458,22 @@ function fileProvenance(files: VendoredFile[]): Array<{ path: string; sha: strin
|
|
|
455
458
|
return files.map(({ path, sha, size }) => ({ path, sha, size }));
|
|
456
459
|
}
|
|
457
460
|
|
|
458
|
-
function readBundledFiles(root: string): BundledFile[] {
|
|
461
|
+
function readBundledFiles(root: string, options: BundledFileReadOptions = {}): BundledFile[] {
|
|
459
462
|
if (!existsSync(root)) return [];
|
|
460
463
|
const files: BundledFile[] = [];
|
|
464
|
+
const omitted = new Set(options.omitFiles ?? []);
|
|
465
|
+
const overrides = new Map((options.fileOverrides ?? []).map((item) => [item.path, item.sourcePath]));
|
|
461
466
|
const visit = (dir: string, prefix = "") => {
|
|
462
467
|
const entries = readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
463
468
|
for (const entry of entries) {
|
|
464
469
|
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
465
470
|
const cleanRel = cleanRelativePath(rel, "file path");
|
|
466
|
-
if (
|
|
471
|
+
if (omitted.has(cleanRel)) continue;
|
|
467
472
|
const abs = join(dir, entry.name);
|
|
468
|
-
if (entry.isDirectory()) {
|
|
469
|
-
visit(abs, rel);
|
|
470
|
-
continue;
|
|
471
|
-
}
|
|
473
|
+
if (entry.isDirectory()) { visit(abs, rel); continue; }
|
|
472
474
|
if (!entry.isFile()) continue;
|
|
473
|
-
const
|
|
475
|
+
const overrideSource = overrides.get(cleanRel);
|
|
476
|
+
const content = overrideSource ? readFileSync(join(root, overrideSource), "utf8") : readFileSync(abs, "utf8");
|
|
474
477
|
files.push({ path: cleanRel, content, size: Buffer.byteLength(content, "utf8") });
|
|
475
478
|
}
|
|
476
479
|
};
|
|
@@ -507,51 +510,35 @@ function upsertBundledProvisioningAsset(input: {
|
|
|
507
510
|
description: string;
|
|
508
511
|
}): void {
|
|
509
512
|
if (!input.files.length) return;
|
|
510
|
-
const definition = {
|
|
511
|
-
kind: input.kind,
|
|
512
|
-
name: input.name,
|
|
513
|
-
files: input.files.map(({ path, content }) => ({ path, content })),
|
|
514
|
-
} as SkillAssetDefinition | PluginAssetDefinition;
|
|
513
|
+
const definition = { kind: input.kind, name: input.name, files: input.files.map(({ path, content }) => ({ path, content })) } as SkillAssetDefinition | PluginAssetDefinition;
|
|
515
514
|
assertDefinition(input.kind, definition);
|
|
516
|
-
const asset = upsertProvisioningAsset({
|
|
517
|
-
kind: input.kind,
|
|
518
|
-
name: input.name,
|
|
519
|
-
provider: input.provider,
|
|
520
|
-
definition,
|
|
521
|
-
});
|
|
515
|
+
const asset = upsertProvisioningAsset({ kind: input.kind, name: input.name, provider: input.provider, definition });
|
|
522
516
|
updateBundledAssetMetadata(asset, input.root, input.files, input.title, input.description);
|
|
523
517
|
}
|
|
524
518
|
|
|
525
|
-
function
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
.sort();
|
|
519
|
+
function bundledSkillRefs(descriptor: ProviderBundledSkillSetDescriptor): string[] {
|
|
520
|
+
const skillsDir = resolve(import.meta.dir, "../../", descriptor.dir);
|
|
521
|
+
const entryFile = descriptor.entryFile ?? "SKILL.md";
|
|
522
|
+
if (!existsSync(skillsDir)) return [];
|
|
523
|
+
return readdirSync(skillsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync(join(skillsDir, entry.name, entryFile)))
|
|
524
|
+
.map((entry) => `${descriptor.refPrefix}${entry.name}`).sort();
|
|
531
525
|
}
|
|
532
526
|
|
|
533
527
|
export function seedBundledProvisioningAssets(): void {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
name: ref,
|
|
549
|
-
provider: "codex",
|
|
550
|
-
root: `runner/plugins/codex/skills/${skillName}`,
|
|
551
|
-
files: readBundledFiles(join(BUNDLED_CODEX_SKILLS_DIR, skillName)),
|
|
552
|
-
title: `Agent Relay Codex skill: ${skillName}`,
|
|
553
|
-
description: "Bundled Agent Relay Codex skill, materialized through the provisioning registry.",
|
|
554
|
-
});
|
|
528
|
+
for (const provider of SPAWN_PROVIDERS) {
|
|
529
|
+
const provisioning = getManifest(provider)?.provisioning;
|
|
530
|
+
if (!provisioning) continue;
|
|
531
|
+
if (provisioning.bundledPlugin) {
|
|
532
|
+
const plugin = provisioning.bundledPlugin;
|
|
533
|
+
upsertBundledProvisioningAsset({ kind: "plugin", name: plugin.ref, provider, root: plugin.dir, files: readBundledFiles(resolve(import.meta.dir, "../../", plugin.dir), { omitFiles: plugin.omitFiles, fileOverrides: plugin.fileOverrides }), title: plugin.title, description: plugin.description });
|
|
534
|
+
}
|
|
535
|
+
if (!provisioning.bundledSkills) continue;
|
|
536
|
+
const skills = provisioning.bundledSkills;
|
|
537
|
+
const skillsDir = resolve(import.meta.dir, "../../", skills.dir);
|
|
538
|
+
for (const ref of bundledSkillRefs(skills)) {
|
|
539
|
+
const skillName = ref.slice(skills.refPrefix.length);
|
|
540
|
+
upsertBundledProvisioningAsset({ kind: "skill", name: ref, provider, root: posix.join(skills.dir, skillName), files: readBundledFiles(join(skillsDir, skillName)), title: `${skills.titlePrefix}${skillName}`, description: skills.description });
|
|
541
|
+
}
|
|
555
542
|
}
|
|
556
543
|
}
|
|
557
544
|
|
|
@@ -1034,7 +1021,7 @@ function resolveRegistryDefinition(kind: ProvisioningAssetKind, ref: string, pro
|
|
|
1034
1021
|
.all(capability.id, provider) as ProvisioningAssetRow[];
|
|
1035
1022
|
const providerCandidates = rows.filter((row) => kind === "mcp" ? row.provider === "any" : row.provider === provider || row.provider === "any");
|
|
1036
1023
|
if (!providerCandidates.length) {
|
|
1037
|
-
const label = provider
|
|
1024
|
+
const label = getManifest(provider)?.label ?? provider;
|
|
1038
1025
|
return { definition: null, skip: skip(kind, ref, provider, "provider_mismatch", `No ${label} variant exists for capability ${ref}.`) };
|
|
1039
1026
|
}
|
|
1040
1027
|
|
|
@@ -1059,13 +1046,14 @@ function hasAssetRef(refs: AgentProfileAssetRef[], source: AgentProfileAssetRef[
|
|
|
1059
1046
|
function relayBundledProvisioningRefs(profile: AgentProfile, provider: SpawnProvider): Pick<AgentProfile, "skills" | "plugins"> {
|
|
1060
1047
|
const skills = [...(profile.skills ?? [])];
|
|
1061
1048
|
const plugins = [...(profile.plugins ?? [])];
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1049
|
+
const provisioning = getManifest(provider)?.provisioning;
|
|
1050
|
+
if (profile.relay.skills !== false && provisioning?.bundledSkills) {
|
|
1051
|
+
for (const ref of bundledSkillRefs(provisioning.bundledSkills)) {
|
|
1052
|
+
if (!hasAssetRef(skills, "relay", ref)) skills.push({ source: "relay", ref, enabled: true, provider });
|
|
1065
1053
|
}
|
|
1066
1054
|
}
|
|
1067
|
-
if (
|
|
1068
|
-
plugins.push({ source: "relay", ref:
|
|
1055
|
+
if (profile.relay.plugins !== false && provisioning?.bundledPlugin && !hasAssetRef(plugins, "relay", provisioning.bundledPlugin.ref)) {
|
|
1056
|
+
plugins.push({ source: "relay", ref: provisioning.bundledPlugin.ref, enabled: true, provider });
|
|
1069
1057
|
}
|
|
1070
1058
|
return { skills, plugins };
|
|
1071
1059
|
}
|
package/src/managed-policy.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getManifest } from "agent-relay-providers";
|
|
1
2
|
import { getAgentProfile } from "./config-store";
|
|
2
3
|
import { withResolvedProvisioning } from "./db";
|
|
3
4
|
import { runnerRuntimeTokenEnv } from "./runtime-tokens";
|
|
@@ -5,15 +6,19 @@ import { buildSpawnCommand, resolveSpawnModelParams } from "./spawn-command";
|
|
|
5
6
|
import { applyDefaultSharedMcp } from "./shared-mcp";
|
|
6
7
|
import type { SpawnPolicy, WorkspaceMode } from "./types";
|
|
7
8
|
|
|
9
|
+
function managedPolicyPromptMode(policy: SpawnPolicy): "prompt" | "append-system-prompt" {
|
|
10
|
+
return getManifest(policy.provider)?.launch?.managedPolicyPrompt?.alwaysOn ?? "prompt";
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
export function managedPolicyProviderArgs(policy: SpawnPolicy): string[] {
|
|
9
|
-
if (policy.
|
|
14
|
+
if (policy.mode === "always-on" && policy.prompt?.trim() && managedPolicyPromptMode(policy) === "append-system-prompt") {
|
|
10
15
|
return [...policy.providerArgs, "--append-system-prompt", policy.prompt.trim()];
|
|
11
16
|
}
|
|
12
17
|
return policy.providerArgs;
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
export function managedPolicyLaunchPrompt(policy: SpawnPolicy): string | undefined {
|
|
16
|
-
if (policy.
|
|
21
|
+
if (policy.mode === "always-on" && managedPolicyPromptMode(policy) === "append-system-prompt") return undefined;
|
|
17
22
|
return policy.prompt;
|
|
18
23
|
}
|
|
19
24
|
|
package/src/mcp/tools-spawn.ts
CHANGED
|
@@ -71,7 +71,7 @@ export const SPAWN_TOOLS: ToolDefinition[] = [
|
|
|
71
71
|
required: ["branch", "mode"],
|
|
72
72
|
additionalProperties: false,
|
|
73
73
|
},
|
|
74
|
-
waitForRegistrationMs: { type: "integer", minimum: 0, maximum: 30000, description: "How long to wait for the spawned agent to register before returning, so the response carries its resolved agent id (default
|
|
74
|
+
waitForRegistrationMs: { type: "integer", minimum: 0, maximum: 30000, description: "How long to wait for the spawned agent to register before returning, so the response carries its resolved agent id (default 0 = fire-and-forget; you will be PUSHED agent.ready when the child is ready)." },
|
|
75
75
|
},
|
|
76
76
|
required: ["provider"],
|
|
77
77
|
additionalProperties: false,
|
|
@@ -158,7 +158,7 @@ export async function relaySpawnAgent(auth: McpAuthContext, args: Record<string,
|
|
|
158
158
|
providerArgs: optionalStringArray(args.providerArgs, "providerArgs"),
|
|
159
159
|
spawnRequestId: optionalString(args.spawnRequestId, "spawnRequestId", 160),
|
|
160
160
|
requestedVia: "mcp",
|
|
161
|
-
waitForRegistrationMs: optionalNonNegativeInt(args.waitForRegistrationMs, "waitForRegistrationMs") ??
|
|
161
|
+
waitForRegistrationMs: optionalNonNegativeInt(args.waitForRegistrationMs, "waitForRegistrationMs") ?? 0,
|
|
162
162
|
resumeWorkspace: parseResumeWorkspace(args.resumeWorkspace),
|
|
163
163
|
};
|
|
164
164
|
const result = await spawnAgent(input, authContextFromMcp(auth));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { APPROVAL_MODES, VALID_AGENT_LIFECYCLES, VALID_WORKSPACE_MODES } from "agent-relay-sdk";
|
|
1
|
+
import { APPROVAL_MODES, SPAWN_PROVIDERS, VALID_AGENT_LIFECYCLES, VALID_WORKSPACE_MODES } from "agent-relay-sdk";
|
|
2
2
|
import { McpAuthError } from "./mcp-errors";
|
|
3
3
|
import { authContextFromMcp } from "./services/auth-context";
|
|
4
4
|
import { deleteTeamTemplate, getTeamTemplate, instantiateTeamTemplate, listTeamTemplates, saveTeamTemplate } from "./services/team-templates";
|
|
@@ -9,7 +9,7 @@ const COMPOSITION_ENTRY_SCHEMA = {
|
|
|
9
9
|
type: "object",
|
|
10
10
|
properties: {
|
|
11
11
|
label: { type: "string" },
|
|
12
|
-
provider: { type: "string", enum:
|
|
12
|
+
provider: { type: "string", enum: SPAWN_PROVIDERS },
|
|
13
13
|
model: { type: "string" },
|
|
14
14
|
effort: { type: "string" },
|
|
15
15
|
profile: { type: "string" },
|
|
@@ -138,7 +138,7 @@ export function mergeProviderCatalog(
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
function normalizeOverrideInput(input: ProviderModelOverrideInput): ProviderModelOverrideInput {
|
|
141
|
-
if (!SPAWN_PROVIDERS.includes(input.provider)) throw new ValidationError(
|
|
141
|
+
if (!SPAWN_PROVIDERS.includes(input.provider)) throw new ValidationError(`provider must be one of: ${SPAWN_PROVIDERS.join(", ")}`);
|
|
142
142
|
const alias = input.alias.trim();
|
|
143
143
|
if (!alias) throw new ValidationError("model alias is required");
|
|
144
144
|
if (input.entry.alias !== alias) throw new ValidationError("model entry alias must match override alias");
|
package/src/recipe-validator.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { MemoryType, Recipe, RecipeAgent, RecipeMemoryPolicy } from "./types";
|
|
2
2
|
import { resolveProviderSelection, type ProviderEffort } from "agent-relay-sdk/provider-catalog";
|
|
3
|
-
import { errMessage, isRecord } from "agent-relay-sdk";
|
|
3
|
+
import { errMessage, isRecord, isSpawnProvider, SPAWN_PROVIDERS } from "agent-relay-sdk";
|
|
4
4
|
import { effectiveProviderCatalogList } from "./provider-catalog-store";
|
|
5
5
|
|
|
6
6
|
class RecipeValidationError extends Error {}
|
|
@@ -41,7 +41,9 @@ export function validateRecipe(value: unknown): Recipe {
|
|
|
41
41
|
function validateAgent(value: unknown): RecipeAgent {
|
|
42
42
|
if (!isRecord(value)) throw new RecipeValidationError("agent must be an object");
|
|
43
43
|
const provider = value.provider;
|
|
44
|
-
if (provider
|
|
44
|
+
if (!isSpawnProvider(provider)) {
|
|
45
|
+
throw new RecipeValidationError(`agent.provider must be one of: ${SPAWN_PROVIDERS.join(", ")}`);
|
|
46
|
+
}
|
|
45
47
|
const capabilities = stringArray(value.capabilities, "agent.capabilities");
|
|
46
48
|
if (capabilities.length === 0) throw new RecipeValidationError("agent.capabilities must not be empty");
|
|
47
49
|
const approvalMode = value.approvalMode;
|
|
@@ -5,7 +5,7 @@ import { deleteAgentProfile, getAgentProfile, listAgentProfiles, setAgentProfile
|
|
|
5
5
|
import { emitConfigChanged } from "../sse";
|
|
6
6
|
import { error, isRootCredentialRequest, json, normalizeConfigPathParam, parseBody, type Handler } from "./_shared";
|
|
7
7
|
import { getComponentAuth, hasComponentScope } from "../security";
|
|
8
|
-
import { isRecord } from "agent-relay-sdk";
|
|
8
|
+
import { isRecord, isSpawnProvider } from "agent-relay-sdk";
|
|
9
9
|
import { SPAWN_PROVIDERS, type AgentProfile, type SpawnProvider } from "../types";
|
|
10
10
|
|
|
11
11
|
export const getAgentProfilesRoute: Handler = () => json(listAgentProfiles());
|
|
@@ -29,7 +29,7 @@ export const getResolvedAgentProfileRoute: Handler = (req, params) => {
|
|
|
29
29
|
const entry = getAgentProfile(name);
|
|
30
30
|
if (!entry) return error("agent profile not found", 404);
|
|
31
31
|
const requestedProvider = new URL(req.url).searchParams.get("provider") ?? undefined;
|
|
32
|
-
const profileProvider = entry.value.provider
|
|
32
|
+
const profileProvider = isSpawnProvider(entry.value.provider) ? entry.value.provider : undefined;
|
|
33
33
|
const provider = optionalEnum(requestedProvider, "provider", SPAWN_PROVIDERS, profileProvider ?? "codex") as SpawnProvider;
|
|
34
34
|
const profile = withResolvedProvisioning(entry.value, provider);
|
|
35
35
|
return profile ? json({ provider, profile }) : error("agent profile not found", 404);
|