chainlesschain 0.162.71 → 0.162.73

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 (176) hide show
  1. package/package.json +3 -2
  2. package/src/assets/web-panel/assets/{AIOps-pes7_jGr.js → AIOps-DTI_-iZ_.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-DiHWdeH1.js → ActionButton-CtpbGicW.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-C51UX-V_.js → Analytics-C1HwMd7E.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-BE6LJLw9.js → AppLayout-Db953rqB.js} +5 -5
  6. package/src/assets/web-panel/assets/{Audit-CtTkLTe2.js → Audit-CuIi9Vup.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-lmpVwPxc.js → Backup-DwR7Wlve.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-Dr7DR1E1.js → BaseInput-B5wNMp7S.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-DXph6klA.js → Chat-DivalHgk.js} +6 -6
  10. package/src/assets/web-panel/assets/{ChatBubbleRenderer-RefrXxqp.js → ChatBubbleRenderer-BwPSr02C.js} +1 -1
  11. package/src/assets/web-panel/assets/{Checkbox-BF0aS5R9.js → Checkbox-CJrXPCJH.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-B-oHWNTV.js → Codegen-BieZvOkC.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-B6KdyRTE.js → Col-BzvPsQgb.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-D5ac0KyO.js → Community-CWcAMlpQ.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-Dc61O2eQ.js → Compact-Bj_BLBpw.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-a48qvNmd.js → Compliance-DKvo0WAi.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-CIF9lSRj.js → Cowork-DRajk8JO.js} +3 -3
  18. package/src/assets/web-panel/assets/{Cron-C_gkf4xd.js → Cron-DPz9MoUo.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-CJt15PzW.js → Crosschain-BpSuvmns.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-D8I_okEj.js → DID-Co98G00H.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-O4q-qCF2.js → Dashboard-8TSG0W7f.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-flRziiaw.js → Dropdown-CKViu7w3.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-CnnkfGPw.js → EmailListRenderer-BqfdADvJ.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CYAJpGqd.js → FamilyGuardDashboard-aWu1imGZ.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-Dm3WqzQ_.js → Federation-w4YpT8EN.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-CpAIter6.js → FormItemContext-CAk4CXid.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-ChzXcyuN.js → GenericCardRenderer-MoJw4J2L.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-DElmMsL4.js → Git-HNU_a9Jz.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-EWmgn9io.js → Governance-BkIXWYUS.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-VtOR_sef.js → Inference-CnquB88h.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-OMqlGkMR.js → KnowledgeGraph-PGSMF-uq.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-CTgYDsTZ.js → Logs-DCc-nYzu.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-DZIUTbn8.js → Marketplace-CyGX8XEt.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-DWeOErCA.js → McpTools-DBkZ5yUL.js} +4 -4
  35. package/src/assets/web-panel/assets/{Memory-DwFBp-ev.js → Memory-DmYLRCwo.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-D42xBfE3.js → MobileBridge-CszJjeNc.js} +2 -2
  37. package/src/assets/web-panel/assets/{MobileProjects-BHCHauUl.js → MobileProjects-DWgtDsta.js} +1 -1
  38. package/src/assets/web-panel/assets/{Mtc-CabnNsyS.js → Mtc-Bmt_Pmg4.js} +3 -3
  39. package/src/assets/web-panel/assets/{MtcAudit-Ddx9Awu5.js → MtcAudit-BmG0ys0l.js} +2 -2
  40. package/src/assets/web-panel/assets/{Multisig-CJpYBZ8f.js → Multisig-DNd5iMmD.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-BvPJskVG.js → NLProgramming-yxbNl09B.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-92ELvJM2.js → Notes-CQ0qAzI4.js} +3 -3
  43. package/src/assets/web-panel/assets/{NotificationSettings-CXz0SUzP.js → NotificationSettings-CIzkE4ao.js} +1 -1
  44. package/src/assets/web-panel/assets/{OrderTableRenderer-4iWRkMr-.js → OrderTableRenderer-e1VMUxTi.js} +1 -1
  45. package/src/assets/web-panel/assets/{Organization-DblqJPeY.js → Organization-BNBIlf_R.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow-CHfS3HwD.js → Overflow-C5GzzJTq.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-DIg0pLIG.js → P2P-DjL2jIED.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-BhD3DGjk.js → PdhVaultBrowser-C2EYDmpW.js} +3 -3
  49. package/src/assets/web-panel/assets/{Permissions-Ct7bwrz8.js → Permissions-BVGCXxDp.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-DG9c1eas.js → PersonalDataHub-B1xBro16.js} +4 -4
  51. package/src/assets/web-panel/assets/{Pipeline-BwQnEqG0.js → Pipeline-DIYWiDnt.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-BvWaf-Ba.js → Privacy--Qx3QdO1.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-BFzMHtiW.js → ProjectInit-DYOEcV04.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-DDpokSpH.js → ProjectSettings-BcOx5-KS.js} +2 -2
  55. package/src/assets/web-panel/assets/{Projects-3IL8lyPl.js → Projects-CO69txk4.js} +1 -1
  56. package/src/assets/web-panel/assets/{Providers-C6BsEzSR.js → Providers-Bj8pCgzo.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-Mh6690Ey.js → QuickAsk-Cukpq-4s.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-CJbZ6wgA.js → Recommend-DJB5wUfM.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-GDcyl6iQ.js → Reputation-B9t0-nT0.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-6p4DKFSi.js → Row-4cniRgxC.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-BLhrkoDq.js → RssFeed-Y4db_T9t.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-Ckiyt35t.js → Search-DggZDzlc.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-qhh-B5Qt.js → Security-C67A6kED.js} +4 -4
  64. package/src/assets/web-panel/assets/{Services-D7a2OwKm.js → Services-BocBjvaa.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-CSMxupKd.js → Skeleton-C9jI-iOy.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-CGpMng8w.js → Skills-2baFHKOW.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla-CYtaU3Ar.js → Sla-CnowXeRb.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-BTCsu1uM.js → SpeechSettings-DWGBkmUt.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-Cnzx2L5I.js → SyncSettings-B89qwCgX.js} +2 -2
  70. package/src/assets/web-panel/assets/Tasks-C1_WvNc0.js +1 -0
  71. package/src/assets/web-panel/assets/{Templates-Ccdx_C3w.js → Templates-P8dBhbxa.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-ZN8c86j3.js → Tenant-DHo0pxrm.js} +1 -1
  73. package/src/assets/web-panel/assets/Terminal-Dr-HvjH-.js +3 -0
  74. package/src/assets/web-panel/assets/{TimelineRenderer-CjgirE9d.js → TimelineRenderer-DM7dOaUR.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-QoRpi6Wf.js → Tokens-DnJxqOPM.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-CEQKJkhs.js → Trigger-C-FLgMzr.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-UTuAzV1q.js → Trust-CIT_RVqA.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-JDs9I7fl.js → UkeySign-Cg9VS6y6.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-BACYQQSd.js → VideoEditing-BzoxE8mA.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-C-SKluhH.js → Wallet-ZkmCII8R.js} +3 -3
  81. package/src/assets/web-panel/assets/{WebAuthn-9chizTFy.js → WebAuthn-CptQdp3u.js} +5 -5
  82. package/src/assets/web-panel/assets/{WorkflowEditor-0mF-LAUo.js → WorkflowEditor-Vkz28khO.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-Di7QINiP.js → chat-DXdmvvEA.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-SBmhHMM9.js → colors-De7FCj5l.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-BmbETbNj.js → compact-item-DnT3XzME.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-C7hFnsJ7.js → createContext-ECZfCCqd.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-gaA8VqZf.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-CFtthKDD.js → hasIn-Uodg6pAt.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-DRBc1Ewn.js → index-1dWvOqru.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-WjZVyLn7.js → index-BC8SOuGi.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-CovTrPpI.js → index-BUMNJRCl.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-CSnTUPQx.js → index-Bg1RsfAJ.js} +3 -3
  93. package/src/assets/web-panel/assets/{index-NxxQTlPJ.js → index-Blo85ZKd.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-ClIp4Vin.js → index-BtJ8BHU0.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-B4etIqbf.js → index-C01WM7Pk.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DWfObC0n.js → index-C4KhsGXo.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-sz0w-D-C.js → index-C6JgjkY7.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-lRVjtXeH.js → index-C9_Cnvha.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BCcCs7LJ.js → index-CCguvASR.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-C6tZzsb9.js → index-CLzPxAp4.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-D0yO3vWh.js → index-CMuqxCVw.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BcawAm_i.js → index-COTabJQd.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-Cib-RTws.js → index-CPjI3AGE.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-C07kpleB.js → index-CaDZ-LMg.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DEYnWiq1.js → index-CbbfcGeJ.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CgXQwqXr.js → index-CcLDjTgI.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-C2CN7coN.js → index-CffZBdA1.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-PN02VlUB.js → index-CwXP0KcT.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-_hO8-EnW.js → index-D2YOclGO.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Dk4ez5rf.js → index-DBsl2SuZ.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-D6vNUp6c.js → index-DCvmjxu9.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-sA4vr5WV.js → index-DGwgEqwl.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DI2xH1fU.js → index-DOIeo8Qs.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-_a2NG3iP.js → index-DPcpxSQt.js} +1 -1
  115. package/src/assets/web-panel/assets/index-DTpmiTGK.js +1 -0
  116. package/src/assets/web-panel/assets/{index-CWDk3nDZ.js → index-Db33l8Ix.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CNpb8m5a.js → index-Dbfj4QL5.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Dww6gCzI.js → index-Dd9Bx3tJ.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-Bpo7UVJe.js → index-Dkvf5upf.js} +1 -1
  120. package/src/assets/web-panel/assets/index-DwpvMYUU.js +1 -0
  121. package/src/assets/web-panel/assets/{index-BT2uOwmA.js → index-Dy0AT-uc.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-C2fhXmhW.js → index-FU8Zo5cp.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BcRTX_WO.js → index-JVwNC4yP.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-Dps9xdE8.js → index-JqSxr1A7.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-D134XFWR.js → index-KV6dXwKC.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-D8CeUlN0.js → index-LwNszM-5.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CcBhsVYn.js → index-cHnmjGqB.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-B1zCwkvT.js → initDefaultProps-B3H1sf95.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-BDGIV9KA.js → motion-DreYO5eb.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-BITqgluK.js → move-B0OVU04M.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-uD97QKBF.js → omit-PcEEXHLZ.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-CZpXhSIE.js → pickAttrs-DH6-hDkF.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-ByUvT_08.js → placementArrow-DFamUSEw.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-BQG9LlBs.js → responsiveObserve-DT1grAyh.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-Bx9TyeEE.js → slide-41lCsY6d.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-XWaMqYM8.js → statusUtils-FzJ7j2T7.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-BZ-nMyDB.js → styleChecker-D0Zvn50h.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-D_iwA3vx.js → useFlexGapSupport-BTxsJE5n.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-DuvW_aRX.js → useFs-DRVttEr3.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-BO7orS0d.js → usePersonalDataHub-BSe7NVv0.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-Cc_fqAL4.js → vnode-D8O7tUXh.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-BGkBp90M.js → zoom-CMQc1RZ4.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/agent.js +12 -0
  145. package/src/commands/cowork.js +8 -0
  146. package/src/commands/crosschain.js +32 -4
  147. package/src/commands/init.js +10 -10
  148. package/src/commands/loop.js +9 -3
  149. package/src/commands/memory.js +6 -4
  150. package/src/commands/orchestrate.js +5 -2
  151. package/src/commands/video.js +5 -1
  152. package/src/lib/agents.js +16 -5
  153. package/src/lib/cowork-workflow.js +357 -136
  154. package/src/lib/micro-compact.js +52 -0
  155. package/src/lib/output-styles.js +41 -7
  156. package/src/lib/permission-rules.cjs +39 -0
  157. package/src/lib/project-root.cjs +87 -0
  158. package/src/lib/settings-hooks.cjs +18 -6
  159. package/src/lib/settings-loader.cjs +12 -5
  160. package/src/lib/skill-loader.js +62 -43
  161. package/src/lib/slash-commands.js +18 -2
  162. package/src/repl/agent-repl.js +228 -20
  163. package/src/repl/chat-repl.js +4 -2
  164. package/src/repl/permission-tier.js +60 -0
  165. package/src/repl/stream-decision.js +16 -0
  166. package/src/repl/think-command.js +36 -0
  167. package/src/runtime/agent-core.js +67 -10
  168. package/src/runtime/file-ref-expander.js +209 -18
  169. package/src/runtime/headless-runner.js +3 -3
  170. package/src/runtime/headless-stream.js +16 -3
  171. package/src/runtime/mcp-config.js +78 -1
  172. package/src/assets/web-panel/assets/Tasks-BhnGtqaZ.js +0 -1
  173. package/src/assets/web-panel/assets/Terminal-BfgM0oyN.js +0 -3
  174. package/src/assets/web-panel/assets/devWarning-gNvbyy4j.js +0 -1
  175. package/src/assets/web-panel/assets/index-BNXpMnIY.js +0 -1
  176. package/src/assets/web-panel/assets/index-gfHxqT1Q.js +0 -1
@@ -79,7 +79,7 @@ import {
79
79
  listBackgroundShellTasks,
80
80
  } from "../runtime/agent-core.js";
81
81
  import { formatBackgroundTasks } from "./tasks-status.js";
82
- import { expandFileRefs } from "../runtime/file-ref-expander.js";
82
+ import { expandFileRefsAsync } from "../runtime/file-ref-expander.js";
83
83
  import { composeSystemPrompt } from "../runtime/system-prompt.js";
84
84
  import {
85
85
  makeFallbackChatFn,
@@ -88,6 +88,13 @@ import {
88
88
  import { resolveSlashMacro } from "./slash-macro.js";
89
89
  import { expandMcpPrompt, renderMcpSurface } from "./mcp-prompt.js";
90
90
  import { newCostStore, addUsage } from "./session-cost.js";
91
+ import { parseThinkCommand } from "./think-command.js";
92
+ import { shouldStreamLive } from "./stream-decision.js";
93
+ import {
94
+ parsePermissionTier,
95
+ describeTier,
96
+ nextTier,
97
+ } from "./permission-tier.js";
91
98
 
92
99
  /**
93
100
  * Reference to the runtime DB for hook execution (set during startAgentRepl)
@@ -230,6 +237,13 @@ async function agentLoop(messages, options) {
230
237
  } else if (event.result?.success) {
231
238
  process.stdout.write(chalk.green(` Done\n`));
232
239
  }
240
+ } else if (event.type === "thinking") {
241
+ // Intermediate-step reasoning (before a tool call) — dimmed, inline.
242
+ if (process.env.CC_REPL_THINKING !== "0" && event.text) {
243
+ process.stdout.write(
244
+ "\n" + chalk.dim("💭 " + event.text.replace(/\n/g, "\n ")) + "\n",
245
+ );
246
+ }
233
247
  } else if (event.type === "token-usage") {
234
248
  usageEvents.push(event);
235
249
  } else if (event.type === "iteration-warning") {
@@ -239,7 +253,7 @@ async function agentLoop(messages, options) {
239
253
  chalk.red(`\n [Budget Exhausted] ${event.budget}\n`),
240
254
  );
241
255
  } else if (event.type === "response-complete") {
242
- return { content: event.content, usageEvents };
256
+ return { content: event.content, usageEvents, thinking: event.thinking };
243
257
  }
244
258
  }
245
259
  return { content: "", usageEvents };
@@ -252,10 +266,16 @@ export async function startAgentRepl(options = {}) {
252
266
  let model = options.model || "qwen2.5:7b";
253
267
  let provider = options.provider || "ollama";
254
268
  // Extended thinking (Anthropic; opt-in via --think/--ultrathink). Carried from
255
- // the runtime policy into the agent-loop options below. thinkingBudget
269
+ // the runtime policy into the agent-loop options below. Mutable so the
270
+ // `/think` · `/ultrathink` slash commands can toggle it mid-session (the
271
+ // per-turn agentLoop call below reads the current value). thinkingBudget
256
272
  // (--thinking-budget) is the companion legacy-model budget_tokens override.
257
- const thinking = options.thinking || null;
273
+ let thinking = options.thinking || null;
258
274
  const thinkingBudget = options.thinkingBudget || null;
275
+ // Current ApprovalGate session tier (strict|trusted|autopilot), mirrored here
276
+ // so Shift+Tab can cycle it and `/permissions <tier>` can set it. Kept in sync
277
+ // with _approvalGate.setSessionPolicy below.
278
+ let _sessionTier = "strict";
259
279
  const baseUrl = options.baseUrl || "http://localhost:11434";
260
280
  const apiKey = options.apiKey || null;
261
281
  // Extra workspace roots (--add-dir): advertised in the system prompt and
@@ -325,6 +345,30 @@ export async function startAgentRepl(options = {}) {
325
345
  // Set hook DB reference for tool pipeline
326
346
  _hookDb = db;
327
347
 
348
+ // Live token streaming (Claude-Code parity): stream the answer (and reasoning)
349
+ // token-by-token as the LLM produces it, instead of replaying the finished
350
+ // text with a typewriter. ONLY safe when no AssistantResponse hook is
351
+ // registered — such a hook can rewrite/suppress the final answer, which is
352
+ // impossible once it's already on screen. CC_REPL_STREAM=0 forces the replay.
353
+ let _arHookCount = 0;
354
+ if (_hookDb) {
355
+ try {
356
+ const { listHooks } = await import("../lib/hook-manager.js");
357
+ _arHookCount = (
358
+ listHooks(_hookDb, {
359
+ event: "AssistantResponse",
360
+ enabledOnly: true,
361
+ }) || []
362
+ ).length;
363
+ } catch {
364
+ _arHookCount = -1; // unknown → shouldStreamLive treats as unsafe
365
+ }
366
+ }
367
+ const _streamLive = shouldStreamLive({
368
+ streamEnv: process.env.CC_REPL_STREAM,
369
+ arHookCount: _arHookCount,
370
+ });
371
+
328
372
  // Wire the persistent ApprovalGate singleton (approval-policies.json) with
329
373
  // a readline confirm prompt. agent-core's run_shell branch gates
330
374
  // MEDIUM/HIGH-risk commands against the session's policy tier
@@ -698,6 +742,9 @@ export async function startAgentRepl(options = {}) {
698
742
  sessionId,
699
743
  _bundleResolved.approvalPolicy.default,
700
744
  );
745
+ // Mirror it so Shift+Tab cycling starts from the real tier.
746
+ const applied = parsePermissionTier(_bundleResolved.approvalPolicy.default);
747
+ if (applied) _sessionTier = applied;
701
748
  } catch (_err) {
702
749
  // Non-critical — invalid policy value is silently ignored
703
750
  }
@@ -848,6 +895,7 @@ export async function startAgentRepl(options = {}) {
848
895
  "/ide",
849
896
  "/mcp",
850
897
  "/memory",
898
+ "/microcompact",
851
899
  "/model",
852
900
  "/output-style",
853
901
  "/permissions",
@@ -869,6 +917,8 @@ export async function startAgentRepl(options = {}) {
869
917
  "/tasks",
870
918
  "/terminal-setup",
871
919
  "/theme",
920
+ "/think",
921
+ "/ultrathink",
872
922
  "/vim",
873
923
  ],
874
924
  getIdeOpenFiles: async () => {
@@ -967,6 +1017,38 @@ export async function startAgentRepl(options = {}) {
967
1017
  return;
968
1018
  }
969
1019
 
1020
+ // 1.5) Shift+Tab cycles the session approval tier (Claude-Code mode
1021
+ // cycling): strict → trusted → autopilot → strict. Drives the existing
1022
+ // ApprovalGate.setSessionPolicy seam; intercepted before vim/completion.
1023
+ const isShiftTab =
1024
+ (k.name === "tab" && k.shift) || k.sequence === "\u001b[Z";
1025
+ if (isShiftTab) {
1026
+ if (
1027
+ _approvalGate &&
1028
+ sessionId &&
1029
+ typeof _approvalGate.setSessionPolicy === "function"
1030
+ ) {
1031
+ const next = nextTier(_sessionTier);
1032
+ try {
1033
+ _approvalGate.setSessionPolicy(sessionId, next);
1034
+ _sessionTier = next;
1035
+ process.stdout.write(
1036
+ "\n" +
1037
+ chalk.cyan(`⇥ approval: ${next}`) +
1038
+ " " +
1039
+ chalk.gray(`(${describeTier(next)})`) +
1040
+ "\n",
1041
+ );
1042
+ if (!_turnAbort) prompt();
1043
+ } catch {
1044
+ process.stdout.write("\x07"); // bell on failure
1045
+ }
1046
+ } else {
1047
+ process.stdout.write("\x07"); // no gate this session
1048
+ }
1049
+ return;
1050
+ }
1051
+
970
1052
  // 2) Vim mode: modal editing on the current input line.
971
1053
  if (_vimEnabled && !_turnAbort) {
972
1054
  if (!_vim) {
@@ -1208,6 +1290,9 @@ export async function startAgentRepl(options = {}) {
1208
1290
  ` ${chalk.cyan("/model")} Show/change model (/model <name>)`,
1209
1291
  );
1210
1292
  logger.log(` ${chalk.cyan("/provider")} Show/change provider`);
1293
+ logger.log(
1294
+ ` ${chalk.cyan("/think")} Extended thinking on/off (/think [on|off|ultra]; /ultrathink = max; Anthropic)`,
1295
+ );
1211
1296
  logger.log(` ${chalk.cyan("/clear")} Clear conversation`);
1212
1297
  logger.log(
1213
1298
  ` ${chalk.cyan("/vim")} Toggle vim-mode line editing (/vim [on|off]; Esc → NORMAL)`,
@@ -1240,7 +1325,7 @@ export async function startAgentRepl(options = {}) {
1240
1325
  ` ${chalk.cyan("/cost")} Session token spend + estimated $ (per model & category)`,
1241
1326
  );
1242
1327
  logger.log(
1243
- ` ${chalk.cyan("/permissions")} Allow/ask/deny rules in effect this session`,
1328
+ ` ${chalk.cyan("/permissions")} Allow/ask/deny rules; set/cycle tier (/permissions <tier> · Shift+Tab cycles)`,
1244
1329
  );
1245
1330
  logger.log(
1246
1331
  ` ${chalk.cyan("/export")} Save this conversation to a Markdown file (/export [path])`,
@@ -1260,6 +1345,9 @@ export async function startAgentRepl(options = {}) {
1260
1345
  logger.log(
1261
1346
  ` ${chalk.cyan("/compact")} Smart compact (importance-based)`,
1262
1347
  );
1348
+ logger.log(
1349
+ ` ${chalk.cyan("/microcompact")} Trim large OLD tool results in place (keeps recent + flow)`,
1350
+ );
1263
1351
  logger.log(
1264
1352
  ` ${chalk.cyan("/task")} Set task objective (/task <objective>)`,
1265
1353
  );
@@ -1454,6 +1542,21 @@ export async function startAgentRepl(options = {}) {
1454
1542
  return;
1455
1543
  }
1456
1544
 
1545
+ // Extended-thinking toggle (Anthropic extended thinking; ignored by other
1546
+ // providers). Mutates `thinking`, read by the next turn's agentLoop call.
1547
+ {
1548
+ const think = parseThinkCommand(trimmed);
1549
+ if (think) {
1550
+ thinking = think.thinking;
1551
+ const note = think.anthropic
1552
+ ? " " + chalk.gray("(Anthropic only; applies next turn)")
1553
+ : "";
1554
+ logger.info(`Extended thinking: ${chalk.cyan(think.label)}${note}`);
1555
+ prompt();
1556
+ return;
1557
+ }
1558
+ }
1559
+
1457
1560
  if (trimmed === "/clear") {
1458
1561
  messages.length = 1; // Keep system prompt
1459
1562
  _checkpointMarks.length = 0; // checkpoint marks no longer map to anything
@@ -1780,6 +1883,27 @@ export async function startAgentRepl(options = {}) {
1780
1883
  return;
1781
1884
  }
1782
1885
 
1886
+ // Micro-compaction: surgically trim large OLD tool results in place (keeps
1887
+ // recent messages + the conversation flow). Safe (never orphans a tool
1888
+ // pair); cheaper + less lossy than a full /compact.
1889
+ if (trimmed === "/microcompact") {
1890
+ const { microCompact } = await import("../lib/micro-compact.js");
1891
+ const { messages: mc, stats } = microCompact(messages);
1892
+ if (stats.trimmed > 0) {
1893
+ messages.length = 0;
1894
+ messages.push(...mc);
1895
+ logger.info(
1896
+ `Micro-compacted: trimmed ${stats.trimmed} old tool result(s), ~${stats.saved} chars freed (recent messages kept).`,
1897
+ );
1898
+ } else {
1899
+ logger.info(
1900
+ "Nothing to micro-compact — no large old tool results in context.",
1901
+ );
1902
+ }
1903
+ prompt();
1904
+ return;
1905
+ }
1906
+
1783
1907
  // Task commands
1784
1908
  if (trimmed.startsWith("/task")) {
1785
1909
  const taskArg = trimmed.slice(5).trim();
@@ -2646,7 +2770,39 @@ export async function startAgentRepl(options = {}) {
2646
2770
 
2647
2771
  // `/permissions` — allow/ask/deny rules in effect this session (Claude-Code
2648
2772
  // parity): what the agent runs unprompted, asks about, or is blocked from.
2649
- if (trimmed === "/permissions" || trimmed === "/permissions ") {
2773
+ if (trimmed === "/permissions" || trimmed.startsWith("/permissions ")) {
2774
+ const arg = trimmed.slice("/permissions".length).trim();
2775
+ if (arg) {
2776
+ // Set this session's approval tier mid-session (Claude-Code
2777
+ // permission-mode / Shift+Tab parity; mirrors `cc session policy --set`).
2778
+ const tier = parsePermissionTier(arg);
2779
+ if (!tier) {
2780
+ logger.info(
2781
+ "Usage: /permissions [strict|trusted|autopilot] " +
2782
+ "(aliases: default · accept-edits · bypass). No arg = show rules.",
2783
+ );
2784
+ } else if (
2785
+ !_approvalGate ||
2786
+ !sessionId ||
2787
+ typeof _approvalGate.setSessionPolicy !== "function"
2788
+ ) {
2789
+ logger.info(
2790
+ "Approval gate not available this session — can't change the tier.",
2791
+ );
2792
+ } else {
2793
+ try {
2794
+ _approvalGate.setSessionPolicy(sessionId, tier);
2795
+ _sessionTier = tier;
2796
+ logger.info(
2797
+ `Approval policy → ${chalk.cyan(tier)} ${chalk.gray(`(${describeTier(tier)})`)}`,
2798
+ );
2799
+ } catch (_err) {
2800
+ logger.info("Could not set the approval policy.");
2801
+ }
2802
+ }
2803
+ prompt();
2804
+ return;
2805
+ }
2650
2806
  let files = [];
2651
2807
  try {
2652
2808
  const { loadSettings } = await import("../lib/settings-loader.cjs");
@@ -2656,6 +2812,11 @@ export async function startAgentRepl(options = {}) {
2656
2812
  }
2657
2813
  const { renderPermissions } = await import("./permissions-status.js");
2658
2814
  logger.log(renderPermissions(_permissionRules, { files }));
2815
+ logger.log(
2816
+ chalk.gray(
2817
+ " Set tier mid-session: /permissions <strict|trusted|autopilot>",
2818
+ ),
2819
+ );
2659
2820
  prompt();
2660
2821
  return;
2661
2822
  }
@@ -2821,7 +2982,9 @@ export async function startAgentRepl(options = {}) {
2821
2982
  // about and left as-is.
2822
2983
  let userContent = effectivePrompt;
2823
2984
  try {
2824
- const fileRefs = expandFileRefs(effectivePrompt, { cwd: process.cwd() });
2985
+ const fileRefs = await expandFileRefsAsync(effectivePrompt, {
2986
+ cwd: process.cwd(),
2987
+ });
2825
2988
  userContent = fileRefs.prompt;
2826
2989
  for (const w of fileRefs.warnings) {
2827
2990
  logger.info(chalk.yellow(`[@ref] ${w}`));
@@ -2951,7 +3114,35 @@ export async function startAgentRepl(options = {}) {
2951
3114
  /* goal binding is best-effort — fall back to defaultPrepareCall */
2952
3115
  }
2953
3116
  _turnAbort = new AbortController();
2954
- const { content: response, usageEvents } = await agentLoop(messages, {
3117
+ // Live streaming hooks: write the answer token-by-token, and stream the
3118
+ // reasoning dimmed before it. Skipped (left undefined) in replay mode.
3119
+ let _liveStreamed = false;
3120
+ let _liveThinkStarted = false;
3121
+ const liveOpts = _streamLive
3122
+ ? {
3123
+ onToken: (t) => {
3124
+ // Separate the answer from the dimmed reasoning above it (once).
3125
+ if (!_liveStreamed && _liveThinkStarted) process.stdout.write("\n");
3126
+ _liveStreamed = true;
3127
+ process.stdout.write(t);
3128
+ },
3129
+ onThinking: (t) => {
3130
+ if (process.env.CC_REPL_THINKING === "0") return;
3131
+ if (!_liveThinkStarted) {
3132
+ process.stdout.write(chalk.dim("💭 "));
3133
+ _liveThinkStarted = true;
3134
+ }
3135
+ process.stdout.write(chalk.dim(t));
3136
+ },
3137
+ }
3138
+ : {};
3139
+ if (_streamLive) process.stdout.write("\n");
3140
+ const {
3141
+ content: response,
3142
+ usageEvents,
3143
+ thinking: reasoning,
3144
+ } = await agentLoop(messages, {
3145
+ ...liveOpts,
2955
3146
  signal: _turnAbort.signal,
2956
3147
  provider,
2957
3148
  model: activeModel,
@@ -3032,19 +3223,36 @@ export async function startAgentRepl(options = {}) {
3032
3223
  effectiveResponse = responseDirective.response;
3033
3224
  }
3034
3225
 
3226
+ // Extended-thinking reasoning (Anthropic, when /think is on): shown dimmed
3227
+ // BEFORE the answer. Not subject to the AssistantResponse rewrite/suppress
3228
+ // hook (that governs the answer text only). CC_REPL_THINKING=0 hides it.
3229
+ // In live mode it already streamed via onThinking, so skip the replay.
3230
+ if (reasoning && !_streamLive && process.env.CC_REPL_THINKING !== "0") {
3231
+ process.stdout.write(
3232
+ "\n" + chalk.dim("💭 " + reasoning.replace(/\n/g, "\n ")) + "\n",
3233
+ );
3234
+ }
3235
+
3035
3236
  if (effectiveResponse) {
3036
- // Phase G #2 — route through StreamRouter so REPL / WS / future
3037
- // streaming providers share one StreamEvent protocol.
3038
- const { streamAgentResponse } = await import("../lib/agent-stream.js");
3039
- process.stdout.write("\n");
3040
- const noStream = options.noStream === true;
3041
- const streamResult = await streamAgentResponse(effectiveResponse, {
3042
- noStream,
3043
- writer: noStream ? null : (chunk) => process.stdout.write(chunk),
3044
- });
3045
- if (noStream) process.stdout.write(streamResult.text);
3046
- process.stdout.write("\n\n");
3047
- messages.push({ role: "assistant", content: streamResult.text });
3237
+ if (_streamLive && _liveStreamed) {
3238
+ // Already streamed live token-by-token during the turn (no
3239
+ // AssistantResponse hook to rewrite it) — just terminate + record.
3240
+ process.stdout.write("\n\n");
3241
+ messages.push({ role: "assistant", content: effectiveResponse });
3242
+ } else {
3243
+ // Phase G #2 — route through StreamRouter so REPL / WS / future
3244
+ // streaming providers share one StreamEvent protocol.
3245
+ const { streamAgentResponse } = await import("../lib/agent-stream.js");
3246
+ process.stdout.write("\n");
3247
+ const noStream = options.noStream === true;
3248
+ const streamResult = await streamAgentResponse(effectiveResponse, {
3249
+ noStream,
3250
+ writer: noStream ? null : (chunk) => process.stdout.write(chunk),
3251
+ });
3252
+ if (noStream) process.stdout.write(streamResult.text);
3253
+ process.stdout.write("\n\n");
3254
+ messages.push({ role: "assistant", content: streamResult.text });
3255
+ }
3048
3256
  } else if (!responseDirective.suppress) {
3049
3257
  process.stdout.write("\n");
3050
3258
  }
@@ -12,7 +12,7 @@ import readline from "readline";
12
12
  import chalk from "chalk";
13
13
  import { logger } from "../lib/logger.js";
14
14
  import { BUILT_IN_PROVIDERS } from "../lib/llm-providers.js";
15
- import { expandFileRefs } from "../runtime/file-ref-expander.js";
15
+ import { expandFileRefsAsync } from "../runtime/file-ref-expander.js";
16
16
  import {
17
17
  streamOllama,
18
18
  streamOpenAI,
@@ -159,7 +159,9 @@ export async function startChatRepl(options = {}) {
159
159
  // content; the JSONL log keeps the original line for readability.
160
160
  let userContent = trimmed;
161
161
  try {
162
- const fileRefs = expandFileRefs(trimmed, { cwd: process.cwd() });
162
+ const fileRefs = await expandFileRefsAsync(trimmed, {
163
+ cwd: process.cwd(),
164
+ });
163
165
  userContent = fileRefs.prompt;
164
166
  for (const w of fileRefs.warnings) {
165
167
  logger.info(chalk.yellow(`[@ref] ${w}`));
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Map a `/permissions <arg>` tier alias to an ApprovalGate session-policy tier
3
+ * (strict | trusted | autopilot), or null when the arg is not a known tier.
4
+ * Pure → unit-testable. Mirrors `cc session policy --set` and the headless
5
+ * --permission-mode mapping (default → strict, acceptEdits → trusted,
6
+ * bypassPermissions → autopilot), giving the interactive REPL a mid-session
7
+ * permission-mode toggle (Claude-Code Shift+Tab mode-cycling parity).
8
+ */
9
+ const TIER_ALIASES = {
10
+ // strict — every risky action asks (the default)
11
+ strict: "strict",
12
+ default: "strict",
13
+ normal: "strict",
14
+ off: "strict",
15
+ // trusted — low/medium-risk auto-approved, high-risk still asks (acceptEdits)
16
+ trusted: "trusted",
17
+ accept: "trusted",
18
+ "accept-edits": "trusted",
19
+ acceptedits: "trusted",
20
+ // autopilot — everything auto-approved (bypassPermissions)
21
+ autopilot: "autopilot",
22
+ bypass: "autopilot",
23
+ bypasspermissions: "autopilot",
24
+ yolo: "autopilot",
25
+ };
26
+
27
+ /** Cycle order for Shift+Tab mode cycling (Claude-Code parity). */
28
+ export const TIER_CYCLE = Object.freeze(["strict", "trusted", "autopilot"]);
29
+
30
+ /**
31
+ * Next tier in the Shift+Tab cycle: strict → trusted → autopilot → strict.
32
+ * An unrecognized current tier resets to the first (strict).
33
+ */
34
+ export function nextTier(current) {
35
+ const i = TIER_CYCLE.indexOf(current);
36
+ return TIER_CYCLE[(i + 1) % TIER_CYCLE.length];
37
+ }
38
+
39
+ export function parsePermissionTier(arg) {
40
+ const a = String(arg == null ? "" : arg)
41
+ .trim()
42
+ .toLowerCase();
43
+ return Object.prototype.hasOwnProperty.call(TIER_ALIASES, a)
44
+ ? TIER_ALIASES[a]
45
+ : null;
46
+ }
47
+
48
+ /** One-line description of what a tier auto-approves. */
49
+ export function describeTier(tier) {
50
+ switch (tier) {
51
+ case "autopilot":
52
+ return "everything auto-approved (no prompts)";
53
+ case "trusted":
54
+ return "low/medium-risk auto-approved; high-risk still asks";
55
+ case "strict":
56
+ return "every risky action asks";
57
+ default:
58
+ return "";
59
+ }
60
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Decide whether the REPL may stream the answer live (token-by-token) instead of
3
+ * replaying the finished text. Pure → unit-testable.
4
+ *
5
+ * Safety invariant: live streaming is ONLY allowed when no AssistantResponse
6
+ * hook is registered. Such a hook can rewrite or suppress the final answer, and
7
+ * once tokens are on screen they can't be un-printed. If we can't determine the
8
+ * hook count (a query error → pass arHookCount < 0), we stay safe (no streaming).
9
+ * `CC_REPL_STREAM=0` forces the replay regardless.
10
+ */
11
+ export function shouldStreamLive({ streamEnv, arHookCount = 0 } = {}) {
12
+ if (streamEnv === "0") return false;
13
+ // 0 → no rewrite/suppress hook → safe. Anything else (hooks present, or -1 for
14
+ // "unknown") → fall back to the replay.
15
+ return arHookCount === 0;
16
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Pure parser for the REPL `/think` · `/ultrathink` extended-thinking toggle.
3
+ * Maps the slash-command string to the next `thinking` value the agent loop
4
+ * reads (true | "ultra" | <level> | null) plus a human label. Returns null when
5
+ * the input is not a think command, so the REPL falls through to its other
6
+ * handlers. vscode/readline-free → unit-testable (mirrors the chat panel's
7
+ * /think; extended thinking is Anthropic-only, ignored by other providers).
8
+ *
9
+ * /think → on (default budget) /think off → off
10
+ * /think ultra → max budget /think <level> → that level
11
+ * /think-off → off (panel-style alias)
12
+ * /ultrathink → max budget (alias)
13
+ */
14
+ export function parseThinkCommand(trimmed) {
15
+ const t = String(trimmed == null ? "" : trimmed).trim();
16
+ const isThink =
17
+ t === "/think" || t.startsWith("/think ") || t.startsWith("/think-");
18
+ const isUltra = t === "/ultrathink" || t.startsWith("/ultrathink ");
19
+ if (!isThink && !isUltra) return null;
20
+
21
+ const arg = isUltra
22
+ ? "ultra"
23
+ : t.slice(6).replace(/^-+/, "").trim().toLowerCase();
24
+
25
+ if (arg === "ultra") {
26
+ return { thinking: "ultra", label: "ultra (max budget)", anthropic: true };
27
+ }
28
+ if (arg === "off" || arg === "false" || arg === "0" || arg === "none") {
29
+ return { thinking: null, label: "off", anthropic: false };
30
+ }
31
+ if (!arg || arg === "on" || arg === "true") {
32
+ return { thinking: true, label: "on", anthropic: true };
33
+ }
34
+ // An explicit effort level (low/medium/high/…) — passed through to the engine.
35
+ return { thinking: arg, label: arg, anthropic: true };
36
+ }
@@ -2658,6 +2658,7 @@ export async function chatWithTools(rawMessages, options) {
2658
2658
  { "x-api-key": key, "anthropic-version": "2023-06-01" },
2659
2659
  options.onToken,
2660
2660
  signal,
2661
+ options.onThinking,
2661
2662
  );
2662
2663
  }
2663
2664
 
@@ -2899,7 +2900,7 @@ function _anthropicInitState() {
2899
2900
  return { text: "", blocks: {}, inputTokens: 0, outputTokens: 0 };
2900
2901
  }
2901
2902
 
2902
- function _anthropicReduceLine(state, raw, onToken) {
2903
+ function _anthropicReduceLine(state, raw, onToken, onThinking) {
2903
2904
  const line = (raw || "").trim();
2904
2905
  if (!line.startsWith("data:")) return state;
2905
2906
  const payload = line.slice(5).trim();
@@ -2939,6 +2940,13 @@ function _anthropicReduceLine(state, raw, onToken) {
2939
2940
  } else if (d.type === "thinking_delta" && state.blocks[obj.index]) {
2940
2941
  state.blocks[obj.index].thinking =
2941
2942
  (state.blocks[obj.index].thinking || "") + (d.thinking || "");
2943
+ if (typeof onThinking === "function" && d.thinking) {
2944
+ try {
2945
+ onThinking(d.thinking);
2946
+ } catch {
2947
+ // a failing UI hook must never break the run
2948
+ }
2949
+ }
2942
2950
  } else if (d.type === "signature_delta" && state.blocks[obj.index]) {
2943
2951
  state.blocks[obj.index].signature =
2944
2952
  (state.blocks[obj.index].signature || "") + (d.signature || "");
@@ -2994,9 +3002,10 @@ function _anthropicFinalize(state) {
2994
3002
  }
2995
3003
 
2996
3004
  /** Pure reducer over Anthropic SSE lines — exported for tests (no HTTP). */
2997
- export function _accumulateAnthropicStream(lines, onToken) {
3005
+ export function _accumulateAnthropicStream(lines, onToken, onThinking) {
2998
3006
  const state = _anthropicInitState();
2999
- for (const line of lines) _anthropicReduceLine(state, line, onToken);
3007
+ for (const line of lines)
3008
+ _anthropicReduceLine(state, line, onToken, onThinking);
3000
3009
  return _anthropicFinalize(state);
3001
3010
  }
3002
3011
 
@@ -3006,6 +3015,7 @@ async function _chatAnthropicStreaming(
3006
3015
  extraHeaders,
3007
3016
  onToken,
3008
3017
  signal,
3018
+ onThinking,
3009
3019
  ) {
3010
3020
  const response = await fetch(apiUrl, {
3011
3021
  method: "POST",
@@ -3026,9 +3036,10 @@ async function _chatAnthropicStreaming(
3026
3036
  buf += decoder.decode(value, { stream: true });
3027
3037
  const lines = buf.split("\n");
3028
3038
  buf = lines.pop() || "";
3029
- for (const line of lines) _anthropicReduceLine(state, line, onToken);
3039
+ for (const line of lines)
3040
+ _anthropicReduceLine(state, line, onToken, onThinking);
3030
3041
  }
3031
- if (buf.trim()) _anthropicReduceLine(state, buf, onToken);
3042
+ if (buf.trim()) _anthropicReduceLine(state, buf, onToken, onThinking);
3032
3043
  return _anthropicFinalize(state);
3033
3044
  }
3034
3045
 
@@ -3576,12 +3587,32 @@ export async function* agentLoop(messages, options) {
3576
3587
  try {
3577
3588
  const compactor = await _getAutoCompactor(options);
3578
3589
  if (compactor && compactor.shouldAutoCompact(messages)) {
3590
+ // Cheap surgical pre-pass (Claude-Code microcompact parity): trim old
3591
+ // large tool results IN PLACE before the disruptive full
3592
+ // summarization. If the trim brings the context back under threshold,
3593
+ // the full compaction below is skipped this round — so heavy-tool
3594
+ // conversations rarely hit a full summarize. Opt out:
3595
+ // autoMicroCompact: false.
3596
+ if (options.autoMicroCompact !== false) {
3597
+ try {
3598
+ const { microCompact } = await import("../lib/micro-compact.js");
3599
+ const mc = microCompact(messages);
3600
+ if (mc.stats.trimmed > 0) {
3601
+ messages.splice(0, messages.length, ...mc.messages);
3602
+ yield { type: "micro-compaction", runId, stats: mc.stats };
3603
+ }
3604
+ } catch {
3605
+ // microcompact is best-effort — never break the run
3606
+ }
3607
+ }
3608
+ // After the trim, is the full (disruptive) compaction still needed?
3609
+ const needFull = compactor.shouldAutoCompact(messages);
3579
3610
  // settings.json PreCompact hooks: a `block` decision SKIPS this
3580
3611
  // compaction round (e.g. the hook archived / owns the history). Fires
3581
3612
  // right before the history would be compacted.
3582
3613
  let preCompactBlocked = false;
3583
3614
  let preCompactReason = null;
3584
- if (options.settingsHooks) {
3615
+ if (needFull && options.settingsHooks) {
3585
3616
  try {
3586
3617
  const pc = runObserveHooks(
3587
3618
  options.settingsHooks,
@@ -3608,9 +3639,10 @@ export async function* agentLoop(messages, options) {
3608
3639
  reason: preCompactReason,
3609
3640
  };
3610
3641
  }
3611
- const { messages: compacted, stats } = preCompactBlocked
3612
- ? { messages, stats: { saved: 0 } }
3613
- : await compactor.compress(messages, { preserveToolPairs: true });
3642
+ const { messages: compacted, stats } =
3643
+ !needFull || preCompactBlocked
3644
+ ? { messages, stats: { saved: 0 } }
3645
+ : await compactor.compress(messages, { preserveToolPairs: true });
3614
3646
  if (stats.saved > 0 && compacted.length < messages.length) {
3615
3647
  messages.splice(0, messages.length, ...compacted);
3616
3648
  // Persist the compaction so a later --resume rebuilds from the
@@ -3699,7 +3731,20 @@ export async function* agentLoop(messages, options) {
3699
3731
  const toolCalls = msg.tool_calls;
3700
3732
 
3701
3733
  if (!toolCalls || toolCalls.length === 0) {
3702
- yield { type: "response-complete", content: msg.content || "" };
3734
+ // Surface the final answer's extended-thinking reasoning (Anthropic, when
3735
+ // --think is on) so non-streaming consumers (the REPL) can show it. The
3736
+ // streaming path forwards reasoning live via onThinking instead.
3737
+ const _thinking = Array.isArray(msg._thinkingBlocks)
3738
+ ? msg._thinkingBlocks
3739
+ .map((b) => b.thinking || "")
3740
+ .join("")
3741
+ .trim()
3742
+ : "";
3743
+ yield {
3744
+ type: "response-complete",
3745
+ content: msg.content || "",
3746
+ ...(_thinking ? { thinking: _thinking } : {}),
3747
+ };
3703
3748
  // settings.json Stop hooks: a `block` decision FORCES the agent to keep
3704
3749
  // going instead of stopping — the reason is injected as a new instruction.
3705
3750
  // `stop_hook_active` lets the hook avoid an infinite loop; the iteration
@@ -3741,6 +3786,18 @@ export async function* agentLoop(messages, options) {
3741
3786
  return;
3742
3787
  }
3743
3788
 
3789
+ // Intermediate-step reasoning (Anthropic, --think): the model's reasoning
3790
+ // before it chose these tool calls. Streaming consumers already get it live
3791
+ // via onThinking, so only surface it as an event for non-streaming consumers
3792
+ // (the REPL) — keeps it out of the --include-partial-messages stream.
3793
+ if (!options.onThinking && Array.isArray(msg._thinkingBlocks)) {
3794
+ const _stepThinking = msg._thinkingBlocks
3795
+ .map((b) => b.thinking || "")
3796
+ .join("")
3797
+ .trim();
3798
+ if (_stepThinking) yield { type: "thinking", text: _stepThinking };
3799
+ }
3800
+
3744
3801
  // Add assistant message with tool calls
3745
3802
  messages.push(msg);
3746
3803