pi-agent-browser-native 0.2.48 → 0.2.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +16 -6
  3. package/dist/extensions/agent-browser/index.js +785 -0
  4. package/dist/extensions/agent-browser/lib/argv-descriptor.js +71 -0
  5. package/dist/extensions/agent-browser/lib/argv-grammar.js +121 -0
  6. package/dist/extensions/agent-browser/lib/bash-guard.js +190 -0
  7. package/dist/extensions/agent-browser/lib/command-policy.js +85 -0
  8. package/dist/extensions/agent-browser/lib/command-taxonomy.js +302 -0
  9. package/dist/extensions/agent-browser/lib/config-policy.js +686 -0
  10. package/dist/extensions/agent-browser/lib/config.js +122 -0
  11. package/dist/extensions/agent-browser/lib/electron/cdp.js +51 -0
  12. package/dist/extensions/agent-browser/lib/electron/cleanup.js +212 -0
  13. package/dist/extensions/agent-browser/lib/electron/discovery.js +633 -0
  14. package/dist/extensions/agent-browser/lib/electron/launch.js +351 -0
  15. package/{extensions/agent-browser/lib/electron/text.ts → dist/extensions/agent-browser/lib/electron/text.js} +5 -5
  16. package/dist/extensions/agent-browser/lib/executable-path.js +20 -0
  17. package/dist/extensions/agent-browser/lib/fs-utils.js +18 -0
  18. package/dist/extensions/agent-browser/lib/input-modes/electron.js +165 -0
  19. package/dist/extensions/agent-browser/lib/input-modes/job.js +519 -0
  20. package/dist/extensions/agent-browser/lib/input-modes/lookups.js +440 -0
  21. package/dist/extensions/agent-browser/lib/input-modes/params.js +164 -0
  22. package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +119 -0
  23. package/dist/extensions/agent-browser/lib/input-modes/shared.js +42 -0
  24. package/dist/extensions/agent-browser/lib/input-modes/types.js +21 -0
  25. package/dist/extensions/agent-browser/lib/input-modes.js +10 -0
  26. package/dist/extensions/agent-browser/lib/json-schema.js +58 -0
  27. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +59 -0
  28. package/dist/extensions/agent-browser/lib/navigation-policy.js +83 -0
  29. package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +62 -0
  30. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +39 -0
  31. package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +276 -0
  32. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +909 -0
  33. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +443 -0
  34. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +47 -0
  35. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +141 -0
  36. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +108 -0
  37. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +112 -0
  38. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +158 -0
  39. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +54 -0
  40. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +762 -0
  41. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +491 -0
  42. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.js +40 -0
  43. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +5 -0
  44. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +731 -0
  45. package/dist/extensions/agent-browser/lib/orchestration/browser-run/types.js +1 -0
  46. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +718 -0
  47. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +247 -0
  48. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +68 -0
  49. package/{extensions/agent-browser/lib/parsing.ts → dist/extensions/agent-browser/lib/parsing.js} +12 -11
  50. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +241 -0
  51. package/dist/extensions/agent-browser/lib/playbook.js +121 -0
  52. package/dist/extensions/agent-browser/lib/process.js +448 -0
  53. package/dist/extensions/agent-browser/lib/prompt-policy.js +91 -0
  54. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +220 -0
  55. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +111 -0
  56. package/{extensions/agent-browser/lib/results/artifact-state.ts → dist/extensions/agent-browser/lib/results/artifact-state.js} +4 -8
  57. package/dist/extensions/agent-browser/lib/results/categories.js +76 -0
  58. package/dist/extensions/agent-browser/lib/results/confirmation.js +63 -0
  59. package/dist/extensions/agent-browser/lib/results/contracts.js +8 -0
  60. package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +74 -0
  61. package/dist/extensions/agent-browser/lib/results/envelope.js +166 -0
  62. package/dist/extensions/agent-browser/lib/results/network-routes.js +92 -0
  63. package/dist/extensions/agent-browser/lib/results/network.js +73 -0
  64. package/dist/extensions/agent-browser/lib/results/next-actions.js +72 -0
  65. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +515 -0
  66. package/dist/extensions/agent-browser/lib/results/presentation/batch.js +397 -0
  67. package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +55 -0
  68. package/dist/extensions/agent-browser/lib/results/presentation/common.js +46 -0
  69. package/dist/extensions/agent-browser/lib/results/presentation/content.js +24 -0
  70. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +960 -0
  71. package/dist/extensions/agent-browser/lib/results/presentation/errors.js +205 -0
  72. package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +134 -0
  73. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +159 -0
  74. package/dist/extensions/agent-browser/lib/results/presentation/registry.js +216 -0
  75. package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +104 -0
  76. package/dist/extensions/agent-browser/lib/results/presentation/skills.js +152 -0
  77. package/dist/extensions/agent-browser/lib/results/presentation.js +177 -0
  78. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +107 -0
  79. package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +50 -0
  80. package/dist/extensions/agent-browser/lib/results/selector-recovery.js +225 -0
  81. package/{extensions/agent-browser/lib/results/shared.ts → dist/extensions/agent-browser/lib/results/shared.js} +0 -1
  82. package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +208 -0
  83. package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +78 -0
  84. package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +331 -0
  85. package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +40 -0
  86. package/dist/extensions/agent-browser/lib/results/snapshot.js +264 -0
  87. package/dist/extensions/agent-browser/lib/results/text.js +40 -0
  88. package/{extensions/agent-browser/lib/results.ts → dist/extensions/agent-browser/lib/results.js} +2 -32
  89. package/dist/extensions/agent-browser/lib/runtime.js +816 -0
  90. package/dist/extensions/agent-browser/lib/session-page-state.js +411 -0
  91. package/dist/extensions/agent-browser/lib/string-enum-schema.js +13 -0
  92. package/dist/extensions/agent-browser/lib/temp.js +498 -0
  93. package/dist/extensions/agent-browser/lib/web-search.js +562 -0
  94. package/docs/RELEASE.md +22 -11
  95. package/docs/SUPPORT_MATRIX.md +4 -3
  96. package/package.json +9 -5
  97. package/scripts/config.mjs +8 -2
  98. package/scripts/doctor.mjs +8 -7
  99. package/extensions/agent-browser/index.ts +0 -961
  100. package/extensions/agent-browser/lib/argv-descriptor.ts +0 -90
  101. package/extensions/agent-browser/lib/argv-grammar.ts +0 -128
  102. package/extensions/agent-browser/lib/bash-guard.ts +0 -205
  103. package/extensions/agent-browser/lib/command-policy.ts +0 -71
  104. package/extensions/agent-browser/lib/command-taxonomy.ts +0 -336
  105. package/extensions/agent-browser/lib/config-policy.js +0 -690
  106. package/extensions/agent-browser/lib/config.ts +0 -211
  107. package/extensions/agent-browser/lib/electron/cdp.ts +0 -69
  108. package/extensions/agent-browser/lib/electron/cleanup.ts +0 -235
  109. package/extensions/agent-browser/lib/electron/discovery.ts +0 -710
  110. package/extensions/agent-browser/lib/electron/launch.ts +0 -499
  111. package/extensions/agent-browser/lib/executable-path.ts +0 -19
  112. package/extensions/agent-browser/lib/fs-utils.ts +0 -18
  113. package/extensions/agent-browser/lib/input-modes/electron.ts +0 -170
  114. package/extensions/agent-browser/lib/input-modes/job.ts +0 -527
  115. package/extensions/agent-browser/lib/input-modes/lookups.ts +0 -447
  116. package/extensions/agent-browser/lib/input-modes/params.ts +0 -205
  117. package/extensions/agent-browser/lib/input-modes/semantic-action.ts +0 -127
  118. package/extensions/agent-browser/lib/input-modes/shared.ts +0 -46
  119. package/extensions/agent-browser/lib/input-modes/types.ts +0 -225
  120. package/extensions/agent-browser/lib/input-modes.ts +0 -45
  121. package/extensions/agent-browser/lib/json-schema.ts +0 -73
  122. package/extensions/agent-browser/lib/launch-scoped-flags.ts +0 -67
  123. package/extensions/agent-browser/lib/navigation-policy.ts +0 -95
  124. package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +0 -65
  125. package/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.ts +0 -44
  126. package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +0 -280
  127. package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -914
  128. package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -521
  129. package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
  130. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.ts +0 -158
  131. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.ts +0 -116
  132. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.ts +0 -147
  133. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.ts +0 -183
  134. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.ts +0 -58
  135. package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -847
  136. package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -559
  137. package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
  138. package/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.ts +0 -8
  139. package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
  140. package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -565
  141. package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +0 -855
  142. package/extensions/agent-browser/lib/orchestration/input-plan.ts +0 -375
  143. package/extensions/agent-browser/lib/orchestration/output-file.ts +0 -86
  144. package/extensions/agent-browser/lib/pi-tool-rendering.ts +0 -267
  145. package/extensions/agent-browser/lib/playbook.ts +0 -142
  146. package/extensions/agent-browser/lib/process.ts +0 -516
  147. package/extensions/agent-browser/lib/prompt-policy.ts +0 -105
  148. package/extensions/agent-browser/lib/results/action-recommendations.ts +0 -264
  149. package/extensions/agent-browser/lib/results/artifact-manifest.ts +0 -111
  150. package/extensions/agent-browser/lib/results/categories.ts +0 -106
  151. package/extensions/agent-browser/lib/results/confirmation.ts +0 -76
  152. package/extensions/agent-browser/lib/results/contracts.ts +0 -241
  153. package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +0 -72
  154. package/extensions/agent-browser/lib/results/envelope.ts +0 -195
  155. package/extensions/agent-browser/lib/results/network-routes.ts +0 -83
  156. package/extensions/agent-browser/lib/results/network.ts +0 -78
  157. package/extensions/agent-browser/lib/results/next-actions.ts +0 -117
  158. package/extensions/agent-browser/lib/results/presentation/artifacts.ts +0 -588
  159. package/extensions/agent-browser/lib/results/presentation/batch.ts +0 -450
  160. package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +0 -67
  161. package/extensions/agent-browser/lib/results/presentation/common.ts +0 -53
  162. package/extensions/agent-browser/lib/results/presentation/content.ts +0 -36
  163. package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +0 -923
  164. package/extensions/agent-browser/lib/results/presentation/errors.ts +0 -227
  165. package/extensions/agent-browser/lib/results/presentation/large-output.ts +0 -182
  166. package/extensions/agent-browser/lib/results/presentation/navigation.ts +0 -184
  167. package/extensions/agent-browser/lib/results/presentation/registry.ts +0 -242
  168. package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +0 -131
  169. package/extensions/agent-browser/lib/results/presentation/skills.ts +0 -143
  170. package/extensions/agent-browser/lib/results/presentation.ts +0 -257
  171. package/extensions/agent-browser/lib/results/recovery-actions.ts +0 -139
  172. package/extensions/agent-browser/lib/results/recovery-next-actions.ts +0 -71
  173. package/extensions/agent-browser/lib/results/selector-recovery.ts +0 -320
  174. package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +0 -273
  175. package/extensions/agent-browser/lib/results/snapshot-refs.ts +0 -100
  176. package/extensions/agent-browser/lib/results/snapshot-segments.ts +0 -366
  177. package/extensions/agent-browser/lib/results/snapshot-spill.ts +0 -63
  178. package/extensions/agent-browser/lib/results/snapshot.ts +0 -329
  179. package/extensions/agent-browser/lib/results/text.ts +0 -40
  180. package/extensions/agent-browser/lib/runtime.ts +0 -988
  181. package/extensions/agent-browser/lib/session-page-state.ts +0 -512
  182. package/extensions/agent-browser/lib/string-enum-schema.ts +0 -20
  183. package/extensions/agent-browser/lib/temp.ts +0 -577
  184. package/extensions/agent-browser/lib/web-search.ts +0 -728
  185. /package/{extensions/agent-browser/lib/orchestration/browser-run.ts → dist/extensions/agent-browser/lib/orchestration/browser-run.js} +0 -0
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Purpose: Derive operator prompt constraints for browser-run preflight guards and legacy bash policy.
3
+ * Responsibilities: Parse the latest user message into requested artifact paths and legacy bash allowance.
4
+ * Scope: Pure prompt-text policy; enforcement lives in orchestration prompt-guards and the extension entrypoint.
5
+ */
6
+ const BROWSER_PROMPT_PATTERNS = [
7
+ /\b(?:agent[_ -]?browser|browser automation|eval\s+--stdin|screenshot|snapshot|tab\s+list)\b/i,
8
+ /\b(?:react\s+(?:tree|inspect|renders|suspense)|web\s+vitals|core\s+web\s+vitals|pushstate)\b/i,
9
+ /\b(?:live\s+docs?|online\s+research|research\s+(?:online|the\s+web)|search\s+(?:online|the\s+web)|web\s+research)\b/i,
10
+ /\bbrowser\b.*\b(?:automation|click|fill|navigate|open|page|screenshot|site|snapshot|tab|url|visit|web(?:site| page)?)\b/i,
11
+ /\b(?:browse|click|fill|login|navigate|open|visit)\b.*\b(?:https?:\/\/\S+|page|site|tab|url|web(?:site| page)?)\b/i,
12
+ ];
13
+ const LEGACY_BASH_ALLOW_PATTERNS = [
14
+ /\b(?:bash-oriented workflow|bash workflow)\b/i,
15
+ /\b(?:use|via|through|with)\s+bash\b/i,
16
+ /\bnpx\s+agent-browser\b/i,
17
+ /\bagent-browser\s+--(?:help|version)\b/i,
18
+ /\bdebug(?:ging)?\b.*\b(?:agent[_ -]?browser|agent_browser|browser integration)\b/i,
19
+ ];
20
+ const PROMPT_ARTIFACT_PATH_PATTERN = /(?:^|[\s"'`(:])((?:\/[^\s"'`),;]+|[A-Za-z]:[\\/][^\s"'`),;]+|\.{1,2}[\\/][^\s"'`),;]+|[^\s"'`),;:\\/]+(?:[\\/][^\s"'`),;]+)+|[^\s"'`),;:\\/]+)\.(?:png|jpe?g|webp|gif|webm|mp4|har|pdf|trace|json))(?:[\s"'`),;.]|$)/gi;
21
+ function extractPromptRequestedArtifacts(prompt) {
22
+ const artifacts = [];
23
+ const seen = new Set();
24
+ for (const line of prompt.split(/\r?\n/)) {
25
+ const lowerLine = line.toLowerCase();
26
+ const kind = lowerLine.includes("screenshot")
27
+ ? "screenshot"
28
+ : /\b(?:screen\s+recording|recording|webm|video)\b/.test(lowerLine)
29
+ ? "recording"
30
+ : undefined;
31
+ if (!kind)
32
+ continue;
33
+ PROMPT_ARTIFACT_PATH_PATTERN.lastIndex = 0;
34
+ for (const match of line.matchAll(PROMPT_ARTIFACT_PATH_PATTERN)) {
35
+ const path = match[1]?.trim();
36
+ if (!path)
37
+ continue;
38
+ const key = `${kind}:${path}`;
39
+ if (seen.has(key))
40
+ continue;
41
+ seen.add(key);
42
+ artifacts.push({
43
+ kind,
44
+ path,
45
+ required: kind === "screenshot" || !/\b(?:if|when)\s+(?:recording\s+)?(?:is\s+)?available\b/i.test(line),
46
+ });
47
+ }
48
+ }
49
+ return artifacts;
50
+ }
51
+ export function buildPromptPolicy(prompt) {
52
+ return {
53
+ allowLegacyAgentBrowserBash: LEGACY_BASH_ALLOW_PATTERNS.some((pattern) => pattern.test(prompt)),
54
+ requestedArtifacts: extractPromptRequestedArtifacts(prompt),
55
+ };
56
+ }
57
+ function getMessageText(content) {
58
+ if (typeof content === "string")
59
+ return content;
60
+ if (!Array.isArray(content))
61
+ return "";
62
+ return content
63
+ .map((item) => {
64
+ if (typeof item !== "object" || item === null)
65
+ return "";
66
+ return item.type === "text" && typeof item.text === "string" ? item.text : "";
67
+ })
68
+ .filter((text) => text.length > 0)
69
+ .join("\n");
70
+ }
71
+ export function shouldAppendBrowserSystemPrompt(prompt) {
72
+ const normalizedPrompt = prompt.trim();
73
+ if (normalizedPrompt.length === 0) {
74
+ return false;
75
+ }
76
+ return BROWSER_PROMPT_PATTERNS.some((pattern) => pattern.test(normalizedPrompt));
77
+ }
78
+ export function getLatestUserPrompt(branch) {
79
+ for (let index = branch.length - 1; index >= 0; index -= 1) {
80
+ const entry = branch[index];
81
+ if (typeof entry !== "object" || entry === null || !("type" in entry) || entry.type !== "message") {
82
+ continue;
83
+ }
84
+ const message = "message" in entry ? entry.message : undefined;
85
+ if (typeof message !== "object" || message === null || !("role" in message) || message.role !== "user") {
86
+ continue;
87
+ }
88
+ return getMessageText("content" in message ? message.content : undefined);
89
+ }
90
+ return "";
91
+ }
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Purpose: Build generic nextAction recommendations from result categories, artifacts, Electron lifecycle state, and recovery context.
3
+ * Responsibilities: Preserve stable action ids/order while keeping recommendation policy out of generic shared helpers.
4
+ * Scope: Generic result-level recommendations only; feature-specific diagnostics append their own actions in the extension entrypoint.
5
+ * Usage: Called by presentation and extension result assembly.
6
+ * Invariants/Assumptions: Action ids are public machine-readable contracts; preserve first-observed order.
7
+ */
8
+ import { isOpenNavigationCommand, isPageMutationCommand } from "../command-taxonomy.js";
9
+ import { isPendingRecordingArtifact } from "./artifact-state.js";
10
+ import { buildNextToolAction } from "./next-actions.js";
11
+ import { AGENT_BROWSER_RECOVERY_NEXT_ACTION_IDS, buildRecoveryNextActions, } from "./recovery-actions.js";
12
+ function buildArtifactAction(path) {
13
+ return {
14
+ artifactPath: path,
15
+ id: "use-saved-artifact",
16
+ reason: "Use the saved artifact path from the structured result instead of scraping it from text.",
17
+ safety: "Verify artifact metadata such as exists/status before treating the file as durable.",
18
+ tool: "agent_browser",
19
+ };
20
+ }
21
+ function buildArtifactVerificationAction(artifact) {
22
+ return {
23
+ artifactPath: artifact.path,
24
+ id: "verify-artifact-path",
25
+ reason: "The wrapper has artifact metadata but did not verify this file as present on disk.",
26
+ safety: "Check details.artifactVerification and the filesystem before treating the artifact as durable.",
27
+ tool: "agent_browser",
28
+ };
29
+ }
30
+ function buildElectronToolAction(options) {
31
+ return {
32
+ id: options.id,
33
+ params: { electron: { action: options.action, launchId: options.launchId } },
34
+ reason: options.reason,
35
+ ...(options.safety ? { safety: options.safety } : {}),
36
+ tool: "agent_browser",
37
+ };
38
+ }
39
+ function getDownloadRetryPath(args, fallback) {
40
+ if (fallback)
41
+ return fallback;
42
+ if (!args || args.length === 0)
43
+ return undefined;
44
+ const downloadFlagIndex = args.indexOf("--download");
45
+ if (downloadFlagIndex >= 0) {
46
+ const candidate = args[downloadFlagIndex + 1];
47
+ return candidate && !candidate.startsWith("-") ? candidate : undefined;
48
+ }
49
+ const downloadCommandIndex = args.indexOf("download");
50
+ if (downloadCommandIndex >= 0 && args.length > downloadCommandIndex + 2) {
51
+ return args[args.length - 1];
52
+ }
53
+ return undefined;
54
+ }
55
+ export function buildAgentBrowserNextActions(options) {
56
+ const actions = [];
57
+ if (options.recovery) {
58
+ actions.push(...buildRecoveryNextActions(options.recovery));
59
+ }
60
+ if (options.electron?.launchId) {
61
+ const { launchId, sessionName, status } = options.electron;
62
+ if (options.resultCategory === "success" && status !== "cleaned") {
63
+ actions.push(buildElectronToolAction({
64
+ action: "status",
65
+ id: "status-electron-launch",
66
+ launchId,
67
+ reason: "Check the wrapper-tracked Electron launch liveness and current CDP targets without mutating the app.",
68
+ }), buildElectronToolAction({
69
+ action: "probe",
70
+ id: "probe-electron-launch",
71
+ launchId,
72
+ reason: "Probe the attached Electron managed session and carry the wrapper launchId for follow-up diagnostics.",
73
+ }), buildElectronToolAction({
74
+ action: "cleanup",
75
+ id: "cleanup-electron-launch",
76
+ launchId,
77
+ reason: "Clean the wrapper-owned Electron process and isolated userDataDir when the run is complete.",
78
+ safety: "Only operates on the launchId created by electron.launch; explicit artifacts and manually launched apps remain host-owned.",
79
+ }));
80
+ if (sessionName) {
81
+ actions.push(buildNextToolAction({
82
+ args: ["--session", sessionName, "tab", "list"],
83
+ id: "list-electron-tabs",
84
+ reason: "Inspect attached Electron page/webview targets before choosing the active tab.",
85
+ }), buildNextToolAction({
86
+ args: ["--session", sessionName, "snapshot", "-i"],
87
+ id: "snapshot-electron-session",
88
+ reason: "Refresh interactive refs for the attached Electron session.",
89
+ safety: "Use current Electron refs only after a fresh snapshot for this session.",
90
+ }));
91
+ }
92
+ }
93
+ else if (options.resultCategory === "failure" && options.failureCategory === "cleanup-failed") {
94
+ actions.push(buildElectronToolAction({
95
+ action: "status",
96
+ id: "status-electron-launch",
97
+ launchId,
98
+ reason: "Inspect which wrapper-tracked Electron resources remain after partial cleanup.",
99
+ }), buildElectronToolAction({
100
+ action: "cleanup",
101
+ id: "retry-electron-cleanup",
102
+ launchId,
103
+ reason: "Retry cleanup for the same wrapper-owned Electron launch after reviewing remaining resources.",
104
+ safety: "Only retry for the same launchId; do not use cleanup for manually launched Electron apps.",
105
+ }));
106
+ }
107
+ }
108
+ if (options.resultCategory === "success") {
109
+ if (isOpenNavigationCommand(options.command)) {
110
+ actions.push(buildNextToolAction({
111
+ args: ["snapshot", "-i"],
112
+ id: "inspect-opened-page",
113
+ reason: "Inspect the opened page before choosing interactive refs.",
114
+ }));
115
+ }
116
+ else if (isPageMutationCommand(options.command)) {
117
+ actions.push(buildNextToolAction({
118
+ args: ["snapshot", "-i"],
119
+ id: "inspect-after-mutation",
120
+ reason: "Refresh interactive refs after a browser mutation, navigation, scroll, or rerender.",
121
+ safety: "Do not reuse prior @refs until a fresh snapshot confirms they still exist.",
122
+ }));
123
+ }
124
+ const artifacts = options.artifacts ?? [];
125
+ const savedFileArtifact = options.savedFilePath ? artifacts.find((artifact) => artifact.path === options.savedFilePath) : undefined;
126
+ if (options.savedFilePath && savedFileArtifact?.exists !== false) {
127
+ actions.push(buildArtifactAction(options.savedFilePath));
128
+ }
129
+ for (const artifact of artifacts) {
130
+ if (isPendingRecordingArtifact(artifact)) {
131
+ continue;
132
+ }
133
+ if (artifact.exists === false) {
134
+ if (artifact.kind === "download") {
135
+ actions.push(buildNextToolAction({
136
+ args: ["wait", "--download", artifact.path],
137
+ id: "wait-for-download",
138
+ reason: "Upstream reported a download path, but the wrapper did not verify the file on disk.",
139
+ safety: "Use an explicit wait timeout; if you set top-level timeoutMs, keep it above the wait duration plus a small grace window.",
140
+ }));
141
+ }
142
+ else {
143
+ actions.push(buildArtifactVerificationAction(artifact));
144
+ }
145
+ continue;
146
+ }
147
+ if (artifact.path !== options.savedFilePath) {
148
+ actions.push(buildArtifactAction(artifact.path));
149
+ }
150
+ }
151
+ }
152
+ else {
153
+ switch (options.failureCategory) {
154
+ case "artifact-missing":
155
+ for (const artifact of options.artifacts ?? []) {
156
+ if (isPendingRecordingArtifact(artifact) || artifact.exists !== false)
157
+ continue;
158
+ if (artifact.kind === "download") {
159
+ actions.push(buildNextToolAction({
160
+ args: ["wait", "--download", artifact.path],
161
+ id: "wait-for-download",
162
+ reason: "The requested download artifact was not found on disk after upstream reported completion.",
163
+ safety: "Use an explicit wait timeout; if you set top-level timeoutMs, keep it above the wait duration plus a small grace window.",
164
+ }));
165
+ }
166
+ else {
167
+ actions.push(buildArtifactVerificationAction(artifact));
168
+ }
169
+ }
170
+ break;
171
+ case "confirmation-required":
172
+ if (options.confirmationId) {
173
+ actions.push(buildNextToolAction({
174
+ args: ["confirm", options.confirmationId],
175
+ id: "approve-confirmation",
176
+ reason: "Approve the pending upstream confirmation when the requested action is safe.",
177
+ safety: "Only confirm after reviewing the guarded action shown in the result.",
178
+ }), buildNextToolAction({
179
+ args: ["deny", options.confirmationId],
180
+ id: "deny-confirmation",
181
+ reason: "Deny the pending upstream confirmation when the guarded action is unsafe or unintended.",
182
+ }));
183
+ }
184
+ break;
185
+ case "stale-ref":
186
+ case "selector-not-found":
187
+ case "selector-unsupported":
188
+ actions.push(buildNextToolAction({
189
+ args: ["snapshot", "-i"],
190
+ id: "refresh-interactive-refs",
191
+ reason: "Get current interactive refs before retrying the element action.",
192
+ safety: "Prefer a current @ref or a stable find locator; do not retry stale refs blindly.",
193
+ }));
194
+ break;
195
+ case "download-not-verified":
196
+ {
197
+ const retryPath = getDownloadRetryPath(options.args, options.savedFilePath);
198
+ actions.push(buildNextToolAction({
199
+ args: retryPath ? ["wait", "--download", retryPath] : ["wait", "--download"],
200
+ id: "wait-for-download",
201
+ reason: "Wait for the browser download and let the wrapper verify saved-file metadata.",
202
+ safety: "Use an explicit wait timeout; if you set top-level timeoutMs, keep it above the wait duration plus a small grace window.",
203
+ }));
204
+ }
205
+ break;
206
+ case "tab-drift":
207
+ if (options.recovery?.kind === "about-blank" || options.recovery?.kind === "tab-drift") {
208
+ break;
209
+ }
210
+ actions.push(buildNextToolAction({
211
+ args: ["tab", "list"],
212
+ id: AGENT_BROWSER_RECOVERY_NEXT_ACTION_IDS.genericTabDriftListTabs,
213
+ reason: "Inspect available tabs before selecting the intended target.",
214
+ safety: "Read-only. Retry snapshot only after selecting or confirming the intended stable tab.",
215
+ }));
216
+ break;
217
+ }
218
+ }
219
+ return actions.length > 0 ? actions : undefined;
220
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Purpose: Own persistent session artifact manifest merge, retention, and validation logic.
3
+ * Responsibilities: Parse manifest bounds, recognize manifest entries, merge new artifact rows, and format retention summaries.
4
+ * Scope: Manifest accounting only; artifact detection and presentation live in presentation modules.
5
+ * Usage: Imported by presentation and snapshot artifact persistence paths.
6
+ * Invariants/Assumptions: Explicit-path artifacts are host-owned while persistent-session spill files are bounded by the manifest cap.
7
+ */
8
+ import { isRecord } from "../parsing.js";
9
+ export const SESSION_ARTIFACT_MANIFEST_VERSION = 1;
10
+ export const SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES_ENV = "PI_AGENT_BROWSER_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES";
11
+ export const DEFAULT_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES = 100;
12
+ function parsePositiveSafeInteger(value) {
13
+ if (value === undefined)
14
+ return undefined;
15
+ const parsed = Number(value);
16
+ if (!Number.isSafeInteger(parsed) || parsed <= 0)
17
+ return undefined;
18
+ return parsed;
19
+ }
20
+ export function getSessionArtifactManifestMaxEntries(env = process.env) {
21
+ return parsePositiveSafeInteger(env[SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES_ENV]) ?? DEFAULT_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES;
22
+ }
23
+ function isManifestEntry(value) {
24
+ if (!isRecord(value))
25
+ return false;
26
+ if (typeof value.path !== "string" || value.path.trim().length === 0)
27
+ return false;
28
+ if (typeof value.createdAtMs !== "number" || !Number.isFinite(value.createdAtMs))
29
+ return false;
30
+ if (!["evicted", "ephemeral", "live", "missing"].includes(String(value.retentionState)))
31
+ return false;
32
+ if (!["explicit-path", "persistent-session", "process-temp"].includes(String(value.storageScope)))
33
+ return false;
34
+ if (typeof value.kind !== "string" || value.kind.trim().length === 0)
35
+ return false;
36
+ return true;
37
+ }
38
+ export function isSessionArtifactManifest(value) {
39
+ if (!isRecord(value))
40
+ return false;
41
+ if (value.version !== SESSION_ARTIFACT_MANIFEST_VERSION)
42
+ return false;
43
+ if (!Array.isArray(value.entries) || !value.entries.every(isManifestEntry))
44
+ return false;
45
+ if (typeof value.updatedAtMs !== "number" || !Number.isFinite(value.updatedAtMs))
46
+ return false;
47
+ if (typeof value.maxEntries !== "number" || !Number.isSafeInteger(value.maxEntries) || value.maxEntries <= 0)
48
+ return false;
49
+ if (typeof value.liveCount !== "number" || !Number.isSafeInteger(value.liveCount) || value.liveCount < 0)
50
+ return false;
51
+ if (typeof value.evictedCount !== "number" || !Number.isSafeInteger(value.evictedCount) || value.evictedCount < 0)
52
+ return false;
53
+ return true;
54
+ }
55
+ export function buildEvictedSessionArtifactEntries(evictedArtifacts, nowMs) {
56
+ return evictedArtifacts.map((artifact) => ({
57
+ createdAtMs: artifact.mtimeMs,
58
+ evictedAtMs: nowMs,
59
+ kind: "spill",
60
+ path: artifact.path,
61
+ retentionState: "evicted",
62
+ sizeBytes: artifact.sizeBytes,
63
+ storageScope: "persistent-session",
64
+ }));
65
+ }
66
+ export function formatSessionArtifactRetentionSummary(manifest) {
67
+ const ephemeralCount = manifest.entries.filter((entry) => entry.retentionState === "ephemeral").length;
68
+ const missingCount = manifest.entries.filter((entry) => entry.retentionState === "missing").length;
69
+ const parts = [`${manifest.liveCount} live`, `${manifest.evictedCount} evicted`];
70
+ if (ephemeralCount > 0)
71
+ parts.push(`${ephemeralCount} ephemeral`);
72
+ if (missingCount > 0)
73
+ parts.push(`${missingCount} missing`);
74
+ return `Session artifacts: ${parts.join(", ")} (${manifest.entries.length}/${manifest.maxEntries} recent).`;
75
+ }
76
+ export function mergeSessionArtifactManifest(options) {
77
+ const nowMs = options.nowMs ?? Date.now();
78
+ const maxEntries = getSessionArtifactManifestMaxEntries();
79
+ const getEntryKey = (entry) => entry.storageScope === "explicit-path" && entry.absolutePath ? `${entry.storageScope}:${entry.absolutePath}` : `${entry.storageScope}:${entry.path}`;
80
+ const byPath = new Map();
81
+ for (const entry of options.base?.entries ?? []) {
82
+ byPath.set(getEntryKey(entry), entry);
83
+ }
84
+ for (const entry of options.entries ?? []) {
85
+ const key = getEntryKey(entry);
86
+ const existing = byPath.get(key);
87
+ byPath.set(key, {
88
+ ...existing,
89
+ ...entry,
90
+ createdAtMs: existing?.createdAtMs ?? entry.createdAtMs,
91
+ evictedAtMs: entry.retentionState === "evicted" ? (entry.evictedAtMs ?? nowMs) : entry.evictedAtMs,
92
+ });
93
+ }
94
+ if (byPath.size === 0)
95
+ return undefined;
96
+ const entries = [...byPath.values()]
97
+ .sort((left, right) => {
98
+ const leftTime = left.evictedAtMs ?? left.createdAtMs;
99
+ const rightTime = right.evictedAtMs ?? right.createdAtMs;
100
+ return rightTime - leftTime || left.path.localeCompare(right.path);
101
+ })
102
+ .slice(0, maxEntries);
103
+ return {
104
+ entries,
105
+ evictedCount: entries.filter((entry) => entry.retentionState === "evicted").length,
106
+ liveCount: entries.filter((entry) => entry.retentionState === "live").length,
107
+ maxEntries,
108
+ updatedAtMs: nowMs,
109
+ version: SESSION_ARTIFACT_MANIFEST_VERSION,
110
+ };
111
+ }
@@ -5,13 +5,9 @@
5
5
  * Usage: Imported by categories, action recommendations, and presentation to avoid divergent artifact-state rules.
6
6
  * Invariants/Assumptions: `record start` / `record restart` video artifacts are pending and should not be treated like verified saved files.
7
7
  */
8
-
9
- import type { FileArtifactKind, FileArtifactMetadata } from "./contracts.js";
10
-
11
- export function isPendingRecordingCommand(command: string | undefined, subcommand: string | undefined, kind: FileArtifactKind | undefined): boolean {
12
- return command === "record" && (subcommand === "start" || subcommand === "restart") && kind === "video";
8
+ export function isPendingRecordingCommand(command, subcommand, kind) {
9
+ return command === "record" && (subcommand === "start" || subcommand === "restart") && kind === "video";
13
10
  }
14
-
15
- export function isPendingRecordingArtifact(artifact: FileArtifactMetadata): boolean {
16
- return isPendingRecordingCommand(artifact.command, artifact.subcommand, artifact.kind);
11
+ export function isPendingRecordingArtifact(artifact) {
12
+ return isPendingRecordingCommand(artifact.command, artifact.subcommand, artifact.kind);
17
13
  }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Purpose: Classify successful and failed agent-browser outcomes into stable result categories.
3
+ * Responsibilities: Map artifacts, inspection calls, errors, refs, selectors, downloads, drift, and timeouts to small enums.
4
+ * Scope: Category policy only; next-action recommendations and presentation formatting live elsewhere.
5
+ * Usage: Called by presentation and extension result assembly before details are exposed to Pi.
6
+ * Invariants/Assumptions: Category strings are public machine-readable contracts covered by tests and docs.
7
+ */
8
+ import { isPendingRecordingArtifact } from "./artifact-state.js";
9
+ function hasUnverifiedFileArtifact(artifacts) {
10
+ return (artifacts ?? []).some((artifact) => !isPendingRecordingArtifact(artifact) && artifact.exists !== true);
11
+ }
12
+ export function classifyAgentBrowserSuccessCategory(options) {
13
+ if (options.inspection)
14
+ return "inspection";
15
+ if ((options.artifacts ?? []).length > 0)
16
+ return hasUnverifiedFileArtifact(options.artifacts) ? "artifact-unverified" : "artifact-saved";
17
+ if (options.savedFile)
18
+ return "artifact-saved";
19
+ return "completed";
20
+ }
21
+ export function classifyAgentBrowserFailureCategory(options) {
22
+ const text = [options.errorText, options.validationError, options.parseError, options.spawnError, options.stderr].filter(Boolean).join("\n");
23
+ const command = options.command ?? "";
24
+ const usedRef = options.args?.some((arg) => /^@e\d+\b/.test(arg)) ?? false;
25
+ if (options.confirmationRequired || /confirmation required|pending confirmation|requires confirmation/i.test(text))
26
+ return "confirmation-required";
27
+ if (options.timedOut || /timeout|timed out|watchdog|IPC read timeout|must stay under its 30s IPC read timeout/i.test(text))
28
+ return "timeout";
29
+ if (/ENOENT|not found on PATH|could not find.*agent-browser|agent-browser is required but was not found/i.test(text))
30
+ return "missing-binary";
31
+ if (options.parseError || /invalid JSON|missing boolean success|success field must be boolean|returned no JSON output/i.test(text))
32
+ return "parse-failure";
33
+ if (/aborted/i.test(text))
34
+ return "aborted";
35
+ if (/policy[- ]blocked|blocked by caller policy|caller deny policy|caller allow policy/i.test(text))
36
+ return "policy-blocked";
37
+ if (/cleanup failed|cleanup.*partial|partial cleanup|remaining resources/i.test(text))
38
+ return "cleanup-failed";
39
+ if (options.validationError)
40
+ return "validation-error";
41
+ if (options.tabDrift || /could not re-select the intended tab|about:blank|selected tab looks wrong|tab drift|tab.*wrong/i.test(text))
42
+ return "tab-drift";
43
+ if (/\bUnknown ref\b|\bstale ref\b|@ref may be stale|\bref\b.*\b(?:not found|missing|expired)\b/i.test(text))
44
+ return "stale-ref";
45
+ if (usedRef && /could not locate element|element not found|no element/i.test(text))
46
+ return "stale-ref";
47
+ const mentionsPlaywrightSelectorDialect = /(?:\btext=|:has-text\(|\bgetByRole\b|\bgetByText\b)/i.test(text);
48
+ const reportsSelectorMatchFailure = /\b(?:no elements? found|failed to find|could not find|unable to find)\b.*\b(?:selector|locator)\b/i.test(text) ||
49
+ /\b(?:selector|locator)\b.*\b(?:no elements? found|not found|missing|failed to find|could not find|unable to find)\b/i.test(text);
50
+ if (/\b(?:unsupported|unknown|invalid)\s+(?:selector|locator)\b/i.test(text) ||
51
+ /\bfailed to parse selector\b/i.test(text) ||
52
+ /\bselector\b.*\b(?:parse|syntax|unsupported|invalid)\b/i.test(text) ||
53
+ (mentionsPlaywrightSelectorDialect && reportsSelectorMatchFailure)) {
54
+ return "selector-unsupported";
55
+ }
56
+ if (command === "find" && /could not locate element|element not found|no elements? found|unable to find/i.test(text))
57
+ return "selector-not-found";
58
+ if (reportsSelectorMatchFailure)
59
+ return "selector-not-found";
60
+ if ((command === "download" || text.includes("wait --download") || /\bdownload\b/i.test(text)) && /missing|not verified|not found|failed|timeout|timed out/i.test(text)) {
61
+ return "download-not-verified";
62
+ }
63
+ return "upstream-error";
64
+ }
65
+ export function buildAgentBrowserResultCategoryDetails(options) {
66
+ if (options.succeeded) {
67
+ return {
68
+ resultCategory: "success",
69
+ successCategory: classifyAgentBrowserSuccessCategory(options),
70
+ };
71
+ }
72
+ return {
73
+ failureCategory: options.failureCategory ?? classifyAgentBrowserFailureCategory(options),
74
+ resultCategory: "failure",
75
+ };
76
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Purpose: Detect upstream guarded-action confirmation-needed result shapes without creating wrapper-owned confirmation state.
3
+ * Responsibilities: Recognize confirmation-required markers, extract the pending upstream confirmation id, and optionally surface a short upstream action label.
4
+ * Scope: Pure result-shape detection shared by presentation and error derivation; command execution, approval state, and redaction stay in their existing modules.
5
+ * Usage: Imported by result presentation to render recovery commands and by envelope error handling to avoid hiding actionable confirmation payloads behind generic failure text.
6
+ * Invariants/Assumptions: Detection must be conservative: a confirmation marker and a non-empty upstream id are both required before a result is treated as actionable.
7
+ */
8
+ import { isRecord } from "../parsing.js";
9
+ const CONFIRMATION_REQUIRED_FIELD_NAMES = [
10
+ "confirmation_required",
11
+ "confirmationRequired",
12
+ "requires_confirmation",
13
+ "requiresConfirmation",
14
+ ];
15
+ const CONFIRMATION_REQUIRED_RECORD_FIELD_NAMES = ["confirmation", "pendingConfirmation", "pending_confirmation"];
16
+ const CONFIRMATION_ID_FIELD_NAMES = ["confirmation_id", "confirmationId", "id"];
17
+ const CONFIRMATION_ACTION_TEXT_FIELD_NAMES = ["action", "description", "message", "summary"];
18
+ const CONFIRMATION_REQUIRED_MARKER = "confirmation_required";
19
+ function getTrimmedStringField(data, fieldNames) {
20
+ for (const fieldName of fieldNames) {
21
+ const value = data[fieldName];
22
+ if (typeof value === "string" && value.trim().length > 0) {
23
+ return value.trim();
24
+ }
25
+ }
26
+ return undefined;
27
+ }
28
+ function hasConfirmationRequiredMarker(data) {
29
+ return CONFIRMATION_REQUIRED_FIELD_NAMES.some((fieldName) => data[fieldName] === true)
30
+ || data.type === CONFIRMATION_REQUIRED_MARKER
31
+ || data.status === CONFIRMATION_REQUIRED_MARKER
32
+ || data.kind === CONFIRMATION_REQUIRED_MARKER;
33
+ }
34
+ function getNestedConfirmationRecord(data) {
35
+ for (const fieldName of CONFIRMATION_REQUIRED_RECORD_FIELD_NAMES) {
36
+ const value = data[fieldName];
37
+ if (isRecord(value)) {
38
+ return value;
39
+ }
40
+ }
41
+ return undefined;
42
+ }
43
+ export function detectConfirmationRequired(data) {
44
+ if (!isRecord(data)) {
45
+ return undefined;
46
+ }
47
+ const nestedRecord = getNestedConfirmationRecord(data);
48
+ const candidateRecords = nestedRecord ? [data, nestedRecord] : [data];
49
+ if (!candidateRecords.some(hasConfirmationRequiredMarker)) {
50
+ return undefined;
51
+ }
52
+ for (const record of candidateRecords) {
53
+ const id = getTrimmedStringField(record, CONFIRMATION_ID_FIELD_NAMES);
54
+ if (!id) {
55
+ continue;
56
+ }
57
+ return {
58
+ actionText: getTrimmedStringField(record, CONFIRMATION_ACTION_TEXT_FIELD_NAMES),
59
+ id,
60
+ };
61
+ }
62
+ return undefined;
63
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Purpose: Define stable result-rendering data contracts shared across focused result modules.
3
+ * Responsibilities: Keep upstream envelope, presentation, artifact, category, and network shapes in one type-only surface.
4
+ * Scope: Types only; runtime classifiers, manifests, network rules, and text helpers live in neighboring modules.
5
+ * Usage: Imported with `import type` by result modules and re-exported by the public results facade.
6
+ * Invariants/Assumptions: This file has no runtime policy so adding fields cannot hide behavior in a catch-all module.
7
+ */
8
+ export {};
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Purpose: Detect whether upstream ref metadata or snapshot text proves an element is editable.
3
+ * Responsibilities: Parse structured ref flags and snapshot-line contenteditable/editable markers into a conservative boolean/unknown signal.
4
+ * Scope: Editable evidence only; callers decide how to rank controls or build recovery actions from the signal.
5
+ * Usage: Imported by snapshot compaction and selector/fill recovery diagnostics.
6
+ * Invariants/Assumptions: Explicit false evidence wins over positive hints; unknown remains undefined.
7
+ */
8
+ const EDITABLE_REF_EVIDENCE_KEYS = ["editable", "contentEditable", "contenteditable", "isContentEditable"];
9
+ const EDITABLE_FALSE_TEXT_PATTERN = /\b(?:contenteditable|editable)\s*=\s*["']?(?:false|0)["']?/i;
10
+ const EDITABLE_ASSIGNMENT_TEXT_PATTERN = /\b(contenteditable|editable)\s*=\s*("[^"]*"|'[^']*'|[^\s,\]]+)/gi;
11
+ const EDITABLE_BARE_TEXT_PATTERN = /\b(?:contenteditable|editable)\b(?!\s*=)/i;
12
+ function parseEditableEvidenceValue(value) {
13
+ if (typeof value === "boolean")
14
+ return value;
15
+ if (typeof value === "number") {
16
+ if (value === 1)
17
+ return true;
18
+ if (value === 0)
19
+ return false;
20
+ return undefined;
21
+ }
22
+ if (typeof value !== "string")
23
+ return undefined;
24
+ const normalized = value.trim().replace(/^["']|["']$/g, "").toLowerCase();
25
+ if (["false", "0", "no"].includes(normalized))
26
+ return false;
27
+ if (["", "true", "1", "yes", "plaintext-only"].includes(normalized))
28
+ return true;
29
+ return undefined;
30
+ }
31
+ function stripLeadingSnapshotAccessibleName(text) {
32
+ return text.replace(/^(\s*[-*]\s+\S+\s+)(?:"[^"]*"|'[^']*')/, "$1");
33
+ }
34
+ function parseEditableEvidenceText(text) {
35
+ if (!text)
36
+ return undefined;
37
+ const markerText = stripLeadingSnapshotAccessibleName(text);
38
+ if (EDITABLE_FALSE_TEXT_PATTERN.test(markerText))
39
+ return false;
40
+ let hasPositiveAssignment = false;
41
+ for (const match of markerText.matchAll(EDITABLE_ASSIGNMENT_TEXT_PATTERN)) {
42
+ const key = match[1]?.toLowerCase();
43
+ const evidence = parseEditableEvidenceValue(match[2]);
44
+ if (evidence === false)
45
+ return false;
46
+ if (evidence === true && (key === "contenteditable" || key === "editable")) {
47
+ hasPositiveAssignment = true;
48
+ }
49
+ }
50
+ if (hasPositiveAssignment)
51
+ return true;
52
+ return EDITABLE_BARE_TEXT_PATTERN.test(markerText) ? true : undefined;
53
+ }
54
+ export function getEditableRefEvidence(options) {
55
+ let hasPositiveEvidence = false;
56
+ if (options.ref) {
57
+ for (const key of EDITABLE_REF_EVIDENCE_KEYS) {
58
+ const evidence = parseEditableEvidenceValue(options.ref[key]);
59
+ if (evidence === false)
60
+ return false;
61
+ if (evidence === true)
62
+ hasPositiveEvidence = true;
63
+ }
64
+ }
65
+ const textEvidence = parseEditableEvidenceText(options.text);
66
+ if (textEvidence === false)
67
+ return false;
68
+ if (textEvidence === true)
69
+ hasPositiveEvidence = true;
70
+ return hasPositiveEvidence ? true : undefined;
71
+ }
72
+ export function hasPositiveEditableRefEvidence(options) {
73
+ return getEditableRefEvidence(options) === true;
74
+ }