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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// side-question-store.mjs — the in-memory state behind /btw side questions:
|
|
2
|
+
// per session, the latest provider request the main loop sent (the request a
|
|
3
|
+
// side question forks) and the side question in flight. It holds live
|
|
4
|
+
// provider/tool objects, so it never reaches the session record or disk.
|
|
5
|
+
import { isAgentOwner } from '../agent-owner.mjs';
|
|
6
|
+
|
|
7
|
+
// sessionId -> { provider, model, messages, tools, opts, response }
|
|
8
|
+
const requests = new Map();
|
|
9
|
+
// sessionId -> AbortController of the side question in flight.
|
|
10
|
+
const inFlight = new Map();
|
|
11
|
+
|
|
12
|
+
// Send options that steer the live loop rather than shape the request: its
|
|
13
|
+
// abort signal, a one-shot cache-break trace tag and the per-request stall
|
|
14
|
+
// budget. A forced tool choice is dropped too (a side question must be able
|
|
15
|
+
// to answer in text); the hard-cap `none` choice is kept.
|
|
16
|
+
const LOOP_ONLY_OPTIONS = new Set(['signal', 'cacheBreakIntent', '_stallRetryBudget', 'toolChoice']);
|
|
17
|
+
|
|
18
|
+
function requestOptions(opts) {
|
|
19
|
+
const out = {};
|
|
20
|
+
for (const [key, value] of Object.entries(opts || {})) {
|
|
21
|
+
if (typeof value === 'function' || LOOP_ONLY_OPTIONS.has(key)) continue;
|
|
22
|
+
out[key] = value;
|
|
23
|
+
}
|
|
24
|
+
if (opts?.toolChoice === 'none') out.toolChoice = 'none';
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A request in the stored shape: its own message list and only the send
|
|
29
|
+
* options a fork may reuse. `response` is null until the reply settles. */
|
|
30
|
+
export function sideQuestionRequestSnapshot({ provider, model, messages, tools, opts }) {
|
|
31
|
+
return {
|
|
32
|
+
provider,
|
|
33
|
+
model,
|
|
34
|
+
messages: [...messages],
|
|
35
|
+
tools: Array.isArray(tools) ? tools : [],
|
|
36
|
+
opts: requestOptions(opts),
|
|
37
|
+
response: null,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Record a provider request the main loop is sending as the session's latest
|
|
43
|
+
* one. Agent sessions record nothing (/btw is a Lead command). The handle
|
|
44
|
+
* attaches the reply once the send proceeds, or puts the previous request
|
|
45
|
+
* back when this one fails, so only a successfully sent request is kept.
|
|
46
|
+
*/
|
|
47
|
+
export function captureSideQuestionRequest({ sessionId, sessionRef, provider, model, messages, tools, opts }) {
|
|
48
|
+
if (!sessionId || !provider || isAgentOwner(sessionRef)) return null;
|
|
49
|
+
const previous = requests.get(sessionId);
|
|
50
|
+
const request = sideQuestionRequestSnapshot({ provider, model, messages, tools, opts });
|
|
51
|
+
requests.set(sessionId, request);
|
|
52
|
+
return {
|
|
53
|
+
settle(response) {
|
|
54
|
+
request.response = response || null;
|
|
55
|
+
},
|
|
56
|
+
discard() {
|
|
57
|
+
if (requests.get(sessionId) !== request) return;
|
|
58
|
+
if (previous) requests.set(sessionId, previous);
|
|
59
|
+
else requests.delete(sessionId);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function sideQuestionRequest(sessionId) {
|
|
65
|
+
return (sessionId && requests.get(sessionId)) || null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Start the session's side question, aborting the one still in flight. */
|
|
69
|
+
export function beginSideQuestion(sessionId) {
|
|
70
|
+
cancelSideQuestion(sessionId);
|
|
71
|
+
const controller = new AbortController();
|
|
72
|
+
inFlight.set(sessionId, controller);
|
|
73
|
+
return {
|
|
74
|
+
signal: controller.signal,
|
|
75
|
+
end() {
|
|
76
|
+
if (inFlight.get(sessionId) === controller) inFlight.delete(sessionId);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function cancelSideQuestion(sessionId) {
|
|
82
|
+
const controller = sessionId ? inFlight.get(sessionId) : null;
|
|
83
|
+
if (!controller) return false;
|
|
84
|
+
inFlight.delete(sessionId);
|
|
85
|
+
controller.abort(new Error('Side question canceled.'));
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The transcript the saved request carried is gone (clear, rewind): a later
|
|
90
|
+
* side question waits for the next request. `cancel` also aborts the side
|
|
91
|
+
* question in flight (session close / runtime unload). */
|
|
92
|
+
export function forgetSideQuestion(sessionId, { cancel = false } = {}) {
|
|
93
|
+
if (!sessionId) return;
|
|
94
|
+
requests.delete(sessionId);
|
|
95
|
+
if (cancel) cancelSideQuestion(sessionId);
|
|
96
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// side-question.mjs — /btw: a quick question answered while the main turn
|
|
2
|
+
// keeps working. The answer comes from a cache-sharing fork of the session's
|
|
3
|
+
// latest provider request (side-question-store.mjs): the same model, system
|
|
4
|
+
// prompt, tools and cache-relevant options and the same message prefix, so
|
|
5
|
+
// the provider's prompt cache serves the prefix. Only the reply that request
|
|
6
|
+
// produced and one question are appended. One provider call, no tool loop;
|
|
7
|
+
// the answer is returned to the caller and never enters the transcript.
|
|
8
|
+
import { collectAssistantToolCallIds } from './context-tool-pairs.mjs';
|
|
9
|
+
import { buildIntermediateAssistantMessage, buildToolCallAssistantMessage } from './loop/assistant-commit.mjs';
|
|
10
|
+
import { logSessionLlmUsage } from './manager/ask-turn-commit.mjs';
|
|
11
|
+
import { disposeTransportScope, forkTransportScope } from '../providers/transport-scope.mjs';
|
|
12
|
+
import { isAgentOwner } from '../agent-owner.mjs';
|
|
13
|
+
import { getProvider } from '../providers/registry.mjs';
|
|
14
|
+
import { prepareTurnEffortConfiguration } from '../providers/effort-configuration.mjs';
|
|
15
|
+
import { providerNativeToolPrefixCount } from '../runtime-core/provider-request-tools.mjs';
|
|
16
|
+
import { snapshotProviderRequestTools } from '../runtime-core/tool-catalog.mjs';
|
|
17
|
+
import { repairTranscriptBeforeProviderSend } from './loop/transcript-repair.mjs';
|
|
18
|
+
import { buildRequestSendOptions } from './manager/ask-turn-loop-options.mjs';
|
|
19
|
+
import { filterModelVisibleSessionMessages } from './manager/message-sanitize.mjs';
|
|
20
|
+
import { hasUserConversationMessage } from './manager/prompt-utils.mjs';
|
|
21
|
+
import { loadSession } from './store.mjs';
|
|
22
|
+
import { projectSyntheticUserEnvelopes } from './synthetic-user-envelope.mjs';
|
|
23
|
+
import { beginSideQuestion, sideQuestionRequest, sideQuestionRequestSnapshot } from './side-question-store.mjs';
|
|
24
|
+
|
|
25
|
+
export { cancelSideQuestion } from './side-question-store.mjs';
|
|
26
|
+
|
|
27
|
+
export const SIDE_QUESTION_NO_REQUEST = 'Send a message first, then try /btw.';
|
|
28
|
+
export const SIDE_QUESTION_TOOL_ATTEMPT =
|
|
29
|
+
'The model tried to use a tool instead of answering. A side question cannot run tools; try asking it differently.';
|
|
30
|
+
const SIDE_QUESTION_EMPTY_ANSWER = 'The model returned no answer.';
|
|
31
|
+
|
|
32
|
+
// The reply's tool calls run in the main session; the fork only needs each
|
|
33
|
+
// call answered so the request stays well-formed.
|
|
34
|
+
const SIDE_QUESTION_TOOL_STUB =
|
|
35
|
+
'This tool call belongs to the main task and is handled there; its result is not part of this side question.';
|
|
36
|
+
|
|
37
|
+
function sideQuestionPrompt(question) {
|
|
38
|
+
return [
|
|
39
|
+
'<side-question>',
|
|
40
|
+
'The user is asking you a quick side question. The main agent keeps working on the task on its own;',
|
|
41
|
+
'this exchange does not reach it and does not change that work.',
|
|
42
|
+
'Answer directly, in this one response, from what this conversation already contains.',
|
|
43
|
+
'No tools are available here: do not call any, and do not offer or promise to do anything.',
|
|
44
|
+
'If the conversation does not tell you the answer, say that you do not know.',
|
|
45
|
+
'',
|
|
46
|
+
question,
|
|
47
|
+
'</side-question>',
|
|
48
|
+
].join('\n');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// The assistant turn the saved request produced, built the way the loop
|
|
52
|
+
// commits it (without the turn's one-shot transcript metadata), plus a stub
|
|
53
|
+
// result per tool call.
|
|
54
|
+
function replyMessages(response) {
|
|
55
|
+
if (!response) return [];
|
|
56
|
+
const reply = response.toolCalls?.length
|
|
57
|
+
? buildToolCallAssistantMessage(response, { calls: response.toolCalls, suppressMidTurnText: false, opts: {} })
|
|
58
|
+
: buildIntermediateAssistantMessage(response, {});
|
|
59
|
+
if (!reply) return [];
|
|
60
|
+
const stubs = collectAssistantToolCallIds(reply).map((id) => ({
|
|
61
|
+
role: 'tool',
|
|
62
|
+
toolCallId: id,
|
|
63
|
+
content: SIDE_QUESTION_TOOL_STUB,
|
|
64
|
+
}));
|
|
65
|
+
return [reply, ...stubs];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The fork of a saved request: its messages unchanged, then the reply and
|
|
70
|
+
* the question. The send options are the saved ones; the provider
|
|
71
|
+
* continuation state follows the reply when it is appended, exactly as the
|
|
72
|
+
* main loop's next request would carry it. `sharedCachePrefixCount` (the
|
|
73
|
+
* shared prefix's message count, which survives model-proxy serialization)
|
|
74
|
+
* keeps an Anthropic message breakpoint on the shared prefix, so the fork reads the
|
|
75
|
+
* saved request's cache entry and writes none for its suffix.
|
|
76
|
+
*/
|
|
77
|
+
export function buildSideQuestionRequest(request, question) {
|
|
78
|
+
const reply = replyMessages(request.response);
|
|
79
|
+
const opts = { ...request.opts };
|
|
80
|
+
if (reply.length && Object.hasOwn(request.response, 'providerState')) {
|
|
81
|
+
opts.providerState = request.response.providerState;
|
|
82
|
+
}
|
|
83
|
+
if (request.messages.length) opts.sharedCachePrefixCount = request.messages.length;
|
|
84
|
+
const messages = [
|
|
85
|
+
...request.messages,
|
|
86
|
+
...reply,
|
|
87
|
+
// A user interjection keeps its own turn (never folded into a preceding
|
|
88
|
+
// tool result), so the prefix's last block reaches the provider unchanged.
|
|
89
|
+
{ role: 'user', content: sideQuestionPrompt(question), meta: { source: 'steering' } },
|
|
90
|
+
];
|
|
91
|
+
return { messages, opts };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The request the main loop would send next for a session that has none saved
|
|
96
|
+
* (the runtime restarted, or the saved one was forgotten): the session's own
|
|
97
|
+
* provider, model, tools, send options and transcript, assembled by the same
|
|
98
|
+
* pieces the loop uses, without the loop's side effects (no compaction, no
|
|
99
|
+
* transcript repair in place, nothing written to the session). Null when the
|
|
100
|
+
* session has no conversation, no resolvable provider, or is an agent's.
|
|
101
|
+
*/
|
|
102
|
+
function nextMainRequest(sessionId) {
|
|
103
|
+
const session = sessionId ? loadSession(sessionId) : null;
|
|
104
|
+
if (!session || session.closed === true || isAgentOwner(session)) return null;
|
|
105
|
+
if (!hasUserConversationMessage(session.messages)) return null;
|
|
106
|
+
const provider = getProvider(session.provider);
|
|
107
|
+
if (!provider) return null;
|
|
108
|
+
const transcript = filterModelVisibleSessionMessages(session.messages);
|
|
109
|
+
repairTranscriptBeforeProviderSend(transcript, sessionId);
|
|
110
|
+
const opts = buildRequestSendOptions({
|
|
111
|
+
sessionId,
|
|
112
|
+
session,
|
|
113
|
+
turnEffort: session.effort || null,
|
|
114
|
+
effortConfiguration: prepareTurnEffortConfiguration(session, provider),
|
|
115
|
+
stepAutoEffort: null,
|
|
116
|
+
codexTurnId: null,
|
|
117
|
+
startedAtMs: Date.now(),
|
|
118
|
+
signal: undefined,
|
|
119
|
+
});
|
|
120
|
+
const sendTools = snapshotProviderRequestTools({
|
|
121
|
+
provider: session.provider || provider.name,
|
|
122
|
+
tools: session.tools,
|
|
123
|
+
nativeTools: opts.nativeTools,
|
|
124
|
+
messages: transcript,
|
|
125
|
+
session,
|
|
126
|
+
});
|
|
127
|
+
opts.iteration = 1;
|
|
128
|
+
opts.providerToolSnapshotAuthoritative = true;
|
|
129
|
+
opts.providerNativeToolPrefixCount = providerNativeToolPrefixCount(sendTools);
|
|
130
|
+
return sideQuestionRequestSnapshot({
|
|
131
|
+
provider,
|
|
132
|
+
model: session.model,
|
|
133
|
+
messages: projectSyntheticUserEnvelopes(transcript).messages,
|
|
134
|
+
tools: sendTools,
|
|
135
|
+
opts,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function answerText(response) {
|
|
140
|
+
if (response?.toolCalls?.length) return SIDE_QUESTION_TOOL_ATTEMPT;
|
|
141
|
+
const text = typeof response?.content === 'string' ? response.content.trim() : '';
|
|
142
|
+
return text || SIDE_QUESTION_EMPTY_ANSWER;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Answer `question` from the session's latest provider request. One side
|
|
147
|
+
* question runs per session: a new one aborts the previous, and
|
|
148
|
+
* cancelSideQuestion aborts it. A session without a saved request forks the
|
|
149
|
+
* request its next turn would send. Rejects when the session has no
|
|
150
|
+
* conversation or provider, with the provider's error, or with the cancel
|
|
151
|
+
* reason.
|
|
152
|
+
*/
|
|
153
|
+
export async function askSideQuestion(sessionId, question) {
|
|
154
|
+
const text = typeof question === 'string' ? question.trim() : '';
|
|
155
|
+
if (!text) throw new TypeError('Side question is empty.');
|
|
156
|
+
const request = sideQuestionRequest(sessionId) || nextMainRequest(sessionId);
|
|
157
|
+
if (!request) throw new Error(SIDE_QUESTION_NO_REQUEST);
|
|
158
|
+
const run = beginSideQuestion(sessionId);
|
|
159
|
+
const startedAt = Date.now();
|
|
160
|
+
// The fork keeps the session id (prompt cache routing) but runs on its own
|
|
161
|
+
// transport scope: no pooled socket, response-chain anchor, turn-state
|
|
162
|
+
// token or provider run of the main session is waited on, read or replaced.
|
|
163
|
+
const transportScope = forkTransportScope(sessionId, 'side');
|
|
164
|
+
try {
|
|
165
|
+
const { messages, opts } = buildSideQuestionRequest(request, text);
|
|
166
|
+
let response;
|
|
167
|
+
try {
|
|
168
|
+
response = await request.provider.send(messages, request.model, request.tools.length ? request.tools : undefined, {
|
|
169
|
+
...opts,
|
|
170
|
+
transportScope,
|
|
171
|
+
admissionOwner: sessionId,
|
|
172
|
+
signal: run.signal,
|
|
173
|
+
});
|
|
174
|
+
} catch (error) {
|
|
175
|
+
throw run.signal.aborted ? run.signal.reason : error;
|
|
176
|
+
}
|
|
177
|
+
if (run.signal.aborted) throw run.signal.reason;
|
|
178
|
+
const session = request.opts.session;
|
|
179
|
+
if (response?.usage && session?.id) {
|
|
180
|
+
void logSessionLlmUsage({
|
|
181
|
+
session: { ...session, model: request.model },
|
|
182
|
+
usage: response.usage,
|
|
183
|
+
startedAt,
|
|
184
|
+
mode: 'side-question',
|
|
185
|
+
}).catch(() => {});
|
|
186
|
+
}
|
|
187
|
+
return { text: answerText(response) };
|
|
188
|
+
} finally {
|
|
189
|
+
run.end();
|
|
190
|
+
disposeTransportScope(transportScope, request.provider);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -158,9 +158,23 @@ function _logSessionCloseMetric(id, existing, closedAt, reason) {
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
/**
|
|
162
|
+
* A barrier that found no record at all — nothing on disk and no live or
|
|
163
|
+
* pending copy in memory — fenced nothing because there is nothing to fence:
|
|
164
|
+
* its pending writes were already cancelled under the commit lock. It still
|
|
165
|
+
* returns null (no generation exists), but a caller passing `options.outcome`
|
|
166
|
+
* learns that the record was legitimately gone rather than vetoed, contended
|
|
167
|
+
* or failed.
|
|
168
|
+
*/
|
|
169
|
+
function _recordAbsent(options) {
|
|
170
|
+
if (options?.outcome && typeof options.outcome === 'object') options.outcome.absent = true;
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
161
174
|
/**
|
|
162
175
|
* Atomically mark a session closed on disk with a bumped generation.
|
|
163
176
|
* Returns the new generation, or null if the session file doesn't exist.
|
|
177
|
+
* `options.outcome` (an object) gets `absent: true` when no record existed.
|
|
164
178
|
* Used by closeSession() to plant a tombstone that races against in-flight
|
|
165
179
|
* saveSession() calls.
|
|
166
180
|
*/
|
|
@@ -187,7 +201,7 @@ export function markSessionClosed(id, reason = 'manual', options = {}) {
|
|
|
187
201
|
_cancelSessionWrites(id);
|
|
188
202
|
_uncacheSessionSummary(id);
|
|
189
203
|
const existing = loadSession(id);
|
|
190
|
-
if (!existing) return
|
|
204
|
+
if (!existing) return _recordAbsent(options);
|
|
191
205
|
// Re-close idempotence: a session that is ALREADY tombstoned keeps its
|
|
192
206
|
// ORIGINAL close time (updatedAt) and generation. The old code refreshed
|
|
193
207
|
// updatedAt=Date.now() on every call, so the 5-min idle sweep re-closing a
|
|
@@ -253,7 +267,7 @@ export function markSessionClosed(id, reason = 'manual', options = {}) {
|
|
|
253
267
|
* of clobbering whatever the resumed session writes next.
|
|
254
268
|
* Returns the new generation, or null if the session file doesn't exist.
|
|
255
269
|
*/
|
|
256
|
-
export function bumpSessionGeneration(id, reason = 'detach') {
|
|
270
|
+
export function bumpSessionGeneration(id, reason = 'detach', options = {}) {
|
|
257
271
|
clearSessionLifecycleCommitError(id);
|
|
258
272
|
// The detach barrier is a canonical write and MUST take the same commit
|
|
259
273
|
// lock as markSessionClosed. Cancellation alone is not a barrier: a writer
|
|
@@ -278,7 +292,7 @@ export function bumpSessionGeneration(id, reason = 'detach') {
|
|
|
278
292
|
_cancelSessionWrites(id);
|
|
279
293
|
_uncacheSessionSummary(id);
|
|
280
294
|
const existing = loadSession(id);
|
|
281
|
-
if (!existing) return
|
|
295
|
+
if (!existing) return _recordAbsent(options);
|
|
282
296
|
const newGen = (typeof existing.generation === 'number' ? existing.generation : 0) + 1;
|
|
283
297
|
const detached = { ...existing, generation: newGen, updatedAt: Date.now(), detachedReason: reason };
|
|
284
298
|
if (!_writeLifecycleRecord(id, detached, existing, reason)) return null;
|
|
@@ -124,6 +124,29 @@ export function _retireSessionIncarnation(id) {
|
|
|
124
124
|
if (id) _sessionIncarnations.delete(id);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
// ── Delete fence ────────────────────────────────────────────────────────────
|
|
128
|
+
// An explicitly deleted session may still own a running turn whose later saves
|
|
129
|
+
// carry a brand-new write guard and incarnation, so neither the cancellation
|
|
130
|
+
// counter nor the retired incarnation stops them: an absent file reads as
|
|
131
|
+
// "creatable". The fence is the id-level refusal every write-admission check
|
|
132
|
+
// consults. Only explicit user deletes fence (ids are never reused); the cap
|
|
133
|
+
// only ever forgets the oldest, long-settled entries.
|
|
134
|
+
const SESSION_DELETE_FENCE_MAX = 4096;
|
|
135
|
+
const _deleteFence = new Set();
|
|
136
|
+
|
|
137
|
+
export function fenceSessionWrites(id) {
|
|
138
|
+
if (!id) return;
|
|
139
|
+
_deleteFence.delete(id);
|
|
140
|
+
_deleteFence.add(id);
|
|
141
|
+
if (_deleteFence.size > SESSION_DELETE_FENCE_MAX) {
|
|
142
|
+
_deleteFence.delete(_deleteFence.values().next().value);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function isSessionWriteFenced(id) {
|
|
147
|
+
return _deleteFence.has(id);
|
|
148
|
+
}
|
|
149
|
+
|
|
127
150
|
/** Clear every save marker for an id (successful hard delete). */
|
|
128
151
|
export function _clearSessionSaveState(id) {
|
|
129
152
|
if (!id) return;
|
|
@@ -83,6 +83,13 @@ function _readSidecarOwnerPid(path) {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/** True only when the sidecar recorded an owner pid that no longer exists
|
|
87
|
+
* (pid-less legacy sidecars prove nothing and read false). */
|
|
88
|
+
export function isSidecarOwnerDead(path) {
|
|
89
|
+
const pid = _readSidecarOwnerPid(path);
|
|
90
|
+
return pid > 0 && !isProcessAlive(pid);
|
|
91
|
+
}
|
|
92
|
+
|
|
86
93
|
// ── Heartbeat publish ─────────────────────────────────────
|
|
87
94
|
// Lightweight per-session timestamp file (`<id>.hb`) consumed by the
|
|
88
95
|
// status aggregator for fresh-session detection. Decoupled from the
|
|
@@ -193,25 +200,74 @@ export function isSessionHeartbeatOwnerDead(id) {
|
|
|
193
200
|
// writer on the same session file (dual-writer last-save-wins clobbering).
|
|
194
201
|
// Refreshed by the engine share tick (~3s call, throttled here) and cleared
|
|
195
202
|
// on session switch/dispose; staleness covers crashes.
|
|
203
|
+
//
|
|
204
|
+
// Presence is a sidecar OF a session record and never outlives it: it is
|
|
205
|
+
// only published while the record exists, and a publish that loses a race
|
|
206
|
+
// with a deletion (this process or another one unlinked the record and its
|
|
207
|
+
// sidecar while the write was in flight) removes what it just wrote.
|
|
196
208
|
const _PRESENCE_THROTTLE_MS = 20_000;
|
|
197
209
|
const _presenceLastAt = new Map();
|
|
210
|
+
// id -> in-flight publish; deleteSessionPresence cancels it so the write that
|
|
211
|
+
// lands after the unlink is removed again instead of resurrecting the file.
|
|
212
|
+
const _presenceInFlight = new Map();
|
|
198
213
|
|
|
199
214
|
function _presencePath(id) {
|
|
200
215
|
return _sessionFilePath(id, '.own');
|
|
201
216
|
}
|
|
202
217
|
|
|
218
|
+
async function _sessionRecordExists(id) {
|
|
219
|
+
try {
|
|
220
|
+
await fsp.stat(sessionPath(id));
|
|
221
|
+
return true;
|
|
222
|
+
} catch (error) {
|
|
223
|
+
// Only a proven absence stops the beacon; an unreadable stat keeps it.
|
|
224
|
+
return error?.code !== 'ENOENT';
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function _publishPresence(id, now, flight) {
|
|
229
|
+
try {
|
|
230
|
+
// A record that does not exist (never saved yet, or deleted) gets no
|
|
231
|
+
// beacon; the throttle stays open so the next tick retries.
|
|
232
|
+
if (!(await _sessionRecordExists(id)) || flight.cancelled) {
|
|
233
|
+
if (_presenceLastAt.get(id) === now) _presenceLastAt.delete(id);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const target = _presencePath(id);
|
|
237
|
+
await fsp.writeFile(target, `${now}\n${process.pid}\n`, 'utf8');
|
|
238
|
+
if (flight.cancelled || !(await _sessionRecordExists(id))) {
|
|
239
|
+
await fsp.unlink(target).catch(() => {});
|
|
240
|
+
if (_presenceLastAt.get(id) === now) _presenceLastAt.delete(id);
|
|
241
|
+
}
|
|
242
|
+
} catch {
|
|
243
|
+
/* best-effort beacon */
|
|
244
|
+
} finally {
|
|
245
|
+
if (_presenceInFlight.get(id) === flight) _presenceInFlight.delete(id);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
203
249
|
export function publishSessionPresence(id, ts) {
|
|
204
250
|
if (!id) return;
|
|
205
251
|
const now = ts || Date.now();
|
|
206
252
|
const last = _presenceLastAt.get(id) || 0;
|
|
207
253
|
if (now - last < _PRESENCE_THROTTLE_MS) return;
|
|
208
|
-
|
|
254
|
+
if (_presenceInFlight.has(id)) return;
|
|
255
|
+
_assertSessionId(id);
|
|
209
256
|
_presenceLastAt.set(id, now);
|
|
210
|
-
|
|
257
|
+
const flight = { cancelled: false, done: null };
|
|
258
|
+
_presenceInFlight.set(id, flight);
|
|
259
|
+
flight.done = _publishPresence(id, now, flight);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Test/drain seam: resolves once the id's in-flight presence publish settled. */
|
|
263
|
+
export function settleSessionPresence(id) {
|
|
264
|
+
return _presenceInFlight.get(id)?.done || Promise.resolve();
|
|
211
265
|
}
|
|
212
266
|
|
|
213
267
|
export function deleteSessionPresence(id) {
|
|
214
268
|
if (!id) return;
|
|
269
|
+
const flight = _presenceInFlight.get(id);
|
|
270
|
+
if (flight) flight.cancelled = true;
|
|
215
271
|
try {
|
|
216
272
|
unlinkSync(_presencePath(id));
|
|
217
273
|
} catch {
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
* edge plus the full local-state purge that must happen under the same lock
|
|
5
5
|
* (incarnation retirement, pending slots, purge hooks, markers, summary row).
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
import { join } from 'node:path';
|
|
7
|
+
import { unlinkSync } from 'node:fs';
|
|
9
8
|
import { isAgentOwner } from '../../agent-owner.mjs';
|
|
10
|
-
import {
|
|
9
|
+
import { listStoredAgentWorkerLinks } from '../store-agent-worker-rows.mjs';
|
|
10
|
+
import { listStoredSessionSummaries } from './listing.mjs';
|
|
11
|
+
import { STORED_SESSION_UNREADABLE, _storedSessionFromFile } from './serialize.mjs';
|
|
11
12
|
import { getStoreDir, sessionPath, deleteSessionPresence } from './paths-heartbeat.mjs';
|
|
12
13
|
import {
|
|
13
14
|
readCanonicalSessionRecord as _readCanonicalRecord,
|
|
@@ -22,49 +23,58 @@ import {
|
|
|
22
23
|
} from './write-guards.mjs';
|
|
23
24
|
import {
|
|
24
25
|
_clearLiveSession,
|
|
26
|
+
_liveSessions,
|
|
25
27
|
_retireSessionIncarnation,
|
|
26
28
|
_clearSessionSaveState,
|
|
27
29
|
clearSessionLifecycleCommitError,
|
|
30
|
+
fenceSessionWrites,
|
|
28
31
|
} from './live-state.mjs';
|
|
29
32
|
import { purgeSessionSaveBookkeeping as _purgeSessionSaveBookkeeping } from './save-worker.mjs';
|
|
30
|
-
import { _uncacheSessionSummary, _queueSessionSummaryRemoval } from './summary-cache.mjs';
|
|
33
|
+
import { _uncacheSessionSummary, _queueSessionSummaryRemoval, _cachedSummaryRows } from './summary-cache.mjs';
|
|
31
34
|
import { _savePending, _clearDebounce, _releasePendingSlot } from './pending-saves.mjs';
|
|
32
35
|
import { _runtimeLivenessVeto, _heartbeatLivenessVeto, _deleteHeartbeatUnlessNewer } from './liveness-veto.mjs';
|
|
33
36
|
import { _runSessionPurgeHooks } from './session-observers.mjs';
|
|
34
37
|
import { deleteCompactionArchive } from '../compaction-archive.mjs';
|
|
35
38
|
|
|
36
|
-
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
+
/** Child-agent session ids linked to one visible parent. Candidates come from
|
|
40
|
+
* metadata only (the summary rows and the agent-worker links — no folder
|
|
41
|
+
* scan); each candidate's own file is then read strictly and must still be an
|
|
42
|
+
* agent-owned record linked to this parent, so a stale/foreign metadata row
|
|
43
|
+
* can never name a deletion target. Used only by explicit parent deletion;
|
|
44
|
+
* ordinary close/context switches keep the relationship intact. */
|
|
39
45
|
export function listOwnedAgentSessionIds(ownerSessionId) {
|
|
40
46
|
const ownerId = String(ownerSessionId || '').trim();
|
|
41
47
|
if (!/^[A-Za-z0-9_-]+$/.test(ownerId)) return [];
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
const linksTo = (row) => row?.ownerSessionId === ownerId || row?.parentSessionId === ownerId;
|
|
49
|
+
const candidates = new Set();
|
|
50
|
+
for (const row of [...listStoredSessionSummaries(), ...listStoredAgentWorkerLinks()]) {
|
|
51
|
+
const candidateId = String(row?.id || row?.sessionId || '').trim();
|
|
52
|
+
if (linksTo(row) && candidateId !== ownerId && /^[A-Za-z0-9_-]+$/.test(candidateId)) {
|
|
53
|
+
candidates.add(candidateId);
|
|
54
|
+
}
|
|
49
55
|
}
|
|
56
|
+
const dir = getStoreDir();
|
|
50
57
|
const ids = [];
|
|
51
|
-
for (const
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const session = record.doc;
|
|
58
|
-
if (!isAgentOwner(session)) continue;
|
|
59
|
-
const linkedOwner = String(session.ownerSessionId || session.parentSessionId || '').trim();
|
|
60
|
-
if (linkedOwner === ownerId) ids.push(candidateId);
|
|
61
|
-
} catch {
|
|
62
|
-
/* unreadable/vanished records are never deletion targets */
|
|
63
|
-
}
|
|
58
|
+
for (const candidateId of candidates) {
|
|
59
|
+
const session = _storedSessionFromFile(dir, `${candidateId}.json`, false);
|
|
60
|
+
// Unreadable, vanished, corrupt or foreign records are never deletion targets.
|
|
61
|
+
if (!session || session === STORED_SESSION_UNREADABLE || !isAgentOwner(session)) continue;
|
|
62
|
+
const linkedOwner = String(session.ownerSessionId || session.parentSessionId || '').trim();
|
|
63
|
+
if (linkedOwner === ownerId) ids.push(candidateId);
|
|
64
64
|
}
|
|
65
65
|
return ids;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/** Working folders the session is known to have used (its cached summary row
|
|
69
|
+
* and live snapshot — no file read, no scan); the purge hooks derive
|
|
70
|
+
* per-folder artifacts such as the transcript path from them. */
|
|
71
|
+
function knownSessionCwds(id) {
|
|
72
|
+
const row = _cachedSummaryRows()?.find((candidate) => candidate.id === id);
|
|
73
|
+
const live = _liveSessions.get(id);
|
|
74
|
+
const cwds = [row?.cwd, row?.workspace?.cwd, row?.workspace?.projectPath, live?.cwd];
|
|
75
|
+
return [...new Set(cwds.filter((cwd) => typeof cwd === 'string' && cwd))];
|
|
76
|
+
}
|
|
77
|
+
|
|
68
78
|
export function deleteSession(id, options = {}) {
|
|
69
79
|
// Keep caller probes and all vetoes ahead of the non-reentrant lock and
|
|
70
80
|
// ahead of pending-save disruption, matching markSessionClosed().
|
|
@@ -122,12 +132,15 @@ export function deleteSession(id, options = {}) {
|
|
|
122
132
|
// its settlement moves no marker and touches no bookkeeping — and a
|
|
123
133
|
// re-created id gets a brand new incarnation of its own.
|
|
124
134
|
_retireSessionIncarnation(id);
|
|
135
|
+
// An explicit user delete also fences the id: a still-running turn's later
|
|
136
|
+
// saves get fresh guards/incarnations and would otherwise recreate the file.
|
|
137
|
+
if (options.fenceWrites === true) fenceSessionWrites(id);
|
|
125
138
|
_cancelSessionWrites(id);
|
|
126
139
|
_clearDebounce(id);
|
|
127
140
|
_releasePendingSlot(_savePending.get(id));
|
|
128
141
|
_savePending.delete(id);
|
|
129
142
|
_purgeSessionSaveBookkeeping(id);
|
|
130
|
-
_runSessionPurgeHooks(id);
|
|
143
|
+
_runSessionPurgeHooks(id, { cwds: knownSessionCwds(id) });
|
|
131
144
|
// Preserve a sidecar published strictly after the sweep's scan snapshot.
|
|
132
145
|
_deleteHeartbeatUnlessNewer(id, options);
|
|
133
146
|
deleteSessionPresence(id);
|
|
@@ -44,10 +44,12 @@ export function _publishLiveSession(session) {
|
|
|
44
44
|
|
|
45
45
|
_setLiveSessionPublisher(_publishLiveSession);
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
/** `context` (optional, e.g. `{ cwds }`: working folders the session is known
|
|
48
|
+
* to have used) is passed to every hook as its second argument. */
|
|
49
|
+
export function _runSessionPurgeHooks(id, context) {
|
|
48
50
|
for (const hook of _sessionPurgeHooks) {
|
|
49
51
|
try {
|
|
50
|
-
hook(id);
|
|
52
|
+
hook(id, context);
|
|
51
53
|
} catch {
|
|
52
54
|
/* a cleanup hook never breaks delete */
|
|
53
55
|
}
|
|
@@ -7,6 +7,11 @@ import { probePath, PROBE_PRESENT, PROBE_ABSENT } from '../fs-probe.mjs';
|
|
|
7
7
|
import { _queueSummaryIndexPrune } from '../summary-cache.mjs';
|
|
8
8
|
import { deleteSession } from '../session-delete.mjs';
|
|
9
9
|
import { COMPACTION_ARCHIVE_SUFFIX } from '../../compaction-archive.mjs';
|
|
10
|
+
import { isSidecarOwnerDead } from '../paths-heartbeat.mjs';
|
|
11
|
+
|
|
12
|
+
// A live presence owner rewrites `.own` every ~20s; one untouched for the
|
|
13
|
+
// attach guard's whole freshness window holds nothing open.
|
|
14
|
+
const PRESENCE_ORPHAN_AGE_MS = 2 * 60 * 1000;
|
|
10
15
|
|
|
11
16
|
/** Retention cap: prune resumable open (non-tombstone) sessions newest-first
|
|
12
17
|
* — keep the most recent openMaxCount, prune anything older than openMaxAgeMs
|
|
@@ -68,7 +73,13 @@ export function* reapOrphanSidecars({ plan, dir, now, tally }) {
|
|
|
68
73
|
if (probePath(join(dir, recordOf(h))).state !== PROBE_ABSENT) continue;
|
|
69
74
|
const sidecarProbe = probePath(join(dir, h));
|
|
70
75
|
if (sidecarProbe.state !== PROBE_PRESENT) continue;
|
|
71
|
-
|
|
76
|
+
// A presence sidecar is only ever published while its record exists,
|
|
77
|
+
// so one without a record whose recorded owner is dead (or which no
|
|
78
|
+
// live owner refreshed within the presence window) is a leftover of a
|
|
79
|
+
// deleted session, not a session mid-create.
|
|
80
|
+
const orphanPresence =
|
|
81
|
+
h.endsWith('.own') && (now - sidecarProbe.mtimeMs > PRESENCE_ORPHAN_AGE_MS || isSidecarOwnerDead(join(dir, h)));
|
|
82
|
+
if (orphanPresence || now - sidecarProbe.mtimeMs > plan.maxAge) {
|
|
72
83
|
try {
|
|
73
84
|
unlinkSync(join(dir, h));
|
|
74
85
|
tally.cleaned++;
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from './canonical-reader.mjs';
|
|
15
15
|
import { isCancelledWrite as _isCancelledWrite } from './write-guards.mjs';
|
|
16
16
|
import { _setSessionWriteAuthorityCheck } from './save-worker.mjs';
|
|
17
|
+
import { isSessionWriteFenced } from './live-state.mjs';
|
|
17
18
|
|
|
18
19
|
const _observeStamp = (target) => {
|
|
19
20
|
try {
|
|
@@ -71,7 +72,7 @@ function _writeAuthorityRecord(target, attempt) {
|
|
|
71
72
|
* under their own authority read).
|
|
72
73
|
*/
|
|
73
74
|
export function _shouldDrop(id, opts, attempt = null) {
|
|
74
|
-
if (_isCancelledWrite(opts)) return true;
|
|
75
|
+
if (_isCancelledWrite(opts) || isSessionWriteFenced(id)) return true;
|
|
75
76
|
const expected = typeof opts?.expectedGeneration === 'number' ? opts.expectedGeneration : null;
|
|
76
77
|
const target = sessionPath(id);
|
|
77
78
|
let record;
|
|
@@ -126,6 +127,7 @@ export function _shouldDrop(id, opts, attempt = null) {
|
|
|
126
127
|
// under the lock by _shouldDrop.
|
|
127
128
|
export function _sessionWriteAuthorityRefusal(id, attempt = null) {
|
|
128
129
|
if (!id) return null;
|
|
130
|
+
if (isSessionWriteFenced(id)) return 'deleted';
|
|
129
131
|
let authority;
|
|
130
132
|
try {
|
|
131
133
|
// A synchronous save passes its `attempt`: its pre-admission is then the
|