opendevbrowser 0.0.28 → 0.0.29

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 (313) hide show
  1. package/README.md +2 -2
  2. package/dist/accessibility-snapshot-PA6NWNS7.js +39 -0
  3. package/dist/accessibility-snapshot-PA6NWNS7.js.map +1 -0
  4. package/dist/active-window-YNYTIPZN.js +37 -0
  5. package/dist/active-window-YNYTIPZN.js.map +1 -0
  6. package/dist/annotate-STYHXZYJ.js +205 -0
  7. package/dist/annotate-STYHXZYJ.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-GHFZZ4SA.js +84 -0
  11. package/dist/attr-GHFZZ4SA.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-54FBOEGP.js +309 -0
  15. package/dist/canvas-54FBOEGP.js.map +1 -0
  16. package/dist/capture-desktop-SNABC24E.js +38 -0
  17. package/dist/capture-desktop-SNABC24E.js.map +1 -0
  18. package/dist/capture-window-UJSB5AMP.js +40 -0
  19. package/dist/capture-window-UJSB5AMP.js.map +1 -0
  20. package/dist/check-ST5UQ2F5.js +71 -0
  21. package/dist/check-ST5UQ2F5.js.map +1 -0
  22. package/dist/checked-IEMWI5CU.js +71 -0
  23. package/dist/checked-IEMWI5CU.js.map +1 -0
  24. package/dist/chunk-2CG4SW3E.js +64 -0
  25. package/dist/chunk-2CG4SW3E.js.map +1 -0
  26. package/dist/chunk-2SIMIPLY.js +67 -0
  27. package/dist/chunk-2SIMIPLY.js.map +1 -0
  28. package/dist/chunk-37VSRUW4.js +141 -0
  29. package/dist/chunk-37VSRUW4.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-ASMHEEKY.js +10 -0
  39. package/dist/chunk-ASMHEEKY.js.map +1 -0
  40. package/dist/chunk-DBF5OKH3.js +111 -0
  41. package/dist/chunk-DBF5OKH3.js.map +1 -0
  42. package/dist/chunk-DW4TX7MU.js +54 -0
  43. package/dist/chunk-DW4TX7MU.js.map +1 -0
  44. package/dist/chunk-IPE7TF2P.js +54 -0
  45. package/dist/chunk-IPE7TF2P.js.map +1 -0
  46. package/dist/chunk-IQTJHXZJ.js +126 -0
  47. package/dist/chunk-IQTJHXZJ.js.map +1 -0
  48. package/dist/chunk-J47N77VG.js +2969 -0
  49. package/dist/chunk-J47N77VG.js.map +1 -0
  50. package/dist/chunk-JZXD6FWR.js +25 -0
  51. package/dist/chunk-JZXD6FWR.js.map +1 -0
  52. package/dist/{chunk-QVWOPIZJ.js → chunk-KDSNXS6N.js} +75 -149
  53. package/dist/chunk-KDSNXS6N.js.map +1 -0
  54. package/dist/chunk-KZ2IXVQT.js +219 -0
  55. package/dist/chunk-KZ2IXVQT.js.map +1 -0
  56. package/dist/chunk-LBPELU7L.js +3649 -0
  57. package/dist/chunk-LBPELU7L.js.map +1 -0
  58. package/dist/chunk-MX3NFLCE.js +940 -0
  59. package/dist/chunk-MX3NFLCE.js.map +1 -0
  60. package/dist/chunk-N44UXKIB.js +26 -0
  61. package/dist/chunk-N44UXKIB.js.map +1 -0
  62. package/dist/chunk-OW5HMYMI.js +19 -0
  63. package/dist/chunk-OW5HMYMI.js.map +1 -0
  64. package/dist/chunk-OYNLAZQU.js +838 -0
  65. package/dist/chunk-OYNLAZQU.js.map +1 -0
  66. package/dist/chunk-PDPJN2OP.js +17 -0
  67. package/dist/chunk-PDPJN2OP.js.map +1 -0
  68. package/dist/chunk-RCZZGGJS.js +226 -0
  69. package/dist/chunk-RCZZGGJS.js.map +1 -0
  70. package/dist/chunk-RJNI3BHT.js +1 -0
  71. package/dist/chunk-RPXWUCQQ.js +112 -0
  72. package/dist/chunk-RPXWUCQQ.js.map +1 -0
  73. package/dist/chunk-S5KZQJJI.js +107 -0
  74. package/dist/chunk-S5KZQJJI.js.map +1 -0
  75. package/dist/{chunk-T3VVHJTK.js → chunk-S6S2UP6U.js} +1074 -1459
  76. package/dist/chunk-S6S2UP6U.js.map +1 -0
  77. package/dist/{chunk-I5ZCOZZV.js → chunk-SXAGSEKZ.js} +1202 -9561
  78. package/dist/chunk-SXAGSEKZ.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-TENZA3Y6.js +81 -0
  102. package/dist/click-TENZA3Y6.js.map +1 -0
  103. package/dist/clone-component-STH5AR6M.js +82 -0
  104. package/dist/clone-component-STH5AR6M.js.map +1 -0
  105. package/dist/clone-page-BSTWAPAJ.js +69 -0
  106. package/dist/clone-page-BSTWAPAJ.js.map +1 -0
  107. package/dist/close-CEMMAAM7.js +63 -0
  108. package/dist/close-CEMMAAM7.js.map +1 -0
  109. package/dist/close-QCWUNRAI.js +63 -0
  110. package/dist/close-QCWUNRAI.js.map +1 -0
  111. package/dist/connect-J3RVSEZF.js +107 -0
  112. package/dist/connect-J3RVSEZF.js.map +1 -0
  113. package/dist/console-poll-HL7BVIVX.js +76 -0
  114. package/dist/console-poll-HL7BVIVX.js.map +1 -0
  115. package/dist/cookie-import-WMUCIIHN.js +177 -0
  116. package/dist/cookie-import-WMUCIIHN.js.map +1 -0
  117. package/dist/cookie-list-PB2N4RPH.js +117 -0
  118. package/dist/cookie-list-PB2N4RPH.js.map +1 -0
  119. package/dist/daemon-5KSVMGN4.js +194 -0
  120. package/dist/daemon-5KSVMGN4.js.map +1 -0
  121. package/dist/daemon-fingerprint.json +1 -1
  122. package/dist/debug-trace-snapshot-RK7KDXA5.js +136 -0
  123. package/dist/debug-trace-snapshot-RK7KDXA5.js.map +1 -0
  124. package/dist/dialog-P6P4U7XE.js +75 -0
  125. package/dist/dialog-P6P4U7XE.js.map +1 -0
  126. package/dist/disconnect-32F7IDIP.js +58 -0
  127. package/dist/disconnect-32F7IDIP.js.map +1 -0
  128. package/dist/enabled-A6C6ZM2O.js +71 -0
  129. package/dist/enabled-A6C6ZM2O.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-ULTSABDM.js +98 -0
  135. package/dist/goto-ULTSABDM.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-UF2ZUMTQ.js +71 -0
  139. package/dist/hover-UF2ZUMTQ.js.map +1 -0
  140. package/dist/html-B6TX7GK7.js +84 -0
  141. package/dist/html-B6TX7GK7.js.map +1 -0
  142. package/dist/index.js +68 -34
  143. package/dist/index.js.map +1 -1
  144. package/dist/inspector-6S5FKUZQ.js +62 -0
  145. package/dist/inspector-6S5FKUZQ.js.map +1 -0
  146. package/dist/inspector-audit-ARGEGOS7.js +84 -0
  147. package/dist/inspector-audit-ARGEGOS7.js.map +1 -0
  148. package/dist/inspector-plan-CSG5HZOC.js +69 -0
  149. package/dist/inspector-plan-CSG5HZOC.js.map +1 -0
  150. package/dist/inspiredesign-7VRMMZN4.js +234 -0
  151. package/dist/inspiredesign-7VRMMZN4.js.map +1 -0
  152. package/dist/install-autostart-output-5DOMKCQL.js +41 -0
  153. package/dist/install-autostart-output-5DOMKCQL.js.map +1 -0
  154. package/dist/install-autostart-reconciliation-NHKOFYTD.js +73 -0
  155. package/dist/install-autostart-reconciliation-NHKOFYTD.js.map +1 -0
  156. package/dist/launch-REYCIR3Z.js +225 -0
  157. package/dist/launch-REYCIR3Z.js.map +1 -0
  158. package/dist/list-NPRXRQY2.js +51 -0
  159. package/dist/list-NPRXRQY2.js.map +1 -0
  160. package/dist/list-STYD2ZWA.js +54 -0
  161. package/dist/list-STYD2ZWA.js.map +1 -0
  162. package/dist/local-HXJLUUNT.js +54 -0
  163. package/dist/local-HXJLUUNT.js.map +1 -0
  164. package/dist/macro-resolve-ZIJZ65QI.js +253 -0
  165. package/dist/macro-resolve-ZIJZ65QI.js.map +1 -0
  166. package/dist/macros/execute-runtime.d.ts +3 -1
  167. package/dist/macros/execute-runtime.d.ts.map +1 -1
  168. package/dist/macros/execute.d.ts +2 -0
  169. package/dist/macros/execute.d.ts.map +1 -1
  170. package/dist/native-UPLVQ2SG.js +22 -0
  171. package/dist/native-UPLVQ2SG.js.map +1 -0
  172. package/dist/network-poll-HLDOSC72.js +76 -0
  173. package/dist/network-poll-HLDOSC72.js.map +1 -0
  174. package/dist/new-HXLLN6UT.js +69 -0
  175. package/dist/new-HXLLN6UT.js.map +1 -0
  176. package/dist/onboarding-metadata-7E3KLYSZ.js +27 -0
  177. package/dist/onboarding-metadata-7E3KLYSZ.js.map +1 -0
  178. package/dist/open-KDR25LQZ.js +81 -0
  179. package/dist/open-KDR25LQZ.js.map +1 -0
  180. package/dist/opendevbrowser.js +68 -34
  181. package/dist/opendevbrowser.js.map +1 -1
  182. package/dist/perf-EM6SWFJ6.js +58 -0
  183. package/dist/perf-EM6SWFJ6.js.map +1 -0
  184. package/dist/pointer-down-ZYWRZNCH.js +55 -0
  185. package/dist/pointer-down-ZYWRZNCH.js.map +1 -0
  186. package/dist/pointer-drag-LVEAVJO4.js +54 -0
  187. package/dist/pointer-drag-LVEAVJO4.js.map +1 -0
  188. package/dist/pointer-move-7SRKUS66.js +52 -0
  189. package/dist/pointer-move-7SRKUS66.js.map +1 -0
  190. package/dist/pointer-up-KLDBSK37.js +55 -0
  191. package/dist/pointer-up-KLDBSK37.js.map +1 -0
  192. package/dist/press-UIIXFTD7.js +83 -0
  193. package/dist/press-UIIXFTD7.js.map +1 -0
  194. package/dist/product-video-PYOXJVAI.js +235 -0
  195. package/dist/product-video-PYOXJVAI.js.map +1 -0
  196. package/dist/providers/artifacts.d.ts +0 -2
  197. package/dist/providers/artifacts.d.ts.map +1 -1
  198. package/dist/providers/blocker.d.ts.map +1 -1
  199. package/dist/providers/bounded-map.d.ts +2 -0
  200. package/dist/providers/bounded-map.d.ts.map +1 -0
  201. package/dist/providers/community/index.d.ts.map +1 -1
  202. package/dist/providers/constraint.d.ts.map +1 -1
  203. package/dist/providers/index.d.ts +1 -0
  204. package/dist/providers/index.d.ts.map +1 -1
  205. package/dist/providers/renderer.d.ts.map +1 -1
  206. package/dist/providers/research-compiler.d.ts +1 -1
  207. package/dist/providers/research-compiler.d.ts.map +1 -1
  208. package/dist/providers/research-executor.d.ts.map +1 -1
  209. package/dist/providers/runtime-factory.d.ts.map +1 -1
  210. package/dist/providers/shared/traversal-url.d.ts +3 -0
  211. package/dist/providers/shared/traversal-url.d.ts.map +1 -1
  212. package/dist/providers/shopping/index.d.ts.map +1 -1
  213. package/dist/providers/social/search-quality.d.ts.map +1 -1
  214. package/dist/providers/workflow-handoff.d.ts +4 -0
  215. package/dist/providers/workflow-handoff.d.ts.map +1 -1
  216. package/dist/providers/workflows.d.ts.map +1 -1
  217. package/dist/{providers-QF2RFB4J.js → providers-4YY2BLXG.js} +19 -14
  218. package/dist/providers-4YY2BLXG.js.map +1 -0
  219. package/dist/public-surface/generated-manifest.d.ts +2 -2
  220. package/dist/public-surface/generated-manifest.d.ts.map +1 -1
  221. package/dist/public-surface/source.d.ts +2 -2
  222. package/dist/public-surface/source.d.ts.map +1 -1
  223. package/dist/relay/protocol.d.ts +3 -1
  224. package/dist/relay/protocol.d.ts.map +1 -1
  225. package/dist/relay/relay-server.d.ts +6 -0
  226. package/dist/relay/relay-server.d.ts.map +1 -1
  227. package/dist/research-CKXMJ2DK.js +295 -0
  228. package/dist/research-CKXMJ2DK.js.map +1 -0
  229. package/dist/review-7HWJPZOD.js +48 -0
  230. package/dist/review-7HWJPZOD.js.map +1 -0
  231. package/dist/review-desktop-2IBJHFB5.js +54 -0
  232. package/dist/review-desktop-2IBJHFB5.js.map +1 -0
  233. package/dist/rpc-3HGIEJUO.js +159 -0
  234. package/dist/rpc-3HGIEJUO.js.map +1 -0
  235. package/dist/run-ADRYI3MS.js +180 -0
  236. package/dist/run-ADRYI3MS.js.map +1 -0
  237. package/dist/screencast-start-DTLUHD5H.js +67 -0
  238. package/dist/screencast-start-DTLUHD5H.js.map +1 -0
  239. package/dist/screencast-stop-54C5LRSS.js +59 -0
  240. package/dist/screencast-stop-54C5LRSS.js.map +1 -0
  241. package/dist/screenshot-HOAKR7P7.js +68 -0
  242. package/dist/screenshot-HOAKR7P7.js.map +1 -0
  243. package/dist/scroll-IAOO5COY.js +84 -0
  244. package/dist/scroll-IAOO5COY.js.map +1 -0
  245. package/dist/scroll-into-view-RKWSLAPH.js +71 -0
  246. package/dist/scroll-into-view-RKWSLAPH.js.map +1 -0
  247. package/dist/select-IGD3T6X4.js +86 -0
  248. package/dist/select-IGD3T6X4.js.map +1 -0
  249. package/dist/serve-7X4INUCU.js +498 -0
  250. package/dist/serve-7X4INUCU.js.map +1 -0
  251. package/dist/shopping-FC6DRW76.js +273 -0
  252. package/dist/shopping-FC6DRW76.js.map +1 -0
  253. package/dist/skill-lifecycle-5UAZGKSN.js +89 -0
  254. package/dist/skill-lifecycle-5UAZGKSN.js.map +1 -0
  255. package/dist/skills-NSXDX6YM.js +26 -0
  256. package/dist/skills-NSXDX6YM.js.map +1 -0
  257. package/dist/snapshot-X22GG324.js +113 -0
  258. package/dist/snapshot-X22GG324.js.map +1 -0
  259. package/dist/status-SP55LMNW.js +132 -0
  260. package/dist/status-SP55LMNW.js.map +1 -0
  261. package/dist/status-VH2WXIDG.js +35 -0
  262. package/dist/status-VH2WXIDG.js.map +1 -0
  263. package/dist/status-capabilities-YBERLRRA.js +57 -0
  264. package/dist/status-capabilities-YBERLRRA.js.map +1 -0
  265. package/dist/text-6TB5WNLI.js +84 -0
  266. package/dist/text-6TB5WNLI.js.map +1 -0
  267. package/dist/tools/macro_resolve.d.ts.map +1 -1
  268. package/dist/type-3UI3TQH3.js +94 -0
  269. package/dist/type-3UI3TQH3.js.map +1 -0
  270. package/dist/uncheck-5L3D2D4U.js +71 -0
  271. package/dist/uncheck-5L3D2D4U.js.map +1 -0
  272. package/dist/uninstall-KYKGJAX7.js +91 -0
  273. package/dist/uninstall-KYKGJAX7.js.map +1 -0
  274. package/dist/update-SMXPYGXS.js +305 -0
  275. package/dist/update-SMXPYGXS.js.map +1 -0
  276. package/dist/update-skill-modes-BVX7IVMW.js +38 -0
  277. package/dist/update-skill-modes-BVX7IVMW.js.map +1 -0
  278. package/dist/upload-YG4J2EMI.js +56 -0
  279. package/dist/upload-YG4J2EMI.js.map +1 -0
  280. package/dist/use-V3LGFP3K.js +63 -0
  281. package/dist/use-V3LGFP3K.js.map +1 -0
  282. package/dist/value-3247D57X.js +71 -0
  283. package/dist/value-3247D57X.js.map +1 -0
  284. package/dist/visible-A7HEV36U.js +71 -0
  285. package/dist/visible-A7HEV36U.js.map +1 -0
  286. package/dist/wait-UZPP4Y4R.js +109 -0
  287. package/dist/wait-UZPP4Y4R.js.map +1 -0
  288. package/dist/windows-76TR3AIP.js +37 -0
  289. package/dist/windows-76TR3AIP.js.map +1 -0
  290. package/extension/dist/background.js +99 -22
  291. package/extension/dist/ops/ops-runtime.js +85 -7
  292. package/extension/dist/ops/ops-session-store.js +3 -0
  293. package/extension/dist/ops/target-session-coordinator.js +3 -0
  294. package/extension/dist/services/CDPRouter.js +9 -0
  295. package/extension/manifest.json +1 -1
  296. package/package.json +1 -1
  297. package/skills/opendevbrowser-best-practices/SKILL.md +8 -6
  298. package/skills/opendevbrowser-best-practices/artifacts/skill-runtime-surface-matrix.md +1 -1
  299. package/skills/opendevbrowser-best-practices/scripts/odb-workflow.sh +3 -2
  300. package/skills/opendevbrowser-best-practices/scripts/validator-fixture-cli.sh +39 -2
  301. package/skills/opendevbrowser-research/SKILL.md +64 -12
  302. package/skills/opendevbrowser-research/artifacts/research-workflows.md +56 -19
  303. package/skills/opendevbrowser-research/assets/templates/compact.md +31 -5
  304. package/skills/opendevbrowser-research/assets/templates/context.json +52 -1
  305. package/skills/opendevbrowser-research/assets/templates/report.md +57 -4
  306. package/skills/opendevbrowser-research/examples/sample-input.json +1 -1
  307. package/skills/opendevbrowser-research/examples/sample-output.md +27 -2
  308. package/skills/opendevbrowser-research/scripts/run-research.sh +2 -6
  309. package/skills/opendevbrowser-research/scripts/validate-skill-assets.sh +115 -1
  310. package/dist/chunk-I5ZCOZZV.js.map +0 -1
  311. package/dist/chunk-QVWOPIZJ.js.map +0 -1
  312. package/dist/chunk-T3VVHJTK.js.map +0 -1
  313. /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";
@@ -9873,123 +9087,7 @@ var isWorkflowResumePayload = (value) => isJsonRecord(value) && isWorkflowResume
9873
9087
 
9874
9088
  // src/providers/workflows.ts
9875
9089
  import { createHash as createHash7 } from "crypto";
9876
-
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 };
9945
- }
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
- }
9990
- }
9991
- return { removed, skipped };
9992
- };
9090
+ import { join as join2 } from "path";
9993
9091
 
9994
9092
  // src/providers/timebox.ts
9995
9093
  var MAX_DAYS = 365;
@@ -10458,232 +9556,24 @@ var design_contract_v1_default = {
10458
9556
  "keyboard-regression",
10459
9557
  "stale-search-results",
10460
9558
  "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"]
9559
+ ],
9560
+ warnOn: [
9561
+ "responsive-mismatch",
9562
+ "layout-shift",
9563
+ "spinner-stacking",
9564
+ "scan-surface-jank"
9565
+ ]
9566
+ },
9567
+ designVectors: {
9568
+ advancedMotionAdvisory: [
9569
+ "Advisory shader-like gradient depth",
9570
+ "Advisory WebGL-style spatial reveal",
9571
+ "Advisory Spline-style product orbit",
9572
+ "Runtime support: none. Library policy authorization: none."
9573
+ ]
9574
+ }
10683
9575
  }
10684
9576
  };
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
9577
 
10688
9578
  // skills/opendevbrowser-design-agent/assets/templates/inspiredesign-advanced-brief.v1.json
10689
9579
  var inspiredesign_advanced_brief_v1_default = {
@@ -14287,10 +13177,111 @@ var createSuccessHandoff = (followthroughSummary, suggestedNextAction, suggested
14287
13177
  });
14288
13178
  var cliExample = (command, args = "") => `npx opendevbrowser ${command}${args ? ` ${args}` : ""}`;
14289
13179
  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
- );
13180
+ var GATED_PROVIDER_REASON_CODES = /* @__PURE__ */ new Set([
13181
+ "auth_required",
13182
+ "token_required",
13183
+ "challenge_detected"
13184
+ ]);
13185
+ var buildResearchRerunCommand = (input, options = {}) => {
13186
+ const browserMode = options.browserMode ?? input.browserMode ?? "managed";
13187
+ const useCookies = options.useCookies ? " --use-cookies" : "";
13188
+ const challengeMode = options.challengeAutomationMode ? ` --challenge-automation-mode ${options.challengeAutomationMode}` : "";
13189
+ return cliExample(
13190
+ "research run",
13191
+ `--topic ${quoteCliValue(input.topic)} --days 14 --sources web,community --browser-mode ${browserMode}${useCookies}${challengeMode} --mode json --output-format json`
13192
+ );
13193
+ };
13194
+ var isJsonRecord3 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
13195
+ var readFailureReasonCode = (failure) => {
13196
+ return failure.error.reasonCode ?? normalizeProviderReasonCode({
13197
+ code: failure.error.code,
13198
+ message: failure.error.message,
13199
+ details: failure.error.details
13200
+ }) ?? null;
13201
+ };
13202
+ var readDiagnosticReasonCode = (diagnostic) => {
13203
+ const reasonCode = diagnostic.reasonCode ?? diagnostic.browserFallbackReasonCode;
13204
+ return isProviderReasonCode(reasonCode) && GATED_PROVIDER_REASON_CODES.has(reasonCode) ? reasonCode : null;
13205
+ };
13206
+ var cookieDiagnosticShowsAvailableCookies = (diagnostic) => {
13207
+ 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;
13208
+ };
13209
+ var addProviderSignal = (signal, provider, reasonCode) => {
13210
+ if (provider) signal.providers.push(provider);
13211
+ if (reasonCode) signal.reasonCodes.push(reasonCode);
13212
+ };
13213
+ var readFailureCookieDiagnostics = (failure) => {
13214
+ const candidate = failure.error.details?.cookieDiagnostics;
13215
+ return isJsonRecord3(candidate) ? candidate : null;
13216
+ };
13217
+ var emptyResearchGatedProviderSignal = () => ({
13218
+ providers: [],
13219
+ reasonCodes: [],
13220
+ useCookies: false
13221
+ });
13222
+ var normalizeResearchGatedProviderSignal = (signal) => {
13223
+ return signal.providers.length > 0 || signal.reasonCodes.length > 0 ? {
13224
+ providers: [...new Set(signal.providers)].sort(),
13225
+ reasonCodes: [...new Set(signal.reasonCodes)].sort(),
13226
+ useCookies: signal.useCookies
13227
+ } : null;
13228
+ };
13229
+ var mergeResearchGatedProviderSignals = (signals) => {
13230
+ const merged = emptyResearchGatedProviderSignal();
13231
+ for (const signal of signals) {
13232
+ merged.providers.push(...signal.providers);
13233
+ merged.reasonCodes.push(...signal.reasonCodes);
13234
+ merged.useCookies = merged.useCookies || signal.useCookies;
13235
+ }
13236
+ return merged;
13237
+ };
13238
+ var detectResearchFailureSignals = (failures) => {
13239
+ const signal = emptyResearchGatedProviderSignal();
13240
+ for (const failure of failures) {
13241
+ const reasonCode = readFailureReasonCode(failure);
13242
+ const isGatedFailure = Boolean(reasonCode && GATED_PROVIDER_REASON_CODES.has(reasonCode));
13243
+ if (isGatedFailure) {
13244
+ addProviderSignal(signal, failure.provider, reasonCode);
13245
+ const diagnostic = readFailureCookieDiagnostics(failure);
13246
+ signal.useCookies = signal.useCookies || Boolean(diagnostic && cookieDiagnosticShowsAvailableCookies(diagnostic));
13247
+ }
13248
+ }
13249
+ return signal;
13250
+ };
13251
+ var detectResearchCookieSignals = (diagnostics) => {
13252
+ const signal = emptyResearchGatedProviderSignal();
13253
+ for (const diagnostic of diagnostics) {
13254
+ const reasonCode = readDiagnosticReasonCode(diagnostic);
13255
+ const isGatedDiagnostic = Boolean(reasonCode || diagnostic.policy === "required");
13256
+ if (isGatedDiagnostic) {
13257
+ addProviderSignal(signal, typeof diagnostic.provider === "string" ? diagnostic.provider : void 0, reasonCode);
13258
+ signal.useCookies = signal.useCookies || cookieDiagnosticShowsAvailableCookies(diagnostic);
13259
+ }
13260
+ }
13261
+ return signal;
13262
+ };
13263
+ var detectResearchChallengeSignals = (diagnostics) => {
13264
+ const signal = emptyResearchGatedProviderSignal();
13265
+ for (const diagnostic of diagnostics) {
13266
+ const reasonCode = readDiagnosticReasonCode(diagnostic);
13267
+ if (reasonCode || diagnostic.blockerType === "auth_required" || diagnostic.blockerType === "anti_bot_challenge") {
13268
+ addProviderSignal(signal, typeof diagnostic.provider === "string" ? diagnostic.provider : void 0, reasonCode);
13269
+ }
13270
+ }
13271
+ return signal;
13272
+ };
13273
+ var detectResearchGatedProviderSignal = (input) => {
13274
+ return normalizeResearchGatedProviderSignal(mergeResearchGatedProviderSignals([
13275
+ detectResearchFailureSignals(input.failures ?? []),
13276
+ detectResearchCookieSignals(input.cookieDiagnostics ?? []),
13277
+ detectResearchChallengeSignals(input.challengeOrchestration ?? [])
13278
+ ]));
13279
+ };
13280
+ var buildResearchRecoveryRerunCommand = (input, signal) => buildResearchRerunCommand(input, {
13281
+ browserMode: "extension",
13282
+ useCookies: signal.useCookies,
13283
+ challengeAutomationMode: "browser_with_helper"
13284
+ });
14294
13285
  var buildShoppingRerunCommand = (input) => {
14295
13286
  const providers = input.providers?.length ? ` --providers ${input.providers.join(",")}` : " --providers shopping/bestbuy,shopping/ebay";
14296
13287
  const budget = typeof input.budget === "number" ? ` --budget ${input.budget}` : "";
@@ -14318,23 +13309,43 @@ var buildMacroResolveArgs = (input, options) => {
14318
13309
  const defaultProvider = input.defaultProvider ? ` --default-provider ${input.defaultProvider}` : "";
14319
13310
  const execute = options?.execute ? " --execute" : "";
14320
13311
  const browserMode = options?.browserMode ? ` --browser-mode ${options.browserMode}` : "";
13312
+ const useCookies = options?.useCookies ? " --use-cookies" : "";
14321
13313
  const challenge = options?.challengeAutomationMode ? ` --challenge-automation-mode ${options.challengeAutomationMode}` : "";
13314
+ const cookiePolicy = options?.cookiePolicyOverride ? ` --cookie-policy ${options.cookiePolicyOverride}` : "";
14322
13315
  const outputFormat = options?.includeOutputFormat === false ? "" : " --output-format json";
14323
- return `--expression ${quoteCliValue(input.expression)}${defaultProvider}${execute}${browserMode}${challenge}${outputFormat}`;
13316
+ return `--expression ${quoteCliValue(input.expression)}${defaultProvider}${execute}${browserMode}${useCookies}${cookiePolicy}${challenge}${outputFormat}`;
14324
13317
  };
14325
13318
  var buildMacroPreviewCommand = (input) => cliExample("macro-resolve", buildMacroResolveArgs(input));
14326
13319
  var buildMacroExecuteCommand = (input, challengeAutomationMode, browserMode) => cliExample("macro-resolve", buildMacroResolveArgs(input, {
14327
13320
  execute: true,
14328
13321
  browserMode,
13322
+ ...browserMode === "extension" ? { useCookies: true, cookiePolicyOverride: "required" } : {},
14329
13323
  challengeAutomationMode
14330
13324
  }));
14331
- var buildResearchSuccessHandoff = (input) => {
13325
+ var buildResearchGatedSuccessHandoff = (input, signal) => {
13326
+ const recoveryCommand = buildResearchRecoveryRerunCommand(input, signal);
13327
+ const providers = signal.providers.length > 0 ? signal.providers.join(", ") : "gated providers";
13328
+ 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.";
13329
+ return createSuccessHandoff(
13330
+ `Review ranked records, artifact metadata, and gated-provider diagnostics for ${providers} before publishing claims.`,
13331
+ `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}`,
13332
+ [
13333
+ { reason: "Check records.json, context.json, meta.json, failures, and cookie diagnostics before using the result as evidence." },
13334
+ {
13335
+ reason: "Rerun with an existing signed-in extension session and browser-scoped challenge assistance when gated providers blocked useful evidence.",
13336
+ command: recoveryCommand
13337
+ },
13338
+ { reason: "Keep SERPs discovery-only and publish only claims supported by destination records that passed review." }
13339
+ ]
13340
+ );
13341
+ };
13342
+ var buildResearchDefaultSuccessHandoff = (input) => {
14332
13343
  const rerunCommand = buildResearchRerunCommand(input);
14333
13344
  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.`,
13345
+ "Review ranked records, artifact metadata, and source support before turning the result into a publishable claim.",
13346
+ `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
13347
  [
14337
- { reason: "Check which records actually support the final claim." },
13348
+ { reason: "Check which ranked records and artifact metadata actually support the final claim." },
14338
13349
  {
14339
13350
  reason: "Rerun with explicit sources and a narrower timebox if the evidence set is still too broad.",
14340
13351
  command: rerunCommand
@@ -14342,6 +13353,10 @@ var buildResearchSuccessHandoff = (input) => {
14342
13353
  ]
14343
13354
  );
14344
13355
  };
13356
+ var buildResearchSuccessHandoff = (input) => {
13357
+ const signal = detectResearchGatedProviderSignal(input);
13358
+ return signal ? buildResearchGatedSuccessHandoff(input, signal) : buildResearchDefaultSuccessHandoff(input);
13359
+ };
14345
13360
  var buildShoppingSuccessHandoff = (input) => {
14346
13361
  const rerunCommand = buildShoppingRerunCommand(input);
14347
13362
  return createSuccessHandoff(
@@ -14485,19 +13500,304 @@ var compactResearchLines = (records, meta) => {
14485
13500
  if (records.length === 0) {
14486
13501
  const summary = primaryConstraintSummaryFromMeta(meta);
14487
13502
  return summary ? [
14488
- "No records matched the requested timebox.",
13503
+ "No usable research findings were available.",
14489
13504
  `Primary constraint: ${summary}`
14490
- ] : ["No records matched the requested timebox."];
13505
+ ] : ["No usable research findings were available."];
14491
13506
  }
14492
13507
  return records.slice(0, 10).map((record, index) => {
14493
13508
  const title = record.title ?? record.url ?? record.provider;
14494
13509
  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}`;
13510
+ return `${index + 1}. ${title} (${record.source}; ${record.provider}) score=${record.confidence.toFixed(2)} engagement=${engagement}`;
13511
+ });
13512
+ };
13513
+ var RESEARCH_REPORT_LIMITS = {
13514
+ findings: 10,
13515
+ sources: 20,
13516
+ failures: 10,
13517
+ excerptCharacters: 240,
13518
+ failureMessageCharacters: 240
13519
+ };
13520
+ var RESEARCH_REPORT_FILE_NAMES = [
13521
+ "summary.md",
13522
+ "report.md",
13523
+ "records.json",
13524
+ "context.json",
13525
+ "meta.json",
13526
+ "bundle-manifest.json"
13527
+ ];
13528
+ var plainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
13529
+ var researchTitle = (record) => record.title ?? record.url ?? record.provider;
13530
+ var normalizedInlineText = (content) => content?.replace(/\s+/g, " ").trim() ?? "";
13531
+ var boundedInlineText = (args) => {
13532
+ const normalized = normalizedInlineText(args.content);
13533
+ if (!normalized) {
13534
+ return args.fallback;
13535
+ }
13536
+ if (normalized.length <= args.limit) {
13537
+ return normalized;
13538
+ }
13539
+ return `${normalized.slice(0, args.limit)} [truncated; see ${args.target}]`;
13540
+ };
13541
+ var researchExcerpt = (content) => boundedInlineText({
13542
+ content,
13543
+ fallback: "No content excerpt was available.",
13544
+ limit: RESEARCH_REPORT_LIMITS.excerptCharacters,
13545
+ target: "records.json for full content"
13546
+ });
13547
+ var researchFailureMessage = (content) => boundedInlineText({
13548
+ content,
13549
+ fallback: "provider failure",
13550
+ limit: RESEARCH_REPORT_LIMITS.failureMessageCharacters,
13551
+ target: "meta.json"
13552
+ });
13553
+ var limitedCount = (total, limit) => Math.min(total, limit);
13554
+ var omissionLine = (args) => {
13555
+ const omitted = args.total - limitedCount(args.total, args.limit);
13556
+ if (omitted <= 0) {
13557
+ return [];
13558
+ }
13559
+ const noun = omitted === 1 ? args.singular : args.plural;
13560
+ return [`- ${omitted} more ${noun} omitted from this report; see ${args.target} for the complete dataset.`];
13561
+ };
13562
+ var researchFindingsLines = (records) => records.length === 0 ? ["- No usable findings were available."] : [
13563
+ ...records.slice(0, RESEARCH_REPORT_LIMITS.findings).flatMap((record, index) => [
13564
+ `### ${index + 1}. ${researchTitle(record)}`,
13565
+ `- Source: ${record.source}`,
13566
+ `- Provider: ${record.provider}`,
13567
+ `- URL: ${record.url ?? "not provided"}`,
13568
+ `- Published: ${record.timestamp}`,
13569
+ `- Confidence: ${record.confidence.toFixed(2)}`,
13570
+ `- Evidence: ${researchExcerpt(record.content)}`
13571
+ ]),
13572
+ ...omissionLine({
13573
+ total: records.length,
13574
+ limit: RESEARCH_REPORT_LIMITS.findings,
13575
+ singular: "finding",
13576
+ plural: "findings",
13577
+ target: "records.json"
13578
+ })
13579
+ ];
13580
+ var researchSourcesLines = (records) => records.length === 0 ? ["- No sources available."] : [
13581
+ ...records.slice(0, RESEARCH_REPORT_LIMITS.sources).map((record) => `- ${researchTitle(record)}: ${record.url ?? "URL not provided"}`),
13582
+ ...omissionLine({
13583
+ total: records.length,
13584
+ limit: RESEARCH_REPORT_LIMITS.sources,
13585
+ singular: "source",
13586
+ plural: "sources",
13587
+ target: "records.json"
13588
+ })
13589
+ ];
13590
+ var researchReasonLine = (metrics) => {
13591
+ const reasons = Object.entries(plainObject(metrics.sanitized_reason_distribution)).map(([reason, count]) => `${reason}: ${String(count)}`);
13592
+ return reasons.length === 0 ? [] : [`- Sanitized record reasons: ${reasons.join(", ")}`];
13593
+ };
13594
+ var deadEndSearchFailures = (failures) => {
13595
+ if (!Array.isArray(failures)) return [];
13596
+ return failures.filter((failure) => {
13597
+ const record = plainObject(failure);
13598
+ const error = plainObject(record.error);
13599
+ const details = plainObject(error.details);
13600
+ return details.fallbackOutputReason === "research_dead_end_shell";
14496
13601
  });
14497
13602
  };
13603
+ var deadEndSearchFailureCount = (meta) => deadEndSearchFailures(meta.failures).length;
13604
+ var rejectedCandidatesFromMeta = (meta) => Array.isArray(meta.rejected_candidates) ? meta.rejected_candidates.map(plainObject).filter((candidate) => Object.keys(candidate).length > 0) : [];
13605
+ var rejectedCandidateCount = (meta) => {
13606
+ const metrics = plainObject(meta.metrics);
13607
+ if (typeof metrics.rejected_candidate_count === "number") {
13608
+ return metrics.rejected_candidate_count;
13609
+ }
13610
+ const sanitized = typeof metrics.sanitized_records === "number" ? metrics.sanitized_records : 0;
13611
+ return sanitized + deadEndSearchFailureCount(meta);
13612
+ };
13613
+ var deadEndSearchFailureLines = (meta) => {
13614
+ const failures = deadEndSearchFailures(meta.failures);
13615
+ if (failures.length === 0) return [];
13616
+ return [`- Dead-end search failures: ${failures.length}`];
13617
+ };
13618
+ var researchRejectedCandidateSummary = (candidate) => {
13619
+ const reason = typeof candidate.reason === "string" ? candidate.reason : "unknown_reason";
13620
+ const provider = typeof candidate.provider === "string" ? candidate.provider : "unknown_provider";
13621
+ const source = typeof candidate.source === "string" ? candidate.source : "unknown_source";
13622
+ const status = typeof candidate.replacement_status === "string" ? candidate.replacement_status : "not_recorded";
13623
+ const retrievalPath = typeof candidate.retrievalPath === "string" ? `; path=${candidate.retrievalPath}` : "";
13624
+ const url = typeof candidate.url === "string" ? candidate.url : "URL not recorded";
13625
+ return `${reason} from ${provider} (${source}; ${status}${retrievalPath}): ${url}`;
13626
+ };
13627
+ var researchFailureSummary = (failure) => {
13628
+ const record = plainObject(failure);
13629
+ const error = plainObject(record.error);
13630
+ const provider = typeof record.provider === "string" ? record.provider : "unknown";
13631
+ const source = typeof record.source === "string" ? record.source : "unknown";
13632
+ const reason = typeof error.reasonCode === "string" ? `${error.reasonCode}: ` : "";
13633
+ const message = researchFailureMessage(typeof error.message === "string" ? error.message : void 0);
13634
+ return `${provider} (${source}): ${reason}${message}`;
13635
+ };
13636
+ var researchFailureLines = (failures) => {
13637
+ if (!Array.isArray(failures) || failures.length === 0) {
13638
+ return [];
13639
+ }
13640
+ const summaries = failures.slice(0, RESEARCH_REPORT_LIMITS.failures).map(researchFailureSummary);
13641
+ const omitted = failures.length - summaries.length;
13642
+ const noun = omitted === 1 ? "failure" : "failures";
13643
+ const suffix = omitted > 0 ? `; ${omitted} more provider ${noun} omitted from this report; see meta.json` : "";
13644
+ return [`- Provider failures: ${summaries.join("; ")}${suffix}`];
13645
+ };
13646
+ var researchGapLines = (meta) => {
13647
+ const metrics = plainObject(meta.metrics);
13648
+ const details = [
13649
+ typeof metrics.final_records === "number" ? `- Final records reported by workflow: ${metrics.final_records}` : "",
13650
+ typeof metrics.sanitized_records === "number" ? `- Sanitized records excluded: ${metrics.sanitized_records}` : "",
13651
+ ...researchReasonLine(metrics),
13652
+ ...researchFailureLines(meta.failures)
13653
+ ].filter(Boolean);
13654
+ const constraint = primaryConstraintSummaryFromMeta(meta);
13655
+ const fallback = "- No provider limitations or sanitization gaps were reported.";
13656
+ const gapDetails = details.length > 0 || constraint ? details : [fallback];
13657
+ return [
13658
+ "## Confidence and Gaps",
13659
+ ...constraint ? [`- Primary constraint: ${constraint}`] : [],
13660
+ ...gapDetails
13661
+ ];
13662
+ };
13663
+ var researchSearchDirectionLines = (meta) => {
13664
+ const selection = plainObject(meta.selection);
13665
+ const sources = Array.isArray(selection.resolved_sources) ? selection.resolved_sources.map(String).join(", ") : "not recorded";
13666
+ return [
13667
+ "## Search Direction",
13668
+ `- Source families searched: ${sources}`,
13669
+ "- Direction: Follow accepted destination pages from provider/search output before synthesis."
13670
+ ];
13671
+ };
13672
+ var researchSourceFamilies = (meta) => {
13673
+ const sources = plainObject(meta.selection).resolved_sources;
13674
+ return Array.isArray(sources) ? sources.map(String) : [];
13675
+ };
13676
+ var researchCandidateTriageSchema = () => ({
13677
+ url: "",
13678
+ rank: 0,
13679
+ engine: "",
13680
+ query: "",
13681
+ source_family: "",
13682
+ title: "",
13683
+ status: "pending|accepted|rejected",
13684
+ blocker_notes: "",
13685
+ rejection_reason: "",
13686
+ replacement_url: "",
13687
+ retrieval_notes: "",
13688
+ extraction_status: "pending|fetched|blocked|shell|stale|irrelevant"
13689
+ });
13690
+ var researchCandidateTriageLines = (records, meta) => {
13691
+ const rejected = rejectedCandidateCount(meta);
13692
+ return [
13693
+ "## Candidate Triage",
13694
+ `- Accepted destination records: ${records.length}`,
13695
+ `- Rejected shell or dead-end candidates: ${rejected}`,
13696
+ "- Rejection policy: search pages, login/account pages, privacy/cookie pages, JavaScript shells, not-found pages, and unsupported shells are not final evidence."
13697
+ ];
13698
+ };
13699
+ var researchRejectedCandidateLines = (meta) => {
13700
+ const rejectedCandidates = rejectedCandidatesFromMeta(meta);
13701
+ const rejectionLines = [
13702
+ ...researchReasonLine(plainObject(meta.metrics)),
13703
+ ...rejectedCandidates.slice(0, RESEARCH_REPORT_LIMITS.failures).map((candidate) => `- Rejected candidate: ${researchRejectedCandidateSummary(candidate)}`),
13704
+ ...deadEndSearchFailureLines(meta)
13705
+ ];
13706
+ return [
13707
+ "## Rejected Candidates",
13708
+ ...rejectionLines,
13709
+ ...rejectionLines.length === 0 ? ["- No rejected candidate distribution was reported."] : []
13710
+ ];
13711
+ };
13712
+ var researchDeepDiveLines = (records) => [
13713
+ "## Deep Dives",
13714
+ ...records.length === 0 ? ["- No destination pages passed the evidence gate."] : records.slice(0, RESEARCH_REPORT_LIMITS.sources).map((record) => {
13715
+ const retrievalPath = typeof record.attributes.retrievalPath === "string" ? record.attributes.retrievalPath : "";
13716
+ const prefix = retrievalPath.includes(":fetch:") ? "Opened destination evidence" : "Accepted evidence record";
13717
+ return `- ${prefix}: ${record.url ?? researchTitle(record)}`;
13718
+ })
13719
+ ];
13720
+ var researchSynthesisFeedbackText = (records, meta) => {
13721
+ const rejected = rejectedCandidateCount(meta);
13722
+ 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.";
13723
+ };
13724
+ var researchSynthesisFeedbackLines = (records, meta) => {
13725
+ return ["## Synthesis Feedback", `- Next step: ${researchSynthesisFeedbackText(records, meta)}`];
13726
+ };
13727
+ var researchContextPayload = (args) => ({
13728
+ topic: args.topic,
13729
+ timebox: plainObject(args.meta.timebox),
13730
+ source_families: researchSourceFamilies(args.meta),
13731
+ evidence_gate: {
13732
+ status: "pending_review",
13733
+ reviewed_artifacts: []
13734
+ },
13735
+ artifact_files: RESEARCH_REPORT_FILE_NAMES,
13736
+ source_ledger: args.records.map((record) => ({
13737
+ title: researchTitle(record),
13738
+ url: record.url,
13739
+ source_family: record.source,
13740
+ provider: record.provider
13741
+ })),
13742
+ search_direction_notes: researchSearchDirectionLines(args.meta),
13743
+ candidate_triage_schema: researchCandidateTriageSchema(),
13744
+ highlights: args.lines,
13745
+ records: args.records,
13746
+ candidate_triage: {
13747
+ accepted_destination_records: args.records.length,
13748
+ rejected_shell_or_dead_end_candidates: rejectedCandidateCount(args.meta)
13749
+ },
13750
+ rejected_candidates: rejectedCandidatesFromMeta(args.meta),
13751
+ deep_dive_pages: args.records.map((record) => ({
13752
+ title: researchTitle(record),
13753
+ url: record.url,
13754
+ provider: record.provider,
13755
+ source: record.source,
13756
+ retrievalPath: record.attributes.retrievalPath
13757
+ })),
13758
+ iteration_log: researchSearchDirectionLines(args.meta),
13759
+ synthesis_feedback: researchSynthesisFeedbackText(args.records, args.meta),
13760
+ meta: args.meta
13761
+ });
13762
+ var researchArtifactFileLines = () => [
13763
+ "## Report Files",
13764
+ ...RESEARCH_REPORT_FILE_NAMES.map((fileName) => `- ${fileName}`)
13765
+ ];
13766
+ var buildResearchReport = (args) => [
13767
+ "# Research Report",
13768
+ "",
13769
+ "## Executive Summary",
13770
+ `- Topic: ${args.topic}`,
13771
+ `- Usable findings: ${args.records.length}`,
13772
+ `- Findings shown in report: ${limitedCount(args.records.length, RESEARCH_REPORT_LIMITS.findings)}`,
13773
+ `- Sources shown in report: ${limitedCount(args.records.length, RESEARCH_REPORT_LIMITS.sources)}`,
13774
+ "- Final output: Usable records are persisted in records.json.",
13775
+ "- Diagnostics: Run metadata, failures, and constraints are persisted in meta.json; this report summarizes the bounded inline subset.",
13776
+ "",
13777
+ ...researchArtifactFileLines(),
13778
+ "",
13779
+ ...researchSearchDirectionLines(args.meta),
13780
+ "",
13781
+ ...researchCandidateTriageLines(args.records, args.meta),
13782
+ "",
13783
+ ...researchRejectedCandidateLines(args.meta),
13784
+ "",
13785
+ ...researchDeepDiveLines(args.records),
13786
+ "",
13787
+ ...researchSynthesisFeedbackLines(args.records, args.meta),
13788
+ "",
13789
+ "## Findings",
13790
+ ...researchFindingsLines(args.records),
13791
+ "",
13792
+ ...researchGapLines(args.meta),
13793
+ "",
13794
+ "## Sources",
13795
+ ...researchSourcesLines(args.records)
13796
+ ].join("\n");
14498
13797
  var renderResearch = (args) => {
14499
13798
  const lines = compactResearchLines(args.records, args.meta);
14500
13799
  const summary = lines.join("\n");
13800
+ const report = buildResearchReport(args);
14501
13801
  const markdown = [
14502
13802
  `# Research: ${args.topic}`,
14503
13803
  "",
@@ -14508,14 +13808,15 @@ var renderResearch = (args) => {
14508
13808
  JSON.stringify(args.meta, null, 2),
14509
13809
  "```"
14510
13810
  ].join("\n");
14511
- const contextPayload = {
13811
+ const contextPayload = researchContextPayload({
14512
13812
  topic: args.topic,
14513
- highlights: lines,
13813
+ lines,
14514
13814
  records: args.records,
14515
13815
  meta: args.meta
14516
- };
13816
+ });
14517
13817
  const files = [
14518
13818
  { path: "summary.md", content: markdown },
13819
+ { path: "report.md", content: report },
14519
13820
  { path: "records.json", content: { records: args.records } },
14520
13821
  { path: "context.json", content: contextPayload },
14521
13822
  { path: "meta.json", content: args.meta }
@@ -15636,14 +14937,14 @@ import { createHash as createHash5 } from "crypto";
15636
14937
  var DEFAULT_SHOPPING_SEARCH_LIMIT = 8;
15637
14938
  var SHOPPING_FETCH_RECOVERY_LIMIT = 2;
15638
14939
  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);
14940
+ var isJsonRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
15640
14941
  var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
15641
14942
  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));
14943
+ 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");
14944
+ 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));
14945
+ 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);
14946
+ var isProviderFailureEntry = (value) => isJsonRecord4(value) && typeof value.provider === "string" && isProviderSource(value.source) && isProviderError2(value.error);
14947
+ 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
14948
  var emptyCheckpointState = () => ({
15648
14949
  completed_step_ids: [],
15649
14950
  step_results_by_id: {}
@@ -15659,7 +14960,7 @@ var readShoppingCheckpointState = (checkpoint) => {
15659
14960
  if (state === void 0 || state === null) {
15660
14961
  return emptyCheckpointState();
15661
14962
  }
15662
- if (!isJsonRecord3(state)) {
14963
+ if (!isJsonRecord4(state)) {
15663
14964
  throw new Error("Shopping workflow checkpoint state must be a record.");
15664
14965
  }
15665
14966
  const completedStepIds = state.completed_step_ids;
@@ -15667,7 +14968,7 @@ var readShoppingCheckpointState = (checkpoint) => {
15667
14968
  throw new Error("Shopping workflow checkpoint state is missing valid completed_step_ids.");
15668
14969
  }
15669
14970
  const rawResults = state.step_results_by_id;
15670
- if (!isJsonRecord3(rawResults)) {
14971
+ if (!isJsonRecord4(rawResults)) {
15671
14972
  throw new Error("Shopping workflow checkpoint state is missing valid step_results_by_id.");
15672
14973
  }
15673
14974
  const stepResultsById = {};
@@ -15978,14 +15279,14 @@ var PRODUCT_VIDEO_STEP_IDS = {
15978
15279
  extractProductData: "product_video:extract_product_data",
15979
15280
  assembleArtifacts: "product_video:assemble_artifacts"
15980
15281
  };
15981
- var isJsonRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
15282
+ var isJsonRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
15982
15283
  var isFiniteNumber2 = (value) => typeof value === "number" && Number.isFinite(value);
15983
15284
  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));
15285
+ 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");
15286
+ 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));
15287
+ 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);
15288
+ var isProviderFailureEntry2 = (value) => isJsonRecord5(value) && typeof value.provider === "string" && isProviderSource2(value.source) && isProviderError3(value.error);
15289
+ 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
15290
  var emptyCheckpointState2 = () => ({
15990
15291
  completed_step_ids: []
15991
15292
  });
@@ -16028,7 +15329,7 @@ var readProductVideoCheckpointState = (checkpoint) => {
16028
15329
  if (state === void 0 || state === null) {
16029
15330
  return emptyCheckpointState2();
16030
15331
  }
16031
- if (!isJsonRecord4(state)) {
15332
+ if (!isJsonRecord5(state)) {
16032
15333
  throw new Error("Product-video workflow checkpoint state must be a record.");
16033
15334
  }
16034
15335
  const completedStepIds = state.completed_step_ids;
@@ -16139,15 +15440,16 @@ import { createHash as createHash6 } from "crypto";
16139
15440
  var RESEARCH_AUTO_SOURCES = ["web", "community", "social"];
16140
15441
  var RESEARCH_ALL_SOURCES = [...RESEARCH_AUTO_SOURCES];
16141
15442
  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);
15443
+ var RESEARCH_WEB_SEARCH_FETCH_LIMIT = 5;
15444
+ var RESEARCH_COMMUNITY_EXPANSION_PER_RECORD = 2;
15445
+ var isJsonRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
16144
15446
  var isFiniteNumber3 = (value) => typeof value === "number" && Number.isFinite(value);
16145
15447
  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));
15448
+ 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");
15449
+ 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));
15450
+ 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);
15451
+ var isProviderFailureEntry3 = (value) => isJsonRecord6(value) && typeof value.provider === "string" && isProviderSource3(value.source) && isProviderError4(value.error);
15452
+ 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
15453
  var emptyCheckpointState3 = () => ({
16152
15454
  completed_step_ids: [],
16153
15455
  step_results_by_id: {}
@@ -16170,7 +15472,7 @@ var readResearchCheckpointState = (checkpoint) => {
16170
15472
  if (state === void 0 || state === null) {
16171
15473
  return emptyCheckpointState3();
16172
15474
  }
16173
- if (!isJsonRecord5(state)) {
15475
+ if (!isJsonRecord6(state)) {
16174
15476
  throw new Error("Research workflow checkpoint state must be a record.");
16175
15477
  }
16176
15478
  const completedStepIds = state.completed_step_ids;
@@ -16178,7 +15480,7 @@ var readResearchCheckpointState = (checkpoint) => {
16178
15480
  throw new Error("Research workflow checkpoint state is missing valid completed_step_ids.");
16179
15481
  }
16180
15482
  const rawResults = state.step_results_by_id;
16181
- if (!isJsonRecord5(rawResults)) {
15483
+ if (!isJsonRecord6(rawResults)) {
16182
15484
  throw new Error("Research workflow checkpoint state is missing valid step_results_by_id.");
16183
15485
  }
16184
15486
  const stepResultsById = {};
@@ -16228,8 +15530,8 @@ var buildResearchSearchFilters = (source, args) => ({
16228
15530
  timebox_to: args.timebox.to,
16229
15531
  ...source === "community" || source === "social" ? {
16230
15532
  pageLimit: 1,
16231
- hopLimit: 0,
16232
- expansionPerRecord: 0
15533
+ hopLimit: source === "community" ? 1 : 0,
15534
+ expansionPerRecord: source === "community" ? RESEARCH_COMMUNITY_EXPANSION_PER_RECORD : 0
16233
15535
  } : {}
16234
15536
  });
16235
15537
  var compileResearchExecutionPlan = (args) => {
@@ -16296,7 +15598,12 @@ var compileResearchExecutionPlan = (args) => {
16296
15598
 
16297
15599
  // src/providers/research-executor.ts
16298
15600
  var RESEARCH_WEB_SEARCH_FETCH_PATHS = /* @__PURE__ */ new Set([
15601
+ "community:search:index",
15602
+ "community:search:url",
16299
15603
  "web:search:index",
15604
+ "web:search:url",
15605
+ "social:youtube:search:url",
15606
+ "social:search:url",
16300
15607
  "social:search:index"
16301
15608
  ]);
16302
15609
  var appendTrace2 = (trace, stage, event, details) => [
@@ -16337,6 +15644,15 @@ var isValidHttpUrl2 = (url) => {
16337
15644
  return false;
16338
15645
  }
16339
15646
  };
15647
+ var attributeLinks = (record) => {
15648
+ const links = record.attributes.links;
15649
+ if (!Array.isArray(links)) return [];
15650
+ return links.filter((link) => typeof link === "string");
15651
+ };
15652
+ var researchCandidateUrls = (record) => [
15653
+ ...typeof record.url === "string" ? [record.url] : [],
15654
+ ...attributeLinks(record)
15655
+ ];
16340
15656
  var resolveResearchWebFetchCandidates = (records, limit) => {
16341
15657
  const candidates = [];
16342
15658
  const seen = /* @__PURE__ */ new Set();
@@ -16345,27 +15661,30 @@ var resolveResearchWebFetchCandidates = (records, limit) => {
16345
15661
  if (!RESEARCH_WEB_SEARCH_FETCH_PATHS.has(retrievalPath)) {
16346
15662
  continue;
16347
15663
  }
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);
15664
+ for (const candidateUrl of researchCandidateUrls(record)) {
15665
+ const rawUrl = canonicalizeUrl(candidateUrl);
15666
+ if (!rawUrl) continue;
15667
+ let resolvedUrl = rawUrl;
15668
+ try {
15669
+ const parsed = new URL(rawUrl);
15670
+ if (/duckduckgo\.com$/i.test(parsed.hostname) && (parsed.pathname === "/l" || parsed.pathname === "/l/")) {
15671
+ const redirect = parsed.searchParams.get("uddg");
15672
+ if (typeof redirect === "string" && redirect.length > 0) {
15673
+ resolvedUrl = canonicalizeUrl(redirect);
15674
+ }
16359
15675
  }
15676
+ } catch {
15677
+ continue;
15678
+ }
15679
+ if (!resolvedUrl || !isValidHttpUrl2(resolvedUrl) || !isLikelyResearchDestinationUrl(resolvedUrl) || /duckduckgo\.com/i.test(resolvedUrl) || seen.has(resolvedUrl)) {
15680
+ continue;
15681
+ }
15682
+ seen.add(resolvedUrl);
15683
+ candidates.push(resolvedUrl);
15684
+ if (candidates.length >= limit) {
15685
+ break;
16360
15686
  }
16361
- } catch {
16362
- continue;
16363
- }
16364
- if (!resolvedUrl || !isValidHttpUrl2(resolvedUrl) || /duckduckgo\.com/i.test(resolvedUrl) || seen.has(resolvedUrl)) {
16365
- continue;
16366
15687
  }
16367
- seen.add(resolvedUrl);
16368
- candidates.push(resolvedUrl);
16369
15688
  if (candidates.length >= limit) {
16370
15689
  break;
16371
15690
  }
@@ -17131,7 +16450,11 @@ var rankResearchRecords = (records) => {
17131
16450
  var hash2 = (value) => createHash7("sha1").update(value).digest("hex").slice(0, 16);
17132
16451
  var RESEARCH_ALWAYS_SANITIZED_PATHS = /* @__PURE__ */ new Set([
17133
16452
  "community:search:index",
17134
- "social:search:index"
16453
+ "community:search:url",
16454
+ "social:search:index",
16455
+ "social:search:url",
16456
+ "social:youtube:search:url",
16457
+ "web:search:url"
17135
16458
  ]);
17136
16459
  var RESEARCH_CONDITIONAL_SANITIZED_PATHS = /* @__PURE__ */ new Set([
17137
16460
  "community:fetch:url",
@@ -17139,9 +16462,13 @@ var RESEARCH_CONDITIONAL_SANITIZED_PATHS = /* @__PURE__ */ new Set([
17139
16462
  "web:search:index"
17140
16463
  ]);
17141
16464
  var RESEARCH_LOGIN_SHELL_RE = /\b(?:log in|login|sign in|sign-in|please log in|continue with google|continue with apple)\b/i;
16465
+ var RESEARCH_LOGIN_SHELL_MAX_CONTENT_CHARS = 600;
16466
+ 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
16467
  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
16468
  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
16469
  var RESEARCH_NOT_FOUND_SHELL_RE = /\b(?:error 404|page not found|not found|can['’]t seem to find the page)\b/i;
16470
+ 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;
16471
+ var RESEARCH_PRIVACY_RECOVERED_CONTENT_RE = /\b(?:blogs? home|permalink|comments|article)\b/i;
17145
16472
  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
16473
  var isDuckDuckGoResearchShellUrl = (url) => {
17147
16474
  try {
@@ -17154,6 +16481,7 @@ var isDuckDuckGoResearchShellUrl = (url) => {
17154
16481
  }
17155
16482
  };
17156
16483
  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;
16484
+ 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
16485
  var resolveShoppingProviderIdForUrl = (url) => {
17158
16486
  try {
17159
16487
  const host = new URL(url).hostname.toLowerCase();
@@ -17208,6 +16536,48 @@ var WALMART_TITLE_CHROME_RE = /\b(?:Walmart\.com|Skip to Main Content|Pickup or
17208
16536
  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
16537
  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
16538
  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;
16539
+ var EBAY_TITLE_BRAND_PREFIX_RE = /^(?:new|used|pre-owned|preowned|open box|refurbished|renewed|genuine|authentic)\s+/i;
16540
+ var EBAY_KNOWN_MULTI_TOKEN_BRANDS = [
16541
+ "3M",
16542
+ "Bang & Olufsen",
16543
+ "Bowers & Wilkins",
16544
+ "Hewlett-Packard",
16545
+ "iRobot",
16546
+ "New Balance"
16547
+ ];
16548
+ var EBAY_KNOWN_SINGLE_TOKEN_BRANDS = /* @__PURE__ */ new Map([
16549
+ ["apple", "Apple"],
16550
+ ["bose", "Bose"],
16551
+ ["canon", "Canon"],
16552
+ ["dell", "Dell"],
16553
+ ["dyson", "Dyson"],
16554
+ ["google", "Google"],
16555
+ ["jbl", "JBL"],
16556
+ ["lenovo", "Lenovo"],
16557
+ ["lg", "LG"],
16558
+ ["microsoft", "Microsoft"],
16559
+ ["nikon", "Nikon"],
16560
+ ["nintendo", "Nintendo"],
16561
+ ["panasonic", "Panasonic"],
16562
+ ["philips", "Philips"],
16563
+ ["samsung", "Samsung"],
16564
+ ["sony", "Sony"]
16565
+ ]);
16566
+ var EBAY_TITLE_BRAND_STOP_WORDS = /* @__PURE__ */ new Set([
16567
+ "bluetooth",
16568
+ "case",
16569
+ "ergonomic",
16570
+ "gaming",
16571
+ "headphones",
16572
+ "keyboard",
16573
+ "leather",
16574
+ "noise",
16575
+ "portable",
16576
+ "rechargeable",
16577
+ "speaker",
16578
+ "vertical",
16579
+ "wireless"
16580
+ ]);
17211
16581
  var PRODUCT_FEATURE_SECTION_MARKERS = [
17212
16582
  "about this item",
17213
16583
  "key item features",
@@ -17273,7 +16643,7 @@ var INSPIREDESIGN_RENDER_MODES = /* @__PURE__ */ new Set(["compact", "json", "md
17273
16643
  var INSPIREDESIGN_CAPTURE_MODES = /* @__PURE__ */ new Set(["off", "deep"]);
17274
16644
  var INSPIREDESIGN_COOKIE_POLICIES = /* @__PURE__ */ new Set(["off", "auto", "required"]);
17275
16645
  var WORKFLOW_BROWSER_MODES = /* @__PURE__ */ new Set(["auto", "extension", "managed"]);
17276
- var isJsonRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
16646
+ var isJsonRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
17277
16647
  var INSPIREDESIGN_CAPTURE_UNAVAILABLE_FAILURE = "Deep capture requested, but browser capture is unavailable in this execution lane.";
17278
16648
  var isCanvasVisualDirectionProfile = (value) => {
17279
16649
  return CANVAS_VISUAL_DIRECTION_PROFILES.includes(value);
@@ -17302,7 +16672,7 @@ var serializeInspiredesignRunInput = (input) => ({
17302
16672
  });
17303
16673
  var isStringArray2 = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
17304
16674
  var parseInspiredesignBriefFormatRoute = (value) => {
17305
- if (!isJsonRecord6(value)) return void 0;
16675
+ if (!isJsonRecord7(value)) return void 0;
17306
16676
  const profile = typeof value.profile === "string" && isCanvasVisualDirectionProfile(value.profile) ? value.profile : void 0;
17307
16677
  const themeStrategy = typeof value.themeStrategy === "string" && isCanvasThemeStrategy(value.themeStrategy) ? value.themeStrategy : void 0;
17308
16678
  const navigationModel = typeof value.navigationModel === "string" && isCanvasNavigationModel(value.navigationModel) ? value.navigationModel : void 0;
@@ -17317,7 +16687,7 @@ var parseInspiredesignBriefFormatRoute = (value) => {
17317
16687
  };
17318
16688
  };
17319
16689
  var parseInspiredesignBriefFormat = (value) => {
17320
- if (!isJsonRecord6(value)) return void 0;
16690
+ if (!isJsonRecord7(value)) return void 0;
17321
16691
  const route = parseInspiredesignBriefFormatRoute(value.route);
17322
16692
  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
16693
  return void 0;
@@ -17346,7 +16716,7 @@ var parseInspiredesignBriefFormat = (value) => {
17346
16716
  };
17347
16717
  };
17348
16718
  var parseInspiredesignBriefExpansion = (value) => {
17349
- if (!isJsonRecord6(value)) return void 0;
16719
+ if (!isJsonRecord7(value)) return void 0;
17350
16720
  const format = parseInspiredesignBriefFormat(value.format);
17351
16721
  if (typeof value.sourceBrief !== "string" || typeof value.advancedBrief !== "string" || typeof value.templateVersion !== "string" || !format) {
17352
16722
  return void 0;
@@ -17368,7 +16738,7 @@ var parseInspiredesignEnvelopeInput = (input) => {
17368
16738
  ...typeof input.captureMode === "string" && INSPIREDESIGN_CAPTURE_MODES.has(input.captureMode) ? { captureMode: input.captureMode } : {},
17369
16739
  ...typeof input.includePrototypeGuidance === "boolean" ? { includePrototypeGuidance: input.includePrototypeGuidance } : {},
17370
16740
  ...typeof input.timeoutMs === "number" ? { timeoutMs: input.timeoutMs } : {},
17371
- ...typeof input.outputDir === "string" && input.outputDir.length > 0 ? { outputDir: input.outputDir } : {},
16741
+ ...typeof input.outputDir === "string" ? { outputDir: input.outputDir } : {},
17372
16742
  ...typeof input.ttlHours === "number" ? { ttlHours: input.ttlHours } : {},
17373
16743
  ...typeof input.browserMode === "string" && WORKFLOW_BROWSER_MODES.has(input.browserMode) ? { browserMode: input.browserMode } : {},
17374
16744
  ...typeof input.useCookies === "boolean" ? { useCookies: input.useCookies } : {},
@@ -17764,9 +17134,19 @@ var buildInspiredesignMeta = (runtime, workflowInput, references, failures, foll
17764
17134
  contractScope: followthrough.contractScope
17765
17135
  };
17766
17136
  };
17767
- var inferBrandFromContent = (content) => {
17137
+ var resolveWorkflowArtifactRoot = (outputDir) => {
17138
+ if (outputDir === void 0) {
17139
+ return join2(process.cwd(), ".opendevbrowser");
17140
+ }
17141
+ if (outputDir.trim() === "") {
17142
+ throw new Error("outputDir cannot be empty");
17143
+ }
17144
+ return outputDir;
17145
+ };
17146
+ var inferBrandFromContent = (content, productUrl) => {
17768
17147
  const normalized = normalizePlainText(content);
17769
17148
  if (!normalized) return void 0;
17149
+ const isEbayProduct = productUrl ? resolveShoppingProviderIdForUrl(productUrl) === "shopping/ebay" : false;
17770
17150
  const bestBuyTitle = inferBestBuyTitleFromContent(normalized);
17771
17151
  const bestBuyBrand = inferBestBuyBrandFromTitle(bestBuyTitle) ?? extractBrandFromTitle(bestBuyTitle);
17772
17152
  if (bestBuyBrand) {
@@ -17784,6 +17164,14 @@ var inferBrandFromContent = (content) => {
17784
17164
  if (productIdentifiersBrandMatch?.[1]) {
17785
17165
  return productIdentifiersBrandMatch[1].trim();
17786
17166
  }
17167
+ const ebayTitle = inferEbayTitleFromContent(normalized);
17168
+ const ebayBrand = inferEbayBrandFromTitle(ebayTitle);
17169
+ if (ebayBrand) {
17170
+ return ebayBrand;
17171
+ }
17172
+ if (isEbayProduct) {
17173
+ return void 0;
17174
+ }
17787
17175
  const brandMatch = /\bBrand ([A-Z][A-Za-z0-9&+' -]{1,60})\b/i.exec(normalized);
17788
17176
  if (brandMatch?.[1]) {
17789
17177
  return brandMatch[1].trim();
@@ -17803,13 +17191,69 @@ var inferBestBuyTitleFromContent = (normalized) => {
17803
17191
  }
17804
17192
  return candidate;
17805
17193
  };
17806
- var inferTitleFromContent = (content) => {
17194
+ var inferEbayTitleFromContent = (normalized) => {
17195
+ const patterns = [
17196
+ /\bExpand Cart Loading\.\.\.\s+(.+?)(?:\s+for sale online\s*\|\s*eBay)?\s+Condition:/i,
17197
+ /\bBuy It Now\s+(.+?)\s+Sign in to check out\b/i
17198
+ ];
17199
+ for (const pattern of patterns) {
17200
+ const candidate = stripMarketplaceTitleFraming(normalizePlainText(pattern.exec(normalized)?.[1]), "https://www.ebay.com");
17201
+ if (candidate && candidate.length >= 20 && candidate.length <= 180 && !candidate.endsWith("...") && !LOOKS_LIKE_URL_RE.test(candidate)) {
17202
+ return candidate;
17203
+ }
17204
+ }
17205
+ return void 0;
17206
+ };
17207
+ var inferKnownEbayBrandFromTitle = (title) => {
17208
+ const normalized = title.toLowerCase();
17209
+ return EBAY_KNOWN_MULTI_TOKEN_BRANDS.find((brand) => {
17210
+ const candidate = brand.toLowerCase();
17211
+ return normalized === candidate || normalized.startsWith(`${candidate} `);
17212
+ });
17213
+ };
17214
+ var inferKnownSingleEbayBrandFromTitle = (title) => {
17215
+ const firstToken = /^([A-Za-z][A-Za-z0-9&+']{1,30})(?:\s|$)/.exec(title)?.[1];
17216
+ return firstToken ? EBAY_KNOWN_SINGLE_TOKEN_BRANDS.get(firstToken.toLowerCase()) : void 0;
17217
+ };
17218
+ var inferEbayBrandFromTitle = (title) => {
17219
+ const framed = normalizePlainText(title).replace(/\s+[-|]\s+[^-|]+$/i, "").trim();
17220
+ const knownBrand = inferKnownEbayBrandFromTitle(framed);
17221
+ if (knownBrand) return knownBrand;
17222
+ const cleaned = framed.replace(EBAY_TITLE_BRAND_PREFIX_RE, "");
17223
+ const prefixedBrand = inferKnownEbayBrandFromTitle(cleaned);
17224
+ if (prefixedBrand) return prefixedBrand;
17225
+ const singleTokenBrand = inferKnownSingleEbayBrandFromTitle(cleaned);
17226
+ if (singleTokenBrand) return singleTokenBrand;
17227
+ const exactBrand = /^([A-Z][A-Za-z0-9&+']{1,30})$/.exec(cleaned)?.[1];
17228
+ if (exactBrand && EBAY_KNOWN_SINGLE_TOKEN_BRANDS.has(exactBrand.toLowerCase())) {
17229
+ return exactBrand;
17230
+ }
17231
+ const match = /^([A-Z][A-Za-z0-9&+']{1,30})\s+([A-Z0-9][A-Za-z0-9&+'().-]+|AirPods)\b/.exec(cleaned);
17232
+ const brand = match?.[1]?.trim();
17233
+ if (!brand || EBAY_TITLE_BRAND_STOP_WORDS.has(brand.toLowerCase())) {
17234
+ return void 0;
17235
+ }
17236
+ if (EBAY_KNOWN_SINGLE_TOKEN_BRANDS.has(brand.toLowerCase())) {
17237
+ return EBAY_KNOWN_SINGLE_TOKEN_BRANDS.get(brand.toLowerCase());
17238
+ }
17239
+ if (!/[\d-]/.test(match?.[2] ?? "")) {
17240
+ return void 0;
17241
+ }
17242
+ return brand;
17243
+ };
17244
+ var inferTitleFromContent = (content, productUrl) => {
17807
17245
  const normalized = normalizePlainText(content);
17808
17246
  if (!normalized) return void 0;
17809
17247
  const bestBuyTitle = inferBestBuyTitleFromContent(normalized);
17810
17248
  if (bestBuyTitle) {
17811
17249
  return bestBuyTitle;
17812
17250
  }
17251
+ if (productUrl && resolveShoppingProviderIdForUrl(productUrl) === "shopping/ebay") {
17252
+ const ebayTitle = inferEbayTitleFromContent(normalized);
17253
+ if (ebayTitle) {
17254
+ return ebayTitle;
17255
+ }
17256
+ }
17813
17257
  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
17258
  const candidate = normalizePlainText(storeMatch?.[1]);
17815
17259
  if (!candidate || candidate.length < 20 || LOOKS_LIKE_URL_RE.test(candidate)) {
@@ -17822,6 +17266,11 @@ var sanitizeProductBrandCandidate = (candidate, productUrl) => {
17822
17266
  if (!normalized) return void 0;
17823
17267
  try {
17824
17268
  const host = new URL(productUrl).hostname.toLowerCase();
17269
+ const hostBrand = normalizePlainText(inferBrandFromUrl(productUrl)).replace(/\.com\b/gi, "").toLowerCase();
17270
+ const candidateBrand = normalized.replace(/\.com\b/gi, "").toLowerCase();
17271
+ if (host.includes("ebay.") && hostBrand && candidateBrand === hostBrand) {
17272
+ return void 0;
17273
+ }
17825
17274
  if (!host.includes("walmart.")) {
17826
17275
  return normalized;
17827
17276
  }
@@ -17966,6 +17415,17 @@ var inferHostDefaultCurrency = (productUrl) => {
17966
17415
  return void 0;
17967
17416
  }
17968
17417
  };
17418
+ var extractProductBrandFromTitle = (title, productUrl) => {
17419
+ try {
17420
+ const host = new URL(productUrl).hostname.toLowerCase();
17421
+ if (host.includes("ebay.")) {
17422
+ return inferEbayBrandFromTitle(title);
17423
+ }
17424
+ } catch {
17425
+ return extractBrandFromTitle(title);
17426
+ }
17427
+ return extractBrandFromTitle(title);
17428
+ };
17969
17429
  var shouldSuppressMarketplacePrice = (record, productUrl, price) => {
17970
17430
  if (price.amount <= 0) return false;
17971
17431
  const expectedCurrency = inferHostDefaultCurrency(productUrl);
@@ -18090,12 +17550,12 @@ var resolveProductBrand = (record, productUrl, refreshedBrand) => {
18090
17550
  const nestedProvider = nested && typeof nested === "object" && !Array.isArray(nested) ? nested.provider : void 0;
18091
17551
  const providerBrand = typeof nestedProvider === "string" ? SHOPPING_PROVIDER_PROFILES.find((entry) => entry.id === nestedProvider)?.displayName : void 0;
18092
17552
  const candidates = [
18093
- inferBrandFromContent(record.content),
17553
+ inferBrandFromContent(record.content, productUrl),
18094
17554
  rejectRetailerBrand(refreshedBrand),
18095
17555
  rejectRetailerBrand(typeof record.attributes.brand === "string" ? record.attributes.brand : void 0),
18096
17556
  rejectRetailerBrand(typeof record.attributes.site_name === "string" ? record.attributes.site_name : void 0),
18097
17557
  rejectRetailerBrand(providerBrand && providerBrand !== "Others" ? providerBrand : void 0),
18098
- extractBrandFromTitle(record.title),
17558
+ extractProductBrandFromTitle(record.title, productUrl),
18099
17559
  inferBrandFromUrl(productUrl)
18100
17560
  ].map((entry) => sanitizeProductBrandCandidate(entry, productUrl)).filter(Boolean);
18101
17561
  return candidates[0] || "unknown";
@@ -18105,7 +17565,7 @@ var resolveProductTitle = (record, productUrl, brand, refreshedTitle) => {
18105
17565
  const nestedTitle = nested && typeof nested === "object" && !Array.isArray(nested) ? nested.title : void 0;
18106
17566
  const candidates = [
18107
17567
  refreshedTitle,
18108
- inferTitleFromContent(record.content),
17568
+ inferTitleFromContent(record.content, productUrl),
18109
17569
  record.title,
18110
17570
  typeof nestedTitle === "string" ? nestedTitle : void 0,
18111
17571
  typeof record.attributes.description === "string" ? record.attributes.description.split(/(?<=[.!?])\s+/)[0] : void 0,
@@ -18154,13 +17614,31 @@ var resolveShoppingSourceForUrl = (url) => {
18154
17614
  return "web";
18155
17615
  }
18156
17616
  };
17617
+ var RESEARCH_REJECTED_CANDIDATE_LIMIT = 25;
17618
+ var isResearchPrivacyPreferenceShell = (content) => {
17619
+ const matchIndex = content.slice(0, 400).search(RESEARCH_PRIVACY_PREFERENCE_SHELL_RE);
17620
+ return matchIndex >= 0 && matchIndex <= 80 && !RESEARCH_PRIVACY_RECOVERED_CONTENT_RE.test(content);
17621
+ };
17622
+ var classifyResearchDeadEndUrl = (value) => {
17623
+ if (!value) return null;
17624
+ return classifyResearchDestinationRejection(value);
17625
+ };
17626
+ var isResearchLoginShellRecord = (args) => {
17627
+ if (args.url.includes("/login")) {
17628
+ return true;
17629
+ }
17630
+ if (!RESEARCH_LOGIN_SHELL_RE.test(args.combined)) {
17631
+ return false;
17632
+ }
17633
+ return args.content.length <= RESEARCH_LOGIN_SHELL_MAX_CONTENT_CHARS || RESEARCH_LOGIN_REQUIRED_RE.test(args.combined);
17634
+ };
18157
17635
  var classifyResearchShellRecord = (record) => {
18158
17636
  const retrievalPath = typeof record.attributes.retrievalPath === "string" ? record.attributes.retrievalPath : "";
18159
17637
  const url = typeof record.url === "string" ? record.url.trim().toLowerCase() : "";
18160
17638
  const title = normalizePlainText(record.title).toLowerCase();
18161
17639
  const content = normalizePlainText(record.content).toLowerCase();
18162
17640
  const combined = `${title} ${content}`.trim();
18163
- if (RESEARCH_LOGIN_SHELL_RE.test(combined) || url.includes("/login")) {
17641
+ if (isResearchLoginShellRecord({ url, combined, content })) {
18164
17642
  return "login_shell";
18165
17643
  }
18166
17644
  if (RESEARCH_JS_REQUIRED_RE.test(combined)) {
@@ -18169,6 +17647,13 @@ var classifyResearchShellRecord = (record) => {
18169
17647
  if (RESEARCH_NOT_FOUND_SHELL_RE.test(combined)) {
18170
17648
  return "not_found_shell";
18171
17649
  }
17650
+ if (isResearchPrivacyPreferenceShell(content)) {
17651
+ return "privacy_preference_shell";
17652
+ }
17653
+ const deadEndUrlReason = classifyResearchDeadEndUrl(url);
17654
+ if (deadEndUrlReason) {
17655
+ return deadEndUrlReason;
17656
+ }
18172
17657
  if (!retrievalPath) {
18173
17658
  return null;
18174
17659
  }
@@ -18191,18 +17676,47 @@ var classifyResearchShellRecord = (record) => {
18191
17676
  };
18192
17677
  var sanitizeResearchRecords = (records) => {
18193
17678
  const reasonDistribution = {};
17679
+ const rejectedCandidates = [];
18194
17680
  const sanitizedRecords = records.filter((record) => {
18195
17681
  const reason = classifyResearchShellRecord(record);
18196
17682
  if (!reason) return true;
18197
17683
  reasonDistribution[reason] = (reasonDistribution[reason] ?? 0) + 1;
17684
+ if (rejectedCandidates.length < RESEARCH_REJECTED_CANDIDATE_LIMIT) {
17685
+ const retrievalPath = typeof record.attributes.retrievalPath === "string" ? record.attributes.retrievalPath : void 0;
17686
+ rejectedCandidates.push({
17687
+ provider: record.provider,
17688
+ source: record.source,
17689
+ reason,
17690
+ replacement_status: "rejected_before_synthesis",
17691
+ ...retrievalPath ? { retrievalPath } : {},
17692
+ ...record.title ? { title: record.title } : {},
17693
+ ...record.url ? { url: record.url } : {}
17694
+ });
17695
+ }
18198
17696
  return false;
18199
17697
  });
18200
17698
  return {
18201
17699
  records: sanitizedRecords,
18202
17700
  sanitizedCount: records.length - sanitizedRecords.length,
18203
- reasonDistribution
17701
+ reasonDistribution,
17702
+ rejectedCandidates
17703
+ };
17704
+ };
17705
+ var rejectedCandidateFromFailure = (failure) => {
17706
+ const details = failure.error.details ?? {};
17707
+ if (details.fallbackOutputReason !== "research_dead_end_shell") return null;
17708
+ const retrievalPath = typeof details.retrievalPath === "string" ? details.retrievalPath : void 0;
17709
+ const url = typeof details.url === "string" ? details.url : void 0;
17710
+ return {
17711
+ provider: failure.provider,
17712
+ source: failure.source,
17713
+ reason: "research_dead_end_shell",
17714
+ replacement_status: "rejected_before_synthesis",
17715
+ ...retrievalPath ? { retrievalPath } : {},
17716
+ ...url ? { url } : {}
18204
17717
  };
18205
17718
  };
17719
+ var rejectedCandidatesFromFailures = (failures) => failures.map(rejectedCandidateFromFailure).filter((candidate) => candidate !== null);
18206
17720
  var isValidHttpUrl3 = (url) => {
18207
17721
  try {
18208
17722
  const parsed = new URL(url.trim());
@@ -18228,6 +17742,13 @@ var classifyInvalidProductTarget = (record) => {
18228
17742
  message: "Product target appears to be a not-found page"
18229
17743
  };
18230
17744
  }
17745
+ const providerId = resolveShoppingProviderIdForUrl(record.url ?? "");
17746
+ if (providerId === "shopping/bestbuy" && combined.toLowerCase().includes("something went wrong") && BESTBUY_PDP_ERROR_SHELL_RE.test(combined)) {
17747
+ return {
17748
+ reason: "provider_error_shell",
17749
+ message: "Best Buy product target returned a generic error shell"
17750
+ };
17751
+ }
18231
17752
  return null;
18232
17753
  };
18233
17754
  var runResearchWorkflow = async (runtime, input) => {
@@ -18235,7 +17756,9 @@ var runResearchWorkflow = async (runtime, input) => {
18235
17756
  if (envelope.kind !== "research") {
18236
17757
  throw new Error(`Research workflow envelope kind mismatch. Expected research but received ${envelope.kind}.`);
18237
17758
  }
18238
- const workflowInput = envelope.input;
17759
+ const rawWorkflowInput = envelope.input;
17760
+ const artifactRoot = resolveWorkflowArtifactRoot(rawWorkflowInput.outputDir);
17761
+ const workflowInput = { ...rawWorkflowInput, outputDir: artifactRoot };
18239
17762
  let trace = [
18240
17763
  ...envelope.trace ?? [],
18241
17764
  {
@@ -18291,21 +17814,23 @@ var runResearchWorkflow = async (runtime, input) => {
18291
17814
  }
18292
17815
  });
18293
17816
  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
- );
17817
+ const rawRecords = [
17818
+ ...execution.searchRuns.flatMap((run) => run.result.records),
17819
+ ...execution.followUpRuns.flatMap((run) => run.result.records)
17820
+ ];
17821
+ const rawFailures = [
17822
+ ...execution.searchRuns.flatMap((run) => run.result.failures),
17823
+ ...execution.followUpRuns.flatMap((run) => run.result.failures)
17824
+ ];
17825
+ const mergedRecords = removeExcludedProviders(rawRecords, excludedProviderSet);
18301
17826
  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
- );
17827
+ const mergedFailures = removeExcludedProviders(rawFailures, excludedProviderSet);
17828
+ const rejectedFailureCandidates = rejectedCandidatesFromFailures(mergedFailures);
17829
+ const rejectedCandidates = [
17830
+ ...sanitizedRecords.rejectedCandidates,
17831
+ ...rejectedFailureCandidates
17832
+ ].slice(0, RESEARCH_REJECTED_CANDIDATE_LIMIT);
17833
+ const rejectedCandidateCount2 = sanitizedRecords.sanitizedCount + rejectedFailureCandidates.length;
18309
17834
  const reasonCodeDistribution = summarizeReasonCodeDistribution(mergedFailures);
18310
17835
  const transcriptStrategyFailures = summarizeTranscriptStrategyFailures(mergedFailures);
18311
17836
  const evaluationNow = /* @__PURE__ */ new Date();
@@ -18313,8 +17838,8 @@ var runResearchWorkflow = async (runtime, input) => {
18313
17838
  const enriched = enrichResearchRecords(withinTimebox, plan.compiled.timebox, evaluationNow);
18314
17839
  const deduped = dedupeResearchRecords(enriched);
18315
17840
  const ranked = rankResearchRecords(deduped);
18316
- const noUsableResearchRecords = mergedRecords.length > 0 && mergedFailures.length === 0 && ranked.length === 0;
18317
17841
  const cookieDiagnostics = summarizeCookieDiagnostics(mergedFailures, mergedRecords);
17842
+ const challengeOrchestration = summarizeChallengeOrchestration(mergedFailures, mergedRecords);
18318
17843
  const transcriptStrategyDetailDistribution = summarizeTranscriptStrategyDetailDistribution(ranked);
18319
17844
  const transcriptDurability = summarizeTranscriptDurability(ranked, mergedFailures);
18320
17845
  const antiBotPressure = summarizeAntiBotPressure(mergedFailures);
@@ -18322,16 +17847,16 @@ var runResearchWorkflow = async (runtime, input) => {
18322
17847
  ...plan.compiled.timebox,
18323
17848
  to: new Date(Math.max(new Date(plan.compiled.timebox.to).getTime(), evaluationNow.getTime())).toISOString()
18324
17849
  } : plan.compiled.timebox;
18325
- if (noUsableResearchRecords && sanitizedRecords.records.length === 0) {
17850
+ if (mergedRecords.length > 0 && sanitizedRecords.records.length === 0) {
18326
17851
  const sanitizedReasons = Object.entries(sanitizedRecords.reasonDistribution).map(([reason, count]) => `${reason}:${count}`).join(",");
18327
17852
  throw new Error(
18328
17853
  `Research workflow produced only shell records and no usable results (${sanitizedReasons || "sanitized"}).`
18329
17854
  );
18330
17855
  }
18331
- if (noUsableResearchRecords && sanitizedRecords.records.length > 0 && withinTimebox.length === 0) {
17856
+ if (sanitizedRecords.records.length > 0 && withinTimebox.length === 0) {
18332
17857
  throw new Error("Research workflow produced no usable in-timebox results after sanitization.");
18333
17858
  }
18334
- if (noUsableResearchRecords) {
17859
+ if (ranked.length === 0) {
18335
17860
  throw new Error("Research workflow produced no usable results after post-processing.");
18336
17861
  }
18337
17862
  const primaryConstraintFailures = selectResearchPrimaryConstraintFailures(mergedFailures);
@@ -18345,8 +17870,10 @@ var runResearchWorkflow = async (runtime, input) => {
18345
17870
  metrics: {
18346
17871
  total_records: mergedRecords.length,
18347
17872
  sanitized_records: sanitizedRecords.sanitizedCount,
17873
+ rejected_candidate_count: rejectedCandidateCount2,
18348
17874
  sanitized_reason_distribution: sanitizedRecords.reasonDistribution,
18349
17875
  sanitizedReasonDistribution: sanitizedRecords.reasonDistribution,
17876
+ rejected_candidate_sample_size: rejectedCandidates.length,
18350
17877
  within_timebox: withinTimebox.length,
18351
17878
  final_records: ranked.length,
18352
17879
  failed_sources: execution.searchRuns.filter((run) => !run.result.ok).map((run) => run.source),
@@ -18361,15 +17888,22 @@ var runResearchWorkflow = async (runtime, input) => {
18361
17888
  transcriptDurability,
18362
17889
  cookie_diagnostics: cookieDiagnostics,
18363
17890
  cookieDiagnostics,
17891
+ challenge_orchestration: challengeOrchestration,
17892
+ challengeOrchestration,
18364
17893
  anti_bot_pressure: antiBotPressure,
18365
17894
  antiBotPressure
18366
17895
  },
18367
17896
  failures: mergedFailures,
17897
+ rejected_candidates: rejectedCandidates,
17898
+ rejectedCandidates,
18368
17899
  alerts: buildWorkflowAlerts(runtime, mergedFailures)
18369
17900
  }, primaryConstraintFailures);
18370
17901
  const handoff = buildResearchSuccessHandoff({
18371
17902
  topic: plan.compiled.topic,
18372
- browserMode: workflowInput.browserMode
17903
+ browserMode: workflowInput.browserMode,
17904
+ failures: mergedFailures,
17905
+ cookieDiagnostics,
17906
+ challengeOrchestration
18373
17907
  });
18374
17908
  const responseMeta = withFollowthroughMeta(meta, handoff);
18375
17909
  const rendered = renderResearch({
@@ -18380,7 +17914,7 @@ var runResearchWorkflow = async (runtime, input) => {
18380
17914
  });
18381
17915
  const bundle = await createArtifactBundle({
18382
17916
  namespace: "research",
18383
- outputDir: workflowInput.outputDir,
17917
+ outputDir: artifactRoot,
18384
17918
  ttlHours: workflowInput.ttlHours,
18385
17919
  files: rendered.files
18386
17920
  });
@@ -18388,7 +17922,7 @@ var runResearchWorkflow = async (runtime, input) => {
18388
17922
  return {
18389
17923
  ...rendered.response,
18390
17924
  ...handoff,
18391
- path: bundle.basePath,
17925
+ artifact_path: bundle.basePath,
18392
17926
  records: ranked,
18393
17927
  meta: {
18394
17928
  ...responseMeta,
@@ -18412,7 +17946,9 @@ var runShoppingWorkflow = async (runtime, input) => {
18412
17946
  if (envelope.kind !== "shopping") {
18413
17947
  throw new Error(`Shopping workflow envelope kind mismatch. Expected shopping but received ${envelope.kind}.`);
18414
17948
  }
18415
- const workflowInput = envelope.input;
17949
+ const rawWorkflowInput = envelope.input;
17950
+ const artifactRoot = resolveWorkflowArtifactRoot(rawWorkflowInput.outputDir);
17951
+ const workflowInput = { ...rawWorkflowInput, outputDir: artifactRoot };
18416
17952
  const remainingTimeoutMs = createRemainingTimeoutResolver(workflowInput.timeoutMs);
18417
17953
  let trace = [
18418
17954
  ...envelope.trace ?? [],
@@ -18566,7 +18102,7 @@ var runShoppingWorkflow = async (runtime, input) => {
18566
18102
  });
18567
18103
  const bundle = await createArtifactBundle({
18568
18104
  namespace: "shopping",
18569
- outputDir: workflowInput.outputDir,
18105
+ outputDir: artifactRoot,
18570
18106
  ttlHours: workflowInput.ttlHours,
18571
18107
  files: rendered.files
18572
18108
  });
@@ -18574,7 +18110,7 @@ var runShoppingWorkflow = async (runtime, input) => {
18574
18110
  return {
18575
18111
  ...rendered.response,
18576
18112
  ...handoff,
18577
- path: bundle.basePath,
18113
+ artifact_path: bundle.basePath,
18578
18114
  offers,
18579
18115
  meta: {
18580
18116
  ...responseMeta,
@@ -18594,7 +18130,9 @@ var runShoppingWorkflow = async (runtime, input) => {
18594
18130
  };
18595
18131
  };
18596
18132
  var runInspiredesignWorkflow = async (runtime, input, options = {}) => {
18597
- const { envelope, workflowInput } = buildInspiredesignEnvelope(input);
18133
+ const { envelope, workflowInput: rawWorkflowInput } = buildInspiredesignEnvelope(input);
18134
+ const artifactRoot = resolveWorkflowArtifactRoot(rawWorkflowInput.outputDir);
18135
+ const workflowInput = { ...rawWorkflowInput, outputDir: artifactRoot };
18598
18136
  const remainingTimeoutMs = createRemainingTimeoutResolver(workflowInput.timeoutMs);
18599
18137
  let trace = appendWorkflowTrace(envelope.trace ?? [], "compile", "compile_started", {
18600
18138
  kind: "inspiredesign"
@@ -18668,14 +18206,14 @@ var runInspiredesignWorkflow = async (runtime, input, options = {}) => {
18668
18206
  });
18669
18207
  const bundle = await createArtifactBundle({
18670
18208
  namespace: "inspiredesign",
18671
- outputDir: workflowInput.outputDir,
18209
+ outputDir: artifactRoot,
18672
18210
  ttlHours: workflowInput.ttlHours,
18673
18211
  files: rendered.files
18674
18212
  });
18675
18213
  if (workflowInput.mode === "path") {
18676
18214
  return {
18677
18215
  ...rendered.response,
18678
- path: bundle.basePath,
18216
+ artifact_path: bundle.basePath,
18679
18217
  meta: {
18680
18218
  ...meta,
18681
18219
  artifact_manifest: bundle.manifest
@@ -18724,7 +18262,9 @@ var runProductVideoWorkflow = async (runtime, input, options = {}) => {
18724
18262
  }
18725
18263
  }
18726
18264
  ];
18727
- const workflowInput = plan.input;
18265
+ const rawWorkflowInput = plan.input;
18266
+ const productVideoArtifactRoot = resolveWorkflowArtifactRoot(rawWorkflowInput.output_dir);
18267
+ const workflowInput = { ...rawWorkflowInput, output_dir: productVideoArtifactRoot };
18728
18268
  const includeScreenshots = plan.compiled.includeScreenshots;
18729
18269
  const includeAllImages = plan.compiled.includeAllImages;
18730
18270
  const includeCopy = plan.compiled.includeCopy;
@@ -18804,6 +18344,8 @@ var runProductVideoWorkflow = async (runtime, input, options = {}) => {
18804
18344
  query: resolveStep.input.product_name,
18805
18345
  providers: providerHint ? [providerHint] : void 0,
18806
18346
  mode: "json",
18347
+ outputDir: productVideoArtifactRoot,
18348
+ ttlHours: workflowInput.ttl_hours,
18807
18349
  ...timeoutOptions,
18808
18350
  browserMode: workflowInput.browserMode,
18809
18351
  useCookies: workflowInput.useCookies,
@@ -19025,11 +18567,10 @@ var runProductVideoWorkflow = async (runtime, input, options = {}) => {
19025
18567
  files: files.length
19026
18568
  });
19027
18569
  const bundle = await createArtifactBundle({
19028
- namespace: "product-assets",
19029
- outputDir: workflowInput.output_dir,
18570
+ namespace: "product-video",
18571
+ outputDir: productVideoArtifactRoot,
19030
18572
  ttlHours: workflowInput.ttl_hours,
19031
- files,
19032
- manifestFileName: "bundle-manifest.json"
18573
+ files
19033
18574
  });
19034
18575
  const reasonCodeDistribution = summarizeReasonCodeDistribution(details.failures);
19035
18576
  const transcriptStrategyFailures = summarizeTranscriptStrategyFailures(details.failures);
@@ -19069,7 +18610,7 @@ var runProductVideoWorkflow = async (runtime, input, options = {}) => {
19069
18610
  }, handoff);
19070
18611
  return {
19071
18612
  ...handoff,
19072
- path: bundle.basePath,
18613
+ artifact_path: bundle.basePath,
19073
18614
  manifest: manifestPayload,
19074
18615
  product: productPayload,
19075
18616
  pricing,
@@ -19123,7 +18664,10 @@ var buildSocialDefaultTraversal = (platform, options) => {
19123
18664
  ...existing ?? {}
19124
18665
  };
19125
18666
  };
19126
- var shouldRecoverSocialDocumentIssue = (platform, operation, issue) => {
18667
+ var shouldRecoverSocialDocumentIssue = (platform, operation, issue, context) => {
18668
+ if (isResearchSearchDiscoveryRun(operation, context)) {
18669
+ return false;
18670
+ }
19127
18671
  if (SOCIAL_BROWSER_RECOVERY_REASON_CODES.has(issue.reasonCode)) {
19128
18672
  return true;
19129
18673
  }
@@ -19132,7 +18676,7 @@ var shouldRecoverSocialDocumentIssue = (platform, operation, issue) => {
19132
18676
  }
19133
18677
  return operation === "search" && SEARCH_RENDER_RECOVERY_SOCIAL_PLATFORMS.has(platform) && issue.reasonCode === "env_limited" && issue.constraint?.kind === "render_required";
19134
18678
  };
19135
- var isJsonRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
18679
+ var isJsonRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
19136
18680
  var unwrapWorkflowResumeEnvelope = (kind, input) => {
19137
18681
  if (!isWorkflowResumePayload(input)) {
19138
18682
  throw new ProviderRuntimeError(
@@ -19185,10 +18729,10 @@ var Semaphore = class {
19185
18729
  this.active += 1;
19186
18730
  return;
19187
18731
  }
19188
- await new Promise((resolve2) => {
18732
+ await new Promise((resolve) => {
19189
18733
  this.queue.push(() => {
19190
18734
  this.active += 1;
19191
- resolve2();
18735
+ resolve();
19192
18736
  });
19193
18737
  });
19194
18738
  }
@@ -19367,7 +18911,7 @@ var isBlockedRedditFallbackLink = (link) => {
19367
18911
  return false;
19368
18912
  }
19369
18913
  };
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);
18914
+ 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
18915
  var shouldReturnCompletedFallbackForOwnerReview = (args) => args.document.browserFallback !== void 0 && args.ownerReview?.({
19372
18916
  providerId: args.providerId,
19373
18917
  source: args.source,
@@ -19440,6 +18984,8 @@ var describeDefaultFetchedIssue = (document) => {
19440
18984
  };
19441
18985
  };
19442
18986
  var shouldRecoverDefaultFetchedIssue = (issue) => issue.reasonCode === "challenge_detected" || issue.reasonCode === "token_required" || issue.reasonCode === "env_limited" && !!issue.constraint;
18987
+ var isUsableCommunityRedditSearchFallback = (args) => args.source === "community" && args.operation === "search" && args.document.browserFallback !== void 0 && isFirstPartySocialSearchRoute("reddit", args.document.url) && hasUsableCommunityFallbackSearchContent(args.pageMessage);
18988
+ var isResearchSearchDiscoveryRun = (operation, context) => operation === "search" && context.suspendedIntent?.kind === "workflow.research";
19443
18989
  var toDefaultFetchedIssueError = (args) => {
19444
18990
  const reasonCode = args.issueDetails.issue?.reasonCode ?? "env_limited";
19445
18991
  return new ProviderRuntimeError(
@@ -19464,6 +19010,14 @@ var resolveDefaultFallbackDocumentIfNeeded = async (args) => {
19464
19010
  if (!initialIssue) {
19465
19011
  return { document: currentDocument, ...described };
19466
19012
  }
19013
+ if (isUsableCommunityRedditSearchFallback({
19014
+ source: args.source,
19015
+ operation: args.operation,
19016
+ document: currentDocument,
19017
+ pageMessage: described.pageMessage
19018
+ })) {
19019
+ return { document: currentDocument, ...described, issue: null };
19020
+ }
19467
19021
  if (shouldReturnCompletedFallbackForOwnerReview({ ...args, document: currentDocument })) {
19468
19022
  return { document: currentDocument, ...described };
19469
19023
  }
@@ -19506,6 +19060,14 @@ var resolveDefaultFallbackDocumentIfNeeded = async (args) => {
19506
19060
  ownerReview: args.ownerReview
19507
19061
  });
19508
19062
  described = describeDefaultFetchedIssue(currentDocument);
19063
+ if (isUsableCommunityRedditSearchFallback({
19064
+ source: args.source,
19065
+ operation: args.operation,
19066
+ document: currentDocument,
19067
+ pageMessage: described.pageMessage
19068
+ })) {
19069
+ return { document: currentDocument, ...described, issue: null };
19070
+ }
19509
19071
  if (shouldReturnCompletedFallbackForOwnerReview({ ...args, document: currentDocument })) {
19510
19072
  return { document: currentDocument, ...described };
19511
19073
  }
@@ -19522,16 +19084,32 @@ var resolveDefaultFallbackDocumentIfNeeded = async (args) => {
19522
19084
  };
19523
19085
  var COMMUNITY_SEARCH_LINK_SCAN_MULTIPLIER = 4;
19524
19086
  var MIN_COMMUNITY_SEARCH_LINK_SCAN = 12;
19525
- var resolveCommunitySearchLinks = (document, limit) => {
19087
+ var resolveCommunitySearchLinks = (document, limit, researchContext) => {
19526
19088
  const scanLimit = Math.max(limit * COMMUNITY_SEARCH_LINK_SCAN_MULTIPLIER, MIN_COMMUNITY_SEARCH_LINK_SCAN);
19527
19089
  const links = prioritizeSocialSearchLinks(
19528
19090
  "reddit",
19529
19091
  document.url,
19530
19092
  dedupeLinks2(document.links, document.url, scanLimit)
19531
19093
  );
19532
- return links.filter((url) => isAllowedSocialSearchExpansionUrl("reddit", url)).slice(0, limit);
19094
+ return links.filter((url) => researchContext ? isLikelyResearchDestinationUrl(url) : isLikelyDocumentUrl(url)).filter((url) => isAllowedSocialSearchExpansionUrl("reddit", url)).slice(0, limit);
19095
+ };
19096
+ var shouldRejectBlockedCommunityFallback = (document, links, pageMessage) => document.browserFallback !== void 0 && isFirstPartySocialSearchRoute("reddit", document.url) && links.length === 0 && !hasUsableCommunityFallbackSearchContent(pageMessage);
19097
+ var COMMUNITY_FALLBACK_SEARCH_MIN_CHARS = 120;
19098
+ var COMMUNITY_FALLBACK_SEARCH_EVIDENCE_RE = [
19099
+ /\bshowing results for\b/i,
19100
+ /\bsearch for\b/i,
19101
+ /\banswers?\b/i,
19102
+ /\bsources?:\s+/i,
19103
+ /\br\/[a-z0-9_]+\b/i
19104
+ ];
19105
+ var hasUsableCommunityFallbackSearchContent = (pageMessage) => {
19106
+ const trimmed = pageMessage.trim();
19107
+ if (trimmed.length < COMMUNITY_FALLBACK_SEARCH_MIN_CHARS) {
19108
+ return false;
19109
+ }
19110
+ const evidenceCount = COMMUNITY_FALLBACK_SEARCH_EVIDENCE_RE.filter((pattern) => pattern.test(trimmed)).length;
19111
+ return evidenceCount >= 2;
19533
19112
  };
19534
- var shouldRejectBlockedCommunityFallback = (document, links) => document.browserFallback !== void 0 && isFirstPartySocialSearchRoute("reddit", document.url) && links.length === 0;
19535
19113
  var toCommunityFallbackSearchError = (args) => {
19536
19114
  const reasonCode = args.document.browserFallback?.reasonCode ?? "env_limited";
19537
19115
  return new ProviderRuntimeError(
@@ -19646,6 +19224,36 @@ var dedupeLinks2 = (links, baseUrl, limit) => {
19646
19224
  }
19647
19225
  return deduped;
19648
19226
  };
19227
+ var isUsableWebSearchResultUrl = (url, researchContext) => !isDuckDuckGoSearchShellUrl(url) && (researchContext ? isLikelyResearchDestinationUrl(url) : isLikelyDocumentUrl(url));
19228
+ var isResearchWorkflowContext = (context) => context.suspendedIntent?.kind === "workflow.research";
19229
+ var resolveWebSearchResultLinks = (document, limit, researchContext, includeDocumentUrl) => {
19230
+ const links = dedupeLinks2(document.links, document.url, limit * 2);
19231
+ const candidates = includeDocumentUrl ? [document.url, ...links] : links;
19232
+ const seen = /* @__PURE__ */ new Set();
19233
+ return candidates.filter((url) => {
19234
+ const keep = isUsableWebSearchResultUrl(url, researchContext) && !seen.has(url);
19235
+ if (keep) seen.add(url);
19236
+ return keep;
19237
+ }).slice(0, limit);
19238
+ };
19239
+ var toResearchDeadEndSearchError = (args) => new ProviderRuntimeError(
19240
+ "unavailable",
19241
+ `Research search resolved only dead-end pages for ${args.document.url}`,
19242
+ {
19243
+ provider: args.providerId,
19244
+ source: args.source,
19245
+ retryable: false,
19246
+ reasonCode: "policy_blocked",
19247
+ details: {
19248
+ status: args.document.status,
19249
+ url: args.document.url,
19250
+ ...args.document.browserFallback?.reasonCode ? { browserFallbackReasonCode: args.document.browserFallback.reasonCode } : {},
19251
+ retrievalPath: args.retrievalPath,
19252
+ fallbackOutputReason: "research_dead_end_shell",
19253
+ ...browserFallbackObservationDetails(args.document.browserFallback)
19254
+ }
19255
+ }
19256
+ );
19649
19257
  var RUNTIME_FALLBACK_ERROR_CODES = /* @__PURE__ */ new Set([
19650
19258
  "auth",
19651
19259
  "rate_limited",
@@ -19785,6 +19393,7 @@ var readResponseTextWithAbort = async (response, args) => {
19785
19393
  }
19786
19394
  };
19787
19395
  var fetchRuntimeDocumentWithFallback = async (args) => {
19396
+ const researchSearchDiscovery = args.context !== void 0 && isResearchSearchDiscoveryRun(args.operation, args.context);
19788
19397
  const fallbackPort = args.context?.browserFallbackPort ?? args.browserFallbackPort;
19789
19398
  const runtimePolicy = args.context?.runtimePolicy;
19790
19399
  const forcedBrowserTransport = runtimePolicy?.browser.forceTransport === true;
@@ -19851,6 +19460,9 @@ var fetchRuntimeDocumentWithFallback = async (args) => {
19851
19460
  if (args.recoverRuntimeErrors === false) {
19852
19461
  throw error;
19853
19462
  }
19463
+ if (researchSearchDiscovery) {
19464
+ throw error;
19465
+ }
19854
19466
  if (!fallbackPort) {
19855
19467
  throw error;
19856
19468
  }
@@ -20216,8 +19828,9 @@ var ProviderRuntime = class {
20216
19828
  };
20217
19829
  }
20218
19830
  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))
19831
+ const results = await this.mapProvidersBounded(
19832
+ providers,
19833
+ (provider) => this.invokeProvider(provider, operation, input, trace, timeoutMs, tierMetadata, runOptions)
20221
19834
  );
20222
19835
  const records = [];
20223
19836
  const failures = [];
@@ -20260,8 +19873,9 @@ var ProviderRuntime = class {
20260
19873
  }
20261
19874
  });
20262
19875
  }
20263
- const fallbackResults = await Promise.all(
20264
- fallbackProviders.map((provider) => this.invokeProvider(provider, operation, input, trace, timeoutMs, fallbackTier, runOptions))
19876
+ const fallbackResults = await this.mapProvidersBounded(
19877
+ fallbackProviders,
19878
+ (provider) => this.invokeProvider(provider, operation, input, trace, timeoutMs, fallbackTier, runOptions)
20265
19879
  );
20266
19880
  for (const result of fallbackResults) {
20267
19881
  attempted += 1;
@@ -20308,6 +19922,9 @@ var ProviderRuntime = class {
20308
19922
  ...!ok && failures[0] ? { error: failures[0].error } : {}
20309
19923
  };
20310
19924
  }
19925
+ async mapProvidersBounded(providers, task) {
19926
+ return mapBounded(providers, this.budgets.concurrency.global, task);
19927
+ }
20311
19928
  async invokeProvider(provider, operation, input, trace, timeoutMs, tierMetadata, runOptions) {
20312
19929
  const startedAt = Date.now();
20313
19930
  const scopeKey = this.resolveScopeKey(provider.id, operation, input);
@@ -20691,7 +20308,7 @@ var ProviderRuntime = class {
20691
20308
  }
20692
20309
  async resumeSuspendedIntent(intent, options) {
20693
20310
  const input = intent.input;
20694
- if (!isJsonRecord7(input)) {
20311
+ if (!isJsonRecord8(input)) {
20695
20312
  throw new ProviderRuntimeError("invalid_input", "Suspended intent input is missing or malformed.", {
20696
20313
  retryable: false
20697
20314
  });
@@ -20779,7 +20396,7 @@ var ProviderRuntime = class {
20779
20396
  return providers.some((provider) => provider.source !== "web");
20780
20397
  }
20781
20398
  resolveScopeKey(providerId, operation, input) {
20782
- const extractHost2 = (value) => {
20399
+ const extractHost = (value) => {
20783
20400
  if (!value) return null;
20784
20401
  try {
20785
20402
  return new URL(value).hostname.toLowerCase();
@@ -20788,14 +20405,14 @@ var ProviderRuntime = class {
20788
20405
  }
20789
20406
  };
20790
20407
  if (operation === "fetch") {
20791
- return extractHost2(input.url) ?? providerId;
20408
+ return extractHost(input.url) ?? providerId;
20792
20409
  }
20793
20410
  if (operation === "crawl") {
20794
20411
  const first = input.seedUrls[0];
20795
- return extractHost2(first) ?? providerId;
20412
+ return extractHost(first) ?? providerId;
20796
20413
  }
20797
20414
  if (operation === "search") {
20798
- return extractHost2(input.query) ?? providerId;
20415
+ return extractHost(input.query) ?? providerId;
20799
20416
  }
20800
20417
  return providerId;
20801
20418
  }
@@ -20973,9 +20590,18 @@ var withDefaultWebOptions = (options, browserFallbackPort) => {
20973
20590
  browserFallbackPort
20974
20591
  });
20975
20592
  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);
20593
+ const researchContext = isResearchWorkflowContext(context);
20594
+ const links = resolveWebSearchResultLinks(document, limit, researchContext, researchContext || isHttpUrl7(query));
20977
20595
  const searchPath = isHttpUrl7(query) ? "web:search:url" : "web:search:index";
20978
20596
  if (links.length === 0) {
20597
+ if (researchContext && !isUsableWebSearchResultUrl(document.url, true)) {
20598
+ throw toResearchDeadEndSearchError({
20599
+ providerId,
20600
+ source: "web",
20601
+ document,
20602
+ retrievalPath: searchPath
20603
+ });
20604
+ }
20979
20605
  return [{
20980
20606
  url: document.url,
20981
20607
  title: document.url,
@@ -21018,6 +20644,14 @@ var withDefaultCommunityOptions = (options, browserFallbackPort) => {
21018
20644
  ...browserFallbackObservationAttributes(args.document.browserFallback)
21019
20645
  };
21020
20646
  if (args.links.length === 0) {
20647
+ if (isResearchWorkflowContext(args.context) && !isLikelyResearchDestinationUrl(args.document.url)) {
20648
+ throw toResearchDeadEndSearchError({
20649
+ providerId,
20650
+ source: "community",
20651
+ document: args.document,
20652
+ retrievalPath: searchPath
20653
+ });
20654
+ }
21021
20655
  return [{
21022
20656
  url: args.document.url,
21023
20657
  title: isHttpUrl7(args.query) ? args.document.url : `Community search: ${args.query}`,
@@ -21059,8 +20693,8 @@ var withDefaultCommunityOptions = (options, browserFallbackPort) => {
21059
20693
  browserFallbackPort,
21060
20694
  ownerReview: shouldOwnerReviewCommunityRedditSearchFallback
21061
20695
  });
21062
- const links = resolveCommunitySearchLinks(resolvedDocument, limit);
21063
- if (shouldRejectBlockedCommunityFallback(resolvedDocument, links)) {
20696
+ const links = resolveCommunitySearchLinks(resolvedDocument, limit, isResearchWorkflowContext(context));
20697
+ if (shouldRejectBlockedCommunityFallback(resolvedDocument, links, pageMessage)) {
21064
20698
  throw toCommunityFallbackSearchError({
21065
20699
  providerId,
21066
20700
  document: resolvedDocument,
@@ -21072,7 +20706,8 @@ var withDefaultCommunityOptions = (options, browserFallbackPort) => {
21072
20706
  page,
21073
20707
  document: resolvedDocument,
21074
20708
  pageMessage,
21075
- links
20709
+ links,
20710
+ context
21076
20711
  });
21077
20712
  }),
21078
20713
  fetch: options?.fetch ?? (async (input, context) => {
@@ -21185,7 +20820,7 @@ var withDefaultSocialPlatformOptions = (platform, options, browserFallbackPort)
21185
20820
  if (initialIssue.reasonCode === "env_limited" && !initialIssue.constraint) {
21186
20821
  return { document: currentDocument, ...described };
21187
20822
  }
21188
- if (!shouldRecoverSocialDocumentIssue(platform, operation, initialIssue)) {
20823
+ if (!shouldRecoverSocialDocumentIssue(platform, operation, initialIssue, context)) {
21189
20824
  throw toIssueError(currentDocument, described);
21190
20825
  }
21191
20826
  const fallback = await resolveProviderBrowserFallback({
@@ -21374,19 +21009,6 @@ var mergeBudgets = (base, partial) => {
21374
21009
  };
21375
21010
 
21376
21011
  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
21012
  CHALLENGE_AUTOMATION_MODES,
21391
21013
  isChallengeAutomationMode,
21392
21014
  resolveChallengeAutomationPolicy,
@@ -21411,7 +21033,6 @@ export {
21411
21033
  CANVAS_BROWSER_VALIDATION_MODES,
21412
21034
  CANVAS_VALIDATION_TARGET_BLOCK_ON_CODES,
21413
21035
  CANVAS_PUBLIC_WARNING_CLASSES,
21414
- summarizePrimaryProviderIssue,
21415
21036
  createTraceContext,
21416
21037
  clampConfidence,
21417
21038
  createStableRecordId,
@@ -21439,17 +21060,11 @@ export {
21439
21060
  createShoppingProviders,
21440
21061
  createShoppingProviderById,
21441
21062
  createWebProvider,
21442
- DEFAULT_ARTIFACT_TTL_HOURS,
21443
- MAX_ARTIFACT_TTL_HOURS,
21444
- createArtifactBundle,
21445
- cleanupExpiredArtifacts,
21446
21063
  resolveTimebox,
21447
21064
  isWithinTimebox,
21448
21065
  filterByTimebox,
21449
21066
  toResearchRecord,
21450
21067
  enrichResearchRecords,
21451
- INSPIREDESIGN_HANDOFF_COMMANDS,
21452
- INSPIREDESIGN_HANDOFF_GUIDANCE,
21453
21068
  buildMacroResolveSuccessHandoff,
21454
21069
  renderResearch,
21455
21070
  renderShopping,
@@ -21465,4 +21080,4 @@ export {
21465
21080
  createProviderRuntime,
21466
21081
  createDefaultRuntime
21467
21082
  };
21468
- //# sourceMappingURL=chunk-T3VVHJTK.js.map
21083
+ //# sourceMappingURL=chunk-S6S2UP6U.js.map