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.
- package/bin/chainlesschain.js +1 -1
- package/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps-DmdtNmWd.js → AIOps-BiB8WfIz.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DkuYVafg.js → ActionButton-NLBhC6jG.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Ba_h8Tub.js → Analytics-k62j-xiL.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-yb8Zm9MX.js → AppLayout-spr0Sm6J.js} +3 -3
- package/src/assets/web-panel/assets/{Audit-BGjex2fm.js → Audit-C3NHJos3.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-IFQ2hOF2.js → Backup-C2V9tGqF.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-W8AkPkrV.js → BaseInput-DeKm11mH.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BgI7t-iW.js → Chat-CHZ2CU7x.js} +6 -6
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DEXSa7tC.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-DuWsZP4g.js → Checkbox-q6E9VeLr.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DyoTNmYg.js → Codegen--4w4QpUI.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DttmlDRk.js → Col-DLOkwTsj.js} +1 -1
- package/src/assets/web-panel/assets/{Community-D9nnIdKn.js → Community-B1LxJGfE.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-C8KVQaHb.js → Compact-C_769oQZ.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-R2owqgjj.js → Compliance-zsI0s7vB.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DwGMMjRn.js → Cowork-A1WA6whF.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-BSTcN_lK.js → Cron-C3JDTyyd.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CTNuIbFD.js → Crosschain-D8O6uB86.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CgApGsFP.js → DID-BpOebm5d.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-D_OJ3UN5.js → Dashboard-Defso6kA.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-B84Jwra_.js → Dropdown-Cv9BrwT_.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Bv-YO-6y.js → EmailListRenderer-BWKHbh4C.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-drgZ408Y.js → FamilyGuardDashboard--m_Ru7Ci.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CtzFkdW2.js → Federation-Bs6ZcAP0.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BFAvNhl9.js → FormItemContext-CcAs3Acx.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DnuEyz_l.js → GenericCardRenderer-DI1oL4pK.js} +1 -1
- package/src/assets/web-panel/assets/{Git-jlHajmRJ.js → Git-C27t3-fW.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DmJC7PGL.js → Governance-Dr_syXc_.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-B-u7xD2n.js → Inference-CWM8dIbA.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-BaYCA2Cd.js → KnowledgeGraph--cFDUZv3.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DTNYQWfp.js → Logs-Cnn2_Onf.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-CUu1xYvo.js → Marketplace-4T9ok3Gz.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BmoeTyrC.js → McpTools-BQvZwqcN.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-DxTU3QU7.js → Memory-BE9rPkM_.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CpcOlKAD.js → MobileBridge-DJ3j5lXC.js} +2 -2
- package/src/assets/web-panel/assets/{MobileProjects-Bjh_z16l.js → MobileProjects-qasLvYdb.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-LfxwOm0x.js → Mtc-D3CSPTD9.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-D6A9Gjkh.js → MtcAudit-DaYVGCN5.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Ch_jofPV.js → Multisig-Dz1c5r5w.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Bkvogg0I.js → NLProgramming-BIKV_K-a.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-C5t5Xihm.js → Notes-f6t-rmOa.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-CTpDUNCb.js → NotificationSettings-DHLQh8Fy.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-CDMZ3o6i.js +1 -0
- package/src/assets/web-panel/assets/{Organization-Dr37BaXa.js → Organization-DIsL758p.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-ZGjsdP7N.js → Overflow-BMM7apnZ.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-bWJU5Vxd.js → P2P-CTGMmTvi.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BRVoW-ye.js → PdhVaultBrowser-DWwmm0k1.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-BOSnFZaC.js → Permissions-Bed5JxMx.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-X4SgjP6P.js → PersonalDataHub-CIiZhSM5.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-DoJhB9bj.js → Pipeline-CtirPodz.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-OM9lDj-R.js → Privacy-DuXhXhE7.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BXQEOmLn.js → ProjectInit-DZrnguBl.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DBXo3K5u.js → ProjectSettings-HIltqsJ1.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-CJ4DBJlS.js → Projects-BWFkePg4.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-Tk9SawmO.js → Providers-DEP0Jdvl.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DRI1-nTC.js → QuickAsk-T2THoHNx.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DtrIVTu9.js → Recommend-CvbxaSwm.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-DkH8ImwZ.js → Reputation-6Afy6tfp.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DpA9dlvi.js → Row-DY8OPWaO.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DV3OhxWd.js → RssFeed-wDGWb9pZ.js} +3 -3
- package/src/assets/web-panel/assets/{Search-QxdntiQx.js → Search-D_zHAwZY.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CGuEnrD2.js → Security-Czq7AlGG.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BvwSSD8b.js → Services-Ac1g0ZcG.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-sx_8L3-5.js → Skeleton-DXQ3eeSW.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-dWOwxRsu.js → Skills-CWRioX4u.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-zxXnfKrT.js → Sla-BlHthzfs.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CmFlcNjr.js → SpeechSettings-Ct240JmL.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BeXeqURL.js → SyncSettings-BgDIt8Q-.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-3PTmatJP.js +1 -0
- package/src/assets/web-panel/assets/{Templates-DlgR3XFH.js → Templates-Dp9QhyIw.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-0P8HgQaM.js → Tenant-CHTYMxzY.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-X-NGwLpv.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-hbO7agZs.js → TimelineRenderer-Bh8jA18j.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CsmhgTBO.js → Tokens-DWkTd5dv.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DnaF_2PP.js → Trigger-CRgVg6sd.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-C1oafGj1.js → Trust-BgWOXd0W.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-eLL4DOmC.js → UkeySign-BlTUB9Y-.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CX45sVq7.js → VideoEditing-DI64XgNb.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-aWPqpHdQ.js → Wallet-CJ3TNGiG.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DMYV1MAo.js → WebAuthn-B2-rWWoV.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-D9uRIJvH.js → WorkflowEditor-VI9otbaH.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BmWYfCxG.js → chat-CgYfiaVh.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DqvTCkBe.js → colors-B9EhRTky.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Bh0L0ejI.js → compact-item-Cb7bjraa.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-r2qgp1mn.js → createContext-DlXPeXuj.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-D-Hp8s_8.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BcffXa-S.js → hasIn-BbgRfrdf.js} +1 -1
- package/src/assets/web-panel/assets/{index-DxajFkK2.js → index-1iUK_kAw.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCBqTRWH.js → index-2ts5iOIB.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDR3GmaO.js → index-5CrFMQjt.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7z0qK1W.js → index-AR-QpAkP.js} +1 -1
- package/src/assets/web-panel/assets/{index-BLN-neIf.js → index-BD2W-qsS.js} +1 -1
- package/src/assets/web-panel/assets/{index-De36_UgR.js → index-BU8hEUyq.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bma_yHcC.js → index-BfSS-U5o.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dcjol7ot.js → index-Bk7r1a9x.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbMox24t.js → index-BoEFFKn3.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2-02rrp.js → index-Bt-lPYpq.js} +1 -1
- package/src/assets/web-panel/assets/index-BvnHuxVM.js +1 -0
- package/src/assets/web-panel/assets/{index-DPFT7J7I.js → index-C73WgOc2.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXXxkeij.js → index-C8DB27uJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-XwbSqOB2.js → index-C95qWAh4.js} +1 -1
- package/src/assets/web-panel/assets/{index-CAlxkpnv.js → index-CGx8aO_Y.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3mmDuOv.js → index-CHR47Q5B.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJt0iuep.js → index-CMyzmvtJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-S4E77Aer.js → index-Caiu2avX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtyXyl3t.js → index-Cf9zwbk-.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCyB-RK5.js → index-Cmr31VCO.js} +3 -3
- package/src/assets/web-panel/assets/{index-CKnEtlZD.js → index-D-Zz9PvD.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWlDSE0F.js → index-D6t-Shqr.js} +1 -1
- package/src/assets/web-panel/assets/{index-h4O0AcBt.js → index-D8kB0k3E.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cbj6C3pA.js → index-DDzNdZcX.js} +1 -1
- package/src/assets/web-panel/assets/{index-DutDlDUF.js → index-DPaffcT8.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXae4ZyX.js → index-DUU9DY4J.js} +1 -1
- package/src/assets/web-panel/assets/{index-U86pxDyR.js → index-DXxa7PR8.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRXcGa5y.js → index-DbLJShJB.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMn_luHQ.js → index-De59Xat-.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ct8qhPZe.js → index-Dh6qWb1v.js} +1 -1
- package/src/assets/web-panel/assets/{index-CY8RXaZR.js → index-DkQIyK-V.js} +1 -1
- package/src/assets/web-panel/assets/index-Dkm5IGwX.js +1 -0
- package/src/assets/web-panel/assets/{index-585fuGAN.js → index-Dx4sm6dm.js} +1 -1
- package/src/assets/web-panel/assets/{index-COrfHebA.js → index-Ira0HLPr.js} +1 -1
- package/src/assets/web-panel/assets/{index-BeV-KoQl.js → index-OVrh8wTN.js} +1 -1
- package/src/assets/web-panel/assets/{index-DW1y18GR.js → index-c7-Jd6WB.js} +1 -1
- package/src/assets/web-panel/assets/{index-C9nh3ANl.js → index-fG-1gXy0.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQlAPNSU.js → index-ojRAd7Nq.js} +1 -1
- package/src/assets/web-panel/assets/{index-B6pAm1iJ.js → index-p03wNqiP.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C1d8I-BX.js → initDefaultProps-JT674ACa.js} +1 -1
- package/src/assets/web-panel/assets/{motion-Dq7fiy4Y.js → motion-CokflrA9.js} +1 -1
- package/src/assets/web-panel/assets/{move-Bqb2dySM.js → move-CfMhRpyC.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BUYqb4My.js → omit-ClYc5II5.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DeytiKlZ.js → pickAttrs-CTwEb_8h.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-xrXZWCqG.js → placementArrow-Cb3StU_t.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CcL2K-YY.js → responsiveObserve-uIxkx5M1.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DmCWaic7.js → slide-B9HZBQ7i.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CqNrFif7.js → statusUtils-C72bwYl0.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-C436m5Xy.js → styleChecker-BFTtaQb8.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-CVhutCN8.js → useFlexGapSupport-DKl5j41_.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DUd49Bui.js → useFs-BUHS6bo3.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-fuS9raic.js → usePersonalDataHub-D8KrYSq4.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-C3kmDmk-.js → vnode-JYP-aZDj.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-hX-F1dT-.js → zoom-BFusdxdH.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +67 -29
- package/src/commands/terminal-setup.js +127 -0
- package/src/harness/mcp-client.js +48 -2
- package/src/index.js +2 -0
- package/src/lib/image-input.js +8 -2
- package/src/lib/llm-pricing.js +15 -0
- package/src/lib/permission-rules.cjs +11 -1
- package/src/lib/personal-data-hub-wiring.js +24 -0
- package/src/lib/repl-multiline.js +64 -0
- package/src/lib/repl-rewind.js +65 -2
- package/src/lib/repl-vim.js +445 -0
- package/src/lib/safe-mode.js +17 -3
- package/src/lib/skill-loader.js +45 -1
- package/src/lib/slash-commands.js +13 -3
- package/src/lib/status-line.cjs +33 -3
- package/src/lib/terminal-setup.js +209 -0
- package/src/repl/agent-repl.js +274 -27
- package/src/repl/session-cost.js +98 -1
- package/src/runtime/agent-core.js +23 -8
- package/src/runtime/fallback-model.js +125 -30
- package/src/runtime/headless-runner.js +2 -0
- package/src/runtime/headless-stream.js +2 -0
- package/src/runtime/mcp-config.js +14 -3
- package/src/assets/web-panel/assets/ChatBubbleRenderer-CfpKEQUF.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-ST2lr-Bi.js +0 -1
- package/src/assets/web-panel/assets/Tasks-iImd8xSO.js +0 -1
- package/src/assets/web-panel/assets/Terminal-B5VDEEHD.js +0 -3
- package/src/assets/web-panel/assets/devWarning-CusWDjWW.js +0 -1
- package/src/assets/web-panel/assets/index-BhYltBvN.js +0 -1
- 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
|
+
}
|
package/src/repl/agent-repl.js
CHANGED
|
@@ -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 {
|
|
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:
|
|
249
|
-
//
|
|
250
|
-
// agentLoop call via chatFn.
|
|
251
|
-
|
|
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
|
-
|
|
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
|
-
|
|
826
|
-
|
|
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
|
-
|
|
837
|
-
//
|
|
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
|
-
|
|
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 $ (
|
|
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 {
|
|
1331
|
-
|
|
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
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
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
|
-
|
|
2519
|
+
const cfg = loadConfig();
|
|
2520
|
+
overrides = cfg?.llm?.pricing;
|
|
2521
|
+
visionModel = cfg?.llm?.visionModel;
|
|
2285
2522
|
} catch (_err) {
|
|
2286
|
-
//
|
|
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,
|