chainlesschain 0.162.34 → 0.162.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-BYfi9NYS.js → AIOps-vAVAFNJ4.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BiS_tAN7.js → ActionButton-BnRHFCKM.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-jiWl_p-B.js → Analytics-BOjwqWqG.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-m4sIzDot.js → AppLayout-Dc0D1Txn.js} +5 -5
  6. package/src/assets/web-panel/assets/{Audit-CPla3Erm.js → Audit-dd_2efaZ.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-BGeQzTaB.js → Backup-HF1jgm8G.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-DTf7Z1iU.js → BaseInput-CCtzmoKe.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-DPTlQlD-.js → Chat-BNfH1c3p.js} +6 -6
  10. package/src/assets/web-panel/assets/{ChatBubbleRenderer-BgRXce4e.js → ChatBubbleRenderer-DCWFqmI4.js} +1 -1
  11. package/src/assets/web-panel/assets/{Checkbox-DY-XuQMu.js → Checkbox-BOr-NscK.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-B6oxPiZI.js → Codegen-DE058N7-.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-Dqxb4wSE.js → Col-SOREo1XE.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-DCIX514p.js → Community-sOvNZo9f.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-BGtCzDoJ.js → Compact-DnBe558D.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-zcOYd55o.js → Compliance-o-r6CUbg.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-DVTtdIdM.js → Cowork-D6_k9mHP.js} +4 -4
  18. package/src/assets/web-panel/assets/{Cron-CPUaR69k.js → Cron-CEV3Xkrm.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-DnjUS6QH.js → Crosschain-eJ1lQWKU.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-Dnz8VDmx.js → DID-B-WqM9Hp.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-CtWf27j7.js → Dashboard-ZnKPcsHN.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-B4GC1ZV4.js → Dropdown-B8uLWDIP.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-wjij3kzr.js → EmailListRenderer-Jmj2Y7aH.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-rS-2W4u5.js → FamilyGuardDashboard-Cb2xetG-.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-90p5Tnoz.js → Federation-C_07GXoq.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-Cnrw7gzq.js → FormItemContext-D3kbYrMU.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-C85NsWa3.js → GenericCardRenderer-9xgqvGPg.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-BFAVM9F8.js → Git-BlwWlMMB.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-DBoRonpq.js → Governance-DxN3wQZ_.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-DHRyD66j.js → Inference-ls7pSw_D.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-CTvUKecD.js → KnowledgeGraph-_n9hYuPI.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-CB0dv_Ts.js → Logs-CvEVY5TK.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-CN7Hm5Uw.js → Marketplace-C3qvQJT7.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-q5H25_8L.js → McpTools-DiwKpnKx.js} +5 -5
  35. package/src/assets/web-panel/assets/{Memory-BCV3pZ1d.js → Memory-CIBPi_da.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-C04Mngt4.js → MobileBridge-D-v0Se8y.js} +2 -2
  37. package/src/assets/web-panel/assets/MobileProjects-cP1apTQD.js +1 -0
  38. package/src/assets/web-panel/assets/{Mtc-ByAMz2DN.js → Mtc-BMFWrI65.js} +4 -4
  39. package/src/assets/web-panel/assets/{MtcAudit-B7V7byJq.js → MtcAudit-2s8LaHtR.js} +2 -2
  40. package/src/assets/web-panel/assets/{Multisig-DtKmcVQV.js → Multisig-dL_nvj7d.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-CaMbT5SC.js → NLProgramming-BbrJp06R.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-DRjbSTCU.js → Notes-jR9irwy3.js} +4 -4
  43. package/src/assets/web-panel/assets/{NotificationSettings-B9YbJID5.js → NotificationSettings-Dk-STCIX.js} +1 -1
  44. package/src/assets/web-panel/assets/{OrderTableRenderer-BcI_-vGS.js → OrderTableRenderer-CqqfY6zq.js} +1 -1
  45. package/src/assets/web-panel/assets/{Organization-oTask4BE.js → Organization-BCK5jylo.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow-Bab06ey7.js → Overflow-BRAY7Smt.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P--wlBeU0N.js → P2P-BltVRGjb.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-D4t77Pwc.js → PdhVaultBrowser-CV8UbXHe.js} +3 -3
  49. package/src/assets/web-panel/assets/{Permissions-B3sf6CJ3.js → Permissions-_tNl47Qh.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-BXOojk63.js → PersonalDataHub-Cgc4HjpX.js} +4 -4
  51. package/src/assets/web-panel/assets/{Pipeline-DReqtBFN.js → Pipeline-Bn_QU4mu.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-cT1GwKLx.js → Privacy-jzJowp5P.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-BhTAzVhH.js → ProjectInit-B_1pJ8qd.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-CK-D8Fyj.js → ProjectSettings-CPVZpXzs.js} +2 -2
  55. package/src/assets/web-panel/assets/{Projects-CbHiwen6.js → Projects-CQsHOWnT.js} +1 -1
  56. package/src/assets/web-panel/assets/{Providers-B-ftiXa8.js → Providers-CzzMiLC0.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-CT5XPwTF.js → QuickAsk-MxBKIn9o.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-CohhlBZ_.js → Recommend-D8lN6Lis.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-CrgbixFz.js → Reputation-CfYK-IrV.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-ClExmBn3.js → Row-Bg7NZDP9.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-VV0qizCJ.js → RssFeed-BOVNJhj0.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-CqJapSiL.js → Search-B38qzmhY.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-DY66Zie6.js → Security-CjqleZpe.js} +4 -4
  64. package/src/assets/web-panel/assets/{Services-RQwxat7-.js → Services-Bu9JSJap.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-0v37UTU_.js → Skeleton-B2RvRkaX.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-B4Vm4DxN.js → Skills-_h42mxMN.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla-CggphTlo.js → Sla-BssLs56D.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-BAOU08C7.js → SpeechSettings-DCxFYHsd.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-DmtC4J1w.js → SyncSettings-D2xQuNLE.js} +2 -2
  70. package/src/assets/web-panel/assets/Tasks-DhpOGOlo.js +1 -0
  71. package/src/assets/web-panel/assets/{Templates-C1QK0YoU.js → Templates-CYG-R-aS.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-CieOfmqp.js → Tenant-BQRYLsvP.js} +1 -1
  73. package/src/assets/web-panel/assets/{Terminal-DWdhrxRq.js → Terminal-imKU7N5j.js} +2 -2
  74. package/src/assets/web-panel/assets/{TimelineRenderer-CjFVUUDU.js → TimelineRenderer-BIZzBftk.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-Bwbk3id9.js → Tokens-uMLH5p_a.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-uJle_yj4.js → Trigger-BzS6XPqx.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-BcOuxAA5.js → Trust-R4zhHufZ.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-DUu7Ufg6.js → UkeySign-DATQCoGe.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-Ck8JtQ2n.js → VideoEditing-ClUmKOtS.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-B3jw43on.js → Wallet-DzJTbQzD.js} +4 -4
  81. package/src/assets/web-panel/assets/{WebAuthn-Baf9K0y7.js → WebAuthn-CrXrLmzQ.js} +5 -5
  82. package/src/assets/web-panel/assets/{WorkflowEditor-CTEDl_83.js → WorkflowEditor-CpvZ0Tma.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-CKV51quV.js → chat-a6wpYmVL.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-BO_RP_yz.js → colors-CXJADb1t.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-BZsxw_ZG.js → compact-item-CL2pohS_.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-CAbvtzVL.js → createContext-xFi_1G5_.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-BtmELbtB.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-QmHT8zDz.js → hasIn-Bchh1rAi.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-fnDgExTu.js → index-B3Tpv7-d.js} +1 -1
  90. package/src/assets/web-panel/assets/index-B4l4vLTB.js +1 -0
  91. package/src/assets/web-panel/assets/{index-BEJa1FiF.js → index-B4zNisy9.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-jd2r-T4p.js → index-B6NehWty.js} +1 -1
  93. package/src/assets/web-panel/assets/index-B7Ek5iiY.js +1 -0
  94. package/src/assets/web-panel/assets/{index-BPZHeug4.js → index-B7knYOpm.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-GPY0LjCu.js → index-B7wT5VRi.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DKnngF_f.js → index-BF4xx1_b.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BRNYA0BV.js → index-BH9t10pe.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DKquNxL2.js → index-BPH5ESqs.js} +3 -3
  99. package/src/assets/web-panel/assets/{index-CEh2Ry_A.js → index-BmsIKzyu.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Dob6B6qS.js → index-BoaRB-4a.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Ha2_56mf.js → index-BrbJBnT-.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Dln_vjSY.js → index-C2eMYASq.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CqiKnXtL.js → index-C4yBRKT4.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-B3fwyCjJ.js → index-CGq4HQno.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-B2aiE8jk.js → index-CMybtJY6.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-B5zhcul9.js → index-CR3kFPuC.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-8BMLlHCv.js → index-CTRd7vkq.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-C6i3reUS.js → index-CdU8BwRW.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BNvTNZ1V.js → index-Cua_P8St.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DjrDGJP2.js → index-CuehgDOp.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BCsZiq4i.js → index-D-TT9Swq.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-qPafbZmr.js → index-DEYcLAl7.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DeC7lehI.js → index-DQ_hw_5P.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BnPBG3Tr.js → index-DTEu7TSF.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-D8CHQnPl.js → index-DVo1GJoj.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-9IqJODII.js → index-DjdOL159.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DBCYOypV.js → index-DsbMVBj1.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BL7gQAuB.js → index-DxahxRP7.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DC1CFfQU.js → index-EPERz4Pu.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CVoYeZ5Q.js → index-IkvkNxbc.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CsBx0u5G.js → index-KCib1PTw.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-5hlO2-JQ.js → index-M8SZI11a.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CSaI8R_7.js → index-TxbHusq2.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-C6AA-xB2.js → index-dsLc7t6W.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DRK0oAV5.js → index-jMcv1u5o.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-B9Z83FTS.js → index-majCS3s2.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-C3K1eHDd.js → index-u8K1y_lh.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-Bc2GWeWe.js → initDefaultProps-DYn3Gc09.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-BI-Rxw6o.js → motion-ZS3eolb9.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-DRPdwDQB.js → move-CEw4uqr3.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-B4XTl3jW.js → omit-DlHFZnPp.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-Do5d86Wr.js → pickAttrs-eZQvV5fA.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-B8VGZ0ZF.js → placementArrow-B31jQwa-.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-Cf0kI_vN.js → responsiveObserve-DAsNmVto.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-Cb0psjSL.js → slide-gPQPrYZC.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-Bjuo5Oal.js → statusUtils-DwWKX5co.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-BLMhoHJ5.js → styleChecker-B3VOtXuH.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-BdCwAfNU.js → useFlexGapSupport-6ADctM2r.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-9Jhaz5gG.js → useFs-6Zx1SSKs.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-xYFyXKwD.js → usePersonalDataHub-BzReowln.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-CVhepE6Z.js → vnode-C8IpEQbD.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-IbbtJ4Zr.js → zoom-ruc9vHr0.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/agent.js +161 -6
  145. package/src/commands/agents.js +199 -0
  146. package/src/commands/command.js +7 -2
  147. package/src/commands/hook.js +136 -28
  148. package/src/commands/ide.js +168 -0
  149. package/src/commands/mcp.js +92 -0
  150. package/src/commands/output-style.js +127 -0
  151. package/src/commands/permissions.js +211 -0
  152. package/src/commands/statusline.js +93 -0
  153. package/src/index.js +8 -0
  154. package/src/lib/agent-core.js +7 -0
  155. package/src/lib/agents.js +147 -0
  156. package/src/lib/hook-manager.js +1 -0
  157. package/src/lib/hook-runner.cjs +183 -0
  158. package/src/lib/ide-bridge.js +310 -0
  159. package/src/lib/image-input.js +156 -0
  160. package/src/lib/mcp-oauth.js +415 -0
  161. package/src/lib/output-styles.js +179 -0
  162. package/src/lib/permission-rules.cjs +325 -0
  163. package/src/lib/provider-options.js +11 -7
  164. package/src/lib/settings-hook-events.cjs +102 -0
  165. package/src/lib/settings-hooks.cjs +163 -0
  166. package/src/lib/settings-loader.cjs +244 -0
  167. package/src/lib/slash-commands.js +21 -13
  168. package/src/lib/status-line.cjs +204 -0
  169. package/src/lib/sub-agent-profiles.js +3 -0
  170. package/src/lib/web-search.js +487 -0
  171. package/src/repl/agent-repl.js +445 -35
  172. package/src/repl/slash-macro.js +45 -0
  173. package/src/runtime/agent-core.js +799 -21
  174. package/src/runtime/coding-agent-contract-shared.cjs +94 -4
  175. package/src/runtime/coding-agent-policy.cjs +24 -0
  176. package/src/runtime/headless-runner.js +162 -6
  177. package/src/runtime/headless-stream.js +133 -7
  178. package/src/runtime/mcp-config.js +161 -15
  179. package/src/runtime/policies/agent-policy.js +1 -0
  180. package/src/runtime/system-prompt.js +6 -1
  181. package/src/assets/web-panel/assets/MobileProjects-CUxONYre.js +0 -1
  182. package/src/assets/web-panel/assets/Tasks-CExqxzL6.js +0 -1
  183. package/src/assets/web-panel/assets/devWarning-DQYatsRR.js +0 -1
  184. package/src/assets/web-panel/assets/index-Bv_y1Ud7.js +0 -1
  185. package/src/assets/web-panel/assets/index-CZZnSJEX.js +0 -1
@@ -0,0 +1,179 @@
1
+ /**
2
+ * output-styles — Claude-Code `/output-style` parity. A named, reusable persona
3
+ * layered onto the agent's system prompt, so `cc agent` can act as a different
4
+ * kind of assistant while keeping its core coding capabilities.
5
+ *
6
+ * Styles are markdown files with frontmatter (`name`, `description`); the body
7
+ * is appended to the system prompt (after the base + `--append-system-prompt`).
8
+ * Discovered from `.chainlesschain/output-styles/` and `.claude/output-styles/`
9
+ * (project) + `~/.claude/output-styles/` (personal); a couple of built-ins ship
10
+ * so it works with no files. The active style comes from `--output-style`, then
11
+ * the `outputStyle` field in `.claude/settings.json`.
12
+ *
13
+ * Pure + zero-dep frontmatter parse (mirrors slash-commands — no js-yaml).
14
+ * `_deps` injection (fs / homedir) for tests.
15
+ */
16
+
17
+ import fsDefault from "node:fs";
18
+ import pathDefault from "node:path";
19
+ import { homedir as homedirDefault } from "node:os";
20
+
21
+ export const _deps = {
22
+ fs: fsDefault,
23
+ path: pathDefault,
24
+ homedir: homedirDefault,
25
+ };
26
+
27
+ /** Built-in styles (shadowed by a same-named file). `default` = no-op. */
28
+ export const BUILTIN_OUTPUT_STYLES = Object.freeze({
29
+ default: {
30
+ name: "default",
31
+ description: "Standard coding assistant (no persona overlay).",
32
+ body: "",
33
+ builtin: true,
34
+ },
35
+ explanatory: {
36
+ name: "explanatory",
37
+ description: "Explains the reasoning and trade-offs behind changes.",
38
+ body: [
39
+ "## Output style: Explanatory",
40
+ "As you work, weave in brief `★ Insight` notes that explain *why* you chose",
41
+ "an approach and any non-obvious trade-offs, so the user learns from the",
42
+ "changes. Keep insights short (1–2 sentences) and tied to what you just did;",
43
+ "do not pad routine steps.",
44
+ ].join("\n"),
45
+ builtin: true,
46
+ },
47
+ learning: {
48
+ name: "learning",
49
+ description: "Collaborative — leaves small instructive pieces for the user.",
50
+ body: [
51
+ "## Output style: Learning",
52
+ "Work collaboratively. When a small, well-scoped piece of the task would be",
53
+ "instructive for the user to write themselves, pause and insert a",
54
+ "`TODO(you):` marker with a one-line explanation of what to do and why,",
55
+ "instead of writing that piece — then continue with the rest. Reserve this",
56
+ "for genuinely educational spots, not boilerplate.",
57
+ ].join("\n"),
58
+ builtin: true,
59
+ },
60
+ });
61
+
62
+ /** Parse `--- ... ---` frontmatter (zero-dep, camelCases keys). */
63
+ function parseFrontmatter(content) {
64
+ const text = String(content || "");
65
+ const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
66
+ if (!m) return { data: {}, body: text.trim() };
67
+ const data = {};
68
+ for (const line of m[1].split(/\r?\n/)) {
69
+ const trimmed = line.trim();
70
+ if (!trimmed || trimmed.startsWith("#")) continue;
71
+ const colon = trimmed.indexOf(":");
72
+ if (colon <= 0) continue;
73
+ const key = trimmed.slice(0, colon).trim();
74
+ let value = trimmed.slice(colon + 1).trim();
75
+ value = value.replace(/^(['"])([\s\S]*)\1$/, "$2");
76
+ const camel = key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
77
+ data[camel] = value;
78
+ }
79
+ return { data, body: (m[2] || "").trim() };
80
+ }
81
+
82
+ /** Directories scanned for style files (project first, then personal). */
83
+ function styleDirs(cwd, home) {
84
+ const { path } = _deps;
85
+ return [
86
+ { dir: path.join(cwd, ".chainlesschain", "output-styles"), scope: "project" },
87
+ { dir: path.join(cwd, ".claude", "output-styles"), scope: "project" },
88
+ { dir: path.join(home, ".claude", "output-styles"), scope: "personal" },
89
+ ];
90
+ }
91
+
92
+ /** Discover all styles: built-ins + files (a file shadows a built-in by name). */
93
+ export function discoverOutputStyles(cwd = process.cwd(), opts = {}) {
94
+ const { fs, path } = _deps;
95
+ const home = opts.home || _deps.homedir();
96
+ const byName = new Map();
97
+ for (const b of Object.values(BUILTIN_OUTPUT_STYLES)) {
98
+ byName.set(b.name, { ...b });
99
+ }
100
+ // Files win over built-ins; project wins over personal (scan personal first).
101
+ for (const { dir, scope } of styleDirs(cwd, home).reverse()) {
102
+ let entries;
103
+ try {
104
+ entries = fs.readdirSync(dir, { withFileTypes: true });
105
+ } catch {
106
+ continue;
107
+ }
108
+ for (const e of entries) {
109
+ if (!e.isFile() || !e.name.endsWith(".md")) continue;
110
+ const file = path.join(dir, e.name);
111
+ let content;
112
+ try {
113
+ content = fs.readFileSync(file, "utf-8");
114
+ } catch {
115
+ continue;
116
+ }
117
+ const { data, body } = parseFrontmatter(content);
118
+ const name = (data.name || e.name.replace(/\.md$/, "")).trim();
119
+ if (!name) continue;
120
+ byName.set(name, {
121
+ name,
122
+ description: data.description || "",
123
+ body,
124
+ scope,
125
+ file,
126
+ builtin: false,
127
+ });
128
+ }
129
+ }
130
+ return [...byName.values()];
131
+ }
132
+
133
+ /** Get one style by name (case-insensitive), or null. */
134
+ export function getOutputStyle(name, cwd = process.cwd(), opts = {}) {
135
+ if (!name) return null;
136
+ const target = String(name).trim().toLowerCase();
137
+ return (
138
+ discoverOutputStyles(cwd, opts).find(
139
+ (s) => s.name.toLowerCase() === target,
140
+ ) || null
141
+ );
142
+ }
143
+
144
+ /** Read the `outputStyle` default from the settings.json hierarchy (last wins). */
145
+ export function settingsDefaultOutputStyle(cwd = process.cwd(), opts = {}) {
146
+ const { fs, path } = _deps;
147
+ const home = opts.home || _deps.homedir();
148
+ const files = [
149
+ path.join(home, ".claude", "settings.json"),
150
+ path.join(cwd, ".claude", "settings.json"),
151
+ path.join(cwd, ".claude", "settings.local.json"),
152
+ ];
153
+ let value = null;
154
+ for (const f of files) {
155
+ try {
156
+ if (!fs.existsSync(f)) continue;
157
+ const data = JSON.parse(fs.readFileSync(f, "utf-8"));
158
+ if (data && typeof data.outputStyle === "string" && data.outputStyle.trim()) {
159
+ value = data.outputStyle.trim();
160
+ }
161
+ } catch {
162
+ // ignore malformed
163
+ }
164
+ }
165
+ return value;
166
+ }
167
+
168
+ /**
169
+ * Resolve the active style's body to append to the system prompt.
170
+ * Precedence: explicit name → settings.json `outputStyle` → none.
171
+ * Returns `{ name, body }` (body may be "" for `default`) or null if unresolved.
172
+ */
173
+ export function resolveOutputStyle(explicitName, cwd = process.cwd(), opts = {}) {
174
+ const name = (explicitName || settingsDefaultOutputStyle(cwd, opts) || "").trim();
175
+ if (!name) return null;
176
+ const style = getOutputStyle(name, cwd, opts);
177
+ if (!style) return { name, body: "", missing: true };
178
+ return { name: style.name, body: style.body || "" };
179
+ }
@@ -0,0 +1,325 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * permission-rules — Claude-Code `permissions.{allow,ask,deny}` rule engine.
5
+ *
6
+ * A rule is a string `Tool(pattern)` (or a bare `Tool` matching every call of
7
+ * that tool). `Tool` may be written with the Claude-Code umbrella name (Bash,
8
+ * Read, Write, Edit, WebFetch, Task, …) or this CLI's own tool name (run_shell,
9
+ * read_file, write_file, …) — both resolve to the same family.
10
+ *
11
+ * Bash(git push:*) → run_shell whose command starts with "git push"
12
+ * Bash(npm run test:*) → run_shell starting with "npm run test"
13
+ * Read(./src/**) → read_file/list_dir on a path under <cwd>/src
14
+ * Edit(//etc/**) → edit_file on an absolute path under /etc
15
+ * WebFetch(domain:example.com) → web_fetch of https://example.com/…
16
+ * Bash → every run_shell call
17
+ *
18
+ * Pure + self-contained (no glob dependency — `globToRegExp` is built in, the
19
+ * repo avoids pulling minimatch/picomatch). Decision precedence is
20
+ * deny > ask > allow; no match returns `{ decision: null }` so callers fall
21
+ * back to the existing risk-tier / shell-policy logic unchanged.
22
+ *
23
+ * This module only *decides*; wiring it into the agent tool loop is a separate
24
+ * step (so it can ship + be unit-tested in isolation).
25
+ */
26
+
27
+ const path = require("node:path");
28
+ const os = require("node:os");
29
+
30
+ const DECISIONS = Object.freeze({
31
+ DENY: "deny",
32
+ ASK: "ask",
33
+ ALLOW: "allow",
34
+ });
35
+
36
+ /**
37
+ * Umbrella (Claude-Code) tool name → the concrete CLI tool names it covers.
38
+ * A rule written with either side resolves to the same family.
39
+ */
40
+ const TOOL_GROUPS = Object.freeze({
41
+ bash: ["run_shell"],
42
+ read: ["read_file", "list_dir"],
43
+ grep: ["search_files"],
44
+ glob: ["search_files"],
45
+ write: ["write_file"],
46
+ edit: ["edit_file", "edit_file_hashed"],
47
+ webfetch: ["web_fetch"],
48
+ websearch: ["web_search"],
49
+ task: ["spawn_sub_agent"],
50
+ skill: ["run_skill", "list_skills"],
51
+ runcode: ["run_code"],
52
+ git: ["git"],
53
+ todowrite: ["todo_write"],
54
+ });
55
+
56
+ /** Tools whose match target is a shell/command string (prefix-style matching). */
57
+ const COMMAND_TOOLS = new Set(["run_shell", "run_code", "git"]);
58
+ /** Tools whose match target is a filesystem path. */
59
+ const PATH_TOOLS = new Set([
60
+ "read_file",
61
+ "list_dir",
62
+ "search_files",
63
+ "write_file",
64
+ "edit_file",
65
+ "edit_file_hashed",
66
+ ]);
67
+ /** Tools whose match target is a URL. */
68
+ const URL_TOOLS = new Set(["web_fetch"]);
69
+
70
+ /**
71
+ * Does a rule's tool token apply to the concrete tool being evaluated?
72
+ * `Bash` → run_shell; `read_file` → read_file only; unknown tokens (e.g.
73
+ * `mcp__srv__do`) fall back to a case-insensitive exact match.
74
+ */
75
+ function toolMatches(ruleTool, actualTool) {
76
+ const r = String(ruleTool || "").toLowerCase();
77
+ const a = String(actualTool || "");
78
+ if (Object.prototype.hasOwnProperty.call(TOOL_GROUPS, r)) {
79
+ return TOOL_GROUPS[r].includes(a);
80
+ }
81
+ return r === a.toLowerCase();
82
+ }
83
+
84
+ /**
85
+ * Parse a rule string into `{ raw, tool, pattern }` (pattern null = bare tool
86
+ * rule). Returns null for a malformed/empty rule so callers can skip it.
87
+ */
88
+ function parseRule(rule) {
89
+ const raw = String(rule || "").trim();
90
+ if (!raw) return null;
91
+ const m = raw.match(/^([A-Za-z_][\w-]*)\s*(?:\(([\s\S]*)\))?$/);
92
+ if (!m) return null;
93
+ const tool = m[1];
94
+ const pattern = m[2] === undefined ? null : m[2].trim();
95
+ return { raw, tool, pattern };
96
+ }
97
+
98
+ /** Convert a glob (`*`, `**`, `?`) into an anchored RegExp. Slash-normalized. */
99
+ function globToRegExp(glob) {
100
+ const s = String(glob || "");
101
+ let re = "";
102
+ for (let i = 0; i < s.length; i++) {
103
+ const c = s[i];
104
+ if (c === "*") {
105
+ if (s[i + 1] === "*") {
106
+ re += ".*";
107
+ i++;
108
+ if (s[i + 1] === "/") i++; // `**/` also matches zero segments
109
+ } else {
110
+ re += "[^/]*";
111
+ }
112
+ } else if (c === "?") {
113
+ re += "[^/]";
114
+ } else {
115
+ re += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
116
+ }
117
+ }
118
+ return new RegExp("^" + re + "$");
119
+ }
120
+
121
+ /** Normalize a filesystem path to forward slashes for glob comparison. */
122
+ function toSlash(p) {
123
+ return String(p || "").replace(/\\/g, "/");
124
+ }
125
+
126
+ /**
127
+ * Resolve the concrete value a path-pattern should be matched against, and the
128
+ * pattern itself, both as absolute slash-normalized strings.
129
+ * ./x or x → relative to cwd
130
+ * //abs/x → absolute (leading `//` is Claude-Code's absolute marker)
131
+ * ~/x → home
132
+ */
133
+ function resolvePathPattern(pattern, cwd) {
134
+ let pat = String(pattern || "");
135
+ if (pat.startsWith("//")) {
136
+ pat = pat.slice(1); // `//etc/**` → `/etc/**`
137
+ } else if (pat.startsWith("~/") || pat === "~") {
138
+ pat = path.join(os.homedir(), pat.slice(1));
139
+ } else {
140
+ pat = path.resolve(cwd || process.cwd(), pat);
141
+ }
142
+ return toSlash(pat);
143
+ }
144
+
145
+ /** Extract the match target (command / path / url) for a tool's args. */
146
+ function extractTarget(actualTool, args, cwd) {
147
+ const a = args || {};
148
+ if (COMMAND_TOOLS.has(actualTool)) {
149
+ return { kind: "command", value: String(a.command || "").trim() };
150
+ }
151
+ if (PATH_TOOLS.has(actualTool)) {
152
+ const raw = a.path || a.file_path || a.dir || a.directory || "";
153
+ if (!raw) return { kind: "path", value: null };
154
+ return {
155
+ kind: "path",
156
+ value: toSlash(path.resolve(cwd || process.cwd(), String(raw))),
157
+ };
158
+ }
159
+ if (URL_TOOLS.has(actualTool)) {
160
+ return { kind: "url", value: String(a.url || "").trim() };
161
+ }
162
+ return { kind: "none", value: null };
163
+ }
164
+
165
+ /** Match a shell command against a Claude-Code Bash-style pattern. */
166
+ function matchCommand(pattern, command) {
167
+ const cmd = String(command || "").trim();
168
+ // `prefix:*` → starts-with prefix (CC idiom: Bash(git push:*))
169
+ if (pattern.endsWith(":*")) {
170
+ const prefix = pattern.slice(0, -2).trim();
171
+ return cmd === prefix || cmd.startsWith(prefix);
172
+ }
173
+ if (pattern.includes("*")) {
174
+ return globToRegExp(pattern).test(cmd);
175
+ }
176
+ return cmd === pattern;
177
+ }
178
+
179
+ /** Match a URL against a `domain:host` or plain glob pattern. */
180
+ function matchUrl(pattern, url) {
181
+ const u = String(url || "");
182
+ if (pattern.startsWith("domain:")) {
183
+ const host = pattern.slice("domain:".length).trim();
184
+ let actualHost = "";
185
+ try {
186
+ actualHost = new URL(u).host;
187
+ } catch {
188
+ return false;
189
+ }
190
+ return globToRegExp(host).test(actualHost);
191
+ }
192
+ return globToRegExp(pattern).test(u);
193
+ }
194
+
195
+ /**
196
+ * Does `pattern` (the inside of `Tool(...)`, or null for a bare rule) match the
197
+ * given tool call? `null` pattern always matches.
198
+ */
199
+ function matchPattern(pattern, actualTool, args, cwd) {
200
+ if (pattern === null) return true;
201
+ const target = extractTarget(actualTool, args, cwd);
202
+ if (target.kind === "command") {
203
+ return target.value ? matchCommand(pattern, target.value) : false;
204
+ }
205
+ if (target.kind === "url") {
206
+ return target.value ? matchUrl(pattern, target.value) : false;
207
+ }
208
+ if (target.kind === "path") {
209
+ if (!target.value) return false;
210
+ return globToRegExp(resolvePathPattern(pattern, cwd)).test(target.value);
211
+ }
212
+ // Tool has no match target but the rule specified a pattern → no match.
213
+ return false;
214
+ }
215
+
216
+ /**
217
+ * Evaluate a tool call against a ruleset.
218
+ *
219
+ * @param {object} input
220
+ * @param {string} input.tool concrete tool name (run_shell, …)
221
+ * @param {object} [input.args] tool arguments
222
+ * @param {string} [input.cwd] base dir for relative path patterns
223
+ * @param {object} input.rules { allow:[], ask:[], deny:[] } of strings
224
+ * @returns {{ decision: 'deny'|'ask'|'allow'|null, rule: string|null }}
225
+ */
226
+ function evaluatePermissionRules({ tool, args = {}, cwd, rules } = {}) {
227
+ const set = rules || {};
228
+ const order = [
229
+ [DECISIONS.DENY, set.deny],
230
+ [DECISIONS.ASK, set.ask],
231
+ [DECISIONS.ALLOW, set.allow],
232
+ ];
233
+ for (const [decision, list] of order) {
234
+ if (!Array.isArray(list)) continue;
235
+ for (const entry of list) {
236
+ const parsed = parseRule(entry);
237
+ if (!parsed) continue;
238
+ if (!toolMatches(parsed.tool, tool)) continue;
239
+ if (matchPattern(parsed.pattern, tool, args, cwd)) {
240
+ return { decision, rule: parsed.raw };
241
+ }
242
+ }
243
+ }
244
+ return { decision: null, rule: null };
245
+ }
246
+
247
+ /** Concrete tool name → the umbrella token a suggested rule should be written
248
+ * with (Claude-Code style; what users expect to see in settings.json). */
249
+ const SUGGEST_UMBRELLA = Object.freeze({
250
+ run_shell: "Bash",
251
+ run_code: "Bash",
252
+ git: "git",
253
+ read_file: "Read",
254
+ list_dir: "Read",
255
+ search_files: "Grep",
256
+ write_file: "Write",
257
+ edit_file: "Edit",
258
+ edit_file_hashed: "Edit",
259
+ web_fetch: "WebFetch",
260
+ web_search: "WebSearch",
261
+ spawn_sub_agent: "Task",
262
+ run_skill: "Skill",
263
+ list_skills: "Skill",
264
+ todo_write: "TodoWrite",
265
+ });
266
+
267
+ /** Commands whose first token is a dispatcher — keep 2 tokens in the prefix. */
268
+ const MULTI_VERB = new Set([
269
+ "git", "npm", "npx", "yarn", "pnpm", "docker", "kubectl", "cargo",
270
+ "go", "pip", "pip3", "python", "python3", "node", "dotnet", "gh", "brew",
271
+ ]);
272
+
273
+ /**
274
+ * Suggest a sensible `allow` rule string for a tool call, for the interactive
275
+ * "always allow" / don't-ask-again flow. Commands → `Bash(<1-2 token prefix>:*)`;
276
+ * paths → `<Umbrella>(<dir>/**)`; urls → `WebFetch(domain:<host>)`; otherwise a
277
+ * bare tool umbrella. Returns null if nothing meaningful can be derived.
278
+ */
279
+ function suggestAllowRule(tool, args = {}) {
280
+ const umbrella = SUGGEST_UMBRELLA[tool] || tool;
281
+ if (COMMAND_TOOLS.has(tool)) {
282
+ const cmd = String(args.command || "").trim();
283
+ if (!cmd) return umbrella;
284
+ const tokens = cmd.split(/\s+/);
285
+ const keep = MULTI_VERB.has(tokens[0]) && tokens.length > 1 ? 2 : 1;
286
+ const prefix = tokens.slice(0, keep).join(" ");
287
+ return `${umbrella}(${prefix}:*)`;
288
+ }
289
+ if (PATH_TOOLS.has(tool)) {
290
+ const raw = args.path || args.file_path || args.dir || args.directory || "";
291
+ if (!raw) return umbrella;
292
+ const slash = String(raw).replace(/\\/g, "/");
293
+ const dir = slash.includes("/") ? slash.slice(0, slash.lastIndexOf("/")) : ".";
294
+ const base = dir === "" ? "/" : dir;
295
+ const norm = /^(\.|\/|~)/.test(base) ? base : `./${base}`;
296
+ return `${umbrella}(${norm}/**)`;
297
+ }
298
+ if (URL_TOOLS.has(tool)) {
299
+ try {
300
+ return `${umbrella}(domain:${new URL(String(args.url || "")).host})`;
301
+ } catch {
302
+ return umbrella;
303
+ }
304
+ }
305
+ return umbrella;
306
+ }
307
+
308
+ module.exports = {
309
+ DECISIONS,
310
+ TOOL_GROUPS,
311
+ COMMAND_TOOLS,
312
+ PATH_TOOLS,
313
+ URL_TOOLS,
314
+ toolMatches,
315
+ parseRule,
316
+ globToRegExp,
317
+ resolvePathPattern,
318
+ extractTarget,
319
+ matchCommand,
320
+ matchUrl,
321
+ matchPattern,
322
+ evaluatePermissionRules,
323
+ suggestAllowRule,
324
+ SUGGEST_UMBRELLA,
325
+ };
@@ -6,12 +6,19 @@
6
6
  * 1. PROVIDER_DEFAULTS[provider] — hand-curated baseline per provider
7
7
  * 2. MODEL_INFERENCE(modelId) — model-specific overrides (e.g. o1
8
8
  * disables temperature, claude-opus
9
- * enables extended thinking)
9
+ * gets a larger maxTokens)
10
10
  * 3. callOverrides — whatever the caller passes
11
11
  *
12
12
  * Later layers win at leaf keys; objects are merged recursively, arrays are
13
13
  * replaced (not concatenated) to keep behavior predictable.
14
14
  *
15
+ * NOTE: extended *thinking* is NOT decided here. It is opt-in via the agent's
16
+ * `--thinking` flag and resolved, model-aware, by `_anthropicThinkingParams`
17
+ * in agent-core.js — the single source of truth. This module contributes only
18
+ * `maxTokens` to the Anthropic request (chatWithTools destructures just that
19
+ * from the merge); a stray `anthropic.thinking` here was a second, divergent
20
+ * config that was never read, so it has been removed.
21
+ *
15
22
  * @module provider-options
16
23
  */
17
24
 
@@ -21,7 +28,6 @@ export const PROVIDER_DEFAULTS = Object.freeze({
21
28
  anthropic: {
22
29
  maxTokens: 8192,
23
30
  temperature: 1.0,
24
- anthropic: { thinking: { type: "disabled" } },
25
31
  },
26
32
  openai: {
27
33
  maxTokens: 4096,
@@ -66,12 +72,10 @@ export function inferModelOverrides(modelId) {
66
72
  return { temperature: undefined, reasoning: { effort: "medium" } };
67
73
  }
68
74
 
69
- // Claude Opus — enable extended thinking by default (users can turn off).
75
+ // Claude Opus — larger default output budget. (Extended thinking is opt-in
76
+ // via `--thinking`, decided by `_anthropicThinkingParams`, not here.)
70
77
  if (id.includes("opus-4") || id.includes("opus-3")) {
71
- return {
72
- maxTokens: 16384,
73
- anthropic: { thinking: { type: "enabled", budgetTokens: 8000 } },
74
- };
78
+ return { maxTokens: 16384 };
75
79
  }
76
80
 
77
81
  // Claude Haiku — cheaper, smaller output by default.
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * settings-hook-events — fire non-tool `.claude/settings.json` hooks
5
+ * (UserPromptSubmit, SessionStart, and generic observe events) that live
6
+ * outside the executeTool seam.
7
+ *
8
+ * Reuses the same loader (collectHooks) + JSON protocol (hook-runner). Tool
9
+ * hooks (PreToolUse/PostToolUse) are dispatched inline in agent-core; this is
10
+ * the prompt/session lifecycle half. DB-backed session hooks (session-hooks.js
11
+ * `fireUserPromptSubmit`) stay separate + observe-only; these settings hooks
12
+ * are decision-capable (block) and can inject `additionalContext`.
13
+ *
14
+ * A hook's non-block stdout becomes context to inject: a JSON
15
+ * `{ "additionalContext": "..." }` field, or — for convenience — plain
16
+ * (non-JSON) stdout text. Exit 2 / `{decision:block}` aborts the turn.
17
+ */
18
+
19
+ const { collectHooks } = require("./settings-hooks.cjs");
20
+ const { runHooks } = require("./hook-runner.cjs");
21
+
22
+ /** Join the context emitted by the hooks that ran (additionalContext / plain stdout). */
23
+ function aggregateContext(results) {
24
+ const parts = [];
25
+ for (const r of results || []) {
26
+ if (r.additionalContext) {
27
+ parts.push(String(r.additionalContext));
28
+ } else if (r.exitCode === 0 && r.stdout) {
29
+ const s = String(r.stdout).trim();
30
+ if (s && s[0] !== "{") parts.push(s); // plain stdout = context to inject
31
+ }
32
+ }
33
+ return parts.length > 0 ? parts.join("\n") : null;
34
+ }
35
+
36
+ /**
37
+ * UserPromptSubmit settings hooks. A `block`/`ask` decision aborts the turn;
38
+ * otherwise any emitted context is returned for the caller to inject.
39
+ * @returns {{ blocked:boolean, reason?:string, hook?:string, additionalContext:string|null }}
40
+ */
41
+ function runUserPromptSubmitHooks(settingsHooks, { prompt, cwd, sessionId } = {}) {
42
+ if (!settingsHooks) return { blocked: false, additionalContext: null };
43
+ const matched = collectHooks(settingsHooks, "UserPromptSubmit", "");
44
+ if (matched.length === 0) return { blocked: false, additionalContext: null };
45
+ const payload = {
46
+ hook_event_name: "UserPromptSubmit",
47
+ prompt: String(prompt || ""),
48
+ cwd,
49
+ session_id: sessionId || null,
50
+ };
51
+ const outcome = runHooks(matched, payload, { cwd, event: "UserPromptSubmit" });
52
+ if (outcome.decision === "block" || outcome.decision === "ask") {
53
+ return {
54
+ blocked: true,
55
+ reason: outcome.reason,
56
+ hook: outcome.hook,
57
+ additionalContext: null,
58
+ };
59
+ }
60
+ return { blocked: false, additionalContext: aggregateContext(outcome.results) };
61
+ }
62
+
63
+ /**
64
+ * SessionStart settings hooks (observe + context injection). The `source`
65
+ * (startup / resume / clear) is the matcher target.
66
+ * @returns {{ additionalContext:string|null }}
67
+ */
68
+ function runSessionStartHooks(settingsHooks, { source, cwd, sessionId } = {}) {
69
+ if (!settingsHooks) return { additionalContext: null };
70
+ const matched = collectHooks(settingsHooks, "SessionStart", source || "");
71
+ if (matched.length === 0) return { additionalContext: null };
72
+ const payload = {
73
+ hook_event_name: "SessionStart",
74
+ source: source || "startup",
75
+ cwd,
76
+ session_id: sessionId || null,
77
+ };
78
+ const outcome = runHooks(matched, payload, { cwd, event: "SessionStart" });
79
+ return { additionalContext: aggregateContext(outcome.results) };
80
+ }
81
+
82
+ /**
83
+ * Generic observe-only fire (SessionEnd / Stop / PreCompact). Returns the raw
84
+ * runHooks outcome so callers can read a block reason; never gates flow here.
85
+ */
86
+ function runObserveHooks(settingsHooks, event, payload = {}, { cwd, matchTarget } = {}) {
87
+ if (!settingsHooks) return { decision: "continue", results: [] };
88
+ const matched = collectHooks(settingsHooks, event, matchTarget || "");
89
+ if (matched.length === 0) return { decision: "continue", results: [] };
90
+ return runHooks(
91
+ matched,
92
+ { hook_event_name: event, cwd, ...payload },
93
+ { cwd, event },
94
+ );
95
+ }
96
+
97
+ module.exports = {
98
+ runUserPromptSubmitHooks,
99
+ runSessionStartHooks,
100
+ runObserveHooks,
101
+ aggregateContext,
102
+ };