@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,201 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
type ErrorInfo,
|
|
4
|
+
type JSX,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
useCallback,
|
|
7
|
+
useMemo,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
10
|
+
|
|
11
|
+
import { copyTextToClipboard } from "../../lib/clipboard";
|
|
12
|
+
import { createSafeDiagnosticReference } from "../../lib/safeDiagnostic";
|
|
13
|
+
import { Button } from "../ui/button";
|
|
14
|
+
import { CrabLogo } from "../ui/crab-logo";
|
|
15
|
+
|
|
16
|
+
export type SafeErrorFallbackProps = {
|
|
17
|
+
reference: string;
|
|
18
|
+
title: string;
|
|
19
|
+
description: string;
|
|
20
|
+
variant: "page" | "feature";
|
|
21
|
+
onRetry?: () => void;
|
|
22
|
+
onReload?: () => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function SafeErrorFallback({
|
|
26
|
+
reference,
|
|
27
|
+
title,
|
|
28
|
+
description,
|
|
29
|
+
variant,
|
|
30
|
+
onRetry,
|
|
31
|
+
onReload,
|
|
32
|
+
}: SafeErrorFallbackProps): JSX.Element {
|
|
33
|
+
const [copied, setCopied] = useState(false);
|
|
34
|
+
const handleCopy = useCallback(() => {
|
|
35
|
+
void copyTextToClipboard(reference).then((success) => {
|
|
36
|
+
if (!success) return;
|
|
37
|
+
setCopied(true);
|
|
38
|
+
});
|
|
39
|
+
}, [reference]);
|
|
40
|
+
|
|
41
|
+
const content = (
|
|
42
|
+
<div
|
|
43
|
+
role="alert"
|
|
44
|
+
className={
|
|
45
|
+
variant === "page"
|
|
46
|
+
? "border-border/70 bg-card/80 rounded-lg border p-7 shadow-sm"
|
|
47
|
+
: "border-amber-400/20 bg-amber-500/[0.07] rounded-lg border p-4 shadow-sm"
|
|
48
|
+
}
|
|
49
|
+
>
|
|
50
|
+
<div className="flex items-center gap-3">
|
|
51
|
+
<CrabLogo className="size-9 shrink-0 text-amber-500" />
|
|
52
|
+
<div>
|
|
53
|
+
<div className="text-muted-foreground text-xs font-semibold uppercase tracking-wider">
|
|
54
|
+
Agent Inspector recovery
|
|
55
|
+
</div>
|
|
56
|
+
<h1 className={variant === "page" ? "mt-1 text-xl font-semibold" : "mt-1 font-semibold"}>
|
|
57
|
+
{title}
|
|
58
|
+
</h1>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
<p className="text-muted-foreground mt-4 max-w-xl text-sm leading-6">{description}</p>
|
|
62
|
+
<div className="border-border/60 bg-background/70 mt-4 flex flex-wrap items-center gap-2 rounded-md border px-3 py-2">
|
|
63
|
+
<span className="text-muted-foreground text-xs">Diagnostic reference</span>
|
|
64
|
+
<code className="min-w-0 flex-1 break-all font-mono text-xs text-foreground">
|
|
65
|
+
{reference}
|
|
66
|
+
</code>
|
|
67
|
+
<Button type="button" variant="outline" size="sm" onClick={handleCopy}>
|
|
68
|
+
{copied ? "Copied" : "Copy reference"}
|
|
69
|
+
</Button>
|
|
70
|
+
</div>
|
|
71
|
+
<p className="text-muted-foreground mt-2 text-xs">
|
|
72
|
+
Error details are hidden to protect captured prompts, source content, headers, and secrets.
|
|
73
|
+
</p>
|
|
74
|
+
<div className="mt-5 flex flex-wrap gap-2">
|
|
75
|
+
{onRetry !== undefined && (
|
|
76
|
+
<Button type="button" size="sm" onClick={onRetry}>
|
|
77
|
+
Retry
|
|
78
|
+
</Button>
|
|
79
|
+
)}
|
|
80
|
+
{onReload !== undefined && (
|
|
81
|
+
<Button type="button" variant="outline" size="sm" onClick={onReload}>
|
|
82
|
+
Reload Inspector
|
|
83
|
+
</Button>
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (variant === "feature") {
|
|
90
|
+
return content;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<main className="min-h-screen bg-background text-foreground">
|
|
95
|
+
<div className="mx-auto flex min-h-screen w-full max-w-3xl flex-col justify-center px-6 py-16">
|
|
96
|
+
{content}
|
|
97
|
+
</div>
|
|
98
|
+
</main>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
type SafeErrorBoundaryProps = {
|
|
103
|
+
children: ReactNode;
|
|
104
|
+
scope: string;
|
|
105
|
+
title: string;
|
|
106
|
+
description: string;
|
|
107
|
+
variant?: "page" | "feature";
|
|
108
|
+
onRetry?: () => void;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
type SafeErrorBoundaryState = {
|
|
112
|
+
caughtAt: number;
|
|
113
|
+
error: unknown;
|
|
114
|
+
failed: boolean;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const INITIAL_ERROR_STATE: SafeErrorBoundaryState = {
|
|
118
|
+
caughtAt: 0,
|
|
119
|
+
error: null,
|
|
120
|
+
failed: false,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export class SafeErrorBoundary extends Component<SafeErrorBoundaryProps, SafeErrorBoundaryState> {
|
|
124
|
+
override state: SafeErrorBoundaryState = INITIAL_ERROR_STATE;
|
|
125
|
+
|
|
126
|
+
static getDerivedStateFromError(error: unknown): SafeErrorBoundaryState {
|
|
127
|
+
return { caughtAt: Date.now(), error, failed: true };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
override componentDidCatch(_error: unknown, _errorInfo: ErrorInfo): void {
|
|
131
|
+
// The safe reference is intentionally the only error detail exposed by this boundary.
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private readonly retry = (): void => {
|
|
135
|
+
this.setState(INITIAL_ERROR_STATE);
|
|
136
|
+
this.props.onRetry?.();
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
override render(): ReactNode {
|
|
140
|
+
if (!this.state.failed) {
|
|
141
|
+
return this.props.children;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const reference = createSafeDiagnosticReference(
|
|
145
|
+
this.props.scope,
|
|
146
|
+
this.state.error,
|
|
147
|
+
this.state.caughtAt,
|
|
148
|
+
);
|
|
149
|
+
return (
|
|
150
|
+
<SafeErrorFallback
|
|
151
|
+
reference={reference}
|
|
152
|
+
title={this.props.title}
|
|
153
|
+
description={this.props.description}
|
|
154
|
+
variant={this.props.variant ?? "feature"}
|
|
155
|
+
onRetry={this.retry}
|
|
156
|
+
onReload={() => window.location.reload()}
|
|
157
|
+
/>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function LazyFeatureBoundary({
|
|
163
|
+
feature,
|
|
164
|
+
children,
|
|
165
|
+
}: {
|
|
166
|
+
feature: string;
|
|
167
|
+
children: ReactNode;
|
|
168
|
+
}): JSX.Element {
|
|
169
|
+
return (
|
|
170
|
+
<SafeErrorBoundary
|
|
171
|
+
scope={`lazy-${feature}`}
|
|
172
|
+
title={`${feature} could not be displayed`}
|
|
173
|
+
description="The rest of the workspace is still available. Retry this feature or reload Inspector."
|
|
174
|
+
variant="feature"
|
|
175
|
+
>
|
|
176
|
+
{children}
|
|
177
|
+
</SafeErrorBoundary>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function SafeRouteError({
|
|
182
|
+
error,
|
|
183
|
+
reset,
|
|
184
|
+
scope,
|
|
185
|
+
}: {
|
|
186
|
+
error: Error;
|
|
187
|
+
reset: () => void;
|
|
188
|
+
scope: string;
|
|
189
|
+
}): JSX.Element {
|
|
190
|
+
const reference = useMemo(() => createSafeDiagnosticReference(scope, error), [error, scope]);
|
|
191
|
+
return (
|
|
192
|
+
<SafeErrorFallback
|
|
193
|
+
reference={reference}
|
|
194
|
+
title="Inspector workspace could not be displayed"
|
|
195
|
+
description="Agent Inspector stopped this view safely. Retry the route or reload the application."
|
|
196
|
+
variant="page"
|
|
197
|
+
onRetry={reset}
|
|
198
|
+
onReload={() => window.location.reload()}
|
|
199
|
+
/>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
import { BrainCircuit, Loader2, Send } from "lucide-react";
|
|
1
|
+
import { BrainCircuit, Loader2, RotateCcw, Send, Sparkles } from "lucide-react";
|
|
2
2
|
import { useMemo, useState, type ChangeEvent, type JSX } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
buildPiAgentHistory,
|
|
5
|
+
createEmptyPiAgentConversation,
|
|
6
|
+
normalizePiAgentResponse,
|
|
7
|
+
PI_AGENT_STARTERS,
|
|
8
|
+
type PiAgentConversationMessage,
|
|
9
|
+
type PiAgentEvidenceView,
|
|
10
|
+
type PiAgentMetricView,
|
|
11
|
+
type PiAgentResponseView,
|
|
12
|
+
} from "./piAgentChatLogic";
|
|
4
13
|
import { useProviders } from "../../lib/useProviders";
|
|
14
|
+
import { dispatchLogFocusRequest } from "../proxy-viewer/logFocus";
|
|
15
|
+
import { AnswerMarkdown } from "../proxy-viewer/AnswerMarkdown";
|
|
5
16
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
|
|
6
17
|
|
|
7
18
|
type PiAgentPanelProps = {
|
|
@@ -49,25 +60,115 @@ function sortProviderBySource<T extends { name: string; source?: "company" | "pe
|
|
|
49
60
|
});
|
|
50
61
|
}
|
|
51
62
|
|
|
63
|
+
function nextMessageId(role: "user" | "assistant"): string {
|
|
64
|
+
return `${role}-${String(Date.now())}-${String(Math.random()).slice(2)}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function MetricStrip({ metrics }: { metrics: readonly PiAgentMetricView[] }): JSX.Element | null {
|
|
68
|
+
if (metrics.length === 0) return null;
|
|
69
|
+
return (
|
|
70
|
+
<div className="mt-2 grid grid-cols-2 gap-1.5">
|
|
71
|
+
{metrics.slice(0, 8).map((metric) => (
|
|
72
|
+
<div key={metric.key} className="rounded-sm bg-black/20 px-2 py-1">
|
|
73
|
+
<div className="truncate text-[9px] uppercase text-muted-foreground">{metric.label}</div>
|
|
74
|
+
<div className="truncate font-mono text-[11px] text-cyan-50">{metric.value}</div>
|
|
75
|
+
</div>
|
|
76
|
+
))}
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function EvidenceCard({ evidence }: { evidence: PiAgentEvidenceView }): JSX.Element {
|
|
82
|
+
const openLog = (): void => {
|
|
83
|
+
if (evidence.logId === null) return;
|
|
84
|
+
dispatchLogFocusRequest({ logId: evidence.logId, tab: "anatomy" });
|
|
85
|
+
};
|
|
86
|
+
const title =
|
|
87
|
+
evidence.logId === null
|
|
88
|
+
? `${evidence.source}:${evidence.id}`
|
|
89
|
+
: `${evidence.source}:log #${String(evidence.logId)}`;
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
onClick={openLog}
|
|
95
|
+
disabled={evidence.logId === null}
|
|
96
|
+
className="w-full rounded-sm bg-black/20 px-2 py-2 text-left shadow-[inset_0_0_0_1px_rgba(255,255,255,0.05)] transition-colors hover:bg-black/30 disabled:cursor-default disabled:hover:bg-black/20"
|
|
97
|
+
>
|
|
98
|
+
<div className="flex min-w-0 items-center justify-between gap-2">
|
|
99
|
+
<span className="truncate font-mono text-[10px] text-cyan-100">{title}</span>
|
|
100
|
+
{evidence.score !== null && (
|
|
101
|
+
<span className="shrink-0 font-mono text-[9px] text-muted-foreground">
|
|
102
|
+
{evidence.score.toFixed(1)}
|
|
103
|
+
</span>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
<div className="mt-1 flex flex-wrap gap-x-2 gap-y-1 text-[10px] text-muted-foreground">
|
|
107
|
+
{evidence.providerName !== null && <span>{evidence.providerName}</span>}
|
|
108
|
+
{evidence.model !== null && <span>{evidence.model}</span>}
|
|
109
|
+
{evidence.status !== null && <span>status {String(evidence.status)}</span>}
|
|
110
|
+
{evidence.elapsedMs !== null && <span>{String(evidence.elapsedMs)} ms</span>}
|
|
111
|
+
</div>
|
|
112
|
+
<p className="mt-1 line-clamp-3 text-[11px] leading-4 text-foreground/85">
|
|
113
|
+
{evidence.excerpt}
|
|
114
|
+
</p>
|
|
115
|
+
</button>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function AssistantMessage({ response }: { response: PiAgentResponseView }): JSX.Element {
|
|
120
|
+
return (
|
|
121
|
+
<div className="rounded-md bg-black/25 p-3 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.05)]">
|
|
122
|
+
<AnswerMarkdown text={response.answer} />
|
|
123
|
+
<div className="mt-2 break-words font-mono text-[10px] text-cyan-100/70">
|
|
124
|
+
{response.providerName} / {response.model} / {response.protocol} /{" "}
|
|
125
|
+
{String(response.logCount)} matched logs
|
|
126
|
+
</div>
|
|
127
|
+
<MetricStrip metrics={response.summaryMetrics} />
|
|
128
|
+
<MetricStrip metrics={response.queryMetrics} />
|
|
129
|
+
{response.evidence.length > 0 && (
|
|
130
|
+
<div className="mt-2 space-y-1.5">
|
|
131
|
+
{response.evidence.slice(0, 6).map((evidence) => (
|
|
132
|
+
<EvidenceCard key={`${evidence.source}-${evidence.id}`} evidence={evidence} />
|
|
133
|
+
))}
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function ConversationMessage({ message }: { message: PiAgentConversationMessage }): JSX.Element {
|
|
141
|
+
if (message.role === "assistant" && message.response !== null) {
|
|
142
|
+
return <AssistantMessage response={message.response} />;
|
|
143
|
+
}
|
|
144
|
+
return (
|
|
145
|
+
<div className="rounded-md bg-cyan-400/10 px-3 py-2 text-xs leading-5 text-cyan-50">
|
|
146
|
+
{message.content}
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
52
151
|
export function PiAgentPanel({ currentSessionId, logCount }: PiAgentPanelProps): JSX.Element {
|
|
53
152
|
const { providers } = useProviders();
|
|
54
153
|
const piProviders = useMemo(
|
|
55
154
|
() => sortProviderBySource(providers.filter(providerSupportsPiAgent)),
|
|
56
155
|
[providers],
|
|
57
156
|
);
|
|
58
|
-
const [
|
|
157
|
+
const [conversation, setConversation] = useState(() => ({
|
|
158
|
+
...createEmptyPiAgentConversation(),
|
|
159
|
+
question: DEFAULT_QUESTION,
|
|
160
|
+
}));
|
|
59
161
|
const [providerId, setProviderId] = useState<string>("__auto__");
|
|
60
162
|
const [model, setModel] = useState<string>("__auto__");
|
|
61
|
-
const [answer, setAnswer] = useState<string>("");
|
|
62
|
-
const [status, setStatus] = useState<string>("");
|
|
63
163
|
const [loading, setLoading] = useState(false);
|
|
64
164
|
|
|
65
165
|
const selectedProvider = piProviders.find((provider) => provider.id === providerId);
|
|
66
166
|
const modelOptions = selectedProvider?.models ?? [];
|
|
167
|
+
const question = conversation.question;
|
|
67
168
|
const canAsk = question.trim() !== "" && piProviders.length > 0 && !loading;
|
|
68
169
|
|
|
69
170
|
const handleQuestionChange = (event: ChangeEvent<HTMLTextAreaElement>): void => {
|
|
70
|
-
|
|
171
|
+
setConversation((current) => ({ ...current, question: event.currentTarget.value }));
|
|
71
172
|
};
|
|
72
173
|
|
|
73
174
|
const handleProviderChange = (value: string): void => {
|
|
@@ -75,48 +176,86 @@ export function PiAgentPanel({ currentSessionId, logCount }: PiAgentPanelProps):
|
|
|
75
176
|
setModel("__auto__");
|
|
76
177
|
};
|
|
77
178
|
|
|
179
|
+
const resetConversation = (): void => {
|
|
180
|
+
setConversation({ ...createEmptyPiAgentConversation(), question: DEFAULT_QUESTION });
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const setStarter = (questionText: string): void => {
|
|
184
|
+
setConversation((current) => ({ ...current, question: questionText, error: null }));
|
|
185
|
+
};
|
|
186
|
+
|
|
78
187
|
const askPiAgent = (): void => {
|
|
79
188
|
if (!canAsk) return;
|
|
189
|
+
const trimmedQuestion = question.trim();
|
|
190
|
+
const history = buildPiAgentHistory(conversation.messages);
|
|
191
|
+
const userMessage: PiAgentConversationMessage = {
|
|
192
|
+
id: nextMessageId("user"),
|
|
193
|
+
role: "user",
|
|
194
|
+
content: trimmedQuestion,
|
|
195
|
+
response: null,
|
|
196
|
+
};
|
|
197
|
+
|
|
80
198
|
setLoading(true);
|
|
81
|
-
|
|
82
|
-
|
|
199
|
+
setConversation((current) => ({
|
|
200
|
+
...current,
|
|
201
|
+
messages: [...current.messages, userMessage],
|
|
202
|
+
question: "",
|
|
203
|
+
error: null,
|
|
204
|
+
}));
|
|
83
205
|
|
|
84
206
|
const body = {
|
|
85
|
-
question:
|
|
207
|
+
question: trimmedQuestion,
|
|
208
|
+
history,
|
|
86
209
|
...(currentSessionId !== null ? { sessionId: currentSessionId } : {}),
|
|
87
210
|
...(providerId !== "__auto__" ? { providerId } : {}),
|
|
88
211
|
...(model !== "__auto__" ? { model } : {}),
|
|
89
|
-
logLimit:
|
|
212
|
+
logLimit: 16,
|
|
90
213
|
};
|
|
91
214
|
|
|
92
215
|
void fetch("/api/pi-agent", {
|
|
93
216
|
method: "POST",
|
|
94
|
-
headers: {
|
|
217
|
+
headers: {
|
|
218
|
+
"content-type": "application/json",
|
|
219
|
+
"x-agent-inspector-csrf": "1",
|
|
220
|
+
},
|
|
95
221
|
body: JSON.stringify(body),
|
|
96
222
|
})
|
|
97
223
|
.then(async (response) => {
|
|
98
224
|
const raw = await response.text();
|
|
99
225
|
if (!response.ok) {
|
|
100
|
-
|
|
101
|
-
|
|
226
|
+
setConversation((current) => ({
|
|
227
|
+
...current,
|
|
228
|
+
error: `Pi Agent failed: HTTP ${String(response.status)} ${raw}`,
|
|
229
|
+
}));
|
|
102
230
|
return;
|
|
103
231
|
}
|
|
104
232
|
const parsedJson: unknown = JSON.parse(raw);
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
233
|
+
const normalized = normalizePiAgentResponse(parsedJson);
|
|
234
|
+
if (normalized === null) {
|
|
235
|
+
setConversation((current) => ({
|
|
236
|
+
...current,
|
|
237
|
+
error: "Pi Agent returned an unexpected response.",
|
|
238
|
+
}));
|
|
109
239
|
return;
|
|
110
240
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
241
|
+
setConversation((current) => ({
|
|
242
|
+
...current,
|
|
243
|
+
messages: [
|
|
244
|
+
...current.messages,
|
|
245
|
+
{
|
|
246
|
+
id: nextMessageId("assistant"),
|
|
247
|
+
role: "assistant",
|
|
248
|
+
content: normalized.answer,
|
|
249
|
+
response: normalized,
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
}));
|
|
117
253
|
})
|
|
118
254
|
.catch((err: unknown) => {
|
|
119
|
-
|
|
255
|
+
setConversation((current) => ({
|
|
256
|
+
...current,
|
|
257
|
+
error: err instanceof Error ? err.message : "Pi Agent request failed.",
|
|
258
|
+
}));
|
|
120
259
|
})
|
|
121
260
|
.finally(() => {
|
|
122
261
|
setLoading(false);
|
|
@@ -130,9 +269,17 @@ export function PiAgentPanel({ currentSessionId, logCount }: PiAgentPanelProps):
|
|
|
130
269
|
<div className="min-w-0 flex-1">
|
|
131
270
|
<div className="text-xs font-semibold text-foreground">Pi Agent</div>
|
|
132
271
|
<div className="text-[11px] text-muted-foreground">
|
|
133
|
-
{currentSessionId === null ? "Inspector-wide
|
|
272
|
+
{currentSessionId === null ? "Inspector-wide ChatBI" : "Session ChatBI"}
|
|
134
273
|
</div>
|
|
135
274
|
</div>
|
|
275
|
+
<button
|
|
276
|
+
type="button"
|
|
277
|
+
onClick={resetConversation}
|
|
278
|
+
className="inline-flex size-7 items-center justify-center rounded-md bg-black/25 text-muted-foreground transition-colors hover:text-foreground"
|
|
279
|
+
title="Reset conversation"
|
|
280
|
+
>
|
|
281
|
+
<RotateCcw className="size-3.5" />
|
|
282
|
+
</button>
|
|
136
283
|
</div>
|
|
137
284
|
|
|
138
285
|
<div className="mt-3 grid grid-cols-2 gap-2">
|
|
@@ -164,11 +311,43 @@ export function PiAgentPanel({ currentSessionId, logCount }: PiAgentPanelProps):
|
|
|
164
311
|
</Select>
|
|
165
312
|
</div>
|
|
166
313
|
|
|
314
|
+
{conversation.messages.length === 0 && (
|
|
315
|
+
<div className="mt-3 flex flex-wrap gap-1.5">
|
|
316
|
+
{PI_AGENT_STARTERS.map((starter) => (
|
|
317
|
+
<button
|
|
318
|
+
key={starter.id}
|
|
319
|
+
type="button"
|
|
320
|
+
onClick={() => {
|
|
321
|
+
setStarter(starter.question);
|
|
322
|
+
}}
|
|
323
|
+
className="inline-flex h-7 items-center gap-1.5 rounded-sm bg-black/25 px-2 text-[11px] text-cyan-50 transition-colors hover:bg-cyan-400/12"
|
|
324
|
+
>
|
|
325
|
+
<Sparkles className="size-3" />
|
|
326
|
+
<span>{starter.label}</span>
|
|
327
|
+
</button>
|
|
328
|
+
))}
|
|
329
|
+
</div>
|
|
330
|
+
)}
|
|
331
|
+
|
|
332
|
+
{conversation.messages.length > 0 && (
|
|
333
|
+
<div className="mt-3 max-h-96 space-y-2 overflow-y-auto pr-1">
|
|
334
|
+
{conversation.messages.map((message) => (
|
|
335
|
+
<ConversationMessage key={message.id} message={message} />
|
|
336
|
+
))}
|
|
337
|
+
{loading && (
|
|
338
|
+
<div className="inline-flex items-center gap-2 rounded-md bg-black/25 px-3 py-2 text-xs text-muted-foreground">
|
|
339
|
+
<Loader2 className="size-3.5 animate-spin" />
|
|
340
|
+
<span>Searching Inspector...</span>
|
|
341
|
+
</div>
|
|
342
|
+
)}
|
|
343
|
+
</div>
|
|
344
|
+
)}
|
|
345
|
+
|
|
167
346
|
<textarea
|
|
168
347
|
value={question}
|
|
169
348
|
onChange={handleQuestionChange}
|
|
170
|
-
className="mt-
|
|
171
|
-
placeholder="Ask Pi Agent
|
|
349
|
+
className="mt-3 min-h-20 w-full resize-y rounded-md bg-black/25 px-3 py-2 text-xs leading-5 text-foreground outline-none shadow-[inset_0_0_0_1px_rgba(255,255,255,0.06)] transition-colors placeholder:text-muted-foreground focus-visible:ring-1 focus-visible:ring-ring"
|
|
350
|
+
placeholder="Ask Pi Agent across saved Inspector data..."
|
|
172
351
|
/>
|
|
173
352
|
|
|
174
353
|
<button
|
|
@@ -181,19 +360,14 @@ export function PiAgentPanel({ currentSessionId, logCount }: PiAgentPanelProps):
|
|
|
181
360
|
<span>{loading ? "Thinking..." : "Ask Pi"}</span>
|
|
182
361
|
</button>
|
|
183
362
|
|
|
363
|
+
{conversation.error !== null && (
|
|
364
|
+
<p className="mt-2 break-words text-[11px] leading-5 text-red-200">{conversation.error}</p>
|
|
365
|
+
)}
|
|
184
366
|
{piProviders.length === 0 && (
|
|
185
367
|
<p className="mt-2 text-[11px] leading-5 text-muted-foreground">
|
|
186
368
|
Configure a Provider with Responses, Chat, or Anthropic URL to enable Pi Agent.
|
|
187
369
|
</p>
|
|
188
370
|
)}
|
|
189
|
-
{status !== "" && (
|
|
190
|
-
<p className="mt-2 break-words font-mono text-[10px] text-cyan-100/70">{status}</p>
|
|
191
|
-
)}
|
|
192
|
-
{answer !== "" && (
|
|
193
|
-
<div className="mt-3 max-h-64 overflow-y-auto whitespace-pre-wrap rounded-md bg-black/25 p-3 text-xs leading-5 text-foreground shadow-[inset_0_0_0_1px_rgba(255,255,255,0.05)]">
|
|
194
|
-
{answer}
|
|
195
|
-
</div>
|
|
196
|
-
)}
|
|
197
371
|
{logCount === 0 && (
|
|
198
372
|
<p className="mt-2 text-[11px] leading-5 text-muted-foreground">
|
|
199
373
|
No logs are visible in this scope yet; Pi Agent can still answer Provider setup questions.
|