pi-agent-browser-native 0.9.3 → 0.9.5

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 (394) hide show
  1. package/CHANGELOG.md +106 -5
  2. package/README.md +58 -791
  3. package/dist/extensions/agent-browser/index.js +43101 -2031
  4. package/dist/extensions/agent-browser/lib/argv-descriptor.js +49 -26
  5. package/dist/extensions/agent-browser/lib/argv-grammar.js +82 -93
  6. package/dist/extensions/agent-browser/lib/bash-guard.js +129 -126
  7. package/dist/extensions/agent-browser/lib/batch-lifecycle.js +112 -51
  8. package/dist/extensions/agent-browser/lib/browser-execution-claim-codec.js +61 -0
  9. package/dist/extensions/agent-browser/lib/browser-execution-claim-storage.js +232 -0
  10. package/dist/extensions/agent-browser/lib/browser-execution-claims.js +202 -0
  11. package/dist/extensions/agent-browser/lib/browser-execution-identity.js +77 -0
  12. package/dist/extensions/agent-browser/lib/browser-journal-branch.js +98 -0
  13. package/dist/extensions/agent-browser/lib/browser-journal-contracts.js +1 -0
  14. package/dist/extensions/agent-browser/lib/browser-journal-projection.js +221 -0
  15. package/dist/extensions/agent-browser/lib/browser-journal-reader.js +89 -0
  16. package/dist/extensions/agent-browser/lib/browser-journal-replay.js +246 -0
  17. package/dist/extensions/agent-browser/lib/browser-journal.js +103 -394
  18. package/dist/extensions/agent-browser/lib/browser-legacy-pages.js +220 -0
  19. package/dist/extensions/agent-browser/lib/browser-legacy-projection.js +282 -0
  20. package/dist/extensions/agent-browser/lib/browser-legacy-types.js +1 -0
  21. package/dist/extensions/agent-browser/lib/browser-session-conversion-stream.js +217 -0
  22. package/dist/extensions/agent-browser/lib/browser-session-conversion.js +80 -335
  23. package/dist/extensions/agent-browser/lib/browser-transcript-contracts.js +35 -0
  24. package/dist/extensions/agent-browser/lib/browser-transcript-validation.js +166 -0
  25. package/dist/extensions/agent-browser/lib/browser-transcript.js +137 -82
  26. package/dist/extensions/agent-browser/lib/browser-value-parsing.js +15 -0
  27. package/dist/extensions/agent-browser/lib/command-capabilities.js +340 -0
  28. package/dist/extensions/agent-browser/lib/command-policy.js +107 -88
  29. package/dist/extensions/agent-browser/lib/command-taxonomy.js +49 -322
  30. package/dist/extensions/agent-browser/lib/config-policy.js +138 -343
  31. package/dist/extensions/agent-browser/lib/config-providers.js +71 -0
  32. package/dist/extensions/agent-browser/lib/config-validation.js +231 -0
  33. package/dist/extensions/agent-browser/lib/config.js +20 -8
  34. package/dist/extensions/agent-browser/lib/electron/cdp.js +20 -8
  35. package/dist/extensions/agent-browser/lib/electron/cleanup.js +128 -83
  36. package/dist/extensions/agent-browser/lib/electron/discovery-darwin.js +109 -0
  37. package/dist/extensions/agent-browser/lib/electron/discovery-desktop.js +136 -0
  38. package/dist/extensions/agent-browser/lib/electron/discovery-evidence.js +90 -0
  39. package/dist/extensions/agent-browser/lib/electron/discovery-linux.js +93 -0
  40. package/dist/extensions/agent-browser/lib/electron/discovery-packages.js +162 -0
  41. package/dist/extensions/agent-browser/lib/electron/discovery-types.js +2 -0
  42. package/dist/extensions/agent-browser/lib/electron/discovery.js +140 -549
  43. package/dist/extensions/agent-browser/lib/electron/launch-monitor.js +102 -0
  44. package/dist/extensions/agent-browser/lib/electron/launch-process.js +209 -0
  45. package/dist/extensions/agent-browser/lib/electron/launch-types.js +2 -0
  46. package/dist/extensions/agent-browser/lib/electron/launch.js +261 -355
  47. package/dist/extensions/agent-browser/lib/executable-path.js +21 -12
  48. package/dist/extensions/agent-browser/lib/execution-cwd.js +35 -18
  49. package/dist/extensions/agent-browser/lib/fs-utils.js +1 -9
  50. package/dist/extensions/agent-browser/lib/input-modes/electron.js +162 -121
  51. package/dist/extensions/agent-browser/lib/input-modes/job.js +123 -317
  52. package/dist/extensions/agent-browser/lib/input-modes/lookup-network-evidence.js +116 -0
  53. package/dist/extensions/agent-browser/lib/input-modes/lookup-plan.js +132 -0
  54. package/dist/extensions/agent-browser/lib/input-modes/lookup-redaction.js +57 -0
  55. package/dist/extensions/agent-browser/lib/input-modes/lookup-source-evidence.js +151 -0
  56. package/dist/extensions/agent-browser/lib/input-modes/lookup-workspace.js +142 -0
  57. package/dist/extensions/agent-browser/lib/input-modes/lookups.js +58 -412
  58. package/dist/extensions/agent-browser/lib/input-modes/params.js +86 -40
  59. package/dist/extensions/agent-browser/lib/input-modes/qa-analysis.js +268 -0
  60. package/dist/extensions/agent-browser/lib/input-modes/qa-presentation.js +117 -0
  61. package/dist/extensions/agent-browser/lib/input-modes/script-child.js +396 -0
  62. package/dist/extensions/agent-browser/lib/input-modes/script-protocol.js +86 -0
  63. package/dist/extensions/agent-browser/lib/input-modes/script-types.js +1 -0
  64. package/dist/extensions/agent-browser/lib/input-modes/script-validation.js +60 -0
  65. package/dist/extensions/agent-browser/lib/input-modes/script.js +60 -409
  66. package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +218 -128
  67. package/dist/extensions/agent-browser/lib/input-modes/shared.js +15 -4
  68. package/dist/extensions/agent-browser/lib/input-modes/types.js +56 -7
  69. package/dist/extensions/agent-browser/lib/json-schema.js +22 -10
  70. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +17 -5
  71. package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +92 -425
  72. package/dist/extensions/agent-browser/lib/managed-session-posix-storage.js +58 -0
  73. package/dist/extensions/agent-browser/lib/managed-session-project-identity.js +244 -0
  74. package/dist/extensions/agent-browser/lib/managed-session-restore-context.js +84 -0
  75. package/dist/extensions/agent-browser/lib/managed-session-restore-policy.js +119 -0
  76. package/dist/extensions/agent-browser/lib/managed-session-restore-state.js +83 -0
  77. package/dist/extensions/agent-browser/lib/managed-session-restore.js +168 -300
  78. package/dist/extensions/agent-browser/lib/managed-session-snapshot-records.js +277 -0
  79. package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +128 -305
  80. package/dist/extensions/agent-browser/lib/managed-session-storage.js +56 -253
  81. package/dist/extensions/agent-browser/lib/native-confirmation.js +140 -0
  82. package/dist/extensions/agent-browser/lib/native-output.js +43 -9
  83. package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +106 -59
  84. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-merge.js +14 -6
  85. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +109 -57
  86. package/dist/extensions/agent-browser/lib/orchestration/browser-run/browser-observation-result.js +82 -0
  87. package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +164 -60
  88. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostic-values.js +31 -0
  89. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +152 -781
  90. package/dist/extensions/agent-browser/lib/orchestration/browser-run/electron-observation.js +203 -0
  91. package/dist/extensions/agent-browser/lib/orchestration/browser-run/electron-text-diagnostics.js +109 -0
  92. package/dist/extensions/agent-browser/lib/orchestration/browser-run/fill-diagnostics.js +119 -0
  93. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-action-recovery.js +147 -0
  94. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-actions.js +144 -0
  95. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-content.js +123 -0
  96. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-contracts.js +1 -0
  97. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-details.js +156 -0
  98. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-electron.js +187 -0
  99. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-evidence.js +39 -0
  100. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-missing-binary.js +89 -0
  101. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-recovery.js +97 -0
  102. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-redaction.js +106 -0
  103. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-timeout.js +110 -0
  104. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +41 -538
  105. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +199 -154
  106. package/dist/extensions/agent-browser/lib/orchestration/browser-run/interaction-diagnostics.js +290 -0
  107. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-close.js +167 -0
  108. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-inspection.js +108 -0
  109. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +127 -205
  110. package/dist/extensions/agent-browser/lib/orchestration/browser-run/native-helper-observation.js +65 -0
  111. package/dist/extensions/agent-browser/lib/orchestration/browser-run/observation-types.js +1 -0
  112. package/dist/extensions/agent-browser/lib/orchestration/browser-run/overlay-diagnostics.js +120 -0
  113. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +113 -30
  114. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +109 -28
  115. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter-data.js +150 -0
  116. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +147 -253
  117. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-rendered-text.js +147 -0
  118. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +59 -24
  119. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-artifacts.js +159 -0
  120. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-command.js +131 -0
  121. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-contracts.js +1 -0
  122. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-daemon.js +93 -0
  123. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-failure.js +42 -0
  124. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-guards.js +242 -0
  125. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-input.js +75 -0
  126. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-observations.js +92 -0
  127. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-page-selection.js +254 -0
  128. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-page-state.js +121 -0
  129. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-refs.js +130 -0
  130. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-resources.js +114 -0
  131. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-semantic.js +121 -0
  132. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-session-plan.js +222 -0
  133. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-timeouts.js +68 -0
  134. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +176 -1123
  135. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-analysis.js +191 -0
  136. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-confirmation.js +152 -0
  137. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-diagnostics.js +182 -0
  138. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-electron.js +150 -0
  139. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-lifecycle-contracts.js +1 -0
  140. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-lifecycle-phase-contracts.js +1 -0
  141. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-lifecycle-phase.js +115 -0
  142. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-lifecycle.js +256 -0
  143. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-managed-contracts.js +1 -0
  144. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-managed.js +256 -0
  145. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-native-phase-contracts.js +1 -0
  146. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-native-phase.js +93 -0
  147. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-native.js +113 -0
  148. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-navigation-contracts.js +1 -0
  149. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-navigation.js +316 -0
  150. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-ownership.js +28 -0
  151. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-page-phase-contracts.js +1 -0
  152. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-page-phase.js +142 -0
  153. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-presentation-contracts.js +1 -0
  154. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-presentation.js +333 -0
  155. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-publication-phase-contracts.js +1 -0
  156. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-publication-phase.js +71 -0
  157. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-publication.js +201 -0
  158. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-receipts.js +183 -0
  159. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-refs.js +200 -0
  160. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-tabs.js +142 -0
  161. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +18 -968
  162. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.js +23 -13
  163. package/dist/extensions/agent-browser/lib/orchestration/browser-run/recording-receipt-reconciliation.js +198 -0
  164. package/dist/extensions/agent-browser/lib/orchestration/browser-run/recording-recovery-contracts.js +1 -0
  165. package/dist/extensions/agent-browser/lib/orchestration/browser-run/recording-recovery-presentation.js +154 -0
  166. package/dist/extensions/agent-browser/lib/orchestration/browser-run/recording-recovery.js +68 -151
  167. package/dist/extensions/agent-browser/lib/orchestration/browser-run/screenshot-capture.js +14 -0
  168. package/dist/extensions/agent-browser/lib/orchestration/browser-run/screenshot-observation.js +169 -46
  169. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state-commands.js +130 -0
  170. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state-electron.js +212 -0
  171. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state-observations.js +61 -0
  172. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state-outcomes.js +187 -0
  173. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state-refs.js +169 -0
  174. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state-tabs.js +228 -0
  175. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +77 -765
  176. package/dist/extensions/agent-browser/lib/orchestration/browser-run/text-visibility-diagnostics.js +182 -0
  177. package/dist/extensions/agent-browser/lib/orchestration/browser-run/timeout-diagnostics.js +256 -0
  178. package/dist/extensions/agent-browser/lib/orchestration/browser-run/timeout-progress.js +108 -0
  179. package/dist/extensions/agent-browser/lib/orchestration/code-artifact-observation.js +39 -0
  180. package/dist/extensions/agent-browser/lib/orchestration/code-image-observation.js +119 -0
  181. package/dist/extensions/agent-browser/lib/orchestration/electron-host/branch.js +93 -0
  182. package/dist/extensions/agent-browser/lib/orchestration/electron-host/cleanup-result.js +58 -0
  183. package/dist/extensions/agent-browser/lib/orchestration/electron-host/cleanup.js +78 -0
  184. package/dist/extensions/agent-browser/lib/orchestration/electron-host/contracts.js +1 -0
  185. package/dist/extensions/agent-browser/lib/orchestration/electron-host/discovery.js +106 -0
  186. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +48 -821
  187. package/dist/extensions/agent-browser/lib/orchestration/electron-host/policy.js +86 -0
  188. package/dist/extensions/agent-browser/lib/orchestration/electron-host/probe-collection.js +68 -0
  189. package/dist/extensions/agent-browser/lib/orchestration/electron-host/probe-data.js +169 -0
  190. package/dist/extensions/agent-browser/lib/orchestration/electron-host/probe-presentation.js +182 -0
  191. package/dist/extensions/agent-browser/lib/orchestration/electron-host/probe.js +161 -0
  192. package/dist/extensions/agent-browser/lib/orchestration/electron-host/status.js +167 -0
  193. package/dist/extensions/agent-browser/lib/orchestration/execution-queue.js +22 -10
  194. package/dist/extensions/agent-browser/lib/orchestration/extension-artifact-preflight.js +211 -0
  195. package/dist/extensions/agent-browser/lib/orchestration/extension-artifacts.js +23 -0
  196. package/dist/extensions/agent-browser/lib/orchestration/extension-attachment-replay.js +99 -0
  197. package/dist/extensions/agent-browser/lib/orchestration/extension-branch-restore.js +275 -0
  198. package/dist/extensions/agent-browser/lib/orchestration/extension-code.js +280 -0
  199. package/dist/extensions/agent-browser/lib/orchestration/extension-command-admission.js +303 -0
  200. package/dist/extensions/agent-browser/lib/orchestration/extension-command-output.js +36 -0
  201. package/dist/extensions/agent-browser/lib/orchestration/extension-command-result.js +335 -0
  202. package/dist/extensions/agent-browser/lib/orchestration/extension-command-run.js +227 -0
  203. package/dist/extensions/agent-browser/lib/orchestration/extension-command-state.js +1 -0
  204. package/dist/extensions/agent-browser/lib/orchestration/extension-electron-ownership.js +204 -0
  205. package/dist/extensions/agent-browser/lib/orchestration/extension-execution-locks.js +139 -0
  206. package/dist/extensions/agent-browser/lib/orchestration/extension-execution-queue.js +24 -0
  207. package/dist/extensions/agent-browser/lib/orchestration/extension-host-coordination.js +98 -0
  208. package/dist/extensions/agent-browser/lib/orchestration/extension-host-run.js +295 -0
  209. package/dist/extensions/agent-browser/lib/orchestration/extension-intent-replay.js +41 -0
  210. package/dist/extensions/agent-browser/lib/orchestration/extension-invocation.js +7 -0
  211. package/dist/extensions/agent-browser/lib/orchestration/extension-lifecycle.js +102 -0
  212. package/dist/extensions/agent-browser/lib/orchestration/extension-managed-ownership.js +133 -0
  213. package/dist/extensions/agent-browser/lib/orchestration/extension-observation.js +120 -0
  214. package/dist/extensions/agent-browser/lib/orchestration/extension-prompt.js +180 -0
  215. package/dist/extensions/agent-browser/lib/orchestration/extension-recording.js +290 -0
  216. package/dist/extensions/agent-browser/lib/orchestration/extension-resource-contracts.js +1 -0
  217. package/dist/extensions/agent-browser/lib/orchestration/extension-resource-replay.js +269 -0
  218. package/dist/extensions/agent-browser/lib/orchestration/extension-result-state.js +113 -0
  219. package/dist/extensions/agent-browser/lib/orchestration/extension-runtime.js +146 -0
  220. package/dist/extensions/agent-browser/lib/orchestration/extension-script-leases.js +58 -0
  221. package/dist/extensions/agent-browser/lib/orchestration/extension-tool-boundary.js +62 -0
  222. package/dist/extensions/agent-browser/lib/orchestration/extension-version-check.js +54 -0
  223. package/dist/extensions/agent-browser/lib/orchestration/input-plan-types.js +1 -0
  224. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +261 -185
  225. package/dist/extensions/agent-browser/lib/orchestration/input-redaction.js +62 -0
  226. package/dist/extensions/agent-browser/lib/orchestration/native-config.js +144 -0
  227. package/dist/extensions/agent-browser/lib/orchestration/native-root-launch.js +77 -0
  228. package/dist/extensions/agent-browser/lib/orchestration/native-session-defaults.js +210 -157
  229. package/dist/extensions/agent-browser/lib/orchestration/operation-paths.js +124 -58
  230. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +197 -99
  231. package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +199 -96
  232. package/dist/extensions/agent-browser/lib/owned-managed-session-context.js +55 -0
  233. package/dist/extensions/agent-browser/lib/page-target-batch.js +118 -0
  234. package/dist/extensions/agent-browser/lib/page-target-guards.js +50 -0
  235. package/dist/extensions/agent-browser/lib/page-target-navigation.js +104 -0
  236. package/dist/extensions/agent-browser/lib/page-target-validation.js +55 -246
  237. package/dist/extensions/agent-browser/lib/parsing.js +13 -4
  238. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +133 -68
  239. package/dist/extensions/agent-browser/lib/playbook.js +16 -12
  240. package/dist/extensions/agent-browser/lib/process-child.js +284 -0
  241. package/dist/extensions/agent-browser/lib/process-environment.js +13 -6
  242. package/dist/extensions/agent-browser/lib/process-errors.js +23 -0
  243. package/dist/extensions/agent-browser/lib/process-identity.js +47 -15
  244. package/dist/extensions/agent-browser/lib/process-socket-storage.js +256 -0
  245. package/dist/extensions/agent-browser/lib/process-stdout.js +74 -0
  246. package/dist/extensions/agent-browser/lib/process.js +145 -536
  247. package/dist/extensions/agent-browser/lib/prompt-artifacts.js +287 -0
  248. package/dist/extensions/agent-browser/lib/prompt-policy.js +15 -194
  249. package/dist/extensions/agent-browser/lib/read-command.js +41 -0
  250. package/dist/extensions/agent-browser/lib/read-confirmation.js +131 -68
  251. package/dist/extensions/agent-browser/lib/recording-reservations.js +118 -45
  252. package/dist/extensions/agent-browser/lib/redaction-fields.js +75 -0
  253. package/dist/extensions/agent-browser/lib/redaction-json.js +149 -0
  254. package/dist/extensions/agent-browser/lib/redaction-url.js +88 -0
  255. package/dist/extensions/agent-browser/lib/results/action-contracts.js +1 -0
  256. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +28 -271
  257. package/dist/extensions/agent-browser/lib/results/artifact-contracts.js +1 -0
  258. package/dist/extensions/agent-browser/lib/results/artifact-manifest-validation.js +133 -0
  259. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +96 -94
  260. package/dist/extensions/agent-browser/lib/results/artifact-recommendations.js +73 -0
  261. package/dist/extensions/agent-browser/lib/results/categories.js +111 -54
  262. package/dist/extensions/agent-browser/lib/results/confirmation.js +29 -13
  263. package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +62 -27
  264. package/dist/extensions/agent-browser/lib/results/electron-recommendations.js +83 -0
  265. package/dist/extensions/agent-browser/lib/results/envelope-decoding.js +48 -0
  266. package/dist/extensions/agent-browser/lib/results/envelope.js +90 -67
  267. package/dist/extensions/agent-browser/lib/results/evidence-contracts.js +1 -0
  268. package/dist/extensions/agent-browser/lib/results/failure-recommendations.js +180 -0
  269. package/dist/extensions/agent-browser/lib/results/network-routes.js +86 -45
  270. package/dist/extensions/agent-browser/lib/results/network.js +35 -12
  271. package/dist/extensions/agent-browser/lib/results/next-actions.js +49 -19
  272. package/dist/extensions/agent-browser/lib/results/presentation/artifact-files.js +320 -0
  273. package/dist/extensions/agent-browser/lib/results/presentation/artifact-format.js +138 -0
  274. package/dist/extensions/agent-browser/lib/results/presentation/artifact-images.js +113 -0
  275. package/dist/extensions/agent-browser/lib/results/presentation/artifact-verification.js +145 -0
  276. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +99 -555
  277. package/dist/extensions/agent-browser/lib/results/presentation/batch-contracts.js +1 -0
  278. package/dist/extensions/agent-browser/lib/results/presentation/batch-recording.js +118 -0
  279. package/dist/extensions/agent-browser/lib/results/presentation/batch-redaction.js +68 -0
  280. package/dist/extensions/agent-browser/lib/results/presentation/batch-rendering.js +135 -0
  281. package/dist/extensions/agent-browser/lib/results/presentation/batch-step.js +227 -0
  282. package/dist/extensions/agent-browser/lib/results/presentation/batch-summary.js +91 -0
  283. package/dist/extensions/agent-browser/lib/results/presentation/batch.js +91 -460
  284. package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +9 -5
  285. package/dist/extensions/agent-browser/lib/results/presentation/command-presenters.js +216 -0
  286. package/dist/extensions/agent-browser/lib/results/presentation/common.js +26 -9
  287. package/dist/extensions/agent-browser/lib/results/presentation/content.js +86 -29
  288. package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-a11y.js +67 -0
  289. package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-health.js +79 -0
  290. package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-identity.js +199 -0
  291. package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-logs.js +75 -0
  292. package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-network-actions.js +216 -0
  293. package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-network.js +124 -0
  294. package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-redaction.js +64 -0
  295. package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-storage.js +209 -0
  296. package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-stream.js +69 -0
  297. package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-summary.js +222 -0
  298. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +103 -1052
  299. package/dist/extensions/agent-browser/lib/results/presentation/early-result.js +70 -0
  300. package/dist/extensions/agent-browser/lib/results/presentation/errors.js +130 -60
  301. package/dist/extensions/agent-browser/lib/results/presentation/input-contracts.js +1 -0
  302. package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +60 -145
  303. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +147 -88
  304. package/dist/extensions/agent-browser/lib/results/presentation/notices.js +75 -0
  305. package/dist/extensions/agent-browser/lib/results/presentation/observation-contracts.js +1 -0
  306. package/dist/extensions/agent-browser/lib/results/presentation/observation-rendering.js +127 -0
  307. package/dist/extensions/agent-browser/lib/results/presentation/outcomes.js +120 -0
  308. package/dist/extensions/agent-browser/lib/results/presentation/output-completion.js +77 -0
  309. package/dist/extensions/agent-browser/lib/results/presentation/output-spill.js +51 -0
  310. package/dist/extensions/agent-browser/lib/results/presentation/registry.js +116 -240
  311. package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +35 -21
  312. package/dist/extensions/agent-browser/lib/results/presentation/skills.js +109 -73
  313. package/dist/extensions/agent-browser/lib/results/presentation/source.js +109 -0
  314. package/dist/extensions/agent-browser/lib/results/presentation.js +17 -261
  315. package/dist/extensions/agent-browser/lib/results/recommendation-contracts.js +1 -0
  316. package/dist/extensions/agent-browser/lib/results/recording.js +52 -14
  317. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +14 -5
  318. package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +30 -22
  319. package/dist/extensions/agent-browser/lib/results/selector-recovery-target.js +83 -0
  320. package/dist/extensions/agent-browser/lib/results/selector-recovery.js +119 -119
  321. package/dist/extensions/agent-browser/lib/results/snapshot-compact.js +266 -0
  322. package/dist/extensions/agent-browser/lib/results/snapshot-content.js +32 -0
  323. package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +104 -71
  324. package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +31 -11
  325. package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +125 -66
  326. package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +26 -7
  327. package/dist/extensions/agent-browser/lib/results/snapshot.js +28 -248
  328. package/dist/extensions/agent-browser/lib/results/text.js +16 -5
  329. package/dist/extensions/agent-browser/lib/runtime-args-validation.js +133 -0
  330. package/dist/extensions/agent-browser/lib/runtime-compatibility.js +81 -0
  331. package/dist/extensions/agent-browser/lib/runtime-contracts.js +1 -0
  332. package/dist/extensions/agent-browser/lib/runtime-redaction.js +118 -0
  333. package/dist/extensions/agent-browser/lib/runtime-replay-events.js +144 -0
  334. package/dist/extensions/agent-browser/lib/runtime-session-identity.js +76 -0
  335. package/dist/extensions/agent-browser/lib/runtime-session-plan.js +114 -0
  336. package/dist/extensions/agent-browser/lib/runtime-session-replay.js +123 -0
  337. package/dist/extensions/agent-browser/lib/runtime-tab-correction.js +65 -0
  338. package/dist/extensions/agent-browser/lib/runtime-timeouts.js +28 -0
  339. package/dist/extensions/agent-browser/lib/runtime.js +137 -1041
  340. package/dist/extensions/agent-browser/lib/session-identity.js +51 -0
  341. package/dist/extensions/agent-browser/lib/session-page-observation.js +357 -0
  342. package/dist/extensions/agent-browser/lib/session-page-state.js +178 -333
  343. package/dist/extensions/agent-browser/lib/session-page-types.js +1 -0
  344. package/dist/extensions/agent-browser/lib/temp-artifact-budget.js +81 -0
  345. package/dist/extensions/agent-browser/lib/temp-mutation-queue.js +7 -0
  346. package/dist/extensions/agent-browser/lib/temp-root-ownership.js +234 -0
  347. package/dist/extensions/agent-browser/lib/temp-root.js +211 -0
  348. package/dist/extensions/agent-browser/lib/temp.js +33 -446
  349. package/dist/extensions/agent-browser/lib/tool-surface.js +94 -24
  350. package/dist/extensions/agent-browser/lib/upstream-version.js +3 -2
  351. package/dist/extensions/agent-browser/lib/web-search-providers.js +193 -0
  352. package/dist/extensions/agent-browser/lib/web-search-request.js +102 -0
  353. package/dist/extensions/agent-browser/lib/web-search-results.js +281 -0
  354. package/dist/extensions/agent-browser/lib/web-search-schema.js +82 -0
  355. package/dist/extensions/agent-browser/lib/web-search.js +124 -614
  356. package/dist/extensions/agent-browser/lib/webmcp-observation.js +13 -6
  357. package/dist/extensions/agent-browser/lib/windows-stock-launcher.js +63 -37
  358. package/dist/extensions/agent-browser/script-worker.js +80 -43
  359. package/dist/scripts/agent-browser-target.mjs +4 -2
  360. package/docs/ARCHITECTURE.md +19 -6
  361. package/docs/CODE_QUALITY.md +267 -0
  362. package/docs/COMMAND_REFERENCE.md +272 -241
  363. package/docs/ELECTRON.md +77 -46
  364. package/docs/REFERENCE.md +869 -0
  365. package/docs/RELEASE.md +25 -22
  366. package/docs/REQUIREMENTS.md +2 -1
  367. package/docs/SUPPORT_MATRIX.md +81 -79
  368. package/docs/TOOL_CONTRACT.md +158 -125
  369. package/docs/platform-smoke.md +8 -8
  370. package/package.json +144 -120
  371. package/scripts/agent-browser-capability-baseline.mjs +361 -957
  372. package/scripts/agent-browser-target.mjs +6 -2
  373. package/scripts/build.mjs +37 -8
  374. package/scripts/capability-core-commands.mjs +193 -0
  375. package/scripts/capability-options.mjs +283 -0
  376. package/scripts/capability-samples.mjs +133 -0
  377. package/scripts/capability-section.mjs +11 -0
  378. package/scripts/config.mjs +235 -132
  379. package/scripts/convert-browser-session.mjs +30 -9
  380. package/scripts/doctor-sources.mjs +195 -0
  381. package/scripts/doctor.mjs +94 -249
  382. package/scripts/platform-smoke/artifacts.mjs +38 -11
  383. package/scripts/platform-smoke/commands.mjs +104 -0
  384. package/scripts/platform-smoke/crabbox-runner.mjs +167 -80
  385. package/scripts/platform-smoke/doctor-support.mjs +157 -0
  386. package/scripts/platform-smoke/doctor-windows.mjs +230 -0
  387. package/scripts/platform-smoke/doctor.mjs +212 -320
  388. package/scripts/platform-smoke/lease-evidence.mjs +132 -0
  389. package/scripts/platform-smoke/linux-image/Dockerfile +8 -2
  390. package/scripts/platform-smoke/suite-checks.mjs +98 -0
  391. package/scripts/platform-smoke/suite-evidence.mjs +281 -0
  392. package/scripts/platform-smoke/targets.mjs +154 -472
  393. package/scripts/platform-smoke.mjs +55 -40
  394. package/scripts/prepare.mjs +16 -7
@@ -1,486 +1,161 @@
1
1
  import { stat } from "node:fs/promises";
2
+ import { setTimeout as sleepMs } from "node:timers/promises";
2
3
  import { isAbsolute, resolve } from "node:path";
3
- import { isCloseCommand, isOpenNavigationCommand } from "../../command-taxonomy.js";
4
- import { isBrowserIndependentRead } from "../../command-policy.js";
5
- import { boundElectronProbeString } from "../../electron/cdp.js";
4
+ import { isCloseCommand } from "../../command-taxonomy.js";
6
5
  import { executableExistsOnPath } from "../../executable-path.js";
7
6
  import { formatSessionArtifactRetentionSummary } from "../../results/artifact-manifest.js";
8
- import { buildInspectOverlayStateAction, buildNextToolAction, withOptionalSessionArgs } from "../../results/next-actions.js";
9
- import { buildVisibleRefFallbackDiagnosticFromSnapshot, getVisibleRefFallbackTarget } from "../../results/selector-recovery.js";
10
- import { extractRefSnapshotFromData, isAboutBlankUrl, normalizeComparableUrl } from "../../session-page-state.js";
11
- import { redactInvocationArgs, redactSensitiveText } from "../../runtime.js";
7
+ import { buildVisibleRefFallbackDiagnosticFromSnapshot, getVisibleRefFallbackTarget, } from "../../results/selector-recovery.js";
8
+ import { isAboutBlankUrl, normalizeComparableUrl, } from "../../session-page-state.js";
12
9
  import { isRecord } from "../../parsing.js";
13
- import { extractBatchResultCommand, extractNavigationSummaryFromData, extractStringResultField, findElectronLaunchRecordForSession, runSessionCommandData, } from "./session-state.js";
14
- import { getUpstreamEffectiveBatchSteps } from "../batch-stdin.js";
15
- import { getExplicitArtifactDestination } from "./artifact-paths.js";
16
- const ELECTRON_FILL_VERIFICATION_TIMEOUT_MS = 2_000;
17
- export function sleepMs(ms) {
18
- return new Promise((resolve) => setTimeout(resolve, ms));
10
+ import { extractNavigationSummaryFromData, extractStringResultField, runSessionCommandData, } from "./session-state.js";
11
+ export { redactTimeoutPartialProgress } from "./timeout-progress.js";
12
+ export { sleepMs };
13
+ function comparePriorNavigationUrl(priorUrl, url) {
14
+ return priorUrl !== undefined && priorUrl !== ""
15
+ ? normalizeComparableUrl(priorUrl) !== normalizeComparableUrl(url)
16
+ : undefined;
17
+ }
18
+ function reusableNavigationTitle(options, url) {
19
+ const prior = options.priorTarget;
20
+ return options.reusePriorTitle !== false &&
21
+ prior !== undefined &&
22
+ prior.title !== undefined &&
23
+ prior.title !== "" &&
24
+ normalizeComparableUrl(prior.url) === normalizeComparableUrl(url)
25
+ ? prior.title
26
+ : undefined;
19
27
  }
20
28
  export async function collectNavigationSummary(options) {
21
- const url = extractStringResultField(await runSessionCommandData({ args: ["get", "url"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal }), "url");
22
- if (!url || !/^[a-z][a-z0-9+.-]*:/i.test(url))
29
+ const url = extractStringResultField(await runSessionCommandData({
30
+ args: ["get", "url"],
31
+ cwd: options.cwd,
32
+ namespace: options.namespace,
33
+ sessionName: options.sessionName,
34
+ signal: options.signal,
35
+ }), "url");
36
+ if (url === undefined || url === "" || !/^[a-z][a-z0-9+.-]*:/i.test(url)) {
23
37
  return undefined;
24
- const urlChanged = options.priorTarget?.url ? normalizeComparableUrl(options.priorTarget.url) !== normalizeComparableUrl(url) : undefined;
25
- if (isAboutBlankUrl(url))
38
+ }
39
+ const urlChanged = comparePriorNavigationUrl(options.priorTarget?.url, url);
40
+ if (isAboutBlankUrl(url)) {
26
41
  return { url, ...(urlChanged !== undefined ? { urlChanged } : {}) };
42
+ }
27
43
  // Reuse the title already observed for this exact URL instead of spending a second probe. Titles can
28
44
  // change without a URL change on SPAs, but this summary is only a "last observed" page label; the URL
29
45
  // stays live-probed on every call.
30
- if (options.reusePriorTitle !== false && options.priorTarget?.title && normalizeComparableUrl(options.priorTarget.url) === normalizeComparableUrl(url)) {
31
- return { title: options.priorTarget.title, url, urlChanged: false };
46
+ const priorTitle = reusableNavigationTitle(options, url);
47
+ if (priorTitle !== undefined) {
48
+ return { title: priorTitle, url, urlChanged: false };
32
49
  }
33
- const title = extractStringResultField(await runSessionCommandData({ args: ["get", "title"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal }), "title");
50
+ const title = extractStringResultField(await runSessionCommandData({
51
+ args: ["get", "title"],
52
+ cwd: options.cwd,
53
+ namespace: options.namespace,
54
+ sessionName: options.sessionName,
55
+ signal: options.signal,
56
+ }), "title");
34
57
  return { title, url, ...(urlChanged !== undefined ? { urlChanged } : {}) };
35
58
  }
36
- function extractScrollPositionSnapshot(data) {
37
- const result = isRecord(data) && isRecord(data.result) ? data.result : data;
38
- if (!isRecord(result))
39
- return undefined;
40
- const scrollX = typeof result.scrollX === "number" ? result.scrollX : undefined;
41
- const scrollY = typeof result.scrollY === "number" ? result.scrollY : undefined;
42
- const innerHeight = typeof result.innerHeight === "number" ? result.innerHeight : undefined;
43
- const innerWidth = typeof result.innerWidth === "number" ? result.innerWidth : undefined;
44
- const scrollHeight = typeof result.scrollHeight === "number" ? result.scrollHeight : undefined;
45
- const scrollWidth = typeof result.scrollWidth === "number" ? result.scrollWidth : undefined;
46
- if (scrollX === undefined || scrollY === undefined || innerHeight === undefined || innerWidth === undefined || scrollHeight === undefined || scrollWidth === undefined)
47
- return undefined;
48
- const containers = Array.isArray(result.containers)
49
- ? result.containers.flatMap((entry, index) => {
50
- if (!isRecord(entry))
51
- return [];
52
- const rawId = typeof entry.id === "string" ? entry.id : undefined;
53
- const id = rawId && /^\d+:[a-z][a-z0-9-]*(?:\[role=[a-z-]+\])?$/i.test(rawId) ? rawId : `sample-${index}`;
54
- const scrollTop = typeof entry.scrollTop === "number" ? entry.scrollTop : undefined;
55
- const scrollLeft = typeof entry.scrollLeft === "number" ? entry.scrollLeft : undefined;
56
- return scrollTop !== undefined && scrollLeft !== undefined ? [{ id, scrollLeft, scrollTop }] : [];
57
- })
58
- : [];
59
- return { containerCount: typeof result.containerCount === "number" ? result.containerCount : containers.length, containers, innerHeight, innerWidth, scrollHeight, scrollWidth, scrollX, scrollY };
60
- }
61
- const SCROLL_POSITION_EVAL = `(() => {
62
- const viewport = {
63
- scrollX: window.scrollX,
64
- scrollY: window.scrollY,
65
- innerHeight: window.innerHeight,
66
- innerWidth: window.innerWidth,
67
- scrollHeight: Math.max(document.documentElement?.scrollHeight || 0, document.body?.scrollHeight || 0),
68
- scrollWidth: Math.max(document.documentElement?.scrollWidth || 0, document.body?.scrollWidth || 0),
69
- };
70
- const describe = (element, index) => {
71
- const role = element.getAttribute("role") || "";
72
- const id = element.tagName.toLowerCase();
73
- return { id: String(index) + ":" + id + (role ? "[role=" + role + "]" : ""), scrollTop: element.scrollTop, scrollLeft: element.scrollLeft, area: element.clientWidth * element.clientHeight };
74
- };
75
- const containers = Array.from(document.querySelectorAll("body *"))
76
- .filter((element) => element instanceof HTMLElement && (element.scrollHeight > element.clientHeight + 1 || element.scrollWidth > element.clientWidth + 1))
77
- .map(describe)
78
- .sort((left, right) => right.area - left.area)
79
- .slice(0, 10)
80
- .map(({ area, ...entry }) => entry);
81
- return { ...viewport, containerCount: containers.length, containers };
82
- })()`;
83
- export async function collectScrollPositionSnapshot(options) {
84
- return extractScrollPositionSnapshot(await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, stdin: SCROLL_POSITION_EVAL }));
85
- }
86
- function sameScrollPositionSnapshot(left, right) {
87
- return left.scrollX === right.scrollX && left.scrollY === right.scrollY && left.scrollHeight === right.scrollHeight && left.scrollWidth === right.scrollWidth && left.containers.length === right.containers.length && left.containers.every((container, index) => {
88
- const other = right.containers[index];
89
- return other?.id === container.id && other.scrollTop === container.scrollTop && other.scrollLeft === container.scrollLeft;
90
- });
91
- }
92
- export function buildUnsupportedScrollIntoViewRecovery(options) {
93
- if (!["scrollintoview", "scrollinto"].includes(options.commandTokens[0] ?? "") || options.commandTokens.some((token) => token === "--help" || token === "-h"))
94
- return undefined;
95
- const match = /^text=(.+)$/s.exec(options.commandTokens[1] ?? "");
96
- if (!match)
97
- return undefined;
98
- const text = redactSensitiveText(match[1]);
99
- return {
100
- error: "scrollintoview accepts a CSS selector, xpath=..., or a current @e… ref; text=... is not supported and can falsely report success without moving the page.",
101
- nextActions: [
102
- { id: "scroll-semantic-text-target", params: { args: withOptionalSessionArgs(options.sessionName, ["find", "text", text, "hover"]) }, reason: "Use the upstream semantic text locator; hover resolves and scrolls the matched element into view.", safety: "Hover may open a tooltip or menu. Use the snapshot/ref recovery instead when hover state could affect the workflow.", tool: "agent_browser" },
103
- { id: "refresh-refs-for-scroll-target", params: { args: withOptionalSessionArgs(options.sessionName, ["snapshot", "-i"]) }, reason: "Capture a current element ref, then retry scrollintoview with that @e… ref.", safety: "Read-only snapshot; choose the intended current ref before retrying the scroll.", tool: "agent_browser" },
104
- ],
105
- };
106
- }
107
- export function buildScrollNoopDiagnostic(before, after) {
108
- if (!before || !after || !sameScrollPositionSnapshot(before, after))
109
- return undefined;
110
- return {
111
- after,
112
- before,
113
- message: "Scroll reported success, but the viewport and sampled scrollable containers did not change position.",
114
- reason: "no-observed-scroll-position-change",
115
- recommendations: [
116
- "Run snapshot -i or screenshot to confirm what is visible before choosing the next action.",
117
- "On dashboards and panes with nested scrolling, use scrollintoview <@ref> for a visible target or target the actual scrollable region instead of repeating page scrolls.",
118
- ],
119
- };
120
- }
121
- export function buildScrollNoopNextActions(sessionName) {
122
- return [
123
- { id: "inspect-after-noop-scroll", params: { args: withOptionalSessionArgs(sessionName, ["snapshot", "-i"]) }, reason: "Refresh interactive refs and inspect whether the intended target is inside a nested scroll container.", safety: "Do not assume repeated page scrolls will move dashboard panels or nested panes.", tool: "agent_browser" },
124
- { id: "verify-noop-scroll-visually", params: { args: withOptionalSessionArgs(sessionName, ["screenshot"]) }, reason: "Capture the current viewport to verify whether the scroll actually changed visible content.", safety: "Use screenshot evidence before concluding a dense dashboard did or did not move.", tool: "agent_browser" },
125
- ];
126
- }
127
- export function formatScrollNoopDiagnosticText(diagnostic) {
128
- if (!diagnostic)
129
- return undefined;
130
- return ["Scroll diagnostic: no observed scroll movement.", `Reason: ${diagnostic.message}`, `Sampled scrollable containers: ${diagnostic.after.containers.length}/${diagnostic.after.containerCount}.`, ...diagnostic.recommendations.map((recommendation) => `- ${recommendation}`)].join("\n");
131
- }
132
- const COMBOBOX_FOCUS_EVAL = `(() => {
133
- const isVisible = (element) => {
134
- if (!(element instanceof HTMLElement)) return false;
135
- const style = window.getComputedStyle(element);
136
- if (style.display === "none" || style.visibility === "hidden" || Number(style.opacity) === 0) return false;
137
- return element.getClientRects().length > 0;
138
- };
139
- const active = document.activeElement instanceof HTMLElement ? document.activeElement : null;
140
- const role = active?.getAttribute("role") || undefined;
141
- const hasPopup = active?.getAttribute("aria-haspopup") || undefined;
142
- const expanded = active?.getAttribute("aria-expanded") || undefined;
143
- const tagName = active?.tagName.toLowerCase();
144
- const name = (active?.getAttribute("aria-label") || active?.getAttribute("placeholder") || active?.getAttribute("title") || active?.textContent || "").trim().slice(0, 80) || undefined;
145
- const visibleListboxCount = Array.from(document.querySelectorAll('[role="listbox"], [role="menu"]')).filter(isVisible).length;
146
- const visibleOptionCount = Array.from(document.querySelectorAll('[role="option"], option, [role="menuitem"]')).filter(isVisible).length;
147
- const comboboxLike = role === "combobox" || hasPopup === "listbox" || hasPopup === "menu" || tagName === "select" || active?.getAttribute("aria-autocomplete") !== null;
148
- return { activeElement: active ? { expanded, hasPopup, name, role, tagName } : undefined, comboboxLike, visibleListboxCount, visibleOptionCount };
149
- })()`;
150
- function extractComboboxFocusDiagnostic(data) {
151
- const result = isRecord(data) && isRecord(data.result) ? data.result : data;
152
- if (!isRecord(result) || result.comboboxLike !== true || !isRecord(result.activeElement))
153
- return undefined;
154
- const visibleListboxCount = typeof result.visibleListboxCount === "number" ? result.visibleListboxCount : 0;
155
- const visibleOptionCount = typeof result.visibleOptionCount === "number" ? result.visibleOptionCount : 0;
156
- const expanded = typeof result.activeElement.expanded === "string" ? result.activeElement.expanded : undefined;
157
- if ((expanded !== "false" && expanded !== "true") || visibleListboxCount > 0 || visibleOptionCount > 0)
158
- return undefined;
159
- return {
160
- activeElement: {
161
- expanded,
162
- hasPopup: typeof result.activeElement.hasPopup === "string" ? result.activeElement.hasPopup : undefined,
163
- name: typeof result.activeElement.name === "string" ? redactSensitiveText(result.activeElement.name) : undefined,
164
- role: typeof result.activeElement.role === "string" ? result.activeElement.role : undefined,
165
- tagName: typeof result.activeElement.tagName === "string" ? result.activeElement.tagName : undefined,
166
- },
167
- message: "A combobox-like control is focused, but no listbox or option elements are visibly open.",
168
- reason: "focused-combobox-without-visible-options",
169
- recommendations: ["Run snapshot -i to inspect whether options appeared under a different role or portal.", "Try ArrowDown or Enter to open the option list before selecting, or use select/visible option refs when available."],
170
- visibleListboxCount,
171
- visibleOptionCount,
172
- };
173
- }
174
- function isComboboxFocusDiagnosticCommand(command, commandTokens) {
175
- const explicitlyTargetsCombobox = commandTokens.some((token) => /^(?:combobox|listbox)$/i.test(token));
176
- if (!explicitlyTargetsCombobox)
177
- return false;
178
- if (command === "click" || command === "fill")
179
- return true;
180
- return command === "find" && commandTokens.some((token) => ["click", "fill"].includes(token));
181
- }
182
- function getCompiledSemanticActionRoleValue(compiled) {
183
- if (compiled.locator !== "role")
184
- return undefined;
185
- const findIndex = compiled.args.indexOf("find");
186
- if (findIndex < 0 || compiled.args[findIndex + 1] !== "role")
187
- return undefined;
188
- return compiled.args[findIndex + 2];
189
- }
190
- function isComboboxFocusDiagnosticSemanticAction(compiled) {
191
- if (!compiled || !["click", "fill"].includes(compiled.action))
192
- return false;
193
- return /^(?:combobox|listbox)$/i.test(getCompiledSemanticActionRoleValue(compiled) ?? "");
194
- }
195
- export async function collectComboboxFocusDiagnostic(options) {
196
- if (!isComboboxFocusDiagnosticCommand(options.command, options.commandTokens) && !isComboboxFocusDiagnosticSemanticAction(options.semanticAction))
197
- return undefined;
198
- return extractComboboxFocusDiagnostic(await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, stdin: COMBOBOX_FOCUS_EVAL }));
199
- }
200
- export function buildComboboxFocusNextActions(sessionName) {
201
- return [
202
- { id: "inspect-focused-combobox", params: { args: withOptionalSessionArgs(sessionName, ["snapshot", "-i"]) }, reason: "Inspect the focused combobox and any portal/listbox refs before choosing an option.", safety: "Prefer visible option refs or select when a native/selectable option list is exposed.", tool: "agent_browser" },
203
- { id: "try-open-combobox-with-arrow", params: { args: withOptionalSessionArgs(sessionName, ["press", "ArrowDown"]) }, reason: "Many searchable comboboxes open their option list with ArrowDown after focus.", safety: "Use only when the focused combobox is still the intended control, then re-snapshot before selecting.", tool: "agent_browser" },
204
- { id: "try-open-combobox-with-enter", params: { args: withOptionalSessionArgs(sessionName, ["press", "Enter"]) }, reason: "Some comboboxes open or confirm their option list with Enter after focus.", safety: "Enter may select a highlighted/default option; prefer ArrowDown first unless Enter is the app's expected opener.", tool: "agent_browser" },
205
- ];
206
- }
207
- export function formatComboboxFocusDiagnosticText(diagnostic) {
208
- if (!diagnostic)
209
- return undefined;
210
- const label = diagnostic.activeElement.name ? ` (${diagnostic.activeElement.name})` : "";
211
- return [`Combobox diagnostic: focused combobox did not expose visible options${label}.`, `Reason: ${diagnostic.message}`, ...diagnostic.recommendations.map((recommendation) => `- ${recommendation}`)].join("\n");
212
- }
59
+ export { collectScrollPositionSnapshot, buildUnsupportedScrollIntoViewRecovery, buildScrollNoopDiagnostic, buildScrollNoopNextActions, formatScrollNoopDiagnosticText, collectComboboxFocusDiagnostic, buildComboboxFocusNextActions, formatComboboxFocusDiagnosticText, } from "./interaction-diagnostics.js";
213
60
  function getRecordStartLikeCommand(command, commandTokens) {
214
- if (command !== "record")
61
+ if (command !== "record") {
215
62
  return undefined;
63
+ }
216
64
  const subcommand = commandTokens[1]?.toLowerCase();
217
- if (subcommand === "start")
65
+ if (subcommand === "start") {
218
66
  return "record start";
219
- if (subcommand === "restart")
67
+ }
68
+ if (subcommand === "restart") {
220
69
  return "record restart";
70
+ }
221
71
  return undefined;
222
72
  }
223
73
  export async function collectRecordingDependencyWarning(options) {
224
- if (!options.succeeded)
225
- return undefined;
226
- const recordCommand = getRecordStartLikeCommand(options.command, options.commandTokens);
227
- if (!recordCommand)
228
- return undefined;
229
- if (await executableExistsOnPath("ffmpeg"))
230
- return undefined;
231
- return { command: recordCommand, dependency: "ffmpeg", message: `${recordCommand} reported a pending recording, but ffmpeg is not on PATH. Its output is unverified; install ffmpeg before starting a new recording.`, reason: "ffmpeg-missing-for-recording", recommendations: ["Install ffmpeg before recording; on macOS with Homebrew, brew install ffmpeg or brew install ffmpeg-full.", "Stop this recording, check the result, and start a new recording after ensuring Pi can find ffmpeg on PATH."] };
232
- }
233
- export function formatRecordingDependencyWarningText(warning) {
234
- if (!warning)
74
+ if (!options.succeeded) {
235
75
  return undefined;
236
- return ["Recording dependency warning: ffmpeg not found on PATH.", `Reason: ${warning.message}`, ...warning.recommendations.map((recommendation) => `- ${recommendation}`)].join("\n");
237
- }
238
- function getSnapshotRefRecord(data) {
239
- return isRecord(data) && isRecord(data.refs) ? data.refs : undefined;
240
- }
241
- const OVERLAY_CLOSE_NAME_PATTERN = /(?:\b(?:close|dismiss|no thanks|not now|maybe later|hide|skip|continue without|x)\b|^\s*×\s*$)/i;
242
- const OVERLAY_CONTEXT_ROLES = new Set(["alertdialog", "dialog"]);
243
- const OVERLAY_ACTION_ROLES = new Set(["button", "link", "menuitem"]);
244
- const OVERLAY_BLOCKER_CANDIDATE_LIMIT = 3;
245
- function getOverlayBlockerCandidates(snapshotData) {
246
- const refs = getSnapshotRefRecord(snapshotData);
247
- if (!refs)
248
- return [];
249
- const hasOverlayContext = Object.values(refs).some((entry) => isRecord(entry) && OVERLAY_CONTEXT_ROLES.has((typeof entry.role === "string" ? entry.role : "").toLowerCase()));
250
- if (!hasOverlayContext)
251
- return [];
252
- const candidates = [];
253
- for (const [ref, entry] of Object.entries(refs)) {
254
- if (!/^e\d+$/.test(ref) || !isRecord(entry))
255
- continue;
256
- const role = typeof entry.role === "string" ? entry.role : undefined;
257
- const name = typeof entry.name === "string" ? entry.name : undefined;
258
- if (!role || !OVERLAY_ACTION_ROLES.has(role.toLowerCase()) || !name || !OVERLAY_CLOSE_NAME_PATTERN.test(name))
259
- continue;
260
- candidates.push({ args: ["click", `@${ref}`], name, reason: `Visible ${role} ${JSON.stringify(name)} appears in a snapshot that also contains overlay/banner/dialog context.`, ref: `@${ref}`, role });
261
- if (candidates.length >= OVERLAY_BLOCKER_CANDIDATE_LIMIT)
262
- break;
263
76
  }
264
- return candidates;
265
- }
266
- export function formatOverlayBlockerText(diagnostic) {
267
- return ["Possible overlay blockers:", ...diagnostic.candidates.map((candidate) => `- ${candidate.ref}${candidate.role ? ` ${candidate.role}` : ""}${candidate.name ? ` ${JSON.stringify(candidate.name)}` : ""}: ${candidate.reason}`)].join("\n");
268
- }
269
- export function buildOverlayBlockerNextActions(options) {
270
- return [buildInspectOverlayStateAction(options.sessionName), ...options.diagnostic.candidates.map((candidate, index) => ({ id: `try-overlay-blocker-candidate-${index + 1}`, params: { args: withOptionalSessionArgs(options.sessionName, candidate.args) }, reason: candidate.reason, safety: "Only click this if the candidate is clearly a close/dismiss control for an overlay that blocks the intended workflow.", tool: "agent_browser" }))];
271
- }
272
- export function collectSnapshotOverlayBlockerDiagnostic(data) {
273
- const candidates = getOverlayBlockerCandidates(data);
274
- const snapshot = extractRefSnapshotFromData(data);
275
- if (candidates.length === 0 || !snapshot)
276
- return undefined;
277
- return { candidates, snapshot, summary: "Snapshot contains dialog/modal context plus likely close or dismiss controls; treat covered controls as potentially obstructed until the overlay state is resolved." };
278
- }
279
- export async function collectOverlayBlockerDiagnostic(options) {
280
- if (options.command !== "click" || !isRecord(options.data) || typeof options.data.clicked !== "string")
281
- return undefined;
282
- if (!options.data.clicked.startsWith("@") && !options.data.clicked.startsWith("ref="))
283
- return undefined;
284
- const priorUrl = normalizeComparableUrl(options.priorTarget?.url);
285
- const currentUrl = normalizeComparableUrl(options.navigationSummary?.url);
286
- if (!priorUrl || !currentUrl || currentUrl !== priorUrl)
287
- return undefined;
288
- const snapshotData = await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal });
289
- const diagnostic = collectSnapshotOverlayBlockerDiagnostic(snapshotData);
290
- if (!diagnostic)
291
- return undefined;
292
- return { ...diagnostic, summary: `Click completed but the page stayed on ${currentUrl}; a fresh snapshot contains likely overlay close/dismiss controls.` };
293
- }
294
- const SELECTOR_TEXT_VISIBILITY_CANDIDATE_LIMIT = 8;
295
- function buildVisibleTextProbeScript(selector) {
296
- return `(() => {\n const selector = ${JSON.stringify(selector)};\n const isVisible = (element) => {\n const style = window.getComputedStyle(element);\n if (!style || style.display === 'none' || style.visibility === 'hidden' || style.visibility === 'collapse' || Number(style.opacity) === 0) return false;\n return Array.from(element.getClientRects()).some((rect) => rect.width > 0 && rect.height > 0);\n };\n let matches = [];\n try {\n matches = Array.from(document.querySelectorAll(selector));\n } catch (error) {\n return JSON.stringify({ selector, error: error instanceof Error ? error.message : String(error) });\n }\n const visible = matches.filter(isVisible);\n const trim = (value) => typeof value === 'string' ? value.trim().replace(/\\s+/g, ' ').slice(0, 200) : undefined;\n const describeCandidate = (element) => {\n const index = matches.indexOf(element);\n const role = element.getAttribute('role');\n const candidate = { index, tagName: element.tagName.toLowerCase(), textPreview: trim(element.textContent) };\n if (role) candidate.role = role;\n return candidate;\n };\n const visibleCandidates = visible.slice(0, ${SELECTOR_TEXT_VISIBILITY_CANDIDATE_LIMIT}).map(describeCandidate);\n return JSON.stringify({ selector, matchCount: matches.length, visibleCount: visible.length, firstMatchVisible: matches[0] ? isVisible(matches[0]) : undefined, firstTextPreview: trim(matches[0]?.textContent), firstVisibleTextPreview: trim(visible[0]?.textContent), visibleCandidates });\n})()`;
297
- }
298
- function parseSelectorTextVisibilityCandidates(value) {
299
- if (!Array.isArray(value))
300
- return undefined;
301
- const candidates = value.flatMap((entry) => {
302
- if (!isRecord(entry) || typeof entry.index !== "number" || typeof entry.tagName !== "string")
303
- return [];
304
- const role = typeof entry.role === "string" && entry.role.length > 0 ? entry.role : undefined;
305
- const textPreview = typeof entry.textPreview === "string" && entry.textPreview.length > 0 ? redactSensitiveText(entry.textPreview) : undefined;
306
- return [{ index: entry.index, tagName: entry.tagName, ...(role ? { role } : {}), ...(textPreview ? { textPreview } : {}) }];
307
- });
308
- return candidates.length > 0 ? candidates : undefined;
309
- }
310
- function parseSelectorTextVisibilityProbe(data, selector) {
311
- const result = extractStringResultField(data, "result");
312
- if (!result)
77
+ const recordCommand = getRecordStartLikeCommand(options.command, options.commandTokens);
78
+ if (recordCommand === undefined) {
313
79
  return undefined;
314
- let parsed;
315
- try {
316
- parsed = JSON.parse(result);
317
80
  }
318
- catch {
81
+ if (await executableExistsOnPath("ffmpeg")) {
319
82
  return undefined;
320
83
  }
321
- if (!isRecord(parsed) || typeof parsed.error === "string")
322
- return undefined;
323
- const matchCount = typeof parsed.matchCount === "number" ? parsed.matchCount : undefined;
324
- const visibleCount = typeof parsed.visibleCount === "number" ? parsed.visibleCount : undefined;
325
- if (matchCount === undefined || visibleCount === undefined)
326
- return undefined;
327
84
  return {
328
- firstMatchVisible: typeof parsed.firstMatchVisible === "boolean" ? parsed.firstMatchVisible : undefined,
329
- firstVisibleTextPreview: typeof parsed.firstVisibleTextPreview === "string" && parsed.firstVisibleTextPreview.length > 0 ? redactSensitiveText(parsed.firstVisibleTextPreview) : undefined,
330
- matchCount,
331
- selector,
332
- visibleCandidates: parseSelectorTextVisibilityCandidates(parsed.visibleCandidates),
333
- visibleCount,
85
+ command: recordCommand,
86
+ dependency: "ffmpeg",
87
+ message: `${recordCommand} reported a pending recording, but ffmpeg is not on PATH. Its output is unverified; install ffmpeg before starting a new recording.`,
88
+ reason: "ffmpeg-missing-for-recording",
89
+ recommendations: [
90
+ "Install ffmpeg before recording; on macOS with Homebrew, brew install ffmpeg or brew install ffmpeg-full.",
91
+ "Stop this recording, check the result, and start a new recording after ensuring Pi can find ffmpeg on PATH.",
92
+ ],
334
93
  };
335
94
  }
336
- function selectorMayExposeSensitiveLiteral(selector) {
337
- return redactSensitiveText(selector) !== selector || /\[[^\]]*[~|^$*]?=\s*(?:"[^"]*"|'[^']*'|[^\]\s]+)\s*(?:[is]\s*)?\]/.test(selector);
338
- }
339
- async function collectSelectorTextVisibilityDiagnosticForSelector(options) {
340
- const { selector } = options;
341
- if (!selector || /^@e\d+$/.test(selector) || /^#[A-Za-z_][\w-]*$/.test(selector) || selectorMayExposeSensitiveLiteral(selector))
342
- return undefined;
343
- const probe = await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, stdin: buildVisibleTextProbeScript(selector) });
344
- const parsed = parseSelectorTextVisibilityProbe(probe, selector);
345
- if (!parsed || parsed.matchCount <= 1 && parsed.firstMatchVisible !== false)
346
- return undefined;
347
- if (parsed.visibleCount === 0)
95
+ export function formatRecordingDependencyWarningText(warning) {
96
+ if (!warning) {
348
97
  return undefined;
349
- const visibleMatchNoun = `visible match${parsed.visibleCount === 1 ? "" : "es"}`;
350
- const visibleMatchVerb = parsed.visibleCount === 1 ? "exists" : "exist";
351
- const summary = parsed.firstMatchVisible === false
352
- ? `Selector ${JSON.stringify(selector)} matched ${parsed.matchCount} elements; the first match is hidden while ${parsed.visibleCount} ${visibleMatchNoun} ${visibleMatchVerb}.`
353
- : `Selector ${JSON.stringify(selector)} matched ${parsed.matchCount} elements; get text reads the first upstream match, which may not be the intended visible tab/panel.`;
354
- return { ...parsed, summary };
355
- }
356
- function getBatchGetTextSelectors(data) {
357
- if (!Array.isArray(data))
358
- return [];
359
- return data.flatMap((item) => {
360
- if (!isRecord(item) || item.success === false)
361
- return [];
362
- const [command, subcommand, selector] = extractBatchResultCommand(item);
363
- return command === "get" && subcommand === "text" && selector ? [selector] : [];
364
- });
365
- }
366
- function getSuccessfulGetTextSelectors(options) {
367
- return options.commandInfo.command === "get" && options.commandInfo.subcommand === "text"
368
- ? [options.commandTokens[2]].filter((selector) => typeof selector === "string" && selector.length > 0)
369
- : options.commandInfo.command === "batch" ? getBatchGetTextSelectors(options.data) : [];
370
- }
371
- export async function collectSelectorTextVisibilityDiagnostics(options) {
372
- const selectors = getSuccessfulGetTextSelectors(options);
373
- const diagnostics = [];
374
- for (const selector of selectors) {
375
- const diagnostic = await collectSelectorTextVisibilityDiagnosticForSelector({ cwd: options.cwd, namespace: options.namespace, selector, sessionName: options.sessionName, signal: options.signal });
376
- if (diagnostic)
377
- diagnostics.push(diagnostic);
378
98
  }
379
- return diagnostics.sort((left, right) => Number(right.firstMatchVisible === false) - Number(left.firstMatchVisible === false));
380
- }
381
- export function formatSelectorTextVisibilityText(diagnostics) {
382
- if (diagnostics.length === 0)
383
- return undefined;
384
- return diagnostics.flatMap((diagnostic, index) => {
385
- const actionId = index === 0 ? "inspect-visible-text-candidates" : `inspect-visible-text-candidates-${index + 1}`;
386
- const lines = [`Selector text visibility warning: ${diagnostic.summary}`];
387
- if (diagnostic.firstVisibleTextPreview)
388
- lines.push(`First visible text preview: ${JSON.stringify(diagnostic.firstVisibleTextPreview)}`);
389
- if (diagnostic.visibleCandidates && diagnostic.visibleCandidates.length > 0) {
390
- lines.push(`Visible candidates (${diagnostic.visibleCandidates.length} shown, querySelectorAll index):`);
391
- for (const candidate of diagnostic.visibleCandidates) {
392
- const rolePart = candidate.role ? ` role=${candidate.role}` : "";
393
- const previewPart = candidate.textPreview ? `: ${JSON.stringify(candidate.textPreview)}` : "";
394
- lines.push(`- [${candidate.index}] ${candidate.tagName}${rolePart}${previewPart}`);
395
- }
396
- }
397
- lines.push(`Next action: use details.nextActions ${actionId} before trusting this selector text.`);
398
- return lines;
399
- }).join("\n");
400
- }
401
- export function buildSelectorTextVisibilityNextActions(options) {
402
- return options.diagnostics.map((diagnostic, index) => ({ id: index === 0 ? "inspect-visible-text-candidates" : `inspect-visible-text-candidates-${index + 1}`, params: { args: withOptionalSessionArgs(options.sessionName, ["eval", "--stdin"]), stdin: buildVisibleTextProbeScript(diagnostic.selector) }, reason: "Inspect selector match count and visible text before trusting get text on tabbed or hidden DOM content.", safety: "Read-only DOM inspection; use a more specific visible selector or current @ref before acting on hidden-tab text.", tool: "agent_browser" }));
403
- }
404
- function normalizeSelectorForScopeHeuristic(selector) {
405
- return selector.trim().replace(/\s+/g, " ").toLowerCase();
406
- }
407
- function isBroadGetTextSelector(selector) {
408
- if (!selector || /^@e\d+$/.test(selector) || selectorMayExposeSensitiveLiteral(selector))
409
- return false;
410
- const normalized = normalizeSelectorForScopeHeuristic(selector);
411
- return normalized === "body" || normalized === "html" || normalized === ":root" || normalized === "*" || normalized === "main" || normalized === "div" || normalized === "section" || normalized === "article" || /^\[role=(?:"application"|'application'|application)\]$/i.test(normalized);
412
- }
413
- function getElectronTextScopeContext(options) {
414
- const record = findElectronLaunchRecordForSession(options.sessionName, options.electronLaunchRecords, options.namespace);
415
- if (!record)
416
- return undefined;
417
- const url = options.currentTarget?.url ?? options.priorTarget?.url;
418
- return { launchId: record.launchId, sessionName: record.sessionName ?? options.sessionName, url };
419
- }
420
- export function getSourceLookupElectronContext(options) {
421
- const record = findElectronLaunchRecordForSession(options.sessionName, options.electronLaunchRecords, options.namespace);
422
- if (!record)
423
- return undefined;
424
- const url = options.currentTarget?.url ?? options.priorTarget?.url;
425
- return { appName: record.appName, appPath: record.appPath, executablePath: record.executablePath, launchId: record.launchId, sessionName: record.sessionName ?? options.sessionName, url };
426
- }
427
- export function buildSourceLookupElectronNextActions(sourceLookup) {
428
- if (sourceLookup?.status !== "no-candidates" || !sourceLookup.electronContext)
429
- return [];
430
- const actions = [];
431
- const { launchId, sessionName } = sourceLookup.electronContext;
432
- if (sessionName)
433
- actions.push({ id: "snapshot-electron-session", params: { args: withOptionalSessionArgs(sessionName, ["snapshot", "-i"]) }, reason: "Refresh interactive refs in the attached Electron session before retrying source lookup with a narrower target.", safety: "Read-only snapshot; no app mutation.", tool: "agent_browser" });
434
- if (launchId)
435
- actions.push({ id: "probe-electron-launch", params: { action: "probe", launchId }, reason: "Collect bounded wrapper/session context for the packaged Electron launch after sourceLookup found no candidates.", safety: "Read-only probe of title, URL, focus, tabs, and compact snapshot metadata.", tool: "agent_browser_electron" });
436
- if (sessionName)
437
- actions.push({ id: "list-electron-tabs", params: { args: withOptionalSessionArgs(sessionName, ["tab", "list"]) }, reason: "Check current Electron tabs/targets before choosing a narrower selector or @ref.", safety: "Read-only tab listing.", tool: "agent_browser" });
438
- return actions;
439
- }
440
- export function collectElectronBroadGetTextScopeDiagnostics(options) {
441
- const electronContext = getElectronTextScopeContext(options);
442
- if (!electronContext)
443
- return [];
444
- return getSuccessfulGetTextSelectors(options).filter(isBroadGetTextSelector).map((selector) => ({ electronContext, selector, summary: `Broad Electron get text selector warning: selector ${JSON.stringify(selector)} may read the entire app shell; prefer snapshot -i and a current @ref or a narrower panel selector.` }));
445
- }
446
- export function formatElectronBroadGetTextScopeText(diagnostics) {
447
- return diagnostics.length > 0 ? diagnostics.map((diagnostic) => diagnostic.summary).join("\n") : undefined;
448
- }
449
- export function buildElectronBroadGetTextScopeNextActions(options) {
450
- return options.diagnostics.map((diagnostic, index) => ({ id: index === 0 ? "snapshot-for-electron-text-scope" : `snapshot-for-electron-text-scope-${index + 1}`, params: { args: withOptionalSessionArgs(options.sessionName, ["snapshot", "-i"]) }, reason: `Refresh Electron refs before trusting broad get text selector ${JSON.stringify(diagnostic.selector)}.`, safety: "Read-only snapshot; prefer a current @ref or narrower selector before extracting app-shell text.", tool: "agent_browser" }));
451
- }
99
+ return [
100
+ "Recording dependency warning: ffmpeg not found on PATH.",
101
+ `Reason: ${warning.message}`,
102
+ ...warning.recommendations.map((recommendation) => `- ${recommendation}`),
103
+ ].join("\n");
104
+ }
105
+ export { formatOverlayBlockerText, buildOverlayBlockerNextActions, collectSnapshotOverlayBlockerDiagnostic, collectOverlayBlockerDiagnostic, } from "./overlay-diagnostics.js";
106
+ export { collectSelectorTextVisibilityDiagnostics, formatSelectorTextVisibilityText, buildSelectorTextVisibilityNextActions, } from "./text-visibility-diagnostics.js";
107
+ export { getSourceLookupElectronContext, buildSourceLookupElectronNextActions, collectElectronBroadGetTextScopeDiagnostics, formatElectronBroadGetTextScopeText, buildElectronBroadGetTextScopeNextActions, } from "./electron-text-diagnostics.js";
452
108
  function looksLikeFunctionEvalStdin(stdin) {
453
109
  const trimmed = stdin?.trim();
454
- if (!trimmed)
110
+ if (trimmed === undefined || trimmed === "") {
455
111
  return false;
456
- return /^(?:async\s+)?function\b/.test(trimmed) || /^(?:async\s*)?\([^)]*\)\s*=>/.test(trimmed) || /^(?:async\s+)?[A-Za-z_$][\w$]*\s*=>/.test(trimmed);
112
+ }
113
+ return (/^(?:async\s+)?function\b/.test(trimmed) ||
114
+ /^(?:async\s*)?\([^)]*\)\s*=>/.test(trimmed) ||
115
+ /^(?:async\s+)?[A-Za-z_$][\w$]*\s*=>/.test(trimmed));
457
116
  }
458
117
  function isPlainEmptyObject(value) {
459
- if (!isRecord(value) || Array.isArray(value))
118
+ if (!isRecord(value) || Array.isArray(value)) {
460
119
  return false;
120
+ }
461
121
  const prototype = Object.getPrototypeOf(value);
462
122
  return (prototype === Object.prototype || prototype === null) && Object.keys(value).length === 0;
463
123
  }
464
124
  export function getEvalStdinHint(options) {
465
- if (options.command !== "eval" || !looksLikeFunctionEvalStdin(options.stdin) || !isRecord(options.data))
466
- return undefined;
125
+ if (options.command !== "eval" ||
126
+ !looksLikeFunctionEvalStdin(options.stdin) ||
127
+ !isRecord(options.data)) {
128
+ return;
129
+ }
467
130
  const result = options.data.result;
468
- if (!isPlainEmptyObject(result))
469
- return undefined;
470
- return { reason: "eval --stdin received a function-shaped snippet and the upstream JSON result was an empty object, which often means the function itself was returned or serialized instead of invoked.", suggestion: "Pass a plain expression such as `({ title: document.title })`, or invoke the function explicitly, for example `(() => ({ title: document.title }))()`." };
131
+ if (!isPlainEmptyObject(result)) {
132
+ return;
133
+ }
134
+ return {
135
+ reason: "eval --stdin received a function-shaped snippet and the upstream JSON result was an empty object, which often means the function itself was returned or serialized instead of invoked.",
136
+ suggestion: "Pass a plain expression such as `({ title: document.title })`, or invoke the function explicitly, for example `(() => ({ title: document.title }))()`.",
137
+ };
471
138
  }
472
139
  export function formatEvalStdinHintText(hint) {
473
140
  return hint ? `Eval stdin hint: ${hint.reason} ${hint.suggestion}` : undefined;
474
141
  }
142
+ function localEvalPageUrl(options) {
143
+ const pageUrl = options.pageUrl?.trim() ??
144
+ options.navigationSummary?.url?.trim() ??
145
+ extractNavigationSummaryFromData(options.data)?.url;
146
+ return pageUrl !== undefined && pageUrl !== "" && /^file:/i.test(pageUrl) ? pageUrl : undefined;
147
+ }
475
148
  export function getEvalResultWarning(options) {
476
- if (options.command !== "eval" || !options.stdin?.trim() || !isRecord(options.data) || options.data.result !== null)
477
- return undefined;
478
- const pageUrl = options.pageUrl?.trim() ?? options.navigationSummary?.url?.trim() ?? extractNavigationSummaryFromData(options.data)?.url;
479
- if (!pageUrl || !/^file:/i.test(pageUrl))
480
- return undefined;
481
- const trimmed = options.stdin.trim();
482
- if (/^(?:null|undefined)$/i.test(trimmed))
483
- return undefined;
149
+ const trimmed = options.stdin?.trim();
150
+ if (options.command !== "eval" ||
151
+ trimmed === undefined ||
152
+ trimmed === "" ||
153
+ !isRecord(options.data) ||
154
+ options.data.result !== null ||
155
+ /^(?:null|undefined)$/i.test(trimmed) ||
156
+ localEvalPageUrl(options) === undefined) {
157
+ return;
158
+ }
484
159
  return {
485
160
  reason: "eval --stdin returned null on a file:// page; upstream may not expose full DOM semantics for local fixtures.",
486
161
  suggestion: "Treat this as inconclusive verification. Use snapshot -i, get text on current @refs, screenshot evidence, or a reachable http(s) fixture before concluding DOM state.",
@@ -489,22 +164,21 @@ export function getEvalResultWarning(options) {
489
164
  export function formatEvalResultWarningText(warning) {
490
165
  return warning ? `Eval result warning: ${warning.reason} ${warning.suggestion}` : undefined;
491
166
  }
492
- export async function getArtifactCleanupGuidance(options) {
493
- if (!options.succeeded || !isCloseCommand(options.command) || !options.manifest)
494
- return undefined;
495
- const explicitEntries = options.manifest.entries.filter((entry) => entry.storageScope === "explicit-path");
496
- if (explicitEntries.length === 0)
497
- return undefined;
167
+ async function collectExistingExplicitArtifactPaths(entries, cwd) {
498
168
  const explicitArtifactPaths = [];
499
169
  const seenPaths = new Set();
500
- for (const entry of explicitEntries) {
501
- if (explicitArtifactPaths.length >= 10)
170
+ for (const entry of entries) {
171
+ if (explicitArtifactPaths.length >= 10) {
502
172
  break;
173
+ }
503
174
  const displayPath = entry.path;
504
- if (seenPaths.has(displayPath))
175
+ if (seenPaths.has(displayPath)) {
505
176
  continue;
506
- const absolutePath = entry.absolutePath ?? (isAbsolute(entry.path) ? entry.path : resolve(options.cwd, entry.path));
177
+ }
178
+ const absolutePath = entry.absolutePath ?? (isAbsolute(entry.path) ? entry.path : resolve(cwd, entry.path));
507
179
  try {
180
+ // The first ten existing distinct display paths are selected in manifest order.
181
+ // oxlint-disable-next-line no-await-in-loop
508
182
  await stat(absolutePath);
509
183
  }
510
184
  catch {
@@ -513,357 +187,54 @@ export async function getArtifactCleanupGuidance(options) {
513
187
  seenPaths.add(displayPath);
514
188
  explicitArtifactPaths.push(displayPath);
515
189
  }
516
- if (explicitArtifactPaths.length === 0)
517
- return undefined;
518
- return { explicitArtifactPaths, note: "Closing the browser session does not delete explicit screenshots, downloads, PDFs, traces, HAR files, or recordings; clean existing paths with host file tools when no longer needed.", owner: "host-file-tools", summary: formatSessionArtifactRetentionSummary(options.manifest) };
190
+ return explicitArtifactPaths;
519
191
  }
520
- export function formatArtifactCleanupGuidanceText(guidance) {
521
- if (!guidance || guidance.explicitArtifactPaths.length === 0)
522
- return undefined;
523
- const explicitCount = guidance.explicitArtifactPaths.length;
524
- return `Artifact lifecycle: ${explicitCount} explicit artifact${explicitCount === 1 ? "" : "s"} remain${explicitCount === 1 ? "s" : ""}; expand or inspect details.artifactCleanup.explicitArtifactPaths for paths. Browser close does not delete explicit screenshots, downloads, PDFs, traces, HAR files, or recordings; use host file tools for cleanup.`;
525
- }
526
- async function collectManagedSessionCommandData(options) {
527
- try {
528
- return { data: await runSessionCommandData({ ...options, pinNamespace: true }) };
529
- }
530
- catch (error) {
531
- return { error: error instanceof Error ? error.message : String(error) };
532
- }
533
- }
534
- async function collectElectronManagedSessionUrl(options) {
535
- const urlResult = await collectManagedSessionCommandData({
536
- args: ["get", "url"],
537
- cwd: options.cwd,
538
- namespace: options.namespace,
539
- sessionName: options.sessionName,
540
- signal: options.signal,
541
- timeoutMs: options.timeoutMs,
542
- });
543
- const url = boundElectronProbeString(extractStringResultField(urlResult.data, "result") ?? extractStringResultField(urlResult.data, "url"), 300);
544
- return urlResult.error ? { error: urlResult.error } : { url };
545
- }
546
- export async function collectElectronManagedSessionTarget(options) {
547
- if (!options.sessionName)
548
- return undefined;
549
- const urlResult = await collectManagedSessionCommandData({ args: ["get", "url"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, timeoutMs: options.timeoutMs });
550
- const url = boundElectronProbeString(extractStringResultField(urlResult.data, "result") ?? extractStringResultField(urlResult.data, "url"), 300);
551
- if (urlResult.error || !url)
552
- return { error: urlResult.error ?? "get url returned no active page URL.", sessionName: options.sessionName };
553
- const titleResult = await collectManagedSessionCommandData({ args: ["get", "title"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, timeoutMs: options.timeoutMs });
554
- const title = boundElectronProbeString(extractStringResultField(titleResult.data, "result") ?? extractStringResultField(titleResult.data, "title"), 160);
555
- return { sessionName: options.sessionName, title, url, ...(titleResult.error ? { error: titleResult.error } : {}) };
556
- }
557
- export async function collectQaAttachedTarget(options) {
558
- if (!options.sessionName)
559
- return undefined;
560
- if (options.currentTarget?.title || options.currentTarget?.url)
561
- return { sessionName: options.sessionName, title: options.currentTarget.title, url: options.currentTarget.url };
562
- return collectElectronManagedSessionTarget({ cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal });
563
- }
564
- export function formatQaAttachedTargetText(target) {
565
- if (!target)
192
+ export async function getArtifactCleanupGuidance(options) {
193
+ if (!options.succeeded || !isCloseCommand(options.command) || !options.manifest) {
566
194
  return undefined;
567
- return ["QA attached target:", target.sessionName, target.title, target.url].filter((part) => typeof part === "string" && part.length > 0).join(" — ");
568
- }
569
- function buildQaAttachedRecoveryNextActions(sessionName) {
570
- const sessionArgs = (args) => withOptionalSessionArgs(sessionName, args);
571
- return [
572
- buildNextToolAction({
573
- args: sessionArgs(["tab", "list"]),
574
- id: "list-tabs-before-qa-attached",
575
- reason: "Inspect the connected session tabs before retrying qa.attached.",
576
- safety: "Read-only tab listing for the attached session.",
577
- }),
578
- buildNextToolAction({
579
- args: sessionArgs(["snapshot", "-i"]),
580
- id: "snapshot-before-qa-attached",
581
- reason: "Capture interactive refs on the active page before retrying qa.attached.",
582
- safety: "Read-only snapshot; confirms a renderable page is selected.",
583
- }),
584
- ];
585
- }
586
- export async function validateQaAttachedPrecondition(options) {
587
- if (!options.sessionName) {
588
- return {
589
- error: "qa.attached requires an active attached session with a resolvable session name.",
590
- nextActions: buildQaAttachedRecoveryNextActions(options.sessionName),
591
- };
592
- }
593
- const urlProbe = await collectElectronManagedSessionUrl({ cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal });
594
- if (urlProbe.error) {
595
- return {
596
- error: `qa.attached could not read the attached session URL: ${urlProbe.error}. Run tab list or snapshot -i before retrying qa.attached.`,
597
- nextActions: buildQaAttachedRecoveryNextActions(options.sessionName),
598
- };
599
195
  }
600
- const url = urlProbe.url?.trim();
601
- if (!url) {
602
- return {
603
- error: "qa.attached requires an attached session with a readable page URL. Run tab list, select a stable tab, then snapshot -i before retrying.",
604
- nextActions: buildQaAttachedRecoveryNextActions(options.sessionName),
605
- };
606
- }
607
- return undefined;
608
- }
609
- function getTopLevelFillInvocation(commandTokens) {
610
- if (commandTokens[0] !== "fill" || commandTokens.length < 3)
611
- return undefined;
612
- const selector = commandTokens[1];
613
- const expected = commandTokens.slice(2).join(" ");
614
- const refId = selector?.match(/^@?(e\d+)$/)?.[1];
615
- return selector && expected.length > 0 ? { expected, ...(refId ? { refId } : {}), selector } : undefined;
616
- }
617
- function shouldVerifyContenteditableFill(fill, refSnapshot) {
618
- if (!fill?.refId)
619
- return false;
620
- const ref = refSnapshot?.refs?.[fill.refId];
621
- if (!ref)
622
- return false;
623
- return ref.isContentEditable === true && (ref.role === "generic" || ref.role === "unknown" || ref.role === "textbox");
624
- }
625
- export function buildFillVerificationNextActions(diagnostic, sessionName) {
626
- return [
627
- { id: "inspect-after-fill-verification", params: { args: withOptionalSessionArgs(sessionName, ["snapshot", "-i"]) }, reason: "Refresh the UI after a fill that reported success but did not appear to update the target.", safety: "Read-only snapshot; use current refs before retrying.", tool: "agent_browser" },
628
- { id: "verify-filled-value", params: { args: withOptionalSessionArgs(sessionName, ["get", diagnostic.method, diagnostic.selector]) }, reason: `Check the target ${diagnostic.method} directly before submitting or creating files.`, safety: "Read-only check; selector may still be stale if the UI rerendered.", tool: "agent_browser" },
629
- ];
630
- }
631
- function extractFillVerificationValue(data) {
632
- if (typeof data === "string")
633
- return data;
634
- if (!isRecord(data))
635
- return undefined;
636
- if (typeof data.value === "string")
637
- return data.value;
638
- if (typeof data.result === "string")
639
- return data.result;
640
- return undefined;
641
- }
642
- export async function collectFillVerificationDiagnostic(options) {
643
- const fill = getTopLevelFillInvocation(options.commandTokens);
644
- if (!fill || !options.sessionName)
645
- return undefined;
646
- const contenteditable = shouldVerifyContenteditableFill(fill, options.refSnapshot);
647
- if (!contenteditable && !options.forceValueVerification)
196
+ const explicitEntries = options.manifest.entries.filter((entry) => entry.storageScope === "explicit-path");
197
+ if (explicitEntries.length === 0) {
648
198
  return undefined;
649
- const method = contenteditable ? "text" : "value";
650
- let valueData;
651
- try {
652
- valueData = await runSessionCommandData({ args: ["get", method, fill.selector], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, timeoutMs: ELECTRON_FILL_VERIFICATION_TIMEOUT_MS });
653
199
  }
654
- catch {
200
+ const explicitArtifactPaths = await collectExistingExplicitArtifactPaths(explicitEntries, options.cwd);
201
+ if (explicitArtifactPaths.length === 0) {
655
202
  return undefined;
656
203
  }
657
- const actual = extractFillVerificationValue(valueData);
658
- if (actual === undefined || actual === fill.expected)
659
- return undefined;
660
- const reason = contenteditable ? "contenteditable-fill-mismatch" : "value-fill-mismatch";
661
- const actualPreview = actual.length > 0 ? `"${boundElectronProbeString(actual, 80)}"` : `an empty ${method}`;
662
- const diagnostic = { actual: actual.length > 0 ? boundElectronProbeString(actual, 160) : "", expected: boundElectronProbeString(fill.expected, 160) ?? fill.expected, method, nextActionIds: [], reason, selector: fill.selector, status: "mismatch", summary: `Fill verification warning: fill ${fill.selector} reported success, but get ${method} returned ${actualPreview}.` };
663
- diagnostic.nextActionIds = buildFillVerificationNextActions(diagnostic, options.sessionName).map((action) => action.id);
664
- return diagnostic;
204
+ return {
205
+ explicitArtifactPaths,
206
+ note: "Closing the browser session does not delete explicit screenshots, downloads, PDFs, traces, HAR files, or recordings; clean existing paths with host file tools when no longer needed.",
207
+ owner: "host-file-tools",
208
+ summary: formatSessionArtifactRetentionSummary(options.manifest),
209
+ };
665
210
  }
666
- export function formatFillVerificationText(diagnostic) {
667
- if (!diagnostic)
211
+ export function formatArtifactCleanupGuidanceText(guidance) {
212
+ if (!guidance || guidance.explicitArtifactPaths.length === 0) {
668
213
  return undefined;
669
- const actual = diagnostic.actual !== undefined ? `actual "${diagnostic.actual}"` : `actual ${diagnostic.method} unavailable`;
670
- const recovery = diagnostic.reason === "contenteditable-fill-mismatch"
671
- ? "Contenteditable fill may append or prepend instead of replacing. Re-run snapshot -i, then prefer focus/click plus keyboard shortcut selection or direct keyboard insertion only after verifying the editor state."
672
- : "Re-run snapshot -i, then prefer click/focus plus keyboard type for custom quick-input controls before submitting.";
673
- return `${diagnostic.summary}\nExpected: "${diagnostic.expected}"; ${actual}.\nNext: ${recovery}`;
214
+ }
215
+ const explicitCount = guidance.explicitArtifactPaths.length;
216
+ return `Artifact lifecycle: ${explicitCount} explicit artifact${explicitCount === 1 ? "" : "s"} remain${explicitCount === 1 ? "s" : ""}; expand or inspect details.artifactCleanup.explicitArtifactPaths for paths. Browser close does not delete explicit screenshots, downloads, PDFs, traces, HAR files, or recordings; use host file tools for cleanup.`;
674
217
  }
218
+ export { collectElectronManagedSessionTarget, collectQaAttachedTarget, formatQaAttachedTargetText, validateQaAttachedPrecondition, collectElectronHandoff, } from "./electron-observation.js";
219
+ export { collectFillVerificationDiagnostic, buildFillVerificationNextActions, formatFillVerificationText, } from "./fill-diagnostics.js";
675
220
  export async function collectVisibleRefFallbackDiagnostic(options) {
676
- if (!options.sessionName)
677
- return undefined;
678
- const target = getVisibleRefFallbackTarget({ commandTokens: options.commandTokens, compiledSemanticAction: options.compiledSemanticAction });
679
- if (!target)
221
+ if (options.sessionName === undefined || options.sessionName === "") {
680
222
  return undefined;
681
- const snapshotData = await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal });
682
- return buildVisibleRefFallbackDiagnosticFromSnapshot({ snapshotData, target });
683
- }
684
- export async function collectElectronHandoff(options) {
685
- if (options.handoff === "connect")
686
- return { handoff: "connect" };
687
- if (options.signal?.aborted)
688
- throw new Error("Electron handoff was aborted.");
689
- const urlData = await runSessionCommandData({ args: ["get", "url"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, throwOnFailure: true });
690
- if (options.signal?.aborted)
691
- throw new Error("Electron handoff was aborted.");
692
- const url = extractStringResultField(urlData, "result") ?? extractStringResultField(urlData, "url");
693
- if (!url)
694
- throw new Error("Electron handoff get url returned no active page URL.");
695
- const tabs = await runSessionCommandData({ args: ["tab", "list"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, throwOnFailure: true });
696
- if (options.signal?.aborted)
697
- throw new Error("Electron handoff was aborted.");
698
- if (options.handoff === "tabs")
699
- return { handoff: "tabs", tabs };
700
- let snapshot = await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, throwOnFailure: true });
701
- let refSnapshot = extractRefSnapshotFromData(snapshot);
702
- let snapshotRetryCount = 0;
703
- while ((!refSnapshot || refSnapshot.refIds.length === 0) && snapshotRetryCount < 2) {
704
- if (options.signal?.aborted)
705
- throw new Error("Electron handoff was aborted.");
706
- snapshotRetryCount += 1;
707
- await sleepMs(250);
708
- if (options.signal?.aborted)
709
- throw new Error("Electron handoff was aborted.");
710
- snapshot = await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, throwOnFailure: true });
711
- refSnapshot = extractRefSnapshotFromData(snapshot);
712
- }
713
- return { handoff: "snapshot", refSnapshot, snapshot, ...(snapshotRetryCount > 0 ? { snapshotRetryCount } : {}), tabs };
714
- }
715
- function getTimeoutProgressSteps(compiledJob, commandTokens, stdin) {
716
- if (compiledJob)
717
- return compiledJob.steps.map((step, index) => ({ args: step.args, generatedFrom: step.generatedFrom, index: index + 1 }));
718
- return getUpstreamEffectiveBatchSteps(commandTokens, stdin).map((args, index) => ({ args, index: index + 1 }));
719
- }
720
- function getLastPositionalToken(args, startIndex = 1) {
721
- for (let index = args.length - 1; index >= startIndex; index -= 1) {
722
- const token = args[index];
723
- if (token && !token.startsWith("-"))
724
- return token;
725
- }
726
- return undefined;
727
- }
728
- function getTimeoutStepArtifactPath(commandTokens) {
729
- return ["screenshot", "pdf", "download", "wait"].includes(commandTokens[0]) ? getExplicitArtifactDestination(commandTokens) : undefined;
730
- }
731
- async function statTimeoutArtifactPath(absolutePath) {
732
- for (let attempt = 0; attempt < 3; attempt += 1) {
733
- try {
734
- const stats = await stat(absolutePath);
735
- return { exists: true, sizeBytes: stats.size };
736
- }
737
- catch {
738
- if (attempt < 2)
739
- await sleepMs(25);
740
- }
741
223
  }
742
- return { exists: false };
743
- }
744
- async function collectTimeoutArtifactEvidence(cwd, steps) {
745
- const evidence = [];
746
- for (const step of steps) {
747
- const path = getTimeoutStepArtifactPath(step.args);
748
- if (!path)
749
- continue;
750
- const absolutePath = isAbsolute(path) ? path : resolve(cwd, path);
751
- const artifact = await statTimeoutArtifactPath(absolutePath);
752
- evidence.push(artifact.exists
753
- ? { absolutePath, exists: true, path, sizeBytes: artifact.sizeBytes, state: "verified", stepIndex: step.index }
754
- : { absolutePath, exists: false, path, state: "missing", stepIndex: step.index });
755
- }
756
- return evidence;
757
- }
758
- function getPlannedCurrentPageUrl(steps) {
759
- for (let index = steps.length - 1; index >= 0; index -= 1) {
760
- const args = steps[index]?.args ?? [];
761
- if (isOpenNavigationCommand(args[0]) || args[0] === "pushstate")
762
- return getLastPositionalToken(args);
763
- }
764
- return undefined;
765
- }
766
- const TIMEOUT_RETRYABLE_COMMANDS = new Set([
767
- "console",
768
- "diff",
769
- "errors",
770
- "get",
771
- "goto",
772
- "navigate",
773
- "network",
774
- "open",
775
- "pdf",
776
- "pushstate",
777
- "screenshot",
778
- "snapshot",
779
- "tab",
780
- "vitals",
781
- "wait",
782
- ]);
783
- function getTimeoutStepRetry(step) {
784
- const command = step.args[0];
785
- return command && TIMEOUT_RETRYABLE_COMMANDS.has(command) ? { args: ["batch"], stdin: JSON.stringify([step.args]) } : undefined;
786
- }
787
- export async function collectTimeoutPartialProgress(options) {
788
- if ((options.commandTokens[0] === "session" && options.commandTokens[1] === "info") || isBrowserIndependentRead(options.commandTokens, options.stdin))
789
- return undefined;
790
- const rawSteps = getTimeoutProgressSteps(options.compiledJob, options.commandTokens, options.stdin);
791
- const artifacts = await collectTimeoutArtifactEvidence(options.operationCwd ?? options.cwd, rawSteps);
792
- const urlData = await runSessionCommandData({ args: ["get", "url"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName });
793
- const recoveredUrl = extractStringResultField(urlData, "result") ?? extractStringResultField(urlData, "url");
794
- const titleData = recoveredUrl
795
- ? await runSessionCommandData({ args: ["get", "title"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName })
796
- : undefined;
797
- const title = extractStringResultField(titleData, "result") ?? extractStringResultField(titleData, "title");
798
- const plannedUrl = recoveredUrl ? undefined : getPlannedCurrentPageUrl(rawSteps);
799
- const url = recoveredUrl ?? plannedUrl;
800
- const currentPageSource = recoveredUrl ? "live" : plannedUrl ? "planned" : title ? "live" : undefined;
801
- // A matching URL or existing file is current state, not an execution receipt.
802
- const steps = rawSteps.map(step => ({ ...step, status: "unknown", reason: "Upstream did not report this step's outcome before timeout." }));
803
- const singleStep = steps.length === 1 ? steps[0] : undefined;
804
- const retry = singleStep ? getTimeoutStepRetry(singleStep) : undefined;
805
- const retryStep = singleStep && retry ? { ...singleStep, retry } : undefined;
806
- if (rawSteps.length === 0 && artifacts.length === 0 && !url && !title)
224
+ const target = getVisibleRefFallbackTarget({
225
+ commandTokens: options.commandTokens,
226
+ compiledSemanticAction: options.compiledSemanticAction,
227
+ });
228
+ if (!target) {
807
229
  return undefined;
808
- const foundArtifacts = artifacts.filter((artifact) => artifact.exists).length;
809
- const pageStateSummary = recoveredUrl || title ? " and current page state" : plannedUrl ? " and planned page URL" : "";
810
- return { artifacts, currentPage: url || title ? { source: currentPageSource, title, url } : undefined, liveUrlRecovered: recoveredUrl !== undefined, retryStep, steps: steps.length > 0 ? steps : undefined, summary: `Timed out before upstream returned final results; ${steps.length} planned step outcome${steps.length === 1 ? " is" : "s are"} unknown. Found ${foundArtifacts}/${artifacts.length} declared artifact path${artifacts.length === 1 ? "" : "s"}${pageStateSummary}; these observations do not prove step execution.` };
811
- }
812
- function redactSensitivePathSegmentsForDiagnostic(path) {
813
- return path.split(/([/\\]+)/).map((segment) => segment === "/" || segment === "\\" || /^[/\\]+$/.test(segment) ? segment : redactSensitiveText(segment) !== segment || /(?:secret|token|password|passwd|credential|auth|api[-_]?key|bearer)/i.test(segment) ? "[REDACTED]" : segment).join("");
814
- }
815
- function sanitizeCurrentPageUrlForTimeoutDiagnostic(url) {
816
- try {
817
- const parsedUrl = new URL(url);
818
- parsedUrl.pathname = parsedUrl.pathname.split("/").map((segment) => redactSensitivePathSegmentsForDiagnostic(segment)).join("/");
819
- for (const [key, value] of parsedUrl.searchParams.entries()) {
820
- if (redactSensitiveText(key) !== key || redactSensitiveText(value) !== value || /(?:secret|token|password|passwd|credential|auth|api[-_]?key|bearer)/i.test(`${key} ${value}`))
821
- parsedUrl.searchParams.set(key, "[REDACTED]");
822
- }
823
- if (parsedUrl.hash)
824
- parsedUrl.hash = redactSensitivePathSegmentsForDiagnostic(redactSensitiveText(parsedUrl.hash));
825
- return redactSensitiveText(parsedUrl.toString());
826
230
  }
827
- catch {
828
- return redactSensitivePathSegmentsForDiagnostic(redactSensitiveText(url));
829
- }
830
- }
831
- export function redactTimeoutPartialProgress(progress) {
832
- const redact = (value) => {
833
- if (typeof value === "string")
834
- return /^https?:\/\//i.test(value) ? sanitizeCurrentPageUrlForTimeoutDiagnostic(value) : redactSensitivePathSegmentsForDiagnostic(redactSensitiveText(value));
835
- if (Array.isArray(value))
836
- return value.map(redact);
837
- if (isRecord(value))
838
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, redact(item)]));
839
- return value;
840
- };
841
- return redact(progress);
842
- }
843
- export function formatTimeoutPartialProgressText(progress, pageTargetUnknown = false) {
844
- const lines = [`Timeout partial progress: ${progress.summary}`];
845
- const currentPageTitle = progress.currentPage?.title ? redactSensitivePathSegmentsForDiagnostic(redactSensitiveText(progress.currentPage.title)) : undefined;
846
- const currentPageUrl = progress.currentPage?.url ? sanitizeCurrentPageUrlForTimeoutDiagnostic(progress.currentPage.url) : undefined;
847
- if (currentPageTitle || currentPageUrl)
848
- lines.push(`Current page: ${[currentPageTitle, currentPageUrl].filter(Boolean).join(" — ")}`);
849
- if (progress.steps && progress.steps.length > 0) {
850
- const shownSteps = progress.steps.slice(0, 6);
851
- lines.push("Planned steps:");
852
- for (const step of shownSteps) {
853
- const commandText = redactSensitivePathSegmentsForDiagnostic(redactInvocationArgs(step.args).join(" "));
854
- const generatedFrom = step.generatedFrom ? `, generated from ${step.generatedFrom}` : "";
855
- lines.push(`- Step ${step.index} [${step.status}${generatedFrom}]: ${commandText}${step.reason ? ` — ${redactSensitivePathSegmentsForDiagnostic(redactSensitiveText(step.reason))}` : ""}`);
856
- }
857
- if (progress.steps.length > shownSteps.length)
858
- lines.push(`- ... ${progress.steps.length - shownSteps.length} more step${progress.steps.length - shownSteps.length === 1 ? "" : "s"} omitted`);
859
- }
860
- if (progress.retryStep?.retry?.args) {
861
- const payload = JSON.stringify({ ...progress.retryStep.retry, stdin: JSON.stringify([redactInvocationArgs(progress.retryStep.args)]) });
862
- lines.push(pageTargetUnknown
863
- ? `Retry candidate for step ${progress.retryStep.index}: ${payload}. Verify the current URL before running it.`
864
- : `Retry candidate for step ${progress.retryStep.index} (outcome unknown): ${payload}`);
865
- }
866
- for (const artifact of progress.artifacts)
867
- lines.push(`Artifact from step ${artifact.stepIndex}: ${redactSensitivePathSegmentsForDiagnostic(artifact.path)} (${artifact.exists ? `exists${typeof artifact.sizeBytes === "number" ? `, ${artifact.sizeBytes} bytes` : ""}` : "missing"})`);
868
- return lines.join("\n");
231
+ const snapshotData = await runSessionCommandData({
232
+ args: ["snapshot", "-i"],
233
+ cwd: options.cwd,
234
+ namespace: options.namespace,
235
+ sessionName: options.sessionName,
236
+ signal: options.signal,
237
+ });
238
+ return buildVisibleRefFallbackDiagnosticFromSnapshot({ snapshotData, target });
869
239
  }
240
+ export { collectTimeoutPartialProgress, formatTimeoutPartialProgressText, } from "./timeout-diagnostics.js";