@tonyclaw/agent-inspector 3.0.6 → 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-DsvO6QyJ.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-m1q-uVTl.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-CWP4pxOU.js → _sessionId-4vk9RMZ5.js} +1 -1
- package/.output/public/assets/index-DBEB1rHe.css +1 -0
- package/.output/public/assets/{index-zfZUz67K.js → index-DINLvnm9.js} +1 -1
- package/.output/public/assets/index-LPIGiUMP.js +1 -0
- package/.output/public/assets/{json-viewer-_4r1LBBE.js → json-viewer-CH5dvS2Y.js} +1 -1
- package/.output/public/assets/{main-C_nEHNUM.js → main-GWkZn4xy.js} +2 -2
- package/.output/server/{_sessionId-CTqCAmUF.mjs → _sessionId-EgsjdkN7.mjs} +3 -3
- package/.output/server/_ssr/{CompareDrawer-D6b6TZ-O.mjs → CompareDrawer-CaM8FYuD.mjs} +3 -3
- package/.output/server/_ssr/{ProxyViewerContainer-Dp7Va9_T.mjs → ProxyViewerContainer-EJIogcGd.mjs} +580 -344
- package/.output/server/_ssr/{ReplayDialog-D_KWQ1GY.mjs → ReplayDialog-BPpJ2-q8.mjs} +58 -23
- package/.output/server/_ssr/{RequestAnatomy-CPubXjMX.mjs → RequestAnatomy-DeQAWnZx.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-CsTiNoOE.mjs → ResponseView-Dshm8iQ7.mjs} +2 -2
- package/.output/server/_ssr/{StreamingChunkSequence-Bgbzt23U.mjs → StreamingChunkSequence-32yIU2hn.mjs} +30 -20
- package/.output/server/_ssr/{index-P1gSpuM5.mjs → index-XRcx9blU.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-10ciVGnc.mjs → json-viewer-C0aX5rqC.mjs} +2 -2
- package/.output/server/_ssr/{router-BJJ0VhsB.mjs → router-BI-Mvxz9.mjs} +441 -172
- package/.output/server/_tanstack-start-manifest_v-jKScklL_.mjs +4 -0
- package/.output/server/index.mjs +73 -73
- 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 +68 -21
- package/src/components/ProxyViewerContainer.tsx +30 -19
- package/src/components/alerts/AlertsDialog.tsx +3 -3
- package/src/components/clients/ClientLogo.tsx +22 -2
- package/src/components/ecosystem/AgentLabDialog.tsx +215 -12
- package/src/components/groups/GroupsDialog.tsx +31 -43
- package/src/components/providers/ImportWizardDialog.tsx +52 -102
- package/src/components/providers/ProviderCard.tsx +4 -4
- 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/CompareDrawer.tsx +1 -1
- package/src/components/proxy-viewer/ConversationHeader.tsx +2 -2
- 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-CfP-komD.js +0 -106
- package/.output/public/assets/ReplayDialog-CQlLX1Uo.js +0 -1
- package/.output/public/assets/ResponseView-DuLLeYSh.js +0 -3
- package/.output/public/assets/StreamingChunkSequence-CN4aRFBx.js +0 -1
- package/.output/public/assets/index-0pt1umWP.css +0 -1
- package/.output/public/assets/index-DSUEOPbC.js +0 -1
- package/.output/server/_tanstack-start-manifest_v-CltlYL3I.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));
|
|
@@ -304,7 +304,7 @@ function AlertList({
|
|
|
304
304
|
{categoryLabel(alert.category)}
|
|
305
305
|
</span>
|
|
306
306
|
</div>
|
|
307
|
-
<div className="mt-1
|
|
307
|
+
<div className="mt-1 text-sm font-semibold leading-snug">{alert.title}</div>
|
|
308
308
|
<div className="mt-1 line-clamp-2 text-xs text-muted-foreground">
|
|
309
309
|
{alert.message}
|
|
310
310
|
</div>
|
|
@@ -315,8 +315,8 @@ function AlertList({
|
|
|
315
315
|
</Badge>
|
|
316
316
|
) : null}
|
|
317
317
|
</div>
|
|
318
|
-
<div className="mt-2 flex min-w-0 items-
|
|
319
|
-
<span className="
|
|
318
|
+
<div className="mt-2 flex min-w-0 items-start justify-between gap-2 text-[11px] text-muted-foreground">
|
|
319
|
+
<span className="min-w-0 leading-snug">{providerModel(alert)}</span>
|
|
320
320
|
<span className="shrink-0">{formatTimestamp(alert.lastSeenAt)}</span>
|
|
321
321
|
</div>
|
|
322
322
|
</button>
|
|
@@ -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,16 +154,68 @@ 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
|
-
<div className="flex items-start justify-between gap-3">
|
|
210
|
+
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
134
211
|
<div className="min-w-0">
|
|
135
|
-
<div className="flex min-w-0 items-
|
|
212
|
+
<div className="flex min-w-0 items-start gap-2">
|
|
136
213
|
<PackageCheck className="size-4 shrink-0 text-white/55" />
|
|
137
|
-
<div className="
|
|
214
|
+
<div className="text-sm font-semibold leading-snug">{pkg.name}</div>
|
|
215
|
+
</div>
|
|
216
|
+
<div className="mt-1 break-all font-mono text-[11px] leading-snug text-white/35">
|
|
217
|
+
{pkg.npmName}
|
|
138
218
|
</div>
|
|
139
|
-
<div className="mt-1 truncate font-mono text-[11px] text-white/35">{pkg.npmName}</div>
|
|
140
219
|
</div>
|
|
141
220
|
<Badge className={cn("shrink-0", stateClassName(pkg.state))} variant="outline">
|
|
142
221
|
{stateLabel(pkg.state)}
|
|
@@ -152,10 +231,87 @@ function PackageStatusCard({ pkg }: { pkg: EcosystemPackage }): JSX.Element {
|
|
|
152
231
|
{pkg.installCommand}
|
|
153
232
|
</span>
|
|
154
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>
|
|
155
274
|
</div>
|
|
156
275
|
);
|
|
157
276
|
}
|
|
158
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
|
+
|
|
159
315
|
function WorkflowTile({ workflow }: { workflow: WorkflowCard }): JSX.Element {
|
|
160
316
|
const Icon = workflow.icon;
|
|
161
317
|
return (
|
|
@@ -163,7 +319,7 @@ function WorkflowTile({ workflow }: { workflow: WorkflowCard }): JSX.Element {
|
|
|
163
319
|
<div className="flex items-start justify-between gap-3">
|
|
164
320
|
<div className="flex min-w-0 items-center gap-2">
|
|
165
321
|
<Icon className="size-4 shrink-0 text-cyan-200/75" />
|
|
166
|
-
<div className="
|
|
322
|
+
<div className="text-sm font-semibold">{workflow.title}</div>
|
|
167
323
|
</div>
|
|
168
324
|
<span className="rounded bg-white/[0.04] px-2 py-0.5 font-mono text-[10px] text-white/45">
|
|
169
325
|
{workflow.label}
|
|
@@ -187,13 +343,13 @@ function buildSessionCommand(sessionId: string | null): string {
|
|
|
187
343
|
function RunnerPresetCard({ preset }: { preset: EcosystemRunnerPreset }): JSX.Element {
|
|
188
344
|
return (
|
|
189
345
|
<div className="rounded-lg bg-white/[0.025] p-3 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.055)]">
|
|
190
|
-
<div className="flex items-start justify-between gap-3">
|
|
346
|
+
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
191
347
|
<div className="min-w-0">
|
|
192
|
-
<div className="flex min-w-0 items-
|
|
348
|
+
<div className="flex min-w-0 items-start gap-2">
|
|
193
349
|
<Rocket className="size-4 shrink-0 text-cyan-200/75" />
|
|
194
|
-
<div className="
|
|
350
|
+
<div className="text-sm font-semibold leading-snug">{preset.name}</div>
|
|
195
351
|
</div>
|
|
196
|
-
<div className="mt-1 font-mono text-[11px] text-white/35">
|
|
352
|
+
<div className="mt-1 break-all font-mono text-[11px] leading-snug text-white/35">
|
|
197
353
|
{preset.binary} {preset.modelFlag}
|
|
198
354
|
{preset.variantFlag !== null ? ` ${preset.variantFlag}` : ""}
|
|
199
355
|
</div>
|
|
@@ -220,10 +376,25 @@ export function AgentLabDialog({ currentSessionId, logCount }: AgentLabDialogPro
|
|
|
220
376
|
const [open, setOpen] = useState(false);
|
|
221
377
|
const [copied, setCopied] = useState(false);
|
|
222
378
|
const [copiedPresets, setCopiedPresets] = useState(false);
|
|
379
|
+
const [activeTaskId, setActiveTaskId] = useState<string | null>(null);
|
|
380
|
+
const [taskError, setTaskError] = useState<string | null>(null);
|
|
223
381
|
const response = useSWR("/api/ecosystem/packages", fetchEcosystemPackages, {
|
|
224
382
|
revalidateOnFocus: false,
|
|
225
383
|
revalidateIfStale: false,
|
|
226
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
|
+
);
|
|
227
398
|
const packages = response.data?.packages ?? [];
|
|
228
399
|
const runnerPresets = response.data?.runnerPresets ?? [];
|
|
229
400
|
const installedCount = useMemo(
|
|
@@ -248,6 +419,26 @@ export function AgentLabDialog({ currentSessionId, logCount }: AgentLabDialogPro
|
|
|
248
419
|
});
|
|
249
420
|
};
|
|
250
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
|
+
|
|
251
442
|
return (
|
|
252
443
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
253
444
|
<DialogTrigger asChild>
|
|
@@ -360,7 +551,12 @@ export function AgentLabDialog({ currentSessionId, logCount }: AgentLabDialogPro
|
|
|
360
551
|
)}
|
|
361
552
|
<div className="grid gap-3 md:grid-cols-3">
|
|
362
553
|
{packages.map((pkg) => (
|
|
363
|
-
<PackageStatusCard
|
|
554
|
+
<PackageStatusCard
|
|
555
|
+
key={pkg.id}
|
|
556
|
+
pkg={pkg}
|
|
557
|
+
activeTask={activeTask}
|
|
558
|
+
onStartTask={handleStartTask}
|
|
559
|
+
/>
|
|
364
560
|
))}
|
|
365
561
|
{response.isLoading &&
|
|
366
562
|
packages.length === 0 &&
|
|
@@ -372,6 +568,13 @@ export function AgentLabDialog({ currentSessionId, logCount }: AgentLabDialogPro
|
|
|
372
568
|
))}
|
|
373
569
|
</div>
|
|
374
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} />
|
|
375
578
|
</div>
|
|
376
579
|
</DialogContent>
|
|
377
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
|
);
|
|
@@ -598,7 +586,7 @@ function GroupList({
|
|
|
598
586
|
)}
|
|
599
587
|
>
|
|
600
588
|
<div className="flex min-w-0 items-center justify-between gap-2">
|
|
601
|
-
<span className="min-w-0
|
|
589
|
+
<span className="min-w-0 text-sm font-medium leading-snug">{group.title}</span>
|
|
602
590
|
<Badge variant="outline" className={statusClassName(group.status)}>
|
|
603
591
|
{statusLabel(group.status)}
|
|
604
592
|
</Badge>
|
|
@@ -673,7 +661,7 @@ function GroupDetails({
|
|
|
673
661
|
<div className="flex min-w-0 flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
674
662
|
<div className="min-w-0 space-y-1">
|
|
675
663
|
<div className="flex min-w-0 flex-wrap items-center gap-2">
|
|
676
|
-
<h3 className="min-w-0
|
|
664
|
+
<h3 className="min-w-0 text-base font-semibold leading-snug">{group.title}</h3>
|
|
677
665
|
<Badge variant="outline" className={statusClassName(group.status)}>
|
|
678
666
|
{statusLabel(group.status)}
|
|
679
667
|
</Badge>
|
|
@@ -1119,7 +1107,7 @@ function MemberRow({
|
|
|
1119
1107
|
</div>
|
|
1120
1108
|
</td>
|
|
1121
1109
|
<td className="max-w-52 px-3 py-2">
|
|
1122
|
-
<span className="block
|
|
1110
|
+
<span className="block whitespace-normal leading-snug">{providerModel(member)}</span>
|
|
1123
1111
|
</td>
|
|
1124
1112
|
<td className="px-3 py-2">
|
|
1125
1113
|
<Badge variant="outline" className={statusClassName(member.status)}>
|