chainlesschain 0.162.48 → 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 (177) hide show
  1. package/bin/chainlesschain.js +1 -1
  2. package/package.json +2 -2
  3. package/src/assets/web-panel/assets/{AIOps-BeMOUkMq.js → AIOps-a2cSbSEu.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-DrRegmIt.js → ActionButton-DwvSB5Pp.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-BM7hvUn8.js → Analytics-BqaRaBDD.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-BfLLYSz_.js → AppLayout-Beck7v8t.js} +5 -5
  7. package/src/assets/web-panel/assets/{Audit-BpiPR-rs.js → Audit-UtJhPdXJ.js} +1 -1
  8. package/src/assets/web-panel/assets/{Backup-BLq4IRI9.js → Backup-HVZhcdll.js} +1 -1
  9. package/src/assets/web-panel/assets/{BaseInput-Bv89IFrM.js → BaseInput-DRY_ZGmj.js} +1 -1
  10. package/src/assets/web-panel/assets/{Chat-BUM9MdnH.js → Chat-D7Vuwfe2.js} +4 -4
  11. package/src/assets/web-panel/assets/{ChatBubbleRenderer-rH_t53ZW.js → ChatBubbleRenderer-BS2q_hPX.js} +1 -1
  12. package/src/assets/web-panel/assets/{Checkbox-82jih_zU.js → Checkbox-B406i7N1.js} +1 -1
  13. package/src/assets/web-panel/assets/{Codegen-ygmM1mNL.js → Codegen-BvTCqHi3.js} +1 -1
  14. package/src/assets/web-panel/assets/{Col-CqXc8_ft.js → Col-DRiyxTQP.js} +1 -1
  15. package/src/assets/web-panel/assets/{Community-CqPPfyR3.js → Community-DWmhxHQa.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compact-LR8Z206-.js → Compact-DO1HBZEz.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compliance-D4alm_Gx.js → Compliance-D4j-VHwS.js} +1 -1
  18. package/src/assets/web-panel/assets/{Cowork-D-DLVanT.js → Cowork-BGBkWtat.js} +3 -3
  19. package/src/assets/web-panel/assets/{Cron-UDtmjvd4.js → Cron-Xa9PtMUQ.js} +2 -2
  20. package/src/assets/web-panel/assets/{Crosschain-iqbVDPNE.js → Crosschain-wVWs4lqN.js} +1 -1
  21. package/src/assets/web-panel/assets/{DID-DzdUfzc9.js → DID-DTkqiRuT.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dashboard-DvKp1skY.js → Dashboard-d9STUbrr.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dropdown-S6ORlfef.js → Dropdown-CrdxS-C8.js} +1 -1
  24. package/src/assets/web-panel/assets/{EmailListRenderer-mJViHjiq.js → EmailListRenderer-D78XHUEp.js} +1 -1
  25. package/src/assets/web-panel/assets/{FamilyGuardDashboard-A4Lu5m_e.js → FamilyGuardDashboard-iAeSETIP.js} +1 -1
  26. package/src/assets/web-panel/assets/{Federation-Bv-6737r.js → Federation-CTV1Sxqs.js} +1 -1
  27. package/src/assets/web-panel/assets/{FormItemContext-DNcZoiWu.js → FormItemContext-BtwNuQKK.js} +1 -1
  28. package/src/assets/web-panel/assets/{GenericCardRenderer-B_a0l9U4.js → GenericCardRenderer-CdEgHjkl.js} +1 -1
  29. package/src/assets/web-panel/assets/{Git-BbVAsh_N.js → Git-BTo-PJr_.js} +2 -2
  30. package/src/assets/web-panel/assets/{Governance-ChxUMZEU.js → Governance-DquOG94r.js} +1 -1
  31. package/src/assets/web-panel/assets/{Inference-Buu_bAQ-.js → Inference-DDtcBxRB.js} +1 -1
  32. package/src/assets/web-panel/assets/{KnowledgeGraph-BMmMjxEu.js → KnowledgeGraph-KUOmNj5C.js} +1 -1
  33. package/src/assets/web-panel/assets/{Logs-BeGHcPEC.js → Logs-HKm7kRs7.js} +2 -2
  34. package/src/assets/web-panel/assets/{Marketplace-CP2Qg_xJ.js → Marketplace-IxrOcbFB.js} +1 -1
  35. package/src/assets/web-panel/assets/{McpTools-C42B4JYb.js → McpTools-D6a1LM3S.js} +4 -4
  36. package/src/assets/web-panel/assets/{Memory-D5VeitFY.js → Memory-lFkD2ZuM.js} +2 -2
  37. package/src/assets/web-panel/assets/{MobileBridge-BouoJ2FQ.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-Chn6ES5y.js → Mtc-BXpJGrjm.js} +6 -6
  40. package/src/assets/web-panel/assets/{MtcAudit-D0_Q6GEn.js → MtcAudit-CWttaim1.js} +2 -2
  41. package/src/assets/web-panel/assets/{Multisig-BWaCi_wo.js → Multisig-jKgTuVLS.js} +3 -3
  42. package/src/assets/web-panel/assets/{NLProgramming-CoSEiroa.js → NLProgramming-xl4RDzQj.js} +1 -1
  43. package/src/assets/web-panel/assets/{Notes-CiqCbDw3.js → Notes-DPBOvscE.js} +3 -3
  44. package/src/assets/web-panel/assets/{NotificationSettings-BXmzKL9F.js → NotificationSettings-8TkIkRo3.js} +1 -1
  45. package/src/assets/web-panel/assets/{OrderTableRenderer-BhlKyGrE.js → OrderTableRenderer-Dwa-XtoE.js} +1 -1
  46. package/src/assets/web-panel/assets/{Organization-CAVgSxyI.js → Organization-CJ0xVwZM.js} +4 -4
  47. package/src/assets/web-panel/assets/{Overflow-BIVUo8YB.js → Overflow-V7VuUslt.js} +1 -1
  48. package/src/assets/web-panel/assets/{P2P-DX-Ov-eJ.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-CANl-V55.js → Permissions-CPJFF0zU.js} +3 -3
  51. package/src/assets/web-panel/assets/{PersonalDataHub-BE90gjUO.js → PersonalDataHub-Cmn2uiuw.js} +4 -4
  52. package/src/assets/web-panel/assets/{Pipeline-Ck8lV8Pn.js → Pipeline-0zX89_iz.js} +1 -1
  53. package/src/assets/web-panel/assets/{Privacy-BBYUDK4T.js → Privacy-DROUg3XE.js} +1 -1
  54. package/src/assets/web-panel/assets/{ProjectInit-DcbOrnbt.js → ProjectInit-c5KESOK4.js} +2 -2
  55. package/src/assets/web-panel/assets/{ProjectSettings-DZ6-whxP.js → ProjectSettings-BfiCcnb_.js} +2 -2
  56. package/src/assets/web-panel/assets/Projects-BtZH5-Eh.js +1 -0
  57. package/src/assets/web-panel/assets/{Providers-VWoO_Y9u.js → Providers-C9Rr_dOk.js} +1 -1
  58. package/src/assets/web-panel/assets/{QuickAsk-RJfSXWYg.js → QuickAsk-Du4p90W6.js} +1 -1
  59. package/src/assets/web-panel/assets/{Recommend-Y7MWWkXa.js → Recommend-B-DQenTl.js} +1 -1
  60. package/src/assets/web-panel/assets/{Reputation-nG8nut3l.js → Reputation-DvwlAVRZ.js} +1 -1
  61. package/src/assets/web-panel/assets/{Row-DAio6Dx2.js → Row-rTnbvkP-.js} +1 -1
  62. package/src/assets/web-panel/assets/{RssFeed-DwY72Lc7.js → RssFeed-DwvsqWbB.js} +3 -3
  63. package/src/assets/web-panel/assets/{Search-qGG6AUWY.js → Search-U_Xj5SvF.js} +1 -1
  64. package/src/assets/web-panel/assets/{Security-Djsmom8n.js → Security-CdjsVDQ8.js} +4 -4
  65. package/src/assets/web-panel/assets/{Services-DTEgHkUO.js → Services-DUd3mFXk.js} +2 -2
  66. package/src/assets/web-panel/assets/{Skeleton-R5xY0J9Y.js → Skeleton-CA2gCJmY.js} +1 -1
  67. package/src/assets/web-panel/assets/{Skills-CFaRLO3o.js → Skills-BIw7Rb-m.js} +1 -1
  68. package/src/assets/web-panel/assets/{Sla-B5bzoY1I.js → Sla-vySPesC0.js} +1 -1
  69. package/src/assets/web-panel/assets/{SpeechSettings-B_al6SiQ.js → SpeechSettings-EniuTjBJ.js} +1 -1
  70. package/src/assets/web-panel/assets/{SyncSettings-DkUU63oJ.js → SyncSettings-DkrqbzYS.js} +2 -2
  71. package/src/assets/web-panel/assets/{Tasks-C2y4XdrQ.js → Tasks-oyPnWRbw.js} +1 -1
  72. package/src/assets/web-panel/assets/{Templates-CcYJxTNB.js → Templates-C2Kvn60U.js} +1 -1
  73. package/src/assets/web-panel/assets/{Tenant-B0_sIpl2.js → Tenant-x6jVMx4D.js} +1 -1
  74. package/src/assets/web-panel/assets/{Terminal-B_waZb0O.js → Terminal-C2nZbPBs.js} +2 -2
  75. package/src/assets/web-panel/assets/{TimelineRenderer-DuMkErBx.js → TimelineRenderer-BF6HAETd.js} +1 -1
  76. package/src/assets/web-panel/assets/{Tokens-BXjPA6rV.js → Tokens-B-KcVAin.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trigger-BMAAx3Uu.js → Trigger-B-Caiptm.js} +1 -1
  78. package/src/assets/web-panel/assets/{Trust-BPeNXpsi.js → Trust-_8sq7pJp.js} +1 -1
  79. package/src/assets/web-panel/assets/{UkeySign-A0qabV14.js → UkeySign-CLveUEgo.js} +1 -1
  80. package/src/assets/web-panel/assets/{VideoEditing-BMLfYykd.js → VideoEditing-iVc9jxt9.js} +1 -1
  81. package/src/assets/web-panel/assets/{Wallet-BfDI3zjs.js → Wallet-D1n5pidD.js} +4 -4
  82. package/src/assets/web-panel/assets/{WebAuthn-BCnZEScW.js → WebAuthn-CA8kubXb.js} +4 -4
  83. package/src/assets/web-panel/assets/{WorkflowEditor-DvtS5Asf.js → WorkflowEditor-BXWwd_fB.js} +1 -1
  84. package/src/assets/web-panel/assets/{chat-CDJZtBM7.js → chat-DUNkQr1A.js} +1 -1
  85. package/src/assets/web-panel/assets/{colors-fbH1Saco.js → colors-Dz5ozTcp.js} +1 -1
  86. package/src/assets/web-panel/assets/{compact-item-CuZFa9l8.js → compact-item-CZ5-JSLh.js} +1 -1
  87. package/src/assets/web-panel/assets/{createContext-CQuPOqqD.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-CDZlDrhJ.js → hasIn-CqWIkHJm.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-BivMeInw.js → index-3elHm6lI.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-DvBgQoaw.js → index-8l5LLWxH.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-BJCXJCUA.js → index-BBq1ySIt.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-CNmJrCxV.js → index-BI2EU3hC.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-BSy0noke.js → index-BJt6sNTF.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-oe9ZPRtQ.js → index-BLLSLAC3.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-C6epsHef.js → index-BLNgGXeg.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-COSyGm80.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-DGAK9Dj4.js → index-BiAcVeea.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DjgZRX0_.js → index-C2SoMbLc.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CQ5FVEji.js → index-C5XUilwu.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Bt1j0mjJ.js → index-C5zhjact.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BKaue5Pv.js → index-CBeASfAD.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Bz5_6E63.js → index-CD7UjlnE.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-Cr_shi_7.js → index-CDq8IVvv.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Dkuecn17.js → index-CE-gpaY9.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CkhudJH0.js → index-CL6wt2JN.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-kGzPbvry.js → index-CLu3Oyef.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-D365qmj8.js → index-C_WWTpLE.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Deqod8La.js → index-CbcHBDYj.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DwAiu9LT.js → index-CguUaiiY.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-WDQkyh-E.js → index-Ci1-8q-g.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BNEG9-EK.js → index-CvMZxZOn.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-CnfR7qmj.js → index-D6Nkerss.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CdiNFWPp.js → index-DEzYXMgc.js} +1 -1
  116. package/src/assets/web-panel/assets/{index--SCX46Az.js → index-DF0hXW5L.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BhZkMMey.js → index-DGAjS_D9.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CD9ml9ZJ.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-DNN-xBWV.js → index-DUyhvh0L.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-ycBlRXAf.js → index-DW18L-o6.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CAzMdnkI.js → index-Dc-5Ulgt.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CzeRvhia.js → index-DvUlrMy-.js} +3 -3
  124. package/src/assets/web-panel/assets/{index-BdORz0iZ.js → index-FsYDYVUk.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CmeO_DfK.js → index-GVbsyUQm.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BzIDfObk.js → index-noQc_RpT.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-14gri6Vh.js → index-rR060KAF.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DhFyStIG.js → index-xaZX6ZDL.js} +1 -1
  129. package/src/assets/web-panel/assets/{initDefaultProps-DHRWNV-y.js → initDefaultProps-PIetywTX.js} +1 -1
  130. package/src/assets/web-panel/assets/{motion-MJ2jhdVO.js → motion-gQJEK3wO.js} +1 -1
  131. package/src/assets/web-panel/assets/{move-Bly0QFE5.js → move-ML1nRxts.js} +1 -1
  132. package/src/assets/web-panel/assets/{omit-DkoMB0pZ.js → omit-wUWsw3YL.js} +1 -1
  133. package/src/assets/web-panel/assets/{pickAttrs-9M-gsXIc.js → pickAttrs-A0Wlomih.js} +1 -1
  134. package/src/assets/web-panel/assets/{placementArrow-Bnht1xci.js → placementArrow-C5RKYdxT.js} +1 -1
  135. package/src/assets/web-panel/assets/{responsiveObserve-B0Cn1i64.js → responsiveObserve-DIxNVSJl.js} +1 -1
  136. package/src/assets/web-panel/assets/{slide-BdI4DDyM.js → slide-B-tNesVu.js} +1 -1
  137. package/src/assets/web-panel/assets/{statusUtils-DdBktcMD.js → statusUtils-CftzO200.js} +1 -1
  138. package/src/assets/web-panel/assets/{styleChecker-aYEwS4Pw.js → styleChecker-CMgvWu90.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFlexGapSupport-y8cUyKiP.js → useFlexGapSupport-sxqoDNhZ.js} +1 -1
  140. package/src/assets/web-panel/assets/{useFs-Bk1SrPFp.js → useFs-CowEXz4v.js} +1 -1
  141. package/src/assets/web-panel/assets/{usePersonalDataHub-Dp04zAB3.js → usePersonalDataHub-6ISRG7V-.js} +1 -1
  142. package/src/assets/web-panel/assets/{vnode-B52a38TC.js → vnode-C2mnXfmw.js} +1 -1
  143. package/src/assets/web-panel/assets/{zoom-DFwyL43U.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/agent-core.js +2 -0
  148. package/src/lib/llm-pricing.js +9 -0
  149. package/src/lib/mcp-client.js +1 -0
  150. package/src/lib/permission-rules.cjs +11 -1
  151. package/src/lib/personal-data-hub-wiring.js +24 -0
  152. package/src/lib/repl-multiline.js +64 -0
  153. package/src/lib/repl-rewind.js +65 -2
  154. package/src/lib/repl-vim.js +445 -0
  155. package/src/lib/safe-mode.js +17 -3
  156. package/src/lib/skill-loader.js +45 -1
  157. package/src/lib/slash-commands.js +13 -3
  158. package/src/lib/status-line.cjs +33 -3
  159. package/src/repl/agent-repl.js +427 -23
  160. package/src/repl/config-summary.js +59 -0
  161. package/src/repl/conversation-export.js +133 -0
  162. package/src/repl/doctor-status.js +114 -0
  163. package/src/repl/memory-status.js +45 -0
  164. package/src/repl/permissions-status.js +51 -0
  165. package/src/repl/recent-sessions.js +46 -0
  166. package/src/repl/session-cost.js +216 -0
  167. package/src/runtime/agent-core.js +23 -8
  168. package/src/runtime/fallback-model.js +125 -30
  169. package/src/runtime/headless-runner.js +2 -0
  170. package/src/runtime/headless-stream.js +2 -0
  171. package/src/runtime/mcp-config.js +14 -3
  172. package/src/assets/web-panel/assets/MobileProjects-mAJsyk7U.js +0 -1
  173. package/src/assets/web-panel/assets/PdhVaultBrowser-6clRu-J6.js +0 -7
  174. package/src/assets/web-panel/assets/Projects-DO25SEFT.js +0 -1
  175. package/src/assets/web-panel/assets/devWarning-mhGhHpNs.js +0 -1
  176. package/src/assets/web-panel/assets/index-DGBjZXvW.js +0 -1
  177. package/src/assets/web-panel/assets/index-DujMkFuc.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,9 +78,13 @@ 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";
87
+ import { newCostStore, addUsage } from "./session-cost.js";
79
88
 
80
89
  /**
81
90
  * Reference to the runtime DB for hook execution (set during startAgentRepl)
@@ -167,6 +176,17 @@ async function agentLoop(messages, options) {
167
176
  ...options,
168
177
  })) {
169
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
+ }
170
190
  process.stdout.write(
171
191
  chalk.gray(` ⎌ checkpoint ${event.id} (before ${event.tool})\n`),
172
192
  );
@@ -244,12 +264,19 @@ export async function startAgentRepl(options = {}) {
244
264
  // can `cc checkpoint restore` to just before any tool call.
245
265
  const autoCheckpoint = options.autoCheckpoint === true;
246
266
 
247
- // --fallback-model: retry a turn's LLM call once on a backup model when the
248
- // primary errors out (overload / network). Built once; passed into every
249
- // agentLoop call via chatFn. Undefined when no fallback configured.
250
- 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
251
278
  ? makeFallbackChatFn({
252
- fallbackModel: options.fallbackModel,
279
+ fallbackModels: _fallbackModels,
253
280
  onFallback: ({ from, to, error }) =>
254
281
  logger.info(
255
282
  chalk.yellow(
@@ -490,6 +517,10 @@ export async function startAgentRepl(options = {}) {
490
517
  );
491
518
  let _activeOutputStyle = null; // { name, body }
492
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 = [];
493
524
  // Apply --output-style or the settings.json `outputStyle` default at startup.
494
525
  try {
495
526
  const { resolveOutputStyle } = await import("../lib/output-styles.js");
@@ -637,6 +668,8 @@ export async function startAgentRepl(options = {}) {
637
668
  // (parity with headless; auto-detect already works via process.env).
638
669
  ide: options.ide,
639
670
  cwd: process.cwd(),
671
+ // advertise the session id to spawned stdio MCP servers
672
+ sessionId,
640
673
  },
641
674
  { writeErr: (s) => process.stderr.write(s) },
642
675
  );
@@ -733,16 +766,31 @@ export async function startAgentRepl(options = {}) {
733
766
  }
734
767
  }
735
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
+
736
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
+ : "";
737
785
  const planManager = getPlanModeManager();
738
786
  if (planManager.isActive()) {
739
787
  const state = planManager.state;
740
788
  if (state === PlanState.APPROVED || state === PlanState.EXECUTING) {
741
- return chalk.green("[plan:exec] > ");
789
+ return vim + chalk.green("[plan:exec] > ");
742
790
  }
743
- return chalk.yellow("[plan] > ");
791
+ return vim + chalk.yellow("[plan] > ");
744
792
  }
745
- return chalk.green("> ");
793
+ return vim + chalk.green("> ");
746
794
  };
747
795
 
748
796
  // `@` tab-completion (Claude-Code @-mention parity): filesystem paths +
@@ -757,14 +805,20 @@ export async function startAgentRepl(options = {}) {
757
805
  "/cd",
758
806
  "/clear",
759
807
  "/compact",
808
+ "/config",
760
809
  "/context",
810
+ "/cost",
761
811
  "/cowork",
812
+ "/doctor",
762
813
  "/exit",
814
+ "/export",
763
815
  "/help",
764
816
  "/ide",
765
817
  "/mcp",
818
+ "/memory",
766
819
  "/model",
767
820
  "/output-style",
821
+ "/permissions",
768
822
  "/plan",
769
823
  "/profile",
770
824
  "/provider",
@@ -774,10 +828,12 @@ export async function startAgentRepl(options = {}) {
774
828
  "/rewind",
775
829
  "/search",
776
830
  "/session",
831
+ "/sessions",
777
832
  "/stats",
778
833
  "/statusline",
779
834
  "/sub-agents",
780
835
  "/task",
836
+ "/vim",
781
837
  ],
782
838
  getIdeOpenFiles: async () => {
783
839
  const exec = _adhocMcp?.externalToolExecutors?.mcp__ide__getOpenEditors;
@@ -805,6 +861,54 @@ export async function startAgentRepl(options = {}) {
805
861
  completer: atCompleter,
806
862
  });
807
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
+
808
912
  // Esc interrupt (Claude-Code parity): pressing Esc while a turn is in
809
913
  // flight aborts the in-flight agentLoop through its existing AbortSignal
810
914
  // seam (throwIfAborted at each iteration); partial conversation is kept.
@@ -814,8 +918,9 @@ export async function startAgentRepl(options = {}) {
814
918
  let _lastIdleEscAt = 0;
815
919
  if (process.stdin.isTTY) {
816
920
  process.stdin.on("keypress", (_str, key) => {
817
- if (!key || key.name !== "escape" || key.meta) return;
818
- if (_turnAbort) {
921
+ const k = key || {};
922
+ // 1) Turn abort always wins, regardless of vim mode.
923
+ if (k.name === "escape" && !k.meta && _turnAbort) {
819
924
  process.stdout.write(chalk.yellow("\n⎋ interrupting…\n"));
820
925
  try {
821
926
  _turnAbort.abort();
@@ -825,8 +930,34 @@ export async function startAgentRepl(options = {}) {
825
930
  _turnAbort = null;
826
931
  return;
827
932
  }
828
- // Double-Esc while idle → rewind picker shortcut (Claude-Code parity);
829
- // 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;
830
961
  const nowTs = Date.now();
831
962
  if (nowTs - _lastIdleEscAt < 600) {
832
963
  _lastIdleEscAt = 0;
@@ -840,7 +971,9 @@ export async function startAgentRepl(options = {}) {
840
971
  );
841
972
  process.stdout.write(
842
973
  chalk.gray(
843
- "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",
844
977
  ),
845
978
  );
846
979
  prompt();
@@ -881,6 +1014,7 @@ export async function startAgentRepl(options = {}) {
881
1014
  let _curModel = model; // tracks the per-turn active model for the readout
882
1015
  let _ctxUsedTokens = 0;
883
1016
  let _turnCount = 0;
1017
+ const _costStore = newCostStore(); // running token spend for `/cost`
884
1018
  let _renderStatus = null;
885
1019
  try {
886
1020
  const slm = await import("../lib/status-line.cjs");
@@ -941,7 +1075,24 @@ export async function startAgentRepl(options = {}) {
941
1075
  // when the current turn finishes.
942
1076
  let _processingLine = false;
943
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 = [];
944
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
+
945
1096
  const trimmed = input.trim();
946
1097
  if (!trimmed) {
947
1098
  prompt();
@@ -1013,20 +1164,44 @@ export async function startAgentRepl(options = {}) {
1013
1164
  logger.log(
1014
1165
  ` ${chalk.cyan("# <note>")} Remember a note in the project cc.md`,
1015
1166
  );
1167
+ logger.log(
1168
+ ` ${chalk.cyan("… \\")} End a line with \\ to continue input onto the next line`,
1169
+ );
1016
1170
  logger.log(` ${chalk.cyan("/exit")} Exit the agent`);
1017
1171
  logger.log(
1018
1172
  ` ${chalk.cyan("/model")} Show/change model (/model <name>)`,
1019
1173
  );
1020
1174
  logger.log(` ${chalk.cyan("/provider")} Show/change provider`);
1021
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
+ );
1022
1179
  logger.log(
1023
1180
  ` ${chalk.cyan("/statusline")} Context-usage line on/off (/statusline [on|off])`,
1024
1181
  );
1182
+ logger.log(
1183
+ ` ${chalk.cyan("/config")} Effective config (provider/model, keys masked)`,
1184
+ );
1185
+ logger.log(
1186
+ ` ${chalk.cyan("/doctor")} Session health check (provider/key/IDE/MCP/hooks)`,
1187
+ );
1188
+ logger.log(
1189
+ ` ${chalk.cyan("/memory")} Project-memory files loaded (cc.md hierarchy + rules)`,
1190
+ );
1025
1191
  logger.log(
1026
1192
  ` ${chalk.cyan("/context")} Live context-window usage by role`,
1027
1193
  );
1028
1194
  logger.log(
1029
- ` ${chalk.cyan("/rewind")} Rewind conversation to an earlier turn (double-Esc lists)`,
1195
+ ` ${chalk.cyan("/cost")} Session token spend + estimated $ (per model & category)`,
1196
+ );
1197
+ logger.log(
1198
+ ` ${chalk.cyan("/permissions")} Allow/ask/deny rules in effect this session`,
1199
+ );
1200
+ logger.log(
1201
+ ` ${chalk.cyan("/export")} Save this conversation to a Markdown file (/export [path])`,
1202
+ );
1203
+ logger.log(
1204
+ ` ${chalk.cyan("/rewind")} Rewind conversation + files to an earlier turn (double-Esc lists)`,
1030
1205
  );
1031
1206
  logger.log(
1032
1207
  ` ${chalk.cyan("/cd <dir>")} Change working directory mid-session (completion/memory follow)`,
@@ -1042,6 +1217,9 @@ export async function startAgentRepl(options = {}) {
1042
1217
  );
1043
1218
  logger.log(` ${chalk.cyan("/task clear")} Clear current task`);
1044
1219
  logger.log(` ${chalk.cyan("/session")} Show current session info`);
1220
+ logger.log(
1221
+ ` ${chalk.cyan("/sessions")} List recent resumable sessions (/session resume <id> to switch)`,
1222
+ );
1045
1223
  logger.log(
1046
1224
  ` ${chalk.cyan("/reindex")} Reindex notes for BM25 search`,
1047
1225
  );
@@ -1200,11 +1378,30 @@ export async function startAgentRepl(options = {}) {
1200
1378
 
1201
1379
  if (trimmed === "/clear") {
1202
1380
  messages.length = 1; // Keep system prompt
1381
+ _checkpointMarks.length = 0; // checkpoint marks no longer map to anything
1203
1382
  logger.info("Conversation cleared");
1204
1383
  prompt();
1205
1384
  return;
1206
1385
  }
1207
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
+
1208
1405
  if (trimmed === "/statusline" || trimmed.startsWith("/statusline ")) {
1209
1406
  const arg = trimmed.slice("/statusline".length).trim().toLowerCase();
1210
1407
  if (arg === "off") {
@@ -1297,17 +1494,21 @@ export async function startAgentRepl(options = {}) {
1297
1494
 
1298
1495
  if (trimmed === "/rewind" || trimmed.startsWith("/rewind ")) {
1299
1496
  try {
1300
- const { listUserTurns, rewindToTurn, renderTurnList } =
1301
- 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");
1302
1504
  const arg = trimmed.slice("/rewind".length).trim();
1303
1505
  if (!arg) {
1304
1506
  logger.log(chalk.bold("\nRewind — pick a user turn (newest first):"));
1305
1507
  logger.log(renderTurnList(listUserTurns(messages)));
1306
- logger.log(
1307
- chalk.gray(
1308
- "Usage: /rewind <n> (conversation only — restore files with `cc checkpoint restore`)",
1309
- ),
1310
- );
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}`));
1311
1512
  } else {
1312
1513
  const res = rewindToTurn(messages, arg);
1313
1514
  if (!res) {
@@ -1318,6 +1519,48 @@ export async function startAgentRepl(options = {}) {
1318
1519
  `⎌ rewound — dropped ${res.removed} message(s); edit and resend below`,
1319
1520
  ),
1320
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
+ }
1321
1564
  prompt();
1322
1565
  if (res.text) rl.write(res.text);
1323
1566
  return;
@@ -1432,7 +1675,7 @@ export async function startAgentRepl(options = {}) {
1432
1675
  }
1433
1676
 
1434
1677
  // Session info
1435
- if (trimmed.startsWith("/session")) {
1678
+ if (trimmed === "/session" || trimmed.startsWith("/session ")) {
1436
1679
  const sessionArg = trimmed.slice(8).trim();
1437
1680
  if (sessionArg.startsWith("resume ")) {
1438
1681
  const resumeId = sessionArg.slice(7).trim();
@@ -2110,6 +2353,42 @@ export async function startAgentRepl(options = {}) {
2110
2353
  return;
2111
2354
  }
2112
2355
 
2356
+ // `/sessions` — list recent RESUMABLE conversations (read-only; the ids
2357
+ // work with `cc agent --resume <id>`). `/session` shows the current one.
2358
+ if (trimmed === "/sessions" || trimmed === "/sessions ") {
2359
+ try {
2360
+ const { listRecentSessions } = await import("../lib/recent-session.js");
2361
+ const { renderRecentSessions } = await import("./recent-sessions.js");
2362
+ const sessions = listRecentSessions({ db: _hookDb }, { scan: 20 });
2363
+ logger.log(renderRecentSessions(sessions, { currentId: sessionId }));
2364
+ } catch (err) {
2365
+ logger.error(chalk.red(`/sessions failed: ${err.message}`));
2366
+ }
2367
+ prompt();
2368
+ return;
2369
+ }
2370
+
2371
+ // `/memory` — project-memory files auto-loaded into the system prompt
2372
+ // (cc.md hierarchy + imports + path-scoped rules). Distinct from `#` (add
2373
+ // a note) and `cc memory recall` (scoped store).
2374
+ if (trimmed === "/memory" || trimmed === "/memory ") {
2375
+ try {
2376
+ const { loadProjectInstructions } =
2377
+ await import("../lib/project-instructions.js");
2378
+ const { renderMemoryFiles } = await import("./memory-status.js");
2379
+ const loaded = loadProjectInstructions({ cwd: process.cwd() });
2380
+ logger.log(
2381
+ renderMemoryFiles(loaded, {
2382
+ enabled: process.env.CC_PROJECT_MEMORY !== "0",
2383
+ }),
2384
+ );
2385
+ } catch (err) {
2386
+ logger.error(chalk.red(`/memory failed: ${err.message}`));
2387
+ }
2388
+ prompt();
2389
+ return;
2390
+ }
2391
+
2113
2392
  // `/mcp` — overview of connected MCP servers' resources + prompts.
2114
2393
  if (trimmed === "/mcp" || trimmed === "/mcp ") {
2115
2394
  const mcpClient = _adhocMcp?.mcpClient || _bundleMcpClient;
@@ -2118,6 +2397,127 @@ export async function startAgentRepl(options = {}) {
2118
2397
  return;
2119
2398
  }
2120
2399
 
2400
+ // `/config` — effective configuration (secret-safe): the LLM provider/model
2401
+ // in effect, whether keys are set, web-search backend, config path.
2402
+ if (trimmed === "/config" || trimmed === "/config ") {
2403
+ try {
2404
+ const { loadConfig } = await import("../lib/config-manager.js");
2405
+ const { getConfigPath } = await import("../lib/paths.js");
2406
+ const { renderConfigSummary } = await import("./config-summary.js");
2407
+ logger.log(
2408
+ renderConfigSummary(loadConfig(), {
2409
+ path: getConfigPath(),
2410
+ activeProvider: provider,
2411
+ activeModel: _curModel || model,
2412
+ }),
2413
+ );
2414
+ } catch (err) {
2415
+ logger.error(chalk.red(`/config failed: ${err.message}`));
2416
+ }
2417
+ prompt();
2418
+ return;
2419
+ }
2420
+
2421
+ // `/doctor` — consolidated session-health readout (Claude-Code parity):
2422
+ // provider/key/IDE/MCP/permissions/hooks in one pass-or-warn view.
2423
+ if (trimmed === "/doctor" || trimmed === "/doctor ") {
2424
+ let config = {};
2425
+ try {
2426
+ config = (await import("../lib/config-manager.js")).loadConfig() || {};
2427
+ } catch (_err) {
2428
+ // config read is best-effort; checks degrade gracefully
2429
+ }
2430
+ const { buildDoctorChecks, renderDoctor } =
2431
+ await import("./doctor-status.js");
2432
+ const { ideToolNames } = await import("./ide-status.js");
2433
+ const checks = buildDoctorChecks({
2434
+ config,
2435
+ ideTools: ideToolNames(_adhocMcp),
2436
+ mcpServers: _adhocMcp?.connected,
2437
+ permissionRules: _permissionRules,
2438
+ settingsHooks: _settingsHooks,
2439
+ });
2440
+ logger.log(renderDoctor(checks));
2441
+ prompt();
2442
+ return;
2443
+ }
2444
+
2445
+ // `/export [path]` — dump the live conversation to a Markdown transcript
2446
+ // (Claude-Code parity). Distinct from `cc export` (knowledge base). Captures
2447
+ // exactly what's in context now, persisted or not.
2448
+ if (trimmed === "/export" || trimmed.startsWith("/export ")) {
2449
+ const arg = trimmed.slice("/export".length).trim();
2450
+ try {
2451
+ const { renderConversationMarkdown, defaultExportFilename } =
2452
+ await import("./conversation-export.js");
2453
+ const fs = await import("fs");
2454
+ const path = await import("path");
2455
+ const md = renderConversationMarkdown(messages, {
2456
+ provider,
2457
+ model: _curModel || model,
2458
+ sessionId,
2459
+ exportedAt: new Date().toISOString(),
2460
+ });
2461
+ const file = arg
2462
+ ? path.resolve(process.cwd(), arg)
2463
+ : path.resolve(process.cwd(), defaultExportFilename(new Date()));
2464
+ fs.writeFileSync(file, md, "utf-8");
2465
+ logger.log(
2466
+ chalk.green(`Exported ${messages.length} messages → ${file}`),
2467
+ );
2468
+ } catch (err) {
2469
+ logger.error(chalk.red(`/export failed: ${err.message}`));
2470
+ }
2471
+ prompt();
2472
+ return;
2473
+ }
2474
+
2475
+ // `/permissions` — allow/ask/deny rules in effect this session (Claude-Code
2476
+ // parity): what the agent runs unprompted, asks about, or is blocked from.
2477
+ if (trimmed === "/permissions" || trimmed === "/permissions ") {
2478
+ let files = [];
2479
+ try {
2480
+ const { loadSettings } = await import("../lib/settings-loader.cjs");
2481
+ files = loadSettings({ cwd: process.cwd() }).files || [];
2482
+ } catch (_err) {
2483
+ // source listing is best-effort — still show the live rules
2484
+ }
2485
+ const { renderPermissions } = await import("./permissions-status.js");
2486
+ logger.log(renderPermissions(_permissionRules, { files }));
2487
+ prompt();
2488
+ return;
2489
+ }
2490
+
2491
+ // `/cost` — running token spend + estimated $ for this session (Claude-Code
2492
+ // parity). In-memory accumulation, so it works without session persistence.
2493
+ if (trimmed === "/cost" || trimmed === "/cost ") {
2494
+ let overrides;
2495
+ let visionModel;
2496
+ try {
2497
+ const { loadConfig } = await import("../lib/config-manager.js");
2498
+ const cfg = loadConfig();
2499
+ overrides = cfg?.llm?.pricing;
2500
+ visionModel = cfg?.llm?.visionModel;
2501
+ } catch (_err) {
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
+ };
2513
+ const { renderSessionCost } = await import("./session-cost.js");
2514
+ logger.log(
2515
+ renderSessionCost(_costStore, { pricingOverrides: overrides, roles }),
2516
+ );
2517
+ prompt();
2518
+ return;
2519
+ }
2520
+
2121
2521
  // `/ide` — IDE bridge connection status (Claude-Code parity): which editor
2122
2522
  // is connected, its tools, or why discovery came up empty.
2123
2523
  if (trimmed === "/ide" || trimmed === "/ide ") {
@@ -2347,6 +2747,7 @@ export async function startAgentRepl(options = {}) {
2347
2747
  additionalDirectories,
2348
2748
  autoCheckpoint,
2349
2749
  checkpointSession: sessionId,
2750
+ checkpointMarks: _checkpointMarks,
2350
2751
  prepareCall,
2351
2752
  approvalGate: _approvalGate,
2352
2753
  permissionRules: _permissionRules,
@@ -2362,6 +2763,9 @@ export async function startAgentRepl(options = {}) {
2362
2763
  });
2363
2764
  _turnAbort = null;
2364
2765
 
2766
+ // Running spend for `/cost` (in-memory, works without persistence).
2767
+ if (usageEvents?.length) addUsage(_costStore, usageEvents);
2768
+
2365
2769
  if (sessionId && usageEvents?.length) {
2366
2770
  for (const ue of usageEvents) {
2367
2771
  try {