@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
package/src/proxy/logIndex.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { writeFile, readdir, rename, unlink, open } from "node:fs/promises";
|
|
3
|
+
import { constants, existsSync } from "node:fs";
|
|
3
4
|
import { join, dirname } from "node:path";
|
|
4
|
-
import { createInterface } from "node:readline";
|
|
5
5
|
import { Buffer } from "node:buffer";
|
|
6
6
|
import { resolveLogDir, logger } from "./logger";
|
|
7
|
-
import
|
|
7
|
+
import {
|
|
8
|
+
CapturedLogSchema,
|
|
9
|
+
type ApiFormat,
|
|
10
|
+
type CaptureIncompleteReason,
|
|
11
|
+
type CapturedLog,
|
|
12
|
+
} from "./schemas";
|
|
8
13
|
import { textByteLength } from "../lib/unknown";
|
|
9
14
|
import {
|
|
10
|
-
findSqliteLogIndexEntry,
|
|
11
15
|
getSqliteLogIndexMaxId,
|
|
12
|
-
listSqliteLogIndexEntries,
|
|
13
16
|
replaceSqliteLogIndexEntries,
|
|
14
17
|
syncSqliteLogIndexEntries,
|
|
15
18
|
upsertSqliteLogIndexEntry,
|
|
@@ -19,6 +22,8 @@ import {
|
|
|
19
22
|
PRIVATE_FILE_MODE,
|
|
20
23
|
securePrivateFileSync,
|
|
21
24
|
} from "./privateDataPath";
|
|
25
|
+
import { recoverCapturedLogStorage } from "./jsonlRecovery";
|
|
26
|
+
import { getSessionArchiveMaxLogId } from "./sessionArchive";
|
|
22
27
|
|
|
23
28
|
export type LogIndexSummary = {
|
|
24
29
|
id: number;
|
|
@@ -50,6 +55,10 @@ export type LogIndexSummary = {
|
|
|
50
55
|
streamingChunksPath: string | null;
|
|
51
56
|
rawRequestBodyBytes: number | null;
|
|
52
57
|
responseTextBytes: number | null;
|
|
58
|
+
captureIncomplete: boolean;
|
|
59
|
+
droppedChunks: number;
|
|
60
|
+
droppedBytes: number;
|
|
61
|
+
captureIncompleteReason: CaptureIncompleteReason | null;
|
|
53
62
|
warnings?: string[];
|
|
54
63
|
error: string | null;
|
|
55
64
|
};
|
|
@@ -78,6 +87,7 @@ type LogIndex = {
|
|
|
78
87
|
|
|
79
88
|
const INDEX_VERSION = 1;
|
|
80
89
|
const INDEX_FILE = "logs.idx";
|
|
90
|
+
const INDEX_READ_CHUNK_BYTES = 64 * 1024;
|
|
81
91
|
|
|
82
92
|
export { resolveLogDir as getLogDir };
|
|
83
93
|
|
|
@@ -86,6 +96,7 @@ function getIndexPath(): string {
|
|
|
86
96
|
}
|
|
87
97
|
|
|
88
98
|
let cachedIndex: LogIndex | null = null;
|
|
99
|
+
let allocatedMaxId = 0;
|
|
89
100
|
let sqliteBackfillScheduledMaxId = 0;
|
|
90
101
|
|
|
91
102
|
export function createLogIndexEntryMetadata(log: CapturedLog): LogIndexEntryMetadata {
|
|
@@ -122,6 +133,10 @@ export function createLogIndexEntryMetadata(log: CapturedLog): LogIndexEntryMeta
|
|
|
122
133
|
streamingChunksPath: log.streamingChunksPath ?? null,
|
|
123
134
|
rawRequestBodyBytes: log.rawRequestBodyBytes ?? textByteLength(log.rawRequestBody),
|
|
124
135
|
responseTextBytes: log.responseTextBytes ?? textByteLength(log.responseText),
|
|
136
|
+
captureIncomplete: log.captureIncomplete ?? false,
|
|
137
|
+
droppedChunks: log.droppedChunks ?? 0,
|
|
138
|
+
droppedBytes: log.droppedBytes ?? 0,
|
|
139
|
+
captureIncompleteReason: log.captureIncompleteReason ?? null,
|
|
125
140
|
warnings: log.warnings,
|
|
126
141
|
error: log.error ?? null,
|
|
127
142
|
},
|
|
@@ -136,45 +151,11 @@ function createEmptyIndex(): LogIndex {
|
|
|
136
151
|
};
|
|
137
152
|
}
|
|
138
153
|
|
|
139
|
-
function isLogIndex(obj: unknown): obj is LogIndex {
|
|
140
|
-
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) return false;
|
|
141
|
-
const versionDesc = Object.getOwnPropertyDescriptor(obj, "version");
|
|
142
|
-
const entriesDesc = Object.getOwnPropertyDescriptor(obj, "entries");
|
|
143
|
-
const maxIdDesc = Object.getOwnPropertyDescriptor(obj, "maxId");
|
|
144
|
-
return (
|
|
145
|
-
versionDesc !== undefined &&
|
|
146
|
-
typeof versionDesc.value === "number" &&
|
|
147
|
-
entriesDesc !== undefined &&
|
|
148
|
-
typeof entriesDesc.value === "object" &&
|
|
149
|
-
entriesDesc.value !== null &&
|
|
150
|
-
maxIdDesc !== undefined &&
|
|
151
|
-
typeof maxIdDesc.value === "number"
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
154
|
export async function loadIndex(): Promise<LogIndex> {
|
|
156
155
|
if (cachedIndex !== null) return cachedIndex;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
cachedIndex = createEmptyIndex();
|
|
161
|
-
return cachedIndex;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
try {
|
|
165
|
-
const content = await readFile(indexPath, "utf-8");
|
|
166
|
-
const parsed: unknown = JSON.parse(content);
|
|
167
|
-
if (isLogIndex(parsed)) {
|
|
168
|
-
cachedIndex = parsed;
|
|
169
|
-
} else {
|
|
170
|
-
cachedIndex = createEmptyIndex();
|
|
171
|
-
}
|
|
172
|
-
return cachedIndex;
|
|
173
|
-
} catch (err) {
|
|
174
|
-
logger.error("[logIndex] Failed to load index:", String(err));
|
|
175
|
-
cachedIndex = createEmptyIndex();
|
|
176
|
-
return cachedIndex;
|
|
177
|
-
}
|
|
156
|
+
// logs.idx and inspector.sqlite are projections only. A cold process always rebuilds from
|
|
157
|
+
// authoritative JSONL rather than allowing a stale, conflicting, or higher derived row to win.
|
|
158
|
+
return await rebuildIndex();
|
|
178
159
|
}
|
|
179
160
|
|
|
180
161
|
export async function saveIndex(index: LogIndex): Promise<void> {
|
|
@@ -186,15 +167,23 @@ export async function saveIndex(index: LogIndex): Promise<void> {
|
|
|
186
167
|
return;
|
|
187
168
|
}
|
|
188
169
|
|
|
170
|
+
const temporaryPath = `${indexPath}.${String(process.pid)}.${randomUUID()}.tmp`;
|
|
189
171
|
try {
|
|
190
|
-
await writeFile(
|
|
172
|
+
await writeFile(temporaryPath, JSON.stringify(index), {
|
|
191
173
|
encoding: "utf-8",
|
|
192
174
|
mode: PRIVATE_FILE_MODE,
|
|
193
175
|
});
|
|
176
|
+
if (!securePrivateFileSync(temporaryPath)) {
|
|
177
|
+
await unlink(temporaryPath).catch(() => undefined);
|
|
178
|
+
logger.error("[logIndex] Failed to secure temporary index file:", temporaryPath);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
await rename(temporaryPath, indexPath);
|
|
194
182
|
if (!securePrivateFileSync(indexPath)) {
|
|
195
183
|
logger.error("[logIndex] Failed to secure index file:", indexPath);
|
|
196
184
|
}
|
|
197
185
|
} catch (err) {
|
|
186
|
+
await unlink(temporaryPath).catch(() => undefined);
|
|
198
187
|
logger.error("[logIndex] Failed to save index:", String(err));
|
|
199
188
|
}
|
|
200
189
|
}
|
|
@@ -220,6 +209,25 @@ export async function addToIndex(
|
|
|
220
209
|
scheduleIndexFlush();
|
|
221
210
|
}
|
|
222
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Refresh the searchable summary after a request reaches its terminal state. The initial index
|
|
214
|
+
* entry is created before the upstream call starts, so leaving it untouched would make compact
|
|
215
|
+
* reads (and the SQLite sidecar) report the pre-request placeholder after finalization.
|
|
216
|
+
*/
|
|
217
|
+
export async function updateLogIndexEntryMetadata(log: CapturedLog): Promise<void> {
|
|
218
|
+
const index = await loadIndex();
|
|
219
|
+
const existing = index.entries[log.id];
|
|
220
|
+
if (existing === undefined) return;
|
|
221
|
+
|
|
222
|
+
const updated: LogIndexEntry = {
|
|
223
|
+
...existing,
|
|
224
|
+
...createLogIndexEntryMetadata(log),
|
|
225
|
+
};
|
|
226
|
+
index.entries[log.id] = updated;
|
|
227
|
+
await upsertSqliteLogIndexEntry(updated);
|
|
228
|
+
scheduleIndexFlush();
|
|
229
|
+
}
|
|
230
|
+
|
|
223
231
|
// Batch writes: collect pending flushes and write once
|
|
224
232
|
let indexFlushScheduled = false;
|
|
225
233
|
let indexFlushTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
@@ -250,9 +258,6 @@ export async function flushIndex(): Promise<void> {
|
|
|
250
258
|
}
|
|
251
259
|
|
|
252
260
|
export async function findInIndex(id: number): Promise<LogIndexEntry | null> {
|
|
253
|
-
const sqliteEntry = await findSqliteLogIndexEntry(id);
|
|
254
|
-
if (sqliteEntry !== null) return sqliteEntry;
|
|
255
|
-
|
|
256
261
|
const index = await loadIndex();
|
|
257
262
|
return index.entries[id] ?? null;
|
|
258
263
|
}
|
|
@@ -260,10 +265,6 @@ export async function findInIndex(id: number): Promise<LogIndexEntry | null> {
|
|
|
260
265
|
export async function listIndexEntries(): Promise<LogIndexEntry[]> {
|
|
261
266
|
const index = await loadIndex();
|
|
262
267
|
const sqliteMaxId = await getSqliteLogIndexMaxId();
|
|
263
|
-
if (sqliteMaxId !== null && sqliteMaxId >= index.maxId) {
|
|
264
|
-
const sqliteEntries = await listSqliteLogIndexEntries();
|
|
265
|
-
if (sqliteEntries !== null) return sqliteEntries;
|
|
266
|
-
}
|
|
267
268
|
maybeBackfillSqliteIndex(index, sqliteMaxId);
|
|
268
269
|
return Object.values(index.entries).sort((left, right) => left.id - right.id);
|
|
269
270
|
}
|
|
@@ -277,12 +278,7 @@ export async function listFilteredIndexEntries({
|
|
|
277
278
|
}): Promise<LogIndexEntry[]> {
|
|
278
279
|
const index = await loadIndex();
|
|
279
280
|
const sqliteMaxId = await getSqliteLogIndexMaxId();
|
|
280
|
-
if (sqliteMaxId !== null && sqliteMaxId >= index.maxId) {
|
|
281
|
-
const sqliteEntries = await listSqliteLogIndexEntries({ sessionId, model });
|
|
282
|
-
if (sqliteEntries !== null) return sqliteEntries;
|
|
283
|
-
}
|
|
284
281
|
maybeBackfillSqliteIndex(index, sqliteMaxId);
|
|
285
|
-
|
|
286
282
|
return Object.values(index.entries)
|
|
287
283
|
.sort((left, right) => left.id - right.id)
|
|
288
284
|
.filter((entry) => {
|
|
@@ -311,6 +307,11 @@ type FileIndexResult = {
|
|
|
311
307
|
maxId: number;
|
|
312
308
|
};
|
|
313
309
|
|
|
310
|
+
type ParsedIndexRecord =
|
|
311
|
+
| { status: "blank" }
|
|
312
|
+
| { status: "log"; log: CapturedLog }
|
|
313
|
+
| { status: "invalid"; message: string };
|
|
314
|
+
|
|
314
315
|
function readStringOrNullField(entry: object, field: string): string | null {
|
|
315
316
|
const desc = Object.getOwnPropertyDescriptor(entry, field);
|
|
316
317
|
if (desc === undefined) return null;
|
|
@@ -349,6 +350,22 @@ function readApiFormatField(entry: object): ApiFormat {
|
|
|
349
350
|
}
|
|
350
351
|
}
|
|
351
352
|
|
|
353
|
+
function readCaptureIncompleteReasonField(entry: object): CaptureIncompleteReason | null {
|
|
354
|
+
const value = readStringOrNullField(entry, "captureIncompleteReason");
|
|
355
|
+
switch (value) {
|
|
356
|
+
case "capacity-exceeded":
|
|
357
|
+
case "pipeline-closed":
|
|
358
|
+
case "write-failed":
|
|
359
|
+
case "finalize-failed":
|
|
360
|
+
case "upstream-stream-error":
|
|
361
|
+
case "shutdown":
|
|
362
|
+
return value;
|
|
363
|
+
case null:
|
|
364
|
+
default:
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
352
369
|
function readStringArrayField(entry: object, field: string): string[] | undefined {
|
|
353
370
|
const desc = Object.getOwnPropertyDescriptor(entry, field);
|
|
354
371
|
if (desc === undefined || !Array.isArray(desc.value)) return undefined;
|
|
@@ -398,58 +415,138 @@ function readLogIndexSummary(entry: object, id: number): LogIndexSummary {
|
|
|
398
415
|
streamingChunksPath: readStringOrNullField(entry, "streamingChunksPath"),
|
|
399
416
|
rawRequestBodyBytes: readTextByteLength(entry, "rawRequestBodyBytes", "rawRequestBody"),
|
|
400
417
|
responseTextBytes: readTextByteLength(entry, "responseTextBytes", "responseText"),
|
|
418
|
+
captureIncomplete: readBooleanField(entry, "captureIncomplete"),
|
|
419
|
+
droppedChunks: readNumberOrNullField(entry, "droppedChunks") ?? 0,
|
|
420
|
+
droppedBytes: readNumberOrNullField(entry, "droppedBytes") ?? 0,
|
|
421
|
+
captureIncompleteReason: readCaptureIncompleteReasonField(entry),
|
|
401
422
|
warnings: readStringArrayField(entry, "warnings"),
|
|
402
423
|
error: readStringOrNullField(entry, "error"),
|
|
403
424
|
};
|
|
404
425
|
}
|
|
405
426
|
|
|
406
|
-
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
427
|
+
function parseIndexRecord(record: Buffer, file: string, byteOffset: number): ParsedIndexRecord {
|
|
428
|
+
const content =
|
|
429
|
+
record.length > 0 && record[record.length - 1] === 0x0d ? record.subarray(0, -1) : record;
|
|
430
|
+
const line = content.toString("utf-8");
|
|
431
|
+
if (line.trim() === "") return { status: "blank" };
|
|
410
432
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
})
|
|
433
|
+
let parsed: unknown;
|
|
434
|
+
try {
|
|
435
|
+
parsed = JSON.parse(line);
|
|
436
|
+
} catch (error) {
|
|
437
|
+
return {
|
|
438
|
+
status: "invalid",
|
|
439
|
+
message: `Malformed JSONL record in ${file} at byte ${String(byteOffset)}: ${String(error)}`,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
const validated = CapturedLogSchema.safeParse(parsed);
|
|
443
|
+
return validated.success
|
|
444
|
+
? { status: "log", log: validated.data }
|
|
445
|
+
: {
|
|
446
|
+
status: "invalid",
|
|
447
|
+
message: `Invalid captured-log record in ${file} at byte ${String(byteOffset)}`,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
415
450
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
}
|
|
451
|
+
function addIndexedRecord(
|
|
452
|
+
result: FileIndexResult,
|
|
453
|
+
parsed: ParsedIndexRecord,
|
|
454
|
+
file: string,
|
|
455
|
+
byteOffset: number,
|
|
456
|
+
byteLength: number,
|
|
457
|
+
): Error | null {
|
|
458
|
+
switch (parsed.status) {
|
|
459
|
+
case "blank":
|
|
460
|
+
return null;
|
|
461
|
+
case "invalid":
|
|
462
|
+
return new Error(parsed.message);
|
|
463
|
+
case "log": {
|
|
464
|
+
const entry = parsed.log;
|
|
465
|
+
result.maxId = Math.max(result.maxId, entry.id);
|
|
466
|
+
result.entries[entry.id] = {
|
|
467
|
+
id: entry.id,
|
|
468
|
+
file,
|
|
469
|
+
byteOffset,
|
|
470
|
+
byteLength,
|
|
471
|
+
sessionId: entry.sessionId,
|
|
472
|
+
model: entry.model,
|
|
473
|
+
summary: readLogIndexSummary(entry, entry.id),
|
|
474
|
+
};
|
|
475
|
+
return null;
|
|
440
476
|
}
|
|
441
|
-
byteOffset += lineBytes;
|
|
442
477
|
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async function indexFile(filePath: string, file: string): Promise<FileIndexResult> {
|
|
481
|
+
const result: FileIndexResult = { entries: {}, maxId: 0 };
|
|
482
|
+
let handle: Awaited<ReturnType<typeof open>> | null = null;
|
|
483
|
+
try {
|
|
484
|
+
handle = await open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
485
|
+
const fileStats = await handle.stat();
|
|
486
|
+
if (!fileStats.isFile()) {
|
|
487
|
+
return await Promise.reject(new Error(`Captured-log ledger is not a regular file: ${file}`));
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const readBuffer = Buffer.alloc(INDEX_READ_CHUNK_BYTES);
|
|
491
|
+
let position = 0;
|
|
492
|
+
let pending = Buffer.alloc(0);
|
|
493
|
+
let pendingOffset = 0;
|
|
494
|
+
while (position < fileStats.size) {
|
|
495
|
+
const length = Math.min(INDEX_READ_CHUNK_BYTES, fileStats.size - position);
|
|
496
|
+
const read = await handle.read(readBuffer, 0, length, position);
|
|
497
|
+
if (read.bytesRead === 0) {
|
|
498
|
+
return await Promise.reject(
|
|
499
|
+
new Error(`Captured-log ledger changed while rebuilding index: ${file}`),
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
const chunk = readBuffer.subarray(0, read.bytesRead);
|
|
503
|
+
const combined = pending.length === 0 ? Buffer.from(chunk) : Buffer.concat([pending, chunk]);
|
|
504
|
+
let consumed = 0;
|
|
505
|
+
let newline = combined.indexOf(0x0a, consumed);
|
|
506
|
+
while (newline !== -1) {
|
|
507
|
+
const record = combined.subarray(consumed, newline);
|
|
508
|
+
const byteOffset = pendingOffset + consumed;
|
|
509
|
+
const byteLength = record.length + 1;
|
|
510
|
+
const error = addIndexedRecord(
|
|
511
|
+
result,
|
|
512
|
+
parseIndexRecord(record, file, byteOffset),
|
|
513
|
+
file,
|
|
514
|
+
byteOffset,
|
|
515
|
+
byteLength,
|
|
516
|
+
);
|
|
517
|
+
if (error !== null) return await Promise.reject(error);
|
|
518
|
+
consumed = newline + 1;
|
|
519
|
+
newline = combined.indexOf(0x0a, consumed);
|
|
520
|
+
}
|
|
521
|
+
pending = Buffer.from(combined.subarray(consumed));
|
|
522
|
+
pendingOffset += consumed;
|
|
523
|
+
position += read.bytesRead;
|
|
524
|
+
}
|
|
443
525
|
|
|
444
|
-
|
|
526
|
+
if (pending.length > 0) {
|
|
527
|
+
return await Promise.reject(
|
|
528
|
+
new Error(`Captured-log ledger ended without recovery delimiter: ${file}`),
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
return result;
|
|
532
|
+
} finally {
|
|
533
|
+
await handle?.close().catch(() => undefined);
|
|
534
|
+
}
|
|
445
535
|
}
|
|
446
536
|
|
|
447
537
|
export async function rebuildIndex(): Promise<LogIndex> {
|
|
448
538
|
const logDir = resolveLogDir();
|
|
449
539
|
const newIndex = createEmptyIndex();
|
|
540
|
+
const recovery = await recoverCapturedLogStorage(logDir);
|
|
541
|
+
if (recovery.level === "unavailable") {
|
|
542
|
+
return await Promise.reject(
|
|
543
|
+
new Error(recovery.diagnostic ?? "Captured-log recovery blocked index rebuild"),
|
|
544
|
+
);
|
|
545
|
+
}
|
|
450
546
|
|
|
451
547
|
if (!existsSync(logDir)) {
|
|
452
548
|
cachedIndex = newIndex;
|
|
549
|
+
allocatedMaxId = 0;
|
|
453
550
|
await saveIndex(newIndex);
|
|
454
551
|
await replaceSqliteLogIndexEntries([]);
|
|
455
552
|
return newIndex;
|
|
@@ -470,6 +567,7 @@ export async function rebuildIndex(): Promise<LogIndex> {
|
|
|
470
567
|
}
|
|
471
568
|
|
|
472
569
|
cachedIndex = newIndex;
|
|
570
|
+
allocatedMaxId = Math.max(allocatedMaxId, newIndex.maxId);
|
|
473
571
|
await saveIndex(newIndex);
|
|
474
572
|
await replaceSqliteLogIndexEntries(Object.values(newIndex.entries));
|
|
475
573
|
return newIndex;
|
|
@@ -494,11 +592,9 @@ async function withIdLock<T>(fn: () => Promise<T>): Promise<T> {
|
|
|
494
592
|
export async function getNextLogId(): Promise<number> {
|
|
495
593
|
return withIdLock(async () => {
|
|
496
594
|
const index = await loadIndex();
|
|
497
|
-
const
|
|
498
|
-
const nextId = Math.max(index.maxId,
|
|
499
|
-
|
|
500
|
-
// Synchronously update the index in memory (disk write is deferred via batching)
|
|
501
|
-
cachedIndex = index;
|
|
595
|
+
const archiveMaxId = await getSessionArchiveMaxLogId();
|
|
596
|
+
const nextId = Math.max(index.maxId, archiveMaxId, allocatedMaxId) + 1;
|
|
597
|
+
allocatedMaxId = nextId;
|
|
502
598
|
return nextId;
|
|
503
599
|
});
|
|
504
600
|
}
|
package/src/proxy/logger.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
-
import {
|
|
2
|
+
import { closeSync, constants, fsyncSync, openSync, writeSync } from "node:fs";
|
|
3
3
|
import { appendFile, readdir, rename, stat, unlink } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { getDataDir } from "./dataDir";
|
|
@@ -9,6 +9,12 @@ import {
|
|
|
9
9
|
securePrivateFileSync,
|
|
10
10
|
securePrivateTreeSync,
|
|
11
11
|
} from "./privateDataPath";
|
|
12
|
+
import {
|
|
13
|
+
canAppendCapturedLogFileOnExit,
|
|
14
|
+
markCapturedLogFileAppended,
|
|
15
|
+
recoverCapturedLogFileBeforeAppend,
|
|
16
|
+
recoverCapturedLogStorage,
|
|
17
|
+
} from "./jsonlRecovery";
|
|
12
18
|
|
|
13
19
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
14
20
|
const DEFAULT_RETENTION_DAYS = 7;
|
|
@@ -236,17 +242,25 @@ export async function initLogger(): Promise<void> {
|
|
|
236
242
|
} else if (!securePrivateTreeSync(dir)) {
|
|
237
243
|
writeStderrFallback("[logger] Failed to secure existing log files:", dir);
|
|
238
244
|
} else {
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
const recovery = await recoverCapturedLogStorage(dir);
|
|
246
|
+
if (recovery.level === "unavailable") {
|
|
247
|
+
writeStderrFallback(
|
|
248
|
+
"[logger] Captured-log recovery blocked readiness:",
|
|
249
|
+
recovery.diagnostic ?? "unknown recovery failure",
|
|
250
|
+
);
|
|
251
|
+
} else {
|
|
252
|
+
const entries = await readdir(dir);
|
|
253
|
+
for (const entry of entries) {
|
|
254
|
+
if (!entry.endsWith(".jsonl")) continue;
|
|
255
|
+
const fullPath = path.join(dir, entry);
|
|
256
|
+
try {
|
|
257
|
+
const s = await stat(fullPath);
|
|
258
|
+
if (s.mtimeMs < cutoff) {
|
|
259
|
+
await unlink(fullPath);
|
|
260
|
+
}
|
|
261
|
+
} catch {
|
|
262
|
+
// Skip files that can't be stat - not critical
|
|
247
263
|
}
|
|
248
|
-
} catch {
|
|
249
|
-
// Skip files that can't be stat - not critical
|
|
250
264
|
}
|
|
251
265
|
}
|
|
252
266
|
}
|
|
@@ -354,9 +368,29 @@ export const logger = {
|
|
|
354
368
|
|
|
355
369
|
// Write buffer for batching async writes
|
|
356
370
|
const MAX_BUFFER_SIZE = 1000;
|
|
371
|
+
const BUFFER_FLUSH_DELAY_MS = 10;
|
|
357
372
|
let writeBuffer: string[] = [];
|
|
358
373
|
let writeQueue: Promise<void> = Promise.resolve();
|
|
359
374
|
let logWriteLock: Promise<void> = Promise.resolve();
|
|
375
|
+
let scheduledBufferFlush: ReturnType<typeof setTimeout> | null = null;
|
|
376
|
+
|
|
377
|
+
function queueBufferedFlush(): void {
|
|
378
|
+
writeQueue = writeQueue.then(() => runWithLogWriteLock(flushWriteBufferUnlocked));
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function cancelScheduledBufferFlush(): void {
|
|
382
|
+
if (scheduledBufferFlush === null) return;
|
|
383
|
+
clearTimeout(scheduledBufferFlush);
|
|
384
|
+
scheduledBufferFlush = null;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function scheduleBufferedFlush(): void {
|
|
388
|
+
if (scheduledBufferFlush !== null) return;
|
|
389
|
+
scheduledBufferFlush = setTimeout(() => {
|
|
390
|
+
scheduledBufferFlush = null;
|
|
391
|
+
queueBufferedFlush();
|
|
392
|
+
}, BUFFER_FLUSH_DELAY_MS);
|
|
393
|
+
}
|
|
360
394
|
|
|
361
395
|
export async function runWithLogWriteLock<T>(fn: () => Promise<T>): Promise<T> {
|
|
362
396
|
const previousLock = logWriteLock;
|
|
@@ -393,10 +427,20 @@ async function flushWriteBufferUnlocked(): Promise<void> {
|
|
|
393
427
|
writeStderrFallback("[logger] Failed to secure captured-log directory:", filePath);
|
|
394
428
|
return;
|
|
395
429
|
}
|
|
430
|
+
const recovery = await recoverCapturedLogFileBeforeAppend(resolveLogDir(), filePath);
|
|
431
|
+
if (recovery.level === "unavailable") {
|
|
432
|
+
writeBuffer.unshift(toWrite);
|
|
433
|
+
writeStderrFallback(
|
|
434
|
+
"[logger] Refused captured-log append until recovery succeeds:",
|
|
435
|
+
recovery.diagnostic ?? "unknown recovery failure",
|
|
436
|
+
);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
396
439
|
await appendFile(filePath, toWrite, { encoding: "utf-8", mode: PRIVATE_FILE_MODE });
|
|
397
440
|
if (!securePrivateFileSync(filePath)) {
|
|
398
441
|
writeStderrFallback("[logger] Failed to secure captured-log file:", filePath);
|
|
399
442
|
}
|
|
443
|
+
await markCapturedLogFileAppended(resolveLogDir(), filePath);
|
|
400
444
|
} catch (err) {
|
|
401
445
|
// On failure, re-queue the data so it is not lost
|
|
402
446
|
writeBuffer.unshift(toWrite);
|
|
@@ -408,9 +452,10 @@ export function appendLogEntry(entry: Record<string, unknown>): void {
|
|
|
408
452
|
const line = JSON.stringify(entry) + "\n";
|
|
409
453
|
writeBuffer.push(line);
|
|
410
454
|
if (writeBuffer.length >= MAX_BUFFER_SIZE) {
|
|
411
|
-
|
|
455
|
+
cancelScheduledBufferFlush();
|
|
456
|
+
queueBufferedFlush();
|
|
412
457
|
} else if (writeBuffer.length === 1) {
|
|
413
|
-
|
|
458
|
+
scheduleBufferedFlush();
|
|
414
459
|
}
|
|
415
460
|
}
|
|
416
461
|
|
|
@@ -419,6 +464,7 @@ export function appendLogEntry(entry: Record<string, unknown>): void {
|
|
|
419
464
|
* to avoid losing unflushed data.
|
|
420
465
|
*/
|
|
421
466
|
export async function flushLogBuffer(): Promise<void> {
|
|
467
|
+
cancelScheduledBufferFlush();
|
|
422
468
|
await writeQueue;
|
|
423
469
|
if (writeBuffer.length > 0) {
|
|
424
470
|
await runWithLogWriteLock(flushWriteBufferUnlocked);
|
|
@@ -441,7 +487,24 @@ process.on("exit", () => {
|
|
|
441
487
|
// Synchronous write required in 'exit' handler (async I/O not allowed)
|
|
442
488
|
const filePath = getLogFilePath();
|
|
443
489
|
if (!ensurePrivateDirectorySync(path.dirname(filePath))) return;
|
|
444
|
-
|
|
490
|
+
if (!canAppendCapturedLogFileOnExit(resolveLogDir(), filePath)) return;
|
|
491
|
+
const descriptor = openSync(
|
|
492
|
+
filePath,
|
|
493
|
+
constants.O_WRONLY | constants.O_APPEND | constants.O_NOFOLLOW,
|
|
494
|
+
PRIVATE_FILE_MODE,
|
|
495
|
+
);
|
|
496
|
+
try {
|
|
497
|
+
const payload = Buffer.from(toWrite, "utf-8");
|
|
498
|
+
let written = 0;
|
|
499
|
+
while (written < payload.length) {
|
|
500
|
+
const writeCount = writeSync(descriptor, payload, written, payload.length - written);
|
|
501
|
+
if (writeCount === 0) return;
|
|
502
|
+
written += writeCount;
|
|
503
|
+
}
|
|
504
|
+
fsyncSync(descriptor);
|
|
505
|
+
} finally {
|
|
506
|
+
closeSync(descriptor);
|
|
507
|
+
}
|
|
445
508
|
void securePrivateFileSync(filePath);
|
|
446
509
|
} catch {
|
|
447
510
|
// Best-effort: nothing we can do in an exit handler
|
|
@@ -205,6 +205,47 @@ export function killPid(pid: number, platform: PlatformName = process.platform):
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
export type ProcessTreeSignal = "SIGTERM" | "SIGKILL";
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Signal a child process and every descendant created in its process tree.
|
|
212
|
+
*
|
|
213
|
+
* Unix callers must spawn the root with `detached: true`, which makes its PID the process-group
|
|
214
|
+
* ID. Windows has no equivalent portable Node signal, so taskkill owns descendant traversal.
|
|
215
|
+
*/
|
|
216
|
+
export function signalProcessTree(
|
|
217
|
+
pid: number,
|
|
218
|
+
signal: ProcessTreeSignal,
|
|
219
|
+
platform: PlatformName = process.platform,
|
|
220
|
+
): boolean {
|
|
221
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
222
|
+
try {
|
|
223
|
+
if (platform === "win32") {
|
|
224
|
+
const args = ["/PID", String(pid), "/T"];
|
|
225
|
+
if (signal === "SIGKILL") args.push("/F");
|
|
226
|
+
execFileSync("taskkill.exe", args, {
|
|
227
|
+
encoding: "utf8",
|
|
228
|
+
timeout: 5000,
|
|
229
|
+
windowsHide: true,
|
|
230
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
231
|
+
});
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
process.kill(-pid, signal);
|
|
235
|
+
return true;
|
|
236
|
+
} catch {
|
|
237
|
+
// Killing only the Windows root would orphan descendants and could make the runtime report a
|
|
238
|
+
// false clean shutdown. Let the caller retry taskkill with /F instead.
|
|
239
|
+
if (platform === "win32") return false;
|
|
240
|
+
try {
|
|
241
|
+
process.kill(pid, signal);
|
|
242
|
+
return true;
|
|
243
|
+
} catch {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
208
249
|
export const platformCommandParsersForTests = {
|
|
209
250
|
parseUnixLsofPids,
|
|
210
251
|
parseUnixSsPids,
|