@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
@@ -0,0 +1,554 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { Buffer } from "node:buffer";
3
+ import { constants, existsSync, lstatSync } from "node:fs";
4
+ import { link, lstat, open, readFile, readdir, stat, unlink } from "node:fs/promises";
5
+ import { basename, join, resolve } from "node:path";
6
+ import { CapturedLogSchema } from "./schemas";
7
+ import {
8
+ ensurePrivateDirectorySync,
9
+ PRIVATE_FILE_MODE,
10
+ securePrivateFileSync,
11
+ } from "./privateDataPath";
12
+
13
+ const READ_CHUNK_BYTES = 64 * 1024;
14
+ const QUARANTINE_DIRECTORY = "quarantine";
15
+
16
+ export type CapturedLogRecoveryLevel = "ok" | "degraded" | "unavailable";
17
+
18
+ export type CapturedLogRecoveryHealth = {
19
+ level: CapturedLogRecoveryLevel;
20
+ diagnostic: string | null;
21
+ repairedFiles: number;
22
+ delimiterRepairs: number;
23
+ quarantinedTails: number;
24
+ };
25
+
26
+ type FileSignature = {
27
+ size: number;
28
+ modifiedAt: number;
29
+ changedAt: number;
30
+ device: number;
31
+ inode: number;
32
+ };
33
+
34
+ type RecoveryContext = {
35
+ root: string;
36
+ health: CapturedLogRecoveryHealth;
37
+ startup: Promise<CapturedLogRecoveryHealth> | null;
38
+ startupComplete: boolean;
39
+ safeFiles: Map<string, FileSignature>;
40
+ };
41
+
42
+ type RecordValidation =
43
+ | { valid: true }
44
+ | { valid: false; reason: "invalid-json" | "invalid-log-schema" };
45
+
46
+ type InvalidRecordReason = "invalid-json" | "invalid-log-schema";
47
+
48
+ type FileRecoveryResult =
49
+ | { status: "clean"; signature: FileSignature }
50
+ | { status: "delimiter-appended"; signature: FileSignature }
51
+ | { status: "tail-quarantined"; signature: FileSignature }
52
+ | {
53
+ status: "interior-corruption";
54
+ line: number;
55
+ byteOffset: number;
56
+ recordHash: string;
57
+ reason: InvalidRecordReason;
58
+ }
59
+ | { status: "failed"; reason: string };
60
+
61
+ type ScanResult =
62
+ | {
63
+ status: "complete";
64
+ pending: Buffer;
65
+ pendingOffset: number;
66
+ nextLine: number;
67
+ }
68
+ | {
69
+ status: "interior-corruption";
70
+ line: number;
71
+ byteOffset: number;
72
+ recordHash: string;
73
+ reason: InvalidRecordReason;
74
+ }
75
+ | { status: "failed"; reason: string };
76
+
77
+ type QuarantineRecord = {
78
+ schemaVersion: 1;
79
+ reason: "invalid-final-jsonl-record";
80
+ sourceFile: string;
81
+ sourcePathHash: string;
82
+ recoveredAt: string;
83
+ tailOffset: number;
84
+ tailBytes: number;
85
+ tailSha256: string;
86
+ tailBase64: string;
87
+ };
88
+
89
+ type ExistingQuarantineState = "missing" | "matching" | "conflicting";
90
+
91
+ let activeContext: RecoveryContext | null = null;
92
+
93
+ function emptyHealth(): CapturedLogRecoveryHealth {
94
+ return {
95
+ level: "ok",
96
+ diagnostic: null,
97
+ repairedFiles: 0,
98
+ delimiterRepairs: 0,
99
+ quarantinedTails: 0,
100
+ };
101
+ }
102
+
103
+ function contextFor(logDir: string): RecoveryContext {
104
+ const root = resolve(logDir);
105
+ if (activeContext === null || activeContext.root !== root) {
106
+ activeContext = {
107
+ root,
108
+ health: emptyHealth(),
109
+ startup: null,
110
+ startupComplete: false,
111
+ safeFiles: new Map(),
112
+ };
113
+ }
114
+ return activeContext;
115
+ }
116
+
117
+ function copyHealth(health: CapturedLogRecoveryHealth): CapturedLogRecoveryHealth {
118
+ return { ...health };
119
+ }
120
+
121
+ function hashBytes(value: Uint8Array): string {
122
+ return createHash("sha256").update(value).digest("hex");
123
+ }
124
+
125
+ function signatureFromStats(stats: {
126
+ size: number;
127
+ mtimeMs: number;
128
+ ctimeMs: number;
129
+ dev: number;
130
+ ino: number;
131
+ }): FileSignature {
132
+ return {
133
+ size: stats.size,
134
+ modifiedAt: stats.mtimeMs,
135
+ changedAt: stats.ctimeMs,
136
+ device: stats.dev,
137
+ inode: stats.ino,
138
+ };
139
+ }
140
+
141
+ function signaturesMatch(left: FileSignature, right: FileSignature): boolean {
142
+ return (
143
+ left.size === right.size &&
144
+ left.modifiedAt === right.modifiedAt &&
145
+ left.changedAt === right.changedAt &&
146
+ left.device === right.device &&
147
+ left.inode === right.inode
148
+ );
149
+ }
150
+
151
+ function validateRecord(bytes: Buffer): RecordValidation {
152
+ const withoutCarriageReturn =
153
+ bytes.length > 0 && bytes[bytes.length - 1] === 0x0d ? bytes.subarray(0, -1) : bytes;
154
+ const text = withoutCarriageReturn.toString("utf-8");
155
+ if (text.trim() === "") return { valid: true };
156
+
157
+ let parsed: unknown;
158
+ try {
159
+ parsed = JSON.parse(text);
160
+ } catch {
161
+ return { valid: false, reason: "invalid-json" };
162
+ }
163
+ return CapturedLogSchema.safeParse(parsed).success
164
+ ? { valid: true }
165
+ : { valid: false, reason: "invalid-log-schema" };
166
+ }
167
+
168
+ async function scanFile(
169
+ handle: Awaited<ReturnType<typeof open>>,
170
+ size: number,
171
+ ): Promise<ScanResult> {
172
+ const readBuffer = Buffer.alloc(READ_CHUNK_BYTES);
173
+ let position = 0;
174
+ let pending = Buffer.alloc(0);
175
+ let pendingOffset = 0;
176
+ let line = 1;
177
+
178
+ while (position < size) {
179
+ const length = Math.min(READ_CHUNK_BYTES, size - position);
180
+ let bytesRead: number;
181
+ try {
182
+ const result = await handle.read(readBuffer, 0, length, position);
183
+ bytesRead = result.bytesRead;
184
+ } catch (error) {
185
+ return { status: "failed", reason: `read failed: ${String(error)}` };
186
+ }
187
+ if (bytesRead === 0) {
188
+ return { status: "failed", reason: "captured-log file changed while being scanned" };
189
+ }
190
+
191
+ const chunk = readBuffer.subarray(0, bytesRead);
192
+ const combined = pending.length === 0 ? Buffer.from(chunk) : Buffer.concat([pending, chunk]);
193
+ let consumed = 0;
194
+ let newline = combined.indexOf(0x0a, consumed);
195
+ while (newline !== -1) {
196
+ const record = combined.subarray(consumed, newline);
197
+ const validation = validateRecord(record);
198
+ if (!validation.valid) {
199
+ return {
200
+ status: "interior-corruption",
201
+ line,
202
+ byteOffset: pendingOffset + consumed,
203
+ recordHash: hashBytes(record),
204
+ reason: validation.reason,
205
+ };
206
+ }
207
+ consumed = newline + 1;
208
+ line += 1;
209
+ newline = combined.indexOf(0x0a, consumed);
210
+ }
211
+
212
+ pending = Buffer.from(combined.subarray(consumed));
213
+ pendingOffset += consumed;
214
+ position += bytesRead;
215
+ }
216
+
217
+ return { status: "complete", pending, pendingOffset, nextLine: line };
218
+ }
219
+
220
+ async function syncDirectory(directory: string): Promise<void> {
221
+ let handle: Awaited<ReturnType<typeof open>> | null = null;
222
+ try {
223
+ handle = await open(directory, "r");
224
+ await handle.sync();
225
+ } catch {
226
+ // Directory fsync is unavailable on some supported Windows filesystems.
227
+ } finally {
228
+ await handle?.close().catch(() => undefined);
229
+ }
230
+ }
231
+
232
+ async function removeTemporaryFile(filePath: string): Promise<void> {
233
+ await unlink(filePath).catch(() => undefined);
234
+ }
235
+
236
+ function readOwnProperty(value: unknown, name: string): unknown {
237
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
238
+ return Object.getOwnPropertyDescriptor(value, name)?.value;
239
+ }
240
+
241
+ async function inspectExistingQuarantine(
242
+ filePath: string,
243
+ expected: QuarantineRecord,
244
+ ): Promise<ExistingQuarantineState> {
245
+ if (!existsSync(filePath)) return "missing";
246
+ try {
247
+ const parsed: unknown = JSON.parse(await readFile(filePath, "utf-8"));
248
+ return readOwnProperty(parsed, "schemaVersion") === expected.schemaVersion &&
249
+ readOwnProperty(parsed, "reason") === expected.reason &&
250
+ readOwnProperty(parsed, "sourceFile") === expected.sourceFile &&
251
+ readOwnProperty(parsed, "sourcePathHash") === expected.sourcePathHash &&
252
+ readOwnProperty(parsed, "tailOffset") === expected.tailOffset &&
253
+ readOwnProperty(parsed, "tailBytes") === expected.tailBytes &&
254
+ readOwnProperty(parsed, "tailSha256") === expected.tailSha256 &&
255
+ readOwnProperty(parsed, "tailBase64") === expected.tailBase64
256
+ ? "matching"
257
+ : "conflicting";
258
+ } catch {
259
+ return "conflicting";
260
+ }
261
+ }
262
+
263
+ async function quarantineTail(
264
+ context: RecoveryContext,
265
+ sourceFile: string,
266
+ tail: Buffer,
267
+ tailOffset: number,
268
+ ): Promise<string | null> {
269
+ const quarantineDirectory = join(context.root, QUARANTINE_DIRECTORY);
270
+ if (!ensurePrivateDirectorySync(quarantineDirectory)) return null;
271
+
272
+ const tailSha256 = hashBytes(tail);
273
+ const sourceFileName = basename(sourceFile);
274
+ const destination = join(quarantineDirectory, `${sourceFileName}.tail-${tailSha256}.json`);
275
+ const temporary = join(
276
+ quarantineDirectory,
277
+ `.${sourceFileName}.${String(process.pid)}.${randomUUID()}.tmp`,
278
+ );
279
+ const record: QuarantineRecord = {
280
+ schemaVersion: 1,
281
+ reason: "invalid-final-jsonl-record",
282
+ sourceFile: sourceFileName,
283
+ sourcePathHash: hashBytes(Buffer.from(resolve(sourceFile), "utf-8")),
284
+ recoveredAt: new Date().toISOString(),
285
+ tailOffset,
286
+ tailBytes: tail.length,
287
+ tailSha256,
288
+ tailBase64: tail.toString("base64"),
289
+ };
290
+
291
+ const existing = await inspectExistingQuarantine(destination, record);
292
+ if (existing === "matching") {
293
+ return securePrivateFileSync(destination) ? destination : null;
294
+ }
295
+ if (existing === "conflicting") return null;
296
+
297
+ let handle: Awaited<ReturnType<typeof open>> | null = null;
298
+ try {
299
+ handle = await open(temporary, "wx", PRIVATE_FILE_MODE);
300
+ await handle.writeFile(`${JSON.stringify(record)}\n`, "utf-8");
301
+ await handle.sync();
302
+ await handle.close();
303
+ handle = null;
304
+ if (!securePrivateFileSync(temporary)) {
305
+ await removeTemporaryFile(temporary);
306
+ return null;
307
+ }
308
+ // A hard-link install has create-if-absent semantics on every supported platform. Unlike
309
+ // rename(), it cannot overwrite a conflicting quarantine artifact that appears after the
310
+ // preflight check on POSIX.
311
+ await link(temporary, destination);
312
+ await unlink(temporary);
313
+ if (!securePrivateFileSync(destination)) return null;
314
+ await syncDirectory(quarantineDirectory);
315
+ return destination;
316
+ } catch {
317
+ await handle?.close().catch(() => undefined);
318
+ await removeTemporaryFile(temporary);
319
+ const racedExisting = await inspectExistingQuarantine(destination, record);
320
+ return racedExisting === "matching" && securePrivateFileSync(destination) ? destination : null;
321
+ }
322
+ }
323
+
324
+ async function recoverFile(
325
+ context: RecoveryContext,
326
+ filePath: string,
327
+ ): Promise<FileRecoveryResult> {
328
+ if (!existsSync(filePath)) {
329
+ context.safeFiles.delete(filePath);
330
+ return {
331
+ status: "clean",
332
+ signature: { size: 0, modifiedAt: 0, changedAt: 0, device: 0, inode: 0 },
333
+ };
334
+ }
335
+
336
+ let fileStats: Awaited<ReturnType<typeof lstat>>;
337
+ try {
338
+ fileStats = await lstat(filePath);
339
+ } catch (error) {
340
+ return { status: "failed", reason: `stat failed: ${String(error)}` };
341
+ }
342
+ if (fileStats.isSymbolicLink() || !fileStats.isFile()) {
343
+ return { status: "failed", reason: "captured-log path is not a regular private file" };
344
+ }
345
+
346
+ const initialSignature = signatureFromStats(fileStats);
347
+ const safeSignature = context.safeFiles.get(filePath);
348
+ if (safeSignature !== undefined && signaturesMatch(safeSignature, initialSignature)) {
349
+ return { status: "clean", signature: initialSignature };
350
+ }
351
+
352
+ let handle: Awaited<ReturnType<typeof open>> | null = null;
353
+ try {
354
+ handle = await open(filePath, constants.O_RDWR | constants.O_NOFOLLOW);
355
+ const openedStats = await handle.stat();
356
+ if (!openedStats.isFile()) {
357
+ return { status: "failed", reason: "captured-log handle is not a regular file" };
358
+ }
359
+ const openedSignature = signatureFromStats(openedStats);
360
+ if (!signaturesMatch(initialSignature, openedSignature)) {
361
+ return {
362
+ status: "failed",
363
+ reason: "captured-log path changed between metadata validation and open",
364
+ };
365
+ }
366
+ const scan = await scanFile(handle, openedStats.size);
367
+ const scannedSignature = signatureFromStats(await handle.stat());
368
+ if (!signaturesMatch(openedSignature, scannedSignature)) {
369
+ return {
370
+ status: "failed",
371
+ reason: "captured-log file changed while recovery was scanning it",
372
+ };
373
+ }
374
+ switch (scan.status) {
375
+ case "failed":
376
+ return scan;
377
+ case "interior-corruption":
378
+ return scan;
379
+ case "complete": {
380
+ if (scan.pending.length === 0) {
381
+ return { status: "clean", signature: signatureFromStats(await handle.stat()) };
382
+ }
383
+
384
+ const validation = validateRecord(scan.pending);
385
+ if (validation.valid) {
386
+ const write = await handle.write(Buffer.from("\n", "utf-8"), 0, 1, openedStats.size);
387
+ if (write.bytesWritten !== 1) {
388
+ return { status: "failed", reason: "captured-log delimiter append was incomplete" };
389
+ }
390
+ await handle.sync();
391
+ return {
392
+ status: "delimiter-appended",
393
+ signature: signatureFromStats(await handle.stat()),
394
+ };
395
+ }
396
+
397
+ const quarantinePath = await quarantineTail(
398
+ context,
399
+ filePath,
400
+ scan.pending,
401
+ scan.pendingOffset,
402
+ );
403
+ if (quarantinePath === null) {
404
+ return { status: "failed", reason: "invalid tail could not be privately quarantined" };
405
+ }
406
+ const beforeTruncate = signatureFromStats(await handle.stat());
407
+ if (!signaturesMatch(openedSignature, beforeTruncate)) {
408
+ return {
409
+ status: "failed",
410
+ reason: "captured-log file changed before invalid-tail truncation",
411
+ };
412
+ }
413
+ await handle.truncate(scan.pendingOffset);
414
+ await handle.sync();
415
+ return {
416
+ status: "tail-quarantined",
417
+ signature: signatureFromStats(await handle.stat()),
418
+ };
419
+ }
420
+ }
421
+ return { status: "failed", reason: "unsupported captured-log scan state" };
422
+ } catch (error) {
423
+ return { status: "failed", reason: String(error) };
424
+ } finally {
425
+ await handle?.close().catch(() => undefined);
426
+ }
427
+ }
428
+
429
+ function applyFileResult(
430
+ context: RecoveryContext,
431
+ filePath: string,
432
+ result: FileRecoveryResult,
433
+ ): void {
434
+ const file = basename(filePath);
435
+ switch (result.status) {
436
+ case "clean":
437
+ context.safeFiles.set(filePath, result.signature);
438
+ return;
439
+ case "delimiter-appended":
440
+ context.safeFiles.set(filePath, result.signature);
441
+ context.health.delimiterRepairs += 1;
442
+ context.health.repairedFiles += 1;
443
+ return;
444
+ case "tail-quarantined":
445
+ context.safeFiles.set(filePath, result.signature);
446
+ context.health.quarantinedTails += 1;
447
+ context.health.repairedFiles += 1;
448
+ if (context.health.level !== "unavailable") {
449
+ context.health.level = "degraded";
450
+ context.health.diagnostic = `Recovered and quarantined an invalid JSONL tail in ${file}`;
451
+ }
452
+ return;
453
+ case "interior-corruption":
454
+ context.safeFiles.delete(filePath);
455
+ context.health.level = "unavailable";
456
+ context.health.diagnostic =
457
+ `JSONL corruption in ${file} at line ${String(result.line)} ` +
458
+ `(offset ${String(result.byteOffset)}, ${result.reason}, sha256 ${result.recordHash})`;
459
+ return;
460
+ case "failed":
461
+ context.safeFiles.delete(filePath);
462
+ context.health.level = "unavailable";
463
+ context.health.diagnostic = `JSONL recovery failed for ${file}: ${result.reason}`;
464
+ }
465
+ }
466
+
467
+ async function runStartupRecovery(context: RecoveryContext): Promise<CapturedLogRecoveryHealth> {
468
+ if (!ensurePrivateDirectorySync(context.root)) {
469
+ context.health.level = "unavailable";
470
+ context.health.diagnostic = "Captured-log directory could not be made private";
471
+ return copyHealth(context.health);
472
+ }
473
+
474
+ let entries: string[];
475
+ try {
476
+ entries = await readdir(context.root);
477
+ } catch (error) {
478
+ context.health.level = "unavailable";
479
+ context.health.diagnostic = `Captured-log directory could not be listed: ${String(error)}`;
480
+ return copyHealth(context.health);
481
+ }
482
+
483
+ const files = entries.filter((entry) => entry.endsWith(".jsonl")).sort();
484
+ for (const file of files) {
485
+ const filePath = join(context.root, file);
486
+ const result = await recoverFile(context, filePath);
487
+ applyFileResult(context, filePath, result);
488
+ if (context.health.level === "unavailable") break;
489
+ }
490
+ return copyHealth(context.health);
491
+ }
492
+
493
+ /** Recover all captured-log ledgers once before startup reads, indexing, readiness, or appends. */
494
+ export async function recoverCapturedLogStorage(
495
+ logDir: string,
496
+ ): Promise<CapturedLogRecoveryHealth> {
497
+ const context = contextFor(logDir);
498
+ if (context.startup === null) {
499
+ context.startup = runStartupRecovery(context).then((health) => {
500
+ context.startupComplete = true;
501
+ return health;
502
+ });
503
+ }
504
+ return await context.startup;
505
+ }
506
+
507
+ /** Re-check a ledger immediately before appending; unchanged files use a stat-only fast path. */
508
+ export async function recoverCapturedLogFileBeforeAppend(
509
+ logDir: string,
510
+ filePath: string,
511
+ ): Promise<CapturedLogRecoveryHealth> {
512
+ const context = contextFor(logDir);
513
+ await recoverCapturedLogStorage(logDir);
514
+ if (context.health.level === "unavailable") return copyHealth(context.health);
515
+
516
+ const result = await recoverFile(context, filePath);
517
+ applyFileResult(context, filePath, result);
518
+ return copyHealth(context.health);
519
+ }
520
+
521
+ /** Refresh the trusted signature after this process successfully appends a complete JSONL record. */
522
+ export async function markCapturedLogFileAppended(logDir: string, filePath: string): Promise<void> {
523
+ const context = contextFor(logDir);
524
+ try {
525
+ const fileStats = await stat(filePath);
526
+ context.safeFiles.set(filePath, signatureFromStats(fileStats));
527
+ } catch {
528
+ context.safeFiles.delete(filePath);
529
+ }
530
+ }
531
+
532
+ export function getCapturedLogRecoveryHealth(logDir: string): CapturedLogRecoveryHealth {
533
+ return copyHealth(contextFor(logDir).health);
534
+ }
535
+
536
+ /** Fail-closed guard for the process-exit writer, where asynchronous recovery is unavailable. */
537
+ export function canAppendCapturedLogFileOnExit(logDir: string, filePath: string): boolean {
538
+ const context = contextFor(logDir);
539
+ if (!context.startupComplete || context.health.level === "unavailable") return false;
540
+ const trusted = context.safeFiles.get(filePath);
541
+ if (trusted === undefined) return false;
542
+
543
+ try {
544
+ const observed = lstatSync(filePath);
545
+ if (observed === undefined || observed.isSymbolicLink() || !observed.isFile()) return false;
546
+ return signaturesMatch(trusted, signatureFromStats(observed));
547
+ } catch {
548
+ return false;
549
+ }
550
+ }
551
+
552
+ export function _resetCapturedLogRecoveryForTests(): void {
553
+ activeContext = null;
554
+ }
@@ -13,7 +13,7 @@ import {
13
13
  getSessionProcessPendingCount,
14
14
  isSessionProcessAvailable,
15
15
  } from "./sessionProcess";
16
- import { finalizeLogUpdate } from "./store";
16
+ import { persistFinalizedLogUpdate } from "./store";
17
17
  import { computeTokensPerSecond } from "./streamTiming";
18
18
  import { analyzeToolSchemaWarnings } from "./toolSchemaWarnings";
19
19
  import { isPlainRecord, safeGetOwnProperty } from "../lib/objectUtils";
@@ -37,6 +37,7 @@ export type FinalizeStreamingLogJob = BaseFinalizeLogJob & {
37
37
  collectStreamingChunks: boolean;
38
38
  firstChunkMs: number | null;
39
39
  totalStreamMs: number;
40
+ terminalError?: string | null;
40
41
  };
41
42
 
42
43
  export type FinalizeStreamAbortLogJob = BaseFinalizeLogJob & {
@@ -99,6 +100,10 @@ export function buildFileLogEntry(log: CapturedLog, upstreamUrl: string): Record
99
100
  clientProjectFolder: log.clientProjectFolder,
100
101
  streamingChunks: log.streamingChunks,
101
102
  streamingChunksPath: log.streamingChunksPath,
103
+ captureIncomplete: log.captureIncomplete ?? false,
104
+ droppedChunks: log.droppedChunks ?? 0,
105
+ droppedBytes: log.droppedBytes ?? 0,
106
+ captureIncompleteReason: log.captureIncompleteReason ?? null,
102
107
  warnings: log.warnings,
103
108
  error: log.error,
104
109
  };
@@ -198,7 +203,7 @@ async function runToolSchemaWarningAnalysis(job: ToolSchemaWarningAnalysisJob):
198
203
  if (updatedLog === null) return;
199
204
 
200
205
  appendLogEntry({ ...buildFileLogEntry(updatedLog, job.upstreamUrl), error: job.error });
201
- finalizeLogUpdate(updatedLog);
206
+ await persistFinalizedLogUpdate(updatedLog);
202
207
  }
203
208
 
204
209
  function scheduleToolSchemaWarningAnalysis(job: ToolSchemaWarningAnalysisJob): void {
@@ -455,8 +460,13 @@ export function buildFinalizeLogResult(job: FinalizeLogJob): FinalizeLogResult {
455
460
  switch (job.type) {
456
461
  case "non-streaming":
457
462
  return finalizeNonStreaming(job, log);
458
- case "streaming":
459
- return finalizeStreaming(job, log);
463
+ case "streaming": {
464
+ const result = finalizeStreaming(job, log);
465
+ if (job.terminalError !== undefined && job.terminalError !== null) {
466
+ result.log.error = job.terminalError;
467
+ }
468
+ return result;
469
+ }
460
470
  case "stream-abort":
461
471
  return finalizeStreamAbort(job, log);
462
472
  default: {
@@ -472,13 +482,18 @@ export function buildFinalizeLogResult(job: FinalizeLogJob): FinalizeLogResult {
472
482
  }
473
483
  }
474
484
 
475
- export function commitFinalizeLogResult(result: FinalizeLogResult): void {
476
- appendLogEntry({ ...buildFileLogEntry(result.log, result.upstreamUrl), error: result.error });
477
- finalizeLogUpdate(result.log);
485
+ export async function commitFinalizeLogResult(result: FinalizeLogResult): Promise<void> {
486
+ const persistedError =
487
+ result.log.captureIncompleteReason === "upstream-stream-error"
488
+ ? (result.log.error ?? result.error ?? null)
489
+ : (result.error ?? result.log.error ?? null);
490
+ result.log.error = persistedError;
491
+ appendLogEntry({ ...buildFileLogEntry(result.log, result.upstreamUrl), error: persistedError });
492
+ await persistFinalizedLogUpdate(result.log);
478
493
  scheduleToolSchemaWarningAnalysis({
479
494
  log: cloneLog(result.log),
480
495
  upstreamUrl: result.upstreamUrl,
481
- error: result.error,
496
+ error: persistedError,
482
497
  });
483
498
  if (result.cleanupRawStreamPath !== undefined && result.cleanupRawStreamPath !== null) {
484
499
  try {
@@ -665,8 +680,8 @@ export function executeBuildInWorker(job: FinalizeLogJob): Promise<FinalizeLogRe
665
680
  }
666
681
 
667
682
  export function executeFinalizeLogJob(job: FinalizeLogJob): Promise<void> {
668
- return resolveBuildPromise(job).then((result) => {
669
- commitFinalizeLogResult(result);
683
+ return resolveBuildPromise(job).then(async (result) => {
684
+ await commitFinalizeLogResult(result);
670
685
  if (result.error !== null && result.error !== "Client aborted") {
671
686
  return Promise.reject(new Error(result.error));
672
687
  }
@@ -4,6 +4,7 @@ import JSZip from "jszip";
4
4
  import { z } from "zod";
5
5
  import {
6
6
  CapturedLogSchema,
7
+ LogIdSchema,
7
8
  StreamingChunkSchema,
8
9
  type ApiFormat,
9
10
  type CapturedLog,
@@ -58,7 +59,7 @@ const ManifestSchema = z
58
59
  exportedAt: z.string().optional(),
59
60
  mode: z.string().optional(),
60
61
  redacted: z.boolean().optional(),
61
- logIds: z.array(z.number().int().positive()).optional(),
62
+ logIds: z.array(LogIdSchema).optional(),
62
63
  })
63
64
  .passthrough();
64
65