@tonyclaw/agent-inspector 3.0.45 → 3.0.46

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 (338) hide show
  1. package/.output/{nitro.json → backend/nitro.json} +3 -3
  2. package/.output/backend-public/assets/index-D4LX-sw-.js +11 -0
  3. package/.output/cli.js +2326 -516
  4. package/.output/server/_libs/extend.mjs +4 -8
  5. package/.output/server/_libs/jszip.mjs +9 -35
  6. package/.output/server/_libs/react-dom.mjs +2 -3
  7. package/.output/server/_libs/react.mjs +1 -27
  8. package/.output/server/_libs/tanstack__react-router.mjs +1 -329
  9. package/.output/server/_libs/tanstack__router-core.mjs +42 -100
  10. package/.output/server/_libs/use-sync-external-store.mjs +1 -64
  11. package/.output/server/_libs/ws.mjs +1 -1
  12. package/.output/server/_ssr/index.mjs +4 -4
  13. package/.output/server/_ssr/{publicBasePathContext-5264L3rX.mjs → publicBasePathContext-IH2ilOfu.mjs} +52 -24
  14. package/.output/server/_ssr/{router-BJ_ltLim.mjs → router-D18yUq36.mjs} +19770 -19447
  15. package/.output/server/_ssr/{start-C6MjPjxp.mjs → start-COQu_AgD.mjs} +1 -1
  16. package/.output/server/_tanstack-start-manifest_v-DnbdNeun.mjs +4 -0
  17. package/.output/server/index.mjs +5 -145
  18. package/.output/{public/assets/CompareDrawer-B3_TLTJh.js → ui/assets/CompareDrawer-DAUuIJ6G.js} +1 -1
  19. package/.output/{public/assets/InspectorPet-Cpn6hv4P.js → ui/assets/InspectorPet-BRBVjOWI.js} +1 -1
  20. package/.output/ui/assets/ProxyViewerContainer-D7Sq0ctc.js +59 -0
  21. package/.output/{public/assets/ReplayDialog-B3EKVdJk.js → ui/assets/ReplayDialog-nDLsjOhs.js} +1 -1
  22. package/.output/{public/assets/RequestAnatomy-COOQjyUC.js → ui/assets/RequestAnatomy-zd4BDgL0.js} +1 -1
  23. package/.output/ui/assets/ResponseView-AdIXGcSn.js +2 -0
  24. package/.output/ui/assets/StreamingChunkSequence-CesTz8He.js +1 -0
  25. package/.output/{public/assets/_sessionId-zbgYl7ZJ.js → ui/assets/_sessionId-BAfzrhSU.js} +1 -1
  26. package/.output/{public/assets/_sessionId-BJlM3nmt.js → ui/assets/_sessionId-QnBomgPD.js} +1 -1
  27. package/.output/ui/assets/favicon-DYEkHSNl.svg +21 -0
  28. package/.output/{public/assets/index-BIK4bPTv.js → ui/assets/index-BcsdxBAd.js} +1 -1
  29. package/.output/ui/assets/index-BvXp42al.css +1 -0
  30. package/.output/{public/assets/index-D2Qw6Ri2.js → ui/assets/index-D0rtCN9V.js} +1 -1
  31. package/.output/ui/assets/index-DHpwr08Z.js +73 -0
  32. package/.output/{public/assets/index-xF_NY8Yb.js → ui/assets/index-dNuk2dsU.js} +5 -5
  33. package/.output/ui/assets/json-viewer-BvFHglMb.js +1 -0
  34. package/.output/{public/assets/jszip.min-B_rfAUCe.js → ui/assets/jszip.min-B5Z7gNZB.js} +1 -1
  35. package/.output/ui/index.html +229 -0
  36. package/.output/ui/runtime-config.js +2 -0
  37. package/.output/workers/logFinalizer.worker.js +979 -414
  38. package/.output/workers/sessionWorkerEntry.js +979 -414
  39. package/README.md +175 -43
  40. package/docs/instance-control.openapi.yaml +59 -4
  41. package/package.json +25 -5
  42. package/scripts/generate-theme-scales.mjs +3 -3
  43. package/src/{router.tsx → backend/router.tsx} +3 -15
  44. package/src/backend/routes/$.ts +26 -0
  45. package/src/backend/routes/__root.ts +4 -0
  46. package/src/{routes → backend/routes}/api/-instances.ts +3 -3
  47. package/src/{routes → backend/routes}/api/alerts.summary.ts +3 -3
  48. package/src/{routes → backend/routes}/api/alerts.ts +4 -4
  49. package/src/{routes → backend/routes}/api/config.paths.ts +1 -1
  50. package/src/{routes → backend/routes}/api/config.ts +11 -2
  51. package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.help.ts +1 -1
  52. package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.install.ts +1 -1
  53. package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.runner-presets.ts +1 -1
  54. package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.upgrade.ts +1 -1
  55. package/src/{routes → backend/routes}/api/ecosystem.packages.ts +5 -1
  56. package/src/{routes → backend/routes}/api/ecosystem.recipes.$recipeId.run.ts +1 -1
  57. package/src/{routes → backend/routes}/api/ecosystem.tasks.$taskId.ts +1 -1
  58. package/src/{routes → backend/routes}/api/ecosystem.tasks.ts +1 -1
  59. package/src/{routes → backend/routes}/api/groups.$groupId.evidence.ts +2 -2
  60. package/src/{routes → backend/routes}/api/groups.$groupId.sessions.ts +2 -2
  61. package/src/{routes → backend/routes}/api/groups.$groupId.ts +2 -2
  62. package/src/{routes → backend/routes}/api/groups.ts +2 -2
  63. package/src/{routes → backend/routes}/api/health.ts +3 -3
  64. package/src/{routes → backend/routes}/api/instances.$name.connection.ts +1 -1
  65. package/src/{routes → backend/routes}/api/instances.$name.restart.ts +2 -2
  66. package/src/{routes → backend/routes}/api/instances.$name.start.ts +2 -2
  67. package/src/{routes → backend/routes}/api/instances.$name.stop.ts +2 -2
  68. package/src/{routes → backend/routes}/api/instances.$name.ts +1 -1
  69. package/src/{routes → backend/routes}/api/instances.ts +1 -1
  70. package/src/{routes → backend/routes}/api/knowledge.candidates.$candidateId.promote.ts +2 -2
  71. package/src/{routes → backend/routes}/api/knowledge.candidates.$candidateId.ts +2 -2
  72. package/src/{routes → backend/routes}/api/knowledge.candidates.ts +1 -1
  73. package/src/{routes → backend/routes}/api/knowledge.project-context.ts +2 -2
  74. package/src/{routes → backend/routes}/api/knowledge.search.ts +2 -2
  75. package/src/{routes → backend/routes}/api/knowledge.sessions.$sessionId.candidates.ts +3 -3
  76. package/src/{routes → backend/routes}/api/logs.$id.body.ts +6 -6
  77. package/src/{routes → backend/routes}/api/logs.$id.chunks.ts +5 -4
  78. package/src/{routes → backend/routes}/api/logs.$id.replay.ts +12 -11
  79. package/src/{routes → backend/routes}/api/logs.$id.ts +4 -3
  80. package/src/{routes → backend/routes}/api/logs.import.ts +3 -3
  81. package/src/{routes → backend/routes}/api/logs.stream.ts +11 -7
  82. package/src/{routes → backend/routes}/api/logs.ts +17 -9
  83. package/src/{routes → backend/routes}/api/mcp.ts +1 -1
  84. package/src/{routes → backend/routes}/api/models.ts +1 -1
  85. package/src/{routes → backend/routes}/api/pi-agent.ts +2 -2
  86. package/src/{routes → backend/routes}/api/providers.$providerId.model-metadata.ts +5 -5
  87. package/src/{routes → backend/routes}/api/providers.$providerId.test.log.ts +7 -7
  88. package/src/{routes → backend/routes}/api/providers.$providerId.ts +4 -4
  89. package/src/{routes → backend/routes}/api/providers.export.ts +1 -1
  90. package/src/{routes → backend/routes}/api/providers.import.ts +3 -3
  91. package/src/{routes → backend/routes}/api/providers.scan.ts +2 -2
  92. package/src/{routes → backend/routes}/api/providers.ts +9 -4
  93. package/src/{routes → backend/routes}/api/runs.$runId.evidence.ts +2 -2
  94. package/src/{routes → backend/routes}/api/runs.$runId.ts +2 -2
  95. package/src/{routes → backend/routes}/api/runs.ts +4 -4
  96. package/src/backend/routes/api/runtime.ts +46 -0
  97. package/src/{routes → backend/routes}/api/sessions.ts +3 -3
  98. package/src/{routes → backend/routes}/proxy/$.ts +1 -1
  99. package/src/{routes → backend/routes}/readyz.ts +1 -1
  100. package/src/{start.ts → backend/start.ts} +1 -1
  101. package/src/cli/alias.ts +2 -11
  102. package/src/cli/doctor.ts +32 -9
  103. package/src/cli/instance.ts +26 -13
  104. package/src/cli/instanceArgs.ts +30 -11
  105. package/src/cli/instanceControl.ts +0 -2
  106. package/src/cli/instanceModel.ts +9 -17
  107. package/src/cli/instanceRegistry.ts +0 -9
  108. package/src/cli/networkHints.ts +27 -14
  109. package/src/cli/onboard.ts +69 -2
  110. package/src/cli/onboardTarget.ts +89 -40
  111. package/src/cli/templates/codex-skill-onboard.ts +44 -14
  112. package/src/cli/templates/skill-onboard.ts +64 -28
  113. package/src/cli.ts +184 -61
  114. package/src/components/ProxyViewer.tsx +11 -9
  115. package/src/components/ProxyViewerContainer.tsx +12 -11
  116. package/src/components/pi-agent/PiAgentPanel.tsx +2 -2
  117. package/src/components/providers/ProviderCard.tsx +1 -1
  118. package/src/components/providers/ProviderTestResultsView.tsx +1 -1
  119. package/src/components/providers/SettingsDialog.tsx +159 -144
  120. package/src/components/proxy-viewer/ApplicationBar.tsx +2 -1
  121. package/src/components/proxy-viewer/LogEntry.tsx +1 -1
  122. package/src/components/proxy-viewer/log-formats/index.ts +1 -1
  123. package/src/contracts/index.ts +5 -0
  124. package/src/contracts/log.ts +33 -3
  125. package/src/knowledge/types.ts +3 -2
  126. package/src/lib/alertContract.ts +2 -2
  127. package/src/lib/apiClient.ts +30 -7
  128. package/src/lib/apiFormat.ts +29 -0
  129. package/src/lib/basePath.ts +29 -11
  130. package/src/lib/browserRuntimeContract.ts +124 -0
  131. package/src/lib/browserRuntimeResolver.ts +244 -0
  132. package/src/{proxy → lib}/claudeCodeStrip.ts +6 -7
  133. package/src/lib/codingAgentEndpoints.ts +38 -0
  134. package/src/lib/instanceContract.ts +27 -11
  135. package/src/lib/piAgentContract.ts +3 -2
  136. package/src/lib/publicBasePathContext.ts +40 -10
  137. package/src/lib/resourceLimits.ts +2 -0
  138. package/src/lib/runContract.ts +3 -3
  139. package/src/lib/sessionInfoContract.ts +3 -2
  140. package/src/mcp/instanceHandlers.ts +1 -0
  141. package/src/mcp/server.ts +19 -24
  142. package/src/proxy/config.ts +25 -30
  143. package/src/proxy/dataDir.ts +43 -7
  144. package/src/proxy/ecosystemTasks.ts +161 -70
  145. package/src/proxy/formats/registry.ts +6 -21
  146. package/src/proxy/handler.ts +129 -29
  147. package/src/proxy/identityProxy.ts +419 -39
  148. package/src/proxy/jsonlRecovery.ts +554 -0
  149. package/src/proxy/logFinalizer.ts +25 -10
  150. package/src/proxy/logImporter.ts +2 -1
  151. package/src/proxy/logIndex.ts +191 -95
  152. package/src/proxy/logger.ts +77 -14
  153. package/src/proxy/platformCommands.ts +41 -0
  154. package/src/proxy/privateDataPath.ts +310 -88
  155. package/src/proxy/rawStreamCapture.ts +194 -13
  156. package/src/proxy/runtimeAdmission.ts +5 -2
  157. package/src/proxy/runtimeHealth.ts +14 -1
  158. package/src/proxy/runtimeShutdown.ts +11 -1
  159. package/src/proxy/schemas.ts +7 -1
  160. package/src/proxy/sessionArchive.ts +78 -14
  161. package/src/proxy/sqliteLogIndex.ts +65 -2
  162. package/src/proxy/store.ts +141 -34
  163. package/src/proxy/uiAssetServer.ts +106 -0
  164. package/src/proxy/upstream.ts +29 -2
  165. package/src/ui/bootstrap.ts +167 -0
  166. package/src/ui/index.html +38 -0
  167. package/src/ui/logsStreamClient.ts +20 -0
  168. package/src/ui/main.tsx +13 -0
  169. package/src/ui/public/runtime-config.js +2 -0
  170. package/src/ui/router.tsx +13 -0
  171. package/src/ui/routes/__root.tsx +69 -0
  172. package/src/{routes → ui/routes}/index.tsx +2 -3
  173. package/src/{routes → ui/routes}/session/$sessionId.tsx +5 -11
  174. package/src/ui/runtimeDiscovery.ts +50 -0
  175. package/styles/globals.css +1 -0
  176. package/.output/public/assets/ProxyViewerContainer-BEAhuGOJ.js +0 -126
  177. package/.output/public/assets/ResponseView-DBCjkwye.js +0 -2
  178. package/.output/public/assets/StreamingChunkSequence-CuXhDgMo.js +0 -1
  179. package/.output/public/assets/index-BZo_8koU.js +0 -70
  180. package/.output/public/assets/index-ZWschkCh.css +0 -1
  181. package/.output/public/assets/json-viewer-B-Ao6pwW.js +0 -1
  182. package/.output/server/_libs/@radix-ui/react-accessible-icon+[...].mjs +0 -1
  183. package/.output/server/_libs/@radix-ui/react-dismissable-layer+[...].mjs +0 -344
  184. package/.output/server/_libs/@radix-ui/react-navigation-menu+[...].mjs +0 -2
  185. package/.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs +0 -2
  186. package/.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs +0 -2
  187. package/.output/server/_libs/@radix-ui/react-use-callback-ref+[...].mjs +0 -11
  188. package/.output/server/_libs/@radix-ui/react-use-controllable-state+[...].mjs +0 -69
  189. package/.output/server/_libs/@radix-ui/react-use-effect-event+[...].mjs +0 -1
  190. package/.output/server/_libs/@radix-ui/react-use-is-hydrated+[...].mjs +0 -28
  191. package/.output/server/_libs/@radix-ui/react-use-layout-effect+[...].mjs +0 -6
  192. package/.output/server/_libs/@radix-ui/react-visually-hidden+[...].mjs +0 -34
  193. package/.output/server/_libs/aria-hidden.mjs +0 -122
  194. package/.output/server/_libs/bail.mjs +0 -8
  195. package/.output/server/_libs/character-entities.mjs +0 -2130
  196. package/.output/server/_libs/class-variance-authority.mjs +0 -44
  197. package/.output/server/_libs/clsx.mjs +0 -16
  198. package/.output/server/_libs/comma-separated-tokens.mjs +0 -10
  199. package/.output/server/_libs/decode-named-character-reference+[...].mjs +0 -8
  200. package/.output/server/_libs/dequal.mjs +0 -27
  201. package/.output/server/_libs/detect-node-es.mjs +0 -1
  202. package/.output/server/_libs/devlop.mjs +0 -8
  203. package/.output/server/_libs/diff.mjs +0 -320
  204. package/.output/server/_libs/estree-util-is-identifier-name.mjs +0 -11
  205. package/.output/server/_libs/floating-ui__core.mjs +0 -725
  206. package/.output/server/_libs/floating-ui__dom.mjs +0 -622
  207. package/.output/server/_libs/floating-ui__react-dom.mjs +0 -292
  208. package/.output/server/_libs/floating-ui__utils.mjs +0 -320
  209. package/.output/server/_libs/get-nonce.mjs +0 -9
  210. package/.output/server/_libs/hast-util-to-jsx-runtime.mjs +0 -388
  211. package/.output/server/_libs/hast-util-whitespace.mjs +0 -10
  212. package/.output/server/_libs/html-url-attributes.mjs +0 -26
  213. package/.output/server/_libs/inline-style-parser.mjs +0 -142
  214. package/.output/server/_libs/is-plain-obj.mjs +0 -10
  215. package/.output/server/_libs/lucide-react.mjs +0 -1027
  216. package/.output/server/_libs/mdast-util-from-markdown.mjs +0 -717
  217. package/.output/server/_libs/mdast-util-to-hast.mjs +0 -710
  218. package/.output/server/_libs/mdast-util-to-string.mjs +0 -38
  219. package/.output/server/_libs/micromark-core-commonmark.mjs +0 -2259
  220. package/.output/server/_libs/micromark-factory-destination.mjs +0 -94
  221. package/.output/server/_libs/micromark-factory-label.mjs +0 -63
  222. package/.output/server/_libs/micromark-factory-space.mjs +0 -24
  223. package/.output/server/_libs/micromark-factory-title.mjs +0 -65
  224. package/.output/server/_libs/micromark-factory-whitespace.mjs +0 -22
  225. package/.output/server/_libs/micromark-util-character.mjs +0 -44
  226. package/.output/server/_libs/micromark-util-chunked.mjs +0 -36
  227. package/.output/server/_libs/micromark-util-classify-character+[...].mjs +0 -12
  228. package/.output/server/_libs/micromark-util-combine-extensions+[...].mjs +0 -41
  229. package/.output/server/_libs/micromark-util-decode-numeric-character-reference+[...].mjs +0 -19
  230. package/.output/server/_libs/micromark-util-decode-string.mjs +0 -21
  231. package/.output/server/_libs/micromark-util-encode.mjs +0 -1
  232. package/.output/server/_libs/micromark-util-html-tag-name.mjs +0 -69
  233. package/.output/server/_libs/micromark-util-normalize-identifier+[...].mjs +0 -6
  234. package/.output/server/_libs/micromark-util-resolve-all.mjs +0 -15
  235. package/.output/server/_libs/micromark-util-sanitize-uri.mjs +0 -41
  236. package/.output/server/_libs/micromark-util-subtokenize.mjs +0 -346
  237. package/.output/server/_libs/micromark.mjs +0 -906
  238. package/.output/server/_libs/property-information.mjs +0 -1209
  239. package/.output/server/_libs/radix-ui.mjs +0 -1
  240. package/.output/server/_libs/radix-ui__number.mjs +0 -6
  241. package/.output/server/_libs/radix-ui__primitive.mjs +0 -11
  242. package/.output/server/_libs/radix-ui__react-accordion.mjs +0 -1
  243. package/.output/server/_libs/radix-ui__react-alert-dialog.mjs +0 -1
  244. package/.output/server/_libs/radix-ui__react-arrow.mjs +0 -23
  245. package/.output/server/_libs/radix-ui__react-aspect-ratio.mjs +0 -1
  246. package/.output/server/_libs/radix-ui__react-avatar.mjs +0 -1
  247. package/.output/server/_libs/radix-ui__react-checkbox.mjs +0 -1
  248. package/.output/server/_libs/radix-ui__react-collapsible.mjs +0 -144
  249. package/.output/server/_libs/radix-ui__react-collection.mjs +0 -69
  250. package/.output/server/_libs/radix-ui__react-compose-refs.mjs +0 -38
  251. package/.output/server/_libs/radix-ui__react-context-menu.mjs +0 -1
  252. package/.output/server/_libs/radix-ui__react-context.mjs +0 -64
  253. package/.output/server/_libs/radix-ui__react-dialog.mjs +0 -280
  254. package/.output/server/_libs/radix-ui__react-direction.mjs +0 -9
  255. package/.output/server/_libs/radix-ui__react-dropdown-menu.mjs +0 -1
  256. package/.output/server/_libs/radix-ui__react-focus-guards.mjs +0 -39
  257. package/.output/server/_libs/radix-ui__react-focus-scope.mjs +0 -210
  258. package/.output/server/_libs/radix-ui__react-form.mjs +0 -1
  259. package/.output/server/_libs/radix-ui__react-hover-card.mjs +0 -1
  260. package/.output/server/_libs/radix-ui__react-id.mjs +0 -14
  261. package/.output/server/_libs/radix-ui__react-label.mjs +0 -1
  262. package/.output/server/_libs/radix-ui__react-menu.mjs +0 -1
  263. package/.output/server/_libs/radix-ui__react-menubar.mjs +0 -1
  264. package/.output/server/_libs/radix-ui__react-popover.mjs +0 -1
  265. package/.output/server/_libs/radix-ui__react-popper.mjs +0 -340
  266. package/.output/server/_libs/radix-ui__react-portal.mjs +0 -16
  267. package/.output/server/_libs/radix-ui__react-presence.mjs +0 -173
  268. package/.output/server/_libs/radix-ui__react-primitive.mjs +0 -42
  269. package/.output/server/_libs/radix-ui__react-progress.mjs +0 -1
  270. package/.output/server/_libs/radix-ui__react-radio-group.mjs +0 -1
  271. package/.output/server/_libs/radix-ui__react-roving-focus.mjs +0 -235
  272. package/.output/server/_libs/radix-ui__react-scroll-area.mjs +0 -731
  273. package/.output/server/_libs/radix-ui__react-select.mjs +0 -1207
  274. package/.output/server/_libs/radix-ui__react-separator.mjs +0 -28
  275. package/.output/server/_libs/radix-ui__react-slider.mjs +0 -1
  276. package/.output/server/_libs/radix-ui__react-slot.mjs +0 -132
  277. package/.output/server/_libs/radix-ui__react-switch.mjs +0 -1
  278. package/.output/server/_libs/radix-ui__react-tabs.mjs +0 -194
  279. package/.output/server/_libs/radix-ui__react-toast.mjs +0 -2
  280. package/.output/server/_libs/radix-ui__react-toggle-group.mjs +0 -1
  281. package/.output/server/_libs/radix-ui__react-toggle.mjs +0 -1
  282. package/.output/server/_libs/radix-ui__react-toolbar.mjs +0 -1
  283. package/.output/server/_libs/radix-ui__react-tooltip.mjs +0 -498
  284. package/.output/server/_libs/radix-ui__react-use-previous.mjs +0 -14
  285. package/.output/server/_libs/radix-ui__react-use-size.mjs +0 -39
  286. package/.output/server/_libs/react-markdown.mjs +0 -235
  287. package/.output/server/_libs/react-remove-scroll-bar.mjs +0 -82
  288. package/.output/server/_libs/react-remove-scroll.mjs +0 -328
  289. package/.output/server/_libs/react-style-singleton.mjs +0 -69
  290. package/.output/server/_libs/remark-parse.mjs +0 -19
  291. package/.output/server/_libs/remark-rehype.mjs +0 -21
  292. package/.output/server/_libs/space-separated-tokens.mjs +0 -6
  293. package/.output/server/_libs/style-to-js.mjs +0 -72
  294. package/.output/server/_libs/style-to-object.mjs +0 -38
  295. package/.output/server/_libs/swr.mjs +0 -947
  296. package/.output/server/_libs/tailwind-merge.mjs +0 -3255
  297. package/.output/server/_libs/tanstack__react-virtual.mjs +0 -130
  298. package/.output/server/_libs/tanstack__virtual-core.mjs +0 -1319
  299. package/.output/server/_libs/three.mjs +0 -26016
  300. package/.output/server/_libs/trim-lines.mjs +0 -41
  301. package/.output/server/_libs/trough.mjs +0 -85
  302. package/.output/server/_libs/tslib.mjs +0 -1
  303. package/.output/server/_libs/ungap__structured-clone.mjs +0 -212
  304. package/.output/server/_libs/unified.mjs +0 -661
  305. package/.output/server/_libs/unist-util-is.mjs +0 -100
  306. package/.output/server/_libs/unist-util-position.mjs +0 -27
  307. package/.output/server/_libs/unist-util-stringify-position.mjs +0 -27
  308. package/.output/server/_libs/unist-util-visit-parents.mjs +0 -82
  309. package/.output/server/_libs/unist-util-visit.mjs +0 -24
  310. package/.output/server/_libs/use-callback-ref.mjs +0 -66
  311. package/.output/server/_libs/use-sidecar.mjs +0 -106
  312. package/.output/server/_libs/vfile-message.mjs +0 -138
  313. package/.output/server/_libs/vfile.mjs +0 -467
  314. package/.output/server/_sessionId-CJ99gZFV.mjs +0 -84
  315. package/.output/server/_sessionId-DEjScBpK.mjs +0 -146
  316. package/.output/server/_ssr/CompareDrawer-DtOIMeQB.mjs +0 -1060
  317. package/.output/server/_ssr/InspectorPet-CXc4oCBQ.mjs +0 -938
  318. package/.output/server/_ssr/ProxyViewerContainer-DVPlwfkt.mjs +0 -17632
  319. package/.output/server/_ssr/ReplayDialog-BQEA95qC.mjs +0 -725
  320. package/.output/server/_ssr/RequestAnatomy-BMyd8B7Q.mjs +0 -837
  321. package/.output/server/_ssr/ResponseView-qrTaEIM8.mjs +0 -757
  322. package/.output/server/_ssr/StreamingChunkSequence-BFTL8mIy.mjs +0 -274
  323. package/.output/server/_ssr/index-Bcj1Hcy4.mjs +0 -135
  324. package/.output/server/_ssr/index-DW-M7XUG.mjs +0 -84
  325. package/.output/server/_ssr/json-viewer-DiI0Yjsn.mjs +0 -483
  326. package/.output/server/_tanstack-start-manifest_v-C6pXXX9R.mjs +0 -4
  327. package/.output/server/node_modules/tslib/modules/index.js +0 -70
  328. package/.output/server/node_modules/tslib/modules/package.json +0 -3
  329. package/.output/server/node_modules/tslib/package.json +0 -47
  330. package/.output/server/node_modules/tslib/tslib.js +0 -484
  331. package/.output/server/package.json +0 -9
  332. package/src/routes/__root.tsx +0 -170
  333. /package/.output/{public → ui}/assets/agent-inspector-58K1_MsC.ico +0 -0
  334. /package/.output/{public → ui}/assets/alibaba-TTwafVwX.svg +0 -0
  335. /package/.output/{public → ui}/assets/minimax-BPMzvuL-.jpeg +0 -0
  336. /package/.output/{public → ui}/assets/qwen-mMn3f5ul.webp +0 -0
  337. /package/.output/{public → ui}/assets/zhipuai-BPNAnxo-.svg +0 -0
  338. /package/src/{routes → backend/routes}/livez.ts +0 -0
@@ -1,7 +1,13 @@
1
1
  import { z } from "zod";
2
+ import { normalizePublicBasePath } from "./basePath";
2
3
 
3
4
  export const INSTANCE_API_SCHEMA_VERSION = 1;
4
5
 
6
+ function isCanonicalPublicBasePath(value: string): boolean {
7
+ const normalized = normalizePublicBasePath(value);
8
+ return normalized !== null && (normalized.length === 0 ? "/" : normalized) === value;
9
+ }
10
+
5
11
  export const InstanceStateSchema = z.enum(["running", "stopped", "conflict"]);
6
12
  export const InstanceStateReasonSchema = z.enum(["identity_mismatch", "port_conflict"]);
7
13
 
@@ -35,18 +41,35 @@ export const InstanceStatusOutputSchema = z
35
41
  host: z.string().min(1),
36
42
  publicPort: z.number().int().min(1).max(65_535),
37
43
  upstreamPort: z.number().int().min(1).max(65_535),
38
- basePath: z.string().startsWith("/"),
44
+ basePath: z.string().refine(isCanonicalPublicBasePath, "Invalid public Base Path"),
39
45
  captureMode: z.enum(["simple", "full"]),
46
+ uiEnabled: z.boolean(),
40
47
  dataDir: z.string().min(1),
41
48
  supervisorPid: z.number().int().positive().nullable(),
42
49
  urls: InstanceUrlsSchema,
43
50
  })
44
51
  .strict();
45
52
 
53
+ export const InstanceConnectionSurfacesSchema = z
54
+ .object({
55
+ ui: z
56
+ .object({
57
+ enabled: z.boolean(),
58
+ baseUrl: z.url().nullable(),
59
+ })
60
+ .strict(),
61
+ control: z.object({ baseUrl: z.url() }).strict(),
62
+ agent: z.object({ baseUrl: z.url() }).strict(),
63
+ })
64
+ .strict();
65
+
46
66
  export const InstanceConnectionOutputSchema = z
47
67
  .object({
48
68
  schemaVersion: z.literal(INSTANCE_API_SCHEMA_VERSION),
69
+ uiEnabled: z.boolean(),
49
70
  instance: InstanceStatusOutputSchema,
71
+ surfaces: InstanceConnectionSurfacesSchema,
72
+ /** @deprecated Use surfaces.control.baseUrl or surfaces.agent.baseUrl. */
50
73
  baseUrl: z.url(),
51
74
  endpoints: InstanceUrlsSchema,
52
75
  environment: z
@@ -82,12 +105,8 @@ export const InstanceStartRequestSchema = z
82
105
  .min(1)
83
106
  .max(2_048)
84
107
  .refine(
85
- (value) =>
86
- value.startsWith("/") &&
87
- !value.includes("?") &&
88
- !value.includes("#") &&
89
- !value.includes("\\"),
90
- "Base Path must be an absolute URL path without query, hash, or backslash",
108
+ (value) => normalizePublicBasePath(value) !== null,
109
+ "Base Path must use URL-safe path segments without traversal, encoded separators, or markup characters",
91
110
  )
92
111
  .optional(),
93
112
  host: z
@@ -105,6 +124,7 @@ export const InstanceStartRequestSchema = z
105
124
  )
106
125
  .optional(),
107
126
  captureMode: z.enum(["simple", "full"]).optional(),
127
+ uiEnabled: z.boolean().optional(),
108
128
  dataDir: z.string().trim().min(1).max(32_767).optional(),
109
129
  })
110
130
  .strict();
@@ -175,15 +195,11 @@ export const InstanceErrorOutputSchema = z
175
195
  })
176
196
  .strict();
177
197
 
178
- export type InstanceState = z.infer<typeof InstanceStateSchema>;
179
- export type InstanceStateReason = z.infer<typeof InstanceStateReasonSchema>;
180
198
  export type InstanceStatusOutput = z.infer<typeof InstanceStatusOutputSchema>;
181
199
  export type InstanceConnectionOutput = z.infer<typeof InstanceConnectionOutputSchema>;
182
200
  export type InstanceStartRequest = z.infer<typeof InstanceStartRequestSchema>;
183
- export type InstanceOperation = z.infer<typeof InstanceOperationSchema>;
184
201
  export type InstanceOperationOutcome = z.infer<typeof InstanceOperationOutcomeSchema>;
185
202
  export type InstanceOperationOutput = z.infer<typeof InstanceOperationOutputSchema>;
186
- export type InstanceListOutput = z.infer<typeof InstanceListOutputSchema>;
187
203
  export type InstancePublicErrorCode = z.infer<typeof InstancePublicErrorCodeSchema>;
188
204
  export type InstancePublicError = z.infer<typeof InstancePublicErrorSchema>;
189
205
  export type InstanceErrorOutput = z.infer<typeof InstanceErrorOutputSchema>;
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { LogIdSchema } from "../contracts";
2
3
 
3
4
  export const PiAgentHistoryMessageSchema = z.object({
4
5
  role: z.enum(["user", "assistant"]),
@@ -55,9 +56,9 @@ export const PiAgentQueryStatsSchema = z.object({
55
56
  export const PiAgentEvidenceSchema = z.object({
56
57
  source: PiAgentEvidenceSourceSchema,
57
58
  id: z.string(),
58
- logId: z.number().int().positive().optional(),
59
+ logId: LogIdSchema.optional(),
59
60
  knowledgeId: z.string().optional(),
60
- logIds: z.array(z.number().int().positive()).optional(),
61
+ logIds: z.array(LogIdSchema).optional(),
61
62
  timestamp: z.string().optional(),
62
63
  sessionId: z.string().optional(),
63
64
  model: z.string().optional(),
@@ -1,9 +1,10 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import {
3
3
  AGENT_INSPECTOR_PUBLIC_BASE_PATH_HEADER,
4
+ AGENT_INSPECTOR_PUBLIC_ORIGIN_HEADER,
4
5
  AGENT_INSPECTOR_PUBLIC_PORT_HEADER,
5
6
  getConfiguredBasePath,
6
- normalizeBasePath,
7
+ normalizePublicBasePath,
7
8
  } from "./basePath";
8
9
  import { getCurrentPort } from "./serverPort";
9
10
 
@@ -11,17 +12,12 @@ type BasePathEnv = Readonly<Record<string, string | undefined>>;
11
12
 
12
13
  type PublicRequestContext = {
13
14
  basePath: string;
15
+ publicOrigin: string | null;
14
16
  publicPort: number | null;
15
17
  };
16
18
 
17
19
  const publicRequestStorage = new AsyncLocalStorage<PublicRequestContext>();
18
20
 
19
- function isSafePublicBasePath(value: string): boolean {
20
- if (value.length === 0) return true;
21
- if (!/^\/[A-Za-z0-9._~-]+(?:\/[A-Za-z0-9._~-]+)*$/u.test(value)) return false;
22
- return !value.split("/").some((segment) => segment === "." || segment === "..");
23
- }
24
-
25
21
  /**
26
22
  * Resolve the public Base Path attached by the trusted ingress. Invalid or
27
23
  * missing values fall back to the process's canonical Base Path.
@@ -29,8 +25,8 @@ function isSafePublicBasePath(value: string): boolean {
29
25
  export function resolveRequestPublicBasePath(request: Request, env?: BasePathEnv): string {
30
26
  const rawValue = request.headers.get(AGENT_INSPECTOR_PUBLIC_BASE_PATH_HEADER);
31
27
  if (rawValue !== null) {
32
- const normalized = normalizeBasePath(rawValue);
33
- if (isSafePublicBasePath(normalized)) return normalized;
28
+ const normalized = normalizePublicBasePath(rawValue);
29
+ if (normalized !== null) return normalized;
34
30
  }
35
31
  return getConfiguredBasePath(env ?? process.env);
36
32
  }
@@ -59,6 +55,29 @@ export function resolveRequestPublicPort(request: Request): number | null {
59
55
  }
60
56
  }
61
57
 
58
+ function normalizedPublicOrigin(value: string): string | null {
59
+ try {
60
+ const parsed = new URL(value);
61
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
62
+ if (parsed.username.length > 0 || parsed.password.length > 0) return null;
63
+ if (parsed.pathname !== "/" || parsed.search.length > 0 || parsed.hash.length > 0) return null;
64
+ return parsed.origin;
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+
70
+ /** Resolve the public origin set by trusted ingress without consulting forwarded headers. */
71
+ export function resolveRequestPublicOrigin(request: Request): string | null {
72
+ const headerOrigin = request.headers.get(AGENT_INSPECTOR_PUBLIC_ORIGIN_HEADER);
73
+ if (headerOrigin !== null) return normalizedPublicOrigin(headerOrigin);
74
+ try {
75
+ return normalizedPublicOrigin(new URL(request.url).origin);
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+
62
81
  /** Return the originating request's public Base Path, or the canonical path outside a request. */
63
82
  export function getPublicBasePath(env?: BasePathEnv): string {
64
83
  return publicRequestStorage.getStore()?.basePath ?? getConfiguredBasePath(env ?? process.env);
@@ -69,12 +88,21 @@ export function getPublicPort(): number {
69
88
  return publicRequestStorage.getStore()?.publicPort ?? getCurrentPort();
70
89
  }
71
90
 
91
+ /** Return the trusted public origin, with a loopback fallback outside an ingress request. */
92
+ export function getPublicOrigin(): string {
93
+ return (
94
+ publicRequestStorage.getStore()?.publicOrigin ?? `http://localhost:${String(getCurrentPort())}`
95
+ );
96
+ }
97
+
72
98
  /** Run work with an explicitly selected public Base Path. Primarily useful for internal calls. */
73
99
  export function runWithPublicBasePath<T>(basePath: string, callback: () => T): T {
74
100
  const current = publicRequestStorage.getStore();
101
+ const normalized = normalizePublicBasePath(basePath) ?? getConfiguredBasePath(process.env);
75
102
  return publicRequestStorage.run(
76
103
  {
77
- basePath: normalizeBasePath(basePath),
104
+ basePath: normalized,
105
+ publicOrigin: current?.publicOrigin ?? null,
78
106
  publicPort: current?.publicPort ?? null,
79
107
  },
80
108
  callback,
@@ -89,6 +117,7 @@ export function runWithPublicPort<T>(publicPort: number, callback: () => T): T {
89
117
  return publicRequestStorage.run(
90
118
  {
91
119
  basePath: current?.basePath ?? getConfiguredBasePath(process.env),
120
+ publicOrigin: current?.publicOrigin ?? null,
92
121
  publicPort: normalizedPort,
93
122
  },
94
123
  callback,
@@ -100,6 +129,7 @@ export function runWithRequestPublicBasePath<T>(request: Request, callback: () =
100
129
  return publicRequestStorage.run(
101
130
  {
102
131
  basePath: resolveRequestPublicBasePath(request),
132
+ publicOrigin: resolveRequestPublicOrigin(request),
103
133
  publicPort: resolveRequestPublicPort(request),
104
134
  },
105
135
  callback,
@@ -9,6 +9,7 @@ export const MAX_PROVIDER_MODEL_METADATA = 64;
9
9
  export const MAX_SSE_CLIENTS = 32;
10
10
  export const MAX_ACTIVE_PROVIDER_TESTS = 2;
11
11
  export const MAX_PROVIDER_TEST_CONCURRENCY = 4;
12
+ export const MAX_ACTIVE_PROXY_REQUESTS = 16;
12
13
 
13
14
  export const ResourceLimitErrorCodeSchema = z.enum([
14
15
  "invalid-page-request",
@@ -16,6 +17,7 @@ export const ResourceLimitErrorCodeSchema = z.enum([
16
17
  "provider-model-limit-exceeded",
17
18
  "sse-capacity-exceeded",
18
19
  "provider-test-capacity-exceeded",
20
+ "proxy-capacity-exceeded",
19
21
  ]);
20
22
 
21
23
  export const ResourceLimitStatusSchema = z.union([
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { JsonValueSchema } from "../contracts";
2
+ import { JsonValueSchema, LogIdSchema } from "../contracts";
3
3
  import { SessionInfoSchema } from "./sessionInfoContract";
4
4
 
5
5
  export const InspectorRunStatusSchema = z.enum([
@@ -33,7 +33,7 @@ export const RunTimelineEventSchema = z.object({
33
33
  severity: RunTimelineEventSeveritySchema,
34
34
  title: z.string(),
35
35
  details: z.string(),
36
- logId: z.number().int().positive().nullable(),
36
+ logId: LogIdSchema.nullable(),
37
37
  status: z.number().nullable(),
38
38
  model: z.string().nullable(),
39
39
  provider: z.string().nullable(),
@@ -62,7 +62,7 @@ export const FailureClassificationSchema = z.object({
62
62
  severity: z.enum(["none", "low", "medium", "high"]),
63
63
  category: FailureCategorySchema,
64
64
  summary: z.string(),
65
- evidenceLogIds: z.array(z.number().int().positive()),
65
+ evidenceLogIds: z.array(LogIdSchema),
66
66
  hints: z.array(z.string()),
67
67
  });
68
68
 
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { LogIdSchema } from "../contracts";
2
3
 
3
4
  export const SessionTokenUsageSchema = z.object({
4
5
  input: z.number().int().nonnegative(),
@@ -9,7 +10,7 @@ export const SessionTokenUsageSchema = z.object({
9
10
  });
10
11
 
11
12
  export const SessionLogSummarySchema = z.object({
12
- id: z.number().int().positive(),
13
+ id: LogIdSchema,
13
14
  sessionLogNumber: z.number().int().positive().optional(),
14
15
  timestamp: z.string(),
15
16
  provider: z.string().nullable(),
@@ -57,7 +58,7 @@ export const SessionInfoSchema = z.object({
57
58
  models: z.array(z.string()),
58
59
  providers: z.array(z.string()),
59
60
  tokenUsage: SessionTokenUsageSchema,
60
- lastLogId: z.number().nullable(),
61
+ lastLogId: LogIdSchema.nullable(),
61
62
  lastModel: z.string().nullable(),
62
63
  clientUserAgent: z.string().nullable().optional(),
63
64
  clientPid: z.number().nullable(),
@@ -90,6 +90,7 @@ function lifecycleRequest(input: InstanceLifecycleToolInput): InstanceStartReque
90
90
  basePath: input.basePath,
91
91
  host: input.host,
92
92
  captureMode: input.captureMode,
93
+ uiEnabled: input.uiEnabled,
93
94
  dataDir: input.dataDir,
94
95
  };
95
96
  }
package/src/mcp/server.ts CHANGED
@@ -36,10 +36,10 @@ import { InspectorRunStatusSchema, UpdateInspectorRunInputSchema } from "../lib/
36
36
  import { withBasePath } from "../lib/basePath";
37
37
  import {
38
38
  getPublicBasePath,
39
- getPublicPort,
39
+ getPublicOrigin,
40
40
  runWithRequestPublicBasePath,
41
41
  } from "../lib/publicBasePathContext";
42
- import { JsonValueSchema } from "../contracts";
42
+ import { JsonValueSchema, LogIdSchema, parseLogId } from "../contracts";
43
43
  import { InstanceListOutputSchema } from "../lib/instanceContract";
44
44
  import {
45
45
  MAX_PROVIDER_MODEL_NAME_CHARS,
@@ -335,7 +335,15 @@ const UpdateProviderInputSchema = z.object({
335
335
  });
336
336
 
337
337
  const LogResourceListResponseSchema = z.object({
338
- logs: z.array(z.object({ id: z.number().int().positive() })),
338
+ logs: z.array(z.object({ id: LogIdSchema })),
339
+ });
340
+
341
+ export const McpLogIdInputSchema = z.object({
342
+ id: LogIdSchema.describe("The log id."),
343
+ });
344
+
345
+ export const McpOptionalLogIdInputSchema = z.object({
346
+ id: LogIdSchema.optional().describe("Optional log id to inspect."),
339
347
  });
340
348
 
341
349
  const ProcessResourceListResponseSchema = z.object({
@@ -361,10 +369,7 @@ function jsonResource(uri: URL, data: unknown): ReadResourceResult {
361
369
  }
362
370
 
363
371
  function mcpEndpointUrl(): string {
364
- return `http://localhost:${String(getPublicPort())}${withBasePath(
365
- "/api/mcp",
366
- getPublicBasePath(),
367
- )}`;
372
+ return `${getPublicOrigin()}${withBasePath("/api/mcp", getPublicBasePath())}`;
368
373
  }
369
374
 
370
375
  function mcpHealth(): Record<string, unknown> {
@@ -968,9 +973,7 @@ function registerTools(server: McpServer): void {
968
973
  title: "Get a single captured log by id",
969
974
  description:
970
975
  "Returns the full CapturedLog for the given id, including rawRequestBody and responseText with no truncation. SSE streaming chunks are NOT included — use inspector_get_log_chunks for those. Returns an error if the id does not exist.",
971
- inputSchema: z.object({
972
- id: z.number().int().positive().describe("The log id."),
973
- }),
976
+ inputSchema: McpLogIdInputSchema,
974
977
  },
975
978
  ({ id }) => safeCall(() => getLogImpl(callApi, id)),
976
979
  );
@@ -981,9 +984,7 @@ function registerTools(server: McpServer): void {
981
984
  title: "Get SSE streaming chunks for a captured log",
982
985
  description:
983
986
  "Returns the SSE chunks array for a streaming log, in the order they were received. Returns an error if the log has no chunks (i.e. was non-streaming or the log id is unknown).",
984
- inputSchema: z.object({
985
- id: z.number().int().positive().describe("The log id."),
986
- }),
987
+ inputSchema: McpLogIdInputSchema,
987
988
  },
988
989
  ({ id }) => safeCall(() => getLogChunksImpl(callApi, id)),
989
990
  );
@@ -994,9 +995,7 @@ function registerTools(server: McpServer): void {
994
995
  title: "Get captured log headers",
995
996
  description:
996
997
  "Returns header-focused evidence for one log without large request/response bodies: raw headers, upstream headers, user-agent, origin, client PID/port, session id, model, and API path.",
997
- inputSchema: z.object({
998
- id: z.number().int().positive().describe("The log id."),
999
- }),
998
+ inputSchema: McpLogIdInputSchema,
1000
999
  },
1001
1000
  ({ id }) => safeCall(() => getLogHeadersImpl(callApi, id)),
1002
1001
  );
@@ -1174,9 +1173,7 @@ function registerTools(server: McpServer): void {
1174
1173
  title: "Diagnose capture detail availability",
1175
1174
  description:
1176
1175
  "Explains why headers or bodies may be missing for the current runtime or a specific log, including captureMode, compact body mode, rawHeaders presence, and next-step recommendations.",
1177
- inputSchema: z.object({
1178
- id: z.number().int().positive().optional().describe("Optional log id to inspect."),
1179
- }),
1176
+ inputSchema: McpOptionalLogIdInputSchema,
1180
1177
  },
1181
1178
  (args) => safeCall(() => diagnoseCaptureImpl(callApi, args)),
1182
1179
  );
@@ -1504,9 +1501,7 @@ function registerTools(server: McpServer): void {
1504
1501
  title: "Replay a captured log against its provider",
1505
1502
  description:
1506
1503
  "Re-sends the captured request body to the upstream LLM and returns the response summary: success flag, status, responseText, input/output token counts, elapsedMs, and whether the response was streaming. Useful for re-running a request after a fix or a transient failure. Returns an error if the log id is unknown or the upstream call fails.",
1507
- inputSchema: z.object({
1508
- id: z.number().int().positive().describe("The log id to replay."),
1509
- }),
1504
+ inputSchema: McpLogIdInputSchema,
1510
1505
  },
1511
1506
  (args) => safeWriteCall("inspector_replay_log", () => replayLogImpl(callApi, args)),
1512
1507
  );
@@ -1815,8 +1810,8 @@ function registerResources(server: McpServer): void {
1815
1810
  },
1816
1811
  async (uri, variables) => {
1817
1812
  const id = variableString(variables, "id");
1818
- const parsed = id === null ? Number.NaN : Number(id);
1819
- if (!Number.isInteger(parsed) || parsed <= 0) {
1813
+ const parsed = id === null ? null : parseLogId(id);
1814
+ if (parsed === null) {
1820
1815
  return textResource(uri, "text/plain", "Invalid log id");
1821
1816
  }
1822
1817
  const result = await getLogImpl(callApi, parsed);
@@ -1,11 +1,4 @@
1
- import {
2
- writeFileSync,
3
- readFileSync,
4
- unlinkSync,
5
- existsSync,
6
- renameSync,
7
- copyFileSync,
8
- } from "node:fs";
1
+ import { writeFileSync, readFileSync, unlinkSync, existsSync, renameSync } from "node:fs";
9
2
  import { join } from "node:path";
10
3
  import { logger } from "./logger";
11
4
  import { getDataDir } from "./dataDir";
@@ -128,19 +121,19 @@ export function getConfig(): RuntimeConfig {
128
121
  }
129
122
 
130
123
  /**
131
- * Applies a partial update to the in-memory config and persists to disk.
132
- * Returns the updated full config. Persistence failures are logged but
133
- * do not throw the in-memory value is always updated so the caller
134
- * sees a consistent response.
124
+ * Applies a partial update after atomically persisting it to disk.
125
+ * Returns the updated full config on success or null when persistence
126
+ * fails. A failed write leaves both the in-memory value and the existing
127
+ * on-disk value unchanged.
135
128
  */
136
- export function setConfig(patch: Partial<MutableRuntimeConfig>): RuntimeConfig {
129
+ export function setConfig(patch: Partial<MutableRuntimeConfig>): RuntimeConfig | null {
137
130
  const next: RuntimeConfig = {
138
131
  ...currentConfig,
139
132
  ...patch,
140
133
  captureMode: currentConfig.captureMode,
141
134
  };
135
+ if (!persistConfig(next)) return null;
142
136
  currentConfig = next;
143
- persistConfig(next);
144
137
  return next;
145
138
  }
146
139
 
@@ -164,8 +157,10 @@ export function _rebootForTests(): void {
164
157
  // --- Atomic write ---
165
158
 
166
159
  /**
167
- * Atomic write: temp file + rename. Mirrors the pattern in chunkStorage.ts.
168
- * Returns true on success, false on failure (logged but not thrown).
160
+ * Atomic write: secure temp file + rename in the same directory.
161
+ * Returns true on success, false on failure (logged but not thrown). There is
162
+ * deliberately no copy fallback because replacing the target through a copy
163
+ * could expose a partially written config after a crash.
169
164
  */
170
165
  function persistConfig(value: RuntimeConfig): boolean {
171
166
  const dir = getDataDir();
@@ -181,13 +176,15 @@ function persistConfig(value: RuntimeConfig): boolean {
181
176
  writeFileSync(tempPath, JSON.stringify(toPersistedConfig(value)), {
182
177
  encoding: "utf-8",
183
178
  mode: PRIVATE_FILE_MODE,
179
+ flush: true,
184
180
  });
185
181
  if (!securePrivateFileSync(tempPath)) {
186
- unlinkSync(tempPath);
182
+ removeConfigTempFile(tempPath);
187
183
  logger.error(`[config] Failed to secure config temp file ${tempPath}`);
188
184
  return false;
189
185
  }
190
186
  } catch (err) {
187
+ removeConfigTempFile(tempPath);
191
188
  logger.error(`[config] Failed to write config temp file: ${String(err)}`);
192
189
  return false;
193
190
  }
@@ -196,19 +193,17 @@ function persistConfig(value: RuntimeConfig): boolean {
196
193
  renameSync(tempPath, targetPath);
197
194
  return true;
198
195
  } catch (err) {
199
- logger.warn(`[config] Rename failed, falling back to copy+delete: ${String(err)}`);
200
- try {
201
- copyFileSync(tempPath, targetPath);
202
- if (!securePrivateFileSync(targetPath)) {
203
- logger.error(`[config] Failed to secure config file ${targetPath}`);
204
- return false;
205
- }
206
- unlinkSync(tempPath);
207
- return true;
208
- } catch (copyErr) {
209
- logger.error(`[config] Failed to copy config file: ${String(copyErr)}`);
210
- return false;
211
- }
196
+ removeConfigTempFile(tempPath);
197
+ logger.error(`[config] Failed to atomically replace config file: ${String(err)}`);
198
+ return false;
199
+ }
200
+ }
201
+
202
+ function removeConfigTempFile(tempPath: string): void {
203
+ try {
204
+ unlinkSync(tempPath);
205
+ } catch {
206
+ // Best effort. A stale temp path is never read as runtime configuration.
212
207
  }
213
208
  }
214
209
 
@@ -1,6 +1,10 @@
1
- import { cpSync, existsSync, rmSync } from "node:fs";
1
+ import { cpSync, existsSync, readdirSync, renameSync, rmSync } from "node:fs";
2
2
  import { isAbsolute, join } from "node:path";
3
- import { ensurePrivateDirectorySync, securePrivateTreeSync } from "./privateDataPath";
3
+ import {
4
+ ensurePrivateDirectorySync,
5
+ isPathTreeFreeOfIndirectionSync,
6
+ securePrivateTreeSync,
7
+ } from "./privateDataPath";
4
8
 
5
9
  const CURRENT_DATA_DIR_NAME = ".agent-inspector";
6
10
  const LEGACY_DATA_DIR_NAMES = [".llm-inspector", ".llm-agent"];
@@ -98,14 +102,46 @@ export function migrateLegacyDataDirs(): void {
98
102
  const legacyDir = join(base, legacyName);
99
103
  if (!existsSync(legacyDir)) continue;
100
104
 
105
+ const stagingDir = join(
106
+ base,
107
+ `${CURRENT_DATA_DIR_NAME}.migration-${String(process.pid)}-${String(Date.now())}`,
108
+ );
109
+
101
110
  try {
102
- if (!ensurePrivateDirectorySync(currentDir)) return;
103
- cpSync(legacyDir, currentDir, { recursive: true, force: true });
104
- if (!securePrivateTreeSync(currentDir)) return;
111
+ // cpSync may dereference junctions on some Windows runtimes. Reject the source tree before
112
+ // copying and recheck it afterwards so migration never imports through an indirection.
113
+ if (!isPathTreeFreeOfIndirectionSync(legacyDir)) return;
114
+ if (existsSync(currentDir)) {
115
+ // Never merge legacy state into an unknown or partially initialized destination. A caller
116
+ // can inspect it and retry after resolving the conflict; the authoritative legacy source
117
+ // remains untouched.
118
+ if (readdirSync(currentDir).length > 0) return;
119
+ rmSync(currentDir, { recursive: true, force: false });
120
+ }
121
+ if (!ensurePrivateDirectorySync(stagingDir)) return;
122
+ for (const entry of readdirSync(legacyDir)) {
123
+ cpSync(join(legacyDir, entry), join(stagingDir, entry), {
124
+ recursive: true,
125
+ force: false,
126
+ errorOnExist: true,
127
+ });
128
+ }
129
+ if (!isPathTreeFreeOfIndirectionSync(legacyDir)) {
130
+ rmSync(stagingDir, { recursive: true, force: true });
131
+ return;
132
+ }
133
+ if (!securePrivateTreeSync(stagingDir)) {
134
+ rmSync(stagingDir, { recursive: true, force: true });
135
+ return;
136
+ }
137
+ renameSync(stagingDir, currentDir);
105
138
  rmSync(legacyDir, { recursive: true, force: true });
139
+ return;
106
140
  } catch {
107
- // Best-effort migration: startup must continue even when the old
108
- // directory is locked or partially unreadable. The next call can retry.
141
+ // A failed staged migration never deletes the legacy source. Remove only this attempt's
142
+ // private sibling and retry on a later startup.
143
+ if (existsSync(stagingDir)) rmSync(stagingDir, { recursive: true, force: true });
144
+ return;
109
145
  }
110
146
  }
111
147
  }