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.
Files changed (97) hide show
  1. package/docs/openapi.json +57 -127
  2. package/package.json +1 -1
  3. package/public/assets/{activity-C6nbfryG.js → activity-DT1JGHnp.js} +2 -2
  4. package/public/assets/{activity-C6nbfryG.js.map → activity-DT1JGHnp.js.map} +1 -1
  5. package/public/assets/{agent-profiles-FEITAgHs.js → agent-profiles-CrMemMkZ.js} +2 -2
  6. package/public/assets/{agent-profiles-FEITAgHs.js.map → agent-profiles-CrMemMkZ.js.map} +1 -1
  7. package/public/assets/{agents-D4S0yIbe.js → agents-Bl-rrgOy.js} +2 -2
  8. package/public/assets/{agents-D4S0yIbe.js.map → agents-Bl-rrgOy.js.map} +1 -1
  9. package/public/assets/{analytics-DM2g62T_.js → analytics-a663ak56.js} +2 -2
  10. package/public/assets/{analytics-DM2g62T_.js.map → analytics-a663ak56.js.map} +1 -1
  11. package/public/assets/{automation-3D2pQa1C.js → automation-CiaLThdO.js} +2 -2
  12. package/public/assets/{automation-3D2pQa1C.js.map → automation-CiaLThdO.js.map} +1 -1
  13. package/public/assets/{branch-state-badge-Bi4IbkOZ.js → branch-state-badge-D4ur3m3_.js} +2 -2
  14. package/public/assets/{branch-state-badge-Bi4IbkOZ.js.map → branch-state-badge-D4ur3m3_.js.map} +1 -1
  15. package/public/assets/{channels-QNp7zmA_.js → channels-o9KLTHoK.js} +2 -2
  16. package/public/assets/{channels-QNp7zmA_.js.map → channels-o9KLTHoK.js.map} +1 -1
  17. package/public/assets/{chat-jeXt_SFs.js → chat-5hvHZcAe.js} +2 -2
  18. package/public/assets/{chat-jeXt_SFs.js.map → chat-5hvHZcAe.js.map} +1 -1
  19. package/public/assets/{connectors-BGJARDui.js → connectors-CdC806mA.js} +2 -2
  20. package/public/assets/{connectors-BGJARDui.js.map → connectors-CdC806mA.js.map} +1 -1
  21. package/public/assets/{formatted-body-impl-B7FgqkYL.js → formatted-body-impl-Ca74OAEH.js} +2 -2
  22. package/public/assets/{formatted-body-impl-B7FgqkYL.js.map → formatted-body-impl-Ca74OAEH.js.map} +1 -1
  23. package/public/assets/{index-2m9mT8kV.js → index-C_33ymaw.js} +6 -6
  24. package/public/assets/{index-2m9mT8kV.js.map → index-C_33ymaw.js.map} +1 -1
  25. package/public/assets/{integrations-CJm8-FcG.js → integrations-1nxMizDY.js} +2 -2
  26. package/public/assets/{integrations-CJm8-FcG.js.map → integrations-1nxMizDY.js.map} +1 -1
  27. package/public/assets/{maintenance-CBvZrVAG.js → maintenance-DiFNzNPN.js} +2 -2
  28. package/public/assets/{maintenance-CBvZrVAG.js.map → maintenance-DiFNzNPN.js.map} +1 -1
  29. package/public/assets/{managed-agents-Dcmm8YKt.js → managed-agents-Do3dKvfj.js} +2 -2
  30. package/public/assets/{managed-agents-Dcmm8YKt.js.map → managed-agents-Do3dKvfj.js.map} +1 -1
  31. package/public/assets/{markdown-preview-impl-7xjqdiEu.js → markdown-preview-impl-CLA0J255.js} +2 -2
  32. package/public/assets/{markdown-preview-impl-7xjqdiEu.js.map → markdown-preview-impl-CLA0J255.js.map} +1 -1
  33. package/public/assets/{memory-BmGNW61h.js → memory-IjwqFzBd.js} +2 -2
  34. package/public/assets/{memory-BmGNW61h.js.map → memory-IjwqFzBd.js.map} +1 -1
  35. package/public/assets/{messages-BvMMhoy-.js → messages-DjvWqHyn.js} +2 -2
  36. package/public/assets/{messages-BvMMhoy-.js.map → messages-DjvWqHyn.js.map} +1 -1
  37. package/public/assets/{orchestrators-DsstaupT.js → orchestrators-D2IqDxDT.js} +2 -2
  38. package/public/assets/{orchestrators-DsstaupT.js.map → orchestrators-D2IqDxDT.js.map} +1 -1
  39. package/public/assets/{overview-kK6PTce3.js → overview-DKC3TbAh.js} +2 -2
  40. package/public/assets/{overview-kK6PTce3.js.map → overview-DKC3TbAh.js.map} +1 -1
  41. package/public/assets/{pairs-BEFvTW6X.js → pairs-WpKCPE1n.js} +2 -2
  42. package/public/assets/{pairs-BEFvTW6X.js.map → pairs-WpKCPE1n.js.map} +1 -1
  43. package/public/assets/{security-Dc5wZwv0.js → security-BF7ZtPQe.js} +2 -2
  44. package/public/assets/{security-Dc5wZwv0.js.map → security-BF7ZtPQe.js.map} +1 -1
  45. package/public/assets/{settings-CEtJrORa.js → settings-CQnjrTa-.js} +2 -2
  46. package/public/assets/{settings-CEtJrORa.js.map → settings-CQnjrTa-.js.map} +1 -1
  47. package/public/assets/{store-DkmReBlH.js → store-C9VcSo05.js} +2 -2
  48. package/public/assets/{store-DkmReBlH.js.map → store-C9VcSo05.js.map} +1 -1
  49. package/public/assets/{tasks-pQKtxqeV.js → tasks-CbN_GSSb.js} +2 -2
  50. package/public/assets/{tasks-pQKtxqeV.js.map → tasks-CbN_GSSb.js.map} +1 -1
  51. package/public/assets/{terminal-viewer-impl-Cc769mYy.js → terminal-viewer-impl-BJRohThT.js} +2 -2
  52. package/public/assets/{terminal-viewer-impl-Cc769mYy.js.map → terminal-viewer-impl-BJRohThT.js.map} +1 -1
  53. package/public/assets/{work-queue-DjAanr02.js → work-queue-C5xLBLmm.js} +2 -2
  54. package/public/assets/{work-queue-DjAanr02.js.map → work-queue-C5xLBLmm.js.map} +1 -1
  55. package/public/assets/{workspaces-DLBNyR4k.js → workspaces-D91H3wDX.js} +2 -2
  56. package/public/assets/{workspaces-DLBNyR4k.js.map → workspaces-D91H3wDX.js.map} +1 -1
  57. package/public/index.html +2 -2
  58. package/scripts/orchestrator-spawn-smoke.ts +2 -1
  59. package/src/automations.ts +2 -4
  60. package/src/managed-policy.ts +2 -4
  61. package/src/mcp.ts +3 -3
  62. package/src/ratchet-files.ts +37 -0
  63. package/src/routes/_shared.ts +376 -0
  64. package/src/routes/activity.ts +61 -0
  65. package/src/routes/agent-profiles.ts +47 -0
  66. package/src/routes/agent-sessions.ts +488 -0
  67. package/src/routes/agents-spawn.ts +274 -0
  68. package/src/routes/agents.ts +251 -0
  69. package/src/routes/artifacts.ts +226 -0
  70. package/src/routes/automations.ts +83 -0
  71. package/src/routes/commands.ts +317 -0
  72. package/src/routes/config.ts +66 -0
  73. package/src/routes/connectors.ts +108 -0
  74. package/src/routes/inbox.ts +142 -0
  75. package/src/routes/index.ts +293 -0
  76. package/src/routes/insights.ts +81 -0
  77. package/src/routes/integrations.ts +592 -0
  78. package/src/routes/memory.ts +337 -0
  79. package/src/routes/messages.ts +529 -0
  80. package/src/routes/orchestrator-bootstrap.ts +100 -0
  81. package/src/routes/orchestrator-proxy.ts +160 -0
  82. package/src/routes/orchestrator.ts +490 -0
  83. package/src/routes/pairs.ts +197 -0
  84. package/src/routes/provider-config.ts +112 -0
  85. package/src/routes/recipes.ts +113 -0
  86. package/src/routes/spawn-policy.ts +231 -0
  87. package/src/routes/spec.ts +54 -0
  88. package/src/routes/sse.ts +9 -0
  89. package/src/routes/stats.ts +32 -0
  90. package/src/routes/steward.ts +45 -0
  91. package/src/routes/tasks.ts +174 -0
  92. package/src/routes/tokens.ts +311 -0
  93. package/src/routes/workspaces.ts +355 -0
  94. package/src/routes.ts +3 -6892
  95. package/src/runtime-tokens.ts +17 -8
  96. package/src/security.ts +0 -2
  97. package/src/validation.ts +134 -0
@@ -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
- /** Grant the spawn/shutdown scopes (resolved from the agent's profile maxSpawnedAgents>0). */
93
- canSpawn?: boolean;
94
- /** Live-children quota baked into the token for the runtime spawn check. */
95
- maxSpawnedAgents?: number;
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(input.canSpawn ?? false),
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
- ...(input.canSpawn && input.maxSpawnedAgents ? { maxSpawnedAgents: input.maxSpawnedAgents } : {}),
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
- canSpawn?: boolean;
230
- maxSpawnedAgents?: number;
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
+ }