pi-agent-browser-native 0.7.0 → 0.8.0
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 +21 -0
- package/README.md +6 -6
- package/dist/extensions/agent-browser/index.js +0 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +7 -80
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +4 -4
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +9 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +26 -9
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +30 -3
- package/dist/extensions/agent-browser/lib/playbook.js +1 -1
- package/dist/extensions/agent-browser/lib/runtime.js +50 -46
- package/dist/extensions/agent-browser/lib/session-page-state.js +17 -7
- package/dist/extensions/agent-browser/lib/tool-surface.js +9 -3
- package/dist/extensions/agent-browser/lib/web-search.js +5 -3
- package/docs/ARCHITECTURE.md +4 -4
- package/docs/COMMAND_REFERENCE.md +5 -3
- package/docs/RELEASE.md +7 -9
- package/docs/SUPPORT_MATRIX.md +6 -6
- package/docs/TOOL_CONTRACT.md +6 -4
- package/docs/platform-smoke.md +2 -2
- package/package.json +10 -15
- package/platform-smoke.config.mjs +1 -1
- package/scripts/doctor.mjs +1 -1
- package/scripts/platform-smoke/doctor.mjs +1 -1
- package/scripts/platform-smoke/targets.mjs +2 -2
|
@@ -120,50 +120,41 @@ function redactLooseUrlUserinfo(text) {
|
|
|
120
120
|
function redactLooseUrlMatches(text) {
|
|
121
121
|
return text.replace(/\b[A-Za-z][A-Za-z0-9+.-]*:\/\/[^\s"'`<>\])]+/g, (match) => redactUrlToken(match));
|
|
122
122
|
}
|
|
123
|
-
function
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
123
|
+
function createBalancedJsonEndFinder(text) {
|
|
124
|
+
let boundaries;
|
|
125
|
+
return (startIndex) => {
|
|
126
|
+
const opener = text[startIndex];
|
|
127
|
+
if (opener !== "{" && opener !== "[")
|
|
128
|
+
return undefined;
|
|
129
|
+
if (!boundaries) {
|
|
130
|
+
// Resolve each suffix once: skip strings and balanced groups to its first unmatched closer.
|
|
131
|
+
boundaries = new Int32Array(text.length + 1).fill(-1);
|
|
132
|
+
let quoteAtNext = -1;
|
|
133
|
+
let quoteAfterNext = -1;
|
|
134
|
+
for (let index = text.length - 1; index >= 0; index -= 1) {
|
|
135
|
+
const char = text[index];
|
|
136
|
+
if (char === "}" || char === "]") {
|
|
137
|
+
boundaries[index] = index;
|
|
138
|
+
}
|
|
139
|
+
else if (char === '"') {
|
|
140
|
+
boundaries[index] = quoteAtNext < 0 ? -1 : boundaries[quoteAtNext + 1];
|
|
141
|
+
}
|
|
142
|
+
else if (char === "{" || char === "[") {
|
|
143
|
+
const end = boundaries[index + 1];
|
|
144
|
+
boundaries[index] = end >= 0 && text[end] === (char === "{" ? "}" : "]") ? boundaries[end + 1] : -1;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
boundaries[index] = boundaries[index + 1];
|
|
148
|
+
}
|
|
149
|
+
// Inside a string, backslash skips one character; outside, it is ordinary text.
|
|
150
|
+
const quote = char === '"' ? index : char === "\\" ? quoteAfterNext : quoteAtNext;
|
|
151
|
+
quoteAfterNext = quoteAtNext;
|
|
152
|
+
quoteAtNext = quote;
|
|
144
153
|
}
|
|
145
|
-
continue;
|
|
146
154
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
if (char === "{") {
|
|
152
|
-
stack.push("}");
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
if (char === "[") {
|
|
156
|
-
stack.push("]");
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
if (char === "}" || char === "]") {
|
|
160
|
-
if (stack.pop() !== char)
|
|
161
|
-
return undefined;
|
|
162
|
-
if (stack.length === 0)
|
|
163
|
-
return index;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return undefined;
|
|
155
|
+
const end = boundaries[startIndex + 1];
|
|
156
|
+
return end >= 0 && text[end] === (opener === "{" ? "}" : "]") ? end : undefined;
|
|
157
|
+
};
|
|
167
158
|
}
|
|
168
159
|
function redactSerializedJson(text) {
|
|
169
160
|
// Validate grammar only; rebuilding parsed values loses duplicates and numeric spelling.
|
|
@@ -175,6 +166,7 @@ function redactSerializedJson(text) {
|
|
|
175
166
|
}
|
|
176
167
|
let output = "";
|
|
177
168
|
let cursor = 0;
|
|
169
|
+
const findEnd = createBalancedJsonEndFinder(text);
|
|
178
170
|
const strings = /"(?:\\.|[^"\\])*"/g;
|
|
179
171
|
let match;
|
|
180
172
|
while ((match = strings.exec(text)) !== null) {
|
|
@@ -189,7 +181,7 @@ function redactSerializedJson(text) {
|
|
|
189
181
|
if (!separator || !isSensitiveFieldName(value))
|
|
190
182
|
continue;
|
|
191
183
|
const valueStart = end + separator[0].length;
|
|
192
|
-
let valueEnd =
|
|
184
|
+
let valueEnd = findEnd(valueStart);
|
|
193
185
|
if (valueEnd !== undefined) {
|
|
194
186
|
valueEnd += 1;
|
|
195
187
|
}
|
|
@@ -214,6 +206,7 @@ function redactSerializedJson(text) {
|
|
|
214
206
|
function redactEmbeddedStructuredText(text) {
|
|
215
207
|
let output = "";
|
|
216
208
|
let cursor = 0;
|
|
209
|
+
const findEnd = createBalancedJsonEndFinder(text);
|
|
217
210
|
while (cursor < text.length) {
|
|
218
211
|
const char = text[cursor];
|
|
219
212
|
if (char !== "{" && char !== "[") {
|
|
@@ -221,7 +214,7 @@ function redactEmbeddedStructuredText(text) {
|
|
|
221
214
|
cursor += 1;
|
|
222
215
|
continue;
|
|
223
216
|
}
|
|
224
|
-
const endIndex =
|
|
217
|
+
const endIndex = findEnd(cursor);
|
|
225
218
|
if (endIndex === undefined) {
|
|
226
219
|
output += char;
|
|
227
220
|
cursor += 1;
|
|
@@ -268,14 +261,25 @@ export function isSensitiveFieldName(key) {
|
|
|
268
261
|
function isEnvSecretAssignmentKey(key) {
|
|
269
262
|
if (!isSensitiveFieldName(key))
|
|
270
263
|
return false;
|
|
271
|
-
if (key.includes("_") || key.includes("-") || key === key.toUpperCase())
|
|
264
|
+
if (key === "password" || key.includes("_") || key.includes("-") || key === key.toUpperCase())
|
|
272
265
|
return true;
|
|
273
266
|
return /(?:apiKey|ApiKey|privateKey|PrivateKey|databaseUrl|DatabaseUrl|dbUrl|DbUrl|connectionString|ConnectionString|mongoUri|MongoUri|mongodbUri|MongodbUri|mongoDbUri|MongoDbUri|redisUrl|RedisUrl|Token|Secret|Password|Credential|Credentials)$/.test(key);
|
|
274
267
|
}
|
|
275
268
|
function redactEnvSecretAssignments(text) {
|
|
276
|
-
return text.replace(ENV_SECRET_ASSIGNMENT_PATTERN, (match, prefix, key) => {
|
|
269
|
+
return text.replace(ENV_SECRET_ASSIGNMENT_PATTERN, (match, prefix, key, _separator, offset) => {
|
|
277
270
|
if (!isEnvSecretAssignmentKey(key))
|
|
278
271
|
return match;
|
|
272
|
+
if (key === "password") {
|
|
273
|
+
// The loose matcher cannot safely distinguish inline punctuation from password characters.
|
|
274
|
+
const end = offset + match.length;
|
|
275
|
+
if ((offset > 0 && text[offset - 1] !== "\n" && text[offset - 1] !== "\r")
|
|
276
|
+
|| (end < text.length && text[end] !== "\n" && text[end] !== "\r")
|
|
277
|
+
|| /^(?:\[REDACTED\]|%5Bredacted%5D)/i.test(match.slice(prefix.length)))
|
|
278
|
+
return match;
|
|
279
|
+
const value = match.slice(prefix.length);
|
|
280
|
+
const boundary = value[0] === '"' || value[0] === "'" ? -1 : value.search(/[&#](?=[A-Za-z_][A-Za-z0-9_-]*\s*=)/);
|
|
281
|
+
return `${prefix}[REDACTED]${boundary < 0 ? "" : value.slice(boundary)}`;
|
|
282
|
+
}
|
|
279
283
|
return `${prefix}[REDACTED]`;
|
|
280
284
|
});
|
|
281
285
|
}
|
|
@@ -2,7 +2,7 @@ import { extractUpstreamCommandTokens } from "./argv-descriptor.js";
|
|
|
2
2
|
import { getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace } from "./argv-grammar.js";
|
|
3
3
|
import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "./batch-lifecycle.js";
|
|
4
4
|
import { getBrowserResultMessage } from "./browser-transcript.js";
|
|
5
|
-
import { isCloseAllCommand, isCloseCommand, isReadOnlyDiagnosticSessionTargetCommand, isRecordPageTransitionCommand, isUnverifiedPageTransitionCommand, isWebMcpPageMutationCommand, isWindowOrDiffPageTransitionCommand } from "./command-taxonomy.js";
|
|
5
|
+
import { isCloseAllCommand, isCloseCommand, isOpenNavigationCommand, isReadOnlyDiagnosticSessionTargetCommand, isRecordPageTransitionCommand, isUnverifiedPageTransitionCommand, isWebMcpPageMutationCommand, isWindowOrDiffPageTransitionCommand } from "./command-taxonomy.js";
|
|
6
6
|
import { isRecord } from "./parsing.js";
|
|
7
7
|
import { findReadConfirmation as findPendingReadConfirmation, parseReadConfirmation } from "./read-confirmation.js";
|
|
8
8
|
import { getEditableRefEvidence } from "./results/editable-ref-evidence.js";
|
|
@@ -31,7 +31,7 @@ export function normalizeSessionTabTarget(target) {
|
|
|
31
31
|
return undefined;
|
|
32
32
|
}
|
|
33
33
|
const title = target.title?.trim();
|
|
34
|
-
return { title: title && title.length > 0 ? title : undefined, url };
|
|
34
|
+
return { ...(target.targetId?.trim() ? { targetId: target.targetId.trim() } : {}), title: title && title.length > 0 ? title : undefined, url };
|
|
35
35
|
}
|
|
36
36
|
export function isAboutBlankUrl(url) {
|
|
37
37
|
return normalizeComparableUrl(url) === "about:blank";
|
|
@@ -64,6 +64,7 @@ function extractStringResultField(data, fieldName) {
|
|
|
64
64
|
}
|
|
65
65
|
export function extractSessionTabTargetFromData(data) {
|
|
66
66
|
const directTarget = normalizeSessionTabTarget({
|
|
67
|
+
targetId: isRecord(data) && typeof data.targetId === "string" ? data.targetId : undefined,
|
|
67
68
|
title: extractStringResultField(data, "title"),
|
|
68
69
|
url: extractStringResultField(data, "url"),
|
|
69
70
|
});
|
|
@@ -94,8 +95,13 @@ export function extractSessionTabTargetFromBatchResults(data) {
|
|
|
94
95
|
for (const item of data) {
|
|
95
96
|
if (!isRecord(item))
|
|
96
97
|
continue;
|
|
97
|
-
|
|
98
|
-
if (
|
|
98
|
+
// Only this row's native identity can describe the active tab after it ran.
|
|
99
|
+
if (currentTarget?.targetId)
|
|
100
|
+
currentTarget = normalizeSessionTabTarget({ title: currentTarget.title, url: currentTarget.url });
|
|
101
|
+
const commandTokens = extractUpstreamCommandTokens(extractBatchResultCommand(item));
|
|
102
|
+
const [name, subcommand] = commandTokens;
|
|
103
|
+
if (isOpenNavigationCommand(name) || isUnverifiedPageTransitionCommand(name, subcommand)
|
|
104
|
+
|| (name === "click" && commandTokens.includes("--new-tab"))) {
|
|
99
105
|
currentTarget = undefined;
|
|
100
106
|
pendingTitle = undefined;
|
|
101
107
|
}
|
|
@@ -159,6 +165,7 @@ function getRestoredSessionTabTarget(details, command, subcommand) {
|
|
|
159
165
|
}
|
|
160
166
|
const storedTarget = isRecord(details.sessionTabTarget)
|
|
161
167
|
? normalizeSessionTabTarget({
|
|
168
|
+
targetId: typeof details.sessionTabTarget.targetId === "string" ? details.sessionTabTarget.targetId : undefined,
|
|
162
169
|
title: typeof details.sessionTabTarget.title === "string" ? details.sessionTabTarget.title : undefined,
|
|
163
170
|
url: typeof details.sessionTabTarget.url === "string" ? details.sessionTabTarget.url : undefined,
|
|
164
171
|
})
|
|
@@ -166,13 +173,16 @@ function getRestoredSessionTabTarget(details, command, subcommand) {
|
|
|
166
173
|
if (command !== "batch") {
|
|
167
174
|
return storedTarget;
|
|
168
175
|
}
|
|
176
|
+
if (isRecord(details.compiledNetworkSourceLookup) || batchContainsOnlyReadOnlyDiagnosticTargets(details.data)) {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
// The stored target includes post-batch live verification and native tab identity.
|
|
180
|
+
if (storedTarget || details.sessionTabTargetUnknown === true)
|
|
181
|
+
return storedTarget;
|
|
169
182
|
const batchTarget = extractSessionTabTargetFromBatchResults(details.data);
|
|
170
183
|
if (batchTarget) {
|
|
171
184
|
return batchTarget;
|
|
172
185
|
}
|
|
173
|
-
if (isRecord(details.compiledNetworkSourceLookup) || batchContainsOnlyReadOnlyDiagnosticTargets(details.data)) {
|
|
174
|
-
return undefined;
|
|
175
|
-
}
|
|
176
186
|
return storedTarget;
|
|
177
187
|
}
|
|
178
188
|
function extractRefSnapshotRefs(data) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
2
|
+
import { formatAgentBrowserRenderCall } from "./pi-tool-rendering.js";
|
|
2
3
|
import { AGENT_BROWSER_PARAMS, AGENT_BROWSER_CODE_PARAMS, AGENT_BROWSER_ACTION_PARAMS, AGENT_BROWSER_QA_PARAMS, AGENT_BROWSER_ELECTRON_PARAMS, AGENT_BROWSER_SOURCE_PARAMS, AGENT_BROWSER_NETWORK_SOURCE_PARAMS, AGENT_BROWSER_TOOLS_PARAMS, } from "./input-modes/params.js";
|
|
3
4
|
import { ADVANCED_TOOL_PROMPT_GUIDELINES, buildToolPromptGuidelines } from "./playbook.js";
|
|
4
5
|
export const AGENT_BROWSER_TOOL_INVENTORY = {
|
|
@@ -29,10 +30,13 @@ export function registerAgentBrowserToolSurface(pi, options) {
|
|
|
29
30
|
description: "Run fresh JavaScript against a persistent browser. await browser({args,stdin?,timeoutMs?}) returns success/data/error/nextActions and imageObservations; emit(selected JSON) and emitImage(image handle) explicitly choose output. No host APIs or imports. Use native batch for fixed sequences.",
|
|
30
31
|
promptSnippet: "Branch, loop, and aggregate browser observations with explicit JSON/image output.",
|
|
31
32
|
parameters: AGENT_BROWSER_CODE_PARAMS,
|
|
32
|
-
constrainedSampling: { type: "json_schema", strict: "prefer" },
|
|
33
33
|
execute: options.executeCode,
|
|
34
34
|
executionMode: options.executionMode,
|
|
35
|
-
renderCall
|
|
35
|
+
renderCall(args, theme, context) {
|
|
36
|
+
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
37
|
+
text.setText(formatAgentBrowserRenderCall(args, theme, context.expanded));
|
|
38
|
+
return text;
|
|
39
|
+
},
|
|
36
40
|
renderResult: options.renderResult,
|
|
37
41
|
});
|
|
38
42
|
pi.registerTool({
|
|
@@ -123,6 +127,8 @@ export function registerAgentBrowserToolSurface(pi, options) {
|
|
|
123
127
|
const available = new Set(pi.getAllTools().map(({ name }) => name));
|
|
124
128
|
if (!["agent_browser", "agent_browser_code", "agent_browser_tools", ...advancedNames].every(name => available.has(name)))
|
|
125
129
|
return;
|
|
130
|
+
// Keep Pi's replay helper behind the awaited restoration boundary, not factory registration.
|
|
131
|
+
const { getCurrentSystemMessage } = await import("@earendil-works/pi-ai");
|
|
126
132
|
const current = getCurrentSystemMessage(ctx.sessionManager.buildSessionProjection().messages);
|
|
127
133
|
const restored = new Set(current?.toolsAdded?.map(({ name }) => name));
|
|
128
134
|
const active = pi.getActiveTools().filter((name) => !advancedNames.has(name) || restored.has(name));
|
|
@@ -487,8 +487,9 @@ const BRAVE_WEB_SEARCH_ADAPTER = {
|
|
|
487
487
|
buildRequest(params) {
|
|
488
488
|
return buildBraveSearchUrl({
|
|
489
489
|
query: params.query,
|
|
490
|
-
count
|
|
491
|
-
|
|
490
|
+
// Brave offsets count pages; fetch the prefix and slice by result like Exa.
|
|
491
|
+
count: params.count + params.offset,
|
|
492
|
+
offset: 0,
|
|
492
493
|
country: params.country,
|
|
493
494
|
searchLang: params.searchLang,
|
|
494
495
|
safesearch: params.safesearch,
|
|
@@ -502,7 +503,8 @@ const BRAVE_WEB_SEARCH_ADAPTER = {
|
|
|
502
503
|
return {
|
|
503
504
|
results: (response.web?.results ?? [])
|
|
504
505
|
.map(normalizeBraveSearchResult)
|
|
505
|
-
.filter((result) => Boolean(result))
|
|
506
|
+
.filter((result) => Boolean(result))
|
|
507
|
+
.slice(params.offset, params.offset + params.count),
|
|
506
508
|
returnedQuery: cleanSearchText(response.query?.altered, 300) ?? cleanSearchText(response.query?.original, 300) ?? params.query,
|
|
507
509
|
};
|
|
508
510
|
},
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -185,10 +185,10 @@ Practical policy:
|
|
|
185
185
|
- expose `details.browserWindow` and one visible login handoff only when a successful first/fresh local wrapper-managed headed result, including `batch`, is not an attachment and has upstream `lifecycle.effectiveLaunch.browserLaunched: true` and a `created`/`replaced` managed-session outcome. Keep `visibility: "unverified"`: this is launch evidence, never a claim about the user's OS desktop
|
|
186
186
|
- leave explicit caller-provided `--session` choices alone unless the caller closes them explicitly, but before any DOM/content-bearing read or interaction against a caller-owned explicit session, live-probe that session with `get url` instead of trusting missing or stale transcript page state; hold the effective canonical namespace/session queue from that probe through semantic snapshot resolution and the main command so another cooperating updated Pi call cannot change tabs in between. Non-bail batch analysis retains every possible page left by a failed transition up to a fixed bound and blocks later content only when the target is unverified; exceeding the bound also fails closed to exact `batch --bail` guidance. Nested `batch` steps remain unsupported, and raw batch command strings mirror upstream's ASCII-space tokenizer, including quote/backslash handling, rather than splitting on other Unicode whitespace.
|
|
187
187
|
- after profiled `open` / `goto` / `navigate` calls, verify the active tab still matches the returned page URL and best-effort switch back when restored profile tabs steal focus
|
|
188
|
-
- once the wrapper observes tab-drift risk for a session (profile restore correction, overlapping stale opens, or restored session state), later active-tab commands verify the intended tab under the existing session queue before semantic/ref helpers and user commands. Native selection runs only when the intended tab is not already active, because upstream selection clears refs and frame scope even on same-tab reselection. Missing targets, failed selection, and post-selection target mismatches fail before user commands. Caller argv/stdin and native `--pin-tab` / `--no-pin-tab` preferences remain unchanged. Local commands, live `get url`, explicit HTTP `read <url>` (including its flags), URL `a11y`/`vitals`/`web-vitals`, `diff url`, `window new`, URL-bearing recording commands, and explicit tab/navigation/`connect`/`state load` recovery do not require the old target; history back/forward/reload, `pushstate`, and page-content operations still do. The same classifier scans effective batch rows past non-page prefixes until a page dependency or explicit context change, without rewriting user rows or changing bail behavior. For `window new` and `diff url`, observe the resulting URL instead of retaining the old target or treating the requested second URL as redirect evidence. Fold only reached native batch rows when available, discard observations and refs from before
|
|
189
|
-
- for sessions with observed tab-drift risk, after a successful command on a known tab target, the wrapper may best-effort restore that same target again if restored/background tabs steal focus after the command returns; routine same-session commands skip
|
|
190
|
-
- after successful
|
|
191
|
-
- keep a per-session `refSnapshot` aligned with the last successful `snapshot` (including refs merged from a successful `batch` by taking the last successful `snapshot` step in batch result order): restore it from persisted tool `details` when reloading, resuming, or moving to a different Pi session-tree branch, store bounded ref role/name metadata from the same snapshot for wrapper-side current-ref diagnostics, drop it on successful close commands (`close`, `quit`, or `exit`), replace it with a persisted `page-transition` invalidation after any upstream-executed `record start` attempt (direct or batch; older supported natives can swap the page before their already-active check, so failed starts count; the warning and persisted summary explicitly describe conservative ref invalidation, not an observed page change), a `record restart` with a URL operand, `window new`, `diff url`, or WebMCP `invoke` / `result` / `cancel` (these page-provided tools can mutate, rerender, or navigate; when a spawned `batch` yields no parseable result rows, for example after a wrapper timeout, planned transition steps still record the invalidation), or after a failed
|
|
188
|
+
- once the wrapper observes tab-drift risk for a session (profile restore correction, overlapping stale opens, or restored session state), later active-tab commands verify the intended tab under the existing session queue before semantic/ref helpers and user commands. Targets retain native CDP `targetId` across transcript replay; exact identity distinguishes duplicate URL/title tabs, while older targets without an ID keep URL-based matching. A cold managed reopen replaces the old ID. Native selection runs only when the intended tab is not already active, because upstream selection clears refs and frame scope even on same-tab reselection. Missing targets, failed selection, and post-selection target mismatches fail before user commands. Caller argv/stdin and native `--pin-tab` / `--no-pin-tab` preferences remain unchanged. Local commands, live `get url`, explicit HTTP `read <url>` (including its flags), URL `a11y`/`vitals`/`web-vitals`, `diff url`, `window new`, URL-bearing recording commands, and explicit tab/navigation/`connect`/`state load` recovery do not require the old target; history back/forward/reload, `pushstate`, and page-content operations still do. The same classifier scans effective batch rows past non-page prefixes until a page dependency or explicit context change, without rewriting user rows or changing bail behavior. For `window new` and `diff url`, observe the resulting URL instead of retaining the old target or treating the requested second URL as redirect evidence. Fold only reached native batch rows when available, discard targets before failed navigation/context transitions and live-verify the final page, discard observations and refs from before window/diff transitions, and let later successful snapshots rebuild refs even when another batch row fails. Retain an observed blank destination after either command instead of recovering the old page; if no final target is observed, use the existing unknown-target state. Caller batch arguments, stdin and bail behavior stay unchanged. Successful batches containing navigation-observable actions use the same final live page/identity observation as direct actions, so same-address popup tabs replace the pre-batch target before later pinning. Terminal batch closes do not trigger a page probe, and pending WebMCP targets remain unknown. Routine same-session commands avoid `tab list` preflights
|
|
189
|
+
- for sessions with observed tab-drift risk, after a successful command on a known tab target, the wrapper may best-effort restore that same target again if restored/background tabs steal focus after the command returns; routine same-session commands skip focus correction, while observed pages without a returned native ID use `tab list` to establish current identity rather than inheriting an earlier tab's ID
|
|
190
|
+
- after successful tab creation, selection, or close, including dispatched native batch rows, read the now-active URL and fresh non-blank title—even when two tabs share a URL—before updating per-session page state; retain the deliberate blank target instead of treating it as accidental drift
|
|
191
|
+
- keep a per-session `refSnapshot` aligned with the last successful `snapshot` (including refs merged from a successful `batch` by taking the last successful `snapshot` step in batch result order): restore it from persisted tool `details` when reloading, resuming, or moving to a different Pi session-tree branch, store bounded ref role/name metadata from the same snapshot for wrapper-side current-ref diagnostics, drop it on successful close commands (`close`, `quit`, or `exit`), replace it with a persisted `page-transition` invalidation after any upstream-executed `record start` attempt (direct or batch; older supported natives can swap the page before their already-active check, so failed starts count; the warning and persisted summary explicitly describe conservative ref invalidation, not an observed page change), a `record restart` with a URL operand, `window new`, `diff url`, or WebMCP `invoke` / `result` / `cancel` (these page-provided tools can mutate, rerender, or navigate; when a spawned `batch` yields no parseable result rows, for example after a wrapper timeout, planned transition steps still record the invalidation), or after a failed transition command (`eval`, `back`, `forward`, `reload`, `connect`, `state load`, `tab` selection), including reached batch transitions and failed batch `open` rows whose live URL re-verification probe observed the page (a failed transition can still have mutated or replaced the document before throwing, so keeping the verified URL must not keep the prior refs; transcript replay preserves the persisted invalidation summary), and refuse page-scoped `@e…` argv before spawn when the active tab URL no longer matches the snapshot URL, when a ref id was never in that snapshot, when the snapshot state is invalidated, or when a `batch` step would reuse `@e…` on a guarded getter or mutation step after an earlier invalidating step (including `record start`, URL-bearing `record restart`, and WebMCP `invoke` / `result` / `cancel`) without a later `snapshot` step in the same plan; batch steps come from the source upstream actually executes (raw batch argument strings exclusively when any exist — filtering only the exact `--bail` token like upstream — stdin only otherwise, via `getUpstreamEffectiveBatchSteps` in `extensions/agent-browser/lib/orchestration/batch-stdin.ts`); tab recovery (which leaves user argv/stdin and continue-on-error control flow unchanged), artifact/recording preflight, batch screenshot path preparation (parent directories are created for effective raw rows too, without rewriting raw strings), and stale-ref echo args use that same selection so pinning and preflights cannot act on upstream-ignored stdin, while the pre-spawn state-policy validator deliberately keeps scanning parseable stdin alongside argv as a fail-closed content superset and treats stdin parse failures as fatal only when upstream would actually read stdin (its raw-token filter also uses the exact `--bail` token only). Same-snapshot `fill @e…` rows are guarded but do not themselves set that invalidation latch, so ordinary form fills can precede a click/submit row in one batch—see [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details) for the agent-visible contract and failure text; typed per-session tab/ref/pinning state lives in `extensions/agent-browser/lib/session-page-state.ts` and is updated from `extensions/agent-browser/index.ts` after each tool result
|
|
192
192
|
- native 0.37 owns first-document setup inheritance for new tabs; the wrapper does not copy headers, user agents or other browser setup itself. The page-summary formatter displays native positive WebMCP availability while retaining raw details; it adds no discovery probe
|
|
193
193
|
- when a direct or batched WebMCP call returns `status: "pending"`, or `result` / `cancel` fails while that target is unknown, keep its tab target unknown and discard same-call snapshot evidence instead of treating an immediate post-dispatch URL probe as stable; `webmcp result` / `cancel`, `get url`, and explicit navigation remain available while unknown; replace the generic blocked snapshot action with `verify-page-target-after-pending-webmcp` (`get url`), and let a completed `batch --bail` use that verification before `snapshot -i` to re-establish both target and refs
|
|
194
194
|
- for top-level non-Electron direct `click` commands with an eligible target, install a bounded in-page target-specific event probe before upstream runs; if upstream reports success but no trusted pointer/mouse/click event reached the resolved target, fail the tool and report `details.clickDispatch` with explicit retry/inspect next actions (the wrapper does not replay clicks in-page). The probe covers `xpath=` targets and current `@e…` / `ref=` refs whose latest stored `refSnapshot.refs` role is `button`, `checkbox`, `menuitem`, `radio`, `switch`, or `tab`; it requires a unique role/name in the saved snapshot and the live candidates instead of taking a fresh pre-click snapshot that could recycle upstream refs. Ref and XPath candidates must additionally prove identity through a per-probe temporary DOM attribute read back by native `get attr <original-selector> <marker>`. Missing or mismatched identity, including XPath/frame scope mismatches, cleans up the probe and lets native click run without dispatch verification; candidate naming heuristics alone are never proof. Duplicate-name refs pass through without a probe: their old ordinal is not target identity. The probe is intentionally skipped for CSS selector clicks, unresolved `find … click` locators, and `batch`/`qa` click steps
|
|
@@ -785,7 +785,9 @@ Stable tab ids look like `t1`, `t2`, and `t3`. Optional user labels such as `doc
|
|
|
785
785
|
| `tab <t<N>|label>` | Switch to a tab by id or label. CDP target ids from `tab list --json` are also accepted and stay stable across daemon restarts. |
|
|
786
786
|
| `tab close [t<N>|label|target]` | Close the current tab or a referenced tab. Generic references in workflows may say `tab close [target]`; use a stable `t<N>` id, label, or CDP target id when you have one. |
|
|
787
787
|
|
|
788
|
-
|
|
788
|
+
The wrapper persists native CDP `targetId` when available so resume/branch recovery distinguishes tabs with the same URL and title. A missing known target requires explicit tab recovery; older transcripts without IDs still use URL matching, and a cold managed reopen adopts its new ID. Failed reached batch navigations retire earlier targets and recheck the live page, including Chrome error pages, before subsequent inspection.
|
|
789
|
+
|
|
790
|
+
After successful tab creation, selection, or close, including dispatched native batch rows, the wrapper live-probes `get url` and, for non-blank pages, `get title` before committing the active target. Tab transitions always refresh the title even when the URL matches the prior tab. New blank tabs, explicitly selected `about:blank` tabs, and blank tabs revealed by close remain active instead of triggering prior-tab correction. A successful probe clears the unverified-page gate for the next command; if the probe fails, `details.sessionTabTargetUnknown` stays true and recovery still starts with `get url`.
|
|
789
791
|
|
|
790
792
|
With `--pin-tab`, a closed bound tab fails as `tab_gone` (`data.targetId`, optional `data.lastUrl`) instead of falling back to another tab.
|
|
791
793
|
|
|
@@ -1077,11 +1079,11 @@ Other useful environment variables include `AGENT_BROWSER_DEFAULT_TIMEOUT`, `AGE
|
|
|
1077
1079
|
- After open/goto/navigate calls with --profile, --restore, --session-name, or --state, agent_browser best-effort re-selects the tab whose URL matches the returned page when restored tabs steal focus during launch or reconnect.
|
|
1078
1080
|
- After confirmed shutdown of an automatically restored managed session, the wrapper retains its complete recorded URL, including the fragment, until the first current-page operation (including get url and reload). Non-page calls such as tab list may start a daemon without fulfilling that reopen; explicit URL reads leave the managed browser and pending reopen untouched. The wrapper uses native open once, verifies the observed tab, and discards old refs/frame scope; it does not restore unsaved forms, JavaScript memory, or history. Explicit navigation, caller-owned/attached sessions, and restore-disabled sessions are not auto-reopened.
|
|
1079
1081
|
- For a still-live browser after tab drift or resume, the wrapper verifies/selects the intended tab before ref/semantic helpers and page commands; failed selection stops the call without navigating. Local commands, read <url>, URL a11y/vitals, diff url, window new, and explicit tab/navigation/connection/state recovery do not require the prior tab. Batch checks follow effective rows past non-page prefixes and stop at explicit context changes, preserving caller argv/stdin and continue-on-error behavior. Same-tab reselection is avoided because it clears refs. Use exact batch --bail for fail-fast, not --bail=<value>. Routine same-session calls skip tab-list preflights.
|
|
1080
|
-
- For sessions with observed tab-drift risk, after a successful command on a known target tab, agent_browser also best-effort restores that intended tab if a restored/background tab steals focus after the command completes. Routine same-session commands skip
|
|
1082
|
+
- For sessions with observed tab-drift risk, after a successful command on a known target tab, agent_browser also best-effort restores that intended tab if a restored/background tab steals focus after the command completes. Routine same-session commands skip focus correction; observed pages without a native targetId use tab list to establish their identity.
|
|
1081
1083
|
- If a known session target unexpectedly reports about:blank, agent_browser best-effort re-selects the prior intended target when it still exists; if recovery fails, it records the observed about:blank target and reports exact recovery guidance instead of treating the prior page as active.
|
|
1082
1084
|
- If upstream reports tab_gone, the pinned bound tab is gone; use details.nextActions (tab list / tab new) instead of assuming another tab is yours.
|
|
1083
1085
|
<!-- agent-browser-playbook:end wrapper-tab-recovery -->
|
|
1084
|
-
- Wrapper-spawned commands clamp `AGENT_BROWSER_DEFAULT_TIMEOUT` to the upstream documented 25-second default and use a 35-second child-process watchdog (`PI_AGENT_BROWSER_PROCESS_TIMEOUT_MS` overrides the default 35s budget; top-level `timeoutMs` overrides it per browser CLI call). Explicit `wait <ms>`, `wait --timeout <ms>`, and WebMCP `invoke` / `result --timeout <ms>` calls can exceed that default; when top-level `timeoutMs` is omitted, the wrapper derives a subprocess watchdog from the requested command duration plus a small grace window. Batch budgeting and timeout recovery read the same effective source as upstream: raw command strings when present, otherwise stdin rows. Dialog commands are additionally bounded to 5 seconds (`PI_AGENT_BROWSER_DIALOG_PROCESS_TIMEOUT_MS`), and click/tap/find refs or tokens plus `eval --stdin` snippets that look like alert/confirm/prompt/dialog triggers are bounded to 8 seconds (`PI_AGENT_BROWSER_DIALOG_TRIGGER_PROCESS_TIMEOUT_MS`). A `session info` timeout returns only an exact-session `retry-session-info` status action, without page probes, liveness claims, or changes to existing page/ref state. When a browser-operation watchdog fires, `details.timeoutPartialProgress` may include a planned step list with
|
|
1086
|
+
- Wrapper-spawned commands clamp `AGENT_BROWSER_DEFAULT_TIMEOUT` to the upstream documented 25-second default and use a 35-second child-process watchdog (`PI_AGENT_BROWSER_PROCESS_TIMEOUT_MS` overrides the default 35s budget; top-level `timeoutMs` overrides it per browser CLI call). Explicit `wait <ms>`, `wait --timeout <ms>`, and WebMCP `invoke` / `result --timeout <ms>` calls can exceed that default; when top-level `timeoutMs` is omitted, the wrapper derives a subprocess watchdog from the requested command duration plus a small grace window. Batch budgeting and timeout recovery read the same effective source as upstream: raw command strings when present, otherwise stdin rows. Dialog commands are additionally bounded to 5 seconds (`PI_AGENT_BROWSER_DIALOG_PROCESS_TIMEOUT_MS`), and click/tap/find refs or tokens plus `eval --stdin` snippets that look like alert/confirm/prompt/dialog triggers are bounded to 8 seconds (`PI_AGENT_BROWSER_DIALOG_TRIGGER_PROCESS_TIMEOUT_MS`). A `session info` timeout returns only an exact-session `retry-session-info` status action, without page probes, liveness claims, or changes to existing page/ref state. When a browser-operation watchdog fires, `details.timeoutPartialProgress` may include a planned step list with `status: "unknown"` (including `generatedFrom` labels for generated QA rows). Current URLs and existing files do not establish which steps ran, failed, or remained pending. Multi-step timeouts offer `inspect-current-page-after-timeout` when the target is already verified, rather than guessing a retry position. A single read-only or idempotent step may offer a `retry-timeout-step` candidate with a one-row native batch (`args: ["batch"]` plus `stdin`); inspect the current state before retrying because its outcome is still unknown. If the target is unknown, standalone snapshots are removed and visible failure text plus `details.nextActions` show `verify-page-target-after-timeout`, including its session-scoped `batch --bail` args and short stdin for fail-fast `get url` then `snapshot -i`; dialog status/accept/dismiss actions remain allowed for blocking-dialog recovery. It also includes current page URL from best-effort session `get url`, followed by `get title` only after a URL is recovered (or a planned URL inferred from the step list when the session cannot answer), and declared artifact paths such as `screenshot`, `pdf`, `download`, or `wait --download` outputs with existence/state checks; the same evidence is appended under `Timeout partial progress` in visible text with URL/path redaction.
|
|
1085
1087
|
- Oversized snapshots and oversized generic outputs may be compacted in tool content, with the full redacted output written to a spill file path shown directly in the tool result. Recent artifact metadata is bounded by `PI_AGENT_BROWSER_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES` (default 100); persisted spill files have a separate `PI_AGENT_BROWSER_SESSION_ARTIFACT_MAX_BYTES` budget (default 32 MiB; `0` disables automatic eviction).
|
|
1086
1088
|
- The wrapper keeps `--help` and `--version` stateless so they do not consume the implicit managed-session slot.
|
|
1087
1089
|
|
package/docs/RELEASE.md
CHANGED
|
@@ -39,7 +39,7 @@ The minimum Pi runtime is 0.87.0, matching the native APIs used by the new tool
|
|
|
39
39
|
|
|
40
40
|
Qualify official Pi 0.87 and the fork independently with the existing host/package/lifecycle infrastructure. Required evidence for the redesign includes compact schema and additive activation/history, native prompt sections, shared direct/code auth continuity, fresh JavaScript globals, selected real images and conservative geometry, visible handled failures/exact recovery, real cross-process helper/action and whole-cell ordering, cancellation/reaping, and ordered transition replay after interruption/branch changes. Retain native batch and advanced action/QA/Electron/source outcome coverage. Small synthetic timing comparisons are not universal performance claims or sandbox certification.
|
|
41
41
|
|
|
42
|
-
Existing owner waivers remain authoritative. Windows qualification is waived; report diagnostic failures without claiming a pass or restarting waived repair work. GitHub Actions
|
|
42
|
+
Existing owner waivers remain authoritative. Windows qualification is waived; report diagnostic failures without claiming a pass or restarting waived repair work. Routine GitHub Actions now runs on PRs to `main` and manual dispatch, with no Windows CI job. These focused checks complement, rather than replace, the local release and publication gates.
|
|
43
43
|
|
|
44
44
|
## Pre-release checks
|
|
45
45
|
|
|
@@ -71,7 +71,7 @@ npm run verify -- pre-pr
|
|
|
71
71
|
3. `npm run verify -- package-pi`, which first validates package contents via `npm pack --json --dry-run` and then smoke-loads the packed package in Pi isolation
|
|
72
72
|
4. `npm run smoke:platform:doctor` and the full Crabbox matrix from [`platform-smoke.md`](platform-smoke.md): macOS SSH, Ubuntu local-container, and native Windows Parallels targets running fast target-local `platform-build` plus `browser-dogfood-smoke`
|
|
73
73
|
|
|
74
|
-
When using the documented, maintainer-approved [local macOS
|
|
74
|
+
When using the documented, maintainer-approved [local macOS alternative](platform-smoke.md#alternate-native-transports), run and inspect that target-local suite separately alongside the remaining release checks. The default composed gate is unchanged; alternate evidence must not be labeled a passing Crabbox matrix.
|
|
75
75
|
|
|
76
76
|
`npm publish` runs npm’s `prepublishOnly` script from `package.json`, which executes the same `npm run verify -- release` gate and then `npm pack --dry-run`. That concatenated gate is everything in the default `npm run verify` step (generated playbook drift, clean `dist/` build, TypeScript, the unit/fake suite, generated command-reference blocks, and live upstream command-reference sampling against the targeted `agent-browser` on `PATH`), the configured-source lifecycle harness, the packaged Pi smoke in `package-pi`, and the release-blocking Crabbox platform matrix. Using `npm publish --ignore-scripts` skips that contract intentionally.
|
|
77
77
|
|
|
@@ -268,7 +268,7 @@ These show up often in cloud dev boxes and scripted smokes; they are maintainer
|
|
|
268
268
|
| Topic | What to watch for | Mitigation |
|
|
269
269
|
| --- | --- | --- |
|
|
270
270
|
| **Pi CLI vs repo devDependencies** | Global `pi` older than the minimum Pi runtime floor for the release can change TUI behavior, `/reload`, package installs, and tool routing during lifecycle or checkout smokes. | Run `npm run doctor` and align `pi` with the current audited baseline before release gates (`pi update` or install the matching version). The published peer range stays wildcard per Pi package docs, and the doctor enforces the minimum Pi runtime floor before package validation. |
|
|
271
|
-
| **npm lockfile (`packageManager`)** | `package.json` pins **npm@
|
|
271
|
+
| **npm lockfile (`packageManager`)** | `package.json` pins **npm@12.1.0** for the Node 24.21.0 runtime baseline. Older npm versions can rewrite optional-platform metadata in `package-lock.json`; private registry proxies can also leak organization URLs into `resolved` entries. | Prefer `npx -y npm@12.1.0 install` when refreshing the lockfile; do not commit unrelated lockfile churn or WorkOS/private-registry URLs. |
|
|
272
272
|
| **`pi -p` / print mode** | Non-interactive `pi -p` may hang or emit no stdout for long real-browser smokes without a TTY. | Use **tmux**-driven interactive `pi` for release evidence and checkout smokes; reserve `-p` for short, non-browser checks. |
|
|
273
273
|
| **Real-browser cleanup** | `real-upstream`, Sauce Demo, and live-site runs can leave defunct Chrome/`agent-browser` children if a session aborts mid-flow. | Close via `agent_browser` / `agent-browser` `close`, kill stray tmux sessions, and remove temp screenshots/HARs under `/tmp` or your chosen artifact dirs. |
|
|
274
274
|
| **Automated prompt driving** | Grepping tmux pane text for words that also appear in the **user** prompt (`PASS`, `FAIL`, `checkout overview`, `Smoke result:`) can false-complete before the agent finishes. | Wait for pane idle (no `Working…`), `agent_browser close` / `Artifact lifecycle`, or JSONL tool results—not instruction phrases copied from the prompt. |
|
|
@@ -322,15 +322,13 @@ node --test test/agent-browser.checkpoint-native.test.mjs
|
|
|
322
322
|
|
|
323
323
|
`PI_CHECKPOINT_TEST_EXTENSION` can select a separate baseline checkout. With `PI_CHECKPOINT_TEST_BASELINE_RESTORE=1`, only its existing root-restore path runs and every receipt must retain the old shutdown blocker; this is not positive sleep qualification. The SDK selector is test-only; it does not change package peers or activate an extension. This gate is separate from the default 0.84 unit fixtures and release/platform/model-based gates. Qualification is for ordinary native save-on-close and explicit URL reopen, not a live browser snapshot. Journal fault injection checks that ongoing native I/O errors reject acquisition and preserve prior bytes/accepted entries. Permission repair must recover through core capture without repeating the failed append; recording retry and cleanup leases remain extension-owned. The test also checks exact repaired entries/selection in a separate-process strict cold restore and rejection of a mismatched artifact. After explicit close it observes native daemon exit before asserting idle readiness; an acknowledgement alone is not proof of inactivity. The abnormal-restart regression kills only its exact SDK fixture PID after ordinary branch navigation, then reopens the real journal in a fresh SDK process. It verifies that the off-branch wrapper-created daemon blocks capture without acquiring cleanup ownership, and that explicit managed-looking names and a nonowned namespace retain ambient routing. Same-namespace/same-name controls check distinct live daemons in owned and ambient socket roots, continued blocking after closing only the caller, ambient-only blocking after closing the historical daemon, and positive readiness after both are explicitly closed. All browser ownership evidence comes from executed native tools, never injected ownership records.
|
|
324
324
|
|
|
325
|
-
####
|
|
325
|
+
#### Routine GitHub PR checks
|
|
326
326
|
|
|
327
|
-
[`.github/workflows/
|
|
327
|
+
[`.github/workflows/pi-compatibility.yml`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/.github/workflows/pi-compatibility.yml) runs on PRs to `main` and manual dispatch. Independent Ubuntu jobs qualify the locked official Pi graph and the pinned fork's complete installed graph on Node 24.21.0. Each runs `check:compat` once: host identity, typecheck, the full offline suite, and packed SDK/CLI loading. A parallel macOS Node 24.21.0 job checks the packed Pi load without repeating the full suite. When the candidate official version differs from the supported Pi floor, that macOS job also selects the actual floor graph for a focused type/package smoke. The host selector uses the pinned `fitchmultz/.github` automation scripts without changing them. There are no cross-job host artifacts or persistent caches.
|
|
328
328
|
|
|
329
|
-
|
|
329
|
+
[`.github/workflows/native-checkpoint.yml`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/.github/workflows/native-checkpoint.yml) independently checks out the PR head and runs on the same PR/manual triggers. Its CI-only [`ci-native-checkpoint.sh`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/scripts/ci-native-checkpoint.sh) builds the pinned fork and compiled extension on Node **24.21.0**, installs stock **agent-browser 0.38.1** and Chrome for Testing, checks generated playbook and live command-reference drift, then requires **13 passed, zero failed/cancelled/skipped/todo** real-browser checkpoints. It does not repeat the official full suite. Sources use a private `/tmp` root, sanitized environment, non-root execution, and a loopback-only network namespace. No personal profile, credentials, or model calls are involved.
|
|
330
330
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
CI uploads raw setup/build/provenance logs, Node 26 and document verification output, and native TAP (including receipts), even on failure. Chrome is downloaded through the pinned stock installer, not separately version-pinned; the actual browser version is logged. These checks prove synthetic native behavior on that runner and sourcehost, not real-site authentication, Electron-app behavior, managed-key cold portability, or complete personal qualification. Passing this gate does not replace code review or full personal-workspace acceptance.
|
|
331
|
+
CI writes setup, provenance, command-reference, and native TAP results to the job log instead of uploading artifacts. The checks cover their exact source, host, and synthetic browser paths; they do not replace release/manual-publication gates, real-site authentication, or full personal-workspace sleep/resume qualification.
|
|
334
332
|
|
|
335
333
|
### Native Linux socket-root regression
|
|
336
334
|
|
package/docs/SUPPORT_MATRIX.md
CHANGED
|
@@ -42,7 +42,7 @@ The compact direct/code/loader surface replaces public multimode inputs while re
|
|
|
42
42
|
| Observation and geometry | Canonical bounded JSON/prose/code observations expose exact recovery and receipts; complete observation spills preserve large output. Image dimensions/CSS viewport/DPR/crop/scroll/frame evidence is measured or explicitly unknown; no viewport-coordinate assumptions for full-page/element images. |
|
|
43
43
|
| Pi integration | Native additive activation/history and prompt sections on independently qualified official Pi 0.87 and fork; optional checkpoint hooks, no provider payload/image-detail overrides. |
|
|
44
44
|
|
|
45
|
-
See [migration](TOOL_CONTRACT.md#07-migration), [canonical observations](TOOL_CONTRACT.md#canonical-observation), and [release qualification/rollback](RELEASE.md#07-upgrade-and-rollback).
|
|
45
|
+
See [migration](TOOL_CONTRACT.md#07-migration), [canonical observations](TOOL_CONTRACT.md#canonical-observation), and [release qualification/rollback](RELEASE.md#07-upgrade-and-rollback). Routine GitHub Actions runs official/fork Linux compatibility, a macOS packed-Pi smoke, and native Linux checkpoints on PRs to `main`; Windows CI is excluded. Release/platform evidence and review remain separate from these focused checks.
|
|
46
46
|
|
|
47
47
|
## Audit result
|
|
48
48
|
|
|
@@ -87,7 +87,7 @@ Current summary:
|
|
|
87
87
|
| RQ-0149 | Upstream 0.35.1 fixes snapshot-diff ref lifecycle, active-main-frame stream URL updates, and Windows ARM64 launcher selection without adding commands or flags. The wrapper preserves the 0.35.0 stable floor and accepts newer stable versions without version-specific shims. | [`docs/COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#upstream-0351-rebaseline), [`scripts/agent-browser-target.mjs`](../scripts/agent-browser-target.mjs), `test/agent-browser.upstream-version.test.ts`, `test/agent-browser.real-upstream-contract.test.ts` |
|
|
88
88
|
| RQ-0150 | Upstream 0.35.2 hardens standalone dashboard origin access and fixes root remote CDP WebSocket query URLs. The wrapper parses `--allowed-origins` as a value and keeps explicit or implicit dashboard-start lifecycle calls sessionless; upstream owns origin validation, tokens, and the CDP fix. | [`docs/COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#upstream-0352-rebaseline), [`docs/TOOL_CONTRACT.md`](TOOL_CONTRACT.md#dashboard-reverse-proxy-origins), `test/agent-browser.runtime.test.ts` |
|
|
89
89
|
| RQ-0151 | Upstream 0.36.0 adds experimental WebMCP page tools, default local-Chrome enablement with launch-scoped `--no-webmcp`, an opt-in MCP profile, and the `webmcp-gen` skill. The wrapper passes list/invoke/result/cancel through, preserves params/frame/detach/timeout arguments, derives watchdogs from effective direct/raw/stdin commands, keeps pending detached targets unverified, and restores batch target/ref state only after URL verification and a fresh snapshot, while keeping 0.35.0 as the stable floor. | [`docs/COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#upstream-0360-rebaseline), [`docs/TOOL_CONTRACT.md`](TOOL_CONTRACT.md#experimental-webmcp), `test/agent-browser.runtime.test.ts`, `test/agent-browser.wait-timeouts.test.ts`, `test/agent-browser.process.test.ts`, `test/agent-browser.page-target-validation.test.ts`, `test/agent-browser.extension-ref-guards.test.ts`, `test/agent-browser.real-upstream-contract.test.ts` |
|
|
90
|
-
| RQ-0152 | Agent feedback closes recovery and evidence gaps without a new runtime layer: unknown-target timeouts return a visibly executable `get url` → `snapshot -i` fail-fast action while dialog recovery remains allowed; successful tab selection/close live-verifies URL/fresh titles and retains
|
|
90
|
+
| RQ-0152 | Agent feedback closes recovery and evidence gaps without a new runtime layer: unknown-target timeouts return a visibly executable `get url` → `snapshot -i` fail-fast action while dialog recovery remains allowed; successful tab creation/selection/close, including dispatched batch rows, live-verifies URL/fresh titles and retains new, selected, and post-close blank tabs; compacted direct or batched `outputPath` writes use only full command-redacted live wrapper spills and fail rather than save metadata; explicit URL reads/all-read batches avoid browser helpers and managed replacement, while proven native read confirmations preserve exact session routing and capability-gated browser independence; single session-info preflights distinguish daemon/browser/native/Pi ownership; native recording receipts expose capture versus encoded measurements and survive failure/timeout exports, with bounded identity-matched recovery and no file-only success; direct or batched first/fresh local headed launches expose an unverified `browserWindow` handoff while attachments do not; snapshot spills and SAML/OAuth URL values are redacted at model/persistence boundaries; unsupported direct/batch `scrollintoview text=...` fails before upstream false success with visible native recovery payloads while help passes through; and web search sends Exa primary/version guidance, removes exact normalized-URL duplicates without overfetch, and labels provider page dates without claiming crawl/version proof. | [`docs/TOOL_CONTRACT.md`](TOOL_CONTRACT.md), [`docs/COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md), [`docs/ARCHITECTURE.md`](ARCHITECTURE.md), `test/agent-browser.extension-errors-artifacts.test.ts`, `test/agent-browser.extension-tabs.test.ts`, `test/agent-browser.extension-validation.test.ts`, `test/agent-browser.page-target-validation.test.ts`, `test/agent-browser.runtime.test.ts`, `test/agent-browser.snapshot-presentation.test.ts`, `test/agent-browser.web-search.test.ts`, `test/agent-browser.real-upstream-contract.test.ts` |
|
|
91
91
|
|
|
92
92
|
QA truthfulness (#121, RQ-0145) is covered by the registered-tool controls in `test/agent-browser.extension-input-modes.test.ts` and the strict `contract suite matches QA non-pass after same-URL error-buffer rollover` case in `test/agent-browser.real-upstream-contract.test.ts`. The real fixture fills the native error FIFO with identical rows, then verifies that a new document actually throws again at the same URL and that QA is always non-pass. Matched rows fail as an unverified check, not an application-error assertion. Empty/cleared and explicit `checkErrors: false` controls preserve valid passes; the wrapper does not fix upstream `errors --clear` or accept a warning-only pass as resolution.
|
|
93
93
|
|
|
@@ -103,7 +103,7 @@ Upstream 0.38.1 observation and input compatibility is covered by `test/agent-br
|
|
|
103
103
|
|
|
104
104
|
Upstream 0.37 recording compatibility uses one command-local path/URL reader around numeric `--fps` pairs; direct, raw-batch and stdin-batch preflight reject destination collisions before native dispatch. FPS-only calls keep the intended pinned tab, and FPS-only restarts keep refs. Every dispatched start still requires fresh refs to protect older supported natives. `test/agent-browser.artifact-diagnostics.test.ts`, `test/agent-browser.batch-fidelity.test.ts` and presentation tests cover these boundaries. The native contract also checks positive/absent WebMCP hints and first-load header inheritance/clearing without custom tab setup. Short/cold native Ubuntu captures can fail to encode; the FPS fixture explicitly logs 12-second captures and verifies output rather than treating an empty file as a frame.
|
|
105
105
|
|
|
106
|
-
Artifact argv and timeout recovery (#119, RQ-0076) use native operand positions, retain literal global-looking tokens inside batch rows, and select raw batch commands instead of ignored stdin. Executable
|
|
106
|
+
Artifact argv and timeout recovery (#119, RQ-0076) use native operand positions, retain literal global-looking tokens inside batch rows, and select raw batch commands instead of ignored stdin. Timeout observations retain current page and artifact existence evidence while all unacknowledged planned rows stay unknown; neither a matching URL nor an existing file proves execution. Multi-step timeouts offer inspection without inferring a failed/pending boundary. Executable retry candidates for a single retryable step preserve the original row in a one-row batch. Interleaved `wait --download` / `-d` timeout pairs keep the next retained path and its original index. Runtime, artifact-diagnostics, and timeout tests cover extraction, reservation, preparation, and presentation; `test/agent-browser.batch-fidelity.test.ts` adds real native saved-file, missing wait-download, reservation, and watchdog evidence under `npm run verify -- real-upstream`. Getter batching amortizes one extra ref-freshness snapshot per top-level call or batch when the existing checks apply. Direct `session info` timeouts stay status-only (`retry-session-info`), preserving page/ref/reopen state; cold-boundary and result tests cover this.
|
|
107
107
|
|
|
108
108
|
Cold URL reopen is covered by `test/agent-browser.cold-resume.test.ts`, the daemon/page-boundary regressions in `test/agent-browser.cold-boundaries.test.ts`, and the opt-in `contract suite matches cold URL reopen after quit` case in `test/agent-browser.real-upstream-contract.test.ts`. Deterministic boundary tests cover non-page prefixes, fragment routes, history commands, explicit destinations, transcript replay, unreached navigation and unchanged native batch/error order. `test/agent-browser.destination-cancel.test.ts` covers direct/native-batch new-window and URL-diff follow-ups, redirected/unknown targets (including blank redirects with a remembered duplicate tab), ordered ref invalidation, reached versus unreached rows, and cancellation before or after a cold-reopen attempt with replay and process cleanup. Release acceptance requires the first page operation after confirmed shutdown to observe the remembered non-root URL with both empty and origin storage, including after non-page calls and on hash-routed pages, before any explicit open, with refreshed refs. Focused native Mac evidence does not replace actual Ubuntu, real Pi lifecycle, checkout, live-site, or final release gates.
|
|
109
109
|
|
|
@@ -123,7 +123,7 @@ At runtime source `3a9adf490883149e7f79ee070d1900877e17b891`, PR compatibility p
|
|
|
123
123
|
|
|
124
124
|
Fresh precommit evidence for that runtime passed real Pi checkout/live-site and supported disposable Electron smoke, configured-source lifecycle, packaged runtime-only SDK/CLI loading, real-upstream checks, native macOS and the canonical named Ubuntu target (Debian 12). Local macOS is the documented alternate, not an SSH pass. Docs/workflow-only delivery may reuse this runtime evidence only after all 104 rebuilt runtime files match the checked artifact; changed published docs require a newly checked retained tarball. No additional paid/live gate is implied by an otherwise byte-identical runtime.
|
|
125
125
|
|
|
126
|
-
Linux/macOS, declared Node floors, official/fork host identities, required consumer artifacts and Native checkpoint Linux remain required.
|
|
126
|
+
Linux/macOS, declared Node floors, official/fork host identities, required consumer artifacts and Native checkpoint Linux remain required. Routine GitHub Actions is enabled for PRs to `main` and manual dispatch; its focused results do not substitute for the release and platform gates. Retain all three final reviewer approvals before merge/release/activation. The unchanged composed `release`/`prepublishOnly` command still includes Windows and is not reported as passed under this waiver; see [release guidance](RELEASE.md#current-0616-rollout-waiver).
|
|
127
127
|
|
|
128
128
|
## Verification evidence
|
|
129
129
|
|
|
@@ -147,7 +147,7 @@ Re-run the gates below before each release. Dated outcomes in this table are his
|
|
|
147
147
|
| Configured-source lifecycle | `npm run verify -- lifecycle` (`scripts/verify-lifecycle.mjs`) drives `/reload`, closes and relaunches Pi with the same exact `--session-id`, checks the JSONL session header id, session continuity, slash-command sentinel tokens (`v1` before reload and `v2` after full relaunch because compiled JS package modules are process-cached), persisted spill reachability, and real Pi `tool_result` failure-patch semantics for a QA reclassification with a fake upstream on `PATH`. Default Pi model is `zai/glm-5.2`; default per-step wait is **180000 ms** (`DEFAULT_TIMEOUT_MS`); override model with `--model <id>` and waits with `--timeout-ms <ms>`. Passthrough flags in [`scripts/project.mjs`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/scripts/project.mjs): `--keep-artifacts`, `--model`, `--verbose`, and `--timeout-ms` plus a value (for example `npm run verify -- lifecycle --model openai-codex/gpt-5.5:minimal --keep-artifacts --verbose --timeout-ms 600000`). | **Pi 0.84.4 on 2026-09-01:** pass; `/reload`, exact-session relaunch, managed continuity, persisted spill reachability, and Pi failure-patch behavior all verified. |
|
|
148
148
|
| Quick isolated Pi smoke | `pi --approve --no-extensions --no-skills -e .` from trusted repo root; checkout-native browser tools only. | **Earlier 0.36.0 / package 0.6.2 / Pi 0.84.4:** isolated checkout tmux smoke passed on 2026-09-01; native `agent_browser` completed version, localhost open, WebMCP list/invoke, visible-text verification, and close. |
|
|
149
149
|
|
|
150
|
-
Runtime floor note: package metadata keeps optional Pi core package peer ranges wildcard per installed Pi package docs, pins direct Pi validation dependencies to 0.87.
|
|
150
|
+
Runtime floor note: package metadata requires Node 24.21.0 or newer, keeps optional Pi core package peer ranges wildcard per installed Pi package docs, pins direct Pi validation dependencies to 0.87.1, and makes `pi --version` below 0.87.0 a setup failure through `pi-agent-browser-doctor` / `npm run doctor`. The Pi floor follows the native tool-history/prompt APIs and includes no older-host compatibility shim.
|
|
151
151
|
|
|
152
152
|
## Baseline checklist by inventory section
|
|
153
153
|
|
|
@@ -155,7 +155,7 @@ Runtime floor note: package metadata keeps optional Pi core package peer ranges
|
|
|
155
155
|
| --- | --- | --- | --- | --- | --- |
|
|
156
156
|
| Built-in skills | 19 canonical tokens from baseline section `skills`; see [`scripts/agent-browser-capability-baseline.mjs`](../scripts/agent-browser-capability-baseline.mjs) and generated [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#built-in-skills). | [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#built-in-skills), generated baseline block, README proof section, release docs. | `needsManagedSession` keeps read-only skills inspection sessionless while preserving thin upstream passthrough; upstream `@agent-browser/sandbox` remains external package guidance, not a bundled wrapper dependency. | Runtime and extension-validation skills/provider matrix; real-upstream inspection/skills group. | Supported. |
|
|
157
157
|
| Core page, element, navigation, and extraction commands | Canonical tokens from baseline section `core-commands`; see [`scripts/agent-browser-capability-baseline.mjs`](../scripts/agent-browser-capability-baseline.mjs) and generated [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#core-page-and-element-commands). | [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#core-page-and-element-commands), [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md), README quick start. | Thin passthrough with wrapper-owned JSON/session planning, ref guidance, artifact verification, page-change summaries, click-dispatch diagnostics only after native temporary-marker target identity readback (otherwise abstaining), inspection-only recovery for upstream overlay-blocked clicks, deterministic document scroll before wheel/no-op diagnostics and instant CSS-container movement, shorthand compilers, and redaction. | Real-upstream core matrix (including snapshot-ref select) plus fake core matrix for passthrough, ordering, diagnostics, and compiler validation. | Supported. Upstream semantics remain upstream-owned. |
|
|
158
|
-
| Sessions, state, tabs, frames, dialogs, and windows | 28 canonical tokens from baseline section `state-tabs-frames-dialogs`; see [`scripts/agent-browser-capability-baseline.mjs`](../scripts/agent-browser-capability-baseline.mjs) and generated [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#session-state-frames-dialogs-windows-and-inspection-commands). | [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#session-state-frames-dialogs-windows-and-inspection-commands), stateful workflow notes, [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details). | Stateful summaries redact credentials while preserving restore identifiers and every session/state list row; explicit targets and paths pass through. Runtime retains state artifact verification, sessionless local command planning, automatic managed restore, tab target
|
|
158
|
+
| Sessions, state, tabs, frames, dialogs, and windows | 28 canonical tokens from baseline section `state-tabs-frames-dialogs`; see [`scripts/agent-browser-capability-baseline.mjs`](../scripts/agent-browser-capability-baseline.mjs) and generated [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#session-state-frames-dialogs-windows-and-inspection-commands). | [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#session-state-frames-dialogs-windows-and-inspection-commands), stateful workflow notes, [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details). | Stateful summaries redact credentials while preserving restore identifiers and every session/state list row; explicit targets and paths pass through. Runtime retains state artifact verification, sessionless local command planning, automatic managed restore, native-CDP-ID tab recovery across duplicate URLs and transcript replay, reached failed-batch target invalidation/live verification, active-target refresh after tab close, and close alias cleanup. | Extension tab/ref tests, real-upstream stable-id/label tab lifecycle, runtime session/resume tests, presentation redaction tests, lifecycle harness. | Supported. External profile/auth state remains operator-owned. |
|
|
159
159
|
| Network, storage, artifacts, diagnostics, and performance | Canonical tokens from baseline section `network-storage-artifacts-diagnostics`; see [`scripts/agent-browser-capability-baseline.mjs`](../scripts/agent-browser-capability-baseline.mjs) and generated [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#page-state-finding-mouse-settings-network-and-storage). | [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#page-state-finding-mouse-settings-network-and-storage), diagnostic sections, [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details). | Thin passthrough plus native-owned click/download execution for all downloads (including loopback, Blob exports, and redirects), compact diagnostics with identical credential redaction for page/origin/URL-filtered details and exports, route-mock warnings, useful-but-redacted storage output, stream idempotency normalization, FPS/cursor/contact-sheet-aware recording destinations/pinning and contact-sheet image verification, truthful ref-refresh and missing-ffmpeg warnings, positive WebMCP navigation hints and catalog updates, sensitive-data redaction, timeout bounds, and cleanup-pair guidance. | Fake non-core matrix and safe real-upstream coverage for network/HAR, diff, trace/profiler, console/errors/highlight, stream, vitals, React missing-renderer, native FPS recording path/page checks and WebMCP navigation hints. | Supported. Environment-sensitive operations need suitable local/browser state. |
|
|
160
160
|
| Batch, auth, confirmations, setup, dashboard, devices, and AI commands | Canonical tokens from baseline section `batch-auth-setup-ai`; see [`scripts/agent-browser-capability-baseline.mjs`](../scripts/agent-browser-capability-baseline.mjs) and generated [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#batch-auth-confirmations-sessions-chat-dashboard-devices-and-setup). | [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#batch-auth-confirmations-sessions-chat-dashboard-devices-and-setup), README security notes, release docs. | Native-tool batch stdin, generated QA/lookup batch plans, auth/confirmation redaction, sessionless local auth/setup/dashboard/doctor/plugin planning, upgrade-only native text normalization with error precedence and explicit-JSON results, plugin list/show JSON envelope normalization, bare-`mcp` validation with `mcp --help` preserved, timeout/cleanup guidance. | Parser/runtime plugin and MCP unit coverage; fake-upstream plugin list/show and MCP help/blocking coverage; registered upgrade text/success/nonzero/timeout/abort/JSON controls in `test/agent-browser.upgrade-output.test.ts`; real-upstream plugin list shape probe; structured input-mode tests. | Supported. Interactive side-effecting setup/auth/chat remains upstream-owned. `plugin` is local/sessionless; `mcp` is external-client-only except help; `auth login --credential-provider` resolves credentials via a plugin; `install --with-deps` failures remain upstream-owned. |
|
|
161
161
|
| Global flags, config, providers, policy, and environment | Canonical tokens from baseline section `options-and-env`; see [`scripts/agent-browser-capability-baseline.mjs`](../scripts/agent-browser-capability-baseline.mjs) and generated [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#important-global-flags-config-and-environment). | [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#important-global-flags-config-and-environment), README provider/setup notes, [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#sessionmode), architecture/runtime docs. | Runtime preserves caller-requested JSON for early filters, scroll results, and preparation failures without output-file prose (help/version remains native text), and handles command discovery, value-flag prevalidation, launch-scoped flags, redacted echoes, fresh-session recovery hints, explicit sessions, provider/device launch-scoping, parent env forwarding with consistent managed idle-timeout/socket/operation-timeout overrides on top-level and helper subprocesses, subprocess completion, and package-owned Pi-scoped config for optional companion features. | Runtime tests for flags/planning/redaction/session behavior; process/ref tests for env consistency and stdio-linger completion; config/web-search/CLI tests; fake provider/specialized-skill matrix; package doctor. | Supported. Provider clouds, iOS/Appium, proxies, profiles, and credentials require external setup. |
|