chainlesschain 0.162.147 → 0.162.149

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 (257) hide show
  1. package/README.md +1 -1
  2. package/package.json +2 -2
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-DywJ7xTv.js → AIOps-BJif14yR.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-BTlqcY-q.js → ActionButton-CXek6gJY.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-FdG1Mvwy.js → Analytics-BgQhdAJi.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-CVsnbvN1.js → AppLayout-DTYl5NtR.js} +5 -5
  8. package/src/assets/web-panel/assets/{Audit-ITdpJ7vR.js → Audit-BzATQAeR.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-BfXqnXo1.js → Backup-BzdPEQhL.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-C09ip3_E.js → BaseInput-BM0KVh8E.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-qdkhB42l.js → Chat-D7fHXHkz.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-D2KfETZD.js → ChatBubbleRenderer-Wm34RR-b.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-j6WZCuff.js → Checkbox-CAiSQ9vO.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-CGZ-K3uK.js → Codegen-Df40CrEe.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-CIlPOAu6.js → Col-DIT2fNFU.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-sY-i-hic.js → Community-CSTIbW2k.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-DcoEfyL8.js → Compact-DtqXZZUi.js} +1 -1
  18. package/src/assets/web-panel/assets/Compliance-CUtzw6o7.js +1 -0
  19. package/src/assets/web-panel/assets/{Cowork-DLY_fkLv.js → Cowork-CezmlnmU.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-D049pZBC.js → Cron-P4BITarg.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DCXdE8M9.js → Crosschain-BRRiYwvq.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-JCvPZtjW.js → DID-L5ijB9i4.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-Cg2xu3iE.js → Dashboard-CFxro4ob.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-DjJLjbLh.js → Dropdown-BmAFCQ71.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-DugNcSLR.js → EmailListRenderer-DJBCEuon.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-J6Nb-E30.js → FamilyGuardDashboard-z4oLq6eT.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-CQ3Ttpbl.js → Federation-DsVCpHMF.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-Cz0NKag4.js → FormItemContext-DP3bP5th.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-NYcMP6iz.js → GenericCardRenderer-BAXP2Gc6.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-B4hYN18N.js → Git-I3g_bAw9.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-DOQPCcC0.js → Governance-jHS5-ioS.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-HtLF746W.js → Inference-CPL7sfx9.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-BXrk8rMm.js → KnowledgeGraph-Jf236h4C.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-DS0JnD3-.js → Logs-Cm2tcSKg.js} +2 -2
  35. package/src/assets/web-panel/assets/{Marketplace-Cqnjihrz.js → Marketplace-CZK82rhi.js} +1 -1
  36. package/src/assets/web-panel/assets/{McpTools-Cx3Psk-U.js → McpTools-CZCeji7a.js} +5 -5
  37. package/src/assets/web-panel/assets/{Memory-BjyysPtj.js → Memory-3l2KVS9k.js} +2 -2
  38. package/src/assets/web-panel/assets/MobileBridge-D3Arxfeg.css +1 -0
  39. package/src/assets/web-panel/assets/MobileBridge-VYdpoLh6.js +1 -0
  40. package/src/assets/web-panel/assets/{MobileProjects-Xwf-fdOQ.js → MobileProjects-CiB9cmm1.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-Djkql5m5.js → Mtc-wCFZbBuL.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-D2BAZlu0.js → MtcAudit-1a3THIyG.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-ByqP1ZzH.js → Multisig-DMzLB2hG.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-C_kn0nE2.js → NLProgramming-D9wZbf6l.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-7l7eXHPq.js → Notes-hrFe2ZM-.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-BrCDoitw.js → NotificationSettings-CHGX5Jwm.js} +1 -1
  47. package/src/assets/web-panel/assets/OrderTableRenderer-CRIFE2Tj.js +1 -0
  48. package/src/assets/web-panel/assets/{Organization-DdoNAxsM.js → Organization-By7FkhtY.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-Bw7R6dE3.js → Overflow--khGSzJn.js} +1 -1
  50. package/src/assets/web-panel/assets/{OverrideContext-C2r4vyBb.js → OverrideContext-BlgL9440.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-DJNtBugA.js → P2P-CAG9dH35.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-Dwhd5_ue.js → PdhVaultBrowser-B3SQZmK9.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-BYoSfhPR.js → Permissions-D35ec6JA.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-aYCYJbnH.js → PersonalDataHub-CxBF7hW_.js} +2 -2
  55. package/src/assets/web-panel/assets/{Pipeline-DJp6CprF.js → Pipeline-s6EtOO1s.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-Cq9HZweR.js → Privacy-BbJYmWmO.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-CmF0HsSt.js → ProjectInit-VGQq1jMT.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-P8V5Aea2.js → ProjectSettings-B1ew3Teh.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-CGcNIs_g.js +1 -0
  60. package/src/assets/web-panel/assets/{Providers-D589v0q2.js → Providers-mrO47FIK.js} +1 -1
  61. package/src/assets/web-panel/assets/QrScannerModal-IJF2mHyw.js +1 -0
  62. package/src/assets/web-panel/assets/{MobileBridge-zJpPuf4E.css → QrScannerModal-NwAHNfiJ.css} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-lvRV2osB.js → QuickAsk-CU0wN_Z0.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-sb84MTpY.js → Recommend-DhN37R6G.js} +1 -1
  65. package/src/assets/web-panel/assets/RemoteSession-D_xOX_hu.js +4 -0
  66. package/src/assets/web-panel/assets/{Reputation-d9Y3vy-W.js → Reputation-D_Ssv7uH.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-DMdhLUTp.js → Row-Kx5dKxX7.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-OL-SMDkz.js → RssFeed-BlGwqZkY.js} +2 -2
  69. package/src/assets/web-panel/assets/{Search-BjStLKq1.js → Search-hLFQzxpb.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-MeI411qr.js → Security-DMlvsVt4.js} +2 -2
  71. package/src/assets/web-panel/assets/{Services-UajosuhT.js → Services-Bd0Qsj2m.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-CGpG-QJ1.js → Skeleton-DFclq9X3.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-C6P2RPY-.js → Skills-BNtln2qY.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-B_fPprgw.js → Sla-Dch5H19G.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-CqxLjSlQ.js → SpeechSettings-2UfQcU1g.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-DixSfFOQ.js → SyncSettings-CD9YQr4i.js} +2 -2
  77. package/src/assets/web-panel/assets/{Tasks-PavzPBxc.js → Tasks-BNu-7MzI.js} +1 -1
  78. package/src/assets/web-panel/assets/{Templates-BYdGRnfX.js → Templates-D1n9CaIR.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-BQLgnarD.js → Tenant-DenL3iBW.js} +1 -1
  80. package/src/assets/web-panel/assets/Terminal-ZzU0Io1Y.js +3 -0
  81. package/src/assets/web-panel/assets/{TimelineRenderer-Bv7uZRM7.js → TimelineRenderer-D87IfukO.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-DxhgszRJ.js → Tokens-BkiZc8E3.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-nQYHayuc.js → Trigger-BNRSytGN.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-CvqZDuZ4.js → Trust-Bwbd4X2m.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-DaR8GV2I.js → UkeySign-B8O2bs0o.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-C6Mpsokw.js → VideoEditing-C2fL8zmH.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-B57VRrWc.js → Wallet-BORYDdE1.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-BbtEEtpq.js → WebAuthn-C0V5S2FM.js} +4 -4
  89. package/src/assets/web-panel/assets/{WorkflowEditor-C5LwvKmH.js → WorkflowEditor-2dR0fcHL.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-MzuPR5jh.js → chat-BmuAFAn8.js} +1 -1
  91. package/src/assets/web-panel/assets/{collapseMotion-mxzzcDsg.js → collapseMotion-DVfhbsdP.js} +1 -1
  92. package/src/assets/web-panel/assets/{colors-HyW8mi_y.js → colors-CN3smMcK.js} +1 -1
  93. package/src/assets/web-panel/assets/{compact-item-DvUSzWAp.js → compact-item-DCnxb4kW.js} +1 -1
  94. package/src/assets/web-panel/assets/{createContext-4MppZl7X.js → createContext-vlR43pHn.js} +1 -1
  95. package/src/assets/web-panel/assets/devWarning-DxwmsL4j.js +1 -0
  96. package/src/assets/web-panel/assets/{echarts-jxgIOFUZ.js → echarts-CL0F2SnX.js} +1 -1
  97. package/src/assets/web-panel/assets/{hasIn-D9q3CJ-u.js → hasIn-D04tHYFd.js} +1 -1
  98. package/src/assets/web-panel/assets/{icons-DP3uiYxy.js → icons-BsDmGpcT.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-C9I5MuJ0.js → index-0DnaZGkC.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Caqtu6Et.js → index-430S68MN.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-eOQO6KnV.js → index-B78xL3s2.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Ci6adKh8.js → index-BTPEZTk1.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CifKUNtV.js → index-BXRg8GFQ.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-D7TDMMfu.js → index-BasvsWDM.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DNYqw0MO.js → index-BeGDEXFs.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-m0b6Fj9q.js → index-BqhASEL4.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BF2JsbiX.js → index-BudvKQfG.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DFqvWeGc.js → index-C-6NO5il.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-SDblbKj6.js → index-C5Sxb1sE.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-UUMCA2Sq.js → index-CP9m9Ggr.js} +4 -4
  111. package/src/assets/web-panel/assets/{index--Bm6OqmU.js → index-CTOCnIQ7.js} +1 -1
  112. package/src/assets/web-panel/assets/index-Cg2ldRfU.js +1 -0
  113. package/src/assets/web-panel/assets/{index-Dv7ffI2g.js → index-ClhAHDK3.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BcunsBIx.js → index-CnXebZLL.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CvAqCAo9.js → index-CngCC8hC.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DcMkjb92.js → index-Cr-A0FYJ.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BFGfC5RS.js → index-CsAsCPJ6.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DDZ6fP5Z.js → index-D28DeAAB.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CgIfXOD9.js → index-D5LZVZ0L.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-DmZ6-suL.js → index-D65_XseV.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-Cs-RKRUM.js → index-D8vwp4qp.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-B994UQfE.js → index-DDwLgQY9.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-Btz8pU68.js → index-DIpY0qM5.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-B5E4DhVc.js → index-DTWg-18U.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-D2jrnaq3.js → index-DTtRscUa.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BmCo2Xdp.js → index-DYAtmqiv.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-Zvtru2oE.js → index-D_n8_vfI.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CLAZs1Vd.js → index-DbADHwhr.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CucRo4Vz.js → index-DbVsQBOH.js} +1 -1
  130. package/src/assets/web-panel/assets/index-Dwzo-dtJ.js +1 -0
  131. package/src/assets/web-panel/assets/{index-C0DiL97c.js → index-HhNjnCfe.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-Bl-E4W4q.js → index-ToUh2b1-.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-ryFLxB1p.js → index-TslMTwNy.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-BGQtFso0.js → index-_FIkN3jd.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-zLwYpvX0.js → index-hpvvtAZ3.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-Dvg5xYuP.js → index-jzR7Ujuw.js} +1 -1
  137. package/src/assets/web-panel/assets/{index-xvUVuFnA.js → index-ljuBiQW9.js} +1 -1
  138. package/src/assets/web-panel/assets/{initDefaultProps-CC--PMsC.js → initDefaultProps-C3wUb4je.js} +1 -1
  139. package/src/assets/web-panel/assets/{motion-DdrBjRF1.js → motion-D-jYFUNA.js} +1 -1
  140. package/src/assets/web-panel/assets/{move-D712Gwl2.js → move-tqb8nwJ2.js} +1 -1
  141. package/src/assets/web-panel/assets/{mtc-parser-39y-keKO.js → mtc-parser-DSOjMJMz.js} +1 -1
  142. package/src/assets/web-panel/assets/{omit-6Y51gDB9.js → omit-CJ7VimIW.js} +1 -1
  143. package/src/assets/web-panel/assets/{pickAttrs-Cy6EHbq9.js → pickAttrs-qXiG1iT3.js} +1 -1
  144. package/src/assets/web-panel/assets/{placementArrow-BYxdAm0p.js → placementArrow-CkGBcy1Z.js} +1 -1
  145. package/src/assets/web-panel/assets/{responsiveObserve-C9R6q_cr.js → responsiveObserve-D3WvTlLO.js} +1 -1
  146. package/src/assets/web-panel/assets/{slide-CXUJlilB.js → slide-BEE2ftge.js} +1 -1
  147. package/src/assets/web-panel/assets/{statusUtils-DOtrOeql.js → statusUtils-CsfBpxiG.js} +1 -1
  148. package/src/assets/web-panel/assets/{styleChecker-D5r39o92.js → styleChecker-xPrIiJPI.js} +1 -1
  149. package/src/assets/web-panel/assets/useFlexGapSupport-C5Yd-SCR.js +1 -0
  150. package/src/assets/web-panel/assets/{useFs-BkrT-Zbc.js → useFs-D_MDS8HI.js} +1 -1
  151. package/src/assets/web-panel/assets/{useMergedState-Cl2q1fes.js → useMergedState-Dc9sO2d7.js} +1 -1
  152. package/src/assets/web-panel/assets/{usePersonalDataHub-BwnnnZjU.js → usePersonalDataHub-BqJh-usA.js} +1 -1
  153. package/src/assets/web-panel/assets/{useRefs-DD9q4mOT.js → useRefs-CAeFRzPf.js} +1 -1
  154. package/src/assets/web-panel/assets/{useState-BK4hy8Ma.js → useState-LwExUYDO.js} +1 -1
  155. package/src/assets/web-panel/assets/{vendor-BvqAck49.js → vendor-BMxUs6UX.js} +16 -16
  156. package/src/assets/web-panel/assets/{vnode-SU-N4pum.js → vnode-chm_p-gz.js} +1 -1
  157. package/src/assets/web-panel/assets/{zoom-DHScfpTP.js → zoom-3-YDNz0Z.js} +1 -1
  158. package/src/assets/web-panel/index.html +3 -3
  159. package/src/assets/web-panel/remote-session-sw.js +43 -0
  160. package/src/command-manifest.json +22 -1
  161. package/src/commands/agent.js +44 -5
  162. package/src/commands/codeintel.js +252 -0
  163. package/src/commands/eval.js +248 -0
  164. package/src/commands/plugin.js +354 -0
  165. package/src/commands/serve.js +10 -0
  166. package/src/commands/team.js +456 -0
  167. package/src/gateways/remote-session-relay.js +160 -0
  168. package/src/gateways/ws/message-dispatcher.js +77 -2
  169. package/src/gateways/ws/remote-session-protocol.js +466 -0
  170. package/src/gateways/ws/ws-server.js +299 -0
  171. package/src/harness/golden-transcript.js +65 -0
  172. package/src/harness/mcp-client.js +6 -2
  173. package/src/harness/prompt-compressor.js +27 -1
  174. package/src/harness/remote-command-ledger.js +158 -0
  175. package/src/harness/remote-session-audit-sink.js +132 -0
  176. package/src/harness/remote-session-audit.js +110 -0
  177. package/src/harness/remote-session-crypto.js +217 -0
  178. package/src/harness/remote-session-push-apns.js +246 -0
  179. package/src/harness/remote-session-push-errors.js +15 -0
  180. package/src/harness/remote-session-push-fcm.js +238 -0
  181. package/src/harness/remote-session-push-huawei.js +174 -0
  182. package/src/harness/remote-session-push-oppo.js +171 -0
  183. package/src/harness/remote-session-push-senders.js +42 -0
  184. package/src/harness/remote-session-push-vivo.js +179 -0
  185. package/src/harness/remote-session-push-web.js +299 -0
  186. package/src/harness/remote-session-push-xiaomi.js +99 -0
  187. package/src/harness/remote-session-push.js +104 -0
  188. package/src/harness/remote-session-registry.js +420 -0
  189. package/src/index.js +6 -0
  190. package/src/lib/agent-core.js +2 -0
  191. package/src/lib/agent-sandbox.js +161 -5
  192. package/src/lib/agent-team/task-lease.js +434 -0
  193. package/src/lib/agent-team/team-budget.js +165 -0
  194. package/src/lib/agent-team/team-mailbox.js +106 -0
  195. package/src/lib/agent-team/team-runner.js +266 -0
  196. package/src/lib/agent-team/team-worktree.js +204 -0
  197. package/src/lib/agents.js +18 -0
  198. package/src/lib/async-hook-supervisor.cjs +305 -0
  199. package/src/lib/did-manager.js +6 -4
  200. package/src/lib/eval/runner.js +163 -0
  201. package/src/lib/eval/tasks.js +456 -0
  202. package/src/lib/eval/trend.js +185 -0
  203. package/src/lib/lsp/__tests__/code-intelligence.integration.test.js +129 -0
  204. package/src/lib/lsp/__tests__/code-intelligence.test.js +228 -0
  205. package/src/lib/lsp/__tests__/jsonrpc-stream.test.js +104 -0
  206. package/src/lib/lsp/__tests__/lsp-client.test.js +258 -0
  207. package/src/lib/lsp/__tests__/lsp-manager.test.js +264 -0
  208. package/src/lib/lsp/__tests__/lsp-server-registry.test.js +128 -0
  209. package/src/lib/lsp/code-intelligence.js +356 -0
  210. package/src/lib/lsp/jsonrpc-stream.js +139 -0
  211. package/src/lib/lsp/lsp-client.js +318 -0
  212. package/src/lib/lsp/lsp-manager.js +323 -0
  213. package/src/lib/lsp/lsp-server-registry.js +196 -0
  214. package/src/lib/mcp-oauth.js +23 -6
  215. package/src/lib/plugin-monitor-supervisor.js +238 -0
  216. package/src/lib/plugin-runtime/agents.js +51 -0
  217. package/src/lib/plugin-runtime/bin.js +100 -0
  218. package/src/lib/plugin-runtime/hooks.js +100 -0
  219. package/src/lib/plugin-runtime/install.js +388 -0
  220. package/src/lib/plugin-runtime/lsp.js +75 -0
  221. package/src/lib/plugin-runtime/manifest.js +458 -0
  222. package/src/lib/plugin-runtime/mcp.js +89 -0
  223. package/src/lib/plugin-runtime/monitors.js +100 -0
  224. package/src/lib/plugin-runtime/policy.js +144 -0
  225. package/src/lib/plugin-runtime/reload.js +79 -0
  226. package/src/lib/plugin-runtime/scopes.js +197 -0
  227. package/src/lib/plugin-runtime/settings.js +119 -0
  228. package/src/lib/plugin-runtime/signature.js +107 -0
  229. package/src/lib/plugin-runtime/skills.js +43 -0
  230. package/src/lib/plugin-runtime/trust.js +140 -0
  231. package/src/lib/sandbox-egress-proxy.js +162 -0
  232. package/src/lib/sandbox-network-policy.js +134 -0
  233. package/src/lib/sandbox-v2.js +10 -4
  234. package/src/lib/session-manager.js +7 -3
  235. package/src/lib/settings-hook-events.cjs +78 -6
  236. package/src/lib/settings-hooks.cjs +29 -3
  237. package/src/lib/settings-loader.cjs +35 -1
  238. package/src/lib/skill-loader.js +18 -0
  239. package/src/lib/telemetry/span-recorder.js +237 -0
  240. package/src/repl/agent-repl.js +400 -4
  241. package/src/runtime/agent-core.js +546 -52
  242. package/src/runtime/agent-runtime.js +8 -13
  243. package/src/runtime/coding-agent-contract-shared.cjs +82 -8
  244. package/src/runtime/coding-agent-policy.cjs +32 -7
  245. package/src/runtime/fallback-model.js +134 -7
  246. package/src/runtime/headless-runner.js +353 -108
  247. package/src/runtime/mcp-config.js +46 -0
  248. package/src/runtime/policies/agent-policy.js +2 -0
  249. package/src/assets/web-panel/assets/Compliance-T8EtTecW.js +0 -1
  250. package/src/assets/web-panel/assets/MobileBridge-CYaITZ7w.js +0 -1
  251. package/src/assets/web-panel/assets/OrderTableRenderer-CmtqIdwr.js +0 -1
  252. package/src/assets/web-panel/assets/Projects-DPd5-lNS.js +0 -1
  253. package/src/assets/web-panel/assets/Terminal-BzOHpq-B.js +0 -3
  254. package/src/assets/web-panel/assets/devWarning-6YJJMjSH.js +0 -1
  255. package/src/assets/web-panel/assets/index-D_oeGLr6.js +0 -1
  256. package/src/assets/web-panel/assets/index-Qa57YG19.js +0 -1
  257. package/src/assets/web-panel/assets/useFlexGapSupport-DfAD5U4Z.js +0 -1
@@ -0,0 +1,15 @@
1
+ // Shared error types for Remote Session vendor push senders.
2
+
3
+ /**
4
+ * A device token the vendor reports as no longer valid (uninstalled app, token
5
+ * rotated). The caller should prune it so it is never retried. Raised by every
6
+ * provider (FCM UNREGISTERED, APNs 410/BadDeviceToken, …) so the dispatcher and
7
+ * ws-server pruning logic can key off a single `code`.
8
+ */
9
+ export class PushTokenUnregisteredError extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = "PushTokenUnregisteredError";
13
+ this.code = "PUSH_TOKEN_UNREGISTERED";
14
+ }
15
+ }
@@ -0,0 +1,238 @@
1
+ // Remote Session vendor push — Firebase Cloud Messaging (HTTP v1) sender.
2
+ //
3
+ // A concrete implementation of the RemoteSessionPushDispatcher `sender`
4
+ // contract that delivers approval wake-ups to Android devices through FCM's
5
+ // current HTTP v1 API (the legacy server-key API is retired). Auth uses a
6
+ // Google service account: a short-lived OAuth2 access token minted by signing a
7
+ // JWT (RS256) with the service account private key and exchanging it at
8
+ // oauth2.googleapis.com — no long-lived server key on disk.
9
+ //
10
+ // Everything is injectable (fetch, clock, signer, fs) so the whole chain is
11
+ // unit-testable with a generated keypair and a fake transport — no real
12
+ // credentials or network. The payload carries NO session content, only the
13
+ // routing shape (aligns with the audit log's privacy-first stance).
14
+
15
+ import { createSign } from "node:crypto";
16
+ import fs from "node:fs";
17
+ import { PushTokenUnregisteredError } from "./remote-session-push-errors.js";
18
+
19
+ // Re-exported for existing importers; canonical definition lives in the shared
20
+ // errors module so every provider raises the same type.
21
+ export { PushTokenUnregisteredError };
22
+
23
+ const FCM_SCOPE = "https://www.googleapis.com/auth/firebase.messaging";
24
+ const DEFAULT_TOKEN_URI = "https://oauth2.googleapis.com/token";
25
+ const JWT_BEARER_GRANT = "urn:ietf:params:oauth:grant-type:jwt-bearer";
26
+ const ACCESS_TOKEN_SKEW_MS = 60_000; // refresh a minute before real expiry
27
+ const ASSERTION_TTL_SEC = 3600;
28
+
29
+ function base64url(input) {
30
+ return Buffer.from(input)
31
+ .toString("base64")
32
+ .replace(/\+/g, "-")
33
+ .replace(/\//g, "_")
34
+ .replace(/=+$/, "");
35
+ }
36
+
37
+ async function safeText(res) {
38
+ try {
39
+ return await res.text();
40
+ } catch {
41
+ return "";
42
+ }
43
+ }
44
+
45
+ function dropUndefined(obj) {
46
+ const out = {};
47
+ for (const [key, value] of Object.entries(obj)) {
48
+ if (value !== undefined) out[key] = value;
49
+ }
50
+ return out;
51
+ }
52
+
53
+ /** Mints + caches OAuth2 access tokens from a Google service account. */
54
+ export class GoogleServiceAccountTokenProvider {
55
+ constructor(serviceAccount = {}, options = {}) {
56
+ if (!serviceAccount.client_email || !serviceAccount.private_key) {
57
+ throw new Error(
58
+ "Service account must include client_email and private_key",
59
+ );
60
+ }
61
+ this.clientEmail = serviceAccount.client_email;
62
+ this.privateKey = serviceAccount.private_key;
63
+ this.tokenUri = serviceAccount.token_uri || DEFAULT_TOKEN_URI;
64
+ this.scope = options.scope || FCM_SCOPE;
65
+ this.now = options.now || Date.now;
66
+ this.fetch = options.fetch || globalThis.fetch;
67
+ // RS256 = RSA PKCS#1 v1.5 over SHA-256. Injectable for tests.
68
+ this.sign =
69
+ options.sign ||
70
+ ((signingInput, key) =>
71
+ createSign("RSA-SHA256").update(signingInput).sign(key));
72
+ this._cached = null; // { token, expiresAt }
73
+ }
74
+
75
+ async getAccessToken() {
76
+ const now = this.now();
77
+ if (this._cached && this._cached.expiresAt - ACCESS_TOKEN_SKEW_MS > now) {
78
+ return this._cached.token;
79
+ }
80
+ const assertion = this._buildAssertion(now);
81
+ const body = new URLSearchParams({
82
+ grant_type: JWT_BEARER_GRANT,
83
+ assertion,
84
+ });
85
+ const res = await this.fetch(this.tokenUri, {
86
+ method: "POST",
87
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
88
+ body: body.toString(),
89
+ });
90
+ if (!res.ok) {
91
+ throw new Error(
92
+ `OAuth token request failed (${res.status}): ${await safeText(res)}`,
93
+ );
94
+ }
95
+ const json = await res.json();
96
+ if (!json.access_token) {
97
+ throw new Error("OAuth token response missing access_token");
98
+ }
99
+ const expiresInMs = (Number(json.expires_in) || ASSERTION_TTL_SEC) * 1000;
100
+ this._cached = { token: json.access_token, expiresAt: now + expiresInMs };
101
+ return this._cached.token;
102
+ }
103
+
104
+ _buildAssertion(now) {
105
+ const iat = Math.floor(now / 1000);
106
+ const header = base64url(JSON.stringify({ alg: "RS256", typ: "JWT" }));
107
+ const payload = base64url(
108
+ JSON.stringify({
109
+ iss: this.clientEmail,
110
+ scope: this.scope,
111
+ aud: this.tokenUri,
112
+ iat,
113
+ exp: iat + ASSERTION_TTL_SEC,
114
+ }),
115
+ );
116
+ const signingInput = `${header}.${payload}`;
117
+ const signature = base64url(this.sign(signingInput, this.privateKey));
118
+ return `${signingInput}.${signature}`;
119
+ }
120
+ }
121
+
122
+ /** Sends one high-priority data+notification message via FCM HTTP v1. */
123
+ export class FcmV1PushSender {
124
+ constructor(options = {}) {
125
+ if (!options.projectId) {
126
+ throw new Error("FcmV1PushSender requires projectId");
127
+ }
128
+ if (!options.tokenProvider) {
129
+ throw new Error("FcmV1PushSender requires a tokenProvider");
130
+ }
131
+ this.projectId = options.projectId;
132
+ this.tokenProvider = options.tokenProvider;
133
+ this.fetch = options.fetch || globalThis.fetch;
134
+ this.endpoint =
135
+ options.endpoint ||
136
+ `https://fcm.googleapis.com/v1/projects/${options.projectId}/messages:send`;
137
+ }
138
+
139
+ /** Bound function matching the dispatcher's `sender` contract. */
140
+ get handler() {
141
+ return (payload) => this.send(payload);
142
+ }
143
+
144
+ async send({ token, notification, sessionId, clientId } = {}) {
145
+ if (!token) throw new Error("FCM send requires a device token");
146
+ const accessToken = await this.tokenProvider.getAccessToken();
147
+ const message = {
148
+ message: dropUndefined({
149
+ token,
150
+ notification: notification
151
+ ? dropUndefined({
152
+ title: notification.title,
153
+ body: notification.body,
154
+ })
155
+ : undefined,
156
+ // Data-only fields let the app route the wake-up; no session content.
157
+ data: dropUndefined({
158
+ type: "remote-session.approval-request",
159
+ sessionId: sessionId != null ? String(sessionId) : undefined,
160
+ clientId: clientId != null ? String(clientId) : undefined,
161
+ }),
162
+ android: { priority: "high" },
163
+ apns: { headers: { "apns-priority": "10" } },
164
+ }),
165
+ };
166
+ const res = await this.fetch(this.endpoint, {
167
+ method: "POST",
168
+ headers: {
169
+ Authorization: `Bearer ${accessToken}`,
170
+ "Content-Type": "application/json",
171
+ },
172
+ body: JSON.stringify(message),
173
+ });
174
+ if (res.ok) {
175
+ const json = await res.json().catch(() => ({}));
176
+ return { id: json.name || null };
177
+ }
178
+ const text = await safeText(res);
179
+ // A retired/uninstalled token surfaces as HTTP 404 or an UNREGISTERED /
180
+ // NOT_FOUND error status — prune it rather than retry forever.
181
+ if (res.status === 404 || /UNREGISTERED|NOT_FOUND/i.test(text)) {
182
+ throw new PushTokenUnregisteredError(`FCM token unregistered: ${text}`);
183
+ }
184
+ throw new Error(`FCM send failed (${res.status}): ${text}`);
185
+ }
186
+ }
187
+
188
+ function loadServiceAccount(env, options) {
189
+ if (options.serviceAccount) return options.serviceAccount;
190
+ const inline = env.CHAINLESSCHAIN_REMOTE_SESSION_FCM_SERVICE_ACCOUNT_JSON;
191
+ if (inline) {
192
+ try {
193
+ return JSON.parse(inline);
194
+ } catch {
195
+ return null;
196
+ }
197
+ }
198
+ const filePath = env.CHAINLESSCHAIN_REMOTE_SESSION_FCM_SERVICE_ACCOUNT;
199
+ if (filePath) {
200
+ const fsMod = options.fs || fs;
201
+ try {
202
+ return JSON.parse(fsMod.readFileSync(filePath, "utf8"));
203
+ } catch {
204
+ return null;
205
+ }
206
+ }
207
+ return null;
208
+ }
209
+
210
+ /**
211
+ * Build a bound FCM `sender` from env, or null when FCM is not configured. The
212
+ * provider dispatch (fcm vs apns vs …) is the caller's job — see
213
+ * remote-session-push-senders.js.
214
+ *
215
+ * FCM env:
216
+ * CHAINLESSCHAIN_REMOTE_SESSION_FCM_SERVICE_ACCOUNT (path to JSON), or
217
+ * CHAINLESSCHAIN_REMOTE_SESSION_FCM_SERVICE_ACCOUNT_JSON (inline JSON)
218
+ * CHAINLESSCHAIN_REMOTE_SESSION_FCM_PROJECT_ID (optional; else service
219
+ * account project_id)
220
+ */
221
+ export function createFcmPushSender(env = {}, options = {}) {
222
+ const serviceAccount = loadServiceAccount(env, options);
223
+ if (!serviceAccount) return null;
224
+ const projectId =
225
+ env.CHAINLESSCHAIN_REMOTE_SESSION_FCM_PROJECT_ID ||
226
+ serviceAccount.project_id;
227
+ if (!projectId) return null;
228
+ let tokenProvider;
229
+ try {
230
+ tokenProvider =
231
+ options.tokenProvider ||
232
+ new GoogleServiceAccountTokenProvider(serviceAccount, options);
233
+ } catch {
234
+ return null; // Malformed service account — stay disabled rather than crash.
235
+ }
236
+ const sender = new FcmV1PushSender({ projectId, tokenProvider, ...options });
237
+ return sender.handler;
238
+ }
@@ -0,0 +1,174 @@
1
+ // Remote Session vendor push — Huawei (HMS Push Kit) sender.
2
+ //
3
+ // A concrete implementation of the RemoteSessionPushDispatcher `sender` contract
4
+ // for Huawei/HarmonyOS devices via HMS Push Kit. Auth is OAuth2 client
5
+ // credentials (app id + secret) exchanged for a short-lived access token —
6
+ // simpler than FCM's signed JWT — then a Bearer POST to messages:send. Plain
7
+ // HTTPS with an injectable `fetch` (default global), fully unit-testable with a
8
+ // fake transport. The payload carries only routing ids (no session content).
9
+
10
+ import { PushTokenUnregisteredError } from "./remote-session-push-errors.js";
11
+
12
+ const DEFAULT_TOKEN_URI =
13
+ "https://oauth-login.cloud.huawei.com/oauth2/v3/token";
14
+ const DEFAULT_PUSH_HOST = "https://push-api.cloud.huawei.com";
15
+ const ACCESS_TOKEN_SKEW_MS = 60_000;
16
+ // HMS success + invalid-token result codes (single-token send).
17
+ const CODE_SUCCESS = "80000000";
18
+ const CODE_ALL_TOKENS_INVALID = "80300007";
19
+ const CODE_SOME_TOKENS_INVALID = "80100000";
20
+ const INVALID_TOKEN_RE = /invalid.*token|token.*invalid|unregist/i;
21
+
22
+ async function safeText(res) {
23
+ try {
24
+ return await res.text();
25
+ } catch {
26
+ return "";
27
+ }
28
+ }
29
+
30
+ function dropUndefined(obj) {
31
+ const out = {};
32
+ for (const [key, value] of Object.entries(obj)) {
33
+ if (value !== undefined) out[key] = value;
34
+ }
35
+ return out;
36
+ }
37
+
38
+ /** Mints + caches an OAuth2 access token from HMS client credentials. */
39
+ export class HuaweiTokenProvider {
40
+ constructor(options = {}) {
41
+ if (!options.appId)
42
+ throw new Error("HuaweiTokenProvider requires an appId");
43
+ if (!options.appSecret) {
44
+ throw new Error("HuaweiTokenProvider requires an appSecret");
45
+ }
46
+ this.appId = options.appId;
47
+ this.appSecret = options.appSecret;
48
+ this.tokenUri = options.tokenUri || DEFAULT_TOKEN_URI;
49
+ this.now = options.now || Date.now;
50
+ this.fetch = options.fetch || globalThis.fetch;
51
+ this._cached = null; // { token, expiresAt }
52
+ }
53
+
54
+ async getAccessToken() {
55
+ const now = this.now();
56
+ if (this._cached && this._cached.expiresAt - ACCESS_TOKEN_SKEW_MS > now) {
57
+ return this._cached.token;
58
+ }
59
+ const body = new URLSearchParams({
60
+ grant_type: "client_credentials",
61
+ client_id: this.appId,
62
+ client_secret: this.appSecret,
63
+ });
64
+ const res = await this.fetch(this.tokenUri, {
65
+ method: "POST",
66
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
67
+ body: body.toString(),
68
+ });
69
+ if (!res.ok) {
70
+ throw new Error(
71
+ `Huawei OAuth failed (${res.status}): ${await safeText(res)}`,
72
+ );
73
+ }
74
+ const json = await res.json();
75
+ if (!json.access_token) {
76
+ throw new Error("Huawei OAuth response missing access_token");
77
+ }
78
+ const ttl = (Number(json.expires_in) || 3600) * 1000;
79
+ this._cached = { token: json.access_token, expiresAt: now + ttl };
80
+ return this._cached.token;
81
+ }
82
+ }
83
+
84
+ /** Sends one notification to an HMS device token. */
85
+ export class HuaweiPushSender {
86
+ constructor(options = {}) {
87
+ if (!options.appId) throw new Error("HuaweiPushSender requires an appId");
88
+ if (!options.tokenProvider) {
89
+ throw new Error("HuaweiPushSender requires a tokenProvider");
90
+ }
91
+ this.appId = options.appId;
92
+ this.tokenProvider = options.tokenProvider;
93
+ this.fetch = options.fetch || globalThis.fetch;
94
+ this.endpoint =
95
+ options.endpoint ||
96
+ `${options.host || DEFAULT_PUSH_HOST}/v1/${options.appId}/messages:send`;
97
+ }
98
+
99
+ /** Bound function matching the dispatcher's `sender` contract. */
100
+ get handler() {
101
+ return (payload) => this.send(payload);
102
+ }
103
+
104
+ async send({ token, notification, sessionId, clientId } = {}) {
105
+ if (!token) throw new Error("Huawei send requires a device token");
106
+ const accessToken = await this.tokenProvider.getAccessToken();
107
+ const data = { type: "remote-session.approval-request" };
108
+ if (sessionId != null) data.sessionId = String(sessionId);
109
+ if (clientId != null) data.clientId = String(clientId);
110
+ const message = {
111
+ message: dropUndefined({
112
+ notification: notification
113
+ ? dropUndefined({
114
+ title: notification.title,
115
+ body: notification.body,
116
+ })
117
+ : undefined,
118
+ android: { urgency: "HIGH" },
119
+ data: JSON.stringify(data), // routing ids only, no session content
120
+ token: [token],
121
+ }),
122
+ };
123
+ const res = await this.fetch(this.endpoint, {
124
+ method: "POST",
125
+ headers: {
126
+ Authorization: `Bearer ${accessToken}`,
127
+ "Content-Type": "application/json",
128
+ },
129
+ body: JSON.stringify(message),
130
+ });
131
+ const json = await res.json().catch(() => ({}));
132
+ if (res.ok && json.code === CODE_SUCCESS) {
133
+ return { id: json.requestId || null };
134
+ }
135
+ const code = json.code || `HTTP ${res.status}`;
136
+ const msg = json.msg || "";
137
+ // A single-token send that reports invalid/all-invalid tokens means this
138
+ // token is dead — prune it rather than retry.
139
+ if (
140
+ code === CODE_ALL_TOKENS_INVALID ||
141
+ code === CODE_SOME_TOKENS_INVALID ||
142
+ INVALID_TOKEN_RE.test(String(msg))
143
+ ) {
144
+ throw new PushTokenUnregisteredError(
145
+ `Huawei token invalid (${code}): ${msg}`.trim(),
146
+ );
147
+ }
148
+ throw new Error(`Huawei push failed (${code}): ${msg}`.trim());
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Build a bound Huawei `sender` from env, or null when unconfigured.
154
+ *
155
+ * Huawei env:
156
+ * CHAINLESSCHAIN_REMOTE_SESSION_HUAWEI_APP_ID
157
+ * CHAINLESSCHAIN_REMOTE_SESSION_HUAWEI_APP_SECRET
158
+ */
159
+ export function createHuaweiPushSender(env = {}, options = {}) {
160
+ const appId =
161
+ options.appId || env.CHAINLESSCHAIN_REMOTE_SESSION_HUAWEI_APP_ID;
162
+ const appSecret =
163
+ options.appSecret || env.CHAINLESSCHAIN_REMOTE_SESSION_HUAWEI_APP_SECRET;
164
+ if (!appId || !appSecret) return null;
165
+ let tokenProvider;
166
+ try {
167
+ tokenProvider =
168
+ options.tokenProvider ||
169
+ new HuaweiTokenProvider({ appId, appSecret, ...options });
170
+ } catch {
171
+ return null; // Malformed credentials — stay disabled rather than crash.
172
+ }
173
+ return new HuaweiPushSender({ appId, tokenProvider, ...options }).handler;
174
+ }
@@ -0,0 +1,171 @@
1
+ // Remote Session vendor push — OPPO (HeyTap / ColorOS push) sender.
2
+ //
3
+ // A concrete implementation of the RemoteSessionPushDispatcher `sender` contract
4
+ // for OPPO/OnePlus/realme devices via the OPPO Push (HeyTap) server API. Auth is
5
+ // a two-step handshake: sign = SHA256(app_key + timestamp + master_secret) is
6
+ // exchanged for a short-lived `auth_token` (valid ~24h), then a form POST to the
7
+ // unicast endpoint carries that token. Plain HTTPS with an injectable `fetch`
8
+ // (default global), fully unit-testable with a fake transport. The payload
9
+ // carries only routing ids (no session content).
10
+
11
+ import { createHash } from "node:crypto";
12
+ import { PushTokenUnregisteredError } from "./remote-session-push-errors.js";
13
+
14
+ const DEFAULT_HOST = "https://api.push.oppomobile.com";
15
+ const AUTH_PATH = "/server/v1/auth";
16
+ const SEND_PATH = "/server/v1/message/notification/unicast";
17
+ // OPPO auth_token is valid for 24h; refresh a few minutes early.
18
+ const AUTH_TOKEN_TTL_MS = 24 * 60 * 60 * 1000;
19
+ const AUTH_TOKEN_SKEW_MS = 5 * 60_000;
20
+ const CODE_SUCCESS = 0;
21
+ // OPPO has no single canonical "unregistered" code for a stale registration id;
22
+ // match the message text it returns alongside the failing code.
23
+ const INVALID_TOKEN_RE =
24
+ /invalid.*registration|registration.*invalid|invalid.*token|token.*invalid|unregist|not ?exist|无效|不存在/i;
25
+
26
+ async function safeText(res) {
27
+ try {
28
+ return await res.text();
29
+ } catch {
30
+ return "";
31
+ }
32
+ }
33
+
34
+ function sha256Hex(input) {
35
+ return createHash("sha256").update(input, "utf8").digest("hex");
36
+ }
37
+
38
+ /** Mints + caches an OPPO auth_token from app key + master secret. */
39
+ export class OppoAuthProvider {
40
+ constructor(options = {}) {
41
+ if (!options.appKey) throw new Error("OppoAuthProvider requires an appKey");
42
+ if (!options.masterSecret) {
43
+ throw new Error("OppoAuthProvider requires a masterSecret");
44
+ }
45
+ this.appKey = options.appKey;
46
+ this.masterSecret = options.masterSecret;
47
+ this.host = options.host || DEFAULT_HOST;
48
+ this.now = options.now || Date.now;
49
+ this.fetch = options.fetch || globalThis.fetch;
50
+ this._cached = null; // { token, expiresAt }
51
+ }
52
+
53
+ async getAuthToken() {
54
+ const now = this.now();
55
+ if (this._cached && this._cached.expiresAt - AUTH_TOKEN_SKEW_MS > now) {
56
+ return this._cached.token;
57
+ }
58
+ const timestamp = String(now);
59
+ const sign = sha256Hex(`${this.appKey}${timestamp}${this.masterSecret}`);
60
+ const body = new URLSearchParams({
61
+ app_key: this.appKey,
62
+ sign,
63
+ timestamp,
64
+ });
65
+ const res = await this.fetch(`${this.host}${AUTH_PATH}`, {
66
+ method: "POST",
67
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
68
+ body: body.toString(),
69
+ });
70
+ if (!res.ok) {
71
+ throw new Error(
72
+ `OPPO auth failed (${res.status}): ${await safeText(res)}`,
73
+ );
74
+ }
75
+ const json = await res.json();
76
+ const token = json?.data?.auth_token;
77
+ if (Number(json.code) !== CODE_SUCCESS || !token) {
78
+ throw new Error(
79
+ `OPPO auth response missing auth_token: ${json.message || json.code}`,
80
+ );
81
+ }
82
+ this._cached = { token, expiresAt: now + AUTH_TOKEN_TTL_MS };
83
+ return token;
84
+ }
85
+ }
86
+
87
+ /** Sends one notification to an OPPO registration id. */
88
+ export class OppoPushSender {
89
+ constructor(options = {}) {
90
+ if (!options.authProvider) {
91
+ throw new Error("OppoPushSender requires an authProvider");
92
+ }
93
+ this.authProvider = options.authProvider;
94
+ this.host = options.host || DEFAULT_HOST;
95
+ this.fetch = options.fetch || globalThis.fetch;
96
+ }
97
+
98
+ /** Bound function matching the dispatcher's `sender` contract. */
99
+ get handler() {
100
+ return (payload) => this.send(payload);
101
+ }
102
+
103
+ async send({ token, notification, sessionId, clientId } = {}) {
104
+ if (!token) throw new Error("OPPO send requires a registration id");
105
+ const authToken = await this.authProvider.getAuthToken();
106
+ // Routing ids only — no session content.
107
+ const data = { type: "remote-session.approval-request" };
108
+ if (sessionId != null) data.sessionId = String(sessionId);
109
+ if (clientId != null) data.clientId = String(clientId);
110
+ const message = {
111
+ target_type: 2, // 2 = single registration_id
112
+ target_value: token,
113
+ notification: {
114
+ title: notification?.title || "Approval requested",
115
+ content: notification?.body || "A coding session needs your approval",
116
+ click_action_type: 0, // open the app
117
+ action_parameters: JSON.stringify(data),
118
+ },
119
+ };
120
+ const body = new URLSearchParams({
121
+ auth_token: authToken,
122
+ message: JSON.stringify(message),
123
+ });
124
+ const res = await this.fetch(`${this.host}${SEND_PATH}`, {
125
+ method: "POST",
126
+ headers: {
127
+ "Content-Type": "application/x-www-form-urlencoded",
128
+ auth_token: authToken,
129
+ },
130
+ body: body.toString(),
131
+ });
132
+ const json = await res.json().catch(() => ({}));
133
+ if (res.ok && Number(json.code) === CODE_SUCCESS) {
134
+ return { id: json.data?.messageId || null };
135
+ }
136
+ const reason = json.message || `HTTP ${res.status}`;
137
+ if (INVALID_TOKEN_RE.test(String(reason))) {
138
+ throw new PushTokenUnregisteredError(
139
+ `OPPO registration id invalid: ${reason}`,
140
+ );
141
+ }
142
+ throw new Error(`OPPO push failed (${json.code ?? res.status}): ${reason}`);
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Build a bound OPPO `sender` from env, or null when unconfigured.
148
+ *
149
+ * OPPO env:
150
+ * CHAINLESSCHAIN_REMOTE_SESSION_OPPO_APP_KEY
151
+ * CHAINLESSCHAIN_REMOTE_SESSION_OPPO_MASTER_SECRET
152
+ * CHAINLESSCHAIN_REMOTE_SESSION_OPPO_HOST (optional; override the API host)
153
+ */
154
+ export function createOppoPushSender(env = {}, options = {}) {
155
+ const appKey =
156
+ options.appKey || env.CHAINLESSCHAIN_REMOTE_SESSION_OPPO_APP_KEY;
157
+ const masterSecret =
158
+ options.masterSecret ||
159
+ env.CHAINLESSCHAIN_REMOTE_SESSION_OPPO_MASTER_SECRET;
160
+ if (!appKey || !masterSecret) return null;
161
+ const host = options.host || env.CHAINLESSCHAIN_REMOTE_SESSION_OPPO_HOST;
162
+ let authProvider;
163
+ try {
164
+ authProvider =
165
+ options.authProvider ||
166
+ new OppoAuthProvider({ appKey, masterSecret, host, ...options });
167
+ } catch {
168
+ return null; // Malformed credentials — stay disabled rather than crash.
169
+ }
170
+ return new OppoPushSender({ authProvider, host, ...options }).handler;
171
+ }
@@ -0,0 +1,42 @@
1
+ // Remote Session vendor push — provider dispatch.
2
+ //
3
+ // Picks a concrete `sender` implementation by CHAINLESSCHAIN_REMOTE_SESSION_
4
+ // PUSH_PROVIDER (default "fcm"). Each provider factory returns a bound sender
5
+ // or null when unconfigured; unknown/unimplemented providers yield null so the
6
+ // dispatcher stays a no-op. Adding a provider (HMS / Xiaomi / …) is one import
7
+ // + one case here.
8
+
9
+ import { createFcmPushSender } from "./remote-session-push-fcm.js";
10
+ import { createApnsPushSender } from "./remote-session-push-apns.js";
11
+ import { createWebPushSender } from "./remote-session-push-web.js";
12
+ import { createXiaomiPushSender } from "./remote-session-push-xiaomi.js";
13
+ import { createHuaweiPushSender } from "./remote-session-push-huawei.js";
14
+ import { createOppoPushSender } from "./remote-session-push-oppo.js";
15
+ import { createVivoPushSender } from "./remote-session-push-vivo.js";
16
+
17
+ export function createRemoteSessionPushSender(env = {}, options = {}) {
18
+ const provider = (
19
+ env.CHAINLESSCHAIN_REMOTE_SESSION_PUSH_PROVIDER || "fcm"
20
+ ).toLowerCase();
21
+ switch (provider) {
22
+ case "fcm":
23
+ return createFcmPushSender(env, options);
24
+ case "apns":
25
+ return createApnsPushSender(env, options);
26
+ case "web":
27
+ return createWebPushSender(env, options);
28
+ case "xiaomi":
29
+ case "mi":
30
+ return createXiaomiPushSender(env, options);
31
+ case "huawei":
32
+ case "hms":
33
+ return createHuaweiPushSender(env, options);
34
+ case "oppo":
35
+ case "heytap":
36
+ return createOppoPushSender(env, options);
37
+ case "vivo":
38
+ return createVivoPushSender(env, options);
39
+ default:
40
+ return null;
41
+ }
42
+ }