@tonyclaw/agent-inspector 3.0.29 → 3.0.31
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/.output/cli.js +791 -191
- package/.output/nitro.json +1 -1
- package/.output/public/assets/CompareDrawer-DB2KggLE.js +1 -0
- package/.output/public/assets/InspectorPet-CfwDw6kD.js +4108 -0
- package/.output/public/assets/ProxyViewerContainer-PcrO96_F.js +126 -0
- package/.output/public/assets/{ReplayDialog-CctnITbt.js → ReplayDialog-3Pl7EKjp.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-BrN0RBbL.js → RequestAnatomy-BcPjcHLX.js} +1 -1
- package/.output/public/assets/ResponseView-DOx51wV3.js +2 -0
- package/.output/public/assets/StreamingChunkSequence-LkYJWM1A.js +1 -0
- package/.output/public/assets/_sessionId-BSg32vC6.js +1 -0
- package/.output/public/assets/{_sessionId-DKM91udC.js → _sessionId-C0CA68QV.js} +1 -1
- package/.output/public/assets/{index-Bq-nqolG.js → index-BpBUqdAB.js} +1 -1
- package/.output/public/assets/index-D93dCgAQ.js +1 -0
- package/.output/public/assets/{index-DPiESBo2.js → index-FV5sxqx3.js} +6 -6
- package/.output/public/assets/index-VUsF4nhf.js +76 -0
- package/.output/public/assets/index-hyQHZeQI.css +1 -0
- package/.output/public/assets/{json-viewer-CfaDJwx8.js → json-viewer-WctIbAh_.js} +1 -1
- package/.output/public/assets/jszip.min-DnSIoTix.js +2 -0
- package/.output/public/assets/qwen-mMn3f5ul.webp +0 -0
- package/.output/server/_libs/jszip.mjs +35 -9
- package/.output/server/_libs/lucide-react.mjs +80 -80
- package/.output/server/_libs/radix-ui__react-dialog.mjs +3 -3
- package/.output/server/_libs/tanstack__react-router.mjs +1 -1
- package/.output/server/{_sessionId-CwGXPD4C.mjs → _sessionId-BHBK3VbF.mjs} +15 -15
- package/.output/server/_sessionId-CyuJ6Kfl.mjs +81 -0
- package/.output/server/_ssr/{CompareDrawer-R_P1-VNr.mjs → CompareDrawer-27amlv_a.mjs} +138 -103
- package/.output/server/_ssr/InspectorPet-CQnGXfLs.mjs +925 -0
- package/.output/server/_ssr/{ProxyViewerContainer-DUgxRNbj.mjs → ProxyViewerContainer-DlAl0uF-.mjs} +1569 -1646
- package/.output/server/_ssr/{ReplayDialog-De9wKKzS.mjs → ReplayDialog-5m2jtlLa.mjs} +18 -18
- package/.output/server/_ssr/{RequestAnatomy-Db4MxUYo.mjs → RequestAnatomy--1WtQNY9.mjs} +58 -58
- package/.output/server/_ssr/{ResponseView-8rDXOiNB.mjs → ResponseView-CgbqS8sr.mjs} +60 -71
- package/.output/server/_ssr/{StreamingChunkSequence-N9teZHkA.mjs → StreamingChunkSequence-BwpnopWx.mjs} +16 -16
- package/.output/server/_ssr/{index-D4DvByuW.mjs → index-CNVi1TzT.mjs} +15 -15
- package/.output/server/_ssr/index-DMCjH_4V.mjs +81 -0
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-BjoHRbjS.mjs → json-viewer-DNGN2uoe.mjs} +58 -58
- package/.output/server/_ssr/{router-BYlePDj8.mjs → router-CgsqLnk5.mjs} +16007 -12632
- package/.output/server/_tanstack-start-manifest_v-BH5f_kWV.mjs +4 -0
- package/.output/server/index.mjs +99 -71
- package/.output/workers/logFinalizer.worker.js +16908 -0
- package/.output/workers/sessionWorkerEntry.js +16904 -0
- package/README.md +21 -5
- package/package.json +6 -2
- package/src/assets/logos/qwen.webp +0 -0
- package/src/cli.ts +85 -10
- package/src/components/OnboardingBanner.tsx +41 -21
- package/src/components/ProxyViewer.tsx +198 -269
- package/src/components/ProxyViewerContainer.tsx +128 -17
- package/src/components/ecosystem/AgentLabDialog.tsx +166 -19
- package/src/components/errors/SafeErrorBoundary.tsx +201 -0
- package/src/components/pi-agent/PiAgentPanel.tsx +209 -35
- package/src/components/pi-agent/piAgentChatLogic.ts +263 -0
- package/src/components/providers/ImportWizardDialog.tsx +18 -42
- package/src/components/providers/ProviderLogo.tsx +1 -1
- package/src/components/providers/ProvidersPanel.tsx +8 -5
- package/src/components/providers/SettingsDialog.tsx +34 -11
- package/src/components/proxy-viewer/ApplicationBar.tsx +63 -0
- package/src/components/proxy-viewer/CompareDrawer.tsx +92 -49
- package/src/components/proxy-viewer/LiveConnectionStatus.tsx +186 -0
- package/src/components/proxy-viewer/LogEntry.tsx +283 -275
- package/src/components/proxy-viewer/RawExportWarning.tsx +77 -0
- package/src/components/proxy-viewer/SessionLoadProgressBar.tsx +61 -0
- package/src/components/proxy-viewer/ThreadConnector.tsx +16 -1
- package/src/components/proxy-viewer/TurnGroup.tsx +13 -2
- package/src/components/proxy-viewer/accessibility.ts +8 -0
- package/src/components/proxy-viewer/lazy.ts +4 -0
- package/src/components/proxy-viewer/liveConnectionState.ts +158 -0
- package/src/components/proxy-viewer/proxyViewerLogic.ts +10 -0
- package/src/components/proxy-viewer/viewerState.ts +2 -6
- package/src/components/ui/select.tsx +1 -1
- package/src/components/ui/tabs.tsx +3 -3
- package/src/components/ui/transient-toast.tsx +7 -6
- package/src/lib/apiClient.ts +17 -2
- package/src/lib/ecosystemContract.ts +34 -0
- package/src/lib/export-logs.ts +1 -1
- package/src/lib/piAgentContract.ts +80 -0
- package/src/lib/providerContract.ts +70 -4
- package/src/lib/providerImportContract.ts +27 -0
- package/src/lib/providerModelMetadata.ts +16 -7
- package/src/lib/resourceLimits.ts +152 -0
- package/src/lib/safeDiagnostic.ts +38 -0
- package/src/lib/useProviders.ts +4 -4
- package/src/mcp/server.ts +39 -6
- package/src/mcp/toolHandlers.ts +131 -4
- package/src/proxy/chunkStorage.ts +20 -6
- package/src/proxy/config.ts +20 -6
- package/src/proxy/dataDir.ts +3 -0
- package/src/proxy/ecosystemExecutionConfirmation.ts +114 -0
- package/src/proxy/ecosystemExecutionRoute.ts +116 -0
- package/src/proxy/ecosystemTasks.ts +48 -0
- package/src/proxy/evidenceExporter.ts +23 -9
- package/src/proxy/groupEvidenceExporter.ts +26 -5
- package/src/proxy/groupStore.ts +13 -3
- package/src/proxy/handler.ts +103 -20
- package/src/proxy/identityProxy.ts +333 -14
- package/src/proxy/logFinalizer.ts +73 -6
- package/src/proxy/logImportUpload.ts +128 -0
- package/src/proxy/logImporter.ts +321 -70
- package/src/proxy/logIndex.ts +16 -6
- package/src/proxy/logger.ts +294 -41
- package/src/proxy/privateDataPath.ts +183 -0
- package/src/proxy/providerScanStore.ts +87 -0
- package/src/proxy/providerSecretStore.ts +58 -19
- package/src/proxy/providers.ts +175 -58
- package/src/proxy/rawStreamCapture.ts +66 -5
- package/src/proxy/runStore.ts +13 -3
- package/src/proxy/runtimeAdmission.ts +52 -0
- package/src/proxy/runtimeHealth.ts +206 -0
- package/src/proxy/runtimeShutdown.ts +75 -0
- package/src/proxy/sessionArchive.ts +15 -2
- package/src/proxy/sessionProcess.ts +19 -0
- package/src/proxy/sessionRuntime.ts +7 -0
- package/src/proxy/shutdownCoordinator.ts +90 -0
- package/src/proxy/sqliteLogIndex.ts +18 -2
- package/src/proxy/store.ts +18 -2
- package/src/routes/__root.tsx +18 -1
- package/src/routes/api/alerts.summary.ts +12 -8
- package/src/routes/api/alerts.ts +27 -9
- package/src/routes/api/ecosystem.packages.$packageId.help.ts +3 -8
- package/src/routes/api/ecosystem.packages.$packageId.install.ts +3 -8
- package/src/routes/api/ecosystem.packages.$packageId.runner-presets.ts +3 -11
- package/src/routes/api/ecosystem.packages.$packageId.upgrade.ts +3 -8
- package/src/routes/api/ecosystem.recipes.$recipeId.run.ts +3 -8
- package/src/routes/api/health.ts +9 -2
- package/src/routes/api/knowledge.project-context.ts +11 -0
- package/src/routes/api/knowledge.search.ts +33 -2
- package/src/routes/api/logs.$id.body.ts +16 -2
- package/src/routes/api/logs.import.ts +7 -18
- package/src/routes/api/logs.stream.ts +147 -69
- package/src/routes/api/logs.ts +55 -14
- package/src/routes/api/providers.$providerId.model-metadata.ts +22 -4
- package/src/routes/api/providers.$providerId.ts +37 -6
- package/src/routes/api/providers.export.ts +34 -16
- package/src/routes/api/providers.import.ts +42 -8
- package/src/routes/api/providers.scan.ts +13 -8
- package/src/routes/api/providers.ts +36 -5
- package/src/routes/api/runs.ts +12 -12
- package/src/routes/api/sessions.ts +15 -8
- package/src/routes/index.tsx +6 -0
- package/src/routes/livez.ts +13 -0
- package/src/routes/readyz.ts +18 -0
- package/src/routes/session/$sessionId.tsx +6 -0
- package/src/services/piAgent.ts +131 -102
- package/src/services/piAgentRetrieval.ts +1180 -0
- package/styles/globals.css +19 -3
- package/.output/public/assets/CompareDrawer-Cb5ccPeX.js +0 -1
- package/.output/public/assets/ProxyViewerContainer-COBe5GTT.js +0 -4234
- package/.output/public/assets/ResponseView-Cqq87lX_.js +0 -3
- package/.output/public/assets/StreamingChunkSequence-CnTa9mCr.js +0 -1
- package/.output/public/assets/index-DTLNVcgc.js +0 -76
- package/.output/public/assets/index-DliqmwUw.css +0 -1
- package/.output/public/assets/qwen-CONDcHqt.png +0 -0
- package/.output/server/_tanstack-start-manifest_v-DMCA8KK3.mjs +0 -4
- package/src/assets/logos/mcp.png +0 -0
- package/src/assets/logos/qwen.png +0 -0
- package/src/components/ui/mcp-logo.tsx +0 -20
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { type JSX } from "react";
|
|
2
|
+
|
|
3
|
+
import { Button } from "../ui/button";
|
|
4
|
+
import { DialogDescription, DialogHeader, DialogTitle } from "../ui/dialog";
|
|
5
|
+
|
|
6
|
+
export const RAW_EXPORT_DIALOG_CLASS_NAME =
|
|
7
|
+
"w-[calc(100%-1rem)] border-warning/30 bg-popover text-popover-foreground sm:max-w-lg";
|
|
8
|
+
|
|
9
|
+
export const RAW_EXPORT_RISK_ITEMS = [
|
|
10
|
+
{
|
|
11
|
+
label: "Headers",
|
|
12
|
+
detail: "request and response headers, including authorization or cookie values",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
label: "Prompts",
|
|
16
|
+
detail: "system instructions, user prompts, model responses, and conversation history",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
label: "Source content",
|
|
20
|
+
detail: "source code, file contents, tool inputs, and tool output",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
label: "Secrets",
|
|
24
|
+
detail: "API keys, access tokens, credentials, and other sensitive values",
|
|
25
|
+
},
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
export type RawExportWarningProps = {
|
|
29
|
+
onCancel: () => void;
|
|
30
|
+
onExportRedacted: () => void;
|
|
31
|
+
onConfirmRaw: () => void;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function RawExportWarning({
|
|
35
|
+
onCancel,
|
|
36
|
+
onExportRedacted,
|
|
37
|
+
onConfirmRaw,
|
|
38
|
+
}: RawExportWarningProps): JSX.Element {
|
|
39
|
+
return (
|
|
40
|
+
<>
|
|
41
|
+
<DialogHeader>
|
|
42
|
+
<DialogTitle>Export unredacted logs?</DialogTitle>
|
|
43
|
+
<DialogDescription>
|
|
44
|
+
Raw exports bypass Agent Inspector redaction and may expose sensitive captured data.
|
|
45
|
+
</DialogDescription>
|
|
46
|
+
</DialogHeader>
|
|
47
|
+
<div
|
|
48
|
+
role="alert"
|
|
49
|
+
className="rounded-md border border-warning/35 bg-warning/10 px-3 py-3 text-sm text-foreground"
|
|
50
|
+
>
|
|
51
|
+
<p className="font-medium">The raw archive may include:</p>
|
|
52
|
+
<ul className="mt-2 list-disc space-y-1 pl-5 text-xs leading-relaxed text-muted-foreground">
|
|
53
|
+
{RAW_EXPORT_RISK_ITEMS.map((item) => (
|
|
54
|
+
<li key={item.label}>
|
|
55
|
+
<span className="font-semibold text-foreground">{item.label}:</span> {item.detail}
|
|
56
|
+
</li>
|
|
57
|
+
))}
|
|
58
|
+
</ul>
|
|
59
|
+
<p className="mt-3 text-xs leading-relaxed text-muted-foreground">
|
|
60
|
+
Only continue if you trust where this file will be stored and everyone it may be shared
|
|
61
|
+
with.
|
|
62
|
+
</p>
|
|
63
|
+
</div>
|
|
64
|
+
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
|
65
|
+
<Button type="button" variant="outline" size="sm" onClick={onCancel}>
|
|
66
|
+
Cancel
|
|
67
|
+
</Button>
|
|
68
|
+
<Button type="button" variant="outline" size="sm" onClick={onExportRedacted}>
|
|
69
|
+
Export redacted instead
|
|
70
|
+
</Button>
|
|
71
|
+
<Button type="button" variant="destructive" size="sm" onClick={onConfirmRaw}>
|
|
72
|
+
Export raw data
|
|
73
|
+
</Button>
|
|
74
|
+
</div>
|
|
75
|
+
</>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type JSX } from "react";
|
|
2
|
+
import { cn } from "../../lib/utils";
|
|
3
|
+
|
|
4
|
+
export type SessionLoadProgress = {
|
|
5
|
+
label: string;
|
|
6
|
+
elapsedMs: number;
|
|
7
|
+
timeoutMs: number;
|
|
8
|
+
loadedLogs: number;
|
|
9
|
+
loadedPages: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function formatDurationSeconds(ms: number): string {
|
|
13
|
+
return `${Math.max(0, Math.ceil(ms / 1000)).toString()}s`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function SessionLoadProgressBar({
|
|
17
|
+
progress,
|
|
18
|
+
}: {
|
|
19
|
+
progress: SessionLoadProgress;
|
|
20
|
+
}): JSX.Element {
|
|
21
|
+
const elapsedRatio = Math.min(1, progress.elapsedMs / progress.timeoutMs);
|
|
22
|
+
const percent = Math.max(4, Math.round(elapsedRatio * 100));
|
|
23
|
+
const remainingMs = Math.max(0, progress.timeoutMs - progress.elapsedMs);
|
|
24
|
+
const nearTimeout = elapsedRatio >= 0.75;
|
|
25
|
+
const details = [
|
|
26
|
+
progress.loadedLogs > 0 ? `${progress.loadedLogs.toString()} logs` : null,
|
|
27
|
+
progress.loadedPages > 0
|
|
28
|
+
? `${progress.loadedPages.toString()} page${progress.loadedPages === 1 ? "" : "s"}`
|
|
29
|
+
: null,
|
|
30
|
+
`${formatDurationSeconds(remainingMs)} before timeout`,
|
|
31
|
+
].filter((item): item is string => item !== null);
|
|
32
|
+
const valueText = `${progress.label}: ${details.join(", ")}`;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="min-w-0 space-y-1 px-1 pb-1">
|
|
36
|
+
<div className="flex min-w-0 items-center justify-between gap-3 font-mono text-[10px] text-muted-foreground">
|
|
37
|
+
<span className="min-w-0 truncate">{progress.label}</span>
|
|
38
|
+
<span className={cn("shrink-0 tabular-nums", nearTimeout && "text-amber-300")}>
|
|
39
|
+
{details.join(" / ")}
|
|
40
|
+
</span>
|
|
41
|
+
</div>
|
|
42
|
+
<div
|
|
43
|
+
role="progressbar"
|
|
44
|
+
aria-label={progress.label}
|
|
45
|
+
aria-valuemin={0}
|
|
46
|
+
aria-valuemax={progress.timeoutMs}
|
|
47
|
+
aria-valuenow={Math.min(progress.elapsedMs, progress.timeoutMs)}
|
|
48
|
+
aria-valuetext={valueText}
|
|
49
|
+
className="h-1.5 overflow-hidden rounded-full bg-white/[0.06]"
|
|
50
|
+
>
|
|
51
|
+
<div
|
|
52
|
+
className={cn(
|
|
53
|
+
"h-full rounded-full transition-all duration-300 motion-reduce:transition-none",
|
|
54
|
+
nearTimeout ? "bg-amber-300/80" : "bg-cyan-300/70",
|
|
55
|
+
)}
|
|
56
|
+
style={{ width: `${percent.toString()}%` }}
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -11,6 +11,8 @@ export type ThreadConnectorProps = {
|
|
|
11
11
|
isTurnStart: boolean;
|
|
12
12
|
/** True when this entry is the turn's only request. */
|
|
13
13
|
isOnlyEntry?: boolean;
|
|
14
|
+
/** True when this is the latest request in a turn that has not reached a real boundary. */
|
|
15
|
+
isOpenTurnTail?: boolean;
|
|
14
16
|
/** Seed for crab variant selection (0-11). */
|
|
15
17
|
crabIndex?: number;
|
|
16
18
|
/** When true the crab is clickable (collapse / expand a turn). */
|
|
@@ -42,6 +44,7 @@ export function ThreadConnector({
|
|
|
42
44
|
isFirst,
|
|
43
45
|
isTurnStart,
|
|
44
46
|
isOnlyEntry = false,
|
|
47
|
+
isOpenTurnTail = false,
|
|
45
48
|
crabIndex = 0,
|
|
46
49
|
collapsible = false,
|
|
47
50
|
onToggle,
|
|
@@ -123,6 +126,18 @@ export function ThreadConnector({
|
|
|
123
126
|
)}
|
|
124
127
|
/>
|
|
125
128
|
</span>
|
|
129
|
+
) : isOpenTurnTail ? (
|
|
130
|
+
<span
|
|
131
|
+
title={isPending ? "Current end of open turn (processing)" : "Current end of open turn"}
|
|
132
|
+
>
|
|
133
|
+
<Crab
|
|
134
|
+
className={cn(
|
|
135
|
+
"size-3.5 text-cyan-300/85",
|
|
136
|
+
isRunning ? "animate-crab-crawl" : "animate-crab-appear",
|
|
137
|
+
"drop-shadow-[0_0_4px_rgba(103,232,249,0.4)]",
|
|
138
|
+
)}
|
|
139
|
+
/>
|
|
140
|
+
</span>
|
|
126
141
|
) : isRunning ? (
|
|
127
142
|
<span title="Processing">
|
|
128
143
|
<Crab className={cn("size-3.5 text-amber-300/80", "animate-crab-crawl")} />
|
|
@@ -134,7 +149,7 @@ export function ThreadConnector({
|
|
|
134
149
|
)}
|
|
135
150
|
|
|
136
151
|
<div className="flex-1 flex justify-center min-h-0">
|
|
137
|
-
{!isBoundary && (
|
|
152
|
+
{!isBoundary && !isOpenTurnTail && (
|
|
138
153
|
<div
|
|
139
154
|
className={cn(
|
|
140
155
|
"w-0.5 h-full",
|
|
@@ -69,7 +69,7 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
69
69
|
const isComplete = lastStop !== null ? isTurnBoundary(lastStop) : false;
|
|
70
70
|
const isPending = entries[lastIdx]?.log.responseStatus === null;
|
|
71
71
|
const isSingleLog = entries.length === 1;
|
|
72
|
-
const collapsible = isTurnCollapsible(entries.length
|
|
72
|
+
const collapsible = isTurnCollapsible(entries.length);
|
|
73
73
|
const [collapsed, setCollapsed] = useState(false);
|
|
74
74
|
|
|
75
75
|
// Auto-collapse when the turn finishes (transitions from incomplete to complete)
|
|
@@ -233,13 +233,23 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
233
233
|
<div className="flex-1 flex justify-center min-h-0">
|
|
234
234
|
<div className="w-px bg-muted-foreground/14 h-full" />
|
|
235
235
|
</div>
|
|
236
|
-
{isComplete
|
|
236
|
+
{isComplete ? (
|
|
237
237
|
<CollapsedCrab
|
|
238
238
|
className={cn(
|
|
239
239
|
"size-3.5 animate-crab-settle text-amber-400",
|
|
240
240
|
"drop-shadow-[0_0_4px_rgba(251,191,36,0.45)]",
|
|
241
241
|
)}
|
|
242
242
|
/>
|
|
243
|
+
) : (
|
|
244
|
+
<span title="Current end of open turn" aria-label="Current end of open turn">
|
|
245
|
+
<CollapsedCrab
|
|
246
|
+
className={cn(
|
|
247
|
+
"size-3.5 text-cyan-300/85",
|
|
248
|
+
isPending ? "animate-crab-crawl" : "animate-crab-appear",
|
|
249
|
+
"drop-shadow-[0_0_4px_rgba(103,232,249,0.4)]",
|
|
250
|
+
)}
|
|
251
|
+
/>
|
|
252
|
+
</span>
|
|
243
253
|
)}
|
|
244
254
|
</div>
|
|
245
255
|
|
|
@@ -343,6 +353,7 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
343
353
|
isFirst={visibleIdx === 0}
|
|
344
354
|
isTurnStart={isTurnStart}
|
|
345
355
|
isOnlyEntry={isSingleLog}
|
|
356
|
+
isOpenTurnTail={visibleIdx === lastIdx && !isComplete}
|
|
346
357
|
crabIndex={log.id % 12}
|
|
347
358
|
collapsible={collapsible && isTurnStart}
|
|
348
359
|
onToggle={toggleCollapse}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function preferredScrollBehavior(reducedMotion: boolean): ScrollBehavior {
|
|
2
|
+
return reducedMotion ? "auto" : "smooth";
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function browserPrefersReducedMotion(): boolean {
|
|
6
|
+
if (typeof window === "undefined") return false;
|
|
7
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
8
|
+
}
|
|
@@ -10,6 +10,10 @@ export const LazyCompareDrawer = lazy(() =>
|
|
|
10
10
|
import("./CompareDrawer").then((m) => ({ default: m.CompareDrawer })),
|
|
11
11
|
);
|
|
12
12
|
|
|
13
|
+
export const LazyInspectorPet = lazy(() =>
|
|
14
|
+
import("../inspector-pet/InspectorPet").then((m) => ({ default: m.InspectorPet })),
|
|
15
|
+
);
|
|
16
|
+
|
|
13
17
|
export const LazyReplayDialog = lazy(() =>
|
|
14
18
|
import("./ReplayDialog").then((m) => ({ default: m.ReplayDialog })),
|
|
15
19
|
);
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
export type LiveConnectionPhase =
|
|
2
|
+
| "connecting"
|
|
3
|
+
| "live"
|
|
4
|
+
| "reconnecting"
|
|
5
|
+
| "stale"
|
|
6
|
+
| "offline"
|
|
7
|
+
| "error";
|
|
8
|
+
|
|
9
|
+
export type LiveConnectionState = {
|
|
10
|
+
status: LiveConnectionPhase;
|
|
11
|
+
lastSuccessAt: number | null;
|
|
12
|
+
retryCount: number;
|
|
13
|
+
errorMessage: string | null;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type LiveConnectionEvent =
|
|
17
|
+
| { type: "connect-requested" }
|
|
18
|
+
| { type: "message-received"; at: number }
|
|
19
|
+
| { type: "connection-lost"; online: boolean; message: string }
|
|
20
|
+
| { type: "stale-timeout" }
|
|
21
|
+
| { type: "browser-offline" }
|
|
22
|
+
| { type: "stop-retrying" }
|
|
23
|
+
| { type: "recoverable-error"; message: string };
|
|
24
|
+
|
|
25
|
+
export function createInitialLiveConnectionState(): LiveConnectionState {
|
|
26
|
+
return {
|
|
27
|
+
status: "connecting",
|
|
28
|
+
lastSuccessAt: null,
|
|
29
|
+
retryCount: 0,
|
|
30
|
+
errorMessage: null,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function reduceLiveConnectionState(
|
|
35
|
+
state: LiveConnectionState,
|
|
36
|
+
event: LiveConnectionEvent,
|
|
37
|
+
): LiveConnectionState {
|
|
38
|
+
switch (event.type) {
|
|
39
|
+
case "connect-requested":
|
|
40
|
+
return {
|
|
41
|
+
...state,
|
|
42
|
+
status:
|
|
43
|
+
state.lastSuccessAt === null
|
|
44
|
+
? "connecting"
|
|
45
|
+
: state.status === "stale"
|
|
46
|
+
? "stale"
|
|
47
|
+
: "reconnecting",
|
|
48
|
+
errorMessage: null,
|
|
49
|
+
};
|
|
50
|
+
case "message-received":
|
|
51
|
+
return {
|
|
52
|
+
status: "live",
|
|
53
|
+
lastSuccessAt: event.at,
|
|
54
|
+
retryCount: 0,
|
|
55
|
+
errorMessage: null,
|
|
56
|
+
};
|
|
57
|
+
case "connection-lost": {
|
|
58
|
+
const retryCount = state.retryCount + 1;
|
|
59
|
+
if (!event.online) {
|
|
60
|
+
return {
|
|
61
|
+
...state,
|
|
62
|
+
status: "offline",
|
|
63
|
+
retryCount,
|
|
64
|
+
errorMessage: "Browser is offline.",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
...state,
|
|
69
|
+
status: state.lastSuccessAt === null ? "error" : "reconnecting",
|
|
70
|
+
retryCount,
|
|
71
|
+
errorMessage: event.message,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
case "stale-timeout":
|
|
75
|
+
if (state.lastSuccessAt === null || state.status === "live" || state.status === "offline") {
|
|
76
|
+
return state;
|
|
77
|
+
}
|
|
78
|
+
return { ...state, status: "stale" };
|
|
79
|
+
case "browser-offline":
|
|
80
|
+
return {
|
|
81
|
+
...state,
|
|
82
|
+
status: "offline",
|
|
83
|
+
errorMessage: "Browser is offline.",
|
|
84
|
+
};
|
|
85
|
+
case "stop-retrying":
|
|
86
|
+
return {
|
|
87
|
+
...state,
|
|
88
|
+
status: "offline",
|
|
89
|
+
errorMessage: "Automatic live updates are paused.",
|
|
90
|
+
};
|
|
91
|
+
case "recoverable-error":
|
|
92
|
+
return {
|
|
93
|
+
...state,
|
|
94
|
+
status: "error",
|
|
95
|
+
errorMessage: event.message,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type LiveEmptyState =
|
|
101
|
+
| "connecting"
|
|
102
|
+
| "empty"
|
|
103
|
+
| "reconnecting"
|
|
104
|
+
| "stale"
|
|
105
|
+
| "offline"
|
|
106
|
+
| "error";
|
|
107
|
+
|
|
108
|
+
export function resolveLiveEmptyState(state: LiveConnectionState | undefined): LiveEmptyState {
|
|
109
|
+
if (state === undefined || state.status === "live") return "empty";
|
|
110
|
+
return state.status;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export type LiveEmptyStateCopy = {
|
|
114
|
+
title: string;
|
|
115
|
+
description: string;
|
|
116
|
+
showConnectionCommands: boolean;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export function getLiveEmptyStateCopy(state: LiveEmptyState): LiveEmptyStateCopy {
|
|
120
|
+
switch (state) {
|
|
121
|
+
case "connecting":
|
|
122
|
+
return {
|
|
123
|
+
title: "Connecting to the live stream...",
|
|
124
|
+
description: "Waiting for the first successful initialization before checking for logs.",
|
|
125
|
+
showConnectionCommands: false,
|
|
126
|
+
};
|
|
127
|
+
case "empty":
|
|
128
|
+
return {
|
|
129
|
+
title: "No logs captured yet.",
|
|
130
|
+
description: "Connect an AI coding tool to capture its first request.",
|
|
131
|
+
showConnectionCommands: true,
|
|
132
|
+
};
|
|
133
|
+
case "reconnecting":
|
|
134
|
+
return {
|
|
135
|
+
title: "Reconnecting to live updates...",
|
|
136
|
+
description: "The stream was interrupted. Agent Inspector is retrying automatically.",
|
|
137
|
+
showConnectionCommands: false,
|
|
138
|
+
};
|
|
139
|
+
case "stale":
|
|
140
|
+
return {
|
|
141
|
+
title: "Live data is stale.",
|
|
142
|
+
description: "No logs are hidden; retry the connection to resume live updates.",
|
|
143
|
+
showConnectionCommands: false,
|
|
144
|
+
};
|
|
145
|
+
case "offline":
|
|
146
|
+
return {
|
|
147
|
+
title: "Live updates are offline.",
|
|
148
|
+
description: "Check the browser and Inspector connection, then retry.",
|
|
149
|
+
showConnectionCommands: false,
|
|
150
|
+
};
|
|
151
|
+
case "error":
|
|
152
|
+
return {
|
|
153
|
+
title: "Live updates could not initialize.",
|
|
154
|
+
description: "The error is recoverable. Retry without reloading the page.",
|
|
155
|
+
showConnectionCommands: false,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CapturedLog } from "../../contracts";
|
|
2
|
+
import type { ExportMode } from "../../lib/export-logs";
|
|
2
3
|
import type { CaptureMode, TimeDisplayFormat } from "../../lib/runtimeConfig";
|
|
3
4
|
import { extractStopReason, type StopReason } from "../../lib/stopReason";
|
|
4
5
|
import { formatTimestampRange } from "../../lib/timeDisplay";
|
|
@@ -23,6 +24,11 @@ export type ConversationLogs = {
|
|
|
23
24
|
logs: CapturedLog[];
|
|
24
25
|
};
|
|
25
26
|
|
|
27
|
+
export const COPYABLE_COMMAND_CONTAINER_CLASS_NAME =
|
|
28
|
+
"flex w-full min-w-0 items-center gap-2 rounded-md border border-border/70 bg-muted/35 px-3 py-2 sm:w-auto sm:max-w-full";
|
|
29
|
+
export const COPYABLE_COMMAND_TEXT_CLASS_NAME =
|
|
30
|
+
"m-0 min-w-0 flex-1 overflow-x-auto font-mono text-xs text-foreground sm:text-sm";
|
|
31
|
+
|
|
26
32
|
export type SessionSlateStats = {
|
|
27
33
|
status: SessionSlateStatus;
|
|
28
34
|
statusLabel: string;
|
|
@@ -74,6 +80,10 @@ export function shouldShowRawExport(captureMode: CaptureMode): boolean {
|
|
|
74
80
|
return captureMode === "full";
|
|
75
81
|
}
|
|
76
82
|
|
|
83
|
+
export function exportRequiresConfirmation(mode: ExportMode): boolean {
|
|
84
|
+
return mode === "raw";
|
|
85
|
+
}
|
|
86
|
+
|
|
77
87
|
export function getExportActionVisibility(
|
|
78
88
|
captureMode: CaptureMode,
|
|
79
89
|
logCount: number,
|
|
@@ -69,12 +69,8 @@ function responseMayContainToolTrace(log: CapturedLog, format: ResolvedLogFormat
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
export function isTurnCollapsible(
|
|
73
|
-
entryCount
|
|
74
|
-
isComplete: boolean,
|
|
75
|
-
isPending: boolean,
|
|
76
|
-
): boolean {
|
|
77
|
-
return entryCount > 1 && isComplete && !isPending;
|
|
72
|
+
export function isTurnCollapsible(entryCount: number): boolean {
|
|
73
|
+
return entryCount > 1;
|
|
78
74
|
}
|
|
79
75
|
|
|
80
76
|
export function buildTurnGroups(logs: CapturedLog[]): TurnGroupData[] {
|
|
@@ -62,7 +62,7 @@ function SelectContent({
|
|
|
62
62
|
<SelectPrimitive.Content
|
|
63
63
|
data-slot="select-content"
|
|
64
64
|
className={cn(
|
|
65
|
-
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
|
65
|
+
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 motion-reduce:data-[state=open]:animate-none motion-reduce:data-[state=closed]:animate-none relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
|
66
66
|
position === "popper" &&
|
|
67
67
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
68
68
|
className,
|
|
@@ -61,9 +61,9 @@ function TabsTrigger({
|
|
|
61
61
|
<TabsPrimitive.Trigger
|
|
62
62
|
data-slot="tabs-trigger"
|
|
63
63
|
className={cn(
|
|
64
|
-
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-
|
|
65
|
-
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:
|
|
66
|
-
"data-[state=active]:
|
|
64
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-muted-foreground hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
65
|
+
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
|
|
66
|
+
"data-[state=active]:border-border data-[state=active]:bg-background data-[state=active]:text-foreground",
|
|
67
67
|
"after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
|
|
68
68
|
className,
|
|
69
69
|
)}
|
|
@@ -80,19 +80,20 @@ export function TransientToast({
|
|
|
80
80
|
<div
|
|
81
81
|
key={notice.id}
|
|
82
82
|
className={cn(
|
|
83
|
-
"pointer-events-auto flex items-start gap-2 rounded-md border px-3 py-2 text-sm shadow-[
|
|
84
|
-
isError
|
|
85
|
-
? "border-red-400/30 bg-[#16090b]/95 text-red-100"
|
|
86
|
-
: "border-cyan-300/25 bg-[#071318]/95 text-cyan-100",
|
|
83
|
+
"pointer-events-auto flex items-start gap-2 rounded-md border bg-popover/95 px-3 py-2 text-sm text-popover-foreground shadow-[0_18px_50px_color-mix(in_oklch,var(--foreground)_16%,transparent)] backdrop-blur",
|
|
84
|
+
isError ? "border-status-danger/35" : "border-status-success/35",
|
|
87
85
|
)}
|
|
88
86
|
>
|
|
89
87
|
<Icon
|
|
90
|
-
className={cn(
|
|
88
|
+
className={cn(
|
|
89
|
+
"mt-0.5 size-4 shrink-0",
|
|
90
|
+
isError ? "text-status-danger" : "text-status-success",
|
|
91
|
+
)}
|
|
91
92
|
/>
|
|
92
93
|
<div className="min-w-0 flex-1 break-words leading-5">{notice.message}</div>
|
|
93
94
|
<button
|
|
94
95
|
type="button"
|
|
95
|
-
className="inline-flex size-6 shrink-0 items-center justify-center rounded-md text-current/70 transition-colors hover:bg-white/10 hover:text-current"
|
|
96
|
+
className="inline-flex size-6 shrink-0 items-center justify-center rounded-md text-current/70 transition-colors hover:bg-white/10 hover:text-current focus-visible:ring-1 focus-visible:ring-current focus-visible:outline-none"
|
|
96
97
|
aria-label="Dismiss notification"
|
|
97
98
|
onClick={onDismiss}
|
|
98
99
|
>
|
package/src/lib/apiClient.ts
CHANGED
|
@@ -5,6 +5,18 @@ const ApiErrorSchema = z.object({
|
|
|
5
5
|
error: z.string(),
|
|
6
6
|
});
|
|
7
7
|
|
|
8
|
+
const MUTATION_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
9
|
+
|
|
10
|
+
export function withBrowserMutationProof(init?: RequestInit): RequestInit | undefined {
|
|
11
|
+
const method = init?.method?.toUpperCase();
|
|
12
|
+
if (method === undefined || !MUTATION_METHODS.has(method)) return init;
|
|
13
|
+
const headers = new Headers(init?.headers);
|
|
14
|
+
if (!headers.has("x-agent-inspector-csrf")) {
|
|
15
|
+
headers.set("x-agent-inspector-csrf", "1");
|
|
16
|
+
}
|
|
17
|
+
return { ...init, headers };
|
|
18
|
+
}
|
|
19
|
+
|
|
8
20
|
/**
|
|
9
21
|
* Parse and validate a JSON response at the browser/server trust boundary.
|
|
10
22
|
*
|
|
@@ -40,7 +52,7 @@ export async function fetchJson<T>(
|
|
|
40
52
|
init?: RequestInit,
|
|
41
53
|
errorFallback?: (response: Response) => string,
|
|
42
54
|
): Promise<T> {
|
|
43
|
-
const response = await fetch(input, init);
|
|
55
|
+
const response = await fetch(input, withBrowserMutationProof(init));
|
|
44
56
|
if (!response.ok) {
|
|
45
57
|
const fallback = errorFallback?.(response) ?? `Request failed with status ${response.status}`;
|
|
46
58
|
throw new Error(await readApiError(response, fallback));
|
|
@@ -88,7 +100,10 @@ export async function fetchWithTimeout(
|
|
|
88
100
|
}
|
|
89
101
|
|
|
90
102
|
try {
|
|
91
|
-
return await fetch(input, {
|
|
103
|
+
return await fetch(input, {
|
|
104
|
+
...withBrowserMutationProof(init),
|
|
105
|
+
signal: controller.signal,
|
|
106
|
+
});
|
|
92
107
|
} catch (error) {
|
|
93
108
|
if (timedOut && error instanceof DOMException && error.name === "AbortError") {
|
|
94
109
|
throw new ApiTimeoutError(timeoutMs);
|
|
@@ -61,6 +61,38 @@ export const EcosystemTaskActionSchema = z.enum([
|
|
|
61
61
|
"recipe",
|
|
62
62
|
]);
|
|
63
63
|
|
|
64
|
+
export const ECOSYSTEM_EXECUTION_CONFIRMATION_PHRASE = "RUN_LOCAL_COMMAND";
|
|
65
|
+
|
|
66
|
+
export const EcosystemExecutionPlanSchema = z.object({
|
|
67
|
+
action: EcosystemTaskActionSchema,
|
|
68
|
+
targetId: z.string(),
|
|
69
|
+
targetName: z.string(),
|
|
70
|
+
command: z.string(),
|
|
71
|
+
source: z.string(),
|
|
72
|
+
timeoutMs: z.number().int().positive(),
|
|
73
|
+
scope: z.string(),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export const EcosystemExecutionConfirmationSchema = z.object({
|
|
77
|
+
nonce: z.string().uuid(),
|
|
78
|
+
phrase: z.literal(ECOSYSTEM_EXECUTION_CONFIRMATION_PHRASE),
|
|
79
|
+
expiresAt: z.string().datetime(),
|
|
80
|
+
plan: EcosystemExecutionPlanSchema,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export const EcosystemExecutionConfirmationResponseSchema = z.object({
|
|
84
|
+
confirmation: EcosystemExecutionConfirmationSchema,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export const EcosystemExecutionRequestSchema = z.discriminatedUnion("intent", [
|
|
88
|
+
z.object({ intent: z.literal("prepare") }),
|
|
89
|
+
z.object({
|
|
90
|
+
intent: z.literal("confirm"),
|
|
91
|
+
nonce: z.string().uuid(),
|
|
92
|
+
confirmation: z.literal(ECOSYSTEM_EXECUTION_CONFIRMATION_PHRASE),
|
|
93
|
+
}),
|
|
94
|
+
]);
|
|
95
|
+
|
|
64
96
|
export const EcosystemTaskStatusSchema = z.enum(["queued", "running", "succeeded", "failed"]);
|
|
65
97
|
|
|
66
98
|
export const EcosystemTaskSchema = z.object({
|
|
@@ -92,5 +124,7 @@ export type EcosystemPackage = z.infer<typeof EcosystemPackageSchema>;
|
|
|
92
124
|
export type EcosystemRunnerPreset = z.infer<typeof EcosystemRunnerPresetSchema>;
|
|
93
125
|
export type EcosystemRecipe = z.infer<typeof EcosystemRecipeSchema>;
|
|
94
126
|
export type EcosystemTaskAction = z.infer<typeof EcosystemTaskActionSchema>;
|
|
127
|
+
export type EcosystemExecutionPlan = z.infer<typeof EcosystemExecutionPlanSchema>;
|
|
128
|
+
export type EcosystemExecutionConfirmation = z.infer<typeof EcosystemExecutionConfirmationSchema>;
|
|
95
129
|
export type EcosystemTaskStatus = z.infer<typeof EcosystemTaskStatusSchema>;
|
|
96
130
|
export type EcosystemTask = z.infer<typeof EcosystemTaskSchema>;
|
package/src/lib/export-logs.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import JSZip from "jszip";
|
|
2
1
|
import type { CapturedLog } from "../contracts";
|
|
3
2
|
import { fetchWithTimeout } from "./apiClient";
|
|
4
3
|
import packageJson from "../../package.json";
|
|
@@ -171,6 +170,7 @@ export async function exportLogsAsZip(
|
|
|
171
170
|
const limitMessage = exportLimitMessage(logs);
|
|
172
171
|
if (limitMessage !== null) return { ok: false, message: limitMessage };
|
|
173
172
|
|
|
173
|
+
const { default: JSZip } = await import("jszip");
|
|
174
174
|
const zip = new JSZip();
|
|
175
175
|
const suffix = mode === "redacted" ? "redacted" : "raw";
|
|
176
176
|
const streamingLogs = logs.filter((log) => log.streaming).slice(0, MAX_STREAMING_CHUNK_EXPORTS);
|