agent-relay-server 0.120.5 → 0.120.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/docs/openapi.json +1 -1
  2. package/package.json +3 -3
  3. package/public/assets/{MessageBubble-BVff6PSi.js → MessageBubble-So86mU9-.js} +2 -2
  4. package/public/assets/{MessageBubble-BVff6PSi.js.map → MessageBubble-So86mU9-.js.map} +1 -1
  5. package/public/assets/{PlanGraphPanel-BjrVU5Tg.js → PlanGraphPanel-DBZYAsww.js} +2 -2
  6. package/public/assets/{PlanGraphPanel-BjrVU5Tg.js.map → PlanGraphPanel-DBZYAsww.js.map} +1 -1
  7. package/public/assets/{activity-Bk9LEUr4.js → activity-CZNwmXg1.js} +2 -2
  8. package/public/assets/{activity-Bk9LEUr4.js.map → activity-CZNwmXg1.js.map} +1 -1
  9. package/public/assets/{agent-profiles-DSVDoQ_N.js → agent-profiles-BhadoGwi.js} +2 -2
  10. package/public/assets/{agent-profiles-DSVDoQ_N.js.map → agent-profiles-BhadoGwi.js.map} +1 -1
  11. package/public/assets/agents-DWn1xx9x.js +2 -0
  12. package/public/assets/{agents-8rQuJqii.js.map → agents-DWn1xx9x.js.map} +1 -1
  13. package/public/assets/{automation-CMxQAf3l.js → automation-BExrtKkb.js} +2 -2
  14. package/public/assets/{automation-CMxQAf3l.js.map → automation-BExrtKkb.js.map} +1 -1
  15. package/public/assets/chat-C7OhKtr6.js +2 -0
  16. package/public/assets/{chat-jlXywb9G.js.map → chat-C7OhKtr6.js.map} +1 -1
  17. package/public/assets/{formatted-body-CzehtPtx.js → formatted-body-BCQT2qSb.js} +3 -3
  18. package/public/assets/{formatted-body-CzehtPtx.js.map → formatted-body-BCQT2qSb.js.map} +1 -1
  19. package/public/assets/{formatted-body-impl-D3Bnt0Fj.js → formatted-body-impl-Bk7wnESl.js} +2 -2
  20. package/public/assets/{formatted-body-impl-D3Bnt0Fj.js.map → formatted-body-impl-Bk7wnESl.js.map} +1 -1
  21. package/public/assets/index-CRraCeo_.js +25 -0
  22. package/public/assets/index-CRraCeo_.js.map +1 -0
  23. package/public/assets/{insights-3H6KttBi.js → insights-DEgES6jz.js} +2 -2
  24. package/public/assets/{insights-3H6KttBi.js.map → insights-DEgES6jz.js.map} +1 -1
  25. package/public/assets/{maintenance-BANYcBcn.js → maintenance-BgVB6_gO.js} +2 -2
  26. package/public/assets/{maintenance-BANYcBcn.js.map → maintenance-BgVB6_gO.js.map} +1 -1
  27. package/public/assets/{managed-agents-DCFMWKqt.js → managed-agents-8_NKSkhI.js} +3 -3
  28. package/public/assets/{managed-agents-DCFMWKqt.js.map → managed-agents-8_NKSkhI.js.map} +1 -1
  29. package/public/assets/{markdown-preview-impl-CfaR-GRX.js → markdown-preview-impl-yxQkrd2M.js} +2 -2
  30. package/public/assets/{markdown-preview-impl-CfaR-GRX.js.map → markdown-preview-impl-yxQkrd2M.js.map} +1 -1
  31. package/public/assets/{memory-BmkUEyd5.js → memory-CJD8eekg.js} +2 -2
  32. package/public/assets/{memory-BmkUEyd5.js.map → memory-CJD8eekg.js.map} +1 -1
  33. package/public/assets/{messages-BQ_fVnSA.js → messages-DLHMZbHZ.js} +2 -2
  34. package/public/assets/{messages-BQ_fVnSA.js.map → messages-DLHMZbHZ.js.map} +1 -1
  35. package/public/assets/{orchestrators-DC7y9vQD.js → orchestrators-DEOep7FX.js} +2 -2
  36. package/public/assets/{orchestrators-DC7y9vQD.js.map → orchestrators-DEOep7FX.js.map} +1 -1
  37. package/public/assets/{overview-6lzywFHq.js → overview-BgpfO6mt.js} +2 -2
  38. package/public/assets/{overview-6lzywFHq.js.map → overview-BgpfO6mt.js.map} +1 -1
  39. package/public/assets/{pairs-CRrRIaHI.js → pairs-8NVKHh96.js} +2 -2
  40. package/public/assets/{pairs-CRrRIaHI.js.map → pairs-8NVKHh96.js.map} +1 -1
  41. package/public/assets/{prompt-settings-DvlnMq8H.js → prompt-settings-fqyLwUa-.js} +2 -2
  42. package/public/assets/{prompt-settings-DvlnMq8H.js.map → prompt-settings-fqyLwUa-.js.map} +1 -1
  43. package/public/assets/{registry-3TWg4zbE.js → registry-BKbLL4mO.js} +2 -2
  44. package/public/assets/{registry-3TWg4zbE.js.map → registry-BKbLL4mO.js.map} +1 -1
  45. package/public/assets/{security-A2VYKHeI.js → security-BlTFTbq0.js} +2 -2
  46. package/public/assets/{security-A2VYKHeI.js.map → security-BlTFTbq0.js.map} +1 -1
  47. package/public/assets/{select-_gK5Ojjc.js → select-Zd_qh0uU.js} +2 -2
  48. package/public/assets/{select-_gK5Ojjc.js.map → select-Zd_qh0uU.js.map} +1 -1
  49. package/public/assets/{settings-BZ26ewcu.js → settings-nHuSVRkL.js} +2 -2
  50. package/public/assets/{settings-BZ26ewcu.js.map → settings-nHuSVRkL.js.map} +1 -1
  51. package/public/assets/{tasks-DlokSl_P.js → tasks-C3JhQ8vB.js} +2 -2
  52. package/public/assets/{tasks-DlokSl_P.js.map → tasks-C3JhQ8vB.js.map} +1 -1
  53. package/public/assets/{teams-t7M1HMgS.js → teams-C9E9Yt39.js} +2 -2
  54. package/public/assets/{teams-t7M1HMgS.js.map → teams-C9E9Yt39.js.map} +1 -1
  55. package/public/assets/{terminal-viewer-impl-DxgrO-jH.js → terminal-viewer-impl-FP0JlsvS.js} +22 -22
  56. package/public/assets/{terminal-viewer-impl-DxgrO-jH.js.map → terminal-viewer-impl-FP0JlsvS.js.map} +1 -1
  57. package/public/assets/types-uVOXgvMT.js.map +1 -1
  58. package/public/assets/{work-queue-V8LR-3Gi.js → work-queue-Ema7Hy3s.js} +2 -2
  59. package/public/assets/{work-queue-V8LR-3Gi.js.map → work-queue-Ema7Hy3s.js.map} +1 -1
  60. package/public/index.html +2 -2
  61. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  62. package/src/commands-db.ts +14 -0
  63. package/src/db/delivery.ts +85 -3
  64. package/src/db/stats.ts +10 -8
  65. package/src/db/task-entity.ts +7 -8
  66. package/src/lifecycle-manager.ts +11 -12
  67. package/src/maintenance/jobs.ts +3 -1
  68. package/src/maintenance/workspaces.ts +28 -47
  69. package/src/managed-policy-escalation.ts +47 -0
  70. package/src/mcp-task-tools.ts +4 -5
  71. package/src/routes/commands.ts +8 -245
  72. package/src/routes/tasks.ts +3 -5
  73. package/src/services/issue-lifecycle.ts +23 -8
  74. package/src/services/task-entity.ts +54 -2
  75. package/src/workspace-auto-merge.ts +94 -34
  76. package/src/workspace-command-settle.ts +227 -0
  77. package/src/workspace-escalation.ts +45 -0
  78. package/src/workspace-merge-progress.ts +16 -0
  79. package/src/workspace-orphans.ts +91 -3
  80. package/public/assets/agents-8rQuJqii.js +0 -2
  81. package/public/assets/chat-jlXywb9G.js +0 -2
  82. package/public/assets/index-4g3PbGVN.js +0 -25
  83. package/public/assets/index-4g3PbGVN.js.map +0 -1
@@ -0,0 +1,227 @@
1
+ import { isRecord, sanitizeFsName, type LandGateRunResult } from "agent-relay-sdk";
2
+ import { storeTextArtifact } from "./artifact-text";
3
+ import { notifyBaseWorktreeStrand, notifyBranchLandFailed, notifyBranchLanded, notifyLandGateFailed, notifyLandGateWarnings } from "./branch-landed";
4
+ import { getWorkspace, releaseMergeLease, setWorkspaceBranch, updateWorkspaceStatus } from "./db";
5
+ import { handleReviewerMergeResult } from "./reviewer-pipeline";
6
+ import { emitWorkspaceTaskSemanticNotifications } from "./services/task-lifecycle";
7
+ import { VALID_WORKSPACE_STATUSES, cleanString, optionalEnum } from "./validation";
8
+ import { CLEAR_MERGE_NO_PROGRESS, noteMergeNoProgress } from "./workspace-merge-progress";
9
+ import { claimMetadataPatch } from "./workspace-claim";
10
+ import { commandTargetsWorkspaceOwner } from "./workspace-host";
11
+ import type { Command, WorkspaceStatus } from "./types";
12
+
13
+ export interface WorkspaceCommandSettleResult {
14
+ settled: boolean;
15
+ ignoredWrongOwner?: boolean;
16
+ }
17
+
18
+ export interface WorkspaceCommandSettleOptions {
19
+ onMergeSettled?: (repoRoot: string | undefined) => void;
20
+ }
21
+
22
+ const WORKSPACE_MERGE_SETTLED_STATUSES = new Set(["succeeded", "failed", "timed_out", "rejected", "canceled"]);
23
+ const WORKSPACE_MERGE_FAILED_STATUSES = new Set(["failed", "timed_out", "rejected", "canceled"]);
24
+ const WORKSPACE_CLEANUP_FAILED_STATUSES = new Set(["failed", "timed_out", "rejected", "canceled"]);
25
+
26
+ function normalizeGateRunResult(value: unknown): LandGateRunResult | undefined {
27
+ if (!isRecord(value)) return undefined;
28
+ const name = typeof value.name === "string" ? value.name : undefined;
29
+ const command = typeof value.command === "string" ? value.command : "";
30
+ if (!name) return undefined;
31
+ return {
32
+ name,
33
+ command,
34
+ optional: value.optional === true,
35
+ passed: value.passed === true,
36
+ exitCode: typeof value.exitCode === "number" ? value.exitCode : null,
37
+ timedOut: value.timedOut === true,
38
+ durationMs: typeof value.durationMs === "number" ? value.durationMs : 0,
39
+ outputTail: typeof value.outputTail === "string" ? value.outputTail : "",
40
+ output: typeof value.output === "string" ? value.output : (typeof value.outputTail === "string" ? value.outputTail : ""),
41
+ };
42
+ }
43
+
44
+ function normalizeGateWarnings(value: unknown): LandGateRunResult[] {
45
+ if (!Array.isArray(value)) return [];
46
+ return value.map(normalizeGateRunResult).filter((g): g is LandGateRunResult => Boolean(g));
47
+ }
48
+
49
+ async function uploadGateOutputArtifact(workspaceId: string, gate: LandGateRunResult): Promise<string | undefined> {
50
+ try {
51
+ const safeName = sanitizeFsName(gate.name, { replacement: "-", maxLen: 60, trimEdge: true, fallback: "gate" });
52
+ const stored = await storeTextArtifact({
53
+ text: gate.output || gate.outputTail || "(no output)",
54
+ filename: `land-gate-${safeName}-${workspaceId}.log`,
55
+ createdBy: "server",
56
+ metadata: { kind: "land-gate-output", workspaceId, gateName: gate.name, gateCommand: gate.command, exitCode: gate.exitCode, timedOut: gate.timedOut },
57
+ });
58
+ return stored.id;
59
+ } catch (error) {
60
+ console.warn(`land-gate artifact upload failed for ${workspaceId} gate "${gate.name}": ${String(error)}`);
61
+ return undefined;
62
+ }
63
+ }
64
+
65
+ function commandFailureReason(command: Command, fallback: string): string {
66
+ if (command.error) return command.error;
67
+ if (command.status === "timed_out") return "command timed out";
68
+ if (command.status === "canceled") return "command canceled";
69
+ if (command.status === "rejected") return "command rejected";
70
+ return fallback;
71
+ }
72
+
73
+ export function settleWorkspaceCleanupCommand(command: Command): WorkspaceCommandSettleResult {
74
+ if (command.type !== "workspace.cleanup") return { settled: false };
75
+ if (command.status === "succeeded" && isRecord(command.result)) {
76
+ const workspaceId = cleanString(command.result.workspaceId, "result.workspaceId", { max: 160 });
77
+ if (!workspaceId) return { settled: true };
78
+ const workspace = getWorkspace(workspaceId);
79
+ if (workspace && commandTargetsWorkspaceOwner(command, workspace)) {
80
+ updateWorkspaceStatus(workspaceId, "cleaned", {
81
+ cleanupResult: command.result,
82
+ cleanupCommandId: command.id,
83
+ });
84
+ }
85
+ return { settled: true };
86
+ }
87
+ if (!WORKSPACE_CLEANUP_FAILED_STATUSES.has(command.status)) return { settled: false };
88
+ const resultWorkspaceId = isRecord(command.result) ? cleanString(command.result.workspaceId, "result.workspaceId", { max: 160 }) : undefined;
89
+ const workspaceId = resultWorkspaceId ?? command.correlationId;
90
+ if (!workspaceId) return { settled: true };
91
+ const workspace = getWorkspace(workspaceId);
92
+ if (!workspace || workspace.status !== "cleanup_requested") return { settled: true };
93
+ if (!commandTargetsWorkspaceOwner(command, workspace)) return { settled: true, ignoredWrongOwner: true };
94
+ updateWorkspaceStatus(workspaceId, "active", {
95
+ cleanupCommandId: command.id,
96
+ cleanupError: commandFailureReason(command, "cleanup failed"),
97
+ cleanupFailedAt: Date.now(),
98
+ cleanupResult: isRecord(command.result) ? command.result : undefined,
99
+ });
100
+ return { settled: true };
101
+ }
102
+
103
+ export function settleWorkspaceMergeCommand(command: Command, opts: WorkspaceCommandSettleOptions = {}): WorkspaceCommandSettleResult {
104
+ if (command.type !== "workspace.merge") return { settled: false };
105
+ if (WORKSPACE_MERGE_SETTLED_STATUSES.has(command.status)) {
106
+ releaseMergeLease({ commandId: command.id });
107
+ const settledRepoRoot = typeof command.params.repoRoot === "string" ? command.params.repoRoot : undefined;
108
+ opts.onMergeSettled?.(settledRepoRoot);
109
+ }
110
+ if (command.status === "succeeded" && isRecord(command.result)) {
111
+ if (handleReviewerMergeResult(command, command.result)) return { settled: true };
112
+ const workspaceId = cleanString(command.result.workspaceId, "result.workspaceId", { max: 160 });
113
+ const resultStatus = optionalEnum(command.result.status, "result.status", VALID_WORKSPACE_STATUSES) as WorkspaceStatus | undefined;
114
+ if (!workspaceId || !resultStatus) return { settled: true };
115
+ const landedWorkspace = getWorkspace(workspaceId);
116
+ if (landedWorkspace && !commandTargetsWorkspaceOwner(command, landedWorkspace)) return { settled: true, ignoredWrongOwner: true };
117
+ const newBranch = cleanString(command.result.newBranch, "result.newBranch", { max: 240 });
118
+ const normalizedStatus = command.result.merged === true
119
+ && !newBranch
120
+ && ["review_requested", "merge_planned", "conflict"].includes(resultStatus)
121
+ ? "merged"
122
+ : resultStatus;
123
+ updateWorkspaceStatus(workspaceId, normalizedStatus, {
124
+ mergeResult: command.result,
125
+ mergeCommandId: command.id,
126
+ mergedAt: Date.now(),
127
+ ...CLEAR_MERGE_NO_PROGRESS,
128
+ mergeError: undefined,
129
+ ...(normalizedStatus !== resultStatus ? { mergeStatusNormalizedFrom: resultStatus } : {}),
130
+ ...claimMetadataPatch(true),
131
+ });
132
+ const mergedSha = cleanString(command.result.mergedSha, "result.mergedSha", { max: 64 });
133
+ const baseSha = cleanString(command.result.baseSha, "result.baseSha", { max: 64 });
134
+ if (newBranch) setWorkspaceBranch(workspaceId, newBranch, baseSha ?? mergedSha);
135
+ const prLanded = isRecord(command.params.prLanded) ? command.params.prLanded : undefined;
136
+ const baseWorktreeSync = isRecord(command.result.baseWorktreeSync) ? command.result.baseWorktreeSync : undefined;
137
+ if (baseWorktreeSync && baseWorktreeSync.reconciled === false && landedWorkspace) {
138
+ notifyBaseWorktreeStrand({
139
+ workspace: landedWorkspace,
140
+ sync: {
141
+ reconciled: false,
142
+ unsyncedPaths: Array.isArray(baseWorktreeSync.unsyncedPaths) ? baseWorktreeSync.unsyncedPaths.filter((p): p is string => typeof p === "string") : undefined,
143
+ preservedWipPaths: Array.isArray(baseWorktreeSync.preservedWipPaths) ? baseWorktreeSync.preservedWipPaths.filter((p): p is string => typeof p === "string") : undefined,
144
+ message: typeof baseWorktreeSync.message === "string" ? baseWorktreeSync.message : undefined,
145
+ },
146
+ });
147
+ }
148
+ if ((command.result.merged === true || prLanded) && landedWorkspace) {
149
+ notifyBranchLanded({
150
+ workspace: landedWorkspace,
151
+ mergedSha: prLanded ? cleanString(prLanded.sha, "params.prLanded.sha", { max: 64 }) ?? mergedSha : mergedSha,
152
+ subject: prLanded
153
+ ? cleanString(prLanded.subject, "params.prLanded.subject", { max: 200 })
154
+ : cleanString(command.result.subject, "result.subject", { max: 200 }),
155
+ prLand: Boolean(prLanded),
156
+ newBranch,
157
+ pushed: prLanded ? true : typeof command.result.pushed === "boolean" ? command.result.pushed : undefined,
158
+ });
159
+ }
160
+ const gateWarnings = command.result.merged === true ? normalizeGateWarnings(command.result.gateWarnings) : [];
161
+ if (gateWarnings.length && landedWorkspace) {
162
+ const warnWorkspace = landedWorkspace;
163
+ void (async () => {
164
+ const combined = gateWarnings.map((g) => `### ${g.name} (${g.command}) - ${g.timedOut ? "timed out" : g.exitCode === null ? "could not run" : `exited ${g.exitCode}`}\n${g.output || g.outputTail || "(no output)"}`).join("\n\n");
165
+ let artifactId: string | undefined;
166
+ try {
167
+ artifactId = (await storeTextArtifact({ text: combined, filename: `land-gate-warnings-${warnWorkspace.id}.log`, createdBy: "server", metadata: { kind: "land-gate-warnings", workspaceId: warnWorkspace.id, gateNames: gateWarnings.map((g) => g.name) } })).id;
168
+ } catch (error) {
169
+ console.warn(`land-gate-warnings artifact upload failed for ${warnWorkspace.id}: ${String(error)}`);
170
+ }
171
+ try {
172
+ notifyLandGateWarnings({ workspace: warnWorkspace, warnings: gateWarnings, artifactId });
173
+ } catch (error) {
174
+ console.warn(`land-gate-warnings notify threw for ${warnWorkspace.id}: ${String(error)}`);
175
+ }
176
+ })();
177
+ }
178
+ return { settled: true };
179
+ }
180
+ if (WORKSPACE_MERGE_FAILED_STATUSES.has(command.status) && command.correlationId) {
181
+ const current = getWorkspace(command.correlationId);
182
+ if (!current || current.status !== "merge_planned") return { settled: true };
183
+ if (!commandTargetsWorkspaceOwner(command, current)) return { settled: true, ignoredWrongOwner: true };
184
+ const failedResultStatus = isRecord(command.result)
185
+ ? optionalEnum(command.result.status, "result.status", VALID_WORKSPACE_STATUSES) as WorkspaceStatus | undefined
186
+ : undefined;
187
+ const nextStatus: WorkspaceStatus = failedResultStatus && failedResultStatus !== "merge_planned"
188
+ ? failedResultStatus
189
+ : "review_requested";
190
+ const reason = commandFailureReason(command, "merge failed");
191
+ updateWorkspaceStatus(command.correlationId, nextStatus, {
192
+ mergeCommandId: command.id,
193
+ mergeError: reason,
194
+ ...noteMergeNoProgress(current.metadata),
195
+ });
196
+ if (nextStatus === "conflict") {
197
+ emitWorkspaceTaskSemanticNotifications({
198
+ workspace: current,
199
+ transition: "land-conflict",
200
+ actor: "workspace-merge",
201
+ occurrenceKey: `workspace-merge-conflict:${current.id}:${command.id}`,
202
+ outcome: { reason, commandId: command.id, status: nextStatus },
203
+ });
204
+ }
205
+ const gateFailure = isRecord(command.result) ? normalizeGateRunResult(command.result.gateFailure) : undefined;
206
+ if (gateFailure) {
207
+ void (async () => {
208
+ const artifactId = await uploadGateOutputArtifact(current.id, gateFailure);
209
+ try {
210
+ notifyLandGateFailed({ workspace: current, gate: gateFailure, artifactId });
211
+ } catch (error) {
212
+ console.warn(`land-gate-failed notify threw for ${current.id}: ${String(error)}`);
213
+ }
214
+ })();
215
+ } else {
216
+ notifyBranchLandFailed({ workspace: current, commandId: command.id, reason });
217
+ }
218
+ return { settled: true };
219
+ }
220
+ return { settled: false };
221
+ }
222
+
223
+ export function settleWorkspaceCommand(command: Command, opts: WorkspaceCommandSettleOptions = {}): WorkspaceCommandSettleResult {
224
+ if (command.type === "workspace.merge") return settleWorkspaceMergeCommand(command, opts);
225
+ if (command.type === "workspace.cleanup") return settleWorkspaceCleanupCommand(command);
226
+ return { settled: false };
227
+ }
@@ -0,0 +1,45 @@
1
+ import { stewardFallbackTarget } from "./config";
2
+ import { routeNotification } from "./notification-router";
3
+ import { resolveStewardIdentity } from "./steward-identity";
4
+ import type { NotificationType } from "./notification-types";
5
+ import type { WorkspaceRecord, WorkspaceStatus } from "./types";
6
+
7
+ export const READY_TO_LAND_ESCALATION_STATUSES = new Set<WorkspaceStatus>(["ready", "review_requested"]);
8
+
9
+ export function routeWorkspaceCustodyEscalation(input: {
10
+ workspace: Pick<WorkspaceRecord, "id" | "repoRoot" | "branch" | "status" | "ownerAgentId">;
11
+ type?: NotificationType;
12
+ subject: string;
13
+ body: string;
14
+ payload: Record<string, unknown>;
15
+ idempotencyKey?: string;
16
+ }): string[] {
17
+ const { ownerAgentId, coordinatorAgentId } = resolveStewardIdentity(input.workspace);
18
+ const fallback = stewardFallbackTarget();
19
+ const recipients = [...new Set([ownerAgentId, coordinatorAgentId, fallback].filter((value): value is string => Boolean(value)))];
20
+ if (!recipients.length) return [];
21
+
22
+ routeNotification({
23
+ type: input.type ?? "workspace.stranded_escalation",
24
+ scope: {
25
+ workspaceId: input.workspace.id,
26
+ repoRoot: input.workspace.repoRoot,
27
+ author: ownerAgentId,
28
+ parent: coordinatorAgentId ?? undefined,
29
+ },
30
+ recipients,
31
+ message: {
32
+ subject: input.subject,
33
+ body: input.body,
34
+ payload: {
35
+ ...input.payload,
36
+ coordinatorAgentId,
37
+ ownerAgentId,
38
+ fallbackTarget: fallback || undefined,
39
+ },
40
+ idempotencyKey: input.idempotencyKey,
41
+ },
42
+ ttlMs: null,
43
+ });
44
+ return recipients;
45
+ }
@@ -0,0 +1,16 @@
1
+ export const MAX_NO_PROGRESS_MERGE_ATTEMPTS = 3;
2
+
3
+ export function mergeNoProgressCount(metadata: Record<string, unknown>): number {
4
+ const n = metadata.autoMergeNoProgressCount;
5
+ return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : 0;
6
+ }
7
+
8
+ export function noteMergeNoProgress(metadata: Record<string, unknown>): Record<string, unknown> {
9
+ return { autoMergeNoProgressCount: mergeNoProgressCount(metadata) + 1, autoMergeNoProgressAt: Date.now() };
10
+ }
11
+
12
+ export const CLEAR_MERGE_NO_PROGRESS: Record<string, unknown> = {
13
+ autoMergeNoProgressCount: 0,
14
+ autoMergeNoProgressAt: undefined,
15
+ autoMergeBackedOffAt: undefined,
16
+ };
@@ -30,6 +30,7 @@ import { isOwnerAlive, liveAgentCwds, worktreeInUseByLiveAgent } from "./workspa
30
30
  import { applyWorkspaceAction } from "./workspace-actions";
31
31
  import { workspaceOwnerOrchestrator } from "./workspace-host";
32
32
  import { UNLANDED_FLAG_COOLDOWN_MS, orphanGraceMs, orphanWorktreeReapEnabled, relayToken } from "./config";
33
+ import { READY_TO_LAND_ESCALATION_STATUSES, routeWorkspaceCustodyEscalation } from "./workspace-escalation";
33
34
 
34
35
  // Don't re-flag the same un-landed orphan every sweep — surface it once, then
35
36
  // stay quiet for this window. In-memory (keyed by worktree path) like the
@@ -100,11 +101,13 @@ async function fetchBranchReapState(
100
101
  apiUrl: string,
101
102
  repoRoot: string,
102
103
  branch: string | undefined,
104
+ workspaceId?: string,
103
105
  baseRef?: string,
104
106
  baseSha?: string,
105
107
  ): Promise<MissingBranchProbe> {
106
108
  if (!branch) return { kind: "unavailable" };
107
109
  const query = new URLSearchParams({ repoRoot, branch, checkPr: "1" });
110
+ if (workspaceId) query.set("workspaceId", workspaceId);
108
111
  if (baseRef) query.set("baseRef", baseRef);
109
112
  if (baseSha) query.set("baseSha", baseSha);
110
113
  try {
@@ -127,6 +130,23 @@ function previewReapable(preview: WorkspaceMergePreview): boolean | undefined {
127
130
  return worktreeReapable({ landed: preview.landed, ahead: preview.ahead, unmergedAhead: preview.unmergedAhead, dirtyCount: 0 });
128
131
  }
129
132
 
133
+ function previewHasWorkEvidence(preview: WorkspaceMergePreview | null | undefined): boolean {
134
+ if (!preview || preview.error) return false;
135
+ if (preview.recoverableTip) return true;
136
+ if ((preview.dirtyCount ?? 0) > 0) return true;
137
+ return (preview.unmergedAhead ?? preview.ahead ?? 0) > 0 && preview.landed !== true;
138
+ }
139
+
140
+ function workspaceNeedsCustodyEscalation(workspace: Pick<WorkspaceRecord, "status">, hasWorkEvidence: boolean): boolean {
141
+ return READY_TO_LAND_ESCALATION_STATUSES.has(workspace.status) || hasWorkEvidence;
142
+ }
143
+
144
+ function orphanHasWorkEvidence(orphan: WorkspaceOrphan): boolean {
145
+ if (orphan.dirty) return true;
146
+ if ((orphan.unmergedAhead ?? orphan.ahead ?? 0) > 0 && orphan.landed !== true) return true;
147
+ return orphan.safeToReap === false;
148
+ }
149
+
130
150
  function onlineOrchestrators(): OnlineOrchestrator[] {
131
151
  return listOrchestrators()
132
152
  .filter((orch) => orch.status === "online" && orch.apiUrl && orch.agentId)
@@ -404,6 +424,15 @@ export async function reapOrphanedWorktrees(): Promise<Record<string, unknown>>
404
424
  : orphan.dirty
405
425
  ? "uncommitted changes in the worktree"
406
426
  : `${orphan.unmergedAhead ?? orphan.ahead ?? "?"} un-landed commit(s)`;
427
+ if (orphanHasWorkEvidence(orphan)) {
428
+ routeWorkspaceCustodyEscalation({
429
+ workspace: { id: `orphan:${orphan.worktreePath}`, repoRoot: orphan.repoRoot, branch: orphan.branch, status: "review_requested", ownerAgentId: undefined },
430
+ subject: "Stranded worktree needs attention",
431
+ body: `${orphan.branch ?? orphan.worktreePath} in ${orphan.repoRoot} is orphaned and holds work that has not landed - ${detail}. Recover the commits before removing it.`,
432
+ payload: { kind: "workspace.orphan-stranded", worktreePath: orphan.worktreePath, repoRoot: orphan.repoRoot, branch: orphan.branch, ahead: orphan.ahead, unmergedAhead: orphan.unmergedAhead, dirty: orphan.dirty, headSha: orphan.headSha },
433
+ idempotencyKey: `workspace-orphan-stranded:${orphan.worktreePath}:${orphan.headSha ?? "unknown"}`,
434
+ });
435
+ }
407
436
  createActivityEvent({
408
437
  clientId: `workspace-orphan-stranded-${orphan.worktreePath}-${now}`,
409
438
  kind: "state",
@@ -426,6 +455,15 @@ export async function reapOrphanedWorktrees(): Promise<Record<string, unknown>>
426
455
  if (ownerAlive || inFlight) {
427
456
  if (now - last < UNLANDED_FLAG_COOLDOWN_MS) continue;
428
457
  flaggedAt.set(key, now);
458
+ if (workspaceNeedsCustodyEscalation(workspace, false)) {
459
+ routeWorkspaceCustodyEscalation({
460
+ workspace,
461
+ subject: "Workspace row has no worktree on disk",
462
+ body: `Workspace \`${workspace.branch ?? workspace.id}\` in ${workspace.repoRoot} is ${workspace.status}, but its worktree ${workspace.worktreePath} is missing on disk. This was in Relay custody; recover or explicitly abandon it.`,
463
+ payload: { kind: "workspace.missing-worktree", workspaceId: workspace.id, repoRoot: workspace.repoRoot, branch: workspace.branch, status: workspace.status, worktreePath: workspace.worktreePath, ownerAlive, inFlight },
464
+ idempotencyKey: `workspace-missing-worktree:${workspace.id}:${workspace.status}:${now}`,
465
+ });
466
+ }
429
467
  createActivityEvent({
430
468
  clientId: `workspace-row-no-worktree-${workspace.id}-${now}`,
431
469
  kind: "state",
@@ -449,7 +487,7 @@ export async function reapOrphanedWorktrees(): Promise<Record<string, unknown>>
449
487
 
450
488
  const orch = workspaceOwnerOrchestrator(workspace, orchestrators, { onlineOnly: false, requireApiUrl: true });
451
489
  const probe = orch?.apiUrl
452
- ? await fetchBranchReapState(orch.apiUrl, workspace.repoRoot, workspace.branch, workspace.baseRef, workspace.baseSha)
490
+ ? await fetchBranchReapState(orch.apiUrl, workspace.repoRoot, workspace.branch, workspace.id, workspace.baseRef, workspace.baseSha)
453
491
  : { kind: "unavailable" } as const;
454
492
  const safeToAbandon = probe.kind === "gone"
455
493
  ? true
@@ -467,6 +505,16 @@ export async function reapOrphanedWorktrees(): Promise<Record<string, unknown>>
467
505
  if (!updated) continue;
468
506
  autoAbandoned.push(workspace.id);
469
507
  flaggedAt.delete(key);
508
+ if (workspaceNeedsCustodyEscalation(workspace, previewHasWorkEvidence(probe.kind === "preview" ? probe.preview : null))) {
509
+ routeWorkspaceCustodyEscalation({
510
+ workspace,
511
+ type: "workspace.auto_abandoned",
512
+ subject: "Workspace auto-abandoned",
513
+ body: `Workspace \`${workspace.branch ?? workspace.id}\` in ${workspace.repoRoot} was auto-abandoned because its worktree is missing and ${probe.kind === "gone" ? "the branch ref is gone" : "the branch appears landed"}.`,
514
+ payload: { kind: "workspace.auto-abandoned", workspaceId: workspace.id, repoRoot: workspace.repoRoot, branch: workspace.branch, status: workspace.status, reason: reasonText, worktreePath: workspace.worktreePath },
515
+ idempotencyKey: `workspace-auto-abandoned:${workspace.id}:${reasonText}`,
516
+ });
517
+ }
470
518
  createActivityEvent({
471
519
  clientId: `workspace-row-auto-abandoned-${workspace.id}-${now}`,
472
520
  kind: "state",
@@ -490,11 +538,32 @@ export async function reapOrphanedWorktrees(): Promise<Record<string, unknown>>
490
538
  if (now - last < UNLANDED_FLAG_COOLDOWN_MS) continue;
491
539
  flaggedAt.set(key, now);
492
540
  flaggedMissingWorktrees.push(workspace.id);
541
+ const hasWorkEvidence = probe.kind === "preview" && previewHasWorkEvidence(probe.preview);
493
542
  const detail = probe.kind === "preview"
494
- ? `${probe.preview.unmergedAhead ?? probe.preview.ahead ?? "?"} un-landed commit(s) still recoverable on branch ${workspace.branch ?? workspace.id}`
543
+ ? probe.preview.recoverableTip
544
+ ? `recoverable workspace tip ${probe.preview.recoverableTip.slice(0, 12)} at ${probe.preview.recoverableTipRef ?? "workspace-tip ref"}`
545
+ : `${probe.preview.unmergedAhead ?? probe.preview.ahead ?? "?"} un-landed commit(s) still recoverable on branch ${workspace.branch ?? workspace.id}`
495
546
  : workspace.branch
496
547
  ? `host could not confirm whether branch ${workspace.branch} has landed`
497
548
  : "host could not confirm whether recoverable branch work remains";
549
+ if (workspaceNeedsCustodyEscalation(workspace, hasWorkEvidence)) {
550
+ routeWorkspaceCustodyEscalation({
551
+ workspace,
552
+ subject: "Missing-worktree workspace needs attention",
553
+ body: `${workspace.id} in ${workspace.repoRoot} has no worktree on disk and cannot be auto-abandoned safely - ${detail}. Recover it, then abandon or clean it up explicitly.`,
554
+ payload: {
555
+ kind: "workspace.missing-worktree-stranded",
556
+ workspaceId: workspace.id,
557
+ repoRoot: workspace.repoRoot,
558
+ branch: workspace.branch,
559
+ status: workspace.status,
560
+ probe: probe.kind,
561
+ worktreePath: workspace.worktreePath,
562
+ ...(probe.kind === "preview" ? { ahead: probe.preview.ahead, unmergedAhead: probe.preview.unmergedAhead, landed: probe.preview.landed, recoverableTip: probe.preview.recoverableTip, recoverableTipRef: probe.preview.recoverableTipRef } : {}),
563
+ },
564
+ idempotencyKey: `workspace-missing-stranded:${workspace.id}:${probe.kind}:${probe.kind === "preview" ? probe.preview.recoverableTip ?? probe.preview.headSha ?? probe.preview.ahead ?? "preview" : "unavailable"}`,
565
+ });
566
+ }
498
567
  createActivityEvent({
499
568
  clientId: `workspace-row-needs-attention-${workspace.id}-${now}`,
500
569
  kind: "state",
@@ -511,7 +580,7 @@ export async function reapOrphanedWorktrees(): Promise<Record<string, unknown>>
511
580
  branch: workspace.branch,
512
581
  status: workspace.status,
513
582
  probe: probe.kind,
514
- ...(probe.kind === "preview" ? { ahead: probe.preview.ahead, unmergedAhead: probe.preview.unmergedAhead, landed: probe.preview.landed } : {}),
583
+ ...(probe.kind === "preview" ? { ahead: probe.preview.ahead, unmergedAhead: probe.preview.unmergedAhead, landed: probe.preview.landed, recoverableTip: probe.preview.recoverableTip, recoverableTipRef: probe.preview.recoverableTipRef } : {}),
515
584
  },
516
585
  });
517
586
  }
@@ -586,6 +655,25 @@ export async function reapOrphanedWorktrees(): Promise<Record<string, unknown>>
586
655
  : (preview && (preview.dirtyCount ?? 0) > 0)
587
656
  ? "uncommitted changes in the worktree"
588
657
  : `${preview?.unmergedAhead ?? preview?.ahead ?? "?"} un-landed commit(s)`;
658
+ const hasWorkEvidence = previewHasWorkEvidence(preview);
659
+ if (workspaceNeedsCustodyEscalation(workspace, hasWorkEvidence)) {
660
+ routeWorkspaceCustodyEscalation({
661
+ workspace,
662
+ subject: "Dead-owner worktree needs attention",
663
+ body: `${workspace.branch ?? workspace.id} in ${workspace.repoRoot} is owned by gone agent ${workspace.ownerAgentId ?? "?"} and holds work that has not landed (${detail}). Recover the commits, then clean it up explicitly.`,
664
+ payload: {
665
+ kind: "workspace.dead-owner-stranded",
666
+ workspaceId: workspace.id,
667
+ repoRoot: workspace.repoRoot,
668
+ branch: workspace.branch,
669
+ ownerAgentId: workspace.ownerAgentId,
670
+ worktreePath: workspace.worktreePath,
671
+ deadOwner: true,
672
+ ...(preview ? { ahead: preview.ahead, unmergedAhead: preview.unmergedAhead, dirtyCount: preview.dirtyCount, landed: preview.landed } : {}),
673
+ },
674
+ idempotencyKey: `workspace-dead-owner-stranded:${workspace.id}:${preview?.headSha ?? preview?.ahead ?? "unknown"}`,
675
+ });
676
+ }
589
677
  createActivityEvent({
590
678
  clientId: `workspace-dead-owner-stranded-${workspace.id}-${now}`,
591
679
  kind: "state",
@@ -1,2 +0,0 @@
1
- import{r as e}from"./chunk-CilyBKbf.js";import{$ as t,K as n,V as r,Vt as i,Wn as a,Yn as o,Zt as s,_ as c,a as l,er as u,g as d,gt as f,hr as p,nr as m,nt as h,u as g,z as _}from"./lucide-react-DuhlF2Sj.js";import{p as ee,t as v}from"./store-nXGxgz2Q.js";import{h as y,p as b}from"./types-uVOXgvMT.js";import{J as x,K as S,W as C,Y as w,a as T,d as E,i as te,n as ne}from"./display-exQDWum3.js";import{o as D,r as O}from"./dist-BIjlN-xB.js";import{t as k}from"./dist-B9G83DXv.js";import{i as re,n as A}from"./dist-B-T7lg0a.js";import{t as j}from"./dist-OUPTEP6q.js";import{t as M}from"./dist-BlzVGRhh.js";import{a as N,i as P,n as F,o as I,r as L,s as ie,t as ae}from"./es2015-CnKXYKpf.js";import{a as R,i as oe,n as se,r as z,t as B}from"./dist-B3e4B9pg.js";import{t as V}from"./badge-DCIT3Irj.js";import{t as H}from"./button-CzdTkRIf.js";import{t as ce}from"./copy-button-B3o1jpLu.js";import{t as U}from"./agent-type-icon-CeEoMouY.js";import{Dt as W,Et as le,J as ue,Ot as de,Y as fe,Z as pe,bt as me,ct as G,et as he,kt as ge,nt as _e,q as ve,rt as ye,tt as be}from"./index-4g3PbGVN.js";import{r as xe,t as Se}from"./card-C3BGuVC6.js";import{i as Ce,n as we,r as Te,t as Ee}from"./branch-state-badge-CyC5Qrhc.js";var K=e(p(),1),q=m(),J=`Popover`,[De,Oe]=re(J,[R]),Y=R(),[ke,X]=De(J),Ae=e=>{let{__scopePopover:t,children:n,open:r,defaultOpen:i,onOpenChange:a,modal:o=!1}=e,s=Y(t),c=K.useRef(null),[l,u]=K.useState(!1),[d,f]=j({prop:r,defaultProp:i??!1,onChange:a,caller:J});return(0,q.jsx)(oe,{...s,children:(0,q.jsx)(ke,{scope:t,contentId:ie(),triggerRef:c,open:d,onOpenChange:f,onOpenToggle:K.useCallback(()=>f(e=>!e),[f]),hasCustomAnchor:l,onCustomAnchorAdd:K.useCallback(()=>u(!0),[]),onCustomAnchorRemove:K.useCallback(()=>u(!1),[]),modal:o,children:n})})};Ae.displayName=J;var je=`PopoverAnchor`,Me=K.forwardRef((e,t)=>{let{__scopePopover:n,...r}=e,i=X(je,n),a=Y(n),{onCustomAnchorAdd:o,onCustomAnchorRemove:s}=i;return K.useEffect(()=>(o(),()=>s()),[o,s]),(0,q.jsx)(B,{...a,...r,ref:t})});Me.displayName=je;var Ne=`PopoverTrigger`,Z=K.forwardRef((e,t)=>{let{__scopePopover:n,...r}=e,i=X(Ne,n),a=Y(n),o=D(t,i.triggerRef),s=(0,q.jsx)(k.button,{type:`button`,"aria-haspopup":`dialog`,"aria-expanded":i.open,"aria-controls":i.contentId,"data-state":Ke(i.open),...r,ref:o,onClick:A(e.onClick,i.onOpenToggle)});return i.hasCustomAnchor?s:(0,q.jsx)(B,{asChild:!0,...a,children:s})});Z.displayName=Ne;var Q=`PopoverPortal`,[Pe,Fe]=De(Q,{forceMount:void 0}),Ie=e=>{let{__scopePopover:t,forceMount:n,children:r,container:i}=e,a=X(Q,t);return(0,q.jsx)(Pe,{scope:t,forceMount:n,children:(0,q.jsx)(M,{present:n||a.open,children:(0,q.jsx)(P,{asChild:!0,container:i,children:r})})})};Ie.displayName=Q;var $=`PopoverContent`,Le=K.forwardRef((e,t)=>{let n=Fe($,e.__scopePopover),{forceMount:r=n.forceMount,...i}=e,a=X($,e.__scopePopover);return(0,q.jsx)(M,{present:r||a.open,children:a.modal?(0,q.jsx)(ze,{...i,ref:t}):(0,q.jsx)(Be,{...i,ref:t})})});Le.displayName=$;var Re=O(`PopoverContent.RemoveScroll`),ze=K.forwardRef((e,t)=>{let n=X($,e.__scopePopover),r=K.useRef(null),i=D(t,r),a=K.useRef(!1);return K.useEffect(()=>{let e=r.current;if(e)return ae(e)},[]),(0,q.jsx)(F,{as:Re,allowPinchZoom:!0,children:(0,q.jsx)(Ve,{...e,ref:i,trapFocus:n.open,disableOutsidePointerEvents:!0,onCloseAutoFocus:A(e.onCloseAutoFocus,e=>{e.preventDefault(),a.current||n.triggerRef.current?.focus()}),onPointerDownOutside:A(e.onPointerDownOutside,e=>{let t=e.detail.originalEvent,n=t.button===0&&t.ctrlKey===!0;a.current=t.button===2||n},{checkForDefaultPrevented:!1}),onFocusOutside:A(e.onFocusOutside,e=>e.preventDefault(),{checkForDefaultPrevented:!1})})})}),Be=K.forwardRef((e,t)=>{let n=X($,e.__scopePopover),r=K.useRef(!1),i=K.useRef(!1);return(0,q.jsx)(Ve,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:t=>{e.onCloseAutoFocus?.(t),t.defaultPrevented||(r.current||n.triggerRef.current?.focus(),t.preventDefault()),r.current=!1,i.current=!1},onInteractOutside:t=>{e.onInteractOutside?.(t),t.defaultPrevented||(r.current=!0,t.detail.originalEvent.type===`pointerdown`&&(i.current=!0));let a=t.target;n.triggerRef.current?.contains(a)&&t.preventDefault(),t.detail.originalEvent.type===`focusin`&&i.current&&t.preventDefault()}})}),Ve=K.forwardRef((e,t)=>{let{__scopePopover:n,trapFocus:r,onOpenAutoFocus:i,onCloseAutoFocus:a,disableOutsidePointerEvents:o,onEscapeKeyDown:s,onPointerDownOutside:c,onFocusOutside:l,onInteractOutside:u,...d}=e,f=X($,n),p=Y(n);return L(),(0,q.jsx)(N,{asChild:!0,loop:!0,trapped:r,onMountAutoFocus:i,onUnmountAutoFocus:a,children:(0,q.jsx)(I,{asChild:!0,disableOutsidePointerEvents:o,onInteractOutside:u,onEscapeKeyDown:s,onPointerDownOutside:c,onFocusOutside:l,onDismiss:()=>f.onOpenChange(!1),children:(0,q.jsx)(z,{"data-state":Ke(f.open),role:`dialog`,id:f.contentId,...p,...d,ref:t,style:{...d.style,"--radix-popover-content-transform-origin":`var(--radix-popper-transform-origin)`,"--radix-popover-content-available-width":`var(--radix-popper-available-width)`,"--radix-popover-content-available-height":`var(--radix-popper-available-height)`,"--radix-popover-trigger-width":`var(--radix-popper-anchor-width)`,"--radix-popover-trigger-height":`var(--radix-popper-anchor-height)`}})})})}),He=`PopoverClose`,Ue=K.forwardRef((e,t)=>{let{__scopePopover:n,...r}=e,i=X(He,n);return(0,q.jsx)(k.button,{type:`button`,...r,ref:t,onClick:A(e.onClick,()=>i.onOpenChange(!1))})});Ue.displayName=He;var We=`PopoverArrow`,Ge=K.forwardRef((e,t)=>{let{__scopePopover:n,...r}=e,i=Y(n);return(0,q.jsx)(se,{...i,...r,ref:t})});Ge.displayName=We;function Ke(e){return e?`open`:`closed`}var qe=Ae,Je=Z,Ye=Ie,Xe=Le;function Ze({...e}){return(0,q.jsx)(qe,{"data-slot":`popover`,...e})}function Qe({...e}){return(0,q.jsx)(Je,{"data-slot":`popover-trigger`,...e})}function $e({className:e,align:t=`center`,sideOffset:n=4,...r}){return(0,q.jsx)(Ye,{children:(0,q.jsx)(Xe,{"data-slot":`popover-content`,align:t,sideOffset:n,className:u(`z-50 flex w-72 origin-(--radix-popover-content-transform-origin) flex-col gap-2.5 rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95`,e),...r})})}function et({className:e,...t}){return(0,q.jsx)(`div`,{"data-slot":`popover-header`,className:u(`flex flex-col gap-0.5 text-sm`,e),...t})}function tt({className:e,...t}){return(0,q.jsx)(`div`,{"data-slot":`popover-title`,className:u(`font-medium`,e),...t})}function nt({agent:e,childAgents:r=[],parentAgent:a}){let o=ee();v(e=>e.set);let l=v(e=>e.switchView),u=v(e=>e.openInboxThread),p=v(e=>e.openAgentDetail),m=v(e=>e.openPairInvite),y=v(e=>e.openRename),b=v(e=>e.openConfirm),D=v(e=>e.doAgentAction),O=v(e=>e.doDeleteAgent),k=v(e=>e.openFilesForAgent),re=v(e=>e.workspaceAction),A=v(e=>e.openWorkspaceFocus),{terminalOpen:j,terminalTarget:M,terminalOpening:N,openTerminal:P,closeTerminal:F}=Ce(e.id),I=le(),L=te(o,e,me(e),I[e.id]||null),ie=T(e,`shutdown`),ae=T(e,`compact`),R=T(e,`clearContext`),oe=!!(e.providerCapabilities?.terminal?.live?.read||e.providerCapabilities?.terminal?.attach?.create),se=!!e.providerCapabilities?.terminal?.live?.write,z=ne(e),B=a?E(a):e.spawnedBy,W=r.length>0;return(0,q.jsxs)(q.Fragment,{children:[(0,q.jsx)(Se,{className:`group hover:border-zinc-600 transition-colors cursor-pointer`,onClick:()=>p(e),children:(0,q.jsxs)(xe,{className:`p-4`,children:[(0,q.jsxs)(`div`,{className:`flex items-start gap-2.5 mb-2.5`,children:[(0,q.jsx)(G,{agent:e,now:o,className:`mt-0.5`}),(0,q.jsx)(U,{agent:e}),(0,q.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,q.jsxs)(`div`,{className:`flex items-center gap-1 min-w-0`,children:[(0,q.jsx)(we,{profile:Te(e),className:`text-sm shrink-0`}),(0,q.jsx)(`span`,{className:`text-sm font-medium truncate`,children:E(e)})]}),(0,q.jsx)(`div`,{className:`text-xs mt-0.5 ${x(L.tone)}`,children:L.label}),typeof e.meta?.cwd==`string`&&e.meta.cwd&&(0,q.jsx)(`button`,{type:`button`,className:`mt-0.5 block max-w-full truncate font-mono text-[10px] text-muted-foreground hover:text-foreground`,title:`Open ${e.meta.cwd} in Files`,onClick:t=>{t.stopPropagation(),k(e)},children:C(e.meta.cwd)}),(0,q.jsx)(pe,{agent:e,className:`mt-1`})]}),e.branchState&&e.branchWorkspaceId&&(0,q.jsx)(Ee,{state:e.branchState,className:`mt-0.5 shrink-0`,onClick:t=>{t.stopPropagation(),A(e.branchWorkspaceId)}})]}),L.badges.length>0&&(0,q.jsx)(`div`,{className:`flex flex-wrap gap-1 mb-2`,children:L.badges.map(e=>(0,q.jsx)(`span`,{className:`text-[10px] px-1.5 py-0.5 rounded-full font-medium ${e.className}`,children:e.label},e.label))}),(W||e.spawnedBy&&B)&&(0,q.jsxs)(`div`,{className:`mb-2 flex flex-wrap gap-1.5`,onClick:e=>e.stopPropagation(),children:[W&&(0,q.jsxs)(Ze,{children:[(0,q.jsx)(Qe,{asChild:!0,children:(0,q.jsxs)(`button`,{type:`button`,className:`inline-flex h-6 max-w-full items-center gap-1.5 rounded-full border border-sky-500/30 bg-sky-500/10 px-2 text-[10px] font-medium text-sky-300 transition-colors hover:bg-sky-500/15`,title:`${r.length} live child ${r.length===1?`agent`:`agents`}`,children:[(0,q.jsx)(g,{className:`h-3 w-3`}),(0,q.jsx)(`span`,{children:r.length}),(0,q.jsx)(`span`,{className:`flex -space-x-1`,children:r.slice(0,4).map(e=>(0,q.jsx)(G,{agent:e,now:o,className:`h-2 w-2 ring-1 ring-background`},e.id))})]})}),(0,q.jsxs)($e,{align:`start`,className:`w-80`,onClick:e=>e.stopPropagation(),children:[(0,q.jsx)(et,{children:(0,q.jsx)(tt,{children:`Team`})}),(0,q.jsx)(`div`,{className:`space-y-1.5`,children:r.map(e=>(0,q.jsxs)(`div`,{className:`rounded-md border border-border bg-card/60 p-2`,children:[(0,q.jsxs)(`div`,{className:`flex items-start gap-2`,children:[(0,q.jsx)(G,{agent:e,now:o,className:`mt-1`}),(0,q.jsx)(U,{agent:e}),(0,q.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,q.jsx)(`div`,{className:`truncate text-xs font-medium`,children:E(e)}),(0,q.jsx)(`div`,{className:`truncate text-[10px] text-muted-foreground`,children:e.providerCapabilities?.model?.alias||e.providerCapabilities?.model?.id||e.providerCapabilities?.model?.provider||e.status})]}),(0,q.jsx)(V,{variant:`outline`,className:`h-4 px-1.5 text-[9px]`,children:e.status})]}),(0,q.jsxs)(`div`,{className:`mt-2 flex gap-1`,children:[(0,q.jsx)(H,{size:`xs`,variant:`outline`,onClick:()=>p(e),children:`Open`}),(0,q.jsxs)(H,{size:`xs`,variant:`ghost`,onClick:()=>{u(e.id),l(`chat`)},children:[(0,q.jsx)(h,{className:`h-3 w-3`}),`Chat`]})]})]},e.id))})]})]}),e.spawnedBy&&B&&(0,q.jsx)(V,{variant:`outline`,className:`max-w-full text-[10px] text-muted-foreground`,children:(0,q.jsxs)(`span`,{className:`truncate`,children:[`spawned by `,B]})})]}),(e.tags.length>0||w(e.capabilities).length>0)&&(0,q.jsxs)(`div`,{className:`flex flex-wrap gap-1 mb-2`,children:[e.tags.slice(0,3).map(e=>(0,q.jsx)(V,{variant:`outline`,className:`text-[9px] px-1 py-0 h-4`,children:e},e)),w(e.capabilities).slice(0,2).map(e=>(0,q.jsx)(V,{variant:`secondary`,className:`text-[9px] px-1 py-0 h-4`,children:e},e))]}),(0,q.jsxs)(`div`,{className:`space-y-2 mb-2`,children:[(0,q.jsx)(_e,{agent:e}),(0,q.jsx)(be,{agent:e,compact:!0}),(0,q.jsx)(ye,{agent:e})]}),(0,q.jsx)(`div`,{className:`text-xs text-muted-foreground`,children:e.id!==`user`&&e.id!==`system`&&S(o,e.lastSeen)}),(0,q.jsxs)(`div`,{className:`flex gap-1 mt-2.5 opacity-100 sm:opacity-0 sm:group-hover:opacity-100 transition-opacity`,onClick:e=>e.stopPropagation(),children:[(0,q.jsx)(ce,{value:e.id,label:`Copy agent ID`,size:`icon`,className:`h-7 w-7`,iconClassName:`w-3 h-3`}),(0,q.jsx)(H,{size:`icon`,variant:`ghost`,className:`h-7 w-7`,title:`Chat`,onClick:()=>{u(e.id),l(`chat`)},children:(0,q.jsx)(h,{className:`w-3 h-3`})}),(0,q.jsx)(H,{size:`icon`,variant:`ghost`,className:`h-7 w-7`,title:`Pair`,onClick:()=>m(e.id),children:(0,q.jsx)(f,{className:`w-3 h-3`})}),(0,q.jsx)(H,{size:`icon`,variant:`ghost`,className:`h-7 w-7`,title:`Rename`,onClick:()=>y(e),children:(0,q.jsx)(n,{className:`w-3 h-3`})}),oe&&(0,q.jsx)(H,{size:`icon`,variant:`ghost`,className:`h-7 w-7`,title:`Terminal`,disabled:N,onClick:()=>void P(),children:(0,q.jsx)(c,{className:`w-3 h-3`})}),(e.branchState===`changes`||e.branchState===`idle`)&&e.branchWorkspaceId&&(0,q.jsx)(H,{size:`icon`,variant:`ghost`,className:`h-7 w-7 text-amber-400 hover:text-amber-300`,title:`Mark workspace ready — hand off to the auto-merge`,onClick:()=>b(`Mark Workspace Ready`,`Mark ${E(e)}'s branch ready to land? The relay auto-merge will rebase and land it.`,()=>re(e.branchWorkspaceId,`ready`)),children:(0,q.jsx)(i,{className:`w-3 h-3`})}),ae&&(0,q.jsx)(H,{size:`icon`,variant:`ghost`,className:`h-7 w-7`,title:`Compact`,onClick:()=>b(`Compact Agent`,`Compact context for ${E(e)}?`,()=>D(e,`compact`,`agents-grid`)),children:(0,q.jsx)(t,{className:`w-3 h-3`})}),R&&(0,q.jsx)(H,{size:`icon`,variant:`ghost`,className:`h-7 w-7`,title:`Clear Context`,onClick:()=>b(`Clear Context`,`Clear context for ${E(e)}?`,()=>D(e,`clearContext`,`agents-grid`)),children:(0,q.jsx)(s,{className:`w-3 h-3`})}),ie&&(0,q.jsx)(H,{size:`icon`,variant:`ghost`,className:`h-7 w-7 text-red-400 hover:text-red-300`,title:`Shutdown`,onClick:()=>b(`Shutdown Agent`,`Shutdown ${E(e)}?`,()=>D(e,`shutdown`,`agents-grid`)),children:(0,q.jsx)(_,{className:`w-3 h-3`})}),z&&(0,q.jsx)(H,{size:`icon`,variant:`ghost`,className:`h-7 w-7 text-red-400 hover:text-red-300`,title:`Forget stale agent`,onClick:()=>b(`Forget Agent`,`Forget ${E(e)}?`,()=>O(e.id)),children:(0,q.jsx)(d,{className:`w-3 h-3`})})]})]})}),M&&(0,q.jsx)(he,{open:j,onOpenChange:F,orchestratorId:M.orchestratorId,session:M.session,interactive:M.interactive||se})]})}function rt(){let e=v(e=>e.agentPresetFilter),t=v(e=>e.agentStatusFilter),n=v(e=>e.agentTagFilter),i=v(e=>e.agentHostFilter),s=v(e=>e.agentSort),c=v(e=>e.agentSortDir),u=v(e=>e.showOffline),d=v(e=>e.set),f=v(e=>e.openAgentSpawn),p=v(e=>e.agentsById),m=ee(),h=W(),g=ge(),_=de(),x=ve(h,m),S=fe(h,m),C=e||t||n||i;function w(){d({agentPresetFilter:``,agentStatusFilter:``,agentTagFilter:``,agentHostFilter:``})}function T(){d({agentSortDir:c===`asc`?`desc`:`asc`})}return(0,q.jsxs)(`div`,{className:`space-y-4`,children:[(0,q.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,q.jsxs)(`select`,{value:e,onChange:e=>d({agentPresetFilter:e.target.value}),className:`h-8 rounded-md border border-border bg-card text-sm px-2 text-foreground`,children:[(0,q.jsx)(`option`,{value:``,children:`All agents`}),(0,q.jsx)(`option`,{value:`active`,children:`Active`}),b().map(e=>(0,q.jsx)(`option`,{value:e,children:y(e)},e)),(0,q.jsx)(`option`,{value:`paired`,children:`Paired`}),(0,q.jsx)(`option`,{value:`unpaired`,children:`Unpaired`}),(0,q.jsx)(`option`,{value:`offline_stale`,children:`Offline / Stale`})]}),(0,q.jsxs)(`select`,{value:t,onChange:e=>d({agentStatusFilter:e.target.value}),className:`h-8 rounded-md border border-border bg-card text-sm px-2 text-foreground`,children:[(0,q.jsx)(`option`,{value:``,children:`Any status`}),(0,q.jsx)(`option`,{value:`online`,children:`Online`}),(0,q.jsx)(`option`,{value:`idle`,children:`Idle`}),(0,q.jsx)(`option`,{value:`busy`,children:`Busy`}),(0,q.jsx)(`option`,{value:`offline`,children:`Offline`}),(0,q.jsx)(`option`,{value:`starting`,children:`Starting`})]}),g.length>0&&(0,q.jsxs)(`select`,{value:n,onChange:e=>d({agentTagFilter:e.target.value}),className:`h-8 rounded-md border border-border bg-card text-sm px-2 text-foreground`,children:[(0,q.jsx)(`option`,{value:``,children:`Any tag`}),g.map(e=>(0,q.jsx)(`option`,{value:e,children:e},e))]}),_.length>1&&(0,q.jsxs)(`select`,{value:i,onChange:e=>d({agentHostFilter:e.target.value}),className:`h-8 rounded-md border border-border bg-card text-sm px-2 text-foreground`,children:[(0,q.jsx)(`option`,{value:``,children:`Any host`}),_.map(e=>(0,q.jsx)(`option`,{value:e,children:e},e))]}),(0,q.jsxs)(`select`,{value:s,onChange:e=>d({agentSort:e.target.value}),className:`h-8 rounded-md border border-border bg-card text-sm px-2 text-foreground`,children:[(0,q.jsx)(`option`,{value:`status`,children:`Sort: Status`}),(0,q.jsx)(`option`,{value:`name`,children:`Sort: Name`}),(0,q.jsx)(`option`,{value:`lastSeen`,children:`Sort: Last seen`}),(0,q.jsx)(`option`,{value:`created`,children:`Sort: Created`})]}),(0,q.jsx)(H,{size:`icon`,variant:`outline`,className:`h-8 w-8`,title:`Toggle sort direction`,onClick:T,children:c===`asc`?(0,q.jsx)(a,{className:`w-3.5 h-3.5`}):(0,q.jsx)(o,{className:`w-3.5 h-3.5`})}),(0,q.jsxs)(`label`,{className:`flex items-center gap-1.5 text-sm text-muted-foreground cursor-pointer select-none`,children:[(0,q.jsx)(`input`,{type:`checkbox`,checked:u,onChange:e=>d({showOffline:e.target.checked}),className:`rounded`}),`Show offline`]}),C&&(0,q.jsxs)(H,{size:`sm`,variant:`ghost`,className:`h-8 text-muted-foreground hover:text-foreground gap-1`,onClick:w,children:[(0,q.jsx)(l,{className:`w-3 h-3`}),`Clear filters`]}),(0,q.jsx)(`div`,{className:`ml-auto`,children:(0,q.jsxs)(H,{size:`sm`,onClick:f,className:`gap-1`,children:[(0,q.jsx)(r,{className:`w-3.5 h-3.5`}),`Spawn`]})})]}),S.length===0?(0,q.jsx)(`div`,{className:`text-center py-16 text-muted-foreground text-sm`,children:`No agents match the current filters.`}):(0,q.jsx)(`div`,{className:`grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-3`,children:S.map(e=>(0,q.jsx)(nt,{agent:e,parentAgent:e.spawnedBy?p[e.spawnedBy]??null:null,childAgents:ue(x.get(e.id)??[])},e.id))})]})}export{rt as AgentsView};
2
- //# sourceMappingURL=agents-8rQuJqii.js.map