gsd-pi 2.80.0-dev.e146beb20 → 2.80.0-dev.e51d2c88c
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 +29 -15
- package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +13 -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 +66 -4
- 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 +21 -0
- 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/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/prompt-loader.js +28 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +14 -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/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +18 -18
- 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 +18 -18
- 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.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +8 -0
- 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 +1 -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 +7 -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.ts +8 -0
- 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 +3 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -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/phases.ts +35 -20
- package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +18 -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 +78 -3
- 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 +22 -0
- 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/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/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +14 -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 +71 -0
- 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/compaction-snapshot.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -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/journal-integration.test.ts +234 -0
- 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/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/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/dist/web/standalone/.next/static/{y73quA-XdLo9n41nxphjW → 8F5YpnZNBaooIWGF4GBV3}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{y73quA-XdLo9n41nxphjW → 8F5YpnZNBaooIWGF4GBV3}/_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
|
+
4a20b588f749081c
|
|
@@ -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;
|
|
@@ -795,11 +788,18 @@ export async function runDispatch(ic, preData, loopState) {
|
|
|
795
788
|
level: 1,
|
|
796
789
|
action: "artifact-found",
|
|
797
790
|
});
|
|
798
|
-
|
|
799
|
-
if (!
|
|
800
|
-
ctx.ui.notify(
|
|
791
|
+
const recoveryDb = refreshRecoveryDbForArtifact(unitType, unitId);
|
|
792
|
+
if (!recoveryDb.ok) {
|
|
793
|
+
ctx.ui.notify(recoveryDb.fatal
|
|
794
|
+
? `${recoveryDb.message} Pausing auto-mode for manual recovery.`
|
|
795
|
+
: `${recoveryDb.message} Keeping stuck state for retry.`, "warning");
|
|
796
|
+
if (recoveryDb.fatal) {
|
|
797
|
+
await deps.pauseAuto(ctx, pi);
|
|
798
|
+
return { action: "break", reason: recoveryDb.reason };
|
|
799
|
+
}
|
|
801
800
|
return { action: "continue" };
|
|
802
801
|
}
|
|
802
|
+
ctx.ui.notify(`Stuck recovery: artifact for ${unitType} ${unitId} found on disk. Invalidating caches.`, "info");
|
|
803
803
|
deps.invalidateAllCaches();
|
|
804
804
|
loopState.recentUnits.length = 0;
|
|
805
805
|
loopState.stuckRecoveryAttempts = 0;
|
|
@@ -818,13 +818,20 @@ export async function runDispatch(ic, preData, loopState) {
|
|
|
818
818
|
level: 2,
|
|
819
819
|
action: "artifact-found",
|
|
820
820
|
});
|
|
821
|
-
|
|
822
|
-
if (
|
|
821
|
+
const recoveryDb = refreshRecoveryDbForArtifact(unitType, unitId);
|
|
822
|
+
if (recoveryDb.ok) {
|
|
823
|
+
ctx.ui.notify(`Stuck recovery: artifact for ${unitType} ${unitId} found on disk after cache invalidation. Continuing.`, "info");
|
|
823
824
|
loopState.recentUnits.length = 0;
|
|
824
825
|
loopState.stuckRecoveryAttempts = 0;
|
|
825
826
|
return { action: "continue" };
|
|
826
827
|
}
|
|
827
|
-
ctx.ui.notify(
|
|
828
|
+
ctx.ui.notify(recoveryDb.fatal
|
|
829
|
+
? `${recoveryDb.message} Pausing auto-mode for manual recovery.`
|
|
830
|
+
: `${recoveryDb.message} Stopping for manual recovery.`, "warning");
|
|
831
|
+
if (recoveryDb.fatal) {
|
|
832
|
+
await deps.pauseAuto(ctx, pi);
|
|
833
|
+
return { action: "break", reason: recoveryDb.reason };
|
|
834
|
+
}
|
|
828
835
|
}
|
|
829
836
|
debugLog("autoLoop", {
|
|
830
837
|
phase: "stuck-detected",
|
|
@@ -1672,6 +1679,13 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
|
|
|
1672
1679
|
}
|
|
1673
1680
|
// Both pre and post verification completed without timeout — reset counter
|
|
1674
1681
|
loopState.consecutiveFinalizeTimeouts = 0;
|
|
1682
|
+
if (preUnitSnapshot) {
|
|
1683
|
+
writeUnitRuntimeRecord(s.basePath, preUnitSnapshot.type, preUnitSnapshot.id, preUnitSnapshot.startedAt, {
|
|
1684
|
+
phase: "finalized",
|
|
1685
|
+
lastProgressAt: Date.now(),
|
|
1686
|
+
lastProgressKind: "finalize-success",
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1675
1689
|
s.currentUnit = null;
|
|
1676
1690
|
clearCurrentPhase();
|
|
1677
1691
|
// 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";
|
|
@@ -71,6 +71,7 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
71
71
|
catch (sessionErr) {
|
|
72
72
|
if (sessionTimeoutHandle)
|
|
73
73
|
clearTimeout(sessionTimeoutHandle);
|
|
74
|
+
_consumePendingSwitchCancellation();
|
|
74
75
|
const msg = sessionErr instanceof Error ? sessionErr.message : String(sessionErr);
|
|
75
76
|
debugLog("runUnit", {
|
|
76
77
|
phase: "session-error",
|
|
@@ -83,15 +84,18 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
83
84
|
if (sessionTimeoutHandle)
|
|
84
85
|
clearTimeout(sessionTimeoutHandle);
|
|
85
86
|
if (sessionResult.cancelled) {
|
|
87
|
+
_consumePendingSwitchCancellation();
|
|
86
88
|
debugLog("runUnit-session-timeout", { unitType, unitId });
|
|
87
89
|
return { status: "cancelled", errorContext: { message: "Session creation timed out", category: "timeout", isTransient: true } };
|
|
88
90
|
}
|
|
89
91
|
if (!s.active) {
|
|
92
|
+
_consumePendingSwitchCancellation();
|
|
90
93
|
return { status: "cancelled" };
|
|
91
94
|
}
|
|
92
95
|
if (s.currentUnitModel && typeof pi.setModel === "function") {
|
|
93
96
|
const restored = await pi.setModel(s.currentUnitModel, { persist: false });
|
|
94
97
|
if (!restored) {
|
|
98
|
+
_consumePendingSwitchCancellation();
|
|
95
99
|
const message = `Failed to restore configured model ${s.currentUnitModel.provider}/${s.currentUnitModel.id} after session creation`;
|
|
96
100
|
ctx.ui.notify(`${message}. Cancelling unit before dispatch.`, "warning");
|
|
97
101
|
return {
|
|
@@ -111,6 +115,14 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
111
115
|
const unitPromise = new Promise((resolve) => {
|
|
112
116
|
_setCurrentResolve(resolve);
|
|
113
117
|
});
|
|
118
|
+
const pendingSwitchCancellation = _consumePendingSwitchCancellation();
|
|
119
|
+
if (pendingSwitchCancellation) {
|
|
120
|
+
_clearCurrentResolve();
|
|
121
|
+
return {
|
|
122
|
+
status: "cancelled",
|
|
123
|
+
...(pendingSwitchCancellation.errorContext ? { errorContext: pendingSwitchCancellation.errorContext } : {}),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
114
126
|
// ── Provider request-readiness pre-check (#4555) ──
|
|
115
127
|
// Verify the provider can accept requests before dispatching. If the token
|
|
116
128
|
// has expired since bootstrap, return cancelled immediately so the unit is
|
|
@@ -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() {
|
|
@@ -1566,6 +1620,10 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1566
1620
|
: new URL("../../../resource-loader.js", import.meta.url).href;
|
|
1567
1621
|
const { initResources } = await import(resourceLoaderPath);
|
|
1568
1622
|
initResources(agentDir);
|
|
1623
|
+
// initResources() uses synchronous fs APIs, so the prompt-template cache
|
|
1624
|
+
// can be primed immediately — no need for the legacy 1s setTimeout deferral.
|
|
1625
|
+
const { primeCache } = await import("./prompt-loader.js");
|
|
1626
|
+
primeCache();
|
|
1569
1627
|
// Open the project DB before rebuild/derive so resume uses DB-backed
|
|
1570
1628
|
// state instead of falling back to stale markdown parsing (#2940).
|
|
1571
1629
|
await openProjectDbIfPresent(s.basePath);
|
|
@@ -1631,6 +1689,10 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1631
1689
|
lockBase,
|
|
1632
1690
|
buildResolver,
|
|
1633
1691
|
};
|
|
1692
|
+
// Register the worker before bootstrap enters a milestone worktree.
|
|
1693
|
+
// This ensures enterMilestone can claim a lease and seed dispatch claims
|
|
1694
|
+
// for crash-recovery fidelity (#5405).
|
|
1695
|
+
registerAutoWorkerForSession(s, base);
|
|
1634
1696
|
const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps, freshStartAssessment);
|
|
1635
1697
|
if (!ready)
|
|
1636
1698
|
return;
|
|
@@ -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
|
|
@@ -3,21 +3,34 @@
|
|
|
3
3
|
// Exposes the Context Mode runtime tools in-process. Default-on; opt out with
|
|
4
4
|
// `context_mode.enabled: false` in preferences.
|
|
5
5
|
import { Type } from "@sinclair/typebox";
|
|
6
|
+
async function loadContextModePreferences(baseDir) {
|
|
7
|
+
const [{ loadEffectiveGSDPreferences }, { logWarning }] = await Promise.all([
|
|
8
|
+
import("../preferences.js"),
|
|
9
|
+
import("../workflow-logger.js"),
|
|
10
|
+
]);
|
|
11
|
+
try {
|
|
12
|
+
return loadEffectiveGSDPreferences(baseDir)?.preferences ?? null;
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
logWarning("tool", `Context Mode tool could not load preferences: ${err instanceof Error ? err.message : String(err)}`);
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
6
19
|
export function registerExecTools(pi) {
|
|
7
20
|
pi.registerTool({
|
|
8
21
|
name: "gsd_exec",
|
|
9
22
|
label: "Exec (Sandboxed)",
|
|
10
|
-
description: "Run a short script (bash/node/python) in a subprocess.
|
|
23
|
+
description: "Run a short script (bash/node/python) in a subprocess. Capped stdout/stderr and metadata persist to " +
|
|
11
24
|
".gsd/exec/<id>.{stdout,stderr,meta.json}; only a short digest returns in context. Use " +
|
|
12
25
|
"this instead of reading many files or emitting large tool outputs — e.g. have the script " +
|
|
13
26
|
"count/grep/summarize and log the finding. Enabled by default; opt out via " +
|
|
14
27
|
"preferences.context_mode.enabled=false.",
|
|
15
|
-
promptSnippet: "Run a bash/node/python script in a sandbox;
|
|
28
|
+
promptSnippet: "Run a bash/node/python script in a sandbox; capped output is saved to disk and only a digest returns",
|
|
16
29
|
promptGuidelines: [
|
|
17
30
|
"Prefer gsd_exec for analyses that would otherwise read >3 files or produce large tool output.",
|
|
18
31
|
"Write scripts that log the finding (counts, matches, summaries) rather than raw dumps.",
|
|
19
32
|
"The digest is the last ~300 chars of stdout — size your log output accordingly.",
|
|
20
|
-
"Need
|
|
33
|
+
"Need persisted output? Read the stdout_path returned in details (file on local disk).",
|
|
21
34
|
],
|
|
22
35
|
parameters: Type.Object({
|
|
23
36
|
runtime: Type.Union([Type.Literal("bash"), Type.Literal("node"), Type.Literal("python")], { description: "Interpreter: bash (-c), node (-e), or python3 (-c)." }),
|
|
@@ -30,21 +43,11 @@ export function registerExecTools(pi) {
|
|
|
30
43
|
})),
|
|
31
44
|
}),
|
|
32
45
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
import("../preferences.js"),
|
|
36
|
-
import("../workflow-logger.js"),
|
|
37
|
-
]);
|
|
38
|
-
let prefs = null;
|
|
39
|
-
try {
|
|
40
|
-
prefs = loadEffectiveGSDPreferences();
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
logWarning("tool", `gsd_exec could not load preferences: ${err instanceof Error ? err.message : String(err)}`);
|
|
44
|
-
}
|
|
46
|
+
const { executeGsdExec } = await import("../tools/exec-tool.js");
|
|
47
|
+
const baseDir = process.cwd();
|
|
45
48
|
return executeGsdExec(params, {
|
|
46
|
-
baseDir
|
|
47
|
-
preferences:
|
|
49
|
+
baseDir,
|
|
50
|
+
preferences: await loadContextModePreferences(baseDir),
|
|
48
51
|
});
|
|
49
52
|
},
|
|
50
53
|
});
|
|
@@ -56,7 +59,7 @@ export function registerExecTools(pi) {
|
|
|
56
59
|
promptSnippet: "Search prior gsd_exec runs by substring, runtime, or failing-only filter",
|
|
57
60
|
promptGuidelines: [
|
|
58
61
|
"Use this before re-running an expensive analysis — the prior run's stdout file may still answer.",
|
|
59
|
-
"The preview shows the trailing ~300 chars of stdout; read stdout_path for
|
|
62
|
+
"The preview shows the trailing ~300 chars of stdout; read stdout_path for persisted output.",
|
|
60
63
|
],
|
|
61
64
|
parameters: Type.Object({
|
|
62
65
|
query: Type.Optional(Type.String({ description: "Substring matched against id and purpose (case-insensitive)." })),
|
|
@@ -68,8 +71,10 @@ export function registerExecTools(pi) {
|
|
|
68
71
|
}),
|
|
69
72
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
70
73
|
const { executeExecSearch } = await import("../tools/exec-search-tool.js");
|
|
74
|
+
const baseDir = process.cwd();
|
|
71
75
|
return executeExecSearch(params, {
|
|
72
|
-
baseDir
|
|
76
|
+
baseDir,
|
|
77
|
+
preferences: await loadContextModePreferences(baseDir),
|
|
73
78
|
});
|
|
74
79
|
},
|
|
75
80
|
});
|
|
@@ -87,8 +92,10 @@ export function registerExecTools(pi) {
|
|
|
87
92
|
parameters: Type.Object({}),
|
|
88
93
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
89
94
|
const { executeResume } = await import("../tools/resume-tool.js");
|
|
95
|
+
const baseDir = process.cwd();
|
|
90
96
|
return executeResume(params, {
|
|
91
|
-
baseDir
|
|
97
|
+
baseDir,
|
|
98
|
+
preferences: await loadContextModePreferences(baseDir),
|
|
92
99
|
});
|
|
93
100
|
},
|
|
94
101
|
});
|
|
@@ -52,6 +52,25 @@ async function applyDisabledModelProviderPolicy(ctx) {
|
|
|
52
52
|
// Non-fatal: keep default provider visibility if preferences cannot be loaded.
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Bridge `context_management.compaction_threshold_percent` from GSD preferences
|
|
57
|
+
* into the agent's runtime compaction settings (#5475). The preference is
|
|
58
|
+
* validated to (0.5, 0.95) at load time, but defense-in-depth normalization
|
|
59
|
+
* here protects against a stale or hand-edited prefs file. Calling with
|
|
60
|
+
* `undefined` clears any prior override so a removed preference does not leak.
|
|
61
|
+
*/
|
|
62
|
+
async function applyCompactionThresholdOverride(ctx) {
|
|
63
|
+
try {
|
|
64
|
+
const { loadEffectiveGSDPreferences } = await import("../preferences.js");
|
|
65
|
+
const prefs = loadEffectiveGSDPreferences();
|
|
66
|
+
const raw = prefs?.preferences.context_management?.compaction_threshold_percent;
|
|
67
|
+
const value = typeof raw === "number" && Number.isFinite(raw) && raw > 0 && raw < 1 ? raw : undefined;
|
|
68
|
+
ctx.setCompactionThresholdOverride(value);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Non-fatal: leave any existing override in place.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
55
74
|
export function resolveNotificationStoreBasePath(cwd = process.cwd()) {
|
|
56
75
|
return resolveWorktreeProjectRoot(cwd);
|
|
57
76
|
}
|
|
@@ -101,6 +120,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
101
120
|
await resetAskUserQuestionsTurnCache();
|
|
102
121
|
await syncServiceTierStatus(ctx);
|
|
103
122
|
await applyDisabledModelProviderPolicy(ctx);
|
|
123
|
+
await applyCompactionThresholdOverride(ctx);
|
|
104
124
|
// Skip MCP auto-prep when running inside an auto-worktree (see session_switch below).
|
|
105
125
|
const { isInAutoWorktree } = await import("../auto-worktree.js");
|
|
106
126
|
if (!isInAutoWorktree(process.cwd())) {
|
|
@@ -149,6 +169,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
149
169
|
clearDiscussionFlowState(process.cwd());
|
|
150
170
|
await syncServiceTierStatus(ctx);
|
|
151
171
|
await applyDisabledModelProviderPolicy(ctx);
|
|
172
|
+
await applyCompactionThresholdOverride(ctx);
|
|
152
173
|
// Skip MCP auto-prep when running inside an auto-worktree. The worktree
|
|
153
174
|
// already has .mcp.json from createAutoWorktree, and re-running the writer
|
|
154
175
|
// post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
|