pi-agent-browser-native 0.2.12 → 0.2.14
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 +21 -0
- package/README.md +87 -27
- package/docs/ARCHITECTURE.md +9 -3
- package/docs/COMMAND_REFERENCE.md +383 -151
- package/docs/RELEASE.md +81 -26
- package/docs/REQUIREMENTS.md +10 -4
- package/docs/TOOL_CONTRACT.md +51 -11
- package/extensions/agent-browser/index.ts +847 -344
- package/extensions/agent-browser/lib/parsing.ts +20 -0
- package/extensions/agent-browser/lib/playbook.ts +79 -0
- package/extensions/agent-browser/lib/process.ts +56 -8
- package/extensions/agent-browser/lib/results/confirmation.ts +76 -0
- package/extensions/agent-browser/lib/results/envelope.ts +42 -5
- package/extensions/agent-browser/lib/results/presentation.ts +907 -50
- package/extensions/agent-browser/lib/results/shared.ts +166 -15
- package/extensions/agent-browser/lib/results/snapshot.ts +69 -7
- package/extensions/agent-browser/lib/results.ts +7 -1
- package/extensions/agent-browser/lib/runtime.ts +204 -15
- package/extensions/agent-browser/lib/temp.ts +131 -23
- package/package.json +14 -8
- package/scripts/agent-browser-capability-baseline.mjs +104 -0
- package/scripts/doctor.mjs +420 -0
|
@@ -6,11 +6,16 @@
|
|
|
6
6
|
* Invariants/Assumptions: agent-browser is installed separately on PATH, the wrapper targets the current locally installed upstream version only, and no backward-compatibility shims are provided.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { rm } from "node:fs/promises";
|
|
9
|
+
import { readFile, rm } from "node:fs/promises";
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { StringEnum } from "@mariozechner/pi-ai";
|
|
12
|
+
import { isToolCallEventType, type AgentToolResult, type ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
12
13
|
import { Type } from "typebox";
|
|
13
14
|
|
|
15
|
+
import {
|
|
16
|
+
PROJECT_RULE_PROMPT,
|
|
17
|
+
buildToolPromptGuidelines,
|
|
18
|
+
} from "./lib/playbook.js";
|
|
14
19
|
import { runAgentBrowserProcess } from "./lib/process.js";
|
|
15
20
|
import {
|
|
16
21
|
buildToolPresentation,
|
|
@@ -30,7 +35,9 @@ import {
|
|
|
30
35
|
getImplicitSessionCloseTimeoutMs,
|
|
31
36
|
getImplicitSessionIdleTimeoutMs,
|
|
32
37
|
getLatestUserPrompt,
|
|
38
|
+
hasLaunchScopedTabCorrectionFlag,
|
|
33
39
|
hasUsableBraveApiKey,
|
|
40
|
+
extractExplicitSessionName,
|
|
34
41
|
redactInvocationArgs,
|
|
35
42
|
redactSensitiveText,
|
|
36
43
|
redactSensitiveValue,
|
|
@@ -41,7 +48,20 @@ import {
|
|
|
41
48
|
type CompatibilityWorkaround,
|
|
42
49
|
type OpenResultTabCorrection,
|
|
43
50
|
} from "./lib/runtime.js";
|
|
44
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
cleanupSecureTempArtifacts,
|
|
53
|
+
type PersistentSessionArtifactEviction,
|
|
54
|
+
type PersistentSessionArtifactStore,
|
|
55
|
+
writePersistentSessionArtifactFile,
|
|
56
|
+
writeSecureTempFile,
|
|
57
|
+
} from "./lib/temp.js";
|
|
58
|
+
import {
|
|
59
|
+
type SessionArtifactManifest,
|
|
60
|
+
buildEvictedSessionArtifactEntries,
|
|
61
|
+
formatSessionArtifactRetentionSummary,
|
|
62
|
+
isSessionArtifactManifest,
|
|
63
|
+
mergeSessionArtifactManifest,
|
|
64
|
+
} from "./lib/results/shared.js";
|
|
45
65
|
|
|
46
66
|
const DEFAULT_SESSION_MODE = "auto" as const;
|
|
47
67
|
|
|
@@ -50,54 +70,20 @@ const AGENT_BROWSER_PARAMS = Type.Object({
|
|
|
50
70
|
description: "Exact agent-browser CLI arguments, excluding the binary name and any shell operators.",
|
|
51
71
|
minItems: 1,
|
|
52
72
|
}),
|
|
53
|
-
stdin: Type.Optional(Type.String({ description: "Optional raw stdin content for
|
|
73
|
+
stdin: Type.Optional(Type.String({ description: "Optional raw stdin content; only supported for batch and eval --stdin." })),
|
|
54
74
|
sessionMode: Type.Optional(
|
|
55
|
-
|
|
75
|
+
StringEnum(["auto", "fresh"] as const, {
|
|
56
76
|
description:
|
|
57
|
-
"Session handling mode. `auto` reuses the extension-managed pi-scoped session when possible. `fresh` switches that managed session to a fresh upstream launch so
|
|
77
|
+
"Session handling mode. `auto` reuses the extension-managed pi-scoped session when possible. `fresh` switches that managed session to a fresh upstream launch so launch-scoped flags like --profile, --session-name, --cdp, --state, or --auto-connect apply and later auto calls follow the new browser.",
|
|
58
78
|
default: DEFAULT_SESSION_MODE,
|
|
59
79
|
}),
|
|
60
80
|
),
|
|
61
81
|
});
|
|
62
|
-
const PROJECT_RULE_PROMPT =
|
|
63
|
-
"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.";
|
|
64
|
-
const QUICK_START_GUIDELINES = [
|
|
65
|
-
"Quick start mental model: args are the exact agent-browser CLI args after the binary; stdin is only for batch and eval --stdin; sessionMode=fresh switches the extension-managed session to a fresh upstream launch when you need new --profile, --session-name, or --cdp state.",
|
|
66
|
-
"Common first calls: { args: [\"open\", \"https://example.com\"] } then { args: [\"snapshot\", \"-i\"] }; after navigation, use { args: [\"click\", \"@e2\"] } then { args: [\"snapshot\", \"-i\"] }.",
|
|
67
|
-
"Common advanced calls: { args: [\"batch\"], stdin: \"[[\\\"open\\\",\\\"https://example.com\\\"],[\\\"snapshot\\\",\\\"-i\\\"]]\" }, { args: [\"eval\", \"--stdin\"], stdin: \"document.title\" }, and { args: [\"--profile\", \"Default\", \"open\", \"https://example.com/account\"], sessionMode: \"fresh\" }.",
|
|
68
|
-
"High-value command reference: download <selector> <path> saves a file triggered by a click; get title/url/text/html/value/attr/count reads page state; screenshot [path] captures an image; pdf <path> saves a PDF; tab list and tab <tab-id-or-label> inspect or recover the active tab.",
|
|
69
|
-
] as const;
|
|
70
|
-
const BRAVE_SEARCH_PROMPT_GUIDELINE =
|
|
71
|
-
"When a non-empty BRAVE_API_KEY is available in the current environment, prefer the Brave Search API via bash/curl to discover specific destination URLs, then open the chosen URL with agent_browser instead of browsing a search engine results page just to find the target.";
|
|
72
|
-
const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
|
|
73
|
-
"Standard workflow: open the page, snapshot -i, interact using refs, and re-snapshot after navigation or major DOM changes.",
|
|
74
|
-
"For authenticated or user-specific content like feeds, inboxes, dashboards, and accounts, prefer --profile Default on the first browser call and let the implicit session carry continuity. Use --auto-connect only if profile-based reuse is unavailable or the task is specifically about attaching to a running debug-enabled browser.",
|
|
75
|
-
"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.",
|
|
76
|
-
"When using --profile, --session-name, or --cdp, 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.",
|
|
77
|
-
"If you already used the implicit session and now need startup-scoped flags like --profile, --session-name, or --cdp, retry with sessionMode set to fresh or pass an explicit --session for the new launch. After a successful unnamed fresh launch, later auto calls follow that new session.",
|
|
78
|
-
"If a session lands on the wrong page or tab, an interaction changes origin unexpectedly, or an open call returns blocked, blank, or otherwise unexpected results, use tab list / tab <tab-id-or-label> / snapshot -i to recover state before retrying different URLs or fallback strategies. Only use wait with an explicit argument like milliseconds, --load <state>, --url <matcher>, --fn <js>, or --text <matcher>.",
|
|
79
|
-
"For feed, timeline, or inbox reading tasks, focus on the main timeline/list region and read the first item there rather than unrelated composer or sidebar content.",
|
|
80
|
-
"For read-only browsing tasks, prefer extracting the answer from the current snapshot, structured ref labels, or eval --stdin on the current page before navigating away. Only click into media viewers, detail routes, or new pages when the current view does not contain the needed information.",
|
|
81
|
-
"For downloads, prefer download <selector> <path> when an element click should save a file. Do not rely on click alone when you need the downloaded file on disk.",
|
|
82
|
-
"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.",
|
|
83
|
-
"When using eval --stdin for extraction, return the value you want instead of relying on console.log as the primary result channel.",
|
|
84
|
-
"Do not call --help or other exploratory inspection commands unless the user explicitly asks for them or debugging the browser integration is necessary.",
|
|
85
|
-
] as const;
|
|
86
|
-
const TOOL_PROMPT_GUIDELINES_PREFIX = ["Use this tool whenever the task requires a real browser or live web content."] as const;
|
|
87
|
-
const TOOL_PROMPT_GUIDELINES_SUFFIX = [
|
|
88
|
-
"Prefer this tool over bash for opening sites, reading docs on the web, clicking, filling, screenshots, eval, and batch workflows.",
|
|
89
|
-
"Do not fall back to osascript, AppleScript, or generic browser-driving bash commands when this tool can do the job.",
|
|
90
|
-
"Pass exact agent-browser CLI arguments in args, excluding the binary name.",
|
|
91
|
-
"Use stdin for commands like eval --stdin and batch instead of shell heredocs.",
|
|
92
|
-
"Let the extension-managed session handle the common path unless you explicitly need a fresh launch for upstream flags like --profile, --session-name, or --cdp.",
|
|
93
|
-
"Use sessionMode=fresh when switching from an existing implicit session to a new profile/debug launch without inventing a fixed explicit session name; later auto calls will follow that new session.",
|
|
94
|
-
] as const;
|
|
95
|
-
|
|
96
82
|
function buildMissingBinaryMessage(): string {
|
|
97
83
|
return [
|
|
98
84
|
"agent-browser is required but was not found on PATH.",
|
|
99
85
|
"This project does not bundle agent-browser.",
|
|
100
|
-
"
|
|
86
|
+
"Run `pi-agent-browser-doctor` for package/PATH diagnostics, then install agent-browser using the upstream docs:",
|
|
101
87
|
"- https://agent-browser.dev/",
|
|
102
88
|
"- https://github.com/vercel-labs/agent-browser",
|
|
103
89
|
].join("\n");
|
|
@@ -108,6 +94,20 @@ function buildInvocationPreview(effectiveArgs: string[]): string {
|
|
|
108
94
|
return preview.length > 120 ? `${preview.slice(0, 117)}...` : preview;
|
|
109
95
|
}
|
|
110
96
|
|
|
97
|
+
function buildWrapperRecoveryHint(options: {
|
|
98
|
+
pinnedBatchUnwrapMode?: PinnedBatchUnwrapMode;
|
|
99
|
+
sessionTabCorrection?: OpenResultTabCorrection;
|
|
100
|
+
}): string | undefined {
|
|
101
|
+
const wrapperManagedContexts = [
|
|
102
|
+
options.sessionTabCorrection ? "session tab correction" : undefined,
|
|
103
|
+
options.pinnedBatchUnwrapMode ? "pinned batch routing" : undefined,
|
|
104
|
+
].filter((item): item is string => item !== undefined);
|
|
105
|
+
if (wrapperManagedContexts.length === 0) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
return `Wrapper recovery hint: this call used ${wrapperManagedContexts.join(" and ")}. Inspect details.effectiveArgs and details.sessionTabCorrection; if the selected tab looks wrong, run tab list for the same session before retrying.`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
111
|
const DIRECT_AGENT_BROWSER_EXECUTABLE_PATTERN = /^(?:[.~]|\.\.?|\/)?(?:[^\s;&|]+\/)?agent-browser$/;
|
|
112
112
|
const HARMLESS_AGENT_BROWSER_INSPECTION_PATTERN = /^\s*(?:command\s+-v|which|type\s+-P)\s+agent-browser\s*$/;
|
|
113
113
|
|
|
@@ -323,14 +323,54 @@ function extractStringResultField(data: unknown, fieldName: "title" | "url"): st
|
|
|
323
323
|
return text.length > 0 ? text : undefined;
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
-
const SESSION_TAB_PINNING_EXCLUDED_COMMANDS = new Set(["
|
|
326
|
+
const SESSION_TAB_PINNING_EXCLUDED_COMMANDS = new Set(["close", "goto", "navigate", "open", "session", "tab"]);
|
|
327
327
|
const SESSION_TAB_POST_COMMAND_CORRECTION_EXCLUDED_COMMANDS = new Set(["batch", "close", "session", "tab"]);
|
|
328
328
|
|
|
329
|
+
type PinnedBatchUnwrapMode = "single-command" | "user-batch";
|
|
330
|
+
|
|
331
|
+
type AgentBrowserToolResult = AgentToolResult<unknown> & { isError?: boolean };
|
|
332
|
+
|
|
333
|
+
type BatchCommandStep = [string, ...string[]];
|
|
334
|
+
|
|
335
|
+
interface PinnedBatchPlan {
|
|
336
|
+
includeNavigationSummary: boolean;
|
|
337
|
+
steps: BatchCommandStep[];
|
|
338
|
+
unwrapMode: PinnedBatchUnwrapMode;
|
|
339
|
+
}
|
|
340
|
+
|
|
329
341
|
interface SessionTabTarget {
|
|
330
342
|
title?: string;
|
|
331
343
|
url: string;
|
|
332
344
|
}
|
|
333
345
|
|
|
346
|
+
interface OrderedSessionTabTarget {
|
|
347
|
+
order: number;
|
|
348
|
+
target: SessionTabTarget;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
interface AboutBlankSessionMismatch {
|
|
352
|
+
activeUrl: "about:blank";
|
|
353
|
+
recoveryApplied: boolean;
|
|
354
|
+
recoveryHint: string;
|
|
355
|
+
targetTitle?: string;
|
|
356
|
+
targetUrl: string;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function getLatestSessionTabTargetOrder(targets: Map<string, OrderedSessionTabTarget>): number {
|
|
360
|
+
let latestOrder = 0;
|
|
361
|
+
for (const target of targets.values()) {
|
|
362
|
+
latestOrder = Math.max(latestOrder, target.order);
|
|
363
|
+
}
|
|
364
|
+
return latestOrder;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function shouldApplySessionTabTargetUpdate(options: {
|
|
368
|
+
current?: OrderedSessionTabTarget;
|
|
369
|
+
updateOrder: number;
|
|
370
|
+
}): boolean {
|
|
371
|
+
return !options.current || options.updateOrder >= options.current.order;
|
|
372
|
+
}
|
|
373
|
+
|
|
334
374
|
function normalizeComparableUrl(url: string | undefined): string | undefined {
|
|
335
375
|
const normalizedUrl = url?.trim();
|
|
336
376
|
if (!normalizedUrl) {
|
|
@@ -345,6 +385,26 @@ function normalizeComparableUrl(url: string | undefined): string | undefined {
|
|
|
345
385
|
}
|
|
346
386
|
}
|
|
347
387
|
|
|
388
|
+
function isAboutBlankUrl(url: string | undefined): boolean {
|
|
389
|
+
return normalizeComparableUrl(url) === "about:blank";
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function isAboutBlankSessionTabTarget(target: SessionTabTarget | undefined): boolean {
|
|
393
|
+
return isAboutBlankUrl(target?.url);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function commandExplicitlyTargetsAboutBlank(commandTokens: string[]): boolean {
|
|
397
|
+
return commandTokens.some((token) => isAboutBlankUrl(token));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function buildAboutBlankRecoveryHint(): string {
|
|
401
|
+
return "agent_browser detected that the active tab became about:blank while this session still had a prior intended tab. Run tab list for this session and re-select the intended tab, or retry with sessionMode=fresh if the tab is gone.";
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function buildAboutBlankWarning(mismatch: AboutBlankSessionMismatch): string {
|
|
405
|
+
return `Warning: agent_browser detected that this session returned about:blank while the prior intended tab was ${mismatch.targetUrl}. ${mismatch.recoveryApplied ? "The wrapper re-selected the intended tab for the session." : "No matching tab could be re-selected; run tab list for the same session or retry with sessionMode=fresh."}`;
|
|
406
|
+
}
|
|
407
|
+
|
|
348
408
|
function normalizeSessionTabTarget(target: { title?: string; url?: string } | undefined): SessionTabTarget | undefined {
|
|
349
409
|
if (!target) {
|
|
350
410
|
return undefined;
|
|
@@ -371,8 +431,50 @@ function extractSessionTabTargetFromData(data: unknown): SessionTabTarget | unde
|
|
|
371
431
|
return undefined;
|
|
372
432
|
}
|
|
373
433
|
|
|
374
|
-
function
|
|
375
|
-
|
|
434
|
+
function extractBatchResultCommand(item: Record<string, unknown>): string[] {
|
|
435
|
+
return Array.isArray(item.command) ? item.command.filter((token): token is string => typeof token === "string") : [];
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function extractSessionTabTargetFromBatchResults(data: unknown): SessionTabTarget | undefined {
|
|
439
|
+
if (!Array.isArray(data)) {
|
|
440
|
+
return undefined;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
let currentTarget: SessionTabTarget | undefined;
|
|
444
|
+
let pendingTitle: string | undefined;
|
|
445
|
+
for (const item of data) {
|
|
446
|
+
if (!isRecord(item) || item.success === false) {
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
const [name, subcommand] = extractBatchResultCommand(item);
|
|
450
|
+
const result = item.result;
|
|
451
|
+
|
|
452
|
+
if (name === "get" && subcommand === "title") {
|
|
453
|
+
pendingTitle = extractStringResultField(result, "title");
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (name === "get" && subcommand === "url") {
|
|
457
|
+
const url = extractStringResultField(result, "url");
|
|
458
|
+
const target = normalizeSessionTabTarget({ title: pendingTitle, url });
|
|
459
|
+
if (target) {
|
|
460
|
+
currentTarget = target;
|
|
461
|
+
}
|
|
462
|
+
pendingTitle = undefined;
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const resultTarget = extractSessionTabTargetFromData(result);
|
|
467
|
+
if (resultTarget) {
|
|
468
|
+
currentTarget = resultTarget;
|
|
469
|
+
}
|
|
470
|
+
pendingTitle = undefined;
|
|
471
|
+
}
|
|
472
|
+
return currentTarget;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function restoreSessionTabTargetsFromBranch(branch: unknown[]): Map<string, OrderedSessionTabTarget> {
|
|
476
|
+
const restoredTargets = new Map<string, OrderedSessionTabTarget>();
|
|
477
|
+
let restoredOrder = 0;
|
|
376
478
|
for (const entry of branch) {
|
|
377
479
|
if (!isRecord(entry) || entry.type !== "message") {
|
|
378
480
|
continue;
|
|
@@ -391,6 +493,7 @@ function restoreSessionTabTargetsFromBranch(branch: unknown[]): Map<string, Sess
|
|
|
391
493
|
}
|
|
392
494
|
const command = typeof details.command === "string" ? details.command : undefined;
|
|
393
495
|
if (command === "close" && message.isError !== true) {
|
|
496
|
+
restoredOrder += 1;
|
|
394
497
|
restoredTargets.delete(sessionName);
|
|
395
498
|
continue;
|
|
396
499
|
}
|
|
@@ -401,21 +504,152 @@ function restoreSessionTabTargetsFromBranch(branch: unknown[]): Map<string, Sess
|
|
|
401
504
|
})
|
|
402
505
|
: undefined;
|
|
403
506
|
if (sessionTabTarget) {
|
|
404
|
-
|
|
507
|
+
restoredOrder += 1;
|
|
508
|
+
restoredTargets.set(sessionName, { order: restoredOrder, target: sessionTabTarget });
|
|
405
509
|
}
|
|
406
510
|
}
|
|
407
511
|
return restoredTargets;
|
|
408
512
|
}
|
|
409
513
|
|
|
410
|
-
function
|
|
514
|
+
function restoreArtifactManifestFromBranch(branch: unknown[]): SessionArtifactManifest | undefined {
|
|
515
|
+
let restoredManifest: SessionArtifactManifest | undefined;
|
|
516
|
+
for (const entry of branch) {
|
|
517
|
+
if (!isRecord(entry) || entry.type !== "message") continue;
|
|
518
|
+
const message = isRecord(entry.message) ? entry.message : undefined;
|
|
519
|
+
if (!message || message.toolName !== "agent_browser") continue;
|
|
520
|
+
const details = isRecord(message.details) ? message.details : undefined;
|
|
521
|
+
if (isSessionArtifactManifest(details?.artifactManifest)) {
|
|
522
|
+
restoredManifest = details.artifactManifest;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return restoredManifest;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function validateStdinCommandContract(options: { command?: string; commandTokens: string[]; stdin?: string }): string | undefined {
|
|
529
|
+
if (options.stdin === undefined) {
|
|
530
|
+
return undefined;
|
|
531
|
+
}
|
|
532
|
+
if (options.command === "batch") {
|
|
533
|
+
return undefined;
|
|
534
|
+
}
|
|
535
|
+
if (options.command === "eval" && options.commandTokens.includes("--stdin")) {
|
|
536
|
+
return undefined;
|
|
537
|
+
}
|
|
538
|
+
const commandLabel = options.command ? `\`${options.command}\`` : "the requested command";
|
|
539
|
+
return `agent_browser stdin is only supported for \`batch\` and \`eval --stdin\`; remove stdin from ${commandLabel} or use one of those command forms.`;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function supportsPinnedStdinCommand(options: { command?: string; commandTokens: string[]; stdin?: string }): boolean {
|
|
543
|
+
if (options.command === "batch") {
|
|
544
|
+
return options.stdin !== undefined;
|
|
545
|
+
}
|
|
546
|
+
if (options.stdin === undefined) {
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
549
|
+
if (options.command === "eval") {
|
|
550
|
+
return options.commandTokens.includes("--stdin");
|
|
551
|
+
}
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function shouldPinSessionTabForCommand(options: {
|
|
556
|
+
command?: string;
|
|
557
|
+
commandTokens: string[];
|
|
558
|
+
sessionName?: string;
|
|
559
|
+
stdin?: string;
|
|
560
|
+
}): boolean {
|
|
411
561
|
return (
|
|
412
562
|
options.sessionName !== undefined &&
|
|
413
|
-
options.stdin === undefined &&
|
|
414
563
|
options.command !== undefined &&
|
|
415
|
-
!SESSION_TAB_PINNING_EXCLUDED_COMMANDS.has(options.command)
|
|
564
|
+
!SESSION_TAB_PINNING_EXCLUDED_COMMANDS.has(options.command) &&
|
|
565
|
+
supportsPinnedStdinCommand(options)
|
|
416
566
|
);
|
|
417
567
|
}
|
|
418
568
|
|
|
569
|
+
function validateUserBatchStep(
|
|
570
|
+
step: unknown,
|
|
571
|
+
index: number,
|
|
572
|
+
):
|
|
573
|
+
| { ok: true; step: BatchCommandStep }
|
|
574
|
+
| { ok: false; error: string } {
|
|
575
|
+
if (!Array.isArray(step)) {
|
|
576
|
+
return {
|
|
577
|
+
ok: false,
|
|
578
|
+
error: `agent_browser batch stdin step ${index} must be a non-empty array of string command tokens.`,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
if (step.length === 0) {
|
|
582
|
+
return {
|
|
583
|
+
ok: false,
|
|
584
|
+
error: `agent_browser batch stdin step ${index} must not be empty.`,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
const invalidTokenIndex = step.findIndex((token) => typeof token !== "string");
|
|
588
|
+
if (invalidTokenIndex !== -1) {
|
|
589
|
+
return {
|
|
590
|
+
ok: false,
|
|
591
|
+
error: `agent_browser batch stdin step ${index} token ${invalidTokenIndex} must be a string.`,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
return { ok: true, step: step as BatchCommandStep };
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function parseUserBatchStdin(stdin: string | undefined): { error?: string; steps?: BatchCommandStep[] } {
|
|
598
|
+
if (stdin === undefined) {
|
|
599
|
+
return { steps: [] };
|
|
600
|
+
}
|
|
601
|
+
try {
|
|
602
|
+
const parsed = JSON.parse(stdin) as unknown;
|
|
603
|
+
if (!Array.isArray(parsed)) {
|
|
604
|
+
return { error: "agent_browser batch stdin must be a JSON array of command steps." };
|
|
605
|
+
}
|
|
606
|
+
const steps: BatchCommandStep[] = [];
|
|
607
|
+
for (const [index, rawStep] of parsed.entries()) {
|
|
608
|
+
const validated = validateUserBatchStep(rawStep, index);
|
|
609
|
+
if (!validated.ok) {
|
|
610
|
+
return { error: validated.error };
|
|
611
|
+
}
|
|
612
|
+
steps.push(validated.step);
|
|
613
|
+
}
|
|
614
|
+
return { steps };
|
|
615
|
+
} catch (error) {
|
|
616
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
617
|
+
return { error: `agent_browser batch stdin could not be parsed as JSON: ${message}` };
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function buildPinnedBatchPlan(options: {
|
|
622
|
+
command?: string;
|
|
623
|
+
commandTokens: string[];
|
|
624
|
+
selectedTab: string;
|
|
625
|
+
stdin?: string;
|
|
626
|
+
}): PinnedBatchPlan | { error: string } | undefined {
|
|
627
|
+
if (options.command === "batch") {
|
|
628
|
+
const parsed = parseUserBatchStdin(options.stdin);
|
|
629
|
+
if (parsed.error) {
|
|
630
|
+
return { error: parsed.error };
|
|
631
|
+
}
|
|
632
|
+
const tabSelectionStep: BatchCommandStep = ["tab", options.selectedTab];
|
|
633
|
+
return {
|
|
634
|
+
includeNavigationSummary: false,
|
|
635
|
+
steps: [tabSelectionStep, ...(parsed.steps ?? [])],
|
|
636
|
+
unwrapMode: "user-batch",
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
if (options.commandTokens.length === 0) {
|
|
640
|
+
return undefined;
|
|
641
|
+
}
|
|
642
|
+
const includeNavigationSummary = options.command !== undefined && NAVIGATION_SUMMARY_COMMANDS.has(options.command);
|
|
643
|
+
const tabSelectionStep: BatchCommandStep = ["tab", options.selectedTab];
|
|
644
|
+
const commandStep = options.commandTokens as BatchCommandStep;
|
|
645
|
+
const navigationSummarySteps: BatchCommandStep[] = includeNavigationSummary ? [["get", "title"], ["get", "url"]] : [];
|
|
646
|
+
return {
|
|
647
|
+
includeNavigationSummary,
|
|
648
|
+
steps: [tabSelectionStep, commandStep, ...navigationSummarySteps],
|
|
649
|
+
unwrapMode: "single-command",
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
|
|
419
653
|
function shouldCorrectSessionTabAfterCommand(options: { command?: string; sessionName?: string }): boolean {
|
|
420
654
|
return (
|
|
421
655
|
options.sessionName !== undefined &&
|
|
@@ -446,6 +680,7 @@ function deriveSessionTabTarget(options: {
|
|
|
446
680
|
}
|
|
447
681
|
return (
|
|
448
682
|
normalizeSessionTabTarget(options.navigationSummary) ??
|
|
683
|
+
extractSessionTabTargetFromBatchResults(options.data) ??
|
|
449
684
|
extractSessionTabTargetFromData(options.data) ??
|
|
450
685
|
options.previousTarget
|
|
451
686
|
);
|
|
@@ -454,6 +689,7 @@ function deriveSessionTabTarget(options: {
|
|
|
454
689
|
function unwrapPinnedSessionBatchEnvelope(options: {
|
|
455
690
|
envelope?: AgentBrowserEnvelope;
|
|
456
691
|
includeNavigationSummary: boolean;
|
|
692
|
+
mode?: PinnedBatchUnwrapMode;
|
|
457
693
|
}): { envelope?: AgentBrowserEnvelope; navigationSummary?: NavigationSummary; parseError?: string } {
|
|
458
694
|
if (!options.envelope) {
|
|
459
695
|
return {};
|
|
@@ -467,19 +703,29 @@ function unwrapPinnedSessionBatchEnvelope(options: {
|
|
|
467
703
|
const steps = options.envelope.data.filter(isRecord) as AgentBrowserBatchResult[];
|
|
468
704
|
const tabSelectionStep = steps[0];
|
|
469
705
|
const commandStep = steps[1];
|
|
470
|
-
if (
|
|
706
|
+
if (tabSelectionStep?.success === false) {
|
|
471
707
|
return {
|
|
472
708
|
envelope: {
|
|
473
709
|
success: false,
|
|
474
|
-
error: "agent-browser
|
|
710
|
+
error: tabSelectionStep.error ?? "agent-browser could not re-select the intended tab before running the command.",
|
|
475
711
|
},
|
|
476
712
|
};
|
|
477
713
|
}
|
|
478
|
-
if (
|
|
714
|
+
if (options.mode === "user-batch") {
|
|
715
|
+
const userSteps = steps.slice(1);
|
|
716
|
+
return {
|
|
717
|
+
envelope: {
|
|
718
|
+
success: userSteps.every((step) => step.success !== false),
|
|
719
|
+
data: userSteps,
|
|
720
|
+
error: userSteps.find((step) => step.success === false)?.error,
|
|
721
|
+
},
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
if (!commandStep) {
|
|
479
725
|
return {
|
|
480
726
|
envelope: {
|
|
481
727
|
success: false,
|
|
482
|
-
error:
|
|
728
|
+
error: "agent-browser did not return the corrected command result.",
|
|
483
729
|
},
|
|
484
730
|
};
|
|
485
731
|
}
|
|
@@ -514,14 +760,14 @@ async function runSessionCommandData(options: {
|
|
|
514
760
|
cwd,
|
|
515
761
|
signal,
|
|
516
762
|
});
|
|
517
|
-
if (processResult.aborted || processResult.spawnError || processResult.exitCode !== 0) {
|
|
518
|
-
return undefined;
|
|
519
|
-
}
|
|
520
|
-
const parsed = await parseAgentBrowserEnvelope({
|
|
521
|
-
stdout: processResult.stdout,
|
|
522
|
-
stdoutPath: processResult.stdoutSpillPath,
|
|
523
|
-
});
|
|
524
763
|
try {
|
|
764
|
+
if (processResult.aborted || processResult.spawnError || processResult.exitCode !== 0) {
|
|
765
|
+
return undefined;
|
|
766
|
+
}
|
|
767
|
+
const parsed = await parseAgentBrowserEnvelope({
|
|
768
|
+
stdout: processResult.stdout,
|
|
769
|
+
stdoutPath: processResult.stdoutSpillPath,
|
|
770
|
+
});
|
|
525
771
|
if (parsed.parseError || parsed.envelope?.success === false) {
|
|
526
772
|
return undefined;
|
|
527
773
|
}
|
|
@@ -619,23 +865,6 @@ async function applyOpenResultTabCorrection(options: {
|
|
|
619
865
|
return result === undefined ? undefined : correction;
|
|
620
866
|
}
|
|
621
867
|
|
|
622
|
-
function buildSharedBrowserPlaybookGuidelines(hasBraveApiKey: boolean): string[] {
|
|
623
|
-
return [
|
|
624
|
-
SHARED_BROWSER_PLAYBOOK_GUIDELINES[0],
|
|
625
|
-
...(hasBraveApiKey ? [BRAVE_SEARCH_PROMPT_GUIDELINE] : []),
|
|
626
|
-
...SHARED_BROWSER_PLAYBOOK_GUIDELINES.slice(1),
|
|
627
|
-
];
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
function buildToolPromptGuidelines(hasBraveApiKey: boolean): string[] {
|
|
631
|
-
return [
|
|
632
|
-
...TOOL_PROMPT_GUIDELINES_PREFIX,
|
|
633
|
-
...QUICK_START_GUIDELINES,
|
|
634
|
-
...buildSharedBrowserPlaybookGuidelines(hasBraveApiKey),
|
|
635
|
-
...TOOL_PROMPT_GUIDELINES_SUFFIX,
|
|
636
|
-
];
|
|
637
|
-
}
|
|
638
|
-
|
|
639
868
|
function buildSessionDetailFields(sessionName: string | undefined, usedImplicitSession: boolean): Record<string, unknown> {
|
|
640
869
|
return sessionName ? { sessionName, usedImplicitSession } : {};
|
|
641
870
|
}
|
|
@@ -656,6 +885,70 @@ function getPersistentSessionArtifactStore(ctx: {
|
|
|
656
885
|
return { sessionDir, sessionId };
|
|
657
886
|
}
|
|
658
887
|
|
|
888
|
+
async function preserveParseFailureOutput(options: {
|
|
889
|
+
artifactManifest?: SessionArtifactManifest;
|
|
890
|
+
persistentArtifactStore?: PersistentSessionArtifactStore;
|
|
891
|
+
stdoutSpillPath?: string;
|
|
892
|
+
}): Promise<{
|
|
893
|
+
artifactManifest?: SessionArtifactManifest;
|
|
894
|
+
artifactRetentionSummary?: string;
|
|
895
|
+
fullOutputPath?: string;
|
|
896
|
+
fullOutputUnavailable?: string;
|
|
897
|
+
}> {
|
|
898
|
+
if (!options.stdoutSpillPath) {
|
|
899
|
+
return {};
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
try {
|
|
903
|
+
const rawOutput = await readFile(options.stdoutSpillPath);
|
|
904
|
+
const nowMs = Date.now();
|
|
905
|
+
let evictedArtifacts: PersistentSessionArtifactEviction[] = [];
|
|
906
|
+
let fullOutputPath: string;
|
|
907
|
+
let storageScope: "persistent-session" | "process-temp";
|
|
908
|
+
if (options.persistentArtifactStore) {
|
|
909
|
+
const result = await writePersistentSessionArtifactFile({
|
|
910
|
+
content: rawOutput,
|
|
911
|
+
prefix: "pi-agent-browser-parse-failure-output",
|
|
912
|
+
store: options.persistentArtifactStore,
|
|
913
|
+
suffix: ".txt",
|
|
914
|
+
});
|
|
915
|
+
fullOutputPath = result.path;
|
|
916
|
+
evictedArtifacts = result.evictedArtifacts;
|
|
917
|
+
storageScope = "persistent-session";
|
|
918
|
+
} else {
|
|
919
|
+
fullOutputPath = await writeSecureTempFile({
|
|
920
|
+
content: rawOutput,
|
|
921
|
+
prefix: "pi-agent-browser-parse-failure-output",
|
|
922
|
+
suffix: ".txt",
|
|
923
|
+
});
|
|
924
|
+
storageScope = "process-temp";
|
|
925
|
+
}
|
|
926
|
+
const artifactManifest = mergeSessionArtifactManifest({
|
|
927
|
+
base: options.artifactManifest,
|
|
928
|
+
entries: [
|
|
929
|
+
{
|
|
930
|
+
command: "agent-browser",
|
|
931
|
+
createdAtMs: nowMs,
|
|
932
|
+
kind: "spill",
|
|
933
|
+
path: fullOutputPath,
|
|
934
|
+
retentionState: storageScope === "persistent-session" ? "live" : "ephemeral",
|
|
935
|
+
storageScope,
|
|
936
|
+
},
|
|
937
|
+
...buildEvictedSessionArtifactEntries(evictedArtifacts, nowMs),
|
|
938
|
+
],
|
|
939
|
+
nowMs,
|
|
940
|
+
});
|
|
941
|
+
return {
|
|
942
|
+
artifactManifest,
|
|
943
|
+
artifactRetentionSummary: artifactManifest ? formatSessionArtifactRetentionSummary(artifactManifest) : undefined,
|
|
944
|
+
fullOutputPath,
|
|
945
|
+
};
|
|
946
|
+
} catch (error) {
|
|
947
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
948
|
+
return { fullOutputUnavailable: message };
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
659
952
|
function redactRecoveryHint(recoveryHint: {
|
|
660
953
|
exampleArgs: string[];
|
|
661
954
|
exampleParams: { args: string[]; sessionMode: "fresh" };
|
|
@@ -676,26 +969,53 @@ function redactRecoveryHint(recoveryHint: {
|
|
|
676
969
|
};
|
|
677
970
|
}
|
|
678
971
|
|
|
972
|
+
// Serializes managed-session read/modify/write work so overlapping tool calls cannot promote stale state or close an in-use session.
|
|
973
|
+
class AsyncExecutionQueue {
|
|
974
|
+
private tail: Promise<void> = Promise.resolve();
|
|
975
|
+
|
|
976
|
+
run<T>(work: () => Promise<T>): Promise<T> {
|
|
977
|
+
const previous = this.tail;
|
|
978
|
+
let release!: () => void;
|
|
979
|
+
this.tail = new Promise<void>((resolve) => {
|
|
980
|
+
release = resolve;
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
return (async () => {
|
|
984
|
+
await previous;
|
|
985
|
+
try {
|
|
986
|
+
return await work();
|
|
987
|
+
} finally {
|
|
988
|
+
release();
|
|
989
|
+
}
|
|
990
|
+
})();
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
679
994
|
async function closeManagedSession(options: { cwd: string; sessionName: string; timeoutMs: number }): Promise<void> {
|
|
680
995
|
const controller = new AbortController();
|
|
681
996
|
const timer = setTimeout(() => controller.abort(), options.timeoutMs);
|
|
997
|
+
let stdoutSpillPath: string | undefined;
|
|
682
998
|
try {
|
|
683
|
-
await runAgentBrowserProcess({
|
|
999
|
+
const processResult = await runAgentBrowserProcess({
|
|
684
1000
|
args: ["--session", options.sessionName, "close"],
|
|
685
1001
|
cwd: options.cwd,
|
|
686
1002
|
signal: controller.signal,
|
|
687
1003
|
});
|
|
1004
|
+
stdoutSpillPath = processResult.stdoutSpillPath;
|
|
688
1005
|
} catch {
|
|
689
1006
|
// Best-effort cleanup only.
|
|
690
1007
|
} finally {
|
|
691
1008
|
clearTimeout(timer);
|
|
1009
|
+
if (stdoutSpillPath) {
|
|
1010
|
+
await rm(stdoutSpillPath, { force: true }).catch(() => undefined);
|
|
1011
|
+
}
|
|
692
1012
|
}
|
|
693
1013
|
}
|
|
694
1014
|
|
|
695
1015
|
export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
696
1016
|
const ephemeralSessionSeed = createEphemeralSessionSeed();
|
|
697
1017
|
const hasBraveApiKey = hasUsableBraveApiKey();
|
|
698
|
-
const toolPromptGuidelines = buildToolPromptGuidelines(hasBraveApiKey);
|
|
1018
|
+
const toolPromptGuidelines = buildToolPromptGuidelines({ includeBraveSearch: hasBraveApiKey });
|
|
699
1019
|
const implicitSessionIdleTimeoutMs = getImplicitSessionIdleTimeoutMs();
|
|
700
1020
|
const implicitSessionCloseTimeoutMs = getImplicitSessionCloseTimeoutMs();
|
|
701
1021
|
let managedSessionActive = false;
|
|
@@ -703,7 +1023,10 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
703
1023
|
let managedSessionName = managedSessionBaseName;
|
|
704
1024
|
let managedSessionCwd = process.cwd();
|
|
705
1025
|
let freshSessionOrdinal = 0;
|
|
706
|
-
let sessionTabTargets = new Map<string,
|
|
1026
|
+
let sessionTabTargets = new Map<string, OrderedSessionTabTarget>();
|
|
1027
|
+
let sessionTabTargetUpdateOrder = 0;
|
|
1028
|
+
let artifactManifest: SessionArtifactManifest | undefined;
|
|
1029
|
+
const managedSessionExecutionQueue = new AsyncExecutionQueue();
|
|
707
1030
|
|
|
708
1031
|
pi.on("session_start", async (_event, ctx) => {
|
|
709
1032
|
managedSessionBaseName = createImplicitSessionName(ctx.sessionManager.getSessionId(), ctx.cwd, ephemeralSessionSeed);
|
|
@@ -713,11 +1036,15 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
713
1036
|
managedSessionCwd = ctx.cwd;
|
|
714
1037
|
freshSessionOrdinal = restoredState.freshSessionOrdinal;
|
|
715
1038
|
sessionTabTargets = restoreSessionTabTargetsFromBranch(ctx.sessionManager.getBranch());
|
|
1039
|
+
sessionTabTargetUpdateOrder = getLatestSessionTabTargetOrder(sessionTabTargets);
|
|
1040
|
+
artifactManifest = restoreArtifactManifestFromBranch(ctx.sessionManager.getBranch());
|
|
716
1041
|
});
|
|
717
1042
|
|
|
718
1043
|
pi.on("session_shutdown", async () => {
|
|
719
1044
|
managedSessionActive = false;
|
|
720
|
-
sessionTabTargets = new Map<string,
|
|
1045
|
+
sessionTabTargets = new Map<string, OrderedSessionTabTarget>();
|
|
1046
|
+
sessionTabTargetUpdateOrder = 0;
|
|
1047
|
+
artifactManifest = undefined;
|
|
721
1048
|
await cleanupSecureTempArtifacts();
|
|
722
1049
|
});
|
|
723
1050
|
|
|
@@ -765,312 +1092,488 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
765
1092
|
};
|
|
766
1093
|
}
|
|
767
1094
|
|
|
768
|
-
const
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
freshSessionName,
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
1095
|
+
const tabTargetUpdateOrder = ++sessionTabTargetUpdateOrder;
|
|
1096
|
+
const runTool = async (): Promise<AgentBrowserToolResult> => {
|
|
1097
|
+
const sessionMode = params.sessionMode ?? DEFAULT_SESSION_MODE;
|
|
1098
|
+
const freshSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal + 1);
|
|
1099
|
+
const executionPlan = buildExecutionPlan(params.args, {
|
|
1100
|
+
freshSessionName,
|
|
1101
|
+
managedSessionActive,
|
|
1102
|
+
managedSessionName,
|
|
1103
|
+
sessionMode,
|
|
1104
|
+
});
|
|
1105
|
+
const redactedEffectiveArgs = redactInvocationArgs(executionPlan.effectiveArgs);
|
|
1106
|
+
const redactedRecoveryHint = redactRecoveryHint(executionPlan.recoveryHint);
|
|
1107
|
+
const compatibilityWorkaround: CompatibilityWorkaround | undefined = executionPlan.compatibilityWorkaround;
|
|
1108
|
+
if (executionPlan.managedSessionName === freshSessionName) {
|
|
1109
|
+
freshSessionOrdinal += 1;
|
|
1110
|
+
}
|
|
782
1111
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
1112
|
+
if (executionPlan.validationError) {
|
|
1113
|
+
return {
|
|
1114
|
+
content: [{ type: "text", text: executionPlan.validationError }],
|
|
1115
|
+
details: {
|
|
1116
|
+
args: redactedArgs,
|
|
1117
|
+
invalidValueFlag: executionPlan.invalidValueFlag,
|
|
1118
|
+
sessionMode,
|
|
1119
|
+
sessionRecoveryHint: redactedRecoveryHint,
|
|
1120
|
+
startupScopedFlags: executionPlan.startupScopedFlags,
|
|
1121
|
+
validationError: executionPlan.validationError,
|
|
1122
|
+
},
|
|
1123
|
+
isError: true,
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
797
1126
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
executionPlan.commandInfo.command !== undefined && NAVIGATION_SUMMARY_COMMANDS.has(executionPlan.commandInfo.command);
|
|
801
|
-
let sessionTabCorrection: OpenResultTabCorrection | undefined;
|
|
802
|
-
let processArgs = executionPlan.effectiveArgs;
|
|
803
|
-
let processStdin = params.stdin;
|
|
804
|
-
if (
|
|
805
|
-
priorSessionTabTarget &&
|
|
806
|
-
shouldPinSessionTabForCommand({
|
|
1127
|
+
const commandTokens = extractCommandTokens(params.args);
|
|
1128
|
+
const stdinValidationError = validateStdinCommandContract({
|
|
807
1129
|
command: executionPlan.commandInfo.command,
|
|
808
|
-
|
|
1130
|
+
commandTokens,
|
|
809
1131
|
stdin: params.stdin,
|
|
810
|
-
})
|
|
811
|
-
) {
|
|
812
|
-
const plannedSessionTabSelection = await collectSessionTabSelection({
|
|
813
|
-
cwd: ctx.cwd,
|
|
814
|
-
sessionName: executionPlan.sessionName,
|
|
815
|
-
signal,
|
|
816
|
-
target: priorSessionTabTarget,
|
|
817
1132
|
});
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
1133
|
+
if (stdinValidationError) {
|
|
1134
|
+
return {
|
|
1135
|
+
content: [{ type: "text", text: stdinValidationError }],
|
|
1136
|
+
details: {
|
|
1137
|
+
args: redactedArgs,
|
|
1138
|
+
command: executionPlan.commandInfo.command,
|
|
1139
|
+
compatibilityWorkaround,
|
|
1140
|
+
effectiveArgs: redactedEffectiveArgs,
|
|
1141
|
+
sessionMode,
|
|
1142
|
+
validationError: stdinValidationError,
|
|
1143
|
+
...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
|
|
1144
|
+
},
|
|
1145
|
+
isError: true,
|
|
1146
|
+
};
|
|
827
1147
|
}
|
|
828
|
-
}
|
|
829
|
-
const redactedProcessArgs = redactInvocationArgs(processArgs);
|
|
830
1148
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1149
|
+
const priorSessionTabTargetState = executionPlan.sessionName ? sessionTabTargets.get(executionPlan.sessionName) : undefined;
|
|
1150
|
+
const priorSessionTabTarget = priorSessionTabTargetState?.target;
|
|
1151
|
+
let pinnedBatchUnwrapMode: PinnedBatchUnwrapMode | undefined;
|
|
1152
|
+
let includePinnedNavigationSummary = false;
|
|
1153
|
+
let sessionTabCorrection: OpenResultTabCorrection | undefined;
|
|
1154
|
+
let processArgs = executionPlan.effectiveArgs;
|
|
1155
|
+
let processStdin = params.stdin;
|
|
1156
|
+
if (
|
|
1157
|
+
priorSessionTabTarget &&
|
|
1158
|
+
shouldPinSessionTabForCommand({
|
|
1159
|
+
command: executionPlan.commandInfo.command,
|
|
1160
|
+
commandTokens,
|
|
1161
|
+
sessionName: executionPlan.sessionName,
|
|
1162
|
+
stdin: params.stdin,
|
|
1163
|
+
})
|
|
1164
|
+
) {
|
|
1165
|
+
const plannedSessionTabSelection = await collectSessionTabSelection({
|
|
1166
|
+
cwd: ctx.cwd,
|
|
1167
|
+
sessionName: executionPlan.sessionName,
|
|
1168
|
+
signal,
|
|
1169
|
+
target: priorSessionTabTarget,
|
|
1170
|
+
});
|
|
1171
|
+
if (plannedSessionTabSelection && executionPlan.sessionName) {
|
|
1172
|
+
if (executionPlan.commandInfo.command === "eval" && params.stdin !== undefined) {
|
|
1173
|
+
const appliedSessionTabSelection = await applyOpenResultTabCorrection({
|
|
1174
|
+
correction: plannedSessionTabSelection,
|
|
1175
|
+
cwd: ctx.cwd,
|
|
1176
|
+
sessionName: executionPlan.sessionName,
|
|
1177
|
+
signal,
|
|
1178
|
+
});
|
|
1179
|
+
if (!appliedSessionTabSelection) {
|
|
1180
|
+
const error = "agent-browser could not re-select the intended tab before running the command.";
|
|
1181
|
+
return {
|
|
1182
|
+
content: [{ type: "text", text: error }],
|
|
1183
|
+
details: {
|
|
1184
|
+
args: redactedArgs,
|
|
1185
|
+
command: executionPlan.commandInfo.command,
|
|
1186
|
+
compatibilityWorkaround,
|
|
1187
|
+
effectiveArgs: redactedEffectiveArgs,
|
|
1188
|
+
sessionMode,
|
|
1189
|
+
sessionTabCorrection: plannedSessionTabSelection,
|
|
1190
|
+
validationError: error,
|
|
1191
|
+
...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
|
|
1192
|
+
},
|
|
1193
|
+
isError: true,
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
sessionTabCorrection = appliedSessionTabSelection;
|
|
1197
|
+
} else {
|
|
1198
|
+
const pinnedBatchPlan = buildPinnedBatchPlan({
|
|
1199
|
+
command: executionPlan.commandInfo.command,
|
|
1200
|
+
commandTokens,
|
|
1201
|
+
selectedTab: plannedSessionTabSelection.selectedTab,
|
|
1202
|
+
stdin: params.stdin,
|
|
1203
|
+
});
|
|
1204
|
+
if (pinnedBatchPlan && "error" in pinnedBatchPlan) {
|
|
1205
|
+
return {
|
|
1206
|
+
content: [{ type: "text", text: pinnedBatchPlan.error }],
|
|
1207
|
+
details: {
|
|
1208
|
+
args: redactedArgs,
|
|
1209
|
+
command: executionPlan.commandInfo.command,
|
|
1210
|
+
compatibilityWorkaround,
|
|
1211
|
+
effectiveArgs: redactedEffectiveArgs,
|
|
1212
|
+
sessionMode,
|
|
1213
|
+
sessionTabCorrection: plannedSessionTabSelection,
|
|
1214
|
+
validationError: pinnedBatchPlan.error,
|
|
1215
|
+
...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
|
|
1216
|
+
},
|
|
1217
|
+
isError: true,
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
if (pinnedBatchPlan) {
|
|
1221
|
+
sessionTabCorrection = plannedSessionTabSelection;
|
|
1222
|
+
processArgs = ["--json", "--session", executionPlan.sessionName, "batch"];
|
|
1223
|
+
processStdin = JSON.stringify(pinnedBatchPlan.steps);
|
|
1224
|
+
includePinnedNavigationSummary = pinnedBatchPlan.includeNavigationSummary;
|
|
1225
|
+
pinnedBatchUnwrapMode = pinnedBatchPlan.unwrapMode;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
const redactedProcessArgs = redactInvocationArgs(processArgs);
|
|
849
1231
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
return {
|
|
853
|
-
content: [{ type: "text", text: errorText }],
|
|
1232
|
+
onUpdate?.({
|
|
1233
|
+
content: [{ type: "text", text: `Running agent-browser ${buildInvocationPreview(redactedProcessArgs)}` }],
|
|
854
1234
|
details: {
|
|
855
|
-
args: redactedArgs,
|
|
856
1235
|
compatibilityWorkaround,
|
|
857
1236
|
effectiveArgs: redactedProcessArgs,
|
|
858
1237
|
sessionMode,
|
|
859
1238
|
sessionTabCorrection,
|
|
860
|
-
|
|
1239
|
+
...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
|
|
861
1240
|
},
|
|
862
|
-
|
|
863
|
-
};
|
|
864
|
-
}
|
|
1241
|
+
});
|
|
865
1242
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
1243
|
+
const processResult = await runAgentBrowserProcess({
|
|
1244
|
+
args: processArgs,
|
|
1245
|
+
cwd: ctx.cwd,
|
|
1246
|
+
env: executionPlan.managedSessionName ? { AGENT_BROWSER_IDLE_TIMEOUT_MS: implicitSessionIdleTimeoutMs } : undefined,
|
|
1247
|
+
signal,
|
|
1248
|
+
stdin: processStdin,
|
|
870
1249
|
});
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
const parseSucceeded = plainTextInspection || parseError === undefined;
|
|
886
|
-
const envelopeSuccess = plainTextInspection ? true : presentationEnvelope?.success !== false;
|
|
887
|
-
const succeeded = processSucceeded && parseSucceeded && envelopeSuccess;
|
|
888
|
-
const inspectionText = plainTextInspection ? processResult.stdout.trim() : undefined;
|
|
889
|
-
|
|
890
|
-
if (succeeded && !navigationSummary && shouldCaptureNavigationSummary(executionPlan.commandInfo.command, presentationEnvelope?.data)) {
|
|
891
|
-
navigationSummary = await collectNavigationSummary({
|
|
892
|
-
cwd: ctx.cwd,
|
|
893
|
-
sessionName: executionPlan.sessionName,
|
|
894
|
-
signal,
|
|
895
|
-
});
|
|
896
|
-
}
|
|
897
|
-
if (navigationSummary && presentationEnvelope) {
|
|
898
|
-
presentationEnvelope = {
|
|
899
|
-
...presentationEnvelope,
|
|
900
|
-
data: mergeNavigationSummaryIntoData(presentationEnvelope.data, navigationSummary),
|
|
1250
|
+
|
|
1251
|
+
if (processResult.spawnError?.message.includes("ENOENT")) {
|
|
1252
|
+
const errorText = buildMissingBinaryMessage();
|
|
1253
|
+
return {
|
|
1254
|
+
content: [{ type: "text", text: errorText }],
|
|
1255
|
+
details: {
|
|
1256
|
+
args: redactedArgs,
|
|
1257
|
+
compatibilityWorkaround,
|
|
1258
|
+
effectiveArgs: redactedProcessArgs,
|
|
1259
|
+
sessionMode,
|
|
1260
|
+
sessionTabCorrection,
|
|
1261
|
+
spawnError: processResult.spawnError.message,
|
|
1262
|
+
},
|
|
1263
|
+
isError: true,
|
|
901
1264
|
};
|
|
902
1265
|
}
|
|
903
1266
|
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
(executionPlan.commandInfo.command === "goto" ||
|
|
910
|
-
executionPlan.commandInfo.command === "navigate" ||
|
|
911
|
-
executionPlan.commandInfo.command === "open")
|
|
912
|
-
) {
|
|
913
|
-
const targetTitle = extractStringResultField(presentationEnvelope?.data, "title");
|
|
914
|
-
const targetUrl = extractStringResultField(presentationEnvelope?.data, "url");
|
|
915
|
-
const plannedTabCorrection = await collectOpenResultTabCorrection({
|
|
916
|
-
cwd: ctx.cwd,
|
|
917
|
-
sessionName: executionPlan.sessionName,
|
|
918
|
-
signal,
|
|
919
|
-
targetTitle,
|
|
920
|
-
targetUrl,
|
|
1267
|
+
try {
|
|
1268
|
+
const persistentArtifactStore = getPersistentSessionArtifactStore(ctx);
|
|
1269
|
+
const parsed = await parseAgentBrowserEnvelope({
|
|
1270
|
+
stdout: processResult.stdout,
|
|
1271
|
+
stdoutPath: processResult.stdoutSpillPath,
|
|
921
1272
|
});
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1273
|
+
let parseError = parsed.parseError;
|
|
1274
|
+
let presentationEnvelope = parsed.envelope;
|
|
1275
|
+
let navigationSummary: NavigationSummary | undefined;
|
|
1276
|
+
if (pinnedBatchUnwrapMode) {
|
|
1277
|
+
const pinnedBatchResult = unwrapPinnedSessionBatchEnvelope({
|
|
1278
|
+
envelope: parsed.envelope,
|
|
1279
|
+
includeNavigationSummary: includePinnedNavigationSummary,
|
|
1280
|
+
mode: pinnedBatchUnwrapMode,
|
|
1281
|
+
});
|
|
1282
|
+
parseError = pinnedBatchResult.parseError ?? parseError;
|
|
1283
|
+
presentationEnvelope = pinnedBatchResult.envelope ?? presentationEnvelope;
|
|
1284
|
+
navigationSummary = pinnedBatchResult.navigationSummary;
|
|
1285
|
+
}
|
|
1286
|
+
const parseFailureOutput = parseError
|
|
1287
|
+
? await preserveParseFailureOutput({
|
|
1288
|
+
artifactManifest,
|
|
1289
|
+
persistentArtifactStore,
|
|
1290
|
+
stdoutSpillPath: processResult.stdoutSpillPath,
|
|
1291
|
+
})
|
|
1292
|
+
: {};
|
|
1293
|
+
const processSucceeded = !processResult.aborted && !processResult.spawnError && processResult.exitCode === 0;
|
|
1294
|
+
const plainTextInspection = executionPlan.plainTextInspection && processSucceeded;
|
|
1295
|
+
const parseSucceeded = plainTextInspection || parseError === undefined;
|
|
1296
|
+
const envelopeSuccess = plainTextInspection ? true : presentationEnvelope?.success !== false;
|
|
1297
|
+
const succeeded = processSucceeded && parseSucceeded && envelopeSuccess;
|
|
1298
|
+
const inspectionText = plainTextInspection ? processResult.stdout.trim() : undefined;
|
|
1299
|
+
|
|
1300
|
+
if (succeeded && !navigationSummary && shouldCaptureNavigationSummary(executionPlan.commandInfo.command, presentationEnvelope?.data)) {
|
|
1301
|
+
navigationSummary = await collectNavigationSummary({
|
|
925
1302
|
cwd: ctx.cwd,
|
|
926
1303
|
sessionName: executionPlan.sessionName,
|
|
927
1304
|
signal,
|
|
928
1305
|
});
|
|
929
1306
|
}
|
|
930
|
-
|
|
1307
|
+
if (navigationSummary && presentationEnvelope) {
|
|
1308
|
+
presentationEnvelope = {
|
|
1309
|
+
...presentationEnvelope,
|
|
1310
|
+
data: mergeNavigationSummaryIntoData(presentationEnvelope.data, navigationSummary),
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
931
1313
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1314
|
+
let openResultTabCorrection: OpenResultTabCorrection | undefined;
|
|
1315
|
+
if (
|
|
1316
|
+
succeeded &&
|
|
1317
|
+
executionPlan.sessionName &&
|
|
1318
|
+
hasLaunchScopedTabCorrectionFlag(params.args) &&
|
|
1319
|
+
(executionPlan.commandInfo.command === "goto" ||
|
|
1320
|
+
executionPlan.commandInfo.command === "navigate" ||
|
|
1321
|
+
executionPlan.commandInfo.command === "open")
|
|
1322
|
+
) {
|
|
1323
|
+
const targetTitle = extractStringResultField(presentationEnvelope?.data, "title");
|
|
1324
|
+
const targetUrl = extractStringResultField(presentationEnvelope?.data, "url");
|
|
1325
|
+
const plannedTabCorrection = await collectOpenResultTabCorrection({
|
|
1326
|
+
cwd: ctx.cwd,
|
|
1327
|
+
sessionName: executionPlan.sessionName,
|
|
1328
|
+
signal,
|
|
1329
|
+
targetTitle,
|
|
1330
|
+
targetUrl,
|
|
1331
|
+
});
|
|
1332
|
+
if (plannedTabCorrection) {
|
|
1333
|
+
openResultTabCorrection = await applyOpenResultTabCorrection({
|
|
1334
|
+
correction: plannedTabCorrection,
|
|
1335
|
+
cwd: ctx.cwd,
|
|
1336
|
+
sessionName: executionPlan.sessionName,
|
|
1337
|
+
signal,
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
const observedSessionTabTarget =
|
|
1343
|
+
normalizeSessionTabTarget(navigationSummary) ??
|
|
1344
|
+
extractSessionTabTargetFromBatchResults(presentationEnvelope?.data) ??
|
|
1345
|
+
extractSessionTabTargetFromData(presentationEnvelope?.data);
|
|
1346
|
+
let currentSessionTabTarget = deriveSessionTabTarget({
|
|
946
1347
|
command: executionPlan.commandInfo.command,
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
const postCommandTabCorrection = await collectSessionTabSelection({
|
|
951
|
-
cwd: ctx.cwd,
|
|
952
|
-
sessionName: executionPlan.sessionName,
|
|
953
|
-
signal,
|
|
954
|
-
target: observedSessionTabTarget,
|
|
1348
|
+
data: presentationEnvelope?.data,
|
|
1349
|
+
navigationSummary,
|
|
1350
|
+
previousTarget: priorSessionTabTarget,
|
|
955
1351
|
});
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1352
|
+
let aboutBlankSessionMismatch: AboutBlankSessionMismatch | undefined;
|
|
1353
|
+
const shouldTreatAboutBlankAsMismatch =
|
|
1354
|
+
succeeded &&
|
|
1355
|
+
priorSessionTabTarget !== undefined &&
|
|
1356
|
+
!isAboutBlankSessionTabTarget(priorSessionTabTarget) &&
|
|
1357
|
+
isAboutBlankSessionTabTarget(observedSessionTabTarget ?? currentSessionTabTarget) &&
|
|
1358
|
+
!commandExplicitlyTargetsAboutBlank(commandTokens);
|
|
1359
|
+
if (shouldTreatAboutBlankAsMismatch && priorSessionTabTarget) {
|
|
1360
|
+
const aboutBlankRecovery = await collectSessionTabSelection({
|
|
959
1361
|
cwd: ctx.cwd,
|
|
960
1362
|
sessionName: executionPlan.sessionName,
|
|
961
1363
|
signal,
|
|
1364
|
+
target: priorSessionTabTarget,
|
|
962
1365
|
});
|
|
963
|
-
|
|
964
|
-
|
|
1366
|
+
const appliedAboutBlankRecovery = aboutBlankRecovery
|
|
1367
|
+
? await applyOpenResultTabCorrection({
|
|
1368
|
+
correction: aboutBlankRecovery,
|
|
1369
|
+
cwd: ctx.cwd,
|
|
1370
|
+
sessionName: executionPlan.sessionName,
|
|
1371
|
+
signal,
|
|
1372
|
+
})
|
|
1373
|
+
: undefined;
|
|
1374
|
+
if (appliedAboutBlankRecovery) {
|
|
1375
|
+
sessionTabCorrection = appliedAboutBlankRecovery;
|
|
965
1376
|
}
|
|
1377
|
+
aboutBlankSessionMismatch = {
|
|
1378
|
+
activeUrl: "about:blank",
|
|
1379
|
+
recoveryApplied: appliedAboutBlankRecovery !== undefined,
|
|
1380
|
+
recoveryHint: buildAboutBlankRecoveryHint(),
|
|
1381
|
+
targetTitle: priorSessionTabTarget.title,
|
|
1382
|
+
targetUrl: priorSessionTabTarget.url,
|
|
1383
|
+
};
|
|
1384
|
+
currentSessionTabTarget = priorSessionTabTarget;
|
|
966
1385
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1386
|
+
if (
|
|
1387
|
+
succeeded &&
|
|
1388
|
+
priorSessionTabTarget &&
|
|
1389
|
+
!sessionTabCorrection &&
|
|
1390
|
+
!aboutBlankSessionMismatch &&
|
|
1391
|
+
!commandExplicitlyTargetsAboutBlank(commandTokens) &&
|
|
1392
|
+
observedSessionTabTarget &&
|
|
1393
|
+
shouldCorrectSessionTabAfterCommand({
|
|
1394
|
+
command: executionPlan.commandInfo.command,
|
|
1395
|
+
sessionName: executionPlan.sessionName,
|
|
1396
|
+
})
|
|
1397
|
+
) {
|
|
1398
|
+
const postCommandTabCorrection = await collectSessionTabSelection({
|
|
1399
|
+
cwd: ctx.cwd,
|
|
1400
|
+
sessionName: executionPlan.sessionName,
|
|
1401
|
+
signal,
|
|
1402
|
+
target: observedSessionTabTarget,
|
|
1403
|
+
});
|
|
1404
|
+
if (postCommandTabCorrection) {
|
|
1405
|
+
const appliedPostCommandCorrection = await applyOpenResultTabCorrection({
|
|
1406
|
+
correction: postCommandTabCorrection,
|
|
1407
|
+
cwd: ctx.cwd,
|
|
1408
|
+
sessionName: executionPlan.sessionName,
|
|
1409
|
+
signal,
|
|
1410
|
+
});
|
|
1411
|
+
if (appliedPostCommandCorrection && !sessionTabCorrection) {
|
|
1412
|
+
sessionTabCorrection = appliedPostCommandCorrection;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
if (executionPlan.sessionName) {
|
|
1417
|
+
const activeSessionTabTargetState = sessionTabTargets.get(executionPlan.sessionName);
|
|
1418
|
+
if (shouldApplySessionTabTargetUpdate({ current: activeSessionTabTargetState, updateOrder: tabTargetUpdateOrder })) {
|
|
1419
|
+
if (executionPlan.commandInfo.command === "close" && succeeded) {
|
|
1420
|
+
sessionTabTargets.delete(executionPlan.sessionName);
|
|
1421
|
+
} else if (currentSessionTabTarget) {
|
|
1422
|
+
sessionTabTargets.set(executionPlan.sessionName, { order: tabTargetUpdateOrder, target: currentSessionTabTarget });
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
973
1425
|
}
|
|
974
|
-
}
|
|
975
1426
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
});
|
|
984
|
-
const replacedManagedSessionName = managedSessionState.replacedSessionName;
|
|
985
|
-
managedSessionActive = managedSessionState.active;
|
|
986
|
-
managedSessionName = managedSessionState.sessionName;
|
|
987
|
-
if (executionPlan.managedSessionName && succeeded) {
|
|
988
|
-
managedSessionCwd = ctx.cwd;
|
|
989
|
-
}
|
|
990
|
-
if (replacedManagedSessionName) {
|
|
991
|
-
sessionTabTargets.delete(replacedManagedSessionName);
|
|
992
|
-
await closeManagedSession({
|
|
993
|
-
cwd: priorManagedSessionCwd,
|
|
994
|
-
sessionName: replacedManagedSessionName,
|
|
995
|
-
timeoutMs: implicitSessionCloseTimeoutMs,
|
|
1427
|
+
const priorManagedSessionCwd = managedSessionCwd;
|
|
1428
|
+
const managedSessionState = resolveManagedSessionState({
|
|
1429
|
+
command: executionPlan.commandInfo.command,
|
|
1430
|
+
managedSessionName: executionPlan.managedSessionName,
|
|
1431
|
+
priorActive: managedSessionActive,
|
|
1432
|
+
priorSessionName: managedSessionName,
|
|
1433
|
+
succeeded,
|
|
996
1434
|
});
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
batchFailure: undefined,
|
|
1012
|
-
batchSteps: undefined,
|
|
1013
|
-
content: [{ type: "text" as const, text: inspectionText ?? "" }],
|
|
1014
|
-
data: undefined,
|
|
1015
|
-
fullOutputPath: undefined,
|
|
1016
|
-
fullOutputPaths: undefined,
|
|
1017
|
-
imagePath: undefined,
|
|
1018
|
-
imagePaths: undefined,
|
|
1019
|
-
summary: `${redactedArgs.join(" ")} completed`,
|
|
1020
|
-
}
|
|
1021
|
-
: await buildToolPresentation({
|
|
1022
|
-
commandInfo: executionPlan.commandInfo,
|
|
1023
|
-
cwd: ctx.cwd,
|
|
1024
|
-
envelope: presentationEnvelope,
|
|
1025
|
-
errorText,
|
|
1026
|
-
persistentArtifactStore: getPersistentSessionArtifactStore(ctx),
|
|
1027
|
-
});
|
|
1028
|
-
const redactedContent = presentation.content.map((item) =>
|
|
1029
|
-
item.type === "text" ? { ...item, text: redactSensitiveText(item.text) } : item,
|
|
1030
|
-
);
|
|
1435
|
+
const replacedManagedSessionName = managedSessionState.replacedSessionName;
|
|
1436
|
+
managedSessionActive = managedSessionState.active;
|
|
1437
|
+
managedSessionName = managedSessionState.sessionName;
|
|
1438
|
+
if (executionPlan.managedSessionName && succeeded) {
|
|
1439
|
+
managedSessionCwd = ctx.cwd;
|
|
1440
|
+
}
|
|
1441
|
+
if (replacedManagedSessionName) {
|
|
1442
|
+
sessionTabTargets.delete(replacedManagedSessionName);
|
|
1443
|
+
await closeManagedSession({
|
|
1444
|
+
cwd: priorManagedSessionCwd,
|
|
1445
|
+
sessionName: replacedManagedSessionName,
|
|
1446
|
+
timeoutMs: implicitSessionCloseTimeoutMs,
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1031
1449
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
details: {
|
|
1035
|
-
args: redactedArgs,
|
|
1036
|
-
batchFailure: redactSensitiveValue(presentation.batchFailure),
|
|
1037
|
-
batchSteps: redactSensitiveValue(presentation.batchSteps),
|
|
1450
|
+
const errorText = getAgentBrowserErrorText({
|
|
1451
|
+
aborted: processResult.aborted,
|
|
1038
1452
|
command: executionPlan.commandInfo.command,
|
|
1039
|
-
compatibilityWorkaround,
|
|
1040
|
-
subcommand: executionPlan.commandInfo.subcommand,
|
|
1041
|
-
data: redactSensitiveValue(presentation.data),
|
|
1042
|
-
error: plainTextInspection ? undefined : redactSensitiveValue(presentationEnvelope?.error),
|
|
1043
|
-
inspection: plainTextInspection || undefined,
|
|
1044
|
-
navigationSummary: redactSensitiveValue(navigationSummary),
|
|
1045
|
-
openResultTabCorrection: redactSensitiveValue(openResultTabCorrection),
|
|
1046
1453
|
effectiveArgs: redactedProcessArgs,
|
|
1454
|
+
envelope: presentationEnvelope,
|
|
1047
1455
|
exitCode: processResult.exitCode,
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
:
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1456
|
+
parseError,
|
|
1457
|
+
plainTextInspection,
|
|
1458
|
+
spawnError: processResult.spawnError,
|
|
1459
|
+
stderr: processResult.stderr,
|
|
1460
|
+
wrapperRecoveryHint: buildWrapperRecoveryHint({ pinnedBatchUnwrapMode, sessionTabCorrection }),
|
|
1461
|
+
});
|
|
1462
|
+
|
|
1463
|
+
const presentation = plainTextInspection
|
|
1464
|
+
? {
|
|
1465
|
+
artifacts: undefined,
|
|
1466
|
+
batchFailure: undefined,
|
|
1467
|
+
batchSteps: undefined,
|
|
1468
|
+
content: [{ type: "text" as const, text: inspectionText ?? "" }],
|
|
1469
|
+
data: undefined,
|
|
1470
|
+
fullOutputPath: undefined,
|
|
1471
|
+
fullOutputPaths: undefined,
|
|
1472
|
+
imagePath: undefined,
|
|
1473
|
+
imagePaths: undefined,
|
|
1474
|
+
savedFile: undefined,
|
|
1475
|
+
savedFilePath: undefined,
|
|
1476
|
+
summary: `${redactedArgs.join(" ")} completed`,
|
|
1477
|
+
}
|
|
1478
|
+
: await buildToolPresentation({
|
|
1479
|
+
artifactManifest,
|
|
1480
|
+
commandInfo: executionPlan.commandInfo,
|
|
1481
|
+
cwd: ctx.cwd,
|
|
1482
|
+
envelope: presentationEnvelope,
|
|
1483
|
+
errorText,
|
|
1484
|
+
persistentArtifactStore,
|
|
1485
|
+
});
|
|
1486
|
+
if (parseFailureOutput.artifactManifest) {
|
|
1487
|
+
presentation.artifactManifest = parseFailureOutput.artifactManifest;
|
|
1488
|
+
presentation.artifactRetentionSummary = parseFailureOutput.artifactRetentionSummary;
|
|
1489
|
+
}
|
|
1490
|
+
if (parseFailureOutput.fullOutputPath || parseFailureOutput.fullOutputUnavailable) {
|
|
1491
|
+
const existingText = presentation.content[0]?.type === "text" ? presentation.content[0].text : "";
|
|
1492
|
+
const noticeLines = [
|
|
1493
|
+
parseFailureOutput.fullOutputPath
|
|
1494
|
+
? `Full output path: ${parseFailureOutput.fullOutputPath}`
|
|
1495
|
+
: `Full raw output unavailable: ${parseFailureOutput.fullOutputUnavailable}`,
|
|
1496
|
+
parseFailureOutput.artifactRetentionSummary,
|
|
1497
|
+
].filter((item): item is string => item !== undefined);
|
|
1498
|
+
const notice = noticeLines.join("\n");
|
|
1499
|
+
presentation.content[0] = {
|
|
1500
|
+
type: "text",
|
|
1501
|
+
text: existingText.length > 0 ? `${existingText}\n\n${notice}` : notice,
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
if (presentation.artifactManifest) {
|
|
1505
|
+
artifactManifest = presentation.artifactManifest;
|
|
1506
|
+
}
|
|
1507
|
+
const contentWithSessionWarnings = aboutBlankSessionMismatch ? [...presentation.content] : presentation.content;
|
|
1508
|
+
if (aboutBlankSessionMismatch) {
|
|
1509
|
+
const warning = buildAboutBlankWarning(aboutBlankSessionMismatch);
|
|
1510
|
+
if (contentWithSessionWarnings[0]?.type === "text") {
|
|
1511
|
+
contentWithSessionWarnings[0] = {
|
|
1512
|
+
...contentWithSessionWarnings[0],
|
|
1513
|
+
text: `${warning}\n\n${contentWithSessionWarnings[0].text}`,
|
|
1514
|
+
};
|
|
1515
|
+
} else {
|
|
1516
|
+
contentWithSessionWarnings.unshift({ type: "text", text: warning });
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
const redactedContent = contentWithSessionWarnings.map((item) =>
|
|
1520
|
+
item.type === "text" ? { ...item, text: redactSensitiveText(item.text) } : item,
|
|
1521
|
+
);
|
|
1522
|
+
|
|
1523
|
+
return {
|
|
1524
|
+
content: redactedContent,
|
|
1525
|
+
details: {
|
|
1526
|
+
args: redactedArgs,
|
|
1527
|
+
artifactManifest: redactSensitiveValue(presentation.artifactManifest),
|
|
1528
|
+
artifactRetentionSummary: presentation.artifactRetentionSummary,
|
|
1529
|
+
artifacts: redactSensitiveValue(presentation.artifacts),
|
|
1530
|
+
batchFailure: redactSensitiveValue(presentation.batchFailure),
|
|
1531
|
+
batchSteps: redactSensitiveValue(presentation.batchSteps),
|
|
1532
|
+
command: executionPlan.commandInfo.command,
|
|
1533
|
+
compatibilityWorkaround,
|
|
1534
|
+
subcommand: executionPlan.commandInfo.subcommand,
|
|
1535
|
+
data: redactSensitiveValue(presentation.data),
|
|
1536
|
+
error: plainTextInspection ? undefined : redactSensitiveValue(presentationEnvelope?.error),
|
|
1537
|
+
inspection: plainTextInspection || undefined,
|
|
1538
|
+
navigationSummary: redactSensitiveValue(navigationSummary),
|
|
1539
|
+
aboutBlankSessionMismatch: redactSensitiveValue(aboutBlankSessionMismatch),
|
|
1540
|
+
openResultTabCorrection: redactSensitiveValue(openResultTabCorrection),
|
|
1541
|
+
effectiveArgs: redactedProcessArgs,
|
|
1542
|
+
exitCode: processResult.exitCode,
|
|
1543
|
+
fullOutputPath: parseFailureOutput.fullOutputPath ?? presentation.fullOutputPath,
|
|
1544
|
+
fullOutputPaths: presentation.fullOutputPaths,
|
|
1545
|
+
fullOutputUnavailable: parseFailureOutput.fullOutputUnavailable,
|
|
1546
|
+
imagePath: presentation.imagePath,
|
|
1547
|
+
imagePaths: presentation.imagePaths,
|
|
1548
|
+
parseError: plainTextInspection ? undefined : parseError,
|
|
1549
|
+
savedFile: redactSensitiveValue(presentation.savedFile),
|
|
1550
|
+
savedFilePath: presentation.savedFilePath ? redactSensitiveText(presentation.savedFilePath) : undefined,
|
|
1551
|
+
sessionMode,
|
|
1552
|
+
sessionTabCorrection: redactSensitiveValue(sessionTabCorrection),
|
|
1553
|
+
sessionTabTarget: redactSensitiveValue(currentSessionTabTarget),
|
|
1554
|
+
...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
|
|
1555
|
+
sessionRecoveryHint: redactedRecoveryHint,
|
|
1556
|
+
startupScopedFlags: executionPlan.startupScopedFlags,
|
|
1557
|
+
stderr: processResult.stderr ? redactSensitiveText(processResult.stderr) : undefined,
|
|
1558
|
+
stdout: plainTextInspection
|
|
1559
|
+
? redactSensitiveText(inspectionText ?? "")
|
|
1560
|
+
: parseSucceeded
|
|
1561
|
+
? undefined
|
|
1562
|
+
: redactSensitiveText(processResult.stdout),
|
|
1563
|
+
summary: redactSensitiveText(presentation.summary),
|
|
1564
|
+
},
|
|
1565
|
+
isError: !succeeded,
|
|
1566
|
+
};
|
|
1567
|
+
} finally {
|
|
1568
|
+
if (processResult.stdoutSpillPath) {
|
|
1569
|
+
await rm(processResult.stdoutSpillPath, { force: true }).catch(() => undefined);
|
|
1570
|
+
}
|
|
1072
1571
|
}
|
|
1073
|
-
}
|
|
1572
|
+
};
|
|
1573
|
+
|
|
1574
|
+
return extractExplicitSessionName(params.args)
|
|
1575
|
+
? runTool()
|
|
1576
|
+
: managedSessionExecutionQueue.run(runTool);
|
|
1074
1577
|
},
|
|
1075
1578
|
});
|
|
1076
1579
|
}
|