@opengsd/gsd-pi 1.1.1-dev.9bb7453 → 1.1.1-dev.9f86580
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/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +18 -2
- package/dist/resources/extensions/browser-tools/engine/selection.js +1 -1
- package/dist/resources/extensions/browser-tools/extension-manifest.json +1 -1
- package/dist/resources/extensions/browser-tools/index.js +29 -2
- package/dist/resources/extensions/browser-tools/web-app-detect.js +52 -0
- package/dist/resources/extensions/gsd/auto/phases.js +45 -3
- package/dist/resources/extensions/gsd/auto/session.js +2 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +21 -2
- package/dist/resources/extensions/gsd/auto-model-selection.js +26 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +4 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +3 -4
- package/dist/resources/extensions/gsd/auto-timers.js +24 -10
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +18 -66
- package/dist/resources/extensions/gsd/auto-worktree.js +18 -5
- package/dist/resources/extensions/gsd/auto.js +26 -4
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +16 -10
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +48 -29
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +18 -29
- package/dist/resources/extensions/gsd/closeout-consistency-gate.js +61 -0
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -0
- package/dist/resources/extensions/gsd/commands-mcp-status.js +1 -1
- package/dist/resources/extensions/gsd/config-overlay.js +1 -0
- package/dist/resources/extensions/gsd/context-masker.js +129 -5
- package/dist/resources/extensions/gsd/guided-flow.js +93 -108
- package/dist/resources/extensions/gsd/milestone-closeout.js +3 -1
- package/dist/resources/extensions/gsd/pending-auto-start.js +0 -1
- package/dist/resources/extensions/gsd/planner-handoff.js +98 -0
- package/dist/resources/extensions/gsd/preferences-models.js +1 -0
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +5 -19
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/recovery-classification.js +20 -0
- package/dist/resources/extensions/gsd/skill-manifest.js +12 -0
- package/dist/resources/extensions/gsd/tool-contract.js +6 -1
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +47 -7
- package/dist/resources/extensions/gsd/tools/complete-slice.js +28 -1
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +113 -8
- package/dist/resources/extensions/gsd/unit-tool-contracts.js +193 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +5 -78
- package/dist/resources/extensions/gsd/worktree-manager.js +26 -0
- package/dist/resources/extensions/gsd/worktree-reentry.js +96 -0
- package/dist/resources/extensions/shared/gsd-browser-cli.js +6 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
- 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 +5 -5
- 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 +1 -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/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +5 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +4 -3
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/dist/harness/agent-harness.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/harness/agent-harness.js +3 -1
- package/packages/pi-agent-core/dist/harness/agent-harness.js.map +1 -1
- package/packages/pi-agent-core/dist/harness/types.d.ts +1 -0
- package/packages/pi-agent-core/dist/harness/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/harness/types.js.map +1 -1
- package/packages/pi-agent-core/dist/types.d.ts +3 -1
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +157 -18
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +159 -36
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.js +8 -1
- package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +2 -2
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.js +3 -2
- package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/render-utils.js +6 -0
- package/packages/pi-coding-agent/dist/core/tools/render-utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.js +3 -2
- package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/scripts/install/handoff.js +16 -3
- package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +21 -2
- package/src/resources/extensions/browser-tools/engine/selection.ts +1 -1
- package/src/resources/extensions/browser-tools/extension-manifest.json +1 -1
- package/src/resources/extensions/browser-tools/index.ts +36 -5
- package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +2 -2
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +37 -0
- package/src/resources/extensions/browser-tools/tests/web-app-detect.test.mjs +68 -0
- package/src/resources/extensions/browser-tools/web-app-detect.ts +63 -0
- package/src/resources/extensions/gsd/auto/phases.ts +48 -6
- package/src/resources/extensions/gsd/auto/session.ts +2 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +48 -2
- package/src/resources/extensions/gsd/auto-model-selection.ts +26 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +4 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +3 -3
- package/src/resources/extensions/gsd/auto-timers.ts +25 -9
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +43 -74
- package/src/resources/extensions/gsd/auto-worktree.ts +23 -5
- package/src/resources/extensions/gsd/auto.ts +28 -4
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +16 -10
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +63 -29
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +50 -54
- package/src/resources/extensions/gsd/closeout-consistency-gate.ts +137 -0
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -0
- package/src/resources/extensions/gsd/commands-mcp-status.ts +1 -1
- package/src/resources/extensions/gsd/config-overlay.ts +1 -0
- package/src/resources/extensions/gsd/context-masker.ts +152 -5
- package/src/resources/extensions/gsd/guided-flow.ts +128 -135
- package/src/resources/extensions/gsd/milestone-closeout.ts +3 -1
- package/src/resources/extensions/gsd/pending-auto-start.ts +0 -2
- package/src/resources/extensions/gsd/planner-handoff.ts +149 -0
- package/src/resources/extensions/gsd/preferences-models.ts +1 -0
- package/src/resources/extensions/gsd/preferences-types.ts +8 -0
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +5 -19
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/recovery-classification.ts +20 -0
- package/src/resources/extensions/gsd/skill-manifest.ts +12 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +66 -4
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +10 -2
- package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +4 -0
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/check-auto-start-pending-gate.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +26 -16
- package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +118 -0
- package/src/resources/extensions/gsd/tests/context-masker.test.ts +56 -1
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +40 -1
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +31 -79
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +5 -3
- package/src/resources/extensions/gsd/tests/guided-flow-state-rebuild.test.ts +40 -4
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +10 -1
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +9 -1
- package/src/resources/extensions/gsd/tests/planner-handoff.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +147 -5
- package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +77 -10
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +409 -0
- package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -0
- package/src/resources/extensions/gsd/tool-contract.ts +7 -1
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +82 -7
- package/src/resources/extensions/gsd/tools/complete-slice.ts +29 -1
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +146 -9
- package/src/resources/extensions/gsd/unit-tool-contracts.ts +210 -0
- package/src/resources/extensions/gsd/workflow-mcp.ts +5 -78
- package/src/resources/extensions/gsd/worktree-manager.ts +32 -0
- package/src/resources/extensions/gsd/worktree-reentry.ts +103 -0
- package/src/resources/extensions/shared/gsd-browser-cli.ts +6 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +0 -246
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +0 -218
- /package/dist/web/standalone/.next/static/{jBtwT9v1u2lUA3UEOy_ZH → zzYMrKpPGfRQRxSFO32Jr}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{jBtwT9v1u2lUA3UEOy_ZH → zzYMrKpPGfRQRxSFO32Jr}/_ssgManifest.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
f692671bcb7f8bc4
|
|
@@ -435,11 +435,27 @@ function formatManagedBrowserError(toolName, error) {
|
|
|
435
435
|
return [
|
|
436
436
|
`gsd-browser engine or tool unavailable for ${toolName}: ${message}`,
|
|
437
437
|
"",
|
|
438
|
-
"
|
|
438
|
+
"The managed gsd-browser engine is enabled for this session but is unavailable.",
|
|
439
439
|
"Run /gsd doctor or reinstall dependencies so @opengsd/gsd-browser is available.",
|
|
440
|
-
"
|
|
440
|
+
"Unset GSD_BROWSER_ENGINE or set GSD_BROWSER_ENGINE=playwright to use the default Playwright engine.",
|
|
441
441
|
].join("\n");
|
|
442
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* Eagerly establish the managed gsd-browser connection so browser tools are
|
|
445
|
+
* ready before first use. Best-effort: returns the error instead of throwing so
|
|
446
|
+
* callers (e.g. session-start warm-up) can surface a warning without failing the
|
|
447
|
+
* session. Connecting only spawns the gsd-browser MCP daemon; it does not launch
|
|
448
|
+
* Chrome (that happens lazily on the first navigation).
|
|
449
|
+
*/
|
|
450
|
+
export async function warmUpManagedGsdBrowser(ctx, signal) {
|
|
451
|
+
try {
|
|
452
|
+
await getOrConnectManagedGsdBrowser(ctx, signal);
|
|
453
|
+
return { ok: true };
|
|
454
|
+
}
|
|
455
|
+
catch (error) {
|
|
456
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
457
|
+
}
|
|
458
|
+
}
|
|
443
459
|
export function registerManagedGsdBrowserTools(pi) {
|
|
444
460
|
for (const tool of MANAGED_BROWSER_TOOLS) {
|
|
445
461
|
pi.registerTool({
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "browser-tools",
|
|
3
3
|
"name": "Browser Tools",
|
|
4
4
|
"version": "1.0.0",
|
|
5
|
-
"description": "GSD browser automation contract adapter backed by
|
|
5
|
+
"description": "GSD browser automation contract adapter backed by Playwright with optional managed gsd-browser support",
|
|
6
6
|
"tier": "bundled",
|
|
7
7
|
"requires": { "platform": ">=2.29.0" },
|
|
8
8
|
"provides": {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/** browser-tools — Pi Browser Automation Contract adapter. */
|
|
2
2
|
import { importExtensionModule } from "@gsd/pi-coding-agent";
|
|
3
|
-
import { closeManagedGsdBrowser, registerManagedGsdBrowserTools } from "./engine/managed-gsd-browser.js";
|
|
3
|
+
import { closeManagedGsdBrowser, registerManagedGsdBrowserTools, warmUpManagedGsdBrowser } from "./engine/managed-gsd-browser.js";
|
|
4
4
|
import { resolveBrowserEngineMode } from "./engine/selection.js";
|
|
5
|
+
import { detectWebApp } from "./web-app-detect.js";
|
|
5
6
|
let legacyRegistrationPromise = null;
|
|
6
7
|
let managedRegistrationPromise = null;
|
|
7
8
|
let registeredEngine = null;
|
|
@@ -147,6 +148,29 @@ async function registerBrowserTools(pi) {
|
|
|
147
148
|
throw error;
|
|
148
149
|
}
|
|
149
150
|
}
|
|
151
|
+
function isWarmUpDisabled() {
|
|
152
|
+
const value = process.env.GSD_BROWSER_WARMUP?.trim().toLowerCase();
|
|
153
|
+
return value === "0" || value === "false" || value === "off";
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Auto-initialize the managed gsd-browser engine only when explicitly selected
|
|
157
|
+
* for a web app. Best-effort and non-blocking: warm-up runs in the background
|
|
158
|
+
* and only surfaces a warning if it fails.
|
|
159
|
+
*/
|
|
160
|
+
function maybeWarmUpManagedEngine(pi, ctx) {
|
|
161
|
+
if (isWarmUpDisabled())
|
|
162
|
+
return;
|
|
163
|
+
if (resolveBrowserEngineMode() !== "gsd-browser")
|
|
164
|
+
return;
|
|
165
|
+
const projectRoot = ctx.cwd || process.cwd();
|
|
166
|
+
if (!detectWebApp(projectRoot))
|
|
167
|
+
return;
|
|
168
|
+
void warmUpManagedGsdBrowser(ctx).then((result) => {
|
|
169
|
+
if (!result.ok && ctx.hasUI) {
|
|
170
|
+
ctx.ui.notify(`gsd-browser auto-init failed: ${result.error}. Browser UAT tools will retry on first use; run /gsd doctor if this persists.`, "warning");
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
150
174
|
async function closeActiveBrowserEngines() {
|
|
151
175
|
await closeManagedGsdBrowser();
|
|
152
176
|
if (legacyRegistrationPromise) {
|
|
@@ -157,12 +181,15 @@ async function closeActiveBrowserEngines() {
|
|
|
157
181
|
export default function (pi) {
|
|
158
182
|
pi.on("session_start", async (_event, ctx) => {
|
|
159
183
|
if (ctx.hasUI) {
|
|
160
|
-
void registerBrowserTools(pi)
|
|
184
|
+
void registerBrowserTools(pi)
|
|
185
|
+
.then(() => maybeWarmUpManagedEngine(pi, ctx))
|
|
186
|
+
.catch((error) => {
|
|
161
187
|
ctx.ui.notify(`browser-tools failed to load: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
162
188
|
});
|
|
163
189
|
return;
|
|
164
190
|
}
|
|
165
191
|
await registerBrowserTools(pi);
|
|
192
|
+
maybeWarmUpManagedEngine(pi, ctx);
|
|
166
193
|
});
|
|
167
194
|
pi.on("session_shutdown", async () => {
|
|
168
195
|
await closeActiveBrowserEngines();
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* web-app-detect — lightweight, synchronous heuristic for deciding whether the
|
|
3
|
+
* project under development is a web app. Used only when the optional managed
|
|
4
|
+
* gsd-browser engine is selected and can be warmed before first use.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
// Frontend frameworks / bundlers whose presence in dependencies indicates a
|
|
9
|
+
// browser-facing web app worth warming the optional managed engine for.
|
|
10
|
+
const WEB_DEPENDENCY_RE = /^(react|react-dom|next|nuxt|vue|@vue\/|svelte|@sveltejs\/|solid-js|astro|@remix-run\/|gatsby|preact|@angular\/core|vite|@vitejs\/|@builder\.io\/qwik|@web\/dev-server|@11ty\/eleventy)/;
|
|
11
|
+
// package.json scripts that imply a dev server / browser-facing build.
|
|
12
|
+
const WEB_SCRIPT_RE = /\b(vite|next|nuxt|astro|remix|webpack(-dev-server)?|parcel|ng serve|serve\b|http-server|live-server|gatsby)\b/;
|
|
13
|
+
function readPackageJson(projectRoot) {
|
|
14
|
+
const packageJsonPath = resolve(projectRoot, "package.json");
|
|
15
|
+
if (!existsSync(packageJsonPath))
|
|
16
|
+
return null;
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
19
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function dependencyNames(pkg) {
|
|
26
|
+
return [
|
|
27
|
+
...Object.keys(pkg.dependencies ?? {}),
|
|
28
|
+
...Object.keys(pkg.devDependencies ?? {}),
|
|
29
|
+
...Object.keys(pkg.peerDependencies ?? {}),
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns true when the project looks like a browser-facing web app. Conservative
|
|
34
|
+
* and dependency-free: a false negative just means lazy connection (the prior
|
|
35
|
+
* behavior); a false positive only warms an idle engine connection.
|
|
36
|
+
*/
|
|
37
|
+
export function detectWebApp(projectRoot) {
|
|
38
|
+
const pkg = readPackageJson(projectRoot);
|
|
39
|
+
if (pkg) {
|
|
40
|
+
if (dependencyNames(pkg).some((name) => WEB_DEPENDENCY_RE.test(name)))
|
|
41
|
+
return true;
|
|
42
|
+
const scriptValues = Object.values(pkg.scripts ?? {}).filter((value) => typeof value === "string");
|
|
43
|
+
if (scriptValues.some((script) => WEB_SCRIPT_RE.test(script)))
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
// No package.json signal — fall back to a top-level index.html (static sites).
|
|
47
|
+
if (existsSync(resolve(projectRoot, "index.html")))
|
|
48
|
+
return true;
|
|
49
|
+
if (existsSync(resolve(projectRoot, "public", "index.html")))
|
|
50
|
+
return true;
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
@@ -16,6 +16,8 @@ import { detectStuck } from "./detect-stuck.js";
|
|
|
16
16
|
import { runUnit } from "./run-unit.js";
|
|
17
17
|
import { debugLog } from "../debug-logger.js";
|
|
18
18
|
import { resolveWorktreeProjectRoot, normalizeWorktreePathForCompare } from "../worktree-root.js";
|
|
19
|
+
import { buildManualValidationGuidance } from "../worktree-manager.js";
|
|
20
|
+
import { relSliceFile } from "../paths.js";
|
|
19
21
|
import { classifyProject } from "../detection.js";
|
|
20
22
|
import { MergeConflictError } from "../git-service.js";
|
|
21
23
|
import { setCurrentPhase, clearCurrentPhase } from "../../shared/gsd-phase-state.js";
|
|
@@ -47,6 +49,7 @@ import { resolveSafetyHarnessConfig } from "../safety/safety-harness.js";
|
|
|
47
49
|
import { getContextPauseAction } from "../auto-budget.js";
|
|
48
50
|
import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForAutoUnit, supportsStructuredQuestions, } from "../workflow-mcp.js";
|
|
49
51
|
import { prepareWorkflowMcpForProject } from "../workflow-mcp-auto-prep.js";
|
|
52
|
+
import { getToolBaselineSnapshot } from "../auto-model-selection.js";
|
|
50
53
|
import { resolveManifest } from "../unit-context-manifest.js";
|
|
51
54
|
import { createWorktreeSafetyModule } from "../worktree-safety.js";
|
|
52
55
|
import { isSuspiciousGhostCompletion } from "../auto-unit-closeout.js";
|
|
@@ -302,6 +305,8 @@ async function validateSourceWriteWorktreeSafety(ic, unitType, unitId, milestone
|
|
|
302
305
|
// ─── Session timeout auto-resume state ────────────────────────────────────────
|
|
303
306
|
let consecutiveSessionTimeouts = 0;
|
|
304
307
|
const MAX_SESSION_TIMEOUT_AUTO_RESUMES = 3;
|
|
308
|
+
/** Maximum zero-tool-call retries before pausing — context exhaustion is deterministic. */
|
|
309
|
+
const MAX_ZERO_TOOL_RETRIES = 1;
|
|
305
310
|
export function resetSessionTimeoutState() {
|
|
306
311
|
consecutiveSessionTimeouts = 0;
|
|
307
312
|
}
|
|
@@ -1070,7 +1075,13 @@ export async function runDispatch(ic, preData, loopState) {
|
|
|
1070
1075
|
const authMode = provider && typeof ctx.modelRegistry?.getProviderAuthMode === "function"
|
|
1071
1076
|
? ctx.modelRegistry.getProviderAuthMode(provider)
|
|
1072
1077
|
: undefined;
|
|
1073
|
-
|
|
1078
|
+
// Use the baseline snapshot rather than the live active-tool set: a prior
|
|
1079
|
+
// unit's per-provider narrowing (hook overrides, Groq 128-tool cap, etc.)
|
|
1080
|
+
// can strip required MCP tools from the live set even though
|
|
1081
|
+
// selectAndApplyModel will restore them before the unit is dispatched.
|
|
1082
|
+
// Checking a stale-narrowed set causes false transport-preflight warnings
|
|
1083
|
+
// that repeat on every /gsd auto resume (#477 follow-up).
|
|
1084
|
+
const activeTools = getToolBaselineSnapshot(pi);
|
|
1074
1085
|
// Deep planning intentionally keeps human checkpoints in plain chat. In
|
|
1075
1086
|
// Claude Code/local MCP transports, structured question requests can be
|
|
1076
1087
|
// cancelled outside the normal chat flow, which made approval gates easy to
|
|
@@ -1093,6 +1104,9 @@ export async function runDispatch(ic, preData, loopState) {
|
|
|
1093
1104
|
sessionContextWindow: ctx.model?.contextWindow,
|
|
1094
1105
|
sessionProvider: ctx.model?.provider,
|
|
1095
1106
|
modelRegistry: ctx.modelRegistry,
|
|
1107
|
+
activeTools,
|
|
1108
|
+
sessionBaseUrl: ctx.model?.baseUrl,
|
|
1109
|
+
sessionAuthMode: authMode,
|
|
1096
1110
|
});
|
|
1097
1111
|
if (isUnhandledPhaseWarning(dispatchResult)) {
|
|
1098
1112
|
deps.invalidateAllCaches();
|
|
@@ -1116,6 +1130,9 @@ export async function runDispatch(ic, preData, loopState) {
|
|
|
1116
1130
|
sessionContextWindow: ctx.model?.contextWindow,
|
|
1117
1131
|
sessionProvider: ctx.model?.provider,
|
|
1118
1132
|
modelRegistry: ctx.modelRegistry,
|
|
1133
|
+
activeTools,
|
|
1134
|
+
sessionBaseUrl: ctx.model?.baseUrl,
|
|
1135
|
+
sessionAuthMode: authMode,
|
|
1119
1136
|
});
|
|
1120
1137
|
}
|
|
1121
1138
|
if (dispatchResult.action === "stop") {
|
|
@@ -2059,13 +2076,23 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
2059
2076
|
});
|
|
2060
2077
|
}
|
|
2061
2078
|
else {
|
|
2079
|
+
const zeroToolKey = `${unitType}/${unitId}`;
|
|
2080
|
+
const attempt = (s.zeroToolRetryCount.get(zeroToolKey) ?? 0) + 1;
|
|
2062
2081
|
debugLog("runUnitPhase", {
|
|
2063
2082
|
phase: "zero-tool-calls",
|
|
2064
2083
|
unitType,
|
|
2065
2084
|
unitId,
|
|
2085
|
+
attempt,
|
|
2066
2086
|
warning: "Unit completed with 0 tool calls — likely context exhaustion, marking as failed",
|
|
2067
2087
|
});
|
|
2068
|
-
|
|
2088
|
+
if (attempt > MAX_ZERO_TOOL_RETRIES) {
|
|
2089
|
+
s.zeroToolRetryCount.delete(zeroToolKey);
|
|
2090
|
+
ctx.ui.notify(`${unitType} ${unitId} completed with 0 tool calls — context exhaustion, pausing auto-mode after ${MAX_ZERO_TOOL_RETRIES} retry.`, "error");
|
|
2091
|
+
await deps.pauseAuto(ctx, pi);
|
|
2092
|
+
return { action: "break", reason: "zero-tool-calls-exhausted" };
|
|
2093
|
+
}
|
|
2094
|
+
s.zeroToolRetryCount.set(zeroToolKey, attempt);
|
|
2095
|
+
ctx.ui.notify(`${unitType} ${unitId} completed with 0 tool calls — context exhaustion, will retry (attempt ${attempt}/${MAX_ZERO_TOOL_RETRIES})`, "warning");
|
|
2069
2096
|
return {
|
|
2070
2097
|
action: "retry",
|
|
2071
2098
|
reason: "zero-tool-calls",
|
|
@@ -2087,6 +2114,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
2087
2114
|
if (artifactVerified) {
|
|
2088
2115
|
s.unitDispatchCount.delete(dispatchKey);
|
|
2089
2116
|
s.unitRecoveryCount.delete(`${unitType}/${unitId}`);
|
|
2117
|
+
s.zeroToolRetryCount.delete(dispatchKey);
|
|
2090
2118
|
}
|
|
2091
2119
|
// Write phase handoff anchor after successful research/planning completion
|
|
2092
2120
|
const anchorPhases = new Set(["research-milestone", "research-slice", "plan-milestone", "plan-slice"]);
|
|
@@ -2232,7 +2260,21 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
|
|
|
2232
2260
|
}
|
|
2233
2261
|
}
|
|
2234
2262
|
if (pauseAfterUatDispatch) {
|
|
2235
|
-
|
|
2263
|
+
const pauseMid = iterData.mid;
|
|
2264
|
+
const pauseSliceId = pauseMid && iterData.unitId.startsWith(`${pauseMid}/`)
|
|
2265
|
+
? iterData.unitId.slice(pauseMid.length + 1)
|
|
2266
|
+
: undefined;
|
|
2267
|
+
const guidance = pauseMid
|
|
2268
|
+
? buildManualValidationGuidance(s.basePath, pauseMid, {
|
|
2269
|
+
uatPath: pauseSliceId
|
|
2270
|
+
? relSliceFile(s.basePath, pauseMid, pauseSliceId, "UAT")
|
|
2271
|
+
: undefined,
|
|
2272
|
+
})
|
|
2273
|
+
: null;
|
|
2274
|
+
const pauseMessage = guidance
|
|
2275
|
+
? `UAT requires human execution. Auto-mode will pause after this unit writes the result file.\n\n${guidance}`
|
|
2276
|
+
: "UAT requires human execution. Auto-mode will pause after this unit writes the result file.";
|
|
2277
|
+
ctx.ui.notify(pauseMessage, "info");
|
|
2236
2278
|
await deps.pauseAuto(ctx, pi);
|
|
2237
2279
|
debugLog("autoLoop", { phase: "exit", reason: "uat-pause" });
|
|
2238
2280
|
clearFinalizingUnit();
|
|
@@ -94,6 +94,7 @@ export class AutoSession {
|
|
|
94
94
|
verificationRetryCount = new Map();
|
|
95
95
|
verificationRetryFailureHashes = new Map();
|
|
96
96
|
exhaustedVerificationUnits = new Set();
|
|
97
|
+
zeroToolRetryCount = new Map();
|
|
97
98
|
pausedSessionFile = null;
|
|
98
99
|
pausedUnitType = null;
|
|
99
100
|
pausedUnitId = null;
|
|
@@ -266,6 +267,7 @@ export class AutoSession {
|
|
|
266
267
|
this.verificationRetryCount.clear();
|
|
267
268
|
this.verificationRetryFailureHashes.clear();
|
|
268
269
|
this.exhaustedVerificationUnits.clear();
|
|
270
|
+
this.zeroToolRetryCount.clear();
|
|
269
271
|
this.pausedSessionFile = null;
|
|
270
272
|
this.pausedUnitType = null;
|
|
271
273
|
this.pausedUnitId = null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Project/App: gsd-pi
|
|
2
2
|
// File Purpose: Declarative auto-mode dispatch rules and dispatch resolver.
|
|
3
3
|
import { loadFile, extractUatType, loadActiveOverrides } from "./files.js";
|
|
4
|
-
import { isDbAvailable, getMilestoneSlices, getPendingGates, markAllGatesOmitted, getMilestone, insertAssessment, setSliceSketchFlag, transaction, getAssessment } from "./gsd-db.js";
|
|
4
|
+
import { isDbAvailable, getMilestoneSlices, getPendingGates, markAllGatesOmitted, getMilestone, insertAssessment, setSliceSketchFlag, transaction, getAssessment, } from "./gsd-db.js";
|
|
5
5
|
import { isClosedStatus } from "./status-guards.js";
|
|
6
6
|
import { extractVerdict, isAcceptableUatVerdict } from "./verdict-parser.js";
|
|
7
7
|
import { gsdRoot, resolveGsdPathContract, resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, relTaskFile, relSliceFile, buildMilestoneFileName, buildSliceFileName, buildTaskFileName, gsdProjectionRoot, } from "./paths.js";
|
|
@@ -21,6 +21,7 @@ import { isAutoActive } from "./auto.js";
|
|
|
21
21
|
import { markDepthVerified } from "./bootstrap/write-gate.js";
|
|
22
22
|
import { ensureWorkflowPreferencesCaptured } from "./planning-depth.js";
|
|
23
23
|
import { MILESTONE_ID_RE } from "./milestone-ids.js";
|
|
24
|
+
import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForAutoUnit, } from "./workflow-mcp.js";
|
|
24
25
|
import { PROJECT_RESEARCH_INFLIGHT_MARKER, } from "./project-research-policy.js";
|
|
25
26
|
import { isWorkflowPrefsCaptured, resolveDeepProjectSetupState, } from "./deep-project-setup-policy.js";
|
|
26
27
|
import { annotateBackgroundable } from "./delegation-policy.js";
|
|
@@ -35,6 +36,7 @@ import { probeGitConflictState } from "./git-conflict-state.js";
|
|
|
35
36
|
import { runTurnGitAction } from "./git-service.js";
|
|
36
37
|
import { parseUnitId } from "./unit-id.js";
|
|
37
38
|
import { resolveExpectedArtifactPath } from "./auto-artifact-paths.js";
|
|
39
|
+
import { checkCloseoutConsistencyGate, formatCloseoutConsistencyBlock, } from "./closeout-consistency-gate.js";
|
|
38
40
|
function resolveExistingExpectedArtifact(unitType, unitId, basePath) {
|
|
39
41
|
const artifactPath = resolveExpectedArtifactPath(unitType, unitId, basePath);
|
|
40
42
|
return artifactPath && existsSync(artifactPath) ? artifactPath : null;
|
|
@@ -466,11 +468,18 @@ export const DISPATCH_RULES = [
|
|
|
466
468
|
},
|
|
467
469
|
{
|
|
468
470
|
name: "run-uat (post-completion)",
|
|
469
|
-
match: async ({ state, mid, basePath, prefs }) => {
|
|
471
|
+
match: async ({ state, mid, basePath, prefs, sessionProvider, sessionAuthMode, activeTools, sessionBaseUrl }) => {
|
|
470
472
|
const needsRunUat = await checkNeedsRunUat(basePath, mid, state, prefs);
|
|
471
473
|
if (!needsRunUat)
|
|
472
474
|
return null;
|
|
473
475
|
const { sliceId, uatType } = needsRunUat;
|
|
476
|
+
// Transport preflight: verify required MCP tools are actually connected
|
|
477
|
+
// before consuming a retry attempt. Fixes tool-starved sessions burning
|
|
478
|
+
// all MAX_UAT_ATTEMPTS before stopping (#477).
|
|
479
|
+
const transportError = getWorkflowTransportSupportError(sessionProvider, getRequiredWorkflowToolsForAutoUnit("run-uat"), { projectRoot: basePath, surface: "auto-mode", unitType: "run-uat", authMode: sessionAuthMode, baseUrl: sessionBaseUrl, activeTools });
|
|
480
|
+
if (transportError) {
|
|
481
|
+
return { action: "stop", reason: transportError, level: "warning" };
|
|
482
|
+
}
|
|
474
483
|
// Cap run-uat dispatch attempts to prevent infinite replay (#3624).
|
|
475
484
|
// Check before incrementing so an exhausted counter cannot create a
|
|
476
485
|
// no-progress skip loop that starves later dispatch rules.
|
|
@@ -1355,6 +1364,16 @@ export const DISPATCH_RULES = [
|
|
|
1355
1364
|
prompt: await buildCompleteMilestonePrompt(mid, midTitle, basePath),
|
|
1356
1365
|
};
|
|
1357
1366
|
}
|
|
1367
|
+
if (milestone) {
|
|
1368
|
+
const closeoutGate = checkCloseoutConsistencyGate(mid, { refreshFromDisk: true });
|
|
1369
|
+
if (!closeoutGate.ok) {
|
|
1370
|
+
return {
|
|
1371
|
+
action: "stop",
|
|
1372
|
+
reason: formatCloseoutConsistencyBlock(closeoutGate),
|
|
1373
|
+
level: "warning",
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1358
1377
|
}
|
|
1359
1378
|
return {
|
|
1360
1379
|
action: "stop",
|
|
@@ -63,6 +63,32 @@ const TOOL_BASELINE = new WeakMap();
|
|
|
63
63
|
export function clearToolBaseline(pi) {
|
|
64
64
|
TOOL_BASELINE.delete(pi);
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Return the union of the pre-dispatch baseline tool set and the current live
|
|
68
|
+
* active tools, or just the live tools when no baseline has been recorded yet.
|
|
69
|
+
*
|
|
70
|
+
* Use this instead of `pi.getActiveTools()` anywhere you need the full tool
|
|
71
|
+
* surface for a preflight/routing check that runs BEFORE `selectAndApplyModel`
|
|
72
|
+
* restores the baseline — e.g. in `runDispatch` and `decideNextUnit`.
|
|
73
|
+
*
|
|
74
|
+
* The union is intentional:
|
|
75
|
+
* - Baseline covers tools that a prior unit's per-provider narrowing (hook
|
|
76
|
+
* overrides, Groq 128-tool cap, etc.) has removed from the live set.
|
|
77
|
+
* Those tools will be restored by `selectAndApplyModel` before dispatch, so
|
|
78
|
+
* dropping them from the preflight check would be a false negative.
|
|
79
|
+
* - Live set covers tools connected after the baseline was first captured
|
|
80
|
+
* (e.g. MCP servers attached mid-session or after a paused resume).
|
|
81
|
+
* Without the live merge, a stale baseline permanently hides newly
|
|
82
|
+
* connected MCP tools and prevents transport-preflight from clearing on
|
|
83
|
+
* resume (#477 follow-up).
|
|
84
|
+
*/
|
|
85
|
+
export function getToolBaselineSnapshot(pi) {
|
|
86
|
+
const live = typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [];
|
|
87
|
+
const baseline = TOOL_BASELINE.get(pi);
|
|
88
|
+
if (baseline === undefined)
|
|
89
|
+
return live;
|
|
90
|
+
return [...new Set([...baseline, ...live])];
|
|
91
|
+
}
|
|
66
92
|
/**
|
|
67
93
|
* Models eligible for the pre-dispatch policy gate. Prefer registry-available
|
|
68
94
|
* models; when that list is empty (common after worktree resume before registry
|
|
@@ -31,6 +31,7 @@ import { hasBrowserRequiredText } from "./browser-evidence.js";
|
|
|
31
31
|
import { debugLog } from "./debug-logger.js";
|
|
32
32
|
import { buildSkillActivationBlock, buildSkillDiscoveryVars } from "./skill-activation.js";
|
|
33
33
|
import { findMilestoneIds } from "./milestone-ids.js";
|
|
34
|
+
import { buildRunUatPresentationForType, RUN_UAT_TOOL_PRESENTATION_PLAN_ID } from "./tool-presentation-plan.js";
|
|
34
35
|
export { buildSkillActivationBlock, buildSkillDiscoveryVars };
|
|
35
36
|
// ─── Preamble Cap ─────────────────────────────────────────────────────────────
|
|
36
37
|
/**
|
|
@@ -2939,6 +2940,7 @@ export async function buildRunUatPrompt(mid, sliceId, uatPath, uatContent, base)
|
|
|
2939
2940
|
emitPromptContextTelemetry("run-uat", contextTelemetry, inlinedContext);
|
|
2940
2941
|
const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "ASSESSMENT"));
|
|
2941
2942
|
const uatType = resolveEffectiveUatType(uatContent);
|
|
2943
|
+
const canonicalPresentation = JSON.stringify(buildRunUatPresentationForType(uatType), null, 2);
|
|
2942
2944
|
return loadPrompt("run-uat", {
|
|
2943
2945
|
workingDirectory: base,
|
|
2944
2946
|
milestoneId: mid,
|
|
@@ -2946,6 +2948,8 @@ export async function buildRunUatPrompt(mid, sliceId, uatPath, uatContent, base)
|
|
|
2946
2948
|
uatPath,
|
|
2947
2949
|
uatResultPath,
|
|
2948
2950
|
uatType,
|
|
2951
|
+
toolPresentationPlanId: RUN_UAT_TOOL_PRESENTATION_PLAN_ID,
|
|
2952
|
+
canonicalPresentation,
|
|
2949
2953
|
inlinedContext,
|
|
2950
2954
|
skillActivation: buildSkillActivationBlock({
|
|
2951
2955
|
base,
|
|
@@ -32,6 +32,7 @@ import { isGsdWorktreePath } from "./worktree-root.js";
|
|
|
32
32
|
import { resolveCanonicalMilestoneRoot } from "./worktree-manager.js";
|
|
33
33
|
import { hasImplementationArtifacts } from "./milestone-implementation-evidence.js";
|
|
34
34
|
import { loadAllCaptures, loadPendingCaptures } from "./captures.js";
|
|
35
|
+
import { checkCloseoutConsistencyGate } from "./closeout-consistency-gate.js";
|
|
35
36
|
// Re-export so existing consumers of auto-recovery.ts keep working.
|
|
36
37
|
export { resolveExpectedArtifactPath, diagnoseExpectedArtifact };
|
|
37
38
|
export { classifyMilestoneSummaryContent, } from "./milestone-summary-classifier.js";
|
|
@@ -571,10 +572,8 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
|
|
|
571
572
|
return false;
|
|
572
573
|
const { milestone: mid } = parseUnitId(unitId);
|
|
573
574
|
if (mid && isDbAvailable()) {
|
|
574
|
-
const
|
|
575
|
-
if (!
|
|
576
|
-
return false;
|
|
577
|
-
if (!isClosedStatus(dbMilestone.status) && summaryOutcome !== "success")
|
|
575
|
+
const closeoutGate = checkCloseoutConsistencyGate(mid, { refreshFromDisk: true });
|
|
576
|
+
if (!closeoutGate.ok)
|
|
578
577
|
return false;
|
|
579
578
|
}
|
|
580
579
|
if (hasImplementationArtifacts(base, mid) === "absent")
|
|
@@ -104,6 +104,14 @@ export function startUnitSupervision(sctx) {
|
|
|
104
104
|
const softTimeoutMs = supervisionTimeouts.softTimeoutMs;
|
|
105
105
|
const idleTimeoutMs = supervisionTimeouts.idleTimeoutMs;
|
|
106
106
|
const hardTimeoutMs = supervisionTimeouts.hardTimeoutMs;
|
|
107
|
+
// A single hung tool gets its own short budget, NOT the general idle window:
|
|
108
|
+
// a long-but-progressing session is not idle, but a tool stuck for minutes
|
|
109
|
+
// is. Falls back to the idle window only if misconfigured to zero. The
|
|
110
|
+
// hung-tool budget is intentionally not scaled by task estimate — a stuck
|
|
111
|
+
// tool call is stuck regardless of how long the overall task should take.
|
|
112
|
+
const stalledToolTimeoutMs = (supervisor.stalled_tool_timeout_minutes ?? 0) > 0
|
|
113
|
+
? supervisor.stalled_tool_timeout_minutes * 60 * 1000
|
|
114
|
+
: idleTimeoutMs;
|
|
107
115
|
// ── 1. Soft timeout warning ──
|
|
108
116
|
s.wrapupWarningHandle = setTimeout(() => {
|
|
109
117
|
s.wrapupWarningHandle = null;
|
|
@@ -144,10 +152,12 @@ export function startUnitSupervision(sctx) {
|
|
|
144
152
|
const runtime = readUnitRuntimeRecord(s.basePath, unitType, unitId);
|
|
145
153
|
if (!runtime)
|
|
146
154
|
return;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
//
|
|
150
|
-
//
|
|
155
|
+
// In-flight tool handling runs on its own dedicated hung-tool budget,
|
|
156
|
+
// independent of the general idle gate below, so a genuinely stuck tool
|
|
157
|
+
// is caught in minutes instead of waiting out the (typically much longer)
|
|
158
|
+
// idle window (#2527, follow-up). A tool actively executing within budget
|
|
159
|
+
// is real progress, so refreshing lastProgressAt here also keeps the idle
|
|
160
|
+
// gate from firing during legitimate long-running tool calls.
|
|
151
161
|
let stalledToolDetected = false;
|
|
152
162
|
if (getInFlightToolCount() > 0) {
|
|
153
163
|
// User-interactive tools (ask_user_questions, secure_env_collect) block
|
|
@@ -161,21 +171,25 @@ export function startUnitSupervision(sctx) {
|
|
|
161
171
|
}
|
|
162
172
|
const oldestStart = getOldestInFlightToolStart();
|
|
163
173
|
const toolAgeMs = Date.now() - oldestStart;
|
|
164
|
-
if (toolAgeMs <
|
|
174
|
+
if (toolAgeMs < stalledToolTimeoutMs) {
|
|
165
175
|
writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
|
|
166
176
|
lastProgressAt: Date.now(),
|
|
167
177
|
lastProgressKind: "tool-in-flight",
|
|
168
178
|
});
|
|
169
179
|
return;
|
|
170
180
|
}
|
|
171
|
-
// Tool has been in-flight longer than
|
|
172
|
-
// Clear the stale entries so subsequent ticks don't re-detect
|
|
173
|
-
// and set the flag so the filesystem-activity check
|
|
174
|
-
// override the stall verdict (#2527).
|
|
181
|
+
// Tool has been in-flight longer than the hung-tool budget — treat as
|
|
182
|
+
// hung. Clear the stale entries so subsequent ticks don't re-detect
|
|
183
|
+
// them, and set the flag so the idle gate and filesystem-activity check
|
|
184
|
+
// below do not override the stall verdict (#2527).
|
|
175
185
|
stalledToolDetected = true;
|
|
176
186
|
clearInFlightTools();
|
|
177
|
-
ctx.ui.notify(`Stalled tool detected: a tool has been in-flight for ${Math.round(toolAgeMs / 60000)}min. Treating as hung — attempting idle recovery.`, "warning");
|
|
187
|
+
ctx.ui.notify(`Stalled tool detected: a tool has been in-flight for ${Math.round(toolAgeMs / 60000)}min (budget ${Math.round(stalledToolTimeoutMs / 60000)}min). Treating as hung — attempting idle recovery.`, "warning");
|
|
178
188
|
}
|
|
189
|
+
// No hung tool — apply the general idle gate. A unit that has made
|
|
190
|
+
// meaningful progress within the idle window is not idle yet.
|
|
191
|
+
if (!stalledToolDetected && Date.now() - runtime.lastProgressAt < idleTimeoutMs)
|
|
192
|
+
return;
|
|
179
193
|
// Check if the agent is producing work on disk.
|
|
180
194
|
// Skip this when a stalled tool was just detected — filesystem changes
|
|
181
195
|
// from earlier in the task should not override the stall verdict (#2527).
|
|
@@ -1,57 +1,6 @@
|
|
|
1
1
|
import { parseUnitId } from "./unit-id.js";
|
|
2
|
-
import {
|
|
3
|
-
export
|
|
4
|
-
"browser_navigate",
|
|
5
|
-
"browser_click",
|
|
6
|
-
"browser_type",
|
|
7
|
-
"browser_fill_form",
|
|
8
|
-
"browser_click_ref",
|
|
9
|
-
"browser_fill_ref",
|
|
10
|
-
"browser_wait_for",
|
|
11
|
-
"browser_assert",
|
|
12
|
-
"browser_verify",
|
|
13
|
-
"browser_screenshot",
|
|
14
|
-
"browser_snapshot_refs",
|
|
15
|
-
"browser_find",
|
|
16
|
-
"browser_get_console_logs",
|
|
17
|
-
"browser_get_network_logs",
|
|
18
|
-
"browser_evaluate",
|
|
19
|
-
"browser_reload",
|
|
20
|
-
"browser_batch",
|
|
21
|
-
"browser_act",
|
|
22
|
-
];
|
|
23
|
-
export const AUTO_UNIT_SCOPED_TOOLS = {
|
|
24
|
-
"research-milestone": ["gsd_summary_save", "gsd_decision_save"],
|
|
25
|
-
"plan-milestone": ["gsd_plan_milestone", "gsd_decision_save", "gsd_requirement_update"],
|
|
26
|
-
"discuss-milestone": [
|
|
27
|
-
"gsd_summary_save",
|
|
28
|
-
"gsd_decision_save",
|
|
29
|
-
"gsd_requirement_save",
|
|
30
|
-
"gsd_requirement_update",
|
|
31
|
-
"gsd_plan_milestone",
|
|
32
|
-
"gsd_milestone_generate_id",
|
|
33
|
-
],
|
|
34
|
-
"discuss-slice": ["gsd_summary_save", "gsd_decision_save"],
|
|
35
|
-
"validate-milestone": ["gsd_validate_milestone", "gsd_reassess_roadmap", "subagent"],
|
|
36
|
-
"complete-milestone": ["gsd_complete_milestone", "subagent"],
|
|
37
|
-
"research-slice": ["gsd_summary_save", "gsd_decision_save"],
|
|
38
|
-
"plan-slice": ["gsd_plan_slice", "gsd_plan_task", "gsd_decision_save"],
|
|
39
|
-
"refine-slice": ["gsd_plan_slice", "gsd_plan_task", "gsd_decision_save"],
|
|
40
|
-
"replan-slice": ["gsd_replan_slice", "gsd_plan_task", "gsd_decision_save"],
|
|
41
|
-
"complete-slice": ["gsd_slice_complete", "gsd_task_reopen", "gsd_replan_slice", "gsd_decision_save", "gsd_requirement_update", "subagent"],
|
|
42
|
-
"reassess-roadmap": ["gsd_reassess_roadmap"],
|
|
43
|
-
"execute-task": ["gsd_task_complete", "gsd_decision_save"],
|
|
44
|
-
"execute-task-simple": ["gsd_task_complete", "gsd_decision_save"],
|
|
45
|
-
"reactive-execute": ["gsd_task_complete", "gsd_decision_save"],
|
|
46
|
-
"run-uat": [...RUN_UAT_WORKFLOW_TOOL_NAMES, "subagent", ...RUN_UAT_BROWSER_TOOL_NAMES],
|
|
47
|
-
"gate-evaluate": ["gsd_save_gate_result"],
|
|
48
|
-
"rewrite-docs": ["gsd_summary_save", "gsd_decision_save"],
|
|
49
|
-
"workflow-preferences": ["gsd_summary_save"],
|
|
50
|
-
"discuss-project": ["gsd_summary_save", "gsd_decision_save", "gsd_requirement_save"],
|
|
51
|
-
"discuss-requirements": ["gsd_requirement_save", "gsd_summary_save"],
|
|
52
|
-
"research-decision": ["gsd_summary_save"],
|
|
53
|
-
"research-project": ["gsd_summary_save", "gsd_decision_save"],
|
|
54
|
-
};
|
|
2
|
+
import { AUTO_UNIT_SCOPED_TOOLS, getForbiddenGsdToolReason, } from "./unit-tool-contracts.js";
|
|
3
|
+
export { AUTO_UNIT_SCOPED_TOOLS, RUN_UAT_BROWSER_TOOL_NAMES, } from "./unit-tool-contracts.js";
|
|
55
4
|
const WORKFLOW_TOOL_ALIASES = {
|
|
56
5
|
gsd_save_decision: "gsd_decision_save",
|
|
57
6
|
gsd_update_requirement: "gsd_requirement_update",
|
|
@@ -88,6 +37,7 @@ const SCOPED_GSD_LIFECYCLE_TOOLS = new Set([
|
|
|
88
37
|
]
|
|
89
38
|
.filter((tool) => tool.startsWith("gsd_"))
|
|
90
39
|
.map(canonicalWorkflowToolName));
|
|
40
|
+
export const GSD_PHASE_SCOPE_DISPLAY_REASON = "This GSD phase only allows its scoped workflow tools.";
|
|
91
41
|
function stripMcpToolPrefix(toolName) {
|
|
92
42
|
if (!toolName.startsWith("mcp__"))
|
|
93
43
|
return toolName;
|
|
@@ -103,11 +53,18 @@ export function isWorkflowAliasTool(toolName) {
|
|
|
103
53
|
}
|
|
104
54
|
function hardBlockReason(unitType, what) {
|
|
105
55
|
return [
|
|
106
|
-
`HARD BLOCK: unit "${unitType}"
|
|
56
|
+
`HARD BLOCK: Tool Contract failure for unit "${unitType}" — ${what}.`,
|
|
107
57
|
"This is a mechanical phase-boundary gate. You MUST NOT proceed, retry the same call,",
|
|
108
58
|
"or route around this block; the orchestrator owns phase transitions.",
|
|
109
59
|
].join(" ");
|
|
110
60
|
}
|
|
61
|
+
function hardBlock(unitType, what) {
|
|
62
|
+
return {
|
|
63
|
+
block: true,
|
|
64
|
+
reason: hardBlockReason(unitType, what),
|
|
65
|
+
displayReason: GSD_PHASE_SCOPE_DISPLAY_REASON,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
111
68
|
function allowedGsdToolsForUnit(unitType) {
|
|
112
69
|
return [...new Set((AUTO_UNIT_SCOPED_TOOLS[unitType] ?? [])
|
|
113
70
|
.filter((tool) => tool.startsWith("gsd_"))
|
|
@@ -143,20 +100,14 @@ function shouldBlockTaskCompletionScope(unitType, unitId, toolName, input) {
|
|
|
143
100
|
actualTask === expected.task) {
|
|
144
101
|
return { block: false };
|
|
145
102
|
}
|
|
146
|
-
return {
|
|
147
|
-
block: true,
|
|
148
|
-
reason: hardBlockReason(unitType, `gsd_task_complete may only complete the active task ${expected.milestone}/${expected.slice}/${expected.task}; requested ${actualMilestone}/${actualSlice}/${actualTask}`),
|
|
149
|
-
};
|
|
103
|
+
return hardBlock(unitType, `gsd_task_complete may only complete the active task ${expected.milestone}/${expected.slice}/${expected.task}; requested ${actualMilestone}/${actualSlice}/${actualTask}`);
|
|
150
104
|
}
|
|
151
105
|
export function shouldBlockAutoUnitToolCall(unitType, toolName, input, unitId) {
|
|
152
106
|
const scopedTools = AUTO_UNIT_SCOPED_TOOLS[unitType];
|
|
153
107
|
if (!scopedTools)
|
|
154
108
|
return { block: false };
|
|
155
109
|
if (isNativeWorkflowTool(toolName)) {
|
|
156
|
-
return
|
|
157
|
-
block: true,
|
|
158
|
-
reason: hardBlockReason(unitType, "native Workflow is not permitted inside a dispatched GSD auto-mode unit"),
|
|
159
|
-
};
|
|
110
|
+
return hardBlock(unitType, "native Workflow is not permitted inside a dispatched GSD auto-mode unit");
|
|
160
111
|
}
|
|
161
112
|
const taskScope = shouldBlockTaskCompletionScope(unitType, unitId, toolName, input);
|
|
162
113
|
if (taskScope.block)
|
|
@@ -167,8 +118,9 @@ export function shouldBlockAutoUnitToolCall(unitType, toolName, input, unitId) {
|
|
|
167
118
|
const allowedTools = allowedGsdToolsForUnit(unitType);
|
|
168
119
|
if (allowedTools.includes(canonicalTool))
|
|
169
120
|
return { block: false };
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
121
|
+
const forbiddenReason = getForbiddenGsdToolReason(unitType, canonicalTool);
|
|
122
|
+
if (forbiddenReason) {
|
|
123
|
+
return hardBlock(unitType, `GSD lifecycle tool "${canonicalTool}" is not permitted; ${forbiddenReason} Fix unit-tool-contracts.ts or the ${unitType} prompt.`);
|
|
124
|
+
}
|
|
125
|
+
return hardBlock(unitType, `GSD lifecycle tool "${canonicalTool}" is not permitted; allowed GSD tools: ${allowedTools.length > 0 ? allowedTools.join(", ") : "(none)"}`);
|
|
174
126
|
}
|