@opengsd/gsd-pi 1.2.0-dev.9ad8ae33 → 1.2.0-dev.a6376d75
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/dist/cli-model-override.d.ts +15 -0
- package/dist/cli-model-override.js +21 -0
- package/dist/cli.js +1 -18
- package/dist/loader.js +6 -4
- package/dist/register-agent-bundles.d.ts +11 -2
- package/dist/register-agent-bundles.js +18 -4
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/ask-user-questions.js +3 -2
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +447 -215
- package/dist/resources/extensions/claude-code-cli/turn-assembler.js +33 -1
- package/dist/resources/extensions/gsd/auto/closeout.js +215 -0
- package/dist/resources/extensions/gsd/auto/dispatch-history.js +21 -6
- package/dist/resources/extensions/gsd/auto/dispatch.js +365 -0
- package/dist/resources/extensions/gsd/auto/finalize.js +347 -0
- package/dist/resources/extensions/gsd/auto/loop.js +4 -1
- package/dist/resources/extensions/gsd/auto/milestone-lease-reclaim.js +56 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +85 -15
- package/dist/resources/extensions/gsd/auto/phase-helpers.js +146 -0
- package/dist/resources/extensions/gsd/auto/phases.js +17 -2372
- package/dist/resources/extensions/gsd/auto/pre-dispatch.js +534 -0
- package/dist/resources/extensions/gsd/auto/unit-phase.js +694 -0
- package/dist/resources/extensions/gsd/auto/workflow-unit-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto/worktree-safety-phase.js +125 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +1 -1
- package/dist/resources/extensions/gsd/auto.js +15 -1
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +37 -7
- package/dist/resources/extensions/gsd/commands-mcp-status.js +2 -2
- package/dist/resources/extensions/gsd/commands-workflow-templates.js +9 -2
- package/dist/resources/extensions/gsd/db/queries.js +30 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +256 -125
- package/dist/resources/extensions/gsd/guided-flow.js +88 -2
- package/dist/resources/extensions/gsd/health-widget.js +87 -28
- package/dist/resources/extensions/gsd/mcp-bridge.js +10 -0
- package/dist/resources/extensions/gsd/milestone-settlement.js +2 -2
- package/dist/resources/extensions/gsd/notifications.js +12 -7
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +2 -0
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -1
- package/dist/resources/extensions/gsd/skill-activation.js +3 -6
- package/dist/resources/extensions/gsd/state.js +6 -2
- package/dist/resources/extensions/gsd/tool-surface-readiness.js +83 -31
- package/dist/resources/extensions/gsd/tools/complete-task.js +62 -0
- package/dist/resources/extensions/gsd/unit-context-composer.js +1 -1
- package/dist/resources/extensions/gsd/unit-registry.js +34 -4
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +2 -0
- package/dist/resources/extensions/gsd/workflow-mcp-readiness-cache.js +105 -0
- package/dist/resources/extensions/gsd/worktree-safety.js +28 -26
- package/dist/resources/extensions/mcp-client/manager.js +6 -1
- package/dist/runtime-checks.d.ts +10 -0
- package/dist/runtime-checks.js +27 -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 +8 -8
- 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 +8 -8
- 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 +2 -2
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/dist/sdk.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/sdk.js +6 -4
- package/packages/gsd-agent-core/dist/sdk.js.map +1 -1
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +10 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +8 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +50 -6
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +34 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +12 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +4 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/README.md +12 -3
- package/packages/mcp-server/dist/cli-runner.d.ts +40 -0
- package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -0
- package/packages/mcp-server/dist/cli-runner.js +137 -0
- package/packages/mcp-server/dist/cli-runner.js.map +1 -0
- package/packages/mcp-server/dist/cli.js +2 -58
- package/packages/mcp-server/dist/cli.js.map +1 -1
- package/packages/mcp-server/dist/pid-registry.d.ts +46 -0
- package/packages/mcp-server/dist/pid-registry.d.ts.map +1 -0
- package/packages/mcp-server/dist/pid-registry.js +452 -0
- package/packages/mcp-server/dist/pid-registry.js.map +1 -0
- package/packages/mcp-server/dist/probe-mode.d.ts +4 -0
- package/packages/mcp-server/dist/probe-mode.d.ts.map +1 -0
- package/packages/mcp-server/dist/probe-mode.js +10 -0
- package/packages/mcp-server/dist/probe-mode.js.map +1 -0
- package/packages/mcp-server/dist/stdio-watchdog.d.ts +8 -0
- package/packages/mcp-server/dist/stdio-watchdog.d.ts.map +1 -0
- package/packages/mcp-server/dist/stdio-watchdog.js +40 -0
- package/packages/mcp-server/dist/stdio-watchdog.js.map +1 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +62 -43
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +5 -5
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +43 -2
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -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 +11 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.js +45 -17
- package/packages/pi-coding-agent/dist/theme/theme.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/index.d.ts +1 -1
- package/packages/pi-tui/dist/index.d.ts.map +1 -1
- package/packages/pi-tui/dist/index.js +1 -1
- package/packages/pi-tui/dist/index.js.map +1 -1
- package/packages/pi-tui/dist/terminal-image.d.ts +33 -0
- package/packages/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal-image.js +54 -2
- package/packages/pi-tui/dist/terminal-image.js.map +1 -1
- package/packages/pi-tui/dist/tui.d.ts +8 -0
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +63 -18
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/dist/utils.d.ts.map +1 -1
- package/packages/pi-tui/dist/utils.js +110 -36
- package/packages/pi-tui/dist/utils.js.map +1 -1
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/dist/theme/theme.d.ts.map +1 -1
- package/pkg/dist/theme/theme.js +45 -17
- package/pkg/dist/theme/theme.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/ask-user-questions.ts +7 -2
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +531 -226
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +672 -7
- package/src/resources/extensions/claude-code-cli/turn-assembler.ts +38 -1
- package/src/resources/extensions/gsd/auto/closeout.ts +309 -0
- package/src/resources/extensions/gsd/auto/dispatch-history.ts +22 -6
- package/src/resources/extensions/gsd/auto/dispatch.ts +449 -0
- package/src/resources/extensions/gsd/auto/finalize.ts +445 -0
- package/src/resources/extensions/gsd/auto/loop.ts +4 -1
- package/src/resources/extensions/gsd/auto/milestone-lease-reclaim.ts +74 -0
- package/src/resources/extensions/gsd/auto/orchestrator.ts +95 -15
- package/src/resources/extensions/gsd/auto/phase-helpers.ts +199 -0
- package/src/resources/extensions/gsd/auto/phases.ts +58 -3061
- package/src/resources/extensions/gsd/auto/pre-dispatch.ts +704 -0
- package/src/resources/extensions/gsd/auto/unit-phase.ts +910 -0
- package/src/resources/extensions/gsd/auto/workflow-unit-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto/worktree-safety-phase.ts +149 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +1 -1
- package/src/resources/extensions/gsd/auto.ts +20 -1
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +56 -6
- package/src/resources/extensions/gsd/commands-mcp-status.ts +2 -2
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +11 -4
- package/src/resources/extensions/gsd/db/queries.ts +29 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +267 -142
- package/src/resources/extensions/gsd/guided-flow.ts +128 -2
- package/src/resources/extensions/gsd/health-widget.ts +91 -27
- package/src/resources/extensions/gsd/mcp-bridge.ts +39 -0
- package/src/resources/extensions/gsd/milestone-settlement.ts +2 -2
- package/src/resources/extensions/gsd/notifications.ts +13 -6
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +2 -0
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -1
- package/src/resources/extensions/gsd/skill-activation.ts +3 -6
- package/src/resources/extensions/gsd/state.ts +7 -1
- package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-blocked-remediation-message.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +206 -22
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +76 -12
- package/src/resources/extensions/gsd/tests/auto-pause-double-entry-guard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +77 -1
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-unit-closeout.test.ts +169 -1
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +141 -5
- package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/dispatch-history.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/dist-redirect.mjs +8 -0
- package/src/resources/extensions/gsd/tests/engine-interfaces-contract.test.ts +117 -91
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-environment-async.test.ts +104 -0
- package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +47 -16
- package/src/resources/extensions/gsd/tests/mcp-readiness-preflight.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/milestone-report-path.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/milestone-settlement.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/notifications.test.ts +64 -9
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/phases-terminal-complete-idempotent.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/plan-gate-failed-doctor-heal-hint.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +10 -2
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +2 -4
- package/src/resources/extensions/gsd/tests/remote-notification-from-desktop.test.ts +31 -81
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +20 -17
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +7 -3
- package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/tool-surface-readiness.test.ts +184 -10
- package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp-readiness-cache.test.ts +119 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +65 -2
- package/src/resources/extensions/gsd/tests/workflow-phase-contract-matrix.test.ts +332 -0
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-project-root-degrade.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-safety-phase.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +72 -0
- package/src/resources/extensions/gsd/tool-surface-readiness.ts +126 -19
- package/src/resources/extensions/gsd/tools/complete-task.ts +87 -0
- package/src/resources/extensions/gsd/unit-context-composer.ts +1 -1
- package/src/resources/extensions/gsd/unit-registry.ts +34 -4
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -0
- package/src/resources/extensions/gsd/workflow-mcp-readiness-cache.ts +150 -0
- package/src/resources/extensions/gsd/worktree-safety.ts +41 -39
- package/src/resources/extensions/mcp-client/manager.ts +7 -1
- /package/dist/web/standalone/.next/static/{FBNo5cT_chy7YNoAQsU3o → xyMkEaICFHJoa98VgJyzY}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{FBNo5cT_chy7YNoAQsU3o → xyMkEaICFHJoa98VgJyzY}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Auto-loop finalize phase — post-unit verification and UAT pause.
|
|
3
|
+
import { clearCurrentPhase } from "../../shared/gsd-phase-state.js";
|
|
4
|
+
import { withTimeout, FINALIZE_PRE_TIMEOUT_MS, FINALIZE_POST_TIMEOUT_MS } from "./finalize-timeout.js";
|
|
5
|
+
import { writeUnitRuntimeRecord } from "../unit-runtime.js";
|
|
6
|
+
import { buildManualValidationGuidance } from "../worktree-manager.js";
|
|
7
|
+
import { relSliceFile } from "../paths.js";
|
|
8
|
+
import { detectRootWriteLeak, formatRootWriteLeakMessage, } from "../root-write-leak-guard.js";
|
|
9
|
+
import { drainLogs, drainAndSummarize, formatForNotification, hasAnyIssues, } from "../workflow-logger.js";
|
|
10
|
+
import { debugLog } from "../debug-logger.js";
|
|
11
|
+
import { buildPhaseHandoffOutcome, setAutoOutcomeWidget } from "../auto-dashboard.js";
|
|
12
|
+
import { applyVerificationRetryPolicy, rememberRetryDispatch, isIsolatedWorktreeSession, } from "./phase-helpers.js";
|
|
13
|
+
import { _runMilestoneMergeOnceWithStashRestore } from "./closeout.js";
|
|
14
|
+
import { MAX_FINALIZE_TIMEOUTS } from "./types.js";
|
|
15
|
+
export async function failClosedOnFinalizeTimeout(ic, iterData, loopState, stage, startedAt) {
|
|
16
|
+
const { ctx, pi, s, deps } = ic;
|
|
17
|
+
const now = Date.now();
|
|
18
|
+
const unitType = iterData.unitType;
|
|
19
|
+
const unitId = iterData.unitId;
|
|
20
|
+
const timeoutMs = stage === "pre" ? FINALIZE_PRE_TIMEOUT_MS : FINALIZE_POST_TIMEOUT_MS;
|
|
21
|
+
const progressKind = stage === "pre" ? "finalize-pre-timeout" : "finalize-post-timeout";
|
|
22
|
+
writeUnitRuntimeRecord(s.basePath, unitType, unitId, startedAt, {
|
|
23
|
+
phase: "finalize-timeout",
|
|
24
|
+
timeoutAt: now,
|
|
25
|
+
lastProgressAt: now,
|
|
26
|
+
lastProgressKind: progressKind,
|
|
27
|
+
});
|
|
28
|
+
deps.emitJournalEvent({
|
|
29
|
+
ts: new Date(now).toISOString(),
|
|
30
|
+
flowId: ic.flowId,
|
|
31
|
+
seq: ic.nextSeq(),
|
|
32
|
+
eventType: "unit-end",
|
|
33
|
+
data: {
|
|
34
|
+
unitType,
|
|
35
|
+
unitId,
|
|
36
|
+
status: "timed-out-finalize",
|
|
37
|
+
artifactVerified: false,
|
|
38
|
+
finalizeStage: stage,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
loopState.consecutiveFinalizeTimeouts++;
|
|
42
|
+
debugLog("autoLoop", {
|
|
43
|
+
phase: progressKind,
|
|
44
|
+
iteration: ic.iteration,
|
|
45
|
+
unitType,
|
|
46
|
+
unitId,
|
|
47
|
+
consecutiveTimeouts: loopState.consecutiveFinalizeTimeouts,
|
|
48
|
+
});
|
|
49
|
+
ctx.ui.notify(`${stage === "pre" ? "postUnitPreVerification" : "postUnitPostVerification"} timed out after ${timeoutMs / 1000}s for ${unitType} ${unitId} (${loopState.consecutiveFinalizeTimeouts}/${MAX_FINALIZE_TIMEOUTS}) — pausing auto-mode for recovery.`, "warning");
|
|
50
|
+
await deps.pauseAuto(ctx, pi);
|
|
51
|
+
s.clearCurrentUnit();
|
|
52
|
+
clearCurrentPhase();
|
|
53
|
+
drainLogs();
|
|
54
|
+
return { action: "break", reason: progressKind };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Phase 5: Post-unit finalize — pre/post verification, UAT pause, step-wizard.
|
|
58
|
+
* Returns break/continue/next to control the outer loop.
|
|
59
|
+
*/
|
|
60
|
+
export async function runFinalize(ic, iterData, loopState, sidecarItem) {
|
|
61
|
+
const { ctx, pi, s, deps } = ic;
|
|
62
|
+
const { pauseAfterUatDispatch } = iterData;
|
|
63
|
+
debugLog("autoLoop", { phase: "finalize", iteration: ic.iteration });
|
|
64
|
+
// Clear unit timeout (unit completed)
|
|
65
|
+
deps.clearUnitTimeout();
|
|
66
|
+
// Post-unit context for pre/post verification
|
|
67
|
+
const postUnitCtx = {
|
|
68
|
+
s,
|
|
69
|
+
ctx,
|
|
70
|
+
pi,
|
|
71
|
+
buildSnapshotOpts: deps.buildSnapshotOpts,
|
|
72
|
+
lockBase: deps.lockBase,
|
|
73
|
+
stopAuto: deps.stopAuto,
|
|
74
|
+
pauseAuto: deps.pauseAuto,
|
|
75
|
+
updateProgressWidget: deps.updateProgressWidget,
|
|
76
|
+
};
|
|
77
|
+
// Pre-verification processing (commit, doctor, state rebuild, etc.)
|
|
78
|
+
// Timeout guard: if postUnitPreVerification hangs (e.g., safety harness
|
|
79
|
+
// deadlock, browser teardown hang, worktree sync stall), force-continue
|
|
80
|
+
// after timeout so the auto-loop is not permanently frozen (#3757).
|
|
81
|
+
//
|
|
82
|
+
// On timeout, null out s.currentUnit so the timed-out task's late async
|
|
83
|
+
// mutations are harmless — postUnitPreVerification guards all side effects
|
|
84
|
+
// behind `if (s.currentUnit)`. The next iteration sets a fresh currentUnit.
|
|
85
|
+
// Sidecar items use lightweight pre-verification opts
|
|
86
|
+
const preVerificationOpts = sidecarItem
|
|
87
|
+
? sidecarItem.kind === "hook"
|
|
88
|
+
? { skipSettleDelay: true, skipWorktreeSync: true, agentEndMessages: s.lastUnitAgentEndMessages ?? undefined }
|
|
89
|
+
: { skipSettleDelay: true, agentEndMessages: s.lastUnitAgentEndMessages ?? undefined }
|
|
90
|
+
: { agentEndMessages: s.lastUnitAgentEndMessages ?? undefined };
|
|
91
|
+
const preUnitSnapshot = s.currentUnit
|
|
92
|
+
? { type: s.currentUnit.type, id: s.currentUnit.id, startedAt: s.currentUnit.startedAt }
|
|
93
|
+
: null;
|
|
94
|
+
const clearFinalizingUnit = () => {
|
|
95
|
+
if (preUnitSnapshot &&
|
|
96
|
+
s.currentUnit?.type === preUnitSnapshot.type &&
|
|
97
|
+
s.currentUnit?.id === preUnitSnapshot.id &&
|
|
98
|
+
s.currentUnit?.startedAt === preUnitSnapshot.startedAt) {
|
|
99
|
+
s.clearCurrentUnit();
|
|
100
|
+
}
|
|
101
|
+
s.rootWriteBaseline = null;
|
|
102
|
+
};
|
|
103
|
+
clearCurrentPhase();
|
|
104
|
+
const preResultGuard = await withTimeout(deps.postUnitPreVerification(postUnitCtx, preVerificationOpts), FINALIZE_PRE_TIMEOUT_MS, "postUnitPreVerification");
|
|
105
|
+
if (preResultGuard.timedOut) {
|
|
106
|
+
return failClosedOnFinalizeTimeout(ic, iterData, loopState, "pre", preUnitSnapshot?.startedAt ?? Date.now());
|
|
107
|
+
}
|
|
108
|
+
const preResult = preResultGuard.value;
|
|
109
|
+
if (preResult === "dispatched") {
|
|
110
|
+
const dispatchedReason = s.lastGitActionFailure
|
|
111
|
+
? "git-closeout-failure"
|
|
112
|
+
: "pre-verification-dispatched";
|
|
113
|
+
debugLog("autoLoop", {
|
|
114
|
+
phase: "exit",
|
|
115
|
+
reason: dispatchedReason,
|
|
116
|
+
gitError: s.lastGitActionFailure ?? undefined,
|
|
117
|
+
});
|
|
118
|
+
clearFinalizingUnit();
|
|
119
|
+
return { action: "break", reason: dispatchedReason };
|
|
120
|
+
}
|
|
121
|
+
if (preResult === "retry") {
|
|
122
|
+
if (sidecarItem) {
|
|
123
|
+
// Sidecar artifact retries are skipped — just continue
|
|
124
|
+
debugLog("autoLoop", { phase: "sidecar-artifact-retry-skipped", iteration: ic.iteration });
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// s.pendingVerificationRetry was set by postUnitPreVerification.
|
|
128
|
+
// Emit a dedicated journal event so forensics can distinguish bounded
|
|
129
|
+
// verification retries from genuine stuck-loop dispatch repetitions (#4540).
|
|
130
|
+
const retryInfo = s.pendingVerificationRetry;
|
|
131
|
+
deps.emitJournalEvent({
|
|
132
|
+
ts: new Date().toISOString(),
|
|
133
|
+
flowId: ic.flowId,
|
|
134
|
+
seq: ic.nextSeq(),
|
|
135
|
+
eventType: "artifact-verification-retry",
|
|
136
|
+
data: {
|
|
137
|
+
unitType: preUnitSnapshot?.type,
|
|
138
|
+
unitId: retryInfo?.unitId,
|
|
139
|
+
attempt: retryInfo?.attempt,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
const retryPolicyResult = await applyVerificationRetryPolicy(ic, preUnitSnapshot?.type, "artifact-verification-retry");
|
|
143
|
+
if (retryPolicyResult) {
|
|
144
|
+
clearFinalizingUnit();
|
|
145
|
+
return retryPolicyResult;
|
|
146
|
+
}
|
|
147
|
+
// Continue the loop — next iteration will inject the retry context into the prompt.
|
|
148
|
+
rememberRetryDispatch(s, preUnitSnapshot, iterData);
|
|
149
|
+
debugLog("autoLoop", { phase: "artifact-verification-retry", iteration: ic.iteration });
|
|
150
|
+
clearFinalizingUnit();
|
|
151
|
+
return { action: "continue" };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (pauseAfterUatDispatch) {
|
|
155
|
+
const pauseMid = iterData.mid;
|
|
156
|
+
const pauseSliceId = pauseMid && iterData.unitId.startsWith(`${pauseMid}/`)
|
|
157
|
+
? iterData.unitId.slice(pauseMid.length + 1)
|
|
158
|
+
: undefined;
|
|
159
|
+
const guidance = pauseMid
|
|
160
|
+
? buildManualValidationGuidance(s.basePath, pauseMid, {
|
|
161
|
+
uatPath: pauseSliceId
|
|
162
|
+
? relSliceFile(s.basePath, pauseMid, pauseSliceId, "UAT")
|
|
163
|
+
: undefined,
|
|
164
|
+
})
|
|
165
|
+
: null;
|
|
166
|
+
const pauseMessage = guidance
|
|
167
|
+
? `UAT requires human execution. Auto-mode will pause after this unit writes the result file.\n\n${guidance}`
|
|
168
|
+
: "UAT requires human execution. Auto-mode will pause after this unit writes the result file.";
|
|
169
|
+
ctx.ui.notify(pauseMessage, "info");
|
|
170
|
+
await deps.pauseAuto(ctx, pi);
|
|
171
|
+
debugLog("autoLoop", { phase: "exit", reason: "uat-pause" });
|
|
172
|
+
clearFinalizingUnit();
|
|
173
|
+
return { action: "break", reason: "uat-pause" };
|
|
174
|
+
}
|
|
175
|
+
// Verification gate
|
|
176
|
+
// Hook sidecar items skip verification entirely.
|
|
177
|
+
// Non-hook sidecar items run verification but skip retries (just continue).
|
|
178
|
+
const skipVerification = sidecarItem?.kind === "hook";
|
|
179
|
+
if (!skipVerification) {
|
|
180
|
+
const verificationResult = await deps.runPostUnitVerification({ s, ctx, pi }, deps.pauseAuto);
|
|
181
|
+
if (verificationResult === "pause") {
|
|
182
|
+
debugLog("autoLoop", { phase: "exit", reason: "verification-pause" });
|
|
183
|
+
clearFinalizingUnit();
|
|
184
|
+
return { action: "break", reason: "verification-pause" };
|
|
185
|
+
}
|
|
186
|
+
if (verificationResult === "retry") {
|
|
187
|
+
if (sidecarItem) {
|
|
188
|
+
// Sidecar verification retries are skipped — just continue
|
|
189
|
+
debugLog("autoLoop", { phase: "sidecar-verification-retry-skipped", iteration: ic.iteration });
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
// s.pendingVerificationRetry was set by runPostUnitVerification.
|
|
193
|
+
const retryPolicyResult = await applyVerificationRetryPolicy(ic, iterData.unitType, "verification-retry");
|
|
194
|
+
if (retryPolicyResult) {
|
|
195
|
+
clearFinalizingUnit();
|
|
196
|
+
return retryPolicyResult;
|
|
197
|
+
}
|
|
198
|
+
// Continue the loop — next iteration will inject the retry context into the prompt.
|
|
199
|
+
rememberRetryDispatch(s, preUnitSnapshot, iterData);
|
|
200
|
+
debugLog("autoLoop", { phase: "verification-retry", iteration: ic.iteration });
|
|
201
|
+
clearFinalizingUnit();
|
|
202
|
+
return { action: "continue" };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Post-verification processing (DB dual-write, hooks, triage, quick-tasks)
|
|
207
|
+
// Timeout guard: if postUnitPostVerification hangs (e.g., module import
|
|
208
|
+
// deadlock, SQLite transaction hang), force-continue after timeout so the
|
|
209
|
+
// auto-loop is not permanently frozen (#2344).
|
|
210
|
+
const postResultGuard = await withTimeout(deps.postUnitPostVerification(postUnitCtx), FINALIZE_POST_TIMEOUT_MS, "postUnitPostVerification");
|
|
211
|
+
if (postResultGuard.timedOut) {
|
|
212
|
+
return failClosedOnFinalizeTimeout(ic, iterData, loopState, "post", preUnitSnapshot?.startedAt ?? Date.now());
|
|
213
|
+
}
|
|
214
|
+
const postResult = postResultGuard.value;
|
|
215
|
+
if (postResult === "retry") {
|
|
216
|
+
if (sidecarItem) {
|
|
217
|
+
debugLog("autoLoop", { phase: "sidecar-pre-execution-retry-skipped", iteration: ic.iteration });
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
const retryInfo = s.pendingVerificationRetry;
|
|
221
|
+
deps.emitJournalEvent({
|
|
222
|
+
ts: new Date().toISOString(),
|
|
223
|
+
flowId: ic.flowId,
|
|
224
|
+
seq: ic.nextSeq(),
|
|
225
|
+
eventType: "pre-execution-retry",
|
|
226
|
+
data: {
|
|
227
|
+
unitType: preUnitSnapshot?.type,
|
|
228
|
+
unitId: retryInfo?.unitId,
|
|
229
|
+
attempt: retryInfo?.attempt,
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
const retryPolicyResult = await applyVerificationRetryPolicy(ic, preUnitSnapshot?.type, "pre-execution-retry");
|
|
233
|
+
if (retryPolicyResult) {
|
|
234
|
+
clearFinalizingUnit();
|
|
235
|
+
return retryPolicyResult;
|
|
236
|
+
}
|
|
237
|
+
rememberRetryDispatch(s, preUnitSnapshot, iterData);
|
|
238
|
+
debugLog("autoLoop", {
|
|
239
|
+
phase: "pre-execution-retry",
|
|
240
|
+
iteration: ic.iteration,
|
|
241
|
+
unitType: preUnitSnapshot?.type,
|
|
242
|
+
unitId: retryInfo?.unitId,
|
|
243
|
+
attempt: retryInfo?.attempt,
|
|
244
|
+
});
|
|
245
|
+
clearFinalizingUnit();
|
|
246
|
+
return { action: "continue" };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (postResult === "stopped") {
|
|
250
|
+
debugLog("autoLoop", {
|
|
251
|
+
phase: "exit",
|
|
252
|
+
reason: "post-verification-stopped",
|
|
253
|
+
});
|
|
254
|
+
clearFinalizingUnit();
|
|
255
|
+
return { action: "break", reason: "post-verification-stopped" };
|
|
256
|
+
}
|
|
257
|
+
if (postResult === "step-wizard") {
|
|
258
|
+
// Step mode — exit the loop (caller handles wizard)
|
|
259
|
+
debugLog("autoLoop", { phase: "exit", reason: "step-wizard" });
|
|
260
|
+
clearFinalizingUnit();
|
|
261
|
+
return { action: "break", reason: "step-wizard" };
|
|
262
|
+
}
|
|
263
|
+
if (preUnitSnapshot && isIsolatedWorktreeSession(s)) {
|
|
264
|
+
const leak = detectRootWriteLeak({
|
|
265
|
+
rootPath: s.originalBasePath,
|
|
266
|
+
worktreePath: s.basePath,
|
|
267
|
+
unitType: preUnitSnapshot.type,
|
|
268
|
+
unitId: preUnitSnapshot.id,
|
|
269
|
+
before: s.rootWriteBaseline,
|
|
270
|
+
});
|
|
271
|
+
s.rootWriteBaseline = null;
|
|
272
|
+
if (leak) {
|
|
273
|
+
const message = formatRootWriteLeakMessage(leak);
|
|
274
|
+
debugLog("autoLoop", {
|
|
275
|
+
phase: "root-write-leak",
|
|
276
|
+
unitType: preUnitSnapshot.type,
|
|
277
|
+
unitId: preUnitSnapshot.id,
|
|
278
|
+
rootPath: leak.rootPath,
|
|
279
|
+
worktreePath: leak.worktreePath,
|
|
280
|
+
files: leak.files.map((file) => ({ path: file.path, status: file.status })),
|
|
281
|
+
});
|
|
282
|
+
ctx.ui.notify(message, "error");
|
|
283
|
+
await deps.stopAuto(ctx, pi, "Root-write leak during isolated auto-mode", {
|
|
284
|
+
preserveCompletedMilestoneBranch: true,
|
|
285
|
+
});
|
|
286
|
+
clearFinalizingUnit();
|
|
287
|
+
return { action: "break", reason: "root-write-leak" };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
s.rootWriteBaseline = null;
|
|
292
|
+
}
|
|
293
|
+
if (preUnitSnapshot?.type === "complete-milestone" && s.currentMilestoneId) {
|
|
294
|
+
const stop = await _runMilestoneMergeOnceWithStashRestore(ic, s.currentMilestoneId, {
|
|
295
|
+
preserveCloseoutTranscript: true,
|
|
296
|
+
});
|
|
297
|
+
if (stop) {
|
|
298
|
+
clearFinalizingUnit();
|
|
299
|
+
return stop;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// Both pre and post verification completed without timeout — reset counter
|
|
303
|
+
loopState.consecutiveFinalizeTimeouts = 0;
|
|
304
|
+
if (preUnitSnapshot) {
|
|
305
|
+
writeUnitRuntimeRecord(s.basePath, preUnitSnapshot.type, preUnitSnapshot.id, preUnitSnapshot.startedAt, {
|
|
306
|
+
phase: "finalized",
|
|
307
|
+
lastProgressAt: Date.now(),
|
|
308
|
+
lastProgressKind: "finalize-success",
|
|
309
|
+
});
|
|
310
|
+
if (!preUnitSnapshot.type.startsWith("hook/") &&
|
|
311
|
+
preUnitSnapshot.type !== "custom-step" &&
|
|
312
|
+
preUnitSnapshot.type !== "complete-milestone") {
|
|
313
|
+
setAutoOutcomeWidget(ctx, {
|
|
314
|
+
...buildPhaseHandoffOutcome({
|
|
315
|
+
unitType: preUnitSnapshot.type,
|
|
316
|
+
unitId: preUnitSnapshot.id,
|
|
317
|
+
agentEndMessages: s.lastUnitAgentEndMessages,
|
|
318
|
+
}),
|
|
319
|
+
startedAt: s.autoStartTime,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
clearFinalizingUnit();
|
|
324
|
+
// Surface accumulated workflow-logger issues for this unit to the user.
|
|
325
|
+
// Warnings/errors logged during the unit are buffered in the logger and
|
|
326
|
+
// drained here so the user sees a single consolidated post-unit alert.
|
|
327
|
+
if (hasAnyIssues()) {
|
|
328
|
+
const { logs } = drainAndSummarize();
|
|
329
|
+
if (logs.length > 0) {
|
|
330
|
+
const severity = logs.some((e) => e.severity === "error") ? "error" : "warning";
|
|
331
|
+
ctx.ui.notify(formatForNotification(logs), severity);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (preUnitSnapshot?.type === "complete-milestone" && s.currentMilestoneId) {
|
|
335
|
+
// cleanupAfterLoopExit skips gsd-progress when preserveCompletionSurface is true, so clear stale controls here.
|
|
336
|
+
ctx.ui.setStatus?.("gsd-step", undefined);
|
|
337
|
+
ctx.ui.setWidget?.("gsd-progress", undefined);
|
|
338
|
+
await deps.stopAuto(ctx, pi, `Milestone ${s.currentMilestoneId} complete`, {
|
|
339
|
+
completionWidget: {
|
|
340
|
+
milestoneId: s.currentMilestoneId,
|
|
341
|
+
milestoneTitle: iterData.midTitle,
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
return { action: "break", reason: "milestone-complete" };
|
|
345
|
+
}
|
|
346
|
+
return { action: "next", data: undefined };
|
|
347
|
+
}
|
|
@@ -13,7 +13,9 @@ import { mkdirSync, writeFileSync } from "node:fs";
|
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { MAX_LOOP_ITERATIONS, } from "./types.js";
|
|
15
15
|
import { _clearCurrentResolve } from "./resolve.js";
|
|
16
|
-
import { runGuards
|
|
16
|
+
import { runGuards } from "./phases.js";
|
|
17
|
+
import { runFinalize } from "./finalize.js";
|
|
18
|
+
import { resetSessionTimeoutState } from "./unit-phase.js";
|
|
17
19
|
import { STUCK_WINDOW_SIZE } from "./dispatch-history.js";
|
|
18
20
|
import { debugLog } from "../debug-logger.js";
|
|
19
21
|
import { isInfrastructureError, isTransientCooldownError, getCooldownRetryAfterMs, COOLDOWN_FALLBACK_WAIT_MS, MAX_COOLDOWN_RETRIES } from "./infra-errors.js";
|
|
@@ -274,6 +276,7 @@ function closeOutCrashedUnit(s, iterData, err) {
|
|
|
274
276
|
*/
|
|
275
277
|
export async function autoLoop(ctx, pi, s, deps, options) {
|
|
276
278
|
debugLog("autoLoop", { phase: "enter" });
|
|
279
|
+
resetSessionTimeoutState();
|
|
277
280
|
let iteration = 0;
|
|
278
281
|
const dispatchContract = options?.dispatchContract ?? "legacy-direct";
|
|
279
282
|
const unitDispatchDeps = createExecutionGraphUnitDispatchDeps();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Recover missing milestone lease state for resumed isolated workers.
|
|
3
|
+
import { debugLog } from "../debug-logger.js";
|
|
4
|
+
import { claimMilestoneLease } from "../db/milestone-leases.js";
|
|
5
|
+
export function hasHeldMilestoneLease(session, milestoneId) {
|
|
6
|
+
return (Boolean(milestoneId) &&
|
|
7
|
+
session.currentMilestoneId === milestoneId &&
|
|
8
|
+
typeof session.milestoneLeaseToken === "number");
|
|
9
|
+
}
|
|
10
|
+
export function reclaimMissingMilestoneLease(session, milestoneId, isolationMode, phase) {
|
|
11
|
+
if (isolationMode === "none")
|
|
12
|
+
return;
|
|
13
|
+
if (!session.workerId || !milestoneId)
|
|
14
|
+
return;
|
|
15
|
+
if (hasHeldMilestoneLease(session, milestoneId))
|
|
16
|
+
return;
|
|
17
|
+
// Note: we intentionally do NOT bail just because the session already holds a
|
|
18
|
+
// lease for a *different* milestone. When dispatch advances to a new
|
|
19
|
+
// milestone the session's stale `currentMilestoneId`/token are for the prior
|
|
20
|
+
// one, and the active milestone's lease must still be claimed — otherwise
|
|
21
|
+
// worktree safety sees `held: false` and fails dispatch (#760). Claiming is
|
|
22
|
+
// safe: claimMilestoneLease refuses to steal a lease another worker holds.
|
|
23
|
+
try {
|
|
24
|
+
const claim = claimMilestoneLease(session.workerId, milestoneId);
|
|
25
|
+
if (claim.ok) {
|
|
26
|
+
session.currentMilestoneId = milestoneId;
|
|
27
|
+
session.milestoneLeaseToken = claim.token;
|
|
28
|
+
debugLog("worktreeSafety", {
|
|
29
|
+
phase: "lease-reclaimed",
|
|
30
|
+
source: phase,
|
|
31
|
+
milestoneId,
|
|
32
|
+
workerId: session.workerId,
|
|
33
|
+
token: claim.token,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
debugLog("worktreeSafety", {
|
|
38
|
+
phase: "lease-reclaim-blocked",
|
|
39
|
+
source: phase,
|
|
40
|
+
milestoneId,
|
|
41
|
+
workerId: session.workerId,
|
|
42
|
+
holderWorkerId: claim.byWorker,
|
|
43
|
+
expiresAt: claim.expiresAt,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
debugLog("worktreeSafety", {
|
|
49
|
+
phase: "lease-reclaim-failed",
|
|
50
|
+
source: phase,
|
|
51
|
+
milestoneId,
|
|
52
|
+
workerId: session.workerId,
|
|
53
|
+
error: err instanceof Error ? err.message : String(err),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -47,6 +47,7 @@ import { buildDispatchKey, createDispatchHistory, STUCK_WINDOW_SIZE, } from "./d
|
|
|
47
47
|
import { existsSync, readFileSync } from "node:fs";
|
|
48
48
|
import { join } from "node:path";
|
|
49
49
|
import { evaluateAllCompleteSettlement } from "../milestone-settlement.js";
|
|
50
|
+
import { hasHeldMilestoneLease, reclaimMissingMilestoneLease } from "./milestone-lease-reclaim.js";
|
|
50
51
|
function now() {
|
|
51
52
|
return Date.now();
|
|
52
53
|
}
|
|
@@ -127,6 +128,7 @@ export async function decideOrchestratorDispatch(ctx, pi, dispatchBasePath, sess
|
|
|
127
128
|
}
|
|
128
129
|
if (active && activeSession && shouldAdoptActiveMilestone(state, activeSession, activeDispatchBasePath)) {
|
|
129
130
|
activeSession.currentMilestoneId = active.id;
|
|
131
|
+
activeSession.milestoneLeaseToken = null;
|
|
130
132
|
}
|
|
131
133
|
const dispatchMid = active?.id ?? activeSession?.currentMilestoneId ?? "";
|
|
132
134
|
const dispatchMidTitle = active?.title ?? "";
|
|
@@ -476,6 +478,29 @@ export class AutoOrchestrator {
|
|
|
476
478
|
},
|
|
477
479
|
};
|
|
478
480
|
}
|
|
481
|
+
async mergePendingCompleteMilestone(milestoneId) {
|
|
482
|
+
const result = this.buildLifecycle().exitMilestone(milestoneId, { merge: true }, this.ctx.ui);
|
|
483
|
+
if (!result.ok) {
|
|
484
|
+
const detail = result.cause instanceof Error
|
|
485
|
+
? result.cause.message
|
|
486
|
+
: result.reason;
|
|
487
|
+
return {
|
|
488
|
+
ok: false,
|
|
489
|
+
reason: `Milestone ${milestoneId} is complete, but the system-owned merge failed: ${detail}`,
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
this.s.milestoneMergedInPhases = true;
|
|
493
|
+
this.s.milestoneSettlement = { ok: true, reason: "settled" };
|
|
494
|
+
try {
|
|
495
|
+
const projectRoot = this.s.originalBasePath || this.s.canonicalProjectRoot || this.runtimeBasePath;
|
|
496
|
+
const { rebuildMarkdownProjectionsFromDb } = await import("../commands-maintenance.js");
|
|
497
|
+
await rebuildMarkdownProjectionsFromDb(projectRoot);
|
|
498
|
+
}
|
|
499
|
+
catch (err) {
|
|
500
|
+
logWarning("engine", `markdown projection rebuild after settlement merge failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
501
|
+
}
|
|
502
|
+
return { ok: true };
|
|
503
|
+
}
|
|
479
504
|
clearPendingDispatch() {
|
|
480
505
|
this.s.pendingOrchestrationDispatch = null;
|
|
481
506
|
}
|
|
@@ -536,9 +561,6 @@ export class AutoOrchestrator {
|
|
|
536
561
|
reason: `No Unit manifest is registered for ${unitType}`,
|
|
537
562
|
};
|
|
538
563
|
}
|
|
539
|
-
if (isolationMode !== "worktree") {
|
|
540
|
-
return { ok: true, reason: "not-required" };
|
|
541
|
-
}
|
|
542
564
|
const writeScope = manifest.tools.mode === "all" || manifest.tools.mode === "docs"
|
|
543
565
|
? "source-writing"
|
|
544
566
|
: "planning-only";
|
|
@@ -546,7 +568,21 @@ export class AutoOrchestrator {
|
|
|
546
568
|
const activeBasePath = this.getLiveDispatchBasePath();
|
|
547
569
|
const snapshot = await deriveState(activeBasePath);
|
|
548
570
|
const milestoneId = snapshot.activeMilestone?.id ?? null;
|
|
549
|
-
const
|
|
571
|
+
const buildExpectedBranch = (mode) => mode !== "none" && milestoneId ? autoWorktreeBranch(milestoneId) : null;
|
|
572
|
+
// The milestone lease coordinates concurrent workers on an isolated
|
|
573
|
+
// milestone worktree/branch. `none` mode has no per-milestone isolation
|
|
574
|
+
// and does not reliably claim a lease, so requiring one there would
|
|
575
|
+
// falsely fail dispatch; enforce it only in isolated modes.
|
|
576
|
+
const buildLease = (mode) => milestoneId && this.s.workerId
|
|
577
|
+
? {
|
|
578
|
+
required: writeScope === "source-writing" && mode !== "none",
|
|
579
|
+
held: hasHeldMilestoneLease(this.s, milestoneId),
|
|
580
|
+
owner: this.s.workerId,
|
|
581
|
+
}
|
|
582
|
+
: undefined;
|
|
583
|
+
if (writeScope === "source-writing") {
|
|
584
|
+
reclaimMissingMilestoneLease(this.s, milestoneId, isolationMode, "orchestrator");
|
|
585
|
+
}
|
|
550
586
|
let result = safety.validateUnitRoot({
|
|
551
587
|
unitType,
|
|
552
588
|
unitId,
|
|
@@ -555,7 +591,8 @@ export class AutoOrchestrator {
|
|
|
555
591
|
unitRoot: activeBasePath,
|
|
556
592
|
milestoneId,
|
|
557
593
|
isolationMode,
|
|
558
|
-
expectedBranch,
|
|
594
|
+
expectedBranch: buildExpectedBranch(isolationMode),
|
|
595
|
+
lease: buildLease(isolationMode),
|
|
559
596
|
});
|
|
560
597
|
if (!result.ok) {
|
|
561
598
|
const repaired = await repairAutoWorktreeSafetyFailure({
|
|
@@ -573,16 +610,20 @@ export class AutoOrchestrator {
|
|
|
573
610
|
this.rebuildScope(this.s.basePath, this.s.currentMilestoneId);
|
|
574
611
|
return { ok: true };
|
|
575
612
|
},
|
|
576
|
-
revalidate: () =>
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
613
|
+
revalidate: () => {
|
|
614
|
+
const revalidatedMode = this.getEffectiveUnitIsolationMode(this.runtimeBasePath);
|
|
615
|
+
return safety.validateUnitRoot({
|
|
616
|
+
unitType,
|
|
617
|
+
unitId,
|
|
618
|
+
writeScope,
|
|
619
|
+
projectRoot: this.runtimeBasePath,
|
|
620
|
+
unitRoot: this.getLiveDispatchBasePath(),
|
|
621
|
+
milestoneId,
|
|
622
|
+
isolationMode: revalidatedMode,
|
|
623
|
+
expectedBranch: buildExpectedBranch(revalidatedMode),
|
|
624
|
+
lease: buildLease(revalidatedMode),
|
|
625
|
+
});
|
|
626
|
+
},
|
|
586
627
|
});
|
|
587
628
|
result = repaired.result;
|
|
588
629
|
if (result.ok) {
|
|
@@ -775,6 +816,35 @@ export class AutoOrchestrator {
|
|
|
775
816
|
if (!decision) {
|
|
776
817
|
const settlementBlock = this.evaluateNoRemainingUnitsSettlement(reconciliation.stateSnapshot);
|
|
777
818
|
if (settlementBlock) {
|
|
819
|
+
const settlement = this.s.milestoneSettlement;
|
|
820
|
+
if (settlement && !settlement.ok && settlement.reason === "merge-pending") {
|
|
821
|
+
const merged = await this.mergePendingCompleteMilestone(settlement.milestoneId);
|
|
822
|
+
if (merged.ok) {
|
|
823
|
+
const terminalOutcome = noRemainingUnitsOutcome(reconciliation.stateSnapshot);
|
|
824
|
+
const stopped = {
|
|
825
|
+
kind: "stopped",
|
|
826
|
+
reason: terminalOutcome.displayReason,
|
|
827
|
+
stateSnapshot: reconciliation.stateSnapshot,
|
|
828
|
+
terminalOutcome,
|
|
829
|
+
};
|
|
830
|
+
this.status.phase = "stopped";
|
|
831
|
+
this.status.activeUnit = undefined;
|
|
832
|
+
this.lastAdvanceKey = null;
|
|
833
|
+
this.dispatchHistory.clearOnRecovery();
|
|
834
|
+
this.bumpTransition();
|
|
835
|
+
this.journalTransition({ name: "advance-stopped", reason: stopped.reason });
|
|
836
|
+
this.postAdvanceRecord(stopped);
|
|
837
|
+
return stopped;
|
|
838
|
+
}
|
|
839
|
+
settlementBlock.reason = merged.reason;
|
|
840
|
+
settlementBlock.terminalOutcome = {
|
|
841
|
+
code: "settlement-blocked",
|
|
842
|
+
displayReason: merged.reason,
|
|
843
|
+
nextAction: `Fix the merge failure, then retry \`/gsd dispatch complete-milestone ${settlement.milestoneId}\`.`,
|
|
844
|
+
milestoneId: settlement.milestoneId,
|
|
845
|
+
allMilestonesComplete: false,
|
|
846
|
+
};
|
|
847
|
+
}
|
|
778
848
|
this.status.phase = "paused";
|
|
779
849
|
this.status.activeUnit = undefined;
|
|
780
850
|
this.lastAdvanceKey = null;
|