gsd-pi 2.80.0-dev.c5c38454b → 2.80.0-dev.cf9433f56
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/GSD-WORKFLOW.md +2 -2
- package/dist/resources/extensions/gsd/auto/loop.js +32 -1
- package/dist/resources/extensions/gsd/auto/phases.js +66 -45
- 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-post-unit.js +10 -10
- package/dist/resources/extensions/gsd/auto-prompts.js +124 -2
- 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 +77 -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 +21 -0
- package/dist/resources/extensions/gsd/clean-root-preflight.js +42 -4
- package/dist/resources/extensions/gsd/commands/dispatcher.js +5 -0
- package/dist/resources/extensions/gsd/context-budget.js +37 -2
- package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
- 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/detection.js +106 -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 +80 -14
- 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/planning-path-scope.js +26 -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 +19 -19
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.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/safety/evidence-collector.js +10 -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/plan-slice.js +9 -0
- package/dist/resources/extensions/gsd/tools/plan-task.js +9 -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 +22 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
- 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 +7 -7
- 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 +7 -7
- 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 +32 -0
- 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/db-snapshot.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.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 +15 -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/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +10 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.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 +38 -0
- 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/db-snapshot.test.ts +32 -0
- package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -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 +24 -7
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +14 -0
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
- 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/GSD-WORKFLOW.md +2 -2
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/loop.ts +50 -8
- package/src/resources/extensions/gsd/auto/phases.ts +77 -48
- 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-post-unit.ts +10 -10
- package/src/resources/extensions/gsd/auto-prompts.ts +133 -2
- 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 +92 -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 +22 -0
- package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
- package/src/resources/extensions/gsd/commands/dispatcher.ts +6 -0
- package/src/resources/extensions/gsd/context-budget.ts +44 -2
- package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
- 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/detection.ts +128 -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 +96 -14
- 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/planning-path-scope.ts +35 -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 +19 -19
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.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/safety/evidence-collector.ts +11 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +72 -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/clean-root-preflight.test.ts +88 -2
- 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/crash-handler-secondary.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/detection.test.ts +140 -0
- 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 +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/plan-slice.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -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/right-sized-workflow-prompts.test.ts +192 -0
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +29 -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/smart-entry-complete.test.ts +38 -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 +101 -2
- 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 +37 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +37 -6
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +9 -2
- 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/plan-slice.ts +13 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +10 -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 +25 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
- package/src/resources/extensions/gsd/worktree-resolver.ts +36 -15
- /package/dist/web/standalone/.next/static/{TCSim36ZpcPu2WgeoC45g → -5nHJWzSdG-WkPMul_khA}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{TCSim36ZpcPu2WgeoC45g → -5nHJWzSdG-WkPMul_khA}/_ssgManifest.js +0 -0
|
@@ -22,6 +22,7 @@ import { bumpTurnGeneration } from "./turn-epoch.js";
|
|
|
22
22
|
|
|
23
23
|
let _currentResolve: ((result: UnitResult) => void) | null = null;
|
|
24
24
|
let _sessionSwitchInFlight = false;
|
|
25
|
+
let _pendingSwitchCancellation: { errorContext?: ErrorContext } | null = null;
|
|
25
26
|
|
|
26
27
|
// ─── Setters (needed for cross-module mutation) ─────────────────────────────
|
|
27
28
|
|
|
@@ -37,6 +38,12 @@ export function _clearCurrentResolve(): void {
|
|
|
37
38
|
_currentResolve = null;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
export function _consumePendingSwitchCancellation(): { errorContext?: ErrorContext } | null {
|
|
42
|
+
const pending = _pendingSwitchCancellation;
|
|
43
|
+
_pendingSwitchCancellation = null;
|
|
44
|
+
return pending;
|
|
45
|
+
}
|
|
46
|
+
|
|
40
47
|
// ─── resolveAgentEnd ─────────────────────────────────────────────────────────
|
|
41
48
|
|
|
42
49
|
/**
|
|
@@ -96,7 +103,7 @@ export function bumpAndResolveSynthetic(reason: string): void {
|
|
|
96
103
|
* blocks to ensure the autoLoop is never stuck awaiting a promise that
|
|
97
104
|
* will never resolve. Safe to call when no resolver is pending (no-op).
|
|
98
105
|
*/
|
|
99
|
-
export function resolveAgentEndCancelled(errorContext?: ErrorContext):
|
|
106
|
+
export function resolveAgentEndCancelled(errorContext?: ErrorContext): boolean {
|
|
100
107
|
if (_currentResolve) {
|
|
101
108
|
// Cancellation supersedes the in-flight turn the same way timeout
|
|
102
109
|
// recovery does — bump the turn epoch so any lingering writes from the
|
|
@@ -107,8 +114,22 @@ export function resolveAgentEndCancelled(errorContext?: ErrorContext): void {
|
|
|
107
114
|
debugLog("resolveAgentEndCancelled", { status: "resolving-cancelled" });
|
|
108
115
|
const r = _currentResolve;
|
|
109
116
|
_currentResolve = null;
|
|
117
|
+
_pendingSwitchCancellation = null;
|
|
110
118
|
r({ status: "cancelled", ...(errorContext ? { errorContext } : {}) });
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (_sessionSwitchInFlight) {
|
|
123
|
+
bumpTurnGeneration(
|
|
124
|
+
`cancelled-during-switch:${errorContext?.category ?? "unknown"}`,
|
|
125
|
+
);
|
|
126
|
+
_pendingSwitchCancellation = errorContext ? { errorContext } : {};
|
|
127
|
+
debugLog("resolveAgentEndCancelled", { status: "queued-during-switch" });
|
|
128
|
+
return false;
|
|
111
129
|
}
|
|
130
|
+
|
|
131
|
+
debugLog("resolveAgentEndCancelled", { status: "no-pending-resolve" });
|
|
132
|
+
return false;
|
|
112
133
|
}
|
|
113
134
|
|
|
114
135
|
// ─── resetPendingResolve (test helper) ───────────────────────────────────────
|
|
@@ -120,6 +141,7 @@ export function resolveAgentEndCancelled(errorContext?: ErrorContext): void {
|
|
|
120
141
|
export function _resetPendingResolve(): void {
|
|
121
142
|
_currentResolve = null;
|
|
122
143
|
_sessionSwitchInFlight = false;
|
|
144
|
+
_pendingSwitchCancellation = null;
|
|
123
145
|
}
|
|
124
146
|
|
|
125
147
|
export function _hasPendingResolveForTest(): boolean {
|
|
@@ -11,7 +11,12 @@ import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
|
|
|
11
11
|
import type { AutoSession } from "./session.js";
|
|
12
12
|
import { NEW_SESSION_TIMEOUT_MS } from "./session.js";
|
|
13
13
|
import type { UnitResult } from "./types.js";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
_clearCurrentResolve,
|
|
16
|
+
_consumePendingSwitchCancellation,
|
|
17
|
+
_setCurrentResolve,
|
|
18
|
+
_setSessionSwitchInFlight,
|
|
19
|
+
} from "./resolve.js";
|
|
15
20
|
import {
|
|
16
21
|
getCurrentTurnGeneration,
|
|
17
22
|
runWithTurnGeneration,
|
|
@@ -97,6 +102,7 @@ export async function runUnit(
|
|
|
97
102
|
sessionResult = await Promise.race([sessionPromise, timeoutPromise]);
|
|
98
103
|
} catch (sessionErr) {
|
|
99
104
|
if (sessionTimeoutHandle) clearTimeout(sessionTimeoutHandle);
|
|
105
|
+
_consumePendingSwitchCancellation();
|
|
100
106
|
const msg =
|
|
101
107
|
sessionErr instanceof Error ? sessionErr.message : String(sessionErr);
|
|
102
108
|
debugLog("runUnit", {
|
|
@@ -110,17 +116,20 @@ export async function runUnit(
|
|
|
110
116
|
if (sessionTimeoutHandle) clearTimeout(sessionTimeoutHandle);
|
|
111
117
|
|
|
112
118
|
if (sessionResult.cancelled) {
|
|
119
|
+
_consumePendingSwitchCancellation();
|
|
113
120
|
debugLog("runUnit-session-timeout", { unitType, unitId });
|
|
114
121
|
return { status: "cancelled", errorContext: { message: "Session creation timed out", category: "timeout", isTransient: true } };
|
|
115
122
|
}
|
|
116
123
|
|
|
117
124
|
if (!s.active) {
|
|
125
|
+
_consumePendingSwitchCancellation();
|
|
118
126
|
return { status: "cancelled" };
|
|
119
127
|
}
|
|
120
128
|
|
|
121
129
|
if (s.currentUnitModel && typeof pi.setModel === "function") {
|
|
122
130
|
const restored = await pi.setModel(s.currentUnitModel, { persist: false });
|
|
123
131
|
if (!restored) {
|
|
132
|
+
_consumePendingSwitchCancellation();
|
|
124
133
|
const message =
|
|
125
134
|
`Failed to restore configured model ${s.currentUnitModel.provider}/${s.currentUnitModel.id} after session creation`;
|
|
126
135
|
ctx.ui.notify(
|
|
@@ -145,6 +154,14 @@ export async function runUnit(
|
|
|
145
154
|
const unitPromise = new Promise<UnitResult>((resolve) => {
|
|
146
155
|
_setCurrentResolve(resolve);
|
|
147
156
|
});
|
|
157
|
+
const pendingSwitchCancellation = _consumePendingSwitchCancellation();
|
|
158
|
+
if (pendingSwitchCancellation) {
|
|
159
|
+
_clearCurrentResolve();
|
|
160
|
+
return {
|
|
161
|
+
status: "cancelled",
|
|
162
|
+
...(pendingSwitchCancellation.errorContext ? { errorContext: pendingSwitchCancellation.errorContext } : {}),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
148
165
|
|
|
149
166
|
// ── Provider request-readiness pre-check (#4555) ──
|
|
150
167
|
// Verify the provider can accept requests before dispatching. If the token
|
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
import { regenerateIfMissing } from "./workflow-projections.js";
|
|
44
44
|
import { syncStateToProjectRoot } from "./auto-worktree.js";
|
|
45
45
|
import { normalizeWorktreePathForCompare } from "./worktree-root.js";
|
|
46
|
-
import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter } from "./gsd-db.js";
|
|
46
|
+
import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter, getVerificationEvidence } from "./gsd-db.js";
|
|
47
47
|
import { renderPlanCheckboxes } from "./markdown-renderer.js";
|
|
48
48
|
import { consumeSignal } from "./session-status-io.js";
|
|
49
49
|
import {
|
|
@@ -852,22 +852,22 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
|
|
|
852
852
|
}
|
|
853
853
|
|
|
854
854
|
// Evidence cross-reference (execute-task only)
|
|
855
|
-
//
|
|
856
|
-
//
|
|
857
|
-
//
|
|
858
|
-
// we can still detect units that claimed success but ran no commands.
|
|
855
|
+
// Only compare against concrete command evidence persisted by the task
|
|
856
|
+
// completion tool. A prose Verify field can be satisfied later by the
|
|
857
|
+
// host verification gate, so it is not enough to accuse the unit.
|
|
859
858
|
if (safetyConfig.evidence_cross_reference && s.currentUnit.type === "execute-task") {
|
|
860
859
|
try {
|
|
861
860
|
const actual = getEvidence();
|
|
862
861
|
const bashCalls = actual.filter(e => e.kind === "bash");
|
|
863
|
-
// If the task is marked complete but zero bash commands were run,
|
|
864
|
-
// it's suspicious — the LLM may have fabricated results.
|
|
865
862
|
if (sMid && sSid && sTid && isDbAvailable()) {
|
|
866
863
|
const taskRow = getTask(sMid, sSid, sTid);
|
|
867
|
-
|
|
868
|
-
|
|
864
|
+
const claimedCommands = getVerificationEvidence(sMid, sSid, sTid)
|
|
865
|
+
.map((row) => row.command)
|
|
866
|
+
.filter((command): command is string => typeof command === "string" && command.trim().length > 0);
|
|
867
|
+
if (taskRow?.status === "complete" && claimedCommands.length > 0 && bashCalls.length === 0) {
|
|
868
|
+
logWarning("safety", "task claimed verification command evidence but no execution tool calls were recorded");
|
|
869
869
|
ctx.ui.notify(
|
|
870
|
-
`Safety: task ${sTid}
|
|
870
|
+
`Safety: task ${sTid} claimed command evidence but no execution tool calls were recorded`,
|
|
871
871
|
"warning",
|
|
872
872
|
);
|
|
873
873
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { loadFile, parseContinue, parseSummary, loadActiveOverrides, formatOverridesSection, parseTaskPlanFile } from "./files.js";
|
|
10
10
|
import type { Override, UatType } from "./files.js";
|
|
11
|
-
import { hasVerdict, getUatType } from "./verdict-parser.js";
|
|
11
|
+
import { hasVerdict, getUatType, extractVerdict } from "./verdict-parser.js";
|
|
12
12
|
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
13
13
|
import {
|
|
14
14
|
resolveMilestoneFile, resolveSliceFile, resolveSlicePath,
|
|
@@ -35,10 +35,12 @@ import {
|
|
|
35
35
|
import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
|
|
36
36
|
import { readPhaseAnchor, formatAnchorForPrompt } from "./phase-anchor.js";
|
|
37
37
|
import { composeContextModeInstructions, composeInlinedContext, type ArtifactResolver, type ContextModeRenderMode } from "./unit-context-composer.js";
|
|
38
|
+
import { readCompactionSnapshot } from "./compaction-snapshot.js";
|
|
38
39
|
import { logWarning } from "./workflow-logger.js";
|
|
39
40
|
import { inlineGraphSubgraph } from "./graph-context.js";
|
|
40
41
|
import { buildExtractionStepsBlock } from "./commands-extract-learnings.js";
|
|
41
42
|
import { resolveSkillManifest, warnIfManifestHasMissingSkills } from "./skill-manifest.js";
|
|
43
|
+
import { classifyProject, type ProjectClassification } from "./detection.js";
|
|
42
44
|
|
|
43
45
|
// ─── Preamble Cap ─────────────────────────────────────────────────────────────
|
|
44
46
|
|
|
@@ -80,6 +82,108 @@ function resolveSummaryBudgetChars(): number {
|
|
|
80
82
|
return resolvePromptBudgets().summaryBudgetChars;
|
|
81
83
|
}
|
|
82
84
|
|
|
85
|
+
function formatProjectClassificationForPlanning(classification: ProjectClassification): string {
|
|
86
|
+
const sampleFiles = classification.contentFiles.slice(0, 8);
|
|
87
|
+
const sample = sampleFiles.length > 0 ? sampleFiles.map((file) => `\`${file}\``).join(", ") : "(none)";
|
|
88
|
+
const lines = [
|
|
89
|
+
"### Project Classification",
|
|
90
|
+
"",
|
|
91
|
+
`- **Kind:** ${classification.kind}`,
|
|
92
|
+
`- **Content files:** ${classification.contentFiles.length}`,
|
|
93
|
+
`- **Sample files:** ${sample}`,
|
|
94
|
+
`- **Reason:** ${classification.reason}`,
|
|
95
|
+
"",
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
if (classification.kind === "untyped-existing") {
|
|
99
|
+
if (classification.contentFiles.length <= 2) {
|
|
100
|
+
lines.push(
|
|
101
|
+
"**Workflow sizing:** This is a tiny existing untyped project. Prefer exactly one slice unless the milestone request clearly spans multiple independent user-visible capabilities.",
|
|
102
|
+
);
|
|
103
|
+
} else if (classification.contentFiles.length <= 5) {
|
|
104
|
+
lines.push(
|
|
105
|
+
"**Workflow sizing:** This is a small existing untyped project. Prefer 1-2 slices unless the milestone request clearly spans multiple independent user-visible capabilities.",
|
|
106
|
+
);
|
|
107
|
+
} else {
|
|
108
|
+
lines.push(
|
|
109
|
+
"**Workflow sizing:** Existing untyped project. Use generic file-level workflow guidance and size slices by real capability boundaries, not by missing tooling markers.",
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
} else if (classification.kind === "greenfield") {
|
|
113
|
+
lines.push("**Workflow sizing:** No project content exists yet. Use normal greenfield sizing for the requested scope.");
|
|
114
|
+
} else if (classification.kind === "typed-existing") {
|
|
115
|
+
lines.push("**Workflow sizing:** Known project markers exist. Use normal ecosystem-aware planning guidance.");
|
|
116
|
+
} else {
|
|
117
|
+
lines.push("**Workflow sizing:** Invalid repository state. Planning should surface this as a blocker rather than inventing project structure.");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return lines.join("\n");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeArtifactRef(value: string): string {
|
|
124
|
+
return value.trim().replace(/^[-\s]+/, "").replace(/^["'`]+|["'`]+$/g, "").replaceAll("\\", "/").replace(/^\.\//, "");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function parseCoveredArtifacts(validationContent: string): Set<string> {
|
|
128
|
+
const covered = new Set<string>();
|
|
129
|
+
const lines = validationContent.split(/\r?\n/);
|
|
130
|
+
let inCoveredArtifacts = false;
|
|
131
|
+
for (const line of lines) {
|
|
132
|
+
if (/^\s*covered[-_]?artifacts\s*:/i.test(line)) {
|
|
133
|
+
inCoveredArtifacts = true;
|
|
134
|
+
const inline = line.split(/covered[-_]?artifacts\s*:/i)[1]?.trim();
|
|
135
|
+
if (inline && inline !== "[]") {
|
|
136
|
+
inline.replace(/^\[|\]$/g, "").split(",").map(normalizeArtifactRef).filter(Boolean).forEach((item) => covered.add(item));
|
|
137
|
+
}
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (!inCoveredArtifacts) continue;
|
|
141
|
+
if (/^\S/.test(line) && !/^\s*-/.test(line)) break;
|
|
142
|
+
const item = line.match(/^\s*-\s*(.+)$/)?.[1];
|
|
143
|
+
if (item) covered.add(normalizeArtifactRef(item));
|
|
144
|
+
}
|
|
145
|
+
return covered;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function isValidationFreshOrApplicable(validationContent: string | null, currentArtifacts: string[]): boolean {
|
|
149
|
+
if (!validationContent) return false;
|
|
150
|
+
if (!/validation_metadata:/i.test(validationContent)) return false;
|
|
151
|
+
const coveredArtifacts = parseCoveredArtifacts(validationContent);
|
|
152
|
+
if (coveredArtifacts.size === 0) return false;
|
|
153
|
+
return currentArtifacts
|
|
154
|
+
.map(normalizeArtifactRef)
|
|
155
|
+
.filter(Boolean)
|
|
156
|
+
.every((artifact) => coveredArtifacts.has(artifact));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function formatCloseoutReviewInstructions(validationContent: string | null, validationRel: string, currentArtifacts: string[]): string {
|
|
160
|
+
const verdict = validationContent ? extractVerdict(validationContent) : null;
|
|
161
|
+
const validationFresh = isValidationFreshOrApplicable(validationContent, currentArtifacts);
|
|
162
|
+
if (verdict === "pass" && validationFresh) {
|
|
163
|
+
return [
|
|
164
|
+
"### Passing Validation Artifact",
|
|
165
|
+
"",
|
|
166
|
+
`A passing validation artifact is present at \`${validationRel}\`. Treat it as authoritative for success criteria, requirement coverage, verification classes, and cross-slice integration.`,
|
|
167
|
+
"",
|
|
168
|
+
"Do not delegate fresh reviewer/security/tester audits and do not redo the validation evidence review unless the artifact is internally inconsistent with the inlined summaries. Focus this unit on final milestone narrative, learnings, PROJECT/requirements updates, and `gsd_complete_milestone`.",
|
|
169
|
+
].join("\n");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (verdict) {
|
|
173
|
+
return [
|
|
174
|
+
"### Validation Requires Attention",
|
|
175
|
+
"",
|
|
176
|
+
`A validation artifact is present at \`${validationRel}\` with verdict \`${verdict}\`, but it is missing freshness metadata or does not cover current milestone artifacts. Do not treat the milestone as complete unless the issues are resolved and evidence supports completion.`,
|
|
177
|
+
].join("\n");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return [
|
|
181
|
+
"### No Passing Validation Artifact",
|
|
182
|
+
"",
|
|
183
|
+
`No passing validation artifact was found at \`${validationRel}\`. Use the full closeout review path before completion.`,
|
|
184
|
+
].join("\n");
|
|
185
|
+
}
|
|
186
|
+
|
|
83
187
|
function capPreamble(preamble: string): string {
|
|
84
188
|
// Cap inlined context at min(historical 30K ceiling, scaled inline budget).
|
|
85
189
|
// The ceiling preserves pre-fix behavior for large-window users; the scaled
|
|
@@ -102,13 +206,28 @@ function renderContextModeForPrompt(
|
|
|
102
206
|
});
|
|
103
207
|
}
|
|
104
208
|
|
|
209
|
+
function renderContextModeBlockForPrompt(
|
|
210
|
+
unitType: string,
|
|
211
|
+
base: string,
|
|
212
|
+
renderMode: ContextModeRenderMode = "standalone",
|
|
213
|
+
): string {
|
|
214
|
+
const contextMode = renderContextModeForPrompt(unitType, base, renderMode);
|
|
215
|
+
if (!contextMode) return "";
|
|
216
|
+
if (renderMode === "nested") return contextMode;
|
|
217
|
+
|
|
218
|
+
const snapshot = readCompactionSnapshot(base);
|
|
219
|
+
if (!snapshot?.trim()) return contextMode;
|
|
220
|
+
|
|
221
|
+
return `${contextMode}\n\n## Context Snapshot\nSource: \`.gsd/last-snapshot.md\`\n\n${snapshot.trimEnd()}`;
|
|
222
|
+
}
|
|
223
|
+
|
|
105
224
|
function prependContextModeToBlock(
|
|
106
225
|
unitType: string,
|
|
107
226
|
base: string,
|
|
108
227
|
block: string,
|
|
109
228
|
renderMode: ContextModeRenderMode = "standalone",
|
|
110
229
|
): string {
|
|
111
|
-
const contextMode =
|
|
230
|
+
const contextMode = renderContextModeBlockForPrompt(unitType, base, renderMode);
|
|
112
231
|
if (!contextMode) return block;
|
|
113
232
|
if (!block.trim()) return contextMode;
|
|
114
233
|
return `${contextMode}\n\n${block}`;
|
|
@@ -1658,6 +1777,8 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
|
|
|
1658
1777
|
const researchAnchor = readPhaseAnchor(base, mid, "research-milestone");
|
|
1659
1778
|
if (researchAnchor) inlined.push(formatAnchorForPrompt(researchAnchor));
|
|
1660
1779
|
|
|
1780
|
+
inlined.push(formatProjectClassificationForPlanning(classifyProject(base)));
|
|
1781
|
+
|
|
1661
1782
|
inlined.push(await inlineFile(contextPath, contextRel, "Milestone Context"));
|
|
1662
1783
|
const researchInline = await inlineFileOptional(researchPath, researchRel, "Milestone Research");
|
|
1663
1784
|
if (researchInline) inlined.push(researchInline);
|
|
@@ -2348,6 +2469,9 @@ export async function buildCompleteMilestonePrompt(
|
|
|
2348
2469
|
const inlineLevel = level ?? resolveInlineLevel();
|
|
2349
2470
|
const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
|
|
2350
2471
|
const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
|
|
2472
|
+
const validationPath = resolveMilestoneFile(base, mid, "VALIDATION");
|
|
2473
|
+
const validationRel = relMilestoneFile(base, mid, "VALIDATION");
|
|
2474
|
+
const validationContent = validationPath ? await loadFile(validationPath) : null;
|
|
2351
2475
|
|
|
2352
2476
|
const inlined: string[] = [];
|
|
2353
2477
|
inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
|
|
@@ -2389,6 +2513,13 @@ export async function buildCompleteMilestonePrompt(
|
|
|
2389
2513
|
`### On-demand Slice Summaries\n\nExcerpted above. Read the full file for any slice when the excerpt's section heads don't carry enough narrative for the milestone summary you're drafting:\n\n${pathList}`,
|
|
2390
2514
|
);
|
|
2391
2515
|
}
|
|
2516
|
+
const validationContext = [
|
|
2517
|
+
formatCloseoutReviewInstructions(validationContent, validationRel, [validationRel, roadmapRel, ...summaryRelPaths]),
|
|
2518
|
+
];
|
|
2519
|
+
if (validationContent) {
|
|
2520
|
+
validationContext.push(`### Milestone Validation\nSource: \`${validationRel}\`\n\n${validationContent.trim()}`);
|
|
2521
|
+
}
|
|
2522
|
+
inlined.unshift(...validationContext);
|
|
2392
2523
|
|
|
2393
2524
|
// Inline root GSD files (skip for minimal — completion can read these if needed)
|
|
2394
2525
|
if (inlineLevel !== "minimal") {
|
|
@@ -67,6 +67,60 @@ export {
|
|
|
67
67
|
|
|
68
68
|
// ─── Artifact Resolution & Verification ───────────────────────────────────────
|
|
69
69
|
|
|
70
|
+
export type ArtifactRecoveryDbRefreshResult =
|
|
71
|
+
| { ok: true }
|
|
72
|
+
| { ok: false; fatal: boolean; message: string; reason: string };
|
|
73
|
+
|
|
74
|
+
export function refreshRecoveryDbForArtifact(
|
|
75
|
+
unitType: string,
|
|
76
|
+
unitId: string,
|
|
77
|
+
): ArtifactRecoveryDbRefreshResult {
|
|
78
|
+
if (unitType !== "plan-slice" && unitType !== "execute-task") return { ok: true };
|
|
79
|
+
if (!isDbAvailable()) return { ok: true };
|
|
80
|
+
|
|
81
|
+
if (!refreshOpenDatabaseFromDisk()) {
|
|
82
|
+
return {
|
|
83
|
+
ok: false,
|
|
84
|
+
fatal: unitType === "execute-task",
|
|
85
|
+
reason: `${unitType}-db-refresh-failed`,
|
|
86
|
+
message: `Stuck recovery found ${unitType} ${unitId} artifacts, but the DB refresh failed.`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (unitType !== "execute-task") return { ok: true };
|
|
91
|
+
|
|
92
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
93
|
+
if (!mid || !sid || !tid) {
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
fatal: true,
|
|
97
|
+
reason: "execute-task-invalid-unit-id",
|
|
98
|
+
message: `Stuck recovery found execute-task ${unitId} artifacts, but the unit id could not be parsed for DB verification.`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const task = getTask(mid, sid, tid);
|
|
103
|
+
if (!task) {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
fatal: true,
|
|
107
|
+
reason: "execute-task-artifact-db-missing",
|
|
108
|
+
message: `Stuck recovery found execute-task ${unitId} artifacts, but no matching DB task row exists after refresh.`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!isClosedStatus(task.status)) {
|
|
113
|
+
return {
|
|
114
|
+
ok: false,
|
|
115
|
+
fatal: true,
|
|
116
|
+
reason: "execute-task-artifact-db-mismatch",
|
|
117
|
+
message: `Stuck recovery found execute-task ${unitId} artifacts, but the DB task status is still '${task.status}' after refresh.`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { ok: true };
|
|
122
|
+
}
|
|
123
|
+
|
|
70
124
|
function hasCapturedWorkflowPrefs(base: string): boolean {
|
|
71
125
|
const prefsPath = resolveExpectedArtifactPath("workflow-preferences", "WORKFLOW-PREFS", base);
|
|
72
126
|
if (!prefsPath || !existsSync(prefsPath)) return false;
|
|
@@ -32,6 +32,7 @@ let _currentSigtermHandler: (() => void) | null = null;
|
|
|
32
32
|
export function registerSigtermHandler(
|
|
33
33
|
currentBasePath: string,
|
|
34
34
|
previousHandler: (() => void) | null,
|
|
35
|
+
onSignalCleanup?: () => void,
|
|
35
36
|
): () => void {
|
|
36
37
|
// Remove the explicitly-passed previous handler
|
|
37
38
|
if (previousHandler) {
|
|
@@ -43,6 +44,12 @@ export function registerSigtermHandler(
|
|
|
43
44
|
for (const sig of CLEANUP_SIGNALS) process.off(sig, _currentSigtermHandler);
|
|
44
45
|
}
|
|
45
46
|
const handler = () => {
|
|
47
|
+
try {
|
|
48
|
+
onSignalCleanup?.();
|
|
49
|
+
} catch {
|
|
50
|
+
void 0;
|
|
51
|
+
// Signal cleanup is best-effort; lock cleanup and process exit still run.
|
|
52
|
+
}
|
|
46
53
|
clearLock(currentBasePath);
|
|
47
54
|
releaseSessionLock(currentBasePath);
|
|
48
55
|
process.exit(0);
|
|
@@ -71,14 +71,14 @@ export async function recoverTimedOutUnit(
|
|
|
71
71
|
recovery: status,
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
const durableComplete = status.summaryExists && status.taskChecked && status.nextActionAdvanced;
|
|
74
|
+
const durableComplete = status.dbComplete || (status.summaryExists && status.taskChecked && status.nextActionAdvanced);
|
|
75
75
|
if (durableComplete) {
|
|
76
76
|
writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
|
|
77
77
|
phase: "finalized",
|
|
78
78
|
recovery: status,
|
|
79
79
|
});
|
|
80
80
|
ctx.ui.notify(
|
|
81
|
-
`${reason === "idle" ? "Idle" : "Timeout"} recovery: ${unitType} ${unitId} already completed
|
|
81
|
+
`${reason === "idle" ? "Idle" : "Timeout"} recovery: ${unitType} ${unitId} already completed. Continuing auto-mode. (attempt ${attemptNumber})`,
|
|
82
82
|
"info",
|
|
83
83
|
);
|
|
84
84
|
unitRecoveryCount.delete(recoveryKey);
|
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
isLockProcessAlive,
|
|
60
60
|
formatCrashInfo,
|
|
61
61
|
emitCrashRecoveredUnitEnd,
|
|
62
|
+
emitOpenUnitEndForUnit,
|
|
62
63
|
} from "./crash-recovery.js";
|
|
63
64
|
import {
|
|
64
65
|
acquireSessionLock,
|
|
@@ -200,6 +201,8 @@ import {
|
|
|
200
201
|
detectWorkingTreeActivity,
|
|
201
202
|
} from "./auto-supervisor.js";
|
|
202
203
|
import { isDbAvailable, getMilestone } from "./gsd-db.js";
|
|
204
|
+
import { markLatestActiveForWorkerCanceled } from "./db/unit-dispatches.js";
|
|
205
|
+
import { writeUnitRuntimeRecord } from "./unit-runtime.js";
|
|
203
206
|
import { countPendingCaptures } from "./captures.js";
|
|
204
207
|
import { CMUX_CHANNELS, type CmuxLogLevel } from "../shared/cmux-events.js";
|
|
205
208
|
import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
|
|
@@ -242,6 +245,20 @@ import {
|
|
|
242
245
|
type WorktreeResolverDeps,
|
|
243
246
|
} from "./worktree-resolver.js";
|
|
244
247
|
import { reorderForCaching } from "./prompt-ordering.js";
|
|
248
|
+
import { initTokenCounter } from "./token-counter.js";
|
|
249
|
+
|
|
250
|
+
// Warm the tiktoken encoder at extension startup so context-budget computations
|
|
251
|
+
// can use accurate token counts via countTokensSync without paying the load
|
|
252
|
+
// cost mid-prompt-build. Fire-and-forget — failure falls back to the
|
|
253
|
+
// provider-aware char-ratio estimator already used by getCharsPerToken().
|
|
254
|
+
// Catch rejections explicitly: an unhandled rejection at module-import time
|
|
255
|
+
// can destabilize startup before the engine logger is configured.
|
|
256
|
+
void initTokenCounter().catch((err) => {
|
|
257
|
+
logWarning(
|
|
258
|
+
"engine",
|
|
259
|
+
`token counter warm-up failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
260
|
+
);
|
|
261
|
+
});
|
|
245
262
|
|
|
246
263
|
// ─── Session State ─────────────────────────────────────────────────────────
|
|
247
264
|
|
|
@@ -293,11 +310,15 @@ const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
|
|
|
293
310
|
* the DB is unavailable (e.g. fresh project before init) we skip registration
|
|
294
311
|
* silently rather than blocking session start.
|
|
295
312
|
*/
|
|
296
|
-
function registerAutoWorkerForSession(
|
|
313
|
+
function registerAutoWorkerForSession(
|
|
314
|
+
session: AutoSession,
|
|
315
|
+
projectRootOverride?: string,
|
|
316
|
+
): void {
|
|
297
317
|
if (session.workerId) return; // already registered (e.g. resume re-runs)
|
|
298
318
|
try {
|
|
299
319
|
const projectRootRealpath = normalizeRealPath(
|
|
300
|
-
|
|
320
|
+
projectRootOverride
|
|
321
|
+
?? session.scope?.workspace.projectRoot
|
|
301
322
|
?? (session.originalBasePath || session.basePath),
|
|
302
323
|
);
|
|
303
324
|
session.workerId = registerAutoWorker({ projectRootRealpath });
|
|
@@ -420,6 +441,17 @@ export function _synthesizePausedSessionRecoveryForTest(
|
|
|
420
441
|
return synthesizePausedSessionRecovery(basePath, unitType, unitId, sessionFile);
|
|
421
442
|
}
|
|
422
443
|
|
|
444
|
+
const DETACHED_AUTO_KEEPALIVE_INTERVAL_MS = 30_000;
|
|
445
|
+
|
|
446
|
+
function withDetachedAutoKeepalive<T>(run: Promise<T>): Promise<T> {
|
|
447
|
+
const keepAlive = setInterval(() => {}, DETACHED_AUTO_KEEPALIVE_INTERVAL_MS);
|
|
448
|
+
return run.finally(() => {
|
|
449
|
+
clearInterval(keepAlive);
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export const _withDetachedAutoKeepaliveForTest = withDetachedAutoKeepalive;
|
|
454
|
+
|
|
423
455
|
export function startAutoDetached(
|
|
424
456
|
ctx: ExtensionCommandContext,
|
|
425
457
|
pi: ExtensionAPI,
|
|
@@ -431,7 +463,7 @@ export function startAutoDetached(
|
|
|
431
463
|
milestoneLock?: string | null;
|
|
432
464
|
},
|
|
433
465
|
): void {
|
|
434
|
-
void startAuto(ctx, pi, base, verboseMode, options).catch((err) => {
|
|
466
|
+
void withDetachedAutoKeepalive(startAuto(ctx, pi, base, verboseMode, options)).catch((err) => {
|
|
435
467
|
const message = getErrorMessage(err);
|
|
436
468
|
ctx.ui.notify(`Auto-start failed: ${message}`, "error");
|
|
437
469
|
logWarning("engine", `auto start error: ${message}`, { file: "auto.ts" });
|
|
@@ -490,9 +522,54 @@ export {
|
|
|
490
522
|
getBudgetEnforcementAction,
|
|
491
523
|
} from "./auto-budget.js";
|
|
492
524
|
|
|
525
|
+
function closeOutSignalInterruptedUnit(currentBasePath: string): void {
|
|
526
|
+
const currentUnit = s.currentUnit;
|
|
527
|
+
if (!currentUnit) return;
|
|
528
|
+
|
|
529
|
+
const reason = "Auto-mode process received a termination signal";
|
|
530
|
+
const errorContext: ErrorContext = {
|
|
531
|
+
message: reason,
|
|
532
|
+
category: "aborted",
|
|
533
|
+
isTransient: false,
|
|
534
|
+
};
|
|
535
|
+
const basePath = s.basePath || currentBasePath;
|
|
536
|
+
|
|
537
|
+
try {
|
|
538
|
+
emitOpenUnitEndForUnit(basePath, currentUnit.type, currentUnit.id, "cancelled", errorContext);
|
|
539
|
+
} catch (err) {
|
|
540
|
+
logWarning("engine", `signal unit-end cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
try {
|
|
544
|
+
writeUnitRuntimeRecord(basePath, currentUnit.type, currentUnit.id, currentUnit.startedAt, {
|
|
545
|
+
phase: "crashed",
|
|
546
|
+
lastProgressAt: Date.now(),
|
|
547
|
+
lastProgressKind: "signal",
|
|
548
|
+
});
|
|
549
|
+
} catch (err) {
|
|
550
|
+
logWarning("engine", `signal runtime cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
try {
|
|
554
|
+
if (s.workerId) markLatestActiveForWorkerCanceled(s.workerId, "signal-exit");
|
|
555
|
+
} catch (err) {
|
|
556
|
+
logWarning("engine", `signal dispatch cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
try {
|
|
560
|
+
resolveAgentEndCancelled(errorContext);
|
|
561
|
+
} catch (err) {
|
|
562
|
+
logWarning("engine", `signal resolve cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
493
566
|
/** Wrapper: register SIGTERM handler and store reference. */
|
|
494
567
|
function registerSigtermHandler(currentBasePath: string): void {
|
|
495
|
-
s.sigtermHandler = _registerSigtermHandler(
|
|
568
|
+
s.sigtermHandler = _registerSigtermHandler(
|
|
569
|
+
currentBasePath,
|
|
570
|
+
s.sigtermHandler,
|
|
571
|
+
() => closeOutSignalInterruptedUnit(currentBasePath),
|
|
572
|
+
);
|
|
496
573
|
}
|
|
497
574
|
|
|
498
575
|
/** Wrapper: deregister SIGTERM handler and clear reference. */
|
|
@@ -957,6 +1034,8 @@ export async function stopAuto(
|
|
|
957
1034
|
if (s.workerId) {
|
|
958
1035
|
markWorkerStopping(s.workerId);
|
|
959
1036
|
}
|
|
1037
|
+
s.workerId = null;
|
|
1038
|
+
s.milestoneLeaseToken = null;
|
|
960
1039
|
} catch (e) {
|
|
961
1040
|
debugLog("stop-cleanup-coordination", { error: e instanceof Error ? e.message : String(e) });
|
|
962
1041
|
}
|
|
@@ -1960,6 +2039,10 @@ export async function startAuto(
|
|
|
1960
2039
|
: new URL("../../../resource-loader.js", import.meta.url).href;
|
|
1961
2040
|
const { initResources } = await import(resourceLoaderPath);
|
|
1962
2041
|
initResources(agentDir);
|
|
2042
|
+
// initResources() uses synchronous fs APIs, so the prompt-template cache
|
|
2043
|
+
// can be primed immediately — no need for the legacy 1s setTimeout deferral.
|
|
2044
|
+
const { primeCache } = await import("./prompt-loader.js");
|
|
2045
|
+
primeCache();
|
|
1963
2046
|
// Open the project DB before rebuild/derive so resume uses DB-backed
|
|
1964
2047
|
// state instead of falling back to stale markdown parsing (#2940).
|
|
1965
2048
|
await openProjectDbIfPresent(s.basePath);
|
|
@@ -2046,6 +2129,11 @@ export async function startAuto(
|
|
|
2046
2129
|
buildResolver,
|
|
2047
2130
|
};
|
|
2048
2131
|
|
|
2132
|
+
// Register the worker before bootstrap enters a milestone worktree.
|
|
2133
|
+
// This ensures enterMilestone can claim a lease and seed dispatch claims
|
|
2134
|
+
// for crash-recovery fidelity (#5405).
|
|
2135
|
+
registerAutoWorkerForSession(s, base);
|
|
2136
|
+
|
|
2049
2137
|
const ready = await bootstrapAutoSession(
|
|
2050
2138
|
s,
|
|
2051
2139
|
ctx,
|
|
@@ -153,9 +153,29 @@ export async function handleAgentEnd(
|
|
|
153
153
|
if (maybeHandleEmptyIntentTurn(event, isAutoActive())) return;
|
|
154
154
|
|
|
155
155
|
if (!isAutoActive()) return;
|
|
156
|
-
if (isSessionSwitchInFlight()) return;
|
|
157
156
|
|
|
158
157
|
const lastMsg = event.messages[event.messages.length - 1];
|
|
158
|
+
if (isSessionSwitchInFlight()) {
|
|
159
|
+
if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "error") {
|
|
160
|
+
const rawErrorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
|
|
161
|
+
if (isUserInitiatedAbortMessage(rawErrorMsg)) {
|
|
162
|
+
resolveAgentEndCancelled({
|
|
163
|
+
message: rawErrorMsg,
|
|
164
|
+
category: "aborted",
|
|
165
|
+
isTransient: false,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
} else if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "aborted") {
|
|
169
|
+
const content = "content" in lastMsg ? lastMsg.content : undefined;
|
|
170
|
+
const hasEmptyContent = Array.isArray(content) && content.length === 0;
|
|
171
|
+
const hasErrorMessage = "errorMessage" in lastMsg && !!lastMsg.errorMessage;
|
|
172
|
+
if (!hasEmptyContent || hasErrorMessage) {
|
|
173
|
+
resolveAgentEndCancelled(_buildAbortedPauseContext(lastMsg as { errorMessage?: unknown }));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
159
179
|
if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "aborted") {
|
|
160
180
|
// Empty content with aborted stopReason is a non-fatal agent stop (the LLM
|
|
161
181
|
// chose to end without producing output). Only pause on genuine fatal aborts
|