gsd-pi 2.78.1-dev.e9d88a536 → 2.78.1-dev.eccf86e27
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 +5 -7
- package/dist/help-text.js +1 -1
- package/dist/resource-loader.js +6 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/detect-stuck.js +41 -5
- package/dist/resources/extensions/gsd/auto/loop.js +235 -36
- package/dist/resources/extensions/gsd/auto/phases.js +14 -7
- package/dist/resources/extensions/gsd/auto/session.js +36 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +49 -4
- package/dist/resources/extensions/gsd/auto-post-unit.js +26 -12
- package/dist/resources/extensions/gsd/auto-worktree.js +185 -201
- package/dist/resources/extensions/gsd/auto.js +139 -49
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +26 -20
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
- package/dist/resources/extensions/gsd/crash-recovery.js +160 -47
- package/dist/resources/extensions/gsd/db/auto-workers.js +227 -0
- package/dist/resources/extensions/gsd/db/command-queue.js +105 -0
- package/dist/resources/extensions/gsd/db/milestone-leases.js +210 -0
- package/dist/resources/extensions/gsd/db/runtime-kv.js +91 -0
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +322 -0
- package/dist/resources/extensions/gsd/db-writer.js +96 -16
- package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
- package/dist/resources/extensions/gsd/docs/COORDINATION.md +42 -0
- package/dist/resources/extensions/gsd/doctor-proactive.js +4 -0
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +22 -6
- package/dist/resources/extensions/gsd/doctor.js +12 -2
- package/dist/resources/extensions/gsd/gsd-db.js +355 -3
- package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
- package/dist/resources/extensions/gsd/guided-flow.js +116 -26
- package/dist/resources/extensions/gsd/interrupted-session.js +18 -15
- package/dist/resources/extensions/gsd/metrics.js +287 -1
- package/dist/resources/extensions/gsd/paths.js +79 -8
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/dist/resources/extensions/gsd/state.js +21 -6
- package/dist/resources/extensions/gsd/templates/project.md +10 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
- package/dist/resources/extensions/gsd/workspace.js +59 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +79 -2
- package/dist/resources/extensions/gsd/write-intercept.js +3 -3
- 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 +14 -14
- 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/required-server-files.json +1 -1
- 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 +14 -14
- 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/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/README.md +2 -11
- package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
- package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
- package/packages/mcp-server/dist/remote-questions.js +28 -0
- package/packages/mcp-server/dist/remote-questions.js.map +1 -1
- package/packages/mcp-server/dist/server.d.ts +28 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +94 -4
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/mcp-server.test.ts +226 -0
- package/packages/mcp-server/src/remote-questions.test.ts +103 -0
- package/packages/mcp-server/src/remote-questions.ts +35 -0
- package/packages/mcp-server/src/server.ts +129 -6
- package/packages/mcp-server/src/workflow-tools.ts +1 -1
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/detect-stuck.ts +37 -5
- package/src/resources/extensions/gsd/auto/loop.ts +263 -41
- package/src/resources/extensions/gsd/auto/phases.ts +15 -7
- package/src/resources/extensions/gsd/auto/session.ts +40 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +63 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +27 -12
- package/src/resources/extensions/gsd/auto-worktree.ts +218 -225
- package/src/resources/extensions/gsd/auto.ts +166 -43
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +26 -21
- package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
- package/src/resources/extensions/gsd/crash-recovery.ts +177 -43
- package/src/resources/extensions/gsd/db/auto-workers.ts +273 -0
- package/src/resources/extensions/gsd/db/command-queue.ts +149 -0
- package/src/resources/extensions/gsd/db/milestone-leases.ts +274 -0
- package/src/resources/extensions/gsd/db/runtime-kv.ts +127 -0
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +446 -0
- package/src/resources/extensions/gsd/db-writer.ts +113 -17
- package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
- package/src/resources/extensions/gsd/docs/COORDINATION.md +42 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +4 -0
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +24 -6
- package/src/resources/extensions/gsd/doctor.ts +10 -2
- package/src/resources/extensions/gsd/gsd-db.ts +354 -3
- package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
- package/src/resources/extensions/gsd/guided-flow.ts +152 -26
- package/src/resources/extensions/gsd/interrupted-session.ts +19 -12
- package/src/resources/extensions/gsd/metrics.ts +321 -1
- package/src/resources/extensions/gsd/paths.ts +67 -8
- package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/src/resources/extensions/gsd/state.ts +44 -6
- package/src/resources/extensions/gsd/templates/project.md +10 -0
- package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
- package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +72 -0
- package/src/resources/extensions/gsd/tests/auto-loop-symlink-worktree.test.ts +190 -0
- package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
- package/src/resources/extensions/gsd/tests/auto-workers.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
- package/src/resources/extensions/gsd/tests/command-queue.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +169 -59
- package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
- package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
- package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/detect-stuck-respects-retry.test.ts +173 -0
- package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
- package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
- package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +22 -12
- package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +24 -10
- package/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts +35 -23
- package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +369 -0
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +72 -25
- package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +72 -25
- package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +9 -6
- package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
- package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
- package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
- package/src/resources/extensions/gsd/tests/milestone-leases.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
- package/src/resources/extensions/gsd/tests/parallel-milestone-isolation.test.ts +106 -0
- package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +119 -0
- package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +3 -17
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +138 -16
- package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/runtime-kv.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +133 -28
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +434 -0
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
- package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +247 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +41 -1
- package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/workspace.test.ts +196 -0
- package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +94 -71
- package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
- package/src/resources/extensions/gsd/workspace.ts +95 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +78 -2
- package/src/resources/extensions/gsd/write-intercept.ts +3 -3
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +0 -213
- package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +0 -87
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +0 -159
- /package/dist/web/standalone/.next/static/{oZGTPvJBQX_IDKKnuV8Bt → Y5UeGFkXTYM9WIQOWHkot}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{oZGTPvJBQX_IDKKnuV8Bt → Y5UeGFkXTYM9WIQOWHkot}/_ssgManifest.js +0 -0
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { deriveState } from "./state.js";
|
|
13
13
|
import { parseUnitId } from "./unit-id.js";
|
|
14
|
-
import { assessInterruptedSession, readPausedSessionMetadata, } from "./interrupted-session.js";
|
|
14
|
+
import { assessInterruptedSession, readPausedSessionMetadata, PAUSED_SESSION_KV_KEY, } from "./interrupted-session.js";
|
|
15
|
+
import { setRuntimeKv, deleteRuntimeKv, } from "./db/runtime-kv.js";
|
|
15
16
|
import { getManifestStatus } from "./files.js";
|
|
16
17
|
export { inlinePriorMilestoneSummary } from "./files.js";
|
|
17
18
|
import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
|
|
@@ -40,7 +41,7 @@ import { setLogBasePath, logWarning } from "./workflow-logger.js";
|
|
|
40
41
|
import { preflightCleanRoot, postflightPopStash } from "./clean-root-preflight.js";
|
|
41
42
|
import { isAbsolute, join } from "node:path";
|
|
42
43
|
import { pathToFileURL } from "node:url";
|
|
43
|
-
import { readFileSync, existsSync, mkdirSync
|
|
44
|
+
import { readFileSync, existsSync, mkdirSync } from "node:fs";
|
|
44
45
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
45
46
|
import { autoCommitCurrentBranch, captureIntegrationBranch, detectWorktreeName, getCurrentBranch, getMainBranch, setActiveMilestoneId, } from "./worktree.js";
|
|
46
47
|
import { GitServiceImpl } from "./git-service.js";
|
|
@@ -86,6 +87,10 @@ import { reorderForCaching } from "./prompt-ordering.js";
|
|
|
86
87
|
export { STUB_RECOVERY_THRESHOLD, NEW_SESSION_TIMEOUT_MS, } from "./auto/session.js";
|
|
87
88
|
import { autoSession as s } from "./auto-runtime-state.js";
|
|
88
89
|
import { gsdHome } from "./gsd-home.js";
|
|
90
|
+
import { createWorkspace, scopeMilestone } from "./workspace.js";
|
|
91
|
+
import { registerAutoWorker, markWorkerStopping } from "./db/auto-workers.js";
|
|
92
|
+
import { releaseMilestoneLease } from "./db/milestone-leases.js";
|
|
93
|
+
import { normalizeRealPath } from "./paths.js";
|
|
89
94
|
// ── ENCAPSULATION INVARIANT ─────────────────────────────────────────────────
|
|
90
95
|
// ALL mutable auto-mode state lives in the AutoSession class (auto/session.ts).
|
|
91
96
|
// This file must NOT declare module-level `let` or `var` variables for state.
|
|
@@ -100,6 +105,27 @@ import { gsdHome } from "./gsd-home.js";
|
|
|
100
105
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
101
106
|
/** Throttle STATE.md rebuilds — at most once per 30 seconds */
|
|
102
107
|
const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
|
|
108
|
+
/**
|
|
109
|
+
* Phase B — register this auto-mode process in the workers table so other
|
|
110
|
+
* workers and janitors can detect liveness via heartbeat. Best-effort: if
|
|
111
|
+
* the DB is unavailable (e.g. fresh project before init) we skip registration
|
|
112
|
+
* silently rather than blocking session start.
|
|
113
|
+
*/
|
|
114
|
+
function registerAutoWorkerForSession(session) {
|
|
115
|
+
if (session.workerId)
|
|
116
|
+
return; // already registered (e.g. resume re-runs)
|
|
117
|
+
try {
|
|
118
|
+
const projectRootRealpath = normalizeRealPath(session.scope?.workspace.projectRoot
|
|
119
|
+
?? (session.originalBasePath || session.basePath));
|
|
120
|
+
session.workerId = registerAutoWorker({ projectRootRealpath });
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
debugLog("autoLoop", {
|
|
124
|
+
phase: "register-worker-failed",
|
|
125
|
+
error: err instanceof Error ? err.message : String(err),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
103
129
|
function captureProjectRootEnv(projectRoot) {
|
|
104
130
|
if (!s.projectRootEnvCaptured) {
|
|
105
131
|
s.hadProjectRootEnv = Object.prototype.hasOwnProperty.call(process.env, "GSD_PROJECT_ROOT");
|
|
@@ -147,6 +173,32 @@ function restoreMilestoneLockEnv() {
|
|
|
147
173
|
s.hadMilestoneLockEnv = false;
|
|
148
174
|
s.milestoneLockEnvCaptured = false;
|
|
149
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Rebuild s.scope from the current s.basePath / s.originalBasePath / s.currentMilestoneId.
|
|
178
|
+
*
|
|
179
|
+
* Pass the worktree path as rawPath when entering a worktree so createWorkspace
|
|
180
|
+
* can detect the worktree layout and set mode="worktree". When no worktree is
|
|
181
|
+
* active, rawPath should equal the project root.
|
|
182
|
+
*
|
|
183
|
+
* Clears s.scope when milestoneId is absent — scope is only meaningful when a
|
|
184
|
+
* milestone is active.
|
|
185
|
+
*
|
|
186
|
+
* TODO(C8): remove basePath/originalBasePath once all readers use s.scope.
|
|
187
|
+
*/
|
|
188
|
+
function rebuildScope(rawPath, milestoneId) {
|
|
189
|
+
if (!milestoneId) {
|
|
190
|
+
s.scope = null;
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
const workspace = createWorkspace(rawPath);
|
|
195
|
+
s.scope = scopeMilestone(workspace, milestoneId);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// Non-fatal — scope is additive. Existing readers still use basePath.
|
|
199
|
+
s.scope = null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
150
202
|
function normalizeSessionFilePath(raw) {
|
|
151
203
|
if (typeof raw !== "string")
|
|
152
204
|
return null;
|
|
@@ -284,6 +336,18 @@ export function isAutoActive() {
|
|
|
284
336
|
export function _setAutoActiveForTest(active) {
|
|
285
337
|
s.active = active;
|
|
286
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* Test-only seam: emit the missing-worktree warning exactly as the resume path
|
|
341
|
+
* does. Allows unit tests to verify the warning is produced without
|
|
342
|
+
* bootstrapping the full auto-mode entry point. Do not use in production code.
|
|
343
|
+
*/
|
|
344
|
+
export function _warnIfWorktreeMissingForTest(worktreePath, milestoneId) {
|
|
345
|
+
if (worktreePath && !existsSync(worktreePath)) {
|
|
346
|
+
logWarning("session", `Worktree was expected at ${worktreePath} but is missing. Continuing in project-root mode. To restart with a fresh worktree, run /gsd-debug or recreate the milestone.`, { file: "auto.ts", milestoneId });
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
287
351
|
export function isAutoPaused() {
|
|
288
352
|
return s.paused;
|
|
289
353
|
}
|
|
@@ -582,6 +646,21 @@ export async function stopAuto(ctx, pi, reason) {
|
|
|
582
646
|
catch (e) {
|
|
583
647
|
debugLog("stop-cleanup-locks", { error: e instanceof Error ? e.message : String(e) });
|
|
584
648
|
}
|
|
649
|
+
// ── Step 1b: Coordination cleanup (Phase B) ──
|
|
650
|
+
// Release any active milestone lease so other workers don't have to
|
|
651
|
+
// wait for TTL expiry, then mark this worker as stopping. Best-effort:
|
|
652
|
+
// DB unavailability or stale state must not block shutdown.
|
|
653
|
+
try {
|
|
654
|
+
if (s.workerId && s.currentMilestoneId && s.milestoneLeaseToken) {
|
|
655
|
+
releaseMilestoneLease(s.workerId, s.currentMilestoneId, s.milestoneLeaseToken);
|
|
656
|
+
}
|
|
657
|
+
if (s.workerId) {
|
|
658
|
+
markWorkerStopping(s.workerId);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
catch (e) {
|
|
662
|
+
debugLog("stop-cleanup-coordination", { error: e instanceof Error ? e.message : String(e) });
|
|
663
|
+
}
|
|
585
664
|
// ── Step 1b: Flush queued follow-up messages (#3512) ──
|
|
586
665
|
// Late async notifications (async_job_result, gsd-auto-wrapup) can trigger
|
|
587
666
|
// extra LLM turns after stop. Flush them the same way run-unit.ts does.
|
|
@@ -760,13 +839,12 @@ export async function stopAuto(ctx, pi, reason) {
|
|
|
760
839
|
debugLog("stop-cleanup-metrics", { error: e instanceof Error ? e.message : String(e) });
|
|
761
840
|
}
|
|
762
841
|
// ── Step 12: Remove paused-session metadata (#1383) ──
|
|
842
|
+
// Phase C pt 2: deleteRuntimeKv replaces unlinkSync(paused-session.json).
|
|
763
843
|
try {
|
|
764
|
-
|
|
765
|
-
if (existsSync(pausedPath))
|
|
766
|
-
unlinkSync(pausedPath);
|
|
844
|
+
deleteRuntimeKv("global", "", PAUSED_SESSION_KV_KEY);
|
|
767
845
|
}
|
|
768
846
|
catch (err) { /* non-fatal */
|
|
769
|
-
logWarning("engine", `
|
|
847
|
+
logWarning("engine", `paused-session DB delete failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
770
848
|
}
|
|
771
849
|
// ── Step 13: Restore original model + thinking (before reset clears IDs) ──
|
|
772
850
|
try {
|
|
@@ -858,10 +936,12 @@ export async function pauseAuto(ctx, _pi, _errorContext) {
|
|
|
858
936
|
resolveAgentEndCancelled(_errorContext);
|
|
859
937
|
s.pausedSessionFile = normalizeSessionFilePath(ctx?.sessionManager?.getSessionFile() ?? null);
|
|
860
938
|
// Persist paused-session metadata so resume survives /exit (#1383).
|
|
861
|
-
//
|
|
939
|
+
// Phase C pt 2: persisted to runtime_kv (global scope, key
|
|
940
|
+
// PAUSED_SESSION_KV_KEY) instead of runtime/paused-session.json. The
|
|
941
|
+
// fresh-start bootstrap below reads from the same key.
|
|
862
942
|
try {
|
|
863
943
|
const pausedMeta = {
|
|
864
|
-
milestoneId: s.currentMilestoneId,
|
|
944
|
+
milestoneId: s.currentMilestoneId ?? undefined,
|
|
865
945
|
worktreePath: isInAutoWorktree(s.basePath) ? s.basePath : null,
|
|
866
946
|
originalBasePath: s.originalBasePath,
|
|
867
947
|
stepMode: s.stepMode,
|
|
@@ -869,17 +949,16 @@ export async function pauseAuto(ctx, _pi, _errorContext) {
|
|
|
869
949
|
sessionFile: s.pausedSessionFile,
|
|
870
950
|
unitType: s.currentUnit?.type ?? undefined,
|
|
871
951
|
unitId: s.currentUnit?.id ?? undefined,
|
|
872
|
-
activeEngineId: s.activeEngineId,
|
|
952
|
+
activeEngineId: s.activeEngineId ?? undefined,
|
|
873
953
|
activeRunDir: s.activeRunDir,
|
|
874
954
|
autoStartTime: s.autoStartTime,
|
|
875
955
|
milestoneLock: s.sessionMilestoneLock ?? undefined,
|
|
876
956
|
};
|
|
877
|
-
|
|
878
|
-
atomicWriteSync(join(runtimeDir, "paused-session.json"), JSON.stringify(pausedMeta, null, 2), "utf-8");
|
|
957
|
+
setRuntimeKv("global", "", PAUSED_SESSION_KV_KEY, pausedMeta);
|
|
879
958
|
}
|
|
880
959
|
catch (err) {
|
|
881
960
|
// Non-fatal — resume will still work via full bootstrap, just without worktree context
|
|
882
|
-
logWarning("engine", `paused-session
|
|
961
|
+
logWarning("engine", `paused-session DB write failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
883
962
|
}
|
|
884
963
|
// Close out the current unit so its runtime record doesn't stay at "dispatched"
|
|
885
964
|
if (s.currentUnit && ctx) {
|
|
@@ -1115,8 +1194,10 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1115
1194
|
if (recoverFailedMigration(base)) {
|
|
1116
1195
|
ctx.ui.notify("Recovered unfinished migration (.gsd.migrating → .gsd).", "info");
|
|
1117
1196
|
}
|
|
1118
|
-
const freshStartAssessment = interruptedAssessment
|
|
1119
|
-
??
|
|
1197
|
+
const freshStartAssessment = await (interruptedAssessment
|
|
1198
|
+
?? (() => {
|
|
1199
|
+
return ensureDbOpen(base).then(() => assessInterruptedSession(base));
|
|
1200
|
+
})());
|
|
1120
1201
|
if (freshStartAssessment.classification === "running") {
|
|
1121
1202
|
const pid = freshStartAssessment.lock?.pid;
|
|
1122
1203
|
ctx.ui.notify(pid
|
|
@@ -1126,10 +1207,20 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1126
1207
|
}
|
|
1127
1208
|
// If resuming from paused state, just re-activate and dispatch next unit.
|
|
1128
1209
|
// Check persisted paused-session first (#1383) — survives /exit.
|
|
1210
|
+
// Phase C pt 2: persisted in runtime_kv (global scope) instead of
|
|
1211
|
+
// runtime/paused-session.json. The `clearPausedSession` helper
|
|
1212
|
+
// replaces every prior unlinkSync(pausedPath) call.
|
|
1213
|
+
const clearPausedSession = (logTag) => {
|
|
1214
|
+
try {
|
|
1215
|
+
deleteRuntimeKv("global", "", PAUSED_SESSION_KV_KEY);
|
|
1216
|
+
}
|
|
1217
|
+
catch (err) {
|
|
1218
|
+
logWarning("session", `${logTag}: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1129
1221
|
if (!s.paused) {
|
|
1130
1222
|
try {
|
|
1131
1223
|
const meta = freshStartAssessment.pausedSession ?? readPausedSessionMetadata(base);
|
|
1132
|
-
const pausedPath = join(gsdRoot(base), "runtime", "paused-session.json");
|
|
1133
1224
|
if (meta?.activeEngineId && meta.activeEngineId !== "dev") {
|
|
1134
1225
|
// Custom workflow resume — restore engine state
|
|
1135
1226
|
s.activeEngineId = meta.activeEngineId;
|
|
@@ -1139,14 +1230,6 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1139
1230
|
s.autoStartTime = meta.autoStartTime || Date.now();
|
|
1140
1231
|
s.sessionMilestoneLock = meta.milestoneLock ?? null;
|
|
1141
1232
|
s.paused = true;
|
|
1142
|
-
try {
|
|
1143
|
-
unlinkSync(pausedPath);
|
|
1144
|
-
}
|
|
1145
|
-
catch (e) {
|
|
1146
|
-
if (e.code !== "ENOENT") {
|
|
1147
|
-
logWarning("session", `pause file cleanup failed: ${e instanceof Error ? e.message : String(e)}`, { file: "auto.ts" });
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
1233
|
ctx.ui.notify(`Resuming paused custom workflow${meta.activeRunDir ? ` (${meta.activeRunDir})` : ""}.`, "info");
|
|
1151
1234
|
}
|
|
1152
1235
|
else if (meta?.milestoneId) {
|
|
@@ -1184,14 +1267,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1184
1267
|
}
|
|
1185
1268
|
}
|
|
1186
1269
|
if (!mDir || summaryIsTerminal) {
|
|
1187
|
-
|
|
1188
|
-
unlinkSync(pausedPath);
|
|
1189
|
-
}
|
|
1190
|
-
catch (err) {
|
|
1191
|
-
if (err.code !== "ENOENT") {
|
|
1192
|
-
logWarning("session", `pause file cleanup failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1270
|
+
clearPausedSession("paused-session DB cleanup failed (milestone gone/complete)");
|
|
1195
1271
|
ctx.ui.notify(`Paused milestone ${meta.milestoneId} is ${!mDir ? "missing" : "already complete"}. Starting fresh.`, "info");
|
|
1196
1272
|
}
|
|
1197
1273
|
else {
|
|
@@ -1204,26 +1280,25 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1204
1280
|
s.autoStartTime = meta.autoStartTime || Date.now();
|
|
1205
1281
|
s.sessionMilestoneLock = meta.milestoneLock ?? null;
|
|
1206
1282
|
s.paused = true;
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
if (
|
|
1212
|
-
logWarning("session", `
|
|
1283
|
+
// Build scope from persisted state. Use worktreePath when present and
|
|
1284
|
+
// still on disk so mode is detected correctly; fall back to project root.
|
|
1285
|
+
{
|
|
1286
|
+
const persistedWorktreePath = meta.worktreePath ?? null;
|
|
1287
|
+
if (persistedWorktreePath && !existsSync(persistedWorktreePath)) {
|
|
1288
|
+
logWarning("session", `Worktree was expected at ${persistedWorktreePath} but is missing. Continuing in project-root mode. To restart with a fresh worktree, run /gsd-debug or recreate the milestone.`, { file: "auto.ts", milestoneId: meta.milestoneId ?? "" });
|
|
1213
1289
|
}
|
|
1290
|
+
const rawForScope = (persistedWorktreePath && existsSync(persistedWorktreePath))
|
|
1291
|
+
? persistedWorktreePath
|
|
1292
|
+
: (s.originalBasePath || base);
|
|
1293
|
+
rebuildScope(rawForScope, s.currentMilestoneId);
|
|
1214
1294
|
}
|
|
1215
1295
|
ctx.ui.notify(`Resuming paused session for ${meta.milestoneId}${meta.worktreePath && existsSync(meta.worktreePath) ? ` (worktree)` : ""}.`, "info");
|
|
1216
1296
|
}
|
|
1217
1297
|
}
|
|
1218
|
-
else if (
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
catch (e) {
|
|
1223
|
-
if (e.code !== "ENOENT") {
|
|
1224
|
-
logWarning("session", `stale pause file cleanup failed: ${e instanceof Error ? e.message : String(e)}`, { file: "auto.ts" });
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1298
|
+
else if (meta) {
|
|
1299
|
+
// Stale paused-session metadata that the assessment chose not to
|
|
1300
|
+
// resume — clean it up so the next bootstrap starts fresh.
|
|
1301
|
+
clearPausedSession("stale paused-session DB cleanup failed");
|
|
1227
1302
|
}
|
|
1228
1303
|
}
|
|
1229
1304
|
}
|
|
@@ -1285,10 +1360,15 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1285
1360
|
// session (e.g. isolation mode changed, detectWorktreeName differs across
|
|
1286
1361
|
// process restarts). We guard with existsSync so a stale or deleted
|
|
1287
1362
|
// worktree directory safely falls back to the project root.
|
|
1288
|
-
const resumeWorktreePath = freshStartAssessment.pausedSession?.worktreePath;
|
|
1363
|
+
const resumeWorktreePath = freshStartAssessment.pausedSession?.worktreePath ?? null;
|
|
1364
|
+
if (resumeWorktreePath && !existsSync(resumeWorktreePath)) {
|
|
1365
|
+
logWarning("session", `Worktree was expected at ${resumeWorktreePath} but is missing. Continuing in project-root mode. To restart with a fresh worktree, run /gsd-debug or recreate the milestone.`, { file: "auto.ts", milestoneId: s.currentMilestoneId ?? "" });
|
|
1366
|
+
}
|
|
1289
1367
|
if (resumeWorktreePath && existsSync(resumeWorktreePath)) {
|
|
1290
1368
|
s.basePath = resumeWorktreePath;
|
|
1291
1369
|
}
|
|
1370
|
+
// Rebuild scope now that s.basePath reflects the actual worktree (or project root).
|
|
1371
|
+
rebuildScope(s.basePath, s.currentMilestoneId);
|
|
1292
1372
|
// Ensure the workflow-logger audit log is pinned to the project root
|
|
1293
1373
|
// even when auto-mode is entered via a path that bypasses the
|
|
1294
1374
|
// bootstrap/dynamic-tools ensureDbOpen() → setLogBasePath() chain
|
|
@@ -1315,6 +1395,8 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1315
1395
|
buildResolver().enterMilestone(s.currentMilestoneId, {
|
|
1316
1396
|
notify: ctx.ui.notify.bind(ctx.ui),
|
|
1317
1397
|
});
|
|
1398
|
+
// s.basePath may have been updated to a worktree path by enterMilestone.
|
|
1399
|
+
rebuildScope(s.basePath, s.currentMilestoneId);
|
|
1318
1400
|
}
|
|
1319
1401
|
registerSigtermHandler(lockBase());
|
|
1320
1402
|
ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
|
|
@@ -1367,10 +1449,14 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1367
1449
|
}
|
|
1368
1450
|
s.pausedSessionFile = null;
|
|
1369
1451
|
}
|
|
1452
|
+
captureProjectRootEnv(s.originalBasePath || s.basePath);
|
|
1453
|
+
registerAutoWorkerForSession(s);
|
|
1370
1454
|
updateSessionLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown");
|
|
1371
|
-
|
|
1455
|
+
if (s.workerId) {
|
|
1456
|
+
writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown");
|
|
1457
|
+
clearPausedSession("paused-session DB cleanup failed (resume activation)");
|
|
1458
|
+
}
|
|
1372
1459
|
pi.events.emit(CMUX_CHANNELS.LOG, { preferences: loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, message: s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", level: "progress" });
|
|
1373
|
-
captureProjectRootEnv(s.originalBasePath || s.basePath);
|
|
1374
1460
|
startAutoCommandPolling(s.basePath);
|
|
1375
1461
|
await runAutoLoopWithUok({
|
|
1376
1462
|
ctx,
|
|
@@ -1393,7 +1479,11 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1393
1479
|
const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps, freshStartAssessment);
|
|
1394
1480
|
if (!ready)
|
|
1395
1481
|
return;
|
|
1482
|
+
// Build scope after bootstrap has populated s.basePath / s.originalBasePath /
|
|
1483
|
+
// s.currentMilestoneId (including worktree setup inside bootstrapAutoSession).
|
|
1484
|
+
rebuildScope(s.basePath, s.currentMilestoneId);
|
|
1396
1485
|
captureProjectRootEnv(s.originalBasePath || s.basePath);
|
|
1486
|
+
registerAutoWorkerForSession(s);
|
|
1397
1487
|
try {
|
|
1398
1488
|
pi.events.emit(CMUX_CHANNELS.SIDEBAR, { action: "sync", preferences: loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, state: await deriveState(s.basePath) });
|
|
1399
1489
|
}
|
|
@@ -86,7 +86,7 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
86
86
|
logWarning("bootstrap", `checkDeepProjectSetupAfterTurn failed: ${message}`);
|
|
87
87
|
}
|
|
88
88
|
if (checkAutoStartAfterDiscuss()) {
|
|
89
|
-
clearDiscussionFlowState();
|
|
89
|
+
clearDiscussionFlowState(resolveAgentEndBasePath() ?? process.cwd());
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
92
|
// #4573 — When the LLM emits "Milestone X ready." but the required files
|
|
@@ -60,7 +60,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
60
60
|
const { initHealthWidget } = await import("../health-widget.js");
|
|
61
61
|
initHealthWidget(ctx);
|
|
62
62
|
}
|
|
63
|
-
resetWriteGateState();
|
|
63
|
+
resetWriteGateState(process.cwd());
|
|
64
64
|
resetToolCallLoopGuard();
|
|
65
65
|
approvalQuestionAbortInFlight = false;
|
|
66
66
|
await resetAskUserQuestionsTurnCache();
|
|
@@ -109,10 +109,10 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
109
109
|
pi.on("session_switch", async (_event, ctx) => {
|
|
110
110
|
initNotificationStore(process.cwd());
|
|
111
111
|
installNotifyInterceptor(ctx);
|
|
112
|
-
resetWriteGateState();
|
|
112
|
+
resetWriteGateState(process.cwd());
|
|
113
113
|
resetToolCallLoopGuard();
|
|
114
114
|
await resetAskUserQuestionsTurnCache();
|
|
115
|
-
clearDiscussionFlowState();
|
|
115
|
+
clearDiscussionFlowState(process.cwd());
|
|
116
116
|
await syncServiceTierStatus(ctx);
|
|
117
117
|
await applyDisabledModelProviderPolicy(ctx);
|
|
118
118
|
// Skip MCP auto-prep when running inside an auto-worktree. The worktree
|
|
@@ -137,13 +137,14 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
137
137
|
// Wait for ecosystem loader to finish (no-op after first turn).
|
|
138
138
|
const { getEcosystemReadyPromise } = await import("../ecosystem/loader.js");
|
|
139
139
|
await getEcosystemReadyPromise();
|
|
140
|
+
const beforeAgentBasePath = process.cwd();
|
|
140
141
|
const pendingApprovalGate = getPendingGate();
|
|
141
142
|
if (pendingApprovalGate && isExplicitApprovalResponse(event.prompt, pendingApprovalGate)) {
|
|
142
|
-
markApprovalGateVerified(pendingApprovalGate);
|
|
143
|
+
markApprovalGateVerified(pendingApprovalGate, beforeAgentBasePath);
|
|
143
144
|
const milestoneId = extractDepthVerificationMilestoneId(pendingApprovalGate);
|
|
144
145
|
if (milestoneId)
|
|
145
|
-
markDepthVerified(milestoneId);
|
|
146
|
-
clearPendingGate();
|
|
146
|
+
markDepthVerified(milestoneId, beforeAgentBasePath);
|
|
147
|
+
clearPendingGate(beforeAgentBasePath);
|
|
147
148
|
}
|
|
148
149
|
// GSD's own context injection (existing behavior — unchanged).
|
|
149
150
|
const { buildBeforeAgentStartResult } = await import("./system-context.js");
|
|
@@ -318,7 +319,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
318
319
|
return;
|
|
319
320
|
const gateId = approvalGateIdForUnit(unitType, unitId);
|
|
320
321
|
if (gateId)
|
|
321
|
-
setPendingGate(gateId);
|
|
322
|
+
setPendingGate(gateId, process.cwd());
|
|
322
323
|
approvalQuestionAbortInFlight = true;
|
|
323
324
|
ctx.ui.notify(`${unitType}${unitId ? ` ${unitId}` : ""} is waiting for your approval - pausing before more tool calls run.`, "info");
|
|
324
325
|
// The pending gate set above blocks subsequent non-read-only tool calls
|
|
@@ -360,7 +361,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
360
361
|
const questions = event.input?.questions ?? [];
|
|
361
362
|
const questionId = questions.find((question) => typeof question?.id === "string" && isGateQuestionId(question.id))?.id;
|
|
362
363
|
if (typeof questionId === "string") {
|
|
363
|
-
setPendingGate(questionId);
|
|
364
|
+
setPendingGate(questionId, discussionBasePath);
|
|
364
365
|
}
|
|
365
366
|
}
|
|
366
367
|
// ── Discussion gate enforcement: block tool calls while gate is pending ──
|
|
@@ -501,7 +502,8 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
501
502
|
const toolName = canonicalToolName(event.toolName);
|
|
502
503
|
if (toolName !== "ask_user_questions")
|
|
503
504
|
return;
|
|
504
|
-
const
|
|
505
|
+
const basePath = process.cwd();
|
|
506
|
+
const milestoneId = await getDiscussionMilestoneIdFor(basePath);
|
|
505
507
|
const queueActive = isQueuePhaseActive();
|
|
506
508
|
const details = event.details;
|
|
507
509
|
// ── Discussion gate enforcement: handle gate question responses ──
|
|
@@ -513,8 +515,13 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
513
515
|
const currentPendingGate = getPendingGate();
|
|
514
516
|
if (currentPendingGate) {
|
|
515
517
|
if (details?.cancelled || !details?.response) {
|
|
516
|
-
// Gate stays pending.
|
|
517
|
-
//
|
|
518
|
+
// Gate stays pending. Direct the agent to the most reliable recovery
|
|
519
|
+
// path — re-calling ask_user_questions with the same gate id — without
|
|
520
|
+
// misrepresenting the plain-text path. The plain-text path also works
|
|
521
|
+
// (isExplicitApprovalResponse on the next before_agent_start clears
|
|
522
|
+
// the gate when the user replies with an approval keyword), but the
|
|
523
|
+
// structured re-ask is more deterministic and gives the user a clear UI.
|
|
524
|
+
resetToolCallLoopGuard();
|
|
518
525
|
return {
|
|
519
526
|
content: [{
|
|
520
527
|
type: "text",
|
|
@@ -522,8 +529,8 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
522
529
|
`HARD BLOCK: approval gate "${currentPendingGate}" is still pending.`,
|
|
523
530
|
"No user response was received for the confirmation question.",
|
|
524
531
|
"Do not infer approval from earlier or prior messages.",
|
|
525
|
-
"Do not proceed, write files, save artifacts, or call
|
|
526
|
-
|
|
532
|
+
"Do not proceed, write files, save artifacts, or call other tools.",
|
|
533
|
+
`Re-call ask_user_questions with the same gate question id ("${currentPendingGate}") and wait for the user's response.`,
|
|
527
534
|
].join(" "),
|
|
528
535
|
}],
|
|
529
536
|
};
|
|
@@ -533,11 +540,11 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
533
540
|
if (pendingQuestion) {
|
|
534
541
|
const answer = details.response?.answers?.[currentPendingGate];
|
|
535
542
|
if (isDepthConfirmationAnswer(answer?.selected, pendingQuestion.options)) {
|
|
536
|
-
markApprovalGateVerified(currentPendingGate);
|
|
543
|
+
markApprovalGateVerified(currentPendingGate, basePath);
|
|
537
544
|
const milestoneIdFromGate = extractDepthVerificationMilestoneId(currentPendingGate);
|
|
538
545
|
if (milestoneIdFromGate)
|
|
539
|
-
markDepthVerified(milestoneIdFromGate);
|
|
540
|
-
clearPendingGate();
|
|
546
|
+
markDepthVerified(milestoneIdFromGate, basePath);
|
|
547
|
+
clearPendingGate(basePath);
|
|
541
548
|
}
|
|
542
549
|
}
|
|
543
550
|
}
|
|
@@ -553,9 +560,9 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
553
560
|
if (isDepthConfirmationAnswer(answer?.selected, question.options)) {
|
|
554
561
|
if (currentPendingGate && question.id !== currentPendingGate)
|
|
555
562
|
break;
|
|
556
|
-
markApprovalGateVerified(question.id);
|
|
557
|
-
markDepthVerified(inferredMilestoneId);
|
|
558
|
-
clearPendingGate();
|
|
563
|
+
markApprovalGateVerified(question.id, basePath);
|
|
564
|
+
markDepthVerified(inferredMilestoneId, basePath);
|
|
565
|
+
clearPendingGate(basePath);
|
|
559
566
|
}
|
|
560
567
|
break;
|
|
561
568
|
}
|
|
@@ -564,7 +571,6 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
564
571
|
return;
|
|
565
572
|
if (!milestoneId)
|
|
566
573
|
return;
|
|
567
|
-
const basePath = process.cwd();
|
|
568
574
|
const milestoneDir = resolveMilestonePath(basePath, milestoneId);
|
|
569
575
|
if (!milestoneDir)
|
|
570
576
|
return;
|