opendevbrowser 0.0.12 → 0.0.16

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 (585) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +425 -42
  3. package/dist/annotate/direct-annotator.d.ts +22 -0
  4. package/dist/annotate/direct-annotator.d.ts.map +1 -0
  5. package/dist/annotate/output.d.ts +10 -0
  6. package/dist/annotate/output.d.ts.map +1 -0
  7. package/dist/browser/annotation-manager.d.ts +30 -0
  8. package/dist/browser/annotation-manager.d.ts.map +1 -0
  9. package/dist/browser/browser-manager.d.ts +397 -0
  10. package/dist/browser/browser-manager.d.ts.map +1 -0
  11. package/dist/browser/fingerprint/adapters.d.ts +26 -0
  12. package/dist/browser/fingerprint/adapters.d.ts.map +1 -0
  13. package/dist/browser/fingerprint/canary.d.ts +25 -0
  14. package/dist/browser/fingerprint/canary.d.ts.map +1 -0
  15. package/dist/browser/fingerprint/profiles.d.ts +16 -0
  16. package/dist/browser/fingerprint/profiles.d.ts.map +1 -0
  17. package/dist/browser/fingerprint/tier1-coherence.d.ts +36 -0
  18. package/dist/browser/fingerprint/tier1-coherence.d.ts.map +1 -0
  19. package/dist/browser/fingerprint/tier2-runtime.d.ts +40 -0
  20. package/dist/browser/fingerprint/tier2-runtime.d.ts.map +1 -0
  21. package/dist/browser/fingerprint/tier3-adaptive.d.ts +30 -0
  22. package/dist/browser/fingerprint/tier3-adaptive.d.ts.map +1 -0
  23. package/dist/browser/manager-types.d.ts +3 -0
  24. package/dist/browser/manager-types.d.ts.map +1 -0
  25. package/dist/browser/ops-browser-manager.d.ts +131 -0
  26. package/dist/browser/ops-browser-manager.d.ts.map +1 -0
  27. package/dist/browser/ops-client.d.ts +56 -0
  28. package/dist/browser/ops-client.d.ts.map +1 -0
  29. package/dist/browser/parallelism-governor.d.ts +31 -0
  30. package/dist/browser/parallelism-governor.d.ts.map +1 -0
  31. package/dist/browser/script-runner.d.ts +23 -0
  32. package/dist/browser/script-runner.d.ts.map +1 -0
  33. package/dist/browser/session-store.d.ts +63 -0
  34. package/dist/browser/session-store.d.ts.map +1 -0
  35. package/dist/browser/target-manager.d.ts +36 -0
  36. package/dist/browser/target-manager.d.ts.map +1 -0
  37. package/dist/cache/chrome-locator.d.ts +2 -0
  38. package/dist/cache/chrome-locator.d.ts.map +1 -0
  39. package/dist/cache/downloader.d.ts +6 -0
  40. package/dist/cache/downloader.d.ts.map +1 -0
  41. package/dist/cache/paths.d.ts +9 -0
  42. package/dist/cache/paths.d.ts.map +1 -0
  43. package/dist/chunk-7W3SPXIB.js +166 -0
  44. package/dist/chunk-7W3SPXIB.js.map +1 -0
  45. package/dist/chunk-ST7CO5FA.js +18668 -0
  46. package/dist/chunk-ST7CO5FA.js.map +1 -0
  47. package/dist/cli/args.d.ts +25 -0
  48. package/dist/cli/args.d.ts.map +1 -0
  49. package/dist/cli/client.d.ts +2 -0
  50. package/dist/cli/client.d.ts.map +1 -0
  51. package/dist/cli/commands/annotate.d.ts +27 -0
  52. package/dist/cli/commands/annotate.d.ts.map +1 -0
  53. package/dist/cli/commands/artifacts.d.ts +24 -0
  54. package/dist/cli/commands/artifacts.d.ts.map +1 -0
  55. package/dist/cli/commands/daemon.d.ts +35 -0
  56. package/dist/cli/commands/daemon.d.ts.map +1 -0
  57. package/dist/cli/commands/devtools/console-poll.d.ts +7 -0
  58. package/dist/cli/commands/devtools/console-poll.d.ts.map +1 -0
  59. package/dist/cli/commands/devtools/debug-trace-snapshot.d.ts +20 -0
  60. package/dist/cli/commands/devtools/debug-trace-snapshot.d.ts.map +1 -0
  61. package/dist/cli/commands/devtools/network-poll.d.ts +7 -0
  62. package/dist/cli/commands/devtools/network-poll.d.ts.map +1 -0
  63. package/dist/cli/commands/devtools/perf.d.ts +7 -0
  64. package/dist/cli/commands/devtools/perf.d.ts.map +1 -0
  65. package/dist/cli/commands/devtools/screenshot.d.ts +17 -0
  66. package/dist/cli/commands/devtools/screenshot.d.ts.map +1 -0
  67. package/dist/cli/commands/dom/attr.d.ts +7 -0
  68. package/dist/cli/commands/dom/attr.d.ts.map +1 -0
  69. package/dist/cli/commands/dom/checked.d.ts +7 -0
  70. package/dist/cli/commands/dom/checked.d.ts.map +1 -0
  71. package/dist/cli/commands/dom/enabled.d.ts +7 -0
  72. package/dist/cli/commands/dom/enabled.d.ts.map +1 -0
  73. package/dist/cli/commands/dom/html.d.ts +7 -0
  74. package/dist/cli/commands/dom/html.d.ts.map +1 -0
  75. package/dist/cli/commands/dom/text.d.ts +7 -0
  76. package/dist/cli/commands/dom/text.d.ts.map +1 -0
  77. package/dist/cli/commands/dom/value.d.ts +7 -0
  78. package/dist/cli/commands/dom/value.d.ts.map +1 -0
  79. package/dist/cli/commands/dom/visible.d.ts +7 -0
  80. package/dist/cli/commands/dom/visible.d.ts.map +1 -0
  81. package/dist/cli/commands/export/clone-component.d.ts +7 -0
  82. package/dist/cli/commands/export/clone-component.d.ts.map +1 -0
  83. package/dist/cli/commands/export/clone-page.d.ts +7 -0
  84. package/dist/cli/commands/export/clone-page.d.ts.map +1 -0
  85. package/dist/cli/commands/interact/check.d.ts +7 -0
  86. package/dist/cli/commands/interact/check.d.ts.map +1 -0
  87. package/dist/cli/commands/interact/click.d.ts +7 -0
  88. package/dist/cli/commands/interact/click.d.ts.map +1 -0
  89. package/dist/cli/commands/interact/hover.d.ts +7 -0
  90. package/dist/cli/commands/interact/hover.d.ts.map +1 -0
  91. package/dist/cli/commands/interact/press.d.ts +7 -0
  92. package/dist/cli/commands/interact/press.d.ts.map +1 -0
  93. package/dist/cli/commands/interact/scroll-into-view.d.ts +7 -0
  94. package/dist/cli/commands/interact/scroll-into-view.d.ts.map +1 -0
  95. package/dist/cli/commands/interact/scroll.d.ts +7 -0
  96. package/dist/cli/commands/interact/scroll.d.ts.map +1 -0
  97. package/dist/cli/commands/interact/select.d.ts +7 -0
  98. package/dist/cli/commands/interact/select.d.ts.map +1 -0
  99. package/dist/cli/commands/interact/type.d.ts +7 -0
  100. package/dist/cli/commands/interact/type.d.ts.map +1 -0
  101. package/dist/cli/commands/interact/uncheck.d.ts +7 -0
  102. package/dist/cli/commands/interact/uncheck.d.ts.map +1 -0
  103. package/dist/cli/commands/macro-resolve.d.ts +18 -0
  104. package/dist/cli/commands/macro-resolve.d.ts.map +1 -0
  105. package/dist/cli/commands/native.d.ts +82 -0
  106. package/dist/cli/commands/native.d.ts.map +1 -0
  107. package/dist/cli/commands/nav/goto.d.ts +7 -0
  108. package/dist/cli/commands/nav/goto.d.ts.map +1 -0
  109. package/dist/cli/commands/nav/snapshot.d.ts +7 -0
  110. package/dist/cli/commands/nav/snapshot.d.ts.map +1 -0
  111. package/dist/cli/commands/nav/wait.d.ts +7 -0
  112. package/dist/cli/commands/nav/wait.d.ts.map +1 -0
  113. package/dist/cli/commands/pages/close.d.ts +6 -0
  114. package/dist/cli/commands/pages/close.d.ts.map +1 -0
  115. package/dist/cli/commands/pages/list.d.ts +7 -0
  116. package/dist/cli/commands/pages/list.d.ts.map +1 -0
  117. package/dist/cli/commands/pages/open.d.ts +7 -0
  118. package/dist/cli/commands/pages/open.d.ts.map +1 -0
  119. package/dist/cli/commands/product-video.d.ts +25 -0
  120. package/dist/cli/commands/product-video.d.ts.map +1 -0
  121. package/dist/cli/commands/registry.d.ts +5 -0
  122. package/dist/cli/commands/registry.d.ts.map +1 -0
  123. package/dist/cli/commands/research.d.ts +27 -0
  124. package/dist/cli/commands/research.d.ts.map +1 -0
  125. package/dist/cli/commands/rpc.d.ts +28 -0
  126. package/dist/cli/commands/rpc.d.ts.map +1 -0
  127. package/dist/cli/commands/run.d.ts +17 -0
  128. package/dist/cli/commands/run.d.ts.map +1 -0
  129. package/dist/cli/commands/serve.d.ts +59 -0
  130. package/dist/cli/commands/serve.d.ts.map +1 -0
  131. package/dist/cli/commands/session/connect.d.ts +9 -0
  132. package/dist/cli/commands/session/connect.d.ts.map +1 -0
  133. package/dist/cli/commands/session/cookie-import.d.ts +31 -0
  134. package/dist/cli/commands/session/cookie-import.d.ts.map +1 -0
  135. package/dist/cli/commands/session/cookie-list.d.ts +17 -0
  136. package/dist/cli/commands/session/cookie-list.d.ts.map +1 -0
  137. package/dist/cli/commands/session/disconnect.d.ts +6 -0
  138. package/dist/cli/commands/session/disconnect.d.ts.map +1 -0
  139. package/dist/cli/commands/session/launch.d.ts +29 -0
  140. package/dist/cli/commands/session/launch.d.ts.map +1 -0
  141. package/dist/cli/commands/session/status.d.ts +7 -0
  142. package/dist/cli/commands/session/status.d.ts.map +1 -0
  143. package/dist/cli/commands/shopping.d.ts +25 -0
  144. package/dist/cli/commands/shopping.d.ts.map +1 -0
  145. package/dist/cli/commands/status.d.ts +19 -0
  146. package/dist/cli/commands/status.d.ts.map +1 -0
  147. package/dist/cli/commands/targets/close.d.ts +6 -0
  148. package/dist/cli/commands/targets/close.d.ts.map +1 -0
  149. package/dist/cli/commands/targets/list.d.ts +7 -0
  150. package/dist/cli/commands/targets/list.d.ts.map +1 -0
  151. package/dist/cli/commands/targets/new.d.ts +7 -0
  152. package/dist/cli/commands/targets/new.d.ts.map +1 -0
  153. package/dist/cli/commands/targets/use.d.ts +7 -0
  154. package/dist/cli/commands/targets/use.d.ts.map +1 -0
  155. package/dist/cli/commands/types.d.ts +13 -0
  156. package/dist/cli/commands/types.d.ts.map +1 -0
  157. package/dist/cli/commands/uninstall.d.ts +14 -0
  158. package/dist/cli/commands/uninstall.d.ts.map +1 -0
  159. package/dist/cli/commands/update.d.ts +7 -0
  160. package/dist/cli/commands/update.d.ts.map +1 -0
  161. package/dist/cli/daemon-autostart.d.ts +46 -0
  162. package/dist/cli/daemon-autostart.d.ts.map +1 -0
  163. package/dist/cli/daemon-client.d.ts +33 -0
  164. package/dist/cli/daemon-client.d.ts.map +1 -0
  165. package/dist/cli/daemon-commands.d.ts +7 -0
  166. package/dist/cli/daemon-commands.d.ts.map +1 -0
  167. package/dist/cli/daemon-state.d.ts +56 -0
  168. package/dist/cli/daemon-state.d.ts.map +1 -0
  169. package/dist/cli/daemon-status.d.ts +19 -0
  170. package/dist/cli/daemon-status.d.ts.map +1 -0
  171. package/dist/cli/daemon.d.ts +29 -0
  172. package/dist/cli/daemon.d.ts.map +1 -0
  173. package/dist/cli/errors.d.ts +20 -0
  174. package/dist/cli/errors.d.ts.map +1 -0
  175. package/dist/cli/help.d.ts +28 -0
  176. package/dist/cli/help.d.ts.map +1 -0
  177. package/dist/cli/index.d.ts +2 -0
  178. package/dist/cli/index.d.ts.map +1 -0
  179. package/dist/cli/index.js +4604 -769
  180. package/dist/cli/index.js.map +1 -1
  181. package/dist/cli/installers/global.d.ts +9 -0
  182. package/dist/cli/installers/global.d.ts.map +1 -0
  183. package/dist/cli/installers/local.d.ts +9 -0
  184. package/dist/cli/installers/local.d.ts.map +1 -0
  185. package/dist/cli/installers/skills.d.ts +19 -0
  186. package/dist/cli/installers/skills.d.ts.map +1 -0
  187. package/dist/cli/output.d.ts +7 -0
  188. package/dist/cli/output.d.ts.map +1 -0
  189. package/dist/cli/remote-manager.d.ts +96 -0
  190. package/dist/cli/remote-manager.d.ts.map +1 -0
  191. package/dist/cli/remote-relay.d.ts +17 -0
  192. package/dist/cli/remote-relay.d.ts.map +1 -0
  193. package/dist/cli/templates/config.d.ts +7 -0
  194. package/dist/cli/templates/config.d.ts.map +1 -0
  195. package/dist/cli/utils/config.d.ts +20 -0
  196. package/dist/cli/utils/config.d.ts.map +1 -0
  197. package/dist/cli/utils/http.d.ts +5 -0
  198. package/dist/cli/utils/http.d.ts.map +1 -0
  199. package/dist/cli/utils/parse.d.ts +8 -0
  200. package/dist/cli/utils/parse.d.ts.map +1 -0
  201. package/dist/cli/utils/skills.d.ts +12 -0
  202. package/dist/cli/utils/skills.d.ts.map +1 -0
  203. package/dist/config.d.ts +208 -0
  204. package/dist/config.d.ts.map +1 -0
  205. package/dist/core/bootstrap.d.ts +3 -0
  206. package/dist/core/bootstrap.d.ts.map +1 -0
  207. package/dist/core/index.d.ts +3 -0
  208. package/dist/core/index.d.ts.map +1 -0
  209. package/dist/core/logging.d.ts +34 -0
  210. package/dist/core/logging.d.ts.map +1 -0
  211. package/dist/core/types.d.ts +34 -0
  212. package/dist/core/types.d.ts.map +1 -0
  213. package/dist/devtools/console-tracker.d.ts +44 -0
  214. package/dist/devtools/console-tracker.d.ts.map +1 -0
  215. package/dist/devtools/exception-tracker.d.ts +42 -0
  216. package/dist/devtools/exception-tracker.d.ts.map +1 -0
  217. package/dist/devtools/network-tracker.d.ts +34 -0
  218. package/dist/devtools/network-tracker.d.ts.map +1 -0
  219. package/dist/export/css-extract.d.ts +5 -0
  220. package/dist/export/css-extract.d.ts.map +1 -0
  221. package/dist/export/dom-capture.d.ts +15 -0
  222. package/dist/export/dom-capture.d.ts.map +1 -0
  223. package/dist/export/react-emitter.d.ts +11 -0
  224. package/dist/export/react-emitter.d.ts.map +1 -0
  225. package/dist/extension-extractor.d.ts +3 -0
  226. package/dist/extension-extractor.d.ts.map +1 -0
  227. package/dist/index.d.ts +3 -4
  228. package/dist/index.d.ts.map +1 -0
  229. package/dist/index.js +1905 -262
  230. package/dist/index.js.map +1 -1
  231. package/dist/macros/execute.d.ts +44 -0
  232. package/dist/macros/execute.d.ts.map +1 -0
  233. package/dist/macros/index.d.ts +9 -0
  234. package/dist/macros/index.d.ts.map +1 -0
  235. package/dist/macros/packs/core.d.ts +3 -0
  236. package/dist/macros/packs/core.d.ts.map +1 -0
  237. package/dist/macros/registry.d.ts +48 -0
  238. package/dist/macros/registry.d.ts.map +1 -0
  239. package/dist/macros-NUBRM44Y.js +399 -0
  240. package/dist/macros-NUBRM44Y.js.map +1 -0
  241. package/dist/opendevbrowser.d.ts +3 -4
  242. package/dist/opendevbrowser.d.ts.map +1 -0
  243. package/dist/opendevbrowser.js +1905 -262
  244. package/dist/opendevbrowser.js.map +1 -1
  245. package/dist/providers/adaptive-concurrency.d.ts +42 -0
  246. package/dist/providers/adaptive-concurrency.d.ts.map +1 -0
  247. package/dist/providers/artifacts.d.ts +34 -0
  248. package/dist/providers/artifacts.d.ts.map +1 -0
  249. package/dist/providers/blocker.d.ts +47 -0
  250. package/dist/providers/blocker.d.ts.map +1 -0
  251. package/dist/providers/community/index.d.ts +44 -0
  252. package/dist/providers/community/index.d.ts.map +1 -0
  253. package/dist/providers/enrichment.d.ts +33 -0
  254. package/dist/providers/enrichment.d.ts.map +1 -0
  255. package/dist/providers/errors.d.ts +41 -0
  256. package/dist/providers/errors.d.ts.map +1 -0
  257. package/dist/providers/index.d.ts +121 -0
  258. package/dist/providers/index.d.ts.map +1 -0
  259. package/dist/providers/normalize.d.ts +39 -0
  260. package/dist/providers/normalize.d.ts.map +1 -0
  261. package/dist/providers/policy.d.ts +5 -0
  262. package/dist/providers/policy.d.ts.map +1 -0
  263. package/dist/providers/registry.d.ts +22 -0
  264. package/dist/providers/registry.d.ts.map +1 -0
  265. package/dist/providers/renderer.d.ts +49 -0
  266. package/dist/providers/renderer.d.ts.map +1 -0
  267. package/dist/providers/runtime-factory.d.ts +20 -0
  268. package/dist/providers/runtime-factory.d.ts.map +1 -0
  269. package/dist/providers/safety/prompt-guard.d.ts +34 -0
  270. package/dist/providers/safety/prompt-guard.d.ts.map +1 -0
  271. package/dist/providers/shared/anti-bot-policy.d.ts +51 -0
  272. package/dist/providers/shared/anti-bot-policy.d.ts.map +1 -0
  273. package/dist/providers/shared/post-policy.d.ts +31 -0
  274. package/dist/providers/shared/post-policy.d.ts.map +1 -0
  275. package/dist/providers/shared/request-headers.d.ts +5 -0
  276. package/dist/providers/shared/request-headers.d.ts.map +1 -0
  277. package/dist/providers/shared/traversal-url.d.ts +2 -0
  278. package/dist/providers/shared/traversal-url.d.ts.map +1 -0
  279. package/dist/providers/shopping/index.d.ts +63 -0
  280. package/dist/providers/shopping/index.d.ts.map +1 -0
  281. package/dist/providers/social/bluesky.d.ts +3 -0
  282. package/dist/providers/social/bluesky.d.ts.map +1 -0
  283. package/dist/providers/social/facebook.d.ts +3 -0
  284. package/dist/providers/social/facebook.d.ts.map +1 -0
  285. package/dist/providers/social/index.d.ts +31 -0
  286. package/dist/providers/social/index.d.ts.map +1 -0
  287. package/dist/providers/social/instagram.d.ts +3 -0
  288. package/dist/providers/social/instagram.d.ts.map +1 -0
  289. package/dist/providers/social/linkedin.d.ts +3 -0
  290. package/dist/providers/social/linkedin.d.ts.map +1 -0
  291. package/dist/providers/social/platform.d.ts +40 -0
  292. package/dist/providers/social/platform.d.ts.map +1 -0
  293. package/dist/providers/social/reddit.d.ts +3 -0
  294. package/dist/providers/social/reddit.d.ts.map +1 -0
  295. package/dist/providers/social/threads.d.ts +3 -0
  296. package/dist/providers/social/threads.d.ts.map +1 -0
  297. package/dist/providers/social/tiktok.d.ts +3 -0
  298. package/dist/providers/social/tiktok.d.ts.map +1 -0
  299. package/dist/providers/social/x.d.ts +3 -0
  300. package/dist/providers/social/x.d.ts.map +1 -0
  301. package/dist/providers/social/youtube-resolver.d.ts +78 -0
  302. package/dist/providers/social/youtube-resolver.d.ts.map +1 -0
  303. package/dist/providers/social/youtube.d.ts +34 -0
  304. package/dist/providers/social/youtube.d.ts.map +1 -0
  305. package/dist/providers/tier-router.d.ts +30 -0
  306. package/dist/providers/tier-router.d.ts.map +1 -0
  307. package/dist/providers/timebox.d.ts +20 -0
  308. package/dist/providers/timebox.d.ts.map +1 -0
  309. package/dist/providers/types.d.ts +344 -0
  310. package/dist/providers/types.d.ts.map +1 -0
  311. package/dist/providers/web/crawl-worker.d.ts +36 -0
  312. package/dist/providers/web/crawl-worker.d.ts.map +1 -0
  313. package/dist/providers/web/crawler.d.ts +101 -0
  314. package/dist/providers/web/crawler.d.ts.map +1 -0
  315. package/dist/providers/web/extract.d.ts +11 -0
  316. package/dist/providers/web/extract.d.ts.map +1 -0
  317. package/dist/providers/web/index.d.ts +24 -0
  318. package/dist/providers/web/index.d.ts.map +1 -0
  319. package/dist/providers/web/policy.d.ts +14 -0
  320. package/dist/providers/web/policy.d.ts.map +1 -0
  321. package/dist/providers/workflows.d.ts +67 -0
  322. package/dist/providers/workflows.d.ts.map +1 -0
  323. package/dist/relay/protocol.d.ts +317 -0
  324. package/dist/relay/protocol.d.ts.map +1 -0
  325. package/dist/relay/relay-endpoints.d.ts +16 -0
  326. package/dist/relay/relay-endpoints.d.ts.map +1 -0
  327. package/dist/relay/relay-server.d.ts +111 -0
  328. package/dist/relay/relay-server.d.ts.map +1 -0
  329. package/dist/relay/relay-types.d.ts +9 -0
  330. package/dist/relay/relay-types.d.ts.map +1 -0
  331. package/dist/skills/continuity-nudge.d.ts +12 -0
  332. package/dist/skills/continuity-nudge.d.ts.map +1 -0
  333. package/dist/skills/skill-loader.d.ts +20 -0
  334. package/dist/skills/skill-loader.d.ts.map +1 -0
  335. package/dist/skills/skill-nudge.d.ts +18 -0
  336. package/dist/skills/skill-nudge.d.ts.map +1 -0
  337. package/dist/skills/types.d.ts +12 -0
  338. package/dist/skills/types.d.ts.map +1 -0
  339. package/dist/snapshot/ops-snapshot.d.ts +16 -0
  340. package/dist/snapshot/ops-snapshot.d.ts.map +1 -0
  341. package/dist/snapshot/refs.d.ts +23 -0
  342. package/dist/snapshot/refs.d.ts.map +1 -0
  343. package/dist/snapshot/snapshotter.d.ts +27 -0
  344. package/dist/snapshot/snapshotter.d.ts.map +1 -0
  345. package/dist/tools/annotate.d.ts +4 -0
  346. package/dist/tools/annotate.d.ts.map +1 -0
  347. package/dist/tools/check.d.ts +4 -0
  348. package/dist/tools/check.d.ts.map +1 -0
  349. package/dist/tools/click.d.ts +4 -0
  350. package/dist/tools/click.d.ts.map +1 -0
  351. package/dist/tools/clone_component.d.ts +4 -0
  352. package/dist/tools/clone_component.d.ts.map +1 -0
  353. package/dist/tools/clone_page.d.ts +4 -0
  354. package/dist/tools/clone_page.d.ts.map +1 -0
  355. package/dist/tools/close.d.ts +4 -0
  356. package/dist/tools/close.d.ts.map +1 -0
  357. package/dist/tools/connect.d.ts +4 -0
  358. package/dist/tools/connect.d.ts.map +1 -0
  359. package/dist/tools/console_poll.d.ts +4 -0
  360. package/dist/tools/console_poll.d.ts.map +1 -0
  361. package/dist/tools/cookie_import.d.ts +25 -0
  362. package/dist/tools/cookie_import.d.ts.map +1 -0
  363. package/dist/tools/cookie_list.d.ts +9 -0
  364. package/dist/tools/cookie_list.d.ts.map +1 -0
  365. package/dist/tools/debug_trace_snapshot.d.ts +4 -0
  366. package/dist/tools/debug_trace_snapshot.d.ts.map +1 -0
  367. package/dist/tools/deps.d.ts +26 -0
  368. package/dist/tools/deps.d.ts.map +1 -0
  369. package/dist/tools/disconnect.d.ts +4 -0
  370. package/dist/tools/disconnect.d.ts.map +1 -0
  371. package/dist/tools/dom_get_html.d.ts +4 -0
  372. package/dist/tools/dom_get_html.d.ts.map +1 -0
  373. package/dist/tools/dom_get_text.d.ts +4 -0
  374. package/dist/tools/dom_get_text.d.ts.map +1 -0
  375. package/dist/tools/get_attr.d.ts +4 -0
  376. package/dist/tools/get_attr.d.ts.map +1 -0
  377. package/dist/tools/get_value.d.ts +4 -0
  378. package/dist/tools/get_value.d.ts.map +1 -0
  379. package/dist/tools/goto.d.ts +4 -0
  380. package/dist/tools/goto.d.ts.map +1 -0
  381. package/dist/tools/hover.d.ts +4 -0
  382. package/dist/tools/hover.d.ts.map +1 -0
  383. package/dist/tools/index.d.ts +4 -0
  384. package/dist/tools/index.d.ts.map +1 -0
  385. package/dist/tools/is_checked.d.ts +4 -0
  386. package/dist/tools/is_checked.d.ts.map +1 -0
  387. package/dist/tools/is_enabled.d.ts +4 -0
  388. package/dist/tools/is_enabled.d.ts.map +1 -0
  389. package/dist/tools/is_visible.d.ts +4 -0
  390. package/dist/tools/is_visible.d.ts.map +1 -0
  391. package/dist/tools/launch.d.ts +4 -0
  392. package/dist/tools/launch.d.ts.map +1 -0
  393. package/dist/tools/list.d.ts +4 -0
  394. package/dist/tools/list.d.ts.map +1 -0
  395. package/dist/tools/macro_resolve.d.ts +25 -0
  396. package/dist/tools/macro_resolve.d.ts.map +1 -0
  397. package/dist/tools/network_poll.d.ts +4 -0
  398. package/dist/tools/network_poll.d.ts.map +1 -0
  399. package/dist/tools/page.d.ts +4 -0
  400. package/dist/tools/page.d.ts.map +1 -0
  401. package/dist/tools/perf.d.ts +4 -0
  402. package/dist/tools/perf.d.ts.map +1 -0
  403. package/dist/tools/press.d.ts +4 -0
  404. package/dist/tools/press.d.ts.map +1 -0
  405. package/dist/tools/product_video_run.d.ts +4 -0
  406. package/dist/tools/product_video_run.d.ts.map +1 -0
  407. package/dist/tools/prompting_guide.d.ts +4 -0
  408. package/dist/tools/prompting_guide.d.ts.map +1 -0
  409. package/dist/tools/research_run.d.ts +4 -0
  410. package/dist/tools/research_run.d.ts.map +1 -0
  411. package/dist/tools/response.d.ts +16 -0
  412. package/dist/tools/response.d.ts.map +1 -0
  413. package/dist/tools/run.d.ts +4 -0
  414. package/dist/tools/run.d.ts.map +1 -0
  415. package/dist/tools/screenshot.d.ts +4 -0
  416. package/dist/tools/screenshot.d.ts.map +1 -0
  417. package/dist/tools/scroll.d.ts +4 -0
  418. package/dist/tools/scroll.d.ts.map +1 -0
  419. package/dist/tools/scroll_into_view.d.ts +4 -0
  420. package/dist/tools/scroll_into_view.d.ts.map +1 -0
  421. package/dist/tools/select.d.ts +4 -0
  422. package/dist/tools/select.d.ts.map +1 -0
  423. package/dist/tools/shopping_run.d.ts +4 -0
  424. package/dist/tools/shopping_run.d.ts.map +1 -0
  425. package/dist/tools/skill_list.d.ts +4 -0
  426. package/dist/tools/skill_list.d.ts.map +1 -0
  427. package/dist/tools/skill_load.d.ts +4 -0
  428. package/dist/tools/skill_load.d.ts.map +1 -0
  429. package/dist/tools/snapshot.d.ts +4 -0
  430. package/dist/tools/snapshot.d.ts.map +1 -0
  431. package/dist/tools/status.d.ts +4 -0
  432. package/dist/tools/status.d.ts.map +1 -0
  433. package/dist/tools/target_close.d.ts +4 -0
  434. package/dist/tools/target_close.d.ts.map +1 -0
  435. package/dist/tools/target_new.d.ts +4 -0
  436. package/dist/tools/target_new.d.ts.map +1 -0
  437. package/dist/tools/target_use.d.ts +4 -0
  438. package/dist/tools/target_use.d.ts.map +1 -0
  439. package/dist/tools/targets_list.d.ts +4 -0
  440. package/dist/tools/targets_list.d.ts.map +1 -0
  441. package/dist/tools/type.d.ts +4 -0
  442. package/dist/tools/type.d.ts.map +1 -0
  443. package/dist/tools/uncheck.d.ts +4 -0
  444. package/dist/tools/uncheck.d.ts.map +1 -0
  445. package/dist/tools/wait.d.ts +4 -0
  446. package/dist/tools/wait.d.ts.map +1 -0
  447. package/dist/tools/workflow-runtime.d.ts +4 -0
  448. package/dist/tools/workflow-runtime.d.ts.map +1 -0
  449. package/dist/utils/crypto.d.ts +2 -0
  450. package/dist/utils/crypto.d.ts.map +1 -0
  451. package/dist/utils/endpoint-validation.d.ts +2 -0
  452. package/dist/utils/endpoint-validation.d.ts.map +1 -0
  453. package/dist/utils/fs.d.ts +5 -0
  454. package/dist/utils/fs.d.ts.map +1 -0
  455. package/dist/utils/hub-enabled.d.ts +3 -0
  456. package/dist/utils/hub-enabled.d.ts.map +1 -0
  457. package/extension/dist/annotate-content.css +237 -0
  458. package/extension/dist/annotate-content.js +934 -0
  459. package/extension/dist/background.js +1203 -35
  460. package/extension/dist/logging.js +50 -0
  461. package/extension/dist/ops/dom-bridge.js +355 -0
  462. package/extension/dist/ops/ops-runtime.js +1751 -0
  463. package/extension/dist/ops/ops-session-store.js +203 -0
  464. package/extension/dist/ops/parallelism-governor.js +117 -0
  465. package/extension/dist/ops/redaction.js +52 -0
  466. package/extension/dist/ops/snapshot-builder.js +4 -0
  467. package/extension/dist/ops/snapshot-shared.js +236 -0
  468. package/extension/dist/popup.js +370 -25
  469. package/extension/dist/relay-settings.js +1 -0
  470. package/extension/dist/services/CDPRouter.js +567 -104
  471. package/extension/dist/services/ConnectionManager.js +469 -60
  472. package/extension/dist/services/NativePortManager.js +182 -0
  473. package/extension/dist/services/RelayClient.js +227 -26
  474. package/extension/dist/services/TabManager.js +81 -0
  475. package/extension/dist/services/TargetSessionMap.js +146 -0
  476. package/extension/dist/services/cdp-router-commands.js +203 -0
  477. package/extension/dist/services/url-restrictions.js +41 -0
  478. package/extension/dist/types.js +3 -1
  479. package/extension/icons/icon128.png +0 -0
  480. package/extension/icons/icon16.png +0 -0
  481. package/extension/icons/icon32.png +0 -0
  482. package/extension/icons/icon48.png +0 -0
  483. package/extension/manifest.json +17 -3
  484. package/extension/popup.html +144 -0
  485. package/package.json +26 -17
  486. package/scripts/native/host.cjs +230 -0
  487. package/scripts/native/install.ps1 +73 -0
  488. package/scripts/native/install.sh +66 -0
  489. package/scripts/native/uninstall.ps1 +25 -0
  490. package/scripts/native/uninstall.sh +26 -0
  491. package/skills/AGENTS.md +47 -66
  492. package/skills/opendevbrowser-best-practices/SKILL.md +196 -49
  493. package/skills/opendevbrowser-best-practices/artifacts/browser-agent-known-issues-matrix.md +44 -0
  494. package/skills/opendevbrowser-best-practices/artifacts/command-channel-reference.md +95 -0
  495. package/skills/opendevbrowser-best-practices/artifacts/debug-trace-playbook.md +36 -0
  496. package/skills/opendevbrowser-best-practices/artifacts/fingerprint-tiers.md +36 -0
  497. package/skills/opendevbrowser-best-practices/artifacts/macro-workflows.md +43 -0
  498. package/skills/opendevbrowser-best-practices/artifacts/parity-gates.md +36 -0
  499. package/skills/opendevbrowser-best-practices/artifacts/provider-workflows.md +89 -0
  500. package/skills/opendevbrowser-best-practices/assets/templates/cdp-forward-envelope.json +11 -0
  501. package/skills/opendevbrowser-best-practices/assets/templates/mode-flag-matrix.json +56 -0
  502. package/skills/opendevbrowser-best-practices/assets/templates/ops-request-envelope.json +9 -0
  503. package/skills/opendevbrowser-best-practices/assets/templates/robustness-checklist.json +79 -0
  504. package/skills/opendevbrowser-best-practices/assets/templates/surface-audit-checklist.json +24 -0
  505. package/skills/opendevbrowser-best-practices/scripts/odb-workflow.sh +144 -0
  506. package/skills/opendevbrowser-best-practices/scripts/run-robustness-audit.sh +83 -0
  507. package/skills/opendevbrowser-best-practices/scripts/validate-skill-assets.sh +93 -0
  508. package/skills/opendevbrowser-continuity-ledger/SKILL.md +67 -23
  509. package/skills/opendevbrowser-data-extraction/SKILL.md +126 -0
  510. package/skills/opendevbrowser-data-extraction/artifacts/extraction-workflows.md +31 -0
  511. package/skills/opendevbrowser-data-extraction/assets/templates/compliance-checklist.md +7 -0
  512. package/skills/opendevbrowser-data-extraction/assets/templates/extraction-schema.json +17 -0
  513. package/skills/opendevbrowser-data-extraction/assets/templates/pagination-state.json +11 -0
  514. package/skills/opendevbrowser-data-extraction/assets/templates/quality-gates.json +10 -0
  515. package/skills/opendevbrowser-data-extraction/examples/sample-schema.json +19 -0
  516. package/skills/opendevbrowser-data-extraction/scripts/run-extraction-workflow.sh +83 -0
  517. package/skills/opendevbrowser-data-extraction/scripts/validate-skill-assets.sh +49 -0
  518. package/skills/opendevbrowser-form-testing/SKILL.md +143 -0
  519. package/skills/opendevbrowser-form-testing/artifacts/form-workflows.md +37 -0
  520. package/skills/opendevbrowser-form-testing/assets/templates/a11y-assertions.md +7 -0
  521. package/skills/opendevbrowser-form-testing/assets/templates/challenge-decision-tree.json +16 -0
  522. package/skills/opendevbrowser-form-testing/assets/templates/multi-step-state.json +11 -0
  523. package/skills/opendevbrowser-form-testing/assets/templates/validation-matrix.json +24 -0
  524. package/skills/opendevbrowser-form-testing/examples/sample-validation-matrix.json +29 -0
  525. package/skills/opendevbrowser-form-testing/scripts/run-form-workflow.sh +82 -0
  526. package/skills/opendevbrowser-form-testing/scripts/validate-skill-assets.sh +49 -0
  527. package/skills/opendevbrowser-login-automation/SKILL.md +159 -0
  528. package/skills/opendevbrowser-login-automation/artifacts/login-workflows.md +39 -0
  529. package/skills/opendevbrowser-login-automation/assets/templates/auth-signals.json +21 -0
  530. package/skills/opendevbrowser-login-automation/assets/templates/challenge-checkpoint.md +10 -0
  531. package/skills/opendevbrowser-login-automation/assets/templates/login-scenario-matrix.json +26 -0
  532. package/skills/opendevbrowser-login-automation/examples/sample-auth-signals.json +14 -0
  533. package/skills/opendevbrowser-login-automation/scripts/record-auth-signals.sh +18 -0
  534. package/skills/opendevbrowser-login-automation/scripts/run-login-workflow.sh +99 -0
  535. package/skills/opendevbrowser-login-automation/scripts/validate-skill-assets.sh +50 -0
  536. package/skills/opendevbrowser-product-presentation-asset/SKILL.md +98 -0
  537. package/skills/opendevbrowser-product-presentation-asset/artifacts/asset-pack-assembly.md +23 -0
  538. package/skills/opendevbrowser-product-presentation-asset/artifacts/ugc-creative-guide.md +21 -0
  539. package/skills/opendevbrowser-product-presentation-asset/assets/templates/claims-evidence-map.md +5 -0
  540. package/skills/opendevbrowser-product-presentation-asset/assets/templates/copy.md +5 -0
  541. package/skills/opendevbrowser-product-presentation-asset/assets/templates/features.md +4 -0
  542. package/skills/opendevbrowser-product-presentation-asset/assets/templates/manifest.schema.json +14 -0
  543. package/skills/opendevbrowser-product-presentation-asset/assets/templates/shot-list.md +7 -0
  544. package/skills/opendevbrowser-product-presentation-asset/assets/templates/ugc-concepts.md +17 -0
  545. package/skills/opendevbrowser-product-presentation-asset/assets/templates/user-actions.md +7 -0
  546. package/skills/opendevbrowser-product-presentation-asset/assets/templates/video-assembly.md +18 -0
  547. package/skills/opendevbrowser-product-presentation-asset/examples/sample-input.json +6 -0
  548. package/skills/opendevbrowser-product-presentation-asset/examples/sample-manifest.json +18 -0
  549. package/skills/opendevbrowser-product-presentation-asset/scripts/capture-screenshots.sh +9 -0
  550. package/skills/opendevbrowser-product-presentation-asset/scripts/collect-product.sh +14 -0
  551. package/skills/opendevbrowser-product-presentation-asset/scripts/download-images.sh +9 -0
  552. package/skills/opendevbrowser-product-presentation-asset/scripts/render-video-brief.sh +96 -0
  553. package/skills/opendevbrowser-product-presentation-asset/scripts/validate-skill-assets.sh +56 -0
  554. package/skills/opendevbrowser-product-presentation-asset/scripts/write-manifest.sh +43 -0
  555. package/skills/opendevbrowser-research/SKILL.md +73 -0
  556. package/skills/opendevbrowser-research/artifacts/research-workflows.md +29 -0
  557. package/skills/opendevbrowser-research/assets/templates/compact.md +7 -0
  558. package/skills/opendevbrowser-research/assets/templates/context.json +18 -0
  559. package/skills/opendevbrowser-research/assets/templates/report.md +9 -0
  560. package/skills/opendevbrowser-research/examples/sample-input.json +6 -0
  561. package/skills/opendevbrowser-research/examples/sample-output.md +4 -0
  562. package/skills/opendevbrowser-research/scripts/render-output.sh +12 -0
  563. package/skills/opendevbrowser-research/scripts/run-research.sh +23 -0
  564. package/skills/opendevbrowser-research/scripts/validate-skill-assets.sh +48 -0
  565. package/skills/opendevbrowser-research/scripts/write-artifacts.sh +29 -0
  566. package/skills/opendevbrowser-shopping/SKILL.md +118 -0
  567. package/skills/opendevbrowser-shopping/artifacts/deal-hunting-workflows.md +37 -0
  568. package/skills/opendevbrowser-shopping/assets/templates/deal-thresholds.json +8 -0
  569. package/skills/opendevbrowser-shopping/assets/templates/deals-context.json +9 -0
  570. package/skills/opendevbrowser-shopping/assets/templates/deals-table.md +4 -0
  571. package/skills/opendevbrowser-shopping/assets/templates/market-analysis.json +30 -0
  572. package/skills/opendevbrowser-shopping/examples/sample-deals.md +4 -0
  573. package/skills/opendevbrowser-shopping/examples/sample-query.json +5 -0
  574. package/skills/opendevbrowser-shopping/scripts/analyze-market.sh +307 -0
  575. package/skills/opendevbrowser-shopping/scripts/normalize-offers.sh +28 -0
  576. package/skills/opendevbrowser-shopping/scripts/render-deals.sh +13 -0
  577. package/skills/opendevbrowser-shopping/scripts/run-deal-hunt.sh +32 -0
  578. package/skills/opendevbrowser-shopping/scripts/run-shopping.sh +19 -0
  579. package/skills/opendevbrowser-shopping/scripts/validate-skill-assets.sh +53 -0
  580. package/dist/chunk-WTFSMBVH.js +0 -2815
  581. package/dist/chunk-WTFSMBVH.js.map +0 -1
  582. package/extension/dist/popup.jsx +0 -150
  583. package/skills/data-extraction/SKILL.md +0 -136
  584. package/skills/form-testing/SKILL.md +0 -113
  585. package/skills/login-automation/SKILL.md +0 -98
@@ -1,14 +1,230 @@
1
1
  import { ConnectionManager } from "./services/ConnectionManager.js";
2
- import { DEFAULT_AUTO_CONNECT, DEFAULT_AUTO_PAIR, DEFAULT_DISCOVERY_PORT, DEFAULT_PAIRING_ENABLED, DEFAULT_RELAY_PORT } from "./relay-settings.js";
2
+ import { NativePortManager } from "./services/NativePortManager.js";
3
+ import { DEFAULT_AUTO_CONNECT, DEFAULT_AUTO_PAIR, DEFAULT_DISCOVERY_PORT, DEFAULT_NATIVE_ENABLED, DEFAULT_PAIRING_ENABLED, DEFAULT_RELAY_PORT } from "./relay-settings.js";
4
+ import { logError } from "./logging.js";
5
+ import { OpsRuntime } from "./ops/ops-runtime.js";
3
6
  const connection = new ConnectionManager();
7
+ const opsRuntime = new OpsRuntime({
8
+ send: (message) => connection.sendOpsMessage(message),
9
+ cdp: connection.getCdpRouter()
10
+ });
11
+ const nativePort = new NativePortManager({
12
+ onMessage: (payload) => {
13
+ handleNativePortMessage(payload).catch((error) => {
14
+ logError("native_port.message", error, { code: "native_message_failed" });
15
+ });
16
+ },
17
+ onDisconnect: () => {
18
+ updateBadge(getEffectiveStatus());
19
+ }
20
+ });
4
21
  let autoConnectInFlight = false;
22
+ let statusNoteOverride = null;
23
+ let retryScheduled = false;
24
+ let retryDelayMs = 5000;
25
+ let nativeEnabled = DEFAULT_NATIVE_ENABLED;
26
+ const RETRY_ALARM_NAME = "opendevbrowser-auto-connect";
27
+ const RETRY_MAX_MS = 60_000;
28
+ const ANNOTATION_CONTENT_SCRIPT = "dist/annotate-content.js";
29
+ const ANNOTATION_CONTENT_STYLE = "dist/annotate-content.css";
30
+ const ANNOTATION_MAX_PAYLOAD_BYTES = 10 * 1024 * 1024;
31
+ const ANNOTATION_REQUEST_TIMEOUT_MS = 120_000;
32
+ const LAST_ANNOTATION_META_KEY = "annotationLastMeta";
33
+ const LAST_ANNOTATION_PAYLOAD_KEY = "annotationLastPayloadSansScreenshots";
34
+ const BADGE_CONNECTED_DOT_COLOR = "#16a34a";
35
+ const BADGE_DISCONNECTED_DOT_COLOR = "#dc2626";
36
+ const annotationSessions = new Map();
37
+ let lastAnnotationFull = null;
38
+ connection.onAnnotationCommand((command) => {
39
+ handleRelayAnnotationCommand(command).catch((error) => {
40
+ logError("annotation.relay_command", error, { code: "annotation_command_failed" });
41
+ });
42
+ });
43
+ connection.onOpsMessage((message) => {
44
+ opsRuntime.handleMessage(message);
45
+ });
46
+ const RESTRICTED_PROTOCOLS = new Set([
47
+ "chrome:",
48
+ "chrome-extension:",
49
+ "chrome-search:",
50
+ "chrome-untrusted:",
51
+ "devtools:",
52
+ "chrome-devtools:",
53
+ "edge:",
54
+ "brave:"
55
+ ]);
5
56
  const updateBadge = (status) => {
6
57
  const isConnected = status === "connected";
7
- chrome.action.setBadgeText({ text: isConnected ? "ON" : "OFF" });
8
- chrome.action.setBadgeBackgroundColor({
9
- color: isConnected ? "#20d5c6" : "#5b667a"
58
+ const dotColor = isConnected ? BADGE_CONNECTED_DOT_COLOR : BADGE_DISCONNECTED_DOT_COLOR;
59
+ chrome.action.setBadgeText({ text: "●" });
60
+ if (typeof chrome.action.setBadgeTextColor === "function") {
61
+ chrome.action.setBadgeTextColor({ color: dotColor });
62
+ chrome.action.setBadgeBackgroundColor({ color: [0, 0, 0, 0] });
63
+ return;
64
+ }
65
+ chrome.action.setBadgeBackgroundColor({ color: dotColor });
66
+ };
67
+ const getEffectiveStatus = () => {
68
+ if (connection.getStatus() === "connected") {
69
+ return "connected";
70
+ }
71
+ if (nativeEnabled && nativePort.isConnected()) {
72
+ return "connected";
73
+ }
74
+ return "disconnected";
75
+ };
76
+ const buildStatusMessage = async () => {
77
+ const error = connection.getLastError();
78
+ const relayStatus = connection.getStatus();
79
+ const status = getEffectiveStatus();
80
+ let note = error?.message;
81
+ let relayHealth = null;
82
+ const isNativeEnabled = nativeEnabled;
83
+ let nativeHealth = isNativeEnabled ? nativePort.getHealth() : null;
84
+ if (isNativeEnabled && nativePort.isConnected()) {
85
+ try {
86
+ await nativePort.ping(1000);
87
+ }
88
+ catch (error) {
89
+ logError("native_port.ping", error, { code: "native_ping_failed" });
90
+ }
91
+ nativeHealth = nativePort.getHealth();
92
+ }
93
+ if (!error) {
94
+ if (relayStatus === "connected") {
95
+ const identity = connection.getRelayIdentity();
96
+ if (identity.relayPort && identity.instanceId) {
97
+ note = `Connected to 127.0.0.1:${identity.relayPort} (relay ${identity.instanceId.slice(0, 8)})`;
98
+ }
99
+ else if (identity.relayPort) {
100
+ note = `Connected to 127.0.0.1:${identity.relayPort}`;
101
+ }
102
+ relayHealth = await connection.relayHealthCheck();
103
+ }
104
+ else if (nativeHealth?.status === "connected") {
105
+ note = "Connected via native host.";
106
+ }
107
+ else {
108
+ const stored = await new Promise((resolve) => {
109
+ chrome.storage.local.get(["relayPort"], (items) => resolve(items));
110
+ });
111
+ const port = parsePort(stored.relayPort) ?? DEFAULT_RELAY_PORT;
112
+ relayHealth = await fetchRelayHealth(port);
113
+ note = statusNoteOverride ?? buildRelayHealthNote(relayHealth);
114
+ if (!statusNoteOverride && isNativeEnabled && nativeHealth?.status === "error") {
115
+ note = buildNativeHealthNote(nativeHealth);
116
+ }
117
+ }
118
+ }
119
+ if (!error) {
120
+ const relayNotice = connection.getRelayNotice();
121
+ if (relayNotice) {
122
+ note = relayNotice;
123
+ }
124
+ }
125
+ return {
126
+ type: "status",
127
+ status,
128
+ note,
129
+ relayHealth,
130
+ nativeHealth,
131
+ nativeEnabled: isNativeEnabled
132
+ };
133
+ };
134
+ const setStorage = (items) => {
135
+ return new Promise((resolve) => {
136
+ chrome.storage.local.set(items, () => resolve());
10
137
  });
11
138
  };
139
+ const setStatusNoteOverride = (note) => {
140
+ statusNoteOverride = note;
141
+ };
142
+ const buildRelayHealthNote = (health) => {
143
+ if (!health) {
144
+ return "Relay unreachable. Start the daemon and retry.";
145
+ }
146
+ switch (health.reason) {
147
+ case "pairing_invalid":
148
+ return "Pairing token mismatch. Update the token and reconnect.";
149
+ case "pairing_required":
150
+ return "Pairing required. Enable auto-pair or set the token.";
151
+ case "handshake_incomplete":
152
+ return "Extension handshake pending. Keep the relay running and retry.";
153
+ case "extension_disconnected":
154
+ return "Extension not connected to relay. Click Connect.";
155
+ case "annotation_disconnected":
156
+ return "Annotation channel disconnected. Keep the extension open and retry.";
157
+ case "ops_disconnected":
158
+ return "Ops channel disconnected. Start a new session and retry.";
159
+ case "cdp_disconnected":
160
+ return "No CDP clients connected. Start a session and retry.";
161
+ case "relay_down":
162
+ return "Relay down. Start the daemon and retry.";
163
+ default:
164
+ return "Local relay only. Tokens stay on-device.";
165
+ }
166
+ };
167
+ const buildNativeHealthNote = (health) => {
168
+ if (health.status === "connected") {
169
+ return "Native host connected.";
170
+ }
171
+ switch (health.error) {
172
+ case "host_not_installed":
173
+ return "Native host not installed. Run `opendevbrowser native install <extension-id>`.";
174
+ case "host_forbidden":
175
+ return "Native host forbidden. Verify the extension ID matches the manifest.";
176
+ case "host_disconnect":
177
+ return "Native host disconnected. Restart the host.";
178
+ case "host_timeout":
179
+ return "Native host ping timed out.";
180
+ case "host_message_too_large":
181
+ return "Native host rejected message size.";
182
+ default:
183
+ return "Native host unavailable.";
184
+ }
185
+ };
186
+ const clearRetry = () => {
187
+ retryScheduled = false;
188
+ retryDelayMs = 5000;
189
+ if (chrome.alarms?.clear) {
190
+ chrome.alarms.clear(RETRY_ALARM_NAME);
191
+ }
192
+ };
193
+ const scheduleRetry = () => {
194
+ if (retryScheduled) {
195
+ return;
196
+ }
197
+ retryScheduled = true;
198
+ const delayMs = retryDelayMs;
199
+ retryDelayMs = Math.min(retryDelayMs * 2, RETRY_MAX_MS);
200
+ if (chrome.alarms?.create) {
201
+ chrome.alarms.create(RETRY_ALARM_NAME, { when: Date.now() + delayMs });
202
+ return;
203
+ }
204
+ setTimeout(() => {
205
+ retryScheduled = false;
206
+ autoConnect().catch((error) => {
207
+ logError("auto_connect.retry", error, { code: "auto_connect_failed" });
208
+ });
209
+ }, delayMs);
210
+ };
211
+ const attemptNativeConnect = async () => {
212
+ if (!nativeEnabled) {
213
+ return false;
214
+ }
215
+ const connected = await nativePort.connect();
216
+ if (!connected) {
217
+ return false;
218
+ }
219
+ try {
220
+ await nativePort.ping(1500);
221
+ }
222
+ catch (error) {
223
+ logError("native_port.ping", error, { code: "native_ping_failed" });
224
+ }
225
+ updateBadge(getEffectiveStatus());
226
+ return nativePort.isConnected();
227
+ };
12
228
  const parsePort = (value) => {
13
229
  if (typeof value === "number" && Number.isInteger(value) && value > 0 && value <= 65535) {
14
230
  return value;
@@ -21,6 +237,30 @@ const parsePort = (value) => {
21
237
  }
22
238
  return null;
23
239
  };
240
+ const parseEpoch = (value) => {
241
+ if (typeof value === "number" && Number.isFinite(value)) {
242
+ return value;
243
+ }
244
+ return null;
245
+ };
246
+ const isWebStoreUrl = (url) => {
247
+ if (url.hostname === "chromewebstore.google.com") {
248
+ return true;
249
+ }
250
+ if (url.hostname === "chrome.google.com" && url.pathname.startsWith("/webstore")) {
251
+ return true;
252
+ }
253
+ return false;
254
+ };
255
+ const getRestrictionMessage = (url) => {
256
+ if (RESTRICTED_PROTOCOLS.has(url.protocol)) {
257
+ return "Active tab uses a restricted URL scheme. Open a normal http(s) page and retry.";
258
+ }
259
+ if (isWebStoreUrl(url)) {
260
+ return "Chrome Web Store tabs cannot be annotated. Open a normal tab and retry.";
261
+ }
262
+ return null;
263
+ };
24
264
  const fetchRelayConfig = async (port) => {
25
265
  try {
26
266
  const response = await fetch(`http://127.0.0.1:${port}/config`, {
@@ -36,12 +276,591 @@ const fetchRelayConfig = async (port) => {
36
276
  return null;
37
277
  }
38
278
  const pairingRequired = typeof data.pairingRequired === "boolean" ? data.pairingRequired : true;
39
- return { relayPort, pairingRequired };
279
+ const instanceId = typeof data.instanceId === "string" ? data.instanceId : null;
280
+ const epoch = parseEpoch(data.epoch);
281
+ return { relayPort, pairingRequired, instanceId, epoch };
40
282
  }
41
- catch {
283
+ catch (error) {
284
+ logError("relay.config_fetch", error, { code: "relay_config_fetch_failed", extra: { port } });
42
285
  return null;
43
286
  }
44
287
  };
288
+ const fetchRelayHealth = async (port) => {
289
+ try {
290
+ const response = await fetch(`http://127.0.0.1:${port}/status`, {
291
+ method: "GET",
292
+ headers: { "Accept": "application/json" }
293
+ });
294
+ if (!response.ok) {
295
+ return null;
296
+ }
297
+ const data = await response.json();
298
+ if (data.health && typeof data.health === "object") {
299
+ return data.health;
300
+ }
301
+ const extensionConnected = data.extensionConnected === true;
302
+ const handshake = data.extensionHandshakeComplete === true;
303
+ const cdpConnected = data.cdpConnected === true;
304
+ const annotationConnected = data.annotationConnected === true;
305
+ const opsConnected = data.opsConnected === true;
306
+ const pairingRequired = data.pairingRequired === true;
307
+ const ok = extensionConnected && handshake;
308
+ return {
309
+ ok,
310
+ reason: ok ? "ok" : (extensionConnected ? "handshake_incomplete" : "extension_disconnected"),
311
+ extensionConnected,
312
+ extensionHandshakeComplete: handshake,
313
+ cdpConnected,
314
+ annotationConnected,
315
+ opsConnected,
316
+ pairingRequired
317
+ };
318
+ }
319
+ catch (error) {
320
+ logError("relay.health_fetch", error, { code: "relay_health_fetch_failed", extra: { port } });
321
+ return null;
322
+ }
323
+ };
324
+ const sendAnnotationResponse = (payload, transport = "relay") => {
325
+ if (transport === "popup") {
326
+ return;
327
+ }
328
+ const response = { type: "annotationResponse", payload };
329
+ if (transport === "native") {
330
+ nativePort.send(response);
331
+ return;
332
+ }
333
+ connection.sendAnnotationResponse(response);
334
+ };
335
+ const sendAnnotationEvent = (payload, transport = "relay") => {
336
+ if (transport === "popup") {
337
+ return;
338
+ }
339
+ const event = { type: "annotationEvent", payload };
340
+ if (transport === "native") {
341
+ nativePort.send(event);
342
+ return;
343
+ }
344
+ connection.sendAnnotationEvent(event);
345
+ };
346
+ const startAnnotationTimeout = (requestId, transport) => {
347
+ return setTimeout(() => {
348
+ const session = annotationSessions.get(requestId);
349
+ if (!session)
350
+ return;
351
+ annotationSessions.delete(requestId);
352
+ const response = {
353
+ version: 1,
354
+ requestId,
355
+ status: "error",
356
+ error: { code: "timeout", message: "Annotation request timed out." }
357
+ };
358
+ const meta = buildLastAnnotationMeta(requestId, response, false);
359
+ lastAnnotationFull = null;
360
+ persistLastAnnotation(meta, null).catch((error) => {
361
+ logError("annotation.persist_timeout_meta", error, { code: "annotation_persist_failed" });
362
+ });
363
+ sendAnnotationResponse(response, transport);
364
+ }, ANNOTATION_REQUEST_TIMEOUT_MS);
365
+ };
366
+ const getTab = async (tabId) => {
367
+ try {
368
+ return await chrome.tabs.get(tabId);
369
+ }
370
+ catch (error) {
371
+ logError("tabs.get", error, { code: "tab_lookup_failed", extra: { tabId } });
372
+ return null;
373
+ }
374
+ };
375
+ const createTab = async (url) => {
376
+ return await new Promise((resolve, reject) => {
377
+ chrome.tabs.create({ url, active: true }, (tab) => {
378
+ const lastError = chrome.runtime.lastError;
379
+ if (lastError) {
380
+ reject(new Error(lastError.message));
381
+ return;
382
+ }
383
+ if (!tab) {
384
+ reject(new Error("Tab creation failed"));
385
+ return;
386
+ }
387
+ resolve(tab);
388
+ });
389
+ });
390
+ };
391
+ const updateTabUrl = async (tabId, url) => {
392
+ return await new Promise((resolve, reject) => {
393
+ chrome.tabs.update(tabId, { url, active: true }, (tab) => {
394
+ const lastError = chrome.runtime.lastError;
395
+ if (lastError) {
396
+ reject(new Error(lastError.message));
397
+ return;
398
+ }
399
+ if (!tab) {
400
+ reject(new Error("Tab update failed"));
401
+ return;
402
+ }
403
+ resolve(tab);
404
+ });
405
+ });
406
+ };
407
+ const waitForTabComplete = async (tabId, timeoutMs = 10000) => {
408
+ const tab = await getTab(tabId);
409
+ if (tab?.status === "complete")
410
+ return;
411
+ await new Promise((resolve, reject) => {
412
+ let settled = false;
413
+ const timeout = setTimeout(() => {
414
+ if (settled)
415
+ return;
416
+ settled = true;
417
+ chrome.tabs.onUpdated.removeListener(listener);
418
+ reject(new Error("Tab load timeout"));
419
+ }, timeoutMs);
420
+ const listener = (updatedId, changeInfo) => {
421
+ if (updatedId !== tabId)
422
+ return;
423
+ if (changeInfo.status === "complete") {
424
+ if (settled)
425
+ return;
426
+ settled = true;
427
+ clearTimeout(timeout);
428
+ chrome.tabs.onUpdated.removeListener(listener);
429
+ resolve();
430
+ }
431
+ };
432
+ chrome.tabs.onUpdated.addListener(listener);
433
+ });
434
+ };
435
+ const getActiveTab = async () => {
436
+ const tabs = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
437
+ return tabs[0] ?? null;
438
+ };
439
+ const resolveAnnotationTab = async (command) => {
440
+ if (typeof command.tabId === "number") {
441
+ if (command.url) {
442
+ const updated = await updateTabUrl(command.tabId, command.url);
443
+ await waitForTabComplete(command.tabId);
444
+ return updated;
445
+ }
446
+ const existing = await getTab(command.tabId);
447
+ if (!existing) {
448
+ throw new Error("Target tab unavailable");
449
+ }
450
+ return existing;
451
+ }
452
+ if (command.url) {
453
+ const created = await createTab(command.url);
454
+ if (typeof created.id === "number") {
455
+ await waitForTabComplete(created.id);
456
+ }
457
+ return created;
458
+ }
459
+ const active = await getActiveTab();
460
+ if (!active) {
461
+ throw new Error("No active tab available");
462
+ }
463
+ return active;
464
+ };
465
+ const isRestrictedTab = (tab) => {
466
+ const rawUrl = tab.url ?? tab.pendingUrl ?? "";
467
+ if (!rawUrl)
468
+ return "Active tab URL unavailable.";
469
+ let parsed = null;
470
+ try {
471
+ parsed = new URL(rawUrl);
472
+ }
473
+ catch (error) {
474
+ logError("annotation.parse_tab_url", error, { code: "tab_url_parse_failed" });
475
+ return "Active tab URL is invalid.";
476
+ }
477
+ return getRestrictionMessage(parsed);
478
+ };
479
+ const injectAnnotationAssets = async (tabId) => {
480
+ await new Promise((resolve, reject) => {
481
+ chrome.scripting.insertCSS({ target: { tabId }, files: [ANNOTATION_CONTENT_STYLE] }, () => {
482
+ const lastError = chrome.runtime.lastError;
483
+ if (lastError) {
484
+ reject(new Error(lastError.message));
485
+ return;
486
+ }
487
+ resolve();
488
+ });
489
+ });
490
+ await new Promise((resolve, reject) => {
491
+ chrome.scripting.executeScript({ target: { tabId }, files: [ANNOTATION_CONTENT_SCRIPT] }, () => {
492
+ const lastError = chrome.runtime.lastError;
493
+ if (lastError) {
494
+ reject(new Error(lastError.message));
495
+ return;
496
+ }
497
+ resolve();
498
+ });
499
+ });
500
+ };
501
+ const sendMessageToTab = async (tabId, message) => {
502
+ return await new Promise((resolve, reject) => {
503
+ chrome.tabs.sendMessage(tabId, message, (response) => {
504
+ const lastError = chrome.runtime.lastError;
505
+ if (lastError) {
506
+ reject(new Error(lastError.message));
507
+ return;
508
+ }
509
+ resolve(response);
510
+ });
511
+ });
512
+ };
513
+ const isMissingAnnotationReceiverError = (error) => {
514
+ if (!(error instanceof Error)) {
515
+ return false;
516
+ }
517
+ return error.message.includes("Receiving end does not exist");
518
+ };
519
+ const sleep = async (ms) => {
520
+ await new Promise((resolve) => setTimeout(resolve, ms));
521
+ };
522
+ const pingAnnotation = async (tabId) => {
523
+ const response = await sendMessageToTab(tabId, { type: "annotation:ping" });
524
+ const ok = typeof response === "object" && response !== null && response.ok === true;
525
+ if (!ok) {
526
+ throw new Error("Annotation ping failed");
527
+ }
528
+ };
529
+ const ensureAnnotationInjected = async (tabId) => {
530
+ try {
531
+ await pingAnnotation(tabId);
532
+ return;
533
+ }
534
+ catch (error) {
535
+ // Initial ping can fail before content script injection; avoid noisy logs for missing receivers.
536
+ if (!isMissingAnnotationReceiverError(error)) {
537
+ logError("annotation.ping", error, { code: "annotation_ping_failed", extra: { tabId } });
538
+ }
539
+ }
540
+ const backoff = [150, 400];
541
+ let lastError = null;
542
+ for (let attempt = 0; attempt <= backoff.length; attempt += 1) {
543
+ try {
544
+ await injectAnnotationAssets(tabId);
545
+ await pingAnnotation(tabId);
546
+ return;
547
+ }
548
+ catch (error) {
549
+ lastError = error;
550
+ logError("annotation.inject", error, { code: "annotation_injection_failed", extra: { tabId, attempt } });
551
+ if (attempt < backoff.length) {
552
+ const delay = backoff[attempt] ?? 0;
553
+ await sleep(delay);
554
+ }
555
+ }
556
+ }
557
+ if (lastError instanceof Error) {
558
+ throw lastError;
559
+ }
560
+ throw new Error("Annotation injection failed");
561
+ };
562
+ const probeAnnotationInjected = async () => {
563
+ const active = await getActiveTab();
564
+ if (!active || typeof active.id !== "number") {
565
+ return { injected: false, detail: "No active tab available." };
566
+ }
567
+ const restricted = isRestrictedTab(active);
568
+ if (restricted) {
569
+ return { injected: false, detail: restricted };
570
+ }
571
+ try {
572
+ await pingAnnotation(active.id);
573
+ return { injected: true };
574
+ }
575
+ catch (error) {
576
+ const message = error instanceof Error ? error.message : "Annotation not injected.";
577
+ return { injected: false, detail: message };
578
+ }
579
+ };
580
+ const toggleAnnotationUi = async () => {
581
+ const tab = await getActiveTab();
582
+ if (!tab || typeof tab.id !== "number") {
583
+ return;
584
+ }
585
+ const restricted = isRestrictedTab(tab);
586
+ if (restricted) {
587
+ return;
588
+ }
589
+ await ensureAnnotationInjected(tab.id);
590
+ await sendMessageToTab(tab.id, { type: "annotation:toggle" });
591
+ };
592
+ const startAnnotationSession = async (command, transport) => {
593
+ const requestId = command.requestId;
594
+ if (annotationSessions.has(requestId)) {
595
+ sendAnnotationResponse({
596
+ version: 1,
597
+ requestId,
598
+ status: "error",
599
+ error: { code: "invalid_request", message: "Duplicate annotation requestId." }
600
+ }, transport);
601
+ return;
602
+ }
603
+ const tab = await resolveAnnotationTab(command);
604
+ const restricted = isRestrictedTab(tab);
605
+ if (restricted) {
606
+ sendAnnotationResponse({
607
+ version: 1,
608
+ requestId,
609
+ status: "error",
610
+ error: { code: "restricted_url", message: restricted }
611
+ }, transport);
612
+ return;
613
+ }
614
+ if (typeof tab.id !== "number") {
615
+ sendAnnotationResponse({
616
+ version: 1,
617
+ requestId,
618
+ status: "error",
619
+ error: { code: "invalid_request", message: "Target tab missing id." }
620
+ }, transport);
621
+ return;
622
+ }
623
+ let timeoutId = null;
624
+ try {
625
+ await ensureAnnotationInjected(tab.id);
626
+ timeoutId = startAnnotationTimeout(requestId, transport);
627
+ annotationSessions.set(requestId, {
628
+ requestId,
629
+ tabId: tab.id,
630
+ options: command.options,
631
+ createdAt: Date.now(),
632
+ timeoutId,
633
+ transport
634
+ });
635
+ await sendMessageToTab(tab.id, {
636
+ type: "annotation:start",
637
+ requestId,
638
+ options: command.options ?? {},
639
+ url: command.url
640
+ });
641
+ }
642
+ catch (error) {
643
+ if (timeoutId !== null) {
644
+ clearTimeout(timeoutId);
645
+ }
646
+ annotationSessions.delete(requestId);
647
+ throw error instanceof Error ? error : new Error("Annotation injection failed");
648
+ }
649
+ };
650
+ const cancelAnnotationSession = async (requestId, transport) => {
651
+ const session = annotationSessions.get(requestId);
652
+ if (!session) {
653
+ sendAnnotationResponse({
654
+ version: 1,
655
+ requestId,
656
+ status: "cancelled",
657
+ error: { code: "cancelled", message: "Annotation session not active." }
658
+ }, transport);
659
+ return;
660
+ }
661
+ clearTimeout(session.timeoutId);
662
+ annotationSessions.delete(requestId);
663
+ await sendMessageToTab(session.tabId, { type: "annotation:cancel", requestId });
664
+ sendAnnotationResponse({
665
+ version: 1,
666
+ requestId,
667
+ status: "cancelled",
668
+ error: { code: "cancelled", message: "Annotation cancelled." }
669
+ }, session.transport);
670
+ };
671
+ const validatePayloadSize = (payload) => {
672
+ const size = new TextEncoder().encode(JSON.stringify(payload)).length;
673
+ return size <= ANNOTATION_MAX_PAYLOAD_BYTES;
674
+ };
675
+ const generateAnnotationRequestId = () => {
676
+ return crypto.randomUUID();
677
+ };
678
+ const stripScreenshots = (payload) => {
679
+ const { screenshots, annotations, ...rest } = payload;
680
+ void screenshots;
681
+ return {
682
+ ...rest,
683
+ annotations: annotations.map((item) => {
684
+ const { screenshotId, ...restItem } = item;
685
+ void screenshotId;
686
+ return restItem;
687
+ })
688
+ };
689
+ };
690
+ const buildLastAnnotationMeta = (requestId, response, hasFullPayloadInMemory) => {
691
+ const payload = response.payload;
692
+ const annotationCount = payload ? payload.annotations.length : undefined;
693
+ const screenshotCount = payload?.screenshots?.length ?? 0;
694
+ return {
695
+ requestId,
696
+ status: response.status,
697
+ error: response.error,
698
+ url: payload?.url,
699
+ title: payload?.title,
700
+ timestamp: payload?.timestamp,
701
+ annotationCount,
702
+ screenshotCount: payload ? screenshotCount : undefined,
703
+ screenshotMode: payload?.screenshotMode,
704
+ storedAt: Date.now(),
705
+ hasScreenshots: screenshotCount > 0,
706
+ hasFullPayloadInMemory
707
+ };
708
+ };
709
+ const persistLastAnnotation = async (meta, payload) => {
710
+ await setStorage({
711
+ [LAST_ANNOTATION_META_KEY]: meta,
712
+ [LAST_ANNOTATION_PAYLOAD_KEY]: payload
713
+ });
714
+ };
715
+ const loadPersistedLastAnnotation = async () => {
716
+ const data = await new Promise((resolve) => {
717
+ chrome.storage.local.get([LAST_ANNOTATION_META_KEY, LAST_ANNOTATION_PAYLOAD_KEY], (items) => resolve(items));
718
+ });
719
+ const metaRecord = data[LAST_ANNOTATION_META_KEY];
720
+ const payloadRecord = data[LAST_ANNOTATION_PAYLOAD_KEY];
721
+ const meta = metaRecord && typeof metaRecord === "object" ? metaRecord : null;
722
+ const payload = payloadRecord && typeof payloadRecord === "object" ? payloadRecord : null;
723
+ return { meta, payload };
724
+ };
725
+ async function handleNativePortMessage(payload) {
726
+ if (!payload || typeof payload !== "object") {
727
+ return;
728
+ }
729
+ const record = payload;
730
+ if (record.type === "annotationCommand") {
731
+ await handleNativeAnnotationCommand(record);
732
+ }
733
+ }
734
+ const handleRelayAnnotationCommand = async (command, transport = "relay") => {
735
+ const payload = command.payload;
736
+ if (!payload || payload.version !== 1 || typeof payload.requestId !== "string") {
737
+ sendAnnotationResponse({
738
+ version: 1,
739
+ requestId: payload?.requestId ?? "unknown",
740
+ status: "error",
741
+ error: { code: "invalid_request", message: "Invalid annotation command." }
742
+ }, transport);
743
+ return;
744
+ }
745
+ if (payload.command === "cancel") {
746
+ await cancelAnnotationSession(payload.requestId, transport);
747
+ return;
748
+ }
749
+ try {
750
+ await startAnnotationSession(payload, transport);
751
+ sendAnnotationEvent({
752
+ version: 1,
753
+ requestId: payload.requestId,
754
+ event: "ready",
755
+ message: "Annotation session started."
756
+ }, transport);
757
+ }
758
+ catch (error) {
759
+ const detail = error instanceof Error ? error.message : "Annotation start failed.";
760
+ sendAnnotationResponse({
761
+ version: 1,
762
+ requestId: payload.requestId,
763
+ status: "error",
764
+ error: { code: "injection_failed", message: detail }
765
+ }, transport);
766
+ }
767
+ };
768
+ const handleNativeAnnotationCommand = async (command) => {
769
+ await handleRelayAnnotationCommand(command, "native");
770
+ };
771
+ const finalizeAnnotationSession = (requestId) => {
772
+ const session = annotationSessions.get(requestId);
773
+ if (!session)
774
+ return null;
775
+ clearTimeout(session.timeoutId);
776
+ annotationSessions.delete(requestId);
777
+ return session;
778
+ };
779
+ const handleAnnotationComplete = (requestId, payload) => {
780
+ const session = finalizeAnnotationSession(requestId);
781
+ if (!session) {
782
+ return;
783
+ }
784
+ if (!validatePayloadSize(payload)) {
785
+ const response = {
786
+ version: 1,
787
+ requestId,
788
+ status: "error",
789
+ error: { code: "payload_too_large", message: "Annotation payload exceeded size limits." }
790
+ };
791
+ const meta = buildLastAnnotationMeta(requestId, response, false);
792
+ lastAnnotationFull = null;
793
+ persistLastAnnotation(meta, null).catch((error) => {
794
+ logError("annotation.persist_payload_too_large", error, { code: "annotation_persist_failed" });
795
+ });
796
+ sendAnnotationResponse(response, session.transport);
797
+ return;
798
+ }
799
+ const response = {
800
+ version: 1,
801
+ requestId,
802
+ status: "ok",
803
+ payload
804
+ };
805
+ const meta = buildLastAnnotationMeta(requestId, response, true);
806
+ lastAnnotationFull = { meta, payload };
807
+ const storageMeta = { ...meta, hasFullPayloadInMemory: false };
808
+ const sanitizedPayload = stripScreenshots(payload);
809
+ persistLastAnnotation(storageMeta, sanitizedPayload).catch((error) => {
810
+ logError("annotation.persist_sanitized_payload", error, { code: "annotation_persist_failed" });
811
+ });
812
+ sendAnnotationResponse(response, session.transport);
813
+ };
814
+ const handleAnnotationError = (requestId, error) => {
815
+ const session = finalizeAnnotationSession(requestId);
816
+ if (!session)
817
+ return;
818
+ const response = {
819
+ version: 1,
820
+ requestId,
821
+ status: "error",
822
+ error
823
+ };
824
+ const meta = buildLastAnnotationMeta(requestId, response, false);
825
+ lastAnnotationFull = null;
826
+ persistLastAnnotation(meta, null).catch((error) => {
827
+ logError("annotation.persist_error_meta", error, { code: "annotation_persist_failed" });
828
+ });
829
+ sendAnnotationResponse(response, session.transport);
830
+ };
831
+ const handleAnnotationCancelled = (requestId) => {
832
+ const session = finalizeAnnotationSession(requestId);
833
+ if (!session)
834
+ return;
835
+ const response = {
836
+ version: 1,
837
+ requestId,
838
+ status: "cancelled",
839
+ error: { code: "cancelled", message: "Annotation cancelled." }
840
+ };
841
+ const meta = buildLastAnnotationMeta(requestId, response, false);
842
+ lastAnnotationFull = null;
843
+ persistLastAnnotation(meta, null).catch((error) => {
844
+ logError("annotation.persist_cancel_meta", error, { code: "annotation_persist_failed" });
845
+ });
846
+ sendAnnotationResponse(response, session.transport);
847
+ };
848
+ const captureVisibleTab = async (tab) => {
849
+ return await new Promise((resolve, reject) => {
850
+ chrome.tabs.captureVisibleTab(tab.windowId, { format: "png" }, (dataUrl) => {
851
+ const lastError = chrome.runtime.lastError;
852
+ if (lastError) {
853
+ reject(new Error(lastError.message));
854
+ return;
855
+ }
856
+ if (!dataUrl) {
857
+ reject(new Error("Capture failed"));
858
+ return;
859
+ }
860
+ resolve(dataUrl);
861
+ });
862
+ });
863
+ };
45
864
  const fetchTokenFromPlugin = async (port) => {
46
865
  try {
47
866
  const response = await fetch(`http://127.0.0.1:${port}/pair`, {
@@ -52,42 +871,135 @@ const fetchTokenFromPlugin = async (port) => {
52
871
  return null;
53
872
  }
54
873
  const data = await response.json();
55
- return typeof data.token === "string" ? data.token : null;
874
+ if (typeof data.token !== "string") {
875
+ return null;
876
+ }
877
+ return {
878
+ token: data.token,
879
+ instanceId: typeof data.instanceId === "string" ? data.instanceId : null,
880
+ epoch: parseEpoch(data.epoch)
881
+ };
56
882
  }
57
- catch {
883
+ catch (error) {
884
+ logError("relay.token_fetch", error, { code: "relay_pair_fetch_failed", extra: { port } });
58
885
  return null;
59
886
  }
60
887
  };
888
+ const clearStoredRelayState = async () => {
889
+ await setStorage({
890
+ relayPort: null,
891
+ relayInstanceId: null,
892
+ relayEpoch: null,
893
+ pairingToken: null,
894
+ tokenEpoch: null
895
+ });
896
+ };
61
897
  const attemptAutoConnect = async () => {
62
898
  const data = await new Promise((resolve) => {
63
- chrome.storage.local.get(["autoConnect", "autoPair", "pairingEnabled", "pairingToken", "relayPort"], (items) => {
899
+ chrome.storage.local.get([
900
+ "autoConnect",
901
+ "autoPair",
902
+ "pairingEnabled",
903
+ "pairingToken",
904
+ "relayPort",
905
+ "relayInstanceId",
906
+ "relayEpoch",
907
+ "tokenEpoch",
908
+ "nativeEnabled"
909
+ ], (items) => {
64
910
  resolve(items);
65
911
  });
66
912
  });
67
913
  const autoConnect = typeof data.autoConnect === "boolean" ? data.autoConnect : DEFAULT_AUTO_CONNECT;
68
914
  if (!autoConnect || connection.getStatus() === "connected") {
915
+ clearRetry();
69
916
  return;
70
917
  }
71
918
  const autoPair = typeof data.autoPair === "boolean" ? data.autoPair : DEFAULT_AUTO_PAIR;
72
919
  const pairingEnabled = typeof data.pairingEnabled === "boolean" ? data.pairingEnabled : DEFAULT_PAIRING_ENABLED;
920
+ const storedRelayPort = parsePort(data.relayPort) ?? DEFAULT_RELAY_PORT;
921
+ let storedPairingToken = typeof data.pairingToken === "string" ? data.pairingToken : null;
922
+ nativeEnabled = typeof data.nativeEnabled === "boolean" ? data.nativeEnabled : DEFAULT_NATIVE_ENABLED;
923
+ if (typeof data.nativeEnabled !== "boolean") {
924
+ await setStorage({ nativeEnabled });
925
+ }
73
926
  if (autoPair && pairingEnabled) {
74
- const config = await fetchRelayConfig(DEFAULT_DISCOVERY_PORT);
75
- const relayPort = config?.relayPort ?? parsePort(data.relayPort) ?? DEFAULT_RELAY_PORT;
76
- if (config?.relayPort) {
77
- chrome.storage.local.set({ relayPort: config.relayPort });
927
+ let config = await fetchRelayConfig(DEFAULT_DISCOVERY_PORT);
928
+ if (!config && storedRelayPort !== DEFAULT_DISCOVERY_PORT) {
929
+ config = await fetchRelayConfig(storedRelayPort);
930
+ }
931
+ const storedRelayEpoch = parseEpoch(data.relayEpoch);
932
+ const storedRelayInstanceId = typeof data.relayInstanceId === "string" ? data.relayInstanceId : null;
933
+ const storedTokenEpoch = parseEpoch(data.tokenEpoch);
934
+ if (!config) {
935
+ setStatusNoteOverride("Relay config unreachable. Start the daemon and retry.");
936
+ scheduleRetry();
937
+ return;
938
+ }
939
+ const relayPort = config.relayPort ?? storedRelayPort;
940
+ const configEpoch = config.epoch ?? null;
941
+ const hasEpoch = config.epoch !== null;
942
+ if (config.relayPort) {
943
+ await setStorage({
944
+ relayPort: config.relayPort,
945
+ relayInstanceId: config.instanceId,
946
+ relayEpoch: config.epoch
947
+ });
948
+ }
949
+ if (hasEpoch && storedRelayEpoch !== null && storedRelayEpoch !== configEpoch) {
950
+ await clearStoredRelayState();
951
+ storedPairingToken = null;
952
+ setStatusNoteOverride("Relay restarted. Refresh the connection.");
78
953
  }
79
- const pairingRequired = config?.pairingRequired ?? true;
954
+ if (config.instanceId && storedRelayInstanceId && config.instanceId !== storedRelayInstanceId) {
955
+ await clearStoredRelayState();
956
+ storedPairingToken = null;
957
+ setStatusNoteOverride("Relay instance mismatch. Open the popup and click Connect.");
958
+ }
959
+ if (hasEpoch && storedTokenEpoch !== null && storedTokenEpoch !== configEpoch) {
960
+ await setStorage({ pairingToken: null, tokenEpoch: null });
961
+ storedPairingToken = null;
962
+ }
963
+ if (hasEpoch && storedTokenEpoch === null && storedPairingToken) {
964
+ await setStorage({ pairingToken: null, tokenEpoch: null });
965
+ storedPairingToken = null;
966
+ }
967
+ const pairingRequired = config.pairingRequired ?? true;
80
968
  if (pairingRequired) {
81
- const fetchedToken = await fetchTokenFromPlugin(relayPort);
82
- if (fetchedToken) {
83
- chrome.storage.local.set({ pairingToken: fetchedToken });
84
- }
85
- else {
86
- return;
969
+ if (!storedPairingToken) {
970
+ const fetched = await fetchTokenFromPlugin(relayPort);
971
+ if (!fetched) {
972
+ setStatusNoteOverride("Auto-pair failed. Start the daemon and retry.");
973
+ scheduleRetry();
974
+ return;
975
+ }
976
+ if (config.instanceId && fetched.instanceId && config.instanceId !== fetched.instanceId) {
977
+ console.warn("[opendevbrowser] Relay instance mismatch during auto-pair. Retrying later.");
978
+ setStatusNoteOverride("Relay instance mismatch. Open the popup and click Connect.");
979
+ return;
980
+ }
981
+ const tokenEpoch = fetched.epoch ?? configEpoch;
982
+ await setStorage({ pairingToken: fetched.token, tokenEpoch });
87
983
  }
88
984
  }
89
985
  }
90
986
  await connection.connect();
987
+ if (connection.getStatus() !== "connected") {
988
+ const nativeConnected = await attemptNativeConnect();
989
+ if (nativeConnected) {
990
+ setStatusNoteOverride("Connected via native host.");
991
+ clearRetry();
992
+ return;
993
+ }
994
+ if (nativeEnabled) {
995
+ setStatusNoteOverride(buildNativeHealthNote(nativePort.getHealth()));
996
+ }
997
+ scheduleRetry();
998
+ return;
999
+ }
1000
+ nativePort.disconnect();
1001
+ setStatusNoteOverride(null);
1002
+ clearRetry();
91
1003
  };
92
1004
  const autoConnect = async () => {
93
1005
  if (autoConnectInFlight) {
@@ -97,57 +1009,313 @@ const autoConnect = async () => {
97
1009
  try {
98
1010
  await attemptAutoConnect();
99
1011
  }
100
- catch {
1012
+ catch (error) {
1013
+ logError("auto_connect.attempt", error, { code: "auto_connect_failed" });
101
1014
  connection.disconnect();
1015
+ nativePort.disconnect();
102
1016
  }
103
1017
  finally {
104
1018
  autoConnectInFlight = false;
105
1019
  }
106
1020
  };
107
- connection.onStatus(updateBadge);
108
- updateBadge(connection.getStatus());
1021
+ connection.onStatus((status) => {
1022
+ const effectiveStatus = status === "connected" ? "connected" : (nativeEnabled && nativePort.isConnected()) ? "connected" : "disconnected";
1023
+ updateBadge(effectiveStatus);
1024
+ if (status === "connected") {
1025
+ nativePort.disconnect();
1026
+ setStatusNoteOverride(null);
1027
+ clearRetry();
1028
+ }
1029
+ if (status === "disconnected" && !nativePort.isConnected()) {
1030
+ for (const session of annotationSessions.values()) {
1031
+ clearTimeout(session.timeoutId);
1032
+ }
1033
+ annotationSessions.clear();
1034
+ }
1035
+ });
1036
+ updateBadge(getEffectiveStatus());
109
1037
  chrome.runtime.onStartup.addListener(() => {
110
- autoConnect().catch(() => { });
1038
+ autoConnect().catch((error) => {
1039
+ logError("auto_connect.startup", error, { code: "auto_connect_failed" });
1040
+ });
111
1041
  });
112
1042
  chrome.runtime.onInstalled.addListener(() => {
113
- autoConnect().catch(() => { });
1043
+ autoConnect().catch((error) => {
1044
+ logError("auto_connect.installed", error, { code: "auto_connect_failed" });
1045
+ });
1046
+ });
1047
+ if (chrome.alarms?.onAlarm) {
1048
+ chrome.alarms.onAlarm.addListener((alarm) => {
1049
+ if (alarm.name === RETRY_ALARM_NAME) {
1050
+ retryScheduled = false;
1051
+ autoConnect().catch((error) => {
1052
+ logError("auto_connect.alarm", error, { code: "auto_connect_failed" });
1053
+ });
1054
+ }
1055
+ });
1056
+ }
1057
+ if (chrome.commands?.onCommand) {
1058
+ chrome.commands.onCommand.addListener((command) => {
1059
+ if (command === "toggle-annotation") {
1060
+ toggleAnnotationUi().catch((error) => {
1061
+ logError("annotation.toggle", error, { code: "annotation_toggle_failed" });
1062
+ });
1063
+ }
1064
+ });
1065
+ }
1066
+ autoConnect().catch((error) => {
1067
+ logError("auto_connect.startup", error, { code: "auto_connect_failed" });
114
1068
  });
115
- autoConnect().catch(() => { });
116
1069
  chrome.storage.onChanged.addListener((changes, area) => {
117
1070
  if (area !== "local") {
118
1071
  return;
119
1072
  }
1073
+ if (changes.nativeEnabled) {
1074
+ nativeEnabled = changes.nativeEnabled.newValue === true;
1075
+ if (!nativeEnabled) {
1076
+ nativePort.disconnect();
1077
+ }
1078
+ updateBadge(getEffectiveStatus());
1079
+ }
120
1080
  if (changes.autoConnect?.newValue === true) {
121
- autoConnect().catch(() => { });
1081
+ autoConnect().catch((error) => {
1082
+ logError("auto_connect.setting", error, { code: "auto_connect_failed" });
1083
+ });
1084
+ }
1085
+ if (changes.pairingToken) {
1086
+ autoConnect().catch((error) => {
1087
+ logError("auto_connect.pairing_token", error, { code: "auto_connect_failed" });
1088
+ });
122
1089
  }
123
1090
  });
124
- chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
1091
+ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
125
1092
  const respond = (status) => {
126
1093
  sendResponse(status);
127
1094
  };
128
1095
  if (message.type === "status") {
129
- respond({ type: "status", status: connection.getStatus() });
1096
+ (async () => {
1097
+ respond(await buildStatusMessage());
1098
+ })().catch((error) => {
1099
+ logError("popup.status", error, { code: "status_failed" });
1100
+ respond({
1101
+ type: "status",
1102
+ status: connection.getStatus(),
1103
+ note: "Background unavailable"
1104
+ });
1105
+ });
130
1106
  return true;
131
1107
  }
132
1108
  if (message.type === "connect") {
133
1109
  (async () => {
134
1110
  await connection.connect();
135
- respond({ type: "status", status: connection.getStatus() });
136
- })().catch(() => {
1111
+ if (connection.getStatus() !== "connected") {
1112
+ await attemptNativeConnect();
1113
+ }
1114
+ respond(await buildStatusMessage());
1115
+ })().catch((error) => {
1116
+ logError("popup.connect", error, { code: "connect_failed" });
137
1117
  connection.disconnect();
138
- respond({ type: "status", status: connection.getStatus() });
1118
+ nativePort.disconnect();
1119
+ respond({
1120
+ type: "status",
1121
+ status: "disconnected",
1122
+ note: "Connect failed"
1123
+ });
139
1124
  });
140
1125
  return true;
141
1126
  }
142
1127
  if (message.type === "disconnect") {
143
1128
  (async () => {
144
1129
  await connection.disconnect();
145
- respond({ type: "status", status: connection.getStatus() });
146
- })().catch(() => {
1130
+ nativePort.disconnect();
1131
+ connection.clearLastError();
1132
+ respond(await buildStatusMessage());
1133
+ })().catch((error) => {
1134
+ logError("popup.disconnect", error, { code: "disconnect_failed" });
147
1135
  connection.disconnect();
148
- respond({ type: "status", status: connection.getStatus() });
1136
+ nativePort.disconnect();
1137
+ connection.clearLastError();
1138
+ respond({
1139
+ type: "status",
1140
+ status: "disconnected",
1141
+ note: "Disconnect failed"
1142
+ });
149
1143
  });
150
1144
  return true;
151
1145
  }
1146
+ if (message.type === "annotation:start") {
1147
+ (async () => {
1148
+ const requestId = generateAnnotationRequestId();
1149
+ const response = {
1150
+ type: "annotation:startResult",
1151
+ requestId,
1152
+ ok: false
1153
+ };
1154
+ try {
1155
+ const active = await getActiveTab();
1156
+ if (!active) {
1157
+ response.error = { code: "invalid_request", message: "No active tab available." };
1158
+ sendResponse(response);
1159
+ return;
1160
+ }
1161
+ const restricted = isRestrictedTab(active);
1162
+ if (restricted) {
1163
+ response.error = { code: "restricted_url", message: restricted };
1164
+ sendResponse(response);
1165
+ return;
1166
+ }
1167
+ if (typeof active.id !== "number") {
1168
+ response.error = { code: "invalid_request", message: "Target tab missing id." };
1169
+ sendResponse(response);
1170
+ return;
1171
+ }
1172
+ await startAnnotationSession({
1173
+ version: 1,
1174
+ requestId,
1175
+ command: "start",
1176
+ tabId: active.id,
1177
+ options: message.options
1178
+ }, "popup");
1179
+ if (!annotationSessions.has(requestId)) {
1180
+ response.error = { code: "injection_failed", message: "Annotation session failed to start." };
1181
+ sendResponse(response);
1182
+ return;
1183
+ }
1184
+ response.ok = true;
1185
+ sendResponse(response);
1186
+ }
1187
+ catch (error) {
1188
+ const detail = error instanceof Error ? error.message : "Annotation start failed.";
1189
+ response.error = { code: "injection_failed", message: detail };
1190
+ sendResponse(response);
1191
+ }
1192
+ })();
1193
+ return true;
1194
+ }
1195
+ if (message.type === "annotation:lastMeta") {
1196
+ (async () => {
1197
+ if (lastAnnotationFull) {
1198
+ const meta = { ...lastAnnotationFull.meta, hasFullPayloadInMemory: true };
1199
+ const response = { type: "annotation:lastMetaResult", meta };
1200
+ sendResponse(response);
1201
+ return;
1202
+ }
1203
+ const stored = await loadPersistedLastAnnotation();
1204
+ const meta = stored.meta ? { ...stored.meta, hasFullPayloadInMemory: false } : null;
1205
+ const response = { type: "annotation:lastMetaResult", meta };
1206
+ sendResponse(response);
1207
+ })().catch(() => {
1208
+ const response = { type: "annotation:lastMetaResult", meta: null };
1209
+ sendResponse(response);
1210
+ });
1211
+ return true;
1212
+ }
1213
+ if (message.type === "annotation:probe") {
1214
+ (async () => {
1215
+ const result = await probeAnnotationInjected();
1216
+ sendResponse({ type: "annotation:probeResult", ...result });
1217
+ })().catch((error) => {
1218
+ logError("annotation.probe", error, { code: "annotation_probe_failed" });
1219
+ sendResponse({ type: "annotation:probeResult", injected: false, detail: "Probe failed." });
1220
+ });
1221
+ return true;
1222
+ }
1223
+ if (message.type === "annotation:getPayload") {
1224
+ (async () => {
1225
+ if (message.includeScreenshots && lastAnnotationFull) {
1226
+ const response = {
1227
+ type: "annotation:payloadResult",
1228
+ payload: lastAnnotationFull.payload,
1229
+ meta: { ...lastAnnotationFull.meta, hasFullPayloadInMemory: true },
1230
+ source: "memory"
1231
+ };
1232
+ sendResponse(response);
1233
+ return;
1234
+ }
1235
+ const stored = await loadPersistedLastAnnotation();
1236
+ const storedMeta = stored.meta ? { ...stored.meta, hasFullPayloadInMemory: false } : null;
1237
+ if (message.includeScreenshots) {
1238
+ const response = {
1239
+ type: "annotation:payloadResult",
1240
+ payload: null,
1241
+ meta: storedMeta,
1242
+ source: "none",
1243
+ warning: "Full payload not available; screenshots may have been dropped."
1244
+ };
1245
+ sendResponse(response);
1246
+ return;
1247
+ }
1248
+ if (lastAnnotationFull) {
1249
+ const response = {
1250
+ type: "annotation:payloadResult",
1251
+ payload: stripScreenshots(lastAnnotationFull.payload),
1252
+ meta: { ...lastAnnotationFull.meta, hasFullPayloadInMemory: true },
1253
+ source: "memory"
1254
+ };
1255
+ sendResponse(response);
1256
+ return;
1257
+ }
1258
+ if (stored.payload) {
1259
+ const response = {
1260
+ type: "annotation:payloadResult",
1261
+ payload: stored.payload,
1262
+ meta: storedMeta,
1263
+ source: "storage"
1264
+ };
1265
+ sendResponse(response);
1266
+ return;
1267
+ }
1268
+ const response = {
1269
+ type: "annotation:payloadResult",
1270
+ payload: null,
1271
+ meta: storedMeta,
1272
+ source: "none",
1273
+ warning: "No stored annotation payload."
1274
+ };
1275
+ sendResponse(response);
1276
+ })().catch(() => {
1277
+ const response = {
1278
+ type: "annotation:payloadResult",
1279
+ payload: null,
1280
+ meta: null,
1281
+ source: "none",
1282
+ warning: "Background unavailable."
1283
+ };
1284
+ sendResponse(response);
1285
+ });
1286
+ return true;
1287
+ }
1288
+ if (message.type === "annotation:capture") {
1289
+ (async () => {
1290
+ const tab = sender.tab;
1291
+ if (!tab) {
1292
+ sendResponse({ ok: false, error: "No tab for capture" });
1293
+ return;
1294
+ }
1295
+ try {
1296
+ const dataUrl = await captureVisibleTab(tab);
1297
+ sendResponse({ ok: true, dataUrl });
1298
+ }
1299
+ catch (error) {
1300
+ sendResponse({ ok: false, error: error instanceof Error ? error.message : "Capture failed" });
1301
+ }
1302
+ })();
1303
+ return true;
1304
+ }
1305
+ if (message.type === "annotation:complete") {
1306
+ handleAnnotationComplete(message.requestId, message.payload);
1307
+ sendResponse({ ok: true });
1308
+ return true;
1309
+ }
1310
+ if (message.type === "annotation:cancelled") {
1311
+ handleAnnotationCancelled(message.requestId);
1312
+ sendResponse({ ok: true });
1313
+ return true;
1314
+ }
1315
+ if (message.type === "annotation:error") {
1316
+ handleAnnotationError(message.requestId, message.error);
1317
+ sendResponse({ ok: true });
1318
+ return true;
1319
+ }
152
1320
  return false;
153
1321
  });