pi-agent-browser-native 0.2.77 → 0.5.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 +154 -0
- package/README.md +69 -35
- package/dist/extensions/agent-browser/index.js +911 -121
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +35 -8
- package/dist/extensions/agent-browser/lib/argv-grammar.js +48 -7
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +71 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +1 -6
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +35 -8
- package/dist/extensions/agent-browser/lib/config-policy.js +0 -5
- package/dist/extensions/agent-browser/lib/config.js +0 -6
- package/dist/extensions/agent-browser/lib/electron/cdp.js +6 -7
- package/dist/extensions/agent-browser/lib/electron/cleanup.js +0 -7
- package/dist/extensions/agent-browser/lib/electron/discovery.js +0 -7
- package/dist/extensions/agent-browser/lib/electron/launch.js +0 -7
- package/dist/extensions/agent-browser/lib/input-modes/electron.js +0 -5
- package/dist/extensions/agent-browser/lib/input-modes/job.js +61 -9
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +2 -7
- package/dist/extensions/agent-browser/lib/input-modes/params.js +23 -29
- package/dist/extensions/agent-browser/lib/input-modes/script.js +462 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +51 -17
- package/dist/extensions/agent-browser/lib/input-modes/shared.js +0 -5
- package/dist/extensions/agent-browser/lib/input-modes/types.js +0 -5
- package/dist/extensions/agent-browser/lib/json-schema.js +0 -5
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +12 -5
- package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -2
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +3 -4
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +55 -48
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +2 -9
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +48 -36
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +50 -29
- package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -5
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +26 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +110 -30
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +27 -26
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +58 -33
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +22 -14
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +148 -34
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +225 -61
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +74 -30
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +47 -26
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +40 -19
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +42 -1
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +299 -0
- package/dist/extensions/agent-browser/lib/parsing.js +0 -7
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +36 -11
- package/dist/extensions/agent-browser/lib/playbook.js +20 -24
- package/dist/extensions/agent-browser/lib/process-environment.js +14 -0
- package/dist/extensions/agent-browser/lib/process-identity.js +4 -9
- package/dist/extensions/agent-browser/lib/process.js +131 -48
- package/dist/extensions/agent-browser/lib/prompt-policy.js +171 -27
- package/dist/extensions/agent-browser/lib/recording-reservations.js +183 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +63 -13
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +68 -11
- package/dist/extensions/agent-browser/lib/results/categories.js +7 -9
- package/dist/extensions/agent-browser/lib/results/confirmation.js +0 -7
- package/dist/extensions/agent-browser/lib/results/contracts.js +0 -7
- package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +0 -7
- package/dist/extensions/agent-browser/lib/results/envelope.js +0 -7
- package/dist/extensions/agent-browser/lib/results/network.js +0 -7
- package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -35
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +85 -44
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +66 -11
- package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +18 -5
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +7 -6
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +2 -1
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +26 -16
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +58 -13
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +1 -15
- package/dist/extensions/agent-browser/lib/results/presentation/skills.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation.js +6 -8
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +2 -7
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +0 -7
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +51 -15
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +13 -14
- package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot.js +0 -7
- package/dist/extensions/agent-browser/lib/results/text.js +0 -7
- package/dist/extensions/agent-browser/lib/runtime.js +119 -52
- package/dist/extensions/agent-browser/lib/session-page-state.js +62 -17
- package/dist/extensions/agent-browser/lib/string-enum-schema.js +0 -5
- package/dist/extensions/agent-browser/lib/temp.js +0 -7
- package/dist/extensions/agent-browser/lib/upstream-version.js +14 -0
- package/dist/extensions/agent-browser/lib/web-search.js +0 -5
- package/dist/extensions/agent-browser/script-worker.js +169 -0
- package/dist/scripts/agent-browser-target.mjs +3 -0
- package/docs/ARCHITECTURE.md +50 -31
- package/docs/COMMAND_REFERENCE.md +97 -41
- package/docs/ELECTRON.md +3 -19
- package/docs/RELEASE.md +8 -20
- package/docs/REQUIREMENTS.md +8 -6
- package/docs/SUPPORT_MATRIX.md +32 -25
- package/docs/TOOL_CONTRACT.md +107 -64
- package/docs/platform-smoke.md +1 -1
- package/package.json +7 -6
- package/platform-smoke.config.mjs +2 -2
- package/scripts/agent-browser-capability-baseline.mjs +24 -6
- package/scripts/agent-browser-target.mjs +3 -0
- package/scripts/build.mjs +41 -0
- package/scripts/doctor.mjs +8 -7
- package/scripts/platform-smoke/browser-dogfood-windows.ps1 +9 -3
- package/scripts/platform-smoke/targets.mjs +12 -6
- package/dist/extensions/agent-browser/lib/electron/text.js +0 -13
- package/dist/extensions/agent-browser/lib/input-modes.js +0 -10
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +0 -5
- package/dist/extensions/agent-browser/lib/results/artifact-state.js +0 -13
- package/dist/extensions/agent-browser/lib/results.js +0 -14
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { open } from "node:fs/promises";
|
|
2
|
+
import { AGENT_BROWSER_SCRIPT_FINAL_OUTPUT_MAX_BYTES, AGENT_BROWSER_SCRIPT_IPC_MESSAGE_MAX_BYTES, AGENT_BROWSER_SCRIPT_SPILL_MAX_BYTES, createAgentBrowserScriptCloseArgs, isAgentBrowserScriptSessionName, validateAgentBrowserScriptBrowserParams, } from "../input-modes/script.js";
|
|
3
|
+
import { isRecord } from "../parsing.js";
|
|
4
|
+
import { parseCommandInfo, redactSensitiveText } from "../runtime.js";
|
|
5
|
+
import { isSessionArtifactManifest } from "../results/artifact-manifest.js";
|
|
6
|
+
import { redactPresentationData } from "../results/presentation/diagnostics.js";
|
|
7
|
+
import { truncateText } from "../results/text.js";
|
|
8
|
+
const SCRIPT_SESSION_ENTRY_TYPE = "agent-browser-script-session";
|
|
9
|
+
const SCRIPT_BROWSER_SUMMARY_MAX_CHARS = 1_024;
|
|
10
|
+
const SCRIPT_BROWSER_TEXT_MAX_CHARS = 8_192;
|
|
11
|
+
export function getScriptSessionLeasesFromBranch(branch) {
|
|
12
|
+
const leases = new Map();
|
|
13
|
+
for (const entry of branch) {
|
|
14
|
+
if (!isRecord(entry) || entry.type !== "custom" || entry.customType !== SCRIPT_SESSION_ENTRY_TYPE || !isRecord(entry.data))
|
|
15
|
+
continue;
|
|
16
|
+
const { cleanup, closeCommandArgs, launchAttempted, sessionName } = entry.data;
|
|
17
|
+
if (!isAgentBrowserScriptSessionName(sessionName))
|
|
18
|
+
continue;
|
|
19
|
+
const expectedCloseCommandArgs = createAgentBrowserScriptCloseArgs(sessionName);
|
|
20
|
+
if ((cleanup !== "active" && cleanup !== "closed" && cleanup !== "failed")
|
|
21
|
+
|| launchAttempted !== true
|
|
22
|
+
|| !Array.isArray(closeCommandArgs)
|
|
23
|
+
|| closeCommandArgs.length !== expectedCloseCommandArgs.length
|
|
24
|
+
|| !closeCommandArgs.every((token, index) => token === expectedCloseCommandArgs[index]))
|
|
25
|
+
continue;
|
|
26
|
+
leases.set(sessionName, { cleanup, closeCommandArgs: expectedCloseCommandArgs, launchAttempted: true, sessionName });
|
|
27
|
+
}
|
|
28
|
+
return leases;
|
|
29
|
+
}
|
|
30
|
+
export function appendScriptSessionLease(pi, sessionName, cleanup) {
|
|
31
|
+
pi.appendEntry(SCRIPT_SESSION_ENTRY_TYPE, {
|
|
32
|
+
cleanup,
|
|
33
|
+
closeCommandArgs: createAgentBrowserScriptCloseArgs(sessionName),
|
|
34
|
+
launchAttempted: true,
|
|
35
|
+
sessionName,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async function readVerifiedScriptSpill(result) {
|
|
39
|
+
const details = isRecord(result.details) ? result.details : undefined;
|
|
40
|
+
const path = typeof details?.fullOutputPath === "string" ? details.fullOutputPath : undefined;
|
|
41
|
+
const manifest = isSessionArtifactManifest(details?.artifactManifest) ? details.artifactManifest : undefined;
|
|
42
|
+
if (!path || !manifest?.entries.some((entry) => entry.kind === "spill"
|
|
43
|
+
&& (entry.path === path || entry.absolutePath === path)
|
|
44
|
+
&& (entry.storageScope === "persistent-session" || entry.storageScope === "process-temp")
|
|
45
|
+
&& (entry.retentionState === "live" || entry.retentionState === "ephemeral")))
|
|
46
|
+
return undefined;
|
|
47
|
+
let handle;
|
|
48
|
+
try {
|
|
49
|
+
handle = await open(path, "r");
|
|
50
|
+
const stats = await handle.stat();
|
|
51
|
+
if (!stats.isFile() || stats.size < 0 || stats.size > AGENT_BROWSER_SCRIPT_SPILL_MAX_BYTES)
|
|
52
|
+
return undefined;
|
|
53
|
+
const buffer = Buffer.alloc(stats.size);
|
|
54
|
+
let offset = 0;
|
|
55
|
+
while (offset < buffer.length) {
|
|
56
|
+
const { bytesRead } = await handle.read(buffer, offset, buffer.length - offset, offset);
|
|
57
|
+
if (bytesRead === 0)
|
|
58
|
+
break;
|
|
59
|
+
offset += bytesRead;
|
|
60
|
+
}
|
|
61
|
+
const text = buffer.subarray(0, offset).toString("utf8");
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(text);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
await handle?.close().catch(() => undefined);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function getToolResultText(result) {
|
|
77
|
+
return result.content
|
|
78
|
+
.filter((item) => item.type === "text")
|
|
79
|
+
.map((item) => item.text)
|
|
80
|
+
.join("\n\n");
|
|
81
|
+
}
|
|
82
|
+
function stripOwnedScriptSessionArgs(args, sessionName) {
|
|
83
|
+
if (!isAgentBrowserScriptSessionName(sessionName))
|
|
84
|
+
return args;
|
|
85
|
+
if (args[0] === "--namespace" && args[1] === "" && args[2] === "--session" && args[3] === sessionName)
|
|
86
|
+
return args.slice(4);
|
|
87
|
+
return args[0] === "--session" && args[1] === sessionName ? args.slice(2) : args;
|
|
88
|
+
}
|
|
89
|
+
function getScriptCompatibleNextActions(value, sessionName) {
|
|
90
|
+
if (!Array.isArray(value))
|
|
91
|
+
return undefined;
|
|
92
|
+
const actions = value.flatMap((action) => {
|
|
93
|
+
if (!isRecord(action))
|
|
94
|
+
return [];
|
|
95
|
+
if (!isRecord(action.params))
|
|
96
|
+
return typeof action.artifactPath === "string" ? [action] : [];
|
|
97
|
+
if (Object.keys(action.params).some((key) => key !== "args" && key !== "stdin"))
|
|
98
|
+
return [];
|
|
99
|
+
if (!Array.isArray(action.params.args) || action.params.args.some((token) => typeof token !== "string"))
|
|
100
|
+
return [];
|
|
101
|
+
if (action.params.stdin !== undefined && typeof action.params.stdin !== "string")
|
|
102
|
+
return [];
|
|
103
|
+
const normalizedParams = {
|
|
104
|
+
args: stripOwnedScriptSessionArgs(action.params.args, sessionName),
|
|
105
|
+
...(typeof action.params.stdin === "string" ? { stdin: action.params.stdin } : {}),
|
|
106
|
+
};
|
|
107
|
+
if (!validateAgentBrowserScriptBrowserParams(normalizedParams).params)
|
|
108
|
+
return [];
|
|
109
|
+
return [{ ...action, params: normalizedParams }];
|
|
110
|
+
});
|
|
111
|
+
return actions.length > 0 ? actions : undefined;
|
|
112
|
+
}
|
|
113
|
+
function scriptBrowserEnvelopeFitsIpc(envelope) {
|
|
114
|
+
try {
|
|
115
|
+
return Buffer.byteLength(JSON.stringify({ envelope, id: Number.MAX_SAFE_INTEGER, type: "response" }), "utf8") + 1 <= AGENT_BROWSER_SCRIPT_IPC_MESSAGE_MAX_BYTES;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function buildOversizedScriptBrowserEnvelope() {
|
|
122
|
+
const text = "Browser result exceeds the script IPC response limit; narrow the inner browser call output.";
|
|
123
|
+
return {
|
|
124
|
+
data: null,
|
|
125
|
+
details: { failureCategory: "upstream-error", resultCategory: "failure" },
|
|
126
|
+
error: text,
|
|
127
|
+
failureCategory: "upstream-error",
|
|
128
|
+
ok: false,
|
|
129
|
+
resultCategory: "failure",
|
|
130
|
+
summary: text,
|
|
131
|
+
text,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export async function buildScriptBrowserEnvelope(result, args, scriptSessionName) {
|
|
135
|
+
const details = isRecord(result.details) ? result.details : undefined;
|
|
136
|
+
const fullData = await readVerifiedScriptSpill(result);
|
|
137
|
+
const commandInfo = parseCommandInfo(args);
|
|
138
|
+
const data = redactPresentationData(commandInfo, fullData ?? details?.data ?? null);
|
|
139
|
+
const resultCategory = details?.resultCategory === "failure" || result.isError === true ? "failure" : "success";
|
|
140
|
+
const text = truncateText(redactSensitiveText(getToolResultText(result)), SCRIPT_BROWSER_TEXT_MAX_CHARS);
|
|
141
|
+
const summary = truncateText(redactSensitiveText(typeof details?.summary === "string" ? details.summary : text.split("\n", 1)[0] || "Browser call completed."), SCRIPT_BROWSER_SUMMARY_MAX_CHARS);
|
|
142
|
+
const compatibleNextActions = getScriptCompatibleNextActions(details?.nextActions, scriptSessionName);
|
|
143
|
+
const redactedNextActions = getScriptCompatibleNextActions(redactPresentationData(commandInfo, compatibleNextActions), undefined);
|
|
144
|
+
const failureCategory = resultCategory === "failure" && typeof details?.failureCategory === "string"
|
|
145
|
+
? details.failureCategory
|
|
146
|
+
: undefined;
|
|
147
|
+
const successCategory = resultCategory === "success" && typeof details?.successCategory === "string"
|
|
148
|
+
? details.successCategory
|
|
149
|
+
: undefined;
|
|
150
|
+
const envelopeDetails = redactPresentationData(commandInfo, {
|
|
151
|
+
artifactVerification: details?.artifactVerification,
|
|
152
|
+
artifacts: details?.artifacts,
|
|
153
|
+
failureCategory,
|
|
154
|
+
pageChangeSummary: details?.pageChangeSummary,
|
|
155
|
+
resultCategory,
|
|
156
|
+
successCategory,
|
|
157
|
+
});
|
|
158
|
+
const envelope = {
|
|
159
|
+
data,
|
|
160
|
+
details: isRecord(envelopeDetails) ? envelopeDetails : { resultCategory },
|
|
161
|
+
error: resultCategory === "failure" ? summary : undefined,
|
|
162
|
+
failureCategory,
|
|
163
|
+
nextActions: Array.isArray(redactedNextActions) ? redactedNextActions : undefined,
|
|
164
|
+
ok: resultCategory === "success",
|
|
165
|
+
resultCategory,
|
|
166
|
+
successCategory,
|
|
167
|
+
summary,
|
|
168
|
+
text,
|
|
169
|
+
};
|
|
170
|
+
return scriptBrowserEnvelopeFitsIpc(envelope) ? envelope : buildOversizedScriptBrowserEnvelope();
|
|
171
|
+
}
|
|
172
|
+
function collectUniqueArtifacts(results) {
|
|
173
|
+
const records = results.flatMap((result) => {
|
|
174
|
+
const details = isRecord(result.details) ? result.details : undefined;
|
|
175
|
+
return Array.isArray(details?.artifacts) ? details.artifacts.filter(isRecord) : [];
|
|
176
|
+
});
|
|
177
|
+
const unique = new Map(records.map((record) => [String(record.absolutePath ?? record.path ?? JSON.stringify(record)), record]));
|
|
178
|
+
return unique.size > 0 ? [...unique.values()] : undefined;
|
|
179
|
+
}
|
|
180
|
+
function collectScriptArtifactVerification(results) {
|
|
181
|
+
const entries = results.flatMap((result) => {
|
|
182
|
+
const details = isRecord(result.details) ? result.details : undefined;
|
|
183
|
+
const verification = isRecord(details?.artifactVerification) ? details.artifactVerification : undefined;
|
|
184
|
+
return Array.isArray(verification?.artifacts) ? verification.artifacts.filter(isRecord) : [];
|
|
185
|
+
});
|
|
186
|
+
const unique = [...new Map(entries.map((entry) => [String(entry.absolutePath ?? entry.path ?? JSON.stringify(entry)), entry])).values()];
|
|
187
|
+
if (unique.length === 0)
|
|
188
|
+
return undefined;
|
|
189
|
+
const count = (state) => unique.filter((entry) => entry.state === state).length;
|
|
190
|
+
return {
|
|
191
|
+
artifacts: unique,
|
|
192
|
+
missingCount: count("missing"),
|
|
193
|
+
pendingCount: count("pending"),
|
|
194
|
+
unverifiedCount: count("unverified"),
|
|
195
|
+
verified: unique.every((entry) => entry.state === "verified"),
|
|
196
|
+
verifiedCount: count("verified"),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function getLatestScriptResultDetail(results, key) {
|
|
200
|
+
for (let index = results.length - 1; index >= 0; index -= 1) {
|
|
201
|
+
const rawDetails = results[index]?.details;
|
|
202
|
+
const details = isRecord(rawDetails) ? rawDetails : undefined;
|
|
203
|
+
if (details?.[key] !== undefined)
|
|
204
|
+
return details[key];
|
|
205
|
+
}
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
function buildScriptCloseNextAction(sessionName) {
|
|
209
|
+
return {
|
|
210
|
+
id: "close-script-session-after-cleanup-failure",
|
|
211
|
+
params: { args: createAgentBrowserScriptCloseArgs(sessionName) },
|
|
212
|
+
reason: "Retry closing the isolated wrapper-owned script session after automatic cleanup failed.",
|
|
213
|
+
safety: "Use these exact args; do not add profile, state, restore, namespace, or connection flags.",
|
|
214
|
+
tool: "agent_browser",
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
export function buildScriptToolResult(options) {
|
|
218
|
+
let data;
|
|
219
|
+
let serializedData;
|
|
220
|
+
let outputError;
|
|
221
|
+
try {
|
|
222
|
+
data = redactPresentationData({ command: "script" }, options.run.data);
|
|
223
|
+
serializedData = data === undefined ? undefined : JSON.stringify(data);
|
|
224
|
+
if (data !== undefined && serializedData === undefined)
|
|
225
|
+
throw new TypeError("Script output is not JSON-serializable.");
|
|
226
|
+
if (serializedData !== undefined && Buffer.byteLength(serializedData, "utf8") > AGENT_BROWSER_SCRIPT_FINAL_OUTPUT_MAX_BYTES) {
|
|
227
|
+
throw new RangeError(`Redacted script output exceeds ${AGENT_BROWSER_SCRIPT_FINAL_OUTPUT_MAX_BYTES} bytes.`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
data = undefined;
|
|
232
|
+
serializedData = undefined;
|
|
233
|
+
outputError = "Final script output could not be safely rendered as bounded JSON.";
|
|
234
|
+
}
|
|
235
|
+
const steps = options.run.steps.map((step) => ({ ...step, summary: redactSensitiveText(step.summary) }));
|
|
236
|
+
const rejectedFailureCategory = steps.find((step) => step.failureCategory === "policy-blocked")?.failureCategory
|
|
237
|
+
?? (options.run.rejectedCallCount > 0 ? "validation-error" : undefined);
|
|
238
|
+
const failureCategory = options.cleanupError
|
|
239
|
+
? "cleanup-failed"
|
|
240
|
+
: outputError ? "validation-error" : options.run.failureCategory ?? rejectedFailureCategory;
|
|
241
|
+
const failed = failureCategory !== undefined || !options.run.ok;
|
|
242
|
+
const failedStepCount = steps.filter((step) => !step.ok).length;
|
|
243
|
+
const failedCallCount = Math.max(0, failedStepCount - options.run.rejectedCallCount);
|
|
244
|
+
const successfulCallCount = steps.filter((step) => step.ok).length;
|
|
245
|
+
const cleanupSuffix = options.sessionName && !options.cleanupError ? " Isolated script session closed." : "";
|
|
246
|
+
const summary = `${options.cleanupError
|
|
247
|
+
? `Script completed, but isolated session cleanup failed: ${redactSensitiveText(options.cleanupError)}`
|
|
248
|
+
: outputError
|
|
249
|
+
? `Script failed: ${outputError}`
|
|
250
|
+
: options.run.ok
|
|
251
|
+
? rejectedFailureCategory
|
|
252
|
+
? `Script failed: ${options.run.rejectedCallCount} browser call${options.run.rejectedCallCount === 1 ? " was" : "s were"} rejected before dispatch.`
|
|
253
|
+
: failedCallCount > 0
|
|
254
|
+
? `Script completed (${options.run.callCount} browser call${options.run.callCount === 1 ? "" : "s"}; ${failedCallCount} returned failure for script handling).`
|
|
255
|
+
: `Script completed (${options.run.callCount} browser call${options.run.callCount === 1 ? "" : "s"}).`
|
|
256
|
+
: `Script failed: ${redactSensitiveText(options.run.error ?? "sandbox execution failed")}`}${cleanupSuffix}`;
|
|
257
|
+
const dataText = serializedData === undefined ? "" : `\n\n${serializedData}`;
|
|
258
|
+
const cleanupActionText = options.cleanupError && options.sessionName
|
|
259
|
+
? `\n\nNext action: ${JSON.stringify({ args: createAgentBrowserScriptCloseArgs(options.sessionName) })}`
|
|
260
|
+
: "";
|
|
261
|
+
const artifactManifest = getLatestScriptResultDetail(options.innerResults, "artifactManifest");
|
|
262
|
+
const artifactRetentionSummary = getLatestScriptResultDetail(options.innerResults, "artifactRetentionSummary");
|
|
263
|
+
const artifacts = collectUniqueArtifacts(options.innerResults);
|
|
264
|
+
const artifactVerification = collectScriptArtifactVerification(options.innerResults);
|
|
265
|
+
const nextActions = options.cleanupError && options.sessionName ? [buildScriptCloseNextAction(options.sessionName)] : undefined;
|
|
266
|
+
return {
|
|
267
|
+
content: [{ type: "text", text: `${summary}${dataText}${cleanupActionText}` }],
|
|
268
|
+
details: {
|
|
269
|
+
artifactManifest,
|
|
270
|
+
artifactRetentionSummary,
|
|
271
|
+
artifacts,
|
|
272
|
+
artifactVerification,
|
|
273
|
+
data,
|
|
274
|
+
failureCategory,
|
|
275
|
+
nextActions,
|
|
276
|
+
resultCategory: failed ? "failure" : "success",
|
|
277
|
+
scriptRun: {
|
|
278
|
+
aborted: options.run.aborted,
|
|
279
|
+
callCount: options.run.callCount,
|
|
280
|
+
emitCount: options.run.emitCount,
|
|
281
|
+
failedCallCount,
|
|
282
|
+
preDispatchRejectedCallCount: options.run.rejectedCallCount,
|
|
283
|
+
successfulCallCount,
|
|
284
|
+
timedOut: options.run.timedOut,
|
|
285
|
+
},
|
|
286
|
+
scriptSession: options.sessionName ? {
|
|
287
|
+
cleanup: options.cleanupError ? "failed" : "closed",
|
|
288
|
+
closeCommandArgs: createAgentBrowserScriptCloseArgs(options.sessionName),
|
|
289
|
+
...(options.cleanupError ? { error: redactSensitiveText(options.cleanupError) } : {}),
|
|
290
|
+
launchAttempted: true,
|
|
291
|
+
sessionName: options.sessionName,
|
|
292
|
+
} : undefined,
|
|
293
|
+
scriptSteps: steps,
|
|
294
|
+
successCategory: failed ? undefined : "completed",
|
|
295
|
+
summary,
|
|
296
|
+
},
|
|
297
|
+
isError: failed,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Centralize low-level boundary parsing helpers shared by runtime planning, temp-artifact lifecycle, and result rendering.
|
|
3
|
-
* Responsibilities: Identify non-null object records and normalize positive-integer string configuration values.
|
|
4
|
-
* Scope: Tiny generic parsing predicates only; module-specific validation and error handling stay with their owning modules.
|
|
5
|
-
* Usage: Imported by agent-browser wrapper modules that parse untyped JSON, persisted state, or environment variables.
|
|
6
|
-
* Invariants/Assumptions: Arrays intentionally count as records to preserve existing object-boundary semantics, and positive integers must be safe base-10 integer strings greater than zero.
|
|
7
|
-
*/
|
|
8
1
|
export function isRecord(value) {
|
|
9
2
|
return typeof value === "object" && value !== null;
|
|
10
3
|
}
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import { getKeybindings, Text, truncateToWidth } from "@earendil-works/pi-tui";
|
|
2
|
-
import { compileAgentBrowserElectron
|
|
2
|
+
import { compileAgentBrowserElectron } from "./input-modes/electron.js";
|
|
3
|
+
import { compileAgentBrowserJob, compileAgentBrowserQaPreset } from "./input-modes/job.js";
|
|
4
|
+
import { compileAgentBrowserNetworkSourceLookup, compileAgentBrowserSourceLookup } from "./input-modes/lookups.js";
|
|
5
|
+
import { compileAgentBrowserSemanticAction } from "./input-modes/semantic-action.js";
|
|
3
6
|
import { isRecord } from "./parsing.js";
|
|
4
7
|
import { redactInvocationArgs } from "./runtime.js";
|
|
5
8
|
const TUI_INVOCATION_PREVIEW_MAX_CHARS = 160;
|
|
6
9
|
const TUI_COLLAPSED_OUTPUT_MAX_LINES = 12;
|
|
7
|
-
const ANSI_CONTROL_SEQUENCE_PATTERN = /\x1B(?:\][^\x07\x1B]*(?:\x07|\x1B\\)|\[[0-?]*[ -/]*[@-~]|P[^\x1B]*(?:\x1B\\)|_[^\x1B]*(?:\x1B\\)|\^[^\x1B]*(?:\x1B\\)|[@-Z\\-_])/g;
|
|
10
|
+
const ANSI_CONTROL_SEQUENCE_PATTERN = /\x1B(?:\][^\x07\x1B\r\n\u2028\u2029]*(?:\x07|\x1B\\)|\[[0-?]*[ -/]*[@-~]|P[^\x1B\r\n\u2028\u2029]*(?:\x1B\\)|_[^\x1B\r\n\u2028\u2029]*(?:\x1B\\)|\^[^\x1B\r\n\u2028\u2029]*(?:\x1B\\)|[@-Z\\-_])/g;
|
|
8
11
|
const JSON_TOKEN_PATTERN = /"(?:\\.|[^"\\])*"(?=\s*:)|"(?:\\.|[^"\\])*"|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null|[{}\[\],:]/g;
|
|
9
12
|
const UNSAFE_DISPLAY_CONTROL_PATTERN = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\x80-\x9F]/g;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
.replace(
|
|
13
|
+
const UNSAFE_DISPLAY_DIRECTIONAL_PATTERN = /[\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/g;
|
|
14
|
+
const UNSAFE_DISPLAY_ZERO_WIDTH_PATTERN = /[\u200B-\u200D\u2060\uFEFF]/g;
|
|
15
|
+
function sanitizeDisplayText(value, markRemovedSequences = false) {
|
|
16
|
+
let sanitized = value
|
|
17
|
+
.replace(/\r\n?/g, "\n")
|
|
18
|
+
.replace(ANSI_CONTROL_SEQUENCE_PATTERN, markRemovedSequences ? "�" : "")
|
|
19
|
+
.replace(UNSAFE_DISPLAY_CONTROL_PATTERN, "�")
|
|
20
|
+
.replace(UNSAFE_DISPLAY_DIRECTIONAL_PATTERN, "�");
|
|
21
|
+
if (markRemovedSequences)
|
|
22
|
+
sanitized = sanitized.replace(/[\u2028\u2029]/g, "\n").replace(UNSAFE_DISPLAY_ZERO_WIDTH_PATTERN, "�");
|
|
23
|
+
return sanitized;
|
|
15
24
|
}
|
|
16
25
|
function replaceTabsForDisplay(value) {
|
|
17
26
|
return value.replaceAll("\t", " ");
|
|
@@ -86,6 +95,8 @@ function formatVisualTruncationNotice(remainingLines, totalLines, theme, width)
|
|
|
86
95
|
return truncateToWidth(notice, Math.max(0, width));
|
|
87
96
|
}
|
|
88
97
|
function getStructuredModeInvocation(input) {
|
|
98
|
+
if (typeof input.script === "string")
|
|
99
|
+
return { mode: "script", rawArgs: [], scriptSource: input.script };
|
|
89
100
|
if (Array.isArray(input.args))
|
|
90
101
|
return { rawArgs: input.args.filter((value) => typeof value === "string") };
|
|
91
102
|
if (input.semanticAction !== undefined)
|
|
@@ -111,14 +122,28 @@ function formatInvocationPreview(rawArgs) {
|
|
|
111
122
|
? `${invocation.slice(0, TUI_INVOCATION_PREVIEW_MAX_CHARS - 3)}...`
|
|
112
123
|
: invocation;
|
|
113
124
|
}
|
|
114
|
-
|
|
125
|
+
function formatScriptSourceForDisplay(source, expanded) {
|
|
126
|
+
const sanitizedSource = replaceTabsForDisplay(sanitizeDisplayText(source, true));
|
|
127
|
+
if (expanded)
|
|
128
|
+
return sanitizedSource;
|
|
129
|
+
const preview = sanitizedSource.replace(/\n/g, " ↵ ").replace(/\s+/g, " ").trim();
|
|
130
|
+
return preview.length > TUI_INVOCATION_PREVIEW_MAX_CHARS
|
|
131
|
+
? `${preview.slice(0, TUI_INVOCATION_PREVIEW_MAX_CHARS - 3)}...`
|
|
132
|
+
: preview;
|
|
133
|
+
}
|
|
134
|
+
export function formatAgentBrowserRenderCall(args, theme, expanded = false) {
|
|
115
135
|
const input = isRecord(args) ? args : {};
|
|
116
|
-
const { mode, rawArgs } = getStructuredModeInvocation(input);
|
|
117
|
-
const invocationPreview =
|
|
136
|
+
const { mode, rawArgs, scriptSource } = getStructuredModeInvocation(input);
|
|
137
|
+
const invocationPreview = scriptSource === undefined
|
|
138
|
+
? formatInvocationPreview(rawArgs)
|
|
139
|
+
: formatScriptSourceForDisplay(scriptSource, expanded);
|
|
118
140
|
let text = theme.fg("toolTitle", theme.bold("agent_browser"));
|
|
119
141
|
if (mode) {
|
|
120
142
|
text += ` ${theme.fg("accent", mode)}`;
|
|
121
|
-
if (
|
|
143
|
+
if (scriptSource !== undefined && expanded) {
|
|
144
|
+
text += `\n${theme.fg("dim", "Source:")}\n${theme.fg("accent", invocationPreview)}`;
|
|
145
|
+
}
|
|
146
|
+
else if (invocationPreview.length > 0) {
|
|
122
147
|
text += ` ${theme.fg("dim", "→")} ${theme.fg("accent", invocationPreview)}`;
|
|
123
148
|
}
|
|
124
149
|
}
|
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
import { LAUNCH_SCOPED_FLAG_LABEL } from "./launch-scoped-flags.js";
|
|
2
|
-
/**
|
|
3
|
-
* Purpose: Provide the canonical agent_browser operating playbook shared by runtime prompt metadata and generated documentation fragments.
|
|
4
|
-
* Responsibilities: Define stable guidance bullets, native tool-call examples, and wrapper-behavior notes without importing runtime/browser process code.
|
|
5
|
-
* Scope: Agent-facing documentation and prompt-guidance text only; command execution and wrapper state behavior live in runtime modules.
|
|
6
|
-
* Usage: Imported by the extension entrypoint for promptGuidelines and by the documentation drift-check script for generated Markdown blocks.
|
|
7
|
-
* Invariants/Assumptions: The native pi tool receives args after the agent-browser binary, stdin is only for batch/eval --stdin/auth save --password-stdin, and wrapper behavior documented here must match implemented behavior.
|
|
8
|
-
*/
|
|
9
2
|
export const PROJECT_RULE_PROMPT = "Project rule: when browser automation is needed, prefer the native `agent_browser` tool. Do not run direct `agent-browser` bash commands unless the user explicitly asks for a bash-oriented workflow or browser-integration debugging.";
|
|
10
3
|
export const TOOL_PROMPT_GUIDELINES_PREFIX = [
|
|
11
4
|
"Use agent_browser for real browser or live web content.",
|
|
@@ -14,37 +7,39 @@ export function buildInstalledDocsGuideline(paths) {
|
|
|
14
7
|
return `For detailed agent_browser docs, read targeted sections: ${paths.readmePath} (setup), ${paths.commandReferencePath} (commands), ${paths.toolContractPath} (result/details). Do not load the full command reference unless needed.`;
|
|
15
8
|
}
|
|
16
9
|
export const QUICK_START_GUIDELINES = [
|
|
17
|
-
`Quick start mental model: use exactly one of args (exact agent-browser CLI args after the binary), semanticAction (a thin shorthand compiled to find argv for locator actions, direct selector/ref click/check/fill, or select argv for native dropdowns), job (a constrained short-workflow schema compiled to batch --bail by default; set failFast:false only when later diagnostics remain safe if an earlier navigation fails), qa (a lightweight fail-fast QA preset built on batch --bail with bounded visible expected-text checks, including qa.attached for current sessions), electron (desktop Electron list/launch/status/cleanup/probe), or the experimental sourceLookup / networkSourceLookup helpers (candidates only; each compiled to batch); stdin is only for batch, eval --stdin, auth save --password-stdin, and wrapper-generated batch stdin from job, qa, sourceLookup, or networkSourceLookup, and is rejected with electron; sessionMode=fresh switches the extension-managed pi-scoped session to a fresh upstream launch when you need new launch-scoped flags (${LAUNCH_SCOPED_FLAG_LABEL}) to apply. Use outputPath for durable eval/get/snapshot captures. Do not pass --json in args; the wrapper injects it.`,
|
|
18
|
-
"
|
|
10
|
+
`Quick start mental model: use exactly one of script (one-shot JavaScript orchestration), args (exact agent-browser CLI args after the binary), semanticAction (a thin shorthand compiled to find argv for locator actions, direct selector/ref click/check/fill, or select argv for native dropdowns), job (a constrained short-workflow schema compiled to batch --bail by default; set failFast:false only when later diagnostics remain safe if an earlier navigation fails), qa (a lightweight fail-fast QA preset built on batch --bail with bounded visible expected-text checks, including qa.attached for current sessions), electron (desktop Electron list/launch/status/cleanup/probe), or the experimental sourceLookup / networkSourceLookup helpers (candidates only; each compiled to batch); stdin is only for batch, eval --stdin, auth save --password-stdin, and wrapper-generated batch stdin from job, qa, sourceLookup, or networkSourceLookup, and is rejected with electron; sessionMode=fresh switches the extension-managed pi-scoped session to a fresh upstream launch when you need new launch-scoped flags (${LAUNCH_SCOPED_FLAG_LABEL}) to apply. Use outputPath for durable eval/get/snapshot captures. Do not pass --json in args; the wrapper injects it.`,
|
|
11
|
+
"Use script only when loops, conditional page branches, or multi-page aggregation would otherwise require several top-level calls: call await browser({ args, stdin?, timeoutMs? }), check each returned { ok, data, error, details, failureCategory, nextActions, resultCategory, summary, text } envelope, and call emit(value) for the one final JSON value. Script runs in a one-shot isolated session without profiles, attachments, imports, host filesystem/network/process access, caller session controls, or inherited agent-browser launch/proxy settings; inner browser calls and their response envelopes are serialized and bounded, compatible suggested next actions can be passed back to browser(), and the wrapper always closes that session. One top-level approval can authorize up to 25 inner calls, so inspect the full source before approval. Use args/job/qa for ordinary linear work.",
|
|
12
|
+
"There is no first-class reusable named browser recipe runtime: script is ad hoc source for one call only, with no saved names, registry, or versioned workflow state. Keep recurring flows in documentation examples, job, qa, or raw batch (closed RQ-0068; see docs/ARCHITECTURE.md#no-reusable-recipe-layer-yet).",
|
|
19
13
|
"Common first calls (first-call recipe): { args: [\"open\", \"<url>\"] } → { args: [\"snapshot\", \"-i\"] } → { args: [\"click\", \"@eN\"] } or { args: [\"fill\", \"@eN\", \"<text>\"] } using @refs and visible labels from that snapshot, then { args: [\"snapshot\", \"-i\"] } after navigation or DOM changes. On https://example.com/ the main link label is Learn more (use exact snapshot text, not guessed link copy).",
|
|
20
14
|
"Locator-first clicks/fills and native select changes without hand-building argv: { semanticAction: { action: \"click\", locator: \"text\", value: \"Close\" } }, { semanticAction: { action: \"fill\", locator: \"label\", value: \"Email\", text: \"user@example.com\" } }, direct current targets such as { semanticAction: { action: \"fill\", selector: \"@e1\", text: \"prompt\" } }, or { semanticAction: { action: \"select\", selector: \"#flavor\", value: \"chocolate\" } }; add semanticAction.session when targeting a named upstream browser session; details.compiledSemanticAction shows the semantic target, while details.effectiveArgs may show a resolved current @ref for active-session role/name click/check/fill actions to avoid hidden duplicate matches; semanticAction does not expose uncheck while upstream find ... uncheck is not runtime-supported, so use raw uncheck with a stable selector or current ref; selector-not-found failures may append bounded click try-*-candidate next actions or, for fill misses with current editable refs, details.richInputRecovery with focus/click actions that do not copy fill text; stale-ref failures can return retry-semantic-action-after-stale-ref for compiled find actions when retry safety is provable.",
|
|
21
15
|
`Common advanced calls: { args: ["batch", "--bail"], stdin: "[[\"open\",\"https://example.com\"],[\"snapshot\",\"-i\"]]" }, { job: { steps: [{ action: "open", url: "https://example.com" }, { action: "assertText", text: "Example Domain" }, { action: "screenshot", path: ".dogfood/example.png" }] } }, { qa: { url: "https://example.com", expectedText: "Example Domain", screenshotPath: ".dogfood/qa-example.png" } } (example.com smoke only; elsewhere match exact visible text from snapshot -i), { electron: { action: "list", query: "code" } }, { electron: { action: "launch", appName: "Visual Studio Code", handoff: "snapshot" } }, { electron: { action: "probe" } }, { qa: { attached: true, expectedText: "Explorer" } }, { args: ["eval", "--stdin"], stdin: "document.title", outputPath: "logs/page-title.json" }, { args: ["auth", "save", "name", "--password-stdin"], stdin: "<password from user-approved secret source>" }, { args: ["--profile", "Default", "open", "https://example.com/account"], sessionMode: "fresh" }, and { args: ["open", "--enable", "react-devtools", "https://example.com"], sessionMode: "fresh" }. For app pages with a native dropdown, job steps can include { action: "select", selector: "#flavor", value: "chocolate" } before the dependent assertion; for locator-friendly pages, job click/fill steps can use semantic locator fields such as { action: "fill", locator: "role", role: "searchbox", name: "Search", text: "agent browser" }; for human-paced input, job type steps can use { action: "type", selector: "#prompt", text: "hello", delayMs: 20, press: "Enter" }; delayed typing is capped at 200 characters per step, and generated per-character rows are compacted in visible batch prose while full rows remain in details.batchSteps.`,
|
|
22
16
|
"Constrained job navigation is explicit only: click (and select/submit flows that may navigate) does not prove the next page loaded; add assertUrl and/or assertText after navigation-prone steps before screenshot or later interactions. Keep jobs short around navigation, click, and rerender boundaries on dynamic React/product apps; avoid a whole checkout in one job. If a long job times out and details.timeoutPartialProgress shows a mutating incomplete step, inspect current page state and continue with a shorter job or single action instead of blindly retrying the mutating step. Example: { job: { steps: [{ action: \"open\", url: \"https://shop.example/checkout\" }, { action: \"fill\", selector: \"#email\", text: \"user@example.com\" }, { action: \"click\", selector: \"#continue\" }, { action: \"assertUrl\", url: \"**/shipping\" }, { action: \"assertText\", text: \"Shipping address\" }, { action: \"screenshot\", path: \".dogfood/shipping.png\" }] } }. Top-level click may add pageChangeSummary hints, but job never auto-inserts post-click asserts.",
|
|
23
17
|
"High-value command reference: click <selector> --new-tab opens link-like targets in a new tab; select <selector> <value...> changes native dropdown values; wrapper-handled scroll <dir> [px|percent] and scroll to end/top target document scrolling before upstream fallback, while scroll <selector> <dir> [px|percent] targets nested scrollers; download <selector> <path> saves a file triggered by a click; read [url] returns agent-readable text (explicit URLs prefer markdown without launching Chrome; omit the URL for rendered active-tab DOM); get title/url need no selector; get text/html/value/count <selector> and get attr <selector> <name> read elements/page state (use body for whole-page text/html); screenshot [selector] [path] captures a page or element image; pdf <path> saves a PDF; tab list and tab <tab-id-or-label> inspect or recover the active tab; react tree, react inspect <fiberId>, react renders start/stop, and react suspense introspect React after --enable react-devtools; vitals [url] measures Core Web Vitals; pushstate <url> performs SPA navigation; tap <selector> and swipe <direction> [distance] support iOS/provider touch flows.",
|
|
24
|
-
"For artifact-producing commands, read the visible artifact block and details.artifactVerification before using files: check requested path, absolute path, existence, size bytes, artifact kind, optional mediaType, status, optional limitation, and verified/missing/pending/unverified counts. details.artifacts contains per-file metadata; record start rows are pending/openRecording until record stop writes the target. The wrapper creates parent directories for direct artifact paths and can save simple loopback HTTP(S) anchor downloads directly to the requested path before upstream download fallback. Browser close does not delete explicit saved files; if close reports details.artifactCleanup, use host file tools to remove paths listed in explicitArtifactPaths (when non-empty) after inspection. If close fails with details.promptGuard.reason=requested-artifacts-missing-before-close, save the exact required artifact path before closing. For annotated screenshots inside batch, put --annotate in top-level args (for example { args: [\"--annotate\", \"batch\"], stdin: \"[[\\\"screenshot\\\",\\\"/tmp/page.png\\\"]]\" }) rather than inside the screenshot step; if annotation labels crowd a dense page, use a scoped or non-annotated screenshot plus snapshot refs instead.",
|
|
18
|
+
"For artifact-producing commands, read the visible artifact block and details.artifactVerification before using files: check requested path, absolute path, existence, size bytes, artifact kind, optional mediaType, status, optional limitation, and verified/missing/pending/unverified counts. details.artifacts contains per-file metadata; record start rows are pending/openRecording until record stop writes the target. Upstream record start uses a fresh active page for video capture, so prior in-page DOM and JavaScript state does not carry over; the wrapper blocks prior @e… refs as stale-ref even when the start fails as already-active, and record restart with a URL navigates and invalidates the same way (plain record restart keeps the page), so take a fresh snapshot before continuing. The wrapper creates parent directories for direct artifact paths and can save simple loopback HTTP(S) anchor downloads directly to the requested path before upstream download fallback. Browser close does not delete explicit saved files; if close reports details.artifactCleanup, use host file tools to remove paths listed in explicitArtifactPaths (when non-empty) after inspection. If close fails with details.promptGuard.reason=requested-artifacts-missing-before-close, save the exact required artifact path before closing. A bare inbound image/video path is not a requested output artifact and does not block close. For annotated screenshots inside batch, put --annotate in top-level args (for example { args: [\"--annotate\", \"batch\"], stdin: \"[[\\\"screenshot\\\",\\\"/tmp/page.png\\\"]]\" }) rather than inside the screenshot step; if annotation labels crowd a dense page, use a scoped or non-annotated screenshot plus snapshot refs instead.",
|
|
25
19
|
"When details.nextActions is present, prefer those exact native agent_browser follow-up payloads over prose guidance; they may include args, stdin, sessionMode, networkSourceLookup, safety notes, or artifactPath for saved files.",
|
|
26
20
|
];
|
|
27
21
|
export const WEB_SEARCH_PROMPT_GUIDELINE = "Use agent_browser_web_search for quick live search/URL discovery; prefer it over public search-engine forms that can hit anti-bot/CAPTCHA-gated pages. Use agent_browser after you have a target URL; one query, one follow-up max; stop on HTTP 429.";
|
|
28
22
|
export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
|
|
23
|
+
"Use top-level script only for one-shot loops, conditional page branches, or multi-page aggregation that would otherwise require several calls: await browser({ args, stdin?, timeoutMs? }), branch on its ok field, and emit one bounded JSON value. Script gets an isolated non-profile browser session that is always closed, cannot use caller session/namespace/lifecycle/attachment controls, inherited agent-browser launch/proxy settings, or host APIs, and is not a reusable named recipe. One top-level approval can authorize up to 25 inner calls, so inspect the full source before approval. Use args/job/qa for ordinary linear flows.",
|
|
29
24
|
"Standard workflow: open the page, snapshot -i, interact using current @refs from that snapshot, and re-snapshot after navigation, scrolling, rerendering, or other major DOM changes because refs are page-scoped; the wrapper fails mutation-prone stale/recycled refs before upstream can silently target a different current-page element. On dense pages, use wrapper-side snapshot -i --search <text> or snapshot -i --filter role=<role> to render matching refs while preserving the full ref map in details.refSnapshot, add snapshot --viewport when scroll position or above/below-fold context matters, and add snapshot --diff when a quick before/after ref-map delta would prevent reading a full spill file.",
|
|
30
25
|
"For ordinary forms from one snapshot, batch multiple fill @refs before the submit/click step to avoid serial tool calls; if a fill may autosubmit, navigate, or rerender later fields, split the flow and refresh refs first.",
|
|
31
26
|
"Do not use browser automation to drive public search-engine forms such as Google for discovery; headless jobs that type a query and press Enter can be redirected to anti-bot or CAPTCHA pages. Use agent_browser_web_search when configured, ask for/search from a direct target URL, or navigate to known result URLs. Do not attempt CAPTCHA bypass.",
|
|
32
|
-
"Snapshot choice: prefer snapshot -i for routine clicks/fills (interactive @refs, main-content-first). Use snapshot --compact when you need a denser same-page tree without full spill; use full snapshot (no -i) only when you need the complete accessibility tree. Re-snapshot after navigation or major DOM changes. When snapshot -i compacts because the tree is oversized, scan visible output for Omitted high-value controls and optional details.data.highValueControlRefIds before opening the spill file: those list bounded searchboxes, textboxes, comboboxes, buttons, tabs, checkboxes, radios, options, and menuitems that did not fit the key/other ref previews.",
|
|
27
|
+
"Snapshot choice: prefer snapshot -i for routine clicks/fills (interactive @refs, main-content-first). Use snapshot --compact when you need a denser same-page tree without full spill; use full snapshot (no -i) only when you need the complete accessibility tree. Re-snapshot after navigation or major DOM changes. When snapshot -i compacts because the tree is oversized, scan visible output for Omitted high-value controls and optional details.data.highValueControlRefIds before opening the spill file: those list bounded searchboxes, textboxes, comboboxes, buttons, named action links, tabs, checkboxes, radios, options, and menuitems that did not fit the key/other ref previews.",
|
|
33
28
|
"When a visible text or accessible-name target should survive ref churn, prefer find locators such as role, text, label, placeholder, alt, title, or testid with the intended action instead of guessing a CSS selector.",
|
|
34
29
|
"For desktop or host-controlled rich inputs, if semanticAction fill misses, refresh refs and prefer a current editable @ref from details.richInputRecovery or the latest snapshot; focus or click that ref, then use keyboard inserttext or keyboard type with the intended text. Do not auto-submit with Enter or a submit button unless the user flow explicitly calls for it.",
|
|
35
30
|
"Do not assume Playwright selector dialects such as text=Close or button:has-text('Close') are supported wrapper syntax unless current upstream agent-browser behavior has been verified.",
|
|
36
|
-
"For authenticated or user-specific content explicitly requested by the user, such as feeds, inboxes, account pages, or private dashboards, use a real profile only when the user/config asks for it or profiles have been inspected; do not assume --profile Default exists on every machine. Do not use a real profile for public pages just because they are dashboards. Treat visible page content from real profiles as model-visible transcript data; use --auto-connect only if profile-based reuse is unavailable or the task is specifically about attaching to a running debug-enabled browser. If profile/user-data-dir resolution fails, stop retrying opens, run profiles and/or doctor through agent_browser, then report what the user needs to configure.",
|
|
31
|
+
"For authenticated or user-specific content explicitly requested by the user, such as feeds, inboxes, account pages, or private dashboards, use a real profile only when the user/config asks for it or profiles have been inspected; do not assume --profile Default exists on every machine. Do not use a real profile for public pages just because they are dashboards. Treat visible page content from real profiles as model-visible transcript data. On macOS, copied Chrome profiles may omit encrypted cookies, so profile selection alone is not proof of authentication; verify the target page and use a user-approved headed login once when needed. Use --auto-connect only if profile-based reuse is unavailable or the task is specifically about attaching to a running debug-enabled browser. If profile/user-data-dir resolution fails, stop retrying opens, run profiles and/or doctor through agent_browser, then report what the user needs to configure.",
|
|
37
32
|
"Do not invent fixed explicit session names for routine tasks. Use the implicit session unless you truly need multiple isolated browser sessions in the same conversation.",
|
|
38
33
|
`When using launch-scoped flags (${LAUNCH_SCOPED_FLAG_LABEL}), put them on the first command for that session. If you intentionally use an explicit --session, keep using that same explicit session for follow-ups.`,
|
|
39
34
|
"Caller-owned explicit sessions are serialized per effective canonical namespace/session inside this extension while live URL checks, semantic-action snapshots, and the requested command run. For raw batches whose later content step depends on navigation, use exact batch --bail or split the calls; unsafe continue-after-navigation-failure shapes are rejected before the batch runs.",
|
|
40
|
-
"After a successful `connect`, `--cdp`, or enabled `--auto-connect` call, verify with get url and keep using the resulting session without repeating the attach flag. The wrapper remembers that attachment across active-branch reload/resume, omits local-launch-only `--args` / `--allow-file-access` defaults from follow-up and cleanup subprocesses so upstream keeps the existing CDP connection, and live-checks the URL before later page reads/interactions because an attached browser can drift externally; a successful close clears the marker. First-use attach plus content calls are blocked until the URL is verified.",
|
|
35
|
+
"After a successful `connect`, `--cdp`, or enabled `--auto-connect` call, verify with get url and keep using the resulting session without repeating the attach flag. The wrapper remembers that attachment across active-branch reload/resume, omits local-launch-only `--args` / `--allow-file-access` defaults from follow-up and cleanup subprocesses so upstream keeps the existing CDP connection, and live-checks the URL before later page reads/interactions because an attached browser can drift externally; a successful close clears the marker. First-use attach plus content calls are blocked until the URL is verified. When several named sessions share one Chrome, pass --pin-tab once (AGENT_BROWSER_PIN_TAB) so a closed bound tab fails as tab_gone instead of acting on a neighbor; recover with tab new or tab list. --no-pin-tab turns the sticky pin off. tab list includes each tab's CDP targetId, accepted as a tab ref.",
|
|
41
36
|
`If you already used the implicit session and now need launch-scoped flags (${LAUNCH_SCOPED_FLAG_LABEL}), retry with top-level sessionMode set to fresh or pass an explicit --session for the new launch; never pass --session-mode inside args. After a successful unnamed fresh launch, later auto calls follow that new session.`,
|
|
42
37
|
"For WebGPU pages, use args [\"--webgpu\", \"open\", \"<url>\"] on a fresh local browser launch; use doctor --webgpu (or --headed on Linux/Windows capture paths) to prove rendering before trusting a non-black screenshot. WebGPU cannot be combined with --cdp, --auto-connect, or provider launches unless --webgpu false overrides an enabled config/environment default.",
|
|
43
38
|
"For --allowed-domains, use a fresh local Chrome context. Upstream rejects CDP/auto-connect, profiles, restore/state replay, direct-page providers, iOS/Safari, and startup/profile Chrome args because they cannot guarantee containment; Chromium also disables RTCPeerConnection while the allowlist is active.",
|
|
44
39
|
"For React introspection, launch the page with --enable react-devtools before first navigation, then use react tree, react inspect <fiberId>, sourceLookup candidates for local UI source hints, react renders start/stop, or react suspense; sourceLookup is experimental and reports confidence/evidence instead of guaranteed DOM-to-file mappings. For failed fetches and APIs, networkSourceLookup (experimental) correlates failed network requests with initiator metadata and bounded workspace URL literals—candidates only, not definitive blame. Use vitals [url] for Core Web Vitals and hydration timing, and pushstate <url> for client-side SPA navigation.",
|
|
45
40
|
"For first-navigation setup, use open without a URL plus network route --resource-type <csv>, cookies set --curl <file>, or --init-script/--enable before navigate/opening the target page.",
|
|
46
41
|
"For stateful browser context work, prefer purpose-specific page actions before dumping browser data: use auth save --password-stdin with the tool stdin field for credentials, auth list/show/delete/remove for local auth-profile maintenance, auth login when you need the browser to fill a saved profile, state save/load for portable test state, state list/show/rename and targeted `state clear <caller-owned-name>` for saved-state lifecycle cleanup (the native wrapper blocks broad clear/clean and managed targets), cookies get/set/clear and storage local|session only when the task needs those values, and expect cookie/storage/auth/state summaries to redact credential-like fields while allowing benign primitive storage values when useful for local QA.",
|
|
47
|
-
"Upstream restore sessions periodically autosave cookies and localStorage while the browser stays open, including page-driven background changes; AGENT_BROWSER_AUTOSAVE_INTERVAL_MS controls the interval (30000 by default; 0 disables periodic saves but keeps save-on-close), while the never value for --restore-save disables automatic saves for that restore session.",
|
|
42
|
+
"Upstream restore sessions periodically autosave cookies and localStorage while the browser stays open, including page-driven background changes; AGENT_BROWSER_AUTOSAVE_INTERVAL_MS controls the interval (30000 by default; 0 disables periodic saves but keeps save-on-close), while the never value for --restore-save disables automatic saves for that restore session. For wrapper-owned headed launches, the wrapper defaults the interval to 0 because upstream 0.33.2 collects multi-origin storage through visible temporary tabs, then records and reapplies the effective launch-time value to helpers and follow-ups so daemon configuration remains stable. Native close still saves, but direct window close can lose newer state because upstream exempts headed browsers from idle shutdown; set AGENT_BROWSER_AUTOSAVE_INTERVAL_MS before launch when periodic preservation matters, because changing it on a running wrapper-owned headed session requires close plus a fresh launch.",
|
|
48
43
|
"For batch chains that touch cookies, storage, auth, or other secret-bearing commands, use details.batchSteps for per-step artifacts, categories, spill paths, and full structured errors; top-level details.data on batch is only a compact redacted step matrix (success, argv-redacted command, redacted result or scrubbed error text) built from the same presentation rules as standalone calls.",
|
|
49
44
|
"For non-core families, pass current upstream commands through the native tool directly: network requests, network route <url>, network har start/stop [path], diff snapshot, diff screenshot --baseline <file>, diff url <u1> <u2>, trace start, trace stop [path], profiler start, profiler stop [path], record start <path>, record stop, console/errors [--clear], highlight <selector>, inspect, clipboard read, clipboard write <text>, clipboard copy/paste, stream enable/disable/status, dashboard start/stop, device list for iOS simulator inventory, and chat <message>. For compact network requests output, prefer details.nextActions for request detail, route-mock diagnostics, actionable failed-request networkSourceLookup, filtering, clearing the aggregate buffer before repro, or HAR capture follow-ups instead of guessing request-id syntax. Artifact-producing commands report details.artifacts and verification state; long-running starts such as stream, dashboard, trace/profiler, and record should be paired with the matching stop/disable command when the task is done; stream enable already-enabled outcomes are treated as idempotent success with status/disable follow-ups.",
|
|
50
45
|
"For Electron desktop apps, prefer top-level electron for wrapper-owned discovery, isolated launch, status, compact probe, and cleanup: list first, treat likely-sensitive annotations as hints rather than enforcement, launch with the default snapshot handoff unless handoff: \"tabs\" is the safer diagnostic starting point, use electron.probe or snapshot -i/qa.attached for current-session state, and always cleanup the returned launchId when done. electron.launch uses an isolated temporary profile; it does not reuse the app's normal signed-in profile or attach to an already-running authenticated app. For signed-in local app state, host-launch the normal app with --remote-debugging-port when appropriate, then use raw args connect <port|url>; after connect, run get url to verify the active target before page-content reads, inspect tab list, select the stable tab id such as tab t2, verify it again with get url, then run a condition wait or snapshot -i before using refs. close commands (`close`, `quit`, or `exit`) only close the browser/CDP session; leave manually launched app shutdown, profile cleanup, and explicit artifacts to the host owner.",
|
|
@@ -56,18 +51,18 @@ export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
|
|
|
56
51
|
"For downloads, prefer download <selector> <path> when an element click should save a file; simple loopback anchor downloads are saved to the requested path when the wrapper can resolve an HTTP(S) href. Do not rely on click alone when you need the downloaded file on disk.",
|
|
57
52
|
"On dashboards with nested scroll containers, verify scroll with a screenshot or fresh snapshot -i; if the viewport did not move, details.data.scrolled may be false/noMovement true and you should prefer scrollintoview <@ref> or target the actual scrollable region with scroll <selector> <dir> [px|percent]. For native selects, use select <selector> <value...> (or semanticAction/job select) instead of clicking option refs; for custom comboboxes, a click/semanticAction may only focus the field, so re-snapshot and fall back to type, press Enter/arrow keys, or visible option refs.",
|
|
58
53
|
"When using eval --stdin, scope checks and actions to the target element or route whenever possible instead of relying on broad page-wide text heuristics.",
|
|
59
|
-
"When using eval --stdin for extraction, pass the JavaScript through the native tool stdin field, not as an extra args token after --stdin, and return the value you want instead of relying on console.log as the primary result channel. Prefer plain expressions like ({ title: document.title }) or explicitly invoked functions like (() => ({ title: document.title }))(); use outputPath when the eval/get/snapshot data should be saved as a durable local file. If a function-shaped snippet returns {}, details.evalStdinHint may warn that the function was serialized instead of called. The native wrapper blocks follow-up inspection, scripting, and interaction on file:// pages to protect authenticated local browser state; use a reachable HTTP(S) fixture instead. If get text on a broad CSS selector surfaces details.selectorTextVisibility or selectorTextVisibilityAll, prefer a visible @ref, a more specific selector, or the inspect-visible-text-candidates nextAction over hidden tab content.",
|
|
54
|
+
"When using eval --stdin for extraction, pass the JavaScript through the native tool stdin field, not as an extra args token after --stdin, and return the value you want instead of relying on console.log as the primary result channel. Prefer plain expressions like ({ title: document.title }) or explicitly invoked functions like (() => ({ title: document.title }))(); use outputPath when the eval/get/snapshot data should be saved as a durable local file, but never reuse a screenshot, download, recording, or other browser artifact destination as outputPath. If a function-shaped snippet returns {}, details.evalStdinHint may warn that the function was serialized instead of called. The native wrapper blocks follow-up inspection, scripting, and interaction on file:// pages to protect authenticated local browser state; use a reachable HTTP(S) fixture instead. If get text on a broad CSS selector surfaces details.selectorTextVisibility or selectorTextVisibilityAll, prefer a visible @ref, a more specific selector, or the inspect-visible-text-candidates nextAction over hidden tab content.",
|
|
60
55
|
"When details.pageChangeSummary is present, use changeType and summary as a compact signal for navigation, DOM mutation, confirmations, or artifacts; when nextActionIds is set, match those ids to entries in details.nextActions (or per-step nextActions inside batch) for concrete follow-up payloads instead of inferring from prose alone. If details.clickDispatch reports a click-dispatch miss, refresh/inspect/retry the real click first; for static local fixtures only, an explicit eval --stdin programmatic .click() can exercise app handlers, but treat it as an untrusted scripted workaround and never use it to bypass stop-before-submit/order/purchase boundaries. If a no-navigation click surfaces details.overlayBlockers, inspect the fresh snapshot evidence before using a close/dismiss candidate nextAction; ordinary page chrome without dialog/alertdialog evidence should not trigger this diagnostic.",
|
|
61
56
|
"When commands save or spill files (screenshots, downloads, PDFs, traces, recordings, HAR, large snapshot spills), use the user's exact requested paths when given and treat paths as provisional until details.artifactVerification shows every row verified: branch on missingCount, pendingCount, unverifiedCount, per-entry state, and optional limitation before downstream file use or PASS/FAIL reporting.",
|
|
62
57
|
"For evidence-only screenshots, QA captures, or other audit artifacts, save to an explicit path and branch on details.artifactVerification plus details.artifacts before reporting PASS/FAIL; do not require vision review of inline image attachments unless the user asked for visual inspection.",
|
|
63
|
-
"Respect explicit user stop boundaries yourself
|
|
58
|
+
"Respect explicit user stop boundaries yourself. When the surrounding authenticated employee or automation context is explicitly unattended/auto-approved, ordinary non-destructive form submissions within the requested flow may proceed without separate confirmation. Still require explicit authorization for purchases, production-control actions, destructive or irreversible actions, and account, security, or privacy changes. The wrapper does not infer broad business intent from prompt text; details.promptGuard is reserved for concrete artifact-before-close checks.",
|
|
64
59
|
"Successful record stop needs ffmpeg on PATH; the wrapper may warn after record start when ffmpeg is missing.",
|
|
65
60
|
"Do not call --help or other exploratory inspection commands unless the user explicitly asks for them or debugging the browser integration is necessary.",
|
|
66
61
|
];
|
|
67
62
|
export const TOOL_PROMPT_GUIDELINES_SUFFIX = [
|
|
68
63
|
"Prefer agent_browser over bash, osascript, AppleScript, or generic browser shell for sites, docs, clicks, fills, screenshots, eval, and batch.",
|
|
69
|
-
"Pass exact agent-browser CLI arguments in agent_browser args when you are not using semanticAction, job, or qa, excluding the binary name and --json (agent_browser injects --json automatically).",
|
|
70
|
-
"Use agent_browser stdin only for eval --stdin, batch, auth save --password-stdin, or wrapper-generated job/qa batches instead of shell heredocs or password args; other command/stdin combinations are rejected before launch.",
|
|
64
|
+
"Pass exact agent-browser CLI arguments in agent_browser args when you are not using script, semanticAction, job, or qa, excluding the binary name and --json (agent_browser injects --json automatically).",
|
|
65
|
+
"Use top-level agent_browser stdin only for eval --stdin, batch, auth save --password-stdin, or wrapper-generated job/qa batches instead of shell heredocs or password args; script puts any inner stdin on browser({ stdin }), and other command/stdin combinations are rejected before launch.",
|
|
71
66
|
`Let the agent_browser extension-managed session handle the common path unless you explicitly need a fresh launch for launch-scoped flags (${LAUNCH_SCOPED_FLAG_LABEL}).`,
|
|
72
67
|
"Use agent_browser sessionMode=fresh when switching from an existing implicit session to a new profile/browser executable/debug/init-script/provider launch without inventing a fixed explicit session name; later auto calls will follow that new session.",
|
|
73
68
|
];
|
|
@@ -80,15 +75,16 @@ export const WRAPPER_TAB_RECOVERY_BEHAVIOR = [
|
|
|
80
75
|
"After the wrapper observes tab-drift risk for a session (for example open correction, overlapping stale opens, or resumed session state), later active-tab commands best-effort pin that tab inside the same upstream invocation. Routine same-session commands are not preflighted with tab list just because a target tab or ref snapshot is known.",
|
|
81
76
|
"For sessions with observed tab-drift risk, after a successful command on a known target tab, agent_browser also best-effort restores that intended tab if a restored/background tab steals focus after the command completes. Routine same-session commands skip this post-command tab-list probe.",
|
|
82
77
|
"If a known session target unexpectedly reports about:blank, agent_browser best-effort re-selects the prior intended target when it still exists; if recovery fails, it records the observed about:blank target and reports exact recovery guidance instead of treating the prior page as active.",
|
|
78
|
+
"If upstream reports tab_gone, the pinned bound tab is gone; use details.nextActions (tab list / tab new) instead of assuming another tab is yours.",
|
|
83
79
|
];
|
|
84
80
|
/** Tier A: always-on tool promptGuidelines (keep small; Tier B lives in SHARED_BROWSER_PLAYBOOK_GUIDELINES and docs). */
|
|
85
81
|
export const RUNTIME_PROMPT_GUIDELINES = [
|
|
86
|
-
"Use agent_browser with one input mode: args, semanticAction, job, qa, sourceLookup/networkSourceLookup, or electron. stdin
|
|
87
|
-
"For agent_browser, use open → snapshot -i →
|
|
88
|
-
"Use agent_browser sessionMode=fresh for launch
|
|
89
|
-
"
|
|
90
|
-
"When agent_browser details.nextActions exists, use exact payloads
|
|
91
|
-
"
|
|
82
|
+
"Use agent_browser with one input mode: script, args, semanticAction, job, qa, sourceLookup/networkSourceLookup, or electron. stdin: batch/eval/auth/wrapper batch only; electron rejects it; never pass --json.",
|
|
83
|
+
"For agent_browser, use open → snapshot -i → @refs; re-snapshot after changes. In authenticated unattended/auto-approved employee flows, ordinary requested non-destructive submissions may proceed. Honor explicit stops; require explicit authorization for purchases, production-control, destructive/irreversible, or account/security/privacy changes.",
|
|
84
|
+
"Use agent_browser sessionMode=fresh for launch flags. Use requested/configured profiles only; run profiles/doctor on failure. --allowed-domains cannot restore; macOS profile copies may omit encrypted cookies. Verify auth; use a user-approved headed login if needed. Profile content is model-visible.",
|
|
85
|
+
"agent_browser: exact user paths; verify artifactVerification/artifacts before success claims. Save promptGuard-required files before close; record stop needs ffmpeg; close keeps files; waited:timeout proves nothing.",
|
|
86
|
+
"When agent_browser details.nextActions exists, use exact payloads. Check Omitted high-value controls in dense snapshots. Dashboards: verify scroll with screenshot/snapshot.",
|
|
87
|
+
"agent_browser: read <url> for docs/text or active DOM; get title/url; get text/html/value/count <selector>; get attr <selector> <name>. Batch 3+ getters; heed visibility warnings.",
|
|
92
88
|
];
|
|
93
89
|
export function buildBrowserExecutablePathGuideline(executablePath) {
|
|
94
90
|
if (!executablePath)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
const isolatedAgentBrowserEnvironment = new AsyncLocalStorage();
|
|
3
|
+
const PROXY_ENV_NAMES = new Set(["ALL_PROXY", "HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY"]);
|
|
4
|
+
export function getAgentBrowserProcessEnvironment(baseEnv = process.env) {
|
|
5
|
+
if (isolatedAgentBrowserEnvironment.getStore() !== true)
|
|
6
|
+
return baseEnv;
|
|
7
|
+
return Object.fromEntries(Object.entries(baseEnv).filter(([name]) => {
|
|
8
|
+
const normalizedName = name.toUpperCase();
|
|
9
|
+
return !normalizedName.startsWith("AGENT_BROWSER_") && !PROXY_ENV_NAMES.has(normalizedName);
|
|
10
|
+
}));
|
|
11
|
+
}
|
|
12
|
+
export function withIsolatedAgentBrowserEnvironment(run) {
|
|
13
|
+
return isolatedAgentBrowserEnvironment.run(true, run);
|
|
14
|
+
}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Describe the native command used to read a process start identity for PID-reuse-safe ownership checks.
|
|
3
|
-
* Responsibilities: Keep POSIX and native-Windows process identity probes aligned across policy locks and temp cleanup.
|
|
4
|
-
* Scope: Command construction, output normalization, and cached async lookup for the current process.
|
|
5
|
-
*/
|
|
6
1
|
import { execFile } from "node:child_process";
|
|
7
|
-
import { win32 } from "node:path";
|
|
2
|
+
import { dirname, join, win32 } from "node:path";
|
|
8
3
|
const WINDOWS_PROCESS_START_IDENTITY_PREFIX = "win32-powershell-ticks-v1:";
|
|
9
|
-
const PROCESS_START_IDENTITY_TIMEOUT_MS =
|
|
4
|
+
const PROCESS_START_IDENTITY_TIMEOUT_MS = 5_000;
|
|
10
5
|
const DEFAULT_WINDOWS_SYSTEM_ROOT = "C:\\Windows";
|
|
11
6
|
export function buildProcessStartIdentityCommand(pid, platform = process.platform) {
|
|
12
7
|
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
@@ -27,7 +22,7 @@ export function buildProcessStartIdentityCommand(pid, platform = process.platfor
|
|
|
27
22
|
}
|
|
28
23
|
: {
|
|
29
24
|
args: ["-p", String(pid), "-o", "lstart="],
|
|
30
|
-
file: "/bin/ps",
|
|
25
|
+
file: platform === "android" ? join(dirname(process.execPath), "ps") : "/bin/ps",
|
|
31
26
|
};
|
|
32
27
|
}
|
|
33
28
|
export function buildProcessStartIdentityCommands(pid, platform = process.platform) {
|
|
@@ -36,7 +31,7 @@ export function buildProcessStartIdentityCommands(pid, platform = process.platfo
|
|
|
36
31
|
return [];
|
|
37
32
|
return platform === "win32"
|
|
38
33
|
? [primary]
|
|
39
|
-
: [primary, { ...primary, file: "/usr/bin/ps" }];
|
|
34
|
+
: [primary, ...(platform === "android" ? [{ ...primary, file: "/bin/ps" }, { ...primary, file: "/usr/bin/ps" }] : [{ ...primary, file: "/usr/bin/ps" }])];
|
|
40
35
|
}
|
|
41
36
|
export function normalizeProcessStartIdentity(stdout) {
|
|
42
37
|
return stdout.trim().replace(/\s+/g, " ") || undefined;
|