pi-agent-browser-native 0.2.33 → 0.2.35
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 +46 -0
- package/README.md +47 -17
- package/docs/ARCHITECTURE.md +25 -13
- package/docs/COMMAND_REFERENCE.md +285 -47
- package/docs/ELECTRON.md +3 -3
- package/docs/RELEASE.md +22 -14
- package/docs/REQUIREMENTS.md +5 -5
- package/docs/SUPPORT_MATRIX.md +26 -22
- package/docs/TOOL_CONTRACT.md +97 -32
- package/extensions/agent-browser/index.ts +519 -2402
- package/extensions/agent-browser/lib/argv-descriptor.ts +90 -0
- package/extensions/agent-browser/lib/argv-grammar.ts +128 -0
- package/extensions/agent-browser/lib/command-policy.ts +71 -0
- package/extensions/agent-browser/lib/command-taxonomy.ts +336 -0
- package/extensions/agent-browser/lib/electron/cleanup.ts +1 -0
- package/extensions/agent-browser/lib/executable-path.ts +19 -0
- package/extensions/agent-browser/lib/input-modes/job.ts +62 -0
- package/extensions/agent-browser/lib/input-modes/params.ts +8 -8
- package/extensions/agent-browser/lib/input-modes.ts +3 -0
- package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +65 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/browser-action-model.ts +154 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +149 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +77 -29
- package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +6 -2
- package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +33 -27
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +74 -23
- package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +67 -17
- package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +93 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +19 -123
- package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +32 -1
- package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +860 -0
- package/extensions/agent-browser/lib/playbook.ts +24 -23
- package/extensions/agent-browser/lib/prompt-policy.ts +122 -0
- package/extensions/agent-browser/lib/results/action-recommendations.ts +3 -23
- package/extensions/agent-browser/lib/results/categories.ts +1 -1
- package/extensions/agent-browser/lib/results/presentation/navigation.ts +2 -34
- package/extensions/agent-browser/lib/results/presentation/registry.ts +34 -6
- package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +133 -0
- package/extensions/agent-browser/lib/results/presentation.ts +11 -6
- package/extensions/agent-browser/lib/runtime.ts +93 -227
- package/extensions/agent-browser/lib/session-page-state.ts +31 -14
- package/extensions/agent-browser/lib/temp.ts +148 -23
- package/package.json +4 -4
- package/scripts/agent-browser-capability-baseline.mjs +198 -1
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Scope: Presentation shaping only; upstream stdout parsing and snapshot compaction internals live in separate modules.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import type { CompiledAgentBrowserSemanticAction } from "../input-modes/types.js";
|
|
7
8
|
import { isRecord } from "../parsing.js";
|
|
8
9
|
import type { CommandInfo } from "../runtime.js";
|
|
9
10
|
import type { PersistentSessionArtifactStore } from "../temp.js";
|
|
@@ -43,6 +44,7 @@ import { buildErrorPresentation } from "./presentation/errors.js";
|
|
|
43
44
|
import { compactLargePresentationOutput } from "./presentation/large-output.js";
|
|
44
45
|
import { buildPageChangeSummary } from "./presentation/navigation.js";
|
|
45
46
|
import { formatPresentationContentText, formatPresentationSummary } from "./presentation/registry.js";
|
|
47
|
+
import { resolvePresentationCommandInfo } from "./presentation/semantic-action.js";
|
|
46
48
|
|
|
47
49
|
function sanitizeModelFacingPresentation(presentation: ToolPresentation): ToolPresentation {
|
|
48
50
|
presentation.content = presentation.content.map((item) => {
|
|
@@ -65,6 +67,7 @@ export async function buildToolPresentation(options: {
|
|
|
65
67
|
artifactRequest?: ArtifactRequestContext;
|
|
66
68
|
batchArtifactRequests?: Array<ArtifactRequestContext | undefined>;
|
|
67
69
|
commandInfo: CommandInfo;
|
|
70
|
+
compiledSemanticAction?: CompiledAgentBrowserSemanticAction;
|
|
68
71
|
cwd: string;
|
|
69
72
|
envelope?: AgentBrowserEnvelope;
|
|
70
73
|
errorText?: string;
|
|
@@ -76,12 +79,14 @@ export async function buildToolPresentation(options: {
|
|
|
76
79
|
artifactManifest,
|
|
77
80
|
artifactRequest,
|
|
78
81
|
commandInfo,
|
|
82
|
+
compiledSemanticAction,
|
|
79
83
|
cwd,
|
|
80
84
|
envelope,
|
|
81
85
|
errorText,
|
|
82
86
|
persistentArtifactStore,
|
|
83
87
|
sessionName,
|
|
84
88
|
} = options;
|
|
89
|
+
const presentationCommandInfo = resolvePresentationCommandInfo(commandInfo, compiledSemanticAction);
|
|
85
90
|
|
|
86
91
|
if (errorText) {
|
|
87
92
|
return buildErrorPresentation({ args, commandInfo, errorText, sessionName });
|
|
@@ -89,10 +94,10 @@ export async function buildToolPresentation(options: {
|
|
|
89
94
|
|
|
90
95
|
const data = enrichStreamStatusData(commandInfo, envelope?.data);
|
|
91
96
|
const presentationData = redactPresentationData(commandInfo, data);
|
|
92
|
-
const artifacts = await extractFileArtifacts({ artifactRequest, commandInfo, cwd, data, sessionName });
|
|
97
|
+
const artifacts = await extractFileArtifacts({ artifactRequest, commandInfo: presentationCommandInfo, cwd, data, sessionName });
|
|
93
98
|
const artifactVerification = buildArtifactVerificationSummary(artifacts);
|
|
94
99
|
const artifactSummary = formatArtifactSummary(artifacts);
|
|
95
|
-
const summary = artifactSummary ?? formatPresentationSummary(commandInfo, data);
|
|
100
|
+
const summary = artifactSummary ?? formatPresentationSummary(commandInfo, data, compiledSemanticAction);
|
|
96
101
|
const artifactText = artifacts.length > 0 ? formatArtifactMetadataLines(artifacts).join("\n") : undefined;
|
|
97
102
|
|
|
98
103
|
let presentation: ToolPresentation;
|
|
@@ -113,7 +118,7 @@ export async function buildToolPresentation(options: {
|
|
|
113
118
|
presentation = {
|
|
114
119
|
artifactVerification,
|
|
115
120
|
artifacts: artifacts.length > 0 ? artifacts : undefined,
|
|
116
|
-
content: [{ type: "text", text: artifactText ?? formatPresentationContentText(commandInfo, data) }],
|
|
121
|
+
content: [{ type: "text", text: artifactText ?? formatPresentationContentText(commandInfo, data, compiledSemanticAction) }],
|
|
117
122
|
data: presentationData,
|
|
118
123
|
summary,
|
|
119
124
|
};
|
|
@@ -159,7 +164,7 @@ export async function buildToolPresentation(options: {
|
|
|
159
164
|
if (!presentationWithManifest.resultCategory) {
|
|
160
165
|
const categoryDetails = buildAgentBrowserResultCategoryDetails({
|
|
161
166
|
artifacts: presentationWithManifest.artifacts,
|
|
162
|
-
command:
|
|
167
|
+
command: presentationCommandInfo.command,
|
|
163
168
|
confirmationRequired: confirmationRequired !== undefined,
|
|
164
169
|
errorText: envelope?.success === false ? presentationWithManifest.summary : undefined,
|
|
165
170
|
savedFile: presentationWithManifest.savedFile,
|
|
@@ -186,7 +191,7 @@ export async function buildToolPresentation(options: {
|
|
|
186
191
|
const genericNextActions = presentationWithManifest.nextActions ? undefined : buildAgentBrowserNextActions({
|
|
187
192
|
artifacts: presentationWithManifest.artifacts,
|
|
188
193
|
args,
|
|
189
|
-
command:
|
|
194
|
+
command: presentationCommandInfo.command,
|
|
190
195
|
confirmationId: confirmationRequired?.id,
|
|
191
196
|
failureCategory: presentationWithManifest.failureCategory,
|
|
192
197
|
resultCategory: presentationWithManifest.resultCategory ?? "success",
|
|
@@ -203,7 +208,7 @@ export async function buildToolPresentation(options: {
|
|
|
203
208
|
);
|
|
204
209
|
presentationWithManifest.pageChangeSummary = presentationWithManifest.pageChangeSummary ?? buildPageChangeSummary({
|
|
205
210
|
artifacts: presentationWithManifest.artifacts,
|
|
206
|
-
commandInfo,
|
|
211
|
+
commandInfo: presentationCommandInfo,
|
|
207
212
|
data,
|
|
208
213
|
nextActions: presentationWithManifest.nextActions,
|
|
209
214
|
savedFilePath: presentationWithManifest.savedFilePath,
|
|
@@ -11,6 +11,23 @@
|
|
|
11
11
|
import { createHash, randomUUID } from "node:crypto";
|
|
12
12
|
import { basename } from "node:path";
|
|
13
13
|
|
|
14
|
+
import {
|
|
15
|
+
extractCommandTokens,
|
|
16
|
+
findCommandStartIndex,
|
|
17
|
+
parseArgvDescriptor,
|
|
18
|
+
parseCommandInfo,
|
|
19
|
+
type CommandInfo,
|
|
20
|
+
} from "./argv-descriptor.js";
|
|
21
|
+
import {
|
|
22
|
+
GLOBAL_VALUE_FLAGS_ALLOWING_DASH_VALUE,
|
|
23
|
+
PREVALIDATED_VALUE_FLAGS,
|
|
24
|
+
} from "./argv-grammar.js";
|
|
25
|
+
import { needsManagedSession } from "./command-policy.js";
|
|
26
|
+
import { isCloseCommand, isOpenNavigationCommand } from "./command-taxonomy.js";
|
|
27
|
+
|
|
28
|
+
export type { CommandInfo } from "./argv-descriptor.js";
|
|
29
|
+
export { extractCommandTokens, findCommandStartIndex, parseArgvDescriptor, parseCommandInfo } from "./argv-descriptor.js";
|
|
30
|
+
|
|
14
31
|
import { isRecord } from "./parsing.js";
|
|
15
32
|
|
|
16
33
|
/**
|
|
@@ -81,7 +98,6 @@ const LAUNCH_SCOPED_FLAG_LABEL = LAUNCH_SCOPED_FLAG_DEFINITIONS.map((definition)
|
|
|
81
98
|
* tab-correction (the `tab list` + re-select cycle).
|
|
82
99
|
*/
|
|
83
100
|
const LAUNCH_SCOPED_TAB_CORRECTION_FLAGS = new Set(["--profile", "--session-name", "--state"] as const);
|
|
84
|
-
const OPEN_COMMANDS = new Set(["goto", "navigate", "open"]);
|
|
85
101
|
const OPENAI_HEADLESS_COMPAT_HOSTS = new Set(["chat.com", "chat.openai.com", "chatgpt.com"]);
|
|
86
102
|
const BRAVE_API_KEY_ENV = "BRAVE_API_KEY";
|
|
87
103
|
const AGENT_BROWSER_IDLE_TIMEOUT_ENV = "AGENT_BROWSER_IDLE_TIMEOUT_MS";
|
|
@@ -89,99 +105,13 @@ const IMPLICIT_SESSION_IDLE_TIMEOUT_ENV = "PI_AGENT_BROWSER_IMPLICIT_SESSION_IDL
|
|
|
89
105
|
const IMPLICIT_SESSION_CLOSE_TIMEOUT_ENV = "PI_AGENT_BROWSER_IMPLICIT_SESSION_CLOSE_TIMEOUT_MS";
|
|
90
106
|
const DEFAULT_IMPLICIT_SESSION_IDLE_TIMEOUT_MS = 15 * 60 * 1000;
|
|
91
107
|
const DEFAULT_IMPLICIT_SESSION_CLOSE_TIMEOUT_MS = 5_000;
|
|
92
|
-
const LEGACY_BASH_ALLOW_PATTERNS = [
|
|
93
|
-
/\b(?:bash-oriented workflow|bash workflow)\b/i,
|
|
94
|
-
/\b(?:use|via|through|with)\s+bash\b/i,
|
|
95
|
-
/\bnpx\s+agent-browser\b/i,
|
|
96
|
-
/\bagent-browser\s+--(?:help|version)\b/i,
|
|
97
|
-
/\bdebug(?:ging)?\b.*\b(?:agent[_ -]?browser|agent_browser|browser integration)\b/i,
|
|
98
|
-
];
|
|
99
|
-
const BROWSER_PROMPT_PATTERNS = [
|
|
100
|
-
/\b(?:agent[_ -]?browser|browser automation|eval\s+--stdin|screenshot|snapshot|tab\s+list)\b/i,
|
|
101
|
-
/\b(?:react\s+(?:tree|inspect|renders|suspense)|web\s+vitals|core\s+web\s+vitals|pushstate)\b/i,
|
|
102
|
-
/\b(?:live\s+docs?|online\s+research|research\s+(?:online|the\s+web)|search\s+(?:online|the\s+web)|web\s+research)\b/i,
|
|
103
|
-
/\bbrowser\b.*\b(?:automation|click|fill|navigate|open|page|screenshot|site|snapshot|tab|url|visit|web(?:site| page)?)\b/i,
|
|
104
|
-
/\b(?:browse|click|fill|login|navigate|open|visit)\b.*\b(?:https?:\/\/\S+|page|site|tab|url|web(?:site| page)?)\b/i,
|
|
105
|
-
];
|
|
106
108
|
const INSPECTION_FLAGS = new Set(["--help", "-h", "--version", "-V"]);
|
|
107
109
|
const SENSITIVE_VALUE_FLAGS = new Set(["--body", "--headers", "--password", "--proxy"]);
|
|
108
|
-
const GLOBAL_VALUE_FLAGS_ALLOWING_DASH_VALUE = new Set(["--args"]);
|
|
109
|
-
const GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES = new Set([
|
|
110
|
-
"--allow-file-access",
|
|
111
|
-
"--annotate",
|
|
112
|
-
"--auto-connect",
|
|
113
|
-
"--confirm-interactive",
|
|
114
|
-
"--content-boundaries",
|
|
115
|
-
"--debug",
|
|
116
|
-
"--headed",
|
|
117
|
-
"--ignore-https-errors",
|
|
118
|
-
"--json",
|
|
119
|
-
"--no-auto-dialog",
|
|
120
|
-
"--quiet",
|
|
121
|
-
"-q",
|
|
122
|
-
"--verbose",
|
|
123
|
-
"-v",
|
|
124
|
-
]);
|
|
125
110
|
const SENSITIVE_QUERY_PARAM_PATTERN =
|
|
126
111
|
/^(?:access(?:_|-)?token|api(?:_|-)?key|auth|authorization|bearer|client(?:_|-)?secret|code|cookie|id(?:_|-)?token|key|pass(?:word)?|refresh(?:_|-)?token|secret|sentry(?:_|-)?key|session(?:_|-)?id|sig(?:nature)?|token|write(?:_|-)?key)$/i;
|
|
127
112
|
const SENSITIVE_FIELD_NAME_PATTERN =
|
|
128
113
|
/^(?:access(?:_|-)?token|api(?:_|-)?key|auth(?:orization)?|bearer|client(?:_|-)?secret|cookie|id(?:_|-)?token|pass(?:word)?|proxy(?:_|-)?authorization|refresh(?:_|-)?token|secret|sentry(?:_|-)?key|session(?:_|-)?id|set(?:_|-)?cookie|sig(?:nature)?|token|write(?:_|-)?key|x(?:_|-)?api(?:_|-)?key)$/i;
|
|
129
114
|
|
|
130
|
-
const VALUE_FLAGS = new Set([
|
|
131
|
-
"--session",
|
|
132
|
-
"--cdp",
|
|
133
|
-
"--config",
|
|
134
|
-
"--profile",
|
|
135
|
-
"--session-name",
|
|
136
|
-
"--proxy",
|
|
137
|
-
"--proxy-bypass",
|
|
138
|
-
"--headers",
|
|
139
|
-
"--executable-path",
|
|
140
|
-
"--extension",
|
|
141
|
-
"--init-script",
|
|
142
|
-
"--enable",
|
|
143
|
-
"--provider",
|
|
144
|
-
"-p",
|
|
145
|
-
"--engine",
|
|
146
|
-
"--state",
|
|
147
|
-
"--download-path",
|
|
148
|
-
"--screenshot-dir",
|
|
149
|
-
"--screenshot-format",
|
|
150
|
-
"--screenshot-quality",
|
|
151
|
-
"--color-scheme",
|
|
152
|
-
"--device",
|
|
153
|
-
"--port",
|
|
154
|
-
"--args",
|
|
155
|
-
"--user-agent",
|
|
156
|
-
"--allowed-domains",
|
|
157
|
-
"--action-policy",
|
|
158
|
-
"--confirm-actions",
|
|
159
|
-
"--max-output",
|
|
160
|
-
"--model",
|
|
161
|
-
"--baseline",
|
|
162
|
-
"--body",
|
|
163
|
-
"--categories",
|
|
164
|
-
"--curl",
|
|
165
|
-
"--depth",
|
|
166
|
-
"-d",
|
|
167
|
-
"--domain",
|
|
168
|
-
"--expires",
|
|
169
|
-
"--filter",
|
|
170
|
-
"--fn",
|
|
171
|
-
"--label",
|
|
172
|
-
"--load",
|
|
173
|
-
"--name",
|
|
174
|
-
"--path",
|
|
175
|
-
"--resource-type",
|
|
176
|
-
"--sameSite",
|
|
177
|
-
"--selector",
|
|
178
|
-
"-s",
|
|
179
|
-
"--text",
|
|
180
|
-
"--timeout",
|
|
181
|
-
"--url",
|
|
182
|
-
"--username",
|
|
183
|
-
"--password",
|
|
184
|
-
]);
|
|
185
115
|
const DEFAULT_HEADLESS_COMPAT_USER_AGENT_BY_PLATFORM: Partial<Record<NodeJS.Platform, string>> = {
|
|
186
116
|
darwin: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
|
|
187
117
|
linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
|
|
@@ -194,11 +124,6 @@ const MAX_PROJECT_SLUG_LENGTH = 24;
|
|
|
194
124
|
const SESSION_NAME_CWD_HASH_LENGTH = 8;
|
|
195
125
|
const SESSION_NAME_SESSION_ID_LENGTH = 12;
|
|
196
126
|
|
|
197
|
-
export interface CommandInfo {
|
|
198
|
-
command?: string;
|
|
199
|
-
subcommand?: string;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
127
|
export type SessionMode = "auto" | "fresh";
|
|
203
128
|
|
|
204
129
|
export interface SessionRecoveryHint {
|
|
@@ -248,13 +173,10 @@ export interface ManagedSessionState {
|
|
|
248
173
|
}
|
|
249
174
|
|
|
250
175
|
export interface RestoredManagedSessionState extends ManagedSessionState {
|
|
176
|
+
closedSessionName?: string;
|
|
251
177
|
freshSessionOrdinal: number;
|
|
252
178
|
}
|
|
253
179
|
|
|
254
|
-
export interface PromptPolicy {
|
|
255
|
-
allowLegacyAgentBrowserBash: boolean;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
180
|
function isStringArray(value: unknown): value is string[] {
|
|
259
181
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
260
182
|
}
|
|
@@ -504,22 +426,10 @@ export function redactInvocationArgs(args: string[]): string[] {
|
|
|
504
426
|
return redacted;
|
|
505
427
|
}
|
|
506
428
|
|
|
507
|
-
export function shouldAppendBrowserSystemPrompt(prompt: string): boolean {
|
|
508
|
-
const normalizedPrompt = prompt.trim();
|
|
509
|
-
if (normalizedPrompt.length === 0) {
|
|
510
|
-
return false;
|
|
511
|
-
}
|
|
512
|
-
return BROWSER_PROMPT_PATTERNS.some((pattern) => pattern.test(normalizedPrompt));
|
|
513
|
-
}
|
|
514
|
-
|
|
515
429
|
export function isPlainTextInspectionArgs(args: string[]): boolean {
|
|
516
430
|
return args.some((token) => INSPECTION_FLAGS.has(token));
|
|
517
431
|
}
|
|
518
432
|
|
|
519
|
-
function isStatelessInspectionCommand(commandInfo: CommandInfo): boolean {
|
|
520
|
-
return commandInfo.command === "skills" && ["list", "get", "path"].includes(commandInfo.subcommand ?? "");
|
|
521
|
-
}
|
|
522
|
-
|
|
523
433
|
export function hasUsableBraveApiKey(apiKey: string | null | undefined = process.env[BRAVE_API_KEY_ENV]): boolean {
|
|
524
434
|
return typeof apiKey === "string" && apiKey.trim().length > 0;
|
|
525
435
|
}
|
|
@@ -556,7 +466,7 @@ export function resolveManagedSessionState(options: {
|
|
|
556
466
|
if (!managedSessionName) {
|
|
557
467
|
return { active: priorActive, sessionName: priorSessionName };
|
|
558
468
|
}
|
|
559
|
-
if (command
|
|
469
|
+
if (isCloseCommand(command) && managedSessionName === priorSessionName) {
|
|
560
470
|
return { active: succeeded ? false : priorActive, sessionName: priorSessionName };
|
|
561
471
|
}
|
|
562
472
|
if (!succeeded) {
|
|
@@ -594,6 +504,29 @@ function getManagedSessionRestoreRank(options: {
|
|
|
594
504
|
return nextRank;
|
|
595
505
|
}
|
|
596
506
|
|
|
507
|
+
function getRestorableManagedSessionName(value: unknown, fallbackSessionName: string): string | undefined {
|
|
508
|
+
return typeof value === "string" && isRestorableManagedSessionName(value, fallbackSessionName) ? value : undefined;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function getElectronCleanupClosedManagedSessionNames(details: Record<string, unknown>, fallbackSessionName: string): string[] {
|
|
512
|
+
const electron = isRecord(details.electron) ? details.electron : undefined;
|
|
513
|
+
const cleanup = isRecord(electron?.cleanup) ? electron.cleanup : undefined;
|
|
514
|
+
const results = Array.isArray(cleanup?.results) ? cleanup.results : [];
|
|
515
|
+
const closedSessionNames = new Set<string>();
|
|
516
|
+
for (const result of results) {
|
|
517
|
+
if (!isRecord(result) || !Array.isArray(result.steps)) continue;
|
|
518
|
+
const record = isRecord(result.record) ? result.record : undefined;
|
|
519
|
+
for (const step of result.steps) {
|
|
520
|
+
if (!isRecord(step) || step.resource !== "managed-session") continue;
|
|
521
|
+
if (step.state !== "removed" && step.state !== "already-gone") continue;
|
|
522
|
+
const sessionName = getRestorableManagedSessionName(step.sessionName, fallbackSessionName)
|
|
523
|
+
?? getRestorableManagedSessionName(record?.sessionName, fallbackSessionName);
|
|
524
|
+
if (sessionName) closedSessionNames.add(sessionName);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return [...closedSessionNames];
|
|
528
|
+
}
|
|
529
|
+
|
|
597
530
|
export function restoreManagedSessionStateFromBranch(
|
|
598
531
|
branch: unknown[],
|
|
599
532
|
fallbackSessionName: string,
|
|
@@ -603,9 +536,21 @@ export function restoreManagedSessionStateFromBranch(
|
|
|
603
536
|
sessionName: fallbackSessionName,
|
|
604
537
|
};
|
|
605
538
|
let activeRestoreRank = 0;
|
|
539
|
+
let closedSessionName: string | undefined;
|
|
606
540
|
let freshSessionOrdinal = 0;
|
|
607
541
|
const freshSessionRanks = new Map<string, number>();
|
|
608
542
|
|
|
543
|
+
const applyManagedClose = (sessionName: string): void => {
|
|
544
|
+
const restoreRank = getManagedSessionRestoreRank({
|
|
545
|
+
fallbackSessionName,
|
|
546
|
+
freshSessionRanks,
|
|
547
|
+
sessionName,
|
|
548
|
+
});
|
|
549
|
+
if (restoreRank === undefined || sessionName !== restoredState.sessionName) return;
|
|
550
|
+
restoredState = { active: false, sessionName: restoredState.sessionName };
|
|
551
|
+
closedSessionName = sessionName;
|
|
552
|
+
};
|
|
553
|
+
|
|
609
554
|
for (const entry of branch) {
|
|
610
555
|
if (!isRecord(entry) || entry.type !== "message") {
|
|
611
556
|
continue;
|
|
@@ -623,17 +568,35 @@ export function restoreManagedSessionStateFromBranch(
|
|
|
623
568
|
continue;
|
|
624
569
|
}
|
|
625
570
|
|
|
571
|
+
for (const sessionName of getElectronCleanupClosedManagedSessionNames(details, fallbackSessionName)) {
|
|
572
|
+
applyManagedClose(sessionName);
|
|
573
|
+
}
|
|
574
|
+
|
|
626
575
|
const explicitSessionName = extractExplicitSessionName(args);
|
|
627
576
|
const sessionName = typeof details.sessionName === "string" ? details.sessionName : undefined;
|
|
628
577
|
const sessionMode = details.sessionMode === "fresh" || details.sessionMode === "auto" ? details.sessionMode : undefined;
|
|
629
578
|
const usedImplicitSession = details.usedImplicitSession === true;
|
|
579
|
+
const command = typeof details.command === "string" ? details.command : parseCommandInfo(args).command;
|
|
580
|
+
const commandClosesSession = isCloseCommand(command);
|
|
581
|
+
const outcome = typeof details.managedSessionOutcome === "object" && details.managedSessionOutcome !== null ? details.managedSessionOutcome as Record<string, unknown> : undefined;
|
|
582
|
+
const outcomeStatus = typeof outcome?.status === "string" ? outcome.status : undefined;
|
|
583
|
+
const outcomeCurrentSessionName = typeof outcome?.currentSessionName === "string" ? outcome.currentSessionName : undefined;
|
|
584
|
+
const outcomeAttemptedSessionName = getRestorableManagedSessionName(outcome?.attemptedSessionName, fallbackSessionName);
|
|
585
|
+
const outcomeClosedSessionName = outcomeStatus === "closed" && outcome?.succeeded === true
|
|
586
|
+
? outcomeAttemptedSessionName ?? getRestorableManagedSessionName(outcomeCurrentSessionName, fallbackSessionName) ?? getRestorableManagedSessionName(sessionName, fallbackSessionName)
|
|
587
|
+
: undefined;
|
|
588
|
+
const restorableDetailSessionName = getRestorableManagedSessionName(sessionName, fallbackSessionName);
|
|
589
|
+
const explicitCloseSessionName = commandClosesSession && explicitSessionName && restorableDetailSessionName === explicitSessionName
|
|
590
|
+
? restorableDetailSessionName
|
|
591
|
+
: undefined;
|
|
630
592
|
const managedSessionName =
|
|
631
593
|
!explicitSessionName &&
|
|
632
|
-
|
|
633
|
-
isRestorableManagedSessionName(sessionName, fallbackSessionName) &&
|
|
594
|
+
restorableDetailSessionName &&
|
|
634
595
|
(usedImplicitSession || sessionMode === "fresh")
|
|
635
|
-
?
|
|
636
|
-
:
|
|
596
|
+
? restorableDetailSessionName
|
|
597
|
+
: commandClosesSession
|
|
598
|
+
? outcomeClosedSessionName ?? explicitCloseSessionName
|
|
599
|
+
: undefined;
|
|
637
600
|
if (!managedSessionName) {
|
|
638
601
|
continue;
|
|
639
602
|
}
|
|
@@ -646,25 +609,19 @@ export function restoreManagedSessionStateFromBranch(
|
|
|
646
609
|
if (restoreRank === undefined) {
|
|
647
610
|
continue;
|
|
648
611
|
}
|
|
612
|
+
freshSessionOrdinal = Math.max(freshSessionOrdinal, restoreRank);
|
|
649
613
|
|
|
650
614
|
const messageIsError = typeof message.isError === "boolean" ? message.isError : undefined;
|
|
651
615
|
const exitCode = typeof details.exitCode === "number" ? details.exitCode : undefined;
|
|
652
|
-
const outcome = typeof details.managedSessionOutcome === "object" && details.managedSessionOutcome !== null ? details.managedSessionOutcome as Record<string, unknown> : undefined;
|
|
653
|
-
const outcomeStatus = typeof outcome?.status === "string" ? outcome.status : undefined;
|
|
654
|
-
const outcomeCurrentSessionName = typeof outcome?.currentSessionName === "string" ? outcome.currentSessionName : undefined;
|
|
655
616
|
const outcomeActiveAfter = outcome?.activeAfter === true;
|
|
656
617
|
const outcomeRepresentsActiveCurrentSession = outcomeActiveAfter && outcomeCurrentSessionName === managedSessionName && (outcomeStatus === "created" || outcomeStatus === "replaced" || outcomeStatus === "unchanged");
|
|
657
618
|
const succeeded = outcomeRepresentsActiveCurrentSession ? true : messageIsError === undefined ? exitCode === undefined || exitCode === 0 : !messageIsError;
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
freshSessionOrdinal += 1;
|
|
661
|
-
}
|
|
662
|
-
const staleCompletion = succeeded && command !== "close" && restoreRank < activeRestoreRank;
|
|
663
|
-
if (staleCompletion) {
|
|
619
|
+
if (commandClosesSession) {
|
|
620
|
+
if (succeeded) applyManagedClose(managedSessionName);
|
|
664
621
|
continue;
|
|
665
622
|
}
|
|
666
|
-
const
|
|
667
|
-
if (
|
|
623
|
+
const staleCompletion = succeeded && restoreRank < activeRestoreRank;
|
|
624
|
+
if (staleCompletion) {
|
|
668
625
|
continue;
|
|
669
626
|
}
|
|
670
627
|
|
|
@@ -675,13 +632,15 @@ export function restoreManagedSessionStateFromBranch(
|
|
|
675
632
|
priorSessionName: restoredState.sessionName,
|
|
676
633
|
succeeded,
|
|
677
634
|
});
|
|
678
|
-
if (succeeded &&
|
|
635
|
+
if (succeeded && restoredState.active) {
|
|
679
636
|
activeRestoreRank = restoreRank;
|
|
637
|
+
closedSessionName = undefined;
|
|
680
638
|
}
|
|
681
639
|
}
|
|
682
640
|
|
|
683
641
|
return {
|
|
684
642
|
...restoredState,
|
|
643
|
+
...(closedSessionName ? { closedSessionName } : {}),
|
|
685
644
|
freshSessionOrdinal,
|
|
686
645
|
};
|
|
687
646
|
}
|
|
@@ -739,11 +698,6 @@ export function validateToolArgs(args: string[]): string | undefined {
|
|
|
739
698
|
return undefined;
|
|
740
699
|
}
|
|
741
700
|
|
|
742
|
-
function isBooleanLiteral(token: string | undefined): boolean {
|
|
743
|
-
const normalized = token?.trim().toLowerCase();
|
|
744
|
-
return normalized === "true" || normalized === "false";
|
|
745
|
-
}
|
|
746
|
-
|
|
747
701
|
function getInvalidValueFlagDetails(args: string[]): InvalidValueFlagDetails | undefined {
|
|
748
702
|
for (let index = 0; index < args.length; index += 1) {
|
|
749
703
|
const token = args[index];
|
|
@@ -751,7 +705,7 @@ function getInvalidValueFlagDetails(args: string[]): InvalidValueFlagDetails | u
|
|
|
751
705
|
continue;
|
|
752
706
|
}
|
|
753
707
|
const normalizedToken = token.split("=", 1)[0] ?? token;
|
|
754
|
-
if (!
|
|
708
|
+
if (!PREVALIDATED_VALUE_FLAGS.has(normalizedToken)) {
|
|
755
709
|
continue;
|
|
756
710
|
}
|
|
757
711
|
if (token.includes("=")) {
|
|
@@ -876,7 +830,7 @@ function getDefaultHeadlessCompatUserAgent(platform: NodeJS.Platform = process.p
|
|
|
876
830
|
}
|
|
877
831
|
|
|
878
832
|
function getCompatibilityWorkaround(args: string[], commandInfo: CommandInfo): CompatibilityWorkaround | undefined {
|
|
879
|
-
if (!commandInfo.command || !
|
|
833
|
+
if (!commandInfo.command || !isOpenNavigationCommand(commandInfo.command) || !commandInfo.subcommand) {
|
|
880
834
|
return undefined;
|
|
881
835
|
}
|
|
882
836
|
if (hasFlagToken(args, "--user-agent")) {
|
|
@@ -947,40 +901,6 @@ export function hasLaunchScopedTabCorrectionFlag(args: string[]): boolean {
|
|
|
947
901
|
});
|
|
948
902
|
}
|
|
949
903
|
|
|
950
|
-
export function buildPromptPolicy(prompt: string): PromptPolicy {
|
|
951
|
-
return {
|
|
952
|
-
allowLegacyAgentBrowserBash: LEGACY_BASH_ALLOW_PATTERNS.some((pattern) => pattern.test(prompt)),
|
|
953
|
-
};
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
function getMessageText(content: unknown): string {
|
|
957
|
-
if (typeof content === "string") return content;
|
|
958
|
-
if (!Array.isArray(content)) return "";
|
|
959
|
-
|
|
960
|
-
return content
|
|
961
|
-
.map((item) => {
|
|
962
|
-
if (typeof item !== "object" || item === null) return "";
|
|
963
|
-
return item.type === "text" && typeof item.text === "string" ? item.text : "";
|
|
964
|
-
})
|
|
965
|
-
.filter((text) => text.length > 0)
|
|
966
|
-
.join("\n");
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
export function getLatestUserPrompt(branch: unknown[]): string {
|
|
970
|
-
for (let index = branch.length - 1; index >= 0; index -= 1) {
|
|
971
|
-
const entry = branch[index];
|
|
972
|
-
if (typeof entry !== "object" || entry === null || !("type" in entry) || entry.type !== "message") {
|
|
973
|
-
continue;
|
|
974
|
-
}
|
|
975
|
-
const message = "message" in entry ? entry.message : undefined;
|
|
976
|
-
if (typeof message !== "object" || message === null || !("role" in message) || message.role !== "user") {
|
|
977
|
-
continue;
|
|
978
|
-
}
|
|
979
|
-
return getMessageText("content" in message ? message.content : undefined);
|
|
980
|
-
}
|
|
981
|
-
return "";
|
|
982
|
-
}
|
|
983
|
-
|
|
984
904
|
export function buildExecutionPlan(
|
|
985
905
|
args: string[],
|
|
986
906
|
options: {
|
|
@@ -993,8 +913,10 @@ export function buildExecutionPlan(
|
|
|
993
913
|
const invalidValueFlag = getInvalidValueFlagDetails(args);
|
|
994
914
|
const startupScopedFlags = getStartupScopedFlags(args);
|
|
995
915
|
const plainTextInspection = isPlainTextInspectionArgs(args);
|
|
996
|
-
const
|
|
997
|
-
const
|
|
916
|
+
const argvDescriptor = parseArgvDescriptor(args);
|
|
917
|
+
const commandTokens = argvDescriptor.commandTokens;
|
|
918
|
+
const commandInfo = argvDescriptor.commandInfo;
|
|
919
|
+
const commandNeedsManagedSession = !plainTextInspection && needsManagedSession(argvDescriptor);
|
|
998
920
|
const effectiveArgs = plainTextInspection ? [...args] : args.includes("--json") ? [] : ["--json"];
|
|
999
921
|
if (invalidValueFlag) {
|
|
1000
922
|
return {
|
|
@@ -1020,7 +942,7 @@ export function buildExecutionPlan(
|
|
|
1020
942
|
|
|
1021
943
|
const explicitSessionName = extractExplicitSessionName(args);
|
|
1022
944
|
const shouldCreateFreshManagedSession =
|
|
1023
|
-
!explicitSessionName && options.sessionMode === "fresh" && commandInfo.command !== undefined && commandInfo.command
|
|
945
|
+
!explicitSessionName && options.sessionMode === "fresh" && commandInfo.command !== undefined && !isCloseCommand(commandInfo.command);
|
|
1024
946
|
const compatibilityWorkaround = getCompatibilityWorkaround(args, commandInfo);
|
|
1025
947
|
let managedSessionName: string | undefined;
|
|
1026
948
|
let recoveryHint: SessionRecoveryHint | undefined;
|
|
@@ -1028,7 +950,7 @@ export function buildExecutionPlan(
|
|
|
1028
950
|
let usedImplicitSession = false;
|
|
1029
951
|
let validationError: string | undefined;
|
|
1030
952
|
|
|
1031
|
-
if (!explicitSessionName && options.sessionMode === "auto" &&
|
|
953
|
+
if (!explicitSessionName && options.sessionMode === "auto" && commandNeedsManagedSession) {
|
|
1032
954
|
if (options.managedSessionActive && startupScopedFlags.length > 0) {
|
|
1033
955
|
recoveryHint = {
|
|
1034
956
|
exampleArgs: args,
|
|
@@ -1047,7 +969,7 @@ export function buildExecutionPlan(
|
|
|
1047
969
|
sessionName = options.managedSessionName;
|
|
1048
970
|
usedImplicitSession = true;
|
|
1049
971
|
}
|
|
1050
|
-
} else if (shouldCreateFreshManagedSession &&
|
|
972
|
+
} else if (shouldCreateFreshManagedSession && commandNeedsManagedSession) {
|
|
1051
973
|
effectiveArgs.push("--session", options.freshSessionName);
|
|
1052
974
|
managedSessionName = options.freshSessionName;
|
|
1053
975
|
sessionName = options.freshSessionName;
|
|
@@ -1116,59 +1038,3 @@ export function chooseOpenResultTabCorrection(options: {
|
|
|
1116
1038
|
}
|
|
1117
1039
|
: undefined;
|
|
1118
1040
|
}
|
|
1119
|
-
|
|
1120
|
-
function getOpenCommandTarget(commandTokens: string[]): string | undefined {
|
|
1121
|
-
for (let index = 1; index < commandTokens.length; index += 1) {
|
|
1122
|
-
const token = commandTokens[index];
|
|
1123
|
-
if (token === "--init-script" || token === "--enable") {
|
|
1124
|
-
index += 1;
|
|
1125
|
-
continue;
|
|
1126
|
-
}
|
|
1127
|
-
if (token.startsWith("--init-script=") || token.startsWith("--enable=")) {
|
|
1128
|
-
continue;
|
|
1129
|
-
}
|
|
1130
|
-
if (token.startsWith("-")) {
|
|
1131
|
-
continue;
|
|
1132
|
-
}
|
|
1133
|
-
return token;
|
|
1134
|
-
}
|
|
1135
|
-
return undefined;
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
export function parseCommandInfo(args: string[]): CommandInfo {
|
|
1139
|
-
const commandTokens = extractCommandTokens(args);
|
|
1140
|
-
const command = commandTokens[0];
|
|
1141
|
-
return {
|
|
1142
|
-
command,
|
|
1143
|
-
subcommand: command && OPEN_COMMANDS.has(command) ? getOpenCommandTarget(commandTokens) : commandTokens[1],
|
|
1144
|
-
};
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
function findCommandStartIndex(args: string[]): number | undefined {
|
|
1148
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
1149
|
-
const token = args[index];
|
|
1150
|
-
if (token.startsWith("--session=")) {
|
|
1151
|
-
continue;
|
|
1152
|
-
}
|
|
1153
|
-
if (token.startsWith("-")) {
|
|
1154
|
-
const normalizedToken = token.split("=", 1)[0] ?? token;
|
|
1155
|
-
if (VALUE_FLAGS.has(normalizedToken) && !token.includes("=")) {
|
|
1156
|
-
index += 1;
|
|
1157
|
-
} else if (
|
|
1158
|
-
GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES.has(normalizedToken) &&
|
|
1159
|
-
!token.includes("=") &&
|
|
1160
|
-
isBooleanLiteral(args[index + 1])
|
|
1161
|
-
) {
|
|
1162
|
-
index += 1;
|
|
1163
|
-
}
|
|
1164
|
-
continue;
|
|
1165
|
-
}
|
|
1166
|
-
return index;
|
|
1167
|
-
}
|
|
1168
|
-
return undefined;
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
export function extractCommandTokens(args: string[]): string[] {
|
|
1172
|
-
const commandStartIndex = findCommandStartIndex(args);
|
|
1173
|
-
return commandStartIndex === undefined ? [] : args.slice(commandStartIndex);
|
|
1174
|
-
}
|