@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.
- package/.output/{nitro.json → backend/nitro.json} +3 -3
- package/.output/backend-public/assets/index-D4LX-sw-.js +11 -0
- package/.output/cli.js +2326 -516
- package/.output/server/_libs/extend.mjs +4 -8
- package/.output/server/_libs/jszip.mjs +9 -35
- package/.output/server/_libs/react-dom.mjs +2 -3
- package/.output/server/_libs/react.mjs +1 -27
- package/.output/server/_libs/tanstack__react-router.mjs +1 -329
- package/.output/server/_libs/tanstack__router-core.mjs +42 -100
- package/.output/server/_libs/use-sync-external-store.mjs +1 -64
- package/.output/server/_libs/ws.mjs +1 -1
- package/.output/server/_ssr/index.mjs +4 -4
- package/.output/server/_ssr/{publicBasePathContext-5264L3rX.mjs → publicBasePathContext-IH2ilOfu.mjs} +52 -24
- package/.output/server/_ssr/{router-BJ_ltLim.mjs → router-D18yUq36.mjs} +19770 -19447
- package/.output/server/_ssr/{start-C6MjPjxp.mjs → start-COQu_AgD.mjs} +1 -1
- package/.output/server/_tanstack-start-manifest_v-DnbdNeun.mjs +4 -0
- package/.output/server/index.mjs +5 -145
- package/.output/{public/assets/CompareDrawer-B3_TLTJh.js → ui/assets/CompareDrawer-DAUuIJ6G.js} +1 -1
- package/.output/{public/assets/InspectorPet-Cpn6hv4P.js → ui/assets/InspectorPet-BRBVjOWI.js} +1 -1
- package/.output/ui/assets/ProxyViewerContainer-D7Sq0ctc.js +59 -0
- package/.output/{public/assets/ReplayDialog-B3EKVdJk.js → ui/assets/ReplayDialog-nDLsjOhs.js} +1 -1
- package/.output/{public/assets/RequestAnatomy-COOQjyUC.js → ui/assets/RequestAnatomy-zd4BDgL0.js} +1 -1
- package/.output/ui/assets/ResponseView-AdIXGcSn.js +2 -0
- package/.output/ui/assets/StreamingChunkSequence-CesTz8He.js +1 -0
- package/.output/{public/assets/_sessionId-zbgYl7ZJ.js → ui/assets/_sessionId-BAfzrhSU.js} +1 -1
- package/.output/{public/assets/_sessionId-BJlM3nmt.js → ui/assets/_sessionId-QnBomgPD.js} +1 -1
- package/.output/ui/assets/favicon-DYEkHSNl.svg +21 -0
- package/.output/{public/assets/index-BIK4bPTv.js → ui/assets/index-BcsdxBAd.js} +1 -1
- package/.output/ui/assets/index-BvXp42al.css +1 -0
- package/.output/{public/assets/index-D2Qw6Ri2.js → ui/assets/index-D0rtCN9V.js} +1 -1
- package/.output/ui/assets/index-DHpwr08Z.js +73 -0
- package/.output/{public/assets/index-xF_NY8Yb.js → ui/assets/index-dNuk2dsU.js} +5 -5
- package/.output/ui/assets/json-viewer-BvFHglMb.js +1 -0
- package/.output/{public/assets/jszip.min-B_rfAUCe.js → ui/assets/jszip.min-B5Z7gNZB.js} +1 -1
- package/.output/ui/index.html +229 -0
- package/.output/ui/runtime-config.js +2 -0
- package/.output/workers/logFinalizer.worker.js +979 -414
- package/.output/workers/sessionWorkerEntry.js +979 -414
- package/README.md +175 -43
- package/docs/instance-control.openapi.yaml +59 -4
- package/package.json +25 -5
- package/scripts/generate-theme-scales.mjs +3 -3
- package/src/{router.tsx → backend/router.tsx} +3 -15
- package/src/backend/routes/$.ts +26 -0
- package/src/backend/routes/__root.ts +4 -0
- package/src/{routes → backend/routes}/api/-instances.ts +3 -3
- package/src/{routes → backend/routes}/api/alerts.summary.ts +3 -3
- package/src/{routes → backend/routes}/api/alerts.ts +4 -4
- package/src/{routes → backend/routes}/api/config.paths.ts +1 -1
- package/src/{routes → backend/routes}/api/config.ts +11 -2
- package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.help.ts +1 -1
- package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.install.ts +1 -1
- package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.runner-presets.ts +1 -1
- package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.upgrade.ts +1 -1
- package/src/{routes → backend/routes}/api/ecosystem.packages.ts +5 -1
- package/src/{routes → backend/routes}/api/ecosystem.recipes.$recipeId.run.ts +1 -1
- package/src/{routes → backend/routes}/api/ecosystem.tasks.$taskId.ts +1 -1
- package/src/{routes → backend/routes}/api/ecosystem.tasks.ts +1 -1
- package/src/{routes → backend/routes}/api/groups.$groupId.evidence.ts +2 -2
- package/src/{routes → backend/routes}/api/groups.$groupId.sessions.ts +2 -2
- package/src/{routes → backend/routes}/api/groups.$groupId.ts +2 -2
- package/src/{routes → backend/routes}/api/groups.ts +2 -2
- package/src/{routes → backend/routes}/api/health.ts +3 -3
- package/src/{routes → backend/routes}/api/instances.$name.connection.ts +1 -1
- package/src/{routes → backend/routes}/api/instances.$name.restart.ts +2 -2
- package/src/{routes → backend/routes}/api/instances.$name.start.ts +2 -2
- package/src/{routes → backend/routes}/api/instances.$name.stop.ts +2 -2
- package/src/{routes → backend/routes}/api/instances.$name.ts +1 -1
- package/src/{routes → backend/routes}/api/instances.ts +1 -1
- package/src/{routes → backend/routes}/api/knowledge.candidates.$candidateId.promote.ts +2 -2
- package/src/{routes → backend/routes}/api/knowledge.candidates.$candidateId.ts +2 -2
- package/src/{routes → backend/routes}/api/knowledge.candidates.ts +1 -1
- package/src/{routes → backend/routes}/api/knowledge.project-context.ts +2 -2
- package/src/{routes → backend/routes}/api/knowledge.search.ts +2 -2
- package/src/{routes → backend/routes}/api/knowledge.sessions.$sessionId.candidates.ts +3 -3
- package/src/{routes → backend/routes}/api/logs.$id.body.ts +6 -6
- package/src/{routes → backend/routes}/api/logs.$id.chunks.ts +5 -4
- package/src/{routes → backend/routes}/api/logs.$id.replay.ts +12 -11
- package/src/{routes → backend/routes}/api/logs.$id.ts +4 -3
- package/src/{routes → backend/routes}/api/logs.import.ts +3 -3
- package/src/{routes → backend/routes}/api/logs.stream.ts +11 -7
- package/src/{routes → backend/routes}/api/logs.ts +17 -9
- package/src/{routes → backend/routes}/api/mcp.ts +1 -1
- package/src/{routes → backend/routes}/api/models.ts +1 -1
- package/src/{routes → backend/routes}/api/pi-agent.ts +2 -2
- package/src/{routes → backend/routes}/api/providers.$providerId.model-metadata.ts +5 -5
- package/src/{routes → backend/routes}/api/providers.$providerId.test.log.ts +7 -7
- package/src/{routes → backend/routes}/api/providers.$providerId.ts +4 -4
- package/src/{routes → backend/routes}/api/providers.export.ts +1 -1
- package/src/{routes → backend/routes}/api/providers.import.ts +3 -3
- package/src/{routes → backend/routes}/api/providers.scan.ts +2 -2
- package/src/{routes → backend/routes}/api/providers.ts +9 -4
- package/src/{routes → backend/routes}/api/runs.$runId.evidence.ts +2 -2
- package/src/{routes → backend/routes}/api/runs.$runId.ts +2 -2
- package/src/{routes → backend/routes}/api/runs.ts +4 -4
- package/src/backend/routes/api/runtime.ts +46 -0
- package/src/{routes → backend/routes}/api/sessions.ts +3 -3
- package/src/{routes → backend/routes}/proxy/$.ts +1 -1
- package/src/{routes → backend/routes}/readyz.ts +1 -1
- package/src/{start.ts → backend/start.ts} +1 -1
- package/src/cli/alias.ts +2 -11
- package/src/cli/doctor.ts +32 -9
- package/src/cli/instance.ts +26 -13
- package/src/cli/instanceArgs.ts +30 -11
- package/src/cli/instanceControl.ts +0 -2
- package/src/cli/instanceModel.ts +9 -17
- package/src/cli/instanceRegistry.ts +0 -9
- package/src/cli/networkHints.ts +27 -14
- package/src/cli/onboard.ts +69 -2
- package/src/cli/onboardTarget.ts +89 -40
- package/src/cli/templates/codex-skill-onboard.ts +44 -14
- package/src/cli/templates/skill-onboard.ts +64 -28
- package/src/cli.ts +184 -61
- package/src/components/ProxyViewer.tsx +11 -9
- package/src/components/ProxyViewerContainer.tsx +12 -11
- package/src/components/pi-agent/PiAgentPanel.tsx +2 -2
- package/src/components/providers/ProviderCard.tsx +1 -1
- package/src/components/providers/ProviderTestResultsView.tsx +1 -1
- package/src/components/providers/SettingsDialog.tsx +159 -144
- package/src/components/proxy-viewer/ApplicationBar.tsx +2 -1
- package/src/components/proxy-viewer/LogEntry.tsx +1 -1
- package/src/components/proxy-viewer/log-formats/index.ts +1 -1
- package/src/contracts/index.ts +5 -0
- package/src/contracts/log.ts +33 -3
- package/src/knowledge/types.ts +3 -2
- package/src/lib/alertContract.ts +2 -2
- package/src/lib/apiClient.ts +30 -7
- package/src/lib/apiFormat.ts +29 -0
- package/src/lib/basePath.ts +29 -11
- package/src/lib/browserRuntimeContract.ts +124 -0
- package/src/lib/browserRuntimeResolver.ts +244 -0
- package/src/{proxy → lib}/claudeCodeStrip.ts +6 -7
- package/src/lib/codingAgentEndpoints.ts +38 -0
- package/src/lib/instanceContract.ts +27 -11
- package/src/lib/piAgentContract.ts +3 -2
- package/src/lib/publicBasePathContext.ts +40 -10
- package/src/lib/resourceLimits.ts +2 -0
- package/src/lib/runContract.ts +3 -3
- package/src/lib/sessionInfoContract.ts +3 -2
- package/src/mcp/instanceHandlers.ts +1 -0
- package/src/mcp/server.ts +19 -24
- package/src/proxy/config.ts +25 -30
- package/src/proxy/dataDir.ts +43 -7
- package/src/proxy/ecosystemTasks.ts +161 -70
- package/src/proxy/formats/registry.ts +6 -21
- package/src/proxy/handler.ts +129 -29
- package/src/proxy/identityProxy.ts +419 -39
- package/src/proxy/jsonlRecovery.ts +554 -0
- package/src/proxy/logFinalizer.ts +25 -10
- package/src/proxy/logImporter.ts +2 -1
- package/src/proxy/logIndex.ts +191 -95
- package/src/proxy/logger.ts +77 -14
- package/src/proxy/platformCommands.ts +41 -0
- package/src/proxy/privateDataPath.ts +310 -88
- package/src/proxy/rawStreamCapture.ts +194 -13
- package/src/proxy/runtimeAdmission.ts +5 -2
- package/src/proxy/runtimeHealth.ts +14 -1
- package/src/proxy/runtimeShutdown.ts +11 -1
- package/src/proxy/schemas.ts +7 -1
- package/src/proxy/sessionArchive.ts +78 -14
- package/src/proxy/sqliteLogIndex.ts +65 -2
- package/src/proxy/store.ts +141 -34
- package/src/proxy/uiAssetServer.ts +106 -0
- package/src/proxy/upstream.ts +29 -2
- package/src/ui/bootstrap.ts +167 -0
- package/src/ui/index.html +38 -0
- package/src/ui/logsStreamClient.ts +20 -0
- package/src/ui/main.tsx +13 -0
- package/src/ui/public/runtime-config.js +2 -0
- package/src/ui/router.tsx +13 -0
- package/src/ui/routes/__root.tsx +69 -0
- package/src/{routes → ui/routes}/index.tsx +2 -3
- package/src/{routes → ui/routes}/session/$sessionId.tsx +5 -11
- package/src/ui/runtimeDiscovery.ts +50 -0
- package/styles/globals.css +1 -0
- package/.output/public/assets/ProxyViewerContainer-BEAhuGOJ.js +0 -126
- package/.output/public/assets/ResponseView-DBCjkwye.js +0 -2
- package/.output/public/assets/StreamingChunkSequence-CuXhDgMo.js +0 -1
- package/.output/public/assets/index-BZo_8koU.js +0 -70
- package/.output/public/assets/index-ZWschkCh.css +0 -1
- package/.output/public/assets/json-viewer-B-Ao6pwW.js +0 -1
- package/.output/server/_libs/@radix-ui/react-accessible-icon+[...].mjs +0 -1
- package/.output/server/_libs/@radix-ui/react-dismissable-layer+[...].mjs +0 -344
- package/.output/server/_libs/@radix-ui/react-navigation-menu+[...].mjs +0 -2
- package/.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs +0 -2
- package/.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs +0 -2
- package/.output/server/_libs/@radix-ui/react-use-callback-ref+[...].mjs +0 -11
- package/.output/server/_libs/@radix-ui/react-use-controllable-state+[...].mjs +0 -69
- package/.output/server/_libs/@radix-ui/react-use-effect-event+[...].mjs +0 -1
- package/.output/server/_libs/@radix-ui/react-use-is-hydrated+[...].mjs +0 -28
- package/.output/server/_libs/@radix-ui/react-use-layout-effect+[...].mjs +0 -6
- package/.output/server/_libs/@radix-ui/react-visually-hidden+[...].mjs +0 -34
- package/.output/server/_libs/aria-hidden.mjs +0 -122
- package/.output/server/_libs/bail.mjs +0 -8
- package/.output/server/_libs/character-entities.mjs +0 -2130
- package/.output/server/_libs/class-variance-authority.mjs +0 -44
- package/.output/server/_libs/clsx.mjs +0 -16
- package/.output/server/_libs/comma-separated-tokens.mjs +0 -10
- package/.output/server/_libs/decode-named-character-reference+[...].mjs +0 -8
- package/.output/server/_libs/dequal.mjs +0 -27
- package/.output/server/_libs/detect-node-es.mjs +0 -1
- package/.output/server/_libs/devlop.mjs +0 -8
- package/.output/server/_libs/diff.mjs +0 -320
- package/.output/server/_libs/estree-util-is-identifier-name.mjs +0 -11
- package/.output/server/_libs/floating-ui__core.mjs +0 -725
- package/.output/server/_libs/floating-ui__dom.mjs +0 -622
- package/.output/server/_libs/floating-ui__react-dom.mjs +0 -292
- package/.output/server/_libs/floating-ui__utils.mjs +0 -320
- package/.output/server/_libs/get-nonce.mjs +0 -9
- package/.output/server/_libs/hast-util-to-jsx-runtime.mjs +0 -388
- package/.output/server/_libs/hast-util-whitespace.mjs +0 -10
- package/.output/server/_libs/html-url-attributes.mjs +0 -26
- package/.output/server/_libs/inline-style-parser.mjs +0 -142
- package/.output/server/_libs/is-plain-obj.mjs +0 -10
- package/.output/server/_libs/lucide-react.mjs +0 -1027
- package/.output/server/_libs/mdast-util-from-markdown.mjs +0 -717
- package/.output/server/_libs/mdast-util-to-hast.mjs +0 -710
- package/.output/server/_libs/mdast-util-to-string.mjs +0 -38
- package/.output/server/_libs/micromark-core-commonmark.mjs +0 -2259
- package/.output/server/_libs/micromark-factory-destination.mjs +0 -94
- package/.output/server/_libs/micromark-factory-label.mjs +0 -63
- package/.output/server/_libs/micromark-factory-space.mjs +0 -24
- package/.output/server/_libs/micromark-factory-title.mjs +0 -65
- package/.output/server/_libs/micromark-factory-whitespace.mjs +0 -22
- package/.output/server/_libs/micromark-util-character.mjs +0 -44
- package/.output/server/_libs/micromark-util-chunked.mjs +0 -36
- package/.output/server/_libs/micromark-util-classify-character+[...].mjs +0 -12
- package/.output/server/_libs/micromark-util-combine-extensions+[...].mjs +0 -41
- package/.output/server/_libs/micromark-util-decode-numeric-character-reference+[...].mjs +0 -19
- package/.output/server/_libs/micromark-util-decode-string.mjs +0 -21
- package/.output/server/_libs/micromark-util-encode.mjs +0 -1
- package/.output/server/_libs/micromark-util-html-tag-name.mjs +0 -69
- package/.output/server/_libs/micromark-util-normalize-identifier+[...].mjs +0 -6
- package/.output/server/_libs/micromark-util-resolve-all.mjs +0 -15
- package/.output/server/_libs/micromark-util-sanitize-uri.mjs +0 -41
- package/.output/server/_libs/micromark-util-subtokenize.mjs +0 -346
- package/.output/server/_libs/micromark.mjs +0 -906
- package/.output/server/_libs/property-information.mjs +0 -1209
- package/.output/server/_libs/radix-ui.mjs +0 -1
- package/.output/server/_libs/radix-ui__number.mjs +0 -6
- package/.output/server/_libs/radix-ui__primitive.mjs +0 -11
- package/.output/server/_libs/radix-ui__react-accordion.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-alert-dialog.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-arrow.mjs +0 -23
- package/.output/server/_libs/radix-ui__react-aspect-ratio.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-avatar.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-checkbox.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-collapsible.mjs +0 -144
- package/.output/server/_libs/radix-ui__react-collection.mjs +0 -69
- package/.output/server/_libs/radix-ui__react-compose-refs.mjs +0 -38
- package/.output/server/_libs/radix-ui__react-context-menu.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-context.mjs +0 -64
- package/.output/server/_libs/radix-ui__react-dialog.mjs +0 -280
- package/.output/server/_libs/radix-ui__react-direction.mjs +0 -9
- package/.output/server/_libs/radix-ui__react-dropdown-menu.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-focus-guards.mjs +0 -39
- package/.output/server/_libs/radix-ui__react-focus-scope.mjs +0 -210
- package/.output/server/_libs/radix-ui__react-form.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-hover-card.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-id.mjs +0 -14
- package/.output/server/_libs/radix-ui__react-label.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-menu.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-menubar.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-popover.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-popper.mjs +0 -340
- package/.output/server/_libs/radix-ui__react-portal.mjs +0 -16
- package/.output/server/_libs/radix-ui__react-presence.mjs +0 -173
- package/.output/server/_libs/radix-ui__react-primitive.mjs +0 -42
- package/.output/server/_libs/radix-ui__react-progress.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-radio-group.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-roving-focus.mjs +0 -235
- package/.output/server/_libs/radix-ui__react-scroll-area.mjs +0 -731
- package/.output/server/_libs/radix-ui__react-select.mjs +0 -1207
- package/.output/server/_libs/radix-ui__react-separator.mjs +0 -28
- package/.output/server/_libs/radix-ui__react-slider.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-slot.mjs +0 -132
- package/.output/server/_libs/radix-ui__react-switch.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-tabs.mjs +0 -194
- package/.output/server/_libs/radix-ui__react-toast.mjs +0 -2
- package/.output/server/_libs/radix-ui__react-toggle-group.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-toggle.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-toolbar.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-tooltip.mjs +0 -498
- package/.output/server/_libs/radix-ui__react-use-previous.mjs +0 -14
- package/.output/server/_libs/radix-ui__react-use-size.mjs +0 -39
- package/.output/server/_libs/react-markdown.mjs +0 -235
- package/.output/server/_libs/react-remove-scroll-bar.mjs +0 -82
- package/.output/server/_libs/react-remove-scroll.mjs +0 -328
- package/.output/server/_libs/react-style-singleton.mjs +0 -69
- package/.output/server/_libs/remark-parse.mjs +0 -19
- package/.output/server/_libs/remark-rehype.mjs +0 -21
- package/.output/server/_libs/space-separated-tokens.mjs +0 -6
- package/.output/server/_libs/style-to-js.mjs +0 -72
- package/.output/server/_libs/style-to-object.mjs +0 -38
- package/.output/server/_libs/swr.mjs +0 -947
- package/.output/server/_libs/tailwind-merge.mjs +0 -3255
- package/.output/server/_libs/tanstack__react-virtual.mjs +0 -130
- package/.output/server/_libs/tanstack__virtual-core.mjs +0 -1319
- package/.output/server/_libs/three.mjs +0 -26016
- package/.output/server/_libs/trim-lines.mjs +0 -41
- package/.output/server/_libs/trough.mjs +0 -85
- package/.output/server/_libs/tslib.mjs +0 -1
- package/.output/server/_libs/ungap__structured-clone.mjs +0 -212
- package/.output/server/_libs/unified.mjs +0 -661
- package/.output/server/_libs/unist-util-is.mjs +0 -100
- package/.output/server/_libs/unist-util-position.mjs +0 -27
- package/.output/server/_libs/unist-util-stringify-position.mjs +0 -27
- package/.output/server/_libs/unist-util-visit-parents.mjs +0 -82
- package/.output/server/_libs/unist-util-visit.mjs +0 -24
- package/.output/server/_libs/use-callback-ref.mjs +0 -66
- package/.output/server/_libs/use-sidecar.mjs +0 -106
- package/.output/server/_libs/vfile-message.mjs +0 -138
- package/.output/server/_libs/vfile.mjs +0 -467
- package/.output/server/_sessionId-CJ99gZFV.mjs +0 -84
- package/.output/server/_sessionId-DEjScBpK.mjs +0 -146
- package/.output/server/_ssr/CompareDrawer-DtOIMeQB.mjs +0 -1060
- package/.output/server/_ssr/InspectorPet-CXc4oCBQ.mjs +0 -938
- package/.output/server/_ssr/ProxyViewerContainer-DVPlwfkt.mjs +0 -17632
- package/.output/server/_ssr/ReplayDialog-BQEA95qC.mjs +0 -725
- package/.output/server/_ssr/RequestAnatomy-BMyd8B7Q.mjs +0 -837
- package/.output/server/_ssr/ResponseView-qrTaEIM8.mjs +0 -757
- package/.output/server/_ssr/StreamingChunkSequence-BFTL8mIy.mjs +0 -274
- package/.output/server/_ssr/index-Bcj1Hcy4.mjs +0 -135
- package/.output/server/_ssr/index-DW-M7XUG.mjs +0 -84
- package/.output/server/_ssr/json-viewer-DiI0Yjsn.mjs +0 -483
- package/.output/server/_tanstack-start-manifest_v-C6pXXX9R.mjs +0 -4
- package/.output/server/node_modules/tslib/modules/index.js +0 -70
- package/.output/server/node_modules/tslib/modules/package.json +0 -3
- package/.output/server/node_modules/tslib/package.json +0 -47
- package/.output/server/node_modules/tslib/tslib.js +0 -484
- package/.output/server/package.json +0 -9
- package/src/routes/__root.tsx +0 -170
- /package/.output/{public → ui}/assets/agent-inspector-58K1_MsC.ico +0 -0
- /package/.output/{public → ui}/assets/alibaba-TTwafVwX.svg +0 -0
- /package/.output/{public → ui}/assets/minimax-BPMzvuL-.jpeg +0 -0
- /package/.output/{public → ui}/assets/qwen-mMn3f5ul.webp +0 -0
- /package/.output/{public → ui}/assets/zhipuai-BPNAnxo-.svg +0 -0
- /package/src/{routes → backend/routes}/livez.ts +0 -0
|
@@ -12,8 +12,28 @@ import {
|
|
|
12
12
|
|
|
13
13
|
const DEFAULT_MEMORY_LIMIT_BYTES = 1024 * 1024;
|
|
14
14
|
const FILE_WRITE_BATCH_BYTES = 64 * 1024;
|
|
15
|
+
const DEFAULT_QUEUE_MAX_MESSAGES = 256;
|
|
16
|
+
const DEFAULT_QUEUE_MAX_BYTES = 8 * 1024 * 1024;
|
|
17
|
+
const DEFAULT_GLOBAL_QUEUE_MAX_MESSAGES = 4096;
|
|
18
|
+
const DEFAULT_GLOBAL_QUEUE_MAX_BYTES = 64 * 1024 * 1024;
|
|
15
19
|
const activeCaptures = new Set<RawStreamCapture>();
|
|
16
20
|
|
|
21
|
+
export type RawStreamCaptureGapReason = "capacity-exceeded" | "pipeline-closed" | "write-failed";
|
|
22
|
+
|
|
23
|
+
export type RawStreamCaptureStatus = {
|
|
24
|
+
incomplete: boolean;
|
|
25
|
+
droppedChunks: number;
|
|
26
|
+
droppedBytes: number;
|
|
27
|
+
reason: RawStreamCaptureGapReason | null;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type RawStreamCaptureOptions = {
|
|
31
|
+
memoryLimitBytes?: number;
|
|
32
|
+
queueMaxMessages?: number;
|
|
33
|
+
queueMaxBytes?: number;
|
|
34
|
+
globalBudget?: RawStreamObservationBudget;
|
|
35
|
+
};
|
|
36
|
+
|
|
17
37
|
export type RawStreamSource =
|
|
18
38
|
| {
|
|
19
39
|
type: "memory";
|
|
@@ -32,6 +52,62 @@ function resolveMemoryLimitBytes(): number {
|
|
|
32
52
|
return parsed;
|
|
33
53
|
}
|
|
34
54
|
|
|
55
|
+
function resolvePositiveIntegerEnv(name: string, fallback: number): number {
|
|
56
|
+
const raw = process.env[name];
|
|
57
|
+
if (raw === undefined || raw === "") return fallback;
|
|
58
|
+
const parsed = Number(raw);
|
|
59
|
+
if (!Number.isSafeInteger(parsed) || parsed < 1) return fallback;
|
|
60
|
+
return parsed;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function defaultQueueMaxMessages(): number {
|
|
64
|
+
return resolvePositiveIntegerEnv(
|
|
65
|
+
"AGENT_INSPECTOR_OBSERVATION_QUEUE_MESSAGES",
|
|
66
|
+
DEFAULT_QUEUE_MAX_MESSAGES,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function defaultQueueMaxBytes(): number {
|
|
71
|
+
return resolvePositiveIntegerEnv(
|
|
72
|
+
"AGENT_INSPECTOR_OBSERVATION_QUEUE_BYTES",
|
|
73
|
+
DEFAULT_QUEUE_MAX_BYTES,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export class RawStreamObservationBudget {
|
|
78
|
+
private messages = 0;
|
|
79
|
+
private bytes = 0;
|
|
80
|
+
|
|
81
|
+
constructor(
|
|
82
|
+
private readonly maxMessages: number,
|
|
83
|
+
private readonly maxBytes: number,
|
|
84
|
+
) {}
|
|
85
|
+
|
|
86
|
+
tryAcquire(bytes: number): (() => void) | null {
|
|
87
|
+
if (this.messages + 1 > this.maxMessages || this.bytes + bytes > this.maxBytes) return null;
|
|
88
|
+
this.messages += 1;
|
|
89
|
+
this.bytes += bytes;
|
|
90
|
+
let released = false;
|
|
91
|
+
return () => {
|
|
92
|
+
if (released) return;
|
|
93
|
+
released = true;
|
|
94
|
+
this.messages -= 1;
|
|
95
|
+
this.bytes -= bytes;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const globalObservationBudget = new RawStreamObservationBudget(
|
|
101
|
+
resolvePositiveIntegerEnv(
|
|
102
|
+
"AGENT_INSPECTOR_OBSERVATION_GLOBAL_QUEUE_MESSAGES",
|
|
103
|
+
DEFAULT_GLOBAL_QUEUE_MAX_MESSAGES,
|
|
104
|
+
),
|
|
105
|
+
resolvePositiveIntegerEnv(
|
|
106
|
+
"AGENT_INSPECTOR_OBSERVATION_GLOBAL_QUEUE_BYTES",
|
|
107
|
+
DEFAULT_GLOBAL_QUEUE_MAX_BYTES,
|
|
108
|
+
),
|
|
109
|
+
);
|
|
110
|
+
|
|
35
111
|
function getRawStreamDir(): string {
|
|
36
112
|
return join(getDataDir(), "raw-streams");
|
|
37
113
|
}
|
|
@@ -42,6 +118,9 @@ function createRawStreamPath(): string {
|
|
|
42
118
|
|
|
43
119
|
export class RawStreamCapture {
|
|
44
120
|
private readonly memoryLimitBytes: number;
|
|
121
|
+
private readonly queueMaxMessages: number;
|
|
122
|
+
private readonly queueMaxBytes: number;
|
|
123
|
+
private readonly globalBudget: RawStreamObservationBudget;
|
|
45
124
|
private chunks: string[] = [];
|
|
46
125
|
private byteLength = 0;
|
|
47
126
|
private filePath: string | null = null;
|
|
@@ -49,11 +128,22 @@ export class RawStreamCapture {
|
|
|
49
128
|
private pendingFileChunks: string[] = [];
|
|
50
129
|
private pendingFileBytes = 0;
|
|
51
130
|
private operation = Promise.resolve();
|
|
131
|
+
private accepting = true;
|
|
52
132
|
private closed = false;
|
|
53
133
|
private sawChunk = false;
|
|
134
|
+
private queuedMessages = 0;
|
|
135
|
+
private queuedBytes = 0;
|
|
136
|
+
private incomplete = false;
|
|
137
|
+
private storageFailed = false;
|
|
138
|
+
private droppedChunks = 0;
|
|
139
|
+
private droppedBytes = 0;
|
|
140
|
+
private gapReason: RawStreamCaptureGapReason | null = null;
|
|
54
141
|
|
|
55
|
-
constructor(
|
|
56
|
-
this.memoryLimitBytes = memoryLimitBytes;
|
|
142
|
+
constructor(options: RawStreamCaptureOptions = {}) {
|
|
143
|
+
this.memoryLimitBytes = options.memoryLimitBytes ?? resolveMemoryLimitBytes();
|
|
144
|
+
this.queueMaxMessages = options.queueMaxMessages ?? defaultQueueMaxMessages();
|
|
145
|
+
this.queueMaxBytes = options.queueMaxBytes ?? defaultQueueMaxBytes();
|
|
146
|
+
this.globalBudget = options.globalBudget ?? globalObservationBudget;
|
|
57
147
|
activeCaptures.add(this);
|
|
58
148
|
}
|
|
59
149
|
|
|
@@ -61,6 +151,21 @@ export class RawStreamCapture {
|
|
|
61
151
|
return this.sawChunk;
|
|
62
152
|
}
|
|
63
153
|
|
|
154
|
+
get status(): RawStreamCaptureStatus {
|
|
155
|
+
return {
|
|
156
|
+
incomplete: this.incomplete,
|
|
157
|
+
droppedChunks: this.droppedChunks,
|
|
158
|
+
droppedBytes: this.droppedBytes,
|
|
159
|
+
reason: this.gapReason,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
recordDroppedObservation(reason: RawStreamCaptureGapReason, observedBytes: number): void {
|
|
164
|
+
const safeObservedBytes =
|
|
165
|
+
Number.isSafeInteger(observedBytes) && observedBytes >= 0 ? observedBytes : 0;
|
|
166
|
+
this.recordGap(reason, safeObservedBytes, safeObservedBytes > 0);
|
|
167
|
+
}
|
|
168
|
+
|
|
64
169
|
async append(text: string): Promise<void> {
|
|
65
170
|
if (text === "") return;
|
|
66
171
|
const next = this.operation.then(() => this.appendInternal(text));
|
|
@@ -68,7 +173,63 @@ export class RawStreamCapture {
|
|
|
68
173
|
return next;
|
|
69
174
|
}
|
|
70
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Submit an observation without waiting for file I/O. Once capacity is exceeded, capture remains
|
|
178
|
+
* incomplete and all later chunks are counted as dropped so the persisted value is an honest
|
|
179
|
+
* prefix rather than a misleading stream with an unmarked hole.
|
|
180
|
+
*/
|
|
181
|
+
tryAppend(text: string, observedBytes: number): boolean {
|
|
182
|
+
const safeObservedBytes =
|
|
183
|
+
Number.isSafeInteger(observedBytes) && observedBytes >= 0 ? observedBytes : 0;
|
|
184
|
+
const queuedBytes = Math.max(safeObservedBytes, Buffer.byteLength(text, "utf-8"));
|
|
185
|
+
|
|
186
|
+
if (!this.accepting || this.closed) {
|
|
187
|
+
this.recordGap("pipeline-closed", safeObservedBytes, safeObservedBytes > 0 || text !== "");
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
if (this.incomplete) {
|
|
191
|
+
this.recordGap("capacity-exceeded", safeObservedBytes, safeObservedBytes > 0 || text !== "");
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
if (text === "") return true;
|
|
195
|
+
if (
|
|
196
|
+
this.queuedMessages + 1 > this.queueMaxMessages ||
|
|
197
|
+
this.queuedBytes + queuedBytes > this.queueMaxBytes
|
|
198
|
+
) {
|
|
199
|
+
this.recordGap("capacity-exceeded", safeObservedBytes, true);
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const releaseGlobal = this.globalBudget.tryAcquire(queuedBytes);
|
|
204
|
+
if (releaseGlobal === null) {
|
|
205
|
+
this.recordGap("capacity-exceeded", safeObservedBytes, true);
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
this.queuedMessages += 1;
|
|
210
|
+
this.queuedBytes += queuedBytes;
|
|
211
|
+
this.operation = this.operation
|
|
212
|
+
.then(async () => {
|
|
213
|
+
if (this.storageFailed) {
|
|
214
|
+
this.recordGap("write-failed", safeObservedBytes, true);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
await this.appendInternal(text);
|
|
218
|
+
})
|
|
219
|
+
.catch(() => {
|
|
220
|
+
this.storageFailed = true;
|
|
221
|
+
this.recordGap("write-failed", safeObservedBytes, true);
|
|
222
|
+
})
|
|
223
|
+
.finally(() => {
|
|
224
|
+
this.queuedMessages -= 1;
|
|
225
|
+
this.queuedBytes -= queuedBytes;
|
|
226
|
+
releaseGlobal();
|
|
227
|
+
});
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
|
|
71
231
|
async finalize(): Promise<RawStreamSource> {
|
|
232
|
+
this.accepting = false;
|
|
72
233
|
const next = this.operation.then(() => this.finalizeInternal());
|
|
73
234
|
this.operation = next.then(
|
|
74
235
|
() => undefined,
|
|
@@ -100,14 +261,29 @@ export class RawStreamCapture {
|
|
|
100
261
|
}
|
|
101
262
|
}
|
|
102
263
|
|
|
264
|
+
private recordGap(
|
|
265
|
+
reason: RawStreamCaptureGapReason,
|
|
266
|
+
observedBytes: number,
|
|
267
|
+
countChunk: boolean,
|
|
268
|
+
): void {
|
|
269
|
+
this.incomplete = true;
|
|
270
|
+
this.gapReason ??= reason;
|
|
271
|
+
if (countChunk) this.droppedChunks += 1;
|
|
272
|
+
this.droppedBytes += observedBytes;
|
|
273
|
+
}
|
|
274
|
+
|
|
103
275
|
private async finalizeInternal(): Promise<RawStreamSource> {
|
|
104
276
|
if (!this.closed) {
|
|
105
277
|
this.closed = true;
|
|
106
278
|
await this.flushFileBuffer();
|
|
107
279
|
if (this.fileHandle !== null) {
|
|
108
|
-
|
|
109
|
-
await this.fileHandle.close();
|
|
280
|
+
const handle = this.fileHandle;
|
|
110
281
|
this.fileHandle = null;
|
|
282
|
+
try {
|
|
283
|
+
await handle.sync();
|
|
284
|
+
} finally {
|
|
285
|
+
await handle.close();
|
|
286
|
+
}
|
|
111
287
|
}
|
|
112
288
|
}
|
|
113
289
|
if (this.filePath !== null) {
|
|
@@ -120,16 +296,21 @@ export class RawStreamCapture {
|
|
|
120
296
|
const path = createRawStreamPath();
|
|
121
297
|
assert(ensurePrivateDirectorySync(getRawStreamDir()), "Could not secure raw-stream directory");
|
|
122
298
|
const handle = await open(path, "wx", PRIVATE_FILE_MODE);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
await handle.
|
|
127
|
-
|
|
128
|
-
|
|
299
|
+
let committed = false;
|
|
300
|
+
try {
|
|
301
|
+
const memoryText = this.chunks.join("");
|
|
302
|
+
if (memoryText.length > 0) await handle.write(memoryText, undefined, "utf-8");
|
|
303
|
+
assert(securePrivateFileSync(path), "Could not secure raw-stream file");
|
|
304
|
+
this.filePath = path;
|
|
305
|
+
this.fileHandle = handle;
|
|
306
|
+
this.chunks = [];
|
|
307
|
+
committed = true;
|
|
308
|
+
} finally {
|
|
309
|
+
if (!committed) {
|
|
310
|
+
await handle.close().catch(() => undefined);
|
|
311
|
+
await unlink(path).catch(() => undefined);
|
|
312
|
+
}
|
|
129
313
|
}
|
|
130
|
-
this.filePath = path;
|
|
131
|
-
this.fileHandle = handle;
|
|
132
|
-
this.chunks = [];
|
|
133
314
|
}
|
|
134
315
|
|
|
135
316
|
private async flushFileBuffer(): Promise<void> {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { MAX_ACTIVE_PROXY_REQUESTS } from "../lib/resourceLimits";
|
|
2
|
+
|
|
1
3
|
export type RuntimeRequestLease = {
|
|
2
4
|
release: () => void;
|
|
3
5
|
};
|
|
@@ -13,7 +15,7 @@ function notifyDrained(): void {
|
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export function acquireRuntimeRequest(): RuntimeRequestLease | null {
|
|
16
|
-
if (!acceptingRequests) return null;
|
|
18
|
+
if (!acceptingRequests || activeRequests >= MAX_ACTIVE_PROXY_REQUESTS) return null;
|
|
17
19
|
activeRequests += 1;
|
|
18
20
|
let released = false;
|
|
19
21
|
return {
|
|
@@ -41,8 +43,9 @@ export async function waitForRuntimeTrafficDrain(): Promise<void> {
|
|
|
41
43
|
export function getRuntimeAdmissionState(): {
|
|
42
44
|
acceptingRequests: boolean;
|
|
43
45
|
activeRequests: number;
|
|
46
|
+
capacity: number;
|
|
44
47
|
} {
|
|
45
|
-
return { acceptingRequests, activeRequests };
|
|
48
|
+
return { acceptingRequests, activeRequests, capacity: MAX_ACTIVE_PROXY_REQUESTS };
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
export function _resetRuntimeAdmissionForTests(): void {
|
|
@@ -5,6 +5,9 @@ import { getDataDir } from "./dataDir";
|
|
|
5
5
|
import { getFinalizerRuntimeHealth } from "./logFinalizer";
|
|
6
6
|
import { ensurePrivateDirectorySync, PRIVATE_FILE_MODE } from "./privateDataPath";
|
|
7
7
|
import { isSqliteLogIndexReady } from "./sqliteLogIndex";
|
|
8
|
+
import { recoverCapturedLogStorage } from "./jsonlRecovery";
|
|
9
|
+
import { resolveLogDir } from "./logger";
|
|
10
|
+
import { loadIndex } from "./logIndex";
|
|
8
11
|
|
|
9
12
|
const DEFAULT_CACHE_MS = 2_000;
|
|
10
13
|
const DEFAULT_MIN_DISK_HEADROOM_BYTES = 256 * 1024 * 1024;
|
|
@@ -118,7 +121,17 @@ function checkQueue(): Promise<ReadinessCheckLevel> {
|
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
async function checkIndex(): Promise<ReadinessCheckLevel> {
|
|
121
|
-
|
|
124
|
+
const recovery = await recoverCapturedLogStorage(resolveLogDir());
|
|
125
|
+
if (recovery.level === "unavailable") return "unavailable";
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
await loadIndex();
|
|
129
|
+
} catch {
|
|
130
|
+
return "unavailable";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const sqliteReady = await isSqliteLogIndexReady();
|
|
134
|
+
return recovery.level === "degraded" || !sqliteReady ? "degraded" : "ok";
|
|
122
135
|
}
|
|
123
136
|
|
|
124
137
|
function checkShutdown(): Promise<ReadinessCheckLevel> {
|
|
@@ -15,6 +15,11 @@ import {
|
|
|
15
15
|
type ShutdownResult,
|
|
16
16
|
} from "./shutdownCoordinator";
|
|
17
17
|
import { destroyAllSessionProcesses } from "./sessionProcess";
|
|
18
|
+
import {
|
|
19
|
+
beginEcosystemTaskShutdown,
|
|
20
|
+
forceCloseEcosystemTasks,
|
|
21
|
+
waitForEcosystemTasksToClose,
|
|
22
|
+
} from "./ecosystemTasks";
|
|
18
23
|
import { flushSessionTasks } from "./sessionRuntime";
|
|
19
24
|
import { closeSqliteLogIndex } from "./sqliteLogIndex";
|
|
20
25
|
import { flushSessionArchiveWrites } from "./store";
|
|
@@ -45,6 +50,7 @@ async function flushPersistence(): Promise<void> {
|
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
async function closeResources(): Promise<void> {
|
|
53
|
+
await waitForEcosystemTasksToClose();
|
|
48
54
|
await closeLogFinalizerWorkers();
|
|
49
55
|
destroyAllSessionProcesses();
|
|
50
56
|
closeSqliteLogIndex();
|
|
@@ -52,6 +58,7 @@ async function closeResources(): Promise<void> {
|
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
function forceCloseResources(): void {
|
|
61
|
+
forceCloseEcosystemTasks();
|
|
55
62
|
forceCloseLogFinalizerWorkers();
|
|
56
63
|
destroyAllSessionProcesses();
|
|
57
64
|
closeSqliteLogIndex();
|
|
@@ -59,7 +66,10 @@ function forceCloseResources(): void {
|
|
|
59
66
|
|
|
60
67
|
const runtimeShutdownCoordinator = createShutdownCoordinator(
|
|
61
68
|
{
|
|
62
|
-
markShuttingDown: () =>
|
|
69
|
+
markShuttingDown: () => {
|
|
70
|
+
setRuntimeShuttingDown(true);
|
|
71
|
+
beginEcosystemTaskShutdown();
|
|
72
|
+
},
|
|
63
73
|
stopAdmission: stopRuntimeAdmission,
|
|
64
74
|
drainTraffic: waitForRuntimeTrafficDrain,
|
|
65
75
|
drainFinalizers,
|
package/src/proxy/schemas.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export {
|
|
4
|
+
CaptureIncompleteReasonSchema,
|
|
5
|
+
CapturedLogSchema,
|
|
6
|
+
JsonValueSchema,
|
|
7
|
+
StreamingChunkSchema,
|
|
8
|
+
} from "../contracts";
|
|
4
9
|
export type {
|
|
5
10
|
CapturedLog,
|
|
6
11
|
ApiFormat,
|
|
12
|
+
CaptureIncompleteReason,
|
|
7
13
|
JsonValue,
|
|
8
14
|
StreamingChunk,
|
|
9
15
|
TokenUsage,
|
|
@@ -40,6 +40,7 @@ ON CONFLICT(id) DO UPDATE SET
|
|
|
40
40
|
`;
|
|
41
41
|
|
|
42
42
|
let didLogUnavailable = false;
|
|
43
|
+
let cachedArchiveMax: { root: string; maxId: number } | null = null;
|
|
43
44
|
|
|
44
45
|
export function getSessionArchiveRoot(): string {
|
|
45
46
|
return join(resolveLogDir(), SESSION_ARCHIVE_DIR);
|
|
@@ -236,23 +237,49 @@ async function openSessionArchiveDb(filePath: string, create: boolean): Promise<
|
|
|
236
237
|
}
|
|
237
238
|
}
|
|
238
239
|
|
|
239
|
-
export async function
|
|
240
|
-
const
|
|
241
|
-
|
|
240
|
+
export async function archiveSessionLogs(logs: readonly CapturedLog[]): Promise<number> {
|
|
241
|
+
const bySession = new Map<string, CapturedLog[]>();
|
|
242
|
+
for (const log of logs) {
|
|
243
|
+
const sessionId = log.sessionId;
|
|
244
|
+
if (sessionId === null || sessionId.trim().length === 0) continue;
|
|
245
|
+
const existing = bySession.get(sessionId);
|
|
246
|
+
if (existing === undefined) bySession.set(sessionId, [log]);
|
|
247
|
+
else existing.push(log);
|
|
248
|
+
}
|
|
242
249
|
|
|
243
|
-
|
|
244
|
-
|
|
250
|
+
let archivedCount = 0;
|
|
251
|
+
for (const [sessionId, sessionLogs] of bySession) {
|
|
252
|
+
const db = await openSessionArchiveDb(getSessionArchivePath(sessionId), true);
|
|
253
|
+
if (db === null || !execSql(db, "BEGIN IMMEDIATE")) {
|
|
254
|
+
if (db !== null) closeDatabase(db);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
245
257
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
258
|
+
let committed = false;
|
|
259
|
+
try {
|
|
260
|
+
const updatedAt = new Date().toISOString();
|
|
261
|
+
const archived = sessionLogs.every((log) =>
|
|
262
|
+
runPrepared(db, UPSERT_SQL, [log.id, sessionId, JSON.stringify(log), updatedAt]),
|
|
263
|
+
);
|
|
264
|
+
if (archived && execSql(db, "COMMIT")) {
|
|
265
|
+
committed = true;
|
|
266
|
+
archivedCount += sessionLogs.length;
|
|
267
|
+
if (cachedArchiveMax?.root === getSessionArchiveRoot()) {
|
|
268
|
+
for (const log of sessionLogs) {
|
|
269
|
+
cachedArchiveMax.maxId = Math.max(cachedArchiveMax.maxId, log.id);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
} finally {
|
|
274
|
+
if (!committed) void execSql(db, "ROLLBACK");
|
|
275
|
+
closeDatabase(db);
|
|
276
|
+
}
|
|
255
277
|
}
|
|
278
|
+
return archivedCount;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export async function archiveSessionLog(log: CapturedLog): Promise<boolean> {
|
|
282
|
+
return (await archiveSessionLogs([log])) === 1;
|
|
256
283
|
}
|
|
257
284
|
|
|
258
285
|
function readString(row: unknown, name: string): string | null {
|
|
@@ -343,6 +370,36 @@ async function listArchiveDatabasePaths(): Promise<string[]> {
|
|
|
343
370
|
}
|
|
344
371
|
}
|
|
345
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Return the highest ID retained by an authoritative session archive. The global SQLite index is a
|
|
375
|
+
* rebuildable projection and must never influence ID allocation, while archive IDs must remain
|
|
376
|
+
* reserved even after their corresponding global JSONL records have aged out.
|
|
377
|
+
*/
|
|
378
|
+
export async function getSessionArchiveMaxLogId(): Promise<number> {
|
|
379
|
+
const root = getSessionArchiveRoot();
|
|
380
|
+
if (cachedArchiveMax?.root === root) return cachedArchiveMax.maxId;
|
|
381
|
+
|
|
382
|
+
let maxId = 0;
|
|
383
|
+
const archivePaths = await listArchiveDatabasePaths();
|
|
384
|
+
for (const archivePath of archivePaths) {
|
|
385
|
+
const db = await openSessionArchiveDb(archivePath, false);
|
|
386
|
+
if (db === null) continue;
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
const rows = allPrepared(db, "SELECT MAX(id) AS max_id FROM session_logs");
|
|
390
|
+
const archiveMax = readNumber(rows[0], "max_id");
|
|
391
|
+
if (archiveMax !== null && Number.isSafeInteger(archiveMax) && archiveMax >= 0) {
|
|
392
|
+
maxId = Math.max(maxId, archiveMax);
|
|
393
|
+
}
|
|
394
|
+
} finally {
|
|
395
|
+
closeDatabase(db);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
cachedArchiveMax = { root, maxId };
|
|
400
|
+
return maxId;
|
|
401
|
+
}
|
|
402
|
+
|
|
346
403
|
export async function findArchivedLogById(id: number): Promise<CapturedLog | null> {
|
|
347
404
|
const archivePaths = await listArchiveDatabasePaths();
|
|
348
405
|
for (const archivePath of archivePaths) {
|
|
@@ -405,6 +462,8 @@ export async function deleteArchivedLogsByIds(ids: ReadonlySet<number>): Promise
|
|
|
405
462
|
}
|
|
406
463
|
}
|
|
407
464
|
|
|
465
|
+
if (removedIds.size > 0) cachedArchiveMax = null;
|
|
466
|
+
|
|
408
467
|
return removedIds;
|
|
409
468
|
}
|
|
410
469
|
|
|
@@ -415,9 +474,14 @@ export async function clearSessionArchives(): Promise<number> {
|
|
|
415
474
|
const archivePaths = await listArchiveDatabasePaths();
|
|
416
475
|
try {
|
|
417
476
|
await rm(root, { recursive: true, force: true });
|
|
477
|
+
cachedArchiveMax = null;
|
|
418
478
|
return archivePaths.length;
|
|
419
479
|
} catch (err) {
|
|
420
480
|
logger.warn("[sessionArchive] Failed to clear session archives:", String(err));
|
|
421
481
|
return 0;
|
|
422
482
|
}
|
|
423
483
|
}
|
|
484
|
+
|
|
485
|
+
export function _resetSessionArchiveCacheForTests(): void {
|
|
486
|
+
cachedArchiveMax = null;
|
|
487
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { logger, resolveLogDir } from "./logger";
|
|
4
|
-
import type { ApiFormat } from "./schemas";
|
|
4
|
+
import type { ApiFormat, CaptureIncompleteReason } from "./schemas";
|
|
5
5
|
import type { LogIndexEntry, LogIndexSummary } from "./logIndex";
|
|
6
6
|
import { ensurePrivateDirectorySync, securePrivateFileSync } from "./privateDataPath";
|
|
7
7
|
|
|
@@ -47,6 +47,10 @@ CREATE TABLE IF NOT EXISTS log_index (
|
|
|
47
47
|
streaming_chunks_path TEXT,
|
|
48
48
|
raw_request_body_bytes INTEGER,
|
|
49
49
|
response_text_bytes INTEGER,
|
|
50
|
+
capture_incomplete INTEGER NOT NULL DEFAULT 0,
|
|
51
|
+
dropped_chunks INTEGER NOT NULL DEFAULT 0,
|
|
52
|
+
dropped_bytes INTEGER NOT NULL DEFAULT 0,
|
|
53
|
+
capture_incomplete_reason TEXT,
|
|
50
54
|
warnings_json TEXT,
|
|
51
55
|
error TEXT
|
|
52
56
|
);
|
|
@@ -89,10 +93,14 @@ INSERT INTO log_index (
|
|
|
89
93
|
streaming_chunks_path,
|
|
90
94
|
raw_request_body_bytes,
|
|
91
95
|
response_text_bytes,
|
|
96
|
+
capture_incomplete,
|
|
97
|
+
dropped_chunks,
|
|
98
|
+
dropped_bytes,
|
|
99
|
+
capture_incomplete_reason,
|
|
92
100
|
warnings_json,
|
|
93
101
|
error
|
|
94
102
|
) VALUES (
|
|
95
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
103
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
96
104
|
)
|
|
97
105
|
ON CONFLICT(id) DO UPDATE SET
|
|
98
106
|
file = excluded.file,
|
|
@@ -126,6 +134,10 @@ ON CONFLICT(id) DO UPDATE SET
|
|
|
126
134
|
streaming_chunks_path = excluded.streaming_chunks_path,
|
|
127
135
|
raw_request_body_bytes = excluded.raw_request_body_bytes,
|
|
128
136
|
response_text_bytes = excluded.response_text_bytes,
|
|
137
|
+
capture_incomplete = excluded.capture_incomplete,
|
|
138
|
+
dropped_chunks = excluded.dropped_chunks,
|
|
139
|
+
dropped_bytes = excluded.dropped_bytes,
|
|
140
|
+
capture_incomplete_reason = excluded.capture_incomplete_reason,
|
|
129
141
|
warnings_json = excluded.warnings_json,
|
|
130
142
|
error = excluded.error
|
|
131
143
|
`;
|
|
@@ -215,6 +227,30 @@ function execSql(db: unknown, sql: string): boolean {
|
|
|
215
227
|
return result !== undefined;
|
|
216
228
|
}
|
|
217
229
|
|
|
230
|
+
const REQUIRED_SCHEMA_COLUMNS = [
|
|
231
|
+
{ name: "capture_incomplete", definition: "INTEGER NOT NULL DEFAULT 0" },
|
|
232
|
+
{ name: "dropped_chunks", definition: "INTEGER NOT NULL DEFAULT 0" },
|
|
233
|
+
{ name: "dropped_bytes", definition: "INTEGER NOT NULL DEFAULT 0" },
|
|
234
|
+
{ name: "capture_incomplete_reason", definition: "TEXT" },
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
function ensureSchemaColumns(db: unknown): boolean {
|
|
238
|
+
const rows = allPrepared(db, "PRAGMA table_info(log_index)");
|
|
239
|
+
if (rows === null) return false;
|
|
240
|
+
const existing = new Set<string>();
|
|
241
|
+
for (const row of rows) {
|
|
242
|
+
const name = readProperty(row, "name");
|
|
243
|
+
if (typeof name === "string") existing.add(name);
|
|
244
|
+
}
|
|
245
|
+
for (const column of REQUIRED_SCHEMA_COLUMNS) {
|
|
246
|
+
if (existing.has(column.name)) continue;
|
|
247
|
+
if (!execSql(db, `ALTER TABLE log_index ADD COLUMN ${column.name} ${column.definition}`)) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
|
|
218
254
|
function sqliteConstructorFromModule(
|
|
219
255
|
moduleValue: unknown,
|
|
220
256
|
exportName: "default" | "Database",
|
|
@@ -316,6 +352,9 @@ async function initializeSqliteState(): Promise<SqliteState> {
|
|
|
316
352
|
if (!execSql(db, CREATE_SCHEMA_SQL)) {
|
|
317
353
|
return { status: "unavailable", path, reason: "SQLite schema initialization failed" };
|
|
318
354
|
}
|
|
355
|
+
if (!ensureSchemaColumns(db)) {
|
|
356
|
+
return { status: "unavailable", path, reason: "SQLite schema migration failed" };
|
|
357
|
+
}
|
|
319
358
|
activeDb = db;
|
|
320
359
|
return { status: "ready", db, path };
|
|
321
360
|
} catch (err) {
|
|
@@ -394,6 +433,10 @@ function entryParams(entry: LogIndexEntry): readonly unknown[] {
|
|
|
394
433
|
summary?.streamingChunksPath ?? null,
|
|
395
434
|
summary?.rawRequestBodyBytes ?? null,
|
|
396
435
|
summary?.responseTextBytes ?? null,
|
|
436
|
+
boolToInt(summary?.captureIncomplete ?? false),
|
|
437
|
+
summary?.droppedChunks ?? 0,
|
|
438
|
+
summary?.droppedBytes ?? 0,
|
|
439
|
+
summary?.captureIncompleteReason ?? null,
|
|
397
440
|
warningJson(summary?.warnings),
|
|
398
441
|
summary?.error ?? null,
|
|
399
442
|
];
|
|
@@ -428,6 +471,22 @@ function readApiFormat(row: unknown): ApiFormat {
|
|
|
428
471
|
}
|
|
429
472
|
}
|
|
430
473
|
|
|
474
|
+
function readCaptureIncompleteReason(row: unknown): CaptureIncompleteReason | null {
|
|
475
|
+
const value = readString(row, "capture_incomplete_reason");
|
|
476
|
+
switch (value) {
|
|
477
|
+
case "capacity-exceeded":
|
|
478
|
+
case "pipeline-closed":
|
|
479
|
+
case "write-failed":
|
|
480
|
+
case "finalize-failed":
|
|
481
|
+
case "upstream-stream-error":
|
|
482
|
+
case "shutdown":
|
|
483
|
+
return value;
|
|
484
|
+
case null:
|
|
485
|
+
default:
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
431
490
|
function readWarnings(row: unknown): string[] | undefined {
|
|
432
491
|
const value = readString(row, "warnings_json");
|
|
433
492
|
if (value === null) return undefined;
|
|
@@ -481,6 +540,10 @@ function summaryFromRow(row: unknown, id: number): LogIndexSummary {
|
|
|
481
540
|
streamingChunksPath: readString(row, "streaming_chunks_path"),
|
|
482
541
|
rawRequestBodyBytes: readNumber(row, "raw_request_body_bytes"),
|
|
483
542
|
responseTextBytes: readNumber(row, "response_text_bytes"),
|
|
543
|
+
captureIncomplete: readBoolean(row, "capture_incomplete"),
|
|
544
|
+
droppedChunks: readNumber(row, "dropped_chunks") ?? 0,
|
|
545
|
+
droppedBytes: readNumber(row, "dropped_bytes") ?? 0,
|
|
546
|
+
captureIncompleteReason: readCaptureIncompleteReason(row),
|
|
484
547
|
warnings: readWarnings(row),
|
|
485
548
|
error: readString(row, "error"),
|
|
486
549
|
};
|