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