chainlesschain 0.162.101 → 0.162.103

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 (214) hide show
  1. package/README.md +1 -1
  2. package/package.json +2 -2
  3. package/scripts/postinstall.mjs +33 -3
  4. package/src/assets/web-panel/assets/{AIOps-NeD6mA1O.js → AIOps-BpJ5yCcV.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-Bhp7A7vO.js → ActionButton-B1uBfxnA.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-DgdI84Jy.js → Analytics-0ubw5quf.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-Qw29caa7.js → AppLayout-go-GyyM_.js} +4 -4
  8. package/src/assets/web-panel/assets/{Audit-DgkzhBjz.js → Audit-CdUfoUnM.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-CiWEQoW5.js → Backup-CBn0xtsa.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-BWNbkeDF.js → BaseInput-a9fqTkvL.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-CUcj4Vyd.js → Chat-D-d_IatD.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-bAMNCgEj.js → ChatBubbleRenderer-Co9h0SbA.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-B0uZTW8f.js → Checkbox-CiKEjtXm.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-BPk7b-Xa.js → Codegen-OQxW_9Z2.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-C2cV_dw4.js → Col-BZHXUEnZ.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-CDN6Yggb.js → Community-D3xno9fj.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-C4WPtW_V.js → Compact-D62jpZk_.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-BGUfeH-3.js → Compliance-D4jFTAcQ.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-CsnVgF-o.js → Cowork-BOU_wir4.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron--rGanASk.js → Cron-Cr9BlJA7.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-D_CLrd-0.js → Crosschain-L9M--CXb.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-iYkbVc8W.js → DID-xFu3-VWT.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-CuFMU98t.js → Dashboard--ax48T3j.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-BPSjv_Ye.js → Dropdown-CTOS8daR.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-DBmk7czO.js → EmailListRenderer-CNLSX81U.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BaCArN11.js → FamilyGuardDashboard-5EjFynBc.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-DsTV-8Ca.js → Federation-CoPyIWIu.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-C6fRr_6F.js → FormItemContext-BbzCNbYy.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-AfuZCfyv.js → GenericCardRenderer-8_9_uXY0.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-CAFIrGhb.js → Git-Bz_qlVPg.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-C-NaxDs4.js → Governance-7eepNCeD.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-BPfeNZP7.js → Inference-DlBx5l1g.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-DDUBte_m.js → KnowledgeGraph-BPkc--rM.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-DWysA6JP.js → Logs-BQw4nPvS.js} +2 -2
  35. package/src/assets/web-panel/assets/{Marketplace-CAK68hXc.js → Marketplace-Cil3SdgN.js} +1 -1
  36. package/src/assets/web-panel/assets/{McpTools-CNmmoDAk.js → McpTools-Bnch7KrG.js} +3 -3
  37. package/src/assets/web-panel/assets/{Memory-D_KmFoui.js → Memory-assQ3Suc.js} +2 -2
  38. package/src/assets/web-panel/assets/{MobileBridge-CaY-6kjj.js → MobileBridge-D5mE8c-z.js} +2 -2
  39. package/src/assets/web-panel/assets/MobileProjects-C4MiDXw1.js +1 -0
  40. package/src/assets/web-panel/assets/{Mtc-YnLrtuXT.js → Mtc-CumojeLk.js} +5 -5
  41. package/src/assets/web-panel/assets/{MtcAudit-D38v836u.js → MtcAudit-3tTBc_fw.js} +4 -4
  42. package/src/assets/web-panel/assets/{Multisig-Dv3ad9S4.js → Multisig-Dl4IJBsl.js} +3 -3
  43. package/src/assets/web-panel/assets/{NLProgramming-MYTh2eWC.js → NLProgramming-DHN_cV9x.js} +1 -1
  44. package/src/assets/web-panel/assets/{Notes-DYO-0Hva.js → Notes-DeyTvwYk.js} +3 -3
  45. package/src/assets/web-panel/assets/{NotificationSettings-D39gOb3h.js → NotificationSettings-BUkj8mTj.js} +1 -1
  46. package/src/assets/web-panel/assets/{OrderTableRenderer-CXdO0z3I.js → OrderTableRenderer-BMpEXTki.js} +1 -1
  47. package/src/assets/web-panel/assets/{Organization-DOYTwQwt.js → Organization-B6Ogmkpz.js} +4 -4
  48. package/src/assets/web-panel/assets/{Overflow-Cm-go0yU.js → Overflow-DRUE-2Wz.js} +1 -1
  49. package/src/assets/web-panel/assets/{P2P-CakGtorw.js → P2P-CnMUkd64.js} +2 -2
  50. package/src/assets/web-panel/assets/{PdhVaultBrowser-BlmbHJnb.js → PdhVaultBrowser-B3nFxT3b.js} +4 -4
  51. package/src/assets/web-panel/assets/{Permissions-C_podo04.js → Permissions-CyG1FR7v.js} +4 -4
  52. package/src/assets/web-panel/assets/{PersonalDataHub-nBbehIKp.js → PersonalDataHub-BmN6YMb5.js} +3 -3
  53. package/src/assets/web-panel/assets/{Pipeline-CGzQUoKv.js → Pipeline-BZcamAnH.js} +1 -1
  54. package/src/assets/web-panel/assets/{Privacy-BuD-Dd9m.js → Privacy-D7_zsaY3.js} +1 -1
  55. package/src/assets/web-panel/assets/{ProjectInit-CLcFmGXU.js → ProjectInit-DNa7XQjg.js} +2 -2
  56. package/src/assets/web-panel/assets/{ProjectSettings-BxXTR0dH.js → ProjectSettings-KH2Dm09G.js} +2 -2
  57. package/src/assets/web-panel/assets/{Projects-DkpBjAfd.js → Projects-T-WqR3D6.js} +1 -1
  58. package/src/assets/web-panel/assets/{Providers-DwHK42ht.js → Providers-8GtQ6GgR.js} +1 -1
  59. package/src/assets/web-panel/assets/{QuickAsk-idJPKErk.js → QuickAsk-BuBM5Zpg.js} +1 -1
  60. package/src/assets/web-panel/assets/{Recommend-BvJj0Nu2.js → Recommend-DvH95W1b.js} +1 -1
  61. package/src/assets/web-panel/assets/{Reputation-BK9l4Rcf.js → Reputation-BHCpMEP6.js} +1 -1
  62. package/src/assets/web-panel/assets/{Row-rLxg7xk5.js → Row-BnMPysBm.js} +1 -1
  63. package/src/assets/web-panel/assets/{RssFeed-Cw6ZcHBY.js → RssFeed-C-T8B5VU.js} +3 -3
  64. package/src/assets/web-panel/assets/{Search-D8mKp506.js → Search-CioiZGkX.js} +1 -1
  65. package/src/assets/web-panel/assets/{Security-CXunojmQ.js → Security-zSApKqdd.js} +4 -4
  66. package/src/assets/web-panel/assets/{Services-BE5DgmPl.js → Services-CQh0UDXC.js} +2 -2
  67. package/src/assets/web-panel/assets/{Skeleton-CssL5wmE.js → Skeleton-C0dRaEVG.js} +1 -1
  68. package/src/assets/web-panel/assets/{Skills-zyS_1YrT.js → Skills-BgQRArsa.js} +1 -1
  69. package/src/assets/web-panel/assets/{Sla-DIVn2Roa.js → Sla-BNn7F3jc.js} +1 -1
  70. package/src/assets/web-panel/assets/{SpeechSettings-CjNdZWdj.js → SpeechSettings-kvVfKfqv.js} +1 -1
  71. package/src/assets/web-panel/assets/{SyncSettings-BCbu5WQT.js → SyncSettings-BwIkkX1E.js} +2 -2
  72. package/src/assets/web-panel/assets/{Tasks-BjFrgXge.js → Tasks-BkcvhBZw.js} +1 -1
  73. package/src/assets/web-panel/assets/{Templates-Cl4Y3lKN.js → Templates-COK0o2pL.js} +1 -1
  74. package/src/assets/web-panel/assets/{Tenant-k1T0F_eH.js → Tenant-DpK87fqo.js} +1 -1
  75. package/src/assets/web-panel/assets/{Terminal-i0tHF8fy.js → Terminal-2g2-XPT_.js} +2 -2
  76. package/src/assets/web-panel/assets/{TimelineRenderer-FmPaNyqK.js → TimelineRenderer-B4SJqyqf.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tokens-BUWEiyab.js → Tokens-C3iSR8fH.js} +1 -1
  78. package/src/assets/web-panel/assets/{Trigger-9srMXwpo.js → Trigger-D1Oo58Yo.js} +1 -1
  79. package/src/assets/web-panel/assets/{Trust-hmGor_R2.js → Trust-CUhy4DDc.js} +1 -1
  80. package/src/assets/web-panel/assets/{UkeySign-Dst7K10a.js → UkeySign-r-kTqSw8.js} +1 -1
  81. package/src/assets/web-panel/assets/{VideoEditing-D_utUv1u.js → VideoEditing-B8MyBfCX.js} +1 -1
  82. package/src/assets/web-panel/assets/{Wallet-BpYTTseI.js → Wallet-CquTfSv0.js} +4 -4
  83. package/src/assets/web-panel/assets/{WebAuthn-CEIw8QrF.js → WebAuthn-BC7TP1H3.js} +5 -5
  84. package/src/assets/web-panel/assets/{WorkflowEditor-Bkb4bUZ_.js → WorkflowEditor-CELBSNbP.js} +1 -1
  85. package/src/assets/web-panel/assets/{chat-CTqDgGRH.js → chat-d66mbO2i.js} +1 -1
  86. package/src/assets/web-panel/assets/{colors-CnJHVFLk.js → colors-D0WRCg20.js} +1 -1
  87. package/src/assets/web-panel/assets/{compact-item-D4-v0ahl.js → compact-item-4__-8TIi.js} +1 -1
  88. package/src/assets/web-panel/assets/{createContext-CYDbnHZX.js → createContext-Dqsf52js.js} +1 -1
  89. package/src/assets/web-panel/assets/devWarning-DUAmX0aE.js +1 -0
  90. package/src/assets/web-panel/assets/{hasIn-CbYwlEz5.js → hasIn-ClTZOS8I.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-DVKfa-z4.js → index-0Axwx1nN.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-Cl7-mbsb.js → index-81SaeU6t.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-CK8nROWH.js → index-B1TGSkBt.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-BygyTsjr.js → index-B3vrmn5v.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-B78d_-pR.js → index-BCOpbfAH.js} +1 -1
  96. package/src/assets/web-panel/assets/index-BLCwqv8j.js +1 -0
  97. package/src/assets/web-panel/assets/{index-YuO8gxKb.js → index-BNKECBZm.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-tmNsJzQU.js → index-BwozLU75.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-dKpwhR5x.js → index-C4_XhCJS.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BNz3yht2.js → index-C5l6tSky.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-4767GEls.js → index-CFwkeSun.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BCQ4kcL3.js → index-CGf3oqM0.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-xDe5BkjU.js → index-CJKyF-y8.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DjFQuno4.js → index-Cg8Qw3Jw.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-Dl_NdY39.js → index-Cops66s2.js} +3 -3
  106. package/src/assets/web-panel/assets/{index-D8MYVsVB.js → index-Ct5SJenr.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CY971z_9.js → index-CxoWS8hl.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CVc_5aBF.js → index-D-ya3Ohe.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-C4chfOjw.js → index-D68tyHYa.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BR4facv0.js → index-D9x6i7iV.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CxKdSB-p.js → index-DDLjwgva.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-B-hiXmVK.js → index-DORg0Hps.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BR0FfrCL.js → index-DQaGurrQ.js} +1 -1
  114. package/src/assets/web-panel/assets/index-Db5JRQ6E.js +1 -0
  115. package/src/assets/web-panel/assets/{index-5NXdAIZK.js → index-DbY1lv1z.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-BOUSaCsn.js → index-Dm7CYK3j.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DxvFrsxl.js → index-DpLhC0nx.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-qNwkSspX.js → index-P4X9zSF2.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-OqmUv5QO.js → index-PAiWA2un.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-B8uhiRML.js → index-PMJ4tBAA.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CfshMjFN.js → index-Yi5Lr63I.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-wxz2s6uW.js → index-ZZpfHd4J.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DdRtpQFq.js → index-b7a5aEX9.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-C2fICr5d.js → index-e5zJLrJQ.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BGUOVPjO.js → index-fi2ikHoc.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-8cmtr1jC.js → index-jAPphbJL.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-DMKNVIqJ.js → index-leuwRsbf.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CP-UKwu8.js → index-luisYDBz.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DaCCLe7i.js → index-we9-1IbF.js} +1 -1
  130. package/src/assets/web-panel/assets/{initDefaultProps-qCQgpAty.js → initDefaultProps-zqDf7qWv.js} +1 -1
  131. package/src/assets/web-panel/assets/{motion-SkVSGdwu.js → motion-DRqTooll.js} +1 -1
  132. package/src/assets/web-panel/assets/{move-BpWxaW_x.js → move-N8tkRqS4.js} +1 -1
  133. package/src/assets/web-panel/assets/{omit-DlZWM2H4.js → omit-Cy01f2vw.js} +1 -1
  134. package/src/assets/web-panel/assets/{pickAttrs-DobtUnVe.js → pickAttrs-C2ycNXNi.js} +1 -1
  135. package/src/assets/web-panel/assets/{placementArrow-CD6mYRZi.js → placementArrow-D60Xb1j9.js} +1 -1
  136. package/src/assets/web-panel/assets/{responsiveObserve-DvIIMTD_.js → responsiveObserve-BF8Y-1HQ.js} +1 -1
  137. package/src/assets/web-panel/assets/{slide-CVgyUfaA.js → slide-DDi6zYZf.js} +1 -1
  138. package/src/assets/web-panel/assets/{statusUtils-DrUDnjy_.js → statusUtils-CWPq6UzA.js} +1 -1
  139. package/src/assets/web-panel/assets/{styleChecker-BNYQS60m.js → styleChecker-D4zVyb4w.js} +1 -1
  140. package/src/assets/web-panel/assets/{useFlexGapSupport-DYv7Tvk0.js → useFlexGapSupport-BEMNO3oR.js} +1 -1
  141. package/src/assets/web-panel/assets/{useFs-DmAD2atJ.js → useFs-DBLO8VR7.js} +1 -1
  142. package/src/assets/web-panel/assets/{usePersonalDataHub-DbLVKA3I.js → usePersonalDataHub-C83ZIrPY.js} +1 -1
  143. package/src/assets/web-panel/assets/{vnode-C3WEt6_P.js → vnode-Dcfyve-7.js} +1 -1
  144. package/src/assets/web-panel/assets/{zoom-CRoSX_oZ.js → zoom-Dg6uoWSc.js} +1 -1
  145. package/src/assets/web-panel/index.html +1 -1
  146. package/src/commands/__tests__/hub-health.test.js +105 -0
  147. package/src/commands/__tests__/hub-stats.test.js +106 -0
  148. package/src/commands/a2a.js +2 -1
  149. package/src/commands/bi.js +4 -3
  150. package/src/commands/browse.js +3 -2
  151. package/src/commands/codegen.js +7 -6
  152. package/src/commands/collab.js +2 -1
  153. package/src/commands/crosschain.js +5 -4
  154. package/src/commands/dao.js +10 -10
  155. package/src/commands/dbevo.js +3 -2
  156. package/src/commands/economy.js +8 -6
  157. package/src/commands/evolution.js +10 -8
  158. package/src/commands/export.js +2 -1
  159. package/src/commands/federation.js +8 -7
  160. package/src/commands/fusion.js +4 -3
  161. package/src/commands/git.js +2 -1
  162. package/src/commands/governance.js +6 -5
  163. package/src/commands/hardening.js +3 -2
  164. package/src/commands/hmemory.js +2 -1
  165. package/src/commands/hub.js +111 -32
  166. package/src/commands/inference.js +9 -8
  167. package/src/commands/infra.js +9 -8
  168. package/src/commands/instinct.js +66 -0
  169. package/src/commands/learning.js +66 -0
  170. package/src/commands/marketplace.js +5 -4
  171. package/src/commands/matrix.js +2 -1
  172. package/src/commands/memory.js +66 -0
  173. package/src/commands/nlprog.js +4 -3
  174. package/src/commands/nostr.js +4 -3
  175. package/src/commands/note.js +2 -1
  176. package/src/commands/ops.js +3 -2
  177. package/src/commands/perception.js +6 -5
  178. package/src/commands/privacy.js +13 -12
  179. package/src/commands/quantization.js +3 -2
  180. package/src/commands/recommend.js +8 -7
  181. package/src/commands/reputation.js +4 -3
  182. package/src/commands/runtime.js +6 -5
  183. package/src/commands/sandbox.js +5 -4
  184. package/src/commands/session.js +2 -1
  185. package/src/commands/sla.js +6 -5
  186. package/src/commands/social.js +6 -5
  187. package/src/commands/stress.js +7 -6
  188. package/src/commands/sync.js +2 -1
  189. package/src/commands/tenant.js +3 -2
  190. package/src/commands/trust.js +7 -6
  191. package/src/commands/wallet.js +2 -1
  192. package/src/gateways/ws/personal-data-hub-protocol.js +16 -3
  193. package/src/lib/__tests__/agents.test.js +225 -0
  194. package/src/lib/__tests__/cowork-observe-html.test.js +141 -0
  195. package/src/lib/__tests__/logger.test.js +110 -0
  196. package/src/lib/__tests__/project-inventory.test.js +280 -0
  197. package/src/lib/__tests__/project-root.test.js +96 -0
  198. package/src/lib/__tests__/provider-stream.test.js +190 -0
  199. package/src/lib/cli-arg.js +41 -0
  200. package/src/lib/cli-numeric.js +12 -3
  201. package/src/lib/instinct-manager.js +64 -0
  202. package/src/lib/learning/learning-tables.js +83 -0
  203. package/src/lib/mcp-oauth.js +10 -2
  204. package/src/lib/memory-manager.js +67 -0
  205. package/src/lib/personal-data-hub-wiring.js +135 -7
  206. package/src/lib/plan-mode.js +3 -2
  207. package/src/repl/__tests__/mcp-prompt.test.js +155 -0
  208. package/src/repl/__tests__/slash-macro.test.js +81 -0
  209. package/src/runtime/__tests__/coding-agent-contract-shared.test.js +136 -0
  210. package/src/runtime/__tests__/coding-agent-events.test.js +263 -0
  211. package/src/assets/web-panel/assets/MobileProjects-DhDxrWze.js +0 -1
  212. package/src/assets/web-panel/assets/devWarning-OhxeajH6.js +0 -1
  213. package/src/assets/web-panel/assets/index-Bbtoi_47.js +0 -1
  214. package/src/assets/web-panel/assets/index-Cb8RWwaB.js +0 -1
@@ -0,0 +1,41 @@
1
+ /**
2
+ * cli-arg — validating commander option coercers.
3
+ *
4
+ * Bare `parseInt` / `parseFloat` / `Number` used as a commander coercer
5
+ * (`.option("--limit <n>", "...", parseInt)`) silently yield NaN on bad input
6
+ * (`--limit abc`), threading NaN into whatever the action does with the option.
7
+ * These factories validate at parse time and raise a commander
8
+ * `InvalidArgumentError`, so a typo fails with the standard
9
+ * `error: option '--limit <n>' argument 'abc' is invalid` message and exit 1 —
10
+ * instead of a silently-corrupt run.
11
+ *
12
+ * Drop-in replacements:
13
+ * .option("--limit <n>", "...", parseInt) -> intArg("--limit")
14
+ * .option("--rate <n>", "...", parseFloat) -> floatArg("--rate")
15
+ * .option("--eps <n>", "...", Number) -> floatArg("--eps")
16
+ *
17
+ * Behaviour for valid and omitted values is identical to the bare coercer
18
+ * (the coercer only runs when the option is supplied with a value); only the
19
+ * NaN-on-bad-input case changes.
20
+ *
21
+ * @param {string} name option label for the message (e.g. "--limit")
22
+ * @param {{min?: number, max?: number}} [opts]
23
+ */
24
+ import { InvalidArgumentError } from "commander";
25
+ import { numericOption } from "./cli-numeric.js";
26
+
27
+ function makeArg(integer) {
28
+ return (name, opts = {}) =>
29
+ (raw) => {
30
+ try {
31
+ return numericOption(raw, { ...opts, name, integer });
32
+ } catch (err) {
33
+ throw new InvalidArgumentError(err.message);
34
+ }
35
+ };
36
+ }
37
+
38
+ export const intArg = makeArg(true);
39
+ export const floatArg = makeArg(false);
40
+
41
+ export default { intArg, floatArg };
@@ -10,7 +10,9 @@
10
10
  * @param {*} raw the option value (string from commander, or undefined)
11
11
  * @param {object} [opts]
12
12
  * @param {string} [opts.name="value"] label for error messages (e.g. "--limit")
13
- * @param {boolean} [opts.integer=false] parse as integer (parseInt) vs float (Number)
13
+ * @param {boolean} [opts.integer=false] require a whole number (rejects 1.5);
14
+ * parsing is strict either way (Number,
15
+ * not parseInt — "12abc" is rejected)
14
16
  * @param {number} [opts.min] inclusive lower bound
15
17
  * @param {number} [opts.max] inclusive upper bound
16
18
  * @param {number} [opts.fallback] returned for missing/invalid input; when
@@ -26,8 +28,15 @@ export function numericOption(raw, opts = {}) {
26
28
  };
27
29
 
28
30
  if (raw == null || raw === "") return fail(`${name} is required`);
29
- const n = integer ? parseInt(raw, 10) : Number(raw);
30
- if (!Number.isFinite(n)) {
31
+ if (typeof raw === "string" && raw.trim() === "")
32
+ return fail(`${name} is required`);
33
+ // Number() (NOT parseInt) so trailing junk is rejected instead of silently
34
+ // truncated: parseInt("12abc") === 12 and parseInt("0x10", 10) === 0 would
35
+ // slip a corrupt value through. Number("12abc") is NaN; whole numbers given
36
+ // via exponent ("1e3" -> 1000) still parse, and a fractional value is
37
+ // rejected in integer mode.
38
+ const n = Number(raw);
39
+ if (!Number.isFinite(n) || (integer && !Number.isInteger(n))) {
31
40
  return fail(
32
41
  `${name} must be a ${integer ? "whole " : ""}number (got ${JSON.stringify(String(raw))})`,
33
42
  );
@@ -20,6 +20,70 @@ export function ensureInstinctsTable(db) {
20
20
  `);
21
21
  }
22
22
 
23
+ /** datetime('now')-style timestamp for rows missing one on import. */
24
+ function _instinctNowSqlite() {
25
+ return new Date().toISOString().replace("T", " ").slice(0, 19);
26
+ }
27
+
28
+ /**
29
+ * Export ALL instincts (full dump) for §8.3 cross-device backup. Deterministic
30
+ * order so the same DB exports identically.
31
+ */
32
+ export function exportInstincts(db) {
33
+ ensureInstinctsTable(db);
34
+ return db
35
+ .prepare(
36
+ `SELECT id, category, pattern, confidence, occurrences, last_seen, created_at
37
+ FROM instincts ORDER BY created_at ASC, id ASC`,
38
+ )
39
+ .all();
40
+ }
41
+
42
+ /**
43
+ * Import instincts (§8.3 restore): idempotent upsert by id — preserves created_at,
44
+ * updates category/pattern/confidence/occurrences/last_seen. Never aborts mid-batch.
45
+ */
46
+ export function importInstincts(db, rows) {
47
+ ensureInstinctsTable(db);
48
+ if (!Array.isArray(rows)) {
49
+ throw new Error("importInstincts: rows must be a JSON array");
50
+ }
51
+ const stmt = db.prepare(
52
+ `INSERT INTO instincts (id, category, pattern, confidence, occurrences, last_seen, created_at)
53
+ VALUES (@id, @category, @pattern, @confidence, @occurrences, @last_seen, @created_at)
54
+ ON CONFLICT(id) DO UPDATE SET
55
+ category = excluded.category,
56
+ pattern = excluded.pattern,
57
+ confidence = excluded.confidence,
58
+ occurrences = excluded.occurrences,
59
+ last_seen = excluded.last_seen`,
60
+ );
61
+ let imported = 0;
62
+ let failed = 0;
63
+ const errors = [];
64
+ for (const r of rows) {
65
+ try {
66
+ if (!r || !r.id || !r.category || !r.pattern) {
67
+ throw new Error("invalid instinct (need id + category + pattern)");
68
+ }
69
+ stmt.run({
70
+ id: String(r.id),
71
+ category: r.category,
72
+ pattern: r.pattern,
73
+ confidence: typeof r.confidence === "number" ? r.confidence : 0.5,
74
+ occurrences: Number.isInteger(r.occurrences) ? r.occurrences : 1,
75
+ last_seen: r.last_seen || _instinctNowSqlite(),
76
+ created_at: r.created_at || _instinctNowSqlite(),
77
+ });
78
+ imported += 1;
79
+ } catch (e) {
80
+ failed += 1;
81
+ if (errors.length < 10) errors.push({ id: r && r.id, error: e.message });
82
+ }
83
+ }
84
+ return { ok: failed === 0, imported, failed, errors };
85
+ }
86
+
23
87
  function generateId() {
24
88
  const hex = () =>
25
89
  Math.floor(Math.random() * 0x10000)
@@ -64,3 +64,86 @@ export function ensureLearningTables(db) {
64
64
  )
65
65
  `);
66
66
  }
67
+
68
+ /** datetime('now')-style timestamp for rows missing one on import. */
69
+ function _learningNowSqlite() {
70
+ return new Date().toISOString().replace("T", " ").slice(0, 19);
71
+ }
72
+
73
+ /**
74
+ * Export ALL learning trajectories (full dump) for §8.3 cross-device backup.
75
+ * Includes the synthesized_skill linkage (synthesized skills travel with the
76
+ * trajectories that produced them). The learning_trajectory_tags side-table is
77
+ * derived and intentionally not exported in v1.
78
+ */
79
+ export function exportTrajectories(db) {
80
+ ensureLearningTables(db);
81
+ return db
82
+ .prepare(
83
+ `SELECT id, session_id, user_intent, tool_chain, tool_count, final_response,
84
+ outcome_score, outcome_source, complexity_level, synthesized_skill,
85
+ created_at, completed_at
86
+ FROM learning_trajectories ORDER BY created_at ASC, id ASC`,
87
+ )
88
+ .all();
89
+ }
90
+
91
+ /**
92
+ * Import learning trajectories (§8.3 restore): idempotent upsert by id —
93
+ * preserves created_at, updates the rest. Never aborts mid-batch.
94
+ */
95
+ export function importTrajectories(db, rows) {
96
+ ensureLearningTables(db);
97
+ if (!Array.isArray(rows)) {
98
+ throw new Error("importTrajectories: rows must be a JSON array");
99
+ }
100
+ const stmt = db.prepare(
101
+ `INSERT INTO learning_trajectories
102
+ (id, session_id, user_intent, tool_chain, tool_count, final_response,
103
+ outcome_score, outcome_source, complexity_level, synthesized_skill,
104
+ created_at, completed_at)
105
+ VALUES (@id, @session_id, @user_intent, @tool_chain, @tool_count, @final_response,
106
+ @outcome_score, @outcome_source, @complexity_level, @synthesized_skill,
107
+ @created_at, @completed_at)
108
+ ON CONFLICT(id) DO UPDATE SET
109
+ session_id = excluded.session_id,
110
+ user_intent = excluded.user_intent,
111
+ tool_chain = excluded.tool_chain,
112
+ tool_count = excluded.tool_count,
113
+ final_response = excluded.final_response,
114
+ outcome_score = excluded.outcome_score,
115
+ outcome_source = excluded.outcome_source,
116
+ complexity_level = excluded.complexity_level,
117
+ synthesized_skill = excluded.synthesized_skill,
118
+ completed_at = excluded.completed_at`,
119
+ );
120
+ let imported = 0;
121
+ let failed = 0;
122
+ const errors = [];
123
+ for (const r of rows) {
124
+ try {
125
+ if (!r || !r.id || !r.session_id) {
126
+ throw new Error("invalid trajectory (need id + session_id)");
127
+ }
128
+ stmt.run({
129
+ id: String(r.id),
130
+ session_id: String(r.session_id),
131
+ user_intent: r.user_intent ?? null,
132
+ tool_chain: r.tool_chain ?? "[]",
133
+ tool_count: Number.isInteger(r.tool_count) ? r.tool_count : 0,
134
+ final_response: r.final_response ?? null,
135
+ outcome_score: typeof r.outcome_score === "number" ? r.outcome_score : null,
136
+ outcome_source: r.outcome_source ?? null,
137
+ complexity_level: r.complexity_level ?? "simple",
138
+ synthesized_skill: r.synthesized_skill ?? null,
139
+ created_at: r.created_at || _learningNowSqlite(),
140
+ completed_at: r.completed_at ?? null,
141
+ });
142
+ imported += 1;
143
+ } catch (e) {
144
+ failed += 1;
145
+ if (errors.length < 10) errors.push({ id: r && r.id, error: e.message });
146
+ }
147
+ }
148
+ return { ok: failed === 0, imported, failed, errors };
149
+ }
@@ -410,7 +410,7 @@ function defaultOpenBrowser(url) {
410
410
  }
411
411
 
412
412
  /** Wait for the OAuth redirect on a localhost callback server; resolve {code,state}. */
413
- function waitForCallback({
413
+ export function waitForCallback({
414
414
  port,
415
415
  host = "127.0.0.1",
416
416
  path = "/callback",
@@ -446,7 +446,15 @@ function waitForCallback({
446
446
  server.close();
447
447
  reject(new Error("timed out waiting for the OAuth callback"));
448
448
  }, timeout);
449
- server.on("error", reject);
449
+ // Backstop timer should never keep the process alive on its own (the
450
+ // listening server does that); the loop exits promptly once it closes.
451
+ timer.unref?.();
452
+ server.on("error", (err) => {
453
+ // Without clearing the timer here, a bind failure (e.g. port in use)
454
+ // left it pending for the full `timeout`, delaying process exit.
455
+ clearTimeout(timer);
456
+ reject(err);
457
+ });
450
458
  server.listen(port, host);
451
459
  });
452
460
  }
@@ -133,6 +133,73 @@ export function listMemory(db, options = {}) {
133
133
  .all(limit);
134
134
  }
135
135
 
136
+ /**
137
+ * Export ALL memory entries (full dump, no limit) for §8.3 cross-device backup.
138
+ * Returns rows with id/content/category/importance/source/created_at/updated_at,
139
+ * ordered deterministically so the same DB exports identically.
140
+ */
141
+ export function exportMemory(db) {
142
+ ensureMemoryTable(db);
143
+ return db
144
+ .prepare(
145
+ `SELECT id, content, category, importance, source, created_at, updated_at
146
+ FROM memory_entries ORDER BY created_at ASC, id ASC`,
147
+ )
148
+ .all();
149
+ }
150
+
151
+ /** datetime('now')-style timestamp for entries missing one on import. */
152
+ function _nowSqlite() {
153
+ return new Date().toISOString().replace("T", " ").slice(0, 19);
154
+ }
155
+
156
+ /**
157
+ * Import memory entries (§8.3 backup restore): idempotent upsert by id —
158
+ * preserves the original created_at, updates content/category/importance/source/
159
+ * updated_at. Never aborts mid-batch; returns {ok, imported, failed, errors}.
160
+ */
161
+ export function importMemory(db, entries) {
162
+ ensureMemoryTable(db);
163
+ if (!Array.isArray(entries)) {
164
+ throw new Error("importMemory: entries must be a JSON array");
165
+ }
166
+ const stmt = db.prepare(
167
+ `INSERT INTO memory_entries
168
+ (id, content, category, importance, source, created_at, updated_at)
169
+ VALUES (@id, @content, @category, @importance, @source, @created_at, @updated_at)
170
+ ON CONFLICT(id) DO UPDATE SET
171
+ content = excluded.content,
172
+ category = excluded.category,
173
+ importance = excluded.importance,
174
+ source = excluded.source,
175
+ updated_at = excluded.updated_at`,
176
+ );
177
+ let imported = 0;
178
+ let failed = 0;
179
+ const errors = [];
180
+ for (const e of entries) {
181
+ try {
182
+ if (!e || !e.id || typeof e.content !== "string") {
183
+ throw new Error("invalid memory entry (need id + content)");
184
+ }
185
+ stmt.run({
186
+ id: String(e.id),
187
+ content: e.content,
188
+ category: e.category || "general",
189
+ importance: Number.isInteger(e.importance) ? e.importance : 3,
190
+ source: e.source || "user",
191
+ created_at: e.created_at || _nowSqlite(),
192
+ updated_at: e.updated_at || _nowSqlite(),
193
+ });
194
+ imported += 1;
195
+ } catch (err) {
196
+ failed += 1;
197
+ if (errors.length < 10) errors.push({ id: e && e.id, error: err.message });
198
+ }
199
+ }
200
+ return { ok: failed === 0, imported, failed, errors };
201
+ }
202
+
136
203
  /**
137
204
  * Delete a memory entry
138
205
  */
@@ -29,11 +29,20 @@ import { mkdirSync } from "node:fs";
29
29
  // Hub package is CJS; in ESM we default-import then destructure (Node 22
30
30
  // won't let us name-import a CJS module unless it ships a separate ESM
31
31
  // shim, which we don't).
32
- import hub from "@chainlesschain/personal-data-hub";
33
- import wechatAdapterModule from "@chainlesschain/personal-data-hub/adapters/wechat";
34
- const { bootstrapWechatAdapter, probeWeChatEnv } = wechatAdapterModule;
35
- const {
36
- LocalVault,
32
+ // PDH is loaded LAZILY (not a top-level static import) so that merely
33
+ // importing this wiring module — which happens on EVERY `cc` invocation,
34
+ // because index.js statically pulls in commands/hub.js (getHub) AND the
35
+ // agent/chat/serve/ui runtime chain (ws pdh-protocol → this wiring) — does
36
+ // NOT eagerly evaluate the entire personal-data-hub adapter graph. Wins:
37
+ // • Robustness: a partial/corrupt pdh install (e.g. an interrupted
38
+ // `npm i -g` that left adapter files unwritten) no longer bricks
39
+ // unrelated commands like `cc -v` with a cryptic ERR_MODULE_NOT_FOUND.
40
+ // pdh only loads when a `cc hub …` / web-shell hub topic actually runs.
41
+ // • Startup cost: non-hub commands skip loading 50+ adapter modules.
42
+ // The bindings below are populated by ensurePdhLoaded(), which initHub() and
43
+ // initHubMinimal() await before touching any of them.
44
+ let bootstrapWechatAdapter, probeWeChatEnv;
45
+ let LocalVault,
37
46
  AdapterRegistry,
38
47
  AnalysisEngine,
39
48
  MockAdapter,
@@ -124,8 +133,125 @@ const {
124
133
  EntityResolverEmbeddingStage,
125
134
  EntityResolverLLMStage,
126
135
  runAnalysisSkill,
127
- ANALYSIS_SKILL_NAMES,
128
- } = hub;
136
+ ANALYSIS_SKILL_NAMES;
137
+
138
+ let _pdhLoadPromise = null;
139
+ /**
140
+ * Lazily import the personal-data-hub package + wechat adapter and populate
141
+ * the module-level binding holders above. Memoized so concurrent
142
+ * getHub()/getHubMinimal() callers share one load. Must be awaited by any
143
+ * code path that uses a pdh binding (initHub / initHubMinimal).
144
+ */
145
+ function ensurePdhLoaded() {
146
+ if (!_pdhLoadPromise) {
147
+ _pdhLoadPromise = (async () => {
148
+ const hubMod = await import("@chainlesschain/personal-data-hub");
149
+ const hub = hubMod.default || hubMod;
150
+ // Composed specifier dodges vite/vitest static import-analysis of
151
+ // subpath exports (same trick as loadAIChatHealthChecker below).
152
+ const wechatSpec =
153
+ "@chainlesschain/personal-data-hub" + "/adapters/wechat";
154
+ const waMod = await import(wechatSpec);
155
+ const wechatAdapterModule = waMod.default || waMod;
156
+ ({ bootstrapWechatAdapter, probeWeChatEnv } = wechatAdapterModule);
157
+ ({
158
+ LocalVault,
159
+ AdapterRegistry,
160
+ AnalysisEngine,
161
+ MockAdapter,
162
+ OllamaClient,
163
+ CcKgSink,
164
+ CcRagSink,
165
+ FileKeyProvider,
166
+ generateKeyHex,
167
+ EmailAdapter,
168
+ AlipayBillAdapter,
169
+ SystemDataAndroidAdapter,
170
+ BrowserHistoryChromeAdapter,
171
+ BrowserHistoryEdgeAdapter,
172
+ VSCodeAdapter,
173
+ WinRecentAdapter,
174
+ GitActivityAdapter,
175
+ ShellHistoryAdapter,
176
+ LocalFilesAdapter,
177
+ BilibiliAdapter,
178
+ WeiboAdapter,
179
+ ZhihuAdapter,
180
+ DoubanAdapter,
181
+ BossZhipinAdapter,
182
+ CsdnAdapter,
183
+ DongchediAdapter,
184
+ TianyanchaAdapter,
185
+ DouyinAdapter,
186
+ XiaohongshuAdapter,
187
+ ToutiaoAdapter,
188
+ KuaishouAdapter,
189
+ GenshinAdapter,
190
+ HonorOfKingsAdapter,
191
+ ZuoyebangAdapter,
192
+ AlipayAdapter,
193
+ HuaweiLearningAdapter,
194
+ QQAdapter,
195
+ WeChatPcAdapter,
196
+ QQPcAdapter,
197
+ AppleHealthAdapter,
198
+ NeteaseMusicAdapter,
199
+ KugouMusicAdapter,
200
+ QQMusicAdapter,
201
+ XimalayaAdapter,
202
+ FanqieReadingAdapter,
203
+ QimaoReadingAdapter,
204
+ JoyrunAdapter,
205
+ KeepAdapter,
206
+ DidiConsumerAdapter,
207
+ MercedesMeAdapter,
208
+ IqiyiVideoAdapter,
209
+ TencentVideoAdapter,
210
+ XiguaVideoAdapter,
211
+ WeReadAdapter,
212
+ WpsDocAdapter,
213
+ TencentDocsAdapter,
214
+ BaiduNetdiskAdapter,
215
+ CamScannerDocAdapter,
216
+ IXiamenAdapter,
217
+ MeiyouAdapter,
218
+ TaxAdapter,
219
+ CmbcBankAdapter,
220
+ BocBankAdapter,
221
+ BankcommBankAdapter,
222
+ IcbcBankAdapter,
223
+ DcepAdapter,
224
+ Tmri12123Adapter,
225
+ DingTalkPcAdapter,
226
+ FeishuPcAdapter,
227
+ WeWorkPcAdapter,
228
+ BaiduMapAdapter,
229
+ TencentMapAdapter,
230
+ JdAdapter,
231
+ MeituanAdapter,
232
+ ElemeAdapter,
233
+ PinduoduoAdapter,
234
+ DianpingAdapter,
235
+ XianyuAdapter,
236
+ VipshopAdapter,
237
+ Train12306Adapter,
238
+ TaobaoAdapter,
239
+ CtripAdapter,
240
+ TongchengAdapter,
241
+ DidiAdapter,
242
+ AmapAdapter,
243
+ TelegramAdapter,
244
+ WhatsAppAdapter,
245
+ EntityResolver,
246
+ EntityResolverEmbeddingStage,
247
+ EntityResolverLLMStage,
248
+ runAnalysisSkill,
249
+ ANALYSIS_SKILL_NAMES,
250
+ } = hub);
251
+ })();
252
+ }
253
+ return _pdhLoadPromise;
254
+ }
129
255
  import {
130
256
  readFileSync,
131
257
  writeFileSync,
@@ -209,6 +335,7 @@ export function resolveHubDir() {
209
335
  }
210
336
 
211
337
  async function initHub() {
338
+ await ensurePdhLoaded();
212
339
  // Phase 1c: hoisted so the hub-level `bilibiliAdbSync` method can reuse
213
340
  // the same bridge instance the system-data-android adapter wires below.
214
341
  // Single bridge per hub keeps `bilibili.cookies` extension state /
@@ -1490,6 +1617,7 @@ let _hubMinimal = null;
1490
1617
  let _hubMinimalInitPromise = null;
1491
1618
 
1492
1619
  async function initHubMinimal() {
1620
+ await ensurePdhLoaded();
1493
1621
  const hubDir = resolveHubDir();
1494
1622
  mkdirSync(hubDir, { recursive: true });
1495
1623
  mkdirSync(join(hubDir, "keys"), { recursive: true });
@@ -31,6 +31,7 @@ export const PlanState = {
31
31
  APPROVED: "approved",
32
32
  EXECUTING: "executing",
33
33
  COMPLETED: "completed",
34
+ FAILED: "failed",
34
35
  REJECTED: "rejected",
35
36
  };
36
37
 
@@ -499,10 +500,10 @@ export class PlanModeManager extends EventEmitter {
499
500
  });
500
501
 
501
502
  const allDone = results.every((r) => r.success);
502
- this.state = allDone ? PlanState.COMPLETED : PlanState.COMPLETED;
503
+ this.state = allDone ? PlanState.COMPLETED : PlanState.FAILED;
503
504
  this.currentPlan.status = allDone
504
505
  ? PlanState.COMPLETED
505
- : PlanState.COMPLETED;
506
+ : PlanState.FAILED;
506
507
 
507
508
  return { results, success: allDone };
508
509
  }
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * mcp-prompt tests (previously untested) — the pure core that surfaces MCP
5
+ * server prompts as `/mcp__<server>__<prompt>` slash commands. Covers
6
+ * parseMcpPromptCommand (server/name split incl. `__` in names, JSON-object args
7
+ * vs non-JSON tail, reject non-mcp / missing parts), renderPromptMessages
8
+ * (text + embedded resource text, ignore binary, single vs array content),
9
+ * expandMcpPrompt (parse -> getPrompt -> render, null for non-mcp, throw when no
10
+ * client) and renderMcpSurface (overview / empty / no-client). No I/O.
11
+ */
12
+
13
+ import { describe, it, expect, vi } from "vitest";
14
+ import {
15
+ parseMcpPromptCommand,
16
+ renderPromptMessages,
17
+ expandMcpPrompt,
18
+ renderMcpSurface,
19
+ } from "../mcp-prompt.js";
20
+
21
+ describe("parseMcpPromptCommand", () => {
22
+ it("returns null for non-mcp lines", () => {
23
+ expect(parseMcpPromptCommand("hello")).toBe(null);
24
+ expect(parseMcpPromptCommand("/other__a__b")).toBe(null);
25
+ expect(parseMcpPromptCommand("")).toBe(null);
26
+ });
27
+
28
+ it("parses server and prompt name with empty args", () => {
29
+ expect(parseMcpPromptCommand("/mcp__github__review")).toEqual({
30
+ server: "github",
31
+ name: "review",
32
+ args: {},
33
+ });
34
+ });
35
+
36
+ it("keeps '__' inside the prompt name", () => {
37
+ expect(parseMcpPromptCommand("/mcp__srv__deep__name").name).toBe(
38
+ "deep__name",
39
+ );
40
+ });
41
+
42
+ it("parses a trailing JSON object as args", () => {
43
+ expect(parseMcpPromptCommand('/mcp__s__p {"a":1,"b":"x"}').args).toEqual({
44
+ a: 1,
45
+ b: "x",
46
+ });
47
+ });
48
+
49
+ it("treats a non-JSON tail as { input: tail }", () => {
50
+ expect(parseMcpPromptCommand("/mcp__s__p just some text").args).toEqual({
51
+ input: "just some text",
52
+ });
53
+ });
54
+
55
+ it("treats a JSON array tail as { input } (not an object)", () => {
56
+ expect(parseMcpPromptCommand("/mcp__s__p [1,2]").args).toEqual({
57
+ input: "[1,2]",
58
+ });
59
+ });
60
+
61
+ it("returns null when server or prompt is missing", () => {
62
+ expect(parseMcpPromptCommand("/mcp__srv")).toBe(null); // < 3 parts
63
+ expect(parseMcpPromptCommand("/mcp____p")).toBe(null); // empty server
64
+ });
65
+ });
66
+
67
+ describe("renderPromptMessages", () => {
68
+ it("returns '' when there are no messages", () => {
69
+ expect(renderPromptMessages(null)).toBe("");
70
+ expect(renderPromptMessages({ messages: [] })).toBe("");
71
+ });
72
+
73
+ it("joins text blocks with blank lines", () => {
74
+ const r = renderPromptMessages({
75
+ messages: [
76
+ { content: [{ type: "text", text: "one" }] },
77
+ { content: [{ type: "text", text: "two" }] },
78
+ ],
79
+ });
80
+ expect(r).toBe("one\n\ntwo");
81
+ });
82
+
83
+ it("includes embedded resource text and ignores binary/unknown blocks", () => {
84
+ const r = renderPromptMessages({
85
+ messages: [
86
+ {
87
+ content: [
88
+ { type: "text", text: "T" },
89
+ { type: "resource", resource: { text: "R" } },
90
+ { type: "image", data: "..." },
91
+ null,
92
+ ],
93
+ },
94
+ ],
95
+ });
96
+ expect(r).toBe("T\n\nR");
97
+ });
98
+
99
+ it("handles content given as a single block (not an array)", () => {
100
+ const r = renderPromptMessages({
101
+ messages: [{ content: { type: "text", text: "solo" } }],
102
+ });
103
+ expect(r).toBe("solo");
104
+ });
105
+ });
106
+
107
+ describe("expandMcpPrompt", () => {
108
+ it("returns null for a non-mcp line", async () => {
109
+ expect(await expandMcpPrompt("hi", {})).toBe(null);
110
+ });
111
+
112
+ it("throws when no MCP client is connected", async () => {
113
+ await expect(expandMcpPrompt("/mcp__s__p", null)).rejects.toThrow(
114
+ /No MCP servers/,
115
+ );
116
+ });
117
+
118
+ it("calls getPrompt with parsed args and renders the result", async () => {
119
+ const getPrompt = vi.fn(async () => ({
120
+ messages: [{ content: [{ type: "text", text: "rendered" }] }],
121
+ }));
122
+ const out = await expandMcpPrompt('/mcp__s__p {"k":1}', { getPrompt });
123
+ expect(getPrompt).toHaveBeenCalledWith("s", "p", { k: 1 });
124
+ expect(out).toBe("rendered");
125
+ });
126
+ });
127
+
128
+ describe("renderMcpSurface", () => {
129
+ it("reports when no client is connected", () => {
130
+ expect(renderMcpSurface(null)).toMatch(/No MCP servers/);
131
+ });
132
+
133
+ it("lists resources and prompts with counts", () => {
134
+ const surface = renderMcpSurface({
135
+ listResources: () => [{ uri: "file://x", server: "fs", name: "X" }],
136
+ listPrompts: () => [
137
+ { server: "gh", name: "review", description: "code review" },
138
+ ],
139
+ });
140
+ expect(surface).toContain("MCP resources (1):");
141
+ expect(surface).toContain("file://x [fs] — X");
142
+ expect(surface).toContain("MCP prompts (1):");
143
+ expect(surface).toContain("/mcp__gh__review — code review");
144
+ });
145
+
146
+ it("notes when servers expose nothing", () => {
147
+ const surface = renderMcpSurface({
148
+ listResources: () => [],
149
+ listPrompts: () => [],
150
+ });
151
+ expect(surface).toContain(
152
+ "(connected servers expose no resources or prompts)",
153
+ );
154
+ });
155
+ });