pi-agent-browser-native 0.6.16 → 0.7.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 +18 -1
- package/README.md +59 -165
- package/dist/extensions/agent-browser/index.js +634 -501
- package/dist/extensions/agent-browser/lib/browser-transcript.js +33 -0
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +8 -4
- package/dist/extensions/agent-browser/lib/execution-cwd.js +66 -0
- package/dist/extensions/agent-browser/lib/input-modes/job.js +2 -213
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +1 -1
- package/dist/extensions/agent-browser/lib/input-modes/params.js +146 -142
- package/dist/extensions/agent-browser/lib/input-modes/script.js +64 -92
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +170 -24
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +27 -20
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +14 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +26 -13
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +21 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +4 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +1 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +41 -24
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/recording-recovery.js +3 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/screenshot-observation.js +79 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +3 -4
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +19 -52
- package/dist/extensions/agent-browser/lib/orchestration/native-session-defaults.js +3 -3
- package/dist/extensions/agent-browser/lib/orchestration/operation-paths.js +76 -0
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +3 -3
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +91 -278
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +9 -9
- package/dist/extensions/agent-browser/lib/playbook.js +46 -25
- package/dist/extensions/agent-browser/lib/process-identity.js +22 -6
- package/dist/extensions/agent-browser/lib/process.js +16 -13
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +2 -2
- package/dist/extensions/agent-browser/lib/results/next-actions.js +1 -2
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +25 -4
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +6 -2
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +2 -0
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +39 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +3 -3
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +41 -1
- package/dist/extensions/agent-browser/lib/results/presentation.js +16 -11
- package/dist/extensions/agent-browser/lib/runtime.js +18 -6
- package/dist/extensions/agent-browser/lib/session-page-state.js +3 -4
- package/dist/extensions/agent-browser/lib/tool-surface.js +131 -0
- package/dist/extensions/agent-browser/script-worker.js +4 -1
- package/docs/ARCHITECTURE.md +47 -61
- package/docs/COMMAND_REFERENCE.md +78 -137
- package/docs/ELECTRON.md +51 -49
- package/docs/RELEASE.md +31 -15
- package/docs/REQUIREMENTS.md +18 -14
- package/docs/SUPPORT_MATRIX.md +33 -15
- package/docs/TOOL_CONTRACT.md +218 -401
- package/package.json +4 -4
- package/scripts/doctor.mjs +2 -2
|
@@ -1,149 +1,153 @@
|
|
|
1
1
|
import { JsonSchema } from "../json-schema.js";
|
|
2
2
|
import { StringEnum as localStringEnum } from "../string-enum-schema.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
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
|
+
export const AGENT_BROWSER_ADVANCED_TOOLS = ["action", "qa", "electron", "source", "network"];
|
|
6
|
+
export const AGENT_BROWSER_CODE_MAX_TIMEOUT_MS = 300_000;
|
|
7
|
+
function outputProperties(Type) {
|
|
8
|
+
return {
|
|
9
|
+
outputPath: Type.Optional(Type.String({ description: "Result-data path, separate from browser artifact paths.", minLength: 1 })),
|
|
10
|
+
timeoutMs: Type.Optional(Type.Integer({ description: "Timeout in ms; exceed explicit waits.", minimum: 1 })),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function sessionModeProperty(Type, StringEnum) {
|
|
14
|
+
return Type.Optional(StringEnum(["auto", "fresh"], {
|
|
15
|
+
description: "Native configured sessions win; auto reuses the browser, fresh intentionally launches a separate managed browser.",
|
|
16
|
+
default: DEFAULT_SESSION_MODE,
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
// Keep the always-on schemas small; specialized guidance belongs to each advanced tool.
|
|
7
20
|
export function createAgentBrowserParamsSchema(Type = JsonSchema, StringEnum = localStringEnum) {
|
|
8
|
-
return Type.Object({
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
action: StringEnum(["check", "click", "fill"]),
|
|
37
|
-
}, { not: { required: ["values"] } }),
|
|
38
|
-
],
|
|
39
|
-
description: "Stable locator or direct-selector action. values only with action=select.",
|
|
40
|
-
})),
|
|
41
|
-
qa: Type.Optional(Type.Union([
|
|
42
|
-
Type.Object({
|
|
43
|
-
attached: Type.Literal(true),
|
|
44
|
-
expectedText: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())])),
|
|
45
|
-
expectedSelector: Type.Optional(Type.String()),
|
|
46
|
-
screenshotPath: Type.Optional(Type.String()),
|
|
47
|
-
checkConsole: Type.Optional(Type.Boolean()),
|
|
48
|
-
checkErrors: Type.Optional(Type.Boolean()),
|
|
49
|
-
checkNetwork: Type.Optional(Type.Boolean()),
|
|
50
|
-
loadState: Type.Optional(StringEnum(AGENT_BROWSER_QA_LOAD_STATES)),
|
|
51
|
-
}, { additionalProperties: false }),
|
|
52
|
-
Type.Object({
|
|
53
|
-
url: Type.String(),
|
|
54
|
-
attached: Type.Optional(Type.Literal(false)),
|
|
55
|
-
expectedText: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())])),
|
|
56
|
-
expectedSelector: Type.Optional(Type.String()),
|
|
57
|
-
screenshotPath: Type.Optional(Type.String()),
|
|
58
|
-
checkConsole: Type.Optional(Type.Boolean()),
|
|
59
|
-
checkErrors: Type.Optional(Type.Boolean()),
|
|
60
|
-
checkNetwork: Type.Optional(Type.Boolean()),
|
|
61
|
-
loadState: Type.Optional(StringEnum(AGENT_BROWSER_QA_LOAD_STATES)),
|
|
62
|
-
}, { additionalProperties: false }),
|
|
63
|
-
], { description: "QA a URL or current session (attached=true). Default readiness: domcontentloaded; use networkidle only without long-lived requests. URL diagnostics default on, attached diagnostics off." })),
|
|
64
|
-
sourceLookup: Type.Optional(Type.Object({
|
|
65
|
-
selector: Type.Optional(Type.String({ description: "Visible selector or @ref." })),
|
|
66
|
-
reactFiberId: Type.Optional(Type.String({ description: "React fiber id; requires --enable react-devtools." })),
|
|
67
|
-
componentName: Type.Optional(Type.String({ description: "Component for local source search." })),
|
|
68
|
-
includeDomHints: Type.Optional(Type.Boolean({ description: "Inspect DOM source hints; default true." })),
|
|
69
|
-
maxWorkspaceFiles: Type.Optional(Type.Number({ description: "Source scan cap; default 2000.", minimum: 1, maximum: SOURCE_LOOKUP_MAX_WORKSPACE_FILES })),
|
|
70
|
-
}, { additionalProperties: false, description: "EXPERIMENTAL UI-to-source candidates; not guaranteed mappings." })),
|
|
71
|
-
networkSourceLookup: Type.Optional(Type.Object({
|
|
72
|
-
filter: Type.Optional(Type.String({ description: "Network request filter." })),
|
|
73
|
-
namespace: Type.Optional(Type.String()),
|
|
74
|
-
requestId: Type.Optional(Type.String({ description: "Request id to inspect." })),
|
|
75
|
-
session: Type.Optional(Type.String()),
|
|
76
|
-
url: Type.Optional(Type.String({ description: "Failed URL or fragment." })),
|
|
77
|
-
maxWorkspaceFiles: Type.Optional(Type.Number({ description: "Source scan cap; default 2000.", minimum: 1, maximum: SOURCE_LOOKUP_MAX_WORKSPACE_FILES })),
|
|
78
|
-
}, { additionalProperties: false, description: "EXPERIMENTAL failed-request-to-source candidates; not proof." })),
|
|
79
|
-
electron: Type.Optional(Type.Union([
|
|
80
|
-
Type.Object({
|
|
81
|
-
action: StringEnum(["list"]),
|
|
82
|
-
query: Type.Optional(Type.String({ description: "Case-insensitive app filter.", minLength: 1 })),
|
|
83
|
-
maxResults: Type.Optional(Type.Integer({ description: `Result cap; default ${ELECTRON_DISCOVERY_DEFAULT_MAX_RESULTS}, values over ${ELECTRON_DISCOVERY_MAX_RESULTS} are clamped.`, minimum: 1 })),
|
|
84
|
-
}, { additionalProperties: false }),
|
|
85
|
-
Type.Object({
|
|
86
|
-
action: StringEnum(["launch"]),
|
|
87
|
-
appPath: Type.Optional(Type.String({ description: "macOS .app path.", minLength: 1 })),
|
|
88
|
-
appName: Type.Optional(Type.String({ description: "Name from electron.list.", minLength: 1 })),
|
|
89
|
-
bundleId: Type.Optional(Type.String({ description: "Bundle id from electron.list.", minLength: 1 })),
|
|
90
|
-
executablePath: Type.Optional(Type.String({ description: "Executable path.", minLength: 1 })),
|
|
91
|
-
appArgs: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
92
|
-
handoff: Type.Optional(StringEnum(AGENT_BROWSER_ELECTRON_HANDOFFS)),
|
|
93
|
-
targetType: Type.Optional(StringEnum(AGENT_BROWSER_ELECTRON_TARGET_TYPES)),
|
|
94
|
-
timeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
95
|
-
allow: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
96
|
-
deny: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
97
|
-
}, { additionalProperties: false }),
|
|
98
|
-
Type.Object({
|
|
99
|
-
action: StringEnum(["status", "cleanup"]),
|
|
100
|
-
launchId: Type.String({ description: "Tracked launch id.", minLength: 1 }),
|
|
101
|
-
timeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
102
|
-
}, { additionalProperties: false }),
|
|
103
|
-
Type.Object({
|
|
104
|
-
action: StringEnum(["status", "cleanup"]),
|
|
105
|
-
all: Type.Literal(true),
|
|
106
|
-
timeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
107
|
-
}, { additionalProperties: false }),
|
|
108
|
-
Type.Object({
|
|
109
|
-
action: StringEnum(["status", "cleanup"]),
|
|
110
|
-
timeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
111
|
-
}, { additionalProperties: false }),
|
|
112
|
-
Type.Object({
|
|
113
|
-
action: StringEnum(["probe"]),
|
|
114
|
-
launchId: Type.Optional(Type.String({ description: "Tracked launch id.", minLength: 1 })),
|
|
115
|
-
timeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
116
|
-
}, { additionalProperties: false }),
|
|
117
|
-
], { description: "Electron discovery, isolated-profile launch, status, probe, or cleanup. Launch defaults: handoff=snapshot, targetType=page; deny wins over allow; lifecycle/debug appArgs rejected." })),
|
|
118
|
-
job: Type.Optional(Type.Object({
|
|
119
|
-
failFast: Type.Optional(Type.Boolean({ description: "Stop on first failure; default true." })),
|
|
120
|
-
steps: Type.Array(Type.Object({
|
|
121
|
-
action: StringEnum(AGENT_BROWSER_JOB_STEP_ACTIONS),
|
|
122
|
-
url: Type.Optional(Type.String({ description: "Open URL, or assertUrl exact URL/glob that distinguishes the expected state from the starting page." })),
|
|
123
|
-
loadState: Type.Optional(StringEnum(AGENT_BROWSER_QA_LOAD_STATES, { description: "Readiness wait after open." })),
|
|
124
|
-
selector: Type.Optional(Type.String({ description: "Selector or @ref." })),
|
|
125
|
-
locator: Type.Optional(StringEnum(AGENT_BROWSER_SEMANTIC_LOCATORS, { description: "Locator when selector is omitted." })),
|
|
126
|
-
role: Type.Optional(Type.String({ description: "Role locator." })),
|
|
127
|
-
name: Type.Optional(Type.String({ description: "Accessible name." })),
|
|
128
|
-
text: Type.Optional(Type.String({ description: "Fill/type text; assertText uses only text, not selector/locator fields." })),
|
|
129
|
-
value: Type.Optional(Type.String({ description: "Select option or locator value." })),
|
|
130
|
-
values: Type.Optional(Type.Array(Type.String(), { description: "Select options.", minItems: 1 })),
|
|
131
|
-
path: Type.Optional(Type.String({ description: "Download or screenshot path." })),
|
|
132
|
-
delayMs: Type.Optional(Type.Integer({ description: `Per-character type delay; text is capped at ${AGENT_BROWSER_JOB_TYPE_DELAYED_TEXT_MAX_CHARACTERS} characters.`, minimum: 1 })),
|
|
133
|
-
press: Type.Optional(Type.String({ description: "Key to press after typing." })),
|
|
134
|
-
milliseconds: Type.Optional(Type.Number({ description: "Wait duration in milliseconds." })),
|
|
135
|
-
}, { additionalProperties: false }), { minItems: 1 }),
|
|
136
|
-
}, { additionalProperties: false, description: "Constrained multi-step batch. Clicks can stale later refs; split and re-snapshot before using them." })),
|
|
137
|
-
stdin: Type.Optional(Type.String({ description: "For batch, a JSON array of token arrays, e.g. [[\"get\",\"title\"]]. Raw text only for eval --stdin or auth save --password-stdin; unavailable with structured modes and electron." })),
|
|
138
|
-
outputPath: Type.Optional(Type.String({ description: "Workspace-relative or absolute result-data path; keep it distinct from screenshot, download, recording, and other browser artifact destinations.", minLength: 1 })),
|
|
139
|
-
timeoutMs: Type.Optional(Type.Integer({ description: "Wrapper timeout in ms; exceed explicit waits. electron.list has no configurable timeout; other Electron actions use electron.timeoutMs.", minimum: 1 })),
|
|
140
|
-
sessionMode: Type.Optional(StringEnum(["auto", "fresh"], {
|
|
141
|
-
description: "Native configured sessions win; otherwise auto reuses the managed session and fresh starts a new managed browser for launch-only flags.",
|
|
142
|
-
default: DEFAULT_SESSION_MODE,
|
|
143
|
-
})),
|
|
21
|
+
return Type.Unsafe(Type.Object({
|
|
22
|
+
args: Type.Array(Type.String(), { description: "Native agent-browser argv without the binary or shell syntax. Fixed sequences: batch --bail with JSON-array stdin.", minItems: 1 }),
|
|
23
|
+
stdin: Type.Optional(Type.String({ description: "Native batch JSON, eval --stdin source, or auth save --password-stdin text." })),
|
|
24
|
+
...outputProperties(Type),
|
|
25
|
+
sessionMode: sessionModeProperty(Type, StringEnum),
|
|
26
|
+
}, { additionalProperties: false }));
|
|
27
|
+
}
|
|
28
|
+
export function createAgentBrowserCodeParamsSchema(Type = JsonSchema) {
|
|
29
|
+
return Type.Unsafe(Type.Object({
|
|
30
|
+
code: Type.String({ description: "JavaScript with await browser({args, stdin?, timeoutMs?}), emit(JSON), and emitImage(image handle). Fresh JS context; persistent browser.", minLength: 1, maxLength: 65_536 }),
|
|
31
|
+
session: Type.Optional(Type.String({ description: "Explicit native session; omitted uses the ordinary default browser.", minLength: 1 })),
|
|
32
|
+
namespace: Type.Optional(Type.String({ description: "Native namespace; empty string selects the default namespace." })),
|
|
33
|
+
...outputProperties(Type),
|
|
34
|
+
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, maximum: AGENT_BROWSER_CODE_MAX_TIMEOUT_MS })),
|
|
35
|
+
}, { additionalProperties: false }));
|
|
36
|
+
}
|
|
37
|
+
export function createAgentBrowserActionParamsSchema(Type = JsonSchema, StringEnum = localStringEnum) {
|
|
38
|
+
return Type.Unsafe(Type.Object({
|
|
39
|
+
action: StringEnum(AGENT_BROWSER_SEMANTIC_ACTIONS),
|
|
40
|
+
locator: Type.Optional(StringEnum(AGENT_BROWSER_SEMANTIC_LOCATORS, { description: "Select supports role or label." })),
|
|
41
|
+
value: Type.Optional(Type.String({ description: "Locator value or select option; label text for select by label." })),
|
|
42
|
+
values: Type.Optional(Type.Array(Type.String(), { description: "Select options; required for select by label.", minItems: 1 })),
|
|
43
|
+
selector: Type.Optional(Type.String({ description: "Direct selector or current @ref." })),
|
|
44
|
+
text: Type.Optional(Type.String({ description: "Fill text." })),
|
|
45
|
+
role: Type.Optional(Type.String({ description: "Role locator; select needs combobox or listbox." })),
|
|
46
|
+
name: Type.Optional(Type.String({ description: "Accessible name." })),
|
|
47
|
+
session: Type.Optional(Type.String({ description: "Upstream session name." })),
|
|
48
|
+
...outputProperties(Type),
|
|
144
49
|
}, {
|
|
145
50
|
additionalProperties: false,
|
|
146
|
-
|
|
147
|
-
|
|
51
|
+
// Top-level properties let Pi normalize optional nulls before validating branches.
|
|
52
|
+
anyOf: [
|
|
53
|
+
Type.Object({ action: StringEnum(["select"]), locator: Type.Optional(StringEnum(["role", "label"])) }, { not: { required: ["text"] } }),
|
|
54
|
+
Type.Object({ action: StringEnum(["check", "click", "fill"]) }, { not: { required: ["values"] } }),
|
|
55
|
+
],
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
export function createAgentBrowserQaParamsSchema(Type = JsonSchema, StringEnum = localStringEnum) {
|
|
59
|
+
return Type.Unsafe(Type.Object({
|
|
60
|
+
url: Type.Optional(Type.String()),
|
|
61
|
+
attached: Type.Optional(Type.Boolean()),
|
|
62
|
+
expectedText: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())])),
|
|
63
|
+
expectedSelector: Type.Optional(Type.String()),
|
|
64
|
+
screenshotPath: Type.Optional(Type.String()),
|
|
65
|
+
checkConsole: Type.Optional(Type.Boolean()),
|
|
66
|
+
checkErrors: Type.Optional(Type.Boolean()),
|
|
67
|
+
checkNetwork: Type.Optional(Type.Boolean()),
|
|
68
|
+
loadState: Type.Optional(StringEnum(AGENT_BROWSER_QA_LOAD_STATES)),
|
|
69
|
+
...outputProperties(Type),
|
|
70
|
+
sessionMode: sessionModeProperty(Type, StringEnum),
|
|
71
|
+
}, {
|
|
72
|
+
additionalProperties: false,
|
|
73
|
+
anyOf: [
|
|
74
|
+
Type.Object({ attached: Type.Literal(true), sessionMode: Type.Optional(StringEnum(["auto"])) }, { not: { required: ["url"] } }),
|
|
75
|
+
Type.Object({ url: Type.String(), attached: Type.Optional(Type.Literal(false)) }),
|
|
76
|
+
],
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
export function createAgentBrowserElectronParamsSchema(Type = JsonSchema, StringEnum = localStringEnum) {
|
|
80
|
+
const common = { outputPath: outputProperties(Type).outputPath };
|
|
81
|
+
const timeoutMs = Type.Optional(Type.Integer({ minimum: 1 }));
|
|
82
|
+
return Type.Unsafe(Type.Union([
|
|
83
|
+
Type.Object({
|
|
84
|
+
action: StringEnum(["list"]),
|
|
85
|
+
query: Type.Optional(Type.String({ description: "Case-insensitive app filter.", minLength: 1 })),
|
|
86
|
+
maxResults: Type.Optional(Type.Integer({ description: `Result cap; default ${ELECTRON_DISCOVERY_DEFAULT_MAX_RESULTS}, clamped to ${ELECTRON_DISCOVERY_MAX_RESULTS}.`, minimum: 1 })),
|
|
87
|
+
...common,
|
|
88
|
+
}, { additionalProperties: false }),
|
|
89
|
+
Type.Object({
|
|
90
|
+
action: StringEnum(["launch"]),
|
|
91
|
+
appPath: Type.Optional(Type.String({ description: "macOS .app path.", minLength: 1 })),
|
|
92
|
+
appName: Type.Optional(Type.String({ description: "Name from agent_browser_electron list.", minLength: 1 })),
|
|
93
|
+
bundleId: Type.Optional(Type.String({ minLength: 1 })),
|
|
94
|
+
executablePath: Type.Optional(Type.String({ minLength: 1 })),
|
|
95
|
+
appArgs: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
96
|
+
handoff: Type.Optional(StringEnum(AGENT_BROWSER_ELECTRON_HANDOFFS)),
|
|
97
|
+
targetType: Type.Optional(StringEnum(AGENT_BROWSER_ELECTRON_TARGET_TYPES)),
|
|
98
|
+
timeoutMs,
|
|
99
|
+
allow: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
100
|
+
deny: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
101
|
+
...common,
|
|
102
|
+
}, { additionalProperties: false }),
|
|
103
|
+
Type.Object({
|
|
104
|
+
action: StringEnum(["status", "cleanup"]),
|
|
105
|
+
launchId: Type.Optional(Type.String({ minLength: 1 })),
|
|
106
|
+
all: Type.Optional(Type.Literal(true)),
|
|
107
|
+
timeoutMs,
|
|
108
|
+
...common,
|
|
109
|
+
}, { additionalProperties: false, not: { required: ["launchId", "all"] } }),
|
|
110
|
+
Type.Object({
|
|
111
|
+
action: StringEnum(["probe"]),
|
|
112
|
+
launchId: Type.Optional(Type.String({ minLength: 1 })),
|
|
113
|
+
timeoutMs,
|
|
114
|
+
...common,
|
|
115
|
+
}, { additionalProperties: false }),
|
|
116
|
+
]));
|
|
117
|
+
}
|
|
118
|
+
export function createAgentBrowserSourceParamsSchema(Type = JsonSchema, StringEnum = localStringEnum) {
|
|
119
|
+
return Type.Unsafe(Type.Object({
|
|
120
|
+
selector: Type.Optional(Type.String({ description: "Visible selector or @ref." })),
|
|
121
|
+
reactFiberId: Type.Optional(Type.String({ description: "React fiber id; requires --enable react-devtools." })),
|
|
122
|
+
componentName: Type.Optional(Type.String({ description: "Component for local source search." })),
|
|
123
|
+
includeDomHints: Type.Optional(Type.Boolean({ description: "Inspect DOM hints; default true." })),
|
|
124
|
+
maxWorkspaceFiles: Type.Optional(Type.Integer({ description: "Source scan cap; default 2000.", minimum: 1, maximum: SOURCE_LOOKUP_MAX_WORKSPACE_FILES })),
|
|
125
|
+
...outputProperties(Type),
|
|
126
|
+
sessionMode: sessionModeProperty(Type, StringEnum),
|
|
127
|
+
}, { additionalProperties: false }));
|
|
128
|
+
}
|
|
129
|
+
export function createAgentBrowserNetworkSourceParamsSchema(Type = JsonSchema, StringEnum = localStringEnum) {
|
|
130
|
+
return Type.Unsafe(Type.Object({
|
|
131
|
+
filter: Type.Optional(Type.String({ description: "Network request filter." })),
|
|
132
|
+
namespace: Type.Optional(Type.String()),
|
|
133
|
+
requestId: Type.Optional(Type.String({ description: "Request id to inspect." })),
|
|
134
|
+
session: Type.Optional(Type.String()),
|
|
135
|
+
url: Type.Optional(Type.String({ description: "Failed URL or fragment." })),
|
|
136
|
+
maxWorkspaceFiles: Type.Optional(Type.Integer({ description: "Source scan cap; default 2000.", minimum: 1, maximum: SOURCE_LOOKUP_MAX_WORKSPACE_FILES })),
|
|
137
|
+
...outputProperties(Type),
|
|
138
|
+
sessionMode: sessionModeProperty(Type, StringEnum),
|
|
139
|
+
}, { additionalProperties: false }));
|
|
140
|
+
}
|
|
141
|
+
export function createAgentBrowserToolsParamsSchema(Type = JsonSchema, StringEnum = localStringEnum) {
|
|
142
|
+
return Type.Unsafe(Type.Object({
|
|
143
|
+
enable: Type.Optional(Type.Array(StringEnum(AGENT_BROWSER_ADVANCED_TOOLS), { description: "Add capabilities to active tools; omit to list inventory." })),
|
|
144
|
+
}, { additionalProperties: false }));
|
|
148
145
|
}
|
|
149
146
|
export const AGENT_BROWSER_PARAMS = createAgentBrowserParamsSchema();
|
|
147
|
+
export const AGENT_BROWSER_CODE_PARAMS = createAgentBrowserCodeParamsSchema();
|
|
148
|
+
export const AGENT_BROWSER_ACTION_PARAMS = createAgentBrowserActionParamsSchema();
|
|
149
|
+
export const AGENT_BROWSER_QA_PARAMS = createAgentBrowserQaParamsSchema();
|
|
150
|
+
export const AGENT_BROWSER_ELECTRON_PARAMS = createAgentBrowserElectronParamsSchema();
|
|
151
|
+
export const AGENT_BROWSER_SOURCE_PARAMS = createAgentBrowserSourceParamsSchema();
|
|
152
|
+
export const AGENT_BROWSER_NETWORK_SOURCE_PARAMS = createAgentBrowserNetworkSourceParamsSchema();
|
|
153
|
+
export const AGENT_BROWSER_TOOLS_PARAMS = createAgentBrowserToolsParamsSchema();
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
2
|
import { existsSync } from "node:fs";
|
|
4
3
|
import { dirname, join } from "node:path";
|
|
5
4
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { parseArgvDescriptor } from "../argv-descriptor.js";
|
|
7
|
-
import { getFlagName } from "../argv-grammar.js";
|
|
8
|
-
import { isBrowserIndependentRead, needsManagedSession } from "../command-policy.js";
|
|
9
|
-
import { isCloseCommand } from "../command-taxonomy.js";
|
|
10
|
-
import { LAUNCH_SCOPED_FLAGS, MANAGED_RESTORE_INCOMPATIBLE_FLAGS } from "../launch-scoped-flags.js";
|
|
11
5
|
import { isRecord } from "../parsing.js";
|
|
12
|
-
import {
|
|
6
|
+
import { extractExplicitNamespace, extractExplicitSessionName, scanUpstreamGlobalFlagOccurrences } from "../argv-grammar.js";
|
|
7
|
+
import { getUpstreamEffectiveBatchSteps } from "../orchestration/batch-stdin.js";
|
|
8
|
+
import { extractUpstreamCommandTokens } from "../argv-descriptor.js";
|
|
9
|
+
import { isCloseAllCommand } from "../command-taxonomy.js";
|
|
10
|
+
import { redactSensitiveText, validateToolArgs } from "../runtime.js";
|
|
13
11
|
export const AGENT_BROWSER_SCRIPT_CODE_MAX_BYTES = 64 * 1_024;
|
|
14
12
|
export const AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS = 120_000;
|
|
15
13
|
export const AGENT_BROWSER_SCRIPT_NAMESPACE = "";
|
|
@@ -18,7 +16,6 @@ export const AGENT_BROWSER_SCRIPT_MAX_CALLS = 25;
|
|
|
18
16
|
export const AGENT_BROWSER_SCRIPT_FINAL_OUTPUT_MAX_BYTES = 64 * 1_024;
|
|
19
17
|
export const AGENT_BROWSER_SCRIPT_IPC_MESSAGE_MAX_BYTES = 1 * 1_024 * 1_024;
|
|
20
18
|
export const AGENT_BROWSER_SCRIPT_IPC_CUMULATIVE_MAX_BYTES = 8 * 1_024 * 1_024;
|
|
21
|
-
export const AGENT_BROWSER_SCRIPT_SPILL_MAX_BYTES = 512 * 1_024;
|
|
22
19
|
function findPackageRoot(startDir) {
|
|
23
20
|
let currentDir = startDir;
|
|
24
21
|
for (;;) {
|
|
@@ -37,25 +34,13 @@ function resolveScriptWorkerPath() {
|
|
|
37
34
|
return workerPath;
|
|
38
35
|
}
|
|
39
36
|
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}$/;
|
|
40
|
-
|
|
41
|
-
const SCRIPT_FORBIDDEN_COMMANDS = new Set(["attach", "auth", "batch", "connect", "script", "session", "state"]);
|
|
42
|
-
const SCRIPT_FORBIDDEN_FLAGS = new Set([
|
|
43
|
-
...LAUNCH_SCOPED_FLAGS.filter((flag) => flag !== SCRIPT_ALLOWED_LAUNCH_FLAG),
|
|
44
|
-
...MANAGED_RESTORE_INCOMPATIBLE_FLAGS.filter((flag) => flag !== SCRIPT_ALLOWED_LAUNCH_FLAG),
|
|
45
|
-
"--namespace",
|
|
46
|
-
"--session",
|
|
47
|
-
"--config",
|
|
48
|
-
]);
|
|
49
|
-
export function compileAgentBrowserScript(input) {
|
|
37
|
+
export function validateAgentBrowserScriptSource(input) {
|
|
50
38
|
if (typeof input !== "string")
|
|
51
39
|
return { error: "script must be a string." };
|
|
52
40
|
const bytes = Buffer.byteLength(input, "utf8");
|
|
53
41
|
return bytes > AGENT_BROWSER_SCRIPT_CODE_MAX_BYTES
|
|
54
42
|
? { error: `script must be ${AGENT_BROWSER_SCRIPT_CODE_MAX_BYTES} bytes or less.` }
|
|
55
|
-
: {
|
|
56
|
-
}
|
|
57
|
-
export function createAgentBrowserScriptSessionName() {
|
|
58
|
-
return `piab-script-${randomUUID()}`;
|
|
43
|
+
: {};
|
|
59
44
|
}
|
|
60
45
|
export function createAgentBrowserScriptCloseArgs(sessionName) {
|
|
61
46
|
return ["--namespace", AGENT_BROWSER_SCRIPT_NAMESPACE, "--session", sessionName, "close"];
|
|
@@ -63,25 +48,6 @@ export function createAgentBrowserScriptCloseArgs(sessionName) {
|
|
|
63
48
|
export function isAgentBrowserScriptSessionName(value) {
|
|
64
49
|
return typeof value === "string" && SCRIPT_SESSION_NAME_PATTERN.test(value);
|
|
65
50
|
}
|
|
66
|
-
function getScriptCallPolicyError(args) {
|
|
67
|
-
const descriptor = parseArgvDescriptor(args);
|
|
68
|
-
const command = descriptor.commandInfo.command;
|
|
69
|
-
if (!command)
|
|
70
|
-
return "script browser call args must contain an agent-browser command.";
|
|
71
|
-
if (isCloseCommand(command))
|
|
72
|
-
return "script browser calls cannot close, quit, or exit their isolated session.";
|
|
73
|
-
if (SCRIPT_FORBIDDEN_COMMANDS.has(command))
|
|
74
|
-
return `script browser calls cannot use ${command}.`;
|
|
75
|
-
if (!needsManagedSession(descriptor) && !isBrowserIndependentRead(descriptor.upstreamCommandTokens))
|
|
76
|
-
return `script browser calls cannot use sessionless/local command ${command}.`;
|
|
77
|
-
for (const token of args) {
|
|
78
|
-
const flag = getFlagName(token);
|
|
79
|
-
if (SCRIPT_FORBIDDEN_FLAGS.has(flag)) {
|
|
80
|
-
return `script browser calls cannot use ${flag}; the parent owns the isolated session identity and launch policy.`;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return undefined;
|
|
84
|
-
}
|
|
85
51
|
export function validateAgentBrowserScriptBrowserParams(input) {
|
|
86
52
|
if (!isRecord(input))
|
|
87
53
|
return { error: "script browser(params) requires an object." };
|
|
@@ -102,53 +68,51 @@ export function validateAgentBrowserScriptBrowserParams(input) {
|
|
|
102
68
|
stdin: input.stdin,
|
|
103
69
|
timeoutMs: input.timeoutMs,
|
|
104
70
|
};
|
|
105
|
-
const policyError = getScriptCallPolicyError(params.args);
|
|
106
|
-
if (policyError)
|
|
107
|
-
return { error: policyError, policyBlocked: true };
|
|
108
71
|
const validationError = validateToolArgs(params.args);
|
|
109
72
|
return validationError ? { error: validationError } : { params };
|
|
110
73
|
}
|
|
111
|
-
function
|
|
74
|
+
export function bindBrowserCodeCall(params, identity) {
|
|
75
|
+
const explicitSession = extractExplicitSessionName(params.args);
|
|
76
|
+
const namespaceFlags = scanUpstreamGlobalFlagOccurrences(params.args, "--namespace");
|
|
77
|
+
const explicitNamespace = extractExplicitNamespace(params.args);
|
|
78
|
+
if ((explicitSession !== undefined && explicitSession !== identity.sessionName)
|
|
79
|
+
|| (namespaceFlags.length > 0 && (explicitNamespace || undefined) !== (identity.namespace || undefined))) {
|
|
80
|
+
throw new Error("A code call uses one browser identity. Set session/namespace on agent_browser_code to choose another browser.");
|
|
81
|
+
}
|
|
82
|
+
const tokens = extractUpstreamCommandTokens(params.args);
|
|
83
|
+
if (isCloseAllCommand(tokens) || getUpstreamEffectiveBatchSteps(tokens, params.stdin).some(isCloseAllCommand)) {
|
|
84
|
+
throw new Error("Run namespace-wide close --all directly with agent_browser, outside a session-scoped code call.");
|
|
85
|
+
}
|
|
86
|
+
return { ...params, args: [
|
|
87
|
+
...(namespaceFlags.length === 0 ? ["--namespace", identity.namespace ?? ""] : []),
|
|
88
|
+
...(explicitSession === undefined ? ["--session", identity.sessionName] : []),
|
|
89
|
+
...params.args,
|
|
90
|
+
] };
|
|
91
|
+
}
|
|
92
|
+
function buildRejectedCallEnvelope(error) {
|
|
112
93
|
return {
|
|
113
94
|
data: null,
|
|
114
|
-
details: { failureCategory: policyBlocked ? "policy-blocked" : "validation-error", resultCategory: "failure" },
|
|
115
95
|
error,
|
|
116
|
-
failureCategory:
|
|
117
|
-
|
|
96
|
+
failureCategory: "validation-error",
|
|
97
|
+
success: false,
|
|
118
98
|
resultCategory: "failure",
|
|
119
99
|
summary: error,
|
|
120
|
-
text: error,
|
|
121
100
|
};
|
|
122
101
|
}
|
|
123
102
|
function normalizeBrowserEnvelope(value) {
|
|
124
|
-
if (!isRecord(value)
|
|
125
|
-
|
|
126
|
-
|| typeof value.text !== "string"
|
|
127
|
-
|| typeof value.summary !== "string"
|
|
128
|
-
|| (value.resultCategory !== "success" && value.resultCategory !== "failure")) {
|
|
129
|
-
return buildRejectedCallEnvelope("The ordinary agent_browser executor returned an invalid script envelope.", false);
|
|
103
|
+
if (!isRecord(value) || typeof value.success !== "boolean" || (value.resultCategory !== "success" && value.resultCategory !== "failure")) {
|
|
104
|
+
return buildRejectedCallEnvelope("The browser executor returned an invalid code observation.");
|
|
130
105
|
}
|
|
131
|
-
return
|
|
132
|
-
data: value.data ?? null,
|
|
133
|
-
details: isRecord(value.details) ? value.details : undefined,
|
|
134
|
-
error: typeof value.error === "string" ? value.error : undefined,
|
|
135
|
-
failureCategory: value.failureCategory,
|
|
136
|
-
nextActions: Array.isArray(value.nextActions) ? value.nextActions : undefined,
|
|
137
|
-
ok: value.ok,
|
|
138
|
-
resultCategory: value.resultCategory,
|
|
139
|
-
successCategory: value.successCategory,
|
|
140
|
-
summary: value.summary,
|
|
141
|
-
text: value.text,
|
|
142
|
-
};
|
|
106
|
+
return value;
|
|
143
107
|
}
|
|
144
108
|
function buildStepSummary(index, envelope) {
|
|
145
109
|
return {
|
|
146
110
|
failureCategory: envelope.failureCategory,
|
|
147
111
|
index,
|
|
148
|
-
ok: envelope.
|
|
112
|
+
ok: envelope.success,
|
|
149
113
|
resultCategory: envelope.resultCategory,
|
|
150
114
|
successCategory: envelope.successCategory,
|
|
151
|
-
summary: envelope.summary,
|
|
115
|
+
summary: envelope.summary ?? (typeof envelope.error === "string" ? envelope.error : "Browser call completed."),
|
|
152
116
|
};
|
|
153
117
|
}
|
|
154
118
|
function buildFailedRun(options) {
|
|
@@ -168,7 +132,7 @@ function isScriptChildMessage(value) {
|
|
|
168
132
|
return true;
|
|
169
133
|
if (value.type === "call")
|
|
170
134
|
return typeof value.id === "number" && Number.isSafeInteger(value.id) && value.id > 0;
|
|
171
|
-
if (value.type === "emit")
|
|
135
|
+
if (value.type === "emit" || value.type === "image")
|
|
172
136
|
return true;
|
|
173
137
|
return value.type === "complete";
|
|
174
138
|
}
|
|
@@ -206,7 +170,7 @@ function serializeFinalOutput(value) {
|
|
|
206
170
|
return JSON.stringify(value);
|
|
207
171
|
}
|
|
208
172
|
export async function runAgentBrowserScript(options) {
|
|
209
|
-
const compiled =
|
|
173
|
+
const compiled = validateAgentBrowserScriptSource(options.code);
|
|
210
174
|
if (compiled.error) {
|
|
211
175
|
return buildFailedRun({ callCount: 0, emitCount: 0, error: compiled.error, failureCategory: "validation-error", rejectedCallCount: 0, steps: [] });
|
|
212
176
|
}
|
|
@@ -241,12 +205,12 @@ export async function runAgentBrowserScript(options) {
|
|
|
241
205
|
let cumulativeBytes = 0;
|
|
242
206
|
let callCount = 0;
|
|
243
207
|
let rejectedCallCount = 0;
|
|
244
|
-
let leaseStarted = false;
|
|
245
208
|
let ready = false;
|
|
246
209
|
let stopping = false;
|
|
247
210
|
let activeCallController;
|
|
248
211
|
const emissions = [];
|
|
249
212
|
const steps = [];
|
|
213
|
+
const failures = [];
|
|
250
214
|
const messages = [];
|
|
251
215
|
let draining = false;
|
|
252
216
|
let drainPromise = Promise.resolve();
|
|
@@ -275,10 +239,11 @@ export async function runAgentBrowserScript(options) {
|
|
|
275
239
|
if (timeout)
|
|
276
240
|
clearTimeout(timeout);
|
|
277
241
|
options.signal?.removeEventListener("abort", abortListener);
|
|
278
|
-
activeCallController?.abort();
|
|
242
|
+
activeCallController?.abort(result.timedOut ? new DOMException("Browser code deadline exceeded.", "TimeoutError") : options.signal?.reason);
|
|
279
243
|
killTimer = terminateChild(child);
|
|
244
|
+
// The caller holds the browser lease until dispatch actually settles.
|
|
280
245
|
if (waitForDrain)
|
|
281
|
-
await
|
|
246
|
+
await drainPromise.catch(() => undefined);
|
|
282
247
|
await settleWithin(childExit, 1_000);
|
|
283
248
|
clearTimeout(killTimer);
|
|
284
249
|
resolveResult(result);
|
|
@@ -287,13 +252,16 @@ export async function runAgentBrowserScript(options) {
|
|
|
287
252
|
...flags,
|
|
288
253
|
callCount,
|
|
289
254
|
emitCount: emissions.length,
|
|
255
|
+
...(emissions.length ? { data: emissions.length === 1 ? emissions[0] : emissions } : {}),
|
|
290
256
|
error,
|
|
291
257
|
failureCategory,
|
|
258
|
+
failures,
|
|
292
259
|
rejectedCallCount,
|
|
293
260
|
steps,
|
|
294
261
|
}), waitForDrain);
|
|
295
262
|
const abortListener = () => {
|
|
296
|
-
|
|
263
|
+
const timedOut = options.signal?.reason instanceof Error && options.signal.reason.name === "TimeoutError";
|
|
264
|
+
void fail(timedOut ? "Browser code deadline exceeded." : "Browser code was aborted.", timedOut ? "timeout" : "aborted", timedOut ? { timedOut: true } : { aborted: true }, true);
|
|
297
265
|
};
|
|
298
266
|
options.signal?.addEventListener("abort", abortListener, { once: true });
|
|
299
267
|
timeout = setTimeout(() => {
|
|
@@ -333,6 +301,18 @@ export async function runAgentBrowserScript(options) {
|
|
|
333
301
|
emissions.push(message.value);
|
|
334
302
|
continue;
|
|
335
303
|
}
|
|
304
|
+
if (message.type === "image") {
|
|
305
|
+
try {
|
|
306
|
+
if (!options.emitImage)
|
|
307
|
+
throw new Error("Image emission is unavailable.");
|
|
308
|
+
await options.emitImage(message.value);
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
await fail(error instanceof Error ? error.message : "Invalid image handle.", "validation-error");
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
336
316
|
if (message.type === "complete") {
|
|
337
317
|
if (message.error) {
|
|
338
318
|
await fail(describeScriptError(message.error), "script-error");
|
|
@@ -353,7 +333,7 @@ export async function runAgentBrowserScript(options) {
|
|
|
353
333
|
await fail(`Final script output exceeds ${AGENT_BROWSER_SCRIPT_FINAL_OUTPUT_MAX_BYTES} bytes.`, "validation-error");
|
|
354
334
|
return;
|
|
355
335
|
}
|
|
356
|
-
await finish({ callCount, data, emitCount: emissions.length, ok: true, rejectedCallCount, steps }, false);
|
|
336
|
+
await finish({ callCount, data, emitCount: emissions.length, failures, ok: true, rejectedCallCount, steps }, false);
|
|
357
337
|
return;
|
|
358
338
|
}
|
|
359
339
|
callCount += 1;
|
|
@@ -365,38 +345,30 @@ export async function runAgentBrowserScript(options) {
|
|
|
365
345
|
let envelope;
|
|
366
346
|
if (!validated.params) {
|
|
367
347
|
rejectedCallCount += 1;
|
|
368
|
-
envelope = buildRejectedCallEnvelope(validated.error ?? "Invalid script browser call."
|
|
348
|
+
envelope = buildRejectedCallEnvelope(validated.error ?? "Invalid script browser call.");
|
|
369
349
|
}
|
|
370
350
|
else {
|
|
371
|
-
if (!leaseStarted) {
|
|
372
|
-
try {
|
|
373
|
-
options.beforeFirstCall?.();
|
|
374
|
-
leaseStarted = true;
|
|
375
|
-
}
|
|
376
|
-
catch {
|
|
377
|
-
await fail("Unable to persist the isolated script session lease.", "upstream-error");
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
351
|
activeCallController = new AbortController();
|
|
382
352
|
try {
|
|
383
353
|
envelope = normalizeBrowserEnvelope(await options.dispatch(validated.params, activeCallController.signal));
|
|
384
354
|
}
|
|
385
|
-
catch {
|
|
386
|
-
envelope = buildRejectedCallEnvelope("The
|
|
355
|
+
catch (error) {
|
|
356
|
+
envelope = buildRejectedCallEnvelope(redactSensitiveText(error instanceof Error ? error.message : "The browser executor failed while dispatching this call."));
|
|
387
357
|
}
|
|
388
358
|
finally {
|
|
389
359
|
activeCallController = undefined;
|
|
390
360
|
}
|
|
391
|
-
if (stopping)
|
|
392
|
-
return;
|
|
393
361
|
}
|
|
394
362
|
steps.push(buildStepSummary(callCount - 1, envelope));
|
|
363
|
+
if (!envelope.success)
|
|
364
|
+
failures.push({ ...envelope, index: callCount - 1 });
|
|
365
|
+
if (stopping)
|
|
366
|
+
return;
|
|
395
367
|
try {
|
|
396
368
|
await sendParentMessage({ envelope, id: message.id, type: "response" });
|
|
397
369
|
}
|
|
398
370
|
catch {
|
|
399
|
-
await fail("Unable to return a browser result
|
|
371
|
+
await fail("Unable to return a browser result within the code IPC limit. Narrow the native extraction or use agent_browser with outputPath; already-dispatched effects are not rolled back.", "upstream-error");
|
|
400
372
|
return;
|
|
401
373
|
}
|
|
402
374
|
}
|