hunter-mate 0.1.15 → 0.1.17

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 (654) hide show
  1. package/dist/assets/extension/background/service-worker.20261001153649.js +4618 -0
  2. package/dist/assets/extension/background/service-worker.js +536 -99
  3. package/dist/assets/extension/build-info.json +1 -1
  4. package/dist/assets/extension/content/index.js +2 -2
  5. package/dist/assets/extension/icons/icon128.png +0 -0
  6. package/dist/assets/extension/manifest.json +7 -3
  7. package/dist/assets/extension/offscreen/offscreen.html +1 -1
  8. package/dist/assets/extension/offscreen/offscreen.js +269 -117
  9. package/dist/assets/extension/package.json +2 -1
  10. package/dist/assets/extension/sidepanel/index.css +1 -1
  11. package/dist/assets/extension/sidepanel/index.html +1 -1
  12. package/dist/assets/extension/sidepanel/index.js +27 -10
  13. package/dist/assets/server/chunks/chunk-EKKMDS42.mjs +26 -0
  14. package/dist/assets/server/chunks/chunk-GNM26E3H.mjs +54 -0
  15. package/dist/assets/server/chunks/chunk-IXYSL3NT.mjs +567 -0
  16. package/dist/assets/server/chunks/chunk-SAJB6JRK.mjs +14421 -0
  17. package/dist/assets/server/chunks/chunk-U7FVFSKP.mjs +1565 -0
  18. package/dist/assets/server/chunks/esm-4U746B76.mjs +29 -0
  19. package/dist/assets/server/chunks/getMachineId-bsd-L5TFP6QC.mjs +46 -0
  20. package/dist/assets/server/chunks/getMachineId-darwin-OY4E7LEK.mjs +45 -0
  21. package/dist/assets/server/chunks/getMachineId-linux-NSVVMJTW.mjs +39 -0
  22. package/dist/assets/server/chunks/getMachineId-unsupported-WBPZVDKV.mjs +29 -0
  23. package/dist/assets/server/chunks/getMachineId-win-LGO5ETVN.mjs +48 -0
  24. package/dist/assets/server/chunks/script-rule-ANR2J7E6.mjs +14 -0
  25. package/dist/assets/server/main.mjs +302490 -0
  26. package/dist/index.js +72986 -174
  27. package/dist/postinstall.js +221 -27
  28. package/dist/watchdog.js +324 -233
  29. package/dist/web/dist/404.html +1 -1
  30. package/dist/web/dist/_next/static/chunks/170-b66b6f731381943e.js +1 -0
  31. package/dist/web/dist/_next/static/chunks/{589-d632233641f35681.js → 589-5e99e548a0ee6bac.js} +1 -1
  32. package/dist/web/dist/_next/static/chunks/686-ade57c57a1737818.js +1 -0
  33. package/dist/web/dist/_next/static/chunks/{935-99f57b7d4151d9e7.js → 935-bb4adb3868af4cd4.js} +2 -2
  34. package/dist/web/dist/_next/static/chunks/{a7067448-96dd4b8457c447e8.js → a7067448-d3882c7e74c162bf.js} +1 -1
  35. package/dist/web/dist/_next/static/chunks/app/_not-found/{page-fdf1a13a7656886a.js → page-cf5ab201496f0914.js} +1 -1
  36. package/dist/web/dist/_next/static/chunks/app/admin/data/page-37e01dad70306bf3.js +1 -0
  37. package/dist/web/dist/_next/static/chunks/app/auth/verify/page-38fa28a48f76defa.js +1 -0
  38. package/dist/web/dist/_next/static/chunks/app/companies/page-985002eea5b295d0.js +1 -0
  39. package/dist/web/dist/_next/static/chunks/app/company/admin/page-8c7ecc92bef09e92.js +1 -0
  40. package/dist/web/dist/_next/static/chunks/app/entity/{page-250f8a9b6d91ab19.js → page-dc9664fb628e2d08.js} +1 -1
  41. package/dist/web/dist/_next/static/chunks/app/jobs/page-e39b3e25f0a81254.js +1 -0
  42. package/dist/web/dist/_next/static/chunks/app/layout-d094dc654772bf32.js +1 -0
  43. package/dist/web/dist/_next/static/chunks/app/logs/{page-d478031d49f7ba00.js → page-ccc6bb863cf77e56.js} +1 -1
  44. package/dist/web/dist/_next/static/chunks/app/matching/jobs/[id]/talents/page-936f6190c968d20b.js +1 -0
  45. package/dist/web/dist/_next/static/chunks/app/matching/portraits/[id]/jobs/page-ea44a2236bd53cae.js +1 -0
  46. package/dist/web/dist/_next/static/chunks/app/page-41d8e008a9411221.js +1 -0
  47. package/dist/web/dist/_next/static/chunks/app/parselog/page-3f756bbd580f337e.js +1 -0
  48. package/dist/web/dist/_next/static/chunks/app/recordings/page-ffd507b12e67f2db.js +1 -0
  49. package/dist/web/dist/_next/static/chunks/app/resource-usage/page-3b8bd27536a7889a.js +1 -0
  50. package/dist/web/dist/_next/static/chunks/app/snapshots/[id]/agent-prompt/page-c89b32aa2ef509f5.js +1 -0
  51. package/dist/web/dist/_next/static/chunks/app/talents/page-8e0b4072df138fa3.js +1 -0
  52. package/dist/web/dist/_next/static/chunks/{main-app-74b80a6877b21dfc.js → main-app-d053d37cc3b76ace.js} +1 -1
  53. package/dist/web/dist/_next/static/chunks/{main-5a4e0cd75dd7f04b.js → main-ead3ac50aa4bc931.js} +1 -1
  54. package/dist/web/dist/_next/static/chunks/pages/{_app-638dc63058a49a9c.js → _app-e11d02a41d9f3cb9.js} +2 -2
  55. package/dist/web/dist/_next/static/chunks/webpack-cf21e951181fd345.js +1 -0
  56. package/dist/web/dist/_next/static/css/1dd88e02ef1af7b9.css +3 -0
  57. package/dist/web/dist/admin/data.html +1 -0
  58. package/dist/web/dist/admin/data.txt +7 -0
  59. package/dist/web/dist/auth/verify.html +1 -1
  60. package/dist/web/dist/auth/verify.txt +3 -3
  61. package/dist/web/dist/companies.html +1 -1
  62. package/dist/web/dist/companies.txt +3 -3
  63. package/dist/web/dist/company/admin.html +1 -0
  64. package/dist/web/dist/company/admin.txt +7 -0
  65. package/dist/web/dist/entity.html +1 -1
  66. package/dist/web/dist/entity.txt +3 -3
  67. package/dist/web/dist/index.html +1 -1
  68. package/dist/web/dist/index.txt +3 -3
  69. package/dist/web/dist/jobs.html +1 -1
  70. package/dist/web/dist/jobs.txt +7 -6
  71. package/dist/web/dist/logs.html +1 -1
  72. package/dist/web/dist/logs.txt +3 -3
  73. package/dist/web/dist/matching/jobs/placeholder/talents.html +1 -1
  74. package/dist/web/dist/matching/jobs/placeholder/talents.txt +3 -3
  75. package/dist/web/dist/matching/portraits/placeholder/jobs.html +1 -1
  76. package/dist/web/dist/matching/portraits/placeholder/jobs.txt +3 -3
  77. package/dist/web/dist/parselog.html +1 -1
  78. package/dist/web/dist/parselog.txt +3 -3
  79. package/dist/web/dist/recordings.html +1 -1
  80. package/dist/web/dist/recordings.txt +3 -3
  81. package/dist/web/dist/resource-usage.html +1 -0
  82. package/dist/web/dist/resource-usage.txt +8 -0
  83. package/dist/web/dist/snapshots/placeholder/agent-prompt.html +1 -1
  84. package/dist/web/dist/snapshots/placeholder/agent-prompt.txt +3 -3
  85. package/dist/web/dist/talents.html +1 -1
  86. package/dist/web/dist/talents.txt +3 -3
  87. package/package.json +18 -15
  88. package/dist/assets/server/agent-session/agent-session.controller.d.ts +0 -32
  89. package/dist/assets/server/agent-session/agent-session.controller.js +0 -83
  90. package/dist/assets/server/agent-session/agent-session.controller.js.map +0 -1
  91. package/dist/assets/server/agent-session/agent-session.guard.d.ts +0 -4
  92. package/dist/assets/server/agent-session/agent-session.guard.js +0 -32
  93. package/dist/assets/server/agent-session/agent-session.guard.js.map +0 -1
  94. package/dist/assets/server/agent-session/agent-session.module.d.ts +0 -2
  95. package/dist/assets/server/agent-session/agent-session.module.js +0 -28
  96. package/dist/assets/server/agent-session/agent-session.module.js.map +0 -1
  97. package/dist/assets/server/agent-session/agent-session.service.d.ts +0 -54
  98. package/dist/assets/server/agent-session/agent-session.service.js +0 -431
  99. package/dist/assets/server/agent-session/agent-session.service.js.map +0 -1
  100. package/dist/assets/server/agent-session/agent-session.types.d.ts +0 -56
  101. package/dist/assets/server/agent-session/agent-session.types.js +0 -3
  102. package/dist/assets/server/agent-session/agent-session.types.js.map +0 -1
  103. package/dist/assets/server/agent-session/entities/agent-event.entity.d.ts +0 -12
  104. package/dist/assets/server/agent-session/entities/agent-event.entity.js +0 -73
  105. package/dist/assets/server/agent-session/entities/agent-event.entity.js.map +0 -1
  106. package/dist/assets/server/agent-session/entities/agent-message.entity.d.ts +0 -13
  107. package/dist/assets/server/agent-session/entities/agent-message.entity.js +0 -77
  108. package/dist/assets/server/agent-session/entities/agent-message.entity.js.map +0 -1
  109. package/dist/assets/server/agent-session/entities/agent-session.entity.d.ts +0 -14
  110. package/dist/assets/server/agent-session/entities/agent-session.entity.js +0 -83
  111. package/dist/assets/server/agent-session/entities/agent-session.entity.js.map +0 -1
  112. package/dist/assets/server/app.module.d.ts +0 -4
  113. package/dist/assets/server/app.module.js +0 -141
  114. package/dist/assets/server/app.module.js.map +0 -1
  115. package/dist/assets/server/auth/auth.controller.d.ts +0 -43
  116. package/dist/assets/server/auth/auth.controller.js +0 -127
  117. package/dist/assets/server/auth/auth.controller.js.map +0 -1
  118. package/dist/assets/server/auth/auth.middleware.d.ts +0 -5
  119. package/dist/assets/server/auth/auth.middleware.js +0 -42
  120. package/dist/assets/server/auth/auth.middleware.js.map +0 -1
  121. package/dist/assets/server/auth/auth.module.d.ts +0 -2
  122. package/dist/assets/server/auth/auth.module.js +0 -27
  123. package/dist/assets/server/auth/auth.module.js.map +0 -1
  124. package/dist/assets/server/auth/auth.service.d.ts +0 -58
  125. package/dist/assets/server/auth/auth.service.js +0 -303
  126. package/dist/assets/server/auth/auth.service.js.map +0 -1
  127. package/dist/assets/server/auth/auth.types.d.ts +0 -28
  128. package/dist/assets/server/auth/auth.types.js +0 -3
  129. package/dist/assets/server/auth/auth.types.js.map +0 -1
  130. package/dist/assets/server/auth/saas-token.entity.d.ts +0 -7
  131. package/dist/assets/server/auth/saas-token.entity.js +0 -46
  132. package/dist/assets/server/auth/saas-token.entity.js.map +0 -1
  133. package/dist/assets/server/build-info.json +0 -4
  134. package/dist/assets/server/common/common.module.d.ts +0 -2
  135. package/dist/assets/server/common/common.module.js +0 -22
  136. package/dist/assets/server/common/common.module.js.map +0 -1
  137. package/dist/assets/server/common/crypto.service.d.ts +0 -11
  138. package/dist/assets/server/common/crypto.service.js +0 -73
  139. package/dist/assets/server/common/crypto.service.js.map +0 -1
  140. package/dist/assets/server/communication/communication-record.entity.d.ts +0 -15
  141. package/dist/assets/server/communication/communication-record.entity.js +0 -80
  142. package/dist/assets/server/communication/communication-record.entity.js.map +0 -1
  143. package/dist/assets/server/communication/communication.controller.d.ts +0 -12
  144. package/dist/assets/server/communication/communication.controller.js +0 -61
  145. package/dist/assets/server/communication/communication.controller.js.map +0 -1
  146. package/dist/assets/server/communication/communication.module.d.ts +0 -2
  147. package/dist/assets/server/communication/communication.module.js +0 -25
  148. package/dist/assets/server/communication/communication.module.js.map +0 -1
  149. package/dist/assets/server/communication/communication.service.d.ts +0 -23
  150. package/dist/assets/server/communication/communication.service.js +0 -51
  151. package/dist/assets/server/communication/communication.service.js.map +0 -1
  152. package/dist/assets/server/customer/customer.controller.d.ts +0 -18
  153. package/dist/assets/server/customer/customer.controller.js +0 -81
  154. package/dist/assets/server/customer/customer.controller.js.map +0 -1
  155. package/dist/assets/server/customer/customer.entity.d.ts +0 -17
  156. package/dist/assets/server/customer/customer.entity.js +0 -82
  157. package/dist/assets/server/customer/customer.entity.js.map +0 -1
  158. package/dist/assets/server/customer/customer.module.d.ts +0 -2
  159. package/dist/assets/server/customer/customer.module.js +0 -28
  160. package/dist/assets/server/customer/customer.module.js.map +0 -1
  161. package/dist/assets/server/customer/customer.service.d.ts +0 -36
  162. package/dist/assets/server/customer/customer.service.js +0 -200
  163. package/dist/assets/server/customer/customer.service.js.map +0 -1
  164. package/dist/assets/server/datasync/datasync.module.d.ts +0 -2
  165. package/dist/assets/server/datasync/datasync.module.js +0 -26
  166. package/dist/assets/server/datasync/datasync.module.js.map +0 -1
  167. package/dist/assets/server/datasync/datasync.service.d.ts +0 -50
  168. package/dist/assets/server/datasync/datasync.service.js +0 -389
  169. package/dist/assets/server/datasync/datasync.service.js.map +0 -1
  170. package/dist/assets/server/datasync/entities/sync-conflict.entity.d.ts +0 -10
  171. package/dist/assets/server/datasync/entities/sync-conflict.entity.js +0 -61
  172. package/dist/assets/server/datasync/entities/sync-conflict.entity.js.map +0 -1
  173. package/dist/assets/server/datasync/entities/sync-cursor.entity.d.ts +0 -9
  174. package/dist/assets/server/datasync/entities/sync-cursor.entity.js +0 -56
  175. package/dist/assets/server/datasync/entities/sync-cursor.entity.js.map +0 -1
  176. package/dist/assets/server/datasync/registry.d.ts +0 -8
  177. package/dist/assets/server/datasync/registry.js +0 -55
  178. package/dist/assets/server/datasync/registry.js.map +0 -1
  179. package/dist/assets/server/db/schema-validator.service.d.ts +0 -27
  180. package/dist/assets/server/db/schema-validator.service.js +0 -138
  181. package/dist/assets/server/db/schema-validator.service.js.map +0 -1
  182. package/dist/assets/server/db/schema-version.entity.d.ts +0 -5
  183. package/dist/assets/server/db/schema-version.entity.js +0 -35
  184. package/dist/assets/server/db/schema-version.entity.js.map +0 -1
  185. package/dist/assets/server/db/sqlite.module.d.ts +0 -3
  186. package/dist/assets/server/db/sqlite.module.js +0 -99
  187. package/dist/assets/server/db/sqlite.module.js.map +0 -1
  188. package/dist/assets/server/devops/devops-sink-init.d.ts +0 -10
  189. package/dist/assets/server/devops/devops-sink-init.js +0 -96
  190. package/dist/assets/server/devops/devops-sink-init.js.map +0 -1
  191. package/dist/assets/server/devops/devops-sink-init.spec.d.ts +0 -1
  192. package/dist/assets/server/devops/devops-sink-init.spec.js +0 -71
  193. package/dist/assets/server/devops/devops-sink-init.spec.js.map +0 -1
  194. package/dist/assets/server/devops/devops-sink.module.d.ts +0 -2
  195. package/dist/assets/server/devops/devops-sink.module.js +0 -22
  196. package/dist/assets/server/devops/devops-sink.module.js.map +0 -1
  197. package/dist/assets/server/doctor/doctor.controller.d.ts +0 -13
  198. package/dist/assets/server/doctor/doctor.controller.js +0 -62
  199. package/dist/assets/server/doctor/doctor.controller.js.map +0 -1
  200. package/dist/assets/server/doctor/doctor.module.d.ts +0 -2
  201. package/dist/assets/server/doctor/doctor.module.js +0 -27
  202. package/dist/assets/server/doctor/doctor.module.js.map +0 -1
  203. package/dist/assets/server/doctor/doctor.service.d.ts +0 -15
  204. package/dist/assets/server/doctor/doctor.service.js +0 -209
  205. package/dist/assets/server/doctor/doctor.service.js.map +0 -1
  206. package/dist/assets/server/doctor/doctor.types.d.ts +0 -46
  207. package/dist/assets/server/doctor/doctor.types.js +0 -3
  208. package/dist/assets/server/doctor/doctor.types.js.map +0 -1
  209. package/dist/assets/server/extension/extension-control.controller.d.ts +0 -86
  210. package/dist/assets/server/extension/extension-control.controller.js +0 -229
  211. package/dist/assets/server/extension/extension-control.controller.js.map +0 -1
  212. package/dist/assets/server/extension/extension-control.service.d.ts +0 -59
  213. package/dist/assets/server/extension/extension-control.service.js +0 -275
  214. package/dist/assets/server/extension/extension-control.service.js.map +0 -1
  215. package/dist/assets/server/extension/extension-core.module.d.ts +0 -2
  216. package/dist/assets/server/extension/extension-core.module.js +0 -21
  217. package/dist/assets/server/extension/extension-core.module.js.map +0 -1
  218. package/dist/assets/server/extension/extension-version.controller.d.ts +0 -6
  219. package/dist/assets/server/extension/extension-version.controller.js +0 -41
  220. package/dist/assets/server/extension/extension-version.controller.js.map +0 -1
  221. package/dist/assets/server/extension/extension.module.d.ts +0 -2
  222. package/dist/assets/server/extension/extension.module.js +0 -26
  223. package/dist/assets/server/extension/extension.module.js.map +0 -1
  224. package/dist/assets/server/extraction/adapter.service.d.ts +0 -58
  225. package/dist/assets/server/extraction/adapter.service.js +0 -396
  226. package/dist/assets/server/extraction/adapter.service.js.map +0 -1
  227. package/dist/assets/server/extraction/anomaly-report.service.d.ts +0 -12
  228. package/dist/assets/server/extraction/anomaly-report.service.js +0 -81
  229. package/dist/assets/server/extraction/anomaly-report.service.js.map +0 -1
  230. package/dist/assets/server/extraction/entities/adapter-rule.entity.d.ts +0 -15
  231. package/dist/assets/server/extraction/entities/adapter-rule.entity.js +0 -81
  232. package/dist/assets/server/extraction/entities/adapter-rule.entity.js.map +0 -1
  233. package/dist/assets/server/extraction/entities/company-profile.entity.d.ts +0 -14
  234. package/dist/assets/server/extraction/entities/company-profile.entity.js +0 -81
  235. package/dist/assets/server/extraction/entities/company-profile.entity.js.map +0 -1
  236. package/dist/assets/server/extraction/entities/job-posting.entity.d.ts +0 -15
  237. package/dist/assets/server/extraction/entities/job-posting.entity.js +0 -86
  238. package/dist/assets/server/extraction/entities/job-posting.entity.js.map +0 -1
  239. package/dist/assets/server/extraction/entities/parse-log.entity.d.ts +0 -18
  240. package/dist/assets/server/extraction/entities/parse-log.entity.js +0 -102
  241. package/dist/assets/server/extraction/entities/parse-log.entity.js.map +0 -1
  242. package/dist/assets/server/extraction/entities/talent-profile.entity.d.ts +0 -15
  243. package/dist/assets/server/extraction/entities/talent-profile.entity.js +0 -86
  244. package/dist/assets/server/extraction/entities/talent-profile.entity.js.map +0 -1
  245. package/dist/assets/server/extraction/extraction-entity.controller.d.ts +0 -156
  246. package/dist/assets/server/extraction/extraction-entity.controller.js +0 -528
  247. package/dist/assets/server/extraction/extraction-entity.controller.js.map +0 -1
  248. package/dist/assets/server/extraction/extraction-rule.controller.d.ts +0 -7
  249. package/dist/assets/server/extraction/extraction-rule.controller.js +0 -48
  250. package/dist/assets/server/extraction/extraction-rule.controller.js.map +0 -1
  251. package/dist/assets/server/extraction/extraction-rule.entity.d.ts +0 -15
  252. package/dist/assets/server/extraction/extraction-rule.entity.js +0 -80
  253. package/dist/assets/server/extraction/extraction-rule.entity.js.map +0 -1
  254. package/dist/assets/server/extraction/extraction-rule.service.d.ts +0 -24
  255. package/dist/assets/server/extraction/extraction-rule.service.js +0 -89
  256. package/dist/assets/server/extraction/extraction-rule.service.js.map +0 -1
  257. package/dist/assets/server/extraction/extraction.module.d.ts +0 -2
  258. package/dist/assets/server/extraction/extraction.module.js +0 -63
  259. package/dist/assets/server/extraction/extraction.module.js.map +0 -1
  260. package/dist/assets/server/extraction/extraction.service.d.ts +0 -81
  261. package/dist/assets/server/extraction/extraction.service.js +0 -488
  262. package/dist/assets/server/extraction/extraction.service.js.map +0 -1
  263. package/dist/assets/server/extraction/harness-overlay.service.d.ts +0 -41
  264. package/dist/assets/server/extraction/harness-overlay.service.js +0 -319
  265. package/dist/assets/server/extraction/harness-overlay.service.js.map +0 -1
  266. package/dist/assets/server/extraction/liepin-extractor.d.ts +0 -1
  267. package/dist/assets/server/extraction/liepin-extractor.js +0 -172
  268. package/dist/assets/server/extraction/liepin-extractor.js.map +0 -1
  269. package/dist/assets/server/extraction/linkedin-extractor.d.ts +0 -1
  270. package/dist/assets/server/extraction/linkedin-extractor.js +0 -244
  271. package/dist/assets/server/extraction/linkedin-extractor.js.map +0 -1
  272. package/dist/assets/server/extraction/rrweb-snapshot.utils.d.ts +0 -15
  273. package/dist/assets/server/extraction/rrweb-snapshot.utils.js +0 -48
  274. package/dist/assets/server/extraction/rrweb-snapshot.utils.js.map +0 -1
  275. package/dist/assets/server/extraction/rrweb-snapshot.utils.spec.d.ts +0 -1
  276. package/dist/assets/server/extraction/rrweb-snapshot.utils.spec.js +0 -60
  277. package/dist/assets/server/extraction/rrweb-snapshot.utils.spec.js.map +0 -1
  278. package/dist/assets/server/extraction/script-rule.d.ts +0 -14
  279. package/dist/assets/server/extraction/script-rule.js +0 -61
  280. package/dist/assets/server/extraction/script-rule.js.map +0 -1
  281. package/dist/assets/server/extraction/snapshot-analysis.service.d.ts +0 -23
  282. package/dist/assets/server/extraction/snapshot-analysis.service.js +0 -147
  283. package/dist/assets/server/extraction/snapshot-analysis.service.js.map +0 -1
  284. package/dist/assets/server/extraction/structured-data.d.ts +0 -7
  285. package/dist/assets/server/extraction/structured-data.js +0 -96
  286. package/dist/assets/server/extraction/structured-data.js.map +0 -1
  287. package/dist/assets/server/governance/action-audit.entity.d.ts +0 -12
  288. package/dist/assets/server/governance/action-audit.entity.js +0 -66
  289. package/dist/assets/server/governance/action-audit.entity.js.map +0 -1
  290. package/dist/assets/server/governance/governance.command-recorder.d.ts +0 -17
  291. package/dist/assets/server/governance/governance.command-recorder.js +0 -98
  292. package/dist/assets/server/governance/governance.command-recorder.js.map +0 -1
  293. package/dist/assets/server/governance/governance.controller.d.ts +0 -14
  294. package/dist/assets/server/governance/governance.controller.js +0 -77
  295. package/dist/assets/server/governance/governance.controller.js.map +0 -1
  296. package/dist/assets/server/governance/governance.ingester.d.ts +0 -21
  297. package/dist/assets/server/governance/governance.ingester.js +0 -117
  298. package/dist/assets/server/governance/governance.ingester.js.map +0 -1
  299. package/dist/assets/server/governance/governance.module.d.ts +0 -2
  300. package/dist/assets/server/governance/governance.module.js +0 -27
  301. package/dist/assets/server/governance/governance.module.js.map +0 -1
  302. package/dist/assets/server/governance/governance.service.d.ts +0 -122
  303. package/dist/assets/server/governance/governance.service.js +0 -398
  304. package/dist/assets/server/governance/governance.service.js.map +0 -1
  305. package/dist/assets/server/hcapi/hcapi.module.d.ts +0 -2
  306. package/dist/assets/server/hcapi/hcapi.module.js +0 -23
  307. package/dist/assets/server/hcapi/hcapi.module.js.map +0 -1
  308. package/dist/assets/server/hcapi/hcapi.service.d.ts +0 -28
  309. package/dist/assets/server/hcapi/hcapi.service.js +0 -81
  310. package/dist/assets/server/hcapi/hcapi.service.js.map +0 -1
  311. package/dist/assets/server/health/health.controller.d.ts +0 -19
  312. package/dist/assets/server/health/health.controller.js +0 -59
  313. package/dist/assets/server/health/health.controller.js.map +0 -1
  314. package/dist/assets/server/jobcase/jobcase.controller.d.ts +0 -13
  315. package/dist/assets/server/jobcase/jobcase.controller.js +0 -61
  316. package/dist/assets/server/jobcase/jobcase.controller.js.map +0 -1
  317. package/dist/assets/server/jobcase/jobcase.entity.d.ts +0 -14
  318. package/dist/assets/server/jobcase/jobcase.entity.js +0 -77
  319. package/dist/assets/server/jobcase/jobcase.entity.js.map +0 -1
  320. package/dist/assets/server/jobcase/jobcase.module.d.ts +0 -2
  321. package/dist/assets/server/jobcase/jobcase.module.js +0 -26
  322. package/dist/assets/server/jobcase/jobcase.module.js.map +0 -1
  323. package/dist/assets/server/jobcase/jobcase.service.d.ts +0 -23
  324. package/dist/assets/server/jobcase/jobcase.service.js +0 -57
  325. package/dist/assets/server/jobcase/jobcase.service.js.map +0 -1
  326. package/dist/assets/server/llm/llm.module.d.ts +0 -2
  327. package/dist/assets/server/llm/llm.module.js +0 -21
  328. package/dist/assets/server/llm/llm.module.js.map +0 -1
  329. package/dist/assets/server/llm/llm.service.d.ts +0 -18
  330. package/dist/assets/server/llm/llm.service.js +0 -65
  331. package/dist/assets/server/llm/llm.service.js.map +0 -1
  332. package/dist/assets/server/main.d.ts +0 -1
  333. package/dist/assets/server/main.js +0 -31
  334. package/dist/assets/server/main.js.map +0 -1
  335. package/dist/assets/server/matching/matching.controller.d.ts +0 -16
  336. package/dist/assets/server/matching/matching.controller.js +0 -97
  337. package/dist/assets/server/matching/matching.controller.js.map +0 -1
  338. package/dist/assets/server/matching/matching.module.d.ts +0 -2
  339. package/dist/assets/server/matching/matching.module.js +0 -36
  340. package/dist/assets/server/matching/matching.module.js.map +0 -1
  341. package/dist/assets/server/matching/matching.service.d.ts +0 -55
  342. package/dist/assets/server/matching/matching.service.js +0 -409
  343. package/dist/assets/server/matching/matching.service.js.map +0 -1
  344. package/dist/assets/server/package.json +0 -59
  345. package/dist/assets/server/parsed-resume/parsed-resume.controller.d.ts +0 -6
  346. package/dist/assets/server/parsed-resume/parsed-resume.controller.js +0 -42
  347. package/dist/assets/server/parsed-resume/parsed-resume.controller.js.map +0 -1
  348. package/dist/assets/server/parsed-resume/parsed-resume.entity.d.ts +0 -124
  349. package/dist/assets/server/parsed-resume/parsed-resume.entity.js +0 -72
  350. package/dist/assets/server/parsed-resume/parsed-resume.entity.js.map +0 -1
  351. package/dist/assets/server/parsed-resume/parsed-resume.module.d.ts +0 -2
  352. package/dist/assets/server/parsed-resume/parsed-resume.module.js +0 -26
  353. package/dist/assets/server/parsed-resume/parsed-resume.module.js.map +0 -1
  354. package/dist/assets/server/parsed-resume/parsed-resume.service.d.ts +0 -10
  355. package/dist/assets/server/parsed-resume/parsed-resume.service.js +0 -45
  356. package/dist/assets/server/parsed-resume/parsed-resume.service.js.map +0 -1
  357. package/dist/assets/server/parsing/parse-error-report.entity.d.ts +0 -11
  358. package/dist/assets/server/parsing/parse-error-report.entity.js +0 -66
  359. package/dist/assets/server/parsing/parse-error-report.entity.js.map +0 -1
  360. package/dist/assets/server/parsing/parsing.controller.d.ts +0 -19
  361. package/dist/assets/server/parsing/parsing.controller.js +0 -97
  362. package/dist/assets/server/parsing/parsing.controller.js.map +0 -1
  363. package/dist/assets/server/parsing/parsing.module.d.ts +0 -2
  364. package/dist/assets/server/parsing/parsing.module.js +0 -38
  365. package/dist/assets/server/parsing/parsing.module.js.map +0 -1
  366. package/dist/assets/server/parsing/parsing.service.d.ts +0 -38
  367. package/dist/assets/server/parsing/parsing.service.js +0 -263
  368. package/dist/assets/server/parsing/parsing.service.js.map +0 -1
  369. package/dist/assets/server/preference/preference.controller.d.ts +0 -14
  370. package/dist/assets/server/preference/preference.controller.js +0 -66
  371. package/dist/assets/server/preference/preference.controller.js.map +0 -1
  372. package/dist/assets/server/preference/preference.entity.d.ts +0 -13
  373. package/dist/assets/server/preference/preference.entity.js +0 -77
  374. package/dist/assets/server/preference/preference.entity.js.map +0 -1
  375. package/dist/assets/server/preference/preference.module.d.ts +0 -2
  376. package/dist/assets/server/preference/preference.module.js +0 -26
  377. package/dist/assets/server/preference/preference.module.js.map +0 -1
  378. package/dist/assets/server/preference/preference.service.d.ts +0 -33
  379. package/dist/assets/server/preference/preference.service.js +0 -122
  380. package/dist/assets/server/preference/preference.service.js.map +0 -1
  381. package/dist/assets/server/query/fields-meta.controller.d.ts +0 -28
  382. package/dist/assets/server/query/fields-meta.controller.js +0 -57
  383. package/dist/assets/server/query/fields-meta.controller.js.map +0 -1
  384. package/dist/assets/server/query/query-builder.controller.d.ts +0 -11
  385. package/dist/assets/server/query/query-builder.controller.js +0 -40
  386. package/dist/assets/server/query/query-builder.controller.js.map +0 -1
  387. package/dist/assets/server/query/query-builder.service.d.ts +0 -8
  388. package/dist/assets/server/query/query-builder.service.js +0 -41
  389. package/dist/assets/server/query/query-builder.service.js.map +0 -1
  390. package/dist/assets/server/query/query.module.d.ts +0 -2
  391. package/dist/assets/server/query/query.module.js +0 -23
  392. package/dist/assets/server/query/query.module.js.map +0 -1
  393. package/dist/assets/server/recording/entities/recording-event.entity.d.ts +0 -9
  394. package/dist/assets/server/recording/entities/recording-event.entity.js +0 -56
  395. package/dist/assets/server/recording/entities/recording-event.entity.js.map +0 -1
  396. package/dist/assets/server/recording/entities/recording-snapshot.entity.d.ts +0 -16
  397. package/dist/assets/server/recording/entities/recording-snapshot.entity.js +0 -91
  398. package/dist/assets/server/recording/entities/recording-snapshot.entity.js.map +0 -1
  399. package/dist/assets/server/recording/recording-event-bus.service.d.ts +0 -4
  400. package/dist/assets/server/recording/recording-event-bus.service.js +0 -25
  401. package/dist/assets/server/recording/recording-event-bus.service.js.map +0 -1
  402. package/dist/assets/server/recording/recording-event-queue.service.d.ts +0 -26
  403. package/dist/assets/server/recording/recording-event-queue.service.js +0 -115
  404. package/dist/assets/server/recording/recording-event-queue.service.js.map +0 -1
  405. package/dist/assets/server/recording/recording-retention.service.d.ts +0 -38
  406. package/dist/assets/server/recording/recording-retention.service.js +0 -174
  407. package/dist/assets/server/recording/recording-retention.service.js.map +0 -1
  408. package/dist/assets/server/recording/recording-snapshot.service.d.ts +0 -14
  409. package/dist/assets/server/recording/recording-snapshot.service.js +0 -85
  410. package/dist/assets/server/recording/recording-snapshot.service.js.map +0 -1
  411. package/dist/assets/server/recording/recording-snapshot.service.spec.d.ts +0 -1
  412. package/dist/assets/server/recording/recording-snapshot.service.spec.js +0 -65
  413. package/dist/assets/server/recording/recording-snapshot.service.spec.js.map +0 -1
  414. package/dist/assets/server/recording/recording.controller.d.ts +0 -74
  415. package/dist/assets/server/recording/recording.controller.js +0 -112
  416. package/dist/assets/server/recording/recording.controller.js.map +0 -1
  417. package/dist/assets/server/recording/recording.gateway.d.ts +0 -15
  418. package/dist/assets/server/recording/recording.gateway.js +0 -132
  419. package/dist/assets/server/recording/recording.gateway.js.map +0 -1
  420. package/dist/assets/server/recording/recording.module.d.ts +0 -2
  421. package/dist/assets/server/recording/recording.module.js +0 -50
  422. package/dist/assets/server/recording/recording.module.js.map +0 -1
  423. package/dist/assets/server/recording/recording.service.d.ts +0 -52
  424. package/dist/assets/server/recording/recording.service.js +0 -149
  425. package/dist/assets/server/recording/recording.service.js.map +0 -1
  426. package/dist/assets/server/remark/remark.controller.d.ts +0 -21
  427. package/dist/assets/server/remark/remark.controller.js +0 -50
  428. package/dist/assets/server/remark/remark.controller.js.map +0 -1
  429. package/dist/assets/server/remark/remark.entity.d.ts +0 -18
  430. package/dist/assets/server/remark/remark.entity.js +0 -67
  431. package/dist/assets/server/remark/remark.entity.js.map +0 -1
  432. package/dist/assets/server/remark/remark.module.d.ts +0 -2
  433. package/dist/assets/server/remark/remark.module.js +0 -26
  434. package/dist/assets/server/remark/remark.module.js.map +0 -1
  435. package/dist/assets/server/remark/remark.service.d.ts +0 -8
  436. package/dist/assets/server/remark/remark.service.js +0 -39
  437. package/dist/assets/server/remark/remark.service.js.map +0 -1
  438. package/dist/assets/server/report/report.controller.d.ts +0 -18
  439. package/dist/assets/server/report/report.controller.js +0 -41
  440. package/dist/assets/server/report/report.controller.js.map +0 -1
  441. package/dist/assets/server/report/report.module.d.ts +0 -2
  442. package/dist/assets/server/report/report.module.js +0 -24
  443. package/dist/assets/server/report/report.module.js.map +0 -1
  444. package/dist/assets/server/report/report.service.d.ts +0 -23
  445. package/dist/assets/server/report/report.service.js +0 -54
  446. package/dist/assets/server/report/report.service.js.map +0 -1
  447. package/dist/assets/server/rules/rules.controller.d.ts +0 -6
  448. package/dist/assets/server/rules/rules.controller.js +0 -35
  449. package/dist/assets/server/rules/rules.controller.js.map +0 -1
  450. package/dist/assets/server/rules/rules.module.d.ts +0 -2
  451. package/dist/assets/server/rules/rules.module.js +0 -23
  452. package/dist/assets/server/rules/rules.module.js.map +0 -1
  453. package/dist/assets/server/rules/rules.service.d.ts +0 -10
  454. package/dist/assets/server/rules/rules.service.js +0 -26
  455. package/dist/assets/server/rules/rules.service.js.map +0 -1
  456. package/dist/assets/server/search/search.controller.d.ts +0 -6
  457. package/dist/assets/server/search/search.controller.js +0 -53
  458. package/dist/assets/server/search/search.controller.js.map +0 -1
  459. package/dist/assets/server/search/search.module.d.ts +0 -2
  460. package/dist/assets/server/search/search.module.js +0 -24
  461. package/dist/assets/server/search/search.module.js.map +0 -1
  462. package/dist/assets/server/search/search.service.d.ts +0 -6
  463. package/dist/assets/server/search/search.service.js +0 -46
  464. package/dist/assets/server/search/search.service.js.map +0 -1
  465. package/dist/assets/server/sentry.d.ts +0 -3
  466. package/dist/assets/server/sentry.js +0 -10
  467. package/dist/assets/server/sentry.js.map +0 -1
  468. package/dist/assets/server/site/entities/site-registry.entity.d.ts +0 -13
  469. package/dist/assets/server/site/entities/site-registry.entity.js +0 -76
  470. package/dist/assets/server/site/entities/site-registry.entity.js.map +0 -1
  471. package/dist/assets/server/site/site.controller.d.ts +0 -38
  472. package/dist/assets/server/site/site.controller.js +0 -60
  473. package/dist/assets/server/site/site.controller.js.map +0 -1
  474. package/dist/assets/server/site/site.module.d.ts +0 -2
  475. package/dist/assets/server/site/site.module.js +0 -26
  476. package/dist/assets/server/site/site.module.js.map +0 -1
  477. package/dist/assets/server/site/site.service.d.ts +0 -42
  478. package/dist/assets/server/site/site.service.js +0 -154
  479. package/dist/assets/server/site/site.service.js.map +0 -1
  480. package/dist/assets/server/snapshot/raw-snapshot.entity.d.ts +0 -35
  481. package/dist/assets/server/snapshot/raw-snapshot.entity.js +0 -62
  482. package/dist/assets/server/snapshot/raw-snapshot.entity.js.map +0 -1
  483. package/dist/assets/server/snapshot/snapshot.controller.d.ts +0 -18
  484. package/dist/assets/server/snapshot/snapshot.controller.js +0 -76
  485. package/dist/assets/server/snapshot/snapshot.controller.js.map +0 -1
  486. package/dist/assets/server/snapshot/snapshot.module.d.ts +0 -2
  487. package/dist/assets/server/snapshot/snapshot.module.js +0 -26
  488. package/dist/assets/server/snapshot/snapshot.module.js.map +0 -1
  489. package/dist/assets/server/snapshot/snapshot.service.d.ts +0 -10
  490. package/dist/assets/server/snapshot/snapshot.service.js +0 -48
  491. package/dist/assets/server/snapshot/snapshot.service.js.map +0 -1
  492. package/dist/assets/server/system/system.controller.d.ts +0 -40
  493. package/dist/assets/server/system/system.controller.js +0 -93
  494. package/dist/assets/server/system/system.controller.js.map +0 -1
  495. package/dist/assets/server/system/system.module.d.ts +0 -2
  496. package/dist/assets/server/system/system.module.js +0 -26
  497. package/dist/assets/server/system/system.module.js.map +0 -1
  498. package/dist/assets/server/system/system.service.d.ts +0 -87
  499. package/dist/assets/server/system/system.service.js +0 -434
  500. package/dist/assets/server/system/system.service.js.map +0 -1
  501. package/dist/assets/server/talent-fingerprint/fingerprint-match.service.d.ts +0 -28
  502. package/dist/assets/server/talent-fingerprint/fingerprint-match.service.js +0 -186
  503. package/dist/assets/server/talent-fingerprint/fingerprint-match.service.js.map +0 -1
  504. package/dist/assets/server/talent-fingerprint/talent-fingerprint.entity.d.ts +0 -30
  505. package/dist/assets/server/talent-fingerprint/talent-fingerprint.entity.js +0 -55
  506. package/dist/assets/server/talent-fingerprint/talent-fingerprint.entity.js.map +0 -1
  507. package/dist/assets/server/talent-fingerprint/talent-fingerprint.module.d.ts +0 -2
  508. package/dist/assets/server/talent-fingerprint/talent-fingerprint.module.js +0 -25
  509. package/dist/assets/server/talent-fingerprint/talent-fingerprint.module.js.map +0 -1
  510. package/dist/assets/server/talent-fingerprint/talent-fingerprint.service.d.ts +0 -8
  511. package/dist/assets/server/talent-fingerprint/talent-fingerprint.service.js +0 -44
  512. package/dist/assets/server/talent-fingerprint/talent-fingerprint.service.js.map +0 -1
  513. package/dist/assets/server/talent-job-match/talent-job-match.controller.d.ts +0 -6
  514. package/dist/assets/server/talent-job-match/talent-job-match.controller.js +0 -42
  515. package/dist/assets/server/talent-job-match/talent-job-match.controller.js.map +0 -1
  516. package/dist/assets/server/talent-job-match/talent-job-match.entity.d.ts +0 -33
  517. package/dist/assets/server/talent-job-match/talent-job-match.entity.js +0 -73
  518. package/dist/assets/server/talent-job-match/talent-job-match.entity.js.map +0 -1
  519. package/dist/assets/server/talent-job-match/talent-job-match.module.d.ts +0 -2
  520. package/dist/assets/server/talent-job-match/talent-job-match.module.js +0 -26
  521. package/dist/assets/server/talent-job-match/talent-job-match.module.js.map +0 -1
  522. package/dist/assets/server/talent-job-match/talent-job-match.service.d.ts +0 -9
  523. package/dist/assets/server/talent-job-match/talent-job-match.service.js +0 -42
  524. package/dist/assets/server/talent-job-match/talent-job-match.service.js.map +0 -1
  525. package/dist/assets/server/talent-portrait/portrait-merge.service.d.ts +0 -17
  526. package/dist/assets/server/talent-portrait/portrait-merge.service.js +0 -200
  527. package/dist/assets/server/talent-portrait/portrait-merge.service.js.map +0 -1
  528. package/dist/assets/server/talent-portrait/talent-portrait.controller.d.ts +0 -7
  529. package/dist/assets/server/talent-portrait/talent-portrait.controller.js +0 -52
  530. package/dist/assets/server/talent-portrait/talent-portrait.controller.js.map +0 -1
  531. package/dist/assets/server/talent-portrait/talent-portrait.entity.d.ts +0 -91
  532. package/dist/assets/server/talent-portrait/talent-portrait.entity.js +0 -71
  533. package/dist/assets/server/talent-portrait/talent-portrait.entity.js.map +0 -1
  534. package/dist/assets/server/talent-portrait/talent-portrait.module.d.ts +0 -2
  535. package/dist/assets/server/talent-portrait/talent-portrait.module.js +0 -27
  536. package/dist/assets/server/talent-portrait/talent-portrait.module.js.map +0 -1
  537. package/dist/assets/server/talent-portrait/talent-portrait.service.d.ts +0 -10
  538. package/dist/assets/server/talent-portrait/talent-portrait.service.js +0 -46
  539. package/dist/assets/server/talent-portrait/talent-portrait.service.js.map +0 -1
  540. package/dist/assets/server/telemetry/telemetry.module.d.ts +0 -2
  541. package/dist/assets/server/telemetry/telemetry.module.js +0 -24
  542. package/dist/assets/server/telemetry/telemetry.module.js.map +0 -1
  543. package/dist/assets/server/telemetry/telemetry.service.d.ts +0 -29
  544. package/dist/assets/server/telemetry/telemetry.service.js +0 -116
  545. package/dist/assets/server/telemetry/telemetry.service.js.map +0 -1
  546. package/dist/auto-doctor.d.ts +0 -8
  547. package/dist/auto-doctor.js +0 -34
  548. package/dist/bundled-assets.d.ts +0 -22
  549. package/dist/bundled-assets.js +0 -80
  550. package/dist/cache/db.d.ts +0 -7
  551. package/dist/cache/db.js +0 -61
  552. package/dist/commands/addons.d.ts +0 -4
  553. package/dist/commands/addons.js +0 -354
  554. package/dist/commands/cache.d.ts +0 -8
  555. package/dist/commands/cache.js +0 -59
  556. package/dist/commands/case.d.ts +0 -67
  557. package/dist/commands/case.js +0 -101
  558. package/dist/commands/config.d.ts +0 -17
  559. package/dist/commands/config.js +0 -30
  560. package/dist/commands/customer.d.ts +0 -25
  561. package/dist/commands/customer.js +0 -137
  562. package/dist/commands/doctor.d.ts +0 -63
  563. package/dist/commands/doctor.js +0 -666
  564. package/dist/commands/extension.d.ts +0 -5
  565. package/dist/commands/extension.js +0 -420
  566. package/dist/commands/extras.d.ts +0 -55
  567. package/dist/commands/extras.js +0 -456
  568. package/dist/commands/init.d.ts +0 -10
  569. package/dist/commands/init.js +0 -58
  570. package/dist/commands/install.d.ts +0 -14
  571. package/dist/commands/install.js +0 -77
  572. package/dist/commands/issue-env.d.ts +0 -23
  573. package/dist/commands/issue-env.js +0 -101
  574. package/dist/commands/job.d.ts +0 -13
  575. package/dist/commands/job.js +0 -131
  576. package/dist/commands/login.d.ts +0 -22
  577. package/dist/commands/login.js +0 -192
  578. package/dist/commands/logout.d.ts +0 -3
  579. package/dist/commands/logout.js +0 -12
  580. package/dist/commands/migrate.d.ts +0 -32
  581. package/dist/commands/migrate.js +0 -116
  582. package/dist/commands/note.d.ts +0 -3
  583. package/dist/commands/note.js +0 -75
  584. package/dist/commands/ops.d.ts +0 -6
  585. package/dist/commands/ops.js +0 -461
  586. package/dist/commands/preference.d.ts +0 -6
  587. package/dist/commands/preference.js +0 -80
  588. package/dist/commands/record.d.ts +0 -12
  589. package/dist/commands/record.js +0 -56
  590. package/dist/commands/report.d.ts +0 -7
  591. package/dist/commands/report.js +0 -48
  592. package/dist/commands/search.d.ts +0 -14
  593. package/dist/commands/search.js +0 -46
  594. package/dist/commands/server.d.ts +0 -35
  595. package/dist/commands/server.js +0 -471
  596. package/dist/commands/status.d.ts +0 -24
  597. package/dist/commands/status.js +0 -98
  598. package/dist/commands/update.d.ts +0 -8
  599. package/dist/commands/update.js +0 -125
  600. package/dist/config.d.ts +0 -67
  601. package/dist/config.js +0 -121
  602. package/dist/extension-sync.d.ts +0 -35
  603. package/dist/extension-sync.js +0 -159
  604. package/dist/i18n/index.d.ts +0 -7
  605. package/dist/i18n/index.js +0 -18
  606. package/dist/i18n/locales/en-US.json +0 -79
  607. package/dist/i18n/locales/zh-CN.json +0 -79
  608. package/dist/index.d.ts +0 -2
  609. package/dist/keychain.d.ts +0 -18
  610. package/dist/keychain.js +0 -109
  611. package/dist/logger.d.ts +0 -7
  612. package/dist/logger.js +0 -80
  613. package/dist/native-check.d.ts +0 -6
  614. package/dist/native-check.js +0 -30
  615. package/dist/postinstall.d.ts +0 -1
  616. package/dist/sentry.d.ts +0 -4
  617. package/dist/sentry.js +0 -6
  618. package/dist/updater.d.ts +0 -119
  619. package/dist/updater.js +0 -475
  620. package/dist/utils/artifact-checksum.d.ts +0 -4
  621. package/dist/utils/artifact-checksum.js +0 -40
  622. package/dist/utils/command-error.d.ts +0 -11
  623. package/dist/utils/command-error.js +0 -47
  624. package/dist/utils/link.d.ts +0 -18
  625. package/dist/utils/link.js +0 -83
  626. package/dist/utils/local-http.d.ts +0 -7
  627. package/dist/utils/local-http.js +0 -30
  628. package/dist/utils/proc.d.ts +0 -35
  629. package/dist/utils/proc.js +0 -81
  630. package/dist/utils/proxy-detect.d.ts +0 -20
  631. package/dist/utils/proxy-detect.js +0 -96
  632. package/dist/utils/script-path.d.ts +0 -5
  633. package/dist/utils/script-path.js +0 -27
  634. package/dist/utils/spawn.d.ts +0 -19
  635. package/dist/utils/spawn.js +0 -60
  636. package/dist/version.d.ts +0 -16
  637. package/dist/version.js +0 -388
  638. package/dist/watchdog.d.ts +0 -31
  639. package/dist/web/dist/_next/static/chunks/539-cd9d7c0ee78f8874.js +0 -1
  640. package/dist/web/dist/_next/static/chunks/app/auth/verify/page-f13c66b48f7cf13e.js +0 -1
  641. package/dist/web/dist/_next/static/chunks/app/companies/page-e38a69e1929d0953.js +0 -1
  642. package/dist/web/dist/_next/static/chunks/app/jobs/page-6a281776fec065f6.js +0 -1
  643. package/dist/web/dist/_next/static/chunks/app/layout-83e623e973c1693f.js +0 -1
  644. package/dist/web/dist/_next/static/chunks/app/matching/jobs/[id]/talents/page-cdf1ed0971999f35.js +0 -1
  645. package/dist/web/dist/_next/static/chunks/app/matching/portraits/[id]/jobs/page-87f04f1d61b94cb2.js +0 -1
  646. package/dist/web/dist/_next/static/chunks/app/page-affa1ffde089f1dd.js +0 -1
  647. package/dist/web/dist/_next/static/chunks/app/parselog/page-31d3630d611649f7.js +0 -1
  648. package/dist/web/dist/_next/static/chunks/app/recordings/page-9cdefceb2bff613f.js +0 -1
  649. package/dist/web/dist/_next/static/chunks/app/snapshots/[id]/agent-prompt/page-6996f7f221b1a7eb.js +0 -1
  650. package/dist/web/dist/_next/static/chunks/app/talents/page-30748338da20d7e0.js +0 -1
  651. package/dist/web/dist/_next/static/chunks/webpack-1784baecc6fdcf0b.js +0 -1
  652. package/dist/web/dist/_next/static/css/b7097b50a6497650.css +0 -3
  653. /package/dist/web/dist/_next/static/{hG-sO5oTR0EvAw6sU7EMO → L4ZJv0swR7OTsvB_PaAB4}/_buildManifest.js +0 -0
  654. /package/dist/web/dist/_next/static/{hG-sO5oTR0EvAw6sU7EMO → L4ZJv0swR7OTsvB_PaAB4}/_ssgManifest.js +0 -0
@@ -0,0 +1,4618 @@
1
+ // src/shared/messaging.ts
2
+ var SERVER_PORT = 34571;
3
+ var WS_URL = `ws://localhost:${SERVER_PORT}/ws`;
4
+ var SERVER_HTTP_URL = `http://localhost:${SERVER_PORT}`;
5
+
6
+ // src/shared/control-runtime.ts
7
+ function getRefStore() {
8
+ const g = globalThis;
9
+ if (!g.__cpRefs) {
10
+ g.__cpRefs = { counter: 1, refs: /* @__PURE__ */ new Map(), cssPaths: /* @__PURE__ */ new Map() };
11
+ }
12
+ return g.__cpRefs;
13
+ }
14
+ function allocRef(el) {
15
+ const store = getRefStore();
16
+ for (const [ref2, existing] of store.refs) {
17
+ if (existing === el) return ref2;
18
+ }
19
+ const ref = store.counter++;
20
+ store.refs.set(ref, el);
21
+ store.cssPaths.set(ref, uniqueCssPath(el));
22
+ return ref;
23
+ }
24
+ function uniqueCssPath(el) {
25
+ if (el.id) return `#${CSS.escape(el.id)}`;
26
+ const parts = [];
27
+ let cur = el;
28
+ while (cur && cur !== document.body && cur !== document.documentElement) {
29
+ let sel = cur.tagName.toLowerCase();
30
+ if (cur.id) {
31
+ parts.unshift(`#${CSS.escape(cur.id)}`);
32
+ break;
33
+ }
34
+ const parent = cur.parentElement;
35
+ if (parent) {
36
+ const sameTag = Array.from(parent.children).filter((c) => c.tagName === cur.tagName);
37
+ if (sameTag.length > 1) {
38
+ const idx = sameTag.indexOf(cur) + 1;
39
+ sel += `:nth-of-type(${idx})`;
40
+ }
41
+ }
42
+ parts.unshift(sel);
43
+ cur = parent;
44
+ }
45
+ return parts.join(" > ");
46
+ }
47
+ function err(code, message, extra) {
48
+ return { error: { code, message, ...extra || {} } };
49
+ }
50
+ var ERROR_CODES = {
51
+ NOT_FOUND: "not_found",
52
+ STALE_REF: "stale_ref",
53
+ INVALID_SELECTOR: "invalid_selector",
54
+ SELECTOR_NOT_FOUND: "selector_not_found",
55
+ SELECTOR_AMBIGUOUS: "selector_ambiguous",
56
+ SELECTOR_NTH_OUT_OF_RANGE: "selector_nth_out_of_range",
57
+ OPTION_NOT_FOUND: "option_not_found",
58
+ NOT_A_SELECT: "not_a_select"
59
+ };
60
+ var IMPLICIT_ROLE = {
61
+ a: "link",
62
+ button: "button",
63
+ select: "combobox",
64
+ textarea: "textbox",
65
+ h1: "heading",
66
+ h2: "heading",
67
+ h3: "heading",
68
+ h4: "heading",
69
+ h5: "heading",
70
+ h6: "heading",
71
+ img: "img",
72
+ nav: "navigation",
73
+ ul: "list",
74
+ ol: "list",
75
+ li: "listitem",
76
+ main: "main",
77
+ article: "article",
78
+ aside: "complementary",
79
+ header: "banner",
80
+ footer: "contentinfo",
81
+ form: "form",
82
+ table: "table",
83
+ summary: "button",
84
+ option: "option",
85
+ output: "status"
86
+ };
87
+ function roleOf(el) {
88
+ const explicit = el.getAttribute("role");
89
+ if (explicit) return explicit.split(/\s+/)[0];
90
+ const tag = el.tagName.toLowerCase();
91
+ if (tag === "input") {
92
+ const type = el.type || "text";
93
+ if (type === "hidden") return null;
94
+ if (type === "checkbox") return "checkbox";
95
+ if (type === "radio") return "radio";
96
+ if (type === "button" || type === "submit" || type === "reset") return "button";
97
+ if (type === "range") return "slider";
98
+ return "textbox";
99
+ }
100
+ if (tag === "progress") return "progressbar";
101
+ if (tag === "meter") return "meter";
102
+ return IMPLICIT_ROLE[tag] ?? null;
103
+ }
104
+ function accessibleName(el) {
105
+ const aria = el.getAttribute("aria-label");
106
+ if (aria) return aria.trim();
107
+ const labelledby = el.getAttribute("aria-labelledby");
108
+ if (labelledby) {
109
+ const parts = labelledby.split(/\s+/).map((id2) => document.getElementById(id2)?.textContent?.trim() || "");
110
+ const joined = parts.filter(Boolean).join(" ");
111
+ if (joined) return joined.slice(0, 120);
112
+ }
113
+ const id = el.getAttribute("id");
114
+ if (id) {
115
+ const label = document.querySelector(`label[for="${CSS.escape(id)}"]`);
116
+ if (label?.textContent?.trim()) return label.textContent.trim().slice(0, 120);
117
+ }
118
+ if (el.tagName.toLowerCase() === "input") {
119
+ const type = el.type;
120
+ if (type === "button" || type === "submit" || type === "reset") {
121
+ const v = el.value;
122
+ if (v) return v;
123
+ }
124
+ }
125
+ const alt = el.getAttribute("alt");
126
+ if (alt) return alt;
127
+ const title = el.getAttribute("title");
128
+ if (title) return title;
129
+ const placeholder = el.getAttribute("placeholder");
130
+ if (placeholder) return placeholder;
131
+ return (el.textContent || "").replace(/\s+/g, " ").trim().slice(0, 120);
132
+ }
133
+ function isVisible(el) {
134
+ if (!(el instanceof HTMLElement) && !(el instanceof SVGElement)) return false;
135
+ const html = el;
136
+ if (html.hasAttribute("hidden")) return false;
137
+ if (html.closest("[hidden]")) return false;
138
+ const style = window.getComputedStyle(html);
139
+ if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") return false;
140
+ if (el instanceof HTMLElement && el.offsetParent === null) {
141
+ const pos = style.position;
142
+ if (pos !== "fixed" && pos !== "sticky") return false;
143
+ }
144
+ return true;
145
+ }
146
+ var INTERACTIVE_SELECTOR = "a[href], button, input, textarea, select, summary, [role], [contenteditable=''], [contenteditable='true'], [onclick], [tabindex]";
147
+ function isInteractive(el) {
148
+ return el.matches(INTERACTIVE_SELECTOR);
149
+ }
150
+ function testidSelector(testid) {
151
+ return `[data-testid="${CSS.escape(testid)}"], [data-test-id="${CSS.escape(testid)}"], [data-test="${CSS.escape(testid)}"], [data-cy="${CSS.escape(testid)}"]`;
152
+ }
153
+ function resolveTargetRich(sel) {
154
+ const targetDesc = sel.ref != null ? String(sel.ref) : sel.css ?? (sel.role || sel.name || sel.label || sel.testid || sel.text ? `${sel.role ? `role=${sel.role} ` : ""}${sel.name ? `name=${sel.name} ` : ""}${sel.label ? `label=${sel.label} ` : ""}${sel.testid ? `testid=${sel.testid} ` : ""}${sel.text ? `text=${sel.text}` : ""}`.trim() : "");
155
+ if (sel.ref != null) {
156
+ const store = getRefStore();
157
+ const el = store.refs.get(sel.ref);
158
+ if (el && document.contains(el) && isVisible(el)) {
159
+ return { el, matches_n: 1, match_level: "exact", target: targetDesc };
160
+ }
161
+ if (el && !document.contains(el)) {
162
+ const cssPath = store.cssPaths.get(sel.ref);
163
+ if (cssPath) {
164
+ const re = document.querySelector(cssPath);
165
+ if (re && isVisible(re)) {
166
+ store.refs.set(sel.ref, re);
167
+ return { el: re, matches_n: 1, match_level: "reidentified", target: targetDesc };
168
+ }
169
+ }
170
+ return { errorEnvelope: err(ERROR_CODES.STALE_REF, "Numeric ref is no longer in the DOM. Re-run state.") };
171
+ }
172
+ return { errorEnvelope: err(ERROR_CODES.NOT_FOUND, "Numeric ref is no longer in the DOM. Re-run state.") };
173
+ }
174
+ let candidates = [];
175
+ if (sel.css) {
176
+ try {
177
+ candidates = Array.from(document.querySelectorAll(sel.css));
178
+ } catch {
179
+ return { errorEnvelope: err(ERROR_CODES.INVALID_SELECTOR, "CSS was rejected by querySelectorAll.", { selector: sel.css }) };
180
+ }
181
+ if (candidates.length === 0) {
182
+ return { errorEnvelope: err(ERROR_CODES.SELECTOR_NOT_FOUND, "CSS matches 0 elements.", { selector: sel.css, candidates: suggestCandidates() }) };
183
+ }
184
+ } else if (sel.testid) {
185
+ try {
186
+ candidates = Array.from(document.querySelectorAll(testidSelector(sel.testid)));
187
+ } catch {
188
+ return { errorEnvelope: err(ERROR_CODES.INVALID_SELECTOR, "Invalid testid.", { testid: sel.testid }) };
189
+ }
190
+ } else if (sel.role || sel.name || sel.label || sel.text) {
191
+ const wantRole = sel.role ? sel.role.toLowerCase() : null;
192
+ const wantName = sel.name ? sel.name.toLowerCase() : null;
193
+ const wantText = sel.text ? sel.text.toLowerCase() : null;
194
+ let labelTargets = [];
195
+ if (sel.label) {
196
+ const lower = sel.label.toLowerCase();
197
+ labelTargets = Array.from(document.querySelectorAll("label")).filter((l) => (l.textContent || "").trim().toLowerCase().includes(lower)).map((l) => {
198
+ const forId = l.getAttribute("for");
199
+ if (forId) return document.getElementById(forId);
200
+ return l.querySelector("input, select, textarea");
201
+ }).filter((x) => !!x);
202
+ }
203
+ const all = Array.from(document.querySelectorAll("*"));
204
+ candidates = all.filter((el) => {
205
+ if (!isVisible(el)) return false;
206
+ const role = roleOf(el);
207
+ if (wantRole && (!role || role.toLowerCase() !== wantRole)) return false;
208
+ if (wantName) {
209
+ const name = accessibleName(el).toLowerCase();
210
+ if (!name.includes(wantName)) return false;
211
+ }
212
+ if (wantText) {
213
+ const t = (el.textContent || "").replace(/\s+/g, " ").trim().toLowerCase();
214
+ if (!t.includes(wantText)) return false;
215
+ }
216
+ return true;
217
+ });
218
+ if (labelTargets.length) {
219
+ const set = new Set(labelTargets);
220
+ candidates = labelTargets.concat(candidates.filter((c) => !set.has(c)));
221
+ }
222
+ if (candidates.length === 0) {
223
+ return {
224
+ errorEnvelope: err(
225
+ ERROR_CODES.SELECTOR_NOT_FOUND,
226
+ "Semantic locator matches 0 elements.",
227
+ { role: sel.role, name: sel.name, label: sel.label, text: sel.text, candidates: suggestCandidates() }
228
+ )
229
+ };
230
+ }
231
+ } else {
232
+ return { errorEnvelope: err("no_target", "Provide a numeric ref, CSS selector, or semantic locator (--role/--name/--label/--text/--testid).") };
233
+ }
234
+ const visible = candidates.filter(isVisible);
235
+ const pool = visible.length ? visible : candidates;
236
+ if (sel.nth != null) {
237
+ if (sel.nth < 0 || sel.nth >= pool.length) {
238
+ return { errorEnvelope: err(ERROR_CODES.SELECTOR_NTH_OUT_OF_RANGE, `--nth ${sel.nth} beyond match count ${pool.length}.`) };
239
+ }
240
+ const el = pool[sel.nth];
241
+ return { el, matches_n: pool.length, match_level: "reidentified", target: targetDesc };
242
+ }
243
+ if (pool.length > 1) {
244
+ return {
245
+ errorEnvelope: err(ERROR_CODES.SELECTOR_AMBIGUOUS, `CSS matches ${pool.length} elements and no --nth given.`, {
246
+ matches_n: pool.length,
247
+ candidates: pool.slice(0, 5).map((el) => uniqueCssPath(el))
248
+ })
249
+ };
250
+ }
251
+ return { el: pool[0], matches_n: 1, match_level: "reidentified", target: targetDesc };
252
+ }
253
+ function suggestCandidates() {
254
+ const store = getRefStore();
255
+ const recent = Array.from(store.refs.entries()).slice(-5).map(([, el]) => uniqueCssPath(el));
256
+ return recent;
257
+ }
258
+ function compoundOf(el) {
259
+ const tag = el.tagName.toLowerCase();
260
+ if (tag === "input") {
261
+ const input = el;
262
+ const type = input.type;
263
+ if (["date", "time", "datetime-local", "month", "week"].includes(type)) {
264
+ const formatMap = {
265
+ date: "YYYY-MM-DD",
266
+ time: "HH:mm",
267
+ "datetime-local": "YYYY-MM-DDTHH:mm",
268
+ month: "YYYY-MM",
269
+ week: "YYYY-Www"
270
+ };
271
+ return {
272
+ control: type,
273
+ format: formatMap[type] ?? type,
274
+ current: input.value || "",
275
+ min: input.min || null,
276
+ max: input.max || null
277
+ };
278
+ }
279
+ if (type === "file") {
280
+ const current = Array.from(input.files ?? []).map((f) => f.name);
281
+ return { control: "file", multiple: input.multiple, current, accept: input.accept || null };
282
+ }
283
+ return null;
284
+ }
285
+ if (tag === "select") {
286
+ const select = el;
287
+ const options = Array.from(select.options).slice(0, 50).map((o) => ({
288
+ label: (o.textContent || "").trim(),
289
+ value: o.value,
290
+ selected: o.selected
291
+ }));
292
+ return {
293
+ control: "select",
294
+ multiple: select.multiple,
295
+ current: select.selectedOptions[0] ? (select.selectedOptions[0].textContent || "").trim() : "",
296
+ options,
297
+ options_total: select.options.length
298
+ };
299
+ }
300
+ return null;
301
+ }
302
+ var SKIP_TAGS = /* @__PURE__ */ new Set(["script", "style", "noscript", "meta", "link", "head", "base", "template", "path", "circle", "rect", "g", "defs", "polygon", "line"]);
303
+ function describeElement(el, ref) {
304
+ const tag = el.tagName.toLowerCase();
305
+ const role = el.getAttribute("role") ?? null;
306
+ const name = accessibleName(el);
307
+ const parts = [];
308
+ if (ref != null) parts.push(`[${ref}]`);
309
+ parts.push(tag);
310
+ if (role && role !== IMPLICIT_ROLE[tag]) parts.push(`role=${role}`);
311
+ if (tag === "input") {
312
+ const input = el;
313
+ if (input.type && input.type !== "text") parts.push(`[${input.type}]`);
314
+ if (input.disabled) parts.push("(disabled)");
315
+ }
316
+ if (name) parts.push(JSON.stringify(name.length > 60 ? name.slice(0, 57) + "\u2026" : name));
317
+ if (tag === "a") {
318
+ const href = el.getAttribute("href");
319
+ if (href) parts.push(`\u2192 ${href.slice(0, 50)}`);
320
+ }
321
+ if (el.disabled) parts.push("(disabled)");
322
+ return parts.join(" ");
323
+ }
324
+ function buildStateTree() {
325
+ const lines = [];
326
+ const compounds = {};
327
+ const budget = { nodes: 0, chars: 0, truncated: false };
328
+ const MAX_NODES = 400;
329
+ const MAX_CHARS = 9e3;
330
+ let hiddenInteractive = 0;
331
+ function emit(depth, text) {
332
+ const line = " ".repeat(Math.min(depth, 12)) + text;
333
+ lines.push(line);
334
+ budget.chars += line.length;
335
+ }
336
+ function walk(el, depth) {
337
+ if (budget.nodes >= MAX_NODES || budget.chars >= MAX_CHARS) {
338
+ budget.truncated = true;
339
+ return;
340
+ }
341
+ const tag = el.tagName.toLowerCase();
342
+ if (SKIP_TAGS.has(tag)) return;
343
+ budget.nodes++;
344
+ const visible = isVisible(el);
345
+ const interactive = isInteractive(el);
346
+ if (interactive) {
347
+ if (visible) {
348
+ const ref = allocRef(el);
349
+ emit(depth, describeElement(el, ref));
350
+ const compound = compoundOf(el);
351
+ if (compound) compounds[String(ref)] = compound;
352
+ } else {
353
+ hiddenInteractive++;
354
+ return;
355
+ }
356
+ } else if (visible) {
357
+ if (/^h[1-6]$/.test(tag)) {
358
+ const t = accessibleName(el);
359
+ if (t) emit(depth, `${"#".repeat(Number(tag[1]))} ${t}`);
360
+ } else if (tag === "p" || tag === "li" || tag === "td" || tag === "th" || tag === "figcaption" || tag === "blockquote") {
361
+ const ownText = ownTextOf(el);
362
+ if (ownText) emit(depth, ownText.slice(0, 100));
363
+ } else if (tag === "img") {
364
+ const alt = el.getAttribute("alt") || "";
365
+ if (alt) emit(depth, `img ${JSON.stringify(alt.slice(0, 60))}`);
366
+ }
367
+ }
368
+ if (depth < 14) {
369
+ for (const child of Array.from(el.children)) {
370
+ walk(child, depth + 1);
371
+ if (budget.truncated) return;
372
+ }
373
+ }
374
+ }
375
+ function ownTextOf(el) {
376
+ let text = "";
377
+ for (const node of Array.from(el.childNodes)) {
378
+ if (node.nodeType === Node.TEXT_NODE) text += node.textContent || "";
379
+ }
380
+ return text.replace(/\s+/g, " ").trim();
381
+ }
382
+ const root = document.querySelector("main") || document.querySelector("#root") || document.body;
383
+ emit(0, `# ${document.title}`);
384
+ walk(root, 0);
385
+ const scrollable = document.documentElement.scrollHeight > window.innerHeight + 200;
386
+ if (scrollable) {
387
+ emit(0, "\u2195 page is scrollable \u2014 run cp.scroll down to reveal more");
388
+ }
389
+ if (hiddenInteractive > 0) {
390
+ emit(0, `\u2139 ${hiddenInteractive} hidden interactive element(s) not tagged`);
391
+ }
392
+ if (budget.truncated) {
393
+ emit(0, "\u2026 (snapshot truncated by budget \u2014 narrow with a selector or scroll)");
394
+ }
395
+ return {
396
+ url: location.href,
397
+ title: document.title,
398
+ text: lines.join("\n"),
399
+ compounds,
400
+ hints: { scroll: scrollable, hidden_interactive: hiddenInteractive },
401
+ truncated: budget.truncated
402
+ };
403
+ }
404
+ function runControl(command, args) {
405
+ const a = args || {};
406
+ function getVisibleText(el) {
407
+ return (el.textContent || "").trim().replace(/\s+/g, " ").slice(0, 120);
408
+ }
409
+ function getAttrs(el) {
410
+ const attrs = {};
411
+ for (const name of ["id", "class", "name", "type", "href", "src", "placeholder", "value", "role", "aria-label", "data-testid"]) {
412
+ attrs[name] = el.getAttribute(name);
413
+ }
414
+ return attrs;
415
+ }
416
+ function sel() {
417
+ return {
418
+ ref: a.ref,
419
+ css: a.css,
420
+ nth: a.nth,
421
+ role: a.role,
422
+ name: a.name,
423
+ label: a.label,
424
+ text: a.text,
425
+ testid: a.testid
426
+ };
427
+ }
428
+ function findElements(findArgs) {
429
+ const limit = findArgs.limit ?? 20;
430
+ const textMax = findArgs["text-max"] ?? 120;
431
+ let elements = [];
432
+ if (findArgs.css) {
433
+ try {
434
+ elements = Array.from(document.querySelectorAll(findArgs.css));
435
+ } catch {
436
+ return err(ERROR_CODES.INVALID_SELECTOR, "CSS was rejected by querySelectorAll.", { selector: findArgs.css });
437
+ }
438
+ } else if (findArgs.testid) {
439
+ try {
440
+ elements = Array.from(document.querySelectorAll(testidSelector(findArgs.testid)));
441
+ } catch {
442
+ return err(ERROR_CODES.INVALID_SELECTOR, "Invalid testid.");
443
+ }
444
+ } else if (findArgs.role || findArgs.name || findArgs.label) {
445
+ const r = resolveTargetRich({ role: findArgs.role, name: findArgs.name, label: findArgs.label, text: findArgs.text });
446
+ if ("errorEnvelope" in r) return r.errorEnvelope;
447
+ elements = document.querySelectorAll("*").length ? semanticCandidates(findArgs) : [];
448
+ } else if (findArgs.text) {
449
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
450
+ const lower = findArgs.text.toLowerCase();
451
+ const seen = /* @__PURE__ */ new Set();
452
+ let node;
453
+ while (node = walker.nextNode()) {
454
+ if (node.textContent?.toLowerCase().includes(lower)) {
455
+ const el = node.parentElement;
456
+ if (el && !seen.has(el)) {
457
+ seen.add(el);
458
+ elements.push(el);
459
+ if (elements.length >= limit) break;
460
+ }
461
+ }
462
+ }
463
+ } else {
464
+ elements = Array.from(document.querySelectorAll(INTERACTIVE_SELECTOR));
465
+ }
466
+ const out = elements.slice(0, limit).map((el, i) => {
467
+ const ref = allocRef(el);
468
+ return {
469
+ nth: i,
470
+ ref,
471
+ tag: el.tagName.toLowerCase(),
472
+ role: roleOf(el),
473
+ text: getVisibleText(el).slice(0, textMax),
474
+ attrs: getAttrs(el),
475
+ visible: isVisible(el),
476
+ compound: compoundOf(el) ?? void 0
477
+ };
478
+ });
479
+ return out;
480
+ }
481
+ function semanticCandidates(findArgs) {
482
+ const wantRole = findArgs.role ? findArgs.role.toLowerCase() : null;
483
+ const wantName = findArgs.name ? findArgs.name.toLowerCase() : null;
484
+ const wantText = findArgs.text ? findArgs.text.toLowerCase() : null;
485
+ const all = Array.from(document.querySelectorAll("*"));
486
+ return all.filter((el) => {
487
+ if (!isVisible(el)) return false;
488
+ if (wantRole) {
489
+ const role = roleOf(el);
490
+ if (!role || role.toLowerCase() !== wantRole) return false;
491
+ }
492
+ if (wantName && !accessibleName(el).toLowerCase().includes(wantName)) return false;
493
+ if (wantText && !(el.textContent || "").toLowerCase().includes(wantText)) return false;
494
+ return true;
495
+ });
496
+ }
497
+ function pageState() {
498
+ return {
499
+ url: location.href,
500
+ title: document.title,
501
+ width: window.innerWidth,
502
+ height: window.innerHeight,
503
+ devicePixelRatio: window.devicePixelRatio
504
+ };
505
+ }
506
+ function goto(gotoArgs) {
507
+ if (!gotoArgs.url) return { error: "url_required" };
508
+ let sameUrl = false;
509
+ try {
510
+ sameUrl = new URL(gotoArgs.url, window.location.href).href === window.location.href;
511
+ } catch {
512
+ sameUrl = false;
513
+ }
514
+ if (sameUrl) {
515
+ if (gotoArgs.reload === true) {
516
+ window.location.reload();
517
+ return { navigated: true, reloaded: true, url: gotoArgs.url };
518
+ }
519
+ return { navigated: false, reused: true, url: gotoArgs.url };
520
+ }
521
+ window.location.href = gotoArgs.url;
522
+ return { navigated: true, url: gotoArgs.url };
523
+ }
524
+ function evalScript(evalArgs) {
525
+ if (!evalArgs.script) return { error: "script_required" };
526
+ try {
527
+ const fn = new Function(evalArgs.script);
528
+ const result = fn();
529
+ return { result };
530
+ } catch (e) {
531
+ return { error: String(e) };
532
+ }
533
+ }
534
+ function fillElement(fArgs) {
535
+ const r = resolveTargetRich(sel());
536
+ if ("errorEnvelope" in r) return r.errorEnvelope;
537
+ const { el, matches_n, match_level, target } = r;
538
+ const input = el;
539
+ if (el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.isContentEditable) {
540
+ const isContentEditable = el.isContentEditable;
541
+ if (isContentEditable) {
542
+ el.innerText = fArgs.text ?? "";
543
+ } else {
544
+ input.value = fArgs.text ?? "";
545
+ }
546
+ input.dispatchEvent(new Event("input", { bubbles: true }));
547
+ input.dispatchEvent(new Event("change", { bubbles: true }));
548
+ const actual = isContentEditable ? el.innerText : input.value;
549
+ return actual === (fArgs.text ?? "") ? { filled: true, verified: true, text: fArgs.text, actual, target, matches_n, match_level } : err("verify_failed", "Input value diverged after fill (controlled component?).", { actual });
550
+ }
551
+ return err("invalid_type", "fill only supports input, textarea, and contenteditable.");
552
+ }
553
+ function typeElement(tArgs) {
554
+ const r = resolveTargetRich(sel());
555
+ if ("errorEnvelope" in r) return r.errorEnvelope;
556
+ const { el, matches_n, match_level, target } = r;
557
+ const input = el;
558
+ el.focus({ preventScroll: true });
559
+ input.value = tArgs.text ?? "";
560
+ input.dispatchEvent(new Event("input", { bubbles: true }));
561
+ input.dispatchEvent(new Event("change", { bubbles: true }));
562
+ let autocomplete = false;
563
+ try {
564
+ autocomplete = !!document.querySelector("[role='listbox'], [role='combobox'] [role='listbox'], datalist, .ant-select-dropdown:not([style*='hidden']), [id^='listbox']");
565
+ } catch {
566
+ }
567
+ return { typed: true, text: tArgs.text, target, matches_n, match_level, autocomplete };
568
+ }
569
+ function clickElement() {
570
+ const r = resolveTargetRich(sel());
571
+ if ("errorEnvelope" in r) return r.errorEnvelope;
572
+ const { el, matches_n, match_level, target } = r;
573
+ el.scrollIntoView({ block: "center" });
574
+ el.click();
575
+ return { clicked: true, target, matches_n, match_level, tag: el.tagName.toLowerCase(), text: getVisibleText(el) };
576
+ }
577
+ function selectElement(sArgs) {
578
+ const r = resolveTargetRich(sel());
579
+ if ("errorEnvelope" in r) return r.errorEnvelope;
580
+ const { el, matches_n, match_level, target } = r;
581
+ if (el.tagName !== "SELECT") {
582
+ return err(ERROR_CODES.NOT_A_SELECT, "select was called on a non-<select> element.", { tag: el.tagName.toLowerCase() });
583
+ }
584
+ const select = el;
585
+ const want = sArgs.label ?? sArgs.value ?? sArgs.option;
586
+ if (!want) return err("option_required", "Provide label or value.");
587
+ const byLabel = Array.from(select.options).find((o) => (o.textContent || "").trim() === want);
588
+ const byValue = Array.from(select.options).find((o) => o.value === want);
589
+ const opt = byLabel ?? byValue;
590
+ if (!opt) {
591
+ const available = Array.from(select.options).slice(0, 50).map((o) => (o.textContent || "").trim());
592
+ return err(ERROR_CODES.OPTION_NOT_FOUND, "Option not found.", { available });
593
+ }
594
+ select.value = opt.value;
595
+ select.dispatchEvent(new Event("input", { bubbles: true }));
596
+ select.dispatchEvent(new Event("change", { bubbles: true }));
597
+ return { selected: true, value: select.value, target, matches_n, match_level };
598
+ }
599
+ function pressKeys(kArgs) {
600
+ const keyCombo = kArgs.keys ?? kArgs.key ?? "";
601
+ if (!keyCombo) return { error: "keys_required" };
602
+ const comboParts = keyCombo.split("+").map((s) => s.trim());
603
+ const main = comboParts[comboParts.length - 1];
604
+ const ctrl = comboParts.includes("Control") || comboParts.includes("Ctrl") || comboParts.includes("Meta") || comboParts.includes("Cmd");
605
+ const alt = comboParts.includes("Alt");
606
+ const shift = comboParts.includes("Shift");
607
+ const target = document.activeElement ?? document.body;
608
+ const opts = { key: main, bubbles: true, cancelable: true, ctrlKey: ctrl, altKey: alt, shiftKey: shift, metaKey: comboParts.includes("Meta") || comboParts.includes("Cmd") };
609
+ target.dispatchEvent(new KeyboardEvent("keydown", opts));
610
+ target.dispatchEvent(new KeyboardEvent("keypress", opts));
611
+ target.dispatchEvent(new KeyboardEvent("keyup", opts));
612
+ return { pressed: true, key: keyCombo };
613
+ }
614
+ function hoverElement() {
615
+ const r = resolveTargetRich(sel());
616
+ if ("errorEnvelope" in r) return r.errorEnvelope;
617
+ const { el, matches_n, match_level, target } = r;
618
+ const rect = el.getBoundingClientRect();
619
+ const opts = { bubbles: true, cancelable: true, clientX: rect.x + rect.width / 2, clientY: rect.y + rect.height / 2 };
620
+ el.dispatchEvent(new MouseEvent("mouseover", opts));
621
+ el.dispatchEvent(new MouseEvent("mousemove", opts));
622
+ return { hovered: true, target, matches_n, match_level };
623
+ }
624
+ function focusElement() {
625
+ const r = resolveTargetRich(sel());
626
+ if ("errorEnvelope" in r) return r.errorEnvelope;
627
+ const { el, matches_n, match_level, target } = r;
628
+ el.focus({ preventScroll: true });
629
+ return { focused: true, target, matches_n, match_level };
630
+ }
631
+ function dblclickElement() {
632
+ const r = resolveTargetRich(sel());
633
+ if ("errorEnvelope" in r) return r.errorEnvelope;
634
+ const { el, matches_n, match_level, target } = r;
635
+ el.dispatchEvent(new MouseEvent("dblclick", { bubbles: true, cancelable: true }));
636
+ return { dblclicked: true, target, matches_n, match_level };
637
+ }
638
+ function setChecked(want) {
639
+ const r = resolveTargetRich(sel());
640
+ if ("errorEnvelope" in r) return r.errorEnvelope;
641
+ const { el, matches_n, match_level, target } = r;
642
+ const input = el;
643
+ const isAria = input.type !== "checkbox" && input.type !== "radio" && el.getAttribute("aria-checked") != null;
644
+ if (!isAria && input.type !== "checkbox" && input.type !== "radio") {
645
+ return err("invalid_type", "Target is not a checkbox/radio/aria-checked control.", { tag: el.tagName.toLowerCase() });
646
+ }
647
+ if (isAria) {
648
+ el.setAttribute("aria-checked", String(want));
649
+ el.dispatchEvent(new Event("change", { bubbles: true }));
650
+ return { [want ? "checked" : "unchecked"]: true, changed: true, target, matches_n, match_level, kind: "aria" };
651
+ }
652
+ if (input.type === "radio" && !want) {
653
+ return err("invalid_type", "Radio buttons cannot be unchecked directly; select another radio in the group.");
654
+ }
655
+ const changed = input.checked !== want;
656
+ input.checked = want;
657
+ input.dispatchEvent(new Event("input", { bubbles: true }));
658
+ input.dispatchEvent(new Event("change", { bubbles: true }));
659
+ return { [want ? "checked" : "unchecked"]: true, changed, target, matches_n, match_level, kind: input.type };
660
+ }
661
+ function scrollElement(sArgs) {
662
+ const dir = sArgs.direction ?? "down";
663
+ const amount = sArgs.amount ?? 500;
664
+ if (dir !== "up" && dir !== "down") return { error: "invalid_direction" };
665
+ const dy = dir === "down" ? amount : -amount;
666
+ window.scrollBy({ top: dy, behavior: "instant" });
667
+ return {
668
+ scrolled: true,
669
+ direction: dir,
670
+ amount,
671
+ scrollY: Math.round(window.scrollY),
672
+ scrollHeight: document.documentElement.scrollHeight
673
+ };
674
+ }
675
+ function backNav() {
676
+ history.back();
677
+ return { back: true };
678
+ }
679
+ function htmlJsonTree(el, depth, maxDepth, childrenMax, textMax, truncated) {
680
+ const children = Array.from(el.children);
681
+ const ownText = (el.textContent || "").replace(/\s+/g, " ").trim();
682
+ const node = {
683
+ tag: el.tagName.toLowerCase(),
684
+ attrs: getAttrs(el),
685
+ text: ownText.length > textMax ? (ownText.slice(0, textMax), truncated.text_truncated = (truncated.text_truncated || 0) + 1, ownText.slice(0, textMax)) : ownText,
686
+ children: [],
687
+ compound: compoundOf(el) ?? void 0
688
+ };
689
+ if (depth < maxDepth) {
690
+ let kids = children;
691
+ if (children.length > childrenMax) {
692
+ truncated.children_dropped = (truncated.children_dropped || 0) + (children.length - childrenMax);
693
+ kids = children.slice(0, childrenMax);
694
+ }
695
+ node.children = kids.map((c) => htmlJsonTree(c, depth + 1, maxDepth, childrenMax, textMax, truncated));
696
+ } else if (children.length) {
697
+ truncated.depth = (truncated.depth || 0) + 1;
698
+ }
699
+ return node;
700
+ }
701
+ function getElement(gArgs) {
702
+ const what = gArgs.what ?? "text";
703
+ if (what === "title") return { value: document.title, matches_n: 1, match_level: "exact" };
704
+ if (what === "url") return { value: location.href, matches_n: 1, match_level: "exact" };
705
+ if (what === "html") {
706
+ if (gArgs["as"] === "json") {
707
+ const root2 = gArgs.css ? document.querySelector(gArgs.css) : document.body;
708
+ if (!root2) return err(ERROR_CODES.SELECTOR_NOT_FOUND, "CSS matches 0 elements.", { selector: gArgs.css });
709
+ const truncated = {};
710
+ const tree = htmlJsonTree(root2, 0, gArgs.depth ?? 6, gArgs["children-max"] ?? 20, gArgs["text-max"] ?? 200, truncated);
711
+ return {
712
+ value: tree,
713
+ truncated: Object.keys(truncated).length ? truncated : void 0
714
+ };
715
+ }
716
+ const root = gArgs.css ? document.querySelector(gArgs.css) : document.body;
717
+ if (!root) return err(ERROR_CODES.SELECTOR_NOT_FOUND, "CSS matches 0 elements.", { selector: gArgs.css });
718
+ let html = root.outerHTML;
719
+ if (gArgs.max && html.length > gArgs.max) html = html.slice(0, gArgs.max) + "\u2026";
720
+ return { value: html };
721
+ }
722
+ const r = resolveTargetRich(gArgs);
723
+ if ("errorEnvelope" in r) return r.errorEnvelope;
724
+ const { el, matches_n, match_level } = r;
725
+ switch (what) {
726
+ case "text": {
727
+ const t = getVisibleText(el);
728
+ return { value: t.length > 2e3 ? t.slice(0, 2e3) : t, matches_n, match_level };
729
+ }
730
+ case "value":
731
+ return { value: el.value ?? el.innerText ?? null, matches_n, match_level };
732
+ case "attributes":
733
+ return { value: getAttrs(el), matches_n, match_level };
734
+ default:
735
+ return { error: `unknown_what: ${what}` };
736
+ }
737
+ }
738
+ function extractMarkdown(eArgs) {
739
+ const chunkSize = eArgs["chunk-size"] ?? 0;
740
+ const start = eArgs.start ?? 0;
741
+ const scope = eArgs.selector ? document.querySelector(eArgs.selector) : document.querySelector("main") || document.querySelector("article") || document.body;
742
+ if (!scope) return { error: "scope_not_found" };
743
+ const parts = [];
744
+ const walker = document.createTreeWalker(scope, NodeFilter.SHOW_ELEMENT);
745
+ let node;
746
+ while (node = walker.nextNode()) {
747
+ const el = node;
748
+ const tag = el.tagName.toLowerCase();
749
+ const text = getVisibleText(el);
750
+ if (!text) continue;
751
+ if (tag === "h1") parts.push(`# ${text}`);
752
+ else if (tag === "h2") parts.push(`## ${text}`);
753
+ else if (tag === "h3") parts.push(`### ${text}`);
754
+ else if (tag === "p" || tag === "li") parts.push(text);
755
+ else if (tag === "a") parts.push(`[${text}](${el.getAttribute("href") ?? ""})`);
756
+ }
757
+ const full = parts.join("\n\n");
758
+ const sliced = chunkSize > 0 ? full.slice(start, start + chunkSize) : full.slice(start);
759
+ return {
760
+ url: location.href,
761
+ title: document.title,
762
+ selector: eArgs.selector ?? "auto",
763
+ total_chars: full.length,
764
+ chunk_size: chunkSize,
765
+ start,
766
+ end: start + sliced.length,
767
+ next_start_char: start + sliced.length < full.length ? start + sliced.length : null,
768
+ content: sliced
769
+ };
770
+ }
771
+ function listFrames() {
772
+ const frames = Array.from(document.querySelectorAll("iframe")).map((f, index) => {
773
+ const src = f.getAttribute("src") ?? "";
774
+ let crossOrigin = false;
775
+ try {
776
+ if (src && /^https?:/.test(src)) crossOrigin = new URL(src, location.href).origin !== location.origin;
777
+ } catch {
778
+ }
779
+ return { index, url: src, name: f.getAttribute("name") ?? "", cross_origin: crossOrigin };
780
+ });
781
+ return { frames };
782
+ }
783
+ function screenshotMeta() {
784
+ return {
785
+ width: window.innerWidth,
786
+ height: window.innerHeight,
787
+ url: location.href,
788
+ title: document.title
789
+ };
790
+ }
791
+ function startTitleMarquee2(tArgs) {
792
+ const g = globalThis;
793
+ if (g.__cpMarquee) {
794
+ clearInterval(g.__cpMarquee.interval);
795
+ } else {
796
+ g.__cpMarquee = { original: document.title, interval: 0 };
797
+ }
798
+ const prefix = tArgs.text ?? "\u{1F916}";
799
+ const base = g.__cpMarquee.original;
800
+ const full = `${prefix} ${base}`;
801
+ const speed = Math.max(100, Math.min(2e3, tArgs.speed ?? 300));
802
+ let pos = 0;
803
+ const tick = () => {
804
+ const display = full.slice(pos) + " " + full.slice(0, pos);
805
+ document.title = display.slice(0, 60);
806
+ pos = (pos + 1) % (full.length + 1 || 1);
807
+ };
808
+ tick();
809
+ g.__cpMarquee.interval = window.setInterval(tick, speed);
810
+ return { started: true, original: base, prefix, speed };
811
+ }
812
+ function stopTitleMarquee2() {
813
+ const g = globalThis;
814
+ const state2 = g.__cpMarquee;
815
+ if (!state2) return { restored: false, reason: "no_active_marquee" };
816
+ clearInterval(state2.interval);
817
+ document.title = state2.original;
818
+ delete g.__cpMarquee;
819
+ return { restored: true, original: state2.original };
820
+ }
821
+ function networkEntriesLegacy(nArgs) {
822
+ const all = performance.getEntriesByType("resource").map((e) => ({
823
+ url: e.name,
824
+ type: e.initiatorType ?? "unknown",
825
+ duration: Math.round(e.duration)
826
+ }));
827
+ let entries = all;
828
+ if (nArgs.filter) {
829
+ const keywords = nArgs.filter.split(",").map((s) => s.trim().toLowerCase());
830
+ entries = all.filter((e) => keywords.some((kw) => e.url.toLowerCase().includes(kw)));
831
+ }
832
+ return { entries, count: entries.length, source: "performance-api" };
833
+ }
834
+ async function waitForCondition(wArgs) {
835
+ const timeout = wArgs.timeout ?? 1e4;
836
+ if (wArgs.time != null) {
837
+ await new Promise((res) => setTimeout(res, Math.min(wArgs.time * 1e3, 3e4)));
838
+ return { waited: true, kind: "time", seconds: wArgs.time };
839
+ }
840
+ const deadline = Date.now() + timeout;
841
+ const pollInterval = 200;
842
+ if (wArgs.selector) {
843
+ while (Date.now() < deadline) {
844
+ if (document.querySelector(wArgs.selector)) return { waited: true, kind: "selector", selector: wArgs.selector, elapsedMs: timeout - (deadline - Date.now()) };
845
+ await new Promise((res) => setTimeout(res, pollInterval));
846
+ }
847
+ return err("wait_timeout", `Selector not found within ${timeout}ms.`, { selector: wArgs.selector });
848
+ }
849
+ if (wArgs.text) {
850
+ const lower = wArgs.text.toLowerCase();
851
+ while (Date.now() < deadline) {
852
+ if ((document.body.textContent || "").toLowerCase().includes(lower)) {
853
+ return { waited: true, kind: "text", text: wArgs.text, elapsedMs: timeout - (deadline - Date.now()) };
854
+ }
855
+ await new Promise((res) => setTimeout(res, pollInterval));
856
+ }
857
+ return err("wait_timeout", `Text not found within ${timeout}ms.`, { text: wArgs.text });
858
+ }
859
+ return { error: "no_condition" };
860
+ }
861
+ function uploadFiles(uArgs) {
862
+ const r = resolveTargetRich(sel());
863
+ if ("errorEnvelope" in r) return r.errorEnvelope;
864
+ const { el, matches_n, match_level, target } = r;
865
+ const input = el;
866
+ if (input.type !== "file") return err("invalid_type", "Target is not an input[type=file].", { tag: el.tagName.toLowerCase() });
867
+ try {
868
+ const dt = new DataTransfer();
869
+ for (const name of uArgs.files ?? []) {
870
+ dt.items.add(new File([""], name.split("/").pop() || name));
871
+ }
872
+ input.files = dt.files;
873
+ } catch {
874
+ }
875
+ input.dispatchEvent(new Event("change", { bubbles: true }));
876
+ return {
877
+ uploaded: true,
878
+ files: uArgs.files ?? [],
879
+ file_names: (uArgs.files ?? []).map((f) => f.split("/").pop()),
880
+ target,
881
+ matches_n,
882
+ match_level,
883
+ multiple: input.multiple,
884
+ accept: input.accept || null
885
+ };
886
+ }
887
+ function dragElements(dArgs) {
888
+ const r1 = resolveTargetRich(sel());
889
+ if ("errorEnvelope" in r1) return r1.errorEnvelope;
890
+ const r2 = resolveTargetRich({ ref: dArgs.ref2, css: dArgs.css2 });
891
+ if ("errorEnvelope" in r2) return r2.errorEnvelope;
892
+ const src = r1.el;
893
+ const tgt = r2.el;
894
+ const make = (type) => {
895
+ const r1rect = src.getBoundingClientRect();
896
+ return new DragEvent(type, { bubbles: true, cancelable: true, clientX: r1rect.x + r1rect.width / 2, clientY: r1rect.y + r1rect.height / 2 });
897
+ };
898
+ src.dispatchEvent(make("dragstart"));
899
+ tgt.dispatchEvent(new DragEvent("dragenter", { bubbles: true }));
900
+ tgt.dispatchEvent(new DragEvent("dragover", { bubbles: true, cancelable: true }));
901
+ tgt.dispatchEvent(new DragEvent("drop", { bubbles: true, cancelable: true }));
902
+ src.dispatchEvent(new DragEvent("dragend", { bubbles: true }));
903
+ return { dragged: true, source: String(sel().ref ?? sel().css ?? ""), target: String(dArgs.ref2 ?? dArgs.css2 ?? ""), source_matches_n: r1.matches_n, target_matches_n: r2.matches_n };
904
+ }
905
+ switch (command) {
906
+ case "cp.state":
907
+ return a.tree === false ? pageState() : buildStateTree();
908
+ case "cp.snapshot":
909
+ return buildStateTree();
910
+ case "cp.find":
911
+ return findElements({
912
+ css: a.css,
913
+ text: a.text,
914
+ limit: a.limit,
915
+ role: a.role,
916
+ name: a.name,
917
+ label: a.label,
918
+ testid: a.testid,
919
+ "text-max": a["text-max"]
920
+ });
921
+ case "cp.click":
922
+ return clickElement();
923
+ case "cp.type":
924
+ return typeElement({ text: a.text });
925
+ case "cp.goto":
926
+ return goto({ url: a.url });
927
+ case "cp.eval":
928
+ return evalScript({ script: a.script });
929
+ case "cp.fill":
930
+ return fillElement({ text: a.text });
931
+ case "cp.select":
932
+ return selectElement({ label: a.label, value: a.value, option: a.option });
933
+ case "cp.keys":
934
+ return pressKeys({ keys: a.keys, key: a.key });
935
+ case "cp.hover":
936
+ return hoverElement();
937
+ case "cp.focus":
938
+ return focusElement();
939
+ case "cp.dblclick":
940
+ return dblclickElement();
941
+ case "cp.check":
942
+ return setChecked(true);
943
+ case "cp.uncheck":
944
+ return setChecked(false);
945
+ case "cp.scroll":
946
+ return scrollElement({ direction: a.direction, amount: a.amount });
947
+ case "cp.back":
948
+ return backNav();
949
+ case "cp.get":
950
+ return getElement({
951
+ what: a.what,
952
+ css: a.css,
953
+ ref: a.ref,
954
+ nth: a.nth,
955
+ "as": a["as"],
956
+ depth: a.depth,
957
+ "children-max": a["children-max"],
958
+ "text-max": a["text-max"],
959
+ max: a.max,
960
+ role: a.role,
961
+ name: a.name,
962
+ label: a.label,
963
+ text: a.text,
964
+ testid: a.testid
965
+ });
966
+ case "cp.extract":
967
+ return extractMarkdown({
968
+ "chunk-size": a["chunk-size"],
969
+ start: a.start,
970
+ selector: a.selector
971
+ });
972
+ case "cp.frames":
973
+ return listFrames();
974
+ case "cp.screenshot":
975
+ return screenshotMeta();
976
+ case "cp.network":
977
+ return networkEntriesLegacy({ filter: a.filter });
978
+ case "cp.wait":
979
+ return waitForCondition({
980
+ selector: a.selector,
981
+ text: a.text,
982
+ time: a.time,
983
+ timeout: a.timeout
984
+ });
985
+ case "cp.upload":
986
+ return uploadFiles({ files: a.files });
987
+ case "cp.drag":
988
+ return dragElements({ ref2: a.ref2, css2: a.css2 });
989
+ case "cp.title.marquee":
990
+ return startTitleMarquee2({ text: a.text, speed: a.speed });
991
+ case "cp.title.restore":
992
+ return stopTitleMarquee2();
993
+ default:
994
+ return { error: `unknown_command: ${command}` };
995
+ }
996
+ }
997
+ var RUNTIME_VERSION = "0.3.0-opencli-parity";
998
+ globalThis.__cpRunControl = runControl;
999
+ globalThis.__cpRuntimeVersion = RUNTIME_VERSION;
1000
+
1001
+ // src/generated/control-runtime-source.ts
1002
+ var control_runtime_source_default = '"use strict";\n(() => {\n // src/shared/control-runtime.ts\n function getRefStore() {\n const g = globalThis;\n if (!g.__cpRefs) {\n g.__cpRefs = { counter: 1, refs: /* @__PURE__ */ new Map(), cssPaths: /* @__PURE__ */ new Map() };\n }\n return g.__cpRefs;\n }\n function allocRef(el) {\n const store = getRefStore();\n for (const [ref2, existing] of store.refs) {\n if (existing === el) return ref2;\n }\n const ref = store.counter++;\n store.refs.set(ref, el);\n store.cssPaths.set(ref, uniqueCssPath(el));\n return ref;\n }\n function uniqueCssPath(el) {\n if (el.id) return `#${CSS.escape(el.id)}`;\n const parts = [];\n let cur = el;\n while (cur && cur !== document.body && cur !== document.documentElement) {\n let sel = cur.tagName.toLowerCase();\n if (cur.id) {\n parts.unshift(`#${CSS.escape(cur.id)}`);\n break;\n }\n const parent = cur.parentElement;\n if (parent) {\n const sameTag = Array.from(parent.children).filter((c) => c.tagName === cur.tagName);\n if (sameTag.length > 1) {\n const idx = sameTag.indexOf(cur) + 1;\n sel += `:nth-of-type(${idx})`;\n }\n }\n parts.unshift(sel);\n cur = parent;\n }\n return parts.join(" > ");\n }\n function err(code, message, extra) {\n return { error: { code, message, ...extra || {} } };\n }\n var ERROR_CODES = {\n NOT_FOUND: "not_found",\n STALE_REF: "stale_ref",\n INVALID_SELECTOR: "invalid_selector",\n SELECTOR_NOT_FOUND: "selector_not_found",\n SELECTOR_AMBIGUOUS: "selector_ambiguous",\n SELECTOR_NTH_OUT_OF_RANGE: "selector_nth_out_of_range",\n OPTION_NOT_FOUND: "option_not_found",\n NOT_A_SELECT: "not_a_select"\n };\n var IMPLICIT_ROLE = {\n a: "link",\n button: "button",\n select: "combobox",\n textarea: "textbox",\n h1: "heading",\n h2: "heading",\n h3: "heading",\n h4: "heading",\n h5: "heading",\n h6: "heading",\n img: "img",\n nav: "navigation",\n ul: "list",\n ol: "list",\n li: "listitem",\n main: "main",\n article: "article",\n aside: "complementary",\n header: "banner",\n footer: "contentinfo",\n form: "form",\n table: "table",\n summary: "button",\n option: "option",\n output: "status"\n };\n function roleOf(el) {\n const explicit = el.getAttribute("role");\n if (explicit) return explicit.split(/\\s+/)[0];\n const tag = el.tagName.toLowerCase();\n if (tag === "input") {\n const type = el.type || "text";\n if (type === "hidden") return null;\n if (type === "checkbox") return "checkbox";\n if (type === "radio") return "radio";\n if (type === "button" || type === "submit" || type === "reset") return "button";\n if (type === "range") return "slider";\n return "textbox";\n }\n if (tag === "progress") return "progressbar";\n if (tag === "meter") return "meter";\n return IMPLICIT_ROLE[tag] ?? null;\n }\n function accessibleName(el) {\n const aria = el.getAttribute("aria-label");\n if (aria) return aria.trim();\n const labelledby = el.getAttribute("aria-labelledby");\n if (labelledby) {\n const parts = labelledby.split(/\\s+/).map((id2) => document.getElementById(id2)?.textContent?.trim() || "");\n const joined = parts.filter(Boolean).join(" ");\n if (joined) return joined.slice(0, 120);\n }\n const id = el.getAttribute("id");\n if (id) {\n const label = document.querySelector(`label[for="${CSS.escape(id)}"]`);\n if (label?.textContent?.trim()) return label.textContent.trim().slice(0, 120);\n }\n if (el.tagName.toLowerCase() === "input") {\n const type = el.type;\n if (type === "button" || type === "submit" || type === "reset") {\n const v = el.value;\n if (v) return v;\n }\n }\n const alt = el.getAttribute("alt");\n if (alt) return alt;\n const title = el.getAttribute("title");\n if (title) return title;\n const placeholder = el.getAttribute("placeholder");\n if (placeholder) return placeholder;\n return (el.textContent || "").replace(/\\s+/g, " ").trim().slice(0, 120);\n }\n function isVisible(el) {\n if (!(el instanceof HTMLElement) && !(el instanceof SVGElement)) return false;\n const html = el;\n if (html.hasAttribute("hidden")) return false;\n if (html.closest("[hidden]")) return false;\n const style = window.getComputedStyle(html);\n if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") return false;\n if (el instanceof HTMLElement && el.offsetParent === null) {\n const pos = style.position;\n if (pos !== "fixed" && pos !== "sticky") return false;\n }\n return true;\n }\n var INTERACTIVE_SELECTOR = "a[href], button, input, textarea, select, summary, [role], [contenteditable=\'\'], [contenteditable=\'true\'], [onclick], [tabindex]";\n function isInteractive(el) {\n return el.matches(INTERACTIVE_SELECTOR);\n }\n function testidSelector(testid) {\n return `[data-testid="${CSS.escape(testid)}"], [data-test-id="${CSS.escape(testid)}"], [data-test="${CSS.escape(testid)}"], [data-cy="${CSS.escape(testid)}"]`;\n }\n function resolveTargetRich(sel) {\n const targetDesc = sel.ref != null ? String(sel.ref) : sel.css ?? (sel.role || sel.name || sel.label || sel.testid || sel.text ? `${sel.role ? `role=${sel.role} ` : ""}${sel.name ? `name=${sel.name} ` : ""}${sel.label ? `label=${sel.label} ` : ""}${sel.testid ? `testid=${sel.testid} ` : ""}${sel.text ? `text=${sel.text}` : ""}`.trim() : "");\n if (sel.ref != null) {\n const store = getRefStore();\n const el = store.refs.get(sel.ref);\n if (el && document.contains(el) && isVisible(el)) {\n return { el, matches_n: 1, match_level: "exact", target: targetDesc };\n }\n if (el && !document.contains(el)) {\n const cssPath = store.cssPaths.get(sel.ref);\n if (cssPath) {\n const re = document.querySelector(cssPath);\n if (re && isVisible(re)) {\n store.refs.set(sel.ref, re);\n return { el: re, matches_n: 1, match_level: "reidentified", target: targetDesc };\n }\n }\n return { errorEnvelope: err(ERROR_CODES.STALE_REF, "Numeric ref is no longer in the DOM. Re-run state.") };\n }\n return { errorEnvelope: err(ERROR_CODES.NOT_FOUND, "Numeric ref is no longer in the DOM. Re-run state.") };\n }\n let candidates = [];\n if (sel.css) {\n try {\n candidates = Array.from(document.querySelectorAll(sel.css));\n } catch {\n return { errorEnvelope: err(ERROR_CODES.INVALID_SELECTOR, "CSS was rejected by querySelectorAll.", { selector: sel.css }) };\n }\n if (candidates.length === 0) {\n return { errorEnvelope: err(ERROR_CODES.SELECTOR_NOT_FOUND, "CSS matches 0 elements.", { selector: sel.css, candidates: suggestCandidates() }) };\n }\n } else if (sel.testid) {\n try {\n candidates = Array.from(document.querySelectorAll(testidSelector(sel.testid)));\n } catch {\n return { errorEnvelope: err(ERROR_CODES.INVALID_SELECTOR, "Invalid testid.", { testid: sel.testid }) };\n }\n } else if (sel.role || sel.name || sel.label || sel.text) {\n const wantRole = sel.role ? sel.role.toLowerCase() : null;\n const wantName = sel.name ? sel.name.toLowerCase() : null;\n const wantText = sel.text ? sel.text.toLowerCase() : null;\n let labelTargets = [];\n if (sel.label) {\n const lower = sel.label.toLowerCase();\n labelTargets = Array.from(document.querySelectorAll("label")).filter((l) => (l.textContent || "").trim().toLowerCase().includes(lower)).map((l) => {\n const forId = l.getAttribute("for");\n if (forId) return document.getElementById(forId);\n return l.querySelector("input, select, textarea");\n }).filter((x) => !!x);\n }\n const all = Array.from(document.querySelectorAll("*"));\n candidates = all.filter((el) => {\n if (!isVisible(el)) return false;\n const role = roleOf(el);\n if (wantRole && (!role || role.toLowerCase() !== wantRole)) return false;\n if (wantName) {\n const name = accessibleName(el).toLowerCase();\n if (!name.includes(wantName)) return false;\n }\n if (wantText) {\n const t = (el.textContent || "").replace(/\\s+/g, " ").trim().toLowerCase();\n if (!t.includes(wantText)) return false;\n }\n return true;\n });\n if (labelTargets.length) {\n const set = new Set(labelTargets);\n candidates = labelTargets.concat(candidates.filter((c) => !set.has(c)));\n }\n if (candidates.length === 0) {\n return {\n errorEnvelope: err(\n ERROR_CODES.SELECTOR_NOT_FOUND,\n "Semantic locator matches 0 elements.",\n { role: sel.role, name: sel.name, label: sel.label, text: sel.text, candidates: suggestCandidates() }\n )\n };\n }\n } else {\n return { errorEnvelope: err("no_target", "Provide a numeric ref, CSS selector, or semantic locator (--role/--name/--label/--text/--testid).") };\n }\n const visible = candidates.filter(isVisible);\n const pool = visible.length ? visible : candidates;\n if (sel.nth != null) {\n if (sel.nth < 0 || sel.nth >= pool.length) {\n return { errorEnvelope: err(ERROR_CODES.SELECTOR_NTH_OUT_OF_RANGE, `--nth ${sel.nth} beyond match count ${pool.length}.`) };\n }\n const el = pool[sel.nth];\n return { el, matches_n: pool.length, match_level: "reidentified", target: targetDesc };\n }\n if (pool.length > 1) {\n return {\n errorEnvelope: err(ERROR_CODES.SELECTOR_AMBIGUOUS, `CSS matches ${pool.length} elements and no --nth given.`, {\n matches_n: pool.length,\n candidates: pool.slice(0, 5).map((el) => uniqueCssPath(el))\n })\n };\n }\n return { el: pool[0], matches_n: 1, match_level: "reidentified", target: targetDesc };\n }\n function suggestCandidates() {\n const store = getRefStore();\n const recent = Array.from(store.refs.entries()).slice(-5).map(([, el]) => uniqueCssPath(el));\n return recent;\n }\n function compoundOf(el) {\n const tag = el.tagName.toLowerCase();\n if (tag === "input") {\n const input = el;\n const type = input.type;\n if (["date", "time", "datetime-local", "month", "week"].includes(type)) {\n const formatMap = {\n date: "YYYY-MM-DD",\n time: "HH:mm",\n "datetime-local": "YYYY-MM-DDTHH:mm",\n month: "YYYY-MM",\n week: "YYYY-Www"\n };\n return {\n control: type,\n format: formatMap[type] ?? type,\n current: input.value || "",\n min: input.min || null,\n max: input.max || null\n };\n }\n if (type === "file") {\n const current = Array.from(input.files ?? []).map((f) => f.name);\n return { control: "file", multiple: input.multiple, current, accept: input.accept || null };\n }\n return null;\n }\n if (tag === "select") {\n const select = el;\n const options = Array.from(select.options).slice(0, 50).map((o) => ({\n label: (o.textContent || "").trim(),\n value: o.value,\n selected: o.selected\n }));\n return {\n control: "select",\n multiple: select.multiple,\n current: select.selectedOptions[0] ? (select.selectedOptions[0].textContent || "").trim() : "",\n options,\n options_total: select.options.length\n };\n }\n return null;\n }\n var SKIP_TAGS = /* @__PURE__ */ new Set(["script", "style", "noscript", "meta", "link", "head", "base", "template", "path", "circle", "rect", "g", "defs", "polygon", "line"]);\n function describeElement(el, ref) {\n const tag = el.tagName.toLowerCase();\n const role = el.getAttribute("role") ?? null;\n const name = accessibleName(el);\n const parts = [];\n if (ref != null) parts.push(`[${ref}]`);\n parts.push(tag);\n if (role && role !== IMPLICIT_ROLE[tag]) parts.push(`role=${role}`);\n if (tag === "input") {\n const input = el;\n if (input.type && input.type !== "text") parts.push(`[${input.type}]`);\n if (input.disabled) parts.push("(disabled)");\n }\n if (name) parts.push(JSON.stringify(name.length > 60 ? name.slice(0, 57) + "\\u2026" : name));\n if (tag === "a") {\n const href = el.getAttribute("href");\n if (href) parts.push(`\\u2192 ${href.slice(0, 50)}`);\n }\n if (el.disabled) parts.push("(disabled)");\n return parts.join(" ");\n }\n function buildStateTree() {\n const lines = [];\n const compounds = {};\n const budget = { nodes: 0, chars: 0, truncated: false };\n const MAX_NODES = 400;\n const MAX_CHARS = 9e3;\n let hiddenInteractive = 0;\n function emit(depth, text) {\n const line = " ".repeat(Math.min(depth, 12)) + text;\n lines.push(line);\n budget.chars += line.length;\n }\n function walk(el, depth) {\n if (budget.nodes >= MAX_NODES || budget.chars >= MAX_CHARS) {\n budget.truncated = true;\n return;\n }\n const tag = el.tagName.toLowerCase();\n if (SKIP_TAGS.has(tag)) return;\n budget.nodes++;\n const visible = isVisible(el);\n const interactive = isInteractive(el);\n if (interactive) {\n if (visible) {\n const ref = allocRef(el);\n emit(depth, describeElement(el, ref));\n const compound = compoundOf(el);\n if (compound) compounds[String(ref)] = compound;\n } else {\n hiddenInteractive++;\n return;\n }\n } else if (visible) {\n if (/^h[1-6]$/.test(tag)) {\n const t = accessibleName(el);\n if (t) emit(depth, `${"#".repeat(Number(tag[1]))} ${t}`);\n } else if (tag === "p" || tag === "li" || tag === "td" || tag === "th" || tag === "figcaption" || tag === "blockquote") {\n const ownText = ownTextOf(el);\n if (ownText) emit(depth, ownText.slice(0, 100));\n } else if (tag === "img") {\n const alt = el.getAttribute("alt") || "";\n if (alt) emit(depth, `img ${JSON.stringify(alt.slice(0, 60))}`);\n }\n }\n if (depth < 14) {\n for (const child of Array.from(el.children)) {\n walk(child, depth + 1);\n if (budget.truncated) return;\n }\n }\n }\n function ownTextOf(el) {\n let text = "";\n for (const node of Array.from(el.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) text += node.textContent || "";\n }\n return text.replace(/\\s+/g, " ").trim();\n }\n const root = document.querySelector("main") || document.querySelector("#root") || document.body;\n emit(0, `# ${document.title}`);\n walk(root, 0);\n const scrollable = document.documentElement.scrollHeight > window.innerHeight + 200;\n if (scrollable) {\n emit(0, "\\u2195 page is scrollable \\u2014 run cp.scroll down to reveal more");\n }\n if (hiddenInteractive > 0) {\n emit(0, `\\u2139 ${hiddenInteractive} hidden interactive element(s) not tagged`);\n }\n if (budget.truncated) {\n emit(0, "\\u2026 (snapshot truncated by budget \\u2014 narrow with a selector or scroll)");\n }\n return {\n url: location.href,\n title: document.title,\n text: lines.join("\\n"),\n compounds,\n hints: { scroll: scrollable, hidden_interactive: hiddenInteractive },\n truncated: budget.truncated\n };\n }\n function runControl(command, args) {\n const a = args || {};\n function getVisibleText(el) {\n return (el.textContent || "").trim().replace(/\\s+/g, " ").slice(0, 120);\n }\n function getAttrs(el) {\n const attrs = {};\n for (const name of ["id", "class", "name", "type", "href", "src", "placeholder", "value", "role", "aria-label", "data-testid"]) {\n attrs[name] = el.getAttribute(name);\n }\n return attrs;\n }\n function sel() {\n return {\n ref: a.ref,\n css: a.css,\n nth: a.nth,\n role: a.role,\n name: a.name,\n label: a.label,\n text: a.text,\n testid: a.testid\n };\n }\n function findElements(findArgs) {\n const limit = findArgs.limit ?? 20;\n const textMax = findArgs["text-max"] ?? 120;\n let elements = [];\n if (findArgs.css) {\n try {\n elements = Array.from(document.querySelectorAll(findArgs.css));\n } catch {\n return err(ERROR_CODES.INVALID_SELECTOR, "CSS was rejected by querySelectorAll.", { selector: findArgs.css });\n }\n } else if (findArgs.testid) {\n try {\n elements = Array.from(document.querySelectorAll(testidSelector(findArgs.testid)));\n } catch {\n return err(ERROR_CODES.INVALID_SELECTOR, "Invalid testid.");\n }\n } else if (findArgs.role || findArgs.name || findArgs.label) {\n const r = resolveTargetRich({ role: findArgs.role, name: findArgs.name, label: findArgs.label, text: findArgs.text });\n if ("errorEnvelope" in r) return r.errorEnvelope;\n elements = document.querySelectorAll("*").length ? semanticCandidates(findArgs) : [];\n } else if (findArgs.text) {\n const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);\n const lower = findArgs.text.toLowerCase();\n const seen = /* @__PURE__ */ new Set();\n let node;\n while (node = walker.nextNode()) {\n if (node.textContent?.toLowerCase().includes(lower)) {\n const el = node.parentElement;\n if (el && !seen.has(el)) {\n seen.add(el);\n elements.push(el);\n if (elements.length >= limit) break;\n }\n }\n }\n } else {\n elements = Array.from(document.querySelectorAll(INTERACTIVE_SELECTOR));\n }\n const out = elements.slice(0, limit).map((el, i) => {\n const ref = allocRef(el);\n return {\n nth: i,\n ref,\n tag: el.tagName.toLowerCase(),\n role: roleOf(el),\n text: getVisibleText(el).slice(0, textMax),\n attrs: getAttrs(el),\n visible: isVisible(el),\n compound: compoundOf(el) ?? void 0\n };\n });\n return out;\n }\n function semanticCandidates(findArgs) {\n const wantRole = findArgs.role ? findArgs.role.toLowerCase() : null;\n const wantName = findArgs.name ? findArgs.name.toLowerCase() : null;\n const wantText = findArgs.text ? findArgs.text.toLowerCase() : null;\n const all = Array.from(document.querySelectorAll("*"));\n return all.filter((el) => {\n if (!isVisible(el)) return false;\n if (wantRole) {\n const role = roleOf(el);\n if (!role || role.toLowerCase() !== wantRole) return false;\n }\n if (wantName && !accessibleName(el).toLowerCase().includes(wantName)) return false;\n if (wantText && !(el.textContent || "").toLowerCase().includes(wantText)) return false;\n return true;\n });\n }\n function pageState() {\n return {\n url: location.href,\n title: document.title,\n width: window.innerWidth,\n height: window.innerHeight,\n devicePixelRatio: window.devicePixelRatio\n };\n }\n function goto(gotoArgs) {\n if (!gotoArgs.url) return { error: "url_required" };\n let sameUrl = false;\n try {\n sameUrl = new URL(gotoArgs.url, window.location.href).href === window.location.href;\n } catch {\n sameUrl = false;\n }\n if (sameUrl) {\n if (gotoArgs.reload === true) {\n window.location.reload();\n return { navigated: true, reloaded: true, url: gotoArgs.url };\n }\n return { navigated: false, reused: true, url: gotoArgs.url };\n }\n window.location.href = gotoArgs.url;\n return { navigated: true, url: gotoArgs.url };\n }\n function evalScript(evalArgs) {\n if (!evalArgs.script) return { error: "script_required" };\n try {\n const fn = new Function(evalArgs.script);\n const result = fn();\n return { result };\n } catch (e) {\n return { error: String(e) };\n }\n }\n function fillElement(fArgs) {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n const input = el;\n if (el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.isContentEditable) {\n const isContentEditable = el.isContentEditable;\n if (isContentEditable) {\n el.innerText = fArgs.text ?? "";\n } else {\n input.value = fArgs.text ?? "";\n }\n input.dispatchEvent(new Event("input", { bubbles: true }));\n input.dispatchEvent(new Event("change", { bubbles: true }));\n const actual = isContentEditable ? el.innerText : input.value;\n return actual === (fArgs.text ?? "") ? { filled: true, verified: true, text: fArgs.text, actual, target, matches_n, match_level } : err("verify_failed", "Input value diverged after fill (controlled component?).", { actual });\n }\n return err("invalid_type", "fill only supports input, textarea, and contenteditable.");\n }\n function typeElement(tArgs) {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n const input = el;\n el.focus({ preventScroll: true });\n input.value = tArgs.text ?? "";\n input.dispatchEvent(new Event("input", { bubbles: true }));\n input.dispatchEvent(new Event("change", { bubbles: true }));\n let autocomplete = false;\n try {\n autocomplete = !!document.querySelector("[role=\'listbox\'], [role=\'combobox\'] [role=\'listbox\'], datalist, .ant-select-dropdown:not([style*=\'hidden\']), [id^=\'listbox\']");\n } catch {\n }\n return { typed: true, text: tArgs.text, target, matches_n, match_level, autocomplete };\n }\n function clickElement() {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n el.scrollIntoView({ block: "center" });\n el.click();\n return { clicked: true, target, matches_n, match_level, tag: el.tagName.toLowerCase(), text: getVisibleText(el) };\n }\n function selectElement(sArgs) {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n if (el.tagName !== "SELECT") {\n return err(ERROR_CODES.NOT_A_SELECT, "select was called on a non-<select> element.", { tag: el.tagName.toLowerCase() });\n }\n const select = el;\n const want = sArgs.label ?? sArgs.value ?? sArgs.option;\n if (!want) return err("option_required", "Provide label or value.");\n const byLabel = Array.from(select.options).find((o) => (o.textContent || "").trim() === want);\n const byValue = Array.from(select.options).find((o) => o.value === want);\n const opt = byLabel ?? byValue;\n if (!opt) {\n const available = Array.from(select.options).slice(0, 50).map((o) => (o.textContent || "").trim());\n return err(ERROR_CODES.OPTION_NOT_FOUND, "Option not found.", { available });\n }\n select.value = opt.value;\n select.dispatchEvent(new Event("input", { bubbles: true }));\n select.dispatchEvent(new Event("change", { bubbles: true }));\n return { selected: true, value: select.value, target, matches_n, match_level };\n }\n function pressKeys(kArgs) {\n const keyCombo = kArgs.keys ?? kArgs.key ?? "";\n if (!keyCombo) return { error: "keys_required" };\n const comboParts = keyCombo.split("+").map((s) => s.trim());\n const main = comboParts[comboParts.length - 1];\n const ctrl = comboParts.includes("Control") || comboParts.includes("Ctrl") || comboParts.includes("Meta") || comboParts.includes("Cmd");\n const alt = comboParts.includes("Alt");\n const shift = comboParts.includes("Shift");\n const target = document.activeElement ?? document.body;\n const opts = { key: main, bubbles: true, cancelable: true, ctrlKey: ctrl, altKey: alt, shiftKey: shift, metaKey: comboParts.includes("Meta") || comboParts.includes("Cmd") };\n target.dispatchEvent(new KeyboardEvent("keydown", opts));\n target.dispatchEvent(new KeyboardEvent("keypress", opts));\n target.dispatchEvent(new KeyboardEvent("keyup", opts));\n return { pressed: true, key: keyCombo };\n }\n function hoverElement() {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n const rect = el.getBoundingClientRect();\n const opts = { bubbles: true, cancelable: true, clientX: rect.x + rect.width / 2, clientY: rect.y + rect.height / 2 };\n el.dispatchEvent(new MouseEvent("mouseover", opts));\n el.dispatchEvent(new MouseEvent("mousemove", opts));\n return { hovered: true, target, matches_n, match_level };\n }\n function focusElement() {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n el.focus({ preventScroll: true });\n return { focused: true, target, matches_n, match_level };\n }\n function dblclickElement() {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n el.dispatchEvent(new MouseEvent("dblclick", { bubbles: true, cancelable: true }));\n return { dblclicked: true, target, matches_n, match_level };\n }\n function setChecked(want) {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n const input = el;\n const isAria = input.type !== "checkbox" && input.type !== "radio" && el.getAttribute("aria-checked") != null;\n if (!isAria && input.type !== "checkbox" && input.type !== "radio") {\n return err("invalid_type", "Target is not a checkbox/radio/aria-checked control.", { tag: el.tagName.toLowerCase() });\n }\n if (isAria) {\n el.setAttribute("aria-checked", String(want));\n el.dispatchEvent(new Event("change", { bubbles: true }));\n return { [want ? "checked" : "unchecked"]: true, changed: true, target, matches_n, match_level, kind: "aria" };\n }\n if (input.type === "radio" && !want) {\n return err("invalid_type", "Radio buttons cannot be unchecked directly; select another radio in the group.");\n }\n const changed = input.checked !== want;\n input.checked = want;\n input.dispatchEvent(new Event("input", { bubbles: true }));\n input.dispatchEvent(new Event("change", { bubbles: true }));\n return { [want ? "checked" : "unchecked"]: true, changed, target, matches_n, match_level, kind: input.type };\n }\n function scrollElement(sArgs) {\n const dir = sArgs.direction ?? "down";\n const amount = sArgs.amount ?? 500;\n if (dir !== "up" && dir !== "down") return { error: "invalid_direction" };\n const dy = dir === "down" ? amount : -amount;\n window.scrollBy({ top: dy, behavior: "instant" });\n return {\n scrolled: true,\n direction: dir,\n amount,\n scrollY: Math.round(window.scrollY),\n scrollHeight: document.documentElement.scrollHeight\n };\n }\n function backNav() {\n history.back();\n return { back: true };\n }\n function htmlJsonTree(el, depth, maxDepth, childrenMax, textMax, truncated) {\n const children = Array.from(el.children);\n const ownText = (el.textContent || "").replace(/\\s+/g, " ").trim();\n const node = {\n tag: el.tagName.toLowerCase(),\n attrs: getAttrs(el),\n text: ownText.length > textMax ? (ownText.slice(0, textMax), truncated.text_truncated = (truncated.text_truncated || 0) + 1, ownText.slice(0, textMax)) : ownText,\n children: [],\n compound: compoundOf(el) ?? void 0\n };\n if (depth < maxDepth) {\n let kids = children;\n if (children.length > childrenMax) {\n truncated.children_dropped = (truncated.children_dropped || 0) + (children.length - childrenMax);\n kids = children.slice(0, childrenMax);\n }\n node.children = kids.map((c) => htmlJsonTree(c, depth + 1, maxDepth, childrenMax, textMax, truncated));\n } else if (children.length) {\n truncated.depth = (truncated.depth || 0) + 1;\n }\n return node;\n }\n function getElement(gArgs) {\n const what = gArgs.what ?? "text";\n if (what === "title") return { value: document.title, matches_n: 1, match_level: "exact" };\n if (what === "url") return { value: location.href, matches_n: 1, match_level: "exact" };\n if (what === "html") {\n if (gArgs["as"] === "json") {\n const root2 = gArgs.css ? document.querySelector(gArgs.css) : document.body;\n if (!root2) return err(ERROR_CODES.SELECTOR_NOT_FOUND, "CSS matches 0 elements.", { selector: gArgs.css });\n const truncated = {};\n const tree = htmlJsonTree(root2, 0, gArgs.depth ?? 6, gArgs["children-max"] ?? 20, gArgs["text-max"] ?? 200, truncated);\n return {\n value: tree,\n truncated: Object.keys(truncated).length ? truncated : void 0\n };\n }\n const root = gArgs.css ? document.querySelector(gArgs.css) : document.body;\n if (!root) return err(ERROR_CODES.SELECTOR_NOT_FOUND, "CSS matches 0 elements.", { selector: gArgs.css });\n let html = root.outerHTML;\n if (gArgs.max && html.length > gArgs.max) html = html.slice(0, gArgs.max) + "\\u2026";\n return { value: html };\n }\n const r = resolveTargetRich(gArgs);\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level } = r;\n switch (what) {\n case "text": {\n const t = getVisibleText(el);\n return { value: t.length > 2e3 ? t.slice(0, 2e3) : t, matches_n, match_level };\n }\n case "value":\n return { value: el.value ?? el.innerText ?? null, matches_n, match_level };\n case "attributes":\n return { value: getAttrs(el), matches_n, match_level };\n default:\n return { error: `unknown_what: ${what}` };\n }\n }\n function extractMarkdown(eArgs) {\n const chunkSize = eArgs["chunk-size"] ?? 0;\n const start = eArgs.start ?? 0;\n const scope = eArgs.selector ? document.querySelector(eArgs.selector) : document.querySelector("main") || document.querySelector("article") || document.body;\n if (!scope) return { error: "scope_not_found" };\n const parts = [];\n const walker = document.createTreeWalker(scope, NodeFilter.SHOW_ELEMENT);\n let node;\n while (node = walker.nextNode()) {\n const el = node;\n const tag = el.tagName.toLowerCase();\n const text = getVisibleText(el);\n if (!text) continue;\n if (tag === "h1") parts.push(`# ${text}`);\n else if (tag === "h2") parts.push(`## ${text}`);\n else if (tag === "h3") parts.push(`### ${text}`);\n else if (tag === "p" || tag === "li") parts.push(text);\n else if (tag === "a") parts.push(`[${text}](${el.getAttribute("href") ?? ""})`);\n }\n const full = parts.join("\\n\\n");\n const sliced = chunkSize > 0 ? full.slice(start, start + chunkSize) : full.slice(start);\n return {\n url: location.href,\n title: document.title,\n selector: eArgs.selector ?? "auto",\n total_chars: full.length,\n chunk_size: chunkSize,\n start,\n end: start + sliced.length,\n next_start_char: start + sliced.length < full.length ? start + sliced.length : null,\n content: sliced\n };\n }\n function listFrames() {\n const frames = Array.from(document.querySelectorAll("iframe")).map((f, index) => {\n const src = f.getAttribute("src") ?? "";\n let crossOrigin = false;\n try {\n if (src && /^https?:/.test(src)) crossOrigin = new URL(src, location.href).origin !== location.origin;\n } catch {\n }\n return { index, url: src, name: f.getAttribute("name") ?? "", cross_origin: crossOrigin };\n });\n return { frames };\n }\n function screenshotMeta() {\n return {\n width: window.innerWidth,\n height: window.innerHeight,\n url: location.href,\n title: document.title\n };\n }\n function startTitleMarquee(tArgs) {\n const g = globalThis;\n if (g.__cpMarquee) {\n clearInterval(g.__cpMarquee.interval);\n } else {\n g.__cpMarquee = { original: document.title, interval: 0 };\n }\n const prefix = tArgs.text ?? "\\u{1F916}";\n const base = g.__cpMarquee.original;\n const full = `${prefix} ${base}`;\n const speed = Math.max(100, Math.min(2e3, tArgs.speed ?? 300));\n let pos = 0;\n const tick = () => {\n const display = full.slice(pos) + " " + full.slice(0, pos);\n document.title = display.slice(0, 60);\n pos = (pos + 1) % (full.length + 1 || 1);\n };\n tick();\n g.__cpMarquee.interval = window.setInterval(tick, speed);\n return { started: true, original: base, prefix, speed };\n }\n function stopTitleMarquee() {\n const g = globalThis;\n const state = g.__cpMarquee;\n if (!state) return { restored: false, reason: "no_active_marquee" };\n clearInterval(state.interval);\n document.title = state.original;\n delete g.__cpMarquee;\n return { restored: true, original: state.original };\n }\n function networkEntriesLegacy(nArgs) {\n const all = performance.getEntriesByType("resource").map((e) => ({\n url: e.name,\n type: e.initiatorType ?? "unknown",\n duration: Math.round(e.duration)\n }));\n let entries = all;\n if (nArgs.filter) {\n const keywords = nArgs.filter.split(",").map((s) => s.trim().toLowerCase());\n entries = all.filter((e) => keywords.some((kw) => e.url.toLowerCase().includes(kw)));\n }\n return { entries, count: entries.length, source: "performance-api" };\n }\n async function waitForCondition(wArgs) {\n const timeout = wArgs.timeout ?? 1e4;\n if (wArgs.time != null) {\n await new Promise((res) => setTimeout(res, Math.min(wArgs.time * 1e3, 3e4)));\n return { waited: true, kind: "time", seconds: wArgs.time };\n }\n const deadline = Date.now() + timeout;\n const pollInterval = 200;\n if (wArgs.selector) {\n while (Date.now() < deadline) {\n if (document.querySelector(wArgs.selector)) return { waited: true, kind: "selector", selector: wArgs.selector, elapsedMs: timeout - (deadline - Date.now()) };\n await new Promise((res) => setTimeout(res, pollInterval));\n }\n return err("wait_timeout", `Selector not found within ${timeout}ms.`, { selector: wArgs.selector });\n }\n if (wArgs.text) {\n const lower = wArgs.text.toLowerCase();\n while (Date.now() < deadline) {\n if ((document.body.textContent || "").toLowerCase().includes(lower)) {\n return { waited: true, kind: "text", text: wArgs.text, elapsedMs: timeout - (deadline - Date.now()) };\n }\n await new Promise((res) => setTimeout(res, pollInterval));\n }\n return err("wait_timeout", `Text not found within ${timeout}ms.`, { text: wArgs.text });\n }\n return { error: "no_condition" };\n }\n function uploadFiles(uArgs) {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n const input = el;\n if (input.type !== "file") return err("invalid_type", "Target is not an input[type=file].", { tag: el.tagName.toLowerCase() });\n try {\n const dt = new DataTransfer();\n for (const name of uArgs.files ?? []) {\n dt.items.add(new File([""], name.split("/").pop() || name));\n }\n input.files = dt.files;\n } catch {\n }\n input.dispatchEvent(new Event("change", { bubbles: true }));\n return {\n uploaded: true,\n files: uArgs.files ?? [],\n file_names: (uArgs.files ?? []).map((f) => f.split("/").pop()),\n target,\n matches_n,\n match_level,\n multiple: input.multiple,\n accept: input.accept || null\n };\n }\n function dragElements(dArgs) {\n const r1 = resolveTargetRich(sel());\n if ("errorEnvelope" in r1) return r1.errorEnvelope;\n const r2 = resolveTargetRich({ ref: dArgs.ref2, css: dArgs.css2 });\n if ("errorEnvelope" in r2) return r2.errorEnvelope;\n const src = r1.el;\n const tgt = r2.el;\n const make = (type) => {\n const r1rect = src.getBoundingClientRect();\n return new DragEvent(type, { bubbles: true, cancelable: true, clientX: r1rect.x + r1rect.width / 2, clientY: r1rect.y + r1rect.height / 2 });\n };\n src.dispatchEvent(make("dragstart"));\n tgt.dispatchEvent(new DragEvent("dragenter", { bubbles: true }));\n tgt.dispatchEvent(new DragEvent("dragover", { bubbles: true, cancelable: true }));\n tgt.dispatchEvent(new DragEvent("drop", { bubbles: true, cancelable: true }));\n src.dispatchEvent(new DragEvent("dragend", { bubbles: true }));\n return { dragged: true, source: String(sel().ref ?? sel().css ?? ""), target: String(dArgs.ref2 ?? dArgs.css2 ?? ""), source_matches_n: r1.matches_n, target_matches_n: r2.matches_n };\n }\n switch (command) {\n case "cp.state":\n return a.tree === false ? pageState() : buildStateTree();\n case "cp.snapshot":\n return buildStateTree();\n case "cp.find":\n return findElements({\n css: a.css,\n text: a.text,\n limit: a.limit,\n role: a.role,\n name: a.name,\n label: a.label,\n testid: a.testid,\n "text-max": a["text-max"]\n });\n case "cp.click":\n return clickElement();\n case "cp.type":\n return typeElement({ text: a.text });\n case "cp.goto":\n return goto({ url: a.url });\n case "cp.eval":\n return evalScript({ script: a.script });\n case "cp.fill":\n return fillElement({ text: a.text });\n case "cp.select":\n return selectElement({ label: a.label, value: a.value, option: a.option });\n case "cp.keys":\n return pressKeys({ keys: a.keys, key: a.key });\n case "cp.hover":\n return hoverElement();\n case "cp.focus":\n return focusElement();\n case "cp.dblclick":\n return dblclickElement();\n case "cp.check":\n return setChecked(true);\n case "cp.uncheck":\n return setChecked(false);\n case "cp.scroll":\n return scrollElement({ direction: a.direction, amount: a.amount });\n case "cp.back":\n return backNav();\n case "cp.get":\n return getElement({\n what: a.what,\n css: a.css,\n ref: a.ref,\n nth: a.nth,\n "as": a["as"],\n depth: a.depth,\n "children-max": a["children-max"],\n "text-max": a["text-max"],\n max: a.max,\n role: a.role,\n name: a.name,\n label: a.label,\n text: a.text,\n testid: a.testid\n });\n case "cp.extract":\n return extractMarkdown({\n "chunk-size": a["chunk-size"],\n start: a.start,\n selector: a.selector\n });\n case "cp.frames":\n return listFrames();\n case "cp.screenshot":\n return screenshotMeta();\n case "cp.network":\n return networkEntriesLegacy({ filter: a.filter });\n case "cp.wait":\n return waitForCondition({\n selector: a.selector,\n text: a.text,\n time: a.time,\n timeout: a.timeout\n });\n case "cp.upload":\n return uploadFiles({ files: a.files });\n case "cp.drag":\n return dragElements({ ref2: a.ref2, css2: a.css2 });\n case "cp.title.marquee":\n return startTitleMarquee({ text: a.text, speed: a.speed });\n case "cp.title.restore":\n return stopTitleMarquee();\n default:\n return { error: `unknown_command: ${command}` };\n }\n }\n var RUNTIME_VERSION = "0.3.0-opencli-parity";\n globalThis.__cpRunControl = runControl;\n globalThis.__cpRuntimeVersion = RUNTIME_VERSION;\n})();\n';
1003
+
1004
+ // src/background/cdp.ts
1005
+ var attached = /* @__PURE__ */ new Set();
1006
+ var CDP_COMMAND_TIMEOUT_MS = 15e3;
1007
+ function send(target, method, params, timeoutMs = CDP_COMMAND_TIMEOUT_MS) {
1008
+ return new Promise((resolve, reject) => {
1009
+ const timer = setTimeout(() => {
1010
+ if (target.tabId !== void 0) {
1011
+ chrome.debugger.detach({ tabId: target.tabId }).catch(() => void 0).then(() => {
1012
+ attached.delete(target.tabId);
1013
+ });
1014
+ }
1015
+ reject(new Error(`cdp_timeout: ${method} after ${timeoutMs}ms (tab frozen or unresponsive)`));
1016
+ }, timeoutMs);
1017
+ chrome.debugger.sendCommand(target, method, params ?? {}).then(
1018
+ (v) => {
1019
+ clearTimeout(timer);
1020
+ resolve(v);
1021
+ },
1022
+ (err3) => {
1023
+ clearTimeout(timer);
1024
+ reject(err3);
1025
+ }
1026
+ );
1027
+ });
1028
+ }
1029
+ function isAttachableUrl(url) {
1030
+ if (!url) return false;
1031
+ return /^(https?|file|chrome-extension):/i.test(url) || /^chrome:\/\/extensions/i.test(url);
1032
+ }
1033
+ var networkCaptures = /* @__PURE__ */ new Map();
1034
+ var networkListenerRegistered = false;
1035
+ var networkEnabledTabs = /* @__PURE__ */ new Set();
1036
+ function netKey(tabId, seq, url) {
1037
+ try {
1038
+ const u = new URL(url);
1039
+ const seg = u.pathname.split("/").filter(Boolean).pop() || u.hostname;
1040
+ return `${seg.slice(0, 24)}-${tabId}${seq}`;
1041
+ } catch {
1042
+ return `req-${tabId}${seq}`;
1043
+ }
1044
+ }
1045
+ function registerNetworkListener() {
1046
+ if (networkListenerRegistered) return;
1047
+ networkListenerRegistered = true;
1048
+ chrome.debugger.onEvent.addListener((source, method, params) => {
1049
+ const tabId = source.tabId;
1050
+ if (tabId === void 0) return;
1051
+ const state2 = networkCaptures.get(tabId);
1052
+ if (method === "Network.requestWillBeSent" && state2) {
1053
+ const p = params;
1054
+ const key = netKey(tabId, ++state2.seq, p.request.url);
1055
+ const entry = {
1056
+ key,
1057
+ requestId: p.requestId,
1058
+ method: p.request.method,
1059
+ url: p.request.url,
1060
+ status: null,
1061
+ ct: "",
1062
+ size: null,
1063
+ finished: false,
1064
+ resourceType: p.type ?? "Other",
1065
+ timestamp: Date.now(),
1066
+ postData: p.request.postData?.slice(0, 4e3)
1067
+ };
1068
+ state2.entries.push(entry);
1069
+ state2.byRequestId.set(p.requestId, entry);
1070
+ }
1071
+ if (method === "Network.responseReceived" && state2) {
1072
+ const p = params;
1073
+ const entry = state2.byRequestId.get(p.requestId);
1074
+ if (entry) {
1075
+ entry.status = p.response?.status ?? null;
1076
+ entry.ct = p.response?.mimeType ?? "";
1077
+ entry.resourceType = p.type ?? entry.resourceType;
1078
+ const len = p.response?.headers?.["content-length"] ?? p.response?.headers?.["Content-Length"];
1079
+ if (len) entry.size = Number(len);
1080
+ }
1081
+ }
1082
+ if ((method === "Network.loadingFinished" || method === "Network.loadingFailed") && state2) {
1083
+ const p = params;
1084
+ const entry = state2.byRequestId.get(p.requestId);
1085
+ if (entry) {
1086
+ entry.finished = true;
1087
+ if (p.encodedDataLength != null && entry.size == null) entry.size = p.encodedDataLength;
1088
+ }
1089
+ }
1090
+ });
1091
+ chrome.tabs.onRemoved?.addListener((tabId) => {
1092
+ networkCaptures.delete(tabId);
1093
+ networkEnabledTabs.delete(tabId);
1094
+ });
1095
+ }
1096
+ async function ensureNetworkEnabled(tabId) {
1097
+ if (networkEnabledTabs.has(tabId)) return;
1098
+ await send({ tabId }, "Network.enable");
1099
+ networkEnabledTabs.add(tabId);
1100
+ }
1101
+ async function readNetworkEntries(tabId, opts) {
1102
+ registerNetworkListener();
1103
+ await ensureAttached(tabId);
1104
+ await ensureNetworkEnabled(tabId);
1105
+ if (!networkCaptures.has(tabId)) {
1106
+ networkCaptures.set(tabId, { seq: 0, entries: [], byRequestId: /* @__PURE__ */ new Map() });
1107
+ }
1108
+ await new Promise((r) => setTimeout(r, 300));
1109
+ const state2 = networkCaptures.get(tabId);
1110
+ let entries = state2.entries.slice();
1111
+ if (!opts.all) {
1112
+ entries = entries.filter(
1113
+ (e) => ["XHR", "Fetch"].includes(e.resourceType) || /\/api\/|json/i.test(e.ct + e.url)
1114
+ );
1115
+ }
1116
+ if (opts.filter) {
1117
+ const lower = opts.filter.toLowerCase();
1118
+ entries = entries.filter((e) => e.url.toLowerCase().includes(lower));
1119
+ }
1120
+ return {
1121
+ source: "cdp",
1122
+ entries: entries.slice(-100).map(({ key, method, status, url, ct, size, resourceType }) => ({
1123
+ key,
1124
+ method,
1125
+ status,
1126
+ url,
1127
+ ct,
1128
+ size,
1129
+ type: resourceType
1130
+ })),
1131
+ count: entries.length,
1132
+ capture_running: true
1133
+ };
1134
+ }
1135
+ async function readNetworkDetail(tabId, key) {
1136
+ registerNetworkListener();
1137
+ await ensureAttached(tabId);
1138
+ await ensureNetworkEnabled(tabId);
1139
+ const state2 = networkCaptures.get(tabId);
1140
+ const entry = state2?.entries.find((e) => e.key === key || e.requestId === key);
1141
+ if (!entry) return { error: "network_key_not_found", key, hint: "run cp.network first; keys expire on page navigation" };
1142
+ const out = {
1143
+ key: entry.key,
1144
+ method: entry.method,
1145
+ status: entry.status,
1146
+ url: entry.url,
1147
+ ct: entry.ct,
1148
+ size: entry.size,
1149
+ request_post_data: entry.postData
1150
+ };
1151
+ if (entry.status != null && entry.status !== 204) {
1152
+ try {
1153
+ const body = await send({ tabId }, "Network.getResponseBody", { requestId: entry.requestId });
1154
+ if (body?.body != null) {
1155
+ const limit = 200 * 1024;
1156
+ out.response_body_truncated = body.body.length > limit;
1157
+ out.response_body = body.body.slice(0, limit);
1158
+ out.base64_encoded = !!body.base64Encoded;
1159
+ }
1160
+ } catch {
1161
+ out.response_body = null;
1162
+ out.note = "body evicted (resource unloaded or navigated)";
1163
+ }
1164
+ }
1165
+ return out;
1166
+ }
1167
+ var tabFrameContexts = /* @__PURE__ */ new Map();
1168
+ var frameTrackingRegistered = false;
1169
+ function registerFrameTracking() {
1170
+ if (frameTrackingRegistered) return;
1171
+ frameTrackingRegistered = true;
1172
+ chrome.debugger.onEvent.addListener((source, method, params) => {
1173
+ const tabId = source.tabId;
1174
+ if (tabId === void 0) return;
1175
+ if (method === "Runtime.executionContextCreated") {
1176
+ const ctx = params.context;
1177
+ if (!ctx?.auxData?.frameId || ctx.auxData.isDefault !== true) return;
1178
+ if (!tabFrameContexts.has(tabId)) tabFrameContexts.set(tabId, /* @__PURE__ */ new Map());
1179
+ tabFrameContexts.get(tabId).set(ctx.auxData.frameId, ctx.id);
1180
+ }
1181
+ if (method === "Runtime.executionContextDestroyed") {
1182
+ const ctxId = params.executionContextId;
1183
+ const contexts = tabFrameContexts.get(tabId);
1184
+ if (contexts) {
1185
+ for (const [fid, cid] of contexts) {
1186
+ if (cid === ctxId) {
1187
+ contexts.delete(fid);
1188
+ break;
1189
+ }
1190
+ }
1191
+ }
1192
+ }
1193
+ });
1194
+ chrome.tabs.onRemoved?.addListener((tabId) => tabFrameContexts.delete(tabId));
1195
+ }
1196
+ async function getFrameTree(tabId) {
1197
+ await ensureAttached(tabId);
1198
+ return send({ tabId }, "Page.getFrameTree");
1199
+ }
1200
+ var frameTargets = /* @__PURE__ */ new Map();
1201
+ var frameTargetCleanup = false;
1202
+ function registerFrameTargetCleanup() {
1203
+ if (frameTargetCleanup) return;
1204
+ frameTargetCleanup = true;
1205
+ chrome.debugger.onEvent.addListener((_source, method, params) => {
1206
+ if (method === "Target.detachedFromTarget") {
1207
+ const targetId = String(params.targetId || "");
1208
+ if (targetId && frameTargets.get("__byTarget__" + targetId)) {
1209
+ frameTargets.delete("__byTarget__" + targetId);
1210
+ }
1211
+ }
1212
+ });
1213
+ }
1214
+ async function ensureFrameTarget(tabId, frameId, targetUrl) {
1215
+ registerFrameTargetCleanup();
1216
+ await ensureAttached(tabId);
1217
+ const key = `${tabId}:${frameId}`;
1218
+ const existing = frameTargets.get(key);
1219
+ if (existing) return existing;
1220
+ await send({ tabId }, "Target.setDiscoverTargets", { discover: true }).catch(() => void 0);
1221
+ await send({ tabId }, "Target.setAutoAttach", {
1222
+ autoAttach: true,
1223
+ waitForDebuggerOnStart: false,
1224
+ flatten: true,
1225
+ filter: [{ type: "iframe", exclude: false }]
1226
+ }).catch(() => void 0);
1227
+ const result = await send({ tabId }, "Target.getTargets").catch(() => null);
1228
+ const targets = result?.targetInfos ?? [];
1229
+ const frameTarget = targets.find((c) => {
1230
+ const cid = c.targetId || c.id;
1231
+ return c.type === "iframe" && (cid === frameId || !!targetUrl && c.url === targetUrl);
1232
+ });
1233
+ const targetId = frameTarget?.targetId || frameTarget?.id;
1234
+ if (!targetId) {
1235
+ throw new Error(`No iframe target found for frame ${frameId}${targetUrl ? ` (${targetUrl})` : ""}`);
1236
+ }
1237
+ try {
1238
+ await chrome.debugger.attach({ targetId }, "1.3");
1239
+ } catch (e) {
1240
+ const message = e instanceof Error ? e.message : String(e);
1241
+ if (!message.includes("Another debugger is already attached")) throw e;
1242
+ }
1243
+ frameTargets.set(key, targetId);
1244
+ frameTargets.set("__byTarget__" + targetId, key);
1245
+ return targetId;
1246
+ }
1247
+ async function evaluateInFrame(tabId, expression, frameId, targetUrl) {
1248
+ registerFrameTracking();
1249
+ await ensureAttached(tabId);
1250
+ await send({ tabId }, "Runtime.enable").catch(() => void 0);
1251
+ let contexts = tabFrameContexts.get(tabId);
1252
+ let contextId = contexts?.get(frameId);
1253
+ if (contextId === void 0) {
1254
+ await new Promise((r) => setTimeout(r, 500));
1255
+ contexts = tabFrameContexts.get(tabId);
1256
+ contextId = contexts?.get(frameId);
1257
+ }
1258
+ if (contextId !== void 0) {
1259
+ try {
1260
+ const result2 = await send({ tabId }, "Runtime.evaluate", {
1261
+ expression,
1262
+ contextId,
1263
+ returnByValue: true,
1264
+ awaitPromise: true
1265
+ });
1266
+ if (result2.exceptionDetails) {
1267
+ throw new Error(result2.exceptionDetails.exception?.description || result2.exceptionDetails.text || "Eval error");
1268
+ }
1269
+ return result2.result?.value;
1270
+ } catch (e) {
1271
+ const msg = String(e?.message || e);
1272
+ if (!/Cannot find context|context with specified id|Execution context was destroyed/i.test(msg)) {
1273
+ throw e;
1274
+ }
1275
+ contexts?.delete(frameId);
1276
+ }
1277
+ }
1278
+ await send({ tabId }, "Runtime.enable").catch(() => void 0);
1279
+ const targetId = await ensureFrameTarget(tabId, frameId, targetUrl);
1280
+ const result = await send({ targetId }, "Runtime.evaluate", {
1281
+ expression,
1282
+ returnByValue: true,
1283
+ awaitPromise: true
1284
+ });
1285
+ if (result.exceptionDetails) {
1286
+ throw new Error(result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Eval error");
1287
+ }
1288
+ return result.result?.value;
1289
+ }
1290
+ async function waitForDownload(pattern, timeoutMs) {
1291
+ const startedAt = Date.now();
1292
+ const lower = pattern?.toLowerCase();
1293
+ return new Promise((resolve) => {
1294
+ const timer = setTimeout(() => {
1295
+ chrome.downloads.onChanged.removeListener(listener);
1296
+ resolve({ error: "wait_timeout", message: `No matching download within ${timeoutMs}ms.`, pattern: pattern ?? null });
1297
+ }, timeoutMs);
1298
+ const listener = (_delta) => {
1299
+ chrome.downloads.search({ limit: 5, orderBy: ["-startTime"] }, (items) => {
1300
+ const match = items.find((it) => {
1301
+ if (it.state !== "complete") return false;
1302
+ const hay = `${it.filename ?? ""} ${it.url ?? ""} ${it.mime ?? ""}`.toLowerCase();
1303
+ return !lower || hay.includes(lower);
1304
+ });
1305
+ if (match) {
1306
+ clearTimeout(timer);
1307
+ chrome.downloads.onChanged.removeListener(listener);
1308
+ resolve({
1309
+ downloaded: true,
1310
+ file: (match.filename ?? "").split("/").pop() ?? match.filename,
1311
+ path: match.filename,
1312
+ url: match.url,
1313
+ mime: match.mime,
1314
+ bytes: match.fileSize,
1315
+ elapsedMs: Date.now() - startedAt
1316
+ });
1317
+ }
1318
+ });
1319
+ };
1320
+ chrome.downloads.onChanged.addListener(listener);
1321
+ listener({});
1322
+ });
1323
+ }
1324
+ async function setFileInputFiles(tabId, cssPath, files) {
1325
+ await ensureAttached(tabId);
1326
+ const doc = await send({ tabId }, "DOM.getDocument", { depth: 0 });
1327
+ const rootNodeId = doc.root?.nodeId;
1328
+ if (!rootNodeId) return { error: "dom_get_document_failed" };
1329
+ const node = await send({ tabId }, "DOM.querySelector", {
1330
+ nodeId: rootNodeId,
1331
+ selector: cssPath
1332
+ });
1333
+ if (!node || node.nodeId === void 0 || node.nodeId === 0) {
1334
+ return { error: "selector_not_found", selector: cssPath };
1335
+ }
1336
+ await send({ tabId }, "DOM.setFileInputFiles", { files, nodeId: node.nodeId });
1337
+ return { uploaded: true, files, via: "cdp-dom" };
1338
+ }
1339
+ async function ensureAttached(tabId) {
1340
+ registerFrameTracking();
1341
+ registerNetworkListener();
1342
+ if (attached.has(tabId)) return;
1343
+ const target = { tabId };
1344
+ try {
1345
+ await chrome.debugger.attach(target, "1.3");
1346
+ } catch (e) {
1347
+ throw new Error(`attach failed: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
1348
+ }
1349
+ attached.add(tabId);
1350
+ try {
1351
+ await send(target, "Runtime.enable");
1352
+ } catch {
1353
+ }
1354
+ }
1355
+ chrome.debugger.onDetach.addListener((source) => {
1356
+ if (source.tabId != null && source.tabId !== void 0) {
1357
+ attached.delete(source.tabId);
1358
+ networkEnabledTabs.delete(source.tabId);
1359
+ }
1360
+ });
1361
+ async function detachIfAttached(tabId) {
1362
+ if (!attached.has(tabId)) return;
1363
+ try {
1364
+ await chrome.debugger.detach({ tabId });
1365
+ } catch {
1366
+ }
1367
+ attached.delete(tabId);
1368
+ networkEnabledTabs.delete(tabId);
1369
+ }
1370
+ chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
1371
+ if (changeInfo.status !== "loading") return;
1372
+ attached.delete(tabId);
1373
+ networkEnabledTabs.delete(tabId);
1374
+ chrome.debugger.detach({ tabId }).catch(() => void 0);
1375
+ });
1376
+ var CDP_ALLOWLIST = /* @__PURE__ */ new Set([
1377
+ "Accessibility.enable",
1378
+ "Accessibility.getFullAXTree",
1379
+ "DOM.enable",
1380
+ "DOM.getDocument",
1381
+ "DOM.getBoxModel",
1382
+ "DOM.getContentQuads",
1383
+ "DOM.focus",
1384
+ "DOM.querySelector",
1385
+ "DOM.querySelectorAll",
1386
+ "DOM.scrollIntoViewIfNeeded",
1387
+ "DOMSnapshot.captureSnapshot",
1388
+ "Input.dispatchMouseEvent",
1389
+ "Input.dispatchKeyEvent",
1390
+ "Input.insertText",
1391
+ "Page.getLayoutMetrics",
1392
+ "Page.captureScreenshot",
1393
+ "Page.getFrameTree",
1394
+ "Page.handleJavaScriptDialog",
1395
+ "Runtime.enable",
1396
+ "Emulation.setDeviceMetricsOverride",
1397
+ "Emulation.clearDeviceMetricsOverride"
1398
+ ]);
1399
+ async function sendRawCommand(tabId, method, params) {
1400
+ if (!CDP_ALLOWLIST.has(method)) {
1401
+ throw new Error(`cdp_method_not_permitted: ${method}`);
1402
+ }
1403
+ await ensureAttached(tabId);
1404
+ return send({ tabId }, method, params);
1405
+ }
1406
+ async function insertText(tabId, text) {
1407
+ await ensureAttached(tabId);
1408
+ await send({ tabId }, "Input.insertText", { text });
1409
+ }
1410
+ async function cdpEvaluate(tabId, expression, timeoutMs = CDP_COMMAND_TIMEOUT_MS) {
1411
+ await ensureAttached(tabId);
1412
+ const result = await send(
1413
+ { tabId },
1414
+ "Runtime.evaluate",
1415
+ { expression, returnByValue: true, awaitPromise: true },
1416
+ timeoutMs
1417
+ );
1418
+ if (result.exceptionDetails) {
1419
+ const errMsg = result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Eval error";
1420
+ throw new Error(errMsg);
1421
+ }
1422
+ return result.result?.value;
1423
+ }
1424
+ async function cdpEvaluateTransient(tabId, expression, timeoutMs = CDP_COMMAND_TIMEOUT_MS) {
1425
+ try {
1426
+ return await cdpEvaluate(tabId, expression, timeoutMs);
1427
+ } finally {
1428
+ await detachIfAttached(tabId);
1429
+ }
1430
+ }
1431
+
1432
+ // src/background/capture-visible-tab.ts
1433
+ var DEFAULT_CAPTURE_TIMEOUT_MS = 4e3;
1434
+ var MAX_ATTEMPTS = 3;
1435
+ var BACKOFF_MS = 300;
1436
+ var defaultDependencies = {
1437
+ capture: (windowId) => chrome.tabs.captureVisibleTab(windowId, { format: "png" }),
1438
+ focusWindow: (windowId) => chrome.windows.update(windowId, { focused: true }),
1439
+ activateTab: (tabId) => chrome.tabs.update(tabId, { active: true }),
1440
+ wait: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
1441
+ getWindow: (windowId) => chrome.windows.get(windowId),
1442
+ getTab: (tabId) => chrome.tabs.get(tabId)
1443
+ };
1444
+ async function captureWithTimeout(windowId, dependencies, timeoutMs) {
1445
+ let timer;
1446
+ try {
1447
+ return await Promise.race([
1448
+ dependencies.capture(windowId),
1449
+ new Promise((_, reject) => {
1450
+ timer = setTimeout(() => reject(new Error(`capture_timeout:${timeoutMs}ms`)), timeoutMs);
1451
+ })
1452
+ ]);
1453
+ } finally {
1454
+ if (timer) clearTimeout(timer);
1455
+ }
1456
+ }
1457
+ async function prepareCompositor(tab, dependencies) {
1458
+ await dependencies.focusWindow(tab.windowId);
1459
+ await dependencies.activateTab(tab.id);
1460
+ await dependencies.wait(150);
1461
+ }
1462
+ async function ensureTargetTabActive(tab, dependencies, options) {
1463
+ try {
1464
+ const info = await dependencies.getTab?.(tab.id);
1465
+ if (info && info.active === false) {
1466
+ logCapture(options, "info", "activating target tab before capture", {
1467
+ tabId: tab.id,
1468
+ windowId: tab.windowId
1469
+ });
1470
+ await dependencies.activateTab(tab.id);
1471
+ await dependencies.wait(100);
1472
+ }
1473
+ } catch {
1474
+ }
1475
+ }
1476
+ function logCapture(opts, level, message, meta) {
1477
+ opts?.onLog?.(level, message, meta);
1478
+ }
1479
+ async function classifyCaptureError(err3, tab, dependencies) {
1480
+ const message = err3 instanceof Error ? err3.message : String(err3);
1481
+ const lower = message.toLowerCase();
1482
+ if (lower.includes("permission denied") || lower.includes("cannot access contents") || lower.includes("activetab") || lower.includes("extension has not been invoked") || lower.includes("'<all_urls>'") || lower.includes("<all_urls>")) {
1483
+ return {
1484
+ type: "permission_denied",
1485
+ recoverableWithoutFocus: false,
1486
+ recoverableWithFocus: false,
1487
+ message
1488
+ };
1489
+ }
1490
+ if (lower.includes("rate") || lower.includes("maximum number of screenshots") || lower.includes("quota")) {
1491
+ return {
1492
+ type: "rate_limited",
1493
+ recoverableWithoutFocus: false,
1494
+ recoverableWithFocus: false,
1495
+ message
1496
+ };
1497
+ }
1498
+ if (lower.includes("image readback failed")) {
1499
+ return {
1500
+ type: "image_readback_failed",
1501
+ recoverableWithoutFocus: true,
1502
+ recoverableWithFocus: true,
1503
+ message
1504
+ };
1505
+ }
1506
+ if (lower.startsWith("capture_timeout:")) {
1507
+ return {
1508
+ type: "timeout",
1509
+ recoverableWithoutFocus: true,
1510
+ recoverableWithFocus: true,
1511
+ message
1512
+ };
1513
+ }
1514
+ try {
1515
+ const [win, tabInfo] = await Promise.all([
1516
+ dependencies.getWindow?.(tab.windowId),
1517
+ dependencies.getTab?.(tab.id)
1518
+ ]);
1519
+ if (win?.state === "minimized") {
1520
+ return {
1521
+ type: "window_minimized",
1522
+ recoverableWithoutFocus: false,
1523
+ recoverableWithFocus: true,
1524
+ message
1525
+ };
1526
+ }
1527
+ if (tabInfo && !tabInfo.active) {
1528
+ return {
1529
+ type: "tab_not_visible",
1530
+ recoverableWithoutFocus: false,
1531
+ recoverableWithFocus: true,
1532
+ message
1533
+ };
1534
+ }
1535
+ } catch {
1536
+ }
1537
+ return {
1538
+ type: "unknown",
1539
+ recoverableWithoutFocus: false,
1540
+ recoverableWithFocus: false,
1541
+ message
1542
+ };
1543
+ }
1544
+ async function captureActiveTabScreenshot2(tab, options = {}) {
1545
+ const { dependencies = defaultDependencies, timeoutMs = DEFAULT_CAPTURE_TIMEOUT_MS, stealFocus = false } = options;
1546
+ const attemptErrors = [];
1547
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
1548
+ const isLastAttempt = attempt === MAX_ATTEMPTS;
1549
+ const shouldPrepareCompositor = isLastAttempt && stealFocus;
1550
+ try {
1551
+ if (shouldPrepareCompositor) {
1552
+ logCapture(options, "info", "capture preparing compositor before attempt", {
1553
+ attempt,
1554
+ tabId: tab.id,
1555
+ windowId: tab.windowId
1556
+ });
1557
+ await prepareCompositor(tab, dependencies);
1558
+ }
1559
+ logCapture(options, "debug", "capture attempt", {
1560
+ attempt,
1561
+ maxAttempts: MAX_ATTEMPTS,
1562
+ tabId: tab.id,
1563
+ windowId: tab.windowId,
1564
+ stealFocus,
1565
+ focusUsed: shouldPrepareCompositor
1566
+ });
1567
+ await ensureTargetTabActive(tab, dependencies, options);
1568
+ const startedAt = Date.now();
1569
+ const result = await captureWithTimeout(tab.windowId, dependencies, timeoutMs);
1570
+ logCapture(options, "info", "capture succeeded", {
1571
+ attempt,
1572
+ tabId: tab.id,
1573
+ windowId: tab.windowId,
1574
+ durationMs: Date.now() - startedAt
1575
+ });
1576
+ return result;
1577
+ } catch (err3) {
1578
+ const info = await classifyCaptureError(err3, tab, dependencies);
1579
+ attemptErrors.push({ attempt, type: info.type, message: info.message });
1580
+ const canRetryWithoutFocus = !isLastAttempt && info.recoverableWithoutFocus;
1581
+ const canRetryWithFocus = !isLastAttempt && info.recoverableWithFocus && stealFocus;
1582
+ const shouldRetry = canRetryWithoutFocus || canRetryWithFocus;
1583
+ logCapture(options, "warn", "capture attempt failed", {
1584
+ attempt,
1585
+ maxAttempts: MAX_ATTEMPTS,
1586
+ tabId: tab.id,
1587
+ windowId: tab.windowId,
1588
+ errorType: info.type,
1589
+ error: info.message,
1590
+ recoverableWithoutFocus: info.recoverableWithoutFocus,
1591
+ recoverableWithFocus: info.recoverableWithFocus,
1592
+ stealFocus,
1593
+ willRetry: shouldRetry
1594
+ });
1595
+ if (!shouldRetry) {
1596
+ const finalMessage = `capture_failed_after_${attempt}_attempts: ${info.type}: ${info.message}; attempts=${JSON.stringify(attemptErrors)}`;
1597
+ const finalErr = new Error(finalMessage);
1598
+ finalErr.errorType = info.type;
1599
+ throw finalErr;
1600
+ }
1601
+ await dependencies.wait(BACKOFF_MS);
1602
+ }
1603
+ }
1604
+ throw new Error("capture_failed_unreachable");
1605
+ }
1606
+
1607
+ // src/shared/eval-wrap.ts
1608
+ function buildEvalSyntaxCheckExpression(script) {
1609
+ return "(() => { try { new Function(" + JSON.stringify("return (async () => (" + script + "\n))();") + "); return true; } catch (e) { return false; } })()";
1610
+ }
1611
+ function buildEvalExpressionWrapper(script) {
1612
+ return `(async () => { return (
1613
+ ${script}
1614
+ ); })()`;
1615
+ }
1616
+ function buildEvalStatementWrapper(script) {
1617
+ return `(async () => {
1618
+ ${script}
1619
+ })()`;
1620
+ }
1621
+
1622
+ // src/background/session-groups.ts
1623
+ var STORAGE_KEY = "hmSessionGroups";
1624
+ var cache = /* @__PURE__ */ new Map();
1625
+ var hydrated = false;
1626
+ async function hydrate() {
1627
+ if (hydrated) return;
1628
+ try {
1629
+ const stored = await chrome.storage.session.get(STORAGE_KEY);
1630
+ const state2 = stored[STORAGE_KEY] ?? {};
1631
+ cache.clear();
1632
+ for (const [sessionId, entries] of Object.entries(state2)) {
1633
+ cache.set(sessionId, entries.map((e) => ({ ...e })));
1634
+ }
1635
+ } catch (err3) {
1636
+ console.warn("[HunterMate] failed to hydrate session groups", err3);
1637
+ } finally {
1638
+ hydrated = true;
1639
+ }
1640
+ }
1641
+ async function persist() {
1642
+ const state2 = {};
1643
+ for (const [sessionId, entries] of cache.entries()) {
1644
+ state2[sessionId] = entries.map((e) => ({ ...e }));
1645
+ }
1646
+ try {
1647
+ await chrome.storage.session.set({ [STORAGE_KEY]: state2 });
1648
+ } catch (err3) {
1649
+ console.warn("[HunterMate] failed to persist session groups", err3);
1650
+ }
1651
+ }
1652
+ function getSessionEntries(sessionId) {
1653
+ return cache.get(sessionId) ?? [];
1654
+ }
1655
+ async function setSessionEntries(sessionId, entries) {
1656
+ if (entries.length === 0) {
1657
+ cache.delete(sessionId);
1658
+ } else {
1659
+ cache.set(sessionId, entries);
1660
+ }
1661
+ await persist();
1662
+ }
1663
+ function hasExplicitTabTarget(args) {
1664
+ return ["tab", "tabId", "page", "targetId"].some((k) => args[k] !== void 0);
1665
+ }
1666
+ function buildGroupTitle(args, sessionId) {
1667
+ const raw = String(args.title ?? args.summary ?? args.taskTitle ?? args.taskName ?? "").trim();
1668
+ const compact = raw ? raw.replace(/\s+/g, " ").slice(0, 12) : sessionId.slice(0, 8);
1669
+ return `\u{1F916}${compact}`;
1670
+ }
1671
+ async function ensureSessionGroup(sessionId, title, tab) {
1672
+ await hydrate();
1673
+ if (!tab.id || tab.windowId == null) {
1674
+ throw new Error("ensureSessionGroup_requires_tab_id_and_window_id");
1675
+ }
1676
+ const entries = getSessionEntries(sessionId);
1677
+ const existing = entries.find((e) => e.windowId === tab.windowId);
1678
+ if (existing) {
1679
+ try {
1680
+ await chrome.tabGroups.get(existing.groupId);
1681
+ } catch {
1682
+ const cleaned = entries.filter((e) => e.groupId !== existing.groupId);
1683
+ await setSessionEntries(sessionId, cleaned);
1684
+ return ensureSessionGroup(sessionId, title, tab);
1685
+ }
1686
+ if (tab.groupId !== existing.groupId) {
1687
+ try {
1688
+ await chrome.tabs.group({ groupId: existing.groupId, tabIds: [tab.id] });
1689
+ } catch (err3) {
1690
+ console.warn("[HunterMate] failed to add tab to existing session group", { sessionId, groupId: existing.groupId, tabId: tab.id, err: err3 });
1691
+ }
1692
+ }
1693
+ if (title && title !== existing.title) {
1694
+ try {
1695
+ await chrome.tabGroups.update(existing.groupId, { title });
1696
+ existing.title = title;
1697
+ await persist();
1698
+ } catch (err3) {
1699
+ console.warn("[HunterMate] failed to update session group title", { sessionId, groupId: existing.groupId, err: err3 });
1700
+ }
1701
+ }
1702
+ if (!existing.tabIds.includes(tab.id)) {
1703
+ existing.tabIds.push(tab.id);
1704
+ await persist();
1705
+ }
1706
+ return { groupId: existing.groupId, created: false };
1707
+ }
1708
+ let groupId;
1709
+ try {
1710
+ groupId = await chrome.tabs.group({ tabIds: [tab.id] });
1711
+ } catch (err3) {
1712
+ console.warn("[HunterMate] failed to create session group", { sessionId, tabId: tab.id, err: err3 });
1713
+ throw err3;
1714
+ }
1715
+ try {
1716
+ await chrome.tabGroups.update(groupId, {
1717
+ title,
1718
+ color: "grey",
1719
+ collapsed: false
1720
+ });
1721
+ } catch (err3) {
1722
+ console.warn("[HunterMate] failed to update newly created session group", { sessionId, groupId, err: err3 });
1723
+ }
1724
+ const entry = {
1725
+ groupId,
1726
+ windowId: tab.windowId,
1727
+ title,
1728
+ tabIds: [tab.id],
1729
+ createdAt: Date.now()
1730
+ };
1731
+ entries.push(entry);
1732
+ await setSessionEntries(sessionId, entries);
1733
+ return { groupId, created: true };
1734
+ }
1735
+ async function addTabToSessionGroup(sessionId, title, tab) {
1736
+ if (!sessionId || !tab.id || tab.windowId == null) {
1737
+ return { created: false };
1738
+ }
1739
+ try {
1740
+ const result = await ensureSessionGroup(sessionId, title, tab);
1741
+ return result;
1742
+ } catch (err3) {
1743
+ console.warn("[HunterMate] addTabToSessionGroup failed", { sessionId, tabId: tab.id, err: err3 });
1744
+ return { created: false };
1745
+ }
1746
+ }
1747
+ async function evictClosedTab(tabId) {
1748
+ await hydrate();
1749
+ let dirty = false;
1750
+ for (const [sessionId, entries] of cache.entries()) {
1751
+ for (const entry of entries) {
1752
+ const before = entry.tabIds.length;
1753
+ entry.tabIds = entry.tabIds.filter((id) => id !== tabId);
1754
+ if (entry.tabIds.length !== before) {
1755
+ dirty = true;
1756
+ }
1757
+ }
1758
+ const cleaned = entries.filter((e) => e.tabIds.length > 0);
1759
+ if (cleaned.length !== entries.length) {
1760
+ dirty = true;
1761
+ }
1762
+ cache.set(sessionId, cleaned);
1763
+ }
1764
+ if (dirty) {
1765
+ await persist();
1766
+ }
1767
+ }
1768
+ chrome.tabs.onRemoved.addListener((tabId) => {
1769
+ evictClosedTab(tabId).catch((err3) => console.warn("[HunterMate] evictClosedTab failed", err3));
1770
+ });
1771
+
1772
+ // src/background/control.ts
1773
+ var boundTabId = null;
1774
+ async function getActiveTab() {
1775
+ const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
1776
+ return tabs[0] || null;
1777
+ }
1778
+ async function getControlTab(tabOverride) {
1779
+ if (typeof tabOverride === "string" || typeof tabOverride === "number") {
1780
+ const tabId = Number(tabOverride);
1781
+ if (Number.isFinite(tabId)) {
1782
+ const tab = await chrome.tabs.get(tabId).catch(() => null);
1783
+ if (tab) return tab;
1784
+ }
1785
+ }
1786
+ if (boundTabId != null) {
1787
+ const tab = await chrome.tabs.get(boundTabId).catch(() => null);
1788
+ if (tab) return tab;
1789
+ boundTabId = null;
1790
+ }
1791
+ return getActiveTab();
1792
+ }
1793
+ async function frameListCdp(tabId) {
1794
+ const tree = await getFrameTree(tabId);
1795
+ const mainUrl = tree?.frameTree?.frame?.url ?? "";
1796
+ const out = [];
1797
+ const walk = (node) => {
1798
+ const f = node?.frame;
1799
+ if (!f) return;
1800
+ let cross = false;
1801
+ try {
1802
+ cross = new URL(f.url ?? "about:blank").origin !== new URL(mainUrl || "about:blank").origin;
1803
+ } catch {
1804
+ }
1805
+ out.push({ index: out.length, frame_id: f.id ?? "", url: f.url ?? "", name: f.name ?? "", cross_origin: cross });
1806
+ for (const child of node?.childFrames ?? []) {
1807
+ walk(child);
1808
+ }
1809
+ };
1810
+ walk(tree?.frameTree);
1811
+ return { frames: out };
1812
+ }
1813
+ function buildRuntimeExpression(command, args) {
1814
+ return '(() => { try { if (typeof globalThis.__cpRunControl !== "function" || globalThis.__cpRuntimeVersion !== ' + JSON.stringify(RUNTIME_VERSION) + ") {\n" + control_runtime_source_default + "\n} const r = globalThis.__cpRunControl(" + JSON.stringify(command) + ", " + JSON.stringify(args) + '); if (r && typeof r.then === "function") { return r; } return r; } catch (e) { return { __cpError: String((e && e.message) || e) }; } })()';
1815
+ }
1816
+ var marqueeCounters = /* @__PURE__ */ new Map();
1817
+ async function startTitleMarquee(tabId, sessionId, text) {
1818
+ if (!isAttachableUrl((await chrome.tabs.get(tabId).catch(() => null))?.url)) {
1819
+ return;
1820
+ }
1821
+ const entry = marqueeCounters.get(tabId);
1822
+ if (entry) {
1823
+ entry.count++;
1824
+ return;
1825
+ }
1826
+ marqueeCounters.set(tabId, { count: 1, sessionId });
1827
+ try {
1828
+ const expression = buildRuntimeExpression("cp.title.marquee", { text: text ?? "\u{1F916}" });
1829
+ await cdpEvaluateTransient(tabId, expression);
1830
+ } catch (err3) {
1831
+ console.warn("[HunterMate] startTitleMarquee failed", { tabId, error: err3.message });
1832
+ }
1833
+ }
1834
+ async function stopTitleMarquee(tabId, _sessionId) {
1835
+ const entry = marqueeCounters.get(tabId);
1836
+ if (!entry) return;
1837
+ entry.count--;
1838
+ if (entry.count > 0) return;
1839
+ marqueeCounters.delete(tabId);
1840
+ try {
1841
+ const expression = buildRuntimeExpression("cp.title.restore", {});
1842
+ await cdpEvaluateTransient(tabId, expression);
1843
+ } catch (err3) {
1844
+ console.warn("[HunterMate] stopTitleMarquee failed", { tabId, error: err3.message });
1845
+ }
1846
+ }
1847
+ async function withSessionBrackets(sessionId, tab, explicitTab, groupTitle, fn) {
1848
+ if (sessionId && !explicitTab && tab.id && tab.windowId != null) {
1849
+ ensureSessionGroup(sessionId, groupTitle, tab).catch(
1850
+ (err3) => console.warn("[HunterMate] ensureSessionGroup failed", { sessionId, tabId: tab.id, error: err3.message })
1851
+ );
1852
+ }
1853
+ if (sessionId && tab.id) {
1854
+ startTitleMarquee(tab.id, sessionId).catch(() => {
1855
+ });
1856
+ }
1857
+ try {
1858
+ return await fn();
1859
+ } finally {
1860
+ if (sessionId && tab.id) {
1861
+ stopTitleMarquee(tab.id, sessionId).catch(() => {
1862
+ });
1863
+ }
1864
+ }
1865
+ }
1866
+ async function executeControlCommand(rawCommand, rawArgs, options = {}) {
1867
+ const { onLog } = options;
1868
+ function log(level, message, meta) {
1869
+ onLog?.(level, message, meta);
1870
+ }
1871
+ try {
1872
+ const controlArgs = rawArgs && typeof rawArgs === "object" ? { ...rawArgs } : {};
1873
+ let command = rawCommand;
1874
+ if (command === "cp.waitFor") {
1875
+ command = "cp.wait";
1876
+ if (typeof controlArgs.expression === "string" && controlArgs.script === void 0) {
1877
+ controlArgs.script = controlArgs.expression;
1878
+ }
1879
+ }
1880
+ if (controlArgs.selector !== void 0 && controlArgs.css === void 0) {
1881
+ controlArgs.css = controlArgs.selector;
1882
+ }
1883
+ if (command === "cp.fill" && controlArgs.value !== void 0 && controlArgs.text === void 0) {
1884
+ controlArgs.text = controlArgs.value;
1885
+ }
1886
+ if (command === "cp.eval" && typeof controlArgs.expression === "string" && controlArgs.script === void 0) {
1887
+ controlArgs.script = controlArgs.expression;
1888
+ }
1889
+ const tabOverride = controlArgs.__tab;
1890
+ delete controlArgs.__tab;
1891
+ const sessionId = String(controlArgs.taskId ?? options.sessionId ?? "");
1892
+ const explicitTab = hasExplicitTabTarget(controlArgs);
1893
+ const groupTitle = sessionId ? buildGroupTitle(controlArgs, sessionId) : "";
1894
+ if (command === "cp.screenshot") {
1895
+ const tab2 = await getControlTab(tabOverride);
1896
+ if (!tab2?.id) return err2("no_active_tab");
1897
+ if (isBlankKilledUrl(tab2.url)) return err2(TAB_KILLED_SECSCAN_MESSAGE, TAB_KILLED_SECSCAN_CODE);
1898
+ const stealFocus = controlArgs.stealFocus === true;
1899
+ const result = await withSessionBrackets(sessionId, tab2, explicitTab, groupTitle, async () => {
1900
+ log("debug", "cp.screenshot capturing", { tabId: tab2.id, windowId: tab2.windowId, stealFocus });
1901
+ const dataUrl = await captureActiveTabScreenshot2(
1902
+ { id: tab2.id, windowId: tab2.windowId },
1903
+ {
1904
+ stealFocus,
1905
+ onLog: (level, message, meta) => log(level, `[capture] ${message}`, meta)
1906
+ }
1907
+ );
1908
+ log("info", "cp.screenshot captured", { tabId: tab2.id, windowId: tab2.windowId, stealFocus });
1909
+ return { screenshot: dataUrl };
1910
+ });
1911
+ return ok(result);
1912
+ }
1913
+ if (command.startsWith("cp.tab.")) {
1914
+ if (command === "cp.tab.new") {
1915
+ const result2 = await handleTabCommand(command, controlArgs, log);
1916
+ const page = typeof result2 === "object" && result2 ? result2.page : void 0;
1917
+ if (sessionId && page && !explicitTab) {
1918
+ const newTabId = Number(page);
1919
+ if (Number.isFinite(newTabId)) {
1920
+ const newTab = await chrome.tabs.get(newTabId).catch(() => null);
1921
+ if (newTab) {
1922
+ addTabToSessionGroup(sessionId, groupTitle, newTab).catch(
1923
+ (err3) => log("warn", "cp.tab.new addTabToSessionGroup failed", { sessionId, tabId: newTabId, error: err3.message })
1924
+ );
1925
+ }
1926
+ }
1927
+ }
1928
+ return ok(result2);
1929
+ }
1930
+ const tab2 = await getControlTab(tabOverride);
1931
+ if (!tab2?.id) return err2("no_active_tab");
1932
+ const result = await withSessionBrackets(
1933
+ sessionId,
1934
+ tab2,
1935
+ explicitTab,
1936
+ groupTitle,
1937
+ async () => handleTabCommand(command, controlArgs, log)
1938
+ );
1939
+ return ok(result);
1940
+ }
1941
+ if (command === "cp.group.ensure") {
1942
+ const tab2 = await getControlTab(tabOverride);
1943
+ if (!tab2?.id || tab2.windowId == null) return err2("no_active_tab");
1944
+ const title = typeof controlArgs.title === "string" ? controlArgs.title : groupTitle;
1945
+ const { groupId, created } = await ensureSessionGroup(sessionId || `manual-${Date.now()}`, title || buildGroupTitle(controlArgs, sessionId || "manual"), tab2);
1946
+ return ok({ groupId, created, title });
1947
+ }
1948
+ if (command === "cp.cookies") {
1949
+ return ok(await handleCookiesCommand(controlArgs));
1950
+ }
1951
+ if (command === "cp.window.close") {
1952
+ const tab2 = await getControlTab(tabOverride);
1953
+ if (!tab2?.windowId) return err2("no_active_tab");
1954
+ const windowId = typeof controlArgs.windowId === "number" ? controlArgs.windowId : tab2.windowId;
1955
+ await chrome.windows.remove(windowId);
1956
+ return ok({ closed: true, windowId });
1957
+ }
1958
+ if (command === "hm.linkedin.search") {
1959
+ try {
1960
+ const result = await runLinkedInSearch(controlArgs, log, {
1961
+ sessionId,
1962
+ groupTitle,
1963
+ explicitTab
1964
+ });
1965
+ return ok(result);
1966
+ } catch (e) {
1967
+ const code = e instanceof ControlBlockedError ? e.code : void 0;
1968
+ return err2(e instanceof Error ? e.message : String(e), code);
1969
+ }
1970
+ }
1971
+ if (command === "hm.liepin.search") {
1972
+ try {
1973
+ const result = await runLiepinSearch(controlArgs, log, {
1974
+ sessionId,
1975
+ groupTitle,
1976
+ explicitTab
1977
+ });
1978
+ return ok(result);
1979
+ } catch (e) {
1980
+ const code = e instanceof ControlBlockedError ? e.code : void 0;
1981
+ return err2(e instanceof Error ? e.message : String(e), code);
1982
+ }
1983
+ }
1984
+ let tab = await getControlTab(tabOverride);
1985
+ if (!tab?.id) {
1986
+ if (command === "cp.goto" && typeof controlArgs.url === "string" && controlArgs.url) {
1987
+ const created = await chrome.tabs.create({ url: controlArgs.url, active: false });
1988
+ log("info", "auto-created tab for cp.goto (no active tab)", { tabId: created.id, url: controlArgs.url });
1989
+ if (sessionId && !explicitTab) {
1990
+ addTabToSessionGroup(sessionId, groupTitle, created).catch(
1991
+ (err3) => log("warn", "cp.goto auto-tab addTabToSessionGroup failed", { sessionId, error: err3.message })
1992
+ );
1993
+ }
1994
+ tab = created;
1995
+ } else {
1996
+ return err2("no_active_tab");
1997
+ }
1998
+ }
1999
+ if (!tab.id) return err2("no_active_tab");
2000
+ if (isBlankKilledUrl(tab.url)) return err2(TAB_KILLED_SECSCAN_MESSAGE, TAB_KILLED_SECSCAN_CODE);
2001
+ if (sessionId && !explicitTab) {
2002
+ ensureSessionGroup(sessionId, groupTitle, tab).catch(
2003
+ (err3) => log("warn", "ensureSessionGroup failed", { sessionId, tabId: tab.id, error: err3.message })
2004
+ );
2005
+ }
2006
+ if (sessionId) {
2007
+ startTitleMarquee(tab.id, sessionId).catch(() => {
2008
+ });
2009
+ }
2010
+ try {
2011
+ if (command === "cp.network" && isAttachableUrl(tab.url)) {
2012
+ try {
2013
+ const detail = controlArgs.detail;
2014
+ const result = typeof detail === "string" ? await readNetworkDetail(tab.id, detail) : await readNetworkEntries(tab.id, {
2015
+ filter: typeof controlArgs.filter === "string" ? controlArgs.filter : void 0,
2016
+ all: controlArgs.all === true
2017
+ });
2018
+ return ok(result);
2019
+ } catch (e) {
2020
+ console.warn("[HunterMate] CDP network failed, falling back:", e);
2021
+ }
2022
+ }
2023
+ if (command === "cp.frames" && isAttachableUrl(tab.url)) {
2024
+ try {
2025
+ return ok(await frameListCdp(tab.id));
2026
+ } catch (e) {
2027
+ console.warn("[HunterMate] CDP frames failed, falling back:", e);
2028
+ }
2029
+ }
2030
+ if (command === "cp.wait" && (controlArgs.download === true || controlArgs.type === "download")) {
2031
+ const timeout = typeof controlArgs.timeout === "number" ? controlArgs.timeout : 3e4;
2032
+ const result = await waitForDownload(
2033
+ typeof controlArgs.pattern === "string" ? controlArgs.pattern : void 0,
2034
+ timeout
2035
+ );
2036
+ return ok(result);
2037
+ }
2038
+ if (command === "cp.upload" && Array.isArray(controlArgs.files) && typeof controlArgs.css === "string" && controlArgs.css && isAttachableUrl(tab.url)) {
2039
+ try {
2040
+ const result = await setFileInputFiles(tab.id, controlArgs.css, controlArgs.files);
2041
+ return ok(result);
2042
+ } catch (e) {
2043
+ console.warn("[HunterMate] CDP upload failed, falling back:", e);
2044
+ }
2045
+ }
2046
+ if (command === "cp.cdp" && typeof controlArgs.method === "string") {
2047
+ if (!isAttachableUrl(tab.url)) return err2("cdp_requires_http_page");
2048
+ try {
2049
+ const params = controlArgs.params && typeof controlArgs.params === "object" ? { ...controlArgs.params } : {};
2050
+ const result = await sendRawCommand(tab.id, controlArgs.method, params);
2051
+ return ok({ method: controlArgs.method, result });
2052
+ } catch (e) {
2053
+ return err2(e instanceof Error ? e.message : String(e));
2054
+ }
2055
+ }
2056
+ if (command === "cp.insert" && typeof controlArgs.text === "string") {
2057
+ if (!isAttachableUrl(tab.url)) return err2("insert_requires_http_page");
2058
+ try {
2059
+ if (controlArgs.ref !== void 0 || typeof controlArgs.css === "string") {
2060
+ log("debug", "cp.insert focusing target before insert", { ref: controlArgs.ref, css: controlArgs.css });
2061
+ try {
2062
+ await cdpEvaluate(tab.id, buildInjectionExpression("cp.focus", { ref: controlArgs.ref, css: controlArgs.css }));
2063
+ } catch {
2064
+ }
2065
+ }
2066
+ await insertText(tab.id, controlArgs.text);
2067
+ return ok({ inserted: true, length: controlArgs.text.length });
2068
+ } catch (e) {
2069
+ return err2(e instanceof Error ? e.message : String(e));
2070
+ }
2071
+ }
2072
+ if (command === "cp.eval" && typeof controlArgs.script === "string") {
2073
+ const script = controlArgs.script;
2074
+ const frame = controlArgs.frame;
2075
+ const tabId = tab.id;
2076
+ const evalWith = async (expr) => {
2077
+ if (frame !== void 0 && isAttachableUrl(tab.url)) {
2078
+ let frameId = typeof frame === "string" ? frame : "";
2079
+ let frameUrl;
2080
+ if (typeof frame === "number" || /^\d+$/.test(String(frame))) {
2081
+ const list = await frameListCdp(tabId);
2082
+ const target = list.frames?.find((f) => f.index === Number(frame));
2083
+ if (!target) throw new Error(`frame_not_found: ${frame}`);
2084
+ frameId = target.frame_id;
2085
+ frameUrl = target.url;
2086
+ }
2087
+ return evaluateInFrame(tabId, expr, frameId, frameUrl);
2088
+ }
2089
+ if (controlArgs.detachAfter !== false) return cdpEvaluateTransient(tabId, expr);
2090
+ return cdpEvaluate(tabId, expr);
2091
+ };
2092
+ const useExpression = await evalWith(buildEvalSyntaxCheckExpression(script)).catch(() => false) === true;
2093
+ const result = await evalWith(useExpression ? buildEvalExpressionWrapper(script) : buildEvalStatementWrapper(script));
2094
+ return ok(result);
2095
+ }
2096
+ if (command === "hm.linkedin.extract") {
2097
+ if (!isAttachableUrl(tab.url)) {
2098
+ return err2("linkedin_extract_requires_http_page");
2099
+ }
2100
+ try {
2101
+ const result = await cdpEvaluate(tab.id, LINKEDIN_EXTRACT_SOURCE);
2102
+ return ok(result);
2103
+ } catch (e) {
2104
+ return err2(e instanceof Error ? e.message : String(e));
2105
+ }
2106
+ }
2107
+ if (command === "hm.hiredchina.extract") {
2108
+ if (!isAttachableUrl(tab.url)) {
2109
+ return err2("hiredchina_extract_requires_http_page");
2110
+ }
2111
+ try {
2112
+ const result = await cdpEvaluate(tab.id, HIREDCHINA_EXTRACT_SOURCE);
2113
+ return ok(result);
2114
+ } catch (e) {
2115
+ return err2(e instanceof Error ? e.message : String(e));
2116
+ }
2117
+ }
2118
+ if (command === "hm.liepin.extract") {
2119
+ if (!isAttachableUrl(tab.url)) {
2120
+ return err2("liepin_extract_requires_http_page");
2121
+ }
2122
+ try {
2123
+ const result = await cdpEvaluateTransient(tab.id, LIEPIN_EXTRACT_SOURCE);
2124
+ return ok(result);
2125
+ } catch (e) {
2126
+ return err2(e instanceof Error ? e.message : String(e));
2127
+ }
2128
+ }
2129
+ if (command.startsWith("hm.")) {
2130
+ return { status: "error", code: "not_implemented", message: `${command} is not implemented yet` };
2131
+ }
2132
+ if (isAttachableUrl(tab.url)) {
2133
+ log("debug", "control running via CDP runtime", { command, tabId: tab.id });
2134
+ try {
2135
+ const result = await cdpEvaluate(tab.id, buildInjectionExpression(command, controlArgs));
2136
+ if (result && typeof result === "object" && !Array.isArray(result) && "__cpError" in result) {
2137
+ return err2(String(result.__cpError));
2138
+ }
2139
+ return ok(result);
2140
+ } catch (e) {
2141
+ console.warn("[HunterMate] CDP control failed, falling back to executeScript:", e);
2142
+ }
2143
+ } else if (isBlankKilledUrl(tab.url)) {
2144
+ return err2(TAB_KILLED_SECSCAN_MESSAGE, TAB_KILLED_SECSCAN_CODE);
2145
+ } else if (tab.url?.startsWith("chrome://") || tab.url?.startsWith("chrome-extension://") || tab.url?.startsWith("about:")) {
2146
+ return err2(
2147
+ `Cannot access a ${tab.url.split("/")[0]}// page for control commands. Open the target page in a normal tab first.`
2148
+ );
2149
+ }
2150
+ const [injection] = await chrome.scripting.executeScript({
2151
+ target: { tabId: tab.id },
2152
+ func: runControl,
2153
+ args: [command, controlArgs]
2154
+ });
2155
+ const inj = injection;
2156
+ if (inj?.error) return err2(String(inj.error));
2157
+ return ok(inj?.result);
2158
+ } finally {
2159
+ if (sessionId && tab?.id) {
2160
+ stopTitleMarquee(tab.id, sessionId).catch(() => {
2161
+ });
2162
+ }
2163
+ }
2164
+ } catch (e) {
2165
+ console.error("[HunterMate] executeControlCommand error", e);
2166
+ return err2(e instanceof Error ? e.message : String(e));
2167
+ }
2168
+ }
2169
+ var ControlBlockedError = class extends Error {
2170
+ constructor(code, message) {
2171
+ super(message);
2172
+ this.code = code;
2173
+ this.name = "ControlBlockedError";
2174
+ }
2175
+ };
2176
+ function buildInjectionExpression(command, controlArgs) {
2177
+ return '(() => { try { if (typeof globalThis.__cpRunControl !== "function" || globalThis.__cpRuntimeVersion !== ' + JSON.stringify(RUNTIME_VERSION) + ") {\n" + control_runtime_source_default + "\n} const r = globalThis.__cpRunControl(" + JSON.stringify(command) + ", " + JSON.stringify(controlArgs) + '); if (r && typeof r.then === "function") { return r; } return r; } catch (e) { return { __cpError: String((e && e.message) || e) }; } })()';
2178
+ }
2179
+ function ok(result) {
2180
+ return { status: "ok", result };
2181
+ }
2182
+ var LINKEDIN_EXTRACT_SOURCE = String.raw`(async () => {
2183
+ const waitFor = (fn, timeout, interval) => new Promise((resolve) => {
2184
+ const t0 = Date.now();
2185
+ const tick = () => {
2186
+ let v = null;
2187
+ try { v = fn(); } catch (e) {}
2188
+ if (v) return resolve(v);
2189
+ if (Date.now() - t0 > (timeout || 8000)) return resolve(null);
2190
+ setTimeout(tick, interval || 250);
2191
+ };
2192
+ tick();
2193
+ });
2194
+ if (document.readyState !== 'complete') {
2195
+ await waitFor(() => document.readyState === 'complete', 12000, 200);
2196
+ }
2197
+ const path = location.pathname;
2198
+ const jsonLdAll = () => Array.from(document.querySelectorAll('script[type="application/ld+json"]'))
2199
+ .map((s) => { try { return JSON.parse(s.textContent); } catch (e) { return null; } })
2200
+ .filter(Boolean)
2201
+ .flatMap((x) => (Array.isArray(x) ? x : [x]));
2202
+ const meta = (sel) => {
2203
+ const el = document.querySelector(sel);
2204
+ return (el && el.content ? String(el.content).trim() : null) || null;
2205
+ };
2206
+ const text = (sel) => {
2207
+ const el = document.querySelector(sel);
2208
+ return el ? el.textContent.trim().replace(/\s+/g, ' ') : null;
2209
+ };
2210
+ const strip = (html) => String(html || '').replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
2211
+
2212
+ let pageClass = 'unknown';
2213
+ if (/^\/in\/[^/]+/.test(path)) pageClass = 'talent';
2214
+ else if (/^\/company\/[^/]+/.test(path)) pageClass = 'company';
2215
+ else if (/^\/jobs\/view\//.test(path)) pageClass = 'job';
2216
+ else if (/^\/(jobs\/(search|collections|job)|search\/results\/(people|companies|content))/.test(path)) pageClass = 'list';
2217
+
2218
+ const canonical = (document.querySelector('link[rel="canonical"]') || {}).href || location.href.split('?')[0];
2219
+ const entities = [];
2220
+ const links = { talent: [], company: [], job: [] };
2221
+
2222
+ if (pageClass === 'talent') {
2223
+ await waitFor(() => document.querySelector('main h1, main h2, h1, h2'), 9000);
2224
+ const person = jsonLdAll().find((x) => x['@type'] === 'Person');
2225
+ // 现代 LinkedIn profile 无 h1/JSON-LD(混淆 class 不稳定),用 main innerText 行结构解析:
2226
+ // [0]=展示名 [· 3rd]=人脉度 [2]=headline [3]=location
2227
+ const lines = ((document.querySelector('main') || document.body).innerText || '')
2228
+ .split('\n').map((s) => s.trim()).filter(Boolean);
2229
+ const titleName = (document.title.split('|')[0] || '').trim() || null;
2230
+ let ni = 0;
2231
+ while (ni < lines.length && lines[ni].startsWith('·')) ni++;
2232
+ const displayName = ni < lines.length ? lines[ni] : null;
2233
+ let hi = ni + 1;
2234
+ while (hi < lines.length && lines[hi].startsWith('·')) hi++;
2235
+ const lineHeadline = hi < lines.length && lines[hi].length > 2 ? lines[hi] : null;
2236
+ let li = hi + 1;
2237
+ while (li < lines.length && lines[li].startsWith('·')) li++;
2238
+ const lineLoc = li < lines.length && !/contact info/i.test(lines[li]) && lines[li].length < 80 ? lines[li] : null;
2239
+ entities.push({
2240
+ entityType: 'talent',
2241
+ name: (person && person.name) || titleName || displayName,
2242
+ displayName: displayName || null,
2243
+ headline: (person && person.jobTitle) || lineHeadline || null,
2244
+ location: (person && person.address && person.address.addressLocality) || lineLoc || null,
2245
+ company: (person && person.worksFor && person.worksFor.name) || null,
2246
+ url: canonical, source: 'linkedin',
2247
+ });
2248
+ } else if (pageClass === 'company') {
2249
+ await waitFor(() => document.querySelector('main h1, main h2, h1, h2'), 9000);
2250
+ const org = jsonLdAll().find((x) => /Organization|Corporation|LocalBusiness/i.test(String(x['@type'])));
2251
+ const mainLines = ((document.querySelector('main') || document.body).innerText || '')
2252
+ .split('\n').map((s) => s.trim()).filter(Boolean);
2253
+ const rawTitle = (document.title.split('|')[0] || '').trim();
2254
+ const titleName = rawTitle.replace(/^\(\d+\)\s*/, '').replace(/:\s*Overview\s*$/, '').trim() || null;
2255
+ // 概览区:industry 是 meta 行的相邻前置 DIV;meta 行内 hq/followers/employees 为分离兄弟元素
2256
+ let industry = (org && org.industry) || null;
2257
+ let headquarters = null;
2258
+ let companySize = null;
2259
+ let followers = null;
2260
+ const folEl = Array.from(document.querySelectorAll('main *'))
2261
+ .find((x) => /followers/i.test(x.textContent || '') && x.children.length === 0);
2262
+ if (folEl) {
2263
+ const row = folEl.parentElement;
2264
+ const cells = Array.from(row ? row.children : []).map((c) => (c.textContent || '').trim()).filter(Boolean);
2265
+ headquarters = cells.find((c) => /,/.test(c) && !/followers|employees/i.test(c)) || null;
2266
+ const sizeC = cells.find((c) => /employees/i.test(c));
2267
+ if (sizeC) companySize = sizeC.replace(/\s*employees.*/i, '').trim();
2268
+ followers = (folEl.textContent || '').replace(/[\s,]*followers.*/i, '').trim();
2269
+ const outer = row ? row.parentElement : null;
2270
+ const prev = outer ? outer.previousElementSibling : null;
2271
+ const ind = prev ? (prev.textContent || '').trim() : '';
2272
+ if (ind && ind.length < 60 && !/follow|learn|sign|connect|notifications/i.test(ind)) industry = ind;
2273
+ }
2274
+ // 兜底:innerText 概览行(游客态/结构变化) "Software Development Redmond, Washington 29M followers 10K+ employees"
2275
+ if (!headquarters && !industry) {
2276
+ const overviewLine = mainLines.slice(0, 4).find((l) => /followers/i.test(l) || /employees/i.test(l));
2277
+ if (overviewLine) {
2278
+ const sizeM = overviewLine.match(/([\d.,]+[KMB]?\+?)\s*employees/i);
2279
+ const folM = overviewLine.match(/([\d.,]+[KMB]?)\s*followers/i);
2280
+ if (sizeM) companySize = companySize || sizeM[1];
2281
+ if (folM) followers = followers || folM[1];
2282
+ let rest = overviewLine;
2283
+ if (sizeM) rest = rest.replace(sizeM[0], ' ');
2284
+ if (folM) rest = rest.replace(folM[0], ' ');
2285
+ rest = rest.replace(/\s+/g, ' ').trim();
2286
+ const lastComma = rest.lastIndexOf(',');
2287
+ if (lastComma > 0) {
2288
+ const state = rest.slice(lastComma + 1).trim();
2289
+ const bw = rest.slice(0, lastComma).trim().split(/\s+/);
2290
+ const city = bw.slice(-2).join(' '); // 常见双词城市("Mountain View");DOM 主路径不受此限制
2291
+ headquarters = city + ', ' + state;
2292
+ industry = bw.slice(0, -2).join(' ') || null;
2293
+ } else if (rest) {
2294
+ industry = rest;
2295
+ }
2296
+ }
2297
+ }
2298
+ const name = text('h1') || (org && org.name) || mainLines[0] || titleName || meta('meta[property="og:title"]');
2299
+ const description = meta('meta[name="description"]') || meta('meta[property="og:description"]') || (org && org.description) || null;
2300
+ entities.push({
2301
+ entityType: 'company', name: name || null,
2302
+ description: description ? description.slice(0, 500) : null,
2303
+ industry: industry || null,
2304
+ companySize: companySize || null,
2305
+ headquarters: headquarters || null,
2306
+ followers: followers || null,
2307
+ url: canonical, source: 'linkedin',
2308
+ });
2309
+ } else if (pageClass === 'job') {
2310
+ await waitFor(() => document.querySelector('h1, .top-card-layout__title, [class*="unified-top-card__job-title"]'), 10000);
2311
+ const job = jsonLdAll().find((x) => x['@type'] === 'JobPosting');
2312
+ const title = (job && job.title) || text('.top-card-layout__title') || text('h1');
2313
+ const company = (job && job.hiringOrganization && job.hiringOrganization.name)
2314
+ || text('.job-details-jobs-unified-top-card__company-name')
2315
+ || text('a[data-test-app-aware-link]');
2316
+ let location = null;
2317
+ if (job && job.jobLocation) {
2318
+ const loc = Array.isArray(job.jobLocation) ? job.jobLocation[0] : job.jobLocation;
2319
+ location = loc && loc.address && loc.address.addressLocality;
2320
+ }
2321
+ if (!location) location = text('.job-details-jobs-unified-top-card__bullet') || text('.top-card-layout__bullet');
2322
+ const description = (job && job.description)
2323
+ ? strip(job.description).slice(0, 2000)
2324
+ : (text('.show-more-less-html__markup') || '').slice(0, 2000) || null;
2325
+ entities.push({
2326
+ entityType: 'job', title: title || null, company: company || null,
2327
+ location: location || null, description: description || null,
2328
+ datePosted: (job && job.datePosted) || null,
2329
+ employmentType: (job && job.employmentType) || null,
2330
+ url: canonical, source: 'linkedin',
2331
+ });
2332
+ } else if (pageClass === 'list') {
2333
+ const seen = new Set();
2334
+ for (const a of Array.from(document.querySelectorAll('a[href]'))) {
2335
+ const href = a.href || '';
2336
+ let m = href.match(/linkedin\.com\/in\/([^/?#]+)/);
2337
+ if (m && !seen.has('t:' + m[1])) {
2338
+ seen.add('t:' + m[1]);
2339
+ links.talent.push({ url: 'https://www.linkedin.com/in/' + m[1] + '/', name: (a.textContent || '').trim().split('\n')[0] || m[1] });
2340
+ }
2341
+ m = href.match(/linkedin\.com\/company\/([^/?#]+)/);
2342
+ if (m && !seen.has('c:' + m[1])) {
2343
+ seen.add('c:' + m[1]);
2344
+ links.company.push({ url: 'https://www.linkedin.com/company/' + m[1] + '/', name: (a.textContent || '').trim().split('\n')[0] || m[1] });
2345
+ }
2346
+ m = href.match(/linkedin\.com\/jobs\/view\/(\d+)/);
2347
+ if (m && !seen.has('j:' + m[1])) {
2348
+ seen.add('j:' + m[1]);
2349
+ links.job.push({ url: 'https://www.linkedin.com/jobs/view/' + m[1] + '/', title: (a.textContent || '').trim().split('\n')[0] || m[1] });
2350
+ }
2351
+ }
2352
+ links.talent = links.talent.slice(0, 10);
2353
+ links.company = links.company.slice(0, 10);
2354
+ links.job = links.job.slice(0, 10);
2355
+ }
2356
+
2357
+ return {
2358
+ pageClass: pageClass,
2359
+ url: location.href,
2360
+ canonical: canonical,
2361
+ title: document.title,
2362
+ entities: entities,
2363
+ links: links,
2364
+ source: 'linkedin',
2365
+ extractedAt: new Date().toISOString(),
2366
+ };
2367
+ })()`;
2368
+ var LINKEDIN_WALL_CHECK_SOURCE = String.raw`(() => {
2369
+ try {
2370
+ if (document.querySelector('iframe[src*="captcha"], iframe[src*="challenge-platform"]')) return 'CAPTCHA';
2371
+ const bodyText = (document.body && document.body.innerText ? document.body.innerText : '').slice(0, 3000);
2372
+ if (/verify you('| a)re human|captcha|security (check|verification)/i.test(bodyText)) return 'CAPTCHA';
2373
+ const url = location.href;
2374
+ if (/linkedin\.com\/(authwall|uas\/login|login)/i.test(url)) return 'LOGIN_WALL';
2375
+ if (/linkedin\.com\/checkpoint/i.test(url)) return 'CAPTCHA';
2376
+ } catch (e) { /* 检测失败不阻断搜索 */ }
2377
+ return null;
2378
+ })()`;
2379
+ var LINKEDIN_NEXT_CLICK_SOURCE = String.raw`(async () => {
2380
+ const btn = document.querySelector('button.artdeco-pagination__button--next')
2381
+ || document.querySelector('button[aria-label="Next"]')
2382
+ || document.querySelector('[aria-label="Next"] button');
2383
+ if (!btn || btn.disabled) return false;
2384
+ btn.click();
2385
+ return true;
2386
+ })()`;
2387
+ function sleep(ms) {
2388
+ return new Promise((r) => setTimeout(r, ms));
2389
+ }
2390
+ async function waitTabLoaded(tabId, timeoutMs = 25e3) {
2391
+ const t0 = Date.now();
2392
+ let lastUrl = "";
2393
+ let stableHits = 0;
2394
+ while (Date.now() - t0 < timeoutMs) {
2395
+ const t = await chrome.tabs.get(tabId).catch(() => null);
2396
+ if (!t) throw new Error(`tab ${tabId} closed while loading`);
2397
+ if (t.status === "complete") {
2398
+ if (t.url === lastUrl) {
2399
+ stableHits++;
2400
+ if (stableHits >= 2) return;
2401
+ } else {
2402
+ stableHits = 0;
2403
+ lastUrl = t.url || "";
2404
+ }
2405
+ } else {
2406
+ stableHits = 0;
2407
+ lastUrl = "";
2408
+ }
2409
+ await sleep(400);
2410
+ }
2411
+ throw new Error(`tab ${tabId} load timeout after ${timeoutMs}ms`);
2412
+ }
2413
+ async function runLinkedInSearch(args, log, ctx) {
2414
+ const keywords = typeof args.keywords === "string" ? args.keywords.trim() : "";
2415
+ const filters = args.filters && typeof args.filters === "object" ? args.filters : {};
2416
+ const maxPages = Math.min(Math.max(Number(args.maxPages) || 3, 1), 10);
2417
+ const keepTab = args.keepTab === true;
2418
+ const activateTab = args.activateTab !== false;
2419
+ const loadTimeoutMs = Math.min(Math.max(Number(args.loadTimeoutMs) || 25e3, 5e3), 3e5);
2420
+ const delayBase = typeof args.delayMs === "number" ? Number(args.delayMs) : 2500;
2421
+ let effectiveKeywords = keywords;
2422
+ if (!filters.geoUrn && typeof filters.location === "string" && filters.location.trim()) {
2423
+ effectiveKeywords = (keywords + " " + filters.location.trim()).trim();
2424
+ }
2425
+ if (!effectiveKeywords && !filters.company && !filters.school) {
2426
+ throw new Error("linkedin_search_requires_keywords_or_filters");
2427
+ }
2428
+ const u = new URL("https://www.linkedin.com/search/results/people/");
2429
+ if (effectiveKeywords) u.searchParams.set("keywords", effectiveKeywords);
2430
+ if (typeof filters.geoUrn === "string" && filters.geoUrn) u.searchParams.set("geoUrn", filters.geoUrn);
2431
+ if (typeof filters.company === "string" && filters.company) u.searchParams.set("company", filters.company);
2432
+ if (typeof filters.school === "string" && filters.school) u.searchParams.set("school", filters.school);
2433
+ const tab = await chrome.tabs.create({ url: u.toString(), active: false });
2434
+ log("info", "hm.linkedin.search tab created", { tabId: tab.id, url: u.toString(), maxPages, activateTab });
2435
+ if (activateTab && tab.id != null) {
2436
+ await chrome.tabs.update(tab.id, { active: true }).catch(
2437
+ (err3) => log("warn", "hm.linkedin.search activate tab failed", { tabId: tab.id, error: err3.message })
2438
+ );
2439
+ }
2440
+ if (ctx.sessionId && !ctx.explicitTab && tab.id) {
2441
+ addTabToSessionGroup(ctx.sessionId, ctx.groupTitle, tab).catch(
2442
+ (err3) => log("warn", "hm.linkedin.search addTabToSessionGroup failed", { error: err3.message })
2443
+ );
2444
+ }
2445
+ const leads = /* @__PURE__ */ new Map();
2446
+ let pagesScanned = 0;
2447
+ let lastUrl = "";
2448
+ try {
2449
+ for (let page = 1; page <= maxPages; page++) {
2450
+ if (tab.id == null) break;
2451
+ await waitTabLoaded(tab.id, loadTimeoutMs);
2452
+ const blocked = await cdpEvaluate(tab.id, LINKEDIN_WALL_CHECK_SOURCE).catch(() => null);
2453
+ if (blocked === "CAPTCHA" || blocked === "LOGIN_WALL") {
2454
+ log("warn", "hm.linkedin.search blocked by linkedin risk control", { blocked, page, url: lastUrl });
2455
+ throw new ControlBlockedError(blocked, `linkedin search aborted on page ${page}: ${blocked}`);
2456
+ }
2457
+ const extracted = await cdpEvaluate(tab.id, LINKEDIN_EXTRACT_SOURCE);
2458
+ lastUrl = extracted?.url || lastUrl;
2459
+ const pageLeads = extracted?.links?.talent || [];
2460
+ for (const l of pageLeads) {
2461
+ if (l.url && !leads.has(l.url)) {
2462
+ leads.set(l.url, { url: l.url, name: l.name ?? null, page, source: "linkedin" });
2463
+ }
2464
+ }
2465
+ pagesScanned = page;
2466
+ log("info", "hm.linkedin.search page extracted", { page, pageLeads: pageLeads.length, total: leads.size });
2467
+ if (page >= maxPages) break;
2468
+ const clicked = await cdpEvaluate(tab.id, LINKEDIN_NEXT_CLICK_SOURCE).catch(() => false);
2469
+ const urlBefore = (await chrome.tabs.get(tab.id).catch(() => null))?.url || "";
2470
+ await sleep(clicked ? 2500 : 0);
2471
+ const urlAfterClick = (await chrome.tabs.get(tab.id).catch(() => null))?.url || "";
2472
+ if (!clicked || urlAfterClick === urlBefore) {
2473
+ const next = new URL(u.toString());
2474
+ next.searchParams.set("page", String(page + 1));
2475
+ if (next.toString() === urlBefore) break;
2476
+ await chrome.tabs.update(tab.id, { url: next.toString() });
2477
+ log("debug", "hm.linkedin.search fallback to page param", { page: page + 1 });
2478
+ }
2479
+ await sleep(delayBase + Math.random() * 2500);
2480
+ }
2481
+ } finally {
2482
+ if (!keepTab && tab.id != null) {
2483
+ await closeTabGracefully(tab.id);
2484
+ }
2485
+ }
2486
+ let diagnostics = null;
2487
+ if (leads.size === 0 && tab.id != null) {
2488
+ const t = await chrome.tabs.get(tab.id).catch(() => null);
2489
+ const signal = t && t.id != null ? await cdpEvaluateTransient(t.id, LIEPIN_WALL_CHECK_SOURCE).catch(() => null) : null;
2490
+ const bodySnippet = t && t.id != null && (!t.url || t.url !== "about:blank") ? await cdpEvaluateTransient(
2491
+ t.id,
2492
+ '(() => { const b = document.body && document.body.innerText; return b ? b.replace(/\\s+/g, " ").trim().slice(0, 300) : null; })()'
2493
+ ).catch(() => null) : null;
2494
+ diagnostics = { url: t?.url ?? null, title: t?.title ?? null, signal, bodySnippet };
2495
+ if (signal) {
2496
+ log("warn", "hm.liepin.search 0 leads with risk-control signal", { signal, url: diagnostics.url });
2497
+ }
2498
+ }
2499
+ return {
2500
+ keywords: effectiveKeywords,
2501
+ searchUrl: u.toString(),
2502
+ pagesScanned,
2503
+ leadCount: leads.size,
2504
+ leads: Array.from(leads.values()),
2505
+ keepTab,
2506
+ tabId: keepTab ? tab.id ?? null : null,
2507
+ // Issue #24 P0:0 leads 不等于"没搜到",可能是登录墙/反爬页根本没渲染结果。
2508
+ // 带出诊断字段,由调用方决定 degraded;真实 0 结果 diagnostics.signal 为 null。
2509
+ ...leads.size === 0 && diagnostics ? { diagnostics, degraded: diagnostics.signal !== null, degradedReason: diagnostics.signal ?? void 0 } : {}
2510
+ };
2511
+ }
2512
+ var LIEPIN_WALL_CHECK_SOURCE = String.raw`(() => {
2513
+ try {
2514
+ if (location.href === 'about:blank' || !location.hostname) return 'BLANK_KILL';
2515
+ const url = location.href;
2516
+ if (/liepin\.com\/(login|passport|signin)/i.test(url)) return 'LOGIN_WALL';
2517
+ if (/liepin\.com\/(safe|verify|check|captcha|slide)/i.test(url)) return 'CAPTCHA';
2518
+ const bodyText = (document.body && document.body.innerText ? document.body.innerText : '').slice(0, 4000);
2519
+ if (/滑块|拖动验证|安全验证|验证码|verify you are human|请完成安全验证/.test(bodyText)) return 'CAPTCHA';
2520
+ if (document.querySelector('[class*="captcha"], [id*="captcha"], iframe[src*="captcha"], iframe[src*="slide"]')) return 'CAPTCHA';
2521
+ if (/请先登录|登录后查看|立即登录/.test(bodyText) && !document.querySelector('#resume-detail-basic-info')) return 'LOGIN_WALL';
2522
+ } catch (e) { /* 检测失败不阻断 */ }
2523
+ return null;
2524
+ })()`;
2525
+ var LIEPIN_EXTRACT_SOURCE = String.raw`(async () => {
2526
+ const waitFor = (fn, timeout, interval) => new Promise((resolve) => {
2527
+ const t0 = Date.now();
2528
+ const tick = () => {
2529
+ let v = null;
2530
+ try { v = fn(); } catch (e) {}
2531
+ if (v) return resolve(v);
2532
+ if (Date.now() - t0 > (timeout || 8000)) return resolve(null);
2533
+ setTimeout(tick, interval || 250);
2534
+ };
2535
+ tick();
2536
+ });
2537
+ if (document.readyState !== 'complete') {
2538
+ await waitFor(() => document.readyState === 'complete', 12000, 200);
2539
+ }
2540
+ const clean = (s) => {
2541
+ if (s == null) return null;
2542
+ const t = String(s).replace(/\s+/g, ' ').trim();
2543
+ return t || null;
2544
+ };
2545
+ const txt = (el) => clean(el ? el.textContent : null);
2546
+ const first = (sel, root) => (root || document).querySelector(sel);
2547
+ const attr = (el, name) => (el ? (el.getAttribute(name) || null) : null);
2548
+ const abs = (href) => {
2549
+ if (!href) return null;
2550
+ try { return new URL(href, location.href).toString(); } catch (e) { return href; }
2551
+ };
2552
+ /* sep-info 用 <i></i> 作分隔符:转 | 后按段切分 */
2553
+ const splitByI = (el) => {
2554
+ if (!el) return [];
2555
+ const html = el.innerHTML || '';
2556
+ const holder = document.createElement('div');
2557
+ return html.replace(/<i[^>]*>\s*<\/i>/g, '|').split('|').map((seg) => {
2558
+ holder.innerHTML = seg;
2559
+ return clean(holder.textContent);
2560
+ }).filter(Boolean);
2561
+ };
2562
+ /* (2012.07 - 至今, 14年2个月) → { start, end, duration } */
2563
+ const parsePeriod = (text) => {
2564
+ const m = (text || '').match(/[((]\s*([^))]*?)\s*[))]/);
2565
+ if (!m) return { start: null, end: null, duration: null };
2566
+ const parts = m[1].split(',').map((s) => clean(s)).filter(Boolean);
2567
+ const range = (parts[0] || '').split('-').map((s) => clean(s));
2568
+ return { start: range[0] || null, end: range[1] || null, duration: parts[1] || null };
2569
+ };
2570
+ /* rd-info-col 映射:每个 col 是 title+cont 对(一行可能两个 col) */
2571
+ const readInfoCols = (root) => {
2572
+ const out = {};
2573
+ (root || document).querySelectorAll('.rd-info-col').forEach((col) => {
2574
+ const title = txt(first('.rd-info-col-title', col));
2575
+ const cont = txt(first('.rd-info-col-cont', col));
2576
+ if (title && cont) out[title.replace(/[::]\s*$/, '')] = cont;
2577
+ });
2578
+ return out;
2579
+ };
2580
+ /* 实体契约 SSOT: hunter-mate packages/server/src/extraction/talent-entity.ts(2026-09-22 用户裁定):
2581
+ 实体字段尽量填满;实体外信息不静默丢弃——条目级未映射 col 进条目 extra,
2582
+ 实体级未消费区块进 extra.sections。两侧(extract 脚本/服务端快照解析)逻辑必须对齐。 */
2583
+ const pickUnmappedCols = (cols, knownList) => {
2584
+ const known = new Set(knownList);
2585
+ const out = {};
2586
+ Object.keys(cols || {}).forEach((k) => { if (!known.has(k)) out[k] = cols[k]; });
2587
+ return Object.keys(out).length ? out : null;
2588
+ };
2589
+ const sweepExtraSections = () => {
2590
+ const CONSUMED = ['single', 'basic-info', 'job-exp-info', 'job-expectancy-info-wrap', 'work-info',
2591
+ 'project-info', 'edu-info', 'lang-info', 'skill-info', 'self-eva-info', 'self-evaluation-info', 'operation-wrap'];
2592
+ const sections = [];
2593
+ const seen = new Set();
2594
+ document.querySelectorAll('[id^="resume-detail-"], [class*="resume-detail-"]').forEach((el) => {
2595
+ const id = el.id || '';
2596
+ const cls = (typeof el.className === 'string' ? el.className : '').split(/\s+/)
2597
+ .find((c) => c.startsWith('resume-detail-') && c !== 'resume-detail-template' && c !== 'resume-detail-template-cont') || '';
2598
+ const key = (id || cls).replace(/^resume-detail-/, '');
2599
+ if (!key || seen.has(key)) return;
2600
+ const isSection = el.querySelector('.resume-detail-template-cont') !== null
2601
+ || !(el.parentElement && el.parentElement.closest('[id^="resume-detail-"], [class*="resume-detail-"]'));
2602
+ if (!isSection) return;
2603
+ seen.add(key);
2604
+ if (CONSUMED.includes(key)) return;
2605
+ const text = txt(el);
2606
+ if (!text) return;
2607
+ sections.push({ key, title: txt(el.querySelector('.resume-detail-template-title, .template-title, h3, h4')), text: text.length > 2000 ? text.slice(0, 2000) + '…' : text });
2608
+ });
2609
+ return sections;
2610
+ };
2611
+ const resIdFrom = (href, scm) => {
2612
+ if (scm) {
2613
+ const m = scm.match(/cid=([^&]+)/);
2614
+ if (m) return m[1];
2615
+ }
2616
+ if (href) {
2617
+ const m = href.match(/res_id_encode=([^&]+)/);
2618
+ if (m) return m[1];
2619
+ }
2620
+ return null;
2621
+ };
2622
+
2623
+ const url = location.href;
2624
+ const canonical = (document.querySelector('link[rel="canonical"]') || {}).href || url.split('?')[0];
2625
+ const entities = [];
2626
+ const links = { talent: [], company: [], job: [] };
2627
+
2628
+ /* 详情页锚点 2026-09-19 drift:容器由 id="resume-detail-*-info" 改为 class="resume-detail-*"(如
2629
+ .resume-detail-basic-info / .resume-detail-job-expectancy-info-wrap / .resume-detail-work-info),
2630
+ 工作经历条目由 .rd-work-item-cont 改为 .rd-info-tpl-item;新旧两种 DOM 均兼容(先 id 后 class)。 */
2631
+ const basicRoot = first('#resume-detail-basic-info') || first('.resume-detail-basic-info');
2632
+ if (basicRoot) {
2633
+ const basic = basicRoot;
2634
+ /* sep-info 分行定位:contact=含"方便联系时间";personal=含 岁/工作N年;company=其余(职位+公司)。
2635
+ 旧 DOM 公司名在个人行末段,新 DOM 公司名在独立行,两种形态都解析。 */
2636
+ const sepEls = Array.from(basic.querySelectorAll('.basic-cont .sep-info, .sep-info'));
2637
+ const sepParts = sepEls.map((el) => splitByI(el));
2638
+ const contactIdx = sepParts.findIndex((p) => /方便联系时间/.test(p.join(' ')));
2639
+ const personalIdx = sepParts.findIndex((p) => /岁|工作\s*\d+\s*年/.test(p.join(' ')));
2640
+ const partsP = personalIdx >= 0 ? sepParts[personalIdx] : (sepParts[0] || []);
2641
+ const companyIdx = sepParts.findIndex((p, i) => i !== personalIdx && i !== contactIdx && p.join(' ').trim().length > 0);
2642
+ const partsC = companyIdx >= 0 ? sepParts[companyIdx] : [];
2643
+ const joined0 = partsP.join(' ');
2644
+ const age = joined0.match(/(\d+)\s*岁/);
2645
+ const workYears = joined0.match(/工作\s*(\d+)\s*年/);
2646
+ const name = txt(first('.name-box .name', basic));
2647
+ const statusBox = first('.res-status-box', basic);
2648
+ const contactTimeRaw = txt(statusBox);
2649
+ const scm = attr(document.querySelector('[data-tlg-scm]'), 'data-tlg-scm');
2650
+
2651
+ const intentRoot = first('#resume-detail-job-exp-info') || first('.resume-detail-job-expectancy-info-wrap');
2652
+ const workExperiences = [];
2653
+ document.querySelectorAll('#resume-detail-work-info .rd-work-item-cont, .resume-detail-work-info .rd-info-tpl-item').forEach((item) => {
2654
+ const cols = readInfoCols(item);
2655
+ const extra = pickUnmappedCols(cols, ['所在部门', '工作地点', '下属人数', '汇报对象', '职责业绩']);
2656
+ workExperiences.push({
2657
+ company: attr(first('.rd-work-comp h5', item), 'title') || txt(first('.rd-work-comp h5', item)),
2658
+ ...parsePeriod(txt(first('.rd-work-time', item))),
2659
+ position: txt(first('.job-name', item)),
2660
+ department: cols['所在部门'] || null,
2661
+ location: cols['工作地点'] || null,
2662
+ subordinates: cols['下属人数'] != null ? Number(cols['下属人数']) : null,
2663
+ reportsTo: cols['汇报对象'] || null,
2664
+ responsibilities: cols['职责业绩'] || null,
2665
+ ...(extra ? { extra } : {}),
2666
+ });
2667
+ });
2668
+ const projects = [];
2669
+ document.querySelectorAll('#resume-detail-project-info .rd-project-item-cont, .resume-detail-project-info .rd-project-item-cont, .resume-detail-project-info .rd-info-tpl-item').forEach((item) => {
2670
+ const cols = readInfoCols(item);
2671
+ const extra = pickUnmappedCols(cols, ['项目职务', '所在公司', '项目描述', '项目职责']);
2672
+ projects.push({
2673
+ name: attr(first('.rd-project-name h5', item), 'title') || txt(first('.rd-project-name h5', item)),
2674
+ ...parsePeriod(txt(first('.rd-project-time', item))),
2675
+ role: cols['项目职务'] || null,
2676
+ company: cols['所在公司'] || null,
2677
+ description: cols['项目描述'] || null,
2678
+ duties: cols['项目职责'] || null,
2679
+ ...(extra ? { extra } : {}),
2680
+ });
2681
+ });
2682
+ const education = [];
2683
+ document.querySelectorAll('#resume-detail-edu-info .rd-edu-info-item, .resume-detail-edu-info .rd-edu-info-item').forEach((item) => {
2684
+ /* 新 DOM 无 .school-special/.school-degree/.school-time,从条目文本 "学校 · 专业 · 学历时间段" 兜底解析 */
2685
+ const raw = txt(item) || '';
2686
+ const segs = raw.split('·').map((s) => clean(s)).filter(Boolean);
2687
+ const periodM = raw.match(/\d{4}\.\d{2}\s*-\s*(?:\d{4}\.\d{2}|至今)/);
2688
+ const segEdu = (segs.length > 2 ? segs[2] : (segs.length > 1 ? segs[1] : '')).match(/(博士|硕士|MBA|本科|大专|专科|高中|中专|初中)/);
2689
+ education.push({
2690
+ school: attr(first('.school-name', item), 'title') || txt(first('.school-name', item)),
2691
+ major: attr(first('.school-special', item), 'title') || txt(first('.school-special', item)) || (segs.length > 1 ? (segs[1].match(/(博士|硕士|MBA|本科|大专|专科|高中|中专|初中)/) ? null : segs[1]) : null),
2692
+ degree: attr(first('.school-degree', item), 'title') || txt(first('.school-degree', item)) || (segEdu ? segEdu[0] : null),
2693
+ period: txt(first('.school-time', item)) || (periodM ? periodM[0] : null),
2694
+ tags: Array.from(item.querySelectorAll('.edu-school-tags .tag-item, .tag-item')).map((t) => txt(t)).filter(Boolean),
2695
+ });
2696
+ });
2697
+
2698
+ /* currentPosition/currentCompany:新 DOM 职位与公司同在独立行(首段=职位,其余=公司);
2699
+ 旧 DOM 公司名在个人行末段,回退取之。脱敏标记优先:个人行末段=「保密」时原样保留
2700
+ (2026-09-03 fixture=事实标准,spec「猎头端脱敏原样保留」,与 liepin-extractor.ts 同步)。 */
2701
+ const currentPosition = partsC.length ? partsC[0] : null;
2702
+ const personalLast = partsP.length ? partsP[partsP.length - 1] : null;
2703
+ const companyFromLine = partsC.length > 1 ? partsC.slice(1).join(' ') : null;
2704
+ const currentCompany = personalLast === '保密' ? '保密' : (companyFromLine || personalLast);
2705
+ /* 实体外区块兜底:未消费的简历区块(培训/证书/荣誉等)原文进 extra.sections */
2706
+ const extraSections = sweepExtraSections();
2707
+ entities.push({
2708
+ entityType: 'talent',
2709
+ platform: 'liepin',
2710
+ resId: resIdFrom(url, scm),
2711
+ name,
2712
+ nameMasked: name ? /\*/.test(name) : undefined,
2713
+ statusTag: txt(first('.user-status-tag', basic)) || (txt(basic).match(/在职[,,][^||]*/) || [])[0] || null,
2714
+ gender: partsP.find((p) => /^(男|女)$/.test(p)) || null,
2715
+ age: age ? Number(age[1]) : null,
2716
+ /* city:先精确匹配已知城市;泛化兜底必须排除学历词——否则「本科」先于「深圳」命中(2026-09-22 实体契约测试实证) */
2717
+ city: partsP.find((p) => /^(北京|上海|广州|深圳|天津|重庆|杭州|南京|苏州|成都|武汉|西安)$/.test(p))
2718
+ || partsP.find((p) => /^[一-龥]{2,6}$/.test(p) && !/^(男|女|保密)$/.test(p) && !/岁|年/.test(p) && !/(博士|硕士|MBA|本科|大专|专科|高中|中专|初中)/.test(p)) || null,
2719
+ degree: partsP.find((p) => /(博士|硕士|MBA|本科|大专|专科|高中|中专|初中)/.test(p)) || null,
2720
+ workYears: workYears ? Number(workYears[1]) : null,
2721
+ currentCompany: currentCompany || null,
2722
+ currentCompanyMasked: partsP.includes('保密') || (currentCompany ? /保密/.test(currentCompany) : false) ? true : undefined,
2723
+ currentPosition,
2724
+ contactTime: contactTimeRaw ? contactTimeRaw.replace(/^方便联系时间[::]\s*/, '') : null,
2725
+ photoUrl: (() => { const img = first('.logo-box img', basic); return img ? abs(img.getAttribute('src')) : null; })(),
2726
+ intent: intentRoot ? {
2727
+ position: attr(first('.tabs .title', intentRoot), 'title') || txt(first('.tabs .title', intentRoot)),
2728
+ salary: txt(first('.tabs .salary', intentRoot)),
2729
+ city: txt(first('.dqname-wrap', intentRoot)),
2730
+ industries: Array.from(intentRoot.querySelectorAll('.lebels-wrap span')).map((s) => txt(s)).filter(Boolean),
2731
+ } : null,
2732
+ workExperiences,
2733
+ projects,
2734
+ education,
2735
+ languages: Array.from(document.querySelectorAll('#resume-detail-lang-info .rd-lang-item .lang-name, .resume-detail-lang-info .rd-lang-item .lang-name')).map((el) => txt(el)).filter(Boolean),
2736
+ /* 我的技能(skill-tag 标签,2026-09-22 实测新 DOM 区块 resume-detail-skill-info) */
2737
+ skills: Array.from(document.querySelectorAll('#resume-detail-skill-info .skill-tag, .resume-detail-skill-info .skill-tag')).map((el) => txt(el)).filter(Boolean),
2738
+ selfEvaluation: txt(first('#resume-detail-self-eva-info .resume-detail-template-cont')) || txt(first('.resume-detail-self-evaluation-info')),
2739
+ statement: txt(first('.c-detail-statement')),
2740
+ hasAntiScrapeWatermark: document.querySelectorAll('.cozue').length > 0,
2741
+ ...(extraSections.length ? { extra: { sections: extraSections } } : {}),
2742
+ url: canonical,
2743
+ source: 'liepin',
2744
+ });
2745
+ } else if (document.querySelector('.detail-resume-card-wrap')) {
2746
+ /* 搜索结果列表 v3(2026-09-22 实测 drift,hmh#57):.detail-resume-card-wrap 卡片,
2747
+ resId 在卡片内 input[name="res_id_encode"];卡片无详情锚点(a[href*=showresumedetail]=0 实证),
2748
+ 正文行「姓名** | NN岁 | 工作N年 | 学历 | 地点…」,详情 URL 按 res_id_encode 构造。
2749
+ 字段解析策略参照 hmh outputs/liepin-uae/driver3.py(真实结果页验证通过实现)。 */
2750
+ document.querySelectorAll('.detail-resume-card-wrap').forEach((card) => {
2751
+ const inp = card.querySelector('input[name="res_id_encode"]');
2752
+ const resId = inp ? inp.value : null;
2753
+ if (!resId) return;
2754
+ const text = clean((card.innerText || card.textContent || '').replace(/\n+/g, ' '));
2755
+ const nameM = text.match(/([^\s|((]+)\*\*/);
2756
+ const ageM = text.match(/(\d+)岁/);
2757
+ const yrsM = text.match(/工作(\d+)年/);
2758
+ const eduM = text.match(/(博士|MBA|硕士|本科|大专|高中|中专|初中)/);
2759
+ const name = nameM ? nameM[1] + '**' : null;
2760
+ const titleBits = [
2761
+ ageM ? ageM[1] + '岁' : null,
2762
+ yrsM ? '工作' + yrsM[1] + '年' : null,
2763
+ eduM ? eduM[1] : null,
2764
+ ].filter(Boolean).join(' ');
2765
+ const href = 'https://h.liepin.com/resume/showresumedetail/?res_id_encode=' + resId + '&resOwnerType=0&sfrom=MY_RESUME';
2766
+ const lead = {
2767
+ url: href,
2768
+ name,
2769
+ titleLine: [name, titleBits].filter(Boolean).join(' | '),
2770
+ resId,
2771
+ education: eduM ? eduM[1] : null,
2772
+ recentWork: [],
2773
+ acquiredAt: null,
2774
+ activity: null,
2775
+ city: null,
2776
+ workYearsText: yrsM ? '工作' + yrsM[1] + '年' : null,
2777
+ expect: null,
2778
+ skills: [],
2779
+ source: 'liepin',
2780
+ };
2781
+ if (!links.talent.some((l) => l.url === lead.url)) links.talent.push(lead);
2782
+ });
2783
+ } else if (document.querySelector('.new-resume-card tbody tr')) {
2784
+ /* 搜索结果列表 v2(2026-09-19 实测 drift):table.new-resume-card > tbody > tr,每行一张卡片
2785
+ (.tlog-common-resume-card);resId 在 checkbox input[name="res_id_encode"];卡片无详情锚点,
2786
+ 详情 URL 按 res_id_encode 构造。旧 .talent-resume-card 分支保留兼容人才管理页。 */
2787
+ document.querySelectorAll('.new-resume-card tbody tr').forEach((tr) => {
2788
+ const card = tr.querySelector('.tlog-common-resume-card');
2789
+ if (!card) return;
2790
+ const cb = tr.querySelector('input[name="res_id_encode"]');
2791
+ const resId = cb ? cb.value : null;
2792
+ if (!resId) return;
2793
+ const cardName = txt(first('.new-resume-personal-name', card));
2794
+ const cardAge = txt(first('.personal-detail-age', card));
2795
+ const cardWorkYears = txt(first('.personal-detail-workyears', card));
2796
+ const cardEdu = txt(first('.personal-detail-edulevel', card));
2797
+ const cardDq = txt(first('.personal-detail-dq', card));
2798
+ const cardActivity = txt(first('.new-resume-offline', card));
2799
+ const cardExpect = txt(first('.personal-expect-content', card)) || txt(first('.new-resume-personal-expect', card));
2800
+ const cardSkills = Array.from(card.querySelectorAll('.new-resume-personal-skills .tag-item, .new-resume-personal-skills span, .new-resume-personal-skills li')).map((s) => txt(s)).filter(Boolean);
2801
+ const href = 'https://h.liepin.com/resume/showresumedetail/?res_id_encode=' + resId + '&resOwnerType=0&sfrom=MY_RESUME';
2802
+ const titleBits = [cardAge, cardWorkYears, cardEdu, cardDq].filter(Boolean).join(' ');
2803
+ const lead = {
2804
+ url: href,
2805
+ name: cardName,
2806
+ titleLine: [cardName, titleBits].filter(Boolean).join(' | '),
2807
+ resId,
2808
+ education: cardEdu,
2809
+ recentWork: [],
2810
+ acquiredAt: null,
2811
+ activity: cardActivity,
2812
+ city: cardDq,
2813
+ workYearsText: cardWorkYears,
2814
+ expect: cardExpect,
2815
+ skills: cardSkills,
2816
+ source: 'liepin',
2817
+ };
2818
+ if (!links.talent.some((l) => l.url === lead.url)) links.talent.push(lead);
2819
+ });
2820
+ } else if (document.querySelector('.talent-resume-card')) {
2821
+ /* 人才管理页/搜索结果列表:.talent-resume-card 卡片(事实源 §7) */
2822
+ document.querySelectorAll('.talent-resume-card').forEach((card) => {
2823
+ const a = first('a.resume-title', card) || first('a[href*="showresumedetail"]', card);
2824
+ const href = a ? abs(attr(a, 'href')) : null;
2825
+ const scm = attr(card, 'data-tlg-scm') || '';
2826
+ const titleLine = attr(a, 'title') || txt(a);
2827
+ const eduItem = Array.from(card.querySelectorAll('.info-item')).find((el) => /最高学历/.test(el.textContent || ''));
2828
+ const workLines = [];
2829
+ card.querySelectorAll('.work-info .info-cont p, .info-cont p').forEach((p) => {
2830
+ const comp = first('.comp', p);
2831
+ const pos = first('.rw-title', p);
2832
+ if (comp || pos) {
2833
+ workLines.push({
2834
+ period: txt(p.querySelector('i')),
2835
+ company: attr(comp, 'title') || txt(comp),
2836
+ position: attr(pos, 'title') || txt(pos),
2837
+ });
2838
+ }
2839
+ });
2840
+ const acquired = txt(first('.right-extension .temp-side', card));
2841
+ const lead = {
2842
+ url: href,
2843
+ name: titleLine ? (titleLine.split(/\s*[||]\s*/)[0] || titleLine) : null,
2844
+ titleLine,
2845
+ resId: resIdFrom(href, scm),
2846
+ education: eduItem ? clean((eduItem.textContent || '').replace(/^最高学历[::]?/, '')) : null,
2847
+ recentWork: workLines,
2848
+ acquiredAt: acquired ? acquired.replace(/^获取时间[::]?/, '') : null,
2849
+ source: 'liepin',
2850
+ };
2851
+ if (lead.url && !links.talent.some((l) => l.url === lead.url)) links.talent.push(lead);
2852
+ });
2853
+ } else {
2854
+ /* 兜底:任何含简历详情链接的页面,提取链接名单 */
2855
+ document.querySelectorAll('a[href*="showresumedetail"]').forEach((a) => {
2856
+ const href = abs(attr(a, 'href'));
2857
+ if (href && !links.talent.some((l) => l.url === href)) {
2858
+ links.talent.push({ url: href, name: txt(a), resId: resIdFrom(href, ''), source: 'liepin' });
2859
+ }
2860
+ });
2861
+ }
2862
+
2863
+ const pageClass = (document.querySelector('#resume-detail-basic-info') || document.querySelector('.resume-detail-basic-info'))
2864
+ ? 'talent'
2865
+ : ((document.querySelector('.detail-resume-card-wrap') || document.querySelector('.new-resume-card tbody tr') || document.querySelector('.talent-resume-card')) ? 'resume-list' : (links.talent.length ? 'list' : 'unknown'));
2866
+ return {
2867
+ url,
2868
+ canonical,
2869
+ pageClass,
2870
+ title: clean(document.title),
2871
+ extractedAt: new Date().toISOString(),
2872
+ entities,
2873
+ links,
2874
+ };
2875
+ })()`;
2876
+ var LIEPIN_FILL_AND_SEARCH_SOURCE = String.raw`(async (P) => {
2877
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
2878
+ const clean = (s) => (s == null ? null : String(s).replace(/\s+/g, ' ').trim() || null);
2879
+ const delay = P.fieldDelayMs && P.fieldDelayMs >= 0 ? P.fieldDelayMs : 1500;
2880
+ const applied = [];
2881
+ const errors = [];
2882
+
2883
+ /* React 受控输入兼容:native setter + input/change 事件 */
2884
+ const setNativeValue = (el, value) => {
2885
+ const setter = Object.getOwnPropertyDescriptor(
2886
+ el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype,
2887
+ 'value',
2888
+ ).set;
2889
+ setter.call(el, value);
2890
+ el.dispatchEvent(new Event('input', { bubbles: true }));
2891
+ el.dispatchEvent(new Event('change', { bubbles: true }));
2892
+ };
2893
+ const setInput = (sel, value) => {
2894
+ const el = document.querySelector(sel);
2895
+ if (!el) { errors.push('missing_input:' + sel); return false; }
2896
+ if (el.disabled) { errors.push('disabled_input:' + sel); return false; }
2897
+ el.focus();
2898
+ setNativeValue(el, String(value));
2899
+ el.blur();
2900
+ return true;
2901
+ };
2902
+ /* 在某个容器内点击包含指定文本的按钮(如"确 定"/"搜 索") */
2903
+ const clickButtonIn = (rootSel, text) => {
2904
+ const root = rootSel ? document.querySelector(rootSel) : document;
2905
+ if (!root) { errors.push('missing_root:' + rootSel); return false; }
2906
+ const btn = Array.from(root.querySelectorAll('button, span, a')).find((b) => {
2907
+ const t = clean(b.textContent);
2908
+ return t && t.replace(/\s+/g, '') === text;
2909
+ });
2910
+ if (!btn) { errors.push('missing_button:' + text + '@' + rootSel); return false; }
2911
+ btn.click();
2912
+ return true;
2913
+ };
2914
+ /* 点击条件 tag(容器可带 #N 出现序号:如 '.sfilter-city#2' = 第 2 个 sfilter-city,即期望城市) */
2915
+ const clickTag = (containerSel, text) => {
2916
+ let sel = containerSel;
2917
+ let idx = 0;
2918
+ const m = String(containerSel).match(/^(.*)#(\d+)$/);
2919
+ if (m) { sel = m[1]; idx = parseInt(m[2], 10) - 1; }
2920
+ const containers = document.querySelectorAll(sel);
2921
+ const container = containers.length > idx ? containers[idx] : null;
2922
+ if (!container) { errors.push('missing_container:' + containerSel); return false; }
2923
+ const tag = Array.from(container.querySelectorAll('label.tag-item, .tag-item, span')).find((el) => {
2924
+ const t = clean(el.textContent);
2925
+ return t && (t === text || t.replace(/\s+/g, '') === String(text).replace(/\s+/g, ''));
2926
+ });
2927
+ if (!tag) {
2928
+ const available = Array.from(container.querySelectorAll('label.tag-item, .tag-item'))
2929
+ .map((el) => clean(el.textContent))
2930
+ .filter(Boolean)
2931
+ .slice(0, 20);
2932
+ errors.push('missing_tag:' + text + '@' + containerSel
2933
+ + (available.length ? '; available=[' + available.join(', ') + ']' : ''));
2934
+ return false;
2935
+ }
2936
+ tag.click();
2937
+ return true;
2938
+ };
2939
+
2940
+ try {
2941
+ /* 1) 关键词:顶部自动补全输入框(与 .search-btn 同容器,placeholder 搜职位/公司/行业等) */
2942
+ if (P.keyword) {
2943
+ const okInput = (() => {
2944
+ const box = document.querySelector('.auto-input-wrap-v3') || document.querySelector('.ant-select-auto-complete');
2945
+ const el = box ? box.querySelector('input.ant-select-selection-search-input') : null;
2946
+ if (!el) { errors.push('missing_input:keyword'); return false; }
2947
+ el.focus();
2948
+ setNativeValue(el, String(P.keyword));
2949
+ el.dispatchEvent(new Event('input', { bubbles: true }));
2950
+ el.dispatchEvent(new Event('change', { bubbles: true }));
2951
+ el.blur();
2952
+ return true;
2953
+ })();
2954
+ if (okInput) applied.push('keyword');
2955
+ await sleep(delay);
2956
+ }
2957
+
2958
+ /* 2) tag 类条件(每次点击后 human-paced) */
2959
+ const tagSteps = [];
2960
+ if (P.city) tagSteps.push(['.sfilter-city', P.city, 'city']);
2961
+ if (P.expectCity) tagSteps.push(['.sfilter-city#2', P.expectCity, 'expectCity']);
2962
+ if (P.workYear) tagSteps.push(['.sfilter-work-year', P.workYear, 'workYear']);
2963
+ if (P.education) tagSteps.push(['.sfilter-edu', P.education, 'education']);
2964
+ if (P.schoolTag) tagSteps.push(['.sfilter-additional', P.schoolTag, 'schoolTag']);
2965
+ if (P.language) tagSteps.push(['.sfilter-lang', P.language, 'language']);
2966
+ for (const [sel, text, key] of tagSteps) {
2967
+ if (clickTag(sel, text)) applied.push(key);
2968
+ await sleep(delay);
2969
+ }
2970
+
2971
+ /* 3) 区间输入类条件:填输入 + 点容器内"确定" */
2972
+ const rangeSteps = [];
2973
+ if (P.ageMin != null || P.ageMax != null) {
2974
+ rangeSteps.push(['#ageLow', P.ageMin, '#ageHigh', P.ageMax, '.age-shadow-box', 'age']);
2975
+ }
2976
+ if (P.wantSalaryMin != null || P.wantSalaryMax != null) {
2977
+ rangeSteps.push(['#wantSalaryLow', P.wantSalaryMin, '#wantSalaryHigh', P.wantSalaryMax, '.sfilter-salary .search-resume-shadow-box', 'wantSalary']);
2978
+ }
2979
+ if (P.nowSalaryMin != null || P.nowSalaryMax != null) {
2980
+ rangeSteps.push(['#nowSalaryLow', P.nowSalaryMin, '#nowSalaryHigh', P.nowSalaryMax, null, 'nowSalary']);
2981
+ }
2982
+ for (const [loSel, lo, hiSel, hi, boxSel, key] of rangeSteps) {
2983
+ let ok = true;
2984
+ if (lo != null) ok = setInput(loSel, lo) && ok;
2985
+ await sleep(Math.max(600, Math.floor(delay / 2)));
2986
+ if (hi != null) ok = setInput(hiSel, hi) && ok;
2987
+ await sleep(Math.max(600, Math.floor(delay / 2)));
2988
+ /* 确定按钮:优先容器内,其次全局匹配(目前年薪容器无专属 class) */
2989
+ if (ok && !clickButtonIn(boxSel, '确定')) {
2990
+ ok = clickButtonIn(null, '确定');
2991
+ }
2992
+ if (ok) applied.push(key);
2993
+ await sleep(delay);
2994
+ }
2995
+
2996
+ /* 4) 工作年限自定义:先点"自定义"tag 解锁输入,再填 + 确定 */
2997
+ if (P.workYearsMin != null || P.workYearsMax != null) {
2998
+ if (clickTag('.sfilter-work-year', '自定义')) {
2999
+ applied.push('workYearCustom');
3000
+ await sleep(delay);
3001
+ let ok = true;
3002
+ if (P.workYearsMin != null) ok = setInput('#workYearsLow', P.workYearsMin) && ok;
3003
+ await sleep(Math.max(600, Math.floor(delay / 2)));
3004
+ if (P.workYearsMax != null) ok = setInput('#workYearsHigh', P.workYearsMax) && ok;
3005
+ await sleep(Math.max(600, Math.floor(delay / 2)));
3006
+ if (ok && clickButtonIn('.year-custom-box', '确定')) applied.push('workYears');
3007
+ }
3008
+ await sleep(delay);
3009
+ }
3010
+
3011
+ /* 5) 复选框:海外工作经验 / 管理经验 */
3012
+ for (const [name, key] of [['workAbroad', 'workAbroad'], ['managerial', 'managerial']]) {
3013
+ if (P[key]) {
3014
+ const input = document.querySelector('input[name="' + name + '"]');
3015
+ if (input && !input.checked) {
3016
+ const wrapper = input.closest('label') || input;
3017
+ wrapper.click();
3018
+ applied.push(key);
3019
+ await sleep(delay);
3020
+ } else if (!input) {
3021
+ errors.push('missing_checkbox:' + name);
3022
+ }
3023
+ }
3024
+ }
3025
+
3026
+ /* 6) 提交搜索前停顿(类人工审阅),点击顶部"搜 索" */
3027
+ await sleep(Math.max(1500, delay));
3028
+ const searchBtn = document.querySelector('button.search-btn');
3029
+ if (!searchBtn) { errors.push('missing_button:search'); return { applied, errors, submitted: false }; }
3030
+ searchBtn.click();
3031
+ applied.push('submit');
3032
+ return { applied, errors, submitted: true };
3033
+ } catch (e) {
3034
+ errors.push('fill_exception:' + String((e && e.message) || e));
3035
+ return { applied, errors, submitted: false };
3036
+ }
3037
+ })`;
3038
+ var LIEPIN_RESULT_WAIT_SOURCE = String.raw`(() => {
3039
+ return new Promise((resolve) => {
3040
+ const t0 = Date.now();
3041
+ const tick = () => {
3042
+ let hit = false;
3043
+ try {
3044
+ hit = !!(document.querySelector('.talent-resume-card') || document.querySelector('a[href*="showresumedetail"]') || document.querySelector('.new-resume-card tbody tr') || document.querySelector('input[name="res_id_encode"]'));
3045
+ if (!hit && /没有找到|暂无相关|0\s*条结果|共\s*0\s*条/.test(document.body ? document.body.innerText : '')) return resolve('empty');
3046
+ } catch (e) {}
3047
+ if (hit) return resolve(true);
3048
+ if (Date.now() - t0 > 20000) return resolve(false);
3049
+ setTimeout(tick, 400);
3050
+ };
3051
+ tick();
3052
+ });
3053
+ })()`;
3054
+ var LIEPIN_NEXT_CLICK_SOURCE = String.raw`(async () => {
3055
+ const next = document.querySelector('.ant-pagination-next button')
3056
+ || document.querySelector('.ant-pagination-next')
3057
+ || document.querySelector('button[aria-label="下一页"]');
3058
+ if (!next || next.getAttribute('aria-disabled') === 'true' || next.disabled) return false;
3059
+ next.click();
3060
+ return true;
3061
+ })()`;
3062
+ function liepinBlockedCode(blocked) {
3063
+ if (blocked === "LOGIN_WALL") return "LIE_PIN_LOGIN_WALL";
3064
+ if (blocked === "BLANK_KILL") return "LIE_PIN_BLANK_KILL";
3065
+ return "LIE_PIN_CAPTCHA";
3066
+ }
3067
+ async function runLiepinSearch(args, log, ctx) {
3068
+ const keyword = typeof args.keyword === "string" ? args.keyword.trim() : "";
3069
+ if (!keyword) throw new Error("liepin_search_requires_keyword");
3070
+ const maxPages = Math.min(Math.max(Number(args.maxPages) || 1, 1), 5);
3071
+ const keepTab = args.keepTab === true;
3072
+ const fieldDelayMs = typeof args.delayMs === "number" ? Number(args.delayMs) : 1500;
3073
+ const fillParams = { keyword, fieldDelayMs };
3074
+ for (const k of [
3075
+ "city",
3076
+ "expectCity",
3077
+ "workYear",
3078
+ "education",
3079
+ "schoolTag",
3080
+ "language",
3081
+ "ageMin",
3082
+ "ageMax",
3083
+ "wantSalaryMin",
3084
+ "wantSalaryMax",
3085
+ "nowSalaryMin",
3086
+ "nowSalaryMax",
3087
+ "workYearsMin",
3088
+ "workYearsMax",
3089
+ "workAbroad",
3090
+ "managerial"
3091
+ ]) {
3092
+ if (args[k] !== void 0) fillParams[k] = args[k];
3093
+ }
3094
+ const searchUrl = "https://h.liepin.com/search/getConditionItem";
3095
+ const existing = await chrome.tabs.query({ url: "https://h.liepin.com/search/getConditionItem*" }).catch(() => []);
3096
+ const reusable = existing.find((t) => !!t.id && !!t.url && t.url.startsWith("https://h.liepin.com/search/"));
3097
+ let tab;
3098
+ let reusedExisting = false;
3099
+ if (reusable && reusable.id != null) {
3100
+ tab = reusable;
3101
+ reusedExisting = true;
3102
+ log("info", "hm.liepin.search reusing existing search tab", { tabId: tab.id, url: tab.url });
3103
+ } else {
3104
+ tab = await chrome.tabs.create({ url: searchUrl, active: false });
3105
+ log("info", "hm.liepin.search tab created", { tabId: tab.id, url: searchUrl, maxPages, keyword });
3106
+ }
3107
+ if (ctx.sessionId && !ctx.explicitTab && tab.id) {
3108
+ addTabToSessionGroup(ctx.sessionId, ctx.groupTitle, tab).catch(
3109
+ (err3) => log("warn", "hm.liepin.search addTabToSessionGroup failed", { error: err3.message })
3110
+ );
3111
+ }
3112
+ const leads = /* @__PURE__ */ new Map();
3113
+ const applied = [];
3114
+ const fillErrors = [];
3115
+ let pagesScanned = 0;
3116
+ const assertNotBlocked = async (phase) => {
3117
+ if (tab.id == null) return;
3118
+ const t = await chrome.tabs.get(tab.id).catch(() => null);
3119
+ if (!t) throw new Error(`tab closed while ${phase}`);
3120
+ if (!t.url || t.url === "about:blank") {
3121
+ throw new ControlBlockedError("LIE_PIN_BLANK_KILL", `liepin search aborted at ${phase}: page killed to about:blank (secscan)`);
3122
+ }
3123
+ const blocked = await cdpEvaluateTransient(tab.id, LIEPIN_WALL_CHECK_SOURCE).catch(() => null);
3124
+ if (blocked) {
3125
+ const code = liepinBlockedCode(blocked);
3126
+ log("warn", "hm.liepin.search blocked by liepin risk control", { blocked, code, phase });
3127
+ throw new ControlBlockedError(code, `liepin search aborted at ${phase}: ${blocked}`);
3128
+ }
3129
+ };
3130
+ try {
3131
+ if (tab.id == null) throw new Error("failed_to_create_tab");
3132
+ await waitTabLoaded(tab.id, 3e4);
3133
+ await sleep(1500);
3134
+ await assertNotBlocked("after-load");
3135
+ const fillExpr = "(() => { const P = " + JSON.stringify(fillParams) + "; return (" + LIEPIN_FILL_AND_SEARCH_SOURCE + ")(P); })()";
3136
+ const fillResult = await cdpEvaluateTransient(tab.id, fillExpr, 6e4);
3137
+ applied.push(...fillResult?.applied ?? []);
3138
+ fillErrors.push(...fillResult?.errors ?? []);
3139
+ if (!fillResult?.submitted) {
3140
+ log("warn", "hm.liepin.search fill did not submit", { fillErrors });
3141
+ throw new Error("liepin_search_fill_failed: " + fillErrors.join(";"));
3142
+ }
3143
+ log("info", "hm.liepin.search form submitted", { applied, fillErrors });
3144
+ for (let page = 1; page <= maxPages; page++) {
3145
+ if (tab.id == null) break;
3146
+ const waited = await cdpEvaluateTransient(tab.id, LIEPIN_RESULT_WAIT_SOURCE, 3e4).catch(() => false);
3147
+ await sleep(1200);
3148
+ await assertNotBlocked(`page-${page}`);
3149
+ if (waited === "empty") {
3150
+ log("info", "hm.liepin.search empty result", { page });
3151
+ break;
3152
+ }
3153
+ const extracted = await cdpEvaluateTransient(tab.id, LIEPIN_EXTRACT_SOURCE, 3e4);
3154
+ const pageLeads = extracted?.links?.talent || [];
3155
+ for (const l of pageLeads) {
3156
+ if (l.url && !leads.has(l.url)) {
3157
+ leads.set(l.url, {
3158
+ url: l.url,
3159
+ name: l.name ?? null,
3160
+ titleLine: l.titleLine ?? null,
3161
+ resId: l.resId ?? null,
3162
+ page,
3163
+ source: "liepin"
3164
+ });
3165
+ }
3166
+ }
3167
+ pagesScanned = page;
3168
+ log("info", "hm.liepin.search page extracted", { page, pageLeads: pageLeads.length, total: leads.size });
3169
+ if (page >= maxPages) break;
3170
+ const clicked = await cdpEvaluateTransient(tab.id, LIEPIN_NEXT_CLICK_SOURCE).catch(() => false);
3171
+ if (!clicked) break;
3172
+ await sleep(3e3 + Math.random() * 2e3);
3173
+ await assertNotBlocked(`page-${page}-next`);
3174
+ }
3175
+ } finally {
3176
+ if (!keepTab && !reusedExisting && tab.id != null) {
3177
+ await closeTabGracefully(tab.id);
3178
+ }
3179
+ }
3180
+ return {
3181
+ keyword,
3182
+ searchUrl,
3183
+ pagesScanned,
3184
+ leadCount: leads.size,
3185
+ leads: Array.from(leads.values()),
3186
+ applied,
3187
+ fillErrors,
3188
+ keepTab,
3189
+ reusedExisting,
3190
+ // tabId 如实记录:自建 tab 关闭后置 null;复用 tab 一定返回其 id(调用方可追踪/不再开)
3191
+ tabId: reusedExisting ? tab.id ?? null : keepTab ? tab.id ?? null : null
3192
+ };
3193
+ }
3194
+ var HIREDCHINA_EXTRACT_SOURCE = String.raw`(async () => {
3195
+ const waitFor = (fn, timeout, interval) => new Promise((resolve) => {
3196
+ const t0 = Date.now();
3197
+ const tick = () => {
3198
+ let v = null;
3199
+ try { v = fn(); } catch (e) {}
3200
+ if (v) return resolve(v);
3201
+ if (Date.now() - t0 > (timeout || 8000)) return resolve(null);
3202
+ setTimeout(tick, interval || 250);
3203
+ };
3204
+ tick();
3205
+ });
3206
+ if (document.readyState !== 'complete') {
3207
+ await waitFor(() => document.readyState === 'complete', 12000, 200);
3208
+ }
3209
+ const path = location.pathname;
3210
+ const jsonLdAll = () => Array.from(document.querySelectorAll('script[type="application/ld+json"]'))
3211
+ .map((s) => { try { return JSON.parse(s.textContent); } catch (e) { return null; } })
3212
+ .filter(Boolean)
3213
+ .flatMap((x) => (Array.isArray(x) ? x : [x]));
3214
+
3215
+ // i18n key 归一化:support.nationalitie.united.states.of.america → United States Of America
3216
+ const normalizeLocality = (s) => {
3217
+ if (!s || typeof s !== 'string') return null;
3218
+ if (/\s/.test(s)) return s; // 已是可读地名(含空格)
3219
+ if (!/^[a-z0-9._-]+$/i.test(s)) return s;
3220
+ // 剥离 i18n 命名空间前缀(如 support.nationalitie.),保留完整国名段
3221
+ const stripped = s.replace(/^support\.nationalitie\./i, '');
3222
+ return stripped.replace(/[._-]+/g, ' ')
3223
+ .split(' ')
3224
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
3225
+ .join(' ');
3226
+ };
3227
+
3228
+ let pageClass = 'unknown';
3229
+ if (/^\/[a-z]{2}(-[A-Z]{2})?\/job\/[^/]+/.test(path)) pageClass = 'job';
3230
+ else if (/^\/([a-z]{2}(-[A-Z]{2})?\/)?talents\/?$/.test(path)) pageClass = 'talents';
3231
+ else pageClass = 'list';
3232
+
3233
+ const canonical = (document.querySelector('link[rel="canonical"]') || {}).href || location.href.split('?')[0];
3234
+ const entities = [];
3235
+ const links = { talent: [], company: [], job: [] };
3236
+
3237
+ if (pageClass === 'job') {
3238
+ await waitFor(() => document.querySelector('script[type="application/ld+json"], h1'), 9000);
3239
+ const job = jsonLdAll().find((x) => x['@type'] === 'JobPosting');
3240
+ const org = job && job.hiringOrganization ? job.hiringOrganization : null;
3241
+ const salary = job && job.baseSalary ? job.baseSalary : null;
3242
+ const salaryValue = salary && salary.value ? salary.value : {};
3243
+ const hasSalary = salary && (salaryValue.minValue > 0 || salaryValue.maxValue > 0);
3244
+ let location = null;
3245
+ if (job && job.jobLocation) {
3246
+ const loc = Array.isArray(job.jobLocation) ? job.jobLocation[0] : job.jobLocation;
3247
+ location = loc && loc.address ? normalizeLocality(loc.address.addressLocality) : null;
3248
+ }
3249
+ entities.push({
3250
+ entityType: 'job',
3251
+ title: (job && job.title) || (document.querySelector('h1') || {}).textContent || null,
3252
+ company: (org && org.name) || null,
3253
+ location: location,
3254
+ description: job && job.description ? String(job.description).slice(0, 2000) : null,
3255
+ employmentType: (job && job.employmentType) || null,
3256
+ datePosted: (job && job.datePosted) || null,
3257
+ salaryCurrency: hasSalary ? (salary.currency || null) : null,
3258
+ salaryMin: hasSalary ? (salaryValue.minValue || null) : null,
3259
+ salaryMax: hasSalary ? (salaryValue.maxValue || null) : null,
3260
+ salaryUnit: hasSalary ? (salaryValue.unitText || null) : null,
3261
+ url: canonical, source: 'hiredchina',
3262
+ });
3263
+ if (org && org.name) {
3264
+ entities.push({
3265
+ entityType: 'company',
3266
+ name: org.name,
3267
+ logo: org.logo || null,
3268
+ url: canonical, source: 'hiredchina',
3269
+ });
3270
+ }
3271
+ } else if (pageClass === 'talents') {
3272
+ // 卡片右列锚点:class 含 col-span-5 且文本含"工作经历";父级即卡片根(左右两列 grid)
3273
+ const rightCols = Array.from(document.querySelectorAll('div[class*="col-span-5"]'))
3274
+ .filter((el) => /工作经历|Work Experience/i.test(el.textContent || ''));
3275
+ const fallbackCols = rightCols.length
3276
+ ? rightCols
3277
+ : Array.from(document.querySelectorAll('div'))
3278
+ .filter((el) => {
3279
+ const t = el.textContent || '';
3280
+ return /工作经历/.test(t) && t.length < 3000
3281
+ && Array.from(el.querySelectorAll('span')).some((s) => (s.textContent || '').trim() === '工作经历')
3282
+ && el.querySelectorAll('div[class*="mb-1"]').length >= 2;
3283
+ });
3284
+ const cards = (fallbackCols.length ? fallbackCols : rightCols)
3285
+ .map((rc) => rc.parentElement)
3286
+ .filter(Boolean);
3287
+ for (const card of cards) {
3288
+ const lines = (card.innerText || '').split('\n').map((s) => s.trim()).filter(Boolean);
3289
+ const name = lines[0] || null;
3290
+ if (!name || name.length > 60) continue;
3291
+ const gaIdx = lines.findIndex((l) => /^[男女] · \d+$/.test(l));
3292
+ const ga = gaIdx >= 0 ? lines[gaIdx].split(' · ') : [];
3293
+ const at = (i) => (gaIdx >= 0 && gaIdx + i < lines.length ? lines[gaIdx + i] : null);
3294
+ const introIdx = lines.findIndex((l) => l === '💬');
3295
+ const intro = introIdx >= 0 && lines[introIdx + 1] && lines[introIdx + 1].length < 300 ? lines[introIdx + 1] : null;
3296
+ const cnIdx = lines.findIndex((l) => l.startsWith('中文水平'));
3297
+ const chineseLevel = cnIdx >= 0 ? lines[cnIdx].replace('中文水平: ', '').replace('中文水平:', '') : null;
3298
+ const workIdx = lines.indexOf('工作经历');
3299
+ const eduIdx = lines.indexOf('教育经历');
3300
+ const workLines = workIdx >= 0 ? lines.slice(workIdx + 1, eduIdx > workIdx ? eduIdx : lines.length) : [];
3301
+ const works = [];
3302
+ for (let i = 0; i + 1 < workLines.length; i += 2) {
3303
+ if (/^\d{4}\.\d{2}/.test(workLines[i + 1] || '')) {
3304
+ works.push({ title: workLines[i], period: workLines[i + 1], company: workLines[i + 2] || null });
3305
+ i += 1;
3306
+ }
3307
+ }
3308
+ const eduLines = eduIdx >= 0 ? lines.slice(eduIdx + 1) : [];
3309
+ const educations = [];
3310
+ for (let i = 0; i + 1 < eduLines.length; i += 2) {
3311
+ if (/^\d{4}$/.test(eduLines[i + 1] || '')) {
3312
+ educations.push({ school: eduLines[i], year: eduLines[i + 1], degree: eduLines[i + 2] || null });
3313
+ i += 1;
3314
+ }
3315
+ }
3316
+ entities.push({
3317
+ entityType: 'talent',
3318
+ name: name,
3319
+ gender: ga[0] || null,
3320
+ age: ga[1] || null,
3321
+ nationality: at(1),
3322
+ location: at(2),
3323
+ degree: at(3),
3324
+ headline: at(4),
3325
+ intro: intro,
3326
+ chineseLevel: chineseLevel,
3327
+ works: works.slice(0, 5),
3328
+ educations: educations.slice(0, 3),
3329
+ url: canonical + '#talent=' + encodeURIComponent(name) + '|' + encodeURIComponent(at(1) || ''),
3330
+ source: 'hiredchina',
3331
+ });
3332
+ }
3333
+ } else {
3334
+ // list:提取 /<lang>/job/ 链接
3335
+ const seen = new Set();
3336
+ for (const a of Array.from(document.querySelectorAll('a[href]'))) {
3337
+ const href = a.href || '';
3338
+ const m = href.match(/hiredchina\.com\/[a-z]{2}(-[A-Z]{2})?\/job\/([^/?#]+)/);
3339
+ if (m && !seen.has(m[2])) {
3340
+ seen.add(m[2]);
3341
+ links.job.push({ url: href.split('?')[0], title: (a.textContent || '').trim().split('\n')[0] || m[2] });
3342
+ }
3343
+ }
3344
+ links.job = links.job.slice(0, 10);
3345
+ }
3346
+
3347
+ return {
3348
+ pageClass: pageClass,
3349
+ url: location.href,
3350
+ canonical: canonical,
3351
+ title: document.title,
3352
+ entities: entities,
3353
+ links: links,
3354
+ source: 'hiredchina',
3355
+ extractedAt: new Date().toISOString(),
3356
+ };
3357
+ })()`;
3358
+ function err2(message, code) {
3359
+ return { status: "error", code, message };
3360
+ }
3361
+ var TAB_KILLED_SECSCAN_CODE = "tab_killed_secscan";
3362
+ var TAB_KILLED_SECSCAN_MESSAGE = "Tab was killed to about:blank (likely anti-bot secscan). Recovery: open a new tab (cp.tab.new) and navigate again.";
3363
+ function isBlankKilledUrl(url) {
3364
+ return !url || url === "about:blank";
3365
+ }
3366
+ async function handleCookiesCommand(args) {
3367
+ const action = typeof args.action === "string" ? args.action : "get";
3368
+ const domain = typeof args.domain === "string" ? args.domain : void 0;
3369
+ const url = typeof args.url === "string" ? args.url : void 0;
3370
+ const name = typeof args.name === "string" ? args.name : void 0;
3371
+ if (action === "get") {
3372
+ if (!domain && !url) {
3373
+ throw new Error("Cookie scope required: provide domain or url to avoid dumping all cookies");
3374
+ }
3375
+ const details = {};
3376
+ if (domain) details.domain = domain;
3377
+ if (url) details.url = url;
3378
+ if (name) details.name = name;
3379
+ const cookies = await chrome.cookies.getAll(details);
3380
+ return {
3381
+ action: "get",
3382
+ count: cookies.length,
3383
+ cookies: cookies.map((c) => ({
3384
+ name: c.name,
3385
+ value: c.value,
3386
+ domain: c.domain,
3387
+ path: c.path,
3388
+ secure: c.secure,
3389
+ httpOnly: c.httpOnly,
3390
+ sameSite: c.sameSite,
3391
+ expirationDate: c.expirationDate
3392
+ }))
3393
+ };
3394
+ }
3395
+ if (action === "set") {
3396
+ if (!name || typeof args.value !== "string" || !domain && !url) {
3397
+ throw new Error("cp.cookies set requires name, value and (domain or url)");
3398
+ }
3399
+ const details = {
3400
+ name,
3401
+ value: args.value,
3402
+ url: url ?? `https://${(domain ?? "").replace(/^\./, "")}/`
3403
+ };
3404
+ if (domain) details.domain = domain;
3405
+ if (typeof args.path === "string") details.path = args.path;
3406
+ if (args.secure === true) details.secure = true;
3407
+ if (args.httpOnly === true) details.httpOnly = true;
3408
+ if (typeof args.expirationDate === "number") details.expirationDate = args.expirationDate;
3409
+ const cookie = await chrome.cookies.set(details);
3410
+ return { action: "set", cookie };
3411
+ }
3412
+ if (action === "delete") {
3413
+ if (!name || !domain && !url) {
3414
+ throw new Error("cp.cookies delete requires name and (domain or url)");
3415
+ }
3416
+ const targetUrl = url ?? `https://${(domain ?? "").replace(/^\./, "")}/`;
3417
+ const deleted = await chrome.cookies.remove({ name, url: targetUrl });
3418
+ return { action: "delete", deleted };
3419
+ }
3420
+ throw new Error(`cookies_unknown_action: ${action}`);
3421
+ }
3422
+ async function closeTabGracefully(tabId) {
3423
+ await detachIfAttached(tabId);
3424
+ await Promise.race([
3425
+ chrome.tabs.remove(tabId),
3426
+ new Promise((_, reject) => setTimeout(() => reject(new Error("tab_remove_timeout")), 5e3))
3427
+ ]).catch(() => {
3428
+ });
3429
+ }
3430
+ async function handleTabCommand(command, args, log) {
3431
+ const tabArg = args.tab ?? args.targetId ?? args.page;
3432
+ switch (command) {
3433
+ case "cp.tab.list": {
3434
+ const tabs = await chrome.tabs.query({});
3435
+ return tabs.map((t, index) => ({
3436
+ index,
3437
+ page: String(t.id),
3438
+ url: t.url ?? "",
3439
+ title: t.title ?? "",
3440
+ active: t.active
3441
+ }));
3442
+ }
3443
+ case "cp.tab.new": {
3444
+ const url = typeof args.url === "string" && args.url ? args.url : "about:blank";
3445
+ const active = args.active === true;
3446
+ log("debug", "cp.tab.new creating tab", { url, active });
3447
+ const tab = await chrome.tabs.create({ url, active });
3448
+ log("info", "cp.tab.new created", { page: String(tab.id), url: tab.url ?? url, active });
3449
+ return { page: String(tab.id), url: tab.url ?? url };
3450
+ }
3451
+ case "cp.tab.open": {
3452
+ const url = typeof args.url === "string" && args.url.trim() ? args.url.trim() : "";
3453
+ if (!url) throw new Error("tab_open_requires_url");
3454
+ if (!/^https?:\/\//.test(url)) throw new Error("tab_open_requires_http_url: " + url);
3455
+ const active = args.active === true;
3456
+ const pattern = url.includes("*") ? url : url + "*";
3457
+ const candidates = await chrome.tabs.query({ url: pattern }).catch(() => []);
3458
+ const hit = candidates.find((t) => !!t.id && !!t.url && t.url.startsWith("http"));
3459
+ if (hit && hit.id != null) {
3460
+ await chrome.tabs.update(hit.id, { active }).catch(() => void 0);
3461
+ log("info", "cp.tab.open reused existing tab", { page: String(hit.id), url: hit.url, active });
3462
+ return { page: String(hit.id), url: hit.url, reused: true };
3463
+ }
3464
+ const tab = await chrome.tabs.create({ url, active });
3465
+ log("info", "cp.tab.open created tab", { page: String(tab.id), url: tab.url ?? url, active });
3466
+ return { page: String(tab.id), url: tab.url ?? url, reused: false };
3467
+ }
3468
+ case "cp.tab.select": {
3469
+ if (!tabArg) throw new Error("tab_select_requires_tab_id");
3470
+ const tabId = Number(tabArg);
3471
+ log("debug", "cp.tab.select activating tab", { tabId });
3472
+ await chrome.tabs.update(tabId, { active: true });
3473
+ log("info", "cp.tab.select activated", { tabId });
3474
+ return { selected: true, page: String(tabArg) };
3475
+ }
3476
+ case "cp.tab.close": {
3477
+ if (!tabArg) throw new Error("tab_close_requires_tab_id");
3478
+ await closeTabGracefully(Number(tabArg));
3479
+ return { closed: true, page: String(tabArg) };
3480
+ }
3481
+ case "cp.tab.bind": {
3482
+ const t = tabArg ? await getControlTab(tabArg) : await getActiveTab();
3483
+ if (!t?.id) throw new Error("no_tab_to_bind");
3484
+ boundTabId = t.id;
3485
+ return { bound: true, page: String(t.id), url: t.url, title: t.title };
3486
+ }
3487
+ case "cp.tab.unbind": {
3488
+ const had = boundTabId;
3489
+ boundTabId = null;
3490
+ return { unbound: true, had: had != null ? String(had) : null };
3491
+ }
3492
+ default:
3493
+ throw new Error(`unknown_command: ${command}`);
3494
+ }
3495
+ }
3496
+
3497
+ // src/shared/etld.ts
3498
+ var MULTI_PART_SUFFIXES = /* @__PURE__ */ new Set([
3499
+ "com.cn",
3500
+ "net.cn",
3501
+ "org.cn",
3502
+ "gov.cn",
3503
+ "edu.cn",
3504
+ "com.hk",
3505
+ "com.tw",
3506
+ "com.sg",
3507
+ "com.my",
3508
+ "com.au",
3509
+ "co.nz",
3510
+ "co.jp",
3511
+ "co.kr",
3512
+ "co.in",
3513
+ "co.uk"
3514
+ ]);
3515
+ function etldPlusOne(hostname) {
3516
+ const host = hostname.toLowerCase().replace(/\.$/, "");
3517
+ const parts = host.split(".");
3518
+ if (parts.length <= 2) return host;
3519
+ const lastTwo = parts.slice(-2).join(".");
3520
+ if (MULTI_PART_SUFFIXES.has(lastTwo)) return parts.slice(-3).join(".");
3521
+ return lastTwo;
3522
+ }
3523
+
3524
+ // src/background/sites.ts
3525
+ var DYNAMIC_SCRIPT_ID = "hm-auto-capture";
3526
+ var RECRUITING_HINTS = [
3527
+ "jobs",
3528
+ "careers",
3529
+ "hiring",
3530
+ "recruit",
3531
+ "vacancy",
3532
+ "vacancies",
3533
+ "openings",
3534
+ "\u804C\u4F4D",
3535
+ "\u62DB\u8058",
3536
+ "\u4EBA\u624D",
3537
+ "\u730E\u5934",
3538
+ "\u7B80\u5386",
3539
+ "\u6C42\u804C",
3540
+ "talent",
3541
+ "headhunter",
3542
+ "linkedin",
3543
+ "zhipin",
3544
+ "liepin",
3545
+ "51job",
3546
+ "glassdoor",
3547
+ "indeed",
3548
+ "xing",
3549
+ "stepstone"
3550
+ ];
3551
+ var PATH_CLASS_HINTS = [
3552
+ { re: /\/(in|profile|pub|resume|cv)\b/i, cls: "talent" },
3553
+ { re: /\/(jobs|job|vacancies|positions|careers)\b/i, cls: "job" },
3554
+ { re: /\/(company|companies|employer)\b/i, cls: "company" },
3555
+ { re: /\/(search|talent|people|directory)\b/i, cls: "list" }
3556
+ ];
3557
+ var sitePolicyMap = /* @__PURE__ */ new Map();
3558
+ function getSitePolicy(hostname) {
3559
+ return sitePolicyMap.get(etldPlusOne(hostname)) ?? { record: false, parse: false };
3560
+ }
3561
+ async function syncSites() {
3562
+ let entries = [];
3563
+ try {
3564
+ const res = await fetch(`${SERVER_HTTP_URL}/sites`);
3565
+ if (res.ok) {
3566
+ const data = await res.json();
3567
+ entries = data.sites || data || [];
3568
+ }
3569
+ } catch {
3570
+ return [];
3571
+ }
3572
+ const next = /* @__PURE__ */ new Map();
3573
+ for (const e of entries) {
3574
+ if (!e.enabled) continue;
3575
+ next.set(e.domain, {
3576
+ record: true,
3577
+ parse: true,
3578
+ adapterPlatform: e.adapterPlatform || e.platform
3579
+ });
3580
+ }
3581
+ sitePolicyMap.clear();
3582
+ for (const [k, v] of next) sitePolicyMap.set(k, v);
3583
+ const domains = [...sitePolicyMap.keys()];
3584
+ await registerDynamicScripts(domains);
3585
+ return entries;
3586
+ }
3587
+ async function registerDynamicScripts(domains) {
3588
+ try {
3589
+ await chrome.scripting.unregisterContentScripts({ ids: [DYNAMIC_SCRIPT_ID] });
3590
+ } catch {
3591
+ }
3592
+ if (!domains.length) return;
3593
+ const matches = domains.map((d) => `https://*.${d}/*`);
3594
+ await chrome.scripting.registerContentScripts([
3595
+ {
3596
+ id: DYNAMIC_SCRIPT_ID,
3597
+ matches,
3598
+ js: ["content/index.js"],
3599
+ runAt: "document_idle",
3600
+ persistAcrossSessions: true
3601
+ }
3602
+ ]);
3603
+ console.log(`[HunterMate] dynamic content scripts registered for ${domains.length} domains`);
3604
+ }
3605
+ function isProductionDomain(hostname) {
3606
+ const h = hostname.toLowerCase().trim();
3607
+ if (!h) return false;
3608
+ if (h === "localhost" || h === "127.0.0.1" || h === "[::1]" || h === "::1") return false;
3609
+ if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h)) return false;
3610
+ if (/^\[?[0-9a-f:]+\]?$/.test(h)) return false;
3611
+ if (!h.includes(".") || h.startsWith(".") || h.endsWith(".")) return false;
3612
+ return true;
3613
+ }
3614
+ var discovered = /* @__PURE__ */ new Set();
3615
+ function watchForDiscovery() {
3616
+ chrome.tabs.onUpdated.addListener((_tabId, changeInfo, tab) => {
3617
+ if (changeInfo.status !== "complete" || !tab.url) return;
3618
+ maybeDiscover(tab.url, changeInfo.title ?? tab.title ?? "");
3619
+ });
3620
+ chrome.tabs.onActivated.addListener(async (activeInfo) => {
3621
+ try {
3622
+ const tab = await chrome.tabs.get(activeInfo.tabId);
3623
+ if (tab.url) maybeDiscover(tab.url, tab.title ?? "");
3624
+ } catch {
3625
+ }
3626
+ });
3627
+ }
3628
+ function maybeDiscover(url, title) {
3629
+ let hostname;
3630
+ try {
3631
+ const u = new URL(url);
3632
+ if (u.protocol !== "https:" && u.protocol !== "http:") return;
3633
+ hostname = u.hostname;
3634
+ } catch {
3635
+ return;
3636
+ }
3637
+ if (!isProductionDomain(hostname)) return;
3638
+ const domain = etldPlusOne(hostname);
3639
+ if (discovered.has(domain) || sitePolicyMap.has(domain)) return;
3640
+ const haystack = `${title} ${url}`.toLowerCase();
3641
+ const hit = RECRUITING_HINTS.find((k) => haystack.includes(k));
3642
+ if (!hit) return;
3643
+ const pathHint = PATH_CLASS_HINTS.find(({ re }) => re.test(new URL(url).pathname));
3644
+ const payload = {
3645
+ domain,
3646
+ url,
3647
+ title,
3648
+ evidence: `keyword:${hit}`,
3649
+ pageClass: pathHint?.cls ?? "unknown"
3650
+ };
3651
+ discovered.add(domain);
3652
+ fetch(`${SERVER_HTTP_URL}/sites/discover`, {
3653
+ method: "POST",
3654
+ headers: { "Content-Type": "application/json" },
3655
+ body: JSON.stringify(payload)
3656
+ }).catch(() => {
3657
+ discovered.delete(domain);
3658
+ });
3659
+ }
3660
+
3661
+ // ../../node_modules/.pnpm/@chromepilot+logger-kit@0.1.4_@nestjs+common@10.4.22_class-transformer@0.5.1_class-validator@_legcruqwso5xb42h6lrzoll4b4/node_modules/@chromepilot/logger-kit/dist/client/logger.js
3662
+ function stringifyArg(arg) {
3663
+ if (typeof arg === "string")
3664
+ return arg;
3665
+ try {
3666
+ return JSON.stringify(arg);
3667
+ } catch {
3668
+ return String(arg);
3669
+ }
3670
+ }
3671
+ var Logger = class {
3672
+ opts;
3673
+ buffer = [];
3674
+ timer = null;
3675
+ originalConsole = {};
3676
+ maxBufferSize;
3677
+ flushIntervalMs;
3678
+ constructor(opts) {
3679
+ this.opts = opts;
3680
+ this.maxBufferSize = opts.maxBufferSize ?? 50;
3681
+ this.flushIntervalMs = opts.flushIntervalMs ?? 1e3;
3682
+ }
3683
+ resolveSessionId() {
3684
+ const sid = typeof this.opts.sessionId === "function" ? this.opts.sessionId() : this.opts.sessionId;
3685
+ return sid || "none";
3686
+ }
3687
+ async resolveTabInfo() {
3688
+ try {
3689
+ return await this.opts.getTabInfo?.() ?? {};
3690
+ } catch {
3691
+ return {};
3692
+ }
3693
+ }
3694
+ enqueue(level, message, meta) {
3695
+ this.resolveTabInfo().then((info) => {
3696
+ this.buffer.push({
3697
+ sessionId: this.resolveSessionId(),
3698
+ context: this.opts.context,
3699
+ level,
3700
+ message,
3701
+ meta: meta ?? null,
3702
+ tabId: info.tabId ?? null,
3703
+ url: info.url ?? null,
3704
+ timestamp: Date.now()
3705
+ });
3706
+ if (this.buffer.length >= this.maxBufferSize) {
3707
+ this.flush().catch(() => {
3708
+ });
3709
+ } else {
3710
+ this.scheduleFlush();
3711
+ }
3712
+ });
3713
+ }
3714
+ scheduleFlush() {
3715
+ if (this.timer)
3716
+ return;
3717
+ this.timer = setTimeout(() => {
3718
+ this.timer = null;
3719
+ this.flush().catch(() => {
3720
+ });
3721
+ }, this.flushIntervalMs);
3722
+ }
3723
+ async flush() {
3724
+ if (this.timer) {
3725
+ clearTimeout(this.timer);
3726
+ this.timer = null;
3727
+ }
3728
+ if (!this.buffer.length)
3729
+ return;
3730
+ const batch = this.buffer.splice(0, this.buffer.length);
3731
+ try {
3732
+ await this.opts.send(batch);
3733
+ } catch {
3734
+ const queue = this.opts.persistentQueue;
3735
+ if (queue) {
3736
+ for (const entry of batch) {
3737
+ await queue.enqueue(entry).catch(() => {
3738
+ });
3739
+ }
3740
+ } else {
3741
+ this.buffer.unshift(...batch);
3742
+ if (this.buffer.length > this.maxBufferSize * 2) {
3743
+ this.buffer.splice(0, this.buffer.length - this.maxBufferSize * 2);
3744
+ }
3745
+ }
3746
+ }
3747
+ }
3748
+ /**
3749
+ * Drain persistent queue by calling send. Returns counts of sent/remaining.
3750
+ * Caller should invoke this when server is reachable again (e.g. on retry tick).
3751
+ */
3752
+ async drainPersistent() {
3753
+ if (!this.opts.persistentQueue)
3754
+ return null;
3755
+ return this.opts.persistentQueue.drain(this.opts.send);
3756
+ }
3757
+ debug(message, meta) {
3758
+ this.enqueue("debug", message, meta);
3759
+ }
3760
+ info(message, meta) {
3761
+ this.enqueue("info", message, meta);
3762
+ }
3763
+ warn(message, meta) {
3764
+ this.enqueue("warn", message, meta);
3765
+ }
3766
+ error(message, meta) {
3767
+ this.enqueue("error", message, meta);
3768
+ }
3769
+ log(message, meta) {
3770
+ this.info(message, meta);
3771
+ }
3772
+ patchConsole() {
3773
+ const levels = ["debug", "info", "warn", "error"];
3774
+ for (const level of levels) {
3775
+ const original = console[level];
3776
+ this.originalConsole[level] = original;
3777
+ console[level] = (...args) => {
3778
+ original.apply(console, args);
3779
+ try {
3780
+ const message = args.map(stringifyArg).join(" ");
3781
+ this[level](message);
3782
+ } catch {
3783
+ }
3784
+ };
3785
+ }
3786
+ const originalLog = console.log;
3787
+ this.originalConsole.log = originalLog;
3788
+ console.log = (...args) => {
3789
+ originalLog.apply(console, args);
3790
+ try {
3791
+ const message = args.map(stringifyArg).join(" ");
3792
+ this.info(message);
3793
+ } catch {
3794
+ }
3795
+ };
3796
+ }
3797
+ patchProcessStreams() {
3798
+ if (typeof process === "undefined")
3799
+ return;
3800
+ const wrap = (stream, label) => {
3801
+ const original = stream.write.bind(stream);
3802
+ stream.write = (chunk, encoding, cb) => {
3803
+ const str = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString();
3804
+ this.info(str.replace(/\n$/, ""), { stream: label });
3805
+ return original(chunk, encoding, cb);
3806
+ };
3807
+ };
3808
+ wrap(process.stdout, "stdout");
3809
+ wrap(process.stderr, "stderr");
3810
+ }
3811
+ };
3812
+
3813
+ // ../../node_modules/.pnpm/@chromepilot+logger-kit@0.1.4_@nestjs+common@10.4.22_class-transformer@0.5.1_class-validator@_legcruqwso5xb42h6lrzoll4b4/node_modules/@chromepilot/logger-kit/dist/client/transports/http-transport.js
3814
+ async function createHttpTransport(opts) {
3815
+ const serverUrl = opts.serverUrl.replace(/\/$/, "");
3816
+ return async (logs) => {
3817
+ if (!logs.length)
3818
+ return;
3819
+ const res = await fetch(`${serverUrl}/logs`, {
3820
+ method: "POST",
3821
+ headers: { "Content-Type": "application/json", ...opts.headers || {} },
3822
+ body: JSON.stringify({ logs })
3823
+ });
3824
+ if (!res.ok) {
3825
+ throw new Error(`HTTP ${res.status}: ${await res.text().catch(() => "")}`);
3826
+ }
3827
+ };
3828
+ }
3829
+
3830
+ // src/shared/queue.ts
3831
+ var STORAGE_KEY2 = "hunter-mate:logs-pending";
3832
+ var ChromeStorageLogQueue = class {
3833
+ buffer = [];
3834
+ nextId = 1;
3835
+ maxBytes = 2 * 1024 * 1024;
3836
+ async load() {
3837
+ const stored = await chromeStorageLocalGet(STORAGE_KEY2);
3838
+ if (!stored) return;
3839
+ try {
3840
+ const parsed = JSON.parse(stored);
3841
+ this.buffer = Array.isArray(parsed) ? parsed : [];
3842
+ for (const line of this.buffer) {
3843
+ if (line.id >= this.nextId) this.nextId = line.id + 1;
3844
+ }
3845
+ } catch {
3846
+ this.buffer = [];
3847
+ }
3848
+ }
3849
+ async enqueue(log) {
3850
+ this.buffer.push({ id: this.nextId++, log });
3851
+ await this.enforceCap();
3852
+ await this.persist();
3853
+ }
3854
+ async enforceCap() {
3855
+ const size = JSON.stringify(this.buffer).length;
3856
+ if (size <= this.maxBytes) return;
3857
+ const drop = Math.ceil(this.buffer.length * 0.1);
3858
+ this.buffer.splice(0, drop);
3859
+ }
3860
+ async persist() {
3861
+ try {
3862
+ await chromeStorageLocalSet(STORAGE_KEY2, JSON.stringify(this.buffer));
3863
+ } catch {
3864
+ this.buffer.splice(0, Math.ceil(this.buffer.length * 0.5));
3865
+ await chromeStorageLocalSet(STORAGE_KEY2, JSON.stringify(this.buffer));
3866
+ }
3867
+ }
3868
+ size() {
3869
+ return this.buffer.length;
3870
+ }
3871
+ async drain(sendFn, batchSize = 100) {
3872
+ if (this.buffer.length === 0) return { sent: 0, remaining: 0 };
3873
+ let sent = 0;
3874
+ while (this.buffer.length > 0) {
3875
+ const batch = this.buffer.slice(0, batchSize);
3876
+ try {
3877
+ await sendFn(batch.map((q) => q.log));
3878
+ this.buffer.splice(0, batch.length);
3879
+ sent += batch.length;
3880
+ } catch {
3881
+ break;
3882
+ }
3883
+ }
3884
+ if (sent > 0) await this.persist();
3885
+ return { sent, remaining: this.buffer.length };
3886
+ }
3887
+ async clear() {
3888
+ this.buffer = [];
3889
+ this.nextId = 1;
3890
+ await chromeStorageLocalRemove(STORAGE_KEY2);
3891
+ }
3892
+ };
3893
+ function chromeStorageLocalGet(key) {
3894
+ return new Promise((resolve) => {
3895
+ chrome.storage.local.get(key, (result) => {
3896
+ resolve(typeof result?.[key] === "string" ? result[key] : null);
3897
+ });
3898
+ });
3899
+ }
3900
+ function chromeStorageLocalSet(key, value) {
3901
+ return new Promise((resolve, reject) => {
3902
+ chrome.storage.local.set({ [key]: value }, () => {
3903
+ if (chrome.runtime.lastError) reject(new Error(chrome.runtime.lastError.message));
3904
+ else resolve();
3905
+ });
3906
+ });
3907
+ }
3908
+ function chromeStorageLocalRemove(key) {
3909
+ return new Promise((resolve) => {
3910
+ chrome.storage.local.remove(key, () => resolve());
3911
+ });
3912
+ }
3913
+
3914
+ // src/shared/logger.ts
3915
+ function createExtensionLogger(options) {
3916
+ const send2 = async (logs) => {
3917
+ const transport = await createHttpTransport({ serverUrl: SERVER_HTTP_URL });
3918
+ await transport(logs);
3919
+ };
3920
+ return new Logger({
3921
+ context: options.context,
3922
+ sessionId: options.sessionId,
3923
+ send: send2,
3924
+ flushIntervalMs: 1500,
3925
+ maxBufferSize: 30
3926
+ });
3927
+ }
3928
+ async function createBackgroundLogForwarder(options) {
3929
+ const transport = options?.transport;
3930
+ const maxPending = options?.maxPending ?? 200;
3931
+ const intervalMs = options?.intervalMs ?? 3e3;
3932
+ const queue = options?.persistentQueue ?? new ChromeStorageLogQueue();
3933
+ await queue.load();
3934
+ const pending = [];
3935
+ async function flush() {
3936
+ if (!transport) return;
3937
+ while (pending.length > 0) {
3938
+ const batch = pending.splice(0, pending.length);
3939
+ try {
3940
+ await transport(batch);
3941
+ } catch {
3942
+ for (const entry of batch) {
3943
+ await queue.enqueue(entry).catch(() => {
3944
+ });
3945
+ }
3946
+ break;
3947
+ }
3948
+ }
3949
+ }
3950
+ async function drainPersistent() {
3951
+ if (!transport) return null;
3952
+ return queue.drain(transport);
3953
+ }
3954
+ if (transport) {
3955
+ setInterval(() => {
3956
+ flush().catch(() => {
3957
+ });
3958
+ drainPersistent().catch(() => {
3959
+ });
3960
+ }, intervalMs);
3961
+ }
3962
+ function onLogsMessage(logs) {
3963
+ if (!transport) return;
3964
+ pending.push(...logs);
3965
+ if (pending.length > maxPending) {
3966
+ pending.splice(0, pending.length - maxPending);
3967
+ }
3968
+ flush().catch(() => {
3969
+ });
3970
+ }
3971
+ return {
3972
+ flush,
3973
+ onLogsMessage,
3974
+ pendingCount: () => pending.length,
3975
+ drainPersistent
3976
+ };
3977
+ }
3978
+
3979
+ // src/shared/diag.ts
3980
+ var ringKey = (src) => `hm.diag.ring.${src}`;
3981
+ var MAX_EVENTS = 400;
3982
+ var writeChain = Promise.resolve();
3983
+ function diag(src, event, detail, onError) {
3984
+ writeChain = writeChain.then(async () => {
3985
+ try {
3986
+ const key = ringKey(src);
3987
+ const got = await chrome.storage.local.get(key);
3988
+ const ring = Array.isArray(got[key]) ? got[key] : [];
3989
+ ring.push({ ts: Date.now(), src, event, detail });
3990
+ await chrome.storage.local.set({ [key]: ring.slice(-MAX_EVENTS) });
3991
+ } catch (e) {
3992
+ try {
3993
+ onError?.(e);
3994
+ } catch {
3995
+ }
3996
+ }
3997
+ });
3998
+ writeChain = writeChain.catch(() => void 0);
3999
+ }
4000
+ async function readDiagRing() {
4001
+ try {
4002
+ const got = await chrome.storage.local.get([ringKey("sw"), ringKey("offscreen")]);
4003
+ const sw = Array.isArray(got[ringKey("sw")]) ? got[ringKey("sw")] : [];
4004
+ const off = Array.isArray(got[ringKey("offscreen")]) ? got[ringKey("offscreen")] : [];
4005
+ return [...sw, ...off].sort((a, b) => a.ts - b.ts);
4006
+ } catch {
4007
+ return [];
4008
+ }
4009
+ }
4010
+
4011
+ // ../../node_modules/.pnpm/@chromepilot+kit@0.1.8_@sentry+node@10.70.0_@opentelemetry+core@2.10.0_@opentelemetry+api@1.9.1__/node_modules/@chromepilot/kit/dist/offscreen.js
4012
+ var OFFSCREEN_MSG = {
4013
+ /** SW → offscreen:注入身份(getManifest 在 offscreen 不可用) */
4014
+ INIT: "offscreen:init",
4015
+ /** SW → offscreen:上行 raw 帧(队列兜底在 offscreen 侧) */
4016
+ SEND: "offscreen:send",
4017
+ /** SW → offscreen:查询连接状态 */
4018
+ GET_STATE: "offscreen:get-state",
4019
+ /** offscreen → SW:连接状态变化 */
4020
+ WS_STATE: "offscreen:ws-state",
4021
+ /** offscreen → SW:server 下行透传(payload 为解析后的 JSON) */
4022
+ WS_MESSAGE: "offscreen:ws-message",
4023
+ /** offscreen → SW:诊断事件(offscreen 无 chrome.storage,经 SW 落环形缓冲) */
4024
+ DIAG: "offscreen:diag"
4025
+ };
4026
+ var OFFSCREEN_REASON_WORKERS = "WORKERS";
4027
+ var CLOSE_DOCUMENT_TIMEOUT_MS = 3e3;
4028
+ var RECREATE_SETTLE_MS = 1e3;
4029
+ var DEFAULT_WS_RECYCLE_TICKS = 2;
4030
+ function createSwOffscreenRelay(opts) {
4031
+ const recycleTicks = opts.recycleTicks ?? DEFAULT_WS_RECYCLE_TICKS;
4032
+ const closeTimeoutMs = opts.closeTimeoutMs ?? CLOSE_DOCUMENT_TIMEOUT_MS;
4033
+ const recreateSettleMs = opts.recreateSettleMs ?? RECREATE_SETTLE_MS;
4034
+ const diag2 = (src, event, detail) => {
4035
+ try {
4036
+ opts.onDiag?.(src, event, detail);
4037
+ } catch {
4038
+ }
4039
+ };
4040
+ let urlCache = null;
4041
+ let wsConnected = false;
4042
+ let wsDisconnectedTicks = 0;
4043
+ async function resolveUrl() {
4044
+ if (!urlCache)
4045
+ urlCache = await opts.getOffscreenUrl();
4046
+ return urlCache;
4047
+ }
4048
+ function sendInitToOffscreen() {
4049
+ const manifest = chrome.runtime.getManifest();
4050
+ chrome.runtime.sendMessage({
4051
+ type: OFFSCREEN_MSG.INIT,
4052
+ extensionId: chrome.runtime.id,
4053
+ version: manifest.version
4054
+ }).catch(() => {
4055
+ });
4056
+ }
4057
+ async function createWithZombieRecovery() {
4058
+ const url = await resolveUrl();
4059
+ try {
4060
+ await chrome.offscreen.createDocument({
4061
+ url,
4062
+ // @types/chrome: Reason 为枚举,字面量字符串不通过 tsc(运行时值相同)
4063
+ reasons: [OFFSCREEN_REASON_WORKERS],
4064
+ justification: opts.justification
4065
+ });
4066
+ diag2("sw", "ensureOffscreen:created", { attempt: 1 });
4067
+ sendInitToOffscreen();
4068
+ } catch (createErr) {
4069
+ const msg = createErr instanceof Error ? createErr.message : String(createErr);
4070
+ opts.sendLog?.("warn", "ensureOffscreen createDocument failed, retry after closeDocument", { error: msg });
4071
+ diag2("sw", "ensureOffscreen:create-conflict", { error: msg });
4072
+ try {
4073
+ await Promise.race([
4074
+ chrome.offscreen.closeDocument(),
4075
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`closeDocument timeout ${closeTimeoutMs / 1e3}s`)), closeTimeoutMs))
4076
+ ]);
4077
+ } catch {
4078
+ diag2("sw", "ensureOffscreen:close-failed-or-timeout", {});
4079
+ }
4080
+ await new Promise((r) => setTimeout(r, recreateSettleMs));
4081
+ await chrome.offscreen.createDocument({
4082
+ url: await resolveUrl(),
4083
+ reasons: [OFFSCREEN_REASON_WORKERS],
4084
+ justification: opts.justification
4085
+ });
4086
+ diag2("sw", "ensureOffscreen:created", { attempt: 2 });
4087
+ sendInitToOffscreen();
4088
+ }
4089
+ }
4090
+ let ensureInFlight = null;
4091
+ async function doEnsureOffscreen() {
4092
+ diag2("sw", "ensureOffscreen:begin", { wsConnected });
4093
+ let docExistedBefore = false;
4094
+ try {
4095
+ const contexts = await chrome.runtime.getContexts({
4096
+ contextTypes: [chrome.runtime.ContextType.OFFSCREEN_DOCUMENT]
4097
+ });
4098
+ diag2("sw", "ensureOffscreen:contexts", {
4099
+ count: contexts.length,
4100
+ // 全量快照(排除 documentId 长哈希):确认占据 offscreen 槽位的到底是谁
4101
+ docs: contexts.map((c) => ({
4102
+ url: c.url ?? null,
4103
+ documentUrl: c.documentUrl ?? null,
4104
+ frameId: c.frameId ?? null,
4105
+ origin: c.origin ?? null
4106
+ }))
4107
+ });
4108
+ if (contexts.length > 0) {
4109
+ docExistedBefore = true;
4110
+ sendInitToOffscreen();
4111
+ } else {
4112
+ await createWithZombieRecovery();
4113
+ }
4114
+ } catch (err3) {
4115
+ console.error("[SwOffscreenRelay] ensureOffscreen failed", err3);
4116
+ diag2("sw", "ensureOffscreen:fail", {
4117
+ error: err3 instanceof Error ? err3.message : String(err3)
4118
+ });
4119
+ opts.sendLog?.("error", "ensureOffscreen failed", {
4120
+ error: err3 instanceof Error ? err3.message : String(err3)
4121
+ });
4122
+ }
4123
+ try {
4124
+ const st = await chrome.runtime.sendMessage({ type: OFFSCREEN_MSG.GET_STATE });
4125
+ wsConnected = !!st?.wsConnected;
4126
+ diag2("sw", "ensureOffscreen:state", { wsConnected });
4127
+ opts.onWsState?.(wsConnected);
4128
+ } catch {
4129
+ diag2("sw", "ensureOffscreen:no-offscreen-response", {});
4130
+ }
4131
+ if (!wsConnected) {
4132
+ if (docExistedBefore) {
4133
+ wsDisconnectedTicks += 1;
4134
+ if (wsDisconnectedTicks >= recycleTicks) {
4135
+ wsDisconnectedTicks = 0;
4136
+ diag2("sw", "ensureOffscreen:recycle-suspect", {});
4137
+ opts.sendLog?.("warn", "offscreen alive but ws disconnected, recycling document", {});
4138
+ try {
4139
+ await chrome.offscreen.closeDocument();
4140
+ await new Promise((r) => setTimeout(r, 300));
4141
+ } catch {
4142
+ }
4143
+ return doEnsureOffscreen();
4144
+ }
4145
+ }
4146
+ } else {
4147
+ wsDisconnectedTicks = 0;
4148
+ }
4149
+ return wsConnected;
4150
+ }
4151
+ function ensureOffscreen2() {
4152
+ if (!ensureInFlight) {
4153
+ ensureInFlight = doEnsureOffscreen().finally(() => {
4154
+ ensureInFlight = null;
4155
+ });
4156
+ }
4157
+ return ensureInFlight;
4158
+ }
4159
+ function onRuntimeMessage(message) {
4160
+ if (message?.type === OFFSCREEN_MSG.WS_STATE) {
4161
+ wsConnected = !!message.wsConnected;
4162
+ diag2("sw", "ws-state-change", { wsConnected });
4163
+ opts.onWsState?.(wsConnected);
4164
+ return;
4165
+ }
4166
+ if (message?.type === OFFSCREEN_MSG.WS_MESSAGE) {
4167
+ try {
4168
+ opts.onDownlink?.(message.payload);
4169
+ } catch (err3) {
4170
+ diag2("sw", "downlink-handler-error", {
4171
+ error: err3 instanceof Error ? err3.message : String(err3)
4172
+ });
4173
+ }
4174
+ return;
4175
+ }
4176
+ if (message?.type === OFFSCREEN_MSG.DIAG && typeof message.event === "string") {
4177
+ diag2("offscreen", message.event, message.detail);
4178
+ return;
4179
+ }
4180
+ }
4181
+ return {
4182
+ attach() {
4183
+ chrome.runtime.onMessage.addListener(onRuntimeMessage);
4184
+ },
4185
+ ensureOffscreen: ensureOffscreen2,
4186
+ sendRaw(raw) {
4187
+ chrome.runtime.sendMessage({ type: OFFSCREEN_MSG.SEND, raw }).catch(() => {
4188
+ });
4189
+ },
4190
+ async getState() {
4191
+ try {
4192
+ const st = await chrome.runtime.sendMessage({ type: OFFSCREEN_MSG.GET_STATE });
4193
+ return { wsConnected: !!st?.wsConnected };
4194
+ } catch {
4195
+ return { wsConnected: false };
4196
+ }
4197
+ },
4198
+ async softReload() {
4199
+ diag2("sw", "soft-reload:begin", {});
4200
+ urlCache = null;
4201
+ wsDisconnectedTicks = 0;
4202
+ try {
4203
+ const contexts = await chrome.runtime.getContexts({
4204
+ contextTypes: [chrome.runtime.ContextType.OFFSCREEN_DOCUMENT]
4205
+ });
4206
+ if (contexts.length > 0)
4207
+ await chrome.offscreen.closeDocument();
4208
+ diag2("sw", "soft-reload:doc-closed", { count: contexts.length });
4209
+ } catch (err3) {
4210
+ diag2("sw", "soft-reload:close-failed", {
4211
+ error: err3 instanceof Error ? err3.message : String(err3)
4212
+ });
4213
+ }
4214
+ const ok2 = await ensureOffscreen2();
4215
+ diag2("sw", "soft-reload:done", {});
4216
+ return ok2;
4217
+ },
4218
+ get wsConnected() {
4219
+ return wsConnected;
4220
+ }
4221
+ };
4222
+ }
4223
+
4224
+ // src/background/service-worker.ts
4225
+ diag("sw", "sw-start", { reason: "top-level-eval" });
4226
+ self.addEventListener("error", (e) => diag("sw", "sw-error", { message: e.message, src: (e.filename || "").split("/").pop() }));
4227
+ self.addEventListener(
4228
+ "unhandledrejection",
4229
+ (e) => diag("sw", "sw-unhandledrejection", { reason: String(e.reason).slice(0, 300) })
4230
+ );
4231
+ chrome.runtime.onSuspend?.addListener(() => {
4232
+ diag("sw", "sw-suspend", { wsConnected: state.wsConnected });
4233
+ });
4234
+ var state = {
4235
+ serverConnected: false,
4236
+ wsConnected: false,
4237
+ ruleVersion: null,
4238
+ sidePanel: "pending",
4239
+ serverVersion: null,
4240
+ serverBuildTime: null
4241
+ };
4242
+ var tabStates = /* @__PURE__ */ new Map();
4243
+ var dedupAlertTargets = /* @__PURE__ */ new Map();
4244
+ var backgroundLogger = createExtensionLogger({ context: "background", sessionId: () => `bg-${Date.now()}` });
4245
+ backgroundLogger.patchConsole();
4246
+ var logForwarder = null;
4247
+ createBackgroundLogForwarder({
4248
+ transport: async (logs) => {
4249
+ const transport = await createHttpTransport({ serverUrl: SERVER_HTTP_URL });
4250
+ await transport(logs);
4251
+ }
4252
+ }).then((f) => {
4253
+ logForwarder = f;
4254
+ });
4255
+ async function getOffscreenUrl() {
4256
+ let v = "0";
4257
+ try {
4258
+ const r = await fetch(chrome.runtime.getURL("build-info.json"), { cache: "no-cache" });
4259
+ v = String((await r.json())?.buildTime ?? "0");
4260
+ } catch {
4261
+ }
4262
+ return `offscreen/offscreen.html?v=${encodeURIComponent(v)}`;
4263
+ }
4264
+ var offscreenRelay = createSwOffscreenRelay({
4265
+ getOffscreenUrl,
4266
+ justification: "hunter-mate \u9700\u4E0E\u672C\u5730 server \u4FDD\u6301\u5E38\u9A7B WebSocket \u8FDE\u63A5(MV3 SW \u4F1A\u88AB\u7A7A\u95F2\u56DE\u6536)",
4267
+ onDiag: (src, event, detail) => diag(src, event, detail),
4268
+ onDownlink: (payload) => {
4269
+ void handleServerMessage(payload);
4270
+ },
4271
+ onWsState: (connected) => {
4272
+ state.wsConnected = connected;
4273
+ broadcastState();
4274
+ },
4275
+ sendLog
4276
+ });
4277
+ offscreenRelay.attach();
4278
+ function sendToOffscreen(raw) {
4279
+ offscreenRelay.sendRaw(raw);
4280
+ }
4281
+ function ensureOffscreen() {
4282
+ return offscreenRelay.ensureOffscreen();
4283
+ }
4284
+ function softReloadExtension() {
4285
+ return offscreenRelay.softReload();
4286
+ }
4287
+ function sendLog(level, message, meta) {
4288
+ try {
4289
+ backgroundLogger[level === "debug" ? "debug" : level]("extension:sendLog", { message, meta });
4290
+ } catch {
4291
+ }
4292
+ sendToOffscreen(JSON.stringify({ type: "log", level, message, meta }));
4293
+ const prefix = `[HunterMate] ${message}`;
4294
+ if (level === "debug") {
4295
+ console.debug(prefix, meta ?? "");
4296
+ } else if (level === "warn") {
4297
+ console.warn(prefix, meta ?? "");
4298
+ } else if (level === "error") {
4299
+ console.error(prefix, meta ?? "");
4300
+ } else {
4301
+ console.log(prefix, meta ?? "");
4302
+ }
4303
+ }
4304
+ async function checkHttpHealth() {
4305
+ const wasConnected = state.serverConnected;
4306
+ try {
4307
+ const res = await fetch(`${SERVER_HTTP_URL}/health`);
4308
+ state.serverConnected = res.ok;
4309
+ if (res.ok) {
4310
+ const data = await res.json();
4311
+ state.serverVersion = data.version ?? null;
4312
+ state.serverBuildTime = data.buildTime ?? null;
4313
+ } else {
4314
+ state.serverVersion = null;
4315
+ state.serverBuildTime = null;
4316
+ }
4317
+ } catch {
4318
+ state.serverConnected = false;
4319
+ state.serverVersion = null;
4320
+ state.serverBuildTime = null;
4321
+ }
4322
+ if (!wasConnected && state.serverConnected) {
4323
+ broadcastState();
4324
+ }
4325
+ }
4326
+ async function checkRuleVersion() {
4327
+ if (!state.serverConnected) return;
4328
+ try {
4329
+ const res = await fetch(`${SERVER_HTTP_URL}/rules/latest`);
4330
+ if (res.ok) {
4331
+ const data = await res.json();
4332
+ state.ruleVersion = data.version;
4333
+ }
4334
+ } catch {
4335
+ }
4336
+ }
4337
+ async function buildStatusForActiveTab() {
4338
+ try {
4339
+ const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
4340
+ const tab = tabs[0];
4341
+ const tabId = tab?.id;
4342
+ if (tabId == null) {
4343
+ return { ...state, tabId: -1, url: "", recordingSessionId: null, eventCount: 0, classification: null };
4344
+ }
4345
+ const ts = tabStates.get(tabId);
4346
+ if (ts) return { ...state, ...ts };
4347
+ return {
4348
+ ...state,
4349
+ tabId,
4350
+ url: tab.url || "",
4351
+ recordingSessionId: null,
4352
+ eventCount: 0,
4353
+ classification: null
4354
+ };
4355
+ } catch {
4356
+ return { ...state, tabId: -1, url: "", recordingSessionId: null, eventCount: 0, classification: null };
4357
+ }
4358
+ }
4359
+ function broadcastState() {
4360
+ buildStatusForActiveTab().then((payload) => {
4361
+ chrome.runtime.sendMessage({ type: "status", payload }).catch(() => {
4362
+ });
4363
+ });
4364
+ }
4365
+ async function handleServerMessage(data) {
4366
+ if (!data || typeof data !== "object") return;
4367
+ if (data.type === "pong" || data.type === "status" || data.type === "ack") return;
4368
+ if (data.type === "reload") {
4369
+ console.log("[HunterMate] received reload broadcast, soft-reloading offscreen...");
4370
+ diag("sw", "reload-broadcast-received", { mode: "soft" });
4371
+ void softReloadExtension();
4372
+ return;
4373
+ }
4374
+ if (data.type === "hm.dedup.alert" && data.entityId) {
4375
+ const entityId = Number(data.entityId);
4376
+ const count = Number(data.count) || 1;
4377
+ const topName = data.top?.name ? String(data.top.name) : "";
4378
+ sendLog("info", "extension dedup alert received", { entityId, count });
4379
+ try {
4380
+ const notifId = `hm-dedup-${entityId}`;
4381
+ chrome.notifications.create(notifId, {
4382
+ type: "basic",
4383
+ iconUrl: chrome.runtime.getURL("icons/icon128.png"),
4384
+ title: "\u4EBA\u624D\u5E93\u53D1\u73B0\u53EF\u80FD\u91CD\u590D\u7684\u8BB0\u5F55",
4385
+ message: `\u300C${topName || "\u4EBA\u624D"}\u300D\u7B49 ${count} \u6761\u5019\u9009\u4E0E\u521A\u5165\u5E93\u7684\u4EBA\u624D\u76F8\u4F3C,\u70B9\u51FB\u6253\u5F00\u8BE6\u60C5\u5904\u7406`,
4386
+ priority: 1
4387
+ }, () => {
4388
+ if (chrome.runtime.lastError) {
4389
+ sendLog("warn", "dedup notification create failed", { error: chrome.runtime.lastError.message });
4390
+ }
4391
+ });
4392
+ dedupAlertTargets.set(notifId, entityId);
4393
+ } catch {
4394
+ }
4395
+ return;
4396
+ }
4397
+ if ((data.type === "start-recording" || data.type === "stop-recording") && data.tabId) {
4398
+ const tabId = Number(data.tabId);
4399
+ if (tabId > 0) {
4400
+ chrome.tabs.sendMessage(tabId, { type: data.type, payload: { sessionId: data.sessionId ?? null } }).then((resp) => sendLog("info", "extension relay recording broadcast", { type: data.type, tabId, sessionId: data.sessionId ?? null, resp: resp ?? null })).catch((err3) => sendLog("warn", "extension relay recording broadcast failed", { type: data.type, tabId, error: String(err3?.message ?? err3) }));
4401
+ }
4402
+ return;
4403
+ }
4404
+ if (data.type === "cmd" && data.requestId) {
4405
+ const startedAt = Date.now();
4406
+ sendLog("info", "extension handling control command", { command: data.command, requestId: data.requestId, params: data.params });
4407
+ let outcome;
4408
+ if (data.command === "hm.status") {
4409
+ const manifest = chrome.runtime.getManifest();
4410
+ outcome = {
4411
+ status: "ok",
4412
+ result: {
4413
+ ...state,
4414
+ tabStatesCount: tabStates.size,
4415
+ extBasePath: chrome.runtime.getURL(""),
4416
+ manifestPermissions: manifest.permissions,
4417
+ hasSidePanelInManifest: manifest.permissions?.includes("sidePanel"),
4418
+ chromeSidePanelType: typeof chrome.sidePanel,
4419
+ wsHeldBy: "offscreen"
4420
+ }
4421
+ };
4422
+ } else if (data.command === "hm.diag") {
4423
+ outcome = {
4424
+ status: "ok",
4425
+ result: { ring: await readDiagRing(), state }
4426
+ };
4427
+ } else {
4428
+ try {
4429
+ outcome = await executeControlCommand(String(data.command ?? ""), data.params ?? {}, {
4430
+ onLog: (level, message, meta) => sendLog(level, message, meta),
4431
+ sessionId: typeof data.sessionId === "string" ? data.sessionId : void 0
4432
+ });
4433
+ } catch (e) {
4434
+ outcome = { status: "error", message: e instanceof Error ? e.message : String(e) };
4435
+ }
4436
+ }
4437
+ const resp = outcome.status === "ok" ? {
4438
+ type: "cmd_response",
4439
+ requestId: data.requestId,
4440
+ status: "ok",
4441
+ // undefined 显式归一为 null:JSON.stringify 会丢 undefined 键,
4442
+ // 调用方无法区分"无 result 键"与"result 为 undefined"
4443
+ result: outcome.result === void 0 ? null : outcome.result,
4444
+ durationMs: Date.now() - startedAt
4445
+ } : {
4446
+ type: "cmd_response",
4447
+ requestId: data.requestId,
4448
+ status: "error",
4449
+ error: { code: outcome.code, message: outcome.message },
4450
+ durationMs: Date.now() - startedAt
4451
+ };
4452
+ sendToOffscreen(JSON.stringify(resp));
4453
+ sendLog("debug", "cmd_response sent", { requestId: data.requestId, status: resp.status, durationMs: resp.durationMs });
4454
+ }
4455
+ }
4456
+ var RECONNECT_ALARM_NAME = "hm:reconnect";
4457
+ function ensureReconnectAlarm() {
4458
+ chrome.alarms.create(RECONNECT_ALARM_NAME, { periodInMinutes: 0.5 }).catch((err3) => console.error("[HunterMate] create reconnect alarm failed", err3));
4459
+ }
4460
+ chrome.alarms.onAlarm.addListener((alarm) => {
4461
+ if (alarm.name !== RECONNECT_ALARM_NAME) return;
4462
+ diag("sw", "alarm-tick", { wsConnected: state.wsConnected });
4463
+ checkHttpHealth();
4464
+ sendLog("debug", "sw:alarm-tick", { wsConnected: state.wsConnected });
4465
+ void ensureOffscreen();
4466
+ });
4467
+ ensureReconnectAlarm();
4468
+ void ensureOffscreen();
4469
+ chrome.runtime.onInstalled.addListener(() => {
4470
+ console.log("[HunterMate] onInstalled: cold-start offscreen");
4471
+ diag("sw", "onInstalled", {});
4472
+ ensureReconnectAlarm();
4473
+ void ensureOffscreen();
4474
+ });
4475
+ chrome.notifications?.onClicked.addListener((notifId) => {
4476
+ const entityId = dedupAlertTargets.get(notifId);
4477
+ if (!entityId) return;
4478
+ const url = `http://localhost:34571/talents?id=${entityId}`;
4479
+ chrome.tabs.query({ url: "http://localhost:34571/talents*" }).then((tabs) => {
4480
+ if (tabs.length > 0 && tabs[0].id != null) {
4481
+ return chrome.tabs.update(tabs[0].id, { url, active: true });
4482
+ }
4483
+ return chrome.tabs.create({ url, active: true });
4484
+ }).then(() => chrome.notifications.clear(notifId)).catch(() => {
4485
+ });
4486
+ });
4487
+ chrome.runtime.onStartup.addListener(() => {
4488
+ console.log("[HunterMate] onStartup: cold-start offscreen");
4489
+ diag("sw", "onStartup", {});
4490
+ ensureReconnectAlarm();
4491
+ void ensureOffscreen();
4492
+ });
4493
+ setInterval(() => {
4494
+ checkHttpHealth();
4495
+ void ensureOffscreen();
4496
+ broadcastState();
4497
+ }, 2e4);
4498
+ setInterval(() => {
4499
+ checkRuleVersion();
4500
+ }, 6e4);
4501
+ checkHttpHealth();
4502
+ checkRuleVersion();
4503
+ syncSites();
4504
+ setInterval(() => {
4505
+ syncSites();
4506
+ }, 10 * 60 * 1e3);
4507
+ watchForDiscovery();
4508
+ if (typeof chrome !== "undefined" && chrome.sidePanel?.setPanelBehavior) {
4509
+ chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).then(() => {
4510
+ state.sidePanel = "ok";
4511
+ broadcastState();
4512
+ }).catch((err3) => {
4513
+ state.sidePanel = `error: ${err3?.message ?? err3}`;
4514
+ console.error("[HunterMate] setPanelBehavior failed", err3);
4515
+ broadcastState();
4516
+ });
4517
+ } else {
4518
+ state.sidePanel = "error: chrome.sidePanel API not available";
4519
+ }
4520
+ chrome.tabs.onActivated.addListener(() => {
4521
+ broadcastState();
4522
+ });
4523
+ chrome.tabs.onUpdated.addListener((_tabId, changeInfo, tab) => {
4524
+ if (!tab.url) return;
4525
+ const ts = tabStates.get(_tabId);
4526
+ if (ts) {
4527
+ ts.url = tab.url;
4528
+ }
4529
+ if (changeInfo.url) {
4530
+ broadcastState();
4531
+ if (state.wsConnected) {
4532
+ sendToOffscreen(JSON.stringify({ type: "tab_event", event: "navigated", tabId: _tabId, url: changeInfo.url, ts: Date.now() }));
4533
+ }
4534
+ }
4535
+ });
4536
+ chrome.tabs.onCreated.addListener((t) => {
4537
+ if (state.wsConnected) {
4538
+ sendToOffscreen(JSON.stringify({ type: "tab_event", event: "created", tabId: t.id, url: t.url ?? null, openerTabId: t.openerTabId ?? null, windowId: t.windowId, index: t.index, ts: Date.now() }));
4539
+ }
4540
+ });
4541
+ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
4542
+ if (message.type === "get-status") {
4543
+ buildStatusForActiveTab().then((payload) => {
4544
+ sendResponse(payload);
4545
+ });
4546
+ return true;
4547
+ }
4548
+ if (message.type === "query-site-policy") {
4549
+ const url = message.payload?.url;
4550
+ let policy = { record: false, parse: false };
4551
+ try {
4552
+ if (url) {
4553
+ const hostname = new URL(url).hostname;
4554
+ policy = getSitePolicy(hostname);
4555
+ }
4556
+ } catch {
4557
+ }
4558
+ sendResponse(policy);
4559
+ return true;
4560
+ }
4561
+ if (message.type === "recording-started" && message.payload?.sessionId) {
4562
+ const tabId = sender.tab?.id ?? -1;
4563
+ const classification = message.payload.classification ?? null;
4564
+ tabStates.set(tabId, {
4565
+ tabId,
4566
+ url: sender.tab?.url || "",
4567
+ recordingSessionId: message.payload.sessionId,
4568
+ eventCount: 0,
4569
+ classification
4570
+ });
4571
+ broadcastState();
4572
+ sendResponse({ ok: true });
4573
+ return true;
4574
+ }
4575
+ if (message.type === "recording-stopped") {
4576
+ const tabId = sender.tab?.id ?? -1;
4577
+ const existing = tabStates.get(tabId);
4578
+ if (existing) {
4579
+ tabStates.set(tabId, { ...existing, recordingSessionId: null, eventCount: 0 });
4580
+ }
4581
+ broadcastState();
4582
+ sendResponse({ ok: true });
4583
+ return true;
4584
+ }
4585
+ if (message.type === "recording-event") {
4586
+ const tabId = sender.tab?.id ?? -1;
4587
+ const payload = { ...message.payload, tabId };
4588
+ sendToOffscreen(JSON.stringify(payload));
4589
+ const ts = tabStates.get(tabId);
4590
+ if (ts && message.payload?.sessionId === ts.recordingSessionId) {
4591
+ ts.eventCount++;
4592
+ }
4593
+ } else if (message.type === "hm:logs" && Array.isArray(message.logs)) {
4594
+ logForwarder?.onLogsMessage(message.logs);
4595
+ }
4596
+ sendResponse({ ok: true });
4597
+ return true;
4598
+ });
4599
+ chrome.tabs.query({}, (tabs) => {
4600
+ for (const tab of tabs) {
4601
+ const tabId = tab.id;
4602
+ if (tabId == null || !tab.url) continue;
4603
+ const policy = getSitePolicy(new URL(tab.url).hostname);
4604
+ if (policy.record) {
4605
+ tabStates.set(tabId, {
4606
+ tabId,
4607
+ url: tab.url,
4608
+ recordingSessionId: null,
4609
+ eventCount: 0,
4610
+ classification: null
4611
+ });
4612
+ }
4613
+ }
4614
+ broadcastState();
4615
+ });
4616
+ chrome.tabs.onRemoved.addListener((tabId) => {
4617
+ tabStates.delete(tabId);
4618
+ });