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.
- package/docs/openapi.json +85 -1
- package/package.json +2 -2
- package/public/assets/{activity-G6l1nOLj.js → activity-Dkzmef0Z.js} +2 -2
- package/public/assets/{activity-G6l1nOLj.js.map → activity-Dkzmef0Z.js.map} +1 -1
- package/public/assets/{agent-profiles-BWKpVoNw.js → agent-profiles-DQYHMAyu.js} +2 -2
- package/public/assets/{agent-profiles-BWKpVoNw.js.map → agent-profiles-DQYHMAyu.js.map} +1 -1
- package/public/assets/{agents-DBuCRS5A.js → agents-atr6HZ1e.js} +2 -2
- package/public/assets/{agents-DBuCRS5A.js.map → agents-atr6HZ1e.js.map} +1 -1
- package/public/assets/{analytics-BhwMtd0_.js → analytics-azFfAy4R.js} +2 -2
- package/public/assets/{analytics-BhwMtd0_.js.map → analytics-azFfAy4R.js.map} +1 -1
- package/public/assets/{automation-B7jnsYhH.js → automation-Bk4umHIm.js} +2 -2
- package/public/assets/{automation-B7jnsYhH.js.map → automation-Bk4umHIm.js.map} +1 -1
- package/public/assets/{branch-state-badge-dN2mQ2gs.js → branch-state-badge-DymObp8d.js} +2 -2
- package/public/assets/{branch-state-badge-dN2mQ2gs.js.map → branch-state-badge-DymObp8d.js.map} +1 -1
- package/public/assets/channels-B_0wDy0s.js +2 -0
- package/public/assets/{channels-CQy-JL7o.js.map → channels-B_0wDy0s.js.map} +1 -1
- package/public/assets/chat-DxV4MyYF.js +2 -0
- package/public/assets/chat-DxV4MyYF.js.map +1 -0
- package/public/assets/{connectors-ByNxzq2k.js → connectors-DLqYF2_3.js} +2 -2
- package/public/assets/{connectors-ByNxzq2k.js.map → connectors-DLqYF2_3.js.map} +1 -1
- package/public/assets/display-ConJ9cJB.js.map +1 -1
- package/public/assets/{formatted-body-impl-BXuR2gfL.js → formatted-body-impl-LK0QcFLC.js} +2 -2
- package/public/assets/{formatted-body-impl-BXuR2gfL.js.map → formatted-body-impl-LK0QcFLC.js.map} +1 -1
- package/public/assets/{index-CAnJGSBl.js → index-CT10XH5Y.js} +8 -8
- package/public/assets/{index-CAnJGSBl.js.map → index-CT10XH5Y.js.map} +1 -1
- package/public/assets/index-Ryvn1efN.css +2 -0
- package/public/assets/{integrations-BBmL-PnQ.js → integrations-CdeQuRSQ.js} +2 -2
- package/public/assets/{integrations-BBmL-PnQ.js.map → integrations-CdeQuRSQ.js.map} +1 -1
- package/public/assets/{maintenance-B1bX9ety.js → maintenance-DbqSE_ps.js} +2 -2
- package/public/assets/{maintenance-B1bX9ety.js.map → maintenance-DbqSE_ps.js.map} +1 -1
- package/public/assets/{managed-agents-D12-nGKd.js → managed-agents-BxhRiBVn.js} +2 -2
- package/public/assets/{managed-agents-D12-nGKd.js.map → managed-agents-BxhRiBVn.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-BWd3swt8.js → markdown-preview-impl-Dz2Spmat.js} +2 -2
- package/public/assets/{markdown-preview-impl-BWd3swt8.js.map → markdown-preview-impl-Dz2Spmat.js.map} +1 -1
- package/public/assets/{memory-4ERBFJBZ.js → memory-BTjUKTyA.js} +2 -2
- package/public/assets/{memory-4ERBFJBZ.js.map → memory-BTjUKTyA.js.map} +1 -1
- package/public/assets/{messages-B3ZES72c.js → messages-C8z05Y3O.js} +2 -2
- package/public/assets/{messages-B3ZES72c.js.map → messages-C8z05Y3O.js.map} +1 -1
- package/public/assets/{orchestrators-qKPntM-a.js → orchestrators-DQqtYryc.js} +2 -2
- package/public/assets/{orchestrators-qKPntM-a.js.map → orchestrators-DQqtYryc.js.map} +1 -1
- package/public/assets/{overview-ekmeyPaO.js → overview-CGYu6qQr.js} +2 -2
- package/public/assets/{overview-ekmeyPaO.js.map → overview-CGYu6qQr.js.map} +1 -1
- package/public/assets/pairs-BgqXzCto.js +2 -0
- package/public/assets/{pairs-B2QdTF0M.js.map → pairs-BgqXzCto.js.map} +1 -1
- package/public/assets/{security-WkMkEhb7.js → security-BqfRVw-p.js} +2 -2
- package/public/assets/{security-WkMkEhb7.js.map → security-BqfRVw-p.js.map} +1 -1
- package/public/assets/{settings-C5aXX3od.js → settings-63whklIq.js} +3 -3
- package/public/assets/{settings-C5aXX3od.js.map → settings-63whklIq.js.map} +1 -1
- package/public/assets/store-B6qT5_de.js +9 -0
- package/public/assets/store-B6qT5_de.js.map +1 -0
- package/public/assets/tasks-DiZ_4TYW.js +2 -0
- package/public/assets/{tasks-ppuVeXkO.js.map → tasks-DiZ_4TYW.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-DXycIJKe.js → terminal-viewer-impl-D3T1F77z.js} +2 -2
- package/public/assets/{terminal-viewer-impl-DXycIJKe.js.map → terminal-viewer-impl-D3T1F77z.js.map} +1 -1
- package/public/assets/work-queue-C1p2Hybt.js +2 -0
- package/public/assets/{work-queue-C8UzeekT.js.map → work-queue-C1p2Hybt.js.map} +1 -1
- package/public/assets/{workspaces-BD1P6J21.js → workspaces-DqVr0uN7.js} +2 -2
- package/public/assets/{workspaces-BD1P6J21.js.map → workspaces-DqVr0uN7.js.map} +1 -1
- package/public/index.html +3 -3
- package/src/agent-ref.ts +7 -4
- package/src/channel-target.ts +1 -0
- package/src/db/agent-search.ts +6 -0
- package/src/db/agents.ts +11 -2
- package/src/db/channels.ts +2 -1
- package/src/db/delivery.ts +4 -1
- package/src/db/index.ts +1 -0
- package/src/db/mappers.ts +1 -1
- package/src/db/message-reads.ts +10 -1
- package/src/db/migrations.ts +54 -2
- package/src/db/plans.ts +17 -2
- package/src/db/schema.ts +24 -3
- package/src/db/teams.ts +151 -0
- package/src/mcp-plan-tools.ts +7 -3
- package/src/mcp-team-tools.ts +87 -0
- package/src/mcp.ts +6 -1
- package/src/routes/index.ts +4 -0
- package/src/routes/integrations.ts +2 -1
- package/src/routes/messages.ts +1 -1
- package/src/routes/plans.ts +9 -3
- package/src/routes/teams.ts +60 -0
- package/src/runtime-tokens.ts +9 -3
- package/src/security.ts +7 -1
- package/src/services/plan-graph.ts +17 -3
- package/src/services/register-agent.ts +3 -1
- package/src/services/spawn-agent.ts +4 -1
- package/src/services/team.ts +69 -0
- package/src/token-db.ts +3 -3
- package/src/validation.ts +2 -0
- package/public/assets/channels-CQy-JL7o.js +0 -2
- package/public/assets/chat-KaK5_x65.js +0 -2
- package/public/assets/chat-KaK5_x65.js.map +0 -1
- package/public/assets/index-DzRKGPXS.css +0 -2
- package/public/assets/pairs-B2QdTF0M.js +0 -2
- package/public/assets/store-CtKmqMfx.js +0 -9
- package/public/assets/store-CtKmqMfx.js.map +0 -1
- package/public/assets/tasks-ppuVeXkO.js +0 -2
- 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
|
+
};
|
package/src/runtime-tokens.ts
CHANGED
|
@@ -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
|
|
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
|