mixdog 1.2.2 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/package.json +2 -1
- package/scripts/provider-pingpong-live.mjs +509 -0
- package/src/agents/advisor/AGENT.md +1 -1
- package/src/agents/advisor/agent.json +1 -1
- package/src/defaults/shipped-history.json +5 -0
- package/src/defaults/skills/setup/SKILL.md +6 -1
- package/src/defaults/skills/setup/references/actions.md +44 -22
- package/src/defaults/skills/setup/references/surfaces.md +10 -8
- package/src/headless-exec.mjs +15 -8
- package/src/lib/keychain-cjs.cjs +7 -4
- package/src/output-styles/common.md +11 -10
- package/src/rules/agent/AGENT.md +5 -5
- package/src/rules/lead/LEAD.md +5 -5
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +92 -61
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +10 -1
- package/src/runtime/agent/orchestrator/config-providers-merge.mjs +7 -0
- package/src/runtime/agent/orchestrator/config.mjs +5 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +1 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +26 -7
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +112 -7
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +48 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +24 -17
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +7 -6
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +29 -10
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-blocks.mjs +20 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-delta.mjs +15 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +11 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/message-events.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +12 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-outcome.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-state.mjs +14 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +58 -7
- package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +29 -10
- package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +16 -3
- package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +10 -61
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +12 -6
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +114 -72
- package/src/runtime/agent/orchestrator/providers/compat-request-policy.mjs +78 -2
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +37 -5
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +43 -2
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +64 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +70 -6
- package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +34 -7
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +32 -6
- package/src/runtime/agent/orchestrator/providers/deepseek.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +11 -4
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +25 -8
- package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +87 -5
- package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +41 -5
- package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +13 -13
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +355 -37
- package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +93 -47
- package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +55 -10
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +26 -8
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +60 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +246 -42
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-server-tool-usage.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-system-blocks.mjs +27 -6
- package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +388 -0
- package/src/runtime/agent/orchestrator/providers/lib/inclusive-usage.mjs +54 -0
- package/src/runtime/agent/orchestrator/providers/lib/origin-bound-fetch.mjs +16 -0
- package/src/runtime/agent/orchestrator/providers/lib/responses-terminal-fields.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +48 -22
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +119 -18
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +13 -15
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +1 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +33 -1
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +121 -61
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +35 -14
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +37 -7
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/tool-call-acc.mjs +2 -9
- package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +47 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +47 -25
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +151 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +42 -24
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +63 -25
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2901 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +172 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +53 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +103 -26
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +23 -4
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +11 -6
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +15 -23
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +23 -10
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +25 -4
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +10 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-items.mjs +18 -7
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +5 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/text-relay.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +80 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +71 -24
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +323 -0
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +44 -16
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +7 -16
- package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/refusals.mjs +60 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +51 -35
- package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +30 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +12 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/terminal-frames.mjs +25 -6
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +11 -3
- package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +170 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +75 -70
- package/src/runtime/agent/orchestrator/providers/openrouter.mjs +75 -0
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +147 -8
- package/src/runtime/agent/orchestrator/providers/registry.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +5 -0
- package/src/runtime/agent/orchestrator/providers/transport-scope.d.mts +6 -0
- package/src/runtime/agent/orchestrator/providers/transport-scope.mjs +28 -0
- package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +157 -0
- package/src/runtime/agent/orchestrator/providers/wire-route.mjs +512 -0
- package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +1131 -0
- package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +183 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +100 -0
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +21 -0
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -0
- package/src/runtime/agent/orchestrator/runtime-core/provider-request-snapshot.mjs +7 -3
- package/src/runtime/agent/orchestrator/runtime-core/session-title.mjs +21 -1
- package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +14 -3
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +526 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +10 -7
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +176 -71
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +62 -10
- package/src/runtime/agent/orchestrator/runtime-core/tool-exposure.mjs +96 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +14 -93
- package/src/runtime/agent/orchestrator/session/compact/summary-anchor.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +120 -0
- package/src/runtime/agent/orchestrator/session/context-tool-schema.mjs +17 -2
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -4
- package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +6 -1
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +91 -1
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +56 -0
- package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +18 -8
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +21 -10
- package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +132 -0
- package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +30 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +63 -20
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-call-resolution.mjs +95 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +30 -3
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +150 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +34 -12
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +57 -25
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +208 -14
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +15 -3
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +25 -3
- package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -2
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/manager.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +96 -0
- package/src/runtime/agent/orchestrator/session/side-question.mjs +192 -0
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +17 -3
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +58 -2
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +41 -28
- package/src/runtime/agent/orchestrator/session/store/session-observers.mjs +4 -2
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +12 -1
- package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/store-summary-archived-agent.mjs +34 -15
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +10 -1
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/store.mjs +19 -3
- package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +77 -0
- package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +8 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +14 -10
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +49 -34
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +23 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +73 -39
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +18 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -4
- package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +88 -12
- package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +2 -0
- package/src/runtime/computer-bridge/action-schema.mjs +44 -21
- package/src/runtime/github/tool.mjs +92 -2
- package/src/runtime/memory/index.mjs +1 -0
- package/src/runtime/memory/lib/http-router/ingest-routes.mjs +22 -1
- package/src/runtime/memory/lib/ingest/transcript-offsets.mjs +2 -0
- package/src/runtime/memory/lib/pg/adapter.mjs +98 -1
- package/src/runtime/memory/lib/pg/process.mjs +31 -26
- package/src/runtime/memory/lib/pg/run-child.mjs +49 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +108 -3
- package/src/runtime/memory/lib/runtime-fetcher.mjs +12 -13
- package/src/runtime/memory/lib/transcript-ingest.mjs +14 -0
- package/src/runtime/office/README.md +1 -1
- package/src/runtime/office/authoring/html-document-author.mjs +2 -0
- package/src/runtime/office/authoring/pptx-contact-sheet.mjs +2 -0
- package/src/runtime/office/authoring/pptx-html-measure.mjs +2 -0
- package/src/runtime/office/authoring/pptx-html-runner.mjs +2 -0
- package/src/runtime/office/authoring/xlsx-html-runner.mjs +2 -0
- package/src/runtime/office/core/office-actions-read.mjs +3 -0
- package/src/runtime/office/core/office-render-preview.mjs +2 -0
- package/src/runtime/office/core/office-session-dispatch.mjs +7 -0
- package/src/runtime/office/core/office-sessionless-actions.mjs +8 -0
- package/src/runtime/office/core/office-sessions.mjs +5 -0
- package/src/runtime/office/core/office-transactions.mjs +3 -0
- package/src/runtime/office/core/office-write-ledger.mjs +98 -0
- package/src/runtime/office/design/library/templates/mixdog-executive.mixdog-edit.pptx +0 -0
- package/src/runtime/office/index.mjs +30 -0
- package/src/runtime/office/pdf/pdf-edit-document.mjs +2 -0
- package/src/runtime/office/pdf/pdf-render.mjs +23 -7
- package/src/runtime/office/pdf/pdf-security.mjs +2 -0
- package/src/runtime/office/portable/portable-opc.mjs +4 -1
- package/src/runtime/office/quality/visual-diff.mjs +2 -0
- package/src/runtime/shared/bounded-file-read.mjs +13 -0
- package/src/runtime/shared/bounded-line-diff.mjs +182 -0
- package/src/runtime/shared/buffered-appender.mjs +18 -0
- package/src/runtime/shared/config.mjs +78 -18
- package/src/runtime/shared/err-text.mjs +6 -0
- package/src/runtime/shared/file-permissions.d.mts +6 -1
- package/src/runtime/shared/file-permissions.mjs +54 -6
- package/src/runtime/shared/llm/anthropic-effort.mjs +6 -0
- package/src/runtime/shared/llm/usage-accounting.mjs +5 -2
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +3 -0
- package/src/runtime/shared/loopback-listener.mjs +9 -0
- package/src/runtime/shared/provider-api-key.mjs +2 -0
- package/src/runtime/shared/review-diff.mjs +52 -0
- package/src/runtime/shared/session-sidebar-host.d.mts +4 -0
- package/src/runtime/shared/session-sidebar-host.mjs +38 -0
- package/src/runtime/shared/slow-span.mjs +114 -0
- package/src/runtime/shared/statusline/statusline-segments.mjs +28 -4
- package/src/runtime/shared/tool-card-model/card-header.mjs +6 -1
- package/src/runtime/shared/tool-card-model/generic-detail.mjs +39 -5
- package/src/runtime/shared/tool-card-model.mjs +5 -2
- package/src/runtime/shared/tool-output-limit.mjs +4 -0
- package/src/runtime/shared/tool-primitives.mjs +13 -0
- package/src/runtime/shared/tool-result-summary.mjs +8 -1
- package/src/runtime/shared/tool-surface.mjs +18 -3
- package/src/runtime/shared/tool-work-units.mjs +40 -4
- package/src/runtime/shared/transcript-writer/purge.mjs +74 -0
- package/src/runtime/shared/transcript-writer.mjs +10 -2
- package/src/runtime/shared/turn-changes.mjs +268 -100
- package/src/runtime/shared/turn-review-reply.mjs +8 -39
- package/src/runtime/shared/turn-snapshot-store.mjs +19 -108
- package/src/runtime/shared/turn-snapshot.mjs +857 -683
- package/src/runtime/shared/turn-worktree-snapshot.mjs +183 -255
- package/src/runtime/shared/user-data-guard.mjs +262 -49
- package/src/runtime/shared/worktrees/carry.mjs +531 -0
- package/src/runtime/shared/worktrees/contract.mjs +34 -0
- package/src/runtime/shared/worktrees/file-recovery.mjs +68 -0
- package/src/runtime/shared/worktrees/git-head.mjs +205 -0
- package/src/runtime/shared/worktrees/git.mjs +8 -3
- package/src/runtime/shared/worktrees/maintenance.mjs +1 -1
- package/src/runtime/shared/worktrees/manager.mjs +156 -8
- package/src/runtime/shared/worktrees/merge.mjs +31 -21
- package/src/runtime/shared/worktrees/paths.d.mts +6 -0
- package/src/runtime/terminal-bridge/client.mjs +44 -11
- package/src/runtime/terminal-bridge/tool-defs.mjs +13 -6
- package/src/runtime/tidy/apply.mjs +47 -2
- package/src/runtime/tidy/run-engines.mjs +11 -5
- package/src/session-runtime/agent-tool-routing.mjs +19 -13
- package/src/session-runtime/boot/apis.mjs +13 -5
- package/src/session-runtime/boot/facade.mjs +1 -0
- package/src/session-runtime/boot/infrastructure.mjs +6 -1
- package/src/session-runtime/boot/session.mjs +0 -1
- package/src/session-runtime/boot/tools.mjs +4 -1
- package/src/session-runtime/cwd-plugins/cwd-apply.mjs +12 -5
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +65 -0
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -1
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +11 -4
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +115 -18
- package/src/session-runtime/internal-tool-executor.mjs +6 -2
- package/src/session-runtime/lifecycle/cold-heir.mjs +188 -0
- package/src/session-runtime/lifecycle/inheritance.mjs +71 -27
- package/src/session-runtime/lifecycle/session-catalog.mjs +12 -3
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +6 -0
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +29 -6
- package/src/session-runtime/lifecycle/teardown.mjs +72 -7
- package/src/session-runtime/lifecycle-api.mjs +1 -0
- package/src/session-runtime/mcp-glue/connect.mjs +17 -5
- package/src/session-runtime/model-route/set-route.mjs +2 -2
- package/src/session-runtime/native-web-search-usage.mjs +51 -0
- package/src/session-runtime/native-web-search.mjs +10 -1
- package/src/session-runtime/new-session-config.mjs +4 -2
- package/src/session-runtime/provider-auth/login.mjs +4 -0
- package/src/session-runtime/resource-scope-decorate.mjs +10 -3
- package/src/session-runtime/runtime-review-api.mjs +8 -2
- package/src/session-runtime/services/agent-tool/index-write-queue.mjs +81 -20
- package/src/session-runtime/services/agent-tool/lead-worker-index/forgotten-leads.mjs +29 -0
- package/src/session-runtime/services/agent-tool/lead-worker-index/index-file.mjs +1 -1
- package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +73 -7
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +15 -1
- package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +10 -1
- package/src/session-runtime/services/agent-tool/worker-index.mjs +46 -7
- package/src/session-runtime/services/agent-tool.mjs +3 -0
- package/src/session-runtime/services/channel-admin.mjs +19 -3
- package/src/session-runtime/services/memory-runtime-proxy/calls.mjs +4 -0
- package/src/session-runtime/services/memory-runtime-proxy.mjs +1 -0
- package/src/session-runtime/services/provider-admin.mjs +81 -15
- package/src/session-runtime/session-transcript-purge.mjs +32 -0
- package/src/session-runtime/session-turn-api.mjs +14 -5
- package/src/session-runtime/settings-builtin-tools-api.mjs +9 -0
- package/src/session-runtime/setup-tool/executor.mjs +36 -6
- package/src/session-runtime/setup-tool/extended-actions.mjs +86 -6
- package/src/session-runtime/setup-tool/settings-contract.mjs +13 -3
- package/src/session-runtime/setup-tool/tool-defs.mjs +13 -4
- package/src/session-runtime/skills-api.mjs +3 -5
- package/src/session-runtime/tool-surface.mjs +5 -1
- package/src/session-runtime/turn/compaction-gate.mjs +25 -0
- package/src/session-runtime/turn/session-ops.mjs +7 -5
- package/src/session-runtime/turn/side-question-ops.mjs +22 -0
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +5 -1
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +8 -3
- package/src/session-runtime/turn/turn-run/turn-settle.mjs +10 -0
- package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
- package/src/session-runtime/turn/turn-run.mjs +25 -9
- package/src/session-runtime/usage-stats-api.mjs +43 -11
- package/src/session-runtime/workspace/project-branches.mjs +7 -5
- package/src/session-runtime/workspace/transition-steps.mjs +129 -31
- package/src/session-runtime/workspace/transitions.mjs +190 -15
- package/src/standalone/daemon-agent-control.mjs +9 -1
- package/src/standalone/daemon-local-session-bridge.mjs +14 -1
- package/src/standalone/daemon-owner.mjs +17 -4
- package/src/standalone/daemon-pg-stop.mjs +51 -0
- package/src/standalone/daemon-stop-budget.d.mts +2 -0
- package/src/standalone/daemon-stop-budget.mjs +18 -0
- package/src/standalone/daemon-stored-session-views.mjs +2 -0
- package/src/standalone/daemon-telemetry.mjs +19 -0
- package/src/standalone/daemon.mjs +61 -1
- package/src/standalone/desktop-service-registry.mjs +5 -0
- package/src/standalone/local-session-runtime.mjs +70 -19
- package/src/standalone/session-client.mjs +42 -0
- package/src/standalone/session-protocol.mjs +7 -0
- package/src/standalone/session-proxy/projection.mjs +41 -4
- package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +15 -1
- package/src/standalone/session-runtime-inline-host.mjs +1 -0
- package/src/standalone/session-service/session-calls/session-action.mjs +18 -3
- package/src/standalone/session-service-api.mjs +6 -1
- package/src/standalone/session-state-patch.d.mts +2 -1
- package/src/standalone/session-state-patch.mjs +41 -1
- package/src/tui/App.jsx +25 -0
- package/src/tui/app/app-view/floating-panel.jsx +26 -1
- package/src/tui/app/btw-controller.mjs +118 -0
- package/src/tui/app/create-app-pickers.mjs +2 -0
- package/src/tui/app/shell-layout/row-budgets.mjs +11 -1
- package/src/tui/app/shell-layout/surfaces.mjs +3 -1
- package/src/tui/app/slash-commands.mjs +13 -3
- package/src/tui/app/slash-dispatch/session-commands.mjs +19 -6
- package/src/tui/app/use-global-key-input/approval-shortcut.mjs +12 -0
- package/src/tui/app/use-global-key-input.mjs +5 -12
- package/src/tui/app/use-prompt-handlers.mjs +11 -0
- package/src/tui/components/BtwPanel.jsx +53 -0
- package/src/tui/components/QuestionPanel.jsx +275 -0
- package/src/tui/components/question-panel-model.mjs +156 -0
- package/src/tui/dist/index.mjs +2413 -1465
- package/src/tui/session/context-state.mjs +4 -2
- package/src/tui/session/session-api/commands.mjs +10 -0
- package/src/tui/session/session-api/controls.mjs +2 -2
- package/src/tui/session/session-api/integrations.mjs +9 -2
- package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
- package/src/tui/session/session-api/lifecycle.mjs +2 -1
- package/src/tui/session/session-api/settings.mjs +6 -0
- package/src/tui/session/session-api.mjs +1 -0
- package/src/tui/session/tool-approval.mjs +10 -7
- package/src/tui/session/transcript-spill.mjs +14 -7
- package/src/tui/session/turn-stream.mjs +174 -31
- package/src/tui/session/turn.mjs +12 -5
- package/src/workflows/default/WORKFLOW.md +3 -1
|
@@ -54,12 +54,14 @@ export function createProjectBranchOps({ backend, inspectProject, findRegistered
|
|
|
54
54
|
const path = await registeredProject(projectPath);
|
|
55
55
|
const project = await inspectProject(path);
|
|
56
56
|
const menu = await (await backend()).listBranchOccupancy({ projectPath: path, sessionId: null });
|
|
57
|
+
const workspace = projectWorkspace({
|
|
58
|
+
projectPath: path,
|
|
59
|
+
repoCommonDir: project.repoCommonDir,
|
|
60
|
+
branch: project.currentBranch,
|
|
61
|
+
});
|
|
57
62
|
return {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
repoCommonDir: project.repoCommonDir,
|
|
61
|
-
branch: project.currentBranch,
|
|
62
|
-
}),
|
|
63
|
+
// Detached: branch null and the checked-out commit in baseCommit.
|
|
64
|
+
workspace: project.currentBranch ? workspace : { ...workspace, baseCommit: project.headCommit ?? null },
|
|
63
65
|
branches: branchMenuEntries(menu.branches, menu.occupancy),
|
|
64
66
|
occupancy: menu.occupancy,
|
|
65
67
|
retainedWorktree: null,
|
|
@@ -18,7 +18,9 @@ import { checkoutFolder, projectSubdirOf, samePath } from '../../runtime/shared/
|
|
|
18
18
|
import { setWorkspaceCwdLock, workspaceCwdLock } from './admission-gate.mjs';
|
|
19
19
|
import { copyProjectIdentity } from './workspace-state.mjs';
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
// workspaceReroot: the staged working-folder change and its pending notice
|
|
22
|
+
// (prompt-utils stageWorkspaceReroot); a rollback drops the notice with it.
|
|
23
|
+
const PROMPT_FIELDS = ['bp3EnvironmentContext', 'bp3CoreContext', 'sessionStartMetaInjected', 'workspaceReroot'];
|
|
22
24
|
|
|
23
25
|
/** Everything a prompt refresh rewrites on `target`, restorable exactly. */
|
|
24
26
|
function snapshotPrompt(target) {
|
|
@@ -90,6 +92,8 @@ export function createTransitionSteps({
|
|
|
90
92
|
inspectProject,
|
|
91
93
|
runtimeToken,
|
|
92
94
|
getCurrentCwd,
|
|
95
|
+
getDesktopSession,
|
|
96
|
+
setDesktopSession,
|
|
93
97
|
applyResolvedCwd,
|
|
94
98
|
persistSession,
|
|
95
99
|
refreshWorkspaceContext,
|
|
@@ -97,11 +101,19 @@ export function createTransitionSteps({
|
|
|
97
101
|
updateLeadWorkerCwd,
|
|
98
102
|
workersOf,
|
|
99
103
|
withProjectFolder,
|
|
104
|
+
assertCheckoutQuiet = async () => {},
|
|
100
105
|
}) {
|
|
101
106
|
const owner = (sessionId) => ({ sessionId, runtimeToken, pid: process.pid });
|
|
107
|
+
const moveLeadRow = (sessionId, cwd) => {
|
|
108
|
+
try {
|
|
109
|
+
updateLeadWorkerCwd(sessionId, cwd);
|
|
110
|
+
} catch {
|
|
111
|
+
/* the Lead pool must never break a transition */
|
|
112
|
+
}
|
|
113
|
+
};
|
|
102
114
|
|
|
103
115
|
/** Point this Lead's idle workers at the new root so a reused worker never keeps an old workspace. */
|
|
104
|
-
async function retargetWorkers(session, cwd, journal) {
|
|
116
|
+
async function retargetWorkers(session, cwd, journal, move = null) {
|
|
105
117
|
for (const worker of workersOf(session)) {
|
|
106
118
|
if (samePath(worker.cwd || '', cwd)) continue;
|
|
107
119
|
const priorCwd = worker.cwd;
|
|
@@ -113,7 +125,7 @@ export function createTransitionSteps({
|
|
|
113
125
|
persistSession(worker);
|
|
114
126
|
});
|
|
115
127
|
worker.cwd = cwd;
|
|
116
|
-
await refreshWorkspaceContext(worker, cwd);
|
|
128
|
+
await refreshWorkspaceContext(worker, cwd, move);
|
|
117
129
|
worker.updatedAt = Date.now();
|
|
118
130
|
persistSession(worker);
|
|
119
131
|
}
|
|
@@ -123,16 +135,10 @@ export function createTransitionSteps({
|
|
|
123
135
|
* Apply the new workspace: runtime cwd, record, worker lock, prompt context
|
|
124
136
|
* of the Lead and its idle workers, persistence. Journals the exact undo
|
|
125
137
|
* (cwd, record, lock, prompts, worker roots) for a transaction that fails
|
|
126
|
-
* after this point.
|
|
138
|
+
* after this point. `move` ({ from, changes }: a move out of the project
|
|
139
|
+
* folder into a worktree) extends the prompt's working-folder notice.
|
|
127
140
|
*/
|
|
128
|
-
async function commit(session, previous, fields, journal) {
|
|
129
|
-
const moveLeadRow = (cwd) => {
|
|
130
|
-
try {
|
|
131
|
-
updateLeadWorkerCwd(session.id, cwd);
|
|
132
|
-
} catch {
|
|
133
|
-
/* the Lead pool must never break a transition */
|
|
134
|
-
}
|
|
135
|
-
};
|
|
141
|
+
async function commit(session, previous, fields, journal, move = null) {
|
|
136
142
|
const next = normalizeSessionWorkspace({
|
|
137
143
|
...fields,
|
|
138
144
|
revision: nextRevision(previous),
|
|
@@ -153,22 +159,70 @@ export function createTransitionSteps({
|
|
|
153
159
|
prior.prompt();
|
|
154
160
|
session.updatedAt = Date.now();
|
|
155
161
|
persistSession(session);
|
|
156
|
-
moveLeadRow(prior.sessionCwd || prior.cwd);
|
|
162
|
+
moveLeadRow(session.id, prior.sessionCwd || prior.cwd);
|
|
157
163
|
invalidateCaches();
|
|
158
164
|
});
|
|
159
165
|
applyResolvedCwd(next.cwd);
|
|
160
166
|
session.cwd = next.cwd;
|
|
161
167
|
session.workspace = next;
|
|
162
|
-
moveLeadRow(next.cwd);
|
|
168
|
+
moveLeadRow(session.id, next.cwd);
|
|
163
169
|
setWorkspaceCwdLock(session.id, isWorktreeBound(next) ? next.cwd : null);
|
|
164
|
-
await refreshWorkspaceContext(session, next.cwd);
|
|
165
|
-
await retargetWorkers(session, next.cwd, journal);
|
|
170
|
+
await refreshWorkspaceContext(session, next.cwd, move);
|
|
171
|
+
await retargetWorkers(session, next.cwd, journal, move);
|
|
166
172
|
session.updatedAt = Date.now();
|
|
167
|
-
|
|
173
|
+
// Awaited: a failed save fails the transaction (and its journal undoes it).
|
|
174
|
+
await persistSession(session);
|
|
168
175
|
invalidateCaches();
|
|
169
176
|
return { revision: next.revision, workspace: next };
|
|
170
177
|
}
|
|
171
178
|
|
|
179
|
+
/**
|
|
180
|
+
* A Project switch (session set-project, the desktop/remote setCwd): the
|
|
181
|
+
* runtime cwd, session.cwd, workspace record and desktop classification
|
|
182
|
+
* move together (applyResolvedCwd with persistProjectSelection), then the
|
|
183
|
+
* prompt context of the Lead and its idle workers follows, awaited, before
|
|
184
|
+
* the switch completes. Journals the exact undo, so a failed refresh puts
|
|
185
|
+
* everything back and the failure reaches the caller.
|
|
186
|
+
*/
|
|
187
|
+
async function switchProject(session, cwd, journal, { classifyByMembership = false } = {}) {
|
|
188
|
+
const sessionFields = ['cwd', 'workspace', 'desktopSession'].map((name) => [
|
|
189
|
+
name,
|
|
190
|
+
Object.hasOwn(session, name),
|
|
191
|
+
session[name],
|
|
192
|
+
]);
|
|
193
|
+
const prior = {
|
|
194
|
+
cwd: getCurrentCwd(),
|
|
195
|
+
desktop: getDesktopSession(),
|
|
196
|
+
lock: workspaceCwdLock([session.id]),
|
|
197
|
+
prompt: snapshotPrompt(session),
|
|
198
|
+
};
|
|
199
|
+
journal.push(() => {
|
|
200
|
+
applyResolvedCwd(prior.cwd);
|
|
201
|
+
for (const [name, had, value] of sessionFields) {
|
|
202
|
+
if (had) session[name] = value;
|
|
203
|
+
else delete session[name];
|
|
204
|
+
}
|
|
205
|
+
setDesktopSession(prior.desktop);
|
|
206
|
+
setWorkspaceCwdLock(session.id, prior.lock);
|
|
207
|
+
prior.prompt();
|
|
208
|
+
session.updatedAt = Date.now();
|
|
209
|
+
persistSession(session);
|
|
210
|
+
moveLeadRow(session.id, session.cwd || prior.cwd);
|
|
211
|
+
invalidateCaches();
|
|
212
|
+
});
|
|
213
|
+
const changed = !samePath(prior.cwd || '', cwd);
|
|
214
|
+
applyResolvedCwd(cwd, { persistProjectSelection: true, classifyByMembership });
|
|
215
|
+
if (changed) {
|
|
216
|
+
moveLeadRow(session.id, cwd);
|
|
217
|
+
await refreshWorkspaceContext(session, cwd);
|
|
218
|
+
await retargetWorkers(session, cwd, journal);
|
|
219
|
+
session.updatedAt = Date.now();
|
|
220
|
+
persistSession(session);
|
|
221
|
+
}
|
|
222
|
+
invalidateCaches();
|
|
223
|
+
return { cwd: getCurrentCwd(), workspace: session.workspace ?? null };
|
|
224
|
+
}
|
|
225
|
+
|
|
172
226
|
async function releaseOwner(backendApi, entryId, sessionId) {
|
|
173
227
|
try {
|
|
174
228
|
await backendApi.releaseWorktreeOwner(entryId, owner(sessionId));
|
|
@@ -178,7 +232,14 @@ export function createTransitionSteps({
|
|
|
178
232
|
}
|
|
179
233
|
}
|
|
180
234
|
|
|
181
|
-
|
|
235
|
+
/**
|
|
236
|
+
* `changes` (a new worktree only): the project folder's uncommitted
|
|
237
|
+
* changes — `ask` refuses with WORKTREE_DIRTY_CHOICE while there are any,
|
|
238
|
+
* `leave` keeps them there, `carry` moves them into the worktree while the
|
|
239
|
+
* project folder is held (refused while other sessions work in it);
|
|
240
|
+
* omitted, they are not examined (lazy provisioning).
|
|
241
|
+
*/
|
|
242
|
+
async function enterWorktree(session, previous, { name, nameHint, confirmLarge, changes, signal, txId, journal }) {
|
|
182
243
|
const backendApi = await backend();
|
|
183
244
|
const projectPath = previous.projectPath;
|
|
184
245
|
const project = await inspectProject(projectPath, { signal });
|
|
@@ -197,16 +258,39 @@ export function createTransitionSteps({
|
|
|
197
258
|
// The backend resolves the base branch commit (from the project
|
|
198
259
|
// folder's checked-out branch), sizes and creates the checkout; the
|
|
199
260
|
// txId makes a retried or duplicate send return the same worktree.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
261
|
+
const create = () =>
|
|
262
|
+
backendApi.createManagedWorktree({
|
|
263
|
+
projectPath,
|
|
264
|
+
sessionId: session.id,
|
|
265
|
+
runtimeToken,
|
|
266
|
+
baseBranch: project.currentBranch,
|
|
267
|
+
nameHint: name ?? nameHint ?? '',
|
|
268
|
+
confirmLarge: confirmLarge === true,
|
|
269
|
+
txId,
|
|
270
|
+
signal,
|
|
271
|
+
carryChanges: changes ?? null,
|
|
272
|
+
});
|
|
273
|
+
// A carry clears the project folder: it is held, and every session
|
|
274
|
+
// sharing it must be quiet (no turn, agents or background shells).
|
|
275
|
+
entry =
|
|
276
|
+
changes === 'carry'
|
|
277
|
+
? await withProjectFolder(session.id, project.topLevel || projectPath, async () => {
|
|
278
|
+
await assertCheckoutQuiet(session, project.topLevel || projectPath);
|
|
279
|
+
return create();
|
|
280
|
+
})
|
|
281
|
+
: await create();
|
|
282
|
+
const recoveryRef = entry.changes?.recoveryRef;
|
|
283
|
+
if (recoveryRef) {
|
|
284
|
+
// Anything that fails before the move is committed puts the changes
|
|
285
|
+
// back into the project folder and drops the worktree made for them;
|
|
286
|
+
// a restore that is refused keeps both (and the recovery ref).
|
|
287
|
+
const created = entry;
|
|
288
|
+
journal.push(async () => {
|
|
289
|
+
await backendApi.restoreCarriedChanges({ projectPath, recoveryRef });
|
|
290
|
+
await backendApi.removeManagedWorktree({ entryId: created.id, force: true });
|
|
291
|
+
await backendApi.dropCarryRecoveryRef({ projectPath, recoveryRef });
|
|
292
|
+
});
|
|
293
|
+
}
|
|
210
294
|
}
|
|
211
295
|
// Cancelled after the worktree exists: it stays retained (same txId on
|
|
212
296
|
// retry), the session does not move.
|
|
@@ -228,10 +312,24 @@ export function createTransitionSteps({
|
|
|
228
312
|
baseCommit: entry.baseCommit,
|
|
229
313
|
mergeTarget: project.currentBranch,
|
|
230
314
|
},
|
|
231
|
-
journal
|
|
315
|
+
journal,
|
|
316
|
+
// The tail notice: what happened to the uncommitted changes, and how
|
|
317
|
+
// the project-folder paths of the conversation so far map to the worktree.
|
|
318
|
+
isWorktreeBound(previous) ? null : { from: previous.cwd, changes: entry.changes?.mode ?? null }
|
|
232
319
|
);
|
|
320
|
+
// The move is committed and persisted: the carried changes' recovery ref
|
|
321
|
+
// has done its job (a failure to drop it only leaves a spare ref).
|
|
322
|
+
if (entry.changes?.recoveryRef) {
|
|
323
|
+
await backendApi
|
|
324
|
+
.dropCarryRecoveryRef({ projectPath, recoveryRef: entry.changes.recoveryRef })
|
|
325
|
+
.catch(() => {});
|
|
326
|
+
}
|
|
233
327
|
// Creation notes (submodules, include entries missing or already present).
|
|
234
|
-
return {
|
|
328
|
+
return {
|
|
329
|
+
...entered,
|
|
330
|
+
warnings: Array.isArray(entry.warnings) ? [...entry.warnings] : [],
|
|
331
|
+
...(entry.changes ? { changes: { mode: entry.changes.mode, count: entry.changes.count } } : {}),
|
|
332
|
+
};
|
|
235
333
|
}
|
|
236
334
|
|
|
237
335
|
async function enterProjectBranch(session, previous, { branch, create, signal, journal }) {
|
|
@@ -360,5 +458,5 @@ export function createTransitionSteps({
|
|
|
360
458
|
return { ...result, removed };
|
|
361
459
|
}
|
|
362
460
|
|
|
363
|
-
return { enterWorktree, enterProjectBranch, removeRetainedWorktree };
|
|
461
|
+
return { enterWorktree, enterProjectBranch, removeRetainedWorktree, switchProject };
|
|
364
462
|
}
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
* (runtime/shared/worktrees/manager.mjs), loaded on first use.
|
|
13
13
|
*/
|
|
14
14
|
import { randomUUID } from 'node:crypto';
|
|
15
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
15
16
|
import {
|
|
17
|
+
WORKTREE_CARRY_CHOICES,
|
|
16
18
|
branchMenuEntries,
|
|
17
19
|
isWorktreeBound,
|
|
18
20
|
isWorktreeError,
|
|
@@ -24,6 +26,7 @@ import { normalizeWorktreeSettings } from '../../runtime/shared/worktrees/settin
|
|
|
24
26
|
import * as maintenance from '../../runtime/shared/worktrees/maintenance.mjs';
|
|
25
27
|
import { inspectRepo, throwIfAborted } from '../../runtime/shared/worktrees/git.mjs';
|
|
26
28
|
import { isWithin, samePath } from '../../runtime/shared/worktrees/paths.mjs';
|
|
29
|
+
import { readGitHead } from '../../runtime/shared/worktrees/git-head.mjs';
|
|
27
30
|
import { sessionHasConversationMessages } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
28
31
|
import { buildWorkspacePromptContext } from '../../runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs';
|
|
29
32
|
import {
|
|
@@ -53,6 +56,9 @@ async function defaultOwnShellJobCount(sessionIds) {
|
|
|
53
56
|
|
|
54
57
|
const lower = (value) => String(value || '').toLowerCase();
|
|
55
58
|
|
|
59
|
+
const WORKTREE_BOUND_SWITCH =
|
|
60
|
+
'The session works in a managed worktree; exit the worktree first (switch to a project-folder branch, or merge or remove the worktree) before moving it to another project.';
|
|
61
|
+
|
|
56
62
|
/** The registered Project (projects.json) at `path`, or null. */
|
|
57
63
|
async function defaultRegisteredProject(path) {
|
|
58
64
|
const { listProjectsStrict } = await import('../../runtime/shared/projects.mjs');
|
|
@@ -62,9 +68,12 @@ async function defaultRegisteredProject(path) {
|
|
|
62
68
|
/**
|
|
63
69
|
* Re-root one session's prompt at `cwd`: environment facts and instruction
|
|
64
70
|
* files for every session, project memory for the Lead only (workers never
|
|
65
|
-
* carried it).
|
|
71
|
+
* carried it). A session with a conversation keeps its system prompt (cache
|
|
72
|
+
* intact) and gets a tail notice instead (refreshSessionWorkspaceContext);
|
|
73
|
+
* `move` ({ from, changes }: the session left the project folder `from` for
|
|
74
|
+
* a worktree) adds the uncommitted-changes and path lines to that notice.
|
|
66
75
|
*/
|
|
67
|
-
export async function refreshWorkspacePrompt(session, cwd, { loadCoreMemoryContext } = {}) {
|
|
76
|
+
export async function refreshWorkspacePrompt(session, cwd, { loadCoreMemoryContext, move = null } = {}) {
|
|
68
77
|
const { environmentContext, instructionFilesContext } = buildWorkspacePromptContext(session, cwd);
|
|
69
78
|
const coreMemoryContext =
|
|
70
79
|
session?.owner === AGENT_OWNER || typeof loadCoreMemoryContext !== 'function'
|
|
@@ -75,6 +84,7 @@ export async function refreshWorkspacePrompt(session, cwd, { loadCoreMemoryConte
|
|
|
75
84
|
environmentContext,
|
|
76
85
|
coreMemoryContext,
|
|
77
86
|
instructionFilesContext,
|
|
87
|
+
...(move ? { move } : {}),
|
|
78
88
|
});
|
|
79
89
|
}
|
|
80
90
|
|
|
@@ -137,9 +147,10 @@ const TRANSITION_STATE = { worktree: 'provisioning', branch: 'switching', 'remov
|
|
|
137
147
|
* @param {object} deps
|
|
138
148
|
* @param {() => any} deps.getSession The Lead session this runtime hosts.
|
|
139
149
|
* @param {() => string} deps.getCurrentCwd
|
|
140
|
-
* @param {(cwd: string) => unknown} deps.applyResolvedCwd Retargets rt cwd, session.cwd and the cwd override.
|
|
150
|
+
* @param {(cwd: string, options?: { persistProjectSelection?: boolean }) => unknown} deps.applyResolvedCwd Retargets rt cwd, session.cwd and the cwd override (and, persisting, the Project selection).
|
|
151
|
+
* @param {(desktopSession: any) => unknown} [deps.setDesktopSession] Restores the runtime's desktop metadata on a rollback.
|
|
141
152
|
* @param {(session: any) => unknown} deps.persistSession
|
|
142
|
-
* @param {(session: any, cwd: string) => Promise<unknown>} deps.refreshWorkspaceContext
|
|
153
|
+
* @param {(session: any, cwd: string, move?: { from: string, changes: 'carried'|'left'|null }|null) => Promise<unknown>} deps.refreshWorkspaceContext
|
|
143
154
|
* @param {() => number} deps.getActiveTurnCount
|
|
144
155
|
* @param {() => { agentWorkers?: any[], agentJobs?: any[] }} deps.agentStatusState
|
|
145
156
|
* @param {(sessionId: string, cwd: string) => unknown} [deps.updateLeadWorkerCwd] Moves the Lead's pool row (lead-workers.json).
|
|
@@ -149,6 +160,7 @@ export function createWorkspaceTransitions({
|
|
|
149
160
|
getReservedSessionId = () => null,
|
|
150
161
|
getCurrentCwd,
|
|
151
162
|
getDesktopSession = () => null,
|
|
163
|
+
setDesktopSession = () => {},
|
|
152
164
|
getConfig = () => null,
|
|
153
165
|
createCurrentSession = null,
|
|
154
166
|
applyResolvedCwd,
|
|
@@ -207,6 +219,37 @@ export function createWorkspaceTransitions({
|
|
|
207
219
|
return normalizeSessionWorkspace({ projectPath, cwd: getCurrentCwd() || projectPath, revision: 0 });
|
|
208
220
|
}
|
|
209
221
|
|
|
222
|
+
let liveWorkspace = null;
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* The workspace the session publishes: a worktree-bound record as stored; a
|
|
226
|
+
* project-folder session (never moved, back from a worktree, or carrying a
|
|
227
|
+
* transition stub) with the folder's checked-out branch / detached commit and
|
|
228
|
+
* repoCommonDir read straight from HEAD — no git process. Null outside a git
|
|
229
|
+
* repository for a session without a record. Identity-stable while equal.
|
|
230
|
+
*/
|
|
231
|
+
function getLiveWorkspace() {
|
|
232
|
+
const session = getSession();
|
|
233
|
+
const stored = readSessionWorkspace(session);
|
|
234
|
+
if (isWorktreeBound(stored)) return session.workspace;
|
|
235
|
+
let base;
|
|
236
|
+
try {
|
|
237
|
+
base = stored ?? currentWorkspace(session);
|
|
238
|
+
} catch {
|
|
239
|
+
return session?.workspace ?? null;
|
|
240
|
+
}
|
|
241
|
+
const head = readGitHead(base.cwd);
|
|
242
|
+
if (!head) return session?.workspace ?? null;
|
|
243
|
+
const next = {
|
|
244
|
+
...base,
|
|
245
|
+
repoCommonDir: head.repoCommonDir,
|
|
246
|
+
branch: head.branch,
|
|
247
|
+
baseCommit: head.branch ? base.baseCommit : head.commit,
|
|
248
|
+
};
|
|
249
|
+
if (!liveWorkspace || !isDeepStrictEqual(liveWorkspace, next)) liveWorkspace = next;
|
|
250
|
+
return liveWorkspace;
|
|
251
|
+
}
|
|
252
|
+
|
|
210
253
|
function isSessionActive(session) {
|
|
211
254
|
const runtime = sessionManager?.getSessionRuntime?.(session.id);
|
|
212
255
|
if (runtime?.controller?.signal && !runtime.controller.signal.aborted) return true;
|
|
@@ -223,8 +266,11 @@ export function createWorkspaceTransitions({
|
|
|
223
266
|
(candidate.ownerSessionId === session.id || candidate.parentSessionId === session.id)
|
|
224
267
|
);
|
|
225
268
|
|
|
226
|
-
/**
|
|
227
|
-
|
|
269
|
+
/**
|
|
270
|
+
* WORKTREE_BUSY_JOBS while this Lead has a turn, workers or background
|
|
271
|
+
* shells running (`shellJobs: false` tolerates background shells).
|
|
272
|
+
*/
|
|
273
|
+
async function assertLeadIdle(session, { ownTurn = false, shellJobs: checkShellJobs = true } = {}) {
|
|
228
274
|
if (!ownTurn && getActiveTurnCount() > 0) {
|
|
229
275
|
throw worktreeError('WORKTREE_BUSY_JOBS', 'The session is running a turn; wait for it to finish.', {
|
|
230
276
|
reason: 'turn',
|
|
@@ -240,6 +286,7 @@ export function createWorkspaceTransitions({
|
|
|
240
286
|
if (busyWorkers || busyJobs) {
|
|
241
287
|
throw worktreeError('WORKTREE_BUSY_JOBS', undefined, { reason: 'workers', workers: busyWorkers, jobs: busyJobs });
|
|
242
288
|
}
|
|
289
|
+
if (!checkShellJobs) return;
|
|
243
290
|
const ids = new Set([session.id, ...workers.map((row) => row?.sessionId).filter(Boolean)]);
|
|
244
291
|
for (const worker of workersOf(session)) ids.add(worker.id);
|
|
245
292
|
const shellJobs = await countOwnShellJobs([...ids]);
|
|
@@ -284,11 +331,44 @@ export function createWorkspaceTransitions({
|
|
|
284
331
|
}
|
|
285
332
|
}
|
|
286
333
|
|
|
334
|
+
/**
|
|
335
|
+
* Before a carry clears the shared checkout `folder` (held by the caller,
|
|
336
|
+
* so no new turn starts there): WORKTREE_BUSY_JOBS while any other session
|
|
337
|
+
* working in it — idle or not, this Lead's workers included — owns a
|
|
338
|
+
* running background shell (holdProjectFolder already refused running
|
|
339
|
+
* turns and agents there, assertLeadIdle this Lead's own). Processes
|
|
340
|
+
* outside Mixdog (editors, watchers) cannot be excluded.
|
|
341
|
+
*/
|
|
342
|
+
async function assertCheckoutQuiet(session, folder) {
|
|
343
|
+
const sharing = listLiveSessions()
|
|
344
|
+
.filter(
|
|
345
|
+
(candidate) =>
|
|
346
|
+
candidate?.id &&
|
|
347
|
+
candidate.id !== session.id &&
|
|
348
|
+
candidate.closed !== true &&
|
|
349
|
+
typeof candidate.cwd === 'string' &&
|
|
350
|
+
isWithin(folder, candidate.cwd)
|
|
351
|
+
)
|
|
352
|
+
.map((candidate) => candidate.id);
|
|
353
|
+
if (!sharing.length) return;
|
|
354
|
+
const shellJobs = await countOwnShellJobs(sharing);
|
|
355
|
+
if (shellJobs > 0) {
|
|
356
|
+
throw worktreeError(
|
|
357
|
+
'WORKTREE_BUSY_JOBS',
|
|
358
|
+
'Background shells of other sessions are running in the project folder; carrying the changes needs it quiet.',
|
|
359
|
+
{ reason: 'folder-shell-jobs', shellJobs }
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
287
364
|
const steps = createTransitionSteps({
|
|
365
|
+
assertCheckoutQuiet,
|
|
288
366
|
backend,
|
|
289
367
|
inspectProject,
|
|
290
368
|
runtimeToken,
|
|
291
369
|
getCurrentCwd,
|
|
370
|
+
getDesktopSession,
|
|
371
|
+
setDesktopSession,
|
|
292
372
|
applyResolvedCwd,
|
|
293
373
|
persistSession,
|
|
294
374
|
refreshWorkspaceContext,
|
|
@@ -331,6 +411,7 @@ export function createWorkspaceTransitions({
|
|
|
331
411
|
name: target.name,
|
|
332
412
|
nameHint: options.nameHint,
|
|
333
413
|
confirmLarge: options.confirmLarge,
|
|
414
|
+
changes: options.changes,
|
|
334
415
|
});
|
|
335
416
|
}
|
|
336
417
|
if (target.kind === 'branch') {
|
|
@@ -442,9 +523,22 @@ export function createWorkspaceTransitions({
|
|
|
442
523
|
});
|
|
443
524
|
// A session in the project folder (never moved, or back from a worktree)
|
|
444
525
|
// is on whatever that folder has checked out now.
|
|
445
|
-
|
|
526
|
+
// Its repository is the project's (a never-moved session has no stored
|
|
527
|
+
// record), and a detached checkout shows branch null with the commit in
|
|
528
|
+
// baseCommit — never mistaken for "not a repository".
|
|
529
|
+
let view = workspace;
|
|
530
|
+
if (!isWorktreeBound(workspace)) {
|
|
531
|
+
const project = await inspectProject(workspace.projectPath);
|
|
532
|
+
const checkedOut = project.currentBranch ?? null;
|
|
533
|
+
view = {
|
|
534
|
+
...workspace,
|
|
535
|
+
repoCommonDir: project.repoCommonDir ?? workspace.repoCommonDir,
|
|
536
|
+
branch: checkedOut,
|
|
537
|
+
baseCommit: checkedOut ? workspace.baseCommit : (project.headCommit ?? null),
|
|
538
|
+
};
|
|
539
|
+
}
|
|
446
540
|
return {
|
|
447
|
-
workspace:
|
|
541
|
+
workspace: view,
|
|
448
542
|
pendingWorktree: pendingIntent ? { name: pendingIntent.name } : null,
|
|
449
543
|
branches: branchMenuEntries(menu.branches, menu.occupancy),
|
|
450
544
|
occupancy: menu.occupancy,
|
|
@@ -510,17 +604,28 @@ export function createWorkspaceTransitions({
|
|
|
510
604
|
* reuse it. Typed failures (large repository, disk, busy) therefore reach
|
|
511
605
|
* the sender while its draft is still unsent, and the turn that follows
|
|
512
606
|
* finds the session already bound.
|
|
607
|
+
*
|
|
608
|
+
* A provisioning that creates a new worktree decides the project folder's
|
|
609
|
+
* uncommitted changes (tracked and untracked): none → it proceeds; without
|
|
610
|
+
* `carryChanges` it refuses with WORKTREE_DIRTY_CHOICE (`details.count`);
|
|
611
|
+
* `leave` starts the worktree at HEAD and leaves them; `carry` moves them
|
|
612
|
+
* into it (the result's `changes` reports which). Re-entering a retained
|
|
613
|
+
* worktree never touches them.
|
|
513
614
|
*/
|
|
514
615
|
async function requestWorktree({
|
|
515
616
|
name = null,
|
|
516
617
|
confirmLarge = false,
|
|
517
618
|
provision = false,
|
|
619
|
+
carryChanges,
|
|
518
620
|
prompt = '',
|
|
519
621
|
txId = '',
|
|
520
622
|
expectedRevision,
|
|
521
623
|
} = {}) {
|
|
522
624
|
const nameHint = validName(name);
|
|
523
625
|
const expected = validRevision(expectedRevision);
|
|
626
|
+
if (carryChanges !== undefined && carryChanges !== null && !WORKTREE_CARRY_CHOICES.includes(carryChanges)) {
|
|
627
|
+
throw new TypeError(`carryChanges must be one of ${WORKTREE_CARRY_CHOICES.join(', ')}.`);
|
|
628
|
+
}
|
|
524
629
|
const session = provision === true ? await ensureSession() : getSession();
|
|
525
630
|
// No session yet: the menu it was read from had revision 0.
|
|
526
631
|
const current = session?.id ? currentWorkspace(session) : null;
|
|
@@ -536,6 +641,7 @@ export function createWorkspaceTransitions({
|
|
|
536
641
|
return { ...entered, pending: false };
|
|
537
642
|
}
|
|
538
643
|
}
|
|
644
|
+
const priorIntent = pendingIntent;
|
|
539
645
|
pendingIntent = {
|
|
540
646
|
name: nameHint ?? pendingIntent?.name ?? null,
|
|
541
647
|
confirmLarge: confirmLarge === true || pendingIntent?.confirmLarge === true,
|
|
@@ -544,10 +650,21 @@ export function createWorkspaceTransitions({
|
|
|
544
650
|
txId: pendingIntent?.txId ?? null,
|
|
545
651
|
};
|
|
546
652
|
if (provision === true) {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
653
|
+
const intent = pendingIntent;
|
|
654
|
+
try {
|
|
655
|
+
return {
|
|
656
|
+
...(await provisionIntent(session, intent, txId, prompt, {
|
|
657
|
+
expectedRevision: expected,
|
|
658
|
+
changes: carryChanges ?? 'ask',
|
|
659
|
+
})),
|
|
660
|
+
pending: false,
|
|
661
|
+
};
|
|
662
|
+
} catch (error) {
|
|
663
|
+
// A refused move (uncommitted changes to decide, busy, …) is never
|
|
664
|
+
// re-armed for the next turn, where nobody could answer it.
|
|
665
|
+
if (pendingIntent === intent) pendingIntent = priorIntent;
|
|
666
|
+
throw error;
|
|
667
|
+
}
|
|
551
668
|
}
|
|
552
669
|
const workspace = session?.id ? currentWorkspace(session) : null;
|
|
553
670
|
return { revision: workspace?.revision ?? 0, workspace, pending: true };
|
|
@@ -570,15 +687,35 @@ export function createWorkspaceTransitions({
|
|
|
570
687
|
if (cleanup !== true || merged.merged !== true) return merged;
|
|
571
688
|
// "Merge and clean up": with autoCleanup on, the merged worktree goes now
|
|
572
689
|
// (a dirty one stays, reported); off, nothing is removed automatically.
|
|
690
|
+
// The merge is committed: a failed or refused cleanup (dirty, retained
|
|
691
|
+
// file recovery data, ...) is a cleanup notice, never a merge failure.
|
|
573
692
|
if (worktreeSettings().autoCleanup !== true) {
|
|
574
|
-
return {
|
|
693
|
+
return {
|
|
694
|
+
...merged,
|
|
695
|
+
cleanup: { removed: false, reason: 'auto-cleanup-off', message: 'Auto cleanup is off; the worktree is kept.' },
|
|
696
|
+
};
|
|
575
697
|
}
|
|
576
698
|
try {
|
|
577
699
|
const removed = await removeWorktree({ force: false }, { ownTurn });
|
|
578
700
|
return { ...merged, cleanup: removed.removed, revision: removed.revision, workspace: removed.workspace };
|
|
579
701
|
} catch (error) {
|
|
580
|
-
|
|
581
|
-
|
|
702
|
+
// The merge stands: a refused or incomplete cleanup (dirty, unmerged,
|
|
703
|
+
// retained file recovery data, partly removed, …) is reported, never
|
|
704
|
+
// thrown, with the workspace as it is now (an incomplete removal already
|
|
705
|
+
// returned to the project folder).
|
|
706
|
+
const workspace = currentWorkspace(session);
|
|
707
|
+
const code = typeof error?.code === 'string' ? error.code : '';
|
|
708
|
+
return {
|
|
709
|
+
...merged,
|
|
710
|
+
cleanup: {
|
|
711
|
+
removed: false,
|
|
712
|
+
reason: code || 'WORKTREE_CLEANUP_FAILED',
|
|
713
|
+
...(code ? { code } : {}),
|
|
714
|
+
message: String(error?.message || error),
|
|
715
|
+
},
|
|
716
|
+
revision: workspace.revision,
|
|
717
|
+
workspace,
|
|
718
|
+
};
|
|
582
719
|
}
|
|
583
720
|
}
|
|
584
721
|
|
|
@@ -620,6 +757,41 @@ export function createWorkspaceTransitions({
|
|
|
620
757
|
}
|
|
621
758
|
}
|
|
622
759
|
|
|
760
|
+
/**
|
|
761
|
+
* Move the hosted Lead to another Project folder (session set-project, the
|
|
762
|
+
* desktop/remote setCwd): the one authority for a Project switch, admitted
|
|
763
|
+
* like a workspace transition. Refused while a turn runs (`ownTurn`: the
|
|
764
|
+
* Lead's own tool call tolerates its calling turn), while workers run, and
|
|
765
|
+
* while the session works in a managed worktree. Serialized with turns and
|
|
766
|
+
* other workspace changes; the prompt refresh of the Lead and its idle
|
|
767
|
+
* workers completes (or rolls the switch back) before it resolves. Without
|
|
768
|
+
* a session (a draft) only the runtime cwd and the selection move.
|
|
769
|
+
* `classifyByMembership` (the desktop/remote move): a folder outside every
|
|
770
|
+
* registered Project is classified as a task (cwd-apply).
|
|
771
|
+
* @returns {Promise<string>} the runtime cwd after the switch
|
|
772
|
+
*/
|
|
773
|
+
async function switchProject(cwd, { ownTurn = false, classifyByMembership = false } = {}) {
|
|
774
|
+
const selection = { classifyByMembership: classifyByMembership === true };
|
|
775
|
+
const session = getSession();
|
|
776
|
+
if (!session?.id) {
|
|
777
|
+
applyResolvedCwd(cwd, { persistProjectSelection: true, ...selection });
|
|
778
|
+
return getCurrentCwd();
|
|
779
|
+
}
|
|
780
|
+
const leave = beginWorkspaceTransition(session.id, `project:${newTxId()}`);
|
|
781
|
+
const journal = [];
|
|
782
|
+
try {
|
|
783
|
+
await assertLeadIdle(session, { ownTurn: ownTurn === true, shellJobs: false });
|
|
784
|
+
if (isWorktreeBound(readSessionWorkspace(session))) throw new Error(WORKTREE_BOUND_SWITCH);
|
|
785
|
+
await steps.switchProject(session, cwd, journal, selection);
|
|
786
|
+
return getCurrentCwd();
|
|
787
|
+
} catch (error) {
|
|
788
|
+
await undoAll(journal, error);
|
|
789
|
+
throw error;
|
|
790
|
+
} finally {
|
|
791
|
+
leave();
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
623
795
|
async function removeWorktree({ force = false, expectedRevision } = {}, { ownTurn = false } = {}) {
|
|
624
796
|
const expected = validRevision(expectedRevision);
|
|
625
797
|
const session = hostedSession(null);
|
|
@@ -636,6 +808,8 @@ export function createWorkspaceTransitions({
|
|
|
636
808
|
admitTurnWorkspace,
|
|
637
809
|
/** The durable workspace record of the hosted session (null: project folder, never moved). */
|
|
638
810
|
getWorkspace: () => getSession()?.workspace ?? null,
|
|
811
|
+
/** What the session snapshot publishes (see getLiveWorkspace). */
|
|
812
|
+
getLiveWorkspace,
|
|
639
813
|
getBranchMenu,
|
|
640
814
|
getProjectBranchMenu: projectBranches.getProjectBranchMenu,
|
|
641
815
|
selectBranch,
|
|
@@ -644,6 +818,7 @@ export function createWorkspaceTransitions({
|
|
|
644
818
|
requestWorktree,
|
|
645
819
|
mergeWorkspace,
|
|
646
820
|
removeWorktree,
|
|
821
|
+
switchProject,
|
|
647
822
|
listWorktrees,
|
|
648
823
|
pruneWorktrees,
|
|
649
824
|
cleanupDeletedSession,
|
|
@@ -68,5 +68,13 @@ export function createCanonicalAgentControl({ getSessionService, getSessionRunti
|
|
|
68
68
|
return await tool.execute(args, scopedContext);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
/** Graceful shutdown: drain and release the canonical agent's index writers.
|
|
72
|
+
* Never builds the tool just to dispose it. */
|
|
73
|
+
async function dispose() {
|
|
74
|
+
if (!canonicalAgentToolPromise) return;
|
|
75
|
+
const tool = await canonicalAgentToolPromise.catch(() => null);
|
|
76
|
+
await tool?.disposeAsync?.({ settle: true });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { canonicalAgentTool, execute, dispose };
|
|
72
80
|
}
|
|
@@ -22,9 +22,22 @@ export function createLocalSessionBridge({ getSessionService, log, pid = process
|
|
|
22
22
|
return createSessionProtocolClient({
|
|
23
23
|
call(name, args = {}, options = {}) {
|
|
24
24
|
if (closed) throw new Error('daemon-local session client is closed');
|
|
25
|
-
|
|
25
|
+
const signal = options?.signal;
|
|
26
|
+
signal?.throwIfAborted();
|
|
27
|
+
const result = getSessionService().handleCall(name, args, {
|
|
26
28
|
clientToken,
|
|
27
29
|
...(options?.callId ? { callId: String(options.callId) } : {}),
|
|
30
|
+
...(signal ? { signal } : {}),
|
|
31
|
+
});
|
|
32
|
+
if (!signal) return result;
|
|
33
|
+
// The in-process service has no cancellation of its own: an abort
|
|
34
|
+
// frees the caller, whose late reply is dropped.
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const onAbort = () => reject(signal.reason);
|
|
37
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
38
|
+
Promise.resolve(result)
|
|
39
|
+
.then(resolve, reject)
|
|
40
|
+
.finally(() => signal.removeEventListener('abort', onAbort));
|
|
28
41
|
});
|
|
29
42
|
},
|
|
30
43
|
async close(reason = 'local session view closed') {
|