agent-relay-server 0.42.0 → 0.44.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.
Files changed (97) hide show
  1. package/docs/openapi.json +85 -1
  2. package/package.json +2 -2
  3. package/public/assets/{activity-G6l1nOLj.js → activity-Dkzmef0Z.js} +2 -2
  4. package/public/assets/{activity-G6l1nOLj.js.map → activity-Dkzmef0Z.js.map} +1 -1
  5. package/public/assets/{agent-profiles-BWKpVoNw.js → agent-profiles-DQYHMAyu.js} +2 -2
  6. package/public/assets/{agent-profiles-BWKpVoNw.js.map → agent-profiles-DQYHMAyu.js.map} +1 -1
  7. package/public/assets/{agents-DBuCRS5A.js → agents-atr6HZ1e.js} +2 -2
  8. package/public/assets/{agents-DBuCRS5A.js.map → agents-atr6HZ1e.js.map} +1 -1
  9. package/public/assets/{analytics-BhwMtd0_.js → analytics-azFfAy4R.js} +2 -2
  10. package/public/assets/{analytics-BhwMtd0_.js.map → analytics-azFfAy4R.js.map} +1 -1
  11. package/public/assets/{automation-B7jnsYhH.js → automation-Bk4umHIm.js} +2 -2
  12. package/public/assets/{automation-B7jnsYhH.js.map → automation-Bk4umHIm.js.map} +1 -1
  13. package/public/assets/{branch-state-badge-dN2mQ2gs.js → branch-state-badge-DymObp8d.js} +2 -2
  14. package/public/assets/{branch-state-badge-dN2mQ2gs.js.map → branch-state-badge-DymObp8d.js.map} +1 -1
  15. package/public/assets/channels-B_0wDy0s.js +2 -0
  16. package/public/assets/{channels-CQy-JL7o.js.map → channels-B_0wDy0s.js.map} +1 -1
  17. package/public/assets/chat-DxV4MyYF.js +2 -0
  18. package/public/assets/chat-DxV4MyYF.js.map +1 -0
  19. package/public/assets/{connectors-ByNxzq2k.js → connectors-DLqYF2_3.js} +2 -2
  20. package/public/assets/{connectors-ByNxzq2k.js.map → connectors-DLqYF2_3.js.map} +1 -1
  21. package/public/assets/display-ConJ9cJB.js.map +1 -1
  22. package/public/assets/{formatted-body-impl-BXuR2gfL.js → formatted-body-impl-LK0QcFLC.js} +2 -2
  23. package/public/assets/{formatted-body-impl-BXuR2gfL.js.map → formatted-body-impl-LK0QcFLC.js.map} +1 -1
  24. package/public/assets/{index-CAnJGSBl.js → index-CT10XH5Y.js} +8 -8
  25. package/public/assets/{index-CAnJGSBl.js.map → index-CT10XH5Y.js.map} +1 -1
  26. package/public/assets/index-Ryvn1efN.css +2 -0
  27. package/public/assets/{integrations-BBmL-PnQ.js → integrations-CdeQuRSQ.js} +2 -2
  28. package/public/assets/{integrations-BBmL-PnQ.js.map → integrations-CdeQuRSQ.js.map} +1 -1
  29. package/public/assets/{maintenance-B1bX9ety.js → maintenance-DbqSE_ps.js} +2 -2
  30. package/public/assets/{maintenance-B1bX9ety.js.map → maintenance-DbqSE_ps.js.map} +1 -1
  31. package/public/assets/{managed-agents-D12-nGKd.js → managed-agents-BxhRiBVn.js} +2 -2
  32. package/public/assets/{managed-agents-D12-nGKd.js.map → managed-agents-BxhRiBVn.js.map} +1 -1
  33. package/public/assets/{markdown-preview-impl-BWd3swt8.js → markdown-preview-impl-Dz2Spmat.js} +2 -2
  34. package/public/assets/{markdown-preview-impl-BWd3swt8.js.map → markdown-preview-impl-Dz2Spmat.js.map} +1 -1
  35. package/public/assets/{memory-4ERBFJBZ.js → memory-BTjUKTyA.js} +2 -2
  36. package/public/assets/{memory-4ERBFJBZ.js.map → memory-BTjUKTyA.js.map} +1 -1
  37. package/public/assets/{messages-B3ZES72c.js → messages-C8z05Y3O.js} +2 -2
  38. package/public/assets/{messages-B3ZES72c.js.map → messages-C8z05Y3O.js.map} +1 -1
  39. package/public/assets/{orchestrators-qKPntM-a.js → orchestrators-DQqtYryc.js} +2 -2
  40. package/public/assets/{orchestrators-qKPntM-a.js.map → orchestrators-DQqtYryc.js.map} +1 -1
  41. package/public/assets/{overview-ekmeyPaO.js → overview-CGYu6qQr.js} +2 -2
  42. package/public/assets/{overview-ekmeyPaO.js.map → overview-CGYu6qQr.js.map} +1 -1
  43. package/public/assets/pairs-BgqXzCto.js +2 -0
  44. package/public/assets/{pairs-B2QdTF0M.js.map → pairs-BgqXzCto.js.map} +1 -1
  45. package/public/assets/{security-WkMkEhb7.js → security-BqfRVw-p.js} +2 -2
  46. package/public/assets/{security-WkMkEhb7.js.map → security-BqfRVw-p.js.map} +1 -1
  47. package/public/assets/{settings-C5aXX3od.js → settings-63whklIq.js} +3 -3
  48. package/public/assets/{settings-C5aXX3od.js.map → settings-63whklIq.js.map} +1 -1
  49. package/public/assets/store-B6qT5_de.js +9 -0
  50. package/public/assets/store-B6qT5_de.js.map +1 -0
  51. package/public/assets/tasks-DiZ_4TYW.js +2 -0
  52. package/public/assets/{tasks-ppuVeXkO.js.map → tasks-DiZ_4TYW.js.map} +1 -1
  53. package/public/assets/{terminal-viewer-impl-DXycIJKe.js → terminal-viewer-impl-D3T1F77z.js} +2 -2
  54. package/public/assets/{terminal-viewer-impl-DXycIJKe.js.map → terminal-viewer-impl-D3T1F77z.js.map} +1 -1
  55. package/public/assets/work-queue-C1p2Hybt.js +2 -0
  56. package/public/assets/{work-queue-C8UzeekT.js.map → work-queue-C1p2Hybt.js.map} +1 -1
  57. package/public/assets/{workspaces-BD1P6J21.js → workspaces-DqVr0uN7.js} +2 -2
  58. package/public/assets/{workspaces-BD1P6J21.js.map → workspaces-DqVr0uN7.js.map} +1 -1
  59. package/public/index.html +3 -3
  60. package/src/agent-ref.ts +7 -4
  61. package/src/channel-target.ts +1 -0
  62. package/src/db/agent-search.ts +6 -0
  63. package/src/db/agents.ts +11 -2
  64. package/src/db/channels.ts +2 -1
  65. package/src/db/delivery.ts +4 -1
  66. package/src/db/index.ts +1 -0
  67. package/src/db/mappers.ts +1 -1
  68. package/src/db/message-reads.ts +10 -1
  69. package/src/db/migrations.ts +54 -2
  70. package/src/db/plans.ts +17 -2
  71. package/src/db/schema.ts +24 -3
  72. package/src/db/teams.ts +151 -0
  73. package/src/mcp-plan-tools.ts +7 -3
  74. package/src/mcp-team-tools.ts +87 -0
  75. package/src/mcp.ts +6 -1
  76. package/src/routes/index.ts +4 -0
  77. package/src/routes/integrations.ts +2 -1
  78. package/src/routes/messages.ts +1 -1
  79. package/src/routes/plans.ts +9 -3
  80. package/src/routes/teams.ts +60 -0
  81. package/src/runtime-tokens.ts +9 -3
  82. package/src/security.ts +7 -1
  83. package/src/services/plan-graph.ts +17 -3
  84. package/src/services/register-agent.ts +3 -1
  85. package/src/services/spawn-agent.ts +4 -1
  86. package/src/services/team.ts +69 -0
  87. package/src/token-db.ts +3 -3
  88. package/src/validation.ts +2 -0
  89. package/public/assets/channels-CQy-JL7o.js +0 -2
  90. package/public/assets/chat-KaK5_x65.js +0 -2
  91. package/public/assets/chat-KaK5_x65.js.map +0 -1
  92. package/public/assets/index-DzRKGPXS.css +0 -2
  93. package/public/assets/pairs-B2QdTF0M.js +0 -2
  94. package/public/assets/store-CtKmqMfx.js +0 -9
  95. package/public/assets/store-CtKmqMfx.js.map +0 -1
  96. package/public/assets/tasks-ppuVeXkO.js +0 -2
  97. package/public/assets/work-queue-C8UzeekT.js +0 -2
@@ -0,0 +1,60 @@
1
+ import { McpAuthError, McpNotFoundError } from "../mcp-errors";
2
+ import { authContextFromRequest } from "../services/auth-context";
3
+ import { ValidationError } from "../db";
4
+ import { dissolveCallerTeam, getCallerTeam, setCallerTeamBrief, TeamAuthError, TeamNotFoundError } from "../services/team";
5
+ import { cleanString, optionalEnum } from "../validation";
6
+ import { error, json, parseBody, type Handler } from "./_shared";
7
+ import type { Team } from "../types";
8
+
9
+ const OUTCOME_STATUSES = ["success", "partial", "failed"] as const;
10
+
11
+ function routeError(err: unknown): Response {
12
+ if (err instanceof TeamAuthError || err instanceof McpAuthError) return error(err.message, 403);
13
+ if (err instanceof TeamNotFoundError || err instanceof McpNotFoundError) return error(err.message, 404);
14
+ if (err instanceof Error) return error(err.message);
15
+ return error("team route failed");
16
+ }
17
+
18
+ export const getMyTeamRoute: Handler = (req) => {
19
+ try {
20
+ const channel = cleanString(new URL(req.url).searchParams.get("channel") ?? undefined, "channel", { max: 120 });
21
+ return json(getCallerTeam(authContextFromRequest(req), channel));
22
+ } catch (err) {
23
+ return routeError(err);
24
+ }
25
+ };
26
+
27
+ export const putMyTeamBriefRoute: Handler = async (req) => {
28
+ const parsed = await parseBody<Record<string, unknown>>(req);
29
+ if (!parsed.ok) return error(parsed.error, parsed.status);
30
+ try {
31
+ const body = parsed.body ?? {};
32
+ return json(setCallerTeamBrief(authContextFromRequest(req), {
33
+ title: cleanString(body.title, "title", { max: 240 }),
34
+ brief: cleanString(body.brief, "brief", { max: 16_000 }),
35
+ definitionOfDone: cleanString(body.definitionOfDone, "definitionOfDone", { max: 4000 }),
36
+ }));
37
+ } catch (err) {
38
+ return routeError(err);
39
+ }
40
+ };
41
+
42
+ export const postMyTeamDissolveRoute: Handler = async (req) => {
43
+ const parsed = await parseBody<Record<string, unknown>>(req);
44
+ if (!parsed.ok) return error(parsed.error, parsed.status);
45
+ try {
46
+ const body = parsed.body ?? {};
47
+ const landsClean = body.landsClean;
48
+ return json(dissolveCallerTeam(authContextFromRequest(req), {
49
+ outcomeStatus: optionalEnum(body.outcomeStatus, "outcomeStatus", OUTCOME_STATUSES) as Team["outcomeStatus"] | undefined,
50
+ outcomeSummary: cleanString(body.outcomeSummary, "outcomeSummary", { max: 4000 }),
51
+ landsClean: landsClean === undefined || landsClean === null
52
+ ? undefined
53
+ : typeof landsClean === "boolean"
54
+ ? landsClean
55
+ : (() => { throw new ValidationError("landsClean must be a boolean"); })(),
56
+ }));
57
+ } catch (err) {
58
+ return routeError(err);
59
+ }
60
+ };
@@ -10,9 +10,10 @@ import type { TokenRecord } from "./types";
10
10
  // purpose — that scope must stay for heartbeat/status without implying spawn rights.
11
11
  const SPAWN_SCOPES = ["command:spawn", "command:shutdown"] as const;
12
12
 
13
- function runnerScopeWithSpawn(canSpawn: boolean): string[] | undefined {
14
- if (!canSpawn) return undefined; // undefined → createToken falls back to the profile's scope
13
+ function runnerScopeWithSpawn(canSpawn: boolean, agentInitiated?: boolean): string[] | undefined {
15
14
  const base = getTokenProfile("provider-agent")?.scope ?? [];
15
+ if (agentInitiated) return base.filter((scope) => scope !== "teams:write");
16
+ if (!canSpawn) return undefined; // undefined → createToken falls back to the profile's scope
16
17
  return [...base, ...SPAWN_SCOPES];
17
18
  }
18
19
 
@@ -101,6 +102,8 @@ export function issueRunnerRuntimeToken(input: {
101
102
  agentInitiated?: boolean;
102
103
  /** Parent agent id — stamped so the child registers with an authoritative `spawnedBy`. */
103
104
  spawnedBy?: string;
105
+ /** Durable team id — stamped so the child registers into the coordinator's team. */
106
+ teamId?: string;
104
107
  }): RuntimeTokenResult {
105
108
  const grant = spawnGrantForProfile(input.profile, input.agentInitiated);
106
109
  const subject = input.policyName
@@ -112,7 +115,7 @@ export function issueRunnerRuntimeToken(input: {
112
115
  profileId: "provider-agent",
113
116
  sub: subject,
114
117
  role: "provider",
115
- scope: runnerScopeWithSpawn(grant.canSpawn),
118
+ scope: runnerScopeWithSpawn(grant.canSpawn, input.agentInitiated),
116
119
  constraints: {
117
120
  orchestrators: [input.orchestratorId],
118
121
  cwdPrefixes: [input.cwd],
@@ -120,6 +123,7 @@ export function issueRunnerRuntimeToken(input: {
120
123
  ...(input.spawnRequestId ? { spawnRequestIds: [input.spawnRequestId] } : {}),
121
124
  ...(grant.canSpawn && grant.maxSpawnedAgents ? { maxSpawnedAgents: grant.maxSpawnedAgents } : {}),
122
125
  ...(input.spawnedBy ? { spawnedBy: input.spawnedBy } : {}),
126
+ ...(input.teamId ? { teamId: input.teamId } : {}),
123
127
  },
124
128
  createdBy: input.createdBy ?? "runtime",
125
129
  });
@@ -133,6 +137,7 @@ function issueChildRunnerRuntimeToken(input: {
133
137
  label?: string;
134
138
  policyName?: string;
135
139
  spawnRequestId: string;
140
+ teamId?: string;
136
141
  createdBy?: string;
137
142
  }): RuntimeTokenResult {
138
143
  return createToken({
@@ -146,6 +151,7 @@ function issueChildRunnerRuntimeToken(input: {
146
151
  cwdPrefixes: [input.cwd],
147
152
  spawnRequestIds: [input.spawnRequestId],
148
153
  canDelegate: false,
154
+ ...(input.teamId ? { teamId: input.teamId } : {}),
149
155
  ...(input.policyName ? { policies: [input.policyName] } : {}),
150
156
  },
151
157
  createdBy: input.createdBy ?? input.parentAgentId,
package/src/security.ts CHANGED
@@ -204,6 +204,7 @@ export function requiredScopeFor(method: string, pathname: string): string | nul
204
204
  if (pathname.startsWith("/api/maintenance")) return "system:admin";
205
205
  if (pathname.startsWith("/api/tasks")) return method === "GET" ? "task:read" : "task:write";
206
206
  if (pathname.startsWith("/api/pairs")) return method === "GET" ? "pairs:read" : "pairs:write";
207
+ if (pathname.startsWith("/api/teams")) return method === "GET" ? "teams:read" : "teams:write";
207
208
  // Insights config (the feature toggle) stays admin-only via the default; only the
208
209
  // mechanical observation feed is writable by lower-privilege callers.
209
210
  if (pathname === "/api/insights/observations") return method === "GET" ? "insights:read" : "insights:write";
@@ -278,6 +279,7 @@ export function requiredComponentScopeFor(method: string, pathname: string): str
278
279
  if (pathname.startsWith("/api/tasks")) return method === "GET" ? "task:read" : "task:write";
279
280
  if (pathname.startsWith("/api/orchestrators")) return method === "GET" ? "agent:read" : "command:write";
280
281
  if (pathname.startsWith("/api/plans")) return method === "GET" ? "plans:read" : "plans:write";
282
+ if (pathname.startsWith("/api/teams")) return method === "GET" ? "teams:read" : "teams:write";
281
283
  // The Runner posts the #184 context-gathering signal here (source:"server") via its
282
284
  // provider token. Without this case the path fell through to the system:admin default
283
285
  // below and every observation was 403-dropped — the whole Insights feed stayed empty.
@@ -415,6 +417,10 @@ export function resolveSpawnLineage(constraints: TokenConstraints | undefined):
415
417
  return constraints?.spawnedBy ?? constraints?.parentAgents?.[0];
416
418
  }
417
419
 
420
+ export function resolveTeamLineage(constraints: TokenConstraints | undefined): string | undefined {
421
+ return constraints?.teamId;
422
+ }
423
+
418
424
  function constraintsAllow(constraints: TokenConstraints | undefined, resource: AuthorizationResource | undefined): boolean {
419
425
  if (!constraints || !resource) return true;
420
426
  const hasConstraints = Object.keys(constraints).length > 0;
@@ -462,7 +468,7 @@ function isTokenConstraints(value: unknown): value is TokenConstraints {
462
468
  for (const [key, item] of Object.entries(record)) {
463
469
  if (["terminalAttach", "logsRead", "canDelegate"].includes(key)) {
464
470
  if (typeof item !== "boolean") return false;
465
- } else if (key === "cwd" || key === "spawnedBy") {
471
+ } else if (key === "cwd" || key === "spawnedBy" || key === "teamId") {
466
472
  if (typeof item !== "string") return false;
467
473
  } else if (key === "maxSpawnedAgents") {
468
474
  if (typeof item !== "number") return false;
@@ -1,5 +1,5 @@
1
1
  import { isRecord } from "agent-relay-sdk";
2
- import { createPlanRow, getPlan, getPlanByThread, planThreadExists, replacePlan, ValidationError } from "../db";
2
+ import { createPlanRow, deletePlan, getAgent, getPlan, getPlanByTeam, getPlanByThread, planThreadExists, replacePlan, ValidationError } from "../db";
3
3
  import { emitRelayEvent } from "../events";
4
4
  import { cleanString, optionalEnum } from "../validation";
5
5
  import type { AuthContext } from "./auth-context";
@@ -30,6 +30,7 @@ export interface PlanNodePatch {
30
30
 
31
31
  export interface PlanCreateInput {
32
32
  threadId: unknown;
33
+ teamId?: unknown;
33
34
  channel?: unknown;
34
35
  title?: unknown;
35
36
  objective?: unknown;
@@ -172,7 +173,7 @@ function existingPlan(id: string): Plan {
172
173
  return plan;
173
174
  }
174
175
 
175
- function emitPlan(type: "plan.created" | "plan.changed", plan: Plan, actor: string): void {
176
+ function emitPlan(type: "plan.created" | "plan.changed" | "plan.deleted", plan: Plan, actor: string): void {
176
177
  emitRelayEvent({ type, source: actor, subject: plan.id, data: { plan, actor } });
177
178
  }
178
179
 
@@ -184,8 +185,11 @@ export function createPlan(input: PlanCreateInput, ctx: AuthContext): Plan {
184
185
  const edges = cleanEdges(input.edges);
185
186
  validateEdges(nodes, edges);
186
187
  const actor = actorId(ctx);
188
+ const explicitTeamId = cleanString(input.teamId, "teamId", { max: 120 });
189
+ const teamId = explicitTeamId ?? (input.teamId === undefined || input.teamId === null ? getAgent(actor)?.teamId : undefined);
187
190
  const plan = createPlanRow({
188
191
  threadId,
192
+ teamId,
189
193
  channel,
190
194
  title: cleanString(input.title, "title", { max: 240 }),
191
195
  objective: cleanString(input.objective, "objective", { max: 4000 }),
@@ -262,9 +266,19 @@ export function removePlanEdge(planId: string, edgeInput: unknown, ctx: AuthCont
262
266
  return updated;
263
267
  }
264
268
 
265
- export function getPlanGraph(input: { planId?: unknown; threadId?: unknown; channel?: unknown }): Plan | null {
269
+ export function deletePlanGraph(planId: string, ctx: AuthContext): { id: string; deleted: true } {
270
+ const plan = existingPlan(planId);
271
+ const actor = actorId(ctx);
272
+ deletePlan(plan.id, actor);
273
+ emitPlan("plan.deleted", plan, actor);
274
+ return { id: plan.id, deleted: true };
275
+ }
276
+
277
+ export function getPlanGraph(input: { planId?: unknown; threadId?: unknown; teamId?: unknown; channel?: unknown }): Plan | null {
266
278
  const planId = cleanString(input.planId, "planId", { max: 120 });
267
279
  if (planId) return getPlan(planId);
280
+ const teamId = cleanString(input.teamId, "teamId", { max: 120 });
281
+ if (teamId) return getPlanByTeam(teamId, cleanString(input.channel, "channel", { max: 120 }));
268
282
  const threadId = cleanThreadId(input.threadId);
269
283
  return getPlanByThread(threadId, cleanString(input.channel, "channel", { max: 120 }));
270
284
  }
@@ -13,7 +13,7 @@ import { createActivityEvent, getAgent, upsertAgent } from "../db";
13
13
  import { emitAgentStatus } from "../sse";
14
14
  import { noteAgentTimelineEvent } from "../compaction-watch";
15
15
  import { notifyAgentReady } from "../agent-lifecycle-events";
16
- import { resolveSpawnLineage } from "../security";
16
+ import { resolveSpawnLineage, resolveTeamLineage } from "../security";
17
17
  import { markManagedAgentRunning } from "./managed-running";
18
18
  import type { AuthContext } from "./auth-context";
19
19
  import type { AgentCard, RegisterAgentInput } from "../types";
@@ -28,6 +28,8 @@ export function registerAgent(input: RegisterAgentInput, ctx: AuthContext): Agen
28
28
  // the client-sent body (a child can't forge its own parent). Set by relay at spawn.
29
29
  const lineage = resolveSpawnLineage(ctx.constraints);
30
30
  if (lineage) input.spawnedBy = lineage;
31
+ const teamLineage = resolveTeamLineage(ctx.constraints);
32
+ if (teamLineage) input.teamId = teamLineage;
31
33
 
32
34
  const existing = getAgent(input.id);
33
35
  const agent = upsertAgent(input);
@@ -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, getAgent, listAgents, ValidationError } from "../db";
28
+ import { countLiveSpawnedAgents, createActivityEvent, ensureTeam, getAgent, listAgents, 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";
@@ -178,6 +178,8 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
178
178
  // Provenance actor: the resolved caller agent when known, else the transport's label, else
179
179
  // the raw token actor. Recorded in the command payload, the token mint, and the audit row.
180
180
  const requestedBy = callerId ?? input.requestedBy ?? ctx.actor.id;
181
+ const callerTeam = callerId ? (caller?.teamId ? { id: caller.teamId } : ensureTeam(callerId)) : undefined;
182
+ if (callerId && callerTeam && !caller?.teamId) setAgentTeam(callerId, callerTeam.id);
181
183
 
182
184
  // Child runner token. `agentInitiated` (set whenever an agent is the caller) forces a
183
185
  // non-spawn-capable child (no grandchildren); `spawnedBy` stamps authoritative lineage the
@@ -197,6 +199,7 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
197
199
  // child regardless of profile (no grandchildren).
198
200
  profile: input.profile || undefined,
199
201
  ...(callerId ? { agentInitiated: true, spawnedBy: callerId } : {}),
202
+ ...(callerTeam ? { teamId: callerTeam.id } : {}),
200
203
  });
201
204
 
202
205
  const command = createCommand({
@@ -0,0 +1,69 @@
1
+ import { dissolveTeam, ensureTeam, getAgent, getPlanByTeam, getTeam, getTeamRoster, setTeamBrief } from "../db";
2
+ import type { AuthContext } from "./auth-context";
3
+ import type { Team } from "../types";
4
+
5
+ export class TeamAuthError extends Error {}
6
+ export class TeamNotFoundError extends Error {}
7
+
8
+ export interface TeamPayload {
9
+ team: Team;
10
+ roster: ReturnType<typeof getTeamRoster>;
11
+ brief: string | null;
12
+ definitionOfDone: string | null;
13
+ plan: ReturnType<typeof getPlanByTeam>;
14
+ }
15
+
16
+ export interface TeamBriefInput {
17
+ title?: string;
18
+ brief?: string;
19
+ definitionOfDone?: string;
20
+ }
21
+
22
+ export interface TeamDissolveServiceInput {
23
+ outcomeStatus?: Team["outcomeStatus"];
24
+ outcomeSummary?: string;
25
+ landsClean?: boolean;
26
+ }
27
+
28
+ function requireCallerAgentId(ctx: AuthContext): string {
29
+ if (!ctx.callerAgentId) throw new TeamAuthError("team operations require a single caller agent identity");
30
+ return ctx.callerAgentId;
31
+ }
32
+
33
+ function payloadForAgent(agentId: string, channel?: string): TeamPayload {
34
+ const agent = getAgent(agentId);
35
+ const team = agent?.teamId ? getTeam(agent.teamId) : null;
36
+ if (!team) throw new TeamNotFoundError("caller has no team");
37
+ return {
38
+ team,
39
+ roster: getTeamRoster(team.id),
40
+ brief: team.brief ?? null,
41
+ definitionOfDone: team.definitionOfDone ?? null,
42
+ plan: getPlanByTeam(team.id, channel) ?? null,
43
+ };
44
+ }
45
+
46
+ export function getCallerTeam(ctx: AuthContext, channel?: string): TeamPayload {
47
+ return payloadForAgent(requireCallerAgentId(ctx), channel);
48
+ }
49
+
50
+ export function setCallerTeamBrief(ctx: AuthContext, input: TeamBriefInput): TeamPayload {
51
+ const agentId = requireCallerAgentId(ctx);
52
+ const team = ensureTeam(agentId);
53
+ setTeamBrief({ teamId: team.id, ...input });
54
+ return payloadForAgent(agentId);
55
+ }
56
+
57
+ export function dissolveCallerTeam(ctx: AuthContext, input: TeamDissolveServiceInput): TeamPayload {
58
+ const agentId = requireCallerAgentId(ctx);
59
+ const agent = getAgent(agentId);
60
+ if (!agent?.teamId) throw new TeamNotFoundError("caller has no team");
61
+ const team = dissolveTeam(agent.teamId, input);
62
+ return {
63
+ team,
64
+ roster: getTeamRoster(team.id),
65
+ brief: team.brief ?? null,
66
+ definitionOfDone: team.definitionOfDone ?? null,
67
+ plan: getPlanByTeam(team.id) ?? null,
68
+ };
69
+ }
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", "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", "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", "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", "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", "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", "insights:write"],
82
82
  constraints: { terminalAttach: false, logsRead: false, canDelegate: false },
83
83
  ttlSeconds: 30 * 24 * 60 * 60,
84
84
  builtIn: true,
package/src/validation.ts CHANGED
@@ -122,6 +122,8 @@ export function cleanTokenConstraints(value: unknown): TokenConstraints | undefi
122
122
  }
123
123
  const cwd = cleanString(value.cwd, "constraints.cwd", { max: 500 });
124
124
  if (cwd) constraints.cwd = cwd;
125
+ const teamId = cleanString(value.teamId, "constraints.teamId", { max: 120 });
126
+ if (teamId) constraints.teamId = teamId;
125
127
  for (const key of ["terminalAttach", "logsRead", "canDelegate"] as const) {
126
128
  if (value[key] !== undefined) {
127
129
  if (typeof value[key] !== "boolean") throw new ValidationError(`constraints.${key} must be a boolean`);
@@ -1,2 +0,0 @@
1
- import{Cn as e,Pt as t,Sn as n,jn as r,q as i,xn as a}from"./lucide-react-ChOGD4vc.js";import{a as o,t as s}from"./store-CtKmqMfx.js";import{G as c,H as l,W as u,c as d,s as f}from"./display-ConJ9cJB.js";import{t as p}from"./badge-CFVr4fG9.js";import{t as m}from"./button-BuTu0MHx.js";import{i as h,n as g,r as _,t as v}from"./card-C3ErQ372.js";var y=r();function b({direction:t}){return t===`inbound`?(0,y.jsx)(n,{className:`w-3 h-3`}):t===`outbound`?(0,y.jsx)(a,{className:`w-3 h-3`}):(0,y.jsx)(e,{className:`w-3 h-3`})}function x({channel:e}){let n=o(),r=s(e=>e.openChannelDetail),a=d(e),f=e.status!==`offline`;return(0,y.jsxs)(v,{className:`${f?``:`opacity-60`} hover:border-zinc-600 transition-colors`,children:[(0,y.jsx)(_,{className:`pb-2 pt-4 px-4`,children:(0,y.jsxs)(`div`,{className:`flex items-start gap-2`,children:[(0,y.jsx)(`div`,{className:`p-1.5 rounded-md ${f?`bg-blue-500/10`:`bg-zinc-800`}`,children:(0,y.jsx)(i,{className:`w-4 h-4 ${f?`text-blue-400`:`text-zinc-500`}`})}),(0,y.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,y.jsx)(h,{className:`text-sm font-medium truncate`,children:e.name||e.id}),(0,y.jsxs)(`div`,{className:`text-xs text-muted-foreground mt-0.5`,children:[e.type,` · `,e.transport]})]}),(0,y.jsx)(p,{className:`text-[10px] shrink-0 ${u(a.tone)}`,children:a.label})]})}),(0,y.jsxs)(g,{className:`px-4 pb-4 space-y-2.5`,children:[(0,y.jsxs)(`div`,{className:`flex items-center gap-1.5 text-xs text-muted-foreground`,children:[(0,y.jsx)(b,{direction:e.direction}),(0,y.jsx)(`span`,{className:`capitalize`,children:e.direction}),e.target&&(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(`span`,{children:`·`}),(0,y.jsx)(`span`,{className:`truncate text-foreground`,children:e.target})]})]}),e.targetHealth&&e.targetHealth.status!==`ok`&&(0,y.jsx)(`div`,{className:`text-xs px-2 py-1 rounded ${e.targetHealth.status===`error`?`bg-red-500/10 text-red-400`:`bg-yellow-500/10 text-yellow-400`}`,children:e.targetHealth.detail}),c(e.capabilities).length>0&&(0,y.jsx)(`div`,{className:`flex flex-wrap gap-1`,children:c(e.capabilities).map(e=>(0,y.jsx)(p,{variant:`secondary`,className:`text-[9px] px-1 py-0 h-4`,children:e},e))}),e.tags.length>0&&(0,y.jsx)(`div`,{className:`flex flex-wrap gap-1`,children:e.tags.map(e=>(0,y.jsx)(p,{variant:`outline`,className:`text-[9px] px-1 py-0 h-4`,children:e},e))}),(0,y.jsxs)(`div`,{className:`flex items-center justify-between pt-0.5`,children:[(0,y.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:l(n,e.lastSeen)}),(0,y.jsxs)(m,{size:`sm`,variant:`outline`,className:`h-7 text-xs gap-1`,onClick:()=>r(e),children:[(0,y.jsx)(t,{className:`w-3 h-3`}),`Details`]})]})]})]})}function S(){let e=s(e=>e.channels),t=e.filter(f).length,n=e.filter(e=>e.targetHealth?.status===`error`).length,r=e.filter(e=>e.targetHealth?.status===`warning`).length;return(0,y.jsxs)(`div`,{className:`space-y-4`,children:[(0,y.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,y.jsxs)(`div`,{children:[(0,y.jsx)(`h2`,{className:`text-base font-semibold`,children:`Channels`}),(0,y.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Inbound / outbound message integrations`})]}),(0,y.jsxs)(`div`,{className:`flex items-center gap-2 ml-auto`,children:[t>0&&(0,y.jsxs)(p,{className:`text-xs bg-emerald-500/15 text-emerald-400 border-emerald-500/30`,children:[t,` ready`]}),r>0&&(0,y.jsxs)(p,{className:`text-xs bg-yellow-500/15 text-yellow-400 border-yellow-500/30`,children:[r,` warning`]}),n>0&&(0,y.jsxs)(p,{className:`text-xs bg-red-500/15 text-red-400 border-red-500/30`,children:[n,` error`]})]})]}),e.length===0?(0,y.jsx)(`div`,{className:`text-center py-16 text-muted-foreground text-sm`,children:`No channels registered.`}):(0,y.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4`,children:e.map(e=>(0,y.jsx)(x,{channel:e},e.id))})]})}export{S as ChannelsView};
2
- //# sourceMappingURL=channels-CQy-JL7o.js.map