agent-relay-server 0.57.2 → 0.58.0

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/src/security.ts CHANGED
@@ -206,6 +206,7 @@ export function requiredScopeFor(method: string, pathname: string): string | nul
206
206
  if (pathname.startsWith("/api/pairs")) return method === "GET" ? "pairs:read" : "pairs:write";
207
207
  if (pathname.startsWith("/api/teams")) return method === "GET" ? "teams:read" : "teams:write";
208
208
  if (pathname.startsWith("/api/blackboard")) return method === "GET" ? "blackboard:read" : "blackboard:write";
209
+ if (pathname.startsWith("/api/projects")) return method === "GET" ? "project:read" : "project:write";
209
210
  // Insights config (the feature toggle) stays admin-only via the default; only the
210
211
  // mechanical observation feed is writable by lower-privilege callers.
211
212
  if (pathname === "/api/insights/observations") return method === "GET" ? "insights:read" : "insights:write";
@@ -282,6 +283,9 @@ export function requiredComponentScopeFor(method: string, pathname: string): str
282
283
  if (pathname.startsWith("/api/plans")) return method === "GET" ? "plans:read" : "plans:write";
283
284
  if (pathname.startsWith("/api/teams")) return method === "GET" ? "teams:read" : "teams:write";
284
285
  if (pathname.startsWith("/api/blackboard")) return method === "GET" ? "blackboard:read" : "blackboard:write";
286
+ // Fail-closed default returns system:admin, so this case is load-bearing: without it
287
+ // every /api/projects call from a scoped component token would 403-blackhole (#405).
288
+ if (pathname.startsWith("/api/projects")) return method === "GET" ? "project:read" : "project:write";
285
289
  // The Runner posts the #184 context-gathering signal here (source:"server") via its
286
290
  // provider token. Without this case the path fell through to the system:admin default
287
291
  // below and every observation was 403-dropped — the whole Insights feed stayed empty.
@@ -25,7 +25,7 @@
25
25
  // deliberately out of scope here — this slice converges the two external TRANSPORTS.
26
26
  import type { SpawnProvider } from "agent-relay-sdk";
27
27
  import { stringValue } from "agent-relay-sdk";
28
- import { countLiveSpawnedAgents, createActivityEvent, ensureTeam, getAgent, listAgents, setAgentTeam, ValidationError } from "../db";
28
+ import { countLiveSpawnedAgents, createActivityEvent, ensureTeam, getAgent, listAgents, resolveProjectForCwd, setAgentTeam, ValidationError } from "../db";
29
29
  import { createCommand } from "../commands-db";
30
30
  import { emitCommandEvent } from "../command-events";
31
31
  import { buildSpawnCommand, generateSpawnRequestId, resolveSpawnModelParams } from "../spawn-command";
@@ -192,6 +192,11 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
192
192
  const callerTeam = callerId ? (caller?.teamId ? { id: caller.teamId } : ensureTeam(callerId)) : undefined;
193
193
  if (callerId && callerTeam && !caller?.teamId) setAgentTeam(callerId, callerTeam.id);
194
194
 
195
+ // #405 — durable project binding: the project whose registered root contains the spawn cwd.
196
+ // Stamped on the child token's constraints (identity-only, like teamId) so callerProject(auth)
197
+ // resolves it. Absent (graceful) when the cwd is inside no registered root.
198
+ const callerProject = resolveProjectForCwd(resolvedCwd);
199
+
195
200
  // Child runner token. `agentInitiated` (set whenever an agent is the caller) forces a
196
201
  // non-spawn-capable child (no grandchildren); `spawnedBy` stamps authoritative lineage the
197
202
  // child can't forge (it registers with spawnedBy = caller, read from the signed token).
@@ -211,6 +216,7 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
211
216
  profile: input.profile || undefined,
212
217
  ...(callerId ? { agentInitiated: true, spawnedBy: callerId } : {}),
213
218
  ...(callerTeam ? { teamId: callerTeam.id } : {}),
219
+ ...(callerProject ? { projectId: callerProject.id } : {}),
214
220
  });
215
221
 
216
222
  // #413 — default the report-back obligation ON for an agent coordinator handing a worker a
@@ -56,7 +56,7 @@ export function setCallerTeamBrief(ctx: AuthContext, input: TeamBriefInput): Tea
56
56
  }
57
57
 
58
58
  export function dissolveTeamById(teamId: string, input: TeamDissolveServiceInput = {}): Team {
59
- const team = dissolveTeam(teamId, input);
59
+ const { team, unpromotedBlackboard } = dissolveTeam(teamId, input);
60
60
  emitRelayEvent({
61
61
  type: "team.dissolved",
62
62
  source: "system",
@@ -65,6 +65,10 @@ export function dissolveTeamById(teamId: string, input: TeamDissolveServiceInput
65
65
  teamId: team.id,
66
66
  outcomeStatus: team.outcomeStatus ?? null,
67
67
  reason: team.outcomeSummary ?? null,
68
+ // Surface (don't auto-promote) the lossy findings the team is leaving behind so a
69
+ // coordinator/operator can decide what to lift into durable project knowledge (#404).
70
+ unpromotedBlackboardCount: unpromotedBlackboard.count,
71
+ unpromotedBlackboard: unpromotedBlackboard.entries,
68
72
  },
69
73
  });
70
74
  return team;
package/src/token-db.ts CHANGED
@@ -57,7 +57,7 @@ const BUILT_IN_PROFILES: Array<Omit<TokenProfile, "createdAt" | "updatedAt">> =
57
57
  name: "Provider Agent",
58
58
  description: "Coding-agent runtime access for messages, commands, tasks, and scoped memory reads.",
59
59
  role: "provider",
60
- scope: ["agent:read", "agent:write", "message:read", "message:send", "command:read", "command:write", "task:read", "task:write", "memory:read", "artifact:read", "artifact:write", "mcp:use", "plans:read", "plans:write", "teams:read", "teams:write", "blackboard:read", "blackboard:write", "insights:write"],
60
+ scope: ["agent:read", "agent:write", "message:read", "message:send", "command:read", "command:write", "task:read", "task:write", "memory:read", "artifact:read", "artifact:write", "mcp:use", "plans:read", "plans:write", "teams:read", "teams:write", "blackboard:read", "blackboard:write", "project:read", "project:write", "insights:write"],
61
61
  ttlSeconds: 24 * 60 * 60,
62
62
  builtIn: true,
63
63
  createdBy: "system",
@@ -67,7 +67,7 @@ const BUILT_IN_PROFILES: Array<Omit<TokenProfile, "createdAt" | "updatedAt">> =
67
67
  name: "Provider Child Agent",
68
68
  description: "Delegated child-agent runtime access, constrained to its parent and spawn request.",
69
69
  role: "provider",
70
- scope: ["agent:read", "agent:write", "message:read", "message:send", "command:read", "command:write", "task:read", "task:write", "memory:read", "artifact:read", "artifact:write", "mcp:use", "teams:read", "blackboard:read", "blackboard:write", "insights:write"],
70
+ scope: ["agent:read", "agent:write", "message:read", "message:send", "command:read", "command:write", "task:read", "task:write", "memory:read", "artifact:read", "artifact:write", "mcp:use", "teams:read", "blackboard:read", "blackboard:write", "project:read", "project:write", "insights:write"],
71
71
  constraints: { canDelegate: false },
72
72
  ttlSeconds: 2 * 60 * 60,
73
73
  builtIn: true,
@@ -78,7 +78,7 @@ const BUILT_IN_PROFILES: Array<Omit<TokenProfile, "createdAt" | "updatedAt">> =
78
78
  name: "Provider Interactive Agent",
79
79
  description: "User-launched provider runtime access constrained to its own agent and cwd for long interactive sessions.",
80
80
  role: "provider",
81
- scope: ["agent:read", "agent:write", "message:read", "message:send", "command:read", "command:write", "task:read", "task:write", "memory:read", "artifact:read", "artifact:write", "mcp:use", "teams:read", "blackboard:read", "blackboard:write", "insights:write"],
81
+ scope: ["agent:read", "agent:write", "message:read", "message:send", "command:read", "command:write", "task:read", "task:write", "memory:read", "artifact:read", "artifact:write", "mcp:use", "teams:read", "blackboard:read", "blackboard:write", "project:read", "project:write", "insights:write"],
82
82
  constraints: { terminalAttach: false, logsRead: false, canDelegate: false },
83
83
  ttlSeconds: 30 * 24 * 60 * 60,
84
84
  builtIn: true,