chainlesschain 0.162.35 → 0.162.37

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 (193) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-CJn02U42.js → AIOps-_oxz4VHy.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-ewURAAoy.js → ActionButton-uaeqFuDj.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-BiSadESb.js → Analytics-BPVV0OUf.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-BR0WOEug.js → AppLayout-ppCYKm3I.js} +4 -4
  6. package/src/assets/web-panel/assets/{Audit-CrqcYx0e.js → Audit-DFAY6umk.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-DtbSBn4e.js → Backup-pAPBFDyP.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-BjSc9j0o.js → BaseInput-BbBl0uT2.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-ixzrlCJE.js → Chat-Ct22JUnT.js} +6 -6
  10. package/src/assets/web-panel/assets/{ChatBubbleRenderer-B78nEq05.js → ChatBubbleRenderer-DPlsLl22.js} +1 -1
  11. package/src/assets/web-panel/assets/{Checkbox-UGYeSsgr.js → Checkbox-DEkCollc.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-B97OOAg4.js → Codegen-Tor-de39.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-D9aGkaZ6.js → Col-ojNrLQU7.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-Dc2v2RGS.js → Community-CLOGhqMF.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-B_FYlUQR.js → Compact-CYKNlSZ4.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-C4FiTHyC.js → Compliance-C5E6ABuA.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-CQ8j3LIg.js → Cowork-CHeEsZ3W.js} +2 -2
  18. package/src/assets/web-panel/assets/{Cron-Dzjs9Z9Z.js → Cron-B4e1n2e7.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-BXI24uzI.js → Crosschain-DbNV8P9R.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-C-I4_d07.js → DID-C5_Tk3nC.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-BzzGh5mo.js → Dashboard-BhdV_c4N.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-Bh8H70De.js → Dropdown-CEi5AMtM.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-DI_qybJP.js → EmailListRenderer-DOhPiYng.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DkKTsfc4.js → FamilyGuardDashboard-fu4NRP3X.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-DS7CmvVG.js → Federation-B7BtIWKL.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-CI97WsB5.js → FormItemContext-BmPWZVLP.js} +1 -1
  27. package/src/assets/web-panel/assets/GenericCardRenderer-hsOPNJq8.js +1 -0
  28. package/src/assets/web-panel/assets/{Git-CEh0gR2W.js → Git-Bi_EFBUH.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-kIr3tls2.js → Governance-emf2ubDK.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-CC1GzyC1.js → Inference-B7KjKzkI.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-BNgTiWOB.js → KnowledgeGraph-uAaBK0F3.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-B2P10gB1.js → Logs-utK7hNpj.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-HPfBvbFZ.js → Marketplace-CzQe6n3z.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-ByYotSKb.js → McpTools-CuAaJr51.js} +5 -5
  35. package/src/assets/web-panel/assets/{Memory-BGIAzFVS.js → Memory-CRuZZJ75.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-CroNYTAH.js → MobileBridge-Cp06wunh.js} +2 -2
  37. package/src/assets/web-panel/assets/MobileProjects-DJEdUwhr.js +1 -0
  38. package/src/assets/web-panel/assets/{Mtc-BqhyIwo9.js → Mtc-8YY4dR7g.js} +2 -2
  39. package/src/assets/web-panel/assets/{MtcAudit-BpEKOvx9.js → MtcAudit-BmPJYHar.js} +2 -2
  40. package/src/assets/web-panel/assets/{Multisig-DST1d_Qo.js → Multisig-d-ydyVdq.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-DlMsZcK_.js → NLProgramming-DA_ikw_n.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-C734UJvD.js → Notes-DIyF-fRe.js} +3 -3
  43. package/src/assets/web-panel/assets/{NotificationSettings-C0-pPxvk.js → NotificationSettings-CzPZXEtK.js} +1 -1
  44. package/src/assets/web-panel/assets/OrderTableRenderer-BiLtg-LY.js +1 -0
  45. package/src/assets/web-panel/assets/{Organization-C5iHC_yW.js → Organization-DdDZ_Ap6.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow-CovuHHVR.js → Overflow-BnMBkttv.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-Dx9QL-Gy.js → P2P-Es1050f-.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-IP1dEt6-.js → PdhVaultBrowser-CKkRmyn9.js} +4 -4
  49. package/src/assets/web-panel/assets/{Permissions-BrR1XZG5.js → Permissions-zU9n9cAD.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-BgqxVE5m.js → PersonalDataHub-BZi5Xwas.js} +2 -2
  51. package/src/assets/web-panel/assets/{Pipeline-DzMk5HAz.js → Pipeline-CRfeGiFc.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-CDoLa6tk.js → Privacy-CQA_IgLA.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-Dy5gc6ve.js → ProjectInit-C9hmEvoT.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-DXy-k4hG.js → ProjectSettings-yXA72ws4.js} +2 -2
  55. package/src/assets/web-panel/assets/Projects-BpWS-qam.js +1 -0
  56. package/src/assets/web-panel/assets/Providers-Cxe55dRD.js +1 -0
  57. package/src/assets/web-panel/assets/{QuickAsk-B8KEHCnd.js → QuickAsk-Do0aUTQr.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-DNVHGYYZ.js → Recommend--ysZHjyA.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-CaDhWP03.js → Reputation-BOBU8JrH.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-CrGLI02x.js → Row-C6X7bRKE.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-BX7P8I6i.js → RssFeed-D8AwqlkQ.js} +3 -3
  62. package/src/assets/web-panel/assets/Search-Bi3rCZD4.js +1 -0
  63. package/src/assets/web-panel/assets/{Security-B6J7IFc1.js → Security-DxUDVrtY.js} +4 -4
  64. package/src/assets/web-panel/assets/{Services-vvdcO3mM.js → Services-BXXN7yC1.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-BoAoPTzZ.js → Skeleton-B3BR34tZ.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-CyIQV5b3.js → Skills-BjYu8OQ1.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla-BAQVgdZV.js → Sla-DDkCtD8w.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-Bxcn1Jkj.js → SpeechSettings-CGhYzP7V.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-Dpaj3hDM.js → SyncSettings-CYNKVAHA.js} +2 -2
  70. package/src/assets/web-panel/assets/{Tasks-Bwqo89En.js → Tasks-DavmlJpd.js} +1 -1
  71. package/src/assets/web-panel/assets/{Templates-Bowcqifn.js → Templates-CQuYFf2C.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-DOkf85uG.js → Tenant-DdzZh8vE.js} +1 -1
  73. package/src/assets/web-panel/assets/Terminal-D75WeG9d.js +3 -0
  74. package/src/assets/web-panel/assets/{TimelineRenderer-B9A3zDXA.js → TimelineRenderer-DKOARnc_.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-jtVVqKFr.js → Tokens-D7QRNG8y.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-26Iw-iIl.js → Trigger-BCsqLZl4.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-DqY5ORrH.js → Trust-BarGUa6p.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-BFsbr3y7.js → UkeySign-pHrg5a8E.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-BtDbj3oa.js → VideoEditing-Dug3m1py.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-BAwmwHbk.js → Wallet-BfK3Z_Ez.js} +4 -4
  81. package/src/assets/web-panel/assets/{WebAuthn-DINJTsfq.js → WebAuthn-CYRdl9td.js} +5 -5
  82. package/src/assets/web-panel/assets/{WorkflowEditor-BEorm8SK.js → WorkflowEditor-DTW5AcqM.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-CE39-Dxg.js → chat-CCXz4j38.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-C_cLZ93a.js → colors-BJBOhAqa.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-BSioWA2c.js → compact-item-E9M6BQcM.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-CGTk4mhN.js → createContext-Cg9CAws4.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-BrsbTJUv.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-Dl1fRwS_.js → hasIn-DhVtqv5L.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-pngH1and.js → index--7o5YdL6.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-BmbVyhk1.js → index-4N5lNXGP.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-BnEPB1Mz.js → index-6-04M2Nx.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-Cxw3p73X.js → index-B111fZ21.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-CST381Qf.js → index-B4NBF4Sa.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-ChwpS1f0.js → index-B8bjEHrQ.js} +1 -1
  95. package/src/assets/web-panel/assets/{index--SWvw6yW.js → index-BAB0nGP7.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-CAwVwBOL.js → index-BFZPRd0T.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-hv4jUdG3.js → index-B_SMPD4L.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Qj2x55mz.js → index-BxSzyly9.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BWpfxzVm.js → index-ByazO4Q9.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Di6nvW1N.js → index-C-2dUIli.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BhqOTuMW.js → index-CFarAlXj.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CA6K7lZB.js → index-CFp-wdrQ.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DTKEXyaW.js → index-CJ8nNT8h.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-iiZfONfx.js → index-CSiyjCYi.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DTpCUi0m.js → index-CUp_c8Le.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Bvi14vJ7.js → index-CVR_s-pT.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DKEipmR8.js → index-Ca8BYV1g.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DJyeeygd.js → index-CeRlLp3F.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-C9tq8Da8.js → index-ChsSljaN.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-B2QiUEgK.js → index-CkTeBHI9.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-OCxo0X6J.js → index-Cm1m7BJh.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DrWERr8C.js → index-ComyTKz-.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-B016Fsqr.js → index-CznfPnOx.js} +3 -3
  114. package/src/assets/web-panel/assets/{index-CisXVbSt.js → index-D5yC2Ps8.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-C-VVk1Jg.js → index-D7DXdf7x.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DDQx2YFc.js → index-DDcJO27F.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Ds2RzRG0.js → index-DSQazU6J.js} +1 -1
  118. package/src/assets/web-panel/assets/index-DSTQDO-Y.js +1 -0
  119. package/src/assets/web-panel/assets/{index-C4JXchTG.js → index-DaFe1aqY.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BAhinBPR.js → index-DdhnGez0.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-9_mmaR42.js → index-Di5LBXcE.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-D9D4q-qI.js → index-Dwvewrul.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CbXnyoSO.js → index-MdXEhfdJ.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-II3JhQu2.js → index-_PNqQ5mE.js} +1 -1
  125. package/src/assets/web-panel/assets/index-c2U6LV3Q.js +1 -0
  126. package/src/assets/web-panel/assets/{index-C2ly7sCw.js → index-kz1oXl1a.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-Ceo9P9tQ.js → index-wkt-o5q5.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-GOhLA2-f.js → initDefaultProps-iyBaePF-.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-jqxFzHTx.js → motion-RWtj4rgu.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-CSLsp6TA.js → move-CqPRVzpH.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-Cnlrb25c.js → omit-DsvJze25.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-CLqlxWWD.js → pickAttrs-B4tfZBhc.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-BAWIWtul.js → placementArrow-KvHUwXMA.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-CSR1DayS.js → responsiveObserve-DGdJ-b7W.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-CNhoPJOp.js → slide-Cd6ebRmw.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-BZiYHRHW.js → statusUtils-Bg9GcIAn.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-BMoY-Fm5.js → styleChecker-MQjKsG84.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-DhtNdlaS.js → useFlexGapSupport-C241WujP.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-DNPtDOZ4.js → useFs-CMpy7RS4.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-DTdjNvAI.js → usePersonalDataHub-BLHtapKb.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-C9zW9IJ2.js → vnode-DmcTV67c.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-D-6RYJJr.js → zoom-DHL8_0Y8.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 +8 -2
  146. package/src/commands/cli-anything.js +14 -6
  147. package/src/commands/command.js +7 -2
  148. package/src/commands/hook.js +136 -28
  149. package/src/commands/ide.js +168 -0
  150. package/src/commands/loop.js +450 -0
  151. package/src/commands/mcp.js +92 -0
  152. package/src/commands/output-style.js +127 -0
  153. package/src/commands/permissions.js +211 -0
  154. package/src/commands/statusline.js +93 -0
  155. package/src/index.js +10 -2
  156. package/src/lib/agent-core.js +7 -0
  157. package/src/lib/agents.js +5 -0
  158. package/src/lib/hook-manager.js +1 -0
  159. package/src/lib/hook-runner.cjs +183 -0
  160. package/src/lib/ide-bridge.js +310 -0
  161. package/src/lib/image-input.js +156 -0
  162. package/src/lib/loop.js +198 -0
  163. package/src/lib/mcp-oauth.js +415 -0
  164. package/src/lib/output-styles.js +179 -0
  165. package/src/lib/permission-rules.cjs +325 -0
  166. package/src/lib/provider-options.js +11 -7
  167. package/src/lib/settings-hook-events.cjs +102 -0
  168. package/src/lib/settings-hooks.cjs +163 -0
  169. package/src/lib/settings-loader.cjs +244 -0
  170. package/src/lib/slash-commands.js +4 -0
  171. package/src/lib/status-line.cjs +204 -0
  172. package/src/lib/sub-agent-profiles.js +3 -0
  173. package/src/lib/web-search.js +487 -0
  174. package/src/repl/agent-repl.js +450 -35
  175. package/src/repl/slash-macro.js +45 -0
  176. package/src/runtime/agent-core.js +799 -21
  177. package/src/runtime/coding-agent-contract-shared.cjs +94 -4
  178. package/src/runtime/coding-agent-policy.cjs +24 -0
  179. package/src/runtime/headless-runner.js +162 -6
  180. package/src/runtime/headless-stream.js +133 -7
  181. package/src/runtime/mcp-config.js +161 -15
  182. package/src/runtime/policies/agent-policy.js +4 -0
  183. package/src/runtime/system-prompt.js +6 -1
  184. package/src/assets/web-panel/assets/GenericCardRenderer-Da27EdR4.js +0 -1
  185. package/src/assets/web-panel/assets/MobileProjects-CH-qnGEV.js +0 -1
  186. package/src/assets/web-panel/assets/OrderTableRenderer-C7zT9eFc.js +0 -1
  187. package/src/assets/web-panel/assets/Projects-DvsaEbZR.js +0 -1
  188. package/src/assets/web-panel/assets/Providers-Demck9PO.js +0 -1
  189. package/src/assets/web-panel/assets/Search-laS6rz8M.js +0 -1
  190. package/src/assets/web-panel/assets/Terminal-v4MM9dCj.js +0 -3
  191. package/src/assets/web-panel/assets/devWarning-PObcVnJR.js +0 -1
  192. package/src/assets/web-panel/assets/index-BNwIzLyX.js +0 -1
  193. package/src/assets/web-panel/assets/index-Dh6FxR9B.js +0 -1
@@ -145,7 +145,7 @@ const CODING_AGENT_TOOL_CONTRACTS = Object.freeze([
145
145
  kind: "shell",
146
146
  tier: "mvp",
147
147
  description:
148
- "Execute a shell command and return the output. Use for running tests, linting, builds, and other non-git workspace commands.",
148
+ "Execute a shell command and return the output. Use for running tests, linting, builds, and other non-git workspace commands. For long-running commands (builds, full test suites, dev servers) pass run_in_background:true to return a task_id immediately and poll with check_shell instead of blocking.",
149
149
  inputSchema: {
150
150
  type: "object",
151
151
  properties: {
@@ -154,6 +154,16 @@ const CODING_AGENT_TOOL_CONTRACTS = Object.freeze([
154
154
  type: "string",
155
155
  description: "Working directory (optional)",
156
156
  },
157
+ run_in_background: {
158
+ type: "boolean",
159
+ description:
160
+ "Run the command in the background and return a task_id immediately instead of blocking. Use for long-running commands (builds, test suites, dev servers). Poll output and completion with the check_shell tool.",
161
+ },
162
+ timeout: {
163
+ type: "number",
164
+ description:
165
+ "Foreground (synchronous) timeout in milliseconds. Default 60000, max 600000. Ignored when run_in_background is true.",
166
+ },
157
167
  },
158
168
  required: ["command"],
159
169
  },
@@ -168,6 +178,40 @@ const CODING_AGENT_TOOL_CONTRACTS = Object.freeze([
168
178
  tags: ["tool:run_shell", "contract:coding-agent", "tier:mvp"],
169
179
  },
170
180
  },
181
+ {
182
+ name: "check_shell",
183
+ title: "Check Shell",
184
+ kind: "shell",
185
+ tier: "mvp",
186
+ description:
187
+ "Poll a background run_shell task: returns its status, exit code, and any new stdout/stderr since the last check. Omit task_id to list all background tasks. Pass kill:true to terminate a still-running task.",
188
+ inputSchema: {
189
+ type: "object",
190
+ properties: {
191
+ task_id: {
192
+ type: "string",
193
+ description:
194
+ "The task_id returned by run_shell { run_in_background: true }. Omit to list all known background tasks.",
195
+ },
196
+ kill: {
197
+ type: "boolean",
198
+ description:
199
+ "Terminate the task if it is still running (e.g. a dev server you no longer need).",
200
+ },
201
+ },
202
+ required: [],
203
+ },
204
+ ...TOOL_POLICY_METADATA.check_shell,
205
+ runtimeDescriptor: "shell",
206
+ permissions: {
207
+ level: "readonly",
208
+ scopes: ["process:read"],
209
+ },
210
+ telemetry: {
211
+ category: "shell",
212
+ tags: ["tool:check_shell", "contract:coding-agent", "tier:mvp"],
213
+ },
214
+ },
171
215
  {
172
216
  name: "git",
173
217
  title: "Git",
@@ -366,14 +410,19 @@ const CODING_AGENT_TOOL_CONTRACTS = Object.freeze([
366
410
  kind: "agent",
367
411
  tier: "extension",
368
412
  description:
369
- "Spawn an isolated sub-agent to handle a subtask. The sub-agent has its own context and message history, and only returns a summary result. Use this for tasks that benefit from focused, independent execution (e.g. code review, summarization, translation).",
413
+ "Spawn an isolated sub-agent to handle a subtask. The sub-agent has its own context and message history, and only returns a summary result. Use this for tasks that benefit from focused, independent execution (e.g. code review, summarization, translation). Pass `agent` to delegate to a named, pre-defined subagent (its persona + tool scope come from its .md file); otherwise give an ad-hoc `role`.",
370
414
  inputSchema: {
371
415
  type: "object",
372
416
  properties: {
417
+ agent: {
418
+ type: "string",
419
+ description:
420
+ 'Optional name of a pre-defined subagent from .chainlesschain/agents/ or .claude/agents/ (e.g. "review:security"). Loads that file\'s system prompt + tool allow-list. Run `cc agents list` to see them. Explicit `role`/`tools` override the agent\'s values.',
421
+ },
373
422
  role: {
374
423
  type: "string",
375
424
  description:
376
- "Sub-agent role (e.g. code-review, summarizer, translator, debugger)",
425
+ "Sub-agent role (e.g. code-review, summarizer, translator, debugger). Required unless `agent` is given.",
377
426
  },
378
427
  task: {
379
428
  type: "string",
@@ -397,7 +446,7 @@ const CODING_AGENT_TOOL_CONTRACTS = Object.freeze([
397
446
  "Optional declarative profile (from sub-agent-profiles). When set, seeds systemPrompt / tool allowlist / iteration cap. Explicit `tools` overrides the profile allowlist.",
398
447
  },
399
448
  },
400
- required: ["role", "task"],
449
+ required: ["task"],
401
450
  },
402
451
  ...TOOL_POLICY_METADATA.spawn_sub_agent,
403
452
  permissions: {
@@ -449,6 +498,47 @@ const CODING_AGENT_TOOL_CONTRACTS = Object.freeze([
449
498
  tags: ["tool:web_fetch", "contract:coding-agent", "tier:extension"],
450
499
  },
451
500
  },
501
+ {
502
+ name: "web_search",
503
+ title: "Web Search",
504
+ kind: "network",
505
+ tier: "extension",
506
+ description:
507
+ "Search the web for a query and return ranked results (title, url, snippet) plus an optional synthesized answer. Use this to discover URLs, then web_fetch to read a page. Backend is configured via .chainlesschain/config.json:webSearch (default provider: auto — uses whichever API key is set, else keyless DuckDuckGo).",
508
+ inputSchema: {
509
+ type: "object",
510
+ properties: {
511
+ query: {
512
+ type: "string",
513
+ description: "Search query (natural-language keywords)",
514
+ },
515
+ provider: {
516
+ type: "string",
517
+ enum: ["auto", "tavily", "brave", "bocha", "qianfan", "duckduckgo", "searxng", "baidu"],
518
+ description:
519
+ "Override the configured search backend for this call (default: from config / auto)",
520
+ },
521
+ maxResults: {
522
+ type: "number",
523
+ description: "Maximum number of results to return (default: 8)",
524
+ },
525
+ timeout: {
526
+ type: "number",
527
+ description: "Request timeout in ms (default: 15000)",
528
+ },
529
+ },
530
+ required: ["query"],
531
+ },
532
+ ...TOOL_POLICY_METADATA.web_search,
533
+ permissions: {
534
+ level: "readonly",
535
+ scopes: ["network:read"],
536
+ },
537
+ telemetry: {
538
+ category: "network",
539
+ tags: ["tool:web_search", "contract:coding-agent", "tier:extension"],
540
+ },
541
+ },
452
542
  {
453
543
  name: "todo_write",
454
544
  title: "Todo Write",
@@ -115,6 +115,20 @@ const TOOL_POLICY_METADATA = Object.freeze({
115
115
  isReadOnly: false,
116
116
  readOnlySubcommands: READ_ONLY_GIT_SUBCOMMANDS,
117
117
  },
118
+ // Polls a background run_shell task (output + completion). Read-only: it never
119
+ // starts new work — the spawn already happened, gated, under run_shell. The
120
+ // optional kill flag terminates a task the agent itself launched, so it stays
121
+ // low-risk and is allowed during plan mode.
122
+ check_shell: {
123
+ riskLevel: RISK_LEVELS.LOW,
124
+ category: TOOL_CATEGORIES.READ,
125
+ availableInPlanMode: true,
126
+ planModeBehavior: "allow",
127
+ requiresPlanApproval: false,
128
+ requiresConfirmation: false,
129
+ approvalFlow: "auto",
130
+ isReadOnly: true,
131
+ },
118
132
  list_skills: {
119
133
  riskLevel: RISK_LEVELS.LOW,
120
134
  category: TOOL_CATEGORIES.SKILL,
@@ -175,6 +189,16 @@ const TOOL_POLICY_METADATA = Object.freeze({
175
189
  approvalFlow: "auto",
176
190
  isReadOnly: true,
177
191
  },
192
+ web_search: {
193
+ riskLevel: RISK_LEVELS.MEDIUM,
194
+ category: TOOL_CATEGORIES.READ,
195
+ availableInPlanMode: true,
196
+ planModeBehavior: "allow",
197
+ requiresPlanApproval: false,
198
+ requiresConfirmation: false,
199
+ approvalFlow: "auto",
200
+ isReadOnly: true,
201
+ },
178
202
  todo_write: {
179
203
  riskLevel: RISK_LEVELS.LOW,
180
204
  category: TOOL_CATEGORIES.WRITE,
@@ -25,9 +25,10 @@ import {
25
25
  buildSystemPrompt,
26
26
  agentLoop as coreAgentLoop,
27
27
  formatToolArgs,
28
+ killAllBackgroundShellTasks,
28
29
  } from "./agent-core.js";
29
30
  import {
30
- loadMcpConfig,
31
+ resolveAgentMcp,
31
32
  resolvePermissionPromptTool,
32
33
  makePermissionPromptConfirmer,
33
34
  } from "./mcp-config.js";
@@ -44,6 +45,7 @@ import {
44
45
  } from "../harness/jsonl-session-store.js";
45
46
  import { expandFileRefs } from "./file-ref-expander.js";
46
47
  import { composeSystemPrompt } from "./system-prompt.js";
48
+ import { buildUserContent } from "../lib/image-input.js";
47
49
  import { withQuietStdout } from "./quiet-stdout.js";
48
50
 
49
51
  /** Tools that cannot mutate the filesystem or run commands. */
@@ -229,6 +231,41 @@ export async function runAgentHeadless(options = {}, deps = {}) {
229
231
  ? options.additionalDirectories.filter(Boolean)
230
232
  : [];
231
233
 
234
+ // .claude/settings.json permission rules (deny > ask > allow). A `deny` hard-
235
+ // blocks, an `allow` pre-authorizes (so a safe op isn't fail-closed headless),
236
+ // an `ask` falls closed (no human to confirm in headless). No file → null →
237
+ // every existing risk-tier / shell-policy layer runs unchanged.
238
+ let permissionRules = options.permissionRules || null;
239
+ if (!permissionRules) {
240
+ try {
241
+ const { loadSettings } = await import("../lib/settings-loader.cjs");
242
+ const loaded = loadSettings({ cwd, settingsFile: options.settingsFile });
243
+ const total =
244
+ loaded.rules.allow.length +
245
+ loaded.rules.ask.length +
246
+ loaded.rules.deny.length;
247
+ permissionRules = total > 0 ? loaded.rules : null;
248
+ } catch {
249
+ permissionRules = null; // fail-open
250
+ }
251
+ }
252
+
253
+ // .claude/settings.json `hooks` block — decision-capable PreToolUse/
254
+ // PostToolUse hooks (see settings-hooks/hook-runner). null = no hooks.
255
+ let settingsHooks = options.settingsHooks || null;
256
+ if (!settingsHooks) {
257
+ try {
258
+ const { loadHooks } = await import("../lib/settings-hooks.cjs");
259
+ const loaded = loadHooks({ cwd, settingsFile: options.settingsFile });
260
+ settingsHooks =
261
+ loaded.hooks && Object.keys(loaded.hooks).length > 0
262
+ ? loaded.hooks
263
+ : null;
264
+ } catch {
265
+ settingsHooks = null; // fail-open
266
+ }
267
+ }
268
+
232
269
  const runLoop = deps.agentLoop || coreAgentLoop;
233
270
  const doBootstrap = deps.bootstrap || bootstrap;
234
271
  const getApprovalGate =
@@ -340,18 +377,89 @@ export async function runAgentHeadless(options = {}, deps = {}) {
340
377
 
341
378
  // Effective system prompt: built-in base, optionally replaced by
342
379
  // --system-prompt and/or extended by --append-system-prompt.
380
+ // --output-style (or settings.json `outputStyle`) → a persona appended to the
381
+ // system prompt. Resolved best-effort; a missing style is ignored with a warn.
382
+ let outputStyleBody = null;
383
+ try {
384
+ const { resolveOutputStyle } = await import("../lib/output-styles.js");
385
+ const st = resolveOutputStyle(options.outputStyle, cwd);
386
+ if (st && st.missing && options.outputStyle) {
387
+ writeErr(` output-style: unknown style "${options.outputStyle}"\n`);
388
+ } else if (st && st.body) {
389
+ outputStyleBody = st.body;
390
+ }
391
+ } catch {
392
+ outputStyleBody = null;
393
+ }
394
+
343
395
  const systemContent = composeSystemPrompt(
344
396
  buildSystemPrompt(cwd, { additionalDirectories }),
345
397
  {
346
398
  systemPrompt: options.systemPrompt,
347
399
  appendSystemPrompt: options.appendSystemPrompt,
400
+ outputStyle: outputStyleBody,
348
401
  },
349
402
  );
350
403
 
404
+ // settings.json UserPromptSubmit hooks. block → abort the run; context → inject.
405
+ if (settingsHooks) {
406
+ try {
407
+ const { runUserPromptSubmitHooks } = await import(
408
+ "../lib/settings-hook-events.cjs"
409
+ );
410
+ const ups = runUserPromptSubmitHooks(settingsHooks, {
411
+ prompt: userContent,
412
+ cwd,
413
+ sessionId,
414
+ });
415
+ if (ups.blocked) {
416
+ writeErr(
417
+ `[hook] prompt blocked${ups.reason ? ": " + ups.reason : ""}\n`,
418
+ );
419
+ return {
420
+ exitCode: 2,
421
+ result: ups.reason || "blocked by UserPromptSubmit hook",
422
+ isError: true,
423
+ };
424
+ }
425
+ if (ups.additionalContext) {
426
+ userContent += `\n\n[hook context]\n${ups.additionalContext}`;
427
+ }
428
+ } catch (_err) {
429
+ // settings hook dispatch is best-effort
430
+ }
431
+ }
432
+
433
+ // settings.json SessionStart hooks → inject session context (observe-only).
434
+ let sessionStartContext = null;
435
+ if (settingsHooks) {
436
+ try {
437
+ const { runSessionStartHooks } = await import(
438
+ "../lib/settings-hook-events.cjs"
439
+ );
440
+ sessionStartContext = runSessionStartHooks(settingsHooks, {
441
+ source: resumeId ? "resume" : "startup",
442
+ cwd,
443
+ sessionId,
444
+ }).additionalContext;
445
+ } catch (_err) {
446
+ sessionStartContext = null;
447
+ }
448
+ }
449
+
450
+ // --image <path>: attach vision input to the user turn. buildUserContent
451
+ // returns the plain string when there are no images, so text-only runs are
452
+ // byte-for-byte unchanged; with images it builds an OpenAI-style multimodal
453
+ // content array (agent-core converts it per-provider for ollama/anthropic).
454
+ const userMessageContent = buildUserContent(userContent, options.images);
455
+
351
456
  const messages = [
352
457
  { role: "system", content: systemContent },
458
+ ...(sessionStartContext
459
+ ? [{ role: "system", content: sessionStartContext }]
460
+ : []),
353
461
  ...history,
354
- { role: "user", content: userContent },
462
+ { role: "user", content: userMessageContent },
355
463
  ];
356
464
 
357
465
  // Persist the user turn up front (best-effort) so a session is recoverable
@@ -378,12 +486,30 @@ export async function runAgentHeadless(options = {}, deps = {}) {
378
486
  // tools to the LLM (Claude-Code parity). Connection is best-effort — a server
379
487
  // that fails to connect is logged to stderr and contributes no tools; a
380
488
  // missing/empty config file fails fast (the user explicitly asked for MCP).
489
+ // Combine the ad-hoc --mcp-config file with the servers registered via
490
+ // `cc mcp add` (their --auto-connect ones) into ONE client, exposing every
491
+ // tool to the LLM. A bad --mcp-config file fails fast; registered connects
492
+ // are best-effort. --no-mcp disables the registered set (ad-hoc still loads).
381
493
  let mcp = null;
382
- if (options.mcpConfig) {
383
- const doLoadMcp = deps.loadMcpConfig || loadMcpConfig;
494
+ {
495
+ const doResolve = deps.resolveAgentMcp || resolveAgentMcp;
384
496
  try {
385
- mcp = await doLoadMcp(options.mcpConfig, { writeErr });
386
- if (isText) {
497
+ mcp = await doResolve(
498
+ {
499
+ mcpConfigPath: options.mcpConfig || null,
500
+ db: db?.getDatabase?.() || null,
501
+ includeRegistered: options.useRegisteredMcp !== false,
502
+ ide: options.ide,
503
+ cwd: options.cwd || process.cwd(),
504
+ },
505
+ {
506
+ writeErr,
507
+ loadMcpConfig: deps.loadMcpConfig,
508
+ loadRegisteredMcp: deps.loadRegisteredMcp,
509
+ loadIdeMcp: deps.loadIdeMcp,
510
+ },
511
+ );
512
+ if (mcp && isText) {
387
513
  for (const c of mcp.connected) {
388
514
  writeErr(` mcp: ${c.server} (${c.tools} tools)\n`);
389
515
  }
@@ -422,6 +548,11 @@ export async function runAgentHeadless(options = {}, deps = {}) {
422
548
  const loopOptions = {
423
549
  model,
424
550
  provider,
551
+ // Extended thinking (Anthropic; opt-in via --think/--ultrathink). null/off
552
+ // → chatWithTools sends no thinking field. thinkingBudget (--thinking-budget)
553
+ // is the legacy-model budget_tokens override; ignored when thinking is off.
554
+ thinking: options.thinking || null,
555
+ thinkingBudget: options.thinkingBudget || null,
425
556
  baseUrl,
426
557
  apiKey,
427
558
  cwd,
@@ -431,6 +562,8 @@ export async function runAgentHeadless(options = {}, deps = {}) {
431
562
  checkpointSession: options.checkpointSession || sessionId,
432
563
  hookDb: db,
433
564
  approvalGate,
565
+ permissionRules,
566
+ settingsHooks,
434
567
  enabledToolNames,
435
568
  disabledTools,
436
569
  iterationBudget: budget,
@@ -623,6 +756,29 @@ export async function runAgentHeadless(options = {}, deps = {}) {
623
756
  // ignore — disconnect is best-effort
624
757
  }
625
758
  }
759
+ // Kill any background run_shell tasks this run spawned so a backgrounded
760
+ // command (e.g. a dev server) doesn't outlive the headless invocation.
761
+ try {
762
+ killAllBackgroundShellTasks();
763
+ } catch {
764
+ // best-effort — never mask the run's own outcome
765
+ }
766
+ // settings.json SessionEnd hooks (observe-only) when the run finishes.
767
+ if (settingsHooks) {
768
+ try {
769
+ const { runObserveHooks } = await import(
770
+ "../lib/settings-hook-events.cjs"
771
+ );
772
+ runObserveHooks(
773
+ settingsHooks,
774
+ "SessionEnd",
775
+ { reason: "completed", cwd, session_id: sessionId },
776
+ { cwd },
777
+ );
778
+ } catch {
779
+ // observe-only
780
+ }
781
+ }
626
782
  }
627
783
 
628
784
  // coreAgentLoop emits run-ended reason "budget-exhausted" when the iteration
@@ -21,7 +21,7 @@ import { buildSystemPrompt, agentLoop as coreAgentLoop } from "./agent-core.js";
21
21
  import { composeSystemPrompt } from "./system-prompt.js";
22
22
  import { expandFileRefs } from "./file-ref-expander.js";
23
23
  import {
24
- loadMcpConfig,
24
+ resolveAgentMcp,
25
25
  resolvePermissionPromptTool,
26
26
  makePermissionPromptConfirmer,
27
27
  } from "./mcp-config.js";
@@ -145,6 +145,38 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
145
145
  ? options.additionalDirectories.filter(Boolean)
146
146
  : [];
147
147
 
148
+ // .claude/settings.json permission rules (deny > ask > allow); see
149
+ // runAgentHeadless for the full semantics. null = no file → unchanged.
150
+ let permissionRules = options.permissionRules || null;
151
+ if (!permissionRules) {
152
+ try {
153
+ const { loadSettings } = await import("../lib/settings-loader.cjs");
154
+ const loaded = loadSettings({ cwd, settingsFile: options.settingsFile });
155
+ const total =
156
+ loaded.rules.allow.length +
157
+ loaded.rules.ask.length +
158
+ loaded.rules.deny.length;
159
+ permissionRules = total > 0 ? loaded.rules : null;
160
+ } catch {
161
+ permissionRules = null; // fail-open
162
+ }
163
+ }
164
+
165
+ // .claude/settings.json `hooks` block (decision-capable PreToolUse/PostToolUse).
166
+ let settingsHooks = options.settingsHooks || null;
167
+ if (!settingsHooks) {
168
+ try {
169
+ const { loadHooks } = await import("../lib/settings-hooks.cjs");
170
+ const loaded = loadHooks({ cwd, settingsFile: options.settingsFile });
171
+ settingsHooks =
172
+ loaded.hooks && Object.keys(loaded.hooks).length > 0
173
+ ? loaded.hooks
174
+ : null;
175
+ } catch {
176
+ settingsHooks = null; // fail-open
177
+ }
178
+ }
179
+
148
180
  const input = deps.input || process.stdin;
149
181
  const runLoop = deps.agentLoop || coreAgentLoop;
150
182
  const doBootstrap = deps.bootstrap || bootstrap;
@@ -191,15 +223,42 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
191
223
  approvalGate = null;
192
224
  }
193
225
 
226
+ // --output-style (or settings.json `outputStyle`) persona, appended.
227
+ let outputStyleBody = null;
228
+ try {
229
+ const { resolveOutputStyle } = await import("../lib/output-styles.js");
230
+ const st = resolveOutputStyle(options.outputStyle, cwd);
231
+ if (st && st.body) outputStyleBody = st.body;
232
+ } catch {
233
+ outputStyleBody = null;
234
+ }
194
235
  const systemContent = composeSystemPrompt(
195
236
  buildSystemPrompt(cwd, { additionalDirectories }),
196
237
  {
197
238
  systemPrompt: options.systemPrompt,
198
239
  appendSystemPrompt: options.appendSystemPrompt,
240
+ outputStyle: outputStyleBody,
199
241
  },
200
242
  );
201
243
  const messages = [{ role: "system", content: systemContent }];
202
244
 
245
+ // settings.json SessionStart hooks → inject session context once (observe-only).
246
+ if (settingsHooks) {
247
+ try {
248
+ const { runSessionStartHooks } = await import(
249
+ "../lib/settings-hook-events.cjs"
250
+ );
251
+ const ctx = runSessionStartHooks(settingsHooks, {
252
+ source: "startup",
253
+ cwd,
254
+ sessionId,
255
+ }).additionalContext;
256
+ if (ctx) messages.push({ role: "system", content: ctx });
257
+ } catch (_err) {
258
+ // best-effort
259
+ }
260
+ }
261
+
203
262
  emit({
204
263
  type: "system",
205
264
  subtype: "init",
@@ -232,14 +291,29 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
232
291
  }
233
292
  }
234
293
 
235
- // --mcp-config: connect ad-hoc MCP servers for the whole stream session and
236
- // expose their tools to the LLM (Claude-Code parity). Best-effort connect; a
237
- // missing/empty config fails the session up front.
294
+ // Combine the ad-hoc --mcp-config file with the registered (cc mcp add)
295
+ // auto-connect servers into one client for the whole stream session, exposing
296
+ // every tool to the LLM. A bad --mcp-config file fails up front; registered
297
+ // connects are best-effort. --no-mcp disables the registered set.
238
298
  let mcp = null;
239
- if (options.mcpConfig) {
240
- const doLoadMcp = deps.loadMcpConfig || loadMcpConfig;
299
+ {
300
+ const doResolve = deps.resolveAgentMcp || resolveAgentMcp;
241
301
  try {
242
- mcp = await doLoadMcp(options.mcpConfig, { writeErr });
302
+ mcp = await doResolve(
303
+ {
304
+ mcpConfigPath: options.mcpConfig || null,
305
+ db: db?.getDatabase?.() || null,
306
+ includeRegistered: options.useRegisteredMcp !== false,
307
+ ide: options.ide,
308
+ cwd: options.cwd || process.cwd(),
309
+ },
310
+ {
311
+ writeErr,
312
+ loadMcpConfig: deps.loadMcpConfig,
313
+ loadRegisteredMcp: deps.loadRegisteredMcp,
314
+ loadIdeMcp: deps.loadIdeMcp,
315
+ },
316
+ );
243
317
  } catch (err) {
244
318
  emit({
245
319
  type: "result",
@@ -281,6 +355,10 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
281
355
  const loopOptionsBase = {
282
356
  model,
283
357
  provider,
358
+ // Extended thinking (Anthropic; opt-in via --think/--ultrathink).
359
+ // thinkingBudget (--thinking-budget) = legacy-model budget_tokens override.
360
+ thinking: options.thinking || null,
361
+ thinkingBudget: options.thinkingBudget || null,
284
362
  baseUrl,
285
363
  apiKey,
286
364
  cwd,
@@ -288,6 +366,8 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
288
366
  sessionId,
289
367
  hookDb: db,
290
368
  approvalGate,
369
+ permissionRules,
370
+ settingsHooks,
291
371
  enabledToolNames,
292
372
  disabledTools,
293
373
  prepareCall: goalPrepareCallFn,
@@ -345,6 +425,35 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
345
425
  for (const w of expanded.warnings) writeErr(` @ref: ${w}\n`);
346
426
  }
347
427
 
428
+ // settings.json UserPromptSubmit hooks. block → skip this turn; context → inject.
429
+ if (settingsHooks) {
430
+ try {
431
+ const { runUserPromptSubmitHooks } = await import(
432
+ "../lib/settings-hook-events.cjs"
433
+ );
434
+ const ups = runUserPromptSubmitHooks(settingsHooks, {
435
+ prompt: userContent,
436
+ cwd,
437
+ sessionId,
438
+ });
439
+ if (ups.blocked) {
440
+ emit({
441
+ type: "result",
442
+ subtype: "blocked",
443
+ is_error: true,
444
+ result: ups.reason || "blocked by UserPromptSubmit hook",
445
+ session_id: sessionId,
446
+ });
447
+ continue;
448
+ }
449
+ if (ups.additionalContext) {
450
+ userContent += `\n\n[hook context]\n${ups.additionalContext}`;
451
+ }
452
+ } catch (_err) {
453
+ // settings hook dispatch is best-effort
454
+ }
455
+ }
456
+
348
457
  messages.push({ role: "user", content: userContent });
349
458
  turns += 1;
350
459
 
@@ -396,6 +505,23 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
396
505
  }
397
506
  }
398
507
 
508
+ // settings.json SessionEnd hooks (observe-only) when stdin closes.
509
+ if (settingsHooks) {
510
+ try {
511
+ const { runObserveHooks } = await import(
512
+ "../lib/settings-hook-events.cjs"
513
+ );
514
+ runObserveHooks(
515
+ settingsHooks,
516
+ "SessionEnd",
517
+ { reason: "stdin_closed", cwd, session_id: sessionId },
518
+ { cwd },
519
+ );
520
+ } catch {
521
+ // observe-only
522
+ }
523
+ }
524
+
399
525
  emit({ type: "system", subtype: "end", session_id: sessionId, turns });
400
526
  return { exitCode: sawError ? 1 : 0, turns };
401
527
  }