@sovovs/bycli 1.8.0 → 2.1.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 (539) hide show
  1. package/cli-manifest.json +0 -61
  2. package/clis/twitter/search.js +3 -3
  3. package/dist/src/adapter-shadow.js +2 -2
  4. package/dist/src/browser/analyze.d.ts +10 -99
  5. package/dist/src/browser/analyze.js +10 -226
  6. package/dist/src/browser/cdp.js +67 -7
  7. package/dist/src/browser/daemon-client.d.ts +2 -0
  8. package/dist/src/browser/daemon-client.js +1 -1
  9. package/dist/src/browser/extension-capabilities.d.ts +13 -0
  10. package/dist/src/browser/extension-capabilities.js +22 -0
  11. package/dist/src/browser/extension-capabilities.test.d.ts +1 -0
  12. package/dist/src/browser/extension-version-metadata.test.d.ts +1 -0
  13. package/dist/src/browser/navigation-guard.d.ts +78 -0
  14. package/dist/src/browser/navigation-guard.js +142 -0
  15. package/dist/src/browser/navigation-guard.test.d.ts +1 -0
  16. package/dist/src/browser/page.d.ts +1 -0
  17. package/dist/src/browser/page.js +20 -1
  18. package/dist/src/browser/url-policy.d.ts +89 -0
  19. package/dist/src/browser/url-policy.js +311 -0
  20. package/dist/src/browser/url-policy.test.d.ts +1 -0
  21. package/dist/src/browser/verify-fixture.js +2 -2
  22. package/dist/src/build-manifest.js +4 -2
  23. package/dist/src/capabilityRouting.d.ts +3 -2
  24. package/dist/src/capabilityRouting.js +10 -2
  25. package/dist/src/cli.js +11 -14
  26. package/dist/src/commanderAdapter.js +5 -5
  27. package/dist/src/config-paths.d.ts +19 -0
  28. package/dist/src/config-paths.js +31 -0
  29. package/dist/src/daemon.js +256 -0
  30. package/dist/src/discovery.d.ts +10 -3
  31. package/dist/src/discovery.js +20 -10
  32. package/dist/src/discovery.test.d.ts +1 -0
  33. package/dist/src/execution.d.ts +5 -0
  34. package/dist/src/execution.js +272 -48
  35. package/dist/src/help.js +8 -8
  36. package/dist/src/main.js +18 -2
  37. package/dist/src/manifest-schema.d.ts +9 -0
  38. package/dist/src/manifest-schema.js +162 -0
  39. package/dist/src/manifest-schema.test.d.ts +1 -0
  40. package/dist/src/manifest-types.d.ts +1 -1
  41. package/dist/src/observation/redaction.js +10 -4
  42. package/dist/src/plugin.js +11 -8
  43. package/dist/src/recorder/highlevel/analyze.d.ts +29 -0
  44. package/dist/src/recorder/highlevel/analyze.js +91 -0
  45. package/dist/src/recorder/highlevel/init.d.ts +125 -0
  46. package/dist/src/recorder/highlevel/init.js +279 -0
  47. package/dist/src/recorder/highlevel/init.test.d.ts +1 -0
  48. package/dist/src/recorder/highlevel/verify.d.ts +73 -0
  49. package/dist/src/recorder/highlevel/verify.js +74 -0
  50. package/dist/src/recorder/http/analyze-runner.d.ts +28 -0
  51. package/dist/src/recorder/http/analyze-runner.js +78 -0
  52. package/dist/src/recorder/http/analyze-runner.test.d.ts +1 -0
  53. package/dist/src/recorder/http/handlers.d.ts +51 -0
  54. package/dist/src/recorder/http/handlers.js +219 -0
  55. package/dist/src/recorder/http/wrapper-config.d.ts +20 -0
  56. package/dist/src/recorder/http/wrapper-config.js +82 -0
  57. package/dist/src/recorder/http/wrapper-envelope.d.ts +31 -0
  58. package/dist/src/recorder/http/wrapper-envelope.js +51 -0
  59. package/dist/src/recorder/http/wrapper-gates.d.ts +16 -0
  60. package/dist/src/recorder/http/wrapper-gates.js +28 -0
  61. package/dist/src/recorder/http/wrapper-metrics.d.ts +1 -0
  62. package/dist/src/recorder/http/wrapper-metrics.js +5 -0
  63. package/dist/src/recorder/http/wrapper-registry.d.ts +50 -0
  64. package/dist/src/recorder/http/wrapper-registry.js +98 -0
  65. package/dist/src/recorder/http/wrapper-server.d.ts +59 -0
  66. package/dist/src/recorder/http/wrapper-server.js +240 -0
  67. package/dist/src/recorder/http/wrapper.test.d.ts +1 -0
  68. package/dist/src/recorder/observability/logger.d.ts +5 -0
  69. package/dist/src/recorder/observability/logger.js +13 -0
  70. package/dist/src/recorder/runner/config.d.ts +34 -0
  71. package/dist/src/recorder/runner/config.js +84 -0
  72. package/dist/src/recorder/runner/config.test.d.ts +1 -0
  73. package/dist/src/recorder/runner/reap.d.ts +79 -0
  74. package/dist/src/recorder/runner/reap.js +230 -0
  75. package/dist/src/recorder/runner/reap.test.d.ts +1 -0
  76. package/dist/src/recorder/runner/runner-port.d.ts +95 -0
  77. package/dist/src/recorder/runner/runner-port.js +518 -0
  78. package/dist/src/recorder/runner/runner-port.test.d.ts +1 -0
  79. package/dist/src/recorder/runner/session-keys.d.ts +28 -0
  80. package/dist/src/recorder/runner/session-keys.js +72 -0
  81. package/dist/src/recorder/runner/session-keys.test.d.ts +1 -0
  82. package/dist/src/recorder/runner/verify-runner-main.d.ts +96 -0
  83. package/dist/src/recorder/runner/verify-runner-main.js +292 -0
  84. package/dist/src/recorder/runner/verify-runner-main.test.d.ts +1 -0
  85. package/dist/src/registry-api.d.ts +1 -1
  86. package/dist/src/registry-api.types.test.d.ts +1 -0
  87. package/dist/src/registry-transaction.d.ts +42 -0
  88. package/dist/src/registry-transaction.js +194 -0
  89. package/dist/src/registry-transaction.test.d.ts +1 -0
  90. package/dist/src/registry.d.ts +58 -16
  91. package/dist/src/registry.js +131 -15
  92. package/dist/src/serialization.d.ts +1 -1
  93. package/dist/src/serialization.js +3 -3
  94. package/dist/src/types.d.ts +2 -0
  95. package/package.json +8 -5
  96. package/scripts/m1-daemon-navigate-drive.mjs +138 -0
  97. package/scripts/m1-extension-spike.mjs +237 -0
  98. package/scripts/m1-live-spike.mjs +156 -0
  99. package/scripts/m6b-connect-back-spike.sh +31 -0
  100. package/clis/12306/utils.test.js +0 -331
  101. package/clis/1688/assets.test.js +0 -39
  102. package/clis/1688/download.test.js +0 -31
  103. package/clis/1688/item.test.js +0 -67
  104. package/clis/1688/search.test.js +0 -75
  105. package/clis/1688/shared.test.js +0 -57
  106. package/clis/1688/store.test.js +0 -62
  107. package/clis/36kr/article.test.js +0 -46
  108. package/clis/36kr/hot.test.js +0 -15
  109. package/clis/36kr/news.test.js +0 -85
  110. package/clis/51job/utils.test.js +0 -69
  111. package/clis/_atlassian/shared.test.js +0 -170
  112. package/clis/aibase/news.test.js +0 -59
  113. package/clis/amazon/bestsellers.test.js +0 -29
  114. package/clis/amazon/discussion.test.js +0 -124
  115. package/clis/amazon/offer.test.js +0 -29
  116. package/clis/amazon/product.test.js +0 -24
  117. package/clis/amazon/rankings.test.js +0 -41
  118. package/clis/amazon/search.test.js +0 -22
  119. package/clis/amazon/shared.test.js +0 -44
  120. package/clis/apple-podcasts/commands.test.js +0 -119
  121. package/clis/apple-podcasts/utils.test.js +0 -57
  122. package/clis/arxiv/arxiv.test.js +0 -112
  123. package/clis/baidu-scholar/search.test.js +0 -23
  124. package/clis/barchart/greeks.test.js +0 -138
  125. package/clis/bilibili/comment.test.js +0 -153
  126. package/clis/bilibili/comments.test.js +0 -142
  127. package/clis/bilibili/dynamic.test.js +0 -68
  128. package/clis/bilibili/hot.test.js +0 -17
  129. package/clis/bilibili/subtitle.test.js +0 -196
  130. package/clis/bilibili/summary.test.js +0 -210
  131. package/clis/bilibili/utils.test.js +0 -61
  132. package/clis/bilibili/video.test.js +0 -132
  133. package/clis/binance/commands.test.js +0 -70
  134. package/clis/bloomberg/utils.test.js +0 -129
  135. package/clis/booking/booking.test.js +0 -356
  136. package/clis/boss/chatlist.test.js +0 -211
  137. package/clis/boss/chatmsg.test.js +0 -230
  138. package/clis/boss/search.test.js +0 -78
  139. package/clis/boss/utils.test.js +0 -34
  140. package/clis/brave/search.test.js +0 -76
  141. package/clis/chaoxing/utils.test.js +0 -45
  142. package/clis/chatgpt/commands.test.js +0 -45
  143. package/clis/chatgpt/envelope.test.js +0 -108
  144. package/clis/chatgpt/image.test.js +0 -188
  145. package/clis/chatgpt/utils.test.js +0 -330
  146. package/clis/chatgpt-app/ax.test.js +0 -35
  147. package/clis/chatwise/composer.test.js +0 -186
  148. package/clis/chess/analyze.test.js +0 -79
  149. package/clis/chess/game.test.js +0 -178
  150. package/clis/chess/games.test.js +0 -164
  151. package/clis/chess/stats.test.js +0 -79
  152. package/clis/chess/utils.test.js +0 -230
  153. package/clis/claude/ask.test.js +0 -338
  154. package/clis/claude/commands.test.js +0 -118
  155. package/clis/claude/utils.test.js +0 -148
  156. package/clis/cnki/search.test.js +0 -18
  157. package/clis/codex/sidebar.test.js +0 -329
  158. package/clis/coingecko/coingecko.test.js +0 -109
  159. package/clis/confluence/commands.test.js +0 -195
  160. package/clis/coupang/coupang.test.js +0 -159
  161. package/clis/coupang/utils.test.js +0 -62
  162. package/clis/ctrip/ctrip.test.js +0 -719
  163. package/clis/dblp/dblp.test.js +0 -397
  164. package/clis/deepseek/ask.test.js +0 -312
  165. package/clis/deepseek/detail.test.js +0 -81
  166. package/clis/deepseek/send.test.js +0 -107
  167. package/clis/deepseek/utils.test.js +0 -370
  168. package/clis/defillama/defillama.test.js +0 -99
  169. package/clis/devto/devto.test.js +0 -236
  170. package/clis/dianping/dianping.test.js +0 -578
  171. package/clis/douban/download.test.js +0 -170
  172. package/clis/douban/movie-hot.test.js +0 -14
  173. package/clis/douban/search.test.js +0 -11
  174. package/clis/douban/subject.test.js +0 -11
  175. package/clis/douban/utils.test.js +0 -438
  176. package/clis/doubao/detail.test.js +0 -42
  177. package/clis/doubao/history.test.js +0 -37
  178. package/clis/doubao/utils.test.js +0 -344
  179. package/clis/douyin/_shared/browser-fetch.test.js +0 -64
  180. package/clis/douyin/_shared/creation-id.test.js +0 -22
  181. package/clis/douyin/_shared/imagex-upload.test.js +0 -87
  182. package/clis/douyin/_shared/sts2.test.js +0 -27
  183. package/clis/douyin/_shared/text-extra.test.js +0 -37
  184. package/clis/douyin/_shared/timing.test.js +0 -28
  185. package/clis/douyin/_shared/tos-upload-short-read.test.js +0 -83
  186. package/clis/douyin/_shared/tos-upload.test.js +0 -229
  187. package/clis/douyin/_shared/transcode.test.js +0 -91
  188. package/clis/douyin/_shared/vod-upload.test.js +0 -38
  189. package/clis/douyin/activities.test.js +0 -55
  190. package/clis/douyin/collections.test.js +0 -45
  191. package/clis/douyin/delete.test.js +0 -100
  192. package/clis/douyin/draft.test.js +0 -335
  193. package/clis/douyin/drafts.test.js +0 -11
  194. package/clis/douyin/hashtag.test.js +0 -171
  195. package/clis/douyin/location.test.js +0 -23
  196. package/clis/douyin/profile.test.js +0 -46
  197. package/clis/douyin/publish-upload-id.test.js +0 -170
  198. package/clis/douyin/publish.test.js +0 -38
  199. package/clis/douyin/stats.test.js +0 -22
  200. package/clis/douyin/update.test.js +0 -11
  201. package/clis/douyin/user-videos.test.js +0 -151
  202. package/clis/douyin/videos.test.js +0 -54
  203. package/clis/duckduckgo/search.test.js +0 -128
  204. package/clis/duckduckgo/suggest.test.js +0 -66
  205. package/clis/eastmoney/hot-rank.test.js +0 -59
  206. package/clis/endoflife/endoflife.test.js +0 -51
  207. package/clis/facebook/feed.test.js +0 -169
  208. package/clis/facebook/marketplace.test.js +0 -91
  209. package/clis/facebook/notifications.test.js +0 -458
  210. package/clis/facebook/search.test.js +0 -55
  211. package/clis/flathub/flathub.test.js +0 -90
  212. package/clis/flomo/memos.test.js +0 -144
  213. package/clis/gemini/ask.test.js +0 -100
  214. package/clis/gemini/deep-research-result.test.js +0 -155
  215. package/clis/gemini/deep-research.test.js +0 -181
  216. package/clis/gemini/reply-state.test.js +0 -671
  217. package/clis/gemini/utils.test.js +0 -198
  218. package/clis/geogebra/geogebra.test.js +0 -175
  219. package/clis/gitee/search.test.js +0 -65
  220. package/clis/gitee/user.test.js +0 -63
  221. package/clis/google/utils.test.js +0 -75
  222. package/clis/google-scholar/cite.test.js +0 -47
  223. package/clis/google-scholar/profile.test.js +0 -49
  224. package/clis/google-scholar/search.test.js +0 -71
  225. package/clis/goproxy/goproxy.test.js +0 -103
  226. package/clis/gov-law/commands.test.js +0 -39
  227. package/clis/gov-policy/gov-policy.test.js +0 -224
  228. package/clis/grok/ask.test.js +0 -29
  229. package/clis/grok/utils.test.js +0 -103
  230. package/clis/hackernews/hackernews.test.js +0 -132
  231. package/clis/hf/hf.test.js +0 -16
  232. package/clis/hupu/hot.test.js +0 -224
  233. package/clis/imdb/utils.test.js +0 -88
  234. package/clis/indeed/indeed.test.js +0 -375
  235. package/clis/instagram/_shared/private-publish.test.js +0 -705
  236. package/clis/instagram/_shared/protocol-capture.test.js +0 -114
  237. package/clis/instagram/download.test.js +0 -118
  238. package/clis/instagram/note.test.js +0 -53
  239. package/clis/instagram/post.test.js +0 -1647
  240. package/clis/instagram/reel.test.js +0 -167
  241. package/clis/instagram/story.test.js +0 -134
  242. package/clis/jd/commands.test.js +0 -41
  243. package/clis/jd/item.test.js +0 -354
  244. package/clis/jianyu/search.test.js +0 -153
  245. package/clis/jianyu/shared/procurement-contract.test.js +0 -72
  246. package/clis/jianyu/shared/procurement-detail.test.js +0 -84
  247. package/clis/jike/read.test.js +0 -86
  248. package/clis/jira/commands.test.js +0 -287
  249. package/clis/juejin/search.js +0 -123
  250. package/clis/juejin/search.test.js +0 -98
  251. package/clis/lesswrong/frontpage.test.js +0 -37
  252. package/clis/lichess/lichess.test.js +0 -85
  253. package/clis/linkedin/connect.test.js +0 -213
  254. package/clis/linkedin/inbox.test.js +0 -152
  255. package/clis/linkedin/job-detail.test.js +0 -38
  256. package/clis/linkedin/jobs-preferences.test.js +0 -43
  257. package/clis/linkedin/people-search.test.js +0 -216
  258. package/clis/linkedin/post-analytics.test.js +0 -40
  259. package/clis/linkedin/posts.test.js +0 -40
  260. package/clis/linkedin/profile-analytics.test.js +0 -67
  261. package/clis/linkedin/profile-experience.test.js +0 -152
  262. package/clis/linkedin/profile-projects.test.js +0 -111
  263. package/clis/linkedin/profile-read.test.js +0 -77
  264. package/clis/linkedin/safe-send.test.js +0 -204
  265. package/clis/linkedin/salesnav-inbox.test.js +0 -113
  266. package/clis/linkedin/salesnav-message.test.js +0 -172
  267. package/clis/linkedin/salesnav-search.test.js +0 -76
  268. package/clis/linkedin/salesnav-thread.test.js +0 -79
  269. package/clis/linkedin/search.test.js +0 -222
  270. package/clis/linkedin/sent-invitations.test.js +0 -62
  271. package/clis/linkedin/services-read.test.js +0 -105
  272. package/clis/linkedin/thread-snapshot.test.js +0 -89
  273. package/clis/linkedin/timeline.test.js +0 -81
  274. package/clis/linkedin-learning/course.test.js +0 -114
  275. package/clis/linkedin-learning/search.test.js +0 -144
  276. package/clis/linkedin-learning/trending.test.js +0 -123
  277. package/clis/linux-do/feed.test.js +0 -153
  278. package/clis/linux-do/topic-content.test.js +0 -59
  279. package/clis/lobsters/lobsters.test.js +0 -169
  280. package/clis/notebooklm/add-source.test.js +0 -97
  281. package/clis/notebooklm/compat.test.js +0 -16
  282. package/clis/notebooklm/create.test.js +0 -58
  283. package/clis/notebooklm/generate-audio.test.js +0 -63
  284. package/clis/notebooklm/generate-slides.test.js +0 -75
  285. package/clis/notebooklm/history.test.js +0 -58
  286. package/clis/notebooklm/note-list.test.js +0 -56
  287. package/clis/notebooklm/notes-get.test.js +0 -72
  288. package/clis/notebooklm/open.test.js +0 -63
  289. package/clis/notebooklm/rpc.test.js +0 -131
  290. package/clis/notebooklm/source-fulltext.test.js +0 -106
  291. package/clis/notebooklm/source-get.test.js +0 -84
  292. package/clis/notebooklm/source-guide.test.js +0 -104
  293. package/clis/notebooklm/summary.test.js +0 -78
  294. package/clis/notebooklm/utils.test.js +0 -449
  295. package/clis/notebooklm/write-note.test.js +0 -70
  296. package/clis/nuget/nuget.test.js +0 -111
  297. package/clis/oeis/oeis.test.js +0 -88
  298. package/clis/ones/task-helpers.test.js +0 -12
  299. package/clis/ones/worklog.test.js +0 -20
  300. package/clis/openfda/openfda.test.js +0 -114
  301. package/clis/openreview/openreview.test.js +0 -427
  302. package/clis/osv/osv.test.js +0 -97
  303. package/clis/paperreview/commands.test.js +0 -243
  304. package/clis/paperreview/utils.test.js +0 -49
  305. package/clis/pixiv/download.test.js +0 -87
  306. package/clis/pixiv/illusts.test.js +0 -99
  307. package/clis/pixiv/search.test.js +0 -83
  308. package/clis/powerchina/search.test.js +0 -93
  309. package/clis/producthunt/utils.test.js +0 -64
  310. package/clis/pubmed/pubmed.test.js +0 -276
  311. package/clis/quark/utils.test.js +0 -58
  312. package/clis/qwen/utils.test.js +0 -45
  313. package/clis/reddit/extract-media.test.js +0 -149
  314. package/clis/reddit/frontpage.test.js +0 -34
  315. package/clis/reddit/home.test.js +0 -170
  316. package/clis/reddit/hot.test.js +0 -32
  317. package/clis/reddit/popular.test.js +0 -26
  318. package/clis/reddit/read.test.js +0 -335
  319. package/clis/reddit/reply.test.js +0 -89
  320. package/clis/reddit/search.test.js +0 -26
  321. package/clis/reddit/subreddit-info.test.js +0 -163
  322. package/clis/reddit/subreddit.test.js +0 -31
  323. package/clis/reddit/subscribed.test.js +0 -168
  324. package/clis/reddit/whoami.test.js +0 -105
  325. package/clis/rednote/navigation.test.js +0 -26
  326. package/clis/rednote/rednote.test.js +0 -222
  327. package/clis/rest-countries/rest-countries.test.js +0 -83
  328. package/clis/reuters/reuters.test.js +0 -299
  329. package/clis/rfc/rfc.test.js +0 -74
  330. package/clis/sinafinance/stock.test.js +0 -59
  331. package/clis/spotify/utils.test.js +0 -67
  332. package/clis/stackoverflow/stackoverflow.test.js +0 -404
  333. package/clis/steam/steam.test.js +0 -46
  334. package/clis/substack/utils.test.js +0 -44
  335. package/clis/suno/commands.test.js +0 -188
  336. package/clis/suno/download.test.js +0 -151
  337. package/clis/suno/generate.test.js +0 -252
  338. package/clis/suno/utils.test.js +0 -329
  339. package/clis/taobao/commands.test.js +0 -41
  340. package/clis/tdx/hot-rank.test.js +0 -59
  341. package/clis/ths/hot-rank.test.js +0 -64
  342. package/clis/tieba/commands.test.js +0 -79
  343. package/clis/tieba/utils.test.js +0 -290
  344. package/clis/tiktok/creator-videos.test.js +0 -113
  345. package/clis/tiktok/refactor.test.js +0 -389
  346. package/clis/tiktok/write-refactor.test.js +0 -370
  347. package/clis/toutiao/toutiao.test.js +0 -378
  348. package/clis/tvmaze/tvmaze.test.js +0 -93
  349. package/clis/twitter/bookmark-folder.test.js +0 -395
  350. package/clis/twitter/bookmark-folders.test.js +0 -152
  351. package/clis/twitter/bookmark.test.js +0 -74
  352. package/clis/twitter/bookmarks.test.js +0 -205
  353. package/clis/twitter/delete.test.js +0 -85
  354. package/clis/twitter/device-follow.test.js +0 -287
  355. package/clis/twitter/download.test.js +0 -457
  356. package/clis/twitter/followers.test.js +0 -62
  357. package/clis/twitter/following.test.js +0 -338
  358. package/clis/twitter/hide-reply.test.js +0 -76
  359. package/clis/twitter/like.test.js +0 -73
  360. package/clis/twitter/likes.test.js +0 -220
  361. package/clis/twitter/list-add.test.js +0 -133
  362. package/clis/twitter/list-create.test.js +0 -169
  363. package/clis/twitter/list-remove.test.js +0 -110
  364. package/clis/twitter/list-tweets.test.js +0 -196
  365. package/clis/twitter/lists.test.js +0 -253
  366. package/clis/twitter/post.test.js +0 -258
  367. package/clis/twitter/profile.test.js +0 -39
  368. package/clis/twitter/quote.test.js +0 -194
  369. package/clis/twitter/reply.test.js +0 -237
  370. package/clis/twitter/retweet.test.js +0 -73
  371. package/clis/twitter/search.test.js +0 -349
  372. package/clis/twitter/shared.test.js +0 -794
  373. package/clis/twitter/thread.test.js +0 -58
  374. package/clis/twitter/timeline.test.js +0 -104
  375. package/clis/twitter/trending.test.js +0 -15
  376. package/clis/twitter/tweets.test.js +0 -362
  377. package/clis/twitter/unbookmark.test.js +0 -73
  378. package/clis/twitter/unlike.test.js +0 -75
  379. package/clis/twitter/unretweet.test.js +0 -73
  380. package/clis/twitter/utils.test.js +0 -169
  381. package/clis/uisdc/news.test.js +0 -66
  382. package/clis/uiverse/_shared.test.js +0 -62
  383. package/clis/uiverse/navigation.test.js +0 -12
  384. package/clis/upwork/upwork.test.js +0 -566
  385. package/clis/wanfang/search.test.js +0 -23
  386. package/clis/web/read.test.js +0 -392
  387. package/clis/weibo/delete.test.js +0 -94
  388. package/clis/weibo/envelope.test.js +0 -85
  389. package/clis/weibo/favorites.test.js +0 -114
  390. package/clis/weibo/publish.test.js +0 -192
  391. package/clis/weibo/user-posts.test.js +0 -92
  392. package/clis/weibo/utils.test.js +0 -36
  393. package/clis/weixin/drafts.test.js +0 -69
  394. package/clis/weixin/search.test.js +0 -227
  395. package/clis/weread/ai-outline.test.js +0 -83
  396. package/clis/weread/book-search.test.js +0 -242
  397. package/clis/weread/commands.test.js +0 -398
  398. package/clis/weread/private-api-regression.test.js +0 -369
  399. package/clis/weread/search-regression.test.js +0 -494
  400. package/clis/weread/utils.test.js +0 -128
  401. package/clis/weread-official/commands.test.js +0 -385
  402. package/clis/weread-official/utils.test.js +0 -242
  403. package/clis/wikidata/wikidata.test.js +0 -83
  404. package/clis/wikipedia/trending.test.js +0 -57
  405. package/clis/wttr/wttr.test.js +0 -84
  406. package/clis/xianyu/chat.test.js +0 -84
  407. package/clis/xianyu/im.test.js +0 -253
  408. package/clis/xianyu/item.test.js +0 -56
  409. package/clis/xianyu/publish.test.js +0 -220
  410. package/clis/xianyu/search.test.js +0 -17
  411. package/clis/xiaoe/xiaoe.test.js +0 -486
  412. package/clis/xiaohongshu/comments.test.js +0 -258
  413. package/clis/xiaohongshu/creator-note-detail.test.js +0 -451
  414. package/clis/xiaohongshu/creator-notes-summary.test.js +0 -94
  415. package/clis/xiaohongshu/creator-notes.test.js +0 -281
  416. package/clis/xiaohongshu/creator-stats.test.js +0 -24
  417. package/clis/xiaohongshu/delete-note.test.js +0 -172
  418. package/clis/xiaohongshu/download.test.js +0 -316
  419. package/clis/xiaohongshu/navigation.test.js +0 -34
  420. package/clis/xiaohongshu/note.test.js +0 -249
  421. package/clis/xiaohongshu/publish.test.js +0 -608
  422. package/clis/xiaohongshu/search.test.js +0 -325
  423. package/clis/xiaohongshu/user-helpers.test.js +0 -160
  424. package/clis/xiaoyuzhou/auth.test.js +0 -124
  425. package/clis/xiaoyuzhou/download.test.js +0 -135
  426. package/clis/xiaoyuzhou/podcast-episodes.test.js +0 -78
  427. package/clis/xiaoyuzhou/transcript.test.js +0 -195
  428. package/clis/xiaoyuzhou/utils.test.js +0 -39
  429. package/clis/xueqiu/comments.test.js +0 -696
  430. package/clis/xueqiu/danjuan-utils.test.js +0 -41
  431. package/clis/xueqiu/utils.test.js +0 -26
  432. package/clis/yahoo/search.test.js +0 -94
  433. package/clis/youdao/note.test.js +0 -99
  434. package/clis/youtube/channel.test.js +0 -59
  435. package/clis/youtube/feed.test.js +0 -131
  436. package/clis/youtube/transcript-group.test.js +0 -99
  437. package/clis/youtube/transcript.test.js +0 -296
  438. package/clis/youtube/utils.test.js +0 -84
  439. package/clis/yuanbao/ask.test.js +0 -124
  440. package/clis/yuanbao/new.test.js +0 -30
  441. package/clis/yuanbao/shared.test.js +0 -80
  442. package/clis/zhihu/answer-comments.test.js +0 -287
  443. package/clis/zhihu/answer-detail.test.js +0 -338
  444. package/clis/zhihu/answer.test.js +0 -54
  445. package/clis/zhihu/collection.test.js +0 -337
  446. package/clis/zhihu/collections.test.js +0 -182
  447. package/clis/zhihu/comment.test.js +0 -50
  448. package/clis/zhihu/download.test.js +0 -12
  449. package/clis/zhihu/favorite.test.js +0 -59
  450. package/clis/zhihu/follow.test.js +0 -42
  451. package/clis/zhihu/like.test.js +0 -41
  452. package/clis/zhihu/question.test.js +0 -251
  453. package/clis/zhihu/recommend.test.js +0 -143
  454. package/clis/zhihu/search.test.js +0 -198
  455. package/clis/zhihu/target.test.js +0 -77
  456. package/clis/zhihu/text.test.js +0 -24
  457. package/clis/zhihu/write-shared.test.js +0 -176
  458. package/clis/zlibrary/commands.test.js +0 -65
  459. package/clis/zsxq/search.test.js +0 -24
  460. package/clis/zsxq/topic.test.js +0 -30
  461. package/clis/zsxq/topics.test.js +0 -24
  462. package/clis/zsxq/utils.test.js +0 -31
  463. package/dist/src/adapter-shadow.test.js +0 -49
  464. package/dist/src/adapter-source.test.js +0 -29
  465. package/dist/src/browser/analyze.test.js +0 -166
  466. package/dist/src/browser/article-extract.e2e.test.js +0 -105
  467. package/dist/src/browser/article-extract.test.js +0 -94
  468. package/dist/src/browser/ax-snapshot.test.js +0 -91
  469. package/dist/src/browser/base-page.test.js +0 -758
  470. package/dist/src/browser/cdp-click-fixture.test.js +0 -87
  471. package/dist/src/browser/cdp.test.js +0 -79
  472. package/dist/src/browser/compound.test.js +0 -175
  473. package/dist/src/browser/daemon-client.test.js +0 -199
  474. package/dist/src/browser/dom-helpers.test.js +0 -106
  475. package/dist/src/browser/dom-snapshot.test.js +0 -313
  476. package/dist/src/browser/errors.test.js +0 -60
  477. package/dist/src/browser/extract.test.js +0 -129
  478. package/dist/src/browser/find.test.js +0 -180
  479. package/dist/src/browser/html-tree.test.js +0 -181
  480. package/dist/src/browser/network-cache.test.js +0 -58
  481. package/dist/src/browser/network-key.test.js +0 -49
  482. package/dist/src/browser/page.test.js +0 -378
  483. package/dist/src/browser/shape-filter.test.js +0 -101
  484. package/dist/src/browser/shape.test.js +0 -82
  485. package/dist/src/browser/stealth.test.js +0 -134
  486. package/dist/src/browser/target-errors.test.js +0 -94
  487. package/dist/src/browser/target-resolver.test.js +0 -118
  488. package/dist/src/browser/utils.test.js +0 -29
  489. package/dist/src/browser/verify-fixture.test.js +0 -219
  490. package/dist/src/browser.test.js +0 -232
  491. package/dist/src/build-manifest.test.js +0 -341
  492. package/dist/src/capabilityRouting.test.js +0 -63
  493. package/dist/src/cli-argv-preprocess.test.js +0 -209
  494. package/dist/src/cli.test.js +0 -2803
  495. package/dist/src/commanderAdapter.test.js +0 -352
  496. package/dist/src/commands/daemon.test.js +0 -219
  497. package/dist/src/completion.test.js +0 -24
  498. package/dist/src/convention-audit.test.js +0 -247
  499. package/dist/src/daemon.test.js +0 -60
  500. package/dist/src/doctor.test.js +0 -263
  501. package/dist/src/download/article-download.test.js +0 -235
  502. package/dist/src/download/index.test.js +0 -140
  503. package/dist/src/download/media-download.test.js +0 -112
  504. package/dist/src/download/progress.test.js +0 -36
  505. package/dist/src/electron-apps.test.js +0 -49
  506. package/dist/src/engine.test.js +0 -312
  507. package/dist/src/errors.test.js +0 -109
  508. package/dist/src/execution.test.js +0 -623
  509. package/dist/src/extension-manifest-regression.test.js +0 -12
  510. package/dist/src/external.test.js +0 -146
  511. package/dist/src/help.test.js +0 -58
  512. package/dist/src/hooks.test.js +0 -92
  513. package/dist/src/interceptor.test.js +0 -81
  514. package/dist/src/launcher.test.js +0 -138
  515. package/dist/src/node-network.test.js +0 -55
  516. package/dist/src/observation/artifact.test.js +0 -121
  517. package/dist/src/observation/manager.test.js +0 -13
  518. package/dist/src/observation/redaction.test.js +0 -32
  519. package/dist/src/observation/retention.test.js +0 -118
  520. package/dist/src/observation/ring-buffer.test.js +0 -22
  521. package/dist/src/output.test.js +0 -53
  522. package/dist/src/package-exports.test.js +0 -112
  523. package/dist/src/pipeline/executor.test.js +0 -182
  524. package/dist/src/pipeline/steps/download.test.js +0 -102
  525. package/dist/src/pipeline/steps/fetch.test.js +0 -117
  526. package/dist/src/pipeline/template.test.js +0 -175
  527. package/dist/src/pipeline/transform.test.js +0 -133
  528. package/dist/src/plugin-manifest.test.js +0 -179
  529. package/dist/src/plugin-scaffold.test.js +0 -83
  530. package/dist/src/plugin.test.js +0 -1324
  531. package/dist/src/registry.test.js +0 -209
  532. package/dist/src/runtime-detect.test.js +0 -39
  533. package/dist/src/serialization.test.js +0 -78
  534. package/dist/src/snapshotFormatter.test.js +0 -521
  535. package/dist/src/tui.test.js +0 -19
  536. package/dist/src/update-check.test.js +0 -116
  537. package/dist/src/utils.test.js +0 -155
  538. package/dist/src/validate.test.js +0 -90
  539. 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 {};