@tonyclaw/agent-inspector 3.0.47 → 3.1.0

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 (29) hide show
  1. package/.output/backend/nitro.json +1 -1
  2. package/.output/cli.js +1855 -1758
  3. package/.output/server/_ssr/index.mjs +1 -1
  4. package/.output/server/_ssr/{router-BwZEsYfS.mjs → router-CPZp1dgy.mjs} +304 -2
  5. package/.output/server/index.mjs +1 -1
  6. package/.output/ui/assets/{CompareDrawer-V-8lPASC.js → CompareDrawer-CkABWY5h.js} +1 -1
  7. package/.output/ui/assets/{InspectorPet-D4CVnfdf.js → InspectorPet-BvsFl3jv.js} +1 -1
  8. package/.output/ui/assets/{ProxyViewerContainer-CO3ZVHLX.js → ProxyViewerContainer-D6O6tY5h.js} +23 -23
  9. package/.output/ui/assets/{ReplayDialog-CpGyTLtt.js → ReplayDialog-uJnW9FgF.js} +1 -1
  10. package/.output/ui/assets/{RequestAnatomy-BLuJL9dZ.js → RequestAnatomy-CJ1IFwXf.js} +1 -1
  11. package/.output/ui/assets/{ResponseView-BuSC-CH7.js → ResponseView-Da8fKfh6.js} +1 -1
  12. package/.output/ui/assets/{StreamingChunkSequence-DeA-JJKa.js → StreamingChunkSequence-zIZ1fbEj.js} +1 -1
  13. package/.output/ui/assets/{_sessionId-Chp67th6.js → _sessionId-CY9mZjlC.js} +1 -1
  14. package/.output/ui/assets/{_sessionId-8HuHFhjo.js → _sessionId-WYeJ04Ke.js} +1 -1
  15. package/.output/ui/assets/{index-atoQrlb8.js → index-Bzm_5L4l.js} +1 -1
  16. package/.output/ui/assets/{index-nY3E1vxV.js → index-C2AvphEC.js} +2 -2
  17. package/.output/ui/assets/{index-DPmKK_5d.js → index-CwOAPU17.js} +1 -1
  18. package/.output/ui/assets/{index-nl9-dpZn.js → index-DWI-2j8i.js} +1 -1
  19. package/.output/ui/assets/{json-viewer-B9kIzqeX.js → json-viewer-C9EpwpoT.js} +1 -1
  20. package/.output/ui/assets/{jszip.min-B7tfyE-I.js → jszip.min-CEk40IyV.js} +1 -1
  21. package/.output/ui/index.html +1 -1
  22. package/README.md +46 -0
  23. package/package.json +4 -4
  24. package/src/cli/instance.ts +12 -1
  25. package/src/cli/instanceArgs.ts +1 -1
  26. package/src/cli/rustBackendPackage.ts +8 -0
  27. package/src/cli.ts +108 -9
  28. package/src/components/ProxyViewer.tsx +4 -13
  29. package/src/components/proxy-viewer/proxyViewerLogic.ts +9 -0
package/.output/cli.js CHANGED
@@ -14,6 +14,206 @@ var __export = (target, all) => {
14
14
  __defProp(target, name, { get: all[name], enumerable: true });
15
15
  };
16
16
 
17
+ // package.json
18
+ var package_default;
19
+ var init_package = __esm({
20
+ "package.json"() {
21
+ package_default = {
22
+ name: "@tonyclaw/agent-inspector",
23
+ version: "3.1.0",
24
+ type: "module",
25
+ description: "Agent observability and knowledge capture layer for AI coding tools.",
26
+ license: "MIT",
27
+ engines: {
28
+ node: ">=22.0.0"
29
+ },
30
+ author: "TonyClaw",
31
+ repository: {
32
+ type: "git",
33
+ url: "git+https://gitcode.com/TonyClaw/agent-inspector.git"
34
+ },
35
+ homepage: "https://gitcode.com/TonyClaw/agent-inspector#readme",
36
+ workspaces: [
37
+ "packages/*"
38
+ ],
39
+ bugs: {
40
+ url: "https://gitcode.com/TonyClaw/agent-inspector/issues"
41
+ },
42
+ publishConfig: {
43
+ access: "public",
44
+ registry: "https://registry.npmjs.org/"
45
+ },
46
+ keywords: [
47
+ "claude",
48
+ "anthropic",
49
+ "openai",
50
+ "proxy",
51
+ "api-inspector",
52
+ "agent-observability",
53
+ "agent-memory",
54
+ "debugging",
55
+ "llm",
56
+ "ai-coding-tools"
57
+ ],
58
+ bin: {
59
+ "agent-inspector": "bin/agent-inspector.js"
60
+ },
61
+ files: [
62
+ "bin",
63
+ "src",
64
+ "!src/**/*.test.ts",
65
+ "!src/**/*.stories.tsx",
66
+ "!src/**/__fixtures__",
67
+ "!src/ui/routeTree.gen.ts",
68
+ "!src/backend/routeTree.gen.ts",
69
+ "scripts/setup-windows-runtime.mjs",
70
+ "scripts/setup-agent-skills.mjs",
71
+ "scripts/generate-theme-scales.mjs",
72
+ "docs/instance-control.openapi.yaml",
73
+ "styles",
74
+ ".output",
75
+ "!.output/*.exe"
76
+ ],
77
+ scripts: {
78
+ dev: "node scripts/dev-decoupled.mjs",
79
+ "dev:ui": "vite dev --config vite.ui.config.ts",
80
+ "dev:backend": "node scripts/dev-backend.mjs",
81
+ "test:smoke": "bun scripts/run-test-suite.mjs smoke",
82
+ "test:fast": "bun scripts/run-test-suite.mjs smoke",
83
+ "test:unit": "bun scripts/run-test-suite.mjs unit",
84
+ "test:integration": "bun scripts/run-test-suite.mjs integration",
85
+ "test:changed": "bun scripts/run-test-suite.mjs changed",
86
+ "test:all": "bun scripts/run-test-suite.mjs all",
87
+ "test:compatibility-tooling": "node --test scripts/compatibility/*.test.mjs",
88
+ start: "node .output/cli.js",
89
+ "start:backend": "node .output/cli.js --backend-only --no-open",
90
+ "preview:ui": "vite preview --config vite.ui.config.ts",
91
+ "clean:output": "node scripts/clean-output.mjs",
92
+ build: "bun clean:output && bun build:backend && bun build:ui && bun smoke:ui-preview && bun build:workers && bun build:cli && bun build:mcp && bun check:bundle && bun check:boundaries",
93
+ "build:backend": "vite build --config vite.backend.config.ts",
94
+ "build:ui": "vite build --config vite.ui.config.ts",
95
+ "build:workers": "npx esbuild src/proxy/logFinalizer.worker.ts src/proxy/sessionWorkerEntry.ts --bundle --platform=node --target=node22 --format=esm --outdir=.output/workers --external:better-sqlite3",
96
+ "smoke:workers": "node scripts/smoke-production-workers.mjs",
97
+ "smoke:packed-workers": "node scripts/smoke-packed-workers.mjs",
98
+ "smoke:instances": "node scripts/smoke-windows-instances.mjs",
99
+ "smoke:coding-agent-instances": "node scripts/smoke-coding-agent-instances.mjs",
100
+ "smoke:ui-preview": "node scripts/smoke-decoupled-ui-preview.mjs",
101
+ "check:bundle": "node scripts/check-client-bundle.mjs",
102
+ "check:boundaries": "node scripts/check-runtime-boundaries.mjs",
103
+ "compatibility:bdd": "node scripts/lint-bdd-catalog.mjs",
104
+ "compatibility:compare": "node scripts/compatibility/cli.mjs compare",
105
+ "compatibility:drift": "node scripts/check-backend-compatibility.mjs",
106
+ "compatibility:fixtures": "bun scripts/validate-compatibility-fixtures.ts",
107
+ "compatibility:freeze": "node scripts/freeze-backend-baseline.mjs",
108
+ "compatibility:run": "node scripts/compatibility/cli.mjs run",
109
+ "compatibility:performance": "node scripts/compatibility/performance-cli.mjs",
110
+ "compatibility:performance:verify": "node scripts/compatibility/performance-cli.mjs verify",
111
+ "build:cli": "npx esbuild src/cli.ts --bundle --platform=node --target=node22 --format=esm --outfile=.output/cli.js",
112
+ "build:mcp": "npx esbuild packages/agent-inspector-mcp/src/cli.ts --bundle --platform=node --target=node22 --format=esm --outfile=packages/agent-inspector-mcp/dist/cli.js",
113
+ "rust:build-package": "node scripts/rust/build-platform.mjs",
114
+ "rust:stage-package": "node scripts/rust/stage-platform.mjs",
115
+ "rust:verify-package": "node scripts/rust/verify-platform.mjs",
116
+ "rust:pack-package": "node scripts/rust/pack-platform.mjs",
117
+ "rust:native-artifact": "node scripts/rust/create-native-artifact.mjs",
118
+ "rust:native:aggregate": "node scripts/rust/aggregate-native-artifacts.mjs",
119
+ "test:rust-packaging": "bun test scripts/rust/npmPackaging.test.ts scripts/rust/nativeReleaseContract.test.ts scripts/rust/nativeStartupSmoke.test.ts scripts/releaseCore.test.ts",
120
+ "rust:packages:check": "node scripts/rust/sync-package-versions.mjs",
121
+ "rust:packages:sync": "node scripts/rust/sync-package-versions.mjs --write",
122
+ "themes:generate": "node scripts/generate-theme-scales.mjs",
123
+ postinstall: "node scripts/setup-windows-runtime.mjs && node scripts/setup-agent-skills.mjs",
124
+ prepublishOnly: "node scripts/rust/require-release-orchestrator.mjs",
125
+ typecheck: "tsc --noEmit --incremental --tsBuildInfoFile .cache/tsconfig.tsbuildinfo",
126
+ lint: "eslint . --cache --cache-location .cache/eslintcache",
127
+ format: "biome format --write .",
128
+ "format:check": "biome format .",
129
+ "verify:fast": "bun scripts/run-static-checks.mjs --format && bun test:changed",
130
+ "extension:zip": "node scripts/pack-chrome-extension.mjs",
131
+ "extension:release": "node scripts/release-chrome-extension.mjs",
132
+ release: "node scripts/release.mjs",
133
+ knip: "knip",
134
+ check: "bun scripts/run-static-checks.mjs --format && bun check:boundaries && bun compatibility:bdd && bun rust:packages:check",
135
+ prepare: "husky",
136
+ ladle: "ladle serve",
137
+ "ladle:build": "ladle build"
138
+ },
139
+ dependencies: {
140
+ "@mariozechner/pi-agent-core": "^0.73.1",
141
+ "@mariozechner/pi-ai": "^0.73.1",
142
+ "@modelcontextprotocol/server": "^2.0.0-beta.4",
143
+ "@tailwindcss/typography": "^0.5.20",
144
+ "@tanstack/react-router": "^1.170.18",
145
+ "@tanstack/react-start": "^1.168.28",
146
+ "@tanstack/react-virtual": "^3.14.6",
147
+ "class-variance-authority": "^0.7.1",
148
+ clsx: "^2.1.1",
149
+ conf: "^15.1.0",
150
+ diff: "^9.0.0",
151
+ jszip: "^3.10.1",
152
+ "lucide-react": "^0.563.0",
153
+ "radix-ui": "^1.6.2",
154
+ react: "^19.2.7",
155
+ "react-dom": "^19.2.7",
156
+ "react-markdown": "^10.1.0",
157
+ swr: "^2.4.2",
158
+ "tailwind-merge": "^3.6.0",
159
+ three: "0.185.1",
160
+ "tw-animate-css": "^1.4.0",
161
+ zod: "^4.4.3"
162
+ },
163
+ optionalDependencies: {
164
+ "@tonyclaw/agent-inspector-backend-darwin-arm64": "3.1.0",
165
+ "@tonyclaw/agent-inspector-backend-linux-x64-gnu": "3.1.0",
166
+ "@tonyclaw/agent-inspector-backend-win32-x64-msvc": "3.1.0",
167
+ "better-sqlite3": "^12.11.1",
168
+ rcedit: "4.0.1"
169
+ },
170
+ devDependencies: {
171
+ "@biomejs/biome": "^2.5.3",
172
+ "@eslint/js": "^10.0.1",
173
+ "@ladle/react": "^5.1.1",
174
+ "@radix-ui/colors": "^3.0.0",
175
+ "@tailwindcss/vite": "^4.3.2",
176
+ "@tanstack/router-plugin": "^1.168.20",
177
+ "@types/bun": "^1.3.14",
178
+ "@types/react": "^19.2.17",
179
+ "@types/react-dom": "^19.2.3",
180
+ "@types/three": "0.185.1",
181
+ "@typescript-eslint/eslint-plugin": "^8.64.0",
182
+ "@typescript-eslint/parser": "^8.64.0",
183
+ "@vitejs/plugin-react": "^5.2.0",
184
+ ajv: "8.20.0",
185
+ "ajv-formats": "3.0.1",
186
+ "colorjs.io": "^0.7.0",
187
+ esbuild: "^0.28.1",
188
+ eslint: "^9.39.5",
189
+ "eslint-plugin-eslint-comments": "^3.2.0",
190
+ "eslint-plugin-functional": "^9.0.5",
191
+ "eslint-plugin-unicorn": "^63.0.0",
192
+ husky: "^9.1.7",
193
+ knip: "^5.88.1",
194
+ nitro: "^3.0.260610-beta",
195
+ playwright: "^1.61.1",
196
+ tailwindcss: "^4.3.2",
197
+ typescript: "^5.9.3",
198
+ vite: "^7.3.6",
199
+ "vite-tsconfig-paths": "^6.1.1"
200
+ },
201
+ overrides: {
202
+ undici: ">=7.24.0 <8.0.0",
203
+ rollup: ">=4.59.0 <5.0.0",
204
+ postcss: ">=8.5.10 <9.0.0",
205
+ "js-yaml": ">=4.1.2 <5.0.0",
206
+ flatted: ">=3.4.0 <4.0.0",
207
+ "smol-toml": ">=1.6.1 <2.0.0",
208
+ koa: ">=2.16.4 <3.0.0",
209
+ "@babel/core": ">=7.29.1 <8.0.0",
210
+ "@typescript-eslint/typescript-estree": "^8.63.0",
211
+ vite: "^7.3.6"
212
+ }
213
+ };
214
+ }
215
+ });
216
+
17
217
  // node_modules/.bun/zod@4.4.3/node_modules/zod/v4/core/core.js
18
218
  // @__NO_SIDE_EFFECTS__
19
219
  function $constructor(name, initializer3, params) {
@@ -15374,555 +15574,868 @@ var init_networkHints = __esm({
15374
15574
  }
15375
15575
  });
15376
15576
 
15377
- // src/lib/instanceContract.ts
15378
- function isCanonicalPublicBasePath(value) {
15379
- const normalized = normalizePublicBasePath(value);
15380
- return normalized !== null && (normalized.length === 0 ? "/" : normalized) === value;
15381
- }
15382
- function isRetryableInstanceError(code) {
15383
- switch (code) {
15384
- case "launch_failed":
15385
- case "launch_unavailable":
15386
- case "lock_timeout":
15387
- case "port_exhausted":
15388
- case "registry_io":
15389
- case "restart_failed_degraded":
15390
- case "restart_failed_rolled_back":
15391
- case "shutdown_failed":
15392
- return true;
15393
- case "argument_invalid":
15394
- case "config_mismatch":
15395
- case "data_dir_conflict":
15396
- case "data_dir_invalid":
15397
- case "identity_mismatch":
15398
- case "instance_not_found":
15399
- case "lock_not_owned":
15400
- case "name_invalid":
15401
- case "port_conflict":
15402
- case "port_invalid":
15403
- case "record_invalid":
15404
- case "self_lifecycle_unsupported":
15405
- return false;
15406
- }
15577
+ // src/cli/rustBackendPackage.ts
15578
+ import { spawnSync } from "node:child_process";
15579
+ import { createHash } from "node:crypto";
15580
+ import { existsSync, readFileSync, statSync } from "node:fs";
15581
+ import { createRequire } from "node:module";
15582
+ import { dirname, isAbsolute, relative, resolve } from "node:path";
15583
+ import { fileURLToPath } from "node:url";
15584
+ function packageFailure(code, message) {
15585
+ return { kind: "error", error: { code, message } };
15407
15586
  }
15408
- function instanceErrorOutput(error51) {
15409
- return { schemaVersion: INSTANCE_API_SCHEMA_VERSION, error: error51 };
15587
+ function targetFailure(message) {
15588
+ return { kind: "error", error: { code: "unsupported_platform", message } };
15410
15589
  }
15411
- var INSTANCE_API_SCHEMA_VERSION, InstanceStateSchema, InstanceStateReasonSchema, InstanceUrlsSchema, InstanceStatusOutputSchema, InstanceConnectionSurfacesSchema, InstanceConnectionOutputSchema, InstanceStartRequestSchema, InstanceEmptyRequestSchema, InstanceOperationSchema, InstanceOperationOutcomeSchema, InstanceOperationOutputSchema, InstanceListOutputSchema, InstancePublicErrorCodeSchema, InstancePublicErrorSchema, InstanceErrorOutputSchema;
15412
- var init_instanceContract = __esm({
15413
- "src/lib/instanceContract.ts"() {
15414
- "use strict";
15415
- init_zod();
15416
- init_backendImplementation();
15417
- init_basePath();
15418
- INSTANCE_API_SCHEMA_VERSION = 1;
15419
- InstanceStateSchema = external_exports.enum(["running", "stopped", "conflict"]);
15420
- InstanceStateReasonSchema = external_exports.enum(["identity_mismatch", "port_conflict"]);
15421
- InstanceUrlsSchema = external_exports.object({
15422
- ui: external_exports.url(),
15423
- api: external_exports.url(),
15424
- health: external_exports.url(),
15425
- proxy: external_exports.url(),
15426
- openai: external_exports.url(),
15427
- mcp: external_exports.url()
15428
- }).strict();
15429
- InstanceStatusOutputSchema = external_exports.object({
15430
- schemaVersion: external_exports.literal(INSTANCE_API_SCHEMA_VERSION),
15431
- name: external_exports.string().min(1),
15432
- instanceId: external_exports.string().min(1),
15433
- state: InstanceStateSchema,
15434
- stateReason: InstanceStateReasonSchema.nullable(),
15435
- checkedAt: external_exports.iso.datetime(),
15436
- createdAt: external_exports.iso.datetime(),
15437
- updatedAt: external_exports.iso.datetime(),
15438
- launch: external_exports.object({
15439
- launchId: external_exports.string().min(1),
15440
- startedAt: external_exports.iso.datetime()
15441
- }).strict(),
15442
- host: external_exports.string().min(1),
15443
- publicPort: external_exports.number().int().min(1).max(65535),
15444
- upstreamPort: external_exports.number().int().min(1).max(65535),
15445
- basePath: external_exports.string().refine(isCanonicalPublicBasePath, "Invalid public Base Path"),
15446
- captureMode: external_exports.enum(["simple", "full"]),
15447
- backend: BackendImplementationSchema.default("typescript"),
15448
- uiEnabled: external_exports.boolean(),
15449
- dataDir: external_exports.string().min(1),
15450
- supervisorPid: external_exports.number().int().positive().nullable(),
15451
- urls: InstanceUrlsSchema
15452
- }).strict();
15453
- InstanceConnectionSurfacesSchema = external_exports.object({
15454
- ui: external_exports.object({
15455
- enabled: external_exports.boolean(),
15456
- baseUrl: external_exports.url().nullable()
15457
- }).strict(),
15458
- control: external_exports.object({ baseUrl: external_exports.url() }).strict(),
15459
- agent: external_exports.object({ baseUrl: external_exports.url() }).strict()
15460
- }).strict();
15461
- InstanceConnectionOutputSchema = external_exports.object({
15462
- schemaVersion: external_exports.literal(INSTANCE_API_SCHEMA_VERSION),
15463
- uiEnabled: external_exports.boolean(),
15464
- instance: InstanceStatusOutputSchema,
15465
- surfaces: InstanceConnectionSurfacesSchema,
15466
- /** @deprecated Use surfaces.control.baseUrl or surfaces.agent.baseUrl. */
15467
- baseUrl: external_exports.url(),
15468
- endpoints: InstanceUrlsSchema,
15469
- environment: external_exports.object({
15470
- ANTHROPIC_BASE_URL: external_exports.url(),
15471
- LLM_BASE_URL: external_exports.url(),
15472
- OPENAI_BASE_URL: external_exports.url(),
15473
- AGENT_INSPECTOR_MCP_URL: external_exports.url()
15474
- }).strict(),
15475
- mcp: external_exports.object({
15476
- serverName: external_exports.string().min(1),
15477
- transport: external_exports.literal("streamable-http"),
15478
- url: external_exports.url()
15479
- }).strict(),
15480
- codex: external_exports.object({
15481
- providerName: external_exports.string().min(1),
15482
- baseUrl: external_exports.url(),
15483
- wireApi: external_exports.literal("responses")
15484
- }).strict()
15485
- }).strict();
15486
- InstanceStartRequestSchema = external_exports.object({
15487
- port: external_exports.number().int().min(1).max(65535).optional(),
15488
- basePath: external_exports.string().min(1).max(2048).refine(
15489
- (value) => normalizePublicBasePath(value) !== null,
15490
- "Base Path must use URL-safe path segments without traversal, encoded separators, or markup characters"
15491
- ).optional(),
15492
- host: external_exports.string().min(1).max(253).refine(
15493
- (value) => value.trim() === value && !value.includes("://") && !value.includes("/") && !value.includes("\\") && !/\s/u.test(value),
15494
- "Host must be a hostname or IP address without protocol, path, or whitespace"
15495
- ).optional(),
15496
- captureMode: external_exports.enum(["simple", "full"]).optional(),
15497
- backend: BackendImplementationSchema.optional(),
15498
- uiEnabled: external_exports.boolean().optional(),
15499
- dataDir: external_exports.string().trim().min(1).max(32767).optional()
15500
- }).strict();
15501
- InstanceEmptyRequestSchema = external_exports.object({}).strict();
15502
- InstanceOperationSchema = external_exports.enum(["start", "stop", "restart"]);
15503
- InstanceOperationOutcomeSchema = external_exports.enum([
15504
- "created",
15505
- "started",
15506
- "already_running",
15507
- "stopped",
15508
- "already_stopped",
15509
- "restarted"
15510
- ]);
15511
- InstanceOperationOutputSchema = external_exports.object({
15512
- schemaVersion: external_exports.literal(INSTANCE_API_SCHEMA_VERSION),
15513
- operation: InstanceOperationSchema,
15514
- outcome: InstanceOperationOutcomeSchema,
15515
- instance: InstanceStatusOutputSchema
15516
- }).strict();
15517
- InstanceListOutputSchema = external_exports.object({
15518
- schemaVersion: external_exports.literal(INSTANCE_API_SCHEMA_VERSION),
15519
- instances: external_exports.array(InstanceStatusOutputSchema)
15520
- }).strict();
15521
- InstancePublicErrorCodeSchema = external_exports.enum([
15522
- "argument_invalid",
15523
- "config_mismatch",
15524
- "data_dir_conflict",
15525
- "data_dir_invalid",
15526
- "identity_mismatch",
15527
- "instance_not_found",
15528
- "launch_failed",
15529
- "launch_unavailable",
15530
- "lock_not_owned",
15531
- "lock_timeout",
15532
- "name_invalid",
15533
- "port_conflict",
15534
- "port_exhausted",
15535
- "port_invalid",
15536
- "record_invalid",
15537
- "registry_io",
15538
- "restart_failed_degraded",
15539
- "restart_failed_rolled_back",
15540
- "self_lifecycle_unsupported",
15541
- "shutdown_failed"
15542
- ]);
15543
- InstancePublicErrorSchema = external_exports.object({
15544
- code: InstancePublicErrorCodeSchema,
15545
- message: external_exports.string().min(1),
15546
- retryable: external_exports.boolean()
15547
- }).strict();
15548
- InstanceErrorOutputSchema = external_exports.object({
15549
- schemaVersion: external_exports.literal(INSTANCE_API_SCHEMA_VERSION),
15550
- error: InstancePublicErrorSchema
15551
- }).strict();
15590
+ function detectLinuxLibc() {
15591
+ if (process.platform !== "linux") return "not-applicable";
15592
+ const report = process.report.getReport();
15593
+ const header = objectProperty(report, "header");
15594
+ const glibcVersion = objectProperty(header, "glibcVersionRuntime");
15595
+ if (typeof glibcVersion === "string" && glibcVersion.length > 0) {
15596
+ return "glibc";
15552
15597
  }
15553
- });
15554
-
15555
- // src/proxy/privateDataPath.ts
15556
- import { spawnSync as spawnSync2 } from "node:child_process";
15557
- import {
15558
- closeSync,
15559
- constants,
15560
- fchmodSync,
15561
- fstatSync,
15562
- lstatSync,
15563
- mkdirSync,
15564
- openSync,
15565
- readdirSync,
15566
- realpathSync
15567
- } from "node:fs";
15568
- import { join, parse as parse3, resolve as resolve2, sep } from "node:path";
15569
- function windowsSystemPath(executable) {
15570
- const systemRoot = process.env["SystemRoot"] ?? "C:\\Windows";
15571
- return `${systemRoot}\\System32\\${executable}`;
15572
- }
15573
- function runWindowsCommand(executable, args) {
15574
- const resolved = process.platform === "win32" && /^(whoami|icacls)\.exe$/iu.test(executable) ? windowsSystemPath(executable) : executable;
15575
- const result = spawnSync2(resolved, [...args], {
15576
- encoding: "utf8",
15577
- shell: false,
15578
- windowsHide: true
15579
- });
15580
- return { status: result.status, stdout: result.stdout };
15581
- }
15582
- function windowsUserSid(output) {
15583
- const match = /S-\d+(?:-\d+)+/.exec(output);
15584
- return match?.[0] ?? null;
15585
- }
15586
- function resolveWindowsUserSid(run) {
15587
- if (run === runWindowsCommand && cachedWindowsSid !== null) return cachedWindowsSid;
15588
- const identity = run("whoami.exe", ["/user", "/fo", "csv", "/nh"]);
15589
- if (identity.status !== 0) return null;
15590
- const sid = windowsUserSid(identity.stdout);
15591
- if (run === runWindowsCommand) cachedWindowsSid = sid;
15592
- return sid;
15598
+ const sharedObjects = objectProperty(report, "sharedObjects");
15599
+ return Array.isArray(sharedObjects) && sharedObjects.some((path2) => typeof path2 === "string" && path2.toLowerCase().includes("musl")) ? "musl" : "unknown";
15593
15600
  }
15594
- function windowsBroadGroupRemovalArgs() {
15595
- return ["/remove:g", "*S-1-1-0", "*S-1-5-11", "*S-1-5-32-545"];
15601
+ function objectProperty(value, name) {
15602
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
15603
+ return Object.getOwnPropertyDescriptor(value, name)?.value;
15596
15604
  }
15597
- function applyWindowsAclWithSid(path2, kind, sid, run) {
15598
- const access = kind === "directory" ? `(OI)(CI)F` : "F";
15599
- const result = run("icacls.exe", [
15600
- path2,
15601
- "/inheritance:r",
15602
- "/grant:r",
15603
- `*${sid}:${access}`,
15604
- ...windowsBroadGroupRemovalArgs(),
15605
- "/q"
15606
- ]);
15607
- return result.status === 0;
15608
- }
15609
- function applyWindowsUserOnlyAcl(path2, kind, run = runWindowsCommand) {
15610
- const sid = resolveWindowsUserSid(run);
15611
- return sid !== null && applyWindowsAclWithSid(path2, kind, sid, run);
15612
- }
15613
- function comparablePath(path2) {
15614
- let normalized = path2;
15615
- if (process.platform === "win32") {
15616
- if (normalized.startsWith("\\\\?\\UNC\\")) {
15617
- normalized = `\\\\${normalized.slice(8)}`;
15618
- } else if (normalized.startsWith("\\\\?\\")) {
15619
- normalized = normalized.slice(4);
15620
- }
15605
+ function resolveRustBackendTarget(platform = process.platform, arch = process.arch, libc = platform === "linux" ? detectLinuxLibc() : "not-applicable") {
15606
+ switch (platform) {
15607
+ case "win32":
15608
+ return arch === "x64" ? {
15609
+ kind: "success",
15610
+ value: {
15611
+ packageName: "@tonyclaw/agent-inspector-backend-win32-x64-msvc",
15612
+ target: "x86_64-pc-windows-msvc",
15613
+ binary: "bin/agent-inspector-backend.exe",
15614
+ os: "win32",
15615
+ cpu: "x64",
15616
+ libc: null
15617
+ }
15618
+ } : targetFailure(`Rust backend packages do not support Windows ${arch}.`);
15619
+ case "linux":
15620
+ if (arch !== "x64")
15621
+ return targetFailure(`Rust backend packages do not support Linux ${arch}.`);
15622
+ if (libc !== "glibc") {
15623
+ return targetFailure(
15624
+ `The Linux Rust backend package requires glibc; detected ${libc}. No binary was started.`
15625
+ );
15626
+ }
15627
+ return {
15628
+ kind: "success",
15629
+ value: {
15630
+ packageName: "@tonyclaw/agent-inspector-backend-linux-x64-gnu",
15631
+ target: "x86_64-unknown-linux-gnu",
15632
+ binary: "bin/agent-inspector-backend",
15633
+ os: "linux",
15634
+ cpu: "x64",
15635
+ libc: "glibc"
15636
+ }
15637
+ };
15638
+ case "darwin":
15639
+ return arch === "arm64" ? {
15640
+ kind: "success",
15641
+ value: {
15642
+ packageName: "@tonyclaw/agent-inspector-backend-darwin-arm64",
15643
+ target: "aarch64-apple-darwin",
15644
+ binary: "bin/agent-inspector-backend",
15645
+ os: "darwin",
15646
+ cpu: "arm64",
15647
+ libc: null
15648
+ }
15649
+ } : targetFailure(`Rust backend packages do not support macOS ${arch}.`);
15650
+ default:
15651
+ return targetFailure(`Rust backend packages do not support ${platform} ${arch}.`);
15621
15652
  }
15622
- const absolute = resolve2(normalized);
15623
- return process.platform === "win32" ? absolute.toLowerCase() : absolute;
15624
- }
15625
- function pathIdentity(stats) {
15626
- return `${String(stats.dev)}:${String(stats.ino)}:${String(stats.birthtimeMs)}`;
15627
15653
  }
15628
- function readPathEntry(path2) {
15629
- let stats;
15630
- try {
15631
- const observed = lstatSync(path2);
15632
- if (observed === void 0) return { status: "missing" };
15633
- stats = observed;
15634
- } catch {
15635
- return { status: "missing" };
15636
- }
15637
- if (stats.isSymbolicLink()) return { status: "unsafe" };
15638
- const kind = stats.isDirectory() ? "directory" : stats.isFile() ? "file" : null;
15639
- if (kind === null) return { status: "unsafe" };
15640
- let canonical;
15654
+ function readJson(path2) {
15641
15655
  try {
15642
- canonical = realpathSync.native(path2);
15656
+ const value = JSON.parse(readFileSync(path2, "utf8"));
15657
+ return { kind: "success", value };
15643
15658
  } catch {
15644
- return { status: "unsafe" };
15659
+ return { kind: "error" };
15645
15660
  }
15646
- if (comparablePath(canonical) !== comparablePath(path2)) return { status: "unsafe" };
15647
- return {
15648
- status: "ok",
15649
- entry: {
15650
- path: path2,
15651
- kind,
15652
- identity: pathIdentity(stats),
15653
- mode: stats.mode & 511
15654
- }
15655
- };
15656
15661
  }
15657
- function snapshotPathChain(path2) {
15658
- const absolutePath = resolve2(path2);
15659
- const root = parse3(absolutePath).root;
15660
- const suffix = absolutePath.slice(root.length);
15661
- const parts = suffix === "" ? [] : suffix.split(sep).filter((part) => part !== "");
15662
- const entries = [];
15663
- let current = root;
15664
- const rootRead = readPathEntry(root);
15665
- if (rootRead.status !== "ok" || rootRead.entry.kind !== "directory") return null;
15666
- entries.push(rootRead.entry);
15667
- for (let index = 0; index < parts.length; index += 1) {
15668
- const part = parts[index];
15669
- if (part === void 0) return null;
15670
- current = join(current, part);
15671
- const read = readPathEntry(current);
15672
- if (read.status === "unsafe") return null;
15673
- if (read.status === "missing") {
15674
- return {
15675
- absolutePath,
15676
- complete: false,
15677
- entries,
15678
- missingParts: parts.slice(index)
15679
- };
15680
- }
15681
- if (index < parts.length - 1 && read.entry.kind !== "directory") return null;
15682
- entries.push(read.entry);
15683
- }
15684
- return { absolutePath, complete: true, entries, missingParts: [] };
15662
+ function isConfinedPath(root, candidate) {
15663
+ const child = relative(root, candidate);
15664
+ return child.length > 0 && !child.startsWith("..") && !isAbsolute(child);
15685
15665
  }
15686
- function chainFingerprint(snapshot) {
15687
- return snapshot.entries.map((entry) => `${entry.path}:${entry.identity}`).join("|");
15666
+ function identitiesMatch(left, right) {
15667
+ return left.schemaVersion === right.schemaVersion && left.name === right.name && left.packageVersion === right.packageVersion && left.target === right.target && left.binary === right.binary && left.cliProtocol === right.cliProtocol && left.productionReady === right.productionReady && left.capabilities.nodeProtectedIngress === right.capabilities.nodeProtectedIngress && left.capabilities.basePath === right.capabilities.basePath && left.capabilities.namedInstances === right.capabilities.namedInstances && left.capabilities.gracefulShutdown === right.capabilities.gracefulShutdown;
15688
15668
  }
15689
- function snapshotsRetainPrefix(before, after) {
15690
- if (after.entries.length < before.entries.length) return false;
15691
- return before.entries.every((entry, index) => {
15692
- const next = after.entries[index];
15693
- return next !== void 0 && next.path === entry.path && next.identity === entry.identity;
15669
+ function probeBinaryIdentity(binaryPath, identityFlag) {
15670
+ const result = spawnSync(binaryPath, [identityFlag], {
15671
+ encoding: "utf8",
15672
+ windowsHide: true,
15673
+ timeout: RUST_BACKEND_IDENTITY_TIMEOUT_MS,
15674
+ maxBuffer: 64 * 1024
15694
15675
  });
15695
- }
15696
- function completeSnapshotForKind(path2, kind) {
15697
- const snapshot = snapshotPathChain(path2);
15698
- if (snapshot === null || !snapshot.complete) return null;
15699
- const leaf = snapshot.entries.at(-1);
15700
- return leaf !== void 0 && leaf.kind === kind ? snapshot : null;
15701
- }
15702
- function applyPosixPermissions(path2, kind, expectedIdentity2) {
15703
- let descriptor = null;
15676
+ if (result.error !== void 0) {
15677
+ return { kind: "error", message: result.error.message };
15678
+ }
15679
+ if (result.status !== 0) {
15680
+ return {
15681
+ kind: "error",
15682
+ message: `identity command exited with status ${String(result.status)}`
15683
+ };
15684
+ }
15685
+ let raw;
15704
15686
  try {
15705
- const directoryFlag = kind === "directory" ? constants.O_DIRECTORY : 0;
15706
- descriptor = openSync(path2, constants.O_RDONLY | constants.O_NOFOLLOW | directoryFlag);
15707
- const before = fstatSync(descriptor);
15708
- if (pathIdentity(before) !== expectedIdentity2) return false;
15709
- fchmodSync(descriptor, kind === "directory" ? PRIVATE_DIRECTORY_MODE : PRIVATE_FILE_MODE);
15710
- const after = fstatSync(descriptor);
15711
- return pathIdentity(after) === expectedIdentity2 && (after.mode & 511) === (kind === "directory" ? PRIVATE_DIRECTORY_MODE : PRIVATE_FILE_MODE);
15687
+ raw = JSON.parse(result.stdout);
15712
15688
  } catch {
15713
- return false;
15714
- } finally {
15715
- if (descriptor !== null) {
15716
- try {
15717
- closeSync(descriptor);
15718
- } catch {
15719
- }
15720
- }
15721
- }
15722
- }
15723
- function permissionsArePrivate(entry) {
15724
- if (process.platform === "win32") return true;
15725
- return entry.mode === (entry.kind === "directory" ? PRIVATE_DIRECTORY_MODE : PRIVATE_FILE_MODE);
15726
- }
15727
- function secureExistingPath(path2, kind, forcePermissions = false) {
15728
- const before = completeSnapshotForKind(path2, kind);
15729
- if (before === null) return false;
15730
- const leaf = before.entries.at(-1);
15731
- if (leaf === void 0) return false;
15732
- const cacheKey = `${kind}:${before.absolutePath}`;
15733
- const beforeFingerprint = chainFingerprint(before);
15734
- if (!forcePermissions && securedPathFingerprints.get(cacheKey) === beforeFingerprint && permissionsArePrivate(leaf)) {
15735
- const cachedCheck = completeSnapshotForKind(path2, kind);
15736
- return cachedCheck !== null && chainFingerprint(cachedCheck) === beforeFingerprint;
15689
+ return { kind: "error", message: "identity command did not return JSON" };
15737
15690
  }
15738
- const applied = process.platform === "win32" ? applyWindowsUserOnlyAcl(path2, kind) : applyPosixPermissions(path2, kind, leaf.identity);
15739
- if (!applied) return false;
15740
- const after = completeSnapshotForKind(path2, kind);
15741
- if (after === null || !snapshotsRetainPrefix(before, after)) return false;
15742
- const afterLeaf = after.entries.at(-1);
15743
- if (afterLeaf === void 0 || afterLeaf.identity !== leaf.identity) return false;
15744
- if (!permissionsArePrivate(afterLeaf)) return false;
15745
- securedPathFingerprints.set(cacheKey, chainFingerprint(after));
15746
- return true;
15691
+ const parsed = RustBackendIdentitySchema.safeParse(raw);
15692
+ return parsed.success ? { kind: "success", value: parsed.data } : { kind: "error", message: "identity command returned an unsupported contract" };
15747
15693
  }
15748
- function ensurePrivateDirectorySync(path2) {
15749
- const before = snapshotPathChain(path2);
15750
- if (before === null) return false;
15751
- let current = before.entries.at(-1)?.path;
15752
- if (current === void 0) return false;
15753
- for (const part of before.missingParts) {
15754
- const next = join(current, part);
15755
- try {
15756
- mkdirSync(next, { mode: PRIVATE_DIRECTORY_MODE });
15757
- } catch {
15758
- const raced = readPathEntry(next);
15759
- if (raced.status !== "ok" || raced.entry.kind !== "directory") return false;
15694
+ function defaultResolvePackageJson(specifier) {
15695
+ try {
15696
+ return packageRequire.resolve(specifier);
15697
+ } catch {
15698
+ const suffix = "/package.json";
15699
+ const packageName = specifier.endsWith(suffix) ? specifier.slice(0, -suffix.length) : "";
15700
+ const directoryName = packageName.split("/").at(-1);
15701
+ if (directoryName !== void 0 && directoryName.length > 0) {
15702
+ const localPackageJson = resolve(
15703
+ dirname(fileURLToPath(import.meta.url)),
15704
+ "../packages",
15705
+ directoryName,
15706
+ "package.json"
15707
+ );
15708
+ if (existsSync(localPackageJson)) return localPackageJson;
15760
15709
  }
15761
- if (!secureExistingPath(next, "directory", true)) return false;
15762
- current = next;
15710
+ return packageRequire.resolve(specifier);
15763
15711
  }
15764
- const complete = completeSnapshotForKind(path2, "directory");
15765
- if (complete === null || !snapshotsRetainPrefix(before, complete)) return false;
15766
- return secureExistingPath(path2, "directory");
15767
15712
  }
15768
- function securePrivateFileSync(path2, forceAcl = false) {
15769
- return secureExistingPath(path2, "file", forceAcl);
15770
- }
15771
- function collectSafeTree(root) {
15772
- const collected = [];
15773
- const visit = (path2) => {
15774
- const snapshot = snapshotPathChain(path2);
15775
- if (snapshot === null || !snapshot.complete) return false;
15776
- const leaf = snapshot.entries.at(-1);
15777
- if (leaf === void 0) return false;
15778
- const fingerprint = chainFingerprint(snapshot);
15779
- collected.push({ path: snapshot.absolutePath, kind: leaf.kind, fingerprint });
15780
- if (leaf.kind === "file") return true;
15781
- let entries;
15782
- try {
15783
- entries = readdirSync(path2);
15784
- } catch {
15785
- return false;
15786
- }
15787
- const afterRead = completeSnapshotForKind(path2, "directory");
15788
- if (afterRead === null || chainFingerprint(afterRead) !== fingerprint) return false;
15789
- for (const entry of entries) {
15790
- if (!visit(join(path2, entry))) return false;
15791
- }
15792
- return true;
15793
- };
15794
- return visit(root) ? collected : null;
15795
- }
15796
- function treeFingerprintsMatch(expected, observed) {
15797
- if (observed === null || observed.length !== expected.length) return false;
15798
- return expected.every((entry, index) => {
15799
- const next = observed[index];
15800
- return next !== void 0 && next.path === entry.path && next.kind === entry.kind && next.fingerprint === entry.fingerprint;
15801
- });
15802
- }
15803
- function isPathTreeFreeOfIndirectionSync(root) {
15804
- return collectSafeTree(root) !== null;
15805
- }
15806
- function applyWindowsUserOnlyTreeAcl(root, run = runWindowsCommand) {
15807
- const entries = collectSafeTree(root);
15808
- if (entries === null) return false;
15809
- const sid = resolveWindowsUserSid(run);
15810
- if (sid === null) return false;
15811
- for (const entry of entries) {
15812
- const before = completeSnapshotForKind(entry.path, entry.kind);
15813
- if (before === null || chainFingerprint(before) !== entry.fingerprint) return false;
15814
- if (!applyWindowsAclWithSid(entry.path, entry.kind, sid, run)) return false;
15815
- const after = completeSnapshotForKind(entry.path, entry.kind);
15816
- if (after === null || chainFingerprint(after) !== entry.fingerprint) return false;
15817
- securedPathFingerprints.set(`${entry.kind}:${entry.path}`, entry.fingerprint);
15713
+ function resolveRustBackendPackage(options) {
15714
+ const target = resolveRustBackendTarget(options.platform, options.arch, options.libc);
15715
+ if (target.kind === "error") return { kind: "error", error: target.error };
15716
+ const packageJsonSpecifier = `${target.value.packageName}/package.json`;
15717
+ let packageJsonPath;
15718
+ try {
15719
+ packageJsonPath = (options.resolvePackageJson ?? defaultResolvePackageJson)(
15720
+ packageJsonSpecifier
15721
+ );
15722
+ } catch {
15723
+ return packageFailure(
15724
+ "package_missing",
15725
+ `Rust backend ${target.value.target} was requested, but optional package ${target.value.packageName}@${options.rootVersion} is not installed. Reinstall without omitting optional dependencies; no TypeScript fallback was used.`
15726
+ );
15818
15727
  }
15819
- return treeFingerprintsMatch(entries, collectSafeTree(root));
15820
- }
15821
- function securePrivateTreeSync(root) {
15822
- const entries = collectSafeTree(root);
15823
- if (entries === null) return false;
15824
- if (process.platform === "win32") return applyWindowsUserOnlyTreeAcl(root);
15825
- for (const entry of entries) {
15826
- if (!secureExistingPath(entry.path, entry.kind, true)) return false;
15728
+ const rawPackage = readJson(packageJsonPath);
15729
+ if (rawPackage.kind === "error") {
15730
+ return packageFailure("package_invalid", `Cannot read ${packageJsonSpecifier}.`);
15827
15731
  }
15828
- return treeFingerprintsMatch(entries, collectSafeTree(root));
15829
- }
15830
- var PRIVATE_DIRECTORY_MODE, PRIVATE_FILE_MODE, securedPathFingerprints, cachedWindowsSid;
15831
- var init_privateDataPath = __esm({
15832
- "src/proxy/privateDataPath.ts"() {
15833
- "use strict";
15834
- PRIVATE_DIRECTORY_MODE = 448;
15835
- PRIVATE_FILE_MODE = 384;
15836
- securedPathFingerprints = /* @__PURE__ */ new Map();
15837
- cachedWindowsSid = null;
15732
+ const packageJson = RustBackendPackageJsonSchema.safeParse(rawPackage.value);
15733
+ if (!packageJson.success) {
15734
+ return packageFailure(
15735
+ "package_invalid",
15736
+ `Package ${target.value.packageName} does not contain the Rust backend package contract.`
15737
+ );
15838
15738
  }
15839
- });
15840
-
15841
- // src/proxy/dataDir.ts
15842
- import { cpSync, existsSync as existsSync2, readdirSync as readdirSync2, renameSync, rmSync } from "node:fs";
15843
- import { isAbsolute as isAbsolute2, join as join2 } from "node:path";
15844
- function getDataDir() {
15845
- migrateLegacyDataDirs();
15846
- return resolveDataDir(existsSync2);
15847
- }
15848
- function hasDataDirState(pathExists, dir) {
15849
- return pathExists(join2(dir, "providers.json")) || pathExists(join2(dir, "config.json")) || pathExists(join2(dir, "logs")) || pathExists(join2(dir, "chunks"));
15850
- }
15851
- function defaultBaseDir(env = process.env, platform = process.platform) {
15852
- return platform === "win32" ? env["USERPROFILE"] ?? env["APPDATA"] ?? "C:\\" : env["HOME"] ?? "/tmp";
15853
- }
15854
- function resolveDataDir(_pathExists, env = process.env, platform = process.platform) {
15855
- const base = defaultBaseDir(env, platform);
15856
- const dirEnv = env["AGENT_INSPECTOR_DATA_DIR"];
15857
- if (dirEnv !== void 0 && dirEnv !== "") {
15858
- return isAbsolute2(dirEnv) ? dirEnv : join2(base, dirEnv);
15739
+ if (packageJson.data.name !== target.value.packageName || packageJson.data.version !== options.rootVersion) {
15740
+ return packageFailure(
15741
+ "version_mismatch",
15742
+ `Rust backend package must exactly match @tonyclaw/agent-inspector@${options.rootVersion}; found ${packageJson.data.name}@${packageJson.data.version}.`
15743
+ );
15859
15744
  }
15860
- const legacyDirEnv = env["AGENT_INSPECTOR_CONFIG_DIR"];
15861
- if (legacyDirEnv !== void 0 && legacyDirEnv !== "") {
15862
- return isAbsolute2(legacyDirEnv) ? legacyDirEnv : join2(base, legacyDirEnv);
15745
+ const metadata = packageJson.data.agentInspectorRustBackend;
15746
+ const expectedLibc = target.value.libc === null ? void 0 : [target.value.libc];
15747
+ if (metadata.target !== target.value.target || metadata.binary !== target.value.binary || metadata.manifest !== "binary-manifest.json" || packageJson.data.os.length !== 1 || packageJson.data.os[0] !== target.value.os || packageJson.data.cpu.length !== 1 || packageJson.data.cpu[0] !== target.value.cpu || JSON.stringify(packageJson.data.libc) !== JSON.stringify(expectedLibc)) {
15748
+ return packageFailure(
15749
+ "target_mismatch",
15750
+ `Package ${target.value.packageName} does not match target ${target.value.target}.`
15751
+ );
15863
15752
  }
15864
- return join2(base, CURRENT_DATA_DIR_NAME);
15865
- }
15866
- function hasExplicitDataDir() {
15867
- return process.env["AGENT_INSPECTOR_DATA_DIR"] !== void 0 && process.env["AGENT_INSPECTOR_DATA_DIR"] !== "" || process.env["AGENT_INSPECTOR_CONFIG_DIR"] !== void 0 && process.env["AGENT_INSPECTOR_CONFIG_DIR"] !== "";
15868
- }
15869
- function migrateLegacyDataDirs() {
15870
- if (hasExplicitDataDir()) return;
15871
- const base = defaultBaseDir();
15872
- const currentDir = join2(base, CURRENT_DATA_DIR_NAME);
15873
- if (hasDataDirState(existsSync2, currentDir)) return;
15874
- for (const legacyName of LEGACY_DATA_DIR_NAMES) {
15875
- const legacyDir = join2(base, legacyName);
15876
- if (!existsSync2(legacyDir)) continue;
15877
- const stagingDir = join2(
15878
- base,
15879
- `${CURRENT_DATA_DIR_NAME}.migration-${String(process.pid)}-${String(Date.now())}`
15753
+ const packageRoot = dirname(packageJsonPath);
15754
+ const manifestPath = resolve(packageRoot, metadata.manifest);
15755
+ const binaryPath = resolve(packageRoot, metadata.binary);
15756
+ if (!isConfinedPath(packageRoot, manifestPath) || !isConfinedPath(packageRoot, binaryPath)) {
15757
+ return packageFailure(
15758
+ "package_invalid",
15759
+ `Package ${target.value.packageName} contains an unsafe artifact path.`
15880
15760
  );
15881
- try {
15882
- if (!isPathTreeFreeOfIndirectionSync(legacyDir)) return;
15883
- if (existsSync2(currentDir)) {
15884
- if (readdirSync2(currentDir).length > 0) return;
15885
- rmSync(currentDir, { recursive: true, force: false });
15886
- }
15887
- if (!ensurePrivateDirectorySync(stagingDir)) return;
15888
- for (const entry of readdirSync2(legacyDir)) {
15889
- cpSync(join2(legacyDir, entry), join2(stagingDir, entry), {
15890
- recursive: true,
15891
- force: false,
15892
- errorOnExist: true
15893
- });
15894
- }
15895
- if (!isPathTreeFreeOfIndirectionSync(legacyDir)) {
15896
- rmSync(stagingDir, { recursive: true, force: true });
15897
- return;
15898
- }
15899
- if (!securePrivateTreeSync(stagingDir)) {
15900
- rmSync(stagingDir, { recursive: true, force: true });
15901
- return;
15902
- }
15903
- renameSync(stagingDir, currentDir);
15904
- rmSync(legacyDir, { recursive: true, force: true });
15905
- return;
15906
- } catch {
15907
- if (existsSync2(stagingDir)) rmSync(stagingDir, { recursive: true, force: true });
15908
- return;
15761
+ }
15762
+ const rawManifest = readJson(manifestPath);
15763
+ if (rawManifest.kind === "error") {
15764
+ return packageFailure(
15765
+ "manifest_invalid",
15766
+ `Rust backend package ${target.value.packageName} is incomplete: binary-manifest.json is missing or invalid.`
15767
+ );
15768
+ }
15769
+ const manifest = RustBackendBinaryManifestSchema.safeParse(rawManifest.value);
15770
+ if (!manifest.success) {
15771
+ return packageFailure(
15772
+ "manifest_invalid",
15773
+ `Rust backend package ${target.value.packageName} has an invalid target manifest.`
15774
+ );
15775
+ }
15776
+ if (manifest.data.packageName !== target.value.packageName || manifest.data.packageVersion !== options.rootVersion || manifest.data.identity.packageVersion !== options.rootVersion) {
15777
+ return packageFailure(
15778
+ "version_mismatch",
15779
+ `Rust backend manifest must exactly match @tonyclaw/agent-inspector@${options.rootVersion}.`
15780
+ );
15781
+ }
15782
+ if (manifest.data.target !== target.value.target || manifest.data.binary !== target.value.binary || manifest.data.identity.target !== target.value.target || manifest.data.identity.binary !== target.value.binary) {
15783
+ return packageFailure(
15784
+ "target_mismatch",
15785
+ `Rust backend manifest identity does not match target ${target.value.target}.`
15786
+ );
15787
+ }
15788
+ let binaryBytes;
15789
+ try {
15790
+ const stats = statSync(binaryPath);
15791
+ if (!stats.isFile()) {
15792
+ return packageFailure("binary_invalid", `Rust backend artifact is not a regular file.`);
15793
+ }
15794
+ if (target.value.os !== "win32" && (stats.mode & 73) === 0) {
15795
+ return packageFailure("binary_invalid", `Rust backend artifact is not executable.`);
15909
15796
  }
15797
+ binaryBytes = readFileSync(binaryPath);
15798
+ } catch {
15799
+ return packageFailure(
15800
+ "binary_missing",
15801
+ `Rust backend binary ${target.value.binary} is missing from ${target.value.packageName}.`
15802
+ );
15803
+ }
15804
+ const sha256 = createHash("sha256").update(binaryBytes).digest("hex");
15805
+ if (sha256 !== manifest.data.sha256) {
15806
+ return packageFailure(
15807
+ "hash_mismatch",
15808
+ `Rust backend binary integrity check failed for ${target.value.packageName}; no binary was started.`
15809
+ );
15810
+ }
15811
+ const probed = (options.identityProbe ?? probeBinaryIdentity)(binaryPath, metadata.identityFlag);
15812
+ if (probed.kind === "error") {
15813
+ return packageFailure(
15814
+ "identity_failed",
15815
+ `Rust backend binary did not pass the production identity gate: ${probed.message}. No TypeScript fallback was used.`
15816
+ );
15817
+ }
15818
+ if (!identitiesMatch(probed.value, manifest.data.identity)) {
15819
+ return packageFailure(
15820
+ "identity_mismatch",
15821
+ `Rust backend binary identity differs from its signed package manifest; no binary was started.`
15822
+ );
15910
15823
  }
15824
+ return {
15825
+ kind: "success",
15826
+ value: {
15827
+ command: binaryPath,
15828
+ args: [],
15829
+ packageName: target.value.packageName,
15830
+ packageVersion: options.rootVersion,
15831
+ target: target.value.target,
15832
+ sha256
15833
+ }
15834
+ };
15911
15835
  }
15912
- var CURRENT_DATA_DIR_NAME, LEGACY_DATA_DIR_NAMES;
15913
- var init_dataDir = __esm({
15914
- "src/proxy/dataDir.ts"() {
15836
+ function resolvePreferredBackendImplementation(options) {
15837
+ return resolveRustBackendPackage(options).kind === "success" ? "rust" : "typescript";
15838
+ }
15839
+ var RUST_BACKEND_IDENTITY_FLAG, RUST_BACKEND_CLI_PROTOCOL, RUST_BACKEND_IDENTITY_TIMEOUT_MS, RustBackendIdentitySchema, RustBackendPackageJsonSchema, RustBackendBinaryManifestSchema, packageRequire;
15840
+ var init_rustBackendPackage = __esm({
15841
+ "src/cli/rustBackendPackage.ts"() {
15915
15842
  "use strict";
15916
- init_privateDataPath();
15917
- CURRENT_DATA_DIR_NAME = ".agent-inspector";
15918
- LEGACY_DATA_DIR_NAMES = [".llm-inspector", ".llm-agent"];
15843
+ init_zod();
15844
+ RUST_BACKEND_IDENTITY_FLAG = "--agent-inspector-package-identity";
15845
+ RUST_BACKEND_CLI_PROTOCOL = "agent-inspector-cli-backend-v1";
15846
+ RUST_BACKEND_IDENTITY_TIMEOUT_MS = 2e3;
15847
+ RustBackendIdentitySchema = external_exports.object({
15848
+ schemaVersion: external_exports.literal(1),
15849
+ name: external_exports.literal("agent-inspector-backend"),
15850
+ packageVersion: external_exports.string().min(1),
15851
+ target: external_exports.string().min(1),
15852
+ binary: external_exports.string().min(1),
15853
+ cliProtocol: external_exports.literal(RUST_BACKEND_CLI_PROTOCOL),
15854
+ productionReady: external_exports.literal(true),
15855
+ capabilities: external_exports.object({
15856
+ nodeProtectedIngress: external_exports.literal(true),
15857
+ basePath: external_exports.literal(true),
15858
+ namedInstances: external_exports.literal(true),
15859
+ gracefulShutdown: external_exports.literal(true)
15860
+ }).strict()
15861
+ }).strict();
15862
+ RustBackendPackageJsonSchema = external_exports.object({
15863
+ name: external_exports.string().min(1),
15864
+ version: external_exports.string().min(1),
15865
+ os: external_exports.array(external_exports.string()).min(1),
15866
+ cpu: external_exports.array(external_exports.string()).min(1),
15867
+ libc: external_exports.array(external_exports.string()).optional(),
15868
+ agentInspectorRustBackend: external_exports.object({
15869
+ schemaVersion: external_exports.literal(1),
15870
+ target: external_exports.string().min(1),
15871
+ binary: external_exports.string().min(1),
15872
+ manifest: external_exports.string().min(1),
15873
+ identityFlag: external_exports.literal(RUST_BACKEND_IDENTITY_FLAG),
15874
+ cliProtocol: external_exports.literal(RUST_BACKEND_CLI_PROTOCOL)
15875
+ }).strict()
15876
+ }).passthrough();
15877
+ RustBackendBinaryManifestSchema = external_exports.object({
15878
+ schemaVersion: external_exports.literal(1),
15879
+ packageName: external_exports.string().min(1),
15880
+ packageVersion: external_exports.string().min(1),
15881
+ target: external_exports.string().min(1),
15882
+ binary: external_exports.string().min(1),
15883
+ sha256: external_exports.string().regex(/^[a-f0-9]{64}$/u),
15884
+ identity: RustBackendIdentitySchema
15885
+ }).strict();
15886
+ packageRequire = createRequire(import.meta.url);
15919
15887
  }
15920
15888
  });
15921
15889
 
15922
- // src/proxy/platformCommands.ts
15923
- import { statSync as statSync2 } from "node:fs";
15924
- import { execFileSync } from "node:child_process";
15925
- import { delimiter, isAbsolute as isAbsolute3, join as join3 } from "node:path";
15890
+ // src/lib/instanceContract.ts
15891
+ function isCanonicalPublicBasePath(value) {
15892
+ const normalized = normalizePublicBasePath(value);
15893
+ return normalized !== null && (normalized.length === 0 ? "/" : normalized) === value;
15894
+ }
15895
+ function isRetryableInstanceError(code) {
15896
+ switch (code) {
15897
+ case "launch_failed":
15898
+ case "launch_unavailable":
15899
+ case "lock_timeout":
15900
+ case "port_exhausted":
15901
+ case "registry_io":
15902
+ case "restart_failed_degraded":
15903
+ case "restart_failed_rolled_back":
15904
+ case "shutdown_failed":
15905
+ return true;
15906
+ case "argument_invalid":
15907
+ case "config_mismatch":
15908
+ case "data_dir_conflict":
15909
+ case "data_dir_invalid":
15910
+ case "identity_mismatch":
15911
+ case "instance_not_found":
15912
+ case "lock_not_owned":
15913
+ case "name_invalid":
15914
+ case "port_conflict":
15915
+ case "port_invalid":
15916
+ case "record_invalid":
15917
+ case "self_lifecycle_unsupported":
15918
+ return false;
15919
+ }
15920
+ }
15921
+ function instanceErrorOutput(error51) {
15922
+ return { schemaVersion: INSTANCE_API_SCHEMA_VERSION, error: error51 };
15923
+ }
15924
+ var INSTANCE_API_SCHEMA_VERSION, InstanceStateSchema, InstanceStateReasonSchema, InstanceUrlsSchema, InstanceStatusOutputSchema, InstanceConnectionSurfacesSchema, InstanceConnectionOutputSchema, InstanceStartRequestSchema, InstanceEmptyRequestSchema, InstanceOperationSchema, InstanceOperationOutcomeSchema, InstanceOperationOutputSchema, InstanceListOutputSchema, InstancePublicErrorCodeSchema, InstancePublicErrorSchema, InstanceErrorOutputSchema;
15925
+ var init_instanceContract = __esm({
15926
+ "src/lib/instanceContract.ts"() {
15927
+ "use strict";
15928
+ init_zod();
15929
+ init_backendImplementation();
15930
+ init_basePath();
15931
+ INSTANCE_API_SCHEMA_VERSION = 1;
15932
+ InstanceStateSchema = external_exports.enum(["running", "stopped", "conflict"]);
15933
+ InstanceStateReasonSchema = external_exports.enum(["identity_mismatch", "port_conflict"]);
15934
+ InstanceUrlsSchema = external_exports.object({
15935
+ ui: external_exports.url(),
15936
+ api: external_exports.url(),
15937
+ health: external_exports.url(),
15938
+ proxy: external_exports.url(),
15939
+ openai: external_exports.url(),
15940
+ mcp: external_exports.url()
15941
+ }).strict();
15942
+ InstanceStatusOutputSchema = external_exports.object({
15943
+ schemaVersion: external_exports.literal(INSTANCE_API_SCHEMA_VERSION),
15944
+ name: external_exports.string().min(1),
15945
+ instanceId: external_exports.string().min(1),
15946
+ state: InstanceStateSchema,
15947
+ stateReason: InstanceStateReasonSchema.nullable(),
15948
+ checkedAt: external_exports.iso.datetime(),
15949
+ createdAt: external_exports.iso.datetime(),
15950
+ updatedAt: external_exports.iso.datetime(),
15951
+ launch: external_exports.object({
15952
+ launchId: external_exports.string().min(1),
15953
+ startedAt: external_exports.iso.datetime()
15954
+ }).strict(),
15955
+ host: external_exports.string().min(1),
15956
+ publicPort: external_exports.number().int().min(1).max(65535),
15957
+ upstreamPort: external_exports.number().int().min(1).max(65535),
15958
+ basePath: external_exports.string().refine(isCanonicalPublicBasePath, "Invalid public Base Path"),
15959
+ captureMode: external_exports.enum(["simple", "full"]),
15960
+ backend: BackendImplementationSchema.default("typescript"),
15961
+ uiEnabled: external_exports.boolean(),
15962
+ dataDir: external_exports.string().min(1),
15963
+ supervisorPid: external_exports.number().int().positive().nullable(),
15964
+ urls: InstanceUrlsSchema
15965
+ }).strict();
15966
+ InstanceConnectionSurfacesSchema = external_exports.object({
15967
+ ui: external_exports.object({
15968
+ enabled: external_exports.boolean(),
15969
+ baseUrl: external_exports.url().nullable()
15970
+ }).strict(),
15971
+ control: external_exports.object({ baseUrl: external_exports.url() }).strict(),
15972
+ agent: external_exports.object({ baseUrl: external_exports.url() }).strict()
15973
+ }).strict();
15974
+ InstanceConnectionOutputSchema = external_exports.object({
15975
+ schemaVersion: external_exports.literal(INSTANCE_API_SCHEMA_VERSION),
15976
+ uiEnabled: external_exports.boolean(),
15977
+ instance: InstanceStatusOutputSchema,
15978
+ surfaces: InstanceConnectionSurfacesSchema,
15979
+ /** @deprecated Use surfaces.control.baseUrl or surfaces.agent.baseUrl. */
15980
+ baseUrl: external_exports.url(),
15981
+ endpoints: InstanceUrlsSchema,
15982
+ environment: external_exports.object({
15983
+ ANTHROPIC_BASE_URL: external_exports.url(),
15984
+ LLM_BASE_URL: external_exports.url(),
15985
+ OPENAI_BASE_URL: external_exports.url(),
15986
+ AGENT_INSPECTOR_MCP_URL: external_exports.url()
15987
+ }).strict(),
15988
+ mcp: external_exports.object({
15989
+ serverName: external_exports.string().min(1),
15990
+ transport: external_exports.literal("streamable-http"),
15991
+ url: external_exports.url()
15992
+ }).strict(),
15993
+ codex: external_exports.object({
15994
+ providerName: external_exports.string().min(1),
15995
+ baseUrl: external_exports.url(),
15996
+ wireApi: external_exports.literal("responses")
15997
+ }).strict()
15998
+ }).strict();
15999
+ InstanceStartRequestSchema = external_exports.object({
16000
+ port: external_exports.number().int().min(1).max(65535).optional(),
16001
+ basePath: external_exports.string().min(1).max(2048).refine(
16002
+ (value) => normalizePublicBasePath(value) !== null,
16003
+ "Base Path must use URL-safe path segments without traversal, encoded separators, or markup characters"
16004
+ ).optional(),
16005
+ host: external_exports.string().min(1).max(253).refine(
16006
+ (value) => value.trim() === value && !value.includes("://") && !value.includes("/") && !value.includes("\\") && !/\s/u.test(value),
16007
+ "Host must be a hostname or IP address without protocol, path, or whitespace"
16008
+ ).optional(),
16009
+ captureMode: external_exports.enum(["simple", "full"]).optional(),
16010
+ backend: BackendImplementationSchema.optional(),
16011
+ uiEnabled: external_exports.boolean().optional(),
16012
+ dataDir: external_exports.string().trim().min(1).max(32767).optional()
16013
+ }).strict();
16014
+ InstanceEmptyRequestSchema = external_exports.object({}).strict();
16015
+ InstanceOperationSchema = external_exports.enum(["start", "stop", "restart"]);
16016
+ InstanceOperationOutcomeSchema = external_exports.enum([
16017
+ "created",
16018
+ "started",
16019
+ "already_running",
16020
+ "stopped",
16021
+ "already_stopped",
16022
+ "restarted"
16023
+ ]);
16024
+ InstanceOperationOutputSchema = external_exports.object({
16025
+ schemaVersion: external_exports.literal(INSTANCE_API_SCHEMA_VERSION),
16026
+ operation: InstanceOperationSchema,
16027
+ outcome: InstanceOperationOutcomeSchema,
16028
+ instance: InstanceStatusOutputSchema
16029
+ }).strict();
16030
+ InstanceListOutputSchema = external_exports.object({
16031
+ schemaVersion: external_exports.literal(INSTANCE_API_SCHEMA_VERSION),
16032
+ instances: external_exports.array(InstanceStatusOutputSchema)
16033
+ }).strict();
16034
+ InstancePublicErrorCodeSchema = external_exports.enum([
16035
+ "argument_invalid",
16036
+ "config_mismatch",
16037
+ "data_dir_conflict",
16038
+ "data_dir_invalid",
16039
+ "identity_mismatch",
16040
+ "instance_not_found",
16041
+ "launch_failed",
16042
+ "launch_unavailable",
16043
+ "lock_not_owned",
16044
+ "lock_timeout",
16045
+ "name_invalid",
16046
+ "port_conflict",
16047
+ "port_exhausted",
16048
+ "port_invalid",
16049
+ "record_invalid",
16050
+ "registry_io",
16051
+ "restart_failed_degraded",
16052
+ "restart_failed_rolled_back",
16053
+ "self_lifecycle_unsupported",
16054
+ "shutdown_failed"
16055
+ ]);
16056
+ InstancePublicErrorSchema = external_exports.object({
16057
+ code: InstancePublicErrorCodeSchema,
16058
+ message: external_exports.string().min(1),
16059
+ retryable: external_exports.boolean()
16060
+ }).strict();
16061
+ InstanceErrorOutputSchema = external_exports.object({
16062
+ schemaVersion: external_exports.literal(INSTANCE_API_SCHEMA_VERSION),
16063
+ error: InstancePublicErrorSchema
16064
+ }).strict();
16065
+ }
16066
+ });
16067
+
16068
+ // src/proxy/privateDataPath.ts
16069
+ import { spawnSync as spawnSync2 } from "node:child_process";
16070
+ import {
16071
+ closeSync,
16072
+ constants,
16073
+ fchmodSync,
16074
+ fstatSync,
16075
+ lstatSync,
16076
+ mkdirSync,
16077
+ openSync,
16078
+ readdirSync,
16079
+ realpathSync
16080
+ } from "node:fs";
16081
+ import { join, parse as parse3, resolve as resolve2, sep } from "node:path";
16082
+ function windowsSystemPath(executable) {
16083
+ const systemRoot = process.env["SystemRoot"] ?? "C:\\Windows";
16084
+ return `${systemRoot}\\System32\\${executable}`;
16085
+ }
16086
+ function runWindowsCommand(executable, args) {
16087
+ const resolved = process.platform === "win32" && /^(whoami|icacls)\.exe$/iu.test(executable) ? windowsSystemPath(executable) : executable;
16088
+ const result = spawnSync2(resolved, [...args], {
16089
+ encoding: "utf8",
16090
+ shell: false,
16091
+ windowsHide: true
16092
+ });
16093
+ return { status: result.status, stdout: result.stdout };
16094
+ }
16095
+ function windowsUserSid(output) {
16096
+ const match = /S-\d+(?:-\d+)+/.exec(output);
16097
+ return match?.[0] ?? null;
16098
+ }
16099
+ function resolveWindowsUserSid(run) {
16100
+ if (run === runWindowsCommand && cachedWindowsSid !== null) return cachedWindowsSid;
16101
+ const identity = run("whoami.exe", ["/user", "/fo", "csv", "/nh"]);
16102
+ if (identity.status !== 0) return null;
16103
+ const sid = windowsUserSid(identity.stdout);
16104
+ if (run === runWindowsCommand) cachedWindowsSid = sid;
16105
+ return sid;
16106
+ }
16107
+ function windowsBroadGroupRemovalArgs() {
16108
+ return ["/remove:g", "*S-1-1-0", "*S-1-5-11", "*S-1-5-32-545"];
16109
+ }
16110
+ function applyWindowsAclWithSid(path2, kind, sid, run) {
16111
+ const access = kind === "directory" ? `(OI)(CI)F` : "F";
16112
+ const result = run("icacls.exe", [
16113
+ path2,
16114
+ "/inheritance:r",
16115
+ "/grant:r",
16116
+ `*${sid}:${access}`,
16117
+ ...windowsBroadGroupRemovalArgs(),
16118
+ "/q"
16119
+ ]);
16120
+ return result.status === 0;
16121
+ }
16122
+ function applyWindowsUserOnlyAcl(path2, kind, run = runWindowsCommand) {
16123
+ const sid = resolveWindowsUserSid(run);
16124
+ return sid !== null && applyWindowsAclWithSid(path2, kind, sid, run);
16125
+ }
16126
+ function comparablePath(path2) {
16127
+ let normalized = path2;
16128
+ if (process.platform === "win32") {
16129
+ if (normalized.startsWith("\\\\?\\UNC\\")) {
16130
+ normalized = `\\\\${normalized.slice(8)}`;
16131
+ } else if (normalized.startsWith("\\\\?\\")) {
16132
+ normalized = normalized.slice(4);
16133
+ }
16134
+ }
16135
+ const absolute = resolve2(normalized);
16136
+ return process.platform === "win32" ? absolute.toLowerCase() : absolute;
16137
+ }
16138
+ function pathIdentity(stats) {
16139
+ return `${String(stats.dev)}:${String(stats.ino)}:${String(stats.birthtimeMs)}`;
16140
+ }
16141
+ function readPathEntry(path2) {
16142
+ let stats;
16143
+ try {
16144
+ const observed = lstatSync(path2);
16145
+ if (observed === void 0) return { status: "missing" };
16146
+ stats = observed;
16147
+ } catch {
16148
+ return { status: "missing" };
16149
+ }
16150
+ if (stats.isSymbolicLink()) return { status: "unsafe" };
16151
+ const kind = stats.isDirectory() ? "directory" : stats.isFile() ? "file" : null;
16152
+ if (kind === null) return { status: "unsafe" };
16153
+ let canonical;
16154
+ try {
16155
+ canonical = realpathSync.native(path2);
16156
+ } catch {
16157
+ return { status: "unsafe" };
16158
+ }
16159
+ if (comparablePath(canonical) !== comparablePath(path2)) return { status: "unsafe" };
16160
+ return {
16161
+ status: "ok",
16162
+ entry: {
16163
+ path: path2,
16164
+ kind,
16165
+ identity: pathIdentity(stats),
16166
+ mode: stats.mode & 511
16167
+ }
16168
+ };
16169
+ }
16170
+ function snapshotPathChain(path2) {
16171
+ const absolutePath = resolve2(path2);
16172
+ const root = parse3(absolutePath).root;
16173
+ const suffix = absolutePath.slice(root.length);
16174
+ const parts = suffix === "" ? [] : suffix.split(sep).filter((part) => part !== "");
16175
+ const entries = [];
16176
+ let current = root;
16177
+ const rootRead = readPathEntry(root);
16178
+ if (rootRead.status !== "ok" || rootRead.entry.kind !== "directory") return null;
16179
+ entries.push(rootRead.entry);
16180
+ for (let index = 0; index < parts.length; index += 1) {
16181
+ const part = parts[index];
16182
+ if (part === void 0) return null;
16183
+ current = join(current, part);
16184
+ const read = readPathEntry(current);
16185
+ if (read.status === "unsafe") return null;
16186
+ if (read.status === "missing") {
16187
+ return {
16188
+ absolutePath,
16189
+ complete: false,
16190
+ entries,
16191
+ missingParts: parts.slice(index)
16192
+ };
16193
+ }
16194
+ if (index < parts.length - 1 && read.entry.kind !== "directory") return null;
16195
+ entries.push(read.entry);
16196
+ }
16197
+ return { absolutePath, complete: true, entries, missingParts: [] };
16198
+ }
16199
+ function chainFingerprint(snapshot) {
16200
+ return snapshot.entries.map((entry) => `${entry.path}:${entry.identity}`).join("|");
16201
+ }
16202
+ function snapshotsRetainPrefix(before, after) {
16203
+ if (after.entries.length < before.entries.length) return false;
16204
+ return before.entries.every((entry, index) => {
16205
+ const next = after.entries[index];
16206
+ return next !== void 0 && next.path === entry.path && next.identity === entry.identity;
16207
+ });
16208
+ }
16209
+ function completeSnapshotForKind(path2, kind) {
16210
+ const snapshot = snapshotPathChain(path2);
16211
+ if (snapshot === null || !snapshot.complete) return null;
16212
+ const leaf = snapshot.entries.at(-1);
16213
+ return leaf !== void 0 && leaf.kind === kind ? snapshot : null;
16214
+ }
16215
+ function applyPosixPermissions(path2, kind, expectedIdentity2) {
16216
+ let descriptor = null;
16217
+ try {
16218
+ const directoryFlag = kind === "directory" ? constants.O_DIRECTORY : 0;
16219
+ descriptor = openSync(path2, constants.O_RDONLY | constants.O_NOFOLLOW | directoryFlag);
16220
+ const before = fstatSync(descriptor);
16221
+ if (pathIdentity(before) !== expectedIdentity2) return false;
16222
+ fchmodSync(descriptor, kind === "directory" ? PRIVATE_DIRECTORY_MODE : PRIVATE_FILE_MODE);
16223
+ const after = fstatSync(descriptor);
16224
+ return pathIdentity(after) === expectedIdentity2 && (after.mode & 511) === (kind === "directory" ? PRIVATE_DIRECTORY_MODE : PRIVATE_FILE_MODE);
16225
+ } catch {
16226
+ return false;
16227
+ } finally {
16228
+ if (descriptor !== null) {
16229
+ try {
16230
+ closeSync(descriptor);
16231
+ } catch {
16232
+ }
16233
+ }
16234
+ }
16235
+ }
16236
+ function permissionsArePrivate(entry) {
16237
+ if (process.platform === "win32") return true;
16238
+ return entry.mode === (entry.kind === "directory" ? PRIVATE_DIRECTORY_MODE : PRIVATE_FILE_MODE);
16239
+ }
16240
+ function secureExistingPath(path2, kind, forcePermissions = false) {
16241
+ const before = completeSnapshotForKind(path2, kind);
16242
+ if (before === null) return false;
16243
+ const leaf = before.entries.at(-1);
16244
+ if (leaf === void 0) return false;
16245
+ const cacheKey = `${kind}:${before.absolutePath}`;
16246
+ const beforeFingerprint = chainFingerprint(before);
16247
+ if (!forcePermissions && securedPathFingerprints.get(cacheKey) === beforeFingerprint && permissionsArePrivate(leaf)) {
16248
+ const cachedCheck = completeSnapshotForKind(path2, kind);
16249
+ return cachedCheck !== null && chainFingerprint(cachedCheck) === beforeFingerprint;
16250
+ }
16251
+ const applied = process.platform === "win32" ? applyWindowsUserOnlyAcl(path2, kind) : applyPosixPermissions(path2, kind, leaf.identity);
16252
+ if (!applied) return false;
16253
+ const after = completeSnapshotForKind(path2, kind);
16254
+ if (after === null || !snapshotsRetainPrefix(before, after)) return false;
16255
+ const afterLeaf = after.entries.at(-1);
16256
+ if (afterLeaf === void 0 || afterLeaf.identity !== leaf.identity) return false;
16257
+ if (!permissionsArePrivate(afterLeaf)) return false;
16258
+ securedPathFingerprints.set(cacheKey, chainFingerprint(after));
16259
+ return true;
16260
+ }
16261
+ function ensurePrivateDirectorySync(path2) {
16262
+ const before = snapshotPathChain(path2);
16263
+ if (before === null) return false;
16264
+ let current = before.entries.at(-1)?.path;
16265
+ if (current === void 0) return false;
16266
+ for (const part of before.missingParts) {
16267
+ const next = join(current, part);
16268
+ try {
16269
+ mkdirSync(next, { mode: PRIVATE_DIRECTORY_MODE });
16270
+ } catch {
16271
+ const raced = readPathEntry(next);
16272
+ if (raced.status !== "ok" || raced.entry.kind !== "directory") return false;
16273
+ }
16274
+ if (!secureExistingPath(next, "directory", true)) return false;
16275
+ current = next;
16276
+ }
16277
+ const complete = completeSnapshotForKind(path2, "directory");
16278
+ if (complete === null || !snapshotsRetainPrefix(before, complete)) return false;
16279
+ return secureExistingPath(path2, "directory");
16280
+ }
16281
+ function securePrivateFileSync(path2, forceAcl = false) {
16282
+ return secureExistingPath(path2, "file", forceAcl);
16283
+ }
16284
+ function collectSafeTree(root) {
16285
+ const collected = [];
16286
+ const visit = (path2) => {
16287
+ const snapshot = snapshotPathChain(path2);
16288
+ if (snapshot === null || !snapshot.complete) return false;
16289
+ const leaf = snapshot.entries.at(-1);
16290
+ if (leaf === void 0) return false;
16291
+ const fingerprint = chainFingerprint(snapshot);
16292
+ collected.push({ path: snapshot.absolutePath, kind: leaf.kind, fingerprint });
16293
+ if (leaf.kind === "file") return true;
16294
+ let entries;
16295
+ try {
16296
+ entries = readdirSync(path2);
16297
+ } catch {
16298
+ return false;
16299
+ }
16300
+ const afterRead = completeSnapshotForKind(path2, "directory");
16301
+ if (afterRead === null || chainFingerprint(afterRead) !== fingerprint) return false;
16302
+ for (const entry of entries) {
16303
+ if (!visit(join(path2, entry))) return false;
16304
+ }
16305
+ return true;
16306
+ };
16307
+ return visit(root) ? collected : null;
16308
+ }
16309
+ function treeFingerprintsMatch(expected, observed) {
16310
+ if (observed === null || observed.length !== expected.length) return false;
16311
+ return expected.every((entry, index) => {
16312
+ const next = observed[index];
16313
+ return next !== void 0 && next.path === entry.path && next.kind === entry.kind && next.fingerprint === entry.fingerprint;
16314
+ });
16315
+ }
16316
+ function isPathTreeFreeOfIndirectionSync(root) {
16317
+ return collectSafeTree(root) !== null;
16318
+ }
16319
+ function applyWindowsUserOnlyTreeAcl(root, run = runWindowsCommand) {
16320
+ const entries = collectSafeTree(root);
16321
+ if (entries === null) return false;
16322
+ const sid = resolveWindowsUserSid(run);
16323
+ if (sid === null) return false;
16324
+ for (const entry of entries) {
16325
+ const before = completeSnapshotForKind(entry.path, entry.kind);
16326
+ if (before === null || chainFingerprint(before) !== entry.fingerprint) return false;
16327
+ if (!applyWindowsAclWithSid(entry.path, entry.kind, sid, run)) return false;
16328
+ const after = completeSnapshotForKind(entry.path, entry.kind);
16329
+ if (after === null || chainFingerprint(after) !== entry.fingerprint) return false;
16330
+ securedPathFingerprints.set(`${entry.kind}:${entry.path}`, entry.fingerprint);
16331
+ }
16332
+ return treeFingerprintsMatch(entries, collectSafeTree(root));
16333
+ }
16334
+ function securePrivateTreeSync(root) {
16335
+ const entries = collectSafeTree(root);
16336
+ if (entries === null) return false;
16337
+ if (process.platform === "win32") return applyWindowsUserOnlyTreeAcl(root);
16338
+ for (const entry of entries) {
16339
+ if (!secureExistingPath(entry.path, entry.kind, true)) return false;
16340
+ }
16341
+ return treeFingerprintsMatch(entries, collectSafeTree(root));
16342
+ }
16343
+ var PRIVATE_DIRECTORY_MODE, PRIVATE_FILE_MODE, securedPathFingerprints, cachedWindowsSid;
16344
+ var init_privateDataPath = __esm({
16345
+ "src/proxy/privateDataPath.ts"() {
16346
+ "use strict";
16347
+ PRIVATE_DIRECTORY_MODE = 448;
16348
+ PRIVATE_FILE_MODE = 384;
16349
+ securedPathFingerprints = /* @__PURE__ */ new Map();
16350
+ cachedWindowsSid = null;
16351
+ }
16352
+ });
16353
+
16354
+ // src/proxy/dataDir.ts
16355
+ import { cpSync, existsSync as existsSync2, readdirSync as readdirSync2, renameSync, rmSync } from "node:fs";
16356
+ import { isAbsolute as isAbsolute2, join as join2 } from "node:path";
16357
+ function getDataDir() {
16358
+ migrateLegacyDataDirs();
16359
+ return resolveDataDir(existsSync2);
16360
+ }
16361
+ function hasDataDirState(pathExists, dir) {
16362
+ return pathExists(join2(dir, "providers.json")) || pathExists(join2(dir, "config.json")) || pathExists(join2(dir, "logs")) || pathExists(join2(dir, "chunks"));
16363
+ }
16364
+ function defaultBaseDir(env = process.env, platform = process.platform) {
16365
+ return platform === "win32" ? env["USERPROFILE"] ?? env["APPDATA"] ?? "C:\\" : env["HOME"] ?? "/tmp";
16366
+ }
16367
+ function resolveDataDir(_pathExists, env = process.env, platform = process.platform) {
16368
+ const base = defaultBaseDir(env, platform);
16369
+ const dirEnv = env["AGENT_INSPECTOR_DATA_DIR"];
16370
+ if (dirEnv !== void 0 && dirEnv !== "") {
16371
+ return isAbsolute2(dirEnv) ? dirEnv : join2(base, dirEnv);
16372
+ }
16373
+ const legacyDirEnv = env["AGENT_INSPECTOR_CONFIG_DIR"];
16374
+ if (legacyDirEnv !== void 0 && legacyDirEnv !== "") {
16375
+ return isAbsolute2(legacyDirEnv) ? legacyDirEnv : join2(base, legacyDirEnv);
16376
+ }
16377
+ return join2(base, CURRENT_DATA_DIR_NAME);
16378
+ }
16379
+ function hasExplicitDataDir() {
16380
+ return process.env["AGENT_INSPECTOR_DATA_DIR"] !== void 0 && process.env["AGENT_INSPECTOR_DATA_DIR"] !== "" || process.env["AGENT_INSPECTOR_CONFIG_DIR"] !== void 0 && process.env["AGENT_INSPECTOR_CONFIG_DIR"] !== "";
16381
+ }
16382
+ function migrateLegacyDataDirs() {
16383
+ if (hasExplicitDataDir()) return;
16384
+ const base = defaultBaseDir();
16385
+ const currentDir = join2(base, CURRENT_DATA_DIR_NAME);
16386
+ if (hasDataDirState(existsSync2, currentDir)) return;
16387
+ for (const legacyName of LEGACY_DATA_DIR_NAMES) {
16388
+ const legacyDir = join2(base, legacyName);
16389
+ if (!existsSync2(legacyDir)) continue;
16390
+ const stagingDir = join2(
16391
+ base,
16392
+ `${CURRENT_DATA_DIR_NAME}.migration-${String(process.pid)}-${String(Date.now())}`
16393
+ );
16394
+ try {
16395
+ if (!isPathTreeFreeOfIndirectionSync(legacyDir)) return;
16396
+ if (existsSync2(currentDir)) {
16397
+ if (readdirSync2(currentDir).length > 0) return;
16398
+ rmSync(currentDir, { recursive: true, force: false });
16399
+ }
16400
+ if (!ensurePrivateDirectorySync(stagingDir)) return;
16401
+ for (const entry of readdirSync2(legacyDir)) {
16402
+ cpSync(join2(legacyDir, entry), join2(stagingDir, entry), {
16403
+ recursive: true,
16404
+ force: false,
16405
+ errorOnExist: true
16406
+ });
16407
+ }
16408
+ if (!isPathTreeFreeOfIndirectionSync(legacyDir)) {
16409
+ rmSync(stagingDir, { recursive: true, force: true });
16410
+ return;
16411
+ }
16412
+ if (!securePrivateTreeSync(stagingDir)) {
16413
+ rmSync(stagingDir, { recursive: true, force: true });
16414
+ return;
16415
+ }
16416
+ renameSync(stagingDir, currentDir);
16417
+ rmSync(legacyDir, { recursive: true, force: true });
16418
+ return;
16419
+ } catch {
16420
+ if (existsSync2(stagingDir)) rmSync(stagingDir, { recursive: true, force: true });
16421
+ return;
16422
+ }
16423
+ }
16424
+ }
16425
+ var CURRENT_DATA_DIR_NAME, LEGACY_DATA_DIR_NAMES;
16426
+ var init_dataDir = __esm({
16427
+ "src/proxy/dataDir.ts"() {
16428
+ "use strict";
16429
+ init_privateDataPath();
16430
+ CURRENT_DATA_DIR_NAME = ".agent-inspector";
16431
+ LEGACY_DATA_DIR_NAMES = [".llm-inspector", ".llm-agent"];
16432
+ }
16433
+ });
16434
+
16435
+ // src/proxy/platformCommands.ts
16436
+ import { statSync as statSync2 } from "node:fs";
16437
+ import { execFileSync } from "node:child_process";
16438
+ import { delimiter, isAbsolute as isAbsolute3, join as join3 } from "node:path";
15926
16439
  function pathEntries(envPath) {
15927
16440
  if (envPath === void 0 || envPath.length === 0) return [];
15928
16441
  return envPath.split(delimiter).filter((entry) => entry.length > 0);
@@ -16277,7 +16790,7 @@ Usage:
16277
16790
  agent-inspector instance restart <name> [options]
16278
16791
 
16279
16792
  Start/restart options:
16280
- --backend <typescript|rust> Persist the backend implementation (default: typescript)
16793
+ --backend <typescript|rust> Persist the backend implementation (default: rust when native package is available)
16281
16794
  --port, -p <port> Public ingress port (auto-allocated on first start)
16282
16795
  --base-path <path> Public Base Path (default: /inspector; use / for root)
16283
16796
  --host, -H <host> Bind host (default: 127.0.0.1)
@@ -17427,6 +17940,9 @@ function validateInstanceDataDirectory(rawPath, records, excludeName) {
17427
17940
  }
17428
17941
  return success2(assigned.value.path);
17429
17942
  }
17943
+ function resolveDefaultInstanceBackend(deps) {
17944
+ return deps.resolveDefaultBackend?.() ?? resolvePreferredBackendImplementation({ rootVersion: package_default.version });
17945
+ }
17430
17946
  async function createNewRecord(root, action, deps) {
17431
17947
  const recordsResult = listInstanceRecords(root);
17432
17948
  if (recordsResult.kind === "error") return fromRegistry(recordsResult);
@@ -17450,7 +17966,7 @@ async function createNewRecord(root, action, deps) {
17450
17966
  upstreamPort: pairResult.value.upstreamPort,
17451
17967
  basePath: action.overrides.basePath ?? getConfiguredBasePath(process.env),
17452
17968
  captureMode: action.overrides.captureMode ?? DEFAULT_INSTANCE_CAPTURE_MODE,
17453
- backend: action.overrides.backend ?? "typescript",
17969
+ backend: action.overrides.backend ?? resolveDefaultInstanceBackend(deps),
17454
17970
  uiEnabled: action.overrides.uiEnabled ?? true,
17455
17971
  dataDir: dataDirResult.value
17456
17972
  });
@@ -17827,6 +18343,7 @@ var DEFAULT_INSTANCE_HOST, DEFAULT_INSTANCE_CAPTURE_MODE, INSTANCE_READY_TIMEOUT
17827
18343
  var init_instance = __esm({
17828
18344
  "src/cli/instance.ts"() {
17829
18345
  "use strict";
18346
+ init_package();
17830
18347
  init_basePath();
17831
18348
  init_instanceContract();
17832
18349
  init_managedInstance();
@@ -17836,6 +18353,7 @@ var init_instance = __esm({
17836
18353
  init_instanceControl();
17837
18354
  init_instanceRegistry();
17838
18355
  init_networkHints();
18356
+ init_rustBackendPackage();
17839
18357
  DEFAULT_INSTANCE_HOST = "127.0.0.1";
17840
18358
  DEFAULT_INSTANCE_CAPTURE_MODE = "simple";
17841
18359
  INSTANCE_READY_TIMEOUT_MS = 15e3;
@@ -20542,1336 +21060,839 @@ function providerCountFromParsed(value) {
20542
21060
  }
20543
21061
  function providerCountFromJson(raw) {
20544
21062
  try {
20545
- const parsed = JSON.parse(raw);
20546
- return providerCountFromParsed(parsed);
20547
- } catch {
20548
- return "invalid";
20549
- }
20550
- }
20551
- function resolveMaybeRelative(base, value) {
20552
- return isAbsolute5(value) ? value : resolve5(base, value);
20553
- }
20554
- function resolveDefaultDataDir(env, platform, exists) {
20555
- return resolveDataDir(exists, env, platform);
20556
- }
20557
- function providerConfigCandidates(options, deps) {
20558
- const explicitPath = deps.env["AGENT_INSPECTOR_CONFIG_PATH"];
20559
- if (explicitPath !== void 0 && explicitPath !== "") {
20560
- return [resolveMaybeRelative(deps.cwd, explicitPath)];
20561
- }
20562
- if (options.configDir !== null) {
20563
- const dir = resolveMaybeRelative(deps.cwd, options.configDir);
20564
- return [join8(dir, "providers.json"), join8(dir, "config.json")];
20565
- }
20566
- const dataDir = resolveDefaultDataDir(deps.env, deps.platform, deps.exists);
20567
- return [join8(dataDir, "providers.json"), join8(dataDir, "config.json")];
20568
- }
20569
- function checkProviderConfig(options, deps) {
20570
- const providersJson = options.providersJson ?? deps.env["AGENT_INSPECTOR_PROVIDERS_JSON"] ?? null;
20571
- if (providersJson !== null) {
20572
- const count = providerCountFromJson(providersJson);
20573
- if (count === "invalid") {
20574
- return check2(
20575
- "Provider config",
20576
- "fail",
20577
- "Provider JSON override is not valid JSON.",
20578
- "Fix --providers or AGENT_INSPECTOR_PROVIDERS_JSON; doctor never prints the raw value."
20579
- );
20580
- }
20581
- if (count !== null) {
20582
- return check2("Provider config", "pass", `${count} provider(s) found in JSON override.`);
20583
- }
20584
- return check2(
20585
- "Provider config",
20586
- "fail",
20587
- "Provider JSON override does not look like a provider array or { providers } object.",
20588
- "Use an exported providers JSON payload or configure providers in the UI."
20589
- );
20590
- }
20591
- const candidates = providerConfigCandidates(options, deps);
20592
- for (const candidate of candidates) {
20593
- if (!deps.exists(candidate)) continue;
20594
- const text = deps.readText(candidate);
20595
- if (text === null) {
20596
- return check2(
20597
- "Provider config",
20598
- "fail",
20599
- `Provider config exists but could not be read: ${candidate}`
20600
- );
20601
- }
20602
- const count = providerCountFromJson(text);
20603
- if (count === "invalid") {
20604
- return check2("Provider config", "fail", `Provider config is invalid JSON: ${candidate}`);
20605
- }
20606
- if (count !== null && count > 0) {
20607
- return check2("Provider config", "pass", `${count} provider(s) configured.`);
20608
- }
20609
- return check2(
20610
- "Provider config",
20611
- "warn",
20612
- `Provider config exists but no providers were found: ${candidate}`,
20613
- "Add a provider through the control API/MCP, or use Settings when a UI is available."
20614
- );
20615
- }
20616
- return check2(
20617
- "Provider config",
20618
- "warn",
20619
- "No provider config was found.",
20620
- "Add a provider through the control API/MCP, use Settings when a UI is available, or pass --providers for this check."
20621
- );
20622
- }
20623
- function publicOriginForDoctor(port, env) {
20624
- const configured = env["AGENT_INSPECTOR_PUBLIC_ORIGIN"];
20625
- if (configured !== void 0 && configured.trim() === configured) {
20626
- try {
20627
- const parsed = new URL(configured);
20628
- if ((parsed.protocol === "http:" || parsed.protocol === "https:") && parsed.username.length === 0 && parsed.password.length === 0 && parsed.pathname === "/" && parsed.search.length === 0 && parsed.hash.length === 0) {
20629
- return parsed.origin;
20630
- }
20631
- } catch {
20632
- }
20633
- }
20634
- return `http://localhost:${String(port)}`;
20635
- }
20636
- async function checkProxyHealth(port, deps) {
20637
- const basePath = getConfiguredBasePath(deps.env);
20638
- const health = await deps.fetchHealth(port, basePath);
20639
- const url2 = publicOriginForDoctor(port, deps.env);
20640
- const proxyUrl = appendBasePathToOrigin(url2, "/proxy", basePath);
20641
- const healthPath = appendBasePathToOrigin("", "/api/health", basePath);
20642
- if (health.ok) {
20643
- return check2(
20644
- "Backend health",
20645
- "pass",
20646
- `Agent Inspector backend is healthy at ${appendBasePathToOrigin(url2, "/", basePath)}.`,
20647
- `Agent proxy URL: ${proxyUrl}. UI availability is not part of this backend check.`
20648
- );
20649
- }
20650
- if (await deps.isPortOpen(port)) {
20651
- const suffix = health.status === null ? "" : ` (HTTP ${health.status})`;
20652
- return check2(
20653
- "Backend health",
20654
- "fail",
20655
- `Port ${port} is accepting connections, but ${healthPath} is not healthy${suffix}.`,
20656
- "Stop that process, choose --port <n>, or start agent-inspector with --force-restart."
20657
- );
20658
- }
20659
- return check2(
20660
- "Backend health",
20661
- "warn",
20662
- `No process is listening on port ${port}.`,
20663
- "Start with `agent-inspector`, `agent-inspector --background`, or `bun run dev`."
20664
- );
20665
- }
20666
- function findProjectRoot(startDir, deps) {
20667
- let current = resolve5(startDir);
20668
- for (let i = 0; i < 6; i++) {
20669
- if (deps.exists(join8(current, "package.json"))) return current;
20670
- const next = resolve5(current, "..");
20671
- if (next === current) return startDir;
20672
- current = next;
20673
- }
20674
- return startDir;
20675
- }
20676
- function checkPackage(rootDir, deps) {
20677
- const packagePath = join8(rootDir, "package.json");
20678
- const text = deps.readText(packagePath);
20679
- if (text === null) {
20680
- return check2("Package metadata", "warn", "package.json was not found near the CLI bundle.");
20681
- }
20682
- try {
20683
- const parsed = JSON.parse(text);
20684
- if (!isObject3(parsed)) {
20685
- return check2("Package metadata", "warn", "package.json is not an object.");
20686
- }
20687
- const name = parsed["name"];
20688
- const version2 = parsed["version"];
20689
- if (typeof name === "string" && typeof version2 === "string") {
20690
- return check2("Package metadata", "pass", `${name}@${version2}`);
20691
- }
20692
- return check2("Package metadata", "warn", "package.json is missing name or version.");
20693
- } catch {
20694
- return check2("Package metadata", "warn", "package.json is not valid JSON.");
20695
- }
20696
- }
20697
- function countFilesInDir(dir, deps) {
20698
- const files = deps.listDir(dir);
20699
- if (files === null) return null;
20700
- let count = 0;
20701
- for (const file2 of files) {
20702
- const fileStat = deps.stat(join8(dir, file2));
20703
- if (fileStat !== null && !fileStat.isDirectory) count += 1;
20704
- }
20705
- return count;
20706
- }
20707
- function formatBytes(bytes) {
20708
- if (bytes < 1024) return `${bytes} B`;
20709
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
20710
- if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MiB`;
20711
- return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GiB`;
20712
- }
20713
- function dirSize(dir, deps, depth = 0) {
20714
- const dirStat = deps.stat(dir);
20715
- if (dirStat === null) return null;
20716
- if (!dirStat.isDirectory) return dirStat.size;
20717
- if (depth > 4) return 0;
20718
- const files = deps.listDir(dir);
20719
- if (files === null) return null;
20720
- let total = 0;
20721
- for (const file2 of files) {
20722
- const childSize = dirSize(join8(dir, file2), deps, depth + 1);
20723
- if (childSize !== null) total += childSize;
20724
- }
20725
- return total;
20726
- }
20727
- function verboseChecks(options, deps) {
20728
- const dataDir = resolveDefaultDataDir(deps.env, deps.platform, deps.exists);
20729
- const logsDir = join8(dataDir, "logs");
20730
- const chunksDir = join8(dataDir, "chunks");
20731
- const providerCandidates = providerConfigCandidates(options, deps);
20732
- const captureMode = deps.env["AGENT_INSPECTOR_MODE"] ?? deps.env["AGENT_INSPECTOR_CAPTURE_MODE"] ?? "simple";
20733
- const logCount = countFilesInDir(logsDir, deps);
20734
- const logSize = dirSize(logsDir, deps);
20735
- const chunkSize = dirSize(chunksDir, deps);
20736
- const homeDir = deps.platform === "win32" ? deps.env["USERPROFILE"] ?? deps.env["APPDATA"] ?? null : deps.env["HOME"] ?? null;
20737
- const claudeSkill = homeDir === null ? null : join8(homeDir, ".claude", "skills", "agent-inspector-onboard", "SKILL.md");
20738
- const claudeCommand = homeDir === null ? null : join8(homeDir, ".claude", "commands", "agent-inspector-onboard.md");
20739
- const codexSkill = homeDir === null ? null : join8(homeDir, ".codex", "skills", "agent-inspector-onboard", "SKILL.md");
20740
- const hasClaudeOnboarding = claudeSkill !== null && claudeCommand !== null && deps.exists(claudeSkill) && deps.exists(claudeCommand);
20741
- const hasCodexOnboarding = codexSkill !== null && deps.exists(codexSkill);
20742
- return [
20743
- check2("Data directory", "pass", dataDir),
20744
- check2("Provider config search", "pass", providerCandidates.join(", ")),
20745
- check2("Capture mode env", "pass", captureMode),
20746
- check2(
20747
- "Log storage",
20748
- logCount === null ? "warn" : "pass",
20749
- logCount === null ? `Log directory was not found or could not be read: ${logsDir}` : `${logCount} file(s), ${formatBytes(logSize ?? 0)} under ${logsDir}`
20750
- ),
20751
- check2(
20752
- "Chunk storage",
20753
- chunkSize === null ? "warn" : "pass",
20754
- chunkSize === null ? `Chunk directory was not found or could not be read: ${chunksDir}` : `${formatBytes(chunkSize)} under ${chunksDir}`
20755
- ),
20756
- check2(
20757
- "Claude onboarding",
20758
- hasClaudeOnboarding ? "pass" : "warn",
20759
- hasClaudeOnboarding ? "Claude Code skill and slash command are installed." : "Claude Code onboarding files were not found.",
20760
- "Run `agent-inspector onboard --force` after installing or upgrading."
20761
- ),
20762
- check2(
20763
- "Codex onboarding",
20764
- hasCodexOnboarding ? "pass" : "warn",
20765
- hasCodexOnboarding ? "Codex onboarding skill is installed." : "Codex onboarding skill was not found.",
20766
- "Run `agent-inspector onboard --force` after installing or upgrading."
20767
- )
20768
- ];
20769
- }
20770
- function checkExtensionSource(rootDir, deps) {
20771
- const extensionDir = join8(rootDir, "extensions", "chrome");
20772
- const missing = EXTENSION_REQUIRED_FILES.filter((file2) => !deps.exists(join8(extensionDir, file2)));
20773
- if (missing.length === 0) {
20774
- return check2(
20775
- "Chrome extension source",
20776
- "pass",
20777
- "Manifest, side panel, service worker, and icons are present."
20778
- );
20779
- }
20780
- return check2(
20781
- "Chrome extension source",
20782
- "fail",
20783
- `Missing ${missing.length} Chrome extension file(s): ${missing.join(", ")}`,
20784
- "Restore the extension files before packaging or loading unpacked."
20785
- );
20786
- }
20787
- function extensionManifestVersion(rootDir, deps) {
20788
- const manifestText = deps.readText(join8(rootDir, "extensions", "chrome", "manifest.json"));
20789
- if (manifestText === null) return null;
20790
- try {
20791
- const manifest = JSON.parse(manifestText);
20792
- if (!isObject3(manifest)) return null;
20793
- const version2 = manifest["version"];
20794
- return typeof version2 === "string" && version2 !== "" ? version2 : null;
21063
+ const parsed = JSON.parse(raw);
21064
+ return providerCountFromParsed(parsed);
20795
21065
  } catch {
20796
- return null;
21066
+ return "invalid";
20797
21067
  }
20798
21068
  }
20799
- function checkExtensionPackage(rootDir, deps) {
20800
- const distDir = join8(rootDir, "dist", "chrome-extension");
20801
- const files = deps.listDir(distDir);
20802
- if (files === null) {
20803
- return check2(
20804
- "Chrome extension package",
20805
- "warn",
20806
- "No packaged Chrome extension artifact was found.",
20807
- "Run `npm run extension:zip` before Chrome Web Store upload."
20808
- );
21069
+ function resolveMaybeRelative(base, value) {
21070
+ return isAbsolute5(value) ? value : resolve5(base, value);
21071
+ }
21072
+ function resolveDefaultDataDir(env, platform, exists) {
21073
+ return resolveDataDir(exists, env, platform);
21074
+ }
21075
+ function providerConfigCandidates(options, deps) {
21076
+ const explicitPath = deps.env["AGENT_INSPECTOR_CONFIG_PATH"];
21077
+ if (explicitPath !== void 0 && explicitPath !== "") {
21078
+ return [resolveMaybeRelative(deps.cwd, explicitPath)];
20809
21079
  }
20810
- const manifestVersion = extensionManifestVersion(rootDir, deps);
20811
- if (manifestVersion !== null) {
20812
- const zipName = `agent-inspector-companion-v${manifestVersion}.zip`;
20813
- const checksumName = `${zipName}.sha256`;
20814
- const hasZip = files.includes(zipName);
20815
- const hasChecksum = files.includes(checksumName);
20816
- if (hasZip && hasChecksum) {
20817
- return check2("Chrome extension package", "pass", `${zipName} with checksum is present.`);
20818
- }
20819
- if (hasZip) {
21080
+ if (options.configDir !== null) {
21081
+ const dir = resolveMaybeRelative(deps.cwd, options.configDir);
21082
+ return [join8(dir, "providers.json"), join8(dir, "config.json")];
21083
+ }
21084
+ const dataDir = resolveDefaultDataDir(deps.env, deps.platform, deps.exists);
21085
+ return [join8(dataDir, "providers.json"), join8(dataDir, "config.json")];
21086
+ }
21087
+ function checkProviderConfig(options, deps) {
21088
+ const providersJson = options.providersJson ?? deps.env["AGENT_INSPECTOR_PROVIDERS_JSON"] ?? null;
21089
+ if (providersJson !== null) {
21090
+ const count = providerCountFromJson(providersJson);
21091
+ if (count === "invalid") {
20820
21092
  return check2(
20821
- "Chrome extension package",
20822
- "warn",
20823
- `${zipName} exists but no .sha256 checksum was found.`,
20824
- "Run `npm run extension:zip` to regenerate the zip and checksum."
21093
+ "Provider config",
21094
+ "fail",
21095
+ "Provider JSON override is not valid JSON.",
21096
+ "Fix --providers or AGENT_INSPECTOR_PROVIDERS_JSON; doctor never prints the raw value."
20825
21097
  );
20826
21098
  }
21099
+ if (count !== null) {
21100
+ return check2("Provider config", "pass", `${count} provider(s) found in JSON override.`);
21101
+ }
20827
21102
  return check2(
20828
- "Chrome extension package",
20829
- "warn",
20830
- `Current Chrome extension package was not found: ${zipName}`,
20831
- "Run `npm run extension:zip` before Chrome Web Store upload."
21103
+ "Provider config",
21104
+ "fail",
21105
+ "Provider JSON override does not look like a provider array or { providers } object.",
21106
+ "Use an exported providers JSON payload or configure providers in the UI."
20832
21107
  );
20833
21108
  }
20834
- const zip = files.find((file2) => file2.endsWith(".zip"));
20835
- const checksum = files.find((file2) => file2.endsWith(".zip.sha256"));
20836
- if (zip !== void 0 && checksum !== void 0) {
20837
- return check2("Chrome extension package", "pass", `${zip} with checksum is present.`);
20838
- }
20839
- if (zip !== void 0) {
21109
+ const candidates = providerConfigCandidates(options, deps);
21110
+ for (const candidate of candidates) {
21111
+ if (!deps.exists(candidate)) continue;
21112
+ const text = deps.readText(candidate);
21113
+ if (text === null) {
21114
+ return check2(
21115
+ "Provider config",
21116
+ "fail",
21117
+ `Provider config exists but could not be read: ${candidate}`
21118
+ );
21119
+ }
21120
+ const count = providerCountFromJson(text);
21121
+ if (count === "invalid") {
21122
+ return check2("Provider config", "fail", `Provider config is invalid JSON: ${candidate}`);
21123
+ }
21124
+ if (count !== null && count > 0) {
21125
+ return check2("Provider config", "pass", `${count} provider(s) configured.`);
21126
+ }
20840
21127
  return check2(
20841
- "Chrome extension package",
21128
+ "Provider config",
20842
21129
  "warn",
20843
- `${zip} exists but no .sha256 checksum was found.`,
20844
- "Run `npm run extension:zip` to regenerate the zip and checksum."
21130
+ `Provider config exists but no providers were found: ${candidate}`,
21131
+ "Add a provider through the control API/MCP, or use Settings when a UI is available."
20845
21132
  );
20846
21133
  }
20847
21134
  return check2(
20848
- "Chrome extension package",
21135
+ "Provider config",
20849
21136
  "warn",
20850
- "No Chrome extension zip artifact was found.",
20851
- "Run `npm run extension:zip` before Chrome Web Store upload."
20852
- );
20853
- }
20854
- async function buildDoctorReport(options, deps = createDoctorDeps()) {
20855
- const rootDir = findProjectRoot(deps.cwd, deps);
20856
- const coreChecks = [
20857
- await checkProxyHealth(options.port, deps),
20858
- checkProviderConfig(options, deps),
20859
- checkPackage(rootDir, deps)
20860
- ];
20861
- const checksWithExtension = options.chromeExtension ? [...coreChecks, checkExtensionSource(rootDir, deps), checkExtensionPackage(rootDir, deps)] : coreChecks;
20862
- const checks = options.verbose ? [...checksWithExtension, ...verboseChecks(options, deps)] : checksWithExtension;
20863
- const passCount = checks.filter((item) => item.severity === "pass").length;
20864
- const warnCount = checks.filter((item) => item.severity === "warn").length;
20865
- const failCount = checks.filter((item) => item.severity === "fail").length;
20866
- return {
20867
- checks,
20868
- passCount,
20869
- warnCount,
20870
- failCount,
20871
- exitCode: failCount > 0 ? 1 : 0
20872
- };
20873
- }
20874
- function formatDoctorReport(report) {
20875
- const lines = ["agent-inspector doctor", ""];
20876
- for (const item of report.checks) {
20877
- lines.push(`${item.severity.toUpperCase().padEnd(4)} ${item.name}: ${item.message}`);
20878
- if (item.hint !== null) {
20879
- lines.push(` ${item.hint}`);
20880
- }
20881
- }
20882
- lines.push("");
20883
- lines.push(
20884
- `Summary: ${report.passCount} pass, ${report.warnCount} warn, ${report.failCount} fail`
21137
+ "No provider config was found.",
21138
+ "Add a provider through the control API/MCP, use Settings when a UI is available, or pass --providers for this check."
20885
21139
  );
20886
- return `${lines.join("\n")}
20887
- `;
20888
- }
20889
- function formatDoctorJson(report) {
20890
- return `${JSON.stringify(report, null, 2)}
20891
- `;
20892
- }
20893
- function doctorHelp() {
20894
- return [
20895
- "agent-inspector doctor",
20896
- "",
20897
- "Usage:",
20898
- " agent-inspector doctor [--port <port>] [--config-dir <dir>] [--providers <json>] [--fix] [--json] [--verbose] [--chrome-extension]",
20899
- "",
20900
- "Checks local backend health, provider config presence, and package metadata.",
20901
- "The backend check probes the local health endpoint and does not require a composed Web UI.",
20902
- "Pass --fix to run safe local repairs before checks, including legacy data-dir migration.",
20903
- "Pass --json to emit a machine-readable report for CI, scripts, and Jenkins.",
20904
- "Pass --verbose to include local paths, capture mode, and log storage checks.",
20905
- "Pass --chrome-extension to also check Chrome companion source and packaged artifacts.",
20906
- ""
20907
- ].join("\n");
20908
21140
  }
20909
- async function fetchHealth(port, basePath) {
20910
- const controller = new AbortController();
20911
- const timeout = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
20912
- try {
20913
- const response = await fetch(
20914
- appendBasePathToOrigin(`http://127.0.0.1:${port}`, "/api/health", basePath),
20915
- {
20916
- cache: "no-store",
20917
- signal: controller.signal
21141
+ function publicOriginForDoctor(port, env) {
21142
+ const configured = env["AGENT_INSPECTOR_PUBLIC_ORIGIN"];
21143
+ if (configured !== void 0 && configured.trim() === configured) {
21144
+ try {
21145
+ const parsed = new URL(configured);
21146
+ if ((parsed.protocol === "http:" || parsed.protocol === "https:") && parsed.username.length === 0 && parsed.password.length === 0 && parsed.pathname === "/" && parsed.search.length === 0 && parsed.hash.length === 0) {
21147
+ return parsed.origin;
20918
21148
  }
20919
- );
20920
- return { ok: response.ok, status: response.status };
20921
- } catch {
20922
- return { ok: false, status: null };
20923
- } finally {
20924
- clearTimeout(timeout);
20925
- }
20926
- }
20927
- function isPortOpen(port) {
20928
- return new Promise((resolveOpen) => {
20929
- const socket = createConnection({ host: "127.0.0.1", port });
20930
- const finish = (value) => {
20931
- socket.removeAllListeners();
20932
- socket.destroy();
20933
- resolveOpen(value);
20934
- };
20935
- socket.setTimeout(PROBE_TIMEOUT_MS);
20936
- socket.once("connect", () => finish(true));
20937
- socket.once("timeout", () => finish(false));
20938
- socket.once("error", () => finish(false));
20939
- });
20940
- }
20941
- function readText(path2) {
20942
- try {
20943
- return readFileSync4(path2, "utf8");
20944
- } catch {
20945
- return null;
20946
- }
20947
- }
20948
- function listDir(path2) {
20949
- try {
20950
- return readdirSync5(path2);
20951
- } catch {
20952
- return null;
20953
- }
20954
- }
20955
- function stat3(path2) {
20956
- try {
20957
- const fileStat = statSync4(path2);
20958
- return { isDirectory: fileStat.isDirectory(), size: fileStat.size };
20959
- } catch {
20960
- return null;
21149
+ } catch {
21150
+ }
20961
21151
  }
21152
+ return `http://localhost:${String(port)}`;
20962
21153
  }
20963
- function createDoctorDeps() {
20964
- return {
20965
- env: process.env,
20966
- platform: process.platform,
20967
- cwd: process.cwd(),
20968
- exists: existsSync7,
20969
- readText,
20970
- listDir,
20971
- stat: stat3,
20972
- fetchHealth,
20973
- isPortOpen
20974
- };
20975
- }
20976
- async function runDoctor(argv) {
20977
- const parsed = parseDoctorArgs(argv);
20978
- if (parsed.kind === "error") {
20979
- process.stderr.write(`${parsed.message}
20980
- `);
20981
- return 2;
20982
- }
20983
- if (parsed.options.help) {
20984
- process.stdout.write(doctorHelp());
20985
- return 0;
21154
+ async function checkProxyHealth(port, deps) {
21155
+ const basePath = getConfiguredBasePath(deps.env);
21156
+ const health = await deps.fetchHealth(port, basePath);
21157
+ const url2 = publicOriginForDoctor(port, deps.env);
21158
+ const proxyUrl = appendBasePathToOrigin(url2, "/proxy", basePath);
21159
+ const healthPath = appendBasePathToOrigin("", "/api/health", basePath);
21160
+ if (health.ok) {
21161
+ return check2(
21162
+ "Backend health",
21163
+ "pass",
21164
+ `Agent Inspector backend is healthy at ${appendBasePathToOrigin(url2, "/", basePath)}.`,
21165
+ `Agent proxy URL: ${proxyUrl}. UI availability is not part of this backend check.`
21166
+ );
20986
21167
  }
20987
- if (parsed.options.fix) {
20988
- migrateLegacyDataDirs();
21168
+ if (await deps.isPortOpen(port)) {
21169
+ const suffix = health.status === null ? "" : ` (HTTP ${health.status})`;
21170
+ return check2(
21171
+ "Backend health",
21172
+ "fail",
21173
+ `Port ${port} is accepting connections, but ${healthPath} is not healthy${suffix}.`,
21174
+ "Stop that process, choose --port <n>, or start agent-inspector with --force-restart."
21175
+ );
20989
21176
  }
20990
- const report = await buildDoctorReport(parsed.options);
20991
- process.stdout.write(parsed.options.json ? formatDoctorJson(report) : formatDoctorReport(report));
20992
- return report.exitCode;
21177
+ return check2(
21178
+ "Backend health",
21179
+ "warn",
21180
+ `No process is listening on port ${port}.`,
21181
+ "Start with `agent-inspector`, `agent-inspector --background`, or `bun run dev`."
21182
+ );
20993
21183
  }
20994
- var DEFAULT_PORT3, PROBE_TIMEOUT_MS, EXTENSION_REQUIRED_FILES;
20995
- var init_doctor = __esm({
20996
- "src/cli/doctor.ts"() {
20997
- "use strict";
20998
- init_basePath();
20999
- init_dataDir();
21000
- DEFAULT_PORT3 = 9527;
21001
- PROBE_TIMEOUT_MS = 2e3;
21002
- EXTENSION_REQUIRED_FILES = [
21003
- "manifest.json",
21004
- "sidepanel.html",
21005
- "sidepanel.js",
21006
- "sidepanel.css",
21007
- "service-worker.js",
21008
- "icons/icon.svg",
21009
- "icons/icon-16.png",
21010
- "icons/icon-32.png",
21011
- "icons/icon-48.png",
21012
- "icons/icon-128.png"
21013
- ];
21184
+ function findProjectRoot(startDir, deps) {
21185
+ let current = resolve5(startDir);
21186
+ for (let i = 0; i < 6; i++) {
21187
+ if (deps.exists(join8(current, "package.json"))) return current;
21188
+ const next = resolve5(current, "..");
21189
+ if (next === current) return startDir;
21190
+ current = next;
21014
21191
  }
21015
- });
21016
-
21017
- // src/cli/alias.ts
21018
- var alias_exports = {};
21019
- __export(alias_exports, {
21020
- parseAliasArgs: () => parseAliasArgs,
21021
- runAlias: () => runAlias
21022
- });
21023
- function parsePort3(raw) {
21024
- if (raw === void 0 || raw.trim() === "") return null;
21025
- const port = Number(raw);
21026
- if (!Number.isInteger(port) || port < 1 || port > 65535) return null;
21027
- return port;
21028
- }
21029
- function normalizeAliasPath(raw) {
21030
- return normalizePublicBasePath(raw);
21031
- }
21032
- function isValidHost(host) {
21033
- const trimmed = host.trim();
21034
- return trimmed.length > 0 && !trimmed.includes("://") && !trimmed.includes("/") && !trimmed.includes("?") && !trimmed.includes("#") && !/\s/u.test(trimmed);
21035
- }
21036
- function formatHostForUrl(host) {
21037
- if (host.startsWith("[") && host.endsWith("]")) return host;
21038
- return host.includes(":") ? `[${host}]` : host;
21192
+ return startDir;
21039
21193
  }
21040
- function parseAliasArgs(argv) {
21041
- let host = DEFAULT_ALIAS_HOST;
21042
- let port = DEFAULT_ALIAS_PORT;
21043
- let json2 = false;
21044
- let help = false;
21045
- const positional = [];
21046
- for (let index = 0; index < argv.length; index++) {
21047
- const arg = argv[index];
21048
- switch (arg) {
21049
- case void 0:
21050
- continue;
21051
- case "--help":
21052
- case "-h":
21053
- help = true;
21054
- break;
21055
- case "--host": {
21056
- const value = argv[index + 1];
21057
- if (value === void 0 || !isValidHost(value)) {
21058
- return {
21059
- kind: "error",
21060
- message: "alias: --host requires a hostname or IP address without a protocol or path"
21061
- };
21062
- }
21063
- host = value.trim();
21064
- index++;
21065
- break;
21066
- }
21067
- case "--port":
21068
- case "-p": {
21069
- const value = parsePort3(argv[index + 1]);
21070
- if (value === null) {
21071
- return {
21072
- kind: "error",
21073
- message: "alias: --port must be an integer from 1 to 65535"
21074
- };
21075
- }
21076
- port = value;
21077
- index++;
21078
- break;
21079
- }
21080
- case "--json":
21081
- json2 = true;
21082
- break;
21083
- default:
21084
- if (arg.startsWith("-")) {
21085
- return { kind: "error", message: `alias: unknown option ${arg}` };
21086
- }
21087
- positional.push(arg);
21088
- }
21194
+ function checkPackage(rootDir, deps) {
21195
+ const packagePath = join8(rootDir, "package.json");
21196
+ const text = deps.readText(packagePath);
21197
+ if (text === null) {
21198
+ return check2("Package metadata", "warn", "package.json was not found near the CLI bundle.");
21089
21199
  }
21090
- if (help) return { kind: "help" };
21091
- const command = positional[0];
21092
- switch (command) {
21093
- case "list":
21094
- if (positional.length !== 1) {
21095
- return { kind: "error", message: "alias: list does not accept a base path" };
21096
- }
21097
- return { kind: "ok", options: { action: { kind: "list" }, host, port, json: json2 } };
21098
- case "add":
21099
- case "remove": {
21100
- const rawBasePath = positional[1];
21101
- if (rawBasePath === void 0 || positional.length !== 2) {
21102
- return { kind: "error", message: `alias: ${command} requires exactly one base path` };
21103
- }
21104
- const basePath = normalizeAliasPath(rawBasePath);
21105
- if (basePath === null) {
21106
- return {
21107
- kind: "error",
21108
- message: `alias: ${command} base path must be an absolute URL path without a query or hash`
21109
- };
21110
- }
21111
- const action = { kind: command, basePath };
21112
- return { kind: "ok", options: { action, host, port, json: json2 } };
21200
+ try {
21201
+ const parsed = JSON.parse(text);
21202
+ if (!isObject3(parsed)) {
21203
+ return check2("Package metadata", "warn", "package.json is not an object.");
21113
21204
  }
21114
- case void 0:
21115
- return { kind: "error", message: "alias: expected add <path>, list, or remove <path>" };
21116
- default:
21117
- return { kind: "error", message: `alias: unknown command ${command}` };
21205
+ const name = parsed["name"];
21206
+ const version2 = parsed["version"];
21207
+ if (typeof name === "string" && typeof version2 === "string") {
21208
+ return check2("Package metadata", "pass", `${name}@${version2}`);
21209
+ }
21210
+ return check2("Package metadata", "warn", "package.json is missing name or version.");
21211
+ } catch {
21212
+ return check2("Package metadata", "warn", "package.json is not valid JSON.");
21118
21213
  }
21119
21214
  }
21120
- function aliasHelp() {
21121
- return `agent-inspector alias - mount additional base paths on a running runtime
21122
-
21123
- Usage:
21124
- agent-inspector alias add <path> [--host <host>] [--port <port>] [--json]
21125
- agent-inspector alias list [--host <host>] [--port <port>] [--json]
21126
- agent-inspector alias remove <path> [--host <host>] [--port <port>] [--json]
21127
-
21128
- Options:
21129
- --host <host> Running instance host (default: localhost)
21130
- --port <port> Running instance port (default: 9527)
21131
- --json Emit machine-readable output
21132
- --help, -h Show this help
21133
-
21134
- Aliases are runtime-scoped. This command never starts another Agent Inspector runtime.
21135
- `;
21215
+ function countFilesInDir(dir, deps) {
21216
+ const files = deps.listDir(dir);
21217
+ if (files === null) return null;
21218
+ let count = 0;
21219
+ for (const file2 of files) {
21220
+ const fileStat = deps.stat(join8(dir, file2));
21221
+ if (fileStat !== null && !fileStat.isDirectory) count += 1;
21222
+ }
21223
+ return count;
21136
21224
  }
21137
- function isObject4(value) {
21138
- return typeof value === "object" && value !== null && !Array.isArray(value);
21225
+ function formatBytes(bytes) {
21226
+ if (bytes < 1024) return `${bytes} B`;
21227
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
21228
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MiB`;
21229
+ return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GiB`;
21139
21230
  }
21140
- function parseAliasSnapshot(value) {
21141
- if (!isObject4(value)) return null;
21142
- const canonicalBasePath = value["canonicalBasePath"];
21143
- const aliases = value["aliases"];
21144
- const runtimeScoped = value["runtimeScoped"];
21145
- if (typeof canonicalBasePath !== "string" || !Array.isArray(aliases) || !aliases.every((alias) => typeof alias === "string") || runtimeScoped !== true) {
21146
- return null;
21231
+ function dirSize(dir, deps, depth = 0) {
21232
+ const dirStat = deps.stat(dir);
21233
+ if (dirStat === null) return null;
21234
+ if (!dirStat.isDirectory) return dirStat.size;
21235
+ if (depth > 4) return 0;
21236
+ const files = deps.listDir(dir);
21237
+ if (files === null) return null;
21238
+ let total = 0;
21239
+ for (const file2 of files) {
21240
+ const childSize = dirSize(join8(dir, file2), deps, depth + 1);
21241
+ if (childSize !== null) total += childSize;
21147
21242
  }
21148
- return {
21149
- canonicalBasePath: normalizeBasePath(canonicalBasePath),
21150
- aliases: aliases.map((alias) => normalizeBasePath(alias)),
21151
- runtimeScoped: true
21152
- };
21243
+ return total;
21244
+ }
21245
+ function verboseChecks(options, deps) {
21246
+ const dataDir = resolveDefaultDataDir(deps.env, deps.platform, deps.exists);
21247
+ const logsDir = join8(dataDir, "logs");
21248
+ const chunksDir = join8(dataDir, "chunks");
21249
+ const providerCandidates = providerConfigCandidates(options, deps);
21250
+ const captureMode = deps.env["AGENT_INSPECTOR_MODE"] ?? deps.env["AGENT_INSPECTOR_CAPTURE_MODE"] ?? "simple";
21251
+ const logCount = countFilesInDir(logsDir, deps);
21252
+ const logSize = dirSize(logsDir, deps);
21253
+ const chunkSize = dirSize(chunksDir, deps);
21254
+ const homeDir = deps.platform === "win32" ? deps.env["USERPROFILE"] ?? deps.env["APPDATA"] ?? null : deps.env["HOME"] ?? null;
21255
+ const claudeSkill = homeDir === null ? null : join8(homeDir, ".claude", "skills", "agent-inspector-onboard", "SKILL.md");
21256
+ const claudeCommand = homeDir === null ? null : join8(homeDir, ".claude", "commands", "agent-inspector-onboard.md");
21257
+ const codexSkill = homeDir === null ? null : join8(homeDir, ".codex", "skills", "agent-inspector-onboard", "SKILL.md");
21258
+ const hasClaudeOnboarding = claudeSkill !== null && claudeCommand !== null && deps.exists(claudeSkill) && deps.exists(claudeCommand);
21259
+ const hasCodexOnboarding = codexSkill !== null && deps.exists(codexSkill);
21260
+ return [
21261
+ check2("Data directory", "pass", dataDir),
21262
+ check2("Provider config search", "pass", providerCandidates.join(", ")),
21263
+ check2("Capture mode env", "pass", captureMode),
21264
+ check2(
21265
+ "Log storage",
21266
+ logCount === null ? "warn" : "pass",
21267
+ logCount === null ? `Log directory was not found or could not be read: ${logsDir}` : `${logCount} file(s), ${formatBytes(logSize ?? 0)} under ${logsDir}`
21268
+ ),
21269
+ check2(
21270
+ "Chunk storage",
21271
+ chunkSize === null ? "warn" : "pass",
21272
+ chunkSize === null ? `Chunk directory was not found or could not be read: ${chunksDir}` : `${formatBytes(chunkSize)} under ${chunksDir}`
21273
+ ),
21274
+ check2(
21275
+ "Claude onboarding",
21276
+ hasClaudeOnboarding ? "pass" : "warn",
21277
+ hasClaudeOnboarding ? "Claude Code skill and slash command are installed." : "Claude Code onboarding files were not found.",
21278
+ "Run `agent-inspector onboard --force` after installing or upgrading."
21279
+ ),
21280
+ check2(
21281
+ "Codex onboarding",
21282
+ hasCodexOnboarding ? "pass" : "warn",
21283
+ hasCodexOnboarding ? "Codex onboarding skill is installed." : "Codex onboarding skill was not found.",
21284
+ "Run `agent-inspector onboard --force` after installing or upgrading."
21285
+ )
21286
+ ];
21153
21287
  }
21154
- function readErrorDetail(value) {
21155
- if (!isObject4(value)) return null;
21156
- const error51 = value["error"];
21157
- if (typeof error51 === "string" && error51.trim() !== "") return error51;
21158
- const message = value["message"];
21159
- return typeof message === "string" && message.trim() !== "" ? message : null;
21288
+ function checkExtensionSource(rootDir, deps) {
21289
+ const extensionDir = join8(rootDir, "extensions", "chrome");
21290
+ const missing = EXTENSION_REQUIRED_FILES.filter((file2) => !deps.exists(join8(extensionDir, file2)));
21291
+ if (missing.length === 0) {
21292
+ return check2(
21293
+ "Chrome extension source",
21294
+ "pass",
21295
+ "Manifest, side panel, service worker, and icons are present."
21296
+ );
21297
+ }
21298
+ return check2(
21299
+ "Chrome extension source",
21300
+ "fail",
21301
+ `Missing ${missing.length} Chrome extension file(s): ${missing.join(", ")}`,
21302
+ "Restore the extension files before packaging or loading unpacked."
21303
+ );
21160
21304
  }
21161
- async function readJson2(response) {
21305
+ function extensionManifestVersion(rootDir, deps) {
21306
+ const manifestText = deps.readText(join8(rootDir, "extensions", "chrome", "manifest.json"));
21307
+ if (manifestText === null) return null;
21162
21308
  try {
21163
- const value = await response.json();
21164
- return value;
21309
+ const manifest = JSON.parse(manifestText);
21310
+ if (!isObject3(manifest)) return null;
21311
+ const version2 = manifest["version"];
21312
+ return typeof version2 === "string" && version2 !== "" ? version2 : null;
21165
21313
  } catch {
21166
21314
  return null;
21167
21315
  }
21168
21316
  }
21169
- async function requestAliases(endpoint2, action, fetchImpl) {
21170
- const controller = new AbortController();
21171
- const timeout = setTimeout(() => {
21172
- controller.abort();
21173
- }, REQUEST_TIMEOUT_MS);
21174
- const method = action.kind === "list" ? "GET" : action.kind === "add" ? "POST" : "DELETE";
21175
- const basePath = action.kind === "list" ? null : action.basePath;
21176
- const init = {
21177
- method,
21178
- headers: action.kind === "list" ? { accept: "application/json" } : {
21179
- accept: "application/json",
21180
- "content-type": "application/json",
21181
- "x-agent-inspector-csrf": "1"
21182
- },
21183
- signal: controller.signal
21184
- };
21185
- if (basePath !== null) init.body = JSON.stringify({ basePath });
21186
- try {
21187
- const response = await fetchImpl(endpoint2, init);
21188
- const value = await readJson2(response);
21189
- if (!response.ok) {
21190
- return { kind: "http-error", status: response.status, detail: readErrorDetail(value) };
21317
+ function checkExtensionPackage(rootDir, deps) {
21318
+ const distDir = join8(rootDir, "dist", "chrome-extension");
21319
+ const files = deps.listDir(distDir);
21320
+ if (files === null) {
21321
+ return check2(
21322
+ "Chrome extension package",
21323
+ "warn",
21324
+ "No packaged Chrome extension artifact was found.",
21325
+ "Run `npm run extension:zip` before Chrome Web Store upload."
21326
+ );
21327
+ }
21328
+ const manifestVersion = extensionManifestVersion(rootDir, deps);
21329
+ if (manifestVersion !== null) {
21330
+ const zipName = `agent-inspector-companion-v${manifestVersion}.zip`;
21331
+ const checksumName = `${zipName}.sha256`;
21332
+ const hasZip = files.includes(zipName);
21333
+ const hasChecksum = files.includes(checksumName);
21334
+ if (hasZip && hasChecksum) {
21335
+ return check2("Chrome extension package", "pass", `${zipName} with checksum is present.`);
21191
21336
  }
21192
- const snapshot = parseAliasSnapshot(value);
21193
- return snapshot === null ? { kind: "invalid-response" } : { kind: "success", snapshot };
21194
- } catch (error51) {
21195
- const timedOut = controller.signal.aborted || error51 instanceof Error && error51.name === "AbortError";
21196
- return { kind: "network-error", timedOut };
21197
- } finally {
21198
- clearTimeout(timeout);
21337
+ if (hasZip) {
21338
+ return check2(
21339
+ "Chrome extension package",
21340
+ "warn",
21341
+ `${zipName} exists but no .sha256 checksum was found.`,
21342
+ "Run `npm run extension:zip` to regenerate the zip and checksum."
21343
+ );
21344
+ }
21345
+ return check2(
21346
+ "Chrome extension package",
21347
+ "warn",
21348
+ `Current Chrome extension package was not found: ${zipName}`,
21349
+ "Run `npm run extension:zip` before Chrome Web Store upload."
21350
+ );
21199
21351
  }
21352
+ const zip = files.find((file2) => file2.endsWith(".zip"));
21353
+ const checksum = files.find((file2) => file2.endsWith(".zip.sha256"));
21354
+ if (zip !== void 0 && checksum !== void 0) {
21355
+ return check2("Chrome extension package", "pass", `${zip} with checksum is present.`);
21356
+ }
21357
+ if (zip !== void 0) {
21358
+ return check2(
21359
+ "Chrome extension package",
21360
+ "warn",
21361
+ `${zip} exists but no .sha256 checksum was found.`,
21362
+ "Run `npm run extension:zip` to regenerate the zip and checksum."
21363
+ );
21364
+ }
21365
+ return check2(
21366
+ "Chrome extension package",
21367
+ "warn",
21368
+ "No Chrome extension zip artifact was found.",
21369
+ "Run `npm run extension:zip` before Chrome Web Store upload."
21370
+ );
21200
21371
  }
21201
- function displayBasePath2(basePath) {
21202
- const normalized = normalizeBasePath(basePath);
21203
- return normalized === "" ? "/" : normalized;
21204
- }
21205
- function fullUrl(origin, basePath) {
21206
- return `${origin}${normalizeBasePath(basePath)}`;
21207
- }
21208
- function buildOutput(origin, action, snapshot) {
21372
+ async function buildDoctorReport(options, deps = createDoctorDeps()) {
21373
+ const rootDir = findProjectRoot(deps.cwd, deps);
21374
+ const coreChecks = [
21375
+ await checkProxyHealth(options.port, deps),
21376
+ checkProviderConfig(options, deps),
21377
+ checkPackage(rootDir, deps)
21378
+ ];
21379
+ const checksWithExtension = options.chromeExtension ? [...coreChecks, checkExtensionSource(rootDir, deps), checkExtensionPackage(rootDir, deps)] : coreChecks;
21380
+ const checks = options.verbose ? [...checksWithExtension, ...verboseChecks(options, deps)] : checksWithExtension;
21381
+ const passCount = checks.filter((item) => item.severity === "pass").length;
21382
+ const warnCount = checks.filter((item) => item.severity === "warn").length;
21383
+ const failCount = checks.filter((item) => item.severity === "fail").length;
21209
21384
  return {
21210
- action: action.kind,
21211
- canonicalBasePath: displayBasePath2(snapshot.canonicalBasePath),
21212
- canonicalUrl: fullUrl(origin, snapshot.canonicalBasePath),
21213
- aliases: snapshot.aliases.map(displayBasePath2),
21214
- aliasUrls: snapshot.aliases.map((alias) => fullUrl(origin, alias)),
21215
- runtimeScoped: true
21385
+ checks,
21386
+ passCount,
21387
+ warnCount,
21388
+ failCount,
21389
+ exitCode: failCount > 0 ? 1 : 0
21216
21390
  };
21217
21391
  }
21218
- function formatHumanOutput(output) {
21219
- const heading = output.action === "add" ? "Base-path alias added." : output.action === "remove" ? "Base-path alias removed." : "Base-path aliases for the running runtime:";
21220
- const aliasPaths = output.aliases.length === 0 ? "(none)" : output.aliases.join(", ");
21221
- const aliasUrls = output.aliasUrls.length === 0 ? " (none)" : output.aliasUrls.map((url2) => ` - ${url2}`).join("\n");
21222
- return `${heading}
21223
- Canonical base path: ${output.canonicalBasePath}
21224
- Canonical URL: ${output.canonicalUrl}
21225
- Alias base paths: ${aliasPaths}
21226
- Alias URLs:
21227
- ${aliasUrls}
21228
- Scope: current runtime only; no second runtime was started and aliases are not persisted.
21392
+ function formatDoctorReport(report) {
21393
+ const lines = ["agent-inspector doctor", ""];
21394
+ for (const item of report.checks) {
21395
+ lines.push(`${item.severity.toUpperCase().padEnd(4)} ${item.name}: ${item.message}`);
21396
+ if (item.hint !== null) {
21397
+ lines.push(` ${item.hint}`);
21398
+ }
21399
+ }
21400
+ lines.push("");
21401
+ lines.push(
21402
+ `Summary: ${report.passCount} pass, ${report.warnCount} warn, ${report.failCount} fail`
21403
+ );
21404
+ return `${lines.join("\n")}
21229
21405
  `;
21230
21406
  }
21231
- function writeError2(io, json2, code, message) {
21232
- if (json2) {
21233
- io.stderr(`${JSON.stringify({ error: code, message, runtimeStarted: false })}
21234
- `);
21235
- return;
21236
- }
21237
- io.stderr(`agent-inspector alias: ${message}
21238
- `);
21407
+ function formatDoctorJson(report) {
21408
+ return `${JSON.stringify(report, null, 2)}
21409
+ `;
21239
21410
  }
21240
- function oldRuntimeMessage(origin) {
21241
- return `the runtime at ${origin} does not support base-path aliases. Upgrade Agent Inspector and restart that instance, then retry; this command did not start a runtime.`;
21411
+ function doctorHelp() {
21412
+ return [
21413
+ "agent-inspector doctor",
21414
+ "",
21415
+ "Usage:",
21416
+ " agent-inspector doctor [--port <port>] [--config-dir <dir>] [--providers <json>] [--fix] [--json] [--verbose] [--chrome-extension]",
21417
+ "",
21418
+ "Checks local backend health, provider config presence, and package metadata.",
21419
+ "The backend check probes the local health endpoint and does not require a composed Web UI.",
21420
+ "Pass --fix to run safe local repairs before checks, including legacy data-dir migration.",
21421
+ "Pass --json to emit a machine-readable report for CI, scripts, and Jenkins.",
21422
+ "Pass --verbose to include local paths, capture mode, and log storage checks.",
21423
+ "Pass --chrome-extension to also check Chrome companion source and packaged artifacts.",
21424
+ ""
21425
+ ].join("\n");
21242
21426
  }
21243
- async function handleHttpError(options, endpoint2, origin, result, fetchImpl, io) {
21244
- if (result.status === 404 && options.action.kind === "remove") {
21245
- const probe = await requestAliases(endpoint2, { kind: "list" }, fetchImpl);
21246
- if (probe.kind === "success") {
21247
- writeError2(
21248
- io,
21249
- options.json,
21250
- "alias_not_found",
21251
- `base path ${displayBasePath2(options.action.basePath)} is not mounted. Run "agent-inspector alias list --host ${options.host} --port ${options.port}" to inspect the current runtime.`
21252
- );
21253
- return 1;
21254
- }
21255
- if (probe.kind === "network-error") {
21256
- writeError2(
21257
- io,
21258
- options.json,
21259
- probe.timedOut ? "request_timeout" : "runtime_unreachable",
21260
- `${probe.timedOut ? "timed out contacting" : "cannot reach"} ${origin}. Confirm the existing runtime is running and the --host/--port values are correct; no runtime was started.`
21261
- );
21262
- return 1;
21263
- }
21264
- if (probe.kind === "http-error" && (probe.status === 404 || probe.status === 405)) {
21265
- writeError2(io, options.json, "unsupported_runtime", oldRuntimeMessage(origin));
21266
- return 1;
21267
- }
21427
+ async function fetchHealth(port, basePath) {
21428
+ const controller = new AbortController();
21429
+ const timeout = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
21430
+ try {
21431
+ const response = await fetch(
21432
+ appendBasePathToOrigin(`http://127.0.0.1:${port}`, "/api/health", basePath),
21433
+ {
21434
+ cache: "no-store",
21435
+ signal: controller.signal
21436
+ }
21437
+ );
21438
+ return { ok: response.ok, status: response.status };
21439
+ } catch {
21440
+ return { ok: false, status: null };
21441
+ } finally {
21442
+ clearTimeout(timeout);
21268
21443
  }
21269
- if (result.status === 404 || result.status === 405) {
21270
- writeError2(io, options.json, "unsupported_runtime", oldRuntimeMessage(origin));
21271
- return 1;
21444
+ }
21445
+ function isPortOpen(port) {
21446
+ return new Promise((resolveOpen) => {
21447
+ const socket = createConnection({ host: "127.0.0.1", port });
21448
+ const finish = (value) => {
21449
+ socket.removeAllListeners();
21450
+ socket.destroy();
21451
+ resolveOpen(value);
21452
+ };
21453
+ socket.setTimeout(PROBE_TIMEOUT_MS);
21454
+ socket.once("connect", () => finish(true));
21455
+ socket.once("timeout", () => finish(false));
21456
+ socket.once("error", () => finish(false));
21457
+ });
21458
+ }
21459
+ function readText(path2) {
21460
+ try {
21461
+ return readFileSync4(path2, "utf8");
21462
+ } catch {
21463
+ return null;
21272
21464
  }
21273
- if (result.status === 409) {
21274
- writeError2(
21275
- io,
21276
- options.json,
21277
- "alias_conflict",
21278
- `${result.detail ?? "the base path is already mounted or conflicts with another mount"}. Run "agent-inspector alias list --host ${options.host} --port ${options.port}" to inspect the current runtime.`
21279
- );
21280
- return 1;
21465
+ }
21466
+ function listDir(path2) {
21467
+ try {
21468
+ return readdirSync5(path2);
21469
+ } catch {
21470
+ return null;
21281
21471
  }
21282
- const detail = result.detail === null ? "the runtime rejected the request" : result.detail;
21283
- writeError2(
21284
- io,
21285
- options.json,
21286
- "runtime_rejected_request",
21287
- `${detail} (HTTP ${result.status}). Check the running instance and retry; no runtime was started.`
21288
- );
21289
- return 1;
21290
21472
  }
21291
- async function runAlias(argv, fetchImpl = fetch, io = processIo2) {
21292
- const parsed = parseAliasArgs(argv);
21293
- if (parsed.kind === "help") {
21294
- io.stdout(aliasHelp());
21295
- return 0;
21473
+ function stat3(path2) {
21474
+ try {
21475
+ const fileStat = statSync4(path2);
21476
+ return { isDirectory: fileStat.isDirectory(), size: fileStat.size };
21477
+ } catch {
21478
+ return null;
21296
21479
  }
21480
+ }
21481
+ function createDoctorDeps() {
21482
+ return {
21483
+ env: process.env,
21484
+ platform: process.platform,
21485
+ cwd: process.cwd(),
21486
+ exists: existsSync7,
21487
+ readText,
21488
+ listDir,
21489
+ stat: stat3,
21490
+ fetchHealth,
21491
+ isPortOpen
21492
+ };
21493
+ }
21494
+ async function runDoctor(argv) {
21495
+ const parsed = parseDoctorArgs(argv);
21297
21496
  if (parsed.kind === "error") {
21298
- io.stderr(`${parsed.message}
21299
-
21300
- ${aliasHelp()}`);
21497
+ process.stderr.write(`${parsed.message}
21498
+ `);
21301
21499
  return 2;
21302
21500
  }
21303
- const { options } = parsed;
21304
- const origin = `http://${formatHostForUrl(options.host)}:${options.port}`;
21305
- const endpoint2 = `${origin}${ALIAS_CONTROL_PATH}`;
21306
- const result = await requestAliases(endpoint2, options.action, fetchImpl);
21307
- switch (result.kind) {
21308
- case "success": {
21309
- const output = buildOutput(origin, options.action, result.snapshot);
21310
- io.stdout(options.json ? `${JSON.stringify(output, null, 2)}
21311
- ` : formatHumanOutput(output));
21312
- return 0;
21313
- }
21314
- case "network-error":
21315
- writeError2(
21316
- io,
21317
- options.json,
21318
- result.timedOut ? "request_timeout" : "runtime_unreachable",
21319
- `${result.timedOut ? "timed out contacting" : "cannot reach"} ${origin}. Confirm the existing runtime is running and the --host/--port values are correct; no runtime was started.`
21320
- );
21321
- return 1;
21322
- case "invalid-response":
21323
- writeError2(
21324
- io,
21325
- options.json,
21326
- "invalid_runtime_response",
21327
- `the runtime at ${origin} returned an invalid alias response. Upgrade and restart that instance, then retry; no runtime was started.`
21328
- );
21329
- return 1;
21330
- case "http-error":
21331
- return handleHttpError(options, endpoint2, origin, result, fetchImpl, io);
21501
+ if (parsed.options.help) {
21502
+ process.stdout.write(doctorHelp());
21503
+ return 0;
21504
+ }
21505
+ if (parsed.options.fix) {
21506
+ migrateLegacyDataDirs();
21332
21507
  }
21508
+ const report = await buildDoctorReport(parsed.options);
21509
+ process.stdout.write(parsed.options.json ? formatDoctorJson(report) : formatDoctorReport(report));
21510
+ return report.exitCode;
21333
21511
  }
21334
- var DEFAULT_ALIAS_HOST, DEFAULT_ALIAS_PORT, ALIAS_CONTROL_PATH, REQUEST_TIMEOUT_MS, processIo2;
21335
- var init_alias = __esm({
21336
- "src/cli/alias.ts"() {
21512
+ var DEFAULT_PORT3, PROBE_TIMEOUT_MS, EXTENSION_REQUIRED_FILES;
21513
+ var init_doctor = __esm({
21514
+ "src/cli/doctor.ts"() {
21337
21515
  "use strict";
21338
21516
  init_basePath();
21339
- DEFAULT_ALIAS_HOST = "localhost";
21340
- DEFAULT_ALIAS_PORT = 9527;
21341
- ALIAS_CONTROL_PATH = "/.agent-inspector/base-path-aliases";
21342
- REQUEST_TIMEOUT_MS = 5e3;
21343
- processIo2 = {
21344
- stdout: (text) => {
21345
- process.stdout.write(text);
21346
- },
21347
- stderr: (text) => {
21348
- process.stderr.write(text);
21349
- }
21350
- };
21517
+ init_dataDir();
21518
+ DEFAULT_PORT3 = 9527;
21519
+ PROBE_TIMEOUT_MS = 2e3;
21520
+ EXTENSION_REQUIRED_FILES = [
21521
+ "manifest.json",
21522
+ "sidepanel.html",
21523
+ "sidepanel.js",
21524
+ "sidepanel.css",
21525
+ "service-worker.js",
21526
+ "icons/icon.svg",
21527
+ "icons/icon-16.png",
21528
+ "icons/icon-32.png",
21529
+ "icons/icon-48.png",
21530
+ "icons/icon-128.png"
21531
+ ];
21351
21532
  }
21352
21533
  });
21353
21534
 
21354
- // src/cli.ts
21355
- import { Buffer as Buffer5 } from "node:buffer";
21356
- import { spawn as spawn2 } from "node:child_process";
21357
- import { createConnection as createConnection2 } from "node:net";
21358
- import { fileURLToPath as fileURLToPath3 } from "node:url";
21359
- import { dirname as dirname3, join as join9, resolve as resolvePath2 } from "node:path";
21360
- import { existsSync as existsSync8 } from "node:fs";
21361
-
21362
- // package.json
21363
- var package_default = {
21364
- name: "@tonyclaw/agent-inspector",
21365
- version: "3.0.47",
21366
- type: "module",
21367
- description: "Agent observability and knowledge capture layer for AI coding tools.",
21368
- license: "MIT",
21369
- engines: {
21370
- node: ">=22.0.0"
21371
- },
21372
- author: "TonyClaw",
21373
- repository: {
21374
- type: "git",
21375
- url: "git+https://gitcode.com/TonyClaw/agent-inspector.git"
21376
- },
21377
- homepage: "https://gitcode.com/TonyClaw/agent-inspector#readme",
21378
- workspaces: [
21379
- "packages/*"
21380
- ],
21381
- bugs: {
21382
- url: "https://gitcode.com/TonyClaw/agent-inspector/issues"
21383
- },
21384
- publishConfig: {
21385
- access: "public",
21386
- registry: "https://registry.npmjs.org/"
21387
- },
21388
- keywords: [
21389
- "claude",
21390
- "anthropic",
21391
- "openai",
21392
- "proxy",
21393
- "api-inspector",
21394
- "agent-observability",
21395
- "agent-memory",
21396
- "debugging",
21397
- "llm",
21398
- "ai-coding-tools"
21399
- ],
21400
- bin: {
21401
- "agent-inspector": "bin/agent-inspector.js"
21402
- },
21403
- files: [
21404
- "bin",
21405
- "src",
21406
- "!src/**/*.test.ts",
21407
- "!src/**/*.stories.tsx",
21408
- "!src/**/__fixtures__",
21409
- "!src/ui/routeTree.gen.ts",
21410
- "!src/backend/routeTree.gen.ts",
21411
- "scripts/setup-windows-runtime.mjs",
21412
- "scripts/setup-agent-skills.mjs",
21413
- "scripts/generate-theme-scales.mjs",
21414
- "docs/instance-control.openapi.yaml",
21415
- "styles",
21416
- ".output",
21417
- "!.output/*.exe"
21418
- ],
21419
- scripts: {
21420
- dev: "node scripts/dev-decoupled.mjs",
21421
- "dev:ui": "vite dev --config vite.ui.config.ts",
21422
- "dev:backend": "node scripts/dev-backend.mjs",
21423
- "test:smoke": "bun scripts/run-test-suite.mjs smoke",
21424
- "test:fast": "bun scripts/run-test-suite.mjs smoke",
21425
- "test:unit": "bun scripts/run-test-suite.mjs unit",
21426
- "test:integration": "bun scripts/run-test-suite.mjs integration",
21427
- "test:changed": "bun scripts/run-test-suite.mjs changed",
21428
- "test:all": "bun scripts/run-test-suite.mjs all",
21429
- "test:compatibility-tooling": "node --test scripts/compatibility/*.test.mjs",
21430
- start: "node .output/cli.js",
21431
- "start:backend": "node .output/cli.js --backend-only --no-open",
21432
- "preview:ui": "vite preview --config vite.ui.config.ts",
21433
- "clean:output": "node scripts/clean-output.mjs",
21434
- build: "bun clean:output && bun build:backend && bun build:ui && bun smoke:ui-preview && bun build:workers && bun build:cli && bun build:mcp && bun check:bundle && bun check:boundaries",
21435
- "build:backend": "vite build --config vite.backend.config.ts",
21436
- "build:ui": "vite build --config vite.ui.config.ts",
21437
- "build:workers": "npx esbuild src/proxy/logFinalizer.worker.ts src/proxy/sessionWorkerEntry.ts --bundle --platform=node --target=node22 --format=esm --outdir=.output/workers --external:better-sqlite3",
21438
- "smoke:workers": "node scripts/smoke-production-workers.mjs",
21439
- "smoke:packed-workers": "node scripts/smoke-packed-workers.mjs",
21440
- "smoke:instances": "node scripts/smoke-windows-instances.mjs",
21441
- "smoke:coding-agent-instances": "node scripts/smoke-coding-agent-instances.mjs",
21442
- "smoke:ui-preview": "node scripts/smoke-decoupled-ui-preview.mjs",
21443
- "check:bundle": "node scripts/check-client-bundle.mjs",
21444
- "check:boundaries": "node scripts/check-runtime-boundaries.mjs",
21445
- "compatibility:bdd": "node scripts/lint-bdd-catalog.mjs",
21446
- "compatibility:compare": "node scripts/compatibility/cli.mjs compare",
21447
- "compatibility:drift": "node scripts/check-backend-compatibility.mjs",
21448
- "compatibility:fixtures": "bun scripts/validate-compatibility-fixtures.ts",
21449
- "compatibility:freeze": "node scripts/freeze-backend-baseline.mjs",
21450
- "compatibility:run": "node scripts/compatibility/cli.mjs run",
21451
- "compatibility:performance": "node scripts/compatibility/performance-cli.mjs",
21452
- "compatibility:performance:verify": "node scripts/compatibility/performance-cli.mjs verify",
21453
- "build:cli": "npx esbuild src/cli.ts --bundle --platform=node --target=node22 --format=esm --outfile=.output/cli.js",
21454
- "build:mcp": "npx esbuild packages/agent-inspector-mcp/src/cli.ts --bundle --platform=node --target=node22 --format=esm --outfile=packages/agent-inspector-mcp/dist/cli.js",
21455
- "rust:build-package": "node scripts/rust/build-platform.mjs",
21456
- "rust:stage-package": "node scripts/rust/stage-platform.mjs",
21457
- "rust:verify-package": "node scripts/rust/verify-platform.mjs",
21458
- "rust:pack-package": "node scripts/rust/pack-platform.mjs",
21459
- "rust:native-artifact": "node scripts/rust/create-native-artifact.mjs",
21460
- "rust:native:aggregate": "node scripts/rust/aggregate-native-artifacts.mjs",
21461
- "test:rust-packaging": "bun test scripts/rust/npmPackaging.test.ts scripts/rust/nativeReleaseContract.test.ts scripts/rust/nativeStartupSmoke.test.ts scripts/releaseCore.test.ts",
21462
- "rust:packages:check": "node scripts/rust/sync-package-versions.mjs",
21463
- "rust:packages:sync": "node scripts/rust/sync-package-versions.mjs --write",
21464
- "themes:generate": "node scripts/generate-theme-scales.mjs",
21465
- postinstall: "node scripts/setup-windows-runtime.mjs && node scripts/setup-agent-skills.mjs",
21466
- prepublishOnly: "node scripts/rust/require-release-orchestrator.mjs",
21467
- typecheck: "tsc --noEmit --incremental --tsBuildInfoFile .cache/tsconfig.tsbuildinfo",
21468
- lint: "eslint . --cache --cache-location .cache/eslintcache",
21469
- format: "biome format --write .",
21470
- "format:check": "biome format .",
21471
- "verify:fast": "bun scripts/run-static-checks.mjs --format && bun test:changed",
21472
- "extension:zip": "node scripts/pack-chrome-extension.mjs",
21473
- "extension:release": "node scripts/release-chrome-extension.mjs",
21474
- release: "node scripts/release.mjs",
21475
- knip: "knip",
21476
- check: "bun scripts/run-static-checks.mjs --format && bun check:boundaries && bun compatibility:bdd && bun rust:packages:check",
21477
- prepare: "husky",
21478
- ladle: "ladle serve",
21479
- "ladle:build": "ladle build"
21480
- },
21481
- dependencies: {
21482
- "@mariozechner/pi-agent-core": "^0.73.1",
21483
- "@mariozechner/pi-ai": "^0.73.1",
21484
- "@modelcontextprotocol/server": "^2.0.0-beta.4",
21485
- "@tailwindcss/typography": "^0.5.20",
21486
- "@tanstack/react-router": "^1.170.18",
21487
- "@tanstack/react-start": "^1.168.28",
21488
- "@tanstack/react-virtual": "^3.14.6",
21489
- "class-variance-authority": "^0.7.1",
21490
- clsx: "^2.1.1",
21491
- conf: "^15.1.0",
21492
- diff: "^9.0.0",
21493
- jszip: "^3.10.1",
21494
- "lucide-react": "^0.563.0",
21495
- "radix-ui": "^1.6.2",
21496
- react: "^19.2.7",
21497
- "react-dom": "^19.2.7",
21498
- "react-markdown": "^10.1.0",
21499
- swr: "^2.4.2",
21500
- "tailwind-merge": "^3.6.0",
21501
- three: "0.185.1",
21502
- "tw-animate-css": "^1.4.0",
21503
- zod: "^4.4.3"
21504
- },
21505
- optionalDependencies: {
21506
- "@tonyclaw/agent-inspector-backend-darwin-arm64": "3.0.47",
21507
- "@tonyclaw/agent-inspector-backend-linux-x64-gnu": "3.0.47",
21508
- "@tonyclaw/agent-inspector-backend-win32-x64-msvc": "3.0.47",
21509
- "better-sqlite3": "^12.11.1",
21510
- rcedit: "4.0.1"
21511
- },
21512
- devDependencies: {
21513
- "@biomejs/biome": "^2.5.3",
21514
- "@eslint/js": "^10.0.1",
21515
- "@ladle/react": "^5.1.1",
21516
- "@radix-ui/colors": "^3.0.0",
21517
- "@tailwindcss/vite": "^4.3.2",
21518
- "@tanstack/router-plugin": "^1.168.20",
21519
- "@types/bun": "^1.3.14",
21520
- "@types/react": "^19.2.17",
21521
- "@types/react-dom": "^19.2.3",
21522
- "@types/three": "0.185.1",
21523
- "@typescript-eslint/eslint-plugin": "^8.64.0",
21524
- "@typescript-eslint/parser": "^8.64.0",
21525
- "@vitejs/plugin-react": "^5.2.0",
21526
- ajv: "8.20.0",
21527
- "ajv-formats": "3.0.1",
21528
- "colorjs.io": "^0.7.0",
21529
- esbuild: "^0.28.1",
21530
- eslint: "^9.39.5",
21531
- "eslint-plugin-eslint-comments": "^3.2.0",
21532
- "eslint-plugin-functional": "^9.0.5",
21533
- "eslint-plugin-unicorn": "^63.0.0",
21534
- husky: "^9.1.7",
21535
- knip: "^5.88.1",
21536
- nitro: "^3.0.260610-beta",
21537
- playwright: "^1.61.1",
21538
- tailwindcss: "^4.3.2",
21539
- typescript: "^5.9.3",
21540
- vite: "^7.3.6",
21541
- "vite-tsconfig-paths": "^6.1.1"
21542
- },
21543
- overrides: {
21544
- undici: ">=7.24.0 <8.0.0",
21545
- rollup: ">=4.59.0 <5.0.0",
21546
- postcss: ">=8.5.10 <9.0.0",
21547
- "js-yaml": ">=4.1.2 <5.0.0",
21548
- flatted: ">=3.4.0 <4.0.0",
21549
- "smol-toml": ">=1.6.1 <2.0.0",
21550
- koa: ">=2.16.4 <3.0.0",
21551
- "@babel/core": ">=7.29.1 <8.0.0",
21552
- "@typescript-eslint/typescript-estree": "^8.63.0",
21553
- vite: "^7.3.6"
21554
- }
21555
- };
21556
-
21557
- // src/cli.ts
21558
- init_backendImplementation();
21559
- init_basePath();
21560
- init_managedInstance();
21561
- init_networkHints();
21562
-
21563
- // src/cli/startupOutput.ts
21564
- function shouldSuppressServerOutputLine(line, context) {
21565
- if (!context.enableIdentityProxy) return false;
21566
- if (context.publicPort === context.upstreamPort) return false;
21567
- const lowerLine = line.toLowerCase();
21568
- return lowerLine.includes("listening on") && lowerLine.includes(`:${context.upstreamPort}`);
21569
- }
21570
-
21571
- // src/cli/rustBackendPackage.ts
21572
- import { spawnSync } from "node:child_process";
21573
- init_zod();
21574
- import { createHash } from "node:crypto";
21575
- import { existsSync, readFileSync, statSync } from "node:fs";
21576
- import { createRequire } from "node:module";
21577
- import { dirname, isAbsolute, relative, resolve } from "node:path";
21578
- import { fileURLToPath } from "node:url";
21579
- var RUST_BACKEND_IDENTITY_FLAG = "--agent-inspector-package-identity";
21580
- var RUST_BACKEND_CLI_PROTOCOL = "agent-inspector-cli-backend-v1";
21581
- var RUST_BACKEND_IDENTITY_TIMEOUT_MS = 2e3;
21582
- var RustBackendIdentitySchema = external_exports.object({
21583
- schemaVersion: external_exports.literal(1),
21584
- name: external_exports.literal("agent-inspector-backend"),
21585
- packageVersion: external_exports.string().min(1),
21586
- target: external_exports.string().min(1),
21587
- binary: external_exports.string().min(1),
21588
- cliProtocol: external_exports.literal(RUST_BACKEND_CLI_PROTOCOL),
21589
- productionReady: external_exports.literal(true),
21590
- capabilities: external_exports.object({
21591
- nodeProtectedIngress: external_exports.literal(true),
21592
- basePath: external_exports.literal(true),
21593
- namedInstances: external_exports.literal(true),
21594
- gracefulShutdown: external_exports.literal(true)
21595
- }).strict()
21596
- }).strict();
21597
- var RustBackendPackageJsonSchema = external_exports.object({
21598
- name: external_exports.string().min(1),
21599
- version: external_exports.string().min(1),
21600
- os: external_exports.array(external_exports.string()).min(1),
21601
- cpu: external_exports.array(external_exports.string()).min(1),
21602
- libc: external_exports.array(external_exports.string()).optional(),
21603
- agentInspectorRustBackend: external_exports.object({
21604
- schemaVersion: external_exports.literal(1),
21605
- target: external_exports.string().min(1),
21606
- binary: external_exports.string().min(1),
21607
- manifest: external_exports.string().min(1),
21608
- identityFlag: external_exports.literal(RUST_BACKEND_IDENTITY_FLAG),
21609
- cliProtocol: external_exports.literal(RUST_BACKEND_CLI_PROTOCOL)
21610
- }).strict()
21611
- }).passthrough();
21612
- var RustBackendBinaryManifestSchema = external_exports.object({
21613
- schemaVersion: external_exports.literal(1),
21614
- packageName: external_exports.string().min(1),
21615
- packageVersion: external_exports.string().min(1),
21616
- target: external_exports.string().min(1),
21617
- binary: external_exports.string().min(1),
21618
- sha256: external_exports.string().regex(/^[a-f0-9]{64}$/u),
21619
- identity: RustBackendIdentitySchema
21620
- }).strict();
21621
- function packageFailure(code, message) {
21622
- return { kind: "error", error: { code, message } };
21535
+ // src/cli/alias.ts
21536
+ var alias_exports = {};
21537
+ __export(alias_exports, {
21538
+ parseAliasArgs: () => parseAliasArgs,
21539
+ runAlias: () => runAlias
21540
+ });
21541
+ function parsePort3(raw) {
21542
+ if (raw === void 0 || raw.trim() === "") return null;
21543
+ const port = Number(raw);
21544
+ if (!Number.isInteger(port) || port < 1 || port > 65535) return null;
21545
+ return port;
21623
21546
  }
21624
- function targetFailure(message) {
21625
- return { kind: "error", error: { code: "unsupported_platform", message } };
21547
+ function normalizeAliasPath(raw) {
21548
+ return normalizePublicBasePath(raw);
21626
21549
  }
21627
- function detectLinuxLibc() {
21628
- if (process.platform !== "linux") return "not-applicable";
21629
- const report = process.report.getReport();
21630
- const header = objectProperty(report, "header");
21631
- const glibcVersion = objectProperty(header, "glibcVersionRuntime");
21632
- if (typeof glibcVersion === "string" && glibcVersion.length > 0) {
21633
- return "glibc";
21634
- }
21635
- const sharedObjects = objectProperty(report, "sharedObjects");
21636
- return Array.isArray(sharedObjects) && sharedObjects.some((path2) => typeof path2 === "string" && path2.toLowerCase().includes("musl")) ? "musl" : "unknown";
21550
+ function isValidHost(host) {
21551
+ const trimmed = host.trim();
21552
+ return trimmed.length > 0 && !trimmed.includes("://") && !trimmed.includes("/") && !trimmed.includes("?") && !trimmed.includes("#") && !/\s/u.test(trimmed);
21637
21553
  }
21638
- function objectProperty(value, name) {
21639
- if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
21640
- return Object.getOwnPropertyDescriptor(value, name)?.value;
21554
+ function formatHostForUrl(host) {
21555
+ if (host.startsWith("[") && host.endsWith("]")) return host;
21556
+ return host.includes(":") ? `[${host}]` : host;
21641
21557
  }
21642
- function resolveRustBackendTarget(platform = process.platform, arch = process.arch, libc = platform === "linux" ? detectLinuxLibc() : "not-applicable") {
21643
- switch (platform) {
21644
- case "win32":
21645
- return arch === "x64" ? {
21646
- kind: "success",
21647
- value: {
21648
- packageName: "@tonyclaw/agent-inspector-backend-win32-x64-msvc",
21649
- target: "x86_64-pc-windows-msvc",
21650
- binary: "bin/agent-inspector-backend.exe",
21651
- os: "win32",
21652
- cpu: "x64",
21653
- libc: null
21654
- }
21655
- } : targetFailure(`Rust backend packages do not support Windows ${arch}.`);
21656
- case "linux":
21657
- if (arch !== "x64")
21658
- return targetFailure(`Rust backend packages do not support Linux ${arch}.`);
21659
- if (libc !== "glibc") {
21660
- return targetFailure(
21661
- `The Linux Rust backend package requires glibc; detected ${libc}. No binary was started.`
21662
- );
21558
+ function parseAliasArgs(argv) {
21559
+ let host = DEFAULT_ALIAS_HOST;
21560
+ let port = DEFAULT_ALIAS_PORT;
21561
+ let json2 = false;
21562
+ let help = false;
21563
+ const positional = [];
21564
+ for (let index = 0; index < argv.length; index++) {
21565
+ const arg = argv[index];
21566
+ switch (arg) {
21567
+ case void 0:
21568
+ continue;
21569
+ case "--help":
21570
+ case "-h":
21571
+ help = true;
21572
+ break;
21573
+ case "--host": {
21574
+ const value = argv[index + 1];
21575
+ if (value === void 0 || !isValidHost(value)) {
21576
+ return {
21577
+ kind: "error",
21578
+ message: "alias: --host requires a hostname or IP address without a protocol or path"
21579
+ };
21580
+ }
21581
+ host = value.trim();
21582
+ index++;
21583
+ break;
21663
21584
  }
21664
- return {
21665
- kind: "success",
21666
- value: {
21667
- packageName: "@tonyclaw/agent-inspector-backend-linux-x64-gnu",
21668
- target: "x86_64-unknown-linux-gnu",
21669
- binary: "bin/agent-inspector-backend",
21670
- os: "linux",
21671
- cpu: "x64",
21672
- libc: "glibc"
21585
+ case "--port":
21586
+ case "-p": {
21587
+ const value = parsePort3(argv[index + 1]);
21588
+ if (value === null) {
21589
+ return {
21590
+ kind: "error",
21591
+ message: "alias: --port must be an integer from 1 to 65535"
21592
+ };
21673
21593
  }
21674
- };
21675
- case "darwin":
21676
- return arch === "arm64" ? {
21677
- kind: "success",
21678
- value: {
21679
- packageName: "@tonyclaw/agent-inspector-backend-darwin-arm64",
21680
- target: "aarch64-apple-darwin",
21681
- binary: "bin/agent-inspector-backend",
21682
- os: "darwin",
21683
- cpu: "arm64",
21684
- libc: null
21594
+ port = value;
21595
+ index++;
21596
+ break;
21597
+ }
21598
+ case "--json":
21599
+ json2 = true;
21600
+ break;
21601
+ default:
21602
+ if (arg.startsWith("-")) {
21603
+ return { kind: "error", message: `alias: unknown option ${arg}` };
21685
21604
  }
21686
- } : targetFailure(`Rust backend packages do not support macOS ${arch}.`);
21687
- default:
21688
- return targetFailure(`Rust backend packages do not support ${platform} ${arch}.`);
21605
+ positional.push(arg);
21606
+ }
21689
21607
  }
21690
- }
21691
- function readJson(path2) {
21692
- try {
21693
- const value = JSON.parse(readFileSync(path2, "utf8"));
21694
- return { kind: "success", value };
21695
- } catch {
21696
- return { kind: "error" };
21608
+ if (help) return { kind: "help" };
21609
+ const command = positional[0];
21610
+ switch (command) {
21611
+ case "list":
21612
+ if (positional.length !== 1) {
21613
+ return { kind: "error", message: "alias: list does not accept a base path" };
21614
+ }
21615
+ return { kind: "ok", options: { action: { kind: "list" }, host, port, json: json2 } };
21616
+ case "add":
21617
+ case "remove": {
21618
+ const rawBasePath = positional[1];
21619
+ if (rawBasePath === void 0 || positional.length !== 2) {
21620
+ return { kind: "error", message: `alias: ${command} requires exactly one base path` };
21621
+ }
21622
+ const basePath = normalizeAliasPath(rawBasePath);
21623
+ if (basePath === null) {
21624
+ return {
21625
+ kind: "error",
21626
+ message: `alias: ${command} base path must be an absolute URL path without a query or hash`
21627
+ };
21628
+ }
21629
+ const action = { kind: command, basePath };
21630
+ return { kind: "ok", options: { action, host, port, json: json2 } };
21631
+ }
21632
+ case void 0:
21633
+ return { kind: "error", message: "alias: expected add <path>, list, or remove <path>" };
21634
+ default:
21635
+ return { kind: "error", message: `alias: unknown command ${command}` };
21697
21636
  }
21698
21637
  }
21699
- function isConfinedPath(root, candidate) {
21700
- const child = relative(root, candidate);
21701
- return child.length > 0 && !child.startsWith("..") && !isAbsolute(child);
21638
+ function aliasHelp() {
21639
+ return `agent-inspector alias - mount additional base paths on a running runtime
21640
+
21641
+ Usage:
21642
+ agent-inspector alias add <path> [--host <host>] [--port <port>] [--json]
21643
+ agent-inspector alias list [--host <host>] [--port <port>] [--json]
21644
+ agent-inspector alias remove <path> [--host <host>] [--port <port>] [--json]
21645
+
21646
+ Options:
21647
+ --host <host> Running instance host (default: localhost)
21648
+ --port <port> Running instance port (default: 9527)
21649
+ --json Emit machine-readable output
21650
+ --help, -h Show this help
21651
+
21652
+ Aliases are runtime-scoped. This command never starts another Agent Inspector runtime.
21653
+ `;
21702
21654
  }
21703
- function identitiesMatch(left, right) {
21704
- return left.schemaVersion === right.schemaVersion && left.name === right.name && left.packageVersion === right.packageVersion && left.target === right.target && left.binary === right.binary && left.cliProtocol === right.cliProtocol && left.productionReady === right.productionReady && left.capabilities.nodeProtectedIngress === right.capabilities.nodeProtectedIngress && left.capabilities.basePath === right.capabilities.basePath && left.capabilities.namedInstances === right.capabilities.namedInstances && left.capabilities.gracefulShutdown === right.capabilities.gracefulShutdown;
21655
+ function isObject4(value) {
21656
+ return typeof value === "object" && value !== null && !Array.isArray(value);
21705
21657
  }
21706
- function probeBinaryIdentity(binaryPath, identityFlag) {
21707
- const result = spawnSync(binaryPath, [identityFlag], {
21708
- encoding: "utf8",
21709
- windowsHide: true,
21710
- timeout: RUST_BACKEND_IDENTITY_TIMEOUT_MS,
21711
- maxBuffer: 64 * 1024
21712
- });
21713
- if (result.error !== void 0) {
21714
- return { kind: "error", message: result.error.message };
21715
- }
21716
- if (result.status !== 0) {
21717
- return {
21718
- kind: "error",
21719
- message: `identity command exited with status ${String(result.status)}`
21720
- };
21721
- }
21722
- let raw;
21723
- try {
21724
- raw = JSON.parse(result.stdout);
21725
- } catch {
21726
- return { kind: "error", message: "identity command did not return JSON" };
21658
+ function parseAliasSnapshot(value) {
21659
+ if (!isObject4(value)) return null;
21660
+ const canonicalBasePath = value["canonicalBasePath"];
21661
+ const aliases = value["aliases"];
21662
+ const runtimeScoped = value["runtimeScoped"];
21663
+ if (typeof canonicalBasePath !== "string" || !Array.isArray(aliases) || !aliases.every((alias) => typeof alias === "string") || runtimeScoped !== true) {
21664
+ return null;
21727
21665
  }
21728
- const parsed = RustBackendIdentitySchema.safeParse(raw);
21729
- return parsed.success ? { kind: "success", value: parsed.data } : { kind: "error", message: "identity command returned an unsupported contract" };
21666
+ return {
21667
+ canonicalBasePath: normalizeBasePath(canonicalBasePath),
21668
+ aliases: aliases.map((alias) => normalizeBasePath(alias)),
21669
+ runtimeScoped: true
21670
+ };
21730
21671
  }
21731
- var packageRequire = createRequire(import.meta.url);
21732
- function defaultResolvePackageJson(specifier) {
21672
+ function readErrorDetail(value) {
21673
+ if (!isObject4(value)) return null;
21674
+ const error51 = value["error"];
21675
+ if (typeof error51 === "string" && error51.trim() !== "") return error51;
21676
+ const message = value["message"];
21677
+ return typeof message === "string" && message.trim() !== "" ? message : null;
21678
+ }
21679
+ async function readJson2(response) {
21733
21680
  try {
21734
- return packageRequire.resolve(specifier);
21681
+ const value = await response.json();
21682
+ return value;
21735
21683
  } catch {
21736
- const suffix = "/package.json";
21737
- const packageName = specifier.endsWith(suffix) ? specifier.slice(0, -suffix.length) : "";
21738
- const directoryName = packageName.split("/").at(-1);
21739
- if (directoryName !== void 0 && directoryName.length > 0) {
21740
- const localPackageJson = resolve(
21741
- dirname(fileURLToPath(import.meta.url)),
21742
- "../packages",
21743
- directoryName,
21744
- "package.json"
21745
- );
21746
- if (existsSync(localPackageJson)) return localPackageJson;
21747
- }
21748
- return packageRequire.resolve(specifier);
21684
+ return null;
21749
21685
  }
21750
21686
  }
21751
- function resolveRustBackendPackage(options) {
21752
- const target = resolveRustBackendTarget(options.platform, options.arch, options.libc);
21753
- if (target.kind === "error") return { kind: "error", error: target.error };
21754
- const packageJsonSpecifier = `${target.value.packageName}/package.json`;
21755
- let packageJsonPath;
21687
+ async function requestAliases(endpoint2, action, fetchImpl) {
21688
+ const controller = new AbortController();
21689
+ const timeout = setTimeout(() => {
21690
+ controller.abort();
21691
+ }, REQUEST_TIMEOUT_MS);
21692
+ const method = action.kind === "list" ? "GET" : action.kind === "add" ? "POST" : "DELETE";
21693
+ const basePath = action.kind === "list" ? null : action.basePath;
21694
+ const init = {
21695
+ method,
21696
+ headers: action.kind === "list" ? { accept: "application/json" } : {
21697
+ accept: "application/json",
21698
+ "content-type": "application/json",
21699
+ "x-agent-inspector-csrf": "1"
21700
+ },
21701
+ signal: controller.signal
21702
+ };
21703
+ if (basePath !== null) init.body = JSON.stringify({ basePath });
21756
21704
  try {
21757
- packageJsonPath = (options.resolvePackageJson ?? defaultResolvePackageJson)(
21758
- packageJsonSpecifier
21759
- );
21760
- } catch {
21761
- return packageFailure(
21762
- "package_missing",
21763
- `Rust backend ${target.value.target} was requested, but optional package ${target.value.packageName}@${options.rootVersion} is not installed. Reinstall without omitting optional dependencies; no TypeScript fallback was used.`
21764
- );
21765
- }
21766
- const rawPackage = readJson(packageJsonPath);
21767
- if (rawPackage.kind === "error") {
21768
- return packageFailure("package_invalid", `Cannot read ${packageJsonSpecifier}.`);
21769
- }
21770
- const packageJson = RustBackendPackageJsonSchema.safeParse(rawPackage.value);
21771
- if (!packageJson.success) {
21772
- return packageFailure(
21773
- "package_invalid",
21774
- `Package ${target.value.packageName} does not contain the Rust backend package contract.`
21775
- );
21776
- }
21777
- if (packageJson.data.name !== target.value.packageName || packageJson.data.version !== options.rootVersion) {
21778
- return packageFailure(
21779
- "version_mismatch",
21780
- `Rust backend package must exactly match @tonyclaw/agent-inspector@${options.rootVersion}; found ${packageJson.data.name}@${packageJson.data.version}.`
21781
- );
21782
- }
21783
- const metadata = packageJson.data.agentInspectorRustBackend;
21784
- const expectedLibc = target.value.libc === null ? void 0 : [target.value.libc];
21785
- if (metadata.target !== target.value.target || metadata.binary !== target.value.binary || metadata.manifest !== "binary-manifest.json" || packageJson.data.os.length !== 1 || packageJson.data.os[0] !== target.value.os || packageJson.data.cpu.length !== 1 || packageJson.data.cpu[0] !== target.value.cpu || JSON.stringify(packageJson.data.libc) !== JSON.stringify(expectedLibc)) {
21786
- return packageFailure(
21787
- "target_mismatch",
21788
- `Package ${target.value.packageName} does not match target ${target.value.target}.`
21789
- );
21790
- }
21791
- const packageRoot = dirname(packageJsonPath);
21792
- const manifestPath = resolve(packageRoot, metadata.manifest);
21793
- const binaryPath = resolve(packageRoot, metadata.binary);
21794
- if (!isConfinedPath(packageRoot, manifestPath) || !isConfinedPath(packageRoot, binaryPath)) {
21795
- return packageFailure(
21796
- "package_invalid",
21797
- `Package ${target.value.packageName} contains an unsafe artifact path.`
21798
- );
21799
- }
21800
- const rawManifest = readJson(manifestPath);
21801
- if (rawManifest.kind === "error") {
21802
- return packageFailure(
21803
- "manifest_invalid",
21804
- `Rust backend package ${target.value.packageName} is incomplete: binary-manifest.json is missing or invalid.`
21805
- );
21806
- }
21807
- const manifest = RustBackendBinaryManifestSchema.safeParse(rawManifest.value);
21808
- if (!manifest.success) {
21809
- return packageFailure(
21810
- "manifest_invalid",
21811
- `Rust backend package ${target.value.packageName} has an invalid target manifest.`
21812
- );
21813
- }
21814
- if (manifest.data.packageName !== target.value.packageName || manifest.data.packageVersion !== options.rootVersion || manifest.data.identity.packageVersion !== options.rootVersion) {
21815
- return packageFailure(
21816
- "version_mismatch",
21817
- `Rust backend manifest must exactly match @tonyclaw/agent-inspector@${options.rootVersion}.`
21818
- );
21705
+ const response = await fetchImpl(endpoint2, init);
21706
+ const value = await readJson2(response);
21707
+ if (!response.ok) {
21708
+ return { kind: "http-error", status: response.status, detail: readErrorDetail(value) };
21709
+ }
21710
+ const snapshot = parseAliasSnapshot(value);
21711
+ return snapshot === null ? { kind: "invalid-response" } : { kind: "success", snapshot };
21712
+ } catch (error51) {
21713
+ const timedOut = controller.signal.aborted || error51 instanceof Error && error51.name === "AbortError";
21714
+ return { kind: "network-error", timedOut };
21715
+ } finally {
21716
+ clearTimeout(timeout);
21819
21717
  }
21820
- if (manifest.data.target !== target.value.target || manifest.data.binary !== target.value.binary || manifest.data.identity.target !== target.value.target || manifest.data.identity.binary !== target.value.binary) {
21821
- return packageFailure(
21822
- "target_mismatch",
21823
- `Rust backend manifest identity does not match target ${target.value.target}.`
21824
- );
21718
+ }
21719
+ function displayBasePath2(basePath) {
21720
+ const normalized = normalizeBasePath(basePath);
21721
+ return normalized === "" ? "/" : normalized;
21722
+ }
21723
+ function fullUrl(origin, basePath) {
21724
+ return `${origin}${normalizeBasePath(basePath)}`;
21725
+ }
21726
+ function buildOutput(origin, action, snapshot) {
21727
+ return {
21728
+ action: action.kind,
21729
+ canonicalBasePath: displayBasePath2(snapshot.canonicalBasePath),
21730
+ canonicalUrl: fullUrl(origin, snapshot.canonicalBasePath),
21731
+ aliases: snapshot.aliases.map(displayBasePath2),
21732
+ aliasUrls: snapshot.aliases.map((alias) => fullUrl(origin, alias)),
21733
+ runtimeScoped: true
21734
+ };
21735
+ }
21736
+ function formatHumanOutput(output) {
21737
+ const heading = output.action === "add" ? "Base-path alias added." : output.action === "remove" ? "Base-path alias removed." : "Base-path aliases for the running runtime:";
21738
+ const aliasPaths = output.aliases.length === 0 ? "(none)" : output.aliases.join(", ");
21739
+ const aliasUrls = output.aliasUrls.length === 0 ? " (none)" : output.aliasUrls.map((url2) => ` - ${url2}`).join("\n");
21740
+ return `${heading}
21741
+ Canonical base path: ${output.canonicalBasePath}
21742
+ Canonical URL: ${output.canonicalUrl}
21743
+ Alias base paths: ${aliasPaths}
21744
+ Alias URLs:
21745
+ ${aliasUrls}
21746
+ Scope: current runtime only; no second runtime was started and aliases are not persisted.
21747
+ `;
21748
+ }
21749
+ function writeError2(io, json2, code, message) {
21750
+ if (json2) {
21751
+ io.stderr(`${JSON.stringify({ error: code, message, runtimeStarted: false })}
21752
+ `);
21753
+ return;
21825
21754
  }
21826
- let binaryBytes;
21827
- try {
21828
- const stats = statSync(binaryPath);
21829
- if (!stats.isFile()) {
21830
- return packageFailure("binary_invalid", `Rust backend artifact is not a regular file.`);
21755
+ io.stderr(`agent-inspector alias: ${message}
21756
+ `);
21757
+ }
21758
+ function oldRuntimeMessage(origin) {
21759
+ return `the runtime at ${origin} does not support base-path aliases. Upgrade Agent Inspector and restart that instance, then retry; this command did not start a runtime.`;
21760
+ }
21761
+ async function handleHttpError(options, endpoint2, origin, result, fetchImpl, io) {
21762
+ if (result.status === 404 && options.action.kind === "remove") {
21763
+ const probe = await requestAliases(endpoint2, { kind: "list" }, fetchImpl);
21764
+ if (probe.kind === "success") {
21765
+ writeError2(
21766
+ io,
21767
+ options.json,
21768
+ "alias_not_found",
21769
+ `base path ${displayBasePath2(options.action.basePath)} is not mounted. Run "agent-inspector alias list --host ${options.host} --port ${options.port}" to inspect the current runtime.`
21770
+ );
21771
+ return 1;
21831
21772
  }
21832
- if (target.value.os !== "win32" && (stats.mode & 73) === 0) {
21833
- return packageFailure("binary_invalid", `Rust backend artifact is not executable.`);
21773
+ if (probe.kind === "network-error") {
21774
+ writeError2(
21775
+ io,
21776
+ options.json,
21777
+ probe.timedOut ? "request_timeout" : "runtime_unreachable",
21778
+ `${probe.timedOut ? "timed out contacting" : "cannot reach"} ${origin}. Confirm the existing runtime is running and the --host/--port values are correct; no runtime was started.`
21779
+ );
21780
+ return 1;
21781
+ }
21782
+ if (probe.kind === "http-error" && (probe.status === 404 || probe.status === 405)) {
21783
+ writeError2(io, options.json, "unsupported_runtime", oldRuntimeMessage(origin));
21784
+ return 1;
21834
21785
  }
21835
- binaryBytes = readFileSync(binaryPath);
21836
- } catch {
21837
- return packageFailure(
21838
- "binary_missing",
21839
- `Rust backend binary ${target.value.binary} is missing from ${target.value.packageName}.`
21840
- );
21841
21786
  }
21842
- const sha256 = createHash("sha256").update(binaryBytes).digest("hex");
21843
- if (sha256 !== manifest.data.sha256) {
21844
- return packageFailure(
21845
- "hash_mismatch",
21846
- `Rust backend binary integrity check failed for ${target.value.packageName}; no binary was started.`
21847
- );
21787
+ if (result.status === 404 || result.status === 405) {
21788
+ writeError2(io, options.json, "unsupported_runtime", oldRuntimeMessage(origin));
21789
+ return 1;
21848
21790
  }
21849
- const probed = (options.identityProbe ?? probeBinaryIdentity)(binaryPath, metadata.identityFlag);
21850
- if (probed.kind === "error") {
21851
- return packageFailure(
21852
- "identity_failed",
21853
- `Rust backend binary did not pass the production identity gate: ${probed.message}. No TypeScript fallback was used.`
21791
+ if (result.status === 409) {
21792
+ writeError2(
21793
+ io,
21794
+ options.json,
21795
+ "alias_conflict",
21796
+ `${result.detail ?? "the base path is already mounted or conflicts with another mount"}. Run "agent-inspector alias list --host ${options.host} --port ${options.port}" to inspect the current runtime.`
21854
21797
  );
21798
+ return 1;
21855
21799
  }
21856
- if (!identitiesMatch(probed.value, manifest.data.identity)) {
21857
- return packageFailure(
21858
- "identity_mismatch",
21859
- `Rust backend binary identity differs from its signed package manifest; no binary was started.`
21860
- );
21800
+ const detail = result.detail === null ? "the runtime rejected the request" : result.detail;
21801
+ writeError2(
21802
+ io,
21803
+ options.json,
21804
+ "runtime_rejected_request",
21805
+ `${detail} (HTTP ${result.status}). Check the running instance and retry; no runtime was started.`
21806
+ );
21807
+ return 1;
21808
+ }
21809
+ async function runAlias(argv, fetchImpl = fetch, io = processIo2) {
21810
+ const parsed = parseAliasArgs(argv);
21811
+ if (parsed.kind === "help") {
21812
+ io.stdout(aliasHelp());
21813
+ return 0;
21861
21814
  }
21862
- return {
21863
- kind: "success",
21864
- value: {
21865
- command: binaryPath,
21866
- args: [],
21867
- packageName: target.value.packageName,
21868
- packageVersion: options.rootVersion,
21869
- target: target.value.target,
21870
- sha256
21815
+ if (parsed.kind === "error") {
21816
+ io.stderr(`${parsed.message}
21817
+
21818
+ ${aliasHelp()}`);
21819
+ return 2;
21820
+ }
21821
+ const { options } = parsed;
21822
+ const origin = `http://${formatHostForUrl(options.host)}:${options.port}`;
21823
+ const endpoint2 = `${origin}${ALIAS_CONTROL_PATH}`;
21824
+ const result = await requestAliases(endpoint2, options.action, fetchImpl);
21825
+ switch (result.kind) {
21826
+ case "success": {
21827
+ const output = buildOutput(origin, options.action, result.snapshot);
21828
+ io.stdout(options.json ? `${JSON.stringify(output, null, 2)}
21829
+ ` : formatHumanOutput(output));
21830
+ return 0;
21871
21831
  }
21872
- };
21832
+ case "network-error":
21833
+ writeError2(
21834
+ io,
21835
+ options.json,
21836
+ result.timedOut ? "request_timeout" : "runtime_unreachable",
21837
+ `${result.timedOut ? "timed out contacting" : "cannot reach"} ${origin}. Confirm the existing runtime is running and the --host/--port values are correct; no runtime was started.`
21838
+ );
21839
+ return 1;
21840
+ case "invalid-response":
21841
+ writeError2(
21842
+ io,
21843
+ options.json,
21844
+ "invalid_runtime_response",
21845
+ `the runtime at ${origin} returned an invalid alias response. Upgrade and restart that instance, then retry; no runtime was started.`
21846
+ );
21847
+ return 1;
21848
+ case "http-error":
21849
+ return handleHttpError(options, endpoint2, origin, result, fetchImpl, io);
21850
+ }
21851
+ }
21852
+ var DEFAULT_ALIAS_HOST, DEFAULT_ALIAS_PORT, ALIAS_CONTROL_PATH, REQUEST_TIMEOUT_MS, processIo2;
21853
+ var init_alias = __esm({
21854
+ "src/cli/alias.ts"() {
21855
+ "use strict";
21856
+ init_basePath();
21857
+ DEFAULT_ALIAS_HOST = "localhost";
21858
+ DEFAULT_ALIAS_PORT = 9527;
21859
+ ALIAS_CONTROL_PATH = "/.agent-inspector/base-path-aliases";
21860
+ REQUEST_TIMEOUT_MS = 5e3;
21861
+ processIo2 = {
21862
+ stdout: (text) => {
21863
+ process.stdout.write(text);
21864
+ },
21865
+ stderr: (text) => {
21866
+ process.stderr.write(text);
21867
+ }
21868
+ };
21869
+ }
21870
+ });
21871
+
21872
+ // src/cli.ts
21873
+ init_package();
21874
+ init_backendImplementation();
21875
+ init_basePath();
21876
+ init_managedInstance();
21877
+ init_networkHints();
21878
+ import { Buffer as Buffer5 } from "node:buffer";
21879
+ import { spawn as spawn2 } from "node:child_process";
21880
+ import { createConnection as createConnection2 } from "node:net";
21881
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
21882
+ import { dirname as dirname3, join as join9, resolve as resolvePath2 } from "node:path";
21883
+ import { appendFileSync, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "node:fs";
21884
+
21885
+ // src/cli/startupOutput.ts
21886
+ function shouldSuppressServerOutputLine(line, context) {
21887
+ if (!context.enableIdentityProxy) return false;
21888
+ if (context.publicPort === context.upstreamPort) return false;
21889
+ const lowerLine = line.toLowerCase();
21890
+ return lowerLine.includes("listening on") && lowerLine.includes(`:${context.upstreamPort}`);
21873
21891
  }
21874
21892
 
21893
+ // src/cli.ts
21894
+ init_rustBackendPackage();
21895
+
21875
21896
  // src/cli/instanceApi.ts
21876
21897
  init_instance();
21877
21898
  init_instanceContract();
@@ -24753,6 +24774,7 @@ function toBuffer(chunk) {
24753
24774
 
24754
24775
  // src/cli.ts
24755
24776
  init_platformCommands();
24777
+ init_dataDir();
24756
24778
  var __filename2 = fileURLToPath3(import.meta.url);
24757
24779
  var __dirname2 = dirname3(__filename2);
24758
24780
  var DEFAULT_PORT4 = 9527;
@@ -24764,6 +24786,7 @@ var BRANDED_WINDOWS_RUNTIME_EXE = "agent-inspector.exe";
24764
24786
  var DEFAULT_CAPTURE_MODE2 = "simple";
24765
24787
  var DEFAULT_SHUTDOWN_DEADLINE_MS = 15e3;
24766
24788
  var SUPERVISOR_SHUTDOWN_GRACE_MS = 2e3;
24789
+ var AGENT_INSPECTOR_BACKGROUND_SUPERVISOR_ENV = "AGENT_INSPECTOR_BACKGROUND_SUPERVISOR";
24767
24790
  process.title = "Agent Inspector";
24768
24791
  var subcommand = process.argv[2];
24769
24792
  function printGlobalHelp() {
@@ -24781,7 +24804,7 @@ Global options:
24781
24804
  -v, --version Print the package version
24782
24805
 
24783
24806
  Start options:
24784
- --backend <typescript|rust> Select the backend implementation (default: typescript)
24807
+ --backend <typescript|rust> Select the backend implementation (default: rust when native package is available)
24785
24808
  --backend-only, --headless Start the API, proxy, and MCP runtime without serving the UI
24786
24809
  --with-ui Serve the independently built UI through the protected ingress
24787
24810
  --port, -p <port> Public ingress port (default: 9527)
@@ -24952,6 +24975,25 @@ function pipeServerOutput(child, context) {
24952
24975
  pipeServerOutputStream(child.stdout, process.stdout, context);
24953
24976
  pipeServerOutputStream(child.stderr, process.stderr, context);
24954
24977
  }
24978
+ function createBackgroundSupervisorLogs(env) {
24979
+ try {
24980
+ const logsDir = join9(resolveDataDir(existsSync8, env), "logs");
24981
+ mkdirSync4(logsDir, { recursive: true });
24982
+ const stdoutPath = join9(logsDir, "backend.stdout.log");
24983
+ const stderrPath = join9(logsDir, "backend.stderr.log");
24984
+ const header = `
24985
+ [${(/* @__PURE__ */ new Date()).toISOString()}] Agent Inspector backend process started
24986
+ `;
24987
+ appendFileSync(stdoutPath, header, { encoding: "utf8" });
24988
+ appendFileSync(stderrPath, header, { encoding: "utf8" });
24989
+ return { stdoutPath, stderrPath };
24990
+ } catch (error51) {
24991
+ const message = error51 instanceof Error ? error51.message : String(error51);
24992
+ process.stderr.write(`Failed to open Agent Inspector background logs: ${message}
24993
+ `);
24994
+ return null;
24995
+ }
24996
+ }
24955
24997
  function pipeServerOutputStream(stream, target, context) {
24956
24998
  if (stream === null) return;
24957
24999
  let buffered = "";
@@ -24974,6 +25016,34 @@ function pipeServerOutputStream(stream, target, context) {
24974
25016
  buffered = "";
24975
25017
  });
24976
25018
  }
25019
+ function appendBackgroundLog(path2, text) {
25020
+ try {
25021
+ appendFileSync(path2, text, { encoding: "utf8" });
25022
+ } catch {
25023
+ }
25024
+ }
25025
+ function pipeServerOutputStreamToFile(stream, targetPath, context) {
25026
+ if (stream === null) return;
25027
+ let buffered = "";
25028
+ stream.on("data", (chunk) => {
25029
+ const text = Buffer5.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
25030
+ const lines = `${buffered}${text}`.split(/\r?\n/);
25031
+ buffered = lines.pop() ?? "";
25032
+ for (const line of lines) {
25033
+ if (!shouldSuppressServerOutputLine(line, context)) {
25034
+ appendBackgroundLog(targetPath, `${line}
25035
+ `);
25036
+ }
25037
+ }
25038
+ });
25039
+ stream.on("end", () => {
25040
+ if (buffered.length === 0) return;
25041
+ if (!shouldSuppressServerOutputLine(buffered, context)) {
25042
+ appendBackgroundLog(targetPath, buffered);
25043
+ }
25044
+ buffered = "";
25045
+ });
25046
+ }
24977
25047
  function resolveServerCommand(outputDir, serverPath, backend) {
24978
25048
  if (backend === "rust") {
24979
25049
  const resolved = resolveRustBackendPackage({ rootVersion: package_default.version });
@@ -25144,7 +25214,8 @@ async function runStart(args) {
25144
25214
  const portDefault = envPort !== void 0 ? Number(envPort) : DEFAULT_PORT4;
25145
25215
  const envHost = process.env["NITRO_HOST"] ?? process.env["HOST"];
25146
25216
  const envMode = process.env["AGENT_INSPECTOR_CAPTURE_MODE"] ?? process.env["AGENT_INSPECTOR_MODE"];
25147
- const envBackend = parseBackendImplementation(process.env[AGENT_INSPECTOR_BACKEND_ENV]);
25217
+ const rawEnvBackend = process.env[AGENT_INSPECTOR_BACKEND_ENV];
25218
+ const envBackend = rawEnvBackend === void 0 ? void 0 : parseBackendImplementation(rawEnvBackend);
25148
25219
  if (envBackend === null) {
25149
25220
  console.error(
25150
25221
  `Invalid ${AGENT_INSPECTOR_BACKEND_ENV}. Use typescript or rust; no backend was started.`
@@ -25165,7 +25236,8 @@ async function runStart(args) {
25165
25236
  let enableIdentityProxy = true;
25166
25237
  let legacyAliasEnabled = false;
25167
25238
  let serveUi = process.env["AGENT_INSPECTOR_BACKEND_ONLY"] !== "1";
25168
- let backend = envBackend;
25239
+ let backend = envBackend ?? "typescript";
25240
+ let backendWasSpecified = envBackend !== void 0;
25169
25241
  if (envMode !== void 0 && envMode !== "") {
25170
25242
  const parsedMode = parseCaptureMode(envMode);
25171
25243
  if (parsedMode === null) {
@@ -25219,6 +25291,7 @@ async function runStart(args) {
25219
25291
  return;
25220
25292
  }
25221
25293
  backend = parsedBackend.data;
25294
+ backendWasSpecified = true;
25222
25295
  continue;
25223
25296
  }
25224
25297
  switch (arg) {
@@ -25249,6 +25322,7 @@ async function runStart(args) {
25249
25322
  return;
25250
25323
  }
25251
25324
  backend = parsedBackend.data;
25325
+ backendWasSpecified = true;
25252
25326
  i++;
25253
25327
  break;
25254
25328
  }
@@ -25338,6 +25412,9 @@ async function runStart(args) {
25338
25412
  process.exitCode = 1;
25339
25413
  return;
25340
25414
  }
25415
+ if (!backendWasSpecified) {
25416
+ backend = managedLaunch?.backend ?? resolvePreferredBackendImplementation({ rootVersion: package_default.version });
25417
+ }
25341
25418
  if (managedLaunch !== null && managedLaunch.backend !== backend) {
25342
25419
  console.error(
25343
25420
  `Managed instance backend mismatch: launch identity requires ${managedLaunch.backend}, but ${backend} was selected.`
@@ -25494,7 +25571,11 @@ async function runStart(args) {
25494
25571
  const supervisorProcess = spawn2(process.execPath, [cliEntry, ...supervisorArgs], {
25495
25572
  stdio: "ignore",
25496
25573
  detached: true,
25497
- env: { ...process.env, [AGENT_INSPECTOR_CLI_ENTRY_ENV]: cliEntry },
25574
+ env: {
25575
+ ...process.env,
25576
+ [AGENT_INSPECTOR_CLI_ENTRY_ENV]: cliEntry,
25577
+ [AGENT_INSPECTOR_BACKGROUND_SUPERVISOR_ENV]: "1"
25578
+ },
25498
25579
  windowsHide: true
25499
25580
  });
25500
25581
  supervisorProcess.once("error", (err) => {
@@ -25558,22 +25639,38 @@ async function runStart(args) {
25558
25639
  if (existsSync8(workerDir)) {
25559
25640
  serverEnv["AGENT_INSPECTOR_WORKER_DIR"] = workerDir;
25560
25641
  }
25642
+ const runningInBackgroundSupervisor = process.env[AGENT_INSPECTOR_BACKGROUND_SUPERVISOR_ENV] === "1";
25643
+ const backgroundSupervisorLogs = runningInBackgroundSupervisor ? createBackgroundSupervisorLogs(serverEnv) : null;
25561
25644
  const serverProcess = spawn2(serverCommand.command, serverCommand.args, {
25562
25645
  stdio: managedLaunch === null ? background ? ["ignore", "ignore", "ignore"] : ["ignore", "pipe", "pipe"] : backend === "typescript" ? background ? ["ignore", "ignore", "ignore", "ipc"] : ["ignore", "pipe", "pipe", "ipc"] : background ? ["ignore", "ignore", "ignore"] : ["ignore", "pipe", "pipe"],
25563
25646
  detached: background,
25564
25647
  env: serverEnv,
25565
- windowsHide: background
25648
+ windowsHide: background || runningInBackgroundSupervisor
25566
25649
  });
25567
25650
  serverProcess.once("error", (err) => {
25568
25651
  console.error(`Failed to start Agent Inspector runtime: ${err.message}`);
25569
25652
  });
25570
25653
  const serverExit = waitForProcessExit(serverProcess);
25571
25654
  if (!background) {
25572
- pipeServerOutput(serverProcess, {
25655
+ const outputContext = {
25573
25656
  enableIdentityProxy,
25574
25657
  publicPort: port,
25575
25658
  upstreamPort
25576
- });
25659
+ };
25660
+ if (backgroundSupervisorLogs === null) {
25661
+ pipeServerOutput(serverProcess, outputContext);
25662
+ } else {
25663
+ pipeServerOutputStreamToFile(
25664
+ serverProcess.stdout,
25665
+ backgroundSupervisorLogs.stdoutPath,
25666
+ outputContext
25667
+ );
25668
+ pipeServerOutputStreamToFile(
25669
+ serverProcess.stderr,
25670
+ backgroundSupervisorLogs.stderrPath,
25671
+ outputContext
25672
+ );
25673
+ }
25577
25674
  }
25578
25675
  const identityProxies = [];
25579
25676
  let supervisorShutdownPromise = null;