mixdog 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/package.json +2 -2
- package/scripts/lib/run-node-tests.mjs +9 -0
- package/scripts/prune-desktop-runtime.mjs +3 -0
- package/scripts/release-paths.mjs +39 -2
- package/scripts/test-direct.mjs +4 -2
- package/src/defaults/shipped-history.json +6 -0
- package/src/defaults/skills/setup/references/actions.md +5 -2
- package/src/defaults/skills/visualize/SKILL.md +157 -0
- package/src/defaults/skills/visualize/references/craft.md +88 -0
- package/src/headless-command.mjs +56 -3
- package/src/headless-exec.mjs +17 -1
- package/src/headless-role.mjs +2 -2
- package/src/help.mjs +2 -0
- package/src/lib/rules-builder.cjs +0 -23
- package/src/rules/lead/LEAD.md +2 -2
- package/src/rules/shared/70-delivery.md +2 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +1 -0
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-providers-merge.mjs +17 -11
- package/src/runtime/agent/orchestrator/config-storage.mjs +1 -0
- package/src/runtime/agent/orchestrator/config.mjs +9 -1
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +47 -7
- package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +51 -8
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +25 -5
- package/src/runtime/agent/orchestrator/mcp/client.mjs +348 -122
- package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +13 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +12 -4
- package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +8 -40
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +51 -12
- package/src/runtime/agent/orchestrator/providers/model-cache.mjs +37 -2
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +67 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +521 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +251 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +69 -102
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +15 -1
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +10 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +36 -3
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +12 -11
- package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +11 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +11 -8
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +9 -5
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +18 -8
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +18 -11
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +362 -0
- package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +253 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +12 -14
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/diagnostic.mjs +1 -7
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/refusal.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +17 -7
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +12 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -16
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +90 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +22 -12
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +32 -3
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +1 -5
- package/src/runtime/agent/orchestrator/session/message-ids.mjs +123 -0
- package/src/runtime/agent/orchestrator/session/message-ids.test-support.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +29 -19
- package/src/runtime/agent/orchestrator/session/read-dedup.mjs +0 -1
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +11 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +23 -4
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +28 -8
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +140 -0
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +52 -2
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +32 -22
- package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +10 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +31 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +11 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +9 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +80 -66
- package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +5 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +16 -7
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +137 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +11 -10
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +21 -11
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +56 -13
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +58 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
- package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +78 -5
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +0 -10
- package/src/runtime/attachments/store.mjs +38 -0
- package/src/runtime/channels/index.mjs +1 -5
- package/src/runtime/channels/lib/runtime-paths.mjs +8 -23
- package/src/runtime/channels/lib/tool-dispatch.mjs +1 -13
- package/src/runtime/channels/lib/worker-ipc.mjs +2 -2
- package/src/runtime/channels/lib/worker-main.mjs +4 -34
- package/src/runtime/shared/atomic-file.mjs +8 -2
- package/src/runtime/shared/best-effort-stderr.mjs +8 -0
- package/src/runtime/shared/code-graph-state.mjs +12 -0
- package/src/runtime/shared/defaults-separation.mjs +2 -0
- package/src/runtime/shared/git-runner-worker.mjs +3 -2
- package/src/runtime/shared/git-runner.mjs +4 -3
- package/src/runtime/shared/llm/default-context-window.mjs +6 -0
- package/src/runtime/shared/llm/model-catalog-projection.mjs +5 -4
- package/src/runtime/shared/llm/model-catalog.mjs +240 -118
- package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +156 -0
- package/src/runtime/shared/llm/provider-seed-rosters.mjs +130 -0
- package/src/runtime/shared/model-proxy-mode.d.mts +3 -0
- package/src/runtime/shared/model-proxy-mode.mjs +19 -0
- package/src/runtime/shared/provider-accounts.mjs +2 -2
- package/src/runtime/shared/provider-api-key.mjs +21 -1
- package/src/runtime/shared/schedule-model-ref.mjs +11 -2
- package/src/runtime/shared/semver.d.mts +1 -0
- package/src/runtime/shared/semver.mjs +57 -0
- package/src/runtime/shared/session-usage-totals.mjs +19 -0
- package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
- package/src/runtime/shared/singleton-owner.mjs +36 -6
- package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
- package/src/runtime/shared/tool-surface.mjs +9 -2
- package/src/runtime/shared/tool-work-units.mjs +14 -0
- package/src/runtime/shared/transcript-metadata.mjs +17 -1
- package/src/runtime/shared/turn-changes.mjs +791 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +346 -12
- package/src/runtime/shared/turn-snapshot.mjs +690 -81
- package/src/runtime/shared/turn-worktree-snapshot.mjs +161 -11
- package/src/runtime/shared/update-checker.mjs +2 -31
- package/src/runtime/shared/worktrees/cache-cleanups.mjs +52 -0
- package/src/runtime/shared/worktrees/contract.mjs +551 -0
- package/src/runtime/shared/worktrees/disk.mjs +57 -0
- package/src/runtime/shared/worktrees/git.mjs +247 -0
- package/src/runtime/shared/worktrees/include.mjs +480 -0
- package/src/runtime/shared/worktrees/lock.mjs +46 -0
- package/src/runtime/shared/worktrees/maintenance.mjs +96 -0
- package/src/runtime/shared/worktrees/manager.mjs +1215 -0
- package/src/runtime/shared/worktrees/merge.mjs +182 -0
- package/src/runtime/shared/worktrees/owner.mjs +46 -0
- package/src/runtime/shared/worktrees/paths.mjs +62 -0
- package/src/runtime/shared/worktrees/registry.mjs +110 -0
- package/src/runtime/shared/worktrees/settings.mjs +84 -0
- package/src/runtime/shared/worktrees/slug.mjs +62 -0
- package/src/runtime/shared/worktrees/test-support.mjs +96 -0
- package/src/runtime/visualize-bridge/client.mjs +181 -0
- package/src/runtime/visualize-bridge/tool-defs.mjs +97 -0
- package/src/session-runtime/boot/apis.mjs +35 -0
- package/src/session-runtime/boot/begin.mjs +0 -2
- package/src/session-runtime/boot/config.mjs +2 -0
- package/src/session-runtime/boot/facade.mjs +20 -0
- package/src/session-runtime/boot/infrastructure.mjs +1 -0
- package/src/session-runtime/boot/session.mjs +1 -0
- package/src/session-runtime/boot/tools.mjs +9 -4
- package/src/session-runtime/config-lifecycle.mjs +10 -1
- package/src/session-runtime/cwd-plugins/cwd-apply.mjs +11 -1
- package/src/session-runtime/global-extensions.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +27 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +32 -1
- package/src/session-runtime/internal-tool-executor.mjs +9 -1
- package/src/session-runtime/lifecycle/session-catalog.mjs +8 -1
- package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +5 -0
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +21 -3
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +16 -0
- package/src/session-runtime/mcp-glue/connect.mjs +11 -6
- package/src/session-runtime/mcp-glue/server-catalog.mjs +7 -3
- package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
- package/src/session-runtime/model-route/route-persist.mjs +3 -0
- package/src/session-runtime/model-route/set-route.mjs +15 -2
- package/src/session-runtime/notification-bus.mjs +9 -1
- package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
- package/src/session-runtime/resource-extension-sync.mjs +30 -7
- package/src/session-runtime/resource-mcp-api.mjs +27 -10
- package/src/session-runtime/route-resolution.mjs +10 -1
- package/src/session-runtime/route-state.mjs +16 -2
- package/src/session-runtime/runtime-bootstrap.mjs +0 -4
- package/src/session-runtime/runtime-core.mjs +5 -0
- package/src/session-runtime/runtime-facade.mjs +8 -0
- package/src/session-runtime/runtime-paths.mjs +0 -1
- package/src/session-runtime/runtime-review-api.mjs +9 -0
- package/src/session-runtime/services/agent-tool/helpers.mjs +5 -0
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +3 -0
- package/src/session-runtime/services/agent-tool/job-views/job-lookup.mjs +1 -0
- package/src/session-runtime/services/agent-tool/job-views/spawn-meta.mjs +1 -0
- package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +15 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +1 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +10 -2
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -7
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +2 -1
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -0
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +1 -0
- package/src/session-runtime/services/agent-tool.mjs +1 -0
- package/src/session-runtime/services/channel-worker.mjs +1 -1
- package/src/session-runtime/services/daemon-candidate.mjs +3 -2
- package/src/session-runtime/services/daemon-crash-capture/spawn-capture.mjs +55 -0
- package/src/session-runtime/session-create/session-options.mjs +10 -1
- package/src/session-runtime/session-turn-api.mjs +1 -0
- package/src/session-runtime/settings-api.mjs +4 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +9 -2
- package/src/session-runtime/settings-profile-api.mjs +27 -2
- package/src/session-runtime/settings-worktree-api.mjs +17 -0
- package/src/session-runtime/setup-tool/executor.mjs +11 -4
- package/src/session-runtime/setup-tool/settings-contract.mjs +3 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +31 -3
- package/src/session-runtime/starter-guide-state.mjs +62 -0
- package/src/session-runtime/turn/agent-ops.mjs +12 -1
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +13 -3
- package/src/session-runtime/turn/turn-run/snapshot-tracker.mjs +20 -15
- package/src/session-runtime/turn/turn-run/turn-open.mjs +4 -5
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +29 -1
- package/src/session-runtime/turn/turn-run/turn-settle.mjs +6 -1
- package/src/session-runtime/turn/turn-run.mjs +6 -7
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -2
- package/src/session-runtime/workspace/admission-gate.mjs +197 -0
- package/src/session-runtime/workspace/project-branches.mjs +136 -0
- package/src/session-runtime/workspace/transition-steps.mjs +364 -0
- package/src/session-runtime/workspace/transitions.mjs +651 -0
- package/src/session-runtime/workspace/workspace-state.mjs +112 -0
- package/src/standalone/channel-binding.mjs +1 -2
- package/src/standalone/channel-transport/call-dispatch.mjs +4 -10
- package/src/standalone/channel-transport/http-routes.mjs +17 -0
- package/src/standalone/channel-transport.mjs +13 -1
- package/src/standalone/daemon-channels-loader.mjs +2 -3
- package/src/standalone/daemon-desktop-runtime.mjs +1 -0
- package/src/standalone/daemon-owner.mjs +372 -0
- package/src/standalone/daemon-stored-session-views.mjs +4 -1
- package/src/standalone/daemon.mjs +139 -5
- package/src/standalone/desktop-service-registry.mjs +20 -0
- package/src/standalone/local-session-runtime.mjs +13 -78
- package/src/standalone/session-client/attachment-calls.mjs +3 -1
- package/src/standalone/session-client/daemon-unavailable.mjs +62 -0
- package/src/standalone/session-client/lease-heartbeat.mjs +47 -0
- package/src/standalone/session-client.mjs +104 -13
- package/src/standalone/session-protocol.mjs +17 -0
- package/src/standalone/session-proxy.mjs +3 -0
- package/src/standalone/session-service/agent-tree/agent-child.mjs +4 -0
- package/src/standalone/session-service/agent-tree/agent-rehydrate.mjs +1 -0
- package/src/standalone/session-service/entries/lifecycle.mjs +6 -0
- package/src/standalone/session-service/projection/revisions.mjs +2 -0
- package/src/standalone/session-service/projection/transcript-window.mjs +149 -19
- package/src/standalone/session-service/projection/workspace.mjs +14 -0
- package/src/standalone/session-service/session-calls/session-action.mjs +11 -3
- package/src/standalone/session-transport/call-route.mjs +13 -0
- package/src/standalone/session-transport/client-registry/client-grace.mjs +45 -3
- package/src/standalone/session-transport/client-registry/registration-replays.mjs +1 -0
- package/src/standalone/session-transport/client-registry.mjs +20 -3
- package/src/standalone/session-transport/routes.mjs +79 -2
- package/src/standalone/session-transport/stack.mjs +2 -0
- package/src/standalone/session-transport.mjs +24 -1
- package/src/tui/app/message-selector.mjs +2 -1
- package/src/tui/app/model-options.mjs +35 -5
- package/src/tui/app/model-picker/provider-models-list.mjs +2 -2
- package/src/tui/app/model-picker.mjs +4 -2
- package/src/tui/dist/index.mjs +332 -36
- package/src/tui/index.jsx +10 -2
- package/src/tui/session/compaction-view.mjs +147 -0
- package/src/tui/session/context-state.mjs +6 -0
- package/src/tui/session/initial-state.mjs +3 -0
- package/src/tui/session/quota-resume.mjs +2 -1
- package/src/tui/session/session-api/commands.mjs +10 -2
- package/src/tui/session/session-api/controls.mjs +4 -0
- package/src/tui/session/session-api/extensions.mjs +12 -8
- package/src/tui/session/session-api/intake/rewind.mjs +2 -1
- package/src/tui/session/session-api/integrations.mjs +18 -0
- package/src/tui/session/session-api/lifecycle/dispose.mjs +6 -1
- package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -0
- package/src/tui/session/session-api/lifecycle/resume.mjs +3 -0
- package/src/tui/session/session-api/routes.mjs +10 -1
- package/src/tui/session/session-api/settings.mjs +2 -0
- package/src/tui/session/session-api/shared.mjs +10 -6
- package/src/tui/session/session-api/worktree.mjs +60 -0
- package/src/tui/session/session-api.mjs +2 -0
- package/src/tui/session/session-flow/auto-clear.mjs +31 -12
- package/src/tui/session/session-flow/drain.mjs +15 -1
- package/src/tui/session/session-flow/session-reset.mjs +4 -3
- package/src/tui/session/session-flow.mjs +2 -0
- package/src/tui/session/transcript-intake.mjs +14 -5
- package/src/tui/session/turn-stream.mjs +32 -4
- package/src/tui/session/turn-tool-cards.mjs +10 -2
- package/src/tui/session/turn.mjs +39 -10
- package/src/runtime/channels/tool-defs.mjs +0 -17
|
@@ -18,9 +18,13 @@ import { readSingletonOwner } from '../runtime/shared/singleton-owner.mjs';
|
|
|
18
18
|
import { isPidAlive } from '../runtime/shared/pid-liveness.mjs';
|
|
19
19
|
import { resolveRuntimeRoot } from '../runtime/shared/runtime-root.mjs';
|
|
20
20
|
import { withHeapCap } from '../runtime/shared/heap-cap.mjs';
|
|
21
|
-
import { daemonDataDir } from '../session-runtime/services/daemon-crash-capture.mjs';
|
|
21
|
+
import { daemonCrashCaptureDir, daemonDataDir } from '../session-runtime/services/daemon-crash-capture.mjs';
|
|
22
22
|
import { forkDaemonCandidate } from '../session-runtime/services/daemon-candidate.mjs';
|
|
23
|
+
import { daemonOwnerIsStale, proveDaemonOwnerRecord } from './daemon-owner.mjs';
|
|
23
24
|
import { createAttachmentCalls } from './session-client/attachment-calls.mjs';
|
|
25
|
+
import { daemonUnavailableError } from './session-client/daemon-unavailable.mjs';
|
|
26
|
+
import { createLeaseHeartbeat } from './session-client/lease-heartbeat.mjs';
|
|
27
|
+
import { clampLeaseMs } from './session-transport/client-registry/client-grace.mjs';
|
|
24
28
|
import {
|
|
25
29
|
EVENT_STREAM_LIVENESS_TIMEOUT_MS,
|
|
26
30
|
EVENT_STREAM_RECONNECT_BASE_MS,
|
|
@@ -90,8 +94,20 @@ function request({
|
|
|
90
94
|
timeoutMs = 120_000,
|
|
91
95
|
control = false,
|
|
92
96
|
urgent = false,
|
|
97
|
+
// Total-duration bound (timeoutMs only measures socket inactivity, which a
|
|
98
|
+
// trickling response keeps resetting). Probes set it; calls do not.
|
|
99
|
+
deadlineMs = 0,
|
|
93
100
|
}) {
|
|
94
|
-
return new Promise((
|
|
101
|
+
return new Promise((settleResolve, settleReject) => {
|
|
102
|
+
let deadlineTimer = null;
|
|
103
|
+
const resolve = (value) => {
|
|
104
|
+
clearTimeout(deadlineTimer);
|
|
105
|
+
settleResolve(value);
|
|
106
|
+
};
|
|
107
|
+
const reject = (error) => {
|
|
108
|
+
clearTimeout(deadlineTimer);
|
|
109
|
+
settleReject(error);
|
|
110
|
+
};
|
|
95
111
|
const payload = body ? JSON.stringify(body) : null;
|
|
96
112
|
const req = http.request(
|
|
97
113
|
{
|
|
@@ -112,6 +128,7 @@ function request({
|
|
|
112
128
|
res.on('data', (chunk) => {
|
|
113
129
|
data += chunk;
|
|
114
130
|
});
|
|
131
|
+
res.on('error', reject);
|
|
115
132
|
res.on('end', () => {
|
|
116
133
|
let parsed = null;
|
|
117
134
|
try {
|
|
@@ -133,20 +150,43 @@ function request({
|
|
|
133
150
|
req.destroy(new Error(`session timeout: ${method} ${urlPath}`));
|
|
134
151
|
});
|
|
135
152
|
req.on('error', reject);
|
|
153
|
+
if (deadlineMs > 0) {
|
|
154
|
+
deadlineTimer = setTimeout(() => {
|
|
155
|
+
const error = new Error(`session timeout: ${method} ${urlPath}`);
|
|
156
|
+
reject(error);
|
|
157
|
+
req.destroy(error);
|
|
158
|
+
}, deadlineMs);
|
|
159
|
+
}
|
|
136
160
|
if (payload) req.write(payload);
|
|
137
161
|
req.end();
|
|
138
162
|
});
|
|
139
163
|
}
|
|
140
164
|
|
|
165
|
+
/** /health within `timeoutMs` of total duration; null when it does not answer. */
|
|
141
166
|
export async function probeSessionHealth({ port, token, timeoutMs = 800 } = {}) {
|
|
142
167
|
try {
|
|
143
|
-
const health = await request({
|
|
168
|
+
const health = await request({
|
|
169
|
+
port,
|
|
170
|
+
token,
|
|
171
|
+
path: '/health',
|
|
172
|
+
timeoutMs,
|
|
173
|
+
deadlineMs: Math.max(1, timeoutMs),
|
|
174
|
+
control: true,
|
|
175
|
+
});
|
|
144
176
|
return health?.status === 'ok' ? health : null;
|
|
145
177
|
} catch {
|
|
146
178
|
return null;
|
|
147
179
|
}
|
|
148
180
|
}
|
|
149
181
|
|
|
182
|
+
/** Health probe + protocol compatibility against an explicit discovery,
|
|
183
|
+
* without reading daemon.json or spawning anything. `health` is null when
|
|
184
|
+
* the daemon does not answer (then `compatibility` is null too). */
|
|
185
|
+
export async function probeSessionCompatibility(discovery, { timeoutMs = 800, probeHealth = probeSessionHealth } = {}) {
|
|
186
|
+
const health = await probeHealth({ port: discovery?.port, token: discovery?.token, timeoutMs });
|
|
187
|
+
return { health, compatibility: health ? sessionDaemonCompatibility(health) : null };
|
|
188
|
+
}
|
|
189
|
+
|
|
150
190
|
export function readSessionDiscovery(discoveryPath = sessionDiscoveryPath()) {
|
|
151
191
|
try {
|
|
152
192
|
const parsed = JSON.parse(readFileSync(discoveryPath, 'utf8'));
|
|
@@ -319,13 +359,15 @@ export function spawnDaemonCandidate({ cwd, log, timeoutMs = 30_000, entry = dae
|
|
|
319
359
|
});
|
|
320
360
|
}
|
|
321
361
|
|
|
322
|
-
/** Spawn-or-attach discovery for the machine-global daemon.
|
|
362
|
+
/** Spawn-or-attach discovery for the machine-global daemon.
|
|
363
|
+
* `entry` is a test seam; production always forks the real daemon. */
|
|
323
364
|
export async function ensureDaemon({
|
|
324
365
|
cwd = process.cwd(),
|
|
325
366
|
log = () => {},
|
|
326
367
|
attempts = 5,
|
|
327
368
|
readyTimeoutMs = null,
|
|
328
369
|
onUpgradeWait = null,
|
|
370
|
+
entry = daemonEntry(),
|
|
329
371
|
} = {}) {
|
|
330
372
|
const configuredTimeoutMs = Number(process.env.MIXDOG_DAEMON_READY_TIMEOUT_MS);
|
|
331
373
|
let requestedTimeoutMs = DEFAULT_DAEMON_READY_TIMEOUT_MS;
|
|
@@ -338,6 +380,11 @@ export async function ensureDaemon({
|
|
|
338
380
|
let spawnAttempts = 0;
|
|
339
381
|
let waitingOwnerPid = 0;
|
|
340
382
|
let waitingDrainPid = 0;
|
|
383
|
+
let staleOwnerPid = 0;
|
|
384
|
+
// What the passes observed, for the error when no endpoint ever appears.
|
|
385
|
+
let unansweredDiscovery = null;
|
|
386
|
+
let waitedOwner = null;
|
|
387
|
+
let lastCandidate = null;
|
|
341
388
|
const startedAt = Date.now();
|
|
342
389
|
const elapsed = () => Date.now() - startedAt;
|
|
343
390
|
while (Date.now() < deadline) {
|
|
@@ -352,7 +399,7 @@ export async function ensureDaemon({
|
|
|
352
399
|
const health = await probeSessionHealth({
|
|
353
400
|
port: discovery.port,
|
|
354
401
|
token: discovery.token,
|
|
355
|
-
timeoutMs: DAEMON_BOOT_PROBE_TIMEOUT_MS,
|
|
402
|
+
timeoutMs: Math.max(1, Math.min(DAEMON_BOOT_PROBE_TIMEOUT_MS, deadline - Date.now())),
|
|
356
403
|
});
|
|
357
404
|
log(
|
|
358
405
|
`discovery pid=${discovery.pid} port=${discovery.port}` +
|
|
@@ -385,13 +432,35 @@ export async function ensureDaemon({
|
|
|
385
432
|
err.sessionProtocolMismatch = true;
|
|
386
433
|
throw err;
|
|
387
434
|
}
|
|
435
|
+
unansweredDiscovery = { pid: discovery.pid, port: discovery.port };
|
|
388
436
|
}
|
|
389
437
|
// A concurrent launcher can win the owner lock before it publishes
|
|
390
438
|
// daemon.json. That is a healthy singleton boot, not a reason to spawn five
|
|
391
|
-
// doomed contenders and report "endpoint unavailable" after ~500 ms.
|
|
392
|
-
|
|
439
|
+
// doomed contenders and report "endpoint unavailable" after ~500 ms. A
|
|
440
|
+
// live pid whose heartbeat stopped is a reused pid (daemon-owner.mjs):
|
|
441
|
+
// spawn, and the candidate reclaims it.
|
|
442
|
+
// A live-pid record whose heartbeat looks stale (or that has none: v1.1.2
|
|
443
|
+
// and older) is reclaimable only when its holder's endpoint does not
|
|
444
|
+
// answer with its pid — a crashed daemon leaves its files behind, naming a
|
|
445
|
+
// pid the OS may have reused. A probe cut short by this deadline proves
|
|
446
|
+
// nothing either way.
|
|
447
|
+
const proof = await proveDaemonOwnerRecord(daemonOwnerPath(), {
|
|
448
|
+
discoveryPath: sessionDiscoveryPath(),
|
|
449
|
+
budgetMs: Math.max(1, deadline - Date.now()),
|
|
450
|
+
});
|
|
451
|
+
const ownerState = readSingletonOwner(daemonOwnerPath(), {
|
|
452
|
+
isStale: (owner) => daemonOwnerIsStale(owner, { proof }),
|
|
453
|
+
});
|
|
454
|
+
if (ownerState.stale && Number(ownerState.owner?.pid) !== staleOwnerPid) {
|
|
455
|
+
staleOwnerPid = Number(ownerState.owner?.pid) || 0;
|
|
456
|
+
log(
|
|
457
|
+
`daemon owner record pid=${staleOwnerPid} heartbeatAt=${ownerState.owner?.heartbeatAt ?? '-'}` +
|
|
458
|
+
` probe=${proof?.verdict ?? '-'} is stale; spawning a daemon to reclaim it`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
393
461
|
const ownerPid = ownerState.alive ? Number(ownerState.owner?.pid) || 0 : 0;
|
|
394
462
|
if (ownerPid) {
|
|
463
|
+
waitedOwner = { pid: ownerPid, claimedAt: ownerState.owner?.claimedAt ?? null };
|
|
395
464
|
if (ownerPid !== waitingOwnerPid) {
|
|
396
465
|
waitingOwnerPid = ownerPid;
|
|
397
466
|
log(`waiting for daemon owner pid=${ownerPid} to publish its session endpoint`);
|
|
@@ -400,16 +469,25 @@ export async function ensureDaemon({
|
|
|
400
469
|
continue;
|
|
401
470
|
}
|
|
402
471
|
waitingOwnerPid = 0;
|
|
472
|
+
waitedOwner = null;
|
|
403
473
|
if (spawnAttempts >= maxSpawnAttempts) break;
|
|
404
474
|
spawnAttempts += 1;
|
|
405
475
|
log(`spawning daemon candidate attempt=${spawnAttempts} at=${elapsed()}ms`);
|
|
406
|
-
await spawnDaemonCandidate({
|
|
476
|
+
lastCandidate = await spawnDaemonCandidate({
|
|
407
477
|
cwd,
|
|
408
478
|
log,
|
|
409
479
|
timeoutMs: Math.max(1, deadline - Date.now()),
|
|
480
|
+
entry,
|
|
410
481
|
});
|
|
411
482
|
}
|
|
412
|
-
throw
|
|
483
|
+
throw daemonUnavailableError({
|
|
484
|
+
discovery: unansweredDiscovery,
|
|
485
|
+
owner: waitedOwner,
|
|
486
|
+
candidate: lastCandidate?.summary?.() ?? null,
|
|
487
|
+
spawnAttempts,
|
|
488
|
+
maxSpawnAttempts,
|
|
489
|
+
captureDir: daemonCrashCaptureDir(),
|
|
490
|
+
});
|
|
413
491
|
}
|
|
414
492
|
|
|
415
493
|
/** Registers this client with the daemon. Transport failures are retried
|
|
@@ -454,6 +532,12 @@ export async function attachSession({
|
|
|
454
532
|
streamReconnectMaxMs = EVENT_STREAM_RECONNECT_MAX_MS,
|
|
455
533
|
streamLivenessTimeoutMs = EVENT_STREAM_LIVENESS_TIMEOUT_MS,
|
|
456
534
|
streamReconnectBudgetMs = EVENT_STREAM_RECONNECT_BUDGET_MS,
|
|
535
|
+
remote = false,
|
|
536
|
+
leaseMs = 30_000,
|
|
537
|
+
heartbeatMs = 10_000,
|
|
538
|
+
readDiscovery = remote ? () => discovery : () => readSessionDiscovery(),
|
|
539
|
+
pidAlive = remote ? () => true : isPidAlive,
|
|
540
|
+
probeHealth = probeSessionHealth,
|
|
457
541
|
log = () => {},
|
|
458
542
|
} = {}) {
|
|
459
543
|
if (!discovery?.port || !discovery?.token) throw new Error('daemon discovery {port, token} required');
|
|
@@ -462,13 +546,15 @@ export async function attachSession({
|
|
|
462
546
|
port,
|
|
463
547
|
serverToken,
|
|
464
548
|
body: {
|
|
465
|
-
|
|
549
|
+
// A remote client's process lives on another machine: no local pid.
|
|
550
|
+
leadPid: remote ? 0 : leadPid,
|
|
466
551
|
cwd,
|
|
467
552
|
lifecycle: lifecycle !== false,
|
|
468
553
|
clientKind: clientKind === 'desktop' ? 'desktop' : 'session',
|
|
469
554
|
registrationId: String(registrationId || randomUUID()),
|
|
470
555
|
protocol: SESSION_PROTOCOL,
|
|
471
556
|
revision: SESSION_REVISION,
|
|
557
|
+
...(remote ? { remote: true, leaseMs: clampLeaseMs(leaseMs) } : {}),
|
|
472
558
|
},
|
|
473
559
|
});
|
|
474
560
|
const clientToken = reg?.token;
|
|
@@ -487,18 +573,23 @@ export async function attachSession({
|
|
|
487
573
|
reconnectMaxMs: streamReconnectMaxMs,
|
|
488
574
|
reconnectBudgetMs: streamReconnectBudgetMs,
|
|
489
575
|
livenessTimeoutMs: streamLivenessTimeoutMs,
|
|
490
|
-
readDiscovery
|
|
491
|
-
probeHealth
|
|
492
|
-
pidAlive
|
|
576
|
+
readDiscovery,
|
|
577
|
+
probeHealth,
|
|
578
|
+
pidAlive,
|
|
493
579
|
log,
|
|
494
580
|
});
|
|
495
581
|
link.open();
|
|
582
|
+
const heartbeat = remote
|
|
583
|
+
? createLeaseHeartbeat({ request, port, serverToken, clientToken, heartbeatMs, leaseMs, log })
|
|
584
|
+
: null;
|
|
585
|
+
heartbeat?.start();
|
|
496
586
|
const { call, close } = createAttachmentCalls({
|
|
497
587
|
request,
|
|
498
588
|
port,
|
|
499
589
|
serverToken,
|
|
500
590
|
clientToken,
|
|
501
591
|
stopStream: link.stop,
|
|
592
|
+
stopHeartbeat: heartbeat?.stop,
|
|
502
593
|
log,
|
|
503
594
|
});
|
|
504
595
|
|
|
@@ -40,6 +40,7 @@ export const SESSION_READ_ACTIONS = Object.freeze([
|
|
|
40
40
|
'getLocalProviderModelDetails',
|
|
41
41
|
'getSessionReviewDiff',
|
|
42
42
|
'getSessionUsage',
|
|
43
|
+
'getTurnChanges',
|
|
43
44
|
'getTurnReviewDiff',
|
|
44
45
|
'getTurnLiveness',
|
|
45
46
|
'getUpdateSettings',
|
|
@@ -49,6 +50,10 @@ export const SESSION_READ_ACTIONS = Object.freeze([
|
|
|
49
50
|
'getVoiceStatus',
|
|
50
51
|
'isSetupRequestActive',
|
|
51
52
|
'getWorkflowPack',
|
|
53
|
+
'getWorktreeSettings',
|
|
54
|
+
'getBranchMenu',
|
|
55
|
+
'getProjectBranchMenu',
|
|
56
|
+
'listWorktrees',
|
|
52
57
|
'hooksStatus',
|
|
53
58
|
'inheritancePreflight',
|
|
54
59
|
'listAgents',
|
|
@@ -132,6 +137,8 @@ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
|
|
|
132
137
|
'restoreQueued',
|
|
133
138
|
'revertTurnReview',
|
|
134
139
|
'revertTurnReviewFile',
|
|
140
|
+
'revertTurnChanges',
|
|
141
|
+
'reapplyTurnChanges',
|
|
135
142
|
'rewindToItem',
|
|
136
143
|
'runDoctor',
|
|
137
144
|
'runScheduleNow',
|
|
@@ -189,8 +196,10 @@ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
|
|
|
189
196
|
'setWebhookEnabled',
|
|
190
197
|
'setWorkflow',
|
|
191
198
|
'setOrchestrationMode',
|
|
199
|
+
'saveStarterGuideState',
|
|
192
200
|
'skipOnboarding',
|
|
193
201
|
'startMediaJob',
|
|
202
|
+
'stopShellJob',
|
|
194
203
|
'switchContext',
|
|
195
204
|
'taskControl',
|
|
196
205
|
'testWebhook',
|
|
@@ -201,6 +210,14 @@ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
|
|
|
201
210
|
'transcribeAudio',
|
|
202
211
|
'prepareTranscription',
|
|
203
212
|
'updatePlugin',
|
|
213
|
+
'setWorktreeSettings',
|
|
214
|
+
'selectBranch',
|
|
215
|
+
'selectProjectBranch',
|
|
216
|
+
'mergeProjectBranch',
|
|
217
|
+
'requestWorktree',
|
|
218
|
+
'mergeWorkspace',
|
|
219
|
+
'removeWorktree',
|
|
220
|
+
'pruneWorktrees',
|
|
204
221
|
]);
|
|
205
222
|
|
|
206
223
|
export const SESSION_READ_ACTION_SET = new Set(SESSION_READ_ACTIONS);
|
|
@@ -35,6 +35,9 @@ export function createSessionProxyFactory({ attachSession, ensureDaemon, closeId
|
|
|
35
35
|
toolMode: options.toolMode || 'full',
|
|
36
36
|
remote: options.remote === true,
|
|
37
37
|
desktopSession: options.desktopSession ?? null,
|
|
38
|
+
// Worktree intent: `--worktree [name]` / `--no-worktree`, or `{ mode: 'default' }`
|
|
39
|
+
// (the TUI's interactive Lead follows the settings default); absent: none.
|
|
40
|
+
...(options.worktree ? { worktree: options.worktree } : {}),
|
|
38
41
|
};
|
|
39
42
|
const attachment = await pool.ensure({ cwd, log });
|
|
40
43
|
const created = await attachment.client.create(openParams, {
|
|
@@ -36,8 +36,11 @@ export function createAgentChildFactory({ registry, rehydrateAgentSessions, crea
|
|
|
36
36
|
model,
|
|
37
37
|
effort: preset.effort,
|
|
38
38
|
fast: preset.fast === true,
|
|
39
|
+
// Explicit, so Main's modelSettings bucket cannot decide the agent's Auto effort.
|
|
40
|
+
autoEffort: preset.autoEffort !== false,
|
|
39
41
|
modelParameters: preset.modelParameters,
|
|
40
42
|
contextPercent: spec.contextPercent,
|
|
43
|
+
contextTokens: spec.contextTokens,
|
|
41
44
|
selectedContextWindow: spec.selectedContextWindow,
|
|
42
45
|
toolMode: 'full',
|
|
43
46
|
sessionProfile,
|
|
@@ -51,6 +54,7 @@ export function createAgentChildFactory({ registry, rehydrateAgentSessions, crea
|
|
|
51
54
|
presetName: preset.id || preset.name || null,
|
|
52
55
|
effort: preset.effort || null,
|
|
53
56
|
fast: preset.fast === true,
|
|
57
|
+
...(preset.autoEffort === false ? { autoEffort: false } : {}),
|
|
54
58
|
createdAt: Date.now(),
|
|
55
59
|
updatedAt: Date.now(),
|
|
56
60
|
status: 'idle',
|
|
@@ -79,6 +79,7 @@ function rehydratedDescriptor(candidate, ownerSessionId) {
|
|
|
79
79
|
presetName: row.presetName || row.preset || row.profileId || null,
|
|
80
80
|
effort: row.effort || null,
|
|
81
81
|
fast: row.fast === true,
|
|
82
|
+
...(row.autoEffort === false ? { autoEffort: false } : {}),
|
|
82
83
|
modelParameters: row.modelParameters || null,
|
|
83
84
|
taskType: row.taskType || null,
|
|
84
85
|
permission: row.permission || null,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Creation of one daemon-owned execution entry around an injected runtime;
|
|
2
2
|
// disposal lives in ./disposal.mjs. Entries are never addressed by clients;
|
|
3
3
|
// sessionId is the only identity outside the service.
|
|
4
|
+
import { syncEntryWorkspace } from '../projection/workspace.mjs';
|
|
4
5
|
import { createEntryDisposal } from './disposal.mjs';
|
|
5
6
|
|
|
6
7
|
export function createEntryLifecycle({
|
|
@@ -46,17 +47,21 @@ export function createEntryLifecycle({
|
|
|
46
47
|
model: params.model,
|
|
47
48
|
effort: params.effort,
|
|
48
49
|
fast: params.fast,
|
|
50
|
+
autoEffort: params.autoEffort,
|
|
49
51
|
modelParameters: params.modelParameters,
|
|
50
52
|
contextPercent: params.contextPercent,
|
|
53
|
+
contextTokens: params.contextTokens,
|
|
51
54
|
selectedContextWindow: params.selectedContextWindow,
|
|
52
55
|
toolMode: params.toolMode || 'full',
|
|
53
56
|
remote: params.remote === true,
|
|
54
57
|
desktopSession: params.desktopSession ?? null,
|
|
55
58
|
sessionProfile: params.sessionProfile ?? null,
|
|
59
|
+
worktree: params.worktree ?? null,
|
|
56
60
|
});
|
|
57
61
|
const entry = {
|
|
58
62
|
runtime,
|
|
59
63
|
cwd: params.cwd || process.cwd(),
|
|
64
|
+
workspaceRevision: null,
|
|
60
65
|
timer: null,
|
|
61
66
|
disposed: false,
|
|
62
67
|
unsubscribe: null,
|
|
@@ -79,6 +84,7 @@ export function createEntryLifecycle({
|
|
|
79
84
|
} catch {
|
|
80
85
|
initialState = { busy: true };
|
|
81
86
|
}
|
|
87
|
+
syncEntryWorkspace(entry, initialState);
|
|
82
88
|
indexSessionEntry(entry, initialState.sessionId);
|
|
83
89
|
updateEntryBusy(entry, initialState);
|
|
84
90
|
addSubscriber(entry, ctx);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { diffSessionState } from '../../session-state-patch.mjs';
|
|
6
6
|
import { projectSessionState } from '../../session-state-projection.mjs';
|
|
7
7
|
import { windowTranscriptSnapshot } from './transcript-window.mjs';
|
|
8
|
+
import { syncEntryWorkspace } from './workspace.mjs';
|
|
8
9
|
|
|
9
10
|
function snapshotOf(entry) {
|
|
10
11
|
const raw = entry.runtime.getState?.() ?? null;
|
|
@@ -127,6 +128,7 @@ export function createRevisionSteps({ revisionEpoch, index, updateEntryBusy, log
|
|
|
127
128
|
};
|
|
128
129
|
}
|
|
129
130
|
const snapshot = current;
|
|
131
|
+
syncEntryWorkspace(entry, snapshot);
|
|
130
132
|
if (!addressedSessionId && projectedSessionId) {
|
|
131
133
|
entry.addressedSessionId = projectedSessionId;
|
|
132
134
|
}
|
|
@@ -97,10 +97,7 @@ function budgetedStart(items, end, maxItems, byteBudget, minItems) {
|
|
|
97
97
|
* goes below TRANSCRIPT_WINDOW_MIN_ITEMS. */
|
|
98
98
|
function tailWindowStart(items, { limit, byteBudget = null, pageBase = 0 }) {
|
|
99
99
|
const held = Math.min(pageBase || 0, limit, items.length);
|
|
100
|
-
|
|
101
|
-
return budgetedStart(items, items.length - held, limit - held, byteBudget, TRANSCRIPT_WINDOW_MIN_ITEMS);
|
|
102
|
-
}
|
|
103
|
-
return budgetedStart(items, items.length, limit, byteBudget, TRANSCRIPT_WINDOW_MIN_ITEMS);
|
|
100
|
+
return budgetedStart(items, items.length - held, limit - held, byteBudget, TRANSCRIPT_WINDOW_MIN_ITEMS);
|
|
104
101
|
}
|
|
105
102
|
|
|
106
103
|
/** A stored projection already holds at most `window.limit` items; the byte
|
|
@@ -122,6 +119,22 @@ function restoredAfterFirstMessage(item) {
|
|
|
122
119
|
return Boolean(match) && Number(match[1]) > 0;
|
|
123
120
|
}
|
|
124
121
|
|
|
122
|
+
/** The compaction-archive segments (`{ after, before }`) a resumed runtime
|
|
123
|
+
* holds none of, or null. */
|
|
124
|
+
function runtimeArchiveRange(state) {
|
|
125
|
+
const range = state?.transcriptArchive;
|
|
126
|
+
const after = Number(range?.after);
|
|
127
|
+
const before = Number(range?.before);
|
|
128
|
+
return Number.isInteger(after) && Number.isInteger(before) && before - 1 > after ? { after, before } : null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function resetLoadedHead(view) {
|
|
132
|
+
view.head = [];
|
|
133
|
+
view.headOlder = null;
|
|
134
|
+
view.headSource = null;
|
|
135
|
+
view.archiveHeld = 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
125
138
|
/** Fold a read/subscribe request into a live entry's window. */
|
|
126
139
|
export function requestLiveTranscriptWindow(entry, request) {
|
|
127
140
|
const view = entry.transcriptView;
|
|
@@ -143,6 +156,10 @@ export function requestLiveTranscriptWindow(entry, request) {
|
|
|
143
156
|
paged: false,
|
|
144
157
|
head: [],
|
|
145
158
|
headOlder: null,
|
|
159
|
+
// Where the loaded head came from: 'stored' (durable history aligned by
|
|
160
|
+
// restore ids) or 'archive' (compaction-archive pages).
|
|
161
|
+
headSource: null,
|
|
162
|
+
archiveHeld: 0,
|
|
146
163
|
cache: null,
|
|
147
164
|
};
|
|
148
165
|
return;
|
|
@@ -183,14 +200,12 @@ export function windowTranscriptSnapshot(entry, wire) {
|
|
|
183
200
|
// rewind, clear): follow the anchor, or restart from the tail.
|
|
184
201
|
const moved = items.findIndex((item) => item?.id === view.anchorId);
|
|
185
202
|
start = moved < 0 ? null : moved;
|
|
186
|
-
view
|
|
187
|
-
view.headOlder = null;
|
|
203
|
+
resetLoadedHead(view);
|
|
188
204
|
}
|
|
189
205
|
if (start !== null && start > items.length) start = null;
|
|
190
206
|
if (start === null) {
|
|
191
207
|
start = tailWindowStart(items, view);
|
|
192
|
-
view
|
|
193
|
-
view.headOlder = null;
|
|
208
|
+
resetLoadedHead(view);
|
|
194
209
|
}
|
|
195
210
|
// The sticky start lets appends travel as suffix patches, so an unpaged
|
|
196
211
|
// tail grows past its byte budget while views stay attached (a worker
|
|
@@ -210,7 +225,8 @@ export function windowTranscriptSnapshot(entry, wire) {
|
|
|
210
225
|
}
|
|
211
226
|
view.start = start;
|
|
212
227
|
view.anchorId = items[start]?.id;
|
|
213
|
-
const older =
|
|
228
|
+
const older =
|
|
229
|
+
start > 0 || (view.headOlder ?? headSourceFor(view.head[0] ?? items[0], runtimeArchiveRange(wire)) !== null);
|
|
214
230
|
const cache = view.cache;
|
|
215
231
|
if (cache && cache.wire === wire && cache.start === start && cache.head === view.head && cache.older === older) {
|
|
216
232
|
return cache.value;
|
|
@@ -223,10 +239,96 @@ export function windowTranscriptSnapshot(entry, wire) {
|
|
|
223
239
|
return value;
|
|
224
240
|
}
|
|
225
241
|
|
|
226
|
-
/**
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
242
|
+
/** Where older rows above `first` come from: durable history aligned by
|
|
243
|
+
* restore ids (which continues into the compaction archive), or — once the
|
|
244
|
+
* runtime's rows are not restored ones (a live compaction archived them, or
|
|
245
|
+
* nothing was restored) — compaction-archive pages alone. */
|
|
246
|
+
function headSourceFor(first, archive) {
|
|
247
|
+
const restoredId = RESTORED_ITEM_ID.test(String(first?.id ?? ''));
|
|
248
|
+
if (restoredId && first?.archived !== true) return restoredAfterFirstMessage(first) || archive ? 'stored' : null;
|
|
249
|
+
if (archive) return 'archive';
|
|
250
|
+
// A compaction archived the first loaded restored row, but the runtime holds
|
|
251
|
+
// no archive range: older rows still align through durable history.
|
|
252
|
+
return restoredId && restoredAfterFirstMessage(first) ? 'stored' : null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Whether a live row shows that a compaction rewrote this runtime's rows. */
|
|
256
|
+
function isCompactionMark(item) {
|
|
257
|
+
return item?.archived === true || (item?.kind === 'statusdone' && item.status === 'compacted');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** The runtime's compaction generation: it only grows, because every
|
|
261
|
+
* compaction adds a divider and archives rows. A loaded head is valid only
|
|
262
|
+
* for the generation it was loaded in. */
|
|
263
|
+
function compactionGeneration(items) {
|
|
264
|
+
return items.reduce((count, item) => count + (isCompactionMark(item) ? 1 : 0), 0);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Where `first` sits in durable history, or -1. Restore ids are positional
|
|
268
|
+
* and only comparable while no compaction touched the live rows (or when
|
|
269
|
+
* `first` itself came from this durable history). Across a compaction the
|
|
270
|
+
* live `hist_…` ids and the durable `arch…`/renumbered ids describe
|
|
271
|
+
* different positions, so nothing but one verified stable identity aligns. */
|
|
272
|
+
function alignedHistoryIndex(history, first, live, fromHistory) {
|
|
273
|
+
if (!first) return -1;
|
|
274
|
+
if (fromHistory || !live.some(isCompactionMark)) return history.findIndex((item) => item?.id === first.id);
|
|
275
|
+
const index = verifiedIdentityIndex(history, first, live);
|
|
276
|
+
return index >= 0 && !prefixRepeatsLiveRow(history.slice(0, index), live) ? index : -1;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Whether a proposed older prefix holds a row whose stable identity (kind +
|
|
280
|
+
* `messageId`, or a tool call id) the live window already shows: joining it
|
|
281
|
+
* would show that row twice. */
|
|
282
|
+
function prefixRepeatsLiveRow(prefix, live) {
|
|
283
|
+
const messages = new Set();
|
|
284
|
+
const calls = new Set();
|
|
285
|
+
for (const item of live) {
|
|
286
|
+
if (item?.messageId) messages.add(`${item.kind}\0${item.messageId}`);
|
|
287
|
+
for (const callId of toolCallIds(item)) calls.add(callId);
|
|
288
|
+
}
|
|
289
|
+
return prefix.some(
|
|
290
|
+
(item) =>
|
|
291
|
+
(item?.messageId && messages.has(`${item.kind}\0${item.messageId}`)) ||
|
|
292
|
+
toolCallIds(item).some((callId) => calls.has(callId))
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** The tool call ids a tool row carries (single card or aggregate). */
|
|
297
|
+
function toolCallIds(item) {
|
|
298
|
+
const ids = Array.isArray(item?.callIds) ? item.callIds : [];
|
|
299
|
+
return item?.callId ? [item.callId, ...ids] : ids;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** The one durable row that is the copy of live row `first`, by stable
|
|
303
|
+
* identity only: same kind and `messageId` for message rows; for tool rows
|
|
304
|
+
* every call id of `first` is in the durable row, which holds no call the
|
|
305
|
+
* live window lacks (an archive aggregate merging a call that is not live
|
|
306
|
+
* would otherwise drop it). No identity, no match, or more than one match:
|
|
307
|
+
* -1 — nothing is guessed. */
|
|
308
|
+
function verifiedIdentityIndex(history, first, live) {
|
|
309
|
+
const calls = toolCallIds(first);
|
|
310
|
+
const liveCalls = new Set(live.flatMap(toolCallIds));
|
|
311
|
+
const matches = [];
|
|
312
|
+
history.forEach((item, index) => {
|
|
313
|
+
if (item?.kind !== first.kind) return;
|
|
314
|
+
if (calls.length) {
|
|
315
|
+
const own = toolCallIds(item);
|
|
316
|
+
if (calls.every((callId) => own.includes(callId)) && own.every((callId) => liveCalls.has(callId))) {
|
|
317
|
+
matches.push(index);
|
|
318
|
+
}
|
|
319
|
+
} else if (first.kind !== 'tool' && first.messageId && item.messageId === first.messageId) {
|
|
320
|
+
matches.push(index);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
return matches.length === 1 ? matches[0] : -1;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** Load history OLDER than the runtime's first item when a grown window
|
|
327
|
+
* reaches past what the runtime holds (a resumed runtime restores only a
|
|
328
|
+
* bounded tail; a compaction moved earlier rows to the display-only
|
|
329
|
+
* archive). Durable items align by their deterministic restore ids; archive
|
|
330
|
+
* pages continue below the archived rows already held. Returns whether
|
|
331
|
+
* durable history was read. */
|
|
230
332
|
export async function loadLiveTranscriptHead(entry, sessionId, readStoredSession) {
|
|
231
333
|
const view = entry.transcriptView;
|
|
232
334
|
if (!view || view.full || !view.paged || typeof readStoredSession !== 'function' || view.headOlder === false) {
|
|
@@ -235,24 +337,52 @@ export async function loadLiveTranscriptHead(entry, sessionId, readStoredSession
|
|
|
235
337
|
// Durable history continues the window only once it shows the runtime's
|
|
236
338
|
// first item; a budgeted page may still be revealing runtime rows.
|
|
237
339
|
if (view.start !== 0) return false;
|
|
238
|
-
const
|
|
239
|
-
const runtimeItems = Array.isArray(items) ? items : [];
|
|
340
|
+
const state = entry.runtime?.getState?.();
|
|
341
|
+
const runtimeItems = Array.isArray(state?.items) ? state.items : [];
|
|
240
342
|
const missing = view.limit - runtimeItems.length - view.head.length;
|
|
241
343
|
const first = view.head[0] ?? runtimeItems[0];
|
|
242
|
-
|
|
344
|
+
const archive = runtimeArchiveRange(state);
|
|
345
|
+
const source = view.headSource || headSourceFor(first, archive);
|
|
346
|
+
if (missing <= 0 || !source) return false;
|
|
347
|
+
const generation = compactionGeneration(runtimeItems);
|
|
348
|
+
if (view.head.length && view.headGeneration !== generation) {
|
|
349
|
+
// A compaction since the head was loaded: keep the rows as they are, and
|
|
350
|
+
// never join older rows across the generation.
|
|
351
|
+
view.headOlder = false;
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
243
354
|
const pageBudget = view.grow ? view.grow.byteBudget : null;
|
|
244
|
-
const
|
|
245
|
-
|
|
355
|
+
const currentItems = () => entry.runtime?.getState?.()?.items;
|
|
356
|
+
const stillCurrent = () =>
|
|
357
|
+
entry.transcriptView === view &&
|
|
358
|
+
(view.head[0] ?? currentItems()?.[0]) === first &&
|
|
359
|
+
compactionGeneration(Array.isArray(currentItems()) ? currentItems() : []) === generation;
|
|
360
|
+
if (source === 'archive') {
|
|
361
|
+
const page = await readStoredSession(sessionId, {
|
|
362
|
+
compactionArchive: { ...archive, skip: view.archiveHeld || 0, limit: missing },
|
|
363
|
+
});
|
|
364
|
+
if (!stillCurrent()) return true;
|
|
365
|
+
const rows = Array.isArray(page?.items) ? page.items : [];
|
|
366
|
+
const from = budgetedStart(rows, rows.length, missing, pageBudget, TRANSCRIPT_WINDOW_MIN_ITEMS);
|
|
367
|
+
view.head = sanitizeForWire(rows.slice(from)).concat(view.head);
|
|
368
|
+
view.archiveHeld = (view.archiveHeld || 0) + rows.length - from;
|
|
369
|
+
view.headSource = 'archive';
|
|
370
|
+
view.headGeneration = generation;
|
|
371
|
+
view.headOlder = from > 0 || page?.hasOlder === true;
|
|
246
372
|
return true;
|
|
247
373
|
}
|
|
374
|
+
const stored = await readStoredSession(sessionId, { transcriptItemLimit: view.limit + runtimeItems.length });
|
|
375
|
+
if (!stillCurrent()) return true;
|
|
248
376
|
const history = Array.isArray(stored?.items) ? stored.items : [];
|
|
249
|
-
const overlap = history.
|
|
377
|
+
const overlap = alignedHistoryIndex(history, first, view.head.concat(runtimeItems), view.head.length > 0);
|
|
250
378
|
if (overlap < 0) {
|
|
251
379
|
view.headOlder = false;
|
|
252
380
|
return true;
|
|
253
381
|
}
|
|
254
382
|
const from = budgetedStart(history, overlap, missing, pageBudget, TRANSCRIPT_WINDOW_MIN_ITEMS);
|
|
255
383
|
view.head = sanitizeForWire(history.slice(from, overlap)).concat(view.head);
|
|
384
|
+
view.headSource = 'stored';
|
|
385
|
+
view.headGeneration = generation;
|
|
256
386
|
view.headOlder = from > 0 || stored.transcriptHasOlder === true;
|
|
257
387
|
return true;
|
|
258
388
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Live execution root of a daemon entry. `entry.cwd` is captured at creation;
|
|
2
|
+
// a workspace transition moves the session's cwd without recreating the entry,
|
|
3
|
+
// so the projected `state.workspace` (cwd + revision) is the authority and the
|
|
4
|
+
// entry mirrors it every time a state is projected.
|
|
5
|
+
|
|
6
|
+
/** Mirror `state.workspace` onto the entry; states without one change nothing. */
|
|
7
|
+
export function syncEntryWorkspace(entry, state) {
|
|
8
|
+
const workspace = state?.workspace;
|
|
9
|
+
if (!entry || !workspace || typeof workspace !== 'object') return;
|
|
10
|
+
if (typeof workspace.cwd === 'string' && workspace.cwd) entry.cwd = workspace.cwd;
|
|
11
|
+
if (Number.isSafeInteger(workspace.revision) && workspace.revision >= 0) {
|
|
12
|
+
entry.workspaceRevision = workspace.revision;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -5,7 +5,7 @@ import { SESSION_CONFIGURE_ACTION_SET, SESSION_READ_ACTION_SET } from '../../ses
|
|
|
5
5
|
import { sanitizeForWire } from '../../session-wire-values.mjs';
|
|
6
6
|
|
|
7
7
|
/** Reads a session without a live runtime answers from disk (readStoredReview). */
|
|
8
|
-
const STORED_REVIEW_ACTIONS = new Set(['getTurnReviewDiff', 'getSessionReviewDiff']);
|
|
8
|
+
const STORED_REVIEW_ACTIONS = new Set(['getTurnReviewDiff', 'getSessionReviewDiff', 'getTurnChanges']);
|
|
9
9
|
|
|
10
10
|
/** One compact description of an action result for the daemon log, never the payload itself. */
|
|
11
11
|
function resultSummary(value) {
|
|
@@ -22,8 +22,16 @@ function requireSessionAction(action, allowed) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export function createSessionActionCalls(ctx) {
|
|
25
|
-
const {
|
|
26
|
-
|
|
25
|
+
const {
|
|
26
|
+
isClosed,
|
|
27
|
+
log,
|
|
28
|
+
listSessions,
|
|
29
|
+
getRemoteSessionState,
|
|
30
|
+
loadProjectStore,
|
|
31
|
+
advanceForCaller,
|
|
32
|
+
readStoredReview,
|
|
33
|
+
responseSessionId,
|
|
34
|
+
} = ctx;
|
|
27
35
|
const { bodyForClient } = ctx.projection;
|
|
28
36
|
const { retainUnwatched } = ctx.retention;
|
|
29
37
|
const { assertAvailable, hostedEntryForSession, entryForSession } = ctx.entries;
|