gsd-pi 2.80.0-dev.e146beb20 → 2.80.0-dev.e6c48c3af
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/phases.js +59 -21
- package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +17 -2
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto-prompts.js +13 -1
- package/dist/resources/extensions/gsd/auto-recovery.js +43 -1
- package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
- package/dist/resources/extensions/gsd/auto.js +84 -5
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +21 -2
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +27 -20
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +75 -4
- package/dist/resources/extensions/gsd/clean-root-preflight.js +24 -6
- package/dist/resources/extensions/gsd/context-budget.js +37 -2
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +39 -0
- package/dist/resources/extensions/gsd/db-base-schema.js +4 -2
- package/dist/resources/extensions/gsd/db-migration-steps.js +6 -0
- package/dist/resources/extensions/gsd/git-service.js +36 -4
- package/dist/resources/extensions/gsd/gsd-db.js +46 -13
- package/dist/resources/extensions/gsd/guided-flow.js +33 -4
- package/dist/resources/extensions/gsd/memory-store.js +69 -12
- package/dist/resources/extensions/gsd/migrate/command.js +40 -1
- package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
- package/dist/resources/extensions/gsd/pre-execution-checks.js +7 -0
- package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/dist/resources/extensions/gsd/quick.js +34 -2
- package/dist/resources/extensions/gsd/tools/context-mode-tool-result.js +15 -0
- package/dist/resources/extensions/gsd/tools/exec-search-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +3 -15
- package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
- package/dist/resources/extensions/gsd/tools/resume-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
- package/dist/resources/extensions/gsd/unit-context-composer.js +12 -3
- package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +33 -17
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
- 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 +16 -16
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +3 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +22 -17
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +75 -2
- package/packages/mcp-server/src/workflow-tools.ts +30 -16
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +32 -0
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +15 -0
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +12 -3
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +3 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +3 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +12 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +13 -5
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +36 -0
- package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +18 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +14 -3
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +3 -1
- package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
- package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +2 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +5 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +12 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +4 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +22 -7
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +18 -8
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/tui.ts +20 -8
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -2
- package/src/resources/extensions/gsd/auto/phases.ts +85 -35
- package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +22 -2
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +17 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +54 -0
- package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
- package/src/resources/extensions/gsd/auto.ts +96 -4
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +21 -1
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +27 -19
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +88 -4
- package/src/resources/extensions/gsd/clean-root-preflight.ts +32 -7
- package/src/resources/extensions/gsd/context-budget.ts +44 -2
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +41 -0
- package/src/resources/extensions/gsd/db-base-schema.ts +4 -2
- package/src/resources/extensions/gsd/db-migration-steps.ts +8 -0
- package/src/resources/extensions/gsd/git-service.ts +46 -8
- package/src/resources/extensions/gsd/gsd-db.ts +50 -13
- package/src/resources/extensions/gsd/guided-flow.ts +49 -4
- package/src/resources/extensions/gsd/memory-store.ts +77 -12
- package/src/resources/extensions/gsd/migrate/command.ts +47 -1
- package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
- package/src/resources/extensions/gsd/pre-execution-checks.ts +7 -0
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/src/resources/extensions/gsd/quick.ts +37 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +215 -1
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +56 -13
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +166 -4
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +15 -6
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
- package/src/resources/extensions/gsd/tests/exec-history.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +239 -1
- package/src/resources/extensions/gsd/tests/memory-decay-factor.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +136 -4
- package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +63 -1
- package/src/resources/extensions/gsd/tools/context-mode-tool-result.ts +25 -0
- package/src/resources/extensions/gsd/tools/exec-search-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -23
- package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
- package/src/resources/extensions/gsd/tools/resume-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
- package/src/resources/extensions/gsd/unit-context-composer.ts +19 -4
- package/src/resources/extensions/gsd/unit-runtime.ts +11 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +36 -15
- /package/dist/web/standalone/.next/static/{y73quA-XdLo9n41nxphjW → 4dQ9NTZJ8pEvFwBgDUX93}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{y73quA-XdLo9n41nxphjW → 4dQ9NTZJ8pEvFwBgDUX93}/_ssgManifest.js +0 -0
|
@@ -306,7 +306,7 @@ export async function dispatchDirectPhase(
|
|
|
306
306
|
return;
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
-
const result = await ctx.newSession();
|
|
309
|
+
const result = await ctx.newSession({ cwd: dispatchBase });
|
|
310
310
|
if (result.cancelled) {
|
|
311
311
|
ctx.ui.notify("Session creation cancelled.", "warning");
|
|
312
312
|
return;
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
|
|
36
36
|
import { readPhaseAnchor, formatAnchorForPrompt } from "./phase-anchor.js";
|
|
37
37
|
import { composeContextModeInstructions, composeInlinedContext, type ArtifactResolver, type ContextModeRenderMode } from "./unit-context-composer.js";
|
|
38
|
+
import { readCompactionSnapshot } from "./compaction-snapshot.js";
|
|
38
39
|
import { logWarning } from "./workflow-logger.js";
|
|
39
40
|
import { inlineGraphSubgraph } from "./graph-context.js";
|
|
40
41
|
import { buildExtractionStepsBlock } from "./commands-extract-learnings.js";
|
|
@@ -205,13 +206,28 @@ function renderContextModeForPrompt(
|
|
|
205
206
|
});
|
|
206
207
|
}
|
|
207
208
|
|
|
209
|
+
function renderContextModeBlockForPrompt(
|
|
210
|
+
unitType: string,
|
|
211
|
+
base: string,
|
|
212
|
+
renderMode: ContextModeRenderMode = "standalone",
|
|
213
|
+
): string {
|
|
214
|
+
const contextMode = renderContextModeForPrompt(unitType, base, renderMode);
|
|
215
|
+
if (!contextMode) return "";
|
|
216
|
+
if (renderMode === "nested") return contextMode;
|
|
217
|
+
|
|
218
|
+
const snapshot = readCompactionSnapshot(base);
|
|
219
|
+
if (!snapshot?.trim()) return contextMode;
|
|
220
|
+
|
|
221
|
+
return `${contextMode}\n\n## Context Snapshot\nSource: \`.gsd/last-snapshot.md\`\n\n${snapshot.trimEnd()}`;
|
|
222
|
+
}
|
|
223
|
+
|
|
208
224
|
function prependContextModeToBlock(
|
|
209
225
|
unitType: string,
|
|
210
226
|
base: string,
|
|
211
227
|
block: string,
|
|
212
228
|
renderMode: ContextModeRenderMode = "standalone",
|
|
213
229
|
): string {
|
|
214
|
-
const contextMode =
|
|
230
|
+
const contextMode = renderContextModeBlockForPrompt(unitType, base, renderMode);
|
|
215
231
|
if (!contextMode) return block;
|
|
216
232
|
if (!block.trim()) return contextMode;
|
|
217
233
|
return `${contextMode}\n\n${block}`;
|
|
@@ -67,6 +67,60 @@ export {
|
|
|
67
67
|
|
|
68
68
|
// ─── Artifact Resolution & Verification ───────────────────────────────────────
|
|
69
69
|
|
|
70
|
+
export type ArtifactRecoveryDbRefreshResult =
|
|
71
|
+
| { ok: true }
|
|
72
|
+
| { ok: false; fatal: boolean; message: string; reason: string };
|
|
73
|
+
|
|
74
|
+
export function refreshRecoveryDbForArtifact(
|
|
75
|
+
unitType: string,
|
|
76
|
+
unitId: string,
|
|
77
|
+
): ArtifactRecoveryDbRefreshResult {
|
|
78
|
+
if (unitType !== "plan-slice" && unitType !== "execute-task") return { ok: true };
|
|
79
|
+
if (!isDbAvailable()) return { ok: true };
|
|
80
|
+
|
|
81
|
+
if (!refreshOpenDatabaseFromDisk()) {
|
|
82
|
+
return {
|
|
83
|
+
ok: false,
|
|
84
|
+
fatal: unitType === "execute-task",
|
|
85
|
+
reason: `${unitType}-db-refresh-failed`,
|
|
86
|
+
message: `Stuck recovery found ${unitType} ${unitId} artifacts, but the DB refresh failed.`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (unitType !== "execute-task") return { ok: true };
|
|
91
|
+
|
|
92
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
93
|
+
if (!mid || !sid || !tid) {
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
fatal: true,
|
|
97
|
+
reason: "execute-task-invalid-unit-id",
|
|
98
|
+
message: `Stuck recovery found execute-task ${unitId} artifacts, but the unit id could not be parsed for DB verification.`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const task = getTask(mid, sid, tid);
|
|
103
|
+
if (!task) {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
fatal: true,
|
|
107
|
+
reason: "execute-task-artifact-db-missing",
|
|
108
|
+
message: `Stuck recovery found execute-task ${unitId} artifacts, but no matching DB task row exists after refresh.`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!isClosedStatus(task.status)) {
|
|
113
|
+
return {
|
|
114
|
+
ok: false,
|
|
115
|
+
fatal: true,
|
|
116
|
+
reason: "execute-task-artifact-db-mismatch",
|
|
117
|
+
message: `Stuck recovery found execute-task ${unitId} artifacts, but the DB task status is still '${task.status}' after refresh.`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { ok: true };
|
|
122
|
+
}
|
|
123
|
+
|
|
70
124
|
function hasCapturedWorkflowPrefs(base: string): boolean {
|
|
71
125
|
const prefsPath = resolveExpectedArtifactPath("workflow-preferences", "WORKFLOW-PREFS", base);
|
|
72
126
|
if (!prefsPath || !existsSync(prefsPath)) return false;
|
|
@@ -32,6 +32,7 @@ let _currentSigtermHandler: (() => void) | null = null;
|
|
|
32
32
|
export function registerSigtermHandler(
|
|
33
33
|
currentBasePath: string,
|
|
34
34
|
previousHandler: (() => void) | null,
|
|
35
|
+
onSignalCleanup?: () => void,
|
|
35
36
|
): () => void {
|
|
36
37
|
// Remove the explicitly-passed previous handler
|
|
37
38
|
if (previousHandler) {
|
|
@@ -43,6 +44,12 @@ export function registerSigtermHandler(
|
|
|
43
44
|
for (const sig of CLEANUP_SIGNALS) process.off(sig, _currentSigtermHandler);
|
|
44
45
|
}
|
|
45
46
|
const handler = () => {
|
|
47
|
+
try {
|
|
48
|
+
onSignalCleanup?.();
|
|
49
|
+
} catch {
|
|
50
|
+
void 0;
|
|
51
|
+
// Signal cleanup is best-effort; lock cleanup and process exit still run.
|
|
52
|
+
}
|
|
46
53
|
clearLock(currentBasePath);
|
|
47
54
|
releaseSessionLock(currentBasePath);
|
|
48
55
|
process.exit(0);
|
|
@@ -71,14 +71,14 @@ export async function recoverTimedOutUnit(
|
|
|
71
71
|
recovery: status,
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
const durableComplete = status.summaryExists && status.taskChecked && status.nextActionAdvanced;
|
|
74
|
+
const durableComplete = status.dbComplete || (status.summaryExists && status.taskChecked && status.nextActionAdvanced);
|
|
75
75
|
if (durableComplete) {
|
|
76
76
|
writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
|
|
77
77
|
phase: "finalized",
|
|
78
78
|
recovery: status,
|
|
79
79
|
});
|
|
80
80
|
ctx.ui.notify(
|
|
81
|
-
`${reason === "idle" ? "Idle" : "Timeout"} recovery: ${unitType} ${unitId} already completed
|
|
81
|
+
`${reason === "idle" ? "Idle" : "Timeout"} recovery: ${unitType} ${unitId} already completed. Continuing auto-mode. (attempt ${attemptNumber})`,
|
|
82
82
|
"info",
|
|
83
83
|
);
|
|
84
84
|
unitRecoveryCount.delete(recoveryKey);
|
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
isLockProcessAlive,
|
|
60
60
|
formatCrashInfo,
|
|
61
61
|
emitCrashRecoveredUnitEnd,
|
|
62
|
+
emitOpenUnitEndForUnit,
|
|
62
63
|
} from "./crash-recovery.js";
|
|
63
64
|
import {
|
|
64
65
|
acquireSessionLock,
|
|
@@ -200,6 +201,8 @@ import {
|
|
|
200
201
|
detectWorkingTreeActivity,
|
|
201
202
|
} from "./auto-supervisor.js";
|
|
202
203
|
import { isDbAvailable, getMilestone } from "./gsd-db.js";
|
|
204
|
+
import { markLatestActiveForWorkerCanceled } from "./db/unit-dispatches.js";
|
|
205
|
+
import { writeUnitRuntimeRecord } from "./unit-runtime.js";
|
|
203
206
|
import { countPendingCaptures } from "./captures.js";
|
|
204
207
|
import { CMUX_CHANNELS, type CmuxLogLevel } from "../shared/cmux-events.js";
|
|
205
208
|
import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
|
|
@@ -242,6 +245,20 @@ import {
|
|
|
242
245
|
type WorktreeResolverDeps,
|
|
243
246
|
} from "./worktree-resolver.js";
|
|
244
247
|
import { reorderForCaching } from "./prompt-ordering.js";
|
|
248
|
+
import { initTokenCounter } from "./token-counter.js";
|
|
249
|
+
|
|
250
|
+
// Warm the tiktoken encoder at extension startup so context-budget computations
|
|
251
|
+
// can use accurate token counts via countTokensSync without paying the load
|
|
252
|
+
// cost mid-prompt-build. Fire-and-forget — failure falls back to the
|
|
253
|
+
// provider-aware char-ratio estimator already used by getCharsPerToken().
|
|
254
|
+
// Catch rejections explicitly: an unhandled rejection at module-import time
|
|
255
|
+
// can destabilize startup before the engine logger is configured.
|
|
256
|
+
void initTokenCounter().catch((err) => {
|
|
257
|
+
logWarning(
|
|
258
|
+
"engine",
|
|
259
|
+
`token counter warm-up failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
260
|
+
);
|
|
261
|
+
});
|
|
245
262
|
|
|
246
263
|
// ─── Session State ─────────────────────────────────────────────────────────
|
|
247
264
|
|
|
@@ -293,11 +310,15 @@ const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
|
|
|
293
310
|
* the DB is unavailable (e.g. fresh project before init) we skip registration
|
|
294
311
|
* silently rather than blocking session start.
|
|
295
312
|
*/
|
|
296
|
-
function registerAutoWorkerForSession(
|
|
313
|
+
function registerAutoWorkerForSession(
|
|
314
|
+
session: AutoSession,
|
|
315
|
+
projectRootOverride?: string,
|
|
316
|
+
): void {
|
|
297
317
|
if (session.workerId) return; // already registered (e.g. resume re-runs)
|
|
298
318
|
try {
|
|
299
319
|
const projectRootRealpath = normalizeRealPath(
|
|
300
|
-
|
|
320
|
+
projectRootOverride
|
|
321
|
+
?? session.scope?.workspace.projectRoot
|
|
301
322
|
?? (session.originalBasePath || session.basePath),
|
|
302
323
|
);
|
|
303
324
|
session.workerId = registerAutoWorker({ projectRootRealpath });
|
|
@@ -501,9 +522,54 @@ export {
|
|
|
501
522
|
getBudgetEnforcementAction,
|
|
502
523
|
} from "./auto-budget.js";
|
|
503
524
|
|
|
525
|
+
function closeOutSignalInterruptedUnit(currentBasePath: string): void {
|
|
526
|
+
const currentUnit = s.currentUnit;
|
|
527
|
+
if (!currentUnit) return;
|
|
528
|
+
|
|
529
|
+
const reason = "Auto-mode process received a termination signal";
|
|
530
|
+
const errorContext: ErrorContext = {
|
|
531
|
+
message: reason,
|
|
532
|
+
category: "aborted",
|
|
533
|
+
isTransient: false,
|
|
534
|
+
};
|
|
535
|
+
const basePath = s.basePath || currentBasePath;
|
|
536
|
+
|
|
537
|
+
try {
|
|
538
|
+
emitOpenUnitEndForUnit(basePath, currentUnit.type, currentUnit.id, "cancelled", errorContext);
|
|
539
|
+
} catch (err) {
|
|
540
|
+
logWarning("engine", `signal unit-end cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
try {
|
|
544
|
+
writeUnitRuntimeRecord(basePath, currentUnit.type, currentUnit.id, currentUnit.startedAt, {
|
|
545
|
+
phase: "crashed",
|
|
546
|
+
lastProgressAt: Date.now(),
|
|
547
|
+
lastProgressKind: "signal",
|
|
548
|
+
});
|
|
549
|
+
} catch (err) {
|
|
550
|
+
logWarning("engine", `signal runtime cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
try {
|
|
554
|
+
if (s.workerId) markLatestActiveForWorkerCanceled(s.workerId, "signal-exit");
|
|
555
|
+
} catch (err) {
|
|
556
|
+
logWarning("engine", `signal dispatch cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
try {
|
|
560
|
+
resolveAgentEndCancelled(errorContext);
|
|
561
|
+
} catch (err) {
|
|
562
|
+
logWarning("engine", `signal resolve cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
504
566
|
/** Wrapper: register SIGTERM handler and store reference. */
|
|
505
567
|
function registerSigtermHandler(currentBasePath: string): void {
|
|
506
|
-
s.sigtermHandler = _registerSigtermHandler(
|
|
568
|
+
s.sigtermHandler = _registerSigtermHandler(
|
|
569
|
+
currentBasePath,
|
|
570
|
+
s.sigtermHandler,
|
|
571
|
+
() => closeOutSignalInterruptedUnit(currentBasePath),
|
|
572
|
+
);
|
|
507
573
|
}
|
|
508
574
|
|
|
509
575
|
/** Wrapper: deregister SIGTERM handler and clear reference. */
|
|
@@ -968,6 +1034,8 @@ export async function stopAuto(
|
|
|
968
1034
|
if (s.workerId) {
|
|
969
1035
|
markWorkerStopping(s.workerId);
|
|
970
1036
|
}
|
|
1037
|
+
s.workerId = null;
|
|
1038
|
+
s.milestoneLeaseToken = null;
|
|
971
1039
|
} catch (e) {
|
|
972
1040
|
debugLog("stop-cleanup-coordination", { error: e instanceof Error ? e.message : String(e) });
|
|
973
1041
|
}
|
|
@@ -1105,6 +1173,21 @@ export async function stopAuto(
|
|
|
1105
1173
|
debugLog("stop-cleanup-basepath", { error: e instanceof Error ? e.message : String(e) });
|
|
1106
1174
|
}
|
|
1107
1175
|
|
|
1176
|
+
// Re-root the active command session/tool runtime after worktree teardown.
|
|
1177
|
+
// mergeAndExit restores process.cwd(), but AgentSession has already captured
|
|
1178
|
+
// its own cwd for tools and system prompt; refresh it before returning to the
|
|
1179
|
+
// user so follow-up commands do not target a removed milestone worktree.
|
|
1180
|
+
if (s.originalBasePath && ctx && s.cmdCtx) {
|
|
1181
|
+
try {
|
|
1182
|
+
const result = await s.cmdCtx.newSession({ cwd: s.basePath });
|
|
1183
|
+
if (result.cancelled) {
|
|
1184
|
+
logWarning("engine", "post-stop session re-root was cancelled", { file: "auto.ts", basePath: s.basePath });
|
|
1185
|
+
}
|
|
1186
|
+
} catch (err) {
|
|
1187
|
+
logWarning("engine", `post-stop session re-root failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts", basePath: s.basePath });
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1108
1191
|
// ── Step 8: Ledger notification ──
|
|
1109
1192
|
try {
|
|
1110
1193
|
const ledger = getLedger();
|
|
@@ -1971,6 +2054,10 @@ export async function startAuto(
|
|
|
1971
2054
|
: new URL("../../../resource-loader.js", import.meta.url).href;
|
|
1972
2055
|
const { initResources } = await import(resourceLoaderPath);
|
|
1973
2056
|
initResources(agentDir);
|
|
2057
|
+
// initResources() uses synchronous fs APIs, so the prompt-template cache
|
|
2058
|
+
// can be primed immediately — no need for the legacy 1s setTimeout deferral.
|
|
2059
|
+
const { primeCache } = await import("./prompt-loader.js");
|
|
2060
|
+
primeCache();
|
|
1974
2061
|
// Open the project DB before rebuild/derive so resume uses DB-backed
|
|
1975
2062
|
// state instead of falling back to stale markdown parsing (#2940).
|
|
1976
2063
|
await openProjectDbIfPresent(s.basePath);
|
|
@@ -2057,6 +2144,11 @@ export async function startAuto(
|
|
|
2057
2144
|
buildResolver,
|
|
2058
2145
|
};
|
|
2059
2146
|
|
|
2147
|
+
// Register the worker before bootstrap enters a milestone worktree.
|
|
2148
|
+
// This ensures enterMilestone can claim a lease and seed dispatch claims
|
|
2149
|
+
// for crash-recovery fidelity (#5405).
|
|
2150
|
+
registerAutoWorkerForSession(s, base);
|
|
2151
|
+
|
|
2060
2152
|
const ready = await bootstrapAutoSession(
|
|
2061
2153
|
s,
|
|
2062
2154
|
ctx,
|
|
@@ -2241,7 +2333,7 @@ export async function dispatchHookUnit(
|
|
|
2241
2333
|
return false;
|
|
2242
2334
|
}
|
|
2243
2335
|
|
|
2244
|
-
const result = await s.cmdCtx!.newSession();
|
|
2336
|
+
const result = await s.cmdCtx!.newSession({ cwd: s.basePath });
|
|
2245
2337
|
if (result.cancelled) {
|
|
2246
2338
|
await stopAuto(ctx, pi);
|
|
2247
2339
|
return false;
|
|
@@ -153,9 +153,29 @@ export async function handleAgentEnd(
|
|
|
153
153
|
if (maybeHandleEmptyIntentTurn(event, isAutoActive())) return;
|
|
154
154
|
|
|
155
155
|
if (!isAutoActive()) return;
|
|
156
|
-
if (isSessionSwitchInFlight()) return;
|
|
157
156
|
|
|
158
157
|
const lastMsg = event.messages[event.messages.length - 1];
|
|
158
|
+
if (isSessionSwitchInFlight()) {
|
|
159
|
+
if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "error") {
|
|
160
|
+
const rawErrorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
|
|
161
|
+
if (isUserInitiatedAbortMessage(rawErrorMsg)) {
|
|
162
|
+
resolveAgentEndCancelled({
|
|
163
|
+
message: rawErrorMsg,
|
|
164
|
+
category: "aborted",
|
|
165
|
+
isTransient: false,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
} else if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "aborted") {
|
|
169
|
+
const content = "content" in lastMsg ? lastMsg.content : undefined;
|
|
170
|
+
const hasEmptyContent = Array.isArray(content) && content.length === 0;
|
|
171
|
+
const hasErrorMessage = "errorMessage" in lastMsg && !!lastMsg.errorMessage;
|
|
172
|
+
if (!hasEmptyContent || hasErrorMessage) {
|
|
173
|
+
resolveAgentEndCancelled(_buildAbortedPauseContext(lastMsg as { errorMessage?: unknown }));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
159
179
|
if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "aborted") {
|
|
160
180
|
// Empty content with aborted stopReason is a non-fatal agent stop (the LLM
|
|
161
181
|
// chose to end without producing output). Only pause on genuine fatal aborts
|
|
@@ -6,23 +6,36 @@
|
|
|
6
6
|
import { Type } from "@sinclair/typebox";
|
|
7
7
|
import type { ExtensionAPI } from "@gsd/pi-coding-agent";
|
|
8
8
|
|
|
9
|
+
async function loadContextModePreferences(baseDir: string) {
|
|
10
|
+
const [{ loadEffectiveGSDPreferences }, { logWarning }] = await Promise.all([
|
|
11
|
+
import("../preferences.js"),
|
|
12
|
+
import("../workflow-logger.js"),
|
|
13
|
+
]);
|
|
14
|
+
try {
|
|
15
|
+
return loadEffectiveGSDPreferences(baseDir)?.preferences ?? null;
|
|
16
|
+
} catch (err) {
|
|
17
|
+
logWarning("tool", `Context Mode tool could not load preferences: ${err instanceof Error ? err.message : String(err)}`);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
export function registerExecTools(pi: ExtensionAPI): void {
|
|
10
23
|
pi.registerTool({
|
|
11
24
|
name: "gsd_exec",
|
|
12
25
|
label: "Exec (Sandboxed)",
|
|
13
26
|
description:
|
|
14
|
-
"Run a short script (bash/node/python) in a subprocess.
|
|
27
|
+
"Run a short script (bash/node/python) in a subprocess. Capped stdout/stderr and metadata persist to " +
|
|
15
28
|
".gsd/exec/<id>.{stdout,stderr,meta.json}; only a short digest returns in context. Use " +
|
|
16
29
|
"this instead of reading many files or emitting large tool outputs — e.g. have the script " +
|
|
17
30
|
"count/grep/summarize and log the finding. Enabled by default; opt out via " +
|
|
18
31
|
"preferences.context_mode.enabled=false.",
|
|
19
32
|
promptSnippet:
|
|
20
|
-
"Run a bash/node/python script in a sandbox;
|
|
33
|
+
"Run a bash/node/python script in a sandbox; capped output is saved to disk and only a digest returns",
|
|
21
34
|
promptGuidelines: [
|
|
22
35
|
"Prefer gsd_exec for analyses that would otherwise read >3 files or produce large tool output.",
|
|
23
36
|
"Write scripts that log the finding (counts, matches, summaries) rather than raw dumps.",
|
|
24
37
|
"The digest is the last ~300 chars of stdout — size your log output accordingly.",
|
|
25
|
-
"Need
|
|
38
|
+
"Need persisted output? Read the stdout_path returned in details (file on local disk).",
|
|
26
39
|
],
|
|
27
40
|
parameters: Type.Object({
|
|
28
41
|
runtime: Type.Union(
|
|
@@ -40,20 +53,11 @@ export function registerExecTools(pi: ExtensionAPI): void {
|
|
|
40
53
|
),
|
|
41
54
|
}),
|
|
42
55
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
import("../preferences.js"),
|
|
46
|
-
import("../workflow-logger.js"),
|
|
47
|
-
]);
|
|
48
|
-
let prefs: ReturnType<typeof loadEffectiveGSDPreferences> | null = null;
|
|
49
|
-
try {
|
|
50
|
-
prefs = loadEffectiveGSDPreferences();
|
|
51
|
-
} catch (err) {
|
|
52
|
-
logWarning("tool", `gsd_exec could not load preferences: ${err instanceof Error ? err.message : String(err)}`);
|
|
53
|
-
}
|
|
56
|
+
const { executeGsdExec } = await import("../tools/exec-tool.js");
|
|
57
|
+
const baseDir = process.cwd();
|
|
54
58
|
return executeGsdExec(params as Parameters<typeof executeGsdExec>[0], {
|
|
55
|
-
baseDir
|
|
56
|
-
preferences:
|
|
59
|
+
baseDir,
|
|
60
|
+
preferences: await loadContextModePreferences(baseDir),
|
|
57
61
|
});
|
|
58
62
|
},
|
|
59
63
|
});
|
|
@@ -67,7 +71,7 @@ export function registerExecTools(pi: ExtensionAPI): void {
|
|
|
67
71
|
promptSnippet: "Search prior gsd_exec runs by substring, runtime, or failing-only filter",
|
|
68
72
|
promptGuidelines: [
|
|
69
73
|
"Use this before re-running an expensive analysis — the prior run's stdout file may still answer.",
|
|
70
|
-
"The preview shows the trailing ~300 chars of stdout; read stdout_path for
|
|
74
|
+
"The preview shows the trailing ~300 chars of stdout; read stdout_path for persisted output.",
|
|
71
75
|
],
|
|
72
76
|
parameters: Type.Object({
|
|
73
77
|
query: Type.Optional(Type.String({ description: "Substring matched against id and purpose (case-insensitive)." })),
|
|
@@ -81,8 +85,10 @@ export function registerExecTools(pi: ExtensionAPI): void {
|
|
|
81
85
|
}),
|
|
82
86
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
83
87
|
const { executeExecSearch } = await import("../tools/exec-search-tool.js");
|
|
88
|
+
const baseDir = process.cwd();
|
|
84
89
|
return executeExecSearch(params as Parameters<typeof executeExecSearch>[0], {
|
|
85
|
-
baseDir
|
|
90
|
+
baseDir,
|
|
91
|
+
preferences: await loadContextModePreferences(baseDir),
|
|
86
92
|
});
|
|
87
93
|
},
|
|
88
94
|
});
|
|
@@ -102,8 +108,10 @@ export function registerExecTools(pi: ExtensionAPI): void {
|
|
|
102
108
|
parameters: Type.Object({}),
|
|
103
109
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
104
110
|
const { executeResume } = await import("../tools/resume-tool.js");
|
|
111
|
+
const baseDir = process.cwd();
|
|
105
112
|
return executeResume(params as Parameters<typeof executeResume>[0], {
|
|
106
|
-
baseDir
|
|
113
|
+
baseDir,
|
|
114
|
+
preferences: await loadContextModePreferences(baseDir),
|
|
107
115
|
});
|
|
108
116
|
},
|
|
109
117
|
});
|
|
@@ -31,6 +31,13 @@ import { approvalGateIdForUnit, isExplicitApprovalResponse, shouldPauseForUserAp
|
|
|
31
31
|
let isFirstSession = true;
|
|
32
32
|
let approvalQuestionAbortInFlight = false;
|
|
33
33
|
|
|
34
|
+
interface DeferredApprovalGate {
|
|
35
|
+
gateId: string;
|
|
36
|
+
basePath: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let deferredApprovalGate: DeferredApprovalGate | null = null;
|
|
40
|
+
|
|
34
41
|
async function deriveGsdState(basePath: string) {
|
|
35
42
|
const { deriveState } = await import("../state.js");
|
|
36
43
|
return deriveState(basePath);
|
|
@@ -65,6 +72,66 @@ async function applyDisabledModelProviderPolicy(ctx: ExtensionContext): Promise<
|
|
|
65
72
|
}
|
|
66
73
|
}
|
|
67
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Bridge `context_management.compaction_threshold_percent` from GSD preferences
|
|
77
|
+
* into the agent's runtime compaction settings (#5475). The preference is
|
|
78
|
+
* validated to (0.5, 0.95) at load time, but defense-in-depth normalization
|
|
79
|
+
* here protects against a stale or hand-edited prefs file. Calling with
|
|
80
|
+
* `undefined` clears any prior override so a removed preference does not leak.
|
|
81
|
+
*/
|
|
82
|
+
async function applyCompactionThresholdOverride(ctx: ExtensionContext): Promise<void> {
|
|
83
|
+
try {
|
|
84
|
+
const { loadEffectiveGSDPreferences } = await import("../preferences.js");
|
|
85
|
+
const prefs = loadEffectiveGSDPreferences();
|
|
86
|
+
const raw = prefs?.preferences.context_management?.compaction_threshold_percent;
|
|
87
|
+
const value =
|
|
88
|
+
typeof raw === "number" && Number.isFinite(raw) && raw > 0 && raw < 1 ? raw : undefined;
|
|
89
|
+
ctx.setCompactionThresholdOverride(value);
|
|
90
|
+
} catch {
|
|
91
|
+
// Non-fatal: leave any existing override in place.
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function clearDeferredApprovalGate(basePath?: string): void {
|
|
96
|
+
if (!basePath || deferredApprovalGate?.basePath === basePath) {
|
|
97
|
+
deferredApprovalGate = null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function deferApprovalGate(gateId: string, basePath: string): void {
|
|
102
|
+
deferredApprovalGate = { gateId, basePath };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function activateDeferredApprovalGate(basePath: string): void {
|
|
106
|
+
if (deferredApprovalGate?.basePath !== basePath) return;
|
|
107
|
+
setPendingGate(deferredApprovalGate.gateId, basePath);
|
|
108
|
+
deferredApprovalGate = null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isContextDraftSummarySave(toolName: string, input: unknown): boolean {
|
|
112
|
+
if (toolName !== "gsd_summary_save" && toolName !== "summary_save") return false;
|
|
113
|
+
if (!input || typeof input !== "object") return false;
|
|
114
|
+
return (input as { artifact_type?: unknown }).artifact_type === "CONTEXT-DRAFT";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function shouldBlockDeferredApprovalTool(
|
|
118
|
+
toolName: string,
|
|
119
|
+
input: unknown,
|
|
120
|
+
basePath: string,
|
|
121
|
+
): { block: boolean; reason?: string } {
|
|
122
|
+
if (deferredApprovalGate?.basePath !== basePath) return { block: false };
|
|
123
|
+
if (toolName === "ask_user_questions") return { block: false };
|
|
124
|
+
if (isContextDraftSummarySave(toolName, input)) return { block: false };
|
|
125
|
+
return {
|
|
126
|
+
block: true,
|
|
127
|
+
reason: [
|
|
128
|
+
`HARD BLOCK: Approval question "${deferredApprovalGate.gateId}" has been shown to the user.`,
|
|
129
|
+
`Only CONTEXT-DRAFT persistence may finish in this same assistant turn.`,
|
|
130
|
+
`Wait for the user's answer before calling additional tools.`,
|
|
131
|
+
].join(" "),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
68
135
|
export function resolveNotificationStoreBasePath(cwd: string = process.cwd()): string {
|
|
69
136
|
return resolveWorktreeProjectRoot(cwd);
|
|
70
137
|
}
|
|
@@ -120,9 +187,11 @@ export function registerHooks(
|
|
|
120
187
|
resetWriteGateState(process.cwd());
|
|
121
188
|
resetToolCallLoopGuard();
|
|
122
189
|
approvalQuestionAbortInFlight = false;
|
|
190
|
+
clearDeferredApprovalGate();
|
|
123
191
|
await resetAskUserQuestionsTurnCache();
|
|
124
192
|
await syncServiceTierStatus(ctx);
|
|
125
193
|
await applyDisabledModelProviderPolicy(ctx);
|
|
194
|
+
await applyCompactionThresholdOverride(ctx);
|
|
126
195
|
// Skip MCP auto-prep when running inside an auto-worktree (see session_switch below).
|
|
127
196
|
const { isInAutoWorktree } = await import("../auto-worktree.js");
|
|
128
197
|
if (!isInAutoWorktree(process.cwd())) {
|
|
@@ -168,10 +237,12 @@ export function registerHooks(
|
|
|
168
237
|
initSessionNotifications(ctx);
|
|
169
238
|
resetWriteGateState(process.cwd());
|
|
170
239
|
resetToolCallLoopGuard();
|
|
240
|
+
clearDeferredApprovalGate();
|
|
171
241
|
await resetAskUserQuestionsTurnCache();
|
|
172
242
|
clearDiscussionFlowState(process.cwd());
|
|
173
243
|
await syncServiceTierStatus(ctx);
|
|
174
244
|
await applyDisabledModelProviderPolicy(ctx);
|
|
245
|
+
await applyCompactionThresholdOverride(ctx);
|
|
175
246
|
// Skip MCP auto-prep when running inside an auto-worktree. The worktree
|
|
176
247
|
// already has .mcp.json from createAutoWorktree, and re-running the writer
|
|
177
248
|
// post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
|
|
@@ -203,6 +274,7 @@ export function registerHooks(
|
|
|
203
274
|
if (milestoneId) markDepthVerified(milestoneId, beforeAgentBasePath);
|
|
204
275
|
clearPendingGate(beforeAgentBasePath);
|
|
205
276
|
}
|
|
277
|
+
clearDeferredApprovalGate(beforeAgentBasePath);
|
|
206
278
|
|
|
207
279
|
// GSD's own context injection (existing behavior — unchanged).
|
|
208
280
|
const { buildBeforeAgentStartResult } = await import("./system-context.js");
|
|
@@ -253,7 +325,11 @@ export function registerHooks(
|
|
|
253
325
|
resetToolCallLoopGuard();
|
|
254
326
|
await resetAskUserQuestionsTurnCache();
|
|
255
327
|
const { handleAgentEnd } = await import("./agent-end-recovery.js");
|
|
256
|
-
|
|
328
|
+
try {
|
|
329
|
+
await handleAgentEnd(pi, event, ctx);
|
|
330
|
+
} finally {
|
|
331
|
+
activateDeferredApprovalGate(process.cwd());
|
|
332
|
+
}
|
|
257
333
|
});
|
|
258
334
|
|
|
259
335
|
// Squash-merge quick-task branch back to the original branch after the
|
|
@@ -355,15 +431,16 @@ export function registerHooks(
|
|
|
355
431
|
if (!shouldPauseForUserApprovalQuestion(unitType, [event.message])) return;
|
|
356
432
|
|
|
357
433
|
const gateId = approvalGateIdForUnit(unitType, unitId);
|
|
358
|
-
if (gateId)
|
|
434
|
+
if (gateId) deferApprovalGate(gateId, process.cwd());
|
|
359
435
|
|
|
360
436
|
approvalQuestionAbortInFlight = true;
|
|
361
437
|
ctx.ui.notify(
|
|
362
438
|
`${unitType}${unitId ? ` ${unitId}` : ""} is waiting for your approval - pausing before more tool calls run.`,
|
|
363
439
|
"info",
|
|
364
440
|
);
|
|
365
|
-
// The pending gate
|
|
366
|
-
//
|
|
441
|
+
// The durable pending gate is activated at agent_end so same-turn
|
|
442
|
+
// CONTEXT-DRAFT persistence can finish after the text boundary streams.
|
|
443
|
+
// The tool_call hook below still blocks non-draft tools in this turn.
|
|
367
444
|
// Aborting mid-stream eats the model's question text on external CLI
|
|
368
445
|
// providers (Claude Code SDK) because lastTextContent isn't populated
|
|
369
446
|
// from in-flight builder state — the user only ever sees "Claude Code
|
|
@@ -395,6 +472,13 @@ export function registerHooks(
|
|
|
395
472
|
return { block: true, reason: loopCheck.reason };
|
|
396
473
|
}
|
|
397
474
|
|
|
475
|
+
const deferredGateGuard = shouldBlockDeferredApprovalTool(
|
|
476
|
+
toolName,
|
|
477
|
+
event.input,
|
|
478
|
+
discussionBasePath,
|
|
479
|
+
);
|
|
480
|
+
if (deferredGateGuard.block) return deferredGateGuard;
|
|
481
|
+
|
|
398
482
|
// ── Discussion gate enforcement: track pending gate questions ─────────
|
|
399
483
|
// Only gate-shaped ask_user_questions calls should block execution.
|
|
400
484
|
// The gate stays pending until the user selects the approval option.
|