scenescout 3.23.3 → 3.23.4
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/CHANGELOG.md +11 -0
- package/README.md +20 -3
- package/dist/browsers.js +7 -6
- package/dist/ci-run.js +3 -2
- package/dist/cli.js +2 -1
- package/dist/clients.js +3 -3
- package/dist/code-routes.js +2 -2
- package/dist/commands.js +4 -4
- package/dist/engine/args.js +21 -0
- package/dist/engine/bench.js +1 -1
- package/dist/engine/brief.js +1 -1
- package/dist/engine/browser.js +59 -15
- package/dist/engine/capture.js +4 -4
- package/dist/engine/check-replay.js +82 -8
- package/dist/engine/check-report.js +8 -8
- package/dist/engine/check.js +15 -15
- package/dist/engine/ci.js +20 -20
- package/dist/engine/claims.js +4 -4
- package/dist/engine/collector.js +7 -7
- package/dist/engine/crawl.js +1 -1
- package/dist/engine/dedup.js +4 -4
- package/dist/engine/design.js +7 -7
- package/dist/engine/expiry.js +1 -1
- package/dist/engine/export.js +7 -7
- package/dist/engine/flow.js +10 -10
- package/dist/engine/forms.js +1 -1
- package/dist/engine/from-run.js +6 -6
- package/dist/engine/injection.js +1 -1
- package/dist/engine/lane.js +1 -1
- package/dist/engine/limits.js +2 -2
- package/dist/engine/live.js +30 -0
- package/dist/engine/memory.js +24 -7
- package/dist/engine/open.js +1 -1
- package/dist/engine/oracles.js +2 -2
- package/dist/engine/plain.js +1 -1
- package/dist/engine/png.js +2 -2
- package/dist/engine/policy.js +2 -2
- package/dist/engine/postmessage.js +7 -7
- package/dist/engine/project-folder.js +1 -1
- package/dist/engine/provider.js +1 -1
- package/dist/engine/replay.js +3 -1
- package/dist/engine/report.js +3 -3
- package/dist/engine/sarif.js +2 -2
- package/dist/engine/script-literal.js +11 -0
- package/dist/engine/sensitive.js +254 -0
- package/dist/engine/signed-in.js +2 -2
- package/dist/engine/status-pane-page.js +4 -3
- package/dist/engine/task.js +1 -1
- package/dist/engine/teardown.js +2 -0
- package/dist/engine/tickets.js +6 -6
- package/dist/engine/verify.js +3 -3
- package/dist/export-run.js +1 -1
- package/dist/installer.js +4 -4
- package/dist/login-run.js +2 -2
- package/dist/mcp-server.js +6 -9
- package/dist/playbook.js +1 -1
- package/package.json +2 -2
package/dist/engine/verify.js
CHANGED
|
@@ -26,7 +26,7 @@ import { normalizePath } from "./fingerprint.js";
|
|
|
26
26
|
import { failingSignatures, isWorthALook } from "./memory.js";
|
|
27
27
|
/** What a re-test found. */
|
|
28
28
|
export const VERDICTS = ["gone", "present", "changed"];
|
|
29
|
-
|
|
29
|
+
const VERDICT_MEANING = {
|
|
30
30
|
gone: "re-tested and the evidence no longer reproduces — resolved",
|
|
31
31
|
present: "re-tested and still reproduces exactly as filed",
|
|
32
32
|
changed: "re-tested and something is different — still broken, but not as described",
|
|
@@ -172,7 +172,7 @@ const LOOK_ONLY = new Set([
|
|
|
172
172
|
"record:full",
|
|
173
173
|
]);
|
|
174
174
|
/** How the finding's page is reached again, or null when a check cannot re-test it deterministically. */
|
|
175
|
-
|
|
175
|
+
function loadRetest(item) {
|
|
176
176
|
const signatures = [...failingSignatures(item.evidence)];
|
|
177
177
|
// A POST that failed is not re-sent by loading a page, and a check would refuse to send it anyway.
|
|
178
178
|
if (signatures.length === 0 || signatures.some((s) => !s.startsWith("GET ")))
|
|
@@ -196,7 +196,7 @@ export function loadRetest(item) {
|
|
|
196
196
|
return null;
|
|
197
197
|
return { item, path: `${url.pathname}${url.search}`, signatures };
|
|
198
198
|
}
|
|
199
|
-
|
|
199
|
+
const RETEST_VERDICTS = ["reproduces", "possibly-fixed", "not-reached"];
|
|
200
200
|
/**
|
|
201
201
|
* Each re-testable finding against the page load that re-tests it.
|
|
202
202
|
*
|
package/dist/export-run.js
CHANGED
|
@@ -20,7 +20,7 @@ import { resolveFrame } from "./engine/replay.js";
|
|
|
20
20
|
* the request before it failed (a create that timed out or got a 5xx), so
|
|
21
21
|
* whether the issue exists has to be asked again rather than assumed.
|
|
22
22
|
*/
|
|
23
|
-
|
|
23
|
+
class ExportError extends Error {
|
|
24
24
|
uncertain;
|
|
25
25
|
constructor(message, uncertain = false) {
|
|
26
26
|
super(message);
|
package/dist/installer.js
CHANGED
|
@@ -11,7 +11,7 @@ import fs from "node:fs";
|
|
|
11
11
|
import { createRequire } from "node:module";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
import { APPROX_DISK_MB, BROWSER_ENGINES, browserPresence, engineOf, playwrightInstallArgs, } from "./browsers.js";
|
|
14
|
-
|
|
14
|
+
const SKILL_NAME = "scenescout";
|
|
15
15
|
export const MCP_NAME = "scenescout";
|
|
16
16
|
/** Names this tool's skill and MCP server had before renames; cleaned up on install so the old slash command and a duplicate tool set do not linger. */
|
|
17
17
|
const LEGACY_SKILL_NAMES = ["frontend-tester", "scenecraft"];
|
|
@@ -242,7 +242,7 @@ export function launchCommand(opts) {
|
|
|
242
242
|
// resolves on PATH is better than an absolute path to nothing.
|
|
243
243
|
return [fs.existsSync(npx) ? npx : "npx", ...NPX_SERVE_ARGS];
|
|
244
244
|
}
|
|
245
|
-
|
|
245
|
+
function mcpAddArgs(launch) {
|
|
246
246
|
return ["mcp", "add", "--scope", "user", MCP_NAME, "--", ...launch];
|
|
247
247
|
}
|
|
248
248
|
export function manualRegisterCommand(launch) {
|
|
@@ -340,7 +340,7 @@ function removeLegacyRegistrations(opts) {
|
|
|
340
340
|
* symlink, and on a case-insensitive filesystem through a differently-cased
|
|
341
341
|
* path. Comparing strings reported both as "pointing elsewhere".
|
|
342
342
|
*/
|
|
343
|
-
|
|
343
|
+
function samePath(a, b) {
|
|
344
344
|
const real = (p) => {
|
|
345
345
|
try {
|
|
346
346
|
return fs.realpathSync.native(p);
|
|
@@ -568,7 +568,7 @@ export function installerFailure(stderr) {
|
|
|
568
568
|
: null;
|
|
569
569
|
}
|
|
570
570
|
/** How long a browser download may run before it is stopped and reported as failed. */
|
|
571
|
-
|
|
571
|
+
const BROWSER_DOWNLOAD_TIMEOUT_MS = 15 * 60_000;
|
|
572
572
|
/**
|
|
573
573
|
* The environment Playwright's installer runs in. A host that runs this server
|
|
574
574
|
* inside its own Electron binary (a desktop app's bundled runtime) makes
|
package/dist/login-run.js
CHANGED
|
@@ -104,7 +104,7 @@ const NOT_SAVED = {
|
|
|
104
104
|
"timed-out": "The sign-in did not finish in time, so the window was closed and nothing was saved.",
|
|
105
105
|
};
|
|
106
106
|
/** Open the window a person signs in in. Throws a sentence when the browser cannot start or the page cannot load. */
|
|
107
|
-
|
|
107
|
+
async function openLoginWindow(options, opts = {}) {
|
|
108
108
|
const engine = options.browser ?? defaultEngine(process.env);
|
|
109
109
|
const types = { chromium, firefox, webkit };
|
|
110
110
|
// Checked before a window opens: a limit out of bounds is a sentence, not a browser left behind.
|
|
@@ -199,7 +199,7 @@ async function lookAt(win, watch) {
|
|
|
199
199
|
* when), the window closes, or `signal` aborts. Resolves with how it ended;
|
|
200
200
|
* the caller saves and closes.
|
|
201
201
|
*/
|
|
202
|
-
|
|
202
|
+
async function watchForSignIn(win, options, opts = {}) {
|
|
203
203
|
let closed = false;
|
|
204
204
|
void win.closed.then(() => {
|
|
205
205
|
closed = true;
|
package/dist/mcp-server.js
CHANGED
|
@@ -52,7 +52,7 @@ import { decodedEntitiesNote, ignoredConventionsNote, LANE_CONVENTION_MAX, LANE_
|
|
|
52
52
|
import { MAX_UNFILED_NAMED, unfiledDefects } from "./engine/calibration.js";
|
|
53
53
|
import { CallWork, SessionQueue, watchdogCap, withWatchdog } from "./engine/dispatch.js";
|
|
54
54
|
import { FIXTURE_KINDS } from "./engine/fixtures.js";
|
|
55
|
-
import { feedForSession, LIVE_ENV, writeStatusFile, LIVE_TOKEN_FILE, liveEngines, liveTokenFileName, pidAlive, statusFileName, LiveServer, StatusBoard, } from "./engine/live.js";
|
|
55
|
+
import { feedForSession, LIVE_ENV, writeLiveToken, writeStatusFile, LIVE_TOKEN_FILE, liveEngines, liveTokenFileName, pidAlive, statusFileName, LiveServer, StatusBoard, } from "./engine/live.js";
|
|
56
56
|
import { liveViewUrl, MCP_APP_MIME, paneData, paneText, STATUS_PANE_URI, STATUS_POLL_TOOL, STATUS_TOOL, } from "./engine/status-pane.js";
|
|
57
57
|
import { statusPanePage } from "./engine/status-pane-page.js";
|
|
58
58
|
import { formatBriefs, MAX_LANES, planLanes, replayBriefs } from "./engine/brief.js";
|
|
@@ -309,17 +309,13 @@ function publishLiveToken(dir) {
|
|
|
309
309
|
// Named for this process: two engines on one project would otherwise hand
|
|
310
310
|
// `watch` one token for two ports, and whichever wrote last would win.
|
|
311
311
|
const file = path.join(dir, liveTokenFileName(process.pid));
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
// The shared name stays too, for a `watch` from before per-pid files.
|
|
315
|
-
.then(() => fs.promises.writeFile(path.join(dir, LIVE_TOKEN_FILE), liveAddress.token, { mode: 0o600 }))
|
|
316
|
-
// `mode` applies only when the file is created; a leftover one keeps its old bits.
|
|
317
|
-
.then(() => fs.promises.chmod(file, 0o600))
|
|
312
|
+
// The shared name is written too, for a `watch` from before per-pid files.
|
|
313
|
+
const write = writeLiveToken(dir, process.pid, liveAddress.token)
|
|
318
314
|
.then(() => {
|
|
319
315
|
liveDirs.add(dir);
|
|
320
316
|
})
|
|
321
317
|
.catch((err) => {
|
|
322
|
-
//
|
|
318
|
+
// Whatever was written before the failure is taken back: nothing is left to remove at shutdown.
|
|
323
319
|
void fs.promises.rm(file, { force: true }).catch(() => { });
|
|
324
320
|
if (!liveTokenWarned)
|
|
325
321
|
console.error(`[scenescout] could not write the live view's token file in ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -553,6 +549,7 @@ function serializedControl(fn) {
|
|
|
553
549
|
// The SDK passes the request's extra (progress token, notifications) after the arguments; it is kept for the call, as requestExtra.
|
|
554
550
|
const call = () => requestExtra.run(asRequestExtra(args[1]), () => fn(...args));
|
|
555
551
|
const run = controlChain.then(call, call);
|
|
552
|
+
// The caller gets `run` and its error; the chain only needs to know the call has finished, so a failure must not block the next control call.
|
|
556
553
|
controlChain = run.catch(() => { });
|
|
557
554
|
return run;
|
|
558
555
|
};
|
|
@@ -1565,7 +1562,7 @@ server.registerTool("scout_click", {
|
|
|
1565
1562
|
}
|
|
1566
1563
|
}));
|
|
1567
1564
|
server.registerTool("scout_type", {
|
|
1568
|
-
description: "Type into a text input/textarea/composer by ref, the way a real user does: if the field already holds content (e.g. an @-mention chip a menu click inserted), the text is APPENDED at the end — preserving that content — and the result reports what was already there (a separating space is added only at a word-to-word boundary). Pass replace=true to clear the field first (correcting a previous entry); an empty textValue always clears. Appending fires input events but not keydown, so keydown-driven triggers (slash/mention menus) will not react to appended text. Use for both valid values and boundary/fuzz values (empty, very long, unicode, script tags).",
|
|
1565
|
+
description: "Type into a text input/textarea/composer by ref, the way a real user does: if the field already holds content (e.g. an @-mention chip a menu click inserted), the text is APPENDED at the end — preserving that content — and the result reports what was already there (a separating space is added only at a word-to-word boundary). Pass replace=true to clear the field first (correcting a previous entry); an empty textValue always clears. Appending fires input events but not keydown, so keydown-driven triggers (slash/mention menus) will not react to appended text. Use for both valid values and boundary/fuzz values (empty, very long, unicode, script tags). A value typed into a secret field (a password, a one-time code, a card number, a field labelled secret or token) is masked in the result and the log, as is what the field held.",
|
|
1569
1566
|
inputSchema: {
|
|
1570
1567
|
ref: z.string().describe("Element ref, e.g. e12"),
|
|
1571
1568
|
textValue: z.string().optional().describe("Text to type"),
|
package/dist/playbook.js
CHANGED
|
@@ -23,7 +23,7 @@ export const SERVER_INSTRUCTIONS = `SceneScout explores a running web app in a r
|
|
|
23
23
|
`If this client offers a SceneScout skill, load that. Otherwise call ${PLAYBOOK_TOOL} once, before the first scout_attach in a conversation, and follow what it returns. ` +
|
|
24
24
|
`They are the same text, so never read both. ` +
|
|
25
25
|
`Never choose mode="destructive" yourself: that needs the user's explicit opt-in.`;
|
|
26
|
-
|
|
26
|
+
const LEVELS = ["minimal", "medium", "extensive"];
|
|
27
27
|
/** What the `explore` prompt accepts, as MCP lists it. Every argument is optional. */
|
|
28
28
|
export const EXPLORE_PROMPT_ARGUMENTS = [
|
|
29
29
|
{ name: "url", description: "URL of the running app, e.g. http://localhost:3000", required: false },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scenescout",
|
|
3
|
-
"version": "3.23.
|
|
3
|
+
"version": "3.23.4",
|
|
4
4
|
"description": "SceneScout — exploratory UI testing for AI coding agents. An MCP server that gives any agent (Claude Code, Cursor, VS Code Copilot, Codex, Gemini CLI and others) a structured view of a running web app, always-on oracles, a network-level write policy, memory across runs and a gap-checked report.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "brunoboto96",
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"devDependencies": {
|
|
120
120
|
"@changesets/cli": "3.0.3",
|
|
121
121
|
"@types/node": "^20.19.0",
|
|
122
|
-
"prettier": "3.9.
|
|
122
|
+
"prettier": "3.9.10",
|
|
123
123
|
"tsx": "^4.23.15",
|
|
124
124
|
"typescript": "^5.9.3",
|
|
125
125
|
"yaml": "^2.9.1"
|