@opengsd/gsd-pi 1.5.0-dev.4fb2d460 → 1.5.0-dev.c4c8885f
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 +114 -36
- package/dist/resources/extensions/gsd/activity-log.js +1 -1
- package/dist/resources/extensions/gsd/auto/orchestrator.js +16 -10
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +2 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +53 -15
- package/dist/resources/extensions/gsd/auto-model-selection.js +28 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +4 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +29 -117
- package/dist/resources/extensions/gsd/auto-start.js +53 -30
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +12 -2
- package/dist/resources/extensions/gsd/auto-timers.js +9 -2
- package/dist/resources/extensions/gsd/auto.js +97 -31
- package/dist/resources/extensions/gsd/compat/compat-marker.js +44 -0
- package/dist/resources/extensions/gsd/config-overlay.js +7 -2
- package/dist/resources/extensions/gsd/db/engine.js +33 -0
- package/dist/resources/extensions/gsd/db-adapter.js +30 -3
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +3 -3
- package/dist/resources/extensions/gsd/journal.js +1 -1
- package/dist/resources/extensions/gsd/markdown-renderer.js +34 -91
- package/dist/resources/extensions/gsd/metrics.js +2 -2
- package/dist/resources/extensions/gsd/paths.js +70 -49
- package/dist/resources/extensions/gsd/post-unit-hooks.js +27 -0
- package/dist/resources/extensions/gsd/preferences-models.js +66 -15
- package/dist/resources/extensions/gsd/preferences-validation.js +66 -10
- package/dist/resources/extensions/gsd/rule-registry.js +106 -10
- package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +1 -10
- package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +3 -24
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +10 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -5
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +16 -10
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +3 -8
- package/dist/resources/extensions/gsd/uat-dispatch.js +94 -0
- package/dist/resources/extensions/gsd/uok/audit-toggle.js +31 -2
- package/dist/resources/extensions/gsd/uok/audit.js +3 -0
- package/dist/resources/extensions/gsd/uok/gate-runner.js +22 -30
- package/dist/resources/extensions/gsd/uok/gitops.js +18 -15
- package/dist/resources/extensions/gsd/uok/kernel.js +120 -46
- package/dist/resources/extensions/gsd/uok/model-policy.js +17 -14
- package/dist/resources/extensions/gsd/workflow-event-ledger.js +1 -1
- package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
- package/dist/resources/extensions/gsd/workflow-projections.js +9 -26
- package/dist/resources/extensions/gsd/workspace.js +5 -14
- package/dist/resources/extensions/shared/gsd-browser-cli.js +29 -0
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +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/api/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +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/{1128.js → 1991.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/dist/worktree-cli-create.d.ts +10 -0
- package/dist/worktree-cli-create.js +11 -0
- package/dist/worktree-cli-format.d.ts +12 -0
- package/dist/worktree-cli-format.js +38 -0
- package/dist/worktree-cli-plan.d.ts +16 -0
- package/dist/worktree-cli-plan.js +19 -0
- package/dist/worktree-cli-session.d.ts +13 -0
- package/dist/worktree-cli-session.js +18 -0
- package/dist/worktree-cli-status.d.ts +35 -0
- package/dist/worktree-cli-status.js +54 -0
- package/dist/worktree-cli.d.ts +1 -16
- package/dist/worktree-cli.js +28 -122
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
- package/integrations/hermes/pyproject.toml +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/dist/orchestrator-agent.d.ts +21 -0
- package/packages/daemon/dist/orchestrator-agent.d.ts.map +1 -0
- package/packages/daemon/dist/orchestrator-agent.js +113 -0
- package/packages/daemon/dist/orchestrator-agent.js.map +1 -0
- package/packages/daemon/dist/orchestrator-tools.d.ts +12 -0
- package/packages/daemon/dist/orchestrator-tools.d.ts.map +1 -0
- package/packages/daemon/dist/orchestrator-tools.js +137 -0
- package/packages/daemon/dist/orchestrator-tools.js.map +1 -0
- package/packages/daemon/dist/orchestrator.d.ts +4 -56
- package/packages/daemon/dist/orchestrator.d.ts.map +1 -1
- package/packages/daemon/dist/orchestrator.js +13 -303
- package/packages/daemon/dist/orchestrator.js.map +1 -1
- package/packages/daemon/dist/orchestrator.test.js +11 -1
- package/packages/daemon/dist/orchestrator.test.js.map +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/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/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/browser-tools/engine/managed-gsd-browser.ts +146 -33
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +25 -0
- package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +93 -0
- package/src/resources/extensions/gsd/activity-log.ts +1 -1
- package/src/resources/extensions/gsd/auto/orchestrator.ts +17 -11
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +2 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +59 -14
- package/src/resources/extensions/gsd/auto-model-selection.ts +33 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +4 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +53 -113
- package/src/resources/extensions/gsd/auto-start.ts +57 -28
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -2
- package/src/resources/extensions/gsd/auto-timers.ts +9 -2
- package/src/resources/extensions/gsd/auto.ts +103 -30
- package/src/resources/extensions/gsd/compat/compat-marker.ts +47 -0
- package/src/resources/extensions/gsd/config-overlay.ts +7 -1
- package/src/resources/extensions/gsd/db/engine.ts +36 -0
- package/src/resources/extensions/gsd/db-adapter.ts +29 -3
- package/src/resources/extensions/gsd/docs/preferences-reference.md +3 -3
- package/src/resources/extensions/gsd/journal.ts +1 -1
- package/src/resources/extensions/gsd/markdown-renderer.ts +52 -103
- package/src/resources/extensions/gsd/metrics.ts +2 -2
- package/src/resources/extensions/gsd/paths.ts +87 -48
- package/src/resources/extensions/gsd/post-unit-hooks.ts +32 -0
- package/src/resources/extensions/gsd/preferences-models.ts +74 -20
- package/src/resources/extensions/gsd/preferences-types.ts +24 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +77 -9
- package/src/resources/extensions/gsd/rule-registry.ts +111 -9
- package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +1 -9
- package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +2 -22
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +14 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +41 -2
- package/src/resources/extensions/gsd/tests/auto-post-unit-artifact-diagnostic.test.ts +48 -1
- package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +29 -1
- package/src/resources/extensions/gsd/tests/auto-timeout-recovery-supervisor-inflight-guard.test.ts +159 -0
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/compat-marker.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/db-adapter.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/db-writable-probe.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +22 -1
- package/src/resources/extensions/gsd/tests/flat-phase-validation-path.test.ts +72 -32
- package/src/resources/extensions/gsd/tests/integration/paths.test.ts +88 -1
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +269 -0
- package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +49 -1
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +95 -1
- package/src/resources/extensions/gsd/tests/sibling-model-fallbacks.test.ts +189 -0
- package/src/resources/extensions/gsd/tests/single-field-model-thinking.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/supervisor-model-swap-inflight-guard.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/supervisor-model-swap-thinking-clamp.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/uat-dispatch.test.ts +211 -0
- package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/uok-kernel-path.test.ts +226 -2
- package/src/resources/extensions/gsd/tests/uok-model-policy.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +213 -2
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +9 -5
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +19 -8
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +13 -9
- package/src/resources/extensions/gsd/types.ts +23 -4
- package/src/resources/extensions/gsd/uat-dispatch.ts +176 -0
- package/src/resources/extensions/gsd/uok/audit-toggle.ts +32 -2
- package/src/resources/extensions/gsd/uok/audit.ts +2 -0
- package/src/resources/extensions/gsd/uok/gate-runner.ts +26 -36
- package/src/resources/extensions/gsd/uok/gitops.ts +21 -18
- package/src/resources/extensions/gsd/uok/kernel.ts +169 -48
- package/src/resources/extensions/gsd/uok/model-policy.ts +20 -17
- package/src/resources/extensions/gsd/workflow-event-ledger.ts +1 -1
- package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
- package/src/resources/extensions/gsd/workflow-projections.ts +9 -24
- package/src/resources/extensions/gsd/workspace.ts +5 -21
- package/src/resources/extensions/shared/gsd-browser-cli.ts +29 -0
- /package/dist/web/standalone/.next/static/{gXoWec4UNqcR0tkuHwYCr → NPMNi2HV8BuK3baQsEUhk}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{gXoWec4UNqcR0tkuHwYCr → NPMNi2HV8BuK3baQsEUhk}/_ssgManifest.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
d89940a8e727f2ea
|
|
@@ -4,8 +4,6 @@ import { Type } from "@sinclair/typebox";
|
|
|
4
4
|
import { BROWSER_CONTRACT_TOOL_NAMES } from "../../shared/browser-contract.js";
|
|
5
5
|
import { resolveGsdBrowserMcpLaunchConfig } from "../../shared/gsd-browser-cli.js";
|
|
6
6
|
import { buildMcpChildEnv } from "../../mcp-client/manager.js";
|
|
7
|
-
const connections = new Map();
|
|
8
|
-
const pendingConnections = new Map();
|
|
9
7
|
const DEFAULT_MAX_LINES = 2_000;
|
|
10
8
|
const DEFAULT_MAX_BYTES = 50 * 1024;
|
|
11
9
|
const MCP_CALL_TIMEOUT_MS = 60_000;
|
|
@@ -297,6 +295,103 @@ function buildConnectionKey(launch) {
|
|
|
297
295
|
env: launch.env ?? {},
|
|
298
296
|
});
|
|
299
297
|
}
|
|
298
|
+
function combineAbortSignals(poolSignal, callerSignal) {
|
|
299
|
+
if (!callerSignal)
|
|
300
|
+
return poolSignal;
|
|
301
|
+
if (typeof AbortSignal.any === "function") {
|
|
302
|
+
return AbortSignal.any([poolSignal, callerSignal]);
|
|
303
|
+
}
|
|
304
|
+
const controller = new AbortController();
|
|
305
|
+
const abort = () => {
|
|
306
|
+
if (!controller.signal.aborted)
|
|
307
|
+
controller.abort();
|
|
308
|
+
};
|
|
309
|
+
if (poolSignal.aborted || callerSignal.aborted) {
|
|
310
|
+
abort();
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
poolSignal.addEventListener("abort", abort, { once: true });
|
|
314
|
+
callerSignal.addEventListener("abort", abort, { once: true });
|
|
315
|
+
}
|
|
316
|
+
return controller.signal;
|
|
317
|
+
}
|
|
318
|
+
export class ManagedGsdBrowserConnectionPool {
|
|
319
|
+
connections = new Map();
|
|
320
|
+
pendingConnections = new Map();
|
|
321
|
+
connect;
|
|
322
|
+
closeConnection;
|
|
323
|
+
generation = 0;
|
|
324
|
+
closing = false;
|
|
325
|
+
constructor(connect, closeConnection) {
|
|
326
|
+
this.connect = connect;
|
|
327
|
+
this.closeConnection = closeConnection;
|
|
328
|
+
}
|
|
329
|
+
get activeCount() {
|
|
330
|
+
return this.connections.size;
|
|
331
|
+
}
|
|
332
|
+
get pendingCount() {
|
|
333
|
+
return this.pendingConnections.size;
|
|
334
|
+
}
|
|
335
|
+
getOrConnect(launch, signal) {
|
|
336
|
+
if (this.closing) {
|
|
337
|
+
return Promise.reject(new Error("gsd-browser connection pool is closing"));
|
|
338
|
+
}
|
|
339
|
+
const key = buildConnectionKey(launch);
|
|
340
|
+
const existing = this.connections.get(key);
|
|
341
|
+
if (existing)
|
|
342
|
+
return Promise.resolve(existing);
|
|
343
|
+
const pending = this.pendingConnections.get(key);
|
|
344
|
+
if (pending)
|
|
345
|
+
return pending.promise;
|
|
346
|
+
const generation = this.generation;
|
|
347
|
+
const abortController = new AbortController();
|
|
348
|
+
const connectionSignal = combineAbortSignals(abortController.signal, signal);
|
|
349
|
+
const connectionPromise = this.connect(launch, connectionSignal)
|
|
350
|
+
.then(async (connection) => {
|
|
351
|
+
if (this.closing || this.generation !== generation) {
|
|
352
|
+
await this.closeConnection(connection);
|
|
353
|
+
throw new Error("gsd-browser connection closed during startup");
|
|
354
|
+
}
|
|
355
|
+
this.connections.set(key, connection);
|
|
356
|
+
return connection;
|
|
357
|
+
})
|
|
358
|
+
.finally(() => {
|
|
359
|
+
if (this.pendingConnections.get(key)?.promise === connectionPromise) {
|
|
360
|
+
this.pendingConnections.delete(key);
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
this.pendingConnections.set(key, { promise: connectionPromise, abortController });
|
|
364
|
+
return connectionPromise;
|
|
365
|
+
}
|
|
366
|
+
async closeAll() {
|
|
367
|
+
this.closing = true;
|
|
368
|
+
this.generation += 1;
|
|
369
|
+
const activeConnections = Array.from(this.connections.values());
|
|
370
|
+
const pendingConnections = Array.from(this.pendingConnections.values());
|
|
371
|
+
this.connections.clear();
|
|
372
|
+
this.pendingConnections.clear();
|
|
373
|
+
for (const pending of pendingConnections) {
|
|
374
|
+
pending.abortController.abort();
|
|
375
|
+
}
|
|
376
|
+
const closingActive = activeConnections.map((connection) => this.closeConnection(connection));
|
|
377
|
+
const closingPending = pendingConnections.map(async (pending) => {
|
|
378
|
+
try {
|
|
379
|
+
const connection = await pending.promise;
|
|
380
|
+
await this.closeConnection(connection);
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
// Failed or invalidated connection attempts have already cleaned up.
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
try {
|
|
387
|
+
await Promise.allSettled([...closingActive, ...closingPending]);
|
|
388
|
+
}
|
|
389
|
+
finally {
|
|
390
|
+
this.closing = false;
|
|
391
|
+
this.generation += 1;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
300
395
|
async function connectManagedGsdBrowser(launch, signal) {
|
|
301
396
|
const client = new Client({ name: "gsd-pi-browser-tools", version: "1.0.0" });
|
|
302
397
|
const transport = new StdioClientTransport({
|
|
@@ -326,27 +421,26 @@ async function connectManagedGsdBrowser(launch, signal) {
|
|
|
326
421
|
throw error;
|
|
327
422
|
}
|
|
328
423
|
}
|
|
424
|
+
async function closeManagedGsdBrowserConnection(connection) {
|
|
425
|
+
try {
|
|
426
|
+
await connection.client.close();
|
|
427
|
+
}
|
|
428
|
+
catch {
|
|
429
|
+
// Best-effort cleanup.
|
|
430
|
+
}
|
|
431
|
+
try {
|
|
432
|
+
await connection.transport.close();
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
// Best-effort cleanup.
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
const connectionPool = new ManagedGsdBrowserConnectionPool(connectManagedGsdBrowser, closeManagedGsdBrowserConnection);
|
|
329
439
|
async function getOrConnectManagedGsdBrowser(ctx, signal) {
|
|
330
440
|
const launch = resolveGsdBrowserMcpLaunchConfig(resolveProjectRoot(ctx), process.env, {
|
|
331
441
|
sessionSuffix: resolveManagedSessionSuffix(ctx),
|
|
332
442
|
});
|
|
333
|
-
|
|
334
|
-
const existing = connections.get(key);
|
|
335
|
-
if (existing)
|
|
336
|
-
return existing;
|
|
337
|
-
const pending = pendingConnections.get(key);
|
|
338
|
-
if (pending)
|
|
339
|
-
return pending;
|
|
340
|
-
const connectionPromise = connectManagedGsdBrowser(launch, signal);
|
|
341
|
-
pendingConnections.set(key, connectionPromise);
|
|
342
|
-
try {
|
|
343
|
-
const connection = await connectionPromise;
|
|
344
|
-
connections.set(key, connection);
|
|
345
|
-
return connection;
|
|
346
|
-
}
|
|
347
|
-
finally {
|
|
348
|
-
pendingConnections.delete(key);
|
|
349
|
-
}
|
|
443
|
+
return connectionPool.getOrConnect(launch, signal);
|
|
350
444
|
}
|
|
351
445
|
function isUnknownMcpToolError(error) {
|
|
352
446
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -609,23 +703,7 @@ export function registerManagedGsdBrowserTools(pi) {
|
|
|
609
703
|
}
|
|
610
704
|
}
|
|
611
705
|
export async function closeManagedGsdBrowser() {
|
|
612
|
-
|
|
613
|
-
try {
|
|
614
|
-
await connection.client.close();
|
|
615
|
-
}
|
|
616
|
-
catch {
|
|
617
|
-
// Best-effort cleanup.
|
|
618
|
-
}
|
|
619
|
-
try {
|
|
620
|
-
await connection.transport.close();
|
|
621
|
-
}
|
|
622
|
-
catch {
|
|
623
|
-
// Best-effort cleanup.
|
|
624
|
-
}
|
|
625
|
-
connections.delete(key);
|
|
626
|
-
});
|
|
627
|
-
await Promise.allSettled(closing);
|
|
628
|
-
pendingConnections.clear();
|
|
706
|
+
await connectionPool.closeAll();
|
|
629
707
|
}
|
|
630
708
|
export async function _resetManagedGsdBrowserForTest() {
|
|
631
709
|
await closeManagedGsdBrowser();
|
|
@@ -115,7 +115,7 @@ export function saveActivityLog(ctx, basePath, unitType, unitId) {
|
|
|
115
115
|
}
|
|
116
116
|
state.nextSeq += 1;
|
|
117
117
|
state.lastSnapshotKeyByUnit.set(unitKey, key);
|
|
118
|
-
if (isUnifiedAuditEnabled()) {
|
|
118
|
+
if (isUnifiedAuditEnabled(basePath)) {
|
|
119
119
|
emitUokAuditEvent(basePath, buildAuditEnvelope({
|
|
120
120
|
traceId: `activity:${unitType}:${unitId}`,
|
|
121
121
|
turnId: unitId,
|
|
@@ -999,16 +999,22 @@ export class AutoOrchestrator {
|
|
|
999
999
|
this.postAdvanceRecord(skipped);
|
|
1000
1000
|
return skipped;
|
|
1001
1001
|
}
|
|
1002
|
-
// Stuck-loop detection:
|
|
1003
|
-
//
|
|
1004
|
-
//
|
|
1005
|
-
//
|
|
1006
|
-
//
|
|
1007
|
-
//
|
|
1008
|
-
//
|
|
1009
|
-
//
|
|
1010
|
-
//
|
|
1011
|
-
|
|
1002
|
+
// Stuck-loop detection: consult the Dispatch History module's full
|
|
1003
|
+
// detect-stuck rule set once the window is *full* — not only when a
|
|
1004
|
+
// single key saturates it. Gating on a single key's saturation count
|
|
1005
|
+
// (matchingCount >= STUCK_WINDOW_SIZE) let two-unit oscillations slip
|
|
1006
|
+
// through forever: an execute-task ↔ complete-slice loop (a slice gate
|
|
1007
|
+
// that keeps reopening the same task, #1225) fills the window with two
|
|
1008
|
+
// alternating keys, so neither key ever reaches the saturation count and
|
|
1009
|
+
// detect-stuck's oscillation/repeat rules were never even invoked. Firing
|
|
1010
|
+
// on window saturation instead covers both shapes. Consecutive same-key
|
|
1011
|
+
// re-advances (the benign pause/resume churn the old count gate guarded
|
|
1012
|
+
// against) are already short-circuited above by the idempotency skip, and
|
|
1013
|
+
// legitimate retry backoff is still suppressed inside detect-stuck via the
|
|
1014
|
+
// dispatch ledger — a saturated window with no verdict means we are inside
|
|
1015
|
+
// the unit's retry-backoff budget, so let the retry proceed.
|
|
1016
|
+
const windowSaturated = this.dispatchHistory.getRecentWindow().length >= STUCK_WINDOW_SIZE;
|
|
1017
|
+
const stuckVerdict = windowSaturated ? this.dispatchHistory.detectStuck() : null;
|
|
1012
1018
|
if (stuckVerdict) {
|
|
1013
1019
|
// #442: before declaring a stuck loop, verify the unit didn't actually
|
|
1014
1020
|
// complete on disk (stale DB) and recover if so — legacy graduated
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// resolveExpectedArtifactPath and diagnoseExpectedArtifact moved here from
|
|
6
6
|
// auto-recovery.ts (Phase 5 dead-code cleanup). The artifact verification
|
|
7
7
|
// function was removed entirely — callers now query WorkflowEngine directly.
|
|
8
|
-
import { gsdRoot, resolveDir, resolveFile, resolveMilestonePath, resolveMilestoneFile, resolveSliceFile, relMilestoneFile, relSliceFile, buildTaskFileName, resolveSlicePath, resolveTasksDir, dirIsMetaOnlyLegacyMilestone, } from "./paths.js";
|
|
8
|
+
import { gsdRoot, resolveDir, resolveFile, resolveMilestonePath, resolveMilestoneFile, resolveSliceFile, relMilestoneFile, relSliceFile, buildTaskFileName, resolveSlicePath, resolveTasksDir, dirIsMetaOnlyLegacyMilestone, normalizeRealPath, } from "./paths.js";
|
|
9
9
|
import { milestoneIdToPhaseNum } from "./layout-policy.js";
|
|
10
10
|
import { parseUnitId } from "./unit-id.js";
|
|
11
11
|
import { basename, dirname, join, relative } from "node:path";
|
|
@@ -308,7 +308,7 @@ export function resolveSliceResearchLocation(basePath, mid, sid) {
|
|
|
308
308
|
if (canonicalPath && existsSync(canonicalPath)) {
|
|
309
309
|
return {
|
|
310
310
|
absolutePath: canonicalPath,
|
|
311
|
-
relativePath: relative(basePath, canonicalPath),
|
|
311
|
+
relativePath: relative(normalizeRealPath(basePath), canonicalPath),
|
|
312
312
|
};
|
|
313
313
|
}
|
|
314
314
|
return { absolutePath: null, relativePath: null };
|
|
@@ -12,8 +12,9 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, readdir
|
|
|
12
12
|
import { logWarning, logError } from "./workflow-logger.js";
|
|
13
13
|
import { dirname, join, sep } from "node:path";
|
|
14
14
|
import { hasImplementationArtifacts } from "./milestone-implementation-evidence.js";
|
|
15
|
-
import { buildDiscussMilestonePrompt, buildDiscussProjectPrompt, buildDiscussRequirementsPrompt, buildResearchDecisionPrompt, buildResearchProjectPrompt, buildResearchMilestonePrompt, buildPlanMilestonePrompt, buildResearchSlicePrompt, buildPlanSlicePrompt, buildRefineSlicePrompt, buildExecuteTaskPrompt, buildCompleteSlicePrompt, buildCompleteMilestonePrompt, buildValidateMilestonePrompt, buildReplanSlicePrompt, buildRunUatPrompt, buildReassessRoadmapPrompt, buildRewriteDocsPrompt, buildReactiveExecutePrompt, buildGateEvaluatePrompt, buildParallelResearchSlicesPrompt, checkNeedsReassessment,
|
|
16
|
-
import {
|
|
15
|
+
import { buildDiscussMilestonePrompt, buildDiscussProjectPrompt, buildDiscussRequirementsPrompt, buildResearchDecisionPrompt, buildResearchProjectPrompt, buildResearchMilestonePrompt, buildPlanMilestonePrompt, buildResearchSlicePrompt, buildPlanSlicePrompt, buildRefineSlicePrompt, buildExecuteTaskPrompt, buildCompleteSlicePrompt, buildCompleteMilestonePrompt, buildValidateMilestonePrompt, buildReplanSlicePrompt, buildRunUatPrompt, buildReassessRoadmapPrompt, buildRewriteDocsPrompt, buildReactiveExecutePrompt, buildGateEvaluatePrompt, buildParallelResearchSlicesPrompt, checkNeedsReassessment, loadRoadmapCompletedSliceCandidates, } from "./auto-prompts.js";
|
|
16
|
+
import { checkNeedsRunUat } from "./uat-dispatch.js";
|
|
17
|
+
import { normalizeModelFieldConfig, resolveModelWithFallbacksForUnit, resolveThinkingLevelForUnit } from "./preferences-models.js";
|
|
17
18
|
import { resolveUokFlags } from "./uok/flags.js";
|
|
18
19
|
import { selectReactiveDispatchBatch } from "./uok/execution-graph.js";
|
|
19
20
|
import { getMilestonePipelineVariant } from "./milestone-scope-classifier.js";
|
|
@@ -142,12 +143,21 @@ export async function readUatGateVerdict(basePath, mid, sliceId) {
|
|
|
142
143
|
if (assessmentScope === "roadmap") {
|
|
143
144
|
return null;
|
|
144
145
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
// Backfilled assessments (#1258) are placeholders created during milestone
|
|
147
|
+
// validation for completed slices that never produced a real UAT ASSESSMENT
|
|
148
|
+
// (e.g. artifact-driven UAT that was never dispatched). Their fabricated
|
|
149
|
+
// verdict must not be treated as a genuine UAT sign-off — otherwise "never
|
|
150
|
+
// checked" is silently read as "passed". Skip the placeholder and fall
|
|
151
|
+
// through to the authoritative run-uat DB row (which stays null unless a
|
|
152
|
+
// real UAT was actually recorded).
|
|
153
|
+
if (assessmentScope !== BACKFILL_ASSESSMENT_SCOPE) {
|
|
154
|
+
const assessmentVerdict = extractVerdict(assessmentContent);
|
|
155
|
+
if (assessmentVerdict) {
|
|
156
|
+
return {
|
|
157
|
+
verdict: assessmentVerdict,
|
|
158
|
+
uatType: uatType ?? extractUatType(assessmentContent),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
151
161
|
}
|
|
152
162
|
}
|
|
153
163
|
if (uatContent) {
|
|
@@ -291,11 +301,20 @@ function stringField(row, key) {
|
|
|
291
301
|
function stripGsdPrefix(path) {
|
|
292
302
|
return path.startsWith(".gsd/") ? path.slice(".gsd/".length) : path;
|
|
293
303
|
}
|
|
294
|
-
|
|
304
|
+
// Scope marker for assessments fabricated by the milestone-validation backfill
|
|
305
|
+
// (#1258). It keeps these placeholders distinguishable from genuine `run-uat`
|
|
306
|
+
// sign-offs so `readUatGateVerdict` never mistakes "never checked" for "passed".
|
|
307
|
+
const BACKFILL_ASSESSMENT_SCOPE = "backfill";
|
|
308
|
+
function persistSliceAssessmentBackfill(assessmentRelPath, mid, sliceId, content, fabricated) {
|
|
295
309
|
const artifactPath = stripGsdPrefix(assessmentRelPath);
|
|
296
310
|
const existingAssessment = getAssessment(assessmentRelPath) ??
|
|
297
311
|
getAssessment(artifactPath);
|
|
298
|
-
|
|
312
|
+
// A newly fabricated placeholder is filed under a distinct scope; a pre-existing
|
|
313
|
+
// on-disk ASSESSMENT keeps its real scope (default `run-uat`) so genuine
|
|
314
|
+
// sign-offs are still honored.
|
|
315
|
+
const scope = fabricated
|
|
316
|
+
? BACKFILL_ASSESSMENT_SCOPE
|
|
317
|
+
: stringField(existingAssessment, "scope") ?? "run-uat";
|
|
299
318
|
const status = stringField(existingAssessment, "status") ??
|
|
300
319
|
extractVerdict(content)?.toLowerCase() ??
|
|
301
320
|
"unknown";
|
|
@@ -343,18 +362,24 @@ function backfillMissingAssessmentsFromSummaries(basePath, mid) {
|
|
|
343
362
|
"---",
|
|
344
363
|
`sliceId: ${sliceId}`,
|
|
345
364
|
"verdict: PASS",
|
|
365
|
+
// Distinguishing marker (#1258): this ASSESSMENT was fabricated to satisfy
|
|
366
|
+
// the per-slice artifact requirement, NOT produced by a real UAT run. It
|
|
367
|
+
// must not be read as a genuine UAT sign-off.
|
|
368
|
+
"backfilled: true",
|
|
369
|
+
"verified: false",
|
|
346
370
|
`date: ${now}`,
|
|
347
371
|
"---",
|
|
348
372
|
"",
|
|
349
373
|
`# Assessment — ${sliceId}`,
|
|
350
374
|
"",
|
|
351
375
|
"Auto-created during milestone validation because this completed slice had a SUMMARY but no ASSESSMENT artifact.",
|
|
376
|
+
"This is a placeholder: no UAT was executed for this slice, so its verdict is not an independent sign-off.",
|
|
352
377
|
"No additional reassessment changes were detected in this backfill step.",
|
|
353
378
|
"",
|
|
354
379
|
].join("\n") : readFileSync(assessmentPath, "utf-8");
|
|
355
380
|
if (isDbAvailable()) {
|
|
356
381
|
try {
|
|
357
|
-
persistSliceAssessmentBackfill(assessmentRelPath, mid, sliceId, content);
|
|
382
|
+
persistSliceAssessmentBackfill(assessmentRelPath, mid, sliceId, content, didCreateAssessment);
|
|
358
383
|
}
|
|
359
384
|
catch (err) {
|
|
360
385
|
logWarning("dispatch", `failed to backfill assessment DB rows for ${mid}/${sliceId}: ${err.message}`);
|
|
@@ -534,7 +559,7 @@ export const DISPATCH_RULES = [
|
|
|
534
559
|
{
|
|
535
560
|
name: "run-uat (post-completion)",
|
|
536
561
|
match: async ({ state, mid, basePath, prefs, sessionProvider, sessionAuthMode, activeTools, registeredTools, sessionBaseUrl, }) => {
|
|
537
|
-
const needsRunUat = await checkNeedsRunUat(basePath, mid,
|
|
562
|
+
const needsRunUat = await checkNeedsRunUat(basePath, mid, prefs, await loadRoadmapCompletedSliceCandidates(basePath, mid));
|
|
538
563
|
if (!needsRunUat)
|
|
539
564
|
return null;
|
|
540
565
|
const { sliceId, uatType } = needsRunUat;
|
|
@@ -1168,8 +1193,15 @@ export const DISPATCH_RULES = [
|
|
|
1168
1193
|
if (resolveSliceFile(basePath, mid, sid, "REACTIVE-BLOCKER"))
|
|
1169
1194
|
return null;
|
|
1170
1195
|
const maxParallel = reactiveConfig?.max_parallel ?? 2;
|
|
1171
|
-
|
|
1172
|
-
|
|
1196
|
+
// `subagent_model` accepts the phase-bucket object form (#1229); honor the
|
|
1197
|
+
// full primary→fallbacks chain in the dispatch prompt (the reactive
|
|
1198
|
+
// subagent model is embedded there rather than set as the session model).
|
|
1199
|
+
const subagentModelConfig = normalizeModelFieldConfig(reactiveConfig?.subagent_model)
|
|
1200
|
+
?? resolveModelWithFallbacksForUnit("subagent");
|
|
1201
|
+
const subagentModel = subagentModelConfig?.primary;
|
|
1202
|
+
// Prefer the per-field `thinking` carried on `reactive_execution.subagent_model`'s
|
|
1203
|
+
// object form over the phase-bucket `subagent` level (#1269).
|
|
1204
|
+
const subagentThinking = subagentModelConfig?.thinking ?? resolveThinkingLevelForUnit("subagent");
|
|
1173
1205
|
// Default-on safety threshold: only activate reactive dispatch when at
|
|
1174
1206
|
// least N tasks are ready. Users who explicitly enabled reactive_execution
|
|
1175
1207
|
// keep the legacy threshold of 2 (matches the prior "any parallelism is
|
|
@@ -1229,7 +1261,13 @@ export const DISPATCH_RULES = [
|
|
|
1229
1261
|
action: "dispatch",
|
|
1230
1262
|
unitType: "reactive-execute",
|
|
1231
1263
|
unitId: `${mid}/${sid}/reactive+${batchSuffix}`,
|
|
1232
|
-
prompt: await buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, selected, basePath, subagentModel, {
|
|
1264
|
+
prompt: await buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, selected, basePath, subagentModel, {
|
|
1265
|
+
sessionContextWindow,
|
|
1266
|
+
modelRegistry,
|
|
1267
|
+
sessionProvider,
|
|
1268
|
+
subagentThinking,
|
|
1269
|
+
subagentModelFallbacks: subagentModelConfig?.fallbacks,
|
|
1270
|
+
}),
|
|
1233
1271
|
};
|
|
1234
1272
|
}
|
|
1235
1273
|
catch (err) {
|
|
@@ -219,10 +219,37 @@ function buildModelPolicyBlockReasons(policyDenyReasons, availableModels, routin
|
|
|
219
219
|
reason: `configured model(s) did not resolve against policy-eligible registry [${eligibleSummary}]`,
|
|
220
220
|
}];
|
|
221
221
|
}
|
|
222
|
-
function isModelUnavailable(basePath, provider, id) {
|
|
222
|
+
export function isModelUnavailable(basePath, provider, id) {
|
|
223
223
|
return isModelBlocked(basePath, provider, id) ||
|
|
224
224
|
isModelTemporarilyUnavailable(basePath, provider, id);
|
|
225
225
|
}
|
|
226
|
+
/** Apply `auto_supervisor.model`, walking configured fallbacks on unavailability (#1229). */
|
|
227
|
+
export async function applySupervisorModelIfConfigured(ctx, pi, basePath) {
|
|
228
|
+
const modelConfig = resolveModelWithFallbacksForUnit("supervisor", basePath);
|
|
229
|
+
if (!modelConfig)
|
|
230
|
+
return;
|
|
231
|
+
const availableModels = ctx.modelRegistry.getAvailable();
|
|
232
|
+
for (const candidate of [modelConfig.primary, ...modelConfig.fallbacks]) {
|
|
233
|
+
const match = resolveModelId(candidate, availableModels, ctx.model?.provider);
|
|
234
|
+
if (!match)
|
|
235
|
+
continue;
|
|
236
|
+
if (isModelUnavailable(basePath, match.provider, match.id))
|
|
237
|
+
continue;
|
|
238
|
+
try {
|
|
239
|
+
if (await pi.setModel(match, { persist: false })) {
|
|
240
|
+
// Prefer the per-field `thinking` from `auto_supervisor.model`'s object
|
|
241
|
+
// form over the session level so a configured supervisor level actually
|
|
242
|
+
// applies (#1269).
|
|
243
|
+
applyThinkingLevelForModel(pi, modelConfig.thinking ?? pi.getThinkingLevel(), match, ctx);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
// Non-fatal — try the next fallback.
|
|
249
|
+
logWarning("dispatch", `supervisor model set failed for ${candidate}: ${err instanceof Error ? err.message : String(err)}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
226
253
|
function restoreToolBaseline(pi) {
|
|
227
254
|
const key = pi;
|
|
228
255
|
const baseline = TOOL_BASELINE.get(key);
|
|
@@ -17,7 +17,7 @@ import { logWarning, logError } from "./workflow-logger.js";
|
|
|
17
17
|
import { loadFile, parseSummary, resolveAllOverrides } from "./files.js";
|
|
18
18
|
import { loadPrompt } from "./prompt-loader.js";
|
|
19
19
|
import { isAwaitingUserInput } from "./consent-question.js";
|
|
20
|
-
import { resolveSliceFile, resolveSlicePath, relSlicePath, resolveTaskFile, resolveMilestoneFile, resolveTasksDir, resolveFile, relMilestoneFile, relSliceFile, relTaskFile, } from "./paths.js";
|
|
20
|
+
import { resolveSliceFile, resolveSlicePath, relSlicePath, resolveTaskFile, resolveMilestoneFile, resolveTasksDir, resolveFile, relMilestoneFile, relSliceFile, relTaskFile, normalizeRealPath, } from "./paths.js";
|
|
21
21
|
import { invalidateAllCaches } from "./cache.js";
|
|
22
22
|
import { rebuildState } from "./doctor.js";
|
|
23
23
|
import { parseUnitId } from "./unit-id.js";
|
|
@@ -770,7 +770,7 @@ function describeArtifactVerificationFailure(unitType, unitId, basePath, agentEn
|
|
|
770
770
|
if (!artifactPath) {
|
|
771
771
|
return `Artifact verification failed: ${unitType} "${unitId}" has no resolvable artifact path.`;
|
|
772
772
|
}
|
|
773
|
-
const relPath = relative(basePath, artifactPath);
|
|
773
|
+
const relPath = relative(normalizeRealPath(basePath), artifactPath);
|
|
774
774
|
if (!existsSync(artifactPath)) {
|
|
775
775
|
const completionToolHint = unitType === "execute-task" && !hasTaskCompletionToolCall(agentEndMessages)
|
|
776
776
|
? " No completion tool call detected (`gsd_task_complete`/alias)."
|
|
@@ -1987,7 +1987,8 @@ export async function postUnitPostVerification(pctx) {
|
|
|
1987
1987
|
persistHookState(s.basePath);
|
|
1988
1988
|
const verdict = gateBlock.verdict ? ` verdict=${gateBlock.verdict};` : "";
|
|
1989
1989
|
const artifact = gateBlock.artifact ? ` artifact=${gateBlock.artifact};` : "";
|
|
1990
|
-
const message = `Post-unit gate "${gateBlock.hookName}" blocked ${gateBlock.triggerUnitType} ${gateBlock.triggerUnitId}
|
|
1990
|
+
const message = `Post-unit gate "${gateBlock.hookName}" blocked ${gateBlock.triggerUnitType} ${gateBlock.triggerUnitId} ` +
|
|
1991
|
+
`(detected on completion of ${s.currentUnit.type} ${s.currentUnit.id}):` +
|
|
1991
1992
|
`${verdict}${artifact} ${gateBlock.reason}. Run /gsd status to inspect, then /gsd auto after recovery.`;
|
|
1992
1993
|
ctx.ui.notify(message, "warning");
|
|
1993
1994
|
await pauseAuto(ctx, pi);
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* utility.
|
|
9
9
|
*/
|
|
10
10
|
import { loadFile, parseContinue, parseSummary, loadActiveOverrides, formatOverridesSection } from "./files.js";
|
|
11
|
-
import {
|
|
11
|
+
import { extractVerdict } from "./verdict-parser.js";
|
|
12
12
|
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
13
13
|
import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, relTaskFile, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile, } from "./paths.js";
|
|
14
14
|
import { resolveInlineLevel, loadEffectiveGSDPreferences } from "./preferences.js";
|
|
@@ -35,7 +35,8 @@ import { debugLog } from "./debug-logger.js";
|
|
|
35
35
|
import { buildSkillActivationBlock, buildSkillDiscoveryVars } from "./skill-activation.js";
|
|
36
36
|
import { findMilestoneIds } from "./milestone-ids.js";
|
|
37
37
|
import { buildRunUatPresentationForType, RUN_UAT_TOOL_PRESENTATION_PLAN_ID } from "./tool-presentation-plan.js";
|
|
38
|
-
import { classifyUatContentForRun
|
|
38
|
+
import { classifyUatContentForRun } from "./uat-policy.js";
|
|
39
|
+
import { checkNeedsRunUat as resolveNeedsRunUat } from "./uat-dispatch.js";
|
|
39
40
|
import { buildWebAppUatGuidanceBlock } from "./web-app-uat.js";
|
|
40
41
|
export { buildSkillActivationBlock, buildSkillDiscoveryVars };
|
|
41
42
|
// ─── Preamble Cap ─────────────────────────────────────────────────────────────
|
|
@@ -1320,120 +1321,26 @@ export async function checkNeedsReassessment(base, mid, state) {
|
|
|
1320
1321
|
return null;
|
|
1321
1322
|
return { sliceId: lastDone };
|
|
1322
1323
|
}
|
|
1324
|
+
export function getRoadmapCompletedSliceCandidates(roadmapContent) {
|
|
1325
|
+
return parseRoadmap(roadmapContent)
|
|
1326
|
+
.slices.filter((slice) => slice.done)
|
|
1327
|
+
.map((slice) => ({ sliceId: slice.id }))
|
|
1328
|
+
.reverse();
|
|
1329
|
+
}
|
|
1330
|
+
export async function loadRoadmapCompletedSliceCandidates(base, mid) {
|
|
1331
|
+
const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
|
|
1332
|
+
const roadmapContent = roadmapPath ? await loadFile(roadmapPath) : null;
|
|
1333
|
+
return roadmapContent ? getRoadmapCompletedSliceCandidates(roadmapContent) : [];
|
|
1334
|
+
}
|
|
1323
1335
|
/**
|
|
1324
|
-
*
|
|
1325
|
-
*
|
|
1326
|
-
*
|
|
1327
|
-
*
|
|
1328
|
-
* - No roadmap or no completed slices
|
|
1329
|
-
* - uat_dispatch is not enabled and the UAT spec does not require runtime/browser evidence
|
|
1330
|
-
* - No UAT file exists for the slice
|
|
1331
|
-
* - UAT result file already exists (idempotent — already ran)
|
|
1332
|
-
*/
|
|
1333
|
-
/**
|
|
1334
|
-
* Resolve the effective UAT mode for the dispatch gate the same way
|
|
1335
|
-
* `buildRunUatPrompt` does: classify the UAT body with the slice's SUMMARY as
|
|
1336
|
-
* supplemental context, so a `browser-executable` UAT whose slice references a
|
|
1337
|
-
* self-contained harness (`npm run test:uat`, `search-uat.mjs`, `npx
|
|
1338
|
-
* playwright test`) is promoted to `runtime-executable` for the gate too.
|
|
1339
|
-
*
|
|
1340
|
-
* Without this, `checkNeedsRunUat` returns `browser-executable` while the
|
|
1341
|
-
* prompt instructs runtime-only execution, causing the dispatch gate to
|
|
1342
|
-
* require browser tools / warm up the browser daemon (or stop dispatch when
|
|
1343
|
-
* browser MCP is unavailable) for UAT runs that never touch the browser. See
|
|
1344
|
-
* cursor[bot] review on PR #696 for the M007/S01 regression.
|
|
1336
|
+
* Back-compat wrapper for callers that import UAT dispatch checks from the
|
|
1337
|
+
* prompt module. UAT dispatch architecture lives in `uat-dispatch.ts`; the
|
|
1338
|
+
* `state` parameter is intentionally ignored because DB/roadmap artifacts are
|
|
1339
|
+
* the source of truth for completed-slice discovery.
|
|
1345
1340
|
*/
|
|
1346
|
-
async function resolveRunUatEffectiveType(base, mid, sliceId, uatContent) {
|
|
1347
|
-
let summaryContent = "";
|
|
1348
|
-
try {
|
|
1349
|
-
const summaryPath = resolveSliceFile(base, mid, sliceId, "SUMMARY");
|
|
1350
|
-
if (summaryPath) {
|
|
1351
|
-
summaryContent = (await loadFile(summaryPath)) ?? "";
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
catch (err) {
|
|
1355
|
-
logWarning("prompt", `resolveRunUatEffectiveType SUMMARY load failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1356
|
-
}
|
|
1357
|
-
return classifyUatContentForRun(uatContent, summaryContent).effectiveType;
|
|
1358
|
-
}
|
|
1359
1341
|
export async function checkNeedsRunUat(base, mid, state, prefs) {
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
const { isDbAvailable, getMilestoneSlices } = await import("./gsd-db.js");
|
|
1363
|
-
if (isDbAvailable()) {
|
|
1364
|
-
const slices = getMilestoneSlices(mid);
|
|
1365
|
-
if (slices.length > 0) {
|
|
1366
|
-
const completedSlices = slices.filter(s => s.status === "complete");
|
|
1367
|
-
if (completedSlices.length === 0)
|
|
1368
|
-
return null;
|
|
1369
|
-
for (const completed of [...completedSlices].reverse()) {
|
|
1370
|
-
const sid = completed.id;
|
|
1371
|
-
const uatFile = resolveSliceFile(base, mid, sid, "UAT");
|
|
1372
|
-
if (!uatFile)
|
|
1373
|
-
continue;
|
|
1374
|
-
const uatContent = await loadFile(uatFile);
|
|
1375
|
-
if (!uatContent)
|
|
1376
|
-
continue;
|
|
1377
|
-
// If the UAT file already contains a verdict, UAT has been run — skip
|
|
1378
|
-
if (hasVerdict(uatContent))
|
|
1379
|
-
continue;
|
|
1380
|
-
// Also check the ASSESSMENT file — the run-uat prompt writes the verdict
|
|
1381
|
-
// there (via gsd_uat_result_save), not into the
|
|
1382
|
-
// UAT spec file. Without this check the unit re-dispatches indefinitely.
|
|
1383
|
-
const assessmentFile = resolveSliceFile(base, mid, sid, "ASSESSMENT");
|
|
1384
|
-
if (assessmentFile) {
|
|
1385
|
-
const assessmentContent = await loadFile(assessmentFile);
|
|
1386
|
-
if (assessmentContent && hasVerdict(assessmentContent))
|
|
1387
|
-
continue;
|
|
1388
|
-
}
|
|
1389
|
-
if (!shouldDispatchUatForContent(uatContent, prefs))
|
|
1390
|
-
continue;
|
|
1391
|
-
const uatType = await resolveRunUatEffectiveType(base, mid, sid, uatContent);
|
|
1392
|
-
return { sliceId: sid, uatType };
|
|
1393
|
-
}
|
|
1394
|
-
return null;
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
catch (err) {
|
|
1399
|
-
logWarning("prompt", `checkNeedsRunUat DB lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1400
|
-
}
|
|
1401
|
-
// File-based fallback using roadmap checkboxes
|
|
1402
|
-
const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
|
|
1403
|
-
if (!roadmapPath)
|
|
1404
|
-
return null;
|
|
1405
|
-
const roadmapContent = await loadFile(roadmapPath);
|
|
1406
|
-
if (!roadmapContent)
|
|
1407
|
-
return null;
|
|
1408
|
-
const parsed = parseRoadmap(roadmapContent);
|
|
1409
|
-
const completedFileSlices = parsed.slices.filter(s => s.done);
|
|
1410
|
-
if (completedFileSlices.length === 0)
|
|
1411
|
-
return null;
|
|
1412
|
-
for (const completed of [...completedFileSlices].reverse()) {
|
|
1413
|
-
const uatSid = completed.id;
|
|
1414
|
-
const uatFileFb = resolveSliceFile(base, mid, uatSid, "UAT");
|
|
1415
|
-
if (!uatFileFb)
|
|
1416
|
-
continue;
|
|
1417
|
-
const uatContentFb = await loadFile(uatFileFb);
|
|
1418
|
-
if (!uatContentFb)
|
|
1419
|
-
continue;
|
|
1420
|
-
// If the UAT file already contains a verdict, UAT has been run — skip
|
|
1421
|
-
if (hasVerdict(uatContentFb))
|
|
1422
|
-
continue;
|
|
1423
|
-
// Also check the ASSESSMENT file for the file-based fallback path (same
|
|
1424
|
-
// reason as the DB path above — verdict lives in ASSESSMENT, not UAT).
|
|
1425
|
-
const assessmentFileFb = resolveSliceFile(base, mid, uatSid, "ASSESSMENT");
|
|
1426
|
-
if (assessmentFileFb) {
|
|
1427
|
-
const assessmentContentFb = await loadFile(assessmentFileFb);
|
|
1428
|
-
if (assessmentContentFb && hasVerdict(assessmentContentFb))
|
|
1429
|
-
continue;
|
|
1430
|
-
}
|
|
1431
|
-
if (!shouldDispatchUatForContent(uatContentFb, prefs))
|
|
1432
|
-
continue;
|
|
1433
|
-
const uatType = await resolveRunUatEffectiveType(base, mid, uatSid, uatContentFb);
|
|
1434
|
-
return { sliceId: uatSid, uatType };
|
|
1435
|
-
}
|
|
1436
|
-
return null;
|
|
1342
|
+
void state;
|
|
1343
|
+
return resolveNeedsRunUat(base, mid, prefs, await loadRoadmapCompletedSliceCandidates(base, mid));
|
|
1437
1344
|
}
|
|
1438
1345
|
export async function buildDiscussMilestoneInlinedContext(mid, base) {
|
|
1439
1346
|
const inlined = [];
|
|
@@ -3401,10 +3308,15 @@ export async function buildReassessRoadmapPrompt(mid, midTitle, completedSliceId
|
|
|
3401
3308
|
* that instructs the coordinator how to dispatch a `subagent` call. Either or
|
|
3402
3309
|
* both may be absent (ADR-026 / #508).
|
|
3403
3310
|
*/
|
|
3404
|
-
function subagentCallSuffix(model, thinking) {
|
|
3311
|
+
function subagentCallSuffix(model, thinking, fallbacks) {
|
|
3405
3312
|
const parts = [];
|
|
3406
|
-
if (model)
|
|
3407
|
-
|
|
3313
|
+
if (model) {
|
|
3314
|
+
let modelHint = `model: "${model}"`;
|
|
3315
|
+
if (fallbacks && fallbacks.length > 0) {
|
|
3316
|
+
modelHint += ` (fallbacks, in order: ${fallbacks.map((f) => `"${f}"`).join(", ")})`;
|
|
3317
|
+
}
|
|
3318
|
+
parts.push(modelHint);
|
|
3319
|
+
}
|
|
3408
3320
|
if (thinking)
|
|
3409
3321
|
parts.push(`thinking: "${thinking}"`);
|
|
3410
3322
|
return parts.length > 0 ? ` with ${parts.join(" and ")}` : "";
|
|
@@ -3490,7 +3402,7 @@ opts) {
|
|
|
3490
3402
|
"",
|
|
3491
3403
|
`When done, say: "Task ${tid} complete."`,
|
|
3492
3404
|
].join("\n");
|
|
3493
|
-
const modelSuffix = subagentCallSuffix(subagentModel, opts?.subagentThinking);
|
|
3405
|
+
const modelSuffix = subagentCallSuffix(subagentModel, opts?.subagentThinking, opts?.subagentModelFallbacks);
|
|
3494
3406
|
subagentSections.push([
|
|
3495
3407
|
`### ${tid}: ${tTitle}`,
|
|
3496
3408
|
"",
|