chainlesschain 0.162.160 → 0.162.162

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 (199) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-CkRHjW8e.js → AIOps-ClI2ALgv.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BP3MeNi2.js → ActionButton-BDjRcJaK.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-CjbjQsOr.js → Analytics-DikSRsaT.js} +2 -2
  5. package/src/assets/web-panel/assets/{AppLayout-i0adH1M8.js → AppLayout-9mLhMFmz.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-CCCG2GOU.js → Artifacts-BSr88BrH.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-DrsgvwHC.js → Audit-CdvyimOF.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-DkXNkbZM.js → BackgroundAgents-D3_hCedh.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-DarTWCex.js → Backup-Cyvv9RHf.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-DaDzhcto.js → BaseInput-BZM14kt8.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-C4EzGw6V.js → Chat-DrQSokVz.js} +5 -5
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-lkavIWMS.js → ChatBubbleRenderer-HwJR-C6B.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-gddNgaQi.js → Checkbox-bFLYeWMV.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-BKldFWf9.js → Codegen-7mN7xJXT.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-ChR-brDs.js → Col-DAB5ZWm7.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-DlHtZX86.js → Community-B_1TymaK.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-BAhfcBpC.js → Compact-Bq1uSWLJ.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-gIrKkm6m.js → Compliance-CyiE1jEe.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-BYnzLBO3.js → Cowork-Cpk0D7vm.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-ByUDIljI.js → Cron-d_Lv3rdB.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DOdNbHFp.js → Crosschain-DMqhHjMR.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BJHaNWD2.js → DID-Br-oPuBw.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-Drm_tClz.js → Dashboard-CfLW_RaP.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-CA1WLzGN.js → Dropdown-DZnTOExV.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-Di1SKjNO.js → EmailListRenderer-DHIUHDv1.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-44Ve7ZFe.js → FamilyGuardDashboard-Bw3t3myu.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-CubPwv_N.js → Federation-D9cpQPAY.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-CsUFe5bU.js → FormItemContext-Jdczr9Mr.js} +1 -1
  29. package/src/assets/web-panel/assets/GenericCardRenderer-BT8ZMpC5.js +1 -0
  30. package/src/assets/web-panel/assets/{Git-D-Qx9dhO.js → Git-DjARip2b.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-_YMYaU4n.js → Governance-BgWbSRcj.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-J7Yzh-K_.js → Inference-CfouJciM.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-D37iI_Mo.js → KnowledgeGraph-50l7mrAr.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-oMTSYfGY.js → Logs-rq50H84B.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-BqaPmC0W.js → MarkdownRenderer-DUPIEdQH.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-DKUqYDaF.js → Marketplace-BYgPDmHU.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-BEv3Gx61.js → McpTools-5DVgcFdx.js} +3 -3
  38. package/src/assets/web-panel/assets/{Memory-CtA8XD2y.js → Memory-B70LPk7f.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-ug7c1mMj.js → MobileBridge-De8ssA5Y.js} +1 -1
  40. package/src/assets/web-panel/assets/MobileProjects-D10VISps.js +1 -0
  41. package/src/assets/web-panel/assets/{Mtc-WJaMs4i2.js → Mtc-RJsOTsIA.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-DXnK_-ru.js → MtcAudit-Crh-6BEK.js} +5 -5
  43. package/src/assets/web-panel/assets/{Multisig-C4Ev7726.js → Multisig-wuoR5l3x.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-BMFb9_zk.js → NLProgramming-U2Mji4fZ.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-Bl93nvBX.js → Notes-CdTZAyDH.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-BN2W5cl6.js → NotificationSettings-C-FucWE7.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-Dxc9oN4o.js → OrderTableRenderer-DCYFTeaI.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-CAs48637.js → Organization-an3OF85L.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-Bhq27Zoc.js → Overflow-C-1vmYFe.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-D61-2542.js → P2P-Cwh47xKg.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-C1j4iUxz.js → PdhVaultBrowser-Dm38GT0k.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-rJ2-fScM.js → Permissions-DS2Y8awD.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-DFIlTok6.js → PersonalDataHub-BQNziA0I.js} +3 -3
  54. package/src/assets/web-panel/assets/{Pipeline-BjIgEWoZ.js → Pipeline-DF85L1ue.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-BQoH5wcX.js → Privacy-BuLornF-.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-CYR_-_Ub.js → ProjectInit-C3Y31IwX.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-nkatCq-9.js → ProjectSettings-BCPiI8aD.js} +2 -2
  58. package/src/assets/web-panel/assets/Projects-BZ6s4k6g.js +1 -0
  59. package/src/assets/web-panel/assets/{Providers-BnKM9fsF.js → Providers-D3Wp022X.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-Cfe82dbz.js → QrScannerModal-CUJfQkH4.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-r1RbPufV.js → QuickAsk-UAc0EO8l.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-CjCbM2WE.js → Recommend-BaxKupcs.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-BuMGPH6V.js → RemoteSession-C75NNnXZ.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-Bmj3NY1F.js → Reputation-BVXynZAk.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-BIsNcE_1.js → Row-C5MRZ0bi.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-BObmojD2.js → RssFeed-DAKkEYka.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-BRmbkQNS.js → Search-DvWv9YOA.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-_rF3DRpY.js → Security-B5peafW1.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-D0If9ZV4.js → Services-UQmvPEaG.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-o7zIksUg.js → Skeleton-C3bhWpuF.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-oUR7zi9u.js → Skills-DfkKf9_H.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-fqKyGi03.js → Sla-DqMSjlBM.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-B1wZ_gxx.js → SpeechSettings-C-WbxWUX.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-B3qJWYXq.js → SyncSettings-CYuHZ4N2.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-BH5_dUfr.js → Tasks-CyeAyB9d.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-CCIVpjRP.js → Templates-BTCeQnpq.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-2GZtTGnB.js → Tenant-BifQuA9Y.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-BlMkfs_u.js → Terminal-Bx_9Bocf.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-U6hi5OMc.js → TimelineRenderer-BX0RFCOm.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-C-_BerGJ.js → Tokens-CGHrCmUS.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-DaKzBgN3.js → Trigger-DlqaY4SO.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-CLvcsco3.js → Trust-DfOmj-ay.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-BFn_Y1zZ.js → UkeySign-B72jH5fE.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-B0V18dLT.js → VideoEditing-BT-63fqv.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-BbmP7Imu.js → Wallet-Dsul3sHr.js} +3 -3
  86. package/src/assets/web-panel/assets/{WebAuthn-Dewj_0kt.js → WebAuthn-Izkwwdgy.js} +4 -4
  87. package/src/assets/web-panel/assets/{WorkflowEditor-DOxz3Lwa.js → WorkflowEditor-D3FHSiH1.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-B1OIpZnh.js → chat-CmVz5z3U.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-CSGNVdri.js → colors-CuLgyJBp.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-8S0vcg0u.js → compact-item-Ml1YkXpw.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-vZyc27TN.js → createContext-BvjJ48bs.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-DhiGrbsh.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DWw3-UhA.js → hasIn-DVC0157C.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-DLAvUtOH.js → index-4SBta3d7.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BZnauCqN.js → index-4wcgvPoF.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-t-q3rkVm.js → index-B0S8NzO6.js} +1 -1
  97. package/src/assets/web-panel/assets/index-B7a1TrU6.js +1 -0
  98. package/src/assets/web-panel/assets/{index-BwKrTVV0.js → index-BUt8kTIB.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BQssf0EQ.js → index-B_EvG_GW.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-xx5M5Rbj.js → index-BbPtzB8M.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DKwl1AkQ.js → index-BbepULSc.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-B4pP2ycr.js → index-BfuKP4qe.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-D1i_8oop.js → index-BiC6dQZG.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Dc40pX3F.js → index-BjHBpRYZ.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DeCS12Mh.js → index-BpFJHiTv.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BjC-TrOb.js → index-BydjdZnl.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DcfLXAW7.js → index-BzG-x9p4.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CfEvacz7.js → index-Bzr0rB5T.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BoIPHP4R.js → index-C229ABfy.js} +1 -1
  110. package/src/assets/web-panel/assets/index-C2B1KRja.js +1 -0
  111. package/src/assets/web-panel/assets/{index-BdyokEau.js → index-CFYVQMvu.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CLPr2vC_.js → index-CXkJH-lK.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DtHWHJnj.js → index-C_H_S-DY.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Q6oyLnKC.js → index-C_XuRgGn.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BeBv896m.js → index-C_vRgTl9.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DLgkAhEE.js → index-Ck_52dz_.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-tr1x96W9.js → index-CmFNvNLZ.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CB2MOS_8.js → index-CudmwNBV.js} +3 -3
  119. package/src/assets/web-panel/assets/{index-CFQWZvak.js → index-Cw0Rjy87.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BuB5P0TR.js → index-D3lD5qap.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-D8qXVfoV.js → index-DC_OI9H2.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-C6r27A-o.js → index-DDGaOWBV.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CbZqw7pg.js → index-DJyN28Et.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CJgKP2cm.js → index-DYXHFr4r.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CI3slBKI.js → index-Dt5KMjwJ.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CwXBblm-.js → index-DuGszaWY.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BKEmQQvR.js → index-DukDVXCv.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Dg0Izipr.js → index-KMqhcF7l.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BkWd6YL7.js → index-KjgKdZe-.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DPgY5GLy.js → index-QXzlT9KD.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BDHyovnK.js → index-vG4XSJ1d.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-dxocDVD_.js → index-wvb_FtaV.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-Di5YFId0.js → initDefaultProps-BDgd2YZ8.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-BVSN0aeL.js → motion-ErtrXdYe.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-DHNiaP2l.js → move-_GPPNCgs.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-xJPf3gQD.js → mtc-parser-DRLL-8V2.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-C7qzUdkN.js → omit-p86sl023.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-pSpjg3Le.js → pickAttrs-BTu7cMLH.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-cs_jL_UC.js → placementArrow-BEKz6Isr.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-DkS_Zs4r.js → responsiveObserve-Ck42c9MP.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-B5O1Kpxo.js → slide-1CEvuPsB.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-BX02GnZp.js → statusUtils-BDfdx1GJ.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-EmBYRftd.js → styleChecker-B4K46m5w.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-ZGzN1cU8.js → useFlexGapSupport-DoIVpJNq.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-CfP7M1m7.js → useFs-DdAWi-ln.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-CbIPHgNh.js → usePersonalDataHub-iUu6k8dx.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-7xh0If_O.js → vnode-DZz4R6Tw.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-DmitvTR2.js → zoom-DXlf9sA3.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agenda.js +15 -1
  151. package/src/commands/agent.js +7 -0
  152. package/src/commands/background-session.js +16 -2
  153. package/src/commands/review.js +6 -1
  154. package/src/data/changelog.json +12 -2
  155. package/src/gateways/ws/background-agent-protocol.js +88 -14
  156. package/src/lib/agent-authority.js +251 -0
  157. package/src/lib/agents.js +19 -0
  158. package/src/lib/background-agent-phase.js +100 -0
  159. package/src/lib/background-agent-supervisor.js +322 -15
  160. package/src/lib/backpressure-policy.js +105 -0
  161. package/src/lib/capability-negotiation.js +212 -0
  162. package/src/lib/credential-guard.js +11 -0
  163. package/src/lib/credential-proxy.js +182 -0
  164. package/src/lib/docs-drift.js +166 -0
  165. package/src/lib/doctor-checkup.js +76 -0
  166. package/src/lib/event-seq-replay.js +167 -0
  167. package/src/lib/goal-condition-engine.js +369 -0
  168. package/src/lib/headless-manifest.js +13 -0
  169. package/src/lib/hook-event-bus.cjs +0 -0
  170. package/src/lib/ide-context.js +53 -14
  171. package/src/lib/json-schema-output.js +30 -1
  172. package/src/lib/json-schema-validate.js +691 -0
  173. package/src/lib/lsp/__tests__/diagnostics-scheduler.test.js +93 -0
  174. package/src/lib/lsp/diagnostics-scheduler.js +127 -0
  175. package/src/lib/operation-fingerprint.js +95 -0
  176. package/src/lib/plugin-runtime/capabilities.js +371 -0
  177. package/src/lib/plugin-runtime/manifest.js +30 -0
  178. package/src/lib/project-instructions.js +98 -1
  179. package/src/lib/remote-approval-bridge.js +16 -0
  180. package/src/lib/remote-path-mapping.js +229 -0
  181. package/src/lib/review-pipeline.js +227 -0
  182. package/src/lib/runtime-checkup.js +318 -0
  183. package/src/lib/schedule-planner.js +136 -0
  184. package/src/lib/session-lifecycle.js +305 -0
  185. package/src/lib/settings-hook-events.cjs +39 -13
  186. package/src/lib/subagent-contract.js +383 -0
  187. package/src/lib/telemetry-ids.js +190 -0
  188. package/src/lib/turn-binding.js +397 -0
  189. package/src/repl/bg-dashboard.js +13 -12
  190. package/src/runtime/agent-core.js +24 -10
  191. package/src/runtime/headless-stream.js +195 -11
  192. package/src/runtime/system-prompt.js +3 -0
  193. package/src/workers/background-agent-worker.js +37 -2
  194. package/src/assets/web-panel/assets/GenericCardRenderer-D7xRjPjZ.js +0 -1
  195. package/src/assets/web-panel/assets/MobileProjects-BpX5g9It.js +0 -1
  196. package/src/assets/web-panel/assets/Projects-SS9uZKCl.js +0 -1
  197. package/src/assets/web-panel/assets/devWarning-DYEZcY55.js +0 -1
  198. package/src/assets/web-panel/assets/index-DmVFfcNR.js +0 -1
  199. package/src/assets/web-panel/assets/index-x7YyhMUa.js +0 -1
@@ -0,0 +1,383 @@
1
+ /**
2
+ * Subagent contract — the full inheritance/override envelope for a spawned
3
+ * child agent (P1 "补齐 Subagent 契约").
4
+ *
5
+ * `agents.js` already parses tools/disallowedTools/model/maxTurns/isolation and
6
+ * agent-core's `_executeSpawnSubAgent` enforces a depth cap
7
+ * (`MAX_SUB_AGENT_DEPTH`) + a shared breadth counter. This module adds the REST
8
+ * of the Claude-Code subagent contract — `permissionMode`, `skills`,
9
+ * `mcpServers`, `hooks`, `memory`, `effort`, `background`,
10
+ * `context (fresh|fork)`, `maxDepth`/`maxChildren` and token/cost/time budgets —
11
+ * as PURE, deterministic functions (no timers, no RNG, no I/O):
12
+ *
13
+ * normalizeSubagentContract(raw) validate + canonicalize one declaration
14
+ * resolveSubagentContract(...) merge parent ⊕ definition ⊕ spawnArgs with
15
+ * SECURITY INVARIANTS
16
+ * enforceRecursionLimits(...) fail-closed depth / breadth guard
17
+ * resolveIsolationFailClosed(...) worktree isolation must fail, not fall back
18
+ *
19
+ * Security invariants (mirrors [[agent-authority.js]] — never trust the child to
20
+ * de-escalate the parent's guardrails; a child may only ever be EQUAL OR MORE
21
+ * RESTRICTIVE than its parent, never widen):
22
+ * - `permissionMode` is TIGHTEN-ONLY: a child requesting a more permissive
23
+ * mode than its parent is clamped to the parent's mode.
24
+ * - `skills`/`mcpServers`/`hooks` are INTERSECTED with the parent's set (the
25
+ * parent list is the ceiling; a child can never gain a capability the
26
+ * parent lacked). `context: fresh` withholds them entirely; only `fork`
27
+ * inherits when the child is silent.
28
+ * - `memory` access can be inherited on fork but never granted if the parent
29
+ * explicitly denied it.
30
+ * - every budget field (tokens/cost/time) is capped at the parent's REMAINING.
31
+ * - `maxDepth`/`maxChildren` are ceilings a child may lower but never raise.
32
+ *
33
+ * These are POLICY functions. The spawn path (`_executeSpawnSubAgent`) and the
34
+ * agent-file parser (`agents.js`) feed them; deeper runtime application of the
35
+ * resolved fields (e.g. threading `permissionMode`/`effort` into the child
36
+ * agentLoop) is wired incrementally.
37
+ */
38
+
39
+ // Public → canonical permission modes, ordered by PERMISSIVENESS (see rank).
40
+ // Kept in sync with headless-runner's VALID_PERMISSION_MODES.
41
+ export const SUBAGENT_PERMISSION_MODES = Object.freeze([
42
+ "plan",
43
+ "manual",
44
+ "default",
45
+ "acceptEdits",
46
+ "auto",
47
+ "dontAsk",
48
+ "bypassPermissions",
49
+ ]);
50
+
51
+ // Higher = more powerful. `plan` is read-only (most restrictive);
52
+ // `bypassPermissions` skips every gate (most permissive). `auto` and `dontAsk`
53
+ // share a tier (both auto-approve). Used only for tighten-only clamping.
54
+ const PERMISSION_RANK = Object.freeze({
55
+ plan: 0,
56
+ manual: 1,
57
+ default: 2,
58
+ acceptEdits: 3,
59
+ auto: 4,
60
+ dontAsk: 4,
61
+ bypassPermissions: 5,
62
+ });
63
+
64
+ export const SUBAGENT_CONTEXT_MODES = Object.freeze(["fresh", "fork"]);
65
+
66
+ // Reasoning-effort levels (matches the REPL `/effort` command's vocabulary).
67
+ export const SUBAGENT_EFFORT_LEVELS = Object.freeze([
68
+ "low",
69
+ "medium",
70
+ "high",
71
+ "xhigh",
72
+ ]);
73
+ const EFFORT_SYNONYMS = Object.freeze({ med: "medium", max: "xhigh" });
74
+
75
+ // ─── small pure helpers ─────────────────────────────────────────────────────
76
+
77
+ function toBool(v) {
78
+ if (v === true) return true;
79
+ if (v === false) return false;
80
+ const s = String(v).trim().toLowerCase();
81
+ if (s === "true" || s === "1" || s === "yes" || s === "on") return true;
82
+ if (s === "false" || s === "0" || s === "no" || s === "off") return false;
83
+ return null; // unrecognized → unspecified
84
+ }
85
+
86
+ function toPosInt(v) {
87
+ const n = Number(v);
88
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
89
+ }
90
+
91
+ function toPosNum(v) {
92
+ const n = Number(v);
93
+ return Number.isFinite(n) && n > 0 ? n : null;
94
+ }
95
+
96
+ /** The smaller of two ceilings, treating null as "no limit from that source". */
97
+ function minNullable(a, b) {
98
+ if (a == null) return b == null ? null : b;
99
+ if (b == null) return a;
100
+ return Math.min(a, b);
101
+ }
102
+
103
+ /**
104
+ * Normalize a capability list (skills/mcpServers/hooks). `null`/`undefined` →
105
+ * `null` (UNSPECIFIED — inherit per context). A string or array → a trimmed,
106
+ * de-duplicated `string[]` (possibly `[]`, meaning EXPLICITLY NONE).
107
+ */
108
+ export function normalizeCapabilityList(v) {
109
+ if (v == null) return null;
110
+ const list = Array.isArray(v) ? v : String(v).split(/[,\s]+/);
111
+ const out = [];
112
+ const seen = new Set();
113
+ for (const item of list) {
114
+ const s = String(item).trim();
115
+ if (!s || seen.has(s)) continue;
116
+ seen.add(s);
117
+ out.push(s);
118
+ }
119
+ return out; // may be [] (explicit empty)
120
+ }
121
+
122
+ /** Normalize a `{tokens, cost/costUsd, time/timeMs}` budget → canonical or null. */
123
+ export function normalizeBudget(raw) {
124
+ if (!raw || typeof raw !== "object") return null;
125
+ const tokens = toPosNum(raw.tokens);
126
+ const costUsd = toPosNum(raw.costUsd ?? raw.cost);
127
+ const timeMs = toPosNum(raw.timeMs ?? raw.time);
128
+ if (tokens == null && costUsd == null && timeMs == null) return null;
129
+ return { tokens, costUsd, timeMs };
130
+ }
131
+
132
+ /** Permissiveness rank of a mode (unknown → `default`'s rank). */
133
+ export function permissionRank(mode) {
134
+ return PERMISSION_RANK[mode] ?? PERMISSION_RANK.default;
135
+ }
136
+
137
+ /**
138
+ * TIGHTEN-ONLY permission resolution. The child's exact choice is honored when
139
+ * it is equal-or-more-restrictive than the parent; a widening request (or an
140
+ * invalid/absent one) clamps to the parent's mode. A child can never escalate.
141
+ */
142
+ export function tightenPermissionMode(parentMode, requestedMode) {
143
+ const parent = SUBAGENT_PERMISSION_MODES.includes(parentMode)
144
+ ? parentMode
145
+ : "default";
146
+ if (requestedMode == null) return parent; // silent child → inherit
147
+ if (!SUBAGENT_PERMISSION_MODES.includes(requestedMode)) return parent; // invalid → fail-safe
148
+ return permissionRank(requestedMode) <= permissionRank(parent)
149
+ ? requestedMode
150
+ : parent;
151
+ }
152
+
153
+ // ─── normalize one declaration ──────────────────────────────────────────────
154
+
155
+ /**
156
+ * Validate + canonicalize a raw contract declaration (agent-file frontmatter or
157
+ * spawn args). Unknown/invalid values are DROPPED (→ null), never thrown, so a
158
+ * malformed field degrades to "unspecified" rather than breaking the spawn.
159
+ *
160
+ * Every field is nullable: `null` means UNSPECIFIED (defer to inheritance),
161
+ * distinct from an explicit value like `false` or `[]`.
162
+ */
163
+ export function normalizeSubagentContract(raw = {}) {
164
+ const r = raw && typeof raw === "object" ? raw : {};
165
+
166
+ const permissionMode = SUBAGENT_PERMISSION_MODES.includes(r.permissionMode)
167
+ ? r.permissionMode
168
+ : null;
169
+
170
+ const context = SUBAGENT_CONTEXT_MODES.includes(r.context) ? r.context : null;
171
+
172
+ let effort = null;
173
+ if (r.effort != null) {
174
+ const e = String(r.effort).trim().toLowerCase();
175
+ const canon = EFFORT_SYNONYMS[e] || e;
176
+ if (SUBAGENT_EFFORT_LEVELS.includes(canon)) effort = canon;
177
+ }
178
+
179
+ return {
180
+ permissionMode,
181
+ skills: normalizeCapabilityList(r.skills),
182
+ mcpServers: normalizeCapabilityList(r.mcpServers),
183
+ hooks: normalizeCapabilityList(r.hooks),
184
+ memory: r.memory == null ? null : toBool(r.memory),
185
+ effort,
186
+ background: r.background == null ? null : toBool(r.background),
187
+ context,
188
+ maxDepth: toPosInt(r.maxDepth),
189
+ maxChildren: toPosInt(r.maxChildren),
190
+ budget: normalizeBudget(r.budget),
191
+ };
192
+ }
193
+
194
+ // ─── resolve inheritance / override ─────────────────────────────────────────
195
+
196
+ /** spawnArgs ⊕ definition — the child's REQUESTED value for one field. */
197
+ function requested(field, definition, spawnArgs) {
198
+ const s = spawnArgs ? spawnArgs[field] : undefined;
199
+ if (s != null) return s;
200
+ const d = definition ? definition[field] : undefined;
201
+ return d != null ? d : null;
202
+ }
203
+
204
+ /**
205
+ * Resolve a capability list (skills/mcp/hooks) with tighten-only semantics.
206
+ * The parent list is the CEILING (null = "all available", no restriction).
207
+ * - explicit child list → INTERSECT with the parent ceiling.
208
+ * - silent child → inherit the parent list on `fork`; `[]` (none) on `fresh`.
209
+ */
210
+ function resolveCapability(parentList, reqList, context) {
211
+ if (reqList != null) {
212
+ if (parentList == null) return reqList; // parent unrestricted → child's list as-is
213
+ const allowed = new Set(parentList);
214
+ return reqList.filter((x) => allowed.has(x)); // never gain what parent lacked
215
+ }
216
+ // silent child
217
+ if (context === "fork") return parentList == null ? null : [...parentList];
218
+ return []; // fresh → no inherited capabilities
219
+ }
220
+
221
+ /** Cap a requested budget at the parent's remaining (per field; null=unlimited). */
222
+ export function capBudget(requestedBudget, parentRemaining) {
223
+ const req = requestedBudget || null;
224
+ const rem = parentRemaining || null;
225
+ if (!req && !rem) return null;
226
+ const cap = (a, b) => {
227
+ if (a == null) return b == null ? null : b;
228
+ if (b == null) return a;
229
+ return Math.min(a, b);
230
+ };
231
+ const tokens = cap(req?.tokens ?? null, rem?.tokens ?? null);
232
+ const costUsd = cap(req?.costUsd ?? null, rem?.costUsd ?? null);
233
+ const timeMs = cap(req?.timeMs ?? null, rem?.timeMs ?? null);
234
+ if (tokens == null && costUsd == null && timeMs == null) return null;
235
+ return { tokens, costUsd, timeMs };
236
+ }
237
+
238
+ /**
239
+ * Merge a parent's effective contract with the child's agent-file definition
240
+ * and per-spawn args into the child's EFFECTIVE contract, applying every
241
+ * security invariant. Precedence for the raw request: spawnArgs > definition;
242
+ * the parent then acts as the ceiling.
243
+ *
244
+ * @param {object} opts
245
+ * @param {object} [opts.parent] parent's EFFECTIVE contract, plus
246
+ * `budgetRemaining` ({tokens,costUsd,timeMs}) if the run tracks it.
247
+ * @param {object} [opts.definition] normalized agent-file contract (or null).
248
+ * @param {object} [opts.spawnArgs] normalized per-spawn contract (or null).
249
+ * @returns {object} effective contract (all fields resolved, never null-widening)
250
+ */
251
+ export function resolveSubagentContract({
252
+ parent = {},
253
+ definition = null,
254
+ spawnArgs = null,
255
+ } = {}) {
256
+ const p = parent && typeof parent === "object" ? parent : {};
257
+
258
+ // context first — it governs how silent capability/memory fields inherit.
259
+ const context =
260
+ requested("context", definition, spawnArgs) ||
261
+ (SUBAGENT_CONTEXT_MODES.includes(p.context) ? p.context : "fresh");
262
+
263
+ const permissionMode = tightenPermissionMode(
264
+ p.permissionMode,
265
+ requested("permissionMode", definition, spawnArgs),
266
+ );
267
+
268
+ // effort is compute, not authority — inherit when the child is silent.
269
+ const effort = requested("effort", definition, spawnArgs) ?? p.effort ?? null;
270
+
271
+ const skills = resolveCapability(
272
+ p.skills ?? null,
273
+ requested("skills", definition, spawnArgs),
274
+ context,
275
+ );
276
+ const mcpServers = resolveCapability(
277
+ p.mcpServers ?? null,
278
+ requested("mcpServers", definition, spawnArgs),
279
+ context,
280
+ );
281
+ const hooks = resolveCapability(
282
+ p.hooks ?? null,
283
+ requested("hooks", definition, spawnArgs),
284
+ context,
285
+ );
286
+
287
+ // memory: want it explicitly, or inherit on fork if the parent had it; but a
288
+ // parent that explicitly denied memory can never have it granted downstream.
289
+ const memReq = requested("memory", definition, spawnArgs);
290
+ const want =
291
+ memReq != null ? Boolean(memReq) : context === "fork" && p.memory === true;
292
+ const memory = want && p.memory !== false;
293
+
294
+ const backgroundReq = requested("background", definition, spawnArgs);
295
+ const background = backgroundReq != null ? Boolean(backgroundReq) : false;
296
+
297
+ // depth/children ceilings — a child may lower but never raise the inherited cap.
298
+ const maxDepth = minNullable(
299
+ p.maxDepth ?? null,
300
+ requested("maxDepth", definition, spawnArgs),
301
+ );
302
+ const maxChildren = minNullable(
303
+ p.maxChildren ?? null,
304
+ requested("maxChildren", definition, spawnArgs),
305
+ );
306
+
307
+ const budget = capBudget(
308
+ requested("budget", definition, spawnArgs),
309
+ p.budgetRemaining ?? null,
310
+ );
311
+
312
+ return {
313
+ permissionMode,
314
+ context,
315
+ effort,
316
+ skills,
317
+ mcpServers,
318
+ hooks,
319
+ memory,
320
+ background,
321
+ maxDepth,
322
+ maxChildren,
323
+ budget,
324
+ };
325
+ }
326
+
327
+ // ─── recursion guard ────────────────────────────────────────────────────────
328
+
329
+ /**
330
+ * Fail-closed depth / breadth guard. `contract.maxDepth`/`maxChildren` are the
331
+ * child-declared ceilings; `hardDepthCap`/`hardChildrenCap` are the run's
332
+ * absolute backstops. The effective cap is the smaller of the two.
333
+ *
334
+ * @returns {{ok: boolean, reason: string|null}}
335
+ */
336
+ export function enforceRecursionLimits({
337
+ depth = 0,
338
+ spawnedCount = 0,
339
+ contract = {},
340
+ hardDepthCap = null,
341
+ hardChildrenCap = null,
342
+ } = {}) {
343
+ const depthCap = minNullable(contract?.maxDepth ?? null, hardDepthCap);
344
+ if (depthCap != null && Number(depth) >= depthCap) {
345
+ return {
346
+ ok: false,
347
+ reason: `max nesting depth (${depthCap}) reached`,
348
+ };
349
+ }
350
+ const childCap = minNullable(contract?.maxChildren ?? null, hardChildrenCap);
351
+ if (childCap != null && Number(spawnedCount) >= childCap) {
352
+ return {
353
+ ok: false,
354
+ reason: `max sub-agents (${childCap}) reached`,
355
+ };
356
+ }
357
+ return { ok: true, reason: null };
358
+ }
359
+
360
+ /**
361
+ * Worktree isolation must FAIL CLOSED, never silently fall back to the parent
362
+ * checkout. When a contract requests `isolation: worktree` but the environment
363
+ * can't provide one (not a git repo, worktree add failed), the spawn is denied.
364
+ *
365
+ * @returns {{ok: boolean, isolation: string|null, reason: string|null}}
366
+ */
367
+ export function resolveIsolationFailClosed({
368
+ requested: isolationRequested = null,
369
+ available = false,
370
+ } = {}) {
371
+ if (isolationRequested !== "worktree") {
372
+ return { ok: true, isolation: null, reason: null };
373
+ }
374
+ if (!available) {
375
+ return {
376
+ ok: false,
377
+ isolation: "worktree",
378
+ reason:
379
+ "worktree isolation was requested but is unavailable — refusing to run in the parent checkout",
380
+ };
381
+ }
382
+ return { ok: true, isolation: "worktree", reason: null };
383
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Unified OpenTelemetry identifier + attribute normalization (P2 "可观测性").
3
+ *
4
+ * The gap: spans/events across the runtime stamp ad-hoc, inconsistent ids, so a
5
+ * session can't be reassembled across turns / tools / sub-agents / workflows, and
6
+ * nothing guarantees prompt/response/tool-argument CONTENT stays out of telemetry
7
+ * or that label cardinality stays bounded.
8
+ *
9
+ * This is the PURE normalization core. Given a loose context object it emits:
10
+ * - the canonical id set (session/turn/prompt/tool_use/agent/parent_agent/
11
+ * workflow.run_id/permission.decision_id/checkpoint.id) under stable keys,
12
+ * - CONTENT (prompt/response/tool arguments) redacted by default — opt-in only,
13
+ * and even then length-capped,
14
+ * - a bounded attribute map: only allow-listed keys survive, id values are
15
+ * charset-sanitized + length-capped, so a run can't explode label cardinality.
16
+ *
17
+ * Deterministic (no clock/RNG) so it's fully unit-testable; the span recorder
18
+ * layer feeds its raw context through here before building OTLP.
19
+ */
20
+
21
+ /**
22
+ * Canonical id attribute keys, mapped from the many aliases the callers use.
23
+ * The VALUE is the stable OTel attribute key; the KEY list are accepted aliases.
24
+ * Order here is the emit order (stable, low cardinality).
25
+ */
26
+ const ID_FIELDS = Object.freeze([
27
+ { key: "session.id", aliases: ["session.id", "sessionId", "session_id"] },
28
+ { key: "turn.id", aliases: ["turn.id", "turnId", "turn_id"] },
29
+ { key: "prompt.id", aliases: ["prompt.id", "promptId", "prompt_id"] },
30
+ {
31
+ key: "tool_use.id",
32
+ aliases: ["tool_use.id", "toolUseId", "tool_use_id", "toolUse.id"],
33
+ },
34
+ { key: "agent.id", aliases: ["agent.id", "agentId", "agent_id"] },
35
+ {
36
+ key: "parent_agent.id",
37
+ aliases: [
38
+ "parent_agent.id",
39
+ "parentAgentId",
40
+ "parent_agent_id",
41
+ "parentAgent.id",
42
+ ],
43
+ },
44
+ {
45
+ key: "workflow.run_id",
46
+ aliases: ["workflow.run_id", "workflowRunId", "workflow_run_id", "runId"],
47
+ },
48
+ {
49
+ key: "permission.decision_id",
50
+ aliases: [
51
+ "permission.decision_id",
52
+ "permissionDecisionId",
53
+ "permission_decision_id",
54
+ "decisionId",
55
+ ],
56
+ },
57
+ {
58
+ key: "checkpoint.id",
59
+ aliases: ["checkpoint.id", "checkpointId", "checkpoint_id"],
60
+ },
61
+ ]);
62
+
63
+ /** Content fields that must never be emitted unless explicitly opted in. */
64
+ const CONTENT_FIELDS = Object.freeze([
65
+ { key: "prompt", aliases: ["prompt", "promptText", "input"] },
66
+ { key: "response", aliases: ["response", "responseText", "output"] },
67
+ {
68
+ key: "tool_arguments",
69
+ aliases: ["tool_arguments", "toolArguments", "arguments", "args"],
70
+ },
71
+ ]);
72
+
73
+ /** The complete set of allow-listed attribute keys (ids + a few low-card meta). */
74
+ const META_KEYS = Object.freeze([
75
+ "agent.kind",
76
+ "tool.name",
77
+ "workflow.name",
78
+ "permission.decision",
79
+ ]);
80
+
81
+ const MAX_ID_LEN = 128;
82
+ const MAX_CONTENT_LEN = 4096;
83
+ const REDACTED = "[redacted]";
84
+
85
+ function firstAlias(ctx, aliases) {
86
+ for (const a of aliases) {
87
+ if (ctx[a] != null && ctx[a] !== "") return ctx[a];
88
+ }
89
+ return undefined;
90
+ }
91
+
92
+ /**
93
+ * Sanitize an id VALUE to a bounded, low-cardinality-safe token: keep a stable
94
+ * printable charset, strip control/whitespace, cap length. Non-scalars are
95
+ * stringified; empties drop out (caller omits the key).
96
+ */
97
+ export function sanitizeIdValue(value) {
98
+ if (value == null) return undefined;
99
+ let s = typeof value === "string" ? value : String(value);
100
+ // collapse anything outside a conservative id charset to '_'
101
+ s = s.replace(/[^\w.\-:/@]/g, "_").replace(/_+/g, "_");
102
+ s = s.replace(/^_+|_+$/g, "");
103
+ if (!s) return undefined;
104
+ return s.length > MAX_ID_LEN ? s.slice(0, MAX_ID_LEN) : s;
105
+ }
106
+
107
+ /**
108
+ * Redact a content value: default → the redaction sentinel; opt-in → the value
109
+ * stringified and length-capped (never unbounded, to keep exports sane).
110
+ */
111
+ export function redactContent(value, includeContent = false) {
112
+ if (value == null) return undefined;
113
+ if (!includeContent) return REDACTED;
114
+ const s = typeof value === "string" ? value : safeStringify(value);
115
+ return s.length > MAX_CONTENT_LEN
116
+ ? s.slice(0, MAX_CONTENT_LEN) + "…[truncated]"
117
+ : s;
118
+ }
119
+
120
+ function safeStringify(value) {
121
+ try {
122
+ return JSON.stringify(value);
123
+ } catch {
124
+ return String(value);
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Build the canonical, bounded, redacted attribute map for a telemetry context.
130
+ *
131
+ * @param {object} ctx raw context (mixed aliases, may carry content)
132
+ * @param {object} [opts]
133
+ * includeContent — emit prompt/response/tool_arguments (still capped). Default
134
+ * false. This is the ONLY switch that lets content out.
135
+ * @returns {{ [key:string]: string }} attributes keyed by stable OTel names
136
+ */
137
+ export function buildTelemetryAttributes(
138
+ ctx = {},
139
+ { includeContent = false } = {},
140
+ ) {
141
+ const attrs = {};
142
+ for (const { key, aliases } of ID_FIELDS) {
143
+ const raw = firstAlias(ctx, aliases);
144
+ const v = sanitizeIdValue(raw);
145
+ if (v !== undefined) attrs[key] = v;
146
+ }
147
+ for (const key of META_KEYS) {
148
+ const v = sanitizeIdValue(ctx[key]);
149
+ if (v !== undefined) attrs[key] = v;
150
+ }
151
+ for (const { key, aliases } of CONTENT_FIELDS) {
152
+ const raw = firstAlias(ctx, aliases);
153
+ if (raw == null || raw === "") continue;
154
+ const v = redactContent(raw, includeContent);
155
+ if (v !== undefined) attrs[`content.${key}`] = v;
156
+ }
157
+ return attrs;
158
+ }
159
+
160
+ /** The set of attribute keys this normalizer can ever emit (cardinality bound). */
161
+ export function allowedAttributeKeys() {
162
+ return [
163
+ ...ID_FIELDS.map((f) => f.key),
164
+ ...META_KEYS,
165
+ ...CONTENT_FIELDS.map((f) => `content.${f.key}`),
166
+ ];
167
+ }
168
+
169
+ /**
170
+ * Assert an already-built attribute map only uses allow-listed keys and never
171
+ * leaks raw content when content wasn't opted in. Returns violation list (empty
172
+ * = clean) so a recorder can fail-closed in strict mode.
173
+ */
174
+ export function auditAttributes(attrs = {}, { includeContent = false } = {}) {
175
+ const allowed = new Set(allowedAttributeKeys());
176
+ const violations = [];
177
+ for (const [k, v] of Object.entries(attrs)) {
178
+ if (!allowed.has(k)) {
179
+ violations.push({ key: k, reason: "key-not-allowlisted" });
180
+ continue;
181
+ }
182
+ if (k.startsWith("content.") && !includeContent && v !== REDACTED) {
183
+ violations.push({ key: k, reason: "content-not-redacted" });
184
+ }
185
+ }
186
+ return violations;
187
+ }
188
+
189
+ export const TELEMETRY_ID_KEYS = Object.freeze(ID_FIELDS.map((f) => f.key));
190
+ export const TELEMETRY_CONTENT_SENTINEL = REDACTED;