agent-relay-server 0.111.0 → 0.113.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 +1 -1
- package/package.json +3 -3
- package/public/assets/{MessageBubble-WYqSyKPM.js → MessageBubble-CFujR6_L.js} +3 -3
- package/public/assets/{MessageBubble-WYqSyKPM.js.map → MessageBubble-CFujR6_L.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-Dvu17jGe.js → PlanGraphPanel-BfxvqmNS.js} +2 -2
- package/public/assets/{PlanGraphPanel-Dvu17jGe.js.map → PlanGraphPanel-BfxvqmNS.js.map} +1 -1
- package/public/assets/{activity-wAN_upn7.js → activity-C7-gIF9O.js} +2 -2
- package/public/assets/{activity-wAN_upn7.js.map → activity-C7-gIF9O.js.map} +1 -1
- package/public/assets/{agent-profiles-D8WLdTCs.js → agent-profiles-BGPa2-BZ.js} +2 -2
- package/public/assets/{agent-profiles-D8WLdTCs.js.map → agent-profiles-BGPa2-BZ.js.map} +1 -1
- package/public/assets/{agents-C5FajNPw.js → agents-CWCbBuq6.js} +2 -2
- package/public/assets/{agents-C5FajNPw.js.map → agents-CWCbBuq6.js.map} +1 -1
- package/public/assets/{analytics-Q6Dl_CE9.js → analytics-BhGpT0JR.js} +2 -2
- package/public/assets/{analytics-Q6Dl_CE9.js.map → analytics-BhGpT0JR.js.map} +1 -1
- package/public/assets/automation-DnpahVqs.js +2 -0
- package/public/assets/automation-DnpahVqs.js.map +1 -0
- package/public/assets/{branch-state-badge-BRjUWyMT.js → branch-state-badge-CcAq2Igv.js} +2 -2
- package/public/assets/{branch-state-badge-BRjUWyMT.js.map → branch-state-badge-CcAq2Igv.js.map} +1 -1
- package/public/assets/{channels-DOYB1iiA.js → channels-DBP3-Gg0.js} +2 -2
- package/public/assets/{channels-DOYB1iiA.js.map → channels-DBP3-Gg0.js.map} +1 -1
- package/public/assets/{chat-aRCk0pLJ.js → chat-Bue-qNx_.js} +2 -2
- package/public/assets/{chat-aRCk0pLJ.js.map → chat-Bue-qNx_.js.map} +1 -1
- package/public/assets/{connectors-C5CeK-2M.js → connectors-BJxbuxoU.js} +2 -2
- package/public/assets/{connectors-C5CeK-2M.js.map → connectors-BJxbuxoU.js.map} +1 -1
- package/public/assets/{formatted-body-impl-DZc7C4-2.js → formatted-body-impl-CTz6wch8.js} +2 -2
- package/public/assets/formatted-body-impl-CTz6wch8.js.map +1 -0
- package/public/assets/{index-DXEYz_JV.js → index-BMAblAKQ.js} +7 -7
- package/public/assets/index-BMAblAKQ.js.map +1 -0
- package/public/assets/{insights-FzTX2wNh.js → insights-CZ_JrJ8M.js} +2 -2
- package/public/assets/{insights-FzTX2wNh.js.map → insights-CZ_JrJ8M.js.map} +1 -1
- package/public/assets/{integrations-CztQfrI7.js → integrations-L75xhZd5.js} +2 -2
- package/public/assets/{integrations-CztQfrI7.js.map → integrations-L75xhZd5.js.map} +1 -1
- package/public/assets/{maintenance-C8Uw8XiI.js → maintenance-Dpb87NaB.js} +2 -2
- package/public/assets/{maintenance-C8Uw8XiI.js.map → maintenance-Dpb87NaB.js.map} +1 -1
- package/public/assets/{managed-agents-DVbJ2hqh.js → managed-agents-DyGbrKwJ.js} +2 -2
- package/public/assets/{managed-agents-DVbJ2hqh.js.map → managed-agents-DyGbrKwJ.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-C5D-B-9S.js → markdown-preview-impl-Bx4AcI8G.js} +2 -2
- package/public/assets/{markdown-preview-impl-C5D-B-9S.js.map → markdown-preview-impl-Bx4AcI8G.js.map} +1 -1
- package/public/assets/{memory-Bp-D_V80.js → memory-2l6G_m1p.js} +2 -2
- package/public/assets/{memory-Bp-D_V80.js.map → memory-2l6G_m1p.js.map} +1 -1
- package/public/assets/{messages-DE1QSeHI.js → messages-DC-QXNwz.js} +2 -2
- package/public/assets/messages-DC-QXNwz.js.map +1 -0
- package/public/assets/{orchestrators-B0u2lQfw.js → orchestrators-D_X3a0dm.js} +2 -2
- package/public/assets/{orchestrators-B0u2lQfw.js.map → orchestrators-D_X3a0dm.js.map} +1 -1
- package/public/assets/{overview-DpaEvvLT.js → overview-76JPEbGZ.js} +2 -2
- package/public/assets/{overview-DpaEvvLT.js.map → overview-76JPEbGZ.js.map} +1 -1
- package/public/assets/{pairs-DRp_qAu8.js → pairs-IeLXl-Z8.js} +2 -2
- package/public/assets/{pairs-DRp_qAu8.js.map → pairs-IeLXl-Z8.js.map} +1 -1
- package/public/assets/{projects-cOeuiMjl.js → projects-D8ClXDUg.js} +2 -2
- package/public/assets/{projects-cOeuiMjl.js.map → projects-D8ClXDUg.js.map} +1 -1
- package/public/assets/{prompt-settings-CNoBiymk.js → prompt-settings-BRduaHOj.js} +2 -2
- package/public/assets/{prompt-settings-CNoBiymk.js.map → prompt-settings-BRduaHOj.js.map} +1 -1
- package/public/assets/{registry-0A4QFMWC.js → registry-CXyaPToV.js} +2 -2
- package/public/assets/{registry-0A4QFMWC.js.map → registry-CXyaPToV.js.map} +1 -1
- package/public/assets/{security-C9P6yiQ3.js → security-8n8VEsf5.js} +2 -2
- package/public/assets/{security-C9P6yiQ3.js.map → security-8n8VEsf5.js.map} +1 -1
- package/public/assets/{select-B1PVUj6D.js → select-D-wcg_2U.js} +2 -2
- package/public/assets/{select-B1PVUj6D.js.map → select-D-wcg_2U.js.map} +1 -1
- package/public/assets/{settings-TnHlB9-t.js → settings-BhnCGXgo.js} +2 -2
- package/public/assets/{settings-TnHlB9-t.js.map → settings-BhnCGXgo.js.map} +1 -1
- package/public/assets/{store-DDWg67Pg.js → store-Du8k5mLJ.js} +3 -3
- package/public/assets/{store-DDWg67Pg.js.map → store-Du8k5mLJ.js.map} +1 -1
- package/public/assets/{tasks-1oK_ggFC.js → tasks-DEKQqgPB.js} +2 -2
- package/public/assets/tasks-DEKQqgPB.js.map +1 -0
- package/public/assets/{teams-BBb0ZYiF.js → teams-DoRSWZRQ.js} +2 -2
- package/public/assets/{teams-BBb0ZYiF.js.map → teams-DoRSWZRQ.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-DzvaRjkp.js → terminal-viewer-impl-C_o3BOpu.js} +2 -2
- package/public/assets/{terminal-viewer-impl-DzvaRjkp.js.map → terminal-viewer-impl-C_o3BOpu.js.map} +1 -1
- package/public/assets/types-DSwhOFcJ.js.map +1 -1
- package/public/assets/{user-avatar-X1pfizLv.js → user-avatar-BQoM4jPT.js} +2 -2
- package/public/assets/{user-avatar-X1pfizLv.js.map → user-avatar-BQoM4jPT.js.map} +1 -1
- package/public/assets/work-queue-CWnf1_iE.js +2 -0
- package/public/assets/work-queue-CWnf1_iE.js.map +1 -0
- package/public/assets/{workspaces-BNLP4IlV.js → workspaces-C_fFCoCu.js} +2 -2
- package/public/assets/{workspaces-BNLP4IlV.js.map → workspaces-C_fFCoCu.js.map} +1 -1
- package/public/index.html +2 -2
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/runner/plugins/claude/skills/task/SKILL.md +28 -0
- package/runner/plugins/codex/skills/task/SKILL.md +28 -0
- package/src/agent-lifecycle-events.ts +13 -7
- package/src/db/mappers.ts +2 -0
- package/src/db/migrations.ts +4 -1
- package/src/db/provisioning-registry.ts +148 -2
- package/src/db/schema.ts +2 -0
- package/src/db/task-entity.ts +37 -12
- package/src/db/task-memory.ts +1 -1
- package/src/mcp/tools-spawn.ts +3 -0
- package/src/mcp-dispatch-tools.ts +2 -2
- package/src/mcp-task-tools.ts +11 -4
- package/src/prompt-resolver.ts +7 -7
- package/src/routes/agents-spawn.ts +8 -1
- package/src/routes/tasks.ts +6 -2
- package/src/services/plan-graph.ts +1 -1
- package/src/services/spawn-agent.ts +82 -35
- package/src/services/spawn-registration-watchdog.ts +57 -21
- package/src/services/task-context-hydration.ts +1 -1
- package/src/services/task-done-when.ts +21 -0
- package/src/services/task-entity.ts +10 -1
- package/src/services/task-issue-weave.ts +2 -2
- package/src/services/task-lifecycle.ts +11 -41
- package/src/services/task-semantic-events.ts +4 -4
- package/src/token-db.ts +1 -1
- package/src/validation.ts +1 -1
- package/public/assets/automation-ShdXg0pX.js +0 -2
- package/public/assets/automation-ShdXg0pX.js.map +0 -1
- package/public/assets/formatted-body-impl-DZc7C4-2.js.map +0 -1
- package/public/assets/index-DXEYz_JV.js.map +0 -1
- package/public/assets/messages-DE1QSeHI.js.map +0 -1
- package/public/assets/tasks-1oK_ggFC.js.map +0 -1
- package/public/assets/work-queue-DQMbkSD8.js +0 -2
- package/public/assets/work-queue-DQMbkSD8.js.map +0 -1
|
@@ -41,7 +41,7 @@ import type { ProjectSkillsPolicy } from "../project-skills";
|
|
|
41
41
|
import { applyDefaultSharedMcp, type SharedMcpSpawnOverride } from "../shared-mcp";
|
|
42
42
|
import { autoSeedProjectForCwd } from "./project";
|
|
43
43
|
import { hydrateTaskContext, projectTaskHydration } from "./task-context-hydration";
|
|
44
|
-
import { recordTaskEntityDispatchDecision } from "./task-entity";
|
|
44
|
+
import { createTaskEntity, recordTaskEntityDispatchDecision } from "./task-entity";
|
|
45
45
|
import { autoSubscribeTaskCoordinator } from "./task-semantic-events";
|
|
46
46
|
import { dispatchTask } from "../task-dispatcher";
|
|
47
47
|
import { hasComponentScope, isComponentAuthorizedFor } from "../security";
|
|
@@ -138,6 +138,11 @@ export interface SpawnAgentInput {
|
|
|
138
138
|
* work-shape (#839). Absent = today's behavior, byte-identical.
|
|
139
139
|
*/
|
|
140
140
|
taskId?: number;
|
|
141
|
+
/**
|
|
142
|
+
* #867 — explicit escape hatch for utility/standby spawns that carry a prompt but are not
|
|
143
|
+
* dispatching deliverable work. Known utility profiles are also exempt by default.
|
|
144
|
+
*/
|
|
145
|
+
taskless?: boolean;
|
|
141
146
|
/** #838 — the latest one-line steer; the actionable instruction layered over the hydrated Task context. */
|
|
142
147
|
steer?: string;
|
|
143
148
|
/**
|
|
@@ -218,6 +223,7 @@ export interface SpawnAgentResult {
|
|
|
218
223
|
agentId: string | null;
|
|
219
224
|
registered: boolean;
|
|
220
225
|
command: Command;
|
|
226
|
+
task?: { id: number; autoScaffolded: boolean; nudge: string };
|
|
221
227
|
}
|
|
222
228
|
|
|
223
229
|
/** Reject when a component token's signed resource constraints disallow the target (#323). */
|
|
@@ -248,6 +254,30 @@ function taskSpawnLabel(task: { id: number; title: string } | undefined, fallbac
|
|
|
248
254
|
return label.length <= 120 ? label : `${label.slice(0, 117)}...`;
|
|
249
255
|
}
|
|
250
256
|
|
|
257
|
+
const WORK_DISPATCH_PROFILES = new Set(["default-relay", "lean-worker", "worker"]);
|
|
258
|
+
const TASKLESS_PROFILES = new Set(["coordinator", "isolated-research", "minimal", "ops-assistant", "reviewer", "vanilla"]);
|
|
259
|
+
const AUTO_SCAFFOLD_TASK_NUDGE = "No taskId was provided, so Relay auto-scaffolded a Task from the spawn brief and bound this spawn to it. Pass taskId for intentional task reuse, or taskless:true for utility/standby spawns.";
|
|
260
|
+
|
|
261
|
+
function spawnBrief(input: SpawnAgentInput): string | undefined {
|
|
262
|
+
const candidates = [input.steer, input.prompt];
|
|
263
|
+
for (const value of candidates) {
|
|
264
|
+
const cleaned = value?.trim();
|
|
265
|
+
if (cleaned) return cleaned;
|
|
266
|
+
}
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function taskTitleFromBrief(brief: string): string {
|
|
271
|
+
const firstLine = brief.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "Spawned work";
|
|
272
|
+
return firstLine.length <= 500 ? firstLine : `${firstLine.slice(0, 497)}...`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function shouldAutoScaffoldTask(input: SpawnAgentInput, profileName: string): boolean {
|
|
276
|
+
if (input.taskId !== undefined || input.taskless === true) return false;
|
|
277
|
+
if (TASKLESS_PROFILES.has(profileName)) return false;
|
|
278
|
+
return WORK_DISPATCH_PROFILES.has(profileName) && spawnBrief(input) !== undefined;
|
|
279
|
+
}
|
|
280
|
+
|
|
251
281
|
/**
|
|
252
282
|
* Spawn a provider agent. Transport-agnostic: every gate + side effect lives here so the
|
|
253
283
|
* MCP and HTTP paths cannot drift. Throws `McpAuthError` (→403) on a gate denial,
|
|
@@ -260,7 +290,9 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
|
|
|
260
290
|
// the #221 gate AND the #328/#331/#324 caller-context inheritance — one lookup, reused.
|
|
261
291
|
const callerId = ctx.callerAgentId;
|
|
262
292
|
const caller: AgentCard | undefined = (callerId ? getAgent(callerId) : undefined) ?? undefined;
|
|
263
|
-
const
|
|
293
|
+
const profileName = input.profile ?? DEFAULT_SPAWN_PROFILE_NAME;
|
|
294
|
+
let autoScaffoldedTask: SpawnAgentResult["task"] | undefined;
|
|
295
|
+
let linkedTask = input.taskId !== undefined ? getTaskDetail(input.taskId) ?? undefined : undefined;
|
|
264
296
|
const taskCreatorAgentId = !callerId ? agentIdFromTaskCreator(linkedTask?.createdBy) : undefined;
|
|
265
297
|
const lineageAgentId = callerId ?? taskCreatorAgentId;
|
|
266
298
|
const lineageAgent: AgentCard | undefined = (lineageAgentId ? getAgent(lineageAgentId) : undefined) ?? undefined;
|
|
@@ -285,6 +317,53 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
|
|
|
285
317
|
const inheritedCwd = callerCwd && isPathWithinBase(callerCwd, orchestrator.baseDir) ? callerCwd : undefined;
|
|
286
318
|
const resolvedCwd = requestedCwd || inheritedCwd || orchestrator.baseDir;
|
|
287
319
|
|
|
320
|
+
// Resolve + validate the named agent profile server-side (was HTTP-only; the MCP path passed
|
|
321
|
+
// the name through unvalidated). Omitted profile uses Relay's spawn default so coordinator
|
|
322
|
+
// spawns still get the bundled Relay context/affordances. Explicit profile always wins.
|
|
323
|
+
let agentProfile = withResolvedProvisioning(getAgentProfile(profileName)?.value, input.provider);
|
|
324
|
+
if (!agentProfile) throw new McpNotFoundError(input.profile ? "agent profile not found" : `default agent profile ${DEFAULT_SPAWN_PROFILE_NAME} not found`);
|
|
325
|
+
|
|
326
|
+
// --- THE GATE (uniform across transports) ---------------------------------
|
|
327
|
+
// Coarse capability: spawning requires command:spawn. The MCP dispatcher already enforced
|
|
328
|
+
// this; the HTTP route did not — asserting here makes it transport-invariant.
|
|
329
|
+
if (!canSpawn(ctx)) {
|
|
330
|
+
throw new McpAuthError(`spawn requires the ${SPAWN_SCOPE} scope`);
|
|
331
|
+
}
|
|
332
|
+
// #221 — a real spawning agent: no grandchildren, and within its maxSpawnedAgents quota.
|
|
333
|
+
// Server/admin/dashboard tokens carry no caller identity → unrestricted by design.
|
|
334
|
+
if (callerId) {
|
|
335
|
+
if (caller?.spawnedBy) {
|
|
336
|
+
throw new McpAuthError("spawned agents cannot spawn further agents (no grandchildren)");
|
|
337
|
+
}
|
|
338
|
+
const quota = ctx.constraints?.maxSpawnedAgents ?? 0;
|
|
339
|
+
const live = countLiveSpawnedAgents(callerId);
|
|
340
|
+
if (live >= quota) {
|
|
341
|
+
throw new ValidationError(`spawn quota reached (${live}/${quota} live children) — shut one down or wait for one to exit`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// Gate the child spawn on the token's legit bounds: the target orchestrator AND the spawn cwd
|
|
345
|
+
// (`cwdPrefixes`/`cwd` constraint). Excludes the #323 footguns (spawnRequestIds/policies) — those
|
|
346
|
+
// are self-scoping per-spawn ids the CHILD gets fresh, so gating on them makes the quota
|
|
347
|
+
// unreachable. The cwd bound was enforced by the old HTTP route (resource.cwd) but NOT by the MCP
|
|
348
|
+
// path; unifying here converges UP to the strict union — a scoped dashboard token still can't spawn
|
|
349
|
+
// outside its cwd prefix, and MCP now honors the same bound.
|
|
350
|
+
assertResourceAllowed(ctx, { scope: "agent:write", resource: { orchestratorId: orchestrator.id, cwd: resolvedCwd } });
|
|
351
|
+
|
|
352
|
+
if (shouldAutoScaffoldTask(input, profileName)) {
|
|
353
|
+
const brief = spawnBrief(input)!;
|
|
354
|
+
const taskRequestedBy = callerId ?? input.requestedBy ?? ctx.actor.id;
|
|
355
|
+
linkedTask = createTaskEntity({
|
|
356
|
+
title: taskTitleFromBrief(brief),
|
|
357
|
+
intent: brief,
|
|
358
|
+
latestSteer: input.steer,
|
|
359
|
+
createdBy: taskRequestedBy,
|
|
360
|
+
source: "spawn",
|
|
361
|
+
target: taskRequestedBy,
|
|
362
|
+
});
|
|
363
|
+
input = { ...input, taskId: linkedTask.id };
|
|
364
|
+
autoScaffoldedTask = { id: linkedTask.id, autoScaffolded: true, nudge: AUTO_SCAFFOLD_TASK_NUDGE };
|
|
365
|
+
}
|
|
366
|
+
|
|
288
367
|
// #838 — spawn-with-task hydration. When a taskId is given, relay assembles the worker's
|
|
289
368
|
// first-turn context FROM the durable Task (intent + linked-issue spec/acceptance + recorded
|
|
290
369
|
// decisions + history + latest steer), resolved server-side ONCE, and projects it onto the
|
|
@@ -346,13 +425,6 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
|
|
|
346
425
|
|
|
347
426
|
const spawnRequestId = input.spawnRequestId ?? generateSpawnRequestId();
|
|
348
427
|
|
|
349
|
-
// Resolve + validate the named agent profile server-side (was HTTP-only; the MCP path passed
|
|
350
|
-
// the name through unvalidated). Omitted profile uses Relay's spawn default so coordinator
|
|
351
|
-
// spawns still get the bundled Relay context/affordances. Explicit profile always wins.
|
|
352
|
-
const profileName = input.profile ?? DEFAULT_SPAWN_PROFILE_NAME;
|
|
353
|
-
let agentProfile = withResolvedProvisioning(getAgentProfile(profileName)?.value, input.provider);
|
|
354
|
-
if (!agentProfile) throw new McpNotFoundError(input.profile ? "agent profile not found" : `default agent profile ${DEFAULT_SPAWN_PROFILE_NAME} not found`);
|
|
355
|
-
|
|
356
428
|
// #667 — project `.mcp.json` projection: a per-spawn `projectMcp` overrides the profile
|
|
357
429
|
// default. The runner does the filesystem read + injection from `agentProfile.projectMcp`
|
|
358
430
|
// (it owns the spawn cwd), so thread the resolved decision onto the in-flight profile copy
|
|
@@ -368,32 +440,6 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
|
|
|
368
440
|
// Explicit opt-in still works for zero-host profiles.
|
|
369
441
|
agentProfile = applyDefaultSharedMcp(agentProfile, input.sharedMcp);
|
|
370
442
|
|
|
371
|
-
// --- THE GATE (uniform across transports) ---------------------------------
|
|
372
|
-
// Coarse capability: spawning requires command:spawn. The MCP dispatcher already enforced
|
|
373
|
-
// this; the HTTP route did not — asserting here makes it transport-invariant.
|
|
374
|
-
if (!canSpawn(ctx)) {
|
|
375
|
-
throw new McpAuthError(`spawn requires the ${SPAWN_SCOPE} scope`);
|
|
376
|
-
}
|
|
377
|
-
// #221 — a real spawning agent: no grandchildren, and within its maxSpawnedAgents quota.
|
|
378
|
-
// Server/admin/dashboard tokens carry no caller identity → unrestricted by design.
|
|
379
|
-
if (callerId) {
|
|
380
|
-
if (caller?.spawnedBy) {
|
|
381
|
-
throw new McpAuthError("spawned agents cannot spawn further agents (no grandchildren)");
|
|
382
|
-
}
|
|
383
|
-
const quota = ctx.constraints?.maxSpawnedAgents ?? 0;
|
|
384
|
-
const live = countLiveSpawnedAgents(callerId);
|
|
385
|
-
if (live >= quota) {
|
|
386
|
-
throw new ValidationError(`spawn quota reached (${live}/${quota} live children) — shut one down or wait for one to exit`);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
// Gate the child spawn on the token's legit bounds: the target orchestrator AND the spawn cwd
|
|
390
|
-
// (`cwdPrefixes`/`cwd` constraint). Excludes the #323 footguns (spawnRequestIds/policies) — those
|
|
391
|
-
// are self-scoping per-spawn ids the CHILD gets fresh, so gating on them makes the quota
|
|
392
|
-
// unreachable. The cwd bound was enforced by the old HTTP route (resource.cwd) but NOT by the MCP
|
|
393
|
-
// path; unifying here converges UP to the strict union — a scoped dashboard token still can't spawn
|
|
394
|
-
// outside its cwd prefix, and MCP now honors the same bound.
|
|
395
|
-
assertResourceAllowed(ctx, { scope: "agent:write", resource: { orchestratorId: orchestrator.id, cwd: resolvedCwd } });
|
|
396
|
-
|
|
397
443
|
// #635 — validate and enrich resumeWorkspace before dispatching to the orchestrator.
|
|
398
444
|
// The relay has the workspace DB records; the orchestrator has disk access. We do the
|
|
399
445
|
// "is this a real managed workspace and is it free?" check here, the orchestrator
|
|
@@ -606,6 +652,7 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
|
|
|
606
652
|
agentId,
|
|
607
653
|
registered: agentId !== null,
|
|
608
654
|
command,
|
|
655
|
+
...(autoScaffoldedTask ? { task: autoScaffoldedTask } : {}),
|
|
609
656
|
};
|
|
610
657
|
}
|
|
611
658
|
|
|
@@ -9,7 +9,15 @@ interface SpawnCommandRow {
|
|
|
9
9
|
id: string;
|
|
10
10
|
params: string;
|
|
11
11
|
correlation_id: string | null;
|
|
12
|
-
|
|
12
|
+
updated_at: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface SpawnCommandGroup {
|
|
16
|
+
spawnRequestId: string;
|
|
17
|
+
parent: string;
|
|
18
|
+
provider?: string;
|
|
19
|
+
latestUpdatedAt: number;
|
|
20
|
+
rows: SpawnCommandRow[];
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
interface SpawnRegistrationWatchdogResult extends Record<string, unknown> {
|
|
@@ -26,45 +34,73 @@ export function reapUnregisteredSpawnCommands(
|
|
|
26
34
|
const cutoff = now - deadlineMs;
|
|
27
35
|
const notifyCutoff = now - notifyMaxAgeMs;
|
|
28
36
|
const rows = getDb().query(`
|
|
29
|
-
SELECT id, params, correlation_id,
|
|
37
|
+
SELECT id, params, correlation_id, updated_at
|
|
30
38
|
FROM commands
|
|
31
39
|
WHERE type = 'agent.spawn'
|
|
32
40
|
AND status = 'succeeded'
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
`).all(cutoff) as SpawnCommandRow[];
|
|
41
|
+
ORDER BY updated_at ASC
|
|
42
|
+
`).all() as SpawnCommandRow[];
|
|
36
43
|
|
|
37
44
|
const failedSpawnCommandIds: string[] = [];
|
|
38
45
|
const notifiedSpawnCommandIds: string[] = [];
|
|
39
46
|
const silencedSpawnCommandIds: string[] = [];
|
|
40
|
-
for (const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const parent = stringParam(params, "spawnedBy");
|
|
44
|
-
if (!spawnRequestId || !parent) continue;
|
|
45
|
-
if (hasEverRegisteredAgent(spawnRequestId)) continue;
|
|
47
|
+
for (const group of groupSpawnCommands(rows)) {
|
|
48
|
+
if (group.latestUpdatedAt > cutoff) continue;
|
|
49
|
+
if (hasEverRegisteredAgent(group.spawnRequestId)) continue;
|
|
46
50
|
|
|
47
51
|
const reason = `spawned agent did not register within ${deadlineMs}ms`;
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
52
|
+
const failedCommands = group.rows
|
|
53
|
+
.map((row) => updateCommand(row.id, { status: "failed", error: reason }))
|
|
54
|
+
.filter((command): command is NonNullable<typeof command> => Boolean(command && command.status === "failed"));
|
|
55
|
+
if (failedCommands.length === 0) continue;
|
|
56
|
+
failedSpawnCommandIds.push(...failedCommands.map((command) => command.id));
|
|
57
|
+
if (group.latestUpdatedAt >= notifyCutoff) {
|
|
58
|
+
notifiedSpawnCommandIds.push(failedCommands[failedCommands.length - 1]!.id);
|
|
53
59
|
notifyAgentSpawnFailed({
|
|
54
|
-
parent,
|
|
55
|
-
spawnRequestId,
|
|
56
|
-
provider:
|
|
60
|
+
parent: group.parent,
|
|
61
|
+
spawnRequestId: group.spawnRequestId,
|
|
62
|
+
provider: group.provider,
|
|
57
63
|
reason,
|
|
58
64
|
});
|
|
59
65
|
} else {
|
|
60
|
-
silencedSpawnCommandIds.push(
|
|
66
|
+
silencedSpawnCommandIds.push(...failedCommands.map((command) => command.id));
|
|
67
|
+
}
|
|
68
|
+
for (const failed of failedCommands) {
|
|
69
|
+
emitCommandEvent(failed, "command.failed");
|
|
61
70
|
}
|
|
62
|
-
emitCommandEvent(failed, "command.failed");
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
return { failedSpawnCommandIds, notifiedSpawnCommandIds, silencedSpawnCommandIds };
|
|
66
74
|
}
|
|
67
75
|
|
|
76
|
+
function groupSpawnCommands(rows: SpawnCommandRow[]): SpawnCommandGroup[] {
|
|
77
|
+
const groups = new Map<string, SpawnCommandGroup>();
|
|
78
|
+
for (const row of rows) {
|
|
79
|
+
const params = parseParams(row.params);
|
|
80
|
+
const spawnRequestId = stringParam(params, "spawnRequestId") ?? row.correlation_id ?? undefined;
|
|
81
|
+
const parent = stringParam(params, "spawnedBy");
|
|
82
|
+
if (!spawnRequestId || !parent) continue;
|
|
83
|
+
const existing = groups.get(spawnRequestId);
|
|
84
|
+
if (existing) {
|
|
85
|
+
existing.rows.push(row);
|
|
86
|
+
if (row.updated_at >= existing.latestUpdatedAt) {
|
|
87
|
+
existing.parent = parent;
|
|
88
|
+
existing.provider = stringParam(params, "provider");
|
|
89
|
+
existing.latestUpdatedAt = row.updated_at;
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
groups.set(spawnRequestId, {
|
|
94
|
+
spawnRequestId,
|
|
95
|
+
parent,
|
|
96
|
+
provider: stringParam(params, "provider"),
|
|
97
|
+
latestUpdatedAt: row.updated_at,
|
|
98
|
+
rows: [row],
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return [...groups.values()];
|
|
102
|
+
}
|
|
103
|
+
|
|
68
104
|
function hasEverRegisteredAgent(spawnRequestId: string): boolean {
|
|
69
105
|
const row = getDb().query(`
|
|
70
106
|
SELECT 1
|
|
@@ -170,7 +170,7 @@ function hasTaskMemory(envelope: TaskMemoryEnvelope): boolean {
|
|
|
170
170
|
function renderTaskMemory(envelope: TaskMemoryEnvelope): string | undefined {
|
|
171
171
|
if (!hasTaskMemory(envelope)) return undefined;
|
|
172
172
|
const rendered = renderContinuation(boundedTaskMemoryEnvelope(envelope), {
|
|
173
|
-
intro: `Task-bound memory — generation ${envelope.generation}. This is Relay-owned memory bound to Task
|
|
173
|
+
intro: `Task-bound memory — generation ${envelope.generation}. This is Relay-owned memory bound to Task T${envelope.taskId}; carry these dead ends, decisions, and working state forward.`,
|
|
174
174
|
});
|
|
175
175
|
return truncateTaskMemorySection(rendered, TASK_MEMORY_SECTION_LIMIT);
|
|
176
176
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { TASK_DELIVERABLE_STAGES } from "agent-relay-sdk";
|
|
2
|
+
import type { TaskDeliverableStage } from "../types";
|
|
3
|
+
|
|
4
|
+
const STAGE_INDEX = new Map<string, number>(TASK_DELIVERABLE_STAGES.map((stage, i) => [stage, i]));
|
|
5
|
+
|
|
6
|
+
// `doneWhen` is free text, but these lifecycle tokens have durable semantics.
|
|
7
|
+
export function doneWhenTargetStage(doneWhen: string | undefined): TaskDeliverableStage {
|
|
8
|
+
const value = doneWhen?.trim().toLowerCase();
|
|
9
|
+
if (!value) return "landed-on-main";
|
|
10
|
+
if (value === "deliverable-attached" || value.includes("deliverable attached")) return "committed-on-branch";
|
|
11
|
+
if (value === "landed" || value === "landed-on-main" || value.includes("landed")) return "landed-on-main";
|
|
12
|
+
if (value.includes("deploying")) return "deploying";
|
|
13
|
+
if (value.includes("deploy")) return "deployed";
|
|
14
|
+
return "landed-on-main";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function stageSatisfiesDoneWhen(stage: TaskDeliverableStage, doneWhen: string | undefined): boolean {
|
|
18
|
+
const current = STAGE_INDEX.get(stage) ?? 0;
|
|
19
|
+
const target = STAGE_INDEX.get(doneWhenTargetStage(doneWhen)) ?? STAGE_INDEX.get("landed-on-main")!;
|
|
20
|
+
return current >= target;
|
|
21
|
+
}
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
import { emitTaskChanged } from "../sse";
|
|
31
31
|
import type { Task, TaskDetail, TaskEvent, TaskGateState } from "../types";
|
|
32
32
|
import { removeSubscriptionsForTask } from "../db/notification-subscriptions";
|
|
33
|
+
import { stageSatisfiesDoneWhen } from "./task-done-when";
|
|
33
34
|
import { autoSubscribeTaskCoordinator, emitTaskSemanticNotification, taskTransitionForStage, taskTransitionForStatus } from "./task-semantic-events";
|
|
34
35
|
|
|
35
36
|
export function createTaskEntity(input: CreateDeliverableTaskInput): TaskDetail {
|
|
@@ -82,7 +83,7 @@ function isTerminalTaskStatus(status: Task["status"]): boolean {
|
|
|
82
83
|
|
|
83
84
|
export function setTaskEntityStage(id: number, input: SetTaskStageInput): TaskDetail {
|
|
84
85
|
const before = getTaskDetail(id);
|
|
85
|
-
|
|
86
|
+
let detail = setTaskStage(id, input);
|
|
86
87
|
emitTaskChanged(detail, "task.stage");
|
|
87
88
|
const transition = taskTransitionForStage(detail.deliverableStage);
|
|
88
89
|
if (input.actor !== "task-lifecycle" && transition && before?.deliverableStage !== detail.deliverableStage) {
|
|
@@ -94,6 +95,14 @@ export function setTaskEntityStage(id: number, input: SetTaskStageInput): TaskDe
|
|
|
94
95
|
occurrenceKey: latestTaskEventOccurrence(detail.id, `stage:${detail.deliverableStage}`),
|
|
95
96
|
});
|
|
96
97
|
}
|
|
98
|
+
const deliverableStage = detail.deliverableStage ?? "none";
|
|
99
|
+
if (detail.status !== "done" && stageSatisfiesDoneWhen(deliverableStage, detail.doneWhen)) {
|
|
100
|
+
detail = setTaskEntityStatus(id, {
|
|
101
|
+
status: "done",
|
|
102
|
+
note: `doneWhen reached at stage ${deliverableStage}`,
|
|
103
|
+
actor: input.actor,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
97
106
|
return detail;
|
|
98
107
|
}
|
|
99
108
|
|
|
@@ -112,8 +112,8 @@ interface TaskCommentPayload {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
const HEADINGS: Record<TaskCommentKind, (p: TaskCommentPayload) => string> = {
|
|
115
|
-
discussion: (p) => `💬 **Comment on
|
|
116
|
-
milestone: (p) => `🏁 **
|
|
115
|
+
discussion: (p) => `💬 **Comment on T${p.taskId}**${p.author ? ` — _${p.author}_` : ""}`,
|
|
116
|
+
milestone: (p) => `🏁 **T${p.taskId} — ${p.milestone ?? "milestone"}**`,
|
|
117
117
|
};
|
|
118
118
|
|
|
119
119
|
// The payload is base64url-encoded inside the marker, NOT raw JSON: a user/route-supplied
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// owns execution telemetry. Issue CLOSE is the canonical #482/#474 path (which runs just before
|
|
22
22
|
// this in `notifyBranchLanded`) — we do NOT re-close; we PROJECT it via #833's `taskCloseBridge`.
|
|
23
23
|
// The one write-through we make is the single milestone summary (#833's milestone exception).
|
|
24
|
-
import { errMessage
|
|
24
|
+
import { errMessage } from "agent-relay-sdk";
|
|
25
25
|
import {
|
|
26
26
|
deliverableTaskIdsByBranch,
|
|
27
27
|
deliverableTaskIdsByOwnerAgent,
|
|
@@ -30,12 +30,13 @@ import {
|
|
|
30
30
|
getTaskDetail,
|
|
31
31
|
markTaskAgentReaped,
|
|
32
32
|
} from "../db";
|
|
33
|
-
import { setTaskEntityStage
|
|
33
|
+
import { setTaskEntityStage } from "./task-entity";
|
|
34
34
|
import { postTaskMilestoneComment, taskCloseBridge, type TaskCloseBridgeEntry } from "./task-issue-weave";
|
|
35
35
|
import { requestTransientAgentShutdown } from "./transient-agent-reaper";
|
|
36
36
|
import { emitRelayEvent } from "../events";
|
|
37
37
|
import { routeNotification } from "../notification-router";
|
|
38
38
|
import { resolveCoordinatorAgentId } from "../steward-identity";
|
|
39
|
+
import { stageSatisfiesDoneWhen } from "./task-done-when";
|
|
39
40
|
import { emitTaskSemanticNotification, type TaskSemanticTransition } from "./task-semantic-events";
|
|
40
41
|
import type { IssueRef, TaskDeliverableStage, TaskGateState, WorkspaceRecord } from "../types";
|
|
41
42
|
import type { IssueTrackerAdapter } from "../issue-tracker";
|
|
@@ -120,33 +121,12 @@ function landedDiffSummary(input: LandedTaskLifecycleInput, log: LogFn): string
|
|
|
120
121
|
}
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
// --- doneWhen → deliverable-stage mapping -------------------------------------------------------
|
|
124
|
-
const STAGE_INDEX = new Map<string, number>(TASK_DELIVERABLE_STAGES.map((stage, i) => [stage, i]));
|
|
125
|
-
|
|
126
|
-
// Which deliverable stage satisfies a Task's `doneWhen`. `doneWhen` is free text (#834): we map
|
|
127
|
-
// the small known vocabulary (`landed` | `deployed` | `deliverable-attached`, with synonyms) and
|
|
128
|
-
// default an UNSET doneWhen to `landed-on-main` — the common case where a Task is done when it
|
|
129
|
-
// lands. An unrecognized value falls back to the landed default rather than blocking forever.
|
|
130
|
-
function doneWhenTargetStage(doneWhen: string | undefined): TaskDeliverableStage {
|
|
131
|
-
const value = doneWhen?.trim().toLowerCase();
|
|
132
|
-
if (!value) return "landed-on-main";
|
|
133
|
-
if (value.includes("deploying")) return "deploying";
|
|
134
|
-
if (value.includes("deploy")) return "deployed";
|
|
135
|
-
return "landed-on-main";
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function stageSatisfiesDoneWhen(stage: TaskDeliverableStage, doneWhen: string | undefined): boolean {
|
|
139
|
-
const current = STAGE_INDEX.get(stage) ?? 0;
|
|
140
|
-
const target = STAGE_INDEX.get(doneWhenTargetStage(doneWhen)) ?? STAGE_INDEX.get("landed-on-main")!;
|
|
141
|
-
return current >= target;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
124
|
// --- gate verdict ------------------------------------------------------------------------------
|
|
145
|
-
// "Gates passed" is the signal that frees the agent (reap)
|
|
125
|
+
// "Gates passed" is the signal that frees the agent (reap). The
|
|
146
126
|
// merge landing green is necessary but NOT sufficient (Codex #831 review): a worker that crashed
|
|
147
127
|
// before reporting gates, or reported only a subset, must NOT read as green on a vacuous `[]`.
|
|
148
128
|
// So a pass REQUIRES the expected gates present-and-passing AND no recorded gate failing. An
|
|
149
|
-
// empty / missing / partial / failed ledger is NOT passed → no reap, no
|
|
129
|
+
// empty / missing / partial / failed ledger is NOT passed → no reap, no green report.
|
|
150
130
|
export const EXPECTED_GATES = ["typecheck", "test"] as const;
|
|
151
131
|
|
|
152
132
|
interface GateVerdict {
|
|
@@ -310,20 +290,10 @@ async function driveTaskOnLand(
|
|
|
310
290
|
const gatesPassed = gateVerdict.passed;
|
|
311
291
|
|
|
312
292
|
// 3 (doneWhen axis) — reaching the Task's doneWhen target CLOSES the Task (execution status →
|
|
313
|
-
// done)
|
|
314
|
-
//
|
|
315
|
-
// but not done", e.g. doneWhen=deployed waits on deploy). Idempotent: an already-done Task isn't
|
|
316
|
-
// re-written.
|
|
293
|
+
// done). Gates are intentionally separate: they guard reaping and green reporting, not the
|
|
294
|
+
// execution status completion predicate. Otherwise empty gates can strand a landed Task forever.
|
|
317
295
|
const doneWhenSatisfied = stageSatisfiesDoneWhen("landed-on-main", before.doneWhen);
|
|
318
|
-
|
|
319
|
-
if (doneWhenSatisfied && gatesPassed && before.status !== "done") {
|
|
320
|
-
detail = setTaskEntityStatus(taskId, {
|
|
321
|
-
status: "done",
|
|
322
|
-
note: `doneWhen reached on land (${input.mergedSha?.slice(0, 12) ?? input.workspace.branch ?? "merged"})`,
|
|
323
|
-
actor: "task-lifecycle",
|
|
324
|
-
});
|
|
325
|
-
statusAdvanced = true;
|
|
326
|
-
}
|
|
296
|
+
const statusAdvanced = before.status !== "done" && detail.status === "done";
|
|
327
297
|
|
|
328
298
|
// 3b (close projection) — the linked issue is closed by the canonical #482/#474 path (which ran
|
|
329
299
|
// immediately before this in notifyBranchLanded). We PROJECT that ledger via #833, never re-close.
|
|
@@ -341,7 +311,7 @@ async function driveTaskOnLand(
|
|
|
341
311
|
const summaryBits = [`Landed to \`${input.workspace.baseRef ?? "main"}\``];
|
|
342
312
|
if (diffSummary) summaryBits.push(diffSummary);
|
|
343
313
|
if (!gatesPassed) summaryBits.push(`gates not verified (${gateVerdict.reason ?? "incomplete"})`);
|
|
344
|
-
summaryBits.push(
|
|
314
|
+
summaryBits.push(doneWhenSatisfied ? "Task done (doneWhen reached)" : `Task at \`landed-on-main\` (doneWhen \`${before.doneWhen ?? "landed"}\` not yet reached)`);
|
|
345
315
|
const milestone = await postTaskMilestoneComment(
|
|
346
316
|
{
|
|
347
317
|
taskId,
|
|
@@ -440,8 +410,8 @@ function emitDeliverableErrorReceipt(input: LandedTaskLifecycleInput, outcome: T
|
|
|
440
410
|
recipients: [{ agentId: coordinator, matchedRule: "task-stakeholder" }],
|
|
441
411
|
ttlMs: null,
|
|
442
412
|
message: {
|
|
443
|
-
subject: `Task
|
|
444
|
-
body: `Task
|
|
413
|
+
subject: `Task T${outcome.taskId} land lifecycle failed`,
|
|
414
|
+
body: `Task T${outcome.taskId} landed, but Relay could not complete the task lifecycle automation: ${outcome.error}`,
|
|
445
415
|
payload: {
|
|
446
416
|
kind: "task.lifecycle-error",
|
|
447
417
|
notification: "task-lifecycle-error",
|
|
@@ -271,7 +271,7 @@ function compactStatusPulseItems(items: StatusPulseDigestItem[]): StatusPulseDig
|
|
|
271
271
|
function taskStatusPulseBody(items: StatusPulseDigestItem[]): string {
|
|
272
272
|
const lines = ["Task status pulse:"];
|
|
273
273
|
for (const item of items) {
|
|
274
|
-
const bits = [
|
|
274
|
+
const bits = [`T${item.taskId}`, item.transition, item.title];
|
|
275
275
|
const issue = issueLabel(item.issue);
|
|
276
276
|
if (issue) bits.push(issue);
|
|
277
277
|
if (item.branch) bits.push(`branch ${item.branch}`);
|
|
@@ -303,13 +303,13 @@ function taskNotificationScope(input: TaskSemanticNotificationInput): Notificati
|
|
|
303
303
|
}
|
|
304
304
|
|
|
305
305
|
function taskNotificationSubject(input: TaskSemanticNotificationInput): string {
|
|
306
|
-
return `Task
|
|
306
|
+
return `Task T${input.task.id} ${input.transition}`;
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
function taskNotificationBody(input: TaskSemanticNotificationInput): string {
|
|
310
310
|
const issue = issueLabel(primaryIssue(input.task));
|
|
311
311
|
if (input.transition === "landed-on-main") {
|
|
312
|
-
const bits = [`Task
|
|
312
|
+
const bits = [`Task T${input.task.id} landed on main: ${input.task.title}`];
|
|
313
313
|
if (input.sha) bits.push(`sha ${input.sha.slice(0, 12)}`);
|
|
314
314
|
const gatesPassed = input.outcome?.gatesPassed;
|
|
315
315
|
if (typeof gatesPassed === "boolean") bits.push(gatesPassed ? "gates passed" : "gates not verified");
|
|
@@ -318,7 +318,7 @@ function taskNotificationBody(input: TaskSemanticNotificationInput): string {
|
|
|
318
318
|
return bits.join("; ");
|
|
319
319
|
}
|
|
320
320
|
const needed = taskNotificationNeeded(input);
|
|
321
|
-
return `Task
|
|
321
|
+
return `Task T${input.task.id} ${input.transition}: ${input.task.title}${issue ? ` (${issue})` : ""}${needed ? `\nNeeded: ${needed}` : ""}`;
|
|
322
322
|
}
|
|
323
323
|
|
|
324
324
|
function taskNotificationNeeded(input: TaskSemanticNotificationInput): string {
|
package/src/token-db.ts
CHANGED
|
@@ -67,7 +67,7 @@ const BUILT_IN_PROFILES: Array<Omit<TokenProfile, "createdAt" | "updatedAt">> =
|
|
|
67
67
|
name: "Orchestrator Host",
|
|
68
68
|
description: "Host supervisor access for spawn, command, log, and terminal operations.",
|
|
69
69
|
role: "orchestrator",
|
|
70
|
-
scope: ["agent:read", "agent:write", "command:read", "command:write", "task:read", "task:write", "logs:read", "terminal:attach", "quota:write"],
|
|
70
|
+
scope: ["agent:read", "agent:write", "command:read", "command:write", "task:read", "task:write", "logs:read", "terminal:attach", "quota:write", "provisioning:read"],
|
|
71
71
|
constraints: { logsRead: true, terminalAttach: true },
|
|
72
72
|
ttlSeconds: 30 * 24 * 60 * 60,
|
|
73
73
|
builtIn: true,
|
package/src/validation.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ValidationError } from "./db";
|
|
1
|
+
import { ValidationError } from "./db/connection.ts";
|
|
2
2
|
import { type TokenConstraints, type WorkspaceAutoMergePolicy, type WorkspaceLandingPolicy, type WorkspaceMetadata, type WorkspaceMode, type WorkspaceProbe, type WorkspaceStatus } from "./types";
|
|
3
3
|
import { AUTO_MERGE_POLICIES, VALID_WORKSPACE_MODES, isRecord } from "agent-relay-sdk";
|
|
4
4
|
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{r as e}from"./chunk-CilyBKbf.js";import{$n as t,An as n,G as r,Ht as i,N as a,U as o,V as s,fr as c,g as l,j as u}from"./lucide-react-DmbfpRvJ.js";import{c as d,f,l as p,s as m,t as h}from"./store-DDWg67Pg.js";import{p as g}from"./types-DSwhOFcJ.js";import{K as _,h as v}from"./display-_5kFrHJh.js";import{t as y}from"./badge-kdQDWO6e.js";import{t as b}from"./button-BKFvNXa3.js";import{t as x}from"./input-DfN6sUbX.js";import{V as S,b as C,c as w,l as T}from"./index-DXEYz_JV.js";import{o as E,r as D,s as O,t as k}from"./card-B0y1i_2D.js";var A=e(c(),1),j=t(),M={scheduled:`bg-blue-500/10 text-blue-400 border-blue-500/20`,dispatching:`bg-sky-500/10 text-sky-400 border-sky-500/20`,waiting_agent:`bg-yellow-500/10 text-yellow-400 border-yellow-500/20`,running:`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`,succeeded:`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`,failed:`bg-red-500/10 text-red-400 border-red-500/20`,canceled:`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`,timed_out:`bg-red-500/10 text-red-400 border-red-500/20`},N=new Set([`scheduled`,`dispatching`,`waiting_agent`,`running`]);function P(e){return e.status===`timed_out`&&e.result?.runtimeBudgetElapsed===!0?`budget elapsed`:e.status}function F(e){return e.status===`timed_out`&&e.result?.runtimeBudgetElapsed===!0?`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`:M[e.status]||``}function I(e){return e.status===`timed_out`&&e.result?.runtimeBudgetElapsed===!0?void 0:e.error}function L(e){return N.has(e.status)}function R(e){return typeof e==`number`&&Number.isFinite(e)?e:void 0}function z(e){return typeof e==`string`&&e.length>0?e:void 0}function B(e){let t=Math.max(0,Math.round(e/1e3)),n=Math.floor(t/3600),r=Math.floor(t%3600/60),i=t%60;return n?`${n}h ${r}m`:r?`${r}m ${i}s`:`${i}s`}function V(e){return R(e.meta?.runtimeBudgetDeadlineAt)??R(e.result?.deadlineAt)}function H(e,t){if(!e.startedAt)return`-`;let n=e.finishedAt||(L(e)?t:e.updatedAt);return B(Number(n)-Number(e.startedAt))}function U(e,t){let n=V(e);if(!(!n||!L(e)))return B(n-t)}function W(e){return e.status===`timed_out`&&e.result?.runtimeBudgetElapsed===!0?`runtime budget elapsed`:e.error}function G(e,t,n){let r=e.targetAgentId||e.spawnedAgentId||z(e.meta?.runtimeAgentId),i=r?t.find(e=>e.id===r):void 0,a=r?n.flatMap(e=>e.managedAgents.map(t=>({orchestrator:e,managedAgent:t}))).find(({managedAgent:e})=>e.agentId===r):void 0,o=z(e.meta?.terminalSession)||z(e.meta?.tmuxSession)||z(e.meta?.sessionName)||z(i?.meta?.terminalSession)||z(i?.meta?.tmuxSession)||z(i?.meta?.sessionName)||a?.managedAgent.terminalSession||a?.managedAgent.tmuxSession||a?.managedAgent.sessionName,s=z(e.meta?.orchestratorId)||a?.orchestrator.id||e.orchestratorId;return o?{orchestratorId:s,session:o}:void 0}function K(e=``,t){let n=p(t);return{name:``,description:``,enabled:!0,schedule:`0 8 * * *`,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone||`UTC`,orchestratorId:e,targetMode:`existing_agent`,provider:n,model:d(n),effort:``,profile:`default-relay`,label:``,tags:``,capabilities:``,ifNoMatch:`fail`,cwd:``,approvalMode:`guarded`,workspaceMode:`inherit`,keepAlive:!1,budgetMinutes:``,warningMinutes:``,warningMessage:``,concurrencyPolicy:`skip`,title:``,body:``,severity:`info`,dedupeKey:``,externalUrl:``}}function q(e){let t=e.split(`,`).map(e=>e.trim()).filter(Boolean);return t.length?t:void 0}function J(e){let t=e.targetPolicy;return{...K(e.orchestratorId),id:e.id,name:e.name,description:e.description||``,enabled:e.enabled,schedule:e.schedule,timezone:e.timezone,orchestratorId:e.orchestratorId,targetMode:t.mode,provider:t.mode===`on_demand_agent`?t.provider:t.selector.provider||g()[0]||``,model:t.mode===`on_demand_agent`?t.model||d(t.provider):``,effort:t.mode===`on_demand_agent`&&t.effort||``,profile:t.mode===`on_demand_agent`&&t.profile||`default-relay`,label:t.mode===`existing_agent`&&t.selector.label||``,tags:t.mode===`existing_agent`?(t.selector.tags||[]).join(`, `):``,capabilities:t.mode===`existing_agent`?(t.selector.capabilities||[]).join(`, `):``,ifNoMatch:t.mode===`existing_agent`&&t.ifNoMatch||`fail`,cwd:t.mode===`on_demand_agent`&&t.cwd||``,approvalMode:t.mode===`on_demand_agent`&&t.approvalMode||`guarded`,workspaceMode:t.mode===`on_demand_agent`&&t.workspaceMode||`inherit`,keepAlive:t.mode===`on_demand_agent`?!!t.keepAlive:!1,budgetMinutes:t.mode===`on_demand_agent`&&t.runtimeBudget?String(Math.round(t.runtimeBudget.maxRuntimeMs/6e4)):``,warningMinutes:t.mode===`on_demand_agent`&&t.runtimeBudget?.warnAtMs!==void 0?String(Math.round(t.runtimeBudget.warnAtMs/6e4)):``,warningMessage:t.mode===`on_demand_agent`&&t.runtimeBudget?.warningMessage||``,concurrencyPolicy:e.concurrencyPolicy,title:e.taskTemplate.title,body:e.taskTemplate.body,severity:e.taskTemplate.severity||`info`,dedupeKey:e.taskTemplate.dedupeKey||``,externalUrl:e.taskTemplate.externalUrl||``}}function Y(e){let t=Number(e.budgetMinutes),n=Number(e.warningMinutes),r=e.targetMode===`on_demand_agent`&&e.budgetMinutes.trim()&&Number.isFinite(t)?{maxRuntimeMs:Math.round(t*6e4),warnAtMs:e.warningMinutes.trim()&&Number.isFinite(n)?Math.round(n*6e4):void 0,warningMessage:e.warningMessage.trim()||void 0}:void 0,i=e.targetMode===`existing_agent`?{mode:`existing_agent`,selector:{provider:e.provider||void 0,label:e.label.trim()||void 0,tags:q(e.tags),capabilities:q(e.capabilities)},ifNoMatch:e.ifNoMatch}:{mode:`on_demand_agent`,provider:e.provider,model:e.model||void 0,effort:e.effort?e.effort:void 0,profile:e.profile||void 0,cwd:e.cwd.trim()||void 0,approvalMode:e.approvalMode,workspaceMode:e.workspaceMode,keepAlive:e.keepAlive,runtimeBudget:r};return{kind:`scheduled_task`,name:e.name.trim(),description:e.description.trim()||void 0,enabled:e.enabled,schedule:e.schedule.trim(),timezone:e.timezone.trim()||`UTC`,catchUpPolicy:`skip`,concurrencyPolicy:e.concurrencyPolicy,orchestratorId:e.orchestratorId,targetPolicy:i,taskTemplate:{title:e.title.trim(),body:e.body.trim(),severity:e.severity,dedupeKey:e.dedupeKey.trim()||void 0,externalUrl:e.externalUrl.trim()||void 0}}}function X(){let e=f(),t=h(e=>e.automations),r=h(e=>e.automationRuns),i=h(e=>e.orchestrators),a=h(e=>e.agents),o=h(e=>e.apiCall),c=h(e=>e.showError),l=h(e=>e.openConfirm),u=h(e=>e.fetchAutomations),d=h(e=>e.fetchAutomationRuns),p=h(e=>e.fetchTasks),m=h(e=>e.fetchProviderCatalog),g=h(e=>e.agentProfiles),[_,v]=(0,A.useState)(``),[x,S]=(0,A.useState)(()=>K(i[0]?.id||``,i[0])),[C,w]=(0,A.useState)(!1),[T,E]=(0,A.useState)(null),D=t.find(e=>e.id===_),O=i.filter(e=>e.status===`online`),k=r.filter(L);(0,A.useEffect)(()=>{Promise.all([u(),d(),m()])},[]),(0,A.useEffect)(()=>{!x.orchestratorId&&i[0]&&S(e=>({...e,orchestratorId:i[0].id}))},[i]);let M=(0,A.useMemo)(()=>{let e=new Map;for(let t of r){let n=e.get(t.automationId)||[];n.push(t),e.set(t.automationId,n)}return e},[r]);function N(){v(``),S(K(i[0]?.id||``,i[0]))}function P(e){v(e.id),S(J(e))}async function F(){w(!0);try{let e=Y(x);if(x.id)await o(`PATCH`,`/automations/`+encodeURIComponent(x.id),e);else{let t=await o(`POST`,`/automations`,e);v(t.id),S(J(t))}await Promise.all([u(),d()])}catch(e){c(`Save Failed`,e.message)}finally{w(!1)}}async function I(e,t){try{await o(`PATCH`,`/automations/`+encodeURIComponent(e.id),t),await u()}catch(e){c(`Update Failed`,e.message)}}async function R(e){try{await o(`POST`,`/automations/`+encodeURIComponent(e.id)+`/run`),await Promise.all([d(),p()])}catch(e){c(`Run Failed`,e.message)}}function z(e){l(`Delete Automation`,`Delete automation "${e.name}"?`,async()=>{try{await o(`DELETE`,`/automations/`+encodeURIComponent(e.id)),_===e.id&&N(),await Promise.all([u(),d()])}catch(e){c(`Delete Failed`,e.message)}})}return(0,j.jsxs)(`div`,{className:`space-y-4`,children:[(0,j.jsxs)(`div`,{className:`flex flex-wrap items-center gap-3`,children:[(0,j.jsx)(n,{className:`w-5 h-5 text-muted-foreground`}),(0,j.jsxs)(`div`,{children:[(0,j.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Automation`}),(0,j.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Scheduled tasks`})]}),(0,j.jsxs)(`div`,{className:`ml-auto flex flex-wrap items-center gap-2`,children:[(0,j.jsxs)(y,{variant:`outline`,className:`text-xs`,children:[t.length,` schedules`]}),(0,j.jsxs)(y,{variant:`outline`,className:`text-xs`,children:[k.length,` running`]}),(0,j.jsxs)(b,{size:`sm`,className:`gap-1`,onClick:N,children:[(0,j.jsx)(s,{className:`w-3.5 h-3.5`}),`New`]})]})]}),k.length>0&&(0,j.jsx)(Z,{runs:k,automations:t,agents:a,orchestrators:i,now:e,logRunId:T,onToggleLogs:e=>E(t=>t===e?null:e)}),(0,j.jsxs)(`div`,{className:`grid gap-4 xl:grid-cols-[minmax(0,1fr)_420px]`,children:[(0,j.jsx)(`div`,{className:`space-y-3`,children:t.length===0?(0,j.jsx)(`div`,{className:`py-16 text-center text-sm text-muted-foreground`,children:`No automations`}):t.map(t=>(0,j.jsx)(Q,{automation:t,runs:M.get(t.id)||[],now:e,selected:_===t.id,onEdit:()=>P(t),onRun:()=>R(t),onToggle:()=>I(t,{enabled:!t.enabled}),onDelete:()=>z(t)},t.id))}),(0,j.jsx)(ee,{form:x,agentProfiles:g,selected:D,saving:C,orchestrators:O.length?O:i,onChange:e=>S(t=>({...t,...e})),onSave:F})]}),(0,j.jsx)(te,{runs:r,automations:t,agents:a,orchestrators:i,now:e,logRunId:T,onToggleLogs:e=>E(t=>t===e?null:e)})]})}function Z({runs:e,automations:t,agents:n,orchestrators:r,now:a,logRunId:o,onToggleLogs:s}){let c=new Map(t.map(e=>[e.id,e.name]));return(0,j.jsxs)(`div`,{className:`rounded-lg border border-border bg-card/40`,children:[(0,j.jsx)(`div`,{className:`border-b border-border px-3 py-2 text-sm font-medium`,children:`Active runs`}),(0,j.jsx)(`div`,{className:`divide-y divide-border`,children:e.map(e=>{let t=G(e,n,r),l=U(e,a);return(0,j.jsxs)(`div`,{className:`space-y-3 px-3 py-3`,children:[(0,j.jsxs)(`div`,{className:`grid gap-3 text-xs md:grid-cols-[minmax(12rem,1fr)_7rem_7rem_7rem_8rem_auto] md:items-center`,children:[(0,j.jsxs)(`div`,{className:`min-w-0`,children:[(0,j.jsx)(`div`,{className:`truncate text-sm font-medium`,children:c.get(e.automationId)||e.automationId.slice(0,12)}),(0,j.jsx)(`div`,{className:`truncate font-mono text-muted-foreground`,children:e.targetAgentId||e.spawnedAgentId||z(e.meta?.onDemandLabel)||`-`})]}),(0,j.jsx)(y,{variant:`outline`,className:`border text-[10px] w-fit ${F(e)}`,children:P(e)}),(0,j.jsxs)(`div`,{children:[(0,j.jsx)(`span`,{className:`text-muted-foreground`,children:`elapsed `}),H(e,a)]}),(0,j.jsxs)(`div`,{children:[(0,j.jsx)(`span`,{className:`text-muted-foreground`,children:`left `}),l||`-`]}),(0,j.jsx)(`div`,{className:`font-mono text-muted-foreground`,children:e.taskId?`task #${e.taskId}`:`-`}),t&&(0,j.jsxs)(b,{type:`button`,size:`sm`,variant:`outline`,className:`h-7 gap-1 text-xs`,onClick:()=>s(e.id),children:[(0,j.jsx)(i,{className:`w-3 h-3`}),`Logs`]})]}),o===e.id&&t&&(0,j.jsx)(C,{orchestratorId:t.orchestratorId,session:t.session})]},e.id)})})]})}function Q({automation:e,runs:t,now:n,selected:i,onEdit:a,onRun:s,onToggle:c,onDelete:u}){let d=t[0],f=e.targetPolicy,p=f.mode===`existing_agent`?[f.selector.provider,f.selector.label?`label:${f.selector.label}`:``,...(f.selector.tags||[]).map(e=>`tag:${e}`),...(f.selector.capabilities||[]).map(e=>`cap:${e}`)].filter(Boolean).join(` `):`${f.provider} on demand`;return(0,j.jsxs)(k,{className:`${i?`ring-1 ring-primary/50`:``} ${e.enabled?``:`opacity-60`}`,children:[(0,j.jsx)(E,{className:`px-4 py-3`,children:(0,j.jsxs)(`div`,{className:`flex items-start gap-3`,children:[(0,j.jsx)(S,{checked:e.enabled,onCheckedChange:c,className:`mt-0.5`}),(0,j.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,j.jsx)(O,{className:`text-sm truncate`,children:e.name}),(0,j.jsxs)(`div`,{className:`mt-1 flex flex-wrap gap-1.5 text-xs text-muted-foreground`,children:[(0,j.jsx)(`span`,{className:`font-mono`,children:e.schedule}),(0,j.jsx)(`span`,{children:e.timezone}),(0,j.jsx)(`span`,{children:e.orchestratorId}),(0,j.jsx)(`span`,{className:`truncate max-w-[28rem]`,children:p})]})]}),(0,j.jsxs)(`div`,{className:`flex gap-1`,children:[(0,j.jsx)(b,{size:`icon`,variant:`ghost`,className:`h-7 w-7`,title:`Run now`,onClick:s,children:(0,j.jsx)(o,{className:`w-3.5 h-3.5`})}),(0,j.jsx)(b,{size:`icon`,variant:`ghost`,className:`h-7 w-7`,title:`Edit`,onClick:a,children:(0,j.jsx)(r,{className:`w-3.5 h-3.5`})}),(0,j.jsx)(b,{size:`icon`,variant:`ghost`,className:`h-7 w-7 text-red-400 hover:text-red-300`,title:`Delete`,onClick:u,children:(0,j.jsx)(l,{className:`w-3.5 h-3.5`})})]})]})}),(0,j.jsx)(D,{className:`px-4 pb-3 pt-0`,children:(0,j.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2 text-xs text-muted-foreground`,children:[(0,j.jsxs)(`span`,{title:e.nextRunAt?v(e.nextRunAt):void 0,children:[`next `,e.nextRunAt?_(n,e.nextRunAt):`disabled`]}),d&&(0,j.jsx)(y,{variant:`outline`,className:`border text-[10px] ${F(d)}`,children:P(d)}),d?.taskId&&(0,j.jsxs)(`span`,{children:[`task #`,d.taskId]}),d&&I(d)&&(0,j.jsx)(`span`,{className:`text-red-400 truncate`,children:I(d)})]})})]})}function ee({form:e,selected:t,saving:n,orchestrators:r,agentProfiles:i,onChange:o,onSave:s}){let c=h(e=>e.providerCatalog),l=r.find(t=>t.id===e.orchestratorId)?.providers||[],f=m(e.provider,c),p=f.find(t=>t.alias===e.model)?.efforts||[];function g(t){let n=r.find(e=>e.id===t)?.providers[0]||e.provider;o({orchestratorId:t,provider:n,model:d(n,c),effort:``})}function _(e){o({provider:e,model:d(e,c),effort:``})}return(0,j.jsxs)(`div`,{className:`rounded-lg border border-border bg-card p-4 space-y-4 h-fit xl:sticky xl:top-4`,children:[(0,j.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,j.jsx)(`h3`,{className:`text-sm font-semibold`,children:t?`Edit schedule`:`New schedule`}),(0,j.jsx)(y,{variant:e.enabled?`default`:`secondary`,className:`ml-auto text-[10px]`,children:e.enabled?`enabled`:`disabled`})]}),(0,j.jsxs)(`div`,{className:`grid gap-3`,children:[(0,j.jsx)($,{label:`Name`,children:(0,j.jsx)(x,{value:e.name,onChange:e=>o({name:e.target.value})})}),(0,j.jsx)($,{label:`Description`,children:(0,j.jsx)(x,{value:e.description,onChange:e=>o({description:e.target.value})})}),(0,j.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,j.jsx)($,{label:`Cron`,children:(0,j.jsx)(x,{value:e.schedule,onChange:e=>o({schedule:e.target.value}),className:`font-mono`})}),(0,j.jsx)($,{label:`Timezone`,children:(0,j.jsx)(x,{value:e.timezone,onChange:e=>o({timezone:e.target.value})})})]}),(0,j.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,j.jsx)($,{label:`Orchestrator`,children:(0,j.jsxs)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.orchestratorId,onChange:e=>g(e.target.value),children:[(0,j.jsx)(`option`,{value:``,children:`Select`}),r.map(e=>(0,j.jsxs)(`option`,{value:e.id,children:[e.hostname,` (`,e.id,`)`]},e.id))]})}),(0,j.jsx)($,{label:`Concurrency`,children:(0,j.jsxs)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.concurrencyPolicy,onChange:e=>o({concurrencyPolicy:e.target.value}),children:[(0,j.jsx)(`option`,{value:`skip`,children:`skip`}),(0,j.jsx)(`option`,{value:`replace`,children:`replace`}),(0,j.jsx)(`option`,{value:`queue`,children:`queue`})]})})]}),(0,j.jsxs)(`label`,{className:`flex items-center justify-between rounded-md border border-border px-3 py-2`,children:[(0,j.jsx)(`span`,{className:`text-sm`,children:`Enabled`}),(0,j.jsx)(S,{checked:e.enabled,onCheckedChange:e=>o({enabled:e})})]})]}),(0,j.jsxs)(`div`,{className:`space-y-3`,children:[(0,j.jsx)(w,{children:`Target`}),(0,j.jsxs)(`div`,{className:`grid grid-cols-2 gap-2`,children:[(0,j.jsx)(b,{type:`button`,variant:e.targetMode===`existing_agent`?`default`:`outline`,onClick:()=>o({targetMode:`existing_agent`}),children:`Existing`}),(0,j.jsx)(b,{type:`button`,variant:e.targetMode===`on_demand_agent`?`default`:`outline`,onClick:()=>o({targetMode:`on_demand_agent`}),children:`On demand`})]}),(0,j.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,j.jsx)($,{label:`Provider`,children:(0,j.jsxs)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.provider,onChange:e=>_(e.target.value),disabled:!l.length,children:[!l.length&&(0,j.jsx)(`option`,{value:e.provider,children:`No providers available`}),l.map(e=>(0,j.jsx)(`option`,{value:e,children:e},e))]})}),e.targetMode===`existing_agent`?(0,j.jsx)($,{label:`If no match`,children:(0,j.jsxs)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.ifNoMatch,onChange:e=>o({ifNoMatch:e.target.value}),children:[(0,j.jsx)(`option`,{value:`fail`,children:`fail`}),(0,j.jsx)(`option`,{value:`spawn`,children:`spawn`})]})}):(0,j.jsx)($,{label:`Approval`,children:(0,j.jsxs)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.approvalMode,onChange:e=>o({approvalMode:e.target.value}),children:[(0,j.jsx)(`option`,{value:`guarded`,children:`guarded`}),(0,j.jsx)(`option`,{value:`read-only`,children:`read-only`}),(0,j.jsx)(`option`,{value:`open`,children:`open`})]})})]}),e.targetMode===`on_demand_agent`&&(0,j.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,j.jsx)($,{label:`Model`,children:(0,j.jsx)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.model,onChange:e=>o({model:e.target.value,effort:``}),children:f.map(e=>(0,j.jsx)(`option`,{value:e.alias,children:e.label},e.alias))})}),(0,j.jsx)($,{label:`Effort`,children:(0,j.jsxs)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.effort,onChange:e=>o({effort:e.target.value}),disabled:!p.length,children:[(0,j.jsx)(`option`,{value:``,children:`Default`}),p.map(e=>(0,j.jsx)(`option`,{value:e,children:e},e))]})}),(0,j.jsx)($,{label:`Agent profile`,children:(0,j.jsxs)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.profile||`default-relay`,onChange:e=>o({profile:e.target.value}),children:[!i.length&&(0,j.jsx)(`option`,{value:`default-relay`,children:`default-relay`}),i.map(e=>(0,j.jsx)(`option`,{value:e.name,children:e.name},e.name))]})}),(0,j.jsx)($,{label:`Workspace`,children:(0,j.jsxs)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.workspaceMode,onChange:e=>o({workspaceMode:e.target.value}),children:[(0,j.jsx)(`option`,{value:`inherit`,children:`inherit (default → isolated worktree)`}),(0,j.jsx)(`option`,{value:`shared`,children:`shared (run in the repo, commit to its branch)`}),(0,j.jsx)(`option`,{value:`isolated`,children:`isolated (fresh git worktree per run)`})]})})]}),e.targetMode===`existing_agent`?(0,j.jsxs)(`div`,{className:`grid gap-3`,children:[(0,j.jsx)($,{label:`Label`,children:(0,j.jsx)(x,{value:e.label,onChange:e=>o({label:e.target.value})})}),(0,j.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,j.jsx)($,{label:`Tags`,children:(0,j.jsx)(x,{value:e.tags,onChange:e=>o({tags:e.target.value})})}),(0,j.jsx)($,{label:`Capabilities`,children:(0,j.jsx)(x,{value:e.capabilities,onChange:e=>o({capabilities:e.target.value})})})]})]}):(0,j.jsxs)(`div`,{className:`grid gap-3`,children:[(0,j.jsx)($,{label:`Cwd`,children:(0,j.jsx)(x,{value:e.cwd,onChange:e=>o({cwd:e.target.value})})}),(0,j.jsxs)(`label`,{className:`flex items-center justify-between rounded-md border border-border px-3 py-2`,children:[(0,j.jsx)(`span`,{className:`text-sm`,children:`Keep alive`}),(0,j.jsx)(S,{checked:e.keepAlive,onCheckedChange:e=>o({keepAlive:e})})]}),(0,j.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,j.jsx)($,{label:`Budget minutes`,children:(0,j.jsx)(x,{type:`number`,min:1,step:1,value:e.budgetMinutes,onChange:e=>o({budgetMinutes:e.target.value})})}),(0,j.jsx)($,{label:`Warning minute`,children:(0,j.jsx)(x,{type:`number`,min:0,step:1,value:e.warningMinutes,onChange:e=>o({warningMinutes:e.target.value})})})]}),(0,j.jsx)($,{label:`Warning message`,children:(0,j.jsx)(T,{value:e.warningMessage,onChange:e=>o({warningMessage:e.target.value}),className:`min-h-20`})})]})]}),(0,j.jsxs)(`div`,{className:`space-y-3`,children:[(0,j.jsx)(w,{children:`Task`}),(0,j.jsx)($,{label:`Title`,children:(0,j.jsx)(x,{value:e.title,onChange:e=>o({title:e.target.value})})}),(0,j.jsx)($,{label:`Body`,children:(0,j.jsx)(T,{value:e.body,onChange:e=>o({body:e.target.value}),className:`min-h-28`})}),(0,j.jsxs)(`div`,{className:`grid grid-cols-3 gap-3`,children:[(0,j.jsx)($,{label:`Severity`,children:(0,j.jsxs)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.severity,onChange:e=>o({severity:e.target.value}),children:[(0,j.jsx)(`option`,{value:`info`,children:`info`}),(0,j.jsx)(`option`,{value:`warning`,children:`warning`}),(0,j.jsx)(`option`,{value:`critical`,children:`critical`})]})}),(0,j.jsx)($,{label:`Dedupe key`,children:(0,j.jsx)(x,{value:e.dedupeKey,onChange:e=>o({dedupeKey:e.target.value})})}),(0,j.jsx)($,{label:`External URL`,children:(0,j.jsx)(x,{value:e.externalUrl,onChange:e=>o({externalUrl:e.target.value})})})]})]}),(0,j.jsxs)(b,{className:`w-full gap-2`,onClick:s,disabled:n||!e.name||!e.schedule||!e.orchestratorId||!e.title||!e.body,children:[n?(0,j.jsx)(a,{className:`w-4 h-4 animate-spin`}):(0,j.jsx)(u,{className:`w-4 h-4`}),`Save`]})]})}function $({label:e,children:t}){return(0,j.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,j.jsx)(w,{className:`text-xs text-muted-foreground`,children:e}),t]})}function te({runs:e,automations:t,agents:n,orchestrators:r,now:a,logRunId:o,onToggleLogs:s}){let c=new Map(t.map(e=>[e.id,e.name]));return(0,j.jsxs)(`div`,{className:`space-y-2`,children:[(0,j.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,j.jsx)(`h3`,{className:`text-sm font-semibold`,children:`Runs`}),(0,j.jsx)(y,{variant:`secondary`,className:`text-[10px]`,children:e.length})]}),(0,j.jsxs)(`div`,{className:`rounded-lg border border-border overflow-x-auto`,children:[(0,j.jsxs)(`div`,{className:`grid grid-cols-[minmax(10rem,1fr)_8rem_8rem_7rem_7rem_5rem] gap-3 bg-muted/30 px-3 py-2 text-xs text-muted-foreground`,children:[(0,j.jsx)(`span`,{children:`Automation`}),(0,j.jsx)(`span`,{children:`Status`}),(0,j.jsx)(`span`,{children:`Agent`}),(0,j.jsx)(`span`,{children:`Runtime`}),(0,j.jsx)(`span`,{children:`Updated`}),(0,j.jsx)(`span`,{children:`Logs`})]}),e.length===0?(0,j.jsx)(`div`,{className:`px-3 py-10 text-center text-sm text-muted-foreground`,children:`No runs`}):e.slice(0,80).map(e=>{let t=G(e,n,r),l=U(e,a),u=W(e);return(0,j.jsxs)(`div`,{className:`border-t border-border`,children:[(0,j.jsxs)(`div`,{className:`grid grid-cols-[minmax(10rem,1fr)_8rem_8rem_7rem_7rem_5rem] gap-3 px-3 py-2 text-xs items-center`,children:[(0,j.jsxs)(`div`,{className:`min-w-0`,children:[(0,j.jsx)(`div`,{className:`truncate`,children:c.get(e.automationId)||e.automationId.slice(0,12)}),(0,j.jsxs)(`div`,{className:`truncate text-muted-foreground`,children:[e.taskId?`task #${e.taskId}`:`no task`,l?` · ${l} left`:``,u?` · ${u}`:``]})]}),(0,j.jsx)(y,{variant:`outline`,className:`border text-[10px] w-fit ${F(e)}`,children:P(e)}),(0,j.jsx)(`span`,{className:`truncate font-mono text-muted-foreground`,children:e.targetAgentId||e.spawnedAgentId||`-`}),(0,j.jsx)(`span`,{className:`text-muted-foreground`,children:H(e,a)}),(0,j.jsx)(`span`,{className:`text-muted-foreground`,title:v(e.updatedAt),children:_(a,e.updatedAt)}),t?(0,j.jsx)(b,{type:`button`,size:`sm`,variant:`ghost`,className:`h-7 w-7 p-0`,title:`Open logs`,onClick:()=>s(e.id),children:(0,j.jsx)(i,{className:`w-3.5 h-3.5`})}):(0,j.jsx)(`span`,{className:`text-muted-foreground`,children:`-`})]}),o===e.id&&t&&(0,j.jsx)(`div`,{className:`border-t border-border px-3 py-3`,children:(0,j.jsx)(C,{orchestratorId:t.orchestratorId,session:t.session})})]},e.id)})]})]})}export{X as AutomationView};
|
|
2
|
-
//# sourceMappingURL=automation-ShdXg0pX.js.map
|