@opengsd/gsd-pi 1.2.0-dev.5457a158 → 1.2.0-dev.84c56d87
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 +209 -88
- package/dist/resources/extensions/browser-tools/engine/selection.js +73 -5
- package/dist/resources/extensions/browser-tools/index.js +69 -12
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +3 -2
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +19 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +27 -9
- package/dist/resources/extensions/gsd/browser-evidence.js +8 -2
- package/dist/resources/extensions/gsd/mcp-filter.js +2 -19
- package/dist/resources/extensions/gsd/uat-policy.js +2 -1
- package/dist/resources/extensions/gsd/unit-registry.js +7 -20
- package/dist/resources/extensions/gsd/web-app-uat.js +45 -8
- package/dist/resources/extensions/search-the-web/native-search.js +5 -3
- package/dist/resources/extensions/shared/browser-contract.js +59 -0
- package/dist/resources/extensions/shared/gsd-browser-cli.js +72 -4
- package/dist/resources/skills/create-skill/references/executable-code.md +1 -1
- package/dist/resources/skills/create-skill/workflows/add-reference.md +8 -3
- package/dist/resources/skills/create-skill/workflows/add-script.md +4 -2
- package/dist/resources/skills/create-skill/workflows/add-template.md +3 -1
- package/dist/resources/skills/create-skill/workflows/add-workflow.md +8 -3
- package/dist/resources/skills/create-skill/workflows/upgrade-to-router.md +10 -5
- package/dist/resources/skills/create-skill/workflows/verify-skill.md +9 -4
- package/dist/resources/skills/spike-wrap-up/SKILL.md +9 -9
- 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 +9 -9
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
- 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/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/dist/web/standalone/node_modules/postcss/lib/container.js +18 -26
- package/dist/web/standalone/node_modules/postcss/lib/css-syntax-error.js +14 -47
- package/dist/web/standalone/node_modules/postcss/lib/declaration.js +4 -4
- package/dist/web/standalone/node_modules/postcss/lib/fromJSON.js +3 -3
- package/dist/web/standalone/node_modules/postcss/lib/input.js +29 -54
- package/dist/web/standalone/node_modules/postcss/lib/lazy-result.js +37 -47
- package/dist/web/standalone/node_modules/postcss/lib/map-generator.js +9 -26
- package/dist/web/standalone/node_modules/postcss/lib/no-work-result.js +55 -57
- package/dist/web/standalone/node_modules/postcss/lib/node.js +31 -99
- package/dist/web/standalone/node_modules/postcss/lib/parse.js +1 -1
- package/dist/web/standalone/node_modules/postcss/lib/parser.js +9 -10
- package/dist/web/standalone/node_modules/postcss/lib/postcss.js +12 -12
- package/dist/web/standalone/node_modules/postcss/lib/previous-map.js +11 -30
- package/dist/web/standalone/node_modules/postcss/lib/processor.js +7 -7
- package/dist/web/standalone/node_modules/postcss/lib/result.js +5 -5
- package/dist/web/standalone/node_modules/postcss/lib/rule.js +6 -6
- package/dist/web/standalone/node_modules/postcss/lib/stringifier.js +28 -69
- package/dist/web/standalone/node_modules/postcss/lib/tokenize.js +2 -6
- package/dist/web/standalone/node_modules/postcss/package.json +48 -48
- package/dist/web/standalone/package.json +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +66 -178
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +116 -204
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +9 -0
- package/packages/pi-tui/dist/tui.js.map +1 -1
- 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 +265 -98
- package/src/resources/extensions/browser-tools/engine/selection.ts +90 -4
- package/src/resources/extensions/browser-tools/index.ts +71 -13
- package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +83 -13
- package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +136 -0
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +3 -2
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +24 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +48 -4
- package/src/resources/extensions/gsd/browser-evidence.ts +18 -2
- package/src/resources/extensions/gsd/mcp-filter.ts +2 -23
- package/src/resources/extensions/gsd/tests/browser-automation-contract-fixture.ts +39 -0
- package/src/resources/extensions/gsd/tests/browser-contract.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/dispatch-run-uat-browser-tools.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +35 -1
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +7 -11
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +20 -58
- package/src/resources/extensions/gsd/tests/integration/gsd-integration-fixture.ts +80 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/uat-policy.test.ts +24 -29
- package/src/resources/extensions/gsd/tests/web-app-uat.test.ts +44 -1
- package/src/resources/extensions/gsd/uat-policy.ts +2 -1
- package/src/resources/extensions/gsd/unit-registry.ts +7 -20
- package/src/resources/extensions/gsd/web-app-uat.ts +51 -8
- package/src/resources/extensions/search-the-web/native-search.ts +5 -3
- package/src/resources/extensions/shared/browser-contract.ts +66 -0
- package/src/resources/extensions/shared/gsd-browser-cli.ts +88 -4
- package/src/resources/skills/create-skill/references/executable-code.md +1 -1
- package/src/resources/skills/create-skill/workflows/add-reference.md +8 -3
- package/src/resources/skills/create-skill/workflows/add-script.md +4 -2
- package/src/resources/skills/create-skill/workflows/add-template.md +3 -1
- package/src/resources/skills/create-skill/workflows/add-workflow.md +8 -3
- package/src/resources/skills/create-skill/workflows/upgrade-to-router.md +10 -5
- package/src/resources/skills/create-skill/workflows/verify-skill.md +9 -4
- package/src/resources/skills/spike-wrap-up/SKILL.md +9 -9
- /package/dist/web/standalone/.next/static/{2p9Rv9pQflAxCBbGVI2vb → AOpDeK_gJHU8OZjRo31gQ}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{2p9Rv9pQflAxCBbGVI2vb → AOpDeK_gJHU8OZjRo31gQ}/_ssgManifest.js +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** browser-tools — Pi Browser Automation Contract adapter. */
|
|
2
2
|
import { importExtensionModule } from "@gsd/pi-coding-agent";
|
|
3
3
|
import { closeManagedGsdBrowser, registerManagedGsdBrowserTools, warmUpManagedGsdBrowser } from "./engine/managed-gsd-browser.js";
|
|
4
|
-
import {
|
|
4
|
+
import { commitBrowserEngineResolution, resolveAmbientBrowserEngineResolution } from "./engine/selection.js";
|
|
5
5
|
import { setArtifactRootForCwd } from "./state.js";
|
|
6
6
|
import { detectWebApp } from "./web-app-detect.js";
|
|
7
7
|
let legacyRegistrationPromise = null;
|
|
@@ -130,12 +130,56 @@ function withBrowserArtifactCwdScope(pi) {
|
|
|
130
130
|
},
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
/** Daemon-connect budget when the probe-resolved managed engine is verified at session start. */
|
|
134
|
+
const PROBE_WARMUP_TIMEOUT_MS = 10_000;
|
|
135
|
+
async function registerBrowserTools(pi, ctx) {
|
|
136
|
+
const projectRoot = ctx.cwd || process.cwd();
|
|
137
|
+
const resolution = resolveAmbientBrowserEngineResolution(projectRoot);
|
|
138
|
+
let engine = resolution.engine;
|
|
135
139
|
if (engine === "off")
|
|
136
140
|
return;
|
|
141
|
+
// A probe-resolved managed engine is only a prediction that gsd-browser
|
|
142
|
+
// works — prove it by connecting the daemon before committing the session's
|
|
143
|
+
// tool registrations to it. Connect failure falls back to legacy Playwright
|
|
144
|
+
// (the failure mode that made ADR-024 freeze the old default) and commits
|
|
145
|
+
// the outcome so ambient readers see the engine actually in use. When eager
|
|
146
|
+
// warm-up is disabled the daemon-connect proof cannot run, so the probe
|
|
147
|
+
// default treats the managed engine as unprovable and falls back to legacy
|
|
148
|
+
// rather than registering it unverified. An explicit
|
|
149
|
+
// GSD_BROWSER_ENGINE=gsd-browser override skips the gate and is honored
|
|
150
|
+
// verbatim, matching prior behavior.
|
|
151
|
+
if (engine === "gsd-browser" && resolution.source === "probe" && !registeredEngine) {
|
|
152
|
+
if (isWarmUpDisabled()) {
|
|
153
|
+
engine = commitLegacyFallback(projectRoot, "warm-up disabled; managed engine unverifiable; using legacy Playwright");
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const warmUp = await warmUpManagedGsdBrowser(ctx, AbortSignal.timeout(PROBE_WARMUP_TIMEOUT_MS));
|
|
157
|
+
if (!warmUp.ok) {
|
|
158
|
+
engine = commitLegacyFallback(projectRoot, `gsd-browser daemon connect failed (${warmUp.error}); using legacy Playwright`);
|
|
159
|
+
if (ctx.hasUI) {
|
|
160
|
+
ctx.ui.notify(`gsd-browser engine unavailable (${warmUp.error}); using Playwright browser tools for this session.`, "warning");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else if (warmUp.coverageWarning && ctx.hasUI) {
|
|
164
|
+
ctx.ui.notify(warmUp.coverageWarning, "warning");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Browser tool registrations are process-global and cannot be swapped once
|
|
169
|
+
// live. When an earlier session in this process already registered an engine
|
|
170
|
+
// and this project resolved a different one (per-project probe resolution can
|
|
171
|
+
// diverge across projects in a multi-session process), adopt the registered
|
|
172
|
+
// engine rather than throwing — a throw surfaces as "browser-tools failed to
|
|
173
|
+
// load" and leaves this session with no browser tools at all. Commit the
|
|
174
|
+
// adoption so ambient readers (UAT guidance, warm-up) describe the engine
|
|
175
|
+
// actually in use.
|
|
137
176
|
if (registeredEngine && registeredEngine !== engine) {
|
|
138
|
-
|
|
177
|
+
engine = registeredEngine;
|
|
178
|
+
commitBrowserEngineResolution(projectRoot, {
|
|
179
|
+
engine,
|
|
180
|
+
source: "probe",
|
|
181
|
+
reason: `browser tools already registered with ${engine} earlier in this process; adopting it`,
|
|
182
|
+
});
|
|
139
183
|
}
|
|
140
184
|
let registration;
|
|
141
185
|
if (engine === "legacy") {
|
|
@@ -165,27 +209,40 @@ async function registerBrowserTools(pi) {
|
|
|
165
209
|
throw error;
|
|
166
210
|
}
|
|
167
211
|
}
|
|
212
|
+
function commitLegacyFallback(projectRoot, reason) {
|
|
213
|
+
commitBrowserEngineResolution(projectRoot, { engine: "legacy", source: "probe", reason });
|
|
214
|
+
return "legacy";
|
|
215
|
+
}
|
|
168
216
|
function isWarmUpDisabled() {
|
|
169
217
|
const value = process.env.GSD_BROWSER_WARMUP?.trim().toLowerCase();
|
|
170
218
|
return value === "0" || value === "false" || value === "off";
|
|
171
219
|
}
|
|
172
220
|
/**
|
|
173
|
-
* Auto-initialize the managed gsd-browser engine
|
|
174
|
-
*
|
|
175
|
-
*
|
|
221
|
+
* Auto-initialize the managed gsd-browser engine when it was selected via the
|
|
222
|
+
* explicit GSD_BROWSER_ENGINE override, which registers without the
|
|
223
|
+
* daemon-connect gate. Best-effort and non-blocking: warm-up runs in the
|
|
224
|
+
* background and only surfaces a warning if it fails. Probe-resolved sessions
|
|
225
|
+
* already connected (or fell back) during registration, so they are excluded
|
|
226
|
+
* to avoid re-warming and double-notifying.
|
|
176
227
|
*/
|
|
177
228
|
function maybeWarmUpManagedEngine(pi, ctx) {
|
|
178
229
|
if (isWarmUpDisabled())
|
|
179
230
|
return;
|
|
180
|
-
if (resolveBrowserEngineMode() !== "gsd-browser")
|
|
181
|
-
return;
|
|
182
231
|
const projectRoot = ctx.cwd || process.cwd();
|
|
232
|
+
const resolution = resolveAmbientBrowserEngineResolution(projectRoot);
|
|
233
|
+
if (resolution.engine !== "gsd-browser" || resolution.source !== "env")
|
|
234
|
+
return;
|
|
183
235
|
if (!detectWebApp(projectRoot))
|
|
184
236
|
return;
|
|
185
237
|
void warmUpManagedGsdBrowser(ctx).then((result) => {
|
|
186
|
-
if (!
|
|
238
|
+
if (!ctx.hasUI)
|
|
239
|
+
return;
|
|
240
|
+
if (!result.ok) {
|
|
187
241
|
ctx.ui.notify(`gsd-browser auto-init failed: ${result.error}. Browser UAT tools will retry on first use; run /gsd doctor if this persists.`, "warning");
|
|
188
242
|
}
|
|
243
|
+
else if (result.coverageWarning) {
|
|
244
|
+
ctx.ui.notify(result.coverageWarning, "warning");
|
|
245
|
+
}
|
|
189
246
|
});
|
|
190
247
|
}
|
|
191
248
|
async function closeActiveBrowserEngines() {
|
|
@@ -198,14 +255,14 @@ async function closeActiveBrowserEngines() {
|
|
|
198
255
|
export default function (pi) {
|
|
199
256
|
pi.on("session_start", async (_event, ctx) => {
|
|
200
257
|
if (ctx.hasUI) {
|
|
201
|
-
void registerBrowserTools(pi)
|
|
258
|
+
void registerBrowserTools(pi, ctx)
|
|
202
259
|
.then(() => maybeWarmUpManagedEngine(pi, ctx))
|
|
203
260
|
.catch((error) => {
|
|
204
261
|
ctx.ui.notify(`browser-tools failed to load: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
205
262
|
});
|
|
206
263
|
return;
|
|
207
264
|
}
|
|
208
|
-
await registerBrowserTools(pi);
|
|
265
|
+
await registerBrowserTools(pi, ctx);
|
|
209
266
|
maybeWarmUpManagedEngine(pi, ctx);
|
|
210
267
|
});
|
|
211
268
|
pi.on("session_shutdown", async () => {
|
|
@@ -25,6 +25,7 @@ import { markInteractiveElicitationStart, markInteractiveElicitationEnd, } from
|
|
|
25
25
|
import { discoverBrowserMcpServerName, discoverMcpServers, discoverMcpServerNames, discoverUserMcpServerNames, discoverWorkflowMcpServerName, computeMcpDisallowedTools, } from "../gsd/mcp-filter.js";
|
|
26
26
|
import { RUN_UAT_CLAUDE_NATIVE_TOOL_NAMES, RUN_UAT_FORBIDDEN_TOOL_NAMES, RUN_UAT_WORKFLOW_TOOL_NAMES, resolveToolPresentationPlan } from "../gsd/tool-presentation-plan.js";
|
|
27
27
|
import { getToolSurfaceReadinessError } from "../gsd/tool-surface-readiness.js";
|
|
28
|
+
import { hasBrowserContractPrefix } from "../shared/browser-contract.js";
|
|
28
29
|
import { showInterviewRound } from "../shared/tui.js";
|
|
29
30
|
export { buildFinalAssistantContent, extractToolResultsFromSdkUserMessage, handleClaudeCodePartialStreamEvent, mergePendingToolCalls, } from "./turn-assembler.js";
|
|
30
31
|
export function serverToolUseToToolCallLike(block) {
|
|
@@ -1290,7 +1291,7 @@ function browserMcpServerNameFromAllowedTools(allowedTools) {
|
|
|
1290
1291
|
const parsed = parseAllowedMcpToolName(toolName);
|
|
1291
1292
|
if (!parsed)
|
|
1292
1293
|
continue;
|
|
1293
|
-
if (parsed.server === "gsd-browser" || parsed.tool
|
|
1294
|
+
if (parsed.server === "gsd-browser" || hasBrowserContractPrefix(parsed.tool)) {
|
|
1294
1295
|
return parsed.server;
|
|
1295
1296
|
}
|
|
1296
1297
|
}
|
|
@@ -1304,7 +1305,7 @@ function workflowMcpServerNameFromAllowedTools(allowedTools) {
|
|
|
1304
1305
|
if (typeof toolName !== "string")
|
|
1305
1306
|
continue;
|
|
1306
1307
|
const parsed = parseAllowedMcpToolName(toolName);
|
|
1307
|
-
if (!parsed || parsed.server === browserServerName || parsed.tool
|
|
1308
|
+
if (!parsed || parsed.server === browserServerName || hasBrowserContractPrefix(parsed.tool))
|
|
1308
1309
|
continue;
|
|
1309
1310
|
return parsed.server;
|
|
1310
1311
|
}
|
|
@@ -13,6 +13,7 @@ import { registerHooks } from "./register-hooks.js";
|
|
|
13
13
|
import { registerShortcuts } from "./register-shortcuts.js";
|
|
14
14
|
import { writeCrashLog } from "./crash-log.js";
|
|
15
15
|
import { logWarning } from "../workflow-logger.js";
|
|
16
|
+
import { UNIT_TOOL_CONTRACTS } from "../unit-tool-contracts.js";
|
|
16
17
|
// Static import so cmux event listeners are registered synchronously during
|
|
17
18
|
// extension bootstrap. Prior implementation used `void import().then()` which
|
|
18
19
|
// queued listener registration as a microtask — any CMUX_CHANNELS emit fired
|
|
@@ -30,6 +31,9 @@ const EPIPE_STORM_THRESHOLD = 100;
|
|
|
30
31
|
const EPIPE_STORM_WINDOW_MS = 10_000;
|
|
31
32
|
let epipeCount = 0;
|
|
32
33
|
let epipeWindowStart = 0;
|
|
34
|
+
export const CRITICAL_GSD_WORKFLOW_TOOL_NAMES = [...new Set(Object.values(UNIT_TOOL_CONTRACTS)
|
|
35
|
+
.flatMap((contract) => contract.requiredWorkflowTools)
|
|
36
|
+
.filter((toolName) => toolName.startsWith("gsd_")))].sort();
|
|
33
37
|
/** Write to stderr without ever re-throwing — stderr can EPIPE too, which would
|
|
34
38
|
* re-enter this handler and re-loop. */
|
|
35
39
|
function safeStderr(msg) {
|
|
@@ -121,6 +125,20 @@ export function installEpipeGuard() {
|
|
|
121
125
|
process.on("unhandledRejection", _gsdRejectionGuard);
|
|
122
126
|
}
|
|
123
127
|
}
|
|
128
|
+
function assertCriticalGsdWorkflowToolsRegistered(pi) {
|
|
129
|
+
if (typeof pi.getAllTools !== "function")
|
|
130
|
+
return;
|
|
131
|
+
const registered = new Set(pi.getAllTools().map((tool) => tool.name));
|
|
132
|
+
const missing = CRITICAL_GSD_WORKFLOW_TOOL_NAMES.filter((toolName) => !registered.has(toolName));
|
|
133
|
+
if (missing.length === 0)
|
|
134
|
+
return;
|
|
135
|
+
const message = [
|
|
136
|
+
`Critical GSD workflow tool registration failed; missing required tool(s): ${missing.join(", ")}.`,
|
|
137
|
+
"Check earlier bootstrap warnings for the registration slot that failed.",
|
|
138
|
+
].join(" ");
|
|
139
|
+
logWarning("bootstrap", message);
|
|
140
|
+
throw new Error(message);
|
|
141
|
+
}
|
|
124
142
|
export function registerGsdExtension(pi) {
|
|
125
143
|
// Note: registerGSDCommand is called by index.ts before this function,
|
|
126
144
|
// so we intentionally skip it here to avoid double-registration.
|
|
@@ -186,4 +204,5 @@ export function registerGsdExtension(pi) {
|
|
|
186
204
|
logWarning("bootstrap", `Failed to register ${name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
187
205
|
}
|
|
188
206
|
}
|
|
207
|
+
assertCriticalGsdWorkflowToolsRegistered(pi);
|
|
189
208
|
}
|
|
@@ -32,12 +32,14 @@ import { applyUnitSkillVisibility, unitHasSkillManifest } from "../skill-scope.j
|
|
|
32
32
|
import { getGuidedUnitContext } from "../guided-unit-context.js";
|
|
33
33
|
import { registerPlanMilestoneSchemaRecovery } from "./plan-milestone-schema-recovery.js";
|
|
34
34
|
import { AUTO_UNIT_SCOPED_TOOLS, RUN_UAT_BROWSER_TOOL_NAMES, canonicalWorkflowToolName, isWorkflowAliasTool } from "../auto-unit-tool-scope.js";
|
|
35
|
+
import { hasBrowserContractPrefix } from "../../shared/browser-contract.js";
|
|
35
36
|
import { filterToolsForProvider } from "../model-router.js";
|
|
36
37
|
import { mcpToolMatchesBaseName } from "../mcp-tool-name.js";
|
|
37
38
|
import { RUN_UAT_READ_ONLY_TOOL_NAMES, RUN_UAT_WORKFLOW_TOOL_NAMES } from "../tool-presentation-plan.js";
|
|
38
39
|
import { supportsSourceObservationsForUnit } from "../source-observations.js";
|
|
39
40
|
import { clearPendingAutoStart } from "../pending-auto-start.js";
|
|
40
41
|
import { resolveWorkflowToolBasePath } from "./dynamic-tools.js";
|
|
42
|
+
import { getRequiredWorkflowToolsForUnit } from "../unit-tool-contracts.js";
|
|
41
43
|
let approvalQuestionAbortInFlight = false;
|
|
42
44
|
async function loadWelcomeScreenModule() {
|
|
43
45
|
const candidates = [];
|
|
@@ -140,7 +142,7 @@ function withPreservedShimTools(toolNames) {
|
|
|
140
142
|
}
|
|
141
143
|
/** True for the browser automation tools (browser_navigate, browser_click, ...). */
|
|
142
144
|
function isBrowserTool(toolName) {
|
|
143
|
-
return canonicalToolName(toolName)
|
|
145
|
+
return hasBrowserContractPrefix(canonicalToolName(toolName));
|
|
144
146
|
}
|
|
145
147
|
/**
|
|
146
148
|
* True when any message in the request is driven by a GSD workflow command
|
|
@@ -202,7 +204,7 @@ export function buildMinimalGsdToolSet(activeToolNames) {
|
|
|
202
204
|
const minimal = resolveScopedToolNames(activeToolNames, MINIMAL_GSD_TOOL_NAMES);
|
|
203
205
|
return withPreservedShimTools([...new Set([...preserved, ...minimal])]);
|
|
204
206
|
}
|
|
205
|
-
export function buildMinimalAutoGsdToolSet(activeToolNames, unitType, registeredToolNames = activeToolNames) {
|
|
207
|
+
export function buildMinimalAutoGsdToolSet(activeToolNames, unitType, registeredToolNames = activeToolNames, warnOnUnresolvedRequiredTools = registeredToolNames !== activeToolNames) {
|
|
206
208
|
if (unitType === "run-uat") {
|
|
207
209
|
return buildRunUatGsdToolSet(activeToolNames, registeredToolNames);
|
|
208
210
|
}
|
|
@@ -214,7 +216,20 @@ export function buildMinimalAutoGsdToolSet(activeToolNames, unitType, registered
|
|
|
214
216
|
...availableBaseTools,
|
|
215
217
|
])];
|
|
216
218
|
const scoped = resolveScopedToolNames([...activeToolNames, ...registeredToolNames], [...MINIMAL_GSD_TOOL_NAMES, ...unitTools]);
|
|
217
|
-
|
|
219
|
+
const result = withPreservedShimTools([...new Set([...preserved, ...scoped])]);
|
|
220
|
+
warnIfRequiredWorkflowToolsUnresolved(unitType, result, warnOnUnresolvedRequiredTools);
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
function hasResolvedWorkflowTool(resolvedToolNames, requiredToolName) {
|
|
224
|
+
return resolvedToolNames.some((name) => name === requiredToolName || mcpToolMatchesBaseName(name, requiredToolName));
|
|
225
|
+
}
|
|
226
|
+
function warnIfRequiredWorkflowToolsUnresolved(unitType, scopedToolNames, shouldWarn) {
|
|
227
|
+
if (!unitType || !shouldWarn)
|
|
228
|
+
return;
|
|
229
|
+
const unresolved = getRequiredWorkflowToolsForUnit(unitType).filter((toolName) => !hasResolvedWorkflowTool(scopedToolNames, toolName));
|
|
230
|
+
if (unresolved.length === 0)
|
|
231
|
+
return;
|
|
232
|
+
safetyLogWarning("bootstrap", `buildMinimalAutoGsdToolSet(${unitType}): required workflow tool(s) not in active/registered surface after scoping: ${unresolved.join(", ")}. Tool registration may have partially failed, provider filtering may have removed a required tool, or workflow MCP may be disconnected.`);
|
|
218
233
|
}
|
|
219
234
|
export function buildRunUatGsdToolSet(activeToolNames, registeredToolNames = activeToolNames) {
|
|
220
235
|
const scoped = resolveScopedToolNames([...activeToolNames, ...registeredToolNames], [
|
|
@@ -240,7 +255,7 @@ export function buildMinimalGsdWorkflowToolSet(activeToolNames, registeredToolNa
|
|
|
240
255
|
const scoped = resolveScopedToolNames([...activeToolNames, ...registeredToolNames], WORKFLOW_GSD_TOOL_NAMES);
|
|
241
256
|
return withPreservedShimTools([...new Set([...preserved, ...scoped])]);
|
|
242
257
|
}
|
|
243
|
-
export function buildRequestScopedGsdToolSet(activeToolNames, requestCustomMessages, registeredToolNames = activeToolNames, guidedUnitType) {
|
|
258
|
+
export function buildRequestScopedGsdToolSet(activeToolNames, requestCustomMessages, registeredToolNames = activeToolNames, guidedUnitType, warnOnUnresolvedRequiredTools = registeredToolNames !== activeToolNames) {
|
|
244
259
|
for (let index = (requestCustomMessages?.length ?? 0) - 1; index >= 0; index--) {
|
|
245
260
|
const currentCustomType = requestCustomMessages?.[index]?.customType;
|
|
246
261
|
if (currentCustomType === "gsd-run" ||
|
|
@@ -248,7 +263,7 @@ export function buildRequestScopedGsdToolSet(activeToolNames, requestCustomMessa
|
|
|
248
263
|
currentCustomType === "gsd-doctor-heal" ||
|
|
249
264
|
currentCustomType === "gsd-triage") {
|
|
250
265
|
if (guidedUnitType) {
|
|
251
|
-
return buildMinimalAutoGsdToolSet(activeToolNames, guidedUnitType, registeredToolNames);
|
|
266
|
+
return buildMinimalAutoGsdToolSet(activeToolNames, guidedUnitType, registeredToolNames, warnOnUnresolvedRequiredTools);
|
|
252
267
|
}
|
|
253
268
|
return buildMinimalGsdWorkflowToolSet(activeToolNames, registeredToolNames);
|
|
254
269
|
}
|
|
@@ -282,8 +297,9 @@ function applyMinimalGsdToolSurface(pi) {
|
|
|
282
297
|
const dash = getAutoRuntimeSnapshot();
|
|
283
298
|
if (dash.active && dash.currentUnit) {
|
|
284
299
|
const currentToolNames = pi.getActiveTools();
|
|
300
|
+
const hasRegisteredSurface = typeof pi.getAllTools === "function";
|
|
285
301
|
const registeredToolNames = resolveRegisteredToolNames(pi, currentToolNames);
|
|
286
|
-
const scopedToolNames = buildMinimalAutoGsdToolSet(currentToolNames, dash.currentUnit.type, registeredToolNames);
|
|
302
|
+
const scopedToolNames = buildMinimalAutoGsdToolSet(currentToolNames, dash.currentUnit.type, registeredToolNames, hasRegisteredSurface);
|
|
287
303
|
recordAutoToolSurfaceSnapshot({
|
|
288
304
|
source: "runtime-scope",
|
|
289
305
|
unitType: dash.currentUnit.type,
|
|
@@ -302,9 +318,10 @@ export function scopeGsdWorkflowToolsForDispatch(pi, unitType) {
|
|
|
302
318
|
if (isFullGsdToolSurfaceRequested())
|
|
303
319
|
return null;
|
|
304
320
|
const current = pi.getActiveTools();
|
|
321
|
+
const hasRegisteredSurface = typeof pi.getAllTools === "function";
|
|
305
322
|
const registeredToolNames = resolveRegisteredToolNames(pi, current);
|
|
306
323
|
const scoped = unitType
|
|
307
|
-
? buildMinimalAutoGsdToolSet(current, unitType, registeredToolNames)
|
|
324
|
+
? buildMinimalAutoGsdToolSet(current, unitType, registeredToolNames, hasRegisteredSurface)
|
|
308
325
|
: buildMinimalGsdWorkflowToolSet(current, registeredToolNames);
|
|
309
326
|
recordAutoToolSurfaceSnapshot({
|
|
310
327
|
source: "dispatch-scope",
|
|
@@ -1303,12 +1320,13 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
1303
1320
|
return surfaceReduced ? { toolNames: providerCompatible } : undefined;
|
|
1304
1321
|
}
|
|
1305
1322
|
const registeredToolNames = resolveRegisteredToolNames(pi, event.activeToolNames);
|
|
1323
|
+
const hasRegisteredSurface = typeof pi.getAllTools === "function";
|
|
1306
1324
|
const compatibleRegisteredToolNames = filterToolsForProvider(registeredToolNames, event.selectedModelApi, event.selectedModelProvider).compatible.filter((name) => !(dropAliases && isWorkflowAliasTool(name)));
|
|
1307
1325
|
const guidedUnit = getGuidedUnitContext();
|
|
1308
1326
|
const requestRegisteredToolNames = guidedUnit?.unitType === "run-uat"
|
|
1309
1327
|
? compatibleRegisteredToolNames
|
|
1310
1328
|
: registeredToolNames;
|
|
1311
|
-
const requestScoped = buildRequestScopedGsdToolSet(guidedUnit?.unitType === "run-uat" ? aliasFilteredCompatible : providerCompatible, event.requestCustomMessages, requestRegisteredToolNames, guidedUnit?.unitType);
|
|
1329
|
+
const requestScoped = buildRequestScopedGsdToolSet(guidedUnit?.unitType === "run-uat" ? aliasFilteredCompatible : providerCompatible, event.requestCustomMessages, requestRegisteredToolNames, guidedUnit?.unitType, hasRegisteredSurface);
|
|
1312
1330
|
if (requestScoped) {
|
|
1313
1331
|
recordAutoToolSurfaceSnapshot({
|
|
1314
1332
|
source: "provider-adjustment",
|
|
@@ -1324,7 +1342,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
1324
1342
|
const registeredForUnit = dash.currentUnit.type === "run-uat"
|
|
1325
1343
|
? compatibleRegisteredToolNames
|
|
1326
1344
|
: resolveRegisteredToolNames(pi, event.activeToolNames);
|
|
1327
|
-
const scopedToolNames = buildMinimalAutoGsdToolSet(dash.currentUnit.type === "run-uat" ? aliasFilteredCompatible : providerCompatible, dash.currentUnit.type, registeredForUnit);
|
|
1345
|
+
const scopedToolNames = buildMinimalAutoGsdToolSet(dash.currentUnit.type === "run-uat" ? aliasFilteredCompatible : providerCompatible, dash.currentUnit.type, registeredForUnit, hasRegisteredSurface);
|
|
1328
1346
|
recordAutoToolSurfaceSnapshot({
|
|
1329
1347
|
source: "provider-adjustment",
|
|
1330
1348
|
unitType: dash.currentUnit.type,
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
// Project/App: gsd-pi
|
|
2
2
|
// File Purpose: Shared browser-observable UAT requirement and evidence detection.
|
|
3
|
-
|
|
3
|
+
import { BROWSER_EVIDENCE_SIGNAL_TOOL_NAMES } from "../shared/browser-contract.js";
|
|
4
|
+
// Alternation fragment over the contract's evidence-signal names, e.g.
|
|
5
|
+
// `browser_(?:assert|batch|...)`. The names are `browser_`-prefixed
|
|
6
|
+
// identifiers (pinned by tests/browser-contract.test.ts), so no escaping is
|
|
7
|
+
// needed.
|
|
8
|
+
const BROWSER_TOOL_SIGNAL = `browser_(?:${BROWSER_EVIDENCE_SIGNAL_TOOL_NAMES.map((name) => name.slice("browser_".length)).join("|")})`;
|
|
9
|
+
export const BROWSER_REQUIREMENT_RE = new RegExp(String.raw `\b(?:file://|localhost|playwright|chrome|screenshot|snapshot|${BROWSER_TOOL_SIGNAL})\b|\b(?:open|launch|navigate|load|visit|serve|start)\b.{0,80}\b(?:browser|page|localhost|file://)\b|\bbrowser\s+(?:check|session|test|uat|tool|automation|interaction|flow)\b`, "i");
|
|
4
10
|
export const NO_BROWSER_EVIDENCE_RE = /\b(?:no|without|not|wasn'?t|isn'?t)\s+(?:automated\s+)?(?:live\s+)?browser(?:\s+(?:session|test|uat))?|\bno\s+automated\s+browser\b|\bnot\s+conducted\b/i;
|
|
5
|
-
export const BROWSER_RUNTIME_RE =
|
|
11
|
+
export const BROWSER_RUNTIME_RE = new RegExp(String.raw `\b(?:browser|playwright|chrome|camoufox|${BROWSER_TOOL_SIGNAL}|screenshot|snapshot|file://|localhost)\b`, "i");
|
|
6
12
|
export const BROWSER_ACTION_RE = /\b(?:open(?:ed)?|navigate(?:d)?|click(?:ed)?|type(?:d)?|reload(?:ed)?|capture(?:d)?|screenshot|snapshot)\b/i;
|
|
7
13
|
export const BROWSER_ASSERTION_RE = /\b(?:assert(?:ed|ion)?|observed|confirmed|verified|expected|visible|text|count|label|strikethrough|localstorage|screenshot|snapshot|passed)\b/i;
|
|
8
14
|
const NON_REQUIREMENT_BROWSER_HEADING_RE = /^(?:not\s+proven|not\s+covered|out\s+of\s+scope|deferred|follow-?ups?|known\s+limitations|notes\s+for\s+tester)\b/i;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
|
+
import { isGsdBrowserMcpServerConfig } from "../shared/gsd-browser-cli.js";
|
|
4
5
|
import { toMcpWildcardToolName } from "./mcp-tool-name.js";
|
|
5
6
|
import { resolveModelMcpConfig } from "./preferences-mcp.js";
|
|
6
7
|
function isRecord(value) {
|
|
@@ -63,29 +64,11 @@ function isWorkflowMcpServerConfig(config) {
|
|
|
63
64
|
const args = Array.isArray(config.args) ? config.args.filter((arg) => typeof arg === "string") : [];
|
|
64
65
|
return args.some((arg) => arg.includes("gsd-mcp-server") || arg.includes("packages/mcp-server"));
|
|
65
66
|
}
|
|
66
|
-
function isBrowserMcpServerConfig(config) {
|
|
67
|
-
if (!isRecord(config))
|
|
68
|
-
return false;
|
|
69
|
-
const command = typeof config.command === "string" ? config.command : "";
|
|
70
|
-
if (command.includes("gsd-browser") || command.includes("@opengsd/gsd-browser")) {
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
const env = config.env;
|
|
74
|
-
if (isRecord(env)) {
|
|
75
|
-
if (typeof env.GSD_BROWSER_CLI_PATH === "string"
|
|
76
|
-
|| typeof env.GSD_BROWSER_BIN_PATH === "string"
|
|
77
|
-
|| typeof env.GSD_BROWSER_MCP_COMMAND === "string") {
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
const args = Array.isArray(config.args) ? config.args.filter((arg) => typeof arg === "string") : [];
|
|
82
|
-
return args.some((arg) => arg.includes("gsd-browser") || arg.includes("@opengsd/gsd-browser"));
|
|
83
|
-
}
|
|
84
67
|
export function discoverWorkflowMcpServerName(projectDir) {
|
|
85
68
|
return discoverMcpServers(projectDir).find((server) => isWorkflowMcpServerConfig(server.config))?.name;
|
|
86
69
|
}
|
|
87
70
|
export function discoverBrowserMcpServerName(projectDir) {
|
|
88
|
-
return discoverMcpServers(projectDir).find((server) =>
|
|
71
|
+
return discoverMcpServers(projectDir).find((server) => isGsdBrowserMcpServerConfig(server.config))?.name;
|
|
89
72
|
}
|
|
90
73
|
export function discoverMcpServerNames(projectDir) {
|
|
91
74
|
return discoverMcpServers(projectDir).map((server) => server.name);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Project/App: gsd-pi
|
|
2
2
|
// File Purpose: Central UAT mode policy for dispatch, tool presentation, and result validation.
|
|
3
|
+
import { hasBrowserContractPrefix } from "../shared/browser-contract.js";
|
|
3
4
|
import { extractUatType } from "./files.js";
|
|
4
5
|
import { hasBrowserRequiredText } from "./browser-evidence.js";
|
|
5
6
|
import { parseMcpToolName } from "./mcp-tool-name.js";
|
|
@@ -84,7 +85,7 @@ export function uatTypeIncludesBrowser(uatType) {
|
|
|
84
85
|
export function isUatBrowserToolName(toolName) {
|
|
85
86
|
const parsed = parseMcpToolName(toolName);
|
|
86
87
|
const canonicalName = parsed?.toolName ?? toolName;
|
|
87
|
-
if (canonicalName
|
|
88
|
+
if (hasBrowserContractPrefix(canonicalName))
|
|
88
89
|
return true;
|
|
89
90
|
return parsed?.toolName === "*" && parsed.serverName.toLowerCase().includes("browser");
|
|
90
91
|
}
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
// (`UNIT_MANIFESTS` stays in unit-context-manifest.ts, already type-enforced
|
|
23
23
|
// against the registry's `UnitType`) and prompt-template association (still
|
|
24
24
|
// implicit in auto-prompts.ts builders).
|
|
25
|
+
import { BROWSER_CONTRACT_TOOL_NAMES } from "../shared/browser-contract.js";
|
|
25
26
|
// ─── Shared tool-name constants (used by registry rows) ──────────────────
|
|
26
27
|
export const RUN_UAT_WORKFLOW_TOOL_NAMES = [
|
|
27
28
|
"gsd_uat_exec",
|
|
@@ -37,26 +38,12 @@ export const RUN_UAT_READ_ONLY_TOOL_NAMES = [
|
|
|
37
38
|
"ls",
|
|
38
39
|
"read",
|
|
39
40
|
];
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"browser_fill_ref",
|
|
47
|
-
"browser_wait_for",
|
|
48
|
-
"browser_assert",
|
|
49
|
-
"browser_verify",
|
|
50
|
-
"browser_screenshot",
|
|
51
|
-
"browser_snapshot_refs",
|
|
52
|
-
"browser_find",
|
|
53
|
-
"browser_get_console_logs",
|
|
54
|
-
"browser_get_network_logs",
|
|
55
|
-
"browser_evaluate",
|
|
56
|
-
"browser_reload",
|
|
57
|
-
"browser_batch",
|
|
58
|
-
"browser_act",
|
|
59
|
-
];
|
|
41
|
+
/**
|
|
42
|
+
* Browser tools presented to run-uat. A derived view of the Browser Automation
|
|
43
|
+
* Contract vocabulary (shared/browser-contract.ts) — the contract module is the
|
|
44
|
+
* only place browser tool names are declared.
|
|
45
|
+
*/
|
|
46
|
+
export const RUN_UAT_BROWSER_TOOL_NAMES = BROWSER_CONTRACT_TOOL_NAMES;
|
|
60
47
|
// ─── The registry ─────────────────────────────────────────────────────────
|
|
61
48
|
export const UNIT_REGISTRY = {
|
|
62
49
|
"research-milestone": {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// Project/App: gsd-pi
|
|
2
|
-
// File Purpose: Web-app detection and
|
|
2
|
+
// File Purpose: Web-app detection and browser-UAT guidance for planning and slice closeout.
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
|
+
import { resolveAmbientBrowserEngineResolution } from "../browser-tools/engine/selection.js";
|
|
5
6
|
import { detectWebApp } from "../browser-tools/web-app-detect.js";
|
|
7
|
+
import { UAT_MODE_POLICIES } from "./uat-policy.js";
|
|
6
8
|
export { detectWebApp };
|
|
7
9
|
function readPackageJson(projectRoot) {
|
|
8
10
|
const packageJsonPath = resolve(projectRoot, "package.json");
|
|
@@ -39,24 +41,56 @@ export function findPlaywrightTestScript(projectRoot) {
|
|
|
39
41
|
}
|
|
40
42
|
return null;
|
|
41
43
|
}
|
|
44
|
+
function describeBrowserToolBacking(engineResolution) {
|
|
45
|
+
switch (engineResolution.engine) {
|
|
46
|
+
case "gsd-browser":
|
|
47
|
+
return "This project looks browser-facing. GSD exposes `browser_*` tools backed by the managed gsd-browser engine for run-uat.";
|
|
48
|
+
case "legacy":
|
|
49
|
+
return "This project looks browser-facing. GSD exposes Playwright-backed `browser_*` tools for run-uat.";
|
|
50
|
+
case "off":
|
|
51
|
+
return "This project looks browser-facing, but Pi browser tools are disabled (GSD_BROWSER_ENGINE=off) — prefer `runtime-executable` UAT with automated browser test commands.";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// One bullet per recommended UAT mode; `mode` keys into UAT_MODE_POLICIES so
|
|
55
|
+
// modes that require browser tools drop out of the guidance when the resolved
|
|
56
|
+
// engine provides none (mixed/live-runtime share one bullet and one policy bit).
|
|
57
|
+
const UAT_MODE_GUIDANCE = [
|
|
58
|
+
{
|
|
59
|
+
mode: "browser-executable",
|
|
60
|
+
bullet: "- `browser-executable` — navigate to `http://localhost:…`, click, screenshot, assert via `browser_*` tools during run-uat",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
mode: "runtime-executable",
|
|
64
|
+
bullet: "- `runtime-executable` — run an automated browser test command via `gsd_uat_exec` (for example `npx playwright test`)",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
mode: "mixed",
|
|
68
|
+
bullet: "- `mixed` / `live-runtime` — combine runtime startup checks with interactive browser verification",
|
|
69
|
+
},
|
|
70
|
+
];
|
|
42
71
|
/**
|
|
43
72
|
* Markdown block injected into plan/complete-slice prompts when the project
|
|
44
|
-
* looks browser-facing. Returns null for CLI/library-only repos.
|
|
73
|
+
* looks browser-facing. Returns null for CLI/library-only repos. Guidance is
|
|
74
|
+
* composed from the resolved Browser Automation Engine so prompts never claim
|
|
75
|
+
* an engine the runtime is not using; `engineResolution` is injectable for
|
|
76
|
+
* tests and defaults to the ambient resolution.
|
|
45
77
|
*/
|
|
46
|
-
export function buildWebAppUatGuidanceBlock(projectRoot) {
|
|
78
|
+
export function buildWebAppUatGuidanceBlock(projectRoot, engineResolution) {
|
|
47
79
|
if (!detectWebApp(projectRoot))
|
|
48
80
|
return null;
|
|
81
|
+
const resolvedEngine = engineResolution ?? resolveAmbientBrowserEngineResolution(projectRoot);
|
|
82
|
+
const browserToolsAvailable = resolvedEngine.engine !== "off";
|
|
49
83
|
const playwrightScript = findPlaywrightTestScript(projectRoot);
|
|
50
84
|
const hasPlaywright = hasPlaywrightTestDependency(projectRoot) || playwrightScript !== null;
|
|
51
85
|
const lines = [
|
|
52
86
|
"### Web App UAT (detected)",
|
|
53
87
|
"",
|
|
54
|
-
|
|
88
|
+
describeBrowserToolBacking(resolvedEngine),
|
|
55
89
|
"",
|
|
56
90
|
"**UAT modes (pick one per slice — do not use `artifact-driven` for browser steps):**",
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
91
|
+
...UAT_MODE_GUIDANCE
|
|
92
|
+
.filter(({ mode }) => browserToolsAvailable || !UAT_MODE_POLICIES[mode].browserTools)
|
|
93
|
+
.map(({ bullet }) => bullet),
|
|
60
94
|
"",
|
|
61
95
|
"**Planning / closeout rules:**",
|
|
62
96
|
"- Preconditions must name the dev-server command and URL (for example `npm run dev` → `http://localhost:3000`)",
|
|
@@ -74,7 +108,10 @@ export function buildWebAppUatGuidanceBlock(projectRoot) {
|
|
|
74
108
|
lines.push("- Name concrete spec paths in slice Verification (for example `e2e/smoke.spec.ts`)");
|
|
75
109
|
}
|
|
76
110
|
else {
|
|
77
|
-
lines.push("", "**Playwright scaffolding (first UI slice):** no `playwright` / `@playwright/test` dependency yet.", "- Add a planning task that installs Playwright, adds `playwright.config.ts`, and creates a minimal smoke spec (for example `e2e/smoke.spec.ts`)", "- Task `verify` should run `npx playwright test` (or the focused spec) with a safe, simple command"
|
|
111
|
+
lines.push("", "**Playwright scaffolding (first UI slice):** no `playwright` / `@playwright/test` dependency yet.", "- Add a planning task that installs Playwright, adds `playwright.config.ts`, and creates a minimal smoke spec (for example `e2e/smoke.spec.ts`)", "- Task `verify` should run `npx playwright test` (or the focused spec) with a safe, simple command");
|
|
112
|
+
if (browserToolsAvailable) {
|
|
113
|
+
lines.push("- Until specs exist, use `browser-executable` UAT with localhost preconditions and interactive `browser_*` checks at slice closeout");
|
|
114
|
+
}
|
|
78
115
|
}
|
|
79
116
|
return lines.join("\n");
|
|
80
117
|
}
|
|
@@ -214,9 +214,11 @@ export function registerNativeSearchHooks(pi) {
|
|
|
214
214
|
isAnthropic = isAnthropicProvider && payloadLooksAnthropic !== false;
|
|
215
215
|
}
|
|
216
216
|
else {
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
|
|
217
|
+
// No authoritative provider info available (no event.model, no model_select).
|
|
218
|
+
// Do NOT inject native web_search — guessing on model name alone causes 400
|
|
219
|
+
// "unsupported_value" errors when the actual provider (copilot, openrouter,
|
|
220
|
+
// proxy, etc.) doesn't expose the server-side search tool (#648).
|
|
221
|
+
isAnthropic = false;
|
|
220
222
|
}
|
|
221
223
|
if (!isAnthropic)
|
|
222
224
|
return;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Browser Automation Contract — the single source for the canonical
|
|
3
|
+
// Pi-facing browser tool vocabulary. Engine adapters (legacy Playwright, managed
|
|
4
|
+
// gsd-browser), UAT policy, dispatch preflight, and evidence detection all derive
|
|
5
|
+
// their browser tool knowledge from this module instead of re-listing names.
|
|
6
|
+
/**
|
|
7
|
+
* Canonical `browser_*` tool names of the Browser Automation Contract.
|
|
8
|
+
*
|
|
9
|
+
* These are the product-level names Units see regardless of which Browser
|
|
10
|
+
* Automation Engine serves them (ADR-024). Adding a capability here is the
|
|
11
|
+
* one-line vocabulary change; the engine adapters and presentation surfaces
|
|
12
|
+
* are typed against this list, so missing coverage fails typecheck.
|
|
13
|
+
*/
|
|
14
|
+
export const BROWSER_CONTRACT_TOOL_NAMES = [
|
|
15
|
+
"browser_navigate",
|
|
16
|
+
"browser_click",
|
|
17
|
+
"browser_type",
|
|
18
|
+
"browser_fill_form",
|
|
19
|
+
"browser_click_ref",
|
|
20
|
+
"browser_fill_ref",
|
|
21
|
+
"browser_wait_for",
|
|
22
|
+
"browser_assert",
|
|
23
|
+
"browser_verify",
|
|
24
|
+
"browser_screenshot",
|
|
25
|
+
"browser_snapshot_refs",
|
|
26
|
+
"browser_find",
|
|
27
|
+
"browser_get_console_logs",
|
|
28
|
+
"browser_get_network_logs",
|
|
29
|
+
"browser_evaluate",
|
|
30
|
+
"browser_reload",
|
|
31
|
+
"browser_batch",
|
|
32
|
+
"browser_act",
|
|
33
|
+
];
|
|
34
|
+
const BROWSER_CONTRACT_TOOL_NAME_SET = new Set(BROWSER_CONTRACT_TOOL_NAMES);
|
|
35
|
+
export function isBrowserContractToolName(name) {
|
|
36
|
+
return BROWSER_CONTRACT_TOOL_NAME_SET.has(name);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Whether a canonical (non-MCP-prefixed) tool name belongs to the browser tool
|
|
40
|
+
* family. Broader than the contract list on purpose: an External MCP Client or
|
|
41
|
+
* host integration may supply additional `browser_*` tools that still satisfy
|
|
42
|
+
* browser-backed UAT.
|
|
43
|
+
*/
|
|
44
|
+
export function hasBrowserContractPrefix(canonicalToolName) {
|
|
45
|
+
return canonicalToolName.startsWith("browser_");
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Contract tool names whose appearance in prose marks browser-backed UAT
|
|
49
|
+
* activity (requirement or evidence language). Consumed by the
|
|
50
|
+
* browser-evidence regexes so textual detection stays derived from the
|
|
51
|
+
* contract vocabulary.
|
|
52
|
+
*/
|
|
53
|
+
export const BROWSER_EVIDENCE_SIGNAL_TOOL_NAMES = [
|
|
54
|
+
"browser_assert",
|
|
55
|
+
"browser_batch",
|
|
56
|
+
"browser_find",
|
|
57
|
+
"browser_verify",
|
|
58
|
+
"browser_snapshot_refs",
|
|
59
|
+
];
|