pi-agent-browser-native 0.3.0 → 0.6.5
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 +265 -0
- package/README.md +130 -54
- package/dist/extensions/agent-browser/index.js +781 -169
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +35 -3
- package/dist/extensions/agent-browser/lib/argv-grammar.js +50 -2
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +71 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +5 -8
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +53 -12
- package/dist/extensions/agent-browser/lib/config-policy.js +25 -1
- package/dist/extensions/agent-browser/lib/config.js +1 -1
- package/dist/extensions/agent-browser/lib/input-modes/job.js +61 -13
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +2 -2
- package/dist/extensions/agent-browser/lib/input-modes/params.js +23 -24
- package/dist/extensions/agent-browser/lib/input-modes/script.js +462 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +51 -12
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +26 -4
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +6 -139
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +26 -116
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +2 -4
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +54 -25
- 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 +54 -48
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +71 -5
- 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 +6 -7
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +119 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +7 -6
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +152 -64
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +244 -102
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +63 -37
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +20 -21
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +36 -18
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +41 -21
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +299 -0
- package/dist/extensions/agent-browser/lib/page-target-validation.js +270 -0
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +32 -10
- package/dist/extensions/agent-browser/lib/playbook.js +29 -25
- package/dist/extensions/agent-browser/lib/process-environment.js +14 -0
- package/dist/extensions/agent-browser/lib/process-identity.js +5 -12
- package/dist/extensions/agent-browser/lib/process.js +130 -104
- package/dist/extensions/agent-browser/lib/recording-reservations.js +116 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +63 -6
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +62 -4
- package/dist/extensions/agent-browser/lib/results/categories.js +6 -1
- package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -5
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +85 -38
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +86 -18
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +38 -2
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +18 -17
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +2 -1
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +38 -20
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +60 -15
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +1 -10
- package/dist/extensions/agent-browser/lib/results/presentation.js +36 -6
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +3 -1
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +9 -0
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +54 -11
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +13 -7
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +2 -1
- package/dist/extensions/agent-browser/lib/results/snapshot.js +4 -4
- package/dist/extensions/agent-browser/lib/runtime.js +186 -108
- package/dist/extensions/agent-browser/lib/session-page-state.js +71 -10
- package/dist/extensions/agent-browser/lib/temp.js +1 -2
- package/dist/extensions/agent-browser/lib/upstream-version.js +14 -0
- package/dist/extensions/agent-browser/lib/web-search.js +108 -24
- package/dist/extensions/agent-browser/script-worker.js +169 -0
- package/dist/scripts/agent-browser-target.mjs +21 -0
- package/docs/ARCHITECTURE.md +57 -34
- package/docs/COMMAND_REFERENCE.md +255 -68
- package/docs/ELECTRON.md +2 -2
- package/docs/RELEASE.md +12 -10
- package/docs/REQUIREMENTS.md +11 -8
- package/docs/SUPPORT_MATRIX.md +36 -24
- package/docs/TOOL_CONTRACT.md +169 -95
- package/package.json +3 -1
- package/platform-smoke.config.mjs +2 -2
- package/scripts/agent-browser-capability-baseline.mjs +87 -9
- package/scripts/agent-browser-target.mjs +21 -0
- package/scripts/build.mjs +41 -0
- package/scripts/config.mjs +1 -0
- package/scripts/doctor.mjs +16 -9
- package/scripts/platform-smoke/browser-dogfood-windows.ps1 +9 -3
- package/scripts/platform-smoke/targets.mjs +12 -6
- package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -20
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +0 -583
- package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -78
- package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -37
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { extractUpstreamCommandTokens } from "./argv-descriptor.js";
|
|
2
|
+
import { getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace } from "./argv-grammar.js";
|
|
3
|
+
import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "./batch-lifecycle.js";
|
|
4
|
+
import { isCloseAllCommand, isCloseCommand, isReadOnlyDiagnosticSessionTargetCommand, isRecordPageTransitionCommand, isUnverifiedPageTransitionCommand, isWebMcpPageMutationCommand } from "./command-taxonomy.js";
|
|
3
5
|
import { isRecord } from "./parsing.js";
|
|
4
6
|
import { getEditableRefEvidence } from "./results/editable-ref-evidence.js";
|
|
5
7
|
import { enrichSnapshotRefEntries, getSnapshotRefEntries } from "./results/snapshot-refs.js";
|
|
@@ -93,6 +95,11 @@ export function extractSessionTabTargetFromBatchResults(data) {
|
|
|
93
95
|
}
|
|
94
96
|
const [name, subcommand] = extractBatchResultCommand(item);
|
|
95
97
|
const result = item.result;
|
|
98
|
+
if (isCloseCommand(name)) {
|
|
99
|
+
currentTarget = undefined;
|
|
100
|
+
pendingTitle = undefined;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
96
103
|
if (name === "get" && subcommand === "title") {
|
|
97
104
|
pendingTitle = extractStringResultField(result, "title");
|
|
98
105
|
continue;
|
|
@@ -197,6 +204,20 @@ export function buildNoActivePageRefSnapshotInvalidation() {
|
|
|
197
204
|
summary: "The latest snapshot for this session reported No active page. Old page-scoped refs are invalid until snapshot -i succeeds.",
|
|
198
205
|
};
|
|
199
206
|
}
|
|
207
|
+
export function buildPageTransitionRefSnapshotInvalidation(summary) {
|
|
208
|
+
return {
|
|
209
|
+
reason: "page-transition",
|
|
210
|
+
summary: summary ?? "A recording command (record start, or record restart with a URL) replaced or navigated the active page and invalidated the prior snapshot. Run snapshot -i before using page-scoped refs.",
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
export function getCommandRefSnapshotInvalidation(commandTokens) {
|
|
214
|
+
if (isRecordPageTransitionCommand(commandTokens))
|
|
215
|
+
return buildPageTransitionRefSnapshotInvalidation();
|
|
216
|
+
if (isWebMcpPageMutationCommand(commandTokens)) {
|
|
217
|
+
return buildPageTransitionRefSnapshotInvalidation("A WebMCP invoke, result, or cancel command can mutate, rerender, or navigate the page, so the prior snapshot refs were invalidated. Run snapshot -i before using page-scoped refs.");
|
|
218
|
+
}
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
200
221
|
export function isNoActivePageSnapshotFailure(command, text) {
|
|
201
222
|
return command === "snapshot" && /\bno active page\b/i.test(text ?? "");
|
|
202
223
|
}
|
|
@@ -207,7 +228,17 @@ export function extractLatestRefSnapshotStateFromBatchResults(data) {
|
|
|
207
228
|
for (const item of data) {
|
|
208
229
|
if (!isRecord(item))
|
|
209
230
|
continue;
|
|
210
|
-
const
|
|
231
|
+
const commandTokens = extractBatchResultCommand(item);
|
|
232
|
+
const [name] = commandTokens;
|
|
233
|
+
if (item.success !== false && isCloseCommand(name)) {
|
|
234
|
+
latestState = undefined;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const transitionInvalidation = getCommandRefSnapshotInvalidation(commandTokens);
|
|
238
|
+
if (transitionInvalidation) {
|
|
239
|
+
latestState = { invalidation: transitionInvalidation };
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
211
242
|
if (name !== "snapshot")
|
|
212
243
|
continue;
|
|
213
244
|
if (item.success === false) {
|
|
@@ -225,9 +256,10 @@ export function extractLatestRefSnapshotStateFromBatchResults(data) {
|
|
|
225
256
|
}
|
|
226
257
|
function getRestoredRefSnapshotInvalidation(details, command) {
|
|
227
258
|
const invalidation = isRecord(details.refSnapshotInvalidation) ? details.refSnapshotInvalidation : undefined;
|
|
228
|
-
if (invalidation
|
|
259
|
+
if (invalidation?.reason === "no-active-page")
|
|
229
260
|
return buildNoActivePageRefSnapshotInvalidation();
|
|
230
|
-
|
|
261
|
+
if (invalidation?.reason === "page-transition")
|
|
262
|
+
return buildPageTransitionRefSnapshotInvalidation(typeof invalidation.summary === "string" ? invalidation.summary : undefined);
|
|
231
263
|
const errorText = typeof details.error === "string"
|
|
232
264
|
? details.error
|
|
233
265
|
: typeof details.summary === "string"
|
|
@@ -316,14 +348,27 @@ export class SessionPageState {
|
|
|
316
348
|
const sessionName = typeof details.sessionName === "string" ? details.sessionName : undefined;
|
|
317
349
|
const namespace = typeof details.namespace === "string" ? details.namespace : undefined;
|
|
318
350
|
const sessionKey = getSessionPageStateKey(sessionName, namespace);
|
|
351
|
+
const args = Array.isArray(details.args) && details.args.every((arg) => typeof arg === "string") ? details.args : [];
|
|
352
|
+
const commandTokens = extractUpstreamCommandTokens(args);
|
|
353
|
+
const command = typeof details.command === "string" ? details.command : commandTokens[0];
|
|
354
|
+
const subcommand = typeof details.subcommand === "string" ? details.subcommand : commandTokens[1];
|
|
355
|
+
const batchCloseLifecycle = getSuccessfulBatchCloseLifecycle(details.batchSteps);
|
|
356
|
+
const closeAllApplied = details.closeAllApplied === true
|
|
357
|
+
|| (message.isError !== true && isCloseAllCommand(commandTokens))
|
|
358
|
+
|| batchHasSuccessfulCloseAll(details.batchSteps);
|
|
359
|
+
if (closeAllApplied) {
|
|
360
|
+
restoredOrder += 1;
|
|
361
|
+
state.clearNamespace(namespace);
|
|
362
|
+
if (isCloseCommand(command) || batchCloseLifecycle?.endsClosed === true)
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
319
365
|
if (!sessionKey)
|
|
320
366
|
continue;
|
|
321
|
-
|
|
322
|
-
const subcommand = typeof details.subcommand === "string" ? details.subcommand : undefined;
|
|
323
|
-
if (isCloseCommand(command) && message.isError !== true) {
|
|
367
|
+
if (!closeAllApplied && ((isCloseCommand(command) && message.isError !== true) || batchCloseLifecycle)) {
|
|
324
368
|
restoredOrder += 1;
|
|
325
369
|
state.clearSession(sessionKey);
|
|
326
|
-
|
|
370
|
+
if (isCloseCommand(command) || batchCloseLifecycle?.endsClosed === true)
|
|
371
|
+
continue;
|
|
327
372
|
}
|
|
328
373
|
const tabTarget = getRestoredSessionTabTarget(details, command, subcommand);
|
|
329
374
|
const tabTargetUnknown = details.sessionTabTargetUnknown === true;
|
|
@@ -333,8 +378,11 @@ export class SessionPageState {
|
|
|
333
378
|
continue;
|
|
334
379
|
restoredOrder += 1;
|
|
335
380
|
if (tabTargetUnknown) {
|
|
336
|
-
state.refSnapshotInvalidations.delete(sessionKey);
|
|
337
381
|
state.refSnapshots.delete(sessionKey);
|
|
382
|
+
if (refSnapshotInvalidation)
|
|
383
|
+
state.refSnapshotInvalidations.set(sessionKey, { ...refSnapshotInvalidation, order: restoredOrder });
|
|
384
|
+
else
|
|
385
|
+
state.refSnapshotInvalidations.delete(sessionKey);
|
|
338
386
|
state.tabTargets.delete(sessionKey);
|
|
339
387
|
state.tabTargetUnknownOrders.set(sessionKey, restoredOrder);
|
|
340
388
|
continue;
|
|
@@ -431,6 +479,19 @@ export class SessionPageState {
|
|
|
431
479
|
this.tabTargetUnknownOrders.delete(sessionName);
|
|
432
480
|
this.tabTargets.delete(sessionName);
|
|
433
481
|
}
|
|
482
|
+
clearNamespace(namespace) {
|
|
483
|
+
const sessionKeys = new Set([
|
|
484
|
+
...this.refSnapshotInvalidations.keys(),
|
|
485
|
+
...this.refSnapshots.keys(),
|
|
486
|
+
...this.tabPinningReasons.keys(),
|
|
487
|
+
...this.tabTargetUnknownOrders.keys(),
|
|
488
|
+
...this.tabTargets.keys(),
|
|
489
|
+
]);
|
|
490
|
+
for (const sessionKey of sessionKeys) {
|
|
491
|
+
if (isAgentBrowserSessionIdentityKeyInNamespace(sessionKey, namespace))
|
|
492
|
+
this.clearSession(sessionKey);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
434
495
|
markPinning(sessionName, reason) {
|
|
435
496
|
this.tabPinningReasons.set(sessionName, reason);
|
|
436
497
|
}
|
|
@@ -8,7 +8,6 @@ import { processStartIdentitiesMatch, readProcessStartIdentity } from "./process
|
|
|
8
8
|
const TEMP_ROOT_PREFIX = "pi-agent-browser-";
|
|
9
9
|
const TEMP_ROOT_MARKER_FILE_NAME = ".pi-agent-browser-owner.json";
|
|
10
10
|
const TEMP_ROOT_MARKER_KIND = "pi-agent-browser-temp-root";
|
|
11
|
-
const TEMP_ROOT_LEGACY_MARKER_VERSION = 1;
|
|
12
11
|
const TEMP_ROOT_MARKER_VERSION = 2;
|
|
13
12
|
const STALE_TEMP_ROOT_MAX_AGE_MS = 24 * 60 * 60 * 1_000;
|
|
14
13
|
const TEMP_ROOT_MAX_BYTES_ENV = "PI_AGENT_BROWSER_TEMP_ROOT_MAX_BYTES";
|
|
@@ -39,7 +38,7 @@ function isProtectedTempChildName(value) {
|
|
|
39
38
|
function isTempRootOwnershipRecord(value) {
|
|
40
39
|
if (!isRecord(value))
|
|
41
40
|
return false;
|
|
42
|
-
if (value.kind !== TEMP_ROOT_MARKER_KIND ||
|
|
41
|
+
if (value.kind !== TEMP_ROOT_MARKER_KIND || value.version !== TEMP_ROOT_MARKER_VERSION)
|
|
43
42
|
return false;
|
|
44
43
|
if (!isPositiveFiniteNumber(value.createdAtMs))
|
|
45
44
|
return false;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { MINIMUM_AGENT_BROWSER_VERSION, MINIMUM_AGENT_BROWSER_VERSION_LABEL, SUPPORTED_AGENT_BROWSER_VERSION_LABEL, TARGET_AGENT_BROWSER_VERSION, TARGET_AGENT_BROWSER_VERSION_LABEL, isSupportedAgentBrowserVersion, } from "../../../scripts/agent-browser-target.mjs";
|
|
2
|
+
export { MINIMUM_AGENT_BROWSER_VERSION, MINIMUM_AGENT_BROWSER_VERSION_LABEL, SUPPORTED_AGENT_BROWSER_VERSION_LABEL, TARGET_AGENT_BROWSER_VERSION, TARGET_AGENT_BROWSER_VERSION_LABEL, isSupportedAgentBrowserVersion, };
|
|
3
|
+
export function parseAgentBrowserVersionOutput(stdout) {
|
|
4
|
+
const match = stdout.trim().match(/^agent-browser\s+(\S+)$/);
|
|
5
|
+
return match?.[1];
|
|
6
|
+
}
|
|
7
|
+
export function getAgentBrowserVersionValidationError(stdout) {
|
|
8
|
+
const observed = parseAgentBrowserVersionOutput(stdout);
|
|
9
|
+
if (observed && isSupportedAgentBrowserVersion(observed))
|
|
10
|
+
return undefined;
|
|
11
|
+
return observed
|
|
12
|
+
? `Installed agent-browser ${observed} is unsupported; stable versions must be at least ${MINIMUM_AGENT_BROWSER_VERSION_LABEL}. Install ${TARGET_AGENT_BROWSER_VERSION_LABEL} (recommended), run pi-agent-browser-doctor, then reload Pi.`
|
|
13
|
+
: `agent-browser --version returned an unrecognized value; expected ${SUPPORTED_AGENT_BROWSER_VERSION_LABEL}. Run pi-agent-browser-doctor and install a supported upstream version.`;
|
|
14
|
+
}
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import { JsonSchema } from "./json-schema.js";
|
|
2
2
|
import { WEB_SEARCH_PROMPT_GUIDELINE } from "./playbook.js";
|
|
3
3
|
import { StringEnum as localStringEnum } from "./string-enum-schema.js";
|
|
4
|
-
import { DEFAULT_WEB_SEARCH_PROVIDER, WEB_SEARCH_PROVIDERS, resolvePreferredWebSearchCredential, } from "./config.js";
|
|
4
|
+
import { DEFAULT_WEB_SEARCH_PROVIDER, EXA_SEARCH_TYPES, WEB_SEARCH_PROVIDERS, resolvePreferredWebSearchCredential, } from "./config.js";
|
|
5
5
|
export const AGENT_BROWSER_WEB_SEARCH_TOOL_NAME = "agent_browser_web_search";
|
|
6
6
|
export const BRAVE_SEARCH_ENDPOINT = "https://api.search.brave.com/res/v1/web/search";
|
|
7
7
|
export const EXA_SEARCH_ENDPOINT = "https://api.exa.ai/search";
|
|
8
8
|
export const DEFAULT_SEARCH_RESULT_COUNT = 5;
|
|
9
9
|
export const MAX_SEARCH_RESULT_COUNT = 10;
|
|
10
10
|
export const SEARCH_REQUEST_TIMEOUT_MS = 15_000;
|
|
11
|
-
export const
|
|
11
|
+
export const EXA_DEEP_LITE_SEARCH_REQUEST_TIMEOUT_MS = 45_000;
|
|
12
|
+
export const EXA_DEEP_SEARCH_REQUEST_TIMEOUT_MS = 60_000;
|
|
13
|
+
export const EXA_DEEP_REASONING_SEARCH_REQUEST_TIMEOUT_MS = 90_000;
|
|
14
|
+
export const EXA_DYNAMIC_HIGHLIGHTS_BETA = "dynamic-highlights-2026-08-28";
|
|
12
15
|
export const WEB_SEARCH_MIN_REQUEST_INTERVAL_MS = 1_100;
|
|
13
|
-
export const
|
|
16
|
+
export const EXA_SEARCH_SYSTEM_PROMPT = "Prefer primary, official sources. Respect any requested version or date. Avoid duplicate or equivalent results.";
|
|
17
|
+
export { EXA_SEARCH_TYPES };
|
|
14
18
|
export const WEB_SEARCH_PROVIDER_PARAM_VALUES = ["auto", ...WEB_SEARCH_PROVIDERS];
|
|
19
|
+
export const EXA_SEARCH_CATEGORIES = ["company", "people", "publication", "news", "personal site", "financial report"];
|
|
20
|
+
const MAX_EXA_DOMAIN_FILTERS = 20;
|
|
21
|
+
const MAX_EXA_ADDITIONAL_QUERIES = 10;
|
|
15
22
|
export function createAgentBrowserWebSearchParamsSchema(Type = JsonSchema, StringEnum = localStringEnum) {
|
|
16
23
|
return Type.Object({
|
|
17
24
|
query: Type.String({
|
|
@@ -22,7 +29,28 @@ export function createAgentBrowserWebSearchParamsSchema(Type = JsonSchema, Strin
|
|
|
22
29
|
description: `Optional provider override. auto uses configured keys and preferredProvider; when both Exa and Brave are available, the default preferred provider is ${DEFAULT_WEB_SEARCH_PROVIDER}.`,
|
|
23
30
|
})),
|
|
24
31
|
searchType: Type.Optional(StringEnum(EXA_SEARCH_TYPES, {
|
|
25
|
-
description: "
|
|
32
|
+
description: "Exa mode; omitted uses webSearch.defaultSearchType, then auto. instant (~250ms) is only for trivial lookups; fast (~450ms) favors latency; auto (~1s) is balanced. Pass searchType: deep-lite (~4s) for research before implementation unless config already defaults it; do not assume auto is deep enough. deep (4–15s) handles hard multi-source research; deep-reasoning (12–40s) is only for the hardest work. Brave ignores this field.",
|
|
33
|
+
})),
|
|
34
|
+
includeDomains: Type.Optional(Type.Array(Type.String({ minLength: 1 }), {
|
|
35
|
+
minItems: 1,
|
|
36
|
+
maxItems: MAX_EXA_DOMAIN_FILTERS,
|
|
37
|
+
description: `Exa only. Limit results to 1–${MAX_EXA_DOMAIN_FILTERS} hostnames, path prefixes (exa.ai/docs), or wildcard subdomains (*.substack.com).`,
|
|
38
|
+
})),
|
|
39
|
+
excludeDomains: Type.Optional(Type.Array(Type.String({ minLength: 1 }), {
|
|
40
|
+
minItems: 1,
|
|
41
|
+
maxItems: MAX_EXA_DOMAIN_FILTERS,
|
|
42
|
+
description: `Exa only. Exclude 1–${MAX_EXA_DOMAIN_FILTERS} hostnames, path prefixes, or wildcard subdomains. Not compatible with category company or people.`,
|
|
43
|
+
})),
|
|
44
|
+
category: Type.Optional(StringEnum(EXA_SEARCH_CATEGORIES, {
|
|
45
|
+
description: "Exa-only result category. company and people cannot be combined with freshness or excludeDomains.",
|
|
46
|
+
})),
|
|
47
|
+
additionalQueries: Type.Optional(Type.Array(Type.String({ minLength: 1 }), {
|
|
48
|
+
minItems: 1,
|
|
49
|
+
maxItems: MAX_EXA_ADDITIONAL_QUERIES,
|
|
50
|
+
description: `Exa only. Add 1–${MAX_EXA_ADDITIONAL_QUERIES} query variations when the effective searchType is deep-lite, deep, or deep-reasoning.`,
|
|
51
|
+
})),
|
|
52
|
+
highlightsDynamic: Type.Optional(Type.Boolean({
|
|
53
|
+
description: "Exa-only research preview. Allocate one highlight budget across all results; the wrapper sends the required Exa-Beta header. Regular per-page highlights remain the default.",
|
|
26
54
|
})),
|
|
27
55
|
count: Type.Optional(Type.Integer({
|
|
28
56
|
minimum: 1,
|
|
@@ -198,12 +226,14 @@ export function normalizeBraveSearchResult(result) {
|
|
|
198
226
|
const url = normalizeSearchUrl(result.url);
|
|
199
227
|
if (!title || !url)
|
|
200
228
|
return undefined;
|
|
229
|
+
const pageDate = cleanSearchText(result.page_age, 80);
|
|
201
230
|
return {
|
|
202
231
|
title,
|
|
203
232
|
url,
|
|
204
233
|
description: cleanSearchText(result.description, 320),
|
|
205
234
|
source: cleanSearchText(result.profile?.name, 120) ?? cleanSearchText(result.meta_url?.hostname, 120),
|
|
206
235
|
age: cleanSearchText(result.age, 80),
|
|
236
|
+
...(pageDate ? { pageDate } : {}),
|
|
207
237
|
language: cleanSearchText(result.language, 40),
|
|
208
238
|
};
|
|
209
239
|
}
|
|
@@ -213,13 +243,14 @@ export function normalizeExaSearchResult(result) {
|
|
|
213
243
|
if (!title || !url)
|
|
214
244
|
return undefined;
|
|
215
245
|
const highlights = normalizeHighlightList(result.highlights);
|
|
246
|
+
const pageDate = cleanSearchText(result.publishedDate, 80);
|
|
216
247
|
return {
|
|
217
248
|
title,
|
|
218
249
|
url,
|
|
219
250
|
description: cleanSearchText(result.summary, 320) ?? highlights?.[0] ?? cleanSearchText(result.text, 320),
|
|
220
251
|
highlights,
|
|
221
252
|
source: cleanSearchText(result.author, 120) ?? cleanSearchText(getHostname(url), 120),
|
|
222
|
-
|
|
253
|
+
...(pageDate ? { pageDate } : {}),
|
|
223
254
|
};
|
|
224
255
|
}
|
|
225
256
|
function getProviderLabel(provider) {
|
|
@@ -236,6 +267,8 @@ export function formatSearchResults(provider, query, results) {
|
|
|
236
267
|
lines.push(` URL: ${result.url}`);
|
|
237
268
|
if (result.source)
|
|
238
269
|
lines.push(` Source: ${result.source}`);
|
|
270
|
+
if (result.pageDate)
|
|
271
|
+
lines.push(` ${provider === "exa" ? "Published" : "Page date"}: ${result.pageDate}`);
|
|
239
272
|
if (result.age)
|
|
240
273
|
lines.push(` Age: ${result.age}`);
|
|
241
274
|
if (result.description)
|
|
@@ -277,14 +310,30 @@ function getStartPublishedDate(freshness, now) {
|
|
|
277
310
|
return new Date(now().getTime() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
278
311
|
}
|
|
279
312
|
export function buildExaSearchRequestBody(params, now = () => new Date()) {
|
|
313
|
+
const searchType = params.searchType ?? "auto";
|
|
314
|
+
if (params.additionalQueries?.length && !searchType.startsWith("deep")) {
|
|
315
|
+
throw new Error(`additionalQueries requires deep-lite, deep, or deep-reasoning; received ${searchType}.`);
|
|
316
|
+
}
|
|
317
|
+
if ((params.category === "company" || params.category === "people") && (params.freshness || params.excludeDomains?.length)) {
|
|
318
|
+
throw new Error(`category ${params.category} cannot be combined with freshness or excludeDomains.`);
|
|
319
|
+
}
|
|
280
320
|
const body = {
|
|
281
321
|
query: params.query,
|
|
282
|
-
type:
|
|
322
|
+
type: searchType,
|
|
283
323
|
numResults: Math.min(params.count + params.offset, 100),
|
|
284
|
-
contents: { highlights: true },
|
|
324
|
+
contents: { highlights: params.highlightsDynamic ? { dynamic: true } : true },
|
|
325
|
+
systemPrompt: EXA_SEARCH_SYSTEM_PROMPT,
|
|
285
326
|
};
|
|
327
|
+
if (params.category)
|
|
328
|
+
body.category = params.category;
|
|
286
329
|
if (params.country)
|
|
287
330
|
body.userLocation = params.country.toUpperCase();
|
|
331
|
+
if (params.includeDomains?.length)
|
|
332
|
+
body.includeDomains = params.includeDomains;
|
|
333
|
+
if (params.excludeDomains?.length)
|
|
334
|
+
body.excludeDomains = params.excludeDomains;
|
|
335
|
+
if (params.additionalQueries?.length)
|
|
336
|
+
body.additionalQueries = params.additionalQueries;
|
|
288
337
|
if (params.safesearch && params.safesearch !== "off")
|
|
289
338
|
body.moderation = true;
|
|
290
339
|
const startPublishedDate = getStartPublishedDate(params.freshness, now);
|
|
@@ -396,7 +445,20 @@ export async function fetchBraveSearchJson(url, apiKey, signal) {
|
|
|
396
445
|
});
|
|
397
446
|
}
|
|
398
447
|
function getExaRequestTimeoutMs(searchType) {
|
|
399
|
-
|
|
448
|
+
if (searchType === "deep-lite")
|
|
449
|
+
return EXA_DEEP_LITE_SEARCH_REQUEST_TIMEOUT_MS;
|
|
450
|
+
if (searchType === "deep")
|
|
451
|
+
return EXA_DEEP_SEARCH_REQUEST_TIMEOUT_MS;
|
|
452
|
+
if (searchType === "deep-reasoning")
|
|
453
|
+
return EXA_DEEP_REASONING_SEARCH_REQUEST_TIMEOUT_MS;
|
|
454
|
+
return SEARCH_REQUEST_TIMEOUT_MS;
|
|
455
|
+
}
|
|
456
|
+
function usesDynamicHighlights(body) {
|
|
457
|
+
const contents = body.contents;
|
|
458
|
+
if (!contents || typeof contents !== "object" || Array.isArray(contents))
|
|
459
|
+
return false;
|
|
460
|
+
const highlights = contents.highlights;
|
|
461
|
+
return Boolean(highlights && typeof highlights === "object" && !Array.isArray(highlights) && highlights.dynamic === true);
|
|
400
462
|
}
|
|
401
463
|
export async function fetchExaSearchJson(body, apiKey, signal, timeoutMs = SEARCH_REQUEST_TIMEOUT_MS) {
|
|
402
464
|
return fetchSearchJson({
|
|
@@ -408,6 +470,7 @@ export async function fetchExaSearchJson(body, apiKey, signal, timeoutMs = SEARC
|
|
|
408
470
|
Accept: "application/json",
|
|
409
471
|
"Content-Type": "application/json",
|
|
410
472
|
"x-api-key": apiKey,
|
|
473
|
+
...(usesDynamicHighlights(body) ? { "Exa-Beta": EXA_DYNAMIC_HIGHLIGHTS_BETA } : {}),
|
|
411
474
|
},
|
|
412
475
|
method: "POST",
|
|
413
476
|
},
|
|
@@ -449,15 +512,7 @@ const EXA_WEB_SEARCH_ADAPTER = {
|
|
|
449
512
|
buildRequest(params) {
|
|
450
513
|
const searchType = params.searchType ?? "auto";
|
|
451
514
|
return {
|
|
452
|
-
body: buildExaSearchRequestBody(
|
|
453
|
-
query: params.query,
|
|
454
|
-
count: params.count,
|
|
455
|
-
offset: params.offset,
|
|
456
|
-
country: params.country,
|
|
457
|
-
safesearch: params.safesearch,
|
|
458
|
-
freshness: params.freshness,
|
|
459
|
-
searchType,
|
|
460
|
-
}),
|
|
515
|
+
body: buildExaSearchRequestBody(params),
|
|
461
516
|
timeoutMs: getExaRequestTimeoutMs(searchType),
|
|
462
517
|
};
|
|
463
518
|
},
|
|
@@ -469,7 +524,7 @@ const EXA_WEB_SEARCH_ADAPTER = {
|
|
|
469
524
|
return {
|
|
470
525
|
extraDetails: {
|
|
471
526
|
requestId: cleanSearchText(response.requestId, 120),
|
|
472
|
-
searchType
|
|
527
|
+
searchType,
|
|
473
528
|
},
|
|
474
529
|
results: (response.results ?? [])
|
|
475
530
|
.map(normalizeExaSearchResult)
|
|
@@ -486,6 +541,15 @@ export const WEB_SEARCH_PROVIDER_ADAPTERS = {
|
|
|
486
541
|
export function getWebSearchProviderAdapter(provider) {
|
|
487
542
|
return WEB_SEARCH_PROVIDER_ADAPTERS[provider];
|
|
488
543
|
}
|
|
544
|
+
export function dedupeSearchResults(results) {
|
|
545
|
+
const seen = new Set();
|
|
546
|
+
return results.filter((result) => {
|
|
547
|
+
if (seen.has(result.url))
|
|
548
|
+
return false;
|
|
549
|
+
seen.add(result.url);
|
|
550
|
+
return true;
|
|
551
|
+
});
|
|
552
|
+
}
|
|
489
553
|
function buildMissingCredentialError(provider) {
|
|
490
554
|
if (provider === "brave")
|
|
491
555
|
return "agent_browser_web_search provider brave was requested but no BRAVE_API_KEY/config credential resolved.";
|
|
@@ -498,13 +562,13 @@ export function createAgentBrowserWebSearchTool(configState, options = {}) {
|
|
|
498
562
|
return {
|
|
499
563
|
name: AGENT_BROWSER_WEB_SEARCH_TOOL_NAME,
|
|
500
564
|
label: "Agent Browser Web Search",
|
|
501
|
-
description: `Search the web with Exa or Brave
|
|
565
|
+
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.`,
|
|
502
566
|
promptSnippet: "Search the live web with Exa or Brave for current or external information.",
|
|
503
567
|
promptGuidelines: [
|
|
504
568
|
WEB_SEARCH_PROMPT_GUIDELINE,
|
|
505
569
|
"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.",
|
|
506
|
-
"
|
|
507
|
-
"
|
|
570
|
+
"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.",
|
|
571
|
+
"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.",
|
|
508
572
|
"After using agent_browser_web_search, cite result URLs in the final answer when web evidence informed the answer.",
|
|
509
573
|
],
|
|
510
574
|
parameters: AgentBrowserWebSearchParams,
|
|
@@ -520,6 +584,18 @@ export function createAgentBrowserWebSearchTool(configState, options = {}) {
|
|
|
520
584
|
const resolved = await resolvePreferredWebSearchCredential(runtimeConfigState, { provider: requestedProvider, signal });
|
|
521
585
|
if (!resolved)
|
|
522
586
|
throw new Error(buildMissingCredentialError(requestedProvider));
|
|
587
|
+
if (resolved.provider === "brave") {
|
|
588
|
+
const exaOnlyFields = [
|
|
589
|
+
params.includeDomains ? "includeDomains" : undefined,
|
|
590
|
+
params.excludeDomains ? "excludeDomains" : undefined,
|
|
591
|
+
params.category ? "category" : undefined,
|
|
592
|
+
params.additionalQueries ? "additionalQueries" : undefined,
|
|
593
|
+
params.highlightsDynamic ? "highlightsDynamic" : undefined,
|
|
594
|
+
].filter((field) => Boolean(field));
|
|
595
|
+
if (exaOnlyFields.length > 0) {
|
|
596
|
+
throw new Error(`${exaOnlyFields.join(", ")} ${exaOnlyFields.length === 1 ? "requires" : "require"} provider exa; resolved provider was brave.`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
523
599
|
const query = params.query.trim();
|
|
524
600
|
if (!query)
|
|
525
601
|
throw new Error("query must not be blank");
|
|
@@ -527,18 +603,25 @@ export function createAgentBrowserWebSearchTool(configState, options = {}) {
|
|
|
527
603
|
const offset = Math.max(params.offset ?? 0, 0);
|
|
528
604
|
const adapter = getWebSearchProviderAdapter(resolved.provider);
|
|
529
605
|
const executionParams = {
|
|
606
|
+
additionalQueries: params.additionalQueries,
|
|
607
|
+
category: params.category,
|
|
530
608
|
country: params.country,
|
|
531
609
|
count,
|
|
610
|
+
excludeDomains: params.excludeDomains,
|
|
532
611
|
freshness: params.freshness,
|
|
612
|
+
highlightsDynamic: params.highlightsDynamic,
|
|
613
|
+
includeDomains: params.includeDomains,
|
|
533
614
|
offset,
|
|
534
615
|
query,
|
|
535
616
|
safesearch: params.safesearch,
|
|
536
617
|
searchLang: params.searchLang,
|
|
537
|
-
searchType: params.searchType ?? "auto",
|
|
618
|
+
searchType: params.searchType ?? runtimeConfigState.config.webSearch?.defaultSearchType ?? "auto",
|
|
538
619
|
};
|
|
539
620
|
const request = adapter.buildRequest(executionParams);
|
|
540
621
|
const data = await requestGate.run(signal, () => adapter.fetchJson(request, resolved.credential.value, signal));
|
|
541
622
|
const normalized = adapter.normalizeResponse(data, executionParams);
|
|
623
|
+
const results = dedupeSearchResults(normalized.results);
|
|
624
|
+
const duplicatesRemoved = normalized.results.length - results.length;
|
|
542
625
|
const details = {
|
|
543
626
|
provider: adapter.provider,
|
|
544
627
|
query,
|
|
@@ -547,10 +630,11 @@ export function createAgentBrowserWebSearchTool(configState, options = {}) {
|
|
|
547
630
|
offset,
|
|
548
631
|
...normalized.extraDetails,
|
|
549
632
|
fetchedAt: new Date().toISOString(),
|
|
550
|
-
results
|
|
633
|
+
results,
|
|
634
|
+
duplicatesRemoved: duplicatesRemoved || undefined,
|
|
551
635
|
};
|
|
552
636
|
return {
|
|
553
|
-
content: [{ type: "text", text: formatSearchResults(adapter.provider, normalized.returnedQuery,
|
|
637
|
+
content: [{ type: "text", text: `${formatSearchResults(adapter.provider, normalized.returnedQuery, results)}${duplicatesRemoved ? `\n\nDuplicate URLs removed: ${duplicatesRemoved}.` : ""}` }],
|
|
554
638
|
details,
|
|
555
639
|
};
|
|
556
640
|
},
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { createContext, runInContext, Script } from "node:vm";
|
|
3
|
+
function parseLimit(value, label) {
|
|
4
|
+
const parsed = Number(value);
|
|
5
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0)
|
|
6
|
+
throw new Error(`Invalid ${label}.`);
|
|
7
|
+
return parsed;
|
|
8
|
+
}
|
|
9
|
+
const maxMessageBytes = parseLimit(process.argv[2], "script IPC message limit");
|
|
10
|
+
const maxCumulativeBytes = parseLimit(process.argv[3], "script IPC cumulative limit");
|
|
11
|
+
let cumulativeBytes = 0;
|
|
12
|
+
let inputBuffer = Buffer.alloc(0);
|
|
13
|
+
let started = false;
|
|
14
|
+
const sandbox = Object.create(null);
|
|
15
|
+
const context = createContext(sandbox, {
|
|
16
|
+
codeGeneration: { strings: false, wasm: false },
|
|
17
|
+
name: "agent-browser-script",
|
|
18
|
+
});
|
|
19
|
+
const bridgeKey = `__piab_send_${randomBytes(16).toString("hex")}`;
|
|
20
|
+
const stateName = `__piab_state_${randomBytes(16).toString("hex")}`;
|
|
21
|
+
const hostSend = (json) => {
|
|
22
|
+
if (typeof json !== "string")
|
|
23
|
+
return false;
|
|
24
|
+
const bytes = Buffer.byteLength(json, "utf8") + 1;
|
|
25
|
+
if (bytes > maxMessageBytes || cumulativeBytes + bytes > maxCumulativeBytes)
|
|
26
|
+
return false;
|
|
27
|
+
cumulativeBytes += bytes;
|
|
28
|
+
try {
|
|
29
|
+
process.stdout.write(`${json}\n`);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
Object.setPrototypeOf(hostSend, null);
|
|
37
|
+
Object.freeze(hostSend);
|
|
38
|
+
sandbox[bridgeKey] = hostSend;
|
|
39
|
+
runInContext("const " + stateName + " = (() => {\n" +
|
|
40
|
+
" 'use strict';\n" +
|
|
41
|
+
" const send = globalThis[" + JSON.stringify(bridgeKey) + "];\n" +
|
|
42
|
+
" delete globalThis[" + JSON.stringify(bridgeKey) + "];\n" +
|
|
43
|
+
" for (const name of ['console','process','require','Buffer','fetch','WebSocket','setTimeout','setInterval','setImmediate','queueMicrotask','clearTimeout','clearInterval','clearImmediate']) Object.defineProperty(globalThis, name, { value: undefined, writable: false, configurable: false });\n" +
|
|
44
|
+
" const NativePromise = Promise;\n" +
|
|
45
|
+
" const promiseThen = Promise.prototype.then;\n" +
|
|
46
|
+
" const reflectApply = Reflect.apply;\n" +
|
|
47
|
+
" const pending = new Map();\n" +
|
|
48
|
+
" let nextId = 0;\n" +
|
|
49
|
+
" const encode = (value) => { const json = JSON.stringify(value); if (typeof json !== 'string') throw new TypeError('Value must be JSON-serializable.'); return json; };\n" +
|
|
50
|
+
" const sendValue = (value) => { const json = encode(value); if (json.length + 1 > " + maxMessageBytes + " || send(json) !== true) throw new RangeError('Script IPC limit exceeded.'); };\n" +
|
|
51
|
+
" const browser = function browser(params) {\n" +
|
|
52
|
+
" return new NativePromise((resolve, reject) => {\n" +
|
|
53
|
+
" const id = ++nextId;\n" +
|
|
54
|
+
" pending.set(id, { resolve, reject });\n" +
|
|
55
|
+
" try { sendValue({ type: 'call', id, params }); } catch (error) { pending.delete(id); reject(error); }\n" +
|
|
56
|
+
" });\n" +
|
|
57
|
+
" };\n" +
|
|
58
|
+
" const emit = function emit(value) { sendValue({ type: 'emit', value }); };\n" +
|
|
59
|
+
" Object.setPrototypeOf(browser, null);\n" +
|
|
60
|
+
" Object.setPrototypeOf(emit, null);\n" +
|
|
61
|
+
" Object.freeze(browser);\n" +
|
|
62
|
+
" Object.freeze(emit);\n" +
|
|
63
|
+
" Object.defineProperties(globalThis, { browser: { value: browser, writable: false, configurable: false }, emit: { value: emit, writable: false, configurable: false } });\n" +
|
|
64
|
+
" const complete = (ok, value) => {\n" +
|
|
65
|
+
" if (ok) {\n" +
|
|
66
|
+
" try { sendValue(value === undefined ? { type: 'complete', hasValue: false } : { type: 'complete', hasValue: true, value }); }\n" +
|
|
67
|
+
" catch { sendValue({ type: 'complete', error: { name: 'RangeError', message: 'Final script value is not serializable or exceeds the IPC limit.' } }); }\n" +
|
|
68
|
+
" return;\n" +
|
|
69
|
+
" }\n" +
|
|
70
|
+
" let name = 'Error'; let message = 'Script execution failed.';\n" +
|
|
71
|
+
" try { if (value && typeof value.name === 'string') name = value.name.slice(0, 80); } catch {}\n" +
|
|
72
|
+
" try { if (value && typeof value.message === 'string') message = value.message.replace(/[\\r\\n]+/g, ' ').slice(0, 400); } catch {}\n" +
|
|
73
|
+
" sendValue({ type: 'complete', error: { name, message } });\n" +
|
|
74
|
+
" };\n" +
|
|
75
|
+
" return Object.freeze({\n" +
|
|
76
|
+
" deliver(json) {\n" +
|
|
77
|
+
" const message = JSON.parse(json);\n" +
|
|
78
|
+
" const target = pending.get(message.id);\n" +
|
|
79
|
+
" if (!target) return;\n" +
|
|
80
|
+
" pending.delete(message.id);\n" +
|
|
81
|
+
" target.resolve(message.envelope);\n" +
|
|
82
|
+
" },\n" +
|
|
83
|
+
" run(thunk) {\n" +
|
|
84
|
+
" let promise;\n" +
|
|
85
|
+
" try { promise = reflectApply(thunk, undefined, []); } catch (error) { complete(false, error); return; }\n" +
|
|
86
|
+
" reflectApply(promiseThen, promise, [value => complete(true, value), error => complete(false, error)]);\n" +
|
|
87
|
+
" }\n" +
|
|
88
|
+
" });\n" +
|
|
89
|
+
"})();", context, { timeout: 1_000 });
|
|
90
|
+
const deliver = runInContext(`${stateName}.deliver`, context, { timeout: 1_000 });
|
|
91
|
+
function fail(name, message) {
|
|
92
|
+
hostSend(JSON.stringify({ type: "complete", error: { name, message } }));
|
|
93
|
+
}
|
|
94
|
+
function describeError(error, fallback) {
|
|
95
|
+
if (!error || typeof error !== "object")
|
|
96
|
+
return { message: fallback, name: "Error" };
|
|
97
|
+
const candidate = error;
|
|
98
|
+
return {
|
|
99
|
+
message: typeof candidate.message === "string" ? candidate.message.replace(/[\r\n]+/g, " ").slice(0, 400) : fallback,
|
|
100
|
+
name: typeof candidate.name === "string" ? candidate.name.slice(0, 80) : "Error",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function handleLine(line) {
|
|
104
|
+
const bytes = Buffer.byteLength(line, "utf8") + 1;
|
|
105
|
+
if (bytes > maxMessageBytes || cumulativeBytes + bytes > maxCumulativeBytes) {
|
|
106
|
+
fail("RangeError", "Script IPC limit exceeded.");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
cumulativeBytes += bytes;
|
|
110
|
+
let message;
|
|
111
|
+
try {
|
|
112
|
+
message = JSON.parse(line);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
fail("Error", "Invalid parent IPC message.");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (!started) {
|
|
119
|
+
if (!message || typeof message !== "object" || message.type !== "start" || typeof message.code !== "string") {
|
|
120
|
+
fail("Error", "Invalid script start message.");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
started = true;
|
|
124
|
+
try {
|
|
125
|
+
const source = `'use strict';\n${stateName}.run(async function () {\n'use strict';\n${message.code}\n});`;
|
|
126
|
+
const script = new Script(source, {
|
|
127
|
+
filename: "agent-browser-script.js",
|
|
128
|
+
importModuleDynamically() {
|
|
129
|
+
process.exit(70);
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
script.runInContext(context, { timeout: undefined });
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
const described = describeError(error, "Script compilation failed.");
|
|
136
|
+
fail(described.name, described.message);
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (!message || typeof message !== "object" || message.type !== "response") {
|
|
141
|
+
fail("Error", "Invalid parent IPC response.");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
deliver(line);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
fail("Error", "Invalid browser response envelope.");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
process.stdin.on("data", (rawChunk) => {
|
|
152
|
+
const chunk = Buffer.isBuffer(rawChunk) ? rawChunk : Buffer.from(rawChunk);
|
|
153
|
+
inputBuffer = Buffer.concat([inputBuffer, chunk]);
|
|
154
|
+
if (inputBuffer.length > maxMessageBytes) {
|
|
155
|
+
fail("RangeError", "Script IPC message limit exceeded.");
|
|
156
|
+
process.stdin.pause();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
for (;;) {
|
|
160
|
+
const newline = inputBuffer.indexOf(10);
|
|
161
|
+
if (newline < 0)
|
|
162
|
+
break;
|
|
163
|
+
const line = inputBuffer.subarray(0, newline).toString("utf8");
|
|
164
|
+
inputBuffer = inputBuffer.subarray(newline + 1);
|
|
165
|
+
handleLine(line);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
process.stdin.on("error", () => undefined);
|
|
169
|
+
hostSend(JSON.stringify({ type: "ready" }));
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const TARGET_AGENT_BROWSER_SOURCE = "scripts/agent-browser-target.mjs";
|
|
2
|
+
export const TARGET_AGENT_BROWSER_VERSION = "0.36.0";
|
|
3
|
+
export const TARGET_AGENT_BROWSER_VERSION_LABEL = `agent-browser ${TARGET_AGENT_BROWSER_VERSION}`;
|
|
4
|
+
export const MINIMUM_AGENT_BROWSER_VERSION = "0.35.0";
|
|
5
|
+
export const MINIMUM_AGENT_BROWSER_VERSION_LABEL = `agent-browser ${MINIMUM_AGENT_BROWSER_VERSION}`;
|
|
6
|
+
export const SUPPORTED_AGENT_BROWSER_VERSION_LABEL = `${MINIMUM_AGENT_BROWSER_VERSION_LABEL} or newer`;
|
|
7
|
+
function stableVersionParts(version) {
|
|
8
|
+
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(version);
|
|
9
|
+
return match?.slice(1).map(Number);
|
|
10
|
+
}
|
|
11
|
+
export function isSupportedAgentBrowserVersion(version) {
|
|
12
|
+
const actual = stableVersionParts(version);
|
|
13
|
+
const minimum = stableVersionParts(MINIMUM_AGENT_BROWSER_VERSION);
|
|
14
|
+
if (!actual || !minimum)
|
|
15
|
+
return false;
|
|
16
|
+
for (let index = 0; index < minimum.length; index += 1) {
|
|
17
|
+
if (actual[index] !== minimum[index])
|
|
18
|
+
return actual[index] > minimum[index];
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|