agent-relay-server 0.32.1 → 0.32.3
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 +57 -127
- package/package.json +1 -1
- package/public/assets/{activity-C6nbfryG.js → activity-DT1JGHnp.js} +2 -2
- package/public/assets/{activity-C6nbfryG.js.map → activity-DT1JGHnp.js.map} +1 -1
- package/public/assets/{agent-profiles-FEITAgHs.js → agent-profiles-CrMemMkZ.js} +2 -2
- package/public/assets/{agent-profiles-FEITAgHs.js.map → agent-profiles-CrMemMkZ.js.map} +1 -1
- package/public/assets/{agents-D4S0yIbe.js → agents-Bl-rrgOy.js} +2 -2
- package/public/assets/{agents-D4S0yIbe.js.map → agents-Bl-rrgOy.js.map} +1 -1
- package/public/assets/{analytics-DM2g62T_.js → analytics-a663ak56.js} +2 -2
- package/public/assets/{analytics-DM2g62T_.js.map → analytics-a663ak56.js.map} +1 -1
- package/public/assets/{automation-3D2pQa1C.js → automation-CiaLThdO.js} +2 -2
- package/public/assets/{automation-3D2pQa1C.js.map → automation-CiaLThdO.js.map} +1 -1
- package/public/assets/{branch-state-badge-Bi4IbkOZ.js → branch-state-badge-D4ur3m3_.js} +2 -2
- package/public/assets/{branch-state-badge-Bi4IbkOZ.js.map → branch-state-badge-D4ur3m3_.js.map} +1 -1
- package/public/assets/{channels-QNp7zmA_.js → channels-o9KLTHoK.js} +2 -2
- package/public/assets/{channels-QNp7zmA_.js.map → channels-o9KLTHoK.js.map} +1 -1
- package/public/assets/{chat-jeXt_SFs.js → chat-5hvHZcAe.js} +2 -2
- package/public/assets/{chat-jeXt_SFs.js.map → chat-5hvHZcAe.js.map} +1 -1
- package/public/assets/{connectors-BGJARDui.js → connectors-CdC806mA.js} +2 -2
- package/public/assets/{connectors-BGJARDui.js.map → connectors-CdC806mA.js.map} +1 -1
- package/public/assets/{formatted-body-impl-B7FgqkYL.js → formatted-body-impl-Ca74OAEH.js} +2 -2
- package/public/assets/{formatted-body-impl-B7FgqkYL.js.map → formatted-body-impl-Ca74OAEH.js.map} +1 -1
- package/public/assets/{index-2m9mT8kV.js → index-C_33ymaw.js} +6 -6
- package/public/assets/{index-2m9mT8kV.js.map → index-C_33ymaw.js.map} +1 -1
- package/public/assets/{integrations-CJm8-FcG.js → integrations-1nxMizDY.js} +2 -2
- package/public/assets/{integrations-CJm8-FcG.js.map → integrations-1nxMizDY.js.map} +1 -1
- package/public/assets/{maintenance-CBvZrVAG.js → maintenance-DiFNzNPN.js} +2 -2
- package/public/assets/{maintenance-CBvZrVAG.js.map → maintenance-DiFNzNPN.js.map} +1 -1
- package/public/assets/{managed-agents-Dcmm8YKt.js → managed-agents-Do3dKvfj.js} +2 -2
- package/public/assets/{managed-agents-Dcmm8YKt.js.map → managed-agents-Do3dKvfj.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-7xjqdiEu.js → markdown-preview-impl-CLA0J255.js} +2 -2
- package/public/assets/{markdown-preview-impl-7xjqdiEu.js.map → markdown-preview-impl-CLA0J255.js.map} +1 -1
- package/public/assets/{memory-BmGNW61h.js → memory-IjwqFzBd.js} +2 -2
- package/public/assets/{memory-BmGNW61h.js.map → memory-IjwqFzBd.js.map} +1 -1
- package/public/assets/{messages-BvMMhoy-.js → messages-DjvWqHyn.js} +2 -2
- package/public/assets/{messages-BvMMhoy-.js.map → messages-DjvWqHyn.js.map} +1 -1
- package/public/assets/{orchestrators-DsstaupT.js → orchestrators-D2IqDxDT.js} +2 -2
- package/public/assets/{orchestrators-DsstaupT.js.map → orchestrators-D2IqDxDT.js.map} +1 -1
- package/public/assets/{overview-kK6PTce3.js → overview-DKC3TbAh.js} +2 -2
- package/public/assets/{overview-kK6PTce3.js.map → overview-DKC3TbAh.js.map} +1 -1
- package/public/assets/{pairs-BEFvTW6X.js → pairs-WpKCPE1n.js} +2 -2
- package/public/assets/{pairs-BEFvTW6X.js.map → pairs-WpKCPE1n.js.map} +1 -1
- package/public/assets/{security-Dc5wZwv0.js → security-BF7ZtPQe.js} +2 -2
- package/public/assets/{security-Dc5wZwv0.js.map → security-BF7ZtPQe.js.map} +1 -1
- package/public/assets/{settings-CEtJrORa.js → settings-CQnjrTa-.js} +2 -2
- package/public/assets/{settings-CEtJrORa.js.map → settings-CQnjrTa-.js.map} +1 -1
- package/public/assets/{store-DkmReBlH.js → store-C9VcSo05.js} +2 -2
- package/public/assets/{store-DkmReBlH.js.map → store-C9VcSo05.js.map} +1 -1
- package/public/assets/{tasks-pQKtxqeV.js → tasks-CbN_GSSb.js} +2 -2
- package/public/assets/{tasks-pQKtxqeV.js.map → tasks-CbN_GSSb.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-Cc769mYy.js → terminal-viewer-impl-BJRohThT.js} +2 -2
- package/public/assets/{terminal-viewer-impl-Cc769mYy.js.map → terminal-viewer-impl-BJRohThT.js.map} +1 -1
- package/public/assets/{work-queue-DjAanr02.js → work-queue-C5xLBLmm.js} +2 -2
- package/public/assets/{work-queue-DjAanr02.js.map → work-queue-C5xLBLmm.js.map} +1 -1
- package/public/assets/{workspaces-DLBNyR4k.js → workspaces-D91H3wDX.js} +2 -2
- package/public/assets/{workspaces-DLBNyR4k.js.map → workspaces-D91H3wDX.js.map} +1 -1
- package/public/index.html +2 -2
- package/scripts/orchestrator-spawn-smoke.ts +2 -1
- package/src/automations.ts +2 -4
- package/src/managed-policy.ts +2 -4
- package/src/mcp.ts +3 -3
- package/src/ratchet-files.ts +37 -0
- package/src/routes/_shared.ts +376 -0
- package/src/routes/activity.ts +61 -0
- package/src/routes/agent-profiles.ts +47 -0
- package/src/routes/agent-sessions.ts +488 -0
- package/src/routes/agents-spawn.ts +274 -0
- package/src/routes/agents.ts +251 -0
- package/src/routes/artifacts.ts +226 -0
- package/src/routes/automations.ts +83 -0
- package/src/routes/commands.ts +317 -0
- package/src/routes/config.ts +66 -0
- package/src/routes/connectors.ts +108 -0
- package/src/routes/inbox.ts +142 -0
- package/src/routes/index.ts +293 -0
- package/src/routes/insights.ts +81 -0
- package/src/routes/integrations.ts +592 -0
- package/src/routes/memory.ts +337 -0
- package/src/routes/messages.ts +529 -0
- package/src/routes/orchestrator-bootstrap.ts +100 -0
- package/src/routes/orchestrator-proxy.ts +160 -0
- package/src/routes/orchestrator.ts +490 -0
- package/src/routes/pairs.ts +197 -0
- package/src/routes/provider-config.ts +112 -0
- package/src/routes/recipes.ts +113 -0
- package/src/routes/spawn-policy.ts +231 -0
- package/src/routes/spec.ts +54 -0
- package/src/routes/sse.ts +9 -0
- package/src/routes/stats.ts +32 -0
- package/src/routes/steward.ts +45 -0
- package/src/routes/tasks.ts +174 -0
- package/src/routes/tokens.ts +311 -0
- package/src/routes/workspaces.ts +355 -0
- package/src/routes.ts +3 -6892
- package/src/runtime-tokens.ts +17 -8
- package/src/security.ts +0 -2
- package/src/validation.ts +134 -0
package/src/runtime-tokens.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createToken, getTokenProfile, revokeToken } from "./token-db";
|
|
2
2
|
import { verifyComponentTokenAllowExpired } from "./security";
|
|
3
|
+
import { spawnGrantForProfile } from "./config-store";
|
|
3
4
|
import type { TokenRecord } from "./types";
|
|
4
5
|
|
|
5
6
|
// Scopes that turn an agent's runtime token into a spawn-capable one (#221). Appended to the
|
|
@@ -89,13 +90,19 @@ export function issueRunnerRuntimeToken(input: {
|
|
|
89
90
|
policyName?: string;
|
|
90
91
|
spawnRequestId?: string;
|
|
91
92
|
createdBy?: string;
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Agent profile name. The spawn grant (the `command:spawn`/`command:shutdown` scope + the
|
|
95
|
+
* live-children quota) is resolved from this profile's `maxSpawnedAgents` HERE, in the single
|
|
96
|
+
* mint, so capability⇒tool holds for EVERY spawn path. Callers pass the profile they already
|
|
97
|
+
* have; they cannot forget to thread the grant (the recurring bug this centralization kills).
|
|
98
|
+
*/
|
|
99
|
+
profile?: string;
|
|
100
|
+
/** Agent-requested spawn → forces a non-spawn-capable child regardless of profile (no grandchildren). */
|
|
101
|
+
agentInitiated?: boolean;
|
|
96
102
|
/** Parent agent id — stamped so the child registers with an authoritative `spawnedBy`. */
|
|
97
103
|
spawnedBy?: string;
|
|
98
104
|
}): RuntimeTokenResult {
|
|
105
|
+
const grant = spawnGrantForProfile(input.profile, input.agentInitiated);
|
|
99
106
|
const subject = input.policyName
|
|
100
107
|
? `runner:policy:${input.policyName}`
|
|
101
108
|
: input.spawnRequestId
|
|
@@ -105,13 +112,13 @@ export function issueRunnerRuntimeToken(input: {
|
|
|
105
112
|
profileId: "provider-agent",
|
|
106
113
|
sub: subject,
|
|
107
114
|
role: "provider",
|
|
108
|
-
scope: runnerScopeWithSpawn(
|
|
115
|
+
scope: runnerScopeWithSpawn(grant.canSpawn),
|
|
109
116
|
constraints: {
|
|
110
117
|
orchestrators: [input.orchestratorId],
|
|
111
118
|
cwdPrefixes: [input.cwd],
|
|
112
119
|
...(input.policyName ? { policies: [input.policyName] } : {}),
|
|
113
120
|
...(input.spawnRequestId ? { spawnRequestIds: [input.spawnRequestId] } : {}),
|
|
114
|
-
...(
|
|
121
|
+
...(grant.canSpawn && grant.maxSpawnedAgents ? { maxSpawnedAgents: grant.maxSpawnedAgents } : {}),
|
|
115
122
|
...(input.spawnedBy ? { spawnedBy: input.spawnedBy } : {}),
|
|
116
123
|
},
|
|
117
124
|
createdBy: input.createdBy ?? "runtime",
|
|
@@ -226,8 +233,10 @@ export function runnerRuntimeTokenEnv(input: {
|
|
|
226
233
|
policyName?: string;
|
|
227
234
|
spawnRequestId?: string;
|
|
228
235
|
createdBy?: string;
|
|
229
|
-
|
|
230
|
-
|
|
236
|
+
/** Agent profile name — spawn grant is resolved from it in the mint (see issueRunnerRuntimeToken). */
|
|
237
|
+
profile?: string;
|
|
238
|
+
/** Agent-requested spawn → forces a non-spawn-capable child (no grandchildren). */
|
|
239
|
+
agentInitiated?: boolean;
|
|
231
240
|
spawnedBy?: string;
|
|
232
241
|
}): Record<string, string> {
|
|
233
242
|
const issued = issueRunnerRuntimeToken(input);
|
package/src/security.ts
CHANGED
|
@@ -170,7 +170,6 @@ export function requiredScopeFor(method: string, pathname: string): string | nul
|
|
|
170
170
|
if (pathname === "/api/orchestrators/bootstrap") return "token:write";
|
|
171
171
|
if (pathname.match(/^\/api\/orchestrators\/[^/]+\/logs\//)) return "logs:read";
|
|
172
172
|
if (pathname.match(/^\/api\/orchestrators\/[^/]+\/terminal\//)) return "terminal:attach";
|
|
173
|
-
if (pathname.match(/^\/api\/orchestrators\/[^/]+\/spawn$/)) return "agent:write";
|
|
174
173
|
if (pathname.startsWith("/api/artifacts")) {
|
|
175
174
|
if (method === "GET" || method === "HEAD") return "artifact:read";
|
|
176
175
|
if (method === "DELETE") return "artifact:admin";
|
|
@@ -242,7 +241,6 @@ export function requiredComponentScopeFor(method: string, pathname: string): str
|
|
|
242
241
|
if (pathname === "/api/mcp") return "mcp:use";
|
|
243
242
|
if (pathname.match(/^\/api\/orchestrators\/[^/]+\/logs\//)) return "logs:read";
|
|
244
243
|
if (pathname.match(/^\/api\/orchestrators\/[^/]+\/terminal\//)) return "terminal:attach";
|
|
245
|
-
if (pathname.match(/^\/api\/orchestrators\/[^/]+\/spawn$/)) return "agent:write";
|
|
246
244
|
if (pathname.startsWith("/api/commands")) {
|
|
247
245
|
if (method === "GET") return "command:read";
|
|
248
246
|
return "command:write";
|
package/src/validation.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { ValidationError } from "./db";
|
|
2
|
+
import { type TokenConstraints, type WorkspaceMetadata, type WorkspaceMode, type WorkspaceProbe, type WorkspaceStatus } from "./types";
|
|
3
|
+
import { VALID_WORKSPACE_MODES, isRecord } from "agent-relay-sdk";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Trim + validate an optional string input. Throws `ValidationError` when the
|
|
@@ -78,3 +80,135 @@ export function cleanStringArray(
|
|
|
78
80
|
}
|
|
79
81
|
return [...new Set(cleaned)];
|
|
80
82
|
}
|
|
83
|
+
|
|
84
|
+
// ---- generic validators relocated from routes.ts (#299) ----
|
|
85
|
+
export const VALID_WORKSPACE_STATUSES = ["active", "ready", "conflict", "review_requested", "merge_planned", "merged", "abandoned", "cleanup_requested", "cleaned"] as const;
|
|
86
|
+
|
|
87
|
+
export function cleanNullableString(value: unknown, field: string, max: number): string | null | undefined {
|
|
88
|
+
if (value === undefined) return undefined;
|
|
89
|
+
if (value === null) return null;
|
|
90
|
+
return cleanString(value, field, { max }) ?? null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function cleanConstraintStringArray(value: unknown, field: string): string[] | undefined {
|
|
94
|
+
if (value === undefined || value === null) return undefined;
|
|
95
|
+
if (!Array.isArray(value)) throw new ValidationError(`${field} must be an array of strings`);
|
|
96
|
+
const cleaned = value.map((item) => cleanString(item, `${field} item`, { max: 500 })).filter(Boolean) as string[];
|
|
97
|
+
if (cleaned.length > 100) throw new ValidationError(`${field} can contain at most 100 values`);
|
|
98
|
+
return [...new Set(cleaned)];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function cleanTokenConstraints(value: unknown): TokenConstraints | undefined {
|
|
102
|
+
if (value === undefined || value === null) return undefined;
|
|
103
|
+
if (!isRecord(value)) throw new ValidationError("constraints must be an object");
|
|
104
|
+
const constraints: TokenConstraints = {};
|
|
105
|
+
const arrayKeys = [
|
|
106
|
+
"agents",
|
|
107
|
+
"policies",
|
|
108
|
+
"parentAgents",
|
|
109
|
+
"targets",
|
|
110
|
+
"channels",
|
|
111
|
+
"orchestrators",
|
|
112
|
+
"hosts",
|
|
113
|
+
"cwdPrefixes",
|
|
114
|
+
"taskIds",
|
|
115
|
+
"memoryScopes",
|
|
116
|
+
"integrationNames",
|
|
117
|
+
"spawnRequestIds",
|
|
118
|
+
] as const;
|
|
119
|
+
for (const key of arrayKeys) {
|
|
120
|
+
const cleaned = cleanConstraintStringArray(value[key], `constraints.${key}`);
|
|
121
|
+
if (cleaned?.length) constraints[key] = cleaned;
|
|
122
|
+
}
|
|
123
|
+
const cwd = cleanString(value.cwd, "constraints.cwd", { max: 500 });
|
|
124
|
+
if (cwd) constraints.cwd = cwd;
|
|
125
|
+
for (const key of ["terminalAttach", "logsRead", "canDelegate"] as const) {
|
|
126
|
+
if (value[key] !== undefined) {
|
|
127
|
+
if (typeof value[key] !== "boolean") throw new ValidationError(`constraints.${key} must be a boolean`);
|
|
128
|
+
constraints[key] = value[key];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return constraints;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function cleanMeta(value: unknown): Record<string, unknown> | undefined {
|
|
135
|
+
if (value === undefined || value === null) return undefined;
|
|
136
|
+
if (!isRecord(value)) throw new ValidationError("meta must be an object");
|
|
137
|
+
if (JSON.stringify(value).length > 8192) throw new ValidationError("meta is too large");
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function cleanParams(value: unknown, field = "params"): Record<string, unknown> | undefined {
|
|
142
|
+
if (value === undefined || value === null) return undefined;
|
|
143
|
+
if (!isRecord(value)) throw new ValidationError(`${field} must be an object`);
|
|
144
|
+
if (JSON.stringify(value).length > 65_536) throw new ValidationError(`${field} is too large`);
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function cleanWorkspaceMetadata(value: unknown, field: string): WorkspaceMetadata | undefined {
|
|
149
|
+
if (value === undefined || value === null) return undefined;
|
|
150
|
+
if (!isRecord(value)) throw new ValidationError(`${field} must be an object`);
|
|
151
|
+
return {
|
|
152
|
+
id: cleanString(value.id, `${field}.id`, { max: 160 }),
|
|
153
|
+
mode: optionalEnum(value.mode, `${field}.mode`, VALID_WORKSPACE_MODES, "shared") as WorkspaceMode,
|
|
154
|
+
requestedMode: optionalEnum(value.requestedMode, `${field}.requestedMode`, VALID_WORKSPACE_MODES) as WorkspaceMode | undefined,
|
|
155
|
+
repoRoot: cleanString(value.repoRoot, `${field}.repoRoot`, { max: 1000 }),
|
|
156
|
+
sourceCwd: cleanString(value.sourceCwd, `${field}.sourceCwd`, { max: 1000 }),
|
|
157
|
+
worktreePath: cleanString(value.worktreePath, `${field}.worktreePath`, { max: 1000 }),
|
|
158
|
+
branch: cleanString(value.branch, `${field}.branch`, { max: 240 }),
|
|
159
|
+
baseRef: cleanString(value.baseRef, `${field}.baseRef`, { max: 240 }),
|
|
160
|
+
baseSha: cleanString(value.baseSha, `${field}.baseSha`, { max: 80 }),
|
|
161
|
+
status: optionalEnum(value.status, `${field}.status`, VALID_WORKSPACE_STATUSES) as WorkspaceStatus | undefined,
|
|
162
|
+
stewardAgentId: cleanString(value.stewardAgentId, `${field}.stewardAgentId`, { max: 240 }),
|
|
163
|
+
probe: isRecord(value.probe) ? value.probe as unknown as WorkspaceProbe : undefined,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function cleanPositiveId(value: unknown, field: string): number | undefined {
|
|
168
|
+
if (value === undefined || value === null) return undefined;
|
|
169
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
|
170
|
+
throw new ValidationError(`${field} must be a positive integer`);
|
|
171
|
+
}
|
|
172
|
+
return value;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function cleanNullablePositiveId(value: unknown, field: string): number | null | undefined {
|
|
176
|
+
if (value === undefined) return undefined;
|
|
177
|
+
if (value === null) return null;
|
|
178
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
|
179
|
+
throw new ValidationError(`${field} must be a positive integer or null`);
|
|
180
|
+
}
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function cleanEpoch(value: unknown, field: string): number | undefined {
|
|
185
|
+
if (value === undefined || value === null) return undefined;
|
|
186
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
187
|
+
throw new ValidationError(`${field} must be a non-negative integer`);
|
|
188
|
+
}
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function cleanTtlMs(value: unknown): number | undefined {
|
|
193
|
+
if (value === undefined || value === null) return undefined;
|
|
194
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
|
195
|
+
throw new ValidationError("ttlMs must be a positive integer");
|
|
196
|
+
}
|
|
197
|
+
return value;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function cleanOperatorId(value: unknown): string {
|
|
201
|
+
return cleanString(value, "operatorId", { max: 200 }) || "user";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function cleanPositiveIdArray(value: unknown, field: string, max = 500): number[] {
|
|
205
|
+
if (!Array.isArray(value)) throw new ValidationError(`${field} must be an array`);
|
|
206
|
+
if (value.length === 0) throw new ValidationError(`${field} required`);
|
|
207
|
+
if (value.length > max) throw new ValidationError(`${field} max ${max}`);
|
|
208
|
+
return value.map((item, index) => {
|
|
209
|
+
if (typeof item !== "number" || !Number.isSafeInteger(item) || item <= 0) {
|
|
210
|
+
throw new ValidationError(`${field}[${index}] must be a positive integer`);
|
|
211
|
+
}
|
|
212
|
+
return item;
|
|
213
|
+
});
|
|
214
|
+
}
|