chainlesschain 0.162.166 → 0.162.168

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 (185) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-LbDx7jL6.js → AIOps-CDzzuWZV.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-DnurtK34.js → ActionButton-Dy-8MmQi.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DXSsV1am.js → Analytics-BtJ_VWkx.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-dqRpzNuD.js → AppLayout-DkjiI4VS.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-BwdmYepK.js → Artifacts-h8PyDO86.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-oSX0kOUG.js → Audit-DiDV0C6l.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-C2KxJahT.js → BackgroundAgents-DTw_aOaf.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-B4TkxWkC.js → Backup-CihFuUO9.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-CX5-KxeU.js → BaseInput-CqP2QNoR.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-O_HzD-PL.js → Chat-tm-ygX-p.js} +5 -5
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-Br8saHQA.js → ChatBubbleRenderer-BVKEhj_y.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-ChepDpC1.js → Checkbox-BFMyRN3Q.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-D9z8kc7q.js → Codegen-B1gTeoAS.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-B-H5eoNw.js → Col-1GP8MvKL.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-Ccyp_Kk5.js → Community-BLY938F6.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-BNvBznp_.js → Compact-DVt_p36I.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-D1FjRd8n.js → Compliance-DxqEBXVG.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-CV9B9u-8.js → Cowork-CyWrM-T8.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-BowJ4VIx.js → Cron-Domu6aI9.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-BJc6jzRF.js → Crosschain-C8LKpjHJ.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BEp8NV6D.js → DID-XBy15RZ5.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-Dls0B3YB.js → Dashboard-DDAyPD-V.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-DQ7zXVtD.js → Dropdown-BHV0MZ13.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-B9q79tOl.js → EmailListRenderer-DmFGW4PX.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CD8Ie-88.js → FamilyGuardDashboard-CqYdbKvd.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-5o7XU5Vf.js → Federation-YP5-Puv5.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-DeKbMvOs.js → FormItemContext-BOmcIcTe.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-CZiQvz_z.js → GenericCardRenderer-CvTZdoc4.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-CTvFxVV8.js → Git-Q2oxua0L.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-DwzFIjHt.js → Governance-D-WpyWEU.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-CJBk59c-.js → Inference-NUtsYHQo.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-DfsaXN2z.js → KnowledgeGraph-BHQLa8fr.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-DVRYf18S.js → Logs-6WsR5Q1G.js} +2 -2
  35. package/src/assets/web-panel/assets/MarkdownRenderer-DuWoDwMx.js +1 -0
  36. package/src/assets/web-panel/assets/{Marketplace-BuIwyE4t.js → Marketplace-CigohRRW.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-D3U0o78E.js → McpTools-jcp6jVLf.js} +5 -5
  38. package/src/assets/web-panel/assets/{Memory-QYVmyb3b.js → Memory-B00NU1ph.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-BFgrj6eZ.js → MobileBridge-Cs_p3PzU.js} +1 -1
  40. package/src/assets/web-panel/assets/MobileProjects-BFs2WljZ.js +1 -0
  41. package/src/assets/web-panel/assets/{Mtc-BZQu8TcH.js → Mtc-CSEflwzw.js} +5 -5
  42. package/src/assets/web-panel/assets/{MtcAudit-BnFfEkLo.js → MtcAudit-dxSiEZ96.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-iUnNS9Cm.js → Multisig-CDAFEAyq.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-DNNz1y9Y.js → NLProgramming-HoFpZNiB.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-CZezNYRU.js → Notes-tAoQvVcE.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-DBvrplIR.js → NotificationSettings-Czo2M9F4.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-CTDtee49.js → OrderTableRenderer-CM1kNTp6.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-CCOUJX1R.js → Organization-Cudr0VOC.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-DcZghdk0.js → Overflow-BSXeDKiN.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-jmddWX9q.js → P2P-Cqv_g-F9.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-wfjMcczl.js → PdhVaultBrowser-7rjprPh8.js} +4 -4
  52. package/src/assets/web-panel/assets/{Permissions-DU1GJjca.js → Permissions-97pIDQKE.js} +3 -3
  53. package/src/assets/web-panel/assets/{PersonalDataHub-BzTMzDF9.js → PersonalDataHub-bparK9eQ.js} +3 -3
  54. package/src/assets/web-panel/assets/{Pipeline-DPmjlpVc.js → Pipeline-CJgerM09.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-B6HRLpfK.js → Privacy-JqFS3PN7.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-Cyy86AsN.js → ProjectInit-DMITd-2c.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-Bl_WQxJ5.js → ProjectSettings-1UkdJ8Ii.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-Co9yKThA.js → Projects-CYZqegqw.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-DLa4NlIs.js → Providers-ChfXz3U0.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-BvzM7q5f.js → QrScannerModal-Rzkka-mf.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-BN8yyyUj.js → QuickAsk-DEz9HaON.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-QsVN4w0i.js → Recommend-EFGP8AVu.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-tkDXw-TY.js → RemoteSession-6um0lGGy.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-DAifkGsy.js → Reputation-CA1BXTup.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-D7dZEyak.js → Row-stTnDJ9y.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-DB5sImTA.js → RssFeed-B10qCCoG.js} +2 -2
  67. package/src/assets/web-panel/assets/{Search-3py3TO9V.js → Search-DSJroZ6p.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-DhNUfT_k.js → Security-BpXkwiMW.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-DjJAA_5D.js → Services-BPQxjwK6.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-u95JjK3n.js → Skeleton-CfbKAMul.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-DPo3L0Tz.js → Skills-CttYTfX0.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-DWhkAZV1.js → Sla-ByluHich.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-PUegMtqV.js → SpeechSettings-BdZXKsfV.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-BMNYrnrU.js → SyncSettings-xCR7iDzy.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-CYm_oTJC.js → Tasks-qTko_INn.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-Dw9Er5DN.js → Templates-CkIzhgDJ.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-7P3QIBSo.js → Tenant-BIaCaaK8.js} +1 -1
  78. package/src/assets/web-panel/assets/Terminal-B2Qq38XP.js +3 -0
  79. package/src/assets/web-panel/assets/{TimelineRenderer-DbCd1bDH.js → TimelineRenderer-D5eQDnEF.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-EQofpA0t.js → Tokens-DmeJ8NMQ.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-ChNLLypd.js → Trigger-C5sgb3C7.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-dlVGUKs5.js → Trust-H2vOb3Rb.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-BXyBUhNF.js → UkeySign-NmPtHK2u.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-BE8-68ge.js → VideoEditing-DHv-Ti7K.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-7ZIJOwNg.js → Wallet-BYZ3-PRP.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-DgNd2Amr.js → WebAuthn-BaSlP7om.js} +4 -4
  87. package/src/assets/web-panel/assets/{WorkflowEditor-Z-Un-9l5.js → WorkflowEditor-CZ7IHuIi.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-CtVDEkUc.js → chat-Dd9kY8gn.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-PXtJ2f1B.js → colors-yhzo6QSV.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-oN96nuBi.js → compact-item-C1JneVdM.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-okCJXQXa.js → createContext-C_waVCg2.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-DaDlyVDY.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-pcHm--4o.js → hasIn-CkVg0Xnh.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-BeAWCGEK.js → index-1VYhIr6D.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-DC0bqrqV.js → index-BTMiVIBI.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-CQYes2mk.js → index-BYOyb5X2.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-vW0lwj0Y.js → index-BjDnI7oM.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BUUJ2WfG.js → index-BlJNvg-2.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-C0rfJBU3.js → index-BlzbSUK9.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Rk0X8lVz.js → index-BrN2hAeE.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BDR2mi1p.js → index-BsSsurcn.js} +1 -1
  102. package/src/assets/web-panel/assets/index-BtsLExcc.js +1 -0
  103. package/src/assets/web-panel/assets/{index-jAY55MSx.js → index-Bwuu3Bf3.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-_SaFHNsG.js → index-Bx3A6X4J.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-T3QE8LuV.js → index-C-Sscgnv.js} +1 -1
  106. package/src/assets/web-panel/assets/index-C3qDvvIb.js +1 -0
  107. package/src/assets/web-panel/assets/{index-BUTyx-Lk.js → index-C4HNdMCt.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-nMqLdwao.js → index-CBwmwTNa.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BuME9k4Y.js → index-CErewFeT.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BHJIfdCV.js → index-CGtoL5mR.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-D0bfypsF.js → index-CUDjGt_V.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Bl6N7ZyX.js → index-C_qqYZT7.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CXhezK4d.js → index-C_riAa6d.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BnhHxZul.js → index-CfEMjbhu.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-Cs1fGk1Z.js → index-ClQhh6v3.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-BjVIZfdA.js → index-CmiLmG0i.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CWUJlCvK.js → index-D1xIbAxQ.js} +3 -3
  118. package/src/assets/web-panel/assets/{index-DfL10EcN.js → index-D6UXDH2D.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-C4ravjdv.js → index-D7kQ_4uL.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-DtsyPnF5.js → index-DPkqQ2Pr.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CQ1ZzJjN.js → index-DUA96wEj.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-5aO2V0V4.js → index-DXHk9Vof.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CopGaCI_.js → index-DapftLUr.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-D1efSh1X.js → index-Dez7TUgG.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DBcKBb1E.js → index-DhZgULsY.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-Bht9n8Sm.js → index-Diquc7-l.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CAQktO-6.js → index-DuGtLiTW.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CK0H3pQW.js → index-U_nLXGIx.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BZKk4Lnm.js → index-_M8El5iz.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Cc_MkksW.js → index-_VmzCYFn.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-mB1UV7wA.js → index-_rZxgFtc.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-BEtzqJw3.js → index-ewb0-LoH.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-CG1jyiI7.js → initDefaultProps-DNN6qtGa.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-C8BYa98D.js → motion-BcM9exeo.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-D1hqwitW.js → move-DsKJ-YvA.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-C2lol8rp.js → mtc-parser-glgGkxaK.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-Ct6Zghfi.js → omit-BwHCU50S.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-CSYbYfdc.js → pickAttrs-BVp-bLt7.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-WCk8di0z.js → placementArrow-CZNe0r-w.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-CEC8CZ-3.js → responsiveObserve-DUafj9pU.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-BsXyd7Lq.js → slide-H4NqXQxu.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-tDKldiUT.js → statusUtils-UjMzzGZ5.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-BIS2unhx.js → styleChecker-qO5aCjyk.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-yFTVMDUv.js → useFlexGapSupport-ChecYwC8.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-CZYbJwkO.js → useFs-BR0QvwO3.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-sbEHRLAM.js → usePersonalDataHub-DitK2Qgz.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-BVjeSEQK.js → vnode-dxSb40dY.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-BZKVvEZ6.js → zoom-ekJk8YB2.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agent.js +75 -0
  151. package/src/commands/session.js +132 -0
  152. package/src/data/changelog.json +8 -2
  153. package/src/harness/jsonl-session-store.js +9 -0
  154. package/src/harness/session-mirror.js +212 -6
  155. package/src/harness/worktree-isolator.js +10 -2
  156. package/src/lib/agent-core.js +1 -0
  157. package/src/lib/async-hook-queue.cjs +288 -0
  158. package/src/lib/async-hook-supervisor.cjs +52 -0
  159. package/src/lib/background-phase-reporter.js +116 -0
  160. package/src/lib/doctor-checkup.js +184 -4
  161. package/src/lib/hook-runner.cjs +248 -36
  162. package/src/lib/install-command-policy.js +343 -0
  163. package/src/lib/jsonl-session-store.js +1 -0
  164. package/src/lib/lsp/__tests__/lsp-server-registry.test.js +27 -0
  165. package/src/lib/lsp/code-intelligence.js +5 -1
  166. package/src/lib/lsp/lsp-server-registry.js +14 -0
  167. package/src/lib/lsp/workspace-roots.js +115 -0
  168. package/src/lib/runtime-checkup.js +33 -0
  169. package/src/lib/settings-hook-events.cjs +173 -3
  170. package/src/lib/settings-hooks.cjs +4 -0
  171. package/src/lib/shell-approval.js +47 -1
  172. package/src/lib/sub-agent-context.js +13 -0
  173. package/src/repl/add-dir.js +59 -1
  174. package/src/repl/agent-repl.js +85 -3
  175. package/src/runtime/agent-core.js +411 -82
  176. package/src/runtime/headless-runner.js +111 -9
  177. package/src/runtime/headless-stream.js +39 -0
  178. package/src/runtime/system-prompt.js +39 -12
  179. package/src/workers/background-agent-worker.js +8 -0
  180. package/src/assets/web-panel/assets/MarkdownRenderer-BL_WsWZM.js +0 -1
  181. package/src/assets/web-panel/assets/MobileProjects-Bp5QB-46.js +0 -1
  182. package/src/assets/web-panel/assets/Terminal-ClPrfWW7.js +0 -3
  183. package/src/assets/web-panel/assets/devWarning-BzV6dk1B.js +0 -1
  184. package/src/assets/web-panel/assets/index-BFrAwXHp.js +0 -1
  185. package/src/assets/web-panel/assets/index-DJQFP47u.js +0 -1
@@ -28,6 +28,11 @@ import { mergeProviderOptions } from "../lib/provider-options.js";
28
28
  import { applyCredentialProxy } from "../lib/credential-proxy.js";
29
29
  import { describeBackgroundCommand } from "../lib/terminal-context.js";
30
30
  import { buildTelemetryAttributes } from "../lib/telemetry-ids.js";
31
+ import {
32
+ workspaceRootsFor,
33
+ pickRootForFile,
34
+ mergeWorkspaceSymbolResults,
35
+ } from "../lib/lsp/workspace-roots.js";
31
36
  import { getPlanModeManager } from "../lib/plan-mode.js";
32
37
  import { CLISkillLoader } from "../lib/skill-loader.js";
33
38
  import { executeHooks, HookEvents } from "../lib/hook-manager.js";
@@ -91,9 +96,14 @@ const { isDangerousGitCommand, isReadOnlyGitCommand, normalizeGitCommand } =
91
96
  const { evaluateShellCommandPolicy } = sharedShellPolicy;
92
97
  const { evaluatePermissionRules } = sharedPermissionRules;
93
98
  const { collectHooks, umbrellaFor } = sharedSettingsHooks;
94
- const { runHooks: runCommandHooks } = sharedHookRunner;
95
- const { runObserveHooks, aggregateContext, partitionAsyncHooks } =
96
- sharedHookEvents;
99
+ const { runHooks: runCommandHooks, runHooksParallel: runCommandHooksParallel } =
100
+ sharedHookRunner;
101
+ const {
102
+ runObserveHooks,
103
+ aggregateContext,
104
+ partitionAsyncHooks,
105
+ withDeliveryId,
106
+ } = sharedHookEvents;
97
107
 
98
108
  // ─── Background shell tasks ────────────────────────────────────────────────
99
109
  //
@@ -409,6 +419,19 @@ function _resolveShellTimeout(raw) {
409
419
  return Math.min(Math.floor(n), MAX_SHELL_TIMEOUT_MS);
410
420
  }
411
421
 
422
+ /**
423
+ * Opt-in: run ALL matching decision hooks and take the STRICTEST outcome
424
+ * (block > ask > allow > continue) instead of the default in-order first-wins
425
+ * short-circuit — so an earlier hook's `ask` can no longer mask a later hook's
426
+ * `block`. Default off = byte-identical short-circuit behavior. Flipping the
427
+ * default is a product decision (all matching hooks then run every time, which
428
+ * changes hook-execution volume + side effects).
429
+ */
430
+ function _hookStrictMergeEnabled() {
431
+ const v = process.env.CC_HOOK_STRICT_MERGE;
432
+ return v === "1" || v === "true";
433
+ }
434
+
412
435
  /**
413
436
  * Run settings.json `PreToolUse` hooks (decision-capable). DB hooks are handled
414
437
  * separately + stay observe-only. A `block` decision stops the tool; an `ask`
@@ -419,18 +442,36 @@ function _resolveShellTimeout(raw) {
419
442
  async function runSettingsPreToolUseHooks(name, args, context, cwd) {
420
443
  const matched = collectHooks(context.settingsHooks, "PreToolUse", name);
421
444
  if (!matched || matched.length === 0) return { blocked: false };
422
- const payload = {
423
- hook_event_name: "PreToolUse",
424
- tool_name: umbrellaFor(name),
425
- raw_tool_name: name,
426
- tool_input: args,
427
- cwd,
428
- session_id: context.sessionId || null,
429
- };
430
- const outcome = runCommandHooks(matched, payload, {
431
- cwd,
432
- event: "PreToolUse",
433
- });
445
+ // Unified-bus envelope (P2): stamps event_id + threads trace_id (this run) /
446
+ // parent_id (spawning run) from the loop context. Additive fields — a hook
447
+ // that ignores them is unaffected; absent context omits them entirely.
448
+ const payload = withDeliveryId(
449
+ "PreToolUse",
450
+ {
451
+ hook_event_name: "PreToolUse",
452
+ tool_name: umbrellaFor(name),
453
+ raw_tool_name: name,
454
+ tool_input: args,
455
+ cwd,
456
+ session_id: context.sessionId || null,
457
+ },
458
+ {
459
+ sessionId: context.sessionId || null,
460
+ traceId: context.hookTraceId || null,
461
+ parentId: context.hookParentId || null,
462
+ },
463
+ );
464
+ // Strict merge (opt-in) runs ALL matching hooks and takes the strictest
465
+ // decision. This path is async so the hooks run in TRUE PARALLEL
466
+ // (runHooksParallel) — wall-clock is the slowest hook, not their sum — while
467
+ // still yielding the same strictest outcome. Default path is the sync in-order
468
+ // short-circuit runner (byte-identical).
469
+ const outcome = _hookStrictMergeEnabled()
470
+ ? await runCommandHooksParallel(matched, payload, {
471
+ cwd,
472
+ event: "PreToolUse",
473
+ })
474
+ : runCommandHooks(matched, payload, { cwd, event: "PreToolUse" });
434
475
  if (outcome.decision === "block") {
435
476
  return { blocked: true, reason: outcome.reason, hook: outcome.hook };
436
477
  }
@@ -490,18 +531,27 @@ function runSettingsPermissionRequestHooks(name, args, context, cwd, reason) {
490
531
  name,
491
532
  );
492
533
  if (!matched || matched.length === 0) return { decision: null };
493
- const payload = {
494
- hook_event_name: "PermissionRequest",
495
- tool_name: umbrellaFor(name),
496
- raw_tool_name: name,
497
- tool_input: args,
498
- permission_reason: reason || null,
499
- cwd,
500
- session_id: context.sessionId || null,
501
- };
534
+ const payload = withDeliveryId(
535
+ "PermissionRequest",
536
+ {
537
+ hook_event_name: "PermissionRequest",
538
+ tool_name: umbrellaFor(name),
539
+ raw_tool_name: name,
540
+ tool_input: args,
541
+ permission_reason: reason || null,
542
+ cwd,
543
+ session_id: context.sessionId || null,
544
+ },
545
+ {
546
+ sessionId: context.sessionId || null,
547
+ traceId: context.hookTraceId || null,
548
+ parentId: context.hookParentId || null,
549
+ },
550
+ );
502
551
  const outcome = runCommandHooks(matched, payload, {
503
552
  cwd,
504
553
  event: "PermissionRequest",
554
+ mergeStrict: _hookStrictMergeEnabled(),
505
555
  });
506
556
  // Precedence: deny > ask > allow > defer. The runner normalizes deny/block →
507
557
  // "block" and short-circuits block/ask, but it COLLAPSES an "allow" into the
@@ -726,6 +776,7 @@ Key behaviors:
726
776
  - When asked about files or code, use read_file and search_files to find information
727
777
  - Before renaming or changing a symbol, use code_intelligence (action: references/definition) to find every real usage instead of guessing with text search. It degrades to "unavailable" when no language server is installed — fall back to search_files then.
728
778
  - After an edit, if the tool result includes a "newDiagnostics" array, you just introduced (or exposed) those errors/warnings — read them and fix before moving on. You can also run code_intelligence (action: diagnostics) on any file to check it on demand.
779
+ - If a tool result includes a "subtreeInstructions" array, you just entered a subdirectory that carries its own cc.md/CLAUDE.md/AGENTS.md — treat that content as authoritative project rules for work in that subtree (it is injected once, the first time you touch the subtree).
729
780
  - You have multi-layer skills (built-in, marketplace, global, project-level) — use list_skills to discover them and run_skill to execute them
730
781
  - Always explain what you're doing and show results
731
782
  - Be concise but thorough
@@ -1642,18 +1693,26 @@ export async function executeTool(name, args, context = {}) {
1642
1693
  // ran synchronously here, defeating fire-and-forget (a background lint /
1643
1694
  // test after each edit would stall the turn).
1644
1695
  const { sync, async: asyncHooks } = partitionAsyncHooks(matched);
1645
- const payload = {
1646
- hook_event_name: "PostToolUse",
1647
- tool_name: umbrellaFor(name),
1648
- raw_tool_name: name,
1649
- tool_input: args,
1650
- tool_response:
1651
- typeof toolResult === "object"
1652
- ? JSON.stringify(toolResult).substring(0, 2000)
1653
- : String(toolResult).substring(0, 2000),
1654
- cwd,
1655
- session_id: context.sessionId || null,
1656
- };
1696
+ const payload = withDeliveryId(
1697
+ "PostToolUse",
1698
+ {
1699
+ hook_event_name: "PostToolUse",
1700
+ tool_name: umbrellaFor(name),
1701
+ raw_tool_name: name,
1702
+ tool_input: args,
1703
+ tool_response:
1704
+ typeof toolResult === "object"
1705
+ ? JSON.stringify(toolResult).substring(0, 2000)
1706
+ : String(toolResult).substring(0, 2000),
1707
+ cwd,
1708
+ session_id: context.sessionId || null,
1709
+ },
1710
+ {
1711
+ sessionId: context.sessionId || null,
1712
+ traceId: context.hookTraceId || null,
1713
+ parentId: context.hookParentId || null,
1714
+ },
1715
+ );
1657
1716
  if (sync.length > 0) {
1658
1717
  const outcome = runCommandHooks(sync, payload, {
1659
1718
  cwd,
@@ -1700,7 +1759,11 @@ export async function executeTool(name, args, context = {}) {
1700
1759
  ? JSON.stringify(toolResult).substring(0, 2000)
1701
1760
  : String(toolResult).substring(0, 2000),
1702
1761
  },
1703
- { cwd },
1762
+ {
1763
+ cwd,
1764
+ traceId: context.hookTraceId || null,
1765
+ parentId: context.hookParentId || null,
1766
+ },
1704
1767
  );
1705
1768
  if (outcome.decision === "block" && outcome.reason) {
1706
1769
  toolResult.hookFeedback = toolResult.hookFeedback
@@ -2056,6 +2119,21 @@ const _codeIntelPool = new Map(); // root -> { ci, idleTimer }
2056
2119
  const CODE_INTEL_IDLE_MS = 60_000;
2057
2120
  let _codeIntelExitHooked = false;
2058
2121
 
2122
+ // Restart backoff between language-server re-spawns (P2 LSP): DEFAULT ON at a
2123
+ // 1s base (1s→2s→4s→8s cap, see lsp-manager) so a server that crashes on
2124
+ // startup can't burst `maxRestarts` spawns back-to-back before quarantine.
2125
+ // `CC_LSP_RESTART_BACKOFF_MS` overrides the base; 0 restores the legacy
2126
+ // immediate-respawn behavior.
2127
+ const DEFAULT_LSP_RESTART_BACKOFF_MS = 1000;
2128
+ function _lspRestartBackoffBaseMs() {
2129
+ const raw = process.env.CC_LSP_RESTART_BACKOFF_MS;
2130
+ if (raw !== undefined && raw !== "") {
2131
+ const n = Number(raw);
2132
+ if (Number.isFinite(n)) return Math.max(0, n);
2133
+ }
2134
+ return DEFAULT_LSP_RESTART_BACKOFF_MS;
2135
+ }
2136
+
2059
2137
  export async function _getSharedCodeIntel(cwd) {
2060
2138
  const root = path.resolve(cwd || process.cwd());
2061
2139
  let entry = _codeIntelPool.get(root);
@@ -2075,7 +2153,11 @@ export async function _getSharedCodeIntel(cwd) {
2075
2153
  entry = _codeIntelPool.get(root);
2076
2154
  if (!entry) {
2077
2155
  entry = {
2078
- ci: new CodeIntelligence({ projectRoot: root, coldStart: true }),
2156
+ ci: new CodeIntelligence({
2157
+ projectRoot: root,
2158
+ coldStart: true,
2159
+ lspOptions: { restartBackoffBaseMs: _lspRestartBackoffBaseMs() },
2160
+ }),
2079
2161
  idleTimer: null,
2080
2162
  };
2081
2163
  _codeIntelPool.set(root, entry);
@@ -2152,7 +2234,17 @@ async function _postEditDiagnostics(filePath, cwd) {
2152
2234
  const diags = (res.diagnostics || []).filter(
2153
2235
  (d) => d.severity === "error" || d.severity === "warning",
2154
2236
  );
2155
- return diags.length ? diags.slice(0, 20) : null;
2237
+ if (!diags.length) return null;
2238
+ // Bound by a TOKEN budget, severity-prioritized (errors survive over
2239
+ // warnings), replacing the arbitrary unsorted count-cap of 20. Guarantees
2240
+ // the model sees the most severe diagnostics first and can't be flooded by
2241
+ // a pathological diagnostics dump. Budget is overridable via
2242
+ // CC_EDIT_DIAGNOSTICS_TOKENS (default 2000).
2243
+ const { capDiagnostics } =
2244
+ await import("../lib/lsp/diagnostics-scheduler.js");
2245
+ const budget = Number(process.env.CC_EDIT_DIAGNOSTICS_TOKENS) || 2000;
2246
+ const { kept } = capDiagnostics(diags, { maxTokens: budget });
2247
+ return kept.length ? kept : null;
2156
2248
  } catch {
2157
2249
  return null;
2158
2250
  }
@@ -2176,6 +2268,96 @@ async function _withPostEditDiagnostics(result, filePath, cwd) {
2176
2268
  return newDiagnostics ? { ...result, newDiagnostics } : result;
2177
2269
  }
2178
2270
 
2271
+ // ─── Lazy subtree instruction injection (large-monorepo lever) ─────────────
2272
+ //
2273
+ // cc.md / CLAUDE.md / AGENTS.md that sit BELOW the startup cwd are intentionally
2274
+ // NOT loaded up front — they cost tokens for subtrees a run may never touch.
2275
+ // When a tool first ACCESSES a path inside such a subtree, we inject that
2276
+ // subtree's directory instructions into the tool result (the SAME channel as
2277
+ // newDiagnostics), exactly once per subtree per process. A stateful
2278
+ // SubtreeInstructionLoader per root remembers what it already injected so a
2279
+ // second access to the same subtree is a no-op. Disable with
2280
+ // CC_SUBTREE_INSTRUCTIONS=0. Common case (no cc.md below cwd) → zero cost.
2281
+ const _subtreeLoaderPool = new Map(); // root -> SubtreeInstructionLoader
2282
+
2283
+ async function _getSubtreeLoader(cwd) {
2284
+ const root = path.resolve(cwd || process.cwd());
2285
+ let loader = _subtreeLoaderPool.get(root);
2286
+ if (!loader) {
2287
+ const { SubtreeInstructionLoader } =
2288
+ await import("../lib/project-instructions.js");
2289
+ let instructionExcludes;
2290
+ try {
2291
+ const { readStringArraySetting } =
2292
+ await import("../lib/settings-loader.cjs");
2293
+ instructionExcludes = readStringArraySetting("instructionExcludes", {
2294
+ cwd: root,
2295
+ });
2296
+ } catch {
2297
+ instructionExcludes = undefined; // fail-open — honor no excludes
2298
+ }
2299
+ loader = new SubtreeInstructionLoader({
2300
+ repoRoot: root,
2301
+ baseDir: root,
2302
+ instructionExcludes,
2303
+ });
2304
+ _subtreeLoaderPool.set(root, loader);
2305
+ }
2306
+ return loader;
2307
+ }
2308
+
2309
+ /** Test/shutdown hook: forget every remembered subtree-injection set. */
2310
+ export function _resetSubtreeInstructionLoaders() {
2311
+ _subtreeLoaderPool.clear();
2312
+ }
2313
+
2314
+ /**
2315
+ * Freshly-discovered subtree instruction files for a path a tool just touched,
2316
+ * with their (capped) content read for inline injection — or null when the
2317
+ * subtree has no NEW cc.md/CLAUDE.md/AGENTS.md (the common case → zero cost).
2318
+ */
2319
+ async function _subtreeInstructionsFor(accessedPath, cwd) {
2320
+ if (process.env.CC_SUBTREE_INSTRUCTIONS === "0") return null;
2321
+ try {
2322
+ const loader = await _getSubtreeLoader(cwd);
2323
+ const fresh = loader.onAccess(accessedPath);
2324
+ if (!fresh || !fresh.length) return null;
2325
+ const { DEFAULT_MAX_FILE_BYTES } =
2326
+ await import("../lib/project-instructions.js");
2327
+ const out = [];
2328
+ for (const f of fresh) {
2329
+ try {
2330
+ const buf = fs.readFileSync(f.path);
2331
+ const truncated = buf.length > DEFAULT_MAX_FILE_BYTES;
2332
+ const content = (
2333
+ truncated ? buf.slice(0, DEFAULT_MAX_FILE_BYTES) : buf
2334
+ ).toString("utf-8");
2335
+ out.push({
2336
+ path: f.path,
2337
+ scope: f.scope || "project",
2338
+ content,
2339
+ ...(truncated ? { truncated: true } : {}),
2340
+ });
2341
+ } catch {
2342
+ // discovered file vanished before we could read it — skip it
2343
+ }
2344
+ }
2345
+ return out.length ? out : null;
2346
+ } catch {
2347
+ return null; // fail-open: never break a tool because injection errored
2348
+ }
2349
+ }
2350
+
2351
+ /**
2352
+ * Attach freshly-discovered subtree instructions to a SUCCESSFUL tool result
2353
+ * (no-op on an error result or when the subtree has nothing new).
2354
+ */
2355
+ async function _withSubtreeInstructions(result, accessedPath, cwd) {
2356
+ if (result && result.error) return result;
2357
+ const subtreeInstructions = await _subtreeInstructionsFor(accessedPath, cwd);
2358
+ return subtreeInstructions ? { ...result, subtreeInstructions } : result;
2359
+ }
2360
+
2179
2361
  /**
2180
2362
  * Drop skills whose `paths:` frontmatter scopes them to a different subtree than
2181
2363
  * the agent's cwd (large-monorepo lazy skill surface). A skill with no `paths`
@@ -2230,6 +2412,9 @@ async function executeToolInner(
2230
2412
  signal = null,
2231
2413
  backgroundSubAgents = null,
2232
2414
  subAgentUsageSink = null,
2415
+ // Hook-envelope tracing: this run's trace id, threaded into child loops
2416
+ // (spawn_sub_agent / isolated run_skill) as their parent_id.
2417
+ hookTraceId = null,
2233
2418
  },
2234
2419
  ) {
2235
2420
  const localToolDescriptor =
@@ -2338,18 +2523,30 @@ async function executeToolInner(
2338
2523
  }
2339
2524
 
2340
2525
  if (rendered.length > 50000) {
2341
- return attachDescriptor({
2342
- content: rendered.substring(0, 50000) + "\n...(truncated)",
2343
- size: rendered.length,
2344
- hashed: args.hashed === true,
2345
- ...(range ? { range } : {}),
2346
- });
2526
+ return attachDescriptor(
2527
+ await _withSubtreeInstructions(
2528
+ {
2529
+ content: rendered.substring(0, 50000) + "\n...(truncated)",
2530
+ size: rendered.length,
2531
+ hashed: args.hashed === true,
2532
+ ...(range ? { range } : {}),
2533
+ },
2534
+ filePath,
2535
+ cwd,
2536
+ ),
2537
+ );
2347
2538
  }
2348
- return attachDescriptor({
2349
- content: rendered,
2350
- hashed: args.hashed === true,
2351
- ...(range ? { range } : {}),
2352
- });
2539
+ return attachDescriptor(
2540
+ await _withSubtreeInstructions(
2541
+ {
2542
+ content: rendered,
2543
+ hashed: args.hashed === true,
2544
+ ...(range ? { range } : {}),
2545
+ },
2546
+ filePath,
2547
+ cwd,
2548
+ ),
2549
+ );
2353
2550
  }
2354
2551
 
2355
2552
  case "write_file": {
@@ -2368,8 +2565,12 @@ async function executeToolInner(
2368
2565
  if (wrote.error) return attachDescriptor({ error: wrote.error });
2369
2566
  _recordFileObservation(filePath);
2370
2567
  return attachDescriptor(
2371
- await _withPostEditDiagnostics(
2372
- { success: true, path: filePath, size: wrote.size },
2568
+ await _withSubtreeInstructions(
2569
+ await _withPostEditDiagnostics(
2570
+ { success: true, path: filePath, size: wrote.size },
2571
+ filePath,
2572
+ cwd,
2573
+ ),
2373
2574
  filePath,
2374
2575
  cwd,
2375
2576
  ),
@@ -2457,13 +2658,17 @@ async function executeToolInner(
2457
2658
  if (wrote.error) return attachDescriptor({ error: wrote.error });
2458
2659
  _recordFileObservation(filePath);
2459
2660
  return attachDescriptor(
2460
- await _withPostEditDiagnostics(
2461
- {
2462
- success: true,
2463
- path: filePath,
2464
- size: wrote.size,
2465
- replaced: replaceAll ? count : 1,
2466
- },
2661
+ await _withSubtreeInstructions(
2662
+ await _withPostEditDiagnostics(
2663
+ {
2664
+ success: true,
2665
+ path: filePath,
2666
+ size: wrote.size,
2667
+ replaced: replaceAll ? count : 1,
2668
+ },
2669
+ filePath,
2670
+ cwd,
2671
+ ),
2467
2672
  filePath,
2468
2673
  cwd,
2469
2674
  ),
@@ -2515,14 +2720,18 @@ async function executeToolInner(
2515
2720
  if (wrote.error) return attachDescriptor({ error: wrote.error });
2516
2721
  _recordFileObservation(filePath);
2517
2722
  return attachDescriptor(
2518
- await _withPostEditDiagnostics(
2519
- {
2520
- success: true,
2521
- path: filePath,
2522
- size: wrote.size,
2523
- lineNumber: result.lineNumber,
2524
- previousContent: result.previousContent,
2525
- },
2723
+ await _withSubtreeInstructions(
2724
+ await _withPostEditDiagnostics(
2725
+ {
2726
+ success: true,
2727
+ path: filePath,
2728
+ size: wrote.size,
2729
+ lineNumber: result.lineNumber,
2730
+ previousContent: result.previousContent,
2731
+ },
2732
+ filePath,
2733
+ cwd,
2734
+ ),
2526
2735
  filePath,
2527
2736
  cwd,
2528
2737
  ),
@@ -3104,6 +3313,8 @@ async function executeToolInner(
3104
3313
  subAgentBudget,
3105
3314
  subAgentContract,
3106
3315
  settingsHooks,
3316
+ // Parent trace for the child's hook envelopes (parent_id).
3317
+ hookTraceId,
3107
3318
  // Parent MCP plumbing — a spawn can inherit these into the child,
3108
3319
  // filtered by the resolved contract's mcpServers allow-list.
3109
3320
  mcpClient,
@@ -3675,8 +3886,15 @@ async function executeToolInner(
3675
3886
  error: `code_intelligence action "rename_preview" requires "new_name".`,
3676
3887
  });
3677
3888
  }
3678
- const ci = await _getSharedCodeIntel(cwd);
3889
+ // Multi-root workspace (P2 LSP): key the shared server pool on the root
3890
+ // that actually CONTAINS the file — a file inside an `--add-dir` root
3891
+ // gets that project's language server, not the cwd's. Single-root
3892
+ // sessions resolve to cwd exactly as before (byte-identical).
3679
3893
  const file = args.file ? path.resolve(cwd, args.file) : null;
3894
+ const wsRoots = workspaceRootsFor(cwd, additionalDirectories);
3895
+ const ci = await _getSharedCodeIntel(
3896
+ file ? pickRootForFile(file, wsRoots) : cwd,
3897
+ );
3680
3898
  let res;
3681
3899
  try {
3682
3900
  switch (action) {
@@ -3692,9 +3910,29 @@ async function executeToolInner(
3692
3910
  case "document_symbols":
3693
3911
  res = await ci.documentSymbols(file);
3694
3912
  break;
3695
- case "workspace_symbols":
3696
- res = await ci.workspaceSymbols(String(args.query));
3913
+ case "workspace_symbols": {
3914
+ // Fan out across EVERY workspace root and merge — symbols from an
3915
+ // --add-dir project are stamped with their `root` so same-named
3916
+ // symbols stay unambiguous. One root → the exact legacy call.
3917
+ if (wsRoots.length > 1) {
3918
+ const perRoot = [];
3919
+ for (const r of wsRoots) {
3920
+ try {
3921
+ const rci = await _getSharedCodeIntel(r);
3922
+ perRoot.push(await rci.workspaceSymbols(String(args.query)));
3923
+ } catch (err) {
3924
+ perRoot.push({
3925
+ available: false,
3926
+ reason: err?.message || "workspace_symbols failed",
3927
+ });
3928
+ }
3929
+ }
3930
+ res = mergeWorkspaceSymbolResults(perRoot, wsRoots);
3931
+ } else {
3932
+ res = await ci.workspaceSymbols(String(args.query));
3933
+ }
3697
3934
  break;
3935
+ }
3698
3936
  case "diagnostics":
3699
3937
  res = await ci.diagnostics(file);
3700
3938
  break;
@@ -3743,12 +3981,18 @@ async function executeToolInner(
3743
3981
  });
3744
3982
  }
3745
3983
  const entries = fs.readdirSync(dirPath, { withFileTypes: true });
3746
- return attachDescriptor({
3747
- entries: entries.map((e) => ({
3748
- name: e.name,
3749
- type: e.isDirectory() ? "dir" : "file",
3750
- })),
3751
- });
3984
+ return attachDescriptor(
3985
+ await _withSubtreeInstructions(
3986
+ {
3987
+ entries: entries.map((e) => ({
3988
+ name: e.name,
3989
+ type: e.isDirectory() ? "dir" : "file",
3990
+ })),
3991
+ },
3992
+ dirPath,
3993
+ cwd,
3994
+ ),
3995
+ );
3752
3996
  }
3753
3997
 
3754
3998
  case "run_skill": {
@@ -3788,6 +4032,7 @@ async function executeToolInner(
3788
4032
  role: `skill-${args.skill_name}`,
3789
4033
  task: `Execute the "${args.skill_name}" skill with input: ${(args.input || "").substring(0, 200)}`,
3790
4034
  allowedTools: ["read_file", "search_files", "list_dir"],
4035
+ hookParentTraceId: hookTraceId || null,
3791
4036
  cwd,
3792
4037
  onUsage: skillUsageSink
3793
4038
  ? (u) => {
@@ -4251,6 +4496,32 @@ async function _executeRunCode(args, cwd) {
4251
4496
  };
4252
4497
  }
4253
4498
 
4499
+ // Unified install-command audit (P0 sandbox slice): the same opt-in
4500
+ // trail (CC_INSTALL_AUDIT / settings installPolicy) that records
4501
+ // run_shell installs also records run_code auto-installs — one audit
4502
+ // file covers every "fetch and run third-party code" entry point.
4503
+ // Best-effort; default (policy off) writes nothing.
4504
+ let auditUnifiedInstall = () => {};
4505
+ try {
4506
+ const icp = await import("../lib/install-command-policy.js");
4507
+ const unified = icp.resolveInstallPolicy({});
4508
+ if (unified.audit) {
4509
+ const installCommand = `${interpreter} -m pip install ${packageName}`;
4510
+ const cls = icp.classifyInstallCommand(installCommand);
4511
+ auditUnifiedInstall = (outcome, detail) =>
4512
+ icp.recordInstallCommandAudit({
4513
+ source: "run_code_auto_install",
4514
+ command: installCommand,
4515
+ outcome,
4516
+ ...(detail ? { detail } : {}),
4517
+ installs: cls.installs,
4518
+ global: icp.hasGlobalInstall(cls),
4519
+ });
4520
+ }
4521
+ } catch {
4522
+ /* unified audit must never affect the install itself */
4523
+ }
4524
+
4254
4525
  // Attempt pip install
4255
4526
  try {
4256
4527
  execSync(`${interpreter} -m pip install ${packageName}`, {
@@ -4265,6 +4536,7 @@ async function _executeRunCode(args, cwd) {
4265
4536
  cwd,
4266
4537
  outcome: "installed",
4267
4538
  });
4539
+ auditUnifiedInstall("installed");
4268
4540
 
4269
4541
  // Retry execution
4270
4542
  const retryStart = Date.now();
@@ -4292,6 +4564,10 @@ async function _executeRunCode(args, cwd) {
4292
4564
  outcome: "failed",
4293
4565
  detail: (pipErr.stderr || pipErr.message || "").substring(0, 200),
4294
4566
  });
4567
+ auditUnifiedInstall(
4568
+ "failed",
4569
+ (pipErr.stderr || pipErr.message || "").substring(0, 200),
4570
+ );
4295
4571
  return {
4296
4572
  error: (stderr || message).substring(0, 5000),
4297
4573
  stderr: stderr.substring(0, 5000),
@@ -4556,6 +4832,8 @@ async function _executeSpawnSubAgent(args, ctx) {
4556
4832
  let mdProfile = null;
4557
4833
  let mdModel = null;
4558
4834
  let mdContract = null; // agent-file's normalized subagent contract (definition)
4835
+ let mdSparsePaths = null; // agent-file worktree sparse-checkout paths
4836
+ let mdSymlinkDirectories = null; // agent-file worktree dep-dir symlinks
4559
4837
  if (args.agent) {
4560
4838
  try {
4561
4839
  const { getAgent } = await import("../lib/agents.js");
@@ -4574,6 +4852,10 @@ async function _executeSpawnSubAgent(args, ctx) {
4574
4852
  if (!subIsolation && md.isolation === "worktree") {
4575
4853
  subIsolation = "worktree";
4576
4854
  }
4855
+ if (md.sparsePaths != null) mdSparsePaths = md.sparsePaths;
4856
+ if (md.symlinkDirectories != null) {
4857
+ mdSymlinkDirectories = md.symlinkDirectories;
4858
+ }
4577
4859
  if (md.model) mdModel = md.model;
4578
4860
  if (md.contract) mdContract = md.contract;
4579
4861
  if (md.systemPrompt) {
@@ -4894,10 +5176,37 @@ async function _executeSpawnSubAgent(args, ctx) {
4894
5176
  }
4895
5177
  : null;
4896
5178
 
5179
+ // Worktree sparse-checkout + dependency symlink (large-monorepo lever): when
5180
+ // the child runs in an isolated worktree, only materialize the packages it
5181
+ // needs (sparsePaths) and reuse approved dep dirs (e.g. node_modules) from the
5182
+ // main checkout (symlinkDirectories). Mirrors the team-worktree passthrough;
5183
+ // spawn args win over the agent-file's values. Resolved ONLY when isolation is
5184
+ // on → absent for a non-worktree spawn = byte-identical (full checkout).
5185
+ let subWorktreeOptions = null;
5186
+ if (subIsolation === "worktree") {
5187
+ try {
5188
+ const { normalizeSparsePaths } =
5189
+ await import("../lib/worktree-sparse.js");
5190
+ const sparse = normalizeSparsePaths(
5191
+ args.sparsePaths ?? mdSparsePaths ?? null,
5192
+ );
5193
+ const symlink = args.symlinkDirectories ?? mdSymlinkDirectories ?? null;
5194
+ const wtOpts = {};
5195
+ if (sparse) wtOpts.sparsePaths = sparse;
5196
+ if (symlink != null) wtOpts.symlinkDirectories = symlink;
5197
+ if (Object.keys(wtOpts).length) subWorktreeOptions = wtOpts;
5198
+ } catch {
5199
+ subWorktreeOptions = null; // best-effort → full checkout on any error
5200
+ }
5201
+ }
5202
+
4897
5203
  const subCtx = SubAgentContext.create({
4898
5204
  role,
4899
5205
  task,
4900
5206
  parentId: parentSessionId,
5207
+ // Hook-envelope tracing: the child loop stamps its own runId as trace_id
5208
+ // and THIS run's id as parent_id on every settings-hook payload it fires.
5209
+ hookParentTraceId: ctx.hookTraceId || null,
4901
5210
  inheritedContext: resolvedContext,
4902
5211
  allowedTools: allowedTools || null,
4903
5212
  cwd: ctx.cwd,
@@ -4912,6 +5221,9 @@ async function _executeSpawnSubAgent(args, ctx) {
4912
5221
  // worktree isolation. undefined keeps the profile/flag defaults intact.
4913
5222
  ...(subMaxTurns ? { maxIterations: subMaxTurns } : {}),
4914
5223
  ...(subIsolation === "worktree" ? { useWorktree: true } : {}),
5224
+ // Worktree sparse-checkout / dep-symlink options (large-monorepo); only
5225
+ // present for a worktree spawn that requested them (else full checkout).
5226
+ ...(subWorktreeOptions ? { worktreeOptions: subWorktreeOptions } : {}),
4915
5227
  // Resolved contract (gap 2026-07-12): cap the child's token budget and hand
4916
5228
  // it its EFFECTIVE contract so its OWN spawns inherit this ceiling.
4917
5229
  ...(effectiveContract?.budget?.tokens
@@ -4993,7 +5305,15 @@ async function _executeSpawnSubAgent(args, ctx) {
4993
5305
  // refuses to finish while any background sub-agent is still running). Only
4994
5306
  // available when the calling loop provided the per-run map — a bare
4995
5307
  // executeTool() without it falls through to the blocking path.
4996
- if (args.background === true && ctx.backgroundSubAgents instanceof Map) {
5308
+ //
5309
+ // `background` is driven by the resolved contract (spawn arg > agent-file
5310
+ // definition), so an agent-file can declare `background: true` and be spawned
5311
+ // detached without the caller passing it. `effectiveContract.background`
5312
+ // already folds in `args.background`; the explicit OR keeps the path
5313
+ // byte-identical if contract resolution was skipped (effectiveContract null).
5314
+ const wantsBackground =
5315
+ args.background === true || effectiveContract?.background === true;
5316
+ if (wantsBackground && ctx.backgroundSubAgents instanceof Map) {
4997
5317
  // Cancel with the parent: forward the loop's abort signal so killing the
4998
5318
  // run doesn't orphan a detached child mid-LLM-call.
4999
5319
  subCtx._signal = subCtx._signal || ctx.signal || null;
@@ -6544,9 +6864,23 @@ export async function* agentLoop(messages, options) {
6544
6864
  // Optional OpenTelemetry recorder (TelemetryRecorder). When present, the loop
6545
6865
  // emits model/tool/retry spans + a per-run counter; when absent, zero cost.
6546
6866
  const recorder = options.recorder || null;
6867
+ // Phase 5 run bookends — a stable runId lets envelope subscribers correlate
6868
+ // every tool_call / tool_result / message / ended event back to one run.
6869
+ // Minted BEFORE the tool context so hook envelopes can carry it as trace_id.
6870
+ const runId =
6871
+ options.runId ||
6872
+ `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
6873
+
6547
6874
  const toolContext = {
6548
6875
  hookDb: options.hookDb || null,
6549
6876
  skillLoader: options.skillLoader || _defaultSkillLoader,
6877
+ // Hook-envelope tracing (P2 unified event bus): every settings-hook payload
6878
+ // fired during this run carries trace_id = this run's id; a spawned child
6879
+ // loop carries parent_id = the spawning run's id (threaded by
6880
+ // spawn_sub_agent via hookParentTraceId), so a subagent's hook events
6881
+ // correlate back to the parent trace. Null parent at the top level.
6882
+ hookTraceId: runId,
6883
+ hookParentId: options.hookParentTraceId || null,
6550
6884
  // Contract-driven skill allow-list (subagent capability INTERSECT). null =
6551
6885
  // unrestricted; [] = none; a list restricts run_skill/list_skills to those
6552
6886
  // ids/dirNames. Set by the spawn path from the resolved subagent contract.
@@ -6736,11 +7070,6 @@ export async function* agentLoop(messages, options) {
6736
7070
  });
6737
7071
  }
6738
7072
 
6739
- // Phase 5 run bookends — a stable runId lets envelope subscribers correlate
6740
- // every tool_call / tool_result / message / ended event back to one run.
6741
- const runId =
6742
- options.runId ||
6743
- `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
6744
7073
  // Workflow-tracing attributes (Claude-Code 2.1.202): stamp the run id (and
6745
7074
  // an optional caller-provided workflow name) onto EVERY span this run emits,
6746
7075
  // so a collector can group one run's model/tool spans into one workflow.