@myagentroam/agent 0.11.33 → 0.11.35
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/dist/cli/catalog-file.d.ts +78 -0
- package/dist/cli/catalog-file.js +102 -0
- package/dist/cli/codex-auth-file.d.ts +1 -1
- package/dist/cli/codex-auth-file.js +9 -10
- package/dist/cli/main.js +44 -54
- package/dist/error.d.ts +15 -0
- package/dist/error.js +74 -0
- package/dist/extensions/agents.d.ts +100 -0
- package/dist/extensions/agents.js +121 -0
- package/dist/extensions/capabilities.d.ts +4 -0
- package/dist/extensions/commands.d.ts +23 -0
- package/dist/extensions/commands.js +45 -0
- package/dist/extensions/compaction.d.ts +36 -0
- package/dist/extensions/contracts.d.ts +147 -0
- package/dist/extensions/data.d.ts +81 -0
- package/dist/extensions/data.js +78 -0
- package/dist/extensions/environment.d.ts +39 -0
- package/dist/extensions/environment.js +52 -0
- package/dist/extensions/files.d.ts +68 -0
- package/dist/extensions/files.js +26 -0
- package/dist/extensions/hooks.d.ts +32 -0
- package/dist/extensions/host-ports.d.ts +24 -0
- package/dist/extensions/manager.d.ts +42 -0
- package/dist/extensions/manager.js +457 -0
- package/dist/extensions/mcp.d.ts +116 -0
- package/dist/extensions/mcp.js +434 -0
- package/dist/extensions/model-request.d.ts +65 -0
- package/dist/extensions/model-request.js +197 -0
- package/dist/extensions/model-stream.d.ts +18 -0
- package/dist/extensions/model-stream.js +108 -0
- package/dist/extensions/models.d.ts +46 -0
- package/dist/extensions/models.js +7 -0
- package/dist/extensions/network.d.ts +40 -0
- package/dist/extensions/network.js +97 -0
- package/dist/extensions/operation.d.ts +32 -0
- package/dist/extensions/processes.d.ts +64 -0
- package/dist/extensions/processes.js +284 -0
- package/dist/extensions/prompt-composition.d.ts +38 -0
- package/dist/extensions/prompt-composition.js +100 -0
- package/dist/extensions/prompt-input.d.ts +30 -0
- package/dist/extensions/prompt-input.js +48 -0
- package/dist/extensions/prompts.d.ts +31 -0
- package/dist/extensions/prompts.js +88 -0
- package/dist/extensions/resources.d.ts +26 -0
- package/dist/extensions/resources.js +221 -0
- package/dist/extensions/services.d.ts +31 -0
- package/dist/extensions/services.js +61 -0
- package/dist/extensions/session-api.d.ts +6 -0
- package/dist/extensions/session-api.js +52 -0
- package/dist/extensions/session-input.d.ts +184 -0
- package/dist/extensions/session-input.js +422 -0
- package/dist/extensions/session-messages.d.ts +41 -0
- package/dist/extensions/session-messages.js +76 -0
- package/dist/extensions/session-tools.d.ts +32 -0
- package/dist/extensions/session-tools.js +208 -0
- package/dist/extensions/session.d.ts +60 -0
- package/dist/extensions/session.js +265 -0
- package/dist/extensions/settings.d.ts +76 -0
- package/dist/extensions/settings.js +115 -0
- package/dist/extensions/store.d.ts +10 -0
- package/dist/extensions/store.js +131 -0
- package/dist/extensions/tool-runtime.d.ts +16 -0
- package/dist/extensions/tools.d.ts +80 -0
- package/dist/extensions/tools.js +270 -0
- package/dist/extensions.d.ts +22 -0
- package/dist/host/contracts.d.ts +58 -0
- package/dist/host/contracts.js +18 -0
- package/dist/host/description.d.ts +19 -0
- package/dist/host/local-agent-host.d.ts +96 -0
- package/dist/host/local-agent-host.js +429 -0
- package/dist/host/local-exec-tool.d.ts +26 -0
- package/dist/host/local-exec-tool.js +686 -0
- package/dist/host/local-file-access.d.ts +30 -0
- package/dist/host/local-file-access.js +107 -0
- package/dist/host/local-network-access.d.ts +6 -0
- package/dist/host/local-network-access.js +101 -0
- package/dist/host/local-process-executor.d.ts +5 -0
- package/dist/host/local-process-executor.js +17 -0
- package/dist/host/local-session-environment.d.ts +18 -0
- package/dist/host/local-shell.d.ts +3 -0
- package/dist/host/local-tool-registry.d.ts +34 -0
- package/dist/host/local-tool-registry.js +134 -0
- package/dist/host/network.d.ts +5 -0
- package/dist/host/network.js +42 -0
- package/dist/host/process-control.d.ts +2 -0
- package/dist/host/process-control.js +26 -0
- package/dist/host/skill-contract.d.ts +6 -0
- package/dist/host.d.ts +17 -0
- package/dist/host.js +3 -0
- package/dist/index.d.ts +26 -51
- package/dist/index.js +9 -12
- package/dist/model/anthropic-messages.d.ts +10 -0
- package/dist/model/anthropic-messages.js +692 -0
- package/dist/model/anthropic-thinking.js +33 -0
- package/dist/model/configuration.d.ts +111 -0
- package/dist/model/configuration.js +201 -0
- package/dist/model/contracts.d.ts +229 -0
- package/dist/model/http.d.ts +13 -0
- package/dist/model/http.js +119 -0
- package/dist/model/image-contract.d.ts +10 -0
- package/dist/model/image.d.ts +9 -0
- package/dist/model/inline-media-budget.d.ts +7 -0
- package/dist/model/inline-media-budget.js +36 -0
- package/dist/model/input-images.d.ts +3 -0
- package/dist/model/input-images.js +16 -0
- package/dist/model/openai-responses.d.ts +59 -0
- package/dist/model/openai-responses.js +1586 -0
- package/dist/model/recovery.js +80 -0
- package/dist/model/request-configuration.d.ts +17 -0
- package/dist/model/request-configuration.js +47 -0
- package/dist/model/responses-event-projection.d.ts +24 -0
- package/dist/model/responses-event-projection.js +194 -0
- package/dist/model/responses-request.d.ts +5 -0
- package/dist/model/responses-request.js +363 -0
- package/dist/model/responses-session-cache.d.ts +33 -0
- package/dist/model/responses-session-cache.js +64 -0
- package/dist/model/runtime-credential.d.ts +1 -0
- package/dist/model/runtime-credential.js +1 -0
- package/dist/model/sse.js +281 -0
- package/dist/model/text-blocks.js +40 -0
- package/dist/model/tool-declarations.d.ts +4 -0
- package/dist/model/tool-definition-projection.d.ts +6 -0
- package/dist/model/tool-definition-projection.js +11 -0
- package/dist/model/tool-schema.d.ts +2 -0
- package/dist/model/tool-schema.js +17 -0
- package/dist/model/transport-test.d.ts +10 -0
- package/dist/model/transport-test.js +27 -0
- package/dist/model/usage-observer.d.ts +5 -0
- package/dist/model/usage.d.ts +40 -0
- package/dist/network/credential-runtime.d.ts +8 -0
- package/dist/network/credential-runtime.js +72 -0
- package/dist/network/credentials.d.ts +20 -0
- package/dist/network/credentials.js +18 -0
- package/dist/network/http.d.ts +20 -0
- package/dist/network/http.js +421 -0
- package/dist/network/product-user-agent.d.ts +5 -0
- package/dist/network/product-user-agent.js +22 -0
- package/dist/network/transport-error.d.ts +5 -0
- package/dist/network/transport-error.js +31 -0
- package/dist/process/contracts.d.ts +26 -0
- package/dist/prompts/index.d.ts +20 -0
- package/dist/prompts/index.js +49 -0
- package/dist/prompts/prompt-sections.d.ts +6 -0
- package/dist/prompts/prompt-sections.js +6 -0
- package/dist/prompts/read-context.js +152 -0
- package/dist/runtime/compaction/contracts.d.ts +23 -0
- package/dist/runtime/compaction/coordinator.js +88 -0
- package/dist/runtime/compaction/generate.d.ts +31 -0
- package/dist/runtime/compaction/generate.js +84 -0
- package/dist/runtime/compaction/history.d.ts +16 -0
- package/dist/runtime/compaction/operation.d.ts +30 -0
- package/dist/runtime/compaction/operation.js +131 -0
- package/dist/runtime/compaction/requests.d.ts +16 -0
- package/dist/runtime/compaction/requests.js +114 -0
- package/dist/runtime/compaction/retained-context.d.ts +6 -0
- package/dist/runtime/compaction/retained-context.js +48 -0
- package/dist/runtime/compaction/smart.d.ts +14 -0
- package/dist/runtime/compaction/smart.js +59 -0
- package/dist/runtime/context/contextual-blocks.d.ts +31 -0
- package/dist/runtime/context/contextual-blocks.js +158 -0
- package/dist/runtime/context/environment.d.ts +5 -0
- package/dist/runtime/context/gc-candidates-code.d.ts +6 -0
- package/dist/runtime/context/gc-candidates-code.js +102 -0
- package/dist/runtime/context/gc-candidates-exec.d.ts +6 -0
- package/dist/runtime/context/gc-candidates-exec.js +131 -0
- package/dist/runtime/context/gc-candidates-read-patch.d.ts +7 -0
- package/dist/runtime/context/gc-candidates-read-patch.js +118 -0
- package/dist/runtime/context/gc-candidates-subagent.d.ts +6 -0
- package/dist/runtime/context/gc-candidates-subagent.js +100 -0
- package/dist/runtime/context/gc-candidates-web.d.ts +6 -0
- package/dist/runtime/context/gc-candidates-web.js +58 -0
- package/dist/runtime/context/gc-candidates.d.ts +12 -0
- package/dist/runtime/context/gc-candidates.js +192 -0
- package/dist/runtime/context/gc-contracts.d.ts +57 -0
- package/dist/runtime/context/gc-economics.d.ts +48 -0
- package/dist/runtime/context/gc-economics.js +161 -0
- package/dist/runtime/context/gc-runtime.d.ts +43 -0
- package/dist/runtime/context/gc-tokens.d.ts +26 -0
- package/dist/runtime/context/gc-tokens.js +106 -0
- package/dist/runtime/context/gc.d.ts +7 -0
- package/dist/runtime/context/gc.js +552 -0
- package/dist/runtime/context/instructions.d.ts +37 -0
- package/dist/runtime/context/instructions.js +297 -0
- package/dist/runtime/context/normalize.d.ts +5 -0
- package/dist/runtime/context/projection.d.ts +23 -0
- package/dist/runtime/context/projection.js +284 -0
- package/dist/runtime/context/request-budget.d.ts +5 -0
- package/dist/runtime/context/request-budget.js +25 -0
- package/dist/runtime/context/restore.d.ts +27 -0
- package/dist/runtime/context/restore.js +186 -0
- package/dist/runtime/context/usage.d.ts +17 -0
- package/dist/runtime/context/usage.js +124 -0
- package/dist/runtime/context/world-state.d.ts +34 -0
- package/dist/runtime/context/world-state.js +232 -0
- package/dist/runtime/contracts.d.ts +29 -0
- package/dist/runtime/event-projection.d.ts +14 -0
- package/dist/runtime/event-projection.js +87 -0
- package/dist/runtime/events.d.ts +195 -0
- package/dist/runtime/execution-handle.d.ts +12 -0
- package/dist/runtime/execution-handle.js +1 -0
- package/dist/runtime/execution.d.ts +55 -0
- package/dist/runtime/execution.js +444 -0
- package/dist/runtime/model-progress.js +85 -0
- package/dist/runtime/resource-publication.d.ts +27 -0
- package/dist/runtime/resource-publication.js +60 -0
- package/dist/runtime/tool-round-contract.d.ts +19 -0
- package/dist/runtime/tool-round-contract.js +1 -0
- package/dist/runtime/wait.js +82 -0
- package/dist/sandbox/linux/bubblewrap-sandbox-executor.d.ts +1 -1
- package/dist/sandbox/linux/bubblewrap-sandbox-executor.js +8 -8
- package/dist/sdk/agent.d.ts +7 -0
- package/dist/sdk/agent.js +321 -0
- package/dist/sdk/execution-code-mode.d.ts +18 -0
- package/dist/sdk/execution-code-mode.js +51 -0
- package/dist/sdk/execution-compact-commit.d.ts +23 -0
- package/dist/sdk/execution-compact-commit.js +41 -0
- package/dist/sdk/execution-compact-extension.d.ts +12 -0
- package/dist/sdk/execution-compact-extension.js +46 -0
- package/dist/sdk/execution-compact-generation.d.ts +17 -0
- package/dist/sdk/execution-compact-generation.js +30 -0
- package/dist/sdk/execution-compaction-flight.d.ts +31 -0
- package/dist/sdk/execution-compaction-flight.js +50 -0
- package/dist/sdk/execution-compaction.d.ts +47 -0
- package/dist/sdk/execution-compaction.js +121 -0
- package/dist/sdk/execution-context-coordinator.d.ts +56 -0
- package/dist/sdk/execution-context-coordinator.js +102 -0
- package/dist/sdk/execution-input.d.ts +58 -0
- package/dist/sdk/execution-input.js +266 -0
- package/dist/sdk/execution-model-context.d.ts +52 -0
- package/dist/sdk/execution-model-context.js +156 -0
- package/dist/sdk/execution-model-lifecycle.d.ts +39 -0
- package/dist/sdk/execution-model-lifecycle.js +73 -0
- package/dist/sdk/execution-model-request.d.ts +40 -0
- package/dist/sdk/execution-model-request.js +45 -0
- package/dist/sdk/execution-round-preparation.d.ts +11 -0
- package/dist/sdk/execution-round-preparation.js +18 -0
- package/dist/sdk/execution-round-request.d.ts +30 -0
- package/dist/sdk/execution-round-request.js +31 -0
- package/dist/sdk/execution-tool-catalog.d.ts +46 -0
- package/dist/sdk/execution-tool-catalog.js +103 -0
- package/dist/sdk/execution-tool-scope.d.ts +67 -0
- package/dist/sdk/execution-tool-scope.js +147 -0
- package/dist/sdk/extension-agent-controller.d.ts +3 -0
- package/dist/sdk/extension-agent-controller.js +69 -0
- package/dist/sdk/extension-agents.d.ts +9 -0
- package/dist/sdk/extension-agents.js +58 -0
- package/dist/sdk/extension-models.d.ts +17 -0
- package/dist/sdk/extension-models.js +142 -0
- package/dist/sdk/extension-session-launch.d.ts +18 -0
- package/dist/sdk/extension-session-launch.js +73 -0
- package/dist/sdk/extension-session.d.ts +30 -0
- package/dist/sdk/extension-session.js +107 -0
- package/dist/sdk/extension-tool-runtime.d.ts +2 -0
- package/dist/sdk/extension-tool-runtime.js +14 -0
- package/dist/sdk/extension-tools.d.ts +16 -0
- package/dist/sdk/extension-tools.js +126 -0
- package/dist/sdk/model-input-projection.d.ts +4 -0
- package/dist/sdk/model-input-projection.js +27 -0
- package/dist/sdk/model-runtime.d.ts +21 -0
- package/dist/sdk/model-runtime.js +91 -0
- package/dist/sdk/native-steer.d.ts +20 -0
- package/dist/sdk/native-steer.js +84 -0
- package/dist/sdk/request-preparation.d.ts +9 -0
- package/dist/sdk/resource-input.d.ts +33 -0
- package/dist/sdk/resource-input.js +402 -0
- package/dist/sdk/session-catalog.d.ts +2 -0
- package/dist/sdk/session-catalog.js +12 -0
- package/dist/sdk/session-composition.d.ts +54 -0
- package/dist/sdk/session-composition.js +1 -0
- package/dist/sdk/session-execution-factory.d.ts +43 -0
- package/dist/sdk/session-execution-factory.js +937 -0
- package/dist/sdk/session-execution.d.ts +163 -0
- package/dist/sdk/session-execution.js +388 -0
- package/dist/sdk/session-extension-messages.d.ts +16 -0
- package/dist/sdk/session-extension-messages.js +64 -0
- package/dist/sdk/session-extension-prompt.d.ts +35 -0
- package/dist/sdk/session-extension-prompt.js +62 -0
- package/dist/sdk/session-maintenance.d.ts +25 -0
- package/dist/sdk/session-maintenance.js +205 -0
- package/dist/sdk/session-model-adapters.d.ts +28 -0
- package/dist/sdk/session-model-adapters.js +96 -0
- package/dist/sdk/session-open.d.ts +22 -0
- package/dist/sdk/session-open.js +44 -0
- package/dist/sdk/session-registry.d.ts +17 -0
- package/dist/sdk/session-registry.js +83 -0
- package/dist/sdk/session-runtime.d.ts +72 -0
- package/dist/sdk/session-runtime.js +205 -0
- package/dist/sdk/session.d.ts +3 -0
- package/dist/sdk/session.js +313 -0
- package/dist/sdk/subagent-backend.d.ts +3 -0
- package/dist/sdk/subagent-backend.js +49 -0
- package/dist/sdk/types.d.ts +80 -0
- package/dist/sdk/types.js +1 -0
- package/dist/session/catalog.d.ts +70 -0
- package/dist/session/catalog.js +194 -0
- package/dist/session/code-observation-contract.d.ts +40 -0
- package/dist/session/code-observation-contract.js +1 -0
- package/dist/session/code-observation-projection.d.ts +54 -0
- package/dist/session/code-observation-projection.js +165 -0
- package/dist/session/code-state.d.ts +27 -0
- package/dist/session/code-state.js +89 -0
- package/dist/session/context-gc-checkpoint.d.ts +26 -0
- package/dist/session/context-gc-checkpoint.js +935 -0
- package/dist/session/context-gc-contract.d.ts +141 -0
- package/dist/session/context-gc-contract.js +1 -0
- package/dist/session/context-gc-observations.d.ts +58 -0
- package/dist/session/context-gc-observations.js +187 -0
- package/dist/session/context-gc-replacements.d.ts +63 -0
- package/dist/session/context-gc-subagent.d.ts +54 -0
- package/dist/session/deferred-input.d.ts +5 -0
- package/dist/session/deferred-input.js +74 -0
- package/dist/session/extension-checkpoint.d.ts +8 -0
- package/dist/session/extension-checkpoint.js +30 -0
- package/dist/session/history-input-limits.d.ts +4 -0
- package/dist/session/history-input-limits.js +4 -0
- package/dist/session/inline-media-contract.d.ts +16 -0
- package/dist/session/inline-media-contract.js +1 -0
- package/dist/session/inline-media.d.ts +26 -0
- package/dist/session/inline-media.js +138 -0
- package/dist/session/model-request.d.ts +14 -0
- package/dist/session/model-request.js +1 -0
- package/dist/session/record-contract.d.ts +19 -0
- package/dist/session/record-contract.js +1 -0
- package/dist/session/record-projection.d.ts +6 -0
- package/dist/session/record-projection.js +80 -0
- package/dist/session/resource-contract.d.ts +9 -0
- package/dist/session/resource-contract.js +1 -0
- package/dist/session/rollback.d.ts +13 -0
- package/dist/session/rollback.js +46 -0
- package/dist/session/store/contracts.d.ts +160 -0
- package/dist/session/store/contracts.js +1 -0
- package/dist/session/store/current-state.d.ts +17 -0
- package/dist/session/store/current-state.js +53 -0
- package/dist/session/store/execution-state.d.ts +13 -0
- package/dist/session/store/execution-state.js +103 -0
- package/dist/session/store/index.d.ts +4 -0
- package/dist/session/store/index.js +2 -0
- package/dist/session/store/jsonl/directory-index.d.ts +26 -0
- package/dist/session/store/jsonl/directory-index.js +283 -0
- package/dist/session/store/jsonl/fork-session.d.ts +19 -0
- package/dist/session/store/jsonl/fork-session.js +142 -0
- package/dist/session/store/jsonl/fs-durability.d.ts +3 -0
- package/dist/session/store/jsonl/fs-durability.js +17 -0
- package/dist/session/store/jsonl/history-replacement.d.ts +48 -0
- package/dist/session/store/jsonl/history-replacement.js +147 -0
- package/dist/session/store/jsonl/history.d.ts +25 -0
- package/dist/session/store/jsonl/history.js +181 -0
- package/dist/session/store/jsonl/images.d.ts +19 -0
- package/dist/session/store/jsonl/images.js +122 -0
- package/dist/session/store/jsonl/index-transaction.d.ts +13 -0
- package/dist/session/store/jsonl/index-transaction.js +87 -0
- package/dist/session/store/jsonl/jsonl-admission.d.ts +10 -0
- package/dist/session/store/jsonl/jsonl-admission.js +205 -0
- package/dist/session/store/jsonl/jsonl-codec.d.ts +11 -0
- package/dist/session/store/jsonl/jsonl-codec.js +138 -0
- package/dist/session/store/jsonl/jsonl-migration.d.ts +12 -0
- package/dist/session/store/jsonl/jsonl-migration.js +105 -0
- package/dist/session/store/jsonl/jsonl-store.d.ts +171 -0
- package/dist/session/store/jsonl/jsonl-store.js +2395 -0
- package/dist/session/store/jsonl/open-recovery.d.ts +13 -0
- package/dist/session/store/jsonl/open-recovery.js +131 -0
- package/dist/session/store/jsonl/read-pins.d.ts +4 -0
- package/dist/session/store/jsonl/read-pins.js +131 -0
- package/dist/session/store/jsonl/record-append.d.ts +16 -0
- package/dist/session/store/jsonl/record-append.js +49 -0
- package/dist/session/store/jsonl/records.d.ts +30 -0
- package/dist/session/store/jsonl/records.js +313 -0
- package/dist/session/store/jsonl/resources.d.ts +19 -0
- package/dist/session/store/jsonl/resources.js +209 -0
- package/dist/session/store/jsonl/store-pool.d.ts +12 -0
- package/dist/session/store/jsonl/store-pool.js +55 -0
- package/dist/session/store/jsonl/transaction-recovery.d.ts +48 -0
- package/dist/session/store/jsonl/transaction-recovery.js +374 -0
- package/dist/session/store/jsonl/tree-maintenance.d.ts +12 -0
- package/dist/session/store/jsonl/tree-maintenance.js +59 -0
- package/dist/session/store/types.d.ts +69 -0
- package/dist/session/store/types.js +1 -0
- package/dist/session/summary.d.ts +18 -0
- package/dist/session/summary.js +1 -0
- package/dist/session/todo.d.ts +11 -0
- package/dist/session/todo.js +30 -0
- package/dist/session/tool-result-resources.d.ts +27 -0
- package/dist/session/tool-result-resources.js +255 -0
- package/dist/session/types.js +1 -0
- package/dist/session/user-history.d.ts +17 -0
- package/dist/session/user-history.js +1 -0
- package/dist/session-store.d.ts +2 -0
- package/dist/session-store.js +1 -0
- package/dist/shared/auto-title.d.ts +3 -3
- package/dist/shared/auto-title.js +6 -6
- package/dist/shared/model-text.d.ts +8 -8
- package/dist/shared/model-text.js +8 -7
- package/dist/storage/file-lock.js +83 -0
- package/dist/subagent/contracts.d.ts +107 -0
- package/dist/subagent/contracts.js +1 -0
- package/dist/subagent/limits.d.ts +17 -0
- package/dist/subagent/limits.js +17 -0
- package/dist/subagent/model-update.js +81 -0
- package/dist/subagent/session-controller.d.ts +87 -0
- package/dist/subagent/session-controller.js +1343 -0
- package/dist/subagent/task.d.ts +134 -0
- package/dist/subagent/task.js +436 -0
- package/dist/subagent/turn-contracts.d.ts +64 -0
- package/dist/subagent/turn-contracts.js +1 -0
- package/dist/subagent/wait-targets.d.ts +10 -0
- package/dist/subagent/wait-targets.js +26 -0
- package/dist/subagent/wait.d.ts +26 -0
- package/dist/subagent/wait.js +73 -0
- package/dist/tools/agent/agent-cancel.d.ts +7 -0
- package/dist/tools/agent/agent-cancel.js +29 -0
- package/dist/tools/agent/agent-message.d.ts +7 -0
- package/dist/tools/agent/agent-message.js +57 -0
- package/dist/tools/agent/agent-start.d.ts +12 -0
- package/dist/tools/agent/agent-start.js +106 -0
- package/dist/tools/agent/agent-wait.d.ts +7 -0
- package/dist/tools/agent/agent-wait.js +91 -0
- package/dist/tools/agent/question.d.ts +7 -0
- package/dist/tools/agent/question.js +125 -0
- package/dist/tools/agent/skill.d.ts +11 -0
- package/dist/tools/agent/skill.js +38 -0
- package/dist/tools/agent/sleep.d.ts +9 -0
- package/dist/tools/agent/sleep.js +54 -0
- package/dist/tools/agent/smart-compact.d.ts +9 -0
- package/dist/tools/agent/smart-compact.js +23 -0
- package/dist/tools/agent/todo-read.d.ts +14 -0
- package/dist/tools/agent/todo-write.d.ts +14 -0
- package/dist/tools/agent/todo-write.js +66 -0
- package/dist/tools/agent/todo.d.ts +25 -0
- package/dist/tools/agent/todo.js +32 -0
- package/dist/tools/callers.d.ts +7 -0
- package/dist/tools/callers.js +39 -0
- package/dist/tools/code-mode/cells.d.ts +29 -0
- package/dist/tools/code-mode/cells.js +338 -0
- package/dist/tools/code-mode/dispatch.d.ts +20 -0
- package/dist/tools/code-mode/dispatch.js +80 -0
- package/dist/tools/code-mode/engine.d.ts +60 -0
- package/dist/tools/code-mode/engine.js +1 -0
- package/dist/tools/code-mode/index.d.ts +31 -0
- package/dist/tools/code-mode/index.js +751 -0
- package/dist/tools/code-mode/observations.d.ts +8 -0
- package/dist/tools/code-mode/observations.js +64 -0
- package/dist/tools/code-mode/worker.d.ts +1 -0
- package/dist/tools/contracts.d.ts +193 -0
- package/dist/tools/contracts.js +1 -0
- package/dist/tools/definition-projection.d.ts +1 -0
- package/dist/tools/definition-projection.js +1 -0
- package/dist/tools/execution-limits.d.ts +69 -0
- package/dist/tools/execution-limits.js +58 -0
- package/dist/tools/execution.d.ts +30 -0
- package/dist/tools/execution.js +191 -0
- package/dist/tools/local/apply-patch-parser.js +177 -0
- package/dist/tools/local/apply-patch.d.ts +19 -0
- package/dist/tools/local/apply-patch.js +489 -0
- package/dist/tools/local/exec.d.ts +28 -0
- package/dist/tools/local/exec.js +194 -0
- package/dist/tools/local/image-processing.js +146 -0
- package/dist/tools/local/read.d.ts +43 -0
- package/dist/tools/local/read.js +470 -0
- package/dist/tools/local/view-image.d.ts +15 -0
- package/dist/tools/local/view-image.js +317 -0
- package/dist/tools/mcp/catalog.js +220 -0
- package/dist/tools/mcp/history.d.ts +4 -0
- package/dist/tools/mcp/tool-search.d.ts +45 -0
- package/dist/tools/mcp/tool-search.js +294 -0
- package/dist/tools/output-projection.d.ts +18 -0
- package/dist/tools/output-projection.js +341 -0
- package/dist/tools/permission-contract.d.ts +25 -0
- package/dist/tools/permission-contract.js +1 -0
- package/dist/tools/permissions.d.ts +61 -0
- package/dist/tools/permissions.js +237 -0
- package/dist/tools/registry.d.ts +7 -0
- package/dist/tools/result-extension-contract.d.ts +37 -0
- package/dist/tools/result-extension-contract.js +32 -0
- package/dist/tools/result-extensions.d.ts +12 -0
- package/dist/tools/result-extensions.js +104 -0
- package/dist/tools/runtime-context.d.ts +89 -0
- package/dist/tools/runtime-context.js +1 -0
- package/dist/tools/runtime-resource-handles.js +28 -0
- package/dist/tools/runtime.js +77 -0
- package/dist/tools/scheduler.d.ts +12 -0
- package/dist/tools/shared/path-resolver.js +133 -0
- package/dist/tools/wait.d.ts +1 -0
- package/dist/tools/wait.js +1 -0
- package/package.json +15 -3
- package/dist/agent-core/error.d.ts +0 -15
- package/dist/agent-core/error.js +0 -74
- package/dist/agent-core/extensions/agents.d.ts +0 -79
- package/dist/agent-core/extensions/agents.js +0 -186
- package/dist/agent-core/extensions/compaction.d.ts +0 -48
- package/dist/agent-core/extensions/contracts.d.ts +0 -265
- package/dist/agent-core/extensions/data.d.ts +0 -84
- package/dist/agent-core/extensions/data.js +0 -83
- package/dist/agent-core/extensions/environment.d.ts +0 -27
- package/dist/agent-core/extensions/environment.js +0 -52
- package/dist/agent-core/extensions/files.d.ts +0 -64
- package/dist/agent-core/extensions/files.js +0 -26
- package/dist/agent-core/extensions/hooks.d.ts +0 -32
- package/dist/agent-core/extensions/manager.d.ts +0 -39
- package/dist/agent-core/extensions/manager.js +0 -458
- package/dist/agent-core/extensions/mcp.d.ts +0 -99
- package/dist/agent-core/extensions/mcp.js +0 -433
- package/dist/agent-core/extensions/model-request.d.ts +0 -56
- package/dist/agent-core/extensions/model-request.js +0 -197
- package/dist/agent-core/extensions/model-stream.d.ts +0 -9
- package/dist/agent-core/extensions/model-stream.js +0 -108
- package/dist/agent-core/extensions/models.d.ts +0 -43
- package/dist/agent-core/extensions/network.d.ts +0 -32
- package/dist/agent-core/extensions/network.js +0 -97
- package/dist/agent-core/extensions/operation.d.ts +0 -30
- package/dist/agent-core/extensions/processes.d.ts +0 -52
- package/dist/agent-core/extensions/processes.js +0 -284
- package/dist/agent-core/extensions/prompt-composition.d.ts +0 -24
- package/dist/agent-core/extensions/prompt-composition.js +0 -106
- package/dist/agent-core/extensions/prompt-input.d.ts +0 -23
- package/dist/agent-core/extensions/prompt-input.js +0 -48
- package/dist/agent-core/extensions/resources.d.ts +0 -27
- package/dist/agent-core/extensions/resources.js +0 -221
- package/dist/agent-core/extensions/services.d.ts +0 -31
- package/dist/agent-core/extensions/services.js +0 -61
- package/dist/agent-core/extensions/session-input.d.ts +0 -163
- package/dist/agent-core/extensions/session-input.js +0 -411
- package/dist/agent-core/extensions/session-messages.d.ts +0 -41
- package/dist/agent-core/extensions/session-messages.js +0 -76
- package/dist/agent-core/extensions/session.d.ts +0 -69
- package/dist/agent-core/extensions/session.js +0 -607
- package/dist/agent-core/extensions/settings.d.ts +0 -59
- package/dist/agent-core/extensions/settings.js +0 -115
- package/dist/agent-core/extensions/store.d.ts +0 -10
- package/dist/agent-core/extensions/store.js +0 -131
- package/dist/agent-core/extensions/tools.d.ts +0 -61
- package/dist/agent-core/extensions/tools.js +0 -269
- package/dist/agent-core/host/contracts.d.ts +0 -100
- package/dist/agent-core/host/contracts.js +0 -18
- package/dist/agent-core/host/local-agent-host.d.ts +0 -96
- package/dist/agent-core/host/local-agent-host.js +0 -425
- package/dist/agent-core/host/local-file-access.d.ts +0 -30
- package/dist/agent-core/host/local-file-access.js +0 -107
- package/dist/agent-core/host/local-network-access.d.ts +0 -6
- package/dist/agent-core/host/local-network-access.js +0 -101
- package/dist/agent-core/host/local-process-executor.d.ts +0 -29
- package/dist/agent-core/host/local-process-executor.js +0 -41
- package/dist/agent-core/host/local-session-environment.d.ts +0 -18
- package/dist/agent-core/host/local-shell.d.ts +0 -3
- package/dist/agent-core/host/network.d.ts +0 -4
- package/dist/agent-core/host/network.js +0 -6
- package/dist/agent-core/model/anthropic-messages.d.ts +0 -10
- package/dist/agent-core/model/anthropic-messages.js +0 -707
- package/dist/agent-core/model/anthropic-thinking.js +0 -33
- package/dist/agent-core/model/catalog-file.d.ts +0 -80
- package/dist/agent-core/model/catalog-file.js +0 -103
- package/dist/agent-core/model/configuration.d.ts +0 -127
- package/dist/agent-core/model/configuration.js +0 -168
- package/dist/agent-core/model/contracts.d.ts +0 -229
- package/dist/agent-core/model/http.d.ts +0 -27
- package/dist/agent-core/model/http.js +0 -534
- package/dist/agent-core/model/image.d.ts +0 -17
- package/dist/agent-core/model/openai-responses.d.ts +0 -77
- package/dist/agent-core/model/openai-responses.js +0 -2123
- package/dist/agent-core/model/product-user-agent.d.ts +0 -5
- package/dist/agent-core/model/product-user-agent.js +0 -22
- package/dist/agent-core/model/recovery.js +0 -80
- package/dist/agent-core/model/request-configuration.d.ts +0 -17
- package/dist/agent-core/model/request-configuration.js +0 -47
- package/dist/agent-core/model/runtime-credential.d.ts +0 -8
- package/dist/agent-core/model/runtime-credential.js +0 -72
- package/dist/agent-core/model/sse.js +0 -281
- package/dist/agent-core/model/text-blocks.js +0 -40
- package/dist/agent-core/model/tool-declarations.d.ts +0 -4
- package/dist/agent-core/model/transport-test.d.ts +0 -3
- package/dist/agent-core/model/transport-test.js +0 -23
- package/dist/agent-core/model/usage-observer.d.ts +0 -31
- package/dist/agent-core/prompts/index.d.ts +0 -20
- package/dist/agent-core/prompts/index.js +0 -49
- package/dist/agent-core/runtime/compaction/coordinator.js +0 -88
- package/dist/agent-core/runtime/compaction/generate.d.ts +0 -28
- package/dist/agent-core/runtime/compaction/generate.js +0 -84
- package/dist/agent-core/runtime/compaction/history.d.ts +0 -16
- package/dist/agent-core/runtime/compaction/operation.d.ts +0 -29
- package/dist/agent-core/runtime/compaction/operation.js +0 -131
- package/dist/agent-core/runtime/compaction/requests.d.ts +0 -16
- package/dist/agent-core/runtime/compaction/requests.js +0 -114
- package/dist/agent-core/runtime/compaction/retained-context.d.ts +0 -6
- package/dist/agent-core/runtime/compaction/retained-context.js +0 -47
- package/dist/agent-core/runtime/compaction/smart.d.ts +0 -14
- package/dist/agent-core/runtime/compaction/smart.js +0 -59
- package/dist/agent-core/runtime/context/contextual-blocks.d.ts +0 -30
- package/dist/agent-core/runtime/context/contextual-blocks.js +0 -146
- package/dist/agent-core/runtime/context/environment.d.ts +0 -5
- package/dist/agent-core/runtime/context/gc-runtime.d.ts +0 -38
- package/dist/agent-core/runtime/context/gc-tokens.d.ts +0 -25
- package/dist/agent-core/runtime/context/gc-tokens.js +0 -103
- package/dist/agent-core/runtime/context/gc.d.ts +0 -75
- package/dist/agent-core/runtime/context/gc.js +0 -1386
- package/dist/agent-core/runtime/context/instructions.d.ts +0 -37
- package/dist/agent-core/runtime/context/instructions.js +0 -297
- package/dist/agent-core/runtime/context/normalize.d.ts +0 -5
- package/dist/agent-core/runtime/context/projection.d.ts +0 -23
- package/dist/agent-core/runtime/context/projection.js +0 -284
- package/dist/agent-core/runtime/context/request-budget.d.ts +0 -4
- package/dist/agent-core/runtime/context/request-budget.js +0 -25
- package/dist/agent-core/runtime/context/restore.d.ts +0 -25
- package/dist/agent-core/runtime/context/restore.js +0 -186
- package/dist/agent-core/runtime/context/usage.d.ts +0 -17
- package/dist/agent-core/runtime/context/usage.js +0 -124
- package/dist/agent-core/runtime/context/world-state.d.ts +0 -33
- package/dist/agent-core/runtime/context/world-state.js +0 -232
- package/dist/agent-core/runtime/contracts.d.ts +0 -41
- package/dist/agent-core/runtime/event-projection.d.ts +0 -14
- package/dist/agent-core/runtime/event-projection.js +0 -87
- package/dist/agent-core/runtime/events.d.ts +0 -197
- package/dist/agent-core/runtime/execution-handle.d.ts +0 -12
- package/dist/agent-core/runtime/execution.d.ts +0 -50
- package/dist/agent-core/runtime/execution.js +0 -445
- package/dist/agent-core/runtime/model-progress.js +0 -85
- package/dist/agent-core/runtime/resource-publication.d.ts +0 -8
- package/dist/agent-core/runtime/resource-publication.js +0 -60
- package/dist/agent-core/sdk/agent.d.ts +0 -11
- package/dist/agent-core/sdk/agent.js +0 -474
- package/dist/agent-core/sdk/extension-agents.d.ts +0 -9
- package/dist/agent-core/sdk/extension-agents.js +0 -58
- package/dist/agent-core/sdk/extension-models.d.ts +0 -13
- package/dist/agent-core/sdk/extension-models.js +0 -137
- package/dist/agent-core/sdk/extension-session.d.ts +0 -30
- package/dist/agent-core/sdk/extension-session.js +0 -105
- package/dist/agent-core/sdk/extension-tools.d.ts +0 -16
- package/dist/agent-core/sdk/extension-tools.js +0 -126
- package/dist/agent-core/sdk/model-runtime.d.ts +0 -18
- package/dist/agent-core/sdk/model-runtime.js +0 -93
- package/dist/agent-core/sdk/native-steer.d.ts +0 -20
- package/dist/agent-core/sdk/native-steer.js +0 -84
- package/dist/agent-core/sdk/request-preparation.d.ts +0 -7
- package/dist/agent-core/sdk/resource-input.d.ts +0 -32
- package/dist/agent-core/sdk/resource-input.js +0 -403
- package/dist/agent-core/sdk/session.d.ts +0 -94
- package/dist/agent-core/sdk/session.js +0 -2377
- package/dist/agent-core/sdk/subagent-backend.d.ts +0 -3
- package/dist/agent-core/sdk/subagent-backend.js +0 -49
- package/dist/agent-core/sdk/types.d.ts +0 -81
- package/dist/agent-core/session/catalog.d.ts +0 -99
- package/dist/agent-core/session/catalog.js +0 -200
- package/dist/agent-core/session/context-gc-checkpoint.d.ts +0 -165
- package/dist/agent-core/session/context-gc-checkpoint.js +0 -935
- package/dist/agent-core/session/context-gc-observations.d.ts +0 -60
- package/dist/agent-core/session/context-gc-observations.js +0 -187
- package/dist/agent-core/session/context-gc-replacements.d.ts +0 -62
- package/dist/agent-core/session/context-gc-subagent.d.ts +0 -53
- package/dist/agent-core/session/deferred-input.d.ts +0 -5
- package/dist/agent-core/session/deferred-input.js +0 -74
- package/dist/agent-core/session/inline-media.d.ts +0 -45
- package/dist/agent-core/session/inline-media.js +0 -163
- package/dist/agent-core/session/rollback.d.ts +0 -13
- package/dist/agent-core/session/rollback.js +0 -46
- package/dist/agent-core/session/store/contracts.d.ts +0 -158
- package/dist/agent-core/session/store/current-state.d.ts +0 -17
- package/dist/agent-core/session/store/current-state.js +0 -53
- package/dist/agent-core/session/store/directory-index.d.ts +0 -18
- package/dist/agent-core/session/store/directory-index.js +0 -169
- package/dist/agent-core/session/store/execution-state.d.ts +0 -18
- package/dist/agent-core/session/store/execution-state.js +0 -175
- package/dist/agent-core/session/store/history.d.ts +0 -25
- package/dist/agent-core/session/store/history.js +0 -181
- package/dist/agent-core/session/store/images.d.ts +0 -19
- package/dist/agent-core/session/store/images.js +0 -110
- package/dist/agent-core/session/store/index.d.ts +0 -4
- package/dist/agent-core/session/store/index.js +0 -2
- package/dist/agent-core/session/store/jsonl-store.d.ts +0 -171
- package/dist/agent-core/session/store/jsonl-store.js +0 -2078
- package/dist/agent-core/session/store/records.d.ts +0 -23
- package/dist/agent-core/session/store/records.js +0 -261
- package/dist/agent-core/session/store/resources.d.ts +0 -26
- package/dist/agent-core/session/store/resources.js +0 -197
- package/dist/agent-core/session/store/types.d.ts +0 -82
- package/dist/agent-core/session/tool-result-resources.d.ts +0 -26
- package/dist/agent-core/session/tool-result-resources.js +0 -256
- package/dist/agent-core/session/transaction-recovery.d.ts +0 -16
- package/dist/agent-core/session/transaction-recovery.js +0 -135
- package/dist/agent-core/storage/file-lock.js +0 -83
- package/dist/agent-core/subagent/contracts.d.ts +0 -117
- package/dist/agent-core/subagent/model-update.js +0 -81
- package/dist/agent-core/subagent/scheduler.d.ts +0 -135
- package/dist/agent-core/subagent/scheduler.js +0 -369
- package/dist/agent-core/subagent/session-controller.d.ts +0 -138
- package/dist/agent-core/subagent/session-controller.js +0 -1594
- package/dist/agent-core/subagent/wait.d.ts +0 -14
- package/dist/agent-core/subagent/wait.js +0 -32
- package/dist/agent-core/tools/agent/agent-cancel.d.ts +0 -7
- package/dist/agent-core/tools/agent/agent-cancel.js +0 -29
- package/dist/agent-core/tools/agent/agent-message.d.ts +0 -7
- package/dist/agent-core/tools/agent/agent-message.js +0 -57
- package/dist/agent-core/tools/agent/agent-start.d.ts +0 -12
- package/dist/agent-core/tools/agent/agent-start.js +0 -106
- package/dist/agent-core/tools/agent/agent-wait.d.ts +0 -7
- package/dist/agent-core/tools/agent/agent-wait.js +0 -91
- package/dist/agent-core/tools/agent/question.d.ts +0 -7
- package/dist/agent-core/tools/agent/question.js +0 -125
- package/dist/agent-core/tools/agent/skill.d.ts +0 -11
- package/dist/agent-core/tools/agent/skill.js +0 -38
- package/dist/agent-core/tools/agent/sleep.d.ts +0 -12
- package/dist/agent-core/tools/agent/sleep.js +0 -54
- package/dist/agent-core/tools/agent/smart-compact.d.ts +0 -9
- package/dist/agent-core/tools/agent/smart-compact.js +0 -23
- package/dist/agent-core/tools/agent/todo-read.d.ts +0 -14
- package/dist/agent-core/tools/agent/todo-write.d.ts +0 -14
- package/dist/agent-core/tools/agent/todo-write.js +0 -66
- package/dist/agent-core/tools/agent/todo.d.ts +0 -26
- package/dist/agent-core/tools/agent/todo.js +0 -60
- package/dist/agent-core/tools/callers.d.ts +0 -6
- package/dist/agent-core/tools/callers.js +0 -39
- package/dist/agent-core/tools/code-mode/cells.d.ts +0 -29
- package/dist/agent-core/tools/code-mode/cells.js +0 -337
- package/dist/agent-core/tools/code-mode/dispatch.d.ts +0 -20
- package/dist/agent-core/tools/code-mode/dispatch.js +0 -80
- package/dist/agent-core/tools/code-mode/engine.d.ts +0 -60
- package/dist/agent-core/tools/code-mode/index.d.ts +0 -29
- package/dist/agent-core/tools/code-mode/index.js +0 -749
- package/dist/agent-core/tools/code-mode/observations.d.ts +0 -98
- package/dist/agent-core/tools/code-mode/observations.js +0 -228
- package/dist/agent-core/tools/code-mode/state.d.ts +0 -9
- package/dist/agent-core/tools/code-mode/state.js +0 -82
- package/dist/agent-core/tools/contracts.d.ts +0 -211
- package/dist/agent-core/tools/definition-projection.d.ts +0 -7
- package/dist/agent-core/tools/definition-projection.js +0 -26
- package/dist/agent-core/tools/execution-limits.d.ts +0 -69
- package/dist/agent-core/tools/execution-limits.js +0 -69
- package/dist/agent-core/tools/execution.d.ts +0 -46
- package/dist/agent-core/tools/execution.js +0 -191
- package/dist/agent-core/tools/local/apply-patch-parser.js +0 -177
- package/dist/agent-core/tools/local/apply-patch.d.ts +0 -19
- package/dist/agent-core/tools/local/apply-patch.js +0 -489
- package/dist/agent-core/tools/local/exec.d.ts +0 -53
- package/dist/agent-core/tools/local/exec.js +0 -877
- package/dist/agent-core/tools/local/image-processing.js +0 -146
- package/dist/agent-core/tools/local/read-context.js +0 -152
- package/dist/agent-core/tools/local/read.d.ts +0 -43
- package/dist/agent-core/tools/local/read.js +0 -470
- package/dist/agent-core/tools/local/registry.d.ts +0 -34
- package/dist/agent-core/tools/local/registry.js +0 -132
- package/dist/agent-core/tools/local/view-image.d.ts +0 -15
- package/dist/agent-core/tools/local/view-image.js +0 -317
- package/dist/agent-core/tools/local/workspace-tools.d.ts +0 -12
- package/dist/agent-core/tools/local/workspace-tools.js +0 -28
- package/dist/agent-core/tools/mcp/catalog.js +0 -220
- package/dist/agent-core/tools/mcp/history.d.ts +0 -4
- package/dist/agent-core/tools/mcp/tool-search.d.ts +0 -45
- package/dist/agent-core/tools/mcp/tool-search.js +0 -294
- package/dist/agent-core/tools/output-projection.d.ts +0 -18
- package/dist/agent-core/tools/output-projection.js +0 -341
- package/dist/agent-core/tools/permissions.d.ts +0 -85
- package/dist/agent-core/tools/permissions.js +0 -237
- package/dist/agent-core/tools/registry.d.ts +0 -7
- package/dist/agent-core/tools/result-extensions.d.ts +0 -46
- package/dist/agent-core/tools/result-extensions.js +0 -134
- package/dist/agent-core/tools/runtime-context.d.ts +0 -74
- package/dist/agent-core/tools/runtime-resource-handles.js +0 -28
- package/dist/agent-core/tools/runtime.js +0 -77
- package/dist/agent-core/tools/scheduler.d.ts +0 -12
- package/dist/agent-core/tools/shared/path-resolver.js +0 -133
- package/dist/agent-core/tools/wait.js +0 -82
- /package/dist/{agent-core/extensions/models.js → extensions/capabilities.js} +0 -0
- /package/dist/{agent-core/extensions → extensions}/compaction.js +0 -0
- /package/dist/{agent-core/model → extensions}/contracts.js +0 -0
- /package/dist/{agent-core/extensions → extensions}/hooks.js +0 -0
- /package/dist/{agent-core/runtime/context/contracts.js → extensions/host-ports.js} +0 -0
- /package/dist/{agent-core/extensions → extensions}/operation.js +0 -0
- /package/dist/{agent-core/runtime/contracts.js → extensions/tool-runtime.js} +0 -0
- /package/dist/{agent-core/extensions/contracts.js → extensions.js} +0 -0
- /package/dist/{agent-core/runtime/events.js → host/description.js} +0 -0
- /package/dist/{agent-core/host → host}/local-session-environment.js +0 -0
- /package/dist/{agent-core/host → host}/local-shell.js +0 -0
- /package/dist/{agent-core/runtime/execution-handle.js → host/skill-contract.js} +0 -0
- /package/dist/{agent-core/model → model}/anthropic-thinking.d.ts +0 -0
- /package/dist/{agent-core/model → model}/attempt-diagnostics.d.ts +0 -0
- /package/dist/{agent-core/model → model}/attempt-diagnostics.js +0 -0
- /package/dist/{agent-core/session/store → model}/contracts.js +0 -0
- /package/dist/{agent-core/sdk/types.js → model/image-contract.js} +0 -0
- /package/dist/{agent-core/model → model}/image.js +0 -0
- /package/dist/{agent-core/model → model}/native-context.d.ts +0 -0
- /package/dist/{agent-core/model → model}/native-context.js +0 -0
- /package/dist/{agent-core/model → model}/recovery.d.ts +0 -0
- /package/dist/{agent-core/model → model}/sse.d.ts +0 -0
- /package/dist/{agent-core/model → model}/text-blocks.d.ts +0 -0
- /package/dist/{agent-core/model → model}/tool-declarations.js +0 -0
- /package/dist/{agent-core/model → model}/tool-input.d.ts +0 -0
- /package/dist/{agent-core/model → model}/tool-input.js +0 -0
- /package/dist/{agent-core/model → model}/usage-observer.js +0 -0
- /package/dist/{agent-core/session/store/types.js → model/usage.js} +0 -0
- /package/dist/{agent-core/subagent → process}/contracts.js +0 -0
- /package/dist/{agent-core/prompts → prompts}/compact.d.ts +0 -0
- /package/dist/{agent-core/prompts → prompts}/compact.js +0 -0
- /package/dist/{agent-core/prompts → prompts}/core.d.ts +0 -0
- /package/dist/{agent-core/prompts → prompts}/core.js +0 -0
- /package/dist/{agent-core/prompts → prompts}/markup.d.ts +0 -0
- /package/dist/{agent-core/prompts → prompts}/markup.js +0 -0
- /package/dist/{agent-core/prompts → prompts}/modes.d.ts +0 -0
- /package/dist/{agent-core/prompts → prompts}/modes.js +0 -0
- /package/dist/{agent-core/prompts → prompts}/output.d.ts +0 -0
- /package/dist/{agent-core/prompts → prompts}/output.js +0 -0
- /package/dist/{agent-core/prompts → prompts}/permissions.d.ts +0 -0
- /package/dist/{agent-core/prompts → prompts}/permissions.js +0 -0
- /package/dist/{agent-core/tools/local → prompts}/read-context.d.ts +0 -0
- /package/dist/{agent-core/prompts → prompts}/resources.d.ts +0 -0
- /package/dist/{agent-core/prompts → prompts}/resources.js +0 -0
- /package/dist/{agent-core/prompts → prompts}/subagent.d.ts +0 -0
- /package/dist/{agent-core/prompts → prompts}/subagent.js +0 -0
- /package/dist/{agent-core/prompts → prompts}/workflow.d.ts +0 -0
- /package/dist/{agent-core/prompts → prompts}/workflow.js +0 -0
- /package/dist/{agent-core/runtime → runtime}/async-queue.d.ts +0 -0
- /package/dist/{agent-core/runtime → runtime}/async-queue.js +0 -0
- /package/dist/{agent-core/tools → runtime/compaction}/contracts.js +0 -0
- /package/dist/{agent-core/runtime → runtime}/compaction/coordinator.d.ts +0 -0
- /package/dist/{agent-core/runtime → runtime}/compaction/history.js +0 -0
- /package/dist/{agent-core/runtime → runtime}/context/contracts.d.ts +0 -0
- /package/dist/{agent-core/session/types.js → runtime/context/contracts.js} +0 -0
- /package/dist/{agent-core/runtime → runtime}/context/environment.js +0 -0
- /package/dist/{agent-core/tools/code-mode/engine.js → runtime/context/gc-contracts.js} +0 -0
- /package/dist/{agent-core/runtime → runtime}/context/gc-runtime.js +0 -0
- /package/dist/{agent-core/runtime → runtime}/context/normalize.js +0 -0
- /package/dist/{agent-core/tools/code-mode/worker.d.ts → runtime/contracts.js} +0 -0
- /package/dist/{agent-core/tools/runtime-context.js → runtime/events.js} +0 -0
- /package/dist/{agent-core/runtime → runtime}/execution-policy.d.ts +0 -0
- /package/dist/{agent-core/runtime → runtime}/execution-policy.js +0 -0
- /package/dist/{agent-core/runtime → runtime}/model-generation-timing.d.ts +0 -0
- /package/dist/{agent-core/runtime → runtime}/model-generation-timing.js +0 -0
- /package/dist/{agent-core/runtime → runtime}/model-progress.d.ts +0 -0
- /package/dist/{agent-core/runtime → runtime}/plan-output.d.ts +0 -0
- /package/dist/{agent-core/runtime → runtime}/plan-output.js +0 -0
- /package/dist/{agent-core/tools/public-metadata.d.ts → runtime/public-tool-metadata.d.ts} +0 -0
- /package/dist/{agent-core/tools/public-metadata.js → runtime/public-tool-metadata.js} +0 -0
- /package/dist/{agent-core/runtime → runtime}/token-budget.d.ts +0 -0
- /package/dist/{agent-core/runtime → runtime}/token-budget.js +0 -0
- /package/dist/{agent-core/tools → runtime}/wait.d.ts +0 -0
- /package/dist/{agent-core/sdk → sdk}/request-preparation.js +0 -0
- /package/dist/{agent-core/session → session}/context-gc-markup.d.ts +0 -0
- /package/dist/{agent-core/session → session}/context-gc-markup.js +0 -0
- /package/dist/{agent-core/session → session}/context-gc-replacements.js +0 -0
- /package/dist/{agent-core/session → session}/context-gc-subagent.js +0 -0
- /package/dist/{agent-core/session → session}/types.d.ts +0 -0
- /package/dist/{agent-core/storage → storage}/file-lock.d.ts +0 -0
- /package/dist/{agent-core/subagent → subagent}/model-update.d.ts +0 -0
- /package/dist/{agent-core/tools → tools}/agent/todo-read.js +0 -0
- /package/dist/{agent-core/tools → tools}/code-mode/quickjs-engine.d.ts +0 -0
- /package/dist/{agent-core/tools → tools}/code-mode/quickjs-engine.js +0 -0
- /package/dist/{agent-core/tools → tools}/code-mode/text-output.d.ts +0 -0
- /package/dist/{agent-core/tools → tools}/code-mode/text-output.js +0 -0
- /package/dist/{agent-core/tools → tools}/code-mode/worker.js +0 -0
- /package/dist/{agent-core/tools → tools}/file-change-diff.d.ts +0 -0
- /package/dist/{agent-core/tools → tools}/file-change-diff.js +0 -0
- /package/dist/{agent-core/tools → tools}/input-diagnostics/source-preview.d.ts +0 -0
- /package/dist/{agent-core/tools → tools}/input-diagnostics/source-preview.js +0 -0
- /package/dist/{agent-core/tools → tools}/input.d.ts +0 -0
- /package/dist/{agent-core/tools → tools}/input.js +0 -0
- /package/dist/{agent-core/tools → tools}/local/apply-patch-parser.d.ts +0 -0
- /package/dist/{agent-core/tools → tools}/local/image-processing.d.ts +0 -0
- /package/dist/{agent-core/tools → tools}/mcp/catalog.d.ts +0 -0
- /package/dist/{agent-core/tools → tools}/mcp/history.js +0 -0
- /package/dist/{agent-core/tools → tools}/registry.js +0 -0
- /package/dist/{agent-core/tools → tools}/runtime-resource-handles.d.ts +0 -0
- /package/dist/{agent-core/tools → tools}/runtime.d.ts +0 -0
- /package/dist/{agent-core/tools → tools}/scheduler.js +0 -0
- /package/dist/{agent-core/tools → tools}/shared/patch-result.d.ts +0 -0
- /package/dist/{agent-core/tools → tools}/shared/patch-result.js +0 -0
- /package/dist/{agent-core/tools → tools}/shared/path-resolver.d.ts +0 -0
|
@@ -0,0 +1,2395 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { rollbackBoundaryIndex } from '../../rollback.js';
|
|
3
|
+
import { lstat, mkdir, readFile, readdir, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
6
|
+
import { AgentError } from '../../../error.js';
|
|
7
|
+
import { isModelImageUrl } from '../../../model/image.js';
|
|
8
|
+
import { cloneCoreJson } from '../../extension-checkpoint.js';
|
|
9
|
+
import { acquireFileLock } from '../../../storage/file-lock.js';
|
|
10
|
+
import { todoItemsFromSnapshot } from '../../todo.js';
|
|
11
|
+
import { applyCodeModeStateMutations, parseCodeModeStateMutations, restoreCodeModeState } from '../../code-state.js';
|
|
12
|
+
import { contextGcBaseFromRecords, contextGcCheckpointIntervalEligible, latestValidContextGcCheckpoint, normalizeContextGcReplacements, sameContextGcBase, validateContextGcCheckpoint } from '../../context-gc-checkpoint.js';
|
|
13
|
+
import { recordToolResources, readToolResultResource, writeToolResultResource } from '../../tool-result-resources.js';
|
|
14
|
+
import { beginTreeDeleteTransaction, finishTreeDelete, finishTreeDeletePreparation, readTreeDeleteTransaction, recoverSessionStorageTransaction, stageTreeDelete } from './transaction-recovery.js';
|
|
15
|
+
import { extensionCheckpointFromPayload, indexExtensionCheckpoint, restoreExtensionCheckpoint, updateSessionCurrentState } from '../current-state.js';
|
|
16
|
+
import { decodeSessionListCursor, encodeSessionListCursor, mergeSessionDerivedCache, normalizeSessionSource, persistFormatSelection, readSessionIndex, restoreSessionIndexMeta, sessionListFilter, sessionMatchesFilter, sessionTreeIds, upgradeSessionIndex } from './directory-index.js';
|
|
17
|
+
import { admitWorldStateVersion, preflightRetiredRecovery, preflightV1Session, preflightV2Recovery } from './jsonl-admission.js';
|
|
18
|
+
import { prepareJsonlV2Generation } from './jsonl-migration.js';
|
|
19
|
+
import { syncJsonlDirectory } from './fs-durability.js';
|
|
20
|
+
import { withSessionReadDrain, withSessionReadPin } from './read-pins.js';
|
|
21
|
+
import { deriveExecutionState, executionModelFromRecord, isCompactCheckpoint, isNativeUserTurn, sameResumeIndex, userMessagePreviewFromRecord } from '../execution-state.js';
|
|
22
|
+
import { forkSessionOperation } from './fork-session.js';
|
|
23
|
+
import { MAX_INPUT_IMAGES, inputImageReference, inputImageReferences, readInputImageBytes } from './images.js';
|
|
24
|
+
import { persistResource, setResourceUrl, readResource, resourceFiles } from './resources.js';
|
|
25
|
+
import { CONTEXTUAL_WORLD_STATE_FILE, openJsonlIndex, reclaimUnpublishedSessions, recoverContextualWorldStateTransactions, recoverJsonlTail, sessionDirectoryExists } from './open-recovery.js';
|
|
26
|
+
import { JsonlStorePool } from './store-pool.js';
|
|
27
|
+
import { acquireTreeMaintenance } from './tree-maintenance.js';
|
|
28
|
+
import { commitSessionIndex, ParentPublicationRejected } from './index-transaction.js';
|
|
29
|
+
import { replaceSessionHistory } from './history-replacement.js';
|
|
30
|
+
import { appendSessionRecord } from './record-append.js';
|
|
31
|
+
import { MAX_SEGMENT_BYTES, SessionHistoryPageReader, readRecords, readRecordsFromHead, readRecordsFromTail } from './records.js';
|
|
32
|
+
const MAX_CONTEXTUAL_WORLD_STATE_BYTES = 8 * 1024 * 1024;
|
|
33
|
+
/** SDK-owned JSONL history with per-HOME in-process sharing and cross-process file locks. */
|
|
34
|
+
export class JsonlSessionStore {
|
|
35
|
+
static #pool = new JsonlStorePool();
|
|
36
|
+
#shared;
|
|
37
|
+
#maintenance = new WeakMap();
|
|
38
|
+
#leaseClosed = false;
|
|
39
|
+
constructor(shared) {
|
|
40
|
+
this.#shared = shared;
|
|
41
|
+
}
|
|
42
|
+
static async open(home) {
|
|
43
|
+
const root = join(home, '.mar', 'agent');
|
|
44
|
+
return new JsonlSessionStore(await JsonlSessionStore.#pool.acquire(root, () => JsonlSessionStore.#openExclusive(root)));
|
|
45
|
+
}
|
|
46
|
+
/** Create a missing index only without Session directories; recover journals and marked creations. */
|
|
47
|
+
static async #openExclusive(root) {
|
|
48
|
+
const index = await openJsonlIndex(root);
|
|
49
|
+
return {
|
|
50
|
+
root,
|
|
51
|
+
index,
|
|
52
|
+
indexOperations: Promise.resolve(),
|
|
53
|
+
pendingSessions: new Map(),
|
|
54
|
+
references: 1,
|
|
55
|
+
closed: false,
|
|
56
|
+
indexDirty: false,
|
|
57
|
+
dirtySessionVersions: new Map(),
|
|
58
|
+
derivedOnlyDirtySessions: new Set(),
|
|
59
|
+
pendingPublishIds: new Set(),
|
|
60
|
+
codeStateWrites: new Map(),
|
|
61
|
+
recordWrites: new Map(),
|
|
62
|
+
writerLeases: new Map(),
|
|
63
|
+
segments: new Map(),
|
|
64
|
+
executionModelReads: new Map(),
|
|
65
|
+
historyPages: new SessionHistoryPageReader(),
|
|
66
|
+
rootConfirmed: false,
|
|
67
|
+
migrationFailures: new Map()
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** Merge dirty metadata with disk membership; only createdIds publish locally new entries. */
|
|
71
|
+
async #saveIndex(deletedIds, createdIds = [], onPersisted) {
|
|
72
|
+
return this.#serializeIndexOperation(() => this.#commitIndex(deletedIds, createdIds, onPersisted));
|
|
73
|
+
}
|
|
74
|
+
async #serializeIndexOperation(operation) {
|
|
75
|
+
const next = this.#shared.indexOperations.then(operation, operation);
|
|
76
|
+
this.#shared.indexOperations = next.then(() => undefined, () => undefined);
|
|
77
|
+
return next;
|
|
78
|
+
}
|
|
79
|
+
async #commitIndex(deletedIds, createdIds = [], onPersisted, storageSelection, deleteExpected) {
|
|
80
|
+
if (this.#shared.rootFailure)
|
|
81
|
+
throw this.#shared.rootFailure.error;
|
|
82
|
+
await commitSessionIndex(this.#shared.root, () => ({
|
|
83
|
+
index: this.#shared.index,
|
|
84
|
+
dirty: new Map(this.#shared.dirtySessionVersions),
|
|
85
|
+
derivedOnly: new Set(this.#shared.derivedOnlyDirtySessions)
|
|
86
|
+
}), (updated, dirty, created) => {
|
|
87
|
+
for (const [id, version] of dirty) {
|
|
88
|
+
// A different creation still needs its own index transaction to publish its directory.
|
|
89
|
+
if (this.#shared.pendingPublishIds.has(id) && !created.has(id))
|
|
90
|
+
continue;
|
|
91
|
+
if (this.#shared.dirtySessionVersions.get(id) === version) {
|
|
92
|
+
this.#shared.dirtySessionVersions.delete(id);
|
|
93
|
+
this.#shared.derivedOnlyDirtySessions.delete(id);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (const id of created)
|
|
97
|
+
this.#shared.pendingPublishIds.delete(id);
|
|
98
|
+
const published = new Set(updated.sessions.map((session) => session.id));
|
|
99
|
+
const pending = this.#shared.index.sessions.filter((session) => this.#shared.dirtySessionVersions.has(session.id) &&
|
|
100
|
+
(published.has(session.id) || this.#shared.pendingPublishIds.has(session.id)));
|
|
101
|
+
const replacements = new Map(pending.map((session) => [session.id, session]));
|
|
102
|
+
this.#shared.index = {
|
|
103
|
+
...updated,
|
|
104
|
+
sessions: [
|
|
105
|
+
...updated.sessions.map((session) => {
|
|
106
|
+
const local = replacements.get(session.id);
|
|
107
|
+
if (!local)
|
|
108
|
+
return session;
|
|
109
|
+
return session.storage === undefined ? local : { ...local, storage: session.storage };
|
|
110
|
+
}),
|
|
111
|
+
...pending.filter((session) => !published.has(session.id))
|
|
112
|
+
]
|
|
113
|
+
};
|
|
114
|
+
this.#shared.indexDirty = this.#shared.dirtySessionVersions.size > 0;
|
|
115
|
+
}, deletedIds, createdIds, onPersisted, storageSelection, deleteExpected);
|
|
116
|
+
}
|
|
117
|
+
/** Fence new opens of the legacy SDK without waiting on any Session while holding the index lock. */
|
|
118
|
+
async #ensureRootV2() {
|
|
119
|
+
if (this.#shared.rootFailure)
|
|
120
|
+
throw this.#shared.rootFailure.error;
|
|
121
|
+
if (this.#shared.rootConfirmed && this.#shared.index.version === 2) {
|
|
122
|
+
await this.#refreshIndex();
|
|
123
|
+
if (this.#shared.index.version === 2) {
|
|
124
|
+
await this.#recoverPublications();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
await this.#serializeIndexOperation(async () => {
|
|
129
|
+
if (this.#shared.rootFailure)
|
|
130
|
+
throw this.#shared.rootFailure.error;
|
|
131
|
+
const path = join(this.#shared.root, 'sessions', 'index.json');
|
|
132
|
+
const release = await acquireFileLock(join(this.#shared.root, 'sessions', 'index.lock'));
|
|
133
|
+
let failure;
|
|
134
|
+
try {
|
|
135
|
+
const disk = await readSessionIndex(path);
|
|
136
|
+
if (!disk)
|
|
137
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session index is missing.');
|
|
138
|
+
try {
|
|
139
|
+
if (disk.version === 1)
|
|
140
|
+
await persistFormatSelection(path, upgradeSessionIndex(disk));
|
|
141
|
+
else
|
|
142
|
+
await syncJsonlDirectory(join(this.#shared.root, 'sessions'));
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
// A failed rename or directory sync is not proof that the fence is absent.
|
|
146
|
+
await readSessionIndex(path).catch(() => undefined);
|
|
147
|
+
this.#shared.rootFailure = { error };
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
this.#shared.rootConfirmed = true;
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
failure = error;
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
if (failure !== undefined)
|
|
158
|
+
await release().catch(() => undefined);
|
|
159
|
+
else
|
|
160
|
+
await release();
|
|
161
|
+
}
|
|
162
|
+
await this.#readCurrentIndex();
|
|
163
|
+
});
|
|
164
|
+
await this.#recoverPublications();
|
|
165
|
+
}
|
|
166
|
+
async #recoverPublications() {
|
|
167
|
+
this.#shared.publicationRecovery ??= reclaimUnpublishedSessions(this.#shared.root);
|
|
168
|
+
await this.#shared.publicationRecovery;
|
|
169
|
+
}
|
|
170
|
+
async #refreshIndex() {
|
|
171
|
+
return this.#serializeIndexOperation(() => this.#readCurrentIndex());
|
|
172
|
+
}
|
|
173
|
+
async #readCurrentIndex() {
|
|
174
|
+
const disk = await readSessionIndex(join(this.#shared.root, 'sessions', 'index.json'));
|
|
175
|
+
if (!disk)
|
|
176
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session index is missing.');
|
|
177
|
+
// A local unflushed revision may exceed another writer's committed revision.
|
|
178
|
+
// Always refresh membership from disk, then overlay only our pending changes.
|
|
179
|
+
const local = new Map(this.#shared.index.sessions.map((session) => [session.id, session]));
|
|
180
|
+
const published = new Set(disk.sessions.map((session) => session.id));
|
|
181
|
+
this.#shared.index = {
|
|
182
|
+
...disk,
|
|
183
|
+
revision: this.#shared.indexDirty || this.#shared.pendingPublishIds.size > 0
|
|
184
|
+
? Math.max(disk.revision, this.#shared.index.revision)
|
|
185
|
+
: disk.revision,
|
|
186
|
+
sessions: disk.sessions
|
|
187
|
+
.map((session) => {
|
|
188
|
+
const current = local.get(session.id);
|
|
189
|
+
if (!current || !this.#shared.dirtySessionVersions.has(session.id))
|
|
190
|
+
return session;
|
|
191
|
+
return this.#shared.derivedOnlyDirtySessions.has(session.id)
|
|
192
|
+
? mergeSessionDerivedCache(session, current)
|
|
193
|
+
: session.storage === undefined
|
|
194
|
+
? current
|
|
195
|
+
: { ...current, storage: session.storage };
|
|
196
|
+
})
|
|
197
|
+
.concat(this.#shared.index.sessions.filter((session) => this.#shared.pendingPublishIds.has(session.id) && !published.has(session.id)))
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/** Hold the new Session lock and mark its directory until JSONL and index publication finish. */
|
|
201
|
+
async #beginUnpublishedSession(sessionId) {
|
|
202
|
+
const directory = join(this.#shared.root, 'sessions', sessionId);
|
|
203
|
+
await mkdir(directory);
|
|
204
|
+
const release = await acquireFileLock(join(directory, 'lock'), 'Session already has a writer.', 0);
|
|
205
|
+
try {
|
|
206
|
+
await writeFile(join(directory, 'publication-pending'), '1', {
|
|
207
|
+
mode: 0o600,
|
|
208
|
+
flag: 'wx',
|
|
209
|
+
flush: true
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
await release();
|
|
214
|
+
await rm(directory, { recursive: true, force: true });
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
return { directory, release };
|
|
218
|
+
}
|
|
219
|
+
async createSession(input) {
|
|
220
|
+
this.#assertOpen();
|
|
221
|
+
const source = normalizeSessionSource(input.source);
|
|
222
|
+
if (source.type !== 'subagent')
|
|
223
|
+
return this.#createSession(input, source);
|
|
224
|
+
// Publish under the parent's existing serial domain so tree admission cannot miss a child.
|
|
225
|
+
return this.#serializeRecordOperation(source.parentSessionId, async () => {
|
|
226
|
+
this.#assertOpen();
|
|
227
|
+
if (this.#shared.writerLeases.get(source.parentSessionId)?.maintenance)
|
|
228
|
+
throw new AgentError('MAR_AGENT_SESSION_LOCKED', 'Session is under maintenance.');
|
|
229
|
+
const release = await this.#acquirePublicationGate(source.parentSessionId, 5000);
|
|
230
|
+
let failure;
|
|
231
|
+
try {
|
|
232
|
+
await this.#recoverPendingTreeDelete(source.parentSessionId);
|
|
233
|
+
if (this.#shared.writerLeases.get(source.parentSessionId)?.maintenance)
|
|
234
|
+
throw new AgentError('MAR_AGENT_SESSION_LOCKED', 'Session is under maintenance.');
|
|
235
|
+
await this.getSession(source.parentSessionId);
|
|
236
|
+
return await this.#createSession(input, source);
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
failure = error;
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
if (failure !== undefined)
|
|
244
|
+
await release().catch(() => undefined);
|
|
245
|
+
else
|
|
246
|
+
await release();
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
async #acquirePublicationGate(sessionId, waitMs = 0) {
|
|
251
|
+
this.#sessionRoot(sessionId);
|
|
252
|
+
const directory = join(this.#shared.root, 'sessions', '.publication-locks');
|
|
253
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
254
|
+
return acquireFileLock(join(directory, sessionId), 'Session is under maintenance.', waitMs);
|
|
255
|
+
}
|
|
256
|
+
/** Append the durable creation record before making this Session visible in the index. */
|
|
257
|
+
async #createSession(input, source) {
|
|
258
|
+
await this.#ensureRootV2();
|
|
259
|
+
const id = randomUUID();
|
|
260
|
+
const now = new Date().toISOString();
|
|
261
|
+
const meta = {
|
|
262
|
+
id,
|
|
263
|
+
title: input.title ?? 'New session',
|
|
264
|
+
workspace: input.workspace,
|
|
265
|
+
createdAt: now,
|
|
266
|
+
lastActivityAt: now,
|
|
267
|
+
source,
|
|
268
|
+
storage: { encoding: 2, generation: randomUUID() },
|
|
269
|
+
nextSequence: 1,
|
|
270
|
+
automaticTitlePending: input.title === undefined,
|
|
271
|
+
resume: {
|
|
272
|
+
version: 1,
|
|
273
|
+
userTurns: 0,
|
|
274
|
+
todoRevision: 0,
|
|
275
|
+
executionModelIndexed: true,
|
|
276
|
+
firstUserMessageIndexed: true
|
|
277
|
+
},
|
|
278
|
+
segments: []
|
|
279
|
+
};
|
|
280
|
+
const publication = await this.#beginUnpublishedSession(id);
|
|
281
|
+
this.#shared.segments.set(id, 1);
|
|
282
|
+
this.#shared.pendingSessions.set(id, meta);
|
|
283
|
+
let discard = false;
|
|
284
|
+
let creationFailure;
|
|
285
|
+
try {
|
|
286
|
+
try {
|
|
287
|
+
const generationDirectory = this.#directoryForMeta(id, meta);
|
|
288
|
+
await mkdir(generationDirectory, { recursive: true, mode: 0o700 });
|
|
289
|
+
await syncJsonlDirectory(join(publication.directory, 'v2'));
|
|
290
|
+
await syncJsonlDirectory(publication.directory);
|
|
291
|
+
await this.append(id, {
|
|
292
|
+
type: 'session.created',
|
|
293
|
+
payload: {
|
|
294
|
+
title: meta.title,
|
|
295
|
+
workspace: meta.workspace,
|
|
296
|
+
automaticTitle: input.title === undefined,
|
|
297
|
+
source: meta.source
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
await syncJsonlDirectory(generationDirectory);
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
this.#shared.pendingSessions.delete(id);
|
|
304
|
+
this.#shared.segments.delete(id);
|
|
305
|
+
discard = true;
|
|
306
|
+
throw error;
|
|
307
|
+
}
|
|
308
|
+
this.#shared.pendingSessions.delete(id);
|
|
309
|
+
this.#shared.index.sessions.unshift(meta);
|
|
310
|
+
this.#shared.pendingPublishIds.add(id);
|
|
311
|
+
this.#markIndexDirty(id);
|
|
312
|
+
let persisted = false;
|
|
313
|
+
try {
|
|
314
|
+
await this.#saveIndex(undefined, [id], () => {
|
|
315
|
+
persisted = true;
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
if (!persisted) {
|
|
320
|
+
if (error instanceof ParentPublicationRejected) {
|
|
321
|
+
discard = true;
|
|
322
|
+
this.#shared.index.sessions = this.#shared.index.sessions.filter((item) => item.id !== id);
|
|
323
|
+
this.#shared.segments.delete(id);
|
|
324
|
+
this.#shared.pendingPublishIds.delete(id);
|
|
325
|
+
this.#shared.dirtySessionVersions.delete(id);
|
|
326
|
+
this.#shared.indexDirty = this.#shared.dirtySessionVersions.size > 0;
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
// A failed lock release can follow a committed index rename; do not unpublish that Session.
|
|
330
|
+
// If the index cannot be read, leave the disk marker for recovery and keep the original error.
|
|
331
|
+
const disk = await readSessionIndex(join(this.#shared.root, 'sessions', 'index.json')).catch(() => undefined);
|
|
332
|
+
if (disk?.sessions.some((session) => session.id === id)) {
|
|
333
|
+
this.#shared.pendingPublishIds.delete(id);
|
|
334
|
+
this.#shared.dirtySessionVersions.delete(id);
|
|
335
|
+
this.#shared.indexDirty = this.#shared.dirtySessionVersions.size > 0;
|
|
336
|
+
this.#shared.migrationFailures.set(id, { error, unreadable: true });
|
|
337
|
+
throw error;
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
// Keep the marker until disk membership can be checked during recovery.
|
|
341
|
+
this.#shared.index.sessions = this.#shared.index.sessions.filter((item) => item.id !== id);
|
|
342
|
+
this.#shared.segments.delete(id);
|
|
343
|
+
this.#shared.pendingPublishIds.delete(id);
|
|
344
|
+
this.#shared.dirtySessionVersions.delete(id);
|
|
345
|
+
this.#shared.indexDirty = this.#shared.dirtySessionVersions.size > 0;
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
await rm(join(publication.directory, 'publication-pending'), { force: true });
|
|
351
|
+
return meta;
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
creationFailure = error;
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
finally {
|
|
358
|
+
if (creationFailure !== undefined) {
|
|
359
|
+
await publication.release().catch(() => undefined);
|
|
360
|
+
if (discard)
|
|
361
|
+
await rm(publication.directory, { recursive: true, force: true }).catch(() => undefined);
|
|
362
|
+
}
|
|
363
|
+
else
|
|
364
|
+
await publication.release();
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
#sessionDirectory(sessionId) {
|
|
368
|
+
this.#assertOpen();
|
|
369
|
+
const migrationFailure = this.#shared.migrationFailures.get(sessionId);
|
|
370
|
+
if (migrationFailure?.unreadable)
|
|
371
|
+
throw migrationFailure.error;
|
|
372
|
+
const failure = this.#shared.writerLeases.get(sessionId)?.historyFailure;
|
|
373
|
+
if (failure)
|
|
374
|
+
throw failure.error;
|
|
375
|
+
if (!this.#shared.index.sessions.some((session) => session.id === sessionId) &&
|
|
376
|
+
!this.#shared.pendingSessions.has(sessionId))
|
|
377
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
378
|
+
const meta = this.#shared.pendingSessions.get(sessionId) ??
|
|
379
|
+
this.#shared.index.sessions.find((session) => session.id === sessionId);
|
|
380
|
+
return this.#directoryForMeta(sessionId, meta);
|
|
381
|
+
}
|
|
382
|
+
#directoryForMeta(sessionId, meta) {
|
|
383
|
+
const root = join(this.#shared.root, 'sessions', sessionId);
|
|
384
|
+
return meta?.storage?.encoding === 2 ? join(root, 'v2', meta.storage.generation) : root;
|
|
385
|
+
}
|
|
386
|
+
#sessionRoot(sessionId) {
|
|
387
|
+
if (!/^[0-9a-f-]{36}$/iu.test(sessionId))
|
|
388
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
389
|
+
return join(this.#shared.root, 'sessions', sessionId);
|
|
390
|
+
}
|
|
391
|
+
#encoding(sessionId) {
|
|
392
|
+
const meta = this.#shared.pendingSessions.get(sessionId) ??
|
|
393
|
+
this.#shared.index.sessions.find((session) => session.id === sessionId);
|
|
394
|
+
return meta?.storage?.encoding ?? 1;
|
|
395
|
+
}
|
|
396
|
+
async readContextualWorldState(sessionId) {
|
|
397
|
+
return withSessionReadPin(this.#sessionRoot(sessionId), async () => {
|
|
398
|
+
await this.#refreshIndex();
|
|
399
|
+
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
400
|
+
if (!meta)
|
|
401
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
402
|
+
await this.#segmentPaths(sessionId, meta);
|
|
403
|
+
const path = join(this.#directoryForMeta(sessionId, meta), CONTEXTUAL_WORLD_STATE_FILE);
|
|
404
|
+
try {
|
|
405
|
+
const envelope = JSON.parse(await readFile(path, 'utf8'));
|
|
406
|
+
admitWorldStateVersion(envelope);
|
|
407
|
+
if (envelope.version !== 1 ||
|
|
408
|
+
!Number.isSafeInteger(envelope.historySequence) ||
|
|
409
|
+
envelope.historySequence < 1 ||
|
|
410
|
+
envelope.historySequence >= meta.nextSequence)
|
|
411
|
+
return undefined;
|
|
412
|
+
return envelope.state;
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
if (error instanceof AgentError && error.code === 'MAR_AGENT_SESSION_VERSION_UNSUPPORTED')
|
|
416
|
+
throw error;
|
|
417
|
+
return undefined;
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
async writeContextualWorldState(sessionId, state) {
|
|
422
|
+
return this.#serializeRecordOperation(sessionId, () => this.#withWriteAdmission(sessionId, async () => {
|
|
423
|
+
const directory = this.#sessionDirectory(sessionId);
|
|
424
|
+
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
425
|
+
if (!meta)
|
|
426
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
427
|
+
const path = join(directory, CONTEXTUAL_WORLD_STATE_FILE);
|
|
428
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
429
|
+
const serialized = JSON.stringify({
|
|
430
|
+
version: 1,
|
|
431
|
+
historySequence: meta.nextSequence - 1,
|
|
432
|
+
state
|
|
433
|
+
});
|
|
434
|
+
if (Buffer.byteLength(serialized) > MAX_CONTEXTUAL_WORLD_STATE_BYTES)
|
|
435
|
+
throw new AgentError('MAR_AGENT_HOST_LIMIT_EXCEEDED', 'Contextual world state exceeds the storage limit.');
|
|
436
|
+
try {
|
|
437
|
+
await writeFile(temporary, serialized, { mode: 0o600, flag: 'wx' });
|
|
438
|
+
await rename(temporary, path);
|
|
439
|
+
}
|
|
440
|
+
finally {
|
|
441
|
+
await rm(temporary, { force: true });
|
|
442
|
+
}
|
|
443
|
+
}));
|
|
444
|
+
}
|
|
445
|
+
/** Mark each image pending before publication; turn.user or lease recovery resolves ownership. */
|
|
446
|
+
async persistInputImages(sessionId, images) {
|
|
447
|
+
this.#assertOpen();
|
|
448
|
+
if (images.length === 0)
|
|
449
|
+
return [];
|
|
450
|
+
if (images.length > MAX_INPUT_IMAGES)
|
|
451
|
+
throw new AgentError('MAR_AGENT_HOST_LIMIT_EXCEEDED', 'Too many image inputs were provided.');
|
|
452
|
+
const resources = [];
|
|
453
|
+
let total = 0;
|
|
454
|
+
for (const image of images) {
|
|
455
|
+
if ((image.url !== undefined && !isModelImageUrl(image.url)) ||
|
|
456
|
+
(image.urlExpiresAt !== undefined &&
|
|
457
|
+
(!Number.isSafeInteger(image.urlExpiresAt) ||
|
|
458
|
+
image.urlExpiresAt <= 0 ||
|
|
459
|
+
image.url === undefined)))
|
|
460
|
+
throw new AgentError('MAR_AGENT_SESSION_INPUT_INVALID', 'Image model URL is invalid.');
|
|
461
|
+
let resource = await this.persistResource(sessionId, image);
|
|
462
|
+
if (image.url !== undefined && image.urlExpiresAt !== undefined)
|
|
463
|
+
resource = await this.setResourceUrl(sessionId, resource.resourceId, {
|
|
464
|
+
url: image.url,
|
|
465
|
+
urlExpiresAt: image.urlExpiresAt
|
|
466
|
+
});
|
|
467
|
+
total += resource.byteSize;
|
|
468
|
+
if (resource.byteSize > 5 * 1024 * 1024 || total > 10 * 1024 * 1024)
|
|
469
|
+
throw new AgentError('MAR_AGENT_HOST_LIMIT_EXCEEDED', 'Image input exceeds the supported size limit.');
|
|
470
|
+
resources.push({
|
|
471
|
+
...resource,
|
|
472
|
+
resourceInputVersion: 1,
|
|
473
|
+
name: image.name
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
return resources;
|
|
477
|
+
}
|
|
478
|
+
async persistResource(sessionId, image) {
|
|
479
|
+
this.#assertOpen();
|
|
480
|
+
return this.#serializeRecordOperation(sessionId, () => this.#withWriteAdmission(sessionId, () => persistResource(join(this.#sessionDirectory(sessionId), 'resources'), image), true));
|
|
481
|
+
}
|
|
482
|
+
async setResourceUrl(sessionId, resourceId, published) {
|
|
483
|
+
this.#assertOpen();
|
|
484
|
+
return this.#serializeRecordOperation(sessionId, () => this.#withWriteAdmission(sessionId, () => setResourceUrl(join(this.#sessionDirectory(sessionId), 'resources'), resourceId, published)));
|
|
485
|
+
}
|
|
486
|
+
async readResource(sessionId, resourceId) {
|
|
487
|
+
this.#assertOpen();
|
|
488
|
+
if (!/^[a-f0-9]{64}$/u.test(resourceId))
|
|
489
|
+
return undefined;
|
|
490
|
+
return this.#serializeRecordOperation(sessionId, () => withSessionReadPin(this.#sessionRoot(sessionId), async () => {
|
|
491
|
+
await this.#refreshIndex();
|
|
492
|
+
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
493
|
+
if (!meta)
|
|
494
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
495
|
+
await this.#segmentPaths(sessionId, meta);
|
|
496
|
+
const directory = join(this.#directoryForMeta(sessionId, meta), 'resources');
|
|
497
|
+
let pending = false;
|
|
498
|
+
try {
|
|
499
|
+
await lstat(join(directory, `.${resourceId}.pending`));
|
|
500
|
+
pending = true;
|
|
501
|
+
}
|
|
502
|
+
catch (cause) {
|
|
503
|
+
if (cause.code !== 'ENOENT')
|
|
504
|
+
throw new AgentError('MAR_AGENT_IMAGE_UNAVAILABLE', 'Session image resource is unavailable.', {
|
|
505
|
+
cause
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
if (pending &&
|
|
509
|
+
(this.#shared.writerLeases.get(sessionId)?.appendFailure !== undefined ||
|
|
510
|
+
!resourceFiles(await this.#readRecords(sessionId, meta)).has(`${resourceId}.bin`)))
|
|
511
|
+
return undefined;
|
|
512
|
+
return readResource(directory, resourceId);
|
|
513
|
+
}));
|
|
514
|
+
}
|
|
515
|
+
async writeToolResultResource(sessionId, text) {
|
|
516
|
+
this.#assertOpen();
|
|
517
|
+
if (!this.#shared.index.sessions.some((item) => item.id === sessionId))
|
|
518
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
519
|
+
return this.#serializeRecordOperation(sessionId, () => this.#withWriteAdmission(sessionId, () => writeToolResultResource(this.#sessionDirectory(sessionId), text), true));
|
|
520
|
+
}
|
|
521
|
+
async readToolResultResource(sessionId, input) {
|
|
522
|
+
return withSessionReadPin(this.#sessionRoot(sessionId), async () => {
|
|
523
|
+
this.#assertOpen();
|
|
524
|
+
await this.#refreshIndex();
|
|
525
|
+
if (!this.#shared.index.sessions.some((item) => item.id === sessionId))
|
|
526
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
527
|
+
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
528
|
+
await this.#segmentPaths(sessionId, meta);
|
|
529
|
+
return readToolResultResource(this.#directoryForMeta(sessionId, meta), sessionId, input, async (sequence) => {
|
|
530
|
+
return (await this.#shared.historyPages.read(meta, { beforeSequence: sequence + 1, limit: 1 }, () => this.#segmentPaths(sessionId, meta))).records.find((record) => record.sequence === sequence);
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
async readInputImages(sessionId, value) {
|
|
535
|
+
this.#assertOpen();
|
|
536
|
+
const references = inputImageReferences(Array.isArray(value) ? value.filter((item) => item?.resourceInputVersion === 1) : undefined);
|
|
537
|
+
if (references.length === 0)
|
|
538
|
+
return [];
|
|
539
|
+
const images = [];
|
|
540
|
+
for (const reference of references) {
|
|
541
|
+
if (reference.resourceId === undefined)
|
|
542
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session resource identity is missing.');
|
|
543
|
+
images.push({
|
|
544
|
+
resourceInputVersion: 1,
|
|
545
|
+
mimeType: reference.mimeType,
|
|
546
|
+
byteSize: reference.byteSize,
|
|
547
|
+
resourceId: reference.resourceId,
|
|
548
|
+
...(reference.url === undefined ? {} : { url: reference.url }),
|
|
549
|
+
...(reference.urlExpiresAt === undefined ? {} : { urlExpiresAt: reference.urlExpiresAt })
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
return images;
|
|
553
|
+
}
|
|
554
|
+
async readInputImage(sessionId, input) {
|
|
555
|
+
return withSessionReadPin(this.#sessionRoot(sessionId), async () => {
|
|
556
|
+
this.#assertOpen();
|
|
557
|
+
if (!Number.isSafeInteger(input.sequence) ||
|
|
558
|
+
input.sequence < 1 ||
|
|
559
|
+
input.sequence >= Number.MAX_SAFE_INTEGER ||
|
|
560
|
+
!Number.isSafeInteger(input.imageIndex) ||
|
|
561
|
+
input.imageIndex < 0)
|
|
562
|
+
return undefined;
|
|
563
|
+
await this.#refreshIndex();
|
|
564
|
+
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
565
|
+
if (!meta)
|
|
566
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
567
|
+
await this.#assertReadable(sessionId, meta);
|
|
568
|
+
const page = await this.#shared.historyPages.read(meta, {
|
|
569
|
+
limit: 1,
|
|
570
|
+
beforeSequence: input.sequence + 1
|
|
571
|
+
}, () => this.#segmentPaths(sessionId, meta));
|
|
572
|
+
const record = page.records[0];
|
|
573
|
+
if (record?.sequence !== input.sequence ||
|
|
574
|
+
record.type !== 'turn.user' ||
|
|
575
|
+
!isObject(record.payload))
|
|
576
|
+
return undefined;
|
|
577
|
+
if (!Array.isArray(record.payload.images) || input.imageIndex >= record.payload.images.length)
|
|
578
|
+
return undefined;
|
|
579
|
+
if (record.payload.images[input.imageIndex]?.resourceInputVersion !== 1)
|
|
580
|
+
return undefined;
|
|
581
|
+
const reference = inputImageReference(record.payload.images[input.imageIndex]);
|
|
582
|
+
if (reference === undefined)
|
|
583
|
+
return undefined;
|
|
584
|
+
if (reference.resourceId === undefined)
|
|
585
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session resource identity is missing.');
|
|
586
|
+
const data = await readInputImageBytes(join(this.#directoryForMeta(sessionId, meta), 'resources'), reference);
|
|
587
|
+
return {
|
|
588
|
+
name: reference.name,
|
|
589
|
+
resourceInputVersion: 1,
|
|
590
|
+
resourceId: reference.resourceId,
|
|
591
|
+
mimeType: reference.mimeType,
|
|
592
|
+
byteSize: reference.byteSize,
|
|
593
|
+
sha256: reference.sha256,
|
|
594
|
+
...(reference.url === undefined ? {} : { url: reference.url }),
|
|
595
|
+
...(reference.urlExpiresAt === undefined ? {} : { urlExpiresAt: reference.urlExpiresAt }),
|
|
596
|
+
dataBase64: data.toString('base64')
|
|
597
|
+
};
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
async forkSession(sourceSessionId, input) {
|
|
601
|
+
return this.#serializeRecordOperation(sourceSessionId, async () => {
|
|
602
|
+
this.#assertOpen();
|
|
603
|
+
await this.#recoverPendingTreeDelete(sourceSessionId);
|
|
604
|
+
if (this.#shared.writerLeases.has(sourceSessionId))
|
|
605
|
+
throw new AgentError('MAR_AGENT_SESSION_LOCKED', 'Session already has a writer.');
|
|
606
|
+
const sourceRoot = join(this.#shared.root, 'sessions', sourceSessionId);
|
|
607
|
+
const release = await acquireFileLock(join(sourceRoot, 'lock'));
|
|
608
|
+
let sourceFailed = false;
|
|
609
|
+
try {
|
|
610
|
+
await this.#refreshIndex();
|
|
611
|
+
const source = this.#shared.index.sessions.find((item) => item.id === sourceSessionId);
|
|
612
|
+
if (!source)
|
|
613
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
614
|
+
if (source.storage?.encoding !== 2)
|
|
615
|
+
await preflightV1Session(sourceRoot);
|
|
616
|
+
await this.#ensureRootV2();
|
|
617
|
+
const records = await this.#readRecords(sourceSessionId);
|
|
618
|
+
let publicationConfirmed = false;
|
|
619
|
+
return await forkSessionOperation(source, records, input, sourceRoot, this.#sessionDirectory(sourceSessionId), {
|
|
620
|
+
begin: async (target) => {
|
|
621
|
+
const publication = await this.#beginUnpublishedSession(target.id);
|
|
622
|
+
this.#shared.segments.set(target.id, 1);
|
|
623
|
+
this.#shared.pendingSessions.set(target.id, target);
|
|
624
|
+
return publication;
|
|
625
|
+
},
|
|
626
|
+
publish: async (target, copied) => {
|
|
627
|
+
this.#shared.segments.set(target.id, target.segments.length);
|
|
628
|
+
for (const record of copied)
|
|
629
|
+
this.#applyRecordToResumeIndex(target, record);
|
|
630
|
+
this.#shared.pendingSessions.delete(target.id);
|
|
631
|
+
this.#shared.index = {
|
|
632
|
+
version: 2,
|
|
633
|
+
revision: this.#shared.index.revision + 1,
|
|
634
|
+
sessions: [target, ...this.#shared.index.sessions]
|
|
635
|
+
};
|
|
636
|
+
this.#shared.pendingPublishIds.add(target.id);
|
|
637
|
+
this.#markIndexDirty(target.id);
|
|
638
|
+
try {
|
|
639
|
+
await this.#saveIndex(undefined, [target.id], () => {
|
|
640
|
+
publicationConfirmed = true;
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
catch (error) {
|
|
644
|
+
if (!publicationConfirmed)
|
|
645
|
+
throw error;
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
reject: async (target, error) => {
|
|
649
|
+
this.#shared.pendingSessions.delete(target.id);
|
|
650
|
+
if (publicationConfirmed) {
|
|
651
|
+
this.#shared.migrationFailures.set(target.id, { error, unreadable: true });
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
let disk;
|
|
655
|
+
try {
|
|
656
|
+
disk = await readSessionIndex(join(this.#shared.root, 'sessions', 'index.json'));
|
|
657
|
+
}
|
|
658
|
+
catch {
|
|
659
|
+
this.#shared.migrationFailures.set(target.id, { error, unreadable: true });
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
if (!disk || disk.sessions.some((session) => session.id === target.id)) {
|
|
663
|
+
this.#shared.migrationFailures.set(target.id, { error, unreadable: true });
|
|
664
|
+
return false;
|
|
665
|
+
}
|
|
666
|
+
const publishedIndex = this.#shared.index.sessions.findIndex((item) => item.id === target.id);
|
|
667
|
+
if (publishedIndex >= 0) {
|
|
668
|
+
this.#shared.index.sessions.splice(publishedIndex, 1);
|
|
669
|
+
this.#shared.index.revision++;
|
|
670
|
+
this.#shared.pendingPublishIds.delete(target.id);
|
|
671
|
+
this.#shared.dirtySessionVersions.delete(target.id);
|
|
672
|
+
this.#shared.indexDirty = this.#shared.dirtySessionVersions.size > 0;
|
|
673
|
+
}
|
|
674
|
+
this.#shared.segments.delete(target.id);
|
|
675
|
+
return true;
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
catch (error) {
|
|
680
|
+
sourceFailed = true;
|
|
681
|
+
throw error;
|
|
682
|
+
}
|
|
683
|
+
finally {
|
|
684
|
+
if (sourceFailed)
|
|
685
|
+
await release().catch(() => undefined);
|
|
686
|
+
else
|
|
687
|
+
await release();
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
/** Replace history under a journal; the indexed transaction ID decides the commit side. */
|
|
692
|
+
async rollbackSession(sessionId, input) {
|
|
693
|
+
this.#assertOpen();
|
|
694
|
+
const release = await this.acquireSessionLease(sessionId);
|
|
695
|
+
let failed = false;
|
|
696
|
+
try {
|
|
697
|
+
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
698
|
+
if (!meta)
|
|
699
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
700
|
+
const records = await this.read(sessionId);
|
|
701
|
+
if (records[0]?.type !== 'session.created')
|
|
702
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session history is corrupt.');
|
|
703
|
+
const boundaryIndex = rollbackBoundaryIndex(records, input);
|
|
704
|
+
const retained = records
|
|
705
|
+
.slice(0, boundaryIndex + 1)
|
|
706
|
+
.filter((record) => record.type !== 'responses.continuation');
|
|
707
|
+
return await this.#replaceHistory(meta, retained, { kind: 'rollback' });
|
|
708
|
+
}
|
|
709
|
+
catch (error) {
|
|
710
|
+
failed = true;
|
|
711
|
+
throw error;
|
|
712
|
+
}
|
|
713
|
+
finally {
|
|
714
|
+
if (failed)
|
|
715
|
+
await release().catch(() => undefined);
|
|
716
|
+
else
|
|
717
|
+
await release();
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
async updateTitle(sessionId, title, input = {}, owner) {
|
|
721
|
+
this.#assertOpen();
|
|
722
|
+
const normalized = title.trim();
|
|
723
|
+
if (!normalized)
|
|
724
|
+
throw new AgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Session title is required.');
|
|
725
|
+
const release = await this.acquireSessionLease(sessionId, owner);
|
|
726
|
+
try {
|
|
727
|
+
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
728
|
+
if (input.expectedTitle !== undefined && meta.title !== input.expectedTitle)
|
|
729
|
+
return false;
|
|
730
|
+
if (input.automatic || input.executionId !== undefined) {
|
|
731
|
+
const records = await this.read(sessionId);
|
|
732
|
+
const latestTitle = records.findLast((record) => record.type === 'session.title.updated');
|
|
733
|
+
if (input.automatic &&
|
|
734
|
+
(records[0]?.payload?.automaticTitle !== true ||
|
|
735
|
+
latestTitle?.payload?.automatic === false))
|
|
736
|
+
return false;
|
|
737
|
+
if (input.executionId !== undefined &&
|
|
738
|
+
records.findLast((record) => record.type === 'turn.user')?.executionId !==
|
|
739
|
+
input.executionId)
|
|
740
|
+
return false;
|
|
741
|
+
}
|
|
742
|
+
await this.append(sessionId, {
|
|
743
|
+
type: 'session.title.updated',
|
|
744
|
+
payload: { title: normalized, automatic: input.automatic === true },
|
|
745
|
+
...(input.executionId ? { executionId: input.executionId } : {}),
|
|
746
|
+
...(input.turnId ? { turnId: input.turnId } : {})
|
|
747
|
+
});
|
|
748
|
+
return true;
|
|
749
|
+
}
|
|
750
|
+
finally {
|
|
751
|
+
await release();
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
/** Reset history with the same recovery journal while preserving the Session identity. */
|
|
755
|
+
async clearSession(sessionId, maintenance) {
|
|
756
|
+
this.#assertOpen();
|
|
757
|
+
const holding = maintenance ?? (await this.acquireSessionMaintenance(sessionId, 'clear'));
|
|
758
|
+
this.#assertMaintenance(sessionId, 'clear', holding);
|
|
759
|
+
let failed = false;
|
|
760
|
+
try {
|
|
761
|
+
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
762
|
+
if (!meta)
|
|
763
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
764
|
+
const records = await this.read(sessionId);
|
|
765
|
+
if (records[0]?.type !== 'session.created')
|
|
766
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session history is corrupt.');
|
|
767
|
+
const clearedAt = new Date().toISOString();
|
|
768
|
+
const retained = [
|
|
769
|
+
{
|
|
770
|
+
recordId: randomUUID(),
|
|
771
|
+
sequence: 1,
|
|
772
|
+
timestamp: meta.createdAt,
|
|
773
|
+
sessionId,
|
|
774
|
+
type: 'session.created',
|
|
775
|
+
payload: {
|
|
776
|
+
title: meta.title,
|
|
777
|
+
workspace: meta.workspace,
|
|
778
|
+
automaticTitle: meta.automaticTitlePending === true,
|
|
779
|
+
source: normalizeSessionSource(meta.source)
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
];
|
|
783
|
+
return await this.#replaceHistory(meta, retained, { kind: 'clear', clearedAt });
|
|
784
|
+
}
|
|
785
|
+
catch (error) {
|
|
786
|
+
failed = true;
|
|
787
|
+
throw error;
|
|
788
|
+
}
|
|
789
|
+
finally {
|
|
790
|
+
if (!maintenance) {
|
|
791
|
+
const releasing = holding.release();
|
|
792
|
+
if (failed)
|
|
793
|
+
await releasing.catch(() => undefined);
|
|
794
|
+
else
|
|
795
|
+
await releasing;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
/** Called only while rollback or clear holds the maintenance lease. */
|
|
800
|
+
async #replaceHistory(meta, retained, operation) {
|
|
801
|
+
const sessionId = meta.id;
|
|
802
|
+
const shared = this.#shared;
|
|
803
|
+
let indexConfirmed = false;
|
|
804
|
+
const before = structuredClone(meta);
|
|
805
|
+
const invalidate = (error) => {
|
|
806
|
+
const writer = shared.writerLeases.get(sessionId);
|
|
807
|
+
if (writer) {
|
|
808
|
+
writer.appendFailure ??= { error };
|
|
809
|
+
writer.historyFailure ??= { error };
|
|
810
|
+
delete writer.codeState;
|
|
811
|
+
delete writer.extensionCheckpoints;
|
|
812
|
+
}
|
|
813
|
+
shared.dirtySessionVersions.delete(sessionId);
|
|
814
|
+
shared.derivedOnlyDirtySessions.delete(sessionId);
|
|
815
|
+
shared.indexDirty = shared.dirtySessionVersions.size > 0;
|
|
816
|
+
shared.segments.delete(sessionId);
|
|
817
|
+
};
|
|
818
|
+
const targetGeneration = operation.kind === 'clear' && (meta.storage?.encoding ?? 1) === 1 ? randomUUID() : undefined;
|
|
819
|
+
return withSessionReadDrain(this.#sessionRoot(sessionId), async () => {
|
|
820
|
+
if (targetGeneration !== undefined) {
|
|
821
|
+
const parent = join(this.#sessionRoot(sessionId), 'v2');
|
|
822
|
+
await mkdir(parent, { recursive: true, mode: 0o700 });
|
|
823
|
+
await mkdir(join(parent, targetGeneration), { mode: 0o700 });
|
|
824
|
+
await syncJsonlDirectory(parent);
|
|
825
|
+
await syncJsonlDirectory(this.#sessionRoot(sessionId));
|
|
826
|
+
}
|
|
827
|
+
const result = await replaceSessionHistory(meta, retained, operation, {
|
|
828
|
+
directory: this.#sessionDirectory(sessionId),
|
|
829
|
+
sessionDirectory: this.#sessionRoot(sessionId),
|
|
830
|
+
...(targetGeneration === undefined ? {} : { targetGeneration }),
|
|
831
|
+
segmentPaths: () => this.#segmentPaths(sessionId),
|
|
832
|
+
settleIndex: (request, recover) => this.#serializeIndexOperation(async () => {
|
|
833
|
+
let current;
|
|
834
|
+
let previous;
|
|
835
|
+
let previousRevision = 0;
|
|
836
|
+
let previousSegment;
|
|
837
|
+
try {
|
|
838
|
+
if (request.kind === 'recover')
|
|
839
|
+
throw request.error;
|
|
840
|
+
const change = request.change;
|
|
841
|
+
current = shared.index.sessions.find((item) => item.id === sessionId);
|
|
842
|
+
if (!current)
|
|
843
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session index entry is missing.');
|
|
844
|
+
previous = structuredClone(current);
|
|
845
|
+
previousRevision = shared.index.revision;
|
|
846
|
+
previousSegment = shared.segments.get(sessionId);
|
|
847
|
+
if (change.kind === 'rollback') {
|
|
848
|
+
current.title = change.title ?? current.title;
|
|
849
|
+
current.lastActivityAt = new Date().toISOString();
|
|
850
|
+
current.automaticTitlePending = change.state.automaticTitlePending;
|
|
851
|
+
current.resume = change.state.resume;
|
|
852
|
+
}
|
|
853
|
+
else {
|
|
854
|
+
current.lastActivityAt = change.clearedAt;
|
|
855
|
+
current.resume = {
|
|
856
|
+
version: 1,
|
|
857
|
+
userTurns: 0,
|
|
858
|
+
todoRevision: 0,
|
|
859
|
+
firstUserMessageIndexed: true
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
current.nextSequence = change.nextSequence;
|
|
863
|
+
current.storageTransactionId = change.transactionId;
|
|
864
|
+
current.segments = change.segments;
|
|
865
|
+
if (change.targetGeneration !== undefined)
|
|
866
|
+
current.storage = { encoding: 2, generation: change.targetGeneration };
|
|
867
|
+
shared.segments.set(sessionId, change.kind === 'clear' ? 1 : (change.segments.at(-1)?.number ?? 1));
|
|
868
|
+
shared.index.revision++;
|
|
869
|
+
this.#markIndexDirty(sessionId);
|
|
870
|
+
await this.#commitIndex(undefined, [], () => {
|
|
871
|
+
indexConfirmed = true;
|
|
872
|
+
}, targetGeneration === undefined
|
|
873
|
+
? undefined
|
|
874
|
+
: { before, generation: targetGeneration });
|
|
875
|
+
return { status: 'committed', meta: structuredClone(current) };
|
|
876
|
+
}
|
|
877
|
+
catch (error) {
|
|
878
|
+
try {
|
|
879
|
+
const selected = await this.#storageTransactionCommitted(sessionId, request.kind === 'apply' ? request.change.transactionId : request.transactionId);
|
|
880
|
+
if (selected && !indexConfirmed)
|
|
881
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'History transaction durability is uncertain.');
|
|
882
|
+
if (selected) {
|
|
883
|
+
shared.dirtySessionVersions.delete(sessionId);
|
|
884
|
+
shared.indexDirty = shared.dirtySessionVersions.size > 0;
|
|
885
|
+
await recover(true);
|
|
886
|
+
return { status: 'committed', meta: structuredClone(current), error };
|
|
887
|
+
}
|
|
888
|
+
await recover(false);
|
|
889
|
+
if (current && previous) {
|
|
890
|
+
restoreSessionIndexMeta(current, previous);
|
|
891
|
+
shared.index.revision = previousRevision;
|
|
892
|
+
if (previousSegment === undefined)
|
|
893
|
+
shared.segments.delete(sessionId);
|
|
894
|
+
else
|
|
895
|
+
shared.segments.set(sessionId, previousSegment);
|
|
896
|
+
this.#markIndexDirty(sessionId);
|
|
897
|
+
}
|
|
898
|
+
return { status: 'not-committed', error };
|
|
899
|
+
}
|
|
900
|
+
catch {
|
|
901
|
+
invalidate(error);
|
|
902
|
+
return { status: 'unknown', error };
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
})
|
|
906
|
+
});
|
|
907
|
+
if (result.status === 'unknown' ||
|
|
908
|
+
(result.status === 'committed' && result.recoveryRequired)) {
|
|
909
|
+
invalidate(result.error);
|
|
910
|
+
}
|
|
911
|
+
if (result.status !== 'committed' || result.error !== undefined)
|
|
912
|
+
throw result.error;
|
|
913
|
+
shared.migrationFailures.delete(sessionId);
|
|
914
|
+
return {
|
|
915
|
+
id: result.meta.id,
|
|
916
|
+
title: result.meta.title,
|
|
917
|
+
workspace: result.meta.workspace,
|
|
918
|
+
createdAt: result.meta.createdAt,
|
|
919
|
+
lastActivityAt: result.meta.lastActivityAt,
|
|
920
|
+
source: normalizeSessionSource(result.meta.source)
|
|
921
|
+
};
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
async getSession(sessionId) {
|
|
925
|
+
this.#assertOpen();
|
|
926
|
+
return withSessionReadPin(this.#sessionRoot(sessionId), async () => {
|
|
927
|
+
await this.#refreshIndex();
|
|
928
|
+
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
929
|
+
if (!meta)
|
|
930
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
931
|
+
return {
|
|
932
|
+
id: meta.id,
|
|
933
|
+
title: meta.title,
|
|
934
|
+
workspace: meta.workspace,
|
|
935
|
+
createdAt: meta.createdAt,
|
|
936
|
+
lastActivityAt: meta.lastActivityAt,
|
|
937
|
+
source: normalizeSessionSource(meta.source),
|
|
938
|
+
automaticTitlePending: meta.automaticTitlePending === true
|
|
939
|
+
};
|
|
940
|
+
}, true);
|
|
941
|
+
}
|
|
942
|
+
/** Serialize physical writes and honor cancellation before entering the writer. */
|
|
943
|
+
async append(sessionId, input, options = {}) {
|
|
944
|
+
this.#assertOpen();
|
|
945
|
+
options.signal?.throwIfAborted();
|
|
946
|
+
return this.#serializeRecordOperation(sessionId, () => this.#withWriteAdmission(sessionId, async () => {
|
|
947
|
+
options.signal?.throwIfAborted();
|
|
948
|
+
const writer = this.#shared.writerLeases.get(sessionId);
|
|
949
|
+
try {
|
|
950
|
+
const result = await this.#appendRecord(sessionId, input);
|
|
951
|
+
if (writer?.pendingResource) {
|
|
952
|
+
for (const name of resourceFiles([result]))
|
|
953
|
+
if (name.endsWith('.bin'))
|
|
954
|
+
writer.pendingResource.delete(`image:${name.slice(0, -4)}`);
|
|
955
|
+
for (const resource of recordToolResources(result))
|
|
956
|
+
writer.pendingResource.delete(`tool:${resource.resourceRef}`);
|
|
957
|
+
if (writer.pendingResource.size === 0) {
|
|
958
|
+
delete writer.pendingResource;
|
|
959
|
+
writer.retained--;
|
|
960
|
+
await this.#releaseUnusedWriter(sessionId, writer);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
return result;
|
|
964
|
+
}
|
|
965
|
+
catch (error) {
|
|
966
|
+
if (writer?.pendingResource && !writer.executing && !writer.appendFailure) {
|
|
967
|
+
delete writer.pendingResource;
|
|
968
|
+
writer.retained--;
|
|
969
|
+
await this.#releaseUnusedWriter(sessionId, writer).catch(() => undefined);
|
|
970
|
+
}
|
|
971
|
+
throw error;
|
|
972
|
+
}
|
|
973
|
+
}));
|
|
974
|
+
}
|
|
975
|
+
/** Called inside the Session serial queue; existing leases already own the physical lock. */
|
|
976
|
+
async #withWriteAdmission(sessionId, action, holdPending = false) {
|
|
977
|
+
if (this.#shared.pendingSessions.has(sessionId))
|
|
978
|
+
return action();
|
|
979
|
+
const existing = this.#shared.writerLeases.get(sessionId);
|
|
980
|
+
if (existing) {
|
|
981
|
+
const recoveryFailure = this.#shared.migrationFailures.get(sessionId);
|
|
982
|
+
if (recoveryFailure)
|
|
983
|
+
throw recoveryFailure.error;
|
|
984
|
+
if (existing.appendFailure)
|
|
985
|
+
throw existing.appendFailure.error;
|
|
986
|
+
if (existing.historyFailure)
|
|
987
|
+
throw existing.historyFailure.error;
|
|
988
|
+
if (existing.maintenance)
|
|
989
|
+
throw new AgentError('MAR_AGENT_SESSION_LOCKED', 'Session is under maintenance.');
|
|
990
|
+
const result = await action();
|
|
991
|
+
if (holdPending && existing.pendingResource)
|
|
992
|
+
this.#trackPendingResource(existing, result);
|
|
993
|
+
return result;
|
|
994
|
+
}
|
|
995
|
+
const release = await this.#acquireSessionLease(sessionId);
|
|
996
|
+
const writer = { owner: undefined, executing: false, retained: 0, release };
|
|
997
|
+
this.#shared.writerLeases.set(sessionId, writer);
|
|
998
|
+
let failure;
|
|
999
|
+
try {
|
|
1000
|
+
const result = await action();
|
|
1001
|
+
if (holdPending) {
|
|
1002
|
+
writer.pendingResource = new Set();
|
|
1003
|
+
this.#trackPendingResource(writer, result);
|
|
1004
|
+
writer.retained++;
|
|
1005
|
+
}
|
|
1006
|
+
return result;
|
|
1007
|
+
}
|
|
1008
|
+
catch (error) {
|
|
1009
|
+
failure = error;
|
|
1010
|
+
throw error;
|
|
1011
|
+
}
|
|
1012
|
+
finally {
|
|
1013
|
+
if (failure !== undefined)
|
|
1014
|
+
await this.#releaseUnusedWriter(sessionId, writer).catch(() => undefined);
|
|
1015
|
+
else
|
|
1016
|
+
await this.#releaseUnusedWriter(sessionId, writer);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
#trackPendingResource(writer, value) {
|
|
1020
|
+
if (!isObject(value) || !writer.pendingResource)
|
|
1021
|
+
return;
|
|
1022
|
+
if (typeof value.resourceId === 'string')
|
|
1023
|
+
writer.pendingResource.add(`image:${value.resourceId}`);
|
|
1024
|
+
if (typeof value.resourceRef === 'string')
|
|
1025
|
+
writer.pendingResource.add(`tool:${value.resourceRef}`);
|
|
1026
|
+
}
|
|
1027
|
+
async #serializeRecordOperation(sessionId, action) {
|
|
1028
|
+
const operation = (this.#shared.recordWrites.get(sessionId) ?? Promise.resolve())
|
|
1029
|
+
.catch(() => undefined)
|
|
1030
|
+
.then(action);
|
|
1031
|
+
this.#shared.recordWrites.set(sessionId, operation);
|
|
1032
|
+
try {
|
|
1033
|
+
return await operation;
|
|
1034
|
+
}
|
|
1035
|
+
finally {
|
|
1036
|
+
if (this.#shared.recordWrites.get(sessionId) === operation)
|
|
1037
|
+
this.#shared.recordWrites.delete(sessionId);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
async #appendRecord(sessionId, input) {
|
|
1041
|
+
this.#assertOpen();
|
|
1042
|
+
const recoveryFailure = this.#shared.migrationFailures.get(sessionId);
|
|
1043
|
+
if (recoveryFailure)
|
|
1044
|
+
throw recoveryFailure.error;
|
|
1045
|
+
const writer = this.#shared.writerLeases.get(sessionId);
|
|
1046
|
+
if (writer?.appendFailure)
|
|
1047
|
+
throw writer.appendFailure.error;
|
|
1048
|
+
const before = this.#shared.index.sessions.find((item) => item.id === sessionId) ??
|
|
1049
|
+
this.#shared.pendingSessions.get(sessionId);
|
|
1050
|
+
if (!before)
|
|
1051
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
1052
|
+
const published = !this.#shared.pendingSessions.has(sessionId);
|
|
1053
|
+
return appendSessionRecord(sessionId, input, {
|
|
1054
|
+
sequence: before.nextSequence,
|
|
1055
|
+
directory: () => this.#sessionDirectory(sessionId),
|
|
1056
|
+
readRecordAtSequence: (sequence) => this.#readRecordAtSequence(before, sequence),
|
|
1057
|
+
currentSegment: () => this.#currentSegment(sessionId),
|
|
1058
|
+
segmentPath: (segment) => this.#segmentPath(sessionId, segment),
|
|
1059
|
+
setCurrentSegment: (segment) => this.#shared.segments.set(sessionId, segment),
|
|
1060
|
+
markWriteFailure: (error) => {
|
|
1061
|
+
if (writer)
|
|
1062
|
+
writer.appendFailure = { error };
|
|
1063
|
+
},
|
|
1064
|
+
invalidateAfterWriteFailure: () => {
|
|
1065
|
+
// A failed write/sync/close can leave a complete record on disk.
|
|
1066
|
+
const current = this.#shared.index.sessions.find((item) => item.id === sessionId) ?? before;
|
|
1067
|
+
if (current.resume?.userTurns !== undefined) {
|
|
1068
|
+
delete current.resume.userTurns;
|
|
1069
|
+
if (published)
|
|
1070
|
+
this.#markIndexDirty(sessionId);
|
|
1071
|
+
}
|
|
1072
|
+
const lease = this.#shared.writerLeases.get(sessionId);
|
|
1073
|
+
if (lease) {
|
|
1074
|
+
delete lease.codeState;
|
|
1075
|
+
delete lease.extensionCheckpoints;
|
|
1076
|
+
}
|
|
1077
|
+
},
|
|
1078
|
+
publish: (record, serialized, segment) => {
|
|
1079
|
+
// Index refreshes and commits can replace metadata during file I/O. Publish all
|
|
1080
|
+
// durable-record metadata onto the current entry without another await boundary.
|
|
1081
|
+
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId) ??
|
|
1082
|
+
this.#shared.pendingSessions.get(sessionId);
|
|
1083
|
+
if (!meta)
|
|
1084
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
1085
|
+
meta.nextSequence = record.sequence + 1;
|
|
1086
|
+
const segmentMetadata = meta.segments ?? (meta.segments = []);
|
|
1087
|
+
const known = segmentMetadata.find((item) => item.number === segment);
|
|
1088
|
+
if (known) {
|
|
1089
|
+
known.lastSequence = record.sequence;
|
|
1090
|
+
known.bytes += Buffer.byteLength(serialized);
|
|
1091
|
+
}
|
|
1092
|
+
else
|
|
1093
|
+
segmentMetadata.push({
|
|
1094
|
+
number: segment,
|
|
1095
|
+
firstSequence: record.sequence,
|
|
1096
|
+
lastSequence: record.sequence,
|
|
1097
|
+
bytes: Buffer.byteLength(serialized)
|
|
1098
|
+
});
|
|
1099
|
+
meta.lastActivityAt = record.timestamp;
|
|
1100
|
+
this.#applyRecordToResumeIndex(meta, record);
|
|
1101
|
+
if (published) {
|
|
1102
|
+
this.#shared.index.revision++;
|
|
1103
|
+
this.#markIndexDirty(sessionId);
|
|
1104
|
+
}
|
|
1105
|
+
updateSessionCurrentState(this.#shared.writerLeases.get(sessionId), record.type, serialized);
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
async read(sessionId) {
|
|
1110
|
+
return this.#serializeRecordOperation(sessionId, () => this.#readRecords(sessionId));
|
|
1111
|
+
}
|
|
1112
|
+
async #readRecords(sessionId, selectedMeta) {
|
|
1113
|
+
return withSessionReadPin(this.#sessionRoot(sessionId), async () => {
|
|
1114
|
+
this.#assertOpen();
|
|
1115
|
+
if (!selectedMeta)
|
|
1116
|
+
await this.#refreshIndex();
|
|
1117
|
+
const failure = this.#shared.writerLeases.get(sessionId)?.historyFailure;
|
|
1118
|
+
if (failure)
|
|
1119
|
+
throw failure.error;
|
|
1120
|
+
const migrationFailure = this.#shared.migrationFailures.get(sessionId);
|
|
1121
|
+
if (migrationFailure?.unreadable)
|
|
1122
|
+
throw migrationFailure.error;
|
|
1123
|
+
try {
|
|
1124
|
+
const records = [];
|
|
1125
|
+
const meta = selectedMeta ?? this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
1126
|
+
const encoding = meta?.storage?.encoding ?? 1;
|
|
1127
|
+
for (const path of await this.#segmentPaths(sessionId, meta))
|
|
1128
|
+
records.push(...(await readRecords(path, encoding)));
|
|
1129
|
+
return records;
|
|
1130
|
+
}
|
|
1131
|
+
catch (cause) {
|
|
1132
|
+
if (cause instanceof AgentError)
|
|
1133
|
+
throw cause;
|
|
1134
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.', { cause });
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
async readCodeModeState(sessionId) {
|
|
1139
|
+
return this.#serializeRecordOperation(sessionId, () => withSessionReadPin(this.#sessionRoot(sessionId), () => this.#readCodeModeState(sessionId)));
|
|
1140
|
+
}
|
|
1141
|
+
async #readCodeModeState(sessionId) {
|
|
1142
|
+
this.#assertOpen();
|
|
1143
|
+
const lease = this.#shared.writerLeases.get(sessionId);
|
|
1144
|
+
if (lease?.owner === undefined)
|
|
1145
|
+
return restoreCodeModeState(await this.#readRecords(sessionId));
|
|
1146
|
+
lease.codeState ??= restoreCodeModeState(await this.#readRecords(sessionId));
|
|
1147
|
+
return applyCodeModeStateMutations(lease.codeState, []);
|
|
1148
|
+
}
|
|
1149
|
+
async readExtensionCheckpoint(sessionId, extensionId, key) {
|
|
1150
|
+
return this.#serializeRecordOperation(sessionId, () => withSessionReadPin(this.#sessionRoot(sessionId), () => this.#readExtensionCheckpoint(sessionId, extensionId, key)));
|
|
1151
|
+
}
|
|
1152
|
+
async #readExtensionCheckpoint(sessionId, extensionId, key) {
|
|
1153
|
+
this.#assertOpen();
|
|
1154
|
+
const lease = this.#shared.writerLeases.get(sessionId);
|
|
1155
|
+
if (lease?.owner === undefined)
|
|
1156
|
+
return restoreExtensionCheckpoint(await this.#readRecords(sessionId), extensionId, key);
|
|
1157
|
+
if (!lease.extensionCheckpoints) {
|
|
1158
|
+
const index = new Map();
|
|
1159
|
+
for (const record of await this.#readRecords(sessionId))
|
|
1160
|
+
if (record.type === 'extension.checkpoint.updated')
|
|
1161
|
+
indexExtensionCheckpoint(index, record.payload);
|
|
1162
|
+
lease.extensionCheckpoints = index;
|
|
1163
|
+
}
|
|
1164
|
+
return extensionCheckpointFromPayload(lease.extensionCheckpoints.get(extensionId)?.get(key));
|
|
1165
|
+
}
|
|
1166
|
+
async writeExtensionCheckpoint(sessionId, extensionId, key, value, expectedRevision, input) {
|
|
1167
|
+
if (typeof key !== 'string' ||
|
|
1168
|
+
(expectedRevision !== undefined &&
|
|
1169
|
+
(!Number.isSafeInteger(expectedRevision) || expectedRevision < 0)))
|
|
1170
|
+
throw new AgentError('MAR_AGENT_EXTENSION_DATA_INVALID', 'Checkpoint key or expected revision is invalid.');
|
|
1171
|
+
const copied = value === undefined ? undefined : cloneCoreJson(value);
|
|
1172
|
+
input.signal.throwIfAborted();
|
|
1173
|
+
const release = await this.acquireSessionWriteLease(sessionId, input.owner);
|
|
1174
|
+
try {
|
|
1175
|
+
return await this.#serializeRecordOperation(sessionId, async () => {
|
|
1176
|
+
const current = await this.#readExtensionCheckpoint(sessionId, extensionId, key);
|
|
1177
|
+
if (expectedRevision !== undefined && expectedRevision !== current.revision)
|
|
1178
|
+
throw new AgentError('MAR_AGENT_EXTENSION_DATA_CONFLICT', 'Checkpoint revision changed before commit.');
|
|
1179
|
+
input.signal.throwIfAborted();
|
|
1180
|
+
const revision = current.revision + 1;
|
|
1181
|
+
await this.#appendRecord(sessionId, {
|
|
1182
|
+
type: 'extension.checkpoint.updated',
|
|
1183
|
+
payload: {
|
|
1184
|
+
extensionId,
|
|
1185
|
+
key,
|
|
1186
|
+
revision,
|
|
1187
|
+
...(copied === undefined ? { deleted: true } : { value: copied })
|
|
1188
|
+
},
|
|
1189
|
+
...(input.executionId ? { executionId: input.executionId } : {}),
|
|
1190
|
+
...(input.turnId ? { turnId: input.turnId } : {})
|
|
1191
|
+
});
|
|
1192
|
+
return { revision, ...(copied === undefined ? {} : { value: cloneCoreJson(copied) }) };
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
finally {
|
|
1196
|
+
await release();
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
async commitCodeModeState(sessionId, mutations, input) {
|
|
1200
|
+
const operation = (this.#shared.codeStateWrites.get(sessionId) ?? Promise.resolve())
|
|
1201
|
+
.catch(() => undefined)
|
|
1202
|
+
.then(async () => {
|
|
1203
|
+
const parsed = parseCodeModeStateMutations(mutations);
|
|
1204
|
+
if (parsed.length === 0)
|
|
1205
|
+
return this.readCodeModeState(sessionId);
|
|
1206
|
+
const current = await this.readCodeModeState(sessionId);
|
|
1207
|
+
if (input.expectedState && !isDeepStrictEqual(current, input.expectedState))
|
|
1208
|
+
throw new AgentError('MAR_AGENT_CODE_STATE_CONFLICT', 'Session state changed while this cell was running.');
|
|
1209
|
+
const state = applyCodeModeStateMutations(current, parsed);
|
|
1210
|
+
await this.append(sessionId, {
|
|
1211
|
+
type: 'code.state.updated',
|
|
1212
|
+
payload: { mutations: parsed },
|
|
1213
|
+
executionId: input.executionId,
|
|
1214
|
+
turnId: input.turnId
|
|
1215
|
+
});
|
|
1216
|
+
return state;
|
|
1217
|
+
});
|
|
1218
|
+
this.#shared.codeStateWrites.set(sessionId, operation);
|
|
1219
|
+
try {
|
|
1220
|
+
return await operation;
|
|
1221
|
+
}
|
|
1222
|
+
finally {
|
|
1223
|
+
if (this.#shared.codeStateWrites.get(sessionId) === operation)
|
|
1224
|
+
this.#shared.codeStateWrites.delete(sessionId);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
async readExecutionState(sessionId) {
|
|
1228
|
+
return this.#serializeRecordOperation(sessionId, () => withSessionReadPin(this.#sessionRoot(sessionId), async () => {
|
|
1229
|
+
this.#assertOpen();
|
|
1230
|
+
await this.#refreshIndex();
|
|
1231
|
+
const failure = this.#shared.writerLeases.get(sessionId)?.historyFailure;
|
|
1232
|
+
if (failure)
|
|
1233
|
+
throw failure.error;
|
|
1234
|
+
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
1235
|
+
if (!meta)
|
|
1236
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
1237
|
+
await this.#assertReadable(sessionId, meta);
|
|
1238
|
+
if (meta.resume?.version !== 1 ||
|
|
1239
|
+
typeof meta.automaticTitlePending !== 'boolean' ||
|
|
1240
|
+
meta.resume.compactSequence === undefined)
|
|
1241
|
+
return this.#rebuildExecutionState(meta);
|
|
1242
|
+
try {
|
|
1243
|
+
const contextRecords = await this.#readFromSequence(meta, meta.resume.compactSequence);
|
|
1244
|
+
if (contextRecords[0]?.sequence !== meta.resume.compactSequence ||
|
|
1245
|
+
!isCompactCheckpoint(contextRecords[0]) ||
|
|
1246
|
+
contextRecords.some((record, index) => index > 0 && isCompactCheckpoint(record)))
|
|
1247
|
+
return this.#rebuildExecutionState(meta);
|
|
1248
|
+
const todo = await this.#readIndexedTodo(meta, contextRecords);
|
|
1249
|
+
if (!todo)
|
|
1250
|
+
return this.#rebuildExecutionState(meta);
|
|
1251
|
+
return {
|
|
1252
|
+
automaticTitlePending: meta.automaticTitlePending,
|
|
1253
|
+
todo,
|
|
1254
|
+
contextRecords
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
catch {
|
|
1258
|
+
return this.#rebuildExecutionState(meta);
|
|
1259
|
+
}
|
|
1260
|
+
}));
|
|
1261
|
+
}
|
|
1262
|
+
/** Compare the planned sequence inside the existing record writer queue. */
|
|
1263
|
+
async commitContextGc(sessionId, draft, planningRecords) {
|
|
1264
|
+
this.#assertOpen();
|
|
1265
|
+
const records = planningRecords ?? (await this.readExecutionState(sessionId)).contextRecords;
|
|
1266
|
+
return this.#serializeRecordOperation(sessionId, () => this.#withWriteAdmission(sessionId, async () => {
|
|
1267
|
+
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
1268
|
+
if (!meta)
|
|
1269
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
1270
|
+
const boundarySequence = meta.nextSequence - 1;
|
|
1271
|
+
if (boundarySequence !== draft.boundarySequence)
|
|
1272
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Context GC projection changed before checkpoint commit.');
|
|
1273
|
+
if (records.at(-1)?.sequence !== boundarySequence ||
|
|
1274
|
+
records.at(-1)?.sessionId !== sessionId ||
|
|
1275
|
+
!sameContextGcBase(contextGcBaseFromRecords(records, boundarySequence), draft.base))
|
|
1276
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Context GC planning boundary is invalid.');
|
|
1277
|
+
const previous = latestValidContextGcCheckpoint(records, boundarySequence);
|
|
1278
|
+
if ((!draft.inlineMedia || draft.inlineMedia.reason === 'TEXT_GC') &&
|
|
1279
|
+
!contextGcCheckpointIntervalEligible(draft.trigger, previous?.payload.trigger))
|
|
1280
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Context GC checkpoint interval validation failed.');
|
|
1281
|
+
const plannedReplacements = normalizeContextGcReplacements(draft.replacements);
|
|
1282
|
+
if (!plannedReplacements || (plannedReplacements.length === 0 && !draft.inlineMedia))
|
|
1283
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Context GC checkpoint must contain a replacement.');
|
|
1284
|
+
const replacements = normalizeContextGcReplacements([
|
|
1285
|
+
...(previous?.payload.replacements ?? []),
|
|
1286
|
+
...plannedReplacements
|
|
1287
|
+
]);
|
|
1288
|
+
if (!replacements ||
|
|
1289
|
+
(previous &&
|
|
1290
|
+
isDeepStrictEqual(replacements, previous.payload.replacements) &&
|
|
1291
|
+
(!draft.inlineMedia ||
|
|
1292
|
+
isDeepStrictEqual(draft.inlineMedia, previous.payload.inlineMedia))))
|
|
1293
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Context GC replacement map is invalid.');
|
|
1294
|
+
const payload = {
|
|
1295
|
+
...structuredClone(draft),
|
|
1296
|
+
checkpointId: randomUUID(),
|
|
1297
|
+
generation: (previous?.payload.generation ?? 0) + 1,
|
|
1298
|
+
replacements
|
|
1299
|
+
};
|
|
1300
|
+
const candidate = {
|
|
1301
|
+
recordId: randomUUID(),
|
|
1302
|
+
sequence: boundarySequence + 1,
|
|
1303
|
+
timestamp: new Date().toISOString(),
|
|
1304
|
+
sessionId,
|
|
1305
|
+
type: 'context.gc.completed',
|
|
1306
|
+
payload
|
|
1307
|
+
};
|
|
1308
|
+
if (Buffer.byteLength(JSON.stringify(payload)) >= MAX_SEGMENT_BYTES ||
|
|
1309
|
+
validateContextGcCheckpoint(records, candidate, meta.workspace, previous?.payload.replacements) === undefined)
|
|
1310
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Context GC checkpoint validation failed.');
|
|
1311
|
+
const turnId = records.findLast((record) => record.type === 'execution.header')?.turnId;
|
|
1312
|
+
return this.#appendRecord(sessionId, {
|
|
1313
|
+
type: 'context.gc.completed',
|
|
1314
|
+
...(turnId === undefined ? {} : { turnId }),
|
|
1315
|
+
payload
|
|
1316
|
+
});
|
|
1317
|
+
}));
|
|
1318
|
+
}
|
|
1319
|
+
async readPage(sessionId, input = {}) {
|
|
1320
|
+
return withSessionReadPin(this.#sessionRoot(sessionId), async () => {
|
|
1321
|
+
await this.#refreshIndex();
|
|
1322
|
+
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
1323
|
+
if (!meta)
|
|
1324
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
1325
|
+
await this.#assertReadable(sessionId, meta);
|
|
1326
|
+
return this.#shared.historyPages.read(meta, input, () => this.#segmentPaths(sessionId, meta));
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
async compact(sessionId, input, options = {}) {
|
|
1330
|
+
if (!input.summary.trim())
|
|
1331
|
+
throw new AgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Compact summary is required.');
|
|
1332
|
+
const recoveryFailure = this.#shared.migrationFailures.get(sessionId);
|
|
1333
|
+
if (recoveryFailure && this.#shared.writerLeases.has(sessionId))
|
|
1334
|
+
throw recoveryFailure.error;
|
|
1335
|
+
await this.#recoverPendingTreeDelete(sessionId);
|
|
1336
|
+
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
1337
|
+
if (!meta)
|
|
1338
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
1339
|
+
return this.append(sessionId, {
|
|
1340
|
+
type: 'compact.completed',
|
|
1341
|
+
payload: {
|
|
1342
|
+
summary: input.summary,
|
|
1343
|
+
...(input.pendingInputs?.length ? { pendingInputs: input.pendingInputs } : {}),
|
|
1344
|
+
...(input.snapshotRecordId === undefined
|
|
1345
|
+
? {}
|
|
1346
|
+
: { snapshotRecordId: input.snapshotRecordId }),
|
|
1347
|
+
...(input.snapshotMessageCount === undefined
|
|
1348
|
+
? {}
|
|
1349
|
+
: { snapshotMessageCount: input.snapshotMessageCount }),
|
|
1350
|
+
...(input.replacementHistory === undefined
|
|
1351
|
+
? {}
|
|
1352
|
+
: { replacementHistory: input.replacementHistory }),
|
|
1353
|
+
...(input.retainedModelContext?.length
|
|
1354
|
+
? { retainedModelContext: input.retainedModelContext }
|
|
1355
|
+
: {}),
|
|
1356
|
+
...(input.inlineMediaState ? { inlineMediaState: input.inlineMediaState } : {}),
|
|
1357
|
+
...(input.environment === undefined ? {} : { environment: input.environment }),
|
|
1358
|
+
boundarySequence: meta.nextSequence - 1,
|
|
1359
|
+
automatic: input.automatic === true,
|
|
1360
|
+
...(input.modelId === undefined ? {} : { modelId: input.modelId }),
|
|
1361
|
+
...(input.contextWindowTokens === undefined
|
|
1362
|
+
? {}
|
|
1363
|
+
: { contextWindowTokens: input.contextWindowTokens }),
|
|
1364
|
+
...(input.usage === undefined ? {} : { usage: input.usage })
|
|
1365
|
+
},
|
|
1366
|
+
...(input.executionId ? { executionId: input.executionId } : {}),
|
|
1367
|
+
...(input.turnId ? { turnId: input.turnId } : {})
|
|
1368
|
+
}, options);
|
|
1369
|
+
}
|
|
1370
|
+
/** A warm cell retains physical writer ownership between Executions of the same Agent. */
|
|
1371
|
+
async acquireSessionLease(sessionId, owner) {
|
|
1372
|
+
return this.#serializeRecordOperation(sessionId, async () => {
|
|
1373
|
+
this.#assertOpen();
|
|
1374
|
+
let lease = this.#shared.writerLeases.get(sessionId);
|
|
1375
|
+
if (lease) {
|
|
1376
|
+
const adoptPending = !!lease.pendingResource && !lease.executing && lease.owner === undefined;
|
|
1377
|
+
if (lease.maintenance ||
|
|
1378
|
+
lease.executing ||
|
|
1379
|
+
(!adoptPending && (owner === undefined || lease.owner !== owner)))
|
|
1380
|
+
throw new AgentError('MAR_AGENT_SESSION_LOCKED', 'Session already has a writer.');
|
|
1381
|
+
if (lease.appendFailure)
|
|
1382
|
+
throw lease.appendFailure.error;
|
|
1383
|
+
if (adoptPending)
|
|
1384
|
+
lease.owner = owner;
|
|
1385
|
+
lease.executing = true;
|
|
1386
|
+
}
|
|
1387
|
+
else {
|
|
1388
|
+
const release = await this.#acquireSessionLease(sessionId);
|
|
1389
|
+
lease = { owner, executing: true, retained: 0, release };
|
|
1390
|
+
this.#shared.writerLeases.set(sessionId, lease);
|
|
1391
|
+
}
|
|
1392
|
+
const current = lease;
|
|
1393
|
+
let released = false;
|
|
1394
|
+
return () => this.#serializeRecordOperation(sessionId, async () => {
|
|
1395
|
+
if (released)
|
|
1396
|
+
return;
|
|
1397
|
+
released = true;
|
|
1398
|
+
current.executing = false;
|
|
1399
|
+
if (current.pendingResource) {
|
|
1400
|
+
delete current.pendingResource;
|
|
1401
|
+
current.retained--;
|
|
1402
|
+
}
|
|
1403
|
+
await this.#releaseUnusedWriter(sessionId, current);
|
|
1404
|
+
});
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
hasRetainedSessionLease(sessionId, owner) {
|
|
1408
|
+
const lease = this.#shared.writerLeases.get(sessionId);
|
|
1409
|
+
return lease?.owner === owner && lease.retained > 0;
|
|
1410
|
+
}
|
|
1411
|
+
async acquireSessionMaintenance(sessionId, kind, owner) {
|
|
1412
|
+
this.#assertOpen();
|
|
1413
|
+
await this.#recoverPendingTreeDelete(sessionId);
|
|
1414
|
+
return acquireTreeMaintenance({
|
|
1415
|
+
sessionId,
|
|
1416
|
+
kind,
|
|
1417
|
+
tree: () => this.sessionTree(sessionId),
|
|
1418
|
+
acquire: (id, holding, registerGate) => this.#serializeRecordOperation(id, async () => {
|
|
1419
|
+
this.#assertOpen();
|
|
1420
|
+
registerGate(await this.#acquirePublicationGate(id));
|
|
1421
|
+
let writer = this.#shared.writerLeases.get(id);
|
|
1422
|
+
if (writer &&
|
|
1423
|
+
(writer.maintenance ||
|
|
1424
|
+
owner === undefined ||
|
|
1425
|
+
writer.owner !== owner ||
|
|
1426
|
+
(kind === 'clear' && writer.executing)))
|
|
1427
|
+
throw new AgentError('MAR_AGENT_SESSION_LOCKED', 'Session already has a writer.');
|
|
1428
|
+
if (!writer) {
|
|
1429
|
+
const release = kind === 'delete' && !(await sessionDirectoryExists(this.#sessionRoot(id)))
|
|
1430
|
+
? async () => { }
|
|
1431
|
+
: await this.#acquireSessionLease(id, kind);
|
|
1432
|
+
writer = { owner, executing: false, retained: 0, release };
|
|
1433
|
+
this.#shared.writerLeases.set(id, writer);
|
|
1434
|
+
}
|
|
1435
|
+
writer.maintenance = holding;
|
|
1436
|
+
const current = writer;
|
|
1437
|
+
return () => this.#serializeRecordOperation(id, async () => {
|
|
1438
|
+
delete current.maintenance;
|
|
1439
|
+
await this.#releaseUnusedWriter(id, current);
|
|
1440
|
+
});
|
|
1441
|
+
}),
|
|
1442
|
+
admitted: (holding) => this.#maintenance.set(holding, { sessionId, kind }),
|
|
1443
|
+
released: (holding) => this.#maintenance.delete(holding)
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
#assertMaintenance(sessionId, kind, holding) {
|
|
1447
|
+
const current = this.#maintenance.get(holding);
|
|
1448
|
+
if (current?.sessionId !== sessionId || current.kind !== kind)
|
|
1449
|
+
throw new AgentError('MAR_AGENT_SESSION_LOCKED', 'Session maintenance ownership is invalid.');
|
|
1450
|
+
}
|
|
1451
|
+
/** Native writes join their Agent's sole writer, without admitting another Execution. */
|
|
1452
|
+
async acquireSessionWriteLease(sessionId, owner) {
|
|
1453
|
+
return this.#serializeRecordOperation(sessionId, async () => {
|
|
1454
|
+
this.#assertOpen();
|
|
1455
|
+
let lease = this.#shared.writerLeases.get(sessionId);
|
|
1456
|
+
const recoveryFailure = this.#shared.migrationFailures.get(sessionId);
|
|
1457
|
+
if (lease && recoveryFailure)
|
|
1458
|
+
throw recoveryFailure.error;
|
|
1459
|
+
if (lease && (lease.maintenance || lease.owner !== owner))
|
|
1460
|
+
throw new AgentError('MAR_AGENT_SESSION_LOCKED', 'Session already has a writer.');
|
|
1461
|
+
if (lease?.appendFailure)
|
|
1462
|
+
throw lease.appendFailure.error;
|
|
1463
|
+
if (!lease) {
|
|
1464
|
+
const release = await this.#acquireSessionLease(sessionId);
|
|
1465
|
+
lease = { owner, executing: false, retained: 0, release };
|
|
1466
|
+
this.#shared.writerLeases.set(sessionId, lease);
|
|
1467
|
+
}
|
|
1468
|
+
lease.retained++;
|
|
1469
|
+
const current = lease;
|
|
1470
|
+
let released = false;
|
|
1471
|
+
return () => this.#serializeRecordOperation(sessionId, async () => {
|
|
1472
|
+
if (released)
|
|
1473
|
+
return;
|
|
1474
|
+
released = true;
|
|
1475
|
+
current.retained--;
|
|
1476
|
+
await this.#releaseUnusedWriter(sessionId, current);
|
|
1477
|
+
});
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
async retainSessionLease(sessionId, owner) {
|
|
1481
|
+
return this.#serializeRecordOperation(sessionId, async () => {
|
|
1482
|
+
const lease = this.#shared.writerLeases.get(sessionId);
|
|
1483
|
+
if (!lease || lease.maintenance || !lease.executing || lease.owner !== owner)
|
|
1484
|
+
throw new AgentError('MAR_AGENT_SESSION_LOCKED', 'Retained work requires its originating Session writer.');
|
|
1485
|
+
lease.retained++;
|
|
1486
|
+
let released = false;
|
|
1487
|
+
return () => this.#serializeRecordOperation(sessionId, async () => {
|
|
1488
|
+
if (released)
|
|
1489
|
+
return;
|
|
1490
|
+
released = true;
|
|
1491
|
+
lease.retained--;
|
|
1492
|
+
await this.#releaseUnusedWriter(sessionId, lease);
|
|
1493
|
+
});
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
async #releaseUnusedWriter(sessionId, lease) {
|
|
1497
|
+
if (lease.maintenance || lease.executing || lease.retained)
|
|
1498
|
+
return;
|
|
1499
|
+
try {
|
|
1500
|
+
await lease.release();
|
|
1501
|
+
}
|
|
1502
|
+
finally {
|
|
1503
|
+
delete lease.codeState;
|
|
1504
|
+
delete lease.extensionCheckpoints;
|
|
1505
|
+
this.#shared.writerLeases.delete(sessionId);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
/** Recover files under the sole writer lock; flush dirty index state before releasing it. */
|
|
1509
|
+
async #acquireSessionLease(sessionId, mode = 'write') {
|
|
1510
|
+
this.#assertOpen();
|
|
1511
|
+
await this.#recoverPendingTreeDelete(sessionId);
|
|
1512
|
+
const priorFailure = this.#shared.migrationFailures.get(sessionId);
|
|
1513
|
+
if (priorFailure && (mode === 'write' || (mode === 'clear' && priorFailure.unreadable)))
|
|
1514
|
+
throw priorFailure.error;
|
|
1515
|
+
await this.#refreshIndex();
|
|
1516
|
+
if (!this.#shared.index.sessions.some((item) => item.id === sessionId))
|
|
1517
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
1518
|
+
const path = join(this.#shared.root, 'sessions', sessionId, 'lock');
|
|
1519
|
+
const release = await acquireFileLock(path, 'Session already has a writer.', 0);
|
|
1520
|
+
try {
|
|
1521
|
+
const disk = await readSessionIndex(join(this.#shared.root, 'sessions', 'index.json'));
|
|
1522
|
+
if (!disk)
|
|
1523
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session index is missing.');
|
|
1524
|
+
const current = disk.sessions.find((item) => item.id === sessionId);
|
|
1525
|
+
if (!current)
|
|
1526
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
1527
|
+
if (mode !== 'delete') {
|
|
1528
|
+
const sessionRoot = join(this.#shared.root, 'sessions', sessionId);
|
|
1529
|
+
if ((current.storage?.encoding ?? 1) === 1) {
|
|
1530
|
+
await preflightV1Session(sessionRoot);
|
|
1531
|
+
await preflightRetiredRecovery(sessionRoot, sessionRoot);
|
|
1532
|
+
}
|
|
1533
|
+
else {
|
|
1534
|
+
if (await sessionDirectoryExists(join(sessionRoot, 'storage-transaction.json'))) {
|
|
1535
|
+
await preflightV1Session(sessionRoot);
|
|
1536
|
+
await preflightRetiredRecovery(sessionRoot, sessionRoot);
|
|
1537
|
+
}
|
|
1538
|
+
const activeDirectory = this.#directoryForMeta(sessionId, current);
|
|
1539
|
+
await preflightV2Recovery(activeDirectory);
|
|
1540
|
+
await preflightRetiredRecovery(sessionRoot, activeDirectory);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
await this.#ensureRootV2();
|
|
1544
|
+
await rm(join(this.#sessionRoot(sessionId), 'publication-pending'), { force: true });
|
|
1545
|
+
if (mode !== 'delete') {
|
|
1546
|
+
await withSessionReadDrain(this.#sessionRoot(sessionId), async () => {
|
|
1547
|
+
await this.#refreshIndex();
|
|
1548
|
+
const sessionRoot = this.#sessionRoot(sessionId);
|
|
1549
|
+
const activeDirectory = this.#sessionDirectory(sessionId);
|
|
1550
|
+
if (activeDirectory !== sessionRoot &&
|
|
1551
|
+
(await sessionDirectoryExists(join(sessionRoot, 'storage-transaction.json'))))
|
|
1552
|
+
await recoverSessionStorageTransaction(sessionRoot, current.storageTransactionId, sessionRoot);
|
|
1553
|
+
await recoverSessionStorageTransaction(activeDirectory, current.storageTransactionId, sessionRoot);
|
|
1554
|
+
await recoverContextualWorldStateTransactions(this.#shared.root, current);
|
|
1555
|
+
await this.#refreshIndex();
|
|
1556
|
+
await this.#reconcileTail(sessionId);
|
|
1557
|
+
});
|
|
1558
|
+
if (mode === 'clear' && this.#shared.indexDirty)
|
|
1559
|
+
await this.#saveIndex();
|
|
1560
|
+
if (mode === 'write' && this.#encoding(sessionId) === 1) {
|
|
1561
|
+
if (this.#shared.indexDirty)
|
|
1562
|
+
await this.#saveIndex();
|
|
1563
|
+
await this.#migrateV1Session(sessionId);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
catch (error) {
|
|
1568
|
+
await release().catch(() => undefined);
|
|
1569
|
+
throw error;
|
|
1570
|
+
}
|
|
1571
|
+
return async () => {
|
|
1572
|
+
let failure;
|
|
1573
|
+
try {
|
|
1574
|
+
if (this.#shared.indexDirty)
|
|
1575
|
+
await this.#saveIndex();
|
|
1576
|
+
}
|
|
1577
|
+
catch (error) {
|
|
1578
|
+
failure = error;
|
|
1579
|
+
throw error;
|
|
1580
|
+
}
|
|
1581
|
+
finally {
|
|
1582
|
+
if (failure !== undefined)
|
|
1583
|
+
await release().catch(() => undefined);
|
|
1584
|
+
else
|
|
1585
|
+
await release();
|
|
1586
|
+
}
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
/** The Session lock is held; conversion is outside the short index selection transaction. */
|
|
1590
|
+
async #migrateV1Session(sessionId) {
|
|
1591
|
+
const source = join(this.#shared.root, 'sessions', sessionId);
|
|
1592
|
+
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
1593
|
+
if (!meta)
|
|
1594
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
1595
|
+
const generation = randomUUID();
|
|
1596
|
+
const parent = join(source, 'v2');
|
|
1597
|
+
const destination = join(parent, generation);
|
|
1598
|
+
let commitAttempted = false;
|
|
1599
|
+
let visible = 'v1';
|
|
1600
|
+
try {
|
|
1601
|
+
await mkdir(parent, { recursive: true, mode: 0o700 });
|
|
1602
|
+
const candidate = await prepareJsonlV2Generation({
|
|
1603
|
+
source,
|
|
1604
|
+
destination,
|
|
1605
|
+
sessionId,
|
|
1606
|
+
nextSequence: meta.nextSequence
|
|
1607
|
+
});
|
|
1608
|
+
await syncJsonlDirectory(parent);
|
|
1609
|
+
await syncJsonlDirectory(source);
|
|
1610
|
+
await this.#serializeIndexOperation(async () => {
|
|
1611
|
+
const path = join(this.#shared.root, 'sessions', 'index.json');
|
|
1612
|
+
const release = await acquireFileLock(join(this.#shared.root, 'sessions', 'index.lock'));
|
|
1613
|
+
let failure;
|
|
1614
|
+
try {
|
|
1615
|
+
const disk = await readSessionIndex(path);
|
|
1616
|
+
const current = disk?.sessions.find((item) => item.id === sessionId);
|
|
1617
|
+
if (!disk ||
|
|
1618
|
+
disk.version !== 2 ||
|
|
1619
|
+
!current ||
|
|
1620
|
+
current.storage?.encoding !== 1 ||
|
|
1621
|
+
current.nextSequence !== meta.nextSequence ||
|
|
1622
|
+
current.storageTransactionId !== meta.storageTransactionId)
|
|
1623
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session migration index changed.');
|
|
1624
|
+
const replacement = {
|
|
1625
|
+
...current,
|
|
1626
|
+
storage: { encoding: 2, generation },
|
|
1627
|
+
segments: candidate.segments
|
|
1628
|
+
};
|
|
1629
|
+
const updated = {
|
|
1630
|
+
...disk,
|
|
1631
|
+
sessions: disk.sessions.map((entry) => (entry.id === sessionId ? replacement : entry))
|
|
1632
|
+
};
|
|
1633
|
+
commitAttempted = true;
|
|
1634
|
+
try {
|
|
1635
|
+
await persistFormatSelection(path, updated);
|
|
1636
|
+
visible = 'v2';
|
|
1637
|
+
}
|
|
1638
|
+
catch (error) {
|
|
1639
|
+
const check = await readSessionIndex(path).catch(() => undefined);
|
|
1640
|
+
const choice = check?.sessions.find((entry) => entry.id === sessionId)?.storage;
|
|
1641
|
+
visible =
|
|
1642
|
+
choice?.encoding === 1
|
|
1643
|
+
? 'v1'
|
|
1644
|
+
: choice?.encoding === 2 && choice.generation === generation
|
|
1645
|
+
? 'v2'
|
|
1646
|
+
: 'unknown';
|
|
1647
|
+
throw error;
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
catch (error) {
|
|
1651
|
+
failure = error;
|
|
1652
|
+
throw error;
|
|
1653
|
+
}
|
|
1654
|
+
finally {
|
|
1655
|
+
if (failure !== undefined)
|
|
1656
|
+
await release().catch(() => undefined);
|
|
1657
|
+
else
|
|
1658
|
+
await release();
|
|
1659
|
+
}
|
|
1660
|
+
await this.#readCurrentIndex();
|
|
1661
|
+
});
|
|
1662
|
+
this.#shared.segments.delete(sessionId);
|
|
1663
|
+
}
|
|
1664
|
+
catch (error) {
|
|
1665
|
+
if (visible === 'v1') {
|
|
1666
|
+
// This candidate cannot be selected; preserve the original failure if cleanup fails.
|
|
1667
|
+
await rm(destination, { recursive: true, force: true })
|
|
1668
|
+
.then(() => syncJsonlDirectory(parent))
|
|
1669
|
+
.catch(() => undefined);
|
|
1670
|
+
}
|
|
1671
|
+
this.#shared.migrationFailures.set(sessionId, {
|
|
1672
|
+
error,
|
|
1673
|
+
unreadable: commitAttempted && visible !== 'v1'
|
|
1674
|
+
});
|
|
1675
|
+
throw error;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
async list(input = {}) {
|
|
1679
|
+
this.#assertOpen();
|
|
1680
|
+
await this.#refreshIndex();
|
|
1681
|
+
const filter = sessionListFilter(input);
|
|
1682
|
+
const start = decodeSessionListCursor(input.cursor, this.#shared.index.revision, filter);
|
|
1683
|
+
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
1684
|
+
const ordered = this.#shared.index.sessions
|
|
1685
|
+
.filter((session) => sessionMatchesFilter(session, filter))
|
|
1686
|
+
.sort((a, b) => b.lastActivityAt.localeCompare(a.lastActivityAt));
|
|
1687
|
+
const sessions = ordered.slice(start, start + limit).map((session) => ({
|
|
1688
|
+
id: session.id,
|
|
1689
|
+
title: session.title,
|
|
1690
|
+
...(session.resume?.firstUserMessagePreview === undefined
|
|
1691
|
+
? {}
|
|
1692
|
+
: { firstUserMessagePreview: session.resume.firstUserMessagePreview }),
|
|
1693
|
+
workspace: session.workspace,
|
|
1694
|
+
createdAt: session.createdAt,
|
|
1695
|
+
lastActivityAt: session.lastActivityAt,
|
|
1696
|
+
source: normalizeSessionSource(session.source),
|
|
1697
|
+
...(session.resume?.modelId === undefined
|
|
1698
|
+
? {}
|
|
1699
|
+
: {
|
|
1700
|
+
modelId: session.resume.modelId,
|
|
1701
|
+
...(session.resume.modelName === undefined
|
|
1702
|
+
? {}
|
|
1703
|
+
: { modelName: session.resume.modelName }),
|
|
1704
|
+
...(session.resume.reasoningEffort === undefined
|
|
1705
|
+
? {}
|
|
1706
|
+
: { reasoningEffort: session.resume.reasoningEffort })
|
|
1707
|
+
})
|
|
1708
|
+
}));
|
|
1709
|
+
const next = start + limit < ordered.length
|
|
1710
|
+
? encodeSessionListCursor(this.#shared.index.revision, start + limit, filter)
|
|
1711
|
+
: undefined;
|
|
1712
|
+
return { sessions, ...(next ? { nextCursor: next } : {}) };
|
|
1713
|
+
}
|
|
1714
|
+
async listSubagentChildren(parentSessionId) {
|
|
1715
|
+
this.#assertOpen();
|
|
1716
|
+
if (!/^[0-9a-f-]{36}$/iu.test(parentSessionId))
|
|
1717
|
+
return [];
|
|
1718
|
+
return withSessionReadPin(this.#sessionRoot(parentSessionId), async () => {
|
|
1719
|
+
await this.#refreshIndex();
|
|
1720
|
+
return this.#shared.index.sessions
|
|
1721
|
+
.map((session) => ({ session, source: normalizeSessionSource(session.source) }))
|
|
1722
|
+
.filter((item) => item.source.type === 'subagent' && item.source.parentSessionId === parentSessionId)
|
|
1723
|
+
.map(({ session, source }) => ({
|
|
1724
|
+
id: session.id,
|
|
1725
|
+
title: session.title,
|
|
1726
|
+
workspace: session.workspace,
|
|
1727
|
+
createdAt: session.createdAt,
|
|
1728
|
+
lastActivityAt: session.lastActivityAt,
|
|
1729
|
+
source,
|
|
1730
|
+
...(session.resume?.modelId === undefined
|
|
1731
|
+
? {}
|
|
1732
|
+
: {
|
|
1733
|
+
modelId: session.resume.modelId,
|
|
1734
|
+
...(session.resume.modelName === undefined
|
|
1735
|
+
? {}
|
|
1736
|
+
: { modelName: session.resume.modelName }),
|
|
1737
|
+
...(session.resume.reasoningEffort === undefined
|
|
1738
|
+
? {}
|
|
1739
|
+
: { reasoningEffort: session.resume.reasoningEffort })
|
|
1740
|
+
})
|
|
1741
|
+
}));
|
|
1742
|
+
}, true);
|
|
1743
|
+
}
|
|
1744
|
+
async executionConfiguration(sessionId) {
|
|
1745
|
+
this.#assertOpen();
|
|
1746
|
+
return withSessionReadPin(this.#sessionRoot(sessionId), async () => {
|
|
1747
|
+
await this.#refreshIndex();
|
|
1748
|
+
let meta = this.#shared.index.sessions.find((session) => session.id === sessionId);
|
|
1749
|
+
if (meta === undefined)
|
|
1750
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
1751
|
+
if (meta.resume?.executionModelIndexed !== true) {
|
|
1752
|
+
await this.#ensureExecutionModelIndexed(sessionId);
|
|
1753
|
+
meta = this.#shared.index.sessions.find((session) => session.id === sessionId);
|
|
1754
|
+
if (meta === undefined)
|
|
1755
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
1756
|
+
}
|
|
1757
|
+
return meta.resume?.modelId === undefined
|
|
1758
|
+
? undefined
|
|
1759
|
+
: {
|
|
1760
|
+
modelId: meta.resume.modelId,
|
|
1761
|
+
...(meta.resume.modelName === undefined ? {} : { modelName: meta.resume.modelName }),
|
|
1762
|
+
...(meta.resume.reasoningEffort === undefined
|
|
1763
|
+
? {}
|
|
1764
|
+
: { reasoningEffort: meta.resume.reasoningEffort })
|
|
1765
|
+
};
|
|
1766
|
+
}, true);
|
|
1767
|
+
}
|
|
1768
|
+
async sessionTree(sessionId) {
|
|
1769
|
+
this.#assertOpen();
|
|
1770
|
+
return withSessionReadPin(this.#sessionRoot(sessionId), async () => {
|
|
1771
|
+
await this.#refreshIndex();
|
|
1772
|
+
if (!this.#shared.index.sessions.some((session) => session.id === sessionId))
|
|
1773
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
1774
|
+
return sessionTreeIds(this.#shared.index.sessions, sessionId);
|
|
1775
|
+
}, true);
|
|
1776
|
+
}
|
|
1777
|
+
#applyRecordToResumeIndex(meta, record) {
|
|
1778
|
+
if (record.type === 'turn.user')
|
|
1779
|
+
meta.automaticTitlePending = false;
|
|
1780
|
+
if (record.type === 'session.title.updated' && isObject(record.payload)) {
|
|
1781
|
+
if (typeof record.payload.title === 'string')
|
|
1782
|
+
meta.title = record.payload.title;
|
|
1783
|
+
meta.automaticTitlePending = false;
|
|
1784
|
+
}
|
|
1785
|
+
const resume = meta.resume;
|
|
1786
|
+
if (resume?.version !== 1 || !Number.isSafeInteger(resume.todoRevision))
|
|
1787
|
+
return;
|
|
1788
|
+
if (resume.userTurns !== undefined && isNativeUserTurn(record))
|
|
1789
|
+
resume.userTurns++;
|
|
1790
|
+
if (resume.firstUserMessageIndexed === true && resume.firstUserMessagePreview === undefined) {
|
|
1791
|
+
const preview = userMessagePreviewFromRecord(record);
|
|
1792
|
+
if (preview !== undefined)
|
|
1793
|
+
resume.firstUserMessagePreview = preview;
|
|
1794
|
+
}
|
|
1795
|
+
const executionModel = executionModelFromRecord(record);
|
|
1796
|
+
if (executionModel !== undefined) {
|
|
1797
|
+
resume.executionModelIndexed = true;
|
|
1798
|
+
resume.modelId = executionModel.modelId;
|
|
1799
|
+
if (executionModel.modelName === undefined)
|
|
1800
|
+
delete resume.modelName;
|
|
1801
|
+
else
|
|
1802
|
+
resume.modelName = executionModel.modelName;
|
|
1803
|
+
if (executionModel.reasoningEffort === undefined)
|
|
1804
|
+
delete resume.reasoningEffort;
|
|
1805
|
+
else
|
|
1806
|
+
resume.reasoningEffort = executionModel.reasoningEffort;
|
|
1807
|
+
}
|
|
1808
|
+
if (isCompactCheckpoint(record))
|
|
1809
|
+
resume.compactSequence = record.sequence;
|
|
1810
|
+
if (record.type !== 'todo.updated' || !todoItemsFromSnapshot(record.payload))
|
|
1811
|
+
return;
|
|
1812
|
+
resume.todoSequence = record.sequence;
|
|
1813
|
+
resume.todoRevision++;
|
|
1814
|
+
}
|
|
1815
|
+
async #readIndexedTodo(meta, contextRecords) {
|
|
1816
|
+
const resume = meta.resume;
|
|
1817
|
+
if (resume?.version !== 1 ||
|
|
1818
|
+
!Number.isSafeInteger(resume.todoRevision) ||
|
|
1819
|
+
resume.todoRevision < 0)
|
|
1820
|
+
return undefined;
|
|
1821
|
+
const latestContextTodo = contextRecords.findLast((record) => record.type === 'todo.updated' && todoItemsFromSnapshot(record.payload) !== undefined);
|
|
1822
|
+
if (latestContextTodo &&
|
|
1823
|
+
(resume.todoSequence === undefined || latestContextTodo.sequence !== resume.todoSequence))
|
|
1824
|
+
return undefined;
|
|
1825
|
+
if (resume.todoSequence === undefined)
|
|
1826
|
+
return resume.todoRevision === 0 ? { revision: 0, items: [] } : undefined;
|
|
1827
|
+
if (resume.todoRevision === 0)
|
|
1828
|
+
return undefined;
|
|
1829
|
+
const record = contextRecords.find((item) => item.sequence === resume.todoSequence) ??
|
|
1830
|
+
(await this.#readRecordAtSequence(meta, resume.todoSequence));
|
|
1831
|
+
if (record?.type !== 'todo.updated')
|
|
1832
|
+
return undefined;
|
|
1833
|
+
const items = todoItemsFromSnapshot(record.payload);
|
|
1834
|
+
return items ? { revision: resume.todoRevision, items } : undefined;
|
|
1835
|
+
}
|
|
1836
|
+
async #readRecordAtSequence(meta, sequence) {
|
|
1837
|
+
const segment = meta.segments?.find((item) => item.firstSequence <= sequence && item.lastSequence >= sequence);
|
|
1838
|
+
if (!segment)
|
|
1839
|
+
return undefined;
|
|
1840
|
+
return (await readRecords(this.#segmentPath(meta.id, segment.number, meta), meta.storage?.encoding ?? 1)).find((record) => record.sequence === sequence);
|
|
1841
|
+
}
|
|
1842
|
+
async #readFromSequence(meta, sequence) {
|
|
1843
|
+
const segments = (meta.segments ?? [])
|
|
1844
|
+
.filter((item) => item.lastSequence >= sequence)
|
|
1845
|
+
.sort((left, right) => left.number - right.number);
|
|
1846
|
+
if (segments.length === 0)
|
|
1847
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session resume index is invalid.');
|
|
1848
|
+
const records = [];
|
|
1849
|
+
for (const segment of segments)
|
|
1850
|
+
records.push(...(await readRecords(this.#segmentPath(meta.id, segment.number, meta), meta.storage?.encoding ?? 1)).filter((record) => record.sequence >= sequence));
|
|
1851
|
+
return records;
|
|
1852
|
+
}
|
|
1853
|
+
async #rebuildExecutionState(meta) {
|
|
1854
|
+
const state = deriveExecutionState(await this.#readRecords(meta.id, meta));
|
|
1855
|
+
if (meta.automaticTitlePending !== state.automaticTitlePending ||
|
|
1856
|
+
!sameResumeIndex(meta.resume, state.resume)) {
|
|
1857
|
+
meta.automaticTitlePending = state.automaticTitlePending;
|
|
1858
|
+
meta.resume = state.resume;
|
|
1859
|
+
this.#shared.index.revision++;
|
|
1860
|
+
if ((meta.storage?.encoding ?? 1) === 2)
|
|
1861
|
+
this.#markIndexDirty(meta.id);
|
|
1862
|
+
}
|
|
1863
|
+
return {
|
|
1864
|
+
automaticTitlePending: state.automaticTitlePending,
|
|
1865
|
+
todo: state.todo,
|
|
1866
|
+
contextRecords: state.contextRecords
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
/** Counts native input records, not the post-Compact model context. */
|
|
1870
|
+
async userTurnCount(sessionId) {
|
|
1871
|
+
return this.#serializeRecordOperation(sessionId, () => withSessionReadPin(this.#sessionRoot(sessionId), async () => {
|
|
1872
|
+
this.#assertOpen();
|
|
1873
|
+
await this.#refreshIndex();
|
|
1874
|
+
const before = this.#shared.index.sessions.find((session) => session.id === sessionId);
|
|
1875
|
+
if (!before)
|
|
1876
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
1877
|
+
const indexed = before.resume?.userTurns;
|
|
1878
|
+
if (indexed !== undefined && this.#shared.writerLeases.has(sessionId))
|
|
1879
|
+
return indexed;
|
|
1880
|
+
const nextSequence = before.nextSequence;
|
|
1881
|
+
const transactionId = before.storageTransactionId;
|
|
1882
|
+
const paths = await this.#segmentPaths(sessionId, before);
|
|
1883
|
+
const encoding = before.storage?.encoding ?? 1;
|
|
1884
|
+
let count = indexed ?? 0;
|
|
1885
|
+
let lastSequence = 0;
|
|
1886
|
+
let reachedPrefix = false;
|
|
1887
|
+
if (indexed !== undefined) {
|
|
1888
|
+
// No local writer: a previous writer may have committed bytes but not its index.
|
|
1889
|
+
for (const path of [...paths].reverse()) {
|
|
1890
|
+
for await (const record of readRecordsFromTail(path, undefined, encoding)) {
|
|
1891
|
+
lastSequence = Math.max(lastSequence, record.sequence);
|
|
1892
|
+
if (record.sequence < nextSequence) {
|
|
1893
|
+
reachedPrefix = true;
|
|
1894
|
+
break;
|
|
1895
|
+
}
|
|
1896
|
+
if (isNativeUserTurn(record))
|
|
1897
|
+
count++;
|
|
1898
|
+
}
|
|
1899
|
+
if (reachedPrefix)
|
|
1900
|
+
break;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
if (indexed === undefined || lastSequence < nextSequence - 1) {
|
|
1904
|
+
count = 0;
|
|
1905
|
+
lastSequence = 0;
|
|
1906
|
+
for (const path of paths) {
|
|
1907
|
+
for await (const record of readRecordsFromHead(path, encoding)) {
|
|
1908
|
+
lastSequence = record.sequence;
|
|
1909
|
+
if (isNativeUserTurn(record))
|
|
1910
|
+
count++;
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
await this.#refreshIndex();
|
|
1915
|
+
const current = this.#shared.index.sessions.find((session) => session.id === sessionId);
|
|
1916
|
+
if (!current)
|
|
1917
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
1918
|
+
if (current.storageTransactionId !== transactionId) {
|
|
1919
|
+
// The indexed prefix no longer belongs to this history. Fall back to the
|
|
1920
|
+
// ordinary physical read boundary without caching or a conflict error.
|
|
1921
|
+
count = 0;
|
|
1922
|
+
for (const path of await this.#segmentPaths(sessionId, current))
|
|
1923
|
+
for await (const record of readRecordsFromHead(path, current.storage?.encoding ?? 1))
|
|
1924
|
+
if (isNativeUserTurn(record))
|
|
1925
|
+
count++;
|
|
1926
|
+
return count;
|
|
1927
|
+
}
|
|
1928
|
+
if (current.nextSequence === nextSequence &&
|
|
1929
|
+
lastSequence === nextSequence - 1 &&
|
|
1930
|
+
current.resume?.userTurns === undefined) {
|
|
1931
|
+
(current.resume ??= { version: 1, todoRevision: 0 }).userTurns = count;
|
|
1932
|
+
this.#markIndexDirty(sessionId, true);
|
|
1933
|
+
}
|
|
1934
|
+
return count;
|
|
1935
|
+
}));
|
|
1936
|
+
}
|
|
1937
|
+
async firstUserMessagePreview(sessionId) {
|
|
1938
|
+
return withSessionReadPin(this.#sessionRoot(sessionId), async () => {
|
|
1939
|
+
this.#assertOpen();
|
|
1940
|
+
await this.#refreshIndex();
|
|
1941
|
+
await this.#ensureFirstUserMessageIndexed(sessionId);
|
|
1942
|
+
return this.#shared.index.sessions.find((session) => session.id === sessionId)?.resume
|
|
1943
|
+
?.firstUserMessagePreview;
|
|
1944
|
+
});
|
|
1945
|
+
}
|
|
1946
|
+
async #ensureFirstUserMessageIndexed(sessionId) {
|
|
1947
|
+
for (;;) {
|
|
1948
|
+
const before = this.#shared.index.sessions.find((session) => session.id === sessionId);
|
|
1949
|
+
if (before === undefined)
|
|
1950
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
1951
|
+
if (before.resume?.firstUserMessageIndexed === true)
|
|
1952
|
+
return;
|
|
1953
|
+
const nextSequence = before.nextSequence;
|
|
1954
|
+
const transactionId = before.storageTransactionId;
|
|
1955
|
+
let preview;
|
|
1956
|
+
try {
|
|
1957
|
+
for (const path of await this.#segmentPaths(sessionId, before)) {
|
|
1958
|
+
for await (const record of readRecordsFromHead(path, before.storage?.encoding ?? 1)) {
|
|
1959
|
+
preview = userMessagePreviewFromRecord(record);
|
|
1960
|
+
if (preview !== undefined)
|
|
1961
|
+
break;
|
|
1962
|
+
}
|
|
1963
|
+
if (preview !== undefined)
|
|
1964
|
+
break;
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
catch (error) {
|
|
1968
|
+
if (error.code !== 'ENOENT' &&
|
|
1969
|
+
!(error instanceof AgentError && error.code === 'MAR_AGENT_SESSION_NOT_FOUND'))
|
|
1970
|
+
throw error;
|
|
1971
|
+
}
|
|
1972
|
+
await this.#refreshIndex();
|
|
1973
|
+
const current = this.#shared.index.sessions.find((session) => session.id === sessionId);
|
|
1974
|
+
if (current === undefined)
|
|
1975
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
1976
|
+
if (current.resume?.firstUserMessageIndexed === true)
|
|
1977
|
+
return;
|
|
1978
|
+
if (current.nextSequence !== nextSequence || current.storageTransactionId !== transactionId)
|
|
1979
|
+
continue;
|
|
1980
|
+
const resume = (current.resume ??= { version: 1, todoRevision: 0 });
|
|
1981
|
+
resume.firstUserMessageIndexed = true;
|
|
1982
|
+
if (preview !== undefined)
|
|
1983
|
+
resume.firstUserMessagePreview = preview;
|
|
1984
|
+
// Derived preview caches do not change list membership, ordering or filters.
|
|
1985
|
+
this.#markIndexDirty(sessionId, true);
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
async #ensureExecutionModelIndexed(sessionId) {
|
|
1990
|
+
const pending = this.#shared.executionModelReads.get(sessionId);
|
|
1991
|
+
if (pending !== undefined)
|
|
1992
|
+
return pending;
|
|
1993
|
+
const read = this.#backfillExecutionModel(sessionId).finally(() => {
|
|
1994
|
+
if (this.#shared.executionModelReads.get(sessionId) === read)
|
|
1995
|
+
this.#shared.executionModelReads.delete(sessionId);
|
|
1996
|
+
});
|
|
1997
|
+
this.#shared.executionModelReads.set(sessionId, read);
|
|
1998
|
+
return read;
|
|
1999
|
+
}
|
|
2000
|
+
async #backfillExecutionModel(sessionId) {
|
|
2001
|
+
for (;;) {
|
|
2002
|
+
const before = this.#shared.index.sessions.find((session) => session.id === sessionId);
|
|
2003
|
+
if (before === undefined)
|
|
2004
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
2005
|
+
if (before.resume?.executionModelIndexed === true)
|
|
2006
|
+
return;
|
|
2007
|
+
const nextSequence = before.nextSequence;
|
|
2008
|
+
const transactionId = before.storageTransactionId;
|
|
2009
|
+
const model = await this.#readLatestExecutionModel(sessionId);
|
|
2010
|
+
// Refresh can replace metadata objects while unrelated sessions are being read.
|
|
2011
|
+
// Merge only into the current entry, and retry if the authoritative history changed.
|
|
2012
|
+
await this.#refreshIndex();
|
|
2013
|
+
const current = this.#shared.index.sessions.find((session) => session.id === sessionId);
|
|
2014
|
+
if (current === undefined)
|
|
2015
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
2016
|
+
if (current.resume?.executionModelIndexed === true)
|
|
2017
|
+
return;
|
|
2018
|
+
if (current.nextSequence !== nextSequence || current.storageTransactionId !== transactionId)
|
|
2019
|
+
continue;
|
|
2020
|
+
// A missing resume index stays incomplete (no compact boundary); execution
|
|
2021
|
+
// recovery will rebuild it separately instead of doing so during discovery.
|
|
2022
|
+
const resume = (current.resume ??= { version: 1, todoRevision: 0 });
|
|
2023
|
+
resume.executionModelIndexed = true;
|
|
2024
|
+
delete resume.modelId;
|
|
2025
|
+
delete resume.modelName;
|
|
2026
|
+
delete resume.reasoningEffort;
|
|
2027
|
+
if (model !== undefined)
|
|
2028
|
+
Object.assign(resume, model);
|
|
2029
|
+
this.#shared.index.revision++;
|
|
2030
|
+
if ((current.storage?.encoding ?? 1) === 2)
|
|
2031
|
+
this.#markIndexDirty(sessionId);
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
async #readLatestExecutionModel(sessionId) {
|
|
2036
|
+
return withSessionReadPin(this.#sessionRoot(sessionId), async () => {
|
|
2037
|
+
try {
|
|
2038
|
+
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
2039
|
+
for (const path of (await this.#segmentPaths(sessionId, meta)).reverse())
|
|
2040
|
+
for await (const record of readRecordsFromTail(path, undefined, meta?.storage?.encoding ?? 1)) {
|
|
2041
|
+
const model = executionModelFromRecord(record);
|
|
2042
|
+
if (model !== undefined)
|
|
2043
|
+
return model;
|
|
2044
|
+
}
|
|
2045
|
+
return undefined;
|
|
2046
|
+
}
|
|
2047
|
+
catch (error) {
|
|
2048
|
+
if (error.code === 'ENOENT' ||
|
|
2049
|
+
(error instanceof AgentError && error.code === 'MAR_AGENT_SESSION_NOT_FOUND'))
|
|
2050
|
+
return undefined;
|
|
2051
|
+
throw error;
|
|
2052
|
+
}
|
|
2053
|
+
});
|
|
2054
|
+
}
|
|
2055
|
+
async #withTreeReadDrains(ids, action) {
|
|
2056
|
+
const ordered = [...ids].sort();
|
|
2057
|
+
const take = (index) => index === ordered.length
|
|
2058
|
+
? action()
|
|
2059
|
+
: withSessionReadDrain(this.#sessionRoot(ordered[index]), () => take(index + 1));
|
|
2060
|
+
return take(0);
|
|
2061
|
+
}
|
|
2062
|
+
async #treeDeleteCommitted(transaction) {
|
|
2063
|
+
const root = join(this.#shared.root, 'sessions');
|
|
2064
|
+
return this.#serializeIndexOperation(async () => {
|
|
2065
|
+
const release = await acquireFileLock(join(root, 'index.lock'));
|
|
2066
|
+
let failure;
|
|
2067
|
+
try {
|
|
2068
|
+
// Lock against another rename between durability confirmation and reading the decision.
|
|
2069
|
+
await syncJsonlDirectory(root);
|
|
2070
|
+
const disk = await readSessionIndex(join(root, 'index.json'));
|
|
2071
|
+
if (!disk)
|
|
2072
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session index is missing.');
|
|
2073
|
+
const count = transaction.members.filter((member) => disk.sessions.some((entry) => entry.id === member.id)).length;
|
|
2074
|
+
if (count !== 0 && count !== transaction.members.length)
|
|
2075
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session deletion outcome is indeterminate.');
|
|
2076
|
+
if (count !== 0)
|
|
2077
|
+
for (const member of transaction.members) {
|
|
2078
|
+
const current = disk.sessions.find((entry) => entry.id === member.id);
|
|
2079
|
+
const identity = member.identity;
|
|
2080
|
+
if (current.createdAt !== identity.createdAt ||
|
|
2081
|
+
current.workspace !== identity.workspace ||
|
|
2082
|
+
current.nextSequence !== identity.nextSequence ||
|
|
2083
|
+
current.storageTransactionId !== identity.storageTransactionId ||
|
|
2084
|
+
!isDeepStrictEqual(current.source, identity.source) ||
|
|
2085
|
+
!isDeepStrictEqual(current.storage, identity.storage))
|
|
2086
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session deletion identity changed.');
|
|
2087
|
+
}
|
|
2088
|
+
return count === 0;
|
|
2089
|
+
}
|
|
2090
|
+
catch (error) {
|
|
2091
|
+
failure = error;
|
|
2092
|
+
throw error;
|
|
2093
|
+
}
|
|
2094
|
+
finally {
|
|
2095
|
+
if (failure !== undefined)
|
|
2096
|
+
await release().catch(() => undefined);
|
|
2097
|
+
else
|
|
2098
|
+
await release();
|
|
2099
|
+
}
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
async #recoverPendingTreeDelete(sessionId) {
|
|
2103
|
+
this.#sessionRoot(sessionId);
|
|
2104
|
+
const root = join(this.#shared.root, 'sessions');
|
|
2105
|
+
const transaction = await readTreeDeleteTransaction(root, sessionId);
|
|
2106
|
+
if (!transaction)
|
|
2107
|
+
return;
|
|
2108
|
+
const releases = [];
|
|
2109
|
+
let failure;
|
|
2110
|
+
try {
|
|
2111
|
+
for (const member of [...transaction.members].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
2112
|
+
if (await sessionDirectoryExists(join(root, member.id)))
|
|
2113
|
+
releases.push(await acquireFileLock(join(root, member.id, 'lock'), 'Session already has a writer.', 0));
|
|
2114
|
+
}
|
|
2115
|
+
await this.#withTreeReadDrains(transaction.members.map((member) => member.id), async () => {
|
|
2116
|
+
// Confirm the journal phase is durable before choosing its recovery path.
|
|
2117
|
+
await syncJsonlDirectory(join(root, '.delete-pending'));
|
|
2118
|
+
const current = await readTreeDeleteTransaction(root, sessionId);
|
|
2119
|
+
if (!current)
|
|
2120
|
+
return;
|
|
2121
|
+
if (current.id !== transaction.id)
|
|
2122
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session deletion journal changed.');
|
|
2123
|
+
if (current.phase === 'preparing' || current.phase === 'restored')
|
|
2124
|
+
await finishTreeDeletePreparation(root, current, syncJsonlDirectory);
|
|
2125
|
+
else {
|
|
2126
|
+
const committed = await this.#treeDeleteCommitted(current);
|
|
2127
|
+
await finishTreeDelete(root, current, committed, syncJsonlDirectory);
|
|
2128
|
+
}
|
|
2129
|
+
});
|
|
2130
|
+
for (const member of transaction.members)
|
|
2131
|
+
this.#shared.migrationFailures.delete(member.id);
|
|
2132
|
+
await this.#refreshIndex();
|
|
2133
|
+
}
|
|
2134
|
+
catch (error) {
|
|
2135
|
+
failure = error;
|
|
2136
|
+
throw error;
|
|
2137
|
+
}
|
|
2138
|
+
finally {
|
|
2139
|
+
let releaseFailure;
|
|
2140
|
+
for (const release of releases.reverse()) {
|
|
2141
|
+
try {
|
|
2142
|
+
await release();
|
|
2143
|
+
}
|
|
2144
|
+
catch (error) {
|
|
2145
|
+
releaseFailure ??= error;
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
if (failure === undefined && releaseFailure !== undefined)
|
|
2149
|
+
throw releaseFailure;
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
async delete(sessionId, maintenance) {
|
|
2153
|
+
this.#assertOpen();
|
|
2154
|
+
const holding = maintenance ?? (await this.acquireSessionMaintenance(sessionId, 'delete'));
|
|
2155
|
+
this.#assertMaintenance(sessionId, 'delete', holding);
|
|
2156
|
+
const tree = [sessionId, ...holding.sessionIds.filter((id) => id !== sessionId)];
|
|
2157
|
+
let failed = false;
|
|
2158
|
+
try {
|
|
2159
|
+
// Same-owner Executions may have left newer target metadata dirty in memory.
|
|
2160
|
+
// Publish that state before using it as the tree deletion CAS baseline.
|
|
2161
|
+
if (this.#shared.indexDirty)
|
|
2162
|
+
await this.#saveIndex();
|
|
2163
|
+
const root = join(this.#shared.root, 'sessions');
|
|
2164
|
+
const expected = tree.map((id) => {
|
|
2165
|
+
const meta = this.#shared.index.sessions.find((entry) => entry.id === id);
|
|
2166
|
+
if (!meta)
|
|
2167
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
2168
|
+
return structuredClone(meta);
|
|
2169
|
+
});
|
|
2170
|
+
const members = await Promise.all(expected.map(async (meta) => ({
|
|
2171
|
+
id: meta.id,
|
|
2172
|
+
existed: await sessionDirectoryExists(this.#sessionRoot(meta.id)),
|
|
2173
|
+
identity: {
|
|
2174
|
+
createdAt: meta.createdAt,
|
|
2175
|
+
workspace: meta.workspace,
|
|
2176
|
+
...(meta.source === undefined ? {} : { source: meta.source }),
|
|
2177
|
+
...(meta.storage === undefined ? {} : { storage: meta.storage }),
|
|
2178
|
+
...(meta.storageTransactionId === undefined
|
|
2179
|
+
? {}
|
|
2180
|
+
: { storageTransactionId: meta.storageTransactionId }),
|
|
2181
|
+
nextSequence: meta.nextSequence
|
|
2182
|
+
}
|
|
2183
|
+
})));
|
|
2184
|
+
const deleted = members.find((member) => member.id === sessionId)?.existed ?? false;
|
|
2185
|
+
const transaction = {
|
|
2186
|
+
version: 1,
|
|
2187
|
+
kind: 'delete',
|
|
2188
|
+
id: randomUUID(),
|
|
2189
|
+
members
|
|
2190
|
+
};
|
|
2191
|
+
await this.#withTreeReadDrains(tree, async () => {
|
|
2192
|
+
let committed = false;
|
|
2193
|
+
try {
|
|
2194
|
+
await beginTreeDeleteTransaction(root, transaction, syncJsonlDirectory);
|
|
2195
|
+
await stageTreeDelete(root, transaction, syncJsonlDirectory);
|
|
2196
|
+
await this.#serializeIndexOperation(() => this.#commitIndex(tree, [], () => {
|
|
2197
|
+
committed = true;
|
|
2198
|
+
}, undefined, expected));
|
|
2199
|
+
await finishTreeDelete(root, transaction, true, syncJsonlDirectory);
|
|
2200
|
+
}
|
|
2201
|
+
catch (error) {
|
|
2202
|
+
try {
|
|
2203
|
+
let pending = await readTreeDeleteTransaction(root, sessionId);
|
|
2204
|
+
if (pending && pending.id !== transaction.id)
|
|
2205
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session deletion journal changed.');
|
|
2206
|
+
if (pending) {
|
|
2207
|
+
await syncJsonlDirectory(join(root, '.delete-pending'));
|
|
2208
|
+
pending = await readTreeDeleteTransaction(root, sessionId);
|
|
2209
|
+
if (!pending || pending.id !== transaction.id)
|
|
2210
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session deletion journal changed.');
|
|
2211
|
+
if (pending.phase === 'preparing' || pending.phase === 'restored')
|
|
2212
|
+
await finishTreeDeletePreparation(root, pending, syncJsonlDirectory);
|
|
2213
|
+
else {
|
|
2214
|
+
if (!committed)
|
|
2215
|
+
committed = await this.#treeDeleteCommitted(pending);
|
|
2216
|
+
await finishTreeDelete(root, pending, committed, syncJsonlDirectory);
|
|
2217
|
+
}
|
|
2218
|
+
await this.#refreshIndex();
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
catch {
|
|
2222
|
+
for (const member of members)
|
|
2223
|
+
this.#shared.migrationFailures.set(member.id, { error, unreadable: true });
|
|
2224
|
+
}
|
|
2225
|
+
throw error;
|
|
2226
|
+
}
|
|
2227
|
+
});
|
|
2228
|
+
for (const id of tree)
|
|
2229
|
+
this.#shared.segments.delete(id);
|
|
2230
|
+
return { deleted };
|
|
2231
|
+
}
|
|
2232
|
+
catch (error) {
|
|
2233
|
+
failed = true;
|
|
2234
|
+
throw error;
|
|
2235
|
+
}
|
|
2236
|
+
finally {
|
|
2237
|
+
if (!maintenance) {
|
|
2238
|
+
const releasing = holding.release();
|
|
2239
|
+
if (failed)
|
|
2240
|
+
await releasing.catch(() => undefined);
|
|
2241
|
+
else
|
|
2242
|
+
await releasing;
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
#assertOpen() {
|
|
2247
|
+
if (this.#leaseClosed || this.#shared.closed)
|
|
2248
|
+
throw new AgentError('MAR_AGENT_DISPOSED', 'Session store is closed.');
|
|
2249
|
+
}
|
|
2250
|
+
#markIndexDirty(sessionId, derivedOnly = false) {
|
|
2251
|
+
if (derivedOnly && this.#encoding(sessionId) === 1)
|
|
2252
|
+
return;
|
|
2253
|
+
if (!derivedOnly)
|
|
2254
|
+
this.#shared.derivedOnlyDirtySessions.delete(sessionId);
|
|
2255
|
+
else if (!this.#shared.dirtySessionVersions.has(sessionId))
|
|
2256
|
+
this.#shared.derivedOnlyDirtySessions.add(sessionId);
|
|
2257
|
+
this.#shared.indexDirty = true;
|
|
2258
|
+
this.#shared.dirtySessionVersions.set(sessionId, (this.#shared.dirtySessionVersions.get(sessionId) ?? 0) + 1);
|
|
2259
|
+
}
|
|
2260
|
+
async #storageTransactionCommitted(sessionId, transactionId) {
|
|
2261
|
+
const disk = await readSessionIndex(join(this.#shared.root, 'sessions', 'index.json'));
|
|
2262
|
+
if (!disk)
|
|
2263
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session index is missing.');
|
|
2264
|
+
const session = disk.sessions.find((item) => item.id === sessionId);
|
|
2265
|
+
if (!session)
|
|
2266
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session index entry is missing.');
|
|
2267
|
+
return session.storageTransactionId === transactionId;
|
|
2268
|
+
}
|
|
2269
|
+
#segmentPath(sessionId, segment, meta) {
|
|
2270
|
+
return join(meta ? this.#directoryForMeta(sessionId, meta) : this.#sessionDirectory(sessionId), `events-${String(segment).padStart(6, '0')}.jsonl`);
|
|
2271
|
+
}
|
|
2272
|
+
async #assertReadable(sessionId, meta) {
|
|
2273
|
+
const failure = this.#shared.migrationFailures.get(sessionId);
|
|
2274
|
+
if (failure?.unreadable)
|
|
2275
|
+
throw failure.error;
|
|
2276
|
+
const historyFailure = this.#shared.writerLeases.get(sessionId)?.historyFailure;
|
|
2277
|
+
if (historyFailure)
|
|
2278
|
+
throw historyFailure.error;
|
|
2279
|
+
const directories = new Set([
|
|
2280
|
+
this.#sessionRoot(sessionId),
|
|
2281
|
+
this.#directoryForMeta(sessionId, meta)
|
|
2282
|
+
]);
|
|
2283
|
+
for (const directory of directories) {
|
|
2284
|
+
try {
|
|
2285
|
+
await lstat(join(directory, 'storage-transaction.json'));
|
|
2286
|
+
}
|
|
2287
|
+
catch (error) {
|
|
2288
|
+
if (error.code === 'ENOENT')
|
|
2289
|
+
continue;
|
|
2290
|
+
throw error;
|
|
2291
|
+
}
|
|
2292
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session recovery is required.');
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
async #segmentPaths(sessionId, meta) {
|
|
2296
|
+
const migrationFailure = this.#shared.migrationFailures.get(sessionId);
|
|
2297
|
+
if (migrationFailure?.unreadable)
|
|
2298
|
+
throw migrationFailure.error;
|
|
2299
|
+
const failure = this.#shared.writerLeases.get(sessionId)?.historyFailure;
|
|
2300
|
+
if (failure)
|
|
2301
|
+
throw failure.error;
|
|
2302
|
+
const selected = meta ?? this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
2303
|
+
if (selected)
|
|
2304
|
+
await this.#assertReadable(sessionId, selected);
|
|
2305
|
+
const directory = meta
|
|
2306
|
+
? this.#directoryForMeta(sessionId, meta)
|
|
2307
|
+
: this.#sessionDirectory(sessionId);
|
|
2308
|
+
const entries = await readdir(directory);
|
|
2309
|
+
if (entries.includes('storage-transaction.json'))
|
|
2310
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session recovery is required.');
|
|
2311
|
+
const names = entries.filter((name) => /^events-\d{6}\.jsonl$/.test(name)).sort();
|
|
2312
|
+
if (names.length === 0)
|
|
2313
|
+
throw new AgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
2314
|
+
return names.map((name) => join(directory, name));
|
|
2315
|
+
}
|
|
2316
|
+
async #currentSegment(sessionId) {
|
|
2317
|
+
const cached = this.#shared.segments.get(sessionId);
|
|
2318
|
+
if (cached !== undefined)
|
|
2319
|
+
return cached;
|
|
2320
|
+
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
2321
|
+
const indexed = meta?.segments?.at(-1)?.number;
|
|
2322
|
+
if (indexed !== undefined) {
|
|
2323
|
+
this.#shared.segments.set(sessionId, indexed);
|
|
2324
|
+
return indexed;
|
|
2325
|
+
}
|
|
2326
|
+
const paths = await this.#segmentPaths(sessionId);
|
|
2327
|
+
const match = /events-(\d{6})\.jsonl$/.exec(paths.at(-1));
|
|
2328
|
+
const segment = Number(match?.[1] ?? 1);
|
|
2329
|
+
this.#shared.segments.set(sessionId, segment);
|
|
2330
|
+
return segment;
|
|
2331
|
+
}
|
|
2332
|
+
/** Repair the last segment and pending images under the lease, then sync sequence metadata. */
|
|
2333
|
+
async #reconcileTail(sessionId) {
|
|
2334
|
+
const paths = await this.#segmentPaths(sessionId);
|
|
2335
|
+
const latestPath = paths.at(-1);
|
|
2336
|
+
const { records, segment } = await recoverJsonlTail(this.#sessionDirectory(sessionId), latestPath, () => this.#readRecords(sessionId), this.#encoding(sessionId));
|
|
2337
|
+
const last = records.at(-1);
|
|
2338
|
+
this.#shared.segments.set(sessionId, segment);
|
|
2339
|
+
if (last === undefined)
|
|
2340
|
+
throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Session history is corrupt.');
|
|
2341
|
+
const bytes = (await stat(latestPath)).size;
|
|
2342
|
+
const firstSequence = records[0].sequence;
|
|
2343
|
+
// Tail recovery must publish onto metadata that is current after all file I/O.
|
|
2344
|
+
const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
|
|
2345
|
+
const known = meta.segments?.find((item) => item.number === segment);
|
|
2346
|
+
const titleRecord = records.findLast((record) => record.type === 'session.title.updated' &&
|
|
2347
|
+
typeof record.payload.title === 'string');
|
|
2348
|
+
const recoveredTitle = titleRecord?.payload?.title;
|
|
2349
|
+
const changed = meta.nextSequence !== last.sequence + 1 ||
|
|
2350
|
+
known?.bytes !== bytes ||
|
|
2351
|
+
(recoveredTitle !== undefined && meta.title !== recoveredTitle);
|
|
2352
|
+
meta.nextSequence = last.sequence + 1;
|
|
2353
|
+
if (recoveredTitle !== undefined)
|
|
2354
|
+
meta.title = recoveredTitle;
|
|
2355
|
+
meta.lastActivityAt =
|
|
2356
|
+
meta.lastActivityAt < last.timestamp ? last.timestamp : meta.lastActivityAt;
|
|
2357
|
+
if (!meta.segments)
|
|
2358
|
+
meta.segments = [];
|
|
2359
|
+
if (known) {
|
|
2360
|
+
known.firstSequence = firstSequence;
|
|
2361
|
+
known.lastSequence = last.sequence;
|
|
2362
|
+
known.bytes = bytes;
|
|
2363
|
+
}
|
|
2364
|
+
else
|
|
2365
|
+
meta.segments.push({ number: segment, firstSequence, lastSequence: last.sequence, bytes });
|
|
2366
|
+
if (changed) {
|
|
2367
|
+
delete meta.automaticTitlePending;
|
|
2368
|
+
delete meta.resume;
|
|
2369
|
+
this.#shared.index.revision++;
|
|
2370
|
+
this.#markIndexDirty(sessionId);
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
async close() {
|
|
2374
|
+
if (this.#leaseClosed)
|
|
2375
|
+
return;
|
|
2376
|
+
this.#leaseClosed = true;
|
|
2377
|
+
await JsonlSessionStore.#pool.release(this.#shared, () => {
|
|
2378
|
+
const pending = [...this.#shared.writerLeases.entries()].filter(([, writer]) => writer.pendingResource && !writer.executing && !writer.maintenance);
|
|
2379
|
+
if (!pending.length && !this.#shared.indexDirty)
|
|
2380
|
+
return undefined;
|
|
2381
|
+
return (async () => {
|
|
2382
|
+
for (const [sessionId, writer] of pending) {
|
|
2383
|
+
delete writer.pendingResource;
|
|
2384
|
+
writer.retained--;
|
|
2385
|
+
await this.#releaseUnusedWriter(sessionId, writer);
|
|
2386
|
+
}
|
|
2387
|
+
if (this.#shared.indexDirty)
|
|
2388
|
+
await this.#saveIndex();
|
|
2389
|
+
})();
|
|
2390
|
+
});
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
function isObject(value) {
|
|
2394
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
2395
|
+
}
|