agent-relay-server 0.102.1 → 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/channels.ts +30 -2
- package/src/db/provider-quota-burn.ts +37 -5
- 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/quota-band-transition.ts +59 -8
- 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 +32 -4
- package/src/sse.ts +12 -1
- 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));
|
package/src/db/channels.ts
CHANGED
|
@@ -284,7 +284,35 @@ function channelTargetHealth(binding: ChannelBinding, now: number = Date.now()):
|
|
|
284
284
|
};
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
// #797 — outbound (egress-only) channel liveness. Bidirectional channels (e.g. Telegram)
|
|
288
|
+
// have a backing pool agent that heartbeats, so their agent `lastSeen`/`status` track
|
|
289
|
+
// reality. Outbound-only channels (e.g. ntfy) are just a channels-host daemon SSE
|
|
290
|
+
// subscription with NO agent behind them to heartbeat — so the registration's `lastSeen`
|
|
291
|
+
// goes stale and the offline sweep flips it offline even while the daemon's subscription is
|
|
292
|
+
// live and delivery works. For these, liveness is "is the channels-host subscribed for this
|
|
293
|
+
// channel agent on the SSE bus?", not heartbeat freshness. A late-bound probe (not a direct
|
|
294
|
+
// import) because sse.ts imports the db barrel — sse registers this at startup, mirroring
|
|
295
|
+
// `onWorkspaceChange`.
|
|
296
|
+
type ChannelSubscriberProbe = (channelAgentId: string) => boolean;
|
|
297
|
+
let channelSubscriberProbe: ChannelSubscriberProbe | null = null;
|
|
298
|
+
export function setChannelSubscriberProbe(probe: ChannelSubscriberProbe | null): void {
|
|
299
|
+
channelSubscriberProbe = probe;
|
|
300
|
+
}
|
|
301
|
+
function hasActiveChannelSubscriber(channelAgentId: string): boolean {
|
|
302
|
+
try {
|
|
303
|
+
return channelSubscriberProbe ? channelSubscriberProbe(channelAgentId) : false;
|
|
304
|
+
} catch {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
287
309
|
function rowToChannelSummary(row: any): ChannelSummary {
|
|
310
|
+
// Outbound channels report online iff their channels-host SSE subscription is active,
|
|
311
|
+
// overriding the heartbeat-derived agent status/ready (which is meaningless for an
|
|
312
|
+
// agentless egress channel). Inbound/bidirectional keep the backing agent's signal.
|
|
313
|
+
const subscriberLive = row.direction === "outbound" ? hasActiveChannelSubscriber(row.agent_id) : null;
|
|
314
|
+
const status: ChannelSummary["status"] = subscriberLive === null ? row.agent_status : (subscriberLive ? "online" : "offline");
|
|
315
|
+
const ready = subscriberLive === null ? row.agent_ready === 1 : subscriberLive;
|
|
288
316
|
const binding = row.binding_id ? rowToChannelBinding({
|
|
289
317
|
id: row.binding_id,
|
|
290
318
|
channel_id: row.id,
|
|
@@ -308,8 +336,8 @@ function rowToChannelSummary(row: any): ChannelSummary {
|
|
|
308
336
|
transport: row.transport,
|
|
309
337
|
agentId: row.agent_id,
|
|
310
338
|
accountId: row.account_id,
|
|
311
|
-
status
|
|
312
|
-
ready
|
|
339
|
+
status,
|
|
340
|
+
ready,
|
|
313
341
|
direction: row.direction,
|
|
314
342
|
target: binding ? bindingTargetToLegacyTarget(binding.target) : undefined,
|
|
315
343
|
binding,
|
|
@@ -15,6 +15,17 @@ const WINDOW_NOMINAL_MS: Record<ProviderQuotaBurnRole, number> = {
|
|
|
15
15
|
// observed rate (and burnRatio) back toward 0 instead of freezing the endpoint slope.
|
|
16
16
|
const RECENCY_HALFLIFE_FRACTION = 0.25;
|
|
17
17
|
|
|
18
|
+
// #798 — idle de-bias: once a provider's utilization has held flat for this long, any residual
|
|
19
|
+
// forward-burn rate is a stale-burst artifact (the recency half-life decays it, but for the 7d
|
|
20
|
+
// window — half-life ~42h — far too slowly: codex idle ~24h still projected ~1.07 at reset,
|
|
21
|
+
// walking the band boundary tick-to-tick). When idle this long the forward-burn term is collapsed
|
|
22
|
+
// so projectedUtilAtReset → current utilization. The threshold is absolute (NOT window-scaled): a
|
|
23
|
+
// 5h window resets long before reaching it, so this only de-biases the slow-decaying 7d window;
|
|
24
|
+
// shorter idle stretches keep the graceful #760 recency decay. The over-target avoid is preserved
|
|
25
|
+
// by the position penalty in spawn-targets (a stable, position-based floor), not by this rate.
|
|
26
|
+
const IDLE_FORWARD_COLLAPSE_MS = 6 * 3_600_000;
|
|
27
|
+
const IDLE_UTILIZATION_EPSILON = 1e-9;
|
|
28
|
+
|
|
18
29
|
type QuotaWindow = QuotaState["windows"][number];
|
|
19
30
|
|
|
20
31
|
// Provider-agnostic: window role is expressed by name only. Each adapter uses the
|
|
@@ -80,6 +91,19 @@ function recencyWeightedRatePerMs(
|
|
|
80
91
|
return Math.max(0, weightedDelta / weightedDt);
|
|
81
92
|
}
|
|
82
93
|
|
|
94
|
+
// #798 — when did utilization last differ from its current value? Scanning back from the newest
|
|
95
|
+
// sample, the first sample that differs marks the last change; everything after it (up to now) is
|
|
96
|
+
// the flat idle stretch. If every sample equals the current value the window has been flat since
|
|
97
|
+
// the oldest sample. Samples are assumed sorted ascending by capturedAt.
|
|
98
|
+
function lastUtilizationChangeAt(samples: Array<{ capturedAt: number; utilization: number }>, current: number): number {
|
|
99
|
+
for (let i = samples.length - 1; i >= 0; i--) {
|
|
100
|
+
if (Math.abs(samples[i]!.utilization - current) > IDLE_UTILIZATION_EPSILON) {
|
|
101
|
+
return samples[i + 1]?.capturedAt ?? samples[i]!.capturedAt;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return samples[0]?.capturedAt ?? 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
83
107
|
function deriveProviderQuotaBurnWindow(input: {
|
|
84
108
|
provider: string;
|
|
85
109
|
currentWindow: QuotaWindow;
|
|
@@ -137,17 +161,25 @@ function deriveProviderQuotaBurnWindow(input: {
|
|
|
137
161
|
? [...samples, { capturedAt: input.now, utilization: utilizationNow }]
|
|
138
162
|
: samples;
|
|
139
163
|
const observedRatePerMs = recencyWeightedRatePerMs(rateSamples, input.now, halfLifeMs);
|
|
164
|
+
// #798 — idle de-bias: if utilization has held flat (no change vs. now) for IDLE_FORWARD_COLLAPSE_MS,
|
|
165
|
+
// treat the forward-burn term as a stale-burst artifact and collapse it to 0 so the projection
|
|
166
|
+
// pins to current utilization instead of walking the band boundary. lastChangeAt is the moment
|
|
167
|
+
// utilization last differed from the current value (the synthetic "still X as of now" interval is
|
|
168
|
+
// what makes a deduped idle stretch observable here).
|
|
169
|
+
const lastChangeAt = lastUtilizationChangeAt(samples, utilizationNow);
|
|
170
|
+
const effectiveRate = input.now - lastChangeAt >= IDLE_FORWARD_COLLAPSE_MS ? 0 : observedRatePerMs;
|
|
140
171
|
// #760 (Defect 2) — pace against a configurable target (<100%); slack carries the underburn
|
|
141
172
|
// signal. The query layer (spawn-targets) may re-derive these against a per-provider target;
|
|
142
173
|
// the ingest-time values use the default so direct burn consumers (dashboard) have a number.
|
|
143
174
|
const pace = quotaPaceMetrics({
|
|
144
175
|
utilization: utilizationNow,
|
|
145
|
-
observedRatePerMs,
|
|
176
|
+
observedRatePerMs: effectiveRate,
|
|
146
177
|
timeToResetMs,
|
|
147
178
|
target: DEFAULT_QUOTA_TARGET_UTILIZATION,
|
|
148
179
|
});
|
|
149
|
-
if (
|
|
150
|
-
// Idle (
|
|
180
|
+
if (effectiveRate <= 0) {
|
|
181
|
+
// Idle (decayed-to-idle, or #798 idle-collapsed): no projected wall, but slack still flags the
|
|
182
|
+
// headroom (positive = underburn) or, when over target, the position the band-floor avoids.
|
|
151
183
|
return {
|
|
152
184
|
...low,
|
|
153
185
|
confidence: "high",
|
|
@@ -158,13 +190,13 @@ function deriveProviderQuotaBurnWindow(input: {
|
|
|
158
190
|
target: pace.target,
|
|
159
191
|
};
|
|
160
192
|
}
|
|
161
|
-
const timeToWallMs = remainingQuota /
|
|
193
|
+
const timeToWallMs = remainingQuota / effectiveRate;
|
|
162
194
|
const projectedWallAt = input.now + timeToWallMs;
|
|
163
195
|
return {
|
|
164
196
|
...low,
|
|
165
197
|
confidence: "high",
|
|
166
198
|
burnRatio: pace.burnRatio,
|
|
167
|
-
observedRatePerMs,
|
|
199
|
+
observedRatePerMs: effectiveRate,
|
|
168
200
|
timeToWallMs,
|
|
169
201
|
projectedWallAt,
|
|
170
202
|
// #760 (Defect 2) — symmetric: negative means the wall lands AFTER reset (headroom left).
|
|
@@ -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
|
|