mixdog 1.2.3 → 1.2.5
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 +1 -1
- package/scripts/gate-local.mjs +6 -4
- package/scripts/lib/gate-scheduler.mjs +29 -11
- package/scripts/mobile-release.mjs +5 -4
- package/scripts/probe-provider-catalog.mjs +31 -0
- package/scripts/provider-pingpong-live.mjs +128 -38
- package/scripts/session-sweep.mjs +5 -25
- package/src/agents/maintainer/AGENT.md +1 -1
- package/src/agents/maintainer/agent.json +1 -1
- package/src/agents/reviewer/AGENT.md +2 -4
- package/src/agents/security/AGENT.md +1 -2
- package/src/agents/writer/AGENT.md +2 -3
- package/src/defaults/shipped-history.json +7 -1
- package/src/defaults/skills/image/SKILL.md +21 -23
- package/src/lib/keychain-cjs.cjs +13 -11
- package/src/output-styles/common.md +2 -1
- package/src/rules/agent/AGENT.md +7 -5
- package/src/rules/lead/LEAD.md +13 -13
- package/src/rules/shared/45-destructive.md +4 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +28 -7
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -11
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +101 -23
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-api-key-fetch.mjs +36 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +26 -44
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +2 -5
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +41 -12
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +13 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +81 -10
- package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +109 -5
- package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +107 -37
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-native-context.mjs +48 -0
- package/src/runtime/agent/orchestrator/providers/cursor-wire-exec.mjs +144 -34
- package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +28 -4
- package/src/runtime/agent/orchestrator/providers/cursor-wire-request.mjs +8 -4
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +41 -11
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +67 -5
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +14 -4
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +9 -6
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +26 -4
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +12 -6
- package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +14 -6
- package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +4 -4
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/gemini-rest-stream/watchdogs.mjs +16 -5
- package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +22 -10
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +33 -7
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +11 -2
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +9 -5
- package/src/runtime/agent/orchestrator/providers/lib/request-compression.mjs +86 -0
- package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -5
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +60 -16
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +11 -13
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +30 -19
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +5 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +42 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +13 -9
- package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +1 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +18 -7
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +36 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +51 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +11 -1
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +64 -52
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +1 -4
- package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +0 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +7 -6
- package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +13 -2
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/openrouter.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +5 -4
- package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +11 -4
- package/src/runtime/agent/orchestrator/providers/wire-route.mjs +14 -4
- package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +15 -3
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +6 -2
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +8 -6
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +4 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +30 -2
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +3 -2
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -3
- package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +3 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge-contract.mjs +6 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +10 -10
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +3 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +46 -8
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -8
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -2
- package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +24 -0
- package/src/runtime/agent/orchestrator/session/cache/tool-result-reuse.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -11
- package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +6 -22
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/historical-session-closure.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +2 -5
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +2 -4
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +0 -27
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -10
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -9
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +1 -4
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +76 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +8 -9
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +1 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -4
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +33 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-support.mjs +0 -4
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +21 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +38 -13
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-failure.mjs +33 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +19 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +57 -13
- package/src/runtime/agent/orchestrator/session/manager/failed-turn-rewind.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +6 -120
- package/src/runtime/agent/orchestrator/session/manager/pending-persist-queue.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +29 -8
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +42 -49
- package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +50 -0
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +32 -14
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +17 -33
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +29 -13
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-recovery.mjs +45 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +2 -9
- package/src/runtime/agent/orchestrator/session/send-instrumentation.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/side-question.mjs +12 -7
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +5 -12
- package/src/runtime/agent/orchestrator/session/store/lifecycle-read.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +2 -6
- package/src/runtime/agent/orchestrator/session/store/live-cache-eviction.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/liveness-veto.mjs +46 -37
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/load-session.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +5 -11
- package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +59 -0
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +27 -32
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +0 -8
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +6 -18
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +33 -106
- package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store-agent-worker-pool.mjs +5 -8
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +160 -212
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +50 -116
- package/src/runtime/agent/orchestrator/session/store-summary-locations.mjs +3 -29
- package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +7 -8
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +9 -20
- package/src/runtime/agent/orchestrator/session/store.mjs +3 -16
- package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +11 -12
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +37 -13
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +15 -4
- package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +4 -2
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +72 -15
- package/src/runtime/agent/orchestrator/session/turn-failure.mjs +45 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +50 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +5 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +204 -10
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +160 -39
- package/src/runtime/agent/orchestrator/tools/builtin/git-partial-stage.mjs +7 -5
- package/src/runtime/agent/orchestrator/tools/builtin/grep-formatting.mjs +14 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-output.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-path-fanout.mjs +48 -44
- package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +10 -3
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +200 -17
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +20 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +101 -2
- package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +67 -0
- package/src/runtime/agent/orchestrator/tools/lib/process-table-fixture.mjs +50 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +303 -63
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +108 -18
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +77 -11
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +81 -5
- package/src/runtime/attachments/store.d.mts +9 -0
- package/src/runtime/attachments/store.mjs +80 -9
- package/src/runtime/browser-bridge/browser-action-contract.d.mts +0 -1
- package/src/runtime/browser-bridge/browser-action-contract.mjs +0 -2
- package/src/runtime/computer-bridge/action-schema.mjs +178 -178
- package/src/runtime/computer-bridge/linux-readiness.d.mts +0 -2
- package/src/runtime/computer-bridge/linux-readiness.mjs +0 -16
- package/src/runtime/effort-judge/judge-client.mjs +21 -4
- package/src/runtime/effort-judge/judge-worker.mjs +4 -1
- package/src/runtime/github/tool.mjs +118 -17
- package/src/runtime/media/adapters/codex-image.mjs +140 -21
- package/src/runtime/media/catalog.mjs +4 -4
- package/src/runtime/media/chatgpt-usage.mjs +55 -0
- package/src/runtime/media/defaults.mjs +37 -0
- package/src/runtime/media/jobs.mjs +12 -2
- package/src/runtime/media/lanes.mjs +12 -0
- package/src/runtime/media/tool-defs.mjs +15 -4
- package/src/runtime/media/tool.mjs +41 -12
- package/src/runtime/media/upstream-error.mjs +72 -6
- package/src/runtime/memory/lib/ingest/transcript-rows.mjs +1 -0
- package/src/runtime/memory/lib/pg/process.mjs +5 -7
- package/src/runtime/memory/lib/pg/supervisor.mjs +4 -1
- package/src/runtime/office/authoring/docx-html-runner.mjs +1 -1
- package/src/runtime/office/authoring/html-browser.mjs +18 -8
- package/src/runtime/office/authoring/pdf-html-runner.mjs +1 -1
- package/src/runtime/office/authoring/pptx-html-measure.mjs +1 -1
- package/src/runtime/office/authoring/xlsx-html-runner.mjs +1 -1
- package/src/runtime/office/core/office-write-ledger.mjs +1 -5
- package/src/runtime/office/index.mjs +2 -3
- package/src/runtime/office/pdf/pdf-analysis.mjs +0 -68
- package/src/runtime/session-search/session-search-ingest.mjs +1 -1
- package/src/runtime/shared/agent-reap-state.mjs +13 -0
- package/src/runtime/shared/atomic-file.d.mts +1 -1
- package/src/runtime/shared/background-tasks.mjs +6 -1
- package/src/runtime/shared/bounded-line-diff.mjs +11 -2
- package/src/runtime/shared/browser-launch.mjs +15 -0
- package/src/runtime/shared/err-text.mjs +42 -4
- package/src/runtime/shared/file-lock.mjs +6 -5
- package/src/runtime/shared/llm/anthropic-betas.mjs +1 -2
- package/src/runtime/shared/llm/provider-model-identities.mjs +1 -0
- package/src/runtime/shared/model-route-chain.d.mts +8 -0
- package/src/runtime/shared/model-route-chain.mjs +48 -0
- package/src/runtime/shared/model-route-fallback.mjs +22 -0
- package/src/runtime/shared/pid-liveness.mjs +12 -0
- package/src/runtime/shared/provider-api-key.mjs +6 -0
- package/src/runtime/shared/provider-timeout-phase.mjs +17 -0
- package/src/runtime/shared/quiet-progress.d.mts +35 -0
- package/src/runtime/shared/quiet-progress.mjs +101 -0
- package/src/runtime/shared/quota-resume-store.mjs +32 -1
- package/src/runtime/shared/read-row-numbers.mjs +44 -0
- package/src/runtime/shared/remote-intent.mjs +2 -42
- package/src/runtime/shared/slow-span.mjs +8 -1
- package/src/runtime/shared/statusline/statusline-segments.mjs +24 -2
- package/src/runtime/shared/tool-card-model/card-header.mjs +2 -1
- package/src/runtime/shared/tool-output-budget.mjs +2 -0
- package/src/runtime/shared/tool-result-summary.mjs +16 -2
- package/src/runtime/shared/tool-status.mjs +3 -2
- package/src/runtime/shared/tool-surface.mjs +2 -3
- package/src/runtime/shared/tool-work-units.mjs +22 -15
- package/src/runtime/shared/transcript-metadata.mjs +49 -1
- package/src/runtime/shared/transcript-turn-keys.mjs +33 -0
- package/src/runtime/shared/transcript-writer/transcript-file.mjs +10 -1
- package/src/runtime/shared/transcript-writer.mjs +14 -4
- package/src/runtime/shared/turn-changes.mjs +2 -1
- package/src/runtime/shared/turn-snapshot.mjs +11 -3
- package/src/runtime/shared/worktrees/activity-gate.mjs +175 -0
- package/src/runtime/shared/worktrees/branch-management.mjs +179 -0
- package/src/runtime/shared/worktrees/carry.mjs +6 -4
- package/src/runtime/shared/worktrees/contract.mjs +27 -1
- package/src/runtime/shared/worktrees/dependencies.mjs +456 -0
- package/src/runtime/shared/worktrees/file-recovery.mjs +29 -13
- package/src/runtime/shared/worktrees/git-head.mjs +22 -13
- package/src/runtime/shared/worktrees/include.mjs +125 -7
- package/src/runtime/shared/worktrees/lock.mjs +1 -1
- package/src/runtime/shared/worktrees/maintenance.mjs +20 -2
- package/src/runtime/shared/worktrees/manager.mjs +378 -32
- package/src/runtime/shared/worktrees/owner.mjs +2 -1
- package/src/runtime/shared/worktrees/preparation-report.mjs +79 -0
- package/src/runtime/shared/worktrees/setup-commands.mjs +232 -0
- package/src/runtime/shared/worktrees/workspace-visibility.mjs +17 -0
- package/src/runtime/terminal-bridge/client.mjs +14 -10
- package/src/session-runtime/boot/apis.mjs +7 -1
- package/src/session-runtime/boot/config.mjs +2 -0
- package/src/session-runtime/boot/facade.mjs +3 -0
- package/src/session-runtime/boot/session.mjs +3 -2
- package/src/session-runtime/boot/tools.mjs +3 -0
- package/src/session-runtime/conversation-import/service.mjs +1 -1
- package/src/session-runtime/goal-facade-api.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +44 -18
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +4 -1
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +22 -8
- package/src/session-runtime/internal-tool-executor.mjs +2 -0
- package/src/session-runtime/lifecycle/cold-heir.mjs +0 -1
- package/src/session-runtime/lifecycle/session-catalog.mjs +14 -16
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +18 -53
- package/src/session-runtime/lifecycle/session-switch.mjs +1 -1
- package/src/session-runtime/lifecycle/shared.mjs +5 -6
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -5
- package/src/session-runtime/lifecycle/teardown.mjs +7 -16
- package/src/session-runtime/media-api.mjs +8 -0
- package/src/session-runtime/model-route/route-ownership.mjs +60 -0
- package/src/session-runtime/model-route/route-persist.mjs +3 -2
- package/src/session-runtime/model-route/set-route.mjs +114 -34
- package/src/session-runtime/model-route/web-search-route.mjs +7 -2
- package/src/session-runtime/model-route-api.mjs +4 -2
- package/src/session-runtime/native-web-search/routes.mjs +37 -24
- package/src/session-runtime/native-web-search.mjs +9 -8
- package/src/session-runtime/provider-auth/setup.mjs +4 -0
- package/src/session-runtime/provider-models/catalog-load.mjs +9 -0
- package/src/session-runtime/provider-models/web-search-catalog.mjs +23 -3
- package/src/session-runtime/provider-models.mjs +1 -0
- package/src/session-runtime/provider-usage.mjs +4 -0
- package/src/session-runtime/route-resolution.mjs +3 -1
- package/src/session-runtime/runtime-core.mjs +4 -0
- package/src/session-runtime/runtime-facade.mjs +6 -0
- package/src/session-runtime/services/agent-task-status.mjs +2 -0
- package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +0 -3
- package/src/session-runtime/services/agent-tool/close-flow.mjs +6 -7
- package/src/session-runtime/services/agent-tool/execute-send.mjs +2 -2
- package/src/session-runtime/services/agent-tool/execute-spawn.mjs +36 -3
- package/src/session-runtime/services/agent-tool/execute.mjs +9 -1
- package/src/session-runtime/services/agent-tool/helpers.mjs +30 -7
- package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +7 -2
- package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +6 -3
- package/src/session-runtime/services/agent-tool/lead-worker-index/lead-rows.mjs +3 -4
- package/src/session-runtime/services/agent-tool/lead-worker-index/reap-timers.mjs +9 -3
- package/src/session-runtime/services/agent-tool/lead-worker-index/row-settlement.mjs +7 -18
- package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +9 -6
- package/src/session-runtime/services/agent-tool/model-select.mjs +82 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -6
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +3 -1
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -5
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -4
- package/src/session-runtime/services/agent-tool/spawn-flow.mjs +2 -1
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
- package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +1 -3
- package/src/session-runtime/services/agent-tool/tag-registry/tag-refresh.mjs +1 -3
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -9
- package/src/session-runtime/services/agent-tool/terminal-reaper/scan-admission.mjs +8 -41
- package/src/session-runtime/services/agent-tool/terminal-reaper/stale-rows.mjs +3 -3
- package/src/session-runtime/services/agent-tool/terminal-reaper.mjs +6 -23
- package/src/session-runtime/services/agent-tool/terminal-trace.mjs +0 -1
- package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +32 -20
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -2
- package/src/session-runtime/services/agent-tool/worker-index.mjs +28 -8
- package/src/session-runtime/services/agent-tool.mjs +4 -0
- package/src/session-runtime/services/agent-watchdog-registry.mjs +2 -0
- package/src/session-runtime/services/provider-admin.mjs +77 -0
- package/src/session-runtime/session-create/session-options.mjs +7 -5
- package/src/session-runtime/session-create/session-start-hook.mjs +2 -1
- package/src/session-runtime/session-transcript.mjs +4 -5
- package/src/session-runtime/session-turn-api.mjs +3 -9
- package/src/session-runtime/settings-builtin-tools-api.mjs +9 -6
- package/src/session-runtime/setup-tool/executor.mjs +23 -5
- package/src/session-runtime/setup-tool/extended-actions.mjs +6 -6
- package/src/session-runtime/setup-tool/settings-contract.mjs +7 -2
- package/src/session-runtime/setup-tool/tool-defs.mjs +13 -1
- package/src/session-runtime/tool-policy-refresh.mjs +83 -32
- package/src/session-runtime/turn/agent-ops.mjs +49 -2
- package/src/session-runtime/turn/side-question-ops.mjs +1 -4
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +2 -0
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +4 -0
- package/src/session-runtime/turn/turn-run/turn-settle.mjs +8 -2
- package/src/session-runtime/turn/turn-run.mjs +1 -36
- package/src/session-runtime/usage-stats-api.mjs +4 -1
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +16 -1
- package/src/session-runtime/workspace/admission-gate.mjs +44 -15
- package/src/session-runtime/workspace/project-branches.mjs +28 -36
- package/src/session-runtime/workspace/transition-steps.mjs +96 -95
- package/src/session-runtime/workspace/transitions.mjs +248 -68
- package/src/standalone/channel-transport.mjs +3 -4
- package/src/standalone/daemon-desktop-runtime.mjs +32 -1
- package/src/standalone/daemon-pg-stop.mjs +1 -3
- package/src/standalone/daemon-session-control.mjs +88 -0
- package/src/standalone/daemon-stored-session-views.mjs +23 -3
- package/src/standalone/daemon.mjs +2 -0
- package/src/standalone/desktop-service-registry.mjs +32 -5
- package/src/standalone/local-session-runtime.mjs +11 -12
- package/src/standalone/session-protocol.mjs +7 -0
- package/src/standalone/session-runtime-inline-host.mjs +2 -0
- package/src/standalone/session-service/agent-tree/agent-cancel.mjs +91 -49
- package/src/standalone/session-service/agent-tree/agent-child.mjs +19 -1
- package/src/standalone/session-service/agent-tree/agent-registry.mjs +25 -0
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +9 -1
- package/src/standalone/session-service/agent-tree.mjs +28 -2
- package/src/standalone/session-service/entries/session-loads.mjs +16 -5
- package/src/standalone/session-service/entries.mjs +1 -0
- package/src/standalone/session-service/projection/transcript-window.mjs +39 -0
- package/src/standalone/session-service/quota-resume.mjs +28 -13
- package/src/standalone/session-service/session-calls/session-turns.mjs +17 -6
- package/src/standalone/session-service/session-calls/session-views.mjs +6 -0
- package/src/standalone/session-tool-output.mjs +82 -0
- package/src/tui/app/app-view.jsx +2 -1
- package/src/tui/app/provider-setup/provider-items.mjs +8 -0
- package/src/tui/app/provider-setup-picker.mjs +15 -3
- package/src/tui/app/slash-commands.mjs +6 -0
- package/src/tui/app/slash-dispatch/session-commands.mjs +49 -0
- package/src/tui/components/Spinner.jsx +4 -0
- package/src/tui/components/SpinnerByline.jsx +21 -2
- package/src/tui/components/status-line/local-line.mjs +10 -2
- package/src/tui/dist/index.mjs +529 -270
- package/src/tui/session/active-tool-summary.mjs +1 -1
- package/src/tui/session/context-state.mjs +0 -3
- package/src/tui/session/goal-continuation/continuation-queue.mjs +2 -3
- package/src/tui/session/goal-continuation/deadline-delivery.mjs +4 -2
- package/src/tui/session/goal-continuation/session-boundary.mjs +21 -0
- package/src/tui/session/quota-resume.mjs +140 -61
- package/src/tui/session/session-api/commands.mjs +4 -10
- package/src/tui/session/session-api/controls.mjs +12 -0
- package/src/tui/session/session-api/lifecycle/dispose.mjs +2 -9
- package/src/tui/session/session-api/lifecycle/resume.mjs +2 -7
- package/src/tui/session/session-api/lifecycle.mjs +13 -11
- package/src/tui/session/session-api/media.mjs +2 -0
- package/src/tui/session/session-api/routes.mjs +81 -57
- package/src/tui/session/session-api/settings.mjs +3 -3
- package/src/tui/session/session-api/worktree.mjs +3 -2
- package/src/tui/session/session-flow/auto-clear.mjs +1 -3
- package/src/tui/session/session-flow/drain.mjs +6 -4
- package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -0
- package/src/tui/session/session-flow/queue/take-entries.mjs +2 -1
- package/src/tui/session/session-flow/queue.mjs +1 -1
- package/src/tui/session/tool-approval.mjs +17 -8
- package/src/tui/session/transcript-store/streaming-tail.mjs +1 -1
- package/src/tui/session/tui-steering-persist.mjs +3 -5
- package/src/tui/session/turn-quiet.mjs +53 -0
- package/src/tui/session/turn-stream.mjs +5 -1
- package/src/tui/session/turn.mjs +38 -31
- package/src/tui/session/worktree-setup-notice.mjs +38 -0
- package/src/tui/spinner-meta.mjs +10 -0
- package/src/tui/spinner-verbs.mjs +3 -0
- package/src/ui/statusline.mjs +13 -3
- package/src/ui/streaming-markdown-heal.mjs +70 -18
- package/src/runtime/agent/orchestrator/providers/model-pricing-rates.mjs +0 -1
- package/src/runtime/agent/orchestrator/session/manager/foreign-pending-messages.mjs +0 -346
- package/src/runtime/agent/orchestrator/session/manager/session-owner-liveness.mjs +0 -117
- package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +0 -302
- package/src/session-runtime/services/agent-tool/terminal-reaper/reap-timers.mjs +0 -142
- package/src/session-runtime/turn/share-ops.mjs +0 -77
- package/src/tui/session/cross-surface-share.mjs +0 -406
- package/src/tui/session/live-share/delta.mjs +0 -153
- package/src/tui/session/live-share/mirror.mjs +0 -106
- package/src/tui/session/live-share/owner.mjs +0 -254
- package/src/tui/session/live-share/retry.mjs +0 -54
- package/src/tui/session/live-share/viewer/frame-router.mjs +0 -32
- package/src/tui/session/live-share/viewer/pipe-client.mjs +0 -108
- package/src/tui/session/live-share/viewer/submit-acks.mjs +0 -65
- package/src/tui/session/live-share/viewer/sync-waiters.mjs +0 -53
- package/src/tui/session/live-share/viewer.mjs +0 -99
- package/src/tui/session/live-share/wire.mjs +0 -44
- package/src/tui/session/live-share.mjs +0 -154
- package/src/tui/session/shared-dir-watch.mjs +0 -69
|
@@ -12,23 +12,235 @@
|
|
|
12
12
|
// Once the shell has exited and been reaped, ANY remaining member
|
|
13
13
|
// of that group is a descendant it left behind, and one signal to
|
|
14
14
|
// the negative pgid reaches all of them.
|
|
15
|
-
// win32 —
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
import {
|
|
15
|
+
// win32 — inspect the process table after every root exit. Closed pipes
|
|
16
|
+
// cannot prove termination: descendants may redirect their stdio.
|
|
17
|
+
// The strict snapshot names survivors still linked to the root.
|
|
18
|
+
//
|
|
19
|
+
// Every process is tracked by IDENTITY — pid plus creation token from the
|
|
20
|
+
// same table read — never by pid alone: a recycled pid is a different process
|
|
21
|
+
// and neither keeps a tracked survivor "alive" nor becomes a kill target.
|
|
22
|
+
import { killProcessTrees, readProcessTable } from '../../mcp/child-tree.mjs';
|
|
23
23
|
|
|
24
24
|
const isWin = process.platform === 'win32';
|
|
25
25
|
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
//
|
|
31
|
-
|
|
26
|
+
// Cadence of every termination re-check (unconfirmed kill, background job,
|
|
27
|
+
// survivors a finished command left behind).
|
|
28
|
+
export const TERMINATION_RECHECK_MS = 5_000;
|
|
29
|
+
|
|
30
|
+
// Upper bound on how long a kill waits for its pre-kill snapshot (one process-
|
|
31
|
+
// table read, ~1 s on Windows). The kill is sent at the bound regardless; the
|
|
32
|
+
// read is not abandoned — a snapshot that lands later is still absorbed.
|
|
33
|
+
export const PROCESS_TABLE_READ_BOUND_MS = 2_000;
|
|
34
|
+
|
|
35
|
+
/** Resolve `promise`'s value, or `onTimeout` once `ms` elapse first. The
|
|
36
|
+
* bound's timer is cleared whichever side wins. */
|
|
37
|
+
export function withinBound(promise, ms, onTimeout) {
|
|
38
|
+
let timer = null;
|
|
39
|
+
const bound = new Promise((resolve) => {
|
|
40
|
+
timer = setTimeout(() => resolve(onTimeout), ms);
|
|
41
|
+
timer.unref?.();
|
|
42
|
+
});
|
|
43
|
+
return Promise.race([promise, bound]).finally(() => clearTimeout(timer));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Every kill path: wait for the pre-kill `snapshot` (true, or a promise) at
|
|
47
|
+
* most PROCESS_TABLE_READ_BOUND_MS, then run `kill` whether it landed or not. */
|
|
48
|
+
export function killAfterSnapshot(snapshot, kill) {
|
|
49
|
+
return withinBound(Promise.resolve(snapshot), PROCESS_TABLE_READ_BOUND_MS, false).then(kill);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const identityKey = (entry) => `${entry.pid}:${entry.token ?? ''}`;
|
|
53
|
+
|
|
54
|
+
/** Tracks one command's process tree across its termination.
|
|
55
|
+
*
|
|
56
|
+
* `capture()` records the identities of the root and every descendant while
|
|
57
|
+
* the root is still alive — before a kill can break the parent chain (an
|
|
58
|
+
* exited Windows intermediate orphans its children; a POSIX descendant may
|
|
59
|
+
* leave the process group). `observe()` takes one strict table read and
|
|
60
|
+
* answers 'running', 'gone' or 'unknown':
|
|
61
|
+
* - the root is alive (same identity) → running
|
|
62
|
+
* - no complete pre-exit capture exists → unknown
|
|
63
|
+
* - a tracked identity, a descendant still linked to one,
|
|
64
|
+
* or (POSIX) a member of the command's group is alive → running
|
|
65
|
+
* - a tracked process without a creation token is present → unknown
|
|
66
|
+
* A failed table read rejects; callers treat it as unknown. */
|
|
67
|
+
export function createProcessTreeTracker({ pid, members = [], exitObserved = false } = {}) {
|
|
68
|
+
const rootPid = Number(pid) || 0;
|
|
69
|
+
let rootToken = null;
|
|
70
|
+
let complete = false;
|
|
71
|
+
const tracked = new Map();
|
|
72
|
+
let live = [];
|
|
73
|
+
for (const entry of members) tracked.set(identityKey(entry), { ...entry });
|
|
74
|
+
if (exitObserved) complete = rootPid > 0;
|
|
75
|
+
|
|
76
|
+
const present = (table, entry) => {
|
|
77
|
+
if (!table.tokenOf.has(entry.pid)) return false;
|
|
78
|
+
if (entry.token == null) return null;
|
|
79
|
+
return table.tokenOf.get(entry.pid) === entry.token;
|
|
80
|
+
};
|
|
81
|
+
const track = (table, child) => {
|
|
82
|
+
const entry = { pid: child, token: table.tokenOf.get(child) ?? null, name: table.nameOf.get(child) || '' };
|
|
83
|
+
const key = identityKey(entry);
|
|
84
|
+
if (tracked.has(key)) return false;
|
|
85
|
+
tracked.set(key, entry);
|
|
86
|
+
return true;
|
|
87
|
+
};
|
|
88
|
+
// The root's pid still names OUR root (or nothing): its dangling child links
|
|
89
|
+
// are ours. A different process under that pid is a reuse — not ours.
|
|
90
|
+
const rootPidIsOurs = (table, rootAlive) =>
|
|
91
|
+
rootAlive || !table.tokenOf.has(rootPid) || (rootToken != null && table.tokenOf.get(rootPid) === rootToken);
|
|
92
|
+
|
|
93
|
+
function absorb(table, rootAlive) {
|
|
94
|
+
const from = [];
|
|
95
|
+
if (rootPidIsOurs(table, rootAlive)) from.push(rootPid);
|
|
96
|
+
for (const entry of tracked.values()) if (present(table, entry) === true) from.push(entry.pid);
|
|
97
|
+
const seen = new Set(from);
|
|
98
|
+
while (from.length) {
|
|
99
|
+
const cur = from.pop();
|
|
100
|
+
for (const child of table.childrenOf.get(cur) ?? []) {
|
|
101
|
+
if (seen.has(child)) continue;
|
|
102
|
+
seen.add(child);
|
|
103
|
+
track(table, child);
|
|
104
|
+
from.push(child);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// POSIX: the command owns the group whose id is the root pid. While any
|
|
108
|
+
// member exists that id cannot be reused, so a live process holding the
|
|
109
|
+
// pid that is not our root means our group is gone.
|
|
110
|
+
if (!isWin && rootPidIsOurs(table, rootAlive)) {
|
|
111
|
+
for (const [member, pgid] of table.pgidOf ?? []) {
|
|
112
|
+
if (pgid === rootPid && member !== rootPid) track(table, member);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (rootAlive) {
|
|
116
|
+
rootToken = rootToken ?? table.tokenOf.get(rootPid) ?? null;
|
|
117
|
+
complete = true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function rootIsAlive(table, rootExited) {
|
|
122
|
+
if (rootExited || !table.tokenOf.has(rootPid)) return false;
|
|
123
|
+
return rootToken == null || table.tokenOf.get(rootPid) === rootToken;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
get complete() {
|
|
128
|
+
return complete;
|
|
129
|
+
},
|
|
130
|
+
/** Identities currently known alive after the last observation. */
|
|
131
|
+
get live() {
|
|
132
|
+
return live;
|
|
133
|
+
},
|
|
134
|
+
/** Pre-termination snapshot; call while the root still runs. */
|
|
135
|
+
async capture({ rootExited = false } = {}) {
|
|
136
|
+
if (rootPid <= 0) return false;
|
|
137
|
+
let table = await readProcessTable({ shared: true });
|
|
138
|
+
// A shared read may have begun before this root was spawned.
|
|
139
|
+
if (!table.tokenOf.has(rootPid)) table = await readProcessTable();
|
|
140
|
+
absorb(table, rootIsAlive(table, rootExited));
|
|
141
|
+
return complete;
|
|
142
|
+
},
|
|
143
|
+
/** For a root that exited ON ITS OWN (no kill in between): it is observed
|
|
144
|
+
* after exit exactly like a foreground command's settle. Callers never
|
|
145
|
+
* waive a missing pre-kill snapshot with it. */
|
|
146
|
+
acceptExitObservation() {
|
|
147
|
+
if (rootPid > 0) complete = true;
|
|
148
|
+
},
|
|
149
|
+
async observe({ rootExited = true } = {}) {
|
|
150
|
+
if (rootPid <= 0) return 'unknown';
|
|
151
|
+
const table = await readProcessTable();
|
|
152
|
+
const rootAlive = rootIsAlive(table, rootExited);
|
|
153
|
+
absorb(table, rootAlive);
|
|
154
|
+
live = [];
|
|
155
|
+
if (rootAlive) {
|
|
156
|
+
live = [{ pid: rootPid, token: rootToken, name: table.nameOf.get(rootPid) || '' }];
|
|
157
|
+
return 'running';
|
|
158
|
+
}
|
|
159
|
+
if (!complete) return 'unknown';
|
|
160
|
+
let unknown = false;
|
|
161
|
+
for (const entry of tracked.values()) {
|
|
162
|
+
const state = present(table, entry);
|
|
163
|
+
if (state === true) live.push(entry);
|
|
164
|
+
else if (state === null) unknown = true;
|
|
165
|
+
}
|
|
166
|
+
if (live.length > 0) return 'running';
|
|
167
|
+
return unknown ? 'unknown' : 'gone';
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** One observation loop: observe now, then every `intervalMs` until the tree
|
|
173
|
+
* is confirmed gone, then run `onTerminated` once. 'running', 'unknown', a
|
|
174
|
+
* failed or unanswered (by the next cadence tick) observation and a failing
|
|
175
|
+
* `onTerminated` all re-check on the same cadence; the first pass that is not
|
|
176
|
+
* 'gone' calls `onUnconfirmed` once. The returned stop() clears the pending
|
|
177
|
+
* timer; nothing else (listener, timer) outlives the loop. Nothing in it ever
|
|
178
|
+
* releases on time alone. */
|
|
179
|
+
export function watchTermination({
|
|
180
|
+
tracker,
|
|
181
|
+
rootExited = () => true,
|
|
182
|
+
onTerminated,
|
|
183
|
+
onUnconfirmed = null,
|
|
184
|
+
releaseRequested = null,
|
|
185
|
+
intervalMs = TERMINATION_RECHECK_MS,
|
|
186
|
+
}) {
|
|
187
|
+
let timer = null;
|
|
188
|
+
let stopped = false;
|
|
189
|
+
let reportedUnconfirmed = false;
|
|
190
|
+
const stop = () => {
|
|
191
|
+
stopped = true;
|
|
192
|
+
if (timer) clearTimeout(timer);
|
|
193
|
+
timer = null;
|
|
194
|
+
};
|
|
195
|
+
const pass = async () => {
|
|
196
|
+
timer = null;
|
|
197
|
+
if (stopped) return;
|
|
198
|
+
let state = 'unknown';
|
|
199
|
+
let observation = null;
|
|
200
|
+
// An explicit user force-release made from another process (the hold's
|
|
201
|
+
// workspace is visible there): released as if confirmed, never on time.
|
|
202
|
+
if (releaseRequested?.()) state = 'gone';
|
|
203
|
+
else
|
|
204
|
+
try {
|
|
205
|
+
observation = tracker.observe({ rootExited: rootExited() });
|
|
206
|
+
observation.catch(() => {});
|
|
207
|
+
// A pass that has not answered by the next cadence tick is unknown.
|
|
208
|
+
state = await withinBound(observation, intervalMs, 'unknown');
|
|
209
|
+
} catch {
|
|
210
|
+
state = 'unknown';
|
|
211
|
+
}
|
|
212
|
+
if (stopped) return;
|
|
213
|
+
if (state !== 'gone' && !reportedUnconfirmed) {
|
|
214
|
+
reportedUnconfirmed = true;
|
|
215
|
+
try {
|
|
216
|
+
onUnconfirmed?.();
|
|
217
|
+
} catch {
|
|
218
|
+
/* presentation only */
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (state === 'gone') {
|
|
222
|
+
try {
|
|
223
|
+
await onTerminated();
|
|
224
|
+
stop();
|
|
225
|
+
return;
|
|
226
|
+
} catch {
|
|
227
|
+
/* the release failed: the reservation stays and is re-checked */
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// The next pass never starts beside this pass's enumerator: a table read
|
|
231
|
+
// always settles by its own deadline (killed on timeout).
|
|
232
|
+
if (observation)
|
|
233
|
+
await observation.then(
|
|
234
|
+
() => {},
|
|
235
|
+
() => {}
|
|
236
|
+
);
|
|
237
|
+
if (stopped) return;
|
|
238
|
+
timer = setTimeout(pass, intervalMs);
|
|
239
|
+
timer.unref?.();
|
|
240
|
+
};
|
|
241
|
+
void pass();
|
|
242
|
+
return stop;
|
|
243
|
+
}
|
|
32
244
|
|
|
33
245
|
// Windows attaches a console host to the hidden console the shell runs in. It
|
|
34
246
|
// is a real child of the shell and it lives exactly as long as console clients
|
|
@@ -44,7 +256,7 @@ function groupAlive(pgid) {
|
|
|
44
256
|
process.kill(-pgid, 0);
|
|
45
257
|
return true;
|
|
46
258
|
} catch (error) {
|
|
47
|
-
return error?.code
|
|
259
|
+
return error?.code !== 'ESRCH';
|
|
48
260
|
}
|
|
49
261
|
}
|
|
50
262
|
|
|
@@ -61,73 +273,101 @@ function delay(ms) {
|
|
|
61
273
|
* reports a plain completion), or a handle describing the survivors:
|
|
62
274
|
* groupPid POSIX process group that still has members
|
|
63
275
|
* killPids pids a cancellation can signal directly
|
|
64
|
-
* watchPids pids
|
|
276
|
+
* watchPids pids of the observed survivors
|
|
277
|
+
* watch their identities ({ pid, token, name }); liveness and kills
|
|
278
|
+
* compare these, never the bare pid
|
|
65
279
|
* reachable false when survivors are observable but no longer addressable
|
|
66
280
|
* by pid (Windows re-parents nothing: a process whose parent
|
|
67
281
|
* exited keeps a dangling parent id, so an intermediate that
|
|
68
|
-
* already exited breaks the link).
|
|
69
|
-
|
|
282
|
+
* already exited breaks the link).
|
|
283
|
+
* Rejects when the process table cannot be read completely. */
|
|
284
|
+
export async function probeShellDescendants({ pid } = {}) {
|
|
70
285
|
const root = Number(pid) || 0;
|
|
71
286
|
if (root <= 0) return null;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
287
|
+
// POSIX fast path: no member of the command's group at all.
|
|
288
|
+
if (!isWin && !groupAlive(root)) return null;
|
|
289
|
+
const tracker = createProcessTreeTracker({ pid: root, exitObserved: true });
|
|
290
|
+
const state = await tracker.observe({ rootExited: true });
|
|
291
|
+
if (state === 'gone') return null;
|
|
292
|
+
if (state === 'unknown') throw new Error('Remaining child processes could not be verified.');
|
|
293
|
+
return survivorHandle(root, tracker.live);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function survivorHandle(rootPid, live) {
|
|
297
|
+
const watch = live.map(({ pid, token, name }) => ({ pid, token, name }));
|
|
298
|
+
const killPids = watch.filter((entry) => !CONSOLE_HOST.test(entry.name)).map((entry) => entry.pid);
|
|
81
299
|
return {
|
|
82
|
-
|
|
300
|
+
rootPid,
|
|
301
|
+
groupPid: isWin ? null : rootPid,
|
|
83
302
|
killPids,
|
|
84
|
-
watchPids:
|
|
85
|
-
|
|
303
|
+
watchPids: watch.map((entry) => entry.pid),
|
|
304
|
+
watch,
|
|
305
|
+
reachable: !isWin || killPids.length > 0,
|
|
86
306
|
};
|
|
87
307
|
}
|
|
88
308
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (!handle) return false;
|
|
92
|
-
if (handle.groupPid) return groupAlive(handle.groupPid);
|
|
93
|
-
return handle.watchPids.some((pid) => isPidAlive(pid));
|
|
94
|
-
}
|
|
309
|
+
const trackerFor = (handle) =>
|
|
310
|
+
createProcessTreeTracker({ pid: handle.rootPid, members: handle.watch, exitObserved: true });
|
|
95
311
|
|
|
96
|
-
/** Resolve once every observed survivor has exited
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
312
|
+
/** Resolve once every observed survivor (by identity) has exited, or false
|
|
313
|
+
* once `signal` aborts. These processes are not our children any more, so
|
|
314
|
+
* there is no exit event to subscribe to; the shared observation loop
|
|
315
|
+
* re-checks them at the termination cadence. */
|
|
316
|
+
export function waitForShellDescendants(handle, { signal = null } = {}) {
|
|
317
|
+
if (!handle) return Promise.resolve(true);
|
|
318
|
+
return new Promise((resolve) => {
|
|
319
|
+
if (signal?.aborted) {
|
|
320
|
+
resolve(false);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const onAbort = () => {
|
|
324
|
+
stop();
|
|
325
|
+
resolve(false);
|
|
326
|
+
};
|
|
327
|
+
const stop = watchTermination({
|
|
328
|
+
tracker: trackerFor(handle),
|
|
329
|
+
onTerminated: () => {
|
|
330
|
+
signal?.removeEventListener('abort', onAbort);
|
|
331
|
+
resolve(true);
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
335
|
+
});
|
|
105
336
|
}
|
|
106
337
|
|
|
107
|
-
/** Terminate the survivors
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* signalled. */
|
|
338
|
+
/** Terminate the survivors, signalling only processes whose identity still
|
|
339
|
+
* matches the observation, then RE-OBSERVE so the caller reports what
|
|
340
|
+
* actually died instead of what was signalled. Never rejects. */
|
|
111
341
|
export async function killShellDescendants(handle) {
|
|
112
342
|
if (!handle) return { terminated: true, survivors: [] };
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
/* already gone */
|
|
118
|
-
}
|
|
119
|
-
await delay(200);
|
|
120
|
-
if (groupAlive(handle.groupPid)) {
|
|
343
|
+
try {
|
|
344
|
+
const table = await readProcessTable();
|
|
345
|
+
// The group id is ours only while no other process holds the root pid.
|
|
346
|
+
if (handle.groupPid && !table.tokenOf.has(handle.groupPid)) {
|
|
121
347
|
try {
|
|
122
|
-
process.kill(-handle.groupPid, '
|
|
348
|
+
process.kill(-handle.groupPid, 'SIGTERM');
|
|
123
349
|
} catch {
|
|
124
350
|
/* already gone */
|
|
125
351
|
}
|
|
126
|
-
await delay(
|
|
352
|
+
await delay(200);
|
|
353
|
+
if (groupAlive(handle.groupPid)) {
|
|
354
|
+
try {
|
|
355
|
+
process.kill(-handle.groupPid, 'SIGKILL');
|
|
356
|
+
} catch {
|
|
357
|
+
/* already gone */
|
|
358
|
+
}
|
|
359
|
+
await delay(100);
|
|
360
|
+
}
|
|
127
361
|
}
|
|
128
|
-
|
|
362
|
+
const targets = handle.watch
|
|
363
|
+
.filter((entry) => entry.token != null && table.tokenOf.get(entry.pid) === entry.token)
|
|
364
|
+
.filter((entry) => !CONSOLE_HOST.test(entry.name))
|
|
365
|
+
.map((entry) => entry.pid);
|
|
366
|
+
await killProcessTrees(targets).catch(() => {});
|
|
367
|
+
const tracker = trackerFor(handle);
|
|
368
|
+
const state = await tracker.observe({ rootExited: true });
|
|
369
|
+
return { terminated: state === 'gone', survivors: tracker.live.map((entry) => entry.pid) };
|
|
370
|
+
} catch {
|
|
371
|
+
return { terminated: false, survivors: [...handle.watchPids] };
|
|
129
372
|
}
|
|
130
|
-
await killProcessTrees(handle.killPids).catch(() => {});
|
|
131
|
-
const survivors = handle.watchPids.filter((pid) => isPidAlive(pid));
|
|
132
|
-
return { terminated: survivors.length === 0, survivors };
|
|
133
373
|
}
|
|
@@ -3,20 +3,65 @@
|
|
|
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 { probeShellDescendants,
|
|
6
|
+
import { createProcessTreeTracker, probeShellDescendants, watchTermination } from './shell-descendants.mjs';
|
|
7
|
+
import { unregisterOwnedShellTree } from './owned-shell-trees.mjs';
|
|
8
|
+
import {
|
|
9
|
+
adoptShellWorkspaceAdmission,
|
|
10
|
+
clearTerminationHold,
|
|
11
|
+
registerTerminationHold,
|
|
12
|
+
takeReleaseRequest,
|
|
13
|
+
} from '../builtin/lib/shell-job-records.mjs';
|
|
7
14
|
import {
|
|
8
15
|
clearForegroundRecord,
|
|
9
16
|
clearForegroundTimers,
|
|
10
17
|
detachAbortHandler,
|
|
18
|
+
keepTerminationUnconfirmedRecord,
|
|
11
19
|
releaseResourceLease,
|
|
12
20
|
} from './shell-run-state.mjs';
|
|
13
21
|
|
|
14
22
|
// Unconfirmed-kill cleanup ceiling: the result is already reported, but the
|
|
15
|
-
// tree may still be alive, so closing the capture and returning the
|
|
16
|
-
//
|
|
17
|
-
//
|
|
23
|
+
// tree may still be alive, so closing the capture and returning the resource
|
|
24
|
+
// lease wait for the child's real exit — bounded so an unkillable tree cannot
|
|
25
|
+
// pin the shell lane forever. The workspace admission is NOT bounded by it:
|
|
26
|
+
// it follows confirmed termination only (see below).
|
|
18
27
|
const UNCONFIRMED_KILL_CLEANUP_CEILING_MS = 60_000;
|
|
19
28
|
|
|
29
|
+
/** Lifecycle completion of a run whose result is already reported: release
|
|
30
|
+
* `run.workspaceAdmission` and retire the live record only once the root AND
|
|
31
|
+
* every descendant identity captured before the kill are confirmed gone. One
|
|
32
|
+
* observation loop at the termination cadence; running, unknown (incomplete
|
|
33
|
+
* snapshot, failed table read) and a failing release all re-check, and no
|
|
34
|
+
* timer ever releases on its own. */
|
|
35
|
+
export function releaseAdmissionOnConfirmedTermination(run, { markNow = true } = {}) {
|
|
36
|
+
const { child } = run;
|
|
37
|
+
if (!child || run.stopTerminationWatch) return;
|
|
38
|
+
// Visible as "termination unconfirmed" until the lifecycle ends, with an
|
|
39
|
+
// explicit user force-release as the only other way out. An unconfirmed
|
|
40
|
+
// kill is marked at once; an owner-cancelled tree whose root is already
|
|
41
|
+
// gone only once its first observation is not 'gone'.
|
|
42
|
+
if (markNow) keepTerminationUnconfirmedRecord(run);
|
|
43
|
+
const release = () => {
|
|
44
|
+
run.workspaceAdmission?.();
|
|
45
|
+
run.workspaceAdmission = null;
|
|
46
|
+
run.stopTerminationWatch?.();
|
|
47
|
+
clearTerminationHold(run.foregroundRecordId);
|
|
48
|
+
clearForegroundRecord(run);
|
|
49
|
+
};
|
|
50
|
+
registerTerminationHold(run.foregroundRecordId, {
|
|
51
|
+
ownerSessionId: run.foregroundRecordInput?.ownerSessionId || null,
|
|
52
|
+
cwd: run.foregroundRecordInput?.cwd || null,
|
|
53
|
+
release,
|
|
54
|
+
});
|
|
55
|
+
run.terminationTracker = run.terminationTracker || createProcessTreeTracker({ pid: child.pid });
|
|
56
|
+
run.stopTerminationWatch = watchTermination({
|
|
57
|
+
tracker: run.terminationTracker,
|
|
58
|
+
rootExited: () => child.exitCode != null || child.signalCode != null,
|
|
59
|
+
onTerminated: release,
|
|
60
|
+
onUnconfirmed: markNow ? null : () => keepTerminationUnconfirmedRecord(run),
|
|
61
|
+
releaseRequested: () => takeReleaseRequest(run.foregroundRecordId),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
20
65
|
// Promotion resolves the call and hands the child's lifecycle to the
|
|
21
66
|
// shell-jobs registry, but the spill FDs stay owned by THIS runner and
|
|
22
67
|
// nothing else would ever close them — one leaked descriptor pair per
|
|
@@ -49,9 +94,17 @@ export function createPromotedCaptureRelease(run) {
|
|
|
49
94
|
|
|
50
95
|
function deferCleanupToChildExit(run) {
|
|
51
96
|
let done = false;
|
|
97
|
+
let ceiling = null;
|
|
52
98
|
const cleanup = () => {
|
|
53
99
|
if (done) return;
|
|
54
100
|
done = true;
|
|
101
|
+
clearTimeout(ceiling);
|
|
102
|
+
try {
|
|
103
|
+
run.child.removeListener('close', cleanup);
|
|
104
|
+
run.child.removeListener('exit', cleanup);
|
|
105
|
+
} catch {
|
|
106
|
+
/* not an emitter */
|
|
107
|
+
}
|
|
55
108
|
try {
|
|
56
109
|
run.taskOutput.closeFds();
|
|
57
110
|
} catch {
|
|
@@ -65,8 +118,9 @@ function deferCleanupToChildExit(run) {
|
|
|
65
118
|
} catch {
|
|
66
119
|
/* child may already be gone */
|
|
67
120
|
}
|
|
68
|
-
|
|
121
|
+
ceiling = setTimeout(cleanup, UNCONFIRMED_KILL_CLEANUP_CEILING_MS);
|
|
69
122
|
if (ceiling.unref) ceiling.unref();
|
|
123
|
+
releaseAdmissionOnConfirmedTermination(run);
|
|
70
124
|
}
|
|
71
125
|
|
|
72
126
|
// getStdout/getStderr can throw on a spilled-file read failure (EBADF after
|
|
@@ -96,21 +150,17 @@ export async function readCapturedOutput(taskOutput, final = false) {
|
|
|
96
150
|
// instead of a clean finish; null means nothing survived.
|
|
97
151
|
async function probeSurvivingDescendants(run) {
|
|
98
152
|
if (run.killed || run.timedOut || !run.child?.pid) return null;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (descendants) descendants.taskId = run.foregroundRecordId;
|
|
105
|
-
return descendants;
|
|
106
|
-
} catch {
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
153
|
+
const descendants = await probeShellDescendants({
|
|
154
|
+
pid: run.child.pid,
|
|
155
|
+
});
|
|
156
|
+
if (descendants) descendants.taskId = run.foregroundRecordId;
|
|
157
|
+
return descendants;
|
|
109
158
|
}
|
|
110
159
|
|
|
111
160
|
export function createSettle(run, releasePromotedCapture) {
|
|
112
161
|
return async (exitCode, signal) => {
|
|
113
162
|
if (run.settled) return;
|
|
163
|
+
if (run.ownedTree) unregisterOwnedShellTree(run.foregroundRecordId, run.ownedTree);
|
|
114
164
|
if (run.autoBackgrounded) {
|
|
115
165
|
releasePromotedCapture();
|
|
116
166
|
return;
|
|
@@ -119,7 +169,15 @@ export function createSettle(run, releasePromotedCapture) {
|
|
|
119
169
|
const { taskOutput } = run;
|
|
120
170
|
// Off the readouts the instant the command is over, before any awaited
|
|
121
171
|
// output capture — a finished command must never linger as "running".
|
|
122
|
-
|
|
172
|
+
// An unconfirmed kill is NOT over: its record stays, marked unconfirmed
|
|
173
|
+
// (releaseAdmissionOnConfirmedTermination), until every process is
|
|
174
|
+
// confirmed gone or the user releases it.
|
|
175
|
+
// Every other killed run (timeout, abort, session close) completes the
|
|
176
|
+
// same way: its pre-kill snapshot, not the root's exit, decides when it
|
|
177
|
+
// is over — a captured descendant can outlive the root it was killed with.
|
|
178
|
+
const confirmKill = run.killed === true && !run.killUnconfirmed && Boolean(run.child?.pid);
|
|
179
|
+
const lifecycleHeld = run.killUnconfirmed || confirmKill;
|
|
180
|
+
if (!lifecycleHeld) clearForegroundRecord(run);
|
|
123
181
|
clearForegroundTimers(run);
|
|
124
182
|
detachAbortHandler(run);
|
|
125
183
|
const stdoutTail = run.stdoutDecoder.end();
|
|
@@ -139,7 +197,8 @@ export function createSettle(run, releasePromotedCapture) {
|
|
|
139
197
|
if (run.killUnconfirmed) {
|
|
140
198
|
// Never confirmed dead: deleting the capture files or handing back the
|
|
141
199
|
// lease now would release resources a live process still owns. The
|
|
142
|
-
// spilled paths therefore survive and travel with the result below
|
|
200
|
+
// spilled paths therefore survive and travel with the result below, and
|
|
201
|
+
// the workspace admission stays held until termination is confirmed.
|
|
143
202
|
deferCleanupToChildExit(run);
|
|
144
203
|
} else if (taskOutput.spilled && taskOutput.totalDiskBytes() <= SHELL_OUTPUT_INLINE_CAP) {
|
|
145
204
|
taskOutput.deleteFiles();
|
|
@@ -148,7 +207,38 @@ export function createSettle(run, releasePromotedCapture) {
|
|
|
148
207
|
taskOutput.closeFds();
|
|
149
208
|
void releaseResourceLease(run);
|
|
150
209
|
}
|
|
151
|
-
|
|
210
|
+
let descendants = null;
|
|
211
|
+
let descendantsUnknown = false;
|
|
212
|
+
try {
|
|
213
|
+
descendants = await probeSurvivingDescendants(run);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
descendantsUnknown = true;
|
|
216
|
+
taskOutput.writeError = taskOutput.writeError || error;
|
|
217
|
+
exitCode = exitCode || 1;
|
|
218
|
+
run.failureReason = 'Remaining child processes could not be verified; workspace cleanup is blocked.';
|
|
219
|
+
// The root exited on its own, so it is observed after exit like any
|
|
220
|
+
// settle; the held admission joins the termination watch and the
|
|
221
|
+
// explicit force-release, and the record is kept, marked unconfirmed.
|
|
222
|
+
// (The probe runs only for an unkilled root, so the lifecycle is not
|
|
223
|
+
// already held here.)
|
|
224
|
+
run.terminationTracker = run.terminationTracker || createProcessTreeTracker({ pid: run.child.pid });
|
|
225
|
+
run.terminationTracker.acceptExitObservation();
|
|
226
|
+
releaseAdmissionOnConfirmedTermination(run);
|
|
227
|
+
}
|
|
228
|
+
// Marked unconfirmed (with its force-release) only once an observation
|
|
229
|
+
// finds a captured identity still alive or cannot tell.
|
|
230
|
+
if (confirmKill) releaseAdmissionOnConfirmedTermination(run, { markNow: false });
|
|
231
|
+
if (!lifecycleHeld && !descendantsUnknown && run.workspaceAdmission) {
|
|
232
|
+
try {
|
|
233
|
+
if (descendants) adoptShellWorkspaceAdmission(run.foregroundRecordId, run.workspaceAdmission);
|
|
234
|
+
else run.workspaceAdmission();
|
|
235
|
+
run.workspaceAdmission = null;
|
|
236
|
+
} catch (error) {
|
|
237
|
+
taskOutput.writeError = taskOutput.writeError || error;
|
|
238
|
+
exitCode = exitCode || 1;
|
|
239
|
+
run.failureReason = 'workspace admission cleanup failed';
|
|
240
|
+
}
|
|
241
|
+
}
|
|
152
242
|
run.resolveResult(
|
|
153
243
|
new ExecResult({
|
|
154
244
|
stdout,
|