gsd-pi 2.80.0-dev.e146beb20 → 2.80.0-dev.e6c48c3af
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/phases.js +59 -21
- package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +17 -2
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto-prompts.js +13 -1
- package/dist/resources/extensions/gsd/auto-recovery.js +43 -1
- package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
- package/dist/resources/extensions/gsd/auto.js +84 -5
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +21 -2
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +27 -20
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +75 -4
- package/dist/resources/extensions/gsd/clean-root-preflight.js +24 -6
- package/dist/resources/extensions/gsd/context-budget.js +37 -2
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +39 -0
- package/dist/resources/extensions/gsd/db-base-schema.js +4 -2
- package/dist/resources/extensions/gsd/db-migration-steps.js +6 -0
- package/dist/resources/extensions/gsd/git-service.js +36 -4
- package/dist/resources/extensions/gsd/gsd-db.js +46 -13
- package/dist/resources/extensions/gsd/guided-flow.js +33 -4
- package/dist/resources/extensions/gsd/memory-store.js +69 -12
- package/dist/resources/extensions/gsd/migrate/command.js +40 -1
- package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
- package/dist/resources/extensions/gsd/pre-execution-checks.js +7 -0
- package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/dist/resources/extensions/gsd/quick.js +34 -2
- package/dist/resources/extensions/gsd/tools/context-mode-tool-result.js +15 -0
- package/dist/resources/extensions/gsd/tools/exec-search-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +3 -15
- package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
- package/dist/resources/extensions/gsd/tools/resume-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
- package/dist/resources/extensions/gsd/unit-context-composer.js +12 -3
- package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +33 -17
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +16 -16
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +3 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +22 -17
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +75 -2
- package/packages/mcp-server/src/workflow-tools.ts +30 -16
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +32 -0
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +15 -0
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +12 -3
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +3 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +3 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +12 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +13 -5
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +36 -0
- package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +18 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +14 -3
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +3 -1
- package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
- package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +2 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +5 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +12 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +4 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +22 -7
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +18 -8
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/tui.ts +20 -8
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -2
- package/src/resources/extensions/gsd/auto/phases.ts +85 -35
- package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +22 -2
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +17 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +54 -0
- package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
- package/src/resources/extensions/gsd/auto.ts +96 -4
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +21 -1
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +27 -19
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +88 -4
- package/src/resources/extensions/gsd/clean-root-preflight.ts +32 -7
- package/src/resources/extensions/gsd/context-budget.ts +44 -2
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +41 -0
- package/src/resources/extensions/gsd/db-base-schema.ts +4 -2
- package/src/resources/extensions/gsd/db-migration-steps.ts +8 -0
- package/src/resources/extensions/gsd/git-service.ts +46 -8
- package/src/resources/extensions/gsd/gsd-db.ts +50 -13
- package/src/resources/extensions/gsd/guided-flow.ts +49 -4
- package/src/resources/extensions/gsd/memory-store.ts +77 -12
- package/src/resources/extensions/gsd/migrate/command.ts +47 -1
- package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
- package/src/resources/extensions/gsd/pre-execution-checks.ts +7 -0
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/src/resources/extensions/gsd/quick.ts +37 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +215 -1
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +56 -13
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +166 -4
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +15 -6
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
- package/src/resources/extensions/gsd/tests/exec-history.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +239 -1
- package/src/resources/extensions/gsd/tests/memory-decay-factor.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +136 -4
- package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +63 -1
- package/src/resources/extensions/gsd/tools/context-mode-tool-result.ts +25 -0
- package/src/resources/extensions/gsd/tools/exec-search-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -23
- package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
- package/src/resources/extensions/gsd/tools/resume-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
- package/src/resources/extensions/gsd/unit-context-composer.ts +19 -4
- package/src/resources/extensions/gsd/unit-runtime.ts +11 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +36 -15
- /package/dist/web/standalone/.next/static/{y73quA-XdLo9n41nxphjW → 4dQ9NTZJ8pEvFwBgDUX93}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{y73quA-XdLo9n41nxphjW → 4dQ9NTZJ8pEvFwBgDUX93}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -259,6 +259,7 @@ Full documentation is in the [`docs/`](./docs/) directory:
|
|
|
259
259
|
|
|
260
260
|
- **[Architecture](./docs/dev/architecture.md)** — system design and dispatch pipeline
|
|
261
261
|
- **[CI/CD Pipeline](./docs/dev/ci-cd-pipeline.md)** — three-stage promotion pipeline (Dev → Test → Prod)
|
|
262
|
+
- **[E2E Testing](./tests/e2e/README.md)** — real-process CLI/runtime coverage and CI runner expectations
|
|
262
263
|
- **[Pipeline Simplification (ADR-003)](./docs/dev/ADR-003-pipeline-simplification.md)** — merged research into planning, mechanical completion
|
|
263
264
|
- **[VS Code Extension](./vscode-extension/README.md)** — chat participant, sidebar dashboard, RPC integration
|
|
264
265
|
|
|
@@ -361,7 +362,7 @@ The database is authoritative for milestones, slices, tasks, requirements, decis
|
|
|
361
362
|
|
|
362
363
|
2. **Context pre-loading** — The dispatch prompt includes inlined task plans, slice plans, prior task summaries, dependency summaries, roadmap excerpts, and decisions register. The LLM starts with everything it needs instead of spending tool calls reading files.
|
|
363
364
|
|
|
364
|
-
3. **Context Mode** — Context Mode is enabled by default and gives
|
|
365
|
+
3. **Context Mode** — Context Mode is enabled by default and gives eligible auto-mode units guidance for preserving context. Agents are steered toward `gsd_exec` for noisy scans, builds, tests, and diagnostics; capped stdout/stderr and metadata are saved under `.gsd/exec/` while only a short digest enters the conversation. `gsd_exec_search` lets agents reuse prior runs instead of repeating expensive checks, and `gsd_resume` reads a prior compaction snapshot from `.gsd/last-snapshot.md` when one exists. Opt out with `context_mode.enabled: false` to disable Context Mode guidance, snapshot injection, `gsd_exec`, `gsd_exec_search`, and `gsd_resume`; tune sandbox timeout/output caps and environment forwarding with `context_mode.exec_timeout_ms`, `context_mode.exec_stdout_cap_bytes`, `context_mode.exec_digest_chars`, and `context_mode.exec_env_allowlist`.
|
|
365
366
|
|
|
366
367
|
4. **Git isolation** — When `git.isolation` is set to `worktree` or `branch`, each milestone runs on its own `milestone/<MID>` branch (in a worktree or in-place). All slice work commits sequentially — no branch switching, no merge conflicts. When the milestone completes, it's squash-merged to main as one clean commit. The default is `none` (work on the current branch), configurable via preferences. If `worktree` is configured in a repo with no committed `HEAD`, GSD temporarily behaves as `none` until the first commit exists because git worktrees need a committed start point.
|
|
367
368
|
|
|
@@ -659,10 +660,11 @@ auto_report: true
|
|
|
659
660
|
| `unique_milestone_ids` | Uses unique milestone names to avoid clashes when working in teams of people |
|
|
660
661
|
| `git.isolation` | `none` (default), `worktree`, or `branch` — enable worktree or branch isolation for milestone work. `worktree` requires a committed `HEAD`; zero-commit repos temporarily run as `none` |
|
|
661
662
|
| `git.manage_gitignore` | Set `false` to prevent GSD from modifying `.gitignore` |
|
|
662
|
-
| `context_mode.enabled` | Context Mode is default-on; set `false` to disable `gsd_exec`,
|
|
663
|
+
| `context_mode.enabled` | Context Mode is default-on; set `false` to disable prompt guidance, snapshot injection, `gsd_exec`, `gsd_exec_search`, and `gsd_resume` |
|
|
663
664
|
| `context_mode.exec_timeout_ms` | Timeout for sandboxed `gsd_exec` runs (default: 30000) |
|
|
664
665
|
| `context_mode.exec_stdout_cap_bytes` | Persisted stdout cap for `gsd_exec` output (default: 1048576) |
|
|
665
666
|
| `context_mode.exec_digest_chars` | Trailing stdout characters returned to the agent context (default: 300) |
|
|
667
|
+
| `context_mode.exec_env_allowlist` | Environment variables forwarded to sandboxed `gsd_exec` runs in addition to `PATH` and `HOME` |
|
|
666
668
|
| `verification_commands` | Array of shell commands to run after task execution (e.g., `["npm run lint", "npm run test"]`) |
|
|
667
669
|
| `verification_auto_fix` | Auto-retry on verification failures (default: true) |
|
|
668
670
|
| `verification_max_retries` | Max retries for verification failures (default: 2) |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
533c696bf1b57db9
|
|
@@ -23,12 +23,12 @@ import { existsSync, cpSync } from "node:fs";
|
|
|
23
23
|
import { logWarning, logError, _resetLogs, drainLogs, drainAndSummarize, formatForNotification, hasAnyIssues, } from "../workflow-logger.js";
|
|
24
24
|
import { gsdRoot } from "../paths.js";
|
|
25
25
|
import { atomicWriteSync } from "../atomic-write.js";
|
|
26
|
-
import { verifyExpectedArtifact, diagnoseExpectedArtifact, buildLoopRemediationSteps } from "../auto-recovery.js";
|
|
26
|
+
import { verifyExpectedArtifact, diagnoseExpectedArtifact, buildLoopRemediationSteps, refreshRecoveryDbForArtifact } from "../auto-recovery.js";
|
|
27
27
|
import { writeUnitRuntimeRecord } from "../unit-runtime.js";
|
|
28
28
|
import { withTimeout, FINALIZE_PRE_TIMEOUT_MS, FINALIZE_POST_TIMEOUT_MS } from "./finalize-timeout.js";
|
|
29
29
|
import { getEligibleSlices } from "../slice-parallel-eligibility.js";
|
|
30
30
|
import { startSliceParallel } from "../slice-parallel-orchestrator.js";
|
|
31
|
-
import { isDbAvailable, getMilestoneSlices
|
|
31
|
+
import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
|
|
32
32
|
import { ensurePlanV2Graph, isEmptyPlanV2GraphResult, isMissingFinalizedContextResult } from "../uok/plan-v2.js";
|
|
33
33
|
import { resolveUokFlags } from "../uok/flags.js";
|
|
34
34
|
import { UokGateRunner } from "../uok/gate-runner.js";
|
|
@@ -42,13 +42,6 @@ import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForAutoUnit,
|
|
|
42
42
|
function isSamePathLocal(a, b) {
|
|
43
43
|
return normalizeWorktreePathForCompare(a) === normalizeWorktreePathForCompare(b);
|
|
44
44
|
}
|
|
45
|
-
function refreshPlanSliceRecoveryDbIfNeeded(unitType) {
|
|
46
|
-
if (unitType !== "plan-slice")
|
|
47
|
-
return true;
|
|
48
|
-
if (!isDbAvailable())
|
|
49
|
-
return true;
|
|
50
|
-
return refreshOpenDatabaseFromDisk();
|
|
51
|
-
}
|
|
52
45
|
// ─── Session timeout auto-resume state ────────────────────────────────────────
|
|
53
46
|
let consecutiveSessionTimeouts = 0;
|
|
54
47
|
const MAX_SESSION_TIMEOUT_AUTO_RESUMES = 3;
|
|
@@ -136,6 +129,22 @@ async function closeoutAndStop(ctx, pi, s, deps, reason) {
|
|
|
136
129
|
}
|
|
137
130
|
await deps.stopAuto(ctx, pi, reason);
|
|
138
131
|
}
|
|
132
|
+
async function stopOnPostflightRecoveryNeeded(ic, result, milestoneId) {
|
|
133
|
+
if (!result.needsManualRecovery)
|
|
134
|
+
return null;
|
|
135
|
+
const { ctx, pi, deps } = ic;
|
|
136
|
+
const reason = `Post-merge stash restore failed for milestone ${milestoneId}`;
|
|
137
|
+
ctx.ui.notify(`${reason}. Resolve the working tree before resuming auto-mode. ${result.message}`, "error");
|
|
138
|
+
await deps.stopAuto(ctx, pi, reason);
|
|
139
|
+
return { action: "break", reason: "postflight-stash-restore-failed" };
|
|
140
|
+
}
|
|
141
|
+
async function restorePreflightStashOrStop(ic, preflight, milestoneId) {
|
|
142
|
+
if (!preflight.stashPushed)
|
|
143
|
+
return null;
|
|
144
|
+
const { ctx, s, deps } = ic;
|
|
145
|
+
const result = deps.postflightPopStash(s.originalBasePath || s.basePath, milestoneId, preflight.stashMarker, ctx.ui.notify.bind(ctx.ui));
|
|
146
|
+
return stopOnPostflightRecoveryNeeded(ic, result, milestoneId);
|
|
147
|
+
}
|
|
139
148
|
async function emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, errorContext) {
|
|
140
149
|
ic.deps.emitJournalEvent({
|
|
141
150
|
ts: new Date().toISOString(),
|
|
@@ -482,6 +491,8 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
482
491
|
const preflightTransition = deps.preflightCleanRoot(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
|
|
483
492
|
try {
|
|
484
493
|
deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
|
|
494
|
+
// Prevent stopAuto() from attempting the same merge again if postflight recovery stops here.
|
|
495
|
+
s.milestoneMergedInPhases = true;
|
|
485
496
|
}
|
|
486
497
|
catch (mergeErr) {
|
|
487
498
|
if (mergeErr instanceof MergeConflictError) {
|
|
@@ -497,8 +508,10 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
497
508
|
return { action: "break", reason: "merge-failed" };
|
|
498
509
|
}
|
|
499
510
|
// #2909: postflight — restore stashed changes after successful merge
|
|
500
|
-
|
|
501
|
-
|
|
511
|
+
{
|
|
512
|
+
const postflightStop = await restorePreflightStashOrStop(ic, preflightTransition, s.currentMilestoneId);
|
|
513
|
+
if (postflightStop)
|
|
514
|
+
return postflightStop;
|
|
502
515
|
}
|
|
503
516
|
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
504
517
|
deps.invalidateAllCaches();
|
|
@@ -573,8 +586,10 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
573
586
|
return { action: "break", reason: "merge-failed" };
|
|
574
587
|
}
|
|
575
588
|
// #2909: postflight — restore stashed changes after successful merge
|
|
576
|
-
|
|
577
|
-
|
|
589
|
+
{
|
|
590
|
+
const postflightStop = await restorePreflightStashOrStop(ic, preflightAllComplete, s.currentMilestoneId);
|
|
591
|
+
if (postflightStop)
|
|
592
|
+
return postflightStop;
|
|
578
593
|
}
|
|
579
594
|
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
580
595
|
}
|
|
@@ -659,8 +674,10 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
659
674
|
return { action: "break", reason: "merge-failed" };
|
|
660
675
|
}
|
|
661
676
|
// #2909: postflight — restore stashed changes after successful merge
|
|
662
|
-
|
|
663
|
-
|
|
677
|
+
{
|
|
678
|
+
const postflightStop = await restorePreflightStashOrStop(ic, preflightComplete, s.currentMilestoneId);
|
|
679
|
+
if (postflightStop)
|
|
680
|
+
return postflightStop;
|
|
664
681
|
}
|
|
665
682
|
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
666
683
|
}
|
|
@@ -795,11 +812,18 @@ export async function runDispatch(ic, preData, loopState) {
|
|
|
795
812
|
level: 1,
|
|
796
813
|
action: "artifact-found",
|
|
797
814
|
});
|
|
798
|
-
|
|
799
|
-
if (!
|
|
800
|
-
ctx.ui.notify(
|
|
815
|
+
const recoveryDb = refreshRecoveryDbForArtifact(unitType, unitId);
|
|
816
|
+
if (!recoveryDb.ok) {
|
|
817
|
+
ctx.ui.notify(recoveryDb.fatal
|
|
818
|
+
? `${recoveryDb.message} Pausing auto-mode for manual recovery.`
|
|
819
|
+
: `${recoveryDb.message} Keeping stuck state for retry.`, "warning");
|
|
820
|
+
if (recoveryDb.fatal) {
|
|
821
|
+
await deps.pauseAuto(ctx, pi);
|
|
822
|
+
return { action: "break", reason: recoveryDb.reason };
|
|
823
|
+
}
|
|
801
824
|
return { action: "continue" };
|
|
802
825
|
}
|
|
826
|
+
ctx.ui.notify(`Stuck recovery: artifact for ${unitType} ${unitId} found on disk. Invalidating caches.`, "info");
|
|
803
827
|
deps.invalidateAllCaches();
|
|
804
828
|
loopState.recentUnits.length = 0;
|
|
805
829
|
loopState.stuckRecoveryAttempts = 0;
|
|
@@ -818,13 +842,20 @@ export async function runDispatch(ic, preData, loopState) {
|
|
|
818
842
|
level: 2,
|
|
819
843
|
action: "artifact-found",
|
|
820
844
|
});
|
|
821
|
-
|
|
822
|
-
if (
|
|
845
|
+
const recoveryDb = refreshRecoveryDbForArtifact(unitType, unitId);
|
|
846
|
+
if (recoveryDb.ok) {
|
|
847
|
+
ctx.ui.notify(`Stuck recovery: artifact for ${unitType} ${unitId} found on disk after cache invalidation. Continuing.`, "info");
|
|
823
848
|
loopState.recentUnits.length = 0;
|
|
824
849
|
loopState.stuckRecoveryAttempts = 0;
|
|
825
850
|
return { action: "continue" };
|
|
826
851
|
}
|
|
827
|
-
ctx.ui.notify(
|
|
852
|
+
ctx.ui.notify(recoveryDb.fatal
|
|
853
|
+
? `${recoveryDb.message} Pausing auto-mode for manual recovery.`
|
|
854
|
+
: `${recoveryDb.message} Stopping for manual recovery.`, "warning");
|
|
855
|
+
if (recoveryDb.fatal) {
|
|
856
|
+
await deps.pauseAuto(ctx, pi);
|
|
857
|
+
return { action: "break", reason: recoveryDb.reason };
|
|
858
|
+
}
|
|
828
859
|
}
|
|
829
860
|
debugLog("autoLoop", {
|
|
830
861
|
phase: "stuck-detected",
|
|
@@ -1672,6 +1703,13 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
|
|
|
1672
1703
|
}
|
|
1673
1704
|
// Both pre and post verification completed without timeout — reset counter
|
|
1674
1705
|
loopState.consecutiveFinalizeTimeouts = 0;
|
|
1706
|
+
if (preUnitSnapshot) {
|
|
1707
|
+
writeUnitRuntimeRecord(s.basePath, preUnitSnapshot.type, preUnitSnapshot.id, preUnitSnapshot.startedAt, {
|
|
1708
|
+
phase: "finalized",
|
|
1709
|
+
lastProgressAt: Date.now(),
|
|
1710
|
+
lastProgressKind: "finalize-success",
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1675
1713
|
s.currentUnit = null;
|
|
1676
1714
|
clearCurrentPhase();
|
|
1677
1715
|
// Surface accumulated workflow-logger issues for this unit to the user.
|
|
@@ -17,6 +17,7 @@ import { bumpTurnGeneration } from "./turn-epoch.js";
|
|
|
17
17
|
// scoped pendingResolve + pendingAgentEndQueue pattern.
|
|
18
18
|
let _currentResolve = null;
|
|
19
19
|
let _sessionSwitchInFlight = false;
|
|
20
|
+
let _pendingSwitchCancellation = null;
|
|
20
21
|
// ─── Setters (needed for cross-module mutation) ─────────────────────────────
|
|
21
22
|
export function _setCurrentResolve(fn) {
|
|
22
23
|
_currentResolve = fn;
|
|
@@ -27,6 +28,11 @@ export function _setSessionSwitchInFlight(v) {
|
|
|
27
28
|
export function _clearCurrentResolve() {
|
|
28
29
|
_currentResolve = null;
|
|
29
30
|
}
|
|
31
|
+
export function _consumePendingSwitchCancellation() {
|
|
32
|
+
const pending = _pendingSwitchCancellation;
|
|
33
|
+
_pendingSwitchCancellation = null;
|
|
34
|
+
return pending;
|
|
35
|
+
}
|
|
30
36
|
// ─── resolveAgentEnd ─────────────────────────────────────────────────────────
|
|
31
37
|
/**
|
|
32
38
|
* Called from the agent_end event handler in index.ts to resolve the
|
|
@@ -90,8 +96,18 @@ export function resolveAgentEndCancelled(errorContext) {
|
|
|
90
96
|
debugLog("resolveAgentEndCancelled", { status: "resolving-cancelled" });
|
|
91
97
|
const r = _currentResolve;
|
|
92
98
|
_currentResolve = null;
|
|
99
|
+
_pendingSwitchCancellation = null;
|
|
93
100
|
r({ status: "cancelled", ...(errorContext ? { errorContext } : {}) });
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
if (_sessionSwitchInFlight) {
|
|
104
|
+
bumpTurnGeneration(`cancelled-during-switch:${errorContext?.category ?? "unknown"}`);
|
|
105
|
+
_pendingSwitchCancellation = errorContext ? { errorContext } : {};
|
|
106
|
+
debugLog("resolveAgentEndCancelled", { status: "queued-during-switch" });
|
|
107
|
+
return false;
|
|
94
108
|
}
|
|
109
|
+
debugLog("resolveAgentEndCancelled", { status: "no-pending-resolve" });
|
|
110
|
+
return false;
|
|
95
111
|
}
|
|
96
112
|
// ─── resetPendingResolve (test helper) ───────────────────────────────────────
|
|
97
113
|
/**
|
|
@@ -101,6 +117,7 @@ export function resolveAgentEndCancelled(errorContext) {
|
|
|
101
117
|
export function _resetPendingResolve() {
|
|
102
118
|
_currentResolve = null;
|
|
103
119
|
_sessionSwitchInFlight = false;
|
|
120
|
+
_pendingSwitchCancellation = null;
|
|
104
121
|
}
|
|
105
122
|
export function _hasPendingResolveForTest() {
|
|
106
123
|
return _currentResolve !== null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// GSD-2 + src/resources/extensions/gsd/auto/run-unit.ts - Runs one GSD auto-mode unit from session creation through agent completion.
|
|
2
2
|
import { NEW_SESSION_TIMEOUT_MS } from "./session.js";
|
|
3
|
-
import { _clearCurrentResolve, _setCurrentResolve, _setSessionSwitchInFlight } from "./resolve.js";
|
|
3
|
+
import { _clearCurrentResolve, _consumePendingSwitchCancellation, _setCurrentResolve, _setSessionSwitchInFlight, } from "./resolve.js";
|
|
4
4
|
import { getCurrentTurnGeneration, runWithTurnGeneration, } from "./turn-epoch.js";
|
|
5
5
|
import { debugLog } from "../debug-logger.js";
|
|
6
6
|
import { logWarning } from "../workflow-logger.js";
|
|
@@ -55,7 +55,10 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
55
55
|
const sessionAbortController = new AbortController();
|
|
56
56
|
_setSessionSwitchInFlight(true);
|
|
57
57
|
try {
|
|
58
|
-
const sessionPromise = s.cmdCtx.newSession({
|
|
58
|
+
const sessionPromise = s.cmdCtx.newSession({
|
|
59
|
+
abortSignal: sessionAbortController.signal,
|
|
60
|
+
cwd: s.basePath,
|
|
61
|
+
}).finally(() => {
|
|
59
62
|
if (sessionSwitchGeneration === mySessionSwitchGeneration) {
|
|
60
63
|
_setSessionSwitchInFlight(false);
|
|
61
64
|
}
|
|
@@ -71,6 +74,7 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
71
74
|
catch (sessionErr) {
|
|
72
75
|
if (sessionTimeoutHandle)
|
|
73
76
|
clearTimeout(sessionTimeoutHandle);
|
|
77
|
+
_consumePendingSwitchCancellation();
|
|
74
78
|
const msg = sessionErr instanceof Error ? sessionErr.message : String(sessionErr);
|
|
75
79
|
debugLog("runUnit", {
|
|
76
80
|
phase: "session-error",
|
|
@@ -83,15 +87,18 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
83
87
|
if (sessionTimeoutHandle)
|
|
84
88
|
clearTimeout(sessionTimeoutHandle);
|
|
85
89
|
if (sessionResult.cancelled) {
|
|
90
|
+
_consumePendingSwitchCancellation();
|
|
86
91
|
debugLog("runUnit-session-timeout", { unitType, unitId });
|
|
87
92
|
return { status: "cancelled", errorContext: { message: "Session creation timed out", category: "timeout", isTransient: true } };
|
|
88
93
|
}
|
|
89
94
|
if (!s.active) {
|
|
95
|
+
_consumePendingSwitchCancellation();
|
|
90
96
|
return { status: "cancelled" };
|
|
91
97
|
}
|
|
92
98
|
if (s.currentUnitModel && typeof pi.setModel === "function") {
|
|
93
99
|
const restored = await pi.setModel(s.currentUnitModel, { persist: false });
|
|
94
100
|
if (!restored) {
|
|
101
|
+
_consumePendingSwitchCancellation();
|
|
95
102
|
const message = `Failed to restore configured model ${s.currentUnitModel.provider}/${s.currentUnitModel.id} after session creation`;
|
|
96
103
|
ctx.ui.notify(`${message}. Cancelling unit before dispatch.`, "warning");
|
|
97
104
|
return {
|
|
@@ -111,6 +118,14 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
111
118
|
const unitPromise = new Promise((resolve) => {
|
|
112
119
|
_setCurrentResolve(resolve);
|
|
113
120
|
});
|
|
121
|
+
const pendingSwitchCancellation = _consumePendingSwitchCancellation();
|
|
122
|
+
if (pendingSwitchCancellation) {
|
|
123
|
+
_clearCurrentResolve();
|
|
124
|
+
return {
|
|
125
|
+
status: "cancelled",
|
|
126
|
+
...(pendingSwitchCancellation.errorContext ? { errorContext: pendingSwitchCancellation.errorContext } : {}),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
114
129
|
// ── Provider request-readiness pre-check (#4555) ──
|
|
115
130
|
// Verify the provider can accept requests before dispatching. If the token
|
|
116
131
|
// has expired since bootstrap, return cancelled immediately so the unit is
|
|
@@ -246,7 +246,7 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
|
246
246
|
ctx.ui.notify(`${msg}. Cancelling dispatch to avoid running in the wrong directory.`, "error");
|
|
247
247
|
return;
|
|
248
248
|
}
|
|
249
|
-
const result = await ctx.newSession();
|
|
249
|
+
const result = await ctx.newSession({ cwd: dispatchBase });
|
|
250
250
|
if (result.cancelled) {
|
|
251
251
|
ctx.ui.notify("Session creation cancelled.", "warning");
|
|
252
252
|
return;
|
|
@@ -21,6 +21,7 @@ import { assertGateCoverage, getGatesForTurn, } from "./gate-registry.js";
|
|
|
21
21
|
import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
|
|
22
22
|
import { readPhaseAnchor, formatAnchorForPrompt } from "./phase-anchor.js";
|
|
23
23
|
import { composeContextModeInstructions, composeInlinedContext } from "./unit-context-composer.js";
|
|
24
|
+
import { readCompactionSnapshot } from "./compaction-snapshot.js";
|
|
24
25
|
import { logWarning } from "./workflow-logger.js";
|
|
25
26
|
import { inlineGraphSubgraph } from "./graph-context.js";
|
|
26
27
|
import { buildExtractionStepsBlock } from "./commands-extract-learnings.js";
|
|
@@ -178,8 +179,19 @@ function renderContextModeForPrompt(unitType, base, renderMode = "standalone") {
|
|
|
178
179
|
renderMode,
|
|
179
180
|
});
|
|
180
181
|
}
|
|
181
|
-
function
|
|
182
|
+
function renderContextModeBlockForPrompt(unitType, base, renderMode = "standalone") {
|
|
182
183
|
const contextMode = renderContextModeForPrompt(unitType, base, renderMode);
|
|
184
|
+
if (!contextMode)
|
|
185
|
+
return "";
|
|
186
|
+
if (renderMode === "nested")
|
|
187
|
+
return contextMode;
|
|
188
|
+
const snapshot = readCompactionSnapshot(base);
|
|
189
|
+
if (!snapshot?.trim())
|
|
190
|
+
return contextMode;
|
|
191
|
+
return `${contextMode}\n\n## Context Snapshot\nSource: \`.gsd/last-snapshot.md\`\n\n${snapshot.trimEnd()}`;
|
|
192
|
+
}
|
|
193
|
+
function prependContextModeToBlock(unitType, base, block, renderMode = "standalone") {
|
|
194
|
+
const contextMode = renderContextModeBlockForPrompt(unitType, base, renderMode);
|
|
183
195
|
if (!contextMode)
|
|
184
196
|
return block;
|
|
185
197
|
if (!block.trim())
|
|
@@ -30,7 +30,49 @@ import { getProjectResearchStatus } from "./project-research-policy.js";
|
|
|
30
30
|
// Re-export so existing consumers of auto-recovery.ts keep working.
|
|
31
31
|
export { resolveExpectedArtifactPath, diagnoseExpectedArtifact };
|
|
32
32
|
export { classifyMilestoneSummaryContent, } from "./milestone-summary-classifier.js";
|
|
33
|
-
|
|
33
|
+
export function refreshRecoveryDbForArtifact(unitType, unitId) {
|
|
34
|
+
if (unitType !== "plan-slice" && unitType !== "execute-task")
|
|
35
|
+
return { ok: true };
|
|
36
|
+
if (!isDbAvailable())
|
|
37
|
+
return { ok: true };
|
|
38
|
+
if (!refreshOpenDatabaseFromDisk()) {
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
fatal: unitType === "execute-task",
|
|
42
|
+
reason: `${unitType}-db-refresh-failed`,
|
|
43
|
+
message: `Stuck recovery found ${unitType} ${unitId} artifacts, but the DB refresh failed.`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (unitType !== "execute-task")
|
|
47
|
+
return { ok: true };
|
|
48
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
49
|
+
if (!mid || !sid || !tid) {
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
fatal: true,
|
|
53
|
+
reason: "execute-task-invalid-unit-id",
|
|
54
|
+
message: `Stuck recovery found execute-task ${unitId} artifacts, but the unit id could not be parsed for DB verification.`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const task = getTask(mid, sid, tid);
|
|
58
|
+
if (!task) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
fatal: true,
|
|
62
|
+
reason: "execute-task-artifact-db-missing",
|
|
63
|
+
message: `Stuck recovery found execute-task ${unitId} artifacts, but no matching DB task row exists after refresh.`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (!isClosedStatus(task.status)) {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
fatal: true,
|
|
70
|
+
reason: "execute-task-artifact-db-mismatch",
|
|
71
|
+
message: `Stuck recovery found execute-task ${unitId} artifacts, but the DB task status is still '${task.status}' after refresh.`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return { ok: true };
|
|
75
|
+
}
|
|
34
76
|
function hasCapturedWorkflowPrefs(base) {
|
|
35
77
|
const prefsPath = resolveExpectedArtifactPath("workflow-preferences", "WORKFLOW-PREFS", base);
|
|
36
78
|
if (!prefsPath || !existsSync(prefsPath))
|
|
@@ -24,7 +24,7 @@ let _currentSigtermHandler = null;
|
|
|
24
24
|
*
|
|
25
25
|
* Returns the new handler so the caller can store and deregister it later.
|
|
26
26
|
*/
|
|
27
|
-
export function registerSigtermHandler(currentBasePath, previousHandler) {
|
|
27
|
+
export function registerSigtermHandler(currentBasePath, previousHandler, onSignalCleanup) {
|
|
28
28
|
// Remove the explicitly-passed previous handler
|
|
29
29
|
if (previousHandler) {
|
|
30
30
|
for (const sig of CLEANUP_SIGNALS)
|
|
@@ -37,6 +37,13 @@ export function registerSigtermHandler(currentBasePath, previousHandler) {
|
|
|
37
37
|
process.off(sig, _currentSigtermHandler);
|
|
38
38
|
}
|
|
39
39
|
const handler = () => {
|
|
40
|
+
try {
|
|
41
|
+
onSignalCleanup?.();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
void 0;
|
|
45
|
+
// Signal cleanup is best-effort; lock cleanup and process exit still run.
|
|
46
|
+
}
|
|
40
47
|
clearLock(currentBasePath);
|
|
41
48
|
releaseSessionLock(currentBasePath);
|
|
42
49
|
process.exit(0);
|
|
@@ -35,13 +35,13 @@ export async function recoverTimedOutUnit(ctx, pi, unitType, unitId, reason, rct
|
|
|
35
35
|
writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
|
|
36
36
|
recovery: status,
|
|
37
37
|
});
|
|
38
|
-
const durableComplete = status.summaryExists && status.taskChecked && status.nextActionAdvanced;
|
|
38
|
+
const durableComplete = status.dbComplete || (status.summaryExists && status.taskChecked && status.nextActionAdvanced);
|
|
39
39
|
if (durableComplete) {
|
|
40
40
|
writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
|
|
41
41
|
phase: "finalized",
|
|
42
42
|
recovery: status,
|
|
43
43
|
});
|
|
44
|
-
ctx.ui.notify(`${reason === "idle" ? "Idle" : "Timeout"} recovery: ${unitType} ${unitId} already completed
|
|
44
|
+
ctx.ui.notify(`${reason === "idle" ? "Idle" : "Timeout"} recovery: ${unitType} ${unitId} already completed. Continuing auto-mode. (attempt ${attemptNumber})`, "info");
|
|
45
45
|
unitRecoveryCount.delete(recoveryKey);
|
|
46
46
|
bumpAndResolveSynthetic(`timeout-recovery:${reason}:${unitType}/${unitId}`);
|
|
47
47
|
return "recovered";
|
|
@@ -20,7 +20,7 @@ import { gsdRoot, resolveMilestoneFile, resolveMilestonePath, resolveDir, milest
|
|
|
20
20
|
import { invalidateAllCaches } from "./cache.js";
|
|
21
21
|
import { clearActivityLogState } from "./activity-log.js";
|
|
22
22
|
import { synthesizeCrashRecovery, getDeepDiagnostic, readActiveMilestoneId, } from "./session-forensics.js";
|
|
23
|
-
import { writeLock, clearLock, readCrashLock, isLockProcessAlive, formatCrashInfo, emitCrashRecoveredUnitEnd, } from "./crash-recovery.js";
|
|
23
|
+
import { writeLock, clearLock, readCrashLock, isLockProcessAlive, formatCrashInfo, emitCrashRecoveredUnitEnd, emitOpenUnitEndForUnit, } from "./crash-recovery.js";
|
|
24
24
|
import { acquireSessionLock, getSessionLockStatus, releaseSessionLock, updateSessionLock, } from "./session-lock.js";
|
|
25
25
|
import { resolveAutoSupervisorConfig, loadEffectiveGSDPreferences, getIsolationMode, } from "./preferences.js";
|
|
26
26
|
import { sendDesktopNotification } from "./notifications.js";
|
|
@@ -61,6 +61,8 @@ import { isClosedStatus } from "./status-guards.js";
|
|
|
61
61
|
import { updateProgressWidget as _updateProgressWidget, updateSliceProgressCache, clearSliceProgressCache, } from "./auto-dashboard.js";
|
|
62
62
|
import { registerSigtermHandler as _registerSigtermHandler, deregisterSigtermHandler as _deregisterSigtermHandler, } from "./auto-supervisor.js";
|
|
63
63
|
import { isDbAvailable, getMilestone } from "./gsd-db.js";
|
|
64
|
+
import { markLatestActiveForWorkerCanceled } from "./db/unit-dispatches.js";
|
|
65
|
+
import { writeUnitRuntimeRecord } from "./unit-runtime.js";
|
|
64
66
|
import { countPendingCaptures } from "./captures.js";
|
|
65
67
|
import { CMUX_CHANNELS } from "../shared/cmux-events.js";
|
|
66
68
|
import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
|
|
@@ -85,6 +87,16 @@ import { validateDirectory } from "./validate-directory.js";
|
|
|
85
87
|
import { createAutoOrchestrator } from "./auto/orchestrator.js";
|
|
86
88
|
import { WorktreeResolver, } from "./worktree-resolver.js";
|
|
87
89
|
import { reorderForCaching } from "./prompt-ordering.js";
|
|
90
|
+
import { initTokenCounter } from "./token-counter.js";
|
|
91
|
+
// Warm the tiktoken encoder at extension startup so context-budget computations
|
|
92
|
+
// can use accurate token counts via countTokensSync without paying the load
|
|
93
|
+
// cost mid-prompt-build. Fire-and-forget — failure falls back to the
|
|
94
|
+
// provider-aware char-ratio estimator already used by getCharsPerToken().
|
|
95
|
+
// Catch rejections explicitly: an unhandled rejection at module-import time
|
|
96
|
+
// can destabilize startup before the engine logger is configured.
|
|
97
|
+
void initTokenCounter().catch((err) => {
|
|
98
|
+
logWarning("engine", `token counter warm-up failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
99
|
+
});
|
|
88
100
|
export { STUB_RECOVERY_THRESHOLD, NEW_SESSION_TIMEOUT_MS, } from "./auto/session.js";
|
|
89
101
|
import { autoSession as s } from "./auto-runtime-state.js";
|
|
90
102
|
import { gsdHome } from "./gsd-home.js";
|
|
@@ -112,11 +124,12 @@ const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
|
|
|
112
124
|
* the DB is unavailable (e.g. fresh project before init) we skip registration
|
|
113
125
|
* silently rather than blocking session start.
|
|
114
126
|
*/
|
|
115
|
-
function registerAutoWorkerForSession(session) {
|
|
127
|
+
function registerAutoWorkerForSession(session, projectRootOverride) {
|
|
116
128
|
if (session.workerId)
|
|
117
129
|
return; // already registered (e.g. resume re-runs)
|
|
118
130
|
try {
|
|
119
|
-
const projectRootRealpath = normalizeRealPath(
|
|
131
|
+
const projectRootRealpath = normalizeRealPath(projectRootOverride
|
|
132
|
+
?? session.scope?.workspace.projectRoot
|
|
120
133
|
?? (session.originalBasePath || session.basePath));
|
|
121
134
|
session.workerId = registerAutoWorker({ projectRootRealpath });
|
|
122
135
|
}
|
|
@@ -272,9 +285,50 @@ export function shouldUseWorktreeIsolation(basePath) {
|
|
|
272
285
|
*/
|
|
273
286
|
// Re-export budget utilities for external consumers
|
|
274
287
|
export { getBudgetAlertLevel, getNewBudgetAlertLevel, getBudgetEnforcementAction, } from "./auto-budget.js";
|
|
288
|
+
function closeOutSignalInterruptedUnit(currentBasePath) {
|
|
289
|
+
const currentUnit = s.currentUnit;
|
|
290
|
+
if (!currentUnit)
|
|
291
|
+
return;
|
|
292
|
+
const reason = "Auto-mode process received a termination signal";
|
|
293
|
+
const errorContext = {
|
|
294
|
+
message: reason,
|
|
295
|
+
category: "aborted",
|
|
296
|
+
isTransient: false,
|
|
297
|
+
};
|
|
298
|
+
const basePath = s.basePath || currentBasePath;
|
|
299
|
+
try {
|
|
300
|
+
emitOpenUnitEndForUnit(basePath, currentUnit.type, currentUnit.id, "cancelled", errorContext);
|
|
301
|
+
}
|
|
302
|
+
catch (err) {
|
|
303
|
+
logWarning("engine", `signal unit-end cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
writeUnitRuntimeRecord(basePath, currentUnit.type, currentUnit.id, currentUnit.startedAt, {
|
|
307
|
+
phase: "crashed",
|
|
308
|
+
lastProgressAt: Date.now(),
|
|
309
|
+
lastProgressKind: "signal",
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
catch (err) {
|
|
313
|
+
logWarning("engine", `signal runtime cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
if (s.workerId)
|
|
317
|
+
markLatestActiveForWorkerCanceled(s.workerId, "signal-exit");
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
logWarning("engine", `signal dispatch cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
resolveAgentEndCancelled(errorContext);
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
logWarning("engine", `signal resolve cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
275
329
|
/** Wrapper: register SIGTERM handler and store reference. */
|
|
276
330
|
function registerSigtermHandler(currentBasePath) {
|
|
277
|
-
s.sigtermHandler = _registerSigtermHandler(currentBasePath, s.sigtermHandler);
|
|
331
|
+
s.sigtermHandler = _registerSigtermHandler(currentBasePath, s.sigtermHandler, () => closeOutSignalInterruptedUnit(currentBasePath));
|
|
278
332
|
}
|
|
279
333
|
/** Wrapper: deregister SIGTERM handler and clear reference. */
|
|
280
334
|
function deregisterSigtermHandler() {
|
|
@@ -666,6 +720,8 @@ export async function stopAuto(ctx, pi, reason) {
|
|
|
666
720
|
if (s.workerId) {
|
|
667
721
|
markWorkerStopping(s.workerId);
|
|
668
722
|
}
|
|
723
|
+
s.workerId = null;
|
|
724
|
+
s.milestoneLeaseToken = null;
|
|
669
725
|
}
|
|
670
726
|
catch (e) {
|
|
671
727
|
debugLog("stop-cleanup-coordination", { error: e instanceof Error ? e.message : String(e) });
|
|
@@ -798,6 +854,21 @@ export async function stopAuto(ctx, pi, reason) {
|
|
|
798
854
|
catch (e) {
|
|
799
855
|
debugLog("stop-cleanup-basepath", { error: e instanceof Error ? e.message : String(e) });
|
|
800
856
|
}
|
|
857
|
+
// Re-root the active command session/tool runtime after worktree teardown.
|
|
858
|
+
// mergeAndExit restores process.cwd(), but AgentSession has already captured
|
|
859
|
+
// its own cwd for tools and system prompt; refresh it before returning to the
|
|
860
|
+
// user so follow-up commands do not target a removed milestone worktree.
|
|
861
|
+
if (s.originalBasePath && ctx && s.cmdCtx) {
|
|
862
|
+
try {
|
|
863
|
+
const result = await s.cmdCtx.newSession({ cwd: s.basePath });
|
|
864
|
+
if (result.cancelled) {
|
|
865
|
+
logWarning("engine", "post-stop session re-root was cancelled", { file: "auto.ts", basePath: s.basePath });
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
catch (err) {
|
|
869
|
+
logWarning("engine", `post-stop session re-root failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts", basePath: s.basePath });
|
|
870
|
+
}
|
|
871
|
+
}
|
|
801
872
|
// ── Step 8: Ledger notification ──
|
|
802
873
|
try {
|
|
803
874
|
const ledger = getLedger();
|
|
@@ -1566,6 +1637,10 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1566
1637
|
: new URL("../../../resource-loader.js", import.meta.url).href;
|
|
1567
1638
|
const { initResources } = await import(resourceLoaderPath);
|
|
1568
1639
|
initResources(agentDir);
|
|
1640
|
+
// initResources() uses synchronous fs APIs, so the prompt-template cache
|
|
1641
|
+
// can be primed immediately — no need for the legacy 1s setTimeout deferral.
|
|
1642
|
+
const { primeCache } = await import("./prompt-loader.js");
|
|
1643
|
+
primeCache();
|
|
1569
1644
|
// Open the project DB before rebuild/derive so resume uses DB-backed
|
|
1570
1645
|
// state instead of falling back to stale markdown parsing (#2940).
|
|
1571
1646
|
await openProjectDbIfPresent(s.basePath);
|
|
@@ -1631,6 +1706,10 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1631
1706
|
lockBase,
|
|
1632
1707
|
buildResolver,
|
|
1633
1708
|
};
|
|
1709
|
+
// Register the worker before bootstrap enters a milestone worktree.
|
|
1710
|
+
// This ensures enterMilestone can claim a lease and seed dispatch claims
|
|
1711
|
+
// for crash-recovery fidelity (#5405).
|
|
1712
|
+
registerAutoWorkerForSession(s, base);
|
|
1634
1713
|
const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps, freshStartAssessment);
|
|
1635
1714
|
if (!ready)
|
|
1636
1715
|
return;
|
|
@@ -1769,7 +1848,7 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
|
|
|
1769
1848
|
}
|
|
1770
1849
|
return false;
|
|
1771
1850
|
}
|
|
1772
|
-
const result = await s.cmdCtx.newSession();
|
|
1851
|
+
const result = await s.cmdCtx.newSession({ cwd: s.basePath });
|
|
1773
1852
|
if (result.cancelled) {
|
|
1774
1853
|
await stopAuto(ctx, pi);
|
|
1775
1854
|
return false;
|
|
@@ -118,9 +118,28 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
118
118
|
return;
|
|
119
119
|
if (!isAutoActive())
|
|
120
120
|
return;
|
|
121
|
-
if (isSessionSwitchInFlight())
|
|
122
|
-
return;
|
|
123
121
|
const lastMsg = event.messages[event.messages.length - 1];
|
|
122
|
+
if (isSessionSwitchInFlight()) {
|
|
123
|
+
if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "error") {
|
|
124
|
+
const rawErrorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
|
|
125
|
+
if (isUserInitiatedAbortMessage(rawErrorMsg)) {
|
|
126
|
+
resolveAgentEndCancelled({
|
|
127
|
+
message: rawErrorMsg,
|
|
128
|
+
category: "aborted",
|
|
129
|
+
isTransient: false,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "aborted") {
|
|
134
|
+
const content = "content" in lastMsg ? lastMsg.content : undefined;
|
|
135
|
+
const hasEmptyContent = Array.isArray(content) && content.length === 0;
|
|
136
|
+
const hasErrorMessage = "errorMessage" in lastMsg && !!lastMsg.errorMessage;
|
|
137
|
+
if (!hasEmptyContent || hasErrorMessage) {
|
|
138
|
+
resolveAgentEndCancelled(_buildAbortedPauseContext(lastMsg));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
124
143
|
if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "aborted") {
|
|
125
144
|
// Empty content with aborted stopReason is a non-fatal agent stop (the LLM
|
|
126
145
|
// chose to end without producing output). Only pause on genuine fatal aborts
|