chainlesschain 0.162.49 → 0.162.61

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 (174) 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-BiB8WfIz.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-DkuYVafg.js → ActionButton-NLBhC6jG.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-Ba_h8Tub.js → Analytics-k62j-xiL.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-yb8Zm9MX.js → AppLayout-spr0Sm6J.js} +3 -3
  7. package/src/assets/web-panel/assets/{Audit-BGjex2fm.js → Audit-C3NHJos3.js} +1 -1
  8. package/src/assets/web-panel/assets/{Backup-IFQ2hOF2.js → Backup-C2V9tGqF.js} +1 -1
  9. package/src/assets/web-panel/assets/{BaseInput-W8AkPkrV.js → BaseInput-DeKm11mH.js} +1 -1
  10. package/src/assets/web-panel/assets/{Chat-BgI7t-iW.js → Chat-CHZ2CU7x.js} +6 -6
  11. package/src/assets/web-panel/assets/ChatBubbleRenderer-DEXSa7tC.js +1 -0
  12. package/src/assets/web-panel/assets/{Checkbox-DuWsZP4g.js → Checkbox-q6E9VeLr.js} +1 -1
  13. package/src/assets/web-panel/assets/{Codegen-DyoTNmYg.js → Codegen--4w4QpUI.js} +1 -1
  14. package/src/assets/web-panel/assets/{Col-DttmlDRk.js → Col-DLOkwTsj.js} +1 -1
  15. package/src/assets/web-panel/assets/{Community-D9nnIdKn.js → Community-B1LxJGfE.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compact-C8KVQaHb.js → Compact-C_769oQZ.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compliance-R2owqgjj.js → Compliance-zsI0s7vB.js} +1 -1
  18. package/src/assets/web-panel/assets/{Cowork-DwGMMjRn.js → Cowork-A1WA6whF.js} +2 -2
  19. package/src/assets/web-panel/assets/{Cron-BSTcN_lK.js → Cron-C3JDTyyd.js} +2 -2
  20. package/src/assets/web-panel/assets/{Crosschain-CTNuIbFD.js → Crosschain-D8O6uB86.js} +1 -1
  21. package/src/assets/web-panel/assets/{DID-CgApGsFP.js → DID-BpOebm5d.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dashboard-D_OJ3UN5.js → Dashboard-Defso6kA.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dropdown-B84Jwra_.js → Dropdown-Cv9BrwT_.js} +1 -1
  24. package/src/assets/web-panel/assets/{EmailListRenderer-Bv-YO-6y.js → EmailListRenderer-BWKHbh4C.js} +1 -1
  25. package/src/assets/web-panel/assets/{FamilyGuardDashboard-drgZ408Y.js → FamilyGuardDashboard--m_Ru7Ci.js} +1 -1
  26. package/src/assets/web-panel/assets/{Federation-CtzFkdW2.js → Federation-Bs6ZcAP0.js} +1 -1
  27. package/src/assets/web-panel/assets/{FormItemContext-BFAvNhl9.js → FormItemContext-CcAs3Acx.js} +1 -1
  28. package/src/assets/web-panel/assets/{GenericCardRenderer-DnuEyz_l.js → GenericCardRenderer-DI1oL4pK.js} +1 -1
  29. package/src/assets/web-panel/assets/{Git-jlHajmRJ.js → Git-C27t3-fW.js} +2 -2
  30. package/src/assets/web-panel/assets/{Governance-DmJC7PGL.js → Governance-Dr_syXc_.js} +1 -1
  31. package/src/assets/web-panel/assets/{Inference-B-u7xD2n.js → Inference-CWM8dIbA.js} +1 -1
  32. package/src/assets/web-panel/assets/{KnowledgeGraph-BaYCA2Cd.js → KnowledgeGraph--cFDUZv3.js} +1 -1
  33. package/src/assets/web-panel/assets/{Logs-DTNYQWfp.js → Logs-Cnn2_Onf.js} +2 -2
  34. package/src/assets/web-panel/assets/{Marketplace-CUu1xYvo.js → Marketplace-4T9ok3Gz.js} +1 -1
  35. package/src/assets/web-panel/assets/{McpTools-BmoeTyrC.js → McpTools-BQvZwqcN.js} +5 -5
  36. package/src/assets/web-panel/assets/{Memory-DxTU3QU7.js → Memory-BE9rPkM_.js} +2 -2
  37. package/src/assets/web-panel/assets/{MobileBridge-CpcOlKAD.js → MobileBridge-DJ3j5lXC.js} +2 -2
  38. package/src/assets/web-panel/assets/{MobileProjects-Bjh_z16l.js → MobileProjects-qasLvYdb.js} +1 -1
  39. package/src/assets/web-panel/assets/{Mtc-LfxwOm0x.js → Mtc-D3CSPTD9.js} +2 -2
  40. package/src/assets/web-panel/assets/{MtcAudit-D6A9Gjkh.js → MtcAudit-DaYVGCN5.js} +2 -2
  41. package/src/assets/web-panel/assets/{Multisig-Ch_jofPV.js → Multisig-Dz1c5r5w.js} +3 -3
  42. package/src/assets/web-panel/assets/{NLProgramming-Bkvogg0I.js → NLProgramming-BIKV_K-a.js} +1 -1
  43. package/src/assets/web-panel/assets/{Notes-C5t5Xihm.js → Notes-f6t-rmOa.js} +3 -3
  44. package/src/assets/web-panel/assets/{NotificationSettings-CTpDUNCb.js → NotificationSettings-DHLQh8Fy.js} +1 -1
  45. package/src/assets/web-panel/assets/OrderTableRenderer-CDMZ3o6i.js +1 -0
  46. package/src/assets/web-panel/assets/{Organization-Dr37BaXa.js → Organization-DIsL758p.js} +4 -4
  47. package/src/assets/web-panel/assets/{Overflow-ZGjsdP7N.js → Overflow-BMM7apnZ.js} +1 -1
  48. package/src/assets/web-panel/assets/{P2P-bWJU5Vxd.js → P2P-CTGMmTvi.js} +2 -2
  49. package/src/assets/web-panel/assets/{PdhVaultBrowser-BRVoW-ye.js → PdhVaultBrowser-DWwmm0k1.js} +3 -3
  50. package/src/assets/web-panel/assets/{Permissions-BOSnFZaC.js → Permissions-Bed5JxMx.js} +4 -4
  51. package/src/assets/web-panel/assets/{PersonalDataHub-X4SgjP6P.js → PersonalDataHub-CIiZhSM5.js} +4 -4
  52. package/src/assets/web-panel/assets/{Pipeline-DoJhB9bj.js → Pipeline-CtirPodz.js} +1 -1
  53. package/src/assets/web-panel/assets/{Privacy-OM9lDj-R.js → Privacy-DuXhXhE7.js} +1 -1
  54. package/src/assets/web-panel/assets/{ProjectInit-BXQEOmLn.js → ProjectInit-DZrnguBl.js} +2 -2
  55. package/src/assets/web-panel/assets/{ProjectSettings-DBXo3K5u.js → ProjectSettings-HIltqsJ1.js} +2 -2
  56. package/src/assets/web-panel/assets/{Projects-CJ4DBJlS.js → Projects-BWFkePg4.js} +1 -1
  57. package/src/assets/web-panel/assets/{Providers-Tk9SawmO.js → Providers-DEP0Jdvl.js} +1 -1
  58. package/src/assets/web-panel/assets/{QuickAsk-DRI1-nTC.js → QuickAsk-T2THoHNx.js} +1 -1
  59. package/src/assets/web-panel/assets/{Recommend-DtrIVTu9.js → Recommend-CvbxaSwm.js} +1 -1
  60. package/src/assets/web-panel/assets/{Reputation-DkH8ImwZ.js → Reputation-6Afy6tfp.js} +1 -1
  61. package/src/assets/web-panel/assets/{Row-DpA9dlvi.js → Row-DY8OPWaO.js} +1 -1
  62. package/src/assets/web-panel/assets/{RssFeed-DV3OhxWd.js → RssFeed-wDGWb9pZ.js} +3 -3
  63. package/src/assets/web-panel/assets/{Search-QxdntiQx.js → Search-D_zHAwZY.js} +1 -1
  64. package/src/assets/web-panel/assets/{Security-CGuEnrD2.js → Security-Czq7AlGG.js} +4 -4
  65. package/src/assets/web-panel/assets/{Services-BvwSSD8b.js → Services-Ac1g0ZcG.js} +2 -2
  66. package/src/assets/web-panel/assets/{Skeleton-sx_8L3-5.js → Skeleton-DXQ3eeSW.js} +1 -1
  67. package/src/assets/web-panel/assets/{Skills-dWOwxRsu.js → Skills-CWRioX4u.js} +1 -1
  68. package/src/assets/web-panel/assets/{Sla-zxXnfKrT.js → Sla-BlHthzfs.js} +1 -1
  69. package/src/assets/web-panel/assets/{SpeechSettings-CmFlcNjr.js → SpeechSettings-Ct240JmL.js} +1 -1
  70. package/src/assets/web-panel/assets/{SyncSettings-BeXeqURL.js → SyncSettings-BgDIt8Q-.js} +2 -2
  71. package/src/assets/web-panel/assets/Tasks-3PTmatJP.js +1 -0
  72. package/src/assets/web-panel/assets/{Templates-DlgR3XFH.js → Templates-Dp9QhyIw.js} +1 -1
  73. package/src/assets/web-panel/assets/{Tenant-0P8HgQaM.js → Tenant-CHTYMxzY.js} +1 -1
  74. package/src/assets/web-panel/assets/Terminal-X-NGwLpv.js +3 -0
  75. package/src/assets/web-panel/assets/{TimelineRenderer-hbO7agZs.js → TimelineRenderer-Bh8jA18j.js} +1 -1
  76. package/src/assets/web-panel/assets/{Tokens-CsmhgTBO.js → Tokens-DWkTd5dv.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trigger-DnaF_2PP.js → Trigger-CRgVg6sd.js} +1 -1
  78. package/src/assets/web-panel/assets/{Trust-C1oafGj1.js → Trust-BgWOXd0W.js} +1 -1
  79. package/src/assets/web-panel/assets/{UkeySign-eLL4DOmC.js → UkeySign-BlTUB9Y-.js} +1 -1
  80. package/src/assets/web-panel/assets/{VideoEditing-CX45sVq7.js → VideoEditing-DI64XgNb.js} +1 -1
  81. package/src/assets/web-panel/assets/{Wallet-aWPqpHdQ.js → Wallet-CJ3TNGiG.js} +4 -4
  82. package/src/assets/web-panel/assets/{WebAuthn-DMYV1MAo.js → WebAuthn-B2-rWWoV.js} +4 -4
  83. package/src/assets/web-panel/assets/{WorkflowEditor-D9uRIJvH.js → WorkflowEditor-VI9otbaH.js} +1 -1
  84. package/src/assets/web-panel/assets/{chat-BmWYfCxG.js → chat-CgYfiaVh.js} +1 -1
  85. package/src/assets/web-panel/assets/{colors-DqvTCkBe.js → colors-B9EhRTky.js} +1 -1
  86. package/src/assets/web-panel/assets/{compact-item-Bh0L0ejI.js → compact-item-Cb7bjraa.js} +1 -1
  87. package/src/assets/web-panel/assets/{createContext-r2qgp1mn.js → createContext-DlXPeXuj.js} +1 -1
  88. package/src/assets/web-panel/assets/devWarning-D-Hp8s_8.js +1 -0
  89. package/src/assets/web-panel/assets/{hasIn-BcffXa-S.js → hasIn-BbgRfrdf.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-DxajFkK2.js → index-1iUK_kAw.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-BCBqTRWH.js → index-2ts5iOIB.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-CDR3GmaO.js → index-5CrFMQjt.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-B7z0qK1W.js → index-AR-QpAkP.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-BLN-neIf.js → index-BD2W-qsS.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-De36_UgR.js → index-BU8hEUyq.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-Bma_yHcC.js → index-BfSS-U5o.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-Dcjol7ot.js → index-Bk7r1a9x.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BbMox24t.js → index-BoEFFKn3.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-C2-02rrp.js → index-Bt-lPYpq.js} +1 -1
  100. package/src/assets/web-panel/assets/index-BvnHuxVM.js +1 -0
  101. package/src/assets/web-panel/assets/{index-DPFT7J7I.js → index-C73WgOc2.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BXXxkeij.js → index-C8DB27uJ.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-XwbSqOB2.js → index-C95qWAh4.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CAlxkpnv.js → index-CGx8aO_Y.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-B3mmDuOv.js → index-CHR47Q5B.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CJt0iuep.js → index-CMyzmvtJ.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-S4E77Aer.js → index-Caiu2avX.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BtyXyl3t.js → index-Cf9zwbk-.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CCyB-RK5.js → index-Cmr31VCO.js} +3 -3
  110. package/src/assets/web-panel/assets/{index-CKnEtlZD.js → index-D-Zz9PvD.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DWlDSE0F.js → index-D6t-Shqr.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-h4O0AcBt.js → index-D8kB0k3E.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-Cbj6C3pA.js → index-DDzNdZcX.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DutDlDUF.js → index-DPaffcT8.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BXae4ZyX.js → index-DUU9DY4J.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-U86pxDyR.js → index-DXxa7PR8.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DRXcGa5y.js → index-DbLJShJB.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BMn_luHQ.js → index-De59Xat-.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-Ct8qhPZe.js → index-Dh6qWb1v.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CY8RXaZR.js → index-DkQIyK-V.js} +1 -1
  121. package/src/assets/web-panel/assets/index-Dkm5IGwX.js +1 -0
  122. package/src/assets/web-panel/assets/{index-585fuGAN.js → index-Dx4sm6dm.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-COrfHebA.js → index-Ira0HLPr.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-BeV-KoQl.js → index-OVrh8wTN.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DW1y18GR.js → index-c7-Jd6WB.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-C9nh3ANl.js → index-fG-1gXy0.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BQlAPNSU.js → index-ojRAd7Nq.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-B6pAm1iJ.js → index-p03wNqiP.js} +1 -1
  129. package/src/assets/web-panel/assets/{initDefaultProps-C1d8I-BX.js → initDefaultProps-JT674ACa.js} +1 -1
  130. package/src/assets/web-panel/assets/{motion-Dq7fiy4Y.js → motion-CokflrA9.js} +1 -1
  131. package/src/assets/web-panel/assets/{move-Bqb2dySM.js → move-CfMhRpyC.js} +1 -1
  132. package/src/assets/web-panel/assets/{omit-BUYqb4My.js → omit-ClYc5II5.js} +1 -1
  133. package/src/assets/web-panel/assets/{pickAttrs-DeytiKlZ.js → pickAttrs-CTwEb_8h.js} +1 -1
  134. package/src/assets/web-panel/assets/{placementArrow-xrXZWCqG.js → placementArrow-Cb3StU_t.js} +1 -1
  135. package/src/assets/web-panel/assets/{responsiveObserve-CcL2K-YY.js → responsiveObserve-uIxkx5M1.js} +1 -1
  136. package/src/assets/web-panel/assets/{slide-DmCWaic7.js → slide-B9HZBQ7i.js} +1 -1
  137. package/src/assets/web-panel/assets/{statusUtils-CqNrFif7.js → statusUtils-C72bwYl0.js} +1 -1
  138. package/src/assets/web-panel/assets/{styleChecker-C436m5Xy.js → styleChecker-BFTtaQb8.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFlexGapSupport-CVhutCN8.js → useFlexGapSupport-DKl5j41_.js} +1 -1
  140. package/src/assets/web-panel/assets/{useFs-DUd49Bui.js → useFs-BUHS6bo3.js} +1 -1
  141. package/src/assets/web-panel/assets/{usePersonalDataHub-fuS9raic.js → usePersonalDataHub-D8KrYSq4.js} +1 -1
  142. package/src/assets/web-panel/assets/{vnode-C3kmDmk-.js → vnode-JYP-aZDj.js} +1 -1
  143. package/src/assets/web-panel/assets/{zoom-hX-F1dT-.js → zoom-BFusdxdH.js} +1 -1
  144. package/src/assets/web-panel/index.html +1 -1
  145. package/src/commands/agent.js +67 -29
  146. package/src/commands/terminal-setup.js +127 -0
  147. package/src/harness/mcp-client.js +48 -2
  148. package/src/index.js +2 -0
  149. package/src/lib/image-input.js +8 -2
  150. package/src/lib/llm-pricing.js +15 -0
  151. package/src/lib/permission-rules.cjs +11 -1
  152. package/src/lib/personal-data-hub-wiring.js +24 -0
  153. package/src/lib/repl-multiline.js +64 -0
  154. package/src/lib/repl-rewind.js +65 -2
  155. package/src/lib/repl-vim.js +445 -0
  156. package/src/lib/safe-mode.js +17 -3
  157. package/src/lib/skill-loader.js +45 -1
  158. package/src/lib/slash-commands.js +13 -3
  159. package/src/lib/status-line.cjs +33 -3
  160. package/src/lib/terminal-setup.js +209 -0
  161. package/src/repl/agent-repl.js +274 -27
  162. package/src/repl/session-cost.js +98 -1
  163. package/src/runtime/agent-core.js +23 -8
  164. package/src/runtime/fallback-model.js +125 -30
  165. package/src/runtime/headless-runner.js +2 -0
  166. package/src/runtime/headless-stream.js +2 -0
  167. package/src/runtime/mcp-config.js +14 -3
  168. package/src/assets/web-panel/assets/ChatBubbleRenderer-CfpKEQUF.js +0 -1
  169. package/src/assets/web-panel/assets/OrderTableRenderer-ST2lr-Bi.js +0 -1
  170. package/src/assets/web-panel/assets/Tasks-iImd8xSO.js +0 -1
  171. package/src/assets/web-panel/assets/Terminal-B5VDEEHD.js +0 -3
  172. package/src/assets/web-panel/assets/devWarning-CusWDjWW.js +0 -1
  173. package/src/assets/web-panel/assets/index-BhYltBvN.js +0 -1
  174. package/src/assets/web-panel/assets/index-CZiIHw4e.js +0 -1
@@ -0,0 +1,209 @@
1
+ /**
2
+ * terminal-setup — make Shift+Enter insert a newline in the agent REPL
3
+ * (Claude-Code `/terminal-setup` parity).
4
+ *
5
+ * cc's REPL is readline-based, so a bare Enter always submits. Multiline input
6
+ * is reached by ending a line with a continuation backslash (see
7
+ * repl-multiline.js). This module configures the terminal so that pressing
8
+ * Shift+Enter emits the byte sequence `<space>\<CR>` — which cc's
9
+ * backslash-continuation turns into a soft newline — giving the familiar
10
+ * "Shift+Enter = newline, Enter = send" editing without any REPL raw-mode hacks.
11
+ *
12
+ * Everything here is pure (detection + keybindings JSON shaping); the command
13
+ * (commands/terminal-setup.js) does the file I/O.
14
+ */
15
+
16
+ import path from "node:path";
17
+ import os from "node:os";
18
+
19
+ /**
20
+ * The bytes Shift+Enter should send: space + backslash + carriage return. The
21
+ * leading space makes the trailing backslash satisfy repl-multiline's
22
+ * whitespace-gated continuation rule (so it is never mistaken for a Windows
23
+ * path), and the backslash is stripped back out when the line is joined.
24
+ */
25
+ export const SHIFT_ENTER_SEQUENCE = " \\\r";
26
+
27
+ /** Identify the host terminal from environment variables. */
28
+ export function detectTerminal(env = process.env) {
29
+ const tp = String(env.TERM_PROGRAM || "").toLowerCase();
30
+ if (tp === "vscode" || env.VSCODE_PID || env.VSCODE_INJECTION) {
31
+ return { id: "vscode", name: "VS Code integrated terminal" };
32
+ }
33
+ if (tp === "iterm.app") return { id: "iterm2", name: "iTerm2" };
34
+ if (tp === "apple_terminal")
35
+ return { id: "apple-terminal", name: "Apple Terminal" };
36
+ if (tp === "wezterm") return { id: "wezterm", name: "WezTerm" };
37
+ if (env.WT_SESSION)
38
+ return { id: "windows-terminal", name: "Windows Terminal" };
39
+ return {
40
+ id: "unknown",
41
+ name: env.TERM_PROGRAM || env.TERM || "your terminal",
42
+ };
43
+ }
44
+
45
+ /** The VS Code keybinding that sends the Shift+Enter sequence in the terminal. */
46
+ export function vscodeKeybinding() {
47
+ return {
48
+ key: "shift+enter",
49
+ command: "workbench.action.terminal.sendSequence",
50
+ when: "terminalFocus",
51
+ args: { text: SHIFT_ENTER_SEQUENCE },
52
+ };
53
+ }
54
+
55
+ /** Per-OS path to VS Code's user keybindings.json. */
56
+ export function vscodeKeybindingsPath(
57
+ platform = process.platform,
58
+ env = process.env,
59
+ ) {
60
+ const home = os.homedir();
61
+ if (platform === "win32") {
62
+ const appData = env.APPDATA || path.join(home, "AppData", "Roaming");
63
+ return path.join(appData, "Code", "User", "keybindings.json");
64
+ }
65
+ if (platform === "darwin") {
66
+ return path.join(
67
+ home,
68
+ "Library",
69
+ "Application Support",
70
+ "Code",
71
+ "User",
72
+ "keybindings.json",
73
+ );
74
+ }
75
+ return path.join(home, ".config", "Code", "User", "keybindings.json");
76
+ }
77
+
78
+ /**
79
+ * Strip JSONC (line + block comments, trailing commas) so a keybindings.json
80
+ * with comments can be parsed. Best-effort and string-aware enough for the
81
+ * common case; callers must tolerate a null parse.
82
+ */
83
+ export function stripJsonc(text) {
84
+ let out = "";
85
+ const s = String(text || "");
86
+ let inStr = false;
87
+ let strCh = "";
88
+ let i = 0;
89
+ while (i < s.length) {
90
+ const c = s[i];
91
+ const n = s[i + 1];
92
+ if (inStr) {
93
+ out += c;
94
+ if (c === "\\") {
95
+ out += n ?? "";
96
+ i += 2;
97
+ continue;
98
+ }
99
+ if (c === strCh) inStr = false;
100
+ i += 1;
101
+ continue;
102
+ }
103
+ if (c === '"' || c === "'") {
104
+ inStr = true;
105
+ strCh = c;
106
+ out += c;
107
+ i += 1;
108
+ continue;
109
+ }
110
+ if (c === "/" && n === "/") {
111
+ while (i < s.length && s[i] !== "\n") i += 1;
112
+ continue;
113
+ }
114
+ if (c === "/" && n === "*") {
115
+ i += 2;
116
+ while (i < s.length && !(s[i] === "*" && s[i + 1] === "/")) i += 1;
117
+ i += 2;
118
+ continue;
119
+ }
120
+ out += c;
121
+ i += 1;
122
+ }
123
+ // Drop trailing commas before } or ].
124
+ return out.replace(/,(\s*[}\]])/g, "$1");
125
+ }
126
+
127
+ /** Parse a JSONC keybindings array → array, or null when it cannot be parsed. */
128
+ export function parseKeybindings(text) {
129
+ const src = String(text || "").trim();
130
+ if (!src) return [];
131
+ try {
132
+ const v = JSON.parse(stripJsonc(src));
133
+ return Array.isArray(v) ? v : null;
134
+ } catch {
135
+ return null;
136
+ }
137
+ }
138
+
139
+ /** True when an equivalent keybinding (key+command+when) already exists. */
140
+ export function hasKeybinding(arr, binding) {
141
+ return (Array.isArray(arr) ? arr : []).some(
142
+ (b) =>
143
+ b &&
144
+ b.key === binding.key &&
145
+ b.command === binding.command &&
146
+ (b.when || "") === (binding.when || ""),
147
+ );
148
+ }
149
+
150
+ /**
151
+ * Textually append a keybinding before the closing `]`, preserving the file's
152
+ * existing comments/formatting. Returns the new file text, or null when `text`
153
+ * is non-empty but not a JSON array (caller should not overwrite blindly).
154
+ */
155
+ export function appendKeybindingText(text, binding) {
156
+ const bindingJson = JSON.stringify(binding, null, 2)
157
+ .split("\n")
158
+ .map((l) => " " + l)
159
+ .join("\n")
160
+ .trimStart();
161
+ const src = String(text || "").trim();
162
+ if (!src || src === "[]") {
163
+ return `[\n ${bindingJson}\n]\n`;
164
+ }
165
+ const close = src.lastIndexOf("]");
166
+ if (close === -1) return null; // not an array
167
+ const before = src.slice(0, close).replace(/\s*$/, "");
168
+ const sep = before.endsWith("[") ? "" : ",";
169
+ return `${before}${sep}\n ${bindingJson}\n]\n`;
170
+ }
171
+
172
+ /** Manual instructions for terminals cc cannot auto-configure. */
173
+ export function instructionsFor(id) {
174
+ const seq = "a space, then a backslash, then Enter";
175
+ const common = `Bind Shift+Enter to send ${seq} (cc turns a trailing "\\" into a newline).`;
176
+ switch (id) {
177
+ case "iterm2":
178
+ return [
179
+ common,
180
+ "iTerm2 → Settings → Profiles → Keys → Key Mappings → + :",
181
+ ' Shortcut: Shift+Return Action: "Send Text" Text: \\ (then a literal Return)',
182
+ ];
183
+ case "apple-terminal":
184
+ return [
185
+ common,
186
+ "Terminal → Settings → Profiles → Keyboard → + :",
187
+ ' Key: Shift+Return Action: send string " \\015" (space, backslash, CR)',
188
+ ];
189
+ case "windows-terminal":
190
+ return [
191
+ common,
192
+ "Windows Terminal → Settings → Actions (settings.json) → add:",
193
+ ' { "command": { "action": "sendInput", "input": " \\\\\\r" }, "keys": "shift+enter" }',
194
+ ];
195
+ case "wezterm":
196
+ return [
197
+ common,
198
+ "WezTerm (~/.wezterm.lua) keys:",
199
+ ' { key="Enter", mods="SHIFT", action=wezterm.action.SendString(" \\\\\\r") }',
200
+ ];
201
+ default:
202
+ return [
203
+ common,
204
+ "Most terminals let you remap a key to send custom text/bytes — point",
205
+ "Shift+Enter at the 3 bytes: space (0x20), backslash (0x5C), CR (0x0D).",
206
+ "Or just type a trailing \\ yourself to continue onto the next line.",
207
+ ];
208
+ }
209
+ }
@@ -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,8 @@ export async function startAgentRepl(options = {}) {
786
833
  "/statusline",
787
834
  "/sub-agents",
788
835
  "/task",
836
+ "/terminal-setup",
837
+ "/vim",
789
838
  ],
790
839
  getIdeOpenFiles: async () => {
791
840
  const exec = _adhocMcp?.externalToolExecutors?.mcp__ide__getOpenEditors;
@@ -813,6 +862,54 @@ export async function startAgentRepl(options = {}) {
813
862
  completer: atCompleter,
814
863
  });
815
864
 
865
+ // Vim-mode plumbing: capture readline's OWN keypress listeners now so we can
866
+ // suspend them while in NORMAL mode (the engine drives editing then) and
867
+ // reattach them for INSERT mode (readline's rich editing/history/completion).
868
+ const _rlKeypressListeners = process.stdin.isTTY
869
+ ? process.stdin.listeners("keypress").slice()
870
+ : [];
871
+ const _suspendReadlineKeys = () => {
872
+ for (const l of _rlKeypressListeners)
873
+ process.stdin.removeListener("keypress", l);
874
+ };
875
+ const _resumeReadlineKeys = () => {
876
+ const cur = process.stdin.listeners("keypress");
877
+ for (const l of _rlKeypressListeners)
878
+ if (!cur.includes(l)) process.stdin.on("keypress", l);
879
+ };
880
+ // Push the engine's line/cursor onto readline and redraw the current line.
881
+ const _vimSync = (vstate) => {
882
+ try {
883
+ rl.line = vstate.line;
884
+ rl.cursor = Math.max(0, Math.min(vstate.cursor, vstate.line.length));
885
+ if (typeof rl._refreshLine === "function") rl._refreshLine();
886
+ } catch {
887
+ /* redraw is best-effort */
888
+ }
889
+ };
890
+ const _vimEnterNormal = () => {
891
+ const cur = Math.max(
892
+ 0,
893
+ Math.min(rl.cursor, Math.max(0, rl.line.length - 1)),
894
+ );
895
+ _vim = { ...createVimState(rl.line, cur), mode: "normal" };
896
+ _suspendReadlineKeys();
897
+ rl.setPrompt(getPrompt());
898
+ _vimSync(_vim);
899
+ };
900
+ const _vimEnterInsert = (vstate) => {
901
+ _resumeReadlineKeys();
902
+ _vim = null;
903
+ rl.setPrompt(getPrompt());
904
+ _vimSync(vstate);
905
+ };
906
+ // Exposed so /vim can leave normal mode cleanly when disabling.
907
+ const _vimDisable = () => {
908
+ if (_vim) _resumeReadlineKeys();
909
+ _vim = null;
910
+ _vimEnabled = false;
911
+ };
912
+
816
913
  // Esc interrupt (Claude-Code parity): pressing Esc while a turn is in
817
914
  // flight aborts the in-flight agentLoop through its existing AbortSignal
818
915
  // seam (throwIfAborted at each iteration); partial conversation is kept.
@@ -822,8 +919,9 @@ export async function startAgentRepl(options = {}) {
822
919
  let _lastIdleEscAt = 0;
823
920
  if (process.stdin.isTTY) {
824
921
  process.stdin.on("keypress", (_str, key) => {
825
- if (!key || key.name !== "escape" || key.meta) return;
826
- if (_turnAbort) {
922
+ const k = key || {};
923
+ // 1) Turn abort always wins, regardless of vim mode.
924
+ if (k.name === "escape" && !k.meta && _turnAbort) {
827
925
  process.stdout.write(chalk.yellow("\n⎋ interrupting…\n"));
828
926
  try {
829
927
  _turnAbort.abort();
@@ -833,8 +931,34 @@ export async function startAgentRepl(options = {}) {
833
931
  _turnAbort = null;
834
932
  return;
835
933
  }
836
- // Double-Esc while idle → rewind picker shortcut (Claude-Code parity);
837
- // the actual rewind is `/rewind <n>` so stdin stays readline-owned.
934
+
935
+ // 2) Vim mode: modal editing on the current input line.
936
+ if (_vimEnabled && !_turnAbort) {
937
+ if (!_vim) {
938
+ // INSERT mode — readline owns the keys; Esc switches to NORMAL.
939
+ if (k.name === "escape" && !k.meta) _vimEnterNormal();
940
+ return;
941
+ }
942
+ // NORMAL mode — readline suspended; the engine interprets every key.
943
+ const res = feedNormalKey(_vim, _str || "", k);
944
+ if (res.submit) {
945
+ // Hand the line to readline as a normal Enter (fires 'line', clears).
946
+ _vimEnterInsert({ ...res, cursor: res.line.length });
947
+ process.stdin.emit("keypress", "\r", { name: "return" });
948
+ return;
949
+ }
950
+ if (res.mode === "insert") {
951
+ _vimEnterInsert(res);
952
+ return;
953
+ }
954
+ _vim = res;
955
+ if (res.message === "bell") process.stdout.write("\x07");
956
+ _vimSync(res);
957
+ return;
958
+ }
959
+
960
+ // 3) Non-vim: double-Esc while idle → rewind picker shortcut.
961
+ if (k.name !== "escape" || k.meta) return;
838
962
  const nowTs = Date.now();
839
963
  if (nowTs - _lastIdleEscAt < 600) {
840
964
  _lastIdleEscAt = 0;
@@ -848,7 +972,9 @@ export async function startAgentRepl(options = {}) {
848
972
  );
849
973
  process.stdout.write(
850
974
  chalk.gray(
851
- "Run /rewind <n> to rewind the conversation (files: cc checkpoint restore).\n",
975
+ _checkpointMarks.length
976
+ ? "Run /rewind <n> to rewind the conversation (and optionally its files).\n"
977
+ : "Run /rewind <n> to rewind the conversation.\n",
852
978
  ),
853
979
  );
854
980
  prompt();
@@ -950,7 +1076,24 @@ export async function startAgentRepl(options = {}) {
950
1076
  // when the current turn finishes.
951
1077
  let _processingLine = false;
952
1078
  const _pendingLines = [];
1079
+ // Multiline input (Claude-Code parity): a physical line ending in a
1080
+ // continuation backslash keeps the prompt open; `_mlBuffer` accumulates the
1081
+ // pieces and the whole block submits when a line does not continue.
1082
+ const _mlBuffer = [];
953
1083
  const handleLine = async (input) => {
1084
+ // Backslash continuation — accumulate and re-prompt without firing a turn.
1085
+ const _cont = analyzeContinuation(input);
1086
+ if (_cont.continued) {
1087
+ _mlBuffer.push(_cont.text);
1088
+ rl.setPrompt(chalk.dim("... "));
1089
+ rl.prompt();
1090
+ return;
1091
+ }
1092
+ if (_mlBuffer.length) {
1093
+ input = joinContinuation(_mlBuffer, input);
1094
+ _mlBuffer.length = 0;
1095
+ }
1096
+
954
1097
  const trimmed = input.trim();
955
1098
  if (!trimmed) {
956
1099
  prompt();
@@ -1022,12 +1165,21 @@ export async function startAgentRepl(options = {}) {
1022
1165
  logger.log(
1023
1166
  ` ${chalk.cyan("# <note>")} Remember a note in the project cc.md`,
1024
1167
  );
1168
+ logger.log(
1169
+ ` ${chalk.cyan("… \\")} End a line with \\ to continue input onto the next line`,
1170
+ );
1025
1171
  logger.log(` ${chalk.cyan("/exit")} Exit the agent`);
1026
1172
  logger.log(
1027
1173
  ` ${chalk.cyan("/model")} Show/change model (/model <name>)`,
1028
1174
  );
1029
1175
  logger.log(` ${chalk.cyan("/provider")} Show/change provider`);
1030
1176
  logger.log(` ${chalk.cyan("/clear")} Clear conversation`);
1177
+ logger.log(
1178
+ ` ${chalk.cyan("/vim")} Toggle vim-mode line editing (/vim [on|off]; Esc → NORMAL)`,
1179
+ );
1180
+ logger.log(
1181
+ ` ${chalk.cyan("/terminal-setup")} Bind Shift+Enter → newline (--apply for VS Code)`,
1182
+ );
1031
1183
  logger.log(
1032
1184
  ` ${chalk.cyan("/statusline")} Context-usage line on/off (/statusline [on|off])`,
1033
1185
  );
@@ -1044,7 +1196,7 @@ export async function startAgentRepl(options = {}) {
1044
1196
  ` ${chalk.cyan("/context")} Live context-window usage by role`,
1045
1197
  );
1046
1198
  logger.log(
1047
- ` ${chalk.cyan("/cost")} Session token spend + estimated $ (so far)`,
1199
+ ` ${chalk.cyan("/cost")} Session token spend + estimated $ (per model & category)`,
1048
1200
  );
1049
1201
  logger.log(
1050
1202
  ` ${chalk.cyan("/permissions")} Allow/ask/deny rules in effect this session`,
@@ -1053,7 +1205,7 @@ export async function startAgentRepl(options = {}) {
1053
1205
  ` ${chalk.cyan("/export")} Save this conversation to a Markdown file (/export [path])`,
1054
1206
  );
1055
1207
  logger.log(
1056
- ` ${chalk.cyan("/rewind")} Rewind conversation to an earlier turn (double-Esc lists)`,
1208
+ ` ${chalk.cyan("/rewind")} Rewind conversation + files to an earlier turn (double-Esc lists)`,
1057
1209
  );
1058
1210
  logger.log(
1059
1211
  ` ${chalk.cyan("/cd <dir>")} Change working directory mid-session (completion/memory follow)`,
@@ -1230,11 +1382,47 @@ export async function startAgentRepl(options = {}) {
1230
1382
 
1231
1383
  if (trimmed === "/clear") {
1232
1384
  messages.length = 1; // Keep system prompt
1385
+ _checkpointMarks.length = 0; // checkpoint marks no longer map to anything
1233
1386
  logger.info("Conversation cleared");
1234
1387
  prompt();
1235
1388
  return;
1236
1389
  }
1237
1390
 
1391
+ if (
1392
+ trimmed === "/terminal-setup" ||
1393
+ trimmed.startsWith("/terminal-setup ")
1394
+ ) {
1395
+ try {
1396
+ const arg = trimmed.slice("/terminal-setup".length).trim();
1397
+ const { runTerminalSetup } =
1398
+ await import("../commands/terminal-setup.js");
1399
+ const res = runTerminalSetup({ apply: arg === "--apply" });
1400
+ for (const l of res.lines) logger.log(l);
1401
+ } catch (err) {
1402
+ logger.error(`/terminal-setup failed: ${err.message}`);
1403
+ }
1404
+ prompt();
1405
+ return;
1406
+ }
1407
+
1408
+ if (trimmed === "/vim" || trimmed.startsWith("/vim ")) {
1409
+ const arg = trimmed.slice("/vim".length).trim().toLowerCase();
1410
+ const turnOn = arg === "on" || (arg === "" && !_vimEnabled);
1411
+ if (turnOn) {
1412
+ _vimEnabled = true;
1413
+ logger.info(
1414
+ chalk.gray(
1415
+ "Vim mode: on — Esc → NORMAL (hjkl/w/b/e, x/dd/dw, i/a/A, etc.), i to insert.",
1416
+ ),
1417
+ );
1418
+ } else {
1419
+ _vimDisable();
1420
+ logger.info(chalk.gray("Vim mode: off"));
1421
+ }
1422
+ prompt();
1423
+ return;
1424
+ }
1425
+
1238
1426
  if (trimmed === "/statusline" || trimmed.startsWith("/statusline ")) {
1239
1427
  const arg = trimmed.slice("/statusline".length).trim().toLowerCase();
1240
1428
  if (arg === "off") {
@@ -1327,17 +1515,21 @@ export async function startAgentRepl(options = {}) {
1327
1515
 
1328
1516
  if (trimmed === "/rewind" || trimmed.startsWith("/rewind ")) {
1329
1517
  try {
1330
- const { listUserTurns, rewindToTurn, renderTurnList } =
1331
- await import("../lib/repl-rewind.js");
1518
+ const {
1519
+ listUserTurns,
1520
+ rewindToTurn,
1521
+ renderTurnList,
1522
+ pickCheckpointForTurn,
1523
+ pruneMarksAfter,
1524
+ } = await import("../lib/repl-rewind.js");
1332
1525
  const arg = trimmed.slice("/rewind".length).trim();
1333
1526
  if (!arg) {
1334
1527
  logger.log(chalk.bold("\nRewind — pick a user turn (newest first):"));
1335
1528
  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
- );
1529
+ const fileHint = _checkpointMarks.length
1530
+ ? " (restores files to that point too — checkpoints are on)"
1531
+ : " (conversation only — start with --checkpoint / git to also rewind files)";
1532
+ logger.log(chalk.gray(`Usage: /rewind <n>${fileHint}`));
1341
1533
  } else {
1342
1534
  const res = rewindToTurn(messages, arg);
1343
1535
  if (!res) {
@@ -1348,6 +1540,48 @@ export async function startAgentRepl(options = {}) {
1348
1540
  `⎌ rewound — dropped ${res.removed} message(s); edit and resend below`,
1349
1541
  ),
1350
1542
  );
1543
+ // Claude-Code parity: rewind restores files too. Match the dropped
1544
+ // turn to the snapshot taken just before it first mutated the tree,
1545
+ // then offer to roll the working tree back to it (undoable — the
1546
+ // restore takes its own safety checkpoint first).
1547
+ const cp = pickCheckpointForTurn(_checkpointMarks, res.index);
1548
+ pruneMarksAfter(_checkpointMarks, res.index);
1549
+ if (cp) {
1550
+ const q = (p) => new Promise((r) => rl.question(p, r));
1551
+ const ans = (
1552
+ await q(
1553
+ chalk.yellow(
1554
+ ` Also restore files to before this turn? (checkpoint ${cp.id}) [Y/n] `,
1555
+ ),
1556
+ )
1557
+ )
1558
+ .trim()
1559
+ .toLowerCase();
1560
+ if (ans === "" || ans === "y" || ans === "yes") {
1561
+ try {
1562
+ const { rewindTo } =
1563
+ await import("../lib/checkpoint-store.js");
1564
+ const r = rewindTo(process.cwd(), cp.id, {
1565
+ session: sessionId,
1566
+ });
1567
+ logger.log(
1568
+ chalk.green(
1569
+ ` ⎌ files restored to ${cp.id} (${r.modified} changed, ${r.deleted} removed, ${r.recreated} recreated; undo: cc checkpoint restore ${r.safetyId})`,
1570
+ ),
1571
+ );
1572
+ } catch (e) {
1573
+ logger.error(
1574
+ ` file restore skipped: ${e.message} (conversation already rewound)`,
1575
+ );
1576
+ }
1577
+ } else {
1578
+ logger.log(
1579
+ chalk.gray(
1580
+ ` files left as-is — restore later with cc checkpoint restore ${cp.id}`,
1581
+ ),
1582
+ );
1583
+ }
1584
+ }
1351
1585
  prompt();
1352
1586
  if (res.text) rl.write(res.text);
1353
1587
  return;
@@ -2279,15 +2513,27 @@ export async function startAgentRepl(options = {}) {
2279
2513
  // parity). In-memory accumulation, so it works without session persistence.
2280
2514
  if (trimmed === "/cost" || trimmed === "/cost ") {
2281
2515
  let overrides;
2516
+ let visionModel;
2282
2517
  try {
2283
2518
  const { loadConfig } = await import("../lib/config-manager.js");
2284
- overrides = loadConfig()?.llm?.pricing;
2519
+ const cfg = loadConfig();
2520
+ overrides = cfg?.llm?.pricing;
2521
+ visionModel = cfg?.llm?.visionModel;
2285
2522
  } catch (_err) {
2286
- // pricing overrides are optional — fall back to the built-in table
2287
- }
2523
+ // config is optional — fall back to the built-in pricing table
2524
+ }
2525
+ // Category breakdown (Claude-Code parity): classify spend by model role —
2526
+ // the live model is "main", the vision model "vision", the fallback chain
2527
+ // "fallback", a switched-to model "other". Shown only when >1 was used.
2528
+ const roles = {
2529
+ mainProvider: provider,
2530
+ mainModel: _curModel || model,
2531
+ visionModel: visionModel || "doubao-seed-2-0-lite-260215",
2532
+ fallbackModels: _fallbackModels || [],
2533
+ };
2288
2534
  const { renderSessionCost } = await import("./session-cost.js");
2289
2535
  logger.log(
2290
- renderSessionCost(_costStore, { pricingOverrides: overrides }),
2536
+ renderSessionCost(_costStore, { pricingOverrides: overrides, roles }),
2291
2537
  );
2292
2538
  prompt();
2293
2539
  return;
@@ -2522,6 +2768,7 @@ export async function startAgentRepl(options = {}) {
2522
2768
  additionalDirectories,
2523
2769
  autoCheckpoint,
2524
2770
  checkpointSession: sessionId,
2771
+ checkpointMarks: _checkpointMarks,
2525
2772
  prepareCall,
2526
2773
  approvalGate: _approvalGate,
2527
2774
  permissionRules: _permissionRules,