gsd-pi 2.78.1-dev.d8826a445 → 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 +7 -5
- package/dist/resources/extensions/gsd/auto/session.js +33 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +46 -2
- package/dist/resources/extensions/gsd/auto-post-unit.js +19 -11
- package/dist/resources/extensions/gsd/auto-worktree.js +26 -187
- package/dist/resources/extensions/gsd/auto.js +79 -50
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -4
- 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/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 +161 -3
- package/dist/resources/extensions/gsd/guided-flow.js +6 -2
- package/dist/resources/extensions/gsd/interrupted-session.js +18 -15
- package/dist/resources/extensions/gsd/state.js +21 -6
- package/dist/resources/extensions/gsd/worktree-resolver.js +64 -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 +12 -12
- 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 +12 -12
- 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 +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 +7 -5
- package/src/resources/extensions/gsd/auto/session.ts +36 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +53 -2
- package/src/resources/extensions/gsd/auto-post-unit.ts +19 -11
- package/src/resources/extensions/gsd/auto-worktree.ts +26 -211
- package/src/resources/extensions/gsd/auto.ts +89 -44
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +9 -4
- 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/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 +170 -3
- package/src/resources/extensions/gsd/guided-flow.ts +6 -2
- package/src/resources/extensions/gsd/interrupted-session.ts +19 -12
- package/src/resources/extensions/gsd/state.ts +44 -6
- 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-workers.test.ts +105 -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/detect-stuck-respects-retry.test.ts +173 -0
- 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 +3 -5
- 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/milestone-leases.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/parallel-milestone-isolation.test.ts +106 -0
- package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +119 -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/register-hooks-depth-verification.test.ts +110 -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 +7 -26
- package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +4 -8
- 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/workspace.test.ts +15 -9
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +31 -23
- package/src/resources/extensions/gsd/worktree-resolver.ts +62 -0
- 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/{AT5qi39nKXkdmQIOIoh0f → Y5UeGFkXTYM9WIQOWHkot}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{AT5qi39nKXkdmQIOIoh0f → Y5UeGFkXTYM9WIQOWHkot}/_ssgManifest.js +0 -0
|
@@ -768,14 +768,16 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
|
|
|
768
768
|
if (s.currentUnit.type === "triage-captures") {
|
|
769
769
|
try {
|
|
770
770
|
const { executeTriageResolutions } = await import("./triage-resolution.js");
|
|
771
|
-
const state = await deriveState(s.
|
|
771
|
+
const state = await deriveState(s.canonicalProjectRoot);
|
|
772
772
|
const mid = state.activeMilestone?.id ?? "";
|
|
773
773
|
const sid = state.activeSlice?.id ?? "";
|
|
774
774
|
|
|
775
775
|
// executeTriageResolutions handles defer milestone creation even
|
|
776
776
|
// without an active milestone/slice (the "all milestones complete"
|
|
777
777
|
// scenario from #1562). inject/replan/quick-task still require mid+sid.
|
|
778
|
-
|
|
778
|
+
// Phase C: write to canonical project root. copyPlanningArtifacts
|
|
779
|
+
// has been deleted, so triage writes land where readers consult.
|
|
780
|
+
const triageResult = executeTriageResolutions(s.canonicalProjectRoot, mid, sid);
|
|
779
781
|
|
|
780
782
|
if (triageResult.injected > 0) {
|
|
781
783
|
ctx.ui.notify(
|
|
@@ -940,10 +942,14 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
|
|
|
940
942
|
try {
|
|
941
943
|
const { milestone: mid, slice: sid } = parseUnitId(s.currentUnit.id);
|
|
942
944
|
if (mid && sid) {
|
|
943
|
-
|
|
945
|
+
// Phase C: write to the canonical project root (#5236 scope)
|
|
946
|
+
// so non-symlinked worktrees no longer maintain a separate
|
|
947
|
+
// local .gsd/ projection. copyPlanningArtifacts has been
|
|
948
|
+
// deleted; reads + writes converge at projectRoot.
|
|
949
|
+
const regenerated = await regenerateIfMissing(s.canonicalProjectRoot, mid, sid, "PLAN");
|
|
944
950
|
if (regenerated) {
|
|
945
951
|
// Re-check after regeneration
|
|
946
|
-
triggerArtifactVerified = verifyExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.
|
|
952
|
+
triggerArtifactVerified = verifyExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.canonicalProjectRoot);
|
|
947
953
|
if (triggerArtifactVerified) {
|
|
948
954
|
invalidateAllCaches();
|
|
949
955
|
}
|
|
@@ -1178,7 +1184,7 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
1178
1184
|
if (mid && sid && tid) {
|
|
1179
1185
|
try {
|
|
1180
1186
|
updateTaskStatus(mid, sid, tid, "pending");
|
|
1181
|
-
await renderPlanCheckboxes(s.
|
|
1187
|
+
await renderPlanCheckboxes(s.canonicalProjectRoot, mid, sid);
|
|
1182
1188
|
} catch (dbErr) {
|
|
1183
1189
|
// DB unavailable — fail explicitly rather than silently reverting to markdown mutation.
|
|
1184
1190
|
// Use 'gsd recover' to rebuild DB state from disk if needed.
|
|
@@ -1188,7 +1194,8 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
1188
1194
|
|
|
1189
1195
|
// 2. Delete SUMMARY.md for the task
|
|
1190
1196
|
if (mid && sid && tid) {
|
|
1191
|
-
|
|
1197
|
+
// Phase C: read+delete via canonical project root.
|
|
1198
|
+
const tasksDir = resolveTasksDir(s.canonicalProjectRoot, mid, sid);
|
|
1192
1199
|
if (tasksDir) {
|
|
1193
1200
|
const summaryFile = join(tasksDir, buildTaskFileName(tid, "SUMMARY"));
|
|
1194
1201
|
if (existsSync(summaryFile)) {
|
|
@@ -1199,7 +1206,7 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
1199
1206
|
|
|
1200
1207
|
// 3. Delete the retry_on artifact (e.g. NEEDS-REWORK.md)
|
|
1201
1208
|
if (trigger.retryArtifact) {
|
|
1202
|
-
const retryArtifactPath = resolveHookArtifactPath(s.
|
|
1209
|
+
const retryArtifactPath = resolveHookArtifactPath(s.canonicalProjectRoot, trigger.unitId, trigger.retryArtifact);
|
|
1203
1210
|
if (existsSync(retryArtifactPath)) {
|
|
1204
1211
|
unlinkSync(retryArtifactPath);
|
|
1205
1212
|
}
|
|
@@ -1477,16 +1484,17 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
1477
1484
|
if (hasPendingCaptures(s.basePath)) {
|
|
1478
1485
|
const pending = loadPendingCaptures(s.basePath);
|
|
1479
1486
|
if (pending.length > 0) {
|
|
1480
|
-
const
|
|
1487
|
+
const readRoot = s.canonicalProjectRoot;
|
|
1488
|
+
const state = await deriveState(readRoot);
|
|
1481
1489
|
const mid = state.activeMilestone?.id;
|
|
1482
1490
|
const sid = state.activeSlice?.id;
|
|
1483
1491
|
|
|
1484
1492
|
if (mid && sid) {
|
|
1485
1493
|
let currentPlan = "";
|
|
1486
1494
|
let roadmapContext = "";
|
|
1487
|
-
const planFile = resolveSliceFile(
|
|
1495
|
+
const planFile = resolveSliceFile(readRoot, mid, sid, "PLAN");
|
|
1488
1496
|
if (planFile) currentPlan = (await loadFile(planFile)) ?? "";
|
|
1489
|
-
const roadmapFile = resolveMilestoneFile(
|
|
1497
|
+
const roadmapFile = resolveMilestoneFile(readRoot, mid, "ROADMAP");
|
|
1490
1498
|
if (roadmapFile) roadmapContext = (await loadFile(roadmapFile)) ?? "";
|
|
1491
1499
|
|
|
1492
1500
|
const capturesList = pending.map(c =>
|
|
@@ -1554,7 +1562,7 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
1554
1562
|
// exits the loop, leaving the user with no hint to /clear and /gsd again.
|
|
1555
1563
|
if (s.stepMode) {
|
|
1556
1564
|
try {
|
|
1557
|
-
const nextState = await deriveState(s.
|
|
1565
|
+
const nextState = await deriveState(s.canonicalProjectRoot);
|
|
1558
1566
|
ctx.ui.notify(buildStepCompleteMessage(nextState), "info");
|
|
1559
1567
|
} catch (e) {
|
|
1560
1568
|
debugLog("postUnit", { phase: "step-wizard-notify", error: String(e) });
|
|
@@ -268,9 +268,12 @@ function getActiveWorkspace(): GsdWorkspace | null {
|
|
|
268
268
|
|
|
269
269
|
function clearProjectRootStateFiles(basePath: string, milestoneId: string): void {
|
|
270
270
|
const gsdDir = gsdRoot(basePath);
|
|
271
|
+
// Phase C pt 2: auto.lock removed from this list — the file is gone
|
|
272
|
+
// (migrated to the workers + unit_dispatches + runtime_kv tables). The
|
|
273
|
+
// remaining transient files (STATE.md, {MID}-META.json) are still
|
|
274
|
+
// worth removing on teardown.
|
|
271
275
|
const transientFiles = [
|
|
272
276
|
join(gsdDir, "STATE.md"),
|
|
273
|
-
join(gsdDir, "auto.lock"),
|
|
274
277
|
join(gsdDir, "milestones", milestoneId, `${milestoneId}-META.json`),
|
|
275
278
|
];
|
|
276
279
|
|
|
@@ -1128,137 +1131,13 @@ export function enterBranchModeForMilestone(
|
|
|
1128
1131
|
* Forward-merge plan checkbox state from the project root into a freshly
|
|
1129
1132
|
* re-attached worktree (#778).
|
|
1130
1133
|
*
|
|
1131
|
-
*
|
|
1132
|
-
*
|
|
1133
|
-
*
|
|
1134
|
-
*
|
|
1135
|
-
*
|
|
1136
|
-
*
|
|
1137
|
-
* dispatch/skip loop.
|
|
1138
|
-
*
|
|
1139
|
-
* Fix: after re-attaching, read every *.md plan file in the milestone
|
|
1140
|
-
* directory at the project root and apply any [x] checkbox states that are
|
|
1141
|
-
* ahead of the worktree version (forward-only: never downgrade [x] → [ ]).
|
|
1142
|
-
*
|
|
1143
|
-
* This is forward-only compatibility for legacy projection copies. The DB
|
|
1144
|
-
* remains authoritative; this never downgrades checked boxes in a local
|
|
1145
|
-
* worktree projection.
|
|
1146
|
-
*/
|
|
1147
|
-
/**
|
|
1148
|
-
* Scope-typed variant of reconcilePlanCheckboxes.
|
|
1149
|
-
*
|
|
1150
|
-
* Takes an explicit (rootScope, worktreeScope) pair. milestoneId is taken
|
|
1151
|
-
* from rootScope. Asserts both scopes belong to the same workspace identity
|
|
1152
|
-
* to prevent silent mismatch bugs.
|
|
1153
|
-
*/
|
|
1154
|
-
export function reconcilePlanCheckboxesByScope(
|
|
1155
|
-
rootScope: MilestoneScope,
|
|
1156
|
-
worktreeScope: MilestoneScope,
|
|
1157
|
-
): void {
|
|
1158
|
-
if (rootScope.workspace.identityKey !== worktreeScope.workspace.identityKey) {
|
|
1159
|
-
throw new Error(
|
|
1160
|
-
`reconcilePlanCheckboxesByScope: scope identity mismatch — ` +
|
|
1161
|
-
`rootScope.identityKey="${rootScope.workspace.identityKey}" ` +
|
|
1162
|
-
`worktreeScope.identityKey="${worktreeScope.workspace.identityKey}"`,
|
|
1163
|
-
);
|
|
1164
|
-
}
|
|
1165
|
-
if (rootScope.milestoneId !== worktreeScope.milestoneId) {
|
|
1166
|
-
throw new Error(
|
|
1167
|
-
`reconcilePlanCheckboxesByScope: milestoneId mismatch — ` +
|
|
1168
|
-
`rootScope.milestoneId="${rootScope.milestoneId}" worktreeScope.milestoneId="${worktreeScope.milestoneId}"`,
|
|
1169
|
-
);
|
|
1170
|
-
}
|
|
1171
|
-
const projectRoot = rootScope.workspace.projectRoot;
|
|
1172
|
-
const wtPath = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
|
|
1173
|
-
const milestoneId = rootScope.milestoneId;
|
|
1174
|
-
reconcilePlanCheckboxes(projectRoot, wtPath, milestoneId);
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
/**
|
|
1178
|
-
* @deprecated Use reconcilePlanCheckboxesByScope instead.
|
|
1179
|
-
* TODO(C-future): remove once all callers migrated.
|
|
1134
|
+
* Phase C: deleted. Writers in workflow-projections.ts, triage-resolution.ts,
|
|
1135
|
+
* rule-registry.ts, and auto-post-unit.ts now route through
|
|
1136
|
+
* s.canonicalProjectRoot, so non-symlinked worktrees no longer need a local
|
|
1137
|
+
* .gsd/ projection — the project-root .gsd/ is the only authoritative source
|
|
1138
|
+
* for both reads and writes. copyPlanningArtifacts and reconcilePlanCheckboxes
|
|
1139
|
+
* (both formerly here) became dead.
|
|
1180
1140
|
*/
|
|
1181
|
-
function reconcilePlanCheckboxes(
|
|
1182
|
-
projectRoot: string,
|
|
1183
|
-
wtPath: string,
|
|
1184
|
-
milestoneId: string,
|
|
1185
|
-
): void {
|
|
1186
|
-
const srcMilestone = join(projectRoot, ".gsd", "milestones", milestoneId);
|
|
1187
|
-
const dstMilestone = join(wtPath, ".gsd", "milestones", milestoneId);
|
|
1188
|
-
if (!existsSync(srcMilestone) || !existsSync(dstMilestone)) return;
|
|
1189
|
-
|
|
1190
|
-
// Walk all markdown files in the milestone directory (plans, summaries, etc.)
|
|
1191
|
-
function walkMd(dir: string): string[] {
|
|
1192
|
-
const results: string[] = [];
|
|
1193
|
-
try {
|
|
1194
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1195
|
-
const full = join(dir, entry.name);
|
|
1196
|
-
if (entry.isDirectory()) {
|
|
1197
|
-
results.push(...walkMd(full));
|
|
1198
|
-
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
1199
|
-
results.push(full);
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
} catch (err) {
|
|
1203
|
-
/* non-fatal */
|
|
1204
|
-
logWarning("worktree", `walkMd directory read failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1205
|
-
}
|
|
1206
|
-
return results;
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
for (const srcFile of walkMd(srcMilestone)) {
|
|
1210
|
-
const rel = srcFile.slice(srcMilestone.length);
|
|
1211
|
-
const dstFile = dstMilestone + rel;
|
|
1212
|
-
if (!existsSync(dstFile)) continue; // only reconcile existing files
|
|
1213
|
-
|
|
1214
|
-
let srcContent: string;
|
|
1215
|
-
let dstContent: string;
|
|
1216
|
-
try {
|
|
1217
|
-
srcContent = readFileSync(srcFile, "utf-8");
|
|
1218
|
-
dstContent = readFileSync(dstFile, "utf-8");
|
|
1219
|
-
} catch (e) {
|
|
1220
|
-
logWarning("worktree", `reconcilePlanCheckboxes read failed: ${(e as Error).message}`);
|
|
1221
|
-
continue;
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
if (srcContent === dstContent) continue;
|
|
1225
|
-
|
|
1226
|
-
// Extract all checked task IDs from the source (project root)
|
|
1227
|
-
// Pattern: - [x] **T<id>: or - [x] **S<id>: (case-insensitive x)
|
|
1228
|
-
const checkedRe = /^- \[[xX]\] \*\*([TS]\d+):/gm;
|
|
1229
|
-
const srcChecked = new Set<string>();
|
|
1230
|
-
for (const m of srcContent.matchAll(checkedRe)) srcChecked.add(m[1]);
|
|
1231
|
-
|
|
1232
|
-
if (srcChecked.size === 0) continue;
|
|
1233
|
-
|
|
1234
|
-
// Forward-apply: replace [ ] → [x] for any IDs that are checked in src
|
|
1235
|
-
let updated = dstContent;
|
|
1236
|
-
let changed = false;
|
|
1237
|
-
for (const id of srcChecked) {
|
|
1238
|
-
const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1239
|
-
const uncheckedRe = new RegExp(
|
|
1240
|
-
`^(- )\\[ \\]( \\*\\*${escapedId}:)`,
|
|
1241
|
-
"gm",
|
|
1242
|
-
);
|
|
1243
|
-
if (uncheckedRe.test(updated)) {
|
|
1244
|
-
updated = updated.replace(
|
|
1245
|
-
new RegExp(`^(- )\\[ \\]( \\*\\*${escapedId}:)`, "gm"),
|
|
1246
|
-
"$1[x]$2",
|
|
1247
|
-
);
|
|
1248
|
-
changed = true;
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
if (changed) {
|
|
1253
|
-
try {
|
|
1254
|
-
atomicWriteSync(dstFile, updated, "utf-8");
|
|
1255
|
-
} catch (err) {
|
|
1256
|
-
/* non-fatal */
|
|
1257
|
-
logWarning("worktree", `plan checkbox reconcile write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
1141
|
|
|
1263
1142
|
export function createAutoWorktree(
|
|
1264
1143
|
basePath: string,
|
|
@@ -1316,32 +1195,15 @@ export function createAutoWorktree(
|
|
|
1316
1195
|
});
|
|
1317
1196
|
}
|
|
1318
1197
|
|
|
1319
|
-
//
|
|
1320
|
-
//
|
|
1321
|
-
//
|
|
1322
|
-
//
|
|
1323
|
-
//
|
|
1324
|
-
//
|
|
1325
|
-
//
|
|
1326
|
-
// The
|
|
1327
|
-
//
|
|
1328
|
-
// overwrite them with stale data ([ ] checkboxes) because the root is
|
|
1329
|
-
// not always fully synced.
|
|
1330
|
-
if (!branchExists) {
|
|
1331
|
-
copyPlanningArtifacts(basePath, info.path);
|
|
1332
|
-
} else {
|
|
1333
|
-
// Re-attaching to an existing branch: forward-merge any plan checkpoint
|
|
1334
|
-
// state from the project root into the worktree (#778).
|
|
1335
|
-
//
|
|
1336
|
-
// If auto-mode stopped via crash, the milestone branch HEAD may lag behind
|
|
1337
|
-
// the project root filesystem because syncStateToProjectRoot() ran after
|
|
1338
|
-
// task completion but the auto-commit never fired. On restart the worktree
|
|
1339
|
-
// is re-created from the branch HEAD (which has [ ] for the crashed task),
|
|
1340
|
-
// causing verifyExpectedArtifact() to return false → stale-key eviction →
|
|
1341
|
-
// infinite dispatch/skip loop. Reconciling here ensures the worktree sees
|
|
1342
|
-
// the same [x] state that syncStateToProjectRoot() wrote to the root.
|
|
1343
|
-
reconcilePlanCheckboxes(basePath, info.path, milestoneId);
|
|
1344
|
-
}
|
|
1198
|
+
// Phase C: copyPlanningArtifacts and reconcilePlanCheckboxes were
|
|
1199
|
+
// deleted. Both addressed the same problem (worktree-local .gsd/
|
|
1200
|
+
// projection lagging behind project-root state) by maintaining a stale
|
|
1201
|
+
// copy. Now that auto-mode writers in workflow-projections.ts,
|
|
1202
|
+
// triage-resolution.ts, rule-registry.ts, and auto-post-unit.ts route
|
|
1203
|
+
// through s.canonicalProjectRoot, the worktree never needs a local
|
|
1204
|
+
// .gsd/ — both reads and writes converge on the project-root .gsd/.
|
|
1205
|
+
// The original concerns (#759, #778) no longer apply because there is
|
|
1206
|
+
// no second copy to drift.
|
|
1345
1207
|
|
|
1346
1208
|
// Run user-configured post-create hook (#597) — e.g. copy .env, symlink assets
|
|
1347
1209
|
const hookError = runWorktreePostCreateHook(basePath, info.path);
|
|
@@ -1368,60 +1230,13 @@ export function createAutoWorktree(
|
|
|
1368
1230
|
return info.path;
|
|
1369
1231
|
}
|
|
1370
1232
|
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
function copyPlanningArtifacts(srcBase: string, wtPath: string): void {
|
|
1379
|
-
const srcGsd = join(srcBase, ".gsd");
|
|
1380
|
-
const dstGsd = join(wtPath, ".gsd");
|
|
1381
|
-
if (!existsSync(srcGsd)) return;
|
|
1382
|
-
if (isSamePath(srcGsd, dstGsd)) return;
|
|
1383
|
-
|
|
1384
|
-
// Copy milestones/ directory (planning files, roadmaps, plans, research)
|
|
1385
|
-
safeCopyRecursive(join(srcGsd, "milestones"), join(dstGsd, "milestones"), {
|
|
1386
|
-
force: true,
|
|
1387
|
-
filter: (src) => !src.endsWith("-META.json"),
|
|
1388
|
-
});
|
|
1389
|
-
|
|
1390
|
-
// Copy top-level planning files
|
|
1391
|
-
for (const file of [
|
|
1392
|
-
"DECISIONS.md",
|
|
1393
|
-
"REQUIREMENTS.md",
|
|
1394
|
-
"PROJECT.md",
|
|
1395
|
-
"QUEUE.md",
|
|
1396
|
-
"STATE.md",
|
|
1397
|
-
"KNOWLEDGE.md",
|
|
1398
|
-
"OVERRIDES.md",
|
|
1399
|
-
"mcp.json",
|
|
1400
|
-
]) {
|
|
1401
|
-
safeCopy(join(srcGsd, file), join(dstGsd, file), { force: true });
|
|
1402
|
-
}
|
|
1403
|
-
|
|
1404
|
-
// Seed canonical PREFERENCES.md when available; fall back to legacy lowercase.
|
|
1405
|
-
if (existsSync(join(srcGsd, PROJECT_PREFERENCES_FILE))) {
|
|
1406
|
-
safeCopy(
|
|
1407
|
-
join(srcGsd, PROJECT_PREFERENCES_FILE),
|
|
1408
|
-
join(dstGsd, PROJECT_PREFERENCES_FILE),
|
|
1409
|
-
{ force: true },
|
|
1410
|
-
);
|
|
1411
|
-
} else if (existsSync(join(srcGsd, LEGACY_PROJECT_PREFERENCES_FILE))) {
|
|
1412
|
-
safeCopy(
|
|
1413
|
-
join(srcGsd, LEGACY_PROJECT_PREFERENCES_FILE),
|
|
1414
|
-
join(dstGsd, LEGACY_PROJECT_PREFERENCES_FILE),
|
|
1415
|
-
{ force: true },
|
|
1416
|
-
);
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
// Shared WAL (R012): worktrees use the project root's DB directly.
|
|
1420
|
-
// No longer copy gsd.db into the worktree — the DB path resolver in
|
|
1421
|
-
// ensureDbOpen() detects the worktree location and opens the root DB.
|
|
1422
|
-
// Compat note: reconcileWorktreeDb() in mergeMilestoneToMain handles
|
|
1423
|
-
// worktrees that already have a local gsd.db from before this change.
|
|
1424
|
-
}
|
|
1233
|
+
// Phase C: copyPlanningArtifacts removed. Planning artifacts now live
|
|
1234
|
+
// only at the project root .gsd/; auto-mode writers (workflow-projections,
|
|
1235
|
+
// triage-resolution, rule-registry, regenerateIfMissing,
|
|
1236
|
+
// resolveHookArtifactPath) all route through s.canonicalProjectRoot.
|
|
1237
|
+
// Worktrees are pure git checkouts — they no longer maintain a parallel
|
|
1238
|
+
// .gsd/ projection. The gsd.db has always lived at the project root via
|
|
1239
|
+
// the shared-WAL R012 contract; that is unchanged.
|
|
1425
1240
|
|
|
1426
1241
|
/**
|
|
1427
1242
|
* Teardown an auto-worktree: chdir back to original base, then remove
|
|
@@ -22,8 +22,14 @@ import type { GSDState } from "./types.js";
|
|
|
22
22
|
import {
|
|
23
23
|
assessInterruptedSession,
|
|
24
24
|
readPausedSessionMetadata,
|
|
25
|
+
PAUSED_SESSION_KV_KEY,
|
|
25
26
|
type InterruptedSessionAssessment,
|
|
27
|
+
type PausedSessionMetadata,
|
|
26
28
|
} from "./interrupted-session.js";
|
|
29
|
+
import {
|
|
30
|
+
setRuntimeKv,
|
|
31
|
+
deleteRuntimeKv,
|
|
32
|
+
} from "./db/runtime-kv.js";
|
|
27
33
|
import { getManifestStatus } from "./files.js";
|
|
28
34
|
export { inlinePriorMilestoneSummary } from "./files.js";
|
|
29
35
|
import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
|
|
@@ -244,6 +250,7 @@ import type {
|
|
|
244
250
|
CurrentUnit,
|
|
245
251
|
UnitRouting,
|
|
246
252
|
StartModel,
|
|
253
|
+
AutoSession,
|
|
247
254
|
} from "./auto/session.js";
|
|
248
255
|
export {
|
|
249
256
|
STUB_RECOVERY_THRESHOLD,
|
|
@@ -257,6 +264,9 @@ export type {
|
|
|
257
264
|
import { autoSession as s } from "./auto-runtime-state.js";
|
|
258
265
|
import { gsdHome } from "./gsd-home.js";
|
|
259
266
|
import { createWorkspace, scopeMilestone } from "./workspace.js";
|
|
267
|
+
import { registerAutoWorker, markWorkerStopping } from "./db/auto-workers.js";
|
|
268
|
+
import { releaseMilestoneLease } from "./db/milestone-leases.js";
|
|
269
|
+
import { normalizeRealPath } from "./paths.js";
|
|
260
270
|
|
|
261
271
|
// ── ENCAPSULATION INVARIANT ─────────────────────────────────────────────────
|
|
262
272
|
// ALL mutable auto-mode state lives in the AutoSession class (auto/session.ts).
|
|
@@ -274,6 +284,28 @@ import { createWorkspace, scopeMilestone } from "./workspace.js";
|
|
|
274
284
|
/** Throttle STATE.md rebuilds — at most once per 30 seconds */
|
|
275
285
|
const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
|
|
276
286
|
|
|
287
|
+
/**
|
|
288
|
+
* Phase B — register this auto-mode process in the workers table so other
|
|
289
|
+
* workers and janitors can detect liveness via heartbeat. Best-effort: if
|
|
290
|
+
* the DB is unavailable (e.g. fresh project before init) we skip registration
|
|
291
|
+
* silently rather than blocking session start.
|
|
292
|
+
*/
|
|
293
|
+
function registerAutoWorkerForSession(session: AutoSession): void {
|
|
294
|
+
if (session.workerId) return; // already registered (e.g. resume re-runs)
|
|
295
|
+
try {
|
|
296
|
+
const projectRootRealpath = normalizeRealPath(
|
|
297
|
+
session.scope?.workspace.projectRoot
|
|
298
|
+
?? (session.originalBasePath || session.basePath),
|
|
299
|
+
);
|
|
300
|
+
session.workerId = registerAutoWorker({ projectRootRealpath });
|
|
301
|
+
} catch (err) {
|
|
302
|
+
debugLog("autoLoop", {
|
|
303
|
+
phase: "register-worker-failed",
|
|
304
|
+
error: err instanceof Error ? err.message : String(err),
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
277
309
|
function captureProjectRootEnv(projectRoot: string): void {
|
|
278
310
|
if (!s.projectRootEnvCaptured) {
|
|
279
311
|
s.hadProjectRootEnv = Object.prototype.hasOwnProperty.call(process.env, "GSD_PROJECT_ROOT");
|
|
@@ -911,6 +943,21 @@ export async function stopAuto(
|
|
|
911
943
|
debugLog("stop-cleanup-locks", { error: e instanceof Error ? e.message : String(e) });
|
|
912
944
|
}
|
|
913
945
|
|
|
946
|
+
// ── Step 1b: Coordination cleanup (Phase B) ──
|
|
947
|
+
// Release any active milestone lease so other workers don't have to
|
|
948
|
+
// wait for TTL expiry, then mark this worker as stopping. Best-effort:
|
|
949
|
+
// DB unavailability or stale state must not block shutdown.
|
|
950
|
+
try {
|
|
951
|
+
if (s.workerId && s.currentMilestoneId && s.milestoneLeaseToken) {
|
|
952
|
+
releaseMilestoneLease(s.workerId, s.currentMilestoneId, s.milestoneLeaseToken);
|
|
953
|
+
}
|
|
954
|
+
if (s.workerId) {
|
|
955
|
+
markWorkerStopping(s.workerId);
|
|
956
|
+
}
|
|
957
|
+
} catch (e) {
|
|
958
|
+
debugLog("stop-cleanup-coordination", { error: e instanceof Error ? e.message : String(e) });
|
|
959
|
+
}
|
|
960
|
+
|
|
914
961
|
// ── Step 1b: Flush queued follow-up messages (#3512) ──
|
|
915
962
|
// Late async notifications (async_job_result, gsd-auto-wrapup) can trigger
|
|
916
963
|
// extra LLM turns after stop. Flush them the same way run-unit.ts does.
|
|
@@ -1095,11 +1142,11 @@ export async function stopAuto(
|
|
|
1095
1142
|
}
|
|
1096
1143
|
|
|
1097
1144
|
// ── Step 12: Remove paused-session metadata (#1383) ──
|
|
1145
|
+
// Phase C pt 2: deleteRuntimeKv replaces unlinkSync(paused-session.json).
|
|
1098
1146
|
try {
|
|
1099
|
-
|
|
1100
|
-
if (existsSync(pausedPath)) unlinkSync(pausedPath);
|
|
1147
|
+
deleteRuntimeKv("global", "", PAUSED_SESSION_KV_KEY);
|
|
1101
1148
|
} catch (err) { /* non-fatal */
|
|
1102
|
-
logWarning("engine", `
|
|
1149
|
+
logWarning("engine", `paused-session DB delete failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
1103
1150
|
}
|
|
1104
1151
|
|
|
1105
1152
|
// ── Step 13: Restore original model + thinking (before reset clears IDs) ──
|
|
@@ -1200,10 +1247,12 @@ export async function pauseAuto(
|
|
|
1200
1247
|
s.pausedSessionFile = normalizeSessionFilePath(ctx?.sessionManager?.getSessionFile() ?? null);
|
|
1201
1248
|
|
|
1202
1249
|
// Persist paused-session metadata so resume survives /exit (#1383).
|
|
1203
|
-
//
|
|
1250
|
+
// Phase C pt 2: persisted to runtime_kv (global scope, key
|
|
1251
|
+
// PAUSED_SESSION_KV_KEY) instead of runtime/paused-session.json. The
|
|
1252
|
+
// fresh-start bootstrap below reads from the same key.
|
|
1204
1253
|
try {
|
|
1205
|
-
const pausedMeta = {
|
|
1206
|
-
milestoneId: s.currentMilestoneId,
|
|
1254
|
+
const pausedMeta: PausedSessionMetadata = {
|
|
1255
|
+
milestoneId: s.currentMilestoneId ?? undefined,
|
|
1207
1256
|
worktreePath: isInAutoWorktree(s.basePath) ? s.basePath : null,
|
|
1208
1257
|
originalBasePath: s.originalBasePath,
|
|
1209
1258
|
stepMode: s.stepMode,
|
|
@@ -1211,20 +1260,15 @@ export async function pauseAuto(
|
|
|
1211
1260
|
sessionFile: s.pausedSessionFile,
|
|
1212
1261
|
unitType: s.currentUnit?.type ?? undefined,
|
|
1213
1262
|
unitId: s.currentUnit?.id ?? undefined,
|
|
1214
|
-
activeEngineId: s.activeEngineId,
|
|
1263
|
+
activeEngineId: s.activeEngineId ?? undefined,
|
|
1215
1264
|
activeRunDir: s.activeRunDir,
|
|
1216
1265
|
autoStartTime: s.autoStartTime,
|
|
1217
1266
|
milestoneLock: s.sessionMilestoneLock ?? undefined,
|
|
1218
1267
|
};
|
|
1219
|
-
|
|
1220
|
-
atomicWriteSync(
|
|
1221
|
-
join(runtimeDir, "paused-session.json"),
|
|
1222
|
-
JSON.stringify(pausedMeta, null, 2),
|
|
1223
|
-
"utf-8",
|
|
1224
|
-
);
|
|
1268
|
+
setRuntimeKv("global", "", PAUSED_SESSION_KV_KEY, pausedMeta);
|
|
1225
1269
|
} catch (err) {
|
|
1226
1270
|
// Non-fatal — resume will still work via full bootstrap, just without worktree context
|
|
1227
|
-
logWarning("engine", `paused-session
|
|
1271
|
+
logWarning("engine", `paused-session DB write failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
1228
1272
|
}
|
|
1229
1273
|
|
|
1230
1274
|
// Close out the current unit so its runtime record doesn't stay at "dispatched"
|
|
@@ -1507,8 +1551,10 @@ export async function startAuto(
|
|
|
1507
1551
|
ctx.ui.notify("Recovered unfinished migration (.gsd.migrating → .gsd).", "info");
|
|
1508
1552
|
}
|
|
1509
1553
|
|
|
1510
|
-
const freshStartAssessment = interruptedAssessment
|
|
1511
|
-
??
|
|
1554
|
+
const freshStartAssessment = await (interruptedAssessment
|
|
1555
|
+
?? (() => {
|
|
1556
|
+
return ensureDbOpen(base).then(() => assessInterruptedSession(base));
|
|
1557
|
+
})());
|
|
1512
1558
|
|
|
1513
1559
|
if (freshStartAssessment.classification === "running") {
|
|
1514
1560
|
const pid = freshStartAssessment.lock?.pid;
|
|
@@ -1523,10 +1569,20 @@ export async function startAuto(
|
|
|
1523
1569
|
|
|
1524
1570
|
// If resuming from paused state, just re-activate and dispatch next unit.
|
|
1525
1571
|
// Check persisted paused-session first (#1383) — survives /exit.
|
|
1572
|
+
// Phase C pt 2: persisted in runtime_kv (global scope) instead of
|
|
1573
|
+
// runtime/paused-session.json. The `clearPausedSession` helper
|
|
1574
|
+
// replaces every prior unlinkSync(pausedPath) call.
|
|
1575
|
+
const clearPausedSession = (logTag: string): void => {
|
|
1576
|
+
try {
|
|
1577
|
+
deleteRuntimeKv("global", "", PAUSED_SESSION_KV_KEY);
|
|
1578
|
+
} catch (err) {
|
|
1579
|
+
logWarning("session", `${logTag}: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
1580
|
+
}
|
|
1581
|
+
};
|
|
1582
|
+
|
|
1526
1583
|
if (!s.paused) {
|
|
1527
1584
|
try {
|
|
1528
1585
|
const meta = freshStartAssessment.pausedSession ?? readPausedSessionMetadata(base);
|
|
1529
|
-
const pausedPath = join(gsdRoot(base), "runtime", "paused-session.json");
|
|
1530
1586
|
if (meta?.activeEngineId && meta.activeEngineId !== "dev") {
|
|
1531
1587
|
// Custom workflow resume — restore engine state
|
|
1532
1588
|
s.activeEngineId = meta.activeEngineId;
|
|
@@ -1536,11 +1592,6 @@ export async function startAuto(
|
|
|
1536
1592
|
s.autoStartTime = meta.autoStartTime || Date.now();
|
|
1537
1593
|
s.sessionMilestoneLock = meta.milestoneLock ?? null;
|
|
1538
1594
|
s.paused = true;
|
|
1539
|
-
try { unlinkSync(pausedPath); } catch (e) {
|
|
1540
|
-
if ((e as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
1541
|
-
logWarning("session", `pause file cleanup failed: ${e instanceof Error ? e.message : String(e)}`, { file: "auto.ts" });
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
1595
|
ctx.ui.notify(
|
|
1545
1596
|
`Resuming paused custom workflow${meta.activeRunDir ? ` (${meta.activeRunDir})` : ""}.`,
|
|
1546
1597
|
"info",
|
|
@@ -1581,11 +1632,7 @@ export async function startAuto(
|
|
|
1581
1632
|
}
|
|
1582
1633
|
}
|
|
1583
1634
|
if (!mDir || summaryIsTerminal) {
|
|
1584
|
-
|
|
1585
|
-
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
1586
|
-
logWarning("session", `pause file cleanup failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1635
|
+
clearPausedSession("paused-session DB cleanup failed (milestone gone/complete)");
|
|
1589
1636
|
ctx.ui.notify(
|
|
1590
1637
|
`Paused milestone ${meta.milestoneId} is ${!mDir ? "missing" : "already complete"}. Starting fresh.`,
|
|
1591
1638
|
"info",
|
|
@@ -1616,22 +1663,15 @@ export async function startAuto(
|
|
|
1616
1663
|
: (s.originalBasePath || base);
|
|
1617
1664
|
rebuildScope(rawForScope, s.currentMilestoneId);
|
|
1618
1665
|
}
|
|
1619
|
-
try { unlinkSync(pausedPath); } catch (e) {
|
|
1620
|
-
if ((e as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
1621
|
-
logWarning("session", `pause file cleanup failed: ${e instanceof Error ? e.message : String(e)}`, { file: "auto.ts" });
|
|
1622
|
-
}
|
|
1623
|
-
}
|
|
1624
1666
|
ctx.ui.notify(
|
|
1625
1667
|
`Resuming paused session for ${meta.milestoneId}${meta.worktreePath && existsSync(meta.worktreePath) ? ` (worktree)` : ""}.`,
|
|
1626
1668
|
"info",
|
|
1627
1669
|
);
|
|
1628
1670
|
}
|
|
1629
|
-
} else if (
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1671
|
+
} else if (meta) {
|
|
1672
|
+
// Stale paused-session metadata that the assessment chose not to
|
|
1673
|
+
// resume — clean it up so the next bootstrap starts fresh.
|
|
1674
|
+
clearPausedSession("stale paused-session DB cleanup failed");
|
|
1635
1675
|
}
|
|
1636
1676
|
}
|
|
1637
1677
|
} catch (err) {
|
|
@@ -1811,19 +1851,23 @@ export async function startAuto(
|
|
|
1811
1851
|
s.pausedSessionFile = null;
|
|
1812
1852
|
}
|
|
1813
1853
|
|
|
1854
|
+
captureProjectRootEnv(s.originalBasePath || s.basePath);
|
|
1855
|
+
registerAutoWorkerForSession(s);
|
|
1814
1856
|
updateSessionLock(
|
|
1815
1857
|
lockBase(),
|
|
1816
1858
|
"resuming",
|
|
1817
1859
|
s.currentMilestoneId ?? "unknown",
|
|
1818
1860
|
);
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1861
|
+
if (s.workerId) {
|
|
1862
|
+
writeLock(
|
|
1863
|
+
lockBase(),
|
|
1864
|
+
"resuming",
|
|
1865
|
+
s.currentMilestoneId ?? "unknown",
|
|
1866
|
+
);
|
|
1867
|
+
clearPausedSession("paused-session DB cleanup failed (resume activation)");
|
|
1868
|
+
}
|
|
1824
1869
|
pi.events.emit(CMUX_CHANNELS.LOG, { preferences: loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, message: s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", level: "progress" });
|
|
1825
1870
|
|
|
1826
|
-
captureProjectRootEnv(s.originalBasePath || s.basePath);
|
|
1827
1871
|
startAutoCommandPolling(s.basePath);
|
|
1828
1872
|
await runAutoLoopWithUok({
|
|
1829
1873
|
ctx,
|
|
@@ -1862,6 +1906,7 @@ export async function startAuto(
|
|
|
1862
1906
|
rebuildScope(s.basePath, s.currentMilestoneId);
|
|
1863
1907
|
|
|
1864
1908
|
captureProjectRootEnv(s.originalBasePath || s.basePath);
|
|
1909
|
+
registerAutoWorkerForSession(s);
|
|
1865
1910
|
try {
|
|
1866
1911
|
pi.events.emit(CMUX_CHANNELS.SIDEBAR, { action: "sync" as const, preferences: loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, state: await deriveState(s.basePath) });
|
|
1867
1912
|
} catch (err) {
|
|
@@ -571,8 +571,13 @@ export function registerHooks(
|
|
|
571
571
|
const currentPendingGate = getPendingGate();
|
|
572
572
|
if (currentPendingGate) {
|
|
573
573
|
if (details?.cancelled || !details?.response) {
|
|
574
|
-
// Gate stays pending.
|
|
575
|
-
//
|
|
574
|
+
// Gate stays pending. Direct the agent to the most reliable recovery
|
|
575
|
+
// path — re-calling ask_user_questions with the same gate id — without
|
|
576
|
+
// misrepresenting the plain-text path. The plain-text path also works
|
|
577
|
+
// (isExplicitApprovalResponse on the next before_agent_start clears
|
|
578
|
+
// the gate when the user replies with an approval keyword), but the
|
|
579
|
+
// structured re-ask is more deterministic and gives the user a clear UI.
|
|
580
|
+
resetToolCallLoopGuard();
|
|
576
581
|
return {
|
|
577
582
|
content: [{
|
|
578
583
|
type: "text" as const,
|
|
@@ -580,8 +585,8 @@ export function registerHooks(
|
|
|
580
585
|
`HARD BLOCK: approval gate "${currentPendingGate}" is still pending.`,
|
|
581
586
|
"No user response was received for the confirmation question.",
|
|
582
587
|
"Do not infer approval from earlier or prior messages.",
|
|
583
|
-
"Do not proceed, write files, save artifacts, or call
|
|
584
|
-
|
|
588
|
+
"Do not proceed, write files, save artifacts, or call other tools.",
|
|
589
|
+
`Re-call ask_user_questions with the same gate question id ("${currentPendingGate}") and wait for the user's response.`,
|
|
585
590
|
].join(" "),
|
|
586
591
|
}],
|
|
587
592
|
};
|