chainlesschain 0.162.166 → 0.162.167

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 (175) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-LbDx7jL6.js → AIOps-BZJrchIA.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-DnurtK34.js → ActionButton-Bhlrylt7.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DXSsV1am.js → Analytics-pHtXVQYs.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-dqRpzNuD.js → AppLayout-ORpuri4R.js} +3 -3
  6. package/src/assets/web-panel/assets/{Artifacts-BwdmYepK.js → Artifacts-eC77yr1_.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-oSX0kOUG.js → Audit-B2dZIiqz.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-C2KxJahT.js → BackgroundAgents-COZiTiwP.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-B4TkxWkC.js → Backup-vY_Srl5d.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-CX5-KxeU.js → BaseInput-Dili5BdM.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-O_HzD-PL.js → Chat-EzKBf0XL.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-Br8saHQA.js → ChatBubbleRenderer-D1CtRAyy.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-ChepDpC1.js → Checkbox-Beh6BmuW.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-D9z8kc7q.js → Codegen-C3fA6kBU.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-B-H5eoNw.js → Col-CefMqONH.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-Ccyp_Kk5.js → Community-CBAxFJtB.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-BNvBznp_.js → Compact--flHc-sM.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-D1FjRd8n.js → Compliance-CPeNLvA6.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-CV9B9u-8.js → Cowork-Bv5yv4BU.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-BowJ4VIx.js → Cron-C22yMEas.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-BJc6jzRF.js → Crosschain-Dsb2bcpu.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BEp8NV6D.js → DID-DAxei4w0.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-Dls0B3YB.js → Dashboard-DEUwuVZ8.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-DQ7zXVtD.js → Dropdown-BVp4si2j.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-B9q79tOl.js → EmailListRenderer-CyFkybOG.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CD8Ie-88.js → FamilyGuardDashboard-BWmmsbVx.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-5o7XU5Vf.js → Federation-Cxw27A5d.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-DeKbMvOs.js → FormItemContext-Q9XthzL1.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-CZiQvz_z.js → GenericCardRenderer-DS5DIdsW.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-CTvFxVV8.js → Git-BOliHfxP.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-DwzFIjHt.js → Governance-CD4oxa-W.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-CJBk59c-.js → Inference-CMlcivo8.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-DfsaXN2z.js → KnowledgeGraph-BwhERcC4.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-DVRYf18S.js → Logs-BjVcZR5P.js} +2 -2
  35. package/src/assets/web-panel/assets/MarkdownRenderer-BfRhXdyG.js +1 -0
  36. package/src/assets/web-panel/assets/{Marketplace-BuIwyE4t.js → Marketplace-DQLU3E2w.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-D3U0o78E.js → McpTools-CeK0XkpB.js} +5 -5
  38. package/src/assets/web-panel/assets/{Memory-QYVmyb3b.js → Memory-DLFkgwaz.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-BFgrj6eZ.js → MobileBridge-Bh6ymXyj.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-Bp5QB-46.js → MobileProjects-D950tUIe.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-BZQu8TcH.js → Mtc-Bx5x9_2m.js} +4 -4
  42. package/src/assets/web-panel/assets/{MtcAudit-BnFfEkLo.js → MtcAudit-a1-Fexsq.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-iUnNS9Cm.js → Multisig-eaw0X8X1.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-DNNz1y9Y.js → NLProgramming-BaNsPV4k.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-CZezNYRU.js → Notes-C7pevN2X.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-DBvrplIR.js → NotificationSettings-HDqeoP0r.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-CTDtee49.js → OrderTableRenderer-DvzVl5T3.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-CCOUJX1R.js → Organization-D_xHDlGW.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-DcZghdk0.js → Overflow-B5sRnueL.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-jmddWX9q.js → P2P-C-IRjOPW.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-wfjMcczl.js → PdhVaultBrowser-BqgUPlcU.js} +4 -4
  52. package/src/assets/web-panel/assets/{Permissions-DU1GJjca.js → Permissions-Bm_T1uWw.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-BzTMzDF9.js → PersonalDataHub-DujDllye.js} +4 -4
  54. package/src/assets/web-panel/assets/{Pipeline-DPmjlpVc.js → Pipeline-DyNPOLIG.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-B6HRLpfK.js → Privacy-CYKTW3iK.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-Cyy86AsN.js → ProjectInit-CGf03uZ0.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-Bl_WQxJ5.js → ProjectSettings-DIec4bTI.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-Co9yKThA.js → Projects-BY43-bmi.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-DLa4NlIs.js → Providers-RrnUmgPk.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-BvzM7q5f.js → QrScannerModal-BBWfqd4y.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-BN8yyyUj.js → QuickAsk-BsICSrFp.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-QsVN4w0i.js → Recommend-B8y0b4ys.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-tkDXw-TY.js → RemoteSession-0QgdB1Oe.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-DAifkGsy.js → Reputation-DBJGM0YO.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-D7dZEyak.js → Row-DtWQ5otv.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-DB5sImTA.js → RssFeed-2zBxHd5H.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-3py3TO9V.js → Search-BofliSkZ.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-DhNUfT_k.js → Security-ockRBBEL.js} +3 -3
  69. package/src/assets/web-panel/assets/{Services-DjJAA_5D.js → Services-QyvG7Vd0.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-u95JjK3n.js → Skeleton-DwbOndX3.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-DPo3L0Tz.js → Skills-cHWa63sZ.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-DWhkAZV1.js → Sla-BHnTEydQ.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-PUegMtqV.js → SpeechSettings-DE2hrrLb.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-BMNYrnrU.js → SyncSettings-CZ9vnbJH.js} +2 -2
  75. package/src/assets/web-panel/assets/Tasks-DWgoAHvM.js +1 -0
  76. package/src/assets/web-panel/assets/{Templates-Dw9Er5DN.js → Templates-DiR3ad4X.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-7P3QIBSo.js → Tenant-NqcW3frV.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-ClPrfWW7.js → Terminal-iYeqRQZ0.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-DbCd1bDH.js → TimelineRenderer-C5GOoxFz.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-EQofpA0t.js → Tokens-82dz0XdU.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-ChNLLypd.js → Trigger-B3JCNrOi.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-dlVGUKs5.js → Trust-BehFYsN_.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-BXyBUhNF.js → UkeySign-BfOxJjVf.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-BE8-68ge.js → VideoEditing-DdITCCPz.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-7ZIJOwNg.js → Wallet-B0EhkCmD.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-DgNd2Amr.js → WebAuthn-DJ4i1f3-.js} +3 -3
  87. package/src/assets/web-panel/assets/{WorkflowEditor-Z-Un-9l5.js → WorkflowEditor-Dr9XTqIT.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-CtVDEkUc.js → chat-Dc2nFyOj.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-PXtJ2f1B.js → colors-Drw9djBR.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-oN96nuBi.js → compact-item-BdLXbU52.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-okCJXQXa.js → createContext-BpQMZiWY.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-CnfpDzMr.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-pcHm--4o.js → hasIn-CbYwawHf.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-BZKk4Lnm.js → index-B1ANzVc6.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CQYes2mk.js → index-B3uRI8_y.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-Cc_MkksW.js → index-B5KboakZ.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CQ1ZzJjN.js → index-B9_u4Qhe.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Bht9n8Sm.js → index-BAq-4KqY.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-_SaFHNsG.js → index-BBueKSTR.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-jAY55MSx.js → index-BIcEW07n.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-nMqLdwao.js → index-BWUoc9-s.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BHJIfdCV.js → index-BZxzdAI0.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CAQktO-6.js → index-BiGDrEt4.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BjVIZfdA.js → index-Blv7c80V.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BEtzqJw3.js → index-BmQlot_u.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-D0bfypsF.js → index-Bo_KwJTF.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-C0rfJBU3.js → index-Bu8Vy-mj.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BuME9k4Y.js → index-C1eo1Qkd.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-D1efSh1X.js → index-C3Alz2QD.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DBcKBb1E.js → index-CBwQwtsE.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-C4ravjdv.js → index-CJQiSQw7.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Cs1fGk1Z.js → index-CKmz3Is-.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BUUJ2WfG.js → index-COoIIMbO.js} +1 -1
  114. package/src/assets/web-panel/assets/index-CYTEB0tb.js +1 -0
  115. package/src/assets/web-panel/assets/index-CgsEp5bm.js +1 -0
  116. package/src/assets/web-panel/assets/{index-DC0bqrqV.js → index-CpmlTfpv.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Bl6N7ZyX.js → index-D7Ipfs4F.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-vW0lwj0Y.js → index-DHMeEu-P.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-mB1UV7wA.js → index-DKQlF7Pw.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BDR2mi1p.js → index-DO7LD_1t.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CK0H3pQW.js → index-DSmZjrFy.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DtsyPnF5.js → index-DTeqWdAS.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-T3QE8LuV.js → index-DWUN8c01.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-BUTyx-Lk.js → index-D_lkTnZ0.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-5aO2V0V4.js → index-DyU21xVi.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CWUJlCvK.js → index-GG_MXjSL.js} +3 -3
  127. package/src/assets/web-panel/assets/{index-CopGaCI_.js → index-IG3__pGj.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BnhHxZul.js → index-UX_Mfyfv.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CXhezK4d.js → index-VIyNlXPX.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Rk0X8lVz.js → index-XYe6kBmi.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DfL10EcN.js → index-_vVucYqC.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-BeAWCGEK.js → index-aHrUfrv1.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-CG1jyiI7.js → initDefaultProps-DybC85E4.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-C8BYa98D.js → motion-BNNuyDg7.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-D1hqwitW.js → move-Bm8gSjEN.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-C2lol8rp.js → mtc-parser-CuHQvzzr.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-Ct6Zghfi.js → omit-lv4NO7Ng.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-CSYbYfdc.js → pickAttrs-BoSMfjHq.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-WCk8di0z.js → placementArrow-BOMDSvgz.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-CEC8CZ-3.js → responsiveObserve-C064Ubmu.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-BsXyd7Lq.js → slide-CeqhbKoG.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-tDKldiUT.js → statusUtils-CunFoYww.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-BIS2unhx.js → styleChecker-Cxz3wPL7.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-yFTVMDUv.js → useFlexGapSupport-DJsIaU2S.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-CZYbJwkO.js → useFs-DnAygceJ.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-sbEHRLAM.js → usePersonalDataHub-CrEUY7ta.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-BVjeSEQK.js → vnode-Cq3rN2eT.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-BZKVvEZ6.js → zoom-TMzcOx__.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 +7 -2
  153. package/src/harness/jsonl-session-store.js +9 -0
  154. package/src/harness/session-mirror.js +212 -6
  155. package/src/lib/async-hook-queue.cjs +288 -0
  156. package/src/lib/async-hook-supervisor.cjs +52 -0
  157. package/src/lib/doctor-checkup.js +184 -4
  158. package/src/lib/install-command-policy.js +333 -0
  159. package/src/lib/jsonl-session-store.js +1 -0
  160. package/src/lib/lsp/__tests__/lsp-server-registry.test.js +27 -0
  161. package/src/lib/lsp/lsp-server-registry.js +14 -0
  162. package/src/lib/runtime-checkup.js +33 -0
  163. package/src/lib/settings-hook-events.cjs +157 -0
  164. package/src/lib/settings-hooks.cjs +4 -0
  165. package/src/lib/shell-approval.js +47 -1
  166. package/src/repl/add-dir.js +59 -1
  167. package/src/repl/agent-repl.js +85 -3
  168. package/src/runtime/headless-runner.js +69 -1
  169. package/src/runtime/headless-stream.js +22 -0
  170. package/src/runtime/system-prompt.js +39 -12
  171. package/src/assets/web-panel/assets/MarkdownRenderer-BL_WsWZM.js +0 -1
  172. package/src/assets/web-panel/assets/Tasks-CYm_oTJC.js +0 -1
  173. package/src/assets/web-panel/assets/devWarning-BzV6dk1B.js +0 -1
  174. package/src/assets/web-panel/assets/index-BFrAwXHp.js +0 -1
  175. package/src/assets/web-panel/assets/index-DJQFP47u.js +0 -1
@@ -178,6 +178,159 @@ function runSessionStartHooks(settingsHooks, { source, cwd, sessionId } = {}) {
178
178
  return { additionalContext: aggregateContext(outcome.results) };
179
179
  }
180
180
 
181
+ /**
182
+ * CwdChanged settings hooks (observe + context injection). Fired when the
183
+ * working directory changes mid-session (`/cd`) — a distinct lifecycle event
184
+ * (Claude-Code-parity extension) automation can react to (re-audit trust of the
185
+ * new dir, reload a per-dir tool config, …). Observe-only: a cwd change never
186
+ * gates flow, so any `block` decision is ignored and only emitted context is
187
+ * returned. No-op (byte-unchanged) without a registered CwdChanged hook.
188
+ * @returns {{ additionalContext:string|null }}
189
+ */
190
+ function runCwdChangedHooks(
191
+ settingsHooks,
192
+ { oldCwd, newCwd, cwd, sessionId } = {},
193
+ ) {
194
+ if (!settingsHooks) return { additionalContext: null };
195
+ const matched = collectHooks(settingsHooks, "CwdChanged", newCwd || "");
196
+ const { sync } = partitionAsyncHooks(matched);
197
+ if (sync.length === 0) return { additionalContext: null };
198
+ const payload = withDeliveryId(
199
+ "CwdChanged",
200
+ {
201
+ hook_event_name: "CwdChanged",
202
+ old_cwd: oldCwd || null,
203
+ cwd: newCwd || cwd || null,
204
+ session_id: sessionId || null,
205
+ },
206
+ { sessionId },
207
+ );
208
+ const outcome = runHooks(sync, payload, {
209
+ cwd: newCwd || cwd,
210
+ event: "CwdChanged",
211
+ });
212
+ return { additionalContext: aggregateContext(outcome.results) };
213
+ }
214
+
215
+ /**
216
+ * WorktreeCreate settings hooks (observe + context injection). Fired right after
217
+ * `cc agent --worktree` carves a fresh isolated git worktree — a hook can react
218
+ * (register the worktree with an external tracker, seed per-branch tooling,
219
+ * warm a build cache). The matcher target is the branch name, so a regex matcher
220
+ * can scope to a branch pattern. Observe-only: worktree setup already succeeded,
221
+ * so a `block` decision is ignored and only emitted context is returned. No-op
222
+ * (byte-unchanged) without a registered WorktreeCreate hook.
223
+ * @returns {{ additionalContext:string|null }}
224
+ */
225
+ function runWorktreeCreateHooks(
226
+ settingsHooks,
227
+ { worktreePath, branch, baseSha, cwd, sessionId } = {},
228
+ ) {
229
+ if (!settingsHooks) return { additionalContext: null };
230
+ const matched = collectHooks(settingsHooks, "WorktreeCreate", branch || "");
231
+ const { sync } = partitionAsyncHooks(matched);
232
+ if (sync.length === 0) return { additionalContext: null };
233
+ const payload = withDeliveryId(
234
+ "WorktreeCreate",
235
+ {
236
+ hook_event_name: "WorktreeCreate",
237
+ worktree_path: worktreePath || null,
238
+ branch: branch || null,
239
+ base_sha: baseSha || null,
240
+ cwd: cwd || worktreePath || null,
241
+ session_id: sessionId || null,
242
+ },
243
+ { sessionId },
244
+ );
245
+ const outcome = runHooks(sync, payload, {
246
+ cwd: cwd || worktreePath,
247
+ event: "WorktreeCreate",
248
+ });
249
+ return { additionalContext: aggregateContext(outcome.results) };
250
+ }
251
+
252
+ /**
253
+ * WorktreeRemove settings hooks (observe + context injection). Fired when a
254
+ * `cc agent --worktree` session finishes and its worktree is torn down (or when
255
+ * a `keep` decision leaves it in place — `removed` distinguishes the two) — a
256
+ * hook can react (unregister the worktree, prune a per-branch cache, notify CI).
257
+ * Matcher target is the branch name. Observe-only: teardown already happened, so
258
+ * a `block` decision is ignored and only emitted context is returned. No-op
259
+ * (byte-unchanged) without a registered WorktreeRemove hook.
260
+ * @returns {{ additionalContext:string|null }}
261
+ */
262
+ function runWorktreeRemoveHooks(
263
+ settingsHooks,
264
+ { worktreePath, branch, removed, reason, cwd, sessionId } = {},
265
+ ) {
266
+ if (!settingsHooks) return { additionalContext: null };
267
+ const matched = collectHooks(settingsHooks, "WorktreeRemove", branch || "");
268
+ const { sync } = partitionAsyncHooks(matched);
269
+ if (sync.length === 0) return { additionalContext: null };
270
+ const payload = withDeliveryId(
271
+ "WorktreeRemove",
272
+ {
273
+ hook_event_name: "WorktreeRemove",
274
+ worktree_path: worktreePath || null,
275
+ branch: branch || null,
276
+ removed: removed === true,
277
+ reason: reason || null,
278
+ cwd: cwd || worktreePath || null,
279
+ session_id: sessionId || null,
280
+ },
281
+ { sessionId },
282
+ );
283
+ const outcome = runHooks(sync, payload, {
284
+ cwd: cwd || worktreePath,
285
+ event: "WorktreeRemove",
286
+ });
287
+ return { additionalContext: aggregateContext(outcome.results) };
288
+ }
289
+
290
+ /**
291
+ * InstructionsLoaded settings hooks (observe + context injection). Fired once at
292
+ * session start after the project-instruction block (cc.md / CLAUDE.md / AGENTS.md
293
+ * hierarchy + `.claude/rules/*`) is composed into the system prompt — a hook can
294
+ * react to WHICH instruction files are authoritative this session (audit them,
295
+ * warn on an unexpected local override, load a companion tool config). The
296
+ * caller passes the EXACT loaded set; this producer trims each entry to
297
+ * `{path, scope, truncated}` (never the file CONTENT — a hook that wants content
298
+ * reads the path). Observe-only: instructions are already loaded, so a `block`
299
+ * decision is ignored and only emitted context is returned. No-op (byte-unchanged)
300
+ * without a registered InstructionsLoaded hook.
301
+ * @returns {{ additionalContext:string|null }}
302
+ */
303
+ function runInstructionsLoadedHooks(
304
+ settingsHooks,
305
+ { files, cwd, sessionId } = {},
306
+ ) {
307
+ if (!settingsHooks) return { additionalContext: null };
308
+ const matched = collectHooks(settingsHooks, "InstructionsLoaded", "");
309
+ const { sync } = partitionAsyncHooks(matched);
310
+ if (sync.length === 0) return { additionalContext: null };
311
+ const list = Array.isArray(files) ? files : [];
312
+ const payload = withDeliveryId(
313
+ "InstructionsLoaded",
314
+ {
315
+ hook_event_name: "InstructionsLoaded",
316
+ files: list.map((f) => ({
317
+ path: f?.path || null,
318
+ scope: f?.scope || null,
319
+ truncated: f?.truncated === true,
320
+ })),
321
+ count: list.length,
322
+ cwd: cwd || null,
323
+ session_id: sessionId || null,
324
+ },
325
+ { sessionId },
326
+ );
327
+ const outcome = runHooks(sync, payload, {
328
+ cwd,
329
+ event: "InstructionsLoaded",
330
+ });
331
+ return { additionalContext: aggregateContext(outcome.results) };
332
+ }
333
+
181
334
  /**
182
335
  * Generic observe-only fire (SessionEnd / Stop / PreCompact). Returns the raw
183
336
  * runHooks outcome so callers can read a block reason; never gates flow here.
@@ -206,6 +359,10 @@ function runObserveHooks(
206
359
  module.exports = {
207
360
  runUserPromptSubmitHooks,
208
361
  runSessionStartHooks,
362
+ runCwdChangedHooks,
363
+ runWorktreeCreateHooks,
364
+ runWorktreeRemoveHooks,
365
+ runInstructionsLoadedHooks,
209
366
  runObserveHooks,
210
367
  aggregateContext,
211
368
  partitionAsyncHooks,
@@ -49,6 +49,10 @@ const HOOK_EVENTS = Object.freeze([
49
49
  "PreCompact",
50
50
  "Notification",
51
51
  "PermissionRequest",
52
+ "CwdChanged",
53
+ "WorktreeCreate",
54
+ "WorktreeRemove",
55
+ "InstructionsLoaded",
52
56
  ]);
53
57
 
54
58
  /**
@@ -24,6 +24,13 @@ import {
24
24
  APPROVAL_RISK as RISK,
25
25
  APPROVAL_DECISION as DECISION,
26
26
  } from "@chainlesschain/session-core";
27
+ import {
28
+ classifyCodeAcquisition,
29
+ hasGlobalInstall,
30
+ applyRiskFloor,
31
+ recordInstallCommandAudit,
32
+ resolveInstallPolicy,
33
+ } from "./install-command-policy.js";
27
34
 
28
35
  export const SHELL_TO_RISK = {
29
36
  allow: RISK.LOW,
@@ -37,13 +44,49 @@ export async function evaluateShellCommandWithApproval({
37
44
  sessionId = null,
38
45
  approvalGate = null,
39
46
  shellPolicyOptions = {},
47
+ installPolicy = null,
40
48
  } = {}) {
41
49
  const shellPolicy = sharedShellPolicy.evaluateShellCommandPolicy(
42
50
  command,
43
51
  shellPolicyOptions,
44
52
  );
45
53
 
46
- const riskLevel = SHELL_TO_RISK[shellPolicy.decision] || RISK.MEDIUM;
54
+ let riskLevel = SHELL_TO_RISK[shellPolicy.decision] || RISK.MEDIUM;
55
+
56
+ // Unified install-command classification (OPT-IN via installPolicy). A package
57
+ // install (npm/pip/winget/brew/…) fetches and runs third-party code, so it is
58
+ // a distinct, auditable permission type regardless of which tool does it. When
59
+ // a riskFloor is configured it RAISES (never lowers) the risk before gating;
60
+ // audit records the attempt. Absent/disabled → nothing runs, byte-unchanged.
61
+ // Explicit policy wins; otherwise self-activate from env (CC_INSTALL_AUDIT /
62
+ // CC_INSTALL_RISK_FLOOR) so the real run_shell path picks it up with no extra
63
+ // wiring. Both unset → disabled → the block below is skipped, byte-unchanged.
64
+ const effectiveInstallPolicy =
65
+ installPolicy || resolveInstallPolicy({ env: process.env });
66
+ let install = null;
67
+ if (effectiveInstallPolicy && effectiveInstallPolicy.enabled) {
68
+ const cls = classifyCodeAcquisition(command);
69
+ if (cls.flagged) {
70
+ install = cls;
71
+ if (effectiveInstallPolicy.riskFloor) {
72
+ riskLevel = applyRiskFloor(riskLevel, effectiveInstallPolicy.riskFloor);
73
+ }
74
+ if (effectiveInstallPolicy.audit) {
75
+ recordInstallCommandAudit(
76
+ {
77
+ command,
78
+ shellDecision: shellPolicy.decision,
79
+ riskLevel,
80
+ installs: cls.installs,
81
+ remoteExec: cls.remoteExec,
82
+ global: hasGlobalInstall(cls),
83
+ sessionId,
84
+ },
85
+ effectiveInstallPolicy.auditOpts || {},
86
+ );
87
+ }
88
+ }
89
+ }
47
90
 
48
91
  // Hard-blocked rules bypass the gate entirely — the gate tier cannot
49
92
  // up-authorize them.
@@ -56,6 +99,7 @@ export async function evaluateShellCommandWithApproval({
56
99
  shellPolicy,
57
100
  riskLevel,
58
101
  policy: null,
102
+ install,
59
103
  };
60
104
  }
61
105
 
@@ -69,6 +113,7 @@ export async function evaluateShellCommandWithApproval({
69
113
  shellPolicy,
70
114
  riskLevel,
71
115
  policy: null,
116
+ install,
72
117
  };
73
118
  }
74
119
 
@@ -87,6 +132,7 @@ export async function evaluateShellCommandWithApproval({
87
132
  shellPolicy,
88
133
  riskLevel,
89
134
  policy: gateResult.policy,
135
+ install,
90
136
  // Explainability passthrough (autoMode.decisions wrapper attaches these):
91
137
  // which gate rule fired and why — surfaced in the permission chain.
92
138
  gateReason: gateResult.reason ?? null,
@@ -61,10 +61,68 @@ export function resolveAddDir(input, { cwd, existing = [], deps } = {}) {
61
61
  */
62
62
  export function formatAddDirRoots(cwd, extraRoots) {
63
63
  const extras = Array.isArray(extraRoots) ? extraRoots : [];
64
- const lines = [`Working roots (${1 + extras.length}):`, ` ${cwd} (primary)`];
64
+ const lines = [
65
+ `Working roots (${1 + extras.length}):`,
66
+ ` ${cwd} (primary)`,
67
+ ];
65
68
  for (const r of extras) lines.push(` ${r}`);
66
69
  if (extras.length === 0) {
67
70
  lines.push(" (no extra roots — add one with /add-dir <dir>)");
68
71
  }
69
72
  return lines.join("\n");
70
73
  }
74
+
75
+ /**
76
+ * The full workspace root list a session advertises to MCP servers (`roots/list`):
77
+ * the primary cwd first, then each extra `--add-dir` / `/add-dir` root, all
78
+ * resolved absolute and de-duplicated (the primary is dropped from the extras if
79
+ * a caller re-added it). Pure. Mirrors what the system prompt advertises so an
80
+ * MCP server sees the same roots the model does.
81
+ *
82
+ * @param {string} cwd primary working directory
83
+ * @param {string[]} extraRoots additional roots
84
+ * @returns {string[]} deduped absolute dirs, primary first
85
+ */
86
+ export function workspaceRootDirs(cwd, extraRoots) {
87
+ const primary = nodePath.resolve(cwd);
88
+ const out = [primary];
89
+ const seen = new Set([primary]);
90
+ for (const r of Array.isArray(extraRoots) ? extraRoots : []) {
91
+ if (!r) continue;
92
+ const abs = nodePath.resolve(cwd, String(r));
93
+ if (seen.has(abs)) continue;
94
+ seen.add(abs);
95
+ out.push(abs);
96
+ }
97
+ return out;
98
+ }
99
+
100
+ /**
101
+ * Best-effort broadcast of a changed workspace-root list to every connected MCP
102
+ * client (via `client.setRoots(dirs)`, which advertises the new list AND fires
103
+ * `notifications/roots/list_changed` only when it actually changed). Skips
104
+ * nullish/duplicate clients and any without `setRoots`; a failing client never
105
+ * disturbs the others or the caller. Returns the number of clients notified.
106
+ * Claude-Code 2.1.203 roots-change parity for the `/add-dir` (and startup
107
+ * `--add-dir`) vector, mirroring what `/cd` already does via
108
+ * `notifyRootsListChanged`.
109
+ *
110
+ * @param {Array<object|null|undefined>} clients MCP clients (may repeat/null)
111
+ * @param {string[]} dirs absolute root dirs
112
+ * @returns {number}
113
+ */
114
+ export function notifyMcpRootsChanged(clients, dirs) {
115
+ let notified = 0;
116
+ const seen = new Set();
117
+ for (const c of Array.isArray(clients) ? clients : []) {
118
+ if (!c || seen.has(c) || typeof c.setRoots !== "function") continue;
119
+ seen.add(c);
120
+ try {
121
+ c.setRoots(dirs);
122
+ notified += 1;
123
+ } catch {
124
+ /* best-effort — one server's failure must not block the rest */
125
+ }
126
+ }
127
+ return notified;
128
+ }
@@ -1045,6 +1045,7 @@ export async function startAgentRepl(options = {}) {
1045
1045
  // `undefined` so the runtime default-on path — and CC_PROJECT_MEMORY=0 — keep
1046
1046
  // their existing behaviour byte-identically.
1047
1047
  const _leanNoProjectMemory = options.projectMemory === false;
1048
+ let _loadedReplInstructions = null;
1048
1049
  let _replBaseSystem = composeSystemPrompt(
1049
1050
  buildSystemPrompt(process.cwd(), {
1050
1051
  additionalDirectories,
@@ -1054,6 +1055,9 @@ export async function startAgentRepl(options = {}) {
1054
1055
  systemPrompt: options.systemPrompt,
1055
1056
  appendSystemPrompt: options.appendSystemPrompt,
1056
1057
  projectMemory: _leanNoProjectMemory ? false : undefined,
1058
+ onInstructionsLoaded: (loaded) => {
1059
+ _loadedReplInstructions = loaded;
1060
+ },
1057
1061
  },
1058
1062
  );
1059
1063
  let _activeOutputStyle = null; // { name, body }
@@ -1088,6 +1092,25 @@ export async function startAgentRepl(options = {}) {
1088
1092
  // best-effort — no output style
1089
1093
  }
1090
1094
 
1095
+ // settings.json InstructionsLoaded hooks (observe-only): fire once with the
1096
+ // exact loaded instruction file set so a hook can audit which cc.md/CLAUDE.md/
1097
+ // AGENTS.md/rules are authoritative this session. Best-effort; no-op when
1098
+ // project memory is off or no hook is registered.
1099
+ if (_settingsHooks && _loadedReplInstructions) {
1100
+ try {
1101
+ const { runInstructionsLoadedHooks } =
1102
+ await import("../lib/settings-hook-events.cjs");
1103
+ const ctx = runInstructionsLoadedHooks(_settingsHooks, {
1104
+ files: _loadedReplInstructions.files,
1105
+ cwd: process.cwd(),
1106
+ sessionId,
1107
+ }).additionalContext;
1108
+ if (ctx) messages.push({ role: "system", content: ctx });
1109
+ } catch (_err) {
1110
+ // best-effort
1111
+ }
1112
+ }
1113
+
1091
1114
  // settings.json SessionStart hooks → inject session context (observe-only).
1092
1115
  if (_settingsHooks) {
1093
1116
  try {
@@ -1267,6 +1290,23 @@ export async function startAgentRepl(options = {}) {
1267
1290
  }
1268
1291
  }
1269
1292
 
1293
+ // Seed connected MCP servers with the startup workspace roots when the session
1294
+ // began with extra `--add-dir` roots — otherwise `roots/list` would only ever
1295
+ // return the cwd until the first mid-session /add-dir. No-op (byte-unchanged)
1296
+ // when there are no extra roots. Best-effort.
1297
+ if (additionalDirectories.length > 0) {
1298
+ try {
1299
+ const { workspaceRootDirs, notifyMcpRootsChanged } =
1300
+ await import("./add-dir.js");
1301
+ notifyMcpRootsChanged(
1302
+ [_adhocMcp?.mcpClient, _bundleMcpClient],
1303
+ workspaceRootDirs(process.cwd(), additionalDirectories),
1304
+ );
1305
+ } catch {
1306
+ /* best-effort — root advertisement must not block session startup */
1307
+ }
1308
+ }
1309
+
1270
1310
  // Apply bundle approval policy to this session (after both gate and sessionId are ready)
1271
1311
  if (_bundleResolved?.approvalPolicy?.default && _approvalGate && sessionId) {
1272
1312
  try {
@@ -2382,8 +2422,12 @@ export async function startAgentRepl(options = {}) {
2382
2422
  if (trimmed === "/add-dir" || trimmed.startsWith("/add-dir ")) {
2383
2423
  const arg = trimmed.slice("/add-dir".length).trim();
2384
2424
  try {
2385
- const { resolveAddDir, formatAddDirRoots } =
2386
- await import("./add-dir.js");
2425
+ const {
2426
+ resolveAddDir,
2427
+ formatAddDirRoots,
2428
+ workspaceRootDirs,
2429
+ notifyMcpRootsChanged,
2430
+ } = await import("./add-dir.js");
2387
2431
  if (!arg) {
2388
2432
  logger.log(
2389
2433
  "\n" +
@@ -2417,6 +2461,14 @@ export async function startAgentRepl(options = {}) {
2417
2461
  messages[0].content = _activeOutputStyle
2418
2462
  ? `${_replBaseSystem}\n\n${_activeOutputStyle.body}`
2419
2463
  : _replBaseSystem;
2464
+ // The workspace root LIST changed — advertise it to connected MCP
2465
+ // servers and fire notifications/roots/list_changed so they re-query
2466
+ // roots/list (Claude-Code 2.1.203 parity, same as /cd but with an
2467
+ // explicit new root, not just a cwd-derived change).
2468
+ notifyMcpRootsChanged(
2469
+ [_adhocMcp?.mcpClient, _bundleMcpClient],
2470
+ workspaceRootDirs(process.cwd(), additionalDirectories),
2471
+ );
2420
2472
  logger.log(chalk.green(`Added working root: ${res.dir}`));
2421
2473
  }
2422
2474
  }
@@ -3002,9 +3054,11 @@ export async function startAgentRepl(options = {}) {
3002
3054
  logger.info(`cwd: ${process.cwd()}`);
3003
3055
  } else {
3004
3056
  try {
3057
+ const oldCwd = process.cwd();
3005
3058
  const expanded = target.replace(/^~(?=$|[\\/])/, os.homedir());
3006
3059
  process.chdir(path.resolve(process.cwd(), expanded));
3007
- logger.log(chalk.green(`cwd ${process.cwd()}`));
3060
+ const newCwd = process.cwd();
3061
+ logger.log(chalk.green(`cwd → ${newCwd}`));
3008
3062
  // MCP roots derive from cwd — tell connected servers to re-query
3009
3063
  // roots/list (Claude-Code 2.1.203 change-notification parity).
3010
3064
  for (const c of [_adhocMcp?.mcpClient, _bundleMcpClient]) {
@@ -3016,6 +3070,34 @@ export async function startAgentRepl(options = {}) {
3016
3070
  }
3017
3071
  }
3018
3072
  }
3073
+ // CwdChanged settings hooks (lifecycle-event parity): the working dir
3074
+ // just changed, so fire CwdChanged with old→new cwd — a hook can
3075
+ // observe/react (re-audit the new dir's trust, reload per-dir config).
3076
+ // Observe-only (a cwd change never gates flow); best-effort; no-op
3077
+ // (byte-unchanged) without a registered CwdChanged hook.
3078
+ if (_settingsHooks && oldCwd !== newCwd) {
3079
+ try {
3080
+ const { runCwdChangedHooks, dispatchAsyncHooks } =
3081
+ await import("../lib/settings-hook-events.cjs");
3082
+ const payload = { oldCwd, newCwd, sessionId };
3083
+ runCwdChangedHooks(_settingsHooks, payload);
3084
+ if (!_asyncHookSupervisor) {
3085
+ const { AsyncHookSupervisor } =
3086
+ await import("../lib/async-hook-supervisor.cjs");
3087
+ _asyncHookSupervisor = new AsyncHookSupervisor({
3088
+ persistStats: true,
3089
+ });
3090
+ }
3091
+ dispatchAsyncHooks(
3092
+ _settingsHooks,
3093
+ "CwdChanged",
3094
+ { old_cwd: oldCwd, cwd: newCwd, session_id: sessionId || null },
3095
+ { cwd: newCwd, supervisor: _asyncHookSupervisor },
3096
+ );
3097
+ } catch {
3098
+ /* CwdChanged firing is best-effort */
3099
+ }
3100
+ }
3019
3101
  } catch (err) {
3020
3102
  logger.error(`/cd failed: ${err.message}`);
3021
3103
  }
@@ -707,6 +707,7 @@ export async function runAgentHeadless(options = {}, deps = {}) {
707
707
  let sideEffectSeq = 0;
708
708
  let currentSideEffectOpId = null;
709
709
  let resumeSideEffectContext = null;
710
+ let resumeAsyncHookContext = null;
710
711
  const persistSideEffectLedger = () => {
711
712
  if (!persist) return;
712
713
  try {
@@ -767,6 +768,34 @@ export async function runAgentHeadless(options = {}, deps = {}) {
767
768
  } catch {
768
769
  resumeSideEffectContext = null;
769
770
  }
771
+ // On resume, recover any async-hook REWAKE (a background check that opted
772
+ // in and FAILED) that the previous run parked but died before draining —
773
+ // surface it to the model instead of silently swallowing the failure. The
774
+ // take also clears the bucket so it isn't replayed on a later resume.
775
+ try {
776
+ const { takePending } = await import("../lib/async-hook-queue.cjs");
777
+ const recovered = takePending(
778
+ { sessionId, now: deps.now ? deps.now() : Date.now() },
779
+ deps.asyncHookQueuePath || undefined,
780
+ deps.asyncHookQueueFs || undefined,
781
+ );
782
+ if (Array.isArray(recovered) && recovered.length > 0) {
783
+ const lines = recovered.map((r) => {
784
+ const detail = r.error || r.additionalContext || "failed";
785
+ return ` • ${r.command} — ${detail}`;
786
+ });
787
+ resumeAsyncHookContext =
788
+ "Recovery notice — the previous run was interrupted after these " +
789
+ "background (async) hook checks FAILED but before you were told. " +
790
+ "Review each failure and decide whether it still needs action:\n" +
791
+ lines.join("\n");
792
+ writeErr(
793
+ `⚠ Recovered ${recovered.length} failed async-hook check(s) from interrupted run (resume ${resumeId}).\n`,
794
+ );
795
+ }
796
+ } catch {
797
+ resumeAsyncHookContext = null;
798
+ }
770
799
  }
771
800
  }
772
801
 
@@ -856,6 +885,7 @@ export async function runAgentHeadless(options = {}, deps = {}) {
856
885
  // rules.md (in buildSystemPrompt) + the cc.md/CLAUDE.md block. Absent flag
857
886
  // (undefined) leaves both paths byte-identical.
858
887
  const _leanNoProjectMemory = options.projectMemory === false;
888
+ let _loadedInstructions = null;
859
889
  const systemContent = composeSystemPrompt(
860
890
  buildSystemPrompt(cwd, {
861
891
  additionalDirectories,
@@ -867,6 +897,9 @@ export async function runAgentHeadless(options = {}, deps = {}) {
867
897
  outputStyle: outputStyleBody,
868
898
  instructionExcludes,
869
899
  projectMemory: _leanNoProjectMemory ? false : undefined,
900
+ onInstructionsLoaded: (loaded) => {
901
+ _loadedInstructions = loaded;
902
+ },
870
903
  },
871
904
  );
872
905
 
@@ -896,6 +929,26 @@ export async function runAgentHeadless(options = {}, deps = {}) {
896
929
  }
897
930
  }
898
931
 
932
+ // settings.json InstructionsLoaded hooks (observe-only): the project-instruction
933
+ // block was just composed — fire with the EXACT loaded file set so a hook can
934
+ // audit which cc.md/CLAUDE.md/AGENTS.md/rules are authoritative this session.
935
+ // Any emitted context is injected like SessionStart's. Best-effort; no-op when
936
+ // project memory is off (no loaded set) or no hook is registered.
937
+ let instructionsLoadedContext = null;
938
+ if (settingsHooks && _loadedInstructions) {
939
+ try {
940
+ const { runInstructionsLoadedHooks } =
941
+ await import("../lib/settings-hook-events.cjs");
942
+ instructionsLoadedContext = runInstructionsLoadedHooks(settingsHooks, {
943
+ files: _loadedInstructions.files,
944
+ cwd,
945
+ sessionId,
946
+ }).additionalContext;
947
+ } catch (_err) {
948
+ instructionsLoadedContext = null;
949
+ }
950
+ }
951
+
899
952
  // settings.json SessionStart hooks → inject session context (observe-only).
900
953
  let sessionStartContext = null;
901
954
  if (settingsHooks) {
@@ -950,12 +1003,18 @@ export async function runAgentHeadless(options = {}, deps = {}) {
950
1003
  // No-op on a healthy alternating transcript.
951
1004
  const messages = mergeConsecutiveMessages([
952
1005
  { role: "system", content: systemContent },
1006
+ ...(instructionsLoadedContext
1007
+ ? [{ role: "system", content: instructionsLoadedContext }]
1008
+ : []),
953
1009
  ...(sessionStartContext
954
1010
  ? [{ role: "system", content: sessionStartContext }]
955
1011
  : []),
956
1012
  ...(resumeSideEffectContext
957
1013
  ? [{ role: "system", content: resumeSideEffectContext }]
958
1014
  : []),
1015
+ ...(resumeAsyncHookContext
1016
+ ? [{ role: "system", content: resumeAsyncHookContext }]
1017
+ : []),
959
1018
  ...history,
960
1019
  { role: "user", content: userMessageContent },
961
1020
  ]);
@@ -1160,7 +1219,16 @@ export async function runAgentHeadless(options = {}, deps = {}) {
1160
1219
  try {
1161
1220
  const { AsyncHookSupervisor } =
1162
1221
  await import("../lib/async-hook-supervisor.cjs");
1163
- _hookSupervisor = new AsyncHookSupervisor({ persistStats: true });
1222
+ _hookSupervisor = new AsyncHookSupervisor({
1223
+ persistStats: true,
1224
+ // Durably park failed-rewake signals keyed by session so a crash before
1225
+ // the turn loop drains them doesn't lose the failure — recovered on the
1226
+ // next `--resume` (see the resume block above). Only a PERSISTABLE
1227
+ // session can be resumed, so the queue follows `persist`; a one-shot /
1228
+ // --ephemeral run stays byte-unchanged (no session id → no writes).
1229
+ persistQueue: persist,
1230
+ sessionId: persist ? sessionId : null,
1231
+ });
1164
1232
  } catch {
1165
1233
  _hookSupervisor = null; // async hooks are best-effort
1166
1234
  }
@@ -1078,6 +1078,7 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
1078
1078
  // cc.md/CLAUDE.md instruction block. Only `=== false` changes anything; when
1079
1079
  // the flag is absent (undefined) both paths stay byte-identical.
1080
1080
  const _leanNoProjectMemory = options.projectMemory === false;
1081
+ let _loadedInstructions = null;
1081
1082
  const systemContent = composeSystemPrompt(
1082
1083
  buildSystemPrompt(cwd, {
1083
1084
  additionalDirectories,
@@ -1093,10 +1094,31 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
1093
1094
  outputStyle: outputStyleBody,
1094
1095
  instructionExcludes,
1095
1096
  projectMemory: _leanNoProjectMemory ? false : undefined,
1097
+ onInstructionsLoaded: (loaded) => {
1098
+ _loadedInstructions = loaded;
1099
+ },
1096
1100
  },
1097
1101
  );
1098
1102
  const messages = [{ role: "system", content: systemContent }];
1099
1103
 
1104
+ // settings.json InstructionsLoaded hooks (observe-only): fire once with the
1105
+ // exact loaded instruction file set right after the system prompt is built.
1106
+ // Best-effort; no-op when project memory is off or no hook is registered.
1107
+ if (settingsHooks && _loadedInstructions) {
1108
+ try {
1109
+ const { runInstructionsLoadedHooks } =
1110
+ await import("../lib/settings-hook-events.cjs");
1111
+ const ctx = runInstructionsLoadedHooks(settingsHooks, {
1112
+ files: _loadedInstructions.files,
1113
+ cwd,
1114
+ sessionId,
1115
+ }).additionalContext;
1116
+ if (ctx) messages.push({ role: "system", content: ctx });
1117
+ } catch (_err) {
1118
+ // best-effort
1119
+ }
1120
+ }
1121
+
1100
1122
  // §3.5.13 flywheel consumption (design module 101): in a PDH session, lead
1101
1123
  // with the user's standing feedback preferences learned ACROSS sessions
1102
1124
  // (corrections + net sentiment) so the agent honours past corrections from