@agent-native/dispatch 0.21.0 → 0.22.1
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/dist/actions/get-agent-thread-debug.d.ts +2 -0
- package/dist/actions/provider-api-register.d.ts +8 -8
- package/dist/actions/search-agent-threads.js +2 -2
- package/dist/actions/search-agent-threads.js.map +1 -1
- package/dist/components/layout/HeaderActions.d.ts +1 -17
- package/dist/components/layout/HeaderActions.d.ts.map +1 -1
- package/dist/components/layout/HeaderActions.js +1 -51
- package/dist/components/layout/HeaderActions.js.map +1 -1
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +22 -14
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/ui/dialog.d.ts +1 -19
- package/dist/components/ui/dialog.d.ts.map +1 -1
- package/dist/components/ui/dialog.js +1 -22
- package/dist/components/ui/dialog.js.map +1 -1
- package/dist/components/ui/dropdown-menu.d.ts +1 -27
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
- package/dist/components/ui/dropdown-menu.js +1 -35
- package/dist/components/ui/dropdown-menu.js.map +1 -1
- package/dist/components/ui/popover.d.ts +2 -1
- package/dist/components/ui/popover.d.ts.map +1 -1
- package/dist/components/ui/popover.js +2 -1
- package/dist/components/ui/popover.js.map +1 -1
- package/dist/components/ui/select.d.ts +1 -13
- package/dist/components/ui/select.d.ts.map +1 -1
- package/dist/components/ui/select.js +1 -26
- package/dist/components/ui/select.js.map +1 -1
- package/dist/hooks/use-mobile.d.ts +1 -1
- package/dist/hooks/use-mobile.d.ts.map +1 -1
- package/dist/hooks/use-mobile.js +1 -15
- package/dist/hooks/use-mobile.js.map +1 -1
- package/dist/lib/automation-display.js +1 -1
- package/dist/lib/automation-display.js.map +1 -1
- package/dist/routes/pages/thread-debug.d.ts.map +1 -1
- package/dist/routes/pages/thread-debug.js +288 -118
- package/dist/routes/pages/thread-debug.js.map +1 -1
- package/dist/server/lib/thread-debug-store.d.ts +2 -0
- package/dist/server/lib/thread-debug-store.d.ts.map +1 -1
- package/dist/server/lib/thread-debug-store.js +6 -2
- package/dist/server/lib/thread-debug-store.js.map +1 -1
- package/package.json +3 -3
- package/src/actions/search-agent-threads.ts +2 -2
- package/src/components/layout/HeaderActions.tsx +1 -84
- package/src/components/layout/Layout.spec.tsx +94 -10
- package/src/components/layout/Layout.tsx +107 -101
- package/src/components/ui/dialog.tsx +1 -120
- package/src/components/ui/dropdown-menu.tsx +1 -198
- package/src/components/ui/popover.tsx +3 -1
- package/src/components/ui/select.tsx +1 -158
- package/src/hooks/use-mobile.tsx +1 -21
- package/src/lib/automation-display.spec.ts +1 -1
- package/src/lib/automation-display.ts +1 -1
- package/src/routes/pages/thread-debug.spec.tsx +103 -5
- package/src/routes/pages/thread-debug.tsx +1051 -451
- package/src/server/lib/thread-debug-store.spec.ts +17 -0
- package/src/server/lib/thread-debug-store.ts +9 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
3
3
|
import { useT } from "@agent-native/core/client/i18n";
|
|
4
|
-
import { IconAlertTriangle,
|
|
4
|
+
import { IconActivity, IconAdjustmentsHorizontal, IconAlertTriangle, IconCopy, IconDatabase, IconFileSearch, IconInfoCircle, IconRefresh, IconSearch, IconTool, } from "@tabler/icons-react";
|
|
5
5
|
import { useMemo, useState } from "react";
|
|
6
6
|
import { useSearchParams } from "react-router";
|
|
7
7
|
import { ActionQueryError } from "../../components/action-query-error.js";
|
|
@@ -9,6 +9,7 @@ import { DispatchShell } from "../../components/dispatch-shell.js";
|
|
|
9
9
|
import { Badge } from "../../components/ui/badge.js";
|
|
10
10
|
import { Button } from "../../components/ui/button.js";
|
|
11
11
|
import { Input } from "../../components/ui/input.js";
|
|
12
|
+
import { Popover, PopoverAnchor, PopoverContent, } from "../../components/ui/popover.js";
|
|
12
13
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "../../components/ui/select.js";
|
|
13
14
|
import { Skeleton } from "../../components/ui/skeleton.js";
|
|
14
15
|
import { Tabs, TabsContent, TabsList, TabsTrigger, } from "../../components/ui/tabs.js";
|
|
@@ -21,6 +22,7 @@ const FAILURE_RANGE_HOURS = {
|
|
|
21
22
|
"7d": 7 * 24,
|
|
22
23
|
"30d": 30 * 24,
|
|
23
24
|
};
|
|
25
|
+
const EMPTY_FAILURES = [];
|
|
24
26
|
function parseMode(value) {
|
|
25
27
|
return value === "threads" ? "threads" : "failures";
|
|
26
28
|
}
|
|
@@ -29,6 +31,9 @@ function parseFailureStatus(value) {
|
|
|
29
31
|
? value
|
|
30
32
|
: "all";
|
|
31
33
|
}
|
|
34
|
+
function parseFailureRegime(value) {
|
|
35
|
+
return value === "interactive" || value === "scheduled" ? value : "all";
|
|
36
|
+
}
|
|
32
37
|
function parseFailureRange(value) {
|
|
33
38
|
return value === "7d" || value === "30d" ? value : "24h";
|
|
34
39
|
}
|
|
@@ -38,6 +43,30 @@ function failureSourceId(failure) {
|
|
|
38
43
|
function failureSourceLabel(failure) {
|
|
39
44
|
return (failure.sourceLabel || failure.source?.label || failureSourceId(failure));
|
|
40
45
|
}
|
|
46
|
+
function isTechnicalIdentifier(value) {
|
|
47
|
+
return /^(?:job|run|thread|turn|request|req)-[a-z0-9][a-z0-9_-]*$/i.test(value.trim());
|
|
48
|
+
}
|
|
49
|
+
function displayListTitle(value, fallbackValue, fallbackLabel) {
|
|
50
|
+
const candidate = [value, fallbackValue]
|
|
51
|
+
.map((entry) => entry?.trim() || "")
|
|
52
|
+
.find((entry) => entry && !isTechnicalIdentifier(entry));
|
|
53
|
+
if (!candidate)
|
|
54
|
+
return fallbackLabel;
|
|
55
|
+
const withoutDate = candidate
|
|
56
|
+
.replace(/\s+[—-]\s+\d{1,2}\/\d{1,2}\/\d{4}\s*$/, "")
|
|
57
|
+
.trim();
|
|
58
|
+
if (!withoutDate)
|
|
59
|
+
return fallbackLabel;
|
|
60
|
+
if (/^job:/i.test(withoutDate)) {
|
|
61
|
+
return withoutDate
|
|
62
|
+
.replace(/^job:\s*/i, "")
|
|
63
|
+
.replace(/[-_]+/g, " ")
|
|
64
|
+
.replace(/\s+/g, " ")
|
|
65
|
+
.replace(/\b\w/g, (character) => character.toUpperCase())
|
|
66
|
+
.trim();
|
|
67
|
+
}
|
|
68
|
+
return withoutDate;
|
|
69
|
+
}
|
|
41
70
|
function formatDate(value) {
|
|
42
71
|
if (value == null || value === "")
|
|
43
72
|
return "n/a";
|
|
@@ -47,6 +76,26 @@ function formatDate(value) {
|
|
|
47
76
|
return "n/a";
|
|
48
77
|
return date.toLocaleString();
|
|
49
78
|
}
|
|
79
|
+
function formatRelativeDate(value) {
|
|
80
|
+
if (value == null || value === "")
|
|
81
|
+
return "n/a";
|
|
82
|
+
const numeric = Number(value);
|
|
83
|
+
const timestamp = Number.isFinite(numeric)
|
|
84
|
+
? numeric
|
|
85
|
+
: new Date(value).getTime();
|
|
86
|
+
if (!Number.isFinite(timestamp))
|
|
87
|
+
return "n/a";
|
|
88
|
+
const elapsed = Date.now() - timestamp;
|
|
89
|
+
if (elapsed < 0)
|
|
90
|
+
return "in a moment";
|
|
91
|
+
if (elapsed < 60_000)
|
|
92
|
+
return "just now";
|
|
93
|
+
if (elapsed < 3_600_000)
|
|
94
|
+
return `${Math.floor(elapsed / 60_000)}m ago`;
|
|
95
|
+
if (elapsed < 86_400_000)
|
|
96
|
+
return `${Math.floor(elapsed / 3_600_000)}h ago`;
|
|
97
|
+
return `${Math.floor(elapsed / 86_400_000)}d ago`;
|
|
98
|
+
}
|
|
50
99
|
function formatDuration(value) {
|
|
51
100
|
if (value == null || !Number.isFinite(value))
|
|
52
101
|
return "n/a";
|
|
@@ -56,6 +105,113 @@ function formatDuration(value) {
|
|
|
56
105
|
return `${(value / 1_000).toFixed(1)}s`;
|
|
57
106
|
return `${(value / 60_000).toFixed(1)}m`;
|
|
58
107
|
}
|
|
108
|
+
function humanizeIdentifier(value) {
|
|
109
|
+
if (!value)
|
|
110
|
+
return "Unknown failure";
|
|
111
|
+
return value
|
|
112
|
+
.replace(/^(error:|failure:)/, "")
|
|
113
|
+
.replace(/[_-]+/g, " ")
|
|
114
|
+
.replace(/\b\w/g, (character) => character.toUpperCase());
|
|
115
|
+
}
|
|
116
|
+
function failureLabel(run) {
|
|
117
|
+
const code = run.errorCode || run.terminalReason || run.abortReason;
|
|
118
|
+
const labels = {
|
|
119
|
+
stale_run: "Worker heartbeat stopped",
|
|
120
|
+
background_worker_never_started: "Background worker never started",
|
|
121
|
+
background_worker_failed: "Background worker setup failed",
|
|
122
|
+
builder_gateway_network_error: "Gateway stream ended early",
|
|
123
|
+
provider_timeout: "Provider timed out",
|
|
124
|
+
provider_network_error: "Provider connection dropped",
|
|
125
|
+
provider_config_error: "Provider configuration rejected",
|
|
126
|
+
authentication_error: "Provider authentication failed",
|
|
127
|
+
overloaded_error: "Provider was overloaded",
|
|
128
|
+
"aborted:user": "Stopped by user",
|
|
129
|
+
};
|
|
130
|
+
return (code && labels[code]) || humanizeIdentifier(code);
|
|
131
|
+
}
|
|
132
|
+
function runDiagnosis(run) {
|
|
133
|
+
const code = run.errorCode || run.terminalReason || run.abortReason || "unknown";
|
|
134
|
+
if (code === "stale_run") {
|
|
135
|
+
const workerStarted = run.dispatchMode === "background-processing";
|
|
136
|
+
return {
|
|
137
|
+
title: "Worker stopped reporting",
|
|
138
|
+
summary: workerStarted
|
|
139
|
+
? "The worker claimed this run, then stopped writing heartbeat or progress signals before it finished. This is a liveness failure, not proof that the provider failed."
|
|
140
|
+
: "The run stayed active until liveness recovery closed it. No completed result was recorded, so inspect the handoff and retained evidence before blaming a provider.",
|
|
141
|
+
nextStep: workerStarted
|
|
142
|
+
? "Check the last worker stage and database heartbeat writes."
|
|
143
|
+
: "Check the scheduled background handoff and whether a worker claimed it.",
|
|
144
|
+
code,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (code === "background_worker_never_started") {
|
|
148
|
+
return {
|
|
149
|
+
title: "Background worker never started",
|
|
150
|
+
summary: "The handoff was acknowledged, but no background worker claimed the run.",
|
|
151
|
+
nextStep: "Check the background route, authentication, and function logs.",
|
|
152
|
+
code,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (code === "background_worker_failed") {
|
|
156
|
+
return {
|
|
157
|
+
title: "Background worker failed during setup",
|
|
158
|
+
summary: "The worker claimed the run but stopped before it could start the turn.",
|
|
159
|
+
nextStep: "Use the recorded setup stage and worker logs to find the first failure.",
|
|
160
|
+
code,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (code === "builder_gateway_network_error") {
|
|
164
|
+
return {
|
|
165
|
+
title: "Gateway stream ended early",
|
|
166
|
+
summary: run.errorDetail ||
|
|
167
|
+
"The model stream ended before the run emitted a terminal event.",
|
|
168
|
+
nextStep: "Retry once; if it repeats, inspect gateway and provider transport health.",
|
|
169
|
+
code,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (code === "aborted:user") {
|
|
173
|
+
return {
|
|
174
|
+
title: "Stopped by user",
|
|
175
|
+
summary: "This run was explicitly aborted and is not a system failure.",
|
|
176
|
+
nextStep: "No recovery action is required.",
|
|
177
|
+
code,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
title: failureLabel(run),
|
|
182
|
+
summary: run.errorDetail || "The run ended without a more specific explanation.",
|
|
183
|
+
nextStep: "Open the timeline and inspect the last recorded stage or event.",
|
|
184
|
+
code,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function eventType(event) {
|
|
188
|
+
return typeof event?.type === "string" ? event.type : "event";
|
|
189
|
+
}
|
|
190
|
+
function eventIsNoise(event) {
|
|
191
|
+
return [
|
|
192
|
+
"thinking",
|
|
193
|
+
"text",
|
|
194
|
+
"tool_input_delta",
|
|
195
|
+
"stream_keepalive",
|
|
196
|
+
"activity",
|
|
197
|
+
].includes(eventType(event));
|
|
198
|
+
}
|
|
199
|
+
function summarizeEvents(events) {
|
|
200
|
+
const toolNames = new Map();
|
|
201
|
+
let toolStarts = 0;
|
|
202
|
+
for (const entry of events) {
|
|
203
|
+
if (eventType(entry.event) !== "tool_start")
|
|
204
|
+
continue;
|
|
205
|
+
toolStarts += 1;
|
|
206
|
+
const name = String(entry.event?.tool ?? "tool");
|
|
207
|
+
toolNames.set(name, (toolNames.get(name) ?? 0) + 1);
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
toolStarts,
|
|
211
|
+
toolNames: [...toolNames.entries()],
|
|
212
|
+
meaningful: events.filter((entry) => !eventIsNoise(entry.event)).slice(-12),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
59
215
|
function json(value) {
|
|
60
216
|
try {
|
|
61
217
|
return JSON.stringify(value, null, 2);
|
|
@@ -104,20 +260,14 @@ function diagnosticStage(value) {
|
|
|
104
260
|
function RawBlock({ value, className, }) {
|
|
105
261
|
return (_jsx("pre", { className: cn("max-h-[520px] overflow-auto rounded-lg border bg-muted/30 p-3 text-xs leading-relaxed text-foreground", "whitespace-pre-wrap break-words", className), children: typeof value === "string" ? value : json(value) }));
|
|
106
262
|
}
|
|
107
|
-
function SourceBadge({ source }) {
|
|
108
|
-
return (_jsx(Badge, { variant: source.current ? "default" : "secondary", children: source.current ? "current" : source.kind }));
|
|
109
|
-
}
|
|
110
263
|
function ResultCard({ result, selected, onSelect, }) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
: "bg-card hover:border-foreground/30 hover:bg-muted/40"), children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate text-sm font-medium text-foreground", children: result.title || result.preview || result.id }), _jsx("div", { className: "mt-1 truncate font-mono text-[11px] text-muted-foreground", children: result.id })] }), _jsx(Badge, { variant: "outline", className: "shrink-0", children: result.messageCount })] }), _jsx("div", { className: "mt-2 line-clamp-3 text-xs leading-relaxed text-muted-foreground", children: result.snippet || result.preview || "No preview" }), _jsxs("div", { className: "mt-2 flex items-center justify-between gap-3 text-[11px] text-muted-foreground", children: [_jsx("span", { className: "truncate", children: result.ownerEmail }), _jsx("span", { className: "shrink-0", children: formatDate(result.updatedAt) })] })] }));
|
|
264
|
+
const title = displayListTitle(result.title, result.preview, "Untitled thread");
|
|
265
|
+
return (_jsxs("button", { type: "button", onClick: onSelect, "aria-current": selected ? "true" : undefined, className: cn("group w-full border-b px-4 py-3 text-left transition-colors last:border-b-0", selected ? "bg-accent/70" : "hover:bg-muted/50"), children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsx("div", { className: "min-w-0", children: _jsx("div", { className: "truncate text-sm font-medium text-foreground", children: title }) }), _jsxs(Badge, { variant: "outline", className: "shrink-0", children: [result.messageCount, " ", result.messageCount === 1 ? "message" : "messages"] })] }), _jsxs("div", { className: "mt-2 flex items-center justify-between gap-3 text-[11px] text-muted-foreground", children: [_jsx("span", { className: "min-w-0 truncate", children: result.ownerEmail }), _jsx("span", { className: "shrink-0", children: formatRelativeDate(result.updatedAt) })] })] }));
|
|
114
266
|
}
|
|
115
267
|
function FailureCard({ failure, selected, onSelect, }) {
|
|
116
268
|
const t = useT();
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
failure.abortReason ||
|
|
120
|
-
failure.status;
|
|
269
|
+
const diagnosis = runDiagnosis(failure);
|
|
270
|
+
const title = displayListTitle(failure.threadTitle, failure.threadPreview, "Agent run");
|
|
121
271
|
const statusLabel = failure.status === "errored"
|
|
122
272
|
? t("dispatch.pages.threadDebugErrored", {
|
|
123
273
|
defaultValue: "Errored",
|
|
@@ -131,11 +281,9 @@ function FailureCard({ failure, selected, onSelect, }) {
|
|
|
131
281
|
defaultValue: "Truncated",
|
|
132
282
|
})
|
|
133
283
|
: failure.status;
|
|
134
|
-
return (_jsxs("button", { type: "button", onClick: onSelect, className: cn("w-full
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
? null
|
|
138
|
-
: ` · ${formatDuration(failure.durationMs)}`] })] }), _jsx("span", { className: "sr-only", children: t("dispatch.pages.threadDebugInspectFailure", {
|
|
284
|
+
return (_jsxs("button", { type: "button", onClick: onSelect, "aria-current": selected ? "true" : undefined, className: cn("group w-full border-b px-4 py-3 text-left transition-[background-color,border-color] last:border-b-0", selected ? "bg-accent/70" : "hover:bg-muted/50"), children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx("span", { "aria-hidden": "true", className: cn("mt-1.5 size-2 shrink-0 rounded-full", failure.status === "aborted"
|
|
285
|
+
? "bg-muted-foreground/50"
|
|
286
|
+
: "bg-destructive") }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate text-sm font-medium text-foreground", children: title }), _jsx("div", { className: "mt-0.5 truncate text-xs text-muted-foreground", children: failureSourceLabel(failure) })] }), _jsx("span", { className: "shrink-0 text-[11px] text-muted-foreground", children: formatRelativeDate(failure.completedAt ?? failure.startedAt) })] }), _jsxs("div", { className: "mt-2 flex min-w-0 items-center gap-2 text-xs", children: [_jsx("span", { className: "truncate font-medium text-foreground", children: diagnosis.title }), _jsx("span", { className: "shrink-0 text-muted-foreground", children: "\u00B7" }), _jsx("span", { className: "shrink-0 text-muted-foreground", children: statusLabel })] }), failure.durationMs == null ? null : (_jsx("div", { className: "mt-1 text-[11px] text-muted-foreground", children: formatDuration(failure.durationMs) }))] })] }), _jsx("span", { className: "sr-only", children: t("dispatch.pages.threadDebugInspectFailure", {
|
|
139
287
|
defaultValue: "Inspect failed run",
|
|
140
288
|
}) })] }));
|
|
141
289
|
}
|
|
@@ -143,8 +291,28 @@ function MessageBlock({ message }) {
|
|
|
143
291
|
const tools = toolParts(message);
|
|
144
292
|
return (_jsxs("div", { className: "rounded-lg bg-card", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 border-b px-3 py-2", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx(Badge, { variant: message.role === "assistant" ? "default" : "secondary", children: message.role }), _jsx("span", { className: "truncate text-sm font-medium text-foreground", children: messageTitle(message) })] }), _jsxs("div", { className: "flex items-center gap-2 text-[11px] text-muted-foreground", children: [message.attachments.length > 0 ? (_jsxs(Badge, { variant: "outline", children: [message.attachments.length, " files"] })) : null, _jsx("span", { children: formatDate(message.createdAt) })] })] }), _jsxs("div", { className: "space-y-3 px-3 py-3", children: [message.text ? (_jsx("div", { className: "whitespace-pre-wrap break-words text-sm leading-relaxed text-foreground", children: message.text })) : (_jsx("div", { className: "text-sm text-muted-foreground", children: "No text content" })), tools.length > 0 ? (_jsx("div", { className: "space-y-2", children: tools.map((tool, index) => (_jsxs("details", { className: "rounded-md border bg-muted/30 px-3 py-2", children: [_jsx("summary", { className: "cursor-pointer text-xs font-medium text-foreground", children: tool.toolName ?? tool.name ?? "tool-call" }), _jsx(RawBlock, { value: tool, className: "mt-2 max-h-72" })] }, `${message.id ?? message.index}-tool-${index}`))) })) : null] })] }));
|
|
145
293
|
}
|
|
294
|
+
function EvidenceStat({ label, value }) {
|
|
295
|
+
return (_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "text-[11px] text-muted-foreground", children: label }), _jsx("div", { className: "mt-1 truncate text-sm font-medium text-foreground", children: value })] }));
|
|
296
|
+
}
|
|
297
|
+
function DiagnosisPanel({ run, eventCount, toolCount, }) {
|
|
298
|
+
const diagnosis = runDiagnosis(run);
|
|
299
|
+
const isStoppedByUser = diagnosis.code === "aborted:user";
|
|
300
|
+
const isScheduled = run.id?.startsWith("job-") || run.dispatchMode === "background-processing";
|
|
301
|
+
const workerClaimed = run.dispatchMode === "background-processing";
|
|
302
|
+
const staleThreshold = isScheduled
|
|
303
|
+
? workerClaimed
|
|
304
|
+
? "45s after claim"
|
|
305
|
+
: "90s before claim"
|
|
306
|
+
: "15s";
|
|
307
|
+
return (_jsxs("section", { className: cn("border-b px-5 py-4", isStoppedByUser ? "bg-muted/20" : "bg-destructive/[0.04]"), "aria-label": "Run diagnosis", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx("span", { "aria-hidden": "true", className: cn("mt-1.5 size-2 shrink-0 rounded-full", isStoppedByUser ? "bg-muted-foreground/50" : "bg-destructive") }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-2", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold text-foreground", children: diagnosis.title }), _jsx("p", { className: "mt-1 max-w-3xl text-sm leading-relaxed text-muted-foreground", children: diagnosis.summary })] }), _jsx("span", { className: "font-mono text-[11px] text-muted-foreground", children: diagnosis.code })] }), _jsxs("div", { className: "mt-3 flex items-start gap-2 text-xs text-foreground", children: [_jsx(IconInfoCircle, { className: "mt-0.5 size-3.5 shrink-0 text-muted-foreground" }), _jsxs("span", { children: [_jsx("span", { className: "font-medium", children: "Next check:" }), " ", diagnosis.nextStep] })] })] })] }), _jsxs("div", { className: "mt-4 grid gap-3 border-t pt-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-7", children: [_jsx(EvidenceStat, { label: "Run type", value: isScheduled ? "Scheduled job" : "Interactive chat" }), _jsx(EvidenceStat, { label: "Worker claim", value: workerClaimed ? "Claimed" : "Not recorded" }), _jsx(EvidenceStat, { label: "Last heartbeat", value: formatDate(run.heartbeatAt) }), _jsx(EvidenceStat, { label: "Last progress", value: formatDate(run.lastProgressAt) }), _jsx(EvidenceStat, { label: "Stale threshold", value: staleThreshold }), _jsx(EvidenceStat, { label: "Recovery payload", value: run.hasDispatchPayload ? "Retained" : "Not retained" }), _jsx(EvidenceStat, { label: "Worker stage", value: diagnosticStage(run.workerStage) ||
|
|
308
|
+
diagnosticStage(run.diagStage) ||
|
|
309
|
+
"Not recorded" })] }), _jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [_jsxs("span", { children: ["Evidence:", " ", _jsx("span", { className: "font-medium text-foreground", children: eventCount.toLocaleString() }), " ", "retained events"] }), _jsx("span", { "aria-hidden": "true", children: "\u00B7" }), _jsxs("span", { children: [_jsx("span", { className: "font-medium text-foreground", children: toolCount.toLocaleString() }), " ", "tool starts"] }), _jsx("span", { "aria-hidden": "true", children: "\u00B7" }), _jsx("span", { children: "Liveness uses the newer heartbeat or progress timestamp." })] })] }));
|
|
310
|
+
}
|
|
311
|
+
function RunTimeline({ run }) {
|
|
312
|
+
const summary = summarizeEvents(run.events);
|
|
313
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-b pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(IconActivity, { className: "size-4 text-muted-foreground" }), _jsxs("span", { className: "text-sm font-medium text-foreground", children: [run.events.length.toLocaleString(), " events retained"] }), _jsxs("span", { className: "text-xs text-muted-foreground", children: [summary.toolStarts, " tool starts"] })] }), _jsxs("span", { className: "text-xs text-muted-foreground", children: ["Started ", formatDate(run.startedAt)] })] }), summary.toolNames.length > 0 ? (_jsx("div", { className: "flex flex-wrap gap-2", children: summary.toolNames.map(([name, count]) => (_jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-md bg-muted/60 px-2 py-1 text-xs text-foreground", children: [_jsx(IconTool, { className: "size-3.5 text-muted-foreground" }), name, " ", _jsxs("span", { className: "text-muted-foreground", children: ["\u00D7", count] })] }, name))) })) : null, _jsx("div", { className: "divide-y rounded-lg border", children: summary.meaningful.length > 0 ? (summary.meaningful.map((entry) => (_jsxs("div", { className: "flex items-start gap-3 px-3 py-2.5 text-xs", children: [_jsxs("span", { className: "w-8 shrink-0 font-mono text-muted-foreground", children: ["#", entry.seq] }), _jsx("span", { className: "min-w-0 flex-1 break-words text-foreground", children: eventLabel(entry.event) })] }, `${run.id}-${entry.seq}`)))) : (_jsx("div", { className: "px-3 py-4 text-sm text-muted-foreground", children: "No summarized events were retained for this run." })) }), _jsxs("details", { className: "rounded-lg border", children: [_jsxs("summary", { className: "flex cursor-pointer list-none items-center justify-between gap-3 px-3 py-2.5 text-xs font-medium text-foreground", children: [_jsx("span", { children: "Show raw event stream" }), _jsxs("span", { className: "font-normal text-muted-foreground", children: [run.events.length.toLocaleString(), " records"] })] }), _jsx("div", { className: "space-y-2 border-t p-3", children: run.events.map((entry) => (_jsxs("details", { className: "rounded-md border bg-muted/20 px-3 py-2", children: [_jsxs("summary", { className: "cursor-pointer text-xs text-foreground", children: ["#", entry.seq, " ", eventLabel(entry.event)] }), _jsx(RawBlock, { value: entry.event, className: "mt-2 max-h-72" })] }, `${run.id}-raw-${entry.seq}`))) })] })] }));
|
|
314
|
+
}
|
|
146
315
|
function ThreadDetail({ detail }) {
|
|
147
|
-
const t = useT();
|
|
148
316
|
const rawBundle = useMemo(() => ({
|
|
149
317
|
thread: detail.thread,
|
|
150
318
|
debug: detail.debug,
|
|
@@ -158,31 +326,24 @@ function ThreadDetail({ detail }) {
|
|
|
158
326
|
evals: detail.evals,
|
|
159
327
|
checkpoints: detail.checkpoints,
|
|
160
328
|
}), [detail]);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}) }), _jsx("div", { className: "mt-0.5 break-words font-mono text-foreground", children: run.dispatchMode || "foreground" })] }), _jsxs("div", { children: [_jsx("div", { className: "text-muted-foreground", children: t("dispatch.pages.threadDebugLastStage", {
|
|
168
|
-
defaultValue: "Last stage",
|
|
169
|
-
}) }), _jsx("div", { className: "mt-0.5 break-words font-mono text-foreground", children: diagnosticStage(run.workerStage) ||
|
|
329
|
+
const primaryRun = detail.runs.find((run) => run.id === detail.lookup?.runId) ??
|
|
330
|
+
detail.runs[0] ??
|
|
331
|
+
null;
|
|
332
|
+
const eventCount = detail.runs.reduce((total, run) => total + run.events.length, 0);
|
|
333
|
+
const toolCount = detail.runs.reduce((total, run) => total + summarizeEvents(run.events).toolStarts, 0);
|
|
334
|
+
return (_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "border-b px-5 py-4", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate text-base font-semibold text-foreground", children: detail.thread.title || detail.thread.preview || detail.thread.id }), _jsxs("div", { className: "mt-1 flex min-w-0 items-center gap-2 text-xs text-muted-foreground", children: [_jsx("span", { className: "truncate font-mono", children: detail.lookup?.runId || detail.thread.id }), detail.lookup?.runId ? (_jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "size-7 shrink-0", title: "Copy run ID", "aria-label": "Copy run ID", onClick: () => void navigator.clipboard?.writeText(detail.lookup?.runId ?? ""), children: _jsx(IconCopy, { className: "size-3.5" }) })) : null] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [primaryRun ? (_jsx(Badge, { variant: primaryRun.status === "errored" ? "destructive" : "secondary", children: primaryRun.status })) : null, _jsx(Badge, { variant: "outline", children: detail.source.label })] })] }), _jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [_jsx("span", { className: "truncate", children: detail.thread.ownerEmail }), _jsx("span", { "aria-hidden": "true", children: "\u00B7" }), _jsxs("span", { children: [detail.messages.length, " messages"] }), _jsx("span", { "aria-hidden": "true", children: "\u00B7" }), _jsxs("span", { children: [detail.runs.length, " runs"] }), _jsx("span", { "aria-hidden": "true", children: "\u00B7" }), _jsxs("span", { children: ["updated ", formatDate(detail.thread.updatedAt)] })] })] }), primaryRun ? (_jsx(DiagnosisPanel, { run: primaryRun, eventCount: eventCount, toolCount: toolCount })) : null, _jsxs(Tabs, { defaultValue: "overview", className: "p-5", children: [_jsxs(TabsList, { className: "h-9", children: [_jsx(TabsTrigger, { value: "overview", children: "Overview" }), _jsx(TabsTrigger, { value: "timeline", children: "Timeline" }), _jsx(TabsTrigger, { value: "transcript", children: "Transcript" }), _jsx(TabsTrigger, { value: "technical", children: "Technical" })] }), _jsxs(TabsContent, { value: "overview", className: "mt-4 space-y-4", children: [_jsxs("div", { className: "flex items-start gap-2 border-b pb-4 text-sm text-muted-foreground", children: [_jsx(IconInfoCircle, { className: "mt-0.5 size-4 shrink-0" }), _jsx("p", { className: "max-w-2xl leading-relaxed", children: "This is the compact readout. Open Timeline for the last meaningful signals, Transcript for persisted messages and tool calls, or Technical for raw records." })] }), _jsxs("div", { className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-3", children: [_jsx(EvidenceStat, { label: "Created", value: formatDate(detail.thread.createdAt) }), _jsx(EvidenceStat, { label: "Updated", value: formatDate(detail.thread.updatedAt) }), _jsx(EvidenceStat, { label: "Source", value: detail.source.label }), _jsx(EvidenceStat, { label: "Messages", value: detail.messages.length.toLocaleString() }), _jsx(EvidenceStat, { label: "Retained events", value: eventCount.toLocaleString() }), _jsx(EvidenceStat, { label: "Tool starts", value: toolCount.toLocaleString() })] }), detail.messages.length === 0 && eventCount > 0 ? (_jsxs("div", { className: "flex items-start gap-2 rounded-lg border bg-muted/20 px-3 py-3 text-sm text-muted-foreground", children: [_jsx(IconActivity, { className: "mt-0.5 size-4 shrink-0" }), _jsxs("span", { children: ["No persisted messages are available, but this run retained", " ", eventCount.toLocaleString(), " execution events. The timeline is the authoritative audit trail for this run."] })] })) : null] }), _jsx(TabsContent, { value: "timeline", className: "mt-4 space-y-5", children: detail.runs.length > 0 ? (detail.runs.map((run) => (_jsxs("section", { className: "border-b pb-5 last:border-b-0 last:pb-0", children: [_jsxs("div", { className: "mb-3 flex flex-wrap items-center gap-2", children: [_jsx(Badge, { variant: run.status === "errored" ? "destructive" : "outline", children: run.status }), _jsx("span", { className: "font-mono text-xs text-muted-foreground", children: run.id }), _jsx("span", { className: "text-xs text-muted-foreground", children: formatDuration(run.durationMs) })] }), _jsx(RunTimeline, { run: run })] }, run.id)))) : (_jsx("div", { className: "rounded-lg border border-dashed px-4 py-8 text-center text-sm text-muted-foreground", children: "No retained runs." })) }), _jsx(TabsContent, { value: "transcript", className: "mt-4 space-y-3", children: detail.messages.length > 0 ? (detail.messages.map((message) => (_jsx(MessageBlock, { message: message }, message.id ?? `message-${message.index}`)))) : (_jsxs("div", { className: "rounded-lg border border-dashed px-4 py-8 text-center text-sm text-muted-foreground", children: [_jsx("div", { children: "No persisted messages." }), eventCount > 0 ? (_jsx("div", { className: "mt-1", children: "Open Timeline to audit the retained execution events." })) : null] })) }), _jsxs(TabsContent, { value: "technical", className: "mt-4 space-y-3", children: [_jsxs("details", { className: "rounded-lg border", children: [_jsxs("summary", { className: "flex cursor-pointer list-none items-center justify-between gap-3 px-3 py-3 text-sm font-medium text-foreground", children: [_jsx("span", { children: "Run records" }), _jsxs("span", { className: "text-xs font-normal text-muted-foreground", children: [detail.runs.length, " ", detail.runs.length === 1 ? "run" : "runs"] })] }), _jsx("div", { className: "space-y-4 border-t p-3", children: detail.runs.length > 0 ? (detail.runs.map((run) => (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Badge, { variant: run.status === "errored" ? "destructive" : "outline", children: run.status }), _jsx("span", { className: "break-all font-mono text-xs text-foreground", children: run.id })] }), _jsxs("div", { className: "grid gap-3 text-xs sm:grid-cols-2 xl:grid-cols-3", children: [_jsx(EvidenceStat, { label: "Failure code", value: run.errorCode || "n/a" }), _jsx(EvidenceStat, { label: "Terminal reason", value: run.terminalReason || run.abortReason || "n/a" }), _jsx(EvidenceStat, { label: "Dispatch mode", value: run.dispatchMode || "foreground" }), _jsx(EvidenceStat, { label: "Last stage", value: diagnosticStage(run.workerStage) ||
|
|
170
335
|
diagnosticStage(run.diagStage) ||
|
|
171
|
-
"n/a" })
|
|
172
|
-
defaultValue: "Duration",
|
|
173
|
-
}) }), _jsx("div", { className: "mt-0.5 text-foreground", children: formatDuration(run.durationMs ??
|
|
336
|
+
"n/a" }), _jsx(EvidenceStat, { label: "Duration", value: formatDuration(run.durationMs ??
|
|
174
337
|
(run.completedAt == null
|
|
175
338
|
? null
|
|
176
|
-
: run.completedAt - run.startedAt)) })] }),
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
checkpoints: detail.checkpoints,
|
|
185
|
-
} })] })] }) }), _jsxs(TabsContent, { value: "raw", className: "mt-4 space-y-4", children: [_jsx(RawBlock, { value: rawBundle }), _jsx(RawBlock, { value: detail.rawThreadData })] })] })] }));
|
|
339
|
+
: run.completedAt - run.startedAt)) }), _jsx(EvidenceStat, { label: "Last progress", value: formatDate(run.lastProgressAt ?? run.heartbeatAt) }), _jsx(EvidenceStat, { label: "In-flight marker", value: formatDate(run.inFlightSince) }), _jsx(EvidenceStat, { label: "Recovery payload", value: run.hasDispatchPayload ? "retained" : "not retained" })] }), run.errorDetail ? (_jsx("div", { className: "rounded-md bg-destructive/10 px-3 py-2 text-xs leading-relaxed text-destructive", children: run.errorDetail })) : null] }, run.id)))) : (_jsx("div", { className: "text-sm text-muted-foreground", children: "No retained run records." })) })] }), _jsxs("details", { className: "rounded-lg border", children: [_jsxs("summary", { className: "flex cursor-pointer list-none items-center justify-between gap-3 px-3 py-3 text-sm font-medium text-foreground", children: [_jsx("span", { children: "Traces, feedback, and evaluations" }), _jsx("span", { className: "text-xs font-normal text-muted-foreground", children: "diagnostic records" })] }), _jsxs("div", { className: "grid gap-4 border-t p-3 lg:grid-cols-2", children: [_jsxs("div", { children: [_jsx("div", { className: "mb-2 text-xs font-medium text-foreground", children: "Debug runs" }), _jsx(RawBlock, { value: detail.debugRuns.length > 0
|
|
340
|
+
? detail.debugRuns
|
|
341
|
+
: (detail.debug ?? {}) })] }), _jsxs("div", { children: [_jsx("div", { className: "mb-2 text-xs font-medium text-foreground", children: "Trace summaries" }), _jsx(RawBlock, { value: detail.traces.summaries })] }), _jsxs("div", { children: [_jsx("div", { className: "mb-2 text-xs font-medium text-foreground", children: "Trace spans" }), _jsx(RawBlock, { value: detail.traces.spans })] }), _jsxs("div", { children: [_jsx("div", { className: "mb-2 text-xs font-medium text-foreground", children: "Feedback and evals" }), _jsx(RawBlock, { value: {
|
|
342
|
+
feedback: detail.feedback,
|
|
343
|
+
satisfaction: detail.satisfaction,
|
|
344
|
+
evals: detail.evals,
|
|
345
|
+
checkpoints: detail.checkpoints,
|
|
346
|
+
} })] })] })] }), _jsxs("details", { className: "rounded-lg border", children: [_jsxs("summary", { className: "flex cursor-pointer list-none items-center justify-between gap-3 px-3 py-3 text-sm font-medium text-foreground", children: [_jsx("span", { children: "Raw thread bundle" }), _jsx("span", { className: "text-xs font-normal text-muted-foreground", children: "JSON" })] }), _jsxs("div", { className: "space-y-3 border-t p-3", children: [_jsx(RawBlock, { value: rawBundle }), _jsx(RawBlock, { value: detail.rawThreadData })] })] })] })] })] }));
|
|
186
347
|
}
|
|
187
348
|
export default function ThreadDebugRoute() {
|
|
188
349
|
const t = useT();
|
|
@@ -193,11 +354,13 @@ export default function ThreadDebugRoute() {
|
|
|
193
354
|
const ownerEmail = routeSearchParams.get("owner") || "";
|
|
194
355
|
const query = routeSearchParams.get("query") || "";
|
|
195
356
|
const status = parseFailureStatus(routeSearchParams.get("status"));
|
|
357
|
+
const regime = parseFailureRegime(routeSearchParams.get("regime"));
|
|
196
358
|
const range = parseFailureRange(routeSearchParams.get("range"));
|
|
197
359
|
const runId = routeSearchParams.get("runId") || "";
|
|
198
360
|
const threadId = routeSearchParams.get("threadId") || "";
|
|
199
361
|
const inspectSourceId = routeSearchParams.get("inspectSource") || "";
|
|
200
362
|
const [lookupId, setLookupId] = useState("");
|
|
363
|
+
const [searchFocused, setSearchFocused] = useState(false);
|
|
201
364
|
function updateRouteState(updates) {
|
|
202
365
|
const next = new URLSearchParams(routeSearchParams);
|
|
203
366
|
for (const [key, value] of Object.entries(updates)) {
|
|
@@ -209,17 +372,30 @@ export default function ThreadDebugRoute() {
|
|
|
209
372
|
setRouteSearchParams(next, { replace: true });
|
|
210
373
|
}
|
|
211
374
|
const sourcesQuery = useActionQuery("list-agent-thread-sources", {});
|
|
212
|
-
const { data: sourcesData
|
|
375
|
+
const { data: sourcesData } = sourcesQuery;
|
|
213
376
|
const sources = sourcesData?.sources ?? [];
|
|
214
377
|
const failureParams = useMemo(() => ({
|
|
215
378
|
sourceId,
|
|
216
379
|
ownerEmail: ownerEmail.trim() || undefined,
|
|
217
380
|
status,
|
|
381
|
+
regime,
|
|
218
382
|
lookbackHours: FAILURE_RANGE_HOURS[range],
|
|
219
383
|
limit: 25,
|
|
220
|
-
}), [ownerEmail, range, sourceId, status]);
|
|
384
|
+
}), [ownerEmail, range, regime, sourceId, status]);
|
|
221
385
|
const { data: failuresData, isLoading: failuresLoading, error: failuresError, refetch: refetchFailures, } = useActionQuery("list-agent-run-failures", failureParams, { enabled: mode === "failures" });
|
|
222
|
-
const failures = failuresData?.failures ??
|
|
386
|
+
const failures = failuresData?.failures ?? EMPTY_FAILURES;
|
|
387
|
+
const failurePatterns = useMemo(() => {
|
|
388
|
+
const patterns = new Map();
|
|
389
|
+
for (const failure of failures) {
|
|
390
|
+
const diagnosis = runDiagnosis(failure);
|
|
391
|
+
const current = patterns.get(diagnosis.code);
|
|
392
|
+
patterns.set(diagnosis.code, {
|
|
393
|
+
label: diagnosis.title,
|
|
394
|
+
count: (current?.count ?? 0) + 1,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
return [...patterns.values()].sort((a, b) => b.count - a.count);
|
|
398
|
+
}, [failures]);
|
|
223
399
|
const unavailableFailureSources = (failuresData?.sources ?? []).filter((source) => source.status !== "ok");
|
|
224
400
|
const failureSourceStatusLabels = {
|
|
225
401
|
ok: "ok",
|
|
@@ -238,11 +414,18 @@ export default function ThreadDebugRoute() {
|
|
|
238
414
|
const searchParams = useMemo(() => ({
|
|
239
415
|
sourceId: threadSourceId,
|
|
240
416
|
query: query.trim() || undefined,
|
|
241
|
-
ownerEmail: ownerEmail.trim() || undefined,
|
|
242
417
|
limit: 25,
|
|
243
|
-
}), [
|
|
418
|
+
}), [query, threadSourceId]);
|
|
244
419
|
const { data: searchData, isLoading: searchLoading, error: searchError, refetch: refetchSearch, } = useActionQuery("search-agent-threads", searchParams, { enabled: mode === "threads" });
|
|
245
420
|
const searchThreads = searchData?.threads ?? [];
|
|
421
|
+
const ownerEmailSuggestions = useMemo(() => {
|
|
422
|
+
const emailQuery = query.trim().includes("@")
|
|
423
|
+
? query.trim().toLowerCase()
|
|
424
|
+
: "";
|
|
425
|
+
return [...new Set(searchThreads.map((thread) => thread.ownerEmail))]
|
|
426
|
+
.filter((email) => !emailQuery || email.toLowerCase().includes(emailQuery))
|
|
427
|
+
.slice(0, 8);
|
|
428
|
+
}, [query, searchThreads]);
|
|
246
429
|
const detailParams = useMemo(() => ({
|
|
247
430
|
sourceId: detailSourceId,
|
|
248
431
|
...(runId ? { runId } : { threadId }),
|
|
@@ -254,14 +437,11 @@ export default function ThreadDebugRoute() {
|
|
|
254
437
|
const { data: detail, isLoading: detailLoading, error: detailError, refetch: refetchDetail, } = useActionQuery("get-agent-thread-debug", detailParams, {
|
|
255
438
|
enabled: Boolean(runId || threadId),
|
|
256
439
|
});
|
|
257
|
-
const
|
|
258
|
-
const detailPane = (_jsxs("section", { className: "min-w-0", children: [detailError ? (_jsx(ActionQueryError, { error: detailError, onRetry: () => void refetchDetail() })) : null, detailLoading ? (_jsxs("div", { className: "rounded-lg bg-card p-4", children: [_jsx(Skeleton, { className: "h-6 w-72" }), _jsx(Skeleton, { className: "mt-3 h-4 w-96" }), _jsx(Skeleton, { className: "mt-6 h-[520px] w-full" })] })) : detail ? (_jsx(ThreadDetail, { detail: detail })) : (_jsxs("div", { className: "flex min-h-[520px] flex-col items-center justify-center rounded-lg border border-dashed bg-card px-4 text-center text-sm text-muted-foreground", children: [_jsx(IconFileSearch, { className: "mb-2 size-5" }), t("dispatch.pages.threadDebugSelectPrompt", {
|
|
259
|
-
defaultValue: "Select a failed run or thread to inspect.",
|
|
260
|
-
})] }))] }));
|
|
440
|
+
const detailPane = (_jsxs("section", { className: "min-w-0", children: [detailError ? (_jsx(ActionQueryError, { error: detailError, onRetry: () => void refetchDetail() })) : null, detailLoading ? (_jsxs("div", { className: "p-5", children: [_jsx(Skeleton, { className: "h-6 w-72" }), _jsx(Skeleton, { className: "mt-3 h-4 w-96" }), _jsx(Skeleton, { className: "mt-6 h-[520px] w-full" })] })) : detail ? (_jsx(ThreadDetail, { detail: detail })) : (_jsxs("div", { className: "flex min-h-[520px] flex-col items-center justify-center px-5 text-center text-sm text-muted-foreground", children: [_jsx(IconFileSearch, { className: "mb-2 size-5" }), _jsx("div", { className: "font-medium text-foreground", children: "Choose a run to inspect" }), _jsx("div", { className: "mt-1 max-w-xs leading-relaxed", children: "See the diagnosis first, then open the timeline or technical evidence." })] }))] }));
|
|
261
441
|
return (_jsx(DispatchShell, { title: t("dispatch.pages.threadDebugTitle", {
|
|
262
442
|
defaultValue: "Thread Debug",
|
|
263
443
|
}), description: t("dispatch.pages.threadDebugDescription", {
|
|
264
|
-
defaultValue: "
|
|
444
|
+
defaultValue: "Find the failure pattern, then inspect the evidence.",
|
|
265
445
|
}), children: _jsxs("div", { className: "space-y-4", children: [sourcesQuery.isError ? (_jsx(ActionQueryError, { error: sourcesQuery.error, onRetry: () => void sourcesQuery.refetch() })) : null, _jsxs(Tabs, { value: mode, onValueChange: (value) => {
|
|
266
446
|
const nextMode = parseMode(value);
|
|
267
447
|
updateRouteState({
|
|
@@ -277,7 +457,9 @@ export default function ThreadDebugRoute() {
|
|
|
277
457
|
defaultValue: "Failed runs",
|
|
278
458
|
}) }), _jsx(TabsTrigger, { value: "threads", children: t("dispatch.pages.threadDebugThreads", {
|
|
279
459
|
defaultValue: "Threads",
|
|
280
|
-
}) })] }), _jsxs(TabsContent, { value: "failures", className: "mt-4 space-y-4", children: [_jsxs("section", { className: "
|
|
460
|
+
}) })] }), _jsxs(TabsContent, { value: "failures", className: "mt-4 space-y-4", children: [_jsxs("section", { className: "border-b pb-4", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold text-foreground", children: "Run health" }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Start with the dominant failure pattern, then open one run." })] }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => void refetchFailures(), "aria-label": t("dispatch.pages.threadDebugRefreshFailures", {
|
|
461
|
+
defaultValue: "Refresh failed runs",
|
|
462
|
+
}), children: _jsx(IconRefresh, { className: "size-4" }) })] }), _jsxs("div", { className: "mt-3 grid gap-2 sm:grid-cols-2 xl:grid-cols-[200px_minmax(180px,1fr)_180px_160px_150px]", children: [_jsxs(Select, { value: sourceId, onValueChange: (value) => updateRouteState({
|
|
281
463
|
source: value,
|
|
282
464
|
runId: null,
|
|
283
465
|
threadId: null,
|
|
@@ -286,7 +468,9 @@ export default function ThreadDebugRoute() {
|
|
|
286
468
|
defaultValue: "Source",
|
|
287
469
|
}) }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: t("dispatch.pages.threadDebugAllSources", {
|
|
288
470
|
defaultValue: "All sources",
|
|
289
|
-
}) }), sources.map((source) => (_jsx(SelectItem, { value: source.id, children: source.label }, source.id)))] })] }), _jsx(Input, { value: ownerEmail, onChange: (event) => updateRouteState({ owner: event.target.value }),
|
|
471
|
+
}) }), sources.map((source) => (_jsx(SelectItem, { value: source.id, children: source.label }, source.id)))] })] }), _jsx(Input, { value: ownerEmail, onChange: (event) => updateRouteState({ owner: event.target.value }), "aria-label": t("dispatch.pages.threadDebugOwner", {
|
|
472
|
+
defaultValue: "Owner email",
|
|
473
|
+
}), placeholder: t("dispatch.pages.threadDebugOwner", {
|
|
290
474
|
defaultValue: "Owner email",
|
|
291
475
|
}) }), _jsxs(Select, { value: status, onValueChange: (value) => updateRouteState({
|
|
292
476
|
status: parseFailureStatus(value),
|
|
@@ -302,7 +486,11 @@ export default function ThreadDebugRoute() {
|
|
|
302
486
|
defaultValue: "Aborted",
|
|
303
487
|
}) }), _jsx(SelectItem, { value: "truncated", children: t("dispatch.pages.threadDebugTruncated", {
|
|
304
488
|
defaultValue: "Truncated",
|
|
305
|
-
}) })] })] }), _jsxs(Select, { value:
|
|
489
|
+
}) })] })] }), _jsxs(Select, { value: regime, onValueChange: (value) => updateRouteState({
|
|
490
|
+
regime: parseFailureRegime(value),
|
|
491
|
+
runId: null,
|
|
492
|
+
inspectSource: null,
|
|
493
|
+
}), children: [_jsx(SelectTrigger, { "aria-label": "Run type", children: _jsx(SelectValue, { placeholder: "Run type" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "All run types" }), _jsx(SelectItem, { value: "interactive", children: "Interactive chats" }), _jsx(SelectItem, { value: "scheduled", children: "Scheduled jobs" })] })] }), _jsxs(Select, { value: range, onValueChange: (value) => updateRouteState({
|
|
306
494
|
range: parseFailureRange(value),
|
|
307
495
|
runId: null,
|
|
308
496
|
inspectSource: null,
|
|
@@ -314,83 +502,65 @@ export default function ThreadDebugRoute() {
|
|
|
314
502
|
defaultValue: "Last 7 days",
|
|
315
503
|
}) }), _jsx(SelectItem, { value: "30d", children: t("dispatch.pages.threadDebugRange30d", {
|
|
316
504
|
defaultValue: "Last 30 days",
|
|
317
|
-
}) })] })] })] }), _jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-2 text-xs text-muted-foreground", children: [_jsxs("span", { children: [
|
|
318
|
-
defaultValue: "failed runs",
|
|
319
|
-
})] }), _jsx("span", { children: "\u00B7" }), _jsx("span", { children: failuresData?.access?.scope ??
|
|
505
|
+
}) })] })] })] }), _jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-2 text-xs text-muted-foreground", children: [_jsxs("span", { className: "font-medium text-foreground", children: [failures.length, " loaded", " ", failures.length === 1 ? "run" : "runs"] }), _jsx("span", { children: "\u00B7" }), _jsx("span", { children: failuresData?.access?.scope ??
|
|
320
506
|
t("dispatch.pages.threadDebugCurrentScope", {
|
|
321
507
|
defaultValue: "current scope",
|
|
322
|
-
}) }), failuresData?.partial ? (_jsx(Badge, { variant: "outline", children: t("dispatch.pages.threadDebugPartialResults", {
|
|
508
|
+
}) }), failurePatterns.slice(0, 3).map((pattern) => (_jsxs(Badge, { variant: "outline", children: [pattern.count, " ", pattern.label] }, pattern.label))), failuresData?.partial ? (_jsx(Badge, { variant: "outline", children: t("dispatch.pages.threadDebugPartialResults", {
|
|
323
509
|
defaultValue: "Partial results",
|
|
324
510
|
}) })) : null] }), unavailableFailureSources.length > 0 ? (_jsxs("div", { className: "mt-2 flex items-start gap-1.5 text-xs text-muted-foreground", children: [_jsx(IconAlertTriangle, { className: "mt-0.5 size-3.5 shrink-0" }), _jsxs("span", { children: [t("dispatch.pages.threadDebugUnavailableSources", {
|
|
325
511
|
defaultValue: "Unavailable sources:",
|
|
326
512
|
}), " ", unavailableFailureSources
|
|
327
513
|
.map(({ source, status: sourceStatus }) => `${source.label} (${failureSourceStatusLabels[sourceStatus]})`)
|
|
328
|
-
.join(", ")] })] })) : null] }), failuresError ? (_jsx(ActionQueryError, { error: failuresError, onRetry: () => void refetchFailures() })) : null, _jsxs("div", { className: "grid
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
defaultValue: "
|
|
346
|
-
}) }) }), _jsxs(
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
})] })] }), _jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-2 text-xs text-muted-foreground", children: [sourcesLoading ? _jsx(Skeleton, { className: "h-5 w-32" }) : null, selectedSource ? (_jsx(SourceBadge, { source: selectedSource })) : null, selectedSource?.databaseUrlEnv ? (_jsx(Badge, { variant: "outline", className: "font-mono", children: selectedSource.databaseUrlEnv })) : null, sourcesData?.access ? (_jsxs("span", { children: [sourcesData.access.viewerEmail, " \u00B7", " ", sourcesData.access.canInspectAll
|
|
374
|
-
? t("dispatch.pages.threadDebugAdminScope", {
|
|
375
|
-
defaultValue: "admin scope",
|
|
376
|
-
})
|
|
377
|
-
: t("dispatch.pages.threadDebugOwnScope", {
|
|
378
|
-
defaultValue: "own scope",
|
|
379
|
-
})] })) : null] })] }), searchError ? (_jsx(ActionQueryError, { error: searchError, onRetry: () => void refetchSearch() })) : null, _jsxs("div", { className: "grid gap-4 xl:grid-cols-[380px_1fr]", children: [_jsxs("section", { className: "min-h-[520px] rounded-lg bg-card", children: [_jsxs("div", { className: "flex items-center justify-between border-b px-4 py-3", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-semibold text-foreground", children: t("dispatch.pages.threadDebugThreads", {
|
|
380
|
-
defaultValue: "Threads",
|
|
381
|
-
}) }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [searchData?.count ?? 0, " ", t("dispatch.pages.threadDebugResults", {
|
|
382
|
-
defaultValue: "results",
|
|
383
|
-
}), " ", "\u00B7", " ", searchData?.access?.scope ??
|
|
384
|
-
t("dispatch.pages.threadDebugCurrentScope", {
|
|
385
|
-
defaultValue: "current scope",
|
|
386
|
-
})] })] }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => void refetchSearch(), "aria-label": t("dispatch.pages.threadDebugRefreshThreads", {
|
|
387
|
-
defaultValue: "Refresh threads",
|
|
388
|
-
}), children: _jsx(IconRefresh, { className: "size-4" }) })] }), _jsxs("div", { className: "max-h-[760px] space-y-2 overflow-auto p-3", children: [searchLoading ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { className: "h-28 w-full rounded-lg" }), _jsx(Skeleton, { className: "h-28 w-full rounded-lg" }), _jsx(Skeleton, { className: "h-28 w-full rounded-lg" })] })) : null, !searchLoading && searchThreads.length === 0 ? (_jsxs("div", { className: "flex min-h-64 flex-col items-center justify-center rounded-lg border border-dashed px-4 text-center text-sm text-muted-foreground", children: [_jsx(IconDatabase, { className: "mb-2 size-5" }), t("dispatch.pages.threadDebugNoThreads", {
|
|
514
|
+
.join(", ")] })] })) : null] }), failuresError ? (_jsx(ActionQueryError, { error: failuresError, onRetry: () => void refetchFailures() })) : null, _jsx("div", { className: "overflow-hidden rounded-xl border bg-card", children: _jsxs("div", { className: "grid xl:grid-cols-[360px_minmax(0,1fr)]", children: [_jsxs("section", { className: "min-h-[560px] border-b xl:border-b-0 xl:border-r", children: [_jsxs("div", { className: "flex items-center justify-between border-b px-4 py-3", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-semibold text-foreground", children: "Needs attention" }), _jsx("div", { className: "mt-0.5 text-xs text-muted-foreground", children: failurePatterns[0]
|
|
515
|
+
? `${failurePatterns[0].count} ${failurePatterns[0].label.toLowerCase()}`
|
|
516
|
+
: "No active failure pattern" })] }), _jsx("span", { className: "text-xs text-muted-foreground", children: failures.length })] }), _jsxs("div", { className: "max-h-[760px] overflow-auto", children: [failuresLoading ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { className: "mx-4 mt-4 h-16 w-[calc(100%-2rem)]" }), _jsx(Skeleton, { className: "mx-4 mt-2 h-16 w-[calc(100%-2rem)]" }), _jsx(Skeleton, { className: "mx-4 mt-2 h-16 w-[calc(100%-2rem)]" })] })) : null, !failuresLoading && failures.length === 0 ? (_jsxs("div", { className: "flex min-h-64 flex-col items-center justify-center px-4 text-center text-sm text-muted-foreground", children: [_jsx(IconDatabase, { className: "mb-2 size-5" }), t("dispatch.pages.threadDebugNoFailures", {
|
|
517
|
+
defaultValue: "No failed runs found.",
|
|
518
|
+
})] })) : null, failures.map((failure) => (_jsx(FailureCard, { failure: failure, selected: runId === failure.id &&
|
|
519
|
+
detailSourceId === failureSourceId(failure), onSelect: () => updateRouteState({
|
|
520
|
+
inspectSource: failureSourceId(failure),
|
|
521
|
+
runId: failure.id,
|
|
522
|
+
threadId: null,
|
|
523
|
+
}) }, `${failureSourceId(failure)}:${failure.id}`)))] })] }), _jsx("div", { className: "min-w-0", children: detailPane })] }) })] }), _jsxs(TabsContent, { value: "threads", className: "mt-4 space-y-4", children: [_jsx("section", { className: "border-b pb-4", children: _jsxs("div", { className: "grid gap-2 pt-3 lg:grid-cols-2 xl:grid-cols-[auto_180px_minmax(220px,1fr)_auto_auto]", children: [_jsxs(Select, { value: threadSourceId, onValueChange: (value) => updateRouteState({
|
|
524
|
+
source: value,
|
|
525
|
+
runId: null,
|
|
526
|
+
threadId: null,
|
|
527
|
+
inspectSource: null,
|
|
528
|
+
}), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: t("dispatch.pages.threadDebugSource", {
|
|
529
|
+
defaultValue: "Source",
|
|
530
|
+
}) }) }), _jsxs(SelectContent, { children: [sources.map((source) => (_jsx(SelectItem, { value: source.id, children: source.label }, source.id))), sources.length === 0 ? (_jsx(SelectItem, { value: "current", children: t("dispatch.pages.threadDebugCurrentDatabase", {
|
|
531
|
+
defaultValue: "Current Dispatch DB",
|
|
532
|
+
}) })) : null] })] }), _jsxs(Popover, { open: searchFocused && ownerEmailSuggestions.length > 0, onOpenChange: setSearchFocused, children: [_jsx(PopoverAnchor, { asChild: true, children: _jsx("span", { className: "block", children: _jsx(Input, { value: query, onChange: (event) => updateRouteState({ query: event.target.value }), onFocus: () => setSearchFocused(true), placeholder: t("dispatch.pages.threadDebugSearchPlaceholder", {
|
|
533
|
+
defaultValue: "Search threads or email",
|
|
534
|
+
}), "aria-label": "Search threads or email" }) }) }), _jsx(PopoverContent, { align: "start", sideOffset: 6, className: "w-[var(--radix-popover-trigger-width)] p-1", onOpenAutoFocus: (event) => event.preventDefault(), children: _jsx("div", { role: "listbox", "aria-label": "Owner email suggestions", children: ownerEmailSuggestions.map((email) => (_jsx("button", { type: "button", role: "option", "aria-selected": "false", className: "flex w-full items-center rounded-sm px-2 py-1.5 text-left text-sm text-foreground outline-none hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground", onMouseDown: (event) => event.preventDefault(), onClick: () => {
|
|
535
|
+
updateRouteState({ query: email });
|
|
536
|
+
setSearchFocused(false);
|
|
537
|
+
}, children: email }, email))) }) })] }), _jsx(Button, { type: "button", size: "icon", onClick: () => void refetchSearch(), "aria-label": "Search threads or email", title: "Search threads or email", children: _jsx(IconSearch, { className: "size-4" }) }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => void refetchSearch(), "aria-label": t("dispatch.pages.threadDebugRefreshThreads", {
|
|
538
|
+
defaultValue: "Refresh threads",
|
|
539
|
+
}), children: _jsx(IconRefresh, { className: "size-4" }) }), _jsxs("details", { className: "order-first justify-self-start rounded-lg border", children: [_jsxs("summary", { "aria-label": "Advanced lookup", title: "Advanced lookup", className: "flex size-10 cursor-pointer list-none items-center justify-center text-foreground", children: [_jsx(IconAdjustmentsHorizontal, { "aria-hidden": "true", className: "size-4" }), _jsx("span", { className: "sr-only", children: "Advanced lookup" })] }), _jsxs("div", { className: "grid gap-3 border-t p-3 lg:w-80", children: [_jsx(Input, { value: lookupId, onChange: (event) => setLookupId(event.target.value), placeholder: t("dispatch.pages.threadDebugLookupPlaceholder", {
|
|
540
|
+
defaultValue: "Paste thread or request/run ID",
|
|
541
|
+
}), className: "font-mono" }), _jsxs(Button, { type: "button", variant: "outline", onClick: () => {
|
|
542
|
+
const trimmed = lookupId.trim();
|
|
543
|
+
if (!trimmed)
|
|
544
|
+
return;
|
|
545
|
+
updateRouteState(trimmed.startsWith("run-")
|
|
546
|
+
? {
|
|
547
|
+
runId: trimmed,
|
|
548
|
+
threadId: null,
|
|
549
|
+
inspectSource: null,
|
|
550
|
+
}
|
|
551
|
+
: {
|
|
552
|
+
threadId: trimmed,
|
|
553
|
+
runId: null,
|
|
554
|
+
inspectSource: null,
|
|
555
|
+
});
|
|
556
|
+
}, children: [_jsx(IconFileSearch, { className: "size-4" }), t("dispatch.pages.threadDebugInspect", {
|
|
557
|
+
defaultValue: "Inspect",
|
|
558
|
+
})] })] })] })] }) }), searchError ? (_jsx(ActionQueryError, { error: searchError, onRetry: () => void refetchSearch() })) : null, _jsx("div", { className: "overflow-hidden rounded-xl border bg-card", children: _jsxs("div", { className: "grid xl:grid-cols-[360px_minmax(0,1fr)]", children: [_jsx("section", { className: "min-h-[560px] border-b xl:border-b-0 xl:border-r", children: _jsxs("div", { className: "max-h-[760px] overflow-auto", children: [searchLoading ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { className: "mx-4 mt-4 h-16 w-[calc(100%-2rem)]" }), _jsx(Skeleton, { className: "mx-4 mt-2 h-16 w-[calc(100%-2rem)]" }), _jsx(Skeleton, { className: "mx-4 mt-2 h-16 w-[calc(100%-2rem)]" })] })) : null, !searchLoading && searchThreads.length === 0 ? (_jsxs("div", { className: "flex min-h-64 flex-col items-center justify-center px-4 text-center text-sm text-muted-foreground", children: [_jsx(IconDatabase, { className: "mb-2 size-5" }), t("dispatch.pages.threadDebugNoThreads", {
|
|
389
559
|
defaultValue: "No threads found.",
|
|
390
560
|
})] })) : null, searchThreads.map((result) => (_jsx(ResultCard, { result: result, selected: threadId === result.id, onSelect: () => updateRouteState({
|
|
391
561
|
threadId: result.id,
|
|
392
562
|
runId: null,
|
|
393
563
|
inspectSource: null,
|
|
394
|
-
}) }, result.id)))] })
|
|
564
|
+
}) }, result.id)))] }) }), _jsx("div", { className: "min-w-0", children: detailPane })] }) })] })] })] }) }));
|
|
395
565
|
}
|
|
396
566
|
//# sourceMappingURL=thread-debug.js.map
|