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