pi-agent-browser-native 0.2.48 → 0.2.50

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.
Files changed (189) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +21 -11
  3. package/dist/extensions/agent-browser/index.js +808 -0
  4. package/dist/extensions/agent-browser/lib/argv-descriptor.js +71 -0
  5. package/dist/extensions/agent-browser/lib/argv-grammar.js +121 -0
  6. package/dist/extensions/agent-browser/lib/bash-guard.js +190 -0
  7. package/dist/extensions/agent-browser/lib/command-policy.js +85 -0
  8. package/dist/extensions/agent-browser/lib/command-taxonomy.js +302 -0
  9. package/dist/extensions/agent-browser/lib/config-policy.js +669 -0
  10. package/dist/extensions/agent-browser/lib/config.js +122 -0
  11. package/dist/extensions/agent-browser/lib/electron/cdp.js +51 -0
  12. package/dist/extensions/agent-browser/lib/electron/cleanup.js +212 -0
  13. package/dist/extensions/agent-browser/lib/electron/discovery.js +633 -0
  14. package/dist/extensions/agent-browser/lib/electron/launch.js +351 -0
  15. package/{extensions/agent-browser/lib/electron/text.ts → dist/extensions/agent-browser/lib/electron/text.js} +5 -5
  16. package/dist/extensions/agent-browser/lib/executable-path.js +20 -0
  17. package/dist/extensions/agent-browser/lib/fs-utils.js +18 -0
  18. package/dist/extensions/agent-browser/lib/input-modes/electron.js +165 -0
  19. package/dist/extensions/agent-browser/lib/input-modes/job.js +519 -0
  20. package/dist/extensions/agent-browser/lib/input-modes/lookups.js +440 -0
  21. package/dist/extensions/agent-browser/lib/input-modes/params.js +164 -0
  22. package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +119 -0
  23. package/dist/extensions/agent-browser/lib/input-modes/shared.js +42 -0
  24. package/dist/extensions/agent-browser/lib/input-modes/types.js +21 -0
  25. package/dist/extensions/agent-browser/lib/input-modes.js +10 -0
  26. package/dist/extensions/agent-browser/lib/json-schema.js +58 -0
  27. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +59 -0
  28. package/dist/extensions/agent-browser/lib/navigation-policy.js +83 -0
  29. package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +62 -0
  30. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +39 -0
  31. package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +276 -0
  32. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +909 -0
  33. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +443 -0
  34. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +47 -0
  35. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +141 -0
  36. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +108 -0
  37. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +112 -0
  38. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +158 -0
  39. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +54 -0
  40. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +762 -0
  41. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +491 -0
  42. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.js +40 -0
  43. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +5 -0
  44. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +731 -0
  45. package/dist/extensions/agent-browser/lib/orchestration/browser-run/types.js +1 -0
  46. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +718 -0
  47. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +247 -0
  48. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +68 -0
  49. package/{extensions/agent-browser/lib/parsing.ts → dist/extensions/agent-browser/lib/parsing.js} +12 -11
  50. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +241 -0
  51. package/dist/extensions/agent-browser/lib/playbook.js +121 -0
  52. package/dist/extensions/agent-browser/lib/process.js +363 -0
  53. package/dist/extensions/agent-browser/lib/prompt-policy.js +91 -0
  54. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +220 -0
  55. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +111 -0
  56. package/{extensions/agent-browser/lib/results/artifact-state.ts → dist/extensions/agent-browser/lib/results/artifact-state.js} +4 -8
  57. package/dist/extensions/agent-browser/lib/results/categories.js +76 -0
  58. package/dist/extensions/agent-browser/lib/results/confirmation.js +63 -0
  59. package/dist/extensions/agent-browser/lib/results/contracts.js +8 -0
  60. package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +74 -0
  61. package/dist/extensions/agent-browser/lib/results/envelope.js +166 -0
  62. package/dist/extensions/agent-browser/lib/results/network-routes.js +92 -0
  63. package/dist/extensions/agent-browser/lib/results/network.js +73 -0
  64. package/dist/extensions/agent-browser/lib/results/next-actions.js +72 -0
  65. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +515 -0
  66. package/dist/extensions/agent-browser/lib/results/presentation/batch.js +397 -0
  67. package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +55 -0
  68. package/dist/extensions/agent-browser/lib/results/presentation/common.js +46 -0
  69. package/dist/extensions/agent-browser/lib/results/presentation/content.js +24 -0
  70. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +956 -0
  71. package/dist/extensions/agent-browser/lib/results/presentation/errors.js +205 -0
  72. package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +134 -0
  73. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +159 -0
  74. package/dist/extensions/agent-browser/lib/results/presentation/registry.js +216 -0
  75. package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +104 -0
  76. package/dist/extensions/agent-browser/lib/results/presentation/skills.js +152 -0
  77. package/dist/extensions/agent-browser/lib/results/presentation.js +177 -0
  78. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +107 -0
  79. package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +50 -0
  80. package/dist/extensions/agent-browser/lib/results/selector-recovery.js +225 -0
  81. package/{extensions/agent-browser/lib/results/shared.ts → dist/extensions/agent-browser/lib/results/shared.js} +0 -1
  82. package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +208 -0
  83. package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +78 -0
  84. package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +331 -0
  85. package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +40 -0
  86. package/dist/extensions/agent-browser/lib/results/snapshot.js +264 -0
  87. package/dist/extensions/agent-browser/lib/results/text.js +40 -0
  88. package/{extensions/agent-browser/lib/results.ts → dist/extensions/agent-browser/lib/results.js} +2 -32
  89. package/dist/extensions/agent-browser/lib/runtime.js +855 -0
  90. package/dist/extensions/agent-browser/lib/session-page-state.js +411 -0
  91. package/dist/extensions/agent-browser/lib/string-enum-schema.js +13 -0
  92. package/dist/extensions/agent-browser/lib/temp.js +498 -0
  93. package/dist/extensions/agent-browser/lib/web-search.js +562 -0
  94. package/docs/ARCHITECTURE.md +5 -5
  95. package/docs/COMMAND_REFERENCE.md +4 -4
  96. package/docs/RELEASE.md +22 -11
  97. package/docs/REQUIREMENTS.md +1 -1
  98. package/docs/SUPPORT_MATRIX.md +5 -4
  99. package/docs/TOOL_CONTRACT.md +1 -1
  100. package/package.json +9 -5
  101. package/scripts/config.mjs +14 -20
  102. package/scripts/doctor.mjs +8 -7
  103. package/extensions/agent-browser/index.ts +0 -961
  104. package/extensions/agent-browser/lib/argv-descriptor.ts +0 -90
  105. package/extensions/agent-browser/lib/argv-grammar.ts +0 -128
  106. package/extensions/agent-browser/lib/bash-guard.ts +0 -205
  107. package/extensions/agent-browser/lib/command-policy.ts +0 -71
  108. package/extensions/agent-browser/lib/command-taxonomy.ts +0 -336
  109. package/extensions/agent-browser/lib/config-policy.js +0 -690
  110. package/extensions/agent-browser/lib/config.ts +0 -211
  111. package/extensions/agent-browser/lib/electron/cdp.ts +0 -69
  112. package/extensions/agent-browser/lib/electron/cleanup.ts +0 -235
  113. package/extensions/agent-browser/lib/electron/discovery.ts +0 -710
  114. package/extensions/agent-browser/lib/electron/launch.ts +0 -499
  115. package/extensions/agent-browser/lib/executable-path.ts +0 -19
  116. package/extensions/agent-browser/lib/fs-utils.ts +0 -18
  117. package/extensions/agent-browser/lib/input-modes/electron.ts +0 -170
  118. package/extensions/agent-browser/lib/input-modes/job.ts +0 -527
  119. package/extensions/agent-browser/lib/input-modes/lookups.ts +0 -447
  120. package/extensions/agent-browser/lib/input-modes/params.ts +0 -205
  121. package/extensions/agent-browser/lib/input-modes/semantic-action.ts +0 -127
  122. package/extensions/agent-browser/lib/input-modes/shared.ts +0 -46
  123. package/extensions/agent-browser/lib/input-modes/types.ts +0 -225
  124. package/extensions/agent-browser/lib/input-modes.ts +0 -45
  125. package/extensions/agent-browser/lib/json-schema.ts +0 -73
  126. package/extensions/agent-browser/lib/launch-scoped-flags.ts +0 -67
  127. package/extensions/agent-browser/lib/navigation-policy.ts +0 -95
  128. package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +0 -65
  129. package/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.ts +0 -44
  130. package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +0 -280
  131. package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -914
  132. package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -521
  133. package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
  134. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.ts +0 -158
  135. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.ts +0 -116
  136. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.ts +0 -147
  137. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.ts +0 -183
  138. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.ts +0 -58
  139. package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -847
  140. package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -559
  141. package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
  142. package/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.ts +0 -8
  143. package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
  144. package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -565
  145. package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +0 -855
  146. package/extensions/agent-browser/lib/orchestration/input-plan.ts +0 -375
  147. package/extensions/agent-browser/lib/orchestration/output-file.ts +0 -86
  148. package/extensions/agent-browser/lib/pi-tool-rendering.ts +0 -267
  149. package/extensions/agent-browser/lib/playbook.ts +0 -142
  150. package/extensions/agent-browser/lib/process.ts +0 -516
  151. package/extensions/agent-browser/lib/prompt-policy.ts +0 -105
  152. package/extensions/agent-browser/lib/results/action-recommendations.ts +0 -264
  153. package/extensions/agent-browser/lib/results/artifact-manifest.ts +0 -111
  154. package/extensions/agent-browser/lib/results/categories.ts +0 -106
  155. package/extensions/agent-browser/lib/results/confirmation.ts +0 -76
  156. package/extensions/agent-browser/lib/results/contracts.ts +0 -241
  157. package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +0 -72
  158. package/extensions/agent-browser/lib/results/envelope.ts +0 -195
  159. package/extensions/agent-browser/lib/results/network-routes.ts +0 -83
  160. package/extensions/agent-browser/lib/results/network.ts +0 -78
  161. package/extensions/agent-browser/lib/results/next-actions.ts +0 -117
  162. package/extensions/agent-browser/lib/results/presentation/artifacts.ts +0 -588
  163. package/extensions/agent-browser/lib/results/presentation/batch.ts +0 -450
  164. package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +0 -67
  165. package/extensions/agent-browser/lib/results/presentation/common.ts +0 -53
  166. package/extensions/agent-browser/lib/results/presentation/content.ts +0 -36
  167. package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +0 -923
  168. package/extensions/agent-browser/lib/results/presentation/errors.ts +0 -227
  169. package/extensions/agent-browser/lib/results/presentation/large-output.ts +0 -182
  170. package/extensions/agent-browser/lib/results/presentation/navigation.ts +0 -184
  171. package/extensions/agent-browser/lib/results/presentation/registry.ts +0 -242
  172. package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +0 -131
  173. package/extensions/agent-browser/lib/results/presentation/skills.ts +0 -143
  174. package/extensions/agent-browser/lib/results/presentation.ts +0 -257
  175. package/extensions/agent-browser/lib/results/recovery-actions.ts +0 -139
  176. package/extensions/agent-browser/lib/results/recovery-next-actions.ts +0 -71
  177. package/extensions/agent-browser/lib/results/selector-recovery.ts +0 -320
  178. package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +0 -273
  179. package/extensions/agent-browser/lib/results/snapshot-refs.ts +0 -100
  180. package/extensions/agent-browser/lib/results/snapshot-segments.ts +0 -366
  181. package/extensions/agent-browser/lib/results/snapshot-spill.ts +0 -63
  182. package/extensions/agent-browser/lib/results/snapshot.ts +0 -329
  183. package/extensions/agent-browser/lib/results/text.ts +0 -40
  184. package/extensions/agent-browser/lib/runtime.ts +0 -988
  185. package/extensions/agent-browser/lib/session-page-state.ts +0 -512
  186. package/extensions/agent-browser/lib/string-enum-schema.ts +0 -20
  187. package/extensions/agent-browser/lib/temp.ts +0 -577
  188. package/extensions/agent-browser/lib/web-search.ts +0 -728
  189. /package/{extensions/agent-browser/lib/orchestration/browser-run.ts → dist/extensions/agent-browser/lib/orchestration/browser-run.js} +0 -0
@@ -0,0 +1,491 @@
1
+ import { readFile, rm } from "node:fs/promises";
2
+ import { isCloseCommand, isOpenNavigationCommand } from "../../command-taxonomy.js";
3
+ import { cleanupElectronLaunchResources, inspectElectronLaunchStatus } from "../../electron/cleanup.js";
4
+ import { getAllowedDomainsViolation, parseAllowedDomainsPolicyFromArgs } from "../../navigation-policy.js";
5
+ import { analyzeNetworkSourceLookupResults, analyzeQaPresetResults, analyzeQaPresetTimeout, analyzeSourceLookupResults, buildQaCompactPassText, extractQaPageContext, redactNetworkSourceLookupAnalysis, } from "../../input-modes.js";
6
+ import { applyNetworkRouteRecords, buildNetworkRouteDiagnostics, buildToolPresentation, getAgentBrowserErrorText, parseAgentBrowserEnvelope, } from "../../results.js";
7
+ import { buildEvictedSessionArtifactEntries, formatSessionArtifactRetentionSummary, mergeSessionArtifactManifest, } from "../../results/artifact-manifest.js";
8
+ import { getClipboardWritePayloadCandidates, redactClipboardPermissionEcho, redactClipboardPermissionErrorValue } from "../../results/presentation/errors.js";
9
+ import { shouldCaptureSemanticActionNavigationSummary } from "../../results/presentation/semantic-action.js";
10
+ import { commandExplicitlyTargetsAboutBlank, deriveSessionTabTarget, extractLatestRefSnapshotStateFromBatchResults, extractRefSnapshotFromData, extractSessionTabTargetFromBatchResults, extractSessionTabTargetFromCommandData, isAboutBlankSessionTabTarget, normalizeSessionTabTarget, } from "../../session-page-state.js";
11
+ import { writePersistentSessionArtifactFile, writeSecureTempFile } from "../../temp.js";
12
+ import { isRecord } from "../../parsing.js";
13
+ import { createFreshSessionName, extractCommandTokens, hasLaunchScopedTabCorrectionFlag, resolveManagedSessionState } from "../../runtime.js";
14
+ import { applyOpenResultTabCorrection, buildAboutBlankRecoveryHint, buildAboutBlankWarning, buildElectronPostCommandHealthDiagnostic, buildElectronRefFreshnessDiagnostic, buildElectronSessionMismatch, buildManagedSessionOutcome, closeManagedSession, collectOpenResultTabCorrection, collectSessionTabSelection, extractNavigationSummaryFromData, extractStringResultField, findElectronLaunchRecordForSession, formatElectronPostCommandHealthText, formatElectronSessionMismatchText, getStaleRefArgs, mergeNavigationSummaryIntoData, shouldCaptureNavigationSummary, shouldCorrectSessionTabAfterCommand, shouldInspectElectronPostCommandHealth, unwrapPinnedSessionBatchEnvelope, updateTraceOwnerState, } from "./session-state.js";
15
+ import { collectClickDispatchDiagnostic } from "./click-dispatch.js";
16
+ import { buildScrollNoopDiagnostic, collectComboboxFocusDiagnostic, collectElectronBroadGetTextScopeDiagnostics, collectElectronHandoff, collectFillVerificationDiagnostic, collectNavigationSummary, collectOverlayBlockerDiagnostic, collectQaAttachedTarget, collectSnapshotOverlayBlockerDiagnostic, collectRecordingDependencyWarning, collectScrollPositionSnapshot, collectSelectorTextVisibilityDiagnostics, collectTimeoutPartialProgress, formatQaAttachedTargetText, getArtifactCleanupGuidance, getEvalResultWarning, getEvalStdinHint, getSourceLookupElectronContext, sleepMs, } from "./diagnostics.js";
17
+ import { repairScreenshotData } from "./prepare.js";
18
+ import { getPersistentSessionArtifactStore } from "./session-artifacts.js";
19
+ import { buildFinalAgentBrowserToolResult, buildRedactedPresentationContent, buildWrapperRecoveryHint, prepareFinalResultRecoveryState, redactExactSensitiveText, redactExactSensitiveValue, } from "./final-result.js";
20
+ async function repairScreenshotArtifact(options) {
21
+ const { cwd, envelope, request } = options;
22
+ if (!request || !envelope || !isRecord(envelope.data))
23
+ return { envelope, request };
24
+ const repaired = await repairScreenshotData({ cwd, data: envelope.data, request });
25
+ return { envelope: { ...envelope, data: repaired.data }, request: repaired.request };
26
+ }
27
+ async function repairBatchScreenshotArtifacts(options) {
28
+ const { cwd, envelope, requests } = options;
29
+ if (!envelope || !Array.isArray(envelope.data) || !requests?.some((request) => request !== undefined))
30
+ return { envelope, requests };
31
+ const repairedRequests = [];
32
+ const repairedData = await Promise.all(envelope.data.map(async (item, index) => {
33
+ const request = requests[index];
34
+ if (!request || !isRecord(item) || !isRecord(item.result))
35
+ return item;
36
+ const repaired = await repairScreenshotData({ cwd, data: item.result, request });
37
+ repairedRequests[index] = repaired.request;
38
+ return { ...item, result: repaired.data };
39
+ }));
40
+ return { envelope: { ...envelope, data: repairedData }, requests: repairedRequests };
41
+ }
42
+ function getEnvelopeErrorString(envelope) {
43
+ if (!envelope?.error)
44
+ return undefined;
45
+ if (typeof envelope.error === "string")
46
+ return envelope.error;
47
+ if (isRecord(envelope.error) && typeof envelope.error.message === "string")
48
+ return envelope.error.message;
49
+ return String(envelope.error);
50
+ }
51
+ function isStreamEnableAlreadyEnabledNoop(options) {
52
+ if (!options.processSucceeded || options.command !== "stream" || options.subcommand !== "enable" || options.envelope?.success !== false)
53
+ return false;
54
+ const message = (getEnvelopeErrorString(options.envelope) ?? "").trim().replace(/[.!]+$/, "").toLowerCase();
55
+ return message === "streaming is already enabled for this session" || message === "streaming is already enabled" || message === "stream already enabled";
56
+ }
57
+ function batchStartedManagedBrowser(data) {
58
+ if (!Array.isArray(data))
59
+ return false;
60
+ return data.some((entry) => {
61
+ if (!isRecord(entry) || entry.success !== true || !Array.isArray(entry.command))
62
+ return false;
63
+ const command = typeof entry.command[0] === "string" ? entry.command[0] : undefined;
64
+ return command === "connect" || command === "goto" || command === "navigate" || isOpenNavigationCommand(command);
65
+ });
66
+ }
67
+ function setNetworkRouteState(options) {
68
+ if (!options.sessionName)
69
+ return options.routesBySession;
70
+ const previousRoutes = options.routesBySession.get(options.sessionName);
71
+ if (options.routes === previousRoutes)
72
+ return options.routesBySession;
73
+ const next = new Map(options.routesBySession);
74
+ if (options.routes && options.routes.length > 0)
75
+ next.set(options.sessionName, options.routes);
76
+ else
77
+ next.delete(options.sessionName);
78
+ return next;
79
+ }
80
+ function applyNetworkRouteState(options) {
81
+ const routes = options.sessionName ? applyNetworkRouteRecords(options.routesBySession.get(options.sessionName), options.commandTokens, options.succeeded) : undefined;
82
+ return setNetworkRouteState({ routes, routesBySession: options.routesBySession, sessionName: options.sessionName });
83
+ }
84
+ function applyBatchNetworkRouteState(options) {
85
+ if (!options.succeeded || !options.sessionName || !Array.isArray(options.data))
86
+ return options.routesBySession;
87
+ let routes = options.routesBySession.get(options.sessionName);
88
+ for (const item of options.data) {
89
+ if (!isRecord(item) || !Array.isArray(item.command) || !item.command.every((token) => typeof token === "string"))
90
+ continue;
91
+ routes = applyNetworkRouteRecords(routes, extractCommandTokens(item.command), item.success !== false);
92
+ }
93
+ return setNetworkRouteState({ routes, routesBySession: options.routesBySession, sessionName: options.sessionName });
94
+ }
95
+ export async function preserveParseFailureOutput(options) {
96
+ if (!options.stdoutSpillPath)
97
+ return {};
98
+ try {
99
+ const rawOutput = redactExactSensitiveText(await readFile(options.stdoutSpillPath, "utf8"), options.exactSensitiveValues ?? []);
100
+ const nowMs = Date.now();
101
+ let evictedArtifacts = [];
102
+ let fullOutputPath;
103
+ let storageScope;
104
+ if (options.persistentArtifactStore) {
105
+ const result = await writePersistentSessionArtifactFile({ content: rawOutput, prefix: "pi-agent-browser-parse-failure-output", store: options.persistentArtifactStore, suffix: ".txt" });
106
+ fullOutputPath = result.path;
107
+ evictedArtifacts = result.evictedArtifacts;
108
+ storageScope = "persistent-session";
109
+ }
110
+ else {
111
+ fullOutputPath = await writeSecureTempFile({ content: rawOutput, prefix: "pi-agent-browser-parse-failure-output", suffix: ".txt" });
112
+ storageScope = "process-temp";
113
+ }
114
+ const artifactManifest = mergeSessionArtifactManifest({
115
+ base: options.artifactManifest,
116
+ entries: [{ command: "agent-browser", createdAtMs: nowMs, kind: "spill", path: fullOutputPath, retentionState: storageScope === "persistent-session" ? "live" : "ephemeral", storageScope }, ...buildEvictedSessionArtifactEntries(evictedArtifacts, nowMs)],
117
+ nowMs,
118
+ });
119
+ return { artifactManifest, artifactRetentionSummary: artifactManifest ? formatSessionArtifactRetentionSummary(artifactManifest) : undefined, fullOutputPath };
120
+ }
121
+ catch (error) {
122
+ const message = error instanceof Error ? error.message : String(error);
123
+ return { fullOutputUnavailable: message };
124
+ }
125
+ }
126
+ export async function processBrowserOutput(input) {
127
+ const { ctx, cwd, electronPostCommandStatusSettleMs, implicitSessionCloseTimeoutMs, sessionPageStateUpdate, signal, state } = input;
128
+ const { prepared, processResult } = input;
129
+ const { electronChildProcesses, electronLaunchRecords, sessionPageState, traceOwners } = state;
130
+ let allowedDomainsBySession = state.allowedDomainsBySession;
131
+ let artifactManifest = state.artifactManifest;
132
+ let freshSessionOrdinal = state.freshSessionOrdinal;
133
+ let managedSessionActive = state.managedSessionActive;
134
+ let managedSessionCwd = state.managedSessionCwd;
135
+ let managedSessionName = state.managedSessionName;
136
+ let networkRoutesBySession = state.networkRoutesBySession;
137
+ try {
138
+ const persistentArtifactStore = getPersistentSessionArtifactStore(ctx);
139
+ const parsed = await parseAgentBrowserEnvelope({ stdout: processResult.stdout, stdoutPath: processResult.stdoutSpillPath });
140
+ let parseError = parsed.parseError;
141
+ let presentationEnvelope = parsed.envelope;
142
+ let navigationSummary = undefined;
143
+ if (prepared.pinnedBatchUnwrapMode) {
144
+ const pinnedBatchResult = unwrapPinnedSessionBatchEnvelope({ envelope: parsed.envelope, includeNavigationSummary: prepared.includePinnedNavigationSummary, mode: prepared.pinnedBatchUnwrapMode });
145
+ parseError = pinnedBatchResult.parseError ?? parseError;
146
+ presentationEnvelope = pinnedBatchResult.envelope ?? presentationEnvelope;
147
+ navigationSummary = pinnedBatchResult.navigationSummary;
148
+ }
149
+ const repairedScreenshot = await repairScreenshotArtifact({ cwd, envelope: presentationEnvelope, request: prepared.preparedArgs.screenshotPathRequest });
150
+ presentationEnvelope = repairedScreenshot.envelope;
151
+ const repairedBatchScreenshots = await repairBatchScreenshotArtifacts({ cwd, envelope: presentationEnvelope, requests: prepared.preparedArgs.batchScreenshotPathRequests });
152
+ presentationEnvelope = repairedBatchScreenshots.envelope;
153
+ const screenshotArtifactRequest = repairedScreenshot.request;
154
+ const batchScreenshotArtifactRequests = repairedBatchScreenshots.requests;
155
+ if (presentationEnvelope && prepared.exactSensitiveValues.length > 0)
156
+ presentationEnvelope = redactExactSensitiveValue(presentationEnvelope, prepared.exactSensitiveValues);
157
+ const parseFailureOutput = parseError ? await preserveParseFailureOutput({ artifactManifest, exactSensitiveValues: prepared.exactSensitiveValues, persistentArtifactStore, stdoutSpillPath: processResult.stdoutSpillPath }) : {};
158
+ const processSucceeded = !processResult.aborted && !processResult.spawnError && processResult.exitCode === 0;
159
+ const plainTextInspection = prepared.executionPlan.plainTextInspection && processSucceeded;
160
+ const parseSucceeded = plainTextInspection || parseError === undefined;
161
+ if (isStreamEnableAlreadyEnabledNoop({ command: prepared.executionPlan.commandInfo.command, envelope: presentationEnvelope, processSucceeded, subcommand: prepared.executionPlan.commandInfo.subcommand })) {
162
+ presentationEnvelope = { success: true, data: { alreadyEnabled: true, enabled: true, message: getEnvelopeErrorString(presentationEnvelope) ?? "Stream already enabled" } };
163
+ }
164
+ const envelopeSuccess = plainTextInspection ? true : presentationEnvelope?.success !== false;
165
+ let succeeded = processSucceeded && parseSucceeded && envelopeSuccess;
166
+ const inspectionText = plainTextInspection ? processResult.stdout.trim() : undefined;
167
+ updateTraceOwnerState({ command: prepared.executionPlan.commandInfo.command, sessionName: prepared.executionPlan.sessionName, subcommand: prepared.executionPlan.commandInfo.subcommand, succeeded, traceOwners });
168
+ let clickDispatchDiagnostic;
169
+ if (succeeded && prepared.clickDispatchProbe) {
170
+ clickDispatchDiagnostic = await collectClickDispatchDiagnostic({ cwd, probe: prepared.clickDispatchProbe, sessionName: prepared.executionPlan.sessionName, signal });
171
+ if (clickDispatchDiagnostic) {
172
+ succeeded = false;
173
+ presentationEnvelope = { ...(presentationEnvelope ?? {}), error: clickDispatchDiagnostic.summary, success: false };
174
+ }
175
+ }
176
+ const parsedAllowedDomainsPolicy = parseAllowedDomainsPolicyFromArgs(prepared.runtimeToolArgs);
177
+ const sessionAllowedDomainsPolicy = prepared.executionPlan.sessionName
178
+ ? parsedAllowedDomainsPolicy ?? allowedDomainsBySession.get(prepared.executionPlan.sessionName)
179
+ : parsedAllowedDomainsPolicy;
180
+ const shouldCaptureAllowedDomainNavigationSummary = prepared.executionPlan.commandInfo.command === "batch" && sessionAllowedDomainsPolicy !== undefined;
181
+ if (succeeded &&
182
+ !navigationSummary &&
183
+ (shouldCaptureNavigationSummary(prepared.executionPlan.commandInfo.command, presentationEnvelope?.data) ||
184
+ shouldCaptureSemanticActionNavigationSummary(prepared.compiledSemanticAction, presentationEnvelope?.data) ||
185
+ shouldCaptureAllowedDomainNavigationSummary)) {
186
+ navigationSummary = await collectNavigationSummary({ cwd, sessionName: prepared.executionPlan.sessionName, signal });
187
+ }
188
+ if (navigationSummary && presentationEnvelope && !Array.isArray(presentationEnvelope.data))
189
+ presentationEnvelope = { ...presentationEnvelope, data: mergeNavigationSummaryIntoData(presentationEnvelope.data, navigationSummary) };
190
+ let overlayBlockerDiagnostic;
191
+ let openResultTabCorrection;
192
+ if (succeeded && prepared.executionPlan.sessionName && hasLaunchScopedTabCorrectionFlag(prepared.runtimeToolArgs) && isOpenNavigationCommand(prepared.executionPlan.commandInfo.command)) {
193
+ const targetTitle = extractStringResultField(presentationEnvelope?.data, "title");
194
+ const targetUrl = extractStringResultField(presentationEnvelope?.data, "url");
195
+ const plannedTabCorrection = await collectOpenResultTabCorrection({ cwd, sessionName: prepared.executionPlan.sessionName, signal, targetTitle, targetUrl });
196
+ if (plannedTabCorrection)
197
+ openResultTabCorrection = await applyOpenResultTabCorrection({ correction: plannedTabCorrection, cwd, sessionName: prepared.executionPlan.sessionName, signal });
198
+ }
199
+ const observedSessionTabTarget = normalizeSessionTabTarget(navigationSummary) ?? extractSessionTabTargetFromBatchResults(presentationEnvelope?.data) ?? extractSessionTabTargetFromCommandData(prepared.commandTokens, presentationEnvelope?.data);
200
+ let currentSessionTabTarget = deriveSessionTabTarget({ command: prepared.executionPlan.commandInfo.command, data: presentationEnvelope?.data, navigationSummary, previousTarget: prepared.priorSessionTabTarget, subcommand: prepared.executionPlan.commandInfo.subcommand });
201
+ let aboutBlankSessionMismatch;
202
+ let electronPostCommandHealth;
203
+ let electronRefFreshnessDiagnostic;
204
+ let electronSessionMismatch;
205
+ let electronStatusAfterCommand;
206
+ const shouldTreatAboutBlankAsMismatch = succeeded && prepared.priorSessionTabTarget !== undefined && !isAboutBlankSessionTabTarget(prepared.priorSessionTabTarget) && isAboutBlankSessionTabTarget(observedSessionTabTarget ?? currentSessionTabTarget) && !commandExplicitlyTargetsAboutBlank(prepared.commandTokens);
207
+ let sessionTabCorrection = prepared.sessionTabCorrection;
208
+ if (shouldTreatAboutBlankAsMismatch && prepared.priorSessionTabTarget) {
209
+ const aboutBlankObservedTarget = observedSessionTabTarget ?? currentSessionTabTarget;
210
+ const aboutBlankRecovery = await collectSessionTabSelection({ cwd, sessionName: prepared.executionPlan.sessionName, signal, target: prepared.priorSessionTabTarget });
211
+ const appliedAboutBlankRecovery = aboutBlankRecovery ? await applyOpenResultTabCorrection({ correction: aboutBlankRecovery, cwd, sessionName: prepared.executionPlan.sessionName, signal }) : undefined;
212
+ if (appliedAboutBlankRecovery) {
213
+ sessionTabCorrection = appliedAboutBlankRecovery;
214
+ currentSessionTabTarget = prepared.priorSessionTabTarget;
215
+ }
216
+ else
217
+ currentSessionTabTarget = aboutBlankObservedTarget ?? normalizeSessionTabTarget({ url: "about:blank" });
218
+ aboutBlankSessionMismatch = { activeUrl: "about:blank", recoveryApplied: appliedAboutBlankRecovery !== undefined, recoveryHint: buildAboutBlankRecoveryHint(), targetTitle: prepared.priorSessionTabTarget.title, targetUrl: prepared.priorSessionTabTarget.url };
219
+ const electronRecord = findElectronLaunchRecordForSession(prepared.executionPlan.sessionName, electronLaunchRecords);
220
+ if (electronRecord && prepared.executionPlan.sessionName) {
221
+ electronStatusAfterCommand = await inspectElectronLaunchStatus(electronRecord);
222
+ electronSessionMismatch = buildElectronSessionMismatch({ managedSession: { sessionName: prepared.executionPlan.sessionName, title: aboutBlankObservedTarget?.title, url: aboutBlankObservedTarget?.url ?? "about:blank" }, record: electronRecord, statusTargets: electronStatusAfterCommand.targets });
223
+ }
224
+ }
225
+ if (succeeded && prepared.priorSessionTabTarget && !sessionTabCorrection && !aboutBlankSessionMismatch && !commandExplicitlyTargetsAboutBlank(prepared.commandTokens) && observedSessionTabTarget && shouldCorrectSessionTabAfterCommand({ command: prepared.executionPlan.commandInfo.command, pinningRequired: prepared.sessionTabPinningReason !== undefined, sessionName: prepared.executionPlan.sessionName })) {
226
+ const postCommandTabCorrection = await collectSessionTabSelection({ cwd, sessionName: prepared.executionPlan.sessionName, signal, target: observedSessionTabTarget });
227
+ if (postCommandTabCorrection) {
228
+ const appliedPostCommandCorrection = await applyOpenResultTabCorrection({ correction: postCommandTabCorrection, cwd, sessionName: prepared.executionPlan.sessionName, signal });
229
+ if (appliedPostCommandCorrection && !sessionTabCorrection)
230
+ sessionTabCorrection = appliedPostCommandCorrection;
231
+ }
232
+ }
233
+ if (succeeded && prepared.executionPlan.sessionName && parsedAllowedDomainsPolicy) {
234
+ allowedDomainsBySession = new Map(allowedDomainsBySession);
235
+ allowedDomainsBySession.set(prepared.executionPlan.sessionName, parsedAllowedDomainsPolicy);
236
+ }
237
+ const allowedDomainsViolation = succeeded ? getAllowedDomainsViolation({
238
+ policy: sessionAllowedDomainsPolicy,
239
+ url: currentSessionTabTarget?.url ?? observedSessionTabTarget?.url ?? navigationSummary?.url,
240
+ }) : undefined;
241
+ if (allowedDomainsViolation) {
242
+ succeeded = false;
243
+ presentationEnvelope = { ...(presentationEnvelope ?? {}), error: allowedDomainsViolation.summary, success: false };
244
+ }
245
+ const electronRecordForCommand = findElectronLaunchRecordForSession(prepared.executionPlan.sessionName, electronLaunchRecords);
246
+ if (succeeded && electronRecordForCommand && shouldInspectElectronPostCommandHealth(prepared.executionPlan.commandInfo.command)) {
247
+ electronStatusAfterCommand ??= await inspectElectronLaunchStatus(electronRecordForCommand);
248
+ electronPostCommandHealth = buildElectronPostCommandHealthDiagnostic({ command: prepared.executionPlan.commandInfo.command, record: electronRecordForCommand, status: electronStatusAfterCommand, target: observedSessionTabTarget ?? currentSessionTabTarget });
249
+ if (electronPostCommandHealth && electronPostCommandHealth.reason !== "process-dead") {
250
+ await sleepMs(electronPostCommandStatusSettleMs);
251
+ electronStatusAfterCommand = await inspectElectronLaunchStatus(electronRecordForCommand);
252
+ electronPostCommandHealth = buildElectronPostCommandHealthDiagnostic({ command: prepared.executionPlan.commandInfo.command, record: electronRecordForCommand, status: electronStatusAfterCommand, target: observedSessionTabTarget ?? currentSessionTabTarget });
253
+ }
254
+ if (electronPostCommandHealth)
255
+ succeeded = false;
256
+ }
257
+ let fillVerificationDiagnostic;
258
+ let selectorTextVisibilityDiagnostics = [];
259
+ let electronBroadGetTextScopeDiagnostics = [];
260
+ const timeoutPartialProgress = processResult.timedOut ? await collectTimeoutPartialProgress({ command: prepared.executionPlan.commandInfo.command, compiledJob: prepared.compiledJob, cwd, sessionName: prepared.executionPlan.sessionName, stdin: prepared.runtimeToolStdin }) : undefined;
261
+ if (succeeded) {
262
+ const fillRefSnapshot = prepared.resolvedSemanticActionRefSnapshot ?? prepared.priorRefSnapshotState;
263
+ fillVerificationDiagnostic = await collectFillVerificationDiagnostic({ commandTokens: prepared.commandTokens, cwd, forceValueVerification: electronRecordForCommand !== undefined, refSnapshot: fillRefSnapshot, sessionName: prepared.executionPlan.sessionName, signal });
264
+ }
265
+ if (succeeded && electronRecordForCommand) {
266
+ electronRefFreshnessDiagnostic = buildElectronRefFreshnessDiagnostic({ command: prepared.executionPlan.commandInfo.command, commandTokens: prepared.commandTokens, record: electronRecordForCommand, sessionName: prepared.executionPlan.sessionName, stdin: prepared.runtimeToolStdin });
267
+ }
268
+ if (succeeded && prepared.executionPlan.commandInfo.command === "snapshot") {
269
+ overlayBlockerDiagnostic = collectSnapshotOverlayBlockerDiagnostic(presentationEnvelope?.data);
270
+ }
271
+ if (succeeded && !overlayBlockerDiagnostic && !sessionTabCorrection && !aboutBlankSessionMismatch && !electronRecordForCommand && !clickDispatchDiagnostic)
272
+ overlayBlockerDiagnostic = await collectOverlayBlockerDiagnostic({ command: prepared.executionPlan.commandInfo.command, cwd, data: presentationEnvelope?.data, navigationSummary, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName, signal });
273
+ if (succeeded) {
274
+ selectorTextVisibilityDiagnostics = await collectSelectorTextVisibilityDiagnostics({ commandInfo: prepared.executionPlan.commandInfo, commandTokens: prepared.commandTokens, cwd, data: presentationEnvelope?.data, sessionName: prepared.executionPlan.sessionName, signal });
275
+ if (electronRecordForCommand)
276
+ electronBroadGetTextScopeDiagnostics = collectElectronBroadGetTextScopeDiagnostics({ commandInfo: prepared.executionPlan.commandInfo, commandTokens: prepared.commandTokens, currentTarget: currentSessionTabTarget, data: presentationEnvelope?.data, electronLaunchRecords, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName });
277
+ }
278
+ const activeNetworkRoutes = prepared.executionPlan.sessionName ? networkRoutesBySession.get(prepared.executionPlan.sessionName) : undefined;
279
+ const networkRouteDiagnostics = succeeded && prepared.executionPlan.commandInfo.command === "network" && prepared.executionPlan.commandInfo.subcommand === "requests" && prepared.executionPlan.sessionName
280
+ ? buildNetworkRouteDiagnostics(presentationEnvelope?.data, activeNetworkRoutes)
281
+ : undefined;
282
+ networkRoutesBySession = applyNetworkRouteState({ commandTokens: prepared.commandTokens, routesBySession: networkRoutesBySession, sessionName: prepared.executionPlan.sessionName, succeeded });
283
+ const comboboxFocusDiagnostic = succeeded ? await collectComboboxFocusDiagnostic({ command: prepared.executionPlan.commandInfo.command, commandTokens: prepared.commandTokens, cwd, semanticAction: prepared.compiledSemanticAction, sessionName: prepared.executionPlan.sessionName, signal }) : undefined;
284
+ const recordingDependencyWarning = await collectRecordingDependencyWarning({ command: prepared.executionPlan.commandInfo.command, commandTokens: prepared.commandTokens, succeeded });
285
+ const scrollNoopDiagnostic = succeeded && prepared.shouldProbeScrollNoop ? buildScrollNoopDiagnostic(prepared.scrollPositionBefore, await collectScrollPositionSnapshot({ cwd, sessionName: prepared.executionPlan.sessionName, signal })) : undefined;
286
+ let currentRefSnapshot;
287
+ let currentRefSnapshotInvalidation;
288
+ const batchRefSnapshotState = prepared.executionPlan.commandInfo.command === "batch" ? extractLatestRefSnapshotStateFromBatchResults(presentationEnvelope?.data) : undefined;
289
+ if (prepared.executionPlan.sessionName) {
290
+ if (isCloseCommand(prepared.executionPlan.commandInfo.command) && succeeded) {
291
+ allowedDomainsBySession = new Map(allowedDomainsBySession);
292
+ allowedDomainsBySession.delete(prepared.executionPlan.sessionName);
293
+ networkRoutesBySession = new Map(networkRoutesBySession);
294
+ networkRoutesBySession.delete(prepared.executionPlan.sessionName);
295
+ sessionPageState.clearSession(prepared.executionPlan.sessionName);
296
+ state.closedManagedSessionNames.add(prepared.executionPlan.sessionName);
297
+ }
298
+ else if (currentSessionTabTarget) {
299
+ const tabUpdate = sessionPageState.applyTabTarget({ sessionName: prepared.executionPlan.sessionName, target: currentSessionTabTarget, update: sessionPageStateUpdate });
300
+ if (!tabUpdate.applied && succeeded)
301
+ sessionPageState.markPinning(prepared.executionPlan.sessionName, "drift");
302
+ }
303
+ const refSnapshot = prepared.executionPlan.commandInfo.command === "batch" ? batchRefSnapshotState?.snapshot : succeeded ? prepared.executionPlan.commandInfo.command === "snapshot" ? extractRefSnapshotFromData(presentationEnvelope?.data) : prepared.resolvedSemanticActionRefSnapshot ?? overlayBlockerDiagnostic?.snapshot : undefined;
304
+ if (refSnapshot) {
305
+ const refUpdate = sessionPageState.applyRefSnapshot({ fallbackTarget: currentSessionTabTarget, sessionName: prepared.executionPlan.sessionName, snapshot: refSnapshot, update: sessionPageStateUpdate });
306
+ currentRefSnapshot = refUpdate.refSnapshot;
307
+ currentRefSnapshotInvalidation = refUpdate.refSnapshotInvalidation;
308
+ }
309
+ else {
310
+ const stateView = sessionPageState.get(prepared.executionPlan.sessionName);
311
+ currentRefSnapshot = stateView.refSnapshot;
312
+ currentRefSnapshotInvalidation = stateView.refSnapshotInvalidation;
313
+ }
314
+ }
315
+ const priorManagedSessionActive = managedSessionActive;
316
+ const priorManagedSessionCwd = managedSessionCwd;
317
+ const priorManagedSessionName = managedSessionName;
318
+ const commandClosesSession = isCloseCommand(prepared.executionPlan.commandInfo.command);
319
+ const managedCloseSessionName = commandClosesSession && succeeded && prepared.executionPlan.sessionName === priorManagedSessionName
320
+ ? prepared.executionPlan.sessionName
321
+ : prepared.executionPlan.managedSessionName;
322
+ const policyBlockedFreshManagedSession = allowedDomainsViolation !== undefined && prepared.sessionMode === "fresh" && prepared.executionPlan.managedSessionName === prepared.executionPlan.sessionName;
323
+ const postLaunchBatchFailure = !succeeded && processSucceeded && parseSucceeded && prepared.sessionMode === "fresh" && prepared.executionPlan.commandInfo.command === "batch" && batchStartedManagedBrowser(presentationEnvelope?.data);
324
+ const postLaunchTimeoutWithPage = !succeeded && processResult.timedOut && prepared.sessionMode === "fresh" && prepared.executionPlan.commandInfo.command === "batch" && timeoutPartialProgress?.liveUrlRecovered === true;
325
+ const managedTransitionSucceeded = succeeded || policyBlockedFreshManagedSession || postLaunchBatchFailure || postLaunchTimeoutWithPage;
326
+ const managedSessionState = resolveManagedSessionState({ command: prepared.executionPlan.commandInfo.command, managedSessionName: managedCloseSessionName, priorActive: priorManagedSessionActive, priorSessionName: priorManagedSessionName, succeeded: managedTransitionSucceeded });
327
+ const replacedManagedSessionName = managedSessionState.replacedSessionName;
328
+ managedSessionActive = managedSessionState.active;
329
+ managedSessionName = managedSessionState.sessionName;
330
+ if (commandClosesSession && succeeded && managedCloseSessionName === priorManagedSessionName && !managedSessionActive) {
331
+ freshSessionOrdinal += 1;
332
+ managedSessionName = createFreshSessionName(state.managedSessionBaseName, state.ephemeralSessionSeed, freshSessionOrdinal);
333
+ }
334
+ let managedSessionOutcome = buildManagedSessionOutcome({ activeAfter: managedSessionActive, activeBefore: priorManagedSessionActive, attemptedSessionName: managedCloseSessionName, command: prepared.executionPlan.commandInfo.command, currentSessionName: managedSessionName, previousSessionName: priorManagedSessionName, replacedSessionName: replacedManagedSessionName, sessionMode: prepared.sessionMode, succeeded: managedTransitionSucceeded });
335
+ if (prepared.executionPlan.managedSessionName && succeeded)
336
+ managedSessionCwd = cwd;
337
+ if (prepared.executionPlan.sessionName && succeeded) {
338
+ if (openResultTabCorrection || sessionTabCorrection || aboutBlankSessionMismatch?.recoveryApplied)
339
+ sessionPageState.markPinning(prepared.executionPlan.sessionName, "drift");
340
+ else if (prepared.sessionTabPinningReason === "restore")
341
+ sessionPageState.clearRestorePinning(prepared.executionPlan.sessionName);
342
+ }
343
+ if (replacedManagedSessionName) {
344
+ allowedDomainsBySession = new Map(allowedDomainsBySession);
345
+ allowedDomainsBySession.delete(replacedManagedSessionName);
346
+ networkRoutesBySession = new Map(networkRoutesBySession);
347
+ networkRoutesBySession.delete(replacedManagedSessionName);
348
+ sessionPageState.clearSession(replacedManagedSessionName);
349
+ const replacedCloseError = await closeManagedSession({ cwd: priorManagedSessionCwd, sessionName: replacedManagedSessionName, timeoutMs: implicitSessionCloseTimeoutMs });
350
+ if (!replacedCloseError)
351
+ state.closedManagedSessionNames.add(replacedManagedSessionName);
352
+ }
353
+ let electronLaunchRecord;
354
+ let electronFailedConnectCleanup = prepared.electronFailedConnectCleanup;
355
+ let electronHandoff = prepared.electronHandoff;
356
+ if (prepared.electronLaunch) {
357
+ if (succeeded && prepared.executionPlan.sessionName) {
358
+ electronLaunchRecord = { ...prepared.electronLaunch.record, sessionName: prepared.executionPlan.sessionName };
359
+ electronLaunchRecords.set(electronLaunchRecord.launchId, electronLaunchRecord);
360
+ electronChildProcesses.set(electronLaunchRecord.launchId, prepared.electronLaunch.child);
361
+ const electronHandoffMode = prepared.compiledElectron?.action === "launch" ? prepared.compiledElectron.handoff : "connect";
362
+ try {
363
+ electronHandoff = await collectElectronHandoff({ cwd, handoff: electronHandoffMode, sessionName: prepared.executionPlan.sessionName, signal });
364
+ }
365
+ catch (error) {
366
+ electronHandoff = { error: error instanceof Error ? error.message : String(error), handoff: electronHandoffMode };
367
+ }
368
+ if (electronHandoff?.refSnapshot) {
369
+ const refUpdate = sessionPageState.applyRefSnapshot({ sessionName: prepared.executionPlan.sessionName, snapshot: electronHandoff.refSnapshot, update: sessionPageStateUpdate });
370
+ currentRefSnapshot = refUpdate.refSnapshot;
371
+ currentRefSnapshotInvalidation = refUpdate.refSnapshotInvalidation;
372
+ if (electronHandoff.refSnapshot.target) {
373
+ currentSessionTabTarget = electronHandoff.refSnapshot.target;
374
+ sessionPageState.applyTabTarget({ sessionName: prepared.executionPlan.sessionName, target: electronHandoff.refSnapshot.target, update: sessionPageStateUpdate });
375
+ }
376
+ }
377
+ }
378
+ else {
379
+ electronFailedConnectCleanup = await cleanupElectronLaunchResources({ child: prepared.electronLaunch.child, record: prepared.electronLaunch.record, timeoutMs: implicitSessionCloseTimeoutMs });
380
+ electronLaunchRecord = electronFailedConnectCleanup.record;
381
+ }
382
+ }
383
+ let errorText = getAgentBrowserErrorText({ aborted: processResult.aborted, command: prepared.executionPlan.commandInfo.command, effectiveArgs: prepared.redactedProcessArgs, envelope: presentationEnvelope, exitCode: processResult.exitCode, parseError, plainTextInspection, staleRefArgs: getStaleRefArgs(prepared.commandTokens, prepared.runtimeToolStdin), spawnError: processResult.spawnError, stderr: processResult.stderr, timedOut: processResult.timedOut, timeoutMs: processResult.timeoutMs, wrapperRecoveryHint: buildWrapperRecoveryHint({ pinnedBatchUnwrapMode: prepared.pinnedBatchUnwrapMode, sessionTabCorrection }) });
384
+ if (errorText) {
385
+ const clipboardWritePayloadCandidates = getClipboardWritePayloadCandidates(prepared.commandTokens);
386
+ errorText = redactClipboardPermissionEcho(prepared.executionPlan.commandInfo, errorText);
387
+ if (presentationEnvelope?.error !== undefined)
388
+ presentationEnvelope = { ...presentationEnvelope, error: redactClipboardPermissionErrorValue(prepared.executionPlan.commandInfo, presentationEnvelope.error, clipboardWritePayloadCandidates) };
389
+ }
390
+ const presentation = plainTextInspection ? { artifacts: undefined, batchFailure: undefined, batchSteps: undefined, content: [{ type: "text", text: inspectionText ?? "" }], data: undefined, fullOutputPath: undefined, fullOutputPaths: undefined, imagePath: undefined, imagePaths: undefined, savedFile: undefined, savedFilePath: undefined, summary: `${prepared.redactedArgs.join(" ")} completed` } : await buildToolPresentation({ args: prepared.redactedProcessArgs, artifactManifest, artifactRequest: screenshotArtifactRequest, batchArtifactRequests: batchScreenshotArtifactRequests, commandInfo: prepared.executionPlan.commandInfo, compiledSemanticAction: prepared.compiledSemanticAction, cwd, envelope: presentationEnvelope, errorText, networkRouteDiagnostics, networkRoutes: activeNetworkRoutes, persistentArtifactStore, sessionName: prepared.executionPlan.sessionName });
391
+ networkRoutesBySession = applyBatchNetworkRouteState({ data: presentationEnvelope?.data, routesBySession: networkRoutesBySession, sessionName: prepared.executionPlan.sessionName, succeeded });
392
+ if (presentation.resultCategory === "failure" && succeeded) {
393
+ succeeded = false;
394
+ presentationEnvelope = { ...(presentationEnvelope ?? {}), error: presentation.summary, success: false };
395
+ }
396
+ if (scrollNoopDiagnostic) {
397
+ presentation.summary = "Scroll completed with no observed movement.";
398
+ if (isRecord(presentation.data))
399
+ presentation.data = { ...presentation.data, noMovement: true, scrolled: false };
400
+ if (presentation.content[0]?.type === "text")
401
+ presentation.content[0] = { ...presentation.content[0], text: `Scroll completed with no observed movement.\n\n${presentation.content[0].text}` };
402
+ else
403
+ presentation.content.unshift({ type: "text", text: "Scroll completed with no observed movement." });
404
+ }
405
+ if (parseFailureOutput.artifactManifest) {
406
+ presentation.artifactManifest = parseFailureOutput.artifactManifest;
407
+ presentation.artifactRetentionSummary = parseFailureOutput.artifactRetentionSummary;
408
+ }
409
+ if (parseFailureOutput.fullOutputPath || parseFailureOutput.fullOutputUnavailable) {
410
+ const existingText = presentation.content[0]?.type === "text" ? presentation.content[0].text : "";
411
+ const noticeLines = [parseFailureOutput.fullOutputPath ? `Full output path: ${parseFailureOutput.fullOutputPath}` : `Full raw output unavailable: ${parseFailureOutput.fullOutputUnavailable}`, parseFailureOutput.artifactRetentionSummary].filter((item) => item !== undefined);
412
+ const notice = noticeLines.join("\n");
413
+ presentation.content[0] = { type: "text", text: existingText.length > 0 ? `${existingText}\n\n${notice}` : notice };
414
+ }
415
+ if (presentation.artifactManifest)
416
+ artifactManifest = presentation.artifactManifest;
417
+ const qaPreset = prepared.compiledQaPreset
418
+ ? (processResult.timedOut ? analyzeQaPresetTimeout(prepared.compiledQaPreset) ?? analyzeQaPresetResults(presentationEnvelope?.data, prepared.compiledQaPreset) : analyzeQaPresetResults(presentationEnvelope?.data, prepared.compiledQaPreset))
419
+ : undefined;
420
+ let qaAttachedTarget = prepared.compiledQaPreset?.checks.attached
421
+ ? await collectQaAttachedTarget({ currentTarget: currentSessionTabTarget ?? prepared.priorSessionTabTarget, cwd, sessionName: prepared.executionPlan.sessionName, signal })
422
+ : undefined;
423
+ const sourceLookupElectronContext = prepared.compiledSourceLookup ? getSourceLookupElectronContext({ currentTarget: currentSessionTabTarget, electronLaunchRecords, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName }) : undefined;
424
+ const sourceLookup = prepared.compiledSourceLookup ? await analyzeSourceLookupResults(presentationEnvelope?.data, prepared.compiledSourceLookup, cwd, { electronContext: sourceLookupElectronContext, workspaceRoot: cwd }) : undefined;
425
+ const networkSourceLookup = prepared.compiledNetworkSourceLookup ? redactNetworkSourceLookupAnalysis(await analyzeNetworkSourceLookupResults(presentationEnvelope?.data, prepared.compiledNetworkSourceLookup, cwd)) : undefined;
426
+ if (networkSourceLookup && presentation.content[0]?.type === "text")
427
+ presentation.content[0] = { ...presentation.content[0], text: `${networkSourceLookup.summary}\n\n${presentation.content[0].text}` };
428
+ else if (networkSourceLookup)
429
+ presentation.content.unshift({ type: "text", text: networkSourceLookup.summary });
430
+ if (sourceLookup && presentation.content[0]?.type === "text")
431
+ presentation.content[0] = { ...presentation.content[0], text: `${sourceLookup.summary}\n\n${presentation.content[0].text}` };
432
+ else if (sourceLookup)
433
+ presentation.content.unshift({ type: "text", text: sourceLookup.summary });
434
+ if (qaPreset && !qaPreset.passed && presentation.failureCategory !== "artifact-missing") {
435
+ succeeded = false;
436
+ presentation.failureCategory = "qa-failure";
437
+ presentation.summary = qaPreset.summary;
438
+ if (presentation.content[0]?.type === "text")
439
+ presentation.content[0] = { ...presentation.content[0], text: `${qaPreset.summary}\n\n${presentation.content[0].text}` };
440
+ else
441
+ presentation.content.unshift({ type: "text", text: qaPreset.summary });
442
+ }
443
+ else if (qaPreset?.passed && prepared.compiledQaPreset && succeeded) {
444
+ const compactText = buildQaCompactPassText({
445
+ artifactVerification: presentation.artifactVerification,
446
+ batchStepCount: presentation.batchSteps?.length ?? prepared.compiledQaPreset.steps.length,
447
+ checks: prepared.compiledQaPreset.checks,
448
+ page: extractQaPageContext({
449
+ attachedTarget: qaAttachedTarget,
450
+ batchData: presentationEnvelope?.data,
451
+ compiled: prepared.compiledQaPreset,
452
+ }),
453
+ qaPreset,
454
+ });
455
+ presentation.summary = qaPreset.summary;
456
+ const nonTextContent = presentation.content.filter((item) => item.type !== "text");
457
+ presentation.content = [{ type: "text", text: compactText }, ...nonTextContent];
458
+ }
459
+ const qaAttachedTargetText = formatQaAttachedTargetText(qaAttachedTarget);
460
+ const qaAttachedDiagnosticsText = prepared.compiledQaPreset?.checks.attached && prepared.compiledQaPreset.checks.diagnosticsResetAtStart === false && (prepared.compiledQaPreset.checks.checkNetwork || prepared.compiledQaPreset.checks.checkConsole || prepared.compiledQaPreset.checks.checkErrors)
461
+ ? "Attached diagnostics: existing upstream session console/network/error buffers were preserved; rows may include events from before qa.attached started."
462
+ : undefined;
463
+ const qaAttachedBannerText = [qaAttachedTargetText, qaAttachedDiagnosticsText].filter((part) => typeof part === "string" && part.length > 0).join("\n");
464
+ const skipAttachedTargetBanner = qaPreset?.passed && prepared.compiledQaPreset?.checks.attached;
465
+ if (!skipAttachedTargetBanner && qaAttachedBannerText && presentation.content[0]?.type === "text")
466
+ presentation.content[0] = { ...presentation.content[0], text: `${qaAttachedBannerText}\n\n${presentation.content[0].text}` };
467
+ else if (!skipAttachedTargetBanner && qaAttachedBannerText)
468
+ presentation.content.unshift({ type: "text", text: qaAttachedBannerText });
469
+ if (managedSessionOutcome && managedSessionOutcome.succeeded !== succeeded)
470
+ managedSessionOutcome = { ...managedSessionOutcome, succeeded };
471
+ const evalNavigationSummary = navigationSummary ?? extractNavigationSummaryFromData(presentationEnvelope?.data);
472
+ const evalSessionTabUrl = prepared.executionPlan.sessionName ? sessionPageState.get(prepared.executionPlan.sessionName).tabTarget?.url : undefined;
473
+ const evalPageUrl = evalNavigationSummary?.url ?? currentSessionTabTarget?.url ?? prepared.priorSessionTabTarget?.url ?? evalSessionTabUrl;
474
+ const evalStdinHint = getEvalStdinHint({ command: prepared.executionPlan.commandInfo.command, data: presentationEnvelope?.data, stdin: prepared.runtimeToolStdin });
475
+ const evalResultWarning = getEvalResultWarning({ command: prepared.executionPlan.commandInfo.command, data: presentationEnvelope?.data, navigationSummary: evalNavigationSummary, pageUrl: evalPageUrl, stdin: prepared.runtimeToolStdin });
476
+ const resultArtifactManifest = presentation.artifactManifest ?? artifactManifest;
477
+ const artifactCleanup = await getArtifactCleanupGuidance({ command: prepared.executionPlan.commandInfo.command, cwd, manifest: resultArtifactManifest, succeeded });
478
+ const warningText = electronPostCommandHealth ? formatElectronPostCommandHealthText(electronPostCommandHealth) : electronSessionMismatch ? formatElectronSessionMismatchText(electronSessionMismatch) : aboutBlankSessionMismatch ? buildAboutBlankWarning(aboutBlankSessionMismatch) : undefined;
479
+ const redactedContent = buildRedactedPresentationContent({ exactSensitiveValues: prepared.exactSensitiveValues, plainTextInspection, presentation, presentationEnvelope, succeeded, userRequestedJson: prepared.userRequestedJson, warningText });
480
+ const finalRecoveryState = await prepareFinalResultRecoveryState({ aboutBlankSessionMismatch, batchRefSnapshotState, commandTokens: prepared.commandTokens, compiledSemanticAction: prepared.compiledSemanticAction, currentRefSnapshot, currentRefSnapshotInvalidation, currentSessionTabTarget, cwd, electronPostCommandHealth, errorText, executionPlan: prepared.executionPlan, parseError, plainTextInspection, presentation, processResult, redactedProcessArgs: prepared.redactedProcessArgs, runtimeToolArgs: prepared.runtimeToolArgs, sessionPageState, sessionPageStateUpdate, sessionTabCorrection, signal, succeeded });
481
+ currentRefSnapshot = finalRecoveryState.currentRefSnapshot;
482
+ currentRefSnapshotInvalidation = finalRecoveryState.currentRefSnapshotInvalidation;
483
+ const result = buildFinalAgentBrowserToolResult({ aboutBlankSessionMismatch, artifactCleanup, categoryDetails: finalRecoveryState.categoryDetails, clickDispatchDiagnostic, commandTokens: prepared.commandTokens, comboboxFocusDiagnostic, compiledNetworkSourceLookup: prepared.compiledNetworkSourceLookup, compiledSemanticAction: prepared.compiledSemanticAction, compatibilityWorkaround: prepared.compatibilityWorkaround, currentRefSnapshot, currentRefSnapshotInvalidation, currentSessionTabTarget, electronBroadGetTextScopeDiagnostics, electronFailedConnectCleanup, electronHandoff, electronLaunch: prepared.electronLaunch, electronLaunchRecord, electronLaunchRecords, electronPostCommandHealth, electronProfileIsolationDetails: input.electronProfileIsolationDetails, electronRefFreshnessDiagnostic, electronSessionMismatch, errorText, evalResultWarning, evalStdinHint, exactSensitiveValues: prepared.exactSensitiveValues, executionPlan: prepared.executionPlan, fillVerificationDiagnostic, inspectionText, managedSessionOutcome, navigationSummary, networkSourceLookup, noActivePageSnapshotFailure: finalRecoveryState.noActivePageSnapshotFailure, openResultTabCorrection, overlayBlockerDiagnostic, parseError, parseFailureOutput, parseSucceeded, plainTextInspection, presentation, presentationEnvelope, priorSessionTabTarget: prepared.priorSessionTabTarget, processResult, qaAttachedTarget, qaPreset, recordingDependencyWarning, redactedArgs: prepared.redactedArgs, redactedCompiledElectron: prepared.redactedCompiledElectron, redactedCompiledJob: prepared.redactedCompiledJob, redactedCompiledNetworkSourceLookup: prepared.redactedCompiledNetworkSourceLookup, redactedCompiledQaPreset: prepared.redactedCompiledQaPreset, redactedCompiledSemanticAction: prepared.redactedCompiledSemanticAction, redactedCompiledSourceLookup: prepared.redactedCompiledSourceLookup, redactedContent, redactedProcessArgs: prepared.redactedProcessArgs, redactedRecoveryHint: prepared.redactedRecoveryHint, resultArtifactManifest, richInputRecoveryDiagnostic: finalRecoveryState.richInputRecoveryDiagnostic, scrollNoopDiagnostic, selectorTextVisibilityDiagnostics, sessionMode: prepared.sessionMode, sessionTabCorrection, sourceLookup, succeeded, timeoutPartialProgress, userRequestedJson: prepared.userRequestedJson, visibleRefFallbackDiagnostic: finalRecoveryState.visibleRefFallbackDiagnostic, visibleRefFallbackSessionName: finalRecoveryState.visibleRefFallbackSessionName });
484
+ const statePatch = { allowedDomainsBySession, artifactManifest, freshSessionOrdinal, managedSessionActive, managedSessionCwd, managedSessionName, networkRoutesBySession };
485
+ return { result, statePatch };
486
+ }
487
+ finally {
488
+ if (processResult.stdoutSpillPath)
489
+ await rm(processResult.stdoutSpillPath, { force: true }).catch(() => undefined);
490
+ }
491
+ }
@@ -0,0 +1,40 @@
1
+ import { isAbsolute, resolve } from "node:path";
2
+ import { isCloseCommand } from "../../command-taxonomy.js";
3
+ import { executableExistsOnPath } from "../../executable-path.js";
4
+ function resolveArtifactPath(cwd, path) {
5
+ return isAbsolute(path) ? path : resolve(cwd, path);
6
+ }
7
+ function manifestContainsArtifact(manifest, cwd, artifact) {
8
+ if (!manifest)
9
+ return false;
10
+ const requestedAbsolutePath = resolveArtifactPath(cwd, artifact.path);
11
+ const expectedKind = artifact.kind === "screenshot" ? "image" : "video";
12
+ return manifest.entries.some((entry) => {
13
+ const entryAbsolutePath = entry.absolutePath ?? resolveArtifactPath(cwd, entry.path);
14
+ return entry.storageScope === "explicit-path" && entry.kind === expectedKind && entryAbsolutePath === requestedAbsolutePath && entry.retentionState === "live" && entry.exists === true;
15
+ });
16
+ }
17
+ async function isArtifactRequired(artifact) {
18
+ if (artifact.required)
19
+ return true;
20
+ return artifact.kind === "recording" && await executableExistsOnPath("ffmpeg");
21
+ }
22
+ export async function findRequestedArtifactCloseViolation(options) {
23
+ if (!isCloseCommand(options.command))
24
+ return undefined;
25
+ const missingArtifacts = [];
26
+ for (const artifact of options.promptPolicy.requestedArtifacts) {
27
+ if (!await isArtifactRequired(artifact))
28
+ continue;
29
+ if (!manifestContainsArtifact(options.artifactManifest, options.cwd, artifact))
30
+ missingArtifacts.push(artifact);
31
+ }
32
+ if (missingArtifacts.length === 0)
33
+ return undefined;
34
+ const missingList = missingArtifacts.map((artifact) => `${artifact.kind}: ${artifact.path}`).join(", ");
35
+ return {
36
+ message: `Blocked browser close because requested artifact path${missingArtifacts.length === 1 ? " is" : "s are"} missing or unverified: ${missingList}. Save the requested artifact path first, or report why an optional artifact is unavailable before closing.`,
37
+ missingArtifacts,
38
+ reason: "requested-artifacts-missing-before-close",
39
+ };
40
+ }
@@ -0,0 +1,5 @@
1
+ export function getPersistentSessionArtifactStore(ctx) {
2
+ const sessionDir = typeof ctx.sessionManager.getSessionDir === "function" ? ctx.sessionManager.getSessionDir() : undefined;
3
+ const sessionId = ctx.sessionManager.getSessionId();
4
+ return sessionDir && sessionId ? { sessionDir, sessionId } : undefined;
5
+ }