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.
- package/docs/openapi.json +1 -1
- package/package.json +3 -3
- package/public/assets/{MessageBubble-BVff6PSi.js → MessageBubble-So86mU9-.js} +2 -2
- package/public/assets/{MessageBubble-BVff6PSi.js.map → MessageBubble-So86mU9-.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-BjrVU5Tg.js → PlanGraphPanel-DBZYAsww.js} +2 -2
- package/public/assets/{PlanGraphPanel-BjrVU5Tg.js.map → PlanGraphPanel-DBZYAsww.js.map} +1 -1
- package/public/assets/{activity-Bk9LEUr4.js → activity-CZNwmXg1.js} +2 -2
- package/public/assets/{activity-Bk9LEUr4.js.map → activity-CZNwmXg1.js.map} +1 -1
- package/public/assets/{agent-profiles-DSVDoQ_N.js → agent-profiles-BhadoGwi.js} +2 -2
- package/public/assets/{agent-profiles-DSVDoQ_N.js.map → agent-profiles-BhadoGwi.js.map} +1 -1
- package/public/assets/agents-DWn1xx9x.js +2 -0
- package/public/assets/{agents-8rQuJqii.js.map → agents-DWn1xx9x.js.map} +1 -1
- package/public/assets/{automation-CMxQAf3l.js → automation-BExrtKkb.js} +2 -2
- package/public/assets/{automation-CMxQAf3l.js.map → automation-BExrtKkb.js.map} +1 -1
- package/public/assets/chat-C7OhKtr6.js +2 -0
- package/public/assets/{chat-jlXywb9G.js.map → chat-C7OhKtr6.js.map} +1 -1
- package/public/assets/{formatted-body-CzehtPtx.js → formatted-body-BCQT2qSb.js} +3 -3
- package/public/assets/{formatted-body-CzehtPtx.js.map → formatted-body-BCQT2qSb.js.map} +1 -1
- package/public/assets/{formatted-body-impl-D3Bnt0Fj.js → formatted-body-impl-Bk7wnESl.js} +2 -2
- package/public/assets/{formatted-body-impl-D3Bnt0Fj.js.map → formatted-body-impl-Bk7wnESl.js.map} +1 -1
- package/public/assets/index-CRraCeo_.js +25 -0
- package/public/assets/index-CRraCeo_.js.map +1 -0
- package/public/assets/{insights-3H6KttBi.js → insights-DEgES6jz.js} +2 -2
- package/public/assets/{insights-3H6KttBi.js.map → insights-DEgES6jz.js.map} +1 -1
- package/public/assets/{maintenance-BANYcBcn.js → maintenance-BgVB6_gO.js} +2 -2
- package/public/assets/{maintenance-BANYcBcn.js.map → maintenance-BgVB6_gO.js.map} +1 -1
- package/public/assets/{managed-agents-DCFMWKqt.js → managed-agents-8_NKSkhI.js} +3 -3
- package/public/assets/{managed-agents-DCFMWKqt.js.map → managed-agents-8_NKSkhI.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-CfaR-GRX.js → markdown-preview-impl-yxQkrd2M.js} +2 -2
- package/public/assets/{markdown-preview-impl-CfaR-GRX.js.map → markdown-preview-impl-yxQkrd2M.js.map} +1 -1
- package/public/assets/{memory-BmkUEyd5.js → memory-CJD8eekg.js} +2 -2
- package/public/assets/{memory-BmkUEyd5.js.map → memory-CJD8eekg.js.map} +1 -1
- package/public/assets/{messages-BQ_fVnSA.js → messages-DLHMZbHZ.js} +2 -2
- package/public/assets/{messages-BQ_fVnSA.js.map → messages-DLHMZbHZ.js.map} +1 -1
- package/public/assets/{orchestrators-DC7y9vQD.js → orchestrators-DEOep7FX.js} +2 -2
- package/public/assets/{orchestrators-DC7y9vQD.js.map → orchestrators-DEOep7FX.js.map} +1 -1
- package/public/assets/{overview-6lzywFHq.js → overview-BgpfO6mt.js} +2 -2
- package/public/assets/{overview-6lzywFHq.js.map → overview-BgpfO6mt.js.map} +1 -1
- package/public/assets/{pairs-CRrRIaHI.js → pairs-8NVKHh96.js} +2 -2
- package/public/assets/{pairs-CRrRIaHI.js.map → pairs-8NVKHh96.js.map} +1 -1
- package/public/assets/{prompt-settings-DvlnMq8H.js → prompt-settings-fqyLwUa-.js} +2 -2
- package/public/assets/{prompt-settings-DvlnMq8H.js.map → prompt-settings-fqyLwUa-.js.map} +1 -1
- package/public/assets/{registry-3TWg4zbE.js → registry-BKbLL4mO.js} +2 -2
- package/public/assets/{registry-3TWg4zbE.js.map → registry-BKbLL4mO.js.map} +1 -1
- package/public/assets/{security-A2VYKHeI.js → security-BlTFTbq0.js} +2 -2
- package/public/assets/{security-A2VYKHeI.js.map → security-BlTFTbq0.js.map} +1 -1
- package/public/assets/{select-_gK5Ojjc.js → select-Zd_qh0uU.js} +2 -2
- package/public/assets/{select-_gK5Ojjc.js.map → select-Zd_qh0uU.js.map} +1 -1
- package/public/assets/{settings-BZ26ewcu.js → settings-nHuSVRkL.js} +2 -2
- package/public/assets/{settings-BZ26ewcu.js.map → settings-nHuSVRkL.js.map} +1 -1
- package/public/assets/{tasks-DlokSl_P.js → tasks-C3JhQ8vB.js} +2 -2
- package/public/assets/{tasks-DlokSl_P.js.map → tasks-C3JhQ8vB.js.map} +1 -1
- package/public/assets/{teams-t7M1HMgS.js → teams-C9E9Yt39.js} +2 -2
- package/public/assets/{teams-t7M1HMgS.js.map → teams-C9E9Yt39.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-DxgrO-jH.js → terminal-viewer-impl-FP0JlsvS.js} +22 -22
- package/public/assets/{terminal-viewer-impl-DxgrO-jH.js.map → terminal-viewer-impl-FP0JlsvS.js.map} +1 -1
- package/public/assets/types-uVOXgvMT.js.map +1 -1
- package/public/assets/{work-queue-V8LR-3Gi.js → work-queue-Ema7Hy3s.js} +2 -2
- package/public/assets/{work-queue-V8LR-3Gi.js.map → work-queue-Ema7Hy3s.js.map} +1 -1
- package/public/index.html +2 -2
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/src/commands-db.ts +14 -0
- package/src/db/delivery.ts +85 -3
- package/src/db/stats.ts +10 -8
- package/src/db/task-entity.ts +7 -8
- package/src/lifecycle-manager.ts +11 -12
- package/src/maintenance/jobs.ts +3 -1
- package/src/maintenance/workspaces.ts +28 -47
- package/src/managed-policy-escalation.ts +47 -0
- package/src/mcp-task-tools.ts +4 -5
- package/src/routes/commands.ts +8 -245
- package/src/routes/tasks.ts +3 -5
- package/src/services/issue-lifecycle.ts +23 -8
- package/src/services/task-entity.ts +54 -2
- package/src/workspace-auto-merge.ts +94 -34
- package/src/workspace-command-settle.ts +227 -0
- package/src/workspace-escalation.ts +45 -0
- package/src/workspace-merge-progress.ts +16 -0
- package/src/workspace-orphans.ts +91 -3
- package/public/assets/agents-8rQuJqii.js +0 -2
- package/public/assets/chat-jlXywb9G.js +0 -2
- package/public/assets/index-4g3PbGVN.js +0 -25
- 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
|
+
};
|
package/src/workspace-orphans.ts
CHANGED
|
@@ -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
|
-
?
|
|
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
|