mixdog 1.4.0 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/scripts/build-shipped-history.mjs +115 -33
- package/scripts/check-voice-runtime-inputs.mjs +25 -0
- package/scripts/gate-local.mjs +183 -63
- package/scripts/latest-guard.mjs +21 -9
- package/scripts/lib/gate-scheduler.mjs +138 -32
- package/scripts/lib/gate-timing-reporter.mjs +16 -0
- package/scripts/lib/isolated-test-env.mjs +10 -5
- package/scripts/lib/run-node-tests.mjs +91 -66
- package/scripts/lib/scoped-temp.d.mts +16 -0
- package/scripts/lib/scoped-temp.mjs +124 -0
- package/scripts/lib/stale-temp.mjs +44 -0
- package/scripts/lib/test-failure-records.mjs +14 -1
- package/scripts/lib/test-failure-summary.mjs +16 -4
- package/scripts/lib/test-spawn-ledger.mjs +12 -3
- package/scripts/post-release-check.mjs +1 -6
- package/scripts/pptx-kit-index.mjs +17 -5
- package/scripts/suite-health.mjs +115 -24
- package/scripts/test-data-dir.mjs +10 -7
- package/scripts/test.mjs +9 -14
- package/src/agents/advisor/AGENT.md +4 -4
- package/src/agents/advisor/agent.json +1 -1
- package/src/agents/explore/AGENT.md +22 -0
- package/src/agents/explore/agent.json +6 -0
- package/src/app.mjs +4 -0
- package/src/cli.mjs +7 -0
- package/src/defaults/agents.json +7 -0
- package/src/defaults/shipped-history.json +23 -2
- package/src/defaults/skills/browser-use/SKILL.md +10 -5
- package/src/defaults/skills/code-tidy/SKILL.md +71 -102
- package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
- package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
- package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
- package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
- package/src/defaults/skills/computer-use/SKILL.md +16 -4
- package/src/defaults/skills/goal-management/SKILL.md +1 -1
- package/src/defaults/skills/setup/references/actions.md +12 -6
- package/src/defaults/skills/setup/references/surfaces.md +2 -2
- package/src/defaults/skills/setup-guide/SKILL.md +5 -2
- package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
- package/src/defaults/skills/skill-creator/SKILL.md +85 -190
- package/src/defaults/skills/skill-creator/references/design.md +70 -162
- package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
- package/src/defaults/skills/test-suite/SKILL.md +194 -0
- package/src/defaults/skills/test-suite/references/case-management.md +51 -0
- package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
- package/src/defaults/skills/test-suite/references/reliability.md +49 -0
- package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
- package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
- package/src/defaults/skills/visualize/SKILL.md +6 -2
- package/src/lib/rules-builder.cjs +35 -42
- package/src/output-styles/common.md +2 -2
- package/src/output-styles/simple.md +1 -1
- package/src/rules/agent/AGENT.md +5 -4
- package/src/rules/lead/LEAD.md +12 -7
- package/src/rules/shared/10-tool-workflow.md +3 -1
- package/src/rules/shared/20-research.md +3 -6
- package/src/rules/shared/40-editing.md +1 -0
- package/src/rules/shared/45-destructive.md +0 -2
- package/src/rules/shared/50-execution.md +2 -0
- package/src/rules/shared/60-verification.md +10 -9
- package/src/rules/shared/70-delivery.md +8 -9
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +156 -24
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
- package/src/runtime/agent/orchestrator/config.mjs +12 -7
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +8 -8
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +3 -1
- package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
- package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +88 -54
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +54 -8
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +37 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
- package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +248 -41
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
- package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
- package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +160 -31
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +22 -12
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +49 -22
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/registry.mjs +45 -2
- package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +41 -13
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -6
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +7 -3
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
- package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +102 -12
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +160 -36
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +164 -23
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +139 -19
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
- package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +100 -22
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +33 -3
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +23 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +39 -21
- package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +30 -13
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +63 -6
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +35 -13
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +24 -14
- package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +10 -6
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +56 -29
- package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +30 -7
- package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +37 -8
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +252 -39
- package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
- package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +61 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -20
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +67 -31
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +25 -13
- package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
- package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
- package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +28 -4
- package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
- package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
- package/src/runtime/channels/lib/whisper-port.mjs +29 -11
- package/src/runtime/channels/lib/whisper-server.mjs +10 -1
- package/src/runtime/computer-bridge/action-schema.mjs +2 -2
- package/src/runtime/computer-bridge/core-actions.mjs +2 -2
- package/src/runtime/computer-bridge/error-recovery.mjs +5 -4
- package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
- package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
- package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
- package/src/runtime/connectors/gmail-app-password.mjs +3 -3
- package/src/runtime/explore/explorer.mjs +93 -0
- package/src/runtime/explore/tool-defs.mjs +35 -0
- package/src/runtime/explore/tool.mjs +123 -0
- package/src/runtime/local-provider/server-process.mjs +2 -1
- package/src/runtime/media/adapters/gemini-video.mjs +4 -1
- package/src/runtime/media/adapters/xai-media.mjs +2 -1
- package/src/runtime/memory/index.mjs +4 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
- package/src/runtime/memory/lib/embedding-worker.mjs +10 -3
- package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
- package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
- package/src/runtime/office/design/design-art-direction.mjs +29 -27
- package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
- package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
- package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
- package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
- package/src/runtime/office/quality/assurance-structure-docx.mjs +4 -2
- package/src/runtime/office/quality/design-review.mjs +1 -5
- package/src/runtime/office/quality/document-brief.mjs +1 -5
- package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
- package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
- package/src/runtime/shared/agent-route-config.mjs +17 -2
- package/src/runtime/shared/atomic-file.mjs +103 -2
- package/src/runtime/shared/background-tasks.mjs +15 -6
- package/src/runtime/shared/bot-owned-work.mjs +56 -0
- package/src/runtime/shared/connector-accounts.mjs +5 -5
- package/src/runtime/shared/connector-catalog.d.mts +11 -4
- package/src/runtime/shared/connector-catalog.mjs +60 -52
- package/src/runtime/shared/custom-provider-keys.mjs +20 -0
- package/src/runtime/shared/defaults-separation.mjs +1 -4
- package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
- package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
- package/src/runtime/shared/file-has-data.mjs +26 -2
- package/src/runtime/shared/file-lock.mjs +13 -15
- package/src/runtime/shared/git-launch.d.mts +9 -0
- package/src/runtime/shared/git-launch.mjs +213 -0
- package/src/runtime/shared/git-runner-worker.mjs +2 -1
- package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
- package/src/runtime/shared/handoff-artifacts.mjs +18 -8
- package/src/runtime/shared/idle-auto-clear-gate.mjs +29 -7
- package/src/runtime/shared/llm/cost.mjs +11 -2
- package/src/runtime/shared/llm/http-agent.mjs +313 -29
- package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
- package/src/runtime/shared/llm/model-catalog.mjs +46 -6
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
- package/src/runtime/shared/llm/system-proxy.mjs +452 -0
- package/src/runtime/shared/loopback-listener.mjs +25 -2
- package/src/runtime/shared/model-route-chain.d.mts +7 -2
- package/src/runtime/shared/model-route-chain.mjs +22 -4
- package/src/runtime/shared/open-url.mjs +10 -0
- package/src/runtime/shared/provider-accounts.mjs +52 -2
- package/src/runtime/shared/session-activity-signal.mjs +14 -0
- package/src/runtime/shared/session-scratch.mjs +37 -0
- package/src/runtime/shared/staged-install-worker.mjs +5 -0
- package/src/runtime/shared/staged-update.mjs +47 -2
- package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
- package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
- package/src/runtime/shared/tool-result-summary.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +113 -36
- package/src/runtime/shared/tool-work-units.mjs +6 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
- package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
- package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
- package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
- package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
- package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
- package/src/runtime/shared/worktrees/contract.mjs +4 -1
- package/src/runtime/shared/worktrees/git.mjs +72 -0
- package/src/runtime/shared/worktrees/manager.mjs +285 -168
- package/src/runtime/shared/worktrees/test-support.mjs +6 -10
- package/src/runtime/tidy/install.mjs +95 -15
- package/src/runtime/tidy/languages.mjs +17 -15
- package/src/runtime/tidy/process.mjs +7 -4
- package/src/runtime/tidy/resolve.mjs +19 -16
- package/src/runtime/tidy/runners/biome.mjs +3 -3
- package/src/runtime/tidy/tool.mjs +1 -1
- package/src/runtime/tidy/worktree.mjs +10 -6
- package/src/runtime/visualize-bridge/client.mjs +6 -1
- package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
- package/src/runtime/web-search/index.mjs +1 -1
- package/src/session-runtime/agent-home-delivery.mjs +41 -0
- package/src/session-runtime/agent-task-sessions.mjs +458 -312
- package/src/session-runtime/agent-tool-routing.mjs +8 -41
- package/src/session-runtime/boot/tools.mjs +7 -1
- package/src/session-runtime/bot-status.mjs +107 -101
- package/src/session-runtime/bot-target.mjs +38 -0
- package/src/session-runtime/channel-config-api.mjs +6 -1
- package/src/session-runtime/connector-merge-migration.mjs +18 -15
- package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
- package/src/session-runtime/conversation-import/discovery.mjs +401 -36
- package/src/session-runtime/conversation-import/service.mjs +35 -8
- package/src/session-runtime/handoff-attachments.mjs +82 -31
- package/src/session-runtime/held-requests.mjs +3 -1
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +12 -13
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +1 -0
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
- package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +145 -112
- package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
- package/src/session-runtime/lifecycle/session-catalog.mjs +9 -1
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
- package/src/session-runtime/lifecycle/teardown.mjs +1 -1
- package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
- package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
- package/src/session-runtime/mixy-heartbeat.mjs +1 -1
- package/src/session-runtime/model-route/set-route.mjs +11 -2
- package/src/session-runtime/provider-auth/login.mjs +2 -2
- package/src/session-runtime/provider-catalog-cache.mjs +82 -36
- package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
- package/src/session-runtime/provider-models.mjs +4 -2
- package/src/session-runtime/resource-mcp-api.mjs +98 -35
- package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
- package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
- package/src/session-runtime/resource-mcp-servers.mjs +5 -4
- package/src/session-runtime/resource-recall-api.mjs +6 -1
- package/src/session-runtime/runtime-feature-gates.mjs +5 -1
- package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
- package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
- package/src/session-runtime/services/agent-tool/render.mjs +71 -17
- package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +6 -2
- package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
- package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
- package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
- package/src/session-runtime/services/daemon-candidate.mjs +4 -1
- package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
- package/src/session-runtime/services/plugin-admin.mjs +10 -6
- package/src/session-runtime/services/provider-admin.mjs +283 -11
- package/src/session-runtime/session-turn-api.mjs +2 -0
- package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
- package/src/session-runtime/settings-system-api.mjs +1 -1
- package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
- package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
- package/src/session-runtime/setup-tool/executor.mjs +6 -2
- package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
- package/src/session-runtime/tool-policy/policy-stage.mjs +1 -0
- package/src/session-runtime/turn/agent-ops.mjs +23 -0
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +25 -5
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
- package/src/session-runtime/workspace/activity-guards.mjs +176 -0
- package/src/session-runtime/workspace/session-view.mjs +149 -0
- package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
- package/src/session-runtime/workspace/transition-runner.mjs +188 -0
- package/src/session-runtime/workspace/transitions.mjs +108 -1041
- package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
- package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
- package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
- package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
- package/src/session-runtime/workspace/worktree-tool.mjs +73 -56
- package/src/standalone/daemon-desktop-runtime.mjs +2 -0
- package/src/standalone/daemon-session-control.mjs +169 -138
- package/src/standalone/daemon.mjs +7 -0
- package/src/standalone/desktop-service-registry.mjs +9 -0
- package/src/standalone/local-session-runtime.mjs +5 -1
- package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
- package/src/standalone/session-client.mjs +5 -1
- package/src/standalone/session-frame-stream.mjs +83 -11
- package/src/standalone/session-protocol.mjs +4 -0
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
- package/src/standalone/session-service/agent-tree.mjs +45 -9
- package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
- package/src/standalone/session-service/session-calls/session-action.mjs +10 -3
- package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
- package/src/standalone/session-service/stored-reader.mjs +127 -40
- package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
- package/src/tui/App.jsx +154 -164
- package/src/tui/app/app-view/floating-panel.jsx +10 -4
- package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
- package/src/tui/app/clipboard.mjs +21 -10
- package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
- package/src/tui/app/model-picker/route-selection.mjs +5 -6
- package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
- package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
- package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
- package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
- package/src/tui/app/provider-setup/oauth-actions.mjs +58 -21
- package/src/tui/app/provider-setup/provider-items.mjs +8 -2
- package/src/tui/app/provider-setup-picker.mjs +29 -18
- package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
- package/src/tui/app/slash-commands.mjs +1 -0
- package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
- package/src/tui/app/slash-dispatch.mjs +1 -1
- package/src/tui/app/use-btw-panel.mjs +29 -0
- package/src/tui/app/use-context-panel-refresh.mjs +31 -0
- package/src/tui/app/use-model-catalog-cache.mjs +13 -10
- package/src/tui/app/use-mouse-input.mjs +11 -0
- package/src/tui/app/use-onboarding-start.mjs +8 -3
- package/src/tui/app/use-panel-transition.mjs +10 -1
- package/src/tui/app/use-pasted-buffers.mjs +28 -20
- package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
- package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
- package/src/tui/app/use-prompt-queue-history.mjs +3 -2
- package/src/tui/app/use-selection-focus.mjs +11 -2
- package/src/tui/app/use-selection-geometry.mjs +18 -9
- package/src/tui/app/use-selection-paint.mjs +4 -4
- package/src/tui/app/use-selection-stitch.mjs +3 -3
- package/src/tui/app/use-slash-palette.mjs +62 -0
- package/src/tui/app/use-terminal-chrome.mjs +2 -2
- package/src/tui/app/use-transcript-activity.mjs +47 -32
- package/src/tui/app/use-transcript-follow.mjs +12 -4
- package/src/tui/app/use-transcript-scroll.mjs +46 -17
- package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
- package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
- package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
- package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
- package/src/tui/app/use-transcript-window.mjs +21 -11
- package/src/tui/app/use-tui-ready.mjs +18 -0
- package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
- package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
- package/src/tui/components/Picker.jsx +27 -3
- package/src/tui/components/QuestionPanel.jsx +13 -4
- package/src/tui/components/SignInUrl.jsx +68 -0
- package/src/tui/components/TextEntryPanel.jsx +76 -46
- package/src/tui/components/sign-in-url-layout.mjs +24 -0
- package/src/tui/dist/index.mjs +1963 -1361
- package/src/tui/session/goal-turn-state.mjs +6 -2
- package/src/tui/session/session-api/controls.mjs +4 -0
- package/src/tui/session/session-api/extensions.mjs +8 -0
- package/src/tui/session/session-api/intake/abort.mjs +2 -1
- package/src/tui/session/session-api/intake.mjs +2 -0
- package/src/tui/session/session-api/integrations.mjs +2 -2
- package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
- package/src/tui/session/session-api/settings.mjs +2 -2
- package/src/tui/session/session-flow/queue/take-entries.mjs +11 -0
- package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
- package/src/tui/session/transcript-spill.mjs +92 -4
- package/src/tui/session/transcript-store.mjs +2 -0
- package/src/ui/statusline-agents.mjs +1 -3
- package/src/defaults/skills/skill-creator/references/source-review.md +0 -94
|
@@ -13,6 +13,51 @@ const RESTART_BACKOFF_MS = 30_000;
|
|
|
13
13
|
const SERVER_READY_TIMEOUT_MS = 5_000;
|
|
14
14
|
const REQUIRED_LIFECYCLE_CAPS = Object.freeze(['trackedForeground', 'promoteTask', 'cancelOwner']);
|
|
15
15
|
|
|
16
|
+
// Windows containment. A spawn that asks for it (`spawnOptions.contain`, the
|
|
17
|
+
// shell runner's commands) runs in a job object it cannot break away from, and
|
|
18
|
+
// the server reports what became of that job's tree (`treeOutcome`, below):
|
|
19
|
+
// verified empty, the whole tree is gone, so a kill needs no process-table
|
|
20
|
+
// snapshot first. A program that must start children with
|
|
21
|
+
// CREATE_BREAKAWAY_FROM_JOB (some installers, launchers) fails to under it.
|
|
22
|
+
// `MIXDOG_SHELL_ALLOW_BREAKAWAY=1` in a command's OWN environment
|
|
23
|
+
// (`spawnOptions.env`) opts that command out; in Mixdog's environment it is
|
|
24
|
+
// the emergency switch for every command. Either way the command keeps the
|
|
25
|
+
// conservative process-table tracking. A failed command is never re-run
|
|
26
|
+
// without containment: it may already have had side effects.
|
|
27
|
+
// Servers without the `verifiedContainment` capability are never asked.
|
|
28
|
+
export const ALLOW_BREAKAWAY_ENV = 'MIXDOG_SHELL_ALLOW_BREAKAWAY';
|
|
29
|
+
|
|
30
|
+
/** Whether a spawn with `spawnOptions` asks the server `caps` for containment. */
|
|
31
|
+
export function nativeSpawnContainmentRequested(
|
|
32
|
+
caps,
|
|
33
|
+
spawnOptions = {},
|
|
34
|
+
{ platform = process.platform, env = process.env } = {}
|
|
35
|
+
) {
|
|
36
|
+
return (
|
|
37
|
+
spawnOptions?.contain === true &&
|
|
38
|
+
platform === 'win32' &&
|
|
39
|
+
caps?.verifiedContainment === true &&
|
|
40
|
+
env[ALLOW_BREAKAWAY_ENV] !== '1' &&
|
|
41
|
+
spawnOptions.env?.[ALLOW_BREAKAWAY_ENV] !== '1'
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// treeOutcome of a contained spawn (null until known):
|
|
46
|
+
// 'empty' verified: every process of the tree has exited (final)
|
|
47
|
+
// 'pending' killed, the server is still verifying
|
|
48
|
+
// 'preserved' the root exited on its own before any kill reached it; the
|
|
49
|
+
// server keeps its survivors in their job for a while, where a
|
|
50
|
+
// targeted cancel (`cancel` by request id, `cancelTask`) still
|
|
51
|
+
// kills exactly them and is verified like any kill
|
|
52
|
+
// 'unverified' killed, but the job was never verified empty (final)
|
|
53
|
+
// 'released' the kept survivors were let go unobserved (final). With
|
|
54
|
+
// `breakawayRestored: false` (on `tree_released`, `exit`, task
|
|
55
|
+
// snapshots) they were not even let go: they stay in a job they
|
|
56
|
+
// cannot leave. Either way unknown for good, never gone.
|
|
57
|
+
const FINAL_TREE_OUTCOMES = new Set(['empty', 'unverified', 'released']);
|
|
58
|
+
const treeOutcomeOf = (value) =>
|
|
59
|
+
value === 'pending' || value === 'preserved' || FINAL_TREE_OUTCOMES.has(value) ? value : null;
|
|
60
|
+
|
|
16
61
|
let _server = null;
|
|
17
62
|
let _binaryPath;
|
|
18
63
|
let _lastFailureAt = 0;
|
|
@@ -205,12 +250,19 @@ function normalizeTask(task) {
|
|
|
205
250
|
stdoutPath: override?.stdoutPath || null,
|
|
206
251
|
stderrPath: override?.stderrPath || null,
|
|
207
252
|
mergeStderr: false,
|
|
253
|
+
contained: task.contained === true,
|
|
254
|
+
treeOutcome: treeOutcomeOf(task.treeOutcome),
|
|
208
255
|
};
|
|
209
256
|
}
|
|
210
257
|
|
|
211
258
|
function acceptTask(message) {
|
|
212
259
|
const task = normalizeTask(message?.task);
|
|
213
260
|
if (!task) return null;
|
|
261
|
+
const known = _tasks.get(task.jobId);
|
|
262
|
+
// A final outcome stands: a snapshot taken before its report is late.
|
|
263
|
+
if (FINAL_TREE_OUTCOMES.has(known?.treeOutcome) && known.requestId === task.requestId) {
|
|
264
|
+
task.treeOutcome = known.treeOutcome;
|
|
265
|
+
}
|
|
214
266
|
_tasks.set(task.jobId, task);
|
|
215
267
|
_markTerminalTask(task.jobId, task.status);
|
|
216
268
|
_taskEvents.emit(task.jobId, task);
|
|
@@ -260,6 +312,13 @@ class NativeSpawnChild extends EventEmitter {
|
|
|
260
312
|
this._cancel = cancel;
|
|
261
313
|
this._send = send;
|
|
262
314
|
this._closed = false;
|
|
315
|
+
// Windows kernel creation time (FILETIME ticks, decimal) from the server's
|
|
316
|
+
// 'spawned' event; null from servers that do not report it.
|
|
317
|
+
this.creationTime = null;
|
|
318
|
+
// Spawned into a job it cannot leave, and what the server reported about
|
|
319
|
+
// that job's tree (treeOutcomeOf).
|
|
320
|
+
this.contained = false;
|
|
321
|
+
this.treeOutcome = null;
|
|
263
322
|
}
|
|
264
323
|
|
|
265
324
|
// stdinPipe children only (warm shell standby): forward script text to the
|
|
@@ -394,6 +453,21 @@ function _teardown(error, { kill = true } = {}) {
|
|
|
394
453
|
}
|
|
395
454
|
}
|
|
396
455
|
|
|
456
|
+
// The server's outcome for the tree of request `id`. Only a request of THIS
|
|
457
|
+
// server still awaiting a final outcome takes it: a lost server's trees stay
|
|
458
|
+
// unconfirmed, whatever a new server later says about a reused request id.
|
|
459
|
+
function _settleTree(server, id, outcome) {
|
|
460
|
+
const entry = server.trees.get(id);
|
|
461
|
+
if (!entry || FINAL_TREE_OUTCOMES.has(entry.child.treeOutcome)) return;
|
|
462
|
+
entry.child.treeOutcome = outcome;
|
|
463
|
+
if (FINAL_TREE_OUTCOMES.has(outcome)) server.trees.delete(id);
|
|
464
|
+
const task = entry.jobId ? _tasks.get(entry.jobId) : null;
|
|
465
|
+
if (task && task.requestId === id && !FINAL_TREE_OUTCOMES.has(task.treeOutcome)) {
|
|
466
|
+
task.treeOutcome = outcome;
|
|
467
|
+
_taskEvents.emit(entry.jobId, { ...task });
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
397
471
|
export async function shutdownNativeSpawnServer(reason = 'process-exit', timeoutMs = 1_000) {
|
|
398
472
|
const server = _server;
|
|
399
473
|
if (!server) return true;
|
|
@@ -442,7 +516,9 @@ function _ensureServer() {
|
|
|
442
516
|
_lastFailureAt = Date.now();
|
|
443
517
|
return null;
|
|
444
518
|
}
|
|
445
|
-
|
|
519
|
+
// trees: request id -> { child, jobId } of contained spawns awaiting a final
|
|
520
|
+
// tree outcome (it may follow their exit).
|
|
521
|
+
const server = { child, pending: new Map(), trees: new Map(), sequence: 0, ready: false, caps: {} };
|
|
446
522
|
let resolveReady;
|
|
447
523
|
server.readyPromise = new Promise((resolve) => {
|
|
448
524
|
resolveReady = resolve;
|
|
@@ -472,7 +548,16 @@ function _ensureServer() {
|
|
|
472
548
|
const jobId = String(message?.jobId || '');
|
|
473
549
|
if (jobId) _dropTaskEntry(jobId, { notifyServer: false });
|
|
474
550
|
} else if (event.startsWith('task_')) {
|
|
475
|
-
acceptTask(message);
|
|
551
|
+
const task = acceptTask(message);
|
|
552
|
+
// A job id assigned by the server (background) or later (track).
|
|
553
|
+
const tree = task ? server.trees.get(task.requestId) : null;
|
|
554
|
+
if (tree) tree.jobId = task.jobId;
|
|
555
|
+
} else if (event === 'tree_empty') {
|
|
556
|
+
_settleTree(server, Number(message?.id), 'empty');
|
|
557
|
+
} else if (event === 'tree_unverified') {
|
|
558
|
+
_settleTree(server, Number(message?.id), 'unverified');
|
|
559
|
+
} else if (event === 'tree_released') {
|
|
560
|
+
_settleTree(server, Number(message?.id), 'released');
|
|
476
561
|
}
|
|
477
562
|
const pending = server.pending.get(Number(message?.id));
|
|
478
563
|
if (!pending) return;
|
|
@@ -570,6 +655,7 @@ export function tryNativeSpawn({ shell, argv, spawnOptions = {}, cwd } = {}) {
|
|
|
570
655
|
// fields and keep piping, so the capability gate decides rather than hope.
|
|
571
656
|
const _fileCapture =
|
|
572
657
|
server.caps?.fileCapture === true && Boolean(spawnOptions.stdoutPath) && Boolean(spawnOptions.stderrPath);
|
|
658
|
+
const contain = nativeSpawnContainmentRequested(server.caps, spawnOptions);
|
|
573
659
|
const request = {
|
|
574
660
|
id,
|
|
575
661
|
program: String(shell || ''),
|
|
@@ -589,6 +675,7 @@ export function tryNativeSpawn({ shell, argv, spawnOptions = {}, cwd } = {}) {
|
|
|
589
675
|
}
|
|
590
676
|
: {}),
|
|
591
677
|
...(spawnOptions.stdinPipe === true ? { stdinPipe: true } : {}),
|
|
678
|
+
...(contain ? { contain: true } : {}),
|
|
592
679
|
command: spawnOptions.command || undefined,
|
|
593
680
|
shellType: spawnOptions.shellType || undefined,
|
|
594
681
|
ownerSessionId: spawnOptions.ownerSessionId || undefined,
|
|
@@ -611,6 +698,12 @@ export function tryNativeSpawn({ shell, argv, spawnOptions = {}, cwd } = {}) {
|
|
|
611
698
|
const event = String(message?.event || '');
|
|
612
699
|
if (event === 'spawned') {
|
|
613
700
|
fake.pid = Number(message.pid) || undefined;
|
|
701
|
+
if (/^\d+$/.test(String(message.creationTime ?? ''))) fake.creationTime = String(message.creationTime);
|
|
702
|
+
// Only a containment the request asked for counts.
|
|
703
|
+
if (contain && message.contained === true) {
|
|
704
|
+
fake.contained = true;
|
|
705
|
+
server.trees.set(id, { child: fake, jobId: request.jobId || null });
|
|
706
|
+
}
|
|
614
707
|
fake.emit('spawn');
|
|
615
708
|
return;
|
|
616
709
|
}
|
|
@@ -628,6 +721,10 @@ export function tryNativeSpawn({ shell, argv, spawnOptions = {}, cwd } = {}) {
|
|
|
628
721
|
server.pending.delete(id);
|
|
629
722
|
fake.exitCode = message.code == null ? null : Number(message.code);
|
|
630
723
|
fake.signalCode = message.signal || null;
|
|
724
|
+
// The outcome when the server finished the request; a pending one is
|
|
725
|
+
// made final by a later report. A contained exit without one (a
|
|
726
|
+
// malformed or partial report) proves nothing.
|
|
727
|
+
if (fake.contained) _settleTree(server, id, treeOutcomeOf(message.treeOutcome) ?? 'unverified');
|
|
631
728
|
fake._closed = true;
|
|
632
729
|
fake.emit('close', fake.exitCode, fake.signalCode);
|
|
633
730
|
_recomputeServerRef(server);
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
// PROCESS_TABLE_READ_BOUND_MS, and then sends the kill whether or not they
|
|
6
6
|
// arrived. A missing snapshot leaves the outcome unknown, so the workspace
|
|
7
7
|
// stays held under the observation loop instead of being released blind.
|
|
8
|
+
// A tree the spawn server contains (Windows job) needs no snapshot: its
|
|
9
|
+
// prepareKill answers true, so a cancel of only such trees is sent at once.
|
|
8
10
|
import { cancelNativeTasks } from './native-spawn-client.mjs';
|
|
9
11
|
import { killAfterSnapshot } from './shell-descendants.mjs';
|
|
10
12
|
|
|
@@ -54,6 +54,17 @@ export function killAfterSnapshot(snapshot, kill) {
|
|
|
54
54
|
|
|
55
55
|
const identityKey = (entry) => `${entry.pid}:${entry.token ?? ''}`;
|
|
56
56
|
|
|
57
|
+
// Windows keeps a process's parent id after that parent exits and hands the
|
|
58
|
+
// pid to a new process within milliseconds, so a parent link can name a
|
|
59
|
+
// process created after the one listing it: an unrelated process then looks
|
|
60
|
+
// like the new holder's child. A child is never created before its parent.
|
|
61
|
+
// (POSIX reparents orphans, so its links never dangle.) Only kernel creation
|
|
62
|
+
// times are compared — the table's tokens and the spawn server's root
|
|
63
|
+
// creation time, both FILETIME ticks — at the table's microsecond precision;
|
|
64
|
+
// a parent whose time is unknown excludes nothing. Any other token (POSIX
|
|
65
|
+
// lstart, scripted test tokens) is unknown.
|
|
66
|
+
const createdUs = (ticks) => (typeof ticks === 'string' && /^\d+$/.test(ticks) ? BigInt(ticks) / 10n : null);
|
|
67
|
+
|
|
57
68
|
/** Tracks one command's process tree across its termination.
|
|
58
69
|
*
|
|
59
70
|
* `capture()` records the identities of the root and every descendant while
|
|
@@ -66,8 +77,11 @@ const identityKey = (entry) => `${entry.pid}:${entry.token ?? ''}`;
|
|
|
66
77
|
* or (POSIX) a member of the command's group is alive → running
|
|
67
78
|
* - no complete pre-exit capture exists → unknown
|
|
68
79
|
* - a tracked process without a creation token is present → unknown
|
|
69
|
-
* A failed table read rejects; callers treat it as unknown.
|
|
70
|
-
|
|
80
|
+
* A failed table read rejects; callers treat it as unknown.
|
|
81
|
+
*
|
|
82
|
+
* `rootCreationTime`: the root's kernel creation time from its spawn (see
|
|
83
|
+
* native-spawn-client), known even once the root is gone uncaptured. */
|
|
84
|
+
export function createProcessTreeTracker({ pid, members = [], exitObserved = false, rootCreationTime = null } = {}) {
|
|
71
85
|
const rootPid = Number(pid) || 0;
|
|
72
86
|
let rootToken = null;
|
|
73
87
|
let complete = false;
|
|
@@ -92,6 +106,12 @@ export function createProcessTreeTracker({ pid, members = [], exitObserved = fal
|
|
|
92
106
|
// are ours. A different process under that pid is a reuse — not ours.
|
|
93
107
|
const rootPidIsOurs = (table, rootAlive) =>
|
|
94
108
|
rootAlive || !table.tokenOf.has(rootPid) || (rootToken != null && table.tokenOf.get(rootPid) === rootToken);
|
|
109
|
+
// When `cur` (a live root, or a present identity) was created, if known.
|
|
110
|
+
const createdAt = (table, cur, rootAlive) => {
|
|
111
|
+
if (!isWin) return null;
|
|
112
|
+
if (cur !== rootPid) return createdUs(table.tokenOf.get(cur));
|
|
113
|
+
return createdUs(rootCreationTime) ?? createdUs(rootAlive ? table.tokenOf.get(rootPid) : rootToken);
|
|
114
|
+
};
|
|
95
115
|
|
|
96
116
|
function absorb(table, rootAlive) {
|
|
97
117
|
const from = [];
|
|
@@ -100,8 +120,11 @@ export function createProcessTreeTracker({ pid, members = [], exitObserved = fal
|
|
|
100
120
|
const seen = new Set(from);
|
|
101
121
|
while (from.length) {
|
|
102
122
|
const cur = from.pop();
|
|
123
|
+
const parentCreated = createdAt(table, cur, rootAlive);
|
|
103
124
|
for (const child of table.childrenOf.get(cur) ?? []) {
|
|
104
125
|
if (seen.has(child)) continue;
|
|
126
|
+
const childCreated = parentCreated == null ? null : createdUs(table.tokenOf.get(child));
|
|
127
|
+
if (childCreated != null && childCreated < parentCreated) continue;
|
|
105
128
|
seen.add(child);
|
|
106
129
|
track(table, child);
|
|
107
130
|
from.push(child);
|
|
@@ -173,19 +196,77 @@ export function createProcessTreeTracker({ pid, members = [], exitObserved = fal
|
|
|
173
196
|
};
|
|
174
197
|
}
|
|
175
198
|
|
|
199
|
+
/** The tracker of a KILLED tree the native spawn server contains (a Windows
|
|
200
|
+
* job object nothing can break away from), driven only by the server's
|
|
201
|
+
* outcome for that job (`outcome()`, see native-spawn-client treeOutcome).
|
|
202
|
+
* It never reads the process table: membership is the job's, never a guess
|
|
203
|
+
* from parent links, which a reused root pid makes point at the wrong
|
|
204
|
+
* processes. So the kill never waits for a snapshot.
|
|
205
|
+
* 'empty' gone
|
|
206
|
+
* 'preserved' the root exited on its own before the kill
|
|
207
|
+
* reached the server, which kept its survivors in
|
|
208
|
+
* the job: `cancel()` re-sends the kill to exactly
|
|
209
|
+
* that job (once), and its verdict follows
|
|
210
|
+
* 'unverified'/'released' unknown for good, and incomplete, so a
|
|
211
|
+
* force-release ends the watch: never gone
|
|
212
|
+
* anything else unknown until the server reports (a lost server
|
|
213
|
+
* never does) */
|
|
214
|
+
export function createContainedTreeTracker({ outcome, cancel }) {
|
|
215
|
+
let cancelled = false;
|
|
216
|
+
return {
|
|
217
|
+
contained: true,
|
|
218
|
+
get complete() {
|
|
219
|
+
const value = outcome();
|
|
220
|
+
return value !== 'unverified' && value !== 'released';
|
|
221
|
+
},
|
|
222
|
+
live: [],
|
|
223
|
+
capture: async () => true,
|
|
224
|
+
acceptExitObservation() {},
|
|
225
|
+
async observe() {
|
|
226
|
+
const value = outcome();
|
|
227
|
+
if (value === 'empty') return 'gone';
|
|
228
|
+
if (value === 'preserved' && !cancelled) {
|
|
229
|
+
cancelled = true;
|
|
230
|
+
try {
|
|
231
|
+
cancel();
|
|
232
|
+
} catch {
|
|
233
|
+
/* the server link is gone: the outcome stays unknown */
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return 'unknown';
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
176
241
|
/** Terminate every identity the last observation found alive (console hosts
|
|
177
242
|
* excepted: they are witnesses, never workers). These are processes of a tree
|
|
178
243
|
* that was KILLED — they escaped its process group (setsid, job control, a
|
|
179
244
|
* double fork that kept its parent link) or outlived the root on Windows —
|
|
180
245
|
* so the kill is not finished while they run. Windows: taskkill /T /F;
|
|
181
246
|
* POSIX: SIGTERM, then SIGKILL after a short grace. */
|
|
182
|
-
function sweepLiveIdentities(tracker) {
|
|
247
|
+
export function sweepLiveIdentities(tracker) {
|
|
183
248
|
const targets = tracker.live
|
|
184
249
|
.filter((entry) => entry.token != null && !CONSOLE_HOST.test(entry.name))
|
|
185
250
|
.map((entry) => entry.pid);
|
|
186
251
|
return targets.length > 0 ? killProcessTrees(targets) : Promise.resolve();
|
|
187
252
|
}
|
|
188
253
|
|
|
254
|
+
/** A tracker for a watch that goes on after its hold was force-released: an
|
|
255
|
+
* observation that stays 'unknown' on a tracker that can never complete (the
|
|
256
|
+
* root is gone and its pre-kill capture was incomplete) ends the watch via
|
|
257
|
+
* `stop`. Termination is never reported by it. */
|
|
258
|
+
export function endWatchWhenUnconfirmable(tracker, { released, stop }) {
|
|
259
|
+
return Object.create(tracker, {
|
|
260
|
+
observe: {
|
|
261
|
+
value: async (options) => {
|
|
262
|
+
const state = await tracker.observe(options);
|
|
263
|
+
if (released() && state === 'unknown' && !tracker.complete) stop();
|
|
264
|
+
return state;
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
189
270
|
/** One observation loop: observe now, then every `intervalMs` until the tree
|
|
190
271
|
* is confirmed gone, then run `onTerminated` once. 'running', 'unknown', a
|
|
191
272
|
* failed or unanswered (by the next cadence tick) observation and a failing
|
|
@@ -194,8 +275,9 @@ function sweepLiveIdentities(tracker) {
|
|
|
194
275
|
* timer; nothing else (listener, timer) outlives the loop. Nothing in it ever
|
|
195
276
|
* releases on time alone.
|
|
196
277
|
*
|
|
197
|
-
* `sweep` (a killed tree
|
|
198
|
-
*
|
|
278
|
+
* `sweep` (a killed tree; a boolean, or a predicate read on every pass so a
|
|
279
|
+
* cancel that arrives later is retried each cadence): a pass that finds
|
|
280
|
+
* captured identities alive terminates them and observes again in the same pass, so a cancellation
|
|
199
281
|
* ends with the tree gone instead of a held folder over live processes.
|
|
200
282
|
* A missing pre-kill capture stays unknown even if the owning session
|
|
201
283
|
* closes: its lifetime is not evidence that every descendant has exited. */
|
|
@@ -230,7 +312,7 @@ export function watchTermination({
|
|
|
230
312
|
observation.catch(() => {});
|
|
231
313
|
// A pass that has not answered by the next cadence tick is unknown.
|
|
232
314
|
state = await withinBound(observation, intervalMs, 'unknown');
|
|
233
|
-
if (state === 'running' &&
|
|
315
|
+
if (state === 'running' && !stopped && (typeof sweep === 'function' ? sweep() : sweep)) {
|
|
234
316
|
await withinBound(
|
|
235
317
|
sweepLiveIdentities(tracker).catch(() => {}),
|
|
236
318
|
intervalMs,
|
|
@@ -315,24 +397,26 @@ function delay(ms) {
|
|
|
315
397
|
* by pid (Windows re-parents nothing: a process whose parent
|
|
316
398
|
* exited keeps a dangling parent id, so an intermediate that
|
|
317
399
|
* already exited breaks the link).
|
|
318
|
-
* Rejects when the process table cannot be read completely.
|
|
319
|
-
|
|
400
|
+
* Rejects when the process table cannot be read completely.
|
|
401
|
+
* `rootCreationTime`: see createProcessTreeTracker. */
|
|
402
|
+
export async function probeShellDescendants({ pid, rootCreationTime = null } = {}) {
|
|
320
403
|
const root = Number(pid) || 0;
|
|
321
404
|
if (root <= 0) return null;
|
|
322
405
|
// POSIX fast path: no member of the command's group at all.
|
|
323
406
|
if (!isWin && !groupAlive(root)) return null;
|
|
324
|
-
const tracker = createProcessTreeTracker({ pid: root, exitObserved: true });
|
|
407
|
+
const tracker = createProcessTreeTracker({ pid: root, exitObserved: true, rootCreationTime });
|
|
325
408
|
const state = await tracker.observe({ rootExited: true });
|
|
326
409
|
if (state === 'gone') return null;
|
|
327
410
|
if (state === 'unknown') throw new Error('Remaining child processes could not be verified.');
|
|
328
|
-
return survivorHandle(root, tracker.live);
|
|
411
|
+
return survivorHandle(root, tracker.live, rootCreationTime);
|
|
329
412
|
}
|
|
330
413
|
|
|
331
|
-
function survivorHandle(rootPid, live) {
|
|
414
|
+
function survivorHandle(rootPid, live, rootCreationTime) {
|
|
332
415
|
const watch = live.map(({ pid, token, name }) => ({ pid, token, name }));
|
|
333
416
|
const killPids = watch.filter((entry) => !CONSOLE_HOST.test(entry.name)).map((entry) => entry.pid);
|
|
334
417
|
return {
|
|
335
418
|
rootPid,
|
|
419
|
+
rootCreationTime,
|
|
336
420
|
groupPid: isWin ? null : rootPid,
|
|
337
421
|
killPids,
|
|
338
422
|
watchPids: watch.map((entry) => entry.pid),
|
|
@@ -342,13 +426,18 @@ function survivorHandle(rootPid, live) {
|
|
|
342
426
|
}
|
|
343
427
|
|
|
344
428
|
const trackerFor = (handle) =>
|
|
345
|
-
createProcessTreeTracker({
|
|
429
|
+
createProcessTreeTracker({
|
|
430
|
+
pid: handle.rootPid,
|
|
431
|
+
members: handle.watch,
|
|
432
|
+
exitObserved: true,
|
|
433
|
+
rootCreationTime: handle.rootCreationTime,
|
|
434
|
+
});
|
|
346
435
|
|
|
347
436
|
/** Resolve once every observed survivor (by identity) has exited, or false
|
|
348
437
|
* once `signal` aborts. These processes are not our children any more, so
|
|
349
438
|
* there is no exit event to subscribe to; the shared observation loop
|
|
350
439
|
* re-checks them at the termination cadence. */
|
|
351
|
-
export function waitForShellDescendants(handle, { signal = null } = {}) {
|
|
440
|
+
export function waitForShellDescendants(handle, { signal = null, sweep = false } = {}) {
|
|
352
441
|
if (!handle) return Promise.resolve(true);
|
|
353
442
|
return new Promise((resolve) => {
|
|
354
443
|
if (signal?.aborted) {
|
|
@@ -361,6 +450,7 @@ export function waitForShellDescendants(handle, { signal = null } = {}) {
|
|
|
361
450
|
};
|
|
362
451
|
const stop = watchTermination({
|
|
363
452
|
tracker: trackerFor(handle),
|
|
453
|
+
sweep,
|
|
364
454
|
onTerminated: () => {
|
|
365
455
|
signal?.removeEventListener('abort', onAbort);
|
|
366
456
|
resolve(true);
|
|
@@ -92,6 +92,8 @@ export async function handOverLease(run) {
|
|
|
92
92
|
const lease = run.resourceLease;
|
|
93
93
|
run.resourceLease = null;
|
|
94
94
|
await lease?.release();
|
|
95
|
-
run.workspaceAdmission?.();
|
|
95
|
+
const released = run.workspaceAdmission?.();
|
|
96
96
|
run.workspaceAdmission = null;
|
|
97
|
+
// Fails only when workspace admission stopped (reported there), which keeps the folder held.
|
|
98
|
+
await Promise.resolve(released).catch(() => {});
|
|
97
99
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// lease cleanup that a promoted or unconfirmed-kill child defers to its own
|
|
4
4
|
// real exit.
|
|
5
5
|
import { ExecResult, SHELL_OUTPUT_INLINE_CAP } from '../shell-exec-output.mjs';
|
|
6
|
-
import {
|
|
6
|
+
import { endWatchWhenUnconfirmable, probeShellDescendants, watchTermination } from './shell-descendants.mjs';
|
|
7
7
|
import { unregisterOwnedShellTree } from './owned-shell-trees.mjs';
|
|
8
8
|
import {
|
|
9
9
|
adoptShellWorkspaceAdmission,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
detachAbortHandler,
|
|
18
18
|
keepTerminationUnconfirmedRecord,
|
|
19
19
|
releaseResourceLease,
|
|
20
|
+
runTreeTracker,
|
|
20
21
|
} from './shell-run-state.mjs';
|
|
21
22
|
|
|
22
23
|
// Unconfirmed-kill cleanup ceiling: the result is already reported, but the
|
|
@@ -26,8 +27,8 @@ import {
|
|
|
26
27
|
// it follows confirmed termination only (see below).
|
|
27
28
|
const UNCONFIRMED_KILL_CLEANUP_CEILING_MS = 60_000;
|
|
28
29
|
|
|
29
|
-
/** Settles once a held run's whole tree is confirmed gone
|
|
30
|
-
*
|
|
30
|
+
/** Settles once a held run's whole tree is confirmed gone: what its result
|
|
31
|
+
* left in use defers to this. A force-release does not settle it. */
|
|
31
32
|
function treeSettled(run) {
|
|
32
33
|
if (!run.treeSettled) {
|
|
33
34
|
run.treeSettled = new Promise((resolve) => {
|
|
@@ -52,28 +53,56 @@ export function releaseAdmissionOnConfirmedTermination(run, { markNow = true, sw
|
|
|
52
53
|
// gone only once its first observation is not 'gone'.
|
|
53
54
|
if (markNow) keepTerminationUnconfirmedRecord(run);
|
|
54
55
|
treeSettled(run);
|
|
56
|
+
// The workspace leases stop counting here; the returned promise settles once
|
|
57
|
+
// their removal is on disk. A force-release only drops the hold: the tree
|
|
58
|
+
// stays observed, and only its confirmed exit settles it (`terminated`).
|
|
59
|
+
let released;
|
|
60
|
+
let holdDropped = false;
|
|
55
61
|
const release = () => {
|
|
56
|
-
run.workspaceAdmission?.();
|
|
57
|
-
run.workspaceAdmission = null;
|
|
58
|
-
run.stopTerminationWatch?.();
|
|
59
62
|
clearTerminationHold(run.foregroundRecordId);
|
|
60
|
-
|
|
63
|
+
if (!holdDropped) {
|
|
64
|
+
released = run.workspaceAdmission?.();
|
|
65
|
+
run.workspaceAdmission = null;
|
|
66
|
+
clearForegroundRecord(run);
|
|
67
|
+
holdDropped = true;
|
|
68
|
+
}
|
|
69
|
+
return released;
|
|
70
|
+
};
|
|
71
|
+
const terminated = () => {
|
|
72
|
+
const dropped = release();
|
|
73
|
+
run.stopTerminationWatch?.();
|
|
61
74
|
run.resolveTreeSettled();
|
|
75
|
+
return dropped;
|
|
62
76
|
};
|
|
63
77
|
registerTerminationHold(run.foregroundRecordId, {
|
|
64
78
|
ownerSessionId: run.foregroundRecordInput?.ownerSessionId || null,
|
|
65
79
|
cwd: run.foregroundRecordInput?.cwd || null,
|
|
66
80
|
release,
|
|
67
81
|
});
|
|
68
|
-
run
|
|
82
|
+
runTreeTracker(run);
|
|
83
|
+
// After a force-release the watch only waits for a confirmed exit. An
|
|
84
|
+
// incomplete pre-kill capture of a root that is gone can never be completed,
|
|
85
|
+
// so an observation that stays unknown on it ends the watch (never settling
|
|
86
|
+
// the tree: `treeSettled` follows a confirmed exit only).
|
|
69
87
|
run.stopTerminationWatch = watchTermination({
|
|
70
|
-
tracker: run.terminationTracker,
|
|
88
|
+
tracker: endWatchWhenUnconfirmable(run.terminationTracker, {
|
|
89
|
+
released: () => holdDropped,
|
|
90
|
+
stop: () => run.stopTerminationWatch?.(),
|
|
91
|
+
}),
|
|
71
92
|
rootExited: () => child.exitCode != null || child.signalCode != null,
|
|
72
93
|
// A killed run is not over while processes it started still run.
|
|
73
94
|
sweep,
|
|
74
|
-
onTerminated:
|
|
75
|
-
onUnconfirmed: markNow
|
|
76
|
-
|
|
95
|
+
onTerminated: terminated,
|
|
96
|
+
onUnconfirmed: markNow
|
|
97
|
+
? null
|
|
98
|
+
: () => {
|
|
99
|
+
if (!holdDropped) keepTerminationUnconfirmedRecord(run);
|
|
100
|
+
},
|
|
101
|
+
// A release requested from another process is the same force-release.
|
|
102
|
+
releaseRequested: () => {
|
|
103
|
+
if (takeReleaseRequest(run.foregroundRecordId)) void release();
|
|
104
|
+
return false;
|
|
105
|
+
},
|
|
77
106
|
});
|
|
78
107
|
}
|
|
79
108
|
|
|
@@ -165,8 +194,11 @@ export async function readCapturedOutput(taskOutput, final = false) {
|
|
|
165
194
|
// instead of a clean finish; null means nothing survived.
|
|
166
195
|
async function probeSurvivingDescendants(run) {
|
|
167
196
|
if (run.killed || run.timedOut || !run.child?.pid) return null;
|
|
197
|
+
// The spawn server verified the command's job empty: nothing survived.
|
|
198
|
+
if (run.child.treeOutcome === 'empty') return null;
|
|
168
199
|
const descendants = await probeShellDescendants({
|
|
169
200
|
pid: run.child.pid,
|
|
201
|
+
rootCreationTime: run.child.creationTime,
|
|
170
202
|
});
|
|
171
203
|
if (descendants) descendants.taskId = run.foregroundRecordId;
|
|
172
204
|
return descendants;
|
|
@@ -240,8 +272,7 @@ export function createSettle(run, releasePromotedCapture) {
|
|
|
240
272
|
// explicit force-release, and the record is kept, marked unconfirmed.
|
|
241
273
|
// (The probe runs only for an unkilled root, so the lifecycle is not
|
|
242
274
|
// already held here.)
|
|
243
|
-
run.
|
|
244
|
-
run.terminationTracker.acceptExitObservation();
|
|
275
|
+
runTreeTracker(run).acceptExitObservation();
|
|
245
276
|
releaseAdmissionOnConfirmedTermination(run);
|
|
246
277
|
}
|
|
247
278
|
// Marked unconfirmed (with its force-release) only once an observation
|
|
@@ -249,9 +280,12 @@ export function createSettle(run, releasePromotedCapture) {
|
|
|
249
280
|
if (confirmKill) releaseAdmissionOnConfirmedTermination(run, { markNow: false });
|
|
250
281
|
if (!lifecycleHeld && !descendantsUnknown && run.workspaceAdmission) {
|
|
251
282
|
try {
|
|
283
|
+
let released = null;
|
|
252
284
|
if (descendants) adoptShellWorkspaceAdmission(run.foregroundRecordId, run.workspaceAdmission);
|
|
253
|
-
else run.workspaceAdmission();
|
|
285
|
+
else released = run.workspaceAdmission();
|
|
254
286
|
run.workspaceAdmission = null;
|
|
287
|
+
// The result is reported once the removal is on disk, as when it was written synchronously.
|
|
288
|
+
await released;
|
|
255
289
|
} catch (error) {
|
|
256
290
|
taskOutput.writeError = taskOutput.writeError || error;
|
|
257
291
|
exitCode = exitCode || 1;
|
|
@@ -18,8 +18,9 @@ import { _maybeEncodePowerShellCommand } from '../shell-powershell.mjs';
|
|
|
18
18
|
import { spawnShellWithRetry as _spawnShellWithRetry } from './shell-spawn-retry.mjs';
|
|
19
19
|
import { STANDBY_ARGS, takeWarmShellStandby } from './shell-warm-standby.mjs';
|
|
20
20
|
import { SHELL_OUTPUT_DISK_CAP } from '../shell-exec-output.mjs';
|
|
21
|
-
import { admitWorkspaceActivity,
|
|
21
|
+
import { admitWorkspaceActivity, tryAdmitWorkspaceActivityAsync } from '../../../../shared/worktrees/activity-gate.mjs';
|
|
22
22
|
import { worktreeError } from '../../../../shared/worktrees/contract.mjs';
|
|
23
|
+
import { bothReleased } from '../builtin/lib/shell-job-records.mjs';
|
|
23
24
|
import { capturePreKill, registerForegroundOwnedTree, treeKillForceSettle } from './shell-run-state.mjs';
|
|
24
25
|
import { releaseAdmissionOnConfirmedTermination } from './shell-run-settle.mjs';
|
|
25
26
|
import { killAfterSnapshot } from './shell-descendants.mjs';
|
|
@@ -135,6 +136,8 @@ async function gatedSpawn({
|
|
|
135
136
|
command,
|
|
136
137
|
ownerSessionId,
|
|
137
138
|
clientHostPid,
|
|
139
|
+
// Windows: the whole tree stays in the command's job (native-spawn-client).
|
|
140
|
+
contain: true,
|
|
138
141
|
...(stdinPipe ? { stdinPipe: true } : {}),
|
|
139
142
|
...(capture
|
|
140
143
|
? {
|
|
@@ -223,20 +226,25 @@ async function spawnAdmittedShellChild({
|
|
|
223
226
|
}));
|
|
224
227
|
run.child = spawned.child;
|
|
225
228
|
registerForegroundOwnedTree(run, ownerSessionId);
|
|
229
|
+
// Attached before the admission below yields: until the caller attaches its
|
|
230
|
+
// listeners, the child's output is buffered and its close replayed.
|
|
231
|
+
spawned.attachErrorHandler(onChildError);
|
|
226
232
|
const parentAdmission = run.workspaceAdmission;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
233
|
+
// The command's own lease (under its pid: what keeps the folder reserved
|
|
234
|
+
// should this runtime die). The parent's lease covers the folder until this
|
|
235
|
+
// one is written to both copies, and stays the only release until then.
|
|
236
|
+
const childAdmission = await tryAdmitWorkspaceActivityAsync(
|
|
237
|
+
ownerSessionId || run.foregroundRecordId,
|
|
238
|
+
cwd || process.cwd(),
|
|
239
|
+
{
|
|
240
|
+
pid: Number(run.child.pid) || process.pid,
|
|
241
|
+
kind: 'command',
|
|
242
|
+
}
|
|
243
|
+
);
|
|
231
244
|
if (!childAdmission) throw worktreeError('WORKTREE_BUSY_JOBS', 'The command workspace changed during spawn.');
|
|
232
|
-
run.workspaceAdmission = () =>
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
};
|
|
236
|
-
spawned.attachErrorHandler(onChildError);
|
|
237
|
-
// Feed the script only after the error handler is attached; server
|
|
238
|
-
// messages cannot be processed before this synchronous block yields.
|
|
239
|
-
// Single atomic write+EOF, as for the standby.
|
|
245
|
+
run.workspaceAdmission = () => bothReleased(childAdmission, parentAdmission);
|
|
246
|
+
// Feed the script only once its lease is written. Single atomic write+EOF,
|
|
247
|
+
// as for the standby.
|
|
240
248
|
abortSignal?.throwIfAborted();
|
|
241
249
|
if (standby) standby.feed(spawnCommand, cwd);
|
|
242
250
|
else if (stdinScript !== null) spawned.child.writeStdin(stdinScript, { close: true });
|
|
@@ -249,18 +257,23 @@ async function spawnAdmittedShellChild({
|
|
|
249
257
|
run.foregroundRecordTimer = null;
|
|
250
258
|
if (run.settled || run.autoBackgrounded || !run.child?.pid) return;
|
|
251
259
|
run.foregroundRecordPublished = true;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
260
|
+
void (async () => {
|
|
261
|
+
try {
|
|
262
|
+
await publishForegroundShellRecord({
|
|
263
|
+
...run.foregroundRecordInput,
|
|
264
|
+
jobId: run.foregroundRecordId,
|
|
265
|
+
pid: run.child.pid,
|
|
266
|
+
startedAtMs: run.commandStartedAtMs,
|
|
267
|
+
});
|
|
268
|
+
} catch (error) {
|
|
269
|
+
// The record's lease could not be written: the command is stopped, as
|
|
270
|
+
// before — unless it ended or was promoted (its own leases) meanwhile.
|
|
271
|
+
if (run.settled || run.autoBackgrounded) return;
|
|
272
|
+
run.spawnError = error;
|
|
273
|
+
run.failureReason = 'workspace publication failed';
|
|
274
|
+
treeKillForceSettle(run, 'workspace-publication-failed');
|
|
275
|
+
}
|
|
276
|
+
})();
|
|
264
277
|
}, FOREGROUND_RECORD_DELAY_MS);
|
|
265
278
|
run.foregroundRecordTimer.unref?.();
|
|
266
279
|
}
|
|
@@ -278,11 +291,16 @@ export async function spawnShellChild(input) {
|
|
|
278
291
|
return await spawnAdmittedShellChild(input);
|
|
279
292
|
} catch (error) {
|
|
280
293
|
if (!input.run.child) {
|
|
281
|
-
input.run.workspaceAdmission();
|
|
294
|
+
const released = input.run.workspaceAdmission();
|
|
282
295
|
input.run.workspaceAdmission = null;
|
|
296
|
+
// The spawn's own error is the one reported; a release fails only when
|
|
297
|
+
// workspace admission stopped (which keeps the folder held).
|
|
298
|
+
await Promise.resolve(released).catch(() => {});
|
|
283
299
|
} else {
|
|
284
300
|
try {
|
|
285
|
-
|
|
301
|
+
input.run.killed = true;
|
|
302
|
+
// Identities first: the kill may break the parent chain (a contained
|
|
303
|
+
// tree needs none: its kill is at once and verified by the server).
|
|
286
304
|
// Bounded: the kill is sent even if the snapshot hangs or fails.
|
|
287
305
|
await killAfterSnapshot(capturePreKill(input.run), () => killShellJob(input.run.foregroundRecordId));
|
|
288
306
|
const detail = await waitForShellJob(input.run.foregroundRecordId);
|