pi-agent-browser-native 0.6.12 → 0.6.13
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 +13 -1
- package/README.md +16 -14
- package/dist/extensions/agent-browser/index.js +17 -6
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +32 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +4 -4
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +18 -6
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +4 -2
- package/dist/extensions/agent-browser/lib/orchestration/native-session-defaults.js +100 -9
- package/dist/extensions/agent-browser/lib/playbook.js +4 -4
- package/dist/extensions/agent-browser/lib/process.js +18 -5
- package/docs/ARCHITECTURE.md +9 -4
- package/docs/COMMAND_REFERENCE.md +4 -1
- package/docs/RELEASE.md +4 -4
- package/docs/SUPPORT_MATRIX.md +3 -1
- package/docs/TOOL_CONTRACT.md +6 -2
- package/docs/platform-smoke.md +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.6.13 - 2026-09-14
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Suppress Chrome's extra startup window using stock `--no-startup-window`, preserving headless defaults and composing caller launch arguments. URL-less `open` now uses native lazy URL reads so active pages keep their browser, profile, and URL across direct, batch, and script calls. External engines and attachments remain unchanged.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Give ordinary browser calls a native session and restore key per root Pi session. Parent/descendant groups share one browser without a global queue or child-exit teardown. Scope named Chrome profile bootstrap to automatic roots; preserve explicit browsers, script, and Electron isolation.
|
|
12
|
+
|
|
13
|
+
### Validation
|
|
14
|
+
|
|
15
|
+
- Start managed-lifecycle release checks explicitly in fresh mode; retain root-session ownership without leaving test browsers open.
|
|
4
16
|
|
|
5
17
|
## 0.6.12 - 2026-09-13
|
|
6
18
|
|
package/README.md
CHANGED
|
@@ -317,6 +317,10 @@ Open a page and inspect it (first-call recipe: open → snapshot -i → interact
|
|
|
317
317
|
|
|
318
318
|
Chromium launch switches belong in the value of `--args`, for example `{ "args": ["--args", "--no-sandbox", "open", "https://example.com"], "sessionMode": "fresh" }` when that switch is needed. A bare `--no-sandbox` in the command slot is an unknown command; in an `open` / `goto` / `navigate` option position, upstream ignores it. The wrapper diagnoses those forms without rejecting literal text, select values or paths in other commands. For batches, put `--args` in top-level `args` before `batch`, not inside a row.
|
|
319
319
|
|
|
320
|
+
Local Chrome startup uses the stock binary's `--args --no-startup-window` capability to suppress Chrome's extra startup window. Headless remains the default. Caller launch arguments keep native CLI → environment → config precedence and follow this default; browsers started before this change with custom arguments may restart once when their native launch configuration changes. Ordinary follow-ups do not resend bootstrap settings. CDP, providers, Electron, and Lightpanda remain native-owned; no tabs are deleted or profiles edited by this behavior.
|
|
321
|
+
|
|
322
|
+
`open` without a URL uses native `get url`: it launches when needed and otherwise preserves the current page. This also applies to effective batch rows and script calls. `details.args` preserves the request, while `effectiveArgs`, command, URL data, and lifecycle describe the actual native operation. Use `open about:blank` only when you intend to navigate there. Help/version requests remain unchanged.
|
|
323
|
+
|
|
320
324
|
Watch a browser window during a demo, QA run, or user-completed login by adding upstream's global `--headed` flag on the first launch. Use `sessionMode: "fresh"` if a managed session may already exist, because headed/headless state is launch-scoped. A successful first/fresh local wrapper-managed headed launch, including a launch inside `batch`, returns `details.browserWindow = { mode: "headed", ownership: "wrapper-managed", sessionName, visibility: "unverified" }` and one visible handoff sentence; CDP, auto-connect, provider, and Electron attachments do not. This proves that the wrapper requested and upstream launched headed mode, not that the OS window is visible on the user's display; remote, container, or virtual-display setups can still hide it. After the user finishes in the window, continue with `sessionMode: "auto"`.
|
|
321
325
|
|
|
322
326
|
```json
|
|
@@ -608,33 +612,31 @@ After a successful unnamed fresh launch, later default `sessionMode: "auto"` cal
|
|
|
608
612
|
|
|
609
613
|
### Shared browser defaults
|
|
610
614
|
|
|
611
|
-
|
|
615
|
+
Ordinary browser calls use one native named browser per **root Pi session**. A parent and its subagents share that browser; unrelated roots get different names and can browse concurrently. `pi-subagents` supplies `PI_SUBAGENT_ROOT_SESSION_ID` through its existing launch records, including detached runs, delegated forks, grandchildren, and revived children. Ordinary Pi new/fork/clone sessions get their own root identity; resuming the same root or changing its cwd keeps its browser name.
|
|
616
|
+
|
|
617
|
+
Coordinate multi-call navigation within the group using existing subagent/intercom tools. Native commands are serialized by each browser daemon; wrapper helper queues are process-local. There is no global focus queue. Parent or child exit does not close the group browser. Native idle policy applies; explicitly close only your group's browser when the group is finished.
|
|
618
|
+
|
|
619
|
+
To bootstrap new root browsers from an authenticated normal Chrome profile, set the existing **Pi package** config in `~/.pi/config/pi-agent-browser-native/config.json`:
|
|
612
620
|
|
|
613
621
|
```json
|
|
614
622
|
{
|
|
615
|
-
"
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
623
|
+
"browser": {
|
|
624
|
+
"defaultProfile": { "name": "Default", "policy": "always" },
|
|
625
|
+
"executablePath": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
|
626
|
+
}
|
|
619
627
|
}
|
|
620
628
|
```
|
|
621
629
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
Pi quit does not close a caller-owned shared browser or impose its implicit 15-minute idle timeout. Native idle policy applies, including its default headed-browser exemption; explicit `--idle-timeout` applies to every helper in that call too. Keep native idle settings consistent between calls because changing them can restart the daemon. `AGENT_BROWSER_SOCKET_DIR` is honored for caller-owned sessions; the wrapper-specific socket override still takes priority.
|
|
630
|
+
For inactive automatic roots, global/override `always` profile **names** and the configured executable bootstrap native launch through a scoped subprocess environment shared with helpers. Active roots retain native launch settings instead of resending defaults that could replace an explicitly profiled browser; their existing restore key is preserved, including follow-ups naming the same root browser. Native Chrome profile names use independent temporary copies, not the source profile's lock. Profile paths and other profile policies remain advisory; project guidance does not change a root's launch settings when a child changes cwd. Explicit unrelated sessions, native launch/config/environment choices, isolated `script`, and Electron launches do not receive the root bootstrap defaults. Do not set a global native profile merely to enable this feature: it can change the launch configuration of existing unrelated browsers.
|
|
625
631
|
|
|
626
|
-
|
|
632
|
+
Each automatic root has its own stable native restore key. Native save/restore preserves cookies, localStorage, and sessionStorage across normal close/restart; it does not merge auth between roots or save new IndexedDB-only credentials, service workers, or page-memory grants. Named profile copies can bootstrap existing IndexedDB, but are discarded on close and never write back to the communal profile. Verify the actual application after restart, not just a successful restore receipt. If an application needs unsupported state, use a supported persistent **per-root** profile and collect required sign-ins together; never point concurrent roots at one writable profile directory. Sites can expire sessions or require a fresh human challenge.
|
|
627
633
|
|
|
628
|
-
|
|
634
|
+
Explicit native `session` defaults (config or `AGENT_BROWSER_SESSION`) still override automatic root selection, including `sessionMode: "fresh"`. They intentionally share whichever browser the caller selected. Per-call session/namespace flags retain precedence. Ordinary launch options such as `--profile` or `--executable-path` still use the root-group name. Repeat explicit options when deliberately relaunching a persistent per-root profile after close. Explicit `fresh` and attachment choices retain their existing managed-session lifecycle; later auto calls follow that selected managed session. Use `session info` with the reported `sessionName` for read-only native daemon/browser evidence. Unknown native fields remain unknown.
|
|
629
635
|
|
|
630
636
|
Policy-required URL reads keep the two-call read → confirm/deny flow. Returned actions name the actual native namespace/session, including `default`; routing survives transcript resume and branch changes. Only explicit-read provenance plus native `capabilities.readRequiresConfirmation: true` permits matching confirm/deny without page helpers. This capability includes native confirmation-ID checking. Legacy prompts retain correct routing but normal page checks; DOM or content-shaped prompts never receive the exemption.
|
|
631
637
|
|
|
632
638
|
Full live identity, browser-independent native reads/confirmations and detailed recording receipts require the companion upstream fixes; the current recommended release does not yet supply all of them. Older supported versions remain accepted with unavailable evidence marked unknown. The extension does not upgrade or restart your installed browser to obtain these fields.
|
|
633
639
|
|
|
634
|
-
Coordinate tabs/navigation between agents sharing a browser; the wrapper's per-session queue is local to one Pi process. Persistent profile storage preserves browser data, not a promise of permanent website login: sites can expire or revoke sessions and require a new human challenge. Standing permission avoids repeated permission requests, but cannot change those site policies.
|
|
635
|
-
|
|
636
|
-
Before enabling global native profile defaults, update and fully restart every participating Pi runtime. Older loaded wrappers can still let disposable `script` launches read HOME config; editing this source or running `/reload` does not replace their cached code. Coordinate the browser cutover too: moving an existing daemon from the old wrapper's explicit 15-minute idle timeout to native idle policy changes its launch fingerprint and can restart it. Do not change that policy while another agent is using the browser.
|
|
637
|
-
|
|
638
640
|
### Profile selection
|
|
639
641
|
|
|
640
642
|
The wrapper does not clone profiles or hide what upstream Chrome/Chromium profile or executable you chose. Passing `--profile` or `--executable-path` is an explicit upstream `agent-browser` choice. Visible page content from real profiles is model-visible and may persist in transcripts or saved artifacts; redaction protects credential-like cookie/storage/auth values, not ordinary page text you asked the browser to read. Redactions use `[REDACTED]` (URL-encoded in parsed URLs); ordinary technical phrases such as `bearer token` stay intact. URL redaction includes `code`, `authorization_session_id`, and auth-context `state` / `nonce` across visible text, details, spills, and `outputPath` exports. URLs needing no redaction keep their original spelling.
|
|
@@ -1370,7 +1370,12 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1370
1370
|
}
|
|
1371
1371
|
if (resolvedInput.kind !== "script")
|
|
1372
1372
|
await beforeExecute?.(nativeToolCallId, { ...ctx, signal });
|
|
1373
|
-
|
|
1373
|
+
const runtimeBrowserConfig = loadAgentBrowserConfigSync({ cwd: ctx.cwd, includeProjectConfig: shouldIncludeProjectConfig(ctx) });
|
|
1374
|
+
const rootProfile = runtimeBrowserConfig.trustedBrowserDefaultProfile;
|
|
1375
|
+
const pendingReadConfirmation = sessionPageState.findReadConfirmation(resolvedInput.toolArgs, resolveAgentBrowserNamespace(resolvedInput.toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE));
|
|
1376
|
+
const rootSessionId = process.env.PI_SUBAGENT_CHILD === "1" && process.env.PI_SUBAGENT_ROOT_SESSION_ID
|
|
1377
|
+
? process.env.PI_SUBAGENT_ROOT_SESSION_ID : ctx.sessionManager.getSessionId();
|
|
1378
|
+
return withNativeSessionDefaults(resolvedInput, ctx.cwd, signal, async (resolvedInput, withLaunchDefaults) => {
|
|
1374
1379
|
const readConfirmation = sessionPageState.findReadConfirmation(resolvedInput.toolArgs, resolveAgentBrowserNamespace(resolvedInput.toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE));
|
|
1375
1380
|
if (readConfirmation)
|
|
1376
1381
|
resolvedInput = { ...resolvedInput, toolArgs: scopeReadConfirmationArgs(resolvedInput.toolArgs, readConfirmation) };
|
|
@@ -1614,7 +1619,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1614
1619
|
const callerOwnedSessionQueueKey = !serializeBrowserCommand && explicitSessionName
|
|
1615
1620
|
? getSessionContextKey(explicitSessionName, callerOwnedSessionNamespace) ?? explicitSessionName
|
|
1616
1621
|
: undefined;
|
|
1617
|
-
const runBrowserCommand = async () => {
|
|
1622
|
+
const runBrowserCommand = async (daemonInactive) => {
|
|
1618
1623
|
flushRecordingReservations();
|
|
1619
1624
|
const branchRestoreGenerationAtStart = branchRestoreGeneration;
|
|
1620
1625
|
const generationAtStart = branchStateGeneration;
|
|
@@ -1664,6 +1669,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1664
1669
|
?? getSessionContextKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace);
|
|
1665
1670
|
const attachedSessionKnown = reusableSessionKey !== undefined && attachedSessionKeys.has(reusableSessionKey);
|
|
1666
1671
|
let result = await runAgentBrowserTool({
|
|
1672
|
+
daemonInactive,
|
|
1667
1673
|
ctx,
|
|
1668
1674
|
cwd: ctx.cwd,
|
|
1669
1675
|
electronPostCommandStatusSettleMs: ELECTRON_POST_COMMAND_STATUS_SETTLE_MS,
|
|
@@ -1771,6 +1777,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1771
1777
|
return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, preserveTextContent: Array.isArray(params.args) && params.args.includes("--json"), result: warnRecordingPersistence(result) });
|
|
1772
1778
|
};
|
|
1773
1779
|
const closesAllSessions = commandClosesAllSessions(toolArgs, resolvedInput.toolStdin);
|
|
1780
|
+
const runWithLaunchDefaults = () => withLaunchDefaults ? withLaunchDefaults(runBrowserCommand) : runBrowserCommand();
|
|
1774
1781
|
const runWithinSessionQueue = () => {
|
|
1775
1782
|
if (closesAllSessions)
|
|
1776
1783
|
return managedSessionExecutionQueue.run(() => {
|
|
@@ -1783,13 +1790,13 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1783
1790
|
sessionMode: params.sessionMode ?? "auto",
|
|
1784
1791
|
stdin: resolvedInput.toolStdin,
|
|
1785
1792
|
});
|
|
1786
|
-
return callerOwnedSessionExecutionQueues.runExclusive(plan.namespace,
|
|
1793
|
+
return callerOwnedSessionExecutionQueues.runExclusive(plan.namespace, runWithLaunchDefaults);
|
|
1787
1794
|
});
|
|
1788
1795
|
if (serializeBrowserCommand)
|
|
1789
|
-
return managedSessionExecutionQueue.run(
|
|
1796
|
+
return managedSessionExecutionQueue.run(runWithLaunchDefaults);
|
|
1790
1797
|
return callerOwnedSessionQueueKey
|
|
1791
|
-
? callerOwnedSessionExecutionQueues.run(callerOwnedSessionQueueKey, callerOwnedSessionNamespace,
|
|
1792
|
-
:
|
|
1798
|
+
? callerOwnedSessionExecutionQueues.run(callerOwnedSessionQueueKey, callerOwnedSessionNamespace, runWithLaunchDefaults)
|
|
1799
|
+
: runWithLaunchDefaults();
|
|
1793
1800
|
};
|
|
1794
1801
|
if (!commandTouchesArtifactLifecycle(toolArgs, resolvedInput.toolStdin, outputPath))
|
|
1795
1802
|
return runWithinSessionQueue();
|
|
@@ -1814,6 +1821,10 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1814
1821
|
validationError: artifactValidationError,
|
|
1815
1822
|
}));
|
|
1816
1823
|
});
|
|
1824
|
+
}, managedSessionActive || freshSessionOrdinal > 0 || params.sessionMode === "fresh" || pendingReadConfirmation ? undefined : {
|
|
1825
|
+
id: rootSessionId,
|
|
1826
|
+
profile: rootProfile?.policy === "always" && !/[\\/~]/.test(rootProfile.name) ? rootProfile.name : undefined,
|
|
1827
|
+
executablePath: runtimeBrowserConfig.trustedBrowserExecutablePath,
|
|
1817
1828
|
});
|
|
1818
1829
|
},
|
|
1819
1830
|
};
|
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
import { projectUpstreamGlobalFlags } from "../argv-grammar.js";
|
|
2
|
+
/** Bare open's native launch action drops prior launch options. URL reads lazily launch without navigating. */
|
|
3
|
+
export function normalizeUrlLessOpen(args, stdin, batchStep = false) {
|
|
4
|
+
const { tokens, indices } = batchStep ? { tokens: args, indices: args.map((_, index) => index) } : projectUpstreamGlobalFlags(args);
|
|
5
|
+
if (tokens[0] === "open" && !tokens.slice(1).some(token => !token.startsWith("--"))) {
|
|
6
|
+
const index = indices[0];
|
|
7
|
+
return { args: [...args.slice(0, index), "get", "url", ...args.slice(index + 1)], stdin };
|
|
8
|
+
}
|
|
9
|
+
if (tokens[0] !== "batch")
|
|
10
|
+
return { args, stdin };
|
|
11
|
+
const rawSteps = tokens.slice(1).flatMap((token, offset) => {
|
|
12
|
+
const step = token === "--bail" ? undefined : parseBatchCommandArgument(token).step;
|
|
13
|
+
return step ? [{ index: indices[offset + 1], step }] : [];
|
|
14
|
+
});
|
|
15
|
+
if (tokens.slice(1).some(token => token !== "--bail")) {
|
|
16
|
+
let normalized = args;
|
|
17
|
+
for (const { index, step } of rawSteps) {
|
|
18
|
+
const row = normalizeUrlLessOpen(step, undefined, true).args;
|
|
19
|
+
if (row === step)
|
|
20
|
+
continue;
|
|
21
|
+
if (normalized === args)
|
|
22
|
+
normalized = [...args];
|
|
23
|
+
normalized[index] = row.map(token => `'${token.replaceAll("'", "'\\''")}'`).join(" ");
|
|
24
|
+
}
|
|
25
|
+
return { args: normalized, stdin };
|
|
26
|
+
}
|
|
27
|
+
const steps = parseUserBatchStdin(stdin).steps;
|
|
28
|
+
if (!steps?.length)
|
|
29
|
+
return { args, stdin };
|
|
30
|
+
const normalized = steps.map(step => normalizeUrlLessOpen(step, undefined, true).args);
|
|
31
|
+
return { args, stdin: normalized.some((step, index) => step !== steps[index]) ? JSON.stringify(normalized) : stdin };
|
|
32
|
+
}
|
|
1
33
|
const BATCH_STDIN_EXAMPLE = ' Example: { "args": ["batch"], "stdin": "[[\\"get\\",\\"title\\"],[\\"get\\",\\"url\\"]]" }';
|
|
2
34
|
// Mirror upstream commands::shell_words_split so policy inspection sees the same argv.
|
|
3
35
|
export function parseBatchCommandArgument(command) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runAgentBrowserProcess, withAttachedBrowserSessionContext } from "../../process.js";
|
|
1
|
+
import { runAgentBrowserProcess, withAttachedBrowserSessionContext, withChromeStartupArgs } from "../../process.js";
|
|
2
2
|
import { isRecord } from "../../parsing.js";
|
|
3
3
|
import { withOwnedManagedSessionContext } from "../../managed-session-restore.js";
|
|
4
4
|
import { cleanupClickDispatchProbe } from "./click-dispatch.js";
|
|
@@ -9,7 +9,7 @@ import { processBrowserOutput } from "./process-output.js";
|
|
|
9
9
|
export { closeManagedSession } from "./managed-session-daemon-policy.js";
|
|
10
10
|
export { getSessionContextKey } from "./session-state.js";
|
|
11
11
|
export async function runAgentBrowserTool(options) {
|
|
12
|
-
const result = await withAttachedBrowserSessionContext(options.preserveAttachedBrowserSession === true, () => runAgentBrowserToolInContext(options));
|
|
12
|
+
const result = await withChromeStartupArgs(options.input.persistentChromeArgs, () => withAttachedBrowserSessionContext(options.preserveAttachedBrowserSession === true, () => runAgentBrowserToolInContext(options)));
|
|
13
13
|
const details = isRecord(result.details) ? result.details : undefined;
|
|
14
14
|
const sessionKey = getSessionContextKey(typeof details?.sessionName === "string" ? details.sessionName : undefined, typeof details?.namespace === "string" ? details.namespace : undefined);
|
|
15
15
|
const page = options.state.sessionPageState.get(sessionKey);
|
|
@@ -29,7 +29,7 @@ async function runAgentBrowserToolInContext(options) {
|
|
|
29
29
|
return await withOwnedManagedSessionContext(ownedManagedSession, async () => {
|
|
30
30
|
try {
|
|
31
31
|
const artifactRunStartedAtMs = Date.now();
|
|
32
|
-
const processResult = await runAgentBrowserProcess({
|
|
32
|
+
const processResult = await withChromeStartupArgs(prepared.chromeStartupArgs, () => runAgentBrowserProcess({
|
|
33
33
|
args: prepared.processArgs,
|
|
34
34
|
browserIndependentReadConfirmation: prepared.readConfirmation !== undefined,
|
|
35
35
|
cwd: options.cwd,
|
|
@@ -43,7 +43,7 @@ async function runAgentBrowserToolInContext(options) {
|
|
|
43
43
|
signal: options.signal,
|
|
44
44
|
stdin: prepared.processStdin,
|
|
45
45
|
timeoutMs: prepared.processTimeoutMs,
|
|
46
|
-
});
|
|
46
|
+
}));
|
|
47
47
|
const missingBinaryResult = await buildMissingBinaryFailureResult({
|
|
48
48
|
compatibilityWorkaround: prepared.compatibilityWorkaround,
|
|
49
49
|
electronLaunch: prepared.electronLaunch,
|
|
@@ -2,7 +2,9 @@ import { copyFile, mkdir } from "node:fs/promises";
|
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
3
|
import { extractExplicitSessionName, getBooleanFlagValue, isUpstreamEnvFlagEnabled, projectUpstreamGlobalFlags, resolveAgentBrowserNamespace } from "../../argv-grammar.js";
|
|
4
4
|
import { isCloseCommand } from "../../command-taxonomy.js";
|
|
5
|
-
import { isBrowserIndependentRead } from "../../command-policy.js";
|
|
5
|
+
import { isBrowserIndependentRead, needsManagedSession } from "../../command-policy.js";
|
|
6
|
+
import { parseArgvDescriptor } from "../../argv-descriptor.js";
|
|
7
|
+
import { prepareAgentBrowserSpawnArgs, withChromeStartupArgs } from "../../process.js";
|
|
6
8
|
import { cleanupElectronLaunchResources } from "../../electron/cleanup.js";
|
|
7
9
|
import { launchElectronApp } from "../../electron/launch.js";
|
|
8
10
|
import { pathExists } from "../../fs-utils.js";
|
|
@@ -22,7 +24,7 @@ import { buildExecutionPlan, canUseHeadlessCompatibilityUserAgent, createFreshSe
|
|
|
22
24
|
import { buildOwnedManagedSessionRestoreContext, resolveExplicitAutosaveInterval, withOwnedManagedSessionContext, } from "../../managed-session-restore.js";
|
|
23
25
|
import { getAgentBrowserProcessEnvironment } from "../../process-environment.js";
|
|
24
26
|
import { getExplicitSessionPageVerificationRequirement, getPageTargetValidationError, } from "../../page-target-validation.js";
|
|
25
|
-
import { acquireOwnedManagedSessionDaemonPolicy, getRunningHeadedAutosavePolicyChangeError } from "./managed-session-daemon-policy.js";
|
|
27
|
+
import { acquireOwnedManagedSessionDaemonPolicy, getRunningHeadedAutosavePolicyChangeError, inspectManagedSessionDaemon } from "./managed-session-daemon-policy.js";
|
|
26
28
|
import { buildManagedSessionOutcome, buildSessionDetailFields, buildStaleRefPreflight, getSessionContextKey, findElectronLaunchRecordForSession, extractStringResultField, ensureSessionTabTarget, getGuardedRefUsage, getTraceOwnerGuardMessage, runSessionCommandData, shouldPinSessionTabForCommand, } from "./session-state.js";
|
|
27
29
|
import { getUpstreamEffectiveBatchSteps, parseBatchStdinJsonArray } from "../batch-stdin.js";
|
|
28
30
|
import { buildElectronHostFailureResult, formatAgentBrowserNextActionsText, getElectronLaunchFailureCategory, redactRecoveryHint } from "./final-result.js";
|
|
@@ -551,7 +553,16 @@ export async function prepareBrowserRun(options) {
|
|
|
551
553
|
};
|
|
552
554
|
}
|
|
553
555
|
}
|
|
554
|
-
|
|
556
|
+
let chromeStartupArgs;
|
|
557
|
+
if (options.input.chromeStartupArgs !== undefined && !options.preserveAttachedBrowserSession && !browserIndependent
|
|
558
|
+
&& !executionPlan.validationError && !isCloseCommand(executionPlan.commandInfo.command)
|
|
559
|
+
&& needsManagedSession(parseArgvDescriptor(preparedArgs.args), runtimeToolStdin)) {
|
|
560
|
+
const inactive = ownedManagedSession ? managedSessionDaemonInactive : options.daemonInactive
|
|
561
|
+
?? (executionPlan.sessionName !== undefined && (await inspectManagedSessionDaemon({ cwd, signal, sessionName: executionPlan.sessionName, namespace: executionPlan.namespace })).status === "inactive");
|
|
562
|
+
if (inactive || options.input.configuredChromeLaunch)
|
|
563
|
+
chromeStartupArgs = options.input.chromeStartupArgs;
|
|
564
|
+
}
|
|
565
|
+
return await withChromeStartupArgs(chromeStartupArgs, () => withOwnedManagedSessionContext(ownedManagedSession, async () => {
|
|
555
566
|
const managedSessionRestoreDisabled = () => state.managedSessionRestoreState.isDisabled(executionPlan.sessionName, executionPlan.namespace);
|
|
556
567
|
const sessionStateKey = getSessionContextKey(executionPlan.sessionName, executionPlan.namespace);
|
|
557
568
|
const priorSessionPageState = sessionPageState.get(sessionStateKey);
|
|
@@ -737,7 +748,7 @@ export async function prepareBrowserRun(options) {
|
|
|
737
748
|
requirement: livePageRequirement,
|
|
738
749
|
stdin: runtimeToolStdin,
|
|
739
750
|
});
|
|
740
|
-
const redactedEffectiveArgs = redactInvocationArgs(executionPlan.effectiveArgs);
|
|
751
|
+
const redactedEffectiveArgs = redactInvocationArgs(prepareAgentBrowserSpawnArgs(executionPlan.effectiveArgs, undefined, options.preserveAttachedBrowserSession, chromeStartupArgs));
|
|
741
752
|
const redactedRecoveryHint = redactRecoveryHint(executionPlan.recoveryHint);
|
|
742
753
|
const compatibilityWorkaround = executionPlan.compatibilityWorkaround;
|
|
743
754
|
const statePatch = executionPlan.managedSessionName === freshSessionName
|
|
@@ -1022,7 +1033,7 @@ export async function prepareBrowserRun(options) {
|
|
|
1022
1033
|
catch { }
|
|
1023
1034
|
}
|
|
1024
1035
|
const processTimeoutMs = options.params.timeoutMs ?? getDialogAwareProcessTimeoutMs(commandTokens, promptRefSnapshot, processStdin) ?? getCommandAwareProcessTimeoutMs(commandTokens, processStdin, readTimeoutPageUrl);
|
|
1025
|
-
const redactedProcessArgs = redactInvocationArgs(processArgs);
|
|
1036
|
+
const redactedProcessArgs = redactInvocationArgs(prepareAgentBrowserSpawnArgs(processArgs, ownedManagedSession?.compatibilityUserAgent, options.preserveAttachedBrowserSession, chromeStartupArgs));
|
|
1026
1037
|
const scrollAmount = Number(commandTokens.find((token) => /^\d+(?:\.\d+)?$/.test(token)));
|
|
1027
1038
|
const shouldProbeScrollNoop = executionPlan.commandInfo.command === "scroll" && executionPlan.startupScopedFlags.length === 0 && (state.managedSessionActive || sessionMode === "fresh") && (!Number.isFinite(scrollAmount) || scrollAmount >= 500);
|
|
1028
1039
|
const scrollPositionBefore = shouldProbeScrollNoop
|
|
@@ -1043,6 +1054,7 @@ export async function prepareBrowserRun(options) {
|
|
|
1043
1054
|
return {
|
|
1044
1055
|
kind: "ready",
|
|
1045
1056
|
prepared: {
|
|
1057
|
+
chromeStartupArgs,
|
|
1046
1058
|
commandTokens,
|
|
1047
1059
|
headedLaunch,
|
|
1048
1060
|
providerLaunch,
|
|
@@ -1091,7 +1103,7 @@ export async function prepareBrowserRun(options) {
|
|
|
1091
1103
|
userRequestedJson,
|
|
1092
1104
|
},
|
|
1093
1105
|
};
|
|
1094
|
-
});
|
|
1106
|
+
}));
|
|
1095
1107
|
}
|
|
1096
1108
|
finally {
|
|
1097
1109
|
if (!managedSessionPolicyLockTransferred)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseArgvDescriptor } from "../argv-descriptor.js";
|
|
2
|
-
import {
|
|
2
|
+
import { normalizeUrlLessOpen } from "./batch-stdin.js";
|
|
3
|
+
import { isPlainTextInspectionArgs, validateToolArgs, redactInvocationArgs, redactSensitiveText } from "../runtime.js";
|
|
3
4
|
import { buildAgentBrowserResultCategoryDetails } from "../results/categories.js";
|
|
4
5
|
import { compileAgentBrowserElectron } from "../input-modes/electron.js";
|
|
5
6
|
import { compileAgentBrowserJob, compileAgentBrowserQaPreset } from "../input-modes/job.js";
|
|
@@ -167,7 +168,8 @@ export function resolveAgentBrowserInput(options) {
|
|
|
167
168
|
const redactedCompiledNetworkSourceLookup = redactCompiledNetworkSourceLookup(compiledNetworkSourceLookup);
|
|
168
169
|
const redactedCompiledQaPreset = compiledQaPreset && redactedCompiledJob ? { ...redactedCompiledJob, checks: compiledQaPreset.checks } : undefined;
|
|
169
170
|
const redactedCompiledSourceLookup = redactCompiledSourceLookup(compiledSourceLookup);
|
|
170
|
-
const
|
|
171
|
+
const normalized = validationError || isPlainTextInspectionArgs(toolArgs) ? { args: toolArgs, stdin: toolStdin } : normalizeUrlLessOpen(toolArgs, toolStdin);
|
|
172
|
+
const resolvedBase = { redactedArgs, toolArgs: normalized.args, toolStdin: normalized.stdin };
|
|
171
173
|
if (validationError) {
|
|
172
174
|
return {
|
|
173
175
|
...resolvedBase,
|
|
@@ -1,15 +1,42 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { readFile, rm } from "node:fs/promises";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
4
|
import { join, resolve } from "node:path";
|
|
4
|
-
import { extractExplicitSessionName, scanUpstreamGlobalFlagOccurrences } from "../argv-grammar.js";
|
|
5
|
+
import { extractExplicitSessionName, getBooleanFlagValue, isUpstreamEnvFlagEnabled, scanUpstreamGlobalFlagOccurrences } from "../argv-grammar.js";
|
|
5
6
|
import { isRecord } from "../parsing.js";
|
|
7
|
+
import { parseArgvDescriptor } from "../argv-descriptor.js";
|
|
8
|
+
import { needsManagedSession } from "../command-policy.js";
|
|
9
|
+
import { getUpstreamEffectiveBatchSteps } from "./batch-stdin.js";
|
|
10
|
+
import { hasLaunchScopedFlagToken } from "../launch-scoped-flags.js";
|
|
6
11
|
import { getAgentBrowserProcessEnvironment, withAgentBrowserProcessEnvironment } from "../process-environment.js";
|
|
7
|
-
import { runAgentBrowserProcess } from "../process.js";
|
|
12
|
+
import { runAgentBrowserProcess, withChromeStartupArgs } from "../process.js";
|
|
8
13
|
import { parseAgentBrowserEnvelope } from "../results/envelope.js";
|
|
9
14
|
import { isPlainTextInspectionArgs } from "../runtime.js";
|
|
15
|
+
import { buildValidationFailureResult } from "./input-plan.js";
|
|
16
|
+
import { inspectManagedSessionDaemon } from "./browser-run/managed-session-daemon-policy.js";
|
|
17
|
+
import { buildMissingBinaryMessage } from "./browser-run/final-result.js";
|
|
18
|
+
// These native defaults already send a local launch command on every call; keep its args stable too.
|
|
19
|
+
const LOCAL_VALUE_DEFAULTS = ["executablePath", "profile", "state", "proxy", "args", "userAgent", "caCert", "colorScheme", "downloadPath", "engine", "allowedDomains"];
|
|
20
|
+
const LOCAL_BOOLEAN_DEFAULTS = ["headed", "allowFileAccess", "webgpu", "noWebmcp"];
|
|
21
|
+
const LOCAL_ARRAY_DEFAULTS = ["extensions", "initScripts", "enable"];
|
|
22
|
+
const nativeEnvName = (key) => `AGENT_BROWSER_${key.replace(/[A-Z]/g, letter => `_${letter}`).toUpperCase()}`;
|
|
23
|
+
function hasLocalLaunchDefaults(config, env) {
|
|
24
|
+
return LOCAL_VALUE_DEFAULTS.some(key => env[nativeEnvName(key)] !== undefined || config[key] !== undefined)
|
|
25
|
+
|| LOCAL_BOOLEAN_DEFAULTS.some(key => isUpstreamEnvFlagEnabled(env[nativeEnvName(key)]) || config[key] === true)
|
|
26
|
+
|| LOCAL_ARRAY_DEFAULTS.some(key => (env[nativeEnvName(key)]?.length ?? 0) > 0 || Array.isArray(config[key]) && config[key].length > 0)
|
|
27
|
+
|| (env.AGENT_BROWSER_CLEAR_CA_CERT !== undefined ? isUpstreamEnvFlagEnabled(env.AGENT_BROWSER_CLEAR_CA_CERT) : config.clearCaCert === true)
|
|
28
|
+
|| (env.AGENT_BROWSER_HIDE_SCROLLBARS !== undefined ? !isUpstreamEnvFlagEnabled(env.AGENT_BROWSER_HIDE_SCROLLBARS) : config.hideScrollbars === false)
|
|
29
|
+
|| ["HTTP_PROXY", "http_proxy", "HTTPS_PROXY", "https_proxy", "ALL_PROXY", "all_proxy"].some(key => env[key] !== undefined);
|
|
30
|
+
}
|
|
31
|
+
function requestsConnection(tokens, stdin) {
|
|
32
|
+
return tokens[0] === "connect" || getUpstreamEffectiveBatchSteps(tokens, stdin).some(step => requestsConnection(step));
|
|
33
|
+
}
|
|
34
|
+
function rootBrowserSessionName(rootSessionId) {
|
|
35
|
+
return `pi-root-${createHash("sha256").update(rootSessionId).digest("hex").slice(0, 24)}`;
|
|
36
|
+
}
|
|
10
37
|
// Native `session` reports the resolved name, but not whether it was configured.
|
|
11
|
-
//
|
|
12
|
-
async function readNativeIdentity(path, cwd, signal) {
|
|
38
|
+
// Native validates its schema; retain configured launch defaults so root fallbacks never replace them.
|
|
39
|
+
async function readNativeIdentity(path, cwd, signal, rootFallback, browserCommand) {
|
|
13
40
|
let config;
|
|
14
41
|
try {
|
|
15
42
|
config = JSON.parse(await readFile(path, "utf8"));
|
|
@@ -17,7 +44,11 @@ async function readNativeIdentity(path, cwd, signal) {
|
|
|
17
44
|
catch {
|
|
18
45
|
return {};
|
|
19
46
|
}
|
|
20
|
-
if (!isRecord(config)
|
|
47
|
+
if (!isRecord(config))
|
|
48
|
+
return {};
|
|
49
|
+
const rootLaunchConfig = browserCommand && (hasLocalLaunchDefaults(config, {}) || ["cdp", "autoConnect", "provider"].some((key) => config[key] !== undefined))
|
|
50
|
+
|| rootFallback && ["restore", "sessionName", "state", "allowedDomains", "profile", "executablePath"].some((key) => config[key] !== undefined);
|
|
51
|
+
if (!rootLaunchConfig && typeof config.session !== "string" && typeof config.namespace !== "string")
|
|
21
52
|
return {};
|
|
22
53
|
const result = await runAgentBrowserProcess({ args: ["--config", path, "--json", "session"], cwd, signal, timeoutMs: 5_000 });
|
|
23
54
|
try {
|
|
@@ -29,7 +60,9 @@ async function readNativeIdentity(path, cwd, signal) {
|
|
|
29
60
|
const data = parsed.envelope?.data;
|
|
30
61
|
if (!isRecord(data) || typeof data.session !== "string")
|
|
31
62
|
throw new Error("Native agent-browser session inspection returned no session name; the browser command was not run.");
|
|
63
|
+
const { session: _session, namespace: _namespace, ...defaults } = config;
|
|
32
64
|
return {
|
|
65
|
+
...defaults,
|
|
33
66
|
...(typeof config.session === "string" ? { session: data.session } : {}),
|
|
34
67
|
...(typeof config.namespace === "string" ? { namespace: config.namespace } : {}),
|
|
35
68
|
};
|
|
@@ -39,7 +72,7 @@ async function readNativeIdentity(path, cwd, signal) {
|
|
|
39
72
|
await rm(result.stdoutSpillPath, { force: true });
|
|
40
73
|
}
|
|
41
74
|
}
|
|
42
|
-
export async function withNativeSessionDefaults(input, cwd, signal, run) {
|
|
75
|
+
export async function withNativeSessionDefaults(input, cwd, signal, run, root) {
|
|
43
76
|
if (input.kind === "electron" && input.compiledElectron.action === "launch")
|
|
44
77
|
return withAgentBrowserProcessEnvironment({ AGENT_BROWSER_SESSION: undefined }, () => run(input));
|
|
45
78
|
if (input.kind === "script" || input.kind === "electron" || isPlainTextInspectionArgs(input.toolArgs))
|
|
@@ -50,10 +83,40 @@ export async function withNativeSessionDefaults(input, cwd, signal, run) {
|
|
|
50
83
|
const paths = configPath !== undefined
|
|
51
84
|
? [resolve(cwd, configPath)]
|
|
52
85
|
: [join(homedir(), ".agent-browser", "config.json"), join(cwd, "agent-browser.json")];
|
|
86
|
+
const rootName = root && rootBrowserSessionName(root.id);
|
|
87
|
+
const explicitSession = extractExplicitSessionName(input.toolArgs);
|
|
88
|
+
const rootFallback = root !== undefined && env.AGENT_BROWSER_SESSION === undefined && (explicitSession === undefined || explicitSession === rootName)
|
|
89
|
+
&& needsManagedSession(parseArgvDescriptor(input.toolArgs), input.toolStdin);
|
|
53
90
|
let identity = {};
|
|
91
|
+
const browserCommand = needsManagedSession(parseArgvDescriptor(input.toolArgs), input.toolStdin);
|
|
54
92
|
for (const path of paths)
|
|
55
|
-
identity = { ...identity, ...await readNativeIdentity(path, cwd, signal) };
|
|
56
|
-
|
|
93
|
+
identity = { ...identity, ...await readNativeIdentity(path, cwd, signal, rootFallback, browserCommand) };
|
|
94
|
+
// Native ORs environment/config booleans; only CLI false overrides configured auto-connect.
|
|
95
|
+
const autoConnect = getBooleanFlagValue(input.toolArgs, "--auto-connect")
|
|
96
|
+
?? (isUpstreamEnvFlagEnabled(env.AGENT_BROWSER_AUTO_CONNECT) || identity.autoConnect === true);
|
|
97
|
+
const attachment = hasLaunchScopedFlagToken(input.toolArgs, "--cdp")
|
|
98
|
+
|| env.AGENT_BROWSER_CDP !== undefined || identity.cdp !== undefined || autoConnect
|
|
99
|
+
|| parseArgvDescriptor(input.toolArgs).upstreamCommandTokens[0] === "connect";
|
|
100
|
+
const configuredSession = env.AGENT_BROWSER_SESSION ?? identity.session;
|
|
101
|
+
const launchArgs = scanUpstreamGlobalFlagOccurrences(input.toolArgs, "--args").at(-1)?.value ?? env.AGENT_BROWSER_ARGS ?? identity.args;
|
|
102
|
+
const engine = scanUpstreamGlobalFlagOccurrences(input.toolArgs, "--engine").at(-1)?.value ?? env.AGENT_BROWSER_ENGINE ?? identity.engine;
|
|
103
|
+
const provider = scanUpstreamGlobalFlagOccurrences(input.toolArgs, "--provider").at(-1)?.value ?? scanUpstreamGlobalFlagOccurrences(input.toolArgs, "-p").at(-1)?.value ?? env.AGENT_BROWSER_PROVIDER ?? identity.provider;
|
|
104
|
+
const tokens = parseArgvDescriptor(input.toolArgs).upstreamCommandTokens;
|
|
105
|
+
const batchAttaches = requestsConnection(tokens, input.toolStdin);
|
|
106
|
+
const localChrome = browserCommand && !attachment && !batchAttaches && provider === undefined
|
|
107
|
+
&& (engine === undefined || engine === "chrome");
|
|
108
|
+
const chromeStartupArgs = localChrome ? ["--no-startup-window", ...(typeof launchArgs === "string" ? [launchArgs] : [])].join(",") : undefined;
|
|
109
|
+
const persistentChromeArgs = hasLocalLaunchDefaults(identity, env) ? chromeStartupArgs : undefined;
|
|
110
|
+
const configuredChromeLaunch = persistentChromeArgs !== undefined || [...LOCAL_VALUE_DEFAULTS, ...LOCAL_BOOLEAN_DEFAULTS, ...LOCAL_ARRAY_DEFAULTS, "hideScrollbars", "clearCaCert"].some(key => {
|
|
111
|
+
const flag = { extensions: "--extension", initScripts: "--init-script", clearCaCert: "--no-ca-cert" }[key] ?? `--${key.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`)}`;
|
|
112
|
+
return scanUpstreamGlobalFlagOccurrences(input.toolArgs, flag).length > 0;
|
|
113
|
+
});
|
|
114
|
+
const rootDefault = rootName && configuredSession === undefined && rootFallback && !attachment
|
|
115
|
+
? rootName : undefined;
|
|
116
|
+
const session = configuredSession ?? rootDefault;
|
|
117
|
+
const restoreEligible = !["--restore", "--session-name", "--state", "--allowed-domains", "--provider", "-p", "--device"].some((flag) => hasLaunchScopedFlagToken(input.toolArgs, flag))
|
|
118
|
+
&& !["restore", "sessionName", "state", "allowedDomains", "provider"].some((key) => identity[key] !== undefined)
|
|
119
|
+
&& !["AGENT_BROWSER_RESTORE", "AGENT_BROWSER_SESSION_NAME", "AGENT_BROWSER_STATE", "AGENT_BROWSER_ALLOWED_DOMAINS", "AGENT_BROWSER_PROVIDER"].some((key) => env[key] !== undefined);
|
|
57
120
|
const namespace = env.AGENT_BROWSER_NAMESPACE ?? identity.namespace;
|
|
58
121
|
let args = input.toolArgs;
|
|
59
122
|
if (session !== undefined && extractExplicitSessionName(args) === undefined)
|
|
@@ -64,5 +127,33 @@ export async function withNativeSessionDefaults(input, cwd, signal, run) {
|
|
|
64
127
|
...(configPath !== undefined ? { AGENT_BROWSER_CONFIG: resolve(cwd, configPath) } : {}),
|
|
65
128
|
...(session !== undefined ? { AGENT_BROWSER_SESSION: session } : {}),
|
|
66
129
|
...(namespace !== undefined ? { AGENT_BROWSER_NAMESPACE: namespace } : {}),
|
|
67
|
-
}, () => run({ ...input, toolArgs: args })
|
|
130
|
+
}, () => run({ ...input, toolArgs: args, chromeStartupArgs, persistentChromeArgs, configuredChromeLaunch }, !rootDefault ? undefined : async (browserRun) => {
|
|
131
|
+
const daemon = await inspectManagedSessionDaemon({ cwd, signal, sessionName: rootDefault,
|
|
132
|
+
namespace: scanUpstreamGlobalFlagOccurrences(args, "--namespace").at(-1)?.value ?? namespace, timeoutMs: 5_000 });
|
|
133
|
+
if (daemon.status === "missing-binary")
|
|
134
|
+
return {
|
|
135
|
+
content: [{ type: "text", text: buildMissingBinaryMessage() }],
|
|
136
|
+
details: { agentBrowserStarted: false, args: input.redactedArgs, sessionName: rootDefault, resultCategory: "failure", failureCategory: "missing-binary" },
|
|
137
|
+
isError: true,
|
|
138
|
+
};
|
|
139
|
+
if (input.kind === "qa" && input.compiledQaPreset.checks.attached && daemon.status !== "active") {
|
|
140
|
+
return buildValidationFailureResult({ ...input, attemptedKind: "qa", kind: "invalid", status: "invalid", validationError: "qa.attached requires an active attached session. Open the root browser first, or select an existing native session." });
|
|
141
|
+
}
|
|
142
|
+
// An active daemon already owns its launch settings. Re-sending defaults can replace an explicitly profiled browser.
|
|
143
|
+
const bootstrap = daemon.status === "inactive";
|
|
144
|
+
let restore;
|
|
145
|
+
if (restoreEligible && daemon.status === "active")
|
|
146
|
+
restore = daemon.restoreKey ?? undefined;
|
|
147
|
+
if (restoreEligible && bootstrap)
|
|
148
|
+
restore = rootDefault;
|
|
149
|
+
const profile = scanUpstreamGlobalFlagOccurrences(args, "--profile").at(-1)?.value
|
|
150
|
+
?? (bootstrap && restoreEligible && identity.profile === undefined && env.AGENT_BROWSER_PROFILE === undefined ? root?.profile : undefined);
|
|
151
|
+
const executablePath = scanUpstreamGlobalFlagOccurrences(args, "--executable-path").at(-1)?.value
|
|
152
|
+
?? (bootstrap && identity.executablePath === undefined && env.AGENT_BROWSER_EXECUTABLE_PATH === undefined ? root?.executablePath : undefined);
|
|
153
|
+
return withAgentBrowserProcessEnvironment({
|
|
154
|
+
...(restore !== undefined ? { AGENT_BROWSER_RESTORE: restore } : {}),
|
|
155
|
+
...(profile !== undefined ? { AGENT_BROWSER_PROFILE: profile } : {}),
|
|
156
|
+
...(executablePath !== undefined ? { AGENT_BROWSER_EXECUTABLE_PATH: executablePath } : {}),
|
|
157
|
+
}, () => withChromeStartupArgs(bootstrap || configuredChromeLaunch ? chromeStartupArgs : undefined, () => browserRun(bootstrap)));
|
|
158
|
+
}));
|
|
68
159
|
}
|
|
@@ -30,7 +30,7 @@ export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
|
|
|
30
30
|
"For desktop or host-controlled rich inputs, if semanticAction fill misses, refresh refs and prefer a current editable @ref from details.richInputRecovery or the latest snapshot; focus or click that ref, then use keyboard type for framework-controlled editors that require real key events. keyboard inserttext is paste-like and can change a DOM value without updating application state, so use it only when later application-state evidence proves the edit was accepted. Do not auto-submit with Enter or a submit button unless the user flow explicitly calls for it.",
|
|
31
31
|
"Do not assume Playwright selector dialects such as text=Close or button:has-text('Close') are supported wrapper syntax unless current upstream agent-browser behavior has been verified.",
|
|
32
32
|
"For authenticated or user-specific content explicitly requested by the user, such as feeds, inboxes, account pages, or private dashboards, use a real profile only when the user/config asks for it or profiles have been inspected; do not assume --profile Default exists on every machine. Do not use a real profile for public pages just because they are dashboards. Treat visible page content from real profiles as model-visible transcript data. On macOS, copied Chrome profiles may omit encrypted cookies, so profile selection alone is not proof of authentication; verify the target page and use a user-approved headed login once when needed. Use --auto-connect only if profile-based reuse is unavailable or the task is specifically about attaching to a running debug-enabled browser. If profile/user-data-dir resolution fails, stop retrying opens, run profiles and/or doctor through agent_browser, then report what the user needs to configure.",
|
|
33
|
-
"
|
|
33
|
+
"Ordinary bare calls share one native browser within your root Pi parent/subagent group; unrelated roots browse independently. Coordinate multi-call navigation inside your group with existing subagent/intercom tools. Explicit native session defaults still win. Pi quit does not close group browsers; never close an unrelated browser.",
|
|
34
34
|
`When using launch-scoped flags (${LAUNCH_SCOPED_FLAG_LABEL}), put them on the first command for that session. If you intentionally use an explicit --session, keep using that same explicit session for follow-ups.`,
|
|
35
35
|
"Caller-owned explicit sessions are serialized per effective canonical namespace/session inside this extension while live URL checks, semantic-action snapshots, and the requested command run. For raw batches whose later content step depends on navigation, use exact batch --bail or split the calls; unsafe continue-after-navigation-failure shapes are rejected before the batch runs.",
|
|
36
36
|
"After a successful `connect`, `--cdp`, or enabled `--auto-connect` call, verify with get url and keep using the resulting session without repeating the attach flag. The wrapper remembers that attachment across active-branch reload/resume and live-checks the URL before later page reads/interactions because an attached browser can drift externally; caller config, file access, launch arguments, and environment pass through unchanged. A successful close clears the marker. When several named sessions share one Chrome, pass --pin-tab once (AGENT_BROWSER_PIN_TAB) so a closed bound tab fails as tab_gone instead of acting on a neighbor; recover with tab new or tab list. --no-pin-tab turns the sticky pin off. tab list includes each tab's CDP targetId, accepted as a tab ref.",
|
|
@@ -80,7 +80,7 @@ export const WRAPPER_TAB_RECOVERY_BEHAVIOR = [
|
|
|
80
80
|
export const RUNTIME_PROMPT_GUIDELINES = [
|
|
81
81
|
"Use agent_browser with one input mode: script, args, semanticAction, job, qa, sourceLookup/networkSourceLookup, or electron. stdin: batch/eval/auth/wrapper batch only; electron rejects it; never pass --json.",
|
|
82
82
|
"For agent_browser, use open → snapshot -i → @refs; re-snapshot after changes. In authenticated unattended/auto-approved employee flows, ordinary requested non-destructive submissions may proceed. Honor explicit stops; require explicit authorization for purchases, production-control, destructive/irreversible, or account/security/privacy changes.",
|
|
83
|
-
"agent_browser
|
|
83
|
+
"agent_browser bare calls share a root Pi browser with descendants, not unrelated roots; coordinate within the group. Native explicit sessions win. Use sessionMode=fresh for an intentional separate launch. Use requested/configured profiles only; verify auth after restart. Script is disposable.",
|
|
84
84
|
"agent_browser: exact user paths; verify artifactVerification/artifacts before success claims. Save promptGuard-required files before close; ffmpeg before recording; close keeps files; waited:timeout proves nothing.",
|
|
85
85
|
"When agent_browser details.nextActions exists, use them. Check Omitted high-value controls in dense snapshots. Dashboards: verify scroll via screenshot/snapshot.",
|
|
86
86
|
"agent_browser: read <url> for docs/text or active DOM; get title/url; get text/html/value/count <selector>; get attr <selector> <name>. Batch 3+ getters; heed visibility warnings.",
|
|
@@ -88,13 +88,13 @@ export const RUNTIME_PROMPT_GUIDELINES = [
|
|
|
88
88
|
export function buildBrowserExecutablePathGuideline(executablePath) {
|
|
89
89
|
if (!executablePath)
|
|
90
90
|
return undefined;
|
|
91
|
-
return `agent_browser config sets browser.executablePath to ${JSON.stringify(executablePath)}
|
|
91
|
+
return `agent_browser config sets browser.executablePath to ${JSON.stringify(executablePath)}. Global/override values bootstrap inactive automatic root browsers; bare calls preserve active native launch settings. Project-only values remain guidance for explicit --executable-path launches. Use sessionMode:fresh only for an intentionally separate managed browser, not routine root-group browsing. Upstream profiles lists Chrome profiles only; use a supported explicit profile path for other Chromium browsers.`;
|
|
92
92
|
}
|
|
93
93
|
export function buildBrowserDefaultProfileGuideline(profile) {
|
|
94
94
|
if (!profile || profile.policy === "explicit-only")
|
|
95
95
|
return undefined;
|
|
96
96
|
if (profile.policy === "always") {
|
|
97
|
-
return `agent_browser config sets browser.defaultProfile.name to ${JSON.stringify(profile.name)} with policy always
|
|
97
|
+
return `agent_browser config sets browser.defaultProfile.name to ${JSON.stringify(profile.name)} with policy always. Global/override Chrome profile names bootstrap automatic root browsers through independent native copies with per-root restore; bare calls and helpers preserve the active native launch settings without resending bootstrap options. Paths and project-only settings stay advisory. Explicit unrelated sessions are unchanged. Verify actual app auth after restart: native JSON restore omits new IndexedDB and memory-only credentials; use a supported per-root persistent profile if needed. Profile content is model-visible user data.`;
|
|
98
98
|
}
|
|
99
99
|
return `agent_browser config sets browser.defaultProfile.name to ${JSON.stringify(profile.name)}; for signed-in/account-specific browser tasks, reuse a configured native shared session with bare calls, otherwise start with --profile ${JSON.stringify(profile.name)} plus sessionMode:fresh unless the user asks for a different profile.`;
|
|
100
100
|
}
|
|
@@ -5,7 +5,7 @@ import { dirname, isAbsolute, join } from "node:path";
|
|
|
5
5
|
import { env as processEnv, platform as processPlatform } from "node:process";
|
|
6
6
|
import { spawn as crossSpawn } from "cross-spawn";
|
|
7
7
|
import { parseArgvDescriptor } from "./argv-descriptor.js";
|
|
8
|
-
import { extractExplicitSessionName, resolveAgentBrowserNamespace } from "./argv-grammar.js";
|
|
8
|
+
import { extractExplicitSessionName, resolveAgentBrowserNamespace, scanUpstreamGlobalFlagOccurrences } from "./argv-grammar.js";
|
|
9
9
|
import { commitManagedSessionRestoreSuppression, getManagedSessionRestoreEnv, getManagedSessionRestoreProtectedEnv, getOwnedManagedSessionCompatibilityEnv, getOwnedManagedSessionNamespaceEnv, isOwnedManagedSessionTarget, validateManagedSessionRestoreContextForSpawn, } from "./managed-session-restore.js";
|
|
10
10
|
import { getPageTargetValidationError, } from "./page-target-validation.js";
|
|
11
11
|
import { getImplicitSessionIdleTimeoutMs } from "./runtime.js";
|
|
@@ -27,6 +27,10 @@ const DEFAULT_AGENT_BROWSER_PROCESS_TIMEOUT_MS = 35_000;
|
|
|
27
27
|
/** Grace period after `exit` before resolving when `close` is delayed by inherited stdio handles. */
|
|
28
28
|
const EXIT_STDIO_GRACE_MS = 100;
|
|
29
29
|
const attachedBrowserSessionContext = new AsyncLocalStorage();
|
|
30
|
+
const chromeStartupArgsContext = new AsyncLocalStorage();
|
|
31
|
+
export function withChromeStartupArgs(args, run) {
|
|
32
|
+
return chromeStartupArgsContext.run(args ?? chromeStartupArgsContext.getStore(), run);
|
|
33
|
+
}
|
|
30
34
|
export function withAttachedBrowserSessionContext(preserve, run) {
|
|
31
35
|
return attachedBrowserSessionContext.run(preserve || attachedBrowserSessionContext.getStore() === true, run);
|
|
32
36
|
}
|
|
@@ -34,10 +38,19 @@ function appendTail(text, addition, maxChars) {
|
|
|
34
38
|
const combined = text + addition;
|
|
35
39
|
return combined.length <= maxChars ? combined : combined.slice(combined.length - maxChars);
|
|
36
40
|
}
|
|
37
|
-
export function prepareAgentBrowserSpawnArgs(args, wrapperCompatibilityUserAgent, preserveAttachedBrowserSession = false) {
|
|
38
|
-
if (preserveAttachedBrowserSession
|
|
41
|
+
export function prepareAgentBrowserSpawnArgs(args, wrapperCompatibilityUserAgent, preserveAttachedBrowserSession = false, startupArgs) {
|
|
42
|
+
if (preserveAttachedBrowserSession)
|
|
43
|
+
return args;
|
|
44
|
+
const occurrence = scanUpstreamGlobalFlagOccurrences(args, "--args").at(-1);
|
|
45
|
+
const customArgs = wrapperCompatibilityUserAgent && !occurrence
|
|
46
|
+
? `${startupArgs ?? "--no-startup-window"},--user-agent=${wrapperCompatibilityUserAgent.replaceAll(/[\r\n,]/g, "")}` : startupArgs;
|
|
47
|
+
if (customArgs === undefined)
|
|
39
48
|
return args;
|
|
40
|
-
|
|
49
|
+
if (!occurrence)
|
|
50
|
+
return ["--args", customArgs, ...args];
|
|
51
|
+
const normalized = [...args];
|
|
52
|
+
normalized[occurrence.index + 1] = customArgs;
|
|
53
|
+
return normalized;
|
|
41
54
|
}
|
|
42
55
|
function terminateSpawnedChild(child, signal) {
|
|
43
56
|
if (processPlatform === "win32" && child.pid) {
|
|
@@ -472,7 +485,7 @@ export async function runAgentBrowserProcess(options) {
|
|
|
472
485
|
return;
|
|
473
486
|
}
|
|
474
487
|
const spawnBrowser = processPlatform === "win32" ? crossSpawn : spawn;
|
|
475
|
-
const child = spawnBrowser("agent-browser", prepareAgentBrowserSpawnArgs(args, ownedManagedSessionCompatibilityEnv.AGENT_BROWSER_USER_AGENT, preserveAttachedBrowserSession), {
|
|
488
|
+
const child = spawnBrowser("agent-browser", prepareAgentBrowserSpawnArgs(args, ownedManagedSessionCompatibilityEnv.AGENT_BROWSER_USER_AGENT, preserveAttachedBrowserSession, chromeStartupArgsContext.getStore()), {
|
|
476
489
|
cwd,
|
|
477
490
|
env: childEnv,
|
|
478
491
|
stdio: ["pipe", "pipe", "pipe"],
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -141,9 +141,9 @@ The default extension factory optionally accepts `{ beforeExecute }`. `index.ts`
|
|
|
141
141
|
|
|
142
142
|
### Default
|
|
143
143
|
|
|
144
|
-
An explicit native `session` default (user/project JSON or `AGENT_BROWSER_SESSION`) selects a caller-owned browser across Pi sessions. Per-call session/namespace flags win. `lib/orchestration/native-session-defaults.ts` reads
|
|
144
|
+
An explicit native `session` default (user/project JSON or `AGENT_BROWSER_SESSION`) selects a caller-owned browser across Pi sessions. Per-call session/namespace flags win. `lib/orchestration/native-session-defaults.ts` reads session/namespace and contributing launch defaults from native config paths and uses sessionless native `--config <path> --json session` to validate each contributing file and resolve its session. Native config continues to own launch/profile/storage settings; the adapter does not duplicate its schema. Explicit config and per-call idle settings are scoped to helpers through `lib/process-environment.ts`. No startup probe, persistent config cache, or new lifecycle service is added.
|
|
145
145
|
|
|
146
|
-
When no native session
|
|
146
|
+
When no native session, attachment, or explicit fresh launch is selected, `sessionMode: "auto"` uses `pi-root-<sha256(root Pi ID)[0:24]>` through the existing caller-owned named-session path. Descendants receive `PI_SUBAGENT_ROOT_SESSION_ID` from pi-subagents; root processes use `ctx.sessionManager.getSessionId()`. No cwd, human fork ancestry, run registry, or process-global assignment participates. The same name is the default native restore key. Existing native daemon inspection runs inside the existing identity queue: inactive roots receive bootstrap defaults, while active roots retain native launch settings and their current restore key. Scoped subprocess environment keeps helper settings consistent; no launch-settings journal is added. Root-name explicit follow-ups retain those defaults; unrelated explicit names do not. Global/override package `browser.defaultProfile` with `policy: "always"` supplies native Chrome profile names only, plus the configured executable. Paths and project-only profile settings stay advisory. Native config/environment and explicit launch settings win. The communal source is copied by native Chrome launch, never by the wrapper, and each root's restore writes remain separate. Native JSON restore omits IndexedDB, service workers, and page-memory credentials; application-level restart verification is required. Parent and child quit leave the root browser alive; existing per-session queues remain process-local, with no global broker. An active older/fresh managed session keeps its existing lifecycle. A configured session has the same precedence over `fresh` as a literal `--session`; script and Electron launch keep their separate wrapper-owned lifecycles. Electron launch suppresses the native session environment default only while allocating its generated session; ordinary browser follow-ups still honor the configured native default, so use the returned Electron session explicitly.
|
|
147
147
|
|
|
148
148
|
Why:
|
|
149
149
|
- works out of the box
|
|
@@ -166,8 +166,9 @@ The tool should also expose a first-class `sessionMode: "fresh"` escape hatch so
|
|
|
166
166
|
|
|
167
167
|
### Ownership
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
-
|
|
169
|
+
Ownership rule:
|
|
170
|
+
- automatic root sessions use native caller-owned lifecycle; group members coordinate and close them explicitly
|
|
171
|
+
- older implicit auto-generated sessions are extension-managed convenience sessions
|
|
171
172
|
- unnamed `sessionMode: "fresh"` launches rotate that extension-managed session to a new upstream browser
|
|
172
173
|
- explicit/user-managed sessions are not auto-managed by default
|
|
173
174
|
- extension-managed sessions should be reusable during an active `pi` session and across `/reload`, exact-session relaunch, `/resume`, and Pi branch-tree transitions, while still being cleaned up predictably
|
|
@@ -208,6 +209,10 @@ This is primarily about ownership clarity and avoiding surprise, not adding a he
|
|
|
208
209
|
|
|
209
210
|
### Launch flags
|
|
210
211
|
|
|
212
|
+
`native-session-defaults.ts` reads launch-argument/engine fields through its existing native-validated config discovery. Local Chrome composes `--no-startup-window` with the highest-priority caller arguments. Root bootstrap retains its existing helper scope; owned/fresh preparation reuses daemon-policy inspection, while explicit local callers use the same session-info probe. `process.ts` applies the scoped arguments after managed restore-policy checks, composing the existing compatibility user agent without overriding caller Chrome arguments. Configured environment/file arguments remain consistent across helpers; ordinary active calls do not receive a new launch configuration. This adds no browser launcher, profile mutation, tab cleanup, binary distribution, or persistent launch-settings state. Older custom-argument browsers can undergo native's normal hash-change restart on first use of the new default.
|
|
213
|
+
|
|
214
|
+
Input resolution replaces URL-less `open` with native `get url` before planning, retaining requested argv separately. Effective raw/stdin batch rows use the existing native argument parser and raw-over-stdin precedence; only changed rows are rewritten. Native URL reads provide lazy launch and real lifecycle/URL output without navigating an existing page. Explicit navigation and help/version input are unchanged.
|
|
215
|
+
|
|
211
216
|
`agent-browser` startup flags are sticky once a session is already running.
|
|
212
217
|
The extension should surface that clearly and avoid hidden restart behavior in v1.
|
|
213
218
|
|
|
@@ -218,6 +218,7 @@ Tool parameters (use exactly one of `script`, `args`, `semanticAction`, `job`, `
|
|
|
218
218
|
- `outputPath`: optional wrapper-owned local file sink for successful results and recording receipts, including failed, pending, timed-out or recovered stops. Recording exports preserve an envelope with original attempt status and native receipt/verification (`details.outputFile.source: "recording-receipt"`); unrelated failed extractions remain unwritten. Use it for durable `eval`, `get`, `snapshot`, or diagnostic outputs, not as the destination for screenshots, downloads, recordings, or other browser artifacts; if the paths resolve to the same file, the browser artifact is preserved and the result-data write fails validation. If presentation compacted a large direct result, a result row, or the whole `batch`, the writer copies each full command-redacted pre-compaction value only from its matching live wrapper-manifest spill; if any required spill is unavailable or untrusted, it fails without writing compact metadata. `details.outputFile` reports the saved path and byte count. If caller argv includes upstream `--json`, the visible JSON content stays parseable and the save notice is only in `details.outputFile`.
|
|
219
219
|
- `timeoutMs`: optional per-call wrapper subprocess watchdog override in milliseconds for the requested browser CLI process. Managed-session policy inspection can independently consume up to 35 seconds before that process; this preflight is intentionally not shortened by `timeoutMs` because a busy but valid daemon must remain distinguishable from an unverifiable one.
|
|
220
220
|
- `sessionMode`:
|
|
221
|
+
- Unconfigured ordinary calls select a root-Pi named browser with native restore; descendants share it, unrelated roots do not wait on each other, and Pi quit leaves it running. Existing managed sessions and explicit fresh launches retain their lifecycle.
|
|
221
222
|
- Native configured `session` / `AGENT_BROWSER_SESSION` defaults select a caller-owned shared browser for ordinary calls, just like explicit `--session`, without requiring repeated flags. Such a selection wins over both modes and is not closed on Pi quit. See [shared browser defaults](../README.md#shared-browser-defaults).
|
|
222
223
|
- `"auto"` reuses the extension-managed session when no native session is selected.
|
|
223
224
|
- `"fresh"` rotates that managed session to a fresh upstream launch so launch-scoped flags (`--allowed-domains`, `--auto-connect`, `--args`, `--ca-cert`, `--no-ca-cert`, `--cdp`, `--enable`, `--executable-path`, `--webgpu`, `--init-script`, `--idle-timeout`, `--user-agent`, `--headed`, `--device`, `--namespace`, `--profile`, `--provider`, `-p`, `--restore`, `--restore-save`, `--restore-check-url`, `--restore-check-text`, `--restore-check-fn`, `--session-name`, `--state`) apply.
|
|
@@ -1015,7 +1016,7 @@ Example config:
|
|
|
1015
1016
|
}
|
|
1016
1017
|
```
|
|
1017
1018
|
|
|
1018
|
-
Browser
|
|
1019
|
+
Browser defaults are scoped: global/override `defaultProfile` with `policy: "always"` and a Chrome profile name (not a path), plus `executablePath`, become native subprocess defaults only for automatic root browsers and their helpers. Roots have distinct named sessions and restore keys; parents and descendants share one root through `PI_SUBAGENT_ROOT_SESSION_ID`. These defaults never apply to unrelated explicit sessions, script, or Electron launches. Profile paths, other policies, and project-only guidance remain advisory. Native restore keeps cookies/localStorage/sessionStorage, not new IndexedDB, service workers, or memory-only grants; verify app access after a real restart. Avoid global native profile changes or a shared writable profile path; see [shared browser defaults](../README.md#shared-browser-defaults). Configure profile/executable guidance globally, in trusted project config, or through `PI_AGENT_BROWSER_CONFIG`. Ask the agent to run `profiles` and `doctor` when profile resolution fails, then use the reported Chrome profile directory name, a full profile/user-data directory path if upstream accepts one, or the configured `browser.executablePath` with top-level `sessionMode: "fresh"`.
|
|
1019
1020
|
|
|
1020
1021
|
## Important global flags, config, and environment
|
|
1021
1022
|
|
|
@@ -1039,6 +1040,8 @@ Browser default config is conservative: it adds agent guidance for signed-in/acc
|
|
|
1039
1040
|
|
|
1040
1041
|
### Browser launch and runtime flags
|
|
1041
1042
|
|
|
1043
|
+
The extension composes stock `--args --no-startup-window` for local Chrome startup without changing the headless default or external browser engines/attachments. Caller arguments retain native precedence. Plain active follow-ups do not repeat bootstrap settings; older custom-argument sessions can restart once when the new argument changes native launch identity. URL-less `open` runs native `get url` to lazily launch or preserve the active page, including batch/script calls; the result exposes the actual command and URL, not a fabricated launch receipt. See [the startup contract](TOOL_CONTRACT.md#headed-and-local-fixture-limits).
|
|
1044
|
+
|
|
1042
1045
|
- `--executable-path <path>`: custom Chromium-compatible browser executable, such as Brave/Edge/Arc/Vivaldi when upstream can launch that binary. Environment: `AGENT_BROWSER_EXECUTABLE_PATH`.
|
|
1043
1046
|
- `--extension <path>`: load browser extensions; repeatable. Environment: `AGENT_BROWSER_EXTENSIONS`.
|
|
1044
1047
|
- `--args <args>`: browser launch args, comma or newline separated. Chromium switches belong in this value, for example `{ "args": ["--args", "--no-sandbox", "open", "https://example.com"], "sessionMode": "fresh" }`. A bare `--no-sandbox` is diagnosed when it occupies the command slot (unknown command) or an `open` / `goto` / `navigate` option position (ignored by upstream); literal operands in other commands are left alone. For batches, put `--args` before `batch` in top-level `args`, not inside a row. Environment: `AGENT_BROWSER_ARGS`.
|
package/docs/RELEASE.md
CHANGED
|
@@ -47,7 +47,7 @@ npm run verify -- pre-pr
|
|
|
47
47
|
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
|
|
48
48
|
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`
|
|
49
49
|
|
|
50
|
-
The 0.6.
|
|
50
|
+
The 0.6.13 release uses the documented [local macOS and hosted native-Windows alternatives](platform-smoke.md#alternate-native-transports) instead of SSH and Parallels. Run and inspect those target-local suites separately alongside the remaining release checks. The default composed gate is unchanged; alternate evidence must not be labeled a passing Crabbox matrix.
|
|
51
51
|
|
|
52
52
|
`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.
|
|
53
53
|
|
|
@@ -221,7 +221,7 @@ Run the automated harness for deterministic configured-source lifecycle regressi
|
|
|
221
221
|
npm run verify -- lifecycle
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
-
The harness creates an isolated `PI_CODING_AGENT_DIR`, writes settings with exactly one temporary configured package source, runs `pi` in `tmux` with `--approve`, default model **`zai/glm-5.2`**, and a deterministic `--session-id`, puts a deterministic fake `agent-browser` first on `PATH`, drives `/reload`, closes Pi, and relaunches with the same exact session id instead of typing `/resume`. It also asserts the JSONL session header id, same-page managed-session continuity, compiled JS code pickup after full process relaunch, persisted spill reachability, and real Pi `tool_result` failure-patch semantics for a QA reclassification. Per-step tmux waits default to **180000 ms** (three minutes) in [`scripts/verify-lifecycle.mjs`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/scripts/verify-lifecycle.mjs) (`DEFAULT_TIMEOUT_MS`); override with `--timeout-ms <ms>` when slower models or cold starts need more headroom. Override the model when needed:
|
|
224
|
+
The harness creates an isolated `PI_CODING_AGENT_DIR`, writes settings with exactly one temporary configured package source, runs `pi` in `tmux` with `--approve`, default model **`zai/glm-5.2`**, and a deterministic `--session-id`, puts a deterministic fake `agent-browser` first on `PATH`, starts an explicit `sessionMode: "fresh"` managed browser, drives `/reload`, closes Pi, and relaunches with the same exact session id instead of typing `/resume`. It also asserts the JSONL session header id, same-page managed-session continuity, compiled JS code pickup after full process relaunch, persisted spill reachability, and real Pi `tool_result` failure-patch semantics for a QA reclassification. Per-step tmux waits default to **180000 ms** (three minutes) in [`scripts/verify-lifecycle.mjs`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/scripts/verify-lifecycle.mjs) (`DEFAULT_TIMEOUT_MS`); override with `--timeout-ms <ms>` when slower models or cold starts need more headroom. Override the model when needed:
|
|
225
225
|
|
|
226
226
|
```bash
|
|
227
227
|
npm run verify -- lifecycle --model openai-codex/gpt-5.5:minimal
|
|
@@ -274,8 +274,8 @@ This suite requires the installed stable `agent-browser --version` to meet the m
|
|
|
274
274
|
- **Navigation metadata and native tab setup:** plain pages stay quiet; a page with tools retains positive native `data.webmcp` and shows the `webmcp list` hint. New tabs inherit configured headers before their first request, and clearing headers removes them on the next tab. No wrapper inheritance code is involved.
|
|
275
275
|
- **Recording FPS paths and pinning:** the opt-in `test/agent-browser.batch-fidelity.test.ts` native case checks direct/raw/stdin preflight before dispatch, actual start/restart destinations and pinned targets with leading `--fps`, conservative start-ref protection, FPS-only restart continuity, and valid 12-fps WebM output after explicitly logged 12-second fixture captures. Run it with `PI_AGENT_BROWSER_REAL_UPSTREAM=1`; it requires native 0.37 or newer. Keep focused older-native controls for fresh-page starts and the start-then-ref batch latch when changing that policy.
|
|
276
276
|
- **Managed session core and safe diagnostic matrix:** fresh `open` on the contract fixture, then implicit reuse across `eval --stdin`, `snapshot -i`, interaction commands (`click`, `dblclick`, `fill`, `type`, `type --clear --delay`, `focus`, `keyboard` with `type` / `inserttext`, `press`, `hover`, `check`, `uncheck`, `select`, failed `select` no-match, `upload`, `drag`, `mouse`, `scroll`, off-viewport click, `scrollintoview`, `wait` on selectors in the main frame and a selected iframe), extraction (`get` variants, `is` variants, `find label … fill` via native `<label>`, `aria-label`, and `aria-labelledby`, inline `eval`), file outputs (`screenshot`, `pdf`), navigation (`back`, `forward`, `reload`, `tab list`, another `open` to the same fixture), `batch` stdin, `pushstate`, `vitals … --json`, network route/requests/HAR, diff snapshot/screenshot/url, trace/profiler, console/errors/highlight, stream enable/status/disable, and `cookies set --curl`.
|
|
277
|
-
- **Managed restore correctness and persistence:** while the restore-enabled managed daemon is active, assert raw argument and stdin batches containing nested `connect` fail before upstream spawn; a new
|
|
278
|
-
- **Cold first-read continuity:** the focused `contract suite matches cold URL reopen after quit` case runs actual quit cleanup, waits for the exact old daemon to exit, reloads the saved branch, and requests `snapshot -i` before any explicit navigation. Empty storage and origin storage must both reach their remembered non-root URL, report fresh refs in the main frame, and retain storage without pretending to retain unsaved forms or JavaScript memory. Separate deterministic cold-resume/boundary tests cover first `get url` and history commands, non-page daemon-starting calls and batch prefixes, hash routes, pending-state transcript replay, live missing tabs versus explicit URL destinations, close retirement, and caller-owned/attached/restore-disabled boundaries. Those fake-upstream checks do not replace native-browser or real Pi lifecycle qualification of the complete candidate.
|
|
277
|
+
- **Managed restore correctness and persistence:** while the restore-enabled managed daemon is active, assert raw argument and stdin batches containing nested `connect` fail before upstream spawn; a new harness replaying the owned launch result must also reject incompatible reuse of that live same-name daemon. Seed a cookie plus localStorage/sessionStorage, close the first managed browser while a conflicting parent namespace is set, verify the default-namespace daemon actually closed, create a new extension harness with the same cwd, reopen the fixture with `sessionMode: "fresh"`, and assert all three values restore before closing the second browser. On POSIX, separate isolated real-browser launches assert automatic restore stays disabled and no snapshot is written through either a symlinked `sessions` directory or a file symlink in `sessions/.tmp`; a relative `HOME`, untrusted writable HOME ancestry, and a non-Git cwd must fail closed. Verify a checkout rename preserves its generation identity but starts a fresh composite restore key (fail-closed, because the cwd-derived managed-session base name changes), a copied or path-replacement checkout gets a new key, and changing the Git-generation marker between planning and spawn prevents agent-browser from starting. Run two same-identity harnesses concurrently so a compatible launch publishes its daemon policy before a waiting incompatible launch re-inspects and fails without reaching its main spawn; also fail a fresh non-batch command after daemon creation and verify shutdown closes the retained identity.
|
|
278
|
+
- **Cold first-read continuity:** the focused `contract suite matches cold URL reopen after quit` case starts an explicit `sessionMode: "fresh"` managed browser, runs actual quit cleanup, waits for the exact old daemon to exit, reloads the saved branch, and requests `snapshot -i` before any explicit navigation. Empty storage and origin storage must both reach their remembered non-root URL, report fresh refs in the main frame, and retain storage without pretending to retain unsaved forms or JavaScript memory. Separate deterministic cold-resume/boundary tests cover first `get url` and history commands, non-page daemon-starting calls and batch prefixes, hash routes, pending-state transcript replay, live missing tabs versus explicit URL destinations, close retirement, and caller-owned/attached/restore-disabled boundaries. Those fake-upstream checks do not replace native-browser or real Pi lifecycle qualification of the complete candidate.
|
|
279
279
|
- **Failure shape:** `react tree` on a page opened with `--enable react-devtools` but without a React app (expects a clear missing-renderer error with session-bound `details`).
|
|
280
280
|
- **Async download:** `open` on the `/download` fixture, anchor-triggered export, then `wait --download <path>` metadata and wrapper artifact reporting for the requested path.
|
|
281
281
|
|
package/docs/SUPPORT_MATRIX.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Current upstream support matrix
|
|
2
2
|
|
|
3
|
+
Root browser groups use native named sessions plus per-root restore, with scoped Chrome profile-name bootstrap from existing package config. `test/agent-browser.shared-defaults.test.ts` covers root separation, descendant routing, exact root-name follow-ups, helper launch consistency, explicit overrides, and quit ownership; the companion pi-subagents tests cover foreground/detached launch and saved revive identity. Native JSON restore supports cookies and web storage, not new IndexedDB-only or memory credentials. Real SDK/native-browser restart and concurrency evidence is required for this boundary; a profile metadata match alone is not login proof.
|
|
4
|
+
|
|
3
5
|
Related docs:
|
|
4
6
|
- [`../README.md`](../README.md)
|
|
5
7
|
- [`../AGENTS.md`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/AGENTS.md) (rebaselining and verification stack)
|
|
@@ -62,7 +64,7 @@ Current summary:
|
|
|
62
64
|
| RQ-0144 | Managed restore keys are scoped to both checkout generation and Pi transcript so upstream 0.33.2's newest-file-wins loading cannot let concurrent chats clobber or inherit cookies/storage. Fresh rotations, reload, restart, and `/resume` of one transcript retain continuity; after a confirmed inactive daemon, a branch-restored compatible owned session retains its pending reopen through daemon-starting non-page calls and transcript replay, then reopens the complete recorded URL (including its fragment) before current-page operations such as `get url` or `reload`, verifies the actual target and invalidates old refs. This reloads a URL, not forms, JavaScript memory, or history; live wrong-tab recovery never auto-navigates. Passive and explicit upstream configs pass through, while explicit config selection suppresses the wrapper's automatic restore injection. | [`docs/ARCHITECTURE.md`](ARCHITECTURE.md), [`docs/TOOL_CONTRACT.md`](TOOL_CONTRACT.md), `test/agent-browser.managed-session-restore.test.ts`, `test/agent-browser.real-upstream-contract.test.ts` |
|
|
63
65
|
| RQ-0145 | Mega-dogfood correctness closes false-positive success paths: URL QA makes matched post-clear page-error evidence non-pass as unverified, counts definitely new errors separately, and waits 150 ms for immediate post-load diagnostics, stale pre-existing artifacts and missing/stale recordings finalized by restart fail verification, aliased destinations (including dangling symlinks, full macOS/Windows Unicode folds, final short/long diff output flags, and same-call `outputPath` aliases) and active recording-path reuse through artifacts, path-bearing HAR stops, long/short or reordered explicit download waits, interspersed-global command forms, Electron cleanup output, or normalized output writes is rejected through an atomic process-wide, transcript-backed namespace/session reservation index independent of bounded manifest eviction; cross-branch close tombstones survive reload and each direct or ordered nested close retires exact identity at that lifecycle point while terminal closes clear attached/ref/page/route state, later lifecycle-proven browser launches—including failed post-close rows and `record stop`—can reactivate/rebuild it without stale pre-close `about:blank` recovery while bounded launch evidence survives failed-step presentation and transcript replay, explicitly non-launching diagnostics preserve the close, missing evidence stays conservatively active even on the first managed call, every successful direct/nested close clears wrapper trace/profiler ownership before later successful rows can rebuild it, and namespace-scoped `close --all` clears all matching managed/attached/page/ref/route/trace/recording ownership, only the newest pending recording path remains authoritative even across same-timestamp restart rows, current recording transitions replay directly without artifact-manifest compatibility inference, definitive direct/batch no-recording stop failures retire stale state at their ordered step, recording starts after nested close are rejected, pending rows coalesce into terminal batch outcomes without retaining an intermediate close-abandoned duplicate after a later saved stop, and any later same-session failure retains exact cleanup actions, same-page getter refs are refreshed, locator select resolves exact current native controls, no-movement scroll fails honestly, and script cleanup always attempts fail-closed close. | [`docs/TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details), [`docs/COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md), `test/agent-browser.extension-input-modes.test.ts`, `test/agent-browser.extension-ref-guards.test.ts`, `test/agent-browser.presentation.test.ts`, `test/agent-browser.presentation-artifacts-batch.test.ts`, `test/agent-browser.recording-reservations.test.ts`, `test/agent-browser.extension-validation.test.ts`, `test/agent-browser.script.test.ts` |
|
|
64
66
|
| RQ-0146 | Upstream `agent-browser 0.34.0` rebaseline adds sticky `--pin-tab` / `--no-pin-tab` (`AGENT_BROWSER_PIN_TAB`) for shared CDP/auto-connect sessions, CDP target ids as tab refs, and `tab_gone` recovery data (`data.targetId`, optional `data.lastUrl`). The wrapper parses those flags as optional global booleans rather than launch-scoped, classifies `tab_gone` as `failureCategory: "tab-gone"` with `list-tabs-after-tab-gone` / `open-tab-after-tab-gone`, and shows `targetId` in `tab list` presentation. | [`docs/COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#upstream-0340-rebaseline), [`docs/TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details), `test/agent-browser.results.test.ts`, `test/agent-browser.runtime.test.ts` |
|
|
65
|
-
| RQ-0147 | Native follow-ups preserve profiled and other launch-configured browser pages by omitting wrapper compatibility launch arguments while the daemon is active; daemon inspection reapplies the retained compatibility user agent only when relaunch is required. Caller `--args
|
|
67
|
+
| RQ-0147 | Local Chrome composes stock `--no-startup-window` with native launch defaults while headless remains the default; URL-less open uses native lazy URL reads across direct/batch/script paths. Existing root/owned daemon probes and explicit-local session info gate bootstrap, without tab cleanup or a launcher/state journal. See `test/agent-browser.stock-startup.test.ts` for stock browser PID/page/storage checks. Native follow-ups preserve profiled and other launch-configured browser pages by omitting wrapper compatibility launch arguments while the daemon is active; daemon inspection reapplies the retained compatibility user agent only when relaunch is required. Caller `--args` retain their values after the startup default and native precedence; config, file access, and explicit local navigation remain native-owned, and cancellation wins over policy validation before spawn. | [`docs/ARCHITECTURE.md`](ARCHITECTURE.md), [`docs/TOOL_CONTRACT.md`](TOOL_CONTRACT.md), `test/agent-browser.runtime.test.ts`, `test/agent-browser.process.test.ts`, `test/agent-browser.real-upstream-contract.test.ts` |
|
|
66
68
|
| RQ-0148 | Upstream 0.35.0 adds Linux Chromium private CA trust and the `protected-vercel-deployments` skill. The wrapper parses CA set/clear globals, requires fresh managed launches, suppresses automatic managed restore for CA-enabled sessions, passes caller certificate paths upstream unchanged, and uses 0.35.0 as its runtime floor. | [`docs/COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#upstream-0350-rebaseline), [`docs/TOOL_CONTRACT.md`](TOOL_CONTRACT.md), `test/agent-browser.runtime.test.ts`, `test/agent-browser.managed-session-restore.test.ts`, `test/agent-browser.page-target-validation.test.ts` |
|
|
67
69
|
| 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` |
|
|
68
70
|
| 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` |
|
package/docs/TOOL_CONTRACT.md
CHANGED
|
@@ -174,6 +174,10 @@ Upstream 0.35.2 adds `dashboard start --allowed-origins <origins>` and `AGENT_BR
|
|
|
174
174
|
|
|
175
175
|
## Headed and local fixture limits
|
|
176
176
|
|
|
177
|
+
Local Chrome uses stock `--args --no-startup-window` at bootstrap and composes it consistently with caller-configured Chrome arguments (CLI overrides environment, which overrides native config). Plain active-session follow-ups do not resend bootstrap settings. A pre-existing custom-argument browser may restart once because stock Chrome launch arguments form part of native session identity; there is no migration or launch-settings journal. Headless remains the default. CDP/auto-connect, providers, Electron, and Lightpanda do not receive this Chrome default. The wrapper does not delete tabs or edit profiles.
|
|
178
|
+
|
|
179
|
+
URL-less `open` is executed as native `get url`, including effective raw/stdin batch rows and script calls. This lazily opens a missing browser without navigating an existing page or issuing a second incomplete launch. `details.args` retains the request; `effectiveArgs`, `command`, native URL data and lifecycle show what ran. No launch receipt is synthesized. Explicit URLs, required-URL errors for `goto`/`navigate`, and top-level help/version argv keep their native meanings. Ignored batch stdin remains unchanged.
|
|
180
|
+
|
|
177
181
|
- Upstream 0.35.0 and newer require separate `args` entries for global flag values. The wrapper rejects `--flag=value` global tokens before normal command dispatch, including trailing tokens; `--restore=<key>` is the explicit upstream-supported exception. Plain help/version inspection preserves exact caller argv, matching upstream. These exceptions are top-level only: global flags for `batch` belong before `batch`, and row-local equals forms fail validation before dispatch.
|
|
178
182
|
- A bare `--no-sandbox` in the command slot, or an option position after `open` / `goto` / `navigate`, fails validation before dispatch with Chromium launch-argument guidance. The former is an unknown upstream command; navigation ignores the latter. Pass the switch as a separate `--args` value and use `sessionMode: "fresh"` for launch changes. For native batches, `--args` belongs on the outer call before `batch`, not in a row. Other commands keep their native literal text, script, select, path and option-value handling; top-level help/version inspection remains unchanged.
|
|
179
183
|
- `--headed` is an upstream global flag passed through `args` (for example `{ "args": ["--headed", "open", "https://example.com"], "sessionMode": "fresh" }`). Use it on the first launch for demos, human-observed QA, or a user-completed login. If a managed browser session already exists, use `sessionMode: "fresh"` so the launch-scoped headed/headless choice is not ignored. Wrapper-owned headed launches default `AGENT_BROWSER_AUTOSAVE_INTERVAL_MS` to `0` because upstream 0.33.2's multi-origin restore collector opens visible temporary tabs and can delay session policy inspection. The wrapper records the effective launch-time interval and reapplies it to every helper and follow-up subprocess, still-owned off-current session, Electron cleanup close, and transcript reload/resume so daemon configuration remains stable. Native close still saves, but direct window close can lose newer state because upstream exempts headed browsers from idle shutdown; set an explicit interval before launch when periodic preservation matters. Because upstream reads it when the daemon starts, changing the recorded effective interval in either direction on a running wrapper-owned headed session is rejected until close plus a fresh launch.
|
|
@@ -197,7 +201,7 @@ Upstream 0.35.2 adds `dashboard start --allowed-origins <origins>` and `AGENT_BR
|
|
|
197
201
|
- For desktop or host-controlled rich inputs, if semanticAction fill misses, refresh refs and prefer a current editable @ref from details.richInputRecovery or the latest snapshot; focus or click that ref, then use keyboard type for framework-controlled editors that require real key events. keyboard inserttext is paste-like and can change a DOM value without updating application state, so use it only when later application-state evidence proves the edit was accepted. Do not auto-submit with Enter or a submit button unless the user flow explicitly calls for it.
|
|
198
202
|
- Do not assume Playwright selector dialects such as text=Close or button:has-text('Close') are supported wrapper syntax unless current upstream agent-browser behavior has been verified.
|
|
199
203
|
- For authenticated or user-specific content explicitly requested by the user, such as feeds, inboxes, account pages, or private dashboards, use a real profile only when the user/config asks for it or profiles have been inspected; do not assume --profile Default exists on every machine. Do not use a real profile for public pages just because they are dashboards. Treat visible page content from real profiles as model-visible transcript data. On macOS, copied Chrome profiles may omit encrypted cookies, so profile selection alone is not proof of authentication; verify the target page and use a user-approved headed login once when needed. Use --auto-connect only if profile-based reuse is unavailable or the task is specifically about attaching to a running debug-enabled browser. If profile/user-data-dir resolution fails, stop retrying opens, run profiles and/or doctor through agent_browser, then report what the user needs to configure.
|
|
200
|
-
-
|
|
204
|
+
- Ordinary bare calls share one native browser within your root Pi parent/subagent group; unrelated roots browse independently. Coordinate multi-call navigation inside your group with existing subagent/intercom tools. Explicit native session defaults still win. Pi quit does not close group browsers; never close an unrelated browser.
|
|
201
205
|
- When using launch-scoped flags (--auto-connect, --allowed-domains, --namespace, --cdp, --ca-cert, --no-ca-cert, --enable, --executable-path, --webgpu, --no-webmcp, --init-script, --idle-timeout, --args, --user-agent, --headed, --device, --profile, --provider, -p, --session-name, --restore, --restore-save, --restore-check-url, --restore-check-text, --restore-check-fn, --state), put them on the first command for that session. If you intentionally use an explicit --session, keep using that same explicit session for follow-ups.
|
|
202
206
|
- Caller-owned explicit sessions are serialized per effective canonical namespace/session inside this extension while live URL checks, semantic-action snapshots, and the requested command run. For raw batches whose later content step depends on navigation, use exact batch --bail or split the calls; unsafe continue-after-navigation-failure shapes are rejected before the batch runs.
|
|
203
207
|
- After a successful `connect`, `--cdp`, or enabled `--auto-connect` call, verify with get url and keep using the resulting session without repeating the attach flag. The wrapper remembers that attachment across active-branch reload/resume and live-checks the URL before later page reads/interactions because an attached browser can drift externally; caller config, file access, launch arguments, and environment pass through unchanged. A successful close clears the marker. When several named sessions share one Chrome, pass --pin-tab once (AGENT_BROWSER_PIN_TAB) so a closed bound tab fails as tab_gone instead of acting on a neighbor; recover with tab new or tab list. --no-pin-tab turns the sticky pin off. tab list includes each tab's CDP targetId, accepted as a tab ref.
|
|
@@ -704,7 +708,7 @@ Example:
|
|
|
704
708
|
|
|
705
709
|
### `sessionMode`
|
|
706
710
|
|
|
707
|
-
A native `session` default from user/project JSON or `AGENT_BROWSER_SESSION` selects a caller-owned browser for `args`, `semanticAction`, `job`, `qa`, and lookups. Per-call flags still win. Like literal `--session`, this selection takes precedence over `fresh`; it reports `usedImplicitSession: false` and is not closed when Pi quits. Without a selected native session, the
|
|
711
|
+
A native `session` default from user/project JSON or `AGENT_BROWSER_SESSION` selects a caller-owned browser for `args`, `semanticAction`, `job`, `qa`, and lookups. Per-call flags still win. Like literal `--session`, this selection takes precedence over `fresh`; it reports `usedImplicitSession: false` and is not closed when Pi quits. Without a selected native session, attachment, or explicit fresh launch, auto calls select a stable root-Pi named session and root-specific native restore key. `PI_SUBAGENT_ROOT_SESSION_ID` carries the parent identity to descendants; ordinary new/fork/clone roots differ, while resume/cwd changes keep identity. Root sessions report `usedImplicitSession: false` and survive parent/child quit. Global/override package profile names with `policy: "always"` and executable defaults bootstrap only inactive automatic roots. Active roots preserve native launch settings/current restore key, including matching root-name follow-ups; unrelated explicit browsers are unchanged. Profile paths and project-only profile config remain advisory. Native restore covers cookies and web storage, not newly created IndexedDB or memory-only authentication. See the README for communal bootstrap and persistent-profile fallback. Active older/fresh managed sessions retain the lifecycle below. `qa.attached` can inspect this shared current session using the same live target checks. See [shared browser defaults](../README.md#shared-browser-defaults).
|
|
708
712
|
|
|
709
713
|
- type: `"auto" | "fresh"`
|
|
710
714
|
- optional
|
package/docs/platform-smoke.md
CHANGED
|
@@ -38,7 +38,7 @@ npm run verify -- platform-smoke run --target ubuntu --suite platform-build
|
|
|
38
38
|
|
|
39
39
|
## Alternate native transports
|
|
40
40
|
|
|
41
|
-
For the 0.6.
|
|
41
|
+
For the 0.6.13 release, local macOS execution replaces localhost SSH, and the [Native Windows workflow](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/.github/workflows/windows-native.yml) runs the existing Windows suites on a GitHub-hosted Windows runner. These are native platform checks, not Crabbox SSH or Parallels passes. The default `release` and `prepublishOnly` commands still select the Crabbox matrix; record alternate suite evidence separately rather than reporting those commands as passed.
|
|
42
42
|
|
|
43
43
|
On macOS, run the unchanged commands returned by `buildPlatformBuildCommand('macos', 'pi-agent-browser-native', 24)` and `buildBrowserDogfoodCommand('macos', '0.37.0', true)` in [`scripts/platform-smoke/targets.mjs`](../scripts/platform-smoke/targets.mjs), serially in a clean private source copy. Use private HOME, npm and Pi settings, and headless browser profiles. No Remote Login or host security change is required.
|
|
44
44
|
|
|
@@ -113,7 +113,7 @@ Each required target runs `platform-build` and `browser-dogfood-smoke` on one Cr
|
|
|
113
113
|
### `browser-dogfood-smoke`
|
|
114
114
|
|
|
115
115
|
1. Run `npm ci` in the synced checkout if needed.
|
|
116
|
-
2. Run the deterministic model-free browser smoke through `scripts/verify-agent-browser-dogfood.ts
|
|
116
|
+
2. Run the deterministic model-free browser smoke through `scripts/verify-agent-browser-dogfood.ts`, starting with `sessionMode: "fresh"` so every browser stays in the test-owned managed lifecycle.
|
|
117
117
|
3. Exercise native wrapper surfaces against the deterministic loopback HTTP fixture from `scripts/verify-agent-browser-dogfood.ts`: top-level `qa`, `semanticAction`, constrained `job`, screenshot artifact verification, and session close.
|
|
118
118
|
4. Persist the dogfood JSON report and stdout/stderr evidence.
|
|
119
119
|
5. Fail on missing browser artifacts, failed tool calls, leaked secrets, or unclosed sessions.
|
package/package.json
CHANGED