@sovovs/bycli 1.8.0 → 2.0.0

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 (509) hide show
  1. package/cli-manifest.json +0 -61
  2. package/dist/src/adapter-shadow.js +2 -2
  3. package/dist/src/browser/analyze.d.ts +10 -99
  4. package/dist/src/browser/analyze.js +10 -226
  5. package/dist/src/browser/cdp.js +64 -7
  6. package/dist/src/browser/navigation-guard.d.ts +78 -0
  7. package/dist/src/browser/navigation-guard.js +142 -0
  8. package/dist/src/browser/navigation-guard.test.d.ts +1 -0
  9. package/dist/src/browser/url-policy.d.ts +89 -0
  10. package/dist/src/browser/url-policy.js +311 -0
  11. package/dist/src/browser/url-policy.test.d.ts +1 -0
  12. package/dist/src/browser/verify-fixture.js +2 -2
  13. package/dist/src/cli.js +10 -13
  14. package/dist/src/config-paths.d.ts +19 -0
  15. package/dist/src/config-paths.js +31 -0
  16. package/dist/src/daemon.js +240 -0
  17. package/dist/src/discovery.d.ts +5 -3
  18. package/dist/src/discovery.js +8 -6
  19. package/dist/src/execution.js +8 -3
  20. package/dist/src/main.js +18 -2
  21. package/dist/src/plugin.js +11 -8
  22. package/dist/src/recorder/highlevel/analyze.d.ts +29 -0
  23. package/dist/src/recorder/highlevel/analyze.js +91 -0
  24. package/dist/src/recorder/highlevel/init.d.ts +125 -0
  25. package/dist/src/recorder/highlevel/init.js +279 -0
  26. package/dist/src/recorder/highlevel/init.test.d.ts +1 -0
  27. package/dist/src/recorder/highlevel/verify.d.ts +73 -0
  28. package/dist/src/recorder/highlevel/verify.js +74 -0
  29. package/dist/src/recorder/http/analyze-runner.d.ts +28 -0
  30. package/dist/src/recorder/http/analyze-runner.js +78 -0
  31. package/dist/src/recorder/http/analyze-runner.test.d.ts +1 -0
  32. package/dist/src/recorder/http/handlers.d.ts +51 -0
  33. package/dist/src/recorder/http/handlers.js +219 -0
  34. package/dist/src/recorder/http/wrapper-config.d.ts +20 -0
  35. package/dist/src/recorder/http/wrapper-config.js +82 -0
  36. package/dist/src/recorder/http/wrapper-envelope.d.ts +31 -0
  37. package/dist/src/recorder/http/wrapper-envelope.js +51 -0
  38. package/dist/src/recorder/http/wrapper-gates.d.ts +16 -0
  39. package/dist/src/recorder/http/wrapper-gates.js +28 -0
  40. package/dist/src/recorder/http/wrapper-metrics.d.ts +1 -0
  41. package/dist/src/recorder/http/wrapper-metrics.js +5 -0
  42. package/dist/src/recorder/http/wrapper-registry.d.ts +50 -0
  43. package/dist/src/recorder/http/wrapper-registry.js +98 -0
  44. package/dist/src/recorder/http/wrapper-server.d.ts +59 -0
  45. package/dist/src/recorder/http/wrapper-server.js +240 -0
  46. package/dist/src/recorder/http/wrapper.test.d.ts +1 -0
  47. package/dist/src/recorder/observability/logger.d.ts +5 -0
  48. package/dist/src/recorder/observability/logger.js +13 -0
  49. package/dist/src/recorder/runner/config.d.ts +34 -0
  50. package/dist/src/recorder/runner/config.js +84 -0
  51. package/dist/src/recorder/runner/config.test.d.ts +1 -0
  52. package/dist/src/recorder/runner/reap.d.ts +79 -0
  53. package/dist/src/recorder/runner/reap.js +230 -0
  54. package/dist/src/recorder/runner/reap.test.d.ts +1 -0
  55. package/dist/src/recorder/runner/runner-port.d.ts +95 -0
  56. package/dist/src/recorder/runner/runner-port.js +518 -0
  57. package/dist/src/recorder/runner/runner-port.test.d.ts +1 -0
  58. package/dist/src/recorder/runner/session-keys.d.ts +28 -0
  59. package/dist/src/recorder/runner/session-keys.js +72 -0
  60. package/dist/src/recorder/runner/session-keys.test.d.ts +1 -0
  61. package/dist/src/recorder/runner/verify-runner-main.d.ts +95 -0
  62. package/dist/src/recorder/runner/verify-runner-main.js +275 -0
  63. package/dist/src/recorder/runner/verify-runner-main.test.d.ts +1 -0
  64. package/package.json +8 -5
  65. package/scripts/m1-daemon-navigate-drive.mjs +138 -0
  66. package/scripts/m1-extension-spike.mjs +237 -0
  67. package/scripts/m1-live-spike.mjs +156 -0
  68. package/scripts/m6b-connect-back-spike.sh +31 -0
  69. package/scripts/recorder.sh +186 -0
  70. package/clis/12306/utils.test.js +0 -331
  71. package/clis/1688/assets.test.js +0 -39
  72. package/clis/1688/download.test.js +0 -31
  73. package/clis/1688/item.test.js +0 -67
  74. package/clis/1688/search.test.js +0 -75
  75. package/clis/1688/shared.test.js +0 -57
  76. package/clis/1688/store.test.js +0 -62
  77. package/clis/36kr/article.test.js +0 -46
  78. package/clis/36kr/hot.test.js +0 -15
  79. package/clis/36kr/news.test.js +0 -85
  80. package/clis/51job/utils.test.js +0 -69
  81. package/clis/_atlassian/shared.test.js +0 -170
  82. package/clis/aibase/news.test.js +0 -59
  83. package/clis/amazon/bestsellers.test.js +0 -29
  84. package/clis/amazon/discussion.test.js +0 -124
  85. package/clis/amazon/offer.test.js +0 -29
  86. package/clis/amazon/product.test.js +0 -24
  87. package/clis/amazon/rankings.test.js +0 -41
  88. package/clis/amazon/search.test.js +0 -22
  89. package/clis/amazon/shared.test.js +0 -44
  90. package/clis/apple-podcasts/commands.test.js +0 -119
  91. package/clis/apple-podcasts/utils.test.js +0 -57
  92. package/clis/arxiv/arxiv.test.js +0 -112
  93. package/clis/baidu-scholar/search.test.js +0 -23
  94. package/clis/barchart/greeks.test.js +0 -138
  95. package/clis/bilibili/comment.test.js +0 -153
  96. package/clis/bilibili/comments.test.js +0 -142
  97. package/clis/bilibili/dynamic.test.js +0 -68
  98. package/clis/bilibili/hot.test.js +0 -17
  99. package/clis/bilibili/subtitle.test.js +0 -196
  100. package/clis/bilibili/summary.test.js +0 -210
  101. package/clis/bilibili/utils.test.js +0 -61
  102. package/clis/bilibili/video.test.js +0 -132
  103. package/clis/binance/commands.test.js +0 -70
  104. package/clis/bloomberg/utils.test.js +0 -129
  105. package/clis/booking/booking.test.js +0 -356
  106. package/clis/boss/chatlist.test.js +0 -211
  107. package/clis/boss/chatmsg.test.js +0 -230
  108. package/clis/boss/search.test.js +0 -78
  109. package/clis/boss/utils.test.js +0 -34
  110. package/clis/brave/search.test.js +0 -76
  111. package/clis/chaoxing/utils.test.js +0 -45
  112. package/clis/chatgpt/commands.test.js +0 -45
  113. package/clis/chatgpt/envelope.test.js +0 -108
  114. package/clis/chatgpt/image.test.js +0 -188
  115. package/clis/chatgpt/utils.test.js +0 -330
  116. package/clis/chatgpt-app/ax.test.js +0 -35
  117. package/clis/chatwise/composer.test.js +0 -186
  118. package/clis/chess/analyze.test.js +0 -79
  119. package/clis/chess/game.test.js +0 -178
  120. package/clis/chess/games.test.js +0 -164
  121. package/clis/chess/stats.test.js +0 -79
  122. package/clis/chess/utils.test.js +0 -230
  123. package/clis/claude/ask.test.js +0 -338
  124. package/clis/claude/commands.test.js +0 -118
  125. package/clis/claude/utils.test.js +0 -148
  126. package/clis/cnki/search.test.js +0 -18
  127. package/clis/codex/sidebar.test.js +0 -329
  128. package/clis/coingecko/coingecko.test.js +0 -109
  129. package/clis/confluence/commands.test.js +0 -195
  130. package/clis/coupang/coupang.test.js +0 -159
  131. package/clis/coupang/utils.test.js +0 -62
  132. package/clis/ctrip/ctrip.test.js +0 -719
  133. package/clis/dblp/dblp.test.js +0 -397
  134. package/clis/deepseek/ask.test.js +0 -312
  135. package/clis/deepseek/detail.test.js +0 -81
  136. package/clis/deepseek/send.test.js +0 -107
  137. package/clis/deepseek/utils.test.js +0 -370
  138. package/clis/defillama/defillama.test.js +0 -99
  139. package/clis/devto/devto.test.js +0 -236
  140. package/clis/dianping/dianping.test.js +0 -578
  141. package/clis/douban/download.test.js +0 -170
  142. package/clis/douban/movie-hot.test.js +0 -14
  143. package/clis/douban/search.test.js +0 -11
  144. package/clis/douban/subject.test.js +0 -11
  145. package/clis/douban/utils.test.js +0 -438
  146. package/clis/doubao/detail.test.js +0 -42
  147. package/clis/doubao/history.test.js +0 -37
  148. package/clis/doubao/utils.test.js +0 -344
  149. package/clis/douyin/_shared/browser-fetch.test.js +0 -64
  150. package/clis/douyin/_shared/creation-id.test.js +0 -22
  151. package/clis/douyin/_shared/imagex-upload.test.js +0 -87
  152. package/clis/douyin/_shared/sts2.test.js +0 -27
  153. package/clis/douyin/_shared/text-extra.test.js +0 -37
  154. package/clis/douyin/_shared/timing.test.js +0 -28
  155. package/clis/douyin/_shared/tos-upload-short-read.test.js +0 -83
  156. package/clis/douyin/_shared/tos-upload.test.js +0 -229
  157. package/clis/douyin/_shared/transcode.test.js +0 -91
  158. package/clis/douyin/_shared/vod-upload.test.js +0 -38
  159. package/clis/douyin/activities.test.js +0 -55
  160. package/clis/douyin/collections.test.js +0 -45
  161. package/clis/douyin/delete.test.js +0 -100
  162. package/clis/douyin/draft.test.js +0 -335
  163. package/clis/douyin/drafts.test.js +0 -11
  164. package/clis/douyin/hashtag.test.js +0 -171
  165. package/clis/douyin/location.test.js +0 -23
  166. package/clis/douyin/profile.test.js +0 -46
  167. package/clis/douyin/publish-upload-id.test.js +0 -170
  168. package/clis/douyin/publish.test.js +0 -38
  169. package/clis/douyin/stats.test.js +0 -22
  170. package/clis/douyin/update.test.js +0 -11
  171. package/clis/douyin/user-videos.test.js +0 -151
  172. package/clis/douyin/videos.test.js +0 -54
  173. package/clis/duckduckgo/search.test.js +0 -128
  174. package/clis/duckduckgo/suggest.test.js +0 -66
  175. package/clis/eastmoney/hot-rank.test.js +0 -59
  176. package/clis/endoflife/endoflife.test.js +0 -51
  177. package/clis/facebook/feed.test.js +0 -169
  178. package/clis/facebook/marketplace.test.js +0 -91
  179. package/clis/facebook/notifications.test.js +0 -458
  180. package/clis/facebook/search.test.js +0 -55
  181. package/clis/flathub/flathub.test.js +0 -90
  182. package/clis/flomo/memos.test.js +0 -144
  183. package/clis/gemini/ask.test.js +0 -100
  184. package/clis/gemini/deep-research-result.test.js +0 -155
  185. package/clis/gemini/deep-research.test.js +0 -181
  186. package/clis/gemini/reply-state.test.js +0 -671
  187. package/clis/gemini/utils.test.js +0 -198
  188. package/clis/geogebra/geogebra.test.js +0 -175
  189. package/clis/gitee/search.test.js +0 -65
  190. package/clis/gitee/user.test.js +0 -63
  191. package/clis/google/utils.test.js +0 -75
  192. package/clis/google-scholar/cite.test.js +0 -47
  193. package/clis/google-scholar/profile.test.js +0 -49
  194. package/clis/google-scholar/search.test.js +0 -71
  195. package/clis/goproxy/goproxy.test.js +0 -103
  196. package/clis/gov-law/commands.test.js +0 -39
  197. package/clis/gov-policy/gov-policy.test.js +0 -224
  198. package/clis/grok/ask.test.js +0 -29
  199. package/clis/grok/utils.test.js +0 -103
  200. package/clis/hackernews/hackernews.test.js +0 -132
  201. package/clis/hf/hf.test.js +0 -16
  202. package/clis/hupu/hot.test.js +0 -224
  203. package/clis/imdb/utils.test.js +0 -88
  204. package/clis/indeed/indeed.test.js +0 -375
  205. package/clis/instagram/_shared/private-publish.test.js +0 -705
  206. package/clis/instagram/_shared/protocol-capture.test.js +0 -114
  207. package/clis/instagram/download.test.js +0 -118
  208. package/clis/instagram/note.test.js +0 -53
  209. package/clis/instagram/post.test.js +0 -1647
  210. package/clis/instagram/reel.test.js +0 -167
  211. package/clis/instagram/story.test.js +0 -134
  212. package/clis/jd/commands.test.js +0 -41
  213. package/clis/jd/item.test.js +0 -354
  214. package/clis/jianyu/search.test.js +0 -153
  215. package/clis/jianyu/shared/procurement-contract.test.js +0 -72
  216. package/clis/jianyu/shared/procurement-detail.test.js +0 -84
  217. package/clis/jike/read.test.js +0 -86
  218. package/clis/jira/commands.test.js +0 -287
  219. package/clis/juejin/search.js +0 -123
  220. package/clis/juejin/search.test.js +0 -98
  221. package/clis/lesswrong/frontpage.test.js +0 -37
  222. package/clis/lichess/lichess.test.js +0 -85
  223. package/clis/linkedin/connect.test.js +0 -213
  224. package/clis/linkedin/inbox.test.js +0 -152
  225. package/clis/linkedin/job-detail.test.js +0 -38
  226. package/clis/linkedin/jobs-preferences.test.js +0 -43
  227. package/clis/linkedin/people-search.test.js +0 -216
  228. package/clis/linkedin/post-analytics.test.js +0 -40
  229. package/clis/linkedin/posts.test.js +0 -40
  230. package/clis/linkedin/profile-analytics.test.js +0 -67
  231. package/clis/linkedin/profile-experience.test.js +0 -152
  232. package/clis/linkedin/profile-projects.test.js +0 -111
  233. package/clis/linkedin/profile-read.test.js +0 -77
  234. package/clis/linkedin/safe-send.test.js +0 -204
  235. package/clis/linkedin/salesnav-inbox.test.js +0 -113
  236. package/clis/linkedin/salesnav-message.test.js +0 -172
  237. package/clis/linkedin/salesnav-search.test.js +0 -76
  238. package/clis/linkedin/salesnav-thread.test.js +0 -79
  239. package/clis/linkedin/search.test.js +0 -222
  240. package/clis/linkedin/sent-invitations.test.js +0 -62
  241. package/clis/linkedin/services-read.test.js +0 -105
  242. package/clis/linkedin/thread-snapshot.test.js +0 -89
  243. package/clis/linkedin/timeline.test.js +0 -81
  244. package/clis/linkedin-learning/course.test.js +0 -114
  245. package/clis/linkedin-learning/search.test.js +0 -144
  246. package/clis/linkedin-learning/trending.test.js +0 -123
  247. package/clis/linux-do/feed.test.js +0 -153
  248. package/clis/linux-do/topic-content.test.js +0 -59
  249. package/clis/lobsters/lobsters.test.js +0 -169
  250. package/clis/notebooklm/add-source.test.js +0 -97
  251. package/clis/notebooklm/compat.test.js +0 -16
  252. package/clis/notebooklm/create.test.js +0 -58
  253. package/clis/notebooklm/generate-audio.test.js +0 -63
  254. package/clis/notebooklm/generate-slides.test.js +0 -75
  255. package/clis/notebooklm/history.test.js +0 -58
  256. package/clis/notebooklm/note-list.test.js +0 -56
  257. package/clis/notebooklm/notes-get.test.js +0 -72
  258. package/clis/notebooklm/open.test.js +0 -63
  259. package/clis/notebooklm/rpc.test.js +0 -131
  260. package/clis/notebooklm/source-fulltext.test.js +0 -106
  261. package/clis/notebooklm/source-get.test.js +0 -84
  262. package/clis/notebooklm/source-guide.test.js +0 -104
  263. package/clis/notebooklm/summary.test.js +0 -78
  264. package/clis/notebooklm/utils.test.js +0 -449
  265. package/clis/notebooklm/write-note.test.js +0 -70
  266. package/clis/nuget/nuget.test.js +0 -111
  267. package/clis/oeis/oeis.test.js +0 -88
  268. package/clis/ones/task-helpers.test.js +0 -12
  269. package/clis/ones/worklog.test.js +0 -20
  270. package/clis/openfda/openfda.test.js +0 -114
  271. package/clis/openreview/openreview.test.js +0 -427
  272. package/clis/osv/osv.test.js +0 -97
  273. package/clis/paperreview/commands.test.js +0 -243
  274. package/clis/paperreview/utils.test.js +0 -49
  275. package/clis/pixiv/download.test.js +0 -87
  276. package/clis/pixiv/illusts.test.js +0 -99
  277. package/clis/pixiv/search.test.js +0 -83
  278. package/clis/powerchina/search.test.js +0 -93
  279. package/clis/producthunt/utils.test.js +0 -64
  280. package/clis/pubmed/pubmed.test.js +0 -276
  281. package/clis/quark/utils.test.js +0 -58
  282. package/clis/qwen/utils.test.js +0 -45
  283. package/clis/reddit/extract-media.test.js +0 -149
  284. package/clis/reddit/frontpage.test.js +0 -34
  285. package/clis/reddit/home.test.js +0 -170
  286. package/clis/reddit/hot.test.js +0 -32
  287. package/clis/reddit/popular.test.js +0 -26
  288. package/clis/reddit/read.test.js +0 -335
  289. package/clis/reddit/reply.test.js +0 -89
  290. package/clis/reddit/search.test.js +0 -26
  291. package/clis/reddit/subreddit-info.test.js +0 -163
  292. package/clis/reddit/subreddit.test.js +0 -31
  293. package/clis/reddit/subscribed.test.js +0 -168
  294. package/clis/reddit/whoami.test.js +0 -105
  295. package/clis/rednote/navigation.test.js +0 -26
  296. package/clis/rednote/rednote.test.js +0 -222
  297. package/clis/rest-countries/rest-countries.test.js +0 -83
  298. package/clis/reuters/reuters.test.js +0 -299
  299. package/clis/rfc/rfc.test.js +0 -74
  300. package/clis/sinafinance/stock.test.js +0 -59
  301. package/clis/spotify/utils.test.js +0 -67
  302. package/clis/stackoverflow/stackoverflow.test.js +0 -404
  303. package/clis/steam/steam.test.js +0 -46
  304. package/clis/substack/utils.test.js +0 -44
  305. package/clis/suno/commands.test.js +0 -188
  306. package/clis/suno/download.test.js +0 -151
  307. package/clis/suno/generate.test.js +0 -252
  308. package/clis/suno/utils.test.js +0 -329
  309. package/clis/taobao/commands.test.js +0 -41
  310. package/clis/tdx/hot-rank.test.js +0 -59
  311. package/clis/ths/hot-rank.test.js +0 -64
  312. package/clis/tieba/commands.test.js +0 -79
  313. package/clis/tieba/utils.test.js +0 -290
  314. package/clis/tiktok/creator-videos.test.js +0 -113
  315. package/clis/tiktok/refactor.test.js +0 -389
  316. package/clis/tiktok/write-refactor.test.js +0 -370
  317. package/clis/toutiao/toutiao.test.js +0 -378
  318. package/clis/tvmaze/tvmaze.test.js +0 -93
  319. package/clis/twitter/bookmark-folder.test.js +0 -395
  320. package/clis/twitter/bookmark-folders.test.js +0 -152
  321. package/clis/twitter/bookmark.test.js +0 -74
  322. package/clis/twitter/bookmarks.test.js +0 -205
  323. package/clis/twitter/delete.test.js +0 -85
  324. package/clis/twitter/device-follow.test.js +0 -287
  325. package/clis/twitter/download.test.js +0 -457
  326. package/clis/twitter/followers.test.js +0 -62
  327. package/clis/twitter/following.test.js +0 -338
  328. package/clis/twitter/hide-reply.test.js +0 -76
  329. package/clis/twitter/like.test.js +0 -73
  330. package/clis/twitter/likes.test.js +0 -220
  331. package/clis/twitter/list-add.test.js +0 -133
  332. package/clis/twitter/list-create.test.js +0 -169
  333. package/clis/twitter/list-remove.test.js +0 -110
  334. package/clis/twitter/list-tweets.test.js +0 -196
  335. package/clis/twitter/lists.test.js +0 -253
  336. package/clis/twitter/post.test.js +0 -258
  337. package/clis/twitter/profile.test.js +0 -39
  338. package/clis/twitter/quote.test.js +0 -194
  339. package/clis/twitter/reply.test.js +0 -237
  340. package/clis/twitter/retweet.test.js +0 -73
  341. package/clis/twitter/search.test.js +0 -349
  342. package/clis/twitter/shared.test.js +0 -794
  343. package/clis/twitter/thread.test.js +0 -58
  344. package/clis/twitter/timeline.test.js +0 -104
  345. package/clis/twitter/trending.test.js +0 -15
  346. package/clis/twitter/tweets.test.js +0 -362
  347. package/clis/twitter/unbookmark.test.js +0 -73
  348. package/clis/twitter/unlike.test.js +0 -75
  349. package/clis/twitter/unretweet.test.js +0 -73
  350. package/clis/twitter/utils.test.js +0 -169
  351. package/clis/uisdc/news.test.js +0 -66
  352. package/clis/uiverse/_shared.test.js +0 -62
  353. package/clis/uiverse/navigation.test.js +0 -12
  354. package/clis/upwork/upwork.test.js +0 -566
  355. package/clis/wanfang/search.test.js +0 -23
  356. package/clis/web/read.test.js +0 -392
  357. package/clis/weibo/delete.test.js +0 -94
  358. package/clis/weibo/envelope.test.js +0 -85
  359. package/clis/weibo/favorites.test.js +0 -114
  360. package/clis/weibo/publish.test.js +0 -192
  361. package/clis/weibo/user-posts.test.js +0 -92
  362. package/clis/weibo/utils.test.js +0 -36
  363. package/clis/weixin/drafts.test.js +0 -69
  364. package/clis/weixin/search.test.js +0 -227
  365. package/clis/weread/ai-outline.test.js +0 -83
  366. package/clis/weread/book-search.test.js +0 -242
  367. package/clis/weread/commands.test.js +0 -398
  368. package/clis/weread/private-api-regression.test.js +0 -369
  369. package/clis/weread/search-regression.test.js +0 -494
  370. package/clis/weread/utils.test.js +0 -128
  371. package/clis/weread-official/commands.test.js +0 -385
  372. package/clis/weread-official/utils.test.js +0 -242
  373. package/clis/wikidata/wikidata.test.js +0 -83
  374. package/clis/wikipedia/trending.test.js +0 -57
  375. package/clis/wttr/wttr.test.js +0 -84
  376. package/clis/xianyu/chat.test.js +0 -84
  377. package/clis/xianyu/im.test.js +0 -253
  378. package/clis/xianyu/item.test.js +0 -56
  379. package/clis/xianyu/publish.test.js +0 -220
  380. package/clis/xianyu/search.test.js +0 -17
  381. package/clis/xiaoe/xiaoe.test.js +0 -486
  382. package/clis/xiaohongshu/comments.test.js +0 -258
  383. package/clis/xiaohongshu/creator-note-detail.test.js +0 -451
  384. package/clis/xiaohongshu/creator-notes-summary.test.js +0 -94
  385. package/clis/xiaohongshu/creator-notes.test.js +0 -281
  386. package/clis/xiaohongshu/creator-stats.test.js +0 -24
  387. package/clis/xiaohongshu/delete-note.test.js +0 -172
  388. package/clis/xiaohongshu/download.test.js +0 -316
  389. package/clis/xiaohongshu/navigation.test.js +0 -34
  390. package/clis/xiaohongshu/note.test.js +0 -249
  391. package/clis/xiaohongshu/publish.test.js +0 -608
  392. package/clis/xiaohongshu/search.test.js +0 -325
  393. package/clis/xiaohongshu/user-helpers.test.js +0 -160
  394. package/clis/xiaoyuzhou/auth.test.js +0 -124
  395. package/clis/xiaoyuzhou/download.test.js +0 -135
  396. package/clis/xiaoyuzhou/podcast-episodes.test.js +0 -78
  397. package/clis/xiaoyuzhou/transcript.test.js +0 -195
  398. package/clis/xiaoyuzhou/utils.test.js +0 -39
  399. package/clis/xueqiu/comments.test.js +0 -696
  400. package/clis/xueqiu/danjuan-utils.test.js +0 -41
  401. package/clis/xueqiu/utils.test.js +0 -26
  402. package/clis/yahoo/search.test.js +0 -94
  403. package/clis/youdao/note.test.js +0 -99
  404. package/clis/youtube/channel.test.js +0 -59
  405. package/clis/youtube/feed.test.js +0 -131
  406. package/clis/youtube/transcript-group.test.js +0 -99
  407. package/clis/youtube/transcript.test.js +0 -296
  408. package/clis/youtube/utils.test.js +0 -84
  409. package/clis/yuanbao/ask.test.js +0 -124
  410. package/clis/yuanbao/new.test.js +0 -30
  411. package/clis/yuanbao/shared.test.js +0 -80
  412. package/clis/zhihu/answer-comments.test.js +0 -287
  413. package/clis/zhihu/answer-detail.test.js +0 -338
  414. package/clis/zhihu/answer.test.js +0 -54
  415. package/clis/zhihu/collection.test.js +0 -337
  416. package/clis/zhihu/collections.test.js +0 -182
  417. package/clis/zhihu/comment.test.js +0 -50
  418. package/clis/zhihu/download.test.js +0 -12
  419. package/clis/zhihu/favorite.test.js +0 -59
  420. package/clis/zhihu/follow.test.js +0 -42
  421. package/clis/zhihu/like.test.js +0 -41
  422. package/clis/zhihu/question.test.js +0 -251
  423. package/clis/zhihu/recommend.test.js +0 -143
  424. package/clis/zhihu/search.test.js +0 -198
  425. package/clis/zhihu/target.test.js +0 -77
  426. package/clis/zhihu/text.test.js +0 -24
  427. package/clis/zhihu/write-shared.test.js +0 -176
  428. package/clis/zlibrary/commands.test.js +0 -65
  429. package/clis/zsxq/search.test.js +0 -24
  430. package/clis/zsxq/topic.test.js +0 -30
  431. package/clis/zsxq/topics.test.js +0 -24
  432. package/clis/zsxq/utils.test.js +0 -31
  433. package/dist/src/adapter-shadow.test.js +0 -49
  434. package/dist/src/adapter-source.test.js +0 -29
  435. package/dist/src/browser/analyze.test.js +0 -166
  436. package/dist/src/browser/article-extract.e2e.test.js +0 -105
  437. package/dist/src/browser/article-extract.test.js +0 -94
  438. package/dist/src/browser/ax-snapshot.test.js +0 -91
  439. package/dist/src/browser/base-page.test.js +0 -758
  440. package/dist/src/browser/cdp-click-fixture.test.js +0 -87
  441. package/dist/src/browser/cdp.test.js +0 -79
  442. package/dist/src/browser/compound.test.js +0 -175
  443. package/dist/src/browser/daemon-client.test.js +0 -199
  444. package/dist/src/browser/dom-helpers.test.js +0 -106
  445. package/dist/src/browser/dom-snapshot.test.js +0 -313
  446. package/dist/src/browser/errors.test.js +0 -60
  447. package/dist/src/browser/extract.test.js +0 -129
  448. package/dist/src/browser/find.test.js +0 -180
  449. package/dist/src/browser/html-tree.test.js +0 -181
  450. package/dist/src/browser/network-cache.test.js +0 -58
  451. package/dist/src/browser/network-key.test.js +0 -49
  452. package/dist/src/browser/page.test.js +0 -378
  453. package/dist/src/browser/shape-filter.test.js +0 -101
  454. package/dist/src/browser/shape.test.js +0 -82
  455. package/dist/src/browser/stealth.test.js +0 -134
  456. package/dist/src/browser/target-errors.test.js +0 -94
  457. package/dist/src/browser/target-resolver.test.js +0 -118
  458. package/dist/src/browser/utils.test.js +0 -29
  459. package/dist/src/browser/verify-fixture.test.js +0 -219
  460. package/dist/src/browser.test.js +0 -232
  461. package/dist/src/build-manifest.test.js +0 -341
  462. package/dist/src/capabilityRouting.test.js +0 -63
  463. package/dist/src/cli-argv-preprocess.test.js +0 -209
  464. package/dist/src/cli.test.js +0 -2803
  465. package/dist/src/commanderAdapter.test.js +0 -352
  466. package/dist/src/commands/daemon.test.js +0 -219
  467. package/dist/src/completion.test.js +0 -24
  468. package/dist/src/convention-audit.test.js +0 -247
  469. package/dist/src/daemon.test.js +0 -60
  470. package/dist/src/doctor.test.js +0 -263
  471. package/dist/src/download/article-download.test.js +0 -235
  472. package/dist/src/download/index.test.js +0 -140
  473. package/dist/src/download/media-download.test.js +0 -112
  474. package/dist/src/download/progress.test.js +0 -36
  475. package/dist/src/electron-apps.test.js +0 -49
  476. package/dist/src/engine.test.js +0 -312
  477. package/dist/src/errors.test.js +0 -109
  478. package/dist/src/execution.test.js +0 -623
  479. package/dist/src/extension-manifest-regression.test.js +0 -12
  480. package/dist/src/external.test.js +0 -146
  481. package/dist/src/help.test.js +0 -58
  482. package/dist/src/hooks.test.js +0 -92
  483. package/dist/src/interceptor.test.js +0 -81
  484. package/dist/src/launcher.test.js +0 -138
  485. package/dist/src/node-network.test.js +0 -55
  486. package/dist/src/observation/artifact.test.js +0 -121
  487. package/dist/src/observation/manager.test.js +0 -13
  488. package/dist/src/observation/redaction.test.js +0 -32
  489. package/dist/src/observation/retention.test.js +0 -118
  490. package/dist/src/observation/ring-buffer.test.js +0 -22
  491. package/dist/src/output.test.js +0 -53
  492. package/dist/src/package-exports.test.js +0 -112
  493. package/dist/src/pipeline/executor.test.js +0 -182
  494. package/dist/src/pipeline/steps/download.test.js +0 -102
  495. package/dist/src/pipeline/steps/fetch.test.js +0 -117
  496. package/dist/src/pipeline/template.test.js +0 -175
  497. package/dist/src/pipeline/transform.test.js +0 -133
  498. package/dist/src/plugin-manifest.test.js +0 -179
  499. package/dist/src/plugin-scaffold.test.js +0 -83
  500. package/dist/src/plugin.test.js +0 -1324
  501. package/dist/src/registry.test.js +0 -209
  502. package/dist/src/runtime-detect.test.js +0 -39
  503. package/dist/src/serialization.test.js +0 -78
  504. package/dist/src/snapshotFormatter.test.js +0 -521
  505. package/dist/src/tui.test.js +0 -19
  506. package/dist/src/update-check.test.js +0 -116
  507. package/dist/src/utils.test.js +0 -155
  508. package/dist/src/validate.test.js +0 -90
  509. package/dist/src/weixin-download.test.js +0 -45
@@ -0,0 +1,518 @@
1
+ /**
2
+ * Real RunnerPort (M6a · 08) — the child-process verify runner mechanism.
3
+ *
4
+ * Replaces M5c's stubRunnerPort. Responsibilities (08 Process Rules):
5
+ * - create requestId BEFORE launch; async registry of in-flight runs
6
+ * - write input.json securely: per-request `0700` temp dir (owner/mode/realpath/lstat
7
+ * checked), exclusive `0600` file; raw seed args live here only, never in status/log
8
+ * - spawn `bycli internal verify-runner --jsonl …` with an args array (no shell), minimal env
9
+ * - parse stdout JSONL via the pure parseRunnerEvent; one and only one terminal `result`
10
+ * - hard timeout → SIGTERM (graceful) → SIGKILL; stdout/stderr byte caps
11
+ * - cancel is idempotent; temp dir is always cleaned on done/cancel/timeout
12
+ *
13
+ * M6a does NOT implement: startup reap, browser-adapter execution (needs a daemon Page),
14
+ * concurrency-queue refinement — those are M6b/c. Browser adapters report not-yet from the
15
+ * runner itself (see verify-runner-main.ts).
16
+ *
17
+ * SECURITY: `rawSeedArgs` go into input.json (0600) only. getVerifyStatus returns a
18
+ * summary-only VerifySummary (normalizeRunnerResult) — raw values never reach it.
19
+ */
20
+ import { spawn as nodeSpawn } from 'node:child_process';
21
+ import * as fs from 'node:fs';
22
+ import * as os from 'node:os';
23
+ import * as path from 'node:path';
24
+ import { randomUUID } from 'node:crypto';
25
+ import { fileURLToPath } from 'node:url';
26
+ import { getUserClisDir } from '../../config-paths.js';
27
+ import { parseRunnerEvent, normalizeRunnerResult, buildRunnerArgs, createMetrics, } from '@sovovs/bycli-recorder-core';
28
+ import { createRecorderLogger } from '../observability/logger.js';
29
+ import { resolveRunnerConfig } from './config.js';
30
+ /** How long a terminal run lingers in `runs` for status reads before GC (Codex M9 review · High):
31
+ * long enough for getRunStatus/whenSettled consumers (incl. the M9 wrapper's background finalize)
32
+ * to read the summary, short enough that the map stays bounded. */
33
+ const TERMINAL_RUN_RETENTION_MS = 300_000;
34
+ /** Sentinel input swapped in once a run is terminal — drops the retained StartInput (rawSeedArgs +
35
+ * evidence) so no raw seed / run input lingers in memory after settle. */
36
+ const MINIMAL_INPUT = { name: '', evidenceSeedArgs: {}, rawSeedArgs: {}, fixture: '', trace: '' };
37
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
38
+ /** dist/src/recorder/runner/runner-port.js → dist/src/main.js (same relative layout in src/). */
39
+ const DEFAULT_MAIN_ENTRY = path.resolve(__dirname, '../../main.js');
40
+ /** Dedicated protocol fd (Codex #3): the child writes JSONL here, not stdout, so an adapter's
41
+ * console.log / process.stdout.write can never pollute or forge the protocol stream. The child is
42
+ * spawned with stdio `['ignore','ignore','ignore','pipe']` — adapter stdout/stderr go to /dev/null. */
43
+ const PROTOCOL_FD = 3;
44
+ function defaultResolveAdapterPath(name) {
45
+ const slash = name.indexOf('/');
46
+ const site = slash === -1 ? name : name.slice(0, slash);
47
+ const command = slash === -1 ? name : name.slice(slash + 1);
48
+ return path.join(getUserClisDir(), site, `${command}.js`);
49
+ }
50
+ /**
51
+ * Create a per-request temp dir + input.json with the 08 security guarantees (POSIX:
52
+ * 0700 dir, owner/mode/realpath/lstat checks, 0600 exclusive file). Returns the real temp
53
+ * root and input path. Raw seed args are written here and nowhere else.
54
+ */
55
+ function writeInputJson(requestId, payload) {
56
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'bycli-verify-'));
57
+ fs.chmodSync(tempRoot, 0o700);
58
+ const st = fs.lstatSync(tempRoot);
59
+ if (!st.isDirectory() || st.isSymbolicLink())
60
+ throw new Error('verify temp root is not a real directory');
61
+ if (typeof process.getuid === 'function' && st.uid !== process.getuid())
62
+ throw new Error('verify temp root owner mismatch');
63
+ if (typeof process.getuid === 'function' && (st.mode & 0o777) !== 0o700)
64
+ throw new Error('verify temp root mode mismatch');
65
+ const realRoot = fs.realpathSync(tempRoot);
66
+ // 08 child marker: cleanup manifest, no seed args / tokens. `ownerPid` is the process that
67
+ // spawned this runner (daemon or CLI) and stands in for 08's localServiceRunId: startup reap
68
+ // only touches runs whose ownerPid is DEAD (true orphans), so a live sibling's in-flight
69
+ // verify is never killed. `pid` (the child) is backfilled by updateMarkerPid after spawn.
70
+ const markerPath = path.join(realRoot, 'marker.json');
71
+ fs.writeFileSync(markerPath, JSON.stringify({ requestId, pid: null, ownerPid: process.pid, startedAt: Date.now(), tempRoot: realRoot, operation: 'verify' }), { mode: 0o600 });
72
+ const inputPath = path.join(realRoot, `${requestId}-input.json`);
73
+ const fd = fs.openSync(inputPath, 'wx', 0o600); // exclusive create, fails if exists
74
+ try {
75
+ fs.writeSync(fd, JSON.stringify(payload));
76
+ }
77
+ finally {
78
+ fs.closeSync(fd);
79
+ }
80
+ return { tempRoot: realRoot, inputPath };
81
+ }
82
+ function safeCleanup(tempRoot) {
83
+ try {
84
+ fs.rmSync(tempRoot, { recursive: true, force: true });
85
+ }
86
+ catch { /* best-effort; startup reap (M6b) is the backstop */ }
87
+ }
88
+ /** Best-effort byte size of one verify temp dir; per-entry races (a vanished file mid-scan) are skipped. */
89
+ function dirSizeBestEffort(dir) {
90
+ let total = 0;
91
+ let entries;
92
+ try {
93
+ entries = fs.readdirSync(dir, { withFileTypes: true });
94
+ }
95
+ catch {
96
+ return 0;
97
+ }
98
+ for (const e of entries) {
99
+ const p = path.join(dir, e.name);
100
+ try {
101
+ if (e.isDirectory())
102
+ total += dirSizeBestEffort(p);
103
+ else if (e.isFile())
104
+ total += fs.statSync(p).size;
105
+ }
106
+ catch { /* entry vanished (race) — skip */ }
107
+ }
108
+ return total;
109
+ }
110
+ /** Default temp-usage measurement (#1d): sum of all `bycli-verify-*` dirs under os.tmpdir(). The
111
+ * top-level readdir is NOT guarded — an unreadable tmpdir throws, which the caller treats as
112
+ * over-capacity (fail-closed). Per-dir scanning is best-effort (races are normal). */
113
+ function defaultMeasureTempBytes() {
114
+ const root = os.tmpdir();
115
+ let total = 0;
116
+ for (const e of fs.readdirSync(root, { withFileTypes: true })) {
117
+ if (e.isDirectory() && e.name.startsWith('bycli-verify-'))
118
+ total += dirSizeBestEffort(path.join(root, e.name));
119
+ }
120
+ return total;
121
+ }
122
+ /** Backfill the spawned child pid into marker.json (08 startup reap needs the live pid). */
123
+ function updateMarkerPid(tempRoot, pid) {
124
+ const markerPath = path.join(tempRoot, 'marker.json');
125
+ try {
126
+ const marker = JSON.parse(fs.readFileSync(markerPath, 'utf8'));
127
+ marker.pid = pid;
128
+ fs.writeFileSync(markerPath, JSON.stringify(marker), { mode: 0o600 });
129
+ }
130
+ catch { /* marker missing/unreadable — reap falls back to temp deletion only */ }
131
+ }
132
+ /** Minimal child env (08 isolation): no inherited NODE_OPTIONS, scratch HOME/config/cache,
133
+ * daemon port so the child can reach the daemon `/command` surface (M6b browser adapters).
134
+ * Deliberately omits the user env: no NODE_OPTIONS, no sensitive tokens. The scratch
135
+ * HOME + BYCLI_CONFIG_DIR mean the child cannot read the user's adapters/cookies/config. */
136
+ function buildChildEnv(tempRoot, daemonPort) {
137
+ const env = {
138
+ HOME: tempRoot,
139
+ PATH: process.env.PATH ?? '/usr/bin:/bin',
140
+ BYCLI_CONFIG_DIR: tempRoot,
141
+ BYCLI_CACHE_DIR: tempRoot,
142
+ };
143
+ if (daemonPort !== undefined)
144
+ env.BYCLI_DAEMON_PORT = String(daemonPort);
145
+ return env;
146
+ }
147
+ export function createRunnerPort(opts = {}) {
148
+ const config = opts.config ?? resolveRunnerConfig();
149
+ const launcher = opts.launcher ?? { command: process.execPath, prefixArgs: [DEFAULT_MAIN_ENTRY] };
150
+ const resolveAdapterPath = opts.resolveAdapterPath ?? defaultResolveAdapterPath;
151
+ const spawnImpl = opts.spawnImpl ?? nodeSpawn;
152
+ const terminalRetentionMs = opts.terminalRetentionMs ?? TERMINAL_RUN_RETENTION_MS;
153
+ // #1c observability: default to a throwaway registry + silent logger so standalone/test construction
154
+ // stays zero-config and the existing runner tests don't change. The daemon injects shared instances.
155
+ const metrics = opts.metrics ?? createMetrics();
156
+ const logger = opts.logger ?? createRecorderLogger('error', () => { });
157
+ // #1d temp guard (off unless tempCapacity injected — only the daemon enforces).
158
+ const tempCapacity = opts.tempCapacity;
159
+ const measureTempBytes = opts.measureTempBytes ?? defaultMeasureTempBytes;
160
+ const sweepTemp = opts.sweepTemp ?? (() => { });
161
+ const runs = new Map();
162
+ const queue = []; // requestIds in 'queued' state, FIFO
163
+ const runningCount = () => { let n = 0; for (const r of runs.values())
164
+ if (r.status === 'running')
165
+ n++; return n; };
166
+ /** Reach a terminal state exactly once (idempotent). Clears the kill + result-grace timers,
167
+ * cleans temp, resolves, then pulls the next queued run into the freed slot. The grace timer
168
+ * (a pending SIGKILL after a timeout) deliberately survives — it is cleared on child exit. */
169
+ function settle(run, status, summary) {
170
+ if (run.status !== 'running' && run.status !== 'queued')
171
+ return; // already terminal
172
+ run.status = status;
173
+ run.summary = summary;
174
+ // #1c observability — single terminal choke point: every settle records duration + outcome, plus
175
+ // the 09 named counters (runner timeout / protocol error). errorCode is the bounded ErrorCode enum;
176
+ // never logs raw seed/stdout/stderr/trace (summary is already normalized to summary-only).
177
+ const durationMs = Date.now() - run.startedAt;
178
+ const errorCode = summary.ok ? undefined : summary.error?.code;
179
+ metrics.observe('runner_verify_duration_ms', durationMs);
180
+ metrics.inc('runner_verify_total', { status });
181
+ if (status === 'timeout')
182
+ metrics.inc('runner_timeout_total');
183
+ if (errorCode === 'runner_protocol_error')
184
+ metrics.inc('runner_protocol_error_total');
185
+ logger.info('runner.verify', { requestId: run.requestId, status, errorCode, durationMs, queueDepth: queue.length });
186
+ if (run.killTimer)
187
+ clearTimeout(run.killTimer);
188
+ if (run.resultGrace)
189
+ clearTimeout(run.resultGrace);
190
+ if (run.tempRoot)
191
+ safeCleanup(run.tempRoot);
192
+ run.input = MINIMAL_INPUT; // drop retained StartInput (rawSeedArgs/evidence) once terminal — never needed again
193
+ run.resolveSettled(summary);
194
+ // Retain the terminal run briefly so getRunStatus/whenSettled consumers can read the summary,
195
+ // then GC it — the `runs` map otherwise grows unbounded and retains run metadata forever
196
+ // (Codex M9 review · High). Unref'd so it never keeps the process alive.
197
+ setTimeout(() => { runs.delete(run.requestId); }, terminalRetentionMs).unref();
198
+ scheduleNext();
199
+ }
200
+ /** #1d temp-store capacity gate: is there room for another verify temp dir? Over the high watermark
201
+ * → shed orphan/aged dirs (sweepTemp) and re-measure once; still over → temp_store_full. A
202
+ * measurement throw counts as over-capacity (fail-closed — never fill the disk on a bad read). */
203
+ function checkTempCapacity() {
204
+ if (!tempCapacity)
205
+ return { ok: true };
206
+ const highBytes = tempCapacity.maxBytes * tempCapacity.highWatermarkRatio;
207
+ let used;
208
+ try {
209
+ used = measureTempBytes();
210
+ }
211
+ catch {
212
+ metrics.inc('temp_store_full_total');
213
+ return { ok: false, reason: 'verify temp usage unmeasurable (fail-closed)' };
214
+ }
215
+ if (used <= highBytes)
216
+ return { ok: true };
217
+ metrics.inc('temp_store_pressure_total');
218
+ try {
219
+ sweepTemp();
220
+ }
221
+ catch { /* best-effort shed */ }
222
+ let after;
223
+ try {
224
+ after = measureTempBytes();
225
+ }
226
+ catch {
227
+ metrics.inc('temp_store_full_total');
228
+ return { ok: false, reason: 'verify temp usage unmeasurable after sweep (fail-closed)' };
229
+ }
230
+ if (after <= highBytes)
231
+ return { ok: true };
232
+ metrics.inc('temp_store_full_total');
233
+ return { ok: false, reason: `verify temp store over capacity (${after} > ${Math.floor(highBytes)} bytes, high watermark)` };
234
+ }
235
+ /** Launch a queued run: write input.json, spawn the child, wire timers + JSONL handling.
236
+ * A synchronous launch failure (temp/spawn) settles the run rather than throwing — the
237
+ * requestId is already minted, so callers always observe a terminal status. */
238
+ function launch(run) {
239
+ // #1d: refuse to write a new temp dir past the high watermark (fail-closed) before doing any work.
240
+ const cap = checkTempCapacity();
241
+ if (!cap.ok) {
242
+ settle(run, 'failed', { ok: false, error: { code: 'temp_store_full', message: cap.reason } });
243
+ return;
244
+ }
245
+ run.status = 'running';
246
+ run.startedAt = Date.now();
247
+ const { requestId, input } = run;
248
+ let inputPath;
249
+ let child;
250
+ try {
251
+ const written = writeInputJson(requestId, {
252
+ requestId,
253
+ name: input.name,
254
+ adapterPath: input.adapterPath ?? resolveAdapterPath(input.name),
255
+ executionSeedArgs: input.rawSeedArgs, // raw → input.json only
256
+ fixture: input.fixture,
257
+ trace: input.trace,
258
+ // contextId intentionally NOT written — M6b verifies against the daemon default
259
+ // profile; RunnerInput.contextId is a tested forward seam (see verify-runner-main).
260
+ });
261
+ run.tempRoot = written.tempRoot;
262
+ inputPath = written.inputPath;
263
+ // raw seed args are now persisted to the 0600 input.json; drop them from memory immediately so a
264
+ // long-running verify doesn't hold the raw secret for its whole lifetime (Codex M9 review · High).
265
+ input.rawSeedArgs = {};
266
+ // maxRuntimeMs (#7): the child's orphan self-watchdog deadline — above our own timeout +
267
+ // grace so it only fires if WE die before killing it (e.g. win32 reaper can't kill the child).
268
+ const maxRuntimeMs = config.timeoutMs + config.killGraceMs + 5000;
269
+ const args = [...launcher.prefixArgs, ...buildRunnerArgs({ requestId, name: input.name, inputPath, protocolFd: PROTOCOL_FD, maxRuntimeMs })];
270
+ // Adapter stdin/stdout/stderr → /dev/null; the JSONL protocol gets its own pipe at fd 3, so
271
+ // user console.log can neither break (pollute) nor forge it (Codex #3). Parent reads stdio[3].
272
+ const spawnOpts = { env: buildChildEnv(written.tempRoot, opts.daemonPort), stdio: ['ignore', 'ignore', 'ignore', 'pipe'] };
273
+ child = spawnImpl(launcher.command, args, spawnOpts);
274
+ run.child = child;
275
+ if (typeof child.pid === 'number')
276
+ updateMarkerPid(written.tempRoot, child.pid);
277
+ }
278
+ catch (err) {
279
+ settle(run, 'failed', { ok: false, error: { code: 'runner_protocol_error', message: `launch failed: ${err instanceof Error ? err.message : String(err)}` } });
280
+ return;
281
+ }
282
+ run.killTimer = setTimeout(() => {
283
+ try {
284
+ child.kill('SIGTERM');
285
+ }
286
+ catch { /* already gone */ }
287
+ run.graceTimer = setTimeout(() => { try {
288
+ child.kill('SIGKILL');
289
+ }
290
+ catch { /* already gone */ } }, config.killGraceMs);
291
+ settle(run, 'timeout', { ok: false, error: { code: 'verify_timeout', message: `verify exceeded ${config.timeoutMs}ms` } });
292
+ }, config.timeoutMs);
293
+ // ── protocol JSONL on fd 3 — buffer-until-exit (M6c strict duplicate-result, 08:67) ──
294
+ // The protocol is read ONLY from the dedicated fd (Codex #3); the adapter's stdout/stderr are
295
+ // /dev/null, so user output can neither pollute nor forge it. The first `result` is RECORDED,
296
+ // not settled immediately: we keep reading so a SECOND result (or a post-result protocol
297
+ // violation) is caught as runner_protocol_error. The recorded result settles on child exit; a
298
+ // child that emits a result then never exits is force-killed after a short grace and settled
299
+ // with the captured result (not a timeout). `stdoutLimitBytes` now caps the protocol stream.
300
+ let protocolBytes = 0;
301
+ let buf = '';
302
+ let sawResult = false;
303
+ let pending = null;
304
+ const finishWithPending = () => {
305
+ if (run.status !== 'running')
306
+ return;
307
+ if (sawResult && pending)
308
+ settle(run, pending.ok ? 'succeeded' : 'failed', pending);
309
+ else
310
+ settle(run, 'failed', { ok: false, error: { code: 'runner_protocol_error', message: 'child exited without a result event' } });
311
+ };
312
+ const protocol = child.stdio[PROTOCOL_FD];
313
+ protocol?.on('data', (chunk) => {
314
+ if (run.status !== 'running')
315
+ return;
316
+ protocolBytes += chunk.length;
317
+ if (protocolBytes > config.stdoutLimitBytes) {
318
+ try {
319
+ child.kill('SIGKILL');
320
+ }
321
+ catch { /* already gone */ }
322
+ settle(run, 'failed', { ok: false, error: { code: 'output_truncated', message: `runner protocol exceeded ${config.stdoutLimitBytes} bytes` } });
323
+ return;
324
+ }
325
+ buf += chunk.toString('utf8');
326
+ let nl;
327
+ while ((nl = buf.indexOf('\n')) >= 0) {
328
+ const line = buf.slice(0, nl);
329
+ buf = buf.slice(nl + 1);
330
+ if (!line.trim())
331
+ continue;
332
+ const parsed = parseRunnerEvent(line, requestId, config.jsonlLineLimit);
333
+ if (!parsed.ok) {
334
+ try {
335
+ child.kill('SIGKILL');
336
+ }
337
+ catch { /* already gone */ }
338
+ settle(run, 'failed', { ok: false, error: { code: 'runner_protocol_error', message: parsed.reason } });
339
+ return;
340
+ }
341
+ if (parsed.event.type === 'result') {
342
+ if (sawResult) {
343
+ // A second terminal result is a protocol violation (08: one and only one result).
344
+ try {
345
+ child.kill('SIGKILL');
346
+ }
347
+ catch { /* already gone */ }
348
+ settle(run, 'failed', { ok: false, error: { code: 'runner_protocol_error', message: 'duplicate result event' } });
349
+ return;
350
+ }
351
+ sawResult = true;
352
+ pending = normalizeRunnerResult(parsed.event);
353
+ // Settle on exit; force-kill + settle if the child lingers past the grace.
354
+ run.resultGrace = setTimeout(() => { try {
355
+ child.kill('SIGKILL');
356
+ }
357
+ catch { /* gone */ } finishWithPending(); }, config.killGraceMs);
358
+ }
359
+ }
360
+ });
361
+ child.on('error', (err) => {
362
+ settle(run, 'failed', { ok: false, error: { code: 'runner_protocol_error', message: `spawn failed: ${err.message}` } });
363
+ });
364
+ // 'close' (not 'exit'): stdout is only guaranteed fully drained at close, so a buffered
365
+ // terminal result is always seen before we decide "no result". Settle with the captured
366
+ // result if any, else a no-result protocol violation (unless already settled via
367
+ // timeout/cancel/cap/duplicate).
368
+ child.on('close', () => {
369
+ if (run.graceTimer)
370
+ clearTimeout(run.graceTimer);
371
+ finishWithPending();
372
+ });
373
+ }
374
+ /** Pull queued runs into freed slots, FIFO, up to maxConcurrency. */
375
+ function scheduleNext() {
376
+ while (runningCount() < config.maxConcurrency && queue.length > 0) {
377
+ const id = queue.shift();
378
+ const run = runs.get(id);
379
+ if (run && run.status === 'queued')
380
+ launch(run);
381
+ }
382
+ }
383
+ /** A request id not already tracked (canonical from the caller, else generated with collision
384
+ * avoidance — Codex #5: a generated id must never alias a live run). */
385
+ function freshRequestId(provided) {
386
+ if (provided !== undefined)
387
+ return provided;
388
+ let id;
389
+ do {
390
+ id = `req_${randomUUID().replace(/-/g, '').slice(0, 16)}`;
391
+ } while (runs.has(id));
392
+ return id;
393
+ }
394
+ function startVerify(input) {
395
+ // Idempotency / anti-clobber (Codex #5 · P2): a repeat start for an id already tracked must NOT
396
+ // overwrite the existing run — `runs.set` would orphan the live child + its kill/grace timers and
397
+ // leak the temp dir, while status/cancel would silently retarget the new run. Return the existing
398
+ // id instead (a canonical-id retry — e.g. be re-forwarding after a network blip — is idempotent;
399
+ // the caller polls getRunStatus for whatever state that run is in).
400
+ if (input.requestId !== undefined) {
401
+ const existing = runs.get(input.requestId);
402
+ if (existing)
403
+ return Promise.resolve({ requestId: existing.requestId });
404
+ }
405
+ // requestId created BEFORE launch (08). Use the caller's canonical id when provided
406
+ // (be ↔ daemon ↔ runner share one id); else generate (standalone CLI / tests).
407
+ const requestId = freshRequestId(input.requestId);
408
+ // Concurrency gate (synchronous, in-memory): launch if a slot is free, else enqueue, else
409
+ // reject queue_full (08 `queued` status; 09 HIGH_LEVEL_QUEUE_LIMIT; 03 queue_full → 429).
410
+ // Invariant maintained by scheduleNext: runningCount < max ⟹ queue empty, so a free slot
411
+ // never jumps the queue.
412
+ const canLaunch = runningCount() < config.maxConcurrency;
413
+ if (!canLaunch && queue.length >= config.queueLimit) {
414
+ metrics.inc('runner_queue_rejected_total'); // #1c · 09 queue rejected count
415
+ return Promise.reject(Object.assign(new Error(`verify queue is full (limit ${config.queueLimit})`), { code: 'queue_full' }));
416
+ }
417
+ let resolveSettled;
418
+ const settled = new Promise((res) => { resolveSettled = res; });
419
+ const run = { requestId, status: 'queued', startedAt: Date.now(), input, summary: null, settled, resolveSettled };
420
+ runs.set(requestId, run);
421
+ if (canLaunch)
422
+ launch(run);
423
+ else {
424
+ queue.push(requestId);
425
+ metrics.observe('runner_queue_depth', queue.length);
426
+ } // #1c · 09 queue depth
427
+ return Promise.resolve({ requestId });
428
+ }
429
+ async function getVerifyStatus(requestId) {
430
+ return runs.get(requestId)?.summary ?? null; // null while queued/running; VerifySummary once terminal
431
+ }
432
+ async function cancelVerify(requestId) {
433
+ const run = runs.get(requestId);
434
+ if (!run)
435
+ return { cancelled: false };
436
+ if (run.status !== 'running' && run.status !== 'queued') {
437
+ // Idempotent: already terminal. "cancelled" only if it ended via cancellation.
438
+ return { cancelled: run.status === 'cancelled' };
439
+ }
440
+ if (run.status === 'queued') {
441
+ const i = queue.indexOf(requestId);
442
+ if (i >= 0)
443
+ queue.splice(i, 1); // drop from queue; no child/temp to clean
444
+ }
445
+ else {
446
+ // SIGTERM first so the child can release its browser lease (#4), then SIGKILL after the grace
447
+ // (mirrors the timeout path). The grace timer is unref'd — settle() already finalizes the run.
448
+ const child = run.child;
449
+ try {
450
+ child?.kill('SIGTERM');
451
+ }
452
+ catch { /* already gone */ }
453
+ if (child)
454
+ setTimeout(() => { try {
455
+ child.kill('SIGKILL');
456
+ }
457
+ catch { /* gone */ } }, config.killGraceMs).unref?.();
458
+ }
459
+ settle(run, 'cancelled', { ok: false, error: { code: 'runner_protocol_error', message: 'cancelled' } });
460
+ return { cancelled: true };
461
+ }
462
+ return {
463
+ startVerify,
464
+ getVerifyStatus,
465
+ cancelVerify,
466
+ whenSettled: (requestId) => runs.get(requestId)?.settled ?? null,
467
+ activeCount: () => runningCount(),
468
+ queuedCount: () => queue.length,
469
+ getRunStatus: (requestId) => {
470
+ const run = runs.get(requestId);
471
+ return run ? { status: run.status, summary: run.summary } : null;
472
+ },
473
+ };
474
+ }
475
+ /** Lazily-constructed process-wide default (used by verifyAdapter when no runner is injected). */
476
+ let _default = null;
477
+ let _defaultDaemonPort;
478
+ let _defaultMetrics;
479
+ let _defaultLogger;
480
+ let _defaultTempCapacity;
481
+ let _defaultSweepTemp;
482
+ /**
483
+ * Configure the daemon port the default runner hands to child processes via
484
+ * BYCLI_DAEMON_PORT (M6b: browser adapters connect back to this daemon for a Page).
485
+ * The daemon calls this at startup BEFORE the first defaultRunnerPort() use; calling it
486
+ * after the singleton is built is a no-op (the port is fixed at construction).
487
+ */
488
+ export function setDefaultRunnerDaemonPort(port) {
489
+ _defaultDaemonPort = port;
490
+ }
491
+ /**
492
+ * Share the daemon's metrics + structured logger with the default runner (#1c), so runner counters
493
+ * (timeout / queue depth / protocol error / verify duration) surface on the daemon's GET /metrics and
494
+ * runner verify logs share the daemon's level. Same injection contract as setDefaultRunnerDaemonPort:
495
+ * the daemon calls this at startup BEFORE the first defaultRunnerPort() use; later calls are no-ops.
496
+ */
497
+ export function setDefaultRunnerObservability(metrics, logger) {
498
+ _defaultMetrics = metrics;
499
+ _defaultLogger = logger;
500
+ }
501
+ /**
502
+ * Enable the temp-store capacity guard on the default runner (#1d). Only the daemon (the long-running
503
+ * owner of verify temp dirs) calls this; one-shot CLI verify leaves it off. `sweepTemp` is the daemon's
504
+ * on-pressure shed (its reap). Same injection contract: call BEFORE the first defaultRunnerPort() use.
505
+ */
506
+ export function setDefaultRunnerTempGuard(capacity, sweepTemp) {
507
+ _defaultTempCapacity = capacity;
508
+ _defaultSweepTemp = sweepTemp;
509
+ }
510
+ export function defaultRunnerPort() {
511
+ return (_default ??= createRunnerPort({
512
+ daemonPort: _defaultDaemonPort,
513
+ metrics: _defaultMetrics,
514
+ logger: _defaultLogger,
515
+ tempCapacity: _defaultTempCapacity,
516
+ sweepTemp: _defaultSweepTemp,
517
+ }));
518
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ /** A recording session is short-lived; an hour is a generous ceiling before the salt is dropped. */
2
+ export declare const DEFAULT_SESSION_KEY_TTL_MS = 3600000;
3
+ export declare class SessionKeyRegistry {
4
+ private readonly ttlMs;
5
+ private readonly now;
6
+ /** Injectable secret source (tests); defaults to a 256-bit CSPRNG salt. */
7
+ private readonly mintKey;
8
+ private readonly salts;
9
+ private fallbackKey;
10
+ constructor(ttlMs?: number, now?: () => number,
11
+ /** Injectable secret source (tests); defaults to a 256-bit CSPRNG salt. */
12
+ mintKey?: () => string);
13
+ /**
14
+ * The per-session HMAC key: lazily minted on first use and stable for the session's lifetime
15
+ * (within the TTL). An absent `sessionId` (standalone CLI verify with no recorder session) falls
16
+ * back to a single process-wide random secret — still session-unlinkable across daemon restarts and
17
+ * never derived from anything on the wire.
18
+ */
19
+ keyFor(sessionId?: string): string;
20
+ /** Explicit eviction when a session ends (best-effort; the TTL sweep is the backstop). */
21
+ evict(sessionId: string): void;
22
+ /** Drop every salt older than the TTL. Called on access and (optionally) on a periodic sweep. */
23
+ sweepExpired(): number;
24
+ /** Live salt count (introspection / tests). */
25
+ size(): number;
26
+ }
27
+ /** Process-wide registry used by the daemon /v1/verify handler. Reset implicitly on daemon restart. */
28
+ export declare function defaultSessionKeyRegistry(): SessionKeyRegistry;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Per-session HMAC key registry (M7a · 04:95-111).
3
+ *
4
+ * The verify evidence HMAC (`deriveEvidenceSeedArgs`) must be keyed by a per-session secret so that
5
+ * seed-arg evidence is scoped to a recorder session (`hmacScope: 'recorder_session'`), is not
6
+ * reversible, and is not comparable across sessions. The secret is a random salt held ONLY in daemon
7
+ * process memory:
8
+ * - it NEVER crosses the wire (be sends the non-secret `sessionId`; the daemon mints + holds the
9
+ * salt) — 04:111 forbids the salt from entering any request/response;
10
+ * - restarting the daemon drops every salt → automatic rotation (04:95);
11
+ * - a salt also TTL-expires so an abandoned session does not pin its secret forever.
12
+ *
13
+ * This replaces the M5c/M6 placeholder key `daemon-${PORT}`, which was process-wide (every session
14
+ * shared one deterministic key derived from the port — not a per-session secret).
15
+ */
16
+ import { randomBytes } from 'node:crypto';
17
+ /** A recording session is short-lived; an hour is a generous ceiling before the salt is dropped. */
18
+ export const DEFAULT_SESSION_KEY_TTL_MS = 3_600_000;
19
+ export class SessionKeyRegistry {
20
+ ttlMs;
21
+ now;
22
+ mintKey;
23
+ salts = new Map();
24
+ fallbackKey = null;
25
+ constructor(ttlMs = DEFAULT_SESSION_KEY_TTL_MS, now = Date.now,
26
+ /** Injectable secret source (tests); defaults to a 256-bit CSPRNG salt. */
27
+ mintKey = () => randomBytes(32).toString('hex')) {
28
+ this.ttlMs = ttlMs;
29
+ this.now = now;
30
+ this.mintKey = mintKey;
31
+ }
32
+ /**
33
+ * The per-session HMAC key: lazily minted on first use and stable for the session's lifetime
34
+ * (within the TTL). An absent `sessionId` (standalone CLI verify with no recorder session) falls
35
+ * back to a single process-wide random secret — still session-unlinkable across daemon restarts and
36
+ * never derived from anything on the wire.
37
+ */
38
+ keyFor(sessionId) {
39
+ if (!sessionId) {
40
+ this.fallbackKey ??= this.mintKey();
41
+ return this.fallbackKey;
42
+ }
43
+ this.sweepExpired();
44
+ const existing = this.salts.get(sessionId);
45
+ if (existing)
46
+ return existing.key;
47
+ const key = this.mintKey();
48
+ this.salts.set(sessionId, { key, createdAt: this.now() });
49
+ return key;
50
+ }
51
+ /** Explicit eviction when a session ends (best-effort; the TTL sweep is the backstop). */
52
+ evict(sessionId) { this.salts.delete(sessionId); }
53
+ /** Drop every salt older than the TTL. Called on access and (optionally) on a periodic sweep. */
54
+ sweepExpired() {
55
+ const cutoff = this.now() - this.ttlMs;
56
+ let dropped = 0;
57
+ for (const [sid, e] of this.salts) {
58
+ if (e.createdAt < cutoff) {
59
+ this.salts.delete(sid);
60
+ dropped++;
61
+ }
62
+ }
63
+ return dropped;
64
+ }
65
+ /** Live salt count (introspection / tests). */
66
+ size() { return this.salts.size; }
67
+ }
68
+ let singleton = null;
69
+ /** Process-wide registry used by the daemon /v1/verify handler. Reset implicitly on daemon restart. */
70
+ export function defaultSessionKeyRegistry() {
71
+ return (singleton ??= new SessionKeyRegistry());
72
+ }
@@ -0,0 +1 @@
1
+ export {};