@tonyclaw/agent-inspector 3.0.29 → 3.0.30

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 (152) hide show
  1. package/.output/cli.js +791 -191
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/CompareDrawer-B6cXJohL.js +1 -0
  4. package/.output/public/assets/InspectorPet-mAzeGmEE.js +4108 -0
  5. package/.output/public/assets/ProxyViewerContainer-CkFWv_Nm.js +126 -0
  6. package/.output/public/assets/{ReplayDialog-CctnITbt.js → ReplayDialog-DodiTL6E.js} +1 -1
  7. package/.output/public/assets/{RequestAnatomy-BrN0RBbL.js → RequestAnatomy-B3IauToI.js} +1 -1
  8. package/.output/public/assets/ResponseView-Hn-5ordK.js +3 -0
  9. package/.output/public/assets/StreamingChunkSequence-vLDOVQM_.js +1 -0
  10. package/.output/public/assets/_sessionId-BFH4TOaI.js +1 -0
  11. package/.output/public/assets/{_sessionId-DKM91udC.js → _sessionId-Cj_HIFZx.js} +1 -1
  12. package/.output/public/assets/{index-Bq-nqolG.js → index-Bmj2lcWE.js} +1 -1
  13. package/.output/public/assets/index-CgJj-HBC.css +1 -0
  14. package/.output/public/assets/index-Ch7uqnCT.js +1 -0
  15. package/.output/public/assets/index-DkDdKvLl.js +76 -0
  16. package/.output/public/assets/{index-DPiESBo2.js → index-ZlhqCrSg.js} +6 -6
  17. package/.output/public/assets/{json-viewer-CfaDJwx8.js → json-viewer-BdrnVQu-.js} +1 -1
  18. package/.output/public/assets/jszip.min-CS_nt_o1.js +2 -0
  19. package/.output/public/assets/qwen-mMn3f5ul.webp +0 -0
  20. package/.output/server/_libs/jszip.mjs +35 -9
  21. package/.output/server/_libs/lucide-react.mjs +86 -86
  22. package/.output/server/_libs/radix-ui__react-dialog.mjs +3 -3
  23. package/.output/server/_libs/tanstack__react-router.mjs +1 -1
  24. package/.output/server/{_sessionId-CwGXPD4C.mjs → _sessionId-C5S3pGZd.mjs} +15 -15
  25. package/.output/server/_sessionId-g6D69lKq.mjs +81 -0
  26. package/.output/server/_ssr/{CompareDrawer-R_P1-VNr.mjs → CompareDrawer-CqUmGHal.mjs} +138 -103
  27. package/.output/server/_ssr/InspectorPet-DI0UJd01.mjs +925 -0
  28. package/.output/server/_ssr/{ProxyViewerContainer-DUgxRNbj.mjs → ProxyViewerContainer-CvsUmbJ3.mjs} +1157 -1613
  29. package/.output/server/_ssr/{ReplayDialog-De9wKKzS.mjs → ReplayDialog-DS48dpFG.mjs} +18 -18
  30. package/.output/server/_ssr/{RequestAnatomy-Db4MxUYo.mjs → RequestAnatomy-DxYVQU1C.mjs} +58 -58
  31. package/.output/server/_ssr/{ResponseView-8rDXOiNB.mjs → ResponseView-BLXW73eq.mjs} +60 -60
  32. package/.output/server/_ssr/{StreamingChunkSequence-N9teZHkA.mjs → StreamingChunkSequence-DD0iFuy0.mjs} +16 -16
  33. package/.output/server/_ssr/{index-D4DvByuW.mjs → index-B10LIaK0.mjs} +15 -15
  34. package/.output/server/_ssr/index-RHVJnU2p.mjs +81 -0
  35. package/.output/server/_ssr/index.mjs +2 -2
  36. package/.output/server/_ssr/{json-viewer-BjoHRbjS.mjs → json-viewer-LDu2XBaK.mjs} +58 -58
  37. package/.output/server/_ssr/{router-BYlePDj8.mjs → router-GKvrwifJ.mjs} +9005 -6488
  38. package/.output/server/_tanstack-start-manifest_v-Df2n4mbT.mjs +4 -0
  39. package/.output/server/index.mjs +103 -75
  40. package/.output/workers/logFinalizer.worker.js +16908 -0
  41. package/.output/workers/sessionWorkerEntry.js +16904 -0
  42. package/README.md +21 -5
  43. package/package.json +6 -2
  44. package/src/assets/logos/qwen.webp +0 -0
  45. package/src/cli.ts +85 -10
  46. package/src/components/OnboardingBanner.tsx +41 -21
  47. package/src/components/ProxyViewer.tsx +198 -269
  48. package/src/components/ProxyViewerContainer.tsx +128 -17
  49. package/src/components/ecosystem/AgentLabDialog.tsx +166 -19
  50. package/src/components/errors/SafeErrorBoundary.tsx +201 -0
  51. package/src/components/pi-agent/PiAgentPanel.tsx +4 -1
  52. package/src/components/providers/ImportWizardDialog.tsx +18 -42
  53. package/src/components/providers/ProviderLogo.tsx +1 -1
  54. package/src/components/providers/ProvidersPanel.tsx +8 -5
  55. package/src/components/providers/SettingsDialog.tsx +34 -11
  56. package/src/components/proxy-viewer/ApplicationBar.tsx +63 -0
  57. package/src/components/proxy-viewer/CompareDrawer.tsx +92 -49
  58. package/src/components/proxy-viewer/LiveConnectionStatus.tsx +186 -0
  59. package/src/components/proxy-viewer/LogEntry.tsx +283 -275
  60. package/src/components/proxy-viewer/RawExportWarning.tsx +77 -0
  61. package/src/components/proxy-viewer/SessionLoadProgressBar.tsx +61 -0
  62. package/src/components/proxy-viewer/ThreadConnector.tsx +16 -1
  63. package/src/components/proxy-viewer/TurnGroup.tsx +13 -2
  64. package/src/components/proxy-viewer/accessibility.ts +8 -0
  65. package/src/components/proxy-viewer/lazy.ts +4 -0
  66. package/src/components/proxy-viewer/liveConnectionState.ts +158 -0
  67. package/src/components/proxy-viewer/proxyViewerLogic.ts +10 -0
  68. package/src/components/proxy-viewer/viewerState.ts +2 -6
  69. package/src/components/ui/select.tsx +1 -1
  70. package/src/components/ui/tabs.tsx +3 -3
  71. package/src/components/ui/transient-toast.tsx +1 -1
  72. package/src/lib/apiClient.ts +17 -2
  73. package/src/lib/ecosystemContract.ts +34 -0
  74. package/src/lib/export-logs.ts +1 -1
  75. package/src/lib/providerContract.ts +70 -4
  76. package/src/lib/providerImportContract.ts +27 -0
  77. package/src/lib/providerModelMetadata.ts +16 -7
  78. package/src/lib/resourceLimits.ts +152 -0
  79. package/src/lib/safeDiagnostic.ts +38 -0
  80. package/src/lib/useProviders.ts +4 -4
  81. package/src/mcp/server.ts +39 -6
  82. package/src/mcp/toolHandlers.ts +131 -4
  83. package/src/proxy/chunkStorage.ts +20 -6
  84. package/src/proxy/config.ts +20 -6
  85. package/src/proxy/dataDir.ts +3 -0
  86. package/src/proxy/ecosystemExecutionConfirmation.ts +114 -0
  87. package/src/proxy/ecosystemExecutionRoute.ts +116 -0
  88. package/src/proxy/ecosystemTasks.ts +48 -0
  89. package/src/proxy/evidenceExporter.ts +23 -9
  90. package/src/proxy/groupEvidenceExporter.ts +26 -5
  91. package/src/proxy/groupStore.ts +13 -3
  92. package/src/proxy/handler.ts +103 -20
  93. package/src/proxy/identityProxy.ts +333 -14
  94. package/src/proxy/logFinalizer.ts +73 -6
  95. package/src/proxy/logImportUpload.ts +128 -0
  96. package/src/proxy/logImporter.ts +321 -70
  97. package/src/proxy/logIndex.ts +16 -6
  98. package/src/proxy/logger.ts +294 -41
  99. package/src/proxy/privateDataPath.ts +183 -0
  100. package/src/proxy/providerScanStore.ts +87 -0
  101. package/src/proxy/providerSecretStore.ts +58 -19
  102. package/src/proxy/providers.ts +175 -58
  103. package/src/proxy/rawStreamCapture.ts +66 -5
  104. package/src/proxy/runStore.ts +13 -3
  105. package/src/proxy/runtimeAdmission.ts +52 -0
  106. package/src/proxy/runtimeHealth.ts +206 -0
  107. package/src/proxy/runtimeShutdown.ts +75 -0
  108. package/src/proxy/sessionArchive.ts +15 -2
  109. package/src/proxy/sessionProcess.ts +19 -0
  110. package/src/proxy/sessionRuntime.ts +7 -0
  111. package/src/proxy/shutdownCoordinator.ts +90 -0
  112. package/src/proxy/sqliteLogIndex.ts +18 -2
  113. package/src/proxy/store.ts +18 -2
  114. package/src/routes/__root.tsx +18 -1
  115. package/src/routes/api/alerts.summary.ts +12 -8
  116. package/src/routes/api/alerts.ts +27 -9
  117. package/src/routes/api/ecosystem.packages.$packageId.help.ts +3 -8
  118. package/src/routes/api/ecosystem.packages.$packageId.install.ts +3 -8
  119. package/src/routes/api/ecosystem.packages.$packageId.runner-presets.ts +3 -11
  120. package/src/routes/api/ecosystem.packages.$packageId.upgrade.ts +3 -8
  121. package/src/routes/api/ecosystem.recipes.$recipeId.run.ts +3 -8
  122. package/src/routes/api/health.ts +9 -2
  123. package/src/routes/api/knowledge.project-context.ts +11 -0
  124. package/src/routes/api/knowledge.search.ts +33 -2
  125. package/src/routes/api/logs.$id.body.ts +16 -2
  126. package/src/routes/api/logs.import.ts +7 -18
  127. package/src/routes/api/logs.stream.ts +147 -69
  128. package/src/routes/api/logs.ts +55 -14
  129. package/src/routes/api/providers.$providerId.model-metadata.ts +22 -4
  130. package/src/routes/api/providers.$providerId.ts +37 -6
  131. package/src/routes/api/providers.export.ts +34 -16
  132. package/src/routes/api/providers.import.ts +42 -8
  133. package/src/routes/api/providers.scan.ts +13 -8
  134. package/src/routes/api/providers.ts +36 -5
  135. package/src/routes/api/runs.ts +12 -12
  136. package/src/routes/api/sessions.ts +15 -8
  137. package/src/routes/index.tsx +6 -0
  138. package/src/routes/livez.ts +13 -0
  139. package/src/routes/readyz.ts +18 -0
  140. package/src/routes/session/$sessionId.tsx +6 -0
  141. package/styles/globals.css +19 -3
  142. package/.output/public/assets/CompareDrawer-Cb5ccPeX.js +0 -1
  143. package/.output/public/assets/ProxyViewerContainer-COBe5GTT.js +0 -4234
  144. package/.output/public/assets/ResponseView-Cqq87lX_.js +0 -3
  145. package/.output/public/assets/StreamingChunkSequence-CnTa9mCr.js +0 -1
  146. package/.output/public/assets/index-DTLNVcgc.js +0 -76
  147. package/.output/public/assets/index-DliqmwUw.css +0 -1
  148. package/.output/public/assets/qwen-CONDcHqt.png +0 -0
  149. package/.output/server/_tanstack-start-manifest_v-DMCA8KK3.mjs +0 -4
  150. package/src/assets/logos/mcp.png +0 -0
  151. package/src/assets/logos/qwen.png +0 -0
  152. package/src/components/ui/mcp-logo.tsx +0 -20
package/.output/cli.js CHANGED
@@ -14,15 +14,156 @@ var __export = (target, all) => {
14
14
  __defProp(target, name, { get: all[name], enumerable: true });
15
15
  };
16
16
 
17
+ // src/proxy/privateDataPath.ts
18
+ import { spawnSync } from "node:child_process";
19
+ import { chmodSync, existsSync, lstatSync, mkdirSync, readdirSync, statSync } from "node:fs";
20
+ import { dirname, join } from "node:path";
21
+ function runWindowsCommand(executable, args) {
22
+ const result = spawnSync(executable, [...args], {
23
+ encoding: "utf8",
24
+ shell: false,
25
+ windowsHide: true
26
+ });
27
+ return { status: result.status, stdout: result.stdout };
28
+ }
29
+ function windowsUserSid(output) {
30
+ const match = /S-\d+(?:-\d+)+/.exec(output);
31
+ return match?.[0] ?? null;
32
+ }
33
+ function resolveWindowsUserSid(run) {
34
+ if (run === runWindowsCommand && cachedWindowsSid !== null) return cachedWindowsSid;
35
+ const identity = run("whoami.exe", ["/user", "/fo", "csv", "/nh"]);
36
+ if (identity.status !== 0) return null;
37
+ const sid = windowsUserSid(identity.stdout);
38
+ if (run === runWindowsCommand) cachedWindowsSid = sid;
39
+ return sid;
40
+ }
41
+ function windowsBroadGroupRemovalArgs() {
42
+ return ["/remove:g", "*S-1-1-0", "*S-1-5-11", "*S-1-5-32-545"];
43
+ }
44
+ function applyWindowsUserOnlyAcl(path2, kind, run = runWindowsCommand) {
45
+ const sid = resolveWindowsUserSid(run);
46
+ if (sid === null) return false;
47
+ const access = kind === "directory" ? `(OI)(CI)F` : "F";
48
+ const result = run("icacls.exe", [
49
+ path2,
50
+ "/inheritance:r",
51
+ "/grant:r",
52
+ `*${sid}:${access}`,
53
+ ...windowsBroadGroupRemovalArgs(),
54
+ "/q"
55
+ ]);
56
+ return result.status === 0;
57
+ }
58
+ function applyWindowsUserOnlyTreeAcl(root, run = runWindowsCommand) {
59
+ const sid = resolveWindowsUserSid(run);
60
+ if (sid === null) return false;
61
+ const result = run("icacls.exe", [
62
+ root,
63
+ "/inheritance:r",
64
+ "/grant:r",
65
+ `*${sid}:(OI)(CI)F`,
66
+ `*${sid}:F`,
67
+ ...windowsBroadGroupRemovalArgs(),
68
+ "/T",
69
+ "/C",
70
+ "/Q"
71
+ ]);
72
+ return result.status === 0;
73
+ }
74
+ function applyPrivatePermissionsSync(path2, kind) {
75
+ if (process.platform === "win32") {
76
+ return applyWindowsUserOnlyAcl(path2, kind);
77
+ }
78
+ try {
79
+ chmodSync(path2, kind === "directory" ? PRIVATE_DIRECTORY_MODE : PRIVATE_FILE_MODE);
80
+ return true;
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+ function secureOnce(path2, kind) {
86
+ const cacheKey = `${kind}:${path2}`;
87
+ if (securedPaths.has(cacheKey)) return true;
88
+ if (!applyPrivatePermissionsSync(path2, kind)) return false;
89
+ securedPaths.add(cacheKey);
90
+ return true;
91
+ }
92
+ function ensurePrivateDirectorySync(path2) {
93
+ try {
94
+ mkdirSync(path2, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });
95
+ } catch {
96
+ return false;
97
+ }
98
+ return secureOnce(path2, "directory");
99
+ }
100
+ function securePrivateFileSync(path2, forceAcl = false) {
101
+ let identity;
102
+ try {
103
+ const stats = statSync(path2);
104
+ identity = `${String(stats.dev)}:${String(stats.ino)}:${String(stats.birthtimeMs)}`;
105
+ } catch {
106
+ return false;
107
+ }
108
+ if (securedFileIdentities.get(path2) === identity) return true;
109
+ if (process.platform === "win32" && !forceAcl && securedPaths.has(`directory:${dirname(path2)}`)) {
110
+ securedFileIdentities.set(path2, identity);
111
+ return true;
112
+ }
113
+ if (!applyPrivatePermissionsSync(path2, "file")) return false;
114
+ securedFileIdentities.set(path2, identity);
115
+ return true;
116
+ }
117
+ function securePrivateTreeSync(root) {
118
+ if (!existsSync(root)) return false;
119
+ let stats;
120
+ try {
121
+ stats = lstatSync(root);
122
+ } catch {
123
+ return false;
124
+ }
125
+ if (stats.isSymbolicLink()) return true;
126
+ if (!stats.isDirectory()) return securePrivateFileSync(root, true);
127
+ if (process.platform === "win32") {
128
+ if (!applyWindowsUserOnlyTreeAcl(root)) return false;
129
+ securedPaths.add(`directory:${root}`);
130
+ return true;
131
+ }
132
+ if (!ensurePrivateDirectorySync(root)) return false;
133
+ let entries;
134
+ try {
135
+ entries = readdirSync(root);
136
+ } catch {
137
+ return false;
138
+ }
139
+ let secured = true;
140
+ for (const entry of entries) {
141
+ const childPath = join(root, entry);
142
+ if (!securePrivateTreeSync(childPath)) secured = false;
143
+ }
144
+ return secured;
145
+ }
146
+ var PRIVATE_DIRECTORY_MODE, PRIVATE_FILE_MODE, securedPaths, securedFileIdentities, cachedWindowsSid;
147
+ var init_privateDataPath = __esm({
148
+ "src/proxy/privateDataPath.ts"() {
149
+ "use strict";
150
+ PRIVATE_DIRECTORY_MODE = 448;
151
+ PRIVATE_FILE_MODE = 384;
152
+ securedPaths = /* @__PURE__ */ new Set();
153
+ securedFileIdentities = /* @__PURE__ */ new Map();
154
+ cachedWindowsSid = null;
155
+ }
156
+ });
157
+
17
158
  // src/proxy/dataDir.ts
18
- import { cpSync, existsSync, rmSync } from "node:fs";
19
- import { isAbsolute, join } from "node:path";
159
+ import { cpSync, existsSync as existsSync2, rmSync } from "node:fs";
160
+ import { isAbsolute, join as join2 } from "node:path";
20
161
  function getDataDir() {
21
162
  migrateLegacyDataDirs();
22
- return resolveDataDir(existsSync);
163
+ return resolveDataDir(existsSync2);
23
164
  }
24
165
  function hasDataDirState(pathExists, dir) {
25
- return pathExists(join(dir, "providers.json")) || pathExists(join(dir, "config.json")) || pathExists(join(dir, "logs")) || pathExists(join(dir, "chunks"));
166
+ return pathExists(join2(dir, "providers.json")) || pathExists(join2(dir, "config.json")) || pathExists(join2(dir, "logs")) || pathExists(join2(dir, "chunks"));
26
167
  }
27
168
  function defaultBaseDir(env = process.env, platform = process.platform) {
28
169
  return platform === "win32" ? env["USERPROFILE"] ?? env["APPDATA"] ?? "C:\\" : env["HOME"] ?? "/tmp";
@@ -31,13 +172,13 @@ function resolveDataDir(_pathExists, env = process.env, platform = process.platf
31
172
  const base = defaultBaseDir(env, platform);
32
173
  const dirEnv = env["AGENT_INSPECTOR_DATA_DIR"];
33
174
  if (dirEnv !== void 0 && dirEnv !== "") {
34
- return isAbsolute(dirEnv) ? dirEnv : join(base, dirEnv);
175
+ return isAbsolute(dirEnv) ? dirEnv : join2(base, dirEnv);
35
176
  }
36
177
  const legacyDirEnv = env["AGENT_INSPECTOR_CONFIG_DIR"];
37
178
  if (legacyDirEnv !== void 0 && legacyDirEnv !== "") {
38
- return isAbsolute(legacyDirEnv) ? legacyDirEnv : join(base, legacyDirEnv);
179
+ return isAbsolute(legacyDirEnv) ? legacyDirEnv : join2(base, legacyDirEnv);
39
180
  }
40
- return join(base, CURRENT_DATA_DIR_NAME);
181
+ return join2(base, CURRENT_DATA_DIR_NAME);
41
182
  }
42
183
  function hasExplicitDataDir() {
43
184
  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"] !== "";
@@ -45,13 +186,15 @@ function hasExplicitDataDir() {
45
186
  function migrateLegacyDataDirs() {
46
187
  if (hasExplicitDataDir()) return;
47
188
  const base = defaultBaseDir();
48
- const currentDir = join(base, CURRENT_DATA_DIR_NAME);
49
- if (hasDataDirState(existsSync, currentDir)) return;
189
+ const currentDir = join2(base, CURRENT_DATA_DIR_NAME);
190
+ if (hasDataDirState(existsSync2, currentDir)) return;
50
191
  for (const legacyName of LEGACY_DATA_DIR_NAMES) {
51
- const legacyDir = join(base, legacyName);
52
- if (!existsSync(legacyDir)) continue;
192
+ const legacyDir = join2(base, legacyName);
193
+ if (!existsSync2(legacyDir)) continue;
53
194
  try {
195
+ if (!ensurePrivateDirectorySync(currentDir)) return;
54
196
  cpSync(legacyDir, currentDir, { recursive: true, force: true });
197
+ if (!securePrivateTreeSync(currentDir)) return;
55
198
  rmSync(legacyDir, { recursive: true, force: true });
56
199
  } catch {
57
200
  }
@@ -61,15 +204,16 @@ var CURRENT_DATA_DIR_NAME, LEGACY_DATA_DIR_NAMES;
61
204
  var init_dataDir = __esm({
62
205
  "src/proxy/dataDir.ts"() {
63
206
  "use strict";
207
+ init_privateDataPath();
64
208
  CURRENT_DATA_DIR_NAME = ".agent-inspector";
65
209
  LEGACY_DATA_DIR_NAMES = [".llm-inspector", ".llm-agent"];
66
210
  }
67
211
  });
68
212
 
69
213
  // src/proxy/platformCommands.ts
70
- import { statSync } from "node:fs";
214
+ import { statSync as statSync2 } from "node:fs";
71
215
  import { execFileSync } from "node:child_process";
72
- import { delimiter, isAbsolute as isAbsolute2, join as join2 } from "node:path";
216
+ import { delimiter, isAbsolute as isAbsolute2, join as join3 } from "node:path";
73
217
  function pathEntries(envPath) {
74
218
  if (envPath === void 0 || envPath.length === 0) return [];
75
219
  return envPath.split(delimiter).filter((entry) => entry.length > 0);
@@ -82,7 +226,7 @@ function windowsExecutableExtensions(envPathExt) {
82
226
  }
83
227
  function isExecutableCandidate(path2, platform) {
84
228
  try {
85
- const stat3 = statSync(path2);
229
+ const stat3 = statSync2(path2);
86
230
  if (!stat3.isFile()) return false;
87
231
  if (platform === "win32" || process.platform === "win32") return true;
88
232
  return (stat3.mode & 73) !== 0;
@@ -93,14 +237,14 @@ function isExecutableCandidate(path2, platform) {
93
237
  function executableCandidates(bin, platform, env) {
94
238
  if (isAbsolute2(bin)) return [bin];
95
239
  const entries = pathEntries(env["PATH"] ?? env["Path"] ?? env["path"]);
96
- if (platform !== "win32") return entries.map((entry) => join2(entry, bin));
240
+ if (platform !== "win32") return entries.map((entry) => join3(entry, bin));
97
241
  const hasExtension = /\.[^\\/]+$/.test(bin);
98
242
  const extensions = hasExtension ? [""] : windowsExecutableExtensions(env["PATHEXT"]);
99
243
  const candidates = [];
100
244
  for (const entry of entries) {
101
245
  for (const extension of extensions) {
102
- candidates.push(join2(entry, `${bin}${extension.toLowerCase()}`));
103
- candidates.push(join2(entry, `${bin}${extension.toUpperCase()}`));
246
+ candidates.push(join3(entry, `${bin}${extension.toLowerCase()}`));
247
+ candidates.push(join3(entry, `${bin}${extension.toUpperCase()}`));
104
248
  }
105
249
  }
106
250
  return candidates;
@@ -220,41 +364,41 @@ var init_platformCommands = __esm({
220
364
  });
221
365
 
222
366
  // src/cli/detect-tools.ts
223
- import { existsSync as existsSync2 } from "node:fs";
224
- import { join as join3 } from "node:path";
367
+ import { existsSync as existsSync3 } from "node:fs";
368
+ import { join as join4 } from "node:path";
225
369
  import { homedir } from "node:os";
226
370
  function which(bin) {
227
371
  return resolveExecutableOnPath(bin);
228
372
  }
229
373
  function tryDir(path2) {
230
- return existsSync2(path2) ? path2 : null;
374
+ return existsSync3(path2) ? path2 : null;
231
375
  }
232
376
  function detectClaudeCode() {
233
- const configDir = tryDir(join3(homedir(), ".claude"));
377
+ const configDir = tryDir(join4(homedir(), ".claude"));
234
378
  const bin = which("claude");
235
379
  if (configDir === null && bin === null) return { found: false };
236
380
  return { found: true, path: configDir ?? bin ?? "" };
237
381
  }
238
382
  function detectOpenCode() {
239
- const configDir = tryDir(join3(homedir(), ".config", "opencode"));
383
+ const configDir = tryDir(join4(homedir(), ".config", "opencode"));
240
384
  const bin = which("opencode");
241
385
  if (configDir === null && bin === null) return { found: false };
242
386
  return { found: true, path: configDir ?? bin ?? "" };
243
387
  }
244
388
  function detectMiMo() {
245
- const configDir = tryDir(join3(homedir(), ".config", "mimocode")) ?? tryDir(join3(homedir(), ".mimocode"));
389
+ const configDir = tryDir(join4(homedir(), ".config", "mimocode")) ?? tryDir(join4(homedir(), ".mimocode"));
246
390
  const bin = which("mimo");
247
391
  if (configDir === null && bin === null) return { found: false };
248
392
  return { found: true, path: configDir ?? bin ?? "" };
249
393
  }
250
394
  function detectCursor() {
251
- const configDir = tryDir(join3(homedir(), ".cursor"));
395
+ const configDir = tryDir(join4(homedir(), ".cursor"));
252
396
  const bin = which("cursor");
253
397
  if (configDir === null && bin === null) return { found: false };
254
398
  return { found: true, path: configDir ?? bin ?? "" };
255
399
  }
256
400
  function detectCody() {
257
- const configDir = tryDir(join3(homedir(), ".config", "cody"));
401
+ const configDir = tryDir(join4(homedir(), ".config", "cody"));
258
402
  const bin = which("cody");
259
403
  if (configDir === null && bin === null) return { found: false };
260
404
  return { found: true, path: configDir ?? bin ?? "" };
@@ -1086,15 +1230,15 @@ __export(onboard_exports, {
1086
1230
  });
1087
1231
  import {
1088
1232
  mkdirSync as mkdirSync2,
1089
- writeFileSync as writeFileSync2,
1090
- existsSync as existsSync3,
1233
+ writeFileSync,
1234
+ existsSync as existsSync4,
1091
1235
  readFileSync,
1092
1236
  unlinkSync,
1093
- readdirSync,
1237
+ readdirSync as readdirSync2,
1094
1238
  rmdirSync
1095
1239
  } from "node:fs";
1096
1240
  import { homedir as homedir2 } from "node:os";
1097
- import { dirname, join as join4 } from "node:path";
1241
+ import { dirname as dirname2, join as join5 } from "node:path";
1098
1242
  import { fileURLToPath } from "node:url";
1099
1243
  function actionForStatus(label, state) {
1100
1244
  switch (state) {
@@ -1350,15 +1494,15 @@ Exit codes:
1350
1494
  `);
1351
1495
  }
1352
1496
  function resolveTargets(flags) {
1353
- const claudeRoot = flags.skillDir ?? join4(homedir2(), ".claude");
1354
- const claudeSkillDir = join4(claudeRoot, "skills", SKILL_DIR_NAME);
1355
- const claudeCommandsDir = join4(claudeRoot, "commands");
1356
- const codexRoot = process.env["CODEX_HOME"] ?? join4(homedir2(), ".codex");
1357
- const codexSkillsDir = flags.codexSkillDir ?? join4(codexRoot, "skills");
1497
+ const claudeRoot = flags.skillDir ?? join5(homedir2(), ".claude");
1498
+ const claudeSkillDir = join5(claudeRoot, "skills", SKILL_DIR_NAME);
1499
+ const claudeCommandsDir = join5(claudeRoot, "commands");
1500
+ const codexRoot = process.env["CODEX_HOME"] ?? join5(homedir2(), ".codex");
1501
+ const codexSkillsDir = flags.codexSkillDir ?? join5(codexRoot, "skills");
1358
1502
  return {
1359
- claudeSkillFile: join4(claudeSkillDir, SKILL_FILE_NAME),
1360
- claudeCommandFile: join4(claudeCommandsDir, COMMAND_FILE_NAME),
1361
- codexSkillFile: join4(codexSkillsDir, SKILL_DIR_NAME, SKILL_FILE_NAME)
1503
+ claudeSkillFile: join5(claudeSkillDir, SKILL_FILE_NAME),
1504
+ claudeCommandFile: join5(claudeCommandsDir, COMMAND_FILE_NAME),
1505
+ codexSkillFile: join5(codexSkillsDir, SKILL_DIR_NAME, SKILL_FILE_NAME)
1362
1506
  };
1363
1507
  }
1364
1508
  function isObject2(value) {
@@ -1377,33 +1521,33 @@ function resolveOpenCodeConfigPath(flags) {
1377
1521
  if (flags.opencodeConfig !== null) {
1378
1522
  return flags.opencodeConfig;
1379
1523
  }
1380
- const configDir = join4(homedir2(), ".config", "opencode");
1381
- const jsoncPath = join4(configDir, "opencode.jsonc");
1382
- if (existsSync3(jsoncPath)) {
1524
+ const configDir = join5(homedir2(), ".config", "opencode");
1525
+ const jsoncPath = join5(configDir, "opencode.jsonc");
1526
+ if (existsSync4(jsoncPath)) {
1383
1527
  return jsoncPath;
1384
1528
  }
1385
- return join4(configDir, "opencode.json");
1529
+ return join5(configDir, "opencode.json");
1386
1530
  }
1387
1531
  function resolveMiMoConfigPath(flags) {
1388
1532
  if (flags.mimoConfig !== null) {
1389
1533
  return flags.mimoConfig;
1390
1534
  }
1391
- const configDir = join4(homedir2(), ".config", "mimocode");
1392
- const jsoncPath = join4(configDir, "mimocode.jsonc");
1393
- if (existsSync3(jsoncPath)) {
1535
+ const configDir = join5(homedir2(), ".config", "mimocode");
1536
+ const jsoncPath = join5(configDir, "mimocode.jsonc");
1537
+ if (existsSync4(jsoncPath)) {
1394
1538
  return jsoncPath;
1395
1539
  }
1396
- const jsonPath = join4(configDir, "mimocode.json");
1397
- if (existsSync3(jsonPath)) {
1540
+ const jsonPath = join5(configDir, "mimocode.json");
1541
+ if (existsSync4(jsonPath)) {
1398
1542
  return jsonPath;
1399
1543
  }
1400
- const legacyDir = join4(homedir2(), ".mimocode");
1401
- const legacyJsoncPath = join4(legacyDir, "config.jsonc");
1402
- if (existsSync3(legacyJsoncPath)) {
1544
+ const legacyDir = join5(homedir2(), ".mimocode");
1545
+ const legacyJsoncPath = join5(legacyDir, "config.jsonc");
1546
+ if (existsSync4(legacyJsoncPath)) {
1403
1547
  return legacyJsoncPath;
1404
1548
  }
1405
- const legacyJsonPath = join4(legacyDir, "config.json");
1406
- if (existsSync3(legacyJsonPath)) {
1549
+ const legacyJsonPath = join5(legacyDir, "config.json");
1550
+ if (existsSync4(legacyJsonPath)) {
1407
1551
  return legacyJsonPath;
1408
1552
  }
1409
1553
  return jsoncPath;
@@ -1487,7 +1631,7 @@ function stripJsoncComments(raw) {
1487
1631
  return result;
1488
1632
  }
1489
1633
  function readAgentConfig(path2, target) {
1490
- if (!existsSync3(path2)) {
1634
+ if (!existsSync4(path2)) {
1491
1635
  return { ok: true, exists: false, config: {} };
1492
1636
  }
1493
1637
  try {
@@ -1781,12 +1925,12 @@ function buildDetectedSummary() {
1781
1925
  }
1782
1926
  function readPackageVersion() {
1783
1927
  const packageJsonPaths = [
1784
- join4(__dirname, "..", "package.json"),
1785
- join4(__dirname, "..", "..", "package.json")
1928
+ join5(__dirname, "..", "package.json"),
1929
+ join5(__dirname, "..", "..", "package.json")
1786
1930
  ];
1787
1931
  try {
1788
1932
  for (const packageJsonPath of packageJsonPaths) {
1789
- if (existsSync3(packageJsonPath)) {
1933
+ if (existsSync4(packageJsonPath)) {
1790
1934
  const raw = JSON.parse(readFileSync(packageJsonPath, "utf8"));
1791
1935
  if (isObject2(raw) && typeof raw["version"] === "string") {
1792
1936
  return raw["version"];
@@ -1881,7 +2025,7 @@ function isMatchingGeneratedFile(path2, currentVersion) {
1881
2025
  }
1882
2026
  }
1883
2027
  function readGeneratedFileStatus(file2, currentVersion) {
1884
- if (!existsSync3(file2.path)) {
2028
+ if (!existsSync4(file2.path)) {
1885
2029
  return {
1886
2030
  label: file2.label,
1887
2031
  path: file2.path,
@@ -1986,7 +2130,7 @@ function shouldWrite(file2, force, currentVersion) {
1986
2130
  if (!file2.enabled) {
1987
2131
  return false;
1988
2132
  }
1989
- if (force || !existsSync3(file2.path)) {
2133
+ if (force || !existsSync4(file2.path)) {
1990
2134
  return true;
1991
2135
  }
1992
2136
  const existingVersion = readGeneratedVersion(file2.path);
@@ -1997,11 +2141,11 @@ function shouldWrite(file2, force, currentVersion) {
1997
2141
  return comparison !== null && comparison < 0;
1998
2142
  }
1999
2143
  function shouldUninstall(file2, currentVersion) {
2000
- return file2.enabled && existsSync3(file2.path) && isMatchingGeneratedFile(file2.path, currentVersion);
2144
+ return file2.enabled && existsSync4(file2.path) && isMatchingGeneratedFile(file2.path, currentVersion);
2001
2145
  }
2002
2146
  function removeEmptyDirectory(path2) {
2003
2147
  try {
2004
- if (readdirSync(path2).length === 0) {
2148
+ if (readdirSync2(path2).length === 0) {
2005
2149
  rmdirSync(path2);
2006
2150
  }
2007
2151
  } catch {
@@ -2049,14 +2193,14 @@ No files were removed.
2049
2193
  try {
2050
2194
  for (const file2 of filesToRemove) {
2051
2195
  unlinkSync(file2.path);
2052
- removeEmptyDirectory(dirname(file2.path));
2196
+ removeEmptyDirectory(dirname2(file2.path));
2053
2197
  process.stdout.write(`Removed ${file2.label}: ${file2.path}
2054
2198
  `);
2055
2199
  }
2056
2200
  for (const plan of agentPlans) {
2057
2201
  if (plan.shouldRemove && plan.body !== null) {
2058
- mkdirSync2(dirname(plan.status.path), { recursive: true });
2059
- writeFileSync2(plan.status.path, plan.body, "utf8");
2202
+ mkdirSync2(dirname2(plan.status.path), { recursive: true });
2203
+ writeFileSync(plan.status.path, plan.body, "utf8");
2060
2204
  process.stdout.write(`Removed ${plan.status.label} entry from: ${plan.status.path}
2061
2205
  `);
2062
2206
  }
@@ -2198,7 +2342,7 @@ function runOnboardSync(argv) {
2198
2342
 
2199
2343
  `);
2200
2344
  for (const file2 of enabledFiles) {
2201
- const status = existsSync3(file2.path) ? "exists" : "missing";
2345
+ const status = existsSync4(file2.path) ? "exists" : "missing";
2202
2346
  process.stdout.write(`${file2.label}: ${file2.path} (${status})
2203
2347
  `);
2204
2348
  }
@@ -2275,13 +2419,13 @@ No files were written.
2275
2419
  }
2276
2420
  try {
2277
2421
  for (const file2 of filesToWrite) {
2278
- mkdirSync2(dirname(file2.path), { recursive: true });
2279
- writeFileSync2(file2.path, file2.body, "utf8");
2422
+ mkdirSync2(dirname2(file2.path), { recursive: true });
2423
+ writeFileSync(file2.path, file2.body, "utf8");
2280
2424
  }
2281
2425
  for (const plan of agentPlansToWrite) {
2282
2426
  if (plan.body !== null) {
2283
- mkdirSync2(dirname(plan.status.path), { recursive: true });
2284
- writeFileSync2(plan.status.path, plan.body, "utf8");
2427
+ mkdirSync2(dirname2(plan.status.path), { recursive: true });
2428
+ writeFileSync(plan.status.path, plan.body, "utf8");
2285
2429
  }
2286
2430
  }
2287
2431
  } catch (err) {
@@ -2338,7 +2482,7 @@ var init_onboard = __esm({
2338
2482
  init_codex_skill_onboard();
2339
2483
  init_skill_onboard();
2340
2484
  __filename = fileURLToPath(import.meta.url);
2341
- __dirname = dirname(__filename);
2485
+ __dirname = dirname2(__filename);
2342
2486
  DEFAULT_PORT = 9527;
2343
2487
  DEFAULT_MCP_URL = `http://localhost:${DEFAULT_PORT}/api/mcp`;
2344
2488
  LEGACY_MCP_URL = "http://localhost:9527/api/mcp";
@@ -2362,8 +2506,8 @@ __export(doctor_exports, {
2362
2506
  parseDoctorArgs: () => parseDoctorArgs,
2363
2507
  runDoctor: () => runDoctor
2364
2508
  });
2365
- import { existsSync as existsSync4, readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
2366
- import { isAbsolute as isAbsolute3, join as join5, resolve } from "node:path";
2509
+ import { existsSync as existsSync5, readFileSync as readFileSync2, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
2510
+ import { isAbsolute as isAbsolute3, join as join6, resolve } from "node:path";
2367
2511
  import { createConnection } from "node:net";
2368
2512
  function check2(name, severity, message, hint = "") {
2369
2513
  return {
@@ -2478,10 +2622,10 @@ function providerConfigCandidates(options, deps) {
2478
2622
  }
2479
2623
  if (options.configDir !== null) {
2480
2624
  const dir = resolveMaybeRelative(deps.cwd, options.configDir);
2481
- return [join5(dir, "providers.json"), join5(dir, "config.json")];
2625
+ return [join6(dir, "providers.json"), join6(dir, "config.json")];
2482
2626
  }
2483
2627
  const dataDir = resolveDefaultDataDir(deps.env, deps.platform, deps.exists);
2484
- return [join5(dataDir, "providers.json"), join5(dataDir, "config.json")];
2628
+ return [join6(dataDir, "providers.json"), join6(dataDir, "config.json")];
2485
2629
  }
2486
2630
  function checkProviderConfig(options, deps) {
2487
2631
  const providersJson = options.providersJson ?? deps.env["AGENT_INSPECTOR_PROVIDERS_JSON"] ?? null;
@@ -2567,7 +2711,7 @@ async function checkProxyHealth(port, deps) {
2567
2711
  function findProjectRoot(startDir, deps) {
2568
2712
  let current = resolve(startDir);
2569
2713
  for (let i = 0; i < 6; i++) {
2570
- if (deps.exists(join5(current, "package.json"))) return current;
2714
+ if (deps.exists(join6(current, "package.json"))) return current;
2571
2715
  const next = resolve(current, "..");
2572
2716
  if (next === current) return startDir;
2573
2717
  current = next;
@@ -2575,7 +2719,7 @@ function findProjectRoot(startDir, deps) {
2575
2719
  return startDir;
2576
2720
  }
2577
2721
  function checkPackage(rootDir, deps) {
2578
- const packagePath = join5(rootDir, "package.json");
2722
+ const packagePath = join6(rootDir, "package.json");
2579
2723
  const text = deps.readText(packagePath);
2580
2724
  if (text === null) {
2581
2725
  return check2("Package metadata", "warn", "package.json was not found near the CLI bundle.");
@@ -2600,7 +2744,7 @@ function countFilesInDir(dir, deps) {
2600
2744
  if (files === null) return null;
2601
2745
  let count = 0;
2602
2746
  for (const file2 of files) {
2603
- const fileStat = deps.stat(join5(dir, file2));
2747
+ const fileStat = deps.stat(join6(dir, file2));
2604
2748
  if (fileStat !== null && !fileStat.isDirectory) count += 1;
2605
2749
  }
2606
2750
  return count;
@@ -2620,24 +2764,24 @@ function dirSize(dir, deps, depth = 0) {
2620
2764
  if (files === null) return null;
2621
2765
  let total = 0;
2622
2766
  for (const file2 of files) {
2623
- const childSize = dirSize(join5(dir, file2), deps, depth + 1);
2767
+ const childSize = dirSize(join6(dir, file2), deps, depth + 1);
2624
2768
  if (childSize !== null) total += childSize;
2625
2769
  }
2626
2770
  return total;
2627
2771
  }
2628
2772
  function verboseChecks(options, deps) {
2629
2773
  const dataDir = resolveDefaultDataDir(deps.env, deps.platform, deps.exists);
2630
- const logsDir = join5(dataDir, "logs");
2631
- const chunksDir = join5(dataDir, "chunks");
2774
+ const logsDir = join6(dataDir, "logs");
2775
+ const chunksDir = join6(dataDir, "chunks");
2632
2776
  const providerCandidates = providerConfigCandidates(options, deps);
2633
2777
  const captureMode = deps.env["AGENT_INSPECTOR_MODE"] ?? deps.env["AGENT_INSPECTOR_CAPTURE_MODE"] ?? "simple";
2634
2778
  const logCount = countFilesInDir(logsDir, deps);
2635
2779
  const logSize = dirSize(logsDir, deps);
2636
2780
  const chunkSize = dirSize(chunksDir, deps);
2637
2781
  const homeDir = deps.platform === "win32" ? deps.env["USERPROFILE"] ?? deps.env["APPDATA"] ?? null : deps.env["HOME"] ?? null;
2638
- const claudeSkill = homeDir === null ? null : join5(homeDir, ".claude", "skills", "agent-inspector-onboard", "SKILL.md");
2639
- const claudeCommand = homeDir === null ? null : join5(homeDir, ".claude", "commands", "agent-inspector-onboard.md");
2640
- const codexSkill = homeDir === null ? null : join5(homeDir, ".codex", "skills", "agent-inspector-onboard", "SKILL.md");
2782
+ const claudeSkill = homeDir === null ? null : join6(homeDir, ".claude", "skills", "agent-inspector-onboard", "SKILL.md");
2783
+ const claudeCommand = homeDir === null ? null : join6(homeDir, ".claude", "commands", "agent-inspector-onboard.md");
2784
+ const codexSkill = homeDir === null ? null : join6(homeDir, ".codex", "skills", "agent-inspector-onboard", "SKILL.md");
2641
2785
  const hasClaudeOnboarding = claudeSkill !== null && claudeCommand !== null && deps.exists(claudeSkill) && deps.exists(claudeCommand);
2642
2786
  const hasCodexOnboarding = codexSkill !== null && deps.exists(codexSkill);
2643
2787
  return [
@@ -2669,8 +2813,8 @@ function verboseChecks(options, deps) {
2669
2813
  ];
2670
2814
  }
2671
2815
  function checkExtensionSource(rootDir, deps) {
2672
- const extensionDir = join5(rootDir, "extensions", "chrome");
2673
- const missing = EXTENSION_REQUIRED_FILES.filter((file2) => !deps.exists(join5(extensionDir, file2)));
2816
+ const extensionDir = join6(rootDir, "extensions", "chrome");
2817
+ const missing = EXTENSION_REQUIRED_FILES.filter((file2) => !deps.exists(join6(extensionDir, file2)));
2674
2818
  if (missing.length === 0) {
2675
2819
  return check2(
2676
2820
  "Chrome extension source",
@@ -2686,7 +2830,7 @@ function checkExtensionSource(rootDir, deps) {
2686
2830
  );
2687
2831
  }
2688
2832
  function extensionManifestVersion(rootDir, deps) {
2689
- const manifestText = deps.readText(join5(rootDir, "extensions", "chrome", "manifest.json"));
2833
+ const manifestText = deps.readText(join6(rootDir, "extensions", "chrome", "manifest.json"));
2690
2834
  if (manifestText === null) return null;
2691
2835
  try {
2692
2836
  const manifest = JSON.parse(manifestText);
@@ -2698,7 +2842,7 @@ function extensionManifestVersion(rootDir, deps) {
2698
2842
  }
2699
2843
  }
2700
2844
  function checkExtensionPackage(rootDir, deps) {
2701
- const distDir = join5(rootDir, "dist", "chrome-extension");
2845
+ const distDir = join6(rootDir, "dist", "chrome-extension");
2702
2846
  const files = deps.listDir(distDir);
2703
2847
  if (files === null) {
2704
2848
  return check2(
@@ -2844,14 +2988,14 @@ function readText(path2) {
2844
2988
  }
2845
2989
  function listDir(path2) {
2846
2990
  try {
2847
- return readdirSync2(path2);
2991
+ return readdirSync3(path2);
2848
2992
  } catch {
2849
2993
  return null;
2850
2994
  }
2851
2995
  }
2852
2996
  function stat2(path2) {
2853
2997
  try {
2854
- const fileStat = statSync2(path2);
2998
+ const fileStat = statSync3(path2);
2855
2999
  return { isDirectory: fileStat.isDirectory(), size: fileStat.size };
2856
3000
  } catch {
2857
3001
  return null;
@@ -2862,7 +3006,7 @@ function createDoctorDeps() {
2862
3006
  env: process.env,
2863
3007
  platform: process.platform,
2864
3008
  cwd: process.cwd(),
2865
- exists: existsSync4,
3009
+ exists: existsSync5,
2866
3010
  readText,
2867
3011
  listDir,
2868
3012
  stat: stat2,
@@ -2911,12 +3055,12 @@ var init_doctor = __esm({
2911
3055
  });
2912
3056
 
2913
3057
  // src/cli.ts
2914
- import { Buffer as Buffer3 } from "node:buffer";
3058
+ import { Buffer as Buffer4 } from "node:buffer";
2915
3059
  import { spawn } from "node:child_process";
2916
3060
  import { createConnection as createConnection2 } from "node:net";
2917
3061
  import { fileURLToPath as fileURLToPath2 } from "node:url";
2918
- import { dirname as dirname2, join as join6, resolve as resolvePath } from "node:path";
2919
- import { existsSync as existsSync5 } from "node:fs";
3062
+ import { dirname as dirname3, join as join7, resolve as resolvePath } from "node:path";
3063
+ import { existsSync as existsSync6 } from "node:fs";
2920
3064
 
2921
3065
  // src/cli/networkHints.ts
2922
3066
  import { networkInterfaces } from "node:os";
@@ -3037,8 +3181,9 @@ function shouldSuppressServerOutputLine(line, context) {
3037
3181
 
3038
3182
  // src/proxy/identityProxy.ts
3039
3183
  import http from "node:http";
3040
- import { Buffer as Buffer2 } from "node:buffer";
3184
+ import { Buffer as Buffer3 } from "node:buffer";
3041
3185
  import { timingSafeEqual } from "node:crypto";
3186
+ import { isIP } from "node:net";
3042
3187
 
3043
3188
  // src/proxy/socketTracker.ts
3044
3189
  import { execFile } from "node:child_process";
@@ -17561,10 +17706,15 @@ config(en_default());
17561
17706
 
17562
17707
  // src/proxy/logger.ts
17563
17708
  init_dataDir();
17564
- import { readdir, stat, unlink, appendFile, mkdir } from "node:fs/promises";
17565
- import { writeFileSync, mkdirSync } from "node:fs";
17709
+ init_privateDataPath();
17710
+ import { Buffer as Buffer2 } from "node:buffer";
17711
+ import { appendFileSync } from "node:fs";
17712
+ import { appendFile, readdir, rename, stat, unlink } from "node:fs/promises";
17566
17713
  import path from "node:path";
17567
- var RETENTION_DAYS = Number(process.env["LOG_RETENTION_DAYS"] ?? "7");
17714
+ var DAY_MS = 24 * 60 * 60 * 1e3;
17715
+ var DEFAULT_RETENTION_DAYS = 7;
17716
+ var DEFAULT_APP_LOG_MAX_BYTES = 10 * 1024 * 1024;
17717
+ var DEFAULT_APP_LOG_MAX_FILES = 5;
17568
17718
  function writeStderrFallback(message, error51) {
17569
17719
  process.stderr.write(`${message} ${String(error51)}
17570
17720
  `);
@@ -17590,45 +17740,211 @@ function getInspectorLogPath() {
17590
17740
  }
17591
17741
  return path.join(getDataDir(), "logs", "inspector.log");
17592
17742
  }
17743
+ function readBoundedInteger(name, fallback, minimum, maximum) {
17744
+ const raw = process.env[name];
17745
+ if (raw === void 0 || raw === "") return fallback;
17746
+ const parsed = Number(raw);
17747
+ if (!Number.isInteger(parsed) || parsed < minimum || parsed > maximum) return fallback;
17748
+ return parsed;
17749
+ }
17750
+ function getApplicationLogPolicy() {
17751
+ const retentionDays = readBoundedInteger("LOG_RETENTION_DAYS", DEFAULT_RETENTION_DAYS, 1, 3650);
17752
+ return {
17753
+ maxBytes: readBoundedInteger(
17754
+ "AGENT_INSPECTOR_APP_LOG_MAX_BYTES",
17755
+ DEFAULT_APP_LOG_MAX_BYTES,
17756
+ 1,
17757
+ 1024 * 1024 * 1024
17758
+ ),
17759
+ maxFiles: readBoundedInteger(
17760
+ "AGENT_INSPECTOR_APP_LOG_MAX_FILES",
17761
+ DEFAULT_APP_LOG_MAX_FILES,
17762
+ 1,
17763
+ 100
17764
+ ),
17765
+ retentionMs: retentionDays * DAY_MS
17766
+ };
17767
+ }
17768
+ function getRotatedGeneration(entry, baseName) {
17769
+ const prefix = `${baseName}.`;
17770
+ if (!entry.startsWith(prefix)) return null;
17771
+ const suffix = entry.slice(prefix.length);
17772
+ if (!/^\d+$/.test(suffix)) return null;
17773
+ const generation = Number(suffix);
17774
+ if (!Number.isSafeInteger(generation) || generation < 1) return null;
17775
+ return generation;
17776
+ }
17777
+ async function listApplicationLogFiles(logPath) {
17778
+ const directory = path.dirname(logPath);
17779
+ const baseName = path.basename(logPath);
17780
+ const entries = await readdir(directory);
17781
+ const files = [];
17782
+ for (const entry of entries) {
17783
+ const generation = entry === baseName ? 0 : getRotatedGeneration(entry, baseName);
17784
+ if (generation === null) continue;
17785
+ const filePath = path.join(directory, entry);
17786
+ try {
17787
+ const fileStat = await stat(filePath);
17788
+ if (!fileStat.isFile()) continue;
17789
+ files.push({ generation, modifiedAt: fileStat.mtimeMs, path: filePath });
17790
+ } catch {
17791
+ }
17792
+ }
17793
+ return files;
17794
+ }
17795
+ async function removeApplicationLog(filePath) {
17796
+ try {
17797
+ await stat(filePath);
17798
+ } catch {
17799
+ return true;
17800
+ }
17801
+ try {
17802
+ await unlink(filePath);
17803
+ return true;
17804
+ } catch {
17805
+ return false;
17806
+ }
17807
+ }
17808
+ async function maintainApplicationLogs(logPath, policy) {
17809
+ const directory = path.dirname(logPath);
17810
+ if (!ensurePrivateDirectorySync(directory)) return false;
17811
+ let files;
17812
+ try {
17813
+ files = await listApplicationLogFiles(logPath);
17814
+ } catch {
17815
+ return false;
17816
+ }
17817
+ for (const file2 of files) {
17818
+ if (!securePrivateFileSync(file2.path)) return false;
17819
+ }
17820
+ const cutoff = Date.now() - policy.retentionMs;
17821
+ const retained = files.filter((file2) => file2.modifiedAt >= cutoff);
17822
+ let maintained = true;
17823
+ for (const file2 of files) {
17824
+ if (file2.modifiedAt < cutoff && !await removeApplicationLog(file2.path)) maintained = false;
17825
+ }
17826
+ const rotated = retained.filter((file2) => file2.generation > 0).sort((left, right) => right.modifiedAt - left.modifiedAt);
17827
+ const retainedRotatedCount = Math.max(0, policy.maxFiles - 1);
17828
+ for (const file2 of rotated.slice(retainedRotatedCount)) {
17829
+ if (!await removeApplicationLog(file2.path)) maintained = false;
17830
+ }
17831
+ return maintained;
17832
+ }
17833
+ async function moveApplicationLog(source, destination) {
17834
+ try {
17835
+ await stat(source);
17836
+ } catch {
17837
+ return true;
17838
+ }
17839
+ try {
17840
+ await rename(source, destination);
17841
+ if (!securePrivateFileSync(destination)) {
17842
+ writeStderrFallback("[logger] Failed to secure rotated application log:", destination);
17843
+ return false;
17844
+ }
17845
+ return true;
17846
+ } catch (error51) {
17847
+ writeStderrFallback("[logger] Failed to rotate application log:", error51);
17848
+ return false;
17849
+ }
17850
+ }
17851
+ async function rotateApplicationLog(logPath, maxFiles) {
17852
+ if (maxFiles === 1) {
17853
+ return await removeApplicationLog(logPath);
17854
+ }
17855
+ if (!await removeApplicationLog(`${logPath}.${String(maxFiles - 1)}`)) return false;
17856
+ for (let generation = maxFiles - 2; generation >= 1; generation -= 1) {
17857
+ if (!await moveApplicationLog(
17858
+ `${logPath}.${String(generation)}`,
17859
+ `${logPath}.${String(generation + 1)}`
17860
+ )) {
17861
+ return false;
17862
+ }
17863
+ }
17864
+ return await moveApplicationLog(logPath, `${logPath}.1`);
17865
+ }
17866
+ async function rotateApplicationLogIfNeeded(logPath, incomingBytes, policy) {
17867
+ try {
17868
+ const fileStat = await stat(logPath);
17869
+ if (fileStat.size > 0 && fileStat.size + incomingBytes > policy.maxBytes) {
17870
+ return await rotateApplicationLog(logPath, policy.maxFiles);
17871
+ }
17872
+ return true;
17873
+ } catch {
17874
+ return true;
17875
+ }
17876
+ }
17593
17877
  async function initLogger() {
17594
17878
  const dir = resolveLogDir();
17595
- const retentionMs = RETENTION_DAYS * 24 * 60 * 60 * 1e3;
17596
- const cutoff = Date.now() - retentionMs;
17879
+ const policy = getApplicationLogPolicy();
17880
+ const cutoff = Date.now() - policy.retentionMs;
17597
17881
  try {
17598
- await mkdir(dir, { recursive: true });
17599
- const entries = await readdir(dir);
17600
- for (const entry of entries) {
17601
- if (!entry.endsWith(".jsonl")) continue;
17602
- const fullPath = path.join(dir, entry);
17603
- try {
17604
- const s = await stat(fullPath);
17605
- if (s.mtimeMs < cutoff) {
17606
- await unlink(fullPath);
17882
+ if (!ensurePrivateDirectorySync(dir)) {
17883
+ writeStderrFallback("[logger] Failed to secure log directory:", dir);
17884
+ } else if (!securePrivateTreeSync(dir)) {
17885
+ writeStderrFallback("[logger] Failed to secure existing log files:", dir);
17886
+ } else {
17887
+ const entries = await readdir(dir);
17888
+ for (const entry of entries) {
17889
+ if (!entry.endsWith(".jsonl")) continue;
17890
+ const fullPath = path.join(dir, entry);
17891
+ try {
17892
+ const s = await stat(fullPath);
17893
+ if (s.mtimeMs < cutoff) {
17894
+ await unlink(fullPath);
17895
+ }
17896
+ } catch {
17607
17897
  }
17608
- } catch {
17609
17898
  }
17610
17899
  }
17611
17900
  } catch (err) {
17612
17901
  writeStderrFallback("[logger] Failed to initialize log directory:", err);
17613
17902
  }
17903
+ const applicationLogPath = getInspectorLogPath();
17904
+ if (!await maintainApplicationLogs(applicationLogPath, policy)) {
17905
+ writeStderrFallback(
17906
+ "[logger] Failed to initialize application log lifecycle:",
17907
+ applicationLogPath
17908
+ );
17909
+ }
17614
17910
  }
17615
- var loggerInitialized = false;
17616
- async function writeAppLog(message) {
17617
- if (!loggerInitialized) {
17618
- loggerInitialized = true;
17619
- void initLogger();
17911
+ var loggerInitialization = null;
17912
+ var applicationLogWriteQueue = Promise.resolve();
17913
+ function ensureLoggerInitialized() {
17914
+ if (loggerInitialization === null) loggerInitialization = initLogger();
17915
+ return loggerInitialization;
17916
+ }
17917
+ async function appendApplicationLogLine(logPath, line, policy) {
17918
+ const logDirPath = path.dirname(logPath);
17919
+ if (!ensurePrivateDirectorySync(logDirPath)) {
17920
+ return { ok: false, reason: "application log directory is not private" };
17620
17921
  }
17621
- const logPath = getInspectorLogPath();
17622
17922
  try {
17623
- const logDirPath = path.dirname(logPath);
17624
- await mkdir(logDirPath, { recursive: true });
17625
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
17626
- await appendFile(logPath, `[${timestamp}] ${message}
17627
- `, "utf-8");
17628
- } catch (err) {
17629
- writeStderrFallback(`[logger] Failed to write to ${logPath}:`, err);
17923
+ if (!await rotateApplicationLogIfNeeded(logPath, Buffer2.byteLength(line, "utf-8"), policy)) {
17924
+ return { ok: false, reason: "application log rotation failed" };
17925
+ }
17926
+ await appendFile(logPath, line, { encoding: "utf-8", mode: PRIVATE_FILE_MODE });
17927
+ if (!securePrivateFileSync(logPath)) {
17928
+ return { ok: false, reason: "application log permissions could not be secured" };
17929
+ }
17930
+ return { ok: true };
17931
+ } catch (error51) {
17932
+ return { ok: false, reason: error51 };
17630
17933
  }
17631
17934
  }
17935
+ async function writeAppLog(message) {
17936
+ const logPath = getInspectorLogPath();
17937
+ const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
17938
+ `;
17939
+ const initialization = ensureLoggerInitialized();
17940
+ const write = async () => {
17941
+ await initialization;
17942
+ const result = await appendApplicationLogLine(logPath, line, getApplicationLogPolicy());
17943
+ if (!result.ok) writeStderrFallback(`[logger] Failed to write to ${logPath}:`, result.reason);
17944
+ };
17945
+ applicationLogWriteQueue = applicationLogWriteQueue.then(write, write);
17946
+ await applicationLogWriteQueue;
17947
+ }
17632
17948
  var logger = {
17633
17949
  debug(message, ...args) {
17634
17950
  const msg = args.length > 0 ? `${message} ${args.map(String).join(" ")}` : message;
@@ -17650,62 +17966,23 @@ var logger = {
17650
17966
  var writeBuffer = [];
17651
17967
  var writeQueue = Promise.resolve();
17652
17968
  var logWriteLock = Promise.resolve();
17653
- async function runWithLogWriteLock(fn) {
17654
- const previousLock = logWriteLock;
17655
- let releaseLock = () => {
17656
- };
17657
- logWriteLock = new Promise((resolve2) => {
17658
- releaseLock = resolve2;
17659
- });
17660
- await previousLock;
17661
- try {
17662
- return await fn();
17663
- } finally {
17664
- releaseLock();
17665
- }
17666
- }
17667
17969
  function drainBuffer() {
17668
17970
  const toWrite = writeBuffer.join("");
17669
17971
  writeBuffer = [];
17670
17972
  return toWrite;
17671
17973
  }
17672
- async function flushWriteBufferUnlocked() {
17673
- let toWrite;
17674
- {
17675
- toWrite = drainBuffer();
17676
- }
17677
- if (toWrite.length === 0) return;
17678
- try {
17679
- const filePath = getLogFilePath();
17680
- await mkdir(path.dirname(filePath), { recursive: true });
17681
- await appendFile(filePath, toWrite, "utf-8");
17682
- } catch (err) {
17683
- writeBuffer.unshift(toWrite);
17684
- writeStderrFallback("[logger] Failed to flush write buffer, re-queued:", err);
17685
- }
17686
- }
17687
- async function flushLogBuffer() {
17688
- await writeQueue;
17689
- if (writeBuffer.length > 0) {
17690
- await runWithLogWriteLock(flushWriteBufferUnlocked);
17691
- }
17692
- }
17693
17974
  process.on("exit", () => {
17694
17975
  if (writeBuffer.length > 0) {
17695
17976
  const toWrite = drainBuffer();
17696
17977
  try {
17697
17978
  const filePath = getLogFilePath();
17698
- mkdirSync(path.dirname(filePath), { recursive: true });
17699
- writeFileSync(filePath, toWrite, "utf-8");
17979
+ if (!ensurePrivateDirectorySync(path.dirname(filePath))) return;
17980
+ appendFileSync(filePath, toWrite, { encoding: "utf-8", mode: PRIVATE_FILE_MODE });
17981
+ void securePrivateFileSync(filePath);
17700
17982
  } catch {
17701
17983
  }
17702
17984
  }
17703
17985
  });
17704
- for (const signal of ["SIGINT", "SIGTERM"]) {
17705
- process.on(signal, () => {
17706
- void flushLogBuffer().then(() => process.exit(0));
17707
- });
17708
- }
17709
17986
 
17710
17987
  // src/proxy/socketTracker.ts
17711
17988
  var execFileAsync = promisify(execFile);
@@ -17871,6 +18148,12 @@ var IDENTITY_HEADERS = {
17871
18148
  cwd: "x-agent-inspector-client-cwd",
17872
18149
  projectFolder: "x-agent-inspector-client-project-folder"
17873
18150
  };
18151
+ var IDENTITY_HEADER_PREFIX = "x-agent-inspector-";
18152
+ var PROXY_TOKEN_HEADER = "x-agent-inspector-proxy-token";
18153
+ var CSRF_HEADER = "x-agent-inspector-csrf";
18154
+ var DEFAULT_MAX_REQUEST_BYTES = 64 * 1024 * 1024;
18155
+ var DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
18156
+ var identityProxyRuntimeStates = /* @__PURE__ */ new WeakMap();
17874
18157
  var REQUEST_HOP_BY_HOP = /* @__PURE__ */ new Set([
17875
18158
  "connection",
17876
18159
  "keep-alive",
@@ -17892,6 +18175,24 @@ var RESPONSE_HOP_BY_HOP = /* @__PURE__ */ new Set([
17892
18175
  "transfer-encoding",
17893
18176
  "upgrade"
17894
18177
  ]);
18178
+ var BASE_SECURITY_HEADERS = {
18179
+ "permissions-policy": "camera=(), microphone=(), geolocation=()",
18180
+ "referrer-policy": "no-referrer",
18181
+ "x-content-type-options": "nosniff",
18182
+ "x-frame-options": "DENY"
18183
+ };
18184
+ var DOCUMENT_CONTENT_SECURITY_POLICY = [
18185
+ "default-src 'self'",
18186
+ "base-uri 'self'",
18187
+ "connect-src 'self'",
18188
+ "font-src 'self' data:",
18189
+ "frame-ancestors 'none'",
18190
+ "img-src 'self' data: blob:",
18191
+ "object-src 'none'",
18192
+ "script-src 'self' 'unsafe-inline'",
18193
+ "style-src 'self' 'unsafe-inline'",
18194
+ "worker-src 'self' blob:"
18195
+ ].join("; ");
17895
18196
  function isLoopbackPeer(address) {
17896
18197
  if (address === void 0) return false;
17897
18198
  const normalized = address.toLowerCase();
@@ -17904,22 +18205,92 @@ function bearerToken(authorization) {
17904
18205
  }
17905
18206
  function tokensMatch(received, expected) {
17906
18207
  if (received === null || expected === void 0 || expected === "") return false;
17907
- const receivedBuffer = Buffer2.from(received);
17908
- const expectedBuffer = Buffer2.from(expected);
18208
+ const receivedBuffer = Buffer3.from(received);
18209
+ const expectedBuffer = Buffer3.from(expected);
17909
18210
  return receivedBuffer.byteLength === expectedBuffer.byteLength && timingSafeEqual(receivedBuffer, expectedBuffer);
17910
18211
  }
18212
+ function firstHeader(value) {
18213
+ return typeof value === "string" ? value : value?.[0];
18214
+ }
18215
+ function normalizedHostname(value) {
18216
+ const candidate = value.trim();
18217
+ if (candidate.length === 0) return null;
18218
+ try {
18219
+ return new URL(`http://${candidate}`).hostname.toLowerCase().replace(/^\[|]$/g, "");
18220
+ } catch {
18221
+ return null;
18222
+ }
18223
+ }
18224
+ function isLoopbackHostname(hostname3) {
18225
+ return hostname3 === "localhost" || hostname3 === "::1" || hostname3.startsWith("127.");
18226
+ }
18227
+ function isTrustedIdentityProxyHost(host, options) {
18228
+ if (host === void 0) return false;
18229
+ const hostname3 = normalizedHostname(host);
18230
+ if (hostname3 === null) return false;
18231
+ if (isLoopbackHostname(hostname3) || isIP(hostname3) !== 0) return true;
18232
+ const listenHostname = normalizedHostname(options.listenHost);
18233
+ if (listenHostname !== null && hostname3 === listenHostname) return true;
18234
+ return (options.trustedHosts ?? []).some(
18235
+ (trustedHost) => normalizedHostname(trustedHost) === hostname3
18236
+ );
18237
+ }
18238
+ function isSafeMethod(method) {
18239
+ const normalized = (method ?? "GET").toUpperCase();
18240
+ return normalized === "GET" || normalized === "HEAD" || normalized === "OPTIONS";
18241
+ }
18242
+ function isPublicHealthPath(pathname) {
18243
+ return pathname === "/livez" || pathname === "/readyz" || pathname === "/api/health";
18244
+ }
18245
+ function configuredPositiveInteger(value, fallback) {
18246
+ return value !== void 0 && Number.isSafeInteger(value) && value > 0 ? value : fallback;
18247
+ }
18248
+ function declaredContentLength(value) {
18249
+ if (value === void 0) return null;
18250
+ if (!/^\d+$/.test(value)) return Number.NaN;
18251
+ const parsed = Number(value);
18252
+ return Number.isSafeInteger(parsed) ? parsed : Number.NaN;
18253
+ }
18254
+ function isBrowserMutationAllowed(input) {
18255
+ if (isSafeMethod(input.method)) return true;
18256
+ const pathname = new URL(input.path, "http://localhost").pathname;
18257
+ if (pathname === "/proxy" || pathname.startsWith("/proxy/")) return true;
18258
+ const fetchSite = input.secFetchSite?.toLowerCase();
18259
+ const browserRequest = input.origin !== void 0 || fetchSite !== void 0;
18260
+ if (!browserRequest) return true;
18261
+ if (fetchSite !== void 0 && fetchSite !== "same-origin") return false;
18262
+ if (input.origin !== void 0) {
18263
+ try {
18264
+ const originHost = new URL(input.origin).host.toLowerCase();
18265
+ if (originHost !== input.host?.toLowerCase()) return false;
18266
+ } catch {
18267
+ return false;
18268
+ }
18269
+ }
18270
+ return input.csrfToken === "1";
18271
+ }
17911
18272
  function isIdentityProxyRequestAllowed(input) {
17912
18273
  if (input.options.restrictRemoteControl !== true) return true;
17913
18274
  if (input.options.allowRemoteControl === true) return true;
17914
18275
  if (isLoopbackPeer(input.remoteAddress)) return true;
17915
18276
  const pathname = new URL(input.path, "http://localhost").pathname;
17916
- if (pathname === "/proxy" || pathname.startsWith("/proxy/")) return true;
17917
- if (pathname === "/api/health") return true;
18277
+ if (pathname === "/proxy" || pathname.startsWith("/proxy/")) {
18278
+ if (input.options.allowUnauthenticatedRemoteProxy === true) return true;
18279
+ const proxyCredential = bearerToken(input.proxyAuthorization) ?? input.proxyTokenHeader ?? null;
18280
+ return tokensMatch(proxyCredential, input.options.proxyToken);
18281
+ }
18282
+ if (isPublicHealthPath(pathname)) return true;
17918
18283
  return tokensMatch(bearerToken(input.authorization), input.options.controlToken);
17919
18284
  }
17920
18285
  function startIdentityProxy(options) {
17921
18286
  return new Promise((resolve2, reject) => {
17922
18287
  const server = http.createServer();
18288
+ const runtimeState = {
18289
+ acceptingRequests: true,
18290
+ activeRequests: 0,
18291
+ drainWaiters: /* @__PURE__ */ new Set()
18292
+ };
18293
+ identityProxyRuntimeStates.set(server, runtimeState);
17923
18294
  server.on("connection", (socket) => {
17924
18295
  const remotePort = socket.remotePort;
17925
18296
  if (remotePort !== void 0 && remotePort > 0) {
@@ -17927,6 +18298,29 @@ function startIdentityProxy(options) {
17927
18298
  }
17928
18299
  });
17929
18300
  server.on("request", (req, res) => {
18301
+ if (!runtimeState.acceptingRequests) {
18302
+ res.writeHead(503, {
18303
+ "content-type": "application/json; charset=utf-8",
18304
+ "cache-control": "no-store",
18305
+ connection: "close",
18306
+ "retry-after": "5"
18307
+ });
18308
+ res.end(JSON.stringify({ error: "Agent Inspector is shutting down" }));
18309
+ req.resume();
18310
+ return;
18311
+ }
18312
+ runtimeState.activeRequests += 1;
18313
+ let released = false;
18314
+ const release = () => {
18315
+ if (released) return;
18316
+ released = true;
18317
+ runtimeState.activeRequests = Math.max(0, runtimeState.activeRequests - 1);
18318
+ if (runtimeState.activeRequests !== 0) return;
18319
+ for (const resolveDrain of runtimeState.drainWaiters) resolveDrain();
18320
+ runtimeState.drainWaiters.clear();
18321
+ };
18322
+ res.once("finish", release);
18323
+ res.once("close", release);
17930
18324
  void handleRequest(req, res, options).catch((err) => {
17931
18325
  const message = err instanceof Error ? err.message : String(err);
17932
18326
  if (!res.headersSent) {
@@ -17942,11 +18336,65 @@ function startIdentityProxy(options) {
17942
18336
  });
17943
18337
  });
17944
18338
  }
18339
+ function stopIdentityProxyAdmission(server) {
18340
+ const state = identityProxyRuntimeStates.get(server);
18341
+ if (state !== void 0) state.acceptingRequests = false;
18342
+ }
18343
+ async function waitForIdentityProxyDrain(server) {
18344
+ const state = identityProxyRuntimeStates.get(server);
18345
+ if (state === void 0 || state.activeRequests === 0) return;
18346
+ await new Promise((resolve2) => {
18347
+ state.drainWaiters.add(resolve2);
18348
+ });
18349
+ }
18350
+ async function closeIdentityProxyGracefully(server) {
18351
+ stopIdentityProxyAdmission(server);
18352
+ const closed = new Promise((resolve2) => {
18353
+ if (!server.listening) {
18354
+ resolve2();
18355
+ return;
18356
+ }
18357
+ server.close(() => resolve2());
18358
+ });
18359
+ await Promise.all([closed, waitForIdentityProxyDrain(server)]);
18360
+ }
18361
+ function forceCloseIdentityProxy(server) {
18362
+ stopIdentityProxyAdmission(server);
18363
+ server.closeAllConnections();
18364
+ server.close();
18365
+ }
17945
18366
  async function handleRequest(req, res, options) {
18367
+ if (!isTrustedIdentityProxyHost(req.headers.host, options)) {
18368
+ res.writeHead(400, {
18369
+ "content-type": "application/json; charset=utf-8",
18370
+ "cache-control": "no-store"
18371
+ });
18372
+ res.end(JSON.stringify({ error: "Untrusted Host header" }));
18373
+ req.resume();
18374
+ return;
18375
+ }
18376
+ if (!isBrowserMutationAllowed({
18377
+ path: req.url ?? "/",
18378
+ method: req.method,
18379
+ host: req.headers.host,
18380
+ origin: req.headers.origin,
18381
+ secFetchSite: firstHeader(req.headers["sec-fetch-site"]),
18382
+ csrfToken: firstHeader(req.headers[CSRF_HEADER])
18383
+ })) {
18384
+ res.writeHead(403, {
18385
+ "content-type": "application/json; charset=utf-8",
18386
+ "cache-control": "no-store"
18387
+ });
18388
+ res.end(JSON.stringify({ error: "Browser mutation requires same-origin CSRF proof" }));
18389
+ req.resume();
18390
+ return;
18391
+ }
17946
18392
  if (!isIdentityProxyRequestAllowed({
17947
18393
  path: req.url ?? "/",
17948
18394
  remoteAddress: req.socket.remoteAddress,
17949
18395
  authorization: req.headers.authorization,
18396
+ proxyAuthorization: req.headers["proxy-authorization"],
18397
+ proxyTokenHeader: firstHeader(req.headers[PROXY_TOKEN_HEADER]),
17950
18398
  options
17951
18399
  })) {
17952
18400
  res.writeHead(403, {
@@ -17955,9 +18403,10 @@ async function handleRequest(req, res, options) {
17955
18403
  });
17956
18404
  res.end(
17957
18405
  JSON.stringify({
17958
- error: "Remote control-plane access is disabled. Use the proxy endpoint or connect locally."
18406
+ error: "Remote access requires a credential scoped to this endpoint."
17959
18407
  })
17960
18408
  );
18409
+ req.resume();
17961
18410
  return;
17962
18411
  }
17963
18412
  const remotePort = req.socket.remotePort;
@@ -17969,14 +18418,34 @@ async function handleRequest(req, res, options) {
17969
18418
  identity = null;
17970
18419
  }
17971
18420
  }
17972
- const chunks = [];
17973
- for await (const chunk of req) {
17974
- chunks.push(typeof chunk === "string" ? Buffer2.from(chunk) : toBuffer(chunk));
18421
+ const maxRequestBytes = configuredPositiveInteger(
18422
+ options.maxRequestBytes,
18423
+ DEFAULT_MAX_REQUEST_BYTES
18424
+ );
18425
+ const contentLength = declaredContentLength(req.headers["content-length"]);
18426
+ if (contentLength !== null && (!Number.isFinite(contentLength) || contentLength < 0)) {
18427
+ res.writeHead(400, {
18428
+ "content-type": "application/json; charset=utf-8",
18429
+ "cache-control": "no-store"
18430
+ });
18431
+ res.end(JSON.stringify({ error: "Invalid Content-Length header" }));
18432
+ req.resume();
18433
+ return;
18434
+ }
18435
+ if (contentLength !== null && contentLength > maxRequestBytes) {
18436
+ res.writeHead(413, {
18437
+ "content-type": "application/json; charset=utf-8",
18438
+ "cache-control": "no-store"
18439
+ });
18440
+ res.end(JSON.stringify({ error: `Request body exceeds ${String(maxRequestBytes)} bytes` }));
18441
+ req.resume();
18442
+ return;
17975
18443
  }
17976
- const body = Buffer2.concat(chunks);
17977
18444
  const upstreamHeaders = {};
17978
18445
  for (const [name, value] of Object.entries(req.headers)) {
17979
- if (REQUEST_HOP_BY_HOP.has(name.toLowerCase())) continue;
18446
+ const lowerName = name.toLowerCase();
18447
+ if (REQUEST_HOP_BY_HOP.has(lowerName)) continue;
18448
+ if (lowerName.startsWith(IDENTITY_HEADER_PREFIX)) continue;
17980
18449
  if (value === void 0) continue;
17981
18450
  upstreamHeaders[name] = value;
17982
18451
  }
@@ -17992,6 +18461,11 @@ async function handleRequest(req, res, options) {
17992
18461
  upstreamHeaders[IDENTITY_HEADERS.projectFolder] = identity.projectFolder;
17993
18462
  }
17994
18463
  }
18464
+ let terminal = false;
18465
+ const requestTimeoutMs = configuredPositiveInteger(
18466
+ options.requestTimeoutMs,
18467
+ DEFAULT_REQUEST_TIMEOUT_MS
18468
+ );
17995
18469
  const proxyReq = http.request(
17996
18470
  {
17997
18471
  hostname: options.upstreamHost,
@@ -18001,38 +18475,100 @@ async function handleRequest(req, res, options) {
18001
18475
  headers: upstreamHeaders
18002
18476
  },
18003
18477
  (proxyRes) => {
18478
+ if (terminal) {
18479
+ proxyRes.resume();
18480
+ return;
18481
+ }
18004
18482
  const outHeaders = {};
18005
18483
  for (const [name, value] of Object.entries(proxyRes.headers)) {
18006
18484
  if (RESPONSE_HOP_BY_HOP.has(name.toLowerCase())) continue;
18007
18485
  if (value === void 0) continue;
18008
18486
  outHeaders[name] = value;
18009
18487
  }
18488
+ for (const [name, value] of Object.entries(BASE_SECURITY_HEADERS)) {
18489
+ outHeaders[name] = value;
18490
+ }
18491
+ const contentType = firstHeader(proxyRes.headers["content-type"]);
18492
+ if (contentType?.toLowerCase().includes("text/html") === true) {
18493
+ outHeaders["content-security-policy"] = DOCUMENT_CONTENT_SECURITY_POLICY;
18494
+ }
18010
18495
  res.writeHead(proxyRes.statusCode ?? 502, outHeaders);
18011
18496
  proxyRes.pipe(res);
18012
18497
  }
18013
18498
  );
18499
+ const deadline = setTimeout(() => {
18500
+ failRequest(408, "Request body deadline exceeded");
18501
+ }, requestTimeoutMs);
18502
+ deadline.unref();
18503
+ function clearDeadline() {
18504
+ clearTimeout(deadline);
18505
+ }
18506
+ function failRequest(status, error51) {
18507
+ if (terminal) return;
18508
+ terminal = true;
18509
+ clearDeadline();
18510
+ proxyReq.destroy();
18511
+ if (res.headersSent) {
18512
+ res.destroy();
18513
+ } else {
18514
+ res.writeHead(status, {
18515
+ "content-type": "application/json; charset=utf-8",
18516
+ "cache-control": "no-store"
18517
+ });
18518
+ res.end(JSON.stringify({ error: error51 }));
18519
+ }
18520
+ req.resume();
18521
+ }
18014
18522
  proxyReq.on("error", (err) => {
18523
+ if (terminal) return;
18524
+ terminal = true;
18525
+ clearDeadline();
18015
18526
  if (!res.headersSent) {
18016
18527
  res.writeHead(502, { "content-type": "text/plain" });
18017
18528
  }
18018
18529
  res.end(`Upstream error: ${err.message}`);
18019
18530
  });
18020
- if (body.length > 0) proxyReq.write(body);
18021
- proxyReq.end();
18531
+ proxyReq.on("drain", () => req.resume());
18532
+ res.once("close", () => {
18533
+ clearDeadline();
18534
+ if (!res.writableEnded) proxyReq.destroy();
18535
+ });
18536
+ let receivedBytes = 0;
18537
+ req.on("data", (chunk) => {
18538
+ if (terminal) return;
18539
+ const buffer = toBuffer(chunk);
18540
+ receivedBytes += buffer.byteLength;
18541
+ if (receivedBytes > maxRequestBytes) {
18542
+ failRequest(413, `Request body exceeds ${String(maxRequestBytes)} bytes`);
18543
+ return;
18544
+ }
18545
+ if (buffer.byteLength > 0 && !proxyReq.write(buffer)) req.pause();
18546
+ });
18547
+ req.on("end", () => {
18548
+ if (terminal) return;
18549
+ clearDeadline();
18550
+ proxyReq.end();
18551
+ });
18552
+ req.on("aborted", () => {
18553
+ failRequest(400, "Client aborted request body");
18554
+ });
18555
+ req.on("error", () => {
18556
+ failRequest(400, "Request body stream failed");
18557
+ });
18022
18558
  }
18023
18559
  function toBuffer(chunk) {
18024
- if (Buffer2.isBuffer(chunk)) return chunk;
18025
- if (typeof chunk === "string") return Buffer2.from(chunk);
18026
- if (chunk instanceof ArrayBuffer) return Buffer2.from(chunk);
18560
+ if (Buffer3.isBuffer(chunk)) return chunk;
18561
+ if (typeof chunk === "string") return Buffer3.from(chunk);
18562
+ if (chunk instanceof ArrayBuffer) return Buffer3.from(chunk);
18027
18563
  if (chunk instanceof Uint8Array)
18028
- return Buffer2.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
18029
- return Buffer2.alloc(0);
18564
+ return Buffer3.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
18565
+ return Buffer3.alloc(0);
18030
18566
  }
18031
18567
 
18032
18568
  // src/cli.ts
18033
18569
  init_platformCommands();
18034
18570
  var __filename2 = fileURLToPath2(import.meta.url);
18035
- var __dirname2 = dirname2(__filename2);
18571
+ var __dirname2 = dirname3(__filename2);
18036
18572
  var DEFAULT_PORT3 = 9527;
18037
18573
  var LEGACY_ALIAS_PORT = 9527;
18038
18574
  var DEFAULT_UPSTREAM_PORT = 9529;
@@ -18040,6 +18576,8 @@ var DEFAULT_BIND_HOST = "127.0.0.1";
18040
18576
  var LOCAL_PROBE_TIMEOUT_MS = 2e3;
18041
18577
  var BRANDED_WINDOWS_RUNTIME_EXE = "agent-inspector.exe";
18042
18578
  var DEFAULT_CAPTURE_MODE = "simple";
18579
+ var DEFAULT_SHUTDOWN_DEADLINE_MS = 15e3;
18580
+ var SUPERVISOR_SHUTDOWN_GRACE_MS = 2e3;
18043
18581
  process.title = "Agent Inspector";
18044
18582
  var subcommand = process.argv[2];
18045
18583
  if (subcommand === "onboard") {
@@ -18142,7 +18680,7 @@ function pipeServerOutputStream(stream, target, context) {
18142
18680
  if (stream === null) return;
18143
18681
  let buffered = "";
18144
18682
  stream.on("data", (chunk) => {
18145
- const text = Buffer3.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
18683
+ const text = Buffer4.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
18146
18684
  const lines = `${buffered}${text}`.split(/\r?\n/);
18147
18685
  buffered = lines.pop() ?? "";
18148
18686
  for (const line of lines) {
@@ -18161,8 +18699,8 @@ function pipeServerOutputStream(stream, target, context) {
18161
18699
  });
18162
18700
  }
18163
18701
  function resolveServerCommand(outputDir, serverPath) {
18164
- const brandedRuntime = join6(outputDir, BRANDED_WINDOWS_RUNTIME_EXE);
18165
- return process.platform === "win32" && existsSync5(brandedRuntime) ? { command: brandedRuntime, args: [serverPath] } : { command: process.execPath, args: [serverPath] };
18702
+ const brandedRuntime = join7(outputDir, BRANDED_WINDOWS_RUNTIME_EXE);
18703
+ return process.platform === "win32" && existsSync6(brandedRuntime) ? { command: brandedRuntime, args: [serverPath] } : { command: process.execPath, args: [serverPath] };
18166
18704
  }
18167
18705
  function parseCaptureMode(value) {
18168
18706
  switch (value) {
@@ -18188,16 +18726,33 @@ function printAccessHints(port, host) {
18188
18726
  console.log(line);
18189
18727
  }
18190
18728
  if (!isLoopbackHost(host)) {
18729
+ const proxyTokenConfigured = (process.env["AGENT_INSPECTOR_PROXY_TOKEN"] ?? "") !== "";
18730
+ const legacyProxyEnabled = process.env["AGENT_INSPECTOR_ALLOW_UNAUTHENTICATED_REMOTE_PROXY"] === "1";
18731
+ if (proxyTokenConfigured) {
18732
+ console.log(" Remote proxy: dedicated token authentication enabled");
18733
+ } else if (legacyProxyEnabled) {
18734
+ console.warn(" Remote proxy: UNAUTHENTICATED compatibility mode enabled");
18735
+ } else {
18736
+ console.warn(" Remote proxy: blocked until AGENT_INSPECTOR_PROXY_TOKEN is configured");
18737
+ }
18191
18738
  if (process.env["AGENT_INSPECTOR_ALLOW_REMOTE_CONTROL"] === "1") {
18192
18739
  console.warn(" Control plane: REMOTE ACCESS ENABLED by administrator override");
18193
18740
  } else {
18194
- console.log(" Control plane: local only; remote clients may use /proxy");
18741
+ console.log(" Control plane: local only by default");
18195
18742
  if ((process.env["AGENT_INSPECTOR_CONTROL_TOKEN"] ?? "") !== "") {
18196
18743
  console.log(" Remote API: Bearer token authentication enabled");
18197
18744
  }
18198
18745
  }
18199
18746
  }
18200
18747
  }
18748
+ function readPositiveIntegerEnv(name) {
18749
+ const raw = process.env[name];
18750
+ if (raw === void 0 || raw.trim().length === 0) return void 0;
18751
+ const parsed = Number(raw);
18752
+ if (Number.isSafeInteger(parsed) && parsed > 0) return parsed;
18753
+ console.warn(`Ignoring invalid ${name}=${raw}; expected a positive integer.`);
18754
+ return void 0;
18755
+ }
18201
18756
  function buildBackgroundSupervisorArgs(options) {
18202
18757
  const args = ["--no-open", "--port", String(options.port), "--mode", options.captureMode];
18203
18758
  if (options.host !== void 0) {
@@ -18236,7 +18791,12 @@ async function tryStartIdentityProxy(identityPort, upstreamPort, listenHost, ups
18236
18791
  upstreamPort,
18237
18792
  restrictRemoteControl: !isLoopbackHost(listenHost),
18238
18793
  allowRemoteControl: process.env["AGENT_INSPECTOR_ALLOW_REMOTE_CONTROL"] === "1",
18239
- controlToken: process.env["AGENT_INSPECTOR_CONTROL_TOKEN"]
18794
+ controlToken: process.env["AGENT_INSPECTOR_CONTROL_TOKEN"],
18795
+ proxyToken: process.env["AGENT_INSPECTOR_PROXY_TOKEN"],
18796
+ allowUnauthenticatedRemoteProxy: process.env["AGENT_INSPECTOR_ALLOW_UNAUTHENTICATED_REMOTE_PROXY"] === "1",
18797
+ trustedHosts: (process.env["AGENT_INSPECTOR_TRUSTED_HOSTS"] ?? "").split(",").map((host) => host.trim()).filter((host) => host.length > 0),
18798
+ maxRequestBytes: readPositiveIntegerEnv("AGENT_INSPECTOR_MAX_REQUEST_BYTES"),
18799
+ requestTimeoutMs: readPositiveIntegerEnv("AGENT_INSPECTOR_REQUEST_TIMEOUT_MS")
18240
18800
  });
18241
18801
  return server;
18242
18802
  } catch (err) {
@@ -18388,9 +18948,9 @@ async function runStart(args) {
18388
18948
  process.exitCode = 1;
18389
18949
  return;
18390
18950
  }
18391
- if (!enableIdentityProxy && !isLoopbackHost(host) && process.env["AGENT_INSPECTOR_ALLOW_REMOTE_CONTROL"] !== "1") {
18951
+ if (!enableIdentityProxy && process.env["AGENT_INSPECTOR_ALLOW_UNPROTECTED_INGRESS"] !== "1") {
18392
18952
  console.error(
18393
- "Refusing an unprotected remote bind with --no-identity-proxy. Keep the identity proxy enabled or set AGENT_INSPECTOR_ALLOW_REMOTE_CONTROL=1 explicitly."
18953
+ "Refusing --no-identity-proxy because it also disables Host, CSRF, credential-scope, and first-hop budget enforcement. Keep the protected ingress enabled or set AGENT_INSPECTOR_ALLOW_UNPROTECTED_INGRESS=1 explicitly for temporary diagnostics."
18394
18954
  );
18395
18955
  process.exitCode = 1;
18396
18956
  return;
@@ -18518,11 +19078,11 @@ async function runStart(args) {
18518
19078
  openBrowser(url2);
18519
19079
  }
18520
19080
  const outputDir = __dirname2;
18521
- const serverPath = join6(outputDir, "../.output/server/index.mjs");
19081
+ const serverPath = join7(outputDir, "../.output/server/index.mjs");
18522
19082
  const serverCommand = resolveServerCommand(outputDir, serverPath);
18523
19083
  const serverEnv = { ...process.env };
18524
19084
  if (configDir !== void 0) {
18525
- let resolvedPath = join6(configDir);
19085
+ let resolvedPath = join7(configDir);
18526
19086
  const msysMatch = /^\\([a-z])\\(.*)$/i.exec(resolvedPath);
18527
19087
  if (msysMatch !== null) {
18528
19088
  const drive = (msysMatch[1] ?? "").toUpperCase();
@@ -18541,6 +19101,10 @@ async function runStart(args) {
18541
19101
  serverEnv["AGENT_INSPECTOR_PUBLIC_PORT"] = String(port);
18542
19102
  serverEnv["PROXY_PORT"] = String(port);
18543
19103
  serverEnv["AGENT_INSPECTOR_CAPTURE_MODE"] = captureMode;
19104
+ const workerDir = join7(__dirname2, "workers");
19105
+ if (existsSync6(workerDir)) {
19106
+ serverEnv["AGENT_INSPECTOR_WORKER_DIR"] = workerDir;
19107
+ }
18544
19108
  const serverProcess = spawn(serverCommand.command, serverCommand.args, {
18545
19109
  stdio: background ? ["ignore", "ignore", "ignore"] : ["ignore", "pipe", "pipe"],
18546
19110
  detached: background,
@@ -18559,6 +19123,37 @@ async function runStart(args) {
18559
19123
  });
18560
19124
  }
18561
19125
  const identityProxies = [];
19126
+ let supervisorShutdownPromise = null;
19127
+ const requestSupervisorShutdown = (signal) => {
19128
+ if (supervisorShutdownPromise !== null) return;
19129
+ for (const proxy of identityProxies) stopIdentityProxyAdmission(proxy.server);
19130
+ serverProcess.kill(signal);
19131
+ const childDeadline = readPositiveIntegerEnv("AGENT_INSPECTOR_SHUTDOWN_TIMEOUT_MS") ?? DEFAULT_SHUTDOWN_DEADLINE_MS;
19132
+ let deadline = null;
19133
+ let forced = false;
19134
+ const graceful = Promise.all([
19135
+ serverExit.then(() => void 0),
19136
+ ...identityProxies.map(async (proxy) => await closeIdentityProxyGracefully(proxy.server))
19137
+ ]).then(() => void 0);
19138
+ const timedOut = new Promise((resolve2) => {
19139
+ deadline = setTimeout(() => {
19140
+ forced = true;
19141
+ for (const proxy of identityProxies) forceCloseIdentityProxy(proxy.server);
19142
+ if (serverProcess.exitCode === null) serverProcess.kill("SIGKILL");
19143
+ resolve2();
19144
+ }, childDeadline + SUPERVISOR_SHUTDOWN_GRACE_MS);
19145
+ });
19146
+ supervisorShutdownPromise = Promise.race([graceful, timedOut]).finally(() => {
19147
+ if (deadline !== null) clearTimeout(deadline);
19148
+ }).then(() => {
19149
+ if (forced) process.exitCode = 1;
19150
+ });
19151
+ };
19152
+ const handleSigint = () => requestSupervisorShutdown("SIGINT");
19153
+ const handleSigterm = () => requestSupervisorShutdown("SIGTERM");
19154
+ const currentSupervisorShutdown = () => supervisorShutdownPromise;
19155
+ process.on("SIGINT", handleSigint);
19156
+ process.on("SIGTERM", handleSigterm);
18562
19157
  if (enableIdentityProxy) {
18563
19158
  const primaryProxy = await tryStartIdentityProxy(port, upstreamPort, host, upstreamHost);
18564
19159
  if (primaryProxy !== null) {
@@ -18618,7 +19213,12 @@ async function runStart(args) {
18618
19213
  }
18619
19214
  }
18620
19215
  process.exitCode = await serverExit;
18621
- for (const proxy of identityProxies) {
18622
- await new Promise((resolve2) => proxy.server.close(() => resolve2()));
19216
+ const activeSupervisorShutdown = currentSupervisorShutdown();
19217
+ if (activeSupervisorShutdown !== null) {
19218
+ await activeSupervisorShutdown;
19219
+ } else {
19220
+ await Promise.all(identityProxies.map((proxy) => closeIdentityProxyGracefully(proxy.server)));
18623
19221
  }
19222
+ process.off("SIGINT", handleSigint);
19223
+ process.off("SIGTERM", handleSigterm);
18624
19224
  }