pi-agent-browser-native 0.9.0 → 0.9.2
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 +36 -0
- package/README.md +19 -3
- package/dist/extensions/agent-browser/index.js +3 -107
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +1 -1
- package/dist/extensions/agent-browser/lib/argv-grammar.js +3 -3
- package/dist/extensions/agent-browser/lib/browser-transcript.js +1 -1
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +0 -3
- package/dist/extensions/agent-browser/lib/config-policy.js +0 -13
- package/dist/extensions/agent-browser/lib/config.js +3 -48
- package/dist/extensions/agent-browser/lib/electron/launch.js +4 -4
- package/dist/extensions/agent-browser/lib/input-modes/params.js +2 -3
- package/dist/extensions/agent-browser/lib/input-modes/script.js +28 -6
- package/dist/extensions/agent-browser/lib/input-modes/types.js +2 -2
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +4 -0
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +1 -4
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +0 -9
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-merge.js +22 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +2 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +4 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/screenshot-observation.js +6 -2
- package/dist/extensions/agent-browser/lib/orchestration/execution-queue.js +86 -0
- package/dist/extensions/agent-browser/lib/orchestration/native-session-defaults.js +4 -3
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +1 -1
- package/dist/extensions/agent-browser/lib/playbook.js +5 -4
- package/dist/extensions/agent-browser/lib/process.js +0 -3
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +2 -2
- package/dist/extensions/agent-browser/lib/results/network-routes.js +1 -1
- package/dist/extensions/agent-browser/lib/results/network.js +1 -1
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +7 -1
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +1 -1
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +1 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +1 -1
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +2 -2
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +1 -1
- package/dist/extensions/agent-browser/lib/runtime.js +4 -2
- package/dist/extensions/agent-browser/lib/session-page-state.js +1 -1
- package/dist/extensions/agent-browser/lib/web-search.js +15 -15
- package/docs/ARCHITECTURE.md +9 -8
- package/docs/COMMAND_REFERENCE.md +26 -4
- package/docs/RELEASE.md +1 -1
- package/docs/REQUIREMENTS.md +2 -1
- package/docs/SUPPORT_MATRIX.md +6 -3
- package/docs/TOOL_CONTRACT.md +12 -9
- package/package.json +3 -3
- package/scripts/doctor.mjs +10 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.9.2 - 2026-10-04
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Treat `--engine` as launch-scoped so switching an active managed browser uses the existing fresh-session recovery instead of replacing its page in place.
|
|
10
|
+
- Label positively identified Lightpanda images as text-rendered in direct, native batch and selected browser-code output; retain image bytes while refusing CSS/mouse-coordinate geometry even when synthetic dimensions match.
|
|
11
|
+
- Omit wrapper-supplied Chrome profile/executable bootstrap defaults when CLI, environment or native config selects Lightpanda, without changing caller-selected launch settings.
|
|
12
|
+
|
|
13
|
+
### Documentation
|
|
14
|
+
|
|
15
|
+
- Keep Chrome default and Lightpanda opt-in. Document separate installation, single-target/visual/authentication limits, native unsupported commands, CORS and telemetry; add no driver, browser bundle or automatic fallback.
|
|
16
|
+
|
|
17
|
+
## 0.9.1 - 2026-10-02
|
|
18
|
+
|
|
19
|
+
### Removed
|
|
20
|
+
|
|
21
|
+
- Deleted dead production code surfaced by the test audit: `getOwnedManagedSessionRestoreKey`, `parseValidBatchStepEntries`, `normalizeCommandName` (alias resolution is owned by the capability-table predicates), `AGENT_BROWSER_JOB_TYPE_DELAYED_TEXT_MAX_CHARACTERS`, `isProjectSafeCredentialValueForProvider`, `isPlaintextCredentialValue`, the unused async `loadAgentBrowserConfig` duplicate of the sync loader, the unused `ensureAgentBrowserSocketDir` wrapper, and 46 export keywords (22 files) with no cross-module consumers. No behavior changed; owning tests now assert through the exported boundaries (`loadAgentBrowserConfigSync`, `getAgentBrowserSocketDirValidationError`, `runAgentBrowserScript`, capability predicates).
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Share the unchanged 300000ms code timeout ceiling between its schema and runner, and the unchanged startup measurement/250ms budget between the regression test and safe profiler (retaining the Android test's 1000ms exception). Remove the unreachable platform-smoke passthrough entry without changing facade gates.
|
|
26
|
+
- Move retained regressions to focused queue, artifact, input-validation, Electron host/branch, and Pi-pipeline owners. Relocate queue and concurrent artifact-merge helpers out of the extension entrypoint without changing their behavior. Consolidate required platform paths and private-file/tarball leak checks into the canonical package verifier and its existing real-tarball test. Allocate the macOS artifact alias fixture under `/tmp` so its path-alias contract does not depend on `TMPDIR`.
|
|
27
|
+
- Consolidate duplicate test invocations into their owning tables and delivery boundaries, preserving distinct lifecycle, redaction, recovery, and native activation checks. Split recording dependency, reservation, and batch-lifecycle assertions into named contracts. Test-only; no production behavior changed.
|
|
28
|
+
- Repaired 23 weak test assertions found by the test audit: literal prompt-byte expectations instead of source-derived constants, per-fixture bound expectations replacing pass-for-every-row alternations, real execution and credential-laziness probes for web-search/config, exact root-session `get url` probe matrices in passthrough coverage, complete guarded-set and key-set assertions, and anchored literal launcher/argv-delivery checks. Test-only; no production behavior changed.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- Execute maintainer command-reference and startup-profile scripts correctly through symlink and space/percent-encoded paths using Node's native main-module detection.
|
|
33
|
+
- Keep ordinary `cookies set --curl` import paths visible in invocation echoes and failed batch diagnostics without exposing inline cookie values, secret-bearing flags or file contents.
|
|
34
|
+
- Host the permissioned `agent_browser_code` child with a real Node runtime from `PATH` when Pi runs as a Bun-compiled binary, and fail as `missing-binary` with a clear remediation when no Node runtime is available instead of hanging the worker handshake (#238).
|
|
35
|
+
- Resolve npm `.cmd` shims on Windows in `pi-agent-browser-doctor` PATH checks so installed `agent-browser` and `pi` binaries stop producing false negatives (#219).
|
|
36
|
+
|
|
37
|
+
- Host cold-boundaries, cold-resume, and destination-cancel fixture browser sockets in a short private `0700` directory (`/tmp/piab-s-…`, via `createShortPrivateSocketDir` in the test harness) so realpath-canonicalized CI temp roots cannot exceed the macOS 103-byte Unix socket path cap. Test-only; production length and privacy guards are unchanged, and the fallback keeps the previous fixture-root path on Windows or when `/tmp` is unusable.
|
|
38
|
+
|
|
3
39
|
## 0.9.0 - 2026-10-01
|
|
4
40
|
|
|
5
41
|
### Breaking changes
|
package/README.md
CHANGED
|
@@ -321,7 +321,7 @@ npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-conf
|
|
|
321
321
|
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config browser executable set "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
|
|
322
322
|
```
|
|
323
323
|
|
|
324
|
-
Profile hints with `authenticated-only` remain advisory. Global/override profile names with `policy: "always"` and executable defaults bootstrap inactive automatic root browsers; active roots and unrelated explicit sessions retain their native settings. Configure profile/executable guidance globally, in trusted project config, or through `PI_AGENT_BROWSER_CONFIG`. Ask the agent to run `agent_browser` with `args: ["profiles"]` and `args: ["doctor"]` when profile resolution fails. The upstream `profiles` command lists Chrome profiles from Chrome's user data directory; `Default` is not canonical on every machine. Use the displayed profile directory name, a full profile/user-data directory path when upstream accepts one, or a configured `browser.executablePath` plus `sessionMode: "fresh"` for a different Chromium-compatible browser.
|
|
324
|
+
Profile hints with `authenticated-only` remain advisory. Global/override profile names with `policy: "always"` and executable defaults bootstrap inactive automatic root browsers only when the effective engine is Chrome; active roots and unrelated explicit sessions retain their native settings. Configure profile/executable guidance globally, in trusted project config, or through `PI_AGENT_BROWSER_CONFIG`. Ask the agent to run `agent_browser` with `args: ["profiles"]` and `args: ["doctor"]` when profile resolution fails. The upstream `profiles` command lists Chrome profiles from Chrome's user data directory; `Default` is not canonical on every machine. Use the displayed profile directory name, a full profile/user-data directory path when upstream accepts one, or a configured `browser.executablePath` plus `sessionMode: "fresh"` for a different Chromium-compatible browser.
|
|
325
325
|
|
|
326
326
|
## Common agent calls
|
|
327
327
|
|
|
@@ -338,6 +338,21 @@ Chromium launch switches belong in the value of `--args`, for example `{ "args":
|
|
|
338
338
|
|
|
339
339
|
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.
|
|
340
340
|
|
|
341
|
+
### Opt-in Lightpanda
|
|
342
|
+
|
|
343
|
+
Chrome remains the default. Install [Lightpanda](https://github.com/lightpanda-io/browser/releases/tag/1.0.0) separately on supported macOS/Linux hosts, put `lightpanda` on `PATH`, then select it explicitly:
|
|
344
|
+
|
|
345
|
+
```json
|
|
346
|
+
{ "args": ["--engine", "lightpanda", "open", "https://example.com"], "sessionMode": "fresh" }
|
|
347
|
+
{ "args": ["snapshot", "-i"] }
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
`--engine` is launch-scoped; changing an active managed browser uses the existing fresh-session recovery. `AGENT_BROWSER_ENGINE` and native config also select the engine. Wrapper-supplied Chrome profile/executable defaults are omitted for Lightpanda; caller arguments, environment, and native config remain unchanged.
|
|
351
|
+
|
|
352
|
+
Lightpanda screenshots render page text, not graphical layout. Positively identified captures carry `imageObservations[].rendering: "text"` and unknown coordinate geometry; keep using refs, not image-derived mouse coordinates. Lightpanda is not Chrome-parity visual QA, profile login, multi-tab, or WebMCP support, and this wrapper never falls back automatically. See [capabilities and limits](docs/COMMAND_REFERENCE.md#lightpanda-engine).
|
|
353
|
+
|
|
354
|
+
### Native navigation and interaction
|
|
355
|
+
|
|
341
356
|
`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 code 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.
|
|
342
357
|
|
|
343
358
|
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"`.
|
|
@@ -505,7 +520,7 @@ Use the exact returned recovery payload after errors. Timeout partial progress r
|
|
|
505
520
|
|
|
506
521
|
Both JSON and prose results expose a bounded canonical observation with recovery actions and artifact receipts. Oversized observations name an `observationPath` for the complete redacted result; audit-only `details` is not the model's sole recovery channel. Inner code calls avoid full model rendering, spills, and image encoding until output is selected.
|
|
507
522
|
|
|
508
|
-
Screenshots expose measured image dimensions and capture geometry when supported by matching before/after samples. A full-page or element image is not a viewport screenshot. Native mouse coordinates are viewport CSS pixels; use the [geometry contract](docs/TOOL_CONTRACT.md#image-observations) and current scroll/frame state before converting image pixels. Unknown geometry stays unknown, including ambiguous or scrolled element captures
|
|
523
|
+
Screenshots expose measured image dimensions and capture geometry when supported by matching before/after samples. A full-page or element image is not a viewport screenshot. Native mouse coordinates are viewport CSS pixels; use the [geometry contract](docs/TOOL_CONTRACT.md#image-observations) and current scroll/frame state before converting image pixels. Unknown geometry stays unknown, including ambiguous or scrolled element captures, unmeasured batch captures, and Lightpanda text-rendered images (`rendering: "text"`). Pi image-resize notes only map sent pixels to original image pixels.
|
|
509
524
|
|
|
510
525
|
Save artifacts to the user's exact paths and check `artifactVerification` before claiming they exist or are fresh. Conditional `changed: false` screenshots produce no new attachment. `outputPath` is a separate result-data destination and must not alias a screenshot, download, or recording. Close ends the browser session; explicit files remain host-owned. Recording starts remain pending until stop and receipt/file verification.
|
|
511
526
|
|
|
@@ -570,6 +585,7 @@ Use these rules:
|
|
|
570
585
|
- Prefer page actions and storage checks over cookie dumps. `cookies get` can expose real profile cookies.
|
|
571
586
|
- Prefer `auth save --password-stdin` over putting passwords in `args`; the wrapper only accepts caller `stdin` for `batch`, `eval --stdin`, and `auth save --password-stdin` (advanced QA and source tools generate their own batch stdin).
|
|
572
587
|
- Use `state save <path>` / `state load <path>` for portable test state. `state save` is reported as a file artifact with verification metadata; if an upstream-successful artifact command reports a non-pending file path that the wrapper cannot find or did not update during this command, the tool fails with `failureCategory: "artifact-missing"` instead of treating missing/stale evidence as durable. `state load` may mention a path but is not treated as a newly saved artifact.
|
|
588
|
+
- `cookies set --curl <file>` treats its operand as a file path, not a cookie value: invocation echoes and failed batch diagnostics keep ordinary import paths visible. Inline cookie values and secret-bearing flags or path text still redact.
|
|
573
589
|
- Treat `cookies get`, `storage local|session`, `state show`, and `auth show` output as sensitive. `state show` is presented as saved-state metadata only, and cookie/localStorage/sessionStorage values are redacted from structured details. The native presentation summarizes and redacts credential-like values while allowing benign primitive storage values to aid local QA, but avoid requesting broad dumps unless the task needs them.
|
|
574
590
|
- Use `dialog status`, `dialog accept [text]`, `dialog dismiss`, and `frame <selector|main>` through native `args`; dialog commands use a shorter wrapper timeout and timed-out interactions add `inspect-dialog-after-timeout` / `dismiss-dialog-after-timeout` / fresh-session recovery actions so a blocking alert/prompt does not burn the full default watchdog. Use exact `confirm <id>` / `deny <id>` next actions for guarded-action confirmations.
|
|
575
591
|
|
|
@@ -729,7 +745,7 @@ Install upstream `agent-browser`, then install dependencies:
|
|
|
729
745
|
npm install
|
|
730
746
|
```
|
|
731
747
|
|
|
732
|
-
Use the npm version declared in `package.json` `packageManager` when refreshing `package-lock.json` (for example `npx -y npm@12.
|
|
748
|
+
Use the npm version declared in `package.json` `packageManager` when refreshing `package-lock.json` (for example `npx -y npm@12.2.0 install`) so optional-platform lockfile metadata does not drift. Use Node 24.21.0 and Pi 1.0.0 or newer for lifecycle and interactive browser smokes; the pinned Pi 1.0.0 devDependencies are validation fixtures, not an exact-version requirement for the host CLI. See [Environment and automation pitfalls](docs/RELEASE.md#environment-and-automation-pitfalls) in `docs/RELEASE.md`.
|
|
733
749
|
|
|
734
750
|
Checkout-only extension smoke test:
|
|
735
751
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
1
|
import { randomUUID } from "node:crypto";
|
|
3
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
3
|
import { dirname, isAbsolute, join } from "node:path";
|
|
@@ -21,6 +20,8 @@ import { getAgentBrowserProcessEnvironment, withAgentBrowserProcessEnvironment,
|
|
|
21
20
|
import { withNativeSessionDefaults } from "./lib/orchestration/native-session-defaults.js";
|
|
22
21
|
import { getBrowserCwdError, resolveExecutionCwd } from "./lib/execution-cwd.js";
|
|
23
22
|
import { resolveOperationPaths } from "./lib/orchestration/operation-paths.js";
|
|
23
|
+
import { AsyncExecutionQueue, KeyedAsyncExecutionQueue } from "./lib/orchestration/execution-queue.js";
|
|
24
|
+
import { mergeBrowserRunArtifactManifest } from "./lib/orchestration/browser-run/artifact-merge.js";
|
|
24
25
|
import { closeManagedSession, inspectManagedSessionDaemon } from "./lib/orchestration/browser-run/managed-session-daemon-policy.js";
|
|
25
26
|
import { MINIMUM_AGENT_BROWSER_VERSION, SUPPORTED_AGENT_BROWSER_VERSION_LABEL, TARGET_AGENT_BROWSER_VERSION, getAgentBrowserVersionValidationError, parseAgentBrowserVersionOutput, } from "./lib/upstream-version.js";
|
|
26
27
|
import { buildPromptPolicy, getLatestUserMessage, getMessageText } from "./lib/prompt-policy.js";
|
|
@@ -37,7 +38,7 @@ import { applyAgentBrowserOutputPath, canWriteAgentBrowserOutput, normalizeReque
|
|
|
37
38
|
import { appendScriptSessionLease, createBrowserCodeOutput, getScriptSessionLeasesFromBranch } from "./lib/orchestration/script-mode.js";
|
|
38
39
|
import { resolveBrowserExecutionIdentity, withBrowserExecutionLock, withBrowserExecutionLocks } from "./lib/managed-session-policy-lock.js";
|
|
39
40
|
import { AGENT_BROWSER_INSTRUCTION_GROUP, AGENT_BROWSER_TOOL_INVENTORY, registerAgentBrowserToolSurface } from "./lib/tool-surface.js";
|
|
40
|
-
import { formatSessionArtifactRetentionSummary, getSessionArtifactManifestEntryKey,
|
|
41
|
+
import { formatSessionArtifactRetentionSummary, getSessionArtifactManifestEntryKey, isSessionArtifactManifest, retirePendingRecordingManifestEntries } from "./lib/results/artifact-manifest.js";
|
|
41
42
|
import { appendUniqueAgentBrowserNextActions, applyNamespaceToNextActions, applySessionToNextActions, buildNextToolAction } from "./lib/results/next-actions.js";
|
|
42
43
|
import { canRegisterWebSearchTool, loadAgentBrowserConfigSync } from "./lib/config.js";
|
|
43
44
|
import { appendRecordingReservationTransition, applyRecordingArtifactsToReservations, restoreRecordingReservationStateFromBranch, retireRecordingReservation, } from "./lib/recording-reservations.js";
|
|
@@ -647,90 +648,6 @@ function shouldSerializeBrowserCommand(options) {
|
|
|
647
648
|
return true;
|
|
648
649
|
return getActiveElectronRecords(options.ownedElectronLaunchRecords).some((record) => record.sessionName === options.explicitSessionName);
|
|
649
650
|
}
|
|
650
|
-
// Serializes managed-session read/modify/write work so overlapping tool calls cannot promote stale state or close an in-use session.
|
|
651
|
-
class AsyncExecutionQueue {
|
|
652
|
-
tail = Promise.resolve();
|
|
653
|
-
active = new AsyncLocalStorage();
|
|
654
|
-
isCurrent() { return this.active.getStore()?.active === true; }
|
|
655
|
-
run(work, signal, barrier = Promise.resolve()) {
|
|
656
|
-
if (this.isCurrent()) {
|
|
657
|
-
signal?.throwIfAborted();
|
|
658
|
-
return work();
|
|
659
|
-
}
|
|
660
|
-
const previous = this.tail;
|
|
661
|
-
let release;
|
|
662
|
-
this.tail = new Promise((resolve) => {
|
|
663
|
-
release = resolve;
|
|
664
|
-
});
|
|
665
|
-
let rejectWaiting;
|
|
666
|
-
const cancelled = new Promise((_resolve, reject) => { rejectWaiting = reject; });
|
|
667
|
-
const abortWaiting = () => rejectWaiting(signal?.reason);
|
|
668
|
-
signal?.addEventListener("abort", abortWaiting, { once: true });
|
|
669
|
-
if (signal?.aborted)
|
|
670
|
-
abortWaiting();
|
|
671
|
-
const execution = (async () => {
|
|
672
|
-
await Promise.all([previous, barrier]);
|
|
673
|
-
signal?.removeEventListener("abort", abortWaiting);
|
|
674
|
-
try {
|
|
675
|
-
signal?.throwIfAborted();
|
|
676
|
-
const scope = { active: true };
|
|
677
|
-
try {
|
|
678
|
-
return await this.active.run(scope, work);
|
|
679
|
-
}
|
|
680
|
-
finally {
|
|
681
|
-
scope.active = false;
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
finally {
|
|
685
|
-
release();
|
|
686
|
-
}
|
|
687
|
-
})();
|
|
688
|
-
return signal ? Promise.race([execution, cancelled]) : execution;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
export class KeyedAsyncExecutionQueue {
|
|
692
|
-
barriers = new Map();
|
|
693
|
-
entries = new Map();
|
|
694
|
-
async run(key, namespace, work, signal) {
|
|
695
|
-
const entry = this.entries.get(key) ?? { queue: new AsyncExecutionQueue(), users: 0 };
|
|
696
|
-
if (entry.queue.isCurrent())
|
|
697
|
-
return work();
|
|
698
|
-
const barrier = this.barriers.get(getAgentBrowserSessionIdentityKey("", namespace)) ?? Promise.resolve();
|
|
699
|
-
entry.users += 1;
|
|
700
|
-
this.entries.set(key, entry);
|
|
701
|
-
try {
|
|
702
|
-
return await entry.queue.run(work, signal, barrier);
|
|
703
|
-
}
|
|
704
|
-
finally {
|
|
705
|
-
entry.users -= 1;
|
|
706
|
-
if (entry.users === 0 && this.entries.get(key) === entry)
|
|
707
|
-
this.entries.delete(key);
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
async runExclusive(namespace, work) {
|
|
711
|
-
const namespaceKey = getAgentBrowserSessionIdentityKey("", namespace);
|
|
712
|
-
const previous = this.barriers.get(namespaceKey) ?? Promise.resolve();
|
|
713
|
-
let release;
|
|
714
|
-
const blocked = new Promise((resolve) => {
|
|
715
|
-
release = resolve;
|
|
716
|
-
});
|
|
717
|
-
const barrier = previous.then(() => blocked);
|
|
718
|
-
this.barriers.set(namespaceKey, barrier);
|
|
719
|
-
const drains = [...this.entries]
|
|
720
|
-
.filter(([key]) => isAgentBrowserSessionIdentityKeyInNamespace(key, namespace))
|
|
721
|
-
.map(([, { queue }]) => queue.run(async () => undefined));
|
|
722
|
-
await previous;
|
|
723
|
-
await Promise.all(drains);
|
|
724
|
-
try {
|
|
725
|
-
return await work();
|
|
726
|
-
}
|
|
727
|
-
finally {
|
|
728
|
-
release();
|
|
729
|
-
if (this.barriers.get(namespaceKey) === barrier)
|
|
730
|
-
this.barriers.delete(namespaceKey);
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
651
|
function mergeBrowserRunMap(current, initial, updated) {
|
|
735
652
|
if (updated === initial)
|
|
736
653
|
return current;
|
|
@@ -745,27 +662,6 @@ function mergeBrowserRunMap(current, initial, updated) {
|
|
|
745
662
|
}
|
|
746
663
|
return merged;
|
|
747
664
|
}
|
|
748
|
-
export function mergeBrowserRunArtifactManifest(current, initial, updated) {
|
|
749
|
-
if (!updated || updated === initial)
|
|
750
|
-
return current;
|
|
751
|
-
if (current === initial)
|
|
752
|
-
return updated;
|
|
753
|
-
const initialEntries = new Map((initial?.entries ?? []).map((entry) => [getSessionArtifactManifestEntryKey(entry), entry]));
|
|
754
|
-
const changedEntries = updated.entries
|
|
755
|
-
.map((entry, index) => ({ entry, index }))
|
|
756
|
-
.filter(({ entry }) => initialEntries.get(getSessionArtifactManifestEntryKey(entry)) !== entry)
|
|
757
|
-
.sort((left, right) => left.entry.createdAtMs - right.entry.createdAtMs
|
|
758
|
-
|| Number(isPendingRecordingCommand(left.entry.command, left.entry.subcommand, left.entry.kind)) - Number(isPendingRecordingCommand(right.entry.command, right.entry.subcommand, right.entry.kind))
|
|
759
|
-
|| left.index - right.index)
|
|
760
|
-
.map(({ entry }) => entry);
|
|
761
|
-
return changedEntries.length === 0
|
|
762
|
-
? current
|
|
763
|
-
: mergeSessionArtifactManifest({
|
|
764
|
-
base: current,
|
|
765
|
-
entries: changedEntries,
|
|
766
|
-
nowMs: Math.max(Date.now(), (current?.updatedAtMs ?? 0) + 1, updated.updatedAtMs),
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
665
|
function findPackageRoot(startDir) {
|
|
770
666
|
let currentDir = startDir;
|
|
771
667
|
while (true) {
|
|
@@ -76,7 +76,7 @@ function getOpenCommandTarget(commandTokens) {
|
|
|
76
76
|
}
|
|
77
77
|
return undefined;
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
function parseCommandInfoFromTokens(commandTokens) {
|
|
80
80
|
const upstreamCommandTokens = stripUpstreamGlobalFlags(commandTokens);
|
|
81
81
|
const command = upstreamCommandTokens[0];
|
|
82
82
|
return {
|
|
@@ -38,7 +38,7 @@ export const GLOBAL_VALUE_FLAGS = [
|
|
|
38
38
|
"--idle-timeout",
|
|
39
39
|
"--input-mode",
|
|
40
40
|
];
|
|
41
|
-
|
|
41
|
+
const COMMAND_VALUE_FLAGS = [
|
|
42
42
|
"--allowed-origins",
|
|
43
43
|
"--baseline",
|
|
44
44
|
"--body",
|
|
@@ -79,7 +79,7 @@ export const COMMAND_VALUE_FLAGS = [
|
|
|
79
79
|
"--password",
|
|
80
80
|
"--wait-until",
|
|
81
81
|
];
|
|
82
|
-
|
|
82
|
+
const OPTIONAL_GLOBAL_VALUE_FLAGS = new Set(["--restore"]);
|
|
83
83
|
export const VALUE_FLAGS = new Set([...GLOBAL_VALUE_FLAGS, ...COMMAND_VALUE_FLAGS]);
|
|
84
84
|
export const PREVALIDATED_VALUE_FLAGS = new Set(GLOBAL_VALUE_FLAGS);
|
|
85
85
|
export const GLOBAL_VALUE_FLAGS_ALLOWING_DASH_VALUE = new Set(["--args", "--session"]);
|
|
@@ -246,7 +246,7 @@ export function extractRequestedRestoreKey(args, sessionName, envValue) {
|
|
|
246
246
|
}
|
|
247
247
|
return restoreKey;
|
|
248
248
|
}
|
|
249
|
-
|
|
249
|
+
function getFlagName(token) {
|
|
250
250
|
return token.split("=", 1)[0] ?? token;
|
|
251
251
|
}
|
|
252
252
|
export function isNonFlagToken(token) {
|
|
@@ -4,7 +4,7 @@ import { buildPageTransitionRefSnapshotInvalidation, normalizeSessionTabTarget,
|
|
|
4
4
|
import { getSessionArtifactManifestEntryKey, isPendingRecordingArtifact, isSessionArtifactManifest } from "./results/artifact-manifest.js";
|
|
5
5
|
import { extractAgentBrowserLifecycle } from "./results/presentation/common.js";
|
|
6
6
|
export const BROWSER_TRANSITION_ENTRY = "agent-browser-transition";
|
|
7
|
-
|
|
7
|
+
const BROWSER_EVENT_VERSION = 1;
|
|
8
8
|
export const BROWSER_RESULT_TOOLS = new Set([
|
|
9
9
|
"agent_browser", "agent_browser_code", "agent_browser_action", "agent_browser_qa", "agent_browser_electron",
|
|
10
10
|
"agent_browser_source", "agent_browser_network_source",
|
|
@@ -295,9 +295,6 @@ function getCommandCapability(command) {
|
|
|
295
295
|
function hasCommandCapability(command, capability) {
|
|
296
296
|
return getCommandCapability(command)?.[capability] === true;
|
|
297
297
|
}
|
|
298
|
-
export function normalizeCommandName(command) {
|
|
299
|
-
return getCommandCapability(command)?.command ?? command;
|
|
300
|
-
}
|
|
301
298
|
export function isCloseCommand(command) {
|
|
302
299
|
return hasCommandCapability(command, "closesSession");
|
|
303
300
|
}
|
|
@@ -229,19 +229,6 @@ function validateBrowserDefaultProfile(value, path, errors) {
|
|
|
229
229
|
}
|
|
230
230
|
return { name, policy: /** @type {BrowserDefaultProfilePolicy} */ (policy) };
|
|
231
231
|
}
|
|
232
|
-
/** @param {string} rawValue */
|
|
233
|
-
export function isPlaintextCredentialValue(rawValue) {
|
|
234
|
-
const trimmed = rawValue.trim();
|
|
235
|
-
return Boolean(trimmed) && !trimmed.startsWith("!") && !trimmed.startsWith("$");
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* @param {string} rawValue
|
|
239
|
-
* @param {WebSearchProvider} provider
|
|
240
|
-
*/
|
|
241
|
-
export function isProjectSafeCredentialValueForProvider(rawValue, provider) {
|
|
242
|
-
void provider;
|
|
243
|
-
return rawValue.trim().length > 0;
|
|
244
|
-
}
|
|
245
232
|
/**
|
|
246
233
|
* @param {unknown} value
|
|
247
234
|
* @param {string} path
|
|
@@ -1,53 +1,8 @@
|
|
|
1
1
|
import { exec as execCallback } from "node:child_process";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
2
|
import { promisify } from "node:util";
|
|
4
|
-
import { SECRET_COMMAND_TIMEOUT_MS,
|
|
5
|
-
export { AGENT_BROWSER_CONFIG_ENV, BRAVE_API_KEY_ENV, CONFIG_RELATIVE_PATH, DEFAULT_WEB_SEARCH_PROVIDER, EXA_API_KEY_ENV, EXA_SEARCH_TYPES, GLOBAL_CONFIG_RELATIVE_PATH, SECRET_COMMAND_TIMEOUT_MS, WEB_SEARCH_PROVIDER_CONFIG_KEYS, WEB_SEARCH_PROVIDER_DESCRIPTORS, WEB_SEARCH_PROVIDER_ENV_VARS, WEB_SEARCH_PROVIDERS, buildAgentBrowserConfigState, buildWebSearchCredentialSources, canRegisterWebSearchTool, classifyCredentialSource, formatBrowserExecutableStatus, formatBrowserProfileStatus, getAgentBrowserConfigPaths, getCredentialSourceSummary, getGlobalAgentBrowserConfigPath, getProjectAgentBrowserConfigPath, getWebSearchCredentialSource, getWebSearchProviderConfigKey, getWebSearchProviderDescriptor, getWebSearchProviderEnvVar, getWebSearchProviderLabel, getWebSearchProviderOrder, hasPotentialCredentialSource,
|
|
3
|
+
import { SECRET_COMMAND_TIMEOUT_MS, getWebSearchCredentialSource, getWebSearchProviderOrder, loadAgentBrowserConfigStateSync, resolveEnvInterpolations, } from "./config-policy.js";
|
|
4
|
+
export { AGENT_BROWSER_CONFIG_ENV, BRAVE_API_KEY_ENV, CONFIG_RELATIVE_PATH, DEFAULT_WEB_SEARCH_PROVIDER, EXA_API_KEY_ENV, EXA_SEARCH_TYPES, GLOBAL_CONFIG_RELATIVE_PATH, SECRET_COMMAND_TIMEOUT_MS, WEB_SEARCH_PROVIDER_CONFIG_KEYS, WEB_SEARCH_PROVIDER_DESCRIPTORS, WEB_SEARCH_PROVIDER_ENV_VARS, WEB_SEARCH_PROVIDERS, buildAgentBrowserConfigState, buildWebSearchCredentialSources, canRegisterWebSearchTool, classifyCredentialSource, formatBrowserExecutableStatus, formatBrowserProfileStatus, getAgentBrowserConfigPaths, getCredentialSourceSummary, getGlobalAgentBrowserConfigPath, getProjectAgentBrowserConfigPath, getWebSearchCredentialSource, getWebSearchProviderConfigKey, getWebSearchProviderDescriptor, getWebSearchProviderEnvVar, getWebSearchProviderLabel, getWebSearchProviderOrder, hasPotentialCredentialSource, isWebSearchProvider, loadAgentBrowserConfigStateSync, mergeAgentBrowserConfig, parseAgentBrowserConfigLayer, resolveEnvInterpolations, summarizeConfigFiles, validateAgentBrowserConfig, validateWebSearchProvider, } from "./config-policy.js";
|
|
6
5
|
const exec = promisify(execCallback);
|
|
7
|
-
async function readConfigLayer(path, scope, errors, warnings) {
|
|
8
|
-
let raw;
|
|
9
|
-
try {
|
|
10
|
-
raw = await readFile(path, "utf8");
|
|
11
|
-
}
|
|
12
|
-
catch (error) {
|
|
13
|
-
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
14
|
-
return undefined;
|
|
15
|
-
}
|
|
16
|
-
errors.push(`Could not read ${scope} config ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
17
|
-
return undefined;
|
|
18
|
-
}
|
|
19
|
-
return parseAgentBrowserConfigLayer(raw, path, scope, errors, warnings);
|
|
20
|
-
}
|
|
21
|
-
export async function loadAgentBrowserConfig(options = {}) {
|
|
22
|
-
const env = options.env ?? process.env;
|
|
23
|
-
const paths = getAgentBrowserConfigPaths({ cwd: options.cwd, env });
|
|
24
|
-
const includeProjectConfig = options.includeProjectConfig !== false;
|
|
25
|
-
const errors = [];
|
|
26
|
-
const warnings = [];
|
|
27
|
-
const layerCandidates = [
|
|
28
|
-
{ path: paths.global, scope: "global" },
|
|
29
|
-
...(includeProjectConfig ? [{ path: paths.project, scope: "project" }] : []),
|
|
30
|
-
...(paths.override ? [{ path: paths.override, scope: "override" }] : []),
|
|
31
|
-
];
|
|
32
|
-
const layers = [];
|
|
33
|
-
let mergedConfig = {};
|
|
34
|
-
for (const candidate of layerCandidates) {
|
|
35
|
-
const layer = await readConfigLayer(candidate.path, candidate.scope, errors, warnings);
|
|
36
|
-
if (!layer)
|
|
37
|
-
continue;
|
|
38
|
-
layers.push(layer);
|
|
39
|
-
mergedConfig = mergeAgentBrowserConfig(mergedConfig, layer.config);
|
|
40
|
-
}
|
|
41
|
-
return buildAgentBrowserConfigState({
|
|
42
|
-
env,
|
|
43
|
-
errors,
|
|
44
|
-
layers,
|
|
45
|
-
mergedConfig,
|
|
46
|
-
paths,
|
|
47
|
-
projectConfigIncluded: includeProjectConfig,
|
|
48
|
-
warnings,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
6
|
export function loadAgentBrowserConfigSync(options = {}) {
|
|
52
7
|
return loadAgentBrowserConfigStateSync(options);
|
|
53
8
|
}
|
|
@@ -70,7 +25,7 @@ async function resolveCommandCredential(rawValue, signal) {
|
|
|
70
25
|
throw new Error("Credential command failed without exposing command output. Check pi-agent-browser-config web-search status and the configured secret manager command.");
|
|
71
26
|
}
|
|
72
27
|
}
|
|
73
|
-
|
|
28
|
+
async function resolveCredentialSource(source, options = {}) {
|
|
74
29
|
if (!source)
|
|
75
30
|
return undefined;
|
|
76
31
|
let value;
|
|
@@ -6,8 +6,8 @@ import { fetchCdpJson, parseCdpTargets, parseCdpVersion, } from "./cdp.js";
|
|
|
6
6
|
import { discoverElectronApps, inspectElectronAppPath, inspectElectronExecutablePath, } from "./discovery.js";
|
|
7
7
|
import { createSecureTempDirectory, preserveSecureTempDirectory } from "../temp.js";
|
|
8
8
|
export const ELECTRON_LAUNCH_RECORD_VERSION = 1;
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const ELECTRON_LAUNCH_DEFAULT_TIMEOUT_MS = 15_000;
|
|
10
|
+
const ELECTRON_LAUNCH_MAX_TIMEOUT_MS = 120_000;
|
|
11
11
|
const DEVTOOLS_ACTIVE_PORT_FILE = "DevToolsActivePort";
|
|
12
12
|
export const ELECTRON_PROFILE_DIR_PREFIX = "electron-profile-";
|
|
13
13
|
const ELECTRON_DEFAULT_APP_ARGS = ["--disable-extensions", "--no-first-run", "--no-default-browser-check"];
|
|
@@ -46,7 +46,7 @@ function policyEntryMatchesApp(entry, app) {
|
|
|
46
46
|
return false;
|
|
47
47
|
return appIdentifiers(app).some((identifier) => identifier.toLowerCase().includes(normalizedEntry));
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
function evaluateElectronLaunchPolicy(options) {
|
|
50
50
|
const denyEntry = options.deny?.find((entry) => policyEntryMatchesApp(entry, options.target));
|
|
51
51
|
if (denyEntry) {
|
|
52
52
|
return {
|
|
@@ -66,7 +66,7 @@ export function evaluateElectronLaunchPolicy(options) {
|
|
|
66
66
|
}
|
|
67
67
|
return undefined;
|
|
68
68
|
}
|
|
69
|
-
|
|
69
|
+
async function resolveElectronLaunchTarget(options) {
|
|
70
70
|
if (options.appPath)
|
|
71
71
|
return inspectElectronAppPath(options.appPath);
|
|
72
72
|
if (options.executablePath)
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { JsonSchema } from "../json-schema.js";
|
|
2
2
|
import { StringEnum as localStringEnum } from "../string-enum-schema.js";
|
|
3
3
|
import { ELECTRON_DISCOVERY_DEFAULT_MAX_RESULTS, ELECTRON_DISCOVERY_MAX_RESULTS } from "../electron/discovery.js";
|
|
4
|
-
import { AGENT_BROWSER_ELECTRON_HANDOFFS, AGENT_BROWSER_ELECTRON_TARGET_TYPES, AGENT_BROWSER_QA_LOAD_STATES, AGENT_BROWSER_SEMANTIC_ACTIONS, AGENT_BROWSER_SEMANTIC_LOCATORS, DEFAULT_SESSION_MODE, SOURCE_LOOKUP_MAX_WORKSPACE_FILES, } from "./types.js";
|
|
5
|
-
|
|
6
|
-
export const AGENT_BROWSER_CODE_MAX_TIMEOUT_MS = 300_000;
|
|
4
|
+
import { AGENT_BROWSER_CODE_MAX_TIMEOUT_MS, AGENT_BROWSER_ELECTRON_HANDOFFS, AGENT_BROWSER_ELECTRON_TARGET_TYPES, AGENT_BROWSER_QA_LOAD_STATES, AGENT_BROWSER_SEMANTIC_ACTIONS, AGENT_BROWSER_SEMANTIC_LOCATORS, DEFAULT_SESSION_MODE, SOURCE_LOOKUP_MAX_WORKSPACE_FILES, } from "./types.js";
|
|
5
|
+
const AGENT_BROWSER_ADVANCED_TOOLS = ["action", "qa", "electron", "source", "network"];
|
|
7
6
|
function outputProperties(Type) {
|
|
8
7
|
return {
|
|
9
8
|
outputPath: Type.Optional(Type.String({ description: "Result-data path, separate from browser artifact paths.", minLength: 1 })),
|
|
@@ -2,20 +2,34 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import which from "which";
|
|
5
6
|
import { isRecord } from "../parsing.js";
|
|
6
7
|
import { extractExplicitNamespace, extractExplicitSessionName, scanUpstreamGlobalFlagOccurrences } from "../argv-grammar.js";
|
|
7
8
|
import { getUpstreamEffectiveBatchSteps } from "../orchestration/batch-stdin.js";
|
|
8
9
|
import { extractUpstreamCommandTokens } from "../argv-descriptor.js";
|
|
9
10
|
import { isCloseAllCommand } from "../command-taxonomy.js";
|
|
10
11
|
import { redactSensitiveText, validateToolArgs } from "../runtime.js";
|
|
12
|
+
import { AGENT_BROWSER_CODE_MAX_TIMEOUT_MS } from "./types.js";
|
|
11
13
|
export const AGENT_BROWSER_SCRIPT_CODE_MAX_BYTES = 64 * 1_024;
|
|
12
14
|
export const AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS = 120_000;
|
|
13
15
|
export const AGENT_BROWSER_SCRIPT_NAMESPACE = "";
|
|
14
|
-
export const AGENT_BROWSER_SCRIPT_MAX_TIMEOUT_MS = 300_000;
|
|
15
16
|
export const AGENT_BROWSER_SCRIPT_MAX_CALLS = 25;
|
|
16
17
|
export const AGENT_BROWSER_SCRIPT_FINAL_OUTPUT_MAX_BYTES = 64 * 1_024;
|
|
17
18
|
export const AGENT_BROWSER_SCRIPT_IPC_MESSAGE_MAX_BYTES = 1 * 1_024 * 1_024;
|
|
18
|
-
|
|
19
|
+
const AGENT_BROWSER_SCRIPT_IPC_CUMULATIVE_MAX_BYTES = 8 * 1_024 * 1_024;
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the Node runtime that hosts the permissioned code child. A Bun-compiled Pi binary
|
|
22
|
+
* points `process.execPath` at the pi executable, which rejects Node-only flags like
|
|
23
|
+
* `--permission`, so those hosts must spawn a real `node` from PATH instead.
|
|
24
|
+
*/
|
|
25
|
+
export function resolveScriptChildNodePath(options) {
|
|
26
|
+
if (options.runtime.bun === undefined)
|
|
27
|
+
return options.execPath;
|
|
28
|
+
const nodePath = options.whichNode();
|
|
29
|
+
if (nodePath === null)
|
|
30
|
+
throw new Error("agent_browser_code requires a `node` runtime on PATH when pi runs on a Bun binary.");
|
|
31
|
+
return nodePath;
|
|
32
|
+
}
|
|
19
33
|
function findPackageRoot(startDir) {
|
|
20
34
|
let currentDir = startDir;
|
|
21
35
|
for (;;) {
|
|
@@ -34,7 +48,7 @@ function resolveScriptWorkerPath() {
|
|
|
34
48
|
return workerPath;
|
|
35
49
|
}
|
|
36
50
|
const SCRIPT_SESSION_NAME_PATTERN = /^piab-script-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
37
|
-
|
|
51
|
+
function validateAgentBrowserScriptSource(input) {
|
|
38
52
|
if (typeof input !== "string")
|
|
39
53
|
return { error: "script must be a string." };
|
|
40
54
|
const bytes = Buffer.byteLength(input, "utf8");
|
|
@@ -175,8 +189,8 @@ export async function runAgentBrowserScript(options) {
|
|
|
175
189
|
return buildFailedRun({ callCount: 0, emitCount: 0, error: compiled.error, failureCategory: "validation-error", rejectedCallCount: 0, steps: [] });
|
|
176
190
|
}
|
|
177
191
|
const timeoutMs = options.timeoutMs ?? AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS;
|
|
178
|
-
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs >
|
|
179
|
-
return buildFailedRun({ callCount: 0, emitCount: 0, error: `script timeoutMs must be between 1 and ${
|
|
192
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > AGENT_BROWSER_CODE_MAX_TIMEOUT_MS) {
|
|
193
|
+
return buildFailedRun({ callCount: 0, emitCount: 0, error: `script timeoutMs must be between 1 and ${AGENT_BROWSER_CODE_MAX_TIMEOUT_MS}.`, failureCategory: "validation-error", rejectedCallCount: 0, steps: [] });
|
|
180
194
|
}
|
|
181
195
|
if (options.signal?.aborted) {
|
|
182
196
|
return buildFailedRun({ aborted: true, callCount: 0, emitCount: 0, error: "Script execution was aborted.", failureCategory: "aborted", rejectedCallCount: 0, steps: [] });
|
|
@@ -189,7 +203,15 @@ export async function runAgentBrowserScript(options) {
|
|
|
189
203
|
const message = error instanceof Error ? error.message : "Compiled script worker is missing.";
|
|
190
204
|
return buildFailedRun({ callCount: 0, emitCount: 0, error: message, failureCategory: "missing-binary", rejectedCallCount: 0, steps: [] });
|
|
191
205
|
}
|
|
192
|
-
|
|
206
|
+
let childNodePath;
|
|
207
|
+
try {
|
|
208
|
+
childNodePath = resolveScriptChildNodePath({ runtime: { bun: process.versions.bun }, execPath: process.execPath, whichNode: () => which.sync("node", { nothrow: true }) });
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
const message = error instanceof Error ? error.message : "No Node runtime is available for the code child.";
|
|
212
|
+
return buildFailedRun({ callCount: 0, emitCount: 0, error: message, failureCategory: "missing-binary", rejectedCallCount: 0, steps: [] });
|
|
213
|
+
}
|
|
214
|
+
const child = spawn(childNodePath, [
|
|
193
215
|
"--permission",
|
|
194
216
|
"--max-old-space-size=64",
|
|
195
217
|
workerPath,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export const DEFAULT_SESSION_MODE = "auto";
|
|
2
|
+
export const AGENT_BROWSER_CODE_MAX_TIMEOUT_MS = 300_000;
|
|
2
3
|
export const AGENT_BROWSER_SEMANTIC_ACTIONS = ["check", "click", "fill", "select"];
|
|
3
4
|
export const AGENT_BROWSER_SEMANTIC_LOCATORS = ["alt", "label", "placeholder", "role", "testid", "text", "title"];
|
|
4
|
-
|
|
5
|
-
export const AGENT_BROWSER_JOB_STEP_ACTIONS = ["open", "click", "fill", "type", "select", "wait", "assertText", "assertUrl", "waitForDownload", "screenshot", "snapshot"];
|
|
5
|
+
const AGENT_BROWSER_JOB_STEP_ACTIONS = ["open", "click", "fill", "type", "select", "wait", "assertText", "assertUrl", "waitForDownload", "screenshot", "snapshot"];
|
|
6
6
|
export const AGENT_BROWSER_QA_LOAD_STATES = ["domcontentloaded", "load", "networkidle"];
|
|
7
7
|
export const AGENT_BROWSER_ELECTRON_ACTIONS = ["list", "launch", "status", "cleanup", "probe"];
|
|
8
8
|
export const AGENT_BROWSER_ELECTRON_HANDOFFS = ["connect", "tabs", "snapshot"];
|
|
@@ -29,6 +29,10 @@ export const LAUNCH_SCOPED_FLAG_DEFINITIONS = [
|
|
|
29
29
|
flag: "--enable",
|
|
30
30
|
reason: "selects built-in page init scripts before the upstream browser session is launched",
|
|
31
31
|
},
|
|
32
|
+
{
|
|
33
|
+
flag: "--engine",
|
|
34
|
+
reason: "selects the browser engine used for the upstream launch",
|
|
35
|
+
},
|
|
32
36
|
{
|
|
33
37
|
flag: "--executable-path",
|
|
34
38
|
reason: "selects the browser executable used for the upstream launch",
|
|
@@ -82,9 +82,6 @@ const ownedManagedSessionStorage = new AsyncLocalStorage();
|
|
|
82
82
|
export async function withOwnedManagedSessionContext(context, run) {
|
|
83
83
|
return await ownedManagedSessionStorage.run(context, run);
|
|
84
84
|
}
|
|
85
|
-
export function getOwnedManagedSessionRestoreKey() {
|
|
86
|
-
return ownedManagedSessionStorage.getStore()?.restoreKey;
|
|
87
|
-
}
|
|
88
85
|
export function resolveOwnedManagedSessionContext(options) {
|
|
89
86
|
const namespace = canonicalizeAgentBrowserNamespace(options.namespace);
|
|
90
87
|
const currentNamespace = canonicalizeAgentBrowserNamespace(options.currentManagedSessionNamespace);
|
|
@@ -115,7 +112,7 @@ function hasExplicitConfigArg(args) {
|
|
|
115
112
|
function closesBrowserSession(args) {
|
|
116
113
|
return ["close", "exit", "quit"].includes(parseCommandInfo(args).command ?? "");
|
|
117
114
|
}
|
|
118
|
-
|
|
115
|
+
function agentBrowserExplicitConfigIsPresent(parentEnv = getAgentBrowserProcessEnvironment(), args = []) {
|
|
119
116
|
return hasExplicitConfigArg(args) || hasUpstreamEnvValue(parentEnv, AGENT_BROWSER_CONFIG_ENV);
|
|
120
117
|
}
|
|
121
118
|
/** Caller-selected upstream config disables the wrapper's automatic restore injection without blocking that config. */
|
|
@@ -139,12 +139,3 @@ export function getUpstreamEffectiveBatchSteps(commandTokens, stdin) {
|
|
|
139
139
|
return argumentSteps;
|
|
140
140
|
return parseUserBatchStdin(stdin).steps ?? [];
|
|
141
141
|
}
|
|
142
|
-
export function parseValidBatchStepEntries(stdin) {
|
|
143
|
-
const parsed = parseBatchStdinJsonArray(stdin);
|
|
144
|
-
if (parsed.error || parsed.steps === undefined)
|
|
145
|
-
return [];
|
|
146
|
-
return parsed.steps.flatMap((step, index) => {
|
|
147
|
-
const validated = validateUserBatchStep(step, index);
|
|
148
|
-
return validated.ok ? [{ index, step: validated.step }] : [];
|
|
149
|
-
});
|
|
150
|
-
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getSessionArtifactManifestEntryKey, isPendingRecordingCommand, mergeSessionArtifactManifest } from "../../results/artifact-manifest.js";
|
|
2
|
+
export function mergeBrowserRunArtifactManifest(current, initial, updated) {
|
|
3
|
+
if (!updated || updated === initial)
|
|
4
|
+
return current;
|
|
5
|
+
if (current === initial)
|
|
6
|
+
return updated;
|
|
7
|
+
const initialEntries = new Map((initial?.entries ?? []).map((entry) => [getSessionArtifactManifestEntryKey(entry), entry]));
|
|
8
|
+
const changedEntries = updated.entries
|
|
9
|
+
.map((entry, index) => ({ entry, index }))
|
|
10
|
+
.filter(({ entry }) => initialEntries.get(getSessionArtifactManifestEntryKey(entry)) !== entry)
|
|
11
|
+
.sort((left, right) => left.entry.createdAtMs - right.entry.createdAtMs
|
|
12
|
+
|| Number(isPendingRecordingCommand(left.entry.command, left.entry.subcommand, left.entry.kind)) - Number(isPendingRecordingCommand(right.entry.command, right.entry.subcommand, right.entry.kind))
|
|
13
|
+
|| left.index - right.index)
|
|
14
|
+
.map(({ entry }) => entry);
|
|
15
|
+
return changedEntries.length === 0
|
|
16
|
+
? current
|
|
17
|
+
: mergeSessionArtifactManifest({
|
|
18
|
+
base: current,
|
|
19
|
+
entries: changedEntries,
|
|
20
|
+
nowMs: Math.max(Date.now(), (current?.updatedAtMs ?? 0) + 1, updated.updatedAtMs),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -566,7 +566,7 @@ export function formatQaAttachedTargetText(target) {
|
|
|
566
566
|
return undefined;
|
|
567
567
|
return ["QA attached target:", target.sessionName, target.title, target.url].filter((part) => typeof part === "string" && part.length > 0).join(" — ");
|
|
568
568
|
}
|
|
569
|
-
|
|
569
|
+
function buildQaAttachedRecoveryNextActions(sessionName) {
|
|
570
570
|
const sessionArgs = (args) => withOptionalSessionArgs(sessionName, args);
|
|
571
571
|
return [
|
|
572
572
|
buildNextToolAction({
|
|
@@ -26,13 +26,13 @@ export function buildMissingBinaryMessage() {
|
|
|
26
26
|
].join("\n");
|
|
27
27
|
}
|
|
28
28
|
const SEMANTIC_ACTION_CANDIDATE_ACTION_IDS = new Set(["try-button-name-candidate", "try-link-name-candidate"]);
|
|
29
|
-
|
|
29
|
+
function formatSemanticActionCandidateText(actions) {
|
|
30
30
|
const candidateActions = actions.filter((action) => SEMANTIC_ACTION_CANDIDATE_ACTION_IDS.has(action.id) && action.params?.args);
|
|
31
31
|
if (candidateActions.length === 0)
|
|
32
32
|
return undefined;
|
|
33
33
|
return ["Agent-browser candidate fallbacks:", ...candidateActions.map((action) => `- ${action.id}: agent_browser ${JSON.stringify({ args: action.params?.args })} — ${action.reason}`)].join("\n");
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
function buildSemanticActionCandidateActions(compiled) {
|
|
36
36
|
const commandIndex = getCompiledSemanticActionCommandIndex(compiled);
|
|
37
37
|
if (commandIndex < 0 || compiled.args[commandIndex] !== "find")
|
|
38
38
|
return [];
|