@ai-agent-forge/agent-forge 0.86.7 → 0.88.0
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 +45 -0
- package/README.md +10 -11
- package/dist/artifacts/artifact-store.d.ts.map +1 -1
- package/dist/artifacts/artifact-store.js +9 -5
- package/dist/artifacts/artifact-store.js.map +1 -1
- package/dist/bundle/.build-manifest.json +430 -413
- package/dist/bundle/chunks/{chunk-A7GUOX2H.js → chunk-6ZKDNZHA.js} +5 -1
- package/dist/bundle/chunks/chunk-HLJEVRWP.js +13 -0
- package/dist/bundle/chunks/chunk-YGHQME4V.js +1456 -0
- package/dist/bundle/chunks/image-resize-worker.js +12 -1
- package/dist/bundle/chunks/{virtual-modules-3IZ4MYSQ.js → virtual-modules-KZ5DRH7Q.js} +1 -1
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/client.js +1 -1
- package/dist/bundle/index.js +2 -2
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +231 -185
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/auth-check.d.ts.map +1 -1
- package/dist/cli/auth-check.js +17 -4
- package/dist/cli/auth-check.js.map +1 -1
- package/dist/cli/auth-command.d.ts.map +1 -1
- package/dist/cli/auth-command.js +15 -9
- package/dist/cli/auth-command.js.map +1 -1
- package/dist/cli/credential-print.d.ts.map +1 -1
- package/dist/cli/credential-print.js +14 -7
- package/dist/cli/credential-print.js.map +1 -1
- package/dist/cli/experimental/command.d.ts.map +1 -1
- package/dist/cli/experimental/command.js +6 -5
- package/dist/cli/experimental/command.js.map +1 -1
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js +9 -8
- package/dist/cli/list-models.js.map +1 -1
- package/dist/cli/plugin-manager-controller.d.ts.map +1 -1
- package/dist/cli/plugin-manager-controller.js +2 -1
- package/dist/cli/plugin-manager-controller.js.map +1 -1
- package/dist/cli/serve.d.ts.map +1 -1
- package/dist/cli/serve.js +8 -5
- package/dist/cli/serve.js.map +1 -1
- package/dist/client/remote-session.d.ts.map +1 -1
- package/dist/client/remote-session.js +16 -9
- package/dist/client/remote-session.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -3
- package/dist/config.js.map +1 -1
- package/dist/core/agent-capability-contract.d.ts +0 -7
- package/dist/core/agent-capability-contract.d.ts.map +1 -1
- package/dist/core/agent-capability-contract.js +72 -68
- package/dist/core/agent-capability-contract.js.map +1 -1
- package/dist/core/agent-session-bash.d.ts.map +1 -1
- package/dist/core/agent-session-bash.js +5 -4
- package/dist/core/agent-session-bash.js.map +1 -1
- package/dist/core/agent-session-compaction.d.ts +6 -10
- package/dist/core/agent-session-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-compaction.js +35 -133
- package/dist/core/agent-session-compaction.js.map +1 -1
- package/dist/core/agent-session-context.d.ts.map +1 -1
- package/dist/core/agent-session-context.js +18 -13
- package/dist/core/agent-session-context.js.map +1 -1
- package/dist/core/agent-session-internal.d.ts +0 -6
- package/dist/core/agent-session-internal.d.ts.map +1 -1
- package/dist/core/agent-session-internal.js.map +1 -1
- package/dist/core/agent-session-model.d.ts.map +1 -1
- package/dist/core/agent-session-model.js +2 -1
- package/dist/core/agent-session-model.js.map +1 -1
- package/dist/core/agent-session-retry.d.ts.map +1 -1
- package/dist/core/agent-session-retry.js +3 -3
- package/dist/core/agent-session-retry.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +7 -6
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +1 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session-tools.d.ts.map +1 -1
- package/dist/core/agent-session-tools.js +3 -2
- package/dist/core/agent-session-tools.js.map +1 -1
- package/dist/core/agent-session.d.ts +40 -22
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +147 -67
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-guidance.d.ts.map +1 -1
- package/dist/core/auth-guidance.js +11 -5
- package/dist/core/auth-guidance.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +9 -6
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +2 -1
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/embedded-sdk.d.ts.map +1 -1
- package/dist/core/embedded-sdk.js +10 -6
- package/dist/core/embedded-sdk.js.map +1 -1
- package/dist/core/export-html/index.d.ts.map +1 -1
- package/dist/core/export-html/index.js +9 -4
- package/dist/core/export-html/index.js.map +1 -1
- package/dist/core/extensions/agent-composition.d.ts.map +1 -1
- package/dist/core/extensions/agent-composition.js +26 -19
- package/dist/core/extensions/agent-composition.js.map +1 -1
- package/dist/core/extensions/bootstrap-agent-loop.d.ts.map +1 -1
- package/dist/core/extensions/bootstrap-agent-loop.js +3 -2
- package/dist/core/extensions/bootstrap-agent-loop.js.map +1 -1
- package/dist/core/extensions/bootstrap-compaction-policy.d.ts.map +1 -1
- package/dist/core/extensions/bootstrap-compaction-policy.js +0 -34
- package/dist/core/extensions/bootstrap-compaction-policy.js.map +1 -1
- package/dist/core/extensions/bootstrap-lifecycle.d.ts.map +1 -1
- package/dist/core/extensions/bootstrap-lifecycle.js +20 -17
- package/dist/core/extensions/bootstrap-lifecycle.js.map +1 -1
- package/dist/core/extensions/bootstrap-overflow-policy.d.ts.map +1 -1
- package/dist/core/extensions/bootstrap-overflow-policy.js +2 -1
- package/dist/core/extensions/bootstrap-overflow-policy.js.map +1 -1
- package/dist/core/extensions/bootstrap-retry-policy.d.ts.map +1 -1
- package/dist/core/extensions/bootstrap-retry-policy.js +4 -3
- package/dist/core/extensions/bootstrap-retry-policy.js.map +1 -1
- package/dist/core/extensions/bootstrap-skills.d.ts.map +1 -1
- package/dist/core/extensions/bootstrap-skills.js +7 -6
- package/dist/core/extensions/bootstrap-skills.js.map +1 -1
- package/dist/core/extensions/bootstrap-tools.d.ts.map +1 -1
- package/dist/core/extensions/bootstrap-tools.js +2 -1
- package/dist/core/extensions/bootstrap-tools.js.map +1 -1
- package/dist/core/extensions/capability-plugin.d.ts.map +1 -1
- package/dist/core/extensions/capability-plugin.js +37 -13
- package/dist/core/extensions/capability-plugin.js.map +1 -1
- package/dist/core/extensions/capability-runtime-events.d.ts.map +1 -1
- package/dist/core/extensions/capability-runtime-events.js +17 -13
- package/dist/core/extensions/capability-runtime-events.js.map +1 -1
- package/dist/core/extensions/capability-runtime-guards.d.ts.map +1 -1
- package/dist/core/extensions/capability-runtime-guards.js +16 -15
- package/dist/core/extensions/capability-runtime-guards.js.map +1 -1
- package/dist/core/extensions/capability-runtime-manifest.d.ts.map +1 -1
- package/dist/core/extensions/capability-runtime-manifest.js +52 -30
- package/dist/core/extensions/capability-runtime-manifest.js.map +1 -1
- package/dist/core/extensions/capability-runtime-validation.d.ts.map +1 -1
- package/dist/core/extensions/capability-runtime-validation.js +66 -65
- package/dist/core/extensions/capability-runtime-validation.js.map +1 -1
- package/dist/core/extensions/capability-runtime.d.ts.map +1 -1
- package/dist/core/extensions/capability-runtime.js +147 -127
- package/dist/core/extensions/capability-runtime.js.map +1 -1
- package/dist/core/extensions/compaction-memory-failure-mode.d.ts.map +1 -1
- package/dist/core/extensions/compaction-memory-failure-mode.js +3 -2
- package/dist/core/extensions/compaction-memory-failure-mode.js.map +1 -1
- package/dist/core/extensions/context-strategy-binding.d.ts.map +1 -1
- package/dist/core/extensions/context-strategy-binding.js +7 -4
- package/dist/core/extensions/context-strategy-binding.js.map +1 -1
- package/dist/core/extensions/lifecycle-catalog.d.ts.map +1 -1
- package/dist/core/extensions/lifecycle-catalog.js +290 -279
- package/dist/core/extensions/lifecycle-catalog.js.map +1 -1
- package/dist/core/extensions/lifecycle-delivery-queue.d.ts.map +1 -1
- package/dist/core/extensions/lifecycle-delivery-queue.js +3 -2
- package/dist/core/extensions/lifecycle-delivery-queue.js.map +1 -1
- package/dist/core/extensions/provider-failover.d.ts +7 -3
- package/dist/core/extensions/provider-failover.d.ts.map +1 -1
- package/dist/core/extensions/provider-failover.js +9 -8
- package/dist/core/extensions/provider-failover.js.map +1 -1
- package/dist/core/facade/coding-agent-facade.d.ts +6 -6
- package/dist/core/facade/coding-agent-facade.d.ts.map +1 -1
- package/dist/core/facade/coding-agent-facade.js +2 -3
- package/dist/core/facade/coding-agent-facade.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +2 -1
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/i18n/index.d.ts +14 -0
- package/dist/core/i18n/index.d.ts.map +1 -0
- package/dist/core/i18n/index.js +67 -0
- package/dist/core/i18n/index.js.map +1 -0
- package/dist/core/i18n/locales/en/example.d.ts +7 -0
- package/dist/core/i18n/locales/en/example.d.ts.map +1 -0
- package/dist/core/i18n/locales/en/example.js +7 -0
- package/dist/core/i18n/locales/en/example.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/cli.d.ts +8 -0
- package/dist/core/i18n/locales/zh-cn/cli.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/cli.js +229 -0
- package/dist/core/i18n/locales/zh-cn/cli.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/common.d.ts +6 -0
- package/dist/core/i18n/locales/zh-cn/common.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/common.js +6 -0
- package/dist/core/i18n/locales/zh-cn/common.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-core.d.ts +13 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-core.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-core.js +173 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-core.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-extensions.d.ts +12 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-extensions.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-extensions.js +582 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-extensions.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-final.d.ts +9 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-final.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-final.js +55 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-final.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-plugins.d.ts +11 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-plugins.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-plugins.js +455 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-plugins.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-errors.d.ts +9 -0
- package/dist/core/i18n/locales/zh-cn/core-errors.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-errors.js +191 -0
- package/dist/core/i18n/locales/zh-cn/core-errors.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/example.d.ts +7 -0
- package/dist/core/i18n/locales/zh-cn/example.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/example.js +10 -0
- package/dist/core/i18n/locales/zh-cn/example.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/index.d.ts +2 -0
- package/dist/core/i18n/locales/zh-cn/index.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/index.js +41 -0
- package/dist/core/i18n/locales/zh-cn/index.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/interactive-mode.d.ts +10 -0
- package/dist/core/i18n/locales/zh-cn/interactive-mode.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/interactive-mode.js +267 -0
- package/dist/core/i18n/locales/zh-cn/interactive-mode.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/keybindings.d.ts +11 -0
- package/dist/core/i18n/locales/zh-cn/keybindings.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/keybindings.js +67 -0
- package/dist/core/i18n/locales/zh-cn/keybindings.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/slash-commands.d.ts +7 -0
- package/dist/core/i18n/locales/zh-cn/slash-commands.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/slash-commands.js +39 -0
- package/dist/core/i18n/locales/zh-cn/slash-commands.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-builtin.d.ts +10 -0
- package/dist/core/i18n/locales/zh-cn/tui-builtin.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-builtin.js +15 -0
- package/dist/core/i18n/locales/zh-cn/tui-builtin.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-components-misc.d.ts +13 -0
- package/dist/core/i18n/locales/zh-cn/tui-components-misc.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-components-misc.js +102 -0
- package/dist/core/i18n/locales/zh-cn/tui-components-misc.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-components-selectors.d.ts +12 -0
- package/dist/core/i18n/locales/zh-cn/tui-components-selectors.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-components-selectors.js +286 -0
- package/dist/core/i18n/locales/zh-cn/tui-components-selectors.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-interactive-misc.d.ts +9 -0
- package/dist/core/i18n/locales/zh-cn/tui-interactive-misc.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-interactive-misc.js +79 -0
- package/dist/core/i18n/locales/zh-cn/tui-interactive-misc.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-self-owned.d.ts +8 -0
- package/dist/core/i18n/locales/zh-cn/tui-self-owned.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-self-owned.js +72 -0
- package/dist/core/i18n/locales/zh-cn/tui-self-owned.js.map +1 -0
- package/dist/core/keybindings.d.ts +15 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +18 -0
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/logger.d.ts.map +1 -1
- package/dist/core/logger.js +5 -1
- package/dist/core/logger.js.map +1 -1
- package/dist/core/model-config.d.ts +0 -1
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +9 -1
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +2 -1
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +11 -7
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/package-install-host.d.ts.map +1 -1
- package/dist/core/package-install-host.js +4 -3
- package/dist/core/package-install-host.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +15 -10
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/package-resource-collection.d.ts.map +1 -1
- package/dist/core/package-resource-collection.js +2 -1
- package/dist/core/package-resource-collection.js.map +1 -1
- package/dist/core/package-source.d.ts.map +1 -1
- package/dist/core/package-source.js +3 -2
- package/dist/core/package-source.js.map +1 -1
- package/dist/core/provider-composer.d.ts +2 -2
- package/dist/core/provider-composer.d.ts.map +1 -1
- package/dist/core/provider-composer.js +29 -21
- package/dist/core/provider-composer.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +6 -2
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/resolve-config-value.d.ts.map +1 -1
- package/dist/core/resolve-config-value.js +15 -5
- package/dist/core/resolve-config-value.js.map +1 -1
- package/dist/core/sdk.d.ts +27 -5
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +80 -55
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-cwd.d.ts.map +1 -1
- package/dist/core/session-cwd.js +5 -2
- package/dist/core/session-cwd.js.map +1 -1
- package/dist/core/session-export.d.ts.map +1 -1
- package/dist/core/session-export.js +2 -1
- package/dist/core/session-export.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +39 -29
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-storage.d.ts.map +1 -1
- package/dist/core/session-storage.js +3 -3
- package/dist/core/session-storage.js.map +1 -1
- package/dist/core/settings-manager.d.ts +20 -20
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +47 -59
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +53 -32
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +25 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +11 -7
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +9 -4
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +4 -3
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +3 -2
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +2 -1
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +6 -2
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +2 -1
- package/dist/core/tools/write.js.map +1 -1
- package/dist/durable/durable-service.d.ts.map +1 -1
- package/dist/durable/durable-service.js +25 -18
- package/dist/durable/durable-service.js.map +1 -1
- package/dist/host/checkpoint-commit.d.ts.map +1 -1
- package/dist/host/checkpoint-commit.js +2 -1
- package/dist/host/checkpoint-commit.js.map +1 -1
- package/dist/host/manual-compaction.d.ts.map +1 -1
- package/dist/host/manual-compaction.js +2 -1
- package/dist/host/manual-compaction.js.map +1 -1
- package/dist/host/output-projection.d.ts +0 -13
- package/dist/host/output-projection.d.ts.map +1 -1
- package/dist/host/output-projection.js +16 -2
- package/dist/host/output-projection.js.map +1 -1
- package/dist/host/summary-retry.d.ts.map +1 -1
- package/dist/host/summary-retry.js +3 -2
- package/dist/host/summary-retry.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +97 -60
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/agents-view.d.ts.map +1 -1
- package/dist/modes/interactive/components/agents-view.js +18 -15
- package/dist/modes/interactive/components/agents-view.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +6 -5
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +12 -7
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.js +2 -1
- package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +6 -5
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +9 -5
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +18 -15
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-entry.js +2 -1
- package/dist/modes/interactive/components/custom-entry.js.map +1 -1
- package/dist/modes/interactive/components/dialog-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/dialog-editor.js +5 -4
- package/dist/modes/interactive/components/dialog-editor.js.map +1 -1
- package/dist/modes/interactive/components/dialog-input.d.ts.map +1 -1
- package/dist/modes/interactive/components/dialog-input.js +2 -1
- package/dist/modes/interactive/components/dialog-input.js.map +1 -1
- package/dist/modes/interactive/components/dialog-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/dialog-selector.js +4 -3
- package/dist/modes/interactive/components/dialog-selector.js.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.js +11 -10
- package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +0 -2
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +8 -10
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js +3 -2
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +12 -11
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/mermaid.d.ts.map +1 -1
- package/dist/modes/interactive/components/mermaid.js +6 -2
- package/dist/modes/interactive/components/mermaid.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +28 -16
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +10 -9
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/plugin-manager-entry.d.ts.map +1 -1
- package/dist/modes/interactive/components/plugin-manager-entry.js +22 -13
- package/dist/modes/interactive/components/plugin-manager-entry.js.map +1 -1
- package/dist/modes/interactive/components/plugin-selection-entry.d.ts.map +1 -1
- package/dist/modes/interactive/components/plugin-selection-entry.js +27 -12
- package/dist/modes/interactive/components/plugin-selection-entry.js.map +1 -1
- package/dist/modes/interactive/components/queue-manager-selector.d.ts +60 -0
- package/dist/modes/interactive/components/queue-manager-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/queue-manager-selector.js +166 -0
- package/dist/modes/interactive/components/queue-manager-selector.js.map +1 -0
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +16 -14
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +43 -31
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +4 -6
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +115 -120
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-submenu.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-submenu.js +5 -4
- package/dist/modes/interactive/components/settings-submenu.js.map +1 -1
- package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/show-images-selector.js +3 -2
- package/dist/modes/interactive/components/show-images-selector.js.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +4 -3
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
- package/dist/modes/interactive/components/status-indicator.js +14 -5
- package/dist/modes/interactive/components/status-indicator.js.map +1 -1
- package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/theme-selector.js +2 -1
- package/dist/modes/interactive/components/theme-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +11 -4
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +8 -3
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-list.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-list.js +7 -6
- package/dist/modes/interactive/components/tree-list.js.map +1 -1
- package/dist/modes/interactive/components/tree-render.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-render.js +28 -23
- package/dist/modes/interactive/components/tree-render.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +9 -7
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.js +5 -4
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
- package/dist/modes/interactive/components/workflow-view.d.ts.map +1 -1
- package/dist/modes/interactive/components/workflow-view.js +36 -21
- package/dist/modes/interactive/components/workflow-view.js.map +1 -1
- package/dist/modes/interactive/external-editor.d.ts.map +1 -1
- package/dist/modes/interactive/external-editor.js +2 -1
- package/dist/modes/interactive/external-editor.js.map +1 -1
- package/dist/modes/interactive/interactive-auth.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-auth.js +71 -40
- package/dist/modes/interactive/interactive-auth.js.map +1 -1
- package/dist/modes/interactive/interactive-commands.d.ts +1 -0
- package/dist/modes/interactive/interactive-commands.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-commands.js +3 -0
- package/dist/modes/interactive/interactive-commands.js.map +1 -1
- package/dist/modes/interactive/interactive-dialog-ui.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-dialog-ui.js +5 -5
- package/dist/modes/interactive/interactive-dialog-ui.js.map +1 -1
- package/dist/modes/interactive/interactive-format.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-format.js +34 -18
- package/dist/modes/interactive/interactive-format.js.map +1 -1
- package/dist/modes/interactive/interactive-helpers.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-helpers.js +7 -2
- package/dist/modes/interactive/interactive-helpers.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +4 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +350 -279
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/interactive-selectors.d.ts +9 -0
- package/dist/modes/interactive/interactive-selectors.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-selectors.js +79 -25
- package/dist/modes/interactive/interactive-selectors.js.map +1 -1
- package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-controller.js +5 -1
- package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +26 -23
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/json-event.d.ts.map +1 -1
- package/dist/modes/json-event.js +3 -2
- package/dist/modes/json-event.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +3 -2
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +0 -8
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +3 -14
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +0 -9
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +0 -19
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +28 -13
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/plugins/ecosystem/catalog.d.ts.map +1 -1
- package/dist/plugins/ecosystem/catalog.js +3 -2
- package/dist/plugins/ecosystem/catalog.js.map +1 -1
- package/dist/plugins/ecosystem/cli.d.ts.map +1 -1
- package/dist/plugins/ecosystem/cli.js +2 -1
- package/dist/plugins/ecosystem/cli.js.map +1 -1
- package/dist/plugins/ecosystem/default-plugin-market.d.ts.map +1 -1
- package/dist/plugins/ecosystem/default-plugin-market.js +3 -2
- package/dist/plugins/ecosystem/default-plugin-market.js.map +1 -1
- package/dist/plugins/ecosystem/distribution.d.ts.map +1 -1
- package/dist/plugins/ecosystem/distribution.js +2 -1
- package/dist/plugins/ecosystem/distribution.js.map +1 -1
- package/dist/plugins/ecosystem/installer.d.ts.map +1 -1
- package/dist/plugins/ecosystem/installer.js +3 -2
- package/dist/plugins/ecosystem/installer.js.map +1 -1
- package/dist/plugins/ecosystem/node-disk-registry-source.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-disk-registry-source.js +20 -17
- package/dist/plugins/ecosystem/node-disk-registry-source.js.map +1 -1
- package/dist/plugins/ecosystem/node-distribution-host.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-distribution-host.js +35 -34
- package/dist/plugins/ecosystem/node-distribution-host.js.map +1 -1
- package/dist/plugins/ecosystem/node-npm-registry-source.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-npm-registry-source.js +35 -34
- package/dist/plugins/ecosystem/node-npm-registry-source.js.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-host.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-host.js +9 -8
- package/dist/plugins/ecosystem/node-package-install-host.js.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-persistence.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-persistence.js +4 -3
- package/dist/plugins/ecosystem/node-package-install-persistence.js.map +1 -1
- package/dist/plugins/ecosystem/node-plugin-bootstrap.d.ts +9 -2
- package/dist/plugins/ecosystem/node-plugin-bootstrap.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-plugin-bootstrap.js +44 -5
- package/dist/plugins/ecosystem/node-plugin-bootstrap.js.map +1 -1
- package/dist/plugins/ecosystem/node-plugin-trust-root.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-plugin-trust-root.js +10 -9
- package/dist/plugins/ecosystem/node-plugin-trust-root.js.map +1 -1
- package/dist/plugins/ecosystem/node-registry-source.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-registry-source.js +31 -30
- package/dist/plugins/ecosystem/node-registry-source.js.map +1 -1
- package/dist/plugins/ecosystem/node-release-package-source.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-release-package-source.js +30 -29
- package/dist/plugins/ecosystem/node-release-package-source.js.map +1 -1
- package/dist/plugins/ecosystem/node-tar-writer.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-tar-writer.js +11 -10
- package/dist/plugins/ecosystem/node-tar-writer.js.map +1 -1
- package/dist/plugins/ecosystem/node-url-package-source.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-url-package-source.js +21 -20
- package/dist/plugins/ecosystem/node-url-package-source.js.map +1 -1
- package/dist/plugins/ecosystem/plugin-acquisition.d.ts.map +1 -1
- package/dist/plugins/ecosystem/plugin-acquisition.js +3 -2
- package/dist/plugins/ecosystem/plugin-acquisition.js.map +1 -1
- package/dist/plugins/ecosystem/plugin-bootstrap.d.ts +9 -1
- package/dist/plugins/ecosystem/plugin-bootstrap.d.ts.map +1 -1
- package/dist/plugins/ecosystem/plugin-bootstrap.js +5 -0
- package/dist/plugins/ecosystem/plugin-bootstrap.js.map +1 -1
- package/dist/plugins/ecosystem/registry-cli-adapter.d.ts.map +1 -1
- package/dist/plugins/ecosystem/registry-cli-adapter.js +2 -1
- package/dist/plugins/ecosystem/registry-cli-adapter.js.map +1 -1
- package/dist/plugins/ecosystem/registry.d.ts.map +1 -1
- package/dist/plugins/ecosystem/registry.js +35 -32
- package/dist/plugins/ecosystem/registry.js.map +1 -1
- package/dist/plugins/ecosystem/resolver.d.ts.map +1 -1
- package/dist/plugins/ecosystem/resolver.js +3 -2
- package/dist/plugins/ecosystem/resolver.js.map +1 -1
- package/dist/plugins/ecosystem/selection-store.d.ts.map +1 -1
- package/dist/plugins/ecosystem/selection-store.js +11 -10
- package/dist/plugins/ecosystem/selection-store.js.map +1 -1
- package/dist/plugins/ecosystem/selection.d.ts.map +1 -1
- package/dist/plugins/ecosystem/selection.js +8 -7
- package/dist/plugins/ecosystem/selection.js.map +1 -1
- package/dist/plugins/ecosystem/state.d.ts.map +1 -1
- package/dist/plugins/ecosystem/state.js +24 -23
- package/dist/plugins/ecosystem/state.js.map +1 -1
- package/dist/plugins/ecosystem/updates.d.ts.map +1 -1
- package/dist/plugins/ecosystem/updates.js +5 -4
- package/dist/plugins/ecosystem/updates.js.map +1 -1
- package/dist/plugins/mcp-http.d.ts +0 -17
- package/dist/plugins/mcp-http.d.ts.map +1 -1
- package/dist/plugins/mcp-http.js +30 -7
- package/dist/plugins/mcp-http.js.map +1 -1
- package/dist/plugins/plugin-supervisor.d.ts.map +1 -1
- package/dist/plugins/plugin-supervisor.js +19 -1
- package/dist/plugins/plugin-supervisor.js.map +1 -1
- package/dist/profile/first-roles.d.ts.map +1 -1
- package/dist/profile/first-roles.js +10 -1
- package/dist/profile/first-roles.js.map +1 -1
- package/dist/profile/goal-plugin.d.ts.map +1 -1
- package/dist/profile/goal-plugin.js +24 -5
- package/dist/profile/goal-plugin.js.map +1 -1
- package/dist/profile/identity.d.ts.map +1 -1
- package/dist/profile/identity.js +7 -6
- package/dist/profile/identity.js.map +1 -1
- package/dist/profile/lock.d.ts.map +1 -1
- package/dist/profile/lock.js +15 -1
- package/dist/profile/lock.js.map +1 -1
- package/dist/profile/plan-plugin.d.ts.map +1 -1
- package/dist/profile/plan-plugin.js +26 -13
- package/dist/profile/plan-plugin.js.map +1 -1
- package/dist/profile/plugin-profiles.d.ts.map +1 -1
- package/dist/profile/plugin-profiles.js +2 -1
- package/dist/profile/plugin-profiles.js.map +1 -1
- package/dist/profile/policy-manifest.d.ts.map +1 -1
- package/dist/profile/policy-manifest.js +5 -4
- package/dist/profile/policy-manifest.js.map +1 -1
- package/dist/profile/profile-registry.d.ts +0 -12
- package/dist/profile/profile-registry.d.ts.map +1 -1
- package/dist/profile/profile-registry.js +14 -1
- package/dist/profile/profile-registry.js.map +1 -1
- package/dist/profile/role-catalog.d.ts +0 -1
- package/dist/profile/role-catalog.d.ts.map +1 -1
- package/dist/profile/role-catalog.js +17 -15
- package/dist/profile/role-catalog.js.map +1 -1
- package/dist/profile/role-orchestrator.d.ts.map +1 -1
- package/dist/profile/role-orchestrator.js +8 -7
- package/dist/profile/role-orchestrator.js.map +1 -1
- package/dist/profile/role-runner.d.ts.map +1 -1
- package/dist/profile/role-runner.js +3 -2
- package/dist/profile/role-runner.js.map +1 -1
- package/dist/profile/root-facade.d.ts.map +1 -1
- package/dist/profile/root-facade.js +2 -1
- package/dist/profile/root-facade.js.map +1 -1
- package/dist/profile/skill-discovery-wiring.d.ts.map +1 -1
- package/dist/profile/skill-discovery-wiring.js +7 -2
- package/dist/profile/skill-discovery-wiring.js.map +1 -1
- package/dist/profile/state-store.d.ts.map +1 -1
- package/dist/profile/state-store.js +13 -9
- package/dist/profile/state-store.js.map +1 -1
- package/dist/profile/todo-plugin.d.ts.map +1 -1
- package/dist/profile/todo-plugin.js +9 -4
- package/dist/profile/todo-plugin.js.map +1 -1
- package/dist/profiles/builtin-suites.d.ts +13 -6
- package/dist/profiles/builtin-suites.d.ts.map +1 -1
- package/dist/profiles/builtin-suites.js +15 -6
- package/dist/profiles/builtin-suites.js.map +1 -1
- package/dist/profiles/suite-loader.d.ts +25 -1
- package/dist/profiles/suite-loader.d.ts.map +1 -1
- package/dist/profiles/suite-loader.js +34 -9
- package/dist/profiles/suite-loader.js.map +1 -1
- package/dist/remote/server-entry.d.ts.map +1 -1
- package/dist/remote/server-entry.js +3 -2
- package/dist/remote/server-entry.js.map +1 -1
- package/dist/remote/session-source.d.ts.map +1 -1
- package/dist/remote/session-source.js +8 -4
- package/dist/remote/session-source.js.map +1 -1
- package/dist/share/session-share.d.ts.map +1 -1
- package/dist/share/session-share.js +14 -2
- package/dist/share/session-share.js.map +1 -1
- package/dist/testing/plugin-testkit.d.ts.map +1 -1
- package/dist/testing/plugin-testkit.js +4 -3
- package/dist/testing/plugin-testkit.js.map +1 -1
- package/dist/testing/runtime-delivery-queue.d.ts.map +1 -1
- package/dist/testing/runtime-delivery-queue.js +2 -1
- package/dist/testing/runtime-delivery-queue.js.map +1 -1
- package/dist/testing/session-plugin-testkit.d.ts.map +1 -1
- package/dist/testing/session-plugin-testkit.js +10 -7
- package/dist/testing/session-plugin-testkit.js.map +1 -1
- package/dist/utils/ansi.d.ts.map +1 -1
- package/dist/utils/ansi.js +2 -1
- package/dist/utils/ansi.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +7 -6
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/image-resize-worker.d.ts.map +1 -1
- package/dist/utils/image-resize-worker.js +3 -2
- package/dist/utils/image-resize-worker.js.map +1 -1
- package/dist/utils/photon.d.ts +2 -2
- package/dist/utils/photon.d.ts.map +1 -1
- package/dist/utils/photon.js +2 -2
- package/dist/utils/photon.js.map +1 -1
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +9 -8
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +23 -13
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +5 -1
- package/dist/utils/version-check.js.map +1 -1
- package/docs/compaction.md +1 -3
- package/docs/docs.json +4 -0
- package/docs/index.md +1 -0
- package/docs/keybindings.md +3 -0
- package/docs/mcp.md +103 -0
- package/docs/models.md +14 -22
- package/docs/providers.md +0 -1
- package/docs/quickstart.md +1 -1
- package/docs/rpc.md +1 -26
- package/docs/sdk.md +4 -6
- package/docs/settings.md +10 -12
- package/docs/skills.md +1 -9
- package/docs/terminal-setup.md +2 -3
- package/docs/usage.md +7 -6
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/extensions/with-deps/plugin.json +1 -1
- package/examples/sdk/10-settings.ts +2 -4
- package/examples/sdk/12-full-control.ts +1 -2
- package/package.json +1 -3
- package/dist/bundle/chunks/chunk-EALJ5DDX.js +0 -1452
- package/dist/core/extensions/microcompact.d.ts +0 -80
- package/dist/core/extensions/microcompact.d.ts.map +0 -1
- package/dist/core/extensions/microcompact.js +0 -134
- package/dist/core/extensions/microcompact.js.map +0 -1
- package/dist/server/create-harness.d.ts +0 -26
- package/dist/server/create-harness.d.ts.map +0 -1
- package/dist/server/create-harness.js +0 -106
- package/dist/server/create-harness.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-storage.d.ts","sourceRoot":"","sources":["../../src/core/session-storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Storage seam for process sessions (D-067 phase 1).\n *\n * `SessionManager` owns the session tree, index, and context logic; a\n * `SessionStorageProvider` owns only physical record storage for one session\n * location. The default implementation is the current local JSONL file\n * protocol, byte-identical to the pre-seam behavior; alternative providers\n * (in-memory for tests, remote backends later) implement the same contract.\n *\n * Scope line: discovery/enrichment reads used by `SessionManager.list` /\n * `listAll` (async streamed `SessionInfo` construction) stay on the fs path\n * for now; they move behind this seam in a later phase.\n *\n * Cross-process write arbitration (D-067 phase 2 S5): providers may expose an\n * optional {@link SessionWriteGuardV1}. The default file provider implements\n * it as a `<sessionFile>.guard` sibling token ({ writerId, hostId, pid }): a\n * write fails explicitly while another LIVE process on the same host owns the\n * session, a dead owner (closed/crashed process) is taken over silently, and\n * same-process writers are never conflicted (in-process sequencing is the\n * caller's concern, unchanged). Cross-host guards are treated as live\n * foreign owners (conservative: no false takeover over shared filesystems).\n */\n\nimport {\n\tappendFileSync,\n\tcloseSync,\n\texistsSync,\n\tmkdirSync,\n\topenSync,\n\treaddirSync,\n\treadFileSync,\n\treadSync,\n\tstatSync,\n\ttruncateSync,\n\tunlinkSync,\n\twriteFileSync,\n\twriteSync,\n} from \"fs\";\nimport { hostname } from \"os\";\nimport { dirname, join } from \"path\";\nimport { StringDecoder } from \"string_decoder\";\nimport { normalizePath } from \"../utils/paths.ts\";\nimport { sleepSync } from \"../utils/sync-sleep.ts\";\nimport type { FileEntry, SessionHeader } from \"./session-manager.ts\";\n\nconst SESSION_READ_BUFFER_SIZE = 1024 * 1024;\nconst SESSION_HEADER_READ_BUFFER_SIZE = 4096;\n/** Bound synchronous header discovery while allowing large cwd and custom metadata fields. */\nconst MAX_SESSION_HEADER_SCAN_BYTES = 1024 * 1024;\n\n/**\n * Thrown by `readHeader` when the header scan exceeds the bounded limit.\n * `SessionManager.open` treats it as a discovery optimization miss and falls\n * back to a full load; discovery callers swallow it.\n */\nexport class SessionHeaderScanLimitError extends Error {\n\tconstructor(filePath: string) {\n\t\tsuper(`Session header exceeds ${MAX_SESSION_HEADER_SCAN_BYTES}-byte scan limit: ${filePath}`);\n\t\tthis.name = \"SessionHeaderScanLimitError\";\n\t}\n}\n\n/**\n * Record-level storage contract for sessions, keyed by location path. All\n * methods are synchronous and reflect the append-only JSONL protocol\n * semantics; implementations for other media map the same operations onto\n * their own storage model.\n */\nexport interface SessionStorageProvider {\n\t/** Ensure the storage location for a session directory exists. */\n\tensureDir(dir: string): void;\n\t/** True when the given session location exists. */\n\texists(path: string): boolean;\n\t/** Current byte size of a session location (rollback baseline). Missing locations throw. */\n\tsize(path: string): number;\n\t/**\n\t * Load all entries (header first). A missing location yields `[]`. When the\n\t * stored content ends with an unterminated final line, implementations\n\t * repair it by terminating it (the file protocol appends a `\\n`).\n\t */\n\tloadEntries(path: string): FileEntry[];\n\t/** Bounded header scan used for discovery and cwd resolution. Missing or header-less locations yield `null`. */\n\treadHeader(path: string): SessionHeader | null;\n\t/** Replace the whole stored content (header included). */\n\trewrite(path: string, entries: readonly FileEntry[]): void;\n\t/** Write initial content; fails when the location already exists (\"wx\" semantics). */\n\tcreateExclusive(path: string, entries: readonly FileEntry[]): void;\n\t/** Append one entry after the existing content. */\n\tappend(path: string, entry: FileEntry): void;\n\t/** Roll back to a previous byte size after a failed append. */\n\ttruncate(path: string, size: number): void;\n\t/** Remove a session location (used when a failed append created it). */\n\tremove(path: string): void;\n\t/** Enumerate session locations inside a directory. Missing directories throw. */\n\tlistSessionFiles(dir: string): string[];\n\t/** Modification time of a session location, used for recent-session ordering. */\n\tmodifiedTime(path: string): Date;\n\t/**\n\t * Optional cross-process write guard (D-067 phase 2 S5). When present,\n\t * `SessionManager` calls `ensureWritable` before every record write and a\n\t * conflicting live foreign owner fails the write explicitly; absent, no\n\t * arbitration happens (the pre-S5 behavior).\n\t */\n\treadonly guard?: SessionWriteGuardV1;\n}\n\n/**\n * Cross-process write guard for one session location (D-067 phase 2 S5).\n * Implementations must make \"check and take ownership\" atomic against other\n * processes, and must throw a descriptive error when another live process\n * owns the session instead of letting two writers interleave entries.\n */\nexport interface SessionWriteGuardV1 {\n\t/**\n\t * Verifies the caller may write the session and records (or refreshes) its\n\t * ownership. Throws when another live process owns the session.\n\t */\n\tensureWritable(sessionFile: string, writerId: string): void;\n}\n\nfunction parseSessionEntryLine(line: string): FileEntry | null {\n\tif (!line.trim()) return null;\n\ttry {\n\t\treturn JSON.parse(line) as FileEntry;\n\t} catch {\n\t\t// Skip malformed lines\n\t\treturn null;\n\t}\n}\n\n/** Serialize one entry as a protocol line (JSON body plus terminating newline). */\nfunction serializeEntryLine(entry: FileEntry): string {\n\treturn `${JSON.stringify(entry)}\\n`;\n}\n\nfunction parseSessionHeaderCandidate(line: string): SessionHeader | null | undefined {\n\tif (!line.trim()) return undefined;\n\tconst entry = parseSessionEntryLine(line);\n\tif (!entry) return undefined;\n\tif (entry.type !== \"session\" || typeof (entry as { id?: unknown }).id !== \"string\") return null;\n\treturn entry;\n}\n\n/** Suffix of the session write-guard sibling file (`<sessionFile>.guard`). */\nconst GUARD_FILE_SUFFIX = \".guard\";\n/** Wall-clock wait between guard-lock acquisition attempts (ledger protocol value). */\nconst GUARD_LOCK_POLL_INTERVAL_MS = 25;\n/** Default ceiling on waiting for a competing guard-lock holder. */\nconst GUARD_LOCK_TIMEOUT_MS = 5000;\n/** Default age at which a guard-lock counts as abandoned and is stolen. */\nconst GUARD_STALE_LOCK_MS = 60_000;\n\ninterface SessionGuardTokenV1 {\n\treadonly writerId: string;\n\treadonly hostId: string;\n\treadonly pid: number;\n}\n\nfunction defaultPidAlive(pid: number): boolean {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error) {\n\t\t// EPERM means the process exists but is owned by another user — alive.\n\t\treturn (error as NodeJS.ErrnoException).code === \"EPERM\";\n\t}\n}\n\n/** Writes every byte of `data` to the fd, looping over partial writeSync results. */\nfunction writeAllSync(fd: number, data: string): void {\n\tconst buffer = Buffer.from(data, \"utf8\");\n\tlet written = 0;\n\twhile (written < buffer.length) {\n\t\twritten += writeSync(fd, buffer, written, buffer.length - written);\n\t}\n}\n\n/**\n * Takes the guard's cross-process lock (same protocol as the memory ledger's\n * and the durable store's persist locks — exclusive create, poll, mtime\n * stale-lock stealing, token-guarded release; the three implementations stay\n * behaviorally identical by precedent, deliberately without module coupling).\n */\nfunction acquireGuardLock(lockPath: string, lockTimeoutMs: number, staleLockMs: number): string {\n\tconst deadline = Date.now() + lockTimeoutMs;\n\tfor (;;) {\n\t\tmkdirSync(dirname(lockPath), { recursive: true });\n\t\tconst token = `${process.pid}:${Date.now()}`;\n\t\ttry {\n\t\t\tconst fd = openSync(lockPath, \"wx\");\n\t\t\ttry {\n\t\t\t\twriteFileSync(fd, token);\n\t\t\t} finally {\n\t\t\t\tcloseSync(fd);\n\t\t\t}\n\t\t\treturn token;\n\t\t} catch (error) {\n\t\t\tconst code = (error as NodeJS.ErrnoException).code;\n\t\t\t// POSIX maps a lost create race to EEXIST; Windows/libuv maps the\n\t\t\t// same concurrent-create sharing violation to EPERM. For EPERM a\n\t\t\t// vanished lock retries, a persistent permission failure propagates.\n\t\t\tif (code === \"EPERM\") {\n\t\t\t\tlet vanished = false;\n\t\t\t\ttry {\n\t\t\t\t\tstatSync(lockPath);\n\t\t\t\t} catch (statError) {\n\t\t\t\t\tif ((statError as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n\t\t\t\t\tvanished = true;\n\t\t\t\t}\n\t\t\t\tif (vanished) {\n\t\t\t\t\tif (Date.now() >= deadline) throw error;\n\t\t\t\t\tsleepSync(GUARD_LOCK_POLL_INTERVAL_MS);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t} else if (code !== \"EEXIST\") {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tlet stale = false;\n\t\t\ttry {\n\t\t\t\tstale = Date.now() - statSync(lockPath).mtimeMs > staleLockMs;\n\t\t\t} catch {\n\t\t\t\t// The holder released between the failed open and the stat.\n\t\t\t}\n\t\t\tif (stale) {\n\t\t\t\ttry {\n\t\t\t\t\tunlinkSync(lockPath);\n\t\t\t\t} catch {\n\t\t\t\t\t// Already stolen — retry.\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (Date.now() >= deadline) {\n\t\t\t\tthrow new Error(`session guard lock timeout after ${lockTimeoutMs} ms: ${lockPath}`);\n\t\t\t}\n\t\t\tsleepSync(GUARD_LOCK_POLL_INTERVAL_MS);\n\t\t}\n\t}\n}\n\n/** Releases the guard lock, but only if our own token still owns it. */\nfunction releaseGuardLock(lockPath: string, token: string): void {\n\ttry {\n\t\tif (readFileSync(lockPath, \"utf8\") === token) unlinkSync(lockPath);\n\t} catch {\n\t\t// Already gone (or unreadable) — nothing to release.\n\t}\n}\n\n/**\n * The default provider: the local JSONL file protocol, moved verbatim from\n * `SessionManager`. Stateless beyond the injectable guard identity options,\n * so a shared module-level instance is the default for every session manager\n * that does not inject its own provider.\n */\nexport class FileSessionStorageProvider implements SessionStorageProvider {\n\t/** Shared stateless instance used as the default provider. */\n\tstatic readonly instance: FileSessionStorageProvider = new FileSessionStorageProvider();\n\n\tprivate readonly hostId: string;\n\tprivate readonly ownPid: number;\n\tprivate readonly pidAlive: (pid: number) => boolean;\n\n\tconstructor(\n\t\toptions: {\n\t\t\t/** Host identity written into guard tokens; defaults to `os.hostname()`. */\n\t\t\treadonly hostId?: string;\n\t\t\t/** Process identity written into guard tokens; defaults to `process.pid`. */\n\t\t\treadonly ownPid?: number;\n\t\t\t/** Injectable liveness check for deterministic tests; defaults to `process.kill(pid, 0)`. */\n\t\t\treadonly pidAlive?: (pid: number) => boolean;\n\t\t} = {},\n\t) {\n\t\tthis.hostId = options.hostId ?? hostname();\n\t\tthis.ownPid = options.ownPid ?? process.pid;\n\t\tthis.pidAlive = options.pidAlive ?? defaultPidAlive;\n\t}\n\n\tensureDir(dir: string): void {\n\t\tmkdirSync(dir, { recursive: true });\n\t}\n\n\texists(path: string): boolean {\n\t\treturn existsSync(path);\n\t}\n\n\tsize(path: string): number {\n\t\treturn statSync(path).size;\n\t}\n\n\tloadEntries(path: string): FileEntry[] {\n\t\tconst resolvedFilePath = normalizePath(path);\n\t\tif (!existsSync(resolvedFilePath)) return [];\n\n\t\tconst entries: FileEntry[] = [];\n\t\tlet pending = \"\";\n\t\tconst fd = openSync(resolvedFilePath, \"r\");\n\t\ttry {\n\t\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\t\tconst buffer = Buffer.allocUnsafe(SESSION_READ_BUFFER_SIZE);\n\n\t\t\twhile (true) {\n\t\t\t\tconst bytesRead = readSync(fd, buffer, 0, buffer.length, null);\n\t\t\t\tif (bytesRead === 0) break;\n\n\t\t\t\tpending += decoder.write(buffer.subarray(0, bytesRead));\n\t\t\t\tlet lineStart = 0;\n\t\t\t\tlet newlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\t\tconst entry = parseSessionEntryLine(pending.slice(lineStart, newlineIndex));\n\t\t\t\t\tif (entry) entries.push(entry);\n\t\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\t\tnewlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t\t}\n\t\t\t\tpending = pending.slice(lineStart);\n\t\t\t}\n\n\t\t\tpending += decoder.end();\n\t\t\tconst finalEntry = parseSessionEntryLine(pending);\n\t\t\tif (finalEntry) entries.push(finalEntry);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\n\t\t// Validate session header before repairing the file.\n\t\tif (entries.length === 0) return entries;\n\t\tconst header = entries[0];\n\t\tif (header.type !== \"session\" || typeof (header as { id?: unknown }).id !== \"string\") {\n\t\t\treturn [];\n\t\t}\n\n\t\tif (pending) appendFileSync(resolvedFilePath, \"\\n\");\n\t\treturn entries;\n\t}\n\n\treadHeader(path: string): SessionHeader | null {\n\t\tif (!existsSync(path)) return null;\n\t\tconst fd = openSync(path, \"r\");\n\t\ttry {\n\t\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\t\tconst buffer = Buffer.allocUnsafe(SESSION_HEADER_READ_BUFFER_SIZE);\n\t\t\tconst lineChunks: string[] = [];\n\t\t\tlet scannedBytes = 0;\n\n\t\t\twhile (scannedBytes < MAX_SESSION_HEADER_SCAN_BYTES) {\n\t\t\t\tconst readLength = Math.min(buffer.length, MAX_SESSION_HEADER_SCAN_BYTES - scannedBytes);\n\t\t\t\tconst bytesRead = readSync(fd, buffer, 0, readLength, null);\n\t\t\t\tif (bytesRead === 0) {\n\t\t\t\t\tlineChunks.push(decoder.end());\n\t\t\t\t\treturn parseSessionHeaderCandidate(lineChunks.join(\"\")) ?? null;\n\t\t\t\t}\n\t\t\t\tscannedBytes += bytesRead;\n\n\t\t\t\tconst chunk = decoder.write(buffer.subarray(0, bytesRead));\n\t\t\t\tlet lineStart = 0;\n\t\t\t\tlet newlineIndex = chunk.indexOf(\"\\n\", lineStart);\n\t\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\t\tlineChunks.push(chunk.slice(lineStart, newlineIndex));\n\t\t\t\t\tconst header = parseSessionHeaderCandidate(lineChunks.join(\"\"));\n\t\t\t\t\tif (header !== undefined) return header;\n\t\t\t\t\tlineChunks.length = 0;\n\t\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\t\tnewlineIndex = chunk.indexOf(\"\\n\", lineStart);\n\t\t\t\t}\n\t\t\t\tlineChunks.push(chunk.slice(lineStart));\n\t\t\t}\n\n\t\t\t// Probe for EOF so a final header without a newline is allowed when it ends\n\t\t\t// exactly at the scan limit. Any additional byte exceeds the bounded scan.\n\t\t\tconst probe = Buffer.allocUnsafe(1);\n\t\t\tif (readSync(fd, probe, 0, probe.length, null) === 0) {\n\t\t\t\tlineChunks.push(decoder.end());\n\t\t\t\treturn parseSessionHeaderCandidate(lineChunks.join(\"\")) ?? null;\n\t\t\t}\n\t\t\tthrow new SessionHeaderScanLimitError(path);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\trewrite(path: string, entries: readonly FileEntry[]): void {\n\t\tconst fd = openSync(path, \"w\");\n\t\ttry {\n\t\t\tfor (const entry of entries) {\n\t\t\t\twriteFileSync(fd, serializeEntryLine(entry));\n\t\t\t}\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\tcreateExclusive(path: string, entries: readonly FileEntry[]): void {\n\t\tconst fd = openSync(path, \"wx\");\n\t\ttry {\n\t\t\tfor (const entry of entries) {\n\t\t\t\twriteFileSync(fd, serializeEntryLine(entry));\n\t\t\t}\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\tappend(path: string, entry: FileEntry): void {\n\t\tappendFileSync(path, serializeEntryLine(entry));\n\t}\n\n\ttruncate(path: string, size: number): void {\n\t\ttruncateSync(path, size);\n\t}\n\n\tremove(path: string): void {\n\t\tunlinkSync(path);\n\t}\n\n\tlistSessionFiles(dir: string): string[] {\n\t\tconst resolvedDir = normalizePath(dir);\n\t\treturn readdirSync(resolvedDir)\n\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t.map((f) => join(resolvedDir, f));\n\t}\n\n\tmodifiedTime(path: string): Date {\n\t\treturn statSync(path).mtime;\n\t}\n\n\t/**\n\t * Cross-process write guard (D-067 phase 2 S5): `<sessionFile>.guard`\n\t * carries `{ writerId, hostId, pid }`. A write conflicts only while the\n\t * recorded owner is a live process on this host; a dead owner (closed or\n\t * crashed process) and same-process writers never conflict, and cross-host\n\t * guards are treated as live foreign owners (conservative over shared\n\t * filesystems). Check-and-take-ownership is atomic under the guard lock.\n\t */\n\treadonly guard: SessionWriteGuardV1 = {\n\t\tensureWritable: (sessionFile: string, writerId: string): void => {\n\t\t\tconst guardPath = `${sessionFile}${GUARD_FILE_SUFFIX}`;\n\t\t\tconst readToken = (): SessionGuardTokenV1 | undefined => {\n\t\t\t\ttry {\n\t\t\t\t\tconst value: unknown = JSON.parse(readFileSync(guardPath, \"utf8\"));\n\t\t\t\t\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) return undefined;\n\t\t\t\t\tconst record = value as { writerId?: unknown; hostId?: unknown; pid?: unknown };\n\t\t\t\t\tif (typeof record.writerId !== \"string\" || record.writerId.length === 0) return undefined;\n\t\t\t\t\tif (typeof record.hostId !== \"string\" || record.hostId.length === 0) return undefined;\n\t\t\t\t\tif (typeof record.pid !== \"number\" || !Number.isInteger(record.pid)) return undefined;\n\t\t\t\t\treturn { writerId: record.writerId, hostId: record.hostId, pid: record.pid };\n\t\t\t\t} catch {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst current = readToken();\n\t\t\t// Fast path: already ours (most appends), or no owner.\n\t\t\tif (current !== undefined && current.writerId === writerId) return;\n\t\t\tif (current !== undefined) {\n\t\t\t\tconst foreignLive =\n\t\t\t\t\tcurrent.hostId === this.hostId && current.pid !== this.ownPid && this.pidAlive(current.pid);\n\t\t\t\tif (foreignLive) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Session file is owned by another live process (pid ${current.pid}): ${sessionFile}. ` +\n\t\t\t\t\t\t\t\"Close that process first; concurrent writers would silently fork the session history.\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Free, dead owner, or same-process writer: take (or refresh) ownership.\n\t\t\tconst token = acquireGuardLock(`${guardPath}.lock`, GUARD_LOCK_TIMEOUT_MS, GUARD_STALE_LOCK_MS);\n\t\t\ttry {\n\t\t\t\tconst fd = openSync(guardPath, \"w\");\n\t\t\t\ttry {\n\t\t\t\t\twriteAllSync(\n\t\t\t\t\t\tfd,\n\t\t\t\t\t\tJSON.stringify({ writerId, hostId: this.hostId, pid: this.ownPid } satisfies SessionGuardTokenV1),\n\t\t\t\t\t);\n\t\t\t\t} finally {\n\t\t\t\t\tcloseSync(fd);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\treleaseGuardLock(`${guardPath}.lock`, token);\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session-storage.d.ts","sourceRoot":"","sources":["../../src/core/session-storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Storage seam for process sessions (D-067 phase 1).\n *\n * `SessionManager` owns the session tree, index, and context logic; a\n * `SessionStorageProvider` owns only physical record storage for one session\n * location. The default implementation is the current local JSONL file\n * protocol, byte-identical to the pre-seam behavior; alternative providers\n * (in-memory for tests, remote backends later) implement the same contract.\n *\n * Scope line: discovery/enrichment reads used by `SessionManager.list` /\n * `listAll` (async streamed `SessionInfo` construction) stay on the fs path\n * for now; they move behind this seam in a later phase.\n *\n * Cross-process write arbitration (D-067 phase 2 S5): providers may expose an\n * optional {@link SessionWriteGuardV1}. The default file provider implements\n * it as a `<sessionFile>.guard` sibling token ({ writerId, hostId, pid }): a\n * write fails explicitly while another LIVE process on the same host owns the\n * session, a dead owner (closed/crashed process) is taken over silently, and\n * same-process writers are never conflicted (in-process sequencing is the\n * caller's concern, unchanged). Cross-host guards are treated as live\n * foreign owners (conservative: no false takeover over shared filesystems).\n */\n\nimport {\n\tappendFileSync,\n\tcloseSync,\n\texistsSync,\n\tmkdirSync,\n\topenSync,\n\treaddirSync,\n\treadFileSync,\n\treadSync,\n\tstatSync,\n\ttruncateSync,\n\tunlinkSync,\n\twriteFileSync,\n\twriteSync,\n} from \"fs\";\nimport { hostname } from \"os\";\nimport { dirname, join } from \"path\";\nimport { StringDecoder } from \"string_decoder\";\nimport { normalizePath } from \"../utils/paths.ts\";\nimport { sleepSync } from \"../utils/sync-sleep.ts\";\nimport { t } from \"./i18n/index.ts\";\nimport type { FileEntry, SessionHeader } from \"./session-manager.ts\";\n\nconst SESSION_READ_BUFFER_SIZE = 1024 * 1024;\nconst SESSION_HEADER_READ_BUFFER_SIZE = 4096;\n/** Bound synchronous header discovery while allowing large cwd and custom metadata fields. */\nconst MAX_SESSION_HEADER_SCAN_BYTES = 1024 * 1024;\n\n/**\n * Thrown by `readHeader` when the header scan exceeds the bounded limit.\n * `SessionManager.open` treats it as a discovery optimization miss and falls\n * back to a full load; discovery callers swallow it.\n */\nexport class SessionHeaderScanLimitError extends Error {\n\tconstructor(filePath: string) {\n\t\tsuper(`Session header exceeds ${MAX_SESSION_HEADER_SCAN_BYTES}-byte scan limit: ${filePath}`);\n\t\tthis.name = \"SessionHeaderScanLimitError\";\n\t}\n}\n\n/**\n * Record-level storage contract for sessions, keyed by location path. All\n * methods are synchronous and reflect the append-only JSONL protocol\n * semantics; implementations for other media map the same operations onto\n * their own storage model.\n */\nexport interface SessionStorageProvider {\n\t/** Ensure the storage location for a session directory exists. */\n\tensureDir(dir: string): void;\n\t/** True when the given session location exists. */\n\texists(path: string): boolean;\n\t/** Current byte size of a session location (rollback baseline). Missing locations throw. */\n\tsize(path: string): number;\n\t/**\n\t * Load all entries (header first). A missing location yields `[]`. When the\n\t * stored content ends with an unterminated final line, implementations\n\t * repair it by terminating it (the file protocol appends a `\\n`).\n\t */\n\tloadEntries(path: string): FileEntry[];\n\t/** Bounded header scan used for discovery and cwd resolution. Missing or header-less locations yield `null`. */\n\treadHeader(path: string): SessionHeader | null;\n\t/** Replace the whole stored content (header included). */\n\trewrite(path: string, entries: readonly FileEntry[]): void;\n\t/** Write initial content; fails when the location already exists (\"wx\" semantics). */\n\tcreateExclusive(path: string, entries: readonly FileEntry[]): void;\n\t/** Append one entry after the existing content. */\n\tappend(path: string, entry: FileEntry): void;\n\t/** Roll back to a previous byte size after a failed append. */\n\ttruncate(path: string, size: number): void;\n\t/** Remove a session location (used when a failed append created it). */\n\tremove(path: string): void;\n\t/** Enumerate session locations inside a directory. Missing directories throw. */\n\tlistSessionFiles(dir: string): string[];\n\t/** Modification time of a session location, used for recent-session ordering. */\n\tmodifiedTime(path: string): Date;\n\t/**\n\t * Optional cross-process write guard (D-067 phase 2 S5). When present,\n\t * `SessionManager` calls `ensureWritable` before every record write and a\n\t * conflicting live foreign owner fails the write explicitly; absent, no\n\t * arbitration happens (the pre-S5 behavior).\n\t */\n\treadonly guard?: SessionWriteGuardV1;\n}\n\n/**\n * Cross-process write guard for one session location (D-067 phase 2 S5).\n * Implementations must make \"check and take ownership\" atomic against other\n * processes, and must throw a descriptive error when another live process\n * owns the session instead of letting two writers interleave entries.\n */\nexport interface SessionWriteGuardV1 {\n\t/**\n\t * Verifies the caller may write the session and records (or refreshes) its\n\t * ownership. Throws when another live process owns the session.\n\t */\n\tensureWritable(sessionFile: string, writerId: string): void;\n}\n\nfunction parseSessionEntryLine(line: string): FileEntry | null {\n\tif (!line.trim()) return null;\n\ttry {\n\t\treturn JSON.parse(line) as FileEntry;\n\t} catch {\n\t\t// Skip malformed lines\n\t\treturn null;\n\t}\n}\n\n/** Serialize one entry as a protocol line (JSON body plus terminating newline). */\nfunction serializeEntryLine(entry: FileEntry): string {\n\treturn `${JSON.stringify(entry)}\\n`;\n}\n\nfunction parseSessionHeaderCandidate(line: string): SessionHeader | null | undefined {\n\tif (!line.trim()) return undefined;\n\tconst entry = parseSessionEntryLine(line);\n\tif (!entry) return undefined;\n\tif (entry.type !== \"session\" || typeof (entry as { id?: unknown }).id !== \"string\") return null;\n\treturn entry;\n}\n\n/** Suffix of the session write-guard sibling file (`<sessionFile>.guard`). */\nconst GUARD_FILE_SUFFIX = \".guard\";\n/** Wall-clock wait between guard-lock acquisition attempts (ledger protocol value). */\nconst GUARD_LOCK_POLL_INTERVAL_MS = 25;\n/** Default ceiling on waiting for a competing guard-lock holder. */\nconst GUARD_LOCK_TIMEOUT_MS = 5000;\n/** Default age at which a guard-lock counts as abandoned and is stolen. */\nconst GUARD_STALE_LOCK_MS = 60_000;\n\ninterface SessionGuardTokenV1 {\n\treadonly writerId: string;\n\treadonly hostId: string;\n\treadonly pid: number;\n}\n\nfunction defaultPidAlive(pid: number): boolean {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error) {\n\t\t// EPERM means the process exists but is owned by another user — alive.\n\t\treturn (error as NodeJS.ErrnoException).code === \"EPERM\";\n\t}\n}\n\n/** Writes every byte of `data` to the fd, looping over partial writeSync results. */\nfunction writeAllSync(fd: number, data: string): void {\n\tconst buffer = Buffer.from(data, \"utf8\");\n\tlet written = 0;\n\twhile (written < buffer.length) {\n\t\twritten += writeSync(fd, buffer, written, buffer.length - written);\n\t}\n}\n\n/**\n * Takes the guard's cross-process lock (same protocol as the memory ledger's\n * and the durable store's persist locks — exclusive create, poll, mtime\n * stale-lock stealing, token-guarded release; the three implementations stay\n * behaviorally identical by precedent, deliberately without module coupling).\n */\nfunction acquireGuardLock(lockPath: string, lockTimeoutMs: number, staleLockMs: number): string {\n\tconst deadline = Date.now() + lockTimeoutMs;\n\tfor (;;) {\n\t\tmkdirSync(dirname(lockPath), { recursive: true });\n\t\tconst token = `${process.pid}:${Date.now()}`;\n\t\ttry {\n\t\t\tconst fd = openSync(lockPath, \"wx\");\n\t\t\ttry {\n\t\t\t\twriteFileSync(fd, token);\n\t\t\t} finally {\n\t\t\t\tcloseSync(fd);\n\t\t\t}\n\t\t\treturn token;\n\t\t} catch (error) {\n\t\t\tconst code = (error as NodeJS.ErrnoException).code;\n\t\t\t// POSIX maps a lost create race to EEXIST; Windows/libuv maps the\n\t\t\t// same concurrent-create sharing violation to EPERM. For EPERM a\n\t\t\t// vanished lock retries, a persistent permission failure propagates.\n\t\t\tif (code === \"EPERM\") {\n\t\t\t\tlet vanished = false;\n\t\t\t\ttry {\n\t\t\t\t\tstatSync(lockPath);\n\t\t\t\t} catch (statError) {\n\t\t\t\t\tif ((statError as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n\t\t\t\t\tvanished = true;\n\t\t\t\t}\n\t\t\t\tif (vanished) {\n\t\t\t\t\tif (Date.now() >= deadline) throw error;\n\t\t\t\t\tsleepSync(GUARD_LOCK_POLL_INTERVAL_MS);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t} else if (code !== \"EEXIST\") {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tlet stale = false;\n\t\t\ttry {\n\t\t\t\tstale = Date.now() - statSync(lockPath).mtimeMs > staleLockMs;\n\t\t\t} catch {\n\t\t\t\t// The holder released between the failed open and the stat.\n\t\t\t}\n\t\t\tif (stale) {\n\t\t\t\ttry {\n\t\t\t\t\tunlinkSync(lockPath);\n\t\t\t\t} catch {\n\t\t\t\t\t// Already stolen — retry.\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (Date.now() >= deadline) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\tt(\"session guard lock timeout after {timeoutMs} ms: {lockPath}\", { timeoutMs: lockTimeoutMs, lockPath }),\n\t\t\t\t);\n\t\t\t}\n\t\t\tsleepSync(GUARD_LOCK_POLL_INTERVAL_MS);\n\t\t}\n\t}\n}\n\n/** Releases the guard lock, but only if our own token still owns it. */\nfunction releaseGuardLock(lockPath: string, token: string): void {\n\ttry {\n\t\tif (readFileSync(lockPath, \"utf8\") === token) unlinkSync(lockPath);\n\t} catch {\n\t\t// Already gone (or unreadable) — nothing to release.\n\t}\n}\n\n/**\n * The default provider: the local JSONL file protocol, moved verbatim from\n * `SessionManager`. Stateless beyond the injectable guard identity options,\n * so a shared module-level instance is the default for every session manager\n * that does not inject its own provider.\n */\nexport class FileSessionStorageProvider implements SessionStorageProvider {\n\t/** Shared stateless instance used as the default provider. */\n\tstatic readonly instance: FileSessionStorageProvider = new FileSessionStorageProvider();\n\n\tprivate readonly hostId: string;\n\tprivate readonly ownPid: number;\n\tprivate readonly pidAlive: (pid: number) => boolean;\n\n\tconstructor(\n\t\toptions: {\n\t\t\t/** Host identity written into guard tokens; defaults to `os.hostname()`. */\n\t\t\treadonly hostId?: string;\n\t\t\t/** Process identity written into guard tokens; defaults to `process.pid`. */\n\t\t\treadonly ownPid?: number;\n\t\t\t/** Injectable liveness check for deterministic tests; defaults to `process.kill(pid, 0)`. */\n\t\t\treadonly pidAlive?: (pid: number) => boolean;\n\t\t} = {},\n\t) {\n\t\tthis.hostId = options.hostId ?? hostname();\n\t\tthis.ownPid = options.ownPid ?? process.pid;\n\t\tthis.pidAlive = options.pidAlive ?? defaultPidAlive;\n\t}\n\n\tensureDir(dir: string): void {\n\t\tmkdirSync(dir, { recursive: true });\n\t}\n\n\texists(path: string): boolean {\n\t\treturn existsSync(path);\n\t}\n\n\tsize(path: string): number {\n\t\treturn statSync(path).size;\n\t}\n\n\tloadEntries(path: string): FileEntry[] {\n\t\tconst resolvedFilePath = normalizePath(path);\n\t\tif (!existsSync(resolvedFilePath)) return [];\n\n\t\tconst entries: FileEntry[] = [];\n\t\tlet pending = \"\";\n\t\tconst fd = openSync(resolvedFilePath, \"r\");\n\t\ttry {\n\t\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\t\tconst buffer = Buffer.allocUnsafe(SESSION_READ_BUFFER_SIZE);\n\n\t\t\twhile (true) {\n\t\t\t\tconst bytesRead = readSync(fd, buffer, 0, buffer.length, null);\n\t\t\t\tif (bytesRead === 0) break;\n\n\t\t\t\tpending += decoder.write(buffer.subarray(0, bytesRead));\n\t\t\t\tlet lineStart = 0;\n\t\t\t\tlet newlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\t\tconst entry = parseSessionEntryLine(pending.slice(lineStart, newlineIndex));\n\t\t\t\t\tif (entry) entries.push(entry);\n\t\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\t\tnewlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t\t}\n\t\t\t\tpending = pending.slice(lineStart);\n\t\t\t}\n\n\t\t\tpending += decoder.end();\n\t\t\tconst finalEntry = parseSessionEntryLine(pending);\n\t\t\tif (finalEntry) entries.push(finalEntry);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\n\t\t// Validate session header before repairing the file.\n\t\tif (entries.length === 0) return entries;\n\t\tconst header = entries[0];\n\t\tif (header.type !== \"session\" || typeof (header as { id?: unknown }).id !== \"string\") {\n\t\t\treturn [];\n\t\t}\n\n\t\tif (pending) appendFileSync(resolvedFilePath, \"\\n\");\n\t\treturn entries;\n\t}\n\n\treadHeader(path: string): SessionHeader | null {\n\t\tif (!existsSync(path)) return null;\n\t\tconst fd = openSync(path, \"r\");\n\t\ttry {\n\t\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\t\tconst buffer = Buffer.allocUnsafe(SESSION_HEADER_READ_BUFFER_SIZE);\n\t\t\tconst lineChunks: string[] = [];\n\t\t\tlet scannedBytes = 0;\n\n\t\t\twhile (scannedBytes < MAX_SESSION_HEADER_SCAN_BYTES) {\n\t\t\t\tconst readLength = Math.min(buffer.length, MAX_SESSION_HEADER_SCAN_BYTES - scannedBytes);\n\t\t\t\tconst bytesRead = readSync(fd, buffer, 0, readLength, null);\n\t\t\t\tif (bytesRead === 0) {\n\t\t\t\t\tlineChunks.push(decoder.end());\n\t\t\t\t\treturn parseSessionHeaderCandidate(lineChunks.join(\"\")) ?? null;\n\t\t\t\t}\n\t\t\t\tscannedBytes += bytesRead;\n\n\t\t\t\tconst chunk = decoder.write(buffer.subarray(0, bytesRead));\n\t\t\t\tlet lineStart = 0;\n\t\t\t\tlet newlineIndex = chunk.indexOf(\"\\n\", lineStart);\n\t\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\t\tlineChunks.push(chunk.slice(lineStart, newlineIndex));\n\t\t\t\t\tconst header = parseSessionHeaderCandidate(lineChunks.join(\"\"));\n\t\t\t\t\tif (header !== undefined) return header;\n\t\t\t\t\tlineChunks.length = 0;\n\t\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\t\tnewlineIndex = chunk.indexOf(\"\\n\", lineStart);\n\t\t\t\t}\n\t\t\t\tlineChunks.push(chunk.slice(lineStart));\n\t\t\t}\n\n\t\t\t// Probe for EOF so a final header without a newline is allowed when it ends\n\t\t\t// exactly at the scan limit. Any additional byte exceeds the bounded scan.\n\t\t\tconst probe = Buffer.allocUnsafe(1);\n\t\t\tif (readSync(fd, probe, 0, probe.length, null) === 0) {\n\t\t\t\tlineChunks.push(decoder.end());\n\t\t\t\treturn parseSessionHeaderCandidate(lineChunks.join(\"\")) ?? null;\n\t\t\t}\n\t\t\tthrow new SessionHeaderScanLimitError(path);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\trewrite(path: string, entries: readonly FileEntry[]): void {\n\t\tconst fd = openSync(path, \"w\");\n\t\ttry {\n\t\t\tfor (const entry of entries) {\n\t\t\t\twriteFileSync(fd, serializeEntryLine(entry));\n\t\t\t}\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\tcreateExclusive(path: string, entries: readonly FileEntry[]): void {\n\t\tconst fd = openSync(path, \"wx\");\n\t\ttry {\n\t\t\tfor (const entry of entries) {\n\t\t\t\twriteFileSync(fd, serializeEntryLine(entry));\n\t\t\t}\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\tappend(path: string, entry: FileEntry): void {\n\t\tappendFileSync(path, serializeEntryLine(entry));\n\t}\n\n\ttruncate(path: string, size: number): void {\n\t\ttruncateSync(path, size);\n\t}\n\n\tremove(path: string): void {\n\t\tunlinkSync(path);\n\t}\n\n\tlistSessionFiles(dir: string): string[] {\n\t\tconst resolvedDir = normalizePath(dir);\n\t\treturn readdirSync(resolvedDir)\n\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t.map((f) => join(resolvedDir, f));\n\t}\n\n\tmodifiedTime(path: string): Date {\n\t\treturn statSync(path).mtime;\n\t}\n\n\t/**\n\t * Cross-process write guard (D-067 phase 2 S5): `<sessionFile>.guard`\n\t * carries `{ writerId, hostId, pid }`. A write conflicts only while the\n\t * recorded owner is a live process on this host; a dead owner (closed or\n\t * crashed process) and same-process writers never conflict, and cross-host\n\t * guards are treated as live foreign owners (conservative over shared\n\t * filesystems). Check-and-take-ownership is atomic under the guard lock.\n\t */\n\treadonly guard: SessionWriteGuardV1 = {\n\t\tensureWritable: (sessionFile: string, writerId: string): void => {\n\t\t\tconst guardPath = `${sessionFile}${GUARD_FILE_SUFFIX}`;\n\t\t\tconst readToken = (): SessionGuardTokenV1 | undefined => {\n\t\t\t\ttry {\n\t\t\t\t\tconst value: unknown = JSON.parse(readFileSync(guardPath, \"utf8\"));\n\t\t\t\t\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) return undefined;\n\t\t\t\t\tconst record = value as { writerId?: unknown; hostId?: unknown; pid?: unknown };\n\t\t\t\t\tif (typeof record.writerId !== \"string\" || record.writerId.length === 0) return undefined;\n\t\t\t\t\tif (typeof record.hostId !== \"string\" || record.hostId.length === 0) return undefined;\n\t\t\t\t\tif (typeof record.pid !== \"number\" || !Number.isInteger(record.pid)) return undefined;\n\t\t\t\t\treturn { writerId: record.writerId, hostId: record.hostId, pid: record.pid };\n\t\t\t\t} catch {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst current = readToken();\n\t\t\t// Fast path: already ours (most appends), or no owner.\n\t\t\tif (current !== undefined && current.writerId === writerId) return;\n\t\t\tif (current !== undefined) {\n\t\t\t\tconst foreignLive =\n\t\t\t\t\tcurrent.hostId === this.hostId && current.pid !== this.ownPid && this.pidAlive(current.pid);\n\t\t\t\tif (foreignLive) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\tt(\n\t\t\t\t\t\t\t\"Session file is owned by another live process (pid {pid}): {sessionFile}. Close that process first; concurrent writers would silently fork the session history.\",\n\t\t\t\t\t\t\t{ pid: current.pid, sessionFile },\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Free, dead owner, or same-process writer: take (or refresh) ownership.\n\t\t\tconst token = acquireGuardLock(`${guardPath}.lock`, GUARD_LOCK_TIMEOUT_MS, GUARD_STALE_LOCK_MS);\n\t\t\ttry {\n\t\t\t\tconst fd = openSync(guardPath, \"w\");\n\t\t\t\ttry {\n\t\t\t\t\twriteAllSync(\n\t\t\t\t\t\tfd,\n\t\t\t\t\t\tJSON.stringify({ writerId, hostId: this.hostId, pid: this.ownPid } satisfies SessionGuardTokenV1),\n\t\t\t\t\t);\n\t\t\t\t} finally {\n\t\t\t\t\tcloseSync(fd);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\treleaseGuardLock(`${guardPath}.lock`, token);\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
|
|
@@ -26,6 +26,7 @@ import { dirname, join } from "path";
|
|
|
26
26
|
import { StringDecoder } from "string_decoder";
|
|
27
27
|
import { normalizePath } from "../utils/paths.js";
|
|
28
28
|
import { sleepSync } from "../utils/sync-sleep.js";
|
|
29
|
+
import { t } from "./i18n/index.js";
|
|
29
30
|
const SESSION_READ_BUFFER_SIZE = 1024 * 1024;
|
|
30
31
|
const SESSION_HEADER_READ_BUFFER_SIZE = 4096;
|
|
31
32
|
/** Bound synchronous header discovery while allowing large cwd and custom metadata fields. */
|
|
@@ -155,7 +156,7 @@ function acquireGuardLock(lockPath, lockTimeoutMs, staleLockMs) {
|
|
|
155
156
|
continue;
|
|
156
157
|
}
|
|
157
158
|
if (Date.now() >= deadline) {
|
|
158
|
-
throw new Error(
|
|
159
|
+
throw new Error(t("session guard lock timeout after {timeoutMs} ms: {lockPath}", { timeoutMs: lockTimeoutMs, lockPath }));
|
|
159
160
|
}
|
|
160
161
|
sleepSync(GUARD_LOCK_POLL_INTERVAL_MS);
|
|
161
162
|
}
|
|
@@ -362,8 +363,7 @@ export class FileSessionStorageProvider {
|
|
|
362
363
|
if (current !== undefined) {
|
|
363
364
|
const foreignLive = current.hostId === this.hostId && current.pid !== this.ownPid && this.pidAlive(current.pid);
|
|
364
365
|
if (foreignLive) {
|
|
365
|
-
throw new Error(
|
|
366
|
-
"Close that process first; concurrent writers would silently fork the session history.");
|
|
366
|
+
throw new Error(t("Session file is owned by another live process (pid {pid}): {sessionFile}. Close that process first; concurrent writers would silently fork the session history.", { pid: current.pid, sessionFile }));
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
369
|
// Free, dead owner, or same-process writer: take (or refresh) ownership.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-storage.js","sourceRoot":"","sources":["../../src/core/session-storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Storage seam for process sessions (D-067 phase 1).\n *\n * `SessionManager` owns the session tree, index, and context logic; a\n * `SessionStorageProvider` owns only physical record storage for one session\n * location. The default implementation is the current local JSONL file\n * protocol, byte-identical to the pre-seam behavior; alternative providers\n * (in-memory for tests, remote backends later) implement the same contract.\n *\n * Scope line: discovery/enrichment reads used by `SessionManager.list` /\n * `listAll` (async streamed `SessionInfo` construction) stay on the fs path\n * for now; they move behind this seam in a later phase.\n *\n * Cross-process write arbitration (D-067 phase 2 S5): providers may expose an\n * optional {@link SessionWriteGuardV1}. The default file provider implements\n * it as a `<sessionFile>.guard` sibling token ({ writerId, hostId, pid }): a\n * write fails explicitly while another LIVE process on the same host owns the\n * session, a dead owner (closed/crashed process) is taken over silently, and\n * same-process writers are never conflicted (in-process sequencing is the\n * caller's concern, unchanged). Cross-host guards are treated as live\n * foreign owners (conservative: no false takeover over shared filesystems).\n */\n\nimport {\n\tappendFileSync,\n\tcloseSync,\n\texistsSync,\n\tmkdirSync,\n\topenSync,\n\treaddirSync,\n\treadFileSync,\n\treadSync,\n\tstatSync,\n\ttruncateSync,\n\tunlinkSync,\n\twriteFileSync,\n\twriteSync,\n} from \"fs\";\nimport { hostname } from \"os\";\nimport { dirname, join } from \"path\";\nimport { StringDecoder } from \"string_decoder\";\nimport { normalizePath } from \"../utils/paths.ts\";\nimport { sleepSync } from \"../utils/sync-sleep.ts\";\nimport type { FileEntry, SessionHeader } from \"./session-manager.ts\";\n\nconst SESSION_READ_BUFFER_SIZE = 1024 * 1024;\nconst SESSION_HEADER_READ_BUFFER_SIZE = 4096;\n/** Bound synchronous header discovery while allowing large cwd and custom metadata fields. */\nconst MAX_SESSION_HEADER_SCAN_BYTES = 1024 * 1024;\n\n/**\n * Thrown by `readHeader` when the header scan exceeds the bounded limit.\n * `SessionManager.open` treats it as a discovery optimization miss and falls\n * back to a full load; discovery callers swallow it.\n */\nexport class SessionHeaderScanLimitError extends Error {\n\tconstructor(filePath: string) {\n\t\tsuper(`Session header exceeds ${MAX_SESSION_HEADER_SCAN_BYTES}-byte scan limit: ${filePath}`);\n\t\tthis.name = \"SessionHeaderScanLimitError\";\n\t}\n}\n\n/**\n * Record-level storage contract for sessions, keyed by location path. All\n * methods are synchronous and reflect the append-only JSONL protocol\n * semantics; implementations for other media map the same operations onto\n * their own storage model.\n */\nexport interface SessionStorageProvider {\n\t/** Ensure the storage location for a session directory exists. */\n\tensureDir(dir: string): void;\n\t/** True when the given session location exists. */\n\texists(path: string): boolean;\n\t/** Current byte size of a session location (rollback baseline). Missing locations throw. */\n\tsize(path: string): number;\n\t/**\n\t * Load all entries (header first). A missing location yields `[]`. When the\n\t * stored content ends with an unterminated final line, implementations\n\t * repair it by terminating it (the file protocol appends a `\\n`).\n\t */\n\tloadEntries(path: string): FileEntry[];\n\t/** Bounded header scan used for discovery and cwd resolution. Missing or header-less locations yield `null`. */\n\treadHeader(path: string): SessionHeader | null;\n\t/** Replace the whole stored content (header included). */\n\trewrite(path: string, entries: readonly FileEntry[]): void;\n\t/** Write initial content; fails when the location already exists (\"wx\" semantics). */\n\tcreateExclusive(path: string, entries: readonly FileEntry[]): void;\n\t/** Append one entry after the existing content. */\n\tappend(path: string, entry: FileEntry): void;\n\t/** Roll back to a previous byte size after a failed append. */\n\ttruncate(path: string, size: number): void;\n\t/** Remove a session location (used when a failed append created it). */\n\tremove(path: string): void;\n\t/** Enumerate session locations inside a directory. Missing directories throw. */\n\tlistSessionFiles(dir: string): string[];\n\t/** Modification time of a session location, used for recent-session ordering. */\n\tmodifiedTime(path: string): Date;\n\t/**\n\t * Optional cross-process write guard (D-067 phase 2 S5). When present,\n\t * `SessionManager` calls `ensureWritable` before every record write and a\n\t * conflicting live foreign owner fails the write explicitly; absent, no\n\t * arbitration happens (the pre-S5 behavior).\n\t */\n\treadonly guard?: SessionWriteGuardV1;\n}\n\n/**\n * Cross-process write guard for one session location (D-067 phase 2 S5).\n * Implementations must make \"check and take ownership\" atomic against other\n * processes, and must throw a descriptive error when another live process\n * owns the session instead of letting two writers interleave entries.\n */\nexport interface SessionWriteGuardV1 {\n\t/**\n\t * Verifies the caller may write the session and records (or refreshes) its\n\t * ownership. Throws when another live process owns the session.\n\t */\n\tensureWritable(sessionFile: string, writerId: string): void;\n}\n\nfunction parseSessionEntryLine(line: string): FileEntry | null {\n\tif (!line.trim()) return null;\n\ttry {\n\t\treturn JSON.parse(line) as FileEntry;\n\t} catch {\n\t\t// Skip malformed lines\n\t\treturn null;\n\t}\n}\n\n/** Serialize one entry as a protocol line (JSON body plus terminating newline). */\nfunction serializeEntryLine(entry: FileEntry): string {\n\treturn `${JSON.stringify(entry)}\\n`;\n}\n\nfunction parseSessionHeaderCandidate(line: string): SessionHeader | null | undefined {\n\tif (!line.trim()) return undefined;\n\tconst entry = parseSessionEntryLine(line);\n\tif (!entry) return undefined;\n\tif (entry.type !== \"session\" || typeof (entry as { id?: unknown }).id !== \"string\") return null;\n\treturn entry;\n}\n\n/** Suffix of the session write-guard sibling file (`<sessionFile>.guard`). */\nconst GUARD_FILE_SUFFIX = \".guard\";\n/** Wall-clock wait between guard-lock acquisition attempts (ledger protocol value). */\nconst GUARD_LOCK_POLL_INTERVAL_MS = 25;\n/** Default ceiling on waiting for a competing guard-lock holder. */\nconst GUARD_LOCK_TIMEOUT_MS = 5000;\n/** Default age at which a guard-lock counts as abandoned and is stolen. */\nconst GUARD_STALE_LOCK_MS = 60_000;\n\ninterface SessionGuardTokenV1 {\n\treadonly writerId: string;\n\treadonly hostId: string;\n\treadonly pid: number;\n}\n\nfunction defaultPidAlive(pid: number): boolean {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error) {\n\t\t// EPERM means the process exists but is owned by another user — alive.\n\t\treturn (error as NodeJS.ErrnoException).code === \"EPERM\";\n\t}\n}\n\n/** Writes every byte of `data` to the fd, looping over partial writeSync results. */\nfunction writeAllSync(fd: number, data: string): void {\n\tconst buffer = Buffer.from(data, \"utf8\");\n\tlet written = 0;\n\twhile (written < buffer.length) {\n\t\twritten += writeSync(fd, buffer, written, buffer.length - written);\n\t}\n}\n\n/**\n * Takes the guard's cross-process lock (same protocol as the memory ledger's\n * and the durable store's persist locks — exclusive create, poll, mtime\n * stale-lock stealing, token-guarded release; the three implementations stay\n * behaviorally identical by precedent, deliberately without module coupling).\n */\nfunction acquireGuardLock(lockPath: string, lockTimeoutMs: number, staleLockMs: number): string {\n\tconst deadline = Date.now() + lockTimeoutMs;\n\tfor (;;) {\n\t\tmkdirSync(dirname(lockPath), { recursive: true });\n\t\tconst token = `${process.pid}:${Date.now()}`;\n\t\ttry {\n\t\t\tconst fd = openSync(lockPath, \"wx\");\n\t\t\ttry {\n\t\t\t\twriteFileSync(fd, token);\n\t\t\t} finally {\n\t\t\t\tcloseSync(fd);\n\t\t\t}\n\t\t\treturn token;\n\t\t} catch (error) {\n\t\t\tconst code = (error as NodeJS.ErrnoException).code;\n\t\t\t// POSIX maps a lost create race to EEXIST; Windows/libuv maps the\n\t\t\t// same concurrent-create sharing violation to EPERM. For EPERM a\n\t\t\t// vanished lock retries, a persistent permission failure propagates.\n\t\t\tif (code === \"EPERM\") {\n\t\t\t\tlet vanished = false;\n\t\t\t\ttry {\n\t\t\t\t\tstatSync(lockPath);\n\t\t\t\t} catch (statError) {\n\t\t\t\t\tif ((statError as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n\t\t\t\t\tvanished = true;\n\t\t\t\t}\n\t\t\t\tif (vanished) {\n\t\t\t\t\tif (Date.now() >= deadline) throw error;\n\t\t\t\t\tsleepSync(GUARD_LOCK_POLL_INTERVAL_MS);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t} else if (code !== \"EEXIST\") {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tlet stale = false;\n\t\t\ttry {\n\t\t\t\tstale = Date.now() - statSync(lockPath).mtimeMs > staleLockMs;\n\t\t\t} catch {\n\t\t\t\t// The holder released between the failed open and the stat.\n\t\t\t}\n\t\t\tif (stale) {\n\t\t\t\ttry {\n\t\t\t\t\tunlinkSync(lockPath);\n\t\t\t\t} catch {\n\t\t\t\t\t// Already stolen — retry.\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (Date.now() >= deadline) {\n\t\t\t\tthrow new Error(`session guard lock timeout after ${lockTimeoutMs} ms: ${lockPath}`);\n\t\t\t}\n\t\t\tsleepSync(GUARD_LOCK_POLL_INTERVAL_MS);\n\t\t}\n\t}\n}\n\n/** Releases the guard lock, but only if our own token still owns it. */\nfunction releaseGuardLock(lockPath: string, token: string): void {\n\ttry {\n\t\tif (readFileSync(lockPath, \"utf8\") === token) unlinkSync(lockPath);\n\t} catch {\n\t\t// Already gone (or unreadable) — nothing to release.\n\t}\n}\n\n/**\n * The default provider: the local JSONL file protocol, moved verbatim from\n * `SessionManager`. Stateless beyond the injectable guard identity options,\n * so a shared module-level instance is the default for every session manager\n * that does not inject its own provider.\n */\nexport class FileSessionStorageProvider implements SessionStorageProvider {\n\t/** Shared stateless instance used as the default provider. */\n\tstatic readonly instance: FileSessionStorageProvider = new FileSessionStorageProvider();\n\n\tprivate readonly hostId: string;\n\tprivate readonly ownPid: number;\n\tprivate readonly pidAlive: (pid: number) => boolean;\n\n\tconstructor(\n\t\toptions: {\n\t\t\t/** Host identity written into guard tokens; defaults to `os.hostname()`. */\n\t\t\treadonly hostId?: string;\n\t\t\t/** Process identity written into guard tokens; defaults to `process.pid`. */\n\t\t\treadonly ownPid?: number;\n\t\t\t/** Injectable liveness check for deterministic tests; defaults to `process.kill(pid, 0)`. */\n\t\t\treadonly pidAlive?: (pid: number) => boolean;\n\t\t} = {},\n\t) {\n\t\tthis.hostId = options.hostId ?? hostname();\n\t\tthis.ownPid = options.ownPid ?? process.pid;\n\t\tthis.pidAlive = options.pidAlive ?? defaultPidAlive;\n\t}\n\n\tensureDir(dir: string): void {\n\t\tmkdirSync(dir, { recursive: true });\n\t}\n\n\texists(path: string): boolean {\n\t\treturn existsSync(path);\n\t}\n\n\tsize(path: string): number {\n\t\treturn statSync(path).size;\n\t}\n\n\tloadEntries(path: string): FileEntry[] {\n\t\tconst resolvedFilePath = normalizePath(path);\n\t\tif (!existsSync(resolvedFilePath)) return [];\n\n\t\tconst entries: FileEntry[] = [];\n\t\tlet pending = \"\";\n\t\tconst fd = openSync(resolvedFilePath, \"r\");\n\t\ttry {\n\t\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\t\tconst buffer = Buffer.allocUnsafe(SESSION_READ_BUFFER_SIZE);\n\n\t\t\twhile (true) {\n\t\t\t\tconst bytesRead = readSync(fd, buffer, 0, buffer.length, null);\n\t\t\t\tif (bytesRead === 0) break;\n\n\t\t\t\tpending += decoder.write(buffer.subarray(0, bytesRead));\n\t\t\t\tlet lineStart = 0;\n\t\t\t\tlet newlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\t\tconst entry = parseSessionEntryLine(pending.slice(lineStart, newlineIndex));\n\t\t\t\t\tif (entry) entries.push(entry);\n\t\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\t\tnewlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t\t}\n\t\t\t\tpending = pending.slice(lineStart);\n\t\t\t}\n\n\t\t\tpending += decoder.end();\n\t\t\tconst finalEntry = parseSessionEntryLine(pending);\n\t\t\tif (finalEntry) entries.push(finalEntry);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\n\t\t// Validate session header before repairing the file.\n\t\tif (entries.length === 0) return entries;\n\t\tconst header = entries[0];\n\t\tif (header.type !== \"session\" || typeof (header as { id?: unknown }).id !== \"string\") {\n\t\t\treturn [];\n\t\t}\n\n\t\tif (pending) appendFileSync(resolvedFilePath, \"\\n\");\n\t\treturn entries;\n\t}\n\n\treadHeader(path: string): SessionHeader | null {\n\t\tif (!existsSync(path)) return null;\n\t\tconst fd = openSync(path, \"r\");\n\t\ttry {\n\t\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\t\tconst buffer = Buffer.allocUnsafe(SESSION_HEADER_READ_BUFFER_SIZE);\n\t\t\tconst lineChunks: string[] = [];\n\t\t\tlet scannedBytes = 0;\n\n\t\t\twhile (scannedBytes < MAX_SESSION_HEADER_SCAN_BYTES) {\n\t\t\t\tconst readLength = Math.min(buffer.length, MAX_SESSION_HEADER_SCAN_BYTES - scannedBytes);\n\t\t\t\tconst bytesRead = readSync(fd, buffer, 0, readLength, null);\n\t\t\t\tif (bytesRead === 0) {\n\t\t\t\t\tlineChunks.push(decoder.end());\n\t\t\t\t\treturn parseSessionHeaderCandidate(lineChunks.join(\"\")) ?? null;\n\t\t\t\t}\n\t\t\t\tscannedBytes += bytesRead;\n\n\t\t\t\tconst chunk = decoder.write(buffer.subarray(0, bytesRead));\n\t\t\t\tlet lineStart = 0;\n\t\t\t\tlet newlineIndex = chunk.indexOf(\"\\n\", lineStart);\n\t\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\t\tlineChunks.push(chunk.slice(lineStart, newlineIndex));\n\t\t\t\t\tconst header = parseSessionHeaderCandidate(lineChunks.join(\"\"));\n\t\t\t\t\tif (header !== undefined) return header;\n\t\t\t\t\tlineChunks.length = 0;\n\t\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\t\tnewlineIndex = chunk.indexOf(\"\\n\", lineStart);\n\t\t\t\t}\n\t\t\t\tlineChunks.push(chunk.slice(lineStart));\n\t\t\t}\n\n\t\t\t// Probe for EOF so a final header without a newline is allowed when it ends\n\t\t\t// exactly at the scan limit. Any additional byte exceeds the bounded scan.\n\t\t\tconst probe = Buffer.allocUnsafe(1);\n\t\t\tif (readSync(fd, probe, 0, probe.length, null) === 0) {\n\t\t\t\tlineChunks.push(decoder.end());\n\t\t\t\treturn parseSessionHeaderCandidate(lineChunks.join(\"\")) ?? null;\n\t\t\t}\n\t\t\tthrow new SessionHeaderScanLimitError(path);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\trewrite(path: string, entries: readonly FileEntry[]): void {\n\t\tconst fd = openSync(path, \"w\");\n\t\ttry {\n\t\t\tfor (const entry of entries) {\n\t\t\t\twriteFileSync(fd, serializeEntryLine(entry));\n\t\t\t}\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\tcreateExclusive(path: string, entries: readonly FileEntry[]): void {\n\t\tconst fd = openSync(path, \"wx\");\n\t\ttry {\n\t\t\tfor (const entry of entries) {\n\t\t\t\twriteFileSync(fd, serializeEntryLine(entry));\n\t\t\t}\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\tappend(path: string, entry: FileEntry): void {\n\t\tappendFileSync(path, serializeEntryLine(entry));\n\t}\n\n\ttruncate(path: string, size: number): void {\n\t\ttruncateSync(path, size);\n\t}\n\n\tremove(path: string): void {\n\t\tunlinkSync(path);\n\t}\n\n\tlistSessionFiles(dir: string): string[] {\n\t\tconst resolvedDir = normalizePath(dir);\n\t\treturn readdirSync(resolvedDir)\n\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t.map((f) => join(resolvedDir, f));\n\t}\n\n\tmodifiedTime(path: string): Date {\n\t\treturn statSync(path).mtime;\n\t}\n\n\t/**\n\t * Cross-process write guard (D-067 phase 2 S5): `<sessionFile>.guard`\n\t * carries `{ writerId, hostId, pid }`. A write conflicts only while the\n\t * recorded owner is a live process on this host; a dead owner (closed or\n\t * crashed process) and same-process writers never conflict, and cross-host\n\t * guards are treated as live foreign owners (conservative over shared\n\t * filesystems). Check-and-take-ownership is atomic under the guard lock.\n\t */\n\treadonly guard: SessionWriteGuardV1 = {\n\t\tensureWritable: (sessionFile: string, writerId: string): void => {\n\t\t\tconst guardPath = `${sessionFile}${GUARD_FILE_SUFFIX}`;\n\t\t\tconst readToken = (): SessionGuardTokenV1 | undefined => {\n\t\t\t\ttry {\n\t\t\t\t\tconst value: unknown = JSON.parse(readFileSync(guardPath, \"utf8\"));\n\t\t\t\t\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) return undefined;\n\t\t\t\t\tconst record = value as { writerId?: unknown; hostId?: unknown; pid?: unknown };\n\t\t\t\t\tif (typeof record.writerId !== \"string\" || record.writerId.length === 0) return undefined;\n\t\t\t\t\tif (typeof record.hostId !== \"string\" || record.hostId.length === 0) return undefined;\n\t\t\t\t\tif (typeof record.pid !== \"number\" || !Number.isInteger(record.pid)) return undefined;\n\t\t\t\t\treturn { writerId: record.writerId, hostId: record.hostId, pid: record.pid };\n\t\t\t\t} catch {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst current = readToken();\n\t\t\t// Fast path: already ours (most appends), or no owner.\n\t\t\tif (current !== undefined && current.writerId === writerId) return;\n\t\t\tif (current !== undefined) {\n\t\t\t\tconst foreignLive =\n\t\t\t\t\tcurrent.hostId === this.hostId && current.pid !== this.ownPid && this.pidAlive(current.pid);\n\t\t\t\tif (foreignLive) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Session file is owned by another live process (pid ${current.pid}): ${sessionFile}. ` +\n\t\t\t\t\t\t\t\"Close that process first; concurrent writers would silently fork the session history.\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Free, dead owner, or same-process writer: take (or refresh) ownership.\n\t\t\tconst token = acquireGuardLock(`${guardPath}.lock`, GUARD_LOCK_TIMEOUT_MS, GUARD_STALE_LOCK_MS);\n\t\t\ttry {\n\t\t\t\tconst fd = openSync(guardPath, \"w\");\n\t\t\t\ttry {\n\t\t\t\t\twriteAllSync(\n\t\t\t\t\t\tfd,\n\t\t\t\t\t\tJSON.stringify({ writerId, hostId: this.hostId, pid: this.ownPid } satisfies SessionGuardTokenV1),\n\t\t\t\t\t);\n\t\t\t\t} finally {\n\t\t\t\t\tcloseSync(fd);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\treleaseGuardLock(`${guardPath}.lock`, token);\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session-storage.js","sourceRoot":"","sources":["../../src/core/session-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EACN,cAAc,EACd,SAAS,EACT,UAAU,EACV,SAAS,EACT,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,aAAa,EACb,SAAS,GACT,MAAM,IAAI,CAAC;AACZ,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAC9B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAC;AAGpC,MAAM,wBAAwB,GAAG,IAAI,GAAG,IAAI,CAAC;AAC7C,MAAM,+BAA+B,GAAG,IAAI,CAAC;AAC7C,8FAA8F;AAC9F,MAAM,6BAA6B,GAAG,IAAI,GAAG,IAAI,CAAC;AAElD;;;;GAIG;AACH,MAAM,OAAO,2BAA4B,SAAQ,KAAK;IACrD,YAAY,QAAgB,EAAE;QAC7B,KAAK,CAAC,0BAA0B,6BAA6B,qBAAqB,QAAQ,EAAE,CAAC,CAAC;QAC9F,IAAI,CAAC,IAAI,GAAG,6BAA6B,CAAC;IAAA,CAC1C;CACD;AA4DD,SAAS,qBAAqB,CAAC,IAAY,EAAoB;IAC9D,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IAC9B,IAAI,CAAC;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAc,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACR,uBAAuB;QACvB,OAAO,IAAI,CAAC;IACb,CAAC;AAAA,CACD;AAED,mFAAmF;AACnF,SAAS,kBAAkB,CAAC,KAAgB,EAAU;IACrD,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC;AAAA,CACpC;AAED,SAAS,2BAA2B,CAAC,IAAY,EAAoC;IACpF,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;QAAE,OAAO,SAAS,CAAC;IACnC,MAAM,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,OAAQ,KAA0B,CAAC,EAAE,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAChG,OAAO,KAAK,CAAC;AAAA,CACb;AAED,8EAA8E;AAC9E,MAAM,iBAAiB,GAAG,QAAQ,CAAC;AACnC,uFAAuF;AACvF,MAAM,2BAA2B,GAAG,EAAE,CAAC;AACvC,oEAAoE;AACpE,MAAM,qBAAqB,GAAG,IAAI,CAAC;AACnC,2EAA2E;AAC3E,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAQnC,SAAS,eAAe,CAAC,GAAW,EAAW;IAC9C,IAAI,CAAC;QACJ,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACb,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,yEAAuE;QACvE,OAAQ,KAA+B,CAAC,IAAI,KAAK,OAAO,CAAC;IAC1D,CAAC;AAAA,CACD;AAED,qFAAqF;AACrF,SAAS,YAAY,CAAC,EAAU,EAAE,IAAY,EAAQ;IACrD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACzC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,OAAO,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;QAChC,OAAO,IAAI,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC;IACpE,CAAC;AAAA,CACD;AAED;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,QAAgB,EAAE,aAAqB,EAAE,WAAmB,EAAU;IAC/F,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,aAAa,CAAC;IAC5C,SAAS,CAAC;QACT,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,MAAM,KAAK,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QAC7C,IAAI,CAAC;YACJ,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YACpC,IAAI,CAAC;gBACJ,aAAa,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;YAC1B,CAAC;oBAAS,CAAC;gBACV,SAAS,CAAC,EAAE,CAAC,CAAC;YACf,CAAC;YACD,OAAO,KAAK,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,IAAI,GAAI,KAA+B,CAAC,IAAI,CAAC;YACnD,kEAAkE;YAClE,iEAAiE;YACjE,qEAAqE;YACrE,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBACtB,IAAI,QAAQ,GAAG,KAAK,CAAC;gBACrB,IAAI,CAAC;oBACJ,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBACpB,CAAC;gBAAC,OAAO,SAAS,EAAE,CAAC;oBACpB,IAAK,SAAmC,CAAC,IAAI,KAAK,QAAQ;wBAAE,MAAM,KAAK,CAAC;oBACxE,QAAQ,GAAG,IAAI,CAAC;gBACjB,CAAC;gBACD,IAAI,QAAQ,EAAE,CAAC;oBACd,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ;wBAAE,MAAM,KAAK,CAAC;oBACxC,SAAS,CAAC,2BAA2B,CAAC,CAAC;oBACvC,SAAS;gBACV,CAAC;YACF,CAAC;iBAAM,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC9B,MAAM,KAAK,CAAC;YACb,CAAC;YACD,IAAI,KAAK,GAAG,KAAK,CAAC;YAClB,IAAI,CAAC;gBACJ,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,GAAG,WAAW,CAAC;YAC/D,CAAC;YAAC,MAAM,CAAC;gBACR,4DAA4D;YAC7D,CAAC;YACD,IAAI,KAAK,EAAE,CAAC;gBACX,IAAI,CAAC;oBACJ,UAAU,CAAC,QAAQ,CAAC,CAAC;gBACtB,CAAC;gBAAC,MAAM,CAAC;oBACR,4BAA0B;gBAC3B,CAAC;gBACD,SAAS;YACV,CAAC;YACD,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CACd,CAAC,CAAC,6DAA6D,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,CACxG,CAAC;YACH,CAAC;YACD,SAAS,CAAC,2BAA2B,CAAC,CAAC;QACxC,CAAC;IACF,CAAC;AAAA,CACD;AAED,wEAAwE;AACxE,SAAS,gBAAgB,CAAC,QAAgB,EAAE,KAAa,EAAQ;IAChE,IAAI,CAAC;QACJ,IAAI,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,KAAK;YAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;IACpE,CAAC;IAAC,MAAM,CAAC;QACR,uDAAqD;IACtD,CAAC;AAAA,CACD;AAED;;;;;GAKG;AACH,MAAM,OAAO,0BAA0B;IACtC,8DAA8D;IAC9D,MAAM,CAAU,QAAQ,GAA+B,IAAI,0BAA0B,EAAE,CAAC;IAEvE,MAAM,CAAS;IACf,MAAM,CAAS;IACf,QAAQ,CAA2B;IAEpD,YACC,OAAO,GAOH,EAAE,EACL;QACD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC3C,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC;QAC5C,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,eAAe,CAAC;IAAA,CACpD;IAED,SAAS,CAAC,GAAW,EAAQ;QAC5B,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAAA,CACpC;IAED,MAAM,CAAC,IAAY,EAAW;QAC7B,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;IAAA,CACxB;IAED,IAAI,CAAC,IAAY,EAAU;QAC1B,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC;IAAA,CAC3B;IAED,WAAW,CAAC,IAAY,EAAe;QACtC,MAAM,gBAAgB,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC;YAAE,OAAO,EAAE,CAAC;QAE7C,MAAM,OAAO,GAAgB,EAAE,CAAC;QAChC,IAAI,OAAO,GAAG,EAAE,CAAC;QACjB,MAAM,EAAE,GAAG,QAAQ,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;QAC3C,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC;YAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,wBAAwB,CAAC,CAAC;YAE5D,OAAO,IAAI,EAAE,CAAC;gBACb,MAAM,SAAS,GAAG,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBAC/D,IAAI,SAAS,KAAK,CAAC;oBAAE,MAAM;gBAE3B,OAAO,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;gBACxD,IAAI,SAAS,GAAG,CAAC,CAAC;gBAClB,IAAI,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;gBACpD,OAAO,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;oBAC5B,MAAM,KAAK,GAAG,qBAAqB,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;oBAC5E,IAAI,KAAK;wBAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;oBAC/B,SAAS,GAAG,YAAY,GAAG,CAAC,CAAC;oBAC7B,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;gBACjD,CAAC;gBACD,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YACpC,CAAC;YAED,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;YACzB,MAAM,UAAU,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;YAClD,IAAI,UAAU;gBAAE,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC1C,CAAC;gBAAS,CAAC;YACV,SAAS,CAAC,EAAE,CAAC,CAAC;QACf,CAAC;QAED,qDAAqD;QACrD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,OAAO,CAAC;QACzC,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QAC1B,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,OAAQ,MAA2B,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;YACtF,OAAO,EAAE,CAAC;QACX,CAAC;QAED,IAAI,OAAO;YAAE,cAAc,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;QACpD,OAAO,OAAO,CAAC;IAAA,CACf;IAED,UAAU,CAAC,IAAY,EAAwB;QAC9C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QACnC,MAAM,EAAE,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC/B,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC;YAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,+BAA+B,CAAC,CAAC;YACnE,MAAM,UAAU,GAAa,EAAE,CAAC;YAChC,IAAI,YAAY,GAAG,CAAC,CAAC;YAErB,OAAO,YAAY,GAAG,6BAA6B,EAAE,CAAC;gBACrD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,6BAA6B,GAAG,YAAY,CAAC,CAAC;gBACzF,MAAM,SAAS,GAAG,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;gBAC5D,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;oBACrB,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;oBAC/B,OAAO,2BAA2B,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC;gBACjE,CAAC;gBACD,YAAY,IAAI,SAAS,CAAC;gBAE1B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;gBAC3D,IAAI,SAAS,GAAG,CAAC,CAAC;gBAClB,IAAI,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;gBAClD,OAAO,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;oBAC5B,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;oBACtD,MAAM,MAAM,GAAG,2BAA2B,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;oBAChE,IAAI,MAAM,KAAK,SAAS;wBAAE,OAAO,MAAM,CAAC;oBACxC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;oBACtB,SAAS,GAAG,YAAY,GAAG,CAAC,CAAC;oBAC7B,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;gBAC/C,CAAC;gBACD,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;YACzC,CAAC;YAED,4EAA4E;YAC5E,2EAA2E;YAC3E,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;YACpC,IAAI,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gBACtD,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;gBAC/B,OAAO,2BAA2B,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC;YACjE,CAAC;YACD,MAAM,IAAI,2BAA2B,CAAC,IAAI,CAAC,CAAC;QAC7C,CAAC;gBAAS,CAAC;YACV,SAAS,CAAC,EAAE,CAAC,CAAC;QACf,CAAC;IAAA,CACD;IAED,OAAO,CAAC,IAAY,EAAE,OAA6B,EAAQ;QAC1D,MAAM,EAAE,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC/B,IAAI,CAAC;YACJ,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC7B,aAAa,CAAC,EAAE,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;YAC9C,CAAC;QACF,CAAC;gBAAS,CAAC;YACV,SAAS,CAAC,EAAE,CAAC,CAAC;QACf,CAAC;IAAA,CACD;IAED,eAAe,CAAC,IAAY,EAAE,OAA6B,EAAQ;QAClE,MAAM,EAAE,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAChC,IAAI,CAAC;YACJ,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC7B,aAAa,CAAC,EAAE,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;YAC9C,CAAC;QACF,CAAC;gBAAS,CAAC;YACV,SAAS,CAAC,EAAE,CAAC,CAAC;QACf,CAAC;IAAA,CACD;IAED,MAAM,CAAC,IAAY,EAAE,KAAgB,EAAQ;QAC5C,cAAc,CAAC,IAAI,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;IAAA,CAChD;IAED,QAAQ,CAAC,IAAY,EAAE,IAAY,EAAQ;QAC1C,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAAA,CACzB;IAED,MAAM,CAAC,IAAY,EAAQ;QAC1B,UAAU,CAAC,IAAI,CAAC,CAAC;IAAA,CACjB;IAED,gBAAgB,CAAC,GAAW,EAAY;QACvC,MAAM,WAAW,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;QACvC,OAAO,WAAW,CAAC,WAAW,CAAC;aAC7B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;aACnC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC;IAAA,CACnC;IAED,YAAY,CAAC,IAAY,EAAQ;QAChC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;IAAA,CAC5B;IAED;;;;;;;OAOG;IACM,KAAK,GAAwB;QACrC,cAAc,EAAE,CAAC,WAAmB,EAAE,QAAgB,EAAQ,EAAE,CAAC;YAChE,MAAM,SAAS,GAAG,GAAG,WAAW,GAAG,iBAAiB,EAAE,CAAC;YACvD,MAAM,SAAS,GAAG,GAAoC,EAAE,CAAC;gBACxD,IAAI,CAAC;oBACJ,MAAM,KAAK,GAAY,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;oBACnE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;wBAAE,OAAO,SAAS,CAAC;oBAC1F,MAAM,MAAM,GAAG,KAAgE,CAAC;oBAChF,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,SAAS,CAAC;oBAC1F,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO,SAAS,CAAC;oBACtF,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC;wBAAE,OAAO,SAAS,CAAC;oBACtF,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC;gBAC9E,CAAC;gBAAC,MAAM,CAAC;oBACR,OAAO,SAAS,CAAC;gBAClB,CAAC;YAAA,CACD,CAAC;YAEF,MAAM,OAAO,GAAG,SAAS,EAAE,CAAC;YAC5B,uDAAuD;YACvD,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ;gBAAE,OAAO;YACnE,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC3B,MAAM,WAAW,GAChB,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,KAAK,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBAC7F,IAAI,WAAW,EAAE,CAAC;oBACjB,MAAM,IAAI,KAAK,CACd,CAAC,CACA,iKAAiK,EACjK,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,WAAW,EAAE,CACjC,CACD,CAAC;gBACH,CAAC;YACF,CAAC;YACD,yEAAyE;YACzE,MAAM,KAAK,GAAG,gBAAgB,CAAC,GAAG,SAAS,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,CAAC,CAAC;YAChG,IAAI,CAAC;gBACJ,MAAM,EAAE,GAAG,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;gBACpC,IAAI,CAAC;oBACJ,YAAY,CACX,EAAE,EACF,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,MAAM,EAAgC,CAAC,CACjG,CAAC;gBACH,CAAC;wBAAS,CAAC;oBACV,SAAS,CAAC,EAAE,CAAC,CAAC;gBACf,CAAC;YACF,CAAC;oBAAS,CAAC;gBACV,gBAAgB,CAAC,GAAG,SAAS,OAAO,EAAE,KAAK,CAAC,CAAC;YAC9C,CAAC;QAAA,CACD;KACD,CAAC;CACF","sourcesContent":["/**\n * Storage seam for process sessions (D-067 phase 1).\n *\n * `SessionManager` owns the session tree, index, and context logic; a\n * `SessionStorageProvider` owns only physical record storage for one session\n * location. The default implementation is the current local JSONL file\n * protocol, byte-identical to the pre-seam behavior; alternative providers\n * (in-memory for tests, remote backends later) implement the same contract.\n *\n * Scope line: discovery/enrichment reads used by `SessionManager.list` /\n * `listAll` (async streamed `SessionInfo` construction) stay on the fs path\n * for now; they move behind this seam in a later phase.\n *\n * Cross-process write arbitration (D-067 phase 2 S5): providers may expose an\n * optional {@link SessionWriteGuardV1}. The default file provider implements\n * it as a `<sessionFile>.guard` sibling token ({ writerId, hostId, pid }): a\n * write fails explicitly while another LIVE process on the same host owns the\n * session, a dead owner (closed/crashed process) is taken over silently, and\n * same-process writers are never conflicted (in-process sequencing is the\n * caller's concern, unchanged). Cross-host guards are treated as live\n * foreign owners (conservative: no false takeover over shared filesystems).\n */\n\nimport {\n\tappendFileSync,\n\tcloseSync,\n\texistsSync,\n\tmkdirSync,\n\topenSync,\n\treaddirSync,\n\treadFileSync,\n\treadSync,\n\tstatSync,\n\ttruncateSync,\n\tunlinkSync,\n\twriteFileSync,\n\twriteSync,\n} from \"fs\";\nimport { hostname } from \"os\";\nimport { dirname, join } from \"path\";\nimport { StringDecoder } from \"string_decoder\";\nimport { normalizePath } from \"../utils/paths.ts\";\nimport { sleepSync } from \"../utils/sync-sleep.ts\";\nimport { t } from \"./i18n/index.ts\";\nimport type { FileEntry, SessionHeader } from \"./session-manager.ts\";\n\nconst SESSION_READ_BUFFER_SIZE = 1024 * 1024;\nconst SESSION_HEADER_READ_BUFFER_SIZE = 4096;\n/** Bound synchronous header discovery while allowing large cwd and custom metadata fields. */\nconst MAX_SESSION_HEADER_SCAN_BYTES = 1024 * 1024;\n\n/**\n * Thrown by `readHeader` when the header scan exceeds the bounded limit.\n * `SessionManager.open` treats it as a discovery optimization miss and falls\n * back to a full load; discovery callers swallow it.\n */\nexport class SessionHeaderScanLimitError extends Error {\n\tconstructor(filePath: string) {\n\t\tsuper(`Session header exceeds ${MAX_SESSION_HEADER_SCAN_BYTES}-byte scan limit: ${filePath}`);\n\t\tthis.name = \"SessionHeaderScanLimitError\";\n\t}\n}\n\n/**\n * Record-level storage contract for sessions, keyed by location path. All\n * methods are synchronous and reflect the append-only JSONL protocol\n * semantics; implementations for other media map the same operations onto\n * their own storage model.\n */\nexport interface SessionStorageProvider {\n\t/** Ensure the storage location for a session directory exists. */\n\tensureDir(dir: string): void;\n\t/** True when the given session location exists. */\n\texists(path: string): boolean;\n\t/** Current byte size of a session location (rollback baseline). Missing locations throw. */\n\tsize(path: string): number;\n\t/**\n\t * Load all entries (header first). A missing location yields `[]`. When the\n\t * stored content ends with an unterminated final line, implementations\n\t * repair it by terminating it (the file protocol appends a `\\n`).\n\t */\n\tloadEntries(path: string): FileEntry[];\n\t/** Bounded header scan used for discovery and cwd resolution. Missing or header-less locations yield `null`. */\n\treadHeader(path: string): SessionHeader | null;\n\t/** Replace the whole stored content (header included). */\n\trewrite(path: string, entries: readonly FileEntry[]): void;\n\t/** Write initial content; fails when the location already exists (\"wx\" semantics). */\n\tcreateExclusive(path: string, entries: readonly FileEntry[]): void;\n\t/** Append one entry after the existing content. */\n\tappend(path: string, entry: FileEntry): void;\n\t/** Roll back to a previous byte size after a failed append. */\n\ttruncate(path: string, size: number): void;\n\t/** Remove a session location (used when a failed append created it). */\n\tremove(path: string): void;\n\t/** Enumerate session locations inside a directory. Missing directories throw. */\n\tlistSessionFiles(dir: string): string[];\n\t/** Modification time of a session location, used for recent-session ordering. */\n\tmodifiedTime(path: string): Date;\n\t/**\n\t * Optional cross-process write guard (D-067 phase 2 S5). When present,\n\t * `SessionManager` calls `ensureWritable` before every record write and a\n\t * conflicting live foreign owner fails the write explicitly; absent, no\n\t * arbitration happens (the pre-S5 behavior).\n\t */\n\treadonly guard?: SessionWriteGuardV1;\n}\n\n/**\n * Cross-process write guard for one session location (D-067 phase 2 S5).\n * Implementations must make \"check and take ownership\" atomic against other\n * processes, and must throw a descriptive error when another live process\n * owns the session instead of letting two writers interleave entries.\n */\nexport interface SessionWriteGuardV1 {\n\t/**\n\t * Verifies the caller may write the session and records (or refreshes) its\n\t * ownership. Throws when another live process owns the session.\n\t */\n\tensureWritable(sessionFile: string, writerId: string): void;\n}\n\nfunction parseSessionEntryLine(line: string): FileEntry | null {\n\tif (!line.trim()) return null;\n\ttry {\n\t\treturn JSON.parse(line) as FileEntry;\n\t} catch {\n\t\t// Skip malformed lines\n\t\treturn null;\n\t}\n}\n\n/** Serialize one entry as a protocol line (JSON body plus terminating newline). */\nfunction serializeEntryLine(entry: FileEntry): string {\n\treturn `${JSON.stringify(entry)}\\n`;\n}\n\nfunction parseSessionHeaderCandidate(line: string): SessionHeader | null | undefined {\n\tif (!line.trim()) return undefined;\n\tconst entry = parseSessionEntryLine(line);\n\tif (!entry) return undefined;\n\tif (entry.type !== \"session\" || typeof (entry as { id?: unknown }).id !== \"string\") return null;\n\treturn entry;\n}\n\n/** Suffix of the session write-guard sibling file (`<sessionFile>.guard`). */\nconst GUARD_FILE_SUFFIX = \".guard\";\n/** Wall-clock wait between guard-lock acquisition attempts (ledger protocol value). */\nconst GUARD_LOCK_POLL_INTERVAL_MS = 25;\n/** Default ceiling on waiting for a competing guard-lock holder. */\nconst GUARD_LOCK_TIMEOUT_MS = 5000;\n/** Default age at which a guard-lock counts as abandoned and is stolen. */\nconst GUARD_STALE_LOCK_MS = 60_000;\n\ninterface SessionGuardTokenV1 {\n\treadonly writerId: string;\n\treadonly hostId: string;\n\treadonly pid: number;\n}\n\nfunction defaultPidAlive(pid: number): boolean {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error) {\n\t\t// EPERM means the process exists but is owned by another user — alive.\n\t\treturn (error as NodeJS.ErrnoException).code === \"EPERM\";\n\t}\n}\n\n/** Writes every byte of `data` to the fd, looping over partial writeSync results. */\nfunction writeAllSync(fd: number, data: string): void {\n\tconst buffer = Buffer.from(data, \"utf8\");\n\tlet written = 0;\n\twhile (written < buffer.length) {\n\t\twritten += writeSync(fd, buffer, written, buffer.length - written);\n\t}\n}\n\n/**\n * Takes the guard's cross-process lock (same protocol as the memory ledger's\n * and the durable store's persist locks — exclusive create, poll, mtime\n * stale-lock stealing, token-guarded release; the three implementations stay\n * behaviorally identical by precedent, deliberately without module coupling).\n */\nfunction acquireGuardLock(lockPath: string, lockTimeoutMs: number, staleLockMs: number): string {\n\tconst deadline = Date.now() + lockTimeoutMs;\n\tfor (;;) {\n\t\tmkdirSync(dirname(lockPath), { recursive: true });\n\t\tconst token = `${process.pid}:${Date.now()}`;\n\t\ttry {\n\t\t\tconst fd = openSync(lockPath, \"wx\");\n\t\t\ttry {\n\t\t\t\twriteFileSync(fd, token);\n\t\t\t} finally {\n\t\t\t\tcloseSync(fd);\n\t\t\t}\n\t\t\treturn token;\n\t\t} catch (error) {\n\t\t\tconst code = (error as NodeJS.ErrnoException).code;\n\t\t\t// POSIX maps a lost create race to EEXIST; Windows/libuv maps the\n\t\t\t// same concurrent-create sharing violation to EPERM. For EPERM a\n\t\t\t// vanished lock retries, a persistent permission failure propagates.\n\t\t\tif (code === \"EPERM\") {\n\t\t\t\tlet vanished = false;\n\t\t\t\ttry {\n\t\t\t\t\tstatSync(lockPath);\n\t\t\t\t} catch (statError) {\n\t\t\t\t\tif ((statError as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n\t\t\t\t\tvanished = true;\n\t\t\t\t}\n\t\t\t\tif (vanished) {\n\t\t\t\t\tif (Date.now() >= deadline) throw error;\n\t\t\t\t\tsleepSync(GUARD_LOCK_POLL_INTERVAL_MS);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t} else if (code !== \"EEXIST\") {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tlet stale = false;\n\t\t\ttry {\n\t\t\t\tstale = Date.now() - statSync(lockPath).mtimeMs > staleLockMs;\n\t\t\t} catch {\n\t\t\t\t// The holder released between the failed open and the stat.\n\t\t\t}\n\t\t\tif (stale) {\n\t\t\t\ttry {\n\t\t\t\t\tunlinkSync(lockPath);\n\t\t\t\t} catch {\n\t\t\t\t\t// Already stolen — retry.\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (Date.now() >= deadline) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\tt(\"session guard lock timeout after {timeoutMs} ms: {lockPath}\", { timeoutMs: lockTimeoutMs, lockPath }),\n\t\t\t\t);\n\t\t\t}\n\t\t\tsleepSync(GUARD_LOCK_POLL_INTERVAL_MS);\n\t\t}\n\t}\n}\n\n/** Releases the guard lock, but only if our own token still owns it. */\nfunction releaseGuardLock(lockPath: string, token: string): void {\n\ttry {\n\t\tif (readFileSync(lockPath, \"utf8\") === token) unlinkSync(lockPath);\n\t} catch {\n\t\t// Already gone (or unreadable) — nothing to release.\n\t}\n}\n\n/**\n * The default provider: the local JSONL file protocol, moved verbatim from\n * `SessionManager`. Stateless beyond the injectable guard identity options,\n * so a shared module-level instance is the default for every session manager\n * that does not inject its own provider.\n */\nexport class FileSessionStorageProvider implements SessionStorageProvider {\n\t/** Shared stateless instance used as the default provider. */\n\tstatic readonly instance: FileSessionStorageProvider = new FileSessionStorageProvider();\n\n\tprivate readonly hostId: string;\n\tprivate readonly ownPid: number;\n\tprivate readonly pidAlive: (pid: number) => boolean;\n\n\tconstructor(\n\t\toptions: {\n\t\t\t/** Host identity written into guard tokens; defaults to `os.hostname()`. */\n\t\t\treadonly hostId?: string;\n\t\t\t/** Process identity written into guard tokens; defaults to `process.pid`. */\n\t\t\treadonly ownPid?: number;\n\t\t\t/** Injectable liveness check for deterministic tests; defaults to `process.kill(pid, 0)`. */\n\t\t\treadonly pidAlive?: (pid: number) => boolean;\n\t\t} = {},\n\t) {\n\t\tthis.hostId = options.hostId ?? hostname();\n\t\tthis.ownPid = options.ownPid ?? process.pid;\n\t\tthis.pidAlive = options.pidAlive ?? defaultPidAlive;\n\t}\n\n\tensureDir(dir: string): void {\n\t\tmkdirSync(dir, { recursive: true });\n\t}\n\n\texists(path: string): boolean {\n\t\treturn existsSync(path);\n\t}\n\n\tsize(path: string): number {\n\t\treturn statSync(path).size;\n\t}\n\n\tloadEntries(path: string): FileEntry[] {\n\t\tconst resolvedFilePath = normalizePath(path);\n\t\tif (!existsSync(resolvedFilePath)) return [];\n\n\t\tconst entries: FileEntry[] = [];\n\t\tlet pending = \"\";\n\t\tconst fd = openSync(resolvedFilePath, \"r\");\n\t\ttry {\n\t\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\t\tconst buffer = Buffer.allocUnsafe(SESSION_READ_BUFFER_SIZE);\n\n\t\t\twhile (true) {\n\t\t\t\tconst bytesRead = readSync(fd, buffer, 0, buffer.length, null);\n\t\t\t\tif (bytesRead === 0) break;\n\n\t\t\t\tpending += decoder.write(buffer.subarray(0, bytesRead));\n\t\t\t\tlet lineStart = 0;\n\t\t\t\tlet newlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\t\tconst entry = parseSessionEntryLine(pending.slice(lineStart, newlineIndex));\n\t\t\t\t\tif (entry) entries.push(entry);\n\t\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\t\tnewlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t\t}\n\t\t\t\tpending = pending.slice(lineStart);\n\t\t\t}\n\n\t\t\tpending += decoder.end();\n\t\t\tconst finalEntry = parseSessionEntryLine(pending);\n\t\t\tif (finalEntry) entries.push(finalEntry);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\n\t\t// Validate session header before repairing the file.\n\t\tif (entries.length === 0) return entries;\n\t\tconst header = entries[0];\n\t\tif (header.type !== \"session\" || typeof (header as { id?: unknown }).id !== \"string\") {\n\t\t\treturn [];\n\t\t}\n\n\t\tif (pending) appendFileSync(resolvedFilePath, \"\\n\");\n\t\treturn entries;\n\t}\n\n\treadHeader(path: string): SessionHeader | null {\n\t\tif (!existsSync(path)) return null;\n\t\tconst fd = openSync(path, \"r\");\n\t\ttry {\n\t\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\t\tconst buffer = Buffer.allocUnsafe(SESSION_HEADER_READ_BUFFER_SIZE);\n\t\t\tconst lineChunks: string[] = [];\n\t\t\tlet scannedBytes = 0;\n\n\t\t\twhile (scannedBytes < MAX_SESSION_HEADER_SCAN_BYTES) {\n\t\t\t\tconst readLength = Math.min(buffer.length, MAX_SESSION_HEADER_SCAN_BYTES - scannedBytes);\n\t\t\t\tconst bytesRead = readSync(fd, buffer, 0, readLength, null);\n\t\t\t\tif (bytesRead === 0) {\n\t\t\t\t\tlineChunks.push(decoder.end());\n\t\t\t\t\treturn parseSessionHeaderCandidate(lineChunks.join(\"\")) ?? null;\n\t\t\t\t}\n\t\t\t\tscannedBytes += bytesRead;\n\n\t\t\t\tconst chunk = decoder.write(buffer.subarray(0, bytesRead));\n\t\t\t\tlet lineStart = 0;\n\t\t\t\tlet newlineIndex = chunk.indexOf(\"\\n\", lineStart);\n\t\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\t\tlineChunks.push(chunk.slice(lineStart, newlineIndex));\n\t\t\t\t\tconst header = parseSessionHeaderCandidate(lineChunks.join(\"\"));\n\t\t\t\t\tif (header !== undefined) return header;\n\t\t\t\t\tlineChunks.length = 0;\n\t\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\t\tnewlineIndex = chunk.indexOf(\"\\n\", lineStart);\n\t\t\t\t}\n\t\t\t\tlineChunks.push(chunk.slice(lineStart));\n\t\t\t}\n\n\t\t\t// Probe for EOF so a final header without a newline is allowed when it ends\n\t\t\t// exactly at the scan limit. Any additional byte exceeds the bounded scan.\n\t\t\tconst probe = Buffer.allocUnsafe(1);\n\t\t\tif (readSync(fd, probe, 0, probe.length, null) === 0) {\n\t\t\t\tlineChunks.push(decoder.end());\n\t\t\t\treturn parseSessionHeaderCandidate(lineChunks.join(\"\")) ?? null;\n\t\t\t}\n\t\t\tthrow new SessionHeaderScanLimitError(path);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\trewrite(path: string, entries: readonly FileEntry[]): void {\n\t\tconst fd = openSync(path, \"w\");\n\t\ttry {\n\t\t\tfor (const entry of entries) {\n\t\t\t\twriteFileSync(fd, serializeEntryLine(entry));\n\t\t\t}\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\tcreateExclusive(path: string, entries: readonly FileEntry[]): void {\n\t\tconst fd = openSync(path, \"wx\");\n\t\ttry {\n\t\t\tfor (const entry of entries) {\n\t\t\t\twriteFileSync(fd, serializeEntryLine(entry));\n\t\t\t}\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\tappend(path: string, entry: FileEntry): void {\n\t\tappendFileSync(path, serializeEntryLine(entry));\n\t}\n\n\ttruncate(path: string, size: number): void {\n\t\ttruncateSync(path, size);\n\t}\n\n\tremove(path: string): void {\n\t\tunlinkSync(path);\n\t}\n\n\tlistSessionFiles(dir: string): string[] {\n\t\tconst resolvedDir = normalizePath(dir);\n\t\treturn readdirSync(resolvedDir)\n\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t.map((f) => join(resolvedDir, f));\n\t}\n\n\tmodifiedTime(path: string): Date {\n\t\treturn statSync(path).mtime;\n\t}\n\n\t/**\n\t * Cross-process write guard (D-067 phase 2 S5): `<sessionFile>.guard`\n\t * carries `{ writerId, hostId, pid }`. A write conflicts only while the\n\t * recorded owner is a live process on this host; a dead owner (closed or\n\t * crashed process) and same-process writers never conflict, and cross-host\n\t * guards are treated as live foreign owners (conservative over shared\n\t * filesystems). Check-and-take-ownership is atomic under the guard lock.\n\t */\n\treadonly guard: SessionWriteGuardV1 = {\n\t\tensureWritable: (sessionFile: string, writerId: string): void => {\n\t\t\tconst guardPath = `${sessionFile}${GUARD_FILE_SUFFIX}`;\n\t\t\tconst readToken = (): SessionGuardTokenV1 | undefined => {\n\t\t\t\ttry {\n\t\t\t\t\tconst value: unknown = JSON.parse(readFileSync(guardPath, \"utf8\"));\n\t\t\t\t\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) return undefined;\n\t\t\t\t\tconst record = value as { writerId?: unknown; hostId?: unknown; pid?: unknown };\n\t\t\t\t\tif (typeof record.writerId !== \"string\" || record.writerId.length === 0) return undefined;\n\t\t\t\t\tif (typeof record.hostId !== \"string\" || record.hostId.length === 0) return undefined;\n\t\t\t\t\tif (typeof record.pid !== \"number\" || !Number.isInteger(record.pid)) return undefined;\n\t\t\t\t\treturn { writerId: record.writerId, hostId: record.hostId, pid: record.pid };\n\t\t\t\t} catch {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst current = readToken();\n\t\t\t// Fast path: already ours (most appends), or no owner.\n\t\t\tif (current !== undefined && current.writerId === writerId) return;\n\t\t\tif (current !== undefined) {\n\t\t\t\tconst foreignLive =\n\t\t\t\t\tcurrent.hostId === this.hostId && current.pid !== this.ownPid && this.pidAlive(current.pid);\n\t\t\t\tif (foreignLive) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\tt(\n\t\t\t\t\t\t\t\"Session file is owned by another live process (pid {pid}): {sessionFile}. Close that process first; concurrent writers would silently fork the session history.\",\n\t\t\t\t\t\t\t{ pid: current.pid, sessionFile },\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Free, dead owner, or same-process writer: take (or refresh) ownership.\n\t\t\tconst token = acquireGuardLock(`${guardPath}.lock`, GUARD_LOCK_TIMEOUT_MS, GUARD_STALE_LOCK_MS);\n\t\t\ttry {\n\t\t\t\tconst fd = openSync(guardPath, \"w\");\n\t\t\t\ttry {\n\t\t\t\t\twriteAllSync(\n\t\t\t\t\t\tfd,\n\t\t\t\t\t\tJSON.stringify({ writerId, hostId: this.hostId, pid: this.ownPid } satisfies SessionGuardTokenV1),\n\t\t\t\t\t);\n\t\t\t\t} finally {\n\t\t\t\t\tcloseSync(fd);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\treleaseGuardLock(`${guardPath}.lock`, token);\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@agent-forge/agent-core";
|
|
2
2
|
import { type Model, type Transport } from "@agent-forge/ai";
|
|
3
3
|
import type { TuiMode as RendererTuiMode, ScrollViewScrollbar, TerminalCapabilities, WheelScrollLines } from "@agent-forge/tui";
|
|
4
|
+
import { type I18nLanguage } from "./i18n/index.ts";
|
|
4
5
|
export interface CompactionModelOverride {
|
|
5
6
|
reserveTokens?: number;
|
|
6
7
|
keepRecentTokens?: number;
|
|
7
8
|
}
|
|
8
9
|
export interface CompactionSettings {
|
|
9
|
-
enabled?: boolean;
|
|
10
10
|
reserveTokens?: number;
|
|
11
11
|
keepRecentTokens?: number;
|
|
12
12
|
modelOverrides?: Record<string, CompactionModelOverride>;
|
|
13
|
-
microcompactEnabled?: boolean;
|
|
14
|
-
summarySelfCritique?: boolean;
|
|
15
13
|
}
|
|
16
14
|
export interface BranchSummarySettings {
|
|
17
15
|
reserveTokens?: number;
|
|
@@ -23,7 +21,6 @@ export interface ProviderRetrySettings {
|
|
|
23
21
|
maxRetryDelayMs?: number;
|
|
24
22
|
}
|
|
25
23
|
export interface RetrySettings {
|
|
26
|
-
enabled?: boolean;
|
|
27
24
|
maxRetries?: number;
|
|
28
25
|
baseDelayMs?: number;
|
|
29
26
|
maxAgentDelayMs?: number;
|
|
@@ -31,6 +28,13 @@ export interface RetrySettings {
|
|
|
31
28
|
}
|
|
32
29
|
export type TuiMode = RendererTuiMode;
|
|
33
30
|
export type FullscreenExitOutput = "transcript" | "resume-hint";
|
|
31
|
+
/**
|
|
32
|
+
* 界面语言(docs/design/国际化与本地化.md):`ui.language` 持久配置面。
|
|
33
|
+
* env `AGENT_FORGE_I18N_LANG` 是更高优先的临时覆盖。
|
|
34
|
+
*/
|
|
35
|
+
export interface UiSettings {
|
|
36
|
+
language?: I18nLanguage;
|
|
37
|
+
}
|
|
34
38
|
export interface TerminalSettings {
|
|
35
39
|
showImages?: boolean;
|
|
36
40
|
imageWidthCells?: number;
|
|
@@ -95,7 +99,6 @@ export interface FailoverChainSettings {
|
|
|
95
99
|
* Phase 2 的 provider-failover 插件的事,本层不实现、不校验、不消费。
|
|
96
100
|
*/
|
|
97
101
|
export interface FailoverSettings {
|
|
98
|
-
enabled?: boolean;
|
|
99
102
|
backupProviders?: string[];
|
|
100
103
|
failureThreshold?: number;
|
|
101
104
|
cooldown?: FailoverCooldownSettings;
|
|
@@ -131,6 +134,7 @@ export interface Settings {
|
|
|
131
134
|
steeringMode?: "all" | "one-at-a-time";
|
|
132
135
|
followUpMode?: "all" | "one-at-a-time";
|
|
133
136
|
theme?: string;
|
|
137
|
+
ui?: UiSettings;
|
|
134
138
|
compaction?: CompactionSettings;
|
|
135
139
|
branchSummary?: BranchSummarySettings;
|
|
136
140
|
retry?: RetrySettings;
|
|
@@ -148,13 +152,12 @@ export interface Settings {
|
|
|
148
152
|
skills?: string[];
|
|
149
153
|
prompts?: string[];
|
|
150
154
|
themes?: string[];
|
|
151
|
-
enableSkillCommands?: boolean;
|
|
152
155
|
terminal?: TerminalSettings;
|
|
153
156
|
images?: ImageSettings;
|
|
154
157
|
enabledModels?: string[];
|
|
155
158
|
defaultTools?: string[];
|
|
156
159
|
suite?: string;
|
|
157
|
-
doubleEscapeAction?: "
|
|
160
|
+
doubleEscapeAction?: "tree" | "none";
|
|
158
161
|
treeFilterMode?: "default" | "no-tools" | "user-only" | "labeled-only" | "all";
|
|
159
162
|
thinkingBudgets?: ThinkingBudgetsSettings;
|
|
160
163
|
editorPaddingX?: number;
|
|
@@ -302,28 +305,25 @@ export declare class SettingsManager {
|
|
|
302
305
|
removeModelThinkingLevel(provider: string, modelId: string): void;
|
|
303
306
|
getTransport(): TransportSetting;
|
|
304
307
|
setTransport(transport: TransportSetting): void;
|
|
305
|
-
getCompactionEnabled(): boolean;
|
|
306
|
-
setCompactionEnabled(enabled: boolean): void;
|
|
307
308
|
private getCompactionTokenSetting;
|
|
308
309
|
getCompactionReserveTokens(model?: Pick<Model<string>, "provider" | "id">): number;
|
|
309
310
|
getCompactionKeepRecentTokens(model?: Pick<Model<string>, "provider" | "id">): number;
|
|
310
|
-
getSummarySelfCritique(): boolean;
|
|
311
|
-
getMicrocompactEnabled(): boolean;
|
|
312
311
|
/** Resolve each token setting through model override, ordinary setting, then built-in default. */
|
|
313
312
|
getCompactionSettings(model?: Pick<Model<string>, "provider" | "id">): {
|
|
314
|
-
enabled: boolean;
|
|
315
313
|
reserveTokens: number;
|
|
316
314
|
keepRecentTokens: number;
|
|
317
|
-
microcompactEnabled: boolean;
|
|
318
|
-
summarySelfCritique: boolean;
|
|
319
315
|
};
|
|
320
316
|
getBranchSummarySettings(): {
|
|
321
317
|
reserveTokens: number;
|
|
322
318
|
skipPrompt: boolean;
|
|
323
319
|
};
|
|
324
320
|
getBranchSummarySkipPrompt(): boolean;
|
|
325
|
-
|
|
326
|
-
|
|
321
|
+
/**
|
|
322
|
+
* 聚合 retry 数值面。返回体带常量 `enabled: true`:pi-ai `RetryPolicy`
|
|
323
|
+
* (packages/ai/src/utils/retry.ts)的 `enabled` 是 required 字段,库层形状
|
|
324
|
+
* 不动;产品侧自动重试无条件生效(2026-10-03 D-079,settings
|
|
325
|
+
* `retry.enabled` 开关已移除),残留旧键按 settings 层宽松契约静默忽略。
|
|
326
|
+
*/
|
|
327
327
|
getRetrySettings(): {
|
|
328
328
|
enabled: boolean;
|
|
329
329
|
maxRetries: number;
|
|
@@ -373,8 +373,6 @@ export declare class SettingsManager {
|
|
|
373
373
|
setProjectSuite(id: string | undefined): void;
|
|
374
374
|
/** Global-scope active suite (方案) id; undefined clears the key (设计 §9). */
|
|
375
375
|
setSuite(id: string | undefined): void;
|
|
376
|
-
getEnableSkillCommands(): boolean;
|
|
377
|
-
setEnableSkillCommands(enabled: boolean): void;
|
|
378
376
|
getThinkingBudgets(): ThinkingBudgetsSettings | undefined;
|
|
379
377
|
getTerminalCapabilityOverrides(): Partial<TerminalCapabilities>;
|
|
380
378
|
getShowImages(): boolean;
|
|
@@ -403,8 +401,10 @@ export declare class SettingsManager {
|
|
|
403
401
|
/** The resolved `defaultTools` selection, or undefined when no settings layer sets it. */
|
|
404
402
|
getDefaultTools(): string[] | undefined;
|
|
405
403
|
setEnabledModels(patterns: string[] | undefined): void;
|
|
406
|
-
getDoubleEscapeAction(): "
|
|
407
|
-
|
|
404
|
+
getDoubleEscapeAction(): "tree" | "none";
|
|
405
|
+
getUiLanguage(): I18nLanguage;
|
|
406
|
+
setUiLanguage(language: I18nLanguage): void;
|
|
407
|
+
setDoubleEscapeAction(action: "tree" | "none"): void;
|
|
408
408
|
getTreeFilterMode(): "default" | "no-tools" | "user-only" | "labeled-only" | "all";
|
|
409
409
|
setTreeFilterMode(mode: "default" | "no-tools" | "user-only" | "labeled-only" | "all"): void;
|
|
410
410
|
getShowHardwareCursor(): boolean;
|