chainlesschain 0.162.162 → 0.162.164

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 (237) hide show
  1. package/package.json +3 -1
  2. package/src/assets/web-panel/assets/{AIOps-ClI2ALgv.js → AIOps-BqRkaHGb.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BDjRcJaK.js → ActionButton-DWO7OZLC.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DikSRsaT.js → Analytics-C5oaVezM.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-9mLhMFmz.js → AppLayout-CsUESDoZ.js} +3 -3
  6. package/src/assets/web-panel/assets/{Artifacts-BSr88BrH.js → Artifacts-Dragqu5k.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-CdvyimOF.js → Audit-CQsaHctY.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-D3_hCedh.js → BackgroundAgents-DdG1jNSM.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-Cyvv9RHf.js → Backup-DN6sjK4z.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-BZM14kt8.js → BaseInput-Cxm_ZTCl.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-DrQSokVz.js → Chat-DVXGmA56.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-HwJR-C6B.js → ChatBubbleRenderer-BdaCL_iE.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-bFLYeWMV.js → Checkbox-DEc4vD_A.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-7mN7xJXT.js → Codegen-CKiTcv36.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-DAB5ZWm7.js → Col-Br9UUNN0.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-B_1TymaK.js → Community-NeUH_70T.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-Bq1uSWLJ.js → Compact-BqR-z2Df.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-CyiE1jEe.js → Compliance-DcWB_SgZ.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-Cpk0D7vm.js → Cowork-BfhgcudF.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-d_Lv3rdB.js → Cron-LaU29LgD.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DMqhHjMR.js → Crosschain-B2ZB9uwW.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-Br-oPuBw.js → DID-BuHl38A2.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-CfLW_RaP.js → Dashboard-m627yqvZ.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-DZnTOExV.js → Dropdown-C7bxF8F5.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-DHIUHDv1.js → EmailListRenderer-DPLPQzLp.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bw3t3myu.js → FamilyGuardDashboard-cOiH6ike.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-D9cpQPAY.js → Federation-Bvkzg88z.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-Jdczr9Mr.js → FormItemContext-BTsiA7h_.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-BT8ZMpC5.js → GenericCardRenderer-BGnVE5qu.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-DjARip2b.js → Git-DS_j6r25.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-BgWbSRcj.js → Governance-CKRG3Rsl.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-CfouJciM.js → Inference-DW8INoNQ.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-50l7mrAr.js → KnowledgeGraph-BhEYeFhC.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-rq50H84B.js → Logs-wqi0uxuR.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-DUPIEdQH.js → MarkdownRenderer-DS8dE8ty.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-BYgPDmHU.js → Marketplace-CMlTuPLT.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-5DVgcFdx.js → McpTools-CSx4m5JJ.js} +3 -3
  38. package/src/assets/web-panel/assets/{Memory-B70LPk7f.js → Memory-vnQCUy-_.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-De8ssA5Y.js → MobileBridge-BV2NA742.js} +1 -1
  40. package/src/assets/web-panel/assets/MobileProjects-Bw0k8e1B.js +1 -0
  41. package/src/assets/web-panel/assets/{Mtc-RJsOTsIA.js → Mtc-CzK_FPB1.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-Crh-6BEK.js → MtcAudit-Cb7G9oL3.js} +5 -5
  43. package/src/assets/web-panel/assets/{Multisig-wuoR5l3x.js → Multisig-qJxlUyzr.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-U2Mji4fZ.js → NLProgramming-DqaBJ7Te.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-CdTZAyDH.js → Notes-CwhTJkRi.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-C-FucWE7.js → NotificationSettings-YDs4Fal4.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-DCYFTeaI.js → OrderTableRenderer-CvEALpq7.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-an3OF85L.js → Organization-CEmQm9n8.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-C-1vmYFe.js → Overflow-zby_P_LA.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-Cwh47xKg.js → P2P-DsmDNFNT.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-Dm38GT0k.js → PdhVaultBrowser-FWwFlZGF.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-DS2Y8awD.js → Permissions-DuzQ6me5.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-BQNziA0I.js → PersonalDataHub-DZbeN613.js} +3 -3
  54. package/src/assets/web-panel/assets/{Pipeline-DF85L1ue.js → Pipeline-CoNSVAUS.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-BuLornF-.js → Privacy-OYJEAaMC.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-C3Y31IwX.js → ProjectInit-CWIapV--.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-BCPiI8aD.js → ProjectSettings-CX8xMxZn.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-BZ6s4k6g.js → Projects-BLCN8YyY.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-D3Wp022X.js → Providers-BV_sxp75.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-CUJfQkH4.js → QrScannerModal-BTnRrHRp.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-UAc0EO8l.js → QuickAsk-D1KZg018.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-BaxKupcs.js → Recommend-COm5cuNg.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-C75NNnXZ.js → RemoteSession-0saWEkYI.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-BVXynZAk.js → Reputation-Dr7iKLS2.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-C5MRZ0bi.js → Row-C1vpPBcr.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-DAKkEYka.js → RssFeed-CxlWrtNj.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-DvWv9YOA.js → Search-BT2V8dPN.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-B5peafW1.js → Security-bm9V3c5k.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-UQmvPEaG.js → Services-CocH1yyX.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-C3bhWpuF.js → Skeleton-DHItlMdd.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-DfkKf9_H.js → Skills-C59sLc4w.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-DqMSjlBM.js → Sla-Crj4BMc8.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-C-WbxWUX.js → SpeechSettings-DX4KFceY.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-CYuHZ4N2.js → SyncSettings-BD_iwQLr.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-CyeAyB9d.js → Tasks-BmCTzYsH.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-BTCeQnpq.js → Templates-BGUaxZYl.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-BifQuA9Y.js → Tenant-BS0tJx2O.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-Bx_9Bocf.js → Terminal--tHSAsld.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-BX0RFCOm.js → TimelineRenderer-D9-MFxS4.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-CGHrCmUS.js → Tokens-CiCB485f.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-DlqaY4SO.js → Trigger-SmCFuW_l.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-DfOmj-ay.js → Trust-DkK3Rqe0.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-B72jH5fE.js → UkeySign-CDM4LGdB.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-BT-63fqv.js → VideoEditing-IDB7DJXt.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-Dsul3sHr.js → Wallet-Bikj5cmt.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-Izkwwdgy.js → WebAuthn-C9abWlAw.js} +2 -2
  87. package/src/assets/web-panel/assets/{WorkflowEditor-D3FHSiH1.js → WorkflowEditor-DrbRKVky.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-CmVz5z3U.js → chat-CccCSDQf.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-CuLgyJBp.js → colors-CXObXLhb.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-Ml1YkXpw.js → compact-item-B5kfYAxC.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-BvjJ48bs.js → createContext-DmiPBfip.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-CvKrADNa.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DVC0157C.js → hasIn-Q6lYteAc.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-C229ABfy.js → index-25Wlqps4.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Ck_52dz_.js → index-33U29UNV.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-QXzlT9KD.js → index-4oXBDczR.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-4SBta3d7.js → index-BGYXvMc5.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-B0S8NzO6.js → index-BJCCa0s_.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DJyN28Et.js → index-BJJ5DhKI.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DC_OI9H2.js → index-BLOzViJ6.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-C_XuRgGn.js → index-BRrbUMoG.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-wvb_FtaV.js → index-BUFpT17Q.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-KMqhcF7l.js → index-BkRDiqGc.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-vG4XSJ1d.js → index-BsfvW6my.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DYXHFr4r.js → index-By-kfPED.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DuGszaWY.js → index-BzFTcQsc.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CXkJH-lK.js → index-C2FXdboq.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Dt5KMjwJ.js → index-C5XOlTHN.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-4wcgvPoF.js → index-CLhBDS2w.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-C_H_S-DY.js → index-CMq7UeoT.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BydjdZnl.js → index-CVMTWNKl.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BjHBpRYZ.js → index-Caghi-yL.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-Bzr0rB5T.js → index-CdB6FuSH.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BbPtzB8M.js → index-CoK8-kWQ.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BUt8kTIB.js → index-CsO3GUAY.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-C_vRgTl9.js → index-CwZBa20Z.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DukDVXCv.js → index-D-kASucw.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BbepULSc.js → index-D8eBw7vW.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BfuKP4qe.js → index-D9wgifk4.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BiC6dQZG.js → index-DAYCTc07.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CmFNvNLZ.js → index-DF6Q8l2h.js} +1 -1
  122. package/src/assets/web-panel/assets/index-DTdFUWc_.js +1 -0
  123. package/src/assets/web-panel/assets/{index-KjgKdZe-.js → index-DgTLApS9.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-BpFJHiTv.js → index-DgxVJ8pg.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Cw0Rjy87.js → index-DlJvIqoR.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CFYVQMvu.js → index-Dw4eHfsG.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CudmwNBV.js → index-Rh-ytwow.js} +3 -3
  128. package/src/assets/web-panel/assets/{index-DDGaOWBV.js → index-StAEt2pq.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-D3lD5qap.js → index-bzfw951k.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BzG-x9p4.js → index-fSUMbMvv.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-B_EvG_GW.js → index-tFHy5Wol.js} +1 -1
  132. package/src/assets/web-panel/assets/index-zQ0M_kz-.js +1 -0
  133. package/src/assets/web-panel/assets/{initDefaultProps-BDgd2YZ8.js → initDefaultProps-BNq7X72N.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-ErtrXdYe.js → motion-DOf9viTc.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-_GPPNCgs.js → move-CRHTcW4x.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-DRLL-8V2.js → mtc-parser-Cyoozgm2.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-p86sl023.js → omit-6-TjFNru.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-BTu7cMLH.js → pickAttrs-DlUmO1zi.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-BEKz6Isr.js → placementArrow-CFOL8mIR.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-Ck42c9MP.js → responsiveObserve-BtaFK5zd.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-1CEvuPsB.js → slide-BIbc2bur.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-BDfdx1GJ.js → statusUtils-BXX9d6-w.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-B4K46m5w.js → styleChecker-BGv10Bpn.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-DoIVpJNq.js → useFlexGapSupport-bKnKT8tw.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-DdAWi-ln.js → useFs-FY4IL4jn.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-iUu6k8dx.js → usePersonalDataHub-Bwl9TEgu.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-DZz4R6Tw.js → vnode-BM3OHxAT.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-DXlf9sA3.js → zoom-D1fbFiHB.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agenda.js +320 -32
  151. package/src/commands/agent.js +105 -11
  152. package/src/commands/context.js +125 -14
  153. package/src/commands/doctor.js +85 -1
  154. package/src/commands/hook.js +181 -7
  155. package/src/commands/plugin.js +456 -1
  156. package/src/commands/review.js +363 -2
  157. package/src/commands/session.js +191 -0
  158. package/src/commands/team.js +19 -1
  159. package/src/data/changelog.json +10 -4
  160. package/src/gateways/ws/remote-session-protocol.js +39 -0
  161. package/src/gateways/ws/session-protocol.js +55 -2
  162. package/src/gateways/ws/ws-agent-handler.js +50 -0
  163. package/src/harness/jsonl-session-store.js +60 -0
  164. package/src/harness/worktree-isolator.js +140 -5
  165. package/src/lib/agent-schedule-store.js +318 -10
  166. package/src/lib/agent-team/team-worktree.js +37 -2
  167. package/src/lib/agent-tool-admission.js +125 -0
  168. package/src/lib/app-preview.js +360 -0
  169. package/src/lib/async-hook-supervisor.cjs +54 -0
  170. package/src/lib/capability-manifest.js +319 -0
  171. package/src/lib/capability-negotiation.js +23 -12
  172. package/src/lib/channels/channel-manager.js +54 -2
  173. package/src/lib/cli-context-engineering.js +9 -2
  174. package/src/lib/context-breakdown.js +116 -0
  175. package/src/lib/diagnostic-bundle.js +230 -0
  176. package/src/lib/doctor-checkup.js +275 -4
  177. package/src/lib/execution-location.js +286 -0
  178. package/src/lib/headless-manifest.js +13 -40
  179. package/src/lib/hook-event-log.cjs +193 -0
  180. package/src/lib/hook-stats-store.cjs +197 -0
  181. package/src/lib/idempotency.js +109 -0
  182. package/src/lib/interaction-adapter.js +34 -4
  183. package/src/lib/json-schema-output.js +46 -12
  184. package/src/lib/json-schema-validate.js +143 -3
  185. package/src/lib/jsonl-session-store.js +1 -0
  186. package/src/lib/lsp/__tests__/lsp-manager.test.js +58 -0
  187. package/src/lib/lsp/lsp-manager.js +56 -0
  188. package/src/lib/monitor-event.js +290 -0
  189. package/src/lib/operation-fingerprint.js +217 -0
  190. package/src/lib/plugin-runtime/bin.js +37 -0
  191. package/src/lib/plugin-runtime/capabilities.js +19 -0
  192. package/src/lib/plugin-runtime/capability-consent.js +203 -0
  193. package/src/lib/plugin-runtime/hooks.js +37 -0
  194. package/src/lib/plugin-runtime/lsp.js +38 -0
  195. package/src/lib/plugin-runtime/mcp.js +40 -0
  196. package/src/lib/plugin-runtime/monitors.js +37 -0
  197. package/src/lib/plugin-runtime/plugin-options.js +151 -0
  198. package/src/lib/plugin-runtime/policy.js +62 -0
  199. package/src/lib/plugin-runtime/scopes.js +20 -4
  200. package/src/lib/pr-automation-policy.js +224 -0
  201. package/src/lib/project-instructions.js +128 -0
  202. package/src/lib/remote-approval-bridge.js +66 -13
  203. package/src/lib/repl-goal.js +132 -0
  204. package/src/lib/repl-rewind.js +153 -0
  205. package/src/lib/review-comment-anchor.js +241 -0
  206. package/src/lib/review-pipeline.js +46 -7
  207. package/src/lib/runtime-checkup.js +221 -0
  208. package/src/lib/schedule-planner.js +17 -1
  209. package/src/lib/secret-scan.js +409 -0
  210. package/src/lib/session-branch.js +0 -0
  211. package/src/lib/settings-hook-events.cjs +13 -0
  212. package/src/lib/settings-loader.cjs +42 -0
  213. package/src/lib/side-effect-ledger-store.js +88 -0
  214. package/src/lib/side-effect-ledger.js +401 -0
  215. package/src/lib/skill-loader.js +32 -0
  216. package/src/lib/skill-path-scope.js +63 -0
  217. package/src/lib/sub-agent-context.js +110 -9
  218. package/src/lib/sub-agent-registry.js +19 -0
  219. package/src/lib/subagent-inheritance.js +112 -0
  220. package/src/lib/telemetry/span-recorder.js +17 -4
  221. package/src/lib/terminal-context.js +195 -0
  222. package/src/lib/turn-binding-store.js +119 -0
  223. package/src/lib/unattended-action-policy.js +182 -0
  224. package/src/lib/worktree-cleanup-safety.js +138 -0
  225. package/src/lib/worktree-isolator.js +8 -0
  226. package/src/lib/worktree-sparse.js +131 -0
  227. package/src/repl/agent-repl.js +280 -30
  228. package/src/repl/permission-tier.js +28 -0
  229. package/src/runtime/agent-core.js +565 -21
  230. package/src/runtime/coding-agent-contract-shared.cjs +60 -1
  231. package/src/runtime/diagnostics.js +60 -0
  232. package/src/runtime/headless-runner.js +393 -1
  233. package/src/runtime/headless-stream.js +60 -1
  234. package/src/assets/web-panel/assets/MobileProjects-D10VISps.js +0 -1
  235. package/src/assets/web-panel/assets/devWarning-DhiGrbsh.js +0 -1
  236. package/src/assets/web-panel/assets/index-B7a1TrU6.js +0 -1
  237. package/src/assets/web-panel/assets/index-C2B1KRja.js +0 -1
@@ -23,9 +23,160 @@ import fs from "fs";
23
23
  import os from "os";
24
24
  import path from "path";
25
25
  import { randomUUID } from "crypto";
26
+ import { isEntryExpired, effectiveFireAt } from "./schedule-planner.js";
27
+ import { SUBAGENT_PERMISSION_MODES } from "./subagent-contract.js";
28
+ import { parseGoalCondition } from "./goal-condition-engine.js";
29
+ import { normalizeActionClass } from "./unattended-action-policy.js";
26
30
 
27
31
  export const SCHEDULE_KINDS = Object.freeze(["wakeup", "cron", "monitor"]);
28
32
 
33
+ /** A valid absolute-epoch-ms expiry, or null (never expires). */
34
+ function normalizeExpiresAt(v) {
35
+ const n = Number(v);
36
+ return Number.isFinite(n) && n > 0 ? n : null;
37
+ }
38
+
39
+ /**
40
+ * A non-negative integer jitter window (ms), or 0. Stored PER entry so each
41
+ * task carries its own deterministic spread — the schedule-planner applies a
42
+ * stable per-id offset within `[0, jitterMs)` so tasks sharing a cron minute
43
+ * fan out instead of firing as a thundering herd. 0 (the default) = no jitter.
44
+ */
45
+ function normalizeJitterMs(v) {
46
+ const n = Math.floor(Number(v));
47
+ return Number.isFinite(n) && n > 0 ? n : 0;
48
+ }
49
+
50
+ /**
51
+ * A per-task execution policy for wakeup/cron entries — how the spawned
52
+ * `cc agent` run should behave when this entry fires. Each field maps to a real
53
+ * `cc agent` flag consumed by `cc agenda run`, so a scheduled task can carry its
54
+ * OWN permission mode, worktree isolation and turn budget instead of inheriting
55
+ * whatever ambient environment `cc agenda run` happens to run in (the gap's
56
+ * "每个定时任务独立预算·权限模式·Worktree"). Returns `null` when nothing is set —
57
+ * the entry then omits `runPolicy` entirely and is byte-identical to before,
58
+ * so the default (unpolicied) task keeps spawning a plain `cc agent -p`.
59
+ *
60
+ * - permissionMode → `--permission-mode <mode>` (validated against the single
61
+ * source SUBAGENT_PERMISSION_MODES; invalid → dropped)
62
+ * - worktree → `--worktree` (run the task in a fresh git worktree)
63
+ * - maxTurns → `--max-turns <n>` (cap the agent loop — a real budget)
64
+ * - goalCondition → `--goal-condition <spec>` (re-drive the run to a
65
+ * completion condition; validated via parseGoalCondition,
66
+ * invalid spec → the whole goal set is dropped) with its
67
+ * own token/cost/time/outer-turn budget:
68
+ * goalMaxTokens → `--goal-max-tokens <n>`
69
+ * goalMaxCost → `--goal-max-cost <usd>`
70
+ * goalMaxTime → `--goal-max-time <ms>`
71
+ * maxOuterTurns → `--max-outer-turns <n>`
72
+ * (the goal budgets are meaningless without a valid condition, so they are
73
+ * only kept when the condition parses.)
74
+ *
75
+ * Monitors run a shell command / watch a file rather than spawning an agent, so
76
+ * a run policy does not apply to them.
77
+ */
78
+ export function normalizeRunPolicy({
79
+ permissionMode = null,
80
+ worktree = false,
81
+ maxTurns = null,
82
+ goalCondition = null,
83
+ goalMaxTokens = null,
84
+ goalMaxCost = null,
85
+ goalMaxTime = null,
86
+ maxOuterTurns = null,
87
+ unattendedAllowlist = null,
88
+ } = {}) {
89
+ const policy = {};
90
+ if (
91
+ typeof permissionMode === "string" &&
92
+ SUBAGENT_PERMISSION_MODES.includes(permissionMode)
93
+ ) {
94
+ policy.permissionMode = permissionMode;
95
+ }
96
+ if (worktree === true) policy.worktree = true;
97
+ const turns = Math.floor(Number(maxTurns));
98
+ if (Number.isFinite(turns) && turns > 0) policy.maxTurns = turns;
99
+
100
+ // goal-condition + its budgets: run to a completion condition with a per-task
101
+ // token/cost/time/outer-turn budget. Validate the spec up front so a doomed
102
+ // condition never gets scheduled; an invalid spec drops the whole goal set.
103
+ if (typeof goalCondition === "string" && goalCondition.trim()) {
104
+ let validSpec = false;
105
+ try {
106
+ parseGoalCondition(goalCondition);
107
+ validSpec = true;
108
+ } catch {
109
+ // invalid spec → drop goalCondition and its budgets entirely
110
+ }
111
+ if (validSpec) {
112
+ policy.goalCondition = goalCondition.trim();
113
+ const gTokens = Math.floor(Number(goalMaxTokens));
114
+ if (Number.isFinite(gTokens) && gTokens > 0)
115
+ policy.goalMaxTokens = gTokens;
116
+ const gCost = Number(goalMaxCost);
117
+ if (Number.isFinite(gCost) && gCost > 0) policy.goalMaxCost = gCost;
118
+ const gTime = Math.floor(Number(goalMaxTime));
119
+ if (Number.isFinite(gTime) && gTime > 0) policy.goalMaxTime = gTime;
120
+ const outer = Math.floor(Number(maxOuterTurns));
121
+ if (Number.isFinite(outer) && outer > 0) policy.maxOuterTurns = outer;
122
+ }
123
+ }
124
+
125
+ // P1-8: action classes this UNATTENDED task may still perform (e.g. "publish",
126
+ // "external_message"). Validated against the known classes; unknowns dropped.
127
+ // Absent → the scheduled run gets the default-deny high-risk tool set.
128
+ if (Array.isArray(unattendedAllowlist)) {
129
+ const cleaned = [
130
+ ...new Set(unattendedAllowlist.map(normalizeActionClass).filter(Boolean)),
131
+ ];
132
+ if (cleaned.length > 0) policy.unattendedAllowlist = cleaned;
133
+ }
134
+
135
+ return Object.keys(policy).length > 0 ? policy : null;
136
+ }
137
+
138
+ /**
139
+ * Resolve an entry's expiry: an explicit absolute `expiresAt` (epoch-ms) wins;
140
+ * otherwise a positive relative `expiresInMs` is anchored to the store's own
141
+ * `now` (symmetric with `delayMs` on a wakeup, and deterministic under an
142
+ * injected clock). No expiry → null.
143
+ */
144
+ function resolveExpiresAt(expiresAt, expiresInMs, now) {
145
+ if (expiresAt != null) return normalizeExpiresAt(expiresAt);
146
+ const rel = Math.floor(Number(expiresInMs));
147
+ if (Number.isFinite(rel) && rel > 0) return normalizeExpiresAt(now + rel);
148
+ return null;
149
+ }
150
+
151
+ /** Is an entry currently in a schedulable (non-terminal) state? */
152
+ function isSchedulableStatus(entry) {
153
+ return entry.kind === "wakeup"
154
+ ? entry.status === "pending"
155
+ : entry.status === "active";
156
+ }
157
+
158
+ /** Statuses an entry can never leave — safe to prune. */
159
+ export const TERMINAL_STATUSES = Object.freeze([
160
+ "fired",
161
+ "matched",
162
+ "exhausted",
163
+ "expired",
164
+ ]);
165
+ const TERMINAL_SET = new Set(TERMINAL_STATUSES);
166
+
167
+ /** Best available "this entry reached a terminal state at" timestamp. */
168
+ function terminalAt(entry) {
169
+ return (
170
+ entry.firedAt ??
171
+ entry.matchedAt ??
172
+ entry.expiredAt ??
173
+ entry.lastRunAt ??
174
+ entry.lastCheckAt ??
175
+ entry.createdAt ??
176
+ 0
177
+ );
178
+ }
179
+
29
180
  export function agentScheduleDir(homedir = os.homedir()) {
30
181
  return path.join(homedir, ".chainlesschain", "agent-schedule");
31
182
  }
@@ -154,11 +305,23 @@ export class AgentScheduleStore {
154
305
 
155
306
  // ── create ──────────────────────────────────────────────────────────────
156
307
 
157
- scheduleWakeup({ prompt, delayMs = 0, dueAt = null, label = null } = {}) {
308
+ scheduleWakeup(options = {}) {
309
+ const {
310
+ prompt,
311
+ delayMs = 0,
312
+ dueAt = null,
313
+ label = null,
314
+ expiresAt = null,
315
+ expiresInMs = null,
316
+ jitterMs = 0,
317
+ } = options;
158
318
  if (!prompt || typeof prompt !== "string") {
159
319
  throw new Error("wakeup requires a prompt");
160
320
  }
161
321
  const now = this._now();
322
+ // The full options object is passed to normalizeRunPolicy, which reads only
323
+ // the policy fields it knows — new policy knobs need no signature change here.
324
+ const runPolicy = normalizeRunPolicy(options);
162
325
  const entry = {
163
326
  id: randomUUID(),
164
327
  kind: "wakeup",
@@ -166,6 +329,9 @@ export class AgentScheduleStore {
166
329
  label: label || null,
167
330
  dueAt:
168
331
  dueAt != null ? Number(dueAt) : now + Math.max(0, Number(delayMs) || 0),
332
+ expiresAt: resolveExpiresAt(expiresAt, expiresInMs, now),
333
+ jitterMs: normalizeJitterMs(jitterMs),
334
+ ...(runPolicy ? { runPolicy } : {}),
169
335
  createdAt: now,
170
336
  status: "pending",
171
337
  };
@@ -175,7 +341,15 @@ export class AgentScheduleStore {
175
341
  return entry;
176
342
  }
177
343
 
178
- createCron({ prompt, cron, label = null } = {}) {
344
+ createCron(options = {}) {
345
+ const {
346
+ prompt,
347
+ cron,
348
+ label = null,
349
+ expiresAt = null,
350
+ expiresInMs = null,
351
+ jitterMs = 0,
352
+ } = options;
179
353
  if (!prompt || typeof prompt !== "string") {
180
354
  throw new Error("cron requires a prompt");
181
355
  }
@@ -184,6 +358,8 @@ export class AgentScheduleStore {
184
358
  if (nextAt == null) {
185
359
  throw new Error(`cron "${cron}" has no upcoming match`);
186
360
  }
361
+ // Full options → normalizeRunPolicy (reads only known policy fields).
362
+ const runPolicy = normalizeRunPolicy(options);
187
363
  const entry = {
188
364
  id: randomUUID(),
189
365
  kind: "cron",
@@ -191,6 +367,9 @@ export class AgentScheduleStore {
191
367
  cron,
192
368
  label: label || null,
193
369
  nextAt,
370
+ expiresAt: resolveExpiresAt(expiresAt, expiresInMs, now),
371
+ jitterMs: normalizeJitterMs(jitterMs),
372
+ ...(runPolicy ? { runPolicy } : {}),
194
373
  createdAt: now,
195
374
  lastRunAt: null,
196
375
  runs: 0,
@@ -204,14 +383,56 @@ export class AgentScheduleStore {
204
383
 
205
384
  createMonitor({
206
385
  command,
386
+ watchFile = null,
387
+ watchUrl = null,
388
+ watchChange = false,
207
389
  intervalMs,
208
390
  stopWhen = null,
209
391
  notify = null,
210
392
  maxChecks = null,
211
393
  label = null,
394
+ expiresAt = null,
395
+ expiresInMs = null,
396
+ jitterMs = 0,
212
397
  } = {}) {
213
- if (!command || typeof command !== "string") {
214
- throw new Error("monitor requires a command");
398
+ // A monitor watches exactly one source:
399
+ // - a shell `command` → match its stdout/stderr
400
+ // - a `watchFile` → match its content, or fire when the file appears
401
+ // - a `watchUrl` → GET it and match the response body, or fire on 2xx
402
+ const hasCommand = typeof command === "string" && command.length > 0;
403
+ const hasFile = typeof watchFile === "string" && watchFile.length > 0;
404
+ const hasUrl = typeof watchUrl === "string" && watchUrl.length > 0;
405
+ const sourceCount = Number(hasCommand) + Number(hasFile) + Number(hasUrl);
406
+ if (sourceCount !== 1) {
407
+ throw new Error(
408
+ "monitor requires exactly one of `command`, `watchFile`, or `watchUrl`",
409
+ );
410
+ }
411
+ if (hasUrl) {
412
+ // Only http(s) endpoints — reject file:// and other schemes up-front so a
413
+ // bad URL errors at creation, not silently at the first poll.
414
+ let parsed;
415
+ try {
416
+ parsed = new URL(watchUrl);
417
+ } catch {
418
+ throw new Error(`monitor watchUrl is not a valid URL: ${watchUrl}`);
419
+ }
420
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
421
+ throw new Error(
422
+ `monitor watchUrl must be http(s), got: ${parsed.protocol}`,
423
+ );
424
+ }
425
+ }
426
+ if (watchChange) {
427
+ // `watchChange` is a file-only mode — fire when the file's mtime advances.
428
+ // It carries its own signal, so it cannot be combined with a stopWhen
429
+ // content pattern (that would be an ambiguous double condition).
430
+ if (!hasFile) {
431
+ throw new Error("monitor watchChange requires a watchFile");
432
+ }
433
+ if (stopWhen != null) {
434
+ throw new Error("monitor watchChange cannot be combined with stopWhen");
435
+ }
215
436
  }
216
437
  const interval = Math.max(1000, Number(intervalMs) || 60000);
217
438
  if (stopWhen != null) {
@@ -221,16 +442,24 @@ export class AgentScheduleStore {
221
442
  new RegExp(stopWhen);
222
443
  }
223
444
  const now = this._now();
445
+ const source = hasFile ? "file" : hasUrl ? "http" : "command";
224
446
  const entry = {
225
447
  id: randomUUID(),
226
448
  kind: "monitor",
227
- command,
449
+ source,
450
+ command: hasCommand ? command : null,
451
+ watchFile: hasFile ? watchFile : null,
452
+ watchUrl: hasUrl ? watchUrl : null,
453
+ watchChange: hasFile ? Boolean(watchChange) : false,
454
+ lastMtimeMs: null,
228
455
  intervalMs: interval,
229
456
  stopWhen: stopWhen || null,
230
457
  notify: notify || null,
231
458
  maxChecks: maxChecks != null ? Number(maxChecks) : null,
232
459
  label: label || null,
233
460
  nextAt: now + interval,
461
+ expiresAt: resolveExpiresAt(expiresAt, expiresInMs, now),
462
+ jitterMs: normalizeJitterMs(jitterMs),
234
463
  createdAt: now,
235
464
  checks: 0,
236
465
  status: "active",
@@ -254,13 +483,45 @@ export class AgentScheduleStore {
254
483
  due(kind = null, atMs = null) {
255
484
  const now = atMs != null ? atMs : this._now();
256
485
  return this.list(kind).filter((entry) => {
257
- if (entry.kind === "wakeup") {
258
- return entry.status === "pending" && entry.dueAt <= now;
259
- }
260
- return entry.status === "active" && entry.nextAt <= now;
486
+ // An entry past its expiry never fires — even if it is not yet retired
487
+ // (defense-in-depth: retireExpired need not have run first).
488
+ if (isEntryExpired(entry, now)) return false;
489
+ if (!isSchedulableStatus(entry)) return false;
490
+ // The effective fire time applies the entry's OWN deterministic jitter
491
+ // (`entry.jitterMs`, default 0 → base fire time, byte-identical to the
492
+ // prior `dueAt`/`nextAt` comparison). A jittered entry fires slightly
493
+ // later, spreading tasks that share a cron minute.
494
+ const fireAt = effectiveFireAt(entry);
495
+ return fireAt != null && fireAt <= now;
261
496
  });
262
497
  }
263
498
 
499
+ /**
500
+ * Retire every schedulable entry whose `expiresAt` has passed (status →
501
+ * "expired", stamping `expiredAt`), across all kinds, so it never fires again.
502
+ * A daemon / `cc agenda run` calls this before firing due entries — mirroring
503
+ * the schedule-planner's "retire expired BEFORE due" semantics. Returns the
504
+ * list of retired entries.
505
+ */
506
+ retireExpired(atMs = null) {
507
+ const now = atMs != null ? atMs : this._now();
508
+ const retired = [];
509
+ for (const kind of SCHEDULE_KINDS) {
510
+ const entries = this._readAll(kind);
511
+ let changed = false;
512
+ for (const entry of entries) {
513
+ if (isSchedulableStatus(entry) && isEntryExpired(entry, now)) {
514
+ entry.status = "expired";
515
+ entry.expiredAt = now;
516
+ retired.push(entry);
517
+ changed = true;
518
+ }
519
+ }
520
+ if (changed) this._writeAll(kind, entries);
521
+ }
522
+ return retired;
523
+ }
524
+
264
525
  cancel(id) {
265
526
  for (const kind of SCHEDULE_KINDS) {
266
527
  const entries = this._readAll(kind);
@@ -274,6 +535,28 @@ export class AgentScheduleStore {
274
535
  return null;
275
536
  }
276
537
 
538
+ /**
539
+ * Remove terminal entries (fired / matched / exhausted / expired) whose
540
+ * terminal timestamp is at or before `before` (default: all terminal
541
+ * entries), across all kinds. Keeps the append-only JSONL store from growing
542
+ * without bound once entries finish. Returns the removed entries; only a kind
543
+ * that actually changed is rewritten.
544
+ */
545
+ pruneTerminal({ before = Infinity } = {}) {
546
+ const removed = [];
547
+ for (const kind of SCHEDULE_KINDS) {
548
+ const entries = this._readAll(kind);
549
+ const keep = entries.filter((entry) => {
550
+ const prunable =
551
+ TERMINAL_SET.has(entry.status) && terminalAt(entry) <= before;
552
+ if (prunable) removed.push(entry);
553
+ return !prunable;
554
+ });
555
+ if (keep.length !== entries.length) this._writeAll(kind, keep);
556
+ }
557
+ return removed;
558
+ }
559
+
277
560
  // ── lifecycle mutations (used by `cc agenda run`) ──────────────────────────
278
561
 
279
562
  /** Mark a one-shot wakeup fired (keeps history; due() skips it). */
@@ -303,14 +586,39 @@ export class AgentScheduleStore {
303
586
  * Record a monitor check. `matched` ends the monitor (done); otherwise it is
304
587
  * re-armed for the next interval, or exhausted once maxChecks is hit.
305
588
  */
306
- recordMonitorCheck(id, { matched = false, atMs = null } = {}) {
589
+ recordMonitorCheck(
590
+ id,
591
+ {
592
+ matched = false,
593
+ atMs = null,
594
+ mtimeMs = null,
595
+ eventId = null,
596
+ authority = null,
597
+ } = {},
598
+ ) {
307
599
  const now = atMs != null ? atMs : this._now();
308
600
  return this._mutate("monitor", id, (entry) => {
309
601
  entry.checks = (entry.checks || 0) + 1;
310
602
  entry.lastCheckAt = now;
603
+ // Record the mtime baseline the first time we see the file (for a
604
+ // watchChange monitor); leave it fixed thereafter so "changed" always
605
+ // means "changed since we started watching".
606
+ if (mtimeMs != null && entry.lastMtimeMs == null) {
607
+ entry.lastMtimeMs = mtimeMs;
608
+ }
311
609
  if (matched) {
312
610
  entry.status = "matched";
313
611
  entry.matchedAt = now;
612
+ // Audit record of the firing: the deterministic monitor-event id + the
613
+ // (always SYSTEM/steer) authority the event was delivered under. Durable
614
+ // on the entry so `cc agenda list --json` and a future resident daemon
615
+ // can recognise / dedup the exact observation. Only stamped when the
616
+ // caller supplies them, so legacy callers stay byte-identical.
617
+ if (eventId != null) {
618
+ entry.lastEventId = String(eventId);
619
+ entry.lastEventAt = now;
620
+ }
621
+ if (authority != null) entry.lastAuthority = String(authority);
314
622
  } else if (entry.maxChecks != null && entry.checks >= entry.maxChecks) {
315
623
  entry.status = "exhausted";
316
624
  } else {
@@ -22,8 +22,31 @@ import {
22
22
  mergeWorktree as _mergeWorktree,
23
23
  } from "../../harness/worktree-isolator.js";
24
24
  import { isGitRepo as _isGitRepo } from "../../lib/git-integration.js";
25
+ import { normalizeSparsePaths } from "../../lib/worktree-sparse.js";
25
26
  import { execFileSync, spawn } from "node:child_process";
26
27
 
28
+ /**
29
+ * Resolve the sparse-checkout / dependency-symlink options for a task, letting a
30
+ * per-task value (from the plan) override the coordinator-wide default. Returns
31
+ * `undefined` when nothing is configured so `createWorktree` takes its unchanged
32
+ * full-checkout path (byte-identical to before this feature).
33
+ */
34
+ function resolveWorktreeOptions(task, defaults) {
35
+ const t = task || {};
36
+ const sparse = normalizeSparsePaths(
37
+ t.sparsePaths ?? t.metadata?.sparsePaths ?? defaults.sparsePaths ?? null,
38
+ );
39
+ const symlink =
40
+ t.symlinkDirectories ??
41
+ t.metadata?.symlinkDirectories ??
42
+ defaults.symlinkDirectories ??
43
+ null;
44
+ const opts = {};
45
+ if (sparse) opts.sparsePaths = sparse;
46
+ if (symlink != null) opts.symlinkDirectories = symlink;
47
+ return Object.keys(opts).length ? opts : undefined;
48
+ }
49
+
27
50
  function defaultRunShell(command, cwd) {
28
51
  return new Promise((resolve, reject) => {
29
52
  const child = spawn(command, { cwd, shell: true, env: process.env });
@@ -72,9 +95,15 @@ export const _deps = {
72
95
  };
73
96
 
74
97
  export class TeamWorktreeCoordinator {
75
- constructor(repoDir) {
98
+ constructor(repoDir, options = {}) {
76
99
  this.repoDir = repoDir;
77
100
  this._created = new Map(); // key → { branch, path, committed }
101
+ // Coordinator-wide sparse-checkout / dependency-symlink defaults; a per-task
102
+ // value (task.sparsePaths / task.metadata.sparsePaths) overrides these.
103
+ this._worktreeDefaults = {
104
+ sparsePaths: options.sparsePaths ?? null,
105
+ symlinkDirectories: options.symlinkDirectories ?? null,
106
+ };
78
107
  }
79
108
 
80
109
  isGitRepo() {
@@ -114,7 +143,13 @@ export class TeamWorktreeCoordinator {
114
143
  }
115
144
  this._created.delete(key);
116
145
  }
117
- const { path: worktreePath } = _deps.createWorktree(this.repoDir, branch);
146
+ const wtOptions = resolveWorktreeOptions(task, this._worktreeDefaults);
147
+ const { path: worktreePath } = _deps.createWorktree(
148
+ this.repoDir,
149
+ branch,
150
+ null,
151
+ wtOptions,
152
+ );
118
153
  this._created.set(key, { branch, path: worktreePath, committed: false });
119
154
  if (typeof runInWorktree === "function") {
120
155
  await runInWorktree({ key, task, holder, cwd: worktreePath });
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Agent tool admission + attribution — the session-level gate behind P1-6
3
+ * (Agent 内 Skills / MCP / Plugin 治理) of
4
+ * CLAUDE_CODE_IDE_INCREMENTAL_GAP_ANALYSIS_2026-07-13.md:
5
+ *
6
+ * "Extension Tier 工具需经过 Capability、Policy、权限、预算和 UI 支持检查后再
7
+ * 开放。每次 Tool/Skill 调用显示 Attribution。"
8
+ *
9
+ * [[plugin-runtime/capabilities.js]] decides whether a PLUGIN declared the
10
+ * capabilities its components need (an install-time contract). It does NOT decide
11
+ * whether an agent TOOL may be opened into a running session. MVP-tier tools
12
+ * (read/write/shell/…) are the baseline set; extension-tier tools (run_skill,
13
+ * spawn_sub_agent, browser, schedule, publish_artifact, MCP tools) reach further
14
+ * and must clear five gates first — and every admitted call must be attributable.
15
+ *
16
+ * This module is that pure decision + the attribution record. Fail-closed and
17
+ * EXHAUSTIVE (every unmet gate is collected, like [[pr-automation-policy.js]]):
18
+ * an extension tool opens only when Capability, Policy, permission, budget and UI
19
+ * support are ALL satisfied. An unknown tier is treated as extension — the
20
+ * most-restrictive reading.
21
+ *
22
+ * PURE: no fs / clock / RNG / process. The caller passes the signals it already
23
+ * gathered (capability grant, policy allow, permission grant, budget, UI
24
+ * support) and reads back allow/deny + a log-safe attribution record.
25
+ */
26
+
27
+ /** Tool tiers. MVP = baseline built-ins; EXTENSION = the gated, further-reaching set. */
28
+ export const TOOL_TIER = Object.freeze({
29
+ MVP: "mvp",
30
+ EXTENSION: "extension",
31
+ });
32
+
33
+ /** Normalize a tier label; anything unrecognized → EXTENSION (fail-closed). */
34
+ export function normalizeToolTier(value) {
35
+ const s = String(value || "")
36
+ .trim()
37
+ .toLowerCase();
38
+ if (s === "mvp" || s === "baseline" || s === "core") return TOOL_TIER.MVP;
39
+ return TOOL_TIER.EXTENSION;
40
+ }
41
+
42
+ /**
43
+ * Decide whether a tool may be admitted into the session. EXHAUSTIVE fail-closed.
44
+ *
45
+ * Extension-tier gates (all required): capability, policy, permission, budget,
46
+ * ui-support. MVP-tier gates: policy + budget only (capability / UI support /
47
+ * per-tool permission do not gate a built-in's ADMISSION — a built-in still
48
+ * asks permission per call at execution time).
49
+ *
50
+ * @param {object} params
51
+ * @param {string} params.tool tool name (for the record)
52
+ * @param {string} params.tier "mvp" | "extension"
53
+ * @param {boolean} params.capabilityGranted plugin declared the capability
54
+ * @param {boolean} params.policyAllowed enterprise/allowlist policy permits it
55
+ * @param {boolean} params.permissionGranted session permission mode permits it
56
+ * @param {boolean} params.budgetOk the run's budget is not exhausted
57
+ * @param {boolean} params.uiSupported the current surface can render it
58
+ * @returns {{admitted:boolean, unmet:string[], reason:string, tier:string}}
59
+ */
60
+ export function admitTool(params = {}) {
61
+ const tier = normalizeToolTier(params.tier);
62
+ const unmet = [];
63
+
64
+ // Policy + budget gate every tier.
65
+ if (params.policyAllowed !== true) unmet.push("policy-blocked");
66
+ if (params.budgetOk !== true) unmet.push("budget-exhausted");
67
+
68
+ // Extension tier additionally requires capability, permission and UI support.
69
+ if (tier === TOOL_TIER.EXTENSION) {
70
+ if (params.capabilityGranted !== true) unmet.push("capability-not-granted");
71
+ if (params.permissionGranted !== true) unmet.push("permission-not-granted");
72
+ if (params.uiSupported !== true) unmet.push("ui-unsupported");
73
+ }
74
+
75
+ return {
76
+ admitted: unmet.length === 0,
77
+ unmet,
78
+ reason: unmet.length === 0 ? "ok" : unmet[0],
79
+ tier,
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Build the log-safe attribution record shown per Tool/Skill call. Carries WHERE
85
+ * a tool came from and WHETHER it was admitted — never a secret, arg value or
86
+ * credential. `decision` is an admitTool() result (or omitted for a bare record).
87
+ *
88
+ * @param {object} params {tool, source, version, scope, tier, callId, decision}
89
+ * @returns {object}
90
+ */
91
+ export function buildToolAttribution(params = {}) {
92
+ const decision = params.decision || null;
93
+ const tier = normalizeToolTier(params.tier ?? decision?.tier);
94
+ return {
95
+ tool: params.tool == null ? null : String(params.tool),
96
+ source: params.source == null ? null : String(params.source),
97
+ version: params.version == null ? null : String(params.version),
98
+ scope: params.scope == null ? null : String(params.scope),
99
+ tier,
100
+ callId: params.callId == null ? null : String(params.callId),
101
+ admitted: decision ? decision.admitted === true : null,
102
+ reason: decision ? decision.reason : null,
103
+ unmet: decision && Array.isArray(decision.unmet) ? [...decision.unmet] : [],
104
+ };
105
+ }
106
+
107
+ /**
108
+ * A one-line, token-free attribution string for the transcript
109
+ * (e.g. "run_skill ← acme-skills@1.2.0 (project) · extension · admitted").
110
+ */
111
+ export function describeToolAttribution(attr = {}) {
112
+ const parts = [String(attr.tool || "tool")];
113
+ if (attr.source) {
114
+ parts.push(
115
+ `← ${attr.source}${attr.version ? `@${attr.version}` : ""}${
116
+ attr.scope ? ` (${attr.scope})` : ""
117
+ }`,
118
+ );
119
+ }
120
+ parts.push(`· ${normalizeToolTier(attr.tier)}`);
121
+ if (attr.admitted === true) parts.push("· admitted");
122
+ else if (attr.admitted === false)
123
+ parts.push(`· denied(${attr.reason || "policy"})`);
124
+ return parts.join(" ");
125
+ }