@gajae-code/coding-agent 0.13.3 → 0.14.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 +184 -2
- package/README.md +2 -0
- package/bin/gajaessi.js +4 -0
- package/dist/types/async/job-manager.d.ts +55 -2
- package/dist/types/async/support.d.ts +14 -0
- package/dist/types/capability/mcp.d.ts +6 -0
- package/dist/types/capability/types.d.ts +6 -0
- package/dist/types/cli/accounts-cli.d.ts +19 -0
- package/dist/types/cli/args.d.ts +3 -0
- package/dist/types/cli/config-cli.d.ts +6 -0
- package/dist/types/cli/crash-cli.d.ts +10 -0
- package/dist/types/cli/customize-doctor.d.ts +145 -0
- package/dist/types/cli/daemon-cli.d.ts +3 -1
- package/dist/types/cli/mcp-cli.d.ts +11 -0
- package/dist/types/cli/notify-cli.d.ts +1 -1
- package/dist/types/cli/root-flags.d.ts +12 -0
- package/dist/types/cli/setup-cli.d.ts +3 -1
- package/dist/types/cli/skills-cli.d.ts +2 -1
- package/dist/types/cli/update-cli.d.ts +19 -1
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/commands/accounts.d.ts +40 -0
- package/dist/types/commands/crash.d.ts +24 -0
- package/dist/types/commands/customize.d.ts +22 -0
- package/dist/types/commands/daemon.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/commands/setup.d.ts +6 -0
- package/dist/types/commands/skills.d.ts +6 -1
- package/dist/types/commands/update.d.ts +7 -0
- package/dist/types/config/atomic-yaml-patch-worker.d.ts +25 -0
- package/dist/types/config/atomic-yaml-patch.d.ts +62 -2
- package/dist/types/config/config-schema-version.d.ts +10 -0
- package/dist/types/config/file-lock.d.ts +18 -0
- package/dist/types/config/model-profile-activation.d.ts +3 -1
- package/dist/types/config/model-registry.d.ts +9 -1
- package/dist/types/config/models-config-schema.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +70 -6
- package/dist/types/config/settings.d.ts +63 -0
- package/dist/types/config/skill-settings-defaults.d.ts +26 -0
- package/dist/types/config/task-delegation.d.ts +39 -0
- package/dist/types/config/update-channel.d.ts +10 -0
- package/dist/types/coordinator-mcp/server.d.ts +5 -1
- package/dist/types/crash/index-store.d.ts +87 -0
- package/dist/types/crash/nudge.d.ts +50 -0
- package/dist/types/crash/record-loader.d.ts +17 -0
- package/dist/types/crash/report.d.ts +134 -0
- package/dist/types/crash/sanitize.d.ts +25 -0
- package/dist/types/cursor.d.ts +13 -2
- package/dist/types/customization/import.d.ts +37 -0
- package/dist/types/customization/index.d.ts +4 -0
- package/dist/types/customization/inventory.d.ts +25 -0
- package/dist/types/customization/mutations.d.ts +32 -0
- package/dist/types/customization/types.d.ts +115 -0
- package/dist/types/daemon/control-types.d.ts +8 -1
- package/dist/types/daemon/operator-contract.d.ts +4 -4
- package/dist/types/discovery/claude.d.ts +39 -1
- package/dist/types/discovery/codex.d.ts +39 -1
- package/dist/types/discovery/index.d.ts +2 -0
- package/dist/types/discovery/mcp-compat.d.ts +30 -0
- package/dist/types/edit/renderer.d.ts +23 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +21 -4
- package/dist/types/extensibility/gjc-plugins/observability.d.ts +6 -1
- package/dist/types/extensibility/gjc-plugins/registry.d.ts +8 -1
- package/dist/types/extensibility/hooks/loader.d.ts +19 -5
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +22 -1
- package/dist/types/extensibility/skill-management.d.ts +107 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +3 -1
- package/dist/types/gjc-runtime/launch-worktree.d.ts +8 -0
- package/dist/types/gjc-runtime/linux-proc.d.ts +3 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +10 -6
- package/dist/types/gjc-runtime/session-state-lock.d.ts +104 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +120 -2
- package/dist/types/gjc-runtime/state-writer.d.ts +10 -1
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +4 -2
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -6
- package/dist/types/gjc-runtime/workflow-settings.d.ts +109 -0
- package/dist/types/hooks/events.d.ts +60 -0
- package/dist/types/hooks/normalize.d.ts +83 -0
- package/dist/types/hooks/skill-state.d.ts +9 -2
- package/dist/types/main.d.ts +1 -1
- package/dist/types/modes/acp/abort-scope.d.ts +13 -0
- package/dist/types/modes/acp/acp-agent.d.ts +7 -4
- package/dist/types/modes/components/customization/customization-dashboard.d.ts +44 -0
- package/dist/types/modes/components/customization/import-wizard.d.ts +21 -0
- package/dist/types/modes/components/customization/index.d.ts +1 -0
- package/dist/types/modes/components/gajae-pet-widget.d.ts +8 -30
- package/dist/types/modes/components/irc-sidebar.d.ts +1 -0
- package/dist/types/modes/components/iterm-pet-transport.d.ts +122 -0
- package/dist/types/modes/components/oauth-selector.d.ts +10 -4
- package/dist/types/modes/components/pet-capability.d.ts +11 -25
- package/dist/types/modes/components/status-line/gh.d.ts +6 -2
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +7 -8
- package/dist/types/modes/controllers/stt-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/injected-user-submission.d.ts +10 -0
- package/dist/types/runtime-mcp/config.d.ts +9 -0
- package/dist/types/runtime-mcp/manager.d.ts +26 -1
- package/dist/types/runtime-mcp/oauth-discovery.d.ts +4 -0
- package/dist/types/runtime-mcp/oauth-flow.d.ts +24 -1
- package/dist/types/runtime-mcp/pool-key.d.ts +2 -0
- package/dist/types/runtime-mcp/protocol.d.ts +278 -0
- package/dist/types/runtime-mcp/scope-config.d.ts +29 -0
- package/dist/types/runtime-mcp/tool-bridge.d.ts +15 -13
- package/dist/types/runtime-mcp/transports/http.d.ts +9 -0
- package/dist/types/runtime-mcp/types.d.ts +102 -10
- package/dist/types/sdk/acp/adapter.d.ts +26 -5
- package/dist/types/sdk/broker/broker.d.ts +3 -55
- package/dist/types/sdk/broker/lifecycle.d.ts +18 -12
- package/dist/types/sdk/broker/session-index.d.ts +14 -5
- package/dist/types/sdk/broker/startup-failure.d.ts +16 -1
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +51 -1
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -41
- package/dist/types/sdk/bus/config.d.ts +11 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +0 -1
- package/dist/types/sdk/bus/daemon-paths.d.ts +6 -0
- package/dist/types/sdk/bus/discord-conversation.d.ts +2 -0
- package/dist/types/sdk/bus/discord-daemon.d.ts +17 -12
- package/dist/types/sdk/bus/existing-thread-readiness.d.ts +4 -34
- package/dist/types/sdk/bus/inbound-reaction-ordering.d.ts +61 -0
- package/dist/types/sdk/bus/index.d.ts +85 -99
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +29 -7
- package/dist/types/sdk/bus/managed-daemon.d.ts +2 -32
- package/dist/types/sdk/bus/notification-service.d.ts +52 -0
- package/dist/types/sdk/bus/operator-runtime.d.ts +1 -1
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +16 -5
- package/dist/types/sdk/bus/reconciliation-store.d.ts +113 -5
- package/dist/types/sdk/bus/session-control.d.ts +25 -1
- package/dist/types/sdk/bus/slack-conversation.d.ts +4 -0
- package/dist/types/sdk/bus/slack-daemon.d.ts +9 -17
- package/dist/types/sdk/bus/slack-live-provider.d.ts +3 -1
- package/dist/types/sdk/bus/slack-provider.d.ts +4 -0
- package/dist/types/sdk/bus/slack-thread-binding.d.ts +6 -40
- package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +30 -104
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +10 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +262 -19
- package/dist/types/sdk/bus/telegram-daemon-orphan-reap.d.ts +125 -0
- package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +22 -0
- package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -310
- package/dist/types/sdk/bus/telegram-reference.d.ts +1 -47
- package/dist/types/sdk/bus/threaded-render.d.ts +1 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +33 -17
- package/dist/types/sdk/cli/rows.d.ts +2 -11
- package/dist/types/sdk/cli/session-cli.d.ts +15 -25
- package/dist/types/sdk/client/client.d.ts +97 -1
- package/dist/types/sdk/host/control/dispatch.d.ts +0 -7
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +18 -5
- package/dist/types/sdk/host/control/runtime-gate.d.ts +19 -0
- package/dist/types/sdk/host/host.d.ts +15 -1
- package/dist/types/sdk/host/query/handlers.d.ts +5 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
- package/dist/types/sdk/host/session-runtime.d.ts +127 -7
- package/dist/types/sdk/host/types.d.ts +1 -1
- package/dist/types/sdk/host/websocket-transport.d.ts +1 -1
- package/dist/types/sdk/index.d.ts +2 -1
- package/dist/types/sdk/lifecycle/broker-client.d.ts +8 -0
- package/dist/types/sdk/lifecycle/client.d.ts +59 -0
- package/dist/types/sdk/lifecycle/index.d.ts +3 -0
- package/dist/types/sdk/lifecycle/service.d.ts +232 -0
- package/dist/types/sdk/mcp/server.d.ts +9 -6
- package/dist/types/sdk/prompt-deadline-lease.d.ts +43 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +28 -0
- package/dist/types/sdk/prompt-status.d.ts +10 -3
- package/dist/types/sdk/protocol/operation-registry.d.ts +5 -0
- package/dist/types/sdk/receipt-state.d.ts +17 -0
- package/dist/types/sdk/reconciliation-extensions.d.ts +2 -0
- package/dist/types/sdk/router/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +152 -0
- package/dist/types/sdk/session-list.d.ts +22 -0
- package/dist/types/sdk/session-reconnect.d.ts +43 -1
- package/dist/types/sdk/session.d.ts +34 -1
- package/dist/types/sdk/transport/serve-cli.d.ts +1 -4
- package/dist/types/session/account-inventory.d.ts +86 -0
- package/dist/types/session/agent-session.d.ts +145 -6
- package/dist/types/session/agent-storage.d.ts +10 -0
- package/dist/types/session/coordinator-tool-label.d.ts +28 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +23 -4
- package/dist/types/session/messages.d.ts +1 -1
- package/dist/types/session/secure-token-file.d.ts +3 -0
- package/dist/types/session/session-manager.d.ts +40 -1
- package/dist/types/session/startup-auth-config.d.ts +44 -0
- package/dist/types/session/terminal-abort.d.ts +378 -0
- package/dist/types/session/yield-queue.d.ts +7 -0
- package/dist/types/session-import/claude.d.ts +26 -0
- package/dist/types/session-import/codex.d.ts +88 -0
- package/dist/types/session-import/command.d.ts +35 -0
- package/dist/types/session-import/detect.d.ts +22 -0
- package/dist/types/session-import/index.d.ts +7 -0
- package/dist/types/session-import/provider-codex.d.ts +22 -0
- package/dist/types/session-import/provider-service.d.ts +38 -0
- package/dist/types/session-import/redact.d.ts +23 -0
- package/dist/types/session-import/service.d.ts +47 -0
- package/dist/types/session-import/types.d.ts +120 -0
- package/dist/types/setup/paseo/check.d.ts +16 -0
- package/dist/types/setup/paseo/install-saga.d.ts +115 -0
- package/dist/types/setup/paseo/json-publisher.d.ts +76 -0
- package/dist/types/setup/paseo/orchestration-preferences.d.ts +24 -0
- package/dist/types/setup/paseo/paseo-ownership.d.ts +87 -0
- package/dist/types/setup/paseo/paseo-setup.d.ts +29 -0
- package/dist/types/setup/paseo/provider-config.d.ts +35 -0
- package/dist/types/setup/paseo/remove.d.ts +14 -0
- package/dist/types/setup/paseo/result-types.d.ts +59 -0
- package/dist/types/setup/paseo/setup-deps.d.ts +73 -0
- package/dist/types/setup/paseo/skills-bridge.d.ts +44 -0
- package/dist/types/skill-state/active-state.d.ts +5 -1
- package/dist/types/slash-commands/acp-builtins.d.ts +5 -1
- package/dist/types/slash-commands/builtin-registry.d.ts +2 -0
- package/dist/types/slash-commands/helpers/credential-switch.d.ts +12 -0
- package/dist/types/slash-commands/helpers/usage-report.d.ts +5 -6
- package/dist/types/slash-commands/types.d.ts +7 -1
- package/dist/types/task/executor.d.ts +10 -0
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/tools/ask-answer-registry.d.ts +6 -3
- package/dist/types/tools/bash-sleep-advisory.d.ts +14 -0
- package/dist/types/tools/bash.d.ts +2 -6
- package/dist/types/tools/browser/attach.d.ts +11 -0
- package/dist/types/tools/browser/launch.d.ts +20 -0
- package/dist/types/tools/browser/profile-discovery.d.ts +6 -0
- package/dist/types/tools/browser/render.d.ts +1 -0
- package/dist/types/tools/browser.d.ts +3 -1
- package/dist/types/tools/image-gen.d.ts +9 -2
- package/dist/types/tools/index.d.ts +8 -0
- package/dist/types/tools/monitor.d.ts +1 -1
- package/dist/types/tools/skill-discovery.d.ts +8 -1
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +2 -2
- package/dist/types/utils/herdr-pane.d.ts +76 -0
- package/package.json +19 -9
- package/scripts/build-binary.ts +2 -1
- package/scripts/build-sdk-package-smoke.ts +2 -13
- package/scripts/capture-sticky-viewport-showcase.ts +0 -1
- package/scripts/compile-args.ts +2 -0
- package/scripts/generate-sdk-adapter-parity-manifest.ts +31 -16
- package/scripts/generate-sdk-operation-inventory.ts +28 -0
- package/scripts/generate-telegram-baseline-manifest.ts +1 -0
- package/scripts/generate-tool-catalog.ts +6 -4
- package/scripts/verify-gjc-sdk-canonicalization.ts +5 -39
- package/src/async/job-manager.ts +352 -17
- package/src/async/support.ts +25 -0
- package/src/autoresearch/dashboard.ts +28 -13
- package/src/capability/index.ts +3 -1
- package/src/capability/mcp.ts +6 -0
- package/src/capability/types.ts +6 -0
- package/src/cli/accounts-cli.ts +395 -0
- package/src/cli/args.ts +23 -0
- package/src/cli/auth-broker-cli.ts +136 -66
- package/src/cli/auth-gateway-cli.ts +94 -70
- package/src/cli/config-cli.ts +27 -2
- package/src/cli/crash-cli.ts +78 -0
- package/src/cli/customize-doctor.ts +1605 -0
- package/src/cli/daemon-cli.ts +12 -3
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/mcp-cli.ts +128 -60
- package/src/cli/notify-cli.ts +8 -14
- package/src/cli/root-flags.ts +8 -0
- package/src/cli/setup-cli.ts +145 -0
- package/src/cli/skills-cli.ts +38 -2
- package/src/cli/update-cli.ts +167 -12
- package/src/cli.ts +4 -0
- package/src/commands/accounts.ts +71 -0
- package/src/commands/crash.ts +35 -0
- package/src/commands/customize.ts +84 -0
- package/src/commands/daemon.ts +2 -2
- package/src/commands/deep-interview.ts +2 -0
- package/src/commands/launch.ts +1 -0
- package/src/commands/mcp.ts +12 -7
- package/src/commands/ralplan.ts +2 -0
- package/src/commands/sdk.ts +65 -32
- package/src/commands/setup.ts +5 -0
- package/src/commands/skills.ts +14 -4
- package/src/commands/ultragoal.ts +10 -1
- package/src/commands/update.ts +26 -15
- package/src/config/atomic-yaml-patch-worker.ts +57 -0
- package/src/config/atomic-yaml-patch.ts +769 -29
- package/src/config/config-schema-version.ts +10 -0
- package/src/config/file-lock.ts +70 -1
- package/src/config/mcp-schema.json +52 -14
- package/src/config/model-profile-activation.ts +20 -1
- package/src/config/model-profiles.ts +29 -19
- package/src/config/model-registry.ts +132 -21
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +76 -6
- package/src/config/settings.ts +4038 -199
- package/src/config/skill-settings-defaults.ts +41 -3
- package/src/config/task-delegation.ts +87 -0
- package/src/config/update-channel.ts +23 -0
- package/src/coordinator-mcp/server.ts +377 -308
- package/src/crash/index-store.ts +723 -0
- package/src/crash/nudge.ts +89 -0
- package/src/crash/record-loader.ts +149 -0
- package/src/crash/report.ts +551 -0
- package/src/crash/sanitize.ts +129 -0
- package/src/cursor.ts +36 -10
- package/src/customization/import.ts +946 -0
- package/src/customization/index.ts +4 -0
- package/src/customization/inventory.ts +335 -0
- package/src/customization/mutations.ts +141 -0
- package/src/customization/types.ts +223 -0
- package/src/daemon/control-types.ts +8 -1
- package/src/daemon/operator-contract.ts +6 -6
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +11 -22
- package/src/defaults/gjc/skills/ralplan/SKILL.md +36 -23
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +16 -0
- package/src/discovery/claude-hooks.ts +38 -0
- package/src/discovery/claude.ts +142 -209
- package/src/discovery/codex-hooks.ts +38 -0
- package/src/discovery/codex.ts +132 -198
- package/src/discovery/helpers.ts +8 -1
- package/src/discovery/index.ts +2 -0
- package/src/discovery/mcp-compat.ts +234 -0
- package/src/discovery/mcp-json.ts +6 -0
- package/src/edit/renderer.ts +40 -0
- package/src/edit/streaming.ts +4 -2
- package/src/exec/bash-executor.ts +24 -20
- package/src/export/html/index.ts +51 -2
- package/src/extensibility/extensions/loader.ts +9 -1
- package/src/extensibility/extensions/runner.ts +17 -4
- package/src/extensibility/extensions/types.ts +31 -6
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +33 -0
- package/src/extensibility/gjc-plugins/installer.ts +9 -5
- package/src/extensibility/gjc-plugins/observability.ts +8 -2
- package/src/extensibility/gjc-plugins/registry.ts +13 -2
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +3 -1
- package/src/extensibility/gjc-plugins/schema.ts +222 -39
- package/src/extensibility/hooks/loader.ts +154 -10
- package/src/extensibility/runtime-skill-discovery.ts +257 -60
- package/src/extensibility/skill-management.ts +368 -0
- package/src/extensibility/skills.ts +52 -16
- package/src/gjc-runtime/deep-interview-runtime.ts +45 -61
- package/src/gjc-runtime/launch-worktree.ts +56 -2
- package/src/gjc-runtime/linux-proc.ts +7 -1
- package/src/gjc-runtime/ralplan-runtime.ts +100 -164
- package/src/gjc-runtime/session-state-lock.ts +992 -0
- package/src/gjc-runtime/session-state-sidecar.ts +644 -142
- package/src/gjc-runtime/state-writer.ts +39 -14
- package/src/gjc-runtime/tmux-owner-isolation.ts +5 -4
- package/src/gjc-runtime/ultragoal-guard.ts +11 -4
- package/src/gjc-runtime/ultragoal-runtime.ts +44 -41
- package/src/gjc-runtime/workflow-settings.ts +596 -0
- package/src/goals/tools/goal-tool.ts +6 -1
- package/src/hooks/events.ts +291 -0
- package/src/hooks/native-skill-hook.ts +4 -0
- package/src/hooks/normalize.ts +369 -0
- package/src/hooks/skill-state.ts +165 -46
- package/src/internal-urls/docs-index.generated.ts +40 -32
- package/src/main.ts +32 -5
- package/src/migrate/mcp-mapper.ts +25 -2
- package/src/modes/acp/abort-scope.ts +29 -0
- package/src/modes/acp/acp-agent.ts +423 -98
- package/src/modes/components/assistant-message.ts +6 -1
- package/src/modes/components/customization/customization-dashboard.ts +443 -0
- package/src/modes/components/customization/import-wizard.ts +372 -0
- package/src/modes/components/customization/index.ts +1 -0
- package/src/modes/components/gajae-pet-widget.ts +539 -213
- package/src/modes/components/irc-sidebar.ts +11 -0
- package/src/modes/components/iterm-pet-transport.ts +585 -0
- package/src/modes/components/oauth-selector.ts +277 -86
- package/src/modes/components/pet-capability.ts +39 -40
- package/src/modes/components/settings-selector.ts +9 -0
- package/src/modes/components/status-line/gh.ts +45 -1
- package/src/modes/components/status-line/segments.ts +16 -1
- package/src/modes/components/tool-status-header.ts +13 -3
- package/src/modes/controllers/command-controller.ts +20 -8
- package/src/modes/controllers/extension-ui-controller.ts +56 -257
- package/src/modes/controllers/input-controller.ts +10 -0
- package/src/modes/controllers/runtime-mcp-command-controller.ts +16 -3
- package/src/modes/controllers/selector-controller.ts +177 -72
- package/src/modes/controllers/stt-controller.ts +3 -3
- package/src/modes/interactive-mode.ts +88 -16
- package/src/modes/print-mode.ts +35 -6
- package/src/modes/runtime-init.ts +21 -68
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/injected-user-submission.ts +25 -0
- package/src/modes/utils/terminal-bell.ts +15 -1
- package/src/prompts/agent-fragments/ralplan-persistence.md +1 -1
- package/src/prompts/agent-fragments/restricted-bash.md +1 -1
- package/src/prompts/agents/critic.md +1 -1
- package/src/prompts/system/rlm-research.md +1 -1
- package/src/prompts/system/system-prompt.md +0 -2
- package/src/prompts/tools/bash.md +7 -1
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/cron.md +6 -6
- package/src/prompts/tools/read.md +0 -2
- package/src/prompts/tools/skill-discovery.md +4 -2
- package/src/runtime-mcp/client.ts +427 -23
- package/src/runtime-mcp/config.ts +37 -5
- package/src/runtime-mcp/manager-pool.test.ts +56 -0
- package/src/runtime-mcp/manager.ts +52 -2
- package/src/runtime-mcp/oauth-discovery.ts +13 -0
- package/src/runtime-mcp/oauth-flow.ts +46 -1
- package/src/runtime-mcp/pool-key.ts +5 -1
- package/src/runtime-mcp/pool.test.ts +9 -0
- package/src/runtime-mcp/protocol.test.ts +353 -0
- package/src/runtime-mcp/protocol.ts +624 -0
- package/src/runtime-mcp/scope-config.ts +78 -0
- package/src/runtime-mcp/tool-bridge.ts +44 -10
- package/src/runtime-mcp/transports/http.ts +84 -13
- package/src/runtime-mcp/types.ts +123 -12
- package/src/sdk/acp/adapter.ts +328 -97
- package/src/sdk/broker/broker.ts +160 -741
- package/src/sdk/broker/ensure.ts +9 -1
- package/src/sdk/broker/lifecycle.ts +510 -116
- package/src/sdk/broker/operation-receipt-ledger.ts +11 -2
- package/src/sdk/broker/process-incarnation.ts +19 -0
- package/src/sdk/broker/session-index.ts +516 -117
- package/src/sdk/broker/startup-failure.ts +28 -5
- package/src/sdk/broker/transport.ts +1 -13
- package/src/sdk/bus/chat-daemon-cli.ts +4 -2
- package/src/sdk/bus/chat-daemon-control.ts +67 -6
- package/src/sdk/bus/chat-daemon-runtime.ts +183 -859
- package/src/sdk/bus/config.ts +13 -0
- package/src/sdk/bus/conversation-store.ts +59 -171
- package/src/sdk/bus/daemon-paths.ts +20 -0
- package/src/sdk/bus/discord-conversation.ts +2 -0
- package/src/sdk/bus/discord-daemon.ts +524 -126
- package/src/sdk/bus/discord-live-provider.ts +206 -79
- package/src/sdk/bus/existing-thread-readiness.ts +4 -97
- package/src/sdk/bus/inbound-reaction-ordering.ts +122 -0
- package/src/sdk/bus/index.ts +2480 -456
- package/src/sdk/bus/kind-aware-reconciliation.ts +204 -34
- package/src/sdk/bus/lifecycle-commands.ts +2 -2
- package/src/sdk/bus/managed-daemon.ts +2 -145
- package/src/sdk/bus/notification-service.ts +196 -0
- package/src/sdk/bus/operator-runtime.ts +1 -1
- package/src/sdk/bus/prompt-reconciliation.ts +24 -15
- package/src/sdk/bus/reconciliation-store.ts +553 -24
- package/src/sdk/bus/session-control.ts +49 -3
- package/src/sdk/bus/slack-conversation.ts +4 -0
- package/src/sdk/bus/slack-daemon.ts +733 -134
- package/src/sdk/bus/slack-live-provider.ts +65 -15
- package/src/sdk/bus/slack-provider.ts +4 -0
- package/src/sdk/bus/slack-thread-binding.ts +9 -104
- package/src/sdk/bus/telegram-adoption-intent.ts +299 -209
- package/src/sdk/bus/telegram-daemon-cli.ts +194 -7
- package/src/sdk/bus/telegram-daemon-contract.ts +265 -19
- package/src/sdk/bus/telegram-daemon-control.ts +3 -13
- package/src/sdk/bus/telegram-daemon-orphan-reap.ts +498 -0
- package/src/sdk/bus/telegram-daemon-owner-registry.ts +176 -0
- package/src/sdk/bus/telegram-daemon.ts +2552 -2280
- package/src/sdk/bus/telegram-reference.ts +1 -226
- package/src/sdk/bus/telegram-setup.ts +2 -2
- package/src/sdk/bus/threaded-render.ts +1 -0
- package/src/sdk/bus/topic-registry.ts +279 -146
- package/src/sdk/cli/rows.ts +2 -35
- package/src/sdk/cli/session-cli.ts +776 -700
- package/src/sdk/client/client.ts +892 -1
- package/src/sdk/host/control/dispatch.ts +93 -58
- package/src/sdk/host/control/index.ts +1 -1
- package/src/sdk/host/control/operations.ts +18 -10
- package/src/sdk/host/control/runtime-gate.ts +40 -0
- package/src/sdk/host/host.ts +63 -12
- package/src/sdk/host/query/handlers.ts +66 -7
- package/src/sdk/host/reverse-leases.ts +2 -2
- package/src/sdk/host/sdk-surface-parity.test.ts +1 -1
- package/src/sdk/host/session-runtime.test.ts +2787 -34
- package/src/sdk/host/session-runtime.ts +1489 -101
- package/src/sdk/host/surface-policy.ts +12 -8
- package/src/sdk/host/types.ts +1 -1
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +31 -59
- package/src/sdk/host/websocket-transport.ts +85 -50
- package/src/sdk/index.ts +2 -1
- package/src/sdk/lifecycle/broker-client.ts +53 -0
- package/src/sdk/lifecycle/client.ts +234 -0
- package/src/sdk/lifecycle/index.ts +3 -0
- package/src/sdk/{bus/recent-activity.ts → lifecycle/recent-sessions.ts} +5 -2
- package/src/sdk/lifecycle/service.ts +720 -0
- package/src/sdk/mcp/server.ts +226 -188
- package/src/sdk/prompt-deadline-lease.ts +55 -0
- package/src/sdk/prompt-deadline-manager.ts +148 -0
- package/src/sdk/prompt-status.ts +11 -3
- package/src/sdk/protocol/operation-inventory.generated.json +272 -129
- package/src/sdk/protocol/operation-registry.ts +71 -8
- package/src/sdk/receipt-state.ts +43 -0
- package/src/sdk/reconciliation-extensions.ts +14 -0
- package/src/sdk/router/index.ts +1 -0
- package/src/sdk/router/session-router.ts +1658 -0
- package/src/sdk/session-list.ts +93 -0
- package/src/sdk/session-reconnect.ts +45 -1
- package/src/sdk/session.ts +716 -120
- package/src/sdk/transport/serve-cli.ts +18 -34
- package/src/session/account-inventory.ts +603 -0
- package/src/session/agent-session.ts +2857 -582
- package/src/session/agent-storage.ts +35 -0
- package/src/session/blob-store.ts +32 -1
- package/src/session/coordinator-tool-label.ts +40 -0
- package/src/session/internal/managed-session-scope.ts +4 -0
- package/src/session/internal/managed-session-storage.ts +173 -12
- package/src/session/messages.ts +1 -1
- package/src/session/secure-token-file.ts +80 -0
- package/src/session/session-manager.ts +730 -58
- package/src/session/session-storage.ts +25 -16
- package/src/session/startup-auth-config.ts +297 -0
- package/src/session/terminal-abort.ts +1253 -0
- package/src/session/yield-queue.ts +57 -15
- package/src/session-import/claude.ts +334 -0
- package/src/session-import/codex.ts +1033 -0
- package/src/session-import/command.ts +77 -0
- package/src/session-import/detect.ts +133 -0
- package/src/session-import/index.ts +7 -0
- package/src/session-import/provider-codex.ts +327 -0
- package/src/session-import/provider-service.ts +457 -0
- package/src/session-import/redact.ts +128 -0
- package/src/session-import/service.ts +1264 -0
- package/src/session-import/types.ts +148 -0
- package/src/setup/paseo/check.ts +213 -0
- package/src/setup/paseo/install-saga.ts +293 -0
- package/src/setup/paseo/json-publisher.ts +263 -0
- package/src/setup/paseo/orchestration-preferences.ts +86 -0
- package/src/setup/paseo/paseo-ownership.ts +224 -0
- package/src/setup/paseo/paseo-setup.ts +253 -0
- package/src/setup/paseo/provider-config.ts +139 -0
- package/src/setup/paseo/remove.ts +183 -0
- package/src/setup/paseo/result-types.ts +79 -0
- package/src/setup/paseo/setup-deps.ts +175 -0
- package/src/setup/paseo/skills-bridge.ts +260 -0
- package/src/skill-state/active-state.ts +21 -11
- package/src/slash-commands/acp-builtins.ts +9 -1
- package/src/slash-commands/builtin-registry.ts +98 -16
- package/src/slash-commands/helpers/credential-switch.ts +143 -0
- package/src/slash-commands/helpers/mcp.ts +9 -3
- package/src/slash-commands/helpers/usage-report.ts +61 -56
- package/src/slash-commands/types.ts +7 -2
- package/src/task/executor.ts +30 -11
- package/src/task/index.ts +66 -7
- package/src/task/render.ts +23 -9
- package/src/tools/ask-answer-registry.ts +23 -7
- package/src/tools/ask.ts +8 -4
- package/src/tools/bash-sleep-advisory.ts +115 -0
- package/src/tools/bash.ts +98 -18
- package/src/tools/browser/attach.ts +71 -3
- package/src/tools/browser/launch.ts +86 -13
- package/src/tools/browser/profile-discovery.ts +57 -7
- package/src/tools/browser/render.ts +8 -1
- package/src/tools/browser.ts +130 -21
- package/src/tools/computer.enforcement.test.ts +1 -1
- package/src/tools/computer.ts +3 -2
- package/src/tools/cron.ts +12 -1
- package/src/tools/image-gen.ts +27 -16
- package/src/tools/index.ts +8 -0
- package/src/tools/job.ts +33 -6
- package/src/tools/monitor.ts +41 -2
- package/src/tools/skill-discovery.ts +21 -29
- package/src/tools/subagent.ts +142 -24
- package/src/tools/tool-catalog.generated.ts +6 -6
- package/src/tools/ultragoal-ask-guard.ts +10 -3
- package/src/utils/herdr-pane.ts +345 -0
- package/src/utils/title-generator.ts +2 -0
- package/src/web/scrapers/index.ts +6 -4
- package/dist/types/cli/malloc-env-guard.test.d.ts +0 -1
- package/dist/types/defaults/gjc-defaults.test.d.ts +0 -1
- package/dist/types/memory-backend/lazy-loading.test.d.ts +0 -1
- package/dist/types/runtime/boot-ordering.test.d.ts +0 -1
- package/dist/types/runtime/lazy-service.test.d.ts +0 -1
- package/dist/types/runtime/network-prewarm-service.test.d.ts +0 -1
- package/dist/types/runtime/workspace-tree-service.test.d.ts +0 -1
- package/dist/types/runtime-mcp/config.test.d.ts +0 -1
- package/dist/types/runtime-mcp/http.test.d.ts +0 -1
- package/dist/types/runtime-mcp/manager-pool.test.d.ts +0 -1
- package/dist/types/runtime-mcp/pool-key.test.d.ts +0 -1
- package/dist/types/runtime-mcp/pool.test.d.ts +0 -1
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +0 -117
- package/dist/types/sdk/bus/lifecycle-orchestrator.d.ts +0 -141
- package/dist/types/sdk/bus/telegram-cli.d.ts +0 -31
- package/dist/types/sdk/elevation/allowlist.d.ts +0 -20
- package/dist/types/sdk/elevation/audit.d.ts +0 -93
- package/dist/types/sdk/elevation/capability.d.ts +0 -3
- package/dist/types/sdk/elevation/digest.d.ts +0 -72
- package/dist/types/sdk/elevation/dispatch-receipt.d.ts +0 -85
- package/dist/types/sdk/elevation/grant-ledger.d.ts +0 -163
- package/dist/types/sdk/elevation/owner.d.ts +0 -48
- package/dist/types/sdk/host/import-graph.test.d.ts +0 -1
- package/dist/types/sdk/host/sdk-surface-parity.test.d.ts +0 -1
- package/dist/types/sdk/host/session-runtime.test.d.ts +0 -1
- package/dist/types/sdk/host/websocket-transport.lifecycle.test.d.ts +0 -1
- package/dist/types/session/auth-broker-config.d.ts +0 -13
- package/dist/types/session/heap-eviction-artifacts.test.d.ts +0 -1
- package/dist/types/skills/skills.test.d.ts +0 -1
- package/dist/types/slash-commands/notify-command.test.d.ts +0 -1
- package/dist/types/task/token-log.test.d.ts +0 -1
- package/dist/types/tools/computer.enforcement.test.d.ts +0 -1
- package/dist/types/tools/descriptors.test.d.ts +0 -1
- package/scripts/g005-daemon-smoke.ts +0 -180
- package/scripts/g011-daemon-path-smoke.ts +0 -153
- package/src/sdk/bus/lifecycle-control-runtime.ts +0 -1733
- package/src/sdk/bus/lifecycle-orchestrator.ts +0 -489
- package/src/sdk/bus/telegram-cli.ts +0 -212
- package/src/sdk/elevation/allowlist.ts +0 -54
- package/src/sdk/elevation/audit.ts +0 -208
- package/src/sdk/elevation/capability.ts +0 -41
- package/src/sdk/elevation/digest.ts +0 -129
- package/src/sdk/elevation/dispatch-receipt.ts +0 -184
- package/src/sdk/elevation/grant-ledger.ts +0 -1142
- package/src/sdk/elevation/owner.ts +0 -109
- package/src/session/auth-broker-config.ts +0 -109
- /package/dist/types/{ai-core-import-gate.test.d.ts → discovery/claude-hooks.d.ts} +0 -0
- /package/dist/types/{cli/fixture-report.test.d.ts → discovery/codex-hooks.d.ts} +0 -0
- /package/dist/types/sdk/{bus/recent-activity.d.ts → lifecycle/recent-sessions.d.ts} +0 -0
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
TextContent,
|
|
16
16
|
Usage,
|
|
17
17
|
} from "@gajae-code/ai/core";
|
|
18
|
+
import { hasAdjacentPrivateThinkingBlocks } from "@gajae-code/ai/core";
|
|
18
19
|
import type * as native from "@gajae-code/natives";
|
|
19
20
|
|
|
20
21
|
function nativeSessionManager(): typeof import("@gajae-code/natives") {
|
|
@@ -39,6 +40,7 @@ import {
|
|
|
39
40
|
Snowflake,
|
|
40
41
|
toError,
|
|
41
42
|
} from "@gajae-code/utils";
|
|
43
|
+
import { EDIT_SNAPSHOT_EXTERNALIZED_NOTICE, editSnapshotReceipt } from "../edit/renderer";
|
|
42
44
|
import type { TtsrInjectionRecord } from "../export/ttsr";
|
|
43
45
|
import { assertSafePathComponent } from "../gjc-runtime/session-layout";
|
|
44
46
|
import { writeTextAtomic } from "../gjc-runtime/state-writer";
|
|
@@ -89,9 +91,11 @@ import {
|
|
|
89
91
|
captureManagedFileNoFollow,
|
|
90
92
|
fsyncManagedArtifactTree,
|
|
91
93
|
MANAGED_ARTIFACT_MAX_FILE_BYTES,
|
|
94
|
+
type ManagedAppendReceipt,
|
|
92
95
|
type ManagedBoundedAppendExpectation,
|
|
93
96
|
ManagedCommittedMutationError,
|
|
94
97
|
type ManagedDirectoryRoot,
|
|
98
|
+
type ManagedFileIdentity,
|
|
95
99
|
type ManagedFileSnapshot,
|
|
96
100
|
ManagedSessionDescendantStore,
|
|
97
101
|
type ManagedSessionSecurityPolicy,
|
|
@@ -1746,7 +1750,7 @@ async function writeOwnerOnlyFileNoReplace(filePath: string, content: Uint8Array
|
|
|
1746
1750
|
await fsyncDirectoryPath(path.dirname(filePath));
|
|
1747
1751
|
}
|
|
1748
1752
|
|
|
1749
|
-
const MEMORY_GUARD_CHECKPOINT_FILE_MAX_BYTES =
|
|
1753
|
+
const MEMORY_GUARD_CHECKPOINT_FILE_MAX_BYTES = 128 * 1024 * 1024;
|
|
1750
1754
|
const MEMORY_GUARD_CHECKPOINT_BLOB_MAX_ENTRIES = 4096;
|
|
1751
1755
|
const MEMORY_GUARD_CHECKPOINT_BLOB_TOTAL_MAX_BYTES = 64 * 1024 * 1024;
|
|
1752
1756
|
|
|
@@ -2042,6 +2046,49 @@ export class SessionAppendPersistenceError extends Error {
|
|
|
2042
2046
|
}
|
|
2043
2047
|
}
|
|
2044
2048
|
|
|
2049
|
+
/**
|
|
2050
|
+
* Typed near-limit append outcome (#4566).
|
|
2051
|
+
*
|
|
2052
|
+
* A live managed append that would cross the per-file transcript cap is now
|
|
2053
|
+
* preflighted: when the append alone cannot fit even after a full rewrite of
|
|
2054
|
+
* the live entries, this deterministic error replaces the generic
|
|
2055
|
+
* `SessionAppendPersistenceError: content_too_large` abort. It states whether
|
|
2056
|
+
* the in-memory entry was kept (so the just-committed source mutation keeps
|
|
2057
|
+
* its receipt on the next successful persist) and how to continue.
|
|
2058
|
+
*/
|
|
2059
|
+
export class SessionNearLimitAppendError extends Error {
|
|
2060
|
+
readonly code = "near_limit_append" as const;
|
|
2061
|
+
/** Serialized size (bytes) of the entry that could not fit. */
|
|
2062
|
+
readonly entryBytes: number;
|
|
2063
|
+
/** Live-entry rewrite size (bytes) the recovery already attempted. */
|
|
2064
|
+
readonly liveBytes: number;
|
|
2065
|
+
/** Managed per-file cap in force when the append was rejected. */
|
|
2066
|
+
readonly capBytes: number;
|
|
2067
|
+
/** True when the entry remains in the resident list awaiting the next persist. */
|
|
2068
|
+
readonly entryRetained: boolean;
|
|
2069
|
+
|
|
2070
|
+
constructor(details: {
|
|
2071
|
+
entryBytes: number;
|
|
2072
|
+
liveBytes: number;
|
|
2073
|
+
capBytes: number;
|
|
2074
|
+
entryRetained: boolean;
|
|
2075
|
+
}) {
|
|
2076
|
+
super(
|
|
2077
|
+
[
|
|
2078
|
+
`near_limit_append: entry (${details.entryBytes} B) plus live transcript (${details.liveBytes} B) exceeds the managed per-file limit (${details.capBytes} B).`,
|
|
2079
|
+
details.entryRetained
|
|
2080
|
+
? "The appended entry is retained in memory; its effect (including any committed source edit) is recorded and will persist on the next successful write. Compact the session (`/compact`) or export to a fresh session (`gjc export <session-file>`) before continuing."
|
|
2081
|
+
: "The appended entry was rolled back from memory; re-issue it after compacting the session (`/compact`) or exporting to a fresh session (`gjc export <session-file>`).",
|
|
2082
|
+
].join(" "),
|
|
2083
|
+
);
|
|
2084
|
+
this.name = "SessionNearLimitAppendError";
|
|
2085
|
+
this.entryBytes = details.entryBytes;
|
|
2086
|
+
this.liveBytes = details.liveBytes;
|
|
2087
|
+
this.capBytes = details.capBytes;
|
|
2088
|
+
this.entryRetained = details.entryRetained;
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2045
2092
|
export class SessionManagedStorageError extends Error {
|
|
2046
2093
|
readonly code = "managed_storage_unsupported";
|
|
2047
2094
|
|
|
@@ -2628,9 +2675,10 @@ function measureJsonLikeBytes(value: unknown, visited = new Set<object>()): numb
|
|
|
2628
2675
|
|
|
2629
2676
|
/** Measure one materialized {@link SessionContext} and throw when over the budget. */
|
|
2630
2677
|
function assertSessionContextWithinBudget(context: SessionContext): SessionContext {
|
|
2678
|
+
const budgetBytes = effectiveSessionContextBudgetBytes();
|
|
2631
2679
|
const measuredBytes = measureJsonLikeBytes(context);
|
|
2632
|
-
if (measuredBytes >
|
|
2633
|
-
throw new SessionContextTooLargeError(measuredBytes);
|
|
2680
|
+
if (measuredBytes > budgetBytes) {
|
|
2681
|
+
throw new SessionContextTooLargeError(measuredBytes, budgetBytes);
|
|
2634
2682
|
}
|
|
2635
2683
|
return context;
|
|
2636
2684
|
}
|
|
@@ -3163,6 +3211,24 @@ function managedFileSnapshotEquals(left: ManagedFileSnapshot | null, right: Mana
|
|
|
3163
3211
|
left.bytes.equals(right.bytes)
|
|
3164
3212
|
);
|
|
3165
3213
|
}
|
|
3214
|
+
|
|
3215
|
+
function managedFileSnapshotMatchesDescriptor(snapshot: ManagedFileSnapshot, descriptor: DescriptorSnapshot): boolean {
|
|
3216
|
+
return (
|
|
3217
|
+
snapshot.identity.dev === descriptor.dev &&
|
|
3218
|
+
snapshot.identity.ino === descriptor.ino &&
|
|
3219
|
+
snapshot.identity.nlink === descriptor.nlink &&
|
|
3220
|
+
snapshot.identity.size === descriptor.size &&
|
|
3221
|
+
snapshot.identity.mtimeNs === descriptor.mtimeNs &&
|
|
3222
|
+
snapshot.identity.ctimeNs === descriptor.ctimeNs
|
|
3223
|
+
);
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
function unrefDelay(ms: number): Promise<void> {
|
|
3227
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
3228
|
+
const timer = setTimeout(resolve, ms);
|
|
3229
|
+
timer.unref();
|
|
3230
|
+
return promise;
|
|
3231
|
+
}
|
|
3166
3232
|
const trustedSessionDestinations = new WeakSet<SessionDestination>();
|
|
3167
3233
|
const explicitProfileAgentDirs = new WeakMap<SessionDestination, string>();
|
|
3168
3234
|
const managedSecurityPolicies = new WeakMap<ManagedSessionSecurityContext, ManagedSessionSecurityPolicy>();
|
|
@@ -3426,6 +3492,18 @@ function descriptorSnapshotAsStorageStat(descriptor: DescriptorSnapshot): Sessio
|
|
|
3426
3492
|
return { ...descriptor, mtimeMs, mtime: new Date(mtimeMs), isFile: true };
|
|
3427
3493
|
}
|
|
3428
3494
|
|
|
3495
|
+
function managedIdentityFromDescriptor(descriptor: DescriptorSnapshot): ManagedFileIdentity {
|
|
3496
|
+
if (descriptor.nlink === undefined) throw new Error("managed_identity_nlink_unavailable");
|
|
3497
|
+
return {
|
|
3498
|
+
dev: descriptor.dev,
|
|
3499
|
+
ino: descriptor.ino,
|
|
3500
|
+
nlink: descriptor.nlink,
|
|
3501
|
+
size: descriptor.size,
|
|
3502
|
+
mtimeNs: descriptor.mtimeNs,
|
|
3503
|
+
ctimeNs: descriptor.ctimeNs,
|
|
3504
|
+
};
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3429
3507
|
function retainedManagedInspectionStorage(
|
|
3430
3508
|
storage: SessionStorage,
|
|
3431
3509
|
store: ManagedSessionDescendantStore,
|
|
@@ -3901,6 +3979,18 @@ function revalidateTranscriptIdentityBounded(
|
|
|
3901
3979
|
return sameResumeIdentity(expected, observed) ? { kind: "valid" } : { kind: "error", reason: "identity-mismatch" };
|
|
3902
3980
|
}
|
|
3903
3981
|
|
|
3982
|
+
function revalidateStrictResumeInspection(
|
|
3983
|
+
filePath: string,
|
|
3984
|
+
storage: SessionStorage,
|
|
3985
|
+
inspection: ResumeInspectionSnapshot,
|
|
3986
|
+
): boolean {
|
|
3987
|
+
if (!storage.readRangeSync) {
|
|
3988
|
+
const inspected = inspectResumeSessionFile(filePath, storage);
|
|
3989
|
+
return !("kind" in inspected) && sameResumeIdentity(inspection.identity, inspected.identity);
|
|
3990
|
+
}
|
|
3991
|
+
return revalidateTranscriptIdentityBounded(filePath, storage, inspection.identity).kind === "valid";
|
|
3992
|
+
}
|
|
3993
|
+
|
|
3904
3994
|
function createTranscriptSnapshotHandle(
|
|
3905
3995
|
inspection: BoundedTranscriptInspection,
|
|
3906
3996
|
filePath: string,
|
|
@@ -3967,8 +4057,9 @@ function createTranscriptSnapshotHandle(
|
|
|
3967
4057
|
},
|
|
3968
4058
|
materialize(): Uint8Array {
|
|
3969
4059
|
if (closed) throw new Error("transcript_handle_closed");
|
|
3970
|
-
|
|
3971
|
-
|
|
4060
|
+
const budgetBytes = effectiveSessionContextBudgetBytes();
|
|
4061
|
+
if (size > budgetBytes - TRANSCRIPT_CAPTURE_CHUNK_BYTES) {
|
|
4062
|
+
throw new SessionContextTooLargeError(size, budgetBytes);
|
|
3972
4063
|
}
|
|
3973
4064
|
const output = Buffer.allocUnsafe(size);
|
|
3974
4065
|
for (let offset = 0; offset < size; offset += TRANSCRIPT_CAPTURE_CHUNK_BYTES) {
|
|
@@ -4587,6 +4678,12 @@ async function movePathAcrossDevicesSafe(source: string, destination: string): P
|
|
|
4587
4678
|
|
|
4588
4679
|
const MAX_PERSIST_CHARS = 500_000;
|
|
4589
4680
|
const TRUNCATION_NOTICE = "\n\n[Session persistence truncated large content]";
|
|
4681
|
+
/**
|
|
4682
|
+
* Inline cap for edit-result snapshot bodies (`EditToolDetails.oldText` /
|
|
4683
|
+
* `.newText` and per-file copies). Bodies up to this size persist verbatim;
|
|
4684
|
+
* larger ones persist as a digest receipt (#4566).
|
|
4685
|
+
*/
|
|
4686
|
+
const EDIT_SNAPSHOT_INLINE_MAX_CHARS = 16 * 1024;
|
|
4590
4687
|
/** Minimum base64 length to externalize to blob store (skip tiny inline images) */
|
|
4591
4688
|
const BLOB_EXTERNALIZE_THRESHOLD = 1024;
|
|
4592
4689
|
const TEXT_CONTENT_KEY = "content";
|
|
@@ -4714,6 +4811,77 @@ function truncateString(value: string, maxLength: number): string {
|
|
|
4714
4811
|
return truncated;
|
|
4715
4812
|
}
|
|
4716
4813
|
|
|
4814
|
+
/**
|
|
4815
|
+
* Bound durable edit-result snapshot bodies (#4566).
|
|
4816
|
+
*
|
|
4817
|
+
* A tiny edit to a large file used to persist the complete pre- and post-edit
|
|
4818
|
+
* file bodies in `EditToolDetails.oldText`/`newText` (and each
|
|
4819
|
+
* `perFileResults[]` copy), so every patch added ~2x file size to the managed
|
|
4820
|
+
* transcript and long sessions hit the 64 MiB per-file append limit mid-turn.
|
|
4821
|
+
*
|
|
4822
|
+
* Persisted edit results keep full bodies only under the inline cap; larger
|
|
4823
|
+
* bodies are replaced by a fixed-size receipt (`oldTextDigest`/`newTextDigest`
|
|
4824
|
+
* = byte length + SHA-256) plus a marker, so rendering, diagnostics, diffs,
|
|
4825
|
+
* paths, and source-change accounting stay intact while the durable cost per
|
|
4826
|
+
* edit is bounded independently of file size. Live in-process results are
|
|
4827
|
+
* untouched — ACP `diff` ToolCallContent still receives full bodies.
|
|
4828
|
+
*/
|
|
4829
|
+
function boundEditSnapshotFields(value: unknown, visited: WeakSet<object>): unknown {
|
|
4830
|
+
if (typeof value !== "object" || value === null) return value;
|
|
4831
|
+
if (visited.has(value)) return value;
|
|
4832
|
+
visited.add(value);
|
|
4833
|
+
|
|
4834
|
+
const boundOne = (entry: Record<string, unknown>): Record<string, unknown> => {
|
|
4835
|
+
let changed = false;
|
|
4836
|
+
const next: Record<string, unknown> = { ...entry };
|
|
4837
|
+
const pairs: Array<["oldText", "oldTextDigest"] | ["newText", "newTextDigest"]> = [
|
|
4838
|
+
["oldText", "oldTextDigest"],
|
|
4839
|
+
["newText", "newTextDigest"],
|
|
4840
|
+
];
|
|
4841
|
+
for (const [bodyKey, digestKey] of pairs) {
|
|
4842
|
+
const body = entry[bodyKey];
|
|
4843
|
+
if (typeof body !== "string" || body.length <= EDIT_SNAPSHOT_INLINE_MAX_CHARS) continue;
|
|
4844
|
+
const receipt = editSnapshotReceipt(body);
|
|
4845
|
+
if (receipt === undefined) continue;
|
|
4846
|
+
next[digestKey] = receipt;
|
|
4847
|
+
next[bodyKey] = EDIT_SNAPSHOT_EXTERNALIZED_NOTICE;
|
|
4848
|
+
changed = true;
|
|
4849
|
+
}
|
|
4850
|
+
return changed ? next : entry;
|
|
4851
|
+
};
|
|
4852
|
+
|
|
4853
|
+
if (Array.isArray(value)) {
|
|
4854
|
+
let changed = false;
|
|
4855
|
+
const result: unknown[] = new Array(value.length);
|
|
4856
|
+
for (let i = 0; i < value.length; i++) {
|
|
4857
|
+
const item = value[i];
|
|
4858
|
+
const bounded =
|
|
4859
|
+
typeof item === "object" && item !== null && !Array.isArray(item)
|
|
4860
|
+
? boundOne(item as Record<string, unknown>)
|
|
4861
|
+
: item;
|
|
4862
|
+
result[i] = bounded === item ? boundEditSnapshotFields(item, visited) : bounded;
|
|
4863
|
+
if (result[i] !== item) changed = true;
|
|
4864
|
+
}
|
|
4865
|
+
return changed ? result : value;
|
|
4866
|
+
}
|
|
4867
|
+
|
|
4868
|
+
for (const [key, child] of Object.entries(value)) {
|
|
4869
|
+
if (child !== null && typeof child === "object" && !Array.isArray(child)) {
|
|
4870
|
+
const boundedChild = boundOne(child as Record<string, unknown>);
|
|
4871
|
+
if (boundedChild !== child) {
|
|
4872
|
+
return { ...value, [key]: boundedChild };
|
|
4873
|
+
}
|
|
4874
|
+
}
|
|
4875
|
+
}
|
|
4876
|
+
for (const [key, child] of Object.entries(value)) {
|
|
4877
|
+
const bounded = boundEditSnapshotFields(child, visited);
|
|
4878
|
+
if (bounded !== child) {
|
|
4879
|
+
return { ...(value as Record<string, unknown>), [key]: bounded };
|
|
4880
|
+
}
|
|
4881
|
+
}
|
|
4882
|
+
return boundOne(value as Record<string, unknown>);
|
|
4883
|
+
}
|
|
4884
|
+
|
|
4717
4885
|
function isImageBlock(value: unknown): value is { type: "image"; data: string; mimeType?: string } {
|
|
4718
4886
|
return (
|
|
4719
4887
|
typeof value === "object" &&
|
|
@@ -5547,7 +5715,10 @@ async function truncateForPersistence(obj: unknown, blobStore: BlobStore, key?:
|
|
|
5547
5715
|
}
|
|
5548
5716
|
|
|
5549
5717
|
async function prepareEntryForPersistence(entry: FileEntry, blobStore: BlobStore): Promise<FileEntry> {
|
|
5550
|
-
|
|
5718
|
+
// Bound edit snapshots before the generic 500k string truncation so the
|
|
5719
|
+
// receipt hashes/lengths identify the exact source bodies, not truncated
|
|
5720
|
+
// prefixes of files larger than MAX_PERSIST_CHARS (#4566).
|
|
5721
|
+
return (await truncateForPersistence(boundEditSnapshotFieldsForEntry(entry), blobStore)) as FileEntry;
|
|
5551
5722
|
}
|
|
5552
5723
|
|
|
5553
5724
|
/**
|
|
@@ -5652,7 +5823,27 @@ function truncateForPersistenceSync(obj: unknown, blobStore: BlobStore, key?: st
|
|
|
5652
5823
|
}
|
|
5653
5824
|
|
|
5654
5825
|
function prepareEntryForPersistenceSync(entry: FileEntry, blobStore: BlobStore): FileEntry {
|
|
5655
|
-
|
|
5826
|
+
// Keep this ordering identical to the async path: snapshot receipts must be
|
|
5827
|
+
// computed from the complete body before generic persistence truncation.
|
|
5828
|
+
return truncateForPersistenceSync(boundEditSnapshotFieldsForEntry(entry), blobStore) as FileEntry;
|
|
5829
|
+
}
|
|
5830
|
+
|
|
5831
|
+
/**
|
|
5832
|
+
* Apply {@link boundEditSnapshotFields} to a persisted entry's edit-result
|
|
5833
|
+
* details. Only `message` entries with `role === "toolResult"` and a `details`
|
|
5834
|
+
* object can carry edit snapshots; everything else returns unchanged, so the
|
|
5835
|
+
* walk never touches unrelated entries (#4566).
|
|
5836
|
+
*/
|
|
5837
|
+
function boundEditSnapshotFieldsForEntry(entry: FileEntry): FileEntry {
|
|
5838
|
+
if (entry.type !== "message") return entry;
|
|
5839
|
+
const message = entry.message;
|
|
5840
|
+
if (message === null || typeof message !== "object" || (message as { role?: unknown }).role !== "toolResult")
|
|
5841
|
+
return entry;
|
|
5842
|
+
const details = (message as { details?: unknown }).details;
|
|
5843
|
+
if (details === null || typeof details !== "object" || Array.isArray(details)) return entry;
|
|
5844
|
+
const bounded = boundEditSnapshotFields(details, new WeakSet());
|
|
5845
|
+
if (bounded === details) return entry;
|
|
5846
|
+
return { ...entry, message: { ...(message as object), details: bounded } } as FileEntry;
|
|
5656
5847
|
}
|
|
5657
5848
|
|
|
5658
5849
|
class NdjsonFileWriter {
|
|
@@ -6475,6 +6666,7 @@ interface SessionManagerStateSnapshot {
|
|
|
6475
6666
|
sessionName: string | undefined;
|
|
6476
6667
|
titleSource: "auto" | "user" | undefined;
|
|
6477
6668
|
sessionFile: string | undefined;
|
|
6669
|
+
managedPersistExpectedIdentity: ManagedFileIdentity | undefined;
|
|
6478
6670
|
flushed: boolean;
|
|
6479
6671
|
ensuredOnDisk: boolean;
|
|
6480
6672
|
needsFullRewriteOnNextPersist: boolean;
|
|
@@ -6495,6 +6687,15 @@ export const SessionManagerTestHooks: {
|
|
|
6495
6687
|
afterForkTranscriptPublished?: () => void | Promise<void>;
|
|
6496
6688
|
beforeEphemeralArtifactManagerInstall?: (dir: string) => void | Promise<void>;
|
|
6497
6689
|
beforePersistPatchFence?: (attempt: number) => void;
|
|
6690
|
+
beforeStrictMissingCheck?: (filePath: string, storage: SessionStorage) => void;
|
|
6691
|
+
beforeManagedResumeAcceptance?: (filePath: string, storage: SessionStorage) => void;
|
|
6692
|
+
beforeManagedResumeReturn?: (filePath: string, storage: SessionStorage) => void;
|
|
6693
|
+
beforeManagedSourceStat?: (filePath: string, storage: SessionStorage) => void | Promise<void>;
|
|
6694
|
+
beforeManagedMissingInit?: (filePath: string, storage: SessionStorage) => void | Promise<void>;
|
|
6695
|
+
beforeManagedMissingPublish?: (filePath: string, storage: SessionStorage) => void | Promise<void>;
|
|
6696
|
+
beforeManagedMissingReturn?: (filePath: string, storage: SessionStorage) => void | Promise<void>;
|
|
6697
|
+
afterManagedMissingAssertion?: (filePath: string, storage: SessionStorage) => void | Promise<void>;
|
|
6698
|
+
beforeManagedSwitchIdentity?: (filePath: string, storage: SessionStorage) => void | Promise<void>;
|
|
6498
6699
|
/** Internal first-open GC strategy override; omitted means current. */
|
|
6499
6700
|
firstOpenGcStrategy?: SessionMemoryGcStrategy;
|
|
6500
6701
|
/** Internal first-open secondary-artifact mode override; omitted means auto. */
|
|
@@ -6511,6 +6712,8 @@ export const SessionManagerTestHooks: {
|
|
|
6511
6712
|
lastSidecarInitError?: string;
|
|
6512
6713
|
/** Test-only generated-ID cache capacity override. */
|
|
6513
6714
|
coldIdHashMaxEntriesOverride?: number;
|
|
6715
|
+
/** Test-only session-context budget override (in-process; does not leak to subprocesses). */
|
|
6716
|
+
sessionContextBudgetBytesOverride?: number;
|
|
6514
6717
|
} = {};
|
|
6515
6718
|
|
|
6516
6719
|
function materializedCacheMaxBytes(): number {
|
|
@@ -6552,6 +6755,13 @@ function coldIdHashMaxEntries(): number {
|
|
|
6552
6755
|
throw new RangeError(`coldIdHashMaxEntriesOverride must be between 1 and ${COLD_ID_HASH_CAPACITY}.`);
|
|
6553
6756
|
return override;
|
|
6554
6757
|
}
|
|
6758
|
+
function effectiveSessionContextBudgetBytes(): number {
|
|
6759
|
+
const override = SessionManagerTestHooks.sessionContextBudgetBytesOverride;
|
|
6760
|
+
if (override === undefined) return SESSION_CONTEXT_MATERIALIZATION_BUDGET_BYTES;
|
|
6761
|
+
if (!Number.isSafeInteger(override) || override < 1)
|
|
6762
|
+
throw new RangeError("sessionContextBudgetBytesOverride must be a positive safe integer.");
|
|
6763
|
+
return override;
|
|
6764
|
+
}
|
|
6555
6765
|
|
|
6556
6766
|
function emptyFirstOpenTelemetry(
|
|
6557
6767
|
strategy: SessionMemoryGcStrategy = "pressure",
|
|
@@ -6802,6 +7012,9 @@ export class SessionManager {
|
|
|
6802
7012
|
#persistChain: Promise<void> = Promise.resolve();
|
|
6803
7013
|
#persistError: Error | undefined;
|
|
6804
7014
|
#persistErrorReported = false;
|
|
7015
|
+
/** Defense-in-depth (#4443): one-shot warn for adjacent private thinking blocks in persisted assistant transcripts. */
|
|
7016
|
+
#warnedAdjacentThinkingPersist = false;
|
|
7017
|
+
#closeRetryPending = false;
|
|
6805
7018
|
/** Depth of the non-yielding same-session persistence fence (reentrancy counter). */
|
|
6806
7019
|
#persistenceFenceDepth = 0;
|
|
6807
7020
|
/** Publication fence counter carried by the mutable `.spill.commit` marker. */
|
|
@@ -6833,6 +7046,7 @@ export class SessionManager {
|
|
|
6833
7046
|
#managedSidecarSecurityContext: ManagedSessionSecurityContext | undefined;
|
|
6834
7047
|
#managedSidecarCacheSessionFile: string | undefined;
|
|
6835
7048
|
#managedRangeExpectedDescriptor: DescriptorSnapshot | undefined;
|
|
7049
|
+
#managedPersistExpectedIdentity: ManagedFileIdentity | undefined;
|
|
6836
7050
|
#boundedReadStorageProxy: SessionStorage | undefined;
|
|
6837
7051
|
#boundedManagedSource:
|
|
6838
7052
|
| { path: string; store: ManagedSessionDescendantStore; descriptor: DescriptorSnapshot; owned: boolean }
|
|
@@ -6897,6 +7111,7 @@ export class SessionManager {
|
|
|
6897
7111
|
private readonly persist: boolean,
|
|
6898
7112
|
storage: SessionStorage,
|
|
6899
7113
|
private destination: SessionDestination = explicitDestination(sessionDir),
|
|
7114
|
+
skipEnsureSessionDir = false,
|
|
6900
7115
|
) {
|
|
6901
7116
|
this.#storage = new Proxy(storage, {
|
|
6902
7117
|
get: (target, property, receiver) => {
|
|
@@ -6999,7 +7214,7 @@ export class SessionManager {
|
|
|
6999
7214
|
this.#blobStore = persist ? new BlobStore(getBlobsDir()) : this.#residentTextBlobStore;
|
|
7000
7215
|
this.#residentImageBlobStore = this.#blobStore;
|
|
7001
7216
|
if (persist && sessionDir) {
|
|
7002
|
-
this.#storage.ensureDirSync(sessionDir);
|
|
7217
|
+
if (!skipEnsureSessionDir) this.#storage.ensureDirSync(sessionDir);
|
|
7003
7218
|
// Canonicalize the trusted session directory (single choke point for every
|
|
7004
7219
|
// creation path: create/open/moveTo/fork/SDK) so benign ancestor symlinks
|
|
7005
7220
|
// (e.g. macOS `/var -> /private/var`, a symlinked `$HOME`) are resolved to a
|
|
@@ -7448,6 +7663,7 @@ export class SessionManager {
|
|
|
7448
7663
|
flushed: this.#flushed,
|
|
7449
7664
|
ensuredOnDisk: this.#ensuredOnDisk,
|
|
7450
7665
|
needsFullRewriteOnNextPersist: this.#needsFullRewriteOnNextPersist,
|
|
7666
|
+
managedPersistExpectedIdentity: this.#managedPersistExpectedIdentity,
|
|
7451
7667
|
fileEntries: [],
|
|
7452
7668
|
materializedFileEntries: [],
|
|
7453
7669
|
adoptedArtifactManager: this.#adoptedArtifactManager,
|
|
@@ -7482,6 +7698,7 @@ export class SessionManager {
|
|
|
7482
7698
|
this.#flushed = snapshot.flushed;
|
|
7483
7699
|
this.#ensuredOnDisk = snapshot.ensuredOnDisk;
|
|
7484
7700
|
this.#needsFullRewriteOnNextPersist = snapshot.needsFullRewriteOnNextPersist;
|
|
7701
|
+
this.#managedPersistExpectedIdentity = snapshot.managedPersistExpectedIdentity;
|
|
7485
7702
|
this.#adoptedArtifactManager = snapshot.adoptedArtifactManager;
|
|
7486
7703
|
}
|
|
7487
7704
|
captureState(): SessionManagerStateSnapshot {
|
|
@@ -7498,6 +7715,7 @@ export class SessionManager {
|
|
|
7498
7715
|
flushed: this.#flushed,
|
|
7499
7716
|
ensuredOnDisk: this.#ensuredOnDisk,
|
|
7500
7717
|
needsFullRewriteOnNextPersist: this.#needsFullRewriteOnNextPersist,
|
|
7718
|
+
managedPersistExpectedIdentity: this.#managedPersistExpectedIdentity,
|
|
7501
7719
|
// Snapshot entry objects by reference: switch/reload replaces the active entry array,
|
|
7502
7720
|
// so rollback does not need structured cloning of extension/custom details.
|
|
7503
7721
|
fileEntries: [...this.#fileEntries],
|
|
@@ -7533,6 +7751,7 @@ export class SessionManager {
|
|
|
7533
7751
|
this.#flushed = snapshot.flushed;
|
|
7534
7752
|
this.#ensuredOnDisk = snapshot.ensuredOnDisk;
|
|
7535
7753
|
this.#needsFullRewriteOnNextPersist = snapshot.needsFullRewriteOnNextPersist;
|
|
7754
|
+
this.#managedPersistExpectedIdentity = snapshot.managedPersistExpectedIdentity;
|
|
7536
7755
|
if (!retainsPersistWriter) {
|
|
7537
7756
|
this.#persistWriter = undefined;
|
|
7538
7757
|
this.#persistWriterPath = undefined;
|
|
@@ -7581,6 +7800,7 @@ export class SessionManager {
|
|
|
7581
7800
|
this.#titleSource = state.header.titleSource;
|
|
7582
7801
|
this.#sessionFile = state.sessionFile;
|
|
7583
7802
|
this.#managedRangeExpectedDescriptor = undefined;
|
|
7803
|
+
this.#managedPersistExpectedIdentity = undefined;
|
|
7584
7804
|
this.#clearBoundedManagedSource();
|
|
7585
7805
|
this.#flushed = false;
|
|
7586
7806
|
this.#needsFullRewriteOnNextPersist = false;
|
|
@@ -7822,6 +8042,7 @@ export class SessionManager {
|
|
|
7822
8042
|
this.#usageStatistics = commit.usageStatistics;
|
|
7823
8043
|
this.#flushed = true;
|
|
7824
8044
|
this.#ensuredOnDisk = true;
|
|
8045
|
+
this.#managedPersistExpectedIdentity = managedIdentityFromDescriptor(terminalDescriptor);
|
|
7825
8046
|
this.#lazyReopenSucceeded = true;
|
|
7826
8047
|
this.#lazyReopenFallbackReason = undefined;
|
|
7827
8048
|
initialized = true;
|
|
@@ -7881,9 +8102,14 @@ export class SessionManager {
|
|
|
7881
8102
|
for (let attempt = 0; attempt < 3000; attempt++) {
|
|
7882
8103
|
const lock = this.#storage.acquireExclusiveLockSync(lockPath);
|
|
7883
8104
|
if (lock) {
|
|
7884
|
-
|
|
8105
|
+
try {
|
|
8106
|
+
if (contended && this.#storage.existsSync(commitPath)) {
|
|
8107
|
+
this.#releaseExclusiveLockWithRetry(lock);
|
|
8108
|
+
return "published";
|
|
8109
|
+
}
|
|
8110
|
+
} catch (error) {
|
|
7885
8111
|
this.#releaseExclusiveLockWithRetry(lock);
|
|
7886
|
-
|
|
8112
|
+
throw error;
|
|
7887
8113
|
}
|
|
7888
8114
|
return lock;
|
|
7889
8115
|
}
|
|
@@ -8003,6 +8229,7 @@ export class SessionManager {
|
|
|
8003
8229
|
this.#lazyReopenFallbackReason = "bounded_first_open_descriptor_changed";
|
|
8004
8230
|
return false;
|
|
8005
8231
|
}
|
|
8232
|
+
this.#managedPersistExpectedIdentity = managedIdentityFromDescriptor(finalDescriptor);
|
|
8006
8233
|
this.#sessionId = discovery.header.id;
|
|
8007
8234
|
this.#sessionName = discovery.header.title;
|
|
8008
8235
|
this.#titleSource = discovery.header.titleSource;
|
|
@@ -8686,9 +8913,24 @@ export class SessionManager {
|
|
|
8686
8913
|
}
|
|
8687
8914
|
|
|
8688
8915
|
/** Initialize with a specific session file (used by factory methods). */
|
|
8689
|
-
async #initSessionFile(
|
|
8916
|
+
async #initSessionFile(
|
|
8917
|
+
sessionFile: string,
|
|
8918
|
+
initializeMissing = false,
|
|
8919
|
+
strictResume?: { inspection: ResumeInspectionSnapshot; storage: SessionStorage; reuseEntries?: boolean },
|
|
8920
|
+
requireExisting = false,
|
|
8921
|
+
deferPersistenceUntilAccepted = false,
|
|
8922
|
+
): Promise<void> {
|
|
8690
8923
|
let strictManagedFallbackEntries: FileEntry[] | undefined;
|
|
8924
|
+
let strictManagedFallbackMigrationApplied = false;
|
|
8691
8925
|
const resolvedSessionFile = this.#storage instanceof FileSessionStorage ? path.resolve(sessionFile) : sessionFile;
|
|
8926
|
+
const revalidateStrictResume = (): void => {
|
|
8927
|
+
if (
|
|
8928
|
+
strictResume &&
|
|
8929
|
+
!revalidateStrictResumeInspection(resolvedSessionFile, strictResume.storage, strictResume.inspection)
|
|
8930
|
+
)
|
|
8931
|
+
throw new Error("Could not open session: unstable");
|
|
8932
|
+
};
|
|
8933
|
+
revalidateStrictResume();
|
|
8692
8934
|
const sidecarRoot =
|
|
8693
8935
|
this.destination.kind === "managed"
|
|
8694
8936
|
? this.#managedSidecarRoot(resolvedSessionFile)
|
|
@@ -8710,6 +8952,7 @@ export class SessionManager {
|
|
|
8710
8952
|
this.#storage.existsSync(`${sidecarRoot}/.session-memory.spill.commit`);
|
|
8711
8953
|
if (boundedTranscriptAdmitted && (await this.#tryInitSessionFileFromSidecar(resolvedSessionFile))) {
|
|
8712
8954
|
writeTerminalBreadcrumb(this.cwd, resolvedSessionFile);
|
|
8955
|
+
revalidateStrictResume();
|
|
8713
8956
|
return;
|
|
8714
8957
|
}
|
|
8715
8958
|
|
|
@@ -8719,9 +8962,16 @@ export class SessionManager {
|
|
|
8719
8962
|
this.#sessionMemoryAutoDisabledReason = "sidecar_reload_failures";
|
|
8720
8963
|
}
|
|
8721
8964
|
writeTerminalBreadcrumb(this.cwd, resolvedSessionFile);
|
|
8965
|
+
revalidateStrictResume();
|
|
8722
8966
|
return;
|
|
8723
8967
|
}
|
|
8724
|
-
|
|
8968
|
+
revalidateStrictResume();
|
|
8969
|
+
SessionManagerTestHooks.beforeStrictMissingCheck?.(resolvedSessionFile, this.#storage);
|
|
8970
|
+
const transcriptMissing =
|
|
8971
|
+
(initializeMissing || strictResume !== undefined || requireExisting) &&
|
|
8972
|
+
!this.#storage.existsSync(resolvedSessionFile);
|
|
8973
|
+
if ((strictResume || requireExisting) && transcriptMissing) throw new Error("Could not open session: unstable");
|
|
8974
|
+
if (initializeMissing && transcriptMissing) {
|
|
8725
8975
|
const fresh = this.#freshSessionState(undefined, resolvedSessionFile);
|
|
8726
8976
|
const prepared = this.#prepareFreshSessionTransition(fresh, "memory-fallback");
|
|
8727
8977
|
this.#applyFreshSessionMetadata(fresh);
|
|
@@ -8733,7 +8983,9 @@ export class SessionManager {
|
|
|
8733
8983
|
this.#ensuredOnDisk = true;
|
|
8734
8984
|
return;
|
|
8735
8985
|
}
|
|
8986
|
+
if (strictResume?.reuseEntries !== false) strictManagedFallbackEntries = strictResume?.inspection.entries;
|
|
8736
8987
|
if (
|
|
8988
|
+
!strictResume &&
|
|
8737
8989
|
this.destination.kind === "managed" &&
|
|
8738
8990
|
this.#sessionMemoryMode === "enabled" &&
|
|
8739
8991
|
process.platform !== "win32"
|
|
@@ -8741,10 +8993,12 @@ export class SessionManager {
|
|
|
8741
8993
|
const inspected = inspectResumeSessionFile(resolvedSessionFile, this.#storage);
|
|
8742
8994
|
if ("kind" in inspected) throw new Error(`Could not open session: ${inspected.reason}`);
|
|
8743
8995
|
strictManagedFallbackEntries = inspected.entries;
|
|
8996
|
+
strictManagedFallbackMigrationApplied = inspected.migrationApplied;
|
|
8744
8997
|
}
|
|
8745
8998
|
const eagerStat = this.#statSync(resolvedSessionFile);
|
|
8746
8999
|
if (eagerStat.size > EAGER_RESUME_TRANSCRIPT_MAX_BYTES) throw new SessionTranscriptOversizedError(eagerStat.size);
|
|
8747
9000
|
const entries = strictManagedFallbackEntries ?? (await loadEntriesFromFile(resolvedSessionFile, this.#storage));
|
|
9001
|
+
revalidateStrictResume();
|
|
8748
9002
|
if (entries.length === 0) {
|
|
8749
9003
|
const fresh = this.#freshSessionState(undefined, resolvedSessionFile);
|
|
8750
9004
|
const prepared = this.#prepareFreshSessionTransition(fresh, "memory-fallback");
|
|
@@ -8759,7 +9013,12 @@ export class SessionManager {
|
|
|
8759
9013
|
}
|
|
8760
9014
|
const header = entries.find(entry => entry.type === "session") as SessionHeader | undefined;
|
|
8761
9015
|
const sessionId = header?.id ?? createSessionId();
|
|
8762
|
-
const migrationApplied =
|
|
9016
|
+
const migrationApplied =
|
|
9017
|
+
strictResume && strictResume.reuseEntries !== false
|
|
9018
|
+
? strictResume.inspection.migrationApplied
|
|
9019
|
+
: strictManagedFallbackEntries
|
|
9020
|
+
? strictManagedFallbackMigrationApplied
|
|
9021
|
+
: migrateToCurrentVersion(entries);
|
|
8763
9022
|
await resolveBlobRefsInEntries(entries, this.#blobStore);
|
|
8764
9023
|
const prepared = this.#prepareResidentTextStoreTransition(
|
|
8765
9024
|
{
|
|
@@ -8782,7 +9041,9 @@ export class SessionManager {
|
|
|
8782
9041
|
writeTerminalBreadcrumb(this.cwd, resolvedSessionFile);
|
|
8783
9042
|
this.#flushed = true;
|
|
8784
9043
|
this.#ensuredOnDisk = true;
|
|
8785
|
-
|
|
9044
|
+
this.#adoptManagedPersistIdentity(resolvedSessionFile);
|
|
9045
|
+
if (!strictResume && !deferPersistenceUntilAccepted)
|
|
9046
|
+
await this.#sanitizeLoadedOpenAIResponsesReplayMetadataAndPersist();
|
|
8786
9047
|
if (publishedSidecarWasPresent && this.#lazyReopenAttempted && !this.#lazyReopenSucceeded) {
|
|
8787
9048
|
this.#sessionMemoryMode = "shadow";
|
|
8788
9049
|
this.#sessionMemoryAutoDisabledReason = "sidecar_reload_failures";
|
|
@@ -8820,6 +9081,7 @@ export class SessionManager {
|
|
|
8820
9081
|
this.#commitResidentTextStoreTransition(prepared);
|
|
8821
9082
|
this.#flushed = true;
|
|
8822
9083
|
this.#ensuredOnDisk = true;
|
|
9084
|
+
this.#adoptManagedPersistIdentity(resolvedSessionFile);
|
|
8823
9085
|
}
|
|
8824
9086
|
|
|
8825
9087
|
/** Initialize with a new session (used by factory methods). */
|
|
@@ -8933,6 +9195,7 @@ export class SessionManager {
|
|
|
8933
9195
|
titleSource: this.#titleSource,
|
|
8934
9196
|
sessionFile: this.#sessionFile,
|
|
8935
9197
|
needsFullRewriteOnNextPersist: this.#needsFullRewriteOnNextPersist,
|
|
9198
|
+
managedPersistExpectedIdentity: this.#managedPersistExpectedIdentity,
|
|
8936
9199
|
};
|
|
8937
9200
|
this.#persistError = undefined;
|
|
8938
9201
|
this.#persistErrorReported = false;
|
|
@@ -8944,6 +9207,8 @@ export class SessionManager {
|
|
|
8944
9207
|
try {
|
|
8945
9208
|
managedTransition?.adopt();
|
|
8946
9209
|
writeTerminalBreadcrumb(this.cwd, resolvedSessionFile);
|
|
9210
|
+
await SessionManagerTestHooks.beforeManagedSwitchIdentity?.(resolvedSessionFile, this.#storage);
|
|
9211
|
+
this.#adoptManagedPersistIdentity(resolvedSessionFile);
|
|
8947
9212
|
this.#commitResidentTextStoreTransition(prepared);
|
|
8948
9213
|
} catch (error) {
|
|
8949
9214
|
managedTransition?.rollback();
|
|
@@ -8952,6 +9217,7 @@ export class SessionManager {
|
|
|
8952
9217
|
this.#titleSource = previous.titleSource;
|
|
8953
9218
|
this.#sessionFile = previous.sessionFile;
|
|
8954
9219
|
this.#needsFullRewriteOnNextPersist = previous.needsFullRewriteOnNextPersist;
|
|
9220
|
+
this.#managedPersistExpectedIdentity = previous.managedPersistExpectedIdentity;
|
|
8955
9221
|
prepared.dispose();
|
|
8956
9222
|
throw error;
|
|
8957
9223
|
}
|
|
@@ -8984,6 +9250,7 @@ export class SessionManager {
|
|
|
8984
9250
|
flushed: this.#flushed,
|
|
8985
9251
|
needsFullRewriteOnNextPersist: this.#needsFullRewriteOnNextPersist,
|
|
8986
9252
|
ensuredOnDisk: this.#ensuredOnDisk,
|
|
9253
|
+
managedPersistExpectedIdentity: this.#managedPersistExpectedIdentity,
|
|
8987
9254
|
artifactManager: this.#artifactManager,
|
|
8988
9255
|
artifactManagerSessionFile: this.#artifactManagerSessionFile,
|
|
8989
9256
|
adoptedArtifactManager: this.#adoptedArtifactManager,
|
|
@@ -9007,6 +9274,7 @@ export class SessionManager {
|
|
|
9007
9274
|
this.#flushed = previous.flushed;
|
|
9008
9275
|
this.#needsFullRewriteOnNextPersist = previous.needsFullRewriteOnNextPersist;
|
|
9009
9276
|
this.#ensuredOnDisk = previous.ensuredOnDisk;
|
|
9277
|
+
this.#managedPersistExpectedIdentity = previous.managedPersistExpectedIdentity;
|
|
9010
9278
|
this.#artifactManager = previous.artifactManager;
|
|
9011
9279
|
this.#artifactManagerSessionFile = previous.artifactManagerSessionFile;
|
|
9012
9280
|
this.#adoptedArtifactManager = previous.adoptedArtifactManager;
|
|
@@ -9162,7 +9430,10 @@ export class SessionManager {
|
|
|
9162
9430
|
);
|
|
9163
9431
|
if (this.destination.kind === "managed") {
|
|
9164
9432
|
const bytes = Buffer.from(`${entries.map(entry => JSON.stringify(entry)).join("\n")}\n`, "utf8");
|
|
9165
|
-
await this.#managedTranscriptStore(stage.sessionFile).
|
|
9433
|
+
await this.#managedTranscriptStore(stage.sessionFile).publishNoReplace(
|
|
9434
|
+
path.basename(stage.sessionFile),
|
|
9435
|
+
bytes,
|
|
9436
|
+
);
|
|
9166
9437
|
} else {
|
|
9167
9438
|
const staleCleanupError = await this.#cleanupPreparedNewSessionPersistence(stage);
|
|
9168
9439
|
if (staleCleanupError) throw staleCleanupError;
|
|
@@ -9466,6 +9737,7 @@ export class SessionManager {
|
|
|
9466
9737
|
this.#artifactManagerSessionFile = null;
|
|
9467
9738
|
this.#adoptedArtifactManager = null;
|
|
9468
9739
|
this.#commitResidentTextStoreTransition(transition);
|
|
9740
|
+
if (stage.flushed) this.#adoptManagedPersistIdentity(stage.sessionFile);
|
|
9469
9741
|
this.#retireEphemeralArtifacts();
|
|
9470
9742
|
stage.committed = true;
|
|
9471
9743
|
this.#preparedNewSessions.delete(stage);
|
|
@@ -9492,6 +9764,7 @@ export class SessionManager {
|
|
|
9492
9764
|
}
|
|
9493
9765
|
this.#preparedNewSessions.delete(stage);
|
|
9494
9766
|
stage.discarded = true;
|
|
9767
|
+
if (this.#preparedNewSessions.size === 0 && this.#fileEntries.length === 0) this.#releaseOwnedManagedAuthority();
|
|
9495
9768
|
}
|
|
9496
9769
|
|
|
9497
9770
|
async #retryPreparedNewSessionCleanups(): Promise<void> {
|
|
@@ -9794,6 +10067,7 @@ export class SessionManager {
|
|
|
9794
10067
|
this.#needsFullRewriteOnNextPersist = false;
|
|
9795
10068
|
this.#ensuredOnDisk = true;
|
|
9796
10069
|
this.#commitResidentTextStoreTransition(transition);
|
|
10070
|
+
this.#adoptManagedPersistIdentity(newSessionFile);
|
|
9797
10071
|
return { oldSessionFile, newSessionFile };
|
|
9798
10072
|
}
|
|
9799
10073
|
|
|
@@ -9975,7 +10249,7 @@ export class SessionManager {
|
|
|
9975
10249
|
parentStore.removeTreeExpected(stagingName, stagingSnapshot);
|
|
9976
10250
|
} catch (cleanupError) {
|
|
9977
10251
|
const cleanupMessage = toError(cleanupError).message;
|
|
9978
|
-
if (cleanupMessage !== "cleanup_pending") {
|
|
10252
|
+
if (cleanupMessage !== "cleanup_pending" && cleanupMessage !== "not_found") {
|
|
9979
10253
|
throw new Error(`Failed to clean up managed fork artifacts: ${cleanupMessage}`, {
|
|
9980
10254
|
cause: toError(error),
|
|
9981
10255
|
});
|
|
@@ -10243,6 +10517,13 @@ export class SessionManager {
|
|
|
10243
10517
|
managedMove && managedDestinationStore
|
|
10244
10518
|
? { directory: path.resolve(newSessionDir), store: managedDestinationStore }
|
|
10245
10519
|
: null;
|
|
10520
|
+
// A transcript is only guaranteed to exist at the destination when the old file was
|
|
10521
|
+
// actually present and moved (hadSessionFile). Absent/fresh/deleted-source sessions
|
|
10522
|
+
// have no destination transcript yet, so adopting a strict expected identity here
|
|
10523
|
+
// would invent identity for a nonexistent file; defer adoption until the first real
|
|
10524
|
+
// publication (#writeEntriesAtomicallySync / #appendManagedRecordsSync) instead.
|
|
10525
|
+
if (hadSessionFile) this.#adoptManagedPersistIdentity(this.#sessionFile);
|
|
10526
|
+
else this.#managedPersistExpectedIdentity = undefined;
|
|
10246
10527
|
|
|
10247
10528
|
const hasAssistant = this.#fileEntries.some(e => e.type === "message" && e.message.role === "assistant");
|
|
10248
10529
|
try {
|
|
@@ -10279,6 +10560,10 @@ export class SessionManager {
|
|
|
10279
10560
|
managedMove && managedSourceStore
|
|
10280
10561
|
? { directory: path.resolve(previousSessionDir), store: managedSourceStore }
|
|
10281
10562
|
: null;
|
|
10563
|
+
// Same rationale as the forward path: only adopt strict identity when the source
|
|
10564
|
+
// transcript actually existed (and was thus restorable) before the failed move.
|
|
10565
|
+
if (hadSessionFile) this.#adoptManagedPersistIdentity(previousSessionFile);
|
|
10566
|
+
else this.#managedPersistExpectedIdentity = undefined;
|
|
10282
10567
|
const header = this.#fileEntries.find(entry => entry.type === "session") as SessionHeader | undefined;
|
|
10283
10568
|
if (header) applyHeaderPatch(header, { cwd: previousCwd });
|
|
10284
10569
|
this.#headerExportRevision++;
|
|
@@ -14095,6 +14380,20 @@ export class SessionManager {
|
|
|
14095
14380
|
return classification;
|
|
14096
14381
|
}
|
|
14097
14382
|
|
|
14383
|
+
#adoptManagedPersistIdentity(sessionFile = this.#sessionFile): void {
|
|
14384
|
+
if (this.destination.kind !== "managed" || !sessionFile) {
|
|
14385
|
+
this.#managedPersistExpectedIdentity = undefined;
|
|
14386
|
+
return;
|
|
14387
|
+
}
|
|
14388
|
+
const sessionDir = path.resolve(path.dirname(sessionFile));
|
|
14389
|
+
if (sessionDir !== path.resolve(this.destination.directory)) {
|
|
14390
|
+
this.#managedPersistExpectedIdentity = undefined;
|
|
14391
|
+
return;
|
|
14392
|
+
}
|
|
14393
|
+
const descriptor = this.#managedTranscriptStore(sessionFile).descriptorExpected(path.basename(sessionFile));
|
|
14394
|
+
if (!descriptor) throw new Error("managed_persist_identity_unavailable");
|
|
14395
|
+
this.#managedPersistExpectedIdentity = managedIdentityFromDescriptor(descriptor);
|
|
14396
|
+
}
|
|
14098
14397
|
/** Current transcript descriptor, or null when unavailable. */
|
|
14099
14398
|
#managedDescriptorSnapshotOrNull(): DescriptorSnapshot | null {
|
|
14100
14399
|
const sessionFile = this.#sessionFile;
|
|
@@ -14127,7 +14426,23 @@ export class SessionManager {
|
|
|
14127
14426
|
this.#withSessionPersistenceFenceSync(() => {
|
|
14128
14427
|
if (this.destination.kind === "managed") {
|
|
14129
14428
|
const bytes = Buffer.from(`${entries.map(entry => JSON.stringify(entry)).join("\n")}\n`, "utf8");
|
|
14130
|
-
this.#managedTranscriptStore(sessionFile)
|
|
14429
|
+
const store = this.#managedTranscriptStore(sessionFile);
|
|
14430
|
+
const relativePath = path.basename(sessionFile);
|
|
14431
|
+
if (this.#managedPersistExpectedIdentity) {
|
|
14432
|
+
try {
|
|
14433
|
+
store.replaceExpectedIdentitySync(relativePath, bytes, this.#managedPersistExpectedIdentity);
|
|
14434
|
+
} catch (err) {
|
|
14435
|
+
// A confirmed missing predecessor can be recreated from the complete
|
|
14436
|
+
// resident transcript. Any present-but-different identity still fails
|
|
14437
|
+
// closed so a concurrent successor is never overwritten.
|
|
14438
|
+
if (!isEnoent(err)) throw err;
|
|
14439
|
+
this.#managedPersistExpectedIdentity = undefined;
|
|
14440
|
+
store.replaceSync(relativePath, bytes);
|
|
14441
|
+
}
|
|
14442
|
+
} else store.replaceSync(relativePath, bytes);
|
|
14443
|
+
const descriptor = store.descriptorExpected(relativePath);
|
|
14444
|
+
if (!descriptor) throw new Error("managed_replace_identity_unavailable");
|
|
14445
|
+
this.#managedPersistExpectedIdentity = managedIdentityFromDescriptor(descriptor);
|
|
14131
14446
|
this.#publishCommitMarkerFromCurrentTranscriptSync();
|
|
14132
14447
|
return;
|
|
14133
14448
|
}
|
|
@@ -14634,6 +14949,74 @@ export class SessionManager {
|
|
|
14634
14949
|
this.#clearBoundedManagedSource();
|
|
14635
14950
|
}
|
|
14636
14951
|
|
|
14952
|
+
#releaseRejectedOpenResources(): void {
|
|
14953
|
+
this.#releaseResidentTextStore();
|
|
14954
|
+
if (this.#preparedNewSessions.size === 0) this.#releaseOwnedManagedAuthority();
|
|
14955
|
+
this.#releaseClosedSessionState();
|
|
14956
|
+
}
|
|
14957
|
+
|
|
14958
|
+
async #retryRejectedOpenWriterCleanup(): Promise<void> {
|
|
14959
|
+
for (let attempt = 0; this.#persistWriter?.getCloseState() === "close_failed_retryable"; attempt++) {
|
|
14960
|
+
await unrefDelay(Math.min(100 * 2 ** Math.min(attempt, 6), 5_000));
|
|
14961
|
+
try {
|
|
14962
|
+
await this.#closePersistWriterInternal();
|
|
14963
|
+
} catch (error) {
|
|
14964
|
+
logger.warn("Retained rejected-open writer close retry failed", { error: toError(error).message });
|
|
14965
|
+
}
|
|
14966
|
+
}
|
|
14967
|
+
// The loop only exits once the writer is terminal (`closed` or the quarantined
|
|
14968
|
+
// `close_unknown`) or was already cleared by a successful close, so ownership of
|
|
14969
|
+
// the descriptor and of the rejected session's resident state always ends here.
|
|
14970
|
+
if (this.#persistWriter) {
|
|
14971
|
+
this.#persistWriter = undefined;
|
|
14972
|
+
this.#persistWriterPath = undefined;
|
|
14973
|
+
}
|
|
14974
|
+
this.#releaseRejectedOpenResources();
|
|
14975
|
+
}
|
|
14976
|
+
|
|
14977
|
+
async #discardRejectedOpenState(): Promise<void> {
|
|
14978
|
+
let closeError: Error | undefined;
|
|
14979
|
+
for (let attempt = 0; this.#persistWriter && attempt < 2; attempt++) {
|
|
14980
|
+
try {
|
|
14981
|
+
await this.#closePersistWriterInternal();
|
|
14982
|
+
} catch (error) {
|
|
14983
|
+
closeError = toError(error);
|
|
14984
|
+
if (this.#persistWriter?.getCloseState() !== "close_failed_retryable") break;
|
|
14985
|
+
}
|
|
14986
|
+
}
|
|
14987
|
+
const state = this.#persistWriter?.getCloseState();
|
|
14988
|
+
if (state === "closed") {
|
|
14989
|
+
// The OS close was dispatched and confirmed, so cleanup succeeded even though
|
|
14990
|
+
// `close()` rethrew a queued write/flush drain failure. Reporting that drain
|
|
14991
|
+
// error as a cleanup failure would mask the real resume rejection, so record
|
|
14992
|
+
// it and let the caller surface the resume error alone.
|
|
14993
|
+
this.#persistWriter = undefined;
|
|
14994
|
+
this.#persistWriterPath = undefined;
|
|
14995
|
+
this.#releaseRejectedOpenResources();
|
|
14996
|
+
if (closeError)
|
|
14997
|
+
logger.warn("Rejected-open writer drained with errors before a confirmed close", {
|
|
14998
|
+
error: closeError.message,
|
|
14999
|
+
});
|
|
15000
|
+
return;
|
|
15001
|
+
}
|
|
15002
|
+
if (state === "close_unknown") {
|
|
15003
|
+
this.#persistWriter = undefined;
|
|
15004
|
+
this.#persistWriterPath = undefined;
|
|
15005
|
+
this.#releaseRejectedOpenResources();
|
|
15006
|
+
throw closeError ?? new Error("Rejected open writer close outcome is unknown");
|
|
15007
|
+
}
|
|
15008
|
+
if (this.#persistWriter) {
|
|
15009
|
+
// Certified pre-dispatch failure: the descriptor is still owned, so the writer
|
|
15010
|
+
// and the resident state it guards are retained until a retry reaches a
|
|
15011
|
+
// terminal close. The retry loop keeps this manager reachable on its own.
|
|
15012
|
+
void this.#retryRejectedOpenWriterCleanup().catch(error => {
|
|
15013
|
+
logger.warn("Rejected-open writer cleanup retry loop failed", { error: toError(error).message });
|
|
15014
|
+
});
|
|
15015
|
+
throw closeError ?? new Error("Rejected open writer close remains retryable");
|
|
15016
|
+
}
|
|
15017
|
+
this.#releaseRejectedOpenResources();
|
|
15018
|
+
}
|
|
15019
|
+
|
|
14637
15020
|
/** Close the persistent writer after flushing all pending data. */
|
|
14638
15021
|
async close(): Promise<void> {
|
|
14639
15022
|
// Drain any uncommitted prepared successors before releasing resources so
|
|
@@ -14645,19 +15028,34 @@ export class SessionManager {
|
|
|
14645
15028
|
error: toError(error).message,
|
|
14646
15029
|
});
|
|
14647
15030
|
}
|
|
14648
|
-
|
|
14649
|
-
|
|
14650
|
-
|
|
14651
|
-
|
|
14652
|
-
|
|
14653
|
-
|
|
14654
|
-
|
|
14655
|
-
|
|
14656
|
-
|
|
15031
|
+
let closeError: unknown;
|
|
15032
|
+
let taskStarted = false;
|
|
15033
|
+
try {
|
|
15034
|
+
await this.#queuePersistTask(
|
|
15035
|
+
async () => {
|
|
15036
|
+
taskStarted = true;
|
|
15037
|
+
if (this.#persistWriter) {
|
|
15038
|
+
await this.#closePersistWriterInternal();
|
|
15039
|
+
this.#flushed = true;
|
|
15040
|
+
}
|
|
15041
|
+
if (this.#needsFullRewriteOnNextPersist && !this.#readOnlyResume) await this.#rewriteFileContents();
|
|
15042
|
+
},
|
|
15043
|
+
{ ignoreError: this.#closeRetryPending },
|
|
15044
|
+
);
|
|
15045
|
+
this.#persistError = undefined;
|
|
15046
|
+
this.#persistErrorReported = false;
|
|
15047
|
+
this.#closeRetryPending = false;
|
|
15048
|
+
this.#retireEphemeralArtifacts();
|
|
15049
|
+
await this.#drainEphemeralArtifactCleanups();
|
|
15050
|
+
} catch (error) {
|
|
15051
|
+
closeError = error;
|
|
15052
|
+
if (taskStarted) this.#closeRetryPending = true;
|
|
15053
|
+
}
|
|
15054
|
+
const terminalError = closeError ?? this.#persistError;
|
|
15055
|
+
if (terminalError) throw terminalError;
|
|
14657
15056
|
this.#releaseResidentTextStore();
|
|
14658
|
-
this.#releaseOwnedManagedAuthority();
|
|
15057
|
+
if (this.#preparedNewSessions.size === 0) this.#releaseOwnedManagedAuthority();
|
|
14659
15058
|
this.#releaseClosedSessionState();
|
|
14660
|
-
if (this.#persistError) throw this.#persistError;
|
|
14661
15059
|
}
|
|
14662
15060
|
/** Flush while open, then strictly close; retryable close skips the invalid second flush. */
|
|
14663
15061
|
async flushAndCloseStrict(): Promise<SessionManagerCloseOutcome> {
|
|
@@ -14720,7 +15118,7 @@ export class SessionManager {
|
|
|
14720
15118
|
} catch (error) {
|
|
14721
15119
|
outcome = { kind: "close_unknown", error: toError(error) };
|
|
14722
15120
|
}
|
|
14723
|
-
this.#releaseOwnedManagedAuthority();
|
|
15121
|
+
if (this.#preparedNewSessions.size === 0) this.#releaseOwnedManagedAuthority();
|
|
14724
15122
|
this.#releaseClosedSessionState();
|
|
14725
15123
|
}
|
|
14726
15124
|
return outcome;
|
|
@@ -15646,9 +16044,25 @@ export class SessionManager {
|
|
|
15646
16044
|
const store = this.#managedTranscriptStore(sessionFile);
|
|
15647
16045
|
const relativePath = path.basename(sessionFile);
|
|
15648
16046
|
const bytes = Buffer.from(`${records.map(record => JSON.stringify(record)).join("\n")}\n`, "utf8");
|
|
15649
|
-
|
|
15650
|
-
|
|
15651
|
-
|
|
16047
|
+
let receipt: ManagedAppendReceipt;
|
|
16048
|
+
if (this.#managedPersistExpectedIdentity) {
|
|
16049
|
+
try {
|
|
16050
|
+
receipt = store.appendExpectedIdentitySync(relativePath, bytes, this.#managedPersistExpectedIdentity);
|
|
16051
|
+
} catch (err) {
|
|
16052
|
+
const predecessorMissing = store.descriptorExpected(relativePath) === null;
|
|
16053
|
+
if (
|
|
16054
|
+
!isEnoent(err) &&
|
|
16055
|
+
(!(err instanceof Error) || err.message !== "managed_append_identity_mismatch" || !predecessorMissing)
|
|
16056
|
+
)
|
|
16057
|
+
throw err;
|
|
16058
|
+
// Appending only the new records would create a truncated transcript.
|
|
16059
|
+
// Recreate the missing file from the complete resident entry set instead.
|
|
16060
|
+
this.#managedPersistExpectedIdentity = undefined;
|
|
16061
|
+
this.#rewriteFileSync();
|
|
16062
|
+
return;
|
|
16063
|
+
}
|
|
16064
|
+
} else receipt = store.appendSync(relativePath, bytes);
|
|
16065
|
+
this.#managedPersistExpectedIdentity = receipt.identity;
|
|
15652
16066
|
this.#publishSessionCommitMarkerSync(receipt.descriptor);
|
|
15653
16067
|
});
|
|
15654
16068
|
}
|
|
@@ -15806,9 +16220,62 @@ export class SessionManager {
|
|
|
15806
16220
|
// in-memory entries, shrinking the file below the limit. The entry has
|
|
15807
16221
|
// already been added to #fileEntries by #appendEntryWithinPersistenceFence.
|
|
15808
16222
|
if (err instanceof Error && err.message === "content_too_large") {
|
|
15809
|
-
|
|
16223
|
+
// Typed near-limit contract (#4566): recover by rewriting only the
|
|
16224
|
+
// live in-memory entries, then verify the recovered file actually
|
|
16225
|
+
// holds the just-appended entry. When even the rewrite cannot fit
|
|
16226
|
+
// the entry (live content alone is at the cap), surface the typed
|
|
16227
|
+
// near-limit outcome instead of silently succeeding without the
|
|
16228
|
+
// receipt for an effect that already committed (e.g. a source edit).
|
|
16229
|
+
const entryBytes = (() => {
|
|
16230
|
+
try {
|
|
16231
|
+
const materialized = materializeResidentEntryForPersistenceSync(
|
|
16232
|
+
entry,
|
|
16233
|
+
this.#residentBlobStores(),
|
|
16234
|
+
new Map(),
|
|
16235
|
+
);
|
|
16236
|
+
return Buffer.byteLength(
|
|
16237
|
+
`${JSON.stringify(prepareEntryForPersistenceSync(materialized, this.#blobStore))}\n`,
|
|
16238
|
+
"utf8",
|
|
16239
|
+
);
|
|
16240
|
+
} catch {
|
|
16241
|
+
return 0;
|
|
16242
|
+
}
|
|
16243
|
+
})();
|
|
16244
|
+
const liveBytesBefore = this.getTranscriptFileBytes();
|
|
16245
|
+
try {
|
|
16246
|
+
this.#rewriteFileSync();
|
|
16247
|
+
} catch (rewriteError) {
|
|
16248
|
+
// The recovery rewrite itself failed. The appended entry stays in
|
|
16249
|
+
// the resident list (its effect, including any committed source
|
|
16250
|
+
// edit, is not lost), but the receipt is not durable yet: report
|
|
16251
|
+
// the typed near-limit outcome instead of an unclassified abort.
|
|
16252
|
+
if (rewriteError instanceof Error && rewriteError.message === "content_too_large") {
|
|
16253
|
+
this.#needsFullRewriteOnNextPersist = true;
|
|
16254
|
+
throw new SessionNearLimitAppendError({
|
|
16255
|
+
entryBytes,
|
|
16256
|
+
liveBytes: liveBytesBefore,
|
|
16257
|
+
capBytes: MANAGED_ARTIFACT_MAX_FILE_BYTES,
|
|
16258
|
+
entryRetained: this.#byId.has(entry.id),
|
|
16259
|
+
});
|
|
16260
|
+
}
|
|
16261
|
+
throw rewriteError;
|
|
16262
|
+
}
|
|
15810
16263
|
if (publishResumeBreadcrumb) writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
|
|
15811
16264
|
this.#readOnlyResume = false;
|
|
16265
|
+
// Post-rewrite verification: a rewrite that still cannot fit the
|
|
16266
|
+
// entry leaves an effect/receipt gap and must be reported, never
|
|
16267
|
+
// silently swallowed as a successful append.
|
|
16268
|
+
const liveBytesAfter = this.getTranscriptFileBytes();
|
|
16269
|
+
const entryRetained = liveBytesAfter <= MANAGED_ARTIFACT_MAX_FILE_BYTES && this.#byId.has(entry.id);
|
|
16270
|
+
if (!entryRetained) {
|
|
16271
|
+
this.#needsFullRewriteOnNextPersist = true;
|
|
16272
|
+
throw new SessionNearLimitAppendError({
|
|
16273
|
+
entryBytes,
|
|
16274
|
+
liveBytes: liveBytesAfter || liveBytesBefore,
|
|
16275
|
+
capBytes: MANAGED_ARTIFACT_MAX_FILE_BYTES,
|
|
16276
|
+
entryRetained: this.#byId.has(entry.id),
|
|
16277
|
+
});
|
|
16278
|
+
}
|
|
15812
16279
|
return;
|
|
15813
16280
|
}
|
|
15814
16281
|
this.#recordPersistError(err);
|
|
@@ -15816,6 +16283,43 @@ export class SessionManager {
|
|
|
15816
16283
|
}
|
|
15817
16284
|
}
|
|
15818
16285
|
|
|
16286
|
+
/**
|
|
16287
|
+
* Defense-in-depth (#4443): detect directly adjacent thinking/redacted_thinking
|
|
16288
|
+
* blocks in a persisted assistant transcript message. Non-mutating read-only
|
|
16289
|
+
* diagnostic that fires at most once per session manager instance. Bounded to
|
|
16290
|
+
* development/test builds only via an explicit environment gate so normal
|
|
16291
|
+
* production never emits the diagnostic or transcript-path metadata.
|
|
16292
|
+
*
|
|
16293
|
+
* Only Anthropic-origin assistant messages are inspected: the adjacency class
|
|
16294
|
+
* is specific to the Anthropic wire contract (#4416), and a provider-neutral
|
|
16295
|
+
* check would false-positive on OpenAI Responses assemblies that emit one
|
|
16296
|
+
* internal `thinking` block per reasoning output item.
|
|
16297
|
+
*
|
|
16298
|
+
* Names only the envelope shape (content block count), never raw thinking
|
|
16299
|
+
* text, signatures, or redacted payloads.
|
|
16300
|
+
*/
|
|
16301
|
+
#warnAdjacentPrivateThinking(message: Parameters<typeof this.appendMessage>[0]): void {
|
|
16302
|
+
if (this.#warnedAdjacentThinkingPersist) return;
|
|
16303
|
+
// Explicit environment gate: production builds must not emit this
|
|
16304
|
+
// diagnostic or transcript-path metadata. `NODE_ENV` is the canonical
|
|
16305
|
+
// build-mode signal; the gate is robust to undefined (default: silent).
|
|
16306
|
+
if (process.env.NODE_ENV !== "development" && process.env.NODE_ENV !== "test") return;
|
|
16307
|
+
// Only assistant messages carry content block arrays with thinking blocks.
|
|
16308
|
+
if (message?.role !== "assistant" || !Array.isArray(message.content)) return;
|
|
16309
|
+
// Restrict to Anthropic-origin: the adjacency rejection is specific to the
|
|
16310
|
+
// Anthropic replay wire contract. Other providers tolerate adjacent
|
|
16311
|
+
// reasoning blocks, so a provider-neutral check would be a false positive.
|
|
16312
|
+
if (message.api !== "anthropic-messages") return;
|
|
16313
|
+
if (!hasAdjacentPrivateThinkingBlocks(message.content)) return;
|
|
16314
|
+
this.#warnedAdjacentThinkingPersist = true;
|
|
16315
|
+
logger.warn("Session transcript: persisted assistant message has adjacent thinking blocks", {
|
|
16316
|
+
role: "assistant",
|
|
16317
|
+
provider: message.provider,
|
|
16318
|
+
contentBlockCount: message.content.length,
|
|
16319
|
+
hasAdjacentPrivateBlocks: true,
|
|
16320
|
+
});
|
|
16321
|
+
}
|
|
16322
|
+
|
|
15819
16323
|
#appendEntry(entry: SessionEntry): void {
|
|
15820
16324
|
this.#withSessionPersistenceFenceSync(() => this.#appendEntryWithinPersistenceFence(entry));
|
|
15821
16325
|
}
|
|
@@ -15874,6 +16378,16 @@ export class SessionManager {
|
|
|
15874
16378
|
this.#needsFullRewriteOnNextPersist = true;
|
|
15875
16379
|
throw new SessionAppendPersistenceError("current_append", residentEntry.id, this.#persistError ?? error);
|
|
15876
16380
|
}
|
|
16381
|
+
// Typed near-limit recovery (#4566) already ran its deterministic
|
|
16382
|
+
// rewrite inside _persist and deliberately kept the entry resident so
|
|
16383
|
+
// the committed effect keeps its receipt. Do not roll it back or wrap
|
|
16384
|
+
// it into a generic SessionAppendPersistenceError: propagate the typed
|
|
16385
|
+
// outcome with its structured fields intact.
|
|
16386
|
+
if (error instanceof SessionNearLimitAppendError) {
|
|
16387
|
+
if (sidecarAppendCharge > 0) activeRuntime?.accountant.release(sidecarAppendCharge);
|
|
16388
|
+
if (sidecarTailCharge > 0) activeRuntime?.tailCache.release(sidecarTailCharge);
|
|
16389
|
+
throw error;
|
|
16390
|
+
}
|
|
15877
16391
|
const removed = this.#fileEntries.pop();
|
|
15878
16392
|
if (removed !== residentEntry)
|
|
15879
16393
|
throw new Error("Session append rollback lost resident ordering.", { cause: error });
|
|
@@ -16031,6 +16545,12 @@ export class SessionManager {
|
|
|
16031
16545
|
message,
|
|
16032
16546
|
};
|
|
16033
16547
|
associateSessionMessageEntryId(message, entry.id);
|
|
16548
|
+
// Defense-in-depth (#4443): detect directly adjacent thinking/redacted_thinking
|
|
16549
|
+
// blocks in a persisted assistant transcript message and warn once per session.
|
|
16550
|
+
// This is a read-only observation — storage is NEVER mutated. The diagnostic is
|
|
16551
|
+
// bounded to development/test to avoid production noise, and names only the
|
|
16552
|
+
// envelope shape (block count), never raw thinking text, signatures, or payloads.
|
|
16553
|
+
this.#warnAdjacentPrivateThinking(message);
|
|
16034
16554
|
this.#appendEntry(entry);
|
|
16035
16555
|
const residentEntry = this.#byId.get(entry.id);
|
|
16036
16556
|
if (residentEntry?.type === "message") transferSessionMessageIdentity([message], [residentEntry.message]);
|
|
@@ -17460,6 +17980,7 @@ export class SessionManager {
|
|
|
17460
17980
|
this.#sessionFile = newSessionFile;
|
|
17461
17981
|
this.#flushed = true;
|
|
17462
17982
|
this.#commitResidentTextStoreTransition(transition);
|
|
17983
|
+
this.#adoptManagedPersistIdentity(newSessionFile);
|
|
17463
17984
|
return newSessionFile;
|
|
17464
17985
|
}
|
|
17465
17986
|
|
|
@@ -18156,6 +18677,7 @@ export class SessionManager {
|
|
|
18156
18677
|
throw error;
|
|
18157
18678
|
}
|
|
18158
18679
|
}
|
|
18680
|
+
let retainedFallbackSnapshot: ManagedFileSnapshot | undefined;
|
|
18159
18681
|
let sourceSize: number | undefined;
|
|
18160
18682
|
try {
|
|
18161
18683
|
sourceSize = boundedStorage.statSync(managedSourcePath).size;
|
|
@@ -18171,12 +18693,32 @@ export class SessionManager {
|
|
|
18171
18693
|
)
|
|
18172
18694
|
return manager;
|
|
18173
18695
|
}
|
|
18696
|
+
if (manager.#boundedManagedSource && sourceSize <= eagerHydrationMaxBytes()) {
|
|
18697
|
+
const boundedSource = manager.#boundedManagedSource;
|
|
18698
|
+
retainedFallbackSnapshot = boundedSource.store.readExpected(path.basename(managedSourcePath)) ?? undefined;
|
|
18699
|
+
const terminalDescriptor = boundedSource.store.descriptorExpected(path.basename(managedSourcePath));
|
|
18700
|
+
if (
|
|
18701
|
+
!retainedFallbackSnapshot ||
|
|
18702
|
+
!terminalDescriptor ||
|
|
18703
|
+
!sameDescriptor(boundedSource.descriptor, terminalDescriptor) ||
|
|
18704
|
+
!managedFileSnapshotMatchesDescriptor(retainedFallbackSnapshot, boundedSource.descriptor)
|
|
18705
|
+
)
|
|
18706
|
+
throw new Error("source_changed");
|
|
18707
|
+
}
|
|
18174
18708
|
} finally {
|
|
18175
18709
|
manager.#clearBoundedManagedSource();
|
|
18176
18710
|
}
|
|
18177
18711
|
if (sourceSize === undefined) throw new Error("Managed source size unavailable");
|
|
18178
18712
|
if (sourceSize > eagerHydrationMaxBytes()) throw new SessionTranscriptOversizedError(sourceSize);
|
|
18179
|
-
const forkEntries =
|
|
18713
|
+
const forkEntries = retainedFallbackSnapshot
|
|
18714
|
+
? (() => {
|
|
18715
|
+
const entries = parseSessionEntries(
|
|
18716
|
+
new TextDecoder("utf-8", { fatal: true }).decode(retainedFallbackSnapshot.bytes),
|
|
18717
|
+
);
|
|
18718
|
+
const header = entries[0] as SessionHeader | undefined;
|
|
18719
|
+
return header?.type === "session" && typeof header.id === "string" ? entries : [];
|
|
18720
|
+
})()
|
|
18721
|
+
: await loadEntriesFromFile(managedSourcePath, storage);
|
|
18180
18722
|
migrateToCurrentVersion(forkEntries);
|
|
18181
18723
|
await resolveBlobRefsInEntries(forkEntries, manager.#blobStore);
|
|
18182
18724
|
const sourceHeader = forkEntries.find(entry => entry.type === "session") as SessionHeader | undefined;
|
|
@@ -18298,17 +18840,46 @@ export class SessionManager {
|
|
|
18298
18840
|
destination,
|
|
18299
18841
|
);
|
|
18300
18842
|
manager.#sessionMemoryMode = sessionMemoryMode;
|
|
18301
|
-
|
|
18302
|
-
|
|
18303
|
-
|
|
18304
|
-
|
|
18305
|
-
|
|
18306
|
-
|
|
18307
|
-
|
|
18843
|
+
try {
|
|
18844
|
+
await manager.#initSessionFile(
|
|
18845
|
+
filePath,
|
|
18846
|
+
false,
|
|
18847
|
+
strictSmallInspection ? { inspection: strictSmallInspection, storage, reuseEntries: false } : undefined,
|
|
18848
|
+
);
|
|
18849
|
+
if (strictSmallInspection) {
|
|
18850
|
+
if (
|
|
18851
|
+
!revalidateStrictResumeInspection(filePath, storage, strictSmallInspection) ||
|
|
18852
|
+
manager.#sessionId !== strictSmallInspection.identity.sessionId
|
|
18853
|
+
)
|
|
18854
|
+
throw new Error("Could not open session: unstable");
|
|
18855
|
+
await manager.#sanitizeLoadedOpenAIResponsesReplayMetadataAndPersist();
|
|
18308
18856
|
}
|
|
18857
|
+
manager.buildSessionContext();
|
|
18858
|
+
return manager;
|
|
18859
|
+
} catch (error) {
|
|
18860
|
+
manager.setSessionMemoryMode("off");
|
|
18861
|
+
for (const sidecarPath of manager.#disposableSidecarPaths()) {
|
|
18862
|
+
if (!sidecarPath) continue;
|
|
18863
|
+
try {
|
|
18864
|
+
manager.#storage.unlinkSync(sidecarPath);
|
|
18865
|
+
} catch (cleanupError) {
|
|
18866
|
+
if (!isEnoent(cleanupError))
|
|
18867
|
+
logger.warn("Rejected explicit resume sidecar cleanup failed", {
|
|
18868
|
+
error: toError(cleanupError).message,
|
|
18869
|
+
});
|
|
18870
|
+
}
|
|
18871
|
+
}
|
|
18872
|
+
manager.#sidecarRuntime = undefined;
|
|
18873
|
+
try {
|
|
18874
|
+
await manager.#discardRejectedOpenState();
|
|
18875
|
+
} catch (cleanupError) {
|
|
18876
|
+
throw new AggregateError(
|
|
18877
|
+
[toError(error), toError(cleanupError)],
|
|
18878
|
+
"Rejected explicit resume cleanup failed",
|
|
18879
|
+
);
|
|
18880
|
+
}
|
|
18881
|
+
throw error;
|
|
18309
18882
|
}
|
|
18310
|
-
manager.buildSessionContext();
|
|
18311
|
-
return manager;
|
|
18312
18883
|
}
|
|
18313
18884
|
const sameManagedDirectory = path.dirname(path.resolve(filePath)) === path.resolve(destination.directory);
|
|
18314
18885
|
let managedInspectionStore: ManagedSessionDescendantStore | undefined;
|
|
@@ -18319,16 +18890,68 @@ export class SessionManager {
|
|
|
18319
18890
|
}
|
|
18320
18891
|
let managedSourceSize: number | undefined;
|
|
18321
18892
|
try {
|
|
18893
|
+
await SessionManagerTestHooks.beforeManagedSourceStat?.(filePath, managedInspectionStorage);
|
|
18322
18894
|
managedSourceSize = managedInspectionStorage.statSync(filePath).size;
|
|
18323
|
-
} catch {
|
|
18324
|
-
|
|
18895
|
+
} catch (error) {
|
|
18896
|
+
if (!isEnoent(error)) {
|
|
18897
|
+
managedInspectionStore?.close();
|
|
18898
|
+
throw error;
|
|
18899
|
+
}
|
|
18900
|
+
if (managedInspectionStore) {
|
|
18901
|
+
managedInspectionStore.assertBound();
|
|
18902
|
+
await SessionManagerTestHooks.beforeManagedMissingInit?.(filePath, managedInspectionStorage);
|
|
18903
|
+
managedInspectionStore.assertBound();
|
|
18904
|
+
const manager = new SessionManager(
|
|
18905
|
+
getProjectDir(),
|
|
18906
|
+
destination.directory,
|
|
18907
|
+
true,
|
|
18908
|
+
storage,
|
|
18909
|
+
destination,
|
|
18910
|
+
true,
|
|
18911
|
+
);
|
|
18912
|
+
manager.#sessionMemoryMode = sessionMemoryMode;
|
|
18913
|
+
try {
|
|
18914
|
+
await SessionManagerTestHooks.beforeManagedMissingPublish?.(filePath, managedInspectionStorage);
|
|
18915
|
+
managedInspectionStore.assertBound();
|
|
18916
|
+
await SessionManagerTestHooks.afterManagedMissingAssertion?.(filePath, managedInspectionStorage);
|
|
18917
|
+
const fresh = manager.#freshSessionState(undefined, filePath);
|
|
18918
|
+
const prepared = manager.#prepareFreshSessionTransition(fresh, "memory-fallback");
|
|
18919
|
+
manager.#applyFreshSessionMetadata(fresh);
|
|
18920
|
+
manager.#commitResidentTextStoreTransition(prepared, false);
|
|
18921
|
+
manager.#retireEphemeralArtifacts();
|
|
18922
|
+
const content = `${JSON.stringify(prepareEntryForPersistenceSync(fresh.header, manager.#blobStore))}\n`;
|
|
18923
|
+
managedInspectionStore.publishNoReplaceSync(path.basename(filePath), Buffer.from(content, "utf8"));
|
|
18924
|
+
const publishedTranscript = managedInspectionStore.readExpected(path.basename(filePath));
|
|
18925
|
+
if (publishedTranscript?.bytes?.equals(Buffer.from(content, "utf8")) !== true)
|
|
18926
|
+
throw new Error("Could not open session: unstable");
|
|
18927
|
+
if (manager.#effectiveSessionMemoryMode() !== "off") {
|
|
18928
|
+
manager.#buildDisposableSidecars(manager.#fileEntries);
|
|
18929
|
+
if (manager.#coldSidecarActive()) manager.#retireColdEntries();
|
|
18930
|
+
}
|
|
18931
|
+
manager.#flushed = true;
|
|
18932
|
+
manager.#ensuredOnDisk = true;
|
|
18933
|
+
await SessionManagerTestHooks.beforeManagedMissingReturn?.(filePath, managedInspectionStorage);
|
|
18934
|
+
const returnTranscript = managedInspectionStore.readExpected(path.basename(filePath));
|
|
18935
|
+
if (!returnTranscript || !managedFileSnapshotEquals(returnTranscript, publishedTranscript))
|
|
18936
|
+
throw new Error("Could not open session: unstable");
|
|
18937
|
+
manager.#managedPersistExpectedIdentity = returnTranscript.identity;
|
|
18938
|
+
writeTerminalBreadcrumb(manager.cwd, filePath);
|
|
18939
|
+
return manager;
|
|
18940
|
+
} catch (createError) {
|
|
18941
|
+
await manager.#discardRejectedOpenState();
|
|
18942
|
+
throw createError;
|
|
18943
|
+
} finally {
|
|
18944
|
+
managedInspectionStore.close();
|
|
18945
|
+
}
|
|
18946
|
+
}
|
|
18947
|
+
// Cross-directory missing candidates continue through the migration path below.
|
|
18325
18948
|
}
|
|
18326
18949
|
const managedResumeBounded =
|
|
18327
|
-
|
|
18328
|
-
(sessionMemoryMode === "
|
|
18329
|
-
|
|
18330
|
-
|
|
18331
|
-
|
|
18950
|
+
managedSourceSize !== undefined &&
|
|
18951
|
+
(sessionMemoryMode === "enabled" ||
|
|
18952
|
+
(sessionMemoryMode === "auto" &&
|
|
18953
|
+
process.platform !== "win32" &&
|
|
18954
|
+
managedSourceSize >= autoModeMinTranscriptBytes()));
|
|
18332
18955
|
let strictManagedSmallInspection: ResumeInspectionSnapshot | undefined;
|
|
18333
18956
|
if (
|
|
18334
18957
|
managedResumeBounded &&
|
|
@@ -18345,32 +18968,78 @@ export class SessionManager {
|
|
|
18345
18968
|
throw error;
|
|
18346
18969
|
}
|
|
18347
18970
|
}
|
|
18971
|
+
const managedBoundedDescriptor =
|
|
18972
|
+
managedResumeBounded && sameManagedDirectory
|
|
18973
|
+
? managedInspectionStore?.descriptorExpected(path.basename(filePath))
|
|
18974
|
+
: undefined;
|
|
18975
|
+
if (managedResumeBounded && sameManagedDirectory && !managedBoundedDescriptor) {
|
|
18976
|
+
managedInspectionStore?.close();
|
|
18977
|
+
throw new Error("Could not open session: unstable");
|
|
18978
|
+
}
|
|
18348
18979
|
|
|
18349
18980
|
if (managedResumeBounded && sameManagedDirectory) {
|
|
18350
18981
|
const manager = new SessionManager(getProjectDir(), destination.directory, true, storage, destination);
|
|
18351
18982
|
manager.#sessionMemoryMode = sessionMemoryMode;
|
|
18352
18983
|
try {
|
|
18353
|
-
await manager.#initSessionFile(
|
|
18984
|
+
await manager.#initSessionFile(
|
|
18985
|
+
filePath,
|
|
18986
|
+
true,
|
|
18987
|
+
strictManagedSmallInspection
|
|
18988
|
+
? { inspection: strictManagedSmallInspection, storage: managedInspectionStorage }
|
|
18989
|
+
: undefined,
|
|
18990
|
+
true,
|
|
18991
|
+
true,
|
|
18992
|
+
);
|
|
18993
|
+
SessionManagerTestHooks.beforeManagedResumeAcceptance?.(filePath, managedInspectionStorage);
|
|
18994
|
+
const terminalManagedDescriptor = managedInspectionStore?.descriptorExpected(path.basename(filePath));
|
|
18995
|
+
if (
|
|
18996
|
+
!managedBoundedDescriptor ||
|
|
18997
|
+
!terminalManagedDescriptor ||
|
|
18998
|
+
!sameDescriptor(managedBoundedDescriptor, terminalManagedDescriptor)
|
|
18999
|
+
)
|
|
19000
|
+
throw new Error("Could not open session: unstable");
|
|
18354
19001
|
if (strictManagedSmallInspection) {
|
|
18355
|
-
const revalidated = revalidateTranscriptIdentityBounded(
|
|
18356
|
-
filePath,
|
|
18357
|
-
managedInspectionStorage,
|
|
18358
|
-
strictManagedSmallInspection.identity,
|
|
18359
|
-
);
|
|
18360
19002
|
if (
|
|
18361
|
-
|
|
19003
|
+
!revalidateStrictResumeInspection(filePath, managedInspectionStorage, strictManagedSmallInspection) ||
|
|
18362
19004
|
manager.#sessionId !== strictManagedSmallInspection.identity.sessionId
|
|
18363
19005
|
) {
|
|
18364
19006
|
manager.setSessionMemoryMode("off");
|
|
18365
19007
|
throw new Error("Could not open session: unstable");
|
|
18366
19008
|
}
|
|
18367
19009
|
}
|
|
19010
|
+
if (manager.#sanitizeLoadedOpenAIResponsesReplayMetadata().length > 0)
|
|
19011
|
+
manager.#needsFullRewriteOnNextPersist = true;
|
|
19012
|
+
SessionManagerTestHooks.beforeManagedResumeReturn?.(filePath, managedInspectionStorage);
|
|
19013
|
+
const returnManagedDescriptor = managedInspectionStore?.descriptorExpected(path.basename(filePath));
|
|
19014
|
+
if (!returnManagedDescriptor || !sameDescriptor(managedBoundedDescriptor, returnManagedDescriptor))
|
|
19015
|
+
throw new Error("Could not open session: unstable");
|
|
19016
|
+
manager.#managedPersistExpectedIdentity = managedIdentityFromDescriptor(returnManagedDescriptor);
|
|
18368
19017
|
const header = manager.#fileEntries.find(entry => entry.type === "session") as SessionHeader | undefined;
|
|
18369
19018
|
if (header?.cwd) manager.cwd = header.cwd;
|
|
18370
19019
|
manager.buildSessionContext();
|
|
18371
19020
|
return manager;
|
|
18372
19021
|
} catch (error) {
|
|
18373
|
-
|
|
19022
|
+
manager.setSessionMemoryMode("off");
|
|
19023
|
+
for (const sidecarPath of manager.#disposableSidecarPaths()) {
|
|
19024
|
+
if (!sidecarPath) continue;
|
|
19025
|
+
try {
|
|
19026
|
+
manager.#storage.unlinkSync(sidecarPath);
|
|
19027
|
+
} catch (cleanupError) {
|
|
19028
|
+
if (!isEnoent(cleanupError))
|
|
19029
|
+
logger.warn("Rejected managed resume sidecar cleanup failed", {
|
|
19030
|
+
error: toError(cleanupError).message,
|
|
19031
|
+
});
|
|
19032
|
+
}
|
|
19033
|
+
}
|
|
19034
|
+
manager.#sidecarRuntime = undefined;
|
|
19035
|
+
try {
|
|
19036
|
+
await manager.#discardRejectedOpenState();
|
|
19037
|
+
} catch (cleanupError) {
|
|
19038
|
+
throw new AggregateError(
|
|
19039
|
+
[toError(error), toError(cleanupError)],
|
|
19040
|
+
"Rejected managed resume cleanup failed",
|
|
19041
|
+
);
|
|
19042
|
+
}
|
|
18374
19043
|
throw error;
|
|
18375
19044
|
} finally {
|
|
18376
19045
|
managedInspectionStore?.close();
|
|
@@ -18462,6 +19131,7 @@ export class SessionManager {
|
|
|
18462
19131
|
const finalDescriptor = store.descriptorExpected(path.basename(resolved));
|
|
18463
19132
|
if (!finalDescriptor || !sameDescriptor(capturedDescriptor, finalDescriptor))
|
|
18464
19133
|
throw new Error("source_changed");
|
|
19134
|
+
boundedManager.#managedPersistExpectedIdentity = managedIdentityFromDescriptor(finalDescriptor);
|
|
18465
19135
|
boundedManager.buildSessionContext();
|
|
18466
19136
|
return boundedManager;
|
|
18467
19137
|
}
|
|
@@ -18901,6 +19571,7 @@ export class SessionManager {
|
|
|
18901
19571
|
manager.destination = destination;
|
|
18902
19572
|
manager.#sessionFile = finalSessionFile;
|
|
18903
19573
|
manager.#commitResidentTextStoreTransition(finalTransition);
|
|
19574
|
+
manager.#adoptManagedPersistIdentity(finalSessionFile);
|
|
18904
19575
|
} catch (error) {
|
|
18905
19576
|
finalTransition.dispose();
|
|
18906
19577
|
throw error;
|
|
@@ -19251,6 +19922,7 @@ export class SessionManager {
|
|
|
19251
19922
|
this.#sessionFile = promotionPath;
|
|
19252
19923
|
this.#recoveryPromotionTranscriptPath = undefined;
|
|
19253
19924
|
this.#commitResidentTextStoreTransition(transition);
|
|
19925
|
+
this.#adoptManagedPersistIdentity(promotionPath);
|
|
19254
19926
|
writeTerminalBreadcrumb(this.cwd, promotionPath);
|
|
19255
19927
|
this.#recoveryHydrationContext = undefined;
|
|
19256
19928
|
}
|