chainlesschain 0.162.70 → 0.162.72

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 (171) hide show
  1. package/package.json +4 -3
  2. package/src/assets/web-panel/assets/{AIOps-CnHIXe2L.js → AIOps-1bl50Cen.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-CzDyhTAp.js → ActionButton-Bw4kAx-1.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-rwvDMCd-.js → Analytics-junq7r42.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-D-Q1M-5V.js → AppLayout-vf1TVGeu.js} +5 -5
  6. package/src/assets/web-panel/assets/{Audit-BIFSGnAE.js → Audit-CBzpgcL9.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-CbUdiVeS.js → Backup-ChwGPeP4.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-D3P_pICH.js → BaseInput-2G2KGE6M.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-DRrX7vVB.js → Chat-Cr9NwfDA.js} +4 -4
  10. package/src/assets/web-panel/assets/{ChatBubbleRenderer-DTC1ElV-.js → ChatBubbleRenderer-CJvlo_ay.js} +1 -1
  11. package/src/assets/web-panel/assets/{Checkbox-Dpq9X8Xd.js → Checkbox-DN_5PBT7.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-yQQOSuHA.js → Codegen-D4bluAgT.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-DLFLDxar.js → Col-DBP3UjYW.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-DYbdX-GB.js → Community-ge-SvR3c.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-Dclrp1HE.js → Compact-DGXvlGNv.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-C9EQMgoP.js → Compliance-Cn7nBsFQ.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-B9IMFsMk.js → Cowork-DgHEO2de.js} +2 -2
  18. package/src/assets/web-panel/assets/{Cron-V6TlbDey.js → Cron-DUbYxPs0.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-Cl_vG8R_.js → Crosschain-3J0MWPQq.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-DoUUF9cz.js → DID-BR_Jpkh4.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-DcA8WmrS.js → Dashboard-ffykLkAJ.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-wyGMYzw9.js → Dropdown-DaI38dXT.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-B1T3ziIH.js → EmailListRenderer-CnIzkUVG.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CW4CwYbZ.js → FamilyGuardDashboard-BxrgAAZg.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-9tEiw0KA.js → Federation-BuS_FRh3.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-DbocfnNQ.js → FormItemContext-DBG-8g1w.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-CGNsG2qI.js → GenericCardRenderer-CmEc8RgH.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-BxA3dgT8.js → Git-CK10BYwY.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-DqnhaC7n.js → Governance-C8cABM_I.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-D6Ll4t9c.js → Inference-Dr4_DIw3.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-Bb8IXGl7.js → KnowledgeGraph-0_bLuvvb.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-DRUUVMdc.js → Logs-PVGZxt9z.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-BYRM186w.js → Marketplace-BtVnS4HW.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-DLbh7uS8.js → McpTools-BVulusCC.js} +3 -3
  35. package/src/assets/web-panel/assets/{Memory-D2_uZEOI.js → Memory-DEMtNbJ9.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-CXVJoMKk.js → MobileBridge-5QL654FQ.js} +3 -3
  37. package/src/assets/web-panel/assets/{MobileProjects-QRZYgIME.js → MobileProjects-G5Du9Thr.js} +1 -1
  38. package/src/assets/web-panel/assets/{Mtc-D3mv30gW.js → Mtc-CNFPJej0.js} +2 -2
  39. package/src/assets/web-panel/assets/{MtcAudit-__QPus22.js → MtcAudit-DAJ81Cej.js} +2 -2
  40. package/src/assets/web-panel/assets/{Multisig-Bu5gaTn6.js → Multisig-Cwj8tXfT.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-SjaViyfR.js → NLProgramming-Di0ptaL6.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-feXsl0dN.js → Notes-Cse_4Ox6.js} +3 -3
  43. package/src/assets/web-panel/assets/{NotificationSettings-BtVFokbM.js → NotificationSettings-ntpoi5ec.js} +1 -1
  44. package/src/assets/web-panel/assets/OrderTableRenderer-CbKsOM0D.js +1 -0
  45. package/src/assets/web-panel/assets/{Organization-CewL60Rd.js → Organization-Bhjya5h9.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow-2ZRChJ7E.js → Overflow-C0u3aive.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-DuMr6G9y.js → P2P-BWrTwf_a.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-BMOcHCWd.js → PdhVaultBrowser-pld2Qlqk.js} +3 -3
  49. package/src/assets/web-panel/assets/{Permissions-Cj94ql75.js → Permissions-CW5FG3mE.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-BYK4mlk7.js → PersonalDataHub-BuBmqfDa.js} +4 -4
  51. package/src/assets/web-panel/assets/{Pipeline-maRvdrbO.js → Pipeline-nZr06BNO.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-CKb2zZJz.js → Privacy-B_XvMHYv.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-C2pfXv4l.js → ProjectInit-CbRHJy85.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-DGeNwq0h.js → ProjectSettings-CCH8SpEa.js} +2 -2
  55. package/src/assets/web-panel/assets/{Projects-BRypTQUV.js → Projects-D5Min2Fu.js} +1 -1
  56. package/src/assets/web-panel/assets/{Providers-DYwMPXEO.js → Providers-DgCY5A8A.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-DE6isYZV.js → QuickAsk-DEcKTiTV.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-Rc-AE6y9.js → Recommend-BRaq2oKc.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-DDQ8_kmM.js → Reputation-DzdbBdVv.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-Do5iQUFj.js → Row-Ds2JNh7c.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-cxG0ulSw.js → RssFeed-Bk1-t4Ie.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-D3-8EdNk.js → Search-DaVhRwoq.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-DtBfws1D.js → Security-D9mXjPSu.js} +3 -3
  64. package/src/assets/web-panel/assets/{Services-DzeN8HsG.js → Services-D8sukL97.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-BQCVeTUf.js → Skeleton-COCJt30X.js} +1 -1
  66. package/src/assets/web-panel/assets/Skills-DtFPSPSc.js +1 -0
  67. package/src/assets/web-panel/assets/{Sla-d2IOd-AA.js → Sla-DIab-wjR.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-BjfopwT6.js → SpeechSettings-xxIH9hgB.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-D5_c1_rI.js → SyncSettings-DrpxzJOH.js} +2 -2
  70. package/src/assets/web-panel/assets/Tasks-maiJ3jAT.js +1 -0
  71. package/src/assets/web-panel/assets/{Templates-CCXFgNy3.js → Templates-COE-dSks.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-Duz6eEK3.js → Tenant-CLGObu4M.js} +1 -1
  73. package/src/assets/web-panel/assets/{Terminal-Buc6UbYo.js → Terminal-Bw8WBvW3.js} +2 -2
  74. package/src/assets/web-panel/assets/{TimelineRenderer-Du5jKHJz.js → TimelineRenderer-D4gdk3Qb.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-B12vLtQm.js → Tokens-CSoL_uAw.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-s_pHOerx.js → Trigger-BCRcowFi.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-4sDhbAls.js → Trust-D5QPZ198.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-UeyFknip.js → UkeySign-C7SsMqfZ.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-DsopM2S5.js → VideoEditing-CDQ4HNZL.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-CreLjC5U.js → Wallet-VmG4P_Fm.js} +3 -3
  81. package/src/assets/web-panel/assets/{WebAuthn-CBuMMZbK.js → WebAuthn-qtC0dZxA.js} +4 -4
  82. package/src/assets/web-panel/assets/{WorkflowEditor-DWhHHSFW.js → WorkflowEditor-C_CSdk3l.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-Iurq_BMY.js → chat-BGm8YCIo.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-BIOTIa89.js → colors-BScOmAwk.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-B7lPu9pO.js → compact-item-HDwaT31n.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-Bzzk7mrh.js → createContext-CheSjPm8.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-CjO6o19j.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-B11xqp5O.js → hasIn-C1_NbX0O.js} +1 -1
  89. package/src/assets/web-panel/assets/index--jhpSxS1.js +1 -0
  90. package/src/assets/web-panel/assets/{index-vZCGFSJN.js → index-6C8mxO8j.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-CLhpFnSR.js → index-B94_DZF-.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-BlR8YVlt.js → index-BGROuzQG.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-CxDOX4g6.js → index-BKQordyU.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-DMrzHgE6.js → index-BMxPL1oG.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-DsoyfOzW.js → index-Bd2zS3jK.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-3LgeP4ij.js → index-ByoUjX8f.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-Y4gmywZo.js → index-C4FSaMAu.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CCxGEK8b.js → index-C6WXnaG4.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-C-lwsB2F.js → index-C71Z__li.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-C4wV32Dn.js → index-CSaztnyZ.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CiXNVmcx.js → index-CYkezxqK.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-D6q-fkXs.js → index-Clqj2JTi.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-R-YSpvg4.js → index-Cm55PcYG.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Dn_Ht3v2.js → index-CxoOEb-l.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DEKeoJ6I.js → index-D2gy4IX0.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-AmHa_sOB.js → index-D8xpncJ0.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CULCwI_H.js → index-DB6gbgU2.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-tub9w6us.js → index-DDk0s3nO.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CzY2mdiR.js → index-DQ01dBAS.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-5BkUKVyj.js → index-DSuWcpsv.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-D8tm9JnQ.js → index-DVyL9jbT.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BLREN4NE.js → index-DaATJtzu.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CEKRqrI-.js → index-DbfAaBxL.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BVIXYV1R.js → index-DhTELUKN.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BgmBZgU9.js → index-Dk2N6hxE.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-B4rXFIOG.js → index-DkiIxTIS.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CA-GPOd9.js → index-DoZwBGUX.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-6QGNBg3m.js → index-DppSbT8L.js} +3 -3
  119. package/src/assets/web-panel/assets/{index-DwGVPCZb.js → index-DzE-95XH.js} +1 -1
  120. package/src/assets/web-panel/assets/index-J4Rzclyc.js +1 -0
  121. package/src/assets/web-panel/assets/{index-BIFscFlm.js → index-KG9xVANC.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DyV9331f.js → index-KXrUKw1h.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-D25-u3Q2.js → index-eSBoP6E_.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-B93pM-xx.js → index-ggl9cj35.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-htxy7qDG.js → index-i-xYmSsZ.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CS2UWrIt.js → index-lB5kN9Yc.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-DX7HGwP_.js → index-yKmudyK7.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-Bl6klCzd.js → initDefaultProps-D7Q7zDzP.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-B9IxS1-S.js → motion-B4DcaqPb.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-NJ5XDNQe.js → move-CYxmYrFY.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-ByO8GI_H.js → omit-ze_9dKw3.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-W6iTcLuH.js → pickAttrs-D24HrRSv.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-JMndCFEP.js → placementArrow-BWJZVYyS.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-DHV7-FuY.js → responsiveObserve-CseJia_L.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-DioRUeNF.js → slide-CBWNEpey.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-DMp2b9Ze.js → statusUtils-f4e6p7yd.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-DamP5BTS.js → styleChecker-DZmfWZ8G.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-D338iARz.js → useFlexGapSupport-4RBaBnVI.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-DE_HJgk6.js → useFs-Cv6bNoEA.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-914YxO9q.js → usePersonalDataHub-DOTETJIA.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-CY39Ky7P.js → vnode-DqS1K6-J.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-BEghlCth.js → zoom-C1MYzIJG.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/cowork.js +8 -0
  145. package/src/commands/crosschain.js +32 -4
  146. package/src/commands/init.js +10 -10
  147. package/src/commands/loop.js +9 -3
  148. package/src/commands/memory.js +6 -4
  149. package/src/commands/orchestrate.js +5 -2
  150. package/src/commands/video.js +5 -1
  151. package/src/lib/cowork-workflow.js +664 -129
  152. package/src/lib/hook-manager.js +3 -2
  153. package/src/lib/micro-compact.js +52 -0
  154. package/src/lib/permission-rules.cjs +39 -0
  155. package/src/lib/skill-loader.js +62 -43
  156. package/src/lib/workflow-state-reader.js +10 -1
  157. package/src/repl/agent-repl.js +228 -20
  158. package/src/repl/chat-repl.js +4 -2
  159. package/src/repl/permission-tier.js +60 -0
  160. package/src/repl/stream-decision.js +16 -0
  161. package/src/repl/think-command.js +36 -0
  162. package/src/runtime/agent-core.js +42 -6
  163. package/src/runtime/file-ref-expander.js +209 -18
  164. package/src/runtime/headless-runner.js +3 -3
  165. package/src/runtime/headless-stream.js +16 -3
  166. package/src/assets/web-panel/assets/OrderTableRenderer-CJJ2GWDf.js +0 -1
  167. package/src/assets/web-panel/assets/Skills-D6mCVKt7.js +0 -1
  168. package/src/assets/web-panel/assets/Tasks-BCmxDMZx.js +0 -1
  169. package/src/assets/web-panel/assets/devWarning-CuIq7zBB.js +0 -1
  170. package/src/assets/web-panel/assets/index-BRsns-SP.js +0 -1
  171. package/src/assets/web-panel/assets/index-QTuoOvJA.js +0 -1
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import crypto from "crypto";
7
+ import { execSync } from "child_process";
7
8
 
8
9
  /**
9
10
  * Hook priority levels — lower values run first.
@@ -256,8 +257,8 @@ export async function executeHook(hook, context = {}) {
256
257
  const type = hook.type || HookType.SYNC;
257
258
 
258
259
  if (type === HookType.COMMAND || type === HookType.SCRIPT) {
259
- // Command/script hooks execute a shell command
260
- const { execSync } = await import("child_process");
260
+ // Command/script hooks execute a shell command (execSync hoisted to a
261
+ // top-level import this runs per hook on the tool-use hot path).
261
262
  const cmd = hook.handler || "";
262
263
  if (!cmd) {
263
264
  return {
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Micro-compaction (Claude-Code `/microcompact` parity). Where full compaction
3
+ * summarizes the whole history when it grows large, micro-compaction is
4
+ * surgical: it trims the bulkiest OLD items — large tool results — in place,
5
+ * keeping the conversation flow and the most-recent messages verbatim. Because
6
+ * it only SHORTENS a tool message's content (never removes the message), it can
7
+ * never orphan a tool_call→tool_result pair, so it's safe to run any time.
8
+ *
9
+ * Pure → unit-testable.
10
+ */
11
+
12
+ const DEFAULT_KEEP_RECENT = 6; // last N messages kept verbatim
13
+ const DEFAULT_MAX_TOOL_CHARS = 400; // trim older tool results longer than this
14
+
15
+ /**
16
+ * @param {Array} messages OpenAI-shaped messages ({role, content, …}).
17
+ * @param {object} [opts] { keepRecent, maxToolChars }
18
+ * @returns {{ messages: Array, stats: {trimmed:number, saved:number, kept:number} }}
19
+ * A NEW array (originals untouched); `saved` ≈ characters freed.
20
+ */
21
+ export function microCompact(messages, opts = {}) {
22
+ if (!Array.isArray(messages)) {
23
+ return { messages, stats: { trimmed: 0, saved: 0, kept: 0 } };
24
+ }
25
+ const keepRecent = Number.isFinite(opts.keepRecent)
26
+ ? opts.keepRecent
27
+ : DEFAULT_KEEP_RECENT;
28
+ const maxChars = Number.isFinite(opts.maxToolChars)
29
+ ? opts.maxToolChars
30
+ : DEFAULT_MAX_TOOL_CHARS;
31
+ // messages at index >= cutoff are "recent" and never touched.
32
+ const cutoff = Math.max(0, messages.length - keepRecent);
33
+
34
+ let trimmed = 0;
35
+ let saved = 0;
36
+ const out = messages.map((m, i) => {
37
+ if (i >= cutoff) return m; // recent → verbatim
38
+ if (!m || m.role !== "tool") return m; // only old tool results
39
+ const content = typeof m.content === "string" ? m.content : "";
40
+ if (content.length <= maxChars) return m; // small enough already
41
+ trimmed += 1;
42
+ saved += content.length - maxChars;
43
+ const head = content.slice(0, maxChars);
44
+ return {
45
+ ...m,
46
+ content: `${head}\n… [tool result trimmed — ${content.length} chars; older context dropped to save space]`,
47
+ _microCompacted: true,
48
+ };
49
+ });
50
+
51
+ return { messages: out, stats: { trimmed, saved, kept: keepRecent } };
52
+ }
@@ -13,6 +13,11 @@
13
13
  * Read(./src/**) → read_file/list_dir on a path under <cwd>/src
14
14
  * Edit(//etc/**) → edit_file on an absolute path under /etc
15
15
  * WebFetch(domain:example.com) → web_fetch of https://example.com/…
16
+ * Bash(command:rm*) → run_shell whose `command` arg matches `rm*` (CC
17
+ * 2.1.178 `Tool(param:value)` — matches any named
18
+ * input parameter, incl. arbitrary MCP tool args;
19
+ * `*` crosses `/` here, unlike path globs)
20
+ * mcp__db__query(table:users) → that MCP tool whose `table` arg is "users"
16
21
  * Bash → every run_shell call
17
22
  * * → every tool call (Claude-Code deny-all idiom)
18
23
  * Bash(*) → every run_shell call (lone-`*` pattern = match-all)
@@ -197,6 +202,30 @@ function matchUrl(pattern, url) {
197
202
  return globToRegExp(pattern).test(u);
198
203
  }
199
204
 
205
+ /** Match a single named-parameter value — `Tool(param:value)`. `prefix:*` →
206
+ * starts-with; otherwise glob/exact. Unlike path globs, `*` here crosses `/`
207
+ * because param values are arbitrary strings (commands often embed paths),
208
+ * not filesystem path segments. */
209
+ function matchParamValue(pattern, value) {
210
+ const v = value == null ? "" : String(value);
211
+ if (pattern === "*" || pattern === "**") return true;
212
+ if (pattern.endsWith(":*")) {
213
+ const prefix = pattern.slice(0, -2).trim();
214
+ return v === prefix || v.startsWith(prefix);
215
+ }
216
+ if (pattern.includes("*") || pattern.includes("?")) {
217
+ const re =
218
+ "^" +
219
+ pattern
220
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
221
+ .replace(/\*/g, ".*")
222
+ .replace(/\?/g, ".") +
223
+ "$";
224
+ return new RegExp(re).test(v);
225
+ }
226
+ return v === pattern;
227
+ }
228
+
200
229
  /**
201
230
  * Does `pattern` (the inside of `Tool(...)`, or null for a bare rule) match the
202
231
  * given tool call? `null` pattern always matches.
@@ -208,6 +237,15 @@ function matchPattern(pattern, actualTool, args, cwd) {
208
237
  // command/url/path containing `/` can't slip past a deny-all (globToRegExp's
209
238
  // `*` → `[^/]*` otherwise excludes slashes).
210
239
  if (pattern === "*" || pattern === "**") return true;
240
+ // Claude-Code 2.1.178: `Tool(param:value)` matches a NAMED input parameter,
241
+ // generalizing the positional command/path/url targets below. Treated as a
242
+ // param rule only when `param` is a bare identifier AND an actual key in the
243
+ // tool's args — so command idioms (`git push:*`) and `domain:host` (whose
244
+ // prefix is not an arg key) correctly fall through to the matching below.
245
+ const pv = pattern.match(/^([A-Za-z_]\w*):([\s\S]*)$/);
246
+ if (pv && Object.prototype.hasOwnProperty.call(args || {}, pv[1])) {
247
+ return matchParamValue(pv[2].trim(), (args || {})[pv[1]]);
248
+ }
211
249
  const target = extractTarget(actualTool, args, cwd);
212
250
  if (target.kind === "command") {
213
251
  return target.value ? matchCommand(pattern, target.value) : false;
@@ -328,6 +366,7 @@ module.exports = {
328
366
  extractTarget,
329
367
  matchCommand,
330
368
  matchUrl,
369
+ matchParamValue,
331
370
  matchPattern,
332
371
  evaluatePermissionRules,
333
372
  suggestAllowRule,
@@ -78,6 +78,10 @@ export const LAYER_NAMES = [
78
78
  "workspace",
79
79
  ];
80
80
 
81
+ /** Max directory depth to descend when scanning a skill layer for nested
82
+ * `<group>/<skill>/SKILL.md` layouts (infinite-recursion / deep-tree guard). */
83
+ export const MAX_SKILL_NEST_DEPTH = 5;
84
+
81
85
  /**
82
86
  * Simple YAML frontmatter parser (no dependencies)
83
87
  * Shared utility extracted from skill.js
@@ -348,54 +352,69 @@ export class CLISkillLoader {
348
352
  */
349
353
  _loadFromDir(dir, layer) {
350
354
  const skills = [];
351
- if (!dir || !fs.existsSync(dir)) return skills;
355
+ this._collectSkills(dir, layer, skills, 0);
356
+ return skills;
357
+ }
352
358
 
359
+ /**
360
+ * Recursively collect skills under `dir`. A directory containing a SKILL.md
361
+ * is a skill (a leaf — we do NOT descend into its asset/script subdirs); a
362
+ * directory WITHOUT one is treated as a grouping/category folder and is
363
+ * descended into, so nested layouts like `.claude/skills/<group>/<skill>/`
364
+ * load too (Claude-Code 2.1.178 nested-skills parity). Depth-capped.
365
+ */
366
+ _collectSkills(dir, layer, out, depth) {
367
+ if (!dir || !fs.existsSync(dir)) return;
368
+ let entries;
353
369
  try {
354
- const dirs = fs.readdirSync(dir, { withFileTypes: true });
355
- for (const entry of dirs) {
356
- if (!entry.isDirectory()) continue;
357
-
358
- const skillMd = path.join(dir, entry.name, "SKILL.md");
359
- if (!fs.existsSync(skillMd)) continue;
360
-
361
- try {
362
- const content = fs.readFileSync(skillMd, "utf-8");
363
- const { data, body } = parseSkillMd(content);
364
-
365
- skills.push({
366
- id: data.name || entry.name,
367
- displayName: data.displayName || entry.name,
368
- description: data.description || "",
369
- version: data.version || "1.0.0",
370
- category: data.category || "uncategorized",
371
- activation: data.activation || "manual",
372
- tags: data.tags || [],
373
- userInvocable: data.userInvocable !== false,
374
- handler: data.handler || null,
375
- capabilities: data.capabilities || [],
376
- os: data.os || [],
377
- // CLI pack extended fields
378
- executionMode: data.executionMode || null,
379
- cliDomain: data.cliDomain || null,
380
- cliVersionHash: data.cliVersionHash || null,
381
- dirName: entry.name,
382
- hasHandler: fs.existsSync(path.join(dir, entry.name, "handler.js")),
383
- body,
384
- // Skill-Embedded MCP: inline server declarations in a
385
- // ```mcp-servers fenced code block. Empty array if absent.
386
- mcpServers: parseSkillMcpServers(body),
387
- source: layer,
388
- skillDir: path.join(dir, entry.name),
389
- });
390
- } catch {
391
- // Skip malformed skill files
370
+ entries = fs.readdirSync(dir, { withFileTypes: true });
371
+ } catch {
372
+ return; // Directory unreadable
373
+ }
374
+ for (const entry of entries) {
375
+ if (!entry.isDirectory()) continue;
376
+ const skillDir = path.join(dir, entry.name);
377
+ const skillMd = path.join(skillDir, "SKILL.md");
378
+ if (!fs.existsSync(skillMd)) {
379
+ // Grouping folder descend (bounded) to find nested skills.
380
+ if (depth < MAX_SKILL_NEST_DEPTH) {
381
+ this._collectSkills(skillDir, layer, out, depth + 1);
392
382
  }
383
+ continue;
384
+ }
385
+ try {
386
+ const content = fs.readFileSync(skillMd, "utf-8");
387
+ const { data, body } = parseSkillMd(content);
388
+
389
+ out.push({
390
+ id: data.name || entry.name,
391
+ displayName: data.displayName || entry.name,
392
+ description: data.description || "",
393
+ version: data.version || "1.0.0",
394
+ category: data.category || "uncategorized",
395
+ activation: data.activation || "manual",
396
+ tags: data.tags || [],
397
+ userInvocable: data.userInvocable !== false,
398
+ handler: data.handler || null,
399
+ capabilities: data.capabilities || [],
400
+ os: data.os || [],
401
+ // CLI pack extended fields
402
+ executionMode: data.executionMode || null,
403
+ cliDomain: data.cliDomain || null,
404
+ cliVersionHash: data.cliVersionHash || null,
405
+ dirName: entry.name,
406
+ hasHandler: fs.existsSync(path.join(skillDir, "handler.js")),
407
+ body,
408
+ // Skill-Embedded MCP: inline server declarations in a
409
+ // ```mcp-servers fenced code block. Empty array if absent.
410
+ mcpServers: parseSkillMcpServers(body),
411
+ source: layer,
412
+ skillDir,
413
+ });
414
+ } catch {
415
+ // Skip malformed skill files
393
416
  }
394
- } catch {
395
- // Directory unreadable
396
417
  }
397
-
398
- return skills;
399
418
  }
400
419
 
401
420
  /**
@@ -19,7 +19,16 @@ import fs from "fs";
19
19
  import path from "path";
20
20
 
21
21
  function safeId(id) {
22
- if (!id || typeof id !== "string" || !/^[A-Za-z0-9._-]+$/.test(id)) {
22
+ // Charset blocks path separators; the explicit "."/".." check closes the
23
+ // remaining single-component traversal (e.g. `..` would join to the parent
24
+ // of the sessions dir and read files outside it).
25
+ if (
26
+ !id ||
27
+ typeof id !== "string" ||
28
+ !/^[A-Za-z0-9._-]+$/.test(id) ||
29
+ id === "." ||
30
+ id === ".."
31
+ ) {
23
32
  throw new Error(`Invalid sessionId: "${id}"`);
24
33
  }
25
34
  return id;
@@ -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
  }