chainlesschain 0.162.167 → 0.162.169

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 (172) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-BZJrchIA.js → AIOps-DCeNRK8z.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-Bhlrylt7.js → ActionButton-ZfD0HgVe.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-pHtXVQYs.js → Analytics-Ccn40f9E.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-ORpuri4R.js → AppLayout-DyXs4nSt.js} +4 -4
  6. package/src/assets/web-panel/assets/{Artifacts-eC77yr1_.js → Artifacts-CYjMh5KR.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-B2dZIiqz.js → Audit-DZIhvx88.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-COZiTiwP.js → BackgroundAgents-C1XJsa2d.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-vY_Srl5d.js → Backup-CMXC0oUN.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-Dili5BdM.js → BaseInput-CRVmIH1t.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-EzKBf0XL.js → Chat-acBud5Yj.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-D1CtRAyy.js → ChatBubbleRenderer-CTjqFhdD.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-Beh6BmuW.js → Checkbox-CZem825o.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-C3fA6kBU.js → Codegen-B9UjZsww.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-CefMqONH.js → Col-Dtu6Frtj.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-CBAxFJtB.js → Community-9bV55Huv.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact--flHc-sM.js → Compact-g-kQ4c84.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-CPeNLvA6.js → Compliance-CQjHzBO1.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-Bv5yv4BU.js → Cowork-D1Qm_Xhi.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-C22yMEas.js → Cron-992qo8zX.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-Dsb2bcpu.js → Crosschain-4TiqEwuc.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-DAxei4w0.js → DID-BbsXzXCb.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-DEUwuVZ8.js → Dashboard-Dx5aO9d3.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-BVp4si2j.js → Dropdown-BhzW6i6i.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-CyFkybOG.js → EmailListRenderer-qEib4yiq.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BWmmsbVx.js → FamilyGuardDashboard-BZ3N-u3h.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-Cxw27A5d.js → Federation-CcOqwzHY.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-Q9XthzL1.js → FormItemContext-dbi8mohd.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-DS5DIdsW.js → GenericCardRenderer-57dqulUe.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-BOliHfxP.js → Git-DtSV7C5g.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-CD4oxa-W.js → Governance-CLnmH-RD.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-CMlcivo8.js → Inference-wXiLeDnk.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-BwhERcC4.js → KnowledgeGraph-NGdIX2Gx.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-BjVcZR5P.js → Logs-Dz0HQn69.js} +2 -2
  35. package/src/assets/web-panel/assets/MarkdownRenderer-CSRZ9da3.js +1 -0
  36. package/src/assets/web-panel/assets/{Marketplace-DQLU3E2w.js → Marketplace-DpPDmY3W.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-CeK0XkpB.js → McpTools-UMysoRQm.js} +3 -3
  38. package/src/assets/web-panel/assets/{Memory-DLFkgwaz.js → Memory-DsnBpQnY.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-Bh6ymXyj.js → MobileBridge-DweooAfG.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-D950tUIe.js → MobileProjects-UdLfZOVA.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-Bx5x9_2m.js → Mtc-oXWhJNgC.js} +4 -4
  42. package/src/assets/web-panel/assets/{MtcAudit-a1-Fexsq.js → MtcAudit-CXTD7qrm.js} +4 -4
  43. package/src/assets/web-panel/assets/{Multisig-eaw0X8X1.js → Multisig-BKt5mJ9O.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-BaNsPV4k.js → NLProgramming-C-zcYzje.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-C7pevN2X.js → Notes-CGaZ_jep.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-HDqeoP0r.js → NotificationSettings-BwX-tDbq.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-DvzVl5T3.js → OrderTableRenderer-Cc9Se8H-.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-D_xHDlGW.js → Organization-8BXdC7Im.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-B5sRnueL.js → Overflow-zCisFuGU.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-C-IRjOPW.js → P2P-BehHH9Ro.js} +2 -2
  51. package/src/assets/web-panel/assets/PdhVaultBrowser-DVuXYKAR.js +7 -0
  52. package/src/assets/web-panel/assets/{Permissions-Bm_T1uWw.js → Permissions-BVDATm3p.js} +3 -3
  53. package/src/assets/web-panel/assets/{PersonalDataHub-DujDllye.js → PersonalDataHub-C43nnYoi.js} +4 -4
  54. package/src/assets/web-panel/assets/{Pipeline-DyNPOLIG.js → Pipeline-D9E7aSVD.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-CYKTW3iK.js → Privacy-CRbIchdL.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-CGf03uZ0.js → ProjectInit-CTsy8lhL.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-DIec4bTI.js → ProjectSettings-CPEKWWJ5.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-BY43-bmi.js → Projects-CNnW0v5N.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-RrnUmgPk.js → Providers-BeZnDL9Y.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-BBWfqd4y.js → QrScannerModal-CaydwuIU.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-BsICSrFp.js → QuickAsk-qYWbu7kw.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-B8y0b4ys.js → Recommend-RV_ogHrM.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-0QgdB1Oe.js → RemoteSession-Mah1i41_.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-DBJGM0YO.js → Reputation-xZQhGktc.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-DtWQ5otv.js → Row-B6cVbPbJ.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-2zBxHd5H.js → RssFeed-C6hKjDPz.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-BofliSkZ.js → Search-0jti_-Pe.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-ockRBBEL.js → Security-Cy48feGz.js} +3 -3
  69. package/src/assets/web-panel/assets/{Services-QyvG7Vd0.js → Services-BW3ObFxH.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-DwbOndX3.js → Skeleton-CfrZRvAM.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-cHWa63sZ.js → Skills-yoccdk3t.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-BHnTEydQ.js → Sla-AKBBdri6.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-DE2hrrLb.js → SpeechSettings-DH87GbkH.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-CZ9vnbJH.js → SyncSettings-DUuO-ZSw.js} +2 -2
  75. package/src/assets/web-panel/assets/Tasks-ZTUfOzMZ.js +1 -0
  76. package/src/assets/web-panel/assets/{Templates-DiR3ad4X.js → Templates-shPrQ4NE.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-NqcW3frV.js → Tenant-C_inPunR.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-iYeqRQZ0.js → Terminal-CvVtx6ie.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-C5GOoxFz.js → TimelineRenderer-Bzp_T3Xc.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-82dz0XdU.js → Tokens-kUFRLdtp.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-B3JCNrOi.js → Trigger-BsTU2blQ.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-BehFYsN_.js → Trust-DEKkuAsi.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-BfOxJjVf.js → UkeySign-D1KEEgbK.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-DdITCCPz.js → VideoEditing-C2yYkm9W.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-B0EhkCmD.js → Wallet-BKKod4qb.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-DJ4i1f3-.js → WebAuthn-XXUgED6d.js} +2 -2
  87. package/src/assets/web-panel/assets/{WorkflowEditor-Dr9XTqIT.js → WorkflowEditor-DnlEyYTi.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-Dc2nFyOj.js → chat-BaVjI6nu.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-Drw9djBR.js → colors-DQ0yx43H.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-BdLXbU52.js → compact-item-DEyWDp0v.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-BpQMZiWY.js → createContext-SuPOfylZ.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-C_RK48j-.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-CbYwawHf.js → hasIn-CQz1JNtC.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-DO7LD_1t.js → index-3xcB2i6I.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Bo_KwJTF.js → index-57GdL9ro.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-CJQiSQw7.js → index-BHJhyrFr.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DTeqWdAS.js → index-BIgWgye3.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DSmZjrFy.js → index-BJEIjYhV.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-D7Ipfs4F.js → index-BffmUosH.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-B9_u4Qhe.js → index-ByGF9ev7.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DyU21xVi.js → index-C-IKjQD3.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-GG_MXjSL.js → index-C2k8eHyq.js} +3 -3
  103. package/src/assets/web-panel/assets/{index-Blv7c80V.js → index-C3Vlo45C.js} +1 -1
  104. package/src/assets/web-panel/assets/index-CDqhisfz.js +1 -0
  105. package/src/assets/web-panel/assets/{index-C1eo1Qkd.js → index-CEfLMvKf.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-COoIIMbO.js → index-CGu-b0RE.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Bu8Vy-mj.js → index-CI5jlfyn.js} +1 -1
  108. package/src/assets/web-panel/assets/index-CKUoX6Yi.js +1 -0
  109. package/src/assets/web-panel/assets/{index-BmQlot_u.js → index-COsmMpAO.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DWUN8c01.js → index-CZrARoXd.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-IG3__pGj.js → index-C_fcNCoV.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-B1ANzVc6.js → index-CfVW-cd_.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BAq-4KqY.js → index-CgvnudM_.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-aHrUfrv1.js → index-CnhJU5To.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-D_lkTnZ0.js → index-CpR6M7eQ.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-BWUoc9-s.js → index-D-gXgEsU.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BZxzdAI0.js → index-D5SY6Lwu.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BiGDrEt4.js → index-DB8AJ6T-.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DHMeEu-P.js → index-DNYVGZzG.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-C3Alz2QD.js → index-DWIuZ9bI.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DKQlF7Pw.js → index-DXczrVij.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CKmz3Is-.js → index-Dfh6OJKz.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BIcEW07n.js → index-DhBeUGCp.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-UX_Mfyfv.js → index-DjZ6szGM.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CBwQwtsE.js → index-DpPv8zeU.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CpmlTfpv.js → index-XjQfQBXH.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-_vVucYqC.js → index-ZKOZ-YFJ.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-XYe6kBmi.js → index-ZMgCYtI7.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-B5KboakZ.js → index-fGgx2f1y.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-B3uRI8_y.js → index-fcA6tfBA.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BBueKSTR.js → index-v8PYYw05.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-VIyNlXPX.js → index-wBsuJZcz.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-DybC85E4.js → initDefaultProps-KetDXPXx.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-BNNuyDg7.js → motion-BfAtwo2m.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-Bm8gSjEN.js → move-BnJvzHgx.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-CuHQvzzr.js → mtc-parser-DZxDclhM.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-lv4NO7Ng.js → omit-CmqfX2P_.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-BoSMfjHq.js → pickAttrs-B2B2ahzC.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-BOMDSvgz.js → placementArrow-C1SIgWRj.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-C064Ubmu.js → responsiveObserve-Dy_adDTl.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-CeqhbKoG.js → slide-hDO9sBPk.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-CunFoYww.js → statusUtils-DlpTjRpg.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-Cxz3wPL7.js → styleChecker-DAv9eo6h.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-DJsIaU2S.js → useFlexGapSupport-vfoXaeH8.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-DnAygceJ.js → useFs-Dzji-S7v.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-CrEUY7ta.js → usePersonalDataHub-jKcLN_Gj.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-Cq3rN2eT.js → vnode-DAWM5GgY.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-TMzcOx__.js → zoom-q3XgSOUd.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/plugin.js +49 -5
  151. package/src/data/changelog.json +18 -4
  152. package/src/harness/worktree-isolator.js +10 -2
  153. package/src/lib/agent-core.js +1 -0
  154. package/src/lib/background-agent-phase.js +22 -0
  155. package/src/lib/background-phase-reporter.js +153 -0
  156. package/src/lib/hook-runner.cjs +248 -36
  157. package/src/lib/install-command-policy.js +40 -1
  158. package/src/lib/lsp/code-intelligence.js +5 -1
  159. package/src/lib/lsp/workspace-roots.js +115 -0
  160. package/src/lib/session-lifecycle.js +3 -0
  161. package/src/lib/settings-hook-events.cjs +16 -3
  162. package/src/lib/sub-agent-context.js +13 -0
  163. package/src/runtime/agent-core.js +442 -89
  164. package/src/runtime/headless-runner.js +75 -8
  165. package/src/runtime/headless-stream.js +175 -2
  166. package/src/workers/background-agent-worker.js +22 -1
  167. package/src/assets/web-panel/assets/MarkdownRenderer-BfRhXdyG.js +0 -1
  168. package/src/assets/web-panel/assets/PdhVaultBrowser-BqgUPlcU.js +0 -7
  169. package/src/assets/web-panel/assets/Tasks-DWgoAHvM.js +0 -1
  170. package/src/assets/web-panel/assets/devWarning-CnfpDzMr.js +0 -1
  171. package/src/assets/web-panel/assets/index-CYTEB0tb.js +0 -1
  172. package/src/assets/web-panel/assets/index-CgsEp5bm.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);
@@ -2128,17 +2210,29 @@ const EDIT_DIAGNOSTICS_WALL_MS = 6000;
2128
2210
  * never cold-starts a server that isn't there) and fully bounded in time.
2129
2211
  * Disable entirely with CC_EDIT_DIAGNOSTICS=0.
2130
2212
  */
2131
- async function _postEditDiagnostics(filePath, cwd) {
2213
+ async function _postEditDiagnostics(filePath, cwd, additionalDirectories) {
2132
2214
  if (process.env.CC_EDIT_DIAGNOSTICS === "0") return null;
2215
+ // Multi-root workspace: resolve the server against the root that CONTAINS
2216
+ // the edited file (mirrors the code_intelligence handler) — editing a file
2217
+ // inside an `--add-dir` root probes/uses THAT project's language server
2218
+ // instead of degrading to "no diagnostics" via the cwd root. Single-root
2219
+ // sessions (no additionalDirectories) resolve to cwd exactly as before.
2220
+ let root;
2221
+ try {
2222
+ root = pickRootForFile(
2223
+ path.resolve(cwd || process.cwd(), filePath),
2224
+ workspaceRootsFor(cwd, additionalDirectories),
2225
+ );
2226
+ } catch {
2227
+ root = path.resolve(cwd || process.cwd());
2228
+ }
2133
2229
  let hasServer = false;
2134
2230
  try {
2135
2231
  const { languageIdForFile, resolveServer } =
2136
2232
  await import("../lib/lsp/lsp-server-registry.js");
2137
2233
  const languageId = languageIdForFile(filePath);
2138
2234
  if (!languageId) return null; // unsupported file type — no LSP, no cost
2139
- hasServer = Boolean(
2140
- resolveServer(languageId, path.resolve(cwd || process.cwd())),
2141
- );
2235
+ hasServer = Boolean(resolveServer(languageId, root));
2142
2236
  } catch {
2143
2237
  return null;
2144
2238
  }
@@ -2146,13 +2240,23 @@ async function _postEditDiagnostics(filePath, cwd) {
2146
2240
 
2147
2241
  const query = (async () => {
2148
2242
  try {
2149
- const ci = await _getSharedCodeIntel(cwd);
2243
+ const ci = await _getSharedCodeIntel(root);
2150
2244
  const res = await ci.refreshFile(filePath, { timeoutMs: 3000 });
2151
2245
  if (!res || res.available === false) return null;
2152
2246
  const diags = (res.diagnostics || []).filter(
2153
2247
  (d) => d.severity === "error" || d.severity === "warning",
2154
2248
  );
2155
- return diags.length ? diags.slice(0, 20) : null;
2249
+ if (!diags.length) return null;
2250
+ // Bound by a TOKEN budget, severity-prioritized (errors survive over
2251
+ // warnings), replacing the arbitrary unsorted count-cap of 20. Guarantees
2252
+ // the model sees the most severe diagnostics first and can't be flooded by
2253
+ // a pathological diagnostics dump. Budget is overridable via
2254
+ // CC_EDIT_DIAGNOSTICS_TOKENS (default 2000).
2255
+ const { capDiagnostics } =
2256
+ await import("../lib/lsp/diagnostics-scheduler.js");
2257
+ const budget = Number(process.env.CC_EDIT_DIAGNOSTICS_TOKENS) || 2000;
2258
+ const { kept } = capDiagnostics(diags, { maxTokens: budget });
2259
+ return kept.length ? kept : null;
2156
2260
  } catch {
2157
2261
  return null;
2158
2262
  }
@@ -2171,11 +2275,110 @@ async function _postEditDiagnostics(filePath, cwd) {
2171
2275
  }
2172
2276
 
2173
2277
  /** Merge post-edit diagnostics into a successful edit result (no-op if none). */
2174
- async function _withPostEditDiagnostics(result, filePath, cwd) {
2175
- const newDiagnostics = await _postEditDiagnostics(filePath, cwd);
2278
+ async function _withPostEditDiagnostics(
2279
+ result,
2280
+ filePath,
2281
+ cwd,
2282
+ additionalDirectories,
2283
+ ) {
2284
+ const newDiagnostics = await _postEditDiagnostics(
2285
+ filePath,
2286
+ cwd,
2287
+ additionalDirectories,
2288
+ );
2176
2289
  return newDiagnostics ? { ...result, newDiagnostics } : result;
2177
2290
  }
2178
2291
 
2292
+ // ─── Lazy subtree instruction injection (large-monorepo lever) ─────────────
2293
+ //
2294
+ // cc.md / CLAUDE.md / AGENTS.md that sit BELOW the startup cwd are intentionally
2295
+ // NOT loaded up front — they cost tokens for subtrees a run may never touch.
2296
+ // When a tool first ACCESSES a path inside such a subtree, we inject that
2297
+ // subtree's directory instructions into the tool result (the SAME channel as
2298
+ // newDiagnostics), exactly once per subtree per process. A stateful
2299
+ // SubtreeInstructionLoader per root remembers what it already injected so a
2300
+ // second access to the same subtree is a no-op. Disable with
2301
+ // CC_SUBTREE_INSTRUCTIONS=0. Common case (no cc.md below cwd) → zero cost.
2302
+ const _subtreeLoaderPool = new Map(); // root -> SubtreeInstructionLoader
2303
+
2304
+ async function _getSubtreeLoader(cwd) {
2305
+ const root = path.resolve(cwd || process.cwd());
2306
+ let loader = _subtreeLoaderPool.get(root);
2307
+ if (!loader) {
2308
+ const { SubtreeInstructionLoader } =
2309
+ await import("../lib/project-instructions.js");
2310
+ let instructionExcludes;
2311
+ try {
2312
+ const { readStringArraySetting } =
2313
+ await import("../lib/settings-loader.cjs");
2314
+ instructionExcludes = readStringArraySetting("instructionExcludes", {
2315
+ cwd: root,
2316
+ });
2317
+ } catch {
2318
+ instructionExcludes = undefined; // fail-open — honor no excludes
2319
+ }
2320
+ loader = new SubtreeInstructionLoader({
2321
+ repoRoot: root,
2322
+ baseDir: root,
2323
+ instructionExcludes,
2324
+ });
2325
+ _subtreeLoaderPool.set(root, loader);
2326
+ }
2327
+ return loader;
2328
+ }
2329
+
2330
+ /** Test/shutdown hook: forget every remembered subtree-injection set. */
2331
+ export function _resetSubtreeInstructionLoaders() {
2332
+ _subtreeLoaderPool.clear();
2333
+ }
2334
+
2335
+ /**
2336
+ * Freshly-discovered subtree instruction files for a path a tool just touched,
2337
+ * with their (capped) content read for inline injection — or null when the
2338
+ * subtree has no NEW cc.md/CLAUDE.md/AGENTS.md (the common case → zero cost).
2339
+ */
2340
+ async function _subtreeInstructionsFor(accessedPath, cwd) {
2341
+ if (process.env.CC_SUBTREE_INSTRUCTIONS === "0") return null;
2342
+ try {
2343
+ const loader = await _getSubtreeLoader(cwd);
2344
+ const fresh = loader.onAccess(accessedPath);
2345
+ if (!fresh || !fresh.length) return null;
2346
+ const { DEFAULT_MAX_FILE_BYTES } =
2347
+ await import("../lib/project-instructions.js");
2348
+ const out = [];
2349
+ for (const f of fresh) {
2350
+ try {
2351
+ const buf = fs.readFileSync(f.path);
2352
+ const truncated = buf.length > DEFAULT_MAX_FILE_BYTES;
2353
+ const content = (
2354
+ truncated ? buf.slice(0, DEFAULT_MAX_FILE_BYTES) : buf
2355
+ ).toString("utf-8");
2356
+ out.push({
2357
+ path: f.path,
2358
+ scope: f.scope || "project",
2359
+ content,
2360
+ ...(truncated ? { truncated: true } : {}),
2361
+ });
2362
+ } catch {
2363
+ // discovered file vanished before we could read it — skip it
2364
+ }
2365
+ }
2366
+ return out.length ? out : null;
2367
+ } catch {
2368
+ return null; // fail-open: never break a tool because injection errored
2369
+ }
2370
+ }
2371
+
2372
+ /**
2373
+ * Attach freshly-discovered subtree instructions to a SUCCESSFUL tool result
2374
+ * (no-op on an error result or when the subtree has nothing new).
2375
+ */
2376
+ async function _withSubtreeInstructions(result, accessedPath, cwd) {
2377
+ if (result && result.error) return result;
2378
+ const subtreeInstructions = await _subtreeInstructionsFor(accessedPath, cwd);
2379
+ return subtreeInstructions ? { ...result, subtreeInstructions } : result;
2380
+ }
2381
+
2179
2382
  /**
2180
2383
  * Drop skills whose `paths:` frontmatter scopes them to a different subtree than
2181
2384
  * the agent's cwd (large-monorepo lazy skill surface). A skill with no `paths`
@@ -2230,6 +2433,9 @@ async function executeToolInner(
2230
2433
  signal = null,
2231
2434
  backgroundSubAgents = null,
2232
2435
  subAgentUsageSink = null,
2436
+ // Hook-envelope tracing: this run's trace id, threaded into child loops
2437
+ // (spawn_sub_agent / isolated run_skill) as their parent_id.
2438
+ hookTraceId = null,
2233
2439
  },
2234
2440
  ) {
2235
2441
  const localToolDescriptor =
@@ -2338,18 +2544,30 @@ async function executeToolInner(
2338
2544
  }
2339
2545
 
2340
2546
  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
- });
2547
+ return attachDescriptor(
2548
+ await _withSubtreeInstructions(
2549
+ {
2550
+ content: rendered.substring(0, 50000) + "\n...(truncated)",
2551
+ size: rendered.length,
2552
+ hashed: args.hashed === true,
2553
+ ...(range ? { range } : {}),
2554
+ },
2555
+ filePath,
2556
+ cwd,
2557
+ ),
2558
+ );
2347
2559
  }
2348
- return attachDescriptor({
2349
- content: rendered,
2350
- hashed: args.hashed === true,
2351
- ...(range ? { range } : {}),
2352
- });
2560
+ return attachDescriptor(
2561
+ await _withSubtreeInstructions(
2562
+ {
2563
+ content: rendered,
2564
+ hashed: args.hashed === true,
2565
+ ...(range ? { range } : {}),
2566
+ },
2567
+ filePath,
2568
+ cwd,
2569
+ ),
2570
+ );
2353
2571
  }
2354
2572
 
2355
2573
  case "write_file": {
@@ -2368,8 +2586,13 @@ async function executeToolInner(
2368
2586
  if (wrote.error) return attachDescriptor({ error: wrote.error });
2369
2587
  _recordFileObservation(filePath);
2370
2588
  return attachDescriptor(
2371
- await _withPostEditDiagnostics(
2372
- { success: true, path: filePath, size: wrote.size },
2589
+ await _withSubtreeInstructions(
2590
+ await _withPostEditDiagnostics(
2591
+ { success: true, path: filePath, size: wrote.size },
2592
+ filePath,
2593
+ cwd,
2594
+ additionalDirectories,
2595
+ ),
2373
2596
  filePath,
2374
2597
  cwd,
2375
2598
  ),
@@ -2457,13 +2680,18 @@ async function executeToolInner(
2457
2680
  if (wrote.error) return attachDescriptor({ error: wrote.error });
2458
2681
  _recordFileObservation(filePath);
2459
2682
  return attachDescriptor(
2460
- await _withPostEditDiagnostics(
2461
- {
2462
- success: true,
2463
- path: filePath,
2464
- size: wrote.size,
2465
- replaced: replaceAll ? count : 1,
2466
- },
2683
+ await _withSubtreeInstructions(
2684
+ await _withPostEditDiagnostics(
2685
+ {
2686
+ success: true,
2687
+ path: filePath,
2688
+ size: wrote.size,
2689
+ replaced: replaceAll ? count : 1,
2690
+ },
2691
+ filePath,
2692
+ cwd,
2693
+ additionalDirectories,
2694
+ ),
2467
2695
  filePath,
2468
2696
  cwd,
2469
2697
  ),
@@ -2515,14 +2743,19 @@ async function executeToolInner(
2515
2743
  if (wrote.error) return attachDescriptor({ error: wrote.error });
2516
2744
  _recordFileObservation(filePath);
2517
2745
  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
- },
2746
+ await _withSubtreeInstructions(
2747
+ await _withPostEditDiagnostics(
2748
+ {
2749
+ success: true,
2750
+ path: filePath,
2751
+ size: wrote.size,
2752
+ lineNumber: result.lineNumber,
2753
+ previousContent: result.previousContent,
2754
+ },
2755
+ filePath,
2756
+ cwd,
2757
+ additionalDirectories,
2758
+ ),
2526
2759
  filePath,
2527
2760
  cwd,
2528
2761
  ),
@@ -3104,6 +3337,8 @@ async function executeToolInner(
3104
3337
  subAgentBudget,
3105
3338
  subAgentContract,
3106
3339
  settingsHooks,
3340
+ // Parent trace for the child's hook envelopes (parent_id).
3341
+ hookTraceId,
3107
3342
  // Parent MCP plumbing — a spawn can inherit these into the child,
3108
3343
  // filtered by the resolved contract's mcpServers allow-list.
3109
3344
  mcpClient,
@@ -3675,8 +3910,15 @@ async function executeToolInner(
3675
3910
  error: `code_intelligence action "rename_preview" requires "new_name".`,
3676
3911
  });
3677
3912
  }
3678
- const ci = await _getSharedCodeIntel(cwd);
3913
+ // Multi-root workspace (P2 LSP): key the shared server pool on the root
3914
+ // that actually CONTAINS the file — a file inside an `--add-dir` root
3915
+ // gets that project's language server, not the cwd's. Single-root
3916
+ // sessions resolve to cwd exactly as before (byte-identical).
3679
3917
  const file = args.file ? path.resolve(cwd, args.file) : null;
3918
+ const wsRoots = workspaceRootsFor(cwd, additionalDirectories);
3919
+ const ci = await _getSharedCodeIntel(
3920
+ file ? pickRootForFile(file, wsRoots) : cwd,
3921
+ );
3680
3922
  let res;
3681
3923
  try {
3682
3924
  switch (action) {
@@ -3692,9 +3934,29 @@ async function executeToolInner(
3692
3934
  case "document_symbols":
3693
3935
  res = await ci.documentSymbols(file);
3694
3936
  break;
3695
- case "workspace_symbols":
3696
- res = await ci.workspaceSymbols(String(args.query));
3937
+ case "workspace_symbols": {
3938
+ // Fan out across EVERY workspace root and merge — symbols from an
3939
+ // --add-dir project are stamped with their `root` so same-named
3940
+ // symbols stay unambiguous. One root → the exact legacy call.
3941
+ if (wsRoots.length > 1) {
3942
+ const perRoot = [];
3943
+ for (const r of wsRoots) {
3944
+ try {
3945
+ const rci = await _getSharedCodeIntel(r);
3946
+ perRoot.push(await rci.workspaceSymbols(String(args.query)));
3947
+ } catch (err) {
3948
+ perRoot.push({
3949
+ available: false,
3950
+ reason: err?.message || "workspace_symbols failed",
3951
+ });
3952
+ }
3953
+ }
3954
+ res = mergeWorkspaceSymbolResults(perRoot, wsRoots);
3955
+ } else {
3956
+ res = await ci.workspaceSymbols(String(args.query));
3957
+ }
3697
3958
  break;
3959
+ }
3698
3960
  case "diagnostics":
3699
3961
  res = await ci.diagnostics(file);
3700
3962
  break;
@@ -3743,12 +4005,18 @@ async function executeToolInner(
3743
4005
  });
3744
4006
  }
3745
4007
  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
- });
4008
+ return attachDescriptor(
4009
+ await _withSubtreeInstructions(
4010
+ {
4011
+ entries: entries.map((e) => ({
4012
+ name: e.name,
4013
+ type: e.isDirectory() ? "dir" : "file",
4014
+ })),
4015
+ },
4016
+ dirPath,
4017
+ cwd,
4018
+ ),
4019
+ );
3752
4020
  }
3753
4021
 
3754
4022
  case "run_skill": {
@@ -3788,6 +4056,7 @@ async function executeToolInner(
3788
4056
  role: `skill-${args.skill_name}`,
3789
4057
  task: `Execute the "${args.skill_name}" skill with input: ${(args.input || "").substring(0, 200)}`,
3790
4058
  allowedTools: ["read_file", "search_files", "list_dir"],
4059
+ hookParentTraceId: hookTraceId || null,
3791
4060
  cwd,
3792
4061
  onUsage: skillUsageSink
3793
4062
  ? (u) => {
@@ -4251,6 +4520,32 @@ async function _executeRunCode(args, cwd) {
4251
4520
  };
4252
4521
  }
4253
4522
 
4523
+ // Unified install-command audit (P0 sandbox slice): the same opt-in
4524
+ // trail (CC_INSTALL_AUDIT / settings installPolicy) that records
4525
+ // run_shell installs also records run_code auto-installs — one audit
4526
+ // file covers every "fetch and run third-party code" entry point.
4527
+ // Best-effort; default (policy off) writes nothing.
4528
+ let auditUnifiedInstall = () => {};
4529
+ try {
4530
+ const icp = await import("../lib/install-command-policy.js");
4531
+ const unified = icp.resolveInstallPolicy({});
4532
+ if (unified.audit) {
4533
+ const installCommand = `${interpreter} -m pip install ${packageName}`;
4534
+ const cls = icp.classifyInstallCommand(installCommand);
4535
+ auditUnifiedInstall = (outcome, detail) =>
4536
+ icp.recordInstallCommandAudit({
4537
+ source: "run_code_auto_install",
4538
+ command: installCommand,
4539
+ outcome,
4540
+ ...(detail ? { detail } : {}),
4541
+ installs: cls.installs,
4542
+ global: icp.hasGlobalInstall(cls),
4543
+ });
4544
+ }
4545
+ } catch {
4546
+ /* unified audit must never affect the install itself */
4547
+ }
4548
+
4254
4549
  // Attempt pip install
4255
4550
  try {
4256
4551
  execSync(`${interpreter} -m pip install ${packageName}`, {
@@ -4265,6 +4560,7 @@ async function _executeRunCode(args, cwd) {
4265
4560
  cwd,
4266
4561
  outcome: "installed",
4267
4562
  });
4563
+ auditUnifiedInstall("installed");
4268
4564
 
4269
4565
  // Retry execution
4270
4566
  const retryStart = Date.now();
@@ -4292,6 +4588,10 @@ async function _executeRunCode(args, cwd) {
4292
4588
  outcome: "failed",
4293
4589
  detail: (pipErr.stderr || pipErr.message || "").substring(0, 200),
4294
4590
  });
4591
+ auditUnifiedInstall(
4592
+ "failed",
4593
+ (pipErr.stderr || pipErr.message || "").substring(0, 200),
4594
+ );
4295
4595
  return {
4296
4596
  error: (stderr || message).substring(0, 5000),
4297
4597
  stderr: stderr.substring(0, 5000),
@@ -4556,6 +4856,8 @@ async function _executeSpawnSubAgent(args, ctx) {
4556
4856
  let mdProfile = null;
4557
4857
  let mdModel = null;
4558
4858
  let mdContract = null; // agent-file's normalized subagent contract (definition)
4859
+ let mdSparsePaths = null; // agent-file worktree sparse-checkout paths
4860
+ let mdSymlinkDirectories = null; // agent-file worktree dep-dir symlinks
4559
4861
  if (args.agent) {
4560
4862
  try {
4561
4863
  const { getAgent } = await import("../lib/agents.js");
@@ -4574,6 +4876,10 @@ async function _executeSpawnSubAgent(args, ctx) {
4574
4876
  if (!subIsolation && md.isolation === "worktree") {
4575
4877
  subIsolation = "worktree";
4576
4878
  }
4879
+ if (md.sparsePaths != null) mdSparsePaths = md.sparsePaths;
4880
+ if (md.symlinkDirectories != null) {
4881
+ mdSymlinkDirectories = md.symlinkDirectories;
4882
+ }
4577
4883
  if (md.model) mdModel = md.model;
4578
4884
  if (md.contract) mdContract = md.contract;
4579
4885
  if (md.systemPrompt) {
@@ -4894,10 +5200,37 @@ async function _executeSpawnSubAgent(args, ctx) {
4894
5200
  }
4895
5201
  : null;
4896
5202
 
5203
+ // Worktree sparse-checkout + dependency symlink (large-monorepo lever): when
5204
+ // the child runs in an isolated worktree, only materialize the packages it
5205
+ // needs (sparsePaths) and reuse approved dep dirs (e.g. node_modules) from the
5206
+ // main checkout (symlinkDirectories). Mirrors the team-worktree passthrough;
5207
+ // spawn args win over the agent-file's values. Resolved ONLY when isolation is
5208
+ // on → absent for a non-worktree spawn = byte-identical (full checkout).
5209
+ let subWorktreeOptions = null;
5210
+ if (subIsolation === "worktree") {
5211
+ try {
5212
+ const { normalizeSparsePaths } =
5213
+ await import("../lib/worktree-sparse.js");
5214
+ const sparse = normalizeSparsePaths(
5215
+ args.sparsePaths ?? mdSparsePaths ?? null,
5216
+ );
5217
+ const symlink = args.symlinkDirectories ?? mdSymlinkDirectories ?? null;
5218
+ const wtOpts = {};
5219
+ if (sparse) wtOpts.sparsePaths = sparse;
5220
+ if (symlink != null) wtOpts.symlinkDirectories = symlink;
5221
+ if (Object.keys(wtOpts).length) subWorktreeOptions = wtOpts;
5222
+ } catch {
5223
+ subWorktreeOptions = null; // best-effort → full checkout on any error
5224
+ }
5225
+ }
5226
+
4897
5227
  const subCtx = SubAgentContext.create({
4898
5228
  role,
4899
5229
  task,
4900
5230
  parentId: parentSessionId,
5231
+ // Hook-envelope tracing: the child loop stamps its own runId as trace_id
5232
+ // and THIS run's id as parent_id on every settings-hook payload it fires.
5233
+ hookParentTraceId: ctx.hookTraceId || null,
4901
5234
  inheritedContext: resolvedContext,
4902
5235
  allowedTools: allowedTools || null,
4903
5236
  cwd: ctx.cwd,
@@ -4912,6 +5245,9 @@ async function _executeSpawnSubAgent(args, ctx) {
4912
5245
  // worktree isolation. undefined keeps the profile/flag defaults intact.
4913
5246
  ...(subMaxTurns ? { maxIterations: subMaxTurns } : {}),
4914
5247
  ...(subIsolation === "worktree" ? { useWorktree: true } : {}),
5248
+ // Worktree sparse-checkout / dep-symlink options (large-monorepo); only
5249
+ // present for a worktree spawn that requested them (else full checkout).
5250
+ ...(subWorktreeOptions ? { worktreeOptions: subWorktreeOptions } : {}),
4915
5251
  // Resolved contract (gap 2026-07-12): cap the child's token budget and hand
4916
5252
  // it its EFFECTIVE contract so its OWN spawns inherit this ceiling.
4917
5253
  ...(effectiveContract?.budget?.tokens
@@ -4993,7 +5329,15 @@ async function _executeSpawnSubAgent(args, ctx) {
4993
5329
  // refuses to finish while any background sub-agent is still running). Only
4994
5330
  // available when the calling loop provided the per-run map — a bare
4995
5331
  // executeTool() without it falls through to the blocking path.
4996
- if (args.background === true && ctx.backgroundSubAgents instanceof Map) {
5332
+ //
5333
+ // `background` is driven by the resolved contract (spawn arg > agent-file
5334
+ // definition), so an agent-file can declare `background: true` and be spawned
5335
+ // detached without the caller passing it. `effectiveContract.background`
5336
+ // already folds in `args.background`; the explicit OR keeps the path
5337
+ // byte-identical if contract resolution was skipped (effectiveContract null).
5338
+ const wantsBackground =
5339
+ args.background === true || effectiveContract?.background === true;
5340
+ if (wantsBackground && ctx.backgroundSubAgents instanceof Map) {
4997
5341
  // Cancel with the parent: forward the loop's abort signal so killing the
4998
5342
  // run doesn't orphan a detached child mid-LLM-call.
4999
5343
  subCtx._signal = subCtx._signal || ctx.signal || null;
@@ -6544,9 +6888,23 @@ export async function* agentLoop(messages, options) {
6544
6888
  // Optional OpenTelemetry recorder (TelemetryRecorder). When present, the loop
6545
6889
  // emits model/tool/retry spans + a per-run counter; when absent, zero cost.
6546
6890
  const recorder = options.recorder || null;
6891
+ // Phase 5 run bookends — a stable runId lets envelope subscribers correlate
6892
+ // every tool_call / tool_result / message / ended event back to one run.
6893
+ // Minted BEFORE the tool context so hook envelopes can carry it as trace_id.
6894
+ const runId =
6895
+ options.runId ||
6896
+ `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
6897
+
6547
6898
  const toolContext = {
6548
6899
  hookDb: options.hookDb || null,
6549
6900
  skillLoader: options.skillLoader || _defaultSkillLoader,
6901
+ // Hook-envelope tracing (P2 unified event bus): every settings-hook payload
6902
+ // fired during this run carries trace_id = this run's id; a spawned child
6903
+ // loop carries parent_id = the spawning run's id (threaded by
6904
+ // spawn_sub_agent via hookParentTraceId), so a subagent's hook events
6905
+ // correlate back to the parent trace. Null parent at the top level.
6906
+ hookTraceId: runId,
6907
+ hookParentId: options.hookParentTraceId || null,
6550
6908
  // Contract-driven skill allow-list (subagent capability INTERSECT). null =
6551
6909
  // unrestricted; [] = none; a list restricts run_skill/list_skills to those
6552
6910
  // ids/dirNames. Set by the spawn path from the resolved subagent contract.
@@ -6736,11 +7094,6 @@ export async function* agentLoop(messages, options) {
6736
7094
  });
6737
7095
  }
6738
7096
 
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
7097
  // Workflow-tracing attributes (Claude-Code 2.1.202): stamp the run id (and
6745
7098
  // an optional caller-provided workflow name) onto EVERY span this run emits,
6746
7099
  // so a collector can group one run's model/tool spans into one workflow.