chainlesschain 0.162.49 → 0.162.60

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 (172) hide show
  1. package/bin/chainlesschain.js +1 -1
  2. package/package.json +2 -2
  3. package/src/assets/web-panel/assets/{AIOps-DmdtNmWd.js → AIOps-a2cSbSEu.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-DkuYVafg.js → ActionButton-DwvSB5Pp.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-Ba_h8Tub.js → Analytics-BqaRaBDD.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-yb8Zm9MX.js → AppLayout-Beck7v8t.js} +5 -5
  7. package/src/assets/web-panel/assets/{Audit-BGjex2fm.js → Audit-UtJhPdXJ.js} +1 -1
  8. package/src/assets/web-panel/assets/{Backup-IFQ2hOF2.js → Backup-HVZhcdll.js} +1 -1
  9. package/src/assets/web-panel/assets/{BaseInput-W8AkPkrV.js → BaseInput-DRY_ZGmj.js} +1 -1
  10. package/src/assets/web-panel/assets/{Chat-BgI7t-iW.js → Chat-D7Vuwfe2.js} +6 -6
  11. package/src/assets/web-panel/assets/ChatBubbleRenderer-BS2q_hPX.js +1 -0
  12. package/src/assets/web-panel/assets/{Checkbox-DuWsZP4g.js → Checkbox-B406i7N1.js} +1 -1
  13. package/src/assets/web-panel/assets/{Codegen-DyoTNmYg.js → Codegen-BvTCqHi3.js} +1 -1
  14. package/src/assets/web-panel/assets/{Col-DttmlDRk.js → Col-DRiyxTQP.js} +1 -1
  15. package/src/assets/web-panel/assets/{Community-D9nnIdKn.js → Community-DWmhxHQa.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compact-C8KVQaHb.js → Compact-DO1HBZEz.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compliance-R2owqgjj.js → Compliance-D4j-VHwS.js} +1 -1
  18. package/src/assets/web-panel/assets/{Cowork-DwGMMjRn.js → Cowork-BGBkWtat.js} +3 -3
  19. package/src/assets/web-panel/assets/{Cron-BSTcN_lK.js → Cron-Xa9PtMUQ.js} +2 -2
  20. package/src/assets/web-panel/assets/{Crosschain-CTNuIbFD.js → Crosschain-wVWs4lqN.js} +1 -1
  21. package/src/assets/web-panel/assets/{DID-CgApGsFP.js → DID-DTkqiRuT.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dashboard-D_OJ3UN5.js → Dashboard-d9STUbrr.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dropdown-B84Jwra_.js → Dropdown-CrdxS-C8.js} +1 -1
  24. package/src/assets/web-panel/assets/{EmailListRenderer-Bv-YO-6y.js → EmailListRenderer-D78XHUEp.js} +1 -1
  25. package/src/assets/web-panel/assets/{FamilyGuardDashboard-drgZ408Y.js → FamilyGuardDashboard-iAeSETIP.js} +1 -1
  26. package/src/assets/web-panel/assets/{Federation-CtzFkdW2.js → Federation-CTV1Sxqs.js} +1 -1
  27. package/src/assets/web-panel/assets/{FormItemContext-BFAvNhl9.js → FormItemContext-BtwNuQKK.js} +1 -1
  28. package/src/assets/web-panel/assets/{GenericCardRenderer-DnuEyz_l.js → GenericCardRenderer-CdEgHjkl.js} +1 -1
  29. package/src/assets/web-panel/assets/{Git-jlHajmRJ.js → Git-BTo-PJr_.js} +2 -2
  30. package/src/assets/web-panel/assets/{Governance-DmJC7PGL.js → Governance-DquOG94r.js} +1 -1
  31. package/src/assets/web-panel/assets/{Inference-B-u7xD2n.js → Inference-DDtcBxRB.js} +1 -1
  32. package/src/assets/web-panel/assets/{KnowledgeGraph-BaYCA2Cd.js → KnowledgeGraph-KUOmNj5C.js} +1 -1
  33. package/src/assets/web-panel/assets/{Logs-DTNYQWfp.js → Logs-HKm7kRs7.js} +2 -2
  34. package/src/assets/web-panel/assets/{Marketplace-CUu1xYvo.js → Marketplace-IxrOcbFB.js} +1 -1
  35. package/src/assets/web-panel/assets/{McpTools-BmoeTyrC.js → McpTools-D6a1LM3S.js} +5 -5
  36. package/src/assets/web-panel/assets/{Memory-DxTU3QU7.js → Memory-lFkD2ZuM.js} +2 -2
  37. package/src/assets/web-panel/assets/{MobileBridge-CpcOlKAD.js → MobileBridge-xwuQTps5.js} +2 -2
  38. package/src/assets/web-panel/assets/MobileProjects-BYr1D3WO.js +1 -0
  39. package/src/assets/web-panel/assets/{Mtc-LfxwOm0x.js → Mtc-BXpJGrjm.js} +5 -5
  40. package/src/assets/web-panel/assets/{MtcAudit-D6A9Gjkh.js → MtcAudit-CWttaim1.js} +2 -2
  41. package/src/assets/web-panel/assets/{Multisig-Ch_jofPV.js → Multisig-jKgTuVLS.js} +3 -3
  42. package/src/assets/web-panel/assets/{NLProgramming-Bkvogg0I.js → NLProgramming-xl4RDzQj.js} +1 -1
  43. package/src/assets/web-panel/assets/{Notes-C5t5Xihm.js → Notes-DPBOvscE.js} +3 -3
  44. package/src/assets/web-panel/assets/{NotificationSettings-CTpDUNCb.js → NotificationSettings-8TkIkRo3.js} +1 -1
  45. package/src/assets/web-panel/assets/OrderTableRenderer-Dwa-XtoE.js +1 -0
  46. package/src/assets/web-panel/assets/{Organization-Dr37BaXa.js → Organization-CJ0xVwZM.js} +4 -4
  47. package/src/assets/web-panel/assets/{Overflow-ZGjsdP7N.js → Overflow-V7VuUslt.js} +1 -1
  48. package/src/assets/web-panel/assets/{P2P-bWJU5Vxd.js → P2P-BxuccEGq.js} +2 -2
  49. package/src/assets/web-panel/assets/PdhVaultBrowser-DaP2Q5kU.js +7 -0
  50. package/src/assets/web-panel/assets/{Permissions-BOSnFZaC.js → Permissions-CPJFF0zU.js} +4 -4
  51. package/src/assets/web-panel/assets/{PersonalDataHub-X4SgjP6P.js → PersonalDataHub-Cmn2uiuw.js} +4 -4
  52. package/src/assets/web-panel/assets/{Pipeline-DoJhB9bj.js → Pipeline-0zX89_iz.js} +1 -1
  53. package/src/assets/web-panel/assets/{Privacy-OM9lDj-R.js → Privacy-DROUg3XE.js} +1 -1
  54. package/src/assets/web-panel/assets/{ProjectInit-BXQEOmLn.js → ProjectInit-c5KESOK4.js} +2 -2
  55. package/src/assets/web-panel/assets/{ProjectSettings-DBXo3K5u.js → ProjectSettings-BfiCcnb_.js} +2 -2
  56. package/src/assets/web-panel/assets/{Projects-CJ4DBJlS.js → Projects-BtZH5-Eh.js} +1 -1
  57. package/src/assets/web-panel/assets/{Providers-Tk9SawmO.js → Providers-C9Rr_dOk.js} +1 -1
  58. package/src/assets/web-panel/assets/{QuickAsk-DRI1-nTC.js → QuickAsk-Du4p90W6.js} +1 -1
  59. package/src/assets/web-panel/assets/{Recommend-DtrIVTu9.js → Recommend-B-DQenTl.js} +1 -1
  60. package/src/assets/web-panel/assets/{Reputation-DkH8ImwZ.js → Reputation-DvwlAVRZ.js} +1 -1
  61. package/src/assets/web-panel/assets/{Row-DpA9dlvi.js → Row-rTnbvkP-.js} +1 -1
  62. package/src/assets/web-panel/assets/{RssFeed-DV3OhxWd.js → RssFeed-DwvsqWbB.js} +3 -3
  63. package/src/assets/web-panel/assets/{Search-QxdntiQx.js → Search-U_Xj5SvF.js} +1 -1
  64. package/src/assets/web-panel/assets/{Security-CGuEnrD2.js → Security-CdjsVDQ8.js} +4 -4
  65. package/src/assets/web-panel/assets/{Services-BvwSSD8b.js → Services-DUd3mFXk.js} +2 -2
  66. package/src/assets/web-panel/assets/{Skeleton-sx_8L3-5.js → Skeleton-CA2gCJmY.js} +1 -1
  67. package/src/assets/web-panel/assets/{Skills-dWOwxRsu.js → Skills-BIw7Rb-m.js} +1 -1
  68. package/src/assets/web-panel/assets/{Sla-zxXnfKrT.js → Sla-vySPesC0.js} +1 -1
  69. package/src/assets/web-panel/assets/{SpeechSettings-CmFlcNjr.js → SpeechSettings-EniuTjBJ.js} +1 -1
  70. package/src/assets/web-panel/assets/{SyncSettings-BeXeqURL.js → SyncSettings-DkrqbzYS.js} +2 -2
  71. package/src/assets/web-panel/assets/Tasks-oyPnWRbw.js +1 -0
  72. package/src/assets/web-panel/assets/{Templates-DlgR3XFH.js → Templates-C2Kvn60U.js} +1 -1
  73. package/src/assets/web-panel/assets/{Tenant-0P8HgQaM.js → Tenant-x6jVMx4D.js} +1 -1
  74. package/src/assets/web-panel/assets/Terminal-C2nZbPBs.js +3 -0
  75. package/src/assets/web-panel/assets/{TimelineRenderer-hbO7agZs.js → TimelineRenderer-BF6HAETd.js} +1 -1
  76. package/src/assets/web-panel/assets/{Tokens-CsmhgTBO.js → Tokens-B-KcVAin.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trigger-DnaF_2PP.js → Trigger-B-Caiptm.js} +1 -1
  78. package/src/assets/web-panel/assets/{Trust-C1oafGj1.js → Trust-_8sq7pJp.js} +1 -1
  79. package/src/assets/web-panel/assets/{UkeySign-eLL4DOmC.js → UkeySign-CLveUEgo.js} +1 -1
  80. package/src/assets/web-panel/assets/{VideoEditing-CX45sVq7.js → VideoEditing-iVc9jxt9.js} +1 -1
  81. package/src/assets/web-panel/assets/{Wallet-aWPqpHdQ.js → Wallet-D1n5pidD.js} +4 -4
  82. package/src/assets/web-panel/assets/{WebAuthn-DMYV1MAo.js → WebAuthn-CA8kubXb.js} +5 -5
  83. package/src/assets/web-panel/assets/{WorkflowEditor-D9uRIJvH.js → WorkflowEditor-BXWwd_fB.js} +1 -1
  84. package/src/assets/web-panel/assets/{chat-BmWYfCxG.js → chat-DUNkQr1A.js} +1 -1
  85. package/src/assets/web-panel/assets/{colors-DqvTCkBe.js → colors-Dz5ozTcp.js} +1 -1
  86. package/src/assets/web-panel/assets/{compact-item-Bh0L0ejI.js → compact-item-CZ5-JSLh.js} +1 -1
  87. package/src/assets/web-panel/assets/{createContext-r2qgp1mn.js → createContext-CFxlcPug.js} +1 -1
  88. package/src/assets/web-panel/assets/devWarning-BMRVR8Xp.js +1 -0
  89. package/src/assets/web-panel/assets/{hasIn-BcffXa-S.js → hasIn-CqWIkHJm.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-DutDlDUF.js → index-3elHm6lI.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-XwbSqOB2.js → index-8l5LLWxH.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-C2-02rrp.js → index-BBq1ySIt.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-CY8RXaZR.js → index-BI2EU3hC.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-B6pAm1iJ.js → index-BJt6sNTF.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Dcjol7ot.js → index-BLLSLAC3.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-U86pxDyR.js → index-BLNgGXeg.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DPFT7J7I.js → index-BO-yo7Jv.js} +1 -1
  98. package/src/assets/web-panel/assets/index-BT2s_zxU.js +1 -0
  99. package/src/assets/web-panel/assets/{index-DRXcGa5y.js → index-BiAcVeea.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-B7z0qK1W.js → index-C2SoMbLc.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DW1y18GR.js → index-C5XUilwu.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BCBqTRWH.js → index-C5zhjact.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BMn_luHQ.js → index-CBeASfAD.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-COrfHebA.js → index-CD7UjlnE.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DxajFkK2.js → index-CDq8IVvv.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CJt0iuep.js → index-CE-gpaY9.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BtyXyl3t.js → index-CL6wt2JN.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Cbj6C3pA.js → index-CLu3Oyef.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CKnEtlZD.js → index-C_WWTpLE.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-De36_UgR.js → index-CbcHBDYj.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CAlxkpnv.js → index-CguUaiiY.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Ct8qhPZe.js → index-Ci1-8q-g.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BbMox24t.js → index-CvMZxZOn.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BLN-neIf.js → index-D6Nkerss.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-h4O0AcBt.js → index-DEzYXMgc.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-BXXxkeij.js → index-DF0hXW5L.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-B3mmDuOv.js → index-DGAjS_D9.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CDR3GmaO.js → index-DO6mf95c.js} +1 -1
  119. package/src/assets/web-panel/assets/index-DOTL6NDc.js +1 -0
  120. package/src/assets/web-panel/assets/{index-BeV-KoQl.js → index-DUyhvh0L.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-Bma_yHcC.js → index-DW18L-o6.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-585fuGAN.js → index-Dc-5Ulgt.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CCyB-RK5.js → index-DvUlrMy-.js} +3 -3
  124. package/src/assets/web-panel/assets/{index-DWlDSE0F.js → index-FsYDYVUk.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BXae4ZyX.js → index-GVbsyUQm.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-C9nh3ANl.js → index-noQc_RpT.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BQlAPNSU.js → index-rR060KAF.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-S4E77Aer.js → index-xaZX6ZDL.js} +1 -1
  129. package/src/assets/web-panel/assets/{initDefaultProps-C1d8I-BX.js → initDefaultProps-PIetywTX.js} +1 -1
  130. package/src/assets/web-panel/assets/{motion-Dq7fiy4Y.js → motion-gQJEK3wO.js} +1 -1
  131. package/src/assets/web-panel/assets/{move-Bqb2dySM.js → move-ML1nRxts.js} +1 -1
  132. package/src/assets/web-panel/assets/{omit-BUYqb4My.js → omit-wUWsw3YL.js} +1 -1
  133. package/src/assets/web-panel/assets/{pickAttrs-DeytiKlZ.js → pickAttrs-A0Wlomih.js} +1 -1
  134. package/src/assets/web-panel/assets/{placementArrow-xrXZWCqG.js → placementArrow-C5RKYdxT.js} +1 -1
  135. package/src/assets/web-panel/assets/{responsiveObserve-CcL2K-YY.js → responsiveObserve-DIxNVSJl.js} +1 -1
  136. package/src/assets/web-panel/assets/{slide-DmCWaic7.js → slide-B-tNesVu.js} +1 -1
  137. package/src/assets/web-panel/assets/{statusUtils-CqNrFif7.js → statusUtils-CftzO200.js} +1 -1
  138. package/src/assets/web-panel/assets/{styleChecker-C436m5Xy.js → styleChecker-CMgvWu90.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFlexGapSupport-CVhutCN8.js → useFlexGapSupport-sxqoDNhZ.js} +1 -1
  140. package/src/assets/web-panel/assets/{useFs-DUd49Bui.js → useFs-CowEXz4v.js} +1 -1
  141. package/src/assets/web-panel/assets/{usePersonalDataHub-fuS9raic.js → usePersonalDataHub-6ISRG7V-.js} +1 -1
  142. package/src/assets/web-panel/assets/{vnode-C3kmDmk-.js → vnode-C2mnXfmw.js} +1 -1
  143. package/src/assets/web-panel/assets/{zoom-hX-F1dT-.js → zoom-DMsur0jx.js} +1 -1
  144. package/src/assets/web-panel/index.html +1 -1
  145. package/src/commands/agent.js +66 -28
  146. package/src/harness/mcp-client.js +48 -2
  147. package/src/lib/llm-pricing.js +9 -0
  148. package/src/lib/permission-rules.cjs +11 -1
  149. package/src/lib/personal-data-hub-wiring.js +24 -0
  150. package/src/lib/repl-multiline.js +64 -0
  151. package/src/lib/repl-rewind.js +65 -2
  152. package/src/lib/repl-vim.js +445 -0
  153. package/src/lib/safe-mode.js +17 -3
  154. package/src/lib/skill-loader.js +45 -1
  155. package/src/lib/slash-commands.js +13 -3
  156. package/src/lib/status-line.cjs +33 -3
  157. package/src/repl/agent-repl.js +253 -27
  158. package/src/repl/session-cost.js +98 -1
  159. package/src/runtime/agent-core.js +23 -8
  160. package/src/runtime/fallback-model.js +125 -30
  161. package/src/runtime/headless-runner.js +2 -0
  162. package/src/runtime/headless-stream.js +2 -0
  163. package/src/runtime/mcp-config.js +14 -3
  164. package/src/assets/web-panel/assets/ChatBubbleRenderer-CfpKEQUF.js +0 -1
  165. package/src/assets/web-panel/assets/MobileProjects-Bjh_z16l.js +0 -1
  166. package/src/assets/web-panel/assets/OrderTableRenderer-ST2lr-Bi.js +0 -1
  167. package/src/assets/web-panel/assets/PdhVaultBrowser-BRVoW-ye.js +0 -7
  168. package/src/assets/web-panel/assets/Tasks-iImd8xSO.js +0 -1
  169. package/src/assets/web-panel/assets/Terminal-B5VDEEHD.js +0 -3
  170. package/src/assets/web-panel/assets/devWarning-CusWDjWW.js +0 -1
  171. package/src/assets/web-panel/assets/index-BhYltBvN.js +0 -1
  172. package/src/assets/web-panel/assets/index-CZiIHw4e.js +0 -1
@@ -24,6 +24,11 @@ import os from "os";
24
24
  import path from "path";
25
25
  import { logger } from "../lib/logger.js";
26
26
  import { getPlanModeManager, PlanState } from "../lib/plan-mode.js";
27
+ import { createVimState, feedNormalKey } from "../lib/repl-vim.js";
28
+ import {
29
+ analyzeContinuation,
30
+ joinContinuation,
31
+ } from "../lib/repl-multiline.js";
27
32
  import { bootstrap, shutdown } from "../runtime/bootstrap.js";
28
33
  import {
29
34
  createSession,
@@ -73,7 +78,10 @@ import {
73
78
  } from "../runtime/agent-core.js";
74
79
  import { expandFileRefs } from "../runtime/file-ref-expander.js";
75
80
  import { composeSystemPrompt } from "../runtime/system-prompt.js";
76
- import { makeFallbackChatFn } from "../runtime/fallback-model.js";
81
+ import {
82
+ makeFallbackChatFn,
83
+ normalizeFallbackModels,
84
+ } from "../runtime/fallback-model.js";
77
85
  import { resolveSlashMacro } from "./slash-macro.js";
78
86
  import { expandMcpPrompt, renderMcpSurface } from "./mcp-prompt.js";
79
87
  import { newCostStore, addUsage } from "./session-cost.js";
@@ -168,6 +176,17 @@ async function agentLoop(messages, options) {
168
176
  ...options,
169
177
  })) {
170
178
  if (event.type === "checkpoint") {
179
+ // Remember which file snapshot lines up with the live conversation so
180
+ // `/rewind <n>` can restore code + conversation together (Claude-Code
181
+ // parity). atMessageCount = messages.length at snapshot time; see
182
+ // repl-rewind.js for how a turn is matched back to its checkpoint.
183
+ if (Array.isArray(options.checkpointMarks)) {
184
+ options.checkpointMarks.push({
185
+ atMessageCount: messages.length,
186
+ id: event.id,
187
+ tool: event.tool,
188
+ });
189
+ }
171
190
  process.stdout.write(
172
191
  chalk.gray(` ⎌ checkpoint ${event.id} (before ${event.tool})\n`),
173
192
  );
@@ -245,12 +264,19 @@ export async function startAgentRepl(options = {}) {
245
264
  // can `cc checkpoint restore` to just before any tool call.
246
265
  const autoCheckpoint = options.autoCheckpoint === true;
247
266
 
248
- // --fallback-model: retry a turn's LLM call once on a backup model when the
249
- // primary errors out (overload / network). Built once; passed into every
250
- // agentLoop call via chatFn. Undefined when no fallback configured.
251
- const _fallbackChatFn = options.fallbackModel
267
+ // --fallback-model: walk an ordered backup-model chain when a turn's LLM
268
+ // call fails (transient error or model-not-found). Built once; passed into
269
+ // every agentLoop call via chatFn. Accepts the resolved chain
270
+ // (options.fallbackModels) or a legacy single model (options.fallbackModel).
271
+ // Undefined when no fallback configured.
272
+ const _fallbackModels = normalizeFallbackModels(
273
+ options.fallbackModels != null
274
+ ? options.fallbackModels
275
+ : options.fallbackModel,
276
+ );
277
+ const _fallbackChatFn = _fallbackModels.length
252
278
  ? makeFallbackChatFn({
253
- fallbackModel: options.fallbackModel,
279
+ fallbackModels: _fallbackModels,
254
280
  onFallback: ({ from, to, error }) =>
255
281
  logger.info(
256
282
  chalk.yellow(
@@ -491,6 +517,10 @@ export async function startAgentRepl(options = {}) {
491
517
  );
492
518
  let _activeOutputStyle = null; // { name, body }
493
519
  const messages = [{ role: "system", content: _replBaseSystem }];
520
+ // Checkpoint marks ({ atMessageCount, id, tool }) recorded as the agent loop
521
+ // emits `checkpoint` events, so `/rewind <n>` can also restore files to the
522
+ // snapshot taken before that turn (Claude-Code rewind = code + conversation).
523
+ const _checkpointMarks = [];
494
524
  // Apply --output-style or the settings.json `outputStyle` default at startup.
495
525
  try {
496
526
  const { resolveOutputStyle } = await import("../lib/output-styles.js");
@@ -638,6 +668,8 @@ export async function startAgentRepl(options = {}) {
638
668
  // (parity with headless; auto-detect already works via process.env).
639
669
  ide: options.ide,
640
670
  cwd: process.cwd(),
671
+ // advertise the session id to spawned stdio MCP servers
672
+ sessionId,
641
673
  },
642
674
  { writeErr: (s) => process.stderr.write(s) },
643
675
  );
@@ -734,16 +766,31 @@ export async function startAgentRepl(options = {}) {
734
766
  }
735
767
  }
736
768
 
769
+ // Vim mode (Claude-Code `/vim` parity): opt-in modal line editing. `_vim`
770
+ // holds the NORMAL-mode engine state while normal mode is active (readline's
771
+ // own key handling is suspended then); it is null in INSERT mode (readline
772
+ // owns editing). Default off — toggled by `/vim`, or on at startup via
773
+ // --vim / CC_VIM=1.
774
+ let _vimEnabled =
775
+ options.vimMode === true || process.env.CC_VIM === "1" ? true : false;
776
+ let _vim = null;
777
+
737
778
  const getPrompt = () => {
779
+ // Mode indicator first so it survives the plan-mode prompt variants.
780
+ const vim = _vimEnabled
781
+ ? _vim
782
+ ? chalk.cyan("[N] ")
783
+ : chalk.dim("[I] ")
784
+ : "";
738
785
  const planManager = getPlanModeManager();
739
786
  if (planManager.isActive()) {
740
787
  const state = planManager.state;
741
788
  if (state === PlanState.APPROVED || state === PlanState.EXECUTING) {
742
- return chalk.green("[plan:exec] > ");
789
+ return vim + chalk.green("[plan:exec] > ");
743
790
  }
744
- return chalk.yellow("[plan] > ");
791
+ return vim + chalk.yellow("[plan] > ");
745
792
  }
746
- return chalk.green("> ");
793
+ return vim + chalk.green("> ");
747
794
  };
748
795
 
749
796
  // `@` tab-completion (Claude-Code @-mention parity): filesystem paths +
@@ -786,6 +833,7 @@ export async function startAgentRepl(options = {}) {
786
833
  "/statusline",
787
834
  "/sub-agents",
788
835
  "/task",
836
+ "/vim",
789
837
  ],
790
838
  getIdeOpenFiles: async () => {
791
839
  const exec = _adhocMcp?.externalToolExecutors?.mcp__ide__getOpenEditors;
@@ -813,6 +861,54 @@ export async function startAgentRepl(options = {}) {
813
861
  completer: atCompleter,
814
862
  });
815
863
 
864
+ // Vim-mode plumbing: capture readline's OWN keypress listeners now so we can
865
+ // suspend them while in NORMAL mode (the engine drives editing then) and
866
+ // reattach them for INSERT mode (readline's rich editing/history/completion).
867
+ const _rlKeypressListeners = process.stdin.isTTY
868
+ ? process.stdin.listeners("keypress").slice()
869
+ : [];
870
+ const _suspendReadlineKeys = () => {
871
+ for (const l of _rlKeypressListeners)
872
+ process.stdin.removeListener("keypress", l);
873
+ };
874
+ const _resumeReadlineKeys = () => {
875
+ const cur = process.stdin.listeners("keypress");
876
+ for (const l of _rlKeypressListeners)
877
+ if (!cur.includes(l)) process.stdin.on("keypress", l);
878
+ };
879
+ // Push the engine's line/cursor onto readline and redraw the current line.
880
+ const _vimSync = (vstate) => {
881
+ try {
882
+ rl.line = vstate.line;
883
+ rl.cursor = Math.max(0, Math.min(vstate.cursor, vstate.line.length));
884
+ if (typeof rl._refreshLine === "function") rl._refreshLine();
885
+ } catch {
886
+ /* redraw is best-effort */
887
+ }
888
+ };
889
+ const _vimEnterNormal = () => {
890
+ const cur = Math.max(
891
+ 0,
892
+ Math.min(rl.cursor, Math.max(0, rl.line.length - 1)),
893
+ );
894
+ _vim = { ...createVimState(rl.line, cur), mode: "normal" };
895
+ _suspendReadlineKeys();
896
+ rl.setPrompt(getPrompt());
897
+ _vimSync(_vim);
898
+ };
899
+ const _vimEnterInsert = (vstate) => {
900
+ _resumeReadlineKeys();
901
+ _vim = null;
902
+ rl.setPrompt(getPrompt());
903
+ _vimSync(vstate);
904
+ };
905
+ // Exposed so /vim can leave normal mode cleanly when disabling.
906
+ const _vimDisable = () => {
907
+ if (_vim) _resumeReadlineKeys();
908
+ _vim = null;
909
+ _vimEnabled = false;
910
+ };
911
+
816
912
  // Esc interrupt (Claude-Code parity): pressing Esc while a turn is in
817
913
  // flight aborts the in-flight agentLoop through its existing AbortSignal
818
914
  // seam (throwIfAborted at each iteration); partial conversation is kept.
@@ -822,8 +918,9 @@ export async function startAgentRepl(options = {}) {
822
918
  let _lastIdleEscAt = 0;
823
919
  if (process.stdin.isTTY) {
824
920
  process.stdin.on("keypress", (_str, key) => {
825
- if (!key || key.name !== "escape" || key.meta) return;
826
- if (_turnAbort) {
921
+ const k = key || {};
922
+ // 1) Turn abort always wins, regardless of vim mode.
923
+ if (k.name === "escape" && !k.meta && _turnAbort) {
827
924
  process.stdout.write(chalk.yellow("\n⎋ interrupting…\n"));
828
925
  try {
829
926
  _turnAbort.abort();
@@ -833,8 +930,34 @@ export async function startAgentRepl(options = {}) {
833
930
  _turnAbort = null;
834
931
  return;
835
932
  }
836
- // Double-Esc while idle → rewind picker shortcut (Claude-Code parity);
837
- // the actual rewind is `/rewind <n>` so stdin stays readline-owned.
933
+
934
+ // 2) Vim mode: modal editing on the current input line.
935
+ if (_vimEnabled && !_turnAbort) {
936
+ if (!_vim) {
937
+ // INSERT mode — readline owns the keys; Esc switches to NORMAL.
938
+ if (k.name === "escape" && !k.meta) _vimEnterNormal();
939
+ return;
940
+ }
941
+ // NORMAL mode — readline suspended; the engine interprets every key.
942
+ const res = feedNormalKey(_vim, _str || "", k);
943
+ if (res.submit) {
944
+ // Hand the line to readline as a normal Enter (fires 'line', clears).
945
+ _vimEnterInsert({ ...res, cursor: res.line.length });
946
+ process.stdin.emit("keypress", "\r", { name: "return" });
947
+ return;
948
+ }
949
+ if (res.mode === "insert") {
950
+ _vimEnterInsert(res);
951
+ return;
952
+ }
953
+ _vim = res;
954
+ if (res.message === "bell") process.stdout.write("\x07");
955
+ _vimSync(res);
956
+ return;
957
+ }
958
+
959
+ // 3) Non-vim: double-Esc while idle → rewind picker shortcut.
960
+ if (k.name !== "escape" || k.meta) return;
838
961
  const nowTs = Date.now();
839
962
  if (nowTs - _lastIdleEscAt < 600) {
840
963
  _lastIdleEscAt = 0;
@@ -848,7 +971,9 @@ export async function startAgentRepl(options = {}) {
848
971
  );
849
972
  process.stdout.write(
850
973
  chalk.gray(
851
- "Run /rewind <n> to rewind the conversation (files: cc checkpoint restore).\n",
974
+ _checkpointMarks.length
975
+ ? "Run /rewind <n> to rewind the conversation (and optionally its files).\n"
976
+ : "Run /rewind <n> to rewind the conversation.\n",
852
977
  ),
853
978
  );
854
979
  prompt();
@@ -950,7 +1075,24 @@ export async function startAgentRepl(options = {}) {
950
1075
  // when the current turn finishes.
951
1076
  let _processingLine = false;
952
1077
  const _pendingLines = [];
1078
+ // Multiline input (Claude-Code parity): a physical line ending in a
1079
+ // continuation backslash keeps the prompt open; `_mlBuffer` accumulates the
1080
+ // pieces and the whole block submits when a line does not continue.
1081
+ const _mlBuffer = [];
953
1082
  const handleLine = async (input) => {
1083
+ // Backslash continuation — accumulate and re-prompt without firing a turn.
1084
+ const _cont = analyzeContinuation(input);
1085
+ if (_cont.continued) {
1086
+ _mlBuffer.push(_cont.text);
1087
+ rl.setPrompt(chalk.dim("... "));
1088
+ rl.prompt();
1089
+ return;
1090
+ }
1091
+ if (_mlBuffer.length) {
1092
+ input = joinContinuation(_mlBuffer, input);
1093
+ _mlBuffer.length = 0;
1094
+ }
1095
+
954
1096
  const trimmed = input.trim();
955
1097
  if (!trimmed) {
956
1098
  prompt();
@@ -1022,12 +1164,18 @@ export async function startAgentRepl(options = {}) {
1022
1164
  logger.log(
1023
1165
  ` ${chalk.cyan("# <note>")} Remember a note in the project cc.md`,
1024
1166
  );
1167
+ logger.log(
1168
+ ` ${chalk.cyan("… \\")} End a line with \\ to continue input onto the next line`,
1169
+ );
1025
1170
  logger.log(` ${chalk.cyan("/exit")} Exit the agent`);
1026
1171
  logger.log(
1027
1172
  ` ${chalk.cyan("/model")} Show/change model (/model <name>)`,
1028
1173
  );
1029
1174
  logger.log(` ${chalk.cyan("/provider")} Show/change provider`);
1030
1175
  logger.log(` ${chalk.cyan("/clear")} Clear conversation`);
1176
+ logger.log(
1177
+ ` ${chalk.cyan("/vim")} Toggle vim-mode line editing (/vim [on|off]; Esc → NORMAL)`,
1178
+ );
1031
1179
  logger.log(
1032
1180
  ` ${chalk.cyan("/statusline")} Context-usage line on/off (/statusline [on|off])`,
1033
1181
  );
@@ -1044,7 +1192,7 @@ export async function startAgentRepl(options = {}) {
1044
1192
  ` ${chalk.cyan("/context")} Live context-window usage by role`,
1045
1193
  );
1046
1194
  logger.log(
1047
- ` ${chalk.cyan("/cost")} Session token spend + estimated $ (so far)`,
1195
+ ` ${chalk.cyan("/cost")} Session token spend + estimated $ (per model & category)`,
1048
1196
  );
1049
1197
  logger.log(
1050
1198
  ` ${chalk.cyan("/permissions")} Allow/ask/deny rules in effect this session`,
@@ -1053,7 +1201,7 @@ export async function startAgentRepl(options = {}) {
1053
1201
  ` ${chalk.cyan("/export")} Save this conversation to a Markdown file (/export [path])`,
1054
1202
  );
1055
1203
  logger.log(
1056
- ` ${chalk.cyan("/rewind")} Rewind conversation to an earlier turn (double-Esc lists)`,
1204
+ ` ${chalk.cyan("/rewind")} Rewind conversation + files to an earlier turn (double-Esc lists)`,
1057
1205
  );
1058
1206
  logger.log(
1059
1207
  ` ${chalk.cyan("/cd <dir>")} Change working directory mid-session (completion/memory follow)`,
@@ -1230,11 +1378,30 @@ export async function startAgentRepl(options = {}) {
1230
1378
 
1231
1379
  if (trimmed === "/clear") {
1232
1380
  messages.length = 1; // Keep system prompt
1381
+ _checkpointMarks.length = 0; // checkpoint marks no longer map to anything
1233
1382
  logger.info("Conversation cleared");
1234
1383
  prompt();
1235
1384
  return;
1236
1385
  }
1237
1386
 
1387
+ if (trimmed === "/vim" || trimmed.startsWith("/vim ")) {
1388
+ const arg = trimmed.slice("/vim".length).trim().toLowerCase();
1389
+ const turnOn = arg === "on" || (arg === "" && !_vimEnabled);
1390
+ if (turnOn) {
1391
+ _vimEnabled = true;
1392
+ logger.info(
1393
+ chalk.gray(
1394
+ "Vim mode: on — Esc → NORMAL (hjkl/w/b/e, x/dd/dw, i/a/A, etc.), i to insert.",
1395
+ ),
1396
+ );
1397
+ } else {
1398
+ _vimDisable();
1399
+ logger.info(chalk.gray("Vim mode: off"));
1400
+ }
1401
+ prompt();
1402
+ return;
1403
+ }
1404
+
1238
1405
  if (trimmed === "/statusline" || trimmed.startsWith("/statusline ")) {
1239
1406
  const arg = trimmed.slice("/statusline".length).trim().toLowerCase();
1240
1407
  if (arg === "off") {
@@ -1327,17 +1494,21 @@ export async function startAgentRepl(options = {}) {
1327
1494
 
1328
1495
  if (trimmed === "/rewind" || trimmed.startsWith("/rewind ")) {
1329
1496
  try {
1330
- const { listUserTurns, rewindToTurn, renderTurnList } =
1331
- await import("../lib/repl-rewind.js");
1497
+ const {
1498
+ listUserTurns,
1499
+ rewindToTurn,
1500
+ renderTurnList,
1501
+ pickCheckpointForTurn,
1502
+ pruneMarksAfter,
1503
+ } = await import("../lib/repl-rewind.js");
1332
1504
  const arg = trimmed.slice("/rewind".length).trim();
1333
1505
  if (!arg) {
1334
1506
  logger.log(chalk.bold("\nRewind — pick a user turn (newest first):"));
1335
1507
  logger.log(renderTurnList(listUserTurns(messages)));
1336
- logger.log(
1337
- chalk.gray(
1338
- "Usage: /rewind <n> (conversation only — restore files with `cc checkpoint restore`)",
1339
- ),
1340
- );
1508
+ const fileHint = _checkpointMarks.length
1509
+ ? " (restores files to that point too — checkpoints are on)"
1510
+ : " (conversation only — start with --checkpoint / git to also rewind files)";
1511
+ logger.log(chalk.gray(`Usage: /rewind <n>${fileHint}`));
1341
1512
  } else {
1342
1513
  const res = rewindToTurn(messages, arg);
1343
1514
  if (!res) {
@@ -1348,6 +1519,48 @@ export async function startAgentRepl(options = {}) {
1348
1519
  `⎌ rewound — dropped ${res.removed} message(s); edit and resend below`,
1349
1520
  ),
1350
1521
  );
1522
+ // Claude-Code parity: rewind restores files too. Match the dropped
1523
+ // turn to the snapshot taken just before it first mutated the tree,
1524
+ // then offer to roll the working tree back to it (undoable — the
1525
+ // restore takes its own safety checkpoint first).
1526
+ const cp = pickCheckpointForTurn(_checkpointMarks, res.index);
1527
+ pruneMarksAfter(_checkpointMarks, res.index);
1528
+ if (cp) {
1529
+ const q = (p) => new Promise((r) => rl.question(p, r));
1530
+ const ans = (
1531
+ await q(
1532
+ chalk.yellow(
1533
+ ` Also restore files to before this turn? (checkpoint ${cp.id}) [Y/n] `,
1534
+ ),
1535
+ )
1536
+ )
1537
+ .trim()
1538
+ .toLowerCase();
1539
+ if (ans === "" || ans === "y" || ans === "yes") {
1540
+ try {
1541
+ const { rewindTo } =
1542
+ await import("../lib/checkpoint-store.js");
1543
+ const r = rewindTo(process.cwd(), cp.id, {
1544
+ session: sessionId,
1545
+ });
1546
+ logger.log(
1547
+ chalk.green(
1548
+ ` ⎌ files restored to ${cp.id} (${r.modified} changed, ${r.deleted} removed, ${r.recreated} recreated; undo: cc checkpoint restore ${r.safetyId})`,
1549
+ ),
1550
+ );
1551
+ } catch (e) {
1552
+ logger.error(
1553
+ ` file restore skipped: ${e.message} (conversation already rewound)`,
1554
+ );
1555
+ }
1556
+ } else {
1557
+ logger.log(
1558
+ chalk.gray(
1559
+ ` files left as-is — restore later with cc checkpoint restore ${cp.id}`,
1560
+ ),
1561
+ );
1562
+ }
1563
+ }
1351
1564
  prompt();
1352
1565
  if (res.text) rl.write(res.text);
1353
1566
  return;
@@ -2279,15 +2492,27 @@ export async function startAgentRepl(options = {}) {
2279
2492
  // parity). In-memory accumulation, so it works without session persistence.
2280
2493
  if (trimmed === "/cost" || trimmed === "/cost ") {
2281
2494
  let overrides;
2495
+ let visionModel;
2282
2496
  try {
2283
2497
  const { loadConfig } = await import("../lib/config-manager.js");
2284
- overrides = loadConfig()?.llm?.pricing;
2498
+ const cfg = loadConfig();
2499
+ overrides = cfg?.llm?.pricing;
2500
+ visionModel = cfg?.llm?.visionModel;
2285
2501
  } catch (_err) {
2286
- // pricing overrides are optional — fall back to the built-in table
2287
- }
2502
+ // config is optional — fall back to the built-in pricing table
2503
+ }
2504
+ // Category breakdown (Claude-Code parity): classify spend by model role —
2505
+ // the live model is "main", the vision model "vision", the fallback chain
2506
+ // "fallback", a switched-to model "other". Shown only when >1 was used.
2507
+ const roles = {
2508
+ mainProvider: provider,
2509
+ mainModel: _curModel || model,
2510
+ visionModel: visionModel || "doubao-seed-1-6-vision-250815",
2511
+ fallbackModels: _fallbackModels || [],
2512
+ };
2288
2513
  const { renderSessionCost } = await import("./session-cost.js");
2289
2514
  logger.log(
2290
- renderSessionCost(_costStore, { pricingOverrides: overrides }),
2515
+ renderSessionCost(_costStore, { pricingOverrides: overrides, roles }),
2291
2516
  );
2292
2517
  prompt();
2293
2518
  return;
@@ -2522,6 +2747,7 @@ export async function startAgentRepl(options = {}) {
2522
2747
  additionalDirectories,
2523
2748
  autoCheckpoint,
2524
2749
  checkpointSession: sessionId,
2750
+ checkpointMarks: _checkpointMarks,
2525
2751
  prepareCall,
2526
2752
  approvalGate: _approvalGate,
2527
2753
  permissionRules: _permissionRules,
@@ -69,6 +69,81 @@ export function addUsage(store, events) {
69
69
  return s;
70
70
  }
71
71
 
72
+ /**
73
+ * Classify a priced model row into a cost CATEGORY by its role in the session
74
+ * (Claude-Code `/cost` breakdown parity). Roles are derived from config + the
75
+ * live session: the active model is "main", the configured vision model is
76
+ * "vision", any model in the fallback chain is "fallback", anything else (e.g.
77
+ * a model switched to mid-session) is "other".
78
+ *
79
+ * The active model wins over vision/fallback when names collide, so a session
80
+ * that never used vision shows everything as "main".
81
+ *
82
+ * @param {string} provider
83
+ * @param {string} model
84
+ * @param {{mainProvider?:string, mainModel?:string, visionModel?:string, fallbackModels?:string[]}} roles
85
+ * @returns {"main"|"vision"|"fallback"|"other"}
86
+ */
87
+ export function classifyModelRole(provider, model, roles = {}) {
88
+ const lc = (x) => String(x || "").toLowerCase();
89
+ const p = lc(provider);
90
+ const m = lc(model);
91
+ if (
92
+ roles.mainModel &&
93
+ m === lc(roles.mainModel) &&
94
+ (!roles.mainProvider || p === lc(roles.mainProvider))
95
+ ) {
96
+ return "main";
97
+ }
98
+ if (roles.visionModel && m === lc(roles.visionModel)) return "vision";
99
+ if (
100
+ Array.isArray(roles.fallbackModels) &&
101
+ roles.fallbackModels.some((fm) => lc(fm) === m)
102
+ ) {
103
+ return "fallback";
104
+ }
105
+ return "other";
106
+ }
107
+
108
+ /**
109
+ * Group a priced rollup's per-model rows into categories. Operates on the output
110
+ * of priceRollup (rows carry cost/matched/free), so dollar sums are accurate and
111
+ * unpriced models are flagged rather than silently counted as $0.
112
+ *
113
+ * @returns {Array<{category,inputTokens,outputTokens,totalTokens,calls,cost,models,anyUnpriced}>}
114
+ * sorted by cost (then tokens) descending.
115
+ */
116
+ export function categorizeByRole(pricedResult, roles = {}) {
117
+ const cats = new Map();
118
+ for (const row of pricedResult?.byModel || []) {
119
+ const category = classifyModelRole(row.provider, row.model, roles);
120
+ let c = cats.get(category);
121
+ if (!c) {
122
+ c = {
123
+ category,
124
+ inputTokens: 0,
125
+ outputTokens: 0,
126
+ totalTokens: 0,
127
+ calls: 0,
128
+ cost: 0,
129
+ models: [],
130
+ anyUnpriced: false,
131
+ };
132
+ cats.set(category, c);
133
+ }
134
+ c.inputTokens += num(row.inputTokens);
135
+ c.outputTokens += num(row.outputTokens);
136
+ c.totalTokens += num(row.totalTokens);
137
+ c.calls += num(row.calls);
138
+ if (row.matched && !row.free) c.cost += num(row.cost);
139
+ if (!row.matched && !row.free) c.anyUnpriced = true;
140
+ if (row.model && !c.models.includes(row.model)) c.models.push(row.model);
141
+ }
142
+ return Array.from(cats.values()).sort(
143
+ (a, b) => b.cost - a.cost || b.totalTokens - a.totalTokens,
144
+ );
145
+ }
146
+
72
147
  /** Snapshot the store as the `{ total, byModel[] }` aggregate priceRollup wants. */
73
148
  export function costAggregate(store) {
74
149
  const s = store || newCostStore();
@@ -84,7 +159,7 @@ export function costAggregate(store) {
84
159
  * Render the live session cost. `pricingOverrides` is typically
85
160
  * `config.llm.pricing`. Returns plain text (the REPL does the I/O).
86
161
  */
87
- export function renderSessionCost(store, { pricingOverrides } = {}) {
162
+ export function renderSessionCost(store, { pricingOverrides, roles } = {}) {
88
163
  const agg = costAggregate(store);
89
164
  if (agg.total.calls === 0) {
90
165
  return "Session cost: no LLM calls yet this session.";
@@ -108,6 +183,28 @@ export function renderSessionCost(store, { pricingOverrides } = {}) {
108
183
  : "unpriced";
109
184
  lines.push(` ${provider} ${model} ${price} ${tokens}`);
110
185
  }
186
+
187
+ // Category breakdown (main / vision / fallback / other) — only worth showing
188
+ // when more than one category was actually used; a single-model session is
189
+ // already fully described by the per-model rows above.
190
+ if (roles) {
191
+ const cats = categorizeByRole(result, roles);
192
+ if (cats.length >= 2) {
193
+ const totalCost = num(result.cost.totalCost);
194
+ lines.push(" by category:");
195
+ for (const c of cats) {
196
+ const label = c.category.padEnd(9);
197
+ const price =
198
+ c.anyUnpriced && c.cost === 0 ? "unpriced" : fmtUsd(c.cost);
199
+ const pct =
200
+ totalCost > 0 ? ` (${Math.round((c.cost / totalCost) * 100)}%)` : "";
201
+ lines.push(
202
+ ` ${label} ${price}${pct} in=${c.inputTokens} out=${c.outputTokens} ${c.calls} calls`,
203
+ );
204
+ }
205
+ }
206
+ }
207
+
111
208
  if (result.unpriced.length > 0) {
112
209
  lines.push(
113
210
  ` note: ${result.unpriced.length} model(s) have no rate — ` +
@@ -921,9 +921,8 @@ export async function executeTool(name, args, context = {}) {
921
921
  settingsVerdict.decision !== "allow" &&
922
922
  args?.path
923
923
  ) {
924
- const { sensitiveFileReason } = await import(
925
- "../lib/sensitive-file-guard.js"
926
- );
924
+ const { sensitiveFileReason } =
925
+ await import("../lib/sensitive-file-guard.js");
927
926
  const sensReason = sensitiveFileReason(args.path);
928
927
  if (sensReason) {
929
928
  const confirm = context.permissionConfirm || context.shellConfirm || null;
@@ -1409,10 +1408,18 @@ async function executeToolInner(
1409
1408
  cwd: task.cwd,
1410
1409
  shell: true,
1411
1410
  windowsHide: true,
1412
- // Same CC_SESSION_ID correlation as the foreground path.
1411
+ // Same agent-identity env as the foreground path: CLAUDECODE marks
1412
+ // "running under the agent"; the session id correlates work to the
1413
+ // run (CC_SESSION_ID + CLAUDE_CODE_SESSION_ID for Claude-Code parity).
1413
1414
  env: {
1414
1415
  ...process.env,
1415
- ...(sessionId ? { CC_SESSION_ID: String(sessionId) } : {}),
1416
+ CLAUDECODE: "1",
1417
+ ...(sessionId
1418
+ ? {
1419
+ CC_SESSION_ID: String(sessionId),
1420
+ CLAUDE_CODE_SESSION_ID: String(sessionId),
1421
+ }
1422
+ : {}),
1416
1423
  },
1417
1424
  // POSIX: own process group so check_shell{kill}/teardown can signal
1418
1425
  // the whole tree (shell + its grandchild command). No-op on Windows
@@ -1471,11 +1478,19 @@ async function executeToolInner(
1471
1478
  encoding: "utf8",
1472
1479
  timeout: _resolveShellTimeout(args.timeout),
1473
1480
  maxBuffer: 1024 * 1024,
1474
- // CC_SESSION_ID (Claude-Code CLAUDE_CODE_SESSION_ID parity,
1475
- // 2.1.132): lets scripts/hooks correlate work to the agent session.
1481
+ // Agent-identity env for shell subprocesses (Claude-Code 2.1.132
1482
+ // parity): CLAUDECODE=1 marks "running under the agent"; CC_SESSION_ID
1483
+ // + its CLAUDE_CODE_SESSION_ID mirror let scripts/hooks correlate work
1484
+ // to the agent session (the mirror is what CC-targeting tools expect).
1476
1485
  env: {
1477
1486
  ...process.env,
1478
- ...(sessionId ? { CC_SESSION_ID: String(sessionId) } : {}),
1487
+ CLAUDECODE: "1",
1488
+ ...(sessionId
1489
+ ? {
1490
+ CC_SESSION_ID: String(sessionId),
1491
+ CLAUDE_CODE_SESSION_ID: String(sessionId),
1492
+ }
1493
+ : {}),
1479
1494
  },
1480
1495
  });
1481
1496
  return attachDescriptor(