@code-yeongyu/senpi 2026.9.26 → 2026.9.27-3
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/CHANGELOG.md +66 -0
- package/dist/bun-runtime.d.ts +18 -2
- package/dist/bun-runtime.d.ts.map +1 -1
- package/dist/bun-runtime.js +60 -8
- package/dist/bun-runtime.js.map +1 -1
- package/dist/bundle/chunks/anthropic-messages-3FGAXYJS.js +8 -0
- package/dist/bundle/chunks/{app-server-command-ZRCKWY2X.js → app-server-command-ZVB35UIJ.js} +1 -1
- package/dist/bundle/chunks/azure-openai-responses-4SUOOZZ4.js +2 -0
- package/dist/bundle/chunks/bun-extension-importer-R4HXF3GD.js +14 -0
- package/dist/bundle/chunks/{chunk-KMJXSXRX.js → chunk-357I4IUU.js} +2 -2
- package/dist/bundle/chunks/chunk-3SIQIY57.js +8 -0
- package/dist/bundle/chunks/chunk-45SZOLCU.js +2 -0
- package/dist/bundle/chunks/{chunk-77ACEMYL.js → chunk-6J6RDMBY.js} +1 -1
- package/dist/bundle/chunks/{chunk-BSOJHK4W.js → chunk-7FXXRPB3.js} +2 -1
- package/dist/bundle/chunks/{chunk-FD34HSJJ.js → chunk-C6LQNMRB.js} +1 -1
- package/dist/bundle/chunks/chunk-ELM77A26.js +2 -0
- package/dist/bundle/chunks/chunk-GZVCRV7I.js +3 -0
- package/dist/bundle/chunks/{chunk-TTY5BHCG.js → chunk-H2SGCPMD.js} +1 -1
- package/dist/bundle/chunks/chunk-KLBKUBDV.js +2 -0
- package/dist/bundle/chunks/chunk-L6QV7NZI.js +2 -0
- package/dist/bundle/chunks/{chunk-ZKZAATFM.js → chunk-LDB5UMH2.js} +2 -2
- package/dist/bundle/chunks/{chunk-HZAKOVHN.js → chunk-LGKKU4VM.js} +1 -1
- package/dist/bundle/chunks/{chunk-6Q6DGME2.js → chunk-NMDALHXH.js} +1 -1
- package/dist/bundle/chunks/chunk-NNILBBOH.js +18 -0
- package/dist/bundle/chunks/{chunk-SUQDU5KM.js → chunk-OQRUK2DK.js} +1 -1
- package/dist/bundle/chunks/{chunk-OZCB6BP3.js → chunk-OZN2KUPR.js} +7 -7
- package/dist/bundle/chunks/{chunk-X3T45623.js → chunk-QK4QKRTF.js} +1 -1
- package/dist/bundle/chunks/{chunk-WM5FVFAP.js → chunk-QPZVJQC5.js} +1 -1
- package/dist/bundle/chunks/{chunk-Y54V65MS.js → chunk-QYAO24GB.js} +1 -1
- package/dist/bundle/chunks/{chunk-MKAM7IRH.js → chunk-RULGEHQM.js} +1 -1
- package/dist/bundle/chunks/{chunk-QGC46CGK.js → chunk-RYI6XR6Z.js} +63 -58
- package/dist/bundle/chunks/chunk-U3VOXCGP.js +2 -0
- package/dist/bundle/chunks/chunk-W3D3ZYDS.js +2 -0
- package/dist/bundle/chunks/{chunk-UBPMXBOJ.js → chunk-XKLR7C5B.js} +1 -1
- package/dist/bundle/chunks/{chunk-CLZVNQW3.js → chunk-Y7EIVDHZ.js} +1 -1
- package/dist/bundle/chunks/chunk-ZIVYE5YK.js +2 -0
- package/dist/bundle/chunks/cli-main-RONHLFWZ.js +3 -0
- package/dist/bundle/chunks/google-generative-ai-ZTGKZTGU.js +2 -0
- package/dist/bundle/chunks/google-vertex-CCZ376YK.js +2 -0
- package/dist/bundle/chunks/help-fast-path-5LRG6FCG.js +2 -0
- package/dist/bundle/chunks/host-command-JVPGQR74.js +2 -0
- package/dist/bundle/chunks/host-lifecycle-R4HLXLBM.js +3 -0
- package/dist/bundle/chunks/host-lifecycle.js +4 -4
- package/dist/bundle/chunks/interactive-host-runtime-FES2G62G.js +4 -0
- package/dist/bundle/chunks/interactive-mode-45EP2FVL.js +2 -0
- package/dist/bundle/chunks/{list-tips-EBRNBPBM.js → list-tips-XXMRDHN6.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-7YX2I7UK.js → multi-session-host-EFCMD5OT.js} +6 -6
- package/dist/bundle/chunks/{openai-completions-4CLXDDHJ.js → openai-completions-OP6VFNUR.js} +1 -1
- package/dist/bundle/chunks/openai-images-6U5FCN5S.js +2 -0
- package/dist/bundle/chunks/openai-responses-7PWMBGYX.js +2 -0
- package/dist/bundle/chunks/openrouter-images-QIKWQZUD.js +2 -0
- package/dist/bundle/chunks/{package-manager-cli-3ISYTEL2.js → package-manager-cli-5XJS3TYM.js} +1 -1
- package/dist/bundle/chunks/rotation-stream-KW7PTEC4.js +2 -0
- package/dist/bundle/chunks/rpc-mode-ZHYECU24.js +2 -0
- package/dist/bundle/chunks/session-picker-QHIYVQP4.js +2 -0
- package/dist/bundle/chunks/session-worker.js +281 -270
- package/dist/bundle/chunks/{state-store-Q5EWEAFG.js → state-store-D4GGGT3J.js} +1 -1
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +4 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/continue-moved.d.ts +15 -0
- package/dist/cli/continue-moved.d.ts.map +1 -0
- package/dist/cli/continue-moved.js +26 -0
- package/dist/cli/continue-moved.js.map +1 -0
- package/dist/cli/cross-project-session.d.ts +22 -0
- package/dist/cli/cross-project-session.d.ts.map +1 -0
- package/dist/cli/cross-project-session.js +38 -0
- package/dist/cli/cross-project-session.js.map +1 -0
- package/dist/core/agent-session-runtime.d.ts +3 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +43 -10
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +19 -5
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-registry-pump.d.ts.map +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-registry-pump.js +5 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-registry-pump.js.map +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-turn-claim.d.ts +13 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-turn-claim.d.ts.map +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-turn-claim.js +25 -2
- package/dist/core/extensions/builtin/anthropic-subscription/session-turn-claim.js.map +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-turn-types.d.ts +1 -0
- package/dist/core/extensions/builtin/anthropic-subscription/session-turn-types.d.ts.map +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-turn-types.js.map +1 -1
- package/dist/core/extensions/builtin/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/index.js +2 -0
- package/dist/core/extensions/builtin/index.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -0
- package/dist/core/extensions/builtin/mcp/catalog.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/catalog.js +9 -0
- package/dist/core/extensions/builtin/mcp/catalog.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/connection-types.d.ts +2 -0
- package/dist/core/extensions/builtin/mcp/connection-types.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/connection-types.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/connection.d.ts +1 -1
- package/dist/core/extensions/builtin/mcp/connection.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/connection.js +3 -2
- package/dist/core/extensions/builtin/mcp/connection.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/expose/session.d.ts +3 -0
- package/dist/core/extensions/builtin/mcp/expose/session.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/expose/session.js +9 -2
- package/dist/core/extensions/builtin/mcp/expose/session.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/service-register.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/service-register.js +7 -1
- package/dist/core/extensions/builtin/mcp/service-register.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/service-tools-changed.d.ts +18 -0
- package/dist/core/extensions/builtin/mcp/service-tools-changed.d.ts.map +1 -0
- package/dist/core/extensions/builtin/mcp/service-tools-changed.js +61 -0
- package/dist/core/extensions/builtin/mcp/service-tools-changed.js.map +1 -0
- package/dist/core/extensions/builtin/mcp/service-types.d.ts +13 -0
- package/dist/core/extensions/builtin/mcp/service-types.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/service-types.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/service.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/service.js +4 -37
- package/dist/core/extensions/builtin/mcp/service.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/shared-lease.js +1 -1
- package/dist/core/extensions/builtin/mcp/shared-lease.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/startup-race.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/startup-race.js +11 -1
- package/dist/core/extensions/builtin/mcp/startup-race.js.map +1 -1
- package/dist/core/extensions/builtin/permission-system/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/permission-system/index.js +5 -2
- package/dist/core/extensions/builtin/permission-system/index.js.map +1 -1
- package/dist/core/extensions/builtin/permission-system/parsers.d.ts +5 -0
- package/dist/core/extensions/builtin/permission-system/parsers.d.ts.map +1 -1
- package/dist/core/extensions/builtin/permission-system/parsers.js +13 -0
- package/dist/core/extensions/builtin/permission-system/parsers.js.map +1 -1
- package/dist/core/extensions/builtin/repository-identity.d.ts +6 -0
- package/dist/core/extensions/builtin/repository-identity.d.ts.map +1 -0
- package/dist/core/extensions/builtin/repository-identity.js +33 -0
- package/dist/core/extensions/builtin/repository-identity.js.map +1 -0
- package/dist/core/extensions/bun-extension-importer.d.ts.map +1 -1
- package/dist/core/extensions/bun-extension-importer.js +10 -4
- package/dist/core/extensions/bun-extension-importer.js.map +1 -1
- package/dist/core/extensions/bun-extension-registry.js +6 -5
- package/dist/core/extensions/bun-extension-registry.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/kernel-prelude.d.ts +13 -0
- package/dist/core/extensions/kernel-prelude.d.ts.map +1 -0
- package/dist/core/extensions/kernel-prelude.js +38 -0
- package/dist/core/extensions/kernel-prelude.js.map +1 -0
- package/dist/core/extensions/types.d.ts +44 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +4 -2
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-resolver.js +1 -1
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/moved-sessions.d.ts +10 -0
- package/dist/core/moved-sessions.d.ts.map +1 -0
- package/dist/core/moved-sessions.js +84 -0
- package/dist/core/moved-sessions.js.map +1 -0
- package/dist/core/process-crash-record.d.ts +62 -0
- package/dist/core/process-crash-record.d.ts.map +1 -0
- package/dist/core/process-crash-record.js +216 -0
- package/dist/core/process-crash-record.js.map +1 -0
- package/dist/core/provider-rejected-images.d.ts +18 -0
- package/dist/core/provider-rejected-images.d.ts.map +1 -0
- package/dist/core/provider-rejected-images.js +107 -0
- package/dist/core/provider-rejected-images.js.map +1 -0
- package/dist/core/repository-identity.d.ts +18 -0
- package/dist/core/repository-identity.d.ts.map +1 -0
- package/dist/core/repository-identity.js +75 -0
- package/dist/core/repository-identity.js.map +1 -0
- package/dist/core/resume-target.d.ts +20 -0
- package/dist/core/resume-target.d.ts.map +1 -0
- package/dist/core/resume-target.js +30 -0
- package/dist/core/resume-target.js.map +1 -0
- package/dist/core/session-discovery.d.ts.map +1 -1
- package/dist/core/session-discovery.js +1 -0
- package/dist/core/session-discovery.js.map +1 -1
- package/dist/core/session-hold-errors.d.ts +20 -0
- package/dist/core/session-hold-errors.d.ts.map +1 -0
- package/dist/core/session-hold-errors.js +28 -0
- package/dist/core/session-hold-errors.js.map +1 -0
- package/dist/core/session-holders.d.ts +22 -0
- package/dist/core/session-holders.d.ts.map +1 -0
- package/dist/core/session-holders.js +234 -0
- package/dist/core/session-holders.js.map +1 -0
- package/dist/core/session-manager.d.ts +5 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-rebind.d.ts +23 -0
- package/dist/core/session-rebind.d.ts.map +1 -0
- package/dist/core/session-rebind.js +127 -0
- package/dist/core/session-rebind.js.map +1 -0
- package/dist/core/session-summary-index-file.d.ts.map +1 -1
- package/dist/core/session-summary-index-file.js +5 -2
- package/dist/core/session-summary-index-file.js.map +1 -1
- package/dist/core/session-summary.d.ts +3 -0
- package/dist/core/session-summary.d.ts.map +1 -1
- package/dist/core/session-summary.js +7 -0
- package/dist/core/session-summary.js.map +1 -1
- package/dist/core/session-title-generator.d.ts.map +1 -1
- package/dist/core/session-title-generator.js +21 -3
- package/dist/core/session-title-generator.js.map +1 -1
- package/dist/core/session-title-reasoning.d.ts +14 -0
- package/dist/core/session-title-reasoning.d.ts.map +1 -0
- package/dist/core/session-title-reasoning.js +31 -0
- package/dist/core/session-title-reasoning.js.map +1 -0
- package/dist/core/tools/path-utils.d.ts.map +1 -1
- package/dist/core/tools/path-utils.js +13 -0
- package/dist/core/tools/path-utils.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/legacy-dir-copy.d.ts +9 -0
- package/dist/legacy-dir-copy.d.ts.map +1 -0
- package/dist/legacy-dir-copy.js +69 -0
- package/dist/legacy-dir-copy.js.map +1 -0
- package/dist/legacy-senpi-dir-migration.d.ts.map +1 -1
- package/dist/legacy-senpi-dir-migration.js +64 -48
- package/dist/legacy-senpi-dir-migration.js.map +1 -1
- package/dist/main.d.ts +6 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +129 -22
- package/dist/main.js.map +1 -1
- package/dist/migrations-state.d.ts +1 -1
- package/dist/migrations-state.d.ts.map +1 -1
- package/dist/migrations-state.js +5 -1
- package/dist/migrations-state.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +4 -0
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +5 -3
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +10 -4
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/resume-rebind.d.ts +12 -0
- package/dist/modes/interactive/resume-rebind.d.ts.map +1 -0
- package/dist/modes/interactive/resume-rebind.js +37 -0
- package/dist/modes/interactive/resume-rebind.js.map +1 -0
- package/dist/modes/interactive/runtime-notice-presenter.d.ts +8 -0
- package/dist/modes/interactive/runtime-notice-presenter.d.ts.map +1 -0
- package/dist/modes/interactive/runtime-notice-presenter.js +69 -0
- package/dist/modes/interactive/runtime-notice-presenter.js.map +1 -0
- package/dist/modes/interactive/runtime-notice.d.ts +50 -0
- package/dist/modes/interactive/runtime-notice.d.ts.map +1 -0
- package/dist/modes/interactive/runtime-notice.js +104 -0
- package/dist/modes/interactive/runtime-notice.js.map +1 -0
- package/dist/modes/interactive/tips/catalog/computer-tips.d.ts +7 -0
- package/dist/modes/interactive/tips/catalog/computer-tips.d.ts.map +1 -0
- package/dist/modes/interactive/tips/catalog/computer-tips.js +36 -0
- package/dist/modes/interactive/tips/catalog/computer-tips.js.map +1 -0
- package/dist/modes/interactive/tips/registry.d.ts.map +1 -1
- package/dist/modes/interactive/tips/registry.js +2 -0
- package/dist/modes/interactive/tips/registry.js.map +1 -1
- package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
- package/dist/modes/rpc/connection-handler.js +3 -0
- package/dist/modes/rpc/connection-handler.js.map +1 -1
- package/dist/modes/rpc/host-child-exit.d.ts.map +1 -1
- package/dist/modes/rpc/host-child-exit.js +4 -0
- package/dist/modes/rpc/host-child-exit.js.map +1 -1
- package/dist/modes/rpc/host-crash-record.d.ts +8 -0
- package/dist/modes/rpc/host-crash-record.d.ts.map +1 -1
- package/dist/modes/rpc/host-crash-record.js.map +1 -1
- package/dist/modes/rpc/host-crash-record.test.js +11 -0
- package/dist/modes/rpc/host-crash-record.test.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +2 -2
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +14 -6
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-request-deadline.d.ts +17 -0
- package/dist/modes/rpc/rpc-request-deadline.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-request-deadline.js +27 -0
- package/dist/modes/rpc/rpc-request-deadline.js.map +1 -0
- package/dist/modes/rpc/rpc-types.d.ts +9 -2
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/modes/rpc/session-command-router.d.ts +3 -6
- package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
- package/dist/modes/rpc/session-command-router.js +9 -22
- package/dist/modes/rpc/session-command-router.js.map +1 -1
- package/dist/modes/rpc/session-path-key.d.ts +10 -0
- package/dist/modes/rpc/session-path-key.d.ts.map +1 -0
- package/dist/modes/rpc/session-path-key.js +33 -0
- package/dist/modes/rpc/session-path-key.js.map +1 -0
- package/dist/modes/rpc/session-registry.d.ts.map +1 -1
- package/dist/modes/rpc/session-registry.js +6 -10
- package/dist/modes/rpc/session-registry.js.map +1 -1
- package/dist/modes/rpc/session-sweep.d.ts +18 -0
- package/dist/modes/rpc/session-sweep.d.ts.map +1 -0
- package/dist/modes/rpc/session-sweep.js +32 -0
- package/dist/modes/rpc/session-sweep.js.map +1 -0
- package/dist/pi-dir-restore.d.ts +4 -0
- package/dist/pi-dir-restore.d.ts.map +1 -0
- package/dist/pi-dir-restore.js +92 -0
- package/dist/pi-dir-restore.js.map +1 -0
- package/docs/environment-variables.md +2 -0
- package/docs/extensions.md +27 -1
- package/docs/quickstart.md +7 -1
- package/docs/rpc.md +8 -3
- package/docs/sdk.md +29 -0
- package/docs/sessions.md +13 -0
- package/docs/usage.md +1 -0
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +38 -0
- package/node_modules/@code-yeongyu/senpi-codemode/README.md +7 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/protocol.ts +9 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/schema-bridge.ts +2 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/extension/kernel-preludes.ts +30 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +15 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +1 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/local-module-loader.ts +16 -2
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel.ts +6 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/prelude.py +15 -2
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/transport.ts +7 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/kernel-prelude-plan.ts +19 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt-template.ts +2 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +4 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool-options.ts +11 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool.ts +1 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/run-eval-cell.ts +1 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/types.ts +3 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts +2 -2
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +7 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/assistant-terminal-state.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/assistant-terminal-state.js +3 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/assistant-terminal-state.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +3 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +10 -7
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +9 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/provider-diagnostic.d.ts +33 -0
- package/node_modules/@earendil-works/pi-ai/dist/provider-diagnostic.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/provider-diagnostic.js +56 -0
- package/node_modules/@earendil-works/pi-ai/dist/provider-diagnostic.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +3 -0
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-carrier.d.ts +5 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-carrier.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-carrier.js +18 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-carrier.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-sources.d.ts +26 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-sources.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-sources.js +103 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-sources.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-vocabulary.d.ts +36 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-vocabulary.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-vocabulary.js +129 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-vocabulary.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/provider-retry.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/provider-retry.js +5 -4
- package/node_modules/@earendil-works/pi-ai/dist/utils/provider-retry.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-pty/native/prebuilds/darwin-arm64/senpi_pty.darwin-arm64.node +0 -0
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
- package/dist/bundle/chunks/anthropic-messages-UYDVRFAS.js +0 -8
- package/dist/bundle/chunks/azure-openai-responses-DODWR4W6.js +0 -2
- package/dist/bundle/chunks/bun-extension-importer-H4IJCWWQ.js +0 -14
- package/dist/bundle/chunks/chunk-34HMVB33.js +0 -2
- package/dist/bundle/chunks/chunk-3WBSBDBX.js +0 -13
- package/dist/bundle/chunks/chunk-L6JO4M6J.js +0 -3
- package/dist/bundle/chunks/chunk-SFYHCFRO.js +0 -2
- package/dist/bundle/chunks/chunk-T3JBT2IK.js +0 -8
- package/dist/bundle/chunks/chunk-TTHMOL5G.js +0 -2
- package/dist/bundle/chunks/chunk-UYCGOK6M.js +0 -2
- package/dist/bundle/chunks/chunk-ZTVQY5V6.js +0 -2
- package/dist/bundle/chunks/cli-main-H6JAXAXV.js +0 -3
- package/dist/bundle/chunks/google-generative-ai-VIEFECKJ.js +0 -2
- package/dist/bundle/chunks/google-vertex-Q64CKFID.js +0 -2
- package/dist/bundle/chunks/help-fast-path-ZGKPSDFO.js +0 -2
- package/dist/bundle/chunks/host-command-BMHJ4YUD.js +0 -2
- package/dist/bundle/chunks/host-lifecycle-2GZAGSGW.js +0 -3
- package/dist/bundle/chunks/interactive-host-runtime-YI75GFD5.js +0 -4
- package/dist/bundle/chunks/interactive-mode-HM7UQYRC.js +0 -2
- package/dist/bundle/chunks/openai-images-JA5N5DUU.js +0 -2
- package/dist/bundle/chunks/openai-responses-IBFGBAJP.js +0 -2
- package/dist/bundle/chunks/openrouter-images-MW5TXV7L.js +0 -2
- package/dist/bundle/chunks/rotation-stream-NATO6IMN.js +0 -2
- package/dist/bundle/chunks/rpc-mode-6PY4TN5F.js +0 -2
- package/dist/bundle/chunks/session-picker-BP7THCWF.js +0 -2
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type RepositoryIdentity, type RepositoryMatch } from "./repository-identity.ts";
|
|
2
|
+
export declare function readRecordedRepositoryIdentity(sessionFile: string): RepositoryIdentity | undefined;
|
|
3
|
+
/**
|
|
4
|
+
* Whether `cwd` is the git repository a session was recorded in. The session side prefers the live
|
|
5
|
+
* checkout at its recorded cwd and falls back to the identity the session recorded, because a moved
|
|
6
|
+
* repository leaves nothing at the old path to ask.
|
|
7
|
+
*/
|
|
8
|
+
export declare function classifySessionRepository(sessionFile: string, sessionCwd: string, cwd: string, read?: (dir: string) => Promise<RepositoryIdentity | undefined>): Promise<RepositoryMatch>;
|
|
9
|
+
export declare function readSessionCwd(sessionFile: string): string | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Moves a session recorded under another project into `targetCwd`'s session directory and points its
|
|
12
|
+
* header at `targetCwd`. The id, file name, every entry after the header, and the session's extension
|
|
13
|
+
* sidecars are kept. The relocated copy is complete before the source is removed, so an interrupted
|
|
14
|
+
* rebind leaves the session readable in at least one place. Returns the session file's new path.
|
|
15
|
+
*
|
|
16
|
+
* The move runs under the session's cross-process move lock and refuses (`SessionHeldError`) while
|
|
17
|
+
* another live process has the session open. A rebind that finds the same move already done by a
|
|
18
|
+
* concurrent process returns that target.
|
|
19
|
+
*/
|
|
20
|
+
export declare function rebindSessionFile(sourcePath: string, targetCwd: string, sessionDir?: string, options?: {
|
|
21
|
+
readonly moveLockWaitMs?: number;
|
|
22
|
+
}): Promise<string>;
|
|
23
|
+
//# sourceMappingURL=session-rebind.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-rebind.d.ts","sourceRoot":"","sources":["../../src/core/session-rebind.ts"],"names":[],"mappings":"AAGA,OAAO,EAIN,KAAK,kBAAkB,EACvB,KAAK,eAAe,EAEpB,MAAM,0BAA0B,CAAC;AAUlC,wBAAgB,8BAA8B,CAAC,WAAW,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAQlG;AAED;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC9C,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAA0B,GACrF,OAAO,CAAC,eAAe,CAAC,CAM1B;AAED,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGtE;AAED;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CACtC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,GAAE;IAAE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GAChD,OAAO,CAAC,MAAM,CAAC,CAUjB"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join } from "node:path";
|
|
3
|
+
import { normalizePath, resolvePath } from "../utils/paths.js";
|
|
4
|
+
import { compareRepositoryIdentities, parseRepositoryIdentity, REPOSITORY_IDENTITY_ENTRY_TYPE, readRepositoryIdentity, } from "./repository-identity.js";
|
|
5
|
+
import { withSessionMoveLock } from "./session-holders.js";
|
|
6
|
+
import { getDefaultSessionDir, loadEntriesFromFile } from "./session-manager.js";
|
|
7
|
+
import { encodedSessionId } from "./session-sidecar-store.js";
|
|
8
|
+
export function readRecordedRepositoryIdentity(sessionFile) {
|
|
9
|
+
let recorded;
|
|
10
|
+
for (const entry of loadEntriesFromFile(sessionFile)) {
|
|
11
|
+
if (entry.type === "custom" && entry.customType === REPOSITORY_IDENTITY_ENTRY_TYPE) {
|
|
12
|
+
recorded = parseRepositoryIdentity(entry.data) ?? recorded;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return recorded;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Whether `cwd` is the git repository a session was recorded in. The session side prefers the live
|
|
19
|
+
* checkout at its recorded cwd and falls back to the identity the session recorded, because a moved
|
|
20
|
+
* repository leaves nothing at the old path to ask.
|
|
21
|
+
*/
|
|
22
|
+
export async function classifySessionRepository(sessionFile, sessionCwd, cwd, read = readRepositoryIdentity) {
|
|
23
|
+
const [current, live] = await Promise.all([
|
|
24
|
+
read(cwd),
|
|
25
|
+
existsSync(sessionCwd) ? read(sessionCwd) : Promise.resolve(undefined),
|
|
26
|
+
]);
|
|
27
|
+
return compareRepositoryIdentities(live ?? readRecordedRepositoryIdentity(sessionFile), current);
|
|
28
|
+
}
|
|
29
|
+
export function readSessionCwd(sessionFile) {
|
|
30
|
+
const { cwd } = readHeaderLine(resolvePath(sessionFile)).header;
|
|
31
|
+
return typeof cwd === "string" && cwd !== "" ? cwd : undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Moves a session recorded under another project into `targetCwd`'s session directory and points its
|
|
35
|
+
* header at `targetCwd`. The id, file name, every entry after the header, and the session's extension
|
|
36
|
+
* sidecars are kept. The relocated copy is complete before the source is removed, so an interrupted
|
|
37
|
+
* rebind leaves the session readable in at least one place. Returns the session file's new path.
|
|
38
|
+
*
|
|
39
|
+
* The move runs under the session's cross-process move lock and refuses (`SessionHeldError`) while
|
|
40
|
+
* another live process has the session open. A rebind that finds the same move already done by a
|
|
41
|
+
* concurrent process returns that target.
|
|
42
|
+
*/
|
|
43
|
+
export async function rebindSessionFile(sourcePath, targetCwd, sessionDir, options = {}) {
|
|
44
|
+
const source = resolvePath(sourcePath);
|
|
45
|
+
const cwd = resolvePath(targetCwd);
|
|
46
|
+
const targetDir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);
|
|
47
|
+
const target = join(targetDir, basename(source));
|
|
48
|
+
if (target !== source && !existsSync(source) && existsSync(target))
|
|
49
|
+
return target;
|
|
50
|
+
const { header } = readHeaderLine(source);
|
|
51
|
+
return withSessionMoveLock(source, header.id, () => moveSessionFile(source, target, cwd), {
|
|
52
|
+
...(options.moveLockWaitMs === undefined ? {} : { waitMs: options.moveLockWaitMs }),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function moveSessionFile(source, target, cwd) {
|
|
56
|
+
if (target !== source && !existsSync(source) && existsSync(target))
|
|
57
|
+
return target;
|
|
58
|
+
const { header, rest } = readHeaderLine(source);
|
|
59
|
+
if (target !== source && existsSync(target)) {
|
|
60
|
+
throw new Error(`Cannot rebind session: ${target} already exists`);
|
|
61
|
+
}
|
|
62
|
+
const targetDir = dirname(target);
|
|
63
|
+
mkdirSync(targetDir, { recursive: true });
|
|
64
|
+
const temp = join(targetDir, `.${basename(source)}.rebind-${process.pid}.tmp`);
|
|
65
|
+
writeFileSync(temp, `${JSON.stringify({ ...header, cwd })}\n${rest}`, { flag: "wx" });
|
|
66
|
+
if (target !== source)
|
|
67
|
+
moveSessionSidecars(dirname(source), targetDir, header.id);
|
|
68
|
+
renameSync(temp, target);
|
|
69
|
+
if (target !== source)
|
|
70
|
+
rmSync(source, { force: true });
|
|
71
|
+
return target;
|
|
72
|
+
}
|
|
73
|
+
function readHeaderLine(sessionFile) {
|
|
74
|
+
const content = readFileSync(sessionFile, "utf8");
|
|
75
|
+
const newline = content.indexOf("\n");
|
|
76
|
+
const firstLine = newline === -1 ? content : content.slice(0, newline);
|
|
77
|
+
let parsed;
|
|
78
|
+
try {
|
|
79
|
+
parsed = JSON.parse(firstLine);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
parsed = undefined;
|
|
83
|
+
}
|
|
84
|
+
if (!isSessionHeader(parsed)) {
|
|
85
|
+
throw new Error(`Cannot rebind session: ${sessionFile} does not start with a session header`);
|
|
86
|
+
}
|
|
87
|
+
return { header: parsed, rest: newline === -1 ? "" : content.slice(newline + 1) };
|
|
88
|
+
}
|
|
89
|
+
function isSessionHeader(value) {
|
|
90
|
+
return (typeof value === "object" &&
|
|
91
|
+
value !== null &&
|
|
92
|
+
value.type === "session" &&
|
|
93
|
+
typeof value.id === "string");
|
|
94
|
+
}
|
|
95
|
+
// Per-session extension state lives at `<sessionDir>/extensions/<extension>/<encoded id>.json`
|
|
96
|
+
// (goal, loop, terminal monitors); without it a rebound session would lose its goal and monitors.
|
|
97
|
+
function moveSessionSidecars(fromDir, toDir, sessionId) {
|
|
98
|
+
const extensionsDir = join(fromDir, "extensions");
|
|
99
|
+
const sidecarName = `${encodedSessionId(sessionId)}.json`;
|
|
100
|
+
if (existsSync(extensionsDir)) {
|
|
101
|
+
for (const extension of readdirSync(extensionsDir, { withFileTypes: true })) {
|
|
102
|
+
if (!extension.isDirectory())
|
|
103
|
+
continue;
|
|
104
|
+
const from = join(extensionsDir, extension.name, sidecarName);
|
|
105
|
+
if (existsSync(from))
|
|
106
|
+
movePath(from, join(toDir, "extensions", extension.name, sidecarName));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const blobs = join(fromDir, "resident-blobs", sessionId);
|
|
110
|
+
if (existsSync(blobs))
|
|
111
|
+
movePath(blobs, join(toDir, "resident-blobs", sessionId));
|
|
112
|
+
}
|
|
113
|
+
function movePath(from, to) {
|
|
114
|
+
if (existsSync(to))
|
|
115
|
+
return;
|
|
116
|
+
mkdirSync(dirname(to), { recursive: true });
|
|
117
|
+
try {
|
|
118
|
+
renameSync(from, to);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (error.code !== "EXDEV")
|
|
122
|
+
throw error;
|
|
123
|
+
cpSync(from, to, { recursive: true });
|
|
124
|
+
rmSync(from, { recursive: true, force: true });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=session-rebind.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-rebind.js","sourceRoot":"","sources":["../../src/core/session-rebind.ts"],"names":[],"mappings":"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","sourcesContent":["import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { basename, dirname, join } from \"node:path\";\nimport { normalizePath, resolvePath } from \"../utils/paths.ts\";\nimport {\n\tcompareRepositoryIdentities,\n\tparseRepositoryIdentity,\n\tREPOSITORY_IDENTITY_ENTRY_TYPE,\n\ttype RepositoryIdentity,\n\ttype RepositoryMatch,\n\treadRepositoryIdentity,\n} from \"./repository-identity.ts\";\nimport { withSessionMoveLock } from \"./session-holders.ts\";\nimport { getDefaultSessionDir, loadEntriesFromFile } from \"./session-manager.ts\";\nimport { encodedSessionId } from \"./session-sidecar-store.ts\";\n\ninterface SessionHeaderLine {\n\treadonly header: { readonly type: \"session\"; readonly id: string; readonly cwd?: unknown };\n\treadonly rest: string;\n}\n\nexport function readRecordedRepositoryIdentity(sessionFile: string): RepositoryIdentity | undefined {\n\tlet recorded: RepositoryIdentity | undefined;\n\tfor (const entry of loadEntriesFromFile(sessionFile)) {\n\t\tif (entry.type === \"custom\" && entry.customType === REPOSITORY_IDENTITY_ENTRY_TYPE) {\n\t\t\trecorded = parseRepositoryIdentity(entry.data) ?? recorded;\n\t\t}\n\t}\n\treturn recorded;\n}\n\n/**\n * Whether `cwd` is the git repository a session was recorded in. The session side prefers the live\n * checkout at its recorded cwd and falls back to the identity the session recorded, because a moved\n * repository leaves nothing at the old path to ask.\n */\nexport async function classifySessionRepository(\n\tsessionFile: string,\n\tsessionCwd: string,\n\tcwd: string,\n\tread: (dir: string) => Promise<RepositoryIdentity | undefined> = readRepositoryIdentity,\n): Promise<RepositoryMatch> {\n\tconst [current, live] = await Promise.all([\n\t\tread(cwd),\n\t\texistsSync(sessionCwd) ? read(sessionCwd) : Promise.resolve(undefined),\n\t]);\n\treturn compareRepositoryIdentities(live ?? readRecordedRepositoryIdentity(sessionFile), current);\n}\n\nexport function readSessionCwd(sessionFile: string): string | undefined {\n\tconst { cwd } = readHeaderLine(resolvePath(sessionFile)).header;\n\treturn typeof cwd === \"string\" && cwd !== \"\" ? cwd : undefined;\n}\n\n/**\n * Moves a session recorded under another project into `targetCwd`'s session directory and points its\n * header at `targetCwd`. The id, file name, every entry after the header, and the session's extension\n * sidecars are kept. The relocated copy is complete before the source is removed, so an interrupted\n * rebind leaves the session readable in at least one place. Returns the session file's new path.\n *\n * The move runs under the session's cross-process move lock and refuses (`SessionHeldError`) while\n * another live process has the session open. A rebind that finds the same move already done by a\n * concurrent process returns that target.\n */\nexport async function rebindSessionFile(\n\tsourcePath: string,\n\ttargetCwd: string,\n\tsessionDir?: string,\n\toptions: { readonly moveLockWaitMs?: number } = {},\n): Promise<string> {\n\tconst source = resolvePath(sourcePath);\n\tconst cwd = resolvePath(targetCwd);\n\tconst targetDir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);\n\tconst target = join(targetDir, basename(source));\n\tif (target !== source && !existsSync(source) && existsSync(target)) return target;\n\tconst { header } = readHeaderLine(source);\n\treturn withSessionMoveLock(source, header.id, () => moveSessionFile(source, target, cwd), {\n\t\t...(options.moveLockWaitMs === undefined ? {} : { waitMs: options.moveLockWaitMs }),\n\t});\n}\n\nfunction moveSessionFile(source: string, target: string, cwd: string): string {\n\tif (target !== source && !existsSync(source) && existsSync(target)) return target;\n\tconst { header, rest } = readHeaderLine(source);\n\tif (target !== source && existsSync(target)) {\n\t\tthrow new Error(`Cannot rebind session: ${target} already exists`);\n\t}\n\tconst targetDir = dirname(target);\n\tmkdirSync(targetDir, { recursive: true });\n\tconst temp = join(targetDir, `.${basename(source)}.rebind-${process.pid}.tmp`);\n\twriteFileSync(temp, `${JSON.stringify({ ...header, cwd })}\\n${rest}`, { flag: \"wx\" });\n\tif (target !== source) moveSessionSidecars(dirname(source), targetDir, header.id);\n\trenameSync(temp, target);\n\tif (target !== source) rmSync(source, { force: true });\n\treturn target;\n}\n\nfunction readHeaderLine(sessionFile: string): SessionHeaderLine {\n\tconst content = readFileSync(sessionFile, \"utf8\");\n\tconst newline = content.indexOf(\"\\n\");\n\tconst firstLine = newline === -1 ? content : content.slice(0, newline);\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(firstLine);\n\t} catch {\n\t\tparsed = undefined;\n\t}\n\tif (!isSessionHeader(parsed)) {\n\t\tthrow new Error(`Cannot rebind session: ${sessionFile} does not start with a session header`);\n\t}\n\treturn { header: parsed, rest: newline === -1 ? \"\" : content.slice(newline + 1) };\n}\n\nfunction isSessionHeader(value: unknown): value is SessionHeaderLine[\"header\"] {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t(value as { type?: unknown }).type === \"session\" &&\n\t\ttypeof (value as { id?: unknown }).id === \"string\"\n\t);\n}\n\n// Per-session extension state lives at `<sessionDir>/extensions/<extension>/<encoded id>.json`\n// (goal, loop, terminal monitors); without it a rebound session would lose its goal and monitors.\nfunction moveSessionSidecars(fromDir: string, toDir: string, sessionId: string): void {\n\tconst extensionsDir = join(fromDir, \"extensions\");\n\tconst sidecarName = `${encodedSessionId(sessionId)}.json`;\n\tif (existsSync(extensionsDir)) {\n\t\tfor (const extension of readdirSync(extensionsDir, { withFileTypes: true })) {\n\t\t\tif (!extension.isDirectory()) continue;\n\t\t\tconst from = join(extensionsDir, extension.name, sidecarName);\n\t\t\tif (existsSync(from)) movePath(from, join(toDir, \"extensions\", extension.name, sidecarName));\n\t\t}\n\t}\n\tconst blobs = join(fromDir, \"resident-blobs\", sessionId);\n\tif (existsSync(blobs)) movePath(blobs, join(toDir, \"resident-blobs\", sessionId));\n}\n\nfunction movePath(from: string, to: string): void {\n\tif (existsSync(to)) return;\n\tmkdirSync(dirname(to), { recursive: true });\n\ttry {\n\t\trenameSync(from, to);\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code !== \"EXDEV\") throw error;\n\t\tcpSync(from, to, { recursive: true });\n\t\trmSync(from, { recursive: true, force: true });\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-summary-index-file.d.ts","sourceRoot":"","sources":["../../src/core/session-summary-index-file.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"session-summary-index-file.d.ts","sourceRoot":"","sources":["../../src/core/session-summary-index-file.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D;;;GAGG;AACH,eAAO,MAAM,0BAA0B,6BAA6B,CAAC;AAMrE,yFAAyF;AACzF,MAAM,MAAM,UAAU,GAAG;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,yFAAyF;AACzF,MAAM,MAAM,WAAW,GAAG;IACzB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,4CAA4C;IAC5C,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAClD,wEAAwE;IACxE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,yFAAyF;IACzF,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CAClC,CAAC;AA8BF,8FAA8F;AAC9F,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,GAAG,UAAU,GAAG,SAAS,CAQ5G;AA6BD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA0BtE;AAED,4FAA4F;AAC5F,wBAAsB,kBAAkB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,eAAe,EAAE,OAAO,GACtB,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CASlG"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { appendFile, rename, rm, writeFile } from "fs/promises";
|
|
2
|
+
import { parseRepositoryIdentity } from "./repository-identity.js";
|
|
2
3
|
import { readFileLines } from "./session-summary.js";
|
|
3
4
|
/**
|
|
4
5
|
* Name of the summary index inside the sessions directory it describes. The
|
|
5
6
|
* extension is deliberately not `.jsonl`: every `*.jsonl` there is a session.
|
|
6
7
|
*/
|
|
7
8
|
export const SESSION_SUMMARY_INDEX_FILE = ".session-summaries.index";
|
|
8
|
-
|
|
9
|
+
// 2: summaries carry the recorded repository identity; a version-1 index predates it and is re-read.
|
|
10
|
+
const INDEX_VERSION = 2;
|
|
9
11
|
const HEADER_LINE = `${JSON.stringify({ version: INDEX_VERSION })}\n`;
|
|
10
12
|
const UNUSABLE = { usable: false, entries: new Map(), entryBytes: 0, endsWithNewline: true };
|
|
11
13
|
function isObject(value) {
|
|
@@ -23,7 +25,8 @@ function isSessionSummary(value) {
|
|
|
23
25
|
typeof value.messageCount === "number" &&
|
|
24
26
|
typeof value.allMessagesText === "string" &&
|
|
25
27
|
isOptional(value.name, "string") &&
|
|
26
|
-
isOptional(value.lastActivityTime, "number")
|
|
28
|
+
isOptional(value.lastActivityTime, "number") &&
|
|
29
|
+
(value.repositoryIdentity === undefined || parseRepositoryIdentity(value.repositoryIdentity) !== undefined));
|
|
27
30
|
}
|
|
28
31
|
function isFiniteNumber(value) {
|
|
29
32
|
return typeof value === "number" && Number.isFinite(value);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-summary-index-file.js","sourceRoot":"","sources":["../../src/core/session-summary-index-file.ts"],"names":[],"mappings":"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","sourcesContent":["import { appendFile, rename, rm, writeFile } from \"fs/promises\";\nimport { readFileLines, type SessionSummary } from \"./session-summary.ts\";\nimport type { FileStamp } from \"./session-summary-lru.ts\";\n\n/**\n * Name of the summary index inside the sessions directory it describes. The\n * extension is deliberately not `.jsonl`: every `*.jsonl` there is a session.\n */\nexport const SESSION_SUMMARY_INDEX_FILE = \".session-summaries.index\";\n\nconst INDEX_VERSION = 1;\nconst HEADER_LINE = `${JSON.stringify({ version: INDEX_VERSION })}\\n`;\n\n/** One indexed session file: its basename, the stamp it was read at, and its summary. */\nexport type IndexEntry = {\n\treadonly file: string;\n\treadonly stamp: FileStamp;\n\treadonly summary: SessionSummary;\n\t/** Serialized line, newline included. */\n\treadonly line: string;\n\treadonly lineBytes: number;\n};\n\n/** An index as loaded from disk; an absent or unusable file loads as `usable: false`. */\nexport type LoadedIndex = {\n\treadonly usable: boolean;\n\t/** Deduplicated entries, last line wins. */\n\treadonly entries: ReadonlyMap<string, IndexEntry>;\n\t/** Bytes after the header line, including torn and superseded lines. */\n\treadonly entryBytes: number;\n\t/** False when the file ends in a torn line, so the next append must start a new line. */\n\treadonly endsWithNewline: boolean;\n};\n\nconst UNUSABLE: LoadedIndex = { usable: false, entries: new Map(), entryBytes: 0, endsWithNewline: true };\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction isOptional(value: unknown, type: \"string\" | \"number\"): boolean {\n\treturn value === undefined || typeof value === type;\n}\n\nfunction isSessionSummary(value: unknown): value is SessionSummary {\n\tif (!isObject(value) || !isObject(value.header)) return false;\n\treturn (\n\t\tvalue.header.type === \"session\" &&\n\t\ttypeof value.header.id === \"string\" &&\n\t\ttypeof value.firstUserMessage === \"string\" &&\n\t\ttypeof value.messageCount === \"number\" &&\n\t\ttypeof value.allMessagesText === \"string\" &&\n\t\tisOptional(value.name, \"string\") &&\n\t\tisOptional(value.lastActivityTime, \"number\")\n\t);\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isFinite(value);\n}\n\n/** Serialize one entry, or undefined when the summary would not survive a JSON round trip. */\nexport function toIndexEntry(file: string, stamp: FileStamp, summary: SessionSummary): IndexEntry | undefined {\n\tif (summary.lastActivityTime !== undefined && !Number.isFinite(summary.lastActivityTime)) return undefined;\n\treturn withLine(\n\t\tfile,\n\t\tstamp,\n\t\tsummary,\n\t\t`${JSON.stringify({ file, size: stamp.size, mtimeMs: stamp.mtimeMs, summary })}\\n`,\n\t);\n}\n\nfunction withLine(file: string, stamp: FileStamp, summary: SessionSummary, line: string): IndexEntry {\n\treturn { file, stamp, summary, line, lineBytes: Buffer.byteLength(line, \"utf8\") };\n}\n\nfunction parseEntryLine(line: string): IndexEntry | undefined {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(line);\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (!isObject(parsed) || typeof parsed.file !== \"string\" || parsed.file.includes(\"/\")) return undefined;\n\tif (!isFiniteNumber(parsed.size) || !isFiniteNumber(parsed.mtimeMs) || !isSessionSummary(parsed.summary)) {\n\t\treturn undefined;\n\t}\n\treturn withLine(parsed.file, { size: parsed.size, mtimeMs: parsed.mtimeMs }, parsed.summary, `${line}\\n`);\n}\n\nfunction isCurrentHeader(line: string): boolean {\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(line);\n\t\treturn isObject(parsed) && parsed.version === INDEX_VERSION;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Load an index, tolerating every failure: a missing file, a missing or foreign\n * version header, or a read error loads as unusable; a line that does not parse\n * (a torn append, truncation, garbage) is skipped.\n */\nexport async function loadIndexFile(path: string): Promise<LoadedIndex> {\n\tconst entries = new Map<string, IndexEntry>();\n\tlet headerSeen = false;\n\tlet headerBytes = 0;\n\ttry {\n\t\tconst read = await readFileLines(path, (line) => {\n\t\t\tif (!headerSeen) {\n\t\t\t\theaderSeen = true;\n\t\t\t\theaderBytes = Buffer.byteLength(line, \"utf8\") + 1;\n\t\t\t\treturn isCurrentHeader(line);\n\t\t\t}\n\t\t\tconst entry = parseEntryLine(line);\n\t\t\tif (entry) entries.set(entry.file, entry);\n\t\t\treturn true;\n\t\t});\n\t\tif (!read.completed || !headerSeen) return UNUSABLE;\n\t\treturn {\n\t\t\tusable: true,\n\t\t\tentries,\n\t\t\tentryBytes: Math.max(0, read.bytes - headerBytes),\n\t\t\tendsWithNewline: read.endsWithNewline,\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof Error) return UNUSABLE;\n\t\tthrow error;\n\t}\n}\n\n/** Append entries one whole line per write, first closing a torn tail when there is one. */\nexport async function appendIndexEntries(\n\tpath: string,\n\tentries: readonly IndexEntry[],\n\tendsWithNewline: boolean,\n): Promise<void> {\n\tlet prefix = endsWithNewline ? \"\" : \"\\n\";\n\tfor (const entry of entries) {\n\t\tawait appendFile(path, prefix + entry.line);\n\t\tprefix = \"\";\n\t}\n}\n\nexport async function rewriteIndexFile(path: string, entries: readonly IndexEntry[]): Promise<void> {\n\tconst temp = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;\n\ttry {\n\t\tawait writeFile(temp, HEADER_LINE + entries.map((entry) => entry.line).join(\"\"));\n\t\tawait rename(temp, path);\n\t} catch (error) {\n\t\tawait rm(temp, { force: true }).catch(() => undefined);\n\t\tthrow error;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session-summary-index-file.js","sourceRoot":"","sources":["../../src/core/session-summary-index-file.ts"],"names":[],"mappings":"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","sourcesContent":["import { appendFile, rename, rm, writeFile } from \"fs/promises\";\nimport { parseRepositoryIdentity } from \"./repository-identity.ts\";\nimport { readFileLines, type SessionSummary } from \"./session-summary.ts\";\nimport type { FileStamp } from \"./session-summary-lru.ts\";\n\n/**\n * Name of the summary index inside the sessions directory it describes. The\n * extension is deliberately not `.jsonl`: every `*.jsonl` there is a session.\n */\nexport const SESSION_SUMMARY_INDEX_FILE = \".session-summaries.index\";\n\n// 2: summaries carry the recorded repository identity; a version-1 index predates it and is re-read.\nconst INDEX_VERSION = 2;\nconst HEADER_LINE = `${JSON.stringify({ version: INDEX_VERSION })}\\n`;\n\n/** One indexed session file: its basename, the stamp it was read at, and its summary. */\nexport type IndexEntry = {\n\treadonly file: string;\n\treadonly stamp: FileStamp;\n\treadonly summary: SessionSummary;\n\t/** Serialized line, newline included. */\n\treadonly line: string;\n\treadonly lineBytes: number;\n};\n\n/** An index as loaded from disk; an absent or unusable file loads as `usable: false`. */\nexport type LoadedIndex = {\n\treadonly usable: boolean;\n\t/** Deduplicated entries, last line wins. */\n\treadonly entries: ReadonlyMap<string, IndexEntry>;\n\t/** Bytes after the header line, including torn and superseded lines. */\n\treadonly entryBytes: number;\n\t/** False when the file ends in a torn line, so the next append must start a new line. */\n\treadonly endsWithNewline: boolean;\n};\n\nconst UNUSABLE: LoadedIndex = { usable: false, entries: new Map(), entryBytes: 0, endsWithNewline: true };\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction isOptional(value: unknown, type: \"string\" | \"number\"): boolean {\n\treturn value === undefined || typeof value === type;\n}\n\nfunction isSessionSummary(value: unknown): value is SessionSummary {\n\tif (!isObject(value) || !isObject(value.header)) return false;\n\treturn (\n\t\tvalue.header.type === \"session\" &&\n\t\ttypeof value.header.id === \"string\" &&\n\t\ttypeof value.firstUserMessage === \"string\" &&\n\t\ttypeof value.messageCount === \"number\" &&\n\t\ttypeof value.allMessagesText === \"string\" &&\n\t\tisOptional(value.name, \"string\") &&\n\t\tisOptional(value.lastActivityTime, \"number\") &&\n\t\t(value.repositoryIdentity === undefined || parseRepositoryIdentity(value.repositoryIdentity) !== undefined)\n\t);\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isFinite(value);\n}\n\n/** Serialize one entry, or undefined when the summary would not survive a JSON round trip. */\nexport function toIndexEntry(file: string, stamp: FileStamp, summary: SessionSummary): IndexEntry | undefined {\n\tif (summary.lastActivityTime !== undefined && !Number.isFinite(summary.lastActivityTime)) return undefined;\n\treturn withLine(\n\t\tfile,\n\t\tstamp,\n\t\tsummary,\n\t\t`${JSON.stringify({ file, size: stamp.size, mtimeMs: stamp.mtimeMs, summary })}\\n`,\n\t);\n}\n\nfunction withLine(file: string, stamp: FileStamp, summary: SessionSummary, line: string): IndexEntry {\n\treturn { file, stamp, summary, line, lineBytes: Buffer.byteLength(line, \"utf8\") };\n}\n\nfunction parseEntryLine(line: string): IndexEntry | undefined {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(line);\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (!isObject(parsed) || typeof parsed.file !== \"string\" || parsed.file.includes(\"/\")) return undefined;\n\tif (!isFiniteNumber(parsed.size) || !isFiniteNumber(parsed.mtimeMs) || !isSessionSummary(parsed.summary)) {\n\t\treturn undefined;\n\t}\n\treturn withLine(parsed.file, { size: parsed.size, mtimeMs: parsed.mtimeMs }, parsed.summary, `${line}\\n`);\n}\n\nfunction isCurrentHeader(line: string): boolean {\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(line);\n\t\treturn isObject(parsed) && parsed.version === INDEX_VERSION;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Load an index, tolerating every failure: a missing file, a missing or foreign\n * version header, or a read error loads as unusable; a line that does not parse\n * (a torn append, truncation, garbage) is skipped.\n */\nexport async function loadIndexFile(path: string): Promise<LoadedIndex> {\n\tconst entries = new Map<string, IndexEntry>();\n\tlet headerSeen = false;\n\tlet headerBytes = 0;\n\ttry {\n\t\tconst read = await readFileLines(path, (line) => {\n\t\t\tif (!headerSeen) {\n\t\t\t\theaderSeen = true;\n\t\t\t\theaderBytes = Buffer.byteLength(line, \"utf8\") + 1;\n\t\t\t\treturn isCurrentHeader(line);\n\t\t\t}\n\t\t\tconst entry = parseEntryLine(line);\n\t\t\tif (entry) entries.set(entry.file, entry);\n\t\t\treturn true;\n\t\t});\n\t\tif (!read.completed || !headerSeen) return UNUSABLE;\n\t\treturn {\n\t\t\tusable: true,\n\t\t\tentries,\n\t\t\tentryBytes: Math.max(0, read.bytes - headerBytes),\n\t\t\tendsWithNewline: read.endsWithNewline,\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof Error) return UNUSABLE;\n\t\tthrow error;\n\t}\n}\n\n/** Append entries one whole line per write, first closing a torn tail when there is one. */\nexport async function appendIndexEntries(\n\tpath: string,\n\tentries: readonly IndexEntry[],\n\tendsWithNewline: boolean,\n): Promise<void> {\n\tlet prefix = endsWithNewline ? \"\" : \"\\n\";\n\tfor (const entry of entries) {\n\t\tawait appendFile(path, prefix + entry.line);\n\t\tprefix = \"\";\n\t}\n}\n\nexport async function rewriteIndexFile(path: string, entries: readonly IndexEntry[]): Promise<void> {\n\tconst temp = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;\n\ttry {\n\t\tawait writeFile(temp, HEADER_LINE + entries.map((entry) => entry.line).join(\"\"));\n\t\tawait rename(temp, path);\n\t} catch (error) {\n\t\tawait rm(temp, { force: true }).catch(() => undefined);\n\t\tthrow error;\n\t}\n}\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type RepositoryIdentity } from "./repository-identity.ts";
|
|
1
2
|
import type { SessionHeader } from "./session-manager.ts";
|
|
2
3
|
/**
|
|
3
4
|
* Everything a picker row needs from one session file, derived from every record
|
|
@@ -17,6 +18,8 @@ export type SessionSummary = {
|
|
|
17
18
|
readonly lastActivityTime: number | undefined;
|
|
18
19
|
/** Full user/assistant transcript text in file order. */
|
|
19
20
|
readonly allMessagesText: string;
|
|
21
|
+
/** Latest repository identity the session recorded, so a moved repository's sessions can be found. */
|
|
22
|
+
readonly repositoryIdentity?: RepositoryIdentity;
|
|
20
23
|
};
|
|
21
24
|
/** What a completed {@link readFileLines} pass saw. */
|
|
22
25
|
export type FileLinesRead = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-summary.d.ts","sourceRoot":"","sources":["../../src/core/session-summary.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAG1D;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG;IAC5B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,4DAA4D;IAC5D,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,yDAAyD;IACzD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,yEAAyE;IACzE,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,yDAAyD;IACzD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"session-summary.d.ts","sourceRoot":"","sources":["../../src/core/session-summary.ts"],"names":[],"mappings":"AAEA,OAAO,EAGN,KAAK,kBAAkB,EACvB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAG1D;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG;IAC5B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,4DAA4D;IAC5D,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,yDAAyD;IACzD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,yEAAyE;IACzE,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,yDAAyD;IACzD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,sGAAsG;IACtG,QAAQ,CAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACjD,CAAC;AAqEF,uDAAuD;AACvD,MAAM,MAAM,aAAa,GAAG;IAC3B,kDAAkD;IAClD,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,gCAAgC;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CAClC,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAmC/G;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAuBzF"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { open } from "fs/promises";
|
|
2
2
|
import { StringDecoder } from "string_decoder";
|
|
3
|
+
import { parseRepositoryIdentity, REPOSITORY_IDENTITY_ENTRY_TYPE, } from "./repository-identity.js";
|
|
3
4
|
import { parseEntryLine, sessionInfoName, visibleMessage } from "./session-record.js";
|
|
4
5
|
function newAccumulator() {
|
|
5
6
|
return {
|
|
@@ -8,6 +9,7 @@ function newAccumulator() {
|
|
|
8
9
|
firstUserMessage: "",
|
|
9
10
|
messageCount: 0,
|
|
10
11
|
lastActivityTime: undefined,
|
|
12
|
+
repositoryIdentity: undefined,
|
|
11
13
|
texts: [],
|
|
12
14
|
};
|
|
13
15
|
}
|
|
@@ -27,6 +29,10 @@ function accumulateLine(accumulator, line) {
|
|
|
27
29
|
accumulator.header = entry;
|
|
28
30
|
return true;
|
|
29
31
|
}
|
|
32
|
+
if (entry.type === "custom" && entry.customType === REPOSITORY_IDENTITY_ENTRY_TYPE) {
|
|
33
|
+
accumulator.repositoryIdentity = parseRepositoryIdentity(entry.data) ?? accumulator.repositoryIdentity;
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
30
36
|
const infoName = sessionInfoName(entry);
|
|
31
37
|
if (infoName !== null) {
|
|
32
38
|
accumulator.name = infoName;
|
|
@@ -124,6 +130,7 @@ export async function readSessionSummary(filePath) {
|
|
|
124
130
|
messageCount: accumulator.messageCount,
|
|
125
131
|
lastActivityTime: accumulator.lastActivityTime,
|
|
126
132
|
allMessagesText: accumulator.texts.join(" "),
|
|
133
|
+
...(accumulator.repositoryIdentity ? { repositoryIdentity: accumulator.repositoryIdentity } : {}),
|
|
127
134
|
};
|
|
128
135
|
}
|
|
129
136
|
//# sourceMappingURL=session-summary.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-summary.js","sourceRoot":"","sources":["../../src/core/session-summary.ts"],"names":[],"mappings":"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","sourcesContent":["import { open } from \"fs/promises\";\nimport { StringDecoder } from \"string_decoder\";\nimport type { SessionHeader } from \"./session-manager.ts\";\nimport { parseEntryLine, sessionInfoName, visibleMessage } from \"./session-record.ts\";\n\n/**\n * Everything a picker row needs from one session file, derived from every record\n * in the file. Exact by construction: no window, no marker heuristic, so a first\n * user message, a rename, or an out-of-order activity timestamp is found no\n * matter how deep in the transcript it sits.\n */\nexport type SessionSummary = {\n\treadonly header: SessionHeader;\n\t/** Latest `session_info` name anywhere in the file, including explicit clears. */\n\treadonly name: string | undefined;\n\t/** First visible user message text anywhere in the file. */\n\treadonly firstUserMessage: string;\n\t/** Count of records that parsed as `type: \"message\"`. */\n\treadonly messageCount: number;\n\t/** Max user/assistant activity time seen, regardless of record order. */\n\treadonly lastActivityTime: number | undefined;\n\t/** Full user/assistant transcript text in file order. */\n\treadonly allMessagesText: string;\n};\n\ntype SummaryAccumulator = {\n\theader: SessionHeader | null;\n\tname: string | undefined;\n\tfirstUserMessage: string;\n\tmessageCount: number;\n\tlastActivityTime: number | undefined;\n\treadonly texts: string[];\n};\n\nfunction newAccumulator(): SummaryAccumulator {\n\treturn {\n\t\theader: null,\n\t\tname: undefined,\n\t\tfirstUserMessage: \"\",\n\t\tmessageCount: 0,\n\t\tlastActivityTime: undefined,\n\t\ttexts: [],\n\t};\n}\n\n/**\n * Fold one JSONL line into the accumulator.\n *\n * Returns false when the file's first parsable record is not a session header,\n * which means the file is not a session and the caller stops reading.\n */\nfunction accumulateLine(accumulator: SummaryAccumulator, line: string): boolean {\n\tconst entry = parseEntryLine(line);\n\tif (!entry) return true;\n\n\tif (!accumulator.header) {\n\t\tif (entry.type !== \"session\" || typeof entry.id !== \"string\") return false;\n\t\taccumulator.header = entry;\n\t\treturn true;\n\t}\n\n\tconst infoName = sessionInfoName(entry);\n\tif (infoName !== null) {\n\t\taccumulator.name = infoName;\n\t\treturn true;\n\t}\n\n\tif (entry.type !== \"message\") return true;\n\taccumulator.messageCount++;\n\n\tconst visible = visibleMessage(entry);\n\tif (!visible) return true;\n\tif (typeof visible.time === \"number\") {\n\t\taccumulator.lastActivityTime = Math.max(accumulator.lastActivityTime ?? 0, visible.time);\n\t}\n\tif (!visible.text) return true;\n\taccumulator.texts.push(visible.text);\n\tif (!accumulator.firstUserMessage && visible.role === \"user\") {\n\t\taccumulator.firstUserMessage = visible.text;\n\t}\n\treturn true;\n}\n\nconst LINE_READ_BUFFER_SIZE = 1024 * 1024;\n\n/** What a completed {@link readFileLines} pass saw. */\nexport type FileLinesRead = {\n\t/** False when `onLine` stopped the pass early. */\n\treadonly completed: boolean;\n\t/** Bytes read from the file. */\n\treadonly bytes: number;\n\t/** Whether the last byte read was a newline (a torn tail has none). */\n\treadonly endsWithNewline: boolean;\n};\n\n/**\n * Read a file line by line through one reused 1 MiB buffer, handing each line\n * (LF-split, a trailing CR dropped) to `onLine` until it returns false.\n *\n * This replaces `readline`, whose per-line async iteration dominated summary\n * cost (senpi#2087). Only the unfinished tail of a chunk is carried between\n * reads, and the next newline search resumes where the carried tail ends, so a\n * record larger than the buffer is not rescanned per chunk. Read errors throw.\n */\nexport async function readFileLines(filePath: string, onLine: (line: string) => boolean): Promise<FileLinesRead> {\n\tconst handle = await open(filePath, \"r\");\n\ttry {\n\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\tconst buffer = Buffer.allocUnsafe(LINE_READ_BUFFER_SIZE);\n\t\tlet pending = \"\";\n\t\tlet bytes = 0;\n\t\tlet lastByte = -1;\n\t\tconst emit = (line: string): boolean => onLine(line.endsWith(\"\\r\") ? line.slice(0, -1) : line);\n\n\t\twhile (true) {\n\t\t\tconst { bytesRead } = await handle.read(buffer, 0, buffer.length, null);\n\t\t\tif (bytesRead === 0) break;\n\t\t\tbytes += bytesRead;\n\t\t\tlastByte = buffer[bytesRead - 1] ?? -1;\n\n\t\t\tconst searchFrom = pending.length;\n\t\t\tpending += decoder.write(buffer.subarray(0, bytesRead));\n\t\t\tlet lineStart = 0;\n\t\t\tlet newlineIndex = pending.indexOf(\"\\n\", searchFrom);\n\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\tif (!emit(pending.slice(lineStart, newlineIndex)))\n\t\t\t\t\treturn { completed: false, bytes, endsWithNewline: false };\n\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\tnewlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t}\n\t\t\tpending = pending.slice(lineStart);\n\t\t}\n\n\t\tpending += decoder.end();\n\t\tif (pending && !emit(pending)) return { completed: false, bytes, endsWithNewline: false };\n\t\treturn { completed: true, bytes, endsWithNewline: lastByte === 0x0a };\n\t} finally {\n\t\tawait handle.close();\n\t}\n}\n\n/**\n * Read one session file line by line and fold it into an exact summary.\n *\n * Memory stays bounded by one read buffer plus the transcript text the summary\n * contract requires; no full-file string is ever materialized. A file whose\n * first record is not a session header, or that cannot be read, yields null.\n */\nexport async function readSessionSummary(filePath: string): Promise<SessionSummary | null> {\n\tconst accumulator = newAccumulator();\n\n\ttry {\n\t\tconst read = await readFileLines(filePath, (line) => accumulateLine(accumulator, line));\n\t\tif (!read.completed) return null;\n\t} catch (error) {\n\t\tif (error instanceof Error) return null;\n\t\tthrow error;\n\t}\n\n\tconst header = accumulator.header;\n\tif (!header) return null;\n\n\treturn {\n\t\theader,\n\t\tname: accumulator.name,\n\t\tfirstUserMessage: accumulator.firstUserMessage,\n\t\tmessageCount: accumulator.messageCount,\n\t\tlastActivityTime: accumulator.lastActivityTime,\n\t\tallMessagesText: accumulator.texts.join(\" \"),\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session-summary.js","sourceRoot":"","sources":["../../src/core/session-summary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EACN,uBAAuB,EACvB,8BAA8B,GAE9B,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAkCtF,SAAS,cAAc;IACtB,OAAO;QACN,MAAM,EAAE,IAAI;QACZ,IAAI,EAAE,SAAS;QACf,gBAAgB,EAAE,EAAE;QACpB,YAAY,EAAE,CAAC;QACf,gBAAgB,EAAE,SAAS;QAC3B,kBAAkB,EAAE,SAAS;QAC7B,KAAK,EAAE,EAAE;KACT,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,cAAc,CAAC,WAA+B,EAAE,IAAY;IACpE,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;QACzB,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC3E,WAAW,CAAC,MAAM,GAAG,KAAK,CAAC;QAC3B,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,KAAK,8BAA8B,EAAE,CAAC;QACpF,WAAW,CAAC,kBAAkB,GAAG,uBAAuB,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC,kBAAkB,CAAC;QACvG,OAAO,IAAI,CAAC;IACb,CAAC;IAED,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACxC,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACvB,WAAW,CAAC,IAAI,GAAG,QAAQ,CAAC;QAC5B,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC1C,WAAW,CAAC,YAAY,EAAE,CAAC;IAE3B,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACtC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtC,WAAW,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,gBAAgB,IAAI,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1F,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,CAAC,WAAW,CAAC,gBAAgB,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC9D,WAAW,CAAC,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAC7C,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED,MAAM,qBAAqB,GAAG,IAAI,GAAG,IAAI,CAAC;AAY1C;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgB,EAAE,MAAiC;IACtF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,qBAAqB,CAAC,CAAC;QACzD,IAAI,OAAO,GAAG,EAAE,CAAC;QACjB,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC;QAClB,MAAM,IAAI,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAE/F,OAAO,IAAI,EAAE,CAAC;YACb,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACxE,IAAI,SAAS,KAAK,CAAC;gBAAE,MAAM;YAC3B,KAAK,IAAI,SAAS,CAAC;YACnB,QAAQ,GAAG,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YAEvC,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;YAClC,OAAO,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;YACxD,IAAI,SAAS,GAAG,CAAC,CAAC;YAClB,IAAI,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;YACrD,OAAO,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;gBAC5B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;oBAChD,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC;gBAC5D,SAAS,GAAG,YAAY,GAAG,CAAC,CAAC;gBAC7B,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YACjD,CAAC;YACD,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACpC,CAAC;QAED,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QACzB,IAAI,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC;QAC1F,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,eAAe,EAAE,QAAQ,KAAK,IAAI,EAAE,CAAC;IACvE,CAAC;YAAS,CAAC;QACV,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;AACF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,QAAgB;IACxD,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IAErC,IAAI,CAAC;QACJ,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC;QACxF,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAI,KAAK,YAAY,KAAK;YAAE,OAAO,IAAI,CAAC;QACxC,MAAM,KAAK,CAAC;IACb,CAAC;IAED,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC;IAClC,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,OAAO;QACN,MAAM;QACN,IAAI,EAAE,WAAW,CAAC,IAAI;QACtB,gBAAgB,EAAE,WAAW,CAAC,gBAAgB;QAC9C,YAAY,EAAE,WAAW,CAAC,YAAY;QACtC,gBAAgB,EAAE,WAAW,CAAC,gBAAgB;QAC9C,eAAe,EAAE,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;QAC5C,GAAG,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,WAAW,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACjG,CAAC;AACH,CAAC","sourcesContent":["import { open } from \"fs/promises\";\nimport { StringDecoder } from \"string_decoder\";\nimport {\n\tparseRepositoryIdentity,\n\tREPOSITORY_IDENTITY_ENTRY_TYPE,\n\ttype RepositoryIdentity,\n} from \"./repository-identity.ts\";\nimport type { SessionHeader } from \"./session-manager.ts\";\nimport { parseEntryLine, sessionInfoName, visibleMessage } from \"./session-record.ts\";\n\n/**\n * Everything a picker row needs from one session file, derived from every record\n * in the file. Exact by construction: no window, no marker heuristic, so a first\n * user message, a rename, or an out-of-order activity timestamp is found no\n * matter how deep in the transcript it sits.\n */\nexport type SessionSummary = {\n\treadonly header: SessionHeader;\n\t/** Latest `session_info` name anywhere in the file, including explicit clears. */\n\treadonly name: string | undefined;\n\t/** First visible user message text anywhere in the file. */\n\treadonly firstUserMessage: string;\n\t/** Count of records that parsed as `type: \"message\"`. */\n\treadonly messageCount: number;\n\t/** Max user/assistant activity time seen, regardless of record order. */\n\treadonly lastActivityTime: number | undefined;\n\t/** Full user/assistant transcript text in file order. */\n\treadonly allMessagesText: string;\n\t/** Latest repository identity the session recorded, so a moved repository's sessions can be found. */\n\treadonly repositoryIdentity?: RepositoryIdentity;\n};\n\ntype SummaryAccumulator = {\n\theader: SessionHeader | null;\n\tname: string | undefined;\n\tfirstUserMessage: string;\n\tmessageCount: number;\n\tlastActivityTime: number | undefined;\n\trepositoryIdentity: RepositoryIdentity | undefined;\n\treadonly texts: string[];\n};\n\nfunction newAccumulator(): SummaryAccumulator {\n\treturn {\n\t\theader: null,\n\t\tname: undefined,\n\t\tfirstUserMessage: \"\",\n\t\tmessageCount: 0,\n\t\tlastActivityTime: undefined,\n\t\trepositoryIdentity: undefined,\n\t\ttexts: [],\n\t};\n}\n\n/**\n * Fold one JSONL line into the accumulator.\n *\n * Returns false when the file's first parsable record is not a session header,\n * which means the file is not a session and the caller stops reading.\n */\nfunction accumulateLine(accumulator: SummaryAccumulator, line: string): boolean {\n\tconst entry = parseEntryLine(line);\n\tif (!entry) return true;\n\n\tif (!accumulator.header) {\n\t\tif (entry.type !== \"session\" || typeof entry.id !== \"string\") return false;\n\t\taccumulator.header = entry;\n\t\treturn true;\n\t}\n\n\tif (entry.type === \"custom\" && entry.customType === REPOSITORY_IDENTITY_ENTRY_TYPE) {\n\t\taccumulator.repositoryIdentity = parseRepositoryIdentity(entry.data) ?? accumulator.repositoryIdentity;\n\t\treturn true;\n\t}\n\n\tconst infoName = sessionInfoName(entry);\n\tif (infoName !== null) {\n\t\taccumulator.name = infoName;\n\t\treturn true;\n\t}\n\n\tif (entry.type !== \"message\") return true;\n\taccumulator.messageCount++;\n\n\tconst visible = visibleMessage(entry);\n\tif (!visible) return true;\n\tif (typeof visible.time === \"number\") {\n\t\taccumulator.lastActivityTime = Math.max(accumulator.lastActivityTime ?? 0, visible.time);\n\t}\n\tif (!visible.text) return true;\n\taccumulator.texts.push(visible.text);\n\tif (!accumulator.firstUserMessage && visible.role === \"user\") {\n\t\taccumulator.firstUserMessage = visible.text;\n\t}\n\treturn true;\n}\n\nconst LINE_READ_BUFFER_SIZE = 1024 * 1024;\n\n/** What a completed {@link readFileLines} pass saw. */\nexport type FileLinesRead = {\n\t/** False when `onLine` stopped the pass early. */\n\treadonly completed: boolean;\n\t/** Bytes read from the file. */\n\treadonly bytes: number;\n\t/** Whether the last byte read was a newline (a torn tail has none). */\n\treadonly endsWithNewline: boolean;\n};\n\n/**\n * Read a file line by line through one reused 1 MiB buffer, handing each line\n * (LF-split, a trailing CR dropped) to `onLine` until it returns false.\n *\n * This replaces `readline`, whose per-line async iteration dominated summary\n * cost (senpi#2087). Only the unfinished tail of a chunk is carried between\n * reads, and the next newline search resumes where the carried tail ends, so a\n * record larger than the buffer is not rescanned per chunk. Read errors throw.\n */\nexport async function readFileLines(filePath: string, onLine: (line: string) => boolean): Promise<FileLinesRead> {\n\tconst handle = await open(filePath, \"r\");\n\ttry {\n\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\tconst buffer = Buffer.allocUnsafe(LINE_READ_BUFFER_SIZE);\n\t\tlet pending = \"\";\n\t\tlet bytes = 0;\n\t\tlet lastByte = -1;\n\t\tconst emit = (line: string): boolean => onLine(line.endsWith(\"\\r\") ? line.slice(0, -1) : line);\n\n\t\twhile (true) {\n\t\t\tconst { bytesRead } = await handle.read(buffer, 0, buffer.length, null);\n\t\t\tif (bytesRead === 0) break;\n\t\t\tbytes += bytesRead;\n\t\t\tlastByte = buffer[bytesRead - 1] ?? -1;\n\n\t\t\tconst searchFrom = pending.length;\n\t\t\tpending += decoder.write(buffer.subarray(0, bytesRead));\n\t\t\tlet lineStart = 0;\n\t\t\tlet newlineIndex = pending.indexOf(\"\\n\", searchFrom);\n\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\tif (!emit(pending.slice(lineStart, newlineIndex)))\n\t\t\t\t\treturn { completed: false, bytes, endsWithNewline: false };\n\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\tnewlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t}\n\t\t\tpending = pending.slice(lineStart);\n\t\t}\n\n\t\tpending += decoder.end();\n\t\tif (pending && !emit(pending)) return { completed: false, bytes, endsWithNewline: false };\n\t\treturn { completed: true, bytes, endsWithNewline: lastByte === 0x0a };\n\t} finally {\n\t\tawait handle.close();\n\t}\n}\n\n/**\n * Read one session file line by line and fold it into an exact summary.\n *\n * Memory stays bounded by one read buffer plus the transcript text the summary\n * contract requires; no full-file string is ever materialized. A file whose\n * first record is not a session header, or that cannot be read, yields null.\n */\nexport async function readSessionSummary(filePath: string): Promise<SessionSummary | null> {\n\tconst accumulator = newAccumulator();\n\n\ttry {\n\t\tconst read = await readFileLines(filePath, (line) => accumulateLine(accumulator, line));\n\t\tif (!read.completed) return null;\n\t} catch (error) {\n\t\tif (error instanceof Error) return null;\n\t\tthrow error;\n\t}\n\n\tconst header = accumulator.header;\n\tif (!header) return null;\n\n\treturn {\n\t\theader,\n\t\tname: accumulator.name,\n\t\tfirstUserMessage: accumulator.firstUserMessage,\n\t\tmessageCount: accumulator.messageCount,\n\t\tlastActivityTime: accumulator.lastActivityTime,\n\t\tallMessagesText: accumulator.texts.join(\" \"),\n\t\t...(accumulator.repositoryIdentity ? { repositoryIdentity: accumulator.repositoryIdentity } : {}),\n\t};\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-title-generator.d.ts","sourceRoot":"","sources":["../../src/core/session-title-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,KAAK,EACX,GAAG,EAGH,KAAK,EACL,mBAAmB,
|
|
1
|
+
{"version":3,"file":"session-title-generator.d.ts","sourceRoot":"","sources":["../../src/core/session-title-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,KAAK,EACX,GAAG,EAGH,KAAK,EACL,mBAAmB,EAGnB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAkB,KAAK,WAAW,EAAsB,MAAM,8BAA8B,CAAC;AAGpG,UAAU,gBAAgB;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED,UAAU,2BAA2B;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC;CAC7B;AAmCD;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,WAAW,GAAG,WAAW,CAM1E;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAK5D;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAqB5G;AA8FD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAsCzD"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { completeSimple, retryAssistantCall } from "@earendil-works/pi-ai/compat";
|
|
2
|
+
import { initialTitleReasoning, mandatoryReasoningRetryLevel } from "./session-title-reasoning.js";
|
|
2
3
|
const TITLE_SYSTEM_PROMPT = `Generate a concise title for this coding-agent session.
|
|
3
4
|
|
|
4
5
|
Rules:
|
|
@@ -22,6 +23,10 @@ const LOW_SIGNAL_PROMPTS = new Set([
|
|
|
22
23
|
"yep",
|
|
23
24
|
"nope",
|
|
24
25
|
]);
|
|
26
|
+
const TITLE_MAX_TOKENS = 64;
|
|
27
|
+
// Reasoning tokens count against the completion budget, so a title that must reason needs room
|
|
28
|
+
// for the thinking and the `<title>` answer.
|
|
29
|
+
const REASONING_TITLE_MAX_TOKENS = 1024;
|
|
25
30
|
const MAX_TITLE_RETRIES = 1;
|
|
26
31
|
const MAX_TITLE_RETRY_DELAY_MS = 2000;
|
|
27
32
|
/**
|
|
@@ -56,12 +61,22 @@ export async function generateSessionTitle(options) {
|
|
|
56
61
|
// Titles are cosmetic background work: honor the caller's retry policy so a
|
|
57
62
|
// single transient provider error (e.g. a 529 overloaded stream) does not
|
|
58
63
|
// surface as a scary runtime error. Mirrors completeSummarization().
|
|
59
|
-
const
|
|
64
|
+
const reasoning = initialTitleReasoning(options.model);
|
|
65
|
+
let response = await requestTitle(options, reasoning);
|
|
66
|
+
const retryReasoning = response.stopReason === "error"
|
|
67
|
+
? mandatoryReasoningRetryLevel(options.model, reasoning, response.errorMessage)
|
|
68
|
+
: undefined;
|
|
69
|
+
if (retryReasoning !== undefined) {
|
|
70
|
+
response = await requestTitle(options, retryReasoning);
|
|
71
|
+
}
|
|
60
72
|
if (response.stopReason === "error") {
|
|
61
73
|
throw new Error(humanizeProviderError(response.errorMessage ?? "Session title generation failed"));
|
|
62
74
|
}
|
|
63
75
|
return parseSessionTitle(response);
|
|
64
76
|
}
|
|
77
|
+
function requestTitle(options, reasoning) {
|
|
78
|
+
return retryAssistantCall(() => completeTitle(options.model, buildTitleContext(options.firstPrompt), buildTitleOptions(options, reasoning), options.streamFn), options.retry, options.signal);
|
|
79
|
+
}
|
|
65
80
|
function buildTitleContext(firstPrompt) {
|
|
66
81
|
return {
|
|
67
82
|
systemPrompt: TITLE_SYSTEM_PROMPT,
|
|
@@ -74,13 +89,16 @@ function buildTitleContext(firstPrompt) {
|
|
|
74
89
|
],
|
|
75
90
|
};
|
|
76
91
|
}
|
|
77
|
-
function buildTitleOptions(options) {
|
|
92
|
+
function buildTitleOptions(options, reasoning) {
|
|
78
93
|
const titleOptions = {
|
|
79
94
|
...options.baseOptions,
|
|
80
95
|
sessionId: options.sessionId,
|
|
81
96
|
cacheRetention: options.model.cacheRetention === "none" ? "none" : "short",
|
|
82
|
-
maxTokens:
|
|
97
|
+
maxTokens: reasoning === undefined ? TITLE_MAX_TOKENS : REASONING_TITLE_MAX_TOKENS,
|
|
83
98
|
};
|
|
99
|
+
if (reasoning !== undefined) {
|
|
100
|
+
titleOptions.reasoning = reasoning;
|
|
101
|
+
}
|
|
84
102
|
if (options.auth.apiKey !== undefined) {
|
|
85
103
|
titleOptions.apiKey = options.auth.apiKey;
|
|
86
104
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-title-generator.js","sourceRoot":"","sources":["../../src/core/session-title-generator.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StreamFn } from \"@earendil-works/pi-agent-core\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tContext,\n\tModel,\n\tSimpleStreamOptions,\n\tTextContent,\n} from \"@earendil-works/pi-ai/compat\";\nimport { completeSimple, type RetryPolicy, retryAssistantCall } from \"@earendil-works/pi-ai/compat\";\n\ninterface SessionTitleAuth {\n\treadonly apiKey?: string;\n\treadonly headers?: Record<string, string>;\n\treadonly extraBody?: Record<string, unknown>;\n\treadonly env?: Record<string, string>;\n}\n\ninterface GenerateSessionTitleOptions {\n\treadonly firstPrompt: string;\n\treadonly model: Model<Api>;\n\treadonly auth: SessionTitleAuth;\n\treadonly sessionId: string;\n\treadonly baseOptions?: SimpleStreamOptions;\n\treadonly signal?: AbortSignal;\n\treadonly streamFn?: StreamFn;\n\treadonly retry?: RetryPolicy;\n}\n\nconst TITLE_SYSTEM_PROMPT = `Generate a concise title for this coding-agent session.\n\nRules:\n- Use 3 to 6 words.\n- Prefer concrete nouns and verbs from the user's task.\n- Do not include quotes, punctuation at the end, markdown, or explanations.\n- If the input is only a greeting, acknowledgement, or too vague to title, return <title>none</title>.\n- Respond only as <title>Session Title</title>.`;\n\nconst LOW_SIGNAL_PROMPTS = new Set([\n\t\"hi\",\n\t\"hello\",\n\t\"hey\",\n\t\"yo\",\n\t\"thanks\",\n\t\"thank you\",\n\t\"ok\",\n\t\"okay\",\n\t\"k\",\n\t\"yes\",\n\t\"no\",\n\t\"yep\",\n\t\"nope\",\n]);\n\nconst MAX_TITLE_RETRIES = 1;\nconst MAX_TITLE_RETRY_DELAY_MS = 2000;\n\n/**\n * Narrow the user's retry budget for cosmetic background title generation.\n *\n * A title is not worth the full agent-turn budget: the default policy would\n * keep hitting an already-overloaded provider for ~14s while the user's real\n * turn competes for the same capacity, and a custom `baseDelayMs` could stall\n * it far longer. One retry recovers the common single-blip case; a title that\n * still fails is regenerated at the next turn end anyway. `enabled` stays the\n * user's call, and a smaller configured budget is never inflated.\n */\nexport function sessionTitleRetryPolicy(settings: RetryPolicy): RetryPolicy {\n\treturn {\n\t\tenabled: settings.enabled,\n\t\tmaxRetries: Math.min(settings.maxRetries, MAX_TITLE_RETRIES),\n\t\tbaseDelayMs: Math.min(settings.baseDelayMs, MAX_TITLE_RETRY_DELAY_MS),\n\t};\n}\n\nexport function shouldSkipSessionTitle(text: string): boolean {\n\tconst normalized = text.replace(/\\s+/g, \" \").trim().toLowerCase();\n\tif (normalized.length === 0) return true;\n\tif (normalized.startsWith(\"/\")) return true;\n\treturn LOW_SIGNAL_PROMPTS.has(normalized);\n}\n\nexport async function generateSessionTitle(options: GenerateSessionTitleOptions): Promise<string | undefined> {\n\tif (shouldSkipSessionTitle(options.firstPrompt)) {\n\t\treturn undefined;\n\t}\n\n\t// Titles are cosmetic background work: honor the caller's retry policy so a\n\t// single transient provider error (e.g. a 529 overloaded stream) does not\n\t// surface as a scary runtime error. Mirrors completeSummarization().\n\tconst response = await retryAssistantCall(\n\t\t() =>\n\t\t\tcompleteTitle(\n\t\t\t\toptions.model,\n\t\t\t\tbuildTitleContext(options.firstPrompt),\n\t\t\t\tbuildTitleOptions(options),\n\t\t\t\toptions.streamFn,\n\t\t\t),\n\t\toptions.retry,\n\t\toptions.signal,\n\t);\n\tif (response.stopReason === \"error\") {\n\t\tthrow new Error(humanizeProviderError(response.errorMessage ?? \"Session title generation failed\"));\n\t}\n\treturn parseSessionTitle(response);\n}\n\nfunction buildTitleContext(firstPrompt: string): Context {\n\treturn {\n\t\tsystemPrompt: TITLE_SYSTEM_PROMPT,\n\t\tmessages: [\n\t\t\t{\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: [{ type: \"text\", text: firstPrompt }],\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t},\n\t\t],\n\t};\n}\n\nfunction buildTitleOptions(options: GenerateSessionTitleOptions): SimpleStreamOptions {\n\tconst titleOptions: SimpleStreamOptions = {\n\t\t...options.baseOptions,\n\t\tsessionId: options.sessionId,\n\t\tcacheRetention: options.model.cacheRetention === \"none\" ? \"none\" : \"short\",\n\t\tmaxTokens: 64,\n\t};\n\tif (options.auth.apiKey !== undefined) {\n\t\ttitleOptions.apiKey = options.auth.apiKey;\n\t}\n\tif (options.auth.headers !== undefined) {\n\t\ttitleOptions.headers = options.auth.headers;\n\t}\n\tif (options.auth.extraBody !== undefined) {\n\t\ttitleOptions.extraBody = options.auth.extraBody;\n\t}\n\tif (options.auth.env !== undefined) {\n\t\ttitleOptions.env = options.auth.env;\n\t}\n\tif (options.signal !== undefined) {\n\t\ttitleOptions.signal = options.signal;\n\t}\n\treturn titleOptions;\n}\n\nasync function completeTitle(\n\tmodel: Model<Api>,\n\tcontext: Context,\n\toptions: SimpleStreamOptions,\n\tstreamFn?: StreamFn,\n): Promise<AssistantMessage> {\n\tif (streamFn === undefined) {\n\t\treturn completeSimple(model, context, options);\n\t}\n\tconst stream = await streamFn(model, context, options);\n\treturn stream.result();\n}\n\nfunction parseSessionTitle(message: AssistantMessage): string | undefined {\n\tconst text = message.content\n\t\t.filter((part): part is TextContent => part.type === \"text\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\")\n\t\t.trim();\n\tconst match = text.match(/<title>\\s*([\\s\\S]*?)\\s*<\\/title>/i);\n\tconst rawTitle = match?.[1];\n\tif (rawTitle === undefined) {\n\t\treturn undefined;\n\t}\n\tconst title = sanitizeTitle(rawTitle);\n\tif (!title || title.toLowerCase() === \"none\") {\n\t\treturn undefined;\n\t}\n\treturn title;\n}\n\n/**\n * Reduce a raw provider error payload (e.g. an Anthropic `{\"type\":\"error\",...}`\n * body, optionally prefixed with an HTTP status code) to a short human-readable\n * line while keeping the error type and request id for follow-up. Non-JSON and\n * unrecognized payloads pass through unchanged.\n */\nexport function humanizeProviderError(raw: string): string {\n\t// `formatProviderError` (packages/ai/src/utils/error-body.ts) emits\n\t// `<status>: <body>` or `<prefix> (<status>): <body>`; Anthropic SSE error\n\t// events arrive as the bare JSON body.\n\tconst statusPrefix = raw.trim().match(/^(?:.*?\\((\\d{3})\\)|(\\d{3}))\\s*:\\s*(?=\\{)/);\n\tconst status = statusPrefix?.[1] ?? statusPrefix?.[2];\n\tconst bodyText = statusPrefix ? raw.trim().slice(statusPrefix[0].length) : raw.trim();\n\tif (!bodyText.startsWith(\"{\")) {\n\t\treturn raw;\n\t}\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(bodyText);\n\t} catch {\n\t\treturn raw;\n\t}\n\tconst body = asRecord(parsed);\n\tif (!body) {\n\t\treturn raw;\n\t}\n\tconst nested = asRecord(body.error);\n\tconst message = asNonEmptyString(nested?.message) ?? asNonEmptyString(body.message) ?? asNonEmptyString(body.error);\n\tif (message === undefined) {\n\t\treturn raw;\n\t}\n\tconst details: string[] = [];\n\tconst errorType = asNonEmptyString(nested?.type) ?? asNonEmptyString(body.type);\n\tif (errorType && errorType !== \"error\") {\n\t\tdetails.push(errorType);\n\t}\n\tif (status) {\n\t\tdetails.push(`HTTP ${status}`);\n\t}\n\tconst requestId = asNonEmptyString(body.request_id);\n\tif (requestId) {\n\t\tdetails.push(`request ${requestId}`);\n\t}\n\treturn details.length > 0 ? `${message} (${details.join(\", \")})` : message;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value)\n\t\t? (value as Record<string, unknown>)\n\t\t: undefined;\n}\n\nfunction asNonEmptyString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction sanitizeTitle(text: string): string {\n\treturn text\n\t\t.replace(/\\u001b\\][^\\u0007]*(?:\\u0007|\\u001b\\\\)/g, \"\")\n\t\t.replace(/\\u001b\\[[0-?]*[ -/]*[@-~]/g, \"\")\n\t\t.replace(/\\u001b[\\u0020-\\u002f]*[\\u0030-\\u007e]/g, \"\")\n\t\t.replace(/[\\r\\n]+/g, \" \")\n\t\t.replace(/[\\u0000-\\u001f\\u007f-\\u009f]+/g, \" \")\n\t\t.replace(/\\s+/g, \" \")\n\t\t.replace(/^[\"'`]+|[\"'`.!?]+$/g, \"\")\n\t\t.trim()\n\t\t.slice(0, 80)\n\t\t.trim();\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session-title-generator.js","sourceRoot":"","sources":["../../src/core/session-title-generator.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StreamFn } from \"@earendil-works/pi-agent-core\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tContext,\n\tModel,\n\tSimpleStreamOptions,\n\tTextContent,\n\tThinkingLevel,\n} from \"@earendil-works/pi-ai/compat\";\nimport { completeSimple, type RetryPolicy, retryAssistantCall } from \"@earendil-works/pi-ai/compat\";\nimport { initialTitleReasoning, mandatoryReasoningRetryLevel } from \"./session-title-reasoning.ts\";\n\ninterface SessionTitleAuth {\n\treadonly apiKey?: string;\n\treadonly headers?: Record<string, string>;\n\treadonly extraBody?: Record<string, unknown>;\n\treadonly env?: Record<string, string>;\n}\n\ninterface GenerateSessionTitleOptions {\n\treadonly firstPrompt: string;\n\treadonly model: Model<Api>;\n\treadonly auth: SessionTitleAuth;\n\treadonly sessionId: string;\n\treadonly baseOptions?: SimpleStreamOptions;\n\treadonly signal?: AbortSignal;\n\treadonly streamFn?: StreamFn;\n\treadonly retry?: RetryPolicy;\n}\n\nconst TITLE_SYSTEM_PROMPT = `Generate a concise title for this coding-agent session.\n\nRules:\n- Use 3 to 6 words.\n- Prefer concrete nouns and verbs from the user's task.\n- Do not include quotes, punctuation at the end, markdown, or explanations.\n- If the input is only a greeting, acknowledgement, or too vague to title, return <title>none</title>.\n- Respond only as <title>Session Title</title>.`;\n\nconst LOW_SIGNAL_PROMPTS = new Set([\n\t\"hi\",\n\t\"hello\",\n\t\"hey\",\n\t\"yo\",\n\t\"thanks\",\n\t\"thank you\",\n\t\"ok\",\n\t\"okay\",\n\t\"k\",\n\t\"yes\",\n\t\"no\",\n\t\"yep\",\n\t\"nope\",\n]);\n\nconst TITLE_MAX_TOKENS = 64;\n// Reasoning tokens count against the completion budget, so a title that must reason needs room\n// for the thinking and the `<title>` answer.\nconst REASONING_TITLE_MAX_TOKENS = 1024;\n\nconst MAX_TITLE_RETRIES = 1;\nconst MAX_TITLE_RETRY_DELAY_MS = 2000;\n\n/**\n * Narrow the user's retry budget for cosmetic background title generation.\n *\n * A title is not worth the full agent-turn budget: the default policy would\n * keep hitting an already-overloaded provider for ~14s while the user's real\n * turn competes for the same capacity, and a custom `baseDelayMs` could stall\n * it far longer. One retry recovers the common single-blip case; a title that\n * still fails is regenerated at the next turn end anyway. `enabled` stays the\n * user's call, and a smaller configured budget is never inflated.\n */\nexport function sessionTitleRetryPolicy(settings: RetryPolicy): RetryPolicy {\n\treturn {\n\t\tenabled: settings.enabled,\n\t\tmaxRetries: Math.min(settings.maxRetries, MAX_TITLE_RETRIES),\n\t\tbaseDelayMs: Math.min(settings.baseDelayMs, MAX_TITLE_RETRY_DELAY_MS),\n\t};\n}\n\nexport function shouldSkipSessionTitle(text: string): boolean {\n\tconst normalized = text.replace(/\\s+/g, \" \").trim().toLowerCase();\n\tif (normalized.length === 0) return true;\n\tif (normalized.startsWith(\"/\")) return true;\n\treturn LOW_SIGNAL_PROMPTS.has(normalized);\n}\n\nexport async function generateSessionTitle(options: GenerateSessionTitleOptions): Promise<string | undefined> {\n\tif (shouldSkipSessionTitle(options.firstPrompt)) {\n\t\treturn undefined;\n\t}\n\n\t// Titles are cosmetic background work: honor the caller's retry policy so a\n\t// single transient provider error (e.g. a 529 overloaded stream) does not\n\t// surface as a scary runtime error. Mirrors completeSummarization().\n\tconst reasoning = initialTitleReasoning(options.model);\n\tlet response = await requestTitle(options, reasoning);\n\tconst retryReasoning =\n\t\tresponse.stopReason === \"error\"\n\t\t\t? mandatoryReasoningRetryLevel(options.model, reasoning, response.errorMessage)\n\t\t\t: undefined;\n\tif (retryReasoning !== undefined) {\n\t\tresponse = await requestTitle(options, retryReasoning);\n\t}\n\tif (response.stopReason === \"error\") {\n\t\tthrow new Error(humanizeProviderError(response.errorMessage ?? \"Session title generation failed\"));\n\t}\n\treturn parseSessionTitle(response);\n}\n\nfunction requestTitle(\n\toptions: GenerateSessionTitleOptions,\n\treasoning: ThinkingLevel | undefined,\n): Promise<AssistantMessage> {\n\treturn retryAssistantCall(\n\t\t() =>\n\t\t\tcompleteTitle(\n\t\t\t\toptions.model,\n\t\t\t\tbuildTitleContext(options.firstPrompt),\n\t\t\t\tbuildTitleOptions(options, reasoning),\n\t\t\t\toptions.streamFn,\n\t\t\t),\n\t\toptions.retry,\n\t\toptions.signal,\n\t);\n}\n\nfunction buildTitleContext(firstPrompt: string): Context {\n\treturn {\n\t\tsystemPrompt: TITLE_SYSTEM_PROMPT,\n\t\tmessages: [\n\t\t\t{\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: [{ type: \"text\", text: firstPrompt }],\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t},\n\t\t],\n\t};\n}\n\nfunction buildTitleOptions(\n\toptions: GenerateSessionTitleOptions,\n\treasoning: ThinkingLevel | undefined,\n): SimpleStreamOptions {\n\tconst titleOptions: SimpleStreamOptions = {\n\t\t...options.baseOptions,\n\t\tsessionId: options.sessionId,\n\t\tcacheRetention: options.model.cacheRetention === \"none\" ? \"none\" : \"short\",\n\t\tmaxTokens: reasoning === undefined ? TITLE_MAX_TOKENS : REASONING_TITLE_MAX_TOKENS,\n\t};\n\tif (reasoning !== undefined) {\n\t\ttitleOptions.reasoning = reasoning;\n\t}\n\tif (options.auth.apiKey !== undefined) {\n\t\ttitleOptions.apiKey = options.auth.apiKey;\n\t}\n\tif (options.auth.headers !== undefined) {\n\t\ttitleOptions.headers = options.auth.headers;\n\t}\n\tif (options.auth.extraBody !== undefined) {\n\t\ttitleOptions.extraBody = options.auth.extraBody;\n\t}\n\tif (options.auth.env !== undefined) {\n\t\ttitleOptions.env = options.auth.env;\n\t}\n\tif (options.signal !== undefined) {\n\t\ttitleOptions.signal = options.signal;\n\t}\n\treturn titleOptions;\n}\n\nasync function completeTitle(\n\tmodel: Model<Api>,\n\tcontext: Context,\n\toptions: SimpleStreamOptions,\n\tstreamFn?: StreamFn,\n): Promise<AssistantMessage> {\n\tif (streamFn === undefined) {\n\t\treturn completeSimple(model, context, options);\n\t}\n\tconst stream = await streamFn(model, context, options);\n\treturn stream.result();\n}\n\nfunction parseSessionTitle(message: AssistantMessage): string | undefined {\n\tconst text = message.content\n\t\t.filter((part): part is TextContent => part.type === \"text\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\")\n\t\t.trim();\n\tconst match = text.match(/<title>\\s*([\\s\\S]*?)\\s*<\\/title>/i);\n\tconst rawTitle = match?.[1];\n\tif (rawTitle === undefined) {\n\t\treturn undefined;\n\t}\n\tconst title = sanitizeTitle(rawTitle);\n\tif (!title || title.toLowerCase() === \"none\") {\n\t\treturn undefined;\n\t}\n\treturn title;\n}\n\n/**\n * Reduce a raw provider error payload (e.g. an Anthropic `{\"type\":\"error\",...}`\n * body, optionally prefixed with an HTTP status code) to a short human-readable\n * line while keeping the error type and request id for follow-up. Non-JSON and\n * unrecognized payloads pass through unchanged.\n */\nexport function humanizeProviderError(raw: string): string {\n\t// `formatProviderError` (packages/ai/src/utils/error-body.ts) emits\n\t// `<status>: <body>` or `<prefix> (<status>): <body>`; Anthropic SSE error\n\t// events arrive as the bare JSON body.\n\tconst statusPrefix = raw.trim().match(/^(?:.*?\\((\\d{3})\\)|(\\d{3}))\\s*:\\s*(?=\\{)/);\n\tconst status = statusPrefix?.[1] ?? statusPrefix?.[2];\n\tconst bodyText = statusPrefix ? raw.trim().slice(statusPrefix[0].length) : raw.trim();\n\tif (!bodyText.startsWith(\"{\")) {\n\t\treturn raw;\n\t}\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(bodyText);\n\t} catch {\n\t\treturn raw;\n\t}\n\tconst body = asRecord(parsed);\n\tif (!body) {\n\t\treturn raw;\n\t}\n\tconst nested = asRecord(body.error);\n\tconst message = asNonEmptyString(nested?.message) ?? asNonEmptyString(body.message) ?? asNonEmptyString(body.error);\n\tif (message === undefined) {\n\t\treturn raw;\n\t}\n\tconst details: string[] = [];\n\tconst errorType = asNonEmptyString(nested?.type) ?? asNonEmptyString(body.type);\n\tif (errorType && errorType !== \"error\") {\n\t\tdetails.push(errorType);\n\t}\n\tif (status) {\n\t\tdetails.push(`HTTP ${status}`);\n\t}\n\tconst requestId = asNonEmptyString(body.request_id);\n\tif (requestId) {\n\t\tdetails.push(`request ${requestId}`);\n\t}\n\treturn details.length > 0 ? `${message} (${details.join(\", \")})` : message;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value)\n\t\t? (value as Record<string, unknown>)\n\t\t: undefined;\n}\n\nfunction asNonEmptyString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction sanitizeTitle(text: string): string {\n\treturn text\n\t\t.replace(/\\u001b\\][^\\u0007]*(?:\\u0007|\\u001b\\\\)/g, \"\")\n\t\t.replace(/\\u001b\\[[0-?]*[ -/]*[@-~]/g, \"\")\n\t\t.replace(/\\u001b[\\u0020-\\u002f]*[\\u0030-\\u007e]/g, \"\")\n\t\t.replace(/[\\r\\n]+/g, \" \")\n\t\t.replace(/[\\u0000-\\u001f\\u007f-\\u009f]+/g, \" \")\n\t\t.replace(/\\s+/g, \" \")\n\t\t.replace(/^[\"'`]+|[\"'`.!?]+$/g, \"\")\n\t\t.trim()\n\t\t.slice(0, 80)\n\t\t.trim();\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Api, Model, ThinkingLevel } from "@earendil-works/pi-ai/compat";
|
|
2
|
+
/**
|
|
3
|
+
* Reasoning level for the first title request. Titles stay reasoning-free unless the catalog
|
|
4
|
+
* says the model cannot turn reasoning off; then the lowest supported level is requested, because
|
|
5
|
+
* an unset level maps to the provider's "disabled" value, which those endpoints reject with 400.
|
|
6
|
+
*/
|
|
7
|
+
export declare function initialTitleReasoning(model: Model<Api>): ThinkingLevel | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Reasoning level for one retry after a reasoning-free title request was rejected because the
|
|
10
|
+
* endpoint mandates reasoning, which means its catalog entry is stale. `low` is the effort every
|
|
11
|
+
* such endpoint seen so far accepts, clamped to what the catalog still allows.
|
|
12
|
+
*/
|
|
13
|
+
export declare function mandatoryReasoningRetryLevel(model: Model<Api>, sentReasoning: ThinkingLevel | undefined, errorMessage: string | undefined): ThinkingLevel | undefined;
|
|
14
|
+
//# sourceMappingURL=session-title-reasoning.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-title-reasoning.d.ts","sourceRoot":"","sources":["../../src/core/session-title-reasoning.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAsB,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAKlG;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,aAAa,GAAG,SAAS,CAKlF;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC3C,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,aAAa,EAAE,aAAa,GAAG,SAAS,EACxC,YAAY,EAAE,MAAM,GAAG,SAAS,GAC9B,aAAa,GAAG,SAAS,CAI3B"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { clampThinkingLevel, getSupportedThinkingLevels } from "@earendil-works/pi-ai/compat";
|
|
2
|
+
const REASONING_MANDATORY_ERROR = /reasoning is mandatory/i;
|
|
3
|
+
/**
|
|
4
|
+
* Reasoning level for the first title request. Titles stay reasoning-free unless the catalog
|
|
5
|
+
* says the model cannot turn reasoning off; then the lowest supported level is requested, because
|
|
6
|
+
* an unset level maps to the provider's "disabled" value, which those endpoints reject with 400.
|
|
7
|
+
*/
|
|
8
|
+
export function initialTitleReasoning(model) {
|
|
9
|
+
if (!model.reasoning)
|
|
10
|
+
return undefined;
|
|
11
|
+
const levels = getSupportedThinkingLevels(model);
|
|
12
|
+
if (levels.includes("off"))
|
|
13
|
+
return undefined;
|
|
14
|
+
return asReasoningLevel(levels[0]);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Reasoning level for one retry after a reasoning-free title request was rejected because the
|
|
18
|
+
* endpoint mandates reasoning, which means its catalog entry is stale. `low` is the effort every
|
|
19
|
+
* such endpoint seen so far accepts, clamped to what the catalog still allows.
|
|
20
|
+
*/
|
|
21
|
+
export function mandatoryReasoningRetryLevel(model, sentReasoning, errorMessage) {
|
|
22
|
+
if (sentReasoning !== undefined || errorMessage === undefined)
|
|
23
|
+
return undefined;
|
|
24
|
+
if (!REASONING_MANDATORY_ERROR.test(errorMessage))
|
|
25
|
+
return undefined;
|
|
26
|
+
return asReasoningLevel(clampThinkingLevel(model, "low"));
|
|
27
|
+
}
|
|
28
|
+
function asReasoningLevel(level) {
|
|
29
|
+
return level === undefined || level === "off" ? undefined : level;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=session-title-reasoning.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-title-reasoning.js","sourceRoot":"","sources":["../../src/core/session-title-reasoning.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAE9F,MAAM,yBAAyB,GAAG,yBAAyB,CAAC;AAE5D;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAiB;IACtD,IAAI,CAAC,KAAK,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IACvC,MAAM,MAAM,GAAG,0BAA0B,CAAC,KAAK,CAAC,CAAC;IACjD,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC7C,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,4BAA4B,CAC3C,KAAiB,EACjB,aAAwC,EACxC,YAAgC;IAEhC,IAAI,aAAa,KAAK,SAAS,IAAI,YAAY,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAChF,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC,YAAY,CAAC;QAAE,OAAO,SAAS,CAAC;IACpE,OAAO,gBAAgB,CAAC,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAqC;IAC9D,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;AACnE,CAAC","sourcesContent":["import type { Api, Model, ModelThinkingLevel, ThinkingLevel } from \"@earendil-works/pi-ai/compat\";\nimport { clampThinkingLevel, getSupportedThinkingLevels } from \"@earendil-works/pi-ai/compat\";\n\nconst REASONING_MANDATORY_ERROR = /reasoning is mandatory/i;\n\n/**\n * Reasoning level for the first title request. Titles stay reasoning-free unless the catalog\n * says the model cannot turn reasoning off; then the lowest supported level is requested, because\n * an unset level maps to the provider's \"disabled\" value, which those endpoints reject with 400.\n */\nexport function initialTitleReasoning(model: Model<Api>): ThinkingLevel | undefined {\n\tif (!model.reasoning) return undefined;\n\tconst levels = getSupportedThinkingLevels(model);\n\tif (levels.includes(\"off\")) return undefined;\n\treturn asReasoningLevel(levels[0]);\n}\n\n/**\n * Reasoning level for one retry after a reasoning-free title request was rejected because the\n * endpoint mandates reasoning, which means its catalog entry is stale. `low` is the effort every\n * such endpoint seen so far accepts, clamped to what the catalog still allows.\n */\nexport function mandatoryReasoningRetryLevel(\n\tmodel: Model<Api>,\n\tsentReasoning: ThinkingLevel | undefined,\n\terrorMessage: string | undefined,\n): ThinkingLevel | undefined {\n\tif (sentReasoning !== undefined || errorMessage === undefined) return undefined;\n\tif (!REASONING_MANDATORY_ERROR.test(errorMessage)) return undefined;\n\treturn asReasoningLevel(clampThinkingLevel(model, \"low\"));\n}\n\nfunction asReasoningLevel(level: ModelThinkingLevel | undefined): ThinkingLevel | undefined {\n\treturn level === undefined || level === \"off\" ? undefined : level;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../../../src/core/tools/path-utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../../../src/core/tools/path-utils.ts"],"names":[],"mappings":"AAoCA,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOnE;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAElE;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAqCrE;AAED,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAqCzF"}
|