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
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import fs from "fs";
7
+ import { numericOption } from "../lib/cli-numeric.js";
7
8
  import path from "path";
8
9
  import chalk from "chalk";
9
10
  import { logger } from "../lib/logger.js";
@@ -187,7 +188,7 @@ export function registerSessionCommand(program) {
187
188
 
188
189
  let messages = sess.messages;
189
190
  if (options.limit) {
190
- messages = messages.slice(-parseInt(options.limit));
191
+ messages = messages.slice(-numericOption(options.limit, { name: "--limit", integer: true, min: 1 }));
191
192
  }
192
193
 
193
194
  for (const msg of messages) {
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import chalk from "chalk";
7
+ import { intArg, floatArg } from "../lib/cli-arg.js";
7
8
  import { logger } from "../lib/logger.js";
8
9
  import { bootstrap, shutdown } from "../runtime/bootstrap.js";
9
10
  import {
@@ -80,7 +81,7 @@ export function registerSlaCommand(program) {
80
81
  .command("create <org-id>")
81
82
  .description("Create a new SLA contract")
82
83
  .option("-t, --tier <tier>", "SLA tier (gold|silver|bronze)", "silver")
83
- .option("-d, --duration <ms>", "Contract duration in ms", parseInt)
84
+ .option("-d, --duration <ms>", "Contract duration in ms", intArg("--duration"))
84
85
  .option("-f, --fee <amount>", "Monthly fee", parseFloat, 0)
85
86
  .option("--json", "Output as JSON")
86
87
  .action(async (orgId, options) => {
@@ -336,8 +337,8 @@ export function registerSlaCommand(program) {
336
337
  sla
337
338
  .command("report <sla-id>")
338
339
  .description("Generate an SLA compliance report")
339
- .option("--start <ms>", "Start of window (ms since epoch)", parseInt)
340
- .option("--end <ms>", "End of window (ms since epoch)", parseInt)
340
+ .option("--start <ms>", "Start of window (ms since epoch)", intArg("--start"))
341
+ .option("--end <ms>", "End of window (ms since epoch)", intArg("--end"))
341
342
  .option("--json", "Output as JSON")
342
343
  .action(async (slaId, options) => {
343
344
  try {
@@ -460,8 +461,8 @@ export function registerSlaCommand(program) {
460
461
  .command("create-v2 <org-id>")
461
462
  .description("Create a V2 SLA contract (enforces per-org active cap)")
462
463
  .option("-t, --tier <tier>", "gold|silver|bronze", "silver")
463
- .option("-d, --duration <ms>", "Contract duration in ms", parseInt)
464
- .option("-f, --fee <amount>", "Monthly fee", parseFloat)
464
+ .option("-d, --duration <ms>", "Contract duration in ms", intArg("--duration"))
465
+ .option("-f, --fee <amount>", "Monthly fee", floatArg("--fee"))
465
466
  .option("--json", "Output as JSON")
466
467
  .action(async (orgId, options) => {
467
468
  try {
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import chalk from "chalk";
7
+ import { numericOption } from "../lib/cli-numeric.js";
7
8
  import { logger } from "../lib/logger.js";
8
9
  import { parseJsonOption } from "../lib/parse-json-option.js";
9
10
  import { bootstrap, shutdown } from "../runtime/bootstrap.js";
@@ -475,7 +476,7 @@ export function registerSocialCommand(program) {
475
476
  ensureSocialTables(db);
476
477
 
477
478
  const messages = getChatMessages(threadId, {
478
- limit: parseInt(options.limit),
479
+ limit: numericOption(options.limit, { name: "--limit", integer: true, min: 1, fallback: 50 }),
479
480
  });
480
481
  if (options.json) {
481
482
  console.log(JSON.stringify(messages, null, 2));
@@ -966,10 +967,10 @@ export function registerSocialCommand(program) {
966
967
  directed: !!options.directed,
967
968
  edgeTypes: _splitEdgeTypes(options.edgeTypes),
968
969
  weights: {
969
- degree: parseFloat(options.wDegree),
970
- closeness: parseFloat(options.wCloseness),
971
- betweenness: parseFloat(options.wBetweenness),
972
- eigenvector: parseFloat(options.wEigenvector),
970
+ degree: numericOption(options.wDegree, { name: "--w-degree", fallback: 0.25 }),
971
+ closeness: numericOption(options.wCloseness, { name: "--w-closeness", fallback: 0.25 }),
972
+ betweenness: numericOption(options.wBetweenness, { name: "--w-betweenness", fallback: 0.25 }),
973
+ eigenvector: numericOption(options.wEigenvector, { name: "--w-eigenvector", fallback: 0.25 }),
973
974
  },
974
975
  });
975
976
  if (options.json) console.log(JSON.stringify(scores, null, 2));
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import chalk from "chalk";
7
+ import { intArg, floatArg } from "../lib/cli-arg.js";
7
8
  import { logger } from "../lib/logger.js";
8
9
  import { bootstrap, shutdown } from "../runtime/bootstrap.js";
9
10
  import {
@@ -58,9 +59,9 @@ export function registerStressCommand(program) {
58
59
  "Load level (light|medium|heavy|extreme)",
59
60
  "medium",
60
61
  )
61
- .option("-c, --concurrency <n>", "Override concurrency", parseInt)
62
- .option("-r, --rps <n>", "Override requests per second", parseInt)
63
- .option("-d, --duration <ms>", "Override duration in ms", parseInt)
62
+ .option("-c, --concurrency <n>", "Override concurrency", intArg("--concurrency"))
63
+ .option("-r, --rps <n>", "Override requests per second", intArg("--rps"))
64
+ .option("-d, --duration <ms>", "Override duration in ms", intArg("--duration"))
64
65
  .option("--json", "Output as JSON")
65
66
  .action(async (options) => {
66
67
  try {
@@ -354,9 +355,9 @@ export function registerStressCommand(program) {
354
355
  "Load level (light|medium|heavy|extreme)",
355
356
  "medium",
356
357
  )
357
- .option("-c, --concurrency <n>", "Override concurrency", parseInt)
358
- .option("-r, --rps <n>", "Override requests per second", parseInt)
359
- .option("-d, --duration <ms>", "Override duration in ms", parseInt)
358
+ .option("-c, --concurrency <n>", "Override concurrency", intArg("--concurrency"))
359
+ .option("-r, --rps <n>", "Override requests per second", intArg("--rps"))
360
+ .option("-d, --duration <ms>", "Override duration in ms", intArg("--duration"))
360
361
  .option("--json", "Output as JSON")
361
362
  .action(async (options) => {
362
363
  try {
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import chalk from "chalk";
7
+ import { numericOption } from "../lib/cli-numeric.js";
7
8
  import { logger } from "../lib/logger.js";
8
9
  import { parseJsonOption } from "../lib/parse-json-option.js";
9
10
  import { bootstrap, shutdown } from "../runtime/bootstrap.js";
@@ -220,7 +221,7 @@ export function registerSyncCommand(program) {
220
221
  process.exit(1);
221
222
  }
222
223
  const db = ctx.db.getDatabase();
223
- const entries = getSyncLog(db, { limit: parseInt(options.limit) });
224
+ const entries = getSyncLog(db, { limit: numericOption(options.limit, { name: "--limit", integer: true, min: 1, fallback: 20 }) });
224
225
 
225
226
  if (options.json) {
226
227
  console.log(JSON.stringify(entries, null, 2));
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import fs from "fs";
9
+ import { intArg, floatArg } from "../lib/cli-arg.js";
9
10
  import chalk from "chalk";
10
11
  import { logger } from "../lib/logger.js";
11
12
  import { parseJsonOption } from "../lib/parse-json-option.js";
@@ -380,8 +381,8 @@ export function registerTenantCommand(program) {
380
381
  .command("subscribe <tenant-id>")
381
382
  .description("Start a new subscription (cancels any prior active one)")
382
383
  .requiredOption("-p, --plan <plan>", "Plan id")
383
- .option("-a, --amount <n>", "Override amount", parseFloat)
384
- .option("-d, --duration-ms <ms>", "Duration in ms (default 30d)", parseInt)
384
+ .option("-a, --amount <n>", "Override amount", floatArg("--amount"))
385
+ .option("-d, --duration-ms <ms>", "Duration in ms (default 30d)", intArg("--duration-ms"))
385
386
  .option("--json", "Output as JSON")
386
387
  .action(async (tenantId, options) => {
387
388
  try {
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { Command } from "commander";
6
+ import { intArg, floatArg } from "../lib/cli-arg.js";
6
7
 
7
8
  import {
8
9
  TRUST_ANCHOR,
@@ -163,7 +164,7 @@ export function registerTrustCommand(program) {
163
164
  .description("List attestations")
164
165
  .option("-a, --anchor <type>", "Filter by anchor")
165
166
  .option("-s, --status <status>", "Filter by status")
166
- .option("--limit <n>", "Max results", parseInt)
167
+ .option("--limit <n>", "Max results", intArg("--limit"))
167
168
  .option("--json", "JSON output")
168
169
  .action((opts) => {
169
170
  const db = _dbFromCtx(tr);
@@ -186,7 +187,7 @@ export function registerTrustCommand(program) {
186
187
  tr.command("interop-test <algorithm>")
187
188
  .description("Run PQC interoperability test")
188
189
  .option("-p, --peer <id>", "Peer identifier")
189
- .option("-l, --latency <ms>", "Latency in ms", parseInt)
190
+ .option("-l, --latency <ms>", "Latency in ms", intArg("--latency"))
190
191
  .option("--json", "JSON output")
191
192
  .action((algorithm, opts) => {
192
193
  const db = _dbFromCtx(tr);
@@ -207,7 +208,7 @@ export function registerTrustCommand(program) {
207
208
  tr.command("interop-tests")
208
209
  .description("List PQC interop tests")
209
210
  .option("-a, --algorithm <algo>", "Filter by algorithm")
210
- .option("--limit <n>", "Max results", parseInt)
211
+ .option("--limit <n>", "Max results", intArg("--limit"))
211
212
  .option("--json", "JSON output")
212
213
  .action((opts) => {
213
214
  const db = _dbFromCtx(tr);
@@ -229,7 +230,7 @@ export function registerTrustCommand(program) {
229
230
  tr.command("sat-send <payload>")
230
231
  .description("Send satellite message")
231
232
  .option("-p, --provider <name>", "Provider (iridium/starlink/beidou)")
232
- .option("-r, --priority <n>", "Priority (1-10)", parseInt)
233
+ .option("-r, --priority <n>", "Priority (1-10)", intArg("--priority"))
233
234
  .option("--json", "JSON output")
234
235
  .action((payload, opts) => {
235
236
  const db = _dbFromCtx(tr);
@@ -277,7 +278,7 @@ export function registerTrustCommand(program) {
277
278
  .description("List satellite messages")
278
279
  .option("-p, --provider <name>", "Filter by provider")
279
280
  .option("-s, --status <status>", "Filter by status")
280
- .option("--limit <n>", "Max results", parseInt)
281
+ .option("--limit <n>", "Max results", intArg("--limit"))
281
282
  .option("--json", "JSON output")
282
283
  .action((opts) => {
283
284
  const db = _dbFromCtx(tr);
@@ -346,7 +347,7 @@ export function registerTrustCommand(program) {
346
347
  tr.command("hsm-devices")
347
348
  .description("List HSM devices")
348
349
  .option("-v, --vendor <name>", "Filter by vendor")
349
- .option("--limit <n>", "Max results", parseInt)
350
+ .option("--limit <n>", "Max results", intArg("--limit"))
350
351
  .option("--json", "JSON output")
351
352
  .action((opts) => {
352
353
  const db = _dbFromCtx(tr);
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import chalk from "chalk";
7
+ import { numericOption } from "../lib/cli-numeric.js";
7
8
  import { logger } from "../lib/logger.js";
8
9
  import { bootstrap, shutdown } from "../runtime/bootstrap.js";
9
10
  import {
@@ -386,7 +387,7 @@ export function registerWalletCommand(program) {
386
387
  const db = ctx.db.getDatabase();
387
388
  const txns = getTransactions(db, {
388
389
  address: options.address,
389
- limit: parseInt(options.limit),
390
+ limit: numericOption(options.limit, { name: "--limit", integer: true, min: 1, fallback: 20 }),
390
391
  });
391
392
 
392
393
  if (options.json) {
@@ -30,9 +30,22 @@ import {
30
30
  writeFileSync,
31
31
  } from "node:fs";
32
32
  import { join } from "node:path";
33
- import pdhPkg from "@chainlesschain/personal-data-hub";
34
-
35
- const { ingestSystemDataAndroidSnapshot } = pdhPkg;
33
+ // Lazily import the pdh package the first time the system-data-android
34
+ // ingest topic is actually used. This module is pulled in by the
35
+ // agent/chat/serve/ui runtime chain at startup (ws-server →
36
+ // message-dispatcher → here), so a top-level static import would force the
37
+ // whole pdh graph to load on every `cc` invocation — and a partial/corrupt
38
+ // pdh install would brick unrelated commands. See the matching rationale in
39
+ // personal-data-hub-wiring.js.
40
+ let _ingestSnapshotFn = null;
41
+ async function ingestSystemDataAndroidSnapshot(hub, snapshot) {
42
+ if (!_ingestSnapshotFn) {
43
+ const pdhMod = await import("@chainlesschain/personal-data-hub");
44
+ const pdhPkg = pdhMod.default || pdhMod;
45
+ _ingestSnapshotFn = pdhPkg.ingestSystemDataAndroidSnapshot;
46
+ }
47
+ return _ingestSnapshotFn(hub, snapshot);
48
+ }
36
49
 
37
50
  /**
38
51
  * If the caller didn't pass `inputPath`, try to pull a snapshot for
@@ -0,0 +1,225 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * agents tests — user-defined subagent discovery (.claude/agents/*.md), the
5
+ * Claude-Code parity layer, previously untested. Covers normalizeTools,
6
+ * parseAgentFile (frontmatter + body=systemPrompt, defaults, bad YAML),
7
+ * discoverAgents (path-derived names with `:`, frontmatter name override,
8
+ * project-shadows-personal precedence, sorting) and getAgent (colon/slash/
9
+ * leading-slash lookup). In-memory fake fs over POSIX paths via the deps seam;
10
+ * no `.git` present so projectRootBase resolves to null (cwd+home dirs only).
11
+ */
12
+
13
+ import { describe, it, expect } from "vitest";
14
+ import pathDefault from "path";
15
+ import {
16
+ normalizeTools,
17
+ parseAgentFile,
18
+ discoverAgents,
19
+ getAgent,
20
+ } from "../agents.js";
21
+
22
+ const P = pathDefault.posix;
23
+
24
+ function makeFs(files) {
25
+ const contents = new Map(Object.entries(files));
26
+ const fileSet = new Set(contents.keys());
27
+ const dirSet = new Set(["/"]);
28
+ for (const f of fileSet) {
29
+ let d = P.dirname(f);
30
+ while (d && !dirSet.has(d)) {
31
+ dirSet.add(d);
32
+ d = P.dirname(d);
33
+ }
34
+ }
35
+ const childrenOf = (dir) => {
36
+ const out = new Map();
37
+ for (const f of fileSet)
38
+ if (P.dirname(f) === dir) out.set(P.basename(f), false);
39
+ for (const d of dirSet)
40
+ if (d !== "/" && P.dirname(d) === dir) out.set(P.basename(d), true);
41
+ return out;
42
+ };
43
+ return {
44
+ readFileSync(p) {
45
+ if (!fileSet.has(p)) {
46
+ const e = new Error("ENOENT");
47
+ e.code = "ENOENT";
48
+ throw e;
49
+ }
50
+ return contents.get(p);
51
+ },
52
+ existsSync(p) {
53
+ return fileSet.has(p) || dirSet.has(p);
54
+ },
55
+ statSync(p) {
56
+ if (!fileSet.has(p) && !dirSet.has(p)) {
57
+ const e = new Error("ENOENT");
58
+ e.code = "ENOENT";
59
+ throw e;
60
+ }
61
+ const isDir = dirSet.has(p);
62
+ return { isDirectory: () => isDir, isFile: () => !isDir };
63
+ },
64
+ readdirSync(p, opts) {
65
+ if (!dirSet.has(p)) {
66
+ const e = new Error("ENOENT");
67
+ e.code = "ENOENT";
68
+ throw e;
69
+ }
70
+ const kids = childrenOf(p);
71
+ if (opts && opts.withFileTypes) {
72
+ return [...kids].map(([name, isDir]) => ({
73
+ name,
74
+ isDirectory: () => isDir,
75
+ isFile: () => !isDir,
76
+ }));
77
+ }
78
+ return [...kids.keys()];
79
+ },
80
+ };
81
+ }
82
+
83
+ const discover = (files, home = "/home/u") =>
84
+ discoverAgents("/proj", { deps: { fs: makeFs(files), path: P }, home });
85
+
86
+ // --------------------------------------------------------------------------- //
87
+ // normalizeTools
88
+ // --------------------------------------------------------------------------- //
89
+ describe("normalizeTools", () => {
90
+ it("returns null for null/undefined", () => {
91
+ expect(normalizeTools(null)).toBe(null);
92
+ expect(normalizeTools(undefined)).toBe(null);
93
+ });
94
+
95
+ it("splits a comma/space string into a trimmed list", () => {
96
+ expect(normalizeTools("read, write, run")).toEqual([
97
+ "read",
98
+ "write",
99
+ "run",
100
+ ]);
101
+ expect(normalizeTools("a b c")).toEqual(["a", "b", "c"]);
102
+ });
103
+
104
+ it("normalizes an array, dropping empties", () => {
105
+ expect(normalizeTools(["read", "", " write "])).toEqual(["read", "write"]);
106
+ });
107
+
108
+ it("returns null when nothing remains", () => {
109
+ expect(normalizeTools("")).toBe(null);
110
+ expect(normalizeTools([" ", ""])).toBe(null);
111
+ });
112
+ });
113
+
114
+ // --------------------------------------------------------------------------- //
115
+ // parseAgentFile
116
+ // --------------------------------------------------------------------------- //
117
+ describe("parseAgentFile", () => {
118
+ const parse = (content, opts) =>
119
+ parseAgentFile("/a.md", "project", {
120
+ deps: { fs: makeFs({ "/a.md": content }) },
121
+ ...opts,
122
+ });
123
+
124
+ it("returns null when the file cannot be read", () => {
125
+ expect(
126
+ parseAgentFile("/missing.md", "project", { deps: { fs: makeFs({}) } }),
127
+ ).toBe(null);
128
+ });
129
+
130
+ it("uses the whole body as systemPrompt when there is no frontmatter", () => {
131
+ const a = parse("You are a helpful agent.");
132
+ expect(a.systemPrompt).toBe("You are a helpful agent.");
133
+ expect(a.name).toBe(null);
134
+ expect(a.description).toBe("");
135
+ expect(a.tools).toBe(null);
136
+ expect(a.model).toBe(null);
137
+ });
138
+
139
+ it("parses frontmatter metadata and trims the body", () => {
140
+ const a = parse(
141
+ "---\nname: sec\ndescription: security review\ntools: read, grep\nmodel: opus\n---\n\nReview for vulns.\n",
142
+ );
143
+ expect(a.name).toBe("sec");
144
+ expect(a.description).toBe("security review");
145
+ expect(a.tools).toEqual(["read", "grep"]);
146
+ expect(a.model).toBe("opus");
147
+ expect(a.systemPrompt).toBe("Review for vulns.");
148
+ });
149
+
150
+ it("accepts a YAML array for tools", () => {
151
+ const a = parse("---\ntools:\n - read\n - write\n---\nbody");
152
+ expect(a.tools).toEqual(["read", "write"]);
153
+ });
154
+
155
+ it("falls back to defaults on invalid YAML frontmatter", () => {
156
+ const a = parse("---\n: : bad : yaml :\n---\nthe body");
157
+ expect(a.name).toBe(null);
158
+ expect(a.systemPrompt).toBe("the body");
159
+ });
160
+ });
161
+
162
+ // --------------------------------------------------------------------------- //
163
+ // discoverAgents
164
+ // --------------------------------------------------------------------------- //
165
+ describe("discoverAgents", () => {
166
+ it("derives names from the path with ':' for nesting", () => {
167
+ const agents = discover({
168
+ "/proj/.claude/agents/reviewer.md": "Review.",
169
+ "/proj/.claude/agents/sec/audit.md": "Audit.",
170
+ });
171
+ const names = agents.map((a) => a.name);
172
+ expect(names).toEqual(["reviewer", "sec:audit"]); // sorted
173
+ });
174
+
175
+ it("lets frontmatter name override the path-derived name", () => {
176
+ const agents = discover({
177
+ "/proj/.claude/agents/x.md": "---\nname: custom\n---\nbody",
178
+ });
179
+ expect(agents[0].name).toBe("custom");
180
+ });
181
+
182
+ it("project scope shadows personal on a name clash", () => {
183
+ const agents = discover({
184
+ "/proj/.claude/agents/reviewer.md": "PROJECT version.",
185
+ "/home/u/.claude/agents/reviewer.md": "PERSONAL version.",
186
+ "/home/u/.claude/agents/helper.md": "Helper.",
187
+ });
188
+ const reviewer = agents.find((a) => a.name === "reviewer");
189
+ expect(reviewer.scope).toBe("project");
190
+ expect(reviewer.systemPrompt).toBe("PROJECT version.");
191
+ // personal-only agent still discovered
192
+ expect(agents.find((a) => a.name === "helper").scope).toBe("personal");
193
+ });
194
+
195
+ it("returns [] when no agent dirs exist", () => {
196
+ expect(discover({ "/proj/readme.md": "x" })).toEqual([]);
197
+ });
198
+ });
199
+
200
+ // --------------------------------------------------------------------------- //
201
+ // getAgent
202
+ // --------------------------------------------------------------------------- //
203
+ describe("getAgent", () => {
204
+ const files = {
205
+ "/proj/.claude/agents/sec/audit.md": "---\ndescription: d\n---\nAudit.",
206
+ };
207
+ const get = (name) =>
208
+ getAgent(name, "/proj", {
209
+ deps: { fs: makeFs(files), path: P },
210
+ home: "/home/u",
211
+ });
212
+
213
+ it("looks up by colon name", () => {
214
+ expect(get("sec:audit")?.description).toBe("d");
215
+ });
216
+
217
+ it("accepts slash and leading-slash forms", () => {
218
+ expect(get("sec/audit")?.name).toBe("sec:audit");
219
+ expect(get("/sec/audit")?.name).toBe("sec:audit");
220
+ });
221
+
222
+ it("returns null for an unknown agent", () => {
223
+ expect(get("nope")).toBe(null);
224
+ });
225
+ });
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * cowork-observe-html buildHtml tests (previously untested).
5
+ *
6
+ * buildHtml renders a static dashboard from an aggregate snapshot and embeds
7
+ * user-controlled data (template names, failure summaries, schedule fields) into
8
+ * HTML and into an inline <script> JSON blob. The escaping is the security
9
+ * boundary — these tests pin XSS safety (HTML entity escaping + JSON
10
+ * script-breakout escaping) plus the structural rendering / empty states.
11
+ */
12
+
13
+ import { describe, it, expect } from "vitest";
14
+ import { buildHtml } from "../cowork-observe-html.js";
15
+
16
+ describe("buildHtml — document shape", () => {
17
+ it("emits a full HTML document with UTF-8 charset", () => {
18
+ const html = buildHtml({});
19
+ expect(html.startsWith("<!DOCTYPE html>")).toBe(true);
20
+ expect(html).toContain('<meta charset="UTF-8">');
21
+ expect(html).toContain("Cowork Observe");
22
+ });
23
+
24
+ it("does not throw on null/undefined data", () => {
25
+ expect(() => buildHtml(null)).not.toThrow();
26
+ expect(() => buildHtml(undefined)).not.toThrow();
27
+ });
28
+
29
+ it("renders empty-state messages when there is no data", () => {
30
+ const html = buildHtml({});
31
+ expect(html).toContain("No template runs in window.");
32
+ expect(html).toContain("No failures in window");
33
+ expect(html).toContain("No upcoming triggers.");
34
+ });
35
+
36
+ it("renders summary stat cards", () => {
37
+ const html = buildHtml({
38
+ tasks: { total: 42, successRate: 0.876, failed: 5, avgTokens: 1200 },
39
+ schedules: { active: 3 },
40
+ });
41
+ expect(html).toContain(">42<"); // total
42
+ expect(html).toContain(">88%<"); // successRate rounded
43
+ expect(html).toContain(">3<"); // active schedules
44
+ });
45
+ });
46
+
47
+ describe("buildHtml — table rendering", () => {
48
+ it("renders template rows with success-rate percentage", () => {
49
+ const html = buildHtml({
50
+ templates: [
51
+ { templateName: "build", runs: 9, successRate: 0.5, avgTokens: 100 },
52
+ ],
53
+ });
54
+ expect(html).toContain("<td>build</td>");
55
+ expect(html).toContain("<td>50%</td>");
56
+ });
57
+
58
+ it("caps template rows at 10", () => {
59
+ const templates = Array.from({ length: 15 }, (_, i) => ({
60
+ templateName: `t${i}`,
61
+ runs: i,
62
+ successRate: 1,
63
+ avgTokens: 0,
64
+ }));
65
+ const html = buildHtml({ templates });
66
+ expect(html).toContain("<td>t9</td>");
67
+ expect(html).not.toContain("<td>t10</td>"); // 11th dropped
68
+ });
69
+
70
+ it("truncates failure summary to 80 chars", () => {
71
+ const long = "x".repeat(200);
72
+ const html = buildHtml({
73
+ failures: [
74
+ {
75
+ templateId: "f",
76
+ failureCount: 2,
77
+ commonSummaries: [{ summary: long }],
78
+ },
79
+ ],
80
+ });
81
+ // the visible table cell is truncated to 80 (the raw 200-char string still
82
+ // lives untruncated inside the embedded JSON blob, so scope to the <td>).
83
+ expect(html).toContain("<td>" + "x".repeat(80) + "</td>");
84
+ expect(html).not.toContain("<td>" + "x".repeat(81));
85
+ });
86
+ });
87
+
88
+ describe("buildHtml — XSS safety", () => {
89
+ it("HTML-escapes user-controlled template names", () => {
90
+ const html = buildHtml({
91
+ templates: [
92
+ {
93
+ templateName: "<script>alert(1)</script>",
94
+ runs: 1,
95
+ successRate: 1,
96
+ avgTokens: 0,
97
+ },
98
+ ],
99
+ });
100
+ expect(html).toContain("&lt;script&gt;alert(1)&lt;/script&gt;");
101
+ // the raw payload must not appear inside a table cell
102
+ expect(html).not.toContain("<td><script>alert(1)</script></td>");
103
+ });
104
+
105
+ it("HTML-escapes failure summaries and schedule fields", () => {
106
+ const html = buildHtml({
107
+ failures: [
108
+ {
109
+ templateName: "<b>x</b>",
110
+ failureCount: 1,
111
+ commonSummaries: [{ summary: "a&b\"c'" }],
112
+ },
113
+ ],
114
+ schedules: {
115
+ nextTriggers: [
116
+ { at: "<i>t</i>", cron: "* * * * *", scheduleId: "s<1>" },
117
+ ],
118
+ },
119
+ });
120
+ expect(html).toContain("&lt;b&gt;x&lt;/b&gt;");
121
+ expect(html).toContain("a&amp;b&quot;c&#39;");
122
+ expect(html).toContain("&lt;i&gt;t&lt;/i&gt;");
123
+ expect(html).toContain("s&lt;1&gt;");
124
+ });
125
+
126
+ it("escapes a </script> breakout in the embedded JSON blob", () => {
127
+ const html = buildHtml({
128
+ tasks: { note: "</script><img src=x onerror=alert(1)>" },
129
+ });
130
+ // the inline JSON must not contain a raw closing script tag
131
+ expect(html).toContain("\\u003c/script");
132
+ expect(html).not.toContain("</script><img");
133
+ });
134
+
135
+ it("escapes <, > and & in the embedded JSON blob", () => {
136
+ const html = buildHtml({ x: "<a> & <b>" });
137
+ expect(html).toContain("\\u003ca\\u003e \\u0026 \\u003cb\\u003e");
138
+ // the script assignment line itself carries the escaped form
139
+ expect(html).toContain("window.__COWORK_OBSERVE__ =");
140
+ });
141
+ });