pi-agent-browser-native 0.8.3 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -1
- package/README.md +11 -11
- package/dist/extensions/agent-browser/index.js +93 -213
- package/dist/extensions/agent-browser/lib/browser-journal.js +33 -12
- package/dist/extensions/agent-browser/lib/execution-cwd.js +0 -16
- package/dist/extensions/agent-browser/lib/native-output.js +46 -0
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +30 -16
- package/dist/extensions/agent-browser/lib/prompt-policy.js +16 -12
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +4 -0
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +15 -8
- package/dist/extensions/agent-browser/lib/tool-surface.js +39 -11
- package/dist/extensions/agent-browser/lib/web-search.js +85 -64
- package/docs/ARCHITECTURE.md +13 -15
- package/docs/COMMAND_REFERENCE.md +4 -2
- package/docs/RELEASE.md +25 -19
- package/docs/REQUIREMENTS.md +5 -5
- package/docs/SESSION_STORAGE.md +4 -4
- package/docs/SUPPORT_MATRIX.md +12 -10
- package/docs/TOOL_CONTRACT.md +9 -5
- package/package.json +4 -4
- package/scripts/doctor.mjs +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.9.0 - 2026-10-01
|
|
4
|
+
|
|
5
|
+
### Breaking changes
|
|
6
|
+
|
|
7
|
+
- Require Pi 1.0.0 or newer; Node 24.21.0 and separately installed upstream `agent-browser` requirements are unchanged. Pin the official development graph to 1.0.0 while retaining host-owned wildcard optional peers.
|
|
8
|
+
- Remove Browser integration with dropped checkpoint and fork metadata/revision APIs. Keep stopped-copy legacy journal conversion, native save-on-close, browser/session replay and ownership-aware cleanup.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Use native `defaultActive: false` for the five advanced direct tools, retaining additive loader activation, explicit/defaultTools selections and the narrow official-1.0 initial-resume fallback.
|
|
13
|
+
- Return canonical native `isError` and prose failure notices directly, without a result-repair hook; preserve explicit parseable JSON. Add native namespace/output-schema metadata and bounded redacted structured observations, loader inventory and normalized search results without exposing internal replay state or replacing the Browser code VM.
|
|
14
|
+
- Skip irrelevant tool-event policy work; keep raw latest-user policy fresh through message completion and native content replacement. Reuse validated capture-local admission prefixes and one queued replay/launch-configuration fold, preserving publication checks and captured-leaf correctness. Skip unchanged per-row TUI formatting and text updates.
|
|
15
|
+
- Replace the fork-only checkpoint CI gate with portable Native lifecycle Linux SDK qualification. Official/fork qualification remains independent; while the fork's `main` is below the supported Pi floor its lane emits a truthful unattempted-below-floor receipt and skips the fork build instead of resurrecting dropped APIs. Windows remains owner-waived, not passed.
|
|
4
16
|
|
|
5
17
|
## 0.8.3 - 2026-10-01
|
|
6
18
|
|
package/README.md
CHANGED
|
@@ -6,9 +6,9 @@ It is for Pi users who want agents to browse sites, inspect pages, click through
|
|
|
6
6
|
|
|
7
7
|
## Pi release qualification
|
|
8
8
|
|
|
9
|
-
Development host dependencies are pinned to official Pi **0.
|
|
9
|
+
Development host dependencies are pinned to official Pi **1.0.0**, with wildcard runtime peers. `npm run check:compat` checks the installed candidate SDK/CLI identity, typechecks, runs the existing offline tests (including the native Pi pipeline with a controlled provider and fake browser), and tests a packed runtime-only installation through both the SDK and the actual bundled RPC CLI. It does not substitute a fork SDK while leaving official types or child CLI installed.
|
|
10
10
|
|
|
11
|
-
Run in an empty HOME with a **short** TMPDIR outside your real home. Long temporary paths can exceed macOS's 103-byte Unix socket limit; keep the fixture's socket safety checks intact.
|
|
11
|
+
Run in an empty HOME with a **short** TMPDIR outside your real home. Long temporary paths can exceed macOS's 103-byte Unix socket limit; keep the fixture's socket safety checks intact. Native lifecycle controls, live upstream help sampling, lifecycle dogfood, and platform qualification remain the separate gates documented below. A Pi-only compatibility run uses no live browser profiles, credentials, or agent-browser fork, and does not certify those external integrations.
|
|
12
12
|
|
|
13
13
|
For the 0.6.16 compatibility rollout, the owner explicitly waived Windows qualification on 2026-09-21. Windows diagnostics and failures remain visible but nonblocking; this is **not** a Windows full-suite pass. Linux/macOS, declared Node floors, official/fork host and consumer-artifact checks remain required. Known Windows failures and follow-up are tracked in [#191](https://github.com/fitchmultz/pi-agent-browser-native/issues/191); see the [current support evidence](docs/SUPPORT_MATRIX.md#current-0616-rollout).
|
|
14
14
|
|
|
@@ -89,7 +89,7 @@ The result is optimized for agent work:
|
|
|
89
89
|
| Auth/profile workflows can leak secrets | Supports `auth save --password-stdin`, redacts sensitive args, SAML/OAuth-bearing URLs, stdout/stderr, details, and snapshot spills, and discards malformed oversized stdout instead of persisting a parse-failure spill | `test/agent-browser.extension-security-redaction.test.ts` |
|
|
90
90
|
| Stateful cookies/storage/auth output bloats or leaks context | Presentation layer redacts `details.data` for cookies and credential-like storage values while keeping low-risk local QA values such as `theme: dark` readable; recursively scrubs other structured upstream JSON (network, diff, trace/profiler, stream, dashboard, chat, auth, dialog, frame, state, and similar) using sensitive key names plus string heuristics; masks sensitive argv flags and positionals, including raw batch rows and ordered code journals; scrubs secrets from failed batch step errors; and exposes a compact redacted `batch` matrix on top-level `details.data` | `extensions/agent-browser/lib/results/presentation.ts`, `extensions/agent-browser/lib/results/presentation/diagnostics.ts`, `extensions/agent-browser/lib/runtime.ts`, `test/agent-browser.presentation-diagnostics.test.ts` |
|
|
91
91
|
| Stale `@eN` refs fail mysteriously | Defines full per-session refs once in the native journal, exposes them on dedicated captures, rejects mismatched URLs / unknown refs / unsafe `batch` stdin ordering before spawn, adds recovery guidance to rerun `snapshot -i` or use stable `find` locators | `extensions/agent-browser/index.ts`, `extensions/agent-browser/lib/session-page-state.ts`, `test/agent-browser.session-page-state.test.ts`, `test/agent-browser.results.test.ts`, `test/agent-browser.extension-ref-guards.test.ts`, `test/agent-browser.extension-semantic-recovery.test.ts` |
|
|
92
|
-
| Agents need stable success/failure buckets | Exposes bounded `resultCategory`, `successCategory`, and `failureCategory` on tool `details` for branching without parsing prose;
|
|
92
|
+
| Agents need stable success/failure buckets | Exposes bounded `resultCategory`, `successCategory`, and `failureCategory` on tool `details` for branching without parsing prose; final results return native `isError` and a visible prose failure notice while preserving parseable caller-requested `--json` output. Native structured results expose the same bounded, redacted observations, not internal replay metadata | [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details), `extensions/agent-browser/lib/results/categories.ts`, `extensions/agent-browser/index.ts`, `extensions/agent-browser/lib/pi-tool-rendering.ts`, `test/agent-browser.results.test.ts`, `test/agent-browser.extension-validation.test.ts`, `test/agent-browser.pi-pipeline.test.ts` |
|
|
93
93
|
| Clicks can report success without the page receiving the event | Top-level non-Electron direct `click` calls on `xpath=` targets or role-gated current `@e…` refs (`button`, `checkbox`, `menuitem`, `radio`, `switch`, `tab`) install a bounded target-specific DOM-event probe; eligible `@e…` refs require a unique role/name in both the latest snapshot and the live candidates. A per-probe temporary DOM marker must be read back by native `get attr <original-selector> <marker>` before the candidate can verify dispatch, including XPath scope. Missing or mismatched identity cleans up the probe and leaves the native click unverified, not failed. Duplicate-name refs pass through without a probe: their old ordinal cannot prove which element received the click. If upstream reports success but no trusted event reaches the resolved target, the wrapper fails the tool, exposes `details.clickDispatch`, and suggests explicit retry/inspect next actions (no in-page replay), including a nested-scroll `scrollintoview` action when the probe sees the target outside a scroll container or viewport. Unresolved locator clicks such as raw `find … click` are left upstream-owned to avoid false failures for frame-scoped targets. Other click results still expose `details.pageChangeSummary`; `observed: false` explicitly marks dispatch-only mutation summaries and adds a visible `Action dispatched; application change unverified` warning. Unchanged-URL clicks can surface evidence-backed `details.overlayBlockers` candidates. | [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details), `extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts`, `extensions/agent-browser/lib/results/presentation/navigation.ts`, `test/agent-browser.presentation.test.ts`, `test/agent-browser.extension-click-dispatch.test.ts` |
|
|
94
94
|
| Dashboard scroll commands can look successful while nothing moves | Handles standard `scroll <dir> [px]` against the document first (including pages whose smooth-scroll CSS defeats upstream wheel timing), falls back upstream when the document cannot move, and samples viewport/containers around the fallback; explicit CSS-container scrolling uses instant movement so smooth-scroll CSS cannot cause a premature no-movement failure; unchanged positions fail as `upstream-error` with `details.scrollNoop`, visible recovery guidance, and exact snapshot/screenshot checks. Unsupported `scrollintoview text=...` fails before dispatch, including inside effective batch rows, and shows exact native `find text ... hover` and snapshot/ref recovery payloads; help remains native pass-through. | [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details), [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#core-page-and-element-commands), `test/agent-browser.extension-validation.test.ts` |
|
|
95
95
|
| Dropdown/combobox clicks can focus or hit native option box-model errors | Adds first-class `select <selector> <value...>` paths through native `args` and `agent_browser_action`; semantic role/name or label select resolves exactly one current visible combobox/listbox ref before action. Custom combobox clicks still detect focused controls with explicit `aria-expanded` state but no visible options and return `details.comboboxFocus` plus exact recovery `nextActions` | [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details), [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#core-page-and-element-commands), `extensions/agent-browser/lib/input-modes/semantic-action.ts`, `test/agent-browser.extension-input-modes.test.ts`, `test/agent-browser.extension-validation.test.ts` |
|
|
@@ -103,7 +103,7 @@ Artifact results show known requested paths separately from reported/resolved lo
|
|
|
103
103
|
|
|
104
104
|
## Fastest way to try it
|
|
105
105
|
|
|
106
|
-
Use Node 24.21.0 or newer and Pi 0.
|
|
106
|
+
Use Node 24.21.0 or newer and Pi 1.0.0 or newer. This package keeps optional Pi core imports as wildcard `peerDependencies` because Pi package docs require the host Pi install to provide those packages, pins its direct Pi validation dependencies to 1.0.0, and makes Pi hosts below 1.0.0 a setup failure through `pi-agent-browser-doctor`. There are no compatibility shims for older Pi releases.
|
|
107
107
|
|
|
108
108
|
Install upstream `agent-browser` first and make sure it is on `PATH`:
|
|
109
109
|
|
|
@@ -172,7 +172,7 @@ pi --no-extensions -e npm:pi-agent-browser-native
|
|
|
172
172
|
|
|
173
173
|
`--no-extensions` disables automatic extension loading, not Pi settings, configured package resolution, skills, prompts, themes, or context files.
|
|
174
174
|
|
|
175
|
-
Pi
|
|
175
|
+
Pi may ask whether to trust projects with trust-gated settings or resources. This extension follows Pi's trust decision when loading its project-local config. `--no-approve` skips that config and Pi's trust-gated project resources; context files such as `AGENTS.md` still load unless context loading is separately disabled.
|
|
176
176
|
|
|
177
177
|
For a specific published version:
|
|
178
178
|
|
|
@@ -286,7 +286,7 @@ printf '%s' "$EXA_API_KEY" | npm exec --yes --package pi-agent-browser-native@la
|
|
|
286
286
|
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config web-search set-command "op read 'op://Private/Brave Search/API Key'" --provider brave --global
|
|
287
287
|
```
|
|
288
288
|
|
|
289
|
-
Config merges in this order: global → project → `PI_AGENT_BROWSER_CONFIG` override.
|
|
289
|
+
Config merges in this order: global → project → `PI_AGENT_BROWSER_CONFIG` override. The globally installed or CLI-loaded extension still loads project-local `.pi/config/pi-agent-browser-native/config.json` when Pi trust allows that project layer; it skips that project layer when Pi reports the project is untrusted or when Pi is launched with `--no-approve`. `webSearch.enabled` is evaluated after the loaded layers merge. Use `web-search disable --global` for a user default, `web-search disable --project` for one repo, and a `PI_AGENT_BROWSER_CONFIG` override with `{ "webSearch": { "enabled": false } }` when web search must stay off even if project config exists. Loaded config may use plaintext, custom environment aliases, interpolation literals, malformed-or-late-bound `$` values, and `!command` credential sources; the resolved secret is passed to the provider request while tool content, details, status output, and docs examples stay redacted. `web-search set-key`, `set-command`, and `clear` require `--provider`; `set-env` infers Exa/Brave from `EXA_API_KEY` or `BRAVE_API_KEY` unless you pass `--provider`.
|
|
290
290
|
|
|
291
291
|
For Exa, the effective mode is the per-call `searchType`, then `webSearch.defaultSearchType`, then `auto`. A research-heavy coding workflow should set the config default to `deep-lite`; callers can still override it per search. Users who do not opt in keep the existing `auto` latency.
|
|
292
292
|
|
|
@@ -550,9 +550,9 @@ Policy-required URL reads keep the two-call read → confirm/deny flow. Returned
|
|
|
550
550
|
|
|
551
551
|
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.
|
|
552
552
|
|
|
553
|
-
###
|
|
553
|
+
### Lifecycle and cleanup
|
|
554
554
|
|
|
555
|
-
|
|
555
|
+
Shared root and caller-owned browsers survive Pi quit; managed/fresh browsers and wrapper-launched Electron resources retain ownership-aware cleanup. Close or clean up owned resources explicitly before disabling or removing the extension. Reload after extension removal has no browser handover guarantee. Native save-on-close and per-root restore remain the persistence path; page JavaScript, unsaved forms, and live recordings are not serialized. See [session ownership](docs/ARCHITECTURE.md#session-model).
|
|
556
556
|
|
|
557
557
|
### Profile selection
|
|
558
558
|
|
|
@@ -702,7 +702,7 @@ npm run verify -- release
|
|
|
702
702
|
`pi-agent-browser-native` is intentionally thin:
|
|
703
703
|
|
|
704
704
|
1. Pi loads the compiled `dist/extensions/agent-browser/index.js` entrypoint from the package manifest; TypeScript under `extensions/` remains the source of truth and `npm run build` regenerates `dist/` before packing.
|
|
705
|
-
2. The extension registers compact direct/code tools, an advanced-tool loader with five
|
|
705
|
+
2. The extension registers compact direct/code tools, an advanced-tool loader with five native default-inactive specialized tools (unless selected by the host), and the optional credential-backed `agent_browser_web_search` companion.
|
|
706
706
|
3. Tool calls are translated into upstream `agent-browser` CLI invocations with controlled args, stdin, environment, timeout, and session planning.
|
|
707
707
|
4. Upstream JSON/plain-text output is parsed into model-friendly content and structured details.
|
|
708
708
|
5. Screenshots, downloads, recordings, traces, profiles, and spill files are normalized as Pi-visible artifacts where possible.
|
|
@@ -729,7 +729,7 @@ Install upstream `agent-browser`, then install dependencies:
|
|
|
729
729
|
npm install
|
|
730
730
|
```
|
|
731
731
|
|
|
732
|
-
Use the npm version declared in `package.json` `packageManager` when refreshing `package-lock.json` (for example `npx -y npm@12.1.0 install`) so optional-platform lockfile metadata does not drift. Use Node 24.21.0 and Pi 0.
|
|
732
|
+
Use the npm version declared in `package.json` `packageManager` when refreshing `package-lock.json` (for example `npx -y npm@12.1.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
733
|
|
|
734
734
|
Checkout-only extension smoke test:
|
|
735
735
|
|
|
@@ -747,7 +747,7 @@ Configured-source lifecycle validation:
|
|
|
747
747
|
npm run verify -- lifecycle
|
|
748
748
|
```
|
|
749
749
|
|
|
750
|
-
The harness defaults to Pi model `zai/glm-5.2` and **180000 ms** per-step tmux waits; pass `--model <id>` and/or `--timeout-ms <ms>` after `lifecycle` when you need different settings (see [Configured-source lifecycle validation](docs/RELEASE.md#configured-source-lifecycle-validation) in `docs/RELEASE.md`). It launches the supported Pi runtime with `--approve` and a deterministic `--session-id`, drives `/reload`, closes Pi, relaunches the exact same session, asserts the JSONL header id, and checks managed-session continuity, compiled-entrypoint pickup after process restart, persisted spill reachability, and
|
|
750
|
+
The harness defaults to Pi model `zai/glm-5.2` and **180000 ms** per-step tmux waits; pass `--model <id>` and/or `--timeout-ms <ms>` after `lifecycle` when you need different settings (see [Configured-source lifecycle validation](docs/RELEASE.md#configured-source-lifecycle-validation) in `docs/RELEASE.md`). It launches the supported Pi runtime with `--approve` and a deterministic `--session-id`, drives `/reload`, closes Pi, relaunches the exact same session, asserts the JSONL header id, and checks managed-session continuity, compiled-entrypoint pickup after process restart, persisted spill reachability, and native returned failure status and JSON preservation.
|
|
751
751
|
|
|
752
752
|
Use lifecycle validation when testing `/reload`, exact-session relaunch, `/resume`, managed-session continuity, or persisted artifact behavior. Branch-backed state and `session_tree` cleanup ownership are covered by focused extension harness tests. Maintainers must run the lifecycle harness before every publish; see [Pre-release checks](docs/RELEASE.md#pre-release-checks).
|
|
753
753
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
4
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { Text } from "@earendil-works/pi-tui";
|
|
7
7
|
import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "./lib/batch-lifecycle.js";
|
|
@@ -13,17 +13,17 @@ import { buildExecutionPlan, canUseHeadlessCompatibilityUserAgent, createEphemer
|
|
|
13
13
|
import { deleteIdentityKeysInNamespace, extractExplicitNamespace, extractExplicitSessionName, getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace, isUpstreamEnvFlagEnabled, resolveAgentBrowserNamespace, scanUpstreamGlobalFlagOccurrences } from "./lib/argv-grammar.js";
|
|
14
14
|
import { parseArgvDescriptor } from "./lib/argv-descriptor.js";
|
|
15
15
|
import { needsManagedSession } from "./lib/command-policy.js";
|
|
16
|
-
import { ManagedSessionRestoreState
|
|
16
|
+
import { ManagedSessionRestoreState } from "./lib/managed-session-restore.js";
|
|
17
17
|
import { isRecord } from "./lib/parsing.js";
|
|
18
18
|
import { getAgentBrowserProcessTimeoutMs, resolveAgentBrowserSocketDir, runAgentBrowserProcess } from "./lib/process.js";
|
|
19
19
|
import { getCommandAwareProcessTimeoutMs } from "./lib/orchestration/browser-run/prepare/wait-timeouts.js";
|
|
20
20
|
import { getAgentBrowserProcessEnvironment, withAgentBrowserProcessEnvironment, withIsolatedAgentBrowserEnvironment } from "./lib/process-environment.js";
|
|
21
21
|
import { withNativeSessionDefaults } from "./lib/orchestration/native-session-defaults.js";
|
|
22
|
-
import { getBrowserCwdError, resolveExecutionCwd
|
|
22
|
+
import { getBrowserCwdError, resolveExecutionCwd } from "./lib/execution-cwd.js";
|
|
23
23
|
import { resolveOperationPaths } from "./lib/orchestration/operation-paths.js";
|
|
24
24
|
import { closeManagedSession, inspectManagedSessionDaemon } from "./lib/orchestration/browser-run/managed-session-daemon-policy.js";
|
|
25
25
|
import { MINIMUM_AGENT_BROWSER_VERSION, SUPPORTED_AGENT_BROWSER_VERSION_LABEL, TARGET_AGENT_BROWSER_VERSION, getAgentBrowserVersionValidationError, parseAgentBrowserVersionOutput, } from "./lib/upstream-version.js";
|
|
26
|
-
import { buildPromptPolicy,
|
|
26
|
+
import { buildPromptPolicy, getLatestUserMessage, getMessageText } from "./lib/prompt-policy.js";
|
|
27
27
|
import { isCloseAllCommand, isCloseCommand } from "./lib/command-taxonomy.js";
|
|
28
28
|
import { hasLaunchScopedFlagToken } from "./lib/launch-scoped-flags.js";
|
|
29
29
|
import { cleanupSecureTempArtifacts } from "./lib/temp.js";
|
|
@@ -44,7 +44,7 @@ import { appendRecordingReservationTransition, applyRecordingArtifactsToReservat
|
|
|
44
44
|
import { createAgentBrowserWebSearchTool } from "./lib/web-search.js";
|
|
45
45
|
import { scopeReadConfirmationArgs } from "./lib/read-confirmation.js";
|
|
46
46
|
import { isDirectAgentBrowserBashAllowed, isHarmlessAgentBrowserInspectionCommand, looksLikeDirectAgentBrowserBash, } from "./lib/bash-guard.js";
|
|
47
|
-
import { AgentBrowserResultComponent,
|
|
47
|
+
import { AgentBrowserResultComponent, formatAgentBrowserRenderCall, } from "./lib/pi-tool-rendering.js";
|
|
48
48
|
function browserExecutionFailure(error, signal, fallbackCategory = "upstream-error") {
|
|
49
49
|
const timeout = error instanceof Error && error.name === "TimeoutError";
|
|
50
50
|
const summary = redactSensitiveText(error instanceof Error ? error.message : String(error));
|
|
@@ -230,84 +230,6 @@ function getRecognizedCompatibilityWorkaround(value) {
|
|
|
230
230
|
? { id: workaround.id, reason: workaround.reason }
|
|
231
231
|
: undefined;
|
|
232
232
|
}
|
|
233
|
-
function restoreManagedSessionCompatibilityWorkaroundFromBranch(branch, sessionName, namespace) {
|
|
234
|
-
let restored;
|
|
235
|
-
const targetKey = getSessionContextKey(sessionName, namespace);
|
|
236
|
-
for (const entry of branch) {
|
|
237
|
-
const message = getBrowserResultMessage(entry);
|
|
238
|
-
if (!message)
|
|
239
|
-
continue;
|
|
240
|
-
const details = isRecord(message.details) ? message.details : undefined;
|
|
241
|
-
if (!details)
|
|
242
|
-
continue;
|
|
243
|
-
if (getSessionContextKey(typeof details.sessionName === "string" ? details.sessionName : undefined, typeof details.namespace === "string" ? details.namespace : undefined) !== targetKey)
|
|
244
|
-
continue;
|
|
245
|
-
const recognizedWorkaround = getRecognizedCompatibilityWorkaround(details.compatibilityWorkaround);
|
|
246
|
-
const succeeded = getSuccessfulToolResult(details, message);
|
|
247
|
-
const outcome = getManagedSessionOutcome(details);
|
|
248
|
-
const activeAfterFailure = recognizedWorkaround
|
|
249
|
-
&& outcome?.activeAfter === true
|
|
250
|
-
&& typeof outcome.currentSessionName === "string"
|
|
251
|
-
&& getSessionContextKey(outcome.currentSessionName, typeof outcome.currentSessionNamespace === "string" ? outcome.currentSessionNamespace : undefined) === targetKey
|
|
252
|
-
&& (outcome.status === "created" || outcome.status === "replaced" || outcome.status === "unchanged");
|
|
253
|
-
if (!succeeded && !activeAfterFailure)
|
|
254
|
-
continue;
|
|
255
|
-
if (recognizedWorkaround) {
|
|
256
|
-
restored = recognizedWorkaround;
|
|
257
|
-
}
|
|
258
|
-
else if (!canUseHeadlessCompatibilityUserAgent(getToolResultArgs(details))) {
|
|
259
|
-
restored = undefined;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
return restored;
|
|
263
|
-
}
|
|
264
|
-
function restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, sessionName, namespace) {
|
|
265
|
-
let restored = false;
|
|
266
|
-
const targetKey = getSessionContextKey(sessionName, namespace);
|
|
267
|
-
for (const entry of branch) {
|
|
268
|
-
const message = getBrowserResultMessage(entry);
|
|
269
|
-
if (!message)
|
|
270
|
-
continue;
|
|
271
|
-
const details = isRecord(message.details) ? message.details : undefined;
|
|
272
|
-
if (!details)
|
|
273
|
-
continue;
|
|
274
|
-
if (getSessionContextKey(typeof details.sessionName === "string" ? details.sessionName : undefined, typeof details.namespace === "string" ? details.namespace : undefined) !== targetKey)
|
|
275
|
-
continue;
|
|
276
|
-
const outcome = getManagedSessionOutcome(details);
|
|
277
|
-
const activeAfterFailure = outcome?.activeAfter === true
|
|
278
|
-
&& typeof outcome.currentSessionName === "string"
|
|
279
|
-
&& getSessionContextKey(outcome.currentSessionName, typeof outcome.currentSessionNamespace === "string" ? outcome.currentSessionNamespace : undefined) === targetKey;
|
|
280
|
-
if ((getSuccessfulToolResult(details, message) || activeAfterFailure) && typeof details.managedSessionHeadedAutosaveDisabled === "boolean") {
|
|
281
|
-
restored = details.managedSessionHeadedAutosaveDisabled;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
return restored;
|
|
285
|
-
}
|
|
286
|
-
function restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, sessionName, namespace) {
|
|
287
|
-
let restored;
|
|
288
|
-
const targetKey = getSessionContextKey(sessionName, namespace);
|
|
289
|
-
for (const entry of branch) {
|
|
290
|
-
const message = getBrowserResultMessage(entry);
|
|
291
|
-
if (!message)
|
|
292
|
-
continue;
|
|
293
|
-
const details = isRecord(message.details) ? message.details : undefined;
|
|
294
|
-
if (!details)
|
|
295
|
-
continue;
|
|
296
|
-
if (getSessionContextKey(typeof details.sessionName === "string" ? details.sessionName : undefined, typeof details.namespace === "string" ? details.namespace : undefined) !== targetKey)
|
|
297
|
-
continue;
|
|
298
|
-
const outcome = getManagedSessionOutcome(details);
|
|
299
|
-
const activeAfterFailure = outcome?.activeAfter === true
|
|
300
|
-
&& typeof outcome.currentSessionName === "string"
|
|
301
|
-
&& getSessionContextKey(outcome.currentSessionName, typeof outcome.currentSessionNamespace === "string" ? outcome.currentSessionNamespace : undefined) === targetKey;
|
|
302
|
-
if (!getSuccessfulToolResult(details, message) && !activeAfterFailure)
|
|
303
|
-
continue;
|
|
304
|
-
if (typeof details.managedSessionHeadedAutosaveInterval === "string")
|
|
305
|
-
restored = details.managedSessionHeadedAutosaveInterval;
|
|
306
|
-
else if (details.managedSessionHeadedAutosaveDisabled === true)
|
|
307
|
-
restored = "0";
|
|
308
|
-
}
|
|
309
|
-
return restored;
|
|
310
|
-
}
|
|
311
233
|
function getToolResultArgs(details) {
|
|
312
234
|
if (Array.isArray(details.args) && details.args.every((arg) => typeof arg === "string"))
|
|
313
235
|
return details.args;
|
|
@@ -385,18 +307,6 @@ function trackOwnedManagedSession(sessions, sessionName, cwd, options = {}) {
|
|
|
385
307
|
const headedManagedAutosaveInterval = options.headedManagedAutosaveInterval ?? existing?.headedManagedAutosaveInterval;
|
|
386
308
|
sessions.set(key, { branchOwned, compatibilityWorkaround, cwd, headedManagedAutosaveDisabled, headedManagedAutosaveInterval, namespace: options.namespace, sessionName, socketDir: options.socketDir ?? existing?.socketDir });
|
|
387
309
|
}
|
|
388
|
-
function restoreManagedSessionSocketDir(branch, sessionName, namespace) {
|
|
389
|
-
const key = getAgentBrowserSessionIdentityKey(sessionName, namespace);
|
|
390
|
-
for (let index = branch.length - 1; index >= 0; index -= 1) {
|
|
391
|
-
const details = getBrowserResultMessage(branch[index])?.details;
|
|
392
|
-
if (!isRecord(details) || typeof details.sessionName !== "string")
|
|
393
|
-
continue;
|
|
394
|
-
if (getAgentBrowserSessionIdentityKey(details.sessionName, typeof details.namespace === "string" ? details.namespace : undefined) === key
|
|
395
|
-
&& typeof details.managedSessionSocketDir === "string")
|
|
396
|
-
return details.managedSessionSocketDir;
|
|
397
|
-
}
|
|
398
|
-
return undefined;
|
|
399
|
-
}
|
|
400
310
|
function untrackOwnedManagedSession(sessions, sessionName, namespace) {
|
|
401
311
|
if (!sessionName)
|
|
402
312
|
return;
|
|
@@ -590,6 +500,7 @@ function collectBranchManagedResourceEvents(branch) {
|
|
|
590
500
|
managedSessionActiveIdentities: new Map(),
|
|
591
501
|
managedSessionActiveRanks: new Map(),
|
|
592
502
|
managedSessionCloseRanks: new Map(),
|
|
503
|
+
managedSessionLaunchState: new Map(),
|
|
593
504
|
};
|
|
594
505
|
let eventRank = 0;
|
|
595
506
|
for (const entry of branch) {
|
|
@@ -611,6 +522,32 @@ function collectBranchManagedResourceEvents(branch) {
|
|
|
611
522
|
const batchCloseLifecycle = getSuccessfulBatchCloseLifecycle(details.batchSteps);
|
|
612
523
|
const closeAllApplied = detailsReportCloseAllApplied(details, succeeded);
|
|
613
524
|
const outcome = getManagedSessionOutcome(details);
|
|
525
|
+
if (sessionName) {
|
|
526
|
+
const key = getSessionContextKey(sessionName, namespace) ?? sessionName;
|
|
527
|
+
const launch = events.managedSessionLaunchState.get(key) ?? { compatibilityWorkaround: undefined, headedManagedAutosaveDisabled: false };
|
|
528
|
+
if (typeof details.managedSessionCwd === "string" && isAbsolute(details.managedSessionCwd))
|
|
529
|
+
launch.cwd = details.managedSessionCwd;
|
|
530
|
+
if (typeof details.managedSessionSocketDir === "string")
|
|
531
|
+
launch.socketDir = details.managedSessionSocketDir;
|
|
532
|
+
const retainedAfterFailure = outcome?.activeAfter === true && typeof outcome.currentSessionName === "string"
|
|
533
|
+
&& getSessionContextKey(outcome.currentSessionName, typeof outcome.currentSessionNamespace === "string" ? outcome.currentSessionNamespace : undefined) === key;
|
|
534
|
+
const workaround = getRecognizedCompatibilityWorkaround(details.compatibilityWorkaround);
|
|
535
|
+
if (succeeded || workaround && retainedAfterFailure && ["created", "replaced", "unchanged"].includes(String(outcome?.status))) {
|
|
536
|
+
if (workaround)
|
|
537
|
+
launch.compatibilityWorkaround = workaround;
|
|
538
|
+
else if (!canUseHeadlessCompatibilityUserAgent(getToolResultArgs(details)))
|
|
539
|
+
launch.compatibilityWorkaround = undefined;
|
|
540
|
+
}
|
|
541
|
+
if (succeeded || retainedAfterFailure) {
|
|
542
|
+
if (typeof details.managedSessionHeadedAutosaveDisabled === "boolean")
|
|
543
|
+
launch.headedManagedAutosaveDisabled = details.managedSessionHeadedAutosaveDisabled;
|
|
544
|
+
if (typeof details.managedSessionHeadedAutosaveInterval === "string")
|
|
545
|
+
launch.headedManagedAutosaveInterval = details.managedSessionHeadedAutosaveInterval;
|
|
546
|
+
else if (details.managedSessionHeadedAutosaveDisabled === true)
|
|
547
|
+
launch.headedManagedAutosaveInterval = "0";
|
|
548
|
+
}
|
|
549
|
+
events.managedSessionLaunchState.set(key, launch);
|
|
550
|
+
}
|
|
614
551
|
const outcomeSucceeded = outcome?.succeeded === true;
|
|
615
552
|
const outcomeStatus = typeof outcome?.status === "string" ? outcome.status : undefined;
|
|
616
553
|
const outcomeCurrentSessionName = typeof outcome?.currentSessionName === "string" ? outcome.currentSessionName : undefined;
|
|
@@ -858,7 +795,7 @@ function hasArgvFlag(argv, longFlag, shortFlag) {
|
|
|
858
795
|
function shouldIncludeProjectConfig(ctx, argv = process.argv) {
|
|
859
796
|
if (hasArgvFlag(argv, "--no-approve", "-na"))
|
|
860
797
|
return false;
|
|
861
|
-
return ctx
|
|
798
|
+
return ctx.isProjectTrusted();
|
|
862
799
|
}
|
|
863
800
|
export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
864
801
|
const ephemeralSessionSeed = createEphemeralSessionSeed();
|
|
@@ -868,6 +805,21 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
868
805
|
});
|
|
869
806
|
const implicitSessionIdleTimeoutMs = String(getImplicitSessionIdleTimeoutMs());
|
|
870
807
|
const implicitSessionCloseTimeoutMs = getImplicitSessionCloseTimeoutMs();
|
|
808
|
+
let promptState;
|
|
809
|
+
const restorePromptState = (ctx) => {
|
|
810
|
+
return promptState = { sessionId: ctx.sessionManager.getSessionId(), message: getLatestUserMessage(ctx.sessionManager) };
|
|
811
|
+
};
|
|
812
|
+
const getPromptPolicy = (ctx) => {
|
|
813
|
+
const state = promptState?.sessionId === ctx.sessionManager.getSessionId() ? promptState : restorePromptState(ctx);
|
|
814
|
+
// Native message_end precedes later transforms/persistence; retain the message
|
|
815
|
+
// reference so replacement user text cannot leave policy on the earlier text.
|
|
816
|
+
const text = getMessageText(state.message?.content);
|
|
817
|
+
if (!state.policy || state.text !== text) {
|
|
818
|
+
state.text = text;
|
|
819
|
+
state.policy = buildPromptPolicy(text);
|
|
820
|
+
}
|
|
821
|
+
return state.policy;
|
|
822
|
+
};
|
|
871
823
|
let webSearchToolRegistered = false;
|
|
872
824
|
let managedSessionActive = false;
|
|
873
825
|
let managedSessionBaseName = createImplicitSessionName(undefined, process.cwd(), ephemeralSessionSeed);
|
|
@@ -1121,11 +1073,11 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1121
1073
|
clearSessionScopedBrowserState(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE);
|
|
1122
1074
|
return undefined;
|
|
1123
1075
|
};
|
|
1124
|
-
const recoverScriptSessionLeasesWithinQueue = async (ctx) => {
|
|
1076
|
+
const recoverScriptSessionLeasesWithinQueue = async (ctx, branch) => {
|
|
1125
1077
|
const pendingSessionNames = new Set([...ownedManagedSessions.values()]
|
|
1126
1078
|
.map((session) => session.sessionName)
|
|
1127
1079
|
.filter(isAgentBrowserScriptSessionName));
|
|
1128
|
-
for (const lease of getScriptSessionLeasesFromBranch(
|
|
1080
|
+
for (const lease of getScriptSessionLeasesFromBranch(branch, ctx.sessionManager.getSessionId()).values()) {
|
|
1129
1081
|
if (lease.cleanup !== "closed")
|
|
1130
1082
|
pendingSessionNames.add(lease.sessionName);
|
|
1131
1083
|
}
|
|
@@ -1138,6 +1090,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1138
1090
|
const restoreBranchBackedState = async (ctx, options) => {
|
|
1139
1091
|
branchRestoreGeneration += 1;
|
|
1140
1092
|
branchStateGeneration += 1;
|
|
1093
|
+
restorePromptState(ctx);
|
|
1141
1094
|
const previousManagedSessionActive = managedSessionActive;
|
|
1142
1095
|
const previousManagedSessionName = managedSessionName;
|
|
1143
1096
|
const previousFreshSessionOrdinal = freshSessionOrdinal;
|
|
@@ -1179,15 +1132,11 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1179
1132
|
: createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, nextFreshSessionOrdinal)
|
|
1180
1133
|
: restoredState.sessionName;
|
|
1181
1134
|
managedSessionNamespace = shouldReservePostCloseSession ? undefined : restoredState.namespace;
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
managedSessionHeadedAutosaveInterval = managedSessionActive
|
|
1188
|
-
? restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, managedSessionName, managedSessionNamespace)
|
|
1189
|
-
: undefined;
|
|
1190
|
-
managedSessionCwd = restoreManagedSessionCwd(branch, managedSessionName, managedSessionNamespace, ctx.cwd);
|
|
1135
|
+
const currentLaunch = branchResourceEvents.managedSessionLaunchState.get(getSessionContextKey(managedSessionName, managedSessionNamespace) ?? managedSessionName);
|
|
1136
|
+
managedSessionCompatibilityWorkaround = managedSessionActive ? currentLaunch?.compatibilityWorkaround : undefined;
|
|
1137
|
+
managedSessionHeadedAutosaveDisabled = managedSessionActive && currentLaunch?.headedManagedAutosaveDisabled === true;
|
|
1138
|
+
managedSessionHeadedAutosaveInterval = managedSessionActive ? currentLaunch?.headedManagedAutosaveInterval : undefined;
|
|
1139
|
+
managedSessionCwd = currentLaunch?.cwd ?? ctx.cwd;
|
|
1191
1140
|
freshSessionOrdinal = nextFreshSessionOrdinal;
|
|
1192
1141
|
sessionPageState = SessionPageState.fromBranch(branch);
|
|
1193
1142
|
traceOwners = new Map();
|
|
@@ -1238,13 +1187,14 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1238
1187
|
continue;
|
|
1239
1188
|
if (!isRestorableManagedSessionName(identity.sessionName, managedSessionBaseName))
|
|
1240
1189
|
continue;
|
|
1241
|
-
|
|
1190
|
+
const launch = branchResourceEvents.managedSessionLaunchState.get(sessionKey);
|
|
1191
|
+
trackOwnedManagedSession(ownedManagedSessions, identity.sessionName, launch?.cwd ?? ctx.cwd, {
|
|
1242
1192
|
branchOwned: true,
|
|
1243
|
-
compatibilityWorkaround:
|
|
1244
|
-
headedManagedAutosaveDisabled:
|
|
1245
|
-
headedManagedAutosaveInterval:
|
|
1193
|
+
compatibilityWorkaround: launch?.compatibilityWorkaround,
|
|
1194
|
+
headedManagedAutosaveDisabled: launch?.headedManagedAutosaveDisabled ?? false,
|
|
1195
|
+
headedManagedAutosaveInterval: launch?.headedManagedAutosaveInterval,
|
|
1246
1196
|
namespace: identity.namespace,
|
|
1247
|
-
socketDir:
|
|
1197
|
+
socketDir: launch?.socketDir,
|
|
1248
1198
|
});
|
|
1249
1199
|
}
|
|
1250
1200
|
if (restoredState.active) {
|
|
@@ -1254,7 +1204,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1254
1204
|
headedManagedAutosaveDisabled: managedSessionHeadedAutosaveDisabled,
|
|
1255
1205
|
headedManagedAutosaveInterval: managedSessionHeadedAutosaveInterval,
|
|
1256
1206
|
namespace: restoredState.namespace,
|
|
1257
|
-
socketDir:
|
|
1207
|
+
socketDir: currentLaunch?.socketDir,
|
|
1258
1208
|
});
|
|
1259
1209
|
}
|
|
1260
1210
|
const pendingOwnedBegins = new Map();
|
|
@@ -1290,11 +1240,12 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1290
1240
|
const closeRank = branchResourceEvents.managedSessionCloseRanks.get(sessionKey);
|
|
1291
1241
|
if (activeRank === undefined || (closeRank !== undefined && closeRank >= activeRank))
|
|
1292
1242
|
continue;
|
|
1293
|
-
|
|
1243
|
+
const launch = branchResourceEvents.managedSessionLaunchState.get(sessionKey);
|
|
1244
|
+
trackOwnedManagedSession(ownedManagedSessions, record.sessionName, launch?.cwd ?? ctx.cwd, {
|
|
1294
1245
|
branchOwned: true,
|
|
1295
|
-
headedManagedAutosaveDisabled:
|
|
1296
|
-
headedManagedAutosaveInterval:
|
|
1297
|
-
socketDir:
|
|
1246
|
+
headedManagedAutosaveDisabled: launch?.headedManagedAutosaveDisabled ?? false,
|
|
1247
|
+
headedManagedAutosaveInterval: launch?.headedManagedAutosaveInterval,
|
|
1248
|
+
socketDir: launch?.socketDir,
|
|
1298
1249
|
});
|
|
1299
1250
|
}
|
|
1300
1251
|
mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, new Map([...electronLaunchRecords].filter(([, record]) => record.ownerSessionId === browserOwnerSessionId)), {
|
|
@@ -1323,6 +1274,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1323
1274
|
attachedSessionKeys.add(sessionKey);
|
|
1324
1275
|
}
|
|
1325
1276
|
}
|
|
1277
|
+
return branch;
|
|
1326
1278
|
};
|
|
1327
1279
|
const registerWebSearchToolIfAvailable = (configState) => {
|
|
1328
1280
|
if (webSearchToolRegistered || !canRegisterWebSearchTool(configState))
|
|
@@ -1339,15 +1291,19 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1339
1291
|
});
|
|
1340
1292
|
webSearchToolRegistered = true;
|
|
1341
1293
|
};
|
|
1294
|
+
pi.on("message_end", (event, ctx) => {
|
|
1295
|
+
if (event.message.role === "user")
|
|
1296
|
+
promptState = { sessionId: ctx.sessionManager.getSessionId(), message: event.message };
|
|
1297
|
+
});
|
|
1342
1298
|
pi.on("session_start", async (_event, ctx) => {
|
|
1343
|
-
await restoreBranchBackedState(ctx, { resetRuntimeOwnership: true });
|
|
1344
|
-
electronChildProcesses = new Map();
|
|
1345
|
-
registerWebSearchToolIfAvailable(loadAgentBrowserConfigSync({
|
|
1346
|
-
cwd: ctx.cwd,
|
|
1347
|
-
includeProjectConfig: shouldIncludeProjectConfig(ctx),
|
|
1348
|
-
}));
|
|
1349
1299
|
await managedSessionExecutionQueue.run(() => artifactExecutionQueue.run(async () => {
|
|
1350
|
-
await
|
|
1300
|
+
const branch = await restoreBranchBackedState(ctx, { resetRuntimeOwnership: true });
|
|
1301
|
+
electronChildProcesses = new Map();
|
|
1302
|
+
registerWebSearchToolIfAvailable(loadAgentBrowserConfigSync({
|
|
1303
|
+
cwd: ctx.cwd,
|
|
1304
|
+
includeProjectConfig: shouldIncludeProjectConfig(ctx),
|
|
1305
|
+
}));
|
|
1306
|
+
await recoverScriptSessionLeasesWithinQueue(ctx, branch);
|
|
1351
1307
|
flushRecordingReservations();
|
|
1352
1308
|
notifyRecordingPersistence(ctx);
|
|
1353
1309
|
}));
|
|
@@ -1358,87 +1314,12 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1358
1314
|
controller.abort();
|
|
1359
1315
|
await Promise.allSettled([...activeScriptExecutions]);
|
|
1360
1316
|
await managedSessionExecutionQueue.run(() => artifactExecutionQueue.run(async () => {
|
|
1361
|
-
await restoreBranchBackedState(ctx, { resetRuntimeOwnership: false });
|
|
1362
|
-
await recoverScriptSessionLeasesWithinQueue(ctx);
|
|
1317
|
+
const branch = await restoreBranchBackedState(ctx, { resetRuntimeOwnership: false });
|
|
1318
|
+
await recoverScriptSessionLeasesWithinQueue(ctx, branch);
|
|
1363
1319
|
flushRecordingReservations();
|
|
1364
1320
|
notifyRecordingPersistence(ctx);
|
|
1365
1321
|
}));
|
|
1366
1322
|
});
|
|
1367
|
-
// Fork-only checkpoint event; official Pi validation types do not declare it.
|
|
1368
|
-
// Pi owns awaited tools/events (including their execution queues). Detached code
|
|
1369
|
-
// children and retained browser/recording resources still need extension-side checks.
|
|
1370
|
-
const checkpointPi = pi;
|
|
1371
|
-
checkpointPi.on("session_checkpoint", async (event, ctx) => {
|
|
1372
|
-
const blocked = (reason) => ({ sleepReady: false, reason });
|
|
1373
|
-
if (browserReplayError)
|
|
1374
|
-
return blocked(browserReplayError);
|
|
1375
|
-
if (activeScriptControllers.size || activeScriptExecutions.size)
|
|
1376
|
-
return blocked("Browser code execution is pending");
|
|
1377
|
-
flushRecordingReservations();
|
|
1378
|
-
if (recordingReservationsDirty)
|
|
1379
|
-
return blocked("Browser recording journal persistence is dirty");
|
|
1380
|
-
if (activeRecordingReservations.size)
|
|
1381
|
-
return blocked("Browser recording is pending; finish it explicitly before sleep");
|
|
1382
|
-
const entries = await readBrowserEntries(ctx.sessionManager, true);
|
|
1383
|
-
if ([...getScriptSessionLeasesFromBranch(entries).values()].some(lease => lease.cleanup !== "closed")
|
|
1384
|
-
|| [...ownedManagedSessions.values()].some(owner => isAgentBrowserScriptSessionName(owner.sessionName))) {
|
|
1385
|
-
return blocked("Browser script cleanup lease is unresolved");
|
|
1386
|
-
}
|
|
1387
|
-
if (getActiveElectronRecords(ownedElectronLaunchRecords).length || getActiveElectronRecords(electronLaunchRecords).length
|
|
1388
|
-
|| [...electronChildProcesses.values()].some(child => child.exitCode === null && child.signalCode === null)) {
|
|
1389
|
-
return blocked("Electron launch is still active");
|
|
1390
|
-
}
|
|
1391
|
-
if (attachedSessionKeys.size || restoreAttachedSessionKeysFromBranch(entries).size)
|
|
1392
|
-
return blocked("Attached browser state is caller-owned and not checkpointed");
|
|
1393
|
-
if (traceOwners.size || [...networkRoutesBySession.values()].some(routes => routes.length))
|
|
1394
|
-
return blocked("Browser trace or network routes are still active");
|
|
1395
|
-
// Include off-branch and caller-owned/root identities, without acquiring
|
|
1396
|
-
// cleanup ownership. Transcript page/ref details do not serialize a browser.
|
|
1397
|
-
const historicalResources = collectBranchManagedResourceEvents(entries);
|
|
1398
|
-
const sessions = new Map(ownedManagedSessions);
|
|
1399
|
-
if (managedSessionActive)
|
|
1400
|
-
trackOwnedManagedSession(sessions, managedSessionName, managedSessionCwd, { namespace: managedSessionNamespace });
|
|
1401
|
-
for (const entry of entries) {
|
|
1402
|
-
const message = getBrowserResultMessage(entry);
|
|
1403
|
-
if (!message)
|
|
1404
|
-
continue;
|
|
1405
|
-
const details = isRecord(message.details) ? message.details : undefined;
|
|
1406
|
-
// Helpers can launch before a main-process failure. Conversely, native
|
|
1407
|
-
// launch flags on sessionless reads can open the unnamed default browser.
|
|
1408
|
-
const sessionName = typeof details?.sessionName === "string" ? details.sessionName
|
|
1409
|
-
: details?.agentBrowserStarted === true ? "default" : undefined;
|
|
1410
|
-
if (!sessionName)
|
|
1411
|
-
continue;
|
|
1412
|
-
const namespace = typeof details?.namespace === "string" ? details.namespace : undefined;
|
|
1413
|
-
const key = getSessionContextKey(sessionName, namespace) ?? sessionName;
|
|
1414
|
-
if (!sessions.has(key))
|
|
1415
|
-
trackOwnedManagedSession(sessions, sessionName, ctx.cwd, { namespace });
|
|
1416
|
-
}
|
|
1417
|
-
for (const [key, owner] of sessions) {
|
|
1418
|
-
event.signal.throwIfAborted();
|
|
1419
|
-
// An abnormal restart can leave a wrapper-created browser only off-branch.
|
|
1420
|
-
// Reuse native ownership events for inspection, never cleanup ownership.
|
|
1421
|
-
// A later close can refer to the same identity in a different socket root.
|
|
1422
|
-
const historicalIdentity = historicalResources.managedSessionActiveIdentities.get(key);
|
|
1423
|
-
const historicallyOwned = historicalIdentity && isRestorableManagedSessionName(historicalIdentity.sessionName, managedSessionBaseName);
|
|
1424
|
-
const context = resolveOwnedManagedSessionContext({
|
|
1425
|
-
...owner,
|
|
1426
|
-
currentManagedSessionName: managedSessionActive ? managedSessionName : undefined,
|
|
1427
|
-
currentManagedSessionNamespace: managedSessionNamespace,
|
|
1428
|
-
recordedOwnedSession: ownedManagedSessions.get(key) ?? (historicallyOwned ? { ...historicalIdentity, cwd: ctx.cwd } : undefined),
|
|
1429
|
-
restoreState: managedSessionRestoreState,
|
|
1430
|
-
});
|
|
1431
|
-
// Owned routing must not mask an explicit caller's same-name ambient daemon.
|
|
1432
|
-
for (const inspectionContext of context ? [{ ...context, reuseOnly: true }, undefined] : [undefined]) {
|
|
1433
|
-
const daemon = await withAgentBrowserProcessEnvironment(inspectionContext && owner.socketDir ? { PI_AGENT_BROWSER_SOCKET_DIR: owner.socketDir } : {}, () => withOwnedManagedSessionContext(inspectionContext, () => inspectManagedSessionDaemon({ ...owner, signal: event.signal, timeoutMs: 2_000 })));
|
|
1434
|
-
if (daemon.status !== "inactive")
|
|
1435
|
-
return blocked("Browser daemon is live or unverified; finish browser work explicitly before sleep");
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
// No detached writer remains to pause/resume. Native ingress stays held;
|
|
1439
|
-
// checkpoint never closes a browser or changes ordinary shutdown ownership.
|
|
1440
|
-
return { sleepReady: true };
|
|
1441
|
-
});
|
|
1442
1323
|
pi.on("session_shutdown", async (event, ctx) => {
|
|
1443
1324
|
for (const controller of activeScriptControllers)
|
|
1444
1325
|
controller.abort();
|
|
@@ -1548,11 +1429,11 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1548
1429
|
event.systemPromptOptions.sections.agent_browser = fullInstructions(ctx);
|
|
1549
1430
|
});
|
|
1550
1431
|
pi.on("tool_call", async (event, ctx) => {
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1432
|
+
if (!isBashToolCallEvent(event) || !looksLikeDirectAgentBrowserBash(event.input.command)
|
|
1433
|
+
|| isHarmlessAgentBrowserInspectionCommand(event.input.command))
|
|
1434
|
+
return;
|
|
1435
|
+
const promptPolicy = getPromptPolicy(ctx);
|
|
1436
|
+
if (!promptPolicy.allowLegacyAgentBrowserBash &&
|
|
1556
1437
|
!(await isDirectAgentBrowserBashAllowed(ctx.cwd))) {
|
|
1557
1438
|
return {
|
|
1558
1439
|
block: true,
|
|
@@ -1560,7 +1441,6 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1560
1441
|
};
|
|
1561
1442
|
}
|
|
1562
1443
|
});
|
|
1563
|
-
pi.on("tool_result", async (event) => buildAgentBrowserToolResultPatch(event));
|
|
1564
1444
|
let emptyBrowserBranch;
|
|
1565
1445
|
const captureCurrentBrowserBranch = (ctx) => {
|
|
1566
1446
|
const generation = branchRestoreGeneration;
|
|
@@ -1583,7 +1463,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1583
1463
|
const component = context.lastComponent instanceof AgentBrowserResultComponent
|
|
1584
1464
|
? context.lastComponent
|
|
1585
1465
|
: new AgentBrowserResultComponent();
|
|
1586
|
-
component.
|
|
1466
|
+
component.setResult(result, options, theme, context.isError);
|
|
1587
1467
|
return component;
|
|
1588
1468
|
},
|
|
1589
1469
|
async execute(toolCallId, params, signal, onUpdate, ctx, nativeToolCallId = toolCallId, capturedCwd, modelVisible = true, commandIndex, selectedBranch) {
|
|
@@ -1595,7 +1475,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1595
1475
|
catch (error) {
|
|
1596
1476
|
return buildValidationFailureResult({ kind: "invalid", status: "invalid", redactedArgs: [], toolArgs: [], validationError: error instanceof Error ? error.message : String(error) });
|
|
1597
1477
|
}
|
|
1598
|
-
const promptPolicy =
|
|
1478
|
+
const promptPolicy = getPromptPolicy(ctx);
|
|
1599
1479
|
const outputPath = isRecord(params) && typeof params.outputPath === "string" ? params.outputPath : undefined;
|
|
1600
1480
|
const admittedInput = resolveAgentBrowserInput({
|
|
1601
1481
|
getBatchPreflightValidationError: (args, stdin) => getArtifactPreflightValidationError({ args, cwd: operationCwd, outputPath, stdin }),
|
|
@@ -2266,10 +2146,10 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
2266
2146
|
}) };
|
|
2267
2147
|
const rendered = await renderAgentBrowserObservation(renderOptions);
|
|
2268
2148
|
if (!priorManifest && !rendered.artifactManifest)
|
|
2269
|
-
return { ...result, content: rendered.content, details: { ...details, browserEventVersion: 1 } };
|
|
2149
|
+
return { ...result, content: rendered.content, structuredContent: rendered.structuredContent, details: { ...details, browserEventVersion: 1 } };
|
|
2270
2150
|
return artifactExecutionQueue.run(async () => {
|
|
2271
2151
|
if (!branch.isCurrent())
|
|
2272
|
-
return { ...result, content: rendered.content, details: { ...details, browserEventVersion: 1, artifactManifest: rendered.artifactManifest } };
|
|
2152
|
+
return { ...result, content: rendered.content, structuredContent: rendered.structuredContent, details: { ...details, browserEventVersion: 1, artifactManifest: rendered.artifactManifest } };
|
|
2273
2153
|
const before = artifactManifest;
|
|
2274
2154
|
const merged = mergeBrowserRunArtifactManifest(artifactManifest, priorManifest, rendered.artifactManifest);
|
|
2275
2155
|
const changes = artifactChanges(before, merged);
|
|
@@ -2277,10 +2157,10 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
2277
2157
|
const record = { event: { version: 1, phase: "state", operationId: randomUUID(), toolCallId: "observation", commandIndex: 0, isError: result.isError === true, state: {}, artifacts: changes } };
|
|
2278
2158
|
if (!await appendBrowserRecord(ctx.sessionManager, () => appendBrowserTransition(pi, record), record, branch)
|
|
2279
2159
|
|| !branch.isCurrent())
|
|
2280
|
-
return { ...result, content: rendered.content, details: { ...details, browserEventVersion: 1, artifactManifest: rendered.artifactManifest } };
|
|
2160
|
+
return { ...result, content: rendered.content, structuredContent: rendered.structuredContent, details: { ...details, browserEventVersion: 1, artifactManifest: rendered.artifactManifest } };
|
|
2281
2161
|
}
|
|
2282
2162
|
artifactManifest = mergeBrowserRunArtifactManifest(artifactManifest, priorManifest, rendered.artifactManifest);
|
|
2283
|
-
return { ...result, content: rendered.content, details: { ...details, browserEventVersion: 1, artifactManifest: invocationArtifactManifest(artifactManifest, before, details, priorManifest) } };
|
|
2163
|
+
return { ...result, content: rendered.content, structuredContent: rendered.structuredContent, details: { ...details, browserEventVersion: 1, artifactManifest: invocationArtifactManifest(artifactManifest, before, details, priorManifest) } };
|
|
2284
2164
|
});
|
|
2285
2165
|
};
|
|
2286
2166
|
registerAgentBrowserToolSurface(pi, {
|