mixdog 1.4.2 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/scripts/build-shipped-history.mjs +15 -5
- package/scripts/gate-local.mjs +65 -19
- package/scripts/graph-parity.mjs +8 -3
- package/scripts/lib/gate-scheduler.mjs +2 -1
- package/scripts/lib/run-node-tests.mjs +17 -5
- package/scripts/lib/test-failure-summary.mjs +1 -8
- package/scripts/lib/test-spawn-ledger.mjs +33 -2
- package/scripts/post-release-check.mjs +26 -16
- package/scripts/provider-pingpong-live.mjs +1 -1
- package/scripts/prune-embedding-runtime.mjs +3 -1
- package/scripts/release-paths.mjs +7 -1
- package/src/defaults/shipped-history.json +20 -0
- package/src/defaults/skills/setup/SKILL.md +2 -2
- package/src/defaults/skills/setup/references/actions.md +16 -3
- package/src/defaults/skills/setup/references/surfaces.md +3 -0
- package/src/defaults/skills/setup-guide/SKILL.md +6 -1
- package/src/lib/instruction-text.cjs +36 -0
- package/src/lib/rules-builder.cjs +34 -20
- package/src/orchestration/balanced.md +23 -0
- package/src/orchestration/focused.md +22 -0
- package/src/orchestration/none.md +5 -0
- package/src/orchestration/swarm.md +24 -0
- package/src/rules/agent-mode/AGENT_MODE.md +3 -1
- package/src/rules/agent-mode/MIXY.md +2 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +3 -3
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +0 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +4 -2
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +0 -9
- package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +2 -2
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +67 -0
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +1 -0
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +4 -4
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -1
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +199 -46
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +3 -5
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +9 -3
- package/src/runtime/agent/orchestrator/mcp/client.mjs +3 -5
- package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +1 -1
- package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +8 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +1 -1
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +14 -0
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-stop.mjs +3 -3
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +4 -6
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2784 -2872
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +5 -8
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +288 -300
- package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +10 -30
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +30 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +0 -11
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +8 -0
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +17 -3
- package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +47 -27
- package/src/runtime/agent/orchestrator/runtime-core/goal-deadlines.mjs +141 -10
- package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +26 -63
- package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +28 -3
- package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +36 -11
- package/src/runtime/agent/orchestrator/runtime-core/goal-state.mjs +88 -56
- package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +4 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +51 -32
- package/src/runtime/agent/orchestrator/runtime-core/goal-text.mjs +45 -145
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-defs.mjs +15 -98
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +32 -99
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +23 -78
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +69 -48
- package/src/runtime/agent/orchestrator/runtime-core/todo-list.mjs +367 -0
- package/src/runtime/agent/orchestrator/runtime-core/todo-tool-defs.mjs +47 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-availability.mjs +155 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +9 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +119 -35
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +28 -22
- package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +30 -0
- package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -12
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +7 -3
- package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +1 -2
- package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +11 -1
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +8 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/todo-check.mjs +196 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +116 -50
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +8 -1
- package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +5 -6
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +6 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/loop/turn-report.mjs +35 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +57 -26
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +9 -7
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +21 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-failure.mjs +27 -11
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-open.mjs +37 -34
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +25 -15
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +79 -24
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +12 -1
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +6 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +9 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-path.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/prompt-settings.mjs +598 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +19 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +19 -10
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +31 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +31 -0
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +42 -6
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +11 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +8 -9
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +339 -88
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-recovery.mjs +135 -11
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +377 -104
- package/src/runtime/agent/orchestrator/session/manager/turn-durability.mjs +144 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +8 -1
- package/src/runtime/agent/orchestrator/session/save-session-worker.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/liveness-veto.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +23 -2
- package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/store/transcript-durability.mjs +88 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-log.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +14 -5
- package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/store.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +9 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/agent-envelope.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +2 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +21 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/worktree-add-guard.mjs +77 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +0 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +7 -3
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +27 -9
- package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +0 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +0 -10
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +1 -24
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary/child-kill.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/modes/calls.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/modes/symbols.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +4 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +3 -4
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4 -22
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +0 -4
- package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +0 -1
- package/src/runtime/attachments/attachment-reference.mjs +1 -1
- package/src/runtime/attachments/pdf-extract.mjs +1 -1
- package/src/runtime/attachments/store.d.mts +1 -0
- package/src/runtime/attachments/store.mjs +2 -2
- package/src/runtime/channels/lib/config-reload-watch.mjs +42 -0
- package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +29 -9
- package/src/runtime/channels/lib/scheduler.mjs +33 -0
- package/src/runtime/channels/lib/schedules-db.mjs +11 -4
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +4 -1
- package/src/runtime/channels/lib/webhooks-db.mjs +9 -3
- package/src/runtime/channels/lib/whisper-server.mjs +12 -12
- package/src/runtime/channels/lib/worker-main.mjs +9 -31
- package/src/runtime/computer-bridge/linux-readiness.mjs +10 -14
- package/src/runtime/connectors/connector-accounts-mcp.mjs +169 -33
- package/src/runtime/connectors/gmail-app-password.mjs +2 -2
- package/src/runtime/connectors/google-mcp.mjs +1 -1
- package/src/runtime/effort-judge/judge-client.mjs +2 -2
- package/src/runtime/effort-judge/judge-input.mjs +4 -4
- package/src/runtime/explore/explorer.mjs +0 -5
- package/src/runtime/explore/tool.mjs +3 -3
- package/src/runtime/local-provider/hardware.mjs +1 -1
- package/src/runtime/media/adapters/codex-image.mjs +3 -3
- package/src/runtime/media/adapters/gemini-video.mjs +1 -1
- package/src/runtime/media/media-usage.mjs +1 -1
- package/src/runtime/media/store.mjs +1 -1
- package/src/runtime/memory/index.mjs +11 -1
- package/src/runtime/memory/lib/embedding-reindex.mjs +27 -0
- package/src/runtime/memory/lib/memory-embed.mjs +83 -14
- package/src/runtime/memory/lib/memory-process-lock.mjs +159 -36
- package/src/runtime/memory/lib/memory-text-utils.mjs +6 -1
- package/src/runtime/memory/lib/memory.mjs +17 -0
- package/src/runtime/memory/lib/pg/process.mjs +1 -1
- package/src/runtime/memory/lib/pg/supervisor.mjs +16 -96
- package/src/runtime/memory/lib/recall-hybrid/hybrid-sql.mjs +12 -3
- package/src/runtime/office/authoring/docx-html-runner.mjs +1 -1
- package/src/runtime/office/authoring/pptx-author-action.mjs +1 -1
- package/src/runtime/office/authoring/xlsx-html-build.mjs +1 -1
- package/src/runtime/office/authoring/xlsx-html-runner.mjs +1 -1
- package/src/runtime/office/capabilities.mjs +1 -1
- package/src/runtime/office/core/office-core.mjs +1 -1
- package/src/runtime/office/design/content-model.mjs +3 -3
- package/src/runtime/office/design/docx/design-docx/section.mjs +6 -6
- package/src/runtime/office/design/xlsx/design-xlsx-components.mjs +1 -1
- package/src/runtime/office/design/xlsx/design-xlsx.mjs +1 -1
- package/src/runtime/office/pdf/ocr-engine.mjs +1 -1
- package/src/runtime/office/pdf/pdf-writer.mjs +4 -4
- package/src/runtime/office/portable/portable-docx-operations.mjs +2 -2
- package/src/runtime/office/portable/portable-soffice.mjs +8 -6
- package/src/runtime/office/portable/xlsx-contract.mjs +1 -1
- package/src/runtime/shared/atomic-file.d.mts +8 -0
- package/src/runtime/shared/background-tasks.mjs +22 -4
- package/src/runtime/shared/background-wait.mjs +66 -0
- package/src/runtime/shared/child-guardian.mjs +17 -7
- package/src/runtime/shared/config.mjs +313 -16
- package/src/runtime/shared/connector-catalog.d.mts +4 -5
- package/src/runtime/shared/connector-catalog.mjs +11 -1
- package/src/runtime/shared/file-lock.mjs +8 -3
- package/src/runtime/shared/git-runner.mjs +7 -2
- package/src/runtime/shared/idle-gc.mjs +30 -7
- package/src/runtime/shared/llm/http-agent.mjs +8 -5
- package/src/runtime/shared/llm/system-proxy.mjs +2 -1
- package/src/runtime/shared/llm/usage-ledger-quota.mjs +31 -12
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +84 -8
- package/src/runtime/shared/llm/usage-ledger.mjs +3 -0
- package/src/runtime/shared/llm/usage-rollup.mjs +48 -0
- package/src/runtime/shared/maintenance-schedule.mjs +109 -0
- package/src/runtime/shared/process-runner.mjs +8 -4
- package/src/runtime/shared/routine-trigger.d.mts +13 -0
- package/src/runtime/shared/routine-trigger.mjs +34 -0
- package/src/runtime/shared/run-child.d.mts +2 -1
- package/src/runtime/shared/run-child.mjs +70 -18
- package/src/runtime/shared/session-scratch.mjs +14 -2
- package/src/runtime/shared/shipped-definitions.mjs +7 -1
- package/src/runtime/shared/tool-card-model.golden.json +3 -12
- package/src/runtime/shared/tool-execution-contract.mjs +2 -0
- package/src/runtime/shared/tool-work-units.mjs +31 -6
- package/src/runtime/shared/turn-snapshot.mjs +26 -4
- package/src/runtime/shared/turn-worktree-snapshot.mjs +6 -11
- package/src/runtime/shared/worker-requests.mjs +17 -3
- package/src/runtime/shared/worktrees/branch-management.mjs +1 -5
- package/src/runtime/shared/worktrees/contract.mjs +16 -6
- package/src/runtime/shared/worktrees/dependencies.mjs +127 -89
- package/src/runtime/shared/worktrees/disk.mjs +79 -2
- package/src/runtime/shared/worktrees/git.mjs +44 -11
- package/src/runtime/shared/worktrees/include.mjs +6 -4
- package/src/runtime/shared/worktrees/maintenance.mjs +422 -30
- package/src/runtime/shared/worktrees/manager.mjs +772 -75
- package/src/runtime/shared/worktrees/settings.mjs +33 -7
- package/src/runtime/shared/worktrees/test-support.mjs +5 -1
- package/src/runtime/shared/worktrees/worker-snapshots.mjs +82 -0
- package/src/runtime/tidy/process.mjs +2 -1
- package/src/runtime/web-search/index.mjs +3 -22
- package/src/runtime/web-search/lib/web-tools.mjs +2 -2
- package/src/runtime/web-search/tool-defs.mjs +17 -26
- package/src/session-runtime/agent-profile-changes.mjs +55 -4
- package/src/session-runtime/boot/apis.mjs +27 -4
- package/src/session-runtime/boot/begin.mjs +2 -0
- package/src/session-runtime/boot/config.mjs +10 -1
- package/src/session-runtime/boot/facade.mjs +1 -0
- package/src/session-runtime/boot/providers.mjs +70 -3
- package/src/session-runtime/boot/session.mjs +2 -0
- package/src/session-runtime/boot/tools.mjs +69 -25
- package/src/session-runtime/channel-config-api.mjs +1 -0
- package/src/session-runtime/config-change-effects.mjs +179 -0
- package/src/session-runtime/config-lifecycle/config-writers.mjs +1 -0
- package/src/session-runtime/config-lifecycle.mjs +160 -24
- package/src/session-runtime/conversation-import/discovery.mjs +86 -7
- package/src/session-runtime/conversation-import/service.mjs +3 -2
- package/src/session-runtime/goal-facade-api.mjs +49 -3
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +3 -0
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -2
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +3 -11
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +18 -4
- package/src/session-runtime/internal-tool-executor.mjs +12 -19
- package/src/session-runtime/lifecycle/cold-heir.mjs +7 -0
- package/src/session-runtime/lifecycle/memory-ingest.mjs +3 -3
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +53 -5
- package/src/session-runtime/lifecycle/session-switch.mjs +13 -2
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +4 -4
- package/src/session-runtime/lifecycle-api.mjs +1 -0
- package/src/session-runtime/mcp-glue/server-catalog.mjs +14 -3
- package/src/session-runtime/mcp-glue/server-connection.mjs +34 -8
- package/src/session-runtime/model-route/set-route.mjs +40 -7
- package/src/session-runtime/native-web-search/routes.mjs +3 -3
- package/src/session-runtime/provider-auth/refresh.mjs +6 -3
- package/src/session-runtime/provider-auth/setup.mjs +1 -1
- package/src/session-runtime/provider-readiness.mjs +53 -4
- package/src/session-runtime/resource-api.mjs +1 -1
- package/src/session-runtime/resource-extension-sync.mjs +4 -8
- package/src/session-runtime/resource-mcp-connector-accounts.mjs +5 -1
- package/src/session-runtime/resource-mcp-secrets.mjs +3 -3
- package/src/session-runtime/resource-plugins-api.mjs +4 -0
- package/src/session-runtime/routine-executor.mjs +152 -0
- package/src/session-runtime/routine-home-turn.mjs +46 -5
- package/src/session-runtime/runtime-facade.mjs +5 -0
- package/src/session-runtime/runtime-feature-gates.mjs +2 -11
- package/src/session-runtime/schedule-session-run.mjs +1 -0
- package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +6 -1
- package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +7 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +110 -6
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +12 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +5 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +24 -6
- package/src/session-runtime/services/agent-tool.mjs +51 -1
- package/src/session-runtime/services/channel-admin.mjs +18 -0
- package/src/session-runtime/services/hook-bus/command-handler.mjs +7 -11
- package/src/session-runtime/services/plugin-admin.mjs +4 -0
- package/src/session-runtime/services/seeds.mjs +7 -0
- package/src/session-runtime/services/usage-stats-model.mjs +32 -11
- package/src/session-runtime/session-create/materialize.mjs +4 -2
- package/src/session-runtime/session-create/session-options.mjs +2 -0
- package/src/session-runtime/settings-builtin-tools-api.mjs +1 -9
- package/src/session-runtime/setup-tool/connector-widen-approval.mjs +1 -1
- package/src/session-runtime/setup-tool/executor.mjs +8 -1
- package/src/session-runtime/setup-tool/extended-actions.mjs +2 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +28 -0
- package/src/session-runtime/setup-tool/tool-defs.mjs +26 -1
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +40 -43
- package/src/session-runtime/tool-policy/mode-switch.mjs +1 -1
- package/src/session-runtime/tool-policy/policy-stage.mjs +106 -8
- package/src/session-runtime/tool-policy/workflow-overlay.mjs +2 -1
- package/src/session-runtime/tool-policy-refresh.mjs +182 -5
- package/src/session-runtime/tool-surface.mjs +38 -1
- package/src/session-runtime/turn/agent-ops.mjs +10 -2
- package/src/session-runtime/turn/session-ops.mjs +2 -0
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +5 -0
- package/src/session-runtime/turn/turn-run.mjs +1 -1
- package/src/session-runtime/webhook-session-run.mjs +2 -0
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +1 -4
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +3 -10
- package/src/session-runtime/workflow-agents-api/instruction-files.mjs +165 -0
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +5 -4
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +2 -3
- package/src/session-runtime/workflow-agents-api.mjs +2 -0
- package/src/session-runtime/workspace/project-branches.mjs +1 -1
- package/src/session-runtime/workspace/session-view.mjs +1 -1
- package/src/session-runtime/workspace/transitions.mjs +20 -4
- package/src/session-runtime/workspace/worktree-maintenance.mjs +126 -6
- package/src/session-runtime/workspace/worktree-setup.mjs +13 -1
- package/src/session-runtime/workspace/worktree-tool.mjs +16 -0
- package/src/standalone/daemon-agent-control.mjs +25 -8
- package/src/standalone/daemon-desktop-runtime.mjs +6 -2
- package/src/standalone/daemon-lag-profiler.mjs +7 -7
- package/src/standalone/daemon.mjs +54 -2
- package/src/standalone/local-session-runtime.mjs +16 -0
- package/src/standalone/session-client/daemon-unavailable.mjs +1 -1
- package/src/standalone/session-protocol.mjs +11 -0
- package/src/standalone/session-service/agent-route-follow.mjs +23 -20
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +18 -6
- package/src/standalone/session-service/quota-resume.mjs +9 -0
- package/src/standalone/session-service/session-calls/session-action.mjs +51 -1
- package/src/standalone/session-service/session-calls/session-turns.mjs +37 -18
- package/src/standalone/session-service/session-calls/turn-gate.mjs +34 -0
- package/src/standalone/session-service/session-calls.mjs +2 -1
- package/src/tui/App.jsx +8 -0
- package/src/tui/app/clipboard.mjs +1 -1
- package/src/tui/app/create-app-pickers.mjs +2 -0
- package/src/tui/app/doctor.mjs +2 -2
- package/src/tui/app/panel-signature.mjs +1 -1
- package/src/tui/app/settings-picker.mjs +53 -8
- package/src/tui/app/shell-layout/row-budgets.mjs +2 -2
- package/src/tui/app/slash-commands.mjs +1 -1
- package/src/tui/app/slash-dispatch/route-commands.mjs +1 -1
- package/src/tui/app/slash-dispatch/session-commands.mjs +1 -1
- package/src/tui/app/use-prompt-queue-history.mjs +2 -2
- package/src/tui/app/use-transcript-activity.mjs +1 -1
- package/src/tui/app/use-transcript-window/geometry.mjs +1 -1
- package/src/tui/components/prompt-input/escape-policy.d.mts +0 -1
- package/src/tui/components/prompt-input/escape-policy.mjs +1 -1
- package/src/tui/components/question-panel-model.mjs +4 -4
- package/src/tui/components/tool-execution/standard-card.jsx +3 -4
- package/src/tui/components/user-message-skills.mjs +1 -1
- package/src/tui/dist/index.mjs +309 -30
- package/src/tui/markdown/render-ansi.mjs +1 -1
- package/src/tui/session/agent-job-feed/notification-router.mjs +10 -0
- package/src/tui/session/agent-job-feed.mjs +2 -0
- package/src/tui/session/context-state.mjs +3 -0
- package/src/tui/session/quota-resume.mjs +1 -1
- package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
- package/src/tui/session/session-api/lifecycle/session-reset.mjs +7 -0
- package/src/tui/session/session-api/routes-agent-definitions.mjs +5 -0
- package/src/tui/session/session-api/worktree.mjs +13 -1
- package/src/tui/session/session-flow/drain.mjs +11 -14
- package/src/tui/session/tool-card-results/grouped-result.mjs +2 -2
- package/src/tui/session/turn-quiet.mjs +1 -1
- package/src/tui/session/turn.mjs +3 -0
- package/src/tui/session/worktree-setup-notice.mjs +1 -1
- package/src/tui/spinner-meta.mjs +3 -3
- package/src/tui/spinner-verbs.mjs +2 -2
- package/src/defaults/skills/goal-management/SKILL.md +0 -169
- package/src/runtime/agent/orchestrator/runtime-core/goal-tasks.mjs +0 -156
- package/src/session-runtime/goal-tasks.mjs +0 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// manager/turn-checkpoint-journal.mjs
|
|
2
2
|
// Bounded-cost, crash-safe turn checkpointing.
|
|
3
3
|
//
|
|
4
|
-
// PROBLEM.
|
|
5
|
-
//
|
|
4
|
+
// PROBLEM. A single full-snapshot checkpoint file rewritten on every flush (up
|
|
5
|
+
// to ~6/s per live session) would re-sanitize and re-serialize the WHOLE growing
|
|
6
6
|
// turn — every committed message, every buffered partial character, every
|
|
7
|
-
// observed tool call —
|
|
8
|
-
//
|
|
9
|
-
//
|
|
7
|
+
// observed tool call — on the engine thread. Cost per flush would grow with the
|
|
8
|
+
// turn (O(turn^2) over a long tool-heavy turn) and at maximum agent fan-out
|
|
9
|
+
// every live session would pay it on the SAME event loop.
|
|
10
10
|
//
|
|
11
11
|
// DESIGN. Split the checkpoint into a write-once header plus an append-only
|
|
12
12
|
// delta journal:
|
|
@@ -15,9 +15,11 @@
|
|
|
15
15
|
// before the provider ever runs, and store-summary-reader
|
|
16
16
|
// keeps probing this exact path to detect a live turn.
|
|
17
17
|
// <session>.jsonl append-only JSONL deltas — NEW/CHANGED turn messages (by
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
18
|
+
// index relative to the header's message list), APPENDED
|
|
19
|
+
// partial text/reasoning, small interruption-state
|
|
20
|
+
// transitions and the terminal seal. A history rewrite is
|
|
21
|
+
// never a delta: the recorder rewrites the header with
|
|
22
|
+
// the whole resulting context (turn-checkpoint.mjs).
|
|
21
23
|
// A normal flush costs O(new bytes), never O(turn).
|
|
22
24
|
// Appends ride a bounded coalescing lane (one in-flight write, one pending
|
|
23
25
|
// buffer, synchronous drain above the byte ceiling) so the engine thread never
|
|
@@ -25,23 +27,32 @@
|
|
|
25
27
|
//
|
|
26
28
|
// Change detection is reference identity on message objects — the same contract
|
|
27
29
|
// the session store's delta saves already rely on ("Replace, never mutate":
|
|
28
|
-
// manager/prompt-utils.mjs, manager/session-lifecycle.mjs).
|
|
29
|
-
//
|
|
30
|
-
// refs, so it journals as changed indices plus a truncation record.
|
|
30
|
+
// manager/prompt-utils.mjs, manager/session-lifecycle.mjs). Messages the turn
|
|
31
|
+
// itself replaced journal as changed indices plus a truncation record.
|
|
31
32
|
//
|
|
32
33
|
// Reads replay header+journal into the EXACT object shape the full-snapshot
|
|
33
34
|
// writer produced, so recovery/projection semantics are unchanged. A crash-torn
|
|
34
35
|
// trailing line is discarded: replay stops at the first unparsable record and
|
|
35
36
|
// keeps the durable prefix.
|
|
36
|
-
import {
|
|
37
|
+
import { createHash } from 'node:crypto';
|
|
38
|
+
import {
|
|
39
|
+
appendFileSync,
|
|
40
|
+
existsSync,
|
|
41
|
+
mkdirSync,
|
|
42
|
+
readdirSync,
|
|
43
|
+
readFileSync,
|
|
44
|
+
renameSync,
|
|
45
|
+
unlinkSync,
|
|
46
|
+
writeFileSync,
|
|
47
|
+
} from 'node:fs';
|
|
37
48
|
import { appendFile, unlink, writeFile } from 'node:fs/promises';
|
|
38
49
|
import { join } from 'node:path';
|
|
39
50
|
import { getPluginData } from '../../config.mjs';
|
|
40
51
|
import { writeFileAtomicSync } from '../../../../shared/atomic-file.mjs';
|
|
52
|
+
import { withFileLockSync } from '../../../../shared/file-lock.mjs';
|
|
41
53
|
import { sanitizeContentForStoredHistory } from '../../providers/media-normalization.mjs';
|
|
42
54
|
import { redactSettledText, sensitiveValuesVersion } from '../sensitive-values.mjs';
|
|
43
55
|
import { redactSensitiveContent, redactSensitiveMessage, redactSensitiveToolCall } from '../sensitive-projection.mjs';
|
|
44
|
-
import { promptContentText } from './prompt-utils.mjs';
|
|
45
56
|
|
|
46
57
|
const TURN_CHECKPOINT_VERSION = 1;
|
|
47
58
|
const SESSION_ID = /^[A-Za-z0-9_-]+$/;
|
|
@@ -98,30 +109,6 @@ export function checkpointMessage(message) {
|
|
|
98
109
|
return content === message.content ? message : { ...message, content };
|
|
99
110
|
}
|
|
100
111
|
|
|
101
|
-
function matchingUserMessage(message, currentUserContent) {
|
|
102
|
-
return (
|
|
103
|
-
message?.role === 'user' &&
|
|
104
|
-
(message.content === currentUserContent ||
|
|
105
|
-
promptContentText(message.content) === promptContentText(currentUserContent))
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Index of the user message that opened this turn, or -1. */
|
|
110
|
-
export function findTurnStart(messages, currentUserContent) {
|
|
111
|
-
const source = Array.isArray(messages) ? messages : [];
|
|
112
|
-
for (let index = source.length - 1; index >= 0; index -= 1) {
|
|
113
|
-
if (matchingUserMessage(source[index], currentUserContent)) return index;
|
|
114
|
-
}
|
|
115
|
-
return -1;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function turnMessagesForCheckpoint(messages, currentUserContent) {
|
|
119
|
-
const source = Array.isArray(messages) ? messages : [];
|
|
120
|
-
const start = findTurnStart(source, currentUserContent);
|
|
121
|
-
const turn = start >= 0 ? source.slice(start) : [];
|
|
122
|
-
return turn.map(checkpointMessage);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
112
|
export function emptyInterruptionSnapshot() {
|
|
126
113
|
return {
|
|
127
114
|
responseStarted: false,
|
|
@@ -168,6 +155,10 @@ function redactInterruptionSnapshot(sessionId, snapshot) {
|
|
|
168
155
|
|
|
169
156
|
export function writeCheckpointHeader(checkpoint) {
|
|
170
157
|
const { sessionId } = checkpoint;
|
|
158
|
+
// A half-moved checkpoint is finished before anything replaces its files.
|
|
159
|
+
if (finishPendingSetAside(sessionId).pending) {
|
|
160
|
+
throw new Error(`[turn-checkpoint] session ${sessionId}: an unrecovered checkpoint is still being set aside`);
|
|
161
|
+
}
|
|
171
162
|
const target = turnCheckpointPath(sessionId);
|
|
172
163
|
const payload = {
|
|
173
164
|
...checkpoint,
|
|
@@ -196,8 +187,11 @@ export function readTurnCheckpointHeader(sessionId) {
|
|
|
196
187
|
}
|
|
197
188
|
|
|
198
189
|
/** `present` is false only when the header file does not exist (ENOENT);
|
|
199
|
-
* an unreadable or invalid header is present with a null `header
|
|
190
|
+
* an unreadable or invalid header is present with a null `header`, and so
|
|
191
|
+
* are the files of an unfinished set-aside (`settingAside`), which are never
|
|
192
|
+
* read as a checkpoint. */
|
|
200
193
|
export function readTurnCheckpointHeaderState(sessionId) {
|
|
194
|
+
if (finishPendingSetAside(sessionId).pending) return { present: true, header: null, settingAside: true };
|
|
201
195
|
let text;
|
|
202
196
|
try {
|
|
203
197
|
text = readFileSync(turnCheckpointReadPath(sessionId), 'utf8');
|
|
@@ -207,6 +201,164 @@ export function readTurnCheckpointHeaderState(sessionId) {
|
|
|
207
201
|
return { present: true, header: parseTurnCheckpointHeader(text, sessionId) };
|
|
208
202
|
}
|
|
209
203
|
|
|
204
|
+
// ── Unrecovered checkpoints, set aside ──────────────────────────────────────
|
|
205
|
+
// A checkpoint recovery cannot apply (it names no base revision, or the record
|
|
206
|
+
// is not of its base) must never be overwritten by the session's next turn.
|
|
207
|
+
// It is renamed, header and journal as they are, to
|
|
208
|
+
// `<session>.unrecovered-<key>.json(l)`, `key` a digest of the header bytes.
|
|
209
|
+
//
|
|
210
|
+
// The move is several filesystem steps, so it is made one recoverable unit by
|
|
211
|
+
// an intent record, `<session>.setaside` ({ key }), written durably FIRST and
|
|
212
|
+
// removed LAST:
|
|
213
|
+
// 1. intent 2. journal → <stem>.jsonl 3. header → <stem>.json 4. drop intent
|
|
214
|
+
// While the intent exists the primary files are never read as a checkpoint,
|
|
215
|
+
// cleared, or written over (readTurnCheckpointHeaderState, clearTurnCheckpoint
|
|
216
|
+
// in turn-checkpoint.mjs, writeCheckpointHeader): every access first finishes
|
|
217
|
+
// the move, and when that is still refused it reports the set-aside as
|
|
218
|
+
// pending instead. A crash or a refused rename after any step therefore
|
|
219
|
+
// resolves only to finishing it. A moved journal is never overwritten: a
|
|
220
|
+
// primary journal found once `<stem>.jsonl` exists is not this checkpoint's.
|
|
221
|
+
const UNRECOVERED = '.unrecovered-';
|
|
222
|
+
const SET_ASIDE_KEY = /^[0-9a-f]{16}$/;
|
|
223
|
+
|
|
224
|
+
// Test seam: called with the step name ('intent' | 'journal' | 'header' |
|
|
225
|
+
// 'done') before each step; throwing simulates a crash or a refused rename there.
|
|
226
|
+
let _setAsideFault = null;
|
|
227
|
+
export function _setSetAsideFaultForTest(fault) {
|
|
228
|
+
_setAsideFault = typeof fault === 'function' ? fault : null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function setAsideIntentPath(sessionId) {
|
|
232
|
+
return checkpointFilePath(sessionId, '.setaside', { ensureDir: false });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function unrecoveredStem(sessionId, key) {
|
|
236
|
+
return join(checkpointDirPath(), `${sessionId}${UNRECOVERED}${key}`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const headerKey = (text) => createHash('sha256').update(text).digest('hex').slice(0, 16);
|
|
240
|
+
|
|
241
|
+
function readFileOrNull(path) {
|
|
242
|
+
try {
|
|
243
|
+
return readFileSync(path, 'utf8');
|
|
244
|
+
} catch (error) {
|
|
245
|
+
if (error?.code === 'ENOENT') return null;
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** The key of the set-aside in progress for `sessionId`, or null. */
|
|
251
|
+
function pendingSetAsideKey(sessionId) {
|
|
252
|
+
const text = readFileOrNull(setAsideIntentPath(sessionId));
|
|
253
|
+
if (text === null) return null;
|
|
254
|
+
try {
|
|
255
|
+
const key = JSON.parse(text)?.key;
|
|
256
|
+
if (typeof key === 'string' && SET_ASIDE_KEY.test(key)) return key;
|
|
257
|
+
} catch {
|
|
258
|
+
/* an intent is written atomically; fall back to the header's own key */
|
|
259
|
+
}
|
|
260
|
+
const header = readFileOrNull(turnCheckpointReadPath(sessionId));
|
|
261
|
+
return header === null ? '' : headerKey(header);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function renameIfPresent(from, to) {
|
|
265
|
+
try {
|
|
266
|
+
renameSync(from, to);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Steps 2-4 for `key` ('' when no primary header is left to name it). */
|
|
273
|
+
function completeSetAside(sessionId, key) {
|
|
274
|
+
if (key) {
|
|
275
|
+
const stem = unrecoveredStem(sessionId, key);
|
|
276
|
+
_setAsideFault?.('journal');
|
|
277
|
+
if (!existsSync(`${stem}.jsonl`)) renameIfPresent(turnJournalReadPath(sessionId), `${stem}.jsonl`);
|
|
278
|
+
_setAsideFault?.('header');
|
|
279
|
+
renameIfPresent(turnCheckpointReadPath(sessionId), `${stem}.json`);
|
|
280
|
+
}
|
|
281
|
+
_setAsideFault?.('done');
|
|
282
|
+
try {
|
|
283
|
+
unlinkSync(setAsideIntentPath(sessionId));
|
|
284
|
+
} catch (error) {
|
|
285
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Finish an interrupted set-aside of `sessionId`. Returns `{ pending }`:
|
|
290
|
+
* true when one is still unfinished (its files must not be touched);
|
|
291
|
+
* `key` names the one it finished. */
|
|
292
|
+
export function finishPendingSetAside(sessionId) {
|
|
293
|
+
let key;
|
|
294
|
+
try {
|
|
295
|
+
key = pendingSetAsideKey(sessionId);
|
|
296
|
+
if (key === null) return { pending: false, key: null };
|
|
297
|
+
completeSetAside(sessionId, key);
|
|
298
|
+
return { pending: false, key: key || null };
|
|
299
|
+
} catch {
|
|
300
|
+
return { pending: true, key: key || null };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Move `sessionId`'s current checkpoint aside (finishing an interrupted move
|
|
305
|
+
* first). Returns the key of what it moved, or null when there was nothing.
|
|
306
|
+
* Throws when the files cannot be moved: the caller must not then write a
|
|
307
|
+
* new checkpoint, and every reader keeps treating them as being set aside. */
|
|
308
|
+
export function setAsideTurnCheckpoint(sessionId) {
|
|
309
|
+
const pending = pendingSetAsideKey(sessionId);
|
|
310
|
+
if (pending !== null) completeSetAside(sessionId, pending);
|
|
311
|
+
const text = readFileOrNull(turnCheckpointReadPath(sessionId));
|
|
312
|
+
if (text === null) return pending || null;
|
|
313
|
+
const key = headerKey(text);
|
|
314
|
+
checkpointDir();
|
|
315
|
+
_setAsideFault?.('intent');
|
|
316
|
+
writeFileAtomicSync(setAsideIntentPath(sessionId), JSON.stringify({ key }));
|
|
317
|
+
completeSetAside(sessionId, key);
|
|
318
|
+
return key;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** `[{ key, checkpoint, pending? }]` for every checkpoint of `sessionId` set
|
|
322
|
+
* aside; `checkpoint` is null when its header is unreadable or its move is
|
|
323
|
+
* still unfinished (`pending`). */
|
|
324
|
+
export function listUnrecoveredTurnCheckpoints(sessionId) {
|
|
325
|
+
const prefix = `${assertSessionId(sessionId)}${UNRECOVERED}`;
|
|
326
|
+
const unfinished = finishPendingSetAside(sessionId);
|
|
327
|
+
let names;
|
|
328
|
+
try {
|
|
329
|
+
names = readdirSync(checkpointDirPath());
|
|
330
|
+
} catch {
|
|
331
|
+
names = [];
|
|
332
|
+
}
|
|
333
|
+
const found = unfinished.pending ? [{ key: unfinished.key, checkpoint: null, pending: true }] : [];
|
|
334
|
+
for (const name of names) {
|
|
335
|
+
if (!name.startsWith(prefix) || !name.endsWith('.json')) continue;
|
|
336
|
+
if (unfinished.pending && name === `${prefix}${unfinished.key}.json`) continue;
|
|
337
|
+
const stem = join(checkpointDirPath(), name.slice(0, -'.json'.length));
|
|
338
|
+
let checkpoint = null;
|
|
339
|
+
try {
|
|
340
|
+
const header = parseTurnCheckpointHeader(readFileSync(`${stem}.json`, 'utf8'), sessionId);
|
|
341
|
+
checkpoint = header ? replayTurnJournal(header, `${stem}.jsonl`) : null;
|
|
342
|
+
} catch {
|
|
343
|
+
checkpoint = null;
|
|
344
|
+
}
|
|
345
|
+
found.push({ key: name.slice(prefix.length, -'.json'.length), checkpoint });
|
|
346
|
+
}
|
|
347
|
+
return found;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Delete one set-aside checkpoint (only on evidence that disk covers it). */
|
|
351
|
+
export function removeUnrecoveredTurnCheckpoint(sessionId, key) {
|
|
352
|
+
const stem = unrecoveredStem(assertSessionId(sessionId), key);
|
|
353
|
+
for (const path of [`${stem}.json`, `${stem}.jsonl`]) {
|
|
354
|
+
try {
|
|
355
|
+
unlinkSync(path);
|
|
356
|
+
} catch (error) {
|
|
357
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
210
362
|
function parseTurnCheckpointHeader(text, sessionId) {
|
|
211
363
|
try {
|
|
212
364
|
const value = JSON.parse(text);
|
|
@@ -293,11 +445,14 @@ function _drainJournalOpsSync(sessionId, entry) {
|
|
|
293
445
|
const ops = entry.ops;
|
|
294
446
|
entry.ops = [];
|
|
295
447
|
entry.bytes = 0;
|
|
296
|
-
for (const op of ops) {
|
|
448
|
+
for (const [index, op] of ops.entries()) {
|
|
297
449
|
try {
|
|
298
450
|
_runJournalOpSync(op);
|
|
451
|
+
op.settle?.(true);
|
|
299
452
|
} catch (error) {
|
|
300
453
|
_warnJournalOnce(sessionId, error);
|
|
454
|
+
// The rest of the queue is dropped: a removal among it did not happen.
|
|
455
|
+
for (const dropped of ops.slice(index)) dropped.settle?.(false);
|
|
301
456
|
break;
|
|
302
457
|
}
|
|
303
458
|
}
|
|
@@ -323,9 +478,13 @@ function _pumpJournal(sessionId) {
|
|
|
323
478
|
entry.writing = true;
|
|
324
479
|
entry.inFlight = op;
|
|
325
480
|
_runJournalOpAsync(op)
|
|
326
|
-
.
|
|
327
|
-
|
|
328
|
-
|
|
481
|
+
.then(
|
|
482
|
+
() => op.settle?.(true),
|
|
483
|
+
(error) => {
|
|
484
|
+
_warnJournalOnce(sessionId, error);
|
|
485
|
+
op.settle?.(false);
|
|
486
|
+
}
|
|
487
|
+
)
|
|
329
488
|
.finally(() => {
|
|
330
489
|
entry.writing = false;
|
|
331
490
|
entry.inFlight = null;
|
|
@@ -361,13 +520,21 @@ export function cancelJournalWrites(sessionId) {
|
|
|
361
520
|
_releaseIdleEntry(sessionId, entry);
|
|
362
521
|
}
|
|
363
522
|
|
|
364
|
-
|
|
523
|
+
/** `immediate`: written on the calling thread when nothing is queued or in
|
|
524
|
+
* flight (the terminal seal), otherwise queued in order like any append. */
|
|
525
|
+
export function appendJournalLines(sessionId, lines, { immediate = false } = {}) {
|
|
365
526
|
if (!Array.isArray(lines) || lines.length === 0) return false;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
527
|
+
const op = { kind: 'append', path: turnJournalPath(sessionId), data: lines.join('') };
|
|
528
|
+
if (immediate && !_journalWrites.has(sessionId)) {
|
|
529
|
+
try {
|
|
530
|
+
_runJournalOpSync(op);
|
|
531
|
+
return true;
|
|
532
|
+
} catch (error) {
|
|
533
|
+
_warnJournalOnce(sessionId, error);
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
_enqueueJournalOp(sessionId, op);
|
|
371
538
|
return true;
|
|
372
539
|
}
|
|
373
540
|
|
|
@@ -398,10 +565,102 @@ export function openJournalForTurn(sessionId, openLines) {
|
|
|
398
565
|
* the file behind it) and before any later turn's open, which the lane fences
|
|
399
566
|
* behind it. A crash before it lands leaves a journal whose header is already
|
|
400
567
|
* gone, which replay never reads; process exit re-runs it synchronously.
|
|
568
|
+
* Resolves true once the file is gone, false when the removal failed (or was
|
|
569
|
+
* dropped behind a failed older write) — it never rejects.
|
|
401
570
|
*/
|
|
402
571
|
export function removeTurnJournal(sessionId) {
|
|
403
572
|
cancelJournalWrites(sessionId);
|
|
404
|
-
|
|
573
|
+
return new Promise((settle) => {
|
|
574
|
+
_enqueueJournalOp(sessionId, { kind: 'remove', path: turnJournalReadPath(sessionId), settle });
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// ── Header publish + journal open vs. the orphan sweep ──────────────────────
|
|
579
|
+
// A journal is only read through its header, so one without a header is
|
|
580
|
+
// removable. The sweep checks "no header" and then unlinks; a turn in another
|
|
581
|
+
// process publishing its header and opening its journal in between would
|
|
582
|
+
// lose the opening record, and its later appends would never replay. Both are
|
|
583
|
+
// therefore done under one per-session cross-process lock (file-lock.mjs).
|
|
584
|
+
const CHECKPOINT_LOCK_WAIT_MS = 200;
|
|
585
|
+
|
|
586
|
+
export function checkpointLockPath(sessionId) {
|
|
587
|
+
return join(checkpointDir(), `${assertSessionId(sessionId)}.lock`);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/** Run `fn` (a header publish plus journal open) under the session's
|
|
591
|
+
* checkpoint lock. A turn never fails or stalls on the lock: it waits at most
|
|
592
|
+
* CHECKPOINT_LOCK_WAIT_MS (holders keep it for one existence check and one
|
|
593
|
+
* unlink), and when the lock cannot be taken at all `fn` runs without it —
|
|
594
|
+
* its own errors still propagate. */
|
|
595
|
+
export function withCheckpointOpenLock(sessionId, fn) {
|
|
596
|
+
let ran = false;
|
|
597
|
+
try {
|
|
598
|
+
return withFileLockSync(
|
|
599
|
+
checkpointLockPath(sessionId),
|
|
600
|
+
() => {
|
|
601
|
+
ran = true;
|
|
602
|
+
return fn();
|
|
603
|
+
},
|
|
604
|
+
{ timeoutMs: CHECKPOINT_LOCK_WAIT_MS }
|
|
605
|
+
);
|
|
606
|
+
} catch (error) {
|
|
607
|
+
if (ran) throw error;
|
|
608
|
+
return fn();
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Remove `sessionId`'s journal when no header stands behind it. A journal is
|
|
614
|
+
* only ever read through its header, so one without it can never be
|
|
615
|
+
* recovered; it is a removal that failed once (or was cut short by a restart)
|
|
616
|
+
* and still holds whatever it held. The header check and the unlink run under
|
|
617
|
+
* the session's checkpoint lock, so a turn cannot publish its header and open
|
|
618
|
+
* its journal between them (withCheckpointOpenLock). A busy lock skips the
|
|
619
|
+
* session instead of waiting, and so does a write lane still active for it
|
|
620
|
+
* (that process is removing or recording it itself). A header being set aside
|
|
621
|
+
* counts as present (readTurnCheckpointHeaderState). True when it removed one.
|
|
622
|
+
*/
|
|
623
|
+
export function removeOrphanTurnJournal(sessionId) {
|
|
624
|
+
const journalPath = turnJournalReadPath(sessionId);
|
|
625
|
+
if (!existsSync(journalPath)) return false;
|
|
626
|
+
try {
|
|
627
|
+
return withFileLockSync(
|
|
628
|
+
checkpointLockPath(sessionId),
|
|
629
|
+
() => {
|
|
630
|
+
if (readTurnCheckpointHeaderState(sessionId).present) return false;
|
|
631
|
+
if (_journalWrites.has(sessionId)) return false;
|
|
632
|
+
try {
|
|
633
|
+
unlinkSync(journalPath);
|
|
634
|
+
} catch (error) {
|
|
635
|
+
if (error?.code === 'ENOENT') return false;
|
|
636
|
+
throw error;
|
|
637
|
+
}
|
|
638
|
+
return true;
|
|
639
|
+
},
|
|
640
|
+
{ timeoutMs: 0 }
|
|
641
|
+
);
|
|
642
|
+
} catch {
|
|
643
|
+
// Contended or unremovable: the next recovery or start tries again.
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/** removeOrphanTurnJournal for every primary journal in the checkpoint
|
|
649
|
+
* directory (set-aside journals, `<id>.unrecovered-<key>.jsonl`, are not
|
|
650
|
+
* primary). Returns how many were removed. */
|
|
651
|
+
export function removeOrphanTurnJournals() {
|
|
652
|
+
let names;
|
|
653
|
+
try {
|
|
654
|
+
names = readdirSync(checkpointDirPath());
|
|
655
|
+
} catch {
|
|
656
|
+
return 0;
|
|
657
|
+
}
|
|
658
|
+
let removed = 0;
|
|
659
|
+
for (const name of names) {
|
|
660
|
+
const match = /^([A-Za-z0-9_-]+)\.jsonl$/.exec(name);
|
|
661
|
+
if (match && removeOrphanTurnJournal(match[1])) removed += 1;
|
|
662
|
+
}
|
|
663
|
+
return removed;
|
|
405
664
|
}
|
|
406
665
|
|
|
407
666
|
/** Test/shutdown helper: resolve once the write lane for a session is idle. */
|
|
@@ -492,15 +751,16 @@ function applyInterruptionDelta(state, delta) {
|
|
|
492
751
|
/** Merge the append-only journal onto its header. Returns the header unchanged
|
|
493
752
|
* when no journal exists, when the journal belongs to another turn, or when it
|
|
494
753
|
* has no usable head record. */
|
|
495
|
-
export function replayTurnJournal(header) {
|
|
754
|
+
export function replayTurnJournal(header, journalPath = header ? turnJournalReadPath(header.sessionId) : null) {
|
|
496
755
|
if (!header) return null;
|
|
497
756
|
let raw;
|
|
498
757
|
try {
|
|
499
|
-
raw = readFileSync(
|
|
758
|
+
raw = readFileSync(journalPath, 'utf8');
|
|
500
759
|
} catch {
|
|
501
760
|
return header;
|
|
502
761
|
}
|
|
503
762
|
let messages = header.turnMessages.slice();
|
|
763
|
+
let sealed = header.sealed === true;
|
|
504
764
|
let fullTranscript = header.fullTranscript === true;
|
|
505
765
|
let contextState = header.contextState ?? null;
|
|
506
766
|
const state = interruptionStateFromSnapshot(header.interruption);
|
|
@@ -545,7 +805,11 @@ export function replayTurnJournal(header) {
|
|
|
545
805
|
if (Number.isInteger(index) && index >= 0 && index <= messages.length) {
|
|
546
806
|
messages[index] = record.m;
|
|
547
807
|
}
|
|
808
|
+
} else if (record.t === 'seal') {
|
|
809
|
+
sealed = true;
|
|
548
810
|
} else if (record.t === 'full' && Array.isArray(record.ms)) {
|
|
811
|
+
// Legacy journals (before checkpoints named their base) replaced the
|
|
812
|
+
// whole context this way; read them so they are not misread as a turn.
|
|
549
813
|
messages = record.ms.slice();
|
|
550
814
|
fullTranscript = true;
|
|
551
815
|
} else if (record.t === 'len') {
|
|
@@ -565,6 +829,7 @@ export function replayTurnJournal(header) {
|
|
|
565
829
|
return {
|
|
566
830
|
...header,
|
|
567
831
|
turnMessages: messages,
|
|
832
|
+
...(sealed ? { sealed: true } : {}),
|
|
568
833
|
...(fullTranscript ? { fullTranscript: true } : {}),
|
|
569
834
|
interruption: interruptionSnapshotFromState(state),
|
|
570
835
|
contextState,
|
|
@@ -573,13 +838,14 @@ export function replayTurnJournal(header) {
|
|
|
573
838
|
}
|
|
574
839
|
|
|
575
840
|
/**
|
|
576
|
-
* Per-turn delta encoder. Holds the message refs already journaled (by
|
|
577
|
-
*
|
|
578
|
-
*
|
|
841
|
+
* Per-turn delta encoder. Holds the message refs already journaled (by index
|
|
842
|
+
* relative to the header's first message, `offset` in the working transcript)
|
|
843
|
+
* plus an opaque interruption cursor, so each encode() touches only what
|
|
844
|
+
* changed since the previous flush.
|
|
579
845
|
*/
|
|
580
846
|
export function createTurnJournalEncoder({ turnToken, sessionId = null } = {}) {
|
|
581
847
|
let journaled = [];
|
|
582
|
-
let
|
|
848
|
+
let offset = 0;
|
|
583
849
|
let cursor = null;
|
|
584
850
|
let contextStateKey = null;
|
|
585
851
|
// Per-turn record sequence. It is assigned where records are produced, so
|
|
@@ -620,16 +886,15 @@ export function createTurnJournalEncoder({ turnToken, sessionId = null } = {}) {
|
|
|
620
886
|
return out;
|
|
621
887
|
};
|
|
622
888
|
return {
|
|
623
|
-
/** Seed from
|
|
889
|
+
/** Seed from `turnOutgoing[offset..]`; returns the header's messages
|
|
624
890
|
* and the journal's opening lines (head record, plus — defensively —
|
|
625
891
|
* the delta that makes the journal authoritative if the tracker already
|
|
626
892
|
* carried state at open time). `snapshot` is the tracker snapshot the
|
|
627
893
|
* header stores; `epoch` names the journal a rewritten header starts. */
|
|
628
|
-
seed(turnOutgoing,
|
|
894
|
+
seed(turnOutgoing, interruption, contextState = null, { offset: from = 0, snapshot = null, epoch = 0 } = {}) {
|
|
629
895
|
const source = Array.isArray(turnOutgoing) ? turnOutgoing : [];
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
journaled = fullTranscript ? source.slice() : source.slice(start);
|
|
896
|
+
offset = from;
|
|
897
|
+
journaled = source.slice(offset);
|
|
633
898
|
mirror = sensitiveValuesVersion(sessionId)
|
|
634
899
|
? {
|
|
635
900
|
raw: interruptionStateFromSnapshot(snapshot),
|
|
@@ -646,40 +911,25 @@ export function createTurnJournalEncoder({ turnToken, sessionId = null } = {}) {
|
|
|
646
911
|
openLines.push(line({ t: 'x', d: seededDelta, at: Date.now() }));
|
|
647
912
|
return {
|
|
648
913
|
turnMessages: journaled.map(checkpointMessage),
|
|
649
|
-
fullTranscript,
|
|
650
914
|
contextState,
|
|
651
915
|
openLines,
|
|
652
916
|
};
|
|
653
917
|
},
|
|
654
|
-
|
|
918
|
+
/** Lines for what changed since the previous flush; `sealed` adds the
|
|
919
|
+
* record that marks the turn finished. */
|
|
920
|
+
encode({ turnOutgoing, interruption, contextState = null, sealed = false }) {
|
|
655
921
|
const source = Array.isArray(turnOutgoing) ? turnOutgoing : [];
|
|
656
|
-
const start = findTurnStart(source, currentUserContent);
|
|
657
922
|
const records = [];
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
ms: journaled.map(stored),
|
|
669
|
-
});
|
|
670
|
-
} else {
|
|
671
|
-
const offset = fullTranscript ? 0 : start;
|
|
672
|
-
const length = offset >= 0 ? source.length - offset : 0;
|
|
673
|
-
for (let index = 0; index < length; index += 1) {
|
|
674
|
-
const raw = source[offset + index];
|
|
675
|
-
if (journaled[index] === raw) continue;
|
|
676
|
-
journaled[index] = raw;
|
|
677
|
-
records.push({ t: 'm', i: index, m: stored(raw) });
|
|
678
|
-
}
|
|
679
|
-
if (journaled.length > length) {
|
|
680
|
-
journaled.length = length;
|
|
681
|
-
records.push({ t: 'len', n: length });
|
|
682
|
-
}
|
|
923
|
+
const length = Math.max(0, source.length - offset);
|
|
924
|
+
for (let index = 0; index < length; index += 1) {
|
|
925
|
+
const raw = source[offset + index];
|
|
926
|
+
if (journaled[index] === raw) continue;
|
|
927
|
+
journaled[index] = raw;
|
|
928
|
+
records.push({ t: 'm', i: index, m: stored(raw) });
|
|
929
|
+
}
|
|
930
|
+
if (journaled.length > length) {
|
|
931
|
+
journaled.length = length;
|
|
932
|
+
records.push({ t: 'len', n: length });
|
|
683
933
|
}
|
|
684
934
|
if (typeof interruption?.journalDelta === 'function') {
|
|
685
935
|
const next = interruption.journalDelta(cursor);
|
|
@@ -692,6 +942,7 @@ export function createTurnJournalEncoder({ turnToken, sessionId = null } = {}) {
|
|
|
692
942
|
contextStateKey = nextContextStateKey;
|
|
693
943
|
records.push({ t: 'c', c: contextState });
|
|
694
944
|
}
|
|
945
|
+
if (sealed) records.push({ t: 'seal' });
|
|
695
946
|
if (records.length === 0) return [];
|
|
696
947
|
// One timestamp per batch keeps `updatedAt` fresh for the
|
|
697
948
|
// newer-than-session recovery guard without a per-record cost.
|