agent-relay-server 0.127.3 → 0.127.4
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-DnXnkt4a.js → MessageBubble-D2Wzxied.js} +2 -2
- package/public/assets/{MessageBubble-DnXnkt4a.js.map → MessageBubble-D2Wzxied.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-D6CPEpQD.js → PlanGraphPanel-DH_xoWZi.js} +2 -2
- package/public/assets/{PlanGraphPanel-D6CPEpQD.js.map → PlanGraphPanel-DH_xoWZi.js.map} +1 -1
- package/public/assets/{activity-ZPwHcPLK.js → activity-B-ozOwNq.js} +2 -2
- package/public/assets/{activity-ZPwHcPLK.js.map → activity-B-ozOwNq.js.map} +1 -1
- package/public/assets/{agent-profiles-CT0E9LV1.js → agent-profiles-DZgjO25A.js} +2 -2
- package/public/assets/{agent-profiles-CT0E9LV1.js.map → agent-profiles-DZgjO25A.js.map} +1 -1
- package/public/assets/{agent-type-icon-BmBR-9CH.js → agent-type-icon-B9cLaOoz.js} +2 -2
- package/public/assets/{agent-type-icon-BmBR-9CH.js.map → agent-type-icon-B9cLaOoz.js.map} +1 -1
- package/public/assets/agents-mXBLSY6O.js +2 -0
- package/public/assets/{agents-Dt5TncjC.js.map → agents-mXBLSY6O.js.map} +1 -1
- package/public/assets/{analytics-BZtRycyL.js → analytics-5Ize_UL5.js} +3 -3
- package/public/assets/{analytics-BZtRycyL.js.map → analytics-5Ize_UL5.js.map} +1 -1
- package/public/assets/{automation-yzUJbvT2.js → automation-CidMvW2e.js} +2 -2
- package/public/assets/{automation-yzUJbvT2.js.map → automation-CidMvW2e.js.map} +1 -1
- package/public/assets/{badge-BCvp7LK3.js → badge-BkNNvFNa.js} +2 -2
- package/public/assets/{badge-BCvp7LK3.js.map → badge-BkNNvFNa.js.map} +1 -1
- package/public/assets/{branch-state-badge-Cscjqf-E.js → branch-state-badge-DnS40KCb.js} +2 -2
- package/public/assets/{branch-state-badge-Cscjqf-E.js.map → branch-state-badge-DnS40KCb.js.map} +1 -1
- package/public/assets/{button-DyHjsHY3.js → button-DUaC2wSQ.js} +2 -2
- package/public/assets/{button-DyHjsHY3.js.map → button-DUaC2wSQ.js.map} +1 -1
- package/public/assets/{card-BoqYxpVW.js → card-r-yAV4cH.js} +2 -2
- package/public/assets/{card-BoqYxpVW.js.map → card-r-yAV4cH.js.map} +1 -1
- package/public/assets/{channels-DAtOnA6o.js → channels-MPO2FOBv.js} +2 -2
- package/public/assets/{channels-DAtOnA6o.js.map → channels-MPO2FOBv.js.map} +1 -1
- package/public/assets/chat-Ca4ERso5.js +2 -0
- package/public/assets/chat-Ca4ERso5.js.map +1 -0
- package/public/assets/{connectors-B2grC-hl.js → connectors-BbYdtRkW.js} +2 -2
- package/public/assets/{connectors-B2grC-hl.js.map → connectors-BbYdtRkW.js.map} +1 -1
- package/public/assets/copy-button-u463GPZR.js +2 -0
- package/public/assets/{copy-button-C67FUG3E.js.map → copy-button-u463GPZR.js.map} +1 -1
- package/public/assets/{display-aTnwsqQh.js → display-BNI-7rdq.js} +2 -2
- package/public/assets/{display-aTnwsqQh.js.map → display-BNI-7rdq.js.map} +1 -1
- package/public/assets/{dist-D5w04z0l.js → dist-5e_is3Xr.js} +2 -2
- package/public/assets/{dist-D5w04z0l.js.map → dist-5e_is3Xr.js.map} +1 -1
- package/public/assets/{dist-BD4Xc13v.js → dist-ActI2qb5.js} +2 -2
- package/public/assets/{dist-BD4Xc13v.js.map → dist-ActI2qb5.js.map} +1 -1
- package/public/assets/dist-B-FQ0-X3.js +2 -0
- package/public/assets/{dist-G8YBD-wp.js.map → dist-B-FQ0-X3.js.map} +1 -1
- package/public/assets/{dist-f6MW74eX.js → dist-BduURKro.js} +2 -2
- package/public/assets/{dist-f6MW74eX.js.map → dist-BduURKro.js.map} +1 -1
- package/public/assets/{dist-BFDfbfNC.js → dist-Bp0-OBcd.js} +2 -2
- package/public/assets/{dist-BFDfbfNC.js.map → dist-Bp0-OBcd.js.map} +1 -1
- package/public/assets/{dist-C_VOBSaK.js → dist-CJ7e7uqU.js} +2 -2
- package/public/assets/{dist-C_VOBSaK.js.map → dist-CJ7e7uqU.js.map} +1 -1
- package/public/assets/{dist-BNB3fTO-.js → dist-CYzOsD_n.js} +2 -2
- package/public/assets/{dist-BNB3fTO-.js.map → dist-CYzOsD_n.js.map} +1 -1
- package/public/assets/{dist-CQMQ5iku.js → dist-CpcQJrKV.js} +2 -2
- package/public/assets/{dist-CQMQ5iku.js.map → dist-CpcQJrKV.js.map} +1 -1
- package/public/assets/dist-DFgmDjLv.js +2 -0
- package/public/assets/{dist-CHKra2JV.js.map → dist-DFgmDjLv.js.map} +1 -1
- package/public/assets/{dist-DG75bvPp.js → dist-DrpSpMKL.js} +2 -2
- package/public/assets/{dist-DG75bvPp.js.map → dist-DrpSpMKL.js.map} +1 -1
- package/public/assets/dist-DzWoiN2z.js +2 -0
- package/public/assets/{dist-BJKxqIQd.js.map → dist-DzWoiN2z.js.map} +1 -1
- package/public/assets/{es2015-a6fysEVz.js → es2015-DNuudVtQ.js} +2 -2
- package/public/assets/{es2015-a6fysEVz.js.map → es2015-DNuudVtQ.js.map} +1 -1
- package/public/assets/formatted-body-BEI1zP7i.js +3 -0
- package/public/assets/{formatted-body-D5-1aiyB.js.map → formatted-body-BEI1zP7i.js.map} +1 -1
- package/public/assets/{formatted-body-impl-Do5NbJUA.js → formatted-body-impl-CNQ5sqwK.js} +2 -2
- package/public/assets/{formatted-body-impl-Do5NbJUA.js.map → formatted-body-impl-CNQ5sqwK.js.map} +1 -1
- package/public/assets/index-D2Mpzpft.js +26 -0
- package/public/assets/{index-BdNafiPz.js.map → index-D2Mpzpft.js.map} +1 -1
- package/public/assets/{input-Rc4oWv4q.js → input-BvNUEkDd.js} +2 -2
- package/public/assets/{input-Rc4oWv4q.js.map → input-BvNUEkDd.js.map} +1 -1
- package/public/assets/{insights-i6AEZT_a.js → insights-5rkMFRgf.js} +2 -2
- package/public/assets/{insights-i6AEZT_a.js.map → insights-5rkMFRgf.js.map} +1 -1
- package/public/assets/{integrations-9c6IdRU4.js → integrations-BEaqjfP4.js} +2 -2
- package/public/assets/{integrations-9c6IdRU4.js.map → integrations-BEaqjfP4.js.map} +1 -1
- package/public/assets/{lib-BZ6WPg2t.js → lib-BvrbNA2I.js} +2 -2
- package/public/assets/{lib-BZ6WPg2t.js.map → lib-BvrbNA2I.js.map} +1 -1
- package/public/assets/{lucide-react-NXHPIMsX.js → lucide-react-DD_zIava.js} +2 -2
- package/public/assets/{lucide-react-NXHPIMsX.js.map → lucide-react-DD_zIava.js.map} +1 -1
- package/public/assets/maintenance-CEiURaYZ.js +2 -0
- package/public/assets/{maintenance-CJ6wikpg.js.map → maintenance-CEiURaYZ.js.map} +1 -1
- package/public/assets/{managed-agents-JZn0xNJR.js → managed-agents-CetHyuQW.js} +4 -4
- package/public/assets/{managed-agents-JZn0xNJR.js.map → managed-agents-CetHyuQW.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-BsvAkpR4.js → markdown-preview-impl-ZwGYvObh.js} +2 -2
- package/public/assets/{markdown-preview-impl-BsvAkpR4.js.map → markdown-preview-impl-ZwGYvObh.js.map} +1 -1
- package/public/assets/memory-BTm8XzCg.js +2 -0
- package/public/assets/{memory-BE9IwCdC.js.map → memory-BTm8XzCg.js.map} +1 -1
- package/public/assets/{messages-vXgdc3H4.js → messages-fJd17pzv.js} +2 -2
- package/public/assets/{messages-vXgdc3H4.js.map → messages-fJd17pzv.js.map} +1 -1
- package/public/assets/{orchestrators-CribhLg-.js → orchestrators-C5XQ2UYy.js} +2 -2
- package/public/assets/{orchestrators-CribhLg-.js.map → orchestrators-C5XQ2UYy.js.map} +1 -1
- package/public/assets/overview-Bhk-tqKc.js +2 -0
- package/public/assets/{overview-q3O9K9VK.js.map → overview-Bhk-tqKc.js.map} +1 -1
- package/public/assets/{pairs-BNOnd0IE.js → pairs-CBrszSZh.js} +2 -2
- package/public/assets/{pairs-BNOnd0IE.js.map → pairs-CBrszSZh.js.map} +1 -1
- package/public/assets/{projects-iDkxAGBW.js → projects-DXy9JviE.js} +2 -2
- package/public/assets/{projects-iDkxAGBW.js.map → projects-DXy9JviE.js.map} +1 -1
- package/public/assets/prompt-settings-DEQhAt2X.js +2 -0
- package/public/assets/{prompt-settings-Bdz_xCgD.js.map → prompt-settings-DEQhAt2X.js.map} +1 -1
- package/public/assets/{react-dom-8yaD9iJG.js → react-dom-CADlISeR.js} +2 -2
- package/public/assets/{react-dom-8yaD9iJG.js.map → react-dom-CADlISeR.js.map} +1 -1
- package/public/assets/{registry-DPcyGypi.js → registry-DsZILce1.js} +4 -4
- package/public/assets/{registry-DPcyGypi.js.map → registry-DsZILce1.js.map} +1 -1
- package/public/assets/{security-Z3l3u-Pl.js → security-C-wVZm5k.js} +2 -2
- package/public/assets/{security-Z3l3u-Pl.js.map → security-C-wVZm5k.js.map} +1 -1
- package/public/assets/{select-BX_lW49D.js → select-DsEGBLJk.js} +2 -2
- package/public/assets/{select-BX_lW49D.js.map → select-DsEGBLJk.js.map} +1 -1
- package/public/assets/settings-D3_r5IGA.js +6 -0
- package/public/assets/{settings-Y381jZt7.js.map → settings-D3_r5IGA.js.map} +1 -1
- package/public/assets/{store-D-Frk_EC.js → store-DKTb9O0v.js} +5 -5
- package/public/assets/{store-D-Frk_EC.js.map → store-DKTb9O0v.js.map} +1 -1
- package/public/assets/{task-chip-CeujtXYT.js → task-chip-DwdauC08.js} +2 -2
- package/public/assets/{task-chip-CeujtXYT.js.map → task-chip-DwdauC08.js.map} +1 -1
- package/public/assets/{tasks-Di6d0_Ch.js → tasks-DnEzBku4.js} +2 -2
- package/public/assets/{tasks-Di6d0_Ch.js.map → tasks-DnEzBku4.js.map} +1 -1
- package/public/assets/{teams-DJv4l-_l.js → teams-DpAbZ3ye.js} +2 -2
- package/public/assets/{teams-DJv4l-_l.js.map → teams-DpAbZ3ye.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-3zBLpRel.js → terminal-viewer-impl-_IU5o8ya.js} +3 -3
- package/public/assets/{terminal-viewer-impl-3zBLpRel.js.map → terminal-viewer-impl-_IU5o8ya.js.map} +1 -1
- package/public/assets/types-BqeSkWrv.js.map +1 -1
- package/public/assets/{use-keyboard-viewport-cGEmrGE7.js → use-keyboard-viewport-wTh5CR4-.js} +2 -2
- package/public/assets/{use-keyboard-viewport-cGEmrGE7.js.map → use-keyboard-viewport-wTh5CR4-.js.map} +1 -1
- package/public/assets/user-avatar-CZ3XO8ml.js +2 -0
- package/public/assets/{user-avatar-qbo-B3dQ.js.map → user-avatar-CZ3XO8ml.js.map} +1 -1
- package/public/assets/{work-queue-D73-hP0c.js → work-queue-DKcLZWRl.js} +2 -2
- package/public/assets/{work-queue-D73-hP0c.js.map → work-queue-DKcLZWRl.js.map} +1 -1
- package/public/assets/workspaces-Cs2IZZOI.js +3 -0
- package/public/assets/{workspaces-CoDz-iZd.js.map → workspaces-Cs2IZZOI.js.map} +1 -1
- package/public/index.html +24 -24
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/runner/src/adapters/claude-transcript.ts +52 -3
- package/runner/src/control-server.ts +34 -17
- package/runner/src/runner-core.ts +64 -30
- package/src/db/messages.ts +20 -8
- package/public/assets/agents-Dt5TncjC.js +0 -2
- package/public/assets/chat-DhYX9BDG.js +0 -2
- package/public/assets/chat-DhYX9BDG.js.map +0 -1
- package/public/assets/copy-button-C67FUG3E.js +0 -2
- package/public/assets/dist-BJKxqIQd.js +0 -2
- package/public/assets/dist-CHKra2JV.js +0 -2
- package/public/assets/dist-G8YBD-wp.js +0 -2
- package/public/assets/formatted-body-D5-1aiyB.js +0 -3
- package/public/assets/index-BdNafiPz.js +0 -26
- package/public/assets/maintenance-CJ6wikpg.js +0 -2
- package/public/assets/memory-BE9IwCdC.js +0 -2
- package/public/assets/overview-q3O9K9VK.js +0 -2
- package/public/assets/prompt-settings-Bdz_xCgD.js +0 -2
- package/public/assets/settings-Y381jZt7.js +0 -6
- package/public/assets/user-avatar-qbo-B3dQ.js +0 -2
- package/public/assets/workspaces-CoDz-iZd.js +0 -3
|
@@ -25,6 +25,7 @@ export interface TurnStep {
|
|
|
25
25
|
type: "narration" | "reasoning" | "tool";
|
|
26
26
|
text: string;
|
|
27
27
|
label?: string;
|
|
28
|
+
occurredAt?: number;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
interface TranscriptMessage {
|
|
@@ -35,6 +36,7 @@ interface TranscriptMessage {
|
|
|
35
36
|
|
|
36
37
|
interface TranscriptEntry {
|
|
37
38
|
type?: string;
|
|
39
|
+
timestamp?: string;
|
|
38
40
|
message?: TranscriptMessage;
|
|
39
41
|
// Claude Code stamps every transcript entry with `isSidechain`: true for
|
|
40
42
|
// entries belonging to a Task (subagent) run, false for the root session.
|
|
@@ -225,6 +227,46 @@ export function countTranscriptEntries(jsonl: string): number {
|
|
|
225
227
|
return count;
|
|
226
228
|
}
|
|
227
229
|
|
|
230
|
+
/** Key-order-independent structural equality, cheap enough for a small tool_input object. */
|
|
231
|
+
function canonicalJson(value: unknown): string {
|
|
232
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
233
|
+
if (value && typeof value === "object") {
|
|
234
|
+
const keys = Object.keys(value as Record<string, unknown>).sort();
|
|
235
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson((value as Record<string, unknown>)[key])}`).join(",")}}`;
|
|
236
|
+
}
|
|
237
|
+
return JSON.stringify(value);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* True when the transcript already contains the exact assistant tool_use block that
|
|
242
|
+
* triggered a PreToolUse hook — the settle-poll anchor (#1116). The hook payload's own
|
|
243
|
+
* `tool_name`/`tool_input` are the precise call that fired it, so matching them against
|
|
244
|
+
* the transcript's tool_use blocks (name + structural input equality) is exact, unlike
|
|
245
|
+
* guessing from transcript shape alone. Claude's JSONL flush is async/bursty and can
|
|
246
|
+
* lag well behind the hook firing, so the entry backing the hook's own call may still be
|
|
247
|
+
* missing from disk the instant the hook runs — this is what the settle-poll waits for.
|
|
248
|
+
* #1086 should adopt this same anchor for its own turn-final settle marker rather than
|
|
249
|
+
* inventing a second one.
|
|
250
|
+
*/
|
|
251
|
+
export function transcriptHasToolUseAnchor(jsonl: string, toolName: string, toolInput: unknown): boolean {
|
|
252
|
+
const wantInput = canonicalJson(toolInput ?? {});
|
|
253
|
+
for (const line of jsonl.split("\n")) {
|
|
254
|
+
const trimmed = line.trim();
|
|
255
|
+
if (!trimmed) continue;
|
|
256
|
+
let entry: TranscriptEntry;
|
|
257
|
+
try {
|
|
258
|
+
entry = JSON.parse(trimmed) as TranscriptEntry;
|
|
259
|
+
} catch {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (entry.type !== "assistant" || isSidechainEntry(entry)) continue;
|
|
263
|
+
for (const block of blocks(entry.message)) {
|
|
264
|
+
if (block.type === "tool_use" && block.name === toolName && canonicalJson(block.input ?? {}) === wantInput) return true;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
|
|
228
270
|
/**
|
|
229
271
|
* Extract the ordered narration, reasoning, and tool steps for the most recent
|
|
230
272
|
* turn (since the last real user prompt). Used by the reasoning tailer to stream
|
|
@@ -283,18 +325,25 @@ export class LatestTurnStepAccumulator {
|
|
|
283
325
|
return;
|
|
284
326
|
}
|
|
285
327
|
if (entry.type !== "assistant") return;
|
|
328
|
+
const occurredAt = transcriptEntryOccurredAt(entry);
|
|
286
329
|
for (const b of blocks(entry.message)) {
|
|
287
330
|
if (b.type === "text" && typeof b.text === "string" && b.text.trim()) {
|
|
288
|
-
this.latestSteps.push({ type: "narration", text: b.text.trim() });
|
|
331
|
+
this.latestSteps.push({ type: "narration", text: b.text.trim(), ...(occurredAt ? { occurredAt } : {}) });
|
|
289
332
|
} else if (b.type === "thinking" && typeof b.thinking === "string" && b.thinking.trim()) {
|
|
290
|
-
this.latestSteps.push({ type: "reasoning", text: b.thinking.trim() });
|
|
333
|
+
this.latestSteps.push({ type: "reasoning", text: b.thinking.trim(), ...(occurredAt ? { occurredAt } : {}) });
|
|
291
334
|
} else if (b.type === "tool_use" && typeof b.name === "string" && b.name) {
|
|
292
|
-
this.latestSteps.push({ type: "tool", label: b.name, text: summarizeToolUse(b.name, b.input) });
|
|
335
|
+
this.latestSteps.push({ type: "tool", label: b.name, text: summarizeToolUse(b.name, b.input), ...(occurredAt ? { occurredAt } : {}) });
|
|
293
336
|
}
|
|
294
337
|
}
|
|
295
338
|
}
|
|
296
339
|
}
|
|
297
340
|
|
|
341
|
+
function transcriptEntryOccurredAt(entry: TranscriptEntry): number | undefined {
|
|
342
|
+
if (typeof entry.timestamp !== "string" || !entry.timestamp) return undefined;
|
|
343
|
+
const parsed = Date.parse(entry.timestamp);
|
|
344
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
345
|
+
}
|
|
346
|
+
|
|
298
347
|
/**
|
|
299
348
|
* Stable dedup keys for a turn's steps, in order. Each key is salted with how many
|
|
300
349
|
* identical (type,label,text) steps preceded it in the same window — so running the
|
|
@@ -49,9 +49,13 @@ interface ControlServerOptions {
|
|
|
49
49
|
// dashboard chat without the agent re-emitting it via /reply.
|
|
50
50
|
// isPreFlush: true signals a mid-turn call from a PreToolUse hook — flush any
|
|
51
51
|
// un-mirrored narrative before the interactive form appears in chat (#435).
|
|
52
|
+
// toolName/toolInput (#1116): the hook payload's own tool call — the exact anchor the
|
|
53
|
+
// runner settle-polls the transcript for before draining and returning. The resolved
|
|
54
|
+
// `reasoningSettled` reflects whether that anchor was actually found (vs a bounded
|
|
55
|
+
// timeout), so the interactive form can stamp an honest value instead of an assumed one.
|
|
52
56
|
// promptId (#1086): Claude's native `prompt_id` from the Stop hook payload — the
|
|
53
57
|
// stable, provider-native turn identifier used instead of a minted random UUID.
|
|
54
|
-
onSessionTurn?(input: { transcriptPath: string; lastAssistantMessage?: unknown; promptId?: string; isPreFlush?: boolean }): Promise<void>;
|
|
58
|
+
onSessionTurn?(input: { transcriptPath: string; lastAssistantMessage?: unknown; promptId?: string; isPreFlush?: boolean; toolName?: string; toolInput?: unknown }): Promise<{ reasoningSettled: boolean } | void>;
|
|
55
59
|
onPermissionResolved?(input: ResolvedPermissionPrompt): Promise<void> | void;
|
|
56
60
|
// A provider UserPromptSubmit hook hands over the prompt the human typed
|
|
57
61
|
// directly into the session (web terminal / TUI) so the runner can mirror it
|
|
@@ -84,6 +88,8 @@ export interface ResolvedPermissionPrompt {
|
|
|
84
88
|
approvalId: string;
|
|
85
89
|
decision: ProviderPermissionDecisionInput["decision"];
|
|
86
90
|
decisionLabel: string;
|
|
91
|
+
occurredAt: number;
|
|
92
|
+
resolvedAt: number;
|
|
87
93
|
questions?: unknown[];
|
|
88
94
|
answers?: Record<string, string>;
|
|
89
95
|
}
|
|
@@ -264,18 +270,27 @@ async function handlePermissionRequest(
|
|
|
264
270
|
options: ControlServerOptions,
|
|
265
271
|
pendingPermissionRequests: Map<string, { resolve(input: ProviderPermissionDecisionInput): void; timer: Timer }>,
|
|
266
272
|
): Promise<Response> {
|
|
273
|
+
const occurredAt = Date.now();
|
|
267
274
|
const body = await req.json().catch(() => null);
|
|
268
275
|
if (!isRecord(body)) return Response.json({});
|
|
269
|
-
// #435: flush any un-mirrored narrative from the transcript before the form
|
|
270
|
-
// appears in chat
|
|
271
|
-
//
|
|
272
|
-
//
|
|
276
|
+
// #435/#1116: flush any un-mirrored narrative from the transcript before the form
|
|
277
|
+
// appears in chat. The old comment here asserted the transcript already had the
|
|
278
|
+
// assistant text block at PreToolUse time — empirically false: Claude's JSONL flush is
|
|
279
|
+
// async/bursty and can lag the hook firing by a long margin. onSessionTurn now
|
|
280
|
+
// settle-polls for the exact tool_use entry that triggered this hook (toolName/
|
|
281
|
+
// toolInput below are that anchor) before draining, and honestly reports back whether
|
|
282
|
+
// it found the anchor or hit its bounded timeout — reasoningSettled reflects that,
|
|
283
|
+
// rather than an assumption nothing here confirmed.
|
|
273
284
|
const transcriptPath = typeof body.transcript_path === "string" ? body.transcript_path : "";
|
|
285
|
+
const toolName = typeof body.tool_name === "string" ? body.tool_name : undefined;
|
|
286
|
+
const toolInput = isRecord(body.tool_input) ? body.tool_input : undefined;
|
|
287
|
+
let reasoningSettled = true;
|
|
274
288
|
if (transcriptPath && options.onSessionTurn) {
|
|
275
|
-
await options.onSessionTurn({ transcriptPath, isPreFlush: true }).catch(() =>
|
|
289
|
+
const result = await options.onSessionTurn({ transcriptPath, isPreFlush: true, toolName, toolInput }).catch(() => undefined);
|
|
290
|
+
if (result && typeof result.reasoningSettled === "boolean") reasoningSettled = result.reasoningSettled;
|
|
276
291
|
}
|
|
277
292
|
const approvalId = crypto.randomUUID();
|
|
278
|
-
const view = claudePermissionApprovalView(approvalId, body);
|
|
293
|
+
const view = claudePermissionApprovalView(approvalId, body, reasoningSettled);
|
|
279
294
|
options.onStatus({
|
|
280
295
|
status: "busy",
|
|
281
296
|
reason: "provider-turn",
|
|
@@ -299,18 +314,18 @@ async function handlePermissionRequest(
|
|
|
299
314
|
});
|
|
300
315
|
|
|
301
316
|
if (decision.reason !== PERMISSION_TIMEOUT_REASON && options.onPermissionResolved) {
|
|
302
|
-
await Promise.resolve(options.onPermissionResolved(claudeResolvedPermissionPrompt(view, decision, body))).catch(() => {});
|
|
317
|
+
await Promise.resolve(options.onPermissionResolved(claudeResolvedPermissionPrompt(view, decision, body, occurredAt))).catch(() => {});
|
|
303
318
|
}
|
|
304
319
|
|
|
305
320
|
return Response.json(claudePermissionHookResponse(decision, body));
|
|
306
321
|
}
|
|
307
322
|
|
|
308
323
|
// Build the normalized InteractivePrompt for a Claude PreToolUse permission/question hook.
|
|
309
|
-
// reasoningSettled is
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
// the server reads the typed contract, not provider identity.
|
|
313
|
-
function claudePermissionApprovalView(id: string, body: Record<string, unknown
|
|
324
|
+
// reasoningSettled is the caller's settle-poll result (#435/#723/#1116): true when
|
|
325
|
+
// handlePermissionRequest's pre-flush confirmed the turn's reasoning was drained before
|
|
326
|
+
// this view was built, false when it hit its bounded timeout without confirming. No
|
|
327
|
+
// `provider` tag — the server reads the typed contract, not provider identity.
|
|
328
|
+
function claudePermissionApprovalView(id: string, body: Record<string, unknown>, reasoningSettled: boolean): InteractivePrompt {
|
|
314
329
|
const toolName = typeof body.tool_name === "string" ? body.tool_name : "Tool";
|
|
315
330
|
const toolInput = isRecord(body.tool_input) ? body.tool_input : {};
|
|
316
331
|
// AskUserQuestion is not a yes/no gate — it asks the user to pick answers.
|
|
@@ -326,7 +341,7 @@ function claudePermissionApprovalView(id: string, body: Record<string, unknown>)
|
|
|
326
341
|
body: "",
|
|
327
342
|
questions: toolInput.questions as InteractivePrompt["questions"],
|
|
328
343
|
choices: [],
|
|
329
|
-
reasoningSettled
|
|
344
|
+
reasoningSettled,
|
|
330
345
|
};
|
|
331
346
|
}
|
|
332
347
|
// ExitPlanMode arrives through the generic PermissionRequest hook (it doesn't
|
|
@@ -347,7 +362,7 @@ function claudePermissionApprovalView(id: string, body: Record<string, unknown>)
|
|
|
347
362
|
{ id: "approve", label: "Approve plan" },
|
|
348
363
|
{ id: "deny", label: "Keep planning" },
|
|
349
364
|
],
|
|
350
|
-
reasoningSettled
|
|
365
|
+
reasoningSettled,
|
|
351
366
|
};
|
|
352
367
|
}
|
|
353
368
|
const command = typeof toolInput.command === "string" ? toolInput.command : "";
|
|
@@ -368,7 +383,7 @@ function claudePermissionApprovalView(id: string, body: Record<string, unknown>)
|
|
|
368
383
|
{ id: "deny", label: "Deny" },
|
|
369
384
|
{ id: "abort", label: "Abort" },
|
|
370
385
|
],
|
|
371
|
-
reasoningSettled
|
|
386
|
+
reasoningSettled,
|
|
372
387
|
};
|
|
373
388
|
}
|
|
374
389
|
|
|
@@ -437,7 +452,7 @@ function claudePermissionHookResponse(decision: ProviderPermissionDecisionInput,
|
|
|
437
452
|
};
|
|
438
453
|
}
|
|
439
454
|
|
|
440
|
-
function claudeResolvedPermissionPrompt(view: InteractivePrompt, decision: ProviderPermissionDecisionInput, body: Record<string, unknown
|
|
455
|
+
function claudeResolvedPermissionPrompt(view: InteractivePrompt, decision: ProviderPermissionDecisionInput, body: Record<string, unknown>, occurredAt: number): ResolvedPermissionPrompt {
|
|
441
456
|
const toolName = typeof body.tool_name === "string" ? body.tool_name : "Tool";
|
|
442
457
|
const toolInput = isRecord(body.tool_input) ? body.tool_input : {};
|
|
443
458
|
const kind = view.kind === "questions" || view.kind === "plan" || view.kind === "command" || view.kind === "tool"
|
|
@@ -459,6 +474,8 @@ function claudeResolvedPermissionPrompt(view: InteractivePrompt, decision: Provi
|
|
|
459
474
|
approvalId: decision.approvalId,
|
|
460
475
|
decision: decision.decision,
|
|
461
476
|
decisionLabel,
|
|
477
|
+
occurredAt,
|
|
478
|
+
resolvedAt: Date.now(),
|
|
462
479
|
...(questions ? { questions } : {}),
|
|
463
480
|
...(decision.answers ? { answers: decision.answers } : {}),
|
|
464
481
|
};
|
|
@@ -16,7 +16,7 @@ import { readRunnerContextProbeState } from "./context-probe-state";
|
|
|
16
16
|
import { startControlServer, type ControlServer, type ResolvedPermissionPrompt } from "./control-server";
|
|
17
17
|
import { ReplyObligationCache, obligationRequiresExplicitReply, responseCaptureReplyRoute } from "./reply-obligation-cache";
|
|
18
18
|
import { Outbox, type OutboxRecord } from "./outbox";
|
|
19
|
-
import { extractLastAssistantTurn, extractLastAssistantTurnAfterEntry, extractAssistantTurnForPrompt, countTranscriptEntries, extractHookAssistantMessage, extractLatestTurnSteps, stepDedupKeys, transcriptLooksComplete } from "./adapters/claude-transcript";
|
|
19
|
+
import { extractLastAssistantTurn, extractLastAssistantTurnAfterEntry, extractAssistantTurnForPrompt, countTranscriptEntries, extractHookAssistantMessage, extractLatestTurnSteps, stepDedupKeys, transcriptHasToolUseAnchor, transcriptLooksComplete } from "./adapters/claude-transcript";
|
|
20
20
|
import { IncrementalClaudeTranscriptTail } from "./adapters/claude-transcript-tail";
|
|
21
21
|
import { getManifest } from "agent-relay-providers";
|
|
22
22
|
import { profileUsesProviderHostGlobals } from "./profile-home";
|
|
@@ -153,6 +153,13 @@ const MAX_INITIAL_PROMPT_ATTEMPTS = 6;
|
|
|
153
153
|
// transcript bytes.
|
|
154
154
|
const REASONING_POLL_MS = 1_200;
|
|
155
155
|
const SESSION_MIRROR_BATCH_MAX = 100;
|
|
156
|
+
// #1116: bounded settle-poll for the PreToolUse pre-flush anchor. Claude's JSONL flush is
|
|
157
|
+
// async/bursty and can lag the hook firing by a long margin, so a one-shot read (the old
|
|
158
|
+
// #435/#499 behavior) often finds nothing. Poll at this cadence until the exact triggering
|
|
159
|
+
// tool_use entry lands on disk, but never past the timeout — the question must never block
|
|
160
|
+
// the user more than a few seconds waiting on a transcript write that might not be coming.
|
|
161
|
+
const SETTLE_POLL_INTERVAL_MS = 250;
|
|
162
|
+
const SETTLE_POLL_TIMEOUT_MS = 4_000;
|
|
156
163
|
interface RunnerTimelineEvent {
|
|
157
164
|
status: string;
|
|
158
165
|
id?: string;
|
|
@@ -285,6 +292,10 @@ export class AgentRunner {
|
|
|
285
292
|
// Reasoning tailer (item 5): streams the in-flight turn's reasoning/tool steps
|
|
286
293
|
// from the Claude transcript into chat as discreet session events.
|
|
287
294
|
private reasoningTail?: ReasoningTailState;
|
|
295
|
+
// Settle-poll cadence for the pre-flush anchor (#1116) — instance fields (not just the
|
|
296
|
+
// module constants) so tests can shrink them without waiting out the real bound.
|
|
297
|
+
private settlePollIntervalMs = SETTLE_POLL_INTERVAL_MS;
|
|
298
|
+
private settlePollTimeoutMs = SETTLE_POLL_TIMEOUT_MS;
|
|
288
299
|
private scratch?: SessionScratchLayout;
|
|
289
300
|
// #417: for non-claude providers, tracks how many times each spawner obligation has
|
|
290
301
|
// been re-injected so a non-responsive agent can't spin in a re-injection loop.
|
|
@@ -424,7 +435,7 @@ export class AgentRunner {
|
|
|
424
435
|
// Hot-path-safe: answered instantly from the local snapshot, never a server
|
|
425
436
|
// round-trip. The snapshot is kept warm by the background refresh below (#196).
|
|
426
437
|
onReplyObligations: () => Promise.resolve(this.obligationCache.get()),
|
|
427
|
-
onSessionTurn: (input) => this.publishSessionTurn(input), onPermissionResolved: (input) => this.publishSessionEvent({ from: this.agentId, to: "user", body: input.body, session: { type: "prompt-resolution", origin: "provider", label: input.decisionLabel, ...(this.currentTurnId ? { turnId: this.currentTurnId } : {}), stepId: `prompt-resolution:${input.approvalId}`, promptResolution: input } }),
|
|
438
|
+
onSessionTurn: (input) => this.publishSessionTurn(input), onPermissionResolved: (input) => this.publishSessionEvent({ from: this.agentId, to: "user", body: input.body, occurredAt: input.occurredAt, session: { type: "prompt-resolution", origin: "provider", label: input.decisionLabel, ...(this.currentTurnId ? { turnId: this.currentTurnId } : {}), stepId: `prompt-resolution:${input.approvalId}`, promptResolution: input } }),
|
|
428
439
|
onUserPrompt: (input) => this.handleUserPrompt(input),
|
|
429
440
|
onSessionBoundary: (input) => this.handleSessionBoundary(input),
|
|
430
441
|
onHookFatal: (report) => this.reportHookFatal(report),
|
|
@@ -1435,17 +1446,21 @@ export class AgentRunner {
|
|
|
1435
1446
|
// no relay message) are mirrored too. A reply obligation, when present, is still
|
|
1436
1447
|
// used as replyTo so the Stop hook stops nagging the agent to /reply.
|
|
1437
1448
|
//
|
|
1438
|
-
// isPreFlush: true (#435/#499) — mid-turn PreToolUse boundary.
|
|
1439
|
-
//
|
|
1440
|
-
// the
|
|
1441
|
-
|
|
1449
|
+
// isPreFlush: true (#435/#499/#1116) — mid-turn PreToolUse boundary. Settle-polls the
|
|
1450
|
+
// transcript for the exact tool_use entry that triggered the hook (toolName/toolInput,
|
|
1451
|
+
// the hook payload's own anchor), then drains the live tail and force-flushes session
|
|
1452
|
+
// messages before showing the blocking control; the entry cursor lets Stop capture skip
|
|
1453
|
+
// already-emitted text. Returns whether the settle-poll actually found the anchor
|
|
1454
|
+
// (reasoningSettled) — an honest `false` on timeout rather than a stamped `true` on a
|
|
1455
|
+
// premise nothing here confirmed (#1116; see InteractivePrompt.reasoningSettled).
|
|
1456
|
+
private async publishSessionTurn(input: { transcriptPath: string; lastAssistantMessage?: unknown; promptId?: string; isPreFlush?: boolean; toolName?: string; toolInput?: unknown }): Promise<{ reasoningSettled: boolean } | void> {
|
|
1442
1457
|
if (input.transcriptPath) this.lastTranscriptPath = input.transcriptPath;
|
|
1443
1458
|
|
|
1444
1459
|
if (input.isPreFlush) {
|
|
1445
|
-
if (!input.transcriptPath) return;
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
if (
|
|
1460
|
+
if (!input.transcriptPath) return { reasoningSettled: true };
|
|
1461
|
+
const settled = await this.settlePollForToolUse(input.transcriptPath, input.toolName, input.toolInput);
|
|
1462
|
+
const jsonl = settled.jsonl;
|
|
1463
|
+
if (jsonl === undefined) return { reasoningSettled: false };
|
|
1449
1464
|
const body = extractLastAssistantTurnAfterEntry(jsonl, this.narrativeFlushEntryCount);
|
|
1450
1465
|
await this.drainReasoningTail();
|
|
1451
1466
|
this.narrativeFlushEntryCount = countTranscriptEntries(jsonl);
|
|
@@ -1455,7 +1470,7 @@ export class AgentRunner {
|
|
|
1455
1470
|
await this.publishSessionEvent({ from: this.agentId, to: "user", body, session: { type: "response", origin: "provider", ...(turnId ? { turnId } : {}) } });
|
|
1456
1471
|
}
|
|
1457
1472
|
await this.sessionOutbox.flush(2_000).then((ok) => { if (!ok) this.sessionLog(`pre-flush session outbox incomplete before blocking control (${this.sessionOutbox.pendingCount()} pending)`); }, (error) => this.sessionLog(`pre-flush session outbox failed before blocking control: ${errMessage(error)}`));
|
|
1458
|
-
return;
|
|
1473
|
+
return { reasoningSettled: settled.settled };
|
|
1459
1474
|
}
|
|
1460
1475
|
|
|
1461
1476
|
// Claude's Stop hook reports its own native `prompt_id` — the same UUID the
|
|
@@ -1465,6 +1480,11 @@ export class AgentRunner {
|
|
|
1465
1480
|
// prompt_id at turn start — see setProviderStatus / handleUserPrompt), but this
|
|
1466
1481
|
// prefers the freshest source so finalization is correct even if the two drift.
|
|
1467
1482
|
const turnId = input.promptId ?? this.currentTurnId;
|
|
1483
|
+
// #1116: drain whatever the tail hasn't polled yet BEFORE stopping it — stopping first
|
|
1484
|
+
// (the old order) clears the poll/timer state, so any steps Claude flushed in the last
|
|
1485
|
+
// window between the tail's previous tick and this Stop hook were silently dropped from
|
|
1486
|
+
// the step lane instead of reaching chat.
|
|
1487
|
+
await this.drainReasoningTail();
|
|
1468
1488
|
this.stopReasoningTail();
|
|
1469
1489
|
// Optional correlation for threading + obligation clearing — never a capture gate.
|
|
1470
1490
|
let replyToMessageId: number | undefined, replyTarget = "user";
|
|
@@ -1550,22 +1570,18 @@ export class AgentRunner {
|
|
|
1550
1570
|
// tool step) as a `kind: "session"` relay message tagged with payload.session so
|
|
1551
1571
|
// the dashboard can render the live provider session faithfully. Display-only:
|
|
1552
1572
|
// session messages are never delivered back into a provider.
|
|
1553
|
-
private publishSessionEvent(input: {
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
}): void {
|
|
1560
|
-
// Durable, ordered, timestamped (#196): the actual POST happens in deliverOutboxEvent,
|
|
1561
|
-
// retried until it lands. occurredAt is stamped now so a queued event reports when it
|
|
1562
|
-
// truly happened, not when the server finally accepted it. Routed through the fast-lane
|
|
1563
|
-
// sessionOutbox (#332) so a transient trace failure can't head-of-line block real messages.
|
|
1573
|
+
private publishSessionEvent(input: { from: string; to: string; body: string; session: MessageSessionMeta; replyTo?: number; occurredAt?: number }): void {
|
|
1574
|
+
// Durable, ordered, timestamped (#196/#1115): the actual POST happens in
|
|
1575
|
+
// deliverOutboxEvent, retried until it lands. An explicit occurredAt preserves the
|
|
1576
|
+
// provider-native event time; otherwise the outbox stamps enqueue time. Routed through
|
|
1577
|
+
// the fast-lane sessionOutbox (#332) so a transient trace failure can't head-of-line
|
|
1578
|
+
// block real messages.
|
|
1564
1579
|
// A stepId-bearing step (Codex tool running→completed, streamed reasoning/response) uses a
|
|
1565
1580
|
// STABLE idempotency key so the server upserts the row in place instead of appending a dup.
|
|
1566
1581
|
const stepId = input.session.stepId;
|
|
1567
1582
|
this.sessionOutbox.enqueue({
|
|
1568
1583
|
kind: "session-message",
|
|
1584
|
+
...(input.occurredAt ? { occurredAt: input.occurredAt } : {}),
|
|
1569
1585
|
...(stepId ? { idempotencyKey: `session-step:${input.from}:${input.session.turnId ?? ""}:${stepId}` } : {}),
|
|
1570
1586
|
payload: {
|
|
1571
1587
|
from: input.from,
|
|
@@ -1578,13 +1594,7 @@ export class AgentRunner {
|
|
|
1578
1594
|
});
|
|
1579
1595
|
}
|
|
1580
1596
|
|
|
1581
|
-
private publishSessionEventsBatch(inputs: Array<{
|
|
1582
|
-
from: string;
|
|
1583
|
-
to: string;
|
|
1584
|
-
body: string;
|
|
1585
|
-
session: MessageSessionMeta;
|
|
1586
|
-
replyTo?: number;
|
|
1587
|
-
}>): void {
|
|
1597
|
+
private publishSessionEventsBatch(inputs: Array<{ from: string; to: string; body: string; session: MessageSessionMeta; replyTo?: number; occurredAt?: number }>): void {
|
|
1588
1598
|
for (let i = 0; i < inputs.length; i += SESSION_MIRROR_BATCH_MAX) {
|
|
1589
1599
|
const chunk = inputs.slice(i, i + SESSION_MIRROR_BATCH_MAX);
|
|
1590
1600
|
if (chunk.length === 1) {
|
|
@@ -1599,6 +1609,7 @@ export class AgentRunner {
|
|
|
1599
1609
|
...(input.replyTo ? { replyTo: input.replyTo } : {}),
|
|
1600
1610
|
kind: "session",
|
|
1601
1611
|
body: input.body,
|
|
1612
|
+
...(input.occurredAt ? { occurredAt: input.occurredAt } : {}),
|
|
1602
1613
|
...(stepId ? { idempotencyKey: `session-step:${input.from}:${input.session.turnId ?? ""}:${stepId}` } : {}),
|
|
1603
1614
|
payload: { session: { provider: this.options.provider, ...input.session } },
|
|
1604
1615
|
} satisfies SendMessageInput;
|
|
@@ -1940,7 +1951,7 @@ export class AgentRunner {
|
|
|
1940
1951
|
}
|
|
1941
1952
|
const turnId = this.currentTurnId ?? turnIdAtStart;
|
|
1942
1953
|
let emitted = 0;
|
|
1943
|
-
const batch: Array<{ from: string; to: string; body: string; session: MessageSessionMeta }> = [];
|
|
1954
|
+
const batch: Array<{ from: string; to: string; body: string; session: MessageSessionMeta; occurredAt?: number }> = [];
|
|
1944
1955
|
for (const { sig, step } of keyed) {
|
|
1945
1956
|
if (seen.has(sig)) continue;
|
|
1946
1957
|
seen.add(sig);
|
|
@@ -1949,6 +1960,7 @@ export class AgentRunner {
|
|
|
1949
1960
|
from: this.agentId,
|
|
1950
1961
|
to: "user",
|
|
1951
1962
|
body: step.text,
|
|
1963
|
+
...(step.occurredAt ? { occurredAt: step.occurredAt } : {}),
|
|
1952
1964
|
session: { type: step.type, origin: "provider", ...(turnId ? { turnId } : {}), ...(step.label ? { label: step.label } : {}) },
|
|
1953
1965
|
});
|
|
1954
1966
|
if (step.type === "narration") emittedNarrationKeys.add(sig);
|
|
@@ -1971,6 +1983,28 @@ export class AgentRunner {
|
|
|
1971
1983
|
await this.reasoningTail?.poll();
|
|
1972
1984
|
}
|
|
1973
1985
|
|
|
1986
|
+
// #1116: bounded settle-poll for the PreToolUse pre-flush anchor. The old pre-flush read
|
|
1987
|
+
// the transcript exactly once and, on the false premise that Claude's JSONL write is
|
|
1988
|
+
// synchronous with the hook firing, treated whatever it found (often nothing) as complete.
|
|
1989
|
+
// Claude's flush is actually async/bursty — sometimes gated on the hook's own resolution —
|
|
1990
|
+
// so this polls at settlePollIntervalMs until the transcript contains the EXACT tool_use
|
|
1991
|
+
// entry that triggered the hook (toolName/toolInput from the hook payload is the precise
|
|
1992
|
+
// anchor), then returns the freshest read. Never polls past settlePollTimeoutMs — the
|
|
1993
|
+
// question must ship regardless, just honestly flagged as `settled: false` when it does.
|
|
1994
|
+
// No toolName (a caller that can't identify an anchor) skips straight to a single read,
|
|
1995
|
+
// matching the old one-shot behavior.
|
|
1996
|
+
private async settlePollForToolUse(transcriptPath: string, toolName?: string, toolInput?: unknown): Promise<{ settled: boolean; jsonl?: string }> {
|
|
1997
|
+
const deadline = Date.now() + this.settlePollTimeoutMs;
|
|
1998
|
+
for (;;) {
|
|
1999
|
+
let jsonl: string | undefined;
|
|
2000
|
+
try { jsonl = await readFile(transcriptPath, "utf8"); } catch { jsonl = undefined; }
|
|
2001
|
+
if (!toolName) return { settled: Boolean(jsonl), jsonl };
|
|
2002
|
+
if (jsonl !== undefined && transcriptHasToolUseAnchor(jsonl, toolName, toolInput)) return { settled: true, jsonl };
|
|
2003
|
+
if (Date.now() >= deadline) return { settled: false, jsonl };
|
|
2004
|
+
await Bun.sleep(this.settlePollIntervalMs);
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
|
|
1974
2008
|
private preFlushBodyAlreadyMirroredByReasoningTail(jsonl: string, body: string): boolean {
|
|
1975
2009
|
const tail = this.reasoningTail;
|
|
1976
2010
|
if (!tail || !tail.emittedNarrationKeys.size) return false;
|
package/src/db/messages.ts
CHANGED
|
@@ -138,8 +138,10 @@ function normalizeMessagePayload(payload: MessagePayload | undefined): MessagePa
|
|
|
138
138
|
* stable `payload.session.stepId`. Instead of appending a second row (which renders as a
|
|
139
139
|
* duplicate on dashboard reload), update the EXISTING row in place: same DB id, so the
|
|
140
140
|
* dashboard's id-keyed merge replaces it. Gated on stepId presence + matching step, so the
|
|
141
|
-
* Claude transcript path (no stepId) is byte-identical.
|
|
142
|
-
*
|
|
141
|
+
* Claude transcript path (no stepId) is byte-identical. The row keeps the first event's
|
|
142
|
+
* occurredAt so long-running tools stay anchored at their start; completion time rides in
|
|
143
|
+
* payload.session.completedAt. Lifecycle rank still prevents a late running retry from
|
|
144
|
+
* clobbering an already-completed row.
|
|
143
145
|
*/
|
|
144
146
|
function upsertSessionStepInPlace(existing: Message, input: SendMessageInput, now: number): Message | null {
|
|
145
147
|
const incomingStep = sessionStepId(input.payload);
|
|
@@ -147,14 +149,24 @@ function upsertSessionStepInPlace(existing: Message, input: SendMessageInput, no
|
|
|
147
149
|
if (existing.kind !== "session") return null;
|
|
148
150
|
if (sessionStepId(existing.payload) !== incomingStep) return null;
|
|
149
151
|
const incomingAt = sanitizeOccurredAt(input.occurredAt, now) ?? now;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
getDb()
|
|
153
|
-
.query("UPDATE messages SET body = ?, payload = ?, occurred_at = ? WHERE id = ?")
|
|
154
|
-
.run(input.body, JSON.stringify(input.payload ?? {}), incomingAt, existing.id);
|
|
152
|
+
if (sessionStepStatusRank(input.payload) < sessionStepStatusRank(existing.payload)) return null;
|
|
153
|
+
const payload = sessionPayloadWithCompletion(input.payload, incomingAt);
|
|
154
|
+
getDb().query("UPDATE messages SET body = ?, payload = ? WHERE id = ?").run(input.body, JSON.stringify(payload), existing.id);
|
|
155
155
|
return getMessage(existing.id);
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
function sessionStepStatusRank(payload: MessagePayload | undefined): number {
|
|
159
|
+
const status = stringValue(isRecord(payload?.session) ? payload.session.status : undefined);
|
|
160
|
+
return status === "completed" || status === "failed" ? 2 : status === "running" ? 1 : 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function sessionPayloadWithCompletion(payload: MessagePayload | undefined, completedAt: number): MessagePayload {
|
|
164
|
+
const original = payload ?? {};
|
|
165
|
+
const session = isRecord(original.session) ? original.session : undefined;
|
|
166
|
+
const status = stringValue(session?.status);
|
|
167
|
+
return status === "completed" || status === "failed" ? { ...original, session: { ...session, completedAt } } : original;
|
|
168
|
+
}
|
|
169
|
+
|
|
158
170
|
function policyNameFromTarget(target: string): string | null {
|
|
159
171
|
if (!target.startsWith("policy:")) return null;
|
|
160
172
|
const name = target.slice("policy:".length).trim();
|
|
@@ -256,7 +268,7 @@ function lineageStreamTurnId(message: Message, session: Record<string, unknown>)
|
|
|
256
268
|
}
|
|
257
269
|
|
|
258
270
|
function lineageStreamEventId(message: Message, session: Record<string, unknown>): string {
|
|
259
|
-
const occurredAt = message.occurredAt ?? message.createdAt;
|
|
271
|
+
const occurredAt = typeof session.completedAt === "number" && Number.isFinite(session.completedAt) ? Math.floor(session.completedAt) : message.occurredAt ?? message.createdAt;
|
|
260
272
|
const stepId = stringValue(session.stepId);
|
|
261
273
|
return stepId ? `${stepId}:${occurredAt}` : `message:${occurredAt}`;
|
|
262
274
|
}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{r as e}from"./chunk-CilyBKbf.js";import{B as t,Gn as n,H as r,Ht as i,Qt as a,Xn as o,_ as s,_t as c,a as l,et as u,g as d,gr as f,q as p,rr as m,rt as h,tr as g,u as _}from"./lucide-react-NXHPIMsX.js";import{p as ee,t as v}from"./store-D-Frk_EC.js";import{_ as te,h as y}from"./types-BqeSkWrv.js";import{J as b,K as x,W as S,Y as C,a as w,d as T,i as ne,n as re}from"./display-aTnwsqQh.js";import{o as E,r as D}from"./dist-BNB3fTO-.js";import{t as O}from"./dist-BD4Xc13v.js";import{i as ie,n as k}from"./dist-BFDfbfNC.js";import{t as A}from"./dist-DG75bvPp.js";import{t as j}from"./dist-CQMQ5iku.js";import{a as M,i as N,n as P,o as F,r as I,s as ae,t as oe}from"./es2015-a6fysEVz.js";import{a as L,i as se,n as R,r as z,t as B}from"./dist-C_VOBSaK.js";import{t as V}from"./badge-BCvp7LK3.js";import{t as H}from"./button-DyHjsHY3.js";import{t as ce}from"./copy-button-C67FUG3E.js";import{t as U}from"./agent-type-icon-BmBR-9CH.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-BdNafiPz.js";import{r as xe,t as Se}from"./card-BoqYxpVW.js";import{i as Ce,n as we,r as Te,t as Ee}from"./branch-state-badge-Cscjqf-E.js";var K=e(f(),1),q=m(),J=`Popover`,[De,Oe]=ie(J,[L]),Y=L(),[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]=A({prop:r,defaultProp:i??!1,onChange:a,caller:J});return(0,q.jsx)(se,{...s,children:(0,q.jsx)(ke,{scope:t,contentId:ae(),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 Z=`PopoverTrigger`,Ne=K.forwardRef((e,t)=>{let{__scopePopover:n,...r}=e,i=X(Z,n),a=Y(n),o=E(t,i.triggerRef),s=(0,q.jsx)(O.button,{type:`button`,"aria-haspopup":`dialog`,"aria-expanded":i.open,"aria-controls":i.contentId,"data-state":Ke(i.open),...r,ref:o,onClick:k(e.onClick,i.onOpenToggle)});return i.hasCustomAnchor?s:(0,q.jsx)(B,{asChild:!0,...a,children:s})});Ne.displayName=Z;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)(j,{present:n||a.open,children:(0,q.jsx)(N,{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)(j,{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=D(`PopoverContent.RemoveScroll`),ze=K.forwardRef((e,t)=>{let n=X($,e.__scopePopover),r=K.useRef(null),i=E(t,r),a=K.useRef(!1);return K.useEffect(()=>{let e=r.current;if(e)return oe(e)},[]),(0,q.jsx)(P,{as:Re,allowPinchZoom:!0,children:(0,q.jsx)(Ve,{...e,ref:i,trapFocus:n.open,disableOutsidePointerEvents:!0,onCloseAutoFocus:k(e.onCloseAutoFocus,e=>{e.preventDefault(),a.current||n.triggerRef.current?.focus()}),onPointerDownOutside:k(e.onPointerDownOutside,e=>{let t=e.detail.originalEvent,n=t.button===0&&t.ctrlKey===!0;a.current=t.button===2||n},{checkForDefaultPrevented:!1}),onFocusOutside:k(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 I(),(0,q.jsx)(M,{asChild:!0,loop:!0,trapped:r,onMountAutoFocus:i,onUnmountAutoFocus:a,children:(0,q.jsx)(F,{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)(O.button,{type:`button`,...r,ref:t,onClick:k(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)(R,{...i,...r,ref:t})});Ge.displayName=We;function Ke(e){return e?`open`:`closed`}var qe=Ae,Je=Ne,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:g(`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:g(`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:g(`font-medium`,e),...t})}function nt({agent:e,childAgents:n=[],parentAgent:r}){let o=ee();v(e=>e.set);let l=v(e=>e.switchView),f=v(e=>e.openInboxThread),m=v(e=>e.openAgentDetail),g=v(e=>e.openPairInvite),te=v(e=>e.openRename),y=v(e=>e.openConfirm),E=v(e=>e.doAgentAction),D=v(e=>e.doDeleteAgent),O=v(e=>e.openFilesForAgent),ie=v(e=>e.workspaceAction),k=v(e=>e.openWorkspaceFocus),{terminalOpen:A,terminalTarget:j,terminalOpening:M,openTerminal:N,closeTerminal:P}=Ce(e.id),F=le(),I=ne(o,e,me(e),F[e.id]||null),ae=w(e,`shutdown`),oe=w(e,`compact`),L=w(e,`clearContext`),se=!!(e.providerCapabilities?.terminal?.live?.read||e.providerCapabilities?.terminal?.attach?.create),R=!!e.providerCapabilities?.terminal?.live?.write,z=re(e),B=r?T(r):e.spawnedBy,W=n.length>0;return(0,q.jsxs)(q.Fragment,{children:[(0,q.jsx)(Se,{className:`group hover:border-zinc-600 transition-colors cursor-pointer`,onClick:()=>m(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:T(e)})]}),(0,q.jsx)(`div`,{className:`text-xs mt-0.5 ${b(I.tone)}`,children:I.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(),O(e)},children:S(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(),k(e.branchWorkspaceId)}})]}),I.badges.length>0&&(0,q.jsx)(`div`,{className:`flex flex-wrap gap-1 mb-2`,children:I.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:`${n.length} live child ${n.length===1?`agent`:`agents`}`,children:[(0,q.jsx)(_,{className:`h-3 w-3`}),(0,q.jsx)(`span`,{children:n.length}),(0,q.jsx)(`span`,{className:`flex -space-x-1`,children:n.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:n.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:T(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:()=>m(e),children:`Open`}),(0,q.jsxs)(H,{size:`xs`,variant:`ghost`,onClick:()=>{f(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||C(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)),C(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`&&x(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:()=>{f(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:()=>g(e.id),children:(0,q.jsx)(c,{className:`w-3 h-3`})}),(0,q.jsx)(H,{size:`icon`,variant:`ghost`,className:`h-7 w-7`,title:`Rename`,onClick:()=>te(e),children:(0,q.jsx)(p,{className:`w-3 h-3`})}),se&&(0,q.jsx)(H,{size:`icon`,variant:`ghost`,className:`h-7 w-7`,title:`Terminal`,disabled:M,onClick:()=>void N(),children:(0,q.jsx)(s,{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:()=>y(`Mark Workspace Ready`,`Mark ${T(e)}'s branch ready to land? The relay auto-merge will rebase and land it.`,()=>ie(e.branchWorkspaceId,`ready`)),children:(0,q.jsx)(i,{className:`w-3 h-3`})}),oe&&(0,q.jsx)(H,{size:`icon`,variant:`ghost`,className:`h-7 w-7`,title:`Compact`,onClick:()=>y(`Compact Agent`,`Compact context for ${T(e)}?`,()=>E(e,`compact`,`agents-grid`)),children:(0,q.jsx)(u,{className:`w-3 h-3`})}),L&&(0,q.jsx)(H,{size:`icon`,variant:`ghost`,className:`h-7 w-7`,title:`Clear Context`,onClick:()=>y(`Clear Context`,`Clear context for ${T(e)}?`,()=>E(e,`clearContext`,`agents-grid`)),children:(0,q.jsx)(a,{className:`w-3 h-3`})}),ae&&(0,q.jsx)(H,{size:`icon`,variant:`ghost`,className:`h-7 w-7 text-red-400 hover:text-red-300`,title:`Shutdown`,onClick:()=>y(`Shutdown Agent`,`Shutdown ${T(e)}?`,()=>E(e,`shutdown`,`agents-grid`)),children:(0,q.jsx)(t,{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:()=>y(`Forget Agent`,`Forget ${T(e)}?`,()=>D(e.id)),children:(0,q.jsx)(d,{className:`w-3 h-3`})})]})]})}),j&&(0,q.jsx)(he,{open:A,onOpenChange:P,orchestratorId:j.orchestratorId,session:j.session,interactive:j.interactive||R,canWrite:R})]})}function rt(){let e=v(e=>e.agentPresetFilter),t=v(e=>e.agentStatusFilter),i=v(e=>e.agentTagFilter),a=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(),b=ve(h,m),x=fe(h,m),S=e||t||i||a;function C(){d({agentPresetFilter:``,agentStatusFilter:``,agentTagFilter:``,agentHostFilter:``})}function w(){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`}),y().map(e=>(0,q.jsx)(`option`,{value:e,children:te(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:i,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:a,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:w,children:c===`asc`?(0,q.jsx)(n,{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`]}),S&&(0,q.jsxs)(H,{size:`sm`,variant:`ghost`,className:`h-8 text-muted-foreground hover:text-foreground gap-1`,onClick:C,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`]})})]}),x.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:x.map(e=>(0,q.jsx)(nt,{agent:e,parentAgent:e.spawnedBy?p[e.spawnedBy]??null:null,childAgents:ue(b.get(e.id)??[])},e.id))})]})}export{rt as AgentsView};
|
|
2
|
-
//# sourceMappingURL=agents-Dt5TncjC.js.map
|