@opengsd/gsd-pi 1.9.0-dev.5c1a3ff2 → 1.9.0-dev.97ef11e3
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 +10 -10
- 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 +10 -10
- 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/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 → DXD9fZPnqtjZSlT5xdC3v}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{6YWV7VAPg-jqjfPswrKZb → DXD9fZPnqtjZSlT5xdC3v}/_ssgManifest.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengsd/rpc-client",
|
|
3
|
-
"version": "1.9.0-dev.
|
|
3
|
+
"version": "1.9.0-dev.97ef11e3",
|
|
4
4
|
"description": "Standalone RPC client SDK for GSD — zero internal dependencies",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"gsd": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"test": "node --test dist/rpc-client.test.js"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@opengsd/contracts": "^1.9.0-dev.
|
|
37
|
+
"@opengsd/contracts": "^1.9.0-dev.97ef11e3"
|
|
38
38
|
},
|
|
39
39
|
"engines": {
|
|
40
40
|
"node": ">=22.0.0"
|
package/pkg/package.json
CHANGED
|
@@ -355,7 +355,41 @@ async function enforceMinRequestInterval(s: AutoSession, prefs: IterationContext
|
|
|
355
355
|
}
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
-
function
|
|
358
|
+
async function snapshotCrashedUnitWork(
|
|
359
|
+
ctx: ExtensionContext,
|
|
360
|
+
s: AutoSession,
|
|
361
|
+
deps: LoopDeps,
|
|
362
|
+
iterData: IterationData,
|
|
363
|
+
): Promise<void> {
|
|
364
|
+
try {
|
|
365
|
+
const commitMsg = await deps.autoCommitUnit?.(
|
|
366
|
+
s.basePath,
|
|
367
|
+
iterData.unitType,
|
|
368
|
+
iterData.unitId,
|
|
369
|
+
ctx,
|
|
370
|
+
);
|
|
371
|
+
if (commitMsg) {
|
|
372
|
+
debugLog("autoLoop", {
|
|
373
|
+
phase: "crash-snapshot-commit",
|
|
374
|
+
unitType: iterData.unitType,
|
|
375
|
+
unitId: iterData.unitId,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
} catch (snapshotErr) {
|
|
379
|
+
logWarning(
|
|
380
|
+
"dispatch",
|
|
381
|
+
`unit crash snapshot failed: ${snapshotErr instanceof Error ? snapshotErr.message : String(snapshotErr)}`,
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async function closeOutCrashedUnit(
|
|
387
|
+
ctx: ExtensionContext,
|
|
388
|
+
s: AutoSession,
|
|
389
|
+
deps: LoopDeps,
|
|
390
|
+
iterData: IterationData,
|
|
391
|
+
err: unknown,
|
|
392
|
+
): Promise<void> {
|
|
359
393
|
const summary = formatDispatchExceptionSummary({ error: err });
|
|
360
394
|
try {
|
|
361
395
|
emitOpenUnitEndForUnit(
|
|
@@ -383,6 +417,7 @@ function closeOutCrashedUnit(s: AutoSession, iterData: IterationData, err: unkno
|
|
|
383
417
|
} catch (closeoutErr) {
|
|
384
418
|
logWarning("dispatch", `unit crash closeout failed: ${closeoutErr instanceof Error ? closeoutErr.message : String(closeoutErr)}`);
|
|
385
419
|
}
|
|
420
|
+
await snapshotCrashedUnitWork(ctx, s, deps, iterData);
|
|
386
421
|
}
|
|
387
422
|
|
|
388
423
|
/**
|
|
@@ -526,11 +561,20 @@ export async function autoLoop(
|
|
|
526
561
|
active: s.active,
|
|
527
562
|
iteration,
|
|
528
563
|
maxIterations: MAX_LOOP_ITERATIONS,
|
|
529
|
-
hasCommandContext:
|
|
564
|
+
hasCommandContext: typeof s.cmdCtx?.newSession === "function",
|
|
530
565
|
sessionLockValid: true,
|
|
531
566
|
});
|
|
532
567
|
if (commandContextDecision.action === "stop" && commandContextDecision.reason === "missing-command-context") {
|
|
533
568
|
debugLog("autoLoop", { phase: "exit", reason: "no-cmdCtx" });
|
|
569
|
+
if (s.currentUnit) {
|
|
570
|
+
await deps.autoCommitUnit?.(
|
|
571
|
+
s.basePath,
|
|
572
|
+
s.currentUnit.type,
|
|
573
|
+
s.currentUnit.id,
|
|
574
|
+
ctx,
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
await deps.stopAuto(ctx, pi, commandContextDecision.message, { preserveWorktree: true });
|
|
534
578
|
finishTurn("stopped", "manual-attention", commandContextDecision.reason);
|
|
535
579
|
break;
|
|
536
580
|
}
|
|
@@ -730,7 +774,7 @@ export async function autoLoop(
|
|
|
730
774
|
if (err instanceof ModelPolicyDispatchBlockedError) {
|
|
731
775
|
throw err;
|
|
732
776
|
}
|
|
733
|
-
closeOutCrashedUnit(s, iterData, err);
|
|
777
|
+
await closeOutCrashedUnit(ctx, s, deps, iterData, err);
|
|
734
778
|
throw err;
|
|
735
779
|
}
|
|
736
780
|
if (unitPhaseResult.action === "next") {
|
|
@@ -1285,7 +1329,7 @@ export async function autoLoop(
|
|
|
1285
1329
|
if (err instanceof ModelPolicyDispatchBlockedError) {
|
|
1286
1330
|
throw err;
|
|
1287
1331
|
}
|
|
1288
|
-
closeOutCrashedUnit(s, iterData, err);
|
|
1332
|
+
await closeOutCrashedUnit(ctx, s, deps, iterData, err);
|
|
1289
1333
|
dispatchSettled = settleDispatchFailed(
|
|
1290
1334
|
dispatchId,
|
|
1291
1335
|
formatDispatchExceptionSummary({ error: err }),
|
|
@@ -91,6 +91,25 @@ export async function runUnit(
|
|
|
91
91
|
// ── Session creation with timeout ──
|
|
92
92
|
debugLog("runUnit", { phase: "session-create", unitType, unitId });
|
|
93
93
|
|
|
94
|
+
const cmdCtx = s.cmdCtx;
|
|
95
|
+
if (typeof cmdCtx?.newSession !== "function") {
|
|
96
|
+
const msg = "command context is missing newSession";
|
|
97
|
+
debugLog("runUnit", {
|
|
98
|
+
phase: "session-error",
|
|
99
|
+
unitType,
|
|
100
|
+
unitId,
|
|
101
|
+
error: msg,
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
status: "cancelled",
|
|
105
|
+
errorContext: {
|
|
106
|
+
message: `Session creation failed: ${msg}`,
|
|
107
|
+
category: "session-failed",
|
|
108
|
+
isTransient: false,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
94
113
|
let sessionResult: { cancelled: boolean };
|
|
95
114
|
let sessionTimeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
96
115
|
const mySessionSwitchGeneration = ++sessionSwitchGeneration;
|
|
@@ -100,7 +119,7 @@ export async function runUnit(
|
|
|
100
119
|
const sessionAbortController = new AbortController();
|
|
101
120
|
_setSessionSwitchInFlight(true);
|
|
102
121
|
try {
|
|
103
|
-
const sessionPromise =
|
|
122
|
+
const sessionPromise = cmdCtx.newSession({
|
|
104
123
|
abortSignal: sessionAbortController.signal,
|
|
105
124
|
workspaceRoot: s.basePath,
|
|
106
125
|
}).finally(() => {
|
|
@@ -56,6 +56,26 @@ const ZERO_TOOL_PROVIDER_ERROR_PREFIX_RE =
|
|
|
56
56
|
/^(?: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;
|
|
57
57
|
const ZERO_TOOL_PROVIDER_ERROR_SIGNAL_RE =
|
|
58
58
|
/(?:\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;
|
|
59
|
+
const ZERO_TOOL_PSEUDO_TOOL_CALL_RE =
|
|
60
|
+
/<arg_key>[\s\S]*?<\/arg_key>\s*<arg_value>[\s\S]*?<\/arg_value>[\s\S]*?<\/tool_call>/i;
|
|
61
|
+
const ZERO_TOOL_PSEUDO_TOOL_CALL_SNIPPET_CHARS = 200;
|
|
62
|
+
|
|
63
|
+
function zeroToolMessageSnippet(message: string): string {
|
|
64
|
+
const compact = message.trim().replace(/\s+/g, " ");
|
|
65
|
+
if (compact.length <= ZERO_TOOL_PSEUDO_TOOL_CALL_SNIPPET_CHARS) return compact;
|
|
66
|
+
return `${compact.slice(0, ZERO_TOOL_PSEUDO_TOOL_CALL_SNIPPET_CHARS - 3)}...`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Provider serialization drift: the model emitted a tool call as plain assistant
|
|
70
|
+
// text (`toolName<arg_key>…</arg_key><arg_value>…</arg_value></tool_call>`)
|
|
71
|
+
// instead of a structured tool call. Returns an operator-visible snippet when
|
|
72
|
+
// detected. Kept separate from error classification so a drift snippet is never
|
|
73
|
+
// conflated with an ErrorClass (which would force a fragile union whose
|
|
74
|
+
// narrowing the root and extensions tsconfigs disagree on).
|
|
75
|
+
function zeroToolPseudoToolCallSnippet(message: string): string | null {
|
|
76
|
+
if (!ZERO_TOOL_PSEUDO_TOOL_CALL_RE.test(message)) return null;
|
|
77
|
+
return zeroToolMessageSnippet(message);
|
|
78
|
+
}
|
|
59
79
|
|
|
60
80
|
function classifyZeroToolProviderMessage(message: string): ReturnType<typeof classifyError> | null {
|
|
61
81
|
const firstLine = message.trim().split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
@@ -68,6 +88,7 @@ function classifyZeroToolProviderMessage(message: string): ReturnType<typeof cla
|
|
|
68
88
|
}
|
|
69
89
|
|
|
70
90
|
export const _classifyZeroToolProviderMessageForTest = classifyZeroToolProviderMessage;
|
|
91
|
+
export const _zeroToolPseudoToolCallSnippetForTest = zeroToolPseudoToolCallSnippet;
|
|
71
92
|
|
|
72
93
|
export function resolveDispatchRecoveryAttempts(
|
|
73
94
|
unitRecoveryCount: Map<string, number>,
|
|
@@ -767,15 +788,30 @@ export async function runUnitPhase(
|
|
|
767
788
|
);
|
|
768
789
|
if (lastUnit && lastUnit.toolCalls === 0) {
|
|
769
790
|
const lastAssistantMessage = lastAssistantText(s.lastUnitAgentEndMessages);
|
|
770
|
-
const
|
|
771
|
-
if (
|
|
772
|
-
|
|
791
|
+
const pseudoToolCallSnippet = zeroToolPseudoToolCallSnippet(lastAssistantMessage);
|
|
792
|
+
if (pseudoToolCallSnippet) {
|
|
793
|
+
debugLog("runUnitPhase", {
|
|
794
|
+
phase: "zero-tool-serialization-drift",
|
|
795
|
+
unitType,
|
|
796
|
+
unitId,
|
|
797
|
+
snippet: pseudoToolCallSnippet,
|
|
798
|
+
});
|
|
799
|
+
ctx.ui.notify(
|
|
800
|
+
`${unitType} ${unitId} completed with 0 tool calls - provider serialization drift: model emitted pseudo-tool-call text. Snippet: ${pseudoToolCallSnippet}`,
|
|
801
|
+
"error",
|
|
802
|
+
);
|
|
803
|
+
await deps.pauseAuto(ctx, pi);
|
|
804
|
+
return { action: "break", reason: "zero-tool-serialization-drift" };
|
|
805
|
+
}
|
|
806
|
+
const providerErrorClass = classifyZeroToolProviderMessage(lastAssistantMessage);
|
|
807
|
+
if (providerErrorClass && isTransient(providerErrorClass)) {
|
|
808
|
+
const retryAfterMs = "retryAfterMs" in providerErrorClass ? providerErrorClass.retryAfterMs : 15_000;
|
|
773
809
|
await pauseAutoForProviderError(
|
|
774
810
|
ctx.ui,
|
|
775
811
|
` for ${unitType} ${unitId}`,
|
|
776
812
|
() => deps.pauseAuto(ctx, pi),
|
|
777
813
|
{
|
|
778
|
-
isRateLimit:
|
|
814
|
+
isRateLimit: providerErrorClass.kind === "rate-limit",
|
|
779
815
|
isTransient: true,
|
|
780
816
|
retryAfterMs,
|
|
781
817
|
resume: () => {
|
|
@@ -793,7 +829,7 @@ export async function runUnitPhase(
|
|
|
793
829
|
});
|
|
794
830
|
return {
|
|
795
831
|
action: "break",
|
|
796
|
-
reason:
|
|
832
|
+
reason: providerErrorClass.kind === "rate-limit" ? "rate-limit" : "api-timeout",
|
|
797
833
|
};
|
|
798
834
|
}
|
|
799
835
|
if (USER_DRIVEN_DEEP_UNITS.has(unitType) && isAwaitingUserInput(s.lastUnitAgentEndMessages ?? undefined)) {
|
|
@@ -1956,6 +1956,7 @@ function applyLanguageDirectiveToDispatch(
|
|
|
1956
1956
|
if (action.action !== "dispatch" || !action.prompt) return action;
|
|
1957
1957
|
const directive = renderLanguageDirectiveForPrompt(prefs);
|
|
1958
1958
|
if (!directive) return action;
|
|
1959
|
+
if (action.prompt.startsWith(directive)) return action;
|
|
1959
1960
|
return { ...action, prompt: `${directive}\n\n${action.prompt}` };
|
|
1960
1961
|
}
|
|
1961
1962
|
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath,
|
|
21
21
|
relTaskFile, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile,
|
|
22
22
|
} from "./paths.js";
|
|
23
|
-
import { resolveInlineLevel, loadEffectiveGSDPreferences } from "./preferences.js";
|
|
23
|
+
import { resolveInlineLevel, loadEffectiveGSDPreferences, renderLanguageDirectiveForPrompt } from "./preferences.js";
|
|
24
24
|
import { createRepositoryRegistryFromPreferences } from "./repository-registry.js";
|
|
25
25
|
import { isContextModeEnabled } from "./preferences-types.js";
|
|
26
26
|
import { parseRoadmap } from "./parsers-legacy.js";
|
|
@@ -78,6 +78,14 @@ export { buildSkillActivationBlock, buildSkillDiscoveryVars };
|
|
|
78
78
|
*/
|
|
79
79
|
const MAX_PREAMBLE_CHARS = 20_000;
|
|
80
80
|
|
|
81
|
+
function prependLanguageDirectiveForBase(base: string, prompt: string): string {
|
|
82
|
+
const directive = renderLanguageDirectiveForPrompt(
|
|
83
|
+
loadEffectiveGSDPreferences(base)?.preferences,
|
|
84
|
+
);
|
|
85
|
+
if (!directive || prompt.startsWith(directive)) return prompt;
|
|
86
|
+
return `${directive}\n\n${prompt}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
81
89
|
/**
|
|
82
90
|
* Resolve prompt budgets from the configured executor context window.
|
|
83
91
|
*
|
|
@@ -1730,7 +1738,7 @@ export async function buildDiscussMilestonePrompt(
|
|
|
1730
1738
|
if (headless) {
|
|
1731
1739
|
const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
|
|
1732
1740
|
const roadmapContent = roadmapPath ? await loadFile(roadmapPath) : null;
|
|
1733
|
-
|
|
1741
|
+
const prompt = loadPrompt("discuss-headless", {
|
|
1734
1742
|
seedContext: roadmapContent ?? "",
|
|
1735
1743
|
inlinedTemplates: contextTemplate,
|
|
1736
1744
|
workingDirectory: base,
|
|
@@ -1739,6 +1747,7 @@ export async function buildDiscussMilestonePrompt(
|
|
|
1739
1747
|
commitInstruction: "Do not commit planning artifacts — .gsd/ is managed externally.",
|
|
1740
1748
|
multiMilestoneCommitInstruction: "Do not commit planning artifacts — .gsd/ is managed externally.",
|
|
1741
1749
|
});
|
|
1750
|
+
return prependLanguageDirectiveForBase(base, prompt);
|
|
1742
1751
|
}
|
|
1743
1752
|
|
|
1744
1753
|
const rawInlinedContext = await buildDiscussMilestoneInlinedContext(mid, base);
|
|
@@ -1769,10 +1778,11 @@ export async function buildDiscussMilestonePrompt(
|
|
|
1769
1778
|
const truncationNote = cappedDraftSeed !== draftSeed
|
|
1770
1779
|
? `\n\n_(Draft seed truncated; read the full draft at \`${draftRelPath}\` if needed.)_`
|
|
1771
1780
|
: "";
|
|
1772
|
-
|
|
1781
|
+
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}`;
|
|
1782
|
+
return prependLanguageDirectiveForBase(base, promptWithDraftSeed);
|
|
1773
1783
|
}
|
|
1774
1784
|
|
|
1775
|
-
return promptWithContextMode;
|
|
1785
|
+
return prependLanguageDirectiveForBase(base, promptWithContextMode);
|
|
1776
1786
|
}
|
|
1777
1787
|
|
|
1778
1788
|
/**
|
|
@@ -3,11 +3,10 @@ import { AutoSession } from "./auto/session.js";
|
|
|
3
3
|
import type { CurrentUnit } from "./auto/session.js";
|
|
4
4
|
import type { SourceObservationStore } from "./source-observations.js";
|
|
5
5
|
import {
|
|
6
|
-
isDeterministicPolicyError,
|
|
7
|
-
isQueuedUserMessageSkip,
|
|
8
|
-
isToolInvocationError,
|
|
9
6
|
markToolEnd as markTrackedToolEnd,
|
|
10
7
|
markToolStart as markTrackedToolStart,
|
|
8
|
+
shouldClearToolInvocationErrorAfterSuccess,
|
|
9
|
+
updateToolInvocationError,
|
|
11
10
|
} from "./auto-tool-tracking.js";
|
|
12
11
|
// Re-exported as a pure pass-through. Must stay UNGATED (no autoSession.active
|
|
13
12
|
// argument, unlike markToolStart at the bottom of this file) so it is true in
|
|
@@ -99,13 +98,19 @@ export function markToolEnd(toolCallId: string): void {
|
|
|
99
98
|
|
|
100
99
|
export function recordToolInvocationError(toolName: string, errorMsg: string): void {
|
|
101
100
|
if (!autoSession.active) return;
|
|
102
|
-
|
|
103
|
-
autoSession.lastToolInvocationError
|
|
104
|
-
|
|
101
|
+
autoSession.lastToolInvocationError = updateToolInvocationError(
|
|
102
|
+
autoSession.lastToolInvocationError,
|
|
103
|
+
toolName,
|
|
104
|
+
errorMsg,
|
|
105
|
+
);
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
export function clearToolInvocationError(): void {
|
|
108
|
+
export function clearToolInvocationError(successfulToolName?: string): void {
|
|
108
109
|
if (!autoSession.active) return;
|
|
110
|
+
if (!shouldClearToolInvocationErrorAfterSuccess(
|
|
111
|
+
autoSession.lastToolInvocationError,
|
|
112
|
+
successfulToolName,
|
|
113
|
+
)) return;
|
|
109
114
|
autoSession.lastToolInvocationError = null;
|
|
110
115
|
}
|
|
111
116
|
|
|
@@ -88,6 +88,7 @@ import { classifyMilestoneSummaryContent } from "./milestone-summary-classifier.
|
|
|
88
88
|
import { extractVerdict } from "./verdict-parser.js";
|
|
89
89
|
import { auditOrphanedPreflightStashes } from "./orphan-stash-audit.js";
|
|
90
90
|
import { parseProject } from "./schemas/parsers.js";
|
|
91
|
+
import { LAYOUT_SEGMENTS } from "./layout-policy.js";
|
|
91
92
|
|
|
92
93
|
import {
|
|
93
94
|
debugLog,
|
|
@@ -152,6 +153,33 @@ export function hasGitIndexLockForTest(basePath: string): boolean {
|
|
|
152
153
|
return existsSync(join(basePath, ".git", "index.lock"));
|
|
153
154
|
}
|
|
154
155
|
|
|
156
|
+
function isEmptyDirectory(dirPath: string): boolean {
|
|
157
|
+
try {
|
|
158
|
+
return existsSync(dirPath) && readdirSync(dirPath).length === 0;
|
|
159
|
+
} catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function reconcileFlatPhaseBootstrapLayout(basePath: string): boolean {
|
|
165
|
+
const gsdDir = join(basePath, ".gsd");
|
|
166
|
+
const phasesPath = join(gsdDir, LAYOUT_SEGMENTS.level1);
|
|
167
|
+
const milestonesPath = join(gsdDir, "milestones");
|
|
168
|
+
|
|
169
|
+
if (isEmptyDirectory(milestonesPath)) {
|
|
170
|
+
if (!existsSync(phasesPath)) mkdirSync(phasesPath, { recursive: true });
|
|
171
|
+
rmSync(milestonesPath, { recursive: true, force: true });
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!existsSync(phasesPath) && !existsSync(milestonesPath)) {
|
|
176
|
+
mkdirSync(phasesPath, { recursive: true });
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
|
|
155
183
|
export function _shouldAbortBootstrapForUnavailableDbForTest(
|
|
156
184
|
gsdDbPath: string,
|
|
157
185
|
dbAvailable: boolean,
|
|
@@ -1152,19 +1180,14 @@ export async function bootstrapAutoSession(
|
|
|
1152
1180
|
ensureGitignore(base, { manageGitignore });
|
|
1153
1181
|
if (manageGitignore !== false) untrackRuntimeFiles(base);
|
|
1154
1182
|
|
|
1155
|
-
// Bootstrap
|
|
1156
|
-
|
|
1157
|
-
// so checking .gsd/ existence would be dead code (#2942).
|
|
1158
|
-
const gsdDir = join(base, ".gsd");
|
|
1159
|
-
const milestonesPath = join(gsdDir, "milestones");
|
|
1160
|
-
if (!existsSync(milestonesPath)) {
|
|
1161
|
-
mkdirSync(milestonesPath, { recursive: true });
|
|
1183
|
+
// Bootstrap the flat-phase projection root and clean empty legacy scaffolds.
|
|
1184
|
+
if (reconcileFlatPhaseBootstrapLayout(base)) {
|
|
1162
1185
|
try {
|
|
1163
1186
|
nativeAddAll(base);
|
|
1164
1187
|
nativeCommit(base, "chore: init gsd");
|
|
1165
1188
|
} catch (err) {
|
|
1166
1189
|
/* nothing to commit */
|
|
1167
|
-
logWarning("engine", `
|
|
1190
|
+
logWarning("engine", `layout bootstrap commit failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1168
1191
|
}
|
|
1169
1192
|
}
|
|
1170
1193
|
|
|
@@ -132,6 +132,8 @@ export function clearInFlightTools(): void {
|
|
|
132
132
|
*/
|
|
133
133
|
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;
|
|
134
134
|
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;
|
|
135
|
+
const SCHEDULE_WAKEUP_TOOL_NAME = "ScheduleWakeup";
|
|
136
|
+
const SCHEDULE_WAKEUP_CONTINUATION_ERROR = "`prompt` is required when `stop` is not true";
|
|
135
137
|
|
|
136
138
|
/**
|
|
137
139
|
* Matches the runtime's "tool not registered" error. Unlike the deterministic
|
|
@@ -151,6 +153,60 @@ export function isToolInvocationError(errorMsg: string): boolean {
|
|
|
151
153
|
return TOOL_INVOCATION_ERROR_RE.test(errorMsg) || isDeterministicPolicyError(errorMsg);
|
|
152
154
|
}
|
|
153
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Claude Code validates its native ScheduleWakeup tool outside GSD's schema.
|
|
158
|
+
* Keep this exact and tool-scoped so similarly worded business validation from
|
|
159
|
+
* unrelated tools does not pause auto-mode.
|
|
160
|
+
*/
|
|
161
|
+
export function isScheduleWakeupContinuationError(toolName: string, errorMsg: string): boolean {
|
|
162
|
+
return stripMcpToolPrefix(toolName) === SCHEDULE_WAKEUP_TOOL_NAME
|
|
163
|
+
&& errorMsg.trim() === SCHEDULE_WAKEUP_CONTINUATION_ERROR;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function shouldRecordToolInvocationError(toolName: string, errorMsg: string): boolean {
|
|
167
|
+
return isScheduleWakeupContinuationError(toolName, errorMsg)
|
|
168
|
+
|| isToolInvocationError(errorMsg)
|
|
169
|
+
|| isQueuedUserMessageSkip(errorMsg);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function isRecordedScheduleWakeupContinuationError(recordedError: string | null): boolean {
|
|
173
|
+
if (!recordedError) return false;
|
|
174
|
+
|
|
175
|
+
const separator = recordedError.indexOf(": ");
|
|
176
|
+
return separator >= 0 && isScheduleWakeupContinuationError(
|
|
177
|
+
recordedError.slice(0, separator),
|
|
178
|
+
recordedError.slice(separator + 2),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function updateToolInvocationError(
|
|
183
|
+
recordedError: string | null,
|
|
184
|
+
toolName: string,
|
|
185
|
+
errorMsg: string,
|
|
186
|
+
): string | null {
|
|
187
|
+
if (!shouldRecordToolInvocationError(toolName, errorMsg)) return recordedError;
|
|
188
|
+
if (
|
|
189
|
+
isRecordedScheduleWakeupContinuationError(recordedError)
|
|
190
|
+
&& !isScheduleWakeupContinuationError(toolName, errorMsg)
|
|
191
|
+
) return recordedError;
|
|
192
|
+
|
|
193
|
+
return `${toolName}: ${errorMsg}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* A failed continuation must survive successful diagnostic/read calls in the
|
|
198
|
+
* same turn. Only a later successful ScheduleWakeup proves the continuation
|
|
199
|
+
* was armed; all other invocation errors retain their existing clear-on-success
|
|
200
|
+
* behavior.
|
|
201
|
+
*/
|
|
202
|
+
export function shouldClearToolInvocationErrorAfterSuccess(
|
|
203
|
+
recordedError: string | null,
|
|
204
|
+
successfulToolName?: string,
|
|
205
|
+
): boolean {
|
|
206
|
+
return !isRecordedScheduleWakeupContinuationError(recordedError)
|
|
207
|
+
|| stripMcpToolPrefix(successfulToolName ?? "") === SCHEDULE_WAKEUP_TOOL_NAME;
|
|
208
|
+
}
|
|
209
|
+
|
|
154
210
|
/**
|
|
155
211
|
* Returns true if the error message indicates the called tool was not on the
|
|
156
212
|
* session's tool surface (MCP startup race — see TOOL_UNAVAILABLE_ERROR_RE).
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
_isSamePath as isSamePath,
|
|
17
17
|
_shouldReconcileWorktreeDb,
|
|
18
18
|
} from "./auto-worktree-cleanup.js";
|
|
19
|
-
import { resolveGsdPathContract } from "./paths.js";
|
|
19
|
+
import { dirIsContentBearingLegacyMilestone, resolveGsdPathContract } from "./paths.js";
|
|
20
20
|
import type { MilestoneScope } from "./workspace.js";
|
|
21
21
|
import { WorktreeStateProjection } from "./worktree-state-projection.js";
|
|
22
22
|
import { logWarning } from "./workflow-logger.js";
|
|
@@ -211,10 +211,17 @@ function syncMilestoneLayout(
|
|
|
211
211
|
if (!existsSync(mainMilestonesDir)) return;
|
|
212
212
|
|
|
213
213
|
try {
|
|
214
|
-
mkdirSync(wtMilestonesDir, { recursive: true });
|
|
215
214
|
const mainMilestones = readdirSync(mainMilestonesDir, { withFileTypes: true })
|
|
216
215
|
.filter((entry) => entry.isDirectory())
|
|
217
|
-
.map((entry) => entry.name)
|
|
216
|
+
.map((entry) => entry.name)
|
|
217
|
+
.filter((name) =>
|
|
218
|
+
layoutSegment !== "milestones" ||
|
|
219
|
+
dirIsContentBearingLegacyMilestone(join(mainMilestonesDir, name)),
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
if (mainMilestones.length === 0) return;
|
|
223
|
+
|
|
224
|
+
mkdirSync(wtMilestonesDir, { recursive: true });
|
|
218
225
|
|
|
219
226
|
for (const milestoneId of mainMilestones) {
|
|
220
227
|
syncMilestoneDirectory(
|
|
@@ -110,9 +110,7 @@ import {
|
|
|
110
110
|
getOldestInFlightToolStart,
|
|
111
111
|
hasInteractiveToolInFlight,
|
|
112
112
|
clearInFlightTools,
|
|
113
|
-
|
|
114
|
-
isQueuedUserMessageSkip,
|
|
115
|
-
isDeterministicPolicyError,
|
|
113
|
+
updateToolInvocationError,
|
|
116
114
|
} from "./auto-tool-tracking.js";
|
|
117
115
|
import { closeoutUnit } from "./auto-unit-closeout.js";
|
|
118
116
|
import { recoverTimedOutUnit } from "./auto-timeout-recovery.js";
|
|
@@ -1001,14 +999,17 @@ export function markToolEnd(toolCallId: string): void {
|
|
|
1001
999
|
* Called from tool_execution_end when a GSD tool fails with isError.
|
|
1002
1000
|
* Stores the error if it matches:
|
|
1003
1001
|
* - tool-invocation-error pattern (malformed/truncated JSON)
|
|
1002
|
+
* - failed ScheduleWakeup continuation validation (#1379)
|
|
1004
1003
|
* - queued-user-message skip pattern
|
|
1005
1004
|
* - deterministic policy rejection (#4973, e.g. context_write_blocked)
|
|
1006
1005
|
*/
|
|
1007
1006
|
export function recordToolInvocationError(toolName: string, errorMsg: string): void {
|
|
1008
1007
|
if (!s.active) return;
|
|
1009
|
-
|
|
1010
|
-
s.lastToolInvocationError
|
|
1011
|
-
|
|
1008
|
+
s.lastToolInvocationError = updateToolInvocationError(
|
|
1009
|
+
s.lastToolInvocationError,
|
|
1010
|
+
toolName,
|
|
1011
|
+
errorMsg,
|
|
1012
|
+
);
|
|
1012
1013
|
}
|
|
1013
1014
|
|
|
1014
1015
|
export function getOldestInFlightToolAgeMs(): number {
|
|
@@ -3162,7 +3163,19 @@ export async function dispatchHookUnit(
|
|
|
3162
3163
|
workspaceRoot: s.basePath,
|
|
3163
3164
|
});
|
|
3164
3165
|
|
|
3165
|
-
const
|
|
3166
|
+
const cmdCtx = s.cmdCtx;
|
|
3167
|
+
if (typeof cmdCtx?.newSession !== "function") {
|
|
3168
|
+
const message = "Auto-mode has no command context for dispatch.";
|
|
3169
|
+
ctx.ui.notify(message, "error");
|
|
3170
|
+
const unitToCommit = previousCurrentUnit ?? s.currentUnit;
|
|
3171
|
+
if (unitToCommit) {
|
|
3172
|
+
await autoCommitUnit(s.basePath, unitToCommit.type, unitToCommit.id, ctx);
|
|
3173
|
+
}
|
|
3174
|
+
await stopAuto(ctx, pi, message, { preserveWorktree: true });
|
|
3175
|
+
return false;
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
const result = await cmdCtx.newSession({ workspaceRoot: s.basePath });
|
|
3166
3179
|
if (result.cancelled) {
|
|
3167
3180
|
await stopAuto(ctx, pi);
|
|
3168
3181
|
return false;
|
|
@@ -1769,7 +1769,7 @@ export function registerHooks(
|
|
|
1769
1769
|
recordToolInvocationError(event.toolName, errorText);
|
|
1770
1770
|
recordRetryableHarnessToolError(toolName, resultPayload, errorText);
|
|
1771
1771
|
} else if (isAutoActive()) {
|
|
1772
|
-
clearToolInvocationError();
|
|
1772
|
+
clearToolInvocationError(event.toolName);
|
|
1773
1773
|
}
|
|
1774
1774
|
// Interactive Closeout adapter (ADR-032): auto-mode owns closeout for its
|
|
1775
1775
|
// own units; interactive completions get the durable git subset (commit +
|
|
@@ -1980,7 +1980,7 @@ export function registerHooks(
|
|
|
1980
1980
|
recordToolInvocationError(event.toolName, errorText);
|
|
1981
1981
|
recordRetryableHarnessToolError(toolName, event.result, errorText);
|
|
1982
1982
|
} else if (isAutoActive()) {
|
|
1983
|
-
clearToolInvocationError();
|
|
1983
|
+
clearToolInvocationError(event.toolName);
|
|
1984
1984
|
}
|
|
1985
1985
|
// Safety harness: record tool execution results for evidence cross-referencing
|
|
1986
1986
|
if (isAutoActive()) {
|
|
@@ -11,6 +11,7 @@ import { executeValidateMilestone } from "./tools/workflow-tool-executors.js";
|
|
|
11
11
|
import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
|
|
12
12
|
import { getLatestAssessmentByScope } from "./gsd-db.js";
|
|
13
13
|
import { checkpointWorkflowDatabase } from "./db-workspace.js";
|
|
14
|
+
import { formatVerdictRecordedNotice, formatVerdictRejectedNotice } from "./stop-notice.js";
|
|
14
15
|
import {
|
|
15
16
|
VALIDATION_VERDICTS,
|
|
16
17
|
extractVerdict,
|
|
@@ -165,17 +166,17 @@ export async function handleVerdict(
|
|
|
165
166
|
basePath: string,
|
|
166
167
|
): Promise<void> {
|
|
167
168
|
if (!rawArgs.trim()) {
|
|
168
|
-
ctx.ui.notify(USAGE, "warning");
|
|
169
|
+
ctx.ui.notify(formatVerdictRejectedNotice(USAGE), "warning");
|
|
169
170
|
return;
|
|
170
171
|
}
|
|
171
172
|
|
|
172
173
|
const parsed = parseArgs(rawArgs);
|
|
173
174
|
if ("error" in parsed) {
|
|
174
|
-
ctx.ui.notify(`${parsed.error}\n${USAGE}
|
|
175
|
+
ctx.ui.notify(formatVerdictRejectedNotice(`${parsed.error}\n${USAGE}`), "warning");
|
|
175
176
|
return;
|
|
176
177
|
}
|
|
177
178
|
if (!parsed.verdict) {
|
|
178
|
-
ctx.ui.notify(USAGE, "warning");
|
|
179
|
+
ctx.ui.notify(formatVerdictRejectedNotice(USAGE), "warning");
|
|
179
180
|
return;
|
|
180
181
|
}
|
|
181
182
|
|
|
@@ -184,7 +185,7 @@ export async function handleVerdict(
|
|
|
184
185
|
const state = await deriveState(basePath);
|
|
185
186
|
if (!state.activeMilestone) {
|
|
186
187
|
ctx.ui.notify(
|
|
187
|
-
"No active milestone — pass --milestone Mxxx to target a specific milestone.",
|
|
188
|
+
formatVerdictRejectedNotice("No active milestone — pass --milestone Mxxx to target a specific milestone."),
|
|
188
189
|
"warning",
|
|
189
190
|
);
|
|
190
191
|
return;
|
|
@@ -195,7 +196,9 @@ export async function handleVerdict(
|
|
|
195
196
|
const existingValidation = await loadExistingValidation(basePath, milestoneId);
|
|
196
197
|
if (!existingValidation) {
|
|
197
198
|
ctx.ui.notify(
|
|
198
|
-
|
|
199
|
+
formatVerdictRejectedNotice(
|
|
200
|
+
`No milestone validation found for ${milestoneId}. Run /gsd auto to validate first, then retry /gsd verdict.`,
|
|
201
|
+
),
|
|
199
202
|
"warning",
|
|
200
203
|
);
|
|
201
204
|
return;
|
|
@@ -205,7 +208,7 @@ export async function handleVerdict(
|
|
|
205
208
|
|
|
206
209
|
if (parsed.verdict !== "pass" && !parsed.rationale) {
|
|
207
210
|
ctx.ui.notify(
|
|
208
|
-
`--rationale is required when overriding to ${parsed.verdict}
|
|
211
|
+
formatVerdictRejectedNotice(`--rationale is required when overriding to ${parsed.verdict}.`),
|
|
209
212
|
"warning",
|
|
210
213
|
);
|
|
211
214
|
return;
|
|
@@ -234,7 +237,7 @@ export async function handleVerdict(
|
|
|
234
237
|
if (result.isError) {
|
|
235
238
|
const msg =
|
|
236
239
|
result.content[0]?.type === "text" ? result.content[0].text : "Unknown error";
|
|
237
|
-
ctx.ui.notify(msg, "error");
|
|
240
|
+
ctx.ui.notify(formatVerdictRejectedNotice(msg), "error");
|
|
238
241
|
return;
|
|
239
242
|
}
|
|
240
243
|
|
|
@@ -244,12 +247,16 @@ export async function handleVerdict(
|
|
|
244
247
|
const effectiveVerdict = extractEffectiveVerdict(result.details, parsed.verdict);
|
|
245
248
|
if (effectiveVerdict !== parsed.verdict) {
|
|
246
249
|
ctx.ui.notify(
|
|
247
|
-
|
|
250
|
+
formatVerdictRecordedNotice(
|
|
251
|
+
`Milestone ${milestoneId} verdict requested: ${parsed.verdict}, effective: ${effectiveVerdict} (${existingValidation.source})`,
|
|
252
|
+
),
|
|
248
253
|
"warning",
|
|
249
254
|
);
|
|
250
255
|
} else {
|
|
251
256
|
ctx.ui.notify(
|
|
252
|
-
|
|
257
|
+
formatVerdictRecordedNotice(
|
|
258
|
+
`Milestone ${milestoneId} verdict: ${prevVerdict} -> ${effectiveVerdict} (${existingValidation.source})`,
|
|
259
|
+
),
|
|
253
260
|
"success",
|
|
254
261
|
);
|
|
255
262
|
}
|