opendevbrowser 0.0.28 → 0.0.30

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 (325) hide show
  1. package/README.md +2 -2
  2. package/dist/accessibility-snapshot-CQ4ZKWKC.js +39 -0
  3. package/dist/accessibility-snapshot-CQ4ZKWKC.js.map +1 -0
  4. package/dist/active-window-TD5HYJ72.js +37 -0
  5. package/dist/active-window-TD5HYJ72.js.map +1 -0
  6. package/dist/annotate-VTLFS2XV.js +205 -0
  7. package/dist/annotate-VTLFS2XV.js.map +1 -0
  8. package/dist/artifacts-KJ6RNDO2.js +120 -0
  9. package/dist/artifacts-KJ6RNDO2.js.map +1 -0
  10. package/dist/attr-BCI5KYCW.js +84 -0
  11. package/dist/attr-BCI5KYCW.js.map +1 -0
  12. package/dist/browser/ops-client.d.ts +1 -0
  13. package/dist/browser/ops-client.d.ts.map +1 -1
  14. package/dist/canvas-5DFEEOKM.js +309 -0
  15. package/dist/canvas-5DFEEOKM.js.map +1 -0
  16. package/dist/capture-desktop-HFTTWY4Z.js +38 -0
  17. package/dist/capture-desktop-HFTTWY4Z.js.map +1 -0
  18. package/dist/capture-window-X63XPIFF.js +40 -0
  19. package/dist/capture-window-X63XPIFF.js.map +1 -0
  20. package/dist/check-LWAUY7GC.js +71 -0
  21. package/dist/check-LWAUY7GC.js.map +1 -0
  22. package/dist/checked-ZSOUKVYT.js +71 -0
  23. package/dist/checked-ZSOUKVYT.js.map +1 -0
  24. package/dist/chunk-2SIMIPLY.js +67 -0
  25. package/dist/chunk-2SIMIPLY.js.map +1 -0
  26. package/dist/chunk-37VSRUW4.js +141 -0
  27. package/dist/chunk-37VSRUW4.js.map +1 -0
  28. package/dist/{chunk-T3VVHJTK.js → chunk-4BEJVZRK.js} +1078 -1458
  29. package/dist/chunk-4BEJVZRK.js.map +1 -0
  30. package/dist/chunk-5SWZDVOW.js +144 -0
  31. package/dist/chunk-5SWZDVOW.js.map +1 -0
  32. package/dist/chunk-6PVZ2ABC.js +429 -0
  33. package/dist/chunk-6PVZ2ABC.js.map +1 -0
  34. package/dist/chunk-7GVOUZMQ.js +64 -0
  35. package/dist/chunk-7GVOUZMQ.js.map +1 -0
  36. package/dist/chunk-7THCPS52.js +84 -0
  37. package/dist/chunk-7THCPS52.js.map +1 -0
  38. package/dist/chunk-AHEWXOKY.js +64 -0
  39. package/dist/chunk-AHEWXOKY.js.map +1 -0
  40. package/dist/chunk-ASMHEEKY.js +10 -0
  41. package/dist/chunk-ASMHEEKY.js.map +1 -0
  42. package/dist/chunk-COAOWH3G.js +3651 -0
  43. package/dist/chunk-COAOWH3G.js.map +1 -0
  44. package/dist/chunk-DBF5OKH3.js +111 -0
  45. package/dist/chunk-DBF5OKH3.js.map +1 -0
  46. package/dist/chunk-DW4TX7MU.js +54 -0
  47. package/dist/chunk-DW4TX7MU.js.map +1 -0
  48. package/dist/chunk-GQJ5S3BL.js +20 -0
  49. package/dist/chunk-GQJ5S3BL.js.map +1 -0
  50. package/dist/chunk-IPE7TF2P.js +54 -0
  51. package/dist/chunk-IPE7TF2P.js.map +1 -0
  52. package/dist/chunk-IQTJHXZJ.js +126 -0
  53. package/dist/chunk-IQTJHXZJ.js.map +1 -0
  54. package/dist/chunk-J47N77VG.js +2969 -0
  55. package/dist/chunk-J47N77VG.js.map +1 -0
  56. package/dist/chunk-JZXD6FWR.js +25 -0
  57. package/dist/chunk-JZXD6FWR.js.map +1 -0
  58. package/dist/{chunk-QVWOPIZJ.js → chunk-KDSNXS6N.js} +75 -149
  59. package/dist/chunk-KDSNXS6N.js.map +1 -0
  60. package/dist/chunk-KZ2IXVQT.js +219 -0
  61. package/dist/chunk-KZ2IXVQT.js.map +1 -0
  62. package/dist/chunk-MD655IPO.js +838 -0
  63. package/dist/chunk-MD655IPO.js.map +1 -0
  64. package/dist/chunk-MX3NFLCE.js +940 -0
  65. package/dist/chunk-MX3NFLCE.js.map +1 -0
  66. package/dist/chunk-OW5HMYMI.js +19 -0
  67. package/dist/chunk-OW5HMYMI.js.map +1 -0
  68. package/dist/chunk-PPUWQKIC.js +26 -0
  69. package/dist/chunk-PPUWQKIC.js.map +1 -0
  70. package/dist/{chunk-I5ZCOZZV.js → chunk-QOMWCRE3.js} +1202 -9561
  71. package/dist/chunk-QOMWCRE3.js.map +1 -0
  72. package/dist/chunk-RCZZGGJS.js +226 -0
  73. package/dist/chunk-RCZZGGJS.js.map +1 -0
  74. package/dist/chunk-RJNI3BHT.js +1 -0
  75. package/dist/chunk-RPXWUCQQ.js +112 -0
  76. package/dist/chunk-RPXWUCQQ.js.map +1 -0
  77. package/dist/chunk-S5KZQJJI.js +107 -0
  78. package/dist/chunk-S5KZQJJI.js.map +1 -0
  79. package/dist/chunk-T4GMCW6Z.js +46 -0
  80. package/dist/chunk-T4GMCW6Z.js.map +1 -0
  81. package/dist/chunk-WHQZBUNY.js +982 -0
  82. package/dist/chunk-WHQZBUNY.js.map +1 -0
  83. package/dist/chunk-WOXBLP7V.js +610 -0
  84. package/dist/chunk-WOXBLP7V.js.map +1 -0
  85. package/dist/cli/commands/inspiredesign.d.ts.map +1 -1
  86. package/dist/cli/commands/macro-resolve.d.ts +4 -1
  87. package/dist/cli/commands/macro-resolve.d.ts.map +1 -1
  88. package/dist/cli/commands/product-video.d.ts.map +1 -1
  89. package/dist/cli/commands/research.d.ts.map +1 -1
  90. package/dist/cli/commands/serve.d.ts.map +1 -1
  91. package/dist/cli/commands/shopping.d.ts.map +1 -1
  92. package/dist/cli/commands/workflow-output.d.ts +2 -0
  93. package/dist/cli/commands/workflow-output.d.ts.map +1 -0
  94. package/dist/cli/daemon-commands.d.ts.map +1 -1
  95. package/dist/cli/daemon.d.ts.map +1 -1
  96. package/dist/cli/index.js +204 -8123
  97. package/dist/cli/index.js.map +1 -1
  98. package/dist/cli/installers/postinstall-skill-sync.js +2 -1
  99. package/dist/cli/installers/postinstall-skill-sync.js.map +1 -1
  100. package/dist/cli/remote-relay.d.ts.map +1 -1
  101. package/dist/click-2AILSEIZ.js +81 -0
  102. package/dist/click-2AILSEIZ.js.map +1 -0
  103. package/dist/clone-component-TPJS3PEG.js +82 -0
  104. package/dist/clone-component-TPJS3PEG.js.map +1 -0
  105. package/dist/clone-page-LE74CIFC.js +69 -0
  106. package/dist/clone-page-LE74CIFC.js.map +1 -0
  107. package/dist/close-HN4YI47K.js +63 -0
  108. package/dist/close-HN4YI47K.js.map +1 -0
  109. package/dist/close-WFERRHX6.js +63 -0
  110. package/dist/close-WFERRHX6.js.map +1 -0
  111. package/dist/connect-RWBV2UCQ.js +107 -0
  112. package/dist/connect-RWBV2UCQ.js.map +1 -0
  113. package/dist/console-poll-PP4YYPDF.js +76 -0
  114. package/dist/console-poll-PP4YYPDF.js.map +1 -0
  115. package/dist/cookie-import-6IP776FC.js +177 -0
  116. package/dist/cookie-import-6IP776FC.js.map +1 -0
  117. package/dist/cookie-list-O2KG6DPU.js +117 -0
  118. package/dist/cookie-list-O2KG6DPU.js.map +1 -0
  119. package/dist/daemon-2BSAZXLT.js +194 -0
  120. package/dist/daemon-2BSAZXLT.js.map +1 -0
  121. package/dist/daemon-fingerprint.json +1 -1
  122. package/dist/debug-trace-snapshot-F3BDVZXS.js +136 -0
  123. package/dist/debug-trace-snapshot-F3BDVZXS.js.map +1 -0
  124. package/dist/dialog-6JQYUWMQ.js +75 -0
  125. package/dist/dialog-6JQYUWMQ.js.map +1 -0
  126. package/dist/disconnect-763TP7GH.js +58 -0
  127. package/dist/disconnect-763TP7GH.js.map +1 -0
  128. package/dist/enabled-DLYQFNIP.js +71 -0
  129. package/dist/enabled-DLYQFNIP.js.map +1 -0
  130. package/dist/extension-extractor-GKWSFHPN.js +11 -0
  131. package/dist/extension-extractor-GKWSFHPN.js.map +1 -0
  132. package/dist/global-D6WLWBXA.js +56 -0
  133. package/dist/global-D6WLWBXA.js.map +1 -0
  134. package/dist/goto-S346TJJH.js +98 -0
  135. package/dist/goto-S346TJJH.js.map +1 -0
  136. package/dist/help-EKKKEDL5.js +491 -0
  137. package/dist/help-EKKKEDL5.js.map +1 -0
  138. package/dist/hover-6JVJFGO7.js +71 -0
  139. package/dist/hover-6JVJFGO7.js.map +1 -0
  140. package/dist/html-EVOSPBIT.js +84 -0
  141. package/dist/html-EVOSPBIT.js.map +1 -0
  142. package/dist/index.d.ts.map +1 -1
  143. package/dist/index.js +87 -38
  144. package/dist/index.js.map +1 -1
  145. package/dist/inspector-H57BVUJP.js +62 -0
  146. package/dist/inspector-H57BVUJP.js.map +1 -0
  147. package/dist/inspector-audit-NQBAJWC7.js +84 -0
  148. package/dist/inspector-audit-NQBAJWC7.js.map +1 -0
  149. package/dist/inspector-plan-ZDIQVND3.js +69 -0
  150. package/dist/inspector-plan-ZDIQVND3.js.map +1 -0
  151. package/dist/inspiredesign-IEUL4PX3.js +234 -0
  152. package/dist/inspiredesign-IEUL4PX3.js.map +1 -0
  153. package/dist/install-autostart-output-5DOMKCQL.js +41 -0
  154. package/dist/install-autostart-output-5DOMKCQL.js.map +1 -0
  155. package/dist/install-autostart-reconciliation-NHKOFYTD.js +73 -0
  156. package/dist/install-autostart-reconciliation-NHKOFYTD.js.map +1 -0
  157. package/dist/launch-EK66VQPF.js +225 -0
  158. package/dist/launch-EK66VQPF.js.map +1 -0
  159. package/dist/list-ADZAQ2IU.js +51 -0
  160. package/dist/list-ADZAQ2IU.js.map +1 -0
  161. package/dist/list-KKUKN467.js +54 -0
  162. package/dist/list-KKUKN467.js.map +1 -0
  163. package/dist/local-HXJLUUNT.js +54 -0
  164. package/dist/local-HXJLUUNT.js.map +1 -0
  165. package/dist/macro-resolve-6DOQJ7CA.js +253 -0
  166. package/dist/macro-resolve-6DOQJ7CA.js.map +1 -0
  167. package/dist/macros/execute-runtime.d.ts +3 -1
  168. package/dist/macros/execute-runtime.d.ts.map +1 -1
  169. package/dist/macros/execute.d.ts +2 -0
  170. package/dist/macros/execute.d.ts.map +1 -1
  171. package/dist/native-UPLVQ2SG.js +22 -0
  172. package/dist/native-UPLVQ2SG.js.map +1 -0
  173. package/dist/network-poll-NUL4PDPY.js +76 -0
  174. package/dist/network-poll-NUL4PDPY.js.map +1 -0
  175. package/dist/new-5NKYPEFT.js +69 -0
  176. package/dist/new-5NKYPEFT.js.map +1 -0
  177. package/dist/onboarding-metadata-7E3KLYSZ.js +27 -0
  178. package/dist/onboarding-metadata-7E3KLYSZ.js.map +1 -0
  179. package/dist/open-NR3BPLXV.js +81 -0
  180. package/dist/open-NR3BPLXV.js.map +1 -0
  181. package/dist/opendevbrowser.d.ts.map +1 -1
  182. package/dist/opendevbrowser.js +87 -38
  183. package/dist/opendevbrowser.js.map +1 -1
  184. package/dist/perf-HJ36ZI6H.js +58 -0
  185. package/dist/perf-HJ36ZI6H.js.map +1 -0
  186. package/dist/pointer-down-IYTTQWXZ.js +55 -0
  187. package/dist/pointer-down-IYTTQWXZ.js.map +1 -0
  188. package/dist/pointer-drag-A2YC5PWI.js +54 -0
  189. package/dist/pointer-drag-A2YC5PWI.js.map +1 -0
  190. package/dist/pointer-move-W5K5FUI4.js +52 -0
  191. package/dist/pointer-move-W5K5FUI4.js.map +1 -0
  192. package/dist/pointer-up-6GWVO64Y.js +55 -0
  193. package/dist/pointer-up-6GWVO64Y.js.map +1 -0
  194. package/dist/press-A3V5WB3S.js +83 -0
  195. package/dist/press-A3V5WB3S.js.map +1 -0
  196. package/dist/product-video-52REKWF3.js +235 -0
  197. package/dist/product-video-52REKWF3.js.map +1 -0
  198. package/dist/providers/artifacts.d.ts +0 -2
  199. package/dist/providers/artifacts.d.ts.map +1 -1
  200. package/dist/providers/blocker.d.ts.map +1 -1
  201. package/dist/providers/bounded-map.d.ts +2 -0
  202. package/dist/providers/bounded-map.d.ts.map +1 -0
  203. package/dist/providers/community/index.d.ts.map +1 -1
  204. package/dist/providers/constraint.d.ts.map +1 -1
  205. package/dist/providers/index.d.ts +1 -0
  206. package/dist/providers/index.d.ts.map +1 -1
  207. package/dist/providers/renderer.d.ts.map +1 -1
  208. package/dist/providers/research-compiler.d.ts +1 -1
  209. package/dist/providers/research-compiler.d.ts.map +1 -1
  210. package/dist/providers/research-executor.d.ts.map +1 -1
  211. package/dist/providers/runtime-factory.d.ts.map +1 -1
  212. package/dist/providers/shared/traversal-url.d.ts +3 -0
  213. package/dist/providers/shared/traversal-url.d.ts.map +1 -1
  214. package/dist/providers/shopping/index.d.ts.map +1 -1
  215. package/dist/providers/social/search-quality.d.ts.map +1 -1
  216. package/dist/providers/workflow-handoff.d.ts +4 -0
  217. package/dist/providers/workflow-handoff.d.ts.map +1 -1
  218. package/dist/providers/workflow-output-root.d.ts +6 -0
  219. package/dist/providers/workflow-output-root.d.ts.map +1 -0
  220. package/dist/providers/workflows.d.ts.map +1 -1
  221. package/dist/{providers-QF2RFB4J.js → providers-IMFYMMHQ.js} +19 -14
  222. package/dist/providers-IMFYMMHQ.js.map +1 -0
  223. package/dist/public-surface/generated-manifest.d.ts +2 -2
  224. package/dist/public-surface/generated-manifest.d.ts.map +1 -1
  225. package/dist/public-surface/source.d.ts +2 -2
  226. package/dist/public-surface/source.d.ts.map +1 -1
  227. package/dist/relay/protocol.d.ts +3 -1
  228. package/dist/relay/protocol.d.ts.map +1 -1
  229. package/dist/relay/relay-server.d.ts +6 -0
  230. package/dist/relay/relay-server.d.ts.map +1 -1
  231. package/dist/research-WB6BBCDD.js +295 -0
  232. package/dist/research-WB6BBCDD.js.map +1 -0
  233. package/dist/review-BGWVY4RA.js +48 -0
  234. package/dist/review-BGWVY4RA.js.map +1 -0
  235. package/dist/review-desktop-LEORC5VS.js +54 -0
  236. package/dist/review-desktop-LEORC5VS.js.map +1 -0
  237. package/dist/rpc-4TSKSFGC.js +159 -0
  238. package/dist/rpc-4TSKSFGC.js.map +1 -0
  239. package/dist/run-3NBLVWXD.js +180 -0
  240. package/dist/run-3NBLVWXD.js.map +1 -0
  241. package/dist/screencast-start-UZVIT3IN.js +67 -0
  242. package/dist/screencast-start-UZVIT3IN.js.map +1 -0
  243. package/dist/screencast-stop-NOSJSIUO.js +59 -0
  244. package/dist/screencast-stop-NOSJSIUO.js.map +1 -0
  245. package/dist/screenshot-LARG4JQG.js +68 -0
  246. package/dist/screenshot-LARG4JQG.js.map +1 -0
  247. package/dist/scroll-VNFMV6TW.js +84 -0
  248. package/dist/scroll-VNFMV6TW.js.map +1 -0
  249. package/dist/scroll-into-view-VYRT3JPT.js +71 -0
  250. package/dist/scroll-into-view-VYRT3JPT.js.map +1 -0
  251. package/dist/select-KJTUZDVO.js +86 -0
  252. package/dist/select-KJTUZDVO.js.map +1 -0
  253. package/dist/serve-EV7K4HKR.js +498 -0
  254. package/dist/serve-EV7K4HKR.js.map +1 -0
  255. package/dist/shopping-DTXHVQ2X.js +273 -0
  256. package/dist/shopping-DTXHVQ2X.js.map +1 -0
  257. package/dist/skill-lifecycle-5UAZGKSN.js +89 -0
  258. package/dist/skill-lifecycle-5UAZGKSN.js.map +1 -0
  259. package/dist/skills-NSXDX6YM.js +26 -0
  260. package/dist/skills-NSXDX6YM.js.map +1 -0
  261. package/dist/snapshot-3XQMCMRJ.js +113 -0
  262. package/dist/snapshot-3XQMCMRJ.js.map +1 -0
  263. package/dist/status-OXSYA5XD.js +35 -0
  264. package/dist/status-OXSYA5XD.js.map +1 -0
  265. package/dist/status-YUMDP5KY.js +132 -0
  266. package/dist/status-YUMDP5KY.js.map +1 -0
  267. package/dist/status-capabilities-P4KDSE2Y.js +57 -0
  268. package/dist/status-capabilities-P4KDSE2Y.js.map +1 -0
  269. package/dist/text-V3B7UVIH.js +84 -0
  270. package/dist/text-V3B7UVIH.js.map +1 -0
  271. package/dist/tools/deps.d.ts +1 -0
  272. package/dist/tools/deps.d.ts.map +1 -1
  273. package/dist/tools/inspiredesign_run.d.ts.map +1 -1
  274. package/dist/tools/macro_resolve.d.ts.map +1 -1
  275. package/dist/tools/product_video_run.d.ts.map +1 -1
  276. package/dist/tools/research_run.d.ts.map +1 -1
  277. package/dist/tools/shopping_run.d.ts.map +1 -1
  278. package/dist/tools/workflow-output.d.ts +3 -0
  279. package/dist/tools/workflow-output.d.ts.map +1 -0
  280. package/dist/type-IYBN3ZLR.js +94 -0
  281. package/dist/type-IYBN3ZLR.js.map +1 -0
  282. package/dist/uncheck-SG737EGI.js +71 -0
  283. package/dist/uncheck-SG737EGI.js.map +1 -0
  284. package/dist/uninstall-KYKGJAX7.js +91 -0
  285. package/dist/uninstall-KYKGJAX7.js.map +1 -0
  286. package/dist/update-SMXPYGXS.js +305 -0
  287. package/dist/update-SMXPYGXS.js.map +1 -0
  288. package/dist/update-skill-modes-BVX7IVMW.js +38 -0
  289. package/dist/update-skill-modes-BVX7IVMW.js.map +1 -0
  290. package/dist/upload-KH6ZABJA.js +56 -0
  291. package/dist/upload-KH6ZABJA.js.map +1 -0
  292. package/dist/use-7YDKO3U4.js +63 -0
  293. package/dist/use-7YDKO3U4.js.map +1 -0
  294. package/dist/value-RZBWSKKM.js +71 -0
  295. package/dist/value-RZBWSKKM.js.map +1 -0
  296. package/dist/visible-BSFTAKXR.js +71 -0
  297. package/dist/visible-BSFTAKXR.js.map +1 -0
  298. package/dist/wait-TMTEAYOP.js +109 -0
  299. package/dist/wait-TMTEAYOP.js.map +1 -0
  300. package/dist/windows-HIZ23OHS.js +37 -0
  301. package/dist/windows-HIZ23OHS.js.map +1 -0
  302. package/extension/dist/background.js +99 -22
  303. package/extension/dist/ops/ops-runtime.js +85 -7
  304. package/extension/dist/ops/ops-session-store.js +3 -0
  305. package/extension/dist/ops/target-session-coordinator.js +3 -0
  306. package/extension/dist/services/CDPRouter.js +9 -0
  307. package/extension/manifest.json +1 -1
  308. package/package.json +1 -1
  309. package/skills/opendevbrowser-best-practices/SKILL.md +8 -6
  310. package/skills/opendevbrowser-best-practices/artifacts/skill-runtime-surface-matrix.md +1 -1
  311. package/skills/opendevbrowser-best-practices/scripts/odb-workflow.sh +3 -2
  312. package/skills/opendevbrowser-best-practices/scripts/validator-fixture-cli.sh +39 -2
  313. package/skills/opendevbrowser-research/SKILL.md +64 -12
  314. package/skills/opendevbrowser-research/artifacts/research-workflows.md +56 -19
  315. package/skills/opendevbrowser-research/assets/templates/compact.md +31 -5
  316. package/skills/opendevbrowser-research/assets/templates/context.json +52 -1
  317. package/skills/opendevbrowser-research/assets/templates/report.md +57 -4
  318. package/skills/opendevbrowser-research/examples/sample-input.json +1 -1
  319. package/skills/opendevbrowser-research/examples/sample-output.md +27 -2
  320. package/skills/opendevbrowser-research/scripts/run-research.sh +2 -6
  321. package/skills/opendevbrowser-research/scripts/validate-skill-assets.sh +115 -1
  322. package/dist/chunk-I5ZCOZZV.js.map +0 -1
  323. package/dist/chunk-QVWOPIZJ.js.map +0 -1
  324. package/dist/chunk-T3VVHJTK.js.map +0 -1
  325. /package/dist/{providers-QF2RFB4J.js.map → chunk-RJNI3BHT.js.map} +0 -0
@@ -1,3 +1,28 @@
1
+ import {
2
+ createArtifactBundle
3
+ } from "./chunk-S5KZQJJI.js";
4
+ import {
5
+ INSPIREDESIGN_ARTIFACT_GUIDE,
6
+ INSPIREDESIGN_CONTRACT_SECTION_GUIDE,
7
+ INSPIREDESIGN_HANDOFF_COMMANDS,
8
+ INSPIREDESIGN_HANDOFF_FILES,
9
+ INSPIREDESIGN_HANDOFF_GUIDANCE,
10
+ INSPIREDESIGN_HANDOFF_RECOMMENDED_SKILLS,
11
+ buildInspiredesignFollowthroughSummary,
12
+ buildInspiredesignNextStep
13
+ } from "./chunk-KZ2IXVQT.js";
14
+ import {
15
+ applyPromptGuard,
16
+ applyProviderIssueHint,
17
+ buildProviderIssueGuidance,
18
+ classifyBlockerSignal,
19
+ classifyProviderIssue,
20
+ createLogger,
21
+ readProviderIssueHint,
22
+ readProviderIssueHintFromRecord,
23
+ redactSensitive,
24
+ summarizePrimaryProviderIssue
25
+ } from "./chunk-WHQZBUNY.js";
1
26
  import {
2
27
  ProviderRuntimeError,
3
28
  createProviderError,
@@ -8,935 +33,11 @@ import {
8
33
  toProviderError
9
34
  } from "./chunk-FUSXMW3G.js";
10
35
 
11
- // src/core/logging.ts
12
- import { randomUUID } from "crypto";
13
- var SECRET_KEY_PATTERN = /(token|secret|password|authorization|cookie|api[-_]?key|session)/i;
14
- var SECRET_VALUE_PATTERN = /(bearer\s+[a-z0-9._-]+|sk_[a-z0-9_-]+|pk_[a-z0-9_-]+|eyJ[a-z0-9_-]+\.[a-z0-9_-]+\.[a-z0-9_-]+)/gi;
15
- function redactString(value) {
16
- return value.replace(SECRET_VALUE_PATTERN, "[REDACTED]");
17
- }
18
- function redactSensitive(value, seen = /* @__PURE__ */ new WeakSet()) {
19
- if (typeof value === "string") {
20
- return redactString(value);
21
- }
22
- if (typeof value !== "object" || value === null) {
23
- return value;
24
- }
25
- if (seen.has(value)) {
26
- return "[Circular]";
27
- }
28
- seen.add(value);
29
- if (Array.isArray(value)) {
30
- return value.map((item) => redactSensitive(item, seen));
31
- }
32
- const output = {};
33
- for (const [key, entry] of Object.entries(value)) {
34
- if (SECRET_KEY_PATTERN.test(key)) {
35
- output[key] = "[REDACTED]";
36
- continue;
37
- }
38
- output[key] = redactSensitive(entry, seen);
39
- }
40
- return output;
41
- }
42
- function createRequestId() {
43
- return randomUUID();
44
- }
45
- var defaultSink = (entry) => {
46
- const payload = JSON.stringify(entry);
47
- if (entry.level === "error") {
48
- console.error(payload);
49
- return;
50
- }
51
- if (entry.level === "warn") {
52
- console.warn(payload);
53
- return;
54
- }
55
- console.log(payload);
56
- };
57
- var stderrSink = (entry) => {
58
- process.stderr.write(`${JSON.stringify(entry)}
59
- `);
60
- };
61
- var configuredDefaultSink = defaultSink;
62
- function setDefaultLogSink(sink) {
63
- configuredDefaultSink = sink ?? defaultSink;
64
- }
65
- function createLogger(moduleName, sink) {
66
- const emit = (level, event, fields = {}) => {
67
- const entry = {
68
- ts: (/* @__PURE__ */ new Date()).toISOString(),
69
- level,
70
- module: moduleName,
71
- event,
72
- requestId: fields.requestId ?? createRequestId(),
73
- ...fields.sessionId ? { sessionId: fields.sessionId } : {},
74
- ...fields.traceId ? { traceId: fields.traceId } : {},
75
- ...typeof fields.data === "undefined" ? {} : { data: redactSensitive(fields.data) }
76
- };
77
- (sink ?? configuredDefaultSink)(entry);
78
- return entry;
79
- };
80
- return {
81
- debug: (event, fields) => emit("debug", event, fields),
82
- info: (event, fields) => emit("info", event, fields),
83
- warn: (event, fields) => emit("warn", event, fields),
84
- error: (event, fields) => emit("error", event, fields),
85
- audit: (event, fields) => emit("audit", event, fields)
86
- };
87
- }
88
-
89
- // src/providers/safety/prompt-guard.ts
90
- var MAX_EXCERPT_LENGTH = 120;
91
- var RULES = [
92
- {
93
- id: "reveal_system_prompt",
94
- regex: /\b(reveal|show|print|dump|expose|leak)\b[^.!?\n]{0,80}\b(system prompt|hidden prompt|internal prompt)\b/gi,
95
- severity: "high",
96
- action: "quarantine"
97
- },
98
- {
99
- id: "tool_abuse_directive",
100
- regex: /\buse (?:the )?tool(?:ing)?\b[^.!?\n]{0,120}\b(delete|remove|drop|wipe|exfiltrat|override|bypass)\w*/gi,
101
- severity: "high",
102
- action: "quarantine"
103
- },
104
- {
105
- id: "ignore_previous_instructions",
106
- regex: /\bignore (?:all )?previous instructions?\b/gi,
107
- severity: "medium",
108
- action: "strip"
109
- },
110
- {
111
- id: "reveal_hidden_data",
112
- regex: /\breveal (?:hidden|secret|confidential) (?:data|information)\b/gi,
113
- severity: "high",
114
- action: "quarantine"
115
- }
116
- ];
117
- var sanitizeExcerpt = (value) => {
118
- const compact = value.replace(/\s+/g, " ").trim();
119
- if (compact.length <= MAX_EXCERPT_LENGTH) return compact;
120
- return `${compact.slice(0, MAX_EXCERPT_LENGTH - 3)}...`;
121
- };
122
- var isJsonObject = (value) => {
123
- return typeof value === "object" && value !== null && !Array.isArray(value);
124
- };
125
- var withSecurityAttributes = (record, enabled, guardEntries, quarantinedSegments) => {
126
- const existingSecurity = isJsonObject(record.attributes.security) ? record.attributes.security : {};
127
- return {
128
- ...record.attributes,
129
- security: {
130
- ...existingSecurity,
131
- untrustedContent: true,
132
- dataOnlyContext: true,
133
- promptGuardEnabled: enabled,
134
- guardEntries,
135
- quarantinedSegments
136
- }
137
- };
138
- };
139
- function sanitizePromptGuardText(text, enabled) {
140
- if (!enabled || !text) {
141
- return {
142
- text,
143
- diagnostics: { entries: 0, quarantinedSegments: 0 },
144
- entries: []
145
- };
146
- }
147
- let output = text;
148
- const entries = [];
149
- for (const rule of RULES) {
150
- rule.regex.lastIndex = 0;
151
- output = output.replace(rule.regex, (match) => {
152
- entries.push({
153
- pattern: rule.id,
154
- action: rule.action,
155
- severity: rule.severity,
156
- excerpt: sanitizeExcerpt(match)
157
- });
158
- return rule.action === "quarantine" ? "[QUARANTINED]" : " ";
159
- });
160
- }
161
- const normalized = output.replace(/\s{2,}/g, " ").trim();
162
- const quarantinedSegments = entries.reduce((count, entry) => {
163
- return entry.action === "quarantine" ? count + 1 : count;
164
- }, 0);
165
- return {
166
- text: normalized,
167
- diagnostics: {
168
- entries: entries.length,
169
- quarantinedSegments
170
- },
171
- entries
172
- };
173
- }
174
- function applyPromptGuard(records, enabled) {
175
- const auditEntries = [];
176
- let totalQuarantinedSegments = 0;
177
- const guardedRecords = records.map((record) => {
178
- if (!enabled) {
179
- return {
180
- ...record,
181
- attributes: withSecurityAttributes(record, false, 0, 0)
182
- };
183
- }
184
- let title = record.title;
185
- let content = record.content;
186
- let recordEntries = 0;
187
- let recordQuarantinedSegments = 0;
188
- if (typeof record.title === "string") {
189
- const sanitizedTitle = sanitizePromptGuardText(record.title, true);
190
- title = sanitizedTitle.text;
191
- recordEntries += sanitizedTitle.diagnostics.entries;
192
- recordQuarantinedSegments += sanitizedTitle.diagnostics.quarantinedSegments;
193
- for (const entry of sanitizedTitle.entries) {
194
- auditEntries.push({
195
- ...entry,
196
- recordId: record.id,
197
- field: "title"
198
- });
199
- }
200
- }
201
- if (typeof record.content === "string") {
202
- const sanitizedContent = sanitizePromptGuardText(record.content, true);
203
- content = sanitizedContent.text;
204
- recordEntries += sanitizedContent.diagnostics.entries;
205
- recordQuarantinedSegments += sanitizedContent.diagnostics.quarantinedSegments;
206
- for (const entry of sanitizedContent.entries) {
207
- auditEntries.push({
208
- ...entry,
209
- recordId: record.id,
210
- field: "content"
211
- });
212
- }
213
- }
214
- totalQuarantinedSegments += recordQuarantinedSegments;
215
- return {
216
- ...record,
217
- ...typeof title === "string" ? { title } : {},
218
- ...typeof content === "string" ? { content } : {},
219
- attributes: withSecurityAttributes(record, true, recordEntries, recordQuarantinedSegments)
220
- };
221
- });
222
- return {
223
- records: guardedRecords,
224
- audit: {
225
- enabled,
226
- quarantinedSegments: enabled ? totalQuarantinedSegments : 0,
227
- entries: enabled ? auditEntries : []
228
- }
229
- };
230
- }
231
-
232
- // src/providers/blocker.ts
233
- var AUTH_URL_PATTERNS = [
234
- { id: "redirect_login_flow", regex: /\/i\/flow\/login/i, confidence: 0.97 },
235
- { id: "auth_login_path", regex: /\/(login|signin|sign-in|auth)(?:[./?]|$)/i, confidence: 0.9 }
236
- ];
237
- var AUTH_TITLE_PATTERNS = [
238
- { id: "title_login", regex: /\b(log ?in|sign ?in|login|signin)\b/i, confidence: 0.92 },
239
- { id: "title_auth_required", regex: /authentication required/i, confidence: 0.9 }
240
- ];
241
- var CHALLENGE_PATTERNS = [
242
- { id: "challenge_keyword", regex: /\b(challenge|captcha|verify|interstitial|cf_chl|bot)\b/i, confidence: 0.88 },
243
- { id: "prove_humanity", regex: /prove your humanity/i, confidence: 0.96 },
244
- { id: "robot_or_human", regex: /robot or human/i, confidence: 0.98 },
245
- { id: "confirm_human", regex: /confirm that you(?:'|’)re human/i, confidence: 0.96 },
246
- { id: "click_and_hold", regex: /\b(?:click|press|tap|activate)\s+(?:and\s+)?hold\b/i, confidence: 0.95 },
247
- { id: "press_and_hold", regex: /activate and hold the button/i, confidence: 0.95 },
248
- { id: "drag_slider", regex: /\b(?:drag|slide|move)(?:\s+the)?\s+(?:slider|puzzle(?:\s+piece)?|piece|button)\b/i, confidence: 0.95 },
249
- { id: "pardon_interruption", regex: /pardon our interruption/i, confidence: 0.97 },
250
- { id: "checking_browser_gate", regex: /checking your browser before you access/i, confidence: 0.97 }
251
- ];
252
- var RECAPTCHA_HOST_PATTERNS = [/recaptcha/i, /hcaptcha/i, /challenges\.cloudflare\.com/i];
253
- var STATIC_BLOCK_HOST_PATTERNS = [/redditstatic\.com$/i, /abs\.twimg\.com$/i, /twimg\.com$/i];
254
- var ENV_LIMITED_PATTERNS = [
255
- /extension not connected/i,
256
- /connect the extension/i,
257
- /manual interaction/i,
258
- /timed out/i,
259
- /not available in this environment/i
260
- ];
261
- var RESTRICTED_TARGET_PATTERNS = [/^chrome:\/\//i, /^chrome-extension:\/\//i, /^about:blank$/i, /^devtools:\/\//i];
262
- var DEFAULT_BLOCKER_ARTIFACT_CAPS = {
263
- maxNetworkEvents: 20,
264
- maxConsoleEvents: 20,
265
- maxExceptionEvents: 10,
266
- maxHosts: 10,
267
- maxTextLength: 512
268
- };
269
- var toLower = (value) => value.trim().toLowerCase();
270
- var clampNumber = (value, min, max) => {
271
- if (!Number.isFinite(value)) return min;
272
- if (value < min) return min;
273
- if (value > max) return max;
274
- return value;
275
- };
276
- var clampBlockerConfidence = (value) => {
277
- return clampNumber(value, 0, 1);
278
- };
279
- var clampThreshold = (value) => {
280
- if (typeof value !== "number") return 0.7;
281
- return clampNumber(value, 0, 1);
282
- };
283
- var clampText = (value, maxLength) => {
284
- if (typeof value !== "string") return void 0;
285
- if (maxLength <= 0) return "";
286
- return value.length <= maxLength ? value : `${value.slice(0, Math.max(0, maxLength - 3))}...`;
287
- };
288
- var boundedUniqueList = (values, maxLength) => {
289
- if (!values || values.length === 0 || maxLength <= 0) return [];
290
- const seen = /* @__PURE__ */ new Set();
291
- const list = [];
292
- for (const value of values) {
293
- if (typeof value !== "string") continue;
294
- const normalized = value.trim();
295
- if (!normalized) continue;
296
- const key = normalized.toLowerCase();
297
- if (seen.has(key)) continue;
298
- seen.add(key);
299
- list.push(normalized);
300
- if (list.length >= maxLength) break;
301
- }
302
- return list;
303
- };
304
- var extractHost = (value) => {
305
- if (!value) return null;
306
- try {
307
- return toLower(new URL(value).hostname);
308
- } catch {
309
- return null;
310
- }
311
- };
312
- var scorePatternMatches = (text, patterns) => {
313
- const matched = [];
314
- let confidence = 0;
315
- for (const pattern of patterns) {
316
- if (!pattern.regex.test(text)) continue;
317
- matched.push(pattern.id);
318
- confidence = Math.max(confidence, pattern.confidence);
319
- }
320
- return { matched, confidence };
321
- };
322
- var hasAnyPattern = (value, patterns) => {
323
- return patterns.some((pattern) => pattern.test(value));
324
- };
325
- var isLoopbackHost = (value) => {
326
- const normalized = toLower(value).replace(/^\[|\]$/g, "");
327
- if (!normalized) return false;
328
- if (normalized === "localhost" || normalized === "::1") return true;
329
- if (normalized === "127.0.0.1" || normalized.startsWith("127.")) return true;
330
- return /^::ffff:127\.\d+\.\d+\.\d+$/.test(normalized);
331
- };
332
- var buildHints = (type) => {
333
- switch (type) {
334
- case "auth_required":
335
- return [
336
- { id: "manual_login", reason: "Authentication flow requires interactive login.", priority: 1 },
337
- { id: "switch_managed_headed", reason: "Headed mode can complete login and persist session state.", priority: 2 },
338
- { id: "switch_extension_mode", reason: "Extension mode can reuse an already logged-in browser profile.", priority: 3 }
339
- ];
340
- case "anti_bot_challenge":
341
- return [
342
- { id: "manual_challenge", reason: "Challenge page requires manual completion.", priority: 1 },
343
- { id: "switch_managed_headed", reason: "Headed mode improves challenge completion reliability.", priority: 2 },
344
- { id: "collect_debug_trace", reason: "Collect trace artifacts to compare challenge indicators before and after manual action.", priority: 3 }
345
- ];
346
- case "rate_limited":
347
- return [
348
- { id: "retry_after_backoff", reason: "Rate-limited responses should be retried after a bounded delay.", priority: 1 },
349
- { id: "collect_debug_trace", reason: "Trace data can confirm cooldown and request pacing behavior.", priority: 2 }
350
- ];
351
- case "upstream_block":
352
- return [
353
- { id: "retry_after_backoff", reason: "Upstream blocks may clear after network or host recovery.", priority: 1 },
354
- { id: "switch_managed_headed", reason: "Browser-assisted retrieval may bypass runtime fetch limitations.", priority: 2 },
355
- { id: "collect_debug_trace", reason: "Trace host evidence helps confirm blocked upstream dependencies.", priority: 3 }
356
- ];
357
- case "restricted_target":
358
- return [
359
- { id: "switch_managed_headed", reason: "Restricted internal targets require navigation to a normal http(s) tab.", priority: 1 },
360
- { id: "collect_debug_trace", reason: "Trace confirms blocked scheme or tab restriction source.", priority: 2 }
361
- ];
362
- case "env_limited":
363
- return [
364
- { id: "switch_extension_mode", reason: "Extension relay availability is required for this operation.", priority: 1 },
365
- { id: "switch_managed_headed", reason: "Managed headed mode is a deterministic fallback when extension is unavailable.", priority: 2 },
366
- { id: "collect_debug_trace", reason: "Diagnostics can confirm environment capability gaps.", priority: 3 }
367
- ];
368
- case "unknown":
369
- return [{ id: "collect_debug_trace", reason: "Additional trace evidence is required for reliable classification.", priority: 1 }];
370
- }
371
- };
372
- var classifyFromInputs = (input, normalizedHosts, matchedPatterns) => {
373
- const status = input.status;
374
- const code = toLower(input.providerErrorCode ?? "");
375
- const url = input.url ?? "";
376
- const finalUrl = input.finalUrl ?? "";
377
- const title = input.title ?? "";
378
- const message = input.message ?? "";
379
- const challengeText = `${title} ${message}`;
380
- const urlSignals = `${url} ${finalUrl}`;
381
- const isUpstreamCode = code === "upstream" || code === "network" || code === "unavailable";
382
- const hasStaticBlockHost = normalizedHosts.some((host) => hasAnyPattern(host, STATIC_BLOCK_HOST_PATTERNS));
383
- const isLoopbackContext = [
384
- extractHost(url),
385
- extractHost(finalUrl),
386
- ...normalizedHosts
387
- ].some((host) => typeof host === "string" && isLoopbackHost(host));
388
- const authMatches = [];
389
- let authConfidence = 0;
390
- if (status === 401 || status === 403) {
391
- authMatches.push(`status:${status}`);
392
- authConfidence = Math.max(authConfidence, status === 401 ? 0.94 : 0.9);
393
- }
394
- if (code === "auth") {
395
- authMatches.push("provider_code:auth");
396
- authConfidence = Math.max(authConfidence, 0.9);
397
- }
398
- const authPathMatches = scorePatternMatches(`${url} ${finalUrl}`, AUTH_URL_PATTERNS);
399
- authMatches.push(...authPathMatches.matched);
400
- authConfidence = Math.max(authConfidence, authPathMatches.confidence);
401
- const authTitleMatches = scorePatternMatches(title, AUTH_TITLE_PATTERNS);
402
- authMatches.push(...authTitleMatches.matched);
403
- authConfidence = Math.max(authConfidence, authTitleMatches.confidence);
404
- if (authConfidence > 0) {
405
- return {
406
- type: "auth_required",
407
- reasonCode: "token_required",
408
- confidence: authConfidence,
409
- retryable: false,
410
- matches: boundedUniqueList([...matchedPatterns, ...authMatches], 16)
411
- };
412
- }
413
- if (!isLoopbackContext) {
414
- const challengeMatches = [];
415
- let challengeConfidence = 0;
416
- const challengePatternMatches = scorePatternMatches(challengeText, CHALLENGE_PATTERNS);
417
- challengeMatches.push(...challengePatternMatches.matched);
418
- challengeConfidence = Math.max(challengeConfidence, challengePatternMatches.confidence);
419
- if (/(captcha|cf_chl|hcaptcha|recaptcha|interstitial)/i.test(urlSignals)) {
420
- challengeMatches.push("url:challenge_token");
421
- challengeConfidence = Math.max(challengeConfidence, 0.9);
422
- }
423
- if (hasAnyPattern(title, CHALLENGE_PATTERNS.map((entry) => entry.regex)) && status === 200) {
424
- challengeMatches.push("status:200_challenge_title");
425
- challengeConfidence = Math.max(challengeConfidence, 0.92);
426
- }
427
- if (normalizedHosts.some((host) => hasAnyPattern(host, RECAPTCHA_HOST_PATTERNS))) {
428
- challengeMatches.push("network:challenge_host");
429
- challengeConfidence = Math.max(challengeConfidence, 0.96);
430
- }
431
- if (challengeConfidence > 0) {
432
- return {
433
- type: "anti_bot_challenge",
434
- reasonCode: "challenge_detected",
435
- confidence: challengeConfidence,
436
- retryable: false,
437
- matches: boundedUniqueList([...matchedPatterns, ...challengeMatches], 16)
438
- };
439
- }
440
- }
441
- if (status === 429 || code === "rate_limited") {
442
- return {
443
- type: "rate_limited",
444
- reasonCode: "rate_limited",
445
- confidence: 0.95,
446
- retryable: true,
447
- matches: boundedUniqueList([...matchedPatterns, status === 429 ? "status:429" : "provider_code:rate_limited"], 16)
448
- };
449
- }
450
- if (isUpstreamCode && (hasStaticBlockHost || /retrieval failed/i.test(message) || typeof status === "number" && status >= 500)) {
451
- return {
452
- type: "upstream_block",
453
- reasonCode: "ip_blocked",
454
- confidence: hasStaticBlockHost ? 0.9 : 0.8,
455
- retryable: input.retryable ?? true,
456
- matches: boundedUniqueList(
457
- [
458
- ...matchedPatterns,
459
- `provider_code:${code}`,
460
- ...hasStaticBlockHost ? ["network:blocked_static_host"] : []
461
- ],
462
- 16
463
- )
464
- };
465
- }
466
- if (input.restrictedTarget || RESTRICTED_TARGET_PATTERNS.some((pattern) => pattern.test(url)) || RESTRICTED_TARGET_PATTERNS.some((pattern) => pattern.test(finalUrl))) {
467
- return {
468
- type: "restricted_target",
469
- confidence: 0.92,
470
- retryable: false,
471
- matches: boundedUniqueList([...matchedPatterns, "restricted_target"], 16)
472
- };
473
- }
474
- if (input.envLimited || code === "unavailable" && ENV_LIMITED_PATTERNS.some((pattern) => pattern.test(message))) {
475
- return {
476
- type: "env_limited",
477
- reasonCode: "env_limited",
478
- confidence: input.envLimited ? 0.9 : 0.78,
479
- retryable: true,
480
- matches: boundedUniqueList([...matchedPatterns, "env_limited"], 16)
481
- };
482
- }
483
- if (status || code || title || message || normalizedHosts.length > 0) {
484
- return {
485
- type: "unknown",
486
- confidence: 0.5,
487
- retryable: input.retryable ?? false,
488
- matches: boundedUniqueList(matchedPatterns, 16)
489
- };
490
- }
491
- return null;
492
- };
493
- var coerceJsonValue = (value, maxTextLength, promptGuardEnabled, diagnostics, seen) => {
494
- if (value === null || typeof value === "number" || typeof value === "boolean") {
495
- return value;
496
- }
497
- if (typeof value === "string") {
498
- const sanitized = sanitizePromptGuardText(value, promptGuardEnabled);
499
- diagnostics.entries += sanitized.diagnostics.entries;
500
- diagnostics.quarantinedSegments += sanitized.diagnostics.quarantinedSegments;
501
- const redacted = redactSensitive(sanitized.text);
502
- const asString = typeof redacted === "string" ? redacted : String(redacted);
503
- return clampText(asString, maxTextLength) ?? "";
504
- }
505
- if (Array.isArray(value)) {
506
- return value.slice(0, 20).map((entry) => coerceJsonValue(entry, maxTextLength, promptGuardEnabled, diagnostics, seen));
507
- }
508
- if (typeof value !== "object") {
509
- return String(value);
510
- }
511
- if (seen.has(value)) {
512
- return "[Circular]";
513
- }
514
- seen.add(value);
515
- const objectValue = value;
516
- const entries = Object.entries(objectValue).slice(0, 30);
517
- const output = {};
518
- for (const [key, entryValue] of entries) {
519
- output[key] = coerceJsonValue(entryValue, maxTextLength, promptGuardEnabled, diagnostics, seen);
520
- }
521
- return output;
522
- };
523
- var coerceEventList = (values, maxItems, maxTextLength, promptGuardEnabled, diagnostics) => {
524
- if (!Array.isArray(values) || maxItems <= 0) return [];
525
- return values.slice(-maxItems).map((entry) => {
526
- const sanitized = coerceJsonValue(
527
- redactSensitive(entry),
528
- maxTextLength,
529
- promptGuardEnabled,
530
- diagnostics,
531
- /* @__PURE__ */ new WeakSet()
532
- );
533
- if (!sanitized || typeof sanitized !== "object" || Array.isArray(sanitized)) {
534
- return { value: sanitized };
535
- }
536
- return sanitized;
537
- });
538
- };
539
- var resolveBlockerArtifactCaps = (partial) => {
540
- return {
541
- maxNetworkEvents: clampNumber(partial?.maxNetworkEvents ?? DEFAULT_BLOCKER_ARTIFACT_CAPS.maxNetworkEvents, 1, 500),
542
- maxConsoleEvents: clampNumber(partial?.maxConsoleEvents ?? DEFAULT_BLOCKER_ARTIFACT_CAPS.maxConsoleEvents, 1, 500),
543
- maxExceptionEvents: clampNumber(partial?.maxExceptionEvents ?? DEFAULT_BLOCKER_ARTIFACT_CAPS.maxExceptionEvents, 1, 200),
544
- maxHosts: clampNumber(partial?.maxHosts ?? DEFAULT_BLOCKER_ARTIFACT_CAPS.maxHosts, 1, 200),
545
- maxTextLength: clampNumber(partial?.maxTextLength ?? DEFAULT_BLOCKER_ARTIFACT_CAPS.maxTextLength, 32, 4096)
546
- };
547
- };
548
- var classifyBlockerSignal = (input) => {
549
- const promptGuardEnabled = input.promptGuardEnabled ?? true;
550
- const titleSanitized = sanitizePromptGuardText(input.title ?? "", promptGuardEnabled);
551
- const messageSanitized = sanitizePromptGuardText(input.message ?? "", promptGuardEnabled);
552
- const matchedPatterns = boundedUniqueList(input.matchedPatterns, 16);
553
- const normalizedHosts = boundedUniqueList(input.networkHosts?.map(toLower), 20);
554
- const classification = classifyFromInputs({
555
- ...input,
556
- title: titleSanitized.text,
557
- message: messageSanitized.text
558
- }, normalizedHosts, matchedPatterns);
559
- if (!classification) {
560
- return null;
561
- }
562
- const threshold = clampThreshold(input.threshold);
563
- const confidence = clampBlockerConfidence(classification.confidence);
564
- if (confidence < threshold) {
565
- return null;
566
- }
567
- const evidence = {
568
- ...input.url ? { url: clampText(input.url, 512) } : {},
569
- ...input.finalUrl ? { finalUrl: clampText(input.finalUrl, 512) } : {},
570
- ...titleSanitized.text ? { title: clampText(titleSanitized.text, 512) } : {},
571
- ...typeof input.status === "number" ? { status: input.status } : {},
572
- ...input.providerErrorCode ? { providerErrorCode: input.providerErrorCode } : {},
573
- matchedPatterns: boundedUniqueList(classification.matches, 16),
574
- networkHosts: boundedUniqueList(normalizedHosts, 10),
575
- ...input.traceRequestId ? { traceRequestId: input.traceRequestId } : {}
576
- };
577
- const sanitationEntries = titleSanitized.diagnostics.entries + messageSanitized.diagnostics.entries;
578
- const sanitationQuarantined = titleSanitized.diagnostics.quarantinedSegments + messageSanitized.diagnostics.quarantinedSegments;
579
- return {
580
- schemaVersion: "1.0",
581
- type: classification.type,
582
- source: input.source,
583
- ...classification.reasonCode ? { reasonCode: classification.reasonCode } : {},
584
- confidence,
585
- retryable: classification.retryable,
586
- detectedAt: input.detectedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
587
- evidence,
588
- actionHints: buildHints(classification.type),
589
- ...sanitationEntries > 0 || sanitationQuarantined > 0 ? {
590
- sanitation: {
591
- entries: sanitationEntries,
592
- quarantinedSegments: sanitationQuarantined
593
- }
594
- } : {}
595
- };
596
- };
597
- var buildBlockerArtifacts = (input) => {
598
- const caps = resolveBlockerArtifactCaps(input.caps);
599
- const promptGuardEnabled = input.promptGuardEnabled ?? true;
600
- const diagnostics = {
601
- entries: 0,
602
- quarantinedSegments: 0
603
- };
604
- const network = coerceEventList(
605
- input.networkEvents,
606
- caps.maxNetworkEvents,
607
- caps.maxTextLength,
608
- promptGuardEnabled,
609
- diagnostics
610
- );
611
- const console2 = coerceEventList(
612
- input.consoleEvents,
613
- caps.maxConsoleEvents,
614
- caps.maxTextLength,
615
- promptGuardEnabled,
616
- diagnostics
617
- );
618
- const exception = coerceEventList(
619
- input.exceptionEvents,
620
- caps.maxExceptionEvents,
621
- caps.maxTextLength,
622
- promptGuardEnabled,
623
- diagnostics
624
- );
625
- const hosts = boundedUniqueList(
626
- network.map((event) => typeof event.url === "string" ? extractHost(event.url) : null).filter((host) => typeof host === "string"),
627
- caps.maxHosts
628
- );
629
- return {
630
- schemaVersion: "1.0",
631
- network,
632
- console: console2,
633
- exception,
634
- hosts,
635
- sanitation: diagnostics
636
- };
637
- };
638
- var __test__ = {
639
- classifyFromInputs,
640
- extractHost,
641
- hasAnyPattern,
642
- clampThreshold,
643
- isLoopbackHost
644
- };
645
-
646
- // src/providers/constraint.ts
647
- var BLOCKER_TYPES = /* @__PURE__ */ new Set([
648
- "auth_required",
649
- "anti_bot_challenge",
650
- "rate_limited",
651
- "upstream_block",
652
- "restricted_target",
653
- "env_limited",
654
- "unknown"
655
- ]);
656
- var CONSTRAINT_KINDS = /* @__PURE__ */ new Set([
657
- "session_required",
658
- "render_required"
659
- ]);
660
- var RENDER_REQUIRED_SHELLS = /* @__PURE__ */ new Set([
661
- "bestbuy_international_gate",
662
- "duckduckgo_non_js_redirect",
663
- "macys_access_denied_shell",
664
- "social_first_party_help_shell",
665
- "social_js_required_shell",
666
- "social_render_shell",
667
- "target_shell_page",
668
- "temu_empty_shell"
669
- ]);
670
- var CHALLENGE_SHELLS = /* @__PURE__ */ new Set(["social_verification_wall", "temu_challenge_shell"]);
671
- var toNonEmptyString = (value) => {
672
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
673
- };
674
- var normalizeBlockerType = (value) => {
675
- return typeof value === "string" && BLOCKER_TYPES.has(value) ? value : void 0;
676
- };
677
- var buildConstraint = (kind, evidenceCode, providerShell, message) => ({
678
- kind,
679
- evidenceCode,
680
- ...providerShell ? { providerShell } : {},
681
- ...message ? { message } : {}
682
- });
683
- var isProviderConstraint = (value) => {
684
- if (!value || typeof value !== "object" || Array.isArray(value)) {
685
- return false;
686
- }
687
- const candidate = value;
688
- return CONSTRAINT_KINDS.has(candidate.kind) && typeof candidate.evidenceCode === "string" && candidate.evidenceCode.trim().length > 0 && (candidate.providerShell === void 0 || typeof candidate.providerShell === "string") && (candidate.message === void 0 || typeof candidate.message === "string");
689
- };
690
- var classifyProviderIssue = (input) => {
691
- const providerShell = toNonEmptyString(input.providerShell);
692
- const message = toNonEmptyString(input.message);
693
- if (providerShell && CHALLENGE_SHELLS.has(providerShell)) {
694
- return {
695
- reasonCode: "challenge_detected",
696
- blockerType: "anti_bot_challenge"
697
- };
698
- }
699
- if (providerShell && RENDER_REQUIRED_SHELLS.has(providerShell)) {
700
- return {
701
- reasonCode: "env_limited",
702
- blockerType: "env_limited",
703
- constraint: buildConstraint("render_required", providerShell, providerShell, message)
704
- };
705
- }
706
- const blocker = classifyBlockerSignal({
707
- source: input.source ?? "runtime_fetch",
708
- ...input.url ? { url: input.url, finalUrl: input.url } : {},
709
- ...input.title ? { title: input.title } : {},
710
- ...message ? { message } : {},
711
- ...typeof input.status === "number" ? { status: input.status } : {},
712
- ...input.providerErrorCode ? { providerErrorCode: input.providerErrorCode } : {},
713
- ...typeof input.retryable === "boolean" ? { retryable: input.retryable } : {},
714
- envLimited: input.browserRequired === true
715
- });
716
- if (blocker?.type === "auth_required") {
717
- return {
718
- /* c8 ignore next -- classifyBlockerSignal always supplies token_required for auth_required blockers */
719
- reasonCode: blocker.reasonCode ?? "token_required",
720
- blockerType: blocker.type,
721
- constraint: buildConstraint("session_required", providerShell ?? "auth_required", providerShell, message)
722
- };
723
- }
724
- if (blocker?.type === "anti_bot_challenge") {
725
- return {
726
- /* c8 ignore next -- classifyBlockerSignal always supplies challenge_detected for anti-bot blockers */
727
- reasonCode: blocker.reasonCode ?? "challenge_detected",
728
- blockerType: blocker.type
729
- };
730
- }
731
- if (input.browserRequired === true) {
732
- return {
733
- /* c8 ignore next -- browserRequired inputs always classify to a blocker payload in the shared classifier */
734
- reasonCode: blocker?.reasonCode ?? "env_limited",
735
- /* c8 ignore next -- browserRequired fallbacks only normalize unknown blocker types */
736
- blockerType: blocker?.type === "unknown" ? "env_limited" : blocker?.type,
737
- /* c8 ignore next -- providerShell or blocker type always survives before the defensive browser_required fallback */
738
- constraint: buildConstraint("render_required", providerShell ?? blocker?.type ?? "browser_required", providerShell, message)
739
- };
740
- }
741
- if (blocker?.type === "env_limited") {
742
- return {
743
- /* c8 ignore next -- classifyBlockerSignal always supplies env_limited for env_limited blockers */
744
- reasonCode: blocker.reasonCode ?? "env_limited",
745
- blockerType: blocker.type
746
- };
747
- }
748
- return null;
749
- };
750
- var readProviderIssueHint = (args) => {
751
- const details = args.details;
752
- const reasonCode = isProviderReasonCode(args.reasonCode) ? args.reasonCode : isProviderReasonCode(details?.reasonCode) ? details.reasonCode : void 0;
753
- const blockerType = normalizeBlockerType(args.blockerType ?? details?.blockerType);
754
- const constraint = isProviderConstraint(details?.constraint) ? details.constraint : void 0;
755
- if (reasonCode || blockerType || constraint) {
756
- return {
757
- reasonCode: reasonCode ?? (constraint?.kind === "session_required" ? "token_required" : "env_limited"),
758
- ...blockerType ? { blockerType } : {},
759
- ...constraint ? { constraint } : {}
760
- };
761
- }
762
- return classifyProviderIssue({
763
- url: toNonEmptyString(details?.url),
764
- title: toNonEmptyString(details?.title),
765
- message: toNonEmptyString(details?.message ?? args.message),
766
- providerShell: toNonEmptyString(details?.providerShell),
767
- browserRequired: details?.browserRequired === true,
768
- providerErrorCode: toNonEmptyString(args.code),
769
- source: "runtime_fetch"
770
- });
771
- };
772
- var readProviderIssueHintFromRecord = (record) => {
773
- const details = record.attributes;
774
- return readProviderIssueHint({
775
- reasonCode: details.reasonCode,
776
- blockerType: details.blockerType,
777
- message: record.content,
778
- details: {
779
- url: record.url,
780
- title: record.title,
781
- message: record.content,
782
- providerShell: details.providerShell,
783
- browserRequired: details.browserRequired,
784
- constraint: details.constraint
785
- }
786
- });
787
- };
788
- var applyProviderIssueHint = (details, hint) => {
789
- if (!hint) return details;
790
- const next = {
791
- ...details ?? {},
792
- reasonCode: hint.reasonCode
793
- };
794
- if (hint.blockerType && typeof next.blockerType !== "string") {
795
- next.blockerType = hint.blockerType;
796
- }
797
- if (hint.constraint && !isProviderConstraint(next.constraint)) {
798
- next.constraint = hint.constraint;
799
- }
800
- if (typeof next.guidance === "undefined") {
801
- const guidance = buildProviderIssueGuidance({ hint, details: next });
802
- if (guidance) {
803
- next.guidance = guidance;
804
- }
805
- }
806
- return next;
807
- };
808
- var providerLabel = (provider) => {
809
- const normalized = toNonEmptyString(provider);
810
- if (!normalized) return "Provider";
811
- const separator = normalized.lastIndexOf("/");
812
- const tail = separator >= 0 ? normalized.slice(separator + 1) : normalized;
813
- return tail.charAt(0).toUpperCase() + tail.slice(1);
814
- };
815
- var hasPreservedBrowserState = (details) => {
816
- return typeof details?.preservedSessionId === "string" || typeof details?.preservedTargetId === "string";
817
- };
818
- var buildGuidance = (reason, recommendedNextCommands) => ({
819
- reason,
820
- recommendedNextCommands
821
- });
822
- var buildAuthGuidance = (subject, preservedBrowserState) => {
823
- return preservedBrowserState ? buildGuidance(
824
- `${subject} preserved browser state that can finish authentication.`,
825
- [
826
- "Complete the login or account checkpoint in the preserved browser session.",
827
- "Rerun the same provider or workflow after the session is fully authenticated."
828
- ]
829
- ) : buildGuidance(
830
- `${subject} needs an authenticated session before retrying.`,
831
- [
832
- "Reuse an authenticated browser session, import logged-in cookies, or use the provider sign-in flow.",
833
- "Rerun the same provider or workflow once the session is active."
834
- ]
835
- );
836
- };
837
- var buildChallengeGuidance = (subject, preservedBrowserState) => {
838
- return preservedBrowserState ? buildGuidance(
839
- `${subject} preserved browser state that can complete the current challenge.`,
840
- [
841
- "Finish the login or anti-bot challenge in the preserved browser session.",
842
- "Rerun the same provider or workflow after the page unlocks."
843
- ]
844
- ) : buildGuidance(
845
- `${subject} hit a challenge that still needs browser-assisted follow-up.`,
846
- [
847
- "Retry with browser assistance so the challenge can be completed interactively.",
848
- "Only ask for manual credentials if browser-assisted recovery still cannot unlock the page."
849
- ]
850
- );
851
- };
852
- var buildRenderGuidance = (subject, preservedBrowserState) => {
853
- return preservedBrowserState ? buildGuidance(
854
- `${subject} still needs a live browser-rendered page, but browser state is already preserved.`,
855
- [
856
- "Inspect the preserved browser session until usable content is visible.",
857
- "Rerun the same provider or workflow after the rendered page is ready."
858
- ]
859
- ) : buildGuidance(
860
- `${subject} needs a live browser-rendered page before retrying.`,
861
- [
862
- "Retry with browser assistance or a headed browser session.",
863
- "Rerun the same provider or workflow after the rendered page is ready."
864
- ]
865
- );
866
- };
867
- var buildProviderIssueGuidance = (args) => {
868
- const subject = providerLabel(args.provider);
869
- const preservedBrowserState = hasPreservedBrowserState(args.details);
870
- const disposition = toNonEmptyString(args.details?.disposition);
871
- if (disposition === "completed") return void 0;
872
- if (disposition === "challenge_preserved") {
873
- return buildChallengeGuidance(subject, true);
874
- }
875
- if (args.hint.reasonCode === "token_required" || args.hint.reasonCode === "auth_required") {
876
- return buildAuthGuidance(subject, preservedBrowserState);
877
- }
878
- if (args.hint.reasonCode === "challenge_detected") {
879
- return buildChallengeGuidance(subject, preservedBrowserState);
880
- }
881
- if (args.hint.constraint?.kind === "render_required" || args.hint.reasonCode === "env_limited") {
882
- return buildRenderGuidance(subject, preservedBrowserState);
883
- }
884
- return void 0;
885
- };
886
- var summaryPriority = (hint) => {
887
- if (hint.reasonCode === "token_required" || hint.reasonCode === "auth_required") return 3;
888
- if (hint.reasonCode === "challenge_detected") return 2;
889
- if (hint.constraint?.kind === "render_required") return 1;
890
- return 0;
891
- };
892
- var summarizeProviderIssue = (args) => {
893
- const subject = providerLabel(args.provider);
894
- if (args.hint.reasonCode === "token_required" || args.hint.reasonCode === "auth_required") {
895
- return `${subject} requires login or an existing session.`;
896
- }
897
- if (args.hint.reasonCode === "challenge_detected") {
898
- return `${subject} hit an anti-bot challenge that requires manual completion.`;
899
- }
900
- if (args.hint.constraint?.kind === "render_required") {
901
- return `${subject} requires a live browser-rendered page.`;
902
- }
903
- return `${subject} requires manual browser follow-up; this run did not determine whether login or page rendering is required.`;
904
- };
905
- var summarizePrimaryProviderIssue = (failures) => {
906
- if (!Array.isArray(failures) || failures.length === 0) return null;
907
- let best = null;
908
- for (const failure of failures) {
909
- const hint = readProviderIssueHint({
910
- reasonCode: failure.error?.reasonCode,
911
- code: failure.error?.code,
912
- message: failure.error?.message,
913
- details: failure.error?.details
914
- });
915
- if (!hint) continue;
916
- const summary = summarizeProviderIssue({ provider: failure.provider, hint });
917
- const guidance = buildProviderIssueGuidance({
918
- provider: failure.provider,
919
- hint,
920
- details: failure.error?.details
921
- });
922
- const candidate = {
923
- provider: failure.provider,
924
- summary,
925
- ...hint,
926
- ...guidance ? { guidance } : {}
927
- };
928
- if (!best || summaryPriority(candidate) > summaryPriority(best)) {
929
- best = candidate;
930
- }
931
- }
932
- return best;
933
- };
934
-
935
36
  // src/providers/normalize.ts
936
- import { createHash, randomUUID as randomUUID2 } from "crypto";
37
+ import { createHash, randomUUID } from "crypto";
937
38
  var createTraceContext = (seed = {}, provider) => {
938
39
  return {
939
- requestId: seed.requestId ?? randomUUID2(),
40
+ requestId: seed.requestId ?? randomUUID(),
940
41
  ...seed.sessionId ? { sessionId: seed.sessionId } : {},
941
42
  ...seed.targetId ? { targetId: seed.targetId } : {},
942
43
  ...provider ?? seed.provider ? { provider: provider ?? seed.provider } : {},
@@ -1648,9 +749,9 @@ var HUMAN_VERIFICATION_RE = /\b(captcha|verify (?:that )?you(?:'re| are) human|s
1648
749
  var NON_SECRET_FIELD_RE = /\b(email|e-mail|username|first name|last name|full name|company|phone|city|state|country|linkedin|portfolio|resume|cv)\b/i;
1649
750
  var CHECKPOINT_RE = /\b(next|continue|resume|verify|submit|approve|allow)\b/i;
1650
751
  var LOGIN_PAGE_RE = /\/(login|signin|sign-in|auth|session)(?:[/?#]|$)/i;
1651
- var GOOGLE_AUTH_RE = /\b(?:continue with google|sign in with google|log in with google|google sign(?: |-)?in|google)\b/i;
1652
- var GITHUB_AUTH_RE = /\b(?:continue with github|sign in with github|log in with github|github)\b/i;
1653
- var APPLE_AUTH_RE = /\b(?:continue with apple|sign in with apple|log in with apple|apple)\b/i;
752
+ var GOOGLE_AUTH_RE = /\b(?:continue with google|sign in with google|log in with google|google sign(?: |-)?in)\b/i;
753
+ var GITHUB_AUTH_RE = /\b(?:continue with github|sign in with github|log in with github|github sign(?: |-)?in)\b/i;
754
+ var APPLE_AUTH_RE = /\b(?:continue with apple|sign in with apple|log in with apple|apple sign(?: |-)?in)\b/i;
1654
755
  var ACCOUNT_CHOOSER_NOISE_RE = /\b(?:help|privacy|terms|learn more|english|afrikaans|espa[ñn]ol)\b/i;
1655
756
  var CLICK_ACTION_RE = /\b(click|tap|select|choose|continue|allow|dismiss|close|not now|got it|delivery|pickup|ship(?:ping)? here)\b/i;
1656
757
  var HOLD_ACTION_RE = /\b(?:click|press|tap|activate)\s+(?:and\s+)?hold\b|\bhold (?:the )?(?:button|slider)\b/i;
@@ -2867,7 +1968,7 @@ var executeStep = async (args) => {
2867
1968
  });
2868
1969
  await args.handle.pointerMove(args.sessionId, point.x, point.y, args.targetId, 12);
2869
1970
  await args.handle.pointerDown(args.sessionId, point.x, point.y, args.targetId, "left", 1);
2870
- await new Promise((resolve2) => setTimeout(resolve2, Math.max(250, args.step.holdMs ?? DEFAULT_HOLD_MS2)));
1971
+ await new Promise((resolve) => setTimeout(resolve, Math.max(250, args.step.holdMs ?? DEFAULT_HOLD_MS2)));
2871
1972
  await args.handle.pointerUp(args.sessionId, point.x, point.y, args.targetId, "left", 1);
2872
1973
  return;
2873
1974
  }
@@ -3948,6 +3049,14 @@ var assertPostPolicy = async (context, hooks = []) => {
3948
3049
 
3949
3050
  // src/providers/shared/traversal-url.ts
3950
3051
  var STATIC_HOST_PATTERNS = [
3052
+ /(^|\.)analytics\.google\.com$/i,
3053
+ /(^|\.)fonts\.googleapis\.com$/i,
3054
+ /(^|\.)google-analytics\.com$/i,
3055
+ /(^|\.)googleadservices\.com$/i,
3056
+ /(^|\.)googletagmanager\.com$/i,
3057
+ /(^|\.)googlesyndication\.com$/i,
3058
+ /(^|\.)gstatic\.com$/i,
3059
+ /(^|\.)doubleclick\.net$/i,
3951
3060
  /(^|\.)redditstatic\.com$/i,
3952
3061
  /(^|\.)twimg\.com$/i,
3953
3062
  /(^|\.)static\.licdn\.com$/i,
@@ -3956,6 +3065,49 @@ var STATIC_HOST_PATTERNS = [
3956
3065
  /(^|\.)cdninstagram\.com$/i
3957
3066
  ];
3958
3067
  var STATIC_PATH_EXT_RE = /\.(?:avif|bmp|css|csv|gif|ico|jpe?g|js|json|map|mjs|mp3|mp4|ogg|pdf|png|svg|txt|wav|webm|webp|woff2?|xml|zip)$/i;
3068
+ var RESEARCH_DEAD_END_LOGIN_SEGMENTS = /* @__PURE__ */ new Set([
3069
+ "account",
3070
+ "accounts",
3071
+ "login",
3072
+ "sign-in",
3073
+ "signin",
3074
+ "submit"
3075
+ ]);
3076
+ var RESEARCH_DEAD_END_PRIVACY_ROOT_SEGMENTS = /* @__PURE__ */ new Set([
3077
+ "choice",
3078
+ "choices",
3079
+ "consent",
3080
+ "cookie",
3081
+ "cookie-policy",
3082
+ "cookie-preferences",
3083
+ "cookies",
3084
+ "legal",
3085
+ "policies",
3086
+ "policy",
3087
+ "privacy",
3088
+ "privacy-policy",
3089
+ "prefs",
3090
+ "preferences",
3091
+ "terms",
3092
+ "terms-of-service"
3093
+ ]);
3094
+ var RESEARCH_DEAD_END_SEARCH_ROOT_SEGMENTS = /* @__PURE__ */ new Set(["find", "results", "search"]);
3095
+ var RESEARCH_DEAD_END_OTHER_ROOT_SEGMENTS = /* @__PURE__ */ new Set(["settings", "verification"]);
3096
+ var RESEARCH_DEAD_END_PATHS = /* @__PURE__ */ new Set([
3097
+ "/legal/privacy",
3098
+ "/policies",
3099
+ "/privacy",
3100
+ "/privacy-policy",
3101
+ "/privacy/choices",
3102
+ "/privacychoices",
3103
+ "/privacychoices/"
3104
+ ]);
3105
+ var isPrivacyDeadEndPath = (segments) => {
3106
+ const [first, second] = segments;
3107
+ if (!first || !RESEARCH_DEAD_END_PRIVACY_ROOT_SEGMENTS.has(first)) return false;
3108
+ if (segments.length === 1) return true;
3109
+ return first === "consent" && second === "manage" || first === "privacy" && second === "choices" || first === "prefs" && second === "privacy" || first === "preferences" && second === "privacy" || first === "legal" && (second === "privacy" || second === "terms") || first === "policies" && second === "privacy-policy";
3110
+ };
3959
3111
  var isLikelyDocumentUrl = (value) => {
3960
3112
  try {
3961
3113
  const parsed = new URL(value);
@@ -3969,6 +3121,22 @@ var isLikelyDocumentUrl = (value) => {
3969
3121
  return false;
3970
3122
  }
3971
3123
  };
3124
+ var isLikelyResearchDestinationUrl = (value) => {
3125
+ return classifyResearchDestinationRejection(value) === null;
3126
+ };
3127
+ var classifyResearchDestinationRejection = (value) => {
3128
+ if (!isLikelyDocumentUrl(value)) return "research_dead_end_shell";
3129
+ const parsed = new URL(value);
3130
+ const pathname = parsed.pathname.toLowerCase().replace(/\/+$/, "") || "/";
3131
+ if (RESEARCH_DEAD_END_PATHS.has(pathname)) return "privacy_preference_shell";
3132
+ const segments = pathname.split("/").filter(Boolean);
3133
+ const firstSegment = segments[0] ?? "";
3134
+ if (segments.some((segment) => RESEARCH_DEAD_END_LOGIN_SEGMENTS.has(segment))) return "login_shell";
3135
+ if (RESEARCH_DEAD_END_SEARCH_ROOT_SEGMENTS.has(firstSegment)) return "search_results_shell";
3136
+ if (RESEARCH_DEAD_END_OTHER_ROOT_SEGMENTS.has(firstSegment)) return "research_dead_end_shell";
3137
+ if (isPrivacyDeadEndPath(segments)) return "privacy_preference_shell";
3138
+ return null;
3139
+ };
3972
3140
 
3973
3141
  // src/providers/web/policy.ts
3974
3142
  var normalizeDomain = (value) => value.trim().toLowerCase();
@@ -4517,8 +3685,8 @@ var CrawlWorkerPool = class {
4517
3685
  }
4518
3686
  const taskId = this.nextTaskId;
4519
3687
  this.nextTaskId += 1;
4520
- return new Promise((resolve2, reject) => {
4521
- this.queue.push({ id: taskId, input, resolve: resolve2, reject });
3688
+ return new Promise((resolve, reject) => {
3689
+ this.queue.push({ id: taskId, input, resolve, reject });
4522
3690
  this.dispatch();
4523
3691
  });
4524
3692
  }
@@ -5122,7 +4290,7 @@ var coerceStringArray = (value) => {
5122
4290
  return value.filter((entry) => typeof entry === "string");
5123
4291
  };
5124
4292
  var extractLinks2 = (row, fallbackUrl) => {
5125
- const attributeLinks = [
4293
+ const attributeLinks2 = [
5126
4294
  ...coerceStringArray(row.attributes?.links),
5127
4295
  ...coerceStringArray(row.attributes?.threadLinks),
5128
4296
  ...coerceStringArray(row.attributes?.replyLinks),
@@ -5130,7 +4298,7 @@ var extractLinks2 = (row, fallbackUrl) => {
5130
4298
  ];
5131
4299
  const contentLinks = [...row.content?.match(LINK_RE) ?? []];
5132
4300
  const deduped = /* @__PURE__ */ new Set();
5133
- for (const candidate of [...attributeLinks, ...contentLinks]) {
4301
+ for (const candidate of [...attributeLinks2, ...contentLinks]) {
5134
4302
  const canonical = canonicalizeUrl(candidate);
5135
4303
  if (!isHttpUrl2(canonical) || canonical === fallbackUrl || !isLikelyDocumentUrl(canonical)) continue;
5136
4304
  deduped.add(canonical);
@@ -5206,13 +4374,21 @@ var createCommunityProvider = (options = {}) => {
5206
4374
  const pending = [];
5207
4375
  const rows = [];
5208
4376
  for (let page = 1; page <= traversal.pageLimit && rows.length < traversal.maxRecords; page += 1) {
5209
- const pageRows = await options.search({
5210
- ...input,
5211
- filters: {
5212
- ...input.filters ?? {},
5213
- page
4377
+ let pageRows;
4378
+ try {
4379
+ pageRows = await options.search({
4380
+ ...input,
4381
+ filters: {
4382
+ ...input.filters ?? {},
4383
+ page
4384
+ }
4385
+ }, context);
4386
+ } catch (error) {
4387
+ if (rows.length > 0 && shouldSkipExpansionError(error)) {
4388
+ break;
5214
4389
  }
5215
- }, context);
4390
+ throw error;
4391
+ }
5216
4392
  for (const row of sortRows(pageRows)) {
5217
4393
  const canonical = canonicalizeUrl(row.url);
5218
4394
  if (!isHttpUrl2(canonical) || seen.has(canonical)) continue;
@@ -5469,6 +4645,7 @@ var createCommunityProvider = (options = {}) => {
5469
4645
  // src/providers/social/search-quality.ts
5470
4646
  var TARGETED_PLATFORMS = /* @__PURE__ */ new Set(["x", "bluesky", "reddit", "facebook", "threads"]);
5471
4647
  var SOCIAL_JS_REQUIRED_RE = /\b(?:javascript (?:is not available|required|is disabled(?: in this browser)?)|you need to enable javascript|please enable javascript)\b/i;
4648
+ var SOCIAL_JS_REQUIRED_SHELL_CONTEXT_RE = /\b(?:supported browsers?|help center|something went wrong|try again|privacy policy|cookie policy|ads info)\b/i;
5472
4649
  var BLUESKY_LOGGED_OUT_SEARCH_RE = /\bsearch is currently unavailable when logged out\b/i;
5473
4650
  var BLUESKY_EMPTY_SEARCH_SHELL_RE = /\b(?:follow 10 people to get started|find people to follow)\b/i;
5474
4651
  var REDDIT_VERIFICATION_WALL_RE = /\b(?:please wait for verification|verify (?:you(?:'re| are) human|that you(?:'re| are) human)|security check)\b/i;
@@ -5585,8 +4762,6 @@ var isRootShellUrl = (platform, parsed) => {
5585
4762
  return isBlockedFacebookNonContentUrl(parsed, { includeSearchRoute: false });
5586
4763
  case "threads":
5587
4764
  return isPrimaryThreadsHost(host) && (pathname === "/" || pathname === "/login" || pathname === "/login/");
5588
- default:
5589
- return false;
5590
4765
  }
5591
4766
  };
5592
4767
  var isBlockedExpansionPath = (platform, parsed) => {
@@ -5603,8 +4778,6 @@ var isBlockedExpansionPath = (platform, parsed) => {
5603
4778
  return isBlockedFacebookNonContentUrl(parsed, { includeSearchRoute: true });
5604
4779
  case "threads":
5605
4780
  return isPrimaryThreadsHost(host) && (pathname === "/" || pathname === "/login" || pathname === "/search" || pathname === "/search/" || isStaticMetadataPath(pathname));
5606
- default:
5607
- return false;
5608
4781
  }
5609
4782
  };
5610
4783
  var isFirstPartySearchRoute = (platform, parsed) => {
@@ -5621,8 +4794,6 @@ var isFirstPartySearchRoute = (platform, parsed) => {
5621
4794
  return isPrimaryFacebookHost(host) && isFacebookSearchLikePath(pathname);
5622
4795
  case "threads":
5623
4796
  return isPrimaryThreadsHost(host) && (pathname === "/search" || pathname === "/search/");
5624
- default:
5625
- return false;
5626
4797
  }
5627
4798
  };
5628
4799
  var collectSocialSearchLinkEvidence = (platform, baseUrl, links) => {
@@ -5662,16 +4833,13 @@ var collectSocialSearchLinkEvidence = (platform, baseUrl, links) => {
5662
4833
  };
5663
4834
  var isUsableFirstPartySearchResultUrl = (platform, url) => {
5664
4835
  const parsed = parseUrl(url);
5665
- if (!parsed) {
4836
+ if (parsed === null) {
5666
4837
  return false;
5667
4838
  }
5668
4839
  const host = parsed.hostname.toLowerCase();
5669
4840
  if (platform === "x" && host !== "x.com" || platform === "bluesky" && host !== "bsky.app") {
5670
4841
  return false;
5671
4842
  }
5672
- if (isFirstPartyHelpHost(platform, host)) {
5673
- return false;
5674
- }
5675
4843
  return !isBlockedExpansionPath(platform, parsed);
5676
4844
  };
5677
4845
  var isUsableBlueskySearchEvidenceUrl = (url) => {
@@ -5706,19 +4874,9 @@ var isRetainableFacebookSearchSupportUrl = (url) => {
5706
4874
  if (parsed === null || !isPrimaryFacebookHost(parsed.hostname)) {
5707
4875
  return false;
5708
4876
  }
5709
- if (isBlockedFacebookNonContentUrl(parsed, { includeSearchRoute: true })) {
5710
- return false;
5711
- }
5712
- if (isFirstPartySearchRoute("facebook", parsed)) {
5713
- return false;
5714
- }
5715
4877
  return !isUsableFacebookSearchEvidenceUrl(url);
5716
4878
  };
5717
- var hasFacebookSearchResultSignals = (input) => {
5718
- const parsed = parseUrl(input.url);
5719
- if (parsed === null || !isFirstPartySearchRoute("facebook", parsed)) {
5720
- return false;
5721
- }
4879
+ var hasFacebookSearchResultSignals = (parsed, input) => {
5722
4880
  const combined = `${normalizeText2(input.title)} ${normalizeText2(input.content)}`.trim();
5723
4881
  const hasSearchHeading = FACEBOOK_SEARCH_RESULTS_HEADING_RE.test(combined);
5724
4882
  const markerCount = FACEBOOK_SEARCH_RESULT_MARKERS.filter((pattern) => pattern.test(combined)).length;
@@ -5748,14 +4906,12 @@ var isUsableSocialSearchContentUrl = (platform, url) => {
5748
4906
  return isUsableFacebookSearchEvidenceUrl(url);
5749
4907
  case "threads":
5750
4908
  return isUsableThreadsSearchEvidenceUrl(url);
5751
- default:
5752
- return false;
5753
4909
  }
5754
4910
  };
5755
4911
  var hasUsableFirstPartySearchEvidence = (platform, parsed, links) => parsed !== null && isFirstPartySearchRoute(platform, parsed) && collectSocialSearchLinkEvidence(platform, parsed.toString(), links).usableContentLinks.length > 0;
5756
4912
  var isFirstPartySocialSearchRoute = (platform, url) => {
5757
4913
  const parsed = parseUrl(url);
5758
- return parsed !== null && isFirstPartySearchRoute(platform, parsed);
4914
+ return parsed !== null && isTargetedPlatform(platform) && isFirstPartySearchRoute(platform, parsed);
5759
4915
  };
5760
4916
  var detectSocialSearchShell = (platform, input) => {
5761
4917
  if (!isTargetedPlatform(platform)) {
@@ -5770,13 +4926,19 @@ var detectSocialSearchShell = (platform, input) => {
5770
4926
  browserRequired: true
5771
4927
  };
5772
4928
  }
4929
+ if ((platform === "x" || platform === "bluesky") && parsed && SOCIAL_JS_REQUIRED_RE.test(combined) && SOCIAL_JS_REQUIRED_SHELL_CONTEXT_RE.test(combined) && !hasUsableFirstPartySearchEvidence(platform, parsed, links)) {
4930
+ return {
4931
+ providerShell: "social_js_required_shell",
4932
+ browserRequired: true
4933
+ };
4934
+ }
5773
4935
  if (platform === "reddit" && REDDIT_VERIFICATION_WALL_RE.test(combined)) {
5774
4936
  return {
5775
4937
  providerShell: "social_verification_wall",
5776
4938
  browserRequired: true
5777
4939
  };
5778
4940
  }
5779
- if (platform === "bluesky" && parsed && isFirstPartySearchRoute(platform, parsed) && BLUESKY_LOGGED_OUT_SEARCH_RE.test(combined)) {
4941
+ if (platform === "bluesky" && parsed && isFirstPartySearchRoute(platform, parsed) && BLUESKY_LOGGED_OUT_SEARCH_RE.test(combined) && !hasUsableFirstPartySearchEvidence(platform, parsed, links)) {
5780
4942
  return {
5781
4943
  providerShell: "social_js_required_shell",
5782
4944
  browserRequired: true
@@ -5800,7 +4962,7 @@ var detectSocialSearchShell = (platform, input) => {
5800
4962
  browserRequired: true
5801
4963
  };
5802
4964
  }
5803
- if (parsed && isFirstPartySearchRoute(platform, parsed) && platform === "facebook" && hasFacebookSearchResultSignals(input)) {
4965
+ if (parsed && isFirstPartySearchRoute(platform, parsed) && platform === "facebook" && hasFacebookSearchResultSignals(parsed, input)) {
5804
4966
  return null;
5805
4967
  }
5806
4968
  if (parsed && isFirstPartySearchRoute(platform, parsed) && !hasUsableFirstPartySearchEvidence(platform, parsed, links)) {
@@ -5958,7 +5120,7 @@ var hasBrowserFallbackMode = (attributes) => typeof attributes?.browser_fallback
5958
5120
  var hasVisibleSearchContent = (row) => typeof row.title === "string" && row.title.trim().length > 0 || typeof row.content === "string" && row.content.trim().length > 0;
5959
5121
  var shouldKeepRecoveredFacebookSearchRow = (platform, row) => platform === "facebook" && hasBrowserFallbackMode(row.attributes) && hasVisibleSearchContent(row);
5960
5122
  var extractLinks3 = (platform, row, fallbackUrl) => {
5961
- const attributeLinks = [
5123
+ const attributeLinks2 = [
5962
5124
  ...coerceStringArray2(row.attributes?.links),
5963
5125
  ...coerceStringArray2(row.attributes?.threadLinks),
5964
5126
  ...coerceStringArray2(row.attributes?.replyLinks),
@@ -5966,7 +5128,7 @@ var extractLinks3 = (platform, row, fallbackUrl) => {
5966
5128
  ];
5967
5129
  const contentLinks = [...row.content?.match(LINK_RE2) ?? []];
5968
5130
  const deduped = /* @__PURE__ */ new Set();
5969
- for (const candidate of [...attributeLinks, ...contentLinks]) {
5131
+ for (const candidate of [...attributeLinks2, ...contentLinks]) {
5970
5132
  const canonical = canonicalizeUrl(candidate);
5971
5133
  if (!isHttpUrl3(canonical) || canonical === fallbackUrl || !isLikelyDocumentUrl(canonical) || !isAllowedSocialSearchExpansionUrl(platform, canonical)) {
5972
5134
  continue;
@@ -8678,7 +7840,7 @@ var PROVIDER_PRODUCT_URL_HINT_RE = {
8678
7840
  bestbuy: /\/site\/[^?#]*\/\d+\.p(?:[?#]|$)/i,
8679
7841
  ebay: /\/itm(?:\/|$)/i,
8680
7842
  costco: /(?:\/|\.)(?:product|warehouse)\.[^?#]+\.html(?:[?#]|$)|[?&](?:prodid|itemnumber)=/i,
8681
- macys: /\/shop\/product\/|[?&]id=\d+/i,
7843
+ macys: /\/shop\/product\/[^?#]+(?:[?#]|$)/i,
8682
7844
  aliexpress: /\/(?:item|i)\/[^?#]+/i,
8683
7845
  temu: /\/(?:goods\.html|g-[^/?#]+\.html)(?:[?#]|$)/i,
8684
7846
  others: /(?:\/ip(?:\/|$)|\/itm(?:\/|$)|\/product(?:s)?\/|\/sku\/|\/site\/[^?#]*\/\d+\.p(?:[?#]|$)|(?:\/|\.)(?:product|warehouse)\.[^?#]+\.html(?:[?#]|$))/i
@@ -8902,6 +8064,14 @@ var requiresBrowserAssistance = (profile, responseUrl, html) => {
8902
8064
  ...text ? { message: toSnippet(text, 400) } : {}
8903
8065
  };
8904
8066
  }
8067
+ const hasPdpErrorShell = textLower.includes("something went wrong") && (textLower.includes("use our search bar") || textLower.includes("pick a category below") || textLower.includes("typed in a url"));
8068
+ if (hasPdpErrorShell) {
8069
+ return {
8070
+ reason: "bestbuy_pdp_error_shell",
8071
+ ...title ? { title } : {},
8072
+ ...text ? { message: toSnippet(text, 400) } : {}
8073
+ };
8074
+ }
8905
8075
  }
8906
8076
  if (profile.name === "target") {
8907
8077
  const isShellPage = /:\s*target$/i.test(title) && textLower.includes("skip to main content") && textLower.includes("skip to footer");
@@ -9496,6 +8666,19 @@ var createDefaultFetch = (profile, providerId, fetcher) => async (input, context
9496
8666
  context
9497
8667
  });
9498
8668
  const extracted = extractStructuredContent(fetched.html, fetched.url);
8669
+ const content = toSnippet(extracted.text, 2e3);
8670
+ const providerShell = requiresBrowserAssistance(profile, fetched.url, fetched.html);
8671
+ if (providerShell) {
8672
+ const pageIssue = classifySearchPageIssue(profile, fetched, extracted, content, providerShell);
8673
+ throwShoppingPageIssue({
8674
+ providerId,
8675
+ fetched,
8676
+ extracted,
8677
+ content,
8678
+ pageIssue: ensureProviderShellIssue(pageIssue, providerShell, content),
8679
+ providerShell
8680
+ });
8681
+ }
9499
8682
  const title = toSnippet(extracted.text, 120) || fetched.url;
9500
8683
  const extractedPrice = extracted.metadata.price ? {
9501
8684
  amount: extracted.metadata.price.amount,
@@ -9856,6 +9039,37 @@ var createWebProvider = (options = {}) => {
9856
9039
  };
9857
9040
  };
9858
9041
 
9042
+ // src/providers/bounded-map.ts
9043
+ var resolveWorkerCount = (itemCount, limit) => {
9044
+ if (itemCount <= 0) return 0;
9045
+ const finiteLimit = Number.isFinite(limit) ? Math.floor(limit) : 1;
9046
+ return Math.min(Math.max(1, finiteLimit), itemCount);
9047
+ };
9048
+ var mapBounded = async (items, limit, task) => {
9049
+ const results = new Array(items.length);
9050
+ let cursor = 0;
9051
+ let firstError = null;
9052
+ const workers = Array.from({ length: resolveWorkerCount(items.length, limit) }, async () => {
9053
+ for (; ; ) {
9054
+ if (firstError) return;
9055
+ const index = cursor;
9056
+ cursor += 1;
9057
+ if (index >= items.length) return;
9058
+ try {
9059
+ results[index] = await task(items[index], index);
9060
+ } catch (error) {
9061
+ if (!firstError) {
9062
+ firstError = error instanceof Error ? error : new Error(String(error));
9063
+ }
9064
+ return;
9065
+ }
9066
+ }
9067
+ });
9068
+ await Promise.all(workers);
9069
+ if (firstError) throw firstError;
9070
+ return results;
9071
+ };
9072
+
9859
9073
  // src/providers/workflow-contracts.ts
9860
9074
  var isJsonRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
9861
9075
  var isWorkflowStage = (value) => value === "compile" || value === "execute" || value === "postprocess" || value === "resume";
@@ -9874,121 +9088,17 @@ var isWorkflowResumePayload = (value) => isJsonRecord(value) && isWorkflowResume
9874
9088
  // src/providers/workflows.ts
9875
9089
  import { createHash as createHash7 } from "crypto";
9876
9090
 
9877
- // src/providers/artifacts.ts
9878
- import { mkdir, readdir as readdir2, readFile, rm as rm2, stat, writeFile } from "fs/promises";
9879
- import { dirname, join as join2, resolve } from "path";
9880
- import { randomUUID as randomUUID3 } from "crypto";
9881
- import { tmpdir as tmpdir2 } from "os";
9882
- var DEFAULT_ARTIFACT_TTL_HOURS = 72;
9883
- var MAX_ARTIFACT_TTL_HOURS = 168;
9884
- var clampTtlHours = (value) => {
9885
- if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
9886
- return DEFAULT_ARTIFACT_TTL_HOURS;
9887
- }
9888
- return Math.min(MAX_ARTIFACT_TTL_HOURS, Math.floor(value));
9889
- };
9890
- var serializeContent = (content) => {
9891
- if (typeof content === "string" || Buffer.isBuffer(content)) {
9892
- return content;
9893
- }
9894
- return `${JSON.stringify(content, null, 2)}
9895
- `;
9896
- };
9897
- var createArtifactBundle = async (args) => {
9898
- const runId = randomUUID3();
9899
- const now = args.now ?? /* @__PURE__ */ new Date();
9900
- const ttlHours = clampTtlHours(args.ttlHours);
9901
- const manifestFileName = args.manifestFileName ?? "bundle-manifest.json";
9902
- const expiresAt = new Date(now.getTime() + ttlHours * 60 * 60 * 1e3);
9903
- const root = args.outputDir ? resolve(args.outputDir) : join2(tmpdir2(), "opendevbrowser");
9904
- const basePath = join2(root, args.namespace, runId);
9905
- await mkdir(basePath, { recursive: true, mode: 448 });
9906
- const writtenFiles = [];
9907
- for (const file of args.files) {
9908
- const filePath = join2(basePath, file.path);
9909
- const directory = dirname(filePath);
9910
- if (directory && directory !== basePath) {
9911
- await mkdir(directory, { recursive: true, mode: 448 });
9912
- }
9913
- await writeFile(filePath, serializeContent(file.content), { mode: 384 });
9914
- writtenFiles.push(file.path);
9915
- }
9916
- const manifest = {
9917
- run_id: runId,
9918
- created_at: now.toISOString(),
9919
- ttl_hours: ttlHours,
9920
- expires_at: expiresAt.toISOString(),
9921
- files: [...writtenFiles, manifestFileName]
9922
- };
9923
- await writeFile(join2(basePath, manifestFileName), `${JSON.stringify(manifest, null, 2)}
9924
- `, { mode: 384 });
9925
- return {
9926
- runId,
9927
- basePath,
9928
- manifest,
9929
- manifestFileName
9930
- };
9931
- };
9932
- var isExpired = (manifest, now) => {
9933
- const expiry = new Date(manifest.expires_at);
9934
- if (Number.isNaN(expiry.getTime())) return false;
9935
- return expiry.getTime() <= now.getTime();
9936
- };
9937
- var cleanupExpiredArtifacts = async (rootDir, now = /* @__PURE__ */ new Date()) => {
9938
- const removed = [];
9939
- const skipped = [];
9940
- let namespaces = [];
9941
- try {
9942
- namespaces = await readdir2(rootDir);
9943
- } catch {
9944
- return { removed, skipped };
9091
+ // src/providers/workflow-output-root.ts
9092
+ import { join as join2 } from "path";
9093
+ var WORKFLOW_ARTIFACT_DIRECTORY = ".opendevbrowser";
9094
+ var resolveWorkflowArtifactRoot = (outputDir, options = {}) => {
9095
+ if (outputDir === void 0) {
9096
+ return join2(options.workspaceRoot ?? process.cwd(), WORKFLOW_ARTIFACT_DIRECTORY);
9945
9097
  }
9946
- for (const namespace of namespaces) {
9947
- const namespacePath = join2(rootDir, namespace);
9948
- let runs = [];
9949
- try {
9950
- runs = await readdir2(namespacePath);
9951
- } catch {
9952
- continue;
9953
- }
9954
- for (const run of runs) {
9955
- const runPath = join2(namespacePath, run);
9956
- const manifestCandidates = [join2(runPath, "bundle-manifest.json"), join2(runPath, "manifest.json")];
9957
- try {
9958
- let manifestPath = null;
9959
- for (const candidate of manifestCandidates) {
9960
- try {
9961
- const candidateMetadata = await stat(candidate);
9962
- if (candidateMetadata.isFile()) {
9963
- manifestPath = candidate;
9964
- break;
9965
- }
9966
- } catch {
9967
- }
9968
- }
9969
- if (!manifestPath) {
9970
- skipped.push(runPath);
9971
- continue;
9972
- }
9973
- const metadata = await stat(manifestPath);
9974
- if (!metadata.isFile()) {
9975
- skipped.push(runPath);
9976
- continue;
9977
- }
9978
- const manifestRaw = await readFile(manifestPath, "utf8");
9979
- const manifest = JSON.parse(manifestRaw);
9980
- if (isExpired(manifest, now)) {
9981
- await rm2(runPath, { recursive: true, force: true });
9982
- removed.push(runPath);
9983
- } else {
9984
- skipped.push(runPath);
9985
- }
9986
- } catch {
9987
- skipped.push(runPath);
9988
- }
9989
- }
9098
+ if (outputDir.trim() === "") {
9099
+ throw new Error("outputDir cannot be empty");
9990
9100
  }
9991
- return { removed, skipped };
9101
+ return outputDir;
9992
9102
  };
9993
9103
 
9994
9104
  // src/providers/timebox.ts
@@ -10455,235 +9565,27 @@ var design_contract_v1_default = {
10455
9565
  validationTargets: {
10456
9566
  blockOn: [
10457
9567
  "contrast-failure",
10458
- "keyboard-regression",
10459
- "stale-search-results",
10460
- "invalid-route-recovery-failure"
10461
- ],
10462
- warnOn: [
10463
- "responsive-mismatch",
10464
- "layout-shift",
10465
- "spinner-stacking",
10466
- "scan-surface-jank"
10467
- ]
10468
- },
10469
- designVectors: {
10470
- advancedMotionAdvisory: [
10471
- "Advisory shader-like gradient depth",
10472
- "Advisory WebGL-style spatial reveal",
10473
- "Advisory Spline-style product orbit",
10474
- "Runtime support: none. Library policy authorization: none."
10475
- ]
10476
- }
10477
- }
10478
- };
10479
-
10480
- // src/inspiredesign/handoff.ts
10481
- var INSPIREDESIGN_HANDOFF_FILES = {
10482
- designMarkdown: "design.md",
10483
- advancedBrief: "advanced-brief.md",
10484
- designContract: "design-contract.json",
10485
- canvasPlanRequest: "canvas-plan.request.json",
10486
- designAgentHandoff: "design-agent-handoff.json",
10487
- generationPlan: "generation-plan.json",
10488
- implementationPlanMarkdown: "implementation-plan.md",
10489
- implementationPlan: "implementation-plan.json",
10490
- evidence: "evidence.json",
10491
- prototypeGuidance: "prototype-guidance.md"
10492
- };
10493
- var INSPIREDESIGN_HANDOFF_SKILLS = {
10494
- bestPractices: {
10495
- name: "opendevbrowser-best-practices",
10496
- topic: "quick start"
10497
- },
10498
- designAgent: {
10499
- name: "opendevbrowser-design-agent",
10500
- topic: "canvas-contract"
10501
- }
10502
- };
10503
- var formatSkillReference = (skill) => `${skill.name} "${skill.topic}"`;
10504
- var formatSkillLoadCommand = (skill) => `opendevbrowser_skill_load ${skill.name} "${skill.topic}"`;
10505
- var INSPIREDESIGN_HANDOFF_COMMANDS = {
10506
- loadBestPractices: formatSkillLoadCommand(INSPIREDESIGN_HANDOFF_SKILLS.bestPractices),
10507
- loadDesignAgent: formatSkillLoadCommand(INSPIREDESIGN_HANDOFF_SKILLS.designAgent),
10508
- continueInCanvas: `opendevbrowser canvas --command canvas.plan.set --params-file ./${INSPIREDESIGN_HANDOFF_FILES.canvasPlanRequest}`
10509
- };
10510
- var INSPIREDESIGN_HANDOFF_RECOMMENDED_SKILLS = [
10511
- formatSkillReference(INSPIREDESIGN_HANDOFF_SKILLS.bestPractices),
10512
- formatSkillReference(INSPIREDESIGN_HANDOFF_SKILLS.designAgent)
10513
- ];
10514
- var INSPIREDESIGN_HANDOFF_GUIDANCE = {
10515
- reviewAdvancedBrief: `${INSPIREDESIGN_HANDOFF_FILES.advancedBrief} is the authoritative reference-first brief. When URL references exist, captured evidence leads the creative direction; selected format, profile defaults, layout posture, motion grammar, and anti-patterns are route guardrails only. Read it before touching Canvas or implementation files.`,
10516
- prepareCanvasPlanRequest: `Fill canvasSessionId, leaseId, and documentId in ${INSPIREDESIGN_HANDOFF_FILES.canvasPlanRequest} before running ${INSPIREDESIGN_HANDOFF_COMMANDS.continueInCanvas}.`,
10517
- deepCaptureRecommendation: "Any inspiredesign run with reference URLs already uses captureMode=deep. Rerun with the same URLs only when you need refreshed DOM/layout evidence, restored session state, or capture-specific debugging."
10518
- };
10519
- var INSPIREDESIGN_ARTIFACT_GUIDE = {
10520
- [INSPIREDESIGN_HANDOFF_FILES.advancedBrief]: {
10521
- purpose: "Authoritative reference-first brief for the downstream design agent.",
10522
- expectedContents: ["Selected prompt format", "reference pattern board", "route guardrails"],
10523
- howToUse: ["Read first", "treat captured evidence as creative priority", "use guardrails to avoid route drift"],
10524
- mustNot: ["Do not treat defaults as stronger than captured references"]
10525
- },
10526
- [INSPIREDESIGN_HANDOFF_FILES.designMarkdown]: {
10527
- purpose: "Human-readable design specification and implementation narrative.",
10528
- expectedContents: ["inspiration analysis", "unified direction", "governance summary", "deliverables"],
10529
- howToUse: ["Use as the readable project brief", "cross-check implementation choices against its sections"],
10530
- mustNot: ["Do not use prose as a substitute for the JSON contract when patching Canvas"]
10531
- },
10532
- [INSPIREDESIGN_HANDOFF_FILES.designContract]: {
10533
- purpose: "Narrowed Canvas governance contract for design decisions.",
10534
- expectedContents: ["emitted governance blocks", "motion system", "library policy", "runtime budgets"],
10535
- howToUse: ["Patch only emitted governance blocks", "compare implementation against this contract before shipping"],
10536
- mustNot: ["Do not add navigation, async, or performance context as Canvas governance patches"]
10537
- },
10538
- [INSPIREDESIGN_HANDOFF_FILES.canvasPlanRequest]: {
10539
- purpose: "Ready-to-fill request payload for `canvas.plan.set`.",
10540
- expectedContents: ["request ids", "Canvas session ids", "mutation-safe generationPlan"],
10541
- howToUse: ["Fill canvasSessionId, leaseId, and documentId", "submit with the provided canvas.plan.set command"],
10542
- mustNot: ["Do not add handoff-only fields or reference-only analysis to generationPlan"]
10543
- },
10544
- [INSPIREDESIGN_HANDOFF_FILES.designAgentHandoff]: {
10545
- purpose: "Downstream index for artifact usage, skills, commands, and omitted implementation context.",
10546
- expectedContents: ["skills", "commands", "contract scope", "implementation context", "artifact and section guides"],
10547
- howToUse: ["Use as the navigation map for the bundle", "load recommended skills before implementation"],
10548
- mustNot: ["Do not treat handoff context as runtime Canvas schema"]
10549
- },
10550
- [INSPIREDESIGN_HANDOFF_FILES.generationPlan]: {
10551
- purpose: "Full generated plan for reasoning about design intent.",
10552
- expectedContents: ["Canvas plan fields", "design vectors", "reference analysis when available"],
10553
- howToUse: ["Use for agent reasoning and audit traceability", "compare with canvas-plan.request.json for runtime subset"],
10554
- mustNot: ["Do not submit this file directly to Canvas when it contains non-request context"]
10555
- },
10556
- [INSPIREDESIGN_HANDOFF_FILES.implementationPlanMarkdown]: {
10557
- purpose: "Human-readable engineering sequence for the first implementation pass.",
10558
- expectedContents: ["build sequence", "component plan", "token strategy", "QA and risk checks"],
10559
- howToUse: ["Convert sections into implementation tasks", "keep tests and browser validation aligned to the plan"],
10560
- mustNot: ["Do not implement sections unsupported by brief or reference evidence"]
10561
- },
10562
- [INSPIREDESIGN_HANDOFF_FILES.implementationPlan]: {
10563
- purpose: "Machine-readable implementation plan matching the Markdown plan.",
10564
- expectedContents: ["architecture steps", "component inventory", "state and validation tasks"],
10565
- howToUse: ["Use for structured task extraction", "keep it synchronized with implementation-plan.md"],
10566
- mustNot: ["Do not treat it as a Canvas document patch payload"]
10567
- },
10568
- [INSPIREDESIGN_HANDOFF_FILES.evidence]: {
10569
- purpose: "Evidence digest for brief, reference, capture, and design-vector provenance.",
10570
- expectedContents: ["brief expansion", "reference outcomes", "capture attempts", "design vectors"],
10571
- howToUse: ["Audit why choices were made", "prefer evidence over generic template defaults"],
10572
- mustNot: ["Do not ignore failed or skipped capture statuses when judging confidence"]
10573
- },
10574
- [INSPIREDESIGN_HANDOFF_FILES.prototypeGuidance]: {
10575
- purpose: "Optional first prototype guidance when the workflow requests prototype output.",
10576
- expectedContents: ["prototype structure", "design-vector guidance", "browser proof checklist"],
10577
- howToUse: ["Use only for the first prototype pass", "promote proven ideas back into contract-aligned work"],
10578
- mustNot: ["Do not treat prototype guidance as final implementation authority"]
10579
- }
10580
- };
10581
- var INSPIREDESIGN_CONTRACT_SECTION_GUIDE = {
10582
- intent: {
10583
- purpose: "Define why the design exists and what success means.",
10584
- expectedContents: ["audience", "task", "success criteria", "trust posture"],
10585
- howToUse: ["Validate the primary user job before styling", "reject sections that do not serve the task"],
10586
- mustNot: ["Do not start visual polish before the audience and task are clear"]
10587
- },
10588
- generationPlan: {
10589
- purpose: "Mutation-safe subset accepted by Canvas planning.",
10590
- expectedContents: ["target outcome", "visual, layout, content, component, motion, responsive, accessibility posture"],
10591
- howToUse: ["Submit only through canvas-plan.request.json", "repair generationPlanIssues before mutation"],
10592
- mustNot: ["Do not add handoff-only guide fields to the Canvas generation plan"]
10593
- },
10594
- designLanguage: {
10595
- purpose: "Name the coherent visual direction and token ownership.",
10596
- expectedContents: ["direction", "style axes", "semantic token source", "approved libraries"],
10597
- howToUse: ["Keep one design language per task", "align repeated components to semantic tokens"],
10598
- mustNot: ["Do not mix unrelated visual families inside one surface"]
10599
- },
10600
- contentModel: {
10601
- purpose: "Define real content, message hierarchy, and UI states.",
10602
- expectedContents: ["primary message", "supporting messages", "states", "loading, empty, and error behavior"],
10603
- howToUse: ["Use real content first", "plan non-happy-path states before polish"],
10604
- mustNot: ["Do not ship placeholder copy as product content"]
10605
- },
10606
- layoutSystem: {
10607
- purpose: "Describe page architecture and section rhythm.",
10608
- expectedContents: ["grid", "containers", "spacing rhythm", "alignment rules"],
10609
- howToUse: ["Use to place sections and scan units consistently", "verify desktop and mobile structure"],
10610
- mustNot: ["Do not invent one-off layout rules for repeated sections"]
10611
- },
10612
- typographySystem: {
10613
- purpose: "Define type families, scale, measure, and loading behavior.",
10614
- expectedContents: ["families", "scale", "measure", "fallback policy", "loading strategy"],
10615
- howToUse: ["Apply type hierarchy consistently", "avoid layout shift from font loading"],
10616
- mustNot: ["Do not default to unapproved system stacks for a distinctive design"]
10617
- },
10618
- colorSystem: {
10619
- purpose: "Define semantic color roles and theme behavior.",
10620
- expectedContents: ["primary roles", "surface roles", "text roles", "state colors"],
10621
- howToUse: ["Map repeated UI to semantic tokens", "validate contrast in every required theme"],
10622
- mustNot: ["Do not scatter raw color values across leaf components"]
10623
- },
10624
- surfaceSystem: {
10625
- purpose: "Define material, depth, borders, and background behavior.",
10626
- expectedContents: ["surface hierarchy", "border rules", "shadow rules", "material effects"],
10627
- howToUse: ["Use depth only to clarify hierarchy", "align material effects with design vectors"],
10628
- mustNot: ["Do not turn every content group into a card by default"]
10629
- },
10630
- iconSystem: {
10631
- purpose: "Define icon usage and decorative asset boundaries.",
10632
- expectedContents: ["icon family", "stroke policy", "labeling rules", "decorative rules"],
10633
- howToUse: ["Use icons to clarify actions", "keep accessible names on icon-only controls"],
10634
- mustNot: ["Do not rely on icons as the only explanation for critical actions"]
10635
- },
10636
- motionSystem: {
10637
- purpose: "Define motion that supports comprehension.",
10638
- expectedContents: ["timing", "interaction moments", "reduced-motion posture", "advanced motion advisory"],
10639
- howToUse: ["Keep shader, WebGL, and Spline cues advisory", "provide reduced-motion replacements"],
10640
- mustNot: ["Do not use motion cues to authorize new runtime libraries"]
10641
- },
10642
- responsiveSystem: {
10643
- purpose: "Define authored behavior across desktop, tablet, and mobile.",
10644
- expectedContents: ["breakpoints", "adaptation rules", "touch policy", "overflow policy"],
10645
- howToUse: ["Validate the primary action at every viewport", "collapse structure before copy becomes cramped"],
10646
- mustNot: ["Do not assume desktop layouts naturally scale down"]
10647
- },
10648
- accessibilityPolicy: {
10649
- purpose: "Set accessibility requirements before implementation.",
10650
- expectedContents: ["WCAG target", "keyboard requirements", "focus policy", "semantic requirements"],
10651
- howToUse: ["Block release on contrast or keyboard regressions", "validate focus on every interactive state"],
10652
- mustNot: ["Do not defer accessibility until after visual implementation"]
10653
- },
10654
- libraryPolicy: {
10655
- purpose: "Declare approved implementation libraries and runtime boundaries.",
10656
- expectedContents: ["components", "icons", "styling", "motion", "threeD"],
10657
- howToUse: ["Use as the dependency authorization boundary", "keep motion and threeD empty unless separately approved"],
10658
- mustNot: ["Do not infer WebGL, shader, Spline, or 3D runtime support from advisory motion"]
10659
- },
10660
- runtimeBudgets: {
10661
- purpose: "Set practical limits for sections, actions, interaction latency, and preview cost.",
10662
- expectedContents: ["section budgets", "action budgets", "latency budgets", "preview notes"],
10663
- howToUse: ["Use as a constraint during implementation", "validate slow or animation-heavy surfaces against it"],
10664
- mustNot: ["Do not add decorative weight that violates the budget"]
10665
- },
10666
- navigationModel: {
10667
- purpose: "Implementation-only context for route, tab, overlay, and deep-link ownership.",
10668
- expectedContents: ["route owner", "deep-link policy", "invalid route fallback", "overlay entry points"],
10669
- howToUse: ["Use from design-agent-handoff.json when wiring implementation state"],
10670
- mustNot: ["Do not patch this omitted block into Canvas governance"]
10671
- },
10672
- asyncModel: {
10673
- purpose: "Implementation-only context for loading, restart, cancellation, and URL-owned query state.",
10674
- expectedContents: ["owner", "load trigger", "restart triggers", "cancellation policy"],
10675
- howToUse: ["Use when wiring fetch/search state and stale-request handling"],
10676
- mustNot: ["Do not let components invent independent async ownership"]
10677
- },
10678
- performanceModel: {
10679
- purpose: "Implementation-only context for render hotspots and measurement posture.",
10680
- expectedContents: ["render hotspots", "stable identity policy", "list strategy", "measurement plan"],
10681
- howToUse: ["Use before building scan-heavy or motion-heavy surfaces"],
10682
- mustNot: ["Do not ship heavy interaction surfaces without measurement evidence"]
9568
+ "keyboard-regression",
9569
+ "stale-search-results",
9570
+ "invalid-route-recovery-failure"
9571
+ ],
9572
+ warnOn: [
9573
+ "responsive-mismatch",
9574
+ "layout-shift",
9575
+ "spinner-stacking",
9576
+ "scan-surface-jank"
9577
+ ]
9578
+ },
9579
+ designVectors: {
9580
+ advancedMotionAdvisory: [
9581
+ "Advisory shader-like gradient depth",
9582
+ "Advisory WebGL-style spatial reveal",
9583
+ "Advisory Spline-style product orbit",
9584
+ "Runtime support: none. Library policy authorization: none."
9585
+ ]
9586
+ }
10683
9587
  }
10684
9588
  };
10685
- var buildInspiredesignFollowthroughSummary = () => `Read ${INSPIREDESIGN_HANDOFF_FILES.advancedBrief} first, then continue in OpenDevBrowser Canvas with ${INSPIREDESIGN_HANDOFF_FILES.canvasPlanRequest} and ${INSPIREDESIGN_HANDOFF_FILES.designAgentHandoff}, load ${INSPIREDESIGN_HANDOFF_RECOMMENDED_SKILLS[0]} plus ${INSPIREDESIGN_HANDOFF_RECOMMENDED_SKILLS[1]} before implementation, and note that any supplied reference URL already uses captureMode=deep.`;
10686
- var buildInspiredesignNextStep = () => `Read ${INSPIREDESIGN_HANDOFF_FILES.advancedBrief} first. ${INSPIREDESIGN_HANDOFF_GUIDANCE.prepareCanvasPlanRequest} Then run ${INSPIREDESIGN_HANDOFF_COMMANDS.continueInCanvas}, confirm planStatus=accepted, then patch only the governance blocks listed in ${INSPIREDESIGN_HANDOFF_FILES.designAgentHandoff}.`;
10687
9589
 
10688
9590
  // skills/opendevbrowser-design-agent/assets/templates/inspiredesign-advanced-brief.v1.json
10689
9591
  var inspiredesign_advanced_brief_v1_default = {
@@ -14287,10 +13189,111 @@ var createSuccessHandoff = (followthroughSummary, suggestedNextAction, suggested
14287
13189
  });
14288
13190
  var cliExample = (command, args = "") => `npx opendevbrowser ${command}${args ? ` ${args}` : ""}`;
14289
13191
  var quoteCliValue = (value) => JSON.stringify(value);
14290
- var buildResearchRerunCommand = (input) => cliExample(
14291
- "research run",
14292
- `--topic ${quoteCliValue(input.topic)} --days 14 --source-selection auto --sources web,community --browser-mode ${input.browserMode ?? "managed"} --mode json --output-format json`
14293
- );
13192
+ var GATED_PROVIDER_REASON_CODES = /* @__PURE__ */ new Set([
13193
+ "auth_required",
13194
+ "token_required",
13195
+ "challenge_detected"
13196
+ ]);
13197
+ var buildResearchRerunCommand = (input, options = {}) => {
13198
+ const browserMode = options.browserMode ?? input.browserMode ?? "managed";
13199
+ const useCookies = options.useCookies ? " --use-cookies" : "";
13200
+ const challengeMode = options.challengeAutomationMode ? ` --challenge-automation-mode ${options.challengeAutomationMode}` : "";
13201
+ return cliExample(
13202
+ "research run",
13203
+ `--topic ${quoteCliValue(input.topic)} --days 14 --sources web,community --browser-mode ${browserMode}${useCookies}${challengeMode} --mode json --output-format json`
13204
+ );
13205
+ };
13206
+ var isJsonRecord3 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
13207
+ var readFailureReasonCode = (failure) => {
13208
+ return failure.error.reasonCode ?? normalizeProviderReasonCode({
13209
+ code: failure.error.code,
13210
+ message: failure.error.message,
13211
+ details: failure.error.details
13212
+ }) ?? null;
13213
+ };
13214
+ var readDiagnosticReasonCode = (diagnostic) => {
13215
+ const reasonCode = diagnostic.reasonCode ?? diagnostic.browserFallbackReasonCode;
13216
+ return isProviderReasonCode(reasonCode) && GATED_PROVIDER_REASON_CODES.has(reasonCode) ? reasonCode : null;
13217
+ };
13218
+ var cookieDiagnosticShowsAvailableCookies = (diagnostic) => {
13219
+ return diagnostic.available === true || typeof diagnostic.loaded === "number" && diagnostic.loaded > 0 || typeof diagnostic.injected === "number" && diagnostic.injected > 0 || typeof diagnostic.verifiedCount === "number" && diagnostic.verifiedCount > 0;
13220
+ };
13221
+ var addProviderSignal = (signal, provider, reasonCode) => {
13222
+ if (provider) signal.providers.push(provider);
13223
+ if (reasonCode) signal.reasonCodes.push(reasonCode);
13224
+ };
13225
+ var readFailureCookieDiagnostics = (failure) => {
13226
+ const candidate = failure.error.details?.cookieDiagnostics;
13227
+ return isJsonRecord3(candidate) ? candidate : null;
13228
+ };
13229
+ var emptyResearchGatedProviderSignal = () => ({
13230
+ providers: [],
13231
+ reasonCodes: [],
13232
+ useCookies: false
13233
+ });
13234
+ var normalizeResearchGatedProviderSignal = (signal) => {
13235
+ return signal.providers.length > 0 || signal.reasonCodes.length > 0 ? {
13236
+ providers: [...new Set(signal.providers)].sort(),
13237
+ reasonCodes: [...new Set(signal.reasonCodes)].sort(),
13238
+ useCookies: signal.useCookies
13239
+ } : null;
13240
+ };
13241
+ var mergeResearchGatedProviderSignals = (signals) => {
13242
+ const merged = emptyResearchGatedProviderSignal();
13243
+ for (const signal of signals) {
13244
+ merged.providers.push(...signal.providers);
13245
+ merged.reasonCodes.push(...signal.reasonCodes);
13246
+ merged.useCookies = merged.useCookies || signal.useCookies;
13247
+ }
13248
+ return merged;
13249
+ };
13250
+ var detectResearchFailureSignals = (failures) => {
13251
+ const signal = emptyResearchGatedProviderSignal();
13252
+ for (const failure of failures) {
13253
+ const reasonCode = readFailureReasonCode(failure);
13254
+ const isGatedFailure = Boolean(reasonCode && GATED_PROVIDER_REASON_CODES.has(reasonCode));
13255
+ if (isGatedFailure) {
13256
+ addProviderSignal(signal, failure.provider, reasonCode);
13257
+ const diagnostic = readFailureCookieDiagnostics(failure);
13258
+ signal.useCookies = signal.useCookies || Boolean(diagnostic && cookieDiagnosticShowsAvailableCookies(diagnostic));
13259
+ }
13260
+ }
13261
+ return signal;
13262
+ };
13263
+ var detectResearchCookieSignals = (diagnostics) => {
13264
+ const signal = emptyResearchGatedProviderSignal();
13265
+ for (const diagnostic of diagnostics) {
13266
+ const reasonCode = readDiagnosticReasonCode(diagnostic);
13267
+ const isGatedDiagnostic = Boolean(reasonCode || diagnostic.policy === "required");
13268
+ if (isGatedDiagnostic) {
13269
+ addProviderSignal(signal, typeof diagnostic.provider === "string" ? diagnostic.provider : void 0, reasonCode);
13270
+ signal.useCookies = signal.useCookies || cookieDiagnosticShowsAvailableCookies(diagnostic);
13271
+ }
13272
+ }
13273
+ return signal;
13274
+ };
13275
+ var detectResearchChallengeSignals = (diagnostics) => {
13276
+ const signal = emptyResearchGatedProviderSignal();
13277
+ for (const diagnostic of diagnostics) {
13278
+ const reasonCode = readDiagnosticReasonCode(diagnostic);
13279
+ if (reasonCode || diagnostic.blockerType === "auth_required" || diagnostic.blockerType === "anti_bot_challenge") {
13280
+ addProviderSignal(signal, typeof diagnostic.provider === "string" ? diagnostic.provider : void 0, reasonCode);
13281
+ }
13282
+ }
13283
+ return signal;
13284
+ };
13285
+ var detectResearchGatedProviderSignal = (input) => {
13286
+ return normalizeResearchGatedProviderSignal(mergeResearchGatedProviderSignals([
13287
+ detectResearchFailureSignals(input.failures ?? []),
13288
+ detectResearchCookieSignals(input.cookieDiagnostics ?? []),
13289
+ detectResearchChallengeSignals(input.challengeOrchestration ?? [])
13290
+ ]));
13291
+ };
13292
+ var buildResearchRecoveryRerunCommand = (input, signal) => buildResearchRerunCommand(input, {
13293
+ browserMode: "extension",
13294
+ useCookies: signal.useCookies,
13295
+ challengeAutomationMode: "browser_with_helper"
13296
+ });
14294
13297
  var buildShoppingRerunCommand = (input) => {
14295
13298
  const providers = input.providers?.length ? ` --providers ${input.providers.join(",")}` : " --providers shopping/bestbuy,shopping/ebay";
14296
13299
  const budget = typeof input.budget === "number" ? ` --budget ${input.budget}` : "";
@@ -14318,23 +13321,43 @@ var buildMacroResolveArgs = (input, options) => {
14318
13321
  const defaultProvider = input.defaultProvider ? ` --default-provider ${input.defaultProvider}` : "";
14319
13322
  const execute = options?.execute ? " --execute" : "";
14320
13323
  const browserMode = options?.browserMode ? ` --browser-mode ${options.browserMode}` : "";
13324
+ const useCookies = options?.useCookies ? " --use-cookies" : "";
14321
13325
  const challenge = options?.challengeAutomationMode ? ` --challenge-automation-mode ${options.challengeAutomationMode}` : "";
13326
+ const cookiePolicy = options?.cookiePolicyOverride ? ` --cookie-policy ${options.cookiePolicyOverride}` : "";
14322
13327
  const outputFormat = options?.includeOutputFormat === false ? "" : " --output-format json";
14323
- return `--expression ${quoteCliValue(input.expression)}${defaultProvider}${execute}${browserMode}${challenge}${outputFormat}`;
13328
+ return `--expression ${quoteCliValue(input.expression)}${defaultProvider}${execute}${browserMode}${useCookies}${cookiePolicy}${challenge}${outputFormat}`;
14324
13329
  };
14325
13330
  var buildMacroPreviewCommand = (input) => cliExample("macro-resolve", buildMacroResolveArgs(input));
14326
13331
  var buildMacroExecuteCommand = (input, challengeAutomationMode, browserMode) => cliExample("macro-resolve", buildMacroResolveArgs(input, {
14327
13332
  execute: true,
14328
13333
  browserMode,
13334
+ ...browserMode === "extension" ? { useCookies: true, cookiePolicyOverride: "required" } : {},
14329
13335
  challengeAutomationMode
14330
13336
  }));
14331
- var buildResearchSuccessHandoff = (input) => {
13337
+ var buildResearchGatedSuccessHandoff = (input, signal) => {
13338
+ const recoveryCommand = buildResearchRecoveryRerunCommand(input, signal);
13339
+ const providers = signal.providers.length > 0 ? signal.providers.join(", ") : "gated providers";
13340
+ const cookieNote = signal.useCookies ? " The command includes --use-cookies because cookie diagnostics show available cookies." : " Add --use-cookies only when legitimate provider cookies are available.";
13341
+ return createSuccessHandoff(
13342
+ `Review ranked records, artifact metadata, and gated-provider diagnostics for ${providers} before publishing claims.`,
13343
+ `Open the returned artifact path, inspect records.json, context.json, meta.json, and report.md, then rerun ${recoveryCommand} only with a user-authorized signed-in relay session.${cookieNote}`,
13344
+ [
13345
+ { reason: "Check records.json, context.json, meta.json, failures, and cookie diagnostics before using the result as evidence." },
13346
+ {
13347
+ reason: "Rerun with an existing signed-in extension session and browser-scoped challenge assistance when gated providers blocked useful evidence.",
13348
+ command: recoveryCommand
13349
+ },
13350
+ { reason: "Keep SERPs discovery-only and publish only claims supported by destination records that passed review." }
13351
+ ]
13352
+ );
13353
+ };
13354
+ var buildResearchDefaultSuccessHandoff = (input) => {
14332
13355
  const rerunCommand = buildResearchRerunCommand(input);
14333
13356
  return createSuccessHandoff(
14334
- "Review the ranked records and artifact bundle before turning the result into a publishable claim.",
14335
- `Open the returned artifact path, inspect the supporting records, and rerun ${rerunCommand} if you need a tighter evidence set.`,
13357
+ "Review ranked records, artifact metadata, and source support before turning the result into a publishable claim.",
13358
+ `Open the returned artifact path, inspect records.json, context.json, meta.json, and report.md, then rerun ${rerunCommand} if you need a tighter evidence set.`,
14336
13359
  [
14337
- { reason: "Check which records actually support the final claim." },
13360
+ { reason: "Check which ranked records and artifact metadata actually support the final claim." },
14338
13361
  {
14339
13362
  reason: "Rerun with explicit sources and a narrower timebox if the evidence set is still too broad.",
14340
13363
  command: rerunCommand
@@ -14342,6 +13365,10 @@ var buildResearchSuccessHandoff = (input) => {
14342
13365
  ]
14343
13366
  );
14344
13367
  };
13368
+ var buildResearchSuccessHandoff = (input) => {
13369
+ const signal = detectResearchGatedProviderSignal(input);
13370
+ return signal ? buildResearchGatedSuccessHandoff(input, signal) : buildResearchDefaultSuccessHandoff(input);
13371
+ };
14345
13372
  var buildShoppingSuccessHandoff = (input) => {
14346
13373
  const rerunCommand = buildShoppingRerunCommand(input);
14347
13374
  return createSuccessHandoff(
@@ -14485,19 +13512,304 @@ var compactResearchLines = (records, meta) => {
14485
13512
  if (records.length === 0) {
14486
13513
  const summary = primaryConstraintSummaryFromMeta(meta);
14487
13514
  return summary ? [
14488
- "No records matched the requested timebox.",
13515
+ "No usable research findings were available.",
14489
13516
  `Primary constraint: ${summary}`
14490
- ] : ["No records matched the requested timebox."];
13517
+ ] : ["No usable research findings were available."];
14491
13518
  }
14492
13519
  return records.slice(0, 10).map((record, index) => {
14493
13520
  const title = record.title ?? record.url ?? record.provider;
14494
13521
  const engagement = record.engagement.likes + record.engagement.comments + record.engagement.upvotes;
14495
- return `${index + 1}. ${title} (${record.source}/${record.provider}) score=${record.confidence.toFixed(2)} engagement=${engagement}`;
13522
+ return `${index + 1}. ${title} (${record.source}; ${record.provider}) score=${record.confidence.toFixed(2)} engagement=${engagement}`;
13523
+ });
13524
+ };
13525
+ var RESEARCH_REPORT_LIMITS = {
13526
+ findings: 10,
13527
+ sources: 20,
13528
+ failures: 10,
13529
+ excerptCharacters: 240,
13530
+ failureMessageCharacters: 240
13531
+ };
13532
+ var RESEARCH_REPORT_FILE_NAMES = [
13533
+ "summary.md",
13534
+ "report.md",
13535
+ "records.json",
13536
+ "context.json",
13537
+ "meta.json",
13538
+ "bundle-manifest.json"
13539
+ ];
13540
+ var plainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
13541
+ var researchTitle = (record) => record.title ?? record.url ?? record.provider;
13542
+ var normalizedInlineText = (content) => content?.replace(/\s+/g, " ").trim() ?? "";
13543
+ var boundedInlineText = (args) => {
13544
+ const normalized = normalizedInlineText(args.content);
13545
+ if (!normalized) {
13546
+ return args.fallback;
13547
+ }
13548
+ if (normalized.length <= args.limit) {
13549
+ return normalized;
13550
+ }
13551
+ return `${normalized.slice(0, args.limit)} [truncated; see ${args.target}]`;
13552
+ };
13553
+ var researchExcerpt = (content) => boundedInlineText({
13554
+ content,
13555
+ fallback: "No content excerpt was available.",
13556
+ limit: RESEARCH_REPORT_LIMITS.excerptCharacters,
13557
+ target: "records.json for full content"
13558
+ });
13559
+ var researchFailureMessage = (content) => boundedInlineText({
13560
+ content,
13561
+ fallback: "provider failure",
13562
+ limit: RESEARCH_REPORT_LIMITS.failureMessageCharacters,
13563
+ target: "meta.json"
13564
+ });
13565
+ var limitedCount = (total, limit) => Math.min(total, limit);
13566
+ var omissionLine = (args) => {
13567
+ const omitted = args.total - limitedCount(args.total, args.limit);
13568
+ if (omitted <= 0) {
13569
+ return [];
13570
+ }
13571
+ const noun = omitted === 1 ? args.singular : args.plural;
13572
+ return [`- ${omitted} more ${noun} omitted from this report; see ${args.target} for the complete dataset.`];
13573
+ };
13574
+ var researchFindingsLines = (records) => records.length === 0 ? ["- No usable findings were available."] : [
13575
+ ...records.slice(0, RESEARCH_REPORT_LIMITS.findings).flatMap((record, index) => [
13576
+ `### ${index + 1}. ${researchTitle(record)}`,
13577
+ `- Source: ${record.source}`,
13578
+ `- Provider: ${record.provider}`,
13579
+ `- URL: ${record.url ?? "not provided"}`,
13580
+ `- Published: ${record.timestamp}`,
13581
+ `- Confidence: ${record.confidence.toFixed(2)}`,
13582
+ `- Evidence: ${researchExcerpt(record.content)}`
13583
+ ]),
13584
+ ...omissionLine({
13585
+ total: records.length,
13586
+ limit: RESEARCH_REPORT_LIMITS.findings,
13587
+ singular: "finding",
13588
+ plural: "findings",
13589
+ target: "records.json"
13590
+ })
13591
+ ];
13592
+ var researchSourcesLines = (records) => records.length === 0 ? ["- No sources available."] : [
13593
+ ...records.slice(0, RESEARCH_REPORT_LIMITS.sources).map((record) => `- ${researchTitle(record)}: ${record.url ?? "URL not provided"}`),
13594
+ ...omissionLine({
13595
+ total: records.length,
13596
+ limit: RESEARCH_REPORT_LIMITS.sources,
13597
+ singular: "source",
13598
+ plural: "sources",
13599
+ target: "records.json"
13600
+ })
13601
+ ];
13602
+ var researchReasonLine = (metrics) => {
13603
+ const reasons = Object.entries(plainObject(metrics.sanitized_reason_distribution)).map(([reason, count]) => `${reason}: ${String(count)}`);
13604
+ return reasons.length === 0 ? [] : [`- Sanitized record reasons: ${reasons.join(", ")}`];
13605
+ };
13606
+ var deadEndSearchFailures = (failures) => {
13607
+ if (!Array.isArray(failures)) return [];
13608
+ return failures.filter((failure) => {
13609
+ const record = plainObject(failure);
13610
+ const error = plainObject(record.error);
13611
+ const details = plainObject(error.details);
13612
+ return details.fallbackOutputReason === "research_dead_end_shell";
14496
13613
  });
14497
13614
  };
13615
+ var deadEndSearchFailureCount = (meta) => deadEndSearchFailures(meta.failures).length;
13616
+ var rejectedCandidatesFromMeta = (meta) => Array.isArray(meta.rejected_candidates) ? meta.rejected_candidates.map(plainObject).filter((candidate) => Object.keys(candidate).length > 0) : [];
13617
+ var rejectedCandidateCount = (meta) => {
13618
+ const metrics = plainObject(meta.metrics);
13619
+ if (typeof metrics.rejected_candidate_count === "number") {
13620
+ return metrics.rejected_candidate_count;
13621
+ }
13622
+ const sanitized = typeof metrics.sanitized_records === "number" ? metrics.sanitized_records : 0;
13623
+ return sanitized + deadEndSearchFailureCount(meta);
13624
+ };
13625
+ var deadEndSearchFailureLines = (meta) => {
13626
+ const failures = deadEndSearchFailures(meta.failures);
13627
+ if (failures.length === 0) return [];
13628
+ return [`- Dead-end search failures: ${failures.length}`];
13629
+ };
13630
+ var researchRejectedCandidateSummary = (candidate) => {
13631
+ const reason = typeof candidate.reason === "string" ? candidate.reason : "unknown_reason";
13632
+ const provider = typeof candidate.provider === "string" ? candidate.provider : "unknown_provider";
13633
+ const source = typeof candidate.source === "string" ? candidate.source : "unknown_source";
13634
+ const status = typeof candidate.replacement_status === "string" ? candidate.replacement_status : "not_recorded";
13635
+ const retrievalPath = typeof candidate.retrievalPath === "string" ? `; path=${candidate.retrievalPath}` : "";
13636
+ const url = typeof candidate.url === "string" ? candidate.url : "URL not recorded";
13637
+ return `${reason} from ${provider} (${source}; ${status}${retrievalPath}): ${url}`;
13638
+ };
13639
+ var researchFailureSummary = (failure) => {
13640
+ const record = plainObject(failure);
13641
+ const error = plainObject(record.error);
13642
+ const provider = typeof record.provider === "string" ? record.provider : "unknown";
13643
+ const source = typeof record.source === "string" ? record.source : "unknown";
13644
+ const reason = typeof error.reasonCode === "string" ? `${error.reasonCode}: ` : "";
13645
+ const message = researchFailureMessage(typeof error.message === "string" ? error.message : void 0);
13646
+ return `${provider} (${source}): ${reason}${message}`;
13647
+ };
13648
+ var researchFailureLines = (failures) => {
13649
+ if (!Array.isArray(failures) || failures.length === 0) {
13650
+ return [];
13651
+ }
13652
+ const summaries = failures.slice(0, RESEARCH_REPORT_LIMITS.failures).map(researchFailureSummary);
13653
+ const omitted = failures.length - summaries.length;
13654
+ const noun = omitted === 1 ? "failure" : "failures";
13655
+ const suffix = omitted > 0 ? `; ${omitted} more provider ${noun} omitted from this report; see meta.json` : "";
13656
+ return [`- Provider failures: ${summaries.join("; ")}${suffix}`];
13657
+ };
13658
+ var researchGapLines = (meta) => {
13659
+ const metrics = plainObject(meta.metrics);
13660
+ const details = [
13661
+ typeof metrics.final_records === "number" ? `- Final records reported by workflow: ${metrics.final_records}` : "",
13662
+ typeof metrics.sanitized_records === "number" ? `- Sanitized records excluded: ${metrics.sanitized_records}` : "",
13663
+ ...researchReasonLine(metrics),
13664
+ ...researchFailureLines(meta.failures)
13665
+ ].filter(Boolean);
13666
+ const constraint = primaryConstraintSummaryFromMeta(meta);
13667
+ const fallback = "- No provider limitations or sanitization gaps were reported.";
13668
+ const gapDetails = details.length > 0 || constraint ? details : [fallback];
13669
+ return [
13670
+ "## Confidence and Gaps",
13671
+ ...constraint ? [`- Primary constraint: ${constraint}`] : [],
13672
+ ...gapDetails
13673
+ ];
13674
+ };
13675
+ var researchSearchDirectionLines = (meta) => {
13676
+ const selection = plainObject(meta.selection);
13677
+ const sources = Array.isArray(selection.resolved_sources) ? selection.resolved_sources.map(String).join(", ") : "not recorded";
13678
+ return [
13679
+ "## Search Direction",
13680
+ `- Source families searched: ${sources}`,
13681
+ "- Direction: Follow accepted destination pages from provider/search output before synthesis."
13682
+ ];
13683
+ };
13684
+ var researchSourceFamilies = (meta) => {
13685
+ const sources = plainObject(meta.selection).resolved_sources;
13686
+ return Array.isArray(sources) ? sources.map(String) : [];
13687
+ };
13688
+ var researchCandidateTriageSchema = () => ({
13689
+ url: "",
13690
+ rank: 0,
13691
+ engine: "",
13692
+ query: "",
13693
+ source_family: "",
13694
+ title: "",
13695
+ status: "pending|accepted|rejected",
13696
+ blocker_notes: "",
13697
+ rejection_reason: "",
13698
+ replacement_url: "",
13699
+ retrieval_notes: "",
13700
+ extraction_status: "pending|fetched|blocked|shell|stale|irrelevant"
13701
+ });
13702
+ var researchCandidateTriageLines = (records, meta) => {
13703
+ const rejected = rejectedCandidateCount(meta);
13704
+ return [
13705
+ "## Candidate Triage",
13706
+ `- Accepted destination records: ${records.length}`,
13707
+ `- Rejected shell or dead-end candidates: ${rejected}`,
13708
+ "- Rejection policy: search pages, login/account pages, privacy/cookie pages, JavaScript shells, not-found pages, and unsupported shells are not final evidence."
13709
+ ];
13710
+ };
13711
+ var researchRejectedCandidateLines = (meta) => {
13712
+ const rejectedCandidates = rejectedCandidatesFromMeta(meta);
13713
+ const rejectionLines = [
13714
+ ...researchReasonLine(plainObject(meta.metrics)),
13715
+ ...rejectedCandidates.slice(0, RESEARCH_REPORT_LIMITS.failures).map((candidate) => `- Rejected candidate: ${researchRejectedCandidateSummary(candidate)}`),
13716
+ ...deadEndSearchFailureLines(meta)
13717
+ ];
13718
+ return [
13719
+ "## Rejected Candidates",
13720
+ ...rejectionLines,
13721
+ ...rejectionLines.length === 0 ? ["- No rejected candidate distribution was reported."] : []
13722
+ ];
13723
+ };
13724
+ var researchDeepDiveLines = (records) => [
13725
+ "## Deep Dives",
13726
+ ...records.length === 0 ? ["- No destination pages passed the evidence gate."] : records.slice(0, RESEARCH_REPORT_LIMITS.sources).map((record) => {
13727
+ const retrievalPath = typeof record.attributes.retrievalPath === "string" ? record.attributes.retrievalPath : "";
13728
+ const prefix = retrievalPath.includes(":fetch:") ? "Opened destination evidence" : "Accepted evidence record";
13729
+ return `- ${prefix}: ${record.url ?? researchTitle(record)}`;
13730
+ })
13731
+ ];
13732
+ var researchSynthesisFeedbackText = (records, meta) => {
13733
+ const rejected = rejectedCandidateCount(meta);
13734
+ return records.length === 0 || rejected > records.length ? "Continue with remaining public destination candidates or narrow the query; use auth/cookies only when a selected evidence page itself requires authorized access." : "Synthesize only the accepted destination evidence and cite records.json for full source text.";
13735
+ };
13736
+ var researchSynthesisFeedbackLines = (records, meta) => {
13737
+ return ["## Synthesis Feedback", `- Next step: ${researchSynthesisFeedbackText(records, meta)}`];
13738
+ };
13739
+ var researchContextPayload = (args) => ({
13740
+ topic: args.topic,
13741
+ timebox: plainObject(args.meta.timebox),
13742
+ source_families: researchSourceFamilies(args.meta),
13743
+ evidence_gate: {
13744
+ status: "pending_review",
13745
+ reviewed_artifacts: []
13746
+ },
13747
+ artifact_files: RESEARCH_REPORT_FILE_NAMES,
13748
+ source_ledger: args.records.map((record) => ({
13749
+ title: researchTitle(record),
13750
+ url: record.url,
13751
+ source_family: record.source,
13752
+ provider: record.provider
13753
+ })),
13754
+ search_direction_notes: researchSearchDirectionLines(args.meta),
13755
+ candidate_triage_schema: researchCandidateTriageSchema(),
13756
+ highlights: args.lines,
13757
+ records: args.records,
13758
+ candidate_triage: {
13759
+ accepted_destination_records: args.records.length,
13760
+ rejected_shell_or_dead_end_candidates: rejectedCandidateCount(args.meta)
13761
+ },
13762
+ rejected_candidates: rejectedCandidatesFromMeta(args.meta),
13763
+ deep_dive_pages: args.records.map((record) => ({
13764
+ title: researchTitle(record),
13765
+ url: record.url,
13766
+ provider: record.provider,
13767
+ source: record.source,
13768
+ retrievalPath: record.attributes.retrievalPath
13769
+ })),
13770
+ iteration_log: researchSearchDirectionLines(args.meta),
13771
+ synthesis_feedback: researchSynthesisFeedbackText(args.records, args.meta),
13772
+ meta: args.meta
13773
+ });
13774
+ var researchArtifactFileLines = () => [
13775
+ "## Report Files",
13776
+ ...RESEARCH_REPORT_FILE_NAMES.map((fileName) => `- ${fileName}`)
13777
+ ];
13778
+ var buildResearchReport = (args) => [
13779
+ "# Research Report",
13780
+ "",
13781
+ "## Executive Summary",
13782
+ `- Topic: ${args.topic}`,
13783
+ `- Usable findings: ${args.records.length}`,
13784
+ `- Findings shown in report: ${limitedCount(args.records.length, RESEARCH_REPORT_LIMITS.findings)}`,
13785
+ `- Sources shown in report: ${limitedCount(args.records.length, RESEARCH_REPORT_LIMITS.sources)}`,
13786
+ "- Final output: Usable records are persisted in records.json.",
13787
+ "- Diagnostics: Run metadata, failures, and constraints are persisted in meta.json; this report summarizes the bounded inline subset.",
13788
+ "",
13789
+ ...researchArtifactFileLines(),
13790
+ "",
13791
+ ...researchSearchDirectionLines(args.meta),
13792
+ "",
13793
+ ...researchCandidateTriageLines(args.records, args.meta),
13794
+ "",
13795
+ ...researchRejectedCandidateLines(args.meta),
13796
+ "",
13797
+ ...researchDeepDiveLines(args.records),
13798
+ "",
13799
+ ...researchSynthesisFeedbackLines(args.records, args.meta),
13800
+ "",
13801
+ "## Findings",
13802
+ ...researchFindingsLines(args.records),
13803
+ "",
13804
+ ...researchGapLines(args.meta),
13805
+ "",
13806
+ "## Sources",
13807
+ ...researchSourcesLines(args.records)
13808
+ ].join("\n");
14498
13809
  var renderResearch = (args) => {
14499
13810
  const lines = compactResearchLines(args.records, args.meta);
14500
13811
  const summary = lines.join("\n");
13812
+ const report = buildResearchReport(args);
14501
13813
  const markdown = [
14502
13814
  `# Research: ${args.topic}`,
14503
13815
  "",
@@ -14508,14 +13820,15 @@ var renderResearch = (args) => {
14508
13820
  JSON.stringify(args.meta, null, 2),
14509
13821
  "```"
14510
13822
  ].join("\n");
14511
- const contextPayload = {
13823
+ const contextPayload = researchContextPayload({
14512
13824
  topic: args.topic,
14513
- highlights: lines,
13825
+ lines,
14514
13826
  records: args.records,
14515
13827
  meta: args.meta
14516
- };
13828
+ });
14517
13829
  const files = [
14518
13830
  { path: "summary.md", content: markdown },
13831
+ { path: "report.md", content: report },
14519
13832
  { path: "records.json", content: { records: args.records } },
14520
13833
  { path: "context.json", content: contextPayload },
14521
13834
  { path: "meta.json", content: args.meta }
@@ -15636,14 +14949,14 @@ import { createHash as createHash5 } from "crypto";
15636
14949
  var DEFAULT_SHOPPING_SEARCH_LIMIT = 8;
15637
14950
  var SHOPPING_FETCH_RECOVERY_LIMIT = 2;
15638
14951
  var SEARCH_INDEX_RETRIEVAL_PATHS = /* @__PURE__ */ new Set(["shopping:search:index", "shopping:search:link"]);
15639
- var isJsonRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
14952
+ var isJsonRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
15640
14953
  var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
15641
14954
  var isProviderSource = (value) => value === "web" || value === "community" || value === "social" || value === "shopping";
15642
- var isTraceContext = (value) => isJsonRecord3(value) && typeof value.requestId === "string" && typeof value.ts === "string" && (value.sessionId === void 0 || typeof value.sessionId === "string") && (value.targetId === void 0 || typeof value.targetId === "string") && (value.provider === void 0 || typeof value.provider === "string");
15643
- var isProviderError2 = (value) => isJsonRecord3(value) && typeof value.code === "string" && typeof value.message === "string" && typeof value.retryable === "boolean" && (value.reasonCode === void 0 || typeof value.reasonCode === "string") && (value.provider === void 0 || typeof value.provider === "string") && (value.source === void 0 || isProviderSource(value.source)) && (value.details === void 0 || isJsonRecord3(value.details));
15644
- var isNormalizedRecord = (value) => isJsonRecord3(value) && typeof value.id === "string" && isProviderSource(value.source) && typeof value.provider === "string" && (value.url === void 0 || typeof value.url === "string") && (value.title === void 0 || typeof value.title === "string") && (value.content === void 0 || typeof value.content === "string") && typeof value.timestamp === "string" && isFiniteNumber(value.confidence) && isJsonRecord3(value.attributes);
15645
- var isProviderFailureEntry = (value) => isJsonRecord3(value) && typeof value.provider === "string" && isProviderSource(value.source) && isProviderError2(value.error);
15646
- var isProviderAggregateResult = (value) => isJsonRecord3(value) && typeof value.ok === "boolean" && Array.isArray(value.records) && value.records.every((entry) => isNormalizedRecord(entry)) && isTraceContext(value.trace) && typeof value.partial === "boolean" && Array.isArray(value.failures) && value.failures.every((entry) => isProviderFailureEntry(entry)) && isJsonRecord3(value.metrics) && isFiniteNumber(value.metrics.attempted) && isFiniteNumber(value.metrics.succeeded) && isFiniteNumber(value.metrics.failed) && isFiniteNumber(value.metrics.retries) && isFiniteNumber(value.metrics.latencyMs) && typeof value.sourceSelection === "string" && Array.isArray(value.providerOrder) && value.providerOrder.every((entry) => typeof entry === "string") && (value.meta === void 0 || isJsonRecord3(value.meta)) && (value.diagnostics === void 0 || isJsonRecord3(value.diagnostics)) && (value.error === void 0 || isProviderError2(value.error));
14955
+ var isTraceContext = (value) => isJsonRecord4(value) && typeof value.requestId === "string" && typeof value.ts === "string" && (value.sessionId === void 0 || typeof value.sessionId === "string") && (value.targetId === void 0 || typeof value.targetId === "string") && (value.provider === void 0 || typeof value.provider === "string");
14956
+ var isProviderError2 = (value) => isJsonRecord4(value) && typeof value.code === "string" && typeof value.message === "string" && typeof value.retryable === "boolean" && (value.reasonCode === void 0 || typeof value.reasonCode === "string") && (value.provider === void 0 || typeof value.provider === "string") && (value.source === void 0 || isProviderSource(value.source)) && (value.details === void 0 || isJsonRecord4(value.details));
14957
+ var isNormalizedRecord = (value) => isJsonRecord4(value) && typeof value.id === "string" && isProviderSource(value.source) && typeof value.provider === "string" && (value.url === void 0 || typeof value.url === "string") && (value.title === void 0 || typeof value.title === "string") && (value.content === void 0 || typeof value.content === "string") && typeof value.timestamp === "string" && isFiniteNumber(value.confidence) && isJsonRecord4(value.attributes);
14958
+ var isProviderFailureEntry = (value) => isJsonRecord4(value) && typeof value.provider === "string" && isProviderSource(value.source) && isProviderError2(value.error);
14959
+ var isProviderAggregateResult = (value) => isJsonRecord4(value) && typeof value.ok === "boolean" && Array.isArray(value.records) && value.records.every((entry) => isNormalizedRecord(entry)) && isTraceContext(value.trace) && typeof value.partial === "boolean" && Array.isArray(value.failures) && value.failures.every((entry) => isProviderFailureEntry(entry)) && isJsonRecord4(value.metrics) && isFiniteNumber(value.metrics.attempted) && isFiniteNumber(value.metrics.succeeded) && isFiniteNumber(value.metrics.failed) && isFiniteNumber(value.metrics.retries) && isFiniteNumber(value.metrics.latencyMs) && typeof value.sourceSelection === "string" && Array.isArray(value.providerOrder) && value.providerOrder.every((entry) => typeof entry === "string") && (value.meta === void 0 || isJsonRecord4(value.meta)) && (value.diagnostics === void 0 || isJsonRecord4(value.diagnostics)) && (value.error === void 0 || isProviderError2(value.error));
15647
14960
  var emptyCheckpointState = () => ({
15648
14961
  completed_step_ids: [],
15649
14962
  step_results_by_id: {}
@@ -15659,7 +14972,7 @@ var readShoppingCheckpointState = (checkpoint) => {
15659
14972
  if (state === void 0 || state === null) {
15660
14973
  return emptyCheckpointState();
15661
14974
  }
15662
- if (!isJsonRecord3(state)) {
14975
+ if (!isJsonRecord4(state)) {
15663
14976
  throw new Error("Shopping workflow checkpoint state must be a record.");
15664
14977
  }
15665
14978
  const completedStepIds = state.completed_step_ids;
@@ -15667,7 +14980,7 @@ var readShoppingCheckpointState = (checkpoint) => {
15667
14980
  throw new Error("Shopping workflow checkpoint state is missing valid completed_step_ids.");
15668
14981
  }
15669
14982
  const rawResults = state.step_results_by_id;
15670
- if (!isJsonRecord3(rawResults)) {
14983
+ if (!isJsonRecord4(rawResults)) {
15671
14984
  throw new Error("Shopping workflow checkpoint state is missing valid step_results_by_id.");
15672
14985
  }
15673
14986
  const stepResultsById = {};
@@ -15978,14 +15291,14 @@ var PRODUCT_VIDEO_STEP_IDS = {
15978
15291
  extractProductData: "product_video:extract_product_data",
15979
15292
  assembleArtifacts: "product_video:assemble_artifacts"
15980
15293
  };
15981
- var isJsonRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
15294
+ var isJsonRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
15982
15295
  var isFiniteNumber2 = (value) => typeof value === "number" && Number.isFinite(value);
15983
15296
  var isProviderSource2 = (value) => value === "web" || value === "community" || value === "social" || value === "shopping";
15984
- var isTraceContext2 = (value) => isJsonRecord4(value) && typeof value.requestId === "string" && typeof value.ts === "string" && (value.sessionId === void 0 || typeof value.sessionId === "string") && (value.targetId === void 0 || typeof value.targetId === "string") && (value.provider === void 0 || typeof value.provider === "string");
15985
- var isProviderError3 = (value) => isJsonRecord4(value) && typeof value.code === "string" && typeof value.message === "string" && typeof value.retryable === "boolean" && (value.reasonCode === void 0 || typeof value.reasonCode === "string") && (value.provider === void 0 || typeof value.provider === "string") && (value.source === void 0 || isProviderSource2(value.source)) && (value.details === void 0 || isJsonRecord4(value.details));
15986
- var isNormalizedRecord2 = (value) => isJsonRecord4(value) && typeof value.id === "string" && isProviderSource2(value.source) && typeof value.provider === "string" && (value.url === void 0 || typeof value.url === "string") && (value.title === void 0 || typeof value.title === "string") && (value.content === void 0 || typeof value.content === "string") && typeof value.timestamp === "string" && isFiniteNumber2(value.confidence) && isJsonRecord4(value.attributes);
15987
- var isProviderFailureEntry2 = (value) => isJsonRecord4(value) && typeof value.provider === "string" && isProviderSource2(value.source) && isProviderError3(value.error);
15988
- var isProviderAggregateResult2 = (value) => isJsonRecord4(value) && typeof value.ok === "boolean" && Array.isArray(value.records) && value.records.every((entry) => isNormalizedRecord2(entry)) && isTraceContext2(value.trace) && typeof value.partial === "boolean" && Array.isArray(value.failures) && value.failures.every((entry) => isProviderFailureEntry2(entry)) && isJsonRecord4(value.metrics) && isFiniteNumber2(value.metrics.attempted) && isFiniteNumber2(value.metrics.succeeded) && isFiniteNumber2(value.metrics.failed) && isFiniteNumber2(value.metrics.retries) && isFiniteNumber2(value.metrics.latencyMs) && typeof value.sourceSelection === "string" && Array.isArray(value.providerOrder) && value.providerOrder.every((entry) => typeof entry === "string") && (value.meta === void 0 || isJsonRecord4(value.meta)) && (value.diagnostics === void 0 || isJsonRecord4(value.diagnostics)) && (value.error === void 0 || isProviderError3(value.error));
15297
+ var isTraceContext2 = (value) => isJsonRecord5(value) && typeof value.requestId === "string" && typeof value.ts === "string" && (value.sessionId === void 0 || typeof value.sessionId === "string") && (value.targetId === void 0 || typeof value.targetId === "string") && (value.provider === void 0 || typeof value.provider === "string");
15298
+ var isProviderError3 = (value) => isJsonRecord5(value) && typeof value.code === "string" && typeof value.message === "string" && typeof value.retryable === "boolean" && (value.reasonCode === void 0 || typeof value.reasonCode === "string") && (value.provider === void 0 || typeof value.provider === "string") && (value.source === void 0 || isProviderSource2(value.source)) && (value.details === void 0 || isJsonRecord5(value.details));
15299
+ var isNormalizedRecord2 = (value) => isJsonRecord5(value) && typeof value.id === "string" && isProviderSource2(value.source) && typeof value.provider === "string" && (value.url === void 0 || typeof value.url === "string") && (value.title === void 0 || typeof value.title === "string") && (value.content === void 0 || typeof value.content === "string") && typeof value.timestamp === "string" && isFiniteNumber2(value.confidence) && isJsonRecord5(value.attributes);
15300
+ var isProviderFailureEntry2 = (value) => isJsonRecord5(value) && typeof value.provider === "string" && isProviderSource2(value.source) && isProviderError3(value.error);
15301
+ var isProviderAggregateResult2 = (value) => isJsonRecord5(value) && typeof value.ok === "boolean" && Array.isArray(value.records) && value.records.every((entry) => isNormalizedRecord2(entry)) && isTraceContext2(value.trace) && typeof value.partial === "boolean" && Array.isArray(value.failures) && value.failures.every((entry) => isProviderFailureEntry2(entry)) && isJsonRecord5(value.metrics) && isFiniteNumber2(value.metrics.attempted) && isFiniteNumber2(value.metrics.succeeded) && isFiniteNumber2(value.metrics.failed) && isFiniteNumber2(value.metrics.retries) && isFiniteNumber2(value.metrics.latencyMs) && typeof value.sourceSelection === "string" && Array.isArray(value.providerOrder) && value.providerOrder.every((entry) => typeof entry === "string") && (value.meta === void 0 || isJsonRecord5(value.meta)) && (value.diagnostics === void 0 || isJsonRecord5(value.diagnostics)) && (value.error === void 0 || isProviderError3(value.error));
15989
15302
  var emptyCheckpointState2 = () => ({
15990
15303
  completed_step_ids: []
15991
15304
  });
@@ -16028,7 +15341,7 @@ var readProductVideoCheckpointState = (checkpoint) => {
16028
15341
  if (state === void 0 || state === null) {
16029
15342
  return emptyCheckpointState2();
16030
15343
  }
16031
- if (!isJsonRecord4(state)) {
15344
+ if (!isJsonRecord5(state)) {
16032
15345
  throw new Error("Product-video workflow checkpoint state must be a record.");
16033
15346
  }
16034
15347
  const completedStepIds = state.completed_step_ids;
@@ -16139,15 +15452,16 @@ import { createHash as createHash6 } from "crypto";
16139
15452
  var RESEARCH_AUTO_SOURCES = ["web", "community", "social"];
16140
15453
  var RESEARCH_ALL_SOURCES = [...RESEARCH_AUTO_SOURCES];
16141
15454
  var DEFAULT_RESEARCH_SEARCH_LIMIT = 10;
16142
- var RESEARCH_WEB_SEARCH_FETCH_LIMIT = 3;
16143
- var isJsonRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
15455
+ var RESEARCH_WEB_SEARCH_FETCH_LIMIT = 5;
15456
+ var RESEARCH_COMMUNITY_EXPANSION_PER_RECORD = 2;
15457
+ var isJsonRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
16144
15458
  var isFiniteNumber3 = (value) => typeof value === "number" && Number.isFinite(value);
16145
15459
  var isProviderSource3 = (value) => value === "web" || value === "community" || value === "social" || value === "shopping";
16146
- var isTraceContext3 = (value) => isJsonRecord5(value) && typeof value.requestId === "string" && typeof value.ts === "string" && (value.sessionId === void 0 || typeof value.sessionId === "string") && (value.targetId === void 0 || typeof value.targetId === "string") && (value.provider === void 0 || typeof value.provider === "string");
16147
- var isProviderError4 = (value) => isJsonRecord5(value) && typeof value.code === "string" && typeof value.message === "string" && typeof value.retryable === "boolean" && (value.reasonCode === void 0 || typeof value.reasonCode === "string") && (value.provider === void 0 || typeof value.provider === "string") && (value.source === void 0 || isProviderSource3(value.source)) && (value.details === void 0 || isJsonRecord5(value.details));
16148
- var isNormalizedRecord3 = (value) => isJsonRecord5(value) && typeof value.id === "string" && isProviderSource3(value.source) && typeof value.provider === "string" && (value.url === void 0 || typeof value.url === "string") && (value.title === void 0 || typeof value.title === "string") && (value.content === void 0 || typeof value.content === "string") && typeof value.timestamp === "string" && isFiniteNumber3(value.confidence) && isJsonRecord5(value.attributes);
16149
- var isProviderFailureEntry3 = (value) => isJsonRecord5(value) && typeof value.provider === "string" && isProviderSource3(value.source) && isProviderError4(value.error);
16150
- var isProviderAggregateResult3 = (value) => isJsonRecord5(value) && typeof value.ok === "boolean" && Array.isArray(value.records) && value.records.every((entry) => isNormalizedRecord3(entry)) && isTraceContext3(value.trace) && typeof value.partial === "boolean" && Array.isArray(value.failures) && value.failures.every((entry) => isProviderFailureEntry3(entry)) && isJsonRecord5(value.metrics) && isFiniteNumber3(value.metrics.attempted) && isFiniteNumber3(value.metrics.succeeded) && isFiniteNumber3(value.metrics.failed) && isFiniteNumber3(value.metrics.retries) && isFiniteNumber3(value.metrics.latencyMs) && typeof value.sourceSelection === "string" && Array.isArray(value.providerOrder) && value.providerOrder.every((entry) => typeof entry === "string") && (value.meta === void 0 || isJsonRecord5(value.meta)) && (value.diagnostics === void 0 || isJsonRecord5(value.diagnostics)) && (value.error === void 0 || isProviderError4(value.error));
15460
+ var isTraceContext3 = (value) => isJsonRecord6(value) && typeof value.requestId === "string" && typeof value.ts === "string" && (value.sessionId === void 0 || typeof value.sessionId === "string") && (value.targetId === void 0 || typeof value.targetId === "string") && (value.provider === void 0 || typeof value.provider === "string");
15461
+ var isProviderError4 = (value) => isJsonRecord6(value) && typeof value.code === "string" && typeof value.message === "string" && typeof value.retryable === "boolean" && (value.reasonCode === void 0 || typeof value.reasonCode === "string") && (value.provider === void 0 || typeof value.provider === "string") && (value.source === void 0 || isProviderSource3(value.source)) && (value.details === void 0 || isJsonRecord6(value.details));
15462
+ var isNormalizedRecord3 = (value) => isJsonRecord6(value) && typeof value.id === "string" && isProviderSource3(value.source) && typeof value.provider === "string" && (value.url === void 0 || typeof value.url === "string") && (value.title === void 0 || typeof value.title === "string") && (value.content === void 0 || typeof value.content === "string") && typeof value.timestamp === "string" && isFiniteNumber3(value.confidence) && isJsonRecord6(value.attributes);
15463
+ var isProviderFailureEntry3 = (value) => isJsonRecord6(value) && typeof value.provider === "string" && isProviderSource3(value.source) && isProviderError4(value.error);
15464
+ var isProviderAggregateResult3 = (value) => isJsonRecord6(value) && typeof value.ok === "boolean" && Array.isArray(value.records) && value.records.every((entry) => isNormalizedRecord3(entry)) && isTraceContext3(value.trace) && typeof value.partial === "boolean" && Array.isArray(value.failures) && value.failures.every((entry) => isProviderFailureEntry3(entry)) && isJsonRecord6(value.metrics) && isFiniteNumber3(value.metrics.attempted) && isFiniteNumber3(value.metrics.succeeded) && isFiniteNumber3(value.metrics.failed) && isFiniteNumber3(value.metrics.retries) && isFiniteNumber3(value.metrics.latencyMs) && typeof value.sourceSelection === "string" && Array.isArray(value.providerOrder) && value.providerOrder.every((entry) => typeof entry === "string") && (value.meta === void 0 || isJsonRecord6(value.meta)) && (value.diagnostics === void 0 || isJsonRecord6(value.diagnostics)) && (value.error === void 0 || isProviderError4(value.error));
16151
15465
  var emptyCheckpointState3 = () => ({
16152
15466
  completed_step_ids: [],
16153
15467
  step_results_by_id: {}
@@ -16170,7 +15484,7 @@ var readResearchCheckpointState = (checkpoint) => {
16170
15484
  if (state === void 0 || state === null) {
16171
15485
  return emptyCheckpointState3();
16172
15486
  }
16173
- if (!isJsonRecord5(state)) {
15487
+ if (!isJsonRecord6(state)) {
16174
15488
  throw new Error("Research workflow checkpoint state must be a record.");
16175
15489
  }
16176
15490
  const completedStepIds = state.completed_step_ids;
@@ -16178,7 +15492,7 @@ var readResearchCheckpointState = (checkpoint) => {
16178
15492
  throw new Error("Research workflow checkpoint state is missing valid completed_step_ids.");
16179
15493
  }
16180
15494
  const rawResults = state.step_results_by_id;
16181
- if (!isJsonRecord5(rawResults)) {
15495
+ if (!isJsonRecord6(rawResults)) {
16182
15496
  throw new Error("Research workflow checkpoint state is missing valid step_results_by_id.");
16183
15497
  }
16184
15498
  const stepResultsById = {};
@@ -16228,8 +15542,8 @@ var buildResearchSearchFilters = (source, args) => ({
16228
15542
  timebox_to: args.timebox.to,
16229
15543
  ...source === "community" || source === "social" ? {
16230
15544
  pageLimit: 1,
16231
- hopLimit: 0,
16232
- expansionPerRecord: 0
15545
+ hopLimit: source === "community" ? 1 : 0,
15546
+ expansionPerRecord: source === "community" ? RESEARCH_COMMUNITY_EXPANSION_PER_RECORD : 0
16233
15547
  } : {}
16234
15548
  });
16235
15549
  var compileResearchExecutionPlan = (args) => {
@@ -16296,7 +15610,12 @@ var compileResearchExecutionPlan = (args) => {
16296
15610
 
16297
15611
  // src/providers/research-executor.ts
16298
15612
  var RESEARCH_WEB_SEARCH_FETCH_PATHS = /* @__PURE__ */ new Set([
15613
+ "community:search:index",
15614
+ "community:search:url",
16299
15615
  "web:search:index",
15616
+ "web:search:url",
15617
+ "social:youtube:search:url",
15618
+ "social:search:url",
16300
15619
  "social:search:index"
16301
15620
  ]);
16302
15621
  var appendTrace2 = (trace, stage, event, details) => [
@@ -16337,6 +15656,15 @@ var isValidHttpUrl2 = (url) => {
16337
15656
  return false;
16338
15657
  }
16339
15658
  };
15659
+ var attributeLinks = (record) => {
15660
+ const links = record.attributes.links;
15661
+ if (!Array.isArray(links)) return [];
15662
+ return links.filter((link) => typeof link === "string");
15663
+ };
15664
+ var researchCandidateUrls = (record) => [
15665
+ ...typeof record.url === "string" ? [record.url] : [],
15666
+ ...attributeLinks(record)
15667
+ ];
16340
15668
  var resolveResearchWebFetchCandidates = (records, limit) => {
16341
15669
  const candidates = [];
16342
15670
  const seen = /* @__PURE__ */ new Set();
@@ -16345,27 +15673,30 @@ var resolveResearchWebFetchCandidates = (records, limit) => {
16345
15673
  if (!RESEARCH_WEB_SEARCH_FETCH_PATHS.has(retrievalPath)) {
16346
15674
  continue;
16347
15675
  }
16348
- const rawUrl = typeof record.url === "string" ? canonicalizeUrl(record.url) : "";
16349
- if (!rawUrl) {
16350
- continue;
16351
- }
16352
- let resolvedUrl = rawUrl;
16353
- try {
16354
- const parsed = new URL(rawUrl);
16355
- if (/duckduckgo\.com$/i.test(parsed.hostname) && parsed.pathname === "/l") {
16356
- const redirect = parsed.searchParams.get("uddg");
16357
- if (typeof redirect === "string" && redirect.length > 0) {
16358
- resolvedUrl = canonicalizeUrl(redirect);
15676
+ for (const candidateUrl of researchCandidateUrls(record)) {
15677
+ const rawUrl = canonicalizeUrl(candidateUrl);
15678
+ if (!rawUrl) continue;
15679
+ let resolvedUrl = rawUrl;
15680
+ try {
15681
+ const parsed = new URL(rawUrl);
15682
+ if (/duckduckgo\.com$/i.test(parsed.hostname) && (parsed.pathname === "/l" || parsed.pathname === "/l/")) {
15683
+ const redirect = parsed.searchParams.get("uddg");
15684
+ if (typeof redirect === "string" && redirect.length > 0) {
15685
+ resolvedUrl = canonicalizeUrl(redirect);
15686
+ }
16359
15687
  }
15688
+ } catch {
15689
+ continue;
15690
+ }
15691
+ if (!resolvedUrl || !isValidHttpUrl2(resolvedUrl) || !isLikelyResearchDestinationUrl(resolvedUrl) || /duckduckgo\.com/i.test(resolvedUrl) || seen.has(resolvedUrl)) {
15692
+ continue;
15693
+ }
15694
+ seen.add(resolvedUrl);
15695
+ candidates.push(resolvedUrl);
15696
+ if (candidates.length >= limit) {
15697
+ break;
16360
15698
  }
16361
- } catch {
16362
- continue;
16363
- }
16364
- if (!resolvedUrl || !isValidHttpUrl2(resolvedUrl) || /duckduckgo\.com/i.test(resolvedUrl) || seen.has(resolvedUrl)) {
16365
- continue;
16366
15699
  }
16367
- seen.add(resolvedUrl);
16368
- candidates.push(resolvedUrl);
16369
15700
  if (candidates.length >= limit) {
16370
15701
  break;
16371
15702
  }
@@ -17131,7 +16462,11 @@ var rankResearchRecords = (records) => {
17131
16462
  var hash2 = (value) => createHash7("sha1").update(value).digest("hex").slice(0, 16);
17132
16463
  var RESEARCH_ALWAYS_SANITIZED_PATHS = /* @__PURE__ */ new Set([
17133
16464
  "community:search:index",
17134
- "social:search:index"
16465
+ "community:search:url",
16466
+ "social:search:index",
16467
+ "social:search:url",
16468
+ "social:youtube:search:url",
16469
+ "web:search:url"
17135
16470
  ]);
17136
16471
  var RESEARCH_CONDITIONAL_SANITIZED_PATHS = /* @__PURE__ */ new Set([
17137
16472
  "community:fetch:url",
@@ -17139,9 +16474,13 @@ var RESEARCH_CONDITIONAL_SANITIZED_PATHS = /* @__PURE__ */ new Set([
17139
16474
  "web:search:index"
17140
16475
  ]);
17141
16476
  var RESEARCH_LOGIN_SHELL_RE = /\b(?:log in|login|sign in|sign-in|please log in|continue with google|continue with apple)\b/i;
16477
+ var RESEARCH_LOGIN_SHELL_MAX_CONTENT_CHARS = 600;
16478
+ var RESEARCH_LOGIN_REQUIRED_RE = /\b(?:log in to continue|sign in to continue|authentication required|please log in|continue with google|continue with apple)\b/i;
17142
16479
  var RESEARCH_JS_REQUIRED_RE = /\b(?:enable javascript|javascript required|javascript is not available|javascript is disabled|you need to enable javascript)\b/i;
17143
16480
  var RESEARCH_GENERIC_SHELL_RE = /\b(?:skip to main content|the heart of the internet|open navigation|get the app|view in app|please wait for verification|verify you are human|security check)\b/i;
17144
16481
  var RESEARCH_NOT_FOUND_SHELL_RE = /\b(?:error 404|page not found|not found|can['’]t seem to find the page)\b/i;
16482
+ var RESEARCH_PRIVACY_PREFERENCE_SHELL_RE = /\b(?:select your cookie preferences|customize cookie preferences|unable to save cookie preferences|your privacy choices|manage consent preferences|privacy opt[- ]out|do not sell or share my personal information)\b/i;
16483
+ var RESEARCH_PRIVACY_RECOVERED_CONTENT_RE = /\b(?:blogs? home|permalink|comments|article)\b/i;
17145
16484
  var RESEARCH_SEARCH_SHELL_RE = /\b(?:duckduckgo|search results|all posts|communities|comments|try another search|no relevant content found|unable to load answer|search page)\b/i;
17146
16485
  var isDuckDuckGoResearchShellUrl = (url) => {
17147
16486
  try {
@@ -17154,6 +16493,7 @@ var isDuckDuckGoResearchShellUrl = (url) => {
17154
16493
  }
17155
16494
  };
17156
16495
  var PRODUCT_TARGET_NOT_FOUND_RE = /\b(?:error 404|page not found|not found|we can['’]t seem to find the page|can['’]t seem to find the page|return to homepage)\b/i;
16496
+ var BESTBUY_PDP_ERROR_SHELL_RE = /\b(?:something went wrong|use our search bar|pick a category below|typed in a url|check it for errors)\b/i;
17157
16497
  var resolveShoppingProviderIdForUrl = (url) => {
17158
16498
  try {
17159
16499
  const host = new URL(url).hostname.toLowerCase();
@@ -17208,6 +16548,48 @@ var WALMART_TITLE_CHROME_RE = /\b(?:Walmart\.com|Skip to Main Content|Pickup or
17208
16548
  var WALMART_BRAND_COLOR_RE = /\bColor\s+[A-Z0-9][A-Za-z0-9 /-]*(?=\s+(?:View full specifications|Current price is|Skip to Main Content|Pickup or delivery\?|How do you want your item\?|Sold and shipped by|Seller Rating|Free shipping|Arrives\b|Shipping\b|Delivery\b|Pickup\b|Departments Services|More details|Add to cart)|$)/i;
17209
16549
  var WALMART_BRAND_CHROME_TAIL_RE = /\b(?:View full specifications|Current price is|Skip to Main Content|Pickup or delivery\?|How do you want your item\?|Sold and shipped by|Seller Rating|Free shipping|Arrives\b|Shipping\b|Delivery\b|Pickup\b|Departments Services|More details|Add to cart)\b.*$/i;
17210
16550
  var WALMART_BRAND_CHROME_RE = /\b(?:Walmart\.com|Skip to Main Content|Pickup or delivery\?|How do you want your item\?|Sold and shipped by|Seller Rating|View full specifications|Current price is|Free shipping|Departments Services)\b/i;
16551
+ var EBAY_TITLE_BRAND_PREFIX_RE = /^(?:new|used|pre-owned|preowned|open box|refurbished|renewed|genuine|authentic)\s+/i;
16552
+ var EBAY_KNOWN_MULTI_TOKEN_BRANDS = [
16553
+ "3M",
16554
+ "Bang & Olufsen",
16555
+ "Bowers & Wilkins",
16556
+ "Hewlett-Packard",
16557
+ "iRobot",
16558
+ "New Balance"
16559
+ ];
16560
+ var EBAY_KNOWN_SINGLE_TOKEN_BRANDS = /* @__PURE__ */ new Map([
16561
+ ["apple", "Apple"],
16562
+ ["bose", "Bose"],
16563
+ ["canon", "Canon"],
16564
+ ["dell", "Dell"],
16565
+ ["dyson", "Dyson"],
16566
+ ["google", "Google"],
16567
+ ["jbl", "JBL"],
16568
+ ["lenovo", "Lenovo"],
16569
+ ["lg", "LG"],
16570
+ ["microsoft", "Microsoft"],
16571
+ ["nikon", "Nikon"],
16572
+ ["nintendo", "Nintendo"],
16573
+ ["panasonic", "Panasonic"],
16574
+ ["philips", "Philips"],
16575
+ ["samsung", "Samsung"],
16576
+ ["sony", "Sony"]
16577
+ ]);
16578
+ var EBAY_TITLE_BRAND_STOP_WORDS = /* @__PURE__ */ new Set([
16579
+ "bluetooth",
16580
+ "case",
16581
+ "ergonomic",
16582
+ "gaming",
16583
+ "headphones",
16584
+ "keyboard",
16585
+ "leather",
16586
+ "noise",
16587
+ "portable",
16588
+ "rechargeable",
16589
+ "speaker",
16590
+ "vertical",
16591
+ "wireless"
16592
+ ]);
17211
16593
  var PRODUCT_FEATURE_SECTION_MARKERS = [
17212
16594
  "about this item",
17213
16595
  "key item features",
@@ -17273,7 +16655,7 @@ var INSPIREDESIGN_RENDER_MODES = /* @__PURE__ */ new Set(["compact", "json", "md
17273
16655
  var INSPIREDESIGN_CAPTURE_MODES = /* @__PURE__ */ new Set(["off", "deep"]);
17274
16656
  var INSPIREDESIGN_COOKIE_POLICIES = /* @__PURE__ */ new Set(["off", "auto", "required"]);
17275
16657
  var WORKFLOW_BROWSER_MODES = /* @__PURE__ */ new Set(["auto", "extension", "managed"]);
17276
- var isJsonRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
16658
+ var isJsonRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
17277
16659
  var INSPIREDESIGN_CAPTURE_UNAVAILABLE_FAILURE = "Deep capture requested, but browser capture is unavailable in this execution lane.";
17278
16660
  var isCanvasVisualDirectionProfile = (value) => {
17279
16661
  return CANVAS_VISUAL_DIRECTION_PROFILES.includes(value);
@@ -17302,7 +16684,7 @@ var serializeInspiredesignRunInput = (input) => ({
17302
16684
  });
17303
16685
  var isStringArray2 = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
17304
16686
  var parseInspiredesignBriefFormatRoute = (value) => {
17305
- if (!isJsonRecord6(value)) return void 0;
16687
+ if (!isJsonRecord7(value)) return void 0;
17306
16688
  const profile = typeof value.profile === "string" && isCanvasVisualDirectionProfile(value.profile) ? value.profile : void 0;
17307
16689
  const themeStrategy = typeof value.themeStrategy === "string" && isCanvasThemeStrategy(value.themeStrategy) ? value.themeStrategy : void 0;
17308
16690
  const navigationModel = typeof value.navigationModel === "string" && isCanvasNavigationModel(value.navigationModel) ? value.navigationModel : void 0;
@@ -17317,7 +16699,7 @@ var parseInspiredesignBriefFormatRoute = (value) => {
17317
16699
  };
17318
16700
  };
17319
16701
  var parseInspiredesignBriefFormat = (value) => {
17320
- if (!isJsonRecord6(value)) return void 0;
16702
+ if (!isJsonRecord7(value)) return void 0;
17321
16703
  const route = parseInspiredesignBriefFormatRoute(value.route);
17322
16704
  if (typeof value.id !== "string" || typeof value.label !== "string" || !isStringArray2(value.bestFor) || !isStringArray2(value.businessFocus) || !isStringArray2(value.keywords) || typeof value.archetype !== "string" || typeof value.layoutArchetype !== "string" || typeof value.typographySystem !== "string" || typeof value.surfaceTreatment !== "string" || typeof value.shapeLanguage !== "string" || typeof value.componentGrammar !== "string" || typeof value.motionGrammar !== "string" || typeof value.paletteIntent !== "string" || typeof value.visualDensity !== "string" || typeof value.designVariance !== "string" || !isStringArray2(value.responsiveCollapseRules) || !isStringArray2(value.guardrails) || !isStringArray2(value.antiPatterns) || !isStringArray2(value.deliverables) || !route) {
17323
16705
  return void 0;
@@ -17346,7 +16728,7 @@ var parseInspiredesignBriefFormat = (value) => {
17346
16728
  };
17347
16729
  };
17348
16730
  var parseInspiredesignBriefExpansion = (value) => {
17349
- if (!isJsonRecord6(value)) return void 0;
16731
+ if (!isJsonRecord7(value)) return void 0;
17350
16732
  const format = parseInspiredesignBriefFormat(value.format);
17351
16733
  if (typeof value.sourceBrief !== "string" || typeof value.advancedBrief !== "string" || typeof value.templateVersion !== "string" || !format) {
17352
16734
  return void 0;
@@ -17368,7 +16750,7 @@ var parseInspiredesignEnvelopeInput = (input) => {
17368
16750
  ...typeof input.captureMode === "string" && INSPIREDESIGN_CAPTURE_MODES.has(input.captureMode) ? { captureMode: input.captureMode } : {},
17369
16751
  ...typeof input.includePrototypeGuidance === "boolean" ? { includePrototypeGuidance: input.includePrototypeGuidance } : {},
17370
16752
  ...typeof input.timeoutMs === "number" ? { timeoutMs: input.timeoutMs } : {},
17371
- ...typeof input.outputDir === "string" && input.outputDir.length > 0 ? { outputDir: input.outputDir } : {},
16753
+ ...typeof input.outputDir === "string" ? { outputDir: input.outputDir } : {},
17372
16754
  ...typeof input.ttlHours === "number" ? { ttlHours: input.ttlHours } : {},
17373
16755
  ...typeof input.browserMode === "string" && WORKFLOW_BROWSER_MODES.has(input.browserMode) ? { browserMode: input.browserMode } : {},
17374
16756
  ...typeof input.useCookies === "boolean" ? { useCookies: input.useCookies } : {},
@@ -17764,9 +17146,10 @@ var buildInspiredesignMeta = (runtime, workflowInput, references, failures, foll
17764
17146
  contractScope: followthrough.contractScope
17765
17147
  };
17766
17148
  };
17767
- var inferBrandFromContent = (content) => {
17149
+ var inferBrandFromContent = (content, productUrl) => {
17768
17150
  const normalized = normalizePlainText(content);
17769
17151
  if (!normalized) return void 0;
17152
+ const isEbayProduct = productUrl ? resolveShoppingProviderIdForUrl(productUrl) === "shopping/ebay" : false;
17770
17153
  const bestBuyTitle = inferBestBuyTitleFromContent(normalized);
17771
17154
  const bestBuyBrand = inferBestBuyBrandFromTitle(bestBuyTitle) ?? extractBrandFromTitle(bestBuyTitle);
17772
17155
  if (bestBuyBrand) {
@@ -17784,6 +17167,14 @@ var inferBrandFromContent = (content) => {
17784
17167
  if (productIdentifiersBrandMatch?.[1]) {
17785
17168
  return productIdentifiersBrandMatch[1].trim();
17786
17169
  }
17170
+ const ebayTitle = inferEbayTitleFromContent(normalized);
17171
+ const ebayBrand = inferEbayBrandFromTitle(ebayTitle);
17172
+ if (ebayBrand) {
17173
+ return ebayBrand;
17174
+ }
17175
+ if (isEbayProduct) {
17176
+ return void 0;
17177
+ }
17787
17178
  const brandMatch = /\bBrand ([A-Z][A-Za-z0-9&+' -]{1,60})\b/i.exec(normalized);
17788
17179
  if (brandMatch?.[1]) {
17789
17180
  return brandMatch[1].trim();
@@ -17803,13 +17194,69 @@ var inferBestBuyTitleFromContent = (normalized) => {
17803
17194
  }
17804
17195
  return candidate;
17805
17196
  };
17806
- var inferTitleFromContent = (content) => {
17197
+ var inferEbayTitleFromContent = (normalized) => {
17198
+ const patterns = [
17199
+ /\bExpand Cart Loading\.\.\.\s+(.+?)(?:\s+for sale online\s*\|\s*eBay)?\s+Condition:/i,
17200
+ /\bBuy It Now\s+(.+?)\s+Sign in to check out\b/i
17201
+ ];
17202
+ for (const pattern of patterns) {
17203
+ const candidate = stripMarketplaceTitleFraming(normalizePlainText(pattern.exec(normalized)?.[1]), "https://www.ebay.com");
17204
+ if (candidate && candidate.length >= 20 && candidate.length <= 180 && !candidate.endsWith("...") && !LOOKS_LIKE_URL_RE.test(candidate)) {
17205
+ return candidate;
17206
+ }
17207
+ }
17208
+ return void 0;
17209
+ };
17210
+ var inferKnownEbayBrandFromTitle = (title) => {
17211
+ const normalized = title.toLowerCase();
17212
+ return EBAY_KNOWN_MULTI_TOKEN_BRANDS.find((brand) => {
17213
+ const candidate = brand.toLowerCase();
17214
+ return normalized === candidate || normalized.startsWith(`${candidate} `);
17215
+ });
17216
+ };
17217
+ var inferKnownSingleEbayBrandFromTitle = (title) => {
17218
+ const firstToken = /^([A-Za-z][A-Za-z0-9&+']{1,30})(?:\s|$)/.exec(title)?.[1];
17219
+ return firstToken ? EBAY_KNOWN_SINGLE_TOKEN_BRANDS.get(firstToken.toLowerCase()) : void 0;
17220
+ };
17221
+ var inferEbayBrandFromTitle = (title) => {
17222
+ const framed = normalizePlainText(title).replace(/\s+[-|]\s+[^-|]+$/i, "").trim();
17223
+ const knownBrand = inferKnownEbayBrandFromTitle(framed);
17224
+ if (knownBrand) return knownBrand;
17225
+ const cleaned = framed.replace(EBAY_TITLE_BRAND_PREFIX_RE, "");
17226
+ const prefixedBrand = inferKnownEbayBrandFromTitle(cleaned);
17227
+ if (prefixedBrand) return prefixedBrand;
17228
+ const singleTokenBrand = inferKnownSingleEbayBrandFromTitle(cleaned);
17229
+ if (singleTokenBrand) return singleTokenBrand;
17230
+ const exactBrand = /^([A-Z][A-Za-z0-9&+']{1,30})$/.exec(cleaned)?.[1];
17231
+ if (exactBrand && EBAY_KNOWN_SINGLE_TOKEN_BRANDS.has(exactBrand.toLowerCase())) {
17232
+ return exactBrand;
17233
+ }
17234
+ const match = /^([A-Z][A-Za-z0-9&+']{1,30})\s+([A-Z0-9][A-Za-z0-9&+'().-]+|AirPods)\b/.exec(cleaned);
17235
+ const brand = match?.[1]?.trim();
17236
+ if (!brand || EBAY_TITLE_BRAND_STOP_WORDS.has(brand.toLowerCase())) {
17237
+ return void 0;
17238
+ }
17239
+ if (EBAY_KNOWN_SINGLE_TOKEN_BRANDS.has(brand.toLowerCase())) {
17240
+ return EBAY_KNOWN_SINGLE_TOKEN_BRANDS.get(brand.toLowerCase());
17241
+ }
17242
+ if (!/[\d-]/.test(match?.[2] ?? "")) {
17243
+ return void 0;
17244
+ }
17245
+ return brand;
17246
+ };
17247
+ var inferTitleFromContent = (content, productUrl) => {
17807
17248
  const normalized = normalizePlainText(content);
17808
17249
  if (!normalized) return void 0;
17809
17250
  const bestBuyTitle = inferBestBuyTitleFromContent(normalized);
17810
17251
  if (bestBuyTitle) {
17811
17252
  return bestBuyTitle;
17812
17253
  }
17254
+ if (productUrl && resolveShoppingProviderIdForUrl(productUrl) === "shopping/ebay") {
17255
+ const ebayTitle = inferEbayTitleFromContent(normalized);
17256
+ if (ebayTitle) {
17257
+ return ebayTitle;
17258
+ }
17259
+ }
17813
17260
  const storeMatch = /\bVisit the [A-Z][A-Za-z0-9&+' -]{1,60} Store\s+(.+?)(?=\s+(?:Brand [A-Z]|About this item|Key item features|Current price is|Actual Color|[0-9]+(?:\.[0-9]+)? stars out of|Best seller\b))/i.exec(normalized);
17814
17261
  const candidate = normalizePlainText(storeMatch?.[1]);
17815
17262
  if (!candidate || candidate.length < 20 || LOOKS_LIKE_URL_RE.test(candidate)) {
@@ -17822,6 +17269,11 @@ var sanitizeProductBrandCandidate = (candidate, productUrl) => {
17822
17269
  if (!normalized) return void 0;
17823
17270
  try {
17824
17271
  const host = new URL(productUrl).hostname.toLowerCase();
17272
+ const hostBrand = normalizePlainText(inferBrandFromUrl(productUrl)).replace(/\.com\b/gi, "").toLowerCase();
17273
+ const candidateBrand = normalized.replace(/\.com\b/gi, "").toLowerCase();
17274
+ if (host.includes("ebay.") && hostBrand && candidateBrand === hostBrand) {
17275
+ return void 0;
17276
+ }
17825
17277
  if (!host.includes("walmart.")) {
17826
17278
  return normalized;
17827
17279
  }
@@ -17966,6 +17418,17 @@ var inferHostDefaultCurrency = (productUrl) => {
17966
17418
  return void 0;
17967
17419
  }
17968
17420
  };
17421
+ var extractProductBrandFromTitle = (title, productUrl) => {
17422
+ try {
17423
+ const host = new URL(productUrl).hostname.toLowerCase();
17424
+ if (host.includes("ebay.")) {
17425
+ return inferEbayBrandFromTitle(title);
17426
+ }
17427
+ } catch {
17428
+ return extractBrandFromTitle(title);
17429
+ }
17430
+ return extractBrandFromTitle(title);
17431
+ };
17969
17432
  var shouldSuppressMarketplacePrice = (record, productUrl, price) => {
17970
17433
  if (price.amount <= 0) return false;
17971
17434
  const expectedCurrency = inferHostDefaultCurrency(productUrl);
@@ -18090,12 +17553,12 @@ var resolveProductBrand = (record, productUrl, refreshedBrand) => {
18090
17553
  const nestedProvider = nested && typeof nested === "object" && !Array.isArray(nested) ? nested.provider : void 0;
18091
17554
  const providerBrand = typeof nestedProvider === "string" ? SHOPPING_PROVIDER_PROFILES.find((entry) => entry.id === nestedProvider)?.displayName : void 0;
18092
17555
  const candidates = [
18093
- inferBrandFromContent(record.content),
17556
+ inferBrandFromContent(record.content, productUrl),
18094
17557
  rejectRetailerBrand(refreshedBrand),
18095
17558
  rejectRetailerBrand(typeof record.attributes.brand === "string" ? record.attributes.brand : void 0),
18096
17559
  rejectRetailerBrand(typeof record.attributes.site_name === "string" ? record.attributes.site_name : void 0),
18097
17560
  rejectRetailerBrand(providerBrand && providerBrand !== "Others" ? providerBrand : void 0),
18098
- extractBrandFromTitle(record.title),
17561
+ extractProductBrandFromTitle(record.title, productUrl),
18099
17562
  inferBrandFromUrl(productUrl)
18100
17563
  ].map((entry) => sanitizeProductBrandCandidate(entry, productUrl)).filter(Boolean);
18101
17564
  return candidates[0] || "unknown";
@@ -18105,7 +17568,7 @@ var resolveProductTitle = (record, productUrl, brand, refreshedTitle) => {
18105
17568
  const nestedTitle = nested && typeof nested === "object" && !Array.isArray(nested) ? nested.title : void 0;
18106
17569
  const candidates = [
18107
17570
  refreshedTitle,
18108
- inferTitleFromContent(record.content),
17571
+ inferTitleFromContent(record.content, productUrl),
18109
17572
  record.title,
18110
17573
  typeof nestedTitle === "string" ? nestedTitle : void 0,
18111
17574
  typeof record.attributes.description === "string" ? record.attributes.description.split(/(?<=[.!?])\s+/)[0] : void 0,
@@ -18154,13 +17617,31 @@ var resolveShoppingSourceForUrl = (url) => {
18154
17617
  return "web";
18155
17618
  }
18156
17619
  };
17620
+ var RESEARCH_REJECTED_CANDIDATE_LIMIT = 25;
17621
+ var isResearchPrivacyPreferenceShell = (content) => {
17622
+ const matchIndex = content.slice(0, 400).search(RESEARCH_PRIVACY_PREFERENCE_SHELL_RE);
17623
+ return matchIndex >= 0 && matchIndex <= 80 && !RESEARCH_PRIVACY_RECOVERED_CONTENT_RE.test(content);
17624
+ };
17625
+ var classifyResearchDeadEndUrl = (value) => {
17626
+ if (!value) return null;
17627
+ return classifyResearchDestinationRejection(value);
17628
+ };
17629
+ var isResearchLoginShellRecord = (args) => {
17630
+ if (args.url.includes("/login")) {
17631
+ return true;
17632
+ }
17633
+ if (!RESEARCH_LOGIN_SHELL_RE.test(args.combined)) {
17634
+ return false;
17635
+ }
17636
+ return args.content.length <= RESEARCH_LOGIN_SHELL_MAX_CONTENT_CHARS || RESEARCH_LOGIN_REQUIRED_RE.test(args.combined);
17637
+ };
18157
17638
  var classifyResearchShellRecord = (record) => {
18158
17639
  const retrievalPath = typeof record.attributes.retrievalPath === "string" ? record.attributes.retrievalPath : "";
18159
17640
  const url = typeof record.url === "string" ? record.url.trim().toLowerCase() : "";
18160
17641
  const title = normalizePlainText(record.title).toLowerCase();
18161
17642
  const content = normalizePlainText(record.content).toLowerCase();
18162
17643
  const combined = `${title} ${content}`.trim();
18163
- if (RESEARCH_LOGIN_SHELL_RE.test(combined) || url.includes("/login")) {
17644
+ if (isResearchLoginShellRecord({ url, combined, content })) {
18164
17645
  return "login_shell";
18165
17646
  }
18166
17647
  if (RESEARCH_JS_REQUIRED_RE.test(combined)) {
@@ -18169,6 +17650,13 @@ var classifyResearchShellRecord = (record) => {
18169
17650
  if (RESEARCH_NOT_FOUND_SHELL_RE.test(combined)) {
18170
17651
  return "not_found_shell";
18171
17652
  }
17653
+ if (isResearchPrivacyPreferenceShell(content)) {
17654
+ return "privacy_preference_shell";
17655
+ }
17656
+ const deadEndUrlReason = classifyResearchDeadEndUrl(url);
17657
+ if (deadEndUrlReason) {
17658
+ return deadEndUrlReason;
17659
+ }
18172
17660
  if (!retrievalPath) {
18173
17661
  return null;
18174
17662
  }
@@ -18191,18 +17679,47 @@ var classifyResearchShellRecord = (record) => {
18191
17679
  };
18192
17680
  var sanitizeResearchRecords = (records) => {
18193
17681
  const reasonDistribution = {};
17682
+ const rejectedCandidates = [];
18194
17683
  const sanitizedRecords = records.filter((record) => {
18195
17684
  const reason = classifyResearchShellRecord(record);
18196
17685
  if (!reason) return true;
18197
17686
  reasonDistribution[reason] = (reasonDistribution[reason] ?? 0) + 1;
17687
+ if (rejectedCandidates.length < RESEARCH_REJECTED_CANDIDATE_LIMIT) {
17688
+ const retrievalPath = typeof record.attributes.retrievalPath === "string" ? record.attributes.retrievalPath : void 0;
17689
+ rejectedCandidates.push({
17690
+ provider: record.provider,
17691
+ source: record.source,
17692
+ reason,
17693
+ replacement_status: "rejected_before_synthesis",
17694
+ ...retrievalPath ? { retrievalPath } : {},
17695
+ ...record.title ? { title: record.title } : {},
17696
+ ...record.url ? { url: record.url } : {}
17697
+ });
17698
+ }
18198
17699
  return false;
18199
17700
  });
18200
17701
  return {
18201
17702
  records: sanitizedRecords,
18202
17703
  sanitizedCount: records.length - sanitizedRecords.length,
18203
- reasonDistribution
17704
+ reasonDistribution,
17705
+ rejectedCandidates
17706
+ };
17707
+ };
17708
+ var rejectedCandidateFromFailure = (failure) => {
17709
+ const details = failure.error.details ?? {};
17710
+ if (details.fallbackOutputReason !== "research_dead_end_shell") return null;
17711
+ const retrievalPath = typeof details.retrievalPath === "string" ? details.retrievalPath : void 0;
17712
+ const url = typeof details.url === "string" ? details.url : void 0;
17713
+ return {
17714
+ provider: failure.provider,
17715
+ source: failure.source,
17716
+ reason: "research_dead_end_shell",
17717
+ replacement_status: "rejected_before_synthesis",
17718
+ ...retrievalPath ? { retrievalPath } : {},
17719
+ ...url ? { url } : {}
18204
17720
  };
18205
17721
  };
17722
+ var rejectedCandidatesFromFailures = (failures) => failures.map(rejectedCandidateFromFailure).filter((candidate) => candidate !== null);
18206
17723
  var isValidHttpUrl3 = (url) => {
18207
17724
  try {
18208
17725
  const parsed = new URL(url.trim());
@@ -18228,6 +17745,13 @@ var classifyInvalidProductTarget = (record) => {
18228
17745
  message: "Product target appears to be a not-found page"
18229
17746
  };
18230
17747
  }
17748
+ const providerId = resolveShoppingProviderIdForUrl(record.url ?? "");
17749
+ if (providerId === "shopping/bestbuy" && combined.toLowerCase().includes("something went wrong") && BESTBUY_PDP_ERROR_SHELL_RE.test(combined)) {
17750
+ return {
17751
+ reason: "provider_error_shell",
17752
+ message: "Best Buy product target returned a generic error shell"
17753
+ };
17754
+ }
18231
17755
  return null;
18232
17756
  };
18233
17757
  var runResearchWorkflow = async (runtime, input) => {
@@ -18235,7 +17759,9 @@ var runResearchWorkflow = async (runtime, input) => {
18235
17759
  if (envelope.kind !== "research") {
18236
17760
  throw new Error(`Research workflow envelope kind mismatch. Expected research but received ${envelope.kind}.`);
18237
17761
  }
18238
- const workflowInput = envelope.input;
17762
+ const rawWorkflowInput = envelope.input;
17763
+ const artifactRoot = resolveWorkflowArtifactRoot(rawWorkflowInput.outputDir);
17764
+ const workflowInput = { ...rawWorkflowInput, outputDir: artifactRoot };
18239
17765
  let trace = [
18240
17766
  ...envelope.trace ?? [],
18241
17767
  {
@@ -18291,21 +17817,23 @@ var runResearchWorkflow = async (runtime, input) => {
18291
17817
  }
18292
17818
  });
18293
17819
  const excludedProviderSet = new Set(plan.compiled.autoExcludedProviders);
18294
- const mergedRecords = removeExcludedProviders(
18295
- [
18296
- ...execution.searchRuns.flatMap((run) => run.result.records),
18297
- ...execution.followUpRuns.flatMap((run) => run.result.records)
18298
- ],
18299
- excludedProviderSet
18300
- );
17820
+ const rawRecords = [
17821
+ ...execution.searchRuns.flatMap((run) => run.result.records),
17822
+ ...execution.followUpRuns.flatMap((run) => run.result.records)
17823
+ ];
17824
+ const rawFailures = [
17825
+ ...execution.searchRuns.flatMap((run) => run.result.failures),
17826
+ ...execution.followUpRuns.flatMap((run) => run.result.failures)
17827
+ ];
17828
+ const mergedRecords = removeExcludedProviders(rawRecords, excludedProviderSet);
18301
17829
  const sanitizedRecords = sanitizeResearchRecords(mergedRecords);
18302
- const mergedFailures = removeExcludedProviders(
18303
- [
18304
- ...execution.searchRuns.flatMap((run) => run.result.failures),
18305
- ...execution.followUpRuns.flatMap((run) => run.result.failures)
18306
- ],
18307
- excludedProviderSet
18308
- );
17830
+ const mergedFailures = removeExcludedProviders(rawFailures, excludedProviderSet);
17831
+ const rejectedFailureCandidates = rejectedCandidatesFromFailures(mergedFailures);
17832
+ const rejectedCandidates = [
17833
+ ...sanitizedRecords.rejectedCandidates,
17834
+ ...rejectedFailureCandidates
17835
+ ].slice(0, RESEARCH_REJECTED_CANDIDATE_LIMIT);
17836
+ const rejectedCandidateCount2 = sanitizedRecords.sanitizedCount + rejectedFailureCandidates.length;
18309
17837
  const reasonCodeDistribution = summarizeReasonCodeDistribution(mergedFailures);
18310
17838
  const transcriptStrategyFailures = summarizeTranscriptStrategyFailures(mergedFailures);
18311
17839
  const evaluationNow = /* @__PURE__ */ new Date();
@@ -18313,8 +17841,8 @@ var runResearchWorkflow = async (runtime, input) => {
18313
17841
  const enriched = enrichResearchRecords(withinTimebox, plan.compiled.timebox, evaluationNow);
18314
17842
  const deduped = dedupeResearchRecords(enriched);
18315
17843
  const ranked = rankResearchRecords(deduped);
18316
- const noUsableResearchRecords = mergedRecords.length > 0 && mergedFailures.length === 0 && ranked.length === 0;
18317
17844
  const cookieDiagnostics = summarizeCookieDiagnostics(mergedFailures, mergedRecords);
17845
+ const challengeOrchestration = summarizeChallengeOrchestration(mergedFailures, mergedRecords);
18318
17846
  const transcriptStrategyDetailDistribution = summarizeTranscriptStrategyDetailDistribution(ranked);
18319
17847
  const transcriptDurability = summarizeTranscriptDurability(ranked, mergedFailures);
18320
17848
  const antiBotPressure = summarizeAntiBotPressure(mergedFailures);
@@ -18322,16 +17850,16 @@ var runResearchWorkflow = async (runtime, input) => {
18322
17850
  ...plan.compiled.timebox,
18323
17851
  to: new Date(Math.max(new Date(plan.compiled.timebox.to).getTime(), evaluationNow.getTime())).toISOString()
18324
17852
  } : plan.compiled.timebox;
18325
- if (noUsableResearchRecords && sanitizedRecords.records.length === 0) {
17853
+ if (mergedRecords.length > 0 && sanitizedRecords.records.length === 0) {
18326
17854
  const sanitizedReasons = Object.entries(sanitizedRecords.reasonDistribution).map(([reason, count]) => `${reason}:${count}`).join(",");
18327
17855
  throw new Error(
18328
17856
  `Research workflow produced only shell records and no usable results (${sanitizedReasons || "sanitized"}).`
18329
17857
  );
18330
17858
  }
18331
- if (noUsableResearchRecords && sanitizedRecords.records.length > 0 && withinTimebox.length === 0) {
17859
+ if (sanitizedRecords.records.length > 0 && withinTimebox.length === 0) {
18332
17860
  throw new Error("Research workflow produced no usable in-timebox results after sanitization.");
18333
17861
  }
18334
- if (noUsableResearchRecords) {
17862
+ if (ranked.length === 0) {
18335
17863
  throw new Error("Research workflow produced no usable results after post-processing.");
18336
17864
  }
18337
17865
  const primaryConstraintFailures = selectResearchPrimaryConstraintFailures(mergedFailures);
@@ -18345,8 +17873,10 @@ var runResearchWorkflow = async (runtime, input) => {
18345
17873
  metrics: {
18346
17874
  total_records: mergedRecords.length,
18347
17875
  sanitized_records: sanitizedRecords.sanitizedCount,
17876
+ rejected_candidate_count: rejectedCandidateCount2,
18348
17877
  sanitized_reason_distribution: sanitizedRecords.reasonDistribution,
18349
17878
  sanitizedReasonDistribution: sanitizedRecords.reasonDistribution,
17879
+ rejected_candidate_sample_size: rejectedCandidates.length,
18350
17880
  within_timebox: withinTimebox.length,
18351
17881
  final_records: ranked.length,
18352
17882
  failed_sources: execution.searchRuns.filter((run) => !run.result.ok).map((run) => run.source),
@@ -18361,15 +17891,22 @@ var runResearchWorkflow = async (runtime, input) => {
18361
17891
  transcriptDurability,
18362
17892
  cookie_diagnostics: cookieDiagnostics,
18363
17893
  cookieDiagnostics,
17894
+ challenge_orchestration: challengeOrchestration,
17895
+ challengeOrchestration,
18364
17896
  anti_bot_pressure: antiBotPressure,
18365
17897
  antiBotPressure
18366
17898
  },
18367
17899
  failures: mergedFailures,
17900
+ rejected_candidates: rejectedCandidates,
17901
+ rejectedCandidates,
18368
17902
  alerts: buildWorkflowAlerts(runtime, mergedFailures)
18369
17903
  }, primaryConstraintFailures);
18370
17904
  const handoff = buildResearchSuccessHandoff({
18371
17905
  topic: plan.compiled.topic,
18372
- browserMode: workflowInput.browserMode
17906
+ browserMode: workflowInput.browserMode,
17907
+ failures: mergedFailures,
17908
+ cookieDiagnostics,
17909
+ challengeOrchestration
18373
17910
  });
18374
17911
  const responseMeta = withFollowthroughMeta(meta, handoff);
18375
17912
  const rendered = renderResearch({
@@ -18380,7 +17917,7 @@ var runResearchWorkflow = async (runtime, input) => {
18380
17917
  });
18381
17918
  const bundle = await createArtifactBundle({
18382
17919
  namespace: "research",
18383
- outputDir: workflowInput.outputDir,
17920
+ outputDir: artifactRoot,
18384
17921
  ttlHours: workflowInput.ttlHours,
18385
17922
  files: rendered.files
18386
17923
  });
@@ -18388,7 +17925,7 @@ var runResearchWorkflow = async (runtime, input) => {
18388
17925
  return {
18389
17926
  ...rendered.response,
18390
17927
  ...handoff,
18391
- path: bundle.basePath,
17928
+ artifact_path: bundle.basePath,
18392
17929
  records: ranked,
18393
17930
  meta: {
18394
17931
  ...responseMeta,
@@ -18412,7 +17949,9 @@ var runShoppingWorkflow = async (runtime, input) => {
18412
17949
  if (envelope.kind !== "shopping") {
18413
17950
  throw new Error(`Shopping workflow envelope kind mismatch. Expected shopping but received ${envelope.kind}.`);
18414
17951
  }
18415
- const workflowInput = envelope.input;
17952
+ const rawWorkflowInput = envelope.input;
17953
+ const artifactRoot = resolveWorkflowArtifactRoot(rawWorkflowInput.outputDir);
17954
+ const workflowInput = { ...rawWorkflowInput, outputDir: artifactRoot };
18416
17955
  const remainingTimeoutMs = createRemainingTimeoutResolver(workflowInput.timeoutMs);
18417
17956
  let trace = [
18418
17957
  ...envelope.trace ?? [],
@@ -18566,7 +18105,7 @@ var runShoppingWorkflow = async (runtime, input) => {
18566
18105
  });
18567
18106
  const bundle = await createArtifactBundle({
18568
18107
  namespace: "shopping",
18569
- outputDir: workflowInput.outputDir,
18108
+ outputDir: artifactRoot,
18570
18109
  ttlHours: workflowInput.ttlHours,
18571
18110
  files: rendered.files
18572
18111
  });
@@ -18574,7 +18113,7 @@ var runShoppingWorkflow = async (runtime, input) => {
18574
18113
  return {
18575
18114
  ...rendered.response,
18576
18115
  ...handoff,
18577
- path: bundle.basePath,
18116
+ artifact_path: bundle.basePath,
18578
18117
  offers,
18579
18118
  meta: {
18580
18119
  ...responseMeta,
@@ -18594,7 +18133,9 @@ var runShoppingWorkflow = async (runtime, input) => {
18594
18133
  };
18595
18134
  };
18596
18135
  var runInspiredesignWorkflow = async (runtime, input, options = {}) => {
18597
- const { envelope, workflowInput } = buildInspiredesignEnvelope(input);
18136
+ const { envelope, workflowInput: rawWorkflowInput } = buildInspiredesignEnvelope(input);
18137
+ const artifactRoot = resolveWorkflowArtifactRoot(rawWorkflowInput.outputDir);
18138
+ const workflowInput = { ...rawWorkflowInput, outputDir: artifactRoot };
18598
18139
  const remainingTimeoutMs = createRemainingTimeoutResolver(workflowInput.timeoutMs);
18599
18140
  let trace = appendWorkflowTrace(envelope.trace ?? [], "compile", "compile_started", {
18600
18141
  kind: "inspiredesign"
@@ -18668,14 +18209,14 @@ var runInspiredesignWorkflow = async (runtime, input, options = {}) => {
18668
18209
  });
18669
18210
  const bundle = await createArtifactBundle({
18670
18211
  namespace: "inspiredesign",
18671
- outputDir: workflowInput.outputDir,
18212
+ outputDir: artifactRoot,
18672
18213
  ttlHours: workflowInput.ttlHours,
18673
18214
  files: rendered.files
18674
18215
  });
18675
18216
  if (workflowInput.mode === "path") {
18676
18217
  return {
18677
18218
  ...rendered.response,
18678
- path: bundle.basePath,
18219
+ artifact_path: bundle.basePath,
18679
18220
  meta: {
18680
18221
  ...meta,
18681
18222
  artifact_manifest: bundle.manifest
@@ -18724,7 +18265,9 @@ var runProductVideoWorkflow = async (runtime, input, options = {}) => {
18724
18265
  }
18725
18266
  }
18726
18267
  ];
18727
- const workflowInput = plan.input;
18268
+ const rawWorkflowInput = plan.input;
18269
+ const productVideoArtifactRoot = resolveWorkflowArtifactRoot(rawWorkflowInput.output_dir);
18270
+ const workflowInput = { ...rawWorkflowInput, output_dir: productVideoArtifactRoot };
18728
18271
  const includeScreenshots = plan.compiled.includeScreenshots;
18729
18272
  const includeAllImages = plan.compiled.includeAllImages;
18730
18273
  const includeCopy = plan.compiled.includeCopy;
@@ -18804,6 +18347,8 @@ var runProductVideoWorkflow = async (runtime, input, options = {}) => {
18804
18347
  query: resolveStep.input.product_name,
18805
18348
  providers: providerHint ? [providerHint] : void 0,
18806
18349
  mode: "json",
18350
+ outputDir: productVideoArtifactRoot,
18351
+ ttlHours: workflowInput.ttl_hours,
18807
18352
  ...timeoutOptions,
18808
18353
  browserMode: workflowInput.browserMode,
18809
18354
  useCookies: workflowInput.useCookies,
@@ -19025,11 +18570,10 @@ var runProductVideoWorkflow = async (runtime, input, options = {}) => {
19025
18570
  files: files.length
19026
18571
  });
19027
18572
  const bundle = await createArtifactBundle({
19028
- namespace: "product-assets",
19029
- outputDir: workflowInput.output_dir,
18573
+ namespace: "product-video",
18574
+ outputDir: productVideoArtifactRoot,
19030
18575
  ttlHours: workflowInput.ttl_hours,
19031
- files,
19032
- manifestFileName: "bundle-manifest.json"
18576
+ files
19033
18577
  });
19034
18578
  const reasonCodeDistribution = summarizeReasonCodeDistribution(details.failures);
19035
18579
  const transcriptStrategyFailures = summarizeTranscriptStrategyFailures(details.failures);
@@ -19069,7 +18613,7 @@ var runProductVideoWorkflow = async (runtime, input, options = {}) => {
19069
18613
  }, handoff);
19070
18614
  return {
19071
18615
  ...handoff,
19072
- path: bundle.basePath,
18616
+ artifact_path: bundle.basePath,
19073
18617
  manifest: manifestPayload,
19074
18618
  product: productPayload,
19075
18619
  pricing,
@@ -19123,7 +18667,10 @@ var buildSocialDefaultTraversal = (platform, options) => {
19123
18667
  ...existing ?? {}
19124
18668
  };
19125
18669
  };
19126
- var shouldRecoverSocialDocumentIssue = (platform, operation, issue) => {
18670
+ var shouldRecoverSocialDocumentIssue = (platform, operation, issue, context) => {
18671
+ if (isResearchSearchDiscoveryRun(operation, context)) {
18672
+ return false;
18673
+ }
19127
18674
  if (SOCIAL_BROWSER_RECOVERY_REASON_CODES.has(issue.reasonCode)) {
19128
18675
  return true;
19129
18676
  }
@@ -19132,7 +18679,7 @@ var shouldRecoverSocialDocumentIssue = (platform, operation, issue) => {
19132
18679
  }
19133
18680
  return operation === "search" && SEARCH_RENDER_RECOVERY_SOCIAL_PLATFORMS.has(platform) && issue.reasonCode === "env_limited" && issue.constraint?.kind === "render_required";
19134
18681
  };
19135
- var isJsonRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
18682
+ var isJsonRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
19136
18683
  var unwrapWorkflowResumeEnvelope = (kind, input) => {
19137
18684
  if (!isWorkflowResumePayload(input)) {
19138
18685
  throw new ProviderRuntimeError(
@@ -19185,10 +18732,10 @@ var Semaphore = class {
19185
18732
  this.active += 1;
19186
18733
  return;
19187
18734
  }
19188
- await new Promise((resolve2) => {
18735
+ await new Promise((resolve) => {
19189
18736
  this.queue.push(() => {
19190
18737
  this.active += 1;
19191
- resolve2();
18738
+ resolve();
19192
18739
  });
19193
18740
  });
19194
18741
  }
@@ -19367,7 +18914,7 @@ var isBlockedRedditFallbackLink = (link) => {
19367
18914
  return false;
19368
18915
  }
19369
18916
  };
19370
- var shouldOwnerReviewCommunityRedditSearchFallback = (input) => input.providerId === "community/default" && input.source === "community" && input.operation === "search" && isFirstPartySocialSearchRoute("reddit", input.url) && input.extracted.links.length > 0 && input.extracted.links.every(isBlockedRedditFallbackLink);
18917
+ var shouldOwnerReviewCommunityRedditSearchFallback = (input) => input.providerId === "community/default" && input.source === "community" && input.operation === "search" && isFirstPartySocialSearchRoute("reddit", input.url) && (hasUsableCommunityFallbackSearchContent(input.extracted.text) || input.extracted.links.length > 0 && input.extracted.links.every(isBlockedRedditFallbackLink));
19371
18918
  var shouldReturnCompletedFallbackForOwnerReview = (args) => args.document.browserFallback !== void 0 && args.ownerReview?.({
19372
18919
  providerId: args.providerId,
19373
18920
  source: args.source,
@@ -19440,6 +18987,8 @@ var describeDefaultFetchedIssue = (document) => {
19440
18987
  };
19441
18988
  };
19442
18989
  var shouldRecoverDefaultFetchedIssue = (issue) => issue.reasonCode === "challenge_detected" || issue.reasonCode === "token_required" || issue.reasonCode === "env_limited" && !!issue.constraint;
18990
+ var isUsableCommunityRedditSearchFallback = (args) => args.source === "community" && args.operation === "search" && args.document.browserFallback !== void 0 && isFirstPartySocialSearchRoute("reddit", args.document.url) && hasUsableCommunityFallbackSearchContent(args.pageMessage);
18991
+ var isResearchSearchDiscoveryRun = (operation, context) => operation === "search" && context.suspendedIntent?.kind === "workflow.research";
19443
18992
  var toDefaultFetchedIssueError = (args) => {
19444
18993
  const reasonCode = args.issueDetails.issue?.reasonCode ?? "env_limited";
19445
18994
  return new ProviderRuntimeError(
@@ -19464,6 +19013,14 @@ var resolveDefaultFallbackDocumentIfNeeded = async (args) => {
19464
19013
  if (!initialIssue) {
19465
19014
  return { document: currentDocument, ...described };
19466
19015
  }
19016
+ if (isUsableCommunityRedditSearchFallback({
19017
+ source: args.source,
19018
+ operation: args.operation,
19019
+ document: currentDocument,
19020
+ pageMessage: described.pageMessage
19021
+ })) {
19022
+ return { document: currentDocument, ...described, issue: null };
19023
+ }
19467
19024
  if (shouldReturnCompletedFallbackForOwnerReview({ ...args, document: currentDocument })) {
19468
19025
  return { document: currentDocument, ...described };
19469
19026
  }
@@ -19506,6 +19063,14 @@ var resolveDefaultFallbackDocumentIfNeeded = async (args) => {
19506
19063
  ownerReview: args.ownerReview
19507
19064
  });
19508
19065
  described = describeDefaultFetchedIssue(currentDocument);
19066
+ if (isUsableCommunityRedditSearchFallback({
19067
+ source: args.source,
19068
+ operation: args.operation,
19069
+ document: currentDocument,
19070
+ pageMessage: described.pageMessage
19071
+ })) {
19072
+ return { document: currentDocument, ...described, issue: null };
19073
+ }
19509
19074
  if (shouldReturnCompletedFallbackForOwnerReview({ ...args, document: currentDocument })) {
19510
19075
  return { document: currentDocument, ...described };
19511
19076
  }
@@ -19522,16 +19087,32 @@ var resolveDefaultFallbackDocumentIfNeeded = async (args) => {
19522
19087
  };
19523
19088
  var COMMUNITY_SEARCH_LINK_SCAN_MULTIPLIER = 4;
19524
19089
  var MIN_COMMUNITY_SEARCH_LINK_SCAN = 12;
19525
- var resolveCommunitySearchLinks = (document, limit) => {
19090
+ var resolveCommunitySearchLinks = (document, limit, researchContext) => {
19526
19091
  const scanLimit = Math.max(limit * COMMUNITY_SEARCH_LINK_SCAN_MULTIPLIER, MIN_COMMUNITY_SEARCH_LINK_SCAN);
19527
19092
  const links = prioritizeSocialSearchLinks(
19528
19093
  "reddit",
19529
19094
  document.url,
19530
19095
  dedupeLinks2(document.links, document.url, scanLimit)
19531
19096
  );
19532
- return links.filter((url) => isAllowedSocialSearchExpansionUrl("reddit", url)).slice(0, limit);
19097
+ return links.filter((url) => researchContext ? isLikelyResearchDestinationUrl(url) : isLikelyDocumentUrl(url)).filter((url) => isAllowedSocialSearchExpansionUrl("reddit", url)).slice(0, limit);
19098
+ };
19099
+ var shouldRejectBlockedCommunityFallback = (document, links, pageMessage) => document.browserFallback !== void 0 && isFirstPartySocialSearchRoute("reddit", document.url) && links.length === 0 && !hasUsableCommunityFallbackSearchContent(pageMessage);
19100
+ var COMMUNITY_FALLBACK_SEARCH_MIN_CHARS = 120;
19101
+ var COMMUNITY_FALLBACK_SEARCH_EVIDENCE_RE = [
19102
+ /\bshowing results for\b/i,
19103
+ /\bsearch for\b/i,
19104
+ /\banswers?\b/i,
19105
+ /\bsources?:\s+/i,
19106
+ /\br\/[a-z0-9_]+\b/i
19107
+ ];
19108
+ var hasUsableCommunityFallbackSearchContent = (pageMessage) => {
19109
+ const trimmed = pageMessage.trim();
19110
+ if (trimmed.length < COMMUNITY_FALLBACK_SEARCH_MIN_CHARS) {
19111
+ return false;
19112
+ }
19113
+ const evidenceCount = COMMUNITY_FALLBACK_SEARCH_EVIDENCE_RE.filter((pattern) => pattern.test(trimmed)).length;
19114
+ return evidenceCount >= 2;
19533
19115
  };
19534
- var shouldRejectBlockedCommunityFallback = (document, links) => document.browserFallback !== void 0 && isFirstPartySocialSearchRoute("reddit", document.url) && links.length === 0;
19535
19116
  var toCommunityFallbackSearchError = (args) => {
19536
19117
  const reasonCode = args.document.browserFallback?.reasonCode ?? "env_limited";
19537
19118
  return new ProviderRuntimeError(
@@ -19646,6 +19227,36 @@ var dedupeLinks2 = (links, baseUrl, limit) => {
19646
19227
  }
19647
19228
  return deduped;
19648
19229
  };
19230
+ var isUsableWebSearchResultUrl = (url, researchContext) => !isDuckDuckGoSearchShellUrl(url) && (researchContext ? isLikelyResearchDestinationUrl(url) : isLikelyDocumentUrl(url));
19231
+ var isResearchWorkflowContext = (context) => context.suspendedIntent?.kind === "workflow.research";
19232
+ var resolveWebSearchResultLinks = (document, limit, researchContext, includeDocumentUrl) => {
19233
+ const links = dedupeLinks2(document.links, document.url, limit * 2);
19234
+ const candidates = includeDocumentUrl ? [document.url, ...links] : links;
19235
+ const seen = /* @__PURE__ */ new Set();
19236
+ return candidates.filter((url) => {
19237
+ const keep = isUsableWebSearchResultUrl(url, researchContext) && !seen.has(url);
19238
+ if (keep) seen.add(url);
19239
+ return keep;
19240
+ }).slice(0, limit);
19241
+ };
19242
+ var toResearchDeadEndSearchError = (args) => new ProviderRuntimeError(
19243
+ "unavailable",
19244
+ `Research search resolved only dead-end pages for ${args.document.url}`,
19245
+ {
19246
+ provider: args.providerId,
19247
+ source: args.source,
19248
+ retryable: false,
19249
+ reasonCode: "policy_blocked",
19250
+ details: {
19251
+ status: args.document.status,
19252
+ url: args.document.url,
19253
+ ...args.document.browserFallback?.reasonCode ? { browserFallbackReasonCode: args.document.browserFallback.reasonCode } : {},
19254
+ retrievalPath: args.retrievalPath,
19255
+ fallbackOutputReason: "research_dead_end_shell",
19256
+ ...browserFallbackObservationDetails(args.document.browserFallback)
19257
+ }
19258
+ }
19259
+ );
19649
19260
  var RUNTIME_FALLBACK_ERROR_CODES = /* @__PURE__ */ new Set([
19650
19261
  "auth",
19651
19262
  "rate_limited",
@@ -19785,6 +19396,7 @@ var readResponseTextWithAbort = async (response, args) => {
19785
19396
  }
19786
19397
  };
19787
19398
  var fetchRuntimeDocumentWithFallback = async (args) => {
19399
+ const researchSearchDiscovery = args.context !== void 0 && isResearchSearchDiscoveryRun(args.operation, args.context);
19788
19400
  const fallbackPort = args.context?.browserFallbackPort ?? args.browserFallbackPort;
19789
19401
  const runtimePolicy = args.context?.runtimePolicy;
19790
19402
  const forcedBrowserTransport = runtimePolicy?.browser.forceTransport === true;
@@ -19851,6 +19463,9 @@ var fetchRuntimeDocumentWithFallback = async (args) => {
19851
19463
  if (args.recoverRuntimeErrors === false) {
19852
19464
  throw error;
19853
19465
  }
19466
+ if (researchSearchDiscovery) {
19467
+ throw error;
19468
+ }
19854
19469
  if (!fallbackPort) {
19855
19470
  throw error;
19856
19471
  }
@@ -20216,8 +19831,9 @@ var ProviderRuntime = class {
20216
19831
  };
20217
19832
  }
20218
19833
  async executeAll(providers, operation, input, trace, timeoutMs, selection, startedAt, tierMetadata, providerIds, runOptions = {}) {
20219
- const results = await Promise.all(
20220
- providers.map((provider) => this.invokeProvider(provider, operation, input, trace, timeoutMs, tierMetadata, runOptions))
19834
+ const results = await this.mapProvidersBounded(
19835
+ providers,
19836
+ (provider) => this.invokeProvider(provider, operation, input, trace, timeoutMs, tierMetadata, runOptions)
20221
19837
  );
20222
19838
  const records = [];
20223
19839
  const failures = [];
@@ -20260,8 +19876,9 @@ var ProviderRuntime = class {
20260
19876
  }
20261
19877
  });
20262
19878
  }
20263
- const fallbackResults = await Promise.all(
20264
- fallbackProviders.map((provider) => this.invokeProvider(provider, operation, input, trace, timeoutMs, fallbackTier, runOptions))
19879
+ const fallbackResults = await this.mapProvidersBounded(
19880
+ fallbackProviders,
19881
+ (provider) => this.invokeProvider(provider, operation, input, trace, timeoutMs, fallbackTier, runOptions)
20265
19882
  );
20266
19883
  for (const result of fallbackResults) {
20267
19884
  attempted += 1;
@@ -20308,6 +19925,9 @@ var ProviderRuntime = class {
20308
19925
  ...!ok && failures[0] ? { error: failures[0].error } : {}
20309
19926
  };
20310
19927
  }
19928
+ async mapProvidersBounded(providers, task) {
19929
+ return mapBounded(providers, this.budgets.concurrency.global, task);
19930
+ }
20311
19931
  async invokeProvider(provider, operation, input, trace, timeoutMs, tierMetadata, runOptions) {
20312
19932
  const startedAt = Date.now();
20313
19933
  const scopeKey = this.resolveScopeKey(provider.id, operation, input);
@@ -20691,7 +20311,7 @@ var ProviderRuntime = class {
20691
20311
  }
20692
20312
  async resumeSuspendedIntent(intent, options) {
20693
20313
  const input = intent.input;
20694
- if (!isJsonRecord7(input)) {
20314
+ if (!isJsonRecord8(input)) {
20695
20315
  throw new ProviderRuntimeError("invalid_input", "Suspended intent input is missing or malformed.", {
20696
20316
  retryable: false
20697
20317
  });
@@ -20779,7 +20399,7 @@ var ProviderRuntime = class {
20779
20399
  return providers.some((provider) => provider.source !== "web");
20780
20400
  }
20781
20401
  resolveScopeKey(providerId, operation, input) {
20782
- const extractHost2 = (value) => {
20402
+ const extractHost = (value) => {
20783
20403
  if (!value) return null;
20784
20404
  try {
20785
20405
  return new URL(value).hostname.toLowerCase();
@@ -20788,14 +20408,14 @@ var ProviderRuntime = class {
20788
20408
  }
20789
20409
  };
20790
20410
  if (operation === "fetch") {
20791
- return extractHost2(input.url) ?? providerId;
20411
+ return extractHost(input.url) ?? providerId;
20792
20412
  }
20793
20413
  if (operation === "crawl") {
20794
20414
  const first = input.seedUrls[0];
20795
- return extractHost2(first) ?? providerId;
20415
+ return extractHost(first) ?? providerId;
20796
20416
  }
20797
20417
  if (operation === "search") {
20798
- return extractHost2(input.query) ?? providerId;
20418
+ return extractHost(input.query) ?? providerId;
20799
20419
  }
20800
20420
  return providerId;
20801
20421
  }
@@ -20973,9 +20593,18 @@ var withDefaultWebOptions = (options, browserFallbackPort) => {
20973
20593
  browserFallbackPort
20974
20594
  });
20975
20595
  const limit = Math.max(1, Math.min(input.limit ?? 5, 10));
20976
- const links = dedupeLinks2(document.links, document.url, limit * 2).filter((url) => !isDuckDuckGoSearchShellUrl(url)).slice(0, limit);
20596
+ const researchContext = isResearchWorkflowContext(context);
20597
+ const links = resolveWebSearchResultLinks(document, limit, researchContext, researchContext || isHttpUrl7(query));
20977
20598
  const searchPath = isHttpUrl7(query) ? "web:search:url" : "web:search:index";
20978
20599
  if (links.length === 0) {
20600
+ if (researchContext && !isUsableWebSearchResultUrl(document.url, true)) {
20601
+ throw toResearchDeadEndSearchError({
20602
+ providerId,
20603
+ source: "web",
20604
+ document,
20605
+ retrievalPath: searchPath
20606
+ });
20607
+ }
20979
20608
  return [{
20980
20609
  url: document.url,
20981
20610
  title: document.url,
@@ -21018,6 +20647,14 @@ var withDefaultCommunityOptions = (options, browserFallbackPort) => {
21018
20647
  ...browserFallbackObservationAttributes(args.document.browserFallback)
21019
20648
  };
21020
20649
  if (args.links.length === 0) {
20650
+ if (isResearchWorkflowContext(args.context) && !isLikelyResearchDestinationUrl(args.document.url)) {
20651
+ throw toResearchDeadEndSearchError({
20652
+ providerId,
20653
+ source: "community",
20654
+ document: args.document,
20655
+ retrievalPath: searchPath
20656
+ });
20657
+ }
21021
20658
  return [{
21022
20659
  url: args.document.url,
21023
20660
  title: isHttpUrl7(args.query) ? args.document.url : `Community search: ${args.query}`,
@@ -21059,8 +20696,8 @@ var withDefaultCommunityOptions = (options, browserFallbackPort) => {
21059
20696
  browserFallbackPort,
21060
20697
  ownerReview: shouldOwnerReviewCommunityRedditSearchFallback
21061
20698
  });
21062
- const links = resolveCommunitySearchLinks(resolvedDocument, limit);
21063
- if (shouldRejectBlockedCommunityFallback(resolvedDocument, links)) {
20699
+ const links = resolveCommunitySearchLinks(resolvedDocument, limit, isResearchWorkflowContext(context));
20700
+ if (shouldRejectBlockedCommunityFallback(resolvedDocument, links, pageMessage)) {
21064
20701
  throw toCommunityFallbackSearchError({
21065
20702
  providerId,
21066
20703
  document: resolvedDocument,
@@ -21072,7 +20709,8 @@ var withDefaultCommunityOptions = (options, browserFallbackPort) => {
21072
20709
  page,
21073
20710
  document: resolvedDocument,
21074
20711
  pageMessage,
21075
- links
20712
+ links,
20713
+ context
21076
20714
  });
21077
20715
  }),
21078
20716
  fetch: options?.fetch ?? (async (input, context) => {
@@ -21185,7 +20823,7 @@ var withDefaultSocialPlatformOptions = (platform, options, browserFallbackPort)
21185
20823
  if (initialIssue.reasonCode === "env_limited" && !initialIssue.constraint) {
21186
20824
  return { document: currentDocument, ...described };
21187
20825
  }
21188
- if (!shouldRecoverSocialDocumentIssue(platform, operation, initialIssue)) {
20826
+ if (!shouldRecoverSocialDocumentIssue(platform, operation, initialIssue, context)) {
21189
20827
  throw toIssueError(currentDocument, described);
21190
20828
  }
21191
20829
  const fallback = await resolveProviderBrowserFallback({
@@ -21374,19 +21012,6 @@ var mergeBudgets = (base, partial) => {
21374
21012
  };
21375
21013
 
21376
21014
  export {
21377
- redactSensitive,
21378
- createRequestId,
21379
- stderrSink,
21380
- setDefaultLogSink,
21381
- createLogger,
21382
- DEFAULT_BLOCKER_ARTIFACT_CAPS,
21383
- clampBlockerConfidence,
21384
- clampText,
21385
- boundedUniqueList,
21386
- resolveBlockerArtifactCaps,
21387
- classifyBlockerSignal,
21388
- buildBlockerArtifacts,
21389
- __test__,
21390
21015
  CHALLENGE_AUTOMATION_MODES,
21391
21016
  isChallengeAutomationMode,
21392
21017
  resolveChallengeAutomationPolicy,
@@ -21411,7 +21036,6 @@ export {
21411
21036
  CANVAS_BROWSER_VALIDATION_MODES,
21412
21037
  CANVAS_VALIDATION_TARGET_BLOCK_ON_CODES,
21413
21038
  CANVAS_PUBLIC_WARNING_CLASSES,
21414
- summarizePrimaryProviderIssue,
21415
21039
  createTraceContext,
21416
21040
  clampConfidence,
21417
21041
  createStableRecordId,
@@ -21439,17 +21063,13 @@ export {
21439
21063
  createShoppingProviders,
21440
21064
  createShoppingProviderById,
21441
21065
  createWebProvider,
21442
- DEFAULT_ARTIFACT_TTL_HOURS,
21443
- MAX_ARTIFACT_TTL_HOURS,
21444
- createArtifactBundle,
21445
- cleanupExpiredArtifacts,
21066
+ WORKFLOW_ARTIFACT_DIRECTORY,
21067
+ resolveWorkflowArtifactRoot,
21446
21068
  resolveTimebox,
21447
21069
  isWithinTimebox,
21448
21070
  filterByTimebox,
21449
21071
  toResearchRecord,
21450
21072
  enrichResearchRecords,
21451
- INSPIREDESIGN_HANDOFF_COMMANDS,
21452
- INSPIREDESIGN_HANDOFF_GUIDANCE,
21453
21073
  buildMacroResolveSuccessHandoff,
21454
21074
  renderResearch,
21455
21075
  renderShopping,
@@ -21465,4 +21085,4 @@ export {
21465
21085
  createProviderRuntime,
21466
21086
  createDefaultRuntime
21467
21087
  };
21468
- //# sourceMappingURL=chunk-T3VVHJTK.js.map
21088
+ //# sourceMappingURL=chunk-4BEJVZRK.js.map