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,141 @@
1
+ import { mkdir, stat, writeFile } from "node:fs/promises";
2
+ import { dirname, resolve } from "node:path";
3
+ import { isRecord } from "../../../parsing.js";
4
+ import { buildAgentBrowserResultCategoryDetails } from "../../../results.js";
5
+ import { formatSessionArtifactRetentionSummary, mergeSessionArtifactManifest } from "../../../results/artifact-manifest.js";
6
+ import { redactSensitiveText } from "../../../runtime.js";
7
+ import { buildSessionDetailFields, runSessionCommandData } from "../session-state.js";
8
+ const DIRECT_ANCHOR_DOWNLOAD_MAX_BYTES = 2 * 1024 * 1024;
9
+ function getDirectDownloadRequest(commandTokens) {
10
+ if (commandTokens[0] !== "download" || commandTokens.length !== 3)
11
+ return undefined;
12
+ const selector = commandTokens[1];
13
+ const path = commandTokens[2];
14
+ if (!selector || !path || selector.startsWith("@"))
15
+ return undefined;
16
+ return { path, selector };
17
+ }
18
+ function buildAnchorDownloadProbe(selector) {
19
+ return `(async () => {\n const selector = ${JSON.stringify(selector)};\n const maxBytes = ${DIRECT_ANCHOR_DOWNLOAD_MAX_BYTES};\n const isLoopbackHttpUrl = (url) => (url.protocol === "http:" || url.protocol === "https:") && (url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]");\n const element = document.querySelector(selector);\n const anchor = element?.closest?.("a[href]");\n const pageUrl = location.href;\n const page = new URL(pageUrl);\n if (!anchor) return { status: "no-anchor", pageUrl };\n const href = anchor.href;\n const anchorUrl = new URL(href, pageUrl);\n if (!isLoopbackHttpUrl(page)) return { download: anchor.getAttribute("download") || "", href, pageUrl, status: "not-loopback-page" };\n if (anchorUrl.origin !== page.origin) return { download: anchor.getAttribute("download") || "", href, pageUrl, status: "not-same-origin" };\n if (!isLoopbackHttpUrl(anchorUrl)) return { download: anchor.getAttribute("download") || "", href, pageUrl, status: "not-loopback-href" };\n const response = await fetch(anchorUrl.href, { credentials: "include", redirect: "manual" });\n if (!response.ok) return { download: anchor.getAttribute("download") || "", href, pageUrl, responseUrl: response.url, status: "fetch-failed", statusCode: response.status };\n const responseUrl = new URL(response.url);\n if (!isLoopbackHttpUrl(responseUrl) || responseUrl.origin !== page.origin) return { download: anchor.getAttribute("download") || "", href, pageUrl, responseUrl: response.url, status: "not-loopback-response" };\n const buffer = await response.arrayBuffer();\n if (buffer.byteLength > maxBytes) return { download: anchor.getAttribute("download") || "", href, pageUrl, responseUrl: response.url, sizeBytes: buffer.byteLength, status: "too-large" };\n const bytes = new Uint8Array(buffer);\n let binary = "";\n for (let index = 0; index < bytes.length; index += 32768) binary += String.fromCharCode(...bytes.subarray(index, index + 32768));\n return { bodyBase64: btoa(binary), contentType: response.headers.get("content-type") || "", download: anchor.getAttribute("download") || "", href, pageUrl, responseUrl: response.url, sizeBytes: buffer.byteLength, status: "fetched-anchor" };\n})()`;
20
+ }
21
+ function isLoopbackHttpUrl(url) {
22
+ return (url.protocol === "http:" || url.protocol === "https:") && (url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]");
23
+ }
24
+ export async function tryDirectAnchorDownload(options) {
25
+ const request = getDirectDownloadRequest(options.commandTokens);
26
+ if (!request || !options.sessionName)
27
+ return undefined;
28
+ try {
29
+ const probeData = await runSessionCommandData({
30
+ args: ["eval", "--stdin"],
31
+ cwd: options.cwd,
32
+ sessionName: options.sessionName,
33
+ signal: options.signal,
34
+ stdin: buildAnchorDownloadProbe(request.selector),
35
+ });
36
+ const probe = isRecord(probeData) && isRecord(probeData.result) ? probeData.result : probeData;
37
+ if (!isRecord(probe) || probe.status !== "fetched-anchor" || typeof probe.href !== "string" || typeof probe.pageUrl !== "string" || typeof probe.bodyBase64 !== "string")
38
+ return undefined;
39
+ const href = new URL(probe.href);
40
+ const pageUrl = new URL(probe.pageUrl);
41
+ const responseUrl = typeof probe.responseUrl === "string" ? new URL(probe.responseUrl) : href;
42
+ if (!isLoopbackHttpUrl(pageUrl) || !isLoopbackHttpUrl(href) || !isLoopbackHttpUrl(responseUrl) || href.origin !== pageUrl.origin || responseUrl.origin !== pageUrl.origin)
43
+ return undefined;
44
+ const body = Buffer.from(probe.bodyBase64, "base64");
45
+ if (body.byteLength > DIRECT_ANCHOR_DOWNLOAD_MAX_BYTES)
46
+ return undefined;
47
+ if (typeof probe.sizeBytes === "number" && probe.sizeBytes !== body.byteLength)
48
+ return undefined;
49
+ const absolutePath = resolve(options.cwd, request.path);
50
+ await mkdir(dirname(absolutePath), { recursive: true });
51
+ await writeFile(absolutePath, body);
52
+ const fileStat = await stat(absolutePath);
53
+ const mediaType = typeof probe.contentType === "string" && probe.contentType.length > 0 ? probe.contentType : undefined;
54
+ const manifestEntry = {
55
+ absolutePath,
56
+ command: "download",
57
+ createdAtMs: Date.now(),
58
+ cwd: options.cwd,
59
+ exists: true,
60
+ kind: "download",
61
+ mediaType,
62
+ path: absolutePath,
63
+ requestedPath: request.path,
64
+ retentionState: "live",
65
+ session: options.sessionName,
66
+ sizeBytes: fileStat.size,
67
+ storageScope: "explicit-path",
68
+ };
69
+ const artifactManifest = mergeSessionArtifactManifest({ base: options.artifactManifest, entries: [manifestEntry] });
70
+ const artifactRetentionSummary = artifactManifest ? formatSessionArtifactRetentionSummary(artifactManifest) : undefined;
71
+ const artifact = {
72
+ absolutePath,
73
+ artifactType: "download",
74
+ command: "download",
75
+ cwd: options.cwd,
76
+ exists: true,
77
+ kind: "download",
78
+ mediaType,
79
+ path: absolutePath,
80
+ requestedPath: request.path,
81
+ session: options.sessionName,
82
+ sizeBytes: fileStat.size,
83
+ status: "saved",
84
+ };
85
+ const artifactVerification = {
86
+ artifacts: [{
87
+ absolutePath,
88
+ exists: true,
89
+ kind: "download",
90
+ mediaType,
91
+ path: absolutePath,
92
+ requestedPath: request.path,
93
+ sizeBytes: fileStat.size,
94
+ state: "verified",
95
+ status: "saved",
96
+ }],
97
+ missingCount: 0,
98
+ pendingCount: 0,
99
+ unverifiedCount: 0,
100
+ verified: true,
101
+ verifiedCount: 1,
102
+ };
103
+ const savedFile = { command: "download", kind: "download", metadata: { download: probe.download, href: redactSensitiveText(href.href), method: "direct-anchor-fetch" }, path: absolutePath };
104
+ return {
105
+ artifactManifest,
106
+ result: {
107
+ content: [{
108
+ type: "text",
109
+ text: [
110
+ `Download completed: ${absolutePath}`,
111
+ `Requested path: ${request.path}`,
112
+ `Source: ${redactSensitiveText(href.href)}`,
113
+ `Size: ${fileStat.size} bytes`,
114
+ "Method: direct anchor fetch before upstream download fallback.",
115
+ ].join("\n"),
116
+ }],
117
+ details: {
118
+ args: options.redactedArgs,
119
+ artifactManifest,
120
+ artifactRetentionSummary,
121
+ artifacts: [artifact],
122
+ artifactVerification,
123
+ command: "download",
124
+ compatibilityWorkaround: options.compatibilityWorkaround,
125
+ downloadRecovery: { href: redactSensitiveText(href.href), method: "direct-anchor-fetch", selector: request.selector },
126
+ effectiveArgs: options.effectiveArgs,
127
+ savedFile,
128
+ savedFilePath: absolutePath,
129
+ sessionMode: options.sessionMode,
130
+ ...buildAgentBrowserResultCategoryDetails({ artifacts: [artifact], args: options.effectiveArgs, command: "download", savedFile, succeeded: true }),
131
+ ...buildSessionDetailFields(options.sessionName, options.usedImplicitSession),
132
+ summary: `Download completed: ${absolutePath}`,
133
+ },
134
+ isError: false,
135
+ },
136
+ };
137
+ }
138
+ catch {
139
+ return undefined;
140
+ }
141
+ }
@@ -0,0 +1,108 @@
1
+ import { isRecord } from "../../../parsing.js";
2
+ import { buildAgentBrowserResultCategoryDetails } from "../../../results.js";
3
+ import { redactSensitiveText } from "../../../runtime.js";
4
+ import { buildSessionDetailFields, runSessionCommandData } from "../session-state.js";
5
+ function parseNetworkRequestsPageFilterRequest(commandTokens) {
6
+ if (commandTokens[0] !== "network" || commandTokens[1] !== "requests")
7
+ return undefined;
8
+ const cleanArgs = [];
9
+ let mode;
10
+ for (const token of commandTokens) {
11
+ if (token === "--current-page" || token === "--current-origin") {
12
+ mode = "origin";
13
+ continue;
14
+ }
15
+ if (token === "--current-url") {
16
+ mode = "url";
17
+ continue;
18
+ }
19
+ cleanArgs.push(token);
20
+ }
21
+ if (!mode)
22
+ return undefined;
23
+ return { cleanArgs, mode };
24
+ }
25
+ function extractCurrentUrl(data) {
26
+ if (typeof data === "string")
27
+ return data;
28
+ if (!isRecord(data))
29
+ return undefined;
30
+ const candidates = [data.url, data.currentUrl, data.href, data.result];
31
+ for (const candidate of candidates)
32
+ if (typeof candidate === "string" && candidate.length > 0)
33
+ return candidate;
34
+ return undefined;
35
+ }
36
+ function getRequestUrl(row) {
37
+ if (!isRecord(row))
38
+ return undefined;
39
+ const candidate = row.url ?? row.requestUrl ?? row.href;
40
+ return typeof candidate === "string" ? candidate : undefined;
41
+ }
42
+ function requestMatchesCurrentPage(row, currentUrl, mode) {
43
+ const requestUrl = getRequestUrl(row);
44
+ if (!requestUrl)
45
+ return false;
46
+ try {
47
+ const current = new URL(currentUrl);
48
+ const request = new URL(requestUrl, current);
49
+ if (mode === "origin")
50
+ return current.origin === request.origin;
51
+ const currentComparable = `${current.origin}${current.pathname}`;
52
+ const requestComparable = `${request.origin}${request.pathname}`;
53
+ return requestComparable === currentComparable;
54
+ }
55
+ catch {
56
+ return mode === "url" ? requestUrl === currentUrl : requestUrl.startsWith(currentUrl);
57
+ }
58
+ }
59
+ function filterNetworkRequestsData(data, currentUrl, request) {
60
+ if (!isRecord(data))
61
+ return undefined;
62
+ const requestRows = Array.isArray(data.requests) ? data.requests : Array.isArray(data.items) ? data.items : Array.isArray(data.entries) ? data.entries : undefined;
63
+ if (!requestRows)
64
+ return undefined;
65
+ const rows = requestRows.filter((row) => requestMatchesCurrentPage(row, currentUrl, request.mode));
66
+ const key = Array.isArray(data.requests) ? "requests" : Array.isArray(data.items) ? "items" : "entries";
67
+ return { data: { ...data, [key]: rows }, matchedRows: rows.length, rows, totalRows: requestRows.length };
68
+ }
69
+ function formatNetworkRequestRow(row) {
70
+ if (!isRecord(row))
71
+ return redactSensitiveText(String(row));
72
+ const status = row.status ?? row.statusCode ?? row.responseStatus ?? "?";
73
+ const method = typeof row.method === "string" ? row.method : typeof row.requestMethod === "string" ? row.requestMethod : "?";
74
+ const id = typeof row.id === "string" ? ` id=${row.id}` : typeof row.requestId === "string" ? ` id=${row.requestId}` : "";
75
+ const url = getRequestUrl(row) ?? "(no url)";
76
+ return redactSensitiveText(`- ${status} ${method}${id} ${url}`);
77
+ }
78
+ export async function tryNetworkRequestsPageFilter(options) {
79
+ const request = parseNetworkRequestsPageFilterRequest(options.commandTokens);
80
+ if (!request || !options.sessionName)
81
+ return undefined;
82
+ const currentUrl = extractCurrentUrl(await runSessionCommandData({ args: ["get", "url"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal }));
83
+ if (!currentUrl)
84
+ return undefined;
85
+ const networkData = await runSessionCommandData({ args: request.cleanArgs, cwd: options.cwd, sessionName: options.sessionName, signal: options.signal });
86
+ const filtered = filterNetworkRequestsData(networkData, currentUrl, request);
87
+ if (!filtered)
88
+ return undefined;
89
+ const summary = `Network requests filtered to current ${request.mode === "origin" ? "origin" : "URL"}: ${filtered.matchedRows}/${filtered.totalRows} rows matched.`;
90
+ const preview = filtered.rows.slice(0, 12).map(formatNetworkRequestRow);
91
+ const omitted = filtered.rows.length > preview.length ? [`- …${filtered.rows.length - preview.length} more matching rows omitted`] : [];
92
+ return {
93
+ content: [{ type: "text", text: [redactSensitiveText(summary), `Current page: ${redactSensitiveText(currentUrl)}`, ...preview, ...omitted].join("\n") }],
94
+ details: {
95
+ args: options.redactedArgs,
96
+ command: "network",
97
+ compatibilityWorkaround: options.compatibilityWorkaround,
98
+ data: filtered.data,
99
+ effectiveArgs: options.effectiveArgs,
100
+ networkRequestsPageFilter: { cleanArgs: request.cleanArgs, currentUrl: redactSensitiveText(currentUrl), matchedRows: filtered.matchedRows, mode: request.mode, totalRows: filtered.totalRows },
101
+ sessionMode: options.sessionMode,
102
+ ...buildAgentBrowserResultCategoryDetails({ args: options.effectiveArgs, command: "network", succeeded: true }),
103
+ ...buildSessionDetailFields(options.sessionName, options.usedImplicitSession),
104
+ summary,
105
+ },
106
+ isError: false,
107
+ };
108
+ }
@@ -0,0 +1,112 @@
1
+ import { isRecord } from "../../../parsing.js";
2
+ import { buildAgentBrowserResultCategoryDetails } from "../../../results.js";
3
+ import { buildScrollNoopNextActions } from "../diagnostics.js";
4
+ import { buildSessionDetailFields, runSessionCommandData } from "../session-state.js";
5
+ const SCROLL_CONTAINER_DIRECTIONS = new Set(["down", "left", "right", "up"]);
6
+ function getContainerScrollRequest(commandTokens) {
7
+ if (commandTokens[0] !== "scroll" || commandTokens.length < 3)
8
+ return undefined;
9
+ const selector = commandTokens[1];
10
+ const direction = commandTokens[2]?.toLowerCase();
11
+ if (!selector || selector.startsWith("-") || selector.startsWith("@") || SCROLL_CONTAINER_DIRECTIONS.has(selector.toLowerCase()))
12
+ return undefined;
13
+ if (!SCROLL_CONTAINER_DIRECTIONS.has(direction))
14
+ return undefined;
15
+ return { amount: commandTokens[3], direction, selector };
16
+ }
17
+ function buildContainerScrollScript(request) {
18
+ return `(() => {
19
+ const selector = ${JSON.stringify(request.selector)};
20
+ const direction = ${JSON.stringify(request.direction)};
21
+ const amountToken = ${JSON.stringify(request.amount ?? "")};
22
+ let element;
23
+ try { element = document.querySelector(selector); } catch (error) { return { status: "invalid-selector", selector, error: String(error && error.message || error) }; }
24
+ if (!(element instanceof HTMLElement)) return { status: "not-found", selector };
25
+ const axis = direction === "left" || direction === "right" ? "x" : "y";
26
+ const before = { scrollLeft: element.scrollLeft, scrollTop: element.scrollTop, scrollHeight: element.scrollHeight, scrollWidth: element.scrollWidth, clientHeight: element.clientHeight, clientWidth: element.clientWidth };
27
+ const parseAmount = () => {
28
+ const token = String(amountToken || "").trim().toLowerCase();
29
+ const extent = axis === "x" ? element.clientWidth : element.clientHeight;
30
+ if (!token) return Math.max(1, Math.floor(extent * 0.8));
31
+ if (token.endsWith("%")) {
32
+ const value = Number(token.slice(0, -1));
33
+ return Number.isFinite(value) ? Math.max(1, Math.floor(extent * value / 100)) : Math.max(1, Math.floor(extent * 0.8));
34
+ }
35
+ const pixels = Number(token.replace(/px$/, ""));
36
+ return Number.isFinite(pixels) && pixels > 0 ? Math.floor(pixels) : Math.max(1, Math.floor(extent * 0.8));
37
+ };
38
+ const delta = parseAmount() * (direction === "up" || direction === "left" ? -1 : 1);
39
+ if (axis === "x") element.scrollLeft += delta;
40
+ else element.scrollTop += delta;
41
+ const after = { scrollLeft: element.scrollLeft, scrollTop: element.scrollTop, scrollHeight: element.scrollHeight, scrollWidth: element.scrollWidth, clientHeight: element.clientHeight, clientWidth: element.clientWidth };
42
+ const moved = before.scrollLeft !== after.scrollLeft || before.scrollTop !== after.scrollTop;
43
+ return { status: moved ? "scrolled" : "no-movement", selector, direction, amount: amountToken || undefined, before, after };
44
+ })()`;
45
+ }
46
+ function buildScrollResult(options) {
47
+ return {
48
+ content: [{ type: "text", text: options.message }],
49
+ details: {
50
+ args: options.redactedArgs,
51
+ command: options.command,
52
+ compatibilityWorkaround: options.compatibilityWorkaround,
53
+ data: options.result,
54
+ effectiveArgs: options.effectiveArgs,
55
+ nextActions: options.succeeded ? undefined : buildScrollNoopNextActions(options.sessionName),
56
+ [options.scrollField]: options.scrollValue,
57
+ sessionMode: options.sessionMode,
58
+ ...buildAgentBrowserResultCategoryDetails({ args: options.effectiveArgs, command: options.command, errorText: options.succeeded ? undefined : options.message, succeeded: options.succeeded, validationError: options.succeeded ? undefined : options.message }),
59
+ ...buildSessionDetailFields(options.sessionName, options.usedImplicitSession),
60
+ summary: options.message,
61
+ validationError: options.succeeded ? undefined : options.message,
62
+ },
63
+ isError: !options.succeeded,
64
+ };
65
+ }
66
+ export async function tryContainerScroll(options) {
67
+ const request = getContainerScrollRequest(options.commandTokens);
68
+ if (!request || !options.sessionName)
69
+ return undefined;
70
+ const data = await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal, stdin: buildContainerScrollScript(request) });
71
+ const result = isRecord(data) && isRecord(data.result) ? data.result : data;
72
+ if (!isRecord(result) || typeof result.status !== "string")
73
+ return undefined;
74
+ const succeeded = result.status === "scrolled";
75
+ const message = succeeded
76
+ ? `Scrolled container ${request.selector} ${request.direction}${request.amount ? ` by ${request.amount}` : ""}.`
77
+ : `Scroll container ${request.selector} did not move (${result.status}).`;
78
+ return buildScrollResult({ ...options, command: "scroll", message, result, scrollField: "scrollContainer", scrollValue: { request, result }, succeeded });
79
+ }
80
+ function getPageScrollToRequest(commandTokens) {
81
+ if (commandTokens[0] !== "scroll" || commandTokens[1]?.toLowerCase() !== "to")
82
+ return undefined;
83
+ const target = commandTokens[2]?.toLowerCase();
84
+ return target === "end" || target === "top" ? { target } : undefined;
85
+ }
86
+ function buildPageScrollToScript(request) {
87
+ return `(() => {
88
+ const target = ${JSON.stringify(request.target)};
89
+ const scroller = document.scrollingElement || document.documentElement || document.body;
90
+ if (!scroller) return { status: "no-scroller", target };
91
+ const before = { scrollLeft: scroller.scrollLeft, scrollTop: scroller.scrollTop, scrollHeight: scroller.scrollHeight, scrollWidth: scroller.scrollWidth, clientHeight: scroller.clientHeight, clientWidth: scroller.clientWidth };
92
+ const nextTop = target === "top" ? 0 : Math.max(0, scroller.scrollHeight - scroller.clientHeight);
93
+ const nextLeft = scroller.scrollLeft;
94
+ scroller.scrollTop = nextTop;
95
+ window.scrollTo(nextLeft, nextTop);
96
+ const after = { scrollLeft: scroller.scrollLeft, scrollTop: scroller.scrollTop, scrollHeight: scroller.scrollHeight, scrollWidth: scroller.scrollWidth, clientHeight: scroller.clientHeight, clientWidth: scroller.clientWidth };
97
+ const moved = before.scrollLeft !== after.scrollLeft || before.scrollTop !== after.scrollTop;
98
+ return { status: moved ? "scrolled" : "no-movement", target, before, after };
99
+ })()`;
100
+ }
101
+ export async function tryPageScrollTo(options) {
102
+ const request = getPageScrollToRequest(options.commandTokens);
103
+ if (!request || !options.sessionName)
104
+ return undefined;
105
+ const data = await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal, stdin: buildPageScrollToScript(request) });
106
+ const result = isRecord(data) && isRecord(data.result) ? data.result : data;
107
+ if (!isRecord(result) || typeof result.status !== "string")
108
+ return undefined;
109
+ const succeeded = result.status === "scrolled";
110
+ const message = succeeded ? `Scrolled page to ${request.target}.` : `Scroll to ${request.target} completed with no observed movement (${result.status}).`;
111
+ return buildScrollResult({ ...options, command: "scroll", message, result, scrollField: "scrollPage", scrollValue: { request, result }, succeeded });
112
+ }
@@ -0,0 +1,158 @@
1
+ import { isRecord } from "../../../parsing.js";
2
+ import { buildAgentBrowserResultCategoryDetails } from "../../../results.js";
3
+ import { buildSnapshotPresentation } from "../../../results/snapshot.js";
4
+ import { extractRefSnapshotFromData } from "../../../session-page-state.js";
5
+ import { collectScrollPositionSnapshot } from "../diagnostics.js";
6
+ import { buildSessionDetailFields, runSessionCommandData } from "../session-state.js";
7
+ function parseSnapshotFilterRequest(commandTokens) {
8
+ if (commandTokens[0] !== "snapshot")
9
+ return undefined;
10
+ const cleanArgs = [];
11
+ let role;
12
+ let search;
13
+ for (let index = 0; index < commandTokens.length; index += 1) {
14
+ const token = commandTokens[index];
15
+ if (token === "--viewport")
16
+ continue;
17
+ if (token === "--diff")
18
+ continue;
19
+ if (token === "--search") {
20
+ const value = commandTokens[index + 1];
21
+ if (typeof value === "string" && !value.startsWith("-")) {
22
+ search = value;
23
+ index += 1;
24
+ continue;
25
+ }
26
+ }
27
+ if (token === "--filter") {
28
+ const value = commandTokens[index + 1];
29
+ if (typeof value === "string" && !value.startsWith("-")) {
30
+ const roleMatch = /^role=(.+)$/i.exec(value.trim());
31
+ if (roleMatch?.[1])
32
+ role = roleMatch[1].trim().toLowerCase();
33
+ index += 1;
34
+ continue;
35
+ }
36
+ }
37
+ cleanArgs.push(token);
38
+ }
39
+ const viewport = commandTokens.includes("--viewport");
40
+ const diff = commandTokens.includes("--diff");
41
+ if (!search && !role && !viewport && !diff)
42
+ return undefined;
43
+ return { cleanArgs, diff, role, search, viewport };
44
+ }
45
+ function buildSnapshotDiff(previous, current) {
46
+ if (!current)
47
+ return undefined;
48
+ const currentRefs = current.refs ?? {};
49
+ const previousRefs = previous?.refs ?? {};
50
+ if (!previous)
51
+ return { addedRefs: Object.keys(currentRefs), changedRefs: [], removedRefs: [], summary: `Snapshot diff: no previous snapshot; ${Object.keys(currentRefs).length} current refs recorded.`, unchangedRefs: 0 };
52
+ const addedRefs = [];
53
+ const removedRefs = [];
54
+ const changedRefs = [];
55
+ let unchangedRefs = 0;
56
+ for (const refId of Object.keys(currentRefs)) {
57
+ const currentRef = currentRefs[refId];
58
+ const previousRef = previousRefs[refId];
59
+ if (!previousRef) {
60
+ addedRefs.push(refId);
61
+ continue;
62
+ }
63
+ if (previousRef.role !== currentRef.role || previousRef.name !== currentRef.name)
64
+ changedRefs.push(refId);
65
+ else
66
+ unchangedRefs += 1;
67
+ }
68
+ for (const refId of Object.keys(previousRefs))
69
+ if (!currentRefs[refId])
70
+ removedRefs.push(refId);
71
+ return { addedRefs, changedRefs, removedRefs, summary: `Snapshot diff: +${addedRefs.length} / -${removedRefs.length} / Δ${changedRefs.length} refs versus previous snapshot.`, unchangedRefs };
72
+ }
73
+ function filterSnapshotData(data, request) {
74
+ if (!isRecord(data))
75
+ return undefined;
76
+ const refs = isRecord(data.refs) ? data.refs : {};
77
+ const snapshot = typeof data.snapshot === "string" ? data.snapshot : "";
78
+ const normalizedSearch = request.search?.trim().toLowerCase();
79
+ const matchingRefIds = new Set();
80
+ for (const [refId, refValue] of Object.entries(refs)) {
81
+ if (!isRecord(refValue))
82
+ continue;
83
+ const role = typeof refValue.role === "string" ? refValue.role.toLowerCase() : "";
84
+ const name = typeof refValue.name === "string" ? refValue.name : "";
85
+ const roleMatches = request.role ? role === request.role : true;
86
+ const searchMatches = normalizedSearch ? `${role} ${name}`.toLowerCase().includes(normalizedSearch) : true;
87
+ if (roleMatches && searchMatches)
88
+ matchingRefIds.add(refId);
89
+ }
90
+ const lines = snapshot.split(/\r?\n/);
91
+ const visibleLines = lines.filter((line) => {
92
+ const normalizedLine = line.toLowerCase();
93
+ if (normalizedSearch && normalizedLine.includes(normalizedSearch))
94
+ return true;
95
+ return [...matchingRefIds].some((refId) => line.includes(`[ref=${refId}]`) || line.includes(`ref=${refId}`));
96
+ });
97
+ const filteredRefs = Object.fromEntries(Object.entries(refs).filter(([refId]) => matchingRefIds.has(refId)));
98
+ const description = [request.role ? `role=${request.role}` : undefined, request.search ? `search=${JSON.stringify(request.search)}` : undefined].filter((part) => part !== undefined).join(", ");
99
+ const filteredSnapshot = visibleLines.length > 0 ? visibleLines.join("\n") : `(no snapshot lines matched ${description})`;
100
+ return {
101
+ data: { ...data, refs: filteredRefs, snapshot: filteredSnapshot },
102
+ matchedRefs: Object.keys(filteredRefs).length,
103
+ totalRefs: Object.keys(refs).length,
104
+ totalLines: lines.filter((line) => line.length > 0).length,
105
+ visibleLines: visibleLines.length,
106
+ };
107
+ }
108
+ export async function trySnapshotFilter(options) {
109
+ const request = parseSnapshotFilterRequest(options.commandTokens);
110
+ if (!request || !options.sessionName)
111
+ return undefined;
112
+ const snapshotData = await runSessionCommandData({ args: request.cleanArgs, cwd: options.cwd, sessionName: options.sessionName, signal: options.signal });
113
+ const filtered = request.role || request.search ? filterSnapshotData(snapshotData, request) : isRecord(snapshotData) ? { data: snapshotData, matchedRefs: isRecord(snapshotData.refs) ? Object.keys(snapshotData.refs).length : 0, totalLines: typeof snapshotData.snapshot === "string" ? snapshotData.snapshot.split(/\r?\n/).filter((line) => line.length > 0).length : 0, totalRefs: isRecord(snapshotData.refs) ? Object.keys(snapshotData.refs).length : 0, visibleLines: typeof snapshotData.snapshot === "string" ? snapshotData.snapshot.split(/\r?\n/).filter((line) => line.length > 0).length : 0 } : undefined;
114
+ if (!filtered)
115
+ return undefined;
116
+ const viewport = request.viewport ? await collectScrollPositionSnapshot({ cwd: options.cwd, sessionName: options.sessionName, signal: options.signal }) : undefined;
117
+ const fullSnapshot = extractRefSnapshotFromData(snapshotData);
118
+ const diff = request.diff ? buildSnapshotDiff(options.previousRefSnapshot, fullSnapshot) : undefined;
119
+ if (fullSnapshot)
120
+ options.sessionPageState.applyRefSnapshot({ sessionName: options.sessionName, snapshot: fullSnapshot, update: options.sessionPageStateUpdate });
121
+ const presentation = await buildSnapshotPresentation(filtered.data, options.persistentArtifactStore, options.artifactManifest);
122
+ const summary = request.role || request.search
123
+ ? `Snapshot filter: ${filtered.matchedRefs}/${filtered.totalRefs} direct refs matched${request.role ? ` role=${request.role}` : ""}${request.search ? ` search ${JSON.stringify(request.search)}` : ""}; ${filtered.visibleLines} surrounding snapshot line${filtered.visibleLines === 1 ? "" : "s"} shown.`
124
+ : request.diff
125
+ ? diff?.summary ?? "Snapshot diff unavailable."
126
+ : "Snapshot viewport metadata collected.";
127
+ const viewportText = viewport ? `Viewport: ${viewport.innerWidth}×${viewport.innerHeight}, scroll ${viewport.scrollX},${viewport.scrollY}, document ${viewport.scrollWidth}×${viewport.scrollHeight}, sampled scroll containers ${viewport.containers.length}/${viewport.containerCount}.` : undefined;
128
+ const diffText = diff && (request.role || request.search) ? diff.summary : undefined;
129
+ const prefix = [summary, diffText, viewportText].filter((line) => line !== undefined).join("\n");
130
+ if (presentation.content[0]?.type === "text")
131
+ presentation.content[0] = { ...presentation.content[0], text: `${prefix}\n\n${presentation.content[0].text}` };
132
+ return {
133
+ artifactManifest: presentation.artifactManifest,
134
+ result: {
135
+ content: presentation.content,
136
+ details: {
137
+ args: options.redactedArgs,
138
+ artifactManifest: presentation.artifactManifest,
139
+ artifactRetentionSummary: presentation.artifactRetentionSummary,
140
+ command: "snapshot",
141
+ compatibilityWorkaround: options.compatibilityWorkaround,
142
+ data: presentation.data,
143
+ effectiveArgs: options.effectiveArgs,
144
+ fullOutputPath: presentation.fullOutputPath,
145
+ fullOutputPaths: presentation.fullOutputPaths,
146
+ refSnapshot: fullSnapshot,
147
+ sessionMode: options.sessionMode,
148
+ snapshotDiff: diff,
149
+ snapshotFilter: request.role || request.search ? { cleanArgs: request.cleanArgs, matchedRefs: filtered.matchedRefs, role: request.role, search: request.search, totalLines: filtered.totalLines, totalRefs: filtered.totalRefs, visibleLines: filtered.visibleLines } : undefined,
150
+ snapshotViewport: viewport,
151
+ ...buildAgentBrowserResultCategoryDetails({ args: options.effectiveArgs, command: "snapshot", succeeded: true }),
152
+ ...buildSessionDetailFields(options.sessionName, options.usedImplicitSession),
153
+ summary,
154
+ },
155
+ isError: false,
156
+ },
157
+ };
158
+ }
@@ -0,0 +1,54 @@
1
+ import { getAgentBrowserProcessTimeoutMs } from "../../../process.js";
2
+ import { parseValidBatchStepEntries } from "../../batch-stdin.js";
3
+ const WAIT_PROCESS_TIMEOUT_GRACE_MS = 5_000;
4
+ function parseMillisecondsToken(token) {
5
+ if (token === undefined || !/^\d+$/.test(token)) {
6
+ return undefined;
7
+ }
8
+ const parsed = Number(token);
9
+ return Number.isSafeInteger(parsed) ? parsed : undefined;
10
+ }
11
+ function findWaitTimeoutMs(commandTokens) {
12
+ if (commandTokens[0] !== "wait") {
13
+ return undefined;
14
+ }
15
+ for (let index = 1; index < commandTokens.length; index += 1) {
16
+ const token = commandTokens[index];
17
+ if (token === "--timeout") {
18
+ return parseMillisecondsToken(commandTokens[index + 1]);
19
+ }
20
+ if (token.startsWith("--timeout=")) {
21
+ return parseMillisecondsToken(token.slice("--timeout=".length));
22
+ }
23
+ }
24
+ const firstWaitArgument = commandTokens[1];
25
+ if (firstWaitArgument && !firstWaitArgument.startsWith("-")) {
26
+ return parseMillisecondsToken(firstWaitArgument);
27
+ }
28
+ return undefined;
29
+ }
30
+ function findWaitTimeoutBudgetMs(commandTokens, stdin) {
31
+ const directWaitTimeout = findWaitTimeoutMs(commandTokens);
32
+ if (directWaitTimeout !== undefined) {
33
+ return directWaitTimeout;
34
+ }
35
+ if (commandTokens[0] !== "batch" || stdin === undefined) {
36
+ return undefined;
37
+ }
38
+ let batchWaitTimeoutTotal = 0;
39
+ for (const { step } of parseValidBatchStepEntries(stdin)) {
40
+ const waitTimeout = findWaitTimeoutMs(step);
41
+ if (waitTimeout !== undefined) {
42
+ batchWaitTimeoutTotal += waitTimeout;
43
+ }
44
+ }
45
+ return batchWaitTimeoutTotal === 0 ? undefined : batchWaitTimeoutTotal;
46
+ }
47
+ export function getWaitAwareProcessTimeoutMs(commandTokens, stdin) {
48
+ const waitTimeoutBudgetMs = findWaitTimeoutBudgetMs(commandTokens, stdin);
49
+ if (waitTimeoutBudgetMs === undefined)
50
+ return undefined;
51
+ const neededTimeoutMs = waitTimeoutBudgetMs + WAIT_PROCESS_TIMEOUT_GRACE_MS;
52
+ const defaultProcessTimeoutMs = getAgentBrowserProcessTimeoutMs();
53
+ return neededTimeoutMs > defaultProcessTimeoutMs ? neededTimeoutMs : undefined;
54
+ }