@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.
Files changed (66) hide show
  1. package/.output/cli.js +245 -171
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-C0Kbsm-Q.js → CompareDrawer-Bth_Ywyt.js} +1 -1
  4. package/.output/public/assets/ProxyViewerContainer-C73EU9Jq.js +106 -0
  5. package/.output/public/assets/ReplayDialog-C2YYAuTe.js +1 -0
  6. package/.output/public/assets/{RequestAnatomy-fa0JXy5p.js → RequestAnatomy-8k9dAX2j.js} +1 -1
  7. package/.output/public/assets/ResponseView-DEL6JcXm.js +3 -0
  8. package/.output/public/assets/StreamingChunkSequence-DngmoR9j.js +1 -0
  9. package/.output/public/assets/{_sessionId-D5fYH2C0.js → _sessionId-4vk9RMZ5.js} +1 -1
  10. package/.output/public/assets/index-DBEB1rHe.css +1 -0
  11. package/.output/public/assets/{index-BEsqvywM.js → index-DINLvnm9.js} +1 -1
  12. package/.output/public/assets/index-LPIGiUMP.js +1 -0
  13. package/.output/public/assets/{json-viewer-B0QRbMSf.js → json-viewer-CH5dvS2Y.js} +1 -1
  14. package/.output/public/assets/{main-CuKZD7n0.js → main-GWkZn4xy.js} +2 -2
  15. package/.output/server/{_sessionId-DdBTK3qm.mjs → _sessionId-EgsjdkN7.mjs} +3 -3
  16. package/.output/server/_ssr/{CompareDrawer-m82cVZ73.mjs → CompareDrawer-CaM8FYuD.mjs} +2 -2
  17. package/.output/server/_ssr/{ProxyViewerContainer-7geuf78f.mjs → ProxyViewerContainer-EJIogcGd.mjs} +545 -309
  18. package/.output/server/_ssr/{ReplayDialog-B8RlWLLH.mjs → ReplayDialog-BPpJ2-q8.mjs} +58 -23
  19. package/.output/server/_ssr/{RequestAnatomy-BE7pmwpv.mjs → RequestAnatomy-DeQAWnZx.mjs} +2 -2
  20. package/.output/server/_ssr/{ResponseView-D-sDsdQr.mjs → ResponseView-Dshm8iQ7.mjs} +2 -2
  21. package/.output/server/_ssr/{StreamingChunkSequence-DU_hThJr.mjs → StreamingChunkSequence-32yIU2hn.mjs} +30 -20
  22. package/.output/server/_ssr/{index-D0sV8UbE.mjs → index-XRcx9blU.mjs} +2 -2
  23. package/.output/server/_ssr/index.mjs +2 -2
  24. package/.output/server/_ssr/{json-viewer-CaDh1zrw.mjs → json-viewer-C0aX5rqC.mjs} +2 -2
  25. package/.output/server/_ssr/{router-Zh2iZ_u0.mjs → router-BI-Mvxz9.mjs} +441 -172
  26. package/.output/server/_tanstack-start-manifest_v-jKScklL_.mjs +4 -0
  27. package/.output/server/index.mjs +71 -71
  28. package/package.json +1 -1
  29. package/src/assets/IDE/opencode.svg +1 -1
  30. package/src/assets/IDE/xiaomimimo.svg +1 -1
  31. package/src/cli/detect-tools.ts +2 -27
  32. package/src/cli/templates/codex-skill-onboard.ts +1 -1
  33. package/src/cli/templates/skill-onboard.ts +2 -3
  34. package/src/cli.ts +9 -86
  35. package/src/components/ProxyViewer.tsx +55 -8
  36. package/src/components/ProxyViewerContainer.tsx +30 -19
  37. package/src/components/clients/ClientLogo.tsx +22 -2
  38. package/src/components/ecosystem/AgentLabDialog.tsx +204 -3
  39. package/src/components/groups/GroupsDialog.tsx +28 -40
  40. package/src/components/providers/ImportWizardDialog.tsx +51 -101
  41. package/src/components/providers/ProvidersPanel.tsx +82 -68
  42. package/src/components/providers/SettingsDialog.tsx +11 -12
  43. package/src/components/proxy-viewer/AgentTraceSummary.tsx +15 -23
  44. package/src/components/proxy-viewer/ReplayDialog.tsx +65 -22
  45. package/src/components/proxy-viewer/StreamingChunkSequence.tsx +33 -20
  46. package/src/lib/apiClient.ts +43 -10
  47. package/src/lib/ecosystemContract.ts +32 -0
  48. package/src/lib/export-logs.ts +37 -3
  49. package/src/proxy/ecosystemRegistry.ts +244 -0
  50. package/src/proxy/ecosystemTasks.ts +153 -0
  51. package/src/proxy/platformCommands.ts +212 -0
  52. package/src/proxy/socketTracker.ts +84 -143
  53. package/src/routes/api/ecosystem.packages.$packageId.help.ts +16 -0
  54. package/src/routes/api/ecosystem.packages.$packageId.install.ts +16 -0
  55. package/src/routes/api/ecosystem.packages.$packageId.runner-presets.ts +19 -0
  56. package/src/routes/api/ecosystem.packages.$packageId.upgrade.ts +16 -0
  57. package/src/routes/api/ecosystem.packages.ts +1 -248
  58. package/src/routes/api/ecosystem.tasks.$taskId.ts +16 -0
  59. package/src/routes/api/ecosystem.tasks.ts +12 -0
  60. package/.output/public/assets/ProxyViewerContainer-p-rpYyiF.js +0 -106
  61. package/.output/public/assets/ReplayDialog-CM3dhP8v.js +0 -1
  62. package/.output/public/assets/ResponseView-Bq6sC7Ay.js +0 -3
  63. package/.output/public/assets/StreamingChunkSequence-CGOun3Df.js +0 -1
  64. package/.output/public/assets/index-C29Olfzt.js +0 -1
  65. package/.output/public/assets/index-DgANE0r3.css +0 -1
  66. package/.output/server/_tanstack-start-manifest_v-LCE-2AaF.mjs +0 -4
@@ -0,0 +1,153 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { spawn } from "node:child_process";
3
+ import { randomUUID } from "node:crypto";
4
+ import type {
5
+ EcosystemTask,
6
+ EcosystemTaskAction,
7
+ EcosystemTaskStatus,
8
+ } from "../lib/ecosystemContract";
9
+ import { findEcosystemPackage } from "./ecosystemRegistry";
10
+ import { npmCommand, npmSpawnCommand } from "./platformCommands";
11
+
12
+ const MAX_TASKS = 50;
13
+ const OUTPUT_LIMIT = 40_000;
14
+ const INSTALL_TIMEOUT_MS = 120_000;
15
+ const HELP_TIMEOUT_MS = 30_000;
16
+
17
+ const tasks = new Map<string, EcosystemTask>();
18
+
19
+ function nowIso(): string {
20
+ return new Date().toISOString();
21
+ }
22
+
23
+ function trimOutput(output: string): string {
24
+ if (output.length <= OUTPUT_LIMIT) return output;
25
+ return output.slice(output.length - OUTPUT_LIMIT);
26
+ }
27
+
28
+ function appendTaskOutput(task: EcosystemTask, chunk: string): void {
29
+ task.output = trimOutput(`${task.output}${chunk}`);
30
+ }
31
+
32
+ function rememberTask(task: EcosystemTask): void {
33
+ tasks.set(task.id, task);
34
+ while (tasks.size > MAX_TASKS) {
35
+ const oldest = tasks.keys().next().value;
36
+ if (typeof oldest !== "string") return;
37
+ tasks.delete(oldest);
38
+ }
39
+ }
40
+
41
+ function packageActionArgs(action: EcosystemTaskAction, npmName: string): string[] {
42
+ switch (action) {
43
+ case "install":
44
+ case "upgrade":
45
+ return ["install", "-g", npmName];
46
+ case "help":
47
+ return ["exec", "--yes", npmName, "--", "--help"];
48
+ case "runner-presets":
49
+ return ["exec", "--yes", "@tonyclaw/eval-harness", "--", "runner-presets"];
50
+ }
51
+ }
52
+
53
+ function packageActionTimeout(action: EcosystemTaskAction): number {
54
+ switch (action) {
55
+ case "install":
56
+ case "upgrade":
57
+ return INSTALL_TIMEOUT_MS;
58
+ case "help":
59
+ case "runner-presets":
60
+ return HELP_TIMEOUT_MS;
61
+ }
62
+ }
63
+
64
+ function taskCommand(args: readonly string[]): string {
65
+ return `${npmCommand()} ${args.join(" ")}`;
66
+ }
67
+
68
+ function completeTask(
69
+ task: EcosystemTask,
70
+ status: Exclude<EcosystemTaskStatus, "queued" | "running">,
71
+ exitCode: number | null,
72
+ error: string | null,
73
+ ): void {
74
+ task.status = status;
75
+ task.exitCode = exitCode;
76
+ task.error = error;
77
+ task.finishedAt = nowIso();
78
+ }
79
+
80
+ export function getEcosystemTask(taskId: string): EcosystemTask | null {
81
+ return tasks.get(taskId) ?? null;
82
+ }
83
+
84
+ export function listEcosystemTasks(): EcosystemTask[] {
85
+ return [...tasks.values()].sort((left, right) => right.createdAt.localeCompare(left.createdAt));
86
+ }
87
+
88
+ export function startEcosystemTask(
89
+ packageId: string,
90
+ action: EcosystemTaskAction,
91
+ ): EcosystemTask | null {
92
+ const definition =
93
+ action === "runner-presets"
94
+ ? findEcosystemPackage("eval-harness")
95
+ : findEcosystemPackage(packageId);
96
+ if (definition === null) return null;
97
+ if (action === "runner-presets" && packageId !== "eval-harness") return null;
98
+
99
+ const args = packageActionArgs(action, definition.npmName);
100
+ const task: EcosystemTask = {
101
+ id: randomUUID(),
102
+ packageId: definition.id,
103
+ npmName: definition.npmName,
104
+ action,
105
+ status: "queued",
106
+ command: taskCommand(args),
107
+ output: "",
108
+ createdAt: nowIso(),
109
+ startedAt: null,
110
+ finishedAt: null,
111
+ exitCode: null,
112
+ error: null,
113
+ };
114
+ rememberTask(task);
115
+
116
+ const spawnCommand = npmSpawnCommand(args);
117
+ const child = spawn(spawnCommand.command, spawnCommand.args, {
118
+ windowsHide: true,
119
+ shell: false,
120
+ stdio: ["ignore", "pipe", "pipe"],
121
+ });
122
+ const timeout = setTimeout(() => {
123
+ child.kill();
124
+ appendTaskOutput(task, `\nTimed out after ${String(packageActionTimeout(action) / 1000)}s.\n`);
125
+ }, packageActionTimeout(action));
126
+
127
+ task.status = "running";
128
+ task.startedAt = nowIso();
129
+ appendTaskOutput(task, `$ ${task.command}\n`);
130
+
131
+ child.stdout.on("data", (chunk: Buffer) => {
132
+ appendTaskOutput(task, chunk.toString("utf8"));
133
+ });
134
+ child.stderr.on("data", (chunk: Buffer) => {
135
+ appendTaskOutput(task, chunk.toString("utf8"));
136
+ });
137
+ child.on("error", (error) => {
138
+ clearTimeout(timeout);
139
+ appendTaskOutput(task, `\n${error.message}\n`);
140
+ completeTask(task, "failed", null, error.message);
141
+ });
142
+ child.on("close", (code) => {
143
+ clearTimeout(timeout);
144
+ if (task.status === "failed") return;
145
+ if (code === 0) {
146
+ completeTask(task, "succeeded", code, null);
147
+ return;
148
+ }
149
+ completeTask(task, "failed", code, `Command exited with code ${String(code ?? "unknown")}`);
150
+ });
151
+
152
+ return task;
153
+ }
@@ -0,0 +1,212 @@
1
+ import { statSync } from "node:fs";
2
+ import { execFileSync } from "node:child_process";
3
+ import { delimiter, isAbsolute, join } from "node:path";
4
+
5
+ export type PlatformName = typeof process.platform;
6
+ type PathEnv = Record<string, string | undefined>;
7
+
8
+ export type CommandSpec = {
9
+ command: string;
10
+ args: string[];
11
+ };
12
+
13
+ export function platformCommand(
14
+ command: string,
15
+ args: readonly string[],
16
+ platform: PlatformName = process.platform,
17
+ ): CommandSpec {
18
+ if (platform !== "win32") return { command, args: [...args] };
19
+ return { command: "cmd.exe", args: ["/d", "/s", "/c", command, ...args] };
20
+ }
21
+
22
+ export function npmCommand(platform: PlatformName = process.platform): string {
23
+ return platform === "win32" ? "npm.cmd" : "npm";
24
+ }
25
+
26
+ export function npmSpawnCommand(
27
+ args: readonly string[],
28
+ platform: PlatformName = process.platform,
29
+ ): CommandSpec {
30
+ return platformCommand(npmCommand(platform), args, platform);
31
+ }
32
+
33
+ function pathEntries(envPath: string | undefined): string[] {
34
+ if (envPath === undefined || envPath.length === 0) return [];
35
+ return envPath.split(delimiter).filter((entry) => entry.length > 0);
36
+ }
37
+
38
+ function windowsExecutableExtensions(envPathExt: string | undefined): string[] {
39
+ const defaults = [".COM", ".EXE", ".BAT", ".CMD"];
40
+ if (envPathExt === undefined || envPathExt.trim().length === 0) return defaults;
41
+ const parsed = envPathExt
42
+ .split(";")
43
+ .map((entry) => entry.trim())
44
+ .filter((entry) => entry.length > 0);
45
+ return parsed.length > 0 ? parsed : defaults;
46
+ }
47
+
48
+ function isExecutableCandidate(path: string, platform: PlatformName): boolean {
49
+ try {
50
+ const stat = statSync(path);
51
+ if (!stat.isFile()) return false;
52
+ if (platform === "win32" || process.platform === "win32") return true;
53
+ return (stat.mode & 0o111) !== 0;
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ function executableCandidates(bin: string, platform: PlatformName, env: PathEnv): string[] {
60
+ if (isAbsolute(bin)) return [bin];
61
+ const entries = pathEntries(env["PATH"] ?? env["Path"] ?? env["path"]);
62
+ if (platform !== "win32") return entries.map((entry) => join(entry, bin));
63
+
64
+ const hasExtension = /\.[^\\/]+$/.test(bin);
65
+ const extensions = hasExtension ? [""] : windowsExecutableExtensions(env["PATHEXT"]);
66
+ const candidates: string[] = [];
67
+ for (const entry of entries) {
68
+ for (const extension of extensions) {
69
+ candidates.push(join(entry, `${bin}${extension.toLowerCase()}`));
70
+ candidates.push(join(entry, `${bin}${extension.toUpperCase()}`));
71
+ }
72
+ }
73
+ return candidates;
74
+ }
75
+
76
+ export function resolveExecutableOnPath(
77
+ bin: string,
78
+ platform: PlatformName = process.platform,
79
+ env: PathEnv = process.env,
80
+ ): string | null {
81
+ if (bin.trim().length === 0) return null;
82
+ for (const candidate of executableCandidates(bin, platform, env)) {
83
+ if (isExecutableCandidate(candidate, platform)) return candidate;
84
+ }
85
+ return null;
86
+ }
87
+
88
+ export function openUrlCommand(
89
+ targetUrl: string,
90
+ platform: PlatformName = process.platform,
91
+ ): CommandSpec | null {
92
+ switch (platform) {
93
+ case "darwin":
94
+ return { command: "open", args: [targetUrl] };
95
+ case "linux":
96
+ case "freebsd":
97
+ case "openbsd":
98
+ case "netbsd":
99
+ return { command: "xdg-open", args: [targetUrl] };
100
+ case "win32":
101
+ return { command: "cmd.exe", args: ["/d", "/s", "/c", "start", "", targetUrl] };
102
+ case "aix":
103
+ case "android":
104
+ case "cygwin":
105
+ case "haiku":
106
+ case "sunos":
107
+ return null;
108
+ }
109
+
110
+ return null;
111
+ }
112
+
113
+ function parseWindowsNetstatPids(output: string, targetPort: number): number[] {
114
+ const pids = new Set<number>();
115
+ const targetToken = `:${targetPort}`;
116
+ for (const line of output.split(/\r?\n/)) {
117
+ const parts = line.trim().split(/\s+/);
118
+ if (parts.length < 5) continue;
119
+ const localAddress = parts[1] ?? "";
120
+ const pidStr = parts[4] ?? "";
121
+ if (!localAddress.endsWith(targetToken)) continue;
122
+ const pid = Number.parseInt(pidStr, 10);
123
+ if (Number.isInteger(pid) && pid > 0) pids.add(pid);
124
+ }
125
+ return [...pids];
126
+ }
127
+
128
+ function parseUnixLsofPids(output: string): number[] {
129
+ const pids = new Set<number>();
130
+ for (const line of output.split(/\r?\n/)) {
131
+ const pid = Number.parseInt(line.trim(), 10);
132
+ if (Number.isInteger(pid) && pid > 0) pids.add(pid);
133
+ }
134
+ return [...pids];
135
+ }
136
+
137
+ function parseUnixSsPids(output: string): number[] {
138
+ const pids = new Set<number>();
139
+ for (const match of output.matchAll(/pid=(\d+)/g)) {
140
+ const pid = Number.parseInt(match[1] ?? "", 10);
141
+ if (Number.isInteger(pid) && pid > 0) pids.add(pid);
142
+ }
143
+ return [...pids];
144
+ }
145
+
146
+ export function findPidsByPort(
147
+ targetPort: number,
148
+ platform: PlatformName = process.platform,
149
+ ): number[] {
150
+ if (!Number.isInteger(targetPort) || targetPort <= 0 || targetPort > 65_535) return [];
151
+ try {
152
+ if (platform === "win32") {
153
+ const output = execFileSync("netstat.exe", ["-ano", "-p", "TCP"], {
154
+ encoding: "utf8",
155
+ timeout: 5000,
156
+ windowsHide: true,
157
+ });
158
+ return parseWindowsNetstatPids(output, targetPort);
159
+ }
160
+
161
+ try {
162
+ const output = execFileSync("lsof", [`-ti:${String(targetPort)}`], {
163
+ encoding: "utf8",
164
+ timeout: 5000,
165
+ stdio: ["ignore", "pipe", "ignore"],
166
+ });
167
+ const pids = parseUnixLsofPids(output);
168
+ if (pids.length > 0) return pids;
169
+ } catch {
170
+ // Fall back to ss below.
171
+ }
172
+
173
+ const output = execFileSync("ss", ["-tanp"], {
174
+ encoding: "utf8",
175
+ timeout: 5000,
176
+ stdio: ["ignore", "pipe", "ignore"],
177
+ });
178
+ return parseUnixSsPids(
179
+ output
180
+ .split(/\r?\n/)
181
+ .filter((line) => line.includes(`:${String(targetPort)}`))
182
+ .join("\n"),
183
+ );
184
+ } catch {
185
+ return [];
186
+ }
187
+ }
188
+
189
+ export function killPid(pid: number, platform: PlatformName = process.platform): boolean {
190
+ if (!Number.isInteger(pid) || pid <= 0) return false;
191
+ try {
192
+ if (platform === "win32") {
193
+ execFileSync("taskkill.exe", ["/PID", String(pid), "/F"], {
194
+ encoding: "utf8",
195
+ timeout: 5000,
196
+ windowsHide: true,
197
+ stdio: ["ignore", "pipe", "ignore"],
198
+ });
199
+ return true;
200
+ }
201
+ process.kill(pid, "SIGKILL");
202
+ return true;
203
+ } catch {
204
+ return false;
205
+ }
206
+ }
207
+
208
+ export const platformCommandParsersForTests = {
209
+ parseUnixLsofPids,
210
+ parseUnixSsPids,
211
+ parseWindowsNetstatPids,
212
+ };