pi-agent-browser-native 0.8.2 → 0.8.3
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 +23 -0
- package/README.md +16 -7
- package/dist/extensions/agent-browser/index.js +430 -157
- package/dist/extensions/agent-browser/lib/browser-journal.js +418 -0
- package/dist/extensions/agent-browser/lib/browser-session-conversion.js +381 -0
- package/dist/extensions/agent-browser/lib/browser-transcript.js +119 -19
- package/dist/extensions/agent-browser/lib/input-modes/job.js +49 -2
- package/dist/extensions/agent-browser/lib/input-modes/params.js +32 -35
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +25 -5
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +20 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +97 -78
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +23 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +52 -17
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +13 -8
- package/dist/extensions/agent-browser/lib/playbook.js +9 -2
- package/dist/extensions/agent-browser/lib/prompt-policy.js +0 -14
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +2 -1
- package/dist/extensions/agent-browser/lib/session-page-state.js +95 -157
- package/dist/extensions/agent-browser/lib/temp.js +14 -6
- package/dist/extensions/agent-browser/lib/tool-surface.js +12 -23
- package/dist/extensions/agent-browser/lib/web-search.js +0 -8
- package/docs/ARCHITECTURE.md +13 -11
- package/docs/COMMAND_REFERENCE.md +9 -7
- package/docs/RELEASE.md +3 -3
- package/docs/REQUIREMENTS.md +1 -1
- package/docs/SESSION_STORAGE.md +60 -0
- package/docs/SUPPORT_MATRIX.md +8 -5
- package/docs/TOOL_CONTRACT.md +17 -14
- package/package.json +9 -5
- package/scripts/build.mjs +4 -3
- package/scripts/convert-browser-session.mjs +22 -0
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import { stat } from "node:fs/promises";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { AGENT_BROWSER_SCRIPT_FINAL_OUTPUT_MAX_BYTES, createAgentBrowserScriptCloseArgs, isAgentBrowserScriptSessionName, } from "../input-modes/script.js";
|
|
3
4
|
import { isRecord } from "../parsing.js";
|
|
5
|
+
import { appendBrowserTransition, getBrowserRecord } from "../browser-transcript.js";
|
|
4
6
|
import { redactSensitiveText } from "../runtime.js";
|
|
5
7
|
import { attachInlineImage } from "../results/presentation/artifacts.js";
|
|
6
8
|
import { projectAgentBrowserObservation } from "../results/presentation/content.js";
|
|
7
9
|
import { redactPresentationData } from "../results/presentation/diagnostics.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export function getScriptSessionLeasesFromBranch(branch) {
|
|
10
|
+
// The offline converter retains outstanding isolated-session cleanup facts in canonical events.
|
|
11
|
+
export function getScriptSessionLeasesFromBranch(branch, ownerSessionId) {
|
|
11
12
|
const leases = new Map();
|
|
12
13
|
for (const entry of branch) {
|
|
13
|
-
|
|
14
|
+
const lease = getBrowserRecord(entry)?.event.state.scriptLease;
|
|
15
|
+
if (!isRecord(lease))
|
|
14
16
|
continue;
|
|
15
|
-
|
|
17
|
+
if (ownerSessionId !== undefined && lease.ownerSessionId !== ownerSessionId)
|
|
18
|
+
continue;
|
|
19
|
+
const { cleanup, closeCommandArgs, launchAttempted, sessionName } = lease;
|
|
16
20
|
if (!isAgentBrowserScriptSessionName(sessionName))
|
|
17
21
|
continue;
|
|
18
22
|
const expected = createAgentBrowserScriptCloseArgs(sessionName);
|
|
@@ -20,12 +24,13 @@ export function getScriptSessionLeasesFromBranch(branch) {
|
|
|
20
24
|
|| !Array.isArray(closeCommandArgs) || closeCommandArgs.length !== expected.length
|
|
21
25
|
|| !closeCommandArgs.every((token, index) => token === expected[index]))
|
|
22
26
|
continue;
|
|
23
|
-
leases.set(sessionName, { cleanup, closeCommandArgs: expected, launchAttempted: true, sessionName });
|
|
27
|
+
leases.set(sessionName, { cleanup, closeCommandArgs: expected, launchAttempted: true, sessionName, ownerSessionId: typeof lease.ownerSessionId === "string" ? lease.ownerSessionId : undefined });
|
|
24
28
|
}
|
|
25
29
|
return leases;
|
|
26
30
|
}
|
|
27
|
-
export function appendScriptSessionLease(pi, sessionName, cleanup) {
|
|
28
|
-
pi
|
|
31
|
+
export function appendScriptSessionLease(pi, sessionName, cleanup, ownerSessionId) {
|
|
32
|
+
appendBrowserTransition(pi, { event: { version: 1, phase: "state", operationId: randomUUID(), toolCallId: "cleanup", commandIndex: 0, isError: cleanup !== "closed",
|
|
33
|
+
state: { scriptLease: { cleanup, closeCommandArgs: createAgentBrowserScriptCloseArgs(sessionName), launchAttempted: true, sessionName, ownerSessionId } } } });
|
|
29
34
|
}
|
|
30
35
|
export function createBrowserCodeOutput() {
|
|
31
36
|
const images = new Map();
|
|
@@ -12,7 +12,7 @@ export const QUICK_START_GUIDELINES = [
|
|
|
12
12
|
"Use { args: [\"batch\", \"--bail\"], stdin: \"[[\\\"fill\\\",\\\"@e1\\\",\\\"text\\\"],[\\\"click\\\",\\\"@e2\\\"]]\" } for same-snapshot form fields followed by submit. Split if a fill rerenders later fields. Add native condition waits after navigation-prone actions; a click alone does not prove the destination loaded. Avoid blind mutation retries after timeout.",
|
|
13
13
|
"agent_browser_code takes { code, session?, namespace?, timeoutMs?, outputPath? }. JavaScript globals are fresh for every call; the selected browser persists and is shared with agent_browser. Use await browser({ args, stdin?, timeoutMs? }), inspect result.success, and emit selected JSON instead of whole envelopes. Use emitImage(result.imageObservations[0]) only when image inspection is needed; image handles belong to the current code call. No imports or host filesystem/network/process APIs are exposed.",
|
|
14
14
|
"Example agent_browser_code: { code: 'const result = await browser({args:[\"get\",\"title\"]}); if (!result.success) { emit(result); } else { emit({title:result.data}); }' }. Native batch still avoids extra CLI processes for fixed segments inside code. Explicit session/namespace controls select the browser for this cell without changing the default browser.",
|
|
15
|
-
"Call agent_browser_tools with no fields for inventory, or { enable: [\"action\", \"qa\", \"electron\", \"source\", \"network\"] } for advanced native tools. It adds agent_browser_action, agent_browser_qa, agent_browser_electron, agent_browser_source, and agent_browser_network_source without disabling other tools.
|
|
15
|
+
"Call agent_browser_tools with no fields for inventory, or { enable: [\"action\", \"qa\", \"electron\", \"source\", \"network\"] } for advanced native tools. It adds agent_browser_action, agent_browser_qa, agent_browser_electron, agent_browser_source, and agent_browser_network_source without disabling other tools. The browser instructions cover these capabilities; activation reveals their selected tool declarations.",
|
|
16
16
|
"Use read <url> for readable web text; get title/url, get text/html/value/count <selector>, or get attr <selector> <name> for targeted state. For eval --stdin, put JavaScript in stdin and return the desired value. outputPath saves result data and must differ from screenshot/download/recording paths. Use --json only when JSON text is needed.",
|
|
17
17
|
"For artifacts, verify artifactVerification and artifacts before reporting success. A pending recording is not verified output; install ffmpeg before starting and pair start with stop. Native download owns the click and saves Blob/redirect downloads. Close keeps explicitly saved files. Save promptGuard-required paths before close. Screenshot pixels are not browser CSS coordinates: use imageObservations geometry (dimensions, viewport, crop/origin, scroll and DPR) before any visual-coordinate action; full-page and element captures are not current-viewport coordinates, and Pi resizing only maps sent pixels to original image pixels.",
|
|
18
18
|
"Follow model-visible nextActions when present, including identity and stdin, instead of guessing commands. Treat native pending confirmations and unknown mutation outcomes as inspection boundaries. For dense snapshots, inspect Omitted high-value controls before reading a full spill.",
|
|
@@ -38,6 +38,13 @@ export const ADVANCED_TOOL_PROMPT_GUIDELINES = {
|
|
|
38
38
|
],
|
|
39
39
|
};
|
|
40
40
|
export const WEB_SEARCH_PROMPT_GUIDELINE = "Prefer agent_browser_web_search for current or external web facts and URL discovery over public search-engine forms that can hit anti-bot/CAPTCHA-gated pages. For research before implementation, pass searchType: deep-lite unless webSearch.defaultSearchType already does; omit it for everyday lookups so config/auto wins. Provider rank is not proof of authority: when correctness or version matters, prefer the vendor or project's primary current docs, inspect page-date and version clues, and constrain one follow-up after discovering the official domain (Exa includeDomains; Brave site: in query). Do not count URL aliases as independent sources. Use agent_browser after you have a target URL that needs interaction, screenshots, or DOM inspection.";
|
|
41
|
+
export const WEB_SEARCH_TOOL_PROMPT_GUIDELINES = [
|
|
42
|
+
WEB_SEARCH_PROMPT_GUIDELINE,
|
|
43
|
+
"agent_browser_web_search chooses Exa or Brave from configured keys; when both are available, Exa is preferred by default unless webSearch.preferredProvider says otherwise. Use provider only when the user/config calls for a specific provider.",
|
|
44
|
+
"Use Exa deep only when deep-lite may miss angles, and deep-reasoning only for exhaustive or still-thin research. Do not run parallel agent_browser_web_search calls; make one high-signal query, inspect its results, then at most one follow-up.",
|
|
45
|
+
"If agent_browser_web_search returns HTTP 429, stop searching and tell the user the API plan/rate limit needs time or a plan change.",
|
|
46
|
+
"After using agent_browser_web_search, cite result URLs in the final answer when web evidence informed the answer.",
|
|
47
|
+
];
|
|
41
48
|
export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
|
|
42
49
|
QUICK_START_GUIDELINES[0],
|
|
43
50
|
QUICK_START_GUIDELINES[3],
|
|
@@ -96,7 +103,7 @@ export const WRAPPER_TAB_RECOVERY_BEHAVIOR = [
|
|
|
96
103
|
"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.",
|
|
97
104
|
"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.",
|
|
98
105
|
];
|
|
99
|
-
/**
|
|
106
|
+
/** Concise orientation included alongside the full operating playbook. */
|
|
100
107
|
export const RUNTIME_PROMPT_GUIDELINES = [
|
|
101
108
|
"Use agent_browser for one native command; batch --bail with JSON-array stdin for fixed sequences; agent_browser_code for loops/branches/aggregation. Return to the model at judgment boundaries. Use agent_browser_tools to discover/enable action, qa, electron, source, or network tools.",
|
|
102
109
|
"agent_browser_code runs fresh JavaScript against the persistent browser: await browser({args, stdin?, timeoutMs?}), check result.success, emit(selected JSON), or emitImage(result.imageObservations[0]). No host APIs/imports; globals do not persist. Explicit session/namespace selects this cell's browser without changing the default.",
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
const BROWSER_PROMPT_PATTERNS = [
|
|
2
|
-
/\b(?:agent[_ -]?browser|browser automation|eval\s+--stdin|screenshot|snapshot|tab\s+list)\b/i,
|
|
3
|
-
/\b(?:react\s+(?:tree|inspect|renders|suspense)|web\s+vitals|core\s+web\s+vitals|pushstate)\b/i,
|
|
4
|
-
/\b(?:live\s+docs?|online\s+research|research\s+(?:online|the\s+web)|search\s+(?:online|the\s+web)|web\s+research)\b/i,
|
|
5
|
-
/\bbrowser\b.*\b(?:automation|click|fill|navigate|open|page|screenshot|site|snapshot|tab|url|visit|web(?:site| page)?)\b/i,
|
|
6
|
-
/\b(?:browse|click|fill|login|navigate|open|visit)\b.*\b(?:https?:\/\/\S+|page|site|tab|url|web(?:site| page)?)\b/i,
|
|
7
|
-
];
|
|
8
1
|
const LEGACY_BASH_ALLOW_PATTERNS = [
|
|
9
2
|
/\b(?:bash-oriented workflow|bash workflow)\b/i,
|
|
10
3
|
/\b(?:use|via|through|with)\s+bash\b/i,
|
|
@@ -220,13 +213,6 @@ function getMessageText(content) {
|
|
|
220
213
|
.filter((text) => text.length > 0)
|
|
221
214
|
.join("\n");
|
|
222
215
|
}
|
|
223
|
-
export function shouldAppendBrowserSystemPrompt(prompt) {
|
|
224
|
-
const normalizedPrompt = prompt.trim();
|
|
225
|
-
if (normalizedPrompt.length === 0) {
|
|
226
|
-
return false;
|
|
227
|
-
}
|
|
228
|
-
return BROWSER_PROMPT_PATTERNS.some((pattern) => pattern.test(normalizedPrompt));
|
|
229
|
-
}
|
|
230
216
|
export function getLatestUserPrompt(branch) {
|
|
231
217
|
for (let index = branch.length - 1; index >= 0; index -= 1) {
|
|
232
218
|
const entry = branch[index];
|
|
@@ -101,7 +101,8 @@ export async function renderAgentBrowserObservation(options) {
|
|
|
101
101
|
let spill;
|
|
102
102
|
let spillError;
|
|
103
103
|
try {
|
|
104
|
-
|
|
104
|
+
const write = () => writeLargeOutputSpillFile({ data: { ...observation, ...(!options.json ? { text: prose } : {}) }, persistentArtifactStore: options.persistentArtifactStore, text });
|
|
105
|
+
spill = await (options.withArtifactWrite ? options.withArtifactWrite(write) : write());
|
|
105
106
|
artifactManifest = applyArtifactManifest({ content: [], summary: "" }, artifactManifest, buildSpillArtifactEntries({
|
|
106
107
|
commandInfo: { command: typeof options.details.command === "string" ? options.details.command : undefined },
|
|
107
108
|
evictedArtifacts: spill.evictedArtifacts, path: spill.path, storageScope: spill.storageScope,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { extractUpstreamCommandTokens } from "./argv-descriptor.js";
|
|
2
3
|
import { getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace } from "./argv-grammar.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { isCloseAllCommand, isCloseCommand, isOpenNavigationCommand, isReadOnlyDiagnosticSessionTargetCommand, isRecordPageTransitionCommand, isUnverifiedPageTransitionCommand, isWebMcpPageMutationCommand, isWindowOrDiffPageTransitionCommand } from "./command-taxonomy.js";
|
|
4
|
+
import { getBrowserRecord, snapshotFromDefinition } from "./browser-transcript.js";
|
|
5
|
+
import { isCloseCommand, isOpenNavigationCommand, isReadOnlyDiagnosticSessionTargetCommand, isRecordPageTransitionCommand, isUnverifiedPageTransitionCommand, isWebMcpPageMutationCommand, isWindowOrDiffPageTransitionCommand } from "./command-taxonomy.js";
|
|
6
6
|
import { isRecord } from "./parsing.js";
|
|
7
7
|
import { findReadConfirmation as findPendingReadConfirmation, parseReadConfirmation } from "./read-confirmation.js";
|
|
8
8
|
import { getEditableRefEvidence } from "./results/editable-ref-evidence.js";
|
|
@@ -148,43 +148,6 @@ export function deriveSessionTabTarget(options) {
|
|
|
148
148
|
return observedTarget ?? options.previousTarget;
|
|
149
149
|
return undefined;
|
|
150
150
|
}
|
|
151
|
-
function batchContainsOnlyReadOnlyDiagnosticTargets(data) {
|
|
152
|
-
if (!Array.isArray(data) || data.length === 0) {
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
|
-
return data.every((item) => {
|
|
156
|
-
if (!isRecord(item))
|
|
157
|
-
return false;
|
|
158
|
-
const [command, subcommand] = extractBatchResultCommand(item);
|
|
159
|
-
return isReadOnlyDiagnosticSessionTargetCommand(command, subcommand);
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
function getRestoredSessionTabTarget(details, command, subcommand) {
|
|
163
|
-
if (isReadOnlyDiagnosticSessionTargetCommand(command, subcommand)) {
|
|
164
|
-
return undefined;
|
|
165
|
-
}
|
|
166
|
-
const storedTarget = isRecord(details.sessionTabTarget)
|
|
167
|
-
? normalizeSessionTabTarget({
|
|
168
|
-
targetId: typeof details.sessionTabTarget.targetId === "string" ? details.sessionTabTarget.targetId : undefined,
|
|
169
|
-
title: typeof details.sessionTabTarget.title === "string" ? details.sessionTabTarget.title : undefined,
|
|
170
|
-
url: typeof details.sessionTabTarget.url === "string" ? details.sessionTabTarget.url : undefined,
|
|
171
|
-
})
|
|
172
|
-
: undefined;
|
|
173
|
-
if (command !== "batch") {
|
|
174
|
-
return storedTarget;
|
|
175
|
-
}
|
|
176
|
-
if (isRecord(details.compiledNetworkSourceLookup) || batchContainsOnlyReadOnlyDiagnosticTargets(details.data)) {
|
|
177
|
-
return undefined;
|
|
178
|
-
}
|
|
179
|
-
// The stored target includes post-batch live verification and native tab identity.
|
|
180
|
-
if (storedTarget || details.sessionTabTargetUnknown === true)
|
|
181
|
-
return storedTarget;
|
|
182
|
-
const batchTarget = extractSessionTabTargetFromBatchResults(details.data);
|
|
183
|
-
if (batchTarget) {
|
|
184
|
-
return batchTarget;
|
|
185
|
-
}
|
|
186
|
-
return storedTarget;
|
|
187
|
-
}
|
|
188
151
|
function extractRefSnapshotRefs(data) {
|
|
189
152
|
if (!isRecord(data) || !isRecord(data.refs))
|
|
190
153
|
return undefined;
|
|
@@ -278,62 +241,6 @@ export function extractLatestRefSnapshotStateFromBatchResults(data) {
|
|
|
278
241
|
}
|
|
279
242
|
return latestState;
|
|
280
243
|
}
|
|
281
|
-
function getRestoredRefSnapshotInvalidation(details, command) {
|
|
282
|
-
const invalidation = isRecord(details.refSnapshotInvalidation) ? details.refSnapshotInvalidation : undefined;
|
|
283
|
-
if (invalidation?.reason === "no-active-page")
|
|
284
|
-
return buildNoActivePageRefSnapshotInvalidation();
|
|
285
|
-
if (invalidation?.reason === "page-transition")
|
|
286
|
-
return buildPageTransitionRefSnapshotInvalidation(typeof invalidation.summary === "string" ? invalidation.summary : undefined);
|
|
287
|
-
const errorText = typeof details.error === "string"
|
|
288
|
-
? details.error
|
|
289
|
-
: typeof details.summary === "string"
|
|
290
|
-
? details.summary
|
|
291
|
-
: undefined;
|
|
292
|
-
return isNoActivePageSnapshotFailure(command, errorText) ? buildNoActivePageRefSnapshotInvalidation() : undefined;
|
|
293
|
-
}
|
|
294
|
-
function getRestoredRefSnapshot(details) {
|
|
295
|
-
const refSnapshot = isRecord(details.refSnapshot) ? details.refSnapshot : undefined;
|
|
296
|
-
if (!refSnapshot || !Array.isArray(refSnapshot.refIds))
|
|
297
|
-
return undefined;
|
|
298
|
-
const refIds = refSnapshot.refIds.filter((refId) => typeof refId === "string" && /^e\d+$/.test(refId));
|
|
299
|
-
const refRecord = isRecord(refSnapshot.refs) ? refSnapshot.refs : undefined;
|
|
300
|
-
const refEntries = refRecord
|
|
301
|
-
? Object.fromEntries(refIds.flatMap((refId) => {
|
|
302
|
-
const entry = refRecord[refId];
|
|
303
|
-
if (!isRecord(entry) || typeof entry.name !== "string" || typeof entry.role !== "string")
|
|
304
|
-
return [];
|
|
305
|
-
const isContentEditable = typeof entry.isContentEditable === "boolean" ? entry.isContentEditable : undefined;
|
|
306
|
-
const isEditable = typeof entry.isEditable === "boolean" ? entry.isEditable : undefined;
|
|
307
|
-
return [[refId, { ...(isContentEditable !== undefined ? { isContentEditable } : {}), ...(isEditable !== undefined ? { isEditable } : {}), name: entry.name, role: entry.role }]];
|
|
308
|
-
}))
|
|
309
|
-
: undefined;
|
|
310
|
-
return {
|
|
311
|
-
refIds,
|
|
312
|
-
...(refEntries && Object.keys(refEntries).length > 0 ? { refs: refEntries } : {}),
|
|
313
|
-
target: isRecord(refSnapshot.target)
|
|
314
|
-
? normalizeSessionTabTarget({
|
|
315
|
-
title: typeof refSnapshot.target.title === "string" ? refSnapshot.target.title : undefined,
|
|
316
|
-
url: typeof refSnapshot.target.url === "string" ? refSnapshot.target.url : undefined,
|
|
317
|
-
})
|
|
318
|
-
: undefined,
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
function getLatestTabTargetOrder(targets, unknownTargets) {
|
|
322
|
-
let latestOrder = 0;
|
|
323
|
-
for (const target of targets.values())
|
|
324
|
-
latestOrder = Math.max(latestOrder, target.order);
|
|
325
|
-
for (const order of unknownTargets.values())
|
|
326
|
-
latestOrder = Math.max(latestOrder, order);
|
|
327
|
-
return latestOrder;
|
|
328
|
-
}
|
|
329
|
-
function getLatestRefStateOrder(snapshots, invalidations) {
|
|
330
|
-
let latestOrder = 0;
|
|
331
|
-
for (const snapshot of snapshots.values())
|
|
332
|
-
latestOrder = Math.max(latestOrder, snapshot.order);
|
|
333
|
-
for (const invalidation of invalidations.values())
|
|
334
|
-
latestOrder = Math.max(latestOrder, invalidation.order);
|
|
335
|
-
return latestOrder;
|
|
336
|
-
}
|
|
337
244
|
function shouldApplyTabTargetUpdate(current, unknownOrder, updateOrder) {
|
|
338
245
|
return updateOrder >= Math.max(current?.order ?? 0, unknownOrder ?? 0);
|
|
339
246
|
}
|
|
@@ -342,7 +249,7 @@ function shouldApplyRefStateUpdate(options) {
|
|
|
342
249
|
return options.updateOrder >= currentOrder;
|
|
343
250
|
}
|
|
344
251
|
function stripRefSnapshotOrder(snapshot) {
|
|
345
|
-
return snapshot ? { refIds: snapshot.refIds, ...(snapshot.refs ? { refs: snapshot.refs } : {}), target: snapshot.target } : undefined;
|
|
252
|
+
return snapshot ? { ...(snapshot.snapshotId ? { snapshotId: snapshot.snapshotId } : {}), ...(snapshot.generation ? { generation: snapshot.generation } : {}), refIds: snapshot.refIds, ...(snapshot.refs ? { refs: snapshot.refs } : {}), target: snapshot.target } : undefined;
|
|
346
253
|
}
|
|
347
254
|
function stripRefSnapshotInvalidationOrder(invalidation) {
|
|
348
255
|
return invalidation ? { reason: invalidation.reason, summary: invalidation.summary } : undefined;
|
|
@@ -358,78 +265,96 @@ export class SessionPageState {
|
|
|
358
265
|
tabTargetUnknownOrders = new Map();
|
|
359
266
|
tabTargets = new Map();
|
|
360
267
|
updateOrder = 0;
|
|
268
|
+
nativeGenerations = new Map();
|
|
269
|
+
pending = new Map();
|
|
361
270
|
static fromBranch(branch) {
|
|
362
271
|
const state = new SessionPageState();
|
|
363
|
-
let restoredOrder = 0;
|
|
364
272
|
for (const entry of branch) {
|
|
365
|
-
const
|
|
366
|
-
if (
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
273
|
+
const record = getBrowserRecord(entry);
|
|
274
|
+
if (record)
|
|
275
|
+
state.applyBrowserRecord(record);
|
|
276
|
+
}
|
|
277
|
+
for (const key of state.tabTargets.keys())
|
|
278
|
+
state.tabPinningReasons.set(key, "restore");
|
|
279
|
+
return state;
|
|
280
|
+
}
|
|
281
|
+
/** A command operates on its admitted state while the committed view is unavailable. */
|
|
282
|
+
fork() {
|
|
283
|
+
const state = new SessionPageState();
|
|
284
|
+
state.readConfirmations = new Map(this.readConfirmations);
|
|
285
|
+
state.refSnapshotInvalidations = new Map(this.refSnapshotInvalidations);
|
|
286
|
+
state.refSnapshots = new Map(this.refSnapshots);
|
|
287
|
+
state.tabPinningReasons = new Map(this.tabPinningReasons);
|
|
288
|
+
state.tabTargetUnknownOrders = new Map(this.tabTargetUnknownOrders);
|
|
289
|
+
state.tabTargets = new Map([...this.tabTargets].map(([key, value]) => [key, { ...value }]));
|
|
290
|
+
state.pending = new Map(this.pending);
|
|
291
|
+
state.nativeGenerations = new Map(this.nativeGenerations);
|
|
292
|
+
state.updateOrder = this.updateOrder;
|
|
293
|
+
return state;
|
|
294
|
+
}
|
|
295
|
+
views() {
|
|
296
|
+
return new Map([...new Set([...this.tabTargets.keys(), ...this.tabTargetUnknownOrders.keys(), ...this.refSnapshots.keys(), ...this.refSnapshotInvalidations.keys()])].map(key => [key, this.get(key)]));
|
|
297
|
+
}
|
|
298
|
+
/** The same reducer commits live observations and replays selected journal envelopes. */
|
|
299
|
+
applyBrowserRecord({ event, snapshot }) {
|
|
300
|
+
const update = this.beginUpdate();
|
|
301
|
+
const confirmation = parseReadConfirmation(event.state.readConfirmation);
|
|
302
|
+
for (const page of event.pages ?? []) {
|
|
303
|
+
const key = page.key;
|
|
304
|
+
if (event.phase === "begin") {
|
|
305
|
+
this.pending.set(key, { operationId: event.operationId, snapshot: this.refSnapshots.get(key) ?? this.pending.get(key)?.snapshot });
|
|
306
|
+
this.markTabTargetUnknown({ sessionName: key, update });
|
|
307
|
+
this.applyRefSnapshotInvalidation({ sessionName: key, update, invalidation: buildPageTransitionRefSnapshotInvalidation("This browser operation has no persisted finish. Inspect the current URL and take a fresh snapshot before using refs; changes may already have happened.") });
|
|
370
308
|
continue;
|
|
371
|
-
const sessionName = typeof details.sessionName === "string" ? details.sessionName : undefined;
|
|
372
|
-
const namespace = typeof details.namespace === "string" ? details.namespace : undefined;
|
|
373
|
-
const sessionKey = getSessionPageStateKey(sessionName, namespace);
|
|
374
|
-
const args = Array.isArray(details.args) && details.args.every((arg) => typeof arg === "string") ? details.args : [];
|
|
375
|
-
const commandTokens = extractUpstreamCommandTokens(args);
|
|
376
|
-
const command = typeof details.command === "string" ? details.command : commandTokens[0];
|
|
377
|
-
const subcommand = typeof details.subcommand === "string" ? details.subcommand : commandTokens[1];
|
|
378
|
-
const batchCloseLifecycle = getSuccessfulBatchCloseLifecycle(details.batchSteps);
|
|
379
|
-
const closeAllApplied = details.closeAllApplied === true
|
|
380
|
-
|| (message.isError !== true && isCloseAllCommand(commandTokens))
|
|
381
|
-
|| batchHasSuccessfulCloseAll(details.batchSteps);
|
|
382
|
-
const lifecycleReset = (isCloseCommand(command) && message.isError !== true) || batchCloseLifecycle !== undefined;
|
|
383
|
-
if (closeAllApplied) {
|
|
384
|
-
restoredOrder += 1;
|
|
385
|
-
state.clearNamespace(namespace);
|
|
386
309
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
state.clearSession(sessionKey);
|
|
390
|
-
}
|
|
391
|
-
const readConfirmation = parseReadConfirmation(details.readConfirmation);
|
|
392
|
-
if (readConfirmation)
|
|
393
|
-
state.applyReadConfirmation(readConfirmation, ++restoredOrder);
|
|
394
|
-
if (!sessionKey || ((closeAllApplied || lifecycleReset) && (isCloseCommand(command) || batchCloseLifecycle?.endsClosed === true)))
|
|
310
|
+
if (page.clear) {
|
|
311
|
+
this.clearSession(key);
|
|
395
312
|
continue;
|
|
396
|
-
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
const refSnapshotInvalidation = getRestoredRefSnapshotInvalidation(details, command);
|
|
400
|
-
const refSnapshot = refSnapshotInvalidation ? undefined : getRestoredRefSnapshot(details);
|
|
401
|
-
if (!tabTarget && !tabTargetUnknown && !refSnapshotInvalidation && !refSnapshot && reopenPending === undefined)
|
|
313
|
+
}
|
|
314
|
+
const pending = this.pending.get(key);
|
|
315
|
+
if (event.phase === "finish" && pending && pending.operationId !== event.operationId)
|
|
402
316
|
continue;
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
state.refSnapshotInvalidations.set(sessionKey, { ...refSnapshotInvalidation, order: restoredOrder });
|
|
408
|
-
else
|
|
409
|
-
state.refSnapshotInvalidations.delete(sessionKey);
|
|
410
|
-
state.tabTargets.delete(sessionKey);
|
|
411
|
-
state.tabTargetUnknownOrders.set(sessionKey, restoredOrder);
|
|
317
|
+
if (page.unknown || page.refs.kind === "unknown") {
|
|
318
|
+
this.markTabTargetUnknown({ sessionName: key, update });
|
|
319
|
+
this.applyRefSnapshotInvalidation({ sessionName: key, update, invalidation: page.refs.kind === "unknown" && page.refs.invalidation
|
|
320
|
+
? page.refs.invalidation : buildPageTransitionRefSnapshotInvalidation("The browser target or operation outcome is unknown. Verify the current URL and take a fresh snapshot before using refs.") });
|
|
412
321
|
continue;
|
|
413
322
|
}
|
|
414
|
-
if (
|
|
415
|
-
|
|
416
|
-
|
|
323
|
+
if (page.target)
|
|
324
|
+
this.applyTabTarget({ sessionName: key, target: page.target, update });
|
|
325
|
+
else {
|
|
326
|
+
this.tabTargets.delete(key);
|
|
327
|
+
this.tabTargetUnknownOrders.delete(key);
|
|
328
|
+
}
|
|
329
|
+
if (page.reopenPending !== undefined)
|
|
330
|
+
this.setTabReopenPending({ pending: page.reopenPending, sessionName: key, update });
|
|
331
|
+
if (page.pinningReason)
|
|
332
|
+
this.markPinning(key, page.pinningReason);
|
|
333
|
+
else
|
|
334
|
+
this.tabPinningReasons.delete(key);
|
|
335
|
+
if (page.refs.kind === "replace") {
|
|
336
|
+
const definition = snapshot?.id === page.refs.snapshotId && snapshot.refs ? snapshotFromDefinition(snapshot)
|
|
337
|
+
: { snapshotId: page.refs.snapshotId, refIds: [] };
|
|
338
|
+
this.applyRefSnapshot({ sessionName: key, snapshot: definition, fallbackTarget: page.target, update });
|
|
417
339
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
340
|
+
else if (page.refs.kind === "reuse") {
|
|
341
|
+
const candidate = pending?.snapshot ?? this.refSnapshots.get(key);
|
|
342
|
+
if (candidate?.snapshotId === page.refs.snapshotId)
|
|
343
|
+
this.applyRefSnapshot({ sessionName: key, snapshot: candidate, fallbackTarget: page.target, update });
|
|
344
|
+
else
|
|
345
|
+
this.applyRefSnapshotInvalidation({ sessionName: key, update, invalidation: buildPageTransitionRefSnapshotInvalidation("The ancestral snapshot definition is unavailable. Take a new complete snapshot before using refs.") });
|
|
424
346
|
}
|
|
425
|
-
else
|
|
426
|
-
|
|
427
|
-
|
|
347
|
+
else {
|
|
348
|
+
this.refSnapshots.delete(key);
|
|
349
|
+
if (page.refs.invalidation)
|
|
350
|
+
this.applyRefSnapshotInvalidation({ sessionName: key, invalidation: page.refs.invalidation, update });
|
|
351
|
+
else
|
|
352
|
+
this.refSnapshotInvalidations.delete(key);
|
|
428
353
|
}
|
|
354
|
+
this.pending.delete(key);
|
|
429
355
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
return state;
|
|
356
|
+
if (confirmation)
|
|
357
|
+
this.applyReadConfirmation(confirmation, update);
|
|
433
358
|
}
|
|
434
359
|
beginUpdate() {
|
|
435
360
|
this.updateOrder += 1;
|
|
@@ -437,6 +362,8 @@ export class SessionPageState {
|
|
|
437
362
|
}
|
|
438
363
|
reset() {
|
|
439
364
|
this.readConfirmations.clear();
|
|
365
|
+
this.pending.clear();
|
|
366
|
+
this.nativeGenerations.clear();
|
|
440
367
|
this.refSnapshotInvalidations = new Map();
|
|
441
368
|
this.refSnapshots = new Map();
|
|
442
369
|
this.tabPinningReasons = new Map();
|
|
@@ -490,11 +417,20 @@ export class SessionPageState {
|
|
|
490
417
|
})) {
|
|
491
418
|
return { ...this.get(options.sessionName), applied: false, stale: true };
|
|
492
419
|
}
|
|
493
|
-
const snapshot = { ...options.snapshot, target: options.snapshot.target ?? options.fallbackTarget };
|
|
420
|
+
const snapshot = { ...options.snapshot, generation: options.snapshot.generation ?? this.nativeGenerations.get(options.sessionName), snapshotId: options.snapshot.snapshotId ?? randomUUID(), target: options.snapshot.target ?? options.fallbackTarget };
|
|
494
421
|
this.refSnapshotInvalidations.delete(options.sessionName);
|
|
495
422
|
this.refSnapshots.set(options.sessionName, { ...snapshot, order: options.update });
|
|
496
423
|
return { ...this.get(options.sessionName), applied: true };
|
|
497
424
|
}
|
|
425
|
+
bindSnapshotGeneration(sessionName, generation) {
|
|
426
|
+
if (generation)
|
|
427
|
+
this.nativeGenerations.set(sessionName, generation);
|
|
428
|
+
else
|
|
429
|
+
this.nativeGenerations.delete(sessionName);
|
|
430
|
+
const snapshot = this.refSnapshots.get(sessionName);
|
|
431
|
+
if (snapshot)
|
|
432
|
+
this.refSnapshots.set(sessionName, { ...snapshot, generation });
|
|
433
|
+
}
|
|
498
434
|
applyRefSnapshotInvalidation(options) {
|
|
499
435
|
if (!shouldApplyRefStateUpdate({
|
|
500
436
|
currentInvalidation: this.refSnapshotInvalidations.get(options.sessionName),
|
|
@@ -519,6 +455,8 @@ export class SessionPageState {
|
|
|
519
455
|
return { ...this.get(options.sessionName), applied: true };
|
|
520
456
|
}
|
|
521
457
|
clearSession(sessionName) {
|
|
458
|
+
this.pending.delete(sessionName);
|
|
459
|
+
this.nativeGenerations.delete(sessionName);
|
|
522
460
|
this.readConfirmations.delete(sessionName);
|
|
523
461
|
this.refSnapshotInvalidations.delete(sessionName);
|
|
524
462
|
this.refSnapshots.delete(sessionName);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomBytes } from "node:crypto";
|
|
1
|
+
import { randomBytes, randomInt } from "node:crypto";
|
|
2
2
|
import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
3
3
|
import { chmod, mkdir, mkdtemp, open, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
@@ -10,6 +10,8 @@ const TEMP_ROOT_MARKER_FILE_NAME = ".pi-agent-browser-owner.json";
|
|
|
10
10
|
const TEMP_ROOT_MARKER_KIND = "pi-agent-browser-temp-root";
|
|
11
11
|
const TEMP_ROOT_MARKER_VERSION = 2;
|
|
12
12
|
const STALE_TEMP_ROOT_MAX_AGE_MS = 24 * 60 * 60 * 1_000;
|
|
13
|
+
const STALE_TEMP_ROOT_BATCH_SIZE = 8;
|
|
14
|
+
let lastTempGcName;
|
|
13
15
|
const TEMP_ROOT_MAX_BYTES_ENV = "PI_AGENT_BROWSER_TEMP_ROOT_MAX_BYTES";
|
|
14
16
|
const DEFAULT_TEMP_ROOT_MAX_BYTES = 32 * 1_024 * 1_024;
|
|
15
17
|
const SESSION_ARTIFACT_MAX_BYTES_ENV = "PI_AGENT_BROWSER_SESSION_ARTIFACT_MAX_BYTES";
|
|
@@ -237,10 +239,17 @@ async function pruneStaleTempRoots(currentTempRoot) {
|
|
|
237
239
|
const entries = await readdir(tmpdir(), { withFileTypes: true }).catch(() => []);
|
|
238
240
|
const cutoffTime = Date.now() - STALE_TEMP_ROOT_MAX_AGE_MS;
|
|
239
241
|
const currentUid = getCurrentProcessUid();
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
.map(
|
|
243
|
-
|
|
242
|
+
// ponytail: list/sort all names; stream directories if enumeration becomes the bottleneck.
|
|
243
|
+
const candidates = entries.filter((entry) => entry.isDirectory() && entry.name.startsWith(TEMP_ROOT_PREFIX))
|
|
244
|
+
.map((entry) => entry.name).sort();
|
|
245
|
+
if (candidates.length === 0)
|
|
246
|
+
return;
|
|
247
|
+
const start = lastTempGcName === undefined ? randomInt(candidates.length)
|
|
248
|
+
: Math.max(0, candidates.findIndex((name) => name > lastTempGcName));
|
|
249
|
+
const batch = Array.from({ length: Math.min(STALE_TEMP_ROOT_BATCH_SIZE, candidates.length) }, (_, offset) => candidates[(start + offset) % candidates.length]);
|
|
250
|
+
lastTempGcName = batch.at(-1);
|
|
251
|
+
await Promise.all(batch.map(async (name) => {
|
|
252
|
+
const path = join(tmpdir(), name);
|
|
244
253
|
if (path === currentTempRoot)
|
|
245
254
|
return;
|
|
246
255
|
const ownershipMarker = await readTempRootOwnershipMarker(path);
|
|
@@ -400,7 +409,6 @@ async function prunePersistentSessionArtifactsToBudget(sessionArtifactDir, addit
|
|
|
400
409
|
async function getSessionTempRoot() {
|
|
401
410
|
if (!sessionTempRootPromise) {
|
|
402
411
|
sessionTempRootPromise = (async () => {
|
|
403
|
-
await pruneStaleTempRoots(undefined);
|
|
404
412
|
const tempRoot = await mkdtemp(join(tmpdir(), TEMP_ROOT_PREFIX));
|
|
405
413
|
await chmod(tempRoot, 0o700).catch(() => undefined);
|
|
406
414
|
await writeSecureTempRootOwnershipMarker(tempRoot);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Text } from "@earendil-works/pi-tui";
|
|
2
2
|
import { formatAgentBrowserRenderCall } from "./pi-tool-rendering.js";
|
|
3
3
|
import { AGENT_BROWSER_PARAMS, AGENT_BROWSER_CODE_PARAMS, AGENT_BROWSER_ACTION_PARAMS, AGENT_BROWSER_QA_PARAMS, AGENT_BROWSER_ELECTRON_PARAMS, AGENT_BROWSER_SOURCE_PARAMS, AGENT_BROWSER_NETWORK_SOURCE_PARAMS, AGENT_BROWSER_TOOLS_PARAMS, } from "./input-modes/params.js";
|
|
4
|
-
import { ADVANCED_TOOL_PROMPT_GUIDELINES, buildToolPromptGuidelines } from "./playbook.js";
|
|
5
4
|
export const AGENT_BROWSER_TOOL_INVENTORY = {
|
|
6
5
|
action: { name: "agent_browser_action", description: "Click, check, fill, or select a unique accessible target using stable locators or current refs." },
|
|
7
6
|
qa: { name: "agent_browser_qa", description: "Check a URL or attached page with visible assertions and a diagnostic pass/fail verdict." },
|
|
@@ -9,23 +8,18 @@ export const AGENT_BROWSER_TOOL_INVENTORY = {
|
|
|
9
8
|
source: { name: "agent_browser_source", description: "Find experimental DOM/React/local-source candidates with a bounded workspace scan." },
|
|
10
9
|
network: { name: "agent_browser_network_source", description: "Correlate failed requests with initiator and local-source candidates." },
|
|
11
10
|
};
|
|
12
|
-
export const
|
|
11
|
+
export const AGENT_BROWSER_INSTRUCTION_GROUP = {
|
|
13
12
|
name: "browser",
|
|
14
13
|
description: "Search the web, browse and interact with pages, verify browser UI, and inspect Electron apps.",
|
|
15
|
-
sections: ["agent_browser"],
|
|
16
14
|
};
|
|
17
|
-
const entryDiscovery = { discovery: { group: AGENT_BROWSER_DISCOVERY_GROUP, role: "entry" } };
|
|
18
|
-
const advancedDiscovery = { discovery: { group: AGENT_BROWSER_DISCOVERY_GROUP, role: "advanced" } };
|
|
19
15
|
const advancedNames = new Set(Object.values(AGENT_BROWSER_TOOL_INVENTORY).map(({ name }) => name));
|
|
20
16
|
/** Register once; advanced calls adapt only their input shape and reuse the ordinary executor. */
|
|
21
17
|
export function registerAgentBrowserToolSurface(pi, options) {
|
|
22
18
|
pi.registerTool({
|
|
23
|
-
...entryDiscovery,
|
|
24
19
|
name: "agent_browser",
|
|
25
20
|
label: "Agent Browser",
|
|
26
21
|
description: "Browse and interact through native agent-browser commands. One command in args; fixed sequences use batch --bail and JSON-array stdin. Use agent_browser_code for loops/branches and agent_browser_tools for advanced capabilities.",
|
|
27
22
|
promptSnippet: "Browse pages, inspect current refs, interact, and run native command batches.",
|
|
28
|
-
promptGuidelines: options.promptGuidelines ?? buildToolPromptGuidelines({ includeWebSearch: false }),
|
|
29
23
|
parameters: AGENT_BROWSER_PARAMS,
|
|
30
24
|
renderCall: options.renderCall,
|
|
31
25
|
renderResult: options.renderResult,
|
|
@@ -33,7 +27,6 @@ export function registerAgentBrowserToolSurface(pi, options) {
|
|
|
33
27
|
executionMode: options.executionMode,
|
|
34
28
|
});
|
|
35
29
|
pi.registerTool({
|
|
36
|
-
...entryDiscovery,
|
|
37
30
|
name: "agent_browser_code",
|
|
38
31
|
label: "Agent Browser Code",
|
|
39
32
|
description: "Run fresh JavaScript against a persistent browser. await browser({args,stdin?,timeoutMs?}) returns success/data/error/nextActions and imageObservations; emit(selected JSON) and emitImage(image handle) explicitly choose output. No host APIs or imports. Use native batch for fixed sequences.",
|
|
@@ -49,10 +42,8 @@ export function registerAgentBrowserToolSurface(pi, options) {
|
|
|
49
42
|
renderResult: options.renderResult,
|
|
50
43
|
});
|
|
51
44
|
pi.registerTool({
|
|
52
|
-
...advancedDiscovery,
|
|
53
45
|
...AGENT_BROWSER_TOOL_INVENTORY.action,
|
|
54
46
|
label: "Browser Action",
|
|
55
|
-
promptGuidelines: [...ADVANCED_TOOL_PROMPT_GUIDELINES.action],
|
|
56
47
|
parameters: AGENT_BROWSER_ACTION_PARAMS,
|
|
57
48
|
renderResult: options.renderResult,
|
|
58
49
|
executionMode: options.executionMode,
|
|
@@ -61,10 +52,8 @@ export function registerAgentBrowserToolSurface(pi, options) {
|
|
|
61
52
|
},
|
|
62
53
|
});
|
|
63
54
|
pi.registerTool({
|
|
64
|
-
...advancedDiscovery,
|
|
65
55
|
...AGENT_BROWSER_TOOL_INVENTORY.qa,
|
|
66
56
|
label: "Browser QA",
|
|
67
|
-
promptGuidelines: [...ADVANCED_TOOL_PROMPT_GUIDELINES.qa],
|
|
68
57
|
parameters: AGENT_BROWSER_QA_PARAMS,
|
|
69
58
|
renderResult: options.renderResult,
|
|
70
59
|
executionMode: options.executionMode,
|
|
@@ -73,10 +62,8 @@ export function registerAgentBrowserToolSurface(pi, options) {
|
|
|
73
62
|
},
|
|
74
63
|
});
|
|
75
64
|
pi.registerTool({
|
|
76
|
-
...advancedDiscovery,
|
|
77
65
|
...AGENT_BROWSER_TOOL_INVENTORY.electron,
|
|
78
66
|
label: "Browser Electron",
|
|
79
|
-
promptGuidelines: [...ADVANCED_TOOL_PROMPT_GUIDELINES.electron],
|
|
80
67
|
parameters: AGENT_BROWSER_ELECTRON_PARAMS,
|
|
81
68
|
renderResult: options.renderResult,
|
|
82
69
|
executionMode: options.executionMode,
|
|
@@ -85,10 +72,8 @@ export function registerAgentBrowserToolSurface(pi, options) {
|
|
|
85
72
|
},
|
|
86
73
|
});
|
|
87
74
|
pi.registerTool({
|
|
88
|
-
...advancedDiscovery,
|
|
89
75
|
...AGENT_BROWSER_TOOL_INVENTORY.source,
|
|
90
76
|
label: "Browser Source",
|
|
91
|
-
promptGuidelines: [...ADVANCED_TOOL_PROMPT_GUIDELINES.source],
|
|
92
77
|
parameters: AGENT_BROWSER_SOURCE_PARAMS,
|
|
93
78
|
renderResult: options.renderResult,
|
|
94
79
|
executionMode: options.executionMode,
|
|
@@ -97,10 +82,8 @@ export function registerAgentBrowserToolSurface(pi, options) {
|
|
|
97
82
|
},
|
|
98
83
|
});
|
|
99
84
|
pi.registerTool({
|
|
100
|
-
...advancedDiscovery,
|
|
101
85
|
...AGENT_BROWSER_TOOL_INVENTORY.network,
|
|
102
86
|
label: "Browser Network Source",
|
|
103
|
-
promptGuidelines: [...ADVANCED_TOOL_PROMPT_GUIDELINES.network],
|
|
104
87
|
parameters: AGENT_BROWSER_NETWORK_SOURCE_PARAMS,
|
|
105
88
|
renderResult: options.renderResult,
|
|
106
89
|
executionMode: options.executionMode,
|
|
@@ -109,7 +92,6 @@ export function registerAgentBrowserToolSurface(pi, options) {
|
|
|
109
92
|
},
|
|
110
93
|
});
|
|
111
94
|
pi.registerTool({
|
|
112
|
-
...entryDiscovery,
|
|
113
95
|
name: "agent_browser_tools",
|
|
114
96
|
label: "Browser Tools",
|
|
115
97
|
description: "List or enable advanced browser tools: action → agent_browser_action; qa → agent_browser_qa; electron → agent_browser_electron; source → agent_browser_source; network → agent_browser_network_source. Omit enable for inventory. Activation only adds tools; it preserves other active tools.",
|
|
@@ -142,10 +124,17 @@ export function registerAgentBrowserToolSurface(pi, options) {
|
|
|
142
124
|
const available = new Set(pi.getAllTools().map(({ name }) => name));
|
|
143
125
|
if (!["agent_browser", "agent_browser_code", "agent_browser_tools", ...advancedNames].every(name => available.has(name)))
|
|
144
126
|
return;
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
const
|
|
127
|
+
// Native dynamic imports bypass Pi's host-package aliases in consumer installs.
|
|
128
|
+
// Only tool names are needed; replay their native deltas without a host import.
|
|
129
|
+
const restored = new Set();
|
|
130
|
+
for (const message of ctx.sessionManager.buildSessionProjection().messages) {
|
|
131
|
+
if (message.role !== "system")
|
|
132
|
+
continue;
|
|
133
|
+
for (const removed of message.toolsRemoved ?? [])
|
|
134
|
+
restored.delete(removed.name);
|
|
135
|
+
for (const added of message.toolsAdded ?? [])
|
|
136
|
+
restored.add(added.name);
|
|
137
|
+
}
|
|
149
138
|
const active = pi.getActiveTools().filter((name) => !advancedNames.has(name) || restored.has(name));
|
|
150
139
|
pi.setActiveTools([...new Set([...active, ...[...restored].filter((name) => advancedNames.has(name) && available.has(name))])]);
|
|
151
140
|
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { JsonSchema } from "./json-schema.js";
|
|
2
|
-
import { WEB_SEARCH_PROMPT_GUIDELINE } from "./playbook.js";
|
|
3
2
|
import { StringEnum as localStringEnum } from "./string-enum-schema.js";
|
|
4
3
|
import { DEFAULT_WEB_SEARCH_PROVIDER, EXA_SEARCH_TYPES, WEB_SEARCH_PROVIDERS, resolvePreferredWebSearchCredential, } from "./config.js";
|
|
5
4
|
export const AGENT_BROWSER_WEB_SEARCH_TOOL_NAME = "agent_browser_web_search";
|
|
@@ -566,13 +565,6 @@ export function createAgentBrowserWebSearchTool(configState, options = {}) {
|
|
|
566
565
|
label: "Agent Browser Web Search",
|
|
567
566
|
description: `Search the live web with Exa or Brave for current or external information. For Exa research tasks, use searchType deep-lite or deeper. Returns up to ${MAX_SEARCH_RESULT_COUNT} concise web results.`,
|
|
568
567
|
promptSnippet: "Search the live web with Exa or Brave for current or external information.",
|
|
569
|
-
promptGuidelines: [
|
|
570
|
-
WEB_SEARCH_PROMPT_GUIDELINE,
|
|
571
|
-
"agent_browser_web_search chooses Exa or Brave from configured keys; when both are available, Exa is preferred by default unless webSearch.preferredProvider says otherwise. Use provider only when the user/config calls for a specific provider.",
|
|
572
|
-
"Use Exa deep only when deep-lite may miss angles, and deep-reasoning only for exhaustive or still-thin research. Do not run parallel agent_browser_web_search calls; make one high-signal query, inspect its results, then at most one follow-up.",
|
|
573
|
-
"If agent_browser_web_search returns HTTP 429, stop searching and tell the user the API plan/rate limit needs time or a plan change.",
|
|
574
|
-
"After using agent_browser_web_search, cite result URLs in the final answer when web evidence informed the answer.",
|
|
575
|
-
],
|
|
576
568
|
parameters: AgentBrowserWebSearchParams,
|
|
577
569
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
578
570
|
const runtimeConfigState = ctx ? options.loadConfigState?.(ctx) ?? configState : configState;
|