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.
- package/bin/chainlesschain.js +1 -1
- package/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps-BeMOUkMq.js → AIOps-a2cSbSEu.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DrRegmIt.js → ActionButton-DwvSB5Pp.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BM7hvUn8.js → Analytics-BqaRaBDD.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BfLLYSz_.js → AppLayout-Beck7v8t.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-BpiPR-rs.js → Audit-UtJhPdXJ.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BLq4IRI9.js → Backup-HVZhcdll.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Bv89IFrM.js → BaseInput-DRY_ZGmj.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BUM9MdnH.js → Chat-D7Vuwfe2.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-rH_t53ZW.js → ChatBubbleRenderer-BS2q_hPX.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-82jih_zU.js → Checkbox-B406i7N1.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-ygmM1mNL.js → Codegen-BvTCqHi3.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CqXc8_ft.js → Col-DRiyxTQP.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CqPPfyR3.js → Community-DWmhxHQa.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-LR8Z206-.js → Compact-DO1HBZEz.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-D4alm_Gx.js → Compliance-D4j-VHwS.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-D-DLVanT.js → Cowork-BGBkWtat.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-UDtmjvd4.js → Cron-Xa9PtMUQ.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-iqbVDPNE.js → Crosschain-wVWs4lqN.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DzdUfzc9.js → DID-DTkqiRuT.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DvKp1skY.js → Dashboard-d9STUbrr.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-S6ORlfef.js → Dropdown-CrdxS-C8.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-mJViHjiq.js → EmailListRenderer-D78XHUEp.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-A4Lu5m_e.js → FamilyGuardDashboard-iAeSETIP.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Bv-6737r.js → Federation-CTV1Sxqs.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DNcZoiWu.js → FormItemContext-BtwNuQKK.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-B_a0l9U4.js → GenericCardRenderer-CdEgHjkl.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BbVAsh_N.js → Git-BTo-PJr_.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-ChxUMZEU.js → Governance-DquOG94r.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Buu_bAQ-.js → Inference-DDtcBxRB.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-BMmMjxEu.js → KnowledgeGraph-KUOmNj5C.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BeGHcPEC.js → Logs-HKm7kRs7.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-CP2Qg_xJ.js → Marketplace-IxrOcbFB.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-C42B4JYb.js → McpTools-D6a1LM3S.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-D5VeitFY.js → Memory-lFkD2ZuM.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BouoJ2FQ.js → MobileBridge-xwuQTps5.js} +2 -2
- package/src/assets/web-panel/assets/MobileProjects-BYr1D3WO.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-Chn6ES5y.js → Mtc-BXpJGrjm.js} +6 -6
- package/src/assets/web-panel/assets/{MtcAudit-D0_Q6GEn.js → MtcAudit-CWttaim1.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-BWaCi_wo.js → Multisig-jKgTuVLS.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CoSEiroa.js → NLProgramming-xl4RDzQj.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CiqCbDw3.js → Notes-DPBOvscE.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BXmzKL9F.js → NotificationSettings-8TkIkRo3.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BhlKyGrE.js → OrderTableRenderer-Dwa-XtoE.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-CAVgSxyI.js → Organization-CJ0xVwZM.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BIVUo8YB.js → Overflow-V7VuUslt.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DX-Ov-eJ.js → P2P-BxuccEGq.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-DaP2Q5kU.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-CANl-V55.js → Permissions-CPJFF0zU.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-BE90gjUO.js → PersonalDataHub-Cmn2uiuw.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-Ck8lV8Pn.js → Pipeline-0zX89_iz.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BBYUDK4T.js → Privacy-DROUg3XE.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DcbOrnbt.js → ProjectInit-c5KESOK4.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DZ6-whxP.js → ProjectSettings-BfiCcnb_.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BtZH5-Eh.js +1 -0
- package/src/assets/web-panel/assets/{Providers-VWoO_Y9u.js → Providers-C9Rr_dOk.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-RJfSXWYg.js → QuickAsk-Du4p90W6.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Y7MWWkXa.js → Recommend-B-DQenTl.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-nG8nut3l.js → Reputation-DvwlAVRZ.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DAio6Dx2.js → Row-rTnbvkP-.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DwY72Lc7.js → RssFeed-DwvsqWbB.js} +3 -3
- package/src/assets/web-panel/assets/{Search-qGG6AUWY.js → Search-U_Xj5SvF.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Djsmom8n.js → Security-CdjsVDQ8.js} +4 -4
- package/src/assets/web-panel/assets/{Services-DTEgHkUO.js → Services-DUd3mFXk.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-R5xY0J9Y.js → Skeleton-CA2gCJmY.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CFaRLO3o.js → Skills-BIw7Rb-m.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-B5bzoY1I.js → Sla-vySPesC0.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-B_al6SiQ.js → SpeechSettings-EniuTjBJ.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DkUU63oJ.js → SyncSettings-DkrqbzYS.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-C2y4XdrQ.js → Tasks-oyPnWRbw.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CcYJxTNB.js → Templates-C2Kvn60U.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-B0_sIpl2.js → Tenant-x6jVMx4D.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B_waZb0O.js → Terminal-C2nZbPBs.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DuMkErBx.js → TimelineRenderer-BF6HAETd.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BXjPA6rV.js → Tokens-B-KcVAin.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BMAAx3Uu.js → Trigger-B-Caiptm.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BPeNXpsi.js → Trust-_8sq7pJp.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-A0qabV14.js → UkeySign-CLveUEgo.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BMLfYykd.js → VideoEditing-iVc9jxt9.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BfDI3zjs.js → Wallet-D1n5pidD.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-BCnZEScW.js → WebAuthn-CA8kubXb.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-DvtS5Asf.js → WorkflowEditor-BXWwd_fB.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CDJZtBM7.js → chat-DUNkQr1A.js} +1 -1
- package/src/assets/web-panel/assets/{colors-fbH1Saco.js → colors-Dz5ozTcp.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CuZFa9l8.js → compact-item-CZ5-JSLh.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CQuPOqqD.js → createContext-CFxlcPug.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BMRVR8Xp.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CDZlDrhJ.js → hasIn-CqWIkHJm.js} +1 -1
- package/src/assets/web-panel/assets/{index-BivMeInw.js → index-3elHm6lI.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvBgQoaw.js → index-8l5LLWxH.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJCXJCUA.js → index-BBq1ySIt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNmJrCxV.js → index-BI2EU3hC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BSy0noke.js → index-BJt6sNTF.js} +1 -1
- package/src/assets/web-panel/assets/{index-oe9ZPRtQ.js → index-BLLSLAC3.js} +1 -1
- package/src/assets/web-panel/assets/{index-C6epsHef.js → index-BLNgGXeg.js} +1 -1
- package/src/assets/web-panel/assets/{index-COSyGm80.js → index-BO-yo7Jv.js} +1 -1
- package/src/assets/web-panel/assets/index-BT2s_zxU.js +1 -0
- package/src/assets/web-panel/assets/{index-DGAK9Dj4.js → index-BiAcVeea.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjgZRX0_.js → index-C2SoMbLc.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQ5FVEji.js → index-C5XUilwu.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bt1j0mjJ.js → index-C5zhjact.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKaue5Pv.js → index-CBeASfAD.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bz5_6E63.js → index-CD7UjlnE.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cr_shi_7.js → index-CDq8IVvv.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dkuecn17.js → index-CE-gpaY9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkhudJH0.js → index-CL6wt2JN.js} +1 -1
- package/src/assets/web-panel/assets/{index-kGzPbvry.js → index-CLu3Oyef.js} +1 -1
- package/src/assets/web-panel/assets/{index-D365qmj8.js → index-C_WWTpLE.js} +1 -1
- package/src/assets/web-panel/assets/{index-Deqod8La.js → index-CbcHBDYj.js} +1 -1
- package/src/assets/web-panel/assets/{index-DwAiu9LT.js → index-CguUaiiY.js} +1 -1
- package/src/assets/web-panel/assets/{index-WDQkyh-E.js → index-Ci1-8q-g.js} +1 -1
- package/src/assets/web-panel/assets/{index-BNEG9-EK.js → index-CvMZxZOn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CnfR7qmj.js → index-D6Nkerss.js} +1 -1
- package/src/assets/web-panel/assets/{index-CdiNFWPp.js → index-DEzYXMgc.js} +1 -1
- package/src/assets/web-panel/assets/{index--SCX46Az.js → index-DF0hXW5L.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhZkMMey.js → index-DGAjS_D9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CD9ml9ZJ.js → index-DO6mf95c.js} +1 -1
- package/src/assets/web-panel/assets/index-DOTL6NDc.js +1 -0
- package/src/assets/web-panel/assets/{index-DNN-xBWV.js → index-DUyhvh0L.js} +1 -1
- package/src/assets/web-panel/assets/{index-ycBlRXAf.js → index-DW18L-o6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CAzMdnkI.js → index-Dc-5Ulgt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzeRvhia.js → index-DvUlrMy-.js} +3 -3
- package/src/assets/web-panel/assets/{index-BdORz0iZ.js → index-FsYDYVUk.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmeO_DfK.js → index-GVbsyUQm.js} +1 -1
- package/src/assets/web-panel/assets/{index-BzIDfObk.js → index-noQc_RpT.js} +1 -1
- package/src/assets/web-panel/assets/{index-14gri6Vh.js → index-rR060KAF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DhFyStIG.js → index-xaZX6ZDL.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DHRWNV-y.js → initDefaultProps-PIetywTX.js} +1 -1
- package/src/assets/web-panel/assets/{motion-MJ2jhdVO.js → motion-gQJEK3wO.js} +1 -1
- package/src/assets/web-panel/assets/{move-Bly0QFE5.js → move-ML1nRxts.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DkoMB0pZ.js → omit-wUWsw3YL.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-9M-gsXIc.js → pickAttrs-A0Wlomih.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-Bnht1xci.js → placementArrow-C5RKYdxT.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-B0Cn1i64.js → responsiveObserve-DIxNVSJl.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BdI4DDyM.js → slide-B-tNesVu.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DdBktcMD.js → statusUtils-CftzO200.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-aYEwS4Pw.js → styleChecker-CMgvWu90.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-y8cUyKiP.js → useFlexGapSupport-sxqoDNhZ.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Bk1SrPFp.js → useFs-CowEXz4v.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-Dp04zAB3.js → usePersonalDataHub-6ISRG7V-.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-B52a38TC.js → vnode-C2mnXfmw.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DFwyL43U.js → zoom-DMsur0jx.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +66 -28
- package/src/harness/mcp-client.js +48 -2
- package/src/lib/agent-core.js +2 -0
- package/src/lib/llm-pricing.js +9 -0
- package/src/lib/mcp-client.js +1 -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/repl/agent-repl.js +427 -23
- package/src/repl/config-summary.js +59 -0
- package/src/repl/conversation-export.js +133 -0
- package/src/repl/doctor-status.js +114 -0
- package/src/repl/memory-status.js +45 -0
- package/src/repl/permissions-status.js +51 -0
- package/src/repl/recent-sessions.js +46 -0
- package/src/repl/session-cost.js +216 -0
- 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/MobileProjects-mAJsyk7U.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-6clRu-J6.js +0 -7
- package/src/assets/web-panel/assets/Projects-DO25SEFT.js +0 -1
- package/src/assets/web-panel/assets/devWarning-mhGhHpNs.js +0 -1
- package/src/assets/web-panel/assets/index-DGBjZXvW.js +0 -1
- package/src/assets/web-panel/assets/index-DujMkFuc.js +0 -1
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,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 {
|
|
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:
|
|
248
|
-
//
|
|
249
|
-
// agentLoop call via chatFn.
|
|
250
|
-
|
|
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
|
-
|
|
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
|
-
|
|
818
|
-
|
|
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
|
-
|
|
829
|
-
//
|
|
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
|
-
|
|
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("/
|
|
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 {
|
|
1301
|
-
|
|
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
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
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 {
|