@tonyclaw/agent-inspector 3.0.7 → 3.0.8
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 +245 -171
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-C0Kbsm-Q.js → CompareDrawer-Bth_Ywyt.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-C73EU9Jq.js +106 -0
- package/.output/public/assets/ReplayDialog-C2YYAuTe.js +1 -0
- package/.output/public/assets/{RequestAnatomy-fa0JXy5p.js → RequestAnatomy-8k9dAX2j.js} +1 -1
- package/.output/public/assets/ResponseView-DEL6JcXm.js +3 -0
- package/.output/public/assets/StreamingChunkSequence-DngmoR9j.js +1 -0
- package/.output/public/assets/{_sessionId-D5fYH2C0.js → _sessionId-4vk9RMZ5.js} +1 -1
- package/.output/public/assets/index-DBEB1rHe.css +1 -0
- package/.output/public/assets/{index-BEsqvywM.js → index-DINLvnm9.js} +1 -1
- package/.output/public/assets/index-LPIGiUMP.js +1 -0
- package/.output/public/assets/{json-viewer-B0QRbMSf.js → json-viewer-CH5dvS2Y.js} +1 -1
- package/.output/public/assets/{main-CuKZD7n0.js → main-GWkZn4xy.js} +2 -2
- package/.output/server/{_sessionId-DdBTK3qm.mjs → _sessionId-EgsjdkN7.mjs} +3 -3
- package/.output/server/_ssr/{CompareDrawer-m82cVZ73.mjs → CompareDrawer-CaM8FYuD.mjs} +2 -2
- package/.output/server/_ssr/{ProxyViewerContainer-7geuf78f.mjs → ProxyViewerContainer-EJIogcGd.mjs} +545 -309
- package/.output/server/_ssr/{ReplayDialog-B8RlWLLH.mjs → ReplayDialog-BPpJ2-q8.mjs} +58 -23
- package/.output/server/_ssr/{RequestAnatomy-BE7pmwpv.mjs → RequestAnatomy-DeQAWnZx.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-D-sDsdQr.mjs → ResponseView-Dshm8iQ7.mjs} +2 -2
- package/.output/server/_ssr/{StreamingChunkSequence-DU_hThJr.mjs → StreamingChunkSequence-32yIU2hn.mjs} +30 -20
- package/.output/server/_ssr/{index-D0sV8UbE.mjs → index-XRcx9blU.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-CaDh1zrw.mjs → json-viewer-C0aX5rqC.mjs} +2 -2
- package/.output/server/_ssr/{router-Zh2iZ_u0.mjs → router-BI-Mvxz9.mjs} +441 -172
- package/.output/server/_tanstack-start-manifest_v-jKScklL_.mjs +4 -0
- package/.output/server/index.mjs +71 -71
- package/package.json +1 -1
- package/src/assets/IDE/opencode.svg +1 -1
- package/src/assets/IDE/xiaomimimo.svg +1 -1
- package/src/cli/detect-tools.ts +2 -27
- package/src/cli/templates/codex-skill-onboard.ts +1 -1
- package/src/cli/templates/skill-onboard.ts +2 -3
- package/src/cli.ts +9 -86
- package/src/components/ProxyViewer.tsx +55 -8
- package/src/components/ProxyViewerContainer.tsx +30 -19
- package/src/components/clients/ClientLogo.tsx +22 -2
- package/src/components/ecosystem/AgentLabDialog.tsx +204 -3
- package/src/components/groups/GroupsDialog.tsx +28 -40
- package/src/components/providers/ImportWizardDialog.tsx +51 -101
- package/src/components/providers/ProvidersPanel.tsx +82 -68
- package/src/components/providers/SettingsDialog.tsx +11 -12
- package/src/components/proxy-viewer/AgentTraceSummary.tsx +15 -23
- package/src/components/proxy-viewer/ReplayDialog.tsx +65 -22
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +33 -20
- package/src/lib/apiClient.ts +43 -10
- package/src/lib/ecosystemContract.ts +32 -0
- package/src/lib/export-logs.ts +37 -3
- package/src/proxy/ecosystemRegistry.ts +244 -0
- package/src/proxy/ecosystemTasks.ts +153 -0
- package/src/proxy/platformCommands.ts +212 -0
- package/src/proxy/socketTracker.ts +84 -143
- package/src/routes/api/ecosystem.packages.$packageId.help.ts +16 -0
- package/src/routes/api/ecosystem.packages.$packageId.install.ts +16 -0
- package/src/routes/api/ecosystem.packages.$packageId.runner-presets.ts +19 -0
- package/src/routes/api/ecosystem.packages.$packageId.upgrade.ts +16 -0
- package/src/routes/api/ecosystem.packages.ts +1 -248
- package/src/routes/api/ecosystem.tasks.$taskId.ts +16 -0
- package/src/routes/api/ecosystem.tasks.ts +12 -0
- package/.output/public/assets/ProxyViewerContainer-p-rpYyiF.js +0 -106
- package/.output/public/assets/ReplayDialog-CM3dhP8v.js +0 -1
- package/.output/public/assets/ResponseView-Bq6sC7Ay.js +0 -3
- package/.output/public/assets/StreamingChunkSequence-CGOun3Df.js +0 -1
- package/.output/public/assets/index-C29Olfzt.js +0 -1
- package/.output/public/assets/index-DgANE0r3.css +0 -1
- package/.output/server/_tanstack-start-manifest_v-LCE-2AaF.mjs +0 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback, useRef, useMemo, useTransition, type JSX } from "react";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { CapturedLogSchema, type CapturedLog } from "../contracts";
|
|
4
|
-
import { fetchJson } from "../lib/apiClient";
|
|
4
|
+
import { fetchJson, fetchJsonWithTimeout } from "../lib/apiClient";
|
|
5
5
|
import { InspectorGroupsListResponseSchema, type InspectorGroup } from "../lib/groupContract";
|
|
6
6
|
import { ImportLogsResponseSchema, type ImportLogsResponse } from "../lib/logImportContract";
|
|
7
7
|
import { useStripConfig } from "../lib/useStripConfig";
|
|
@@ -118,6 +118,8 @@ const HASH_HIGHLIGHT_MS = 1800;
|
|
|
118
118
|
const SESSION_PAGE_LIMIT = 100;
|
|
119
119
|
const SESSION_RELOAD_PAGE_LIMIT = 500;
|
|
120
120
|
const MAX_SESSION_RELOAD_PAGES = 200;
|
|
121
|
+
const SESSION_LOGS_FETCH_TIMEOUT_MS = 20_000;
|
|
122
|
+
const SESSION_MEMBERSHIP_FETCH_TIMEOUT_MS = 10_000;
|
|
121
123
|
|
|
122
124
|
function buildLogsStreamUrl(sessionId: string | undefined): string {
|
|
123
125
|
const params = new URLSearchParams();
|
|
@@ -167,6 +169,18 @@ function buildLogIndex(logs: readonly CapturedLog[]): Map<number, number> {
|
|
|
167
169
|
return idx;
|
|
168
170
|
}
|
|
169
171
|
|
|
172
|
+
function fetchSessionLogsPage(
|
|
173
|
+
sessionId: string,
|
|
174
|
+
request: SessionPageRequest,
|
|
175
|
+
limit = SESSION_PAGE_LIMIT,
|
|
176
|
+
): Promise<LogCursorPage> {
|
|
177
|
+
return fetchJsonWithTimeout(
|
|
178
|
+
buildSessionLogsPageUrl(sessionId, request, limit),
|
|
179
|
+
LogCursorPageSchema,
|
|
180
|
+
SESSION_LOGS_FETCH_TIMEOUT_MS,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
170
184
|
export function mergeLogsById(
|
|
171
185
|
previousLogs: readonly CapturedLog[],
|
|
172
186
|
incomingLogs: readonly CapturedLog[],
|
|
@@ -357,7 +371,7 @@ export function ProxyViewerContainer({
|
|
|
357
371
|
setSessionPageLoading(true);
|
|
358
372
|
dismissToast();
|
|
359
373
|
|
|
360
|
-
void
|
|
374
|
+
void fetchSessionLogsPage(initialSessionId, request)
|
|
361
375
|
.then((page) => {
|
|
362
376
|
if (sessionPageRequestIdRef.current !== requestId) return;
|
|
363
377
|
const nextLogs = page.logs;
|
|
@@ -388,9 +402,10 @@ export function ProxyViewerContainer({
|
|
|
388
402
|
|
|
389
403
|
void (async () => {
|
|
390
404
|
const collected: CapturedLog[] = [];
|
|
391
|
-
let page = await
|
|
392
|
-
|
|
393
|
-
|
|
405
|
+
let page = await fetchSessionLogsPage(
|
|
406
|
+
sessionId,
|
|
407
|
+
{ kind: "oldest" },
|
|
408
|
+
SESSION_RELOAD_PAGE_LIMIT,
|
|
394
409
|
);
|
|
395
410
|
collected.push(...page.logs);
|
|
396
411
|
|
|
@@ -400,13 +415,10 @@ export function ProxyViewerContainer({
|
|
|
400
415
|
page.newestLogId !== null &&
|
|
401
416
|
loadedPages < MAX_SESSION_RELOAD_PAGES
|
|
402
417
|
) {
|
|
403
|
-
page = await
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
SESSION_RELOAD_PAGE_LIMIT,
|
|
408
|
-
),
|
|
409
|
-
LogCursorPageSchema,
|
|
418
|
+
page = await fetchSessionLogsPage(
|
|
419
|
+
sessionId,
|
|
420
|
+
{ kind: "newer", afterLogId: page.newestLogId },
|
|
421
|
+
SESSION_RELOAD_PAGE_LIMIT,
|
|
410
422
|
);
|
|
411
423
|
collected.push(...page.logs);
|
|
412
424
|
loadedPages += 1;
|
|
@@ -444,13 +456,12 @@ export function ProxyViewerContainer({
|
|
|
444
456
|
}
|
|
445
457
|
|
|
446
458
|
let cancelled = false;
|
|
447
|
-
void
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
})
|
|
459
|
+
void fetchJsonWithTimeout(
|
|
460
|
+
"/api/groups",
|
|
461
|
+
InspectorGroupsListResponseSchema,
|
|
462
|
+
SESSION_MEMBERSHIP_FETCH_TIMEOUT_MS,
|
|
463
|
+
)
|
|
464
|
+
.then((response) => response.groups)
|
|
454
465
|
.then((groups) => {
|
|
455
466
|
if (cancelled || groups === null) return;
|
|
456
467
|
setSessionMemberships(buildSessionMembershipEvidence(groups, initialSessionId));
|
|
@@ -67,6 +67,20 @@ const CLIENT_LOGOS: Record<Exclude<ClientApp, "unknown">, string> = {
|
|
|
67
67
|
"claude-code": ClaudeCodeLogoSvg,
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
+
const CLIENT_LOGO_FRAMES: Record<Exclude<ClientApp, "unknown">, string> = {
|
|
71
|
+
codex: "border-cyan-200/20 bg-cyan-300/[0.08]",
|
|
72
|
+
opencode: "border-sky-200/25 bg-sky-300/[0.12]",
|
|
73
|
+
"mimo-code": "border-orange-200/25 bg-orange-400/[0.12]",
|
|
74
|
+
"claude-code": "border-amber-100/20 bg-amber-300/[0.09]",
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const CLIENT_LOGO_IMAGES: Record<Exclude<ClientApp, "unknown">, string> = {
|
|
78
|
+
codex: "p-0.5",
|
|
79
|
+
opencode: "p-1 drop-shadow-[0_0_8px_rgba(125,211,252,0.35)]",
|
|
80
|
+
"mimo-code": "p-0.5 drop-shadow-[0_0_8px_rgba(255,105,0,0.35)]",
|
|
81
|
+
"claude-code": "p-0.5",
|
|
82
|
+
};
|
|
83
|
+
|
|
70
84
|
export function ClientLogo({
|
|
71
85
|
client,
|
|
72
86
|
className,
|
|
@@ -82,13 +96,19 @@ export function ClientLogo({
|
|
|
82
96
|
return (
|
|
83
97
|
<span
|
|
84
98
|
className={cn(
|
|
85
|
-
"
|
|
99
|
+
"inline-flex size-6 shrink-0 items-center justify-center rounded-md border p-0 shadow-sm",
|
|
100
|
+
CLIENT_LOGO_FRAMES[client],
|
|
86
101
|
className,
|
|
87
102
|
)}
|
|
88
103
|
aria-label={label}
|
|
89
104
|
title={label}
|
|
90
105
|
>
|
|
91
|
-
<img
|
|
106
|
+
<img
|
|
107
|
+
src={logoSrc}
|
|
108
|
+
alt=""
|
|
109
|
+
className={cn("size-full object-contain", CLIENT_LOGO_IMAGES[client])}
|
|
110
|
+
aria-hidden="true"
|
|
111
|
+
/>
|
|
92
112
|
</span>
|
|
93
113
|
);
|
|
94
114
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { type JSX, useMemo, useState } from "react";
|
|
1
|
+
import { type JSX, useEffect, useMemo, useState } from "react";
|
|
2
2
|
import {
|
|
3
3
|
Activity,
|
|
4
4
|
Beaker,
|
|
5
5
|
Check,
|
|
6
6
|
Copy,
|
|
7
|
+
Download,
|
|
7
8
|
FlaskConical,
|
|
9
|
+
Loader2,
|
|
8
10
|
Network,
|
|
9
11
|
PackageCheck,
|
|
10
12
|
RefreshCw,
|
|
@@ -15,9 +17,12 @@ import {
|
|
|
15
17
|
import useSWR from "swr";
|
|
16
18
|
import {
|
|
17
19
|
EcosystemPackagesResponseSchema,
|
|
20
|
+
EcosystemTaskResponseSchema,
|
|
18
21
|
type EcosystemPackage,
|
|
19
22
|
type EcosystemPackageState,
|
|
20
23
|
type EcosystemRunnerPreset,
|
|
24
|
+
type EcosystemTask,
|
|
25
|
+
type EcosystemTaskAction,
|
|
21
26
|
} from "../../lib/ecosystemContract";
|
|
22
27
|
import { fetchJson } from "../../lib/apiClient";
|
|
23
28
|
import { copyTextToClipboard } from "../../lib/clipboard";
|
|
@@ -87,6 +92,28 @@ async function fetchEcosystemPackages(url: string) {
|
|
|
87
92
|
);
|
|
88
93
|
}
|
|
89
94
|
|
|
95
|
+
async function fetchEcosystemTask(url: string) {
|
|
96
|
+
return fetchJson(
|
|
97
|
+
url,
|
|
98
|
+
EcosystemTaskResponseSchema,
|
|
99
|
+
undefined,
|
|
100
|
+
(response) => `Failed to load ecosystem task: ${String(response.status)}`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function startEcosystemTask(
|
|
105
|
+
packageId: string,
|
|
106
|
+
action: EcosystemTaskAction,
|
|
107
|
+
): Promise<EcosystemTask> {
|
|
108
|
+
const response = await fetchJson(
|
|
109
|
+
`/api/ecosystem/packages/${encodeURIComponent(packageId)}/${action}`,
|
|
110
|
+
EcosystemTaskResponseSchema,
|
|
111
|
+
{ method: "POST" },
|
|
112
|
+
(result) => `Failed to start ecosystem task: ${String(result.status)}`,
|
|
113
|
+
);
|
|
114
|
+
return response.task;
|
|
115
|
+
}
|
|
116
|
+
|
|
90
117
|
function stateLabel(state: EcosystemPackageState): string {
|
|
91
118
|
switch (state) {
|
|
92
119
|
case "installed":
|
|
@@ -127,7 +154,57 @@ function versionLabel(pkg: EcosystemPackage): string {
|
|
|
127
154
|
return "not published";
|
|
128
155
|
}
|
|
129
156
|
|
|
130
|
-
function
|
|
157
|
+
function taskActionLabel(action: EcosystemTaskAction): string {
|
|
158
|
+
switch (action) {
|
|
159
|
+
case "install":
|
|
160
|
+
return "Install";
|
|
161
|
+
case "upgrade":
|
|
162
|
+
return "Upgrade";
|
|
163
|
+
case "help":
|
|
164
|
+
return "Help";
|
|
165
|
+
case "runner-presets":
|
|
166
|
+
return "Presets";
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function taskStatusLabel(task: EcosystemTask): string {
|
|
171
|
+
switch (task.status) {
|
|
172
|
+
case "queued":
|
|
173
|
+
return "Queued";
|
|
174
|
+
case "running":
|
|
175
|
+
return `${taskActionLabel(task.action)} running`;
|
|
176
|
+
case "succeeded":
|
|
177
|
+
return `${taskActionLabel(task.action)} complete`;
|
|
178
|
+
case "failed":
|
|
179
|
+
return `${taskActionLabel(task.action)} failed`;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function packagePrimaryAction(pkg: EcosystemPackage): EcosystemTaskAction | null {
|
|
184
|
+
switch (pkg.state) {
|
|
185
|
+
case "available":
|
|
186
|
+
return "install";
|
|
187
|
+
case "update-available":
|
|
188
|
+
return "upgrade";
|
|
189
|
+
case "installed":
|
|
190
|
+
case "planned":
|
|
191
|
+
case "unknown":
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function PackageStatusCard({
|
|
197
|
+
pkg,
|
|
198
|
+
activeTask,
|
|
199
|
+
onStartTask,
|
|
200
|
+
}: {
|
|
201
|
+
pkg: EcosystemPackage;
|
|
202
|
+
activeTask: EcosystemTask | null;
|
|
203
|
+
onStartTask: (packageId: string, action: EcosystemTaskAction) => void;
|
|
204
|
+
}): JSX.Element {
|
|
205
|
+
const primaryAction = packagePrimaryAction(pkg);
|
|
206
|
+
const packageTaskRunning =
|
|
207
|
+
activeTask !== null && activeTask.packageId === pkg.id && activeTask.status === "running";
|
|
131
208
|
return (
|
|
132
209
|
<div className="rounded-lg bg-white/[0.025] p-3 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.055)]">
|
|
133
210
|
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
@@ -154,10 +231,87 @@ function PackageStatusCard({ pkg }: { pkg: EcosystemPackage }): JSX.Element {
|
|
|
154
231
|
{pkg.installCommand}
|
|
155
232
|
</span>
|
|
156
233
|
</div>
|
|
234
|
+
<div className="mt-3 flex flex-wrap items-center gap-2">
|
|
235
|
+
{primaryAction !== null && (
|
|
236
|
+
<Button
|
|
237
|
+
type="button"
|
|
238
|
+
size="sm"
|
|
239
|
+
variant="secondary"
|
|
240
|
+
disabled={packageTaskRunning}
|
|
241
|
+
onClick={() => onStartTask(pkg.id, primaryAction)}
|
|
242
|
+
>
|
|
243
|
+
{packageTaskRunning ? (
|
|
244
|
+
<Loader2 className="size-3.5 animate-spin" />
|
|
245
|
+
) : (
|
|
246
|
+
<Download className="size-3.5" />
|
|
247
|
+
)}
|
|
248
|
+
{taskActionLabel(primaryAction)}
|
|
249
|
+
</Button>
|
|
250
|
+
)}
|
|
251
|
+
<Button
|
|
252
|
+
type="button"
|
|
253
|
+
size="sm"
|
|
254
|
+
variant="outline"
|
|
255
|
+
disabled={packageTaskRunning || pkg.state === "planned"}
|
|
256
|
+
onClick={() => onStartTask(pkg.id, "help")}
|
|
257
|
+
>
|
|
258
|
+
<Terminal className="size-3.5" />
|
|
259
|
+
Help
|
|
260
|
+
</Button>
|
|
261
|
+
{pkg.id === "eval-harness" && (
|
|
262
|
+
<Button
|
|
263
|
+
type="button"
|
|
264
|
+
size="sm"
|
|
265
|
+
variant="ghost"
|
|
266
|
+
disabled={packageTaskRunning || pkg.state === "planned"}
|
|
267
|
+
onClick={() => onStartTask(pkg.id, "runner-presets")}
|
|
268
|
+
>
|
|
269
|
+
<Beaker className="size-3.5" />
|
|
270
|
+
Presets
|
|
271
|
+
</Button>
|
|
272
|
+
)}
|
|
273
|
+
</div>
|
|
157
274
|
</div>
|
|
158
275
|
);
|
|
159
276
|
}
|
|
160
277
|
|
|
278
|
+
function TaskConsole({ task }: { task: EcosystemTask | null }): JSX.Element | null {
|
|
279
|
+
if (task === null) return null;
|
|
280
|
+
const isRunning = task.status === "queued" || task.status === "running";
|
|
281
|
+
return (
|
|
282
|
+
<section className="mt-3 rounded-lg bg-white/[0.025] p-3 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.055)]">
|
|
283
|
+
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
284
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
285
|
+
{isRunning ? (
|
|
286
|
+
<Loader2 className="size-4 animate-spin text-cyan-200/75" />
|
|
287
|
+
) : (
|
|
288
|
+
<Terminal className="size-4 text-white/55" />
|
|
289
|
+
)}
|
|
290
|
+
<div className="min-w-0">
|
|
291
|
+
<div className="text-sm font-semibold">{taskStatusLabel(task)}</div>
|
|
292
|
+
<div className="break-all font-mono text-[11px] text-white/35">{task.command}</div>
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
<Badge
|
|
296
|
+
variant="outline"
|
|
297
|
+
className={cn(
|
|
298
|
+
"shrink-0",
|
|
299
|
+
task.status === "failed"
|
|
300
|
+
? "border-red-400/20 bg-red-400/8 text-red-100"
|
|
301
|
+
: "border-cyan-300/20 bg-cyan-300/8 text-cyan-100",
|
|
302
|
+
)}
|
|
303
|
+
>
|
|
304
|
+
{task.status}
|
|
305
|
+
</Badge>
|
|
306
|
+
</div>
|
|
307
|
+
<pre className="mt-3 max-h-56 overflow-auto whitespace-pre-wrap rounded-md bg-black/30 p-3 font-mono text-[11px] leading-5 text-white/60 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.045)]">
|
|
308
|
+
{task.output.trim().length > 0 ? task.output : "Waiting for npm output..."}
|
|
309
|
+
</pre>
|
|
310
|
+
{task.error !== null && <div className="mt-2 text-xs text-red-100">{task.error}</div>}
|
|
311
|
+
</section>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
161
315
|
function WorkflowTile({ workflow }: { workflow: WorkflowCard }): JSX.Element {
|
|
162
316
|
const Icon = workflow.icon;
|
|
163
317
|
return (
|
|
@@ -222,10 +376,25 @@ export function AgentLabDialog({ currentSessionId, logCount }: AgentLabDialogPro
|
|
|
222
376
|
const [open, setOpen] = useState(false);
|
|
223
377
|
const [copied, setCopied] = useState(false);
|
|
224
378
|
const [copiedPresets, setCopiedPresets] = useState(false);
|
|
379
|
+
const [activeTaskId, setActiveTaskId] = useState<string | null>(null);
|
|
380
|
+
const [taskError, setTaskError] = useState<string | null>(null);
|
|
225
381
|
const response = useSWR("/api/ecosystem/packages", fetchEcosystemPackages, {
|
|
226
382
|
revalidateOnFocus: false,
|
|
227
383
|
revalidateIfStale: false,
|
|
228
384
|
});
|
|
385
|
+
const activeTaskResponse = useSWR(
|
|
386
|
+
activeTaskId === null ? null : `/api/ecosystem/tasks/${activeTaskId}`,
|
|
387
|
+
fetchEcosystemTask,
|
|
388
|
+
{
|
|
389
|
+
refreshInterval: (taskResponse) => {
|
|
390
|
+
const task = taskResponse?.task;
|
|
391
|
+
return task !== undefined && (task.status === "queued" || task.status === "running")
|
|
392
|
+
? 900
|
|
393
|
+
: 0;
|
|
394
|
+
},
|
|
395
|
+
revalidateOnFocus: false,
|
|
396
|
+
},
|
|
397
|
+
);
|
|
229
398
|
const packages = response.data?.packages ?? [];
|
|
230
399
|
const runnerPresets = response.data?.runnerPresets ?? [];
|
|
231
400
|
const installedCount = useMemo(
|
|
@@ -250,6 +419,26 @@ export function AgentLabDialog({ currentSessionId, logCount }: AgentLabDialogPro
|
|
|
250
419
|
});
|
|
251
420
|
};
|
|
252
421
|
|
|
422
|
+
const activeTask = activeTaskResponse.data?.task ?? null;
|
|
423
|
+
|
|
424
|
+
useEffect(() => {
|
|
425
|
+
if (activeTask === null) return;
|
|
426
|
+
if (activeTask.status !== "succeeded") return;
|
|
427
|
+
void response.mutate();
|
|
428
|
+
}, [activeTask, response]);
|
|
429
|
+
|
|
430
|
+
const handleStartTask = (packageId: string, action: EcosystemTaskAction): void => {
|
|
431
|
+
setTaskError(null);
|
|
432
|
+
void startEcosystemTask(packageId, action)
|
|
433
|
+
.then((task) => {
|
|
434
|
+
setActiveTaskId(task.id);
|
|
435
|
+
void activeTaskResponse.mutate({ task }, { revalidate: true });
|
|
436
|
+
})
|
|
437
|
+
.catch((error: unknown) => {
|
|
438
|
+
setTaskError(error instanceof Error ? error.message : "Failed to start ecosystem task");
|
|
439
|
+
});
|
|
440
|
+
};
|
|
441
|
+
|
|
253
442
|
return (
|
|
254
443
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
255
444
|
<DialogTrigger asChild>
|
|
@@ -362,7 +551,12 @@ export function AgentLabDialog({ currentSessionId, logCount }: AgentLabDialogPro
|
|
|
362
551
|
)}
|
|
363
552
|
<div className="grid gap-3 md:grid-cols-3">
|
|
364
553
|
{packages.map((pkg) => (
|
|
365
|
-
<PackageStatusCard
|
|
554
|
+
<PackageStatusCard
|
|
555
|
+
key={pkg.id}
|
|
556
|
+
pkg={pkg}
|
|
557
|
+
activeTask={activeTask}
|
|
558
|
+
onStartTask={handleStartTask}
|
|
559
|
+
/>
|
|
366
560
|
))}
|
|
367
561
|
{response.isLoading &&
|
|
368
562
|
packages.length === 0 &&
|
|
@@ -374,6 +568,13 @@ export function AgentLabDialog({ currentSessionId, logCount }: AgentLabDialogPro
|
|
|
374
568
|
))}
|
|
375
569
|
</div>
|
|
376
570
|
</section>
|
|
571
|
+
|
|
572
|
+
{taskError !== null && (
|
|
573
|
+
<div className="mt-3 rounded-lg bg-red-500/8 p-3 text-xs text-red-100 shadow-[inset_0_0_0_1px_rgba(248,113,113,0.16)]">
|
|
574
|
+
{taskError}
|
|
575
|
+
</div>
|
|
576
|
+
)}
|
|
577
|
+
<TaskConsole task={activeTask} />
|
|
377
578
|
</div>
|
|
378
579
|
</DialogContent>
|
|
379
580
|
</Dialog>
|
|
@@ -16,7 +16,7 @@ import { Badge } from "../ui/badge";
|
|
|
16
16
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "../ui/dialog";
|
|
17
17
|
import { INSPECTOR_ICON_TRIGGER_CLASS } from "../ui/icon-trigger";
|
|
18
18
|
import { cn } from "../../lib/utils";
|
|
19
|
-
import {
|
|
19
|
+
import { fetchJsonWithTimeout } from "../../lib/apiClient";
|
|
20
20
|
import {
|
|
21
21
|
DeleteInspectorGroupResponseSchema,
|
|
22
22
|
DeleteInspectorGroupsResponseSchema,
|
|
@@ -53,6 +53,8 @@ type GroupMemberRow = {
|
|
|
53
53
|
source: "group" | "export" | "evidence";
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
+
const GROUP_OPERATION_TIMEOUT_MS = 20_000;
|
|
57
|
+
|
|
56
58
|
export function groupDeletePath(groupId: string, deleteEvidence: boolean): string {
|
|
57
59
|
const path = `/api/groups/${encodeURIComponent(groupId)}`;
|
|
58
60
|
return deleteEvidence ? `${path}?deleteEvidence=1` : path;
|
|
@@ -333,19 +335,13 @@ export function GroupsDialog(): JSX.Element {
|
|
|
333
335
|
setExportError(null);
|
|
334
336
|
|
|
335
337
|
try {
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
tone: "error",
|
|
344
|
-
});
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
const parsed = await parseJsonResponse(response, DeleteInspectorGroupsResponseSchema);
|
|
338
|
+
const parsed = await fetchJsonWithTimeout(
|
|
339
|
+
groupsClearPath(false),
|
|
340
|
+
DeleteInspectorGroupsResponseSchema,
|
|
341
|
+
GROUP_OPERATION_TIMEOUT_MS,
|
|
342
|
+
{ method: "DELETE" },
|
|
343
|
+
(response) => `Clear failed with status ${String(response.status)}`,
|
|
344
|
+
);
|
|
349
345
|
setExportResult(null);
|
|
350
346
|
setSelectedGroupId(null);
|
|
351
347
|
await mutate();
|
|
@@ -370,20 +366,17 @@ export function GroupsDialog(): JSX.Element {
|
|
|
370
366
|
setExportError(null);
|
|
371
367
|
|
|
372
368
|
try {
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
)
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const parsed = await parseJsonResponse(response, GroupEvidenceExportResultSchema);
|
|
369
|
+
const parsed = await fetchJsonWithTimeout(
|
|
370
|
+
`/api/groups/${encodeURIComponent(group.id)}/evidence`,
|
|
371
|
+
GroupEvidenceExportResultSchema,
|
|
372
|
+
GROUP_OPERATION_TIMEOUT_MS,
|
|
373
|
+
{
|
|
374
|
+
method: "POST",
|
|
375
|
+
headers: { "content-type": "application/json" },
|
|
376
|
+
body: JSON.stringify({ latestLogLimit: 10 }),
|
|
377
|
+
},
|
|
378
|
+
(response) => `Export failed with status ${String(response.status)}`,
|
|
379
|
+
);
|
|
387
380
|
setExportResult(parsed);
|
|
388
381
|
await mutate();
|
|
389
382
|
await mutateEvidence();
|
|
@@ -424,18 +417,13 @@ export function GroupsDialog(): JSX.Element {
|
|
|
424
417
|
setDeleteError(null);
|
|
425
418
|
|
|
426
419
|
try {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
);
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
await parseJsonResponse(response, DeleteInspectorGroupResponseSchema);
|
|
420
|
+
await fetchJsonWithTimeout(
|
|
421
|
+
groupDeletePath(group.id, deleteEvidence),
|
|
422
|
+
DeleteInspectorGroupResponseSchema,
|
|
423
|
+
GROUP_OPERATION_TIMEOUT_MS,
|
|
424
|
+
{ method: "DELETE" },
|
|
425
|
+
(response) => `Delete failed with status ${String(response.status)}`,
|
|
426
|
+
);
|
|
439
427
|
setExportResult((current) =>
|
|
440
428
|
current !== null && current.group.id === group.id ? null : current,
|
|
441
429
|
);
|