pi-agent-browser-native 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,6 +16,7 @@ import { buildToolPresentation, getAgentBrowserErrorText, parseAgentBrowserEnvel
16
16
  import {
17
17
  buildExecutionPlan,
18
18
  buildPromptPolicy,
19
+ chooseOpenResultTabCorrection,
19
20
  createEphemeralSessionSeed,
20
21
  createFreshSessionName,
21
22
  createImplicitSessionName,
@@ -23,10 +24,17 @@ import {
23
24
  getImplicitSessionIdleTimeoutMs,
24
25
  getLatestUserPrompt,
25
26
  hasUsableBraveApiKey,
27
+ redactInvocationArgs,
28
+ redactSensitiveText,
29
+ redactSensitiveValue,
30
+ restoreManagedSessionStateFromBranch,
26
31
  resolveManagedSessionState,
32
+ shouldAppendBrowserSystemPrompt,
27
33
  validateToolArgs,
34
+ type CompatibilityWorkaround,
35
+ type OpenResultTabCorrection,
28
36
  } from "./lib/runtime.js";
29
- import { cleanupSecureTempArtifacts } from "./lib/temp.js";
37
+ import { cleanupSecureTempArtifacts, type PersistentSessionArtifactStore } from "./lib/temp.js";
30
38
 
31
39
  const DEFAULT_SESSION_MODE = "auto" as const;
32
40
 
@@ -91,25 +99,190 @@ function buildInvocationPreview(effectiveArgs: string[]): string {
91
99
  return preview.length > 120 ? `${preview.slice(0, 117)}...` : preview;
92
100
  }
93
101
 
94
- const AGENT_BROWSER_BASH_PREFIX = String.raw`(?:env(?:\s+[A-Za-z_][A-Za-z0-9_]*=[^\s;&|]+)*\s+)?(?:(?:npx|bunx)(?:\s+-[^\s;&|]+|\s+--[^\s;&|]+(?:=[^\s;&|]+)?)*\s+|(?:pnpm|yarn)\s+dlx(?:\s+-[^\s;&|]+|\s+--[^\s;&|]+(?:=[^\s;&|]+)?)*\s+)?`;
95
- const AGENT_BROWSER_BASH_EXECUTABLE = String.raw`(?:[.~]|\.\.?|\/)?(?:[^\s;&|]+\/)?agent-browser`;
96
- const DIRECT_AGENT_BROWSER_BASH_PATTERN = new RegExp(
97
- String.raw`(^|[\s;&|])${AGENT_BROWSER_BASH_PREFIX}${AGENT_BROWSER_BASH_EXECUTABLE}(?=\s|$)`,
98
- );
99
- const HARMLESS_AGENT_BROWSER_INSPECTION_PATTERN = /(command\s+-v|which|type\s+-P)\s+agent-browser\b/;
102
+ const DIRECT_AGENT_BROWSER_EXECUTABLE_PATTERN = /^(?:[.~]|\.\.?|\/)?(?:[^\s;&|]+\/)?agent-browser$/;
103
+ const HARMLESS_AGENT_BROWSER_INSPECTION_PATTERN = /^\s*(?:command\s+-v|which|type\s+-P)\s+agent-browser\s*$/;
100
104
 
105
+ type ShellQuoteState = 'double' | 'single' | undefined;
106
+
107
+ function isShellAssignmentToken(token: string): boolean {
108
+ return /^[A-Za-z_][A-Za-z0-9_]*=/.test(token);
109
+ }
110
+
111
+ function stripOuterQuotes(token: string): string {
112
+ if (token.length >= 2 && ((token.startsWith('"') && token.endsWith('"')) || (token.startsWith("'") && token.endsWith("'")))) {
113
+ return token.slice(1, -1);
114
+ }
115
+ return token;
116
+ }
117
+
118
+ function segmentLaunchesAgentBrowser(tokens: string[]): boolean {
119
+ let index = 0;
120
+ while (index < tokens.length && isShellAssignmentToken(tokens[index])) {
121
+ index += 1;
122
+ }
123
+ if (index >= tokens.length) {
124
+ return false;
125
+ }
126
+
127
+ let executableToken = tokens[index];
128
+ if (executableToken === 'env') {
129
+ index += 1;
130
+ while (index < tokens.length && isShellAssignmentToken(tokens[index])) {
131
+ index += 1;
132
+ }
133
+ executableToken = tokens[index] ?? '';
134
+ }
135
+ if (executableToken === 'npx' || executableToken === 'bunx') {
136
+ index += 1;
137
+ while (index < tokens.length && tokens[index].startsWith('-')) {
138
+ index += 1;
139
+ }
140
+ executableToken = tokens[index] ?? '';
141
+ }
142
+ if (executableToken === 'pnpm' || executableToken === 'yarn') {
143
+ index += 1;
144
+ if (tokens[index] !== 'dlx') {
145
+ return false;
146
+ }
147
+ index += 1;
148
+ while (index < tokens.length && tokens[index].startsWith('-')) {
149
+ index += 1;
150
+ }
151
+ executableToken = tokens[index] ?? '';
152
+ }
153
+ return DIRECT_AGENT_BROWSER_EXECUTABLE_PATTERN.test(executableToken);
154
+ }
155
+
156
+ // Best-effort detection for common direct launches only. This is an ergonomics guard,
157
+ // not a general-purpose bash parser or security boundary.
101
158
  function looksLikeDirectAgentBrowserBash(command: string): boolean {
102
- return DIRECT_AGENT_BROWSER_BASH_PATTERN.test(command);
159
+ let currentToken = '';
160
+ let quoteState: ShellQuoteState;
161
+ let awaitingHeredocDelimiter: { stripTabs: boolean } | undefined;
162
+ let pendingHeredoc: { delimiter: string; stripTabs: boolean } | undefined;
163
+ let pendingHeredocLine = '';
164
+ let segmentTokens: string[] = [];
165
+
166
+ const acceptToken = (token: string) => {
167
+ if (token.length === 0) {
168
+ return;
169
+ }
170
+ if (awaitingHeredocDelimiter) {
171
+ pendingHeredoc = {
172
+ delimiter: stripOuterQuotes(token),
173
+ stripTabs: awaitingHeredocDelimiter.stripTabs,
174
+ };
175
+ awaitingHeredocDelimiter = undefined;
176
+ return;
177
+ }
178
+ segmentTokens.push(token);
179
+ };
180
+ const flushToken = () => {
181
+ acceptToken(currentToken);
182
+ currentToken = '';
183
+ };
184
+ const flushSegment = () => {
185
+ const launchesAgentBrowser = segmentLaunchesAgentBrowser(segmentTokens);
186
+ segmentTokens = [];
187
+ return launchesAgentBrowser;
188
+ };
189
+
190
+ for (let index = 0; index < command.length; index += 1) {
191
+ const char = command[index];
192
+ if (pendingHeredoc) {
193
+ if (char === '\n') {
194
+ const candidate = pendingHeredoc.stripTabs ? pendingHeredocLine.replace(/^\t+/, '') : pendingHeredocLine;
195
+ if (candidate === pendingHeredoc.delimiter) {
196
+ pendingHeredoc = undefined;
197
+ }
198
+ pendingHeredocLine = '';
199
+ continue;
200
+ }
201
+ pendingHeredocLine += char;
202
+ continue;
203
+ }
204
+
205
+ if (quoteState === 'single') {
206
+ currentToken += char;
207
+ if (char === "'") {
208
+ quoteState = undefined;
209
+ }
210
+ continue;
211
+ }
212
+ if (quoteState === 'double') {
213
+ currentToken += char;
214
+ if (char === '\\' && index + 1 < command.length) {
215
+ currentToken += command[index + 1];
216
+ index += 1;
217
+ continue;
218
+ }
219
+ if (char === '"') {
220
+ quoteState = undefined;
221
+ }
222
+ continue;
223
+ }
224
+ if (char === "'" || char === '"') {
225
+ currentToken += char;
226
+ quoteState = char === "'" ? 'single' : 'double';
227
+ continue;
228
+ }
229
+ if (char === '\\' && index + 1 < command.length) {
230
+ currentToken += char;
231
+ currentToken += command[index + 1];
232
+ index += 1;
233
+ continue;
234
+ }
235
+ if (char === '\n') {
236
+ flushToken();
237
+ if (flushSegment()) {
238
+ return true;
239
+ }
240
+ continue;
241
+ }
242
+ if (/\s/.test(char)) {
243
+ flushToken();
244
+ continue;
245
+ }
246
+ const threeCharOperator = command.slice(index, index + 3);
247
+ if (threeCharOperator === '<<-') {
248
+ flushToken();
249
+ awaitingHeredocDelimiter = { stripTabs: true };
250
+ index += 2;
251
+ continue;
252
+ }
253
+ const twoCharOperator = command.slice(index, index + 2);
254
+ if (twoCharOperator === '<<') {
255
+ flushToken();
256
+ awaitingHeredocDelimiter = { stripTabs: false };
257
+ index += 1;
258
+ continue;
259
+ }
260
+ if (twoCharOperator === '&&' || twoCharOperator === '||') {
261
+ flushToken();
262
+ if (flushSegment()) {
263
+ return true;
264
+ }
265
+ index += 1;
266
+ continue;
267
+ }
268
+ if (char === '|' || char === ';' || char === '&') {
269
+ flushToken();
270
+ if (flushSegment()) {
271
+ return true;
272
+ }
273
+ continue;
274
+ }
275
+ currentToken += char;
276
+ }
277
+
278
+ flushToken();
279
+ return flushSegment();
103
280
  }
104
281
 
105
282
  function isHarmlessAgentBrowserInspectionCommand(command: string): boolean {
106
283
  return HARMLESS_AGENT_BROWSER_INSPECTION_PATTERN.test(command);
107
284
  }
108
285
 
109
- function isPlainTextInspectionArgs(args: string[]): boolean {
110
- return args.includes("--help") || args.includes("-h") || args.includes("--version") || args.includes("-V");
111
- }
112
-
113
286
  const NAVIGATION_SUMMARY_COMMANDS = new Set(["back", "click", "dblclick", "forward", "reload"]);
114
287
 
115
288
  interface NavigationSummary {
@@ -141,41 +314,53 @@ function extractStringResultField(data: unknown, fieldName: "title" | "url"): st
141
314
  return text.length > 0 ? text : undefined;
142
315
  }
143
316
 
144
- async function collectNavigationSummary(options: {
317
+ async function runSessionCommandData(options: {
318
+ args: string[];
145
319
  cwd: string;
146
320
  sessionName?: string;
147
321
  signal?: AbortSignal;
148
- }): Promise<NavigationSummary | undefined> {
149
- const { cwd, sessionName, signal } = options;
322
+ }): Promise<unknown | undefined> {
323
+ const { args, cwd, sessionName, signal } = options;
150
324
  if (!sessionName) return undefined;
151
325
 
152
- const readField = async (fieldName: "title" | "url"): Promise<string | undefined> => {
153
- const processResult = await runAgentBrowserProcess({
154
- args: ["--json", "--session", sessionName, "get", fieldName],
155
- cwd,
156
- signal,
157
- });
158
- if (processResult.aborted || processResult.spawnError || processResult.exitCode !== 0) {
326
+ const processResult = await runAgentBrowserProcess({
327
+ args: ["--json", "--session", sessionName, ...args],
328
+ cwd,
329
+ signal,
330
+ });
331
+ if (processResult.aborted || processResult.spawnError || processResult.exitCode !== 0) {
332
+ return undefined;
333
+ }
334
+ const parsed = await parseAgentBrowserEnvelope({
335
+ stdout: processResult.stdout,
336
+ stdoutPath: processResult.stdoutSpillPath,
337
+ });
338
+ try {
339
+ if (parsed.parseError || parsed.envelope?.success === false) {
159
340
  return undefined;
160
341
  }
161
- const parsed = await parseAgentBrowserEnvelope({
162
- stdout: processResult.stdout,
163
- stdoutPath: processResult.stdoutSpillPath,
164
- });
165
- try {
166
- if (parsed.parseError || parsed.envelope?.success === false) {
167
- return undefined;
168
- }
169
- return extractStringResultField(parsed.envelope?.data, fieldName);
170
- } finally {
171
- if (processResult.stdoutSpillPath) {
172
- await rm(processResult.stdoutSpillPath, { force: true }).catch(() => undefined);
173
- }
342
+ return parsed.envelope?.data;
343
+ } finally {
344
+ if (processResult.stdoutSpillPath) {
345
+ await rm(processResult.stdoutSpillPath, { force: true }).catch(() => undefined);
174
346
  }
175
- };
347
+ }
348
+ }
176
349
 
177
- const title = await readField("title");
178
- const url = await readField("url");
350
+ async function collectNavigationSummary(options: {
351
+ cwd: string;
352
+ sessionName?: string;
353
+ signal?: AbortSignal;
354
+ }): Promise<NavigationSummary | undefined> {
355
+ const { cwd, sessionName, signal } = options;
356
+ const title = extractStringResultField(
357
+ await runSessionCommandData({ args: ["get", "title"], cwd, sessionName, signal }),
358
+ "title",
359
+ );
360
+ const url = extractStringResultField(
361
+ await runSessionCommandData({ args: ["get", "url"], cwd, sessionName, signal }),
362
+ "url",
363
+ );
179
364
  if (!title && !url) return undefined;
180
365
  return { title, url };
181
366
  }
@@ -187,6 +372,43 @@ function mergeNavigationSummaryIntoData(data: unknown, navigationSummary: Naviga
187
372
  return { navigationSummary, result: data };
188
373
  }
189
374
 
375
+ async function collectOpenResultTabCorrection(options: {
376
+ cwd: string;
377
+ sessionName?: string;
378
+ signal?: AbortSignal;
379
+ targetTitle?: string;
380
+ targetUrl?: string;
381
+ }): Promise<OpenResultTabCorrection | undefined> {
382
+ const { cwd, sessionName, signal, targetTitle, targetUrl } = options;
383
+ const tabData = await runSessionCommandData({ args: ["tab", "list"], cwd, sessionName, signal });
384
+ if (!isRecord(tabData) || !Array.isArray(tabData.tabs)) {
385
+ return undefined;
386
+ }
387
+ const tabs = tabData.tabs.filter(isRecord).map((tab) => ({
388
+ active: tab.active === true,
389
+ index: typeof tab.index === "number" ? tab.index : undefined,
390
+ title: typeof tab.title === "string" ? tab.title : undefined,
391
+ url: typeof tab.url === "string" ? tab.url : undefined,
392
+ }));
393
+ return chooseOpenResultTabCorrection({ tabs, targetTitle, targetUrl });
394
+ }
395
+
396
+ async function applyOpenResultTabCorrection(options: {
397
+ correction: OpenResultTabCorrection;
398
+ cwd: string;
399
+ sessionName?: string;
400
+ signal?: AbortSignal;
401
+ }): Promise<OpenResultTabCorrection | undefined> {
402
+ const { correction, cwd, sessionName, signal } = options;
403
+ const result = await runSessionCommandData({
404
+ args: ["tab", String(correction.selectedIndex)],
405
+ cwd,
406
+ sessionName,
407
+ signal,
408
+ });
409
+ return result === undefined ? undefined : correction;
410
+ }
411
+
190
412
  function buildSharedBrowserPlaybookGuidelines(hasBraveApiKey: boolean): string[] {
191
413
  return [
192
414
  SHARED_BROWSER_PLAYBOOK_GUIDELINES[0],
@@ -195,18 +417,6 @@ function buildSharedBrowserPlaybookGuidelines(hasBraveApiKey: boolean): string[]
195
417
  ];
196
418
  }
197
419
 
198
- function buildBrowserSystemPromptAppendix(hasBraveApiKey: boolean): string {
199
- return [
200
- PROJECT_RULE_PROMPT,
201
- "",
202
- "Quick start:",
203
- ...QUICK_START_GUIDELINES.map((guideline) => `- ${guideline}`),
204
- "",
205
- "Browser operating playbook:",
206
- ...buildSharedBrowserPlaybookGuidelines(hasBraveApiKey).map((guideline) => `- ${guideline}`),
207
- ].join("\n");
208
- }
209
-
210
420
  function buildToolPromptGuidelines(hasBraveApiKey: boolean): string[] {
211
421
  return [
212
422
  ...TOOL_PROMPT_GUIDELINES_PREFIX,
@@ -216,6 +426,46 @@ function buildToolPromptGuidelines(hasBraveApiKey: boolean): string[] {
216
426
  ];
217
427
  }
218
428
 
429
+ function buildSessionDetailFields(sessionName: string | undefined, usedImplicitSession: boolean): Record<string, unknown> {
430
+ return sessionName ? { sessionName, usedImplicitSession } : {};
431
+ }
432
+
433
+ function getPersistentSessionArtifactStore(ctx: {
434
+ sessionManager: {
435
+ getSessionDir?: () => string;
436
+ getSessionFile?: () => string | undefined;
437
+ getSessionId: () => string | undefined;
438
+ };
439
+ }): PersistentSessionArtifactStore | undefined {
440
+ const sessionFile = typeof ctx.sessionManager.getSessionFile === "function" ? ctx.sessionManager.getSessionFile() : undefined;
441
+ const sessionDir = typeof ctx.sessionManager.getSessionDir === "function" ? ctx.sessionManager.getSessionDir() : undefined;
442
+ const sessionId = ctx.sessionManager.getSessionId();
443
+ if (!sessionFile || !sessionDir || !sessionId) {
444
+ return undefined;
445
+ }
446
+ return { sessionDir, sessionId };
447
+ }
448
+
449
+ function redactRecoveryHint(recoveryHint: {
450
+ exampleArgs: string[];
451
+ exampleParams: { args: string[]; sessionMode: "fresh" };
452
+ reason: string;
453
+ recommendedSessionMode: "fresh";
454
+ } | undefined): typeof recoveryHint {
455
+ if (!recoveryHint) {
456
+ return undefined;
457
+ }
458
+ const exampleArgs = redactInvocationArgs(recoveryHint.exampleArgs);
459
+ return {
460
+ ...recoveryHint,
461
+ exampleArgs,
462
+ exampleParams: {
463
+ ...recoveryHint.exampleParams,
464
+ args: exampleArgs,
465
+ },
466
+ };
467
+ }
468
+
219
469
  async function closeManagedSession(options: { cwd: string; sessionName: string; timeoutMs: number }): Promise<void> {
220
470
  const controller = new AbortController();
221
471
  const timer = setTimeout(() => controller.abort(), options.timeoutMs);
@@ -235,7 +485,6 @@ async function closeManagedSession(options: { cwd: string; sessionName: string;
235
485
  export default function agentBrowserExtension(pi: ExtensionAPI) {
236
486
  const ephemeralSessionSeed = createEphemeralSessionSeed();
237
487
  const hasBraveApiKey = hasUsableBraveApiKey();
238
- const browserSystemPromptAppendix = buildBrowserSystemPromptAppendix(hasBraveApiKey);
239
488
  const toolPromptGuidelines = buildToolPromptGuidelines(hasBraveApiKey);
240
489
  const implicitSessionIdleTimeoutMs = getImplicitSessionIdleTimeoutMs();
241
490
  const implicitSessionCloseTimeoutMs = getImplicitSessionCloseTimeoutMs();
@@ -246,26 +495,25 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
246
495
  let freshSessionOrdinal = 0;
247
496
 
248
497
  pi.on("session_start", async (_event, ctx) => {
249
- managedSessionActive = false;
250
498
  managedSessionBaseName = createImplicitSessionName(ctx.sessionManager.getSessionId(), ctx.cwd, ephemeralSessionSeed);
251
- managedSessionName = managedSessionBaseName;
499
+ const restoredState = restoreManagedSessionStateFromBranch(ctx.sessionManager.getBranch(), managedSessionBaseName);
500
+ managedSessionActive = restoredState.active;
501
+ managedSessionName = restoredState.sessionName;
252
502
  managedSessionCwd = ctx.cwd;
253
- freshSessionOrdinal = 0;
503
+ freshSessionOrdinal = restoredState.freshSessionOrdinal;
254
504
  });
255
505
 
256
506
  pi.on("session_shutdown", async () => {
257
507
  managedSessionActive = false;
258
- await closeManagedSession({
259
- cwd: managedSessionCwd,
260
- sessionName: managedSessionName,
261
- timeoutMs: implicitSessionCloseTimeoutMs,
262
- });
263
508
  await cleanupSecureTempArtifacts();
264
509
  });
265
510
 
266
511
  pi.on("before_agent_start", async (event) => {
512
+ if (!shouldAppendBrowserSystemPrompt(event.prompt)) {
513
+ return undefined;
514
+ }
267
515
  return {
268
- systemPrompt: `${event.systemPrompt}\n\n${browserSystemPromptAppendix}`,
516
+ systemPrompt: `${event.systemPrompt}\n\n${PROJECT_RULE_PROMPT}`,
269
517
  };
270
518
  });
271
519
 
@@ -294,11 +542,12 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
294
542
  promptGuidelines: toolPromptGuidelines,
295
543
  parameters: AGENT_BROWSER_PARAMS,
296
544
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
545
+ const redactedArgs = redactInvocationArgs(params.args);
297
546
  const validationError = validateToolArgs(params.args);
298
547
  if (validationError) {
299
548
  return {
300
549
  content: [{ type: "text", text: validationError }],
301
- details: { args: params.args, validationError },
550
+ details: { args: redactedArgs, validationError },
302
551
  isError: true,
303
552
  };
304
553
  }
@@ -311,6 +560,9 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
311
560
  managedSessionName,
312
561
  sessionMode,
313
562
  });
563
+ const redactedEffectiveArgs = redactInvocationArgs(executionPlan.effectiveArgs);
564
+ const redactedRecoveryHint = redactRecoveryHint(executionPlan.recoveryHint);
565
+ const compatibilityWorkaround: CompatibilityWorkaround | undefined = executionPlan.compatibilityWorkaround;
314
566
  if (executionPlan.managedSessionName === freshSessionName) {
315
567
  freshSessionOrdinal += 1;
316
568
  }
@@ -319,10 +571,10 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
319
571
  return {
320
572
  content: [{ type: "text", text: executionPlan.validationError }],
321
573
  details: {
322
- args: params.args,
574
+ args: redactedArgs,
323
575
  invalidValueFlag: executionPlan.invalidValueFlag,
324
576
  sessionMode,
325
- sessionRecoveryHint: executionPlan.recoveryHint,
577
+ sessionRecoveryHint: redactedRecoveryHint,
326
578
  startupScopedFlags: executionPlan.startupScopedFlags,
327
579
  validationError: executionPlan.validationError,
328
580
  },
@@ -331,12 +583,12 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
331
583
  }
332
584
 
333
585
  onUpdate?.({
334
- content: [{ type: "text", text: `Running agent-browser ${buildInvocationPreview(executionPlan.effectiveArgs)}` }],
586
+ content: [{ type: "text", text: `Running agent-browser ${buildInvocationPreview(redactedEffectiveArgs)}` }],
335
587
  details: {
336
- effectiveArgs: executionPlan.effectiveArgs,
588
+ compatibilityWorkaround,
589
+ effectiveArgs: redactedEffectiveArgs,
337
590
  sessionMode,
338
- sessionName: executionPlan.sessionName,
339
- usedImplicitSession: executionPlan.usedImplicitSession,
591
+ ...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
340
592
  },
341
593
  });
342
594
 
@@ -353,8 +605,9 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
353
605
  return {
354
606
  content: [{ type: "text", text: errorText }],
355
607
  details: {
356
- args: params.args,
357
- effectiveArgs: executionPlan.effectiveArgs,
608
+ args: redactedArgs,
609
+ compatibilityWorkaround,
610
+ effectiveArgs: redactedEffectiveArgs,
358
611
  sessionMode,
359
612
  spawnError: processResult.spawnError.message,
360
613
  },
@@ -369,10 +622,11 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
369
622
  });
370
623
  let presentationEnvelope = parsed.envelope;
371
624
  const processSucceeded = !processResult.aborted && !processResult.spawnError && processResult.exitCode === 0;
372
- const plainTextInspection = isPlainTextInspectionArgs(params.args) && processSucceeded && parsed.parseError !== undefined;
373
- const envelopeSuccess = plainTextInspection ? true : parsed.envelope?.success !== false;
625
+ const plainTextInspection = executionPlan.plainTextInspection && processSucceeded;
374
626
  const parseSucceeded = plainTextInspection || parsed.parseError === undefined;
627
+ const envelopeSuccess = plainTextInspection ? true : parsed.envelope?.success !== false;
375
628
  const succeeded = processSucceeded && parseSucceeded && envelopeSuccess;
629
+ const inspectionText = plainTextInspection ? processResult.stdout.trim() : undefined;
376
630
 
377
631
  let navigationSummary: NavigationSummary | undefined;
378
632
  if (succeeded && shouldCaptureNavigationSummary(executionPlan.commandInfo.command, parsed.envelope?.data)) {
@@ -389,6 +643,34 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
389
643
  }
390
644
  }
391
645
 
646
+ let openResultTabCorrection: OpenResultTabCorrection | undefined;
647
+ if (
648
+ succeeded &&
649
+ executionPlan.sessionName &&
650
+ params.args.some((token) => token === "--profile" || token.startsWith("--profile=")) &&
651
+ (executionPlan.commandInfo.command === "goto" ||
652
+ executionPlan.commandInfo.command === "navigate" ||
653
+ executionPlan.commandInfo.command === "open")
654
+ ) {
655
+ const targetTitle = extractStringResultField(parsed.envelope?.data, "title");
656
+ const targetUrl = extractStringResultField(parsed.envelope?.data, "url");
657
+ const plannedTabCorrection = await collectOpenResultTabCorrection({
658
+ cwd: ctx.cwd,
659
+ sessionName: executionPlan.sessionName,
660
+ signal,
661
+ targetTitle,
662
+ targetUrl,
663
+ });
664
+ if (plannedTabCorrection) {
665
+ openResultTabCorrection = await applyOpenResultTabCorrection({
666
+ correction: plannedTabCorrection,
667
+ cwd: ctx.cwd,
668
+ sessionName: executionPlan.sessionName,
669
+ signal,
670
+ });
671
+ }
672
+ }
673
+
392
674
  const priorManagedSessionCwd = managedSessionCwd;
393
675
  const managedSessionState = resolveManagedSessionState({
394
676
  command: executionPlan.commandInfo.command,
@@ -423,43 +705,59 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
423
705
 
424
706
  const presentation = plainTextInspection
425
707
  ? {
426
- content: [{ type: "text" as const, text: processResult.stdout.trim() }],
708
+ batchFailure: undefined,
709
+ batchSteps: undefined,
710
+ content: [{ type: "text" as const, text: inspectionText ?? "" }],
711
+ data: undefined,
712
+ fullOutputPath: undefined,
713
+ fullOutputPaths: undefined,
427
714
  imagePath: undefined,
428
- summary: `${params.args.join(" ")} completed`,
715
+ imagePaths: undefined,
716
+ summary: `${redactedArgs.join(" ")} completed`,
429
717
  }
430
718
  : await buildToolPresentation({
431
719
  commandInfo: executionPlan.commandInfo,
432
720
  cwd: ctx.cwd,
433
721
  envelope: presentationEnvelope,
434
722
  errorText,
723
+ persistentArtifactStore: getPersistentSessionArtifactStore(ctx),
435
724
  });
725
+ const redactedContent = presentation.content.map((item) =>
726
+ item.type === "text" ? { ...item, text: redactSensitiveText(item.text) } : item,
727
+ );
436
728
 
437
729
  return {
438
- content: presentation.content,
730
+ content: redactedContent,
439
731
  details: {
440
- args: params.args,
441
- batchFailure: presentation.batchFailure,
442
- batchSteps: presentation.batchSteps,
732
+ args: redactedArgs,
733
+ batchFailure: redactSensitiveValue(presentation.batchFailure),
734
+ batchSteps: redactSensitiveValue(presentation.batchSteps),
443
735
  command: executionPlan.commandInfo.command,
736
+ compatibilityWorkaround,
444
737
  subcommand: executionPlan.commandInfo.subcommand,
445
- data: presentation.data,
446
- error: parsed.envelope?.error,
447
- navigationSummary,
448
- effectiveArgs: executionPlan.effectiveArgs,
738
+ data: redactSensitiveValue(presentation.data),
739
+ error: plainTextInspection ? undefined : redactSensitiveValue(parsed.envelope?.error),
740
+ inspection: plainTextInspection || undefined,
741
+ navigationSummary: redactSensitiveValue(navigationSummary),
742
+ openResultTabCorrection: redactSensitiveValue(openResultTabCorrection),
743
+ effectiveArgs: redactedEffectiveArgs,
449
744
  exitCode: processResult.exitCode,
450
745
  fullOutputPath: presentation.fullOutputPath,
451
746
  fullOutputPaths: presentation.fullOutputPaths,
452
747
  imagePath: presentation.imagePath,
453
748
  imagePaths: presentation.imagePaths,
454
- parseError: parsed.parseError,
749
+ parseError: plainTextInspection ? undefined : parsed.parseError,
455
750
  sessionMode,
456
- sessionName: executionPlan.sessionName,
457
- sessionRecoveryHint: executionPlan.recoveryHint,
751
+ ...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
752
+ sessionRecoveryHint: redactedRecoveryHint,
458
753
  startupScopedFlags: executionPlan.startupScopedFlags,
459
- stderr: processResult.stderr || undefined,
460
- stdout: parseSucceeded ? undefined : processResult.stdout,
461
- summary: presentation.summary,
462
- usedImplicitSession: executionPlan.usedImplicitSession,
754
+ stderr: processResult.stderr ? redactSensitiveText(processResult.stderr) : undefined,
755
+ stdout: plainTextInspection
756
+ ? redactSensitiveText(inspectionText ?? "")
757
+ : parseSucceeded
758
+ ? undefined
759
+ : redactSensitiveText(processResult.stdout),
760
+ summary: redactSensitiveText(presentation.summary),
463
761
  },
464
762
  isError: !succeeded,
465
763
  };
@@ -65,7 +65,7 @@ const INHERITED_ENV_NAMES = new Set([
65
65
  allProxyEnvName,
66
66
  noProxyEnvName,
67
67
  ]);
68
- const INHERITED_ENV_PREFIXES = ["AGENT_BROWSER_", "AI_GATEWAY_", "XDG_"] as const;
68
+ const INHERITED_ENV_PREFIXES = ["AI_GATEWAY_", "XDG_"] as const;
69
69
 
70
70
  export interface ProcessRunResult {
71
71
  aborted: boolean;