zelari-code 1.18.0 → 1.18.1
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.
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* The "visual verification loop" for web projects: after the agent edits a UI,
|
|
5
5
|
* it navigates the running app and gets back the signals an LLM can actually
|
|
6
6
|
* act on — console errors, uncaught page exceptions, failed network requests,
|
|
7
|
-
* the final title/URL, whether an expected selector is present,
|
|
8
|
-
* screenshot path. Far stronger than "the tests pass"
|
|
7
|
+
* the final title/URL, whether an expected selector is present, evaluate
|
|
8
|
+
* results, and a saved screenshot path. Far stronger than "the tests pass"
|
|
9
|
+
* for front-end work.
|
|
9
10
|
*
|
|
10
11
|
* Playwright is an OPTIONAL dependency, loaded via dynamic import so it is not
|
|
11
12
|
* a hard requirement of the package. When it (or a browser) isn't available,
|
|
@@ -19,6 +20,12 @@
|
|
|
19
20
|
import { createRequire } from 'node:module';
|
|
20
21
|
import path from 'node:path';
|
|
21
22
|
import { pathToFileURL } from 'node:url';
|
|
23
|
+
/** Max length of an evaluate expression (chars). */
|
|
24
|
+
const MAX_EVAL_EXPR = 4_000;
|
|
25
|
+
/** Max JSON-serialized evaluate result size. */
|
|
26
|
+
const MAX_EVAL_RESULT = 8_000;
|
|
27
|
+
/** Max body text sample returned to the agent. */
|
|
28
|
+
const MAX_BODY_SNIPPET = 2_000;
|
|
22
29
|
/** Coerce a dynamic-import module shape into PlaywrightLike (or null). */
|
|
23
30
|
function asPlaywright(mod) {
|
|
24
31
|
if (!mod || typeof mod !== 'object')
|
|
@@ -74,6 +81,50 @@ export async function loadPlaywright(cwd) {
|
|
|
74
81
|
* Prefer `loadPlaywright(explicitCwd)` at call sites that know the workspace.
|
|
75
82
|
*/
|
|
76
83
|
export const defaultPlaywrightLoader = async () => loadPlaywright(process.cwd());
|
|
84
|
+
/** Cap and JSON-safe-clone evaluate return values for the agent. */
|
|
85
|
+
export function serializeEvaluateValue(value) {
|
|
86
|
+
try {
|
|
87
|
+
const json = JSON.stringify(value, (_k, v) => {
|
|
88
|
+
if (typeof v === 'function')
|
|
89
|
+
return '[Function]';
|
|
90
|
+
if (typeof v === 'bigint')
|
|
91
|
+
return v.toString();
|
|
92
|
+
if (v instanceof Error)
|
|
93
|
+
return { name: v.name, message: v.message };
|
|
94
|
+
return v;
|
|
95
|
+
});
|
|
96
|
+
if (json === undefined)
|
|
97
|
+
return null;
|
|
98
|
+
if (json.length > MAX_EVAL_RESULT) {
|
|
99
|
+
return {
|
|
100
|
+
truncated: true,
|
|
101
|
+
preview: json.slice(0, MAX_EVAL_RESULT),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return JSON.parse(json);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
return {
|
|
108
|
+
error: `non-serializable: ${err instanceof Error ? err.message : String(err)}`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Wrap a user expression as a Playwright evaluate **function body**.
|
|
114
|
+
* Playwright treats string pageFunctions as `function(){ <body> }`, so we
|
|
115
|
+
* must `return` the value.
|
|
116
|
+
*/
|
|
117
|
+
export function wrapEvaluateExpression(expression) {
|
|
118
|
+
const expr = expression.trim();
|
|
119
|
+
if (!expr)
|
|
120
|
+
return 'return undefined';
|
|
121
|
+
// Multi-statement or already has return → use as body (ensure return if missing).
|
|
122
|
+
if (/\breturn\b/.test(expr) || expr.includes('\n')) {
|
|
123
|
+
return expr;
|
|
124
|
+
}
|
|
125
|
+
// Single expression
|
|
126
|
+
return `return (${expr})`;
|
|
127
|
+
}
|
|
77
128
|
/**
|
|
78
129
|
* Navigate to a URL (optionally running a sequence of actions) and collect
|
|
79
130
|
* verification signals. Best-effort — never throws; a missing browser or a
|
|
@@ -83,6 +134,7 @@ export async function runBrowserCheck(options, loader) {
|
|
|
83
134
|
const consoleErrors = [];
|
|
84
135
|
const pageErrors = [];
|
|
85
136
|
const failedRequests = [];
|
|
137
|
+
const evaluateResults = [];
|
|
86
138
|
const base = { ok: false, consoleErrors, pageErrors, failedRequests };
|
|
87
139
|
const resolve = loader ??
|
|
88
140
|
(() => loadPlaywright(options.cwd ?? process.cwd()));
|
|
@@ -99,6 +151,7 @@ export async function runBrowserCheck(options, loader) {
|
|
|
99
151
|
}
|
|
100
152
|
const timeout = options.timeoutMs ?? 15_000;
|
|
101
153
|
let browser;
|
|
154
|
+
let textFound;
|
|
102
155
|
try {
|
|
103
156
|
browser = await pw.chromium.launch({ headless: true });
|
|
104
157
|
const page = await browser.newPage();
|
|
@@ -126,6 +179,61 @@ export async function runBrowserCheck(options, loader) {
|
|
|
126
179
|
case 'wait':
|
|
127
180
|
await page.waitForTimeout(action.ms);
|
|
128
181
|
break;
|
|
182
|
+
case 'press': {
|
|
183
|
+
const key = action.key.trim();
|
|
184
|
+
if (!key)
|
|
185
|
+
break;
|
|
186
|
+
if (page.keyboard && typeof page.keyboard.press === 'function') {
|
|
187
|
+
await page.keyboard.press(key, { timeout });
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// Fallback for fakes / surfaces without keyboard
|
|
191
|
+
await page.evaluate(wrapEvaluateExpression(`document.dispatchEvent(new KeyboardEvent('keydown',{key:${JSON.stringify(key)},bubbles:true})); true`));
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
case 'waitForText': {
|
|
196
|
+
const needle = action.text;
|
|
197
|
+
const t = action.timeoutMs ?? timeout;
|
|
198
|
+
try {
|
|
199
|
+
// String function body: Playwright injects as function(arg) { ... }
|
|
200
|
+
await page.waitForFunction(`return (document.body && (document.body.innerText || document.body.textContent || '')).includes(arguments[0])`, needle, { timeout: t });
|
|
201
|
+
textFound = textFound === false ? false : true;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
textFound = false;
|
|
205
|
+
}
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
case 'evaluate': {
|
|
209
|
+
const raw = action.expression ?? '';
|
|
210
|
+
if (raw.length > MAX_EVAL_EXPR) {
|
|
211
|
+
evaluateResults.push({
|
|
212
|
+
expression: raw.slice(0, 120) + '…',
|
|
213
|
+
error: `expression too long (max ${MAX_EVAL_EXPR} chars)`,
|
|
214
|
+
});
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
if (!raw.trim()) {
|
|
218
|
+
evaluateResults.push({ expression: raw, error: 'empty expression' });
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
const wrapped = wrapEvaluateExpression(raw);
|
|
223
|
+
const value = await page.evaluate(wrapped);
|
|
224
|
+
evaluateResults.push({
|
|
225
|
+
expression: raw.length > 200 ? raw.slice(0, 200) + '…' : raw,
|
|
226
|
+
value: serializeEvaluateValue(value),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
evaluateResults.push({
|
|
231
|
+
expression: raw.length > 200 ? raw.slice(0, 200) + '…' : raw,
|
|
232
|
+
error: err instanceof Error ? err.message : String(err),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
129
237
|
default:
|
|
130
238
|
break;
|
|
131
239
|
}
|
|
@@ -140,6 +248,21 @@ export async function runBrowserCheck(options, loader) {
|
|
|
140
248
|
selectorFound = false;
|
|
141
249
|
}
|
|
142
250
|
}
|
|
251
|
+
let bodyTextSnippet;
|
|
252
|
+
if (options.textSample) {
|
|
253
|
+
try {
|
|
254
|
+
const text = await page.evaluate(wrapEvaluateExpression(`(document.body?.innerText || document.body?.textContent || '').trim()`));
|
|
255
|
+
if (typeof text === 'string' && text.length > 0) {
|
|
256
|
+
bodyTextSnippet =
|
|
257
|
+
text.length > MAX_BODY_SNIPPET
|
|
258
|
+
? text.slice(0, MAX_BODY_SNIPPET) + '…'
|
|
259
|
+
: text;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// ignore
|
|
264
|
+
}
|
|
265
|
+
}
|
|
143
266
|
let screenshotPath;
|
|
144
267
|
if (options.screenshotPath) {
|
|
145
268
|
try {
|
|
@@ -159,6 +282,9 @@ export async function runBrowserCheck(options, loader) {
|
|
|
159
282
|
url: page.url(),
|
|
160
283
|
...(title !== undefined ? { title } : {}),
|
|
161
284
|
...(selectorFound !== undefined ? { selectorFound } : {}),
|
|
285
|
+
...(evaluateResults.length > 0 ? { evaluateResults } : {}),
|
|
286
|
+
...(textFound !== undefined ? { textFound } : {}),
|
|
287
|
+
...(bodyTextSnippet !== undefined ? { bodyTextSnippet } : {}),
|
|
162
288
|
...(screenshotPath ? { screenshotPath } : {}),
|
|
163
289
|
};
|
|
164
290
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"driver.js","sourceRoot":"","sources":["../../../src/cli/browser/driver.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"driver.js","sourceRoot":"","sources":["../../../src/cli/browser/driver.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* browser/tools — the `browser_check` visual-verification tool.
|
|
3
3
|
*
|
|
4
4
|
* Lets the agent confirm a web change actually works in a real browser:
|
|
5
|
-
* navigate the running app, optionally click/fill, and get back
|
|
6
|
-
* errors, uncaught exceptions, failed requests, the title/URL, whether
|
|
7
|
-
* expected element appeared, and a screenshot path.
|
|
8
|
-
* degrades with install instructions when
|
|
5
|
+
* navigate the running app, optionally click/fill/evaluate/press, and get back
|
|
6
|
+
* console errors, uncaught exceptions, failed requests, the title/URL, whether
|
|
7
|
+
* an expected element/text appeared, evaluate results, and a screenshot path.
|
|
8
|
+
* Optional Playwright dep — degrades with install instructions when absent.
|
|
9
9
|
*/
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import os from 'node:os';
|
|
@@ -17,24 +17,54 @@ const ActionSchema = z.discriminatedUnion('type', [
|
|
|
17
17
|
z.object({ type: z.literal('fill'), selector: z.string().min(1), value: z.string() }),
|
|
18
18
|
z.object({ type: z.literal('wait'), ms: z.number().int().positive().max(30_000) }),
|
|
19
19
|
z.object({ type: z.literal('goto'), url: z.string().min(1) }),
|
|
20
|
+
z.object({
|
|
21
|
+
type: z.literal('evaluate'),
|
|
22
|
+
expression: z
|
|
23
|
+
.string()
|
|
24
|
+
.min(1)
|
|
25
|
+
.max(4_000)
|
|
26
|
+
.describe('JS expression or statements run in the page (page.evaluate). Prefer DOM reads ' +
|
|
27
|
+
'(document.querySelector…), not assuming window.game exists. Return JSON-safe values.'),
|
|
28
|
+
}),
|
|
29
|
+
z.object({
|
|
30
|
+
type: z.literal('press'),
|
|
31
|
+
key: z
|
|
32
|
+
.string()
|
|
33
|
+
.min(1)
|
|
34
|
+
.describe('Playwright key name, e.g. "Space", "KeyW", "ArrowLeft", "Enter".'),
|
|
35
|
+
}),
|
|
36
|
+
z.object({
|
|
37
|
+
type: z.literal('waitForText'),
|
|
38
|
+
text: z.string().min(1).describe('Substring that must appear in document body text.'),
|
|
39
|
+
timeoutMs: z.number().int().positive().max(60_000).optional(),
|
|
40
|
+
}),
|
|
20
41
|
]);
|
|
21
42
|
export function createBrowserTool(deps = {}) {
|
|
22
43
|
return {
|
|
23
44
|
name: 'browser_check',
|
|
24
45
|
description: 'Open a URL in a headless browser to VERIFY a web change: optionally run ' +
|
|
25
|
-
'click/fill/goto/wait actions, then report console errors,
|
|
26
|
-
'exceptions, failed network requests,
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
46
|
+
'click/fill/goto/wait/press/waitForText/evaluate actions, then report console errors, ' +
|
|
47
|
+
'uncaught page exceptions, failed network requests, final title/URL, selector/text ' +
|
|
48
|
+
'presence, evaluate results, and a screenshot path. ' +
|
|
49
|
+
'Prefer DOM assertions (waitForSelector, waitForText, evaluate on document.querySelector) ' +
|
|
50
|
+
'over window.* globals — ES modules and const keep symbols off window. ' +
|
|
51
|
+
'Absence of console errors after a short wait is WEAK smoke only; assert UI state when ' +
|
|
52
|
+
'claiming a fix is verified. Requires Playwright (optional dependency).',
|
|
30
53
|
permissions: ['network'],
|
|
31
54
|
// A browser launch + navigation can take a while.
|
|
32
55
|
timeoutMs: 60_000,
|
|
33
56
|
inputSchema: z.object({
|
|
34
57
|
url: z.string().min(1).describe('URL to open (e.g. http://localhost:3000).'),
|
|
35
|
-
actions: z
|
|
58
|
+
actions: z
|
|
59
|
+
.array(ActionSchema)
|
|
60
|
+
.optional()
|
|
61
|
+
.describe('Optional sequence of interactions / probes before checking.'),
|
|
36
62
|
waitForSelector: z.string().optional().describe('Assert this CSS selector is present after actions.'),
|
|
37
63
|
screenshot: z.boolean().optional().describe('Save a screenshot (default true).'),
|
|
64
|
+
textSample: z
|
|
65
|
+
.boolean()
|
|
66
|
+
.optional()
|
|
67
|
+
.describe('Include a short body.innerText snippet (HUD / Game Over text).'),
|
|
38
68
|
}),
|
|
39
69
|
execute: async (args, ctx) => {
|
|
40
70
|
const a = args;
|
|
@@ -48,6 +78,7 @@ export function createBrowserTool(deps = {}) {
|
|
|
48
78
|
...(a.actions ? { actions: a.actions } : {}),
|
|
49
79
|
...(a.waitForSelector ? { waitForSelector: a.waitForSelector } : {}),
|
|
50
80
|
...(screenshotPath ? { screenshotPath } : {}),
|
|
81
|
+
...(a.textSample ? { textSample: true } : {}),
|
|
51
82
|
}, deps.loader);
|
|
52
83
|
if (!result.ok) {
|
|
53
84
|
return typedOk({ ok: false, note: result.error ?? 'browser check failed' });
|
|
@@ -56,7 +87,14 @@ export function createBrowserTool(deps = {}) {
|
|
|
56
87
|
const clean = result.consoleErrors.length === 0 &&
|
|
57
88
|
result.pageErrors.length === 0 &&
|
|
58
89
|
result.failedRequests.length === 0 &&
|
|
59
|
-
result.selectorFound !== false
|
|
90
|
+
result.selectorFound !== false &&
|
|
91
|
+
result.textFound !== false;
|
|
92
|
+
const hadDomAssertion = result.selectorFound !== undefined ||
|
|
93
|
+
result.textFound !== undefined ||
|
|
94
|
+
(result.evaluateResults !== undefined && result.evaluateResults.length > 0) ||
|
|
95
|
+
!!result.bodyTextSnippet;
|
|
96
|
+
// Only waits / navigate / screenshot with no errors → weak smoke.
|
|
97
|
+
const onlyWeakSmoke = clean && !hadDomAssertion;
|
|
60
98
|
return typedOk({
|
|
61
99
|
ok: true,
|
|
62
100
|
clean,
|
|
@@ -66,7 +104,18 @@ export function createBrowserTool(deps = {}) {
|
|
|
66
104
|
pageErrors: result.pageErrors,
|
|
67
105
|
failedRequests: result.failedRequests,
|
|
68
106
|
...(result.selectorFound !== undefined ? { selectorFound: result.selectorFound } : {}),
|
|
107
|
+
...(result.evaluateResults ? { evaluateResults: result.evaluateResults } : {}),
|
|
108
|
+
...(result.textFound !== undefined ? { textFound: result.textFound } : {}),
|
|
109
|
+
...(result.bodyTextSnippet ? { bodyTextSnippet: result.bodyTextSnippet } : {}),
|
|
69
110
|
...(result.screenshotPath ? { screenshotPath: result.screenshotPath } : {}),
|
|
111
|
+
...(onlyWeakSmoke
|
|
112
|
+
? {
|
|
113
|
+
note: 'Weak smoke only: no selector/text/evaluate assertions. ' +
|
|
114
|
+
'No console/page errors is necessary but not sufficient to claim a logic fix. ' +
|
|
115
|
+
'Add waitForSelector, waitForText, or evaluate (DOM/read hooks) for stronger evidence.',
|
|
116
|
+
smokeStrength: 'weak',
|
|
117
|
+
}
|
|
118
|
+
: { smokeStrength: 'asserted' }),
|
|
70
119
|
});
|
|
71
120
|
},
|
|
72
121
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../../src/cli/browser/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,OAAO,EAAuB,MAAM,sCAAsC,CAAC;AACpF,OAAO,EAAE,eAAe,EAA6C,MAAM,aAAa,CAAC;AAEzF,MAAM,YAAY,GAAG,CAAC,CAAC,kBAAkB,CAAC,MAAM,EAAE;IAChD,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACnE,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;IACrF,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;IAClF,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../../src/cli/browser/tools.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/main.bundled.js
CHANGED
|
@@ -18431,7 +18431,8 @@ Earlier members' outputs are shared context. Build on them; do not re-derive or
|
|
|
18431
18431
|
- **Verify**: after non-trivial edits, run the project's tests/typecheck/build when available; fix failures you introduced.
|
|
18432
18432
|
- **Use project scripts**: prefer package.json / Makefile / existing tooling over ad-hoc commands.
|
|
18433
18433
|
- **Finish**: list the paths you wrote/edited and how to verify. If you wrote nothing, say so honestly \u2014 do not invent a done report.
|
|
18434
|
-
- **No spam**: never repeat the same paragraph or status line. One diagnosis, then tools (or one short final answer)
|
|
18434
|
+
- **No spam**: never repeat the same paragraph or status line. One diagnosis, then tools (or one short final answer).
|
|
18435
|
+
- **Browser smoke honesty**: with \`browser_check\`, prefer \`waitForSelector\` / \`waitForText\` / \`evaluate\` on DOM (or explicit test hooks). Do not claim a logic fix is verified from \u201Cno console errors after N seconds\u201D alone (\`smokeStrength: weak\`). ES modules keep symbols off \`window\` \u2014 do not loop on exposing globals; assert visible UI or inject \`evaluate\` on \`document\`.`
|
|
18435
18436
|
};
|
|
18436
18437
|
CLARIFICATION_PROTOCOL_MODULE = {
|
|
18437
18438
|
type: "custom",
|
|
@@ -22623,10 +22624,41 @@ async function loadPlaywright(cwd) {
|
|
|
22623
22624
|
return null;
|
|
22624
22625
|
}
|
|
22625
22626
|
}
|
|
22627
|
+
function serializeEvaluateValue(value) {
|
|
22628
|
+
try {
|
|
22629
|
+
const json2 = JSON.stringify(value, (_k, v) => {
|
|
22630
|
+
if (typeof v === "function") return "[Function]";
|
|
22631
|
+
if (typeof v === "bigint") return v.toString();
|
|
22632
|
+
if (v instanceof Error) return { name: v.name, message: v.message };
|
|
22633
|
+
return v;
|
|
22634
|
+
});
|
|
22635
|
+
if (json2 === void 0) return null;
|
|
22636
|
+
if (json2.length > MAX_EVAL_RESULT) {
|
|
22637
|
+
return {
|
|
22638
|
+
truncated: true,
|
|
22639
|
+
preview: json2.slice(0, MAX_EVAL_RESULT)
|
|
22640
|
+
};
|
|
22641
|
+
}
|
|
22642
|
+
return JSON.parse(json2);
|
|
22643
|
+
} catch (err) {
|
|
22644
|
+
return {
|
|
22645
|
+
error: `non-serializable: ${err instanceof Error ? err.message : String(err)}`
|
|
22646
|
+
};
|
|
22647
|
+
}
|
|
22648
|
+
}
|
|
22649
|
+
function wrapEvaluateExpression(expression) {
|
|
22650
|
+
const expr = expression.trim();
|
|
22651
|
+
if (!expr) return "return undefined";
|
|
22652
|
+
if (/\breturn\b/.test(expr) || expr.includes("\n")) {
|
|
22653
|
+
return expr;
|
|
22654
|
+
}
|
|
22655
|
+
return `return (${expr})`;
|
|
22656
|
+
}
|
|
22626
22657
|
async function runBrowserCheck(options, loader) {
|
|
22627
22658
|
const consoleErrors = [];
|
|
22628
22659
|
const pageErrors = [];
|
|
22629
22660
|
const failedRequests = [];
|
|
22661
|
+
const evaluateResults = [];
|
|
22630
22662
|
const base = { ok: false, consoleErrors, pageErrors, failedRequests };
|
|
22631
22663
|
const resolve = loader ?? (() => loadPlaywright(options.cwd ?? process.cwd()));
|
|
22632
22664
|
const pw = await resolve();
|
|
@@ -22638,6 +22670,7 @@ async function runBrowserCheck(options, loader) {
|
|
|
22638
22670
|
}
|
|
22639
22671
|
const timeout = options.timeoutMs ?? 15e3;
|
|
22640
22672
|
let browser;
|
|
22673
|
+
let textFound;
|
|
22641
22674
|
try {
|
|
22642
22675
|
browser = await pw.chromium.launch({ headless: true });
|
|
22643
22676
|
const page = await browser.newPage();
|
|
@@ -22664,6 +22697,63 @@ async function runBrowserCheck(options, loader) {
|
|
|
22664
22697
|
case "wait":
|
|
22665
22698
|
await page.waitForTimeout(action.ms);
|
|
22666
22699
|
break;
|
|
22700
|
+
case "press": {
|
|
22701
|
+
const key = action.key.trim();
|
|
22702
|
+
if (!key) break;
|
|
22703
|
+
if (page.keyboard && typeof page.keyboard.press === "function") {
|
|
22704
|
+
await page.keyboard.press(key, { timeout });
|
|
22705
|
+
} else {
|
|
22706
|
+
await page.evaluate(
|
|
22707
|
+
wrapEvaluateExpression(
|
|
22708
|
+
`document.dispatchEvent(new KeyboardEvent('keydown',{key:${JSON.stringify(key)},bubbles:true})); true`
|
|
22709
|
+
)
|
|
22710
|
+
);
|
|
22711
|
+
}
|
|
22712
|
+
break;
|
|
22713
|
+
}
|
|
22714
|
+
case "waitForText": {
|
|
22715
|
+
const needle = action.text;
|
|
22716
|
+
const t = action.timeoutMs ?? timeout;
|
|
22717
|
+
try {
|
|
22718
|
+
await page.waitForFunction(
|
|
22719
|
+
`return (document.body && (document.body.innerText || document.body.textContent || '')).includes(arguments[0])`,
|
|
22720
|
+
needle,
|
|
22721
|
+
{ timeout: t }
|
|
22722
|
+
);
|
|
22723
|
+
textFound = textFound === false ? false : true;
|
|
22724
|
+
} catch {
|
|
22725
|
+
textFound = false;
|
|
22726
|
+
}
|
|
22727
|
+
break;
|
|
22728
|
+
}
|
|
22729
|
+
case "evaluate": {
|
|
22730
|
+
const raw = action.expression ?? "";
|
|
22731
|
+
if (raw.length > MAX_EVAL_EXPR) {
|
|
22732
|
+
evaluateResults.push({
|
|
22733
|
+
expression: raw.slice(0, 120) + "\u2026",
|
|
22734
|
+
error: `expression too long (max ${MAX_EVAL_EXPR} chars)`
|
|
22735
|
+
});
|
|
22736
|
+
break;
|
|
22737
|
+
}
|
|
22738
|
+
if (!raw.trim()) {
|
|
22739
|
+
evaluateResults.push({ expression: raw, error: "empty expression" });
|
|
22740
|
+
break;
|
|
22741
|
+
}
|
|
22742
|
+
try {
|
|
22743
|
+
const wrapped = wrapEvaluateExpression(raw);
|
|
22744
|
+
const value = await page.evaluate(wrapped);
|
|
22745
|
+
evaluateResults.push({
|
|
22746
|
+
expression: raw.length > 200 ? raw.slice(0, 200) + "\u2026" : raw,
|
|
22747
|
+
value: serializeEvaluateValue(value)
|
|
22748
|
+
});
|
|
22749
|
+
} catch (err) {
|
|
22750
|
+
evaluateResults.push({
|
|
22751
|
+
expression: raw.length > 200 ? raw.slice(0, 200) + "\u2026" : raw,
|
|
22752
|
+
error: err instanceof Error ? err.message : String(err)
|
|
22753
|
+
});
|
|
22754
|
+
}
|
|
22755
|
+
break;
|
|
22756
|
+
}
|
|
22667
22757
|
default:
|
|
22668
22758
|
break;
|
|
22669
22759
|
}
|
|
@@ -22677,6 +22767,20 @@ async function runBrowserCheck(options, loader) {
|
|
|
22677
22767
|
selectorFound = false;
|
|
22678
22768
|
}
|
|
22679
22769
|
}
|
|
22770
|
+
let bodyTextSnippet;
|
|
22771
|
+
if (options.textSample) {
|
|
22772
|
+
try {
|
|
22773
|
+
const text = await page.evaluate(
|
|
22774
|
+
wrapEvaluateExpression(
|
|
22775
|
+
`(document.body?.innerText || document.body?.textContent || '').trim()`
|
|
22776
|
+
)
|
|
22777
|
+
);
|
|
22778
|
+
if (typeof text === "string" && text.length > 0) {
|
|
22779
|
+
bodyTextSnippet = text.length > MAX_BODY_SNIPPET ? text.slice(0, MAX_BODY_SNIPPET) + "\u2026" : text;
|
|
22780
|
+
}
|
|
22781
|
+
} catch {
|
|
22782
|
+
}
|
|
22783
|
+
}
|
|
22680
22784
|
let screenshotPath;
|
|
22681
22785
|
if (options.screenshotPath) {
|
|
22682
22786
|
try {
|
|
@@ -22694,6 +22798,9 @@ async function runBrowserCheck(options, loader) {
|
|
|
22694
22798
|
url: page.url(),
|
|
22695
22799
|
...title !== void 0 ? { title } : {},
|
|
22696
22800
|
...selectorFound !== void 0 ? { selectorFound } : {},
|
|
22801
|
+
...evaluateResults.length > 0 ? { evaluateResults } : {},
|
|
22802
|
+
...textFound !== void 0 ? { textFound } : {},
|
|
22803
|
+
...bodyTextSnippet !== void 0 ? { bodyTextSnippet } : {},
|
|
22697
22804
|
...screenshotPath ? { screenshotPath } : {}
|
|
22698
22805
|
};
|
|
22699
22806
|
} catch (err) {
|
|
@@ -22705,9 +22812,13 @@ async function runBrowserCheck(options, loader) {
|
|
|
22705
22812
|
}
|
|
22706
22813
|
}
|
|
22707
22814
|
}
|
|
22815
|
+
var MAX_EVAL_EXPR, MAX_EVAL_RESULT, MAX_BODY_SNIPPET;
|
|
22708
22816
|
var init_driver = __esm({
|
|
22709
22817
|
"src/cli/browser/driver.ts"() {
|
|
22710
22818
|
"use strict";
|
|
22819
|
+
MAX_EVAL_EXPR = 4e3;
|
|
22820
|
+
MAX_EVAL_RESULT = 8e3;
|
|
22821
|
+
MAX_BODY_SNIPPET = 2e3;
|
|
22711
22822
|
}
|
|
22712
22823
|
});
|
|
22713
22824
|
|
|
@@ -22717,15 +22828,16 @@ import os7 from "node:os";
|
|
|
22717
22828
|
function createBrowserTool(deps = {}) {
|
|
22718
22829
|
return {
|
|
22719
22830
|
name: "browser_check",
|
|
22720
|
-
description:
|
|
22831
|
+
description: "Open a URL in a headless browser to VERIFY a web change: optionally run click/fill/goto/wait/press/waitForText/evaluate actions, then report console errors, uncaught page exceptions, failed network requests, final title/URL, selector/text presence, evaluate results, and a screenshot path. Prefer DOM assertions (waitForSelector, waitForText, evaluate on document.querySelector) over window.* globals \u2014 ES modules and const keep symbols off window. Absence of console errors after a short wait is WEAK smoke only; assert UI state when claiming a fix is verified. Requires Playwright (optional dependency).",
|
|
22721
22832
|
permissions: ["network"],
|
|
22722
22833
|
// A browser launch + navigation can take a while.
|
|
22723
22834
|
timeoutMs: 6e4,
|
|
22724
22835
|
inputSchema: external_exports.object({
|
|
22725
22836
|
url: external_exports.string().min(1).describe("URL to open (e.g. http://localhost:3000)."),
|
|
22726
|
-
actions: external_exports.array(ActionSchema).optional().describe("Optional sequence of interactions before checking."),
|
|
22837
|
+
actions: external_exports.array(ActionSchema).optional().describe("Optional sequence of interactions / probes before checking."),
|
|
22727
22838
|
waitForSelector: external_exports.string().optional().describe("Assert this CSS selector is present after actions."),
|
|
22728
|
-
screenshot: external_exports.boolean().optional().describe("Save a screenshot (default true).")
|
|
22839
|
+
screenshot: external_exports.boolean().optional().describe("Save a screenshot (default true)."),
|
|
22840
|
+
textSample: external_exports.boolean().optional().describe("Include a short body.innerText snippet (HUD / Game Over text).")
|
|
22729
22841
|
}),
|
|
22730
22842
|
execute: async (args, ctx) => {
|
|
22731
22843
|
const a = args;
|
|
@@ -22737,14 +22849,17 @@ function createBrowserTool(deps = {}) {
|
|
|
22737
22849
|
cwd: ctx.cwd,
|
|
22738
22850
|
...a.actions ? { actions: a.actions } : {},
|
|
22739
22851
|
...a.waitForSelector ? { waitForSelector: a.waitForSelector } : {},
|
|
22740
|
-
...screenshotPath ? { screenshotPath } : {}
|
|
22852
|
+
...screenshotPath ? { screenshotPath } : {},
|
|
22853
|
+
...a.textSample ? { textSample: true } : {}
|
|
22741
22854
|
},
|
|
22742
22855
|
deps.loader
|
|
22743
22856
|
);
|
|
22744
22857
|
if (!result.ok) {
|
|
22745
22858
|
return typedOk({ ok: false, note: result.error ?? "browser check failed" });
|
|
22746
22859
|
}
|
|
22747
|
-
const clean = result.consoleErrors.length === 0 && result.pageErrors.length === 0 && result.failedRequests.length === 0 && result.selectorFound !== false;
|
|
22860
|
+
const clean = result.consoleErrors.length === 0 && result.pageErrors.length === 0 && result.failedRequests.length === 0 && result.selectorFound !== false && result.textFound !== false;
|
|
22861
|
+
const hadDomAssertion = result.selectorFound !== void 0 || result.textFound !== void 0 || result.evaluateResults !== void 0 && result.evaluateResults.length > 0 || !!result.bodyTextSnippet;
|
|
22862
|
+
const onlyWeakSmoke = clean && !hadDomAssertion;
|
|
22748
22863
|
return typedOk({
|
|
22749
22864
|
ok: true,
|
|
22750
22865
|
clean,
|
|
@@ -22754,7 +22869,14 @@ function createBrowserTool(deps = {}) {
|
|
|
22754
22869
|
pageErrors: result.pageErrors,
|
|
22755
22870
|
failedRequests: result.failedRequests,
|
|
22756
22871
|
...result.selectorFound !== void 0 ? { selectorFound: result.selectorFound } : {},
|
|
22757
|
-
...result.
|
|
22872
|
+
...result.evaluateResults ? { evaluateResults: result.evaluateResults } : {},
|
|
22873
|
+
...result.textFound !== void 0 ? { textFound: result.textFound } : {},
|
|
22874
|
+
...result.bodyTextSnippet ? { bodyTextSnippet: result.bodyTextSnippet } : {},
|
|
22875
|
+
...result.screenshotPath ? { screenshotPath: result.screenshotPath } : {},
|
|
22876
|
+
...onlyWeakSmoke ? {
|
|
22877
|
+
note: "Weak smoke only: no selector/text/evaluate assertions. No console/page errors is necessary but not sufficient to claim a logic fix. Add waitForSelector, waitForText, or evaluate (DOM/read hooks) for stronger evidence.",
|
|
22878
|
+
smokeStrength: "weak"
|
|
22879
|
+
} : { smokeStrength: "asserted" }
|
|
22758
22880
|
});
|
|
22759
22881
|
}
|
|
22760
22882
|
};
|
|
@@ -22770,7 +22892,22 @@ var init_tools5 = __esm({
|
|
|
22770
22892
|
external_exports.object({ type: external_exports.literal("click"), selector: external_exports.string().min(1) }),
|
|
22771
22893
|
external_exports.object({ type: external_exports.literal("fill"), selector: external_exports.string().min(1), value: external_exports.string() }),
|
|
22772
22894
|
external_exports.object({ type: external_exports.literal("wait"), ms: external_exports.number().int().positive().max(3e4) }),
|
|
22773
|
-
external_exports.object({ type: external_exports.literal("goto"), url: external_exports.string().min(1) })
|
|
22895
|
+
external_exports.object({ type: external_exports.literal("goto"), url: external_exports.string().min(1) }),
|
|
22896
|
+
external_exports.object({
|
|
22897
|
+
type: external_exports.literal("evaluate"),
|
|
22898
|
+
expression: external_exports.string().min(1).max(4e3).describe(
|
|
22899
|
+
"JS expression or statements run in the page (page.evaluate). Prefer DOM reads (document.querySelector\u2026), not assuming window.game exists. Return JSON-safe values."
|
|
22900
|
+
)
|
|
22901
|
+
}),
|
|
22902
|
+
external_exports.object({
|
|
22903
|
+
type: external_exports.literal("press"),
|
|
22904
|
+
key: external_exports.string().min(1).describe('Playwright key name, e.g. "Space", "KeyW", "ArrowLeft", "Enter".')
|
|
22905
|
+
}),
|
|
22906
|
+
external_exports.object({
|
|
22907
|
+
type: external_exports.literal("waitForText"),
|
|
22908
|
+
text: external_exports.string().min(1).describe("Substring that must appear in document body text."),
|
|
22909
|
+
timeoutMs: external_exports.number().int().positive().max(6e4).optional()
|
|
22910
|
+
})
|
|
22774
22911
|
]);
|
|
22775
22912
|
}
|
|
22776
22913
|
});
|