pi-agent-browser-native 0.2.77 → 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 +154 -0
- package/README.md +69 -35
- package/dist/extensions/agent-browser/index.js +911 -121
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +35 -8
- package/dist/extensions/agent-browser/lib/argv-grammar.js +48 -7
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +71 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +1 -6
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +35 -8
- package/dist/extensions/agent-browser/lib/config-policy.js +0 -5
- package/dist/extensions/agent-browser/lib/config.js +0 -6
- package/dist/extensions/agent-browser/lib/electron/cdp.js +6 -7
- package/dist/extensions/agent-browser/lib/electron/cleanup.js +0 -7
- package/dist/extensions/agent-browser/lib/electron/discovery.js +0 -7
- package/dist/extensions/agent-browser/lib/electron/launch.js +0 -7
- package/dist/extensions/agent-browser/lib/input-modes/electron.js +0 -5
- package/dist/extensions/agent-browser/lib/input-modes/job.js +61 -9
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +2 -7
- package/dist/extensions/agent-browser/lib/input-modes/params.js +23 -29
- package/dist/extensions/agent-browser/lib/input-modes/script.js +462 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +51 -17
- package/dist/extensions/agent-browser/lib/input-modes/shared.js +0 -5
- package/dist/extensions/agent-browser/lib/input-modes/types.js +0 -5
- package/dist/extensions/agent-browser/lib/json-schema.js +0 -5
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +12 -5
- package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -2
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +3 -4
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +55 -48
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +2 -9
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +48 -36
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +50 -29
- package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -5
- 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 +27 -26
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +58 -33
- 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 +22 -14
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +148 -34
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +225 -61
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +74 -30
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +47 -26
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +40 -19
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +42 -1
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +299 -0
- package/dist/extensions/agent-browser/lib/parsing.js +0 -7
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +36 -11
- package/dist/extensions/agent-browser/lib/playbook.js +20 -24
- package/dist/extensions/agent-browser/lib/process-environment.js +14 -0
- package/dist/extensions/agent-browser/lib/process-identity.js +4 -9
- package/dist/extensions/agent-browser/lib/process.js +131 -48
- package/dist/extensions/agent-browser/lib/prompt-policy.js +171 -27
- package/dist/extensions/agent-browser/lib/recording-reservations.js +183 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +63 -13
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +68 -11
- package/dist/extensions/agent-browser/lib/results/categories.js +7 -9
- package/dist/extensions/agent-browser/lib/results/confirmation.js +0 -7
- package/dist/extensions/agent-browser/lib/results/contracts.js +0 -7
- package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +0 -7
- package/dist/extensions/agent-browser/lib/results/envelope.js +0 -7
- package/dist/extensions/agent-browser/lib/results/network.js +0 -7
- package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -35
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +85 -44
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +66 -11
- package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +18 -5
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +7 -6
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +2 -1
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +26 -16
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +58 -13
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +1 -15
- package/dist/extensions/agent-browser/lib/results/presentation/skills.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation.js +6 -8
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +2 -7
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +0 -7
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +51 -15
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +13 -14
- package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot.js +0 -7
- package/dist/extensions/agent-browser/lib/results/text.js +0 -7
- package/dist/extensions/agent-browser/lib/runtime.js +119 -52
- package/dist/extensions/agent-browser/lib/session-page-state.js +62 -17
- package/dist/extensions/agent-browser/lib/string-enum-schema.js +0 -5
- package/dist/extensions/agent-browser/lib/temp.js +0 -7
- package/dist/extensions/agent-browser/lib/upstream-version.js +14 -0
- package/dist/extensions/agent-browser/lib/web-search.js +0 -5
- package/dist/extensions/agent-browser/script-worker.js +169 -0
- package/dist/scripts/agent-browser-target.mjs +3 -0
- package/docs/ARCHITECTURE.md +50 -31
- package/docs/COMMAND_REFERENCE.md +97 -41
- package/docs/ELECTRON.md +3 -19
- package/docs/RELEASE.md +8 -20
- package/docs/REQUIREMENTS.md +8 -6
- package/docs/SUPPORT_MATRIX.md +32 -25
- package/docs/TOOL_CONTRACT.md +107 -64
- package/docs/platform-smoke.md +1 -1
- package/package.json +7 -6
- 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 +8 -7
- package/scripts/platform-smoke/browser-dogfood-windows.ps1 +9 -3
- package/scripts/platform-smoke/targets.mjs +12 -6
- package/dist/extensions/agent-browser/lib/electron/text.js +0 -13
- package/dist/extensions/agent-browser/lib/input-modes.js +0 -10
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +0 -5
- package/dist/extensions/agent-browser/lib/results/artifact-state.js +0 -13
- package/dist/extensions/agent-browser/lib/results.js +0 -14
|
@@ -1,12 +1,14 @@
|
|
|
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
|
-
import { buildAgentBrowserNextActions
|
|
4
|
+
import { buildAgentBrowserNextActions } from "../../results/action-recommendations.js";
|
|
5
|
+
import { parseAgentBrowserEnvelope } from "../../results/envelope.js";
|
|
4
6
|
import { buildNextToolAction, withOptionalNamespaceArgs, withOptionalSessionArgs } from "../../results/next-actions.js";
|
|
5
7
|
import { getSessionPageStateKey, isAboutBlankUrl, normalizeComparableUrl, normalizeSessionTabTarget, targetsMatch, } from "../../session-page-state.js";
|
|
6
8
|
import { isCloseCommand, isElectronPostCommandHealthCommand, isNavigationObservableCommandName, isRefGuardedCommand, isRefInvalidatingBatchCommand, isSessionTabPinningExcludedCommand, isSessionTabPostCommandCorrectionExcludedCommand, } from "../../command-taxonomy.js";
|
|
7
9
|
import { chooseOpenResultTabCorrection } from "../../runtime.js";
|
|
8
10
|
import { isRecord } from "../../parsing.js";
|
|
9
|
-
import { parseUserBatchStdin } from "../batch-stdin.js";
|
|
11
|
+
import { getUpstreamEffectiveBatchSteps, parseUserBatchStdin } from "../batch-stdin.js";
|
|
10
12
|
export const NAVIGATION_SUMMARY_EVAL = `({ title: document.title, url: location.href })`;
|
|
11
13
|
export function applyBrowserRunStatePatch(state, patch) {
|
|
12
14
|
if (!patch)
|
|
@@ -21,6 +23,10 @@ export function applyBrowserRunStatePatch(state, patch) {
|
|
|
21
23
|
state.managedSessionActive = patch.managedSessionActive;
|
|
22
24
|
if ("managedSessionCompatibilityWorkaround" in patch)
|
|
23
25
|
state.managedSessionCompatibilityWorkaround = patch.managedSessionCompatibilityWorkaround;
|
|
26
|
+
if (patch.managedSessionHeadedAutosaveDisabled !== undefined)
|
|
27
|
+
state.managedSessionHeadedAutosaveDisabled = patch.managedSessionHeadedAutosaveDisabled;
|
|
28
|
+
if ("managedSessionHeadedAutosaveInterval" in patch)
|
|
29
|
+
state.managedSessionHeadedAutosaveInterval = patch.managedSessionHeadedAutosaveInterval;
|
|
24
30
|
if (patch.managedSessionCwd !== undefined)
|
|
25
31
|
state.managedSessionCwd = patch.managedSessionCwd;
|
|
26
32
|
if (patch.managedSessionName !== undefined)
|
|
@@ -33,7 +39,7 @@ export function applyBrowserRunStatePatch(state, patch) {
|
|
|
33
39
|
export const getSessionContextKey = getSessionPageStateKey;
|
|
34
40
|
export function buildSessionDetailFields(sessionName, usedImplicitSession, namespace, managedSessionRestoreDisabled = false) {
|
|
35
41
|
return {
|
|
36
|
-
...(namespace ? { namespace } : {}),
|
|
42
|
+
...(namespace !== undefined ? { namespace } : {}),
|
|
37
43
|
...(sessionName
|
|
38
44
|
? {
|
|
39
45
|
sessionName,
|
|
@@ -136,6 +142,12 @@ function formatManagedSessionOutcomeRecoveryGuidance(outcome) {
|
|
|
136
142
|
export function formatManagedSessionOutcomeText(outcome) {
|
|
137
143
|
if (!outcome)
|
|
138
144
|
return undefined;
|
|
145
|
+
if (outcome.replacedSessionClosed === false) {
|
|
146
|
+
const cleanupWarning = "Cleanup warning: Automatic close of the previous wrapper-managed session failed, so it remains wrapper-owned. Use details.managedSessionOutcome for its exact identity and close it explicitly when safe.";
|
|
147
|
+
return outcome.succeeded
|
|
148
|
+
? ["Managed session outcome: The fresh browser became current.", cleanupWarning].join("\n")
|
|
149
|
+
: [formatManagedSessionOutcomeHeadline(outcome), formatManagedSessionOutcomeRecoveryGuidance(outcome), cleanupWarning].join("\n");
|
|
150
|
+
}
|
|
139
151
|
if (outcome.status === "closed" && outcome.succeeded) {
|
|
140
152
|
return [
|
|
141
153
|
"Managed session outcome: The current wrapper-managed browser session was closed.",
|
|
@@ -202,10 +214,15 @@ export function getTraceOwnerGuardMessage(options) {
|
|
|
202
214
|
: `Wrapper believes tracing for session ${options.sessionName} is owned by ${activeOwner}; run ${activeOwner} stop instead of ${owner} stop.`;
|
|
203
215
|
}
|
|
204
216
|
export function updateTraceOwnerState(options) {
|
|
205
|
-
|
|
206
|
-
|
|
217
|
+
if (!options.sessionName || !options.succeeded)
|
|
218
|
+
return;
|
|
219
|
+
if (isCloseCommand(options.command)) {
|
|
220
|
+
options.traceOwners.delete(options.sessionName);
|
|
207
221
|
return;
|
|
208
222
|
}
|
|
223
|
+
const owner = getTraceOwner(options.command);
|
|
224
|
+
if (!owner)
|
|
225
|
+
return;
|
|
209
226
|
if (options.subcommand === "start") {
|
|
210
227
|
options.traceOwners.set(options.sessionName, owner);
|
|
211
228
|
}
|
|
@@ -237,8 +254,6 @@ export function extractNavigationSummaryFromData(data) {
|
|
|
237
254
|
export function shouldCaptureNavigationSummary(command, data) {
|
|
238
255
|
if (command === "eval")
|
|
239
256
|
return true;
|
|
240
|
-
if (isRecord(data) && typeof data.clicked === "string" && !data.clicked.startsWith("@") && !data.clicked.startsWith("ref=") && typeof data.href !== "string")
|
|
241
|
-
return false;
|
|
242
257
|
return (isNavigationObservableCommandName(command) &&
|
|
243
258
|
(!isRecord(data) || (typeof data.title !== "string" && typeof data.url !== "string")));
|
|
244
259
|
}
|
|
@@ -258,29 +273,42 @@ function extractBatchResultCommand(item) {
|
|
|
258
273
|
return Array.isArray(item.command) ? item.command.filter((token) => typeof token === "string") : [];
|
|
259
274
|
}
|
|
260
275
|
export function getStaleRefArgs(commandTokens, stdin) {
|
|
261
|
-
if (commandTokens[0] !== "batch"
|
|
262
|
-
return commandTokens;
|
|
263
|
-
}
|
|
264
|
-
const parsed = parseUserBatchStdin(stdin);
|
|
265
|
-
if (parsed.error || parsed.steps === undefined) {
|
|
276
|
+
if (commandTokens[0] !== "batch") {
|
|
266
277
|
return commandTokens;
|
|
267
278
|
}
|
|
268
|
-
|
|
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);
|
|
269
292
|
}
|
|
270
293
|
function collectRefsFromTokens(tokens) {
|
|
271
|
-
|
|
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;
|
|
272
303
|
}
|
|
273
304
|
export function getGuardedRefUsage(commandTokens, stdin, options = {}) {
|
|
274
305
|
const collectFromStep = (step) => isRefGuardedCommand(step[0]) ? collectRefsFromTokens(step) : [];
|
|
275
|
-
if (commandTokens[0] !== "batch"
|
|
276
|
-
return collectFromStep(commandTokens);
|
|
277
|
-
}
|
|
278
|
-
const parsed = parseUserBatchStdin(stdin);
|
|
279
|
-
if (parsed.error || parsed.steps === undefined) {
|
|
306
|
+
if (commandTokens[0] !== "batch") {
|
|
280
307
|
return collectFromStep(commandTokens);
|
|
281
308
|
}
|
|
309
|
+
const steps = getUpstreamEffectiveBatchSteps(commandTokens, stdin);
|
|
282
310
|
const refsBeforeInBatchSnapshot = [];
|
|
283
|
-
for (const step of
|
|
311
|
+
for (const step of steps) {
|
|
284
312
|
if (!options.includeRefsAfterBatchSnapshot && (step[0] ?? "") === "snapshot")
|
|
285
313
|
break;
|
|
286
314
|
refsBeforeInBatchSnapshot.push(...collectFromStep(step));
|
|
@@ -307,13 +335,10 @@ function isSafeSameSnapshotFormBatchStep(step, refSnapshot) {
|
|
|
307
335
|
return false;
|
|
308
336
|
}
|
|
309
337
|
function getBatchRefInvalidationMessage(commandTokens, stdin, refSnapshot) {
|
|
310
|
-
if (commandTokens[0] !== "batch"
|
|
311
|
-
return undefined;
|
|
312
|
-
const parsed = parseUserBatchStdin(stdin);
|
|
313
|
-
if (parsed.error || parsed.steps === undefined)
|
|
338
|
+
if (commandTokens[0] !== "batch")
|
|
314
339
|
return undefined;
|
|
315
340
|
let priorStepInvalidatesRefs = false;
|
|
316
|
-
for (const step of
|
|
341
|
+
for (const step of getUpstreamEffectiveBatchSteps(commandTokens, stdin)) {
|
|
317
342
|
if ((step[0] ?? "") === "snapshot") {
|
|
318
343
|
priorStepInvalidatesRefs = false;
|
|
319
344
|
}
|
|
@@ -321,7 +346,7 @@ function getBatchRefInvalidationMessage(commandTokens, stdin, refSnapshot) {
|
|
|
321
346
|
if (refIds.length > 0 && isRefGuardedCommand(step[0]) && priorStepInvalidatesRefs) {
|
|
322
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.`;
|
|
323
348
|
}
|
|
324
|
-
if (isRefInvalidatingBatchCommand(step
|
|
349
|
+
if (isRefInvalidatingBatchCommand(step) && !isSafeSameSnapshotFormBatchStep(step, refSnapshot)) {
|
|
325
350
|
priorStepInvalidatesRefs = true;
|
|
326
351
|
}
|
|
327
352
|
}
|
|
@@ -344,7 +369,9 @@ export function buildStaleRefPreflight(options) {
|
|
|
344
369
|
return undefined;
|
|
345
370
|
if (options.refSnapshotInvalidation) {
|
|
346
371
|
return {
|
|
347
|
-
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.`,
|
|
348
375
|
refIds: usedRefIds,
|
|
349
376
|
snapshotInvalidation: options.refSnapshotInvalidation,
|
|
350
377
|
};
|
|
@@ -390,11 +417,22 @@ export function shouldPinSessionTabForCommand(options) {
|
|
|
390
417
|
}
|
|
391
418
|
export function buildPinnedBatchPlan(options) {
|
|
392
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
|
+
}
|
|
393
432
|
const parsed = parseUserBatchStdin(options.stdin);
|
|
394
433
|
if (parsed.error) {
|
|
395
434
|
return { error: parsed.error };
|
|
396
435
|
}
|
|
397
|
-
const tabSelectionStep = ["tab", options.selectedTab];
|
|
398
436
|
return {
|
|
399
437
|
includeNavigationSummary: false,
|
|
400
438
|
steps: [tabSelectionStep, ...(parsed.steps ?? [])],
|
|
@@ -602,10 +640,11 @@ export function electronTargetLabel(target) {
|
|
|
602
640
|
export function getActiveElectronRecords(records) {
|
|
603
641
|
return [...records.values()].filter((record) => record.cleanupState === "active" || record.cleanupState === "dead" || record.cleanupState === "partial" || record.cleanupState === "failed");
|
|
604
642
|
}
|
|
605
|
-
export function findElectronLaunchRecordForSession(sessionName, records) {
|
|
643
|
+
export function findElectronLaunchRecordForSession(sessionName, records, namespace) {
|
|
606
644
|
if (!sessionName)
|
|
607
645
|
return undefined;
|
|
608
|
-
|
|
646
|
+
const sessionKey = getAgentBrowserSessionIdentityKey(sessionName, namespace);
|
|
647
|
+
return getActiveElectronRecords(records).find((record) => record.sessionName && getAgentBrowserSessionIdentityKey(record.sessionName, record.namespace) === sessionKey);
|
|
609
648
|
}
|
|
610
649
|
function buildElectronReattachNextAction(record, liveTarget) {
|
|
611
650
|
const endpoint = liveTarget?.webSocketDebuggerUrl ?? record.webSocketDebuggerUrl ?? String(record.port);
|
|
@@ -753,3 +792,8 @@ export function formatElectronRefFreshnessText(diagnostic) {
|
|
|
753
792
|
return diagnostic?.summary;
|
|
754
793
|
}
|
|
755
794
|
export { extractBatchResultCommand };
|
|
795
|
+
export function getPersistentSessionArtifactStore(ctx) {
|
|
796
|
+
const sessionDir = typeof ctx.sessionManager.getSessionDir === "function" ? ctx.sessionManager.getSessionDir() : undefined;
|
|
797
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
798
|
+
return sessionDir && sessionId ? { sessionDir, sessionId } : undefined;
|
|
799
|
+
}
|
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Own wrapper-side Electron host orchestration for agent_browser structured electron input.
|
|
3
|
-
* Responsibilities: Discover Electron apps, inspect/probe/cleanup wrapper-tracked Electron launches, and build Pi-facing Electron host results.
|
|
4
|
-
* Scope: Electron host actions that do not spawn the main upstream browser command; generic agent_browser execution stays in browser-run.
|
|
5
|
-
*/
|
|
6
1
|
import { cleanupElectronLaunchResources, inspectElectronLaunchStatus } from "../../electron/cleanup.js";
|
|
7
2
|
import { discoverElectronApps } from "../../electron/discovery.js";
|
|
8
|
-
import { boundElectronProbeString } from "../../electron/
|
|
3
|
+
import { boundElectronProbeString } from "../../electron/cdp.js";
|
|
9
4
|
import { buildOwnedManagedSessionRestoreContext, withOwnedManagedSessionContext, } from "../../managed-session-restore.js";
|
|
10
5
|
import { getManagedSessionStateAccessValidationError } from "../../managed-session-state-policy.js";
|
|
11
6
|
import { isRecord } from "../../parsing.js";
|
|
12
7
|
import { withAttachedBrowserSessionContext } from "../../process.js";
|
|
13
|
-
import { buildAgentBrowserNextActions
|
|
8
|
+
import { buildAgentBrowserNextActions } from "../../results/action-recommendations.js";
|
|
9
|
+
import { buildAgentBrowserResultCategoryDetails } from "../../results/categories.js";
|
|
14
10
|
import { appendUniqueAgentBrowserNextActions } from "../../results/next-actions.js";
|
|
15
11
|
import { extractRefSnapshotFromData, getSessionPageStateKey, isAboutBlankUrl, normalizeSessionTabTarget } from "../../session-page-state.js";
|
|
16
12
|
import { redactSensitiveText } from "../../runtime.js";
|
|
17
13
|
import { collectElectronManagedSessionTarget } from "../browser-run/diagnostics.js";
|
|
18
14
|
import { buildElectronHostFailureResult, formatElectronTargetLines, redactToolDetails } from "../browser-run/final-result.js";
|
|
19
|
-
import { acquireOwnedManagedSessionDaemonPolicy, closeManagedSession } from "../browser-run/managed-session-daemon-policy.js";
|
|
15
|
+
import { acquireOwnedManagedSessionDaemonPolicy, closeManagedSession, getRunningHeadedAutosavePolicyChangeError } from "../browser-run/managed-session-daemon-policy.js";
|
|
20
16
|
import { buildElectronIdentifiers, buildElectronMismatchNextActions, buildElectronSessionMismatch, extractStringResultField, findElectronLaunchRecordForSession, formatElectronSessionMismatchText, getActiveElectronRecords, getLiveElectronRendererTargets, runSessionCommandData, } from "../browser-run/session-state.js";
|
|
21
17
|
const ELECTRON_PROFILE_ISOLATION_NOTE = "Profile note: electron.launch starts an isolated temporary profile; it does not reuse the app's normal signed-in profile or attach to an already-running authenticated app.";
|
|
22
18
|
const ELECTRON_EXISTING_AUTH_GUIDANCE = "For already-authenticated desktop app content, do not stop here: if host tools are allowed and the app is not running, launch the normal app with --remote-debugging-port=<port>, verify the port, then run agent_browser connect <port>; if it is already running without a debug port, ask before relaunching it.";
|
|
@@ -118,6 +114,7 @@ function isElectronLaunchRecord(value) {
|
|
|
118
114
|
return false;
|
|
119
115
|
return value.version === 1 &&
|
|
120
116
|
value.launchedByWrapper === true &&
|
|
117
|
+
(value.namespace === undefined || typeof value.namespace === "string") &&
|
|
121
118
|
typeof value.launchId === "string" &&
|
|
122
119
|
typeof value.appName === "string" &&
|
|
123
120
|
typeof value.executablePath === "string" &&
|
|
@@ -137,13 +134,14 @@ export function restoreElectronLaunchRecordsFromBranch(branch) {
|
|
|
137
134
|
const electron = isRecord(details?.electron) ? details.electron : undefined;
|
|
138
135
|
if (!electron)
|
|
139
136
|
continue;
|
|
137
|
+
const namespace = typeof details?.namespace === "string" ? details.namespace : undefined;
|
|
140
138
|
const launch = isElectronLaunchRecord(electron.launch) ? electron.launch : undefined;
|
|
141
139
|
if (launch)
|
|
142
|
-
records.set(launch.launchId, launch);
|
|
140
|
+
records.set(launch.launchId, { ...launch, namespace: launch.namespace ?? namespace });
|
|
143
141
|
const cleanupRecords = isRecord(electron.cleanup) && Array.isArray(electron.cleanup.records) ? electron.cleanup.records : [];
|
|
144
142
|
for (const cleanupRecord of cleanupRecords) {
|
|
145
143
|
if (isElectronLaunchRecord(cleanupRecord))
|
|
146
|
-
records.set(cleanupRecord.launchId, cleanupRecord);
|
|
144
|
+
records.set(cleanupRecord.launchId, { ...cleanupRecord, namespace: cleanupRecord.namespace ?? namespace });
|
|
147
145
|
}
|
|
148
146
|
}
|
|
149
147
|
return records;
|
|
@@ -398,9 +396,14 @@ function getElectronProbeSummary(probe) {
|
|
|
398
396
|
class ElectronManagedSessionPolicyError extends Error {
|
|
399
397
|
}
|
|
400
398
|
async function withOwnedElectronManagedSessionPolicy(options, run) {
|
|
399
|
+
const autosavePolicyChangeError = getRunningHeadedAutosavePolicyChangeError(options.headedManagedAutosaveInterval);
|
|
400
|
+
if (autosavePolicyChangeError)
|
|
401
|
+
throw new ElectronManagedSessionPolicyError(autosavePolicyChangeError);
|
|
401
402
|
const context = buildOwnedManagedSessionRestoreContext({
|
|
402
403
|
args: ["--namespace", options.namespace ?? "", "--session", options.sessionName, ...options.args],
|
|
403
404
|
cwd: options.cwd,
|
|
405
|
+
headedManagedAutosaveDisabled: options.headedManagedAutosaveDisabled,
|
|
406
|
+
headedManagedAutosaveInterval: options.headedManagedAutosaveInterval,
|
|
404
407
|
managedSessionName: options.sessionName,
|
|
405
408
|
namespace: options.namespace,
|
|
406
409
|
restoreState: options.restoreState,
|
|
@@ -427,7 +430,7 @@ async function withOwnedElectronManagedSessionPolicy(options, run) {
|
|
|
427
430
|
}
|
|
428
431
|
async function collectOwnedElectronManagedSessionTarget(options) {
|
|
429
432
|
try {
|
|
430
|
-
|
|
433
|
+
const target = await withOwnedElectronManagedSessionPolicy({ ...options, args: ["get", "url"] }, async () => await collectElectronManagedSessionTarget({
|
|
431
434
|
allowManagedSessionTarget: true,
|
|
432
435
|
cwd: options.cwd,
|
|
433
436
|
namespace: options.namespace,
|
|
@@ -435,9 +438,10 @@ async function collectOwnedElectronManagedSessionTarget(options) {
|
|
|
435
438
|
signal: options.signal,
|
|
436
439
|
timeoutMs: options.timeoutMs,
|
|
437
440
|
})) ?? { sessionName: options.sessionName };
|
|
441
|
+
return { ...target, namespace: options.namespace };
|
|
438
442
|
}
|
|
439
443
|
catch (error) {
|
|
440
|
-
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 };
|
|
441
445
|
}
|
|
442
446
|
}
|
|
443
447
|
async function runElectronProbeCommandData(options) {
|
|
@@ -581,6 +585,8 @@ function buildElectronProbeResult(options) {
|
|
|
581
585
|
},
|
|
582
586
|
nextActions: nextActions.length > 0 ? nextActions : undefined,
|
|
583
587
|
...buildAgentBrowserResultCategoryDetails({ args: [], succeeded: true }),
|
|
588
|
+
managedSessionHeadedAutosaveDisabled: options.headedManagedAutosaveDisabled === true ? true : undefined,
|
|
589
|
+
managedSessionHeadedAutosaveInterval: options.headedManagedAutosaveInterval,
|
|
584
590
|
namespace: options.namespace,
|
|
585
591
|
refSnapshot: options.probe.refSnapshot,
|
|
586
592
|
sessionName: options.probe.sessionName,
|
|
@@ -597,8 +603,10 @@ function buildElectronProbeResult(options) {
|
|
|
597
603
|
export async function cleanupTrackedElectronHostLaunches(options) {
|
|
598
604
|
const results = [];
|
|
599
605
|
for (const record of options.records) {
|
|
606
|
+
const sessionKey = getSessionPageStateKey(record.sessionName, record.namespace) ?? record.sessionName;
|
|
607
|
+
const managedSessionOwner = sessionKey ? options.ownedManagedSessions.get(sessionKey) : undefined;
|
|
600
608
|
const managedSessionCloseError = record.sessionName
|
|
601
|
-
? await closeManagedSession({ cwd: options.cwd, preserveAttachedBrowserSession: options.attachedSessionKeys.has(
|
|
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 })
|
|
602
610
|
: undefined;
|
|
603
611
|
const managedSessionStep = record.sessionName
|
|
604
612
|
? managedSessionCloseError
|
|
@@ -643,11 +651,11 @@ export async function cleanupActiveElectronHostLaunches(options) {
|
|
|
643
651
|
export async function handleElectronHostInput(options) {
|
|
644
652
|
const currentSessionKey = getSessionPageStateKey(options.managedSessionName, options.managedSessionNamespace) ?? options.managedSessionName;
|
|
645
653
|
const preserveAttachedBrowserSession = options.attachedSessionKeys.has(currentSessionKey)
|
|
646
|
-
|| [...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));
|
|
647
655
|
return await withAttachedBrowserSessionContext(preserveAttachedBrowserSession, () => handleElectronHostInputInContext(options));
|
|
648
656
|
}
|
|
649
657
|
async function handleElectronHostInputInContext(options) {
|
|
650
|
-
const { attachedSessionKeys, compiledElectron, cwd, electronChildProcesses, electronLaunchRecords, implicitSessionCloseTimeoutMs, managedSessionActive, managedSessionName, managedSessionNamespace, managedSessionRestoreState, redactedCompiledElectron, sessionPageState, signal, } = options;
|
|
658
|
+
const { attachedSessionKeys, compiledElectron, cwd, electronChildProcesses, electronLaunchRecords, implicitSessionCloseTimeoutMs, managedSessionActive, managedSessionName, managedSessionNamespace, managedSessionRestoreState, ownedManagedSessions, redactedCompiledElectron, sessionPageState, signal, } = options;
|
|
651
659
|
if (compiledElectron?.action === "list") {
|
|
652
660
|
try {
|
|
653
661
|
const discovery = await discoverElectronApps({ maxResults: compiledElectron.maxResults, query: compiledElectron.query });
|
|
@@ -665,16 +673,22 @@ async function handleElectronHostInputInContext(options) {
|
|
|
665
673
|
const statuses = await Promise.all(records.map((record) => inspectElectronLaunchStatus(record)));
|
|
666
674
|
const managedSessions = await Promise.all(records
|
|
667
675
|
.filter((record) => typeof record.sessionName === "string")
|
|
668
|
-
.map((record) =>
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
676
|
+
.map((record) => {
|
|
677
|
+
const sessionKey = getSessionPageStateKey(record.sessionName, record.namespace) ?? record.sessionName;
|
|
678
|
+
return collectOwnedElectronManagedSessionTarget({
|
|
679
|
+
cwd,
|
|
680
|
+
headedManagedAutosaveDisabled: ownedManagedSessions.get(sessionKey)?.headedManagedAutosaveDisabled,
|
|
681
|
+
headedManagedAutosaveInterval: ownedManagedSessions.get(sessionKey)?.headedManagedAutosaveInterval,
|
|
682
|
+
namespace: record.namespace,
|
|
683
|
+
restoreState: managedSessionRestoreState,
|
|
684
|
+
sessionName: record.sessionName,
|
|
685
|
+
signal,
|
|
686
|
+
timeoutMs: compiledElectron.timeoutMs,
|
|
687
|
+
});
|
|
688
|
+
}));
|
|
675
689
|
const mismatches = managedSessions
|
|
676
690
|
.map((managedSession) => {
|
|
677
|
-
const record = records.find((candidate) => candidate.sessionName === managedSession.sessionName);
|
|
691
|
+
const record = records.find((candidate) => candidate.sessionName === managedSession.sessionName && candidate.namespace === managedSession.namespace);
|
|
678
692
|
const status = record ? statuses.find((candidate) => candidate.launchId === record.launchId) : undefined;
|
|
679
693
|
return record && status ? buildElectronSessionMismatch({ managedSession, record, statusTargets: status.targets }) : undefined;
|
|
680
694
|
})
|
|
@@ -690,7 +704,7 @@ async function handleElectronHostInputInContext(options) {
|
|
|
690
704
|
if (compiledElectron?.action === "probe") {
|
|
691
705
|
const launchRecord = compiledElectron.launchId
|
|
692
706
|
? electronLaunchRecords.get(compiledElectron.launchId)
|
|
693
|
-
: findElectronLaunchRecordForSession(managedSessionName, electronLaunchRecords);
|
|
707
|
+
: findElectronLaunchRecordForSession(managedSessionName, electronLaunchRecords, managedSessionNamespace);
|
|
694
708
|
if (compiledElectron.launchId && !launchRecord) {
|
|
695
709
|
return buildElectronHostFailureResult({
|
|
696
710
|
compiledElectron: redactedCompiledElectron ?? compiledElectron,
|
|
@@ -722,7 +736,7 @@ async function handleElectronHostInputInContext(options) {
|
|
|
722
736
|
}
|
|
723
737
|
try {
|
|
724
738
|
const status = launchRecord ? await inspectElectronLaunchStatus(launchRecord) : undefined;
|
|
725
|
-
const probeNamespace = compiledElectron.launchId ?
|
|
739
|
+
const probeNamespace = compiledElectron.launchId ? launchRecord?.namespace : managedSessionNamespace;
|
|
726
740
|
const pageStateKey = getSessionPageStateKey(probeSessionName, probeNamespace) ?? probeSessionName;
|
|
727
741
|
const currentPageState = sessionPageState.get(pageStateKey);
|
|
728
742
|
const fileAccessError = getManagedSessionStateAccessValidationError({
|
|
@@ -733,9 +747,14 @@ async function handleElectronHostInputInContext(options) {
|
|
|
733
747
|
});
|
|
734
748
|
if (fileAccessError)
|
|
735
749
|
throw new ElectronManagedSessionPolicyError(fileAccessError);
|
|
750
|
+
const managedSessionOwner = ownedManagedSessions.get(pageStateKey);
|
|
751
|
+
const headedManagedAutosaveDisabled = managedSessionOwner?.headedManagedAutosaveDisabled === true;
|
|
752
|
+
const headedManagedAutosaveInterval = managedSessionOwner?.headedManagedAutosaveInterval;
|
|
736
753
|
const probe = await withOwnedElectronManagedSessionPolicy({
|
|
737
754
|
args: ["snapshot", "-i"],
|
|
738
755
|
cwd,
|
|
756
|
+
headedManagedAutosaveDisabled,
|
|
757
|
+
headedManagedAutosaveInterval,
|
|
739
758
|
namespace: probeNamespace,
|
|
740
759
|
restoreState: managedSessionRestoreState,
|
|
741
760
|
sessionName: probeSessionName,
|
|
@@ -779,6 +798,8 @@ async function handleElectronHostInputInContext(options) {
|
|
|
779
798
|
}
|
|
780
799
|
return buildElectronProbeResult({
|
|
781
800
|
compiledElectron: redactedCompiledElectron ?? compiledElectron,
|
|
801
|
+
headedManagedAutosaveDisabled,
|
|
802
|
+
headedManagedAutosaveInterval,
|
|
782
803
|
mismatch: sessionMismatch,
|
|
783
804
|
namespace: probeNamespace,
|
|
784
805
|
probe,
|
|
@@ -801,7 +822,7 @@ async function handleElectronHostInputInContext(options) {
|
|
|
801
822
|
const selection = selectElectronRecords(compiledElectron, electronLaunchRecords);
|
|
802
823
|
if (selection.error)
|
|
803
824
|
return buildElectronHostFailureResult({ compiledElectron: redactedCompiledElectron ?? compiledElectron, errorText: selection.error, failureCategory: "validation-error" });
|
|
804
|
-
const cleanupResults = await cleanupTrackedElectronHostLaunches({ attachedSessionKeys, cwd, electronChildProcesses, electronLaunchRecords, managedSessionRestoreState, records: selection.records ?? [], timeoutMs: compiledElectron.timeoutMs ?? implicitSessionCloseTimeoutMs });
|
|
825
|
+
const cleanupResults = await cleanupTrackedElectronHostLaunches({ attachedSessionKeys, cwd, electronChildProcesses, electronLaunchRecords, managedSessionRestoreState, ownedManagedSessions, records: selection.records ?? [], timeoutMs: compiledElectron.timeoutMs ?? implicitSessionCloseTimeoutMs });
|
|
805
826
|
return buildElectronCleanupResult(redactedCompiledElectron ?? compiledElectron, cleanupResults);
|
|
806
827
|
}
|
|
807
828
|
return undefined;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { parseArgvDescriptor } from "../argv-descriptor.js";
|
|
2
2
|
import { validateToolArgs, redactInvocationArgs, redactSensitiveText } from "../runtime.js";
|
|
3
3
|
import { buildAgentBrowserResultCategoryDetails } from "../results/categories.js";
|
|
4
|
-
import { compileAgentBrowserElectron
|
|
4
|
+
import { compileAgentBrowserElectron } from "../input-modes/electron.js";
|
|
5
|
+
import { compileAgentBrowserJob, compileAgentBrowserQaPreset } from "../input-modes/job.js";
|
|
6
|
+
import { compileAgentBrowserNetworkSourceLookup, compileAgentBrowserSourceLookup, redactNetworkSourceLookupArgs, redactNetworkSourceLookupUrl } from "../input-modes/lookups.js";
|
|
7
|
+
import { compileAgentBrowserSemanticAction } from "../input-modes/semantic-action.js";
|
|
8
|
+
import { AGENT_BROWSER_SCRIPT_MAX_TIMEOUT_MS, compileAgentBrowserScript } from "../input-modes/script.js";
|
|
5
9
|
function redactCompiledElectron(compiled) {
|
|
6
10
|
if (!compiled)
|
|
7
11
|
return undefined;
|
|
@@ -68,6 +72,7 @@ export function resolveAgentBrowserInput(options) {
|
|
|
68
72
|
const sourceLookupResult = params.sourceLookup === undefined ? {} : compileAgentBrowserSourceLookup(params.sourceLookup);
|
|
69
73
|
const networkSourceLookupResult = params.networkSourceLookup === undefined ? {} : compileAgentBrowserNetworkSourceLookup(params.networkSourceLookup);
|
|
70
74
|
const electronResult = params.electron === undefined ? {} : compileAgentBrowserElectron(params.electron);
|
|
75
|
+
const scriptResult = params.script === undefined ? {} : compileAgentBrowserScript(params.script);
|
|
71
76
|
const hasExplicitArgs = Array.isArray(params.args);
|
|
72
77
|
const explicitInputModes = [
|
|
73
78
|
hasExplicitArgs,
|
|
@@ -77,19 +82,21 @@ export function resolveAgentBrowserInput(options) {
|
|
|
77
82
|
Boolean(sourceLookupResult.compiled),
|
|
78
83
|
Boolean(networkSourceLookupResult.compiled),
|
|
79
84
|
Boolean(electronResult.compiled),
|
|
85
|
+
Boolean(scriptResult.compiled),
|
|
80
86
|
].filter(Boolean).length;
|
|
81
87
|
const inputModeError = explicitInputModes !== 1
|
|
82
|
-
? "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."
|
|
83
89
|
: undefined;
|
|
84
90
|
const compiledSemanticAction = semanticActionResult.compiled;
|
|
85
91
|
const compiledQaPreset = qaResult.compiled;
|
|
86
92
|
const compiledSourceLookup = sourceLookupResult.compiled;
|
|
87
93
|
const compiledNetworkSourceLookup = networkSourceLookupResult.compiled;
|
|
88
94
|
const compiledElectron = electronResult.compiled;
|
|
95
|
+
const compiledScript = scriptResult.compiled;
|
|
89
96
|
const compiledJob = jobResult.compiled ?? compiledQaPreset;
|
|
90
97
|
const compiledGeneratedBatch = compiledNetworkSourceLookup ?? compiledSourceLookup ?? compiledJob;
|
|
91
98
|
const normalizedExplicitArgs = normalizeExplicitEvalStdinArgs(params.args ?? [], params.stdin);
|
|
92
|
-
const toolArgs = compiledElectron ? [] : compiledSemanticAction?.args ?? compiledGeneratedBatch?.args ?? normalizedExplicitArgs.args;
|
|
99
|
+
const toolArgs = compiledElectron || compiledScript ? [] : compiledSemanticAction?.args ?? compiledGeneratedBatch?.args ?? normalizedExplicitArgs.args;
|
|
93
100
|
const toolStdin = compiledGeneratedBatch?.stdin ?? normalizedExplicitArgs.stdin;
|
|
94
101
|
const redactedArgs = redactInvocationArgs(toolArgs);
|
|
95
102
|
const generatedStdinError = params.stdin !== undefined
|
|
@@ -97,7 +104,9 @@ export function resolveAgentBrowserInput(options) {
|
|
|
97
104
|
? "Do not provide stdin with job, qa, sourceLookup, or networkSourceLookup; those modes generate their own batch stdin."
|
|
98
105
|
: compiledElectron
|
|
99
106
|
? "Do not provide stdin with electron; electron mode is host-only or manages its own input."
|
|
100
|
-
:
|
|
107
|
+
: compiledScript
|
|
108
|
+
? "Do not provide stdin with script; browser call stdin belongs inside browser({ args, stdin })."
|
|
109
|
+
: undefined
|
|
101
110
|
: undefined;
|
|
102
111
|
const outputPathError = params.outputPath !== undefined && (typeof params.outputPath !== "string" || params.outputPath.trim().length === 0)
|
|
103
112
|
? "outputPath must be a non-empty string when provided."
|
|
@@ -106,7 +115,12 @@ export function resolveAgentBrowserInput(options) {
|
|
|
106
115
|
? "timeoutMs must be a positive integer when provided."
|
|
107
116
|
: compiledElectron && params.timeoutMs !== undefined
|
|
108
117
|
? "Use electron.timeoutMs for electron actions; top-level timeoutMs applies only to browser CLI subprocess calls."
|
|
109
|
-
: 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;
|
|
110
124
|
const attachedQaSessionError = compiledQaPreset?.checks.attached
|
|
111
125
|
? params.sessionMode === "fresh"
|
|
112
126
|
? "qa.attached cannot be used with sessionMode=fresh; attach or launch a session first, then run qa.attached with the current session."
|
|
@@ -120,31 +134,35 @@ export function resolveAgentBrowserInput(options) {
|
|
|
120
134
|
?? sourceLookupResult.error
|
|
121
135
|
?? networkSourceLookupResult.error
|
|
122
136
|
?? electronResult.error
|
|
137
|
+
?? scriptResult.error
|
|
123
138
|
?? inputModeError
|
|
124
139
|
?? generatedStdinError
|
|
125
140
|
?? outputPathError
|
|
126
141
|
?? timeoutMsError
|
|
142
|
+
?? scriptSessionModeError
|
|
127
143
|
?? attachedQaSessionError
|
|
128
|
-
?? (compiledElectron ? undefined : validateToolArgs(toolArgs) ?? getBatchPreflightValidationError(toolArgs, toolStdin));
|
|
144
|
+
?? (compiledElectron || compiledScript ? undefined : validateToolArgs(toolArgs) ?? getBatchPreflightValidationError(toolArgs, toolStdin));
|
|
129
145
|
const redactedCompiledJob = redactCompiledJob(compiledJob);
|
|
130
146
|
const redactedCompiledSemanticAction = compiledSemanticAction
|
|
131
147
|
? { ...compiledSemanticAction, args: redactInvocationArgs(compiledSemanticAction.args) }
|
|
132
148
|
: undefined;
|
|
133
149
|
const attemptedKind = compiledElectron
|
|
134
150
|
? "electron"
|
|
135
|
-
:
|
|
136
|
-
? "
|
|
137
|
-
:
|
|
138
|
-
? "
|
|
139
|
-
:
|
|
140
|
-
? "
|
|
141
|
-
:
|
|
142
|
-
? "
|
|
143
|
-
:
|
|
144
|
-
? "
|
|
145
|
-
:
|
|
146
|
-
? "
|
|
147
|
-
:
|
|
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;
|
|
148
166
|
const redactedCompiledElectron = redactCompiledElectron(compiledElectron);
|
|
149
167
|
const redactedCompiledNetworkSourceLookup = redactCompiledNetworkSourceLookup(compiledNetworkSourceLookup);
|
|
150
168
|
const redactedCompiledQaPreset = compiledQaPreset && redactedCompiledJob ? { ...redactedCompiledJob, checks: compiledQaPreset.checks } : undefined;
|
|
@@ -175,6 +193,9 @@ export function resolveAgentBrowserInput(options) {
|
|
|
175
193
|
if (compiledElectron && redactedCompiledElectron) {
|
|
176
194
|
return { ...resolvedBase, compiledElectron, kind: "electron", redactedCompiledElectron, status: "valid" };
|
|
177
195
|
}
|
|
196
|
+
if (compiledScript) {
|
|
197
|
+
return { ...resolvedBase, compiledScript, kind: "script", status: "valid" };
|
|
198
|
+
}
|
|
178
199
|
if (compiledNetworkSourceLookup && redactedCompiledNetworkSourceLookup) {
|
|
179
200
|
return {
|
|
180
201
|
...resolvedBase,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
1
|
+
import { mkdir, realpath, stat, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, isAbsolute, resolve } from "node:path";
|
|
3
3
|
import { getAgentBrowserStoragePathValidationError } from "../managed-session-state-policy.js";
|
|
4
4
|
import { isRecord } from "../parsing.js";
|
|
@@ -31,6 +31,33 @@ function appendOutputFileNotice(result, message) {
|
|
|
31
31
|
export function getAgentBrowserOutputPathValidationError(outputPath, cwd) {
|
|
32
32
|
return outputPath ? getAgentBrowserStoragePathValidationError(normalizeRequestedOutputPath(outputPath), cwd) : undefined;
|
|
33
33
|
}
|
|
34
|
+
function getArtifactPaths(result, cwd) {
|
|
35
|
+
const details = isRecord(result.details) ? result.details : undefined;
|
|
36
|
+
if (!details || !Array.isArray(details.artifacts))
|
|
37
|
+
return [];
|
|
38
|
+
return details.artifacts.flatMap((artifact) => {
|
|
39
|
+
if (!isRecord(artifact))
|
|
40
|
+
return [];
|
|
41
|
+
const path = typeof artifact.absolutePath === "string" ? artifact.absolutePath : typeof artifact.path === "string" ? artifact.path : undefined;
|
|
42
|
+
return path ? [isAbsolute(path) ? path : resolve(cwd, path)] : [];
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async function pathsReferToSameFile(left, right) {
|
|
46
|
+
if (resolve(left) === resolve(right))
|
|
47
|
+
return true;
|
|
48
|
+
try {
|
|
49
|
+
if (await realpath(left) === await realpath(right))
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
catch { }
|
|
53
|
+
try {
|
|
54
|
+
const [leftStat, rightStat] = await Promise.all([stat(left), stat(right)]);
|
|
55
|
+
return leftStat.dev === rightStat.dev && leftStat.ino === rightStat.ino;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
34
61
|
export async function applyAgentBrowserOutputPath(options) {
|
|
35
62
|
if (!options.outputPath)
|
|
36
63
|
return options.result;
|
|
@@ -47,6 +74,20 @@ export async function applyAgentBrowserOutputPath(options) {
|
|
|
47
74
|
const requestedPath = normalizeRequestedOutputPath(options.outputPath);
|
|
48
75
|
const absolutePath = isAbsolute(requestedPath) ? requestedPath : resolve(options.cwd, requestedPath);
|
|
49
76
|
const payload = getOutputPayload(options.result);
|
|
77
|
+
for (const artifactPath of getArtifactPaths(options.result, options.cwd)) {
|
|
78
|
+
if (!await pathsReferToSameFile(absolutePath, artifactPath))
|
|
79
|
+
continue;
|
|
80
|
+
const message = "outputPath resolves to the same file as a browser artifact destination; choose a separate outputPath or omit it. The browser artifact was preserved.";
|
|
81
|
+
const outputFile = { absolutePath, error: message, path: requestedPath, source: payload.source, status: "failed" };
|
|
82
|
+
const details = isRecord(options.result.details) ? { ...options.result.details } : {};
|
|
83
|
+
delete details.successCategory;
|
|
84
|
+
return {
|
|
85
|
+
...options.result,
|
|
86
|
+
content: appendOutputFileNotice(options.result, `Output file rejected: ${message}`),
|
|
87
|
+
details: { ...details, failureCategory: "validation-error", outputFile, resultCategory: "failure" },
|
|
88
|
+
isError: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
50
91
|
try {
|
|
51
92
|
const serialized = serializeOutputPayload(payload.value);
|
|
52
93
|
await mkdir(dirname(absolutePath), { recursive: true });
|