@tonyclaw/agent-inspector 3.0.29 → 3.0.30
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-B6cXJohL.js +1 -0
- package/.output/public/assets/InspectorPet-mAzeGmEE.js +4108 -0
- package/.output/public/assets/ProxyViewerContainer-CkFWv_Nm.js +126 -0
- package/.output/public/assets/{ReplayDialog-CctnITbt.js → ReplayDialog-DodiTL6E.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-BrN0RBbL.js → RequestAnatomy-B3IauToI.js} +1 -1
- package/.output/public/assets/ResponseView-Hn-5ordK.js +3 -0
- package/.output/public/assets/StreamingChunkSequence-vLDOVQM_.js +1 -0
- package/.output/public/assets/_sessionId-BFH4TOaI.js +1 -0
- package/.output/public/assets/{_sessionId-DKM91udC.js → _sessionId-Cj_HIFZx.js} +1 -1
- package/.output/public/assets/{index-Bq-nqolG.js → index-Bmj2lcWE.js} +1 -1
- package/.output/public/assets/index-CgJj-HBC.css +1 -0
- package/.output/public/assets/index-Ch7uqnCT.js +1 -0
- package/.output/public/assets/index-DkDdKvLl.js +76 -0
- package/.output/public/assets/{index-DPiESBo2.js → index-ZlhqCrSg.js} +6 -6
- package/.output/public/assets/{json-viewer-CfaDJwx8.js → json-viewer-BdrnVQu-.js} +1 -1
- package/.output/public/assets/jszip.min-CS_nt_o1.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 +86 -86
- 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-C5S3pGZd.mjs} +15 -15
- package/.output/server/_sessionId-g6D69lKq.mjs +81 -0
- package/.output/server/_ssr/{CompareDrawer-R_P1-VNr.mjs → CompareDrawer-CqUmGHal.mjs} +138 -103
- package/.output/server/_ssr/InspectorPet-DI0UJd01.mjs +925 -0
- package/.output/server/_ssr/{ProxyViewerContainer-DUgxRNbj.mjs → ProxyViewerContainer-CvsUmbJ3.mjs} +1157 -1613
- package/.output/server/_ssr/{ReplayDialog-De9wKKzS.mjs → ReplayDialog-DS48dpFG.mjs} +18 -18
- package/.output/server/_ssr/{RequestAnatomy-Db4MxUYo.mjs → RequestAnatomy-DxYVQU1C.mjs} +58 -58
- package/.output/server/_ssr/{ResponseView-8rDXOiNB.mjs → ResponseView-BLXW73eq.mjs} +60 -60
- package/.output/server/_ssr/{StreamingChunkSequence-N9teZHkA.mjs → StreamingChunkSequence-DD0iFuy0.mjs} +16 -16
- package/.output/server/_ssr/{index-D4DvByuW.mjs → index-B10LIaK0.mjs} +15 -15
- package/.output/server/_ssr/index-RHVJnU2p.mjs +81 -0
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-BjoHRbjS.mjs → json-viewer-LDu2XBaK.mjs} +58 -58
- package/.output/server/_ssr/{router-BYlePDj8.mjs → router-GKvrwifJ.mjs} +9005 -6488
- package/.output/server/_tanstack-start-manifest_v-Df2n4mbT.mjs +4 -0
- package/.output/server/index.mjs +103 -75
- 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 +4 -1
- 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 +1 -1
- 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/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/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
|
@@ -13,15 +13,20 @@ import { OnboardingBanner } from "./OnboardingBanner";
|
|
|
13
13
|
import {
|
|
14
14
|
ProxyViewer,
|
|
15
15
|
type LogPaginationControls,
|
|
16
|
-
type SessionLoadProgress,
|
|
17
16
|
type SessionMembershipEvidence,
|
|
18
17
|
} from "./ProxyViewer";
|
|
18
|
+
import type { SessionLoadProgress } from "./proxy-viewer/SessionLoadProgressBar";
|
|
19
|
+
import { browserPrefersReducedMotion, preferredScrollBehavior } from "./proxy-viewer/accessibility";
|
|
19
20
|
import {
|
|
20
21
|
filterLogs,
|
|
21
22
|
mergeLogsById,
|
|
22
23
|
mergeSessionIds,
|
|
23
24
|
} from "./proxy-viewer/proxyViewerContainerLogic";
|
|
24
25
|
import { dispatchLogFocusRequest } from "./proxy-viewer/logFocus";
|
|
26
|
+
import {
|
|
27
|
+
createInitialLiveConnectionState,
|
|
28
|
+
reduceLiveConnectionState,
|
|
29
|
+
} from "./proxy-viewer/liveConnectionState";
|
|
25
30
|
import { TransientToast, useTransientToast } from "./ui/transient-toast";
|
|
26
31
|
|
|
27
32
|
type SSEUpdate =
|
|
@@ -125,12 +130,13 @@ const SESSION_RELOAD_PAGE_LIMIT = 500;
|
|
|
125
130
|
const MAX_SESSION_RELOAD_PAGES = 200;
|
|
126
131
|
const SESSION_LOGS_FETCH_TIMEOUT_MS = 20_000;
|
|
127
132
|
const SESSION_MEMBERSHIP_FETCH_TIMEOUT_MS = 10_000;
|
|
133
|
+
const SSE_RECONNECT_DELAY_MS = 3_000;
|
|
134
|
+
const SSE_STALE_AFTER_MS = 10_000;
|
|
128
135
|
|
|
129
136
|
function buildLogsStreamUrl(sessionId: string | undefined): string {
|
|
130
|
-
const params = new URLSearchParams();
|
|
137
|
+
const params = new URLSearchParams({ compact: "1" });
|
|
131
138
|
if (sessionId !== undefined) {
|
|
132
139
|
params.set("sessionId", sessionId);
|
|
133
|
-
params.set("compact", "1");
|
|
134
140
|
}
|
|
135
141
|
const query = params.toString();
|
|
136
142
|
return query.length > 0 ? `/api/logs/stream?${query}` : "/api/logs/stream";
|
|
@@ -253,9 +259,10 @@ export function ProxyViewerContainer({
|
|
|
253
259
|
const [selectedModel, setSelectedModel] = useState("__all__");
|
|
254
260
|
const [knownSessions, setKnownSessions] = useState<string[]>([]);
|
|
255
261
|
const { notice, showToast, dismissToast } = useTransientToast();
|
|
256
|
-
const [
|
|
262
|
+
const [liveConnection, setLiveConnection] = useState(createInitialLiveConnectionState);
|
|
257
263
|
const [logPage, setLogPage] = useState<LogCursorPage | null>(null);
|
|
258
264
|
const [sessionPageLoading, setSessionPageLoading] = useState(initialSessionId !== undefined);
|
|
265
|
+
const [sessionLoadError, setSessionLoadError] = useState<string | null>(null);
|
|
259
266
|
const [sessionLoadProgress, setSessionLoadProgress] = useState<SessionLoadProgress | undefined>(
|
|
260
267
|
undefined,
|
|
261
268
|
);
|
|
@@ -263,6 +270,8 @@ export function ProxyViewerContainer({
|
|
|
263
270
|
const [, startLogTransition] = useTransition();
|
|
264
271
|
const eventSourceRef = useRef<EventSource | null>(null);
|
|
265
272
|
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
273
|
+
const staleTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
274
|
+
const automaticRetryEnabledRef = useRef(true);
|
|
266
275
|
const handledHashRef = useRef<string | null>(null);
|
|
267
276
|
const sessionPageRequestIdRef = useRef(0);
|
|
268
277
|
const sessionLoadStartedAtRef = useRef<number | null>(null);
|
|
@@ -381,7 +390,11 @@ export function ProxyViewerContainer({
|
|
|
381
390
|
eventSourceRef.current.close();
|
|
382
391
|
}
|
|
383
392
|
|
|
384
|
-
|
|
393
|
+
if (reconnectTimeoutRef.current !== null) {
|
|
394
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
395
|
+
reconnectTimeoutRef.current = null;
|
|
396
|
+
}
|
|
397
|
+
setLiveConnection((state) => reduceLiveConnectionState(state, { type: "connect-requested" }));
|
|
385
398
|
const es = new EventSource(buildLogsStreamUrl(initialSessionId));
|
|
386
399
|
eventSourceRef.current = es;
|
|
387
400
|
|
|
@@ -391,6 +404,12 @@ export function ProxyViewerContainer({
|
|
|
391
404
|
const parsed: unknown = JSON.parse(rawData);
|
|
392
405
|
const updateResult = SSEUpdateSchema.safeParse(parsed);
|
|
393
406
|
if (!updateResult.success) {
|
|
407
|
+
setLiveConnection((state) =>
|
|
408
|
+
reduceLiveConnectionState(state, {
|
|
409
|
+
type: "recoverable-error",
|
|
410
|
+
message: "The live stream returned invalid data.",
|
|
411
|
+
}),
|
|
412
|
+
);
|
|
394
413
|
showErrorToast("Failed to parse SSE data");
|
|
395
414
|
return;
|
|
396
415
|
}
|
|
@@ -409,7 +428,13 @@ export function ProxyViewerContainer({
|
|
|
409
428
|
return nextLogs;
|
|
410
429
|
});
|
|
411
430
|
setKnownSessions((prev) => mergeSessionIds(prev, extractSessions(update.logs)));
|
|
412
|
-
|
|
431
|
+
if (staleTimeoutRef.current !== null) {
|
|
432
|
+
clearTimeout(staleTimeoutRef.current);
|
|
433
|
+
staleTimeoutRef.current = null;
|
|
434
|
+
}
|
|
435
|
+
setLiveConnection((state) =>
|
|
436
|
+
reduceLiveConnectionState(state, { type: "message-received", at: Date.now() }),
|
|
437
|
+
);
|
|
413
438
|
dismissToast();
|
|
414
439
|
} else if (update.type === "update") {
|
|
415
440
|
setKnownSessions((prev) => {
|
|
@@ -417,23 +442,70 @@ export function ProxyViewerContainer({
|
|
|
417
442
|
return mergeSessionIds(prev, [update.log.sessionId]);
|
|
418
443
|
});
|
|
419
444
|
scheduleUpdate(update.log);
|
|
445
|
+
if (staleTimeoutRef.current !== null) {
|
|
446
|
+
clearTimeout(staleTimeoutRef.current);
|
|
447
|
+
staleTimeoutRef.current = null;
|
|
448
|
+
}
|
|
449
|
+
setLiveConnection((state) =>
|
|
450
|
+
reduceLiveConnectionState(state, { type: "message-received", at: Date.now() }),
|
|
451
|
+
);
|
|
420
452
|
}
|
|
421
453
|
} catch {
|
|
454
|
+
setLiveConnection((state) =>
|
|
455
|
+
reduceLiveConnectionState(state, {
|
|
456
|
+
type: "recoverable-error",
|
|
457
|
+
message: "The live stream returned malformed JSON.",
|
|
458
|
+
}),
|
|
459
|
+
);
|
|
422
460
|
showErrorToast("Failed to parse SSE data");
|
|
423
461
|
}
|
|
424
462
|
};
|
|
425
463
|
|
|
426
464
|
es.onerror = () => {
|
|
427
|
-
|
|
428
|
-
|
|
465
|
+
const online = navigator.onLine;
|
|
466
|
+
setLiveConnection((state) =>
|
|
467
|
+
reduceLiveConnectionState(state, {
|
|
468
|
+
type: "connection-lost",
|
|
469
|
+
online,
|
|
470
|
+
message: "The live stream disconnected.",
|
|
471
|
+
}),
|
|
472
|
+
);
|
|
429
473
|
es.close();
|
|
430
|
-
if (
|
|
431
|
-
|
|
474
|
+
if (!online || !automaticRetryEnabledRef.current) return;
|
|
475
|
+
if (staleTimeoutRef.current === null) {
|
|
476
|
+
staleTimeoutRef.current = setTimeout(() => {
|
|
477
|
+
staleTimeoutRef.current = null;
|
|
478
|
+
setLiveConnection((state) => reduceLiveConnectionState(state, { type: "stale-timeout" }));
|
|
479
|
+
}, SSE_STALE_AFTER_MS);
|
|
432
480
|
}
|
|
433
|
-
reconnectTimeoutRef.current = setTimeout(connectSSE,
|
|
481
|
+
reconnectTimeoutRef.current = setTimeout(connectSSE, SSE_RECONNECT_DELAY_MS);
|
|
434
482
|
};
|
|
435
483
|
}, [dismissToast, initialSessionId, scheduleUpdate, showErrorToast]);
|
|
436
484
|
|
|
485
|
+
const retryLiveConnection = useCallback(() => {
|
|
486
|
+
automaticRetryEnabledRef.current = true;
|
|
487
|
+
if (reconnectTimeoutRef.current !== null) {
|
|
488
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
489
|
+
reconnectTimeoutRef.current = null;
|
|
490
|
+
}
|
|
491
|
+
connectSSE();
|
|
492
|
+
}, [connectSSE]);
|
|
493
|
+
|
|
494
|
+
const stopLiveConnection = useCallback(() => {
|
|
495
|
+
automaticRetryEnabledRef.current = false;
|
|
496
|
+
eventSourceRef.current?.close();
|
|
497
|
+
eventSourceRef.current = null;
|
|
498
|
+
if (reconnectTimeoutRef.current !== null) {
|
|
499
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
500
|
+
reconnectTimeoutRef.current = null;
|
|
501
|
+
}
|
|
502
|
+
if (staleTimeoutRef.current !== null) {
|
|
503
|
+
clearTimeout(staleTimeoutRef.current);
|
|
504
|
+
staleTimeoutRef.current = null;
|
|
505
|
+
}
|
|
506
|
+
setLiveConnection((state) => reduceLiveConnectionState(state, { type: "stop-retrying" }));
|
|
507
|
+
}, []);
|
|
508
|
+
|
|
437
509
|
const loadSessionPage = useCallback(
|
|
438
510
|
(request: SessionPageRequest) => {
|
|
439
511
|
if (initialSessionId === undefined) return;
|
|
@@ -441,6 +513,7 @@ export function ProxyViewerContainer({
|
|
|
441
513
|
const requestId = sessionPageRequestIdRef.current + 1;
|
|
442
514
|
sessionPageRequestIdRef.current = requestId;
|
|
443
515
|
setSessionPageLoading(true);
|
|
516
|
+
setSessionLoadError(null);
|
|
444
517
|
startSessionLoadProgress("Loading session logs", requestId);
|
|
445
518
|
dismissToast();
|
|
446
519
|
|
|
@@ -453,16 +526,17 @@ export function ProxyViewerContainer({
|
|
|
453
526
|
setLogPage(page);
|
|
454
527
|
setSelectedSession(initialSessionId);
|
|
455
528
|
setSessionPageLoading(false);
|
|
529
|
+
setSessionLoadError(null);
|
|
456
530
|
stopSessionLoadProgress();
|
|
457
531
|
dismissToast();
|
|
458
532
|
})
|
|
459
533
|
.catch((err: unknown) => {
|
|
460
534
|
if (sessionPageRequestIdRef.current !== requestId) return;
|
|
461
|
-
setAllLogs([]);
|
|
462
|
-
setLogPage(null);
|
|
463
535
|
setSessionPageLoading(false);
|
|
464
536
|
stopSessionLoadProgress();
|
|
465
|
-
|
|
537
|
+
const message = err instanceof Error ? err.message : "Failed to load session logs";
|
|
538
|
+
setSessionLoadError(message);
|
|
539
|
+
showErrorToast(message);
|
|
466
540
|
});
|
|
467
541
|
},
|
|
468
542
|
[
|
|
@@ -479,6 +553,7 @@ export function ProxyViewerContainer({
|
|
|
479
553
|
const requestId = sessionPageRequestIdRef.current + 1;
|
|
480
554
|
sessionPageRequestIdRef.current = requestId;
|
|
481
555
|
setSessionPageLoading(true);
|
|
556
|
+
setSessionLoadError(null);
|
|
482
557
|
startSessionLoadProgress("Reloading full session", requestId);
|
|
483
558
|
dismissToast();
|
|
484
559
|
|
|
@@ -521,6 +596,7 @@ export function ProxyViewerContainer({
|
|
|
521
596
|
});
|
|
522
597
|
setKnownSessions((prev) => mergeSessionIds(prev, [sessionId]));
|
|
523
598
|
setSessionPageLoading(false);
|
|
599
|
+
setSessionLoadError(null);
|
|
524
600
|
stopSessionLoadProgress();
|
|
525
601
|
if (page.hasNewer) {
|
|
526
602
|
showToast({
|
|
@@ -534,7 +610,9 @@ export function ProxyViewerContainer({
|
|
|
534
610
|
if (sessionPageRequestIdRef.current !== requestId) return;
|
|
535
611
|
setSessionPageLoading(false);
|
|
536
612
|
stopSessionLoadProgress();
|
|
537
|
-
|
|
613
|
+
const message = err instanceof Error ? err.message : "Failed to reload session logs";
|
|
614
|
+
setSessionLoadError(message);
|
|
615
|
+
showErrorToast(message);
|
|
538
616
|
});
|
|
539
617
|
},
|
|
540
618
|
[
|
|
@@ -597,7 +675,28 @@ export function ProxyViewerContainer({
|
|
|
597
675
|
useEffect(() => {
|
|
598
676
|
if (initialSessionId !== undefined) return undefined;
|
|
599
677
|
connectSSE();
|
|
678
|
+
const handleOffline = (): void => {
|
|
679
|
+
eventSourceRef.current?.close();
|
|
680
|
+
eventSourceRef.current = null;
|
|
681
|
+
if (reconnectTimeoutRef.current !== null) {
|
|
682
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
683
|
+
reconnectTimeoutRef.current = null;
|
|
684
|
+
}
|
|
685
|
+
if (staleTimeoutRef.current !== null) {
|
|
686
|
+
clearTimeout(staleTimeoutRef.current);
|
|
687
|
+
staleTimeoutRef.current = null;
|
|
688
|
+
}
|
|
689
|
+
setLiveConnection((state) => reduceLiveConnectionState(state, { type: "browser-offline" }));
|
|
690
|
+
};
|
|
691
|
+
const handleOnline = (): void => {
|
|
692
|
+
if (!automaticRetryEnabledRef.current) return;
|
|
693
|
+
connectSSE();
|
|
694
|
+
};
|
|
695
|
+
window.addEventListener("offline", handleOffline);
|
|
696
|
+
window.addEventListener("online", handleOnline);
|
|
600
697
|
return () => {
|
|
698
|
+
window.removeEventListener("offline", handleOffline);
|
|
699
|
+
window.removeEventListener("online", handleOnline);
|
|
601
700
|
if (eventSourceRef.current) {
|
|
602
701
|
eventSourceRef.current.close();
|
|
603
702
|
eventSourceRef.current = null;
|
|
@@ -606,6 +705,10 @@ export function ProxyViewerContainer({
|
|
|
606
705
|
clearTimeout(reconnectTimeoutRef.current);
|
|
607
706
|
reconnectTimeoutRef.current = null;
|
|
608
707
|
}
|
|
708
|
+
if (staleTimeoutRef.current !== null) {
|
|
709
|
+
clearTimeout(staleTimeoutRef.current);
|
|
710
|
+
staleTimeoutRef.current = null;
|
|
711
|
+
}
|
|
609
712
|
if (flushTimerRef.current !== null) {
|
|
610
713
|
clearTimeout(flushTimerRef.current);
|
|
611
714
|
flushTimerRef.current = null;
|
|
@@ -632,7 +735,10 @@ export function ProxyViewerContainer({
|
|
|
632
735
|
const target = document.getElementById(targetId);
|
|
633
736
|
if (target !== null) {
|
|
634
737
|
handledHashRef.current = hash;
|
|
635
|
-
target.scrollIntoView({
|
|
738
|
+
target.scrollIntoView({
|
|
739
|
+
block: "center",
|
|
740
|
+
behavior: preferredScrollBehavior(browserPrefersReducedMotion()),
|
|
741
|
+
});
|
|
636
742
|
if (target instanceof HTMLElement) {
|
|
637
743
|
highlightedTarget = target;
|
|
638
744
|
target.setAttribute("data-deep-link-highlight", "true");
|
|
@@ -926,11 +1032,16 @@ export function ProxyViewerContainer({
|
|
|
926
1032
|
onImportLogs={initialSessionId === undefined ? handleImportLogs : undefined}
|
|
927
1033
|
isLoading={
|
|
928
1034
|
initialSessionId === undefined
|
|
929
|
-
?
|
|
1035
|
+
? (liveConnection.status === "connecting" && liveConnection.lastSuccessAt === null) ||
|
|
1036
|
+
sessionPageLoading
|
|
930
1037
|
: sessionPageLoading
|
|
931
1038
|
}
|
|
1039
|
+
liveConnection={initialSessionId === undefined ? liveConnection : undefined}
|
|
1040
|
+
onRetryLiveConnection={retryLiveConnection}
|
|
1041
|
+
onStopLiveConnection={stopLiveConnection}
|
|
932
1042
|
pagination={pagination}
|
|
933
1043
|
sessionLoadProgress={sessionLoadProgress}
|
|
1044
|
+
sessionLoadError={sessionLoadError ?? undefined}
|
|
934
1045
|
sessionMemberships={sessionMemberships}
|
|
935
1046
|
viewMode={viewMode}
|
|
936
1047
|
captureMode={captureMode}
|
|
@@ -17,12 +17,14 @@ import {
|
|
|
17
17
|
} from "lucide-react";
|
|
18
18
|
import useSWR from "swr";
|
|
19
19
|
import {
|
|
20
|
+
EcosystemExecutionConfirmationResponseSchema,
|
|
20
21
|
EcosystemPackagesResponseSchema,
|
|
21
22
|
EcosystemTaskResponseSchema,
|
|
22
23
|
EcosystemTasksResponseSchema,
|
|
23
24
|
type EcosystemPackage,
|
|
24
25
|
type EcosystemPackageState,
|
|
25
26
|
type EcosystemRecipe,
|
|
27
|
+
type EcosystemExecutionConfirmation,
|
|
26
28
|
type EcosystemRunnerPreset,
|
|
27
29
|
type EcosystemTask,
|
|
28
30
|
type EcosystemTaskAction,
|
|
@@ -32,7 +34,14 @@ import { copyTextToClipboard } from "../../lib/clipboard";
|
|
|
32
34
|
import { cn } from "../../lib/utils";
|
|
33
35
|
import { Badge } from "../ui/badge";
|
|
34
36
|
import { Button } from "../ui/button";
|
|
35
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
Dialog,
|
|
39
|
+
DialogContent,
|
|
40
|
+
DialogDescription,
|
|
41
|
+
DialogHeader,
|
|
42
|
+
DialogTitle,
|
|
43
|
+
DialogTrigger,
|
|
44
|
+
} from "../ui/dialog";
|
|
36
45
|
import { INSPECTOR_ICON_TRIGGER_CLASS } from "../ui/icon-trigger";
|
|
37
46
|
|
|
38
47
|
type AgentLabDialogProps = {
|
|
@@ -49,6 +58,11 @@ type WorkflowCard = {
|
|
|
49
58
|
icon: typeof Activity;
|
|
50
59
|
};
|
|
51
60
|
|
|
61
|
+
type PendingEcosystemExecution = {
|
|
62
|
+
endpoint: string;
|
|
63
|
+
confirmation: EcosystemExecutionConfirmation;
|
|
64
|
+
};
|
|
65
|
+
|
|
52
66
|
const WORKFLOWS: WorkflowCard[] = [
|
|
53
67
|
{
|
|
54
68
|
id: "observe",
|
|
@@ -113,25 +127,55 @@ async function fetchEcosystemTasks(url: string) {
|
|
|
113
127
|
);
|
|
114
128
|
}
|
|
115
129
|
|
|
116
|
-
async function
|
|
130
|
+
async function prepareEcosystemTask(
|
|
117
131
|
packageId: string,
|
|
118
132
|
action: EcosystemTaskAction,
|
|
119
|
-
): Promise<
|
|
133
|
+
): Promise<PendingEcosystemExecution> {
|
|
134
|
+
const endpoint = `/api/ecosystem/packages/${encodeURIComponent(packageId)}/${action}`;
|
|
120
135
|
const response = await fetchJson(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
{
|
|
124
|
-
|
|
136
|
+
endpoint,
|
|
137
|
+
EcosystemExecutionConfirmationResponseSchema,
|
|
138
|
+
{
|
|
139
|
+
method: "POST",
|
|
140
|
+
headers: { "content-type": "application/json" },
|
|
141
|
+
body: JSON.stringify({ intent: "prepare" }),
|
|
142
|
+
},
|
|
143
|
+
(result) => `Failed to prepare ecosystem task: ${String(result.status)}`,
|
|
125
144
|
);
|
|
126
|
-
return response.
|
|
145
|
+
return { endpoint, confirmation: response.confirmation };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function prepareEcosystemRecipe(recipeId: string): Promise<PendingEcosystemExecution> {
|
|
149
|
+
const endpoint = `/api/ecosystem/recipes/${encodeURIComponent(recipeId)}/run`;
|
|
150
|
+
const response = await fetchJson(
|
|
151
|
+
endpoint,
|
|
152
|
+
EcosystemExecutionConfirmationResponseSchema,
|
|
153
|
+
{
|
|
154
|
+
method: "POST",
|
|
155
|
+
headers: { "content-type": "application/json" },
|
|
156
|
+
body: JSON.stringify({ intent: "prepare" }),
|
|
157
|
+
},
|
|
158
|
+
(result) => `Failed to prepare ecosystem recipe: ${String(result.status)}`,
|
|
159
|
+
);
|
|
160
|
+
return { endpoint, confirmation: response.confirmation };
|
|
127
161
|
}
|
|
128
162
|
|
|
129
|
-
async function
|
|
163
|
+
async function confirmEcosystemExecution(
|
|
164
|
+
pending: PendingEcosystemExecution,
|
|
165
|
+
): Promise<EcosystemTask> {
|
|
130
166
|
const response = await fetchJson(
|
|
131
|
-
|
|
167
|
+
pending.endpoint,
|
|
132
168
|
EcosystemTaskResponseSchema,
|
|
133
|
-
{
|
|
134
|
-
|
|
169
|
+
{
|
|
170
|
+
method: "POST",
|
|
171
|
+
headers: { "content-type": "application/json" },
|
|
172
|
+
body: JSON.stringify({
|
|
173
|
+
intent: "confirm",
|
|
174
|
+
nonce: pending.confirmation.nonce,
|
|
175
|
+
confirmation: pending.confirmation.phrase,
|
|
176
|
+
}),
|
|
177
|
+
},
|
|
178
|
+
(result) => `Failed to start ecosystem task: ${String(result.status)}`,
|
|
135
179
|
);
|
|
136
180
|
return response.task;
|
|
137
181
|
}
|
|
@@ -499,6 +543,84 @@ function RecentTaskRow({
|
|
|
499
543
|
);
|
|
500
544
|
}
|
|
501
545
|
|
|
546
|
+
function ExecutionConfirmationDialog({
|
|
547
|
+
pending,
|
|
548
|
+
busy,
|
|
549
|
+
onOpenChange,
|
|
550
|
+
onConfirm,
|
|
551
|
+
}: {
|
|
552
|
+
pending: PendingEcosystemExecution | null;
|
|
553
|
+
busy: boolean;
|
|
554
|
+
onOpenChange: (open: boolean) => void;
|
|
555
|
+
onConfirm: () => void;
|
|
556
|
+
}): JSX.Element {
|
|
557
|
+
const plan = pending?.confirmation.plan ?? null;
|
|
558
|
+
return (
|
|
559
|
+
<Dialog open={pending !== null} onOpenChange={onOpenChange}>
|
|
560
|
+
<DialogContent className="max-w-xl">
|
|
561
|
+
<DialogHeader>
|
|
562
|
+
<DialogTitle>Confirm local command</DialogTitle>
|
|
563
|
+
<DialogDescription>
|
|
564
|
+
Review the exact allowlisted action Agent Inspector will run on this machine.
|
|
565
|
+
</DialogDescription>
|
|
566
|
+
</DialogHeader>
|
|
567
|
+
{plan !== null && (
|
|
568
|
+
<div className="space-y-3">
|
|
569
|
+
<div className="grid gap-2 text-xs sm:grid-cols-2">
|
|
570
|
+
<div className="rounded-md border bg-muted/30 p-3">
|
|
571
|
+
<div className="text-muted-foreground">Source</div>
|
|
572
|
+
<div className="mt-1 break-words font-medium">{plan.source}</div>
|
|
573
|
+
</div>
|
|
574
|
+
<div className="rounded-md border bg-muted/30 p-3">
|
|
575
|
+
<div className="text-muted-foreground">Timeout</div>
|
|
576
|
+
<div className="mt-1 font-medium">{String(plan.timeoutMs / 1000)} seconds</div>
|
|
577
|
+
</div>
|
|
578
|
+
<div className="rounded-md border bg-muted/30 p-3 sm:col-span-2">
|
|
579
|
+
<div className="text-muted-foreground">Affected scope</div>
|
|
580
|
+
<div className="mt-1 font-medium">{plan.scope}</div>
|
|
581
|
+
</div>
|
|
582
|
+
</div>
|
|
583
|
+
<div>
|
|
584
|
+
<div className="mb-1 text-xs font-medium">Resolved command</div>
|
|
585
|
+
<pre className="max-h-32 overflow-auto whitespace-pre-wrap break-all rounded-md bg-black/80 p-3 font-mono text-xs leading-5 text-white/80">
|
|
586
|
+
{plan.command}
|
|
587
|
+
</pre>
|
|
588
|
+
</div>
|
|
589
|
+
<p className="text-xs text-muted-foreground">
|
|
590
|
+
Confirmation phrase: <code>{pending?.confirmation.phrase}</code>. The one-time
|
|
591
|
+
confirmation expires at{" "}
|
|
592
|
+
{new Date(pending?.confirmation.expiresAt ?? "").toLocaleTimeString()}.
|
|
593
|
+
</p>
|
|
594
|
+
</div>
|
|
595
|
+
)}
|
|
596
|
+
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
|
597
|
+
<Button
|
|
598
|
+
type="button"
|
|
599
|
+
variant="outline"
|
|
600
|
+
disabled={busy}
|
|
601
|
+
onClick={() => onOpenChange(false)}
|
|
602
|
+
>
|
|
603
|
+
Cancel
|
|
604
|
+
</Button>
|
|
605
|
+
<Button
|
|
606
|
+
type="button"
|
|
607
|
+
variant="secondary"
|
|
608
|
+
disabled={busy || plan === null}
|
|
609
|
+
onClick={onConfirm}
|
|
610
|
+
>
|
|
611
|
+
{busy ? (
|
|
612
|
+
<Loader2 className="size-3.5 animate-spin" />
|
|
613
|
+
) : (
|
|
614
|
+
<Terminal className="size-3.5" />
|
|
615
|
+
)}
|
|
616
|
+
{busy ? "Starting" : "Confirm & Run"}
|
|
617
|
+
</Button>
|
|
618
|
+
</div>
|
|
619
|
+
</DialogContent>
|
|
620
|
+
</Dialog>
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
|
|
502
624
|
export function AgentLabDialog({ currentSessionId, logCount }: AgentLabDialogProps): JSX.Element {
|
|
503
625
|
const [open, setOpen] = useState(false);
|
|
504
626
|
const [copied, setCopied] = useState(false);
|
|
@@ -506,6 +628,8 @@ export function AgentLabDialog({ currentSessionId, logCount }: AgentLabDialogPro
|
|
|
506
628
|
const [copiedRecipeId, setCopiedRecipeId] = useState<string | null>(null);
|
|
507
629
|
const [activeTaskId, setActiveTaskId] = useState<string | null>(null);
|
|
508
630
|
const [taskError, setTaskError] = useState<string | null>(null);
|
|
631
|
+
const [pendingExecution, setPendingExecution] = useState<PendingEcosystemExecution | null>(null);
|
|
632
|
+
const [confirmationBusy, setConfirmationBusy] = useState(false);
|
|
509
633
|
const response = useSWR("/api/ecosystem/packages", fetchEcosystemPackages, {
|
|
510
634
|
revalidateOnFocus: false,
|
|
511
635
|
revalidateIfStale: false,
|
|
@@ -560,14 +684,12 @@ export function AgentLabDialog({ currentSessionId, logCount }: AgentLabDialogPro
|
|
|
560
684
|
};
|
|
561
685
|
const runRecipe = (recipe: EcosystemRecipe): void => {
|
|
562
686
|
setTaskError(null);
|
|
563
|
-
void
|
|
564
|
-
.then((
|
|
565
|
-
|
|
566
|
-
void activeTaskResponse.mutate({ task }, { revalidate: true });
|
|
567
|
-
void tasksResponse.mutate();
|
|
687
|
+
void prepareEcosystemRecipe(recipe.id)
|
|
688
|
+
.then((pending) => {
|
|
689
|
+
setPendingExecution(pending);
|
|
568
690
|
})
|
|
569
691
|
.catch((error: unknown) => {
|
|
570
|
-
setTaskError(error instanceof Error ? error.message : "Failed to
|
|
692
|
+
setTaskError(error instanceof Error ? error.message : "Failed to prepare ecosystem recipe");
|
|
571
693
|
});
|
|
572
694
|
};
|
|
573
695
|
|
|
@@ -582,14 +704,31 @@ export function AgentLabDialog({ currentSessionId, logCount }: AgentLabDialogPro
|
|
|
582
704
|
|
|
583
705
|
const handleStartTask = (packageId: string, action: EcosystemTaskAction): void => {
|
|
584
706
|
setTaskError(null);
|
|
585
|
-
void
|
|
707
|
+
void prepareEcosystemTask(packageId, action)
|
|
708
|
+
.then((pending) => {
|
|
709
|
+
setPendingExecution(pending);
|
|
710
|
+
})
|
|
711
|
+
.catch((error: unknown) => {
|
|
712
|
+
setTaskError(error instanceof Error ? error.message : "Failed to prepare ecosystem task");
|
|
713
|
+
});
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
const confirmPendingExecution = (): void => {
|
|
717
|
+
if (pendingExecution === null) return;
|
|
718
|
+
setTaskError(null);
|
|
719
|
+
setConfirmationBusy(true);
|
|
720
|
+
void confirmEcosystemExecution(pendingExecution)
|
|
586
721
|
.then((task) => {
|
|
587
722
|
setActiveTaskId(task.id);
|
|
723
|
+
setPendingExecution(null);
|
|
588
724
|
void activeTaskResponse.mutate({ task }, { revalidate: true });
|
|
589
725
|
void tasksResponse.mutate();
|
|
590
726
|
})
|
|
591
727
|
.catch((error: unknown) => {
|
|
592
728
|
setTaskError(error instanceof Error ? error.message : "Failed to start ecosystem task");
|
|
729
|
+
})
|
|
730
|
+
.finally(() => {
|
|
731
|
+
setConfirmationBusy(false);
|
|
593
732
|
});
|
|
594
733
|
};
|
|
595
734
|
|
|
@@ -788,6 +927,14 @@ export function AgentLabDialog({ currentSessionId, logCount }: AgentLabDialogPro
|
|
|
788
927
|
<TaskConsole task={activeTask} />
|
|
789
928
|
</div>
|
|
790
929
|
</DialogContent>
|
|
930
|
+
<ExecutionConfirmationDialog
|
|
931
|
+
pending={pendingExecution}
|
|
932
|
+
busy={confirmationBusy}
|
|
933
|
+
onOpenChange={(confirmationOpen) => {
|
|
934
|
+
if (!confirmationOpen && !confirmationBusy) setPendingExecution(null);
|
|
935
|
+
}}
|
|
936
|
+
onConfirm={confirmPendingExecution}
|
|
937
|
+
/>
|
|
791
938
|
</Dialog>
|
|
792
939
|
);
|
|
793
940
|
}
|