chainlesschain 0.162.101 → 0.162.102

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 (210) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-NeD6mA1O.js → AIOps-C1k00Gsz.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-Bhp7A7vO.js → ActionButton-BinI6ZDq.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DgdI84Jy.js → Analytics-Cg0jo_cd.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-Qw29caa7.js → AppLayout-CClMs_AA.js} +5 -5
  6. package/src/assets/web-panel/assets/{Audit-DgkzhBjz.js → Audit-BtQgvFdM.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-CiWEQoW5.js → Backup-43FRi7ZI.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-BWNbkeDF.js → BaseInput-Cfza-FZU.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-CUcj4Vyd.js → Chat-7AjHSiSC.js} +6 -6
  10. package/src/assets/web-panel/assets/{ChatBubbleRenderer-bAMNCgEj.js → ChatBubbleRenderer-Bh1YNdtx.js} +1 -1
  11. package/src/assets/web-panel/assets/{Checkbox-B0uZTW8f.js → Checkbox-DgjfqlWW.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-BPk7b-Xa.js → Codegen-CkwOBThj.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-C2cV_dw4.js → Col-Cohk5WYM.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-CDN6Yggb.js → Community-D3LMGh3F.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-C4WPtW_V.js → Compact-DqoMJmh0.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-BGUfeH-3.js → Compliance-BVjWZlEj.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-CsnVgF-o.js → Cowork-C0lqQPqu.js} +2 -2
  18. package/src/assets/web-panel/assets/{Cron--rGanASk.js → Cron-DHd_PJZS.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-D_CLrd-0.js → Crosschain-BphK2Q9u.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-iYkbVc8W.js → DID-CWIw0aG4.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-CuFMU98t.js → Dashboard-CKgDU9U6.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-BPSjv_Ye.js → Dropdown-DkIlDUXF.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-DBmk7czO.js → EmailListRenderer-5sXA9hwZ.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BaCArN11.js → FamilyGuardDashboard-CgcdJLH7.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-DsTV-8Ca.js → Federation-DPOcJISA.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-C6fRr_6F.js → FormItemContext-DxOutBMJ.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-AfuZCfyv.js → GenericCardRenderer-BYVNtkcd.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-CAFIrGhb.js → Git-B37LKxOu.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-C-NaxDs4.js → Governance-CnKcx5un.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-BPfeNZP7.js → Inference-CbRAWN3_.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-DDUBte_m.js → KnowledgeGraph-D0w6RfE_.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-DWysA6JP.js → Logs-DxBDKpzq.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-CAK68hXc.js → Marketplace-DNLi6O_3.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-CNmmoDAk.js → McpTools-s0sBCPXw.js} +4 -4
  35. package/src/assets/web-panel/assets/{Memory-D_KmFoui.js → Memory-BdSEgpmP.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-CaY-6kjj.js → MobileBridge-Dz7nz4K9.js} +2 -2
  37. package/src/assets/web-panel/assets/MobileProjects-DakdA2PC.js +1 -0
  38. package/src/assets/web-panel/assets/{Mtc-YnLrtuXT.js → Mtc-Be9OBypk.js} +6 -6
  39. package/src/assets/web-panel/assets/{MtcAudit-D38v836u.js → MtcAudit-KI2ih4Tl.js} +4 -4
  40. package/src/assets/web-panel/assets/{Multisig-Dv3ad9S4.js → Multisig-Cg3jHFjL.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-MYTh2eWC.js → NLProgramming-BepHx8fU.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-DYO-0Hva.js → Notes-2f05HyqN.js} +3 -3
  43. package/src/assets/web-panel/assets/{NotificationSettings-D39gOb3h.js → NotificationSettings-Blwio_G5.js} +1 -1
  44. package/src/assets/web-panel/assets/{OrderTableRenderer-CXdO0z3I.js → OrderTableRenderer-D7HJfvo_.js} +1 -1
  45. package/src/assets/web-panel/assets/{Organization-DOYTwQwt.js → Organization-Uihlz1cI.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow-Cm-go0yU.js → Overflow-gZx8kADL.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-CakGtorw.js → P2P-DqEEGqba.js} +2 -2
  48. package/src/assets/web-panel/assets/PdhVaultBrowser-BHUi1nb5.js +7 -0
  49. package/src/assets/web-panel/assets/{Permissions-C_podo04.js → Permissions-ERF2ZQ3G.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-nBbehIKp.js → PersonalDataHub-CzDy9CFB.js} +3 -3
  51. package/src/assets/web-panel/assets/{Pipeline-CGzQUoKv.js → Pipeline-Dy4x9Ejw.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-BuD-Dd9m.js → Privacy-C6UJys52.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-CLcFmGXU.js → ProjectInit-Bmi027fa.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-BxXTR0dH.js → ProjectSettings-NinSZmGA.js} +2 -2
  55. package/src/assets/web-panel/assets/{Projects-DkpBjAfd.js → Projects-DeW-p-hT.js} +1 -1
  56. package/src/assets/web-panel/assets/{Providers-DwHK42ht.js → Providers-4hxrBTLo.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-idJPKErk.js → QuickAsk-B5inZlW8.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-BvJj0Nu2.js → Recommend-7lpBm00-.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-BK9l4Rcf.js → Reputation-QfdGZxYO.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-rLxg7xk5.js → Row-DqHC7BCT.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-Cw6ZcHBY.js → RssFeed-B3F1aGP6.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-D8mKp506.js → Search-BXtjuoYO.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-CXunojmQ.js → Security-Iy0MOV-A.js} +4 -4
  64. package/src/assets/web-panel/assets/{Services-BE5DgmPl.js → Services-C_puMp_6.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-CssL5wmE.js → Skeleton-Bg6mTVZU.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-zyS_1YrT.js → Skills-D4wXhanR.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla-DIVn2Roa.js → Sla-Cgr2B9R6.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-CjNdZWdj.js → SpeechSettings-ctYqZEY5.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-BCbu5WQT.js → SyncSettings-C3qWh9y4.js} +2 -2
  70. package/src/assets/web-panel/assets/{Tasks-BjFrgXge.js → Tasks-D3AAgUx1.js} +1 -1
  71. package/src/assets/web-panel/assets/{Templates-Cl4Y3lKN.js → Templates-Bm_Dyz8T.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-k1T0F_eH.js → Tenant-DOSrOw9X.js} +1 -1
  73. package/src/assets/web-panel/assets/Terminal-DrG0bEOS.js +3 -0
  74. package/src/assets/web-panel/assets/TimelineRenderer-BB8Qq6Cx.js +1 -0
  75. package/src/assets/web-panel/assets/{Tokens-BUWEiyab.js → Tokens-DqyF_Flh.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-9srMXwpo.js → Trigger-ClwQC2hP.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-hmGor_R2.js → Trust-DGQSLPQ0.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-Dst7K10a.js → UkeySign-BAkeYsUO.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-D_utUv1u.js → VideoEditing-WyUxS65Y.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-BpYTTseI.js → Wallet-Br7GdjEg.js} +4 -4
  81. package/src/assets/web-panel/assets/{WebAuthn-CEIw8QrF.js → WebAuthn-bKs1dahj.js} +4 -4
  82. package/src/assets/web-panel/assets/{WorkflowEditor-Bkb4bUZ_.js → WorkflowEditor-CVqAALYe.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-CTqDgGRH.js → chat-BDGDRbab.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-CnJHVFLk.js → colors-DIUxZwT8.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-D4-v0ahl.js → compact-item-C2iM4GmN.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-CYDbnHZX.js → createContext-D27NeHwO.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-Daz0PHhS.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-CbYwlEz5.js → hasIn-D1lXdsFj.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-BR4facv0.js → index-B5ZB5lsV.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-DMKNVIqJ.js → index-B6kc4-nD.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-Cl7-mbsb.js → index-B8POWlbO.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-CVc_5aBF.js → index-BE9bNvdn.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-BCQ4kcL3.js → index-BIVYPlpl.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-B8uhiRML.js → index-BTUFhfHs.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-qNwkSspX.js → index-BTedVe_v.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-OqmUv5QO.js → index-Bi8u5GsP.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CP-UKwu8.js → index-Bil9t91t.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CfshMjFN.js → index-BqbjsJcS.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-tmNsJzQU.js → index-CAnMUhHO.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BOUSaCsn.js → index-CDzSUTpa.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Dl_NdY39.js → index-CEO9vhv7.js} +3 -3
  102. package/src/assets/web-panel/assets/{index-DxvFrsxl.js → index-CFEwtE3q.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-C2fICr5d.js → index-CFY1KjB1.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-5NXdAIZK.js → index-CKMDgjfh.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BR0FfrCL.js → index-CNk98bb1.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DaCCLe7i.js → index-Ca8n97h9.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BGUOVPjO.js → index-Cb9u9MMo.js} +1 -1
  108. package/src/assets/web-panel/assets/index-Cbe_eJAR.js +1 -0
  109. package/src/assets/web-panel/assets/{index-CxKdSB-p.js → index-Cbqr8kW6.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CY971z_9.js → index-CfR3Pvsu.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DVKfa-z4.js → index-Cgf3zMwq.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-4767GEls.js → index-CiA-L5ld.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CK8nROWH.js → index-CoygRreE.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-B-hiXmVK.js → index-CsIw-YQF.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-wxz2s6uW.js → index-DFhdJm7M.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-8cmtr1jC.js → index-DK0jgmJg.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BygyTsjr.js → index-Dc4k4V1u.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-D8MYVsVB.js → index-DfSav-yQ.js} +1 -1
  119. package/src/assets/web-panel/assets/index-DgcPjW1c.js +1 -0
  120. package/src/assets/web-panel/assets/{index-xDe5BkjU.js → index-DkOu8er7.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-C4chfOjw.js → index-DnCVI295.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BNz3yht2.js → index-Do13fQ5W.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DdRtpQFq.js → index-DrS-Mrm-.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-B78d_-pR.js → index-ET5LYk9B.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-YuO8gxKb.js → index-HZGu1woY.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DjFQuno4.js → index-Y1rkRas5.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-dKpwhR5x.js → index-yIM-KxVm.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-qCQgpAty.js → initDefaultProps-C0Yrc6yY.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-SkVSGdwu.js → motion-DnOI85ek.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-BpWxaW_x.js → move-DT9zQPZp.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-DlZWM2H4.js → omit-DFtDi5Wd.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-DobtUnVe.js → pickAttrs-Cobkb_rd.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-CD6mYRZi.js → placementArrow-DWQDu4i-.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-DvIIMTD_.js → responsiveObserve-daoQcWcX.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-CVgyUfaA.js → slide-BLkzd2OV.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-DrUDnjy_.js → statusUtils-Uyxi81M0.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-BNYQS60m.js → styleChecker-Dtw5lUcs.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-DYv7Tvk0.js → useFlexGapSupport-_JpMgpdt.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-DmAD2atJ.js → useFs-huwAlPD-.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-DbLVKA3I.js → usePersonalDataHub-7cafj8_f.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-C3WEt6_P.js → vnode-DYUSfRt7.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-CRoSX_oZ.js → zoom-B-tjvmer.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/a2a.js +2 -1
  145. package/src/commands/bi.js +4 -3
  146. package/src/commands/browse.js +3 -2
  147. package/src/commands/codegen.js +7 -6
  148. package/src/commands/collab.js +2 -1
  149. package/src/commands/crosschain.js +5 -4
  150. package/src/commands/dao.js +10 -10
  151. package/src/commands/dbevo.js +3 -2
  152. package/src/commands/economy.js +8 -6
  153. package/src/commands/evolution.js +10 -8
  154. package/src/commands/export.js +2 -1
  155. package/src/commands/federation.js +8 -7
  156. package/src/commands/fusion.js +4 -3
  157. package/src/commands/git.js +2 -1
  158. package/src/commands/governance.js +6 -5
  159. package/src/commands/hardening.js +3 -2
  160. package/src/commands/hmemory.js +2 -1
  161. package/src/commands/inference.js +9 -8
  162. package/src/commands/infra.js +9 -8
  163. package/src/commands/instinct.js +66 -0
  164. package/src/commands/learning.js +66 -0
  165. package/src/commands/marketplace.js +5 -4
  166. package/src/commands/matrix.js +2 -1
  167. package/src/commands/memory.js +66 -0
  168. package/src/commands/nlprog.js +4 -3
  169. package/src/commands/nostr.js +4 -3
  170. package/src/commands/note.js +2 -1
  171. package/src/commands/ops.js +3 -2
  172. package/src/commands/perception.js +6 -5
  173. package/src/commands/privacy.js +13 -12
  174. package/src/commands/quantization.js +3 -2
  175. package/src/commands/recommend.js +8 -7
  176. package/src/commands/reputation.js +4 -3
  177. package/src/commands/runtime.js +6 -5
  178. package/src/commands/sandbox.js +5 -4
  179. package/src/commands/session.js +2 -1
  180. package/src/commands/sla.js +6 -5
  181. package/src/commands/social.js +6 -5
  182. package/src/commands/stress.js +7 -6
  183. package/src/commands/sync.js +2 -1
  184. package/src/commands/tenant.js +3 -2
  185. package/src/commands/trust.js +7 -6
  186. package/src/commands/wallet.js +2 -1
  187. package/src/lib/__tests__/agents.test.js +225 -0
  188. package/src/lib/__tests__/cowork-observe-html.test.js +141 -0
  189. package/src/lib/__tests__/logger.test.js +110 -0
  190. package/src/lib/__tests__/project-inventory.test.js +280 -0
  191. package/src/lib/__tests__/project-root.test.js +96 -0
  192. package/src/lib/__tests__/provider-stream.test.js +190 -0
  193. package/src/lib/cli-arg.js +41 -0
  194. package/src/lib/cli-numeric.js +12 -3
  195. package/src/lib/instinct-manager.js +64 -0
  196. package/src/lib/learning/learning-tables.js +83 -0
  197. package/src/lib/mcp-oauth.js +10 -2
  198. package/src/lib/memory-manager.js +67 -0
  199. package/src/lib/plan-mode.js +3 -2
  200. package/src/repl/__tests__/mcp-prompt.test.js +155 -0
  201. package/src/repl/__tests__/slash-macro.test.js +81 -0
  202. package/src/runtime/__tests__/coding-agent-contract-shared.test.js +136 -0
  203. package/src/runtime/__tests__/coding-agent-events.test.js +263 -0
  204. package/src/assets/web-panel/assets/MobileProjects-DhDxrWze.js +0 -1
  205. package/src/assets/web-panel/assets/PdhVaultBrowser-BlmbHJnb.js +0 -7
  206. package/src/assets/web-panel/assets/Terminal-i0tHF8fy.js +0 -3
  207. package/src/assets/web-panel/assets/TimelineRenderer-FmPaNyqK.js +0 -1
  208. package/src/assets/web-panel/assets/devWarning-OhxeajH6.js +0 -1
  209. package/src/assets/web-panel/assets/index-Bbtoi_47.js +0 -1
  210. package/src/assets/web-panel/assets/index-Cb8RWwaB.js +0 -1
@@ -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
+ });
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * logger tests (previously untested) — the cc CLI's console logger. Small but
5
+ * the gating semantics matter: quiet suppresses info/success/log/newline but
6
+ * must NEVER suppress warn/error (otherwise failures would be hidden), and
7
+ * verbose only emits when enabled. console is spied; module-level verbose/quiet
8
+ * flags are reset between tests.
9
+ */
10
+
11
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
12
+ import {
13
+ setVerbose,
14
+ setQuiet,
15
+ info,
16
+ success,
17
+ warn,
18
+ error,
19
+ verbose,
20
+ log,
21
+ newline,
22
+ logger,
23
+ } from "../logger.js";
24
+
25
+ let logSpy;
26
+ let errSpy;
27
+
28
+ beforeEach(() => {
29
+ logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
30
+ errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
31
+ setQuiet(false);
32
+ setVerbose(false);
33
+ });
34
+
35
+ afterEach(() => {
36
+ setQuiet(false);
37
+ setVerbose(false);
38
+ vi.restoreAllMocks();
39
+ });
40
+
41
+ describe("default (not quiet, not verbose)", () => {
42
+ it("info/success/log/newline write to console.log", () => {
43
+ info("i");
44
+ success("s");
45
+ log("l");
46
+ newline();
47
+ expect(logSpy).toHaveBeenCalledTimes(4);
48
+ });
49
+
50
+ it("verbose is suppressed unless enabled", () => {
51
+ verbose("v");
52
+ expect(logSpy).not.toHaveBeenCalled();
53
+ });
54
+
55
+ it("error writes to console.error, not console.log", () => {
56
+ error("boom");
57
+ expect(errSpy).toHaveBeenCalledTimes(1);
58
+ expect(logSpy).not.toHaveBeenCalled();
59
+ });
60
+ });
61
+
62
+ describe("quiet mode", () => {
63
+ beforeEach(() => setQuiet(true));
64
+
65
+ it("suppresses info/success/log/newline", () => {
66
+ info("i");
67
+ success("s");
68
+ log("l");
69
+ newline();
70
+ expect(logSpy).not.toHaveBeenCalled();
71
+ });
72
+
73
+ it("does NOT suppress warn (still console.log)", () => {
74
+ warn("careful");
75
+ expect(logSpy).toHaveBeenCalledTimes(1);
76
+ });
77
+
78
+ it("does NOT suppress error", () => {
79
+ error("boom");
80
+ expect(errSpy).toHaveBeenCalledTimes(1);
81
+ });
82
+ });
83
+
84
+ describe("verbose mode", () => {
85
+ it("emits verbose only after setVerbose(true)", () => {
86
+ verbose("before");
87
+ expect(logSpy).not.toHaveBeenCalled();
88
+ setVerbose(true);
89
+ verbose("after");
90
+ expect(logSpy).toHaveBeenCalledTimes(1);
91
+ });
92
+ });
93
+
94
+ describe("logger object", () => {
95
+ it("exposes the full method surface", () => {
96
+ for (const m of [
97
+ "info",
98
+ "success",
99
+ "warn",
100
+ "error",
101
+ "verbose",
102
+ "log",
103
+ "newline",
104
+ "setVerbose",
105
+ "setQuiet",
106
+ ]) {
107
+ expect(typeof logger[m]).toBe("function");
108
+ }
109
+ });
110
+ });