@tonyclaw/agent-inspector 3.0.43 → 3.0.45

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 (80) hide show
  1. package/.output/cli.js +20744 -16945
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-BAXxwTwT.js → CompareDrawer-B3_TLTJh.js} +1 -1
  4. package/.output/public/assets/{InspectorPet-xYuGV4Qz.js → InspectorPet-Cpn6hv4P.js} +1 -1
  5. package/.output/public/assets/{ProxyViewerContainer-B6GZnUWi.js → ProxyViewerContainer-BEAhuGOJ.js} +27 -27
  6. package/.output/public/assets/{ReplayDialog-D73pyIg3.js → ReplayDialog-B3EKVdJk.js} +1 -1
  7. package/.output/public/assets/{RequestAnatomy-CSDta_nu.js → RequestAnatomy-COOQjyUC.js} +1 -1
  8. package/.output/public/assets/{ResponseView-C4aIUPby.js → ResponseView-DBCjkwye.js} +1 -1
  9. package/.output/public/assets/{StreamingChunkSequence-D4xxosLx.js → StreamingChunkSequence-CuXhDgMo.js} +1 -1
  10. package/.output/public/assets/{_sessionId-CZHbJyWO.js → _sessionId-BJlM3nmt.js} +1 -1
  11. package/.output/public/assets/{_sessionId-DvuNLzSE.js → _sessionId-zbgYl7ZJ.js} +1 -1
  12. package/.output/public/assets/{index-DZ_wpqQM.js → index-BIK4bPTv.js} +1 -1
  13. package/.output/public/assets/{index-B17u7JMC.js → index-BZo_8koU.js} +15 -15
  14. package/.output/public/assets/{index-C0g79BAs.js → index-D2Qw6Ri2.js} +1 -1
  15. package/.output/public/assets/{index-BWjeqxbk.css → index-ZWschkCh.css} +1 -1
  16. package/.output/public/assets/{index-CmEO8ykj.js → index-xF_NY8Yb.js} +5 -5
  17. package/.output/public/assets/{json-viewer-CuWU-H6K.js → json-viewer-B-Ao6pwW.js} +1 -1
  18. package/.output/public/assets/{jszip.min-DPJI_UKW.js → jszip.min-B_rfAUCe.js} +1 -1
  19. package/.output/server/_libs/modelcontextprotocol__core.mjs +25 -25
  20. package/.output/server/_libs/modelcontextprotocol__server.mjs +2 -2
  21. package/.output/server/_libs/zod.mjs +6 -6
  22. package/.output/server/{_sessionId-Cun21KZF.mjs → _sessionId-CJ99gZFV.mjs} +3 -1
  23. package/.output/server/{_sessionId-CoFyeOti.mjs → _sessionId-DEjScBpK.mjs} +5 -3
  24. package/.output/server/_ssr/{CompareDrawer-Bgc2TZh_.mjs → CompareDrawer-DtOIMeQB.mjs} +4 -2
  25. package/.output/server/_ssr/{InspectorPet-DyK2ojeh.mjs → InspectorPet-CXc4oCBQ.mjs} +3 -1
  26. package/.output/server/_ssr/{ProxyViewerContainer-hvn6oVWD.mjs → ProxyViewerContainer-DVPlwfkt.mjs} +52 -27
  27. package/.output/server/_ssr/{ReplayDialog-KpnrGtZ-.mjs → ReplayDialog-BQEA95qC.mjs} +6 -4
  28. package/.output/server/_ssr/{RequestAnatomy-uCsfucn8.mjs → RequestAnatomy-BMyd8B7Q.mjs} +4 -2
  29. package/.output/server/_ssr/{ResponseView-C8UPqoPO.mjs → ResponseView-qrTaEIM8.mjs} +4 -2
  30. package/.output/server/_ssr/{StreamingChunkSequence-BP28I9sh.mjs → StreamingChunkSequence-BFTL8mIy.mjs} +5 -3
  31. package/.output/server/_ssr/{index-Du3YNDKx.mjs → index-Bcj1Hcy4.mjs} +4 -2
  32. package/.output/server/_ssr/{index-BVwVoQlZ.mjs → index-DW-M7XUG.mjs} +3 -1
  33. package/.output/server/_ssr/index.mjs +4 -3
  34. package/.output/server/_ssr/{json-viewer-DrE0vtlB.mjs → json-viewer-DiI0Yjsn.mjs} +4 -2
  35. package/.output/server/_ssr/publicBasePathContext-5264L3rX.mjs +148 -0
  36. package/.output/server/_ssr/{router-C6nWUZHK.mjs → router-BJ_ltLim.mjs} +2432 -475
  37. package/.output/server/_ssr/start-C6MjPjxp.mjs +57 -0
  38. package/.output/server/_tanstack-start-manifest_v-C6pXXX9R.mjs +4 -0
  39. package/.output/server/index.mjs +99 -99
  40. package/.output/workers/logFinalizer.worker.js +4 -0
  41. package/.output/workers/sessionWorkerEntry.js +4 -0
  42. package/README.md +221 -25
  43. package/docs/instance-control.openapi.yaml +306 -0
  44. package/package.json +4 -1
  45. package/src/cli/alias.ts +440 -0
  46. package/src/cli/instance.ts +1037 -0
  47. package/src/cli/instanceArgs.ts +266 -0
  48. package/src/cli/instanceControl.ts +183 -0
  49. package/src/cli/instanceModel.ts +275 -0
  50. package/src/cli/instanceRegistry.ts +740 -0
  51. package/src/cli/onboard.ts +123 -35
  52. package/src/cli/onboardTarget.ts +179 -0
  53. package/src/cli/templates/codex-skill-onboard.ts +76 -28
  54. package/src/cli/templates/skill-onboard.ts +93 -59
  55. package/src/cli.ts +154 -16
  56. package/src/components/ProxyViewer.tsx +13 -5
  57. package/src/components/providers/SettingsDialog.tsx +13 -8
  58. package/src/lib/basePath.ts +8 -2
  59. package/src/lib/codingAgentEndpoints.ts +16 -0
  60. package/src/lib/instanceContract.ts +220 -0
  61. package/src/lib/managedInstance.ts +104 -0
  62. package/src/lib/publicBasePathContext.ts +107 -0
  63. package/src/mcp/instanceHandlers.ts +149 -0
  64. package/src/mcp/loopback.ts +16 -5
  65. package/src/mcp/mode.ts +25 -0
  66. package/src/mcp/server.ts +258 -18
  67. package/src/mcp/toolHandlers.ts +2 -2
  68. package/src/proxy/identityProxy.ts +656 -36
  69. package/src/proxy/runtimeShutdown.ts +73 -1
  70. package/src/proxy/sessionInfo.ts +5 -4
  71. package/src/routes/api/-instances.ts +109 -0
  72. package/src/routes/api/instances.$name.connection.ts +12 -0
  73. package/src/routes/api/instances.$name.restart.ts +23 -0
  74. package/src/routes/api/instances.$name.start.ts +18 -0
  75. package/src/routes/api/instances.$name.stop.ts +23 -0
  76. package/src/routes/api/instances.$name.ts +12 -0
  77. package/src/routes/api/instances.ts +16 -0
  78. package/src/start.ts +10 -0
  79. package/.output/server/_ssr/start-HYkvq4Ni.mjs +0 -4
  80. package/.output/server/_tanstack-start-manifest_v-rshlRaiN.mjs +0 -4
@@ -0,0 +1,266 @@
1
+ import { normalizeBasePath } from "../lib/basePath";
2
+ import { isValidBindHost } from "./networkHints";
3
+
4
+ export type InstanceCaptureMode = "simple" | "full";
5
+
6
+ export type InstanceStartOverrides = {
7
+ port: number | null;
8
+ basePath: string | null;
9
+ host: string | null;
10
+ captureMode: InstanceCaptureMode | null;
11
+ dataDir: string | null;
12
+ open: boolean;
13
+ };
14
+
15
+ export type InstanceAction =
16
+ | { kind: "start"; name: string; overrides: InstanceStartOverrides; json: boolean }
17
+ | { kind: "list"; json: boolean }
18
+ | { kind: "status"; name: string; json: boolean }
19
+ | { kind: "connect"; name: string; json: boolean }
20
+ | { kind: "stop"; name: string; json: boolean }
21
+ | { kind: "restart"; name: string; overrides: InstanceStartOverrides; json: boolean };
22
+
23
+ export type InstanceArgsResult =
24
+ | { kind: "help" }
25
+ | { kind: "error"; message: string }
26
+ | { kind: "ok"; action: InstanceAction };
27
+
28
+ function parsePort(value: string | undefined): number | null {
29
+ if (value === undefined || value.trim().length === 0) return null;
30
+ const parsed = Number(value);
31
+ return Number.isInteger(parsed) && parsed >= 1 && parsed <= 65_535 ? parsed : null;
32
+ }
33
+
34
+ function parseMode(value: string | undefined): InstanceCaptureMode | null {
35
+ return value === "simple" || value === "full" ? value : null;
36
+ }
37
+
38
+ function parseBasePath(value: string | undefined): string | null {
39
+ if (
40
+ value === undefined ||
41
+ !value.startsWith("/") ||
42
+ value.includes("?") ||
43
+ value.includes("#") ||
44
+ value.includes("\\")
45
+ ) {
46
+ return null;
47
+ }
48
+ return normalizeBasePath(value);
49
+ }
50
+
51
+ function optionWithEquals(arg: string): { name: string; value: string } | null {
52
+ if (!arg.startsWith("--") || !arg.includes("=")) return null;
53
+ const separator = arg.indexOf("=");
54
+ return { name: arg.slice(0, separator), value: arg.slice(separator + 1) };
55
+ }
56
+
57
+ function emptyOverrides(): InstanceStartOverrides {
58
+ return {
59
+ port: null,
60
+ basePath: null,
61
+ host: null,
62
+ captureMode: null,
63
+ dataDir: null,
64
+ open: false,
65
+ };
66
+ }
67
+
68
+ type MutableOptions = {
69
+ json: boolean;
70
+ help: boolean;
71
+ overrides: InstanceStartOverrides;
72
+ positional: string[];
73
+ };
74
+
75
+ function setValueOption(
76
+ options: MutableOptions,
77
+ name: string,
78
+ value: string | undefined,
79
+ ): string | null {
80
+ switch (name) {
81
+ case "--port":
82
+ case "-p": {
83
+ const port = parsePort(value);
84
+ if (port === null) return "instance: --port must be an integer from 1 to 65535";
85
+ options.overrides.port = port;
86
+ return null;
87
+ }
88
+ case "--base-path": {
89
+ const basePath = parseBasePath(value);
90
+ if (basePath === null) {
91
+ return "instance: --base-path must be an absolute URL path without a query or hash";
92
+ }
93
+ options.overrides.basePath = basePath;
94
+ return null;
95
+ }
96
+ case "--host":
97
+ case "-H": {
98
+ const host = value?.trim() ?? "";
99
+ if (!isValidBindHost(host)) {
100
+ return "instance: --host requires a hostname or IP address without a protocol or path";
101
+ }
102
+ options.overrides.host = host;
103
+ return null;
104
+ }
105
+ case "--mode":
106
+ case "--capture-mode": {
107
+ const captureMode = parseMode(value);
108
+ if (captureMode === null) return "instance: --mode must be simple or full";
109
+ options.overrides.captureMode = captureMode;
110
+ return null;
111
+ }
112
+ case "--data-dir": {
113
+ const dataDir = value?.trim() ?? "";
114
+ if (dataDir.length === 0) return "instance: --data-dir requires a directory path";
115
+ options.overrides.dataDir = dataDir;
116
+ return null;
117
+ }
118
+ default:
119
+ return `instance: unknown option ${name}`;
120
+ }
121
+ }
122
+
123
+ export function parseInstanceArgs(argv: readonly string[]): InstanceArgsResult {
124
+ const options: MutableOptions = {
125
+ json: false,
126
+ help: false,
127
+ overrides: emptyOverrides(),
128
+ positional: [],
129
+ };
130
+
131
+ for (let index = 0; index < argv.length; index++) {
132
+ const arg = argv[index];
133
+ if (arg === undefined) continue;
134
+ const equalsOption = optionWithEquals(arg);
135
+ if (equalsOption !== null) {
136
+ const error = setValueOption(options, equalsOption.name, equalsOption.value);
137
+ if (error !== null) return { kind: "error", message: error };
138
+ continue;
139
+ }
140
+ switch (arg) {
141
+ case "--help":
142
+ case "-h":
143
+ options.help = true;
144
+ break;
145
+ case "--json":
146
+ options.json = true;
147
+ break;
148
+ case "--open":
149
+ options.overrides.open = true;
150
+ break;
151
+ case "--port":
152
+ case "-p":
153
+ case "--base-path":
154
+ case "--host":
155
+ case "-H":
156
+ case "--mode":
157
+ case "--capture-mode":
158
+ case "--data-dir": {
159
+ const error = setValueOption(options, arg, argv[index + 1]);
160
+ if (error !== null) return { kind: "error", message: error };
161
+ index++;
162
+ break;
163
+ }
164
+ default:
165
+ if (arg.startsWith("-")) {
166
+ return { kind: "error", message: `instance: unknown option ${arg}` };
167
+ }
168
+ options.positional.push(arg);
169
+ }
170
+ }
171
+
172
+ if (options.help) return { kind: "help" };
173
+ const command = options.positional[0];
174
+ if (command === "list") {
175
+ if (options.positional.length !== 1) {
176
+ return { kind: "error", message: "instance: list does not accept an instance name" };
177
+ }
178
+ if (
179
+ options.overrides.port !== null ||
180
+ options.overrides.basePath !== null ||
181
+ options.overrides.host !== null ||
182
+ options.overrides.captureMode !== null ||
183
+ options.overrides.dataDir !== null ||
184
+ options.overrides.open
185
+ ) {
186
+ return { kind: "error", message: "instance: list accepts only --json" };
187
+ }
188
+ return { kind: "ok", action: { kind: "list", json: options.json } };
189
+ }
190
+ if (
191
+ command === "status" ||
192
+ command === "stop" ||
193
+ command === "connect" ||
194
+ command === "connection"
195
+ ) {
196
+ const name = options.positional[1];
197
+ if (name === undefined || options.positional.length !== 2) {
198
+ return { kind: "error", message: `instance: ${command} requires exactly one instance name` };
199
+ }
200
+ if (
201
+ options.overrides.port !== null ||
202
+ options.overrides.basePath !== null ||
203
+ options.overrides.host !== null ||
204
+ options.overrides.captureMode !== null ||
205
+ options.overrides.dataDir !== null ||
206
+ options.overrides.open
207
+ ) {
208
+ return {
209
+ kind: "error",
210
+ message: `instance: ${command} accepts only an instance name and --json`,
211
+ };
212
+ }
213
+ return {
214
+ kind: "ok",
215
+ action: { kind: command === "connection" ? "connect" : command, name, json: options.json },
216
+ };
217
+ }
218
+ if (command === "start" || command === "restart") {
219
+ const name = options.positional[1];
220
+ if (name === undefined || options.positional.length !== 2) {
221
+ return { kind: "error", message: `instance: ${command} requires exactly one instance name` };
222
+ }
223
+ return {
224
+ kind: "ok",
225
+ action: { kind: command, name, overrides: options.overrides, json: options.json },
226
+ };
227
+ }
228
+ if (command === undefined) {
229
+ return {
230
+ kind: "error",
231
+ message: "instance: expected start, list, status, connect, stop, or restart",
232
+ };
233
+ }
234
+ return { kind: "error", message: `instance: unknown command ${command}` };
235
+ }
236
+
237
+ export function instanceHelp(): string {
238
+ return `agent-inspector instance - manage isolated named runtimes
239
+
240
+ Usage:
241
+ agent-inspector instance start <name> [options]
242
+ agent-inspector instance list [--json]
243
+ agent-inspector instance status <name> [--json]
244
+ agent-inspector instance connect <name> [--json]
245
+ agent-inspector instance stop <name> [--json]
246
+ agent-inspector instance restart <name> [options]
247
+
248
+ Start/restart options:
249
+ --port, -p <port> Public ingress port (auto-allocated on first start)
250
+ --base-path <path> Public Base Path (default: /inspector; use / for root)
251
+ --host, -H <host> Bind host (default: 127.0.0.1)
252
+ --mode <simple|full> Capture mode (default: simple)
253
+ --data-dir <path> Isolated data/config directory
254
+ --open Open the UI after readiness
255
+ --json Emit machine-readable output
256
+ --help, -h Show this help
257
+
258
+ Named instances run as hidden background supervisors and persist their configuration. Each one
259
+ uses separate Provider, config, capture, and index storage. The existing \`agent-inspector\` command
260
+ remains the unmanaged default instance. Base Path aliases mount another URL on one runtime; named
261
+ instances start separate runtimes.
262
+
263
+ The connect command prints protocol-correct Anthropic, OpenAI /v1, MCP, health, and UI settings
264
+ without exposing lifecycle or Provider credentials. \`connection\` is accepted as an alias.
265
+ `;
266
+ }
@@ -0,0 +1,183 @@
1
+ import {
2
+ MANAGED_INSTANCE_CONTROL_PATH,
3
+ MANAGED_INSTANCE_SHUTDOWN_PATH,
4
+ MANAGED_INSTANCE_TOKEN_HEADER,
5
+ } from "../lib/managedInstance";
6
+
7
+ const DEFAULT_CONTROL_TIMEOUT_MS = 2_000;
8
+
9
+ export type InstanceControlFetch = (input: string, init?: RequestInit) => Promise<Response>;
10
+
11
+ export type ExpectedInstanceControlIdentity = {
12
+ instanceId: string;
13
+ launchId: string;
14
+ controlToken: string;
15
+ host: string;
16
+ publicPort: number;
17
+ };
18
+
19
+ export type InstanceControlIdentity = {
20
+ schemaVersion: 1;
21
+ instanceId: string;
22
+ launchId: string;
23
+ supervisorPid: number;
24
+ startedAt: string;
25
+ publicPort: number;
26
+ upstreamPort: number;
27
+ };
28
+
29
+ export type InstanceControlProbe =
30
+ | { kind: "running"; identity: InstanceControlIdentity }
31
+ | { kind: "unreachable"; timedOut: boolean }
32
+ | { kind: "identity-mismatch" }
33
+ | { kind: "invalid-response" };
34
+
35
+ export type InstanceShutdownResult =
36
+ | { kind: "accepted" }
37
+ | { kind: "unreachable"; timedOut: boolean }
38
+ | { kind: "identity-mismatch" }
39
+ | { kind: "rejected" };
40
+
41
+ function isRecord(value: unknown): value is Record<string, unknown> {
42
+ return typeof value === "object" && value !== null && !Array.isArray(value);
43
+ }
44
+
45
+ function parseIdentity(value: unknown): InstanceControlIdentity | null {
46
+ if (!isRecord(value)) return null;
47
+ const schemaVersion = value["schemaVersion"];
48
+ const instanceId = value["instanceId"];
49
+ const launchId = value["launchId"];
50
+ const supervisorPid = value["supervisorPid"];
51
+ const startedAt = value["startedAt"];
52
+ const publicPort = value["publicPort"];
53
+ const upstreamPort = value["upstreamPort"];
54
+ if (
55
+ schemaVersion !== 1 ||
56
+ typeof instanceId !== "string" ||
57
+ instanceId.length === 0 ||
58
+ typeof launchId !== "string" ||
59
+ launchId.length === 0 ||
60
+ typeof supervisorPid !== "number" ||
61
+ !Number.isInteger(supervisorPid) ||
62
+ supervisorPid < 1 ||
63
+ typeof startedAt !== "string" ||
64
+ !Number.isFinite(Date.parse(startedAt)) ||
65
+ typeof publicPort !== "number" ||
66
+ !Number.isInteger(publicPort) ||
67
+ publicPort < 1 ||
68
+ publicPort > 65_535 ||
69
+ typeof upstreamPort !== "number" ||
70
+ !Number.isInteger(upstreamPort) ||
71
+ upstreamPort < 1 ||
72
+ upstreamPort > 65_535
73
+ ) {
74
+ return null;
75
+ }
76
+ return {
77
+ schemaVersion: 1,
78
+ instanceId,
79
+ launchId,
80
+ supervisorPid,
81
+ startedAt,
82
+ publicPort,
83
+ upstreamPort,
84
+ };
85
+ }
86
+
87
+ function hostForUrl(host: string): string {
88
+ if (host.startsWith("[") && host.endsWith("]")) return host;
89
+ return host.includes(":") ? `[${host}]` : host;
90
+ }
91
+
92
+ function endpoint(identity: ExpectedInstanceControlIdentity, path: string): string {
93
+ return `http://${hostForUrl(identity.host)}:${String(identity.publicPort)}${path}`;
94
+ }
95
+
96
+ function requestInit(
97
+ identity: ExpectedInstanceControlIdentity,
98
+ controller: AbortController,
99
+ method: "GET" | "POST",
100
+ ): RequestInit {
101
+ return {
102
+ method,
103
+ headers: {
104
+ accept: "application/json",
105
+ [MANAGED_INSTANCE_TOKEN_HEADER]: identity.controlToken,
106
+ },
107
+ cache: "no-store",
108
+ signal: controller.signal,
109
+ };
110
+ }
111
+
112
+ async function responseJson(response: Response): Promise<unknown | null> {
113
+ try {
114
+ const value: unknown = await response.json();
115
+ return value;
116
+ } catch {
117
+ return null;
118
+ }
119
+ }
120
+
121
+ export async function probeInstanceControl(
122
+ expected: ExpectedInstanceControlIdentity,
123
+ fetchImpl: InstanceControlFetch = fetch,
124
+ timeoutMs = DEFAULT_CONTROL_TIMEOUT_MS,
125
+ ): Promise<InstanceControlProbe> {
126
+ const controller = new AbortController();
127
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
128
+ try {
129
+ const response = await fetchImpl(
130
+ endpoint(expected, MANAGED_INSTANCE_CONTROL_PATH),
131
+ requestInit(expected, controller, "GET"),
132
+ );
133
+ if (!response.ok) return { kind: "identity-mismatch" };
134
+ const identity = parseIdentity(await responseJson(response));
135
+ if (identity === null) return { kind: "invalid-response" };
136
+ if (
137
+ identity.instanceId !== expected.instanceId ||
138
+ identity.launchId !== expected.launchId ||
139
+ identity.publicPort !== expected.publicPort
140
+ ) {
141
+ return { kind: "identity-mismatch" };
142
+ }
143
+ return { kind: "running", identity };
144
+ } catch (error) {
145
+ return {
146
+ kind: "unreachable",
147
+ timedOut:
148
+ controller.signal.aborted || (error instanceof Error && error.name === "AbortError"),
149
+ };
150
+ } finally {
151
+ clearTimeout(timeout);
152
+ }
153
+ }
154
+
155
+ export async function requestInstanceShutdown(
156
+ expected: ExpectedInstanceControlIdentity,
157
+ fetchImpl: InstanceControlFetch = fetch,
158
+ timeoutMs = DEFAULT_CONTROL_TIMEOUT_MS,
159
+ ): Promise<InstanceShutdownResult> {
160
+ const controller = new AbortController();
161
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
162
+ try {
163
+ const response = await fetchImpl(
164
+ endpoint(expected, MANAGED_INSTANCE_SHUTDOWN_PATH),
165
+ requestInit(expected, controller, "POST"),
166
+ );
167
+ if (response.status === 202) return { kind: "accepted" };
168
+ if (response.status === 401 || response.status === 403 || response.status === 404) {
169
+ return { kind: "identity-mismatch" };
170
+ }
171
+ return { kind: "rejected" };
172
+ } catch (error) {
173
+ return {
174
+ kind: "unreachable",
175
+ timedOut:
176
+ controller.signal.aborted || (error instanceof Error && error.name === "AbortError"),
177
+ };
178
+ } finally {
179
+ clearTimeout(timeout);
180
+ }
181
+ }
182
+
183
+ export const instanceControlForTests = { parseIdentity };