@opengsd/gsd-pi 1.9.0-dev.5c1a3ff2 → 1.9.0-dev.d2070876
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/headless.js +2 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +24 -4
- package/dist/resources/extensions/gsd/auto/run-unit.js +19 -1
- package/dist/resources/extensions/gsd/auto/unit-phase.js +37 -5
- package/dist/resources/extensions/gsd/auto-dispatch.js +2 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +12 -4
- package/dist/resources/extensions/gsd/auto-runtime-state.js +5 -5
- package/dist/resources/extensions/gsd/auto-start.js +29 -9
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +40 -0
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +7 -3
- package/dist/resources/extensions/gsd/auto.js +15 -5
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +2 -2
- package/dist/resources/extensions/gsd/commands-verdict.js +10 -9
- package/dist/resources/extensions/gsd/compat/compat-marker.js +80 -5
- package/dist/resources/extensions/gsd/db/engine.js +5 -2
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +21 -2
- package/dist/resources/extensions/gsd/error-classifier.js +12 -2
- package/dist/resources/extensions/gsd/exec-sandbox.js +1 -0
- package/dist/resources/extensions/gsd/flat-phase-migration.js +12 -0
- package/dist/resources/extensions/gsd/gsd-db.js +75 -31
- package/dist/resources/extensions/gsd/guided-flow.js +2 -2
- package/dist/resources/extensions/gsd/markdown-renderer.js +3 -9
- package/dist/resources/extensions/gsd/paths.js +17 -7
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +5 -5
- package/dist/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.js +98 -2
- package/dist/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.js +4 -1
- package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +5 -2
- package/dist/resources/extensions/gsd/stop-notice.js +9 -0
- package/dist/resources/extensions/gsd/worktree-state-projection.js +20 -18
- package/dist/resources/extensions/shared/interview-ui.js +43 -6
- 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 +11 -11
- 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 +11 -11
- package/dist/web/standalone/.next/server/chunks/1991.js +2 -2
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +2 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/gsd-cloud/README.md +9 -3
- package/packages/gsd-cloud/dist/background-cli.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/background-cli.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/background-cli.test.js +248 -0
- package/packages/gsd-cloud/dist/background-cli.test.js.map +1 -0
- package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cli.js +121 -15
- package/packages/gsd-cloud/dist/cli.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.js +7 -1
- package/packages/gsd-cloud/dist/cloud-config.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts +1 -3
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.js +9 -7
- package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -1
- package/packages/gsd-cloud/dist/runtime-process.d.ts +27 -0
- package/packages/gsd-cloud/dist/runtime-process.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.js +272 -0
- package/packages/gsd-cloud/dist/runtime-process.js.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.test.js +97 -0
- package/packages/gsd-cloud/dist/runtime-process.test.js.map +1 -0
- package/packages/gsd-cloud/package.json +2 -2
- package/packages/mcp-server/dist/cli-runner.d.ts +3 -0
- package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -1
- package/packages/mcp-server/dist/cli-runner.js +119 -33
- package/packages/mcp-server/dist/cli-runner.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +17 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +17 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +103 -0
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +54 -0
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/loop.ts +48 -4
- package/src/resources/extensions/gsd/auto/run-unit.ts +20 -1
- package/src/resources/extensions/gsd/auto/unit-phase.ts +41 -5
- package/src/resources/extensions/gsd/auto-dispatch.ts +1 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +14 -4
- package/src/resources/extensions/gsd/auto-runtime-state.ts +12 -7
- package/src/resources/extensions/gsd/auto-start.ts +31 -8
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +56 -0
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +10 -3
- package/src/resources/extensions/gsd/auto.ts +20 -7
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +2 -2
- package/src/resources/extensions/gsd/commands-verdict.ts +16 -9
- package/src/resources/extensions/gsd/compat/compat-marker.ts +99 -5
- package/src/resources/extensions/gsd/db/engine.ts +5 -2
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +27 -2
- package/src/resources/extensions/gsd/error-classifier.ts +13 -2
- package/src/resources/extensions/gsd/exec-sandbox.ts +1 -0
- package/src/resources/extensions/gsd/flat-phase-migration.ts +14 -0
- package/src/resources/extensions/gsd/gsd-db.ts +85 -33
- package/src/resources/extensions/gsd/guided-flow.ts +2 -2
- package/src/resources/extensions/gsd/markdown-renderer.ts +3 -9
- package/src/resources/extensions/gsd/paths.ts +17 -7
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +5 -5
- package/src/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.ts +132 -2
- package/src/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.ts +4 -1
- package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +4 -2
- package/src/resources/extensions/gsd/stop-notice.ts +11 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +264 -0
- package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +74 -1
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/compat-marker.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/db-engine-cjs-compat.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +33 -1
- package/src/resources/extensions/gsd/tests/external-markdown-edit.test.ts +38 -1
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +8 -9
- package/src/resources/extensions/gsd/tests/flat-phase-validation-path.test.ts +20 -1
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +13 -0
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +121 -1
- package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/stop-notice.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +60 -0
- package/src/resources/extensions/gsd/worktree-state-projection.ts +21 -24
- package/src/resources/extensions/shared/interview-ui.ts +47 -8
- package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +29 -0
- /package/dist/web/standalone/.next/static/{6YWV7VAPg-jqjfPswrKZb → sggX-h41OjePo-FjdArP5}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{6YWV7VAPg-jqjfPswrKZb → sggX-h41OjePo-FjdArP5}/_ssgManifest.js +0 -0
package/dist/headless.js
CHANGED
|
@@ -29,7 +29,8 @@ export function buildHeadlessSlashCommand(options) {
|
|
|
29
29
|
if (options.command === 'new-milestone' && options.auto && !commandArgs.includes(HEADLESS_CHAIN_AUTO_FLAG)) {
|
|
30
30
|
commandArgs.push(HEADLESS_CHAIN_AUTO_FLAG);
|
|
31
31
|
}
|
|
32
|
-
|
|
32
|
+
const serializedArgs = commandArgs.map(arg => /\s/.test(arg) ? JSON.stringify(arg) : arg);
|
|
33
|
+
return `/gsd ${options.command}${serializedArgs.length > 0 ? ' ' + serializedArgs.join(' ') : ''}`;
|
|
33
34
|
}
|
|
34
35
|
/**
|
|
35
36
|
* Commands classified as multi-turn in headless mode: they involve multiple
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
5f0f1c788a126951
|
|
@@ -261,7 +261,22 @@ async function enforceMinRequestInterval(s, prefs) {
|
|
|
261
261
|
await new Promise(r => setTimeout(r, decision.waitMs));
|
|
262
262
|
}
|
|
263
263
|
}
|
|
264
|
-
function
|
|
264
|
+
async function snapshotCrashedUnitWork(ctx, s, deps, iterData) {
|
|
265
|
+
try {
|
|
266
|
+
const commitMsg = await deps.autoCommitUnit?.(s.basePath, iterData.unitType, iterData.unitId, ctx);
|
|
267
|
+
if (commitMsg) {
|
|
268
|
+
debugLog("autoLoop", {
|
|
269
|
+
phase: "crash-snapshot-commit",
|
|
270
|
+
unitType: iterData.unitType,
|
|
271
|
+
unitId: iterData.unitId,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch (snapshotErr) {
|
|
276
|
+
logWarning("dispatch", `unit crash snapshot failed: ${snapshotErr instanceof Error ? snapshotErr.message : String(snapshotErr)}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async function closeOutCrashedUnit(ctx, s, deps, iterData, err) {
|
|
265
280
|
const summary = formatDispatchExceptionSummary({ error: err });
|
|
266
281
|
try {
|
|
267
282
|
emitOpenUnitEndForUnit(s.basePath, iterData.unitType, iterData.unitId, "cancelled", {
|
|
@@ -278,6 +293,7 @@ function closeOutCrashedUnit(s, iterData, err) {
|
|
|
278
293
|
catch (closeoutErr) {
|
|
279
294
|
logWarning("dispatch", `unit crash closeout failed: ${closeoutErr instanceof Error ? closeoutErr.message : String(closeoutErr)}`);
|
|
280
295
|
}
|
|
296
|
+
await snapshotCrashedUnitWork(ctx, s, deps, iterData);
|
|
281
297
|
}
|
|
282
298
|
/**
|
|
283
299
|
* Main auto-mode execution loop. Iterates: orchestration.advance → guards →
|
|
@@ -400,11 +416,15 @@ export async function autoLoop(ctx, pi, s, deps, options) {
|
|
|
400
416
|
active: s.active,
|
|
401
417
|
iteration,
|
|
402
418
|
maxIterations: MAX_LOOP_ITERATIONS,
|
|
403
|
-
hasCommandContext:
|
|
419
|
+
hasCommandContext: typeof s.cmdCtx?.newSession === "function",
|
|
404
420
|
sessionLockValid: true,
|
|
405
421
|
});
|
|
406
422
|
if (commandContextDecision.action === "stop" && commandContextDecision.reason === "missing-command-context") {
|
|
407
423
|
debugLog("autoLoop", { phase: "exit", reason: "no-cmdCtx" });
|
|
424
|
+
if (s.currentUnit) {
|
|
425
|
+
await deps.autoCommitUnit?.(s.basePath, s.currentUnit.type, s.currentUnit.id, ctx);
|
|
426
|
+
}
|
|
427
|
+
await deps.stopAuto(ctx, pi, commandContextDecision.message, { preserveWorktree: true });
|
|
408
428
|
finishTurn("stopped", "manual-attention", commandContextDecision.reason);
|
|
409
429
|
break;
|
|
410
430
|
}
|
|
@@ -586,7 +606,7 @@ export async function autoLoop(ctx, pi, s, deps, options) {
|
|
|
586
606
|
if (err instanceof ModelPolicyDispatchBlockedError) {
|
|
587
607
|
throw err;
|
|
588
608
|
}
|
|
589
|
-
closeOutCrashedUnit(s, iterData, err);
|
|
609
|
+
await closeOutCrashedUnit(ctx, s, deps, iterData, err);
|
|
590
610
|
throw err;
|
|
591
611
|
}
|
|
592
612
|
if (unitPhaseResult.action === "next") {
|
|
@@ -1109,7 +1129,7 @@ export async function autoLoop(ctx, pi, s, deps, options) {
|
|
|
1109
1129
|
if (err instanceof ModelPolicyDispatchBlockedError) {
|
|
1110
1130
|
throw err;
|
|
1111
1131
|
}
|
|
1112
|
-
closeOutCrashedUnit(s, iterData, err);
|
|
1132
|
+
await closeOutCrashedUnit(ctx, s, deps, iterData, err);
|
|
1113
1133
|
dispatchSettled = settleDispatchFailed(dispatchId, formatDispatchExceptionSummary({ error: err }), {
|
|
1114
1134
|
markFailed: markDispatchFailed,
|
|
1115
1135
|
logWriteFailure: logDispatchLedgerWriteFailure,
|
|
@@ -58,6 +58,24 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
58
58
|
debugLog("runUnit", { phase: "start", unitType, unitId });
|
|
59
59
|
// ── Session creation with timeout ──
|
|
60
60
|
debugLog("runUnit", { phase: "session-create", unitType, unitId });
|
|
61
|
+
const cmdCtx = s.cmdCtx;
|
|
62
|
+
if (typeof cmdCtx?.newSession !== "function") {
|
|
63
|
+
const msg = "command context is missing newSession";
|
|
64
|
+
debugLog("runUnit", {
|
|
65
|
+
phase: "session-error",
|
|
66
|
+
unitType,
|
|
67
|
+
unitId,
|
|
68
|
+
error: msg,
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
status: "cancelled",
|
|
72
|
+
errorContext: {
|
|
73
|
+
message: `Session creation failed: ${msg}`,
|
|
74
|
+
category: "session-failed",
|
|
75
|
+
isTransient: false,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
61
79
|
let sessionResult;
|
|
62
80
|
let sessionTimeoutHandle;
|
|
63
81
|
const mySessionSwitchGeneration = ++sessionSwitchGeneration;
|
|
@@ -67,7 +85,7 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
67
85
|
const sessionAbortController = new AbortController();
|
|
68
86
|
_setSessionSwitchInFlight(true);
|
|
69
87
|
try {
|
|
70
|
-
const sessionPromise =
|
|
88
|
+
const sessionPromise = cmdCtx.newSession({
|
|
71
89
|
abortSignal: sessionAbortController.signal,
|
|
72
90
|
workspaceRoot: s.basePath,
|
|
73
91
|
}).finally(() => {
|
|
@@ -30,6 +30,25 @@ import { isIsolatedWorktreeSession, _resolveCurrentUnitStartedAtForTest, emitCan
|
|
|
30
30
|
import { MAX_RECOVERY_CHARS } from "./types.js";
|
|
31
31
|
const ZERO_TOOL_PROVIDER_ERROR_PREFIX_RE = /^(?:api error(?::|$|\s*\()|provider error(?::|$|\s*\()|request failed\b|(?:http\s*)?(?:429|500|502|503)\b|\b(?:econnreset|etimedout|econnrefused|epipe)\b|socket hang up\b|fetch failed\b|(?:network|connection|server) error(?::|$)|connection (?:reset|refused)(?::|$|\s+by\b)|dns\b.*(?:fail|error|timeout)|unexpected eof\b|stream idle timeout\b|partial response received\b|stream_exhausted\b|terminated(?::|$)|(?:connection|stream|request)\b.{0,40}\bterminated\b|other side closed\b|rate.?limit(?:ed| exceeded| reached| error)|too many requests\b|you(?:'ve| have) (?:hit|reached) your (?:\w+ )?limit\b|.*\b(?:usage|session|weekly|daily|monthly|quota) limit\b|limit\b.{0,40}\bresets?\b|out of extra usage\b|service.?unavailable\b|internal(?: server)? error(?::|$)|internal(?:[_-]server)?[_-]error\b|server[_-]error\b|(?:provider|server|api|model|codex|claude|openai|anthropic|gemini)\b.{0,80}\boverloaded\b|overloaded\b.{0,80}\b(?:provider|server|api|model)\b|context (?:window|length) exceed|context window exceed)/i;
|
|
32
32
|
const ZERO_TOOL_PROVIDER_ERROR_SIGNAL_RE = /(?:\b(?:http|status(?: code)?|code|error:)\s*(?:429|500|502|503)\b|\b(?:api|provider) error\s*[:(]?\s*(?:429|500|502|503)\b|\b(?:typeerror|error):\s*(?:fetch failed\b|socket hang up\b|terminated(?::|$)|connection (?:reset|refused)(?::|$|\s+by\b)|(?:network|connection|server) error(?::|$)|stream idle timeout\b|partial response received\b|unexpected eof\b)|\b(?:server_error|api_error|stream_exhausted(?:_without_result)?)\b|\b(?:econnreset|etimedout|econnrefused|epipe)\b|context (?:window|length) exceed|context window exceed)/i;
|
|
33
|
+
const ZERO_TOOL_PSEUDO_TOOL_CALL_RE = /<arg_key>[\s\S]*?<\/arg_key>\s*<arg_value>[\s\S]*?<\/arg_value>[\s\S]*?<\/tool_call>/i;
|
|
34
|
+
const ZERO_TOOL_PSEUDO_TOOL_CALL_SNIPPET_CHARS = 200;
|
|
35
|
+
function zeroToolMessageSnippet(message) {
|
|
36
|
+
const compact = message.trim().replace(/\s+/g, " ");
|
|
37
|
+
if (compact.length <= ZERO_TOOL_PSEUDO_TOOL_CALL_SNIPPET_CHARS)
|
|
38
|
+
return compact;
|
|
39
|
+
return `${compact.slice(0, ZERO_TOOL_PSEUDO_TOOL_CALL_SNIPPET_CHARS - 3)}...`;
|
|
40
|
+
}
|
|
41
|
+
// Provider serialization drift: the model emitted a tool call as plain assistant
|
|
42
|
+
// text (`toolName<arg_key>…</arg_key><arg_value>…</arg_value></tool_call>`)
|
|
43
|
+
// instead of a structured tool call. Returns an operator-visible snippet when
|
|
44
|
+
// detected. Kept separate from error classification so a drift snippet is never
|
|
45
|
+
// conflated with an ErrorClass (which would force a fragile union whose
|
|
46
|
+
// narrowing the root and extensions tsconfigs disagree on).
|
|
47
|
+
function zeroToolPseudoToolCallSnippet(message) {
|
|
48
|
+
if (!ZERO_TOOL_PSEUDO_TOOL_CALL_RE.test(message))
|
|
49
|
+
return null;
|
|
50
|
+
return zeroToolMessageSnippet(message);
|
|
51
|
+
}
|
|
33
52
|
function classifyZeroToolProviderMessage(message) {
|
|
34
53
|
const firstLine = message.trim().split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
35
54
|
if (!firstLine ||
|
|
@@ -39,6 +58,7 @@ function classifyZeroToolProviderMessage(message) {
|
|
|
39
58
|
return classifyError(firstLine);
|
|
40
59
|
}
|
|
41
60
|
export const _classifyZeroToolProviderMessageForTest = classifyZeroToolProviderMessage;
|
|
61
|
+
export const _zeroToolPseudoToolCallSnippetForTest = zeroToolPseudoToolCallSnippet;
|
|
42
62
|
export function resolveDispatchRecoveryAttempts(unitRecoveryCount, unitType, unitId) {
|
|
43
63
|
return (unitRecoveryCount.get(`${unitType}/${unitId}`) ?? 0) > 0
|
|
44
64
|
? 0
|
|
@@ -572,11 +592,23 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
572
592
|
const lastUnit = [...currentLedger.units].reverse().find((u) => u.type === unitType && u.id === unitId && u.startedAt === _resolveCurrentUnitStartedAtForTest(s.currentUnit));
|
|
573
593
|
if (lastUnit && lastUnit.toolCalls === 0) {
|
|
574
594
|
const lastAssistantMessage = lastAssistantText(s.lastUnitAgentEndMessages);
|
|
575
|
-
const
|
|
576
|
-
if (
|
|
577
|
-
|
|
595
|
+
const pseudoToolCallSnippet = zeroToolPseudoToolCallSnippet(lastAssistantMessage);
|
|
596
|
+
if (pseudoToolCallSnippet) {
|
|
597
|
+
debugLog("runUnitPhase", {
|
|
598
|
+
phase: "zero-tool-serialization-drift",
|
|
599
|
+
unitType,
|
|
600
|
+
unitId,
|
|
601
|
+
snippet: pseudoToolCallSnippet,
|
|
602
|
+
});
|
|
603
|
+
ctx.ui.notify(`${unitType} ${unitId} completed with 0 tool calls - provider serialization drift: model emitted pseudo-tool-call text. Snippet: ${pseudoToolCallSnippet}`, "error");
|
|
604
|
+
await deps.pauseAuto(ctx, pi);
|
|
605
|
+
return { action: "break", reason: "zero-tool-serialization-drift" };
|
|
606
|
+
}
|
|
607
|
+
const providerErrorClass = classifyZeroToolProviderMessage(lastAssistantMessage);
|
|
608
|
+
if (providerErrorClass && isTransient(providerErrorClass)) {
|
|
609
|
+
const retryAfterMs = "retryAfterMs" in providerErrorClass ? providerErrorClass.retryAfterMs : 15_000;
|
|
578
610
|
await pauseAutoForProviderError(ctx.ui, ` for ${unitType} ${unitId}`, () => deps.pauseAuto(ctx, pi), {
|
|
579
|
-
isRateLimit:
|
|
611
|
+
isRateLimit: providerErrorClass.kind === "rate-limit",
|
|
580
612
|
isTransient: true,
|
|
581
613
|
retryAfterMs,
|
|
582
614
|
resume: () => {
|
|
@@ -593,7 +625,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
593
625
|
});
|
|
594
626
|
return {
|
|
595
627
|
action: "break",
|
|
596
|
-
reason:
|
|
628
|
+
reason: providerErrorClass.kind === "rate-limit" ? "rate-limit" : "api-timeout",
|
|
597
629
|
};
|
|
598
630
|
}
|
|
599
631
|
if (USER_DRIVEN_DEEP_UNITS.has(unitType) && isAwaitingUserInput(s.lastUnitAgentEndMessages ?? undefined)) {
|
|
@@ -1603,6 +1603,8 @@ function applyLanguageDirectiveToDispatch(action, prefs) {
|
|
|
1603
1603
|
const directive = renderLanguageDirectiveForPrompt(prefs);
|
|
1604
1604
|
if (!directive)
|
|
1605
1605
|
return action;
|
|
1606
|
+
if (action.prompt.startsWith(directive))
|
|
1607
|
+
return action;
|
|
1606
1608
|
return { ...action, prompt: `${directive}\n\n${action.prompt}` };
|
|
1607
1609
|
}
|
|
1608
1610
|
// ─── Resolver ─────────────────────────────────────────────────────────────
|
|
@@ -11,7 +11,7 @@ import { loadFile, parseContinue, parseSummary, loadActiveOverrides, formatOverr
|
|
|
11
11
|
import { extractVerdict } from "./verdict-parser.js";
|
|
12
12
|
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
13
13
|
import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, taskIdFromTaskFileName, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, relTaskFile, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile, } from "./paths.js";
|
|
14
|
-
import { resolveInlineLevel, loadEffectiveGSDPreferences } from "./preferences.js";
|
|
14
|
+
import { resolveInlineLevel, loadEffectiveGSDPreferences, renderLanguageDirectiveForPrompt } from "./preferences.js";
|
|
15
15
|
import { createRepositoryRegistryFromPreferences } from "./repository-registry.js";
|
|
16
16
|
import { isContextModeEnabled } from "./preferences-types.js";
|
|
17
17
|
import { parseRoadmap } from "./parsers-legacy.js";
|
|
@@ -47,6 +47,12 @@ export { buildSkillActivationBlock, buildSkillDiscoveryVars };
|
|
|
47
47
|
* from their configured executor window.
|
|
48
48
|
*/
|
|
49
49
|
const MAX_PREAMBLE_CHARS = 20_000;
|
|
50
|
+
function prependLanguageDirectiveForBase(base, prompt) {
|
|
51
|
+
const directive = renderLanguageDirectiveForPrompt(loadEffectiveGSDPreferences(base)?.preferences);
|
|
52
|
+
if (!directive || prompt.startsWith(directive))
|
|
53
|
+
return prompt;
|
|
54
|
+
return `${directive}\n\n${prompt}`;
|
|
55
|
+
}
|
|
50
56
|
/**
|
|
51
57
|
* Resolve prompt budgets from the configured executor context window.
|
|
52
58
|
*
|
|
@@ -1463,7 +1469,7 @@ export async function buildDiscussMilestonePrompt(mid, midTitle, base, structure
|
|
|
1463
1469
|
if (headless) {
|
|
1464
1470
|
const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
|
|
1465
1471
|
const roadmapContent = roadmapPath ? await loadFile(roadmapPath) : null;
|
|
1466
|
-
|
|
1472
|
+
const prompt = loadPrompt("discuss-headless", {
|
|
1467
1473
|
seedContext: roadmapContent ?? "",
|
|
1468
1474
|
inlinedTemplates: contextTemplate,
|
|
1469
1475
|
workingDirectory: base,
|
|
@@ -1472,6 +1478,7 @@ export async function buildDiscussMilestonePrompt(mid, midTitle, base, structure
|
|
|
1472
1478
|
commitInstruction: "Do not commit planning artifacts — .gsd/ is managed externally.",
|
|
1473
1479
|
multiMilestoneCommitInstruction: "Do not commit planning artifacts — .gsd/ is managed externally.",
|
|
1474
1480
|
});
|
|
1481
|
+
return prependLanguageDirectiveForBase(base, prompt);
|
|
1475
1482
|
}
|
|
1476
1483
|
const rawInlinedContext = await buildDiscussMilestoneInlinedContext(mid, base);
|
|
1477
1484
|
const cappedInlinedContext = capPreamble(rawInlinedContext);
|
|
@@ -1498,9 +1505,10 @@ export async function buildDiscussMilestonePrompt(mid, midTitle, base, structure
|
|
|
1498
1505
|
const truncationNote = cappedDraftSeed !== draftSeed
|
|
1499
1506
|
? `\n\n_(Draft seed truncated; read the full draft at \`${draftRelPath}\` if needed.)_`
|
|
1500
1507
|
: "";
|
|
1501
|
-
|
|
1508
|
+
const promptWithDraftSeed = `${promptWithContextMode}\n\n## Prior Discussion (Draft Seed)\n\nThe following draft was captured from a prior multi-milestone discussion. Use it as seed material — the user has already provided this context. Start with a brief reflection on what the draft covers, then probe for any gaps or open questions before writing the full CONTEXT.md.\n\n${cappedDraftSeed}${truncationNote}`;
|
|
1509
|
+
return prependLanguageDirectiveForBase(base, promptWithDraftSeed);
|
|
1502
1510
|
}
|
|
1503
|
-
return promptWithContextMode;
|
|
1511
|
+
return prependLanguageDirectiveForBase(base, promptWithContextMode);
|
|
1504
1512
|
}
|
|
1505
1513
|
/**
|
|
1506
1514
|
* Build a prompt for the workflow-preferences unit type (deep mode).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// GSD auto-mode runtime state
|
|
2
2
|
import { AutoSession } from "./auto/session.js";
|
|
3
|
-
import {
|
|
3
|
+
import { markToolEnd as markTrackedToolEnd, markToolStart as markTrackedToolStart, shouldClearToolInvocationErrorAfterSuccess, updateToolInvocationError, } from "./auto-tool-tracking.js";
|
|
4
4
|
// Re-exported as a pure pass-through. Must stay UNGATED (no autoSession.active
|
|
5
5
|
// argument, unlike markToolStart at the bottom of this file) so it is true in
|
|
6
6
|
// foreground where the foreground approval-gate pause consults it.
|
|
@@ -61,13 +61,13 @@ export function markToolEnd(toolCallId) {
|
|
|
61
61
|
export function recordToolInvocationError(toolName, errorMsg) {
|
|
62
62
|
if (!autoSession.active)
|
|
63
63
|
return;
|
|
64
|
-
|
|
65
|
-
autoSession.lastToolInvocationError = `${toolName}: ${errorMsg}`;
|
|
66
|
-
}
|
|
64
|
+
autoSession.lastToolInvocationError = updateToolInvocationError(autoSession.lastToolInvocationError, toolName, errorMsg);
|
|
67
65
|
}
|
|
68
|
-
export function clearToolInvocationError() {
|
|
66
|
+
export function clearToolInvocationError(successfulToolName) {
|
|
69
67
|
if (!autoSession.active)
|
|
70
68
|
return;
|
|
69
|
+
if (!shouldClearToolInvocationErrorAfterSuccess(autoSession.lastToolInvocationError, successfulToolName))
|
|
70
|
+
return;
|
|
71
71
|
autoSession.lastToolInvocationError = null;
|
|
72
72
|
}
|
|
73
73
|
export function getSourceObservationStore() {
|
|
@@ -46,9 +46,10 @@ import { classifyMilestoneSummaryContent } from "./milestone-summary-classifier.
|
|
|
46
46
|
import { extractVerdict } from "./verdict-parser.js";
|
|
47
47
|
import { auditOrphanedPreflightStashes } from "./orphan-stash-audit.js";
|
|
48
48
|
import { parseProject } from "./schemas/parsers.js";
|
|
49
|
+
import { LAYOUT_SEGMENTS } from "./layout-policy.js";
|
|
49
50
|
import { debugLog, enableDebug, isDebugEnabled, getDebugLogPath, } from "./debug-logger.js";
|
|
50
51
|
import { logWarning, logError } from "./workflow-logger.js";
|
|
51
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, } from "node:fs";
|
|
52
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, } from "node:fs";
|
|
52
53
|
import { join } from "node:path";
|
|
53
54
|
import { validateDirectory } from "./validate-directory.js";
|
|
54
55
|
import { isCustomProvider, resolveDefaultSessionModel, resolveDynamicRoutingConfig, } from "./preferences-models.js";
|
|
@@ -73,6 +74,30 @@ const MAX_CONSECUTIVE_COMPLETE_BOOTSTRAPS = 2;
|
|
|
73
74
|
export function hasGitIndexLockForTest(basePath) {
|
|
74
75
|
return existsSync(join(basePath, ".git", "index.lock"));
|
|
75
76
|
}
|
|
77
|
+
function isEmptyDirectory(dirPath) {
|
|
78
|
+
try {
|
|
79
|
+
return existsSync(dirPath) && readdirSync(dirPath).length === 0;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export function reconcileFlatPhaseBootstrapLayout(basePath) {
|
|
86
|
+
const gsdDir = join(basePath, ".gsd");
|
|
87
|
+
const phasesPath = join(gsdDir, LAYOUT_SEGMENTS.level1);
|
|
88
|
+
const milestonesPath = join(gsdDir, "milestones");
|
|
89
|
+
if (isEmptyDirectory(milestonesPath)) {
|
|
90
|
+
if (!existsSync(phasesPath))
|
|
91
|
+
mkdirSync(phasesPath, { recursive: true });
|
|
92
|
+
rmSync(milestonesPath, { recursive: true, force: true });
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
if (!existsSync(phasesPath) && !existsSync(milestonesPath)) {
|
|
96
|
+
mkdirSync(phasesPath, { recursive: true });
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
76
101
|
export function _shouldAbortBootstrapForUnavailableDbForTest(gsdDbPath, dbAvailable, pathExists = existsSync) {
|
|
77
102
|
return pathExists(gsdDbPath) && !dbAvailable;
|
|
78
103
|
}
|
|
@@ -868,20 +893,15 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
868
893
|
ensureGitignore(base, { manageGitignore });
|
|
869
894
|
if (manageGitignore !== false)
|
|
870
895
|
untrackRuntimeFiles(base);
|
|
871
|
-
// Bootstrap
|
|
872
|
-
|
|
873
|
-
// so checking .gsd/ existence would be dead code (#2942).
|
|
874
|
-
const gsdDir = join(base, ".gsd");
|
|
875
|
-
const milestonesPath = join(gsdDir, "milestones");
|
|
876
|
-
if (!existsSync(milestonesPath)) {
|
|
877
|
-
mkdirSync(milestonesPath, { recursive: true });
|
|
896
|
+
// Bootstrap the flat-phase projection root and clean empty legacy scaffolds.
|
|
897
|
+
if (reconcileFlatPhaseBootstrapLayout(base)) {
|
|
878
898
|
try {
|
|
879
899
|
nativeAddAll(base);
|
|
880
900
|
nativeCommit(base, "chore: init gsd");
|
|
881
901
|
}
|
|
882
902
|
catch (err) {
|
|
883
903
|
/* nothing to commit */
|
|
884
|
-
logWarning("engine", `
|
|
904
|
+
logWarning("engine", `layout bootstrap commit failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
885
905
|
}
|
|
886
906
|
}
|
|
887
907
|
{
|
|
@@ -118,6 +118,8 @@ export function clearInFlightTools() {
|
|
|
118
118
|
*/
|
|
119
119
|
const TOOL_INVOCATION_ERROR_RE = /Validation failed for tool|Input validation error|Invalid arguments for tool|MCP error -32602|No such tool available|Expected ',' or '\}'(?: after property value)?(?: in JSON)?|Unexpected end of JSON|Unexpected token.*in JSON|does not provide an export named|Named export .* not found|Cannot find module|ERR_MODULE_NOT_FOUND|ERR_MODULE_NOT_EXPORTED|ERR_PACKAGE_PATH_NOT_EXPORTED/i;
|
|
120
120
|
const DETERMINISTIC_POLICY_ERROR_RE = /(?:^|\b)(?:HARD BLOCK:|Blocked: \/gsd queue is a planning tool|Direct writes to \.gsd\/STATE\.md and \.gsd\/gsd\.db are blocked|This is a mechanical gate)/i;
|
|
121
|
+
const SCHEDULE_WAKEUP_TOOL_NAME = "ScheduleWakeup";
|
|
122
|
+
const SCHEDULE_WAKEUP_CONTINUATION_ERROR = "`prompt` is required when `stop` is not true";
|
|
121
123
|
/**
|
|
122
124
|
* Matches the runtime's "tool not registered" error. Unlike the deterministic
|
|
123
125
|
* invocation failures above, this one is usually transient: the workflow MCP
|
|
@@ -135,6 +137,44 @@ export function isToolInvocationError(errorMsg) {
|
|
|
135
137
|
return false;
|
|
136
138
|
return TOOL_INVOCATION_ERROR_RE.test(errorMsg) || isDeterministicPolicyError(errorMsg);
|
|
137
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Claude Code validates its native ScheduleWakeup tool outside GSD's schema.
|
|
142
|
+
* Keep this exact and tool-scoped so similarly worded business validation from
|
|
143
|
+
* unrelated tools does not pause auto-mode.
|
|
144
|
+
*/
|
|
145
|
+
export function isScheduleWakeupContinuationError(toolName, errorMsg) {
|
|
146
|
+
return stripMcpToolPrefix(toolName) === SCHEDULE_WAKEUP_TOOL_NAME
|
|
147
|
+
&& errorMsg.trim() === SCHEDULE_WAKEUP_CONTINUATION_ERROR;
|
|
148
|
+
}
|
|
149
|
+
function shouldRecordToolInvocationError(toolName, errorMsg) {
|
|
150
|
+
return isScheduleWakeupContinuationError(toolName, errorMsg)
|
|
151
|
+
|| isToolInvocationError(errorMsg)
|
|
152
|
+
|| isQueuedUserMessageSkip(errorMsg);
|
|
153
|
+
}
|
|
154
|
+
function isRecordedScheduleWakeupContinuationError(recordedError) {
|
|
155
|
+
if (!recordedError)
|
|
156
|
+
return false;
|
|
157
|
+
const separator = recordedError.indexOf(": ");
|
|
158
|
+
return separator >= 0 && isScheduleWakeupContinuationError(recordedError.slice(0, separator), recordedError.slice(separator + 2));
|
|
159
|
+
}
|
|
160
|
+
export function updateToolInvocationError(recordedError, toolName, errorMsg) {
|
|
161
|
+
if (!shouldRecordToolInvocationError(toolName, errorMsg))
|
|
162
|
+
return recordedError;
|
|
163
|
+
if (isRecordedScheduleWakeupContinuationError(recordedError)
|
|
164
|
+
&& !isScheduleWakeupContinuationError(toolName, errorMsg))
|
|
165
|
+
return recordedError;
|
|
166
|
+
return `${toolName}: ${errorMsg}`;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* A failed continuation must survive successful diagnostic/read calls in the
|
|
170
|
+
* same turn. Only a later successful ScheduleWakeup proves the continuation
|
|
171
|
+
* was armed; all other invocation errors retain their existing clear-on-success
|
|
172
|
+
* behavior.
|
|
173
|
+
*/
|
|
174
|
+
export function shouldClearToolInvocationErrorAfterSuccess(recordedError, successfulToolName) {
|
|
175
|
+
return !isRecordedScheduleWakeupContinuationError(recordedError)
|
|
176
|
+
|| stripMcpToolPrefix(successfulToolName ?? "") === SCHEDULE_WAKEUP_TOOL_NAME;
|
|
177
|
+
}
|
|
138
178
|
/**
|
|
139
179
|
* Returns true if the error message indicates the called tool was not on the
|
|
140
180
|
* session's tool surface (MCP startup race — see TOOL_UNAVAILABLE_ERROR_RE).
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { cpSync, existsSync, lstatSync as lstatSyncFn, mkdirSync, readdirSync, } from "node:fs";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { _isSamePath as isSamePath, } from "./auto-worktree-cleanup.js";
|
|
8
|
-
import { resolveGsdPathContract } from "./paths.js";
|
|
8
|
+
import { dirIsContentBearingLegacyMilestone, resolveGsdPathContract } from "./paths.js";
|
|
9
9
|
import { WorktreeStateProjection } from "./worktree-state-projection.js";
|
|
10
10
|
import { logWarning } from "./workflow-logger.js";
|
|
11
11
|
const PROJECT_PREFERENCES_FILE = "PREFERENCES.md";
|
|
@@ -143,10 +143,14 @@ function syncMilestoneLayout(mainGsd, wtGsd, layoutSegment, synced) {
|
|
|
143
143
|
if (!existsSync(mainMilestonesDir))
|
|
144
144
|
return;
|
|
145
145
|
try {
|
|
146
|
-
mkdirSync(wtMilestonesDir, { recursive: true });
|
|
147
146
|
const mainMilestones = readdirSync(mainMilestonesDir, { withFileTypes: true })
|
|
148
147
|
.filter((entry) => entry.isDirectory())
|
|
149
|
-
.map((entry) => entry.name)
|
|
148
|
+
.map((entry) => entry.name)
|
|
149
|
+
.filter((name) => layoutSegment !== "milestones" ||
|
|
150
|
+
dirIsContentBearingLegacyMilestone(join(mainMilestonesDir, name)));
|
|
151
|
+
if (mainMilestones.length === 0)
|
|
152
|
+
return;
|
|
153
|
+
mkdirSync(wtMilestonesDir, { recursive: true });
|
|
150
154
|
for (const milestoneId of mainMilestones) {
|
|
151
155
|
syncMilestoneDirectory(mainMilestonesDir, wtMilestonesDir, milestoneId, synced);
|
|
152
156
|
}
|
|
@@ -28,7 +28,7 @@ import { acquireSessionLock, getSessionLockStatus, releaseSessionLock, updateSes
|
|
|
28
28
|
import { resolveAutoSupervisorConfig, loadEffectiveGSDPreferences, loadEffectiveGSDPreferencesWithRegistry, resolveProfileAnchorProvider, getIsolationMode, resolveEffectiveUnitIsolationMode, } from "./preferences.js";
|
|
29
29
|
import { playNotificationBell, sendDesktopNotification } from "./notifications.js";
|
|
30
30
|
import { getBudgetAlertLevel, getNewBudgetAlertLevel, getBudgetEnforcementAction, HARD_CONTEXT_REROOT_THRESHOLD_PERCENT, resolveCompactionThresholdPercent, shouldRerootStepSessionForContext, shouldWarnStepSessionForContext, } from "./auto-budget.js";
|
|
31
|
-
import { markToolStart as _markToolStart, markToolEnd as _markToolEnd, getOldestInFlightToolAgeMs as _getOldestInFlightToolAgeMs, clearInFlightTools,
|
|
31
|
+
import { markToolStart as _markToolStart, markToolEnd as _markToolEnd, getOldestInFlightToolAgeMs as _getOldestInFlightToolAgeMs, clearInFlightTools, updateToolInvocationError, } from "./auto-tool-tracking.js";
|
|
32
32
|
import { closeoutUnit } from "./auto-unit-closeout.js";
|
|
33
33
|
import { selectAndApplyModel, resolveModelId, clearToolBaseline, isModelUnavailable, applyThinkingLevelForModel } from "./auto-model-selection.js";
|
|
34
34
|
import { resolveModelWithFallbacksForUnit } from "./preferences-models.js";
|
|
@@ -617,15 +617,14 @@ export function markToolEnd(toolCallId) {
|
|
|
617
617
|
* Called from tool_execution_end when a GSD tool fails with isError.
|
|
618
618
|
* Stores the error if it matches:
|
|
619
619
|
* - tool-invocation-error pattern (malformed/truncated JSON)
|
|
620
|
+
* - failed ScheduleWakeup continuation validation (#1379)
|
|
620
621
|
* - queued-user-message skip pattern
|
|
621
622
|
* - deterministic policy rejection (#4973, e.g. context_write_blocked)
|
|
622
623
|
*/
|
|
623
624
|
export function recordToolInvocationError(toolName, errorMsg) {
|
|
624
625
|
if (!s.active)
|
|
625
626
|
return;
|
|
626
|
-
|
|
627
|
-
s.lastToolInvocationError = `${toolName}: ${errorMsg}`;
|
|
628
|
-
}
|
|
627
|
+
s.lastToolInvocationError = updateToolInvocationError(s.lastToolInvocationError, toolName, errorMsg);
|
|
629
628
|
}
|
|
630
629
|
export function getOldestInFlightToolAgeMs() {
|
|
631
630
|
return _getOldestInFlightToolAgeMs();
|
|
@@ -2441,7 +2440,18 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
|
|
|
2441
2440
|
startedAt: hookStartedAt,
|
|
2442
2441
|
workspaceRoot: s.basePath,
|
|
2443
2442
|
});
|
|
2444
|
-
const
|
|
2443
|
+
const cmdCtx = s.cmdCtx;
|
|
2444
|
+
if (typeof cmdCtx?.newSession !== "function") {
|
|
2445
|
+
const message = "Auto-mode has no command context for dispatch.";
|
|
2446
|
+
ctx.ui.notify(message, "error");
|
|
2447
|
+
const unitToCommit = previousCurrentUnit ?? s.currentUnit;
|
|
2448
|
+
if (unitToCommit) {
|
|
2449
|
+
await autoCommitUnit(s.basePath, unitToCommit.type, unitToCommit.id, ctx);
|
|
2450
|
+
}
|
|
2451
|
+
await stopAuto(ctx, pi, message, { preserveWorktree: true });
|
|
2452
|
+
return false;
|
|
2453
|
+
}
|
|
2454
|
+
const result = await cmdCtx.newSession({ workspaceRoot: s.basePath });
|
|
2445
2455
|
if (result.cancelled) {
|
|
2446
2456
|
await stopAuto(ctx, pi);
|
|
2447
2457
|
return false;
|
|
@@ -1448,7 +1448,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
1448
1448
|
recordRetryableHarnessToolError(toolName, resultPayload, errorText);
|
|
1449
1449
|
}
|
|
1450
1450
|
else if (isAutoActive()) {
|
|
1451
|
-
clearToolInvocationError();
|
|
1451
|
+
clearToolInvocationError(event.toolName);
|
|
1452
1452
|
}
|
|
1453
1453
|
// Interactive Closeout adapter (ADR-032): auto-mode owns closeout for its
|
|
1454
1454
|
// own units; interactive completions get the durable git subset (commit +
|
|
@@ -1631,7 +1631,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
1631
1631
|
recordRetryableHarnessToolError(toolName, event.result, errorText);
|
|
1632
1632
|
}
|
|
1633
1633
|
else if (isAutoActive()) {
|
|
1634
|
-
clearToolInvocationError();
|
|
1634
|
+
clearToolInvocationError(event.toolName);
|
|
1635
1635
|
}
|
|
1636
1636
|
// Safety harness: record tool execution results for evidence cross-referencing
|
|
1637
1637
|
if (isAutoActive()) {
|
|
@@ -8,6 +8,7 @@ import { executeValidateMilestone } from "./tools/workflow-tool-executors.js";
|
|
|
8
8
|
import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
|
|
9
9
|
import { getLatestAssessmentByScope } from "./gsd-db.js";
|
|
10
10
|
import { checkpointWorkflowDatabase } from "./db-workspace.js";
|
|
11
|
+
import { formatVerdictRecordedNotice, formatVerdictRejectedNotice } from "./stop-notice.js";
|
|
11
12
|
import { VALIDATION_VERDICTS, extractVerdict, isValidMilestoneVerdict, } from "./verdict-parser.js";
|
|
12
13
|
const USAGE = 'Usage: /gsd verdict <pass|needs-attention|needs-remediation> [--milestone Mxxx] [--rationale "..."]';
|
|
13
14
|
function tokenize(raw) {
|
|
@@ -123,35 +124,35 @@ async function loadExistingValidation(basePath, milestoneId) {
|
|
|
123
124
|
}
|
|
124
125
|
export async function handleVerdict(rawArgs, ctx, basePath) {
|
|
125
126
|
if (!rawArgs.trim()) {
|
|
126
|
-
ctx.ui.notify(USAGE, "warning");
|
|
127
|
+
ctx.ui.notify(formatVerdictRejectedNotice(USAGE), "warning");
|
|
127
128
|
return;
|
|
128
129
|
}
|
|
129
130
|
const parsed = parseArgs(rawArgs);
|
|
130
131
|
if ("error" in parsed) {
|
|
131
|
-
ctx.ui.notify(`${parsed.error}\n${USAGE}
|
|
132
|
+
ctx.ui.notify(formatVerdictRejectedNotice(`${parsed.error}\n${USAGE}`), "warning");
|
|
132
133
|
return;
|
|
133
134
|
}
|
|
134
135
|
if (!parsed.verdict) {
|
|
135
|
-
ctx.ui.notify(USAGE, "warning");
|
|
136
|
+
ctx.ui.notify(formatVerdictRejectedNotice(USAGE), "warning");
|
|
136
137
|
return;
|
|
137
138
|
}
|
|
138
139
|
let milestoneId = parsed.milestoneId;
|
|
139
140
|
if (!milestoneId) {
|
|
140
141
|
const state = await deriveState(basePath);
|
|
141
142
|
if (!state.activeMilestone) {
|
|
142
|
-
ctx.ui.notify("No active milestone — pass --milestone Mxxx to target a specific milestone.", "warning");
|
|
143
|
+
ctx.ui.notify(formatVerdictRejectedNotice("No active milestone — pass --milestone Mxxx to target a specific milestone."), "warning");
|
|
143
144
|
return;
|
|
144
145
|
}
|
|
145
146
|
milestoneId = state.activeMilestone.id;
|
|
146
147
|
}
|
|
147
148
|
const existingValidation = await loadExistingValidation(basePath, milestoneId);
|
|
148
149
|
if (!existingValidation) {
|
|
149
|
-
ctx.ui.notify(`No milestone validation found for ${milestoneId}. Run /gsd auto to validate first, then retry /gsd verdict
|
|
150
|
+
ctx.ui.notify(formatVerdictRejectedNotice(`No milestone validation found for ${milestoneId}. Run /gsd auto to validate first, then retry /gsd verdict.`), "warning");
|
|
150
151
|
return;
|
|
151
152
|
}
|
|
152
153
|
const current = parseValidationFile(existingValidation.content);
|
|
153
154
|
if (parsed.verdict !== "pass" && !parsed.rationale) {
|
|
154
|
-
ctx.ui.notify(`--rationale is required when overriding to ${parsed.verdict}
|
|
155
|
+
ctx.ui.notify(formatVerdictRejectedNotice(`--rationale is required when overriding to ${parsed.verdict}.`), "warning");
|
|
155
156
|
return;
|
|
156
157
|
}
|
|
157
158
|
const verdictRationale = parsed.rationale ?? "Manually overridden via /gsd verdict";
|
|
@@ -169,17 +170,17 @@ export async function handleVerdict(rawArgs, ctx, basePath) {
|
|
|
169
170
|
}, basePath, parsed.verdict === "pass" ? { skipBrowserEvidenceGate: true } : undefined);
|
|
170
171
|
if (result.isError) {
|
|
171
172
|
const msg = result.content[0]?.type === "text" ? result.content[0].text : "Unknown error";
|
|
172
|
-
ctx.ui.notify(msg, "error");
|
|
173
|
+
ctx.ui.notify(formatVerdictRejectedNotice(msg), "error");
|
|
173
174
|
return;
|
|
174
175
|
}
|
|
175
176
|
checkpointWorkflowDatabase();
|
|
176
177
|
const prevVerdict = current.verdict ?? "unknown";
|
|
177
178
|
const effectiveVerdict = extractEffectiveVerdict(result.details, parsed.verdict);
|
|
178
179
|
if (effectiveVerdict !== parsed.verdict) {
|
|
179
|
-
ctx.ui.notify(`Milestone ${milestoneId} verdict requested: ${parsed.verdict}, effective: ${effectiveVerdict} (${existingValidation.source})
|
|
180
|
+
ctx.ui.notify(formatVerdictRecordedNotice(`Milestone ${milestoneId} verdict requested: ${parsed.verdict}, effective: ${effectiveVerdict} (${existingValidation.source})`), "warning");
|
|
180
181
|
}
|
|
181
182
|
else {
|
|
182
|
-
ctx.ui.notify(`Milestone ${milestoneId} verdict: ${prevVerdict} -> ${effectiveVerdict} (${existingValidation.source})
|
|
183
|
+
ctx.ui.notify(formatVerdictRecordedNotice(`Milestone ${milestoneId} verdict: ${prevVerdict} -> ${effectiveVerdict} (${existingValidation.source})`), "success");
|
|
183
184
|
}
|
|
184
185
|
if (effectiveVerdict === "needs-remediation") {
|
|
185
186
|
ctx.ui.notify("Follow up with /gsd dispatch reassess to add remediation slices, then re-run /gsd auto.", "info");
|