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.
- package/CHANGELOG.md +20 -2
- package/README.md +9 -3
- package/docs/ARCHITECTURE.md +9 -5
- package/docs/RELEASE.md +11 -2
- package/docs/REQUIREMENTS.md +6 -2
- package/docs/TOOL_CONTRACT.md +22 -5
- package/extensions/agent-browser/index.ts +386 -88
- package/extensions/agent-browser/lib/process.ts +1 -1
- package/extensions/agent-browser/lib/results/presentation.ts +92 -6
- package/extensions/agent-browser/lib/results/snapshot.ts +15 -6
- package/extensions/agent-browser/lib/runtime.ts +400 -7
- package/extensions/agent-browser/lib/temp.ts +94 -12
- package/package.json +1 -1
|
@@ -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
|
|
95
|
-
const
|
|
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
|
-
|
|
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
|
|
317
|
+
async function runSessionCommandData(options: {
|
|
318
|
+
args: string[];
|
|
145
319
|
cwd: string;
|
|
146
320
|
sessionName?: string;
|
|
147
321
|
signal?: AbortSignal;
|
|
148
|
-
}): Promise<
|
|
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
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
178
|
-
|
|
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
|
-
|
|
499
|
+
const restoredState = restoreManagedSessionStateFromBranch(ctx.sessionManager.getBranch(), managedSessionBaseName);
|
|
500
|
+
managedSessionActive = restoredState.active;
|
|
501
|
+
managedSessionName = restoredState.sessionName;
|
|
252
502
|
managedSessionCwd = ctx.cwd;
|
|
253
|
-
freshSessionOrdinal =
|
|
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${
|
|
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:
|
|
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:
|
|
574
|
+
args: redactedArgs,
|
|
323
575
|
invalidValueFlag: executionPlan.invalidValueFlag,
|
|
324
576
|
sessionMode,
|
|
325
|
-
sessionRecoveryHint:
|
|
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(
|
|
586
|
+
content: [{ type: "text", text: `Running agent-browser ${buildInvocationPreview(redactedEffectiveArgs)}` }],
|
|
335
587
|
details: {
|
|
336
|
-
|
|
588
|
+
compatibilityWorkaround,
|
|
589
|
+
effectiveArgs: redactedEffectiveArgs,
|
|
337
590
|
sessionMode,
|
|
338
|
-
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:
|
|
357
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
730
|
+
content: redactedContent,
|
|
439
731
|
details: {
|
|
440
|
-
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
|
-
|
|
448
|
-
|
|
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
|
|
457
|
-
sessionRecoveryHint:
|
|
751
|
+
...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
|
|
752
|
+
sessionRecoveryHint: redactedRecoveryHint,
|
|
458
753
|
startupScopedFlags: executionPlan.startupScopedFlags,
|
|
459
|
-
stderr: processResult.stderr
|
|
460
|
-
stdout:
|
|
461
|
-
|
|
462
|
-
|
|
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 = ["
|
|
68
|
+
const INHERITED_ENV_PREFIXES = ["AI_GATEWAY_", "XDG_"] as const;
|
|
69
69
|
|
|
70
70
|
export interface ProcessRunResult {
|
|
71
71
|
aborted: boolean;
|