mixdog 1.4.1 → 1.4.2
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 +3 -2
- package/scripts/build-shipped-history.mjs +115 -33
- package/scripts/gate-local.mjs +172 -61
- package/scripts/lib/gate-scheduler.mjs +138 -32
- package/scripts/lib/gate-timing-reporter.mjs +16 -0
- package/scripts/lib/run-node-tests.mjs +54 -29
- package/scripts/lib/scoped-temp.mjs +1 -0
- package/scripts/lib/test-failure-records.mjs +14 -1
- package/scripts/lib/test-failure-summary.mjs +16 -4
- package/scripts/lib/test-spawn-ledger.mjs +12 -3
- package/scripts/pptx-kit-index.mjs +5 -6
- package/scripts/suite-health.mjs +110 -28
- package/scripts/test.mjs +9 -14
- package/src/agents/advisor/AGENT.md +4 -4
- package/src/agents/advisor/agent.json +1 -1
- package/src/agents/explore/AGENT.md +22 -0
- package/src/agents/explore/agent.json +6 -0
- package/src/cli.mjs +7 -0
- package/src/defaults/agents.json +7 -0
- package/src/defaults/shipped-history.json +18 -2
- package/src/defaults/skills/browser-use/SKILL.md +4 -1
- package/src/defaults/skills/code-tidy/SKILL.md +71 -102
- package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
- package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
- package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
- package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
- package/src/defaults/skills/computer-use/SKILL.md +12 -2
- package/src/defaults/skills/goal-management/SKILL.md +1 -1
- package/src/defaults/skills/setup/references/actions.md +12 -6
- package/src/defaults/skills/setup/references/surfaces.md +2 -2
- package/src/defaults/skills/setup-guide/SKILL.md +5 -2
- package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
- package/src/defaults/skills/skill-creator/SKILL.md +85 -190
- package/src/defaults/skills/skill-creator/references/design.md +70 -162
- package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
- package/src/defaults/skills/test-suite/SKILL.md +194 -0
- package/src/defaults/skills/test-suite/references/case-management.md +51 -0
- package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
- package/src/defaults/skills/test-suite/references/reliability.md +49 -0
- package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
- package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
- package/src/defaults/skills/visualize/SKILL.md +6 -2
- package/src/lib/rules-builder.cjs +6 -30
- package/src/rules/lead/LEAD.md +3 -3
- package/src/rules/shared/20-research.md +3 -6
- package/src/rules/shared/40-editing.md +1 -0
- package/src/rules/shared/60-verification.md +10 -9
- package/src/rules/shared/70-delivery.md +4 -3
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +130 -21
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
- package/src/runtime/agent/orchestrator/config.mjs +12 -7
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +7 -7
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
- package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
- package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +83 -52
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +43 -8
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +30 -28
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
- package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +48 -31
- package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +26 -28
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/registry.mjs +33 -2
- package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +2 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
- package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +94 -10
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +10 -5
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +5 -5
- package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +11 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +21 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
- package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +3 -4
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +25 -14
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +17 -14
- package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +47 -29
- package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +243 -38
- package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
- package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +36 -26
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +3 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
- package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
- package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
- package/src/runtime/channels/lib/whisper-port.mjs +19 -10
- package/src/runtime/channels/lib/whisper-server.mjs +10 -1
- package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
- package/src/runtime/connectors/gmail-app-password.mjs +3 -3
- package/src/runtime/explore/explorer.mjs +93 -0
- package/src/runtime/explore/tool-defs.mjs +35 -0
- package/src/runtime/explore/tool.mjs +123 -0
- package/src/runtime/memory/index.mjs +4 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
- package/src/runtime/memory/lib/embedding-worker.mjs +4 -0
- package/src/runtime/office/quality/assurance-structure-docx.mjs +1 -1
- package/src/runtime/shared/agent-route-config.mjs +17 -2
- package/src/runtime/shared/atomic-file.mjs +103 -2
- package/src/runtime/shared/background-tasks.mjs +15 -6
- package/src/runtime/shared/connector-accounts.mjs +5 -5
- package/src/runtime/shared/connector-catalog.d.mts +11 -4
- package/src/runtime/shared/connector-catalog.mjs +60 -52
- package/src/runtime/shared/custom-provider-keys.mjs +20 -0
- package/src/runtime/shared/file-has-data.mjs +26 -2
- package/src/runtime/shared/file-lock.mjs +13 -15
- package/src/runtime/shared/git-launch.d.mts +9 -0
- package/src/runtime/shared/git-launch.mjs +213 -0
- package/src/runtime/shared/git-runner-worker.mjs +2 -1
- package/src/runtime/shared/llm/cost.mjs +6 -1
- package/src/runtime/shared/llm/http-agent.mjs +313 -29
- package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
- package/src/runtime/shared/llm/model-catalog.mjs +46 -6
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
- package/src/runtime/shared/llm/system-proxy.mjs +452 -0
- package/src/runtime/shared/provider-accounts.mjs +52 -2
- package/src/runtime/shared/staged-install-worker.mjs +5 -0
- package/src/runtime/shared/staged-update.mjs +47 -2
- package/src/runtime/shared/tool-surface.mjs +110 -36
- package/src/runtime/shared/tool-work-units.mjs +4 -0
- package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
- package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
- package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
- package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
- package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
- package/src/runtime/shared/worktrees/git.mjs +57 -0
- package/src/runtime/shared/worktrees/manager.mjs +205 -137
- package/src/runtime/tidy/install.mjs +95 -15
- package/src/runtime/tidy/languages.mjs +17 -15
- package/src/runtime/tidy/process.mjs +7 -4
- package/src/runtime/tidy/resolve.mjs +19 -16
- package/src/runtime/tidy/runners/biome.mjs +3 -3
- package/src/runtime/tidy/tool.mjs +1 -1
- package/src/runtime/tidy/worktree.mjs +10 -6
- package/src/runtime/visualize-bridge/client.mjs +4 -1
- package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
- package/src/runtime/web-search/index.mjs +1 -1
- package/src/session-runtime/agent-task-sessions.mjs +410 -353
- package/src/session-runtime/boot/tools.mjs +2 -0
- package/src/session-runtime/bot-status.mjs +103 -103
- package/src/session-runtime/connector-merge-migration.mjs +18 -15
- package/src/session-runtime/conversation-import/discovery.mjs +159 -40
- package/src/session-runtime/conversation-import/service.mjs +9 -3
- package/src/session-runtime/handoff-attachments.mjs +1 -1
- package/src/session-runtime/held-requests.mjs +3 -1
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +143 -116
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
- package/src/session-runtime/lifecycle/teardown.mjs +1 -1
- package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
- package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
- package/src/session-runtime/mixy-heartbeat.mjs +1 -1
- package/src/session-runtime/provider-auth/login.mjs +2 -2
- package/src/session-runtime/resource-mcp-api.mjs +98 -35
- package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
- package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
- package/src/session-runtime/resource-mcp-servers.mjs +5 -4
- package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
- package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
- package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
- package/src/session-runtime/services/daemon-candidate.mjs +4 -1
- package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
- package/src/session-runtime/services/plugin-admin.mjs +10 -6
- package/src/session-runtime/services/provider-admin.mjs +281 -10
- package/src/session-runtime/session-turn-api.mjs +2 -0
- package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
- package/src/session-runtime/settings-system-api.mjs +1 -1
- package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
- package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
- package/src/session-runtime/setup-tool/executor.mjs +6 -2
- package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
- package/src/session-runtime/turn/agent-ops.mjs +23 -0
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +10 -1
- package/src/session-runtime/workspace/activity-guards.mjs +176 -0
- package/src/session-runtime/workspace/session-view.mjs +149 -0
- package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
- package/src/session-runtime/workspace/transition-runner.mjs +188 -0
- package/src/session-runtime/workspace/transitions.mjs +108 -1066
- package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
- package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
- package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
- package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
- package/src/session-runtime/workspace/worktree-tool.mjs +54 -54
- package/src/standalone/daemon-desktop-runtime.mjs +2 -0
- package/src/standalone/daemon-session-control.mjs +151 -136
- package/src/standalone/daemon.mjs +7 -0
- package/src/standalone/desktop-service-registry.mjs +9 -0
- package/src/standalone/local-session-runtime.mjs +5 -1
- package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
- package/src/standalone/session-client.mjs +5 -1
- package/src/standalone/session-frame-stream.mjs +83 -11
- package/src/standalone/session-protocol.mjs +4 -0
- package/src/standalone/session-service/agent-tree.mjs +45 -9
- package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
- package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
- package/src/standalone/session-service/stored-reader.mjs +127 -40
- package/src/tui/App.jsx +154 -164
- package/src/tui/app/app-view/floating-panel.jsx +3 -0
- package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
- package/src/tui/app/clipboard.mjs +21 -10
- package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
- package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
- package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
- package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
- package/src/tui/app/provider-setup/oauth-actions.mjs +25 -6
- package/src/tui/app/provider-setup-picker.mjs +20 -13
- package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
- package/src/tui/app/slash-commands.mjs +1 -0
- package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
- package/src/tui/app/slash-dispatch.mjs +1 -1
- package/src/tui/app/use-btw-panel.mjs +29 -0
- package/src/tui/app/use-context-panel-refresh.mjs +31 -0
- package/src/tui/app/use-model-catalog-cache.mjs +13 -10
- package/src/tui/app/use-mouse-input.mjs +11 -0
- package/src/tui/app/use-onboarding-start.mjs +8 -3
- package/src/tui/app/use-panel-transition.mjs +10 -1
- package/src/tui/app/use-pasted-buffers.mjs +28 -20
- package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
- package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
- package/src/tui/app/use-prompt-queue-history.mjs +3 -2
- package/src/tui/app/use-selection-focus.mjs +11 -2
- package/src/tui/app/use-selection-geometry.mjs +18 -9
- package/src/tui/app/use-selection-paint.mjs +4 -4
- package/src/tui/app/use-selection-stitch.mjs +3 -3
- package/src/tui/app/use-slash-palette.mjs +62 -0
- package/src/tui/app/use-terminal-chrome.mjs +2 -2
- package/src/tui/app/use-transcript-activity.mjs +47 -32
- package/src/tui/app/use-transcript-follow.mjs +12 -4
- package/src/tui/app/use-transcript-scroll.mjs +46 -17
- package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
- package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
- package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
- package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
- package/src/tui/app/use-transcript-window.mjs +21 -11
- package/src/tui/app/use-tui-ready.mjs +18 -0
- package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
- package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
- package/src/tui/components/Picker.jsx +27 -3
- package/src/tui/components/SignInUrl.jsx +68 -0
- package/src/tui/components/TextEntryPanel.jsx +76 -46
- package/src/tui/components/sign-in-url-layout.mjs +24 -0
- package/src/tui/dist/index.mjs +1896 -1341
- package/src/tui/session/goal-turn-state.mjs +6 -2
- package/src/tui/session/session-api/controls.mjs +4 -0
- package/src/tui/session/session-api/extensions.mjs +8 -0
- package/src/tui/session/session-api/intake/abort.mjs +2 -1
- package/src/tui/session/session-api/intake.mjs +2 -0
- package/src/tui/session/session-api/integrations.mjs +2 -2
- package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
- package/src/tui/session/session-api/settings.mjs +2 -2
- package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
- package/src/tui/session/transcript-spill.mjs +92 -4
- package/src/tui/session/transcript-store.mjs +2 -0
- package/src/defaults/skills/skill-creator/references/source-review.md +0 -94
|
@@ -6,60 +6,26 @@ import { SESSION_READ_ACTION_SET } from '../../session-protocol.mjs';
|
|
|
6
6
|
import { SESSION_ID_PATTERN } from '../agent-tree.mjs';
|
|
7
7
|
import {
|
|
8
8
|
loadLiveTranscriptHead,
|
|
9
|
+
readTranscriptHistory,
|
|
9
10
|
readTranscriptRange,
|
|
10
11
|
rebudgetTranscriptWindow,
|
|
12
|
+
requestedTranscriptHistory,
|
|
11
13
|
requestedHeldTranscript,
|
|
12
14
|
requestedTranscriptWindow,
|
|
13
15
|
requestLiveTranscriptWindow,
|
|
16
|
+
restartTranscriptWindow,
|
|
17
|
+
transcriptAnswerBounds,
|
|
14
18
|
transcriptPageBody,
|
|
19
|
+
transcriptWindowOversized,
|
|
15
20
|
} from '../projection/transcript-window.mjs';
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const {
|
|
25
|
-
subscriberToken,
|
|
26
|
-
addSubscriber,
|
|
27
|
-
trackPendingViewer,
|
|
28
|
-
dropPendingViewer,
|
|
29
|
-
adoptPendingViewers,
|
|
30
|
-
notePrependViewer,
|
|
31
|
-
} = ctx.viewers;
|
|
32
|
-
const { sessionBusy, retainUnwatched, releaseProjection, startEvictionSweep } = ctx.retention;
|
|
33
|
-
const {
|
|
34
|
-
assertAvailable,
|
|
35
|
-
createEntry,
|
|
36
|
-
getOrCreateSessionEntry,
|
|
37
|
-
entryForSession,
|
|
38
|
-
liveEntryForView,
|
|
39
|
-
bindExternalSessionView,
|
|
40
|
-
destroy,
|
|
41
|
-
} = ctx.entries;
|
|
42
|
-
const {
|
|
43
|
-
storedSessionProjection,
|
|
44
|
-
storedProjectionUnchanged,
|
|
45
|
-
requestedMessageSlice,
|
|
46
|
-
forgetStoredSession,
|
|
47
|
-
releaseStoredSession,
|
|
48
|
-
} = ctx.storedReader;
|
|
49
|
-
|
|
50
|
-
/** A read/subscribe names the transcript window its view wants (none: the
|
|
51
|
-
* whole transcript). A window grown past what a resumed runtime holds is
|
|
52
|
-
* completed from durable history, which a live session never keeps cached. */
|
|
53
|
-
async function applyTranscriptRequest(entry, params, sessionId) {
|
|
54
|
-
const request = requestedTranscriptWindow(params);
|
|
55
|
-
// A model-message read (messageStart) is not a view and leaves the window.
|
|
56
|
-
if (request || !Number.isInteger(params.messageStart)) requestLiveTranscriptWindow(entry, request);
|
|
57
|
-
// No baseline the next step can patch: this caller receives the window
|
|
58
|
-
// whole, so it goes out within its byte budget.
|
|
59
|
-
if (request && params.baseRevision !== entry.revision) rebudgetTranscriptWindow(entry);
|
|
60
|
-
if (await loadLiveTranscriptHead(entry, sessionId, readStoredSession)) forgetStoredSession(sessionId);
|
|
61
|
-
assertAvailable(entry);
|
|
62
|
-
}
|
|
22
|
+
/** Creating (reserving) a session. */
|
|
23
|
+
function createSessionCreation(ctx) {
|
|
24
|
+
const { log, sessionResult, advanceForCaller } = ctx;
|
|
25
|
+
const { currentSessionId } = ctx.projection;
|
|
26
|
+
const { addSubscriber, adoptPendingViewers } = ctx.viewers;
|
|
27
|
+
const { retainUnwatched } = ctx.retention;
|
|
28
|
+
const { assertAvailable, createEntry, getOrCreateSessionEntry, destroy } = ctx.entries;
|
|
63
29
|
|
|
64
30
|
async function createReservedSession(params, viewer, reservedSessionId) {
|
|
65
31
|
const entry = await createEntry({ ...params, sessionId: reservedSessionId }, viewer);
|
|
@@ -103,6 +69,138 @@ export function createSessionViewCalls(ctx) {
|
|
|
103
69
|
return sessionResult(entry, step);
|
|
104
70
|
}
|
|
105
71
|
|
|
72
|
+
return { createSession };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The transcript window a live view is answered through: requesting it,
|
|
76
|
+
* bounding the answer, and attaching a subscriber to a live entry. */
|
|
77
|
+
function createLiveTranscriptAnswers(ctx) {
|
|
78
|
+
const { readStoredSession, sessionResult, advanceForCaller } = ctx;
|
|
79
|
+
const { currentSessionId } = ctx.projection;
|
|
80
|
+
const { addSubscriber } = ctx.viewers;
|
|
81
|
+
const { assertAvailable } = ctx.entries;
|
|
82
|
+
|
|
83
|
+
/** A read/subscribe names the transcript window its view wants (none: the
|
|
84
|
+
* whole transcript). A window grown past what a resumed runtime holds is
|
|
85
|
+
* completed from durable history, sliced from the store's cached display
|
|
86
|
+
* history of the record's content. */
|
|
87
|
+
async function applyTranscriptRequest(entry, params, sessionId) {
|
|
88
|
+
const request = requestedTranscriptWindow(params);
|
|
89
|
+
// A model-message read (messageStart) is not a view and leaves the window.
|
|
90
|
+
if (request || !Number.isInteger(params.messageStart)) requestLiveTranscriptWindow(entry, request);
|
|
91
|
+
// No baseline the next step can patch: this caller receives the window
|
|
92
|
+
// whole, so it goes out within its byte budget.
|
|
93
|
+
if (request && params.baseRevision !== entry.revision) rebudgetTranscriptWindow(entry);
|
|
94
|
+
await loadLiveTranscriptHead(entry, sessionId, readStoredSession);
|
|
95
|
+
assertAvailable(entry);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The caller's answer for `step`, reduced to a page when it proves the rows
|
|
99
|
+
* it holds and they fit one answer. A window that would go out whole past
|
|
100
|
+
* the request's bounds (transcriptAnswerBounds) does not: a caller already
|
|
101
|
+
* at this revision needs no body; any other gets a bounded recovery
|
|
102
|
+
* snapshot — the window restarts from a fresh tail, which the views
|
|
103
|
+
* attached receive as a step too. */
|
|
104
|
+
function boundedSessionResult(entry, step, params, baseRevision, extra) {
|
|
105
|
+
const prepend = params.transcriptPrepend === true;
|
|
106
|
+
const bounds = transcriptAnswerBounds(requestedTranscriptWindow(params));
|
|
107
|
+
const result = transcriptPageBody(
|
|
108
|
+
sessionResult(entry, step, baseRevision, extra, { prepend }),
|
|
109
|
+
requestedHeldTranscript(params),
|
|
110
|
+
{ byteBudget: bounds.byteBudget }
|
|
111
|
+
);
|
|
112
|
+
if (!transcriptWindowOversized(entry, result.full, bounds)) return result;
|
|
113
|
+
if (baseRevision === step.revision) {
|
|
114
|
+
const { full: _full, ...bodiless } = result;
|
|
115
|
+
return bodiless;
|
|
116
|
+
}
|
|
117
|
+
restartTranscriptWindow(entry, bounds.maxItems);
|
|
118
|
+
return sessionResult(entry, advanceForCaller(entry), baseRevision, extra, { prepend });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Publish BEFORE attaching: the views already there get the frame, the new
|
|
122
|
+
* one gets the same revision once in its reply. */
|
|
123
|
+
const subscribeLive = async (entry, params, viewer, baseRevision) => {
|
|
124
|
+
await applyTranscriptRequest(entry, params, currentSessionId(entry) || String(params.sessionId || ''));
|
|
125
|
+
const prepend = params.transcriptPrepend === true;
|
|
126
|
+
let step = advanceForCaller(entry);
|
|
127
|
+
// Bounded like a read (boundedSessionResult), before attaching: the new
|
|
128
|
+
// view receives the restarted tail once, in its reply.
|
|
129
|
+
const bounds = transcriptAnswerBounds(requestedTranscriptWindow(params));
|
|
130
|
+
if (transcriptWindowOversized(entry, sessionResult(entry, step, baseRevision, {}, { prepend }).full, bounds)) {
|
|
131
|
+
restartTranscriptWindow(entry, bounds.maxItems);
|
|
132
|
+
step = advanceForCaller(entry);
|
|
133
|
+
}
|
|
134
|
+
addSubscriber(entry, viewer);
|
|
135
|
+
return sessionResult(entry, step, baseRevision, { subscribed: true }, { prepend });
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return { applyTranscriptRequest, boundedSessionResult, subscribeLive };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Detaching a view from a live or cold session. */
|
|
142
|
+
function createSessionUnsubscribe(ctx) {
|
|
143
|
+
const { log, externalViewEntries } = ctx;
|
|
144
|
+
const { externalEntryForView, sessionOwner } = ctx.projection;
|
|
145
|
+
const { subscriberToken, dropPendingViewer } = ctx.viewers;
|
|
146
|
+
const { sessionBusy, releaseProjection, startEvictionSweep } = ctx.retention;
|
|
147
|
+
const { destroy } = ctx.entries;
|
|
148
|
+
const { releaseStoredSession } = ctx.storedReader;
|
|
149
|
+
|
|
150
|
+
async function unsubscribeSession({ sessionId } = {}, viewer = null) {
|
|
151
|
+
const id = String(sessionId || '');
|
|
152
|
+
if (!id) throw new TypeError('sessionId is required');
|
|
153
|
+
const coldViewers = dropPendingViewer(id, viewer);
|
|
154
|
+
const owner = sessionOwner(id);
|
|
155
|
+
if (!owner) {
|
|
156
|
+
const external = externalEntryForView(id);
|
|
157
|
+
if (external) {
|
|
158
|
+
const token = subscriberToken(viewer);
|
|
159
|
+
if (token) external.subscribers?.delete(token);
|
|
160
|
+
if ((external.subscribers?.size || 0) === 0) externalViewEntries.delete(id);
|
|
161
|
+
}
|
|
162
|
+
// The last cold view left: its disk projection turns idle, kept within
|
|
163
|
+
// the store's idle budget so a revisit skips the parse.
|
|
164
|
+
if (!coldViewers) releaseStoredSession(id);
|
|
165
|
+
return { sessionId: id, unsubscribed: true };
|
|
166
|
+
}
|
|
167
|
+
const entry = owner;
|
|
168
|
+
const token = subscriberToken(viewer);
|
|
169
|
+
if (token) entry.subscribers?.delete(token);
|
|
170
|
+
if ((entry.subscribers?.size || 0) === 0) {
|
|
171
|
+
entry.transcriptView = null;
|
|
172
|
+
if (entry.reservedOnly && !sessionBusy(entry)) {
|
|
173
|
+
await destroy(entry, 'unclaimed session reservation', {
|
|
174
|
+
keepBackgroundWork: true,
|
|
175
|
+
});
|
|
176
|
+
return { sessionId: id, unsubscribed: true };
|
|
177
|
+
}
|
|
178
|
+
entry.retainedAt = Date.now();
|
|
179
|
+
releaseProjection(entry);
|
|
180
|
+
startEvictionSweep();
|
|
181
|
+
}
|
|
182
|
+
log(`session unsubscribed session=${id}${token ? ` client=${token}` : ''}`);
|
|
183
|
+
return { sessionId: id, unsubscribed: true };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { unsubscribeSession };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function createSessionViewCalls(ctx) {
|
|
190
|
+
const { readStoredSession, runSessionAction, advanceForCaller } = ctx;
|
|
191
|
+
const { externalEntryForView, projectionResult, unchangedProjectionResult, sessionOwner } = ctx.projection;
|
|
192
|
+
/** The owner a concurrent materialization may have produced while a disk
|
|
193
|
+
* read was in flight: a daemon-owned runtime outranks an external view. */
|
|
194
|
+
const lateOwnerFor = (id) => sessionOwner(id) || externalEntryForView(id);
|
|
195
|
+
const { trackPendingViewer, dropPendingViewer, notePrependViewer } = ctx.viewers;
|
|
196
|
+
const { retainUnwatched } = ctx.retention;
|
|
197
|
+
const { assertAvailable, entryForSession, liveEntryForView, bindExternalSessionView } = ctx.entries;
|
|
198
|
+
const { storedSessionProjection, storedProjectionUnchanged, storedPageOnly, requestedMessageSlice } =
|
|
199
|
+
ctx.storedReader;
|
|
200
|
+
const { createSession } = createSessionCreation(ctx);
|
|
201
|
+
const { applyTranscriptRequest, boundedSessionResult, subscribeLive } = createLiveTranscriptAnswers(ctx);
|
|
202
|
+
const { unsubscribeSession } = createSessionUnsubscribe(ctx);
|
|
203
|
+
|
|
106
204
|
async function liveSessionReadResult(entry, params, sessionId, baseRevision) {
|
|
107
205
|
assertAvailable(entry);
|
|
108
206
|
await applyTranscriptRequest(entry, params, sessionId);
|
|
@@ -110,11 +208,7 @@ export function createSessionViewCalls(ctx) {
|
|
|
110
208
|
retainUnwatched(entry, 'headless session read');
|
|
111
209
|
const messages = await requestedMessageSlice(params, sessionId);
|
|
112
210
|
assertAvailable(entry);
|
|
113
|
-
|
|
114
|
-
return transcriptPageBody(
|
|
115
|
-
sessionResult(entry, step, baseRevision, messages, { prepend }),
|
|
116
|
-
requestedHeldTranscript(params)
|
|
117
|
-
);
|
|
211
|
+
return boundedSessionResult(entry, step, params, baseRevision, messages);
|
|
118
212
|
}
|
|
119
213
|
|
|
120
214
|
/** The live entry a view can attach to: the owner, an external view, or a
|
|
@@ -146,6 +240,13 @@ export function createSessionViewCalls(ctx) {
|
|
|
146
240
|
assertAvailable(live);
|
|
147
241
|
return { range };
|
|
148
242
|
}
|
|
243
|
+
const historyRequest = requestedTranscriptHistory(params);
|
|
244
|
+
if (historyRequest) {
|
|
245
|
+
// Read-only: no window, baseline or revision of any view moves.
|
|
246
|
+
const history = await readTranscriptHistory(live, id, readStoredSession, historyRequest);
|
|
247
|
+
assertAvailable(live);
|
|
248
|
+
return { history };
|
|
249
|
+
}
|
|
149
250
|
if (live) {
|
|
150
251
|
return liveSessionReadResult(live, params, id, baseRevision);
|
|
151
252
|
}
|
|
@@ -162,7 +263,8 @@ export function createSessionViewCalls(ctx) {
|
|
|
162
263
|
const lateOwner = lateOwnerFor(id);
|
|
163
264
|
return lateOwner ? liveSessionReadResult(lateOwner, params, id, baseRevision) : held;
|
|
164
265
|
}
|
|
165
|
-
const
|
|
266
|
+
const heldRows = requestedHeldTranscript(params);
|
|
267
|
+
const projection = await storedSessionProjection(id, openHints, window, heldRows, baseProjectionStamp);
|
|
166
268
|
assertAvailable();
|
|
167
269
|
const lateOwner = lateOwnerFor(id);
|
|
168
270
|
if (lateOwner) {
|
|
@@ -179,28 +281,18 @@ export function createSessionViewCalls(ctx) {
|
|
|
179
281
|
});
|
|
180
282
|
const messages = await requestedMessageSlice(params, id);
|
|
181
283
|
assertAvailable();
|
|
182
|
-
|
|
284
|
+
// The stored reader already proved (or disproved) the held rows, and
|
|
285
|
+
// its projection then carries only the page above them.
|
|
286
|
+
const page = storedPageOnly(projection)
|
|
287
|
+
? transcriptPageBody(result, heldRows, { proven: true, byteBudget: window?.byteBudget, pageOnly: true })
|
|
288
|
+
: result;
|
|
289
|
+
return { ...page, ...messages };
|
|
183
290
|
}
|
|
184
291
|
// Embedders without a store reader keep the legacy load-on-read seam.
|
|
185
292
|
const entry = await entryForSession(id, openHints || {});
|
|
186
293
|
return liveSessionReadResult(entry, params, id, baseRevision);
|
|
187
294
|
}
|
|
188
295
|
|
|
189
|
-
/** Publish BEFORE attaching: the views already there get the frame, the new
|
|
190
|
-
* one gets the same revision once in its reply. */
|
|
191
|
-
const subscribeLive = async (entry, params, viewer, baseRevision) => {
|
|
192
|
-
await applyTranscriptRequest(entry, params, currentSessionId(entry) || String(params.sessionId || ''));
|
|
193
|
-
const step = advanceForCaller(entry);
|
|
194
|
-
addSubscriber(entry, viewer);
|
|
195
|
-
return sessionResult(
|
|
196
|
-
entry,
|
|
197
|
-
step,
|
|
198
|
-
baseRevision,
|
|
199
|
-
{ subscribed: true },
|
|
200
|
-
{ prepend: params.transcriptPrepend === true }
|
|
201
|
-
);
|
|
202
|
-
};
|
|
203
|
-
|
|
204
296
|
async function subscribeSession(params = {}, viewer = null) {
|
|
205
297
|
const { sessionId, open: openHints = {}, baseRevision = null } = params;
|
|
206
298
|
assertAvailable();
|
|
@@ -236,41 +328,5 @@ export function createSessionViewCalls(ctx) {
|
|
|
236
328
|
return subscribeLive(entry, params, viewer, baseRevision);
|
|
237
329
|
}
|
|
238
330
|
|
|
239
|
-
async function unsubscribeSession({ sessionId } = {}, viewer = null) {
|
|
240
|
-
const id = String(sessionId || '');
|
|
241
|
-
if (!id) throw new TypeError('sessionId is required');
|
|
242
|
-
const coldViewers = dropPendingViewer(id, viewer);
|
|
243
|
-
const owner = sessionOwner(id);
|
|
244
|
-
if (!owner) {
|
|
245
|
-
const external = externalEntryForView(id);
|
|
246
|
-
if (external) {
|
|
247
|
-
const token = subscriberToken(viewer);
|
|
248
|
-
if (token) external.subscribers?.delete(token);
|
|
249
|
-
if ((external.subscribers?.size || 0) === 0) externalViewEntries.delete(id);
|
|
250
|
-
}
|
|
251
|
-
// The last cold view left: its disk projection turns idle, kept within
|
|
252
|
-
// the store's idle budget so a revisit skips the parse.
|
|
253
|
-
if (!coldViewers) releaseStoredSession(id);
|
|
254
|
-
return { sessionId: id, unsubscribed: true };
|
|
255
|
-
}
|
|
256
|
-
const entry = owner;
|
|
257
|
-
const token = subscriberToken(viewer);
|
|
258
|
-
if (token) entry.subscribers?.delete(token);
|
|
259
|
-
if ((entry.subscribers?.size || 0) === 0) {
|
|
260
|
-
entry.transcriptView = null;
|
|
261
|
-
if (entry.reservedOnly && !sessionBusy(entry)) {
|
|
262
|
-
await destroy(entry, 'unclaimed session reservation', {
|
|
263
|
-
keepBackgroundWork: true,
|
|
264
|
-
});
|
|
265
|
-
return { sessionId: id, unsubscribed: true };
|
|
266
|
-
}
|
|
267
|
-
entry.retainedAt = Date.now();
|
|
268
|
-
releaseProjection(entry);
|
|
269
|
-
startEvictionSweep();
|
|
270
|
-
}
|
|
271
|
-
log(`session unsubscribed session=${id}${token ? ` client=${token}` : ''}`);
|
|
272
|
-
return { sessionId: id, unsubscribed: true };
|
|
273
|
-
}
|
|
274
|
-
|
|
275
331
|
return { createSession, readSession, subscribeSession, unsubscribeSession };
|
|
276
332
|
}
|
|
@@ -1,9 +1,72 @@
|
|
|
1
1
|
import { sanitizeForWire } from '../session-wire-values.mjs';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
clampStoredPage,
|
|
4
|
+
clampStoredWindow,
|
|
5
|
+
holdsTranscriptTail,
|
|
6
|
+
TRANSCRIPT_RESPONSE_MAX_ITEMS,
|
|
7
|
+
} from './projection/transcript-window.mjs';
|
|
3
8
|
|
|
4
9
|
const SLOW_STORED_PROJECTION_MS = 250;
|
|
5
10
|
const PROJECTED_FILE_LIMIT = 256;
|
|
6
11
|
|
|
12
|
+
// Every window of a stored session is a slice of the store's one cached
|
|
13
|
+
// display history, so its rows are the same objects page after page: each
|
|
14
|
+
// is sanitized once (keyed weakly — dropping the store's cache entry
|
|
15
|
+
// releases the clones) and a page costs a slice, not a whole-window clone.
|
|
16
|
+
const wireItems = new WeakMap();
|
|
17
|
+
function wireItem(item) {
|
|
18
|
+
if (!item || typeof item !== 'object') return sanitizeForWire(item, 2) ?? null;
|
|
19
|
+
let wire = wireItems.get(item);
|
|
20
|
+
if (wire === undefined) {
|
|
21
|
+
wire = sanitizeForWire(item, 2) ?? null;
|
|
22
|
+
wireItems.set(item, wire);
|
|
23
|
+
}
|
|
24
|
+
return wire;
|
|
25
|
+
}
|
|
26
|
+
// The store returns one object per unchanged window, so a repeated read
|
|
27
|
+
// reuses its wire clone (the 1s cold-view refresh would re-clone it).
|
|
28
|
+
const wireSnapshots = new WeakMap();
|
|
29
|
+
function wireSnapshot(snapshot) {
|
|
30
|
+
let wire = wireSnapshots.get(snapshot);
|
|
31
|
+
if (!wire) {
|
|
32
|
+
// The slice's own proof fields stay here.
|
|
33
|
+
const { items, heldProven: _heldProven, skippedFirstId: _skippedFirstId, ...fields } = snapshot;
|
|
34
|
+
wire = sanitizeForWire(fields);
|
|
35
|
+
if (Array.isArray(items)) wire = { ...wire, items: items.map(wireItem) };
|
|
36
|
+
wireSnapshots.set(snapshot, wire);
|
|
37
|
+
}
|
|
38
|
+
return wire;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// A slice's stamp is `<history stamp>/<slice>` (storedHistorySlice); an
|
|
42
|
+
// answer cut from it is served as `<slice stamp>#<rows delivered>`.
|
|
43
|
+
const historyOf = (stamp) => {
|
|
44
|
+
const window = stamp.includes('#') ? stamp.slice(0, stamp.lastIndexOf('#')) : stamp;
|
|
45
|
+
return window.includes('/') ? window.slice(0, window.lastIndexOf('/')) : window;
|
|
46
|
+
};
|
|
47
|
+
const deliveredOf = (stamp) => (stamp.includes('#') ? Number(stamp.slice(stamp.lastIndexOf('#') + 1)) : -1);
|
|
48
|
+
/** The stamp an answer of `delivered` rows is served under. Every answer cut
|
|
49
|
+
* from one display history is a tail of it, so the rows a caller received
|
|
50
|
+
* are named by that history and their count, byte-budget cuts included.
|
|
51
|
+
* A request that pages nothing is answered under the caller's own stamp
|
|
52
|
+
* (unchanged) when the tail it received already covers this whole answer —
|
|
53
|
+
* a host that paged deeper than this window keeps its rows on a refresh;
|
|
54
|
+
* any answer that would reveal more is served. */
|
|
55
|
+
function servedStamp(stamp, window, baseProjectionStamp, delivered) {
|
|
56
|
+
if (typeof stamp !== 'string' || !stamp.includes('/')) return stamp;
|
|
57
|
+
const own = `${stamp}#${delivered}`;
|
|
58
|
+
if (window?.paged || typeof baseProjectionStamp !== 'string') return own;
|
|
59
|
+
const covers = historyOf(baseProjectionStamp) === historyOf(own) && deliveredOf(baseProjectionStamp) >= delivered;
|
|
60
|
+
return covers ? baseProjectionStamp : own;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const itemLimitFor = (hints, window) => {
|
|
64
|
+
const requested = window ? window.limit : Number(hints?.resumeOptions?.transcriptItemLimit);
|
|
65
|
+
return Number.isFinite(requested) && requested > 0 ? requested : 512;
|
|
66
|
+
};
|
|
67
|
+
const projectedKey = (sessionId, hints, window) =>
|
|
68
|
+
`${sessionId}|${itemLimitFor(hints, window)}:${window?.byteBudget ?? ''}:${window?.pageBase ?? ''}:${window?.paged ? 'p' : ''}`;
|
|
69
|
+
|
|
7
70
|
export function createStoredSessionReader({
|
|
8
71
|
readStoredSession,
|
|
9
72
|
readStoredGoal,
|
|
@@ -38,21 +101,9 @@ export function createStoredSessionReader({
|
|
|
38
101
|
// that guarantee once several 512-item views exceed its byte budget: it
|
|
39
102
|
// evicts each one just before its next refresh.
|
|
40
103
|
const projectedFiles = new Map();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// Keyed weakly: dropping the store's cache entry releases the clone too.
|
|
45
|
-
const wireSnapshots = new WeakMap();
|
|
46
|
-
function wireSnapshot(snapshot, window) {
|
|
47
|
-
const budget = `${window?.byteBudget ?? ''}:${window?.pageBase ?? ''}`;
|
|
48
|
-
let cached = wireSnapshots.get(snapshot);
|
|
49
|
-
if (!cached || cached.budget !== budget) {
|
|
50
|
-
const wire = cached?.wire ?? sanitizeForWire(snapshot);
|
|
51
|
-
cached = { wire, budget, value: budgetStoredWindow(wire, window) };
|
|
52
|
-
wireSnapshots.set(snapshot, cached);
|
|
53
|
-
}
|
|
54
|
-
return cached.value;
|
|
55
|
-
}
|
|
104
|
+
/** Projections that carry only the page above their caller's proven rows
|
|
105
|
+
* (storedPageOnly). */
|
|
106
|
+
const pageOnly = new WeakSet();
|
|
56
107
|
|
|
57
108
|
/** Drop every cold projection the store retains for this session. */
|
|
58
109
|
function forgetStoredProjection(sessionId) {
|
|
@@ -87,13 +138,6 @@ export function createStoredSessionReader({
|
|
|
87
138
|
);
|
|
88
139
|
}
|
|
89
140
|
|
|
90
|
-
const itemLimitFor = (hints, window) => {
|
|
91
|
-
const requested = window ? window.limit : Number(hints?.resumeOptions?.transcriptItemLimit);
|
|
92
|
-
return Number.isFinite(requested) && requested > 0 ? requested : 512;
|
|
93
|
-
};
|
|
94
|
-
const projectedKey = (sessionId, hints, window) =>
|
|
95
|
-
`${sessionId}|${itemLimitFor(hints, window)}:${window?.byteBudget ?? ''}:${window?.pageBase ?? ''}`;
|
|
96
|
-
|
|
97
141
|
async function fileStampOf(sessionId) {
|
|
98
142
|
if (typeof statStoredSession !== 'function') return null;
|
|
99
143
|
let stamp;
|
|
@@ -125,32 +169,68 @@ export function createStoredSessionReader({
|
|
|
125
169
|
}
|
|
126
170
|
|
|
127
171
|
/** `window` (from requestedTranscriptWindow) selects a paged tail; without
|
|
128
|
-
* one, the resume hint (default 512 items) keeps the legacy page.
|
|
129
|
-
|
|
172
|
+
* one, the resume hint (default 512 items) keeps the legacy page. A window
|
|
173
|
+
* answers within clampStoredWindow's bounds, above the rows `held`
|
|
174
|
+
* (requestedHeldTranscript) when the caller proves it holds them. */
|
|
175
|
+
async function storedSessionProjection(sessionId, hints, window = null, held = null, baseProjectionStamp = null) {
|
|
130
176
|
if (typeof readStoredSession !== 'function') return null;
|
|
131
177
|
const key = projectedKey(sessionId, hints, window);
|
|
132
178
|
// stat -> read -> stat: the projection is attributed to a file identity
|
|
133
179
|
// only when no write can have landed while it was read.
|
|
134
180
|
const fileStamp = await fileStampOf(sessionId);
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
181
|
+
const read = async (options) => {
|
|
182
|
+
try {
|
|
183
|
+
const value = await readStoredSession(sessionId, { trace: traceStoredProjectionRead, ...options });
|
|
184
|
+
return value && typeof value === 'object' ? value : null;
|
|
185
|
+
} catch (err) {
|
|
186
|
+
log(`stored session projection failed session=${sessionId}: ${err?.message || err}`);
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
const limit = itemLimitFor(hints, window);
|
|
191
|
+
const pagedHeld = window?.paged ? held : null;
|
|
192
|
+
// A paged caller's held rows are proven where the history is (by the
|
|
193
|
+
// history stamp they were served under, or their digest), at any depth:
|
|
194
|
+
// only the page above them crosses, never the rows held. Unproven, a
|
|
195
|
+
// fresh tail of at most one answer's rows.
|
|
196
|
+
const snapshot = await read({
|
|
197
|
+
transcriptItemLimit: window?.paged && !pagedHeld ? Math.min(limit, TRANSCRIPT_RESPONSE_MAX_ITEMS) : limit,
|
|
198
|
+
...(pagedHeld
|
|
199
|
+
? {
|
|
200
|
+
transcriptHeld: pagedHeld,
|
|
201
|
+
transcriptBaseHistory: typeof baseProjectionStamp === 'string' ? historyOf(baseProjectionStamp) : null,
|
|
202
|
+
transcriptPage: { maxRows: TRANSCRIPT_RESPONSE_MAX_ITEMS, byteBudget: window.byteBudget },
|
|
203
|
+
}
|
|
204
|
+
: {}),
|
|
205
|
+
paging: Boolean(pagedHeld),
|
|
206
|
+
});
|
|
207
|
+
if (!snapshot) return null;
|
|
208
|
+
const whole = wireSnapshot(snapshot);
|
|
209
|
+
let paged = Boolean(pagedHeld) && snapshot.heldProven === true;
|
|
210
|
+
let wire;
|
|
211
|
+
if (pagedHeld && snapshot.heldProven === undefined && Array.isArray(whole.items)) {
|
|
212
|
+
// A reader that predates held rows answers the window itself: they are
|
|
213
|
+
// proven against it here, by digest.
|
|
214
|
+
const proven = holdsTranscriptTail(whole.items, pagedHeld);
|
|
215
|
+
wire = clampStoredWindow(whole, window, proven ? pagedHeld.count : 0);
|
|
216
|
+
if (proven) {
|
|
217
|
+
wire = { ...wire, items: wire.items.slice(0, wire.items.length - pagedHeld.count) };
|
|
218
|
+
paged = true;
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
wire = paged ? clampStoredPage(whole, window, pagedHeld.count) : clampStoredWindow(whole, window, 0);
|
|
144
222
|
}
|
|
145
|
-
|
|
223
|
+
const heldRows = paged ? pagedHeld.count : 0;
|
|
224
|
+
const delivered = (Array.isArray(wire.items) ? wire.items.length : 0) + heldRows;
|
|
225
|
+
const projectionStamp = servedStamp(snapshot.projectionStamp, window, baseProjectionStamp, delivered);
|
|
146
226
|
projectedFiles.delete(key);
|
|
147
227
|
if (
|
|
148
228
|
fileStamp !== null &&
|
|
149
|
-
typeof
|
|
150
|
-
|
|
229
|
+
typeof projectionStamp === 'string' &&
|
|
230
|
+
projectionStamp &&
|
|
151
231
|
(await fileStampOf(sessionId)) === fileStamp
|
|
152
232
|
) {
|
|
153
|
-
projectedFiles.set(key, { projectionStamp
|
|
233
|
+
projectedFiles.set(key, { projectionStamp, fileStamp });
|
|
154
234
|
if (projectedFiles.size > PROJECTED_FILE_LIMIT) projectedFiles.delete(projectedFiles.keys().next().value);
|
|
155
235
|
}
|
|
156
236
|
let goal;
|
|
@@ -162,9 +242,9 @@ export function createStoredSessionReader({
|
|
|
162
242
|
goal = null;
|
|
163
243
|
}
|
|
164
244
|
}
|
|
165
|
-
const
|
|
166
|
-
return {
|
|
245
|
+
const projection = {
|
|
167
246
|
...wire,
|
|
247
|
+
...(typeof projectionStamp === 'string' ? { projectionStamp } : {}),
|
|
168
248
|
sessionId,
|
|
169
249
|
...(typeof readStoredGoal === 'function' ? { goal: sanitizeForWire(goal, 1) ?? null } : {}),
|
|
170
250
|
...(typeof readQuotaResume === 'function'
|
|
@@ -172,8 +252,14 @@ export function createStoredSessionReader({
|
|
|
172
252
|
: {}),
|
|
173
253
|
queued: Array.isArray(snapshot.queued) ? wire.queued : [],
|
|
174
254
|
};
|
|
255
|
+
if (paged) pageOnly.add(projection);
|
|
256
|
+
return projection;
|
|
175
257
|
}
|
|
176
258
|
|
|
259
|
+
/** Whether `projection` carries only the rows above its caller's proven
|
|
260
|
+
* ones (the page itself), not those rows. */
|
|
261
|
+
const storedPageOnly = (projection) => Boolean(projection) && pageOnly.has(projection);
|
|
262
|
+
|
|
177
263
|
async function requestedMessageSlice(params, sessionId) {
|
|
178
264
|
if (!Number.isInteger(params?.messageStart)) return {};
|
|
179
265
|
const start = Math.max(0, params.messageStart);
|
|
@@ -204,6 +290,7 @@ export function createStoredSessionReader({
|
|
|
204
290
|
return {
|
|
205
291
|
storedSessionProjection,
|
|
206
292
|
storedProjectionUnchanged,
|
|
293
|
+
storedPageOnly,
|
|
207
294
|
requestedMessageSlice,
|
|
208
295
|
forgetStoredSession: forgetStoredProjection,
|
|
209
296
|
releaseStoredSession: releaseStoredProjection,
|