pi-agent-browser-native 0.9.3 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +77 -5
- package/README.md +103 -81
- package/dist/extensions/agent-browser/index.js +43057 -2031
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +49 -26
- package/dist/extensions/agent-browser/lib/argv-grammar.js +82 -93
- package/dist/extensions/agent-browser/lib/bash-guard.js +129 -126
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +112 -51
- package/dist/extensions/agent-browser/lib/browser-execution-claim-codec.js +61 -0
- package/dist/extensions/agent-browser/lib/browser-execution-claim-storage.js +232 -0
- package/dist/extensions/agent-browser/lib/browser-execution-claims.js +202 -0
- package/dist/extensions/agent-browser/lib/browser-execution-identity.js +77 -0
- package/dist/extensions/agent-browser/lib/browser-journal-branch.js +98 -0
- package/dist/extensions/agent-browser/lib/browser-journal-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/browser-journal-projection.js +221 -0
- package/dist/extensions/agent-browser/lib/browser-journal-reader.js +89 -0
- package/dist/extensions/agent-browser/lib/browser-journal-replay.js +246 -0
- package/dist/extensions/agent-browser/lib/browser-journal.js +103 -394
- package/dist/extensions/agent-browser/lib/browser-legacy-pages.js +220 -0
- package/dist/extensions/agent-browser/lib/browser-legacy-projection.js +282 -0
- package/dist/extensions/agent-browser/lib/browser-legacy-types.js +1 -0
- package/dist/extensions/agent-browser/lib/browser-session-conversion-stream.js +217 -0
- package/dist/extensions/agent-browser/lib/browser-session-conversion.js +80 -335
- package/dist/extensions/agent-browser/lib/browser-transcript-contracts.js +35 -0
- package/dist/extensions/agent-browser/lib/browser-transcript-validation.js +166 -0
- package/dist/extensions/agent-browser/lib/browser-transcript.js +137 -82
- package/dist/extensions/agent-browser/lib/browser-value-parsing.js +15 -0
- package/dist/extensions/agent-browser/lib/command-capabilities.js +340 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +107 -88
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +49 -322
- package/dist/extensions/agent-browser/lib/config-policy.js +138 -343
- package/dist/extensions/agent-browser/lib/config-providers.js +71 -0
- package/dist/extensions/agent-browser/lib/config-validation.js +218 -0
- package/dist/extensions/agent-browser/lib/config.js +19 -7
- package/dist/extensions/agent-browser/lib/electron/cdp.js +15 -7
- package/dist/extensions/agent-browser/lib/electron/cleanup.js +134 -55
- package/dist/extensions/agent-browser/lib/electron/discovery-darwin.js +109 -0
- package/dist/extensions/agent-browser/lib/electron/discovery-desktop.js +136 -0
- package/dist/extensions/agent-browser/lib/electron/discovery-evidence.js +89 -0
- package/dist/extensions/agent-browser/lib/electron/discovery-linux.js +93 -0
- package/dist/extensions/agent-browser/lib/electron/discovery-packages.js +162 -0
- package/dist/extensions/agent-browser/lib/electron/discovery-types.js +2 -0
- package/dist/extensions/agent-browser/lib/electron/discovery.js +140 -549
- package/dist/extensions/agent-browser/lib/electron/launch-monitor.js +102 -0
- package/dist/extensions/agent-browser/lib/electron/launch-process.js +209 -0
- package/dist/extensions/agent-browser/lib/electron/launch-types.js +2 -0
- package/dist/extensions/agent-browser/lib/electron/launch.js +249 -354
- package/dist/extensions/agent-browser/lib/executable-path.js +21 -12
- package/dist/extensions/agent-browser/lib/execution-cwd.js +35 -18
- package/dist/extensions/agent-browser/lib/input-modes/electron.js +162 -121
- package/dist/extensions/agent-browser/lib/input-modes/job.js +116 -313
- package/dist/extensions/agent-browser/lib/input-modes/lookup-network-evidence.js +104 -0
- package/dist/extensions/agent-browser/lib/input-modes/lookup-plan.js +132 -0
- package/dist/extensions/agent-browser/lib/input-modes/lookup-redaction.js +57 -0
- package/dist/extensions/agent-browser/lib/input-modes/lookup-source-evidence.js +151 -0
- package/dist/extensions/agent-browser/lib/input-modes/lookup-workspace.js +142 -0
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +57 -412
- package/dist/extensions/agent-browser/lib/input-modes/params.js +63 -17
- package/dist/extensions/agent-browser/lib/input-modes/qa-analysis.js +268 -0
- package/dist/extensions/agent-browser/lib/input-modes/qa-presentation.js +117 -0
- package/dist/extensions/agent-browser/lib/input-modes/script-child.js +396 -0
- package/dist/extensions/agent-browser/lib/input-modes/script-protocol.js +86 -0
- package/dist/extensions/agent-browser/lib/input-modes/script-types.js +1 -0
- package/dist/extensions/agent-browser/lib/input-modes/script-validation.js +60 -0
- package/dist/extensions/agent-browser/lib/input-modes/script.js +60 -409
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +218 -128
- package/dist/extensions/agent-browser/lib/input-modes/shared.js +15 -4
- package/dist/extensions/agent-browser/lib/input-modes/types.js +56 -7
- package/dist/extensions/agent-browser/lib/json-schema.js +22 -10
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +17 -5
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +92 -425
- package/dist/extensions/agent-browser/lib/managed-session-posix-storage.js +58 -0
- package/dist/extensions/agent-browser/lib/managed-session-project-identity.js +244 -0
- package/dist/extensions/agent-browser/lib/managed-session-restore-context.js +84 -0
- package/dist/extensions/agent-browser/lib/managed-session-restore-policy.js +119 -0
- package/dist/extensions/agent-browser/lib/managed-session-restore-state.js +83 -0
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +168 -300
- package/dist/extensions/agent-browser/lib/managed-session-snapshot-records.js +277 -0
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +128 -305
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +56 -253
- package/dist/extensions/agent-browser/lib/native-confirmation.js +140 -0
- package/dist/extensions/agent-browser/lib/native-output.js +43 -9
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +106 -59
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-merge.js +14 -6
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +109 -57
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/browser-observation-result.js +82 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +164 -60
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostic-values.js +31 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +152 -781
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/electron-observation.js +202 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/electron-text-diagnostics.js +109 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/fill-diagnostics.js +119 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-action-recovery.js +147 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-actions.js +144 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-content.js +123 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-details.js +156 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-electron.js +179 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-evidence.js +39 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-missing-binary.js +89 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-recovery.js +97 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-redaction.js +106 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result-timeout.js +110 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +41 -538
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +199 -154
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/interaction-diagnostics.js +290 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-close.js +167 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-inspection.js +108 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +127 -205
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/native-helper-observation.js +65 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/observation-types.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/overlay-diagnostics.js +120 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +113 -30
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +109 -28
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter-data.js +150 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +138 -253
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-rendered-text.js +147 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +59 -24
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-artifacts.js +159 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-command.js +131 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-daemon.js +93 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-failure.js +42 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-guards.js +242 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-input.js +75 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-observations.js +92 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-page-selection.js +254 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-page-state.js +121 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-refs.js +130 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-resources.js +114 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-semantic.js +121 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-session-plan.js +222 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-timeouts.js +68 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +176 -1123
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-analysis.js +191 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-confirmation.js +152 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-diagnostics.js +182 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-electron.js +150 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-lifecycle-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-lifecycle-phase-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-lifecycle-phase.js +115 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-lifecycle.js +256 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-managed-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-managed.js +256 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-native-phase-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-native-phase.js +93 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-native.js +113 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-navigation-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-navigation.js +316 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-ownership.js +28 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-page-phase-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-page-phase.js +142 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-presentation-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-presentation.js +333 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-publication-phase-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-publication-phase.js +71 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-publication.js +201 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-receipts.js +183 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-refs.js +200 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output-tabs.js +142 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +18 -968
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.js +23 -13
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/recording-receipt-reconciliation.js +198 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/recording-recovery-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/recording-recovery-presentation.js +154 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/recording-recovery.js +68 -151
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/screenshot-capture.js +14 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/screenshot-observation.js +169 -46
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state-commands.js +130 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state-electron.js +210 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state-observations.js +61 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state-outcomes.js +187 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state-refs.js +171 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state-tabs.js +228 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +77 -765
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/text-visibility-diagnostics.js +182 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/timeout-diagnostics.js +256 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/timeout-progress.js +108 -0
- package/dist/extensions/agent-browser/lib/orchestration/code-artifact-observation.js +39 -0
- package/dist/extensions/agent-browser/lib/orchestration/code-image-observation.js +119 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/branch.js +92 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/cleanup-result.js +58 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/cleanup.js +78 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/discovery.js +106 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +48 -821
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/policy.js +86 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/probe-collection.js +69 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/probe-data.js +167 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/probe-presentation.js +164 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/probe.js +161 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/status.js +165 -0
- package/dist/extensions/agent-browser/lib/orchestration/execution-queue.js +21 -9
- package/dist/extensions/agent-browser/lib/orchestration/extension-artifact-preflight.js +211 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-artifacts.js +23 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-attachment-replay.js +99 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-branch-restore.js +275 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-code.js +280 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-command-admission.js +303 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-command-output.js +36 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-command-result.js +335 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-command-run.js +227 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-command-state.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-electron-ownership.js +204 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-execution-locks.js +139 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-execution-queue.js +24 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-host-coordination.js +98 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-host-run.js +295 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-intent-replay.js +41 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-invocation.js +7 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-lifecycle.js +102 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-managed-ownership.js +133 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-observation.js +120 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-prompt.js +180 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-recording.js +290 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-resource-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-resource-replay.js +269 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-result-state.js +113 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-runtime.js +146 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-script-leases.js +58 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-tool-boundary.js +62 -0
- package/dist/extensions/agent-browser/lib/orchestration/extension-version-check.js +54 -0
- package/dist/extensions/agent-browser/lib/orchestration/input-plan-types.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +261 -185
- package/dist/extensions/agent-browser/lib/orchestration/input-redaction.js +62 -0
- package/dist/extensions/agent-browser/lib/orchestration/native-config.js +144 -0
- package/dist/extensions/agent-browser/lib/orchestration/native-root-launch.js +77 -0
- package/dist/extensions/agent-browser/lib/orchestration/native-session-defaults.js +210 -157
- package/dist/extensions/agent-browser/lib/orchestration/operation-paths.js +124 -58
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +197 -99
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +199 -96
- package/dist/extensions/agent-browser/lib/owned-managed-session-context.js +55 -0
- package/dist/extensions/agent-browser/lib/page-target-batch.js +118 -0
- package/dist/extensions/agent-browser/lib/page-target-guards.js +50 -0
- package/dist/extensions/agent-browser/lib/page-target-navigation.js +104 -0
- package/dist/extensions/agent-browser/lib/page-target-validation.js +55 -246
- package/dist/extensions/agent-browser/lib/parsing.js +13 -4
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +133 -68
- package/dist/extensions/agent-browser/lib/playbook.js +16 -12
- package/dist/extensions/agent-browser/lib/process-child.js +284 -0
- package/dist/extensions/agent-browser/lib/process-environment.js +13 -6
- package/dist/extensions/agent-browser/lib/process-errors.js +23 -0
- package/dist/extensions/agent-browser/lib/process-identity.js +47 -15
- package/dist/extensions/agent-browser/lib/process-socket-storage.js +256 -0
- package/dist/extensions/agent-browser/lib/process-stdout.js +74 -0
- package/dist/extensions/agent-browser/lib/process.js +145 -536
- package/dist/extensions/agent-browser/lib/prompt-artifacts.js +287 -0
- package/dist/extensions/agent-browser/lib/prompt-policy.js +15 -194
- package/dist/extensions/agent-browser/lib/read-command.js +41 -0
- package/dist/extensions/agent-browser/lib/read-confirmation.js +131 -68
- package/dist/extensions/agent-browser/lib/recording-reservations.js +118 -45
- package/dist/extensions/agent-browser/lib/redaction-fields.js +75 -0
- package/dist/extensions/agent-browser/lib/redaction-json.js +149 -0
- package/dist/extensions/agent-browser/lib/redaction-url.js +88 -0
- package/dist/extensions/agent-browser/lib/results/action-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +28 -271
- package/dist/extensions/agent-browser/lib/results/artifact-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/results/artifact-manifest-validation.js +133 -0
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +96 -94
- package/dist/extensions/agent-browser/lib/results/artifact-recommendations.js +73 -0
- package/dist/extensions/agent-browser/lib/results/categories.js +111 -54
- package/dist/extensions/agent-browser/lib/results/confirmation.js +29 -13
- package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +62 -27
- package/dist/extensions/agent-browser/lib/results/electron-recommendations.js +83 -0
- package/dist/extensions/agent-browser/lib/results/envelope-decoding.js +48 -0
- package/dist/extensions/agent-browser/lib/results/envelope.js +90 -67
- package/dist/extensions/agent-browser/lib/results/evidence-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/results/failure-recommendations.js +180 -0
- package/dist/extensions/agent-browser/lib/results/network-routes.js +86 -45
- package/dist/extensions/agent-browser/lib/results/network.js +35 -12
- package/dist/extensions/agent-browser/lib/results/next-actions.js +49 -19
- package/dist/extensions/agent-browser/lib/results/presentation/artifact-files.js +320 -0
- package/dist/extensions/agent-browser/lib/results/presentation/artifact-format.js +138 -0
- package/dist/extensions/agent-browser/lib/results/presentation/artifact-images.js +113 -0
- package/dist/extensions/agent-browser/lib/results/presentation/artifact-verification.js +145 -0
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +99 -555
- package/dist/extensions/agent-browser/lib/results/presentation/batch-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/results/presentation/batch-recording.js +118 -0
- package/dist/extensions/agent-browser/lib/results/presentation/batch-redaction.js +68 -0
- package/dist/extensions/agent-browser/lib/results/presentation/batch-rendering.js +135 -0
- package/dist/extensions/agent-browser/lib/results/presentation/batch-step.js +227 -0
- package/dist/extensions/agent-browser/lib/results/presentation/batch-summary.js +91 -0
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +91 -460
- package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +9 -5
- package/dist/extensions/agent-browser/lib/results/presentation/command-presenters.js +216 -0
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +26 -9
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +86 -29
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-a11y.js +67 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-health.js +79 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-identity.js +199 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-logs.js +75 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-network-actions.js +216 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-network.js +124 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-redaction.js +64 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-storage.js +209 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-stream.js +69 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostic-summary.js +222 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +103 -1052
- package/dist/extensions/agent-browser/lib/results/presentation/early-result.js +70 -0
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +130 -60
- package/dist/extensions/agent-browser/lib/results/presentation/input-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +60 -145
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +147 -88
- package/dist/extensions/agent-browser/lib/results/presentation/notices.js +75 -0
- package/dist/extensions/agent-browser/lib/results/presentation/observation-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/results/presentation/observation-rendering.js +127 -0
- package/dist/extensions/agent-browser/lib/results/presentation/outcomes.js +120 -0
- package/dist/extensions/agent-browser/lib/results/presentation/output-completion.js +77 -0
- package/dist/extensions/agent-browser/lib/results/presentation/output-spill.js +51 -0
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +116 -240
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +35 -21
- package/dist/extensions/agent-browser/lib/results/presentation/skills.js +104 -73
- package/dist/extensions/agent-browser/lib/results/presentation/source.js +109 -0
- package/dist/extensions/agent-browser/lib/results/presentation.js +17 -261
- package/dist/extensions/agent-browser/lib/results/recommendation-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/results/recording.js +52 -14
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +14 -5
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +30 -22
- package/dist/extensions/agent-browser/lib/results/selector-recovery-target.js +83 -0
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +119 -119
- package/dist/extensions/agent-browser/lib/results/snapshot-compact.js +266 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-content.js +32 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +104 -71
- package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +31 -11
- package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +125 -66
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +26 -7
- package/dist/extensions/agent-browser/lib/results/snapshot.js +28 -248
- package/dist/extensions/agent-browser/lib/results/text.js +16 -5
- package/dist/extensions/agent-browser/lib/runtime-args-validation.js +133 -0
- package/dist/extensions/agent-browser/lib/runtime-compatibility.js +81 -0
- package/dist/extensions/agent-browser/lib/runtime-contracts.js +1 -0
- package/dist/extensions/agent-browser/lib/runtime-redaction.js +118 -0
- package/dist/extensions/agent-browser/lib/runtime-replay-events.js +144 -0
- package/dist/extensions/agent-browser/lib/runtime-session-identity.js +76 -0
- package/dist/extensions/agent-browser/lib/runtime-session-plan.js +114 -0
- package/dist/extensions/agent-browser/lib/runtime-session-replay.js +123 -0
- package/dist/extensions/agent-browser/lib/runtime-tab-correction.js +65 -0
- package/dist/extensions/agent-browser/lib/runtime-timeouts.js +28 -0
- package/dist/extensions/agent-browser/lib/runtime.js +137 -1041
- package/dist/extensions/agent-browser/lib/session-identity.js +51 -0
- package/dist/extensions/agent-browser/lib/session-page-observation.js +357 -0
- package/dist/extensions/agent-browser/lib/session-page-state.js +178 -333
- package/dist/extensions/agent-browser/lib/session-page-types.js +1 -0
- package/dist/extensions/agent-browser/lib/temp-artifact-budget.js +78 -0
- package/dist/extensions/agent-browser/lib/temp-mutation-queue.js +7 -0
- package/dist/extensions/agent-browser/lib/temp-root-ownership.js +234 -0
- package/dist/extensions/agent-browser/lib/temp-root.js +207 -0
- package/dist/extensions/agent-browser/lib/temp.js +33 -446
- package/dist/extensions/agent-browser/lib/tool-surface.js +94 -24
- package/dist/extensions/agent-browser/lib/upstream-version.js +3 -2
- package/dist/extensions/agent-browser/lib/web-search-providers.js +229 -0
- package/dist/extensions/agent-browser/lib/web-search-request.js +108 -0
- package/dist/extensions/agent-browser/lib/web-search-results.js +281 -0
- package/dist/extensions/agent-browser/lib/web-search-schema.js +82 -0
- package/dist/extensions/agent-browser/lib/web-search.js +109 -617
- package/dist/extensions/agent-browser/lib/webmcp-observation.js +13 -6
- package/dist/extensions/agent-browser/lib/windows-stock-launcher.js +63 -37
- package/dist/extensions/agent-browser/script-worker.js +80 -43
- package/dist/scripts/agent-browser-target.mjs +4 -2
- package/docs/ARCHITECTURE.md +17 -5
- package/docs/CODE_QUALITY.md +242 -0
- package/docs/COMMAND_REFERENCE.md +251 -220
- package/docs/ELECTRON.md +53 -44
- package/docs/RELEASE.md +18 -15
- package/docs/REQUIREMENTS.md +1 -0
- package/docs/SUPPORT_MATRIX.md +76 -75
- package/docs/TOOL_CONTRACT.md +145 -116
- package/docs/platform-smoke.md +5 -5
- package/package.json +143 -120
- package/scripts/agent-browser-capability-baseline.mjs +361 -957
- package/scripts/agent-browser-target.mjs +6 -2
- package/scripts/build.mjs +37 -8
- package/scripts/capability-core-commands.mjs +193 -0
- package/scripts/capability-options.mjs +283 -0
- package/scripts/capability-samples.mjs +133 -0
- package/scripts/capability-section.mjs +11 -0
- package/scripts/config.mjs +213 -114
- package/scripts/convert-browser-session.mjs +30 -9
- package/scripts/doctor-sources.mjs +195 -0
- package/scripts/doctor.mjs +92 -247
- package/scripts/platform-smoke/artifacts.mjs +38 -11
- package/scripts/platform-smoke/commands.mjs +104 -0
- package/scripts/platform-smoke/crabbox-runner.mjs +167 -80
- package/scripts/platform-smoke/doctor-support.mjs +157 -0
- package/scripts/platform-smoke/doctor-windows.mjs +230 -0
- package/scripts/platform-smoke/doctor.mjs +212 -320
- package/scripts/platform-smoke/lease-evidence.mjs +134 -0
- package/scripts/platform-smoke/suite-checks.mjs +98 -0
- package/scripts/platform-smoke/suite-evidence.mjs +281 -0
- package/scripts/platform-smoke/targets.mjs +158 -472
- package/scripts/platform-smoke.mjs +55 -40
- 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
|
|
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 {
|
|
9
|
-
import {
|
|
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 {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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({
|
|
22
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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({
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
|
337
|
-
|
|
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
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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 (
|
|
110
|
+
if (trimmed === undefined || trimmed === "") {
|
|
455
111
|
return false;
|
|
456
|
-
|
|
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" ||
|
|
466
|
-
|
|
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
|
|
470
|
-
|
|
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
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
521
|
-
if (!
|
|
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
|
|
601
|
-
if (
|
|
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
|
-
|
|
200
|
+
const explicitArtifactPaths = await collectExistingExplicitArtifactPaths(explicitEntries, options.cwd);
|
|
201
|
+
if (explicitArtifactPaths.length === 0) {
|
|
655
202
|
return undefined;
|
|
656
203
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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
|
|
667
|
-
if (!
|
|
211
|
+
export function formatArtifactCleanupGuidanceText(guidance) {
|
|
212
|
+
if (!guidance || guidance.explicitArtifactPaths.length === 0) {
|
|
668
213
|
return undefined;
|
|
669
|
-
|
|
670
|
-
const
|
|
671
|
-
|
|
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 (
|
|
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
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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";
|