pi-agent-browser-native 0.3.0 → 0.5.0
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 +127 -0
- package/README.md +63 -20
- package/dist/extensions/agent-browser/index.js +787 -105
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +35 -3
- package/dist/extensions/agent-browser/lib/argv-grammar.js +44 -2
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +71 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +1 -1
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +35 -2
- package/dist/extensions/agent-browser/lib/input-modes/job.js +61 -4
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +2 -2
- package/dist/extensions/agent-browser/lib/input-modes/params.js +22 -23
- package/dist/extensions/agent-browser/lib/input-modes/script.js +462 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +51 -12
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +8 -0
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +3 -1
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +26 -36
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +2 -4
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +47 -29
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +50 -24
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +26 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +110 -30
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +26 -25
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +17 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +6 -4
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +129 -32
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +191 -55
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +57 -29
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +15 -11
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +36 -18
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +299 -0
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +32 -10
- package/dist/extensions/agent-browser/lib/playbook.js +18 -15
- package/dist/extensions/agent-browser/lib/process-environment.js +14 -0
- package/dist/extensions/agent-browser/lib/process-identity.js +4 -4
- package/dist/extensions/agent-browser/lib/process.js +131 -41
- package/dist/extensions/agent-browser/lib/recording-reservations.js +183 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +62 -5
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +62 -4
- package/dist/extensions/agent-browser/lib/results/categories.js +6 -1
- package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -5
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +85 -38
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +66 -11
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +18 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +7 -1
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +2 -1
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +26 -11
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +58 -13
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +1 -10
- package/dist/extensions/agent-browser/lib/results/presentation.js +6 -3
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +2 -0
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +51 -8
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +13 -7
- package/dist/extensions/agent-browser/lib/runtime.js +116 -39
- package/dist/extensions/agent-browser/lib/session-page-state.js +62 -10
- package/dist/extensions/agent-browser/lib/upstream-version.js +14 -0
- package/dist/extensions/agent-browser/script-worker.js +169 -0
- package/dist/scripts/agent-browser-target.mjs +3 -0
- package/docs/ARCHITECTURE.md +40 -21
- package/docs/COMMAND_REFERENCE.md +90 -35
- package/docs/RELEASE.md +3 -3
- package/docs/REQUIREMENTS.md +4 -2
- package/docs/SUPPORT_MATRIX.md +26 -19
- package/docs/TOOL_CONTRACT.md +93 -52
- package/package.json +3 -1
- package/platform-smoke.config.mjs +2 -2
- package/scripts/agent-browser-capability-baseline.mjs +24 -6
- package/scripts/agent-browser-target.mjs +3 -0
- package/scripts/build.mjs +41 -0
- package/scripts/doctor.mjs +7 -6
- package/scripts/platform-smoke/browser-dogfood-windows.ps1 +9 -3
- package/scripts/platform-smoke/targets.mjs +12 -6
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { rm } from "node:fs/promises";
|
|
2
|
+
import { getAgentBrowserSessionIdentityKey, VALUE_FLAGS } from "../../argv-grammar.js";
|
|
2
3
|
import { runAgentBrowserProcess } from "../../process.js";
|
|
3
4
|
import { buildAgentBrowserNextActions } from "../../results/action-recommendations.js";
|
|
4
5
|
import { parseAgentBrowserEnvelope } from "../../results/envelope.js";
|
|
@@ -7,7 +8,7 @@ import { getSessionPageStateKey, isAboutBlankUrl, normalizeComparableUrl, normal
|
|
|
7
8
|
import { isCloseCommand, isElectronPostCommandHealthCommand, isNavigationObservableCommandName, isRefGuardedCommand, isRefInvalidatingBatchCommand, isSessionTabPinningExcludedCommand, isSessionTabPostCommandCorrectionExcludedCommand, } from "../../command-taxonomy.js";
|
|
8
9
|
import { chooseOpenResultTabCorrection } from "../../runtime.js";
|
|
9
10
|
import { isRecord } from "../../parsing.js";
|
|
10
|
-
import { parseUserBatchStdin } from "../batch-stdin.js";
|
|
11
|
+
import { getUpstreamEffectiveBatchSteps, parseUserBatchStdin } from "../batch-stdin.js";
|
|
11
12
|
export const NAVIGATION_SUMMARY_EVAL = `({ title: document.title, url: location.href })`;
|
|
12
13
|
export function applyBrowserRunStatePatch(state, patch) {
|
|
13
14
|
if (!patch)
|
|
@@ -38,7 +39,7 @@ export function applyBrowserRunStatePatch(state, patch) {
|
|
|
38
39
|
export const getSessionContextKey = getSessionPageStateKey;
|
|
39
40
|
export function buildSessionDetailFields(sessionName, usedImplicitSession, namespace, managedSessionRestoreDisabled = false) {
|
|
40
41
|
return {
|
|
41
|
-
...(namespace ? { namespace } : {}),
|
|
42
|
+
...(namespace !== undefined ? { namespace } : {}),
|
|
42
43
|
...(sessionName
|
|
43
44
|
? {
|
|
44
45
|
sessionName,
|
|
@@ -213,10 +214,15 @@ export function getTraceOwnerGuardMessage(options) {
|
|
|
213
214
|
: `Wrapper believes tracing for session ${options.sessionName} is owned by ${activeOwner}; run ${activeOwner} stop instead of ${owner} stop.`;
|
|
214
215
|
}
|
|
215
216
|
export function updateTraceOwnerState(options) {
|
|
216
|
-
|
|
217
|
-
|
|
217
|
+
if (!options.sessionName || !options.succeeded)
|
|
218
|
+
return;
|
|
219
|
+
if (isCloseCommand(options.command)) {
|
|
220
|
+
options.traceOwners.delete(options.sessionName);
|
|
218
221
|
return;
|
|
219
222
|
}
|
|
223
|
+
const owner = getTraceOwner(options.command);
|
|
224
|
+
if (!owner)
|
|
225
|
+
return;
|
|
220
226
|
if (options.subcommand === "start") {
|
|
221
227
|
options.traceOwners.set(options.sessionName, owner);
|
|
222
228
|
}
|
|
@@ -248,8 +254,6 @@ export function extractNavigationSummaryFromData(data) {
|
|
|
248
254
|
export function shouldCaptureNavigationSummary(command, data) {
|
|
249
255
|
if (command === "eval")
|
|
250
256
|
return true;
|
|
251
|
-
if (isRecord(data) && typeof data.clicked === "string" && !data.clicked.startsWith("@") && !data.clicked.startsWith("ref=") && typeof data.href !== "string")
|
|
252
|
-
return false;
|
|
253
257
|
return (isNavigationObservableCommandName(command) &&
|
|
254
258
|
(!isRecord(data) || (typeof data.title !== "string" && typeof data.url !== "string")));
|
|
255
259
|
}
|
|
@@ -269,29 +273,42 @@ function extractBatchResultCommand(item) {
|
|
|
269
273
|
return Array.isArray(item.command) ? item.command.filter((token) => typeof token === "string") : [];
|
|
270
274
|
}
|
|
271
275
|
export function getStaleRefArgs(commandTokens, stdin) {
|
|
272
|
-
if (commandTokens[0] !== "batch"
|
|
273
|
-
return commandTokens;
|
|
274
|
-
}
|
|
275
|
-
const parsed = parseUserBatchStdin(stdin);
|
|
276
|
-
if (parsed.error || parsed.steps === undefined) {
|
|
276
|
+
if (commandTokens[0] !== "batch") {
|
|
277
277
|
return commandTokens;
|
|
278
278
|
}
|
|
279
|
-
|
|
279
|
+
const steps = getUpstreamEffectiveBatchSteps(commandTokens, stdin);
|
|
280
|
+
return steps.length > 0 ? steps.flatMap((step) => step) : commandTokens;
|
|
281
|
+
}
|
|
282
|
+
// Selector-carrying flags whose values remain page-scoped refs; values of other value-taking flags
|
|
283
|
+
// (--text, --baseline, --name, and similar) are literal text or paths that may merely look like refs,
|
|
284
|
+
// per upstream parsing. Boolean flags such as --new-tab or --full do not consume the following token,
|
|
285
|
+
// so a ref after them is a genuine positional selector and must stay guarded.
|
|
286
|
+
const SELECTOR_FLAG_TOKENS = new Set(["--selector", "-s"]);
|
|
287
|
+
const SHORT_VALUE_FLAG_TOKENS = new Set(["-b", "-o", "-t"]);
|
|
288
|
+
function isNonSelectorValueFlagToken(token) {
|
|
289
|
+
if (token === undefined || SELECTOR_FLAG_TOKENS.has(token))
|
|
290
|
+
return false;
|
|
291
|
+
return VALUE_FLAGS.has(token) || SHORT_VALUE_FLAG_TOKENS.has(token);
|
|
280
292
|
}
|
|
281
293
|
function collectRefsFromTokens(tokens) {
|
|
282
|
-
|
|
294
|
+
const refs = [];
|
|
295
|
+
for (const [index, token] of tokens.entries()) {
|
|
296
|
+
if (!/^@e\d+\b/.test(token))
|
|
297
|
+
continue;
|
|
298
|
+
if (isNonSelectorValueFlagToken(index > 0 ? tokens[index - 1] : undefined))
|
|
299
|
+
continue;
|
|
300
|
+
refs.push(token.slice(1));
|
|
301
|
+
}
|
|
302
|
+
return refs;
|
|
283
303
|
}
|
|
284
304
|
export function getGuardedRefUsage(commandTokens, stdin, options = {}) {
|
|
285
305
|
const collectFromStep = (step) => isRefGuardedCommand(step[0]) ? collectRefsFromTokens(step) : [];
|
|
286
|
-
if (commandTokens[0] !== "batch"
|
|
287
|
-
return collectFromStep(commandTokens);
|
|
288
|
-
}
|
|
289
|
-
const parsed = parseUserBatchStdin(stdin);
|
|
290
|
-
if (parsed.error || parsed.steps === undefined) {
|
|
306
|
+
if (commandTokens[0] !== "batch") {
|
|
291
307
|
return collectFromStep(commandTokens);
|
|
292
308
|
}
|
|
309
|
+
const steps = getUpstreamEffectiveBatchSteps(commandTokens, stdin);
|
|
293
310
|
const refsBeforeInBatchSnapshot = [];
|
|
294
|
-
for (const step of
|
|
311
|
+
for (const step of steps) {
|
|
295
312
|
if (!options.includeRefsAfterBatchSnapshot && (step[0] ?? "") === "snapshot")
|
|
296
313
|
break;
|
|
297
314
|
refsBeforeInBatchSnapshot.push(...collectFromStep(step));
|
|
@@ -318,13 +335,10 @@ function isSafeSameSnapshotFormBatchStep(step, refSnapshot) {
|
|
|
318
335
|
return false;
|
|
319
336
|
}
|
|
320
337
|
function getBatchRefInvalidationMessage(commandTokens, stdin, refSnapshot) {
|
|
321
|
-
if (commandTokens[0] !== "batch"
|
|
322
|
-
return undefined;
|
|
323
|
-
const parsed = parseUserBatchStdin(stdin);
|
|
324
|
-
if (parsed.error || parsed.steps === undefined)
|
|
338
|
+
if (commandTokens[0] !== "batch")
|
|
325
339
|
return undefined;
|
|
326
340
|
let priorStepInvalidatesRefs = false;
|
|
327
|
-
for (const step of
|
|
341
|
+
for (const step of getUpstreamEffectiveBatchSteps(commandTokens, stdin)) {
|
|
328
342
|
if ((step[0] ?? "") === "snapshot") {
|
|
329
343
|
priorStepInvalidatesRefs = false;
|
|
330
344
|
}
|
|
@@ -332,7 +346,7 @@ function getBatchRefInvalidationMessage(commandTokens, stdin, refSnapshot) {
|
|
|
332
346
|
if (refIds.length > 0 && isRefGuardedCommand(step[0]) && priorStepInvalidatesRefs) {
|
|
333
347
|
return `Batch step ${step[0]} uses page-scoped ref ${refIds.map((refId) => `@${refId}`).join(", ")} after an earlier batch step can navigate or mutate the page. Split the batch, run snapshot -i after the page-changing step, then retry with current refs.`;
|
|
334
348
|
}
|
|
335
|
-
if (isRefInvalidatingBatchCommand(step
|
|
349
|
+
if (isRefInvalidatingBatchCommand(step) && !isSafeSameSnapshotFormBatchStep(step, refSnapshot)) {
|
|
336
350
|
priorStepInvalidatesRefs = true;
|
|
337
351
|
}
|
|
338
352
|
}
|
|
@@ -355,7 +369,9 @@ export function buildStaleRefPreflight(options) {
|
|
|
355
369
|
return undefined;
|
|
356
370
|
if (options.refSnapshotInvalidation) {
|
|
357
371
|
return {
|
|
358
|
-
message:
|
|
372
|
+
message: options.refSnapshotInvalidation.reason === "page-transition"
|
|
373
|
+
? `Ref ${usedRefIds.map((refId) => `@${refId}`).join(", ")} cannot be used yet. ${options.refSnapshotInvalidation.summary}`
|
|
374
|
+
: `Ref ${usedRefIds.map((refId) => `@${refId}`).join(", ")} cannot be used because the latest snapshot for this session reported No active page. Run snapshot -i successfully before using page-scoped refs.`,
|
|
359
375
|
refIds: usedRefIds,
|
|
360
376
|
snapshotInvalidation: options.refSnapshotInvalidation,
|
|
361
377
|
};
|
|
@@ -401,11 +417,22 @@ export function shouldPinSessionTabForCommand(options) {
|
|
|
401
417
|
}
|
|
402
418
|
export function buildPinnedBatchPlan(options) {
|
|
403
419
|
if (options.command === "batch") {
|
|
420
|
+
const tabSelectionStep = ["tab", options.selectedTab];
|
|
421
|
+
// Upstream executes raw argument steps exclusively when any exist, so the
|
|
422
|
+
// pinned rewrite must dispatch those same steps rather than resurrecting
|
|
423
|
+
// caller stdin the guard never scanned.
|
|
424
|
+
const argumentSteps = getUpstreamEffectiveBatchSteps(options.commandTokens, undefined);
|
|
425
|
+
if (argumentSteps.length > 0) {
|
|
426
|
+
return {
|
|
427
|
+
includeNavigationSummary: false,
|
|
428
|
+
steps: [tabSelectionStep, ...argumentSteps],
|
|
429
|
+
unwrapMode: "user-batch",
|
|
430
|
+
};
|
|
431
|
+
}
|
|
404
432
|
const parsed = parseUserBatchStdin(options.stdin);
|
|
405
433
|
if (parsed.error) {
|
|
406
434
|
return { error: parsed.error };
|
|
407
435
|
}
|
|
408
|
-
const tabSelectionStep = ["tab", options.selectedTab];
|
|
409
436
|
return {
|
|
410
437
|
includeNavigationSummary: false,
|
|
411
438
|
steps: [tabSelectionStep, ...(parsed.steps ?? [])],
|
|
@@ -613,10 +640,11 @@ export function electronTargetLabel(target) {
|
|
|
613
640
|
export function getActiveElectronRecords(records) {
|
|
614
641
|
return [...records.values()].filter((record) => record.cleanupState === "active" || record.cleanupState === "dead" || record.cleanupState === "partial" || record.cleanupState === "failed");
|
|
615
642
|
}
|
|
616
|
-
export function findElectronLaunchRecordForSession(sessionName, records) {
|
|
643
|
+
export function findElectronLaunchRecordForSession(sessionName, records, namespace) {
|
|
617
644
|
if (!sessionName)
|
|
618
645
|
return undefined;
|
|
619
|
-
|
|
646
|
+
const sessionKey = getAgentBrowserSessionIdentityKey(sessionName, namespace);
|
|
647
|
+
return getActiveElectronRecords(records).find((record) => record.sessionName && getAgentBrowserSessionIdentityKey(record.sessionName, record.namespace) === sessionKey);
|
|
620
648
|
}
|
|
621
649
|
function buildElectronReattachNextAction(record, liveTarget) {
|
|
622
650
|
const endpoint = liveTarget?.webSocketDebuggerUrl ?? record.webSocketDebuggerUrl ?? String(record.port);
|
|
@@ -114,6 +114,7 @@ function isElectronLaunchRecord(value) {
|
|
|
114
114
|
return false;
|
|
115
115
|
return value.version === 1 &&
|
|
116
116
|
value.launchedByWrapper === true &&
|
|
117
|
+
(value.namespace === undefined || typeof value.namespace === "string") &&
|
|
117
118
|
typeof value.launchId === "string" &&
|
|
118
119
|
typeof value.appName === "string" &&
|
|
119
120
|
typeof value.executablePath === "string" &&
|
|
@@ -133,13 +134,14 @@ export function restoreElectronLaunchRecordsFromBranch(branch) {
|
|
|
133
134
|
const electron = isRecord(details?.electron) ? details.electron : undefined;
|
|
134
135
|
if (!electron)
|
|
135
136
|
continue;
|
|
137
|
+
const namespace = typeof details?.namespace === "string" ? details.namespace : undefined;
|
|
136
138
|
const launch = isElectronLaunchRecord(electron.launch) ? electron.launch : undefined;
|
|
137
139
|
if (launch)
|
|
138
|
-
records.set(launch.launchId, launch);
|
|
140
|
+
records.set(launch.launchId, { ...launch, namespace: launch.namespace ?? namespace });
|
|
139
141
|
const cleanupRecords = isRecord(electron.cleanup) && Array.isArray(electron.cleanup.records) ? electron.cleanup.records : [];
|
|
140
142
|
for (const cleanupRecord of cleanupRecords) {
|
|
141
143
|
if (isElectronLaunchRecord(cleanupRecord))
|
|
142
|
-
records.set(cleanupRecord.launchId, cleanupRecord);
|
|
144
|
+
records.set(cleanupRecord.launchId, { ...cleanupRecord, namespace: cleanupRecord.namespace ?? namespace });
|
|
143
145
|
}
|
|
144
146
|
}
|
|
145
147
|
return records;
|
|
@@ -428,7 +430,7 @@ async function withOwnedElectronManagedSessionPolicy(options, run) {
|
|
|
428
430
|
}
|
|
429
431
|
async function collectOwnedElectronManagedSessionTarget(options) {
|
|
430
432
|
try {
|
|
431
|
-
|
|
433
|
+
const target = await withOwnedElectronManagedSessionPolicy({ ...options, args: ["get", "url"] }, async () => await collectElectronManagedSessionTarget({
|
|
432
434
|
allowManagedSessionTarget: true,
|
|
433
435
|
cwd: options.cwd,
|
|
434
436
|
namespace: options.namespace,
|
|
@@ -436,9 +438,10 @@ async function collectOwnedElectronManagedSessionTarget(options) {
|
|
|
436
438
|
signal: options.signal,
|
|
437
439
|
timeoutMs: options.timeoutMs,
|
|
438
440
|
})) ?? { sessionName: options.sessionName };
|
|
441
|
+
return { ...target, namespace: options.namespace };
|
|
439
442
|
}
|
|
440
443
|
catch (error) {
|
|
441
|
-
return { error: error instanceof Error ? error.message : String(error), sessionName: options.sessionName };
|
|
444
|
+
return { error: error instanceof Error ? error.message : String(error), namespace: options.namespace, sessionName: options.sessionName };
|
|
442
445
|
}
|
|
443
446
|
}
|
|
444
447
|
async function runElectronProbeCommandData(options) {
|
|
@@ -600,10 +603,10 @@ function buildElectronProbeResult(options) {
|
|
|
600
603
|
export async function cleanupTrackedElectronHostLaunches(options) {
|
|
601
604
|
const results = [];
|
|
602
605
|
for (const record of options.records) {
|
|
603
|
-
const sessionKey = getSessionPageStateKey(record.sessionName) ?? record.sessionName;
|
|
606
|
+
const sessionKey = getSessionPageStateKey(record.sessionName, record.namespace) ?? record.sessionName;
|
|
604
607
|
const managedSessionOwner = sessionKey ? options.ownedManagedSessions.get(sessionKey) : undefined;
|
|
605
608
|
const managedSessionCloseError = record.sessionName
|
|
606
|
-
? await closeManagedSession({ cwd: options.cwd, headedManagedAutosaveInterval: managedSessionOwner?.headedManagedAutosaveInterval, preserveAttachedBrowserSession: options.attachedSessionKeys.has(sessionKey ?? record.sessionName), restoreState: options.managedSessionRestoreState, sessionName: record.sessionName, timeoutMs: options.timeoutMs })
|
|
609
|
+
? await closeManagedSession({ cwd: options.cwd, headedManagedAutosaveInterval: managedSessionOwner?.headedManagedAutosaveInterval, namespace: record.namespace, preserveAttachedBrowserSession: options.attachedSessionKeys.has(sessionKey ?? record.sessionName), restoreState: options.managedSessionRestoreState, sessionName: record.sessionName, timeoutMs: options.timeoutMs })
|
|
607
610
|
: undefined;
|
|
608
611
|
const managedSessionStep = record.sessionName
|
|
609
612
|
? managedSessionCloseError
|
|
@@ -648,7 +651,7 @@ export async function cleanupActiveElectronHostLaunches(options) {
|
|
|
648
651
|
export async function handleElectronHostInput(options) {
|
|
649
652
|
const currentSessionKey = getSessionPageStateKey(options.managedSessionName, options.managedSessionNamespace) ?? options.managedSessionName;
|
|
650
653
|
const preserveAttachedBrowserSession = options.attachedSessionKeys.has(currentSessionKey)
|
|
651
|
-
|| [...options.electronLaunchRecords.values()].some((record) => record.sessionName !== undefined && options.attachedSessionKeys.has(getSessionPageStateKey(record.sessionName) ?? record.sessionName));
|
|
654
|
+
|| [...options.electronLaunchRecords.values()].some((record) => record.sessionName !== undefined && options.attachedSessionKeys.has(getSessionPageStateKey(record.sessionName, record.namespace) ?? record.sessionName));
|
|
652
655
|
return await withAttachedBrowserSessionContext(preserveAttachedBrowserSession, () => handleElectronHostInputInContext(options));
|
|
653
656
|
}
|
|
654
657
|
async function handleElectronHostInputInContext(options) {
|
|
@@ -671,11 +674,12 @@ async function handleElectronHostInputInContext(options) {
|
|
|
671
674
|
const managedSessions = await Promise.all(records
|
|
672
675
|
.filter((record) => typeof record.sessionName === "string")
|
|
673
676
|
.map((record) => {
|
|
674
|
-
const sessionKey = getSessionPageStateKey(record.sessionName) ?? record.sessionName;
|
|
677
|
+
const sessionKey = getSessionPageStateKey(record.sessionName, record.namespace) ?? record.sessionName;
|
|
675
678
|
return collectOwnedElectronManagedSessionTarget({
|
|
676
679
|
cwd,
|
|
677
680
|
headedManagedAutosaveDisabled: ownedManagedSessions.get(sessionKey)?.headedManagedAutosaveDisabled,
|
|
678
681
|
headedManagedAutosaveInterval: ownedManagedSessions.get(sessionKey)?.headedManagedAutosaveInterval,
|
|
682
|
+
namespace: record.namespace,
|
|
679
683
|
restoreState: managedSessionRestoreState,
|
|
680
684
|
sessionName: record.sessionName,
|
|
681
685
|
signal,
|
|
@@ -684,7 +688,7 @@ async function handleElectronHostInputInContext(options) {
|
|
|
684
688
|
}));
|
|
685
689
|
const mismatches = managedSessions
|
|
686
690
|
.map((managedSession) => {
|
|
687
|
-
const record = records.find((candidate) => candidate.sessionName === managedSession.sessionName);
|
|
691
|
+
const record = records.find((candidate) => candidate.sessionName === managedSession.sessionName && candidate.namespace === managedSession.namespace);
|
|
688
692
|
const status = record ? statuses.find((candidate) => candidate.launchId === record.launchId) : undefined;
|
|
689
693
|
return record && status ? buildElectronSessionMismatch({ managedSession, record, statusTargets: status.targets }) : undefined;
|
|
690
694
|
})
|
|
@@ -700,7 +704,7 @@ async function handleElectronHostInputInContext(options) {
|
|
|
700
704
|
if (compiledElectron?.action === "probe") {
|
|
701
705
|
const launchRecord = compiledElectron.launchId
|
|
702
706
|
? electronLaunchRecords.get(compiledElectron.launchId)
|
|
703
|
-
: findElectronLaunchRecordForSession(managedSessionName, electronLaunchRecords);
|
|
707
|
+
: findElectronLaunchRecordForSession(managedSessionName, electronLaunchRecords, managedSessionNamespace);
|
|
704
708
|
if (compiledElectron.launchId && !launchRecord) {
|
|
705
709
|
return buildElectronHostFailureResult({
|
|
706
710
|
compiledElectron: redactedCompiledElectron ?? compiledElectron,
|
|
@@ -732,7 +736,7 @@ async function handleElectronHostInputInContext(options) {
|
|
|
732
736
|
}
|
|
733
737
|
try {
|
|
734
738
|
const status = launchRecord ? await inspectElectronLaunchStatus(launchRecord) : undefined;
|
|
735
|
-
const probeNamespace = compiledElectron.launchId ?
|
|
739
|
+
const probeNamespace = compiledElectron.launchId ? launchRecord?.namespace : managedSessionNamespace;
|
|
736
740
|
const pageStateKey = getSessionPageStateKey(probeSessionName, probeNamespace) ?? probeSessionName;
|
|
737
741
|
const currentPageState = sessionPageState.get(pageStateKey);
|
|
738
742
|
const fileAccessError = getManagedSessionStateAccessValidationError({
|
|
@@ -5,6 +5,7 @@ import { compileAgentBrowserElectron } from "../input-modes/electron.js";
|
|
|
5
5
|
import { compileAgentBrowserJob, compileAgentBrowserQaPreset } from "../input-modes/job.js";
|
|
6
6
|
import { compileAgentBrowserNetworkSourceLookup, compileAgentBrowserSourceLookup, redactNetworkSourceLookupArgs, redactNetworkSourceLookupUrl } from "../input-modes/lookups.js";
|
|
7
7
|
import { compileAgentBrowserSemanticAction } from "../input-modes/semantic-action.js";
|
|
8
|
+
import { AGENT_BROWSER_SCRIPT_MAX_TIMEOUT_MS, compileAgentBrowserScript } from "../input-modes/script.js";
|
|
8
9
|
function redactCompiledElectron(compiled) {
|
|
9
10
|
if (!compiled)
|
|
10
11
|
return undefined;
|
|
@@ -71,6 +72,7 @@ export function resolveAgentBrowserInput(options) {
|
|
|
71
72
|
const sourceLookupResult = params.sourceLookup === undefined ? {} : compileAgentBrowserSourceLookup(params.sourceLookup);
|
|
72
73
|
const networkSourceLookupResult = params.networkSourceLookup === undefined ? {} : compileAgentBrowserNetworkSourceLookup(params.networkSourceLookup);
|
|
73
74
|
const electronResult = params.electron === undefined ? {} : compileAgentBrowserElectron(params.electron);
|
|
75
|
+
const scriptResult = params.script === undefined ? {} : compileAgentBrowserScript(params.script);
|
|
74
76
|
const hasExplicitArgs = Array.isArray(params.args);
|
|
75
77
|
const explicitInputModes = [
|
|
76
78
|
hasExplicitArgs,
|
|
@@ -80,19 +82,21 @@ export function resolveAgentBrowserInput(options) {
|
|
|
80
82
|
Boolean(sourceLookupResult.compiled),
|
|
81
83
|
Boolean(networkSourceLookupResult.compiled),
|
|
82
84
|
Boolean(electronResult.compiled),
|
|
85
|
+
Boolean(scriptResult.compiled),
|
|
83
86
|
].filter(Boolean).length;
|
|
84
87
|
const inputModeError = explicitInputModes !== 1
|
|
85
|
-
? "Provide exactly one of args, semanticAction, job, qa, sourceLookup, networkSourceLookup, or electron."
|
|
88
|
+
? "Provide exactly one of script, args, semanticAction, job, qa, sourceLookup, networkSourceLookup, or electron."
|
|
86
89
|
: undefined;
|
|
87
90
|
const compiledSemanticAction = semanticActionResult.compiled;
|
|
88
91
|
const compiledQaPreset = qaResult.compiled;
|
|
89
92
|
const compiledSourceLookup = sourceLookupResult.compiled;
|
|
90
93
|
const compiledNetworkSourceLookup = networkSourceLookupResult.compiled;
|
|
91
94
|
const compiledElectron = electronResult.compiled;
|
|
95
|
+
const compiledScript = scriptResult.compiled;
|
|
92
96
|
const compiledJob = jobResult.compiled ?? compiledQaPreset;
|
|
93
97
|
const compiledGeneratedBatch = compiledNetworkSourceLookup ?? compiledSourceLookup ?? compiledJob;
|
|
94
98
|
const normalizedExplicitArgs = normalizeExplicitEvalStdinArgs(params.args ?? [], params.stdin);
|
|
95
|
-
const toolArgs = compiledElectron ? [] : compiledSemanticAction?.args ?? compiledGeneratedBatch?.args ?? normalizedExplicitArgs.args;
|
|
99
|
+
const toolArgs = compiledElectron || compiledScript ? [] : compiledSemanticAction?.args ?? compiledGeneratedBatch?.args ?? normalizedExplicitArgs.args;
|
|
96
100
|
const toolStdin = compiledGeneratedBatch?.stdin ?? normalizedExplicitArgs.stdin;
|
|
97
101
|
const redactedArgs = redactInvocationArgs(toolArgs);
|
|
98
102
|
const generatedStdinError = params.stdin !== undefined
|
|
@@ -100,7 +104,9 @@ export function resolveAgentBrowserInput(options) {
|
|
|
100
104
|
? "Do not provide stdin with job, qa, sourceLookup, or networkSourceLookup; those modes generate their own batch stdin."
|
|
101
105
|
: compiledElectron
|
|
102
106
|
? "Do not provide stdin with electron; electron mode is host-only or manages its own input."
|
|
103
|
-
:
|
|
107
|
+
: compiledScript
|
|
108
|
+
? "Do not provide stdin with script; browser call stdin belongs inside browser({ args, stdin })."
|
|
109
|
+
: undefined
|
|
104
110
|
: undefined;
|
|
105
111
|
const outputPathError = params.outputPath !== undefined && (typeof params.outputPath !== "string" || params.outputPath.trim().length === 0)
|
|
106
112
|
? "outputPath must be a non-empty string when provided."
|
|
@@ -109,7 +115,12 @@ export function resolveAgentBrowserInput(options) {
|
|
|
109
115
|
? "timeoutMs must be a positive integer when provided."
|
|
110
116
|
: compiledElectron && params.timeoutMs !== undefined
|
|
111
117
|
? "Use electron.timeoutMs for electron actions; top-level timeoutMs applies only to browser CLI subprocess calls."
|
|
112
|
-
: undefined
|
|
118
|
+
: compiledScript && params.timeoutMs !== undefined && params.timeoutMs > AGENT_BROWSER_SCRIPT_MAX_TIMEOUT_MS
|
|
119
|
+
? `script timeoutMs must be ${AGENT_BROWSER_SCRIPT_MAX_TIMEOUT_MS} or less.`
|
|
120
|
+
: undefined;
|
|
121
|
+
const scriptSessionModeError = compiledScript && params.sessionMode !== undefined
|
|
122
|
+
? "Do not provide sessionMode with script; script always uses its own isolated session."
|
|
123
|
+
: undefined;
|
|
113
124
|
const attachedQaSessionError = compiledQaPreset?.checks.attached
|
|
114
125
|
? params.sessionMode === "fresh"
|
|
115
126
|
? "qa.attached cannot be used with sessionMode=fresh; attach or launch a session first, then run qa.attached with the current session."
|
|
@@ -123,31 +134,35 @@ export function resolveAgentBrowserInput(options) {
|
|
|
123
134
|
?? sourceLookupResult.error
|
|
124
135
|
?? networkSourceLookupResult.error
|
|
125
136
|
?? electronResult.error
|
|
137
|
+
?? scriptResult.error
|
|
126
138
|
?? inputModeError
|
|
127
139
|
?? generatedStdinError
|
|
128
140
|
?? outputPathError
|
|
129
141
|
?? timeoutMsError
|
|
142
|
+
?? scriptSessionModeError
|
|
130
143
|
?? attachedQaSessionError
|
|
131
|
-
?? (compiledElectron ? undefined : validateToolArgs(toolArgs) ?? getBatchPreflightValidationError(toolArgs, toolStdin));
|
|
144
|
+
?? (compiledElectron || compiledScript ? undefined : validateToolArgs(toolArgs) ?? getBatchPreflightValidationError(toolArgs, toolStdin));
|
|
132
145
|
const redactedCompiledJob = redactCompiledJob(compiledJob);
|
|
133
146
|
const redactedCompiledSemanticAction = compiledSemanticAction
|
|
134
147
|
? { ...compiledSemanticAction, args: redactInvocationArgs(compiledSemanticAction.args) }
|
|
135
148
|
: undefined;
|
|
136
149
|
const attemptedKind = compiledElectron
|
|
137
150
|
? "electron"
|
|
138
|
-
:
|
|
139
|
-
? "
|
|
140
|
-
:
|
|
141
|
-
? "
|
|
142
|
-
:
|
|
143
|
-
? "
|
|
144
|
-
:
|
|
145
|
-
? "
|
|
146
|
-
:
|
|
147
|
-
? "
|
|
148
|
-
:
|
|
149
|
-
? "
|
|
150
|
-
:
|
|
151
|
+
: compiledScript
|
|
152
|
+
? "script"
|
|
153
|
+
: compiledNetworkSourceLookup
|
|
154
|
+
? "networkSourceLookup"
|
|
155
|
+
: compiledSourceLookup
|
|
156
|
+
? "sourceLookup"
|
|
157
|
+
: compiledQaPreset
|
|
158
|
+
? "qa"
|
|
159
|
+
: jobResult.compiled
|
|
160
|
+
? "job"
|
|
161
|
+
: compiledSemanticAction
|
|
162
|
+
? "semanticAction"
|
|
163
|
+
: hasExplicitArgs
|
|
164
|
+
? "args"
|
|
165
|
+
: undefined;
|
|
151
166
|
const redactedCompiledElectron = redactCompiledElectron(compiledElectron);
|
|
152
167
|
const redactedCompiledNetworkSourceLookup = redactCompiledNetworkSourceLookup(compiledNetworkSourceLookup);
|
|
153
168
|
const redactedCompiledQaPreset = compiledQaPreset && redactedCompiledJob ? { ...redactedCompiledJob, checks: compiledQaPreset.checks } : undefined;
|
|
@@ -178,6 +193,9 @@ export function resolveAgentBrowserInput(options) {
|
|
|
178
193
|
if (compiledElectron && redactedCompiledElectron) {
|
|
179
194
|
return { ...resolvedBase, compiledElectron, kind: "electron", redactedCompiledElectron, status: "valid" };
|
|
180
195
|
}
|
|
196
|
+
if (compiledScript) {
|
|
197
|
+
return { ...resolvedBase, compiledScript, kind: "script", status: "valid" };
|
|
198
|
+
}
|
|
181
199
|
if (compiledNetworkSourceLookup && redactedCompiledNetworkSourceLookup) {
|
|
182
200
|
return {
|
|
183
201
|
...resolvedBase,
|