@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
|
@@ -55,6 +55,31 @@ describe("resolveGsdBrowserMcpLaunchConfig identity flags", () => {
|
|
|
55
55
|
assert.equal(args[1], "mcp");
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
+
it("appends GSD_BROWSER_MCP_EXTRA_ARGS after the identity flags (string form)", () => {
|
|
59
|
+
const { args } = resolveGsdBrowserMcpLaunchConfig("/tmp/example-project", {
|
|
60
|
+
GSD_BROWSER_MCP_EXTRA_ARGS: "--stealth --browser-path /usr/bin/chromium",
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Managed flags stay intact and the extra flags trail them.
|
|
64
|
+
assert.ok(args.indexOf("--identity-project") >= 0);
|
|
65
|
+
assert.deepEqual(args.slice(-3), ["--stealth", "--browser-path", "/usr/bin/chromium"]);
|
|
66
|
+
assert.ok(args.indexOf("--stealth") > args.indexOf("--identity-project"));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("accepts GSD_BROWSER_MCP_EXTRA_ARGS as a JSON array", () => {
|
|
70
|
+
const { args } = resolveGsdBrowserMcpLaunchConfig("/tmp/example-project", {
|
|
71
|
+
GSD_BROWSER_MCP_EXTRA_ARGS: '["--stealth"]',
|
|
72
|
+
});
|
|
73
|
+
assert.equal(args[args.length - 1], "--stealth");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("forwards GSD_BROWSER_MCP_EXTRA_ARGS to the daemon-start invocation", () => {
|
|
77
|
+
const env = { GSD_BROWSER_MCP_EXTRA_ARGS: "--stealth" };
|
|
78
|
+
const daemon = resolveGsdBrowserDaemonStartInvocation("/tmp/example-project", env);
|
|
79
|
+
assert.equal(daemon.args[daemon.args.length - 1], "--stealth");
|
|
80
|
+
assert.ok(daemon.args.indexOf("daemon") >= 0 && daemon.args.indexOf("start") >= 0);
|
|
81
|
+
});
|
|
82
|
+
|
|
58
83
|
it("uses a path-safe identity-project identifier", () => {
|
|
59
84
|
const { args } = resolveGsdBrowserMcpLaunchConfig("/tmp/example/project", {});
|
|
60
85
|
const projectId = args[args.indexOf("--identity-project") + 1];
|
|
@@ -4,6 +4,7 @@ import assert from "node:assert/strict";
|
|
|
4
4
|
const {
|
|
5
5
|
MANAGED_BROWSER_TOOL_SPECS,
|
|
6
6
|
MANAGED_GSD_BROWSER_TOOL_NAMES,
|
|
7
|
+
ManagedGsdBrowserConnectionPool,
|
|
7
8
|
findMissingContractCoverage,
|
|
8
9
|
normalizeManagedArgs,
|
|
9
10
|
registerManagedGsdBrowserTools,
|
|
@@ -29,6 +30,17 @@ const GSD_BROWSER_SERVED_TOOLS = [
|
|
|
29
30
|
"browser_act",
|
|
30
31
|
];
|
|
31
32
|
|
|
33
|
+
function makeLaunchConfig() {
|
|
34
|
+
return {
|
|
35
|
+
command: "gsd-browser",
|
|
36
|
+
args: ["mcp"],
|
|
37
|
+
cwd: "/tmp/example-project",
|
|
38
|
+
projectRoot: "/tmp/example-project",
|
|
39
|
+
serverName: "gsd-browser",
|
|
40
|
+
sessionName: "test-session",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
32
44
|
describe("registerManagedGsdBrowserTools", () => {
|
|
33
45
|
it("registers the curated Pi browser contract", () => {
|
|
34
46
|
const tools = [];
|
|
@@ -72,6 +84,87 @@ describe("findMissingContractCoverage", () => {
|
|
|
72
84
|
});
|
|
73
85
|
});
|
|
74
86
|
|
|
87
|
+
describe("ManagedGsdBrowserConnectionPool", () => {
|
|
88
|
+
it("closes a pending connection that resolves after the pool is closed", async () => {
|
|
89
|
+
const closed = [];
|
|
90
|
+
let resolveConnect;
|
|
91
|
+
const pool = new ManagedGsdBrowserConnectionPool(async () => {
|
|
92
|
+
return new Promise((resolve) => {
|
|
93
|
+
resolveConnect = resolve;
|
|
94
|
+
});
|
|
95
|
+
}, async (connection) => {
|
|
96
|
+
closed.push(connection.id);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const launch = makeLaunchConfig();
|
|
100
|
+
|
|
101
|
+
const pending = pool.getOrConnect(launch);
|
|
102
|
+
const closedPool = pool.closeAll();
|
|
103
|
+
resolveConnect({ id: "late-connection" });
|
|
104
|
+
|
|
105
|
+
await assert.rejects(
|
|
106
|
+
pending,
|
|
107
|
+
/closed during startup/,
|
|
108
|
+
"pending callers must not receive a reusable connection after close",
|
|
109
|
+
);
|
|
110
|
+
await closedPool;
|
|
111
|
+
|
|
112
|
+
assert.deepEqual(closed, ["late-connection"]);
|
|
113
|
+
assert.equal(pool.activeCount, 0);
|
|
114
|
+
assert.equal(pool.pendingCount, 0);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("aborts pending connection attempts when the pool is closed", async () => {
|
|
118
|
+
let receivedSignal;
|
|
119
|
+
const pool = new ManagedGsdBrowserConnectionPool(async (_launch, signal) => {
|
|
120
|
+
receivedSignal = signal;
|
|
121
|
+
return new Promise((_resolve, reject) => {
|
|
122
|
+
signal.addEventListener("abort", () => reject(new Error("connect aborted")), { once: true });
|
|
123
|
+
});
|
|
124
|
+
}, async () => {});
|
|
125
|
+
|
|
126
|
+
const pending = pool.getOrConnect(makeLaunchConfig());
|
|
127
|
+
const closedPool = pool.closeAll();
|
|
128
|
+
|
|
129
|
+
await assert.rejects(pending, /connect aborted/);
|
|
130
|
+
await closedPool;
|
|
131
|
+
|
|
132
|
+
assert.equal(receivedSignal?.aborted, true);
|
|
133
|
+
assert.equal(pool.activeCount, 0);
|
|
134
|
+
assert.equal(pool.pendingCount, 0);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("rejects new connection attempts started while the pool is closing", async () => {
|
|
138
|
+
let resolveConnect;
|
|
139
|
+
let connectCount = 0;
|
|
140
|
+
const pool = new ManagedGsdBrowserConnectionPool(async () => {
|
|
141
|
+
connectCount += 1;
|
|
142
|
+
return new Promise((resolve) => {
|
|
143
|
+
resolveConnect = resolve;
|
|
144
|
+
});
|
|
145
|
+
}, async () => {});
|
|
146
|
+
|
|
147
|
+
const pending = pool.getOrConnect(makeLaunchConfig());
|
|
148
|
+
const closedPool = pool.closeAll();
|
|
149
|
+
|
|
150
|
+
// A caller that arrives during the in-flight closeAll must not open a new
|
|
151
|
+
// connection that could survive shutdown.
|
|
152
|
+
await assert.rejects(
|
|
153
|
+
pool.getOrConnect(makeLaunchConfig()),
|
|
154
|
+
/closing/,
|
|
155
|
+
"getOrConnect must reject while the pool is closing",
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
resolveConnect({ id: "pending-connection" });
|
|
159
|
+
await assert.rejects(pending, /closed during startup/);
|
|
160
|
+
await closedPool;
|
|
161
|
+
|
|
162
|
+
assert.equal(connectCount, 1, "no second connection is started during shutdown");
|
|
163
|
+
assert.equal(pool.activeCount, 0);
|
|
164
|
+
assert.equal(pool.pendingCount, 0);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
75
168
|
describe("contract tool translations", () => {
|
|
76
169
|
it("translates browser_click into a single-step batch call", () => {
|
|
77
170
|
const calls = MANAGED_BROWSER_TOOL_SPECS.browser_click.translate.build({ selector: "#save" });
|
|
@@ -1196,17 +1196,23 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
1196
1196
|
return skipped;
|
|
1197
1197
|
}
|
|
1198
1198
|
|
|
1199
|
-
// Stuck-loop detection:
|
|
1200
|
-
//
|
|
1201
|
-
//
|
|
1202
|
-
//
|
|
1203
|
-
//
|
|
1204
|
-
//
|
|
1205
|
-
//
|
|
1206
|
-
//
|
|
1207
|
-
//
|
|
1208
|
-
|
|
1209
|
-
|
|
1199
|
+
// Stuck-loop detection: consult the Dispatch History module's full
|
|
1200
|
+
// detect-stuck rule set once the window is *full* — not only when a
|
|
1201
|
+
// single key saturates it. Gating on a single key's saturation count
|
|
1202
|
+
// (matchingCount >= STUCK_WINDOW_SIZE) let two-unit oscillations slip
|
|
1203
|
+
// through forever: an execute-task ↔ complete-slice loop (a slice gate
|
|
1204
|
+
// that keeps reopening the same task, #1225) fills the window with two
|
|
1205
|
+
// alternating keys, so neither key ever reaches the saturation count and
|
|
1206
|
+
// detect-stuck's oscillation/repeat rules were never even invoked. Firing
|
|
1207
|
+
// on window saturation instead covers both shapes. Consecutive same-key
|
|
1208
|
+
// re-advances (the benign pause/resume churn the old count gate guarded
|
|
1209
|
+
// against) are already short-circuited above by the idempotency skip, and
|
|
1210
|
+
// legitimate retry backoff is still suppressed inside detect-stuck via the
|
|
1211
|
+
// dispatch ledger — a saturated window with no verdict means we are inside
|
|
1212
|
+
// the unit's retry-backoff budget, so let the retry proceed.
|
|
1213
|
+
const windowSaturated =
|
|
1214
|
+
this.dispatchHistory.getRecentWindow().length >= STUCK_WINDOW_SIZE;
|
|
1215
|
+
const stuckVerdict = windowSaturated ? this.dispatchHistory.detectStuck() : null;
|
|
1210
1216
|
if (stuckVerdict) {
|
|
1211
1217
|
// #442: before declaring a stuck loop, verify the unit didn't actually
|
|
1212
1218
|
// complete on disk (stale DB) and recover if so — legacy graduated
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
resolveSlicePath,
|
|
20
20
|
resolveTasksDir,
|
|
21
21
|
dirIsMetaOnlyLegacyMilestone,
|
|
22
|
+
normalizeRealPath,
|
|
22
23
|
} from "./paths.js";
|
|
23
24
|
import { milestoneIdToPhaseNum } from "./layout-policy.js";
|
|
24
25
|
import { parseUnitId } from "./unit-id.js";
|
|
@@ -354,7 +355,7 @@ export function resolveSliceResearchLocation(
|
|
|
354
355
|
if (canonicalPath && existsSync(canonicalPath)) {
|
|
355
356
|
return {
|
|
356
357
|
absolutePath: canonicalPath,
|
|
357
|
-
relativePath: relative(basePath, canonicalPath),
|
|
358
|
+
relativePath: relative(normalizeRealPath(basePath), canonicalPath),
|
|
358
359
|
};
|
|
359
360
|
}
|
|
360
361
|
|
|
@@ -79,9 +79,10 @@ import {
|
|
|
79
79
|
buildGateEvaluatePrompt,
|
|
80
80
|
buildParallelResearchSlicesPrompt,
|
|
81
81
|
checkNeedsReassessment,
|
|
82
|
-
|
|
82
|
+
loadRoadmapCompletedSliceCandidates,
|
|
83
83
|
} from "./auto-prompts.js";
|
|
84
|
-
import {
|
|
84
|
+
import { checkNeedsRunUat } from "./uat-dispatch.js";
|
|
85
|
+
import { normalizeModelFieldConfig, resolveModelWithFallbacksForUnit, resolveThinkingLevelForUnit } from "./preferences-models.js";
|
|
85
86
|
import { resolveUokFlags } from "./uok/flags.js";
|
|
86
87
|
import { selectReactiveDispatchBatch } from "./uok/execution-graph.js";
|
|
87
88
|
import { getMilestonePipelineVariant } from "./milestone-scope-classifier.js";
|
|
@@ -308,12 +309,21 @@ export async function readUatGateVerdict(
|
|
|
308
309
|
return null;
|
|
309
310
|
}
|
|
310
311
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
312
|
+
// Backfilled assessments (#1258) are placeholders created during milestone
|
|
313
|
+
// validation for completed slices that never produced a real UAT ASSESSMENT
|
|
314
|
+
// (e.g. artifact-driven UAT that was never dispatched). Their fabricated
|
|
315
|
+
// verdict must not be treated as a genuine UAT sign-off — otherwise "never
|
|
316
|
+
// checked" is silently read as "passed". Skip the placeholder and fall
|
|
317
|
+
// through to the authoritative run-uat DB row (which stays null unless a
|
|
318
|
+
// real UAT was actually recorded).
|
|
319
|
+
if (assessmentScope !== BACKFILL_ASSESSMENT_SCOPE) {
|
|
320
|
+
const assessmentVerdict = extractVerdict(assessmentContent);
|
|
321
|
+
if (assessmentVerdict) {
|
|
322
|
+
return {
|
|
323
|
+
verdict: assessmentVerdict,
|
|
324
|
+
uatType: uatType ?? extractUatType(assessmentContent),
|
|
325
|
+
};
|
|
326
|
+
}
|
|
317
327
|
}
|
|
318
328
|
}
|
|
319
329
|
|
|
@@ -489,17 +499,28 @@ function stripGsdPrefix(path: string): string {
|
|
|
489
499
|
return path.startsWith(".gsd/") ? path.slice(".gsd/".length) : path;
|
|
490
500
|
}
|
|
491
501
|
|
|
502
|
+
// Scope marker for assessments fabricated by the milestone-validation backfill
|
|
503
|
+
// (#1258). It keeps these placeholders distinguishable from genuine `run-uat`
|
|
504
|
+
// sign-offs so `readUatGateVerdict` never mistakes "never checked" for "passed".
|
|
505
|
+
const BACKFILL_ASSESSMENT_SCOPE = "backfill";
|
|
506
|
+
|
|
492
507
|
function persistSliceAssessmentBackfill(
|
|
493
508
|
assessmentRelPath: string,
|
|
494
509
|
mid: string,
|
|
495
510
|
sliceId: string,
|
|
496
511
|
content: string,
|
|
512
|
+
fabricated: boolean,
|
|
497
513
|
): void {
|
|
498
514
|
const artifactPath = stripGsdPrefix(assessmentRelPath);
|
|
499
515
|
const existingAssessment =
|
|
500
516
|
getAssessment(assessmentRelPath) ??
|
|
501
517
|
getAssessment(artifactPath);
|
|
502
|
-
|
|
518
|
+
// A newly fabricated placeholder is filed under a distinct scope; a pre-existing
|
|
519
|
+
// on-disk ASSESSMENT keeps its real scope (default `run-uat`) so genuine
|
|
520
|
+
// sign-offs are still honored.
|
|
521
|
+
const scope = fabricated
|
|
522
|
+
? BACKFILL_ASSESSMENT_SCOPE
|
|
523
|
+
: stringField(existingAssessment, "scope") ?? "run-uat";
|
|
503
524
|
const status = stringField(existingAssessment, "status") ??
|
|
504
525
|
extractVerdict(content)?.toLowerCase() ??
|
|
505
526
|
"unknown";
|
|
@@ -548,19 +569,25 @@ function backfillMissingAssessmentsFromSummaries(basePath: string, mid: string):
|
|
|
548
569
|
"---",
|
|
549
570
|
`sliceId: ${sliceId}`,
|
|
550
571
|
"verdict: PASS",
|
|
572
|
+
// Distinguishing marker (#1258): this ASSESSMENT was fabricated to satisfy
|
|
573
|
+
// the per-slice artifact requirement, NOT produced by a real UAT run. It
|
|
574
|
+
// must not be read as a genuine UAT sign-off.
|
|
575
|
+
"backfilled: true",
|
|
576
|
+
"verified: false",
|
|
551
577
|
`date: ${now}`,
|
|
552
578
|
"---",
|
|
553
579
|
"",
|
|
554
580
|
`# Assessment — ${sliceId}`,
|
|
555
581
|
"",
|
|
556
582
|
"Auto-created during milestone validation because this completed slice had a SUMMARY but no ASSESSMENT artifact.",
|
|
583
|
+
"This is a placeholder: no UAT was executed for this slice, so its verdict is not an independent sign-off.",
|
|
557
584
|
"No additional reassessment changes were detected in this backfill step.",
|
|
558
585
|
"",
|
|
559
586
|
].join("\n") : readFileSync(assessmentPath, "utf-8");
|
|
560
587
|
|
|
561
588
|
if (isDbAvailable()) {
|
|
562
589
|
try {
|
|
563
|
-
persistSliceAssessmentBackfill(assessmentRelPath, mid, sliceId, content);
|
|
590
|
+
persistSliceAssessmentBackfill(assessmentRelPath, mid, sliceId, content, didCreateAssessment);
|
|
564
591
|
} catch (err) {
|
|
565
592
|
logWarning("dispatch", `failed to backfill assessment DB rows for ${mid}/${sliceId}: ${(err as Error).message}`);
|
|
566
593
|
}
|
|
@@ -769,7 +796,12 @@ export const DISPATCH_RULES: DispatchRule[] = [
|
|
|
769
796
|
registeredTools,
|
|
770
797
|
sessionBaseUrl,
|
|
771
798
|
}) => {
|
|
772
|
-
const needsRunUat = await checkNeedsRunUat(
|
|
799
|
+
const needsRunUat = await checkNeedsRunUat(
|
|
800
|
+
basePath,
|
|
801
|
+
mid,
|
|
802
|
+
prefs,
|
|
803
|
+
await loadRoadmapCompletedSliceCandidates(basePath, mid),
|
|
804
|
+
);
|
|
773
805
|
if (!needsRunUat) return null;
|
|
774
806
|
const { sliceId, uatType } = needsRunUat;
|
|
775
807
|
|
|
@@ -1428,8 +1460,15 @@ export const DISPATCH_RULES: DispatchRule[] = [
|
|
|
1428
1460
|
const sTitle = state.activeSlice.title;
|
|
1429
1461
|
if (resolveSliceFile(basePath, mid, sid, "REACTIVE-BLOCKER")) return null;
|
|
1430
1462
|
const maxParallel = reactiveConfig?.max_parallel ?? 2;
|
|
1431
|
-
|
|
1432
|
-
|
|
1463
|
+
// `subagent_model` accepts the phase-bucket object form (#1229); honor the
|
|
1464
|
+
// full primary→fallbacks chain in the dispatch prompt (the reactive
|
|
1465
|
+
// subagent model is embedded there rather than set as the session model).
|
|
1466
|
+
const subagentModelConfig = normalizeModelFieldConfig(reactiveConfig?.subagent_model)
|
|
1467
|
+
?? resolveModelWithFallbacksForUnit("subagent");
|
|
1468
|
+
const subagentModel = subagentModelConfig?.primary;
|
|
1469
|
+
// Prefer the per-field `thinking` carried on `reactive_execution.subagent_model`'s
|
|
1470
|
+
// object form over the phase-bucket `subagent` level (#1269).
|
|
1471
|
+
const subagentThinking = subagentModelConfig?.thinking ?? resolveThinkingLevelForUnit("subagent");
|
|
1433
1472
|
// Default-on safety threshold: only activate reactive dispatch when at
|
|
1434
1473
|
// least N tasks are ready. Users who explicitly enabled reactive_execution
|
|
1435
1474
|
// keep the legacy threshold of 2 (matches the prior "any parallelism is
|
|
@@ -1518,7 +1557,13 @@ export const DISPATCH_RULES: DispatchRule[] = [
|
|
|
1518
1557
|
selected,
|
|
1519
1558
|
basePath,
|
|
1520
1559
|
subagentModel,
|
|
1521
|
-
{
|
|
1560
|
+
{
|
|
1561
|
+
sessionContextWindow,
|
|
1562
|
+
modelRegistry,
|
|
1563
|
+
sessionProvider,
|
|
1564
|
+
subagentThinking,
|
|
1565
|
+
subagentModelFallbacks: subagentModelConfig?.fallbacks,
|
|
1566
|
+
},
|
|
1522
1567
|
),
|
|
1523
1568
|
};
|
|
1524
1569
|
} catch (err) {
|
|
@@ -273,7 +273,7 @@ function buildModelPolicyBlockReasons(
|
|
|
273
273
|
}];
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
-
function isModelUnavailable(
|
|
276
|
+
export function isModelUnavailable(
|
|
277
277
|
basePath: string,
|
|
278
278
|
provider: string | undefined,
|
|
279
279
|
id: string | undefined,
|
|
@@ -282,6 +282,38 @@ function isModelUnavailable(
|
|
|
282
282
|
isModelTemporarilyUnavailable(basePath, provider, id);
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
/** Apply `auto_supervisor.model`, walking configured fallbacks on unavailability (#1229). */
|
|
286
|
+
export async function applySupervisorModelIfConfigured(
|
|
287
|
+
ctx: ExtensionContext,
|
|
288
|
+
pi: ExtensionAPI,
|
|
289
|
+
basePath: string,
|
|
290
|
+
): Promise<void> {
|
|
291
|
+
const modelConfig = resolveModelWithFallbacksForUnit("supervisor", basePath);
|
|
292
|
+
if (!modelConfig) return;
|
|
293
|
+
|
|
294
|
+
const availableModels = ctx.modelRegistry.getAvailable();
|
|
295
|
+
for (const candidate of [modelConfig.primary, ...modelConfig.fallbacks]) {
|
|
296
|
+
const match = resolveModelId(candidate, availableModels, ctx.model?.provider);
|
|
297
|
+
if (!match) continue;
|
|
298
|
+
if (isModelUnavailable(basePath, match.provider, match.id)) continue;
|
|
299
|
+
try {
|
|
300
|
+
if (await pi.setModel(match, { persist: false })) {
|
|
301
|
+
// Prefer the per-field `thinking` from `auto_supervisor.model`'s object
|
|
302
|
+
// form over the session level so a configured supervisor level actually
|
|
303
|
+
// applies (#1269).
|
|
304
|
+
applyThinkingLevelForModel(pi, modelConfig.thinking ?? pi.getThinkingLevel(), match, ctx);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
} catch (err) {
|
|
308
|
+
// Non-fatal — try the next fallback.
|
|
309
|
+
logWarning(
|
|
310
|
+
"dispatch",
|
|
311
|
+
`supervisor model set failed for ${candidate}: ${err instanceof Error ? err.message : String(err)}`,
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
285
317
|
function restoreToolBaseline(pi: ExtensionAPI): void {
|
|
286
318
|
const key = pi as unknown as object;
|
|
287
319
|
const baseline = TOOL_BASELINE.get(key);
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
relMilestoneFile,
|
|
31
31
|
relSliceFile,
|
|
32
32
|
relTaskFile,
|
|
33
|
+
normalizeRealPath,
|
|
33
34
|
} from "./paths.js";
|
|
34
35
|
import { invalidateAllCaches } from "./cache.js";
|
|
35
36
|
import { rebuildState } from "./doctor.js";
|
|
@@ -960,7 +961,7 @@ function describeArtifactVerificationFailure(
|
|
|
960
961
|
if (!artifactPath) {
|
|
961
962
|
return `Artifact verification failed: ${unitType} "${unitId}" has no resolvable artifact path.`;
|
|
962
963
|
}
|
|
963
|
-
const relPath = relative(basePath, artifactPath);
|
|
964
|
+
const relPath = relative(normalizeRealPath(basePath), artifactPath);
|
|
964
965
|
if (!existsSync(artifactPath)) {
|
|
965
966
|
const completionToolHint = unitType === "execute-task" && !hasTaskCompletionToolCall(agentEndMessages)
|
|
966
967
|
? " No completion tool call detected (`gsd_task_complete`/alias)."
|
|
@@ -2398,7 +2399,8 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
2398
2399
|
const verdict = gateBlock.verdict ? ` verdict=${gateBlock.verdict};` : "";
|
|
2399
2400
|
const artifact = gateBlock.artifact ? ` artifact=${gateBlock.artifact};` : "";
|
|
2400
2401
|
const message =
|
|
2401
|
-
`Post-unit gate "${gateBlock.hookName}" blocked ${gateBlock.triggerUnitType} ${gateBlock.triggerUnitId}
|
|
2402
|
+
`Post-unit gate "${gateBlock.hookName}" blocked ${gateBlock.triggerUnitType} ${gateBlock.triggerUnitId} ` +
|
|
2403
|
+
`(detected on completion of ${s.currentUnit.type} ${s.currentUnit.id}):` +
|
|
2402
2404
|
`${verdict}${artifact} ${gateBlock.reason}. Run /gsd status to inspect, then /gsd auto after recovery.`;
|
|
2403
2405
|
ctx.ui.notify(message, "warning");
|
|
2404
2406
|
await pauseAuto(ctx, pi);
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { loadFile, parseContinue, parseSummary, loadActiveOverrides, formatOverridesSection, parseTaskPlanFile } from "./files.js";
|
|
13
13
|
import type { Override } from "./files.js";
|
|
14
|
-
import {
|
|
14
|
+
import { extractVerdict } from "./verdict-parser.js";
|
|
15
15
|
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
16
16
|
import {
|
|
17
17
|
resolveMilestoneFile, resolveSliceFile, resolveSlicePath,
|
|
@@ -60,11 +60,8 @@ import { debugLog } from "./debug-logger.js";
|
|
|
60
60
|
import { buildSkillActivationBlock, buildSkillDiscoveryVars } from "./skill-activation.js";
|
|
61
61
|
import { findMilestoneIds } from "./milestone-ids.js";
|
|
62
62
|
import { buildRunUatPresentationForType, RUN_UAT_TOOL_PRESENTATION_PLAN_ID } from "./tool-presentation-plan.js";
|
|
63
|
-
import {
|
|
64
|
-
|
|
65
|
-
shouldDispatchUatForContent,
|
|
66
|
-
type UatType,
|
|
67
|
-
} from "./uat-policy.js";
|
|
63
|
+
import { classifyUatContentForRun } from "./uat-policy.js";
|
|
64
|
+
import { checkNeedsRunUat as resolveNeedsRunUat, type UatDispatchCandidate } from "./uat-dispatch.js";
|
|
68
65
|
import { buildWebAppUatGuidanceBlock } from "./web-app-uat.js";
|
|
69
66
|
|
|
70
67
|
export { buildSkillActivationBlock, buildSkillDiscoveryVars };
|
|
@@ -1514,116 +1511,43 @@ export async function checkNeedsReassessment(
|
|
|
1514
1511
|
return { sliceId: lastDone };
|
|
1515
1512
|
}
|
|
1516
1513
|
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
* `buildRunUatPrompt` does: classify the UAT body with the slice's SUMMARY as
|
|
1530
|
-
* supplemental context, so a `browser-executable` UAT whose slice references a
|
|
1531
|
-
* self-contained harness (`npm run test:uat`, `search-uat.mjs`, `npx
|
|
1532
|
-
* playwright test`) is promoted to `runtime-executable` for the gate too.
|
|
1533
|
-
*
|
|
1534
|
-
* Without this, `checkNeedsRunUat` returns `browser-executable` while the
|
|
1535
|
-
* prompt instructs runtime-only execution, causing the dispatch gate to
|
|
1536
|
-
* require browser tools / warm up the browser daemon (or stop dispatch when
|
|
1537
|
-
* browser MCP is unavailable) for UAT runs that never touch the browser. See
|
|
1538
|
-
* cursor[bot] review on PR #696 for the M007/S01 regression.
|
|
1539
|
-
*/
|
|
1540
|
-
async function resolveRunUatEffectiveType(
|
|
1514
|
+
|
|
1515
|
+
export function getRoadmapCompletedSliceCandidates(
|
|
1516
|
+
roadmapContent: string,
|
|
1517
|
+
): UatDispatchCandidate[] {
|
|
1518
|
+
return parseRoadmap(roadmapContent)
|
|
1519
|
+
.slices.filter((slice) => slice.done)
|
|
1520
|
+
.map((slice) => ({ sliceId: slice.id }))
|
|
1521
|
+
.reverse();
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
|
|
1525
|
+
export async function loadRoadmapCompletedSliceCandidates(
|
|
1541
1526
|
base: string,
|
|
1542
1527
|
mid: string,
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
):
|
|
1546
|
-
|
|
1547
|
-
try {
|
|
1548
|
-
const summaryPath = resolveSliceFile(base, mid, sliceId, "SUMMARY");
|
|
1549
|
-
if (summaryPath) {
|
|
1550
|
-
summaryContent = (await loadFile(summaryPath)) ?? "";
|
|
1551
|
-
}
|
|
1552
|
-
} catch (err) {
|
|
1553
|
-
logWarning(
|
|
1554
|
-
"prompt",
|
|
1555
|
-
`resolveRunUatEffectiveType SUMMARY load failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1556
|
-
);
|
|
1557
|
-
}
|
|
1558
|
-
return classifyUatContentForRun(uatContent, summaryContent).effectiveType;
|
|
1528
|
+
): Promise<UatDispatchCandidate[]> {
|
|
1529
|
+
const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
|
|
1530
|
+
const roadmapContent = roadmapPath ? await loadFile(roadmapPath) : null;
|
|
1531
|
+
return roadmapContent ? getRoadmapCompletedSliceCandidates(roadmapContent) : [];
|
|
1559
1532
|
}
|
|
1560
1533
|
|
|
1534
|
+
/**
|
|
1535
|
+
* Back-compat wrapper for callers that import UAT dispatch checks from the
|
|
1536
|
+
* prompt module. UAT dispatch architecture lives in `uat-dispatch.ts`; the
|
|
1537
|
+
* `state` parameter is intentionally ignored because DB/roadmap artifacts are
|
|
1538
|
+
* the source of truth for completed-slice discovery.
|
|
1539
|
+
*/
|
|
1561
1540
|
export async function checkNeedsRunUat(
|
|
1562
1541
|
base: string, mid: string, state: GSDState, prefs: GSDPreferences | undefined,
|
|
1563
|
-
): Promise<
|
|
1564
|
-
|
|
1565
|
-
try {
|
|
1566
|
-
const { isDbAvailable, getMilestoneSlices } = await import("./gsd-db.js");
|
|
1567
|
-
if (isDbAvailable()) {
|
|
1568
|
-
const slices = getMilestoneSlices(mid);
|
|
1569
|
-
if (slices.length > 0) {
|
|
1570
|
-
const completedSlices = slices.filter(s => s.status === "complete");
|
|
1571
|
-
if (completedSlices.length === 0) return null;
|
|
1572
|
-
for (const completed of [...completedSlices].reverse()) {
|
|
1573
|
-
const sid = completed.id;
|
|
1574
|
-
const uatFile = resolveSliceFile(base, mid, sid, "UAT");
|
|
1575
|
-
if (!uatFile) continue;
|
|
1576
|
-
const uatContent = await loadFile(uatFile);
|
|
1577
|
-
if (!uatContent) continue;
|
|
1578
|
-
// If the UAT file already contains a verdict, UAT has been run — skip
|
|
1579
|
-
if (hasVerdict(uatContent)) continue;
|
|
1580
|
-
// Also check the ASSESSMENT file — the run-uat prompt writes the verdict
|
|
1581
|
-
// there (via gsd_uat_result_save), not into the
|
|
1582
|
-
// UAT spec file. Without this check the unit re-dispatches indefinitely.
|
|
1583
|
-
const assessmentFile = resolveSliceFile(base, mid, sid, "ASSESSMENT");
|
|
1584
|
-
if (assessmentFile) {
|
|
1585
|
-
const assessmentContent = await loadFile(assessmentFile);
|
|
1586
|
-
if (assessmentContent && hasVerdict(assessmentContent)) continue;
|
|
1587
|
-
}
|
|
1588
|
-
if (!shouldDispatchUatForContent(uatContent, prefs)) continue;
|
|
1589
|
-
const uatType = await resolveRunUatEffectiveType(base, mid, sid, uatContent);
|
|
1590
|
-
return { sliceId: sid, uatType };
|
|
1591
|
-
}
|
|
1592
|
-
return null;
|
|
1593
|
-
}
|
|
1594
|
-
}
|
|
1595
|
-
} catch (err) {
|
|
1596
|
-
logWarning("prompt", `checkNeedsRunUat DB lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1597
|
-
}
|
|
1542
|
+
): Promise<Awaited<ReturnType<typeof resolveNeedsRunUat>>> {
|
|
1543
|
+
void state;
|
|
1598
1544
|
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
const completedFileSlices = parsed.slices.filter(s => s.done);
|
|
1606
|
-
if (completedFileSlices.length === 0) return null;
|
|
1607
|
-
for (const completed of [...completedFileSlices].reverse()) {
|
|
1608
|
-
const uatSid = completed.id;
|
|
1609
|
-
const uatFileFb = resolveSliceFile(base, mid, uatSid, "UAT");
|
|
1610
|
-
if (!uatFileFb) continue;
|
|
1611
|
-
const uatContentFb = await loadFile(uatFileFb);
|
|
1612
|
-
if (!uatContentFb) continue;
|
|
1613
|
-
// If the UAT file already contains a verdict, UAT has been run — skip
|
|
1614
|
-
if (hasVerdict(uatContentFb)) continue;
|
|
1615
|
-
// Also check the ASSESSMENT file for the file-based fallback path (same
|
|
1616
|
-
// reason as the DB path above — verdict lives in ASSESSMENT, not UAT).
|
|
1617
|
-
const assessmentFileFb = resolveSliceFile(base, mid, uatSid, "ASSESSMENT");
|
|
1618
|
-
if (assessmentFileFb) {
|
|
1619
|
-
const assessmentContentFb = await loadFile(assessmentFileFb);
|
|
1620
|
-
if (assessmentContentFb && hasVerdict(assessmentContentFb)) continue;
|
|
1621
|
-
}
|
|
1622
|
-
if (!shouldDispatchUatForContent(uatContentFb, prefs)) continue;
|
|
1623
|
-
const uatType = await resolveRunUatEffectiveType(base, mid, uatSid, uatContentFb);
|
|
1624
|
-
return { sliceId: uatSid, uatType };
|
|
1625
|
-
}
|
|
1626
|
-
return null;
|
|
1545
|
+
return resolveNeedsRunUat(
|
|
1546
|
+
base,
|
|
1547
|
+
mid,
|
|
1548
|
+
prefs,
|
|
1549
|
+
await loadRoadmapCompletedSliceCandidates(base, mid),
|
|
1550
|
+
);
|
|
1627
1551
|
}
|
|
1628
1552
|
|
|
1629
1553
|
// ─── Prompt Builders ──────────────────────────────────────────────────────
|
|
@@ -3909,9 +3833,15 @@ export async function buildReassessRoadmapPrompt(
|
|
|
3909
3833
|
* that instructs the coordinator how to dispatch a `subagent` call. Either or
|
|
3910
3834
|
* both may be absent (ADR-026 / #508).
|
|
3911
3835
|
*/
|
|
3912
|
-
function subagentCallSuffix(model?: string, thinking?: string): string {
|
|
3836
|
+
function subagentCallSuffix(model?: string, thinking?: string, fallbacks?: string[]): string {
|
|
3913
3837
|
const parts: string[] = [];
|
|
3914
|
-
if (model)
|
|
3838
|
+
if (model) {
|
|
3839
|
+
let modelHint = `model: "${model}"`;
|
|
3840
|
+
if (fallbacks && fallbacks.length > 0) {
|
|
3841
|
+
modelHint += ` (fallbacks, in order: ${fallbacks.map((f) => `"${f}"`).join(", ")})`;
|
|
3842
|
+
}
|
|
3843
|
+
parts.push(modelHint);
|
|
3844
|
+
}
|
|
3915
3845
|
if (thinking) parts.push(`thinking: "${thinking}"`);
|
|
3916
3846
|
return parts.length > 0 ? ` with ${parts.join(" and ")}` : "";
|
|
3917
3847
|
}
|
|
@@ -3922,7 +3852,13 @@ export async function buildReactiveExecutePrompt(
|
|
|
3922
3852
|
subagentModel?: string,
|
|
3923
3853
|
// Reasoning effort travels inside opts here (not as a positional param) so
|
|
3924
3854
|
// existing positional `opts` callers don't shift (#508).
|
|
3925
|
-
opts?: {
|
|
3855
|
+
opts?: {
|
|
3856
|
+
sessionContextWindow?: number;
|
|
3857
|
+
modelRegistry?: MinimalModelRegistry;
|
|
3858
|
+
sessionProvider?: string;
|
|
3859
|
+
subagentThinking?: string;
|
|
3860
|
+
subagentModelFallbacks?: string[];
|
|
3861
|
+
},
|
|
3926
3862
|
): Promise<string> {
|
|
3927
3863
|
const { loadSliceTaskIO, deriveTaskGraph, graphMetrics } = await import("./reactive-graph.js");
|
|
3928
3864
|
|
|
@@ -4019,7 +3955,11 @@ export async function buildReactiveExecutePrompt(
|
|
|
4019
3955
|
`When done, say: "Task ${tid} complete."`,
|
|
4020
3956
|
].join("\n");
|
|
4021
3957
|
|
|
4022
|
-
const modelSuffix = subagentCallSuffix(
|
|
3958
|
+
const modelSuffix = subagentCallSuffix(
|
|
3959
|
+
subagentModel,
|
|
3960
|
+
opts?.subagentThinking,
|
|
3961
|
+
opts?.subagentModelFallbacks,
|
|
3962
|
+
);
|
|
4023
3963
|
subagentSections.push([
|
|
4024
3964
|
`### ${tid}: ${tTitle}`,
|
|
4025
3965
|
"",
|