chainlesschain 0.162.30 → 0.162.32
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/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps-CsNttUU7.js → AIOps-Cg_uWAVl.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-lgohjckQ.js → ActionButton-DSFtQ1c2.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-ccV3LAca.js → Analytics-BMxpkw8y.js} +3 -3
- package/src/assets/web-panel/assets/AppLayout-tgVxlmsx.js +9 -0
- package/src/assets/web-panel/assets/{Audit-B1gFM5U9.js → Audit-DwzGllcp.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BeWE3ERo.js → Backup-BG28Y2MV.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CDkPsNG2.js → BaseInput-TXthbazl.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-ztb9ia6e.js → Chat-D096SxaD.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-Dlw_6n3M.js → ChatBubbleRenderer-PIx0Eu9I.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BcfRBlIY.js → Checkbox-Czttw1JS.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DOs99xkr.js → Codegen-DZtMgv4q.js} +1 -1
- package/src/assets/web-panel/assets/{Col-D1X6tYlj.js → Col-D3DnfExY.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DTksIWtz.js → Community-Bj5AdwqY.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DIJtAYBO.js → Compact-BQ8Zszub.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BBf7LF_k.js → Compliance-DXacb34n.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-UBPXQ40s.js → Cowork-BgMUBTkw.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-CkRm1jPB.js → Cron-fqBWOqlN.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-qALlTl7e.js → Crosschain-E4oa1MWy.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CqyqVS6E.js → DID-pwgfYZaV.js} +2 -2
- package/src/assets/web-panel/assets/Dashboard-n8mdLFIR.js +3 -0
- package/src/assets/web-panel/assets/{Dropdown-Cb5UzbSZ.js → Dropdown--6DYqxk7.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CarBq8Fk.js → EmailListRenderer-CkjQluz3.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CSiGXaZz.js → FamilyGuardDashboard-u-QTQ-OC.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DUxhVoBN.js → Federation-D219M5Qc.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BoMQpkhx.js → FormItemContext-BBU_aopC.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DTVqC_CX.js → GenericCardRenderer-pTMCIHcM.js} +1 -1
- package/src/assets/web-panel/assets/{Git-C_XuPtK5.js → Git-ClcCARWt.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BZyqlqz-.js → Governance-CvUi3I93.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DdZVUimI.js → Inference-DT-a4pVg.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-IzZ-jnCn.js → KnowledgeGraph-DHMs2LY8.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-koTK6eNc.js → Logs-D2s4eV1N.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-6zpJ1L8n.js → Marketplace-YC5-fx-6.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Ywc4IVks.js → McpTools-7JHTEC4T.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-C_zB9dUa.js → Memory-BudotVLD.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-Nc05r24L.js → MobileBridge-CAiRyLVU.js} +2 -2
- package/src/assets/web-panel/assets/{MobileProjects-BJGxL526.js → MobileProjects-CrJJOCFw.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-Im7SIcz1.js → Mtc-d0iY0CeK.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-BFFzvzMD.js → MtcAudit-aI2cG1UP.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-CcNEbycq.js → Multisig-4bF70khG.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CDH6OTXN.js → NLProgramming-CwLib1S7.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Dqg3QXcU.js → Notes-Wt7AuFRU.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-CDVmK1eU.js → NotificationSettings-D081vV_7.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-DCPei1L9.js +1 -0
- package/src/assets/web-panel/assets/{Organization-DJb9bRQS.js → Organization-BNEsUNdP.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CK7Q5dje.js → Overflow-B_1iUXDD.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CJIyYfwc.js → P2P-Dbc-kNwJ.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-uqRULcuw.js → PdhVaultBrowser-D8Xh289k.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-Crvwt6bq.js → Permissions-C77mM6-n.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-DcN5OWzg.js → PersonalDataHub-Dj0J3r_K.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-DfWJvvJW.js → Pipeline-B6F0WQ2C.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DepD0S3v.js → Privacy-eDKOkyyq.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-B7OKhH27.js → ProjectInit-DAWwhr5_.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BJ4ueRFv.js → ProjectSettings-DwdK8k6I.js} +2 -2
- package/src/assets/web-panel/assets/Projects-Cb3p5QAP.js +1 -0
- package/src/assets/web-panel/assets/{Providers-Dl0FT1S3.js → Providers--DcYxQfN.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-V2hYLhfp.js → QuickAsk-DU268niT.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-8Kaiodgv.js → Recommend-ChnflhV1.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-CsxB3JGg.js → Reputation-DSsY3bQG.js} +1 -1
- package/src/assets/web-panel/assets/{Row-6-x7tEYq.js → Row-Zb-EjmgQ.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Buv6f5tw.js → RssFeed-CGLiixZB.js} +3 -3
- package/src/assets/web-panel/assets/{Search-ABrDz84n.js → Search-Dhr_po-U.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DqOJmz18.js → Security-GMYNhGsR.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Cq4Tda3q.js → Services-DiOpnVY0.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-n74QlyYq.js → Skeleton-DG3ez6ME.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CC0iozL5.js → Skills-DZGptytP.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-hwRgJ99Z.js → Sla-CtGpE3xA.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-B6Bs6_-8.js → SpeechSettings-DQFw6Cf9.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CTp2dZ0z.js → SyncSettings-C8X78RpX.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-D70Lis6S.js → Tasks-DtVkhWCV.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Cags0ssw.js → Templates-SF9_ZWsV.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BxCMzzGt.js → Tenant-BbIQSVZz.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-v05SDqHd.js → Terminal-DKr5zDwu.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BLUDHbBL.js → TimelineRenderer-BtLaNaWr.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-D-xKLJYv.js → Tokens-CfYbk2NG.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-B47tVIbH.js → Trigger-BLX_XDP0.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DmRU9kfs.js → Trust-BWxUv9PR.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DzgSGs-c.js → UkeySign-DRwTyQD4.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-C6qu58up.js → VideoEditing-BsC4VOSo.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Dh8ZWx8f.js → Wallet-CSsO1NJU.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DFHOVuAY.js → WebAuthn-z1MxiFzS.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-B_fyQ3Y_.js → WorkflowEditor-B1vV7uuJ.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BR-WxnCQ.js → chat-C0NJRaL2.js} +1 -1
- package/src/assets/web-panel/assets/{colors-C-6RysQe.js → colors-CHRiteWF.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-B_9_SCKN.js → compact-item-2XmBBKPD.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-D6rklIbE.js → createContext-DkedHC38.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DmNpkOdC.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BrotgSvd.js → hasIn-Bpn9Xrlw.js} +1 -1
- package/src/assets/web-panel/assets/index-7nAysteg.js +1 -0
- package/src/assets/web-panel/assets/{index-MCmNzIC7.js → index-B5NGWgHp.js} +1 -1
- package/src/assets/web-panel/assets/{index-GzuCTHVZ.js → index-BItcSqan.js} +3 -3
- package/src/assets/web-panel/assets/index-BKWSQilQ.js +1 -0
- package/src/assets/web-panel/assets/{index-DTCUOKu9.js → index-BN068mCR.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bv9BrnD2.js → index-BOsIgPge.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfqUsPl2.js → index-BYUd69vM.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cn21XmDt.js → index-BYmwEaIk.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWmJukRW.js → index-BZ1gOoiG.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bwkg_EJk.js → index-BfY9U3X5.js} +1 -1
- package/src/assets/web-panel/assets/{index-MBOwmoOi.js → index-BveL_4n3.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJ70GAW2.js → index-CCg6ZY4t.js} +1 -1
- package/src/assets/web-panel/assets/{index-B85rQNYG.js → index-CJOoo72F.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cn5ghmbB.js → index-CToQxpWz.js} +1 -1
- package/src/assets/web-panel/assets/{index-rWiOF7Iu.js → index-CWgWrrWs.js} +1 -1
- package/src/assets/web-panel/assets/{index-PzM_GlKb.js → index-CdR7RfRP.js} +1 -1
- package/src/assets/web-panel/assets/{index-ZehgEQYa.js → index-Cljnfuxu.js} +1 -1
- package/src/assets/web-panel/assets/{index-BsDNNDBN.js → index-CxvA72CP.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6KqyxG1.js → index-CyJpmSHZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-E_5VXq8H.js → index-D7U411hK.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJgp_QFo.js → index-D9mNfpxi.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTpElYJs.js → index-DAFLFMXQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMnomft7.js → index-DAeHmElB.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2yXH6vy.js → index-DDy_RDjs.js} +1 -1
- package/src/assets/web-panel/assets/{index-kkjq_hwC.js → index-DE5Qm9UI.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTh0fWI4.js → index-DM9JrnYi.js} +1 -1
- package/src/assets/web-panel/assets/{index-DigjvHuo.js → index-DMbF-Euw.js} +1 -1
- package/src/assets/web-panel/assets/{index-DkpDFJRn.js → index-DUBsq_1G.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIiCIC2j.js → index-De49R7TX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CsWVDOd2.js → index-De5vOO9V.js} +1 -1
- package/src/assets/web-panel/assets/{index-CAfRNHna.js → index-Dk7P-q3n.js} +1 -1
- package/src/assets/web-panel/assets/{index-CdDmzoPE.js → index-DryKGM_t.js} +1 -1
- package/src/assets/web-panel/assets/{index-CTQkYbir.js → index-DtU4qZRF.js} +1 -1
- package/src/assets/web-panel/assets/{index-CK8YwdNd.js → index-NuBsCRaR.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaLhL3Tj.js → index-Sk3-3tKa.js} +1 -1
- package/src/assets/web-panel/assets/{index-CTpxOc5s.js → index-alGjpoM1.js} +1 -1
- package/src/assets/web-panel/assets/{index-CrGp-4E2.js → index-cfSUlOfY.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbRl_gIW.js → index-i4W_EAuh.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCWzUY8K.js → index-uHGxyZtQ.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C2v_L5na.js → initDefaultProps-DlDE-QgI.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DNDqGbfr.js → motion-CodUbIRF.js} +1 -1
- package/src/assets/web-panel/assets/{move-xvpQ_6hJ.js → move-DaLwsHeR.js} +1 -1
- package/src/assets/web-panel/assets/{omit-Cb0FsfrO.js → omit-DdVg-3rL.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BxhYpnum.js → pickAttrs-KLR1EVCo.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-B3soaW4h.js → placementArrow-ChV7HvNw.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-B-eRSLvd.js → responsiveObserve-BB_A8dBt.js} +1 -1
- package/src/assets/web-panel/assets/{slide--cM2ZOx-.js → slide-Bc1tQnIK.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DjBhfi8Q.js → statusUtils-CgrveSb0.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-C30mMh8o.js → styleChecker-vXAYhhjz.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-f7y2Qlzs.js → useFlexGapSupport-BCIMPfq9.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-iTCXoLoZ.js → useFs-DMZGdr6G.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BH0RXmVF.js → usePersonalDataHub-118tWI_Z.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DQtmeDXM.js → vnode-Z7O2Y7JP.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-vw50zkLZ.js → zoom-BXym6zmD.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +333 -1
- package/src/commands/ask.js +35 -1
- package/src/commands/checkpoint.js +439 -0
- package/src/commands/compact.js +150 -0
- package/src/commands/cost.js +114 -0
- package/src/commands/goal.js +417 -0
- package/src/commands/hub.js +7 -0
- package/src/commands/session.js +22 -2
- package/src/harness/prompt-compressor.js +71 -1
- package/src/index.js +8 -0
- package/src/lib/agent-core.js +1 -0
- package/src/lib/checkpoint-store.js +523 -0
- package/src/lib/file-checkpoint.js +300 -0
- package/src/lib/goal-context.js +87 -0
- package/src/lib/goal-store.js +308 -0
- package/src/lib/llm-pricing.js +227 -0
- package/src/lib/personal-data-hub-wiring.js +30 -0
- package/src/lib/recent-session.js +72 -0
- package/src/lib/session-picker.js +68 -0
- package/src/repl/agent-repl.js +101 -9
- package/src/repl/chat-repl.js +16 -1
- package/src/runtime/agent-core.js +313 -32
- package/src/runtime/fallback-model.js +109 -0
- package/src/runtime/file-ref-expander.js +258 -0
- package/src/runtime/headless-runner.js +601 -0
- package/src/runtime/headless-stream.js +315 -0
- package/src/runtime/policies/agent-policy.js +7 -0
- package/src/runtime/quiet-stdout.js +35 -0
- package/src/runtime/system-prompt.js +60 -0
- package/src/assets/web-panel/assets/AppLayout-B0hl5cPk.js +0 -9
- package/src/assets/web-panel/assets/Dashboard-XlMpT7K_.js +0 -3
- package/src/assets/web-panel/assets/OrderTableRenderer-Bg0bkfjR.js +0 -1
- package/src/assets/web-panel/assets/Projects-Dl_hPdhU.js +0 -1
- package/src/assets/web-panel/assets/devWarning-BiN5HELJ.js +0 -1
- package/src/assets/web-panel/assets/index-BhxiT2LJ.js +0 -1
- package/src/assets/web-panel/assets/index-DBNSZ2oz.js +0 -1
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* llm-pricing — turn token counts into estimated $ cost.
|
|
3
|
+
*
|
|
4
|
+
* Layers a price table on top of the token roll-ups produced by
|
|
5
|
+
* `session-usage.js`, so `cc cost` can show spend per provider/model.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: these rates are ESTIMATES of public list prices (USD per 1M
|
|
8
|
+
* tokens) and drift over time. Local providers (ollama / local) are free.
|
|
9
|
+
* An unmatched model is reported as `matched:false` with zero cost rather
|
|
10
|
+
* than guessed — the caller surfaces it as "unpriced" so the number is never
|
|
11
|
+
* silently wrong. Override or extend PRICE_TABLE below as prices change.
|
|
12
|
+
*
|
|
13
|
+
* Rate shape: { in: <USD per 1M input tokens>, out: <USD per 1M output> }.
|
|
14
|
+
* Matching is provider-scoped, longest-pattern-first, case-insensitive
|
|
15
|
+
* substring against the model id.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** Providers whose models run locally and cost nothing to call. */
|
|
19
|
+
export const FREE_PROVIDERS = Object.freeze([
|
|
20
|
+
"ollama",
|
|
21
|
+
"local",
|
|
22
|
+
"llamacpp",
|
|
23
|
+
"mediapipe",
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Per-provider model price patterns. Each entry: { match, in, out }.
|
|
28
|
+
* Order within a provider matters — more specific patterns first so
|
|
29
|
+
* "gpt-4o-mini" is not shadowed by "gpt-4o".
|
|
30
|
+
*/
|
|
31
|
+
export const PRICE_TABLE = Object.freeze({
|
|
32
|
+
anthropic: [
|
|
33
|
+
{ match: "opus", in: 15, out: 75 },
|
|
34
|
+
{ match: "sonnet", in: 3, out: 15 },
|
|
35
|
+
{ match: "haiku", in: 1, out: 5 },
|
|
36
|
+
],
|
|
37
|
+
openai: [
|
|
38
|
+
{ match: "gpt-4o-mini", in: 0.15, out: 0.6 },
|
|
39
|
+
{ match: "gpt-4o", in: 2.5, out: 10 },
|
|
40
|
+
{ match: "gpt-4-turbo", in: 10, out: 30 },
|
|
41
|
+
{ match: "gpt-4", in: 30, out: 60 },
|
|
42
|
+
{ match: "gpt-3.5", in: 0.5, out: 1.5 },
|
|
43
|
+
{ match: "o1-mini", in: 1.1, out: 4.4 },
|
|
44
|
+
{ match: "o1", in: 15, out: 60 },
|
|
45
|
+
],
|
|
46
|
+
deepseek: [
|
|
47
|
+
{ match: "reasoner", in: 0.55, out: 2.19 },
|
|
48
|
+
{ match: "chat", in: 0.27, out: 1.1 },
|
|
49
|
+
],
|
|
50
|
+
// Volcengine Doubao — rough USD conversion of public RMB list pricing.
|
|
51
|
+
volcengine: [
|
|
52
|
+
{ match: "seed-1-6", in: 0.11, out: 0.28 },
|
|
53
|
+
{ match: "seed", in: 0.11, out: 0.28 },
|
|
54
|
+
{ match: "pro", in: 0.11, out: 0.28 },
|
|
55
|
+
{ match: "lite", in: 0.04, out: 0.08 },
|
|
56
|
+
{ match: "doubao", in: 0.11, out: 0.28 },
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const round = (n, dp = 6) => {
|
|
61
|
+
const f = Math.pow(10, dp);
|
|
62
|
+
return Math.round((Number(n) + Number.EPSILON) * f) / f;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Merge user-supplied price overrides (typically `config.llm.pricing`) onto the
|
|
67
|
+
* built-in table. Override shape mirrors PRICE_TABLE:
|
|
68
|
+
* { "<provider>": [ { match: "<substr>", in: <num>, out: <num> }, ... ] }
|
|
69
|
+
*
|
|
70
|
+
* Per provider, a user entry whose `match` equals a built-in pattern REPLACES
|
|
71
|
+
* it; brand-new patterns are prepended so they win ties; unknown providers are
|
|
72
|
+
* added. Malformed entries (missing match / non-numeric rate) are skipped so a
|
|
73
|
+
* bad config line can't crash cost reporting. Returns a new table; never
|
|
74
|
+
* mutates PRICE_TABLE or the input.
|
|
75
|
+
*
|
|
76
|
+
* @param {object} [overrides]
|
|
77
|
+
* @param {object} [base=PRICE_TABLE]
|
|
78
|
+
* @returns {object} merged price table
|
|
79
|
+
*/
|
|
80
|
+
export function mergePricing(overrides, base = PRICE_TABLE) {
|
|
81
|
+
const merged = {};
|
|
82
|
+
for (const [p, entries] of Object.entries(base)) {
|
|
83
|
+
merged[p] = entries.map((e) => ({ ...e }));
|
|
84
|
+
}
|
|
85
|
+
if (!overrides || typeof overrides !== "object") return merged;
|
|
86
|
+
|
|
87
|
+
for (const [provider, rawEntries] of Object.entries(overrides)) {
|
|
88
|
+
if (!Array.isArray(rawEntries)) continue;
|
|
89
|
+
const valid = rawEntries
|
|
90
|
+
.filter(
|
|
91
|
+
(e) =>
|
|
92
|
+
e &&
|
|
93
|
+
typeof e.match === "string" &&
|
|
94
|
+
e.match.trim() &&
|
|
95
|
+
Number.isFinite(Number(e.in)) &&
|
|
96
|
+
Number.isFinite(Number(e.out)),
|
|
97
|
+
)
|
|
98
|
+
.map((e) => ({
|
|
99
|
+
match: e.match.toLowerCase(),
|
|
100
|
+
in: Number(e.in),
|
|
101
|
+
out: Number(e.out),
|
|
102
|
+
}));
|
|
103
|
+
if (valid.length === 0) continue;
|
|
104
|
+
const overridden = new Set(valid.map((v) => v.match));
|
|
105
|
+
const kept = (merged[provider] || []).filter(
|
|
106
|
+
(e) => !overridden.has(e.match.toLowerCase()),
|
|
107
|
+
);
|
|
108
|
+
merged[provider] = [...valid, ...kept];
|
|
109
|
+
}
|
|
110
|
+
return merged;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Look up the rate for a provider/model, or null if unpriced.
|
|
115
|
+
* @param {string} provider
|
|
116
|
+
* @param {string} model
|
|
117
|
+
* @param {object} [table=PRICE_TABLE] merged table from mergePricing()
|
|
118
|
+
* @returns {{ in:number, out:number, pattern:string }|null}
|
|
119
|
+
*/
|
|
120
|
+
export function lookupRate(provider, model, table = PRICE_TABLE) {
|
|
121
|
+
const p = String(provider || "").toLowerCase();
|
|
122
|
+
const m = String(model || "").toLowerCase();
|
|
123
|
+
if (FREE_PROVIDERS.includes(p)) {
|
|
124
|
+
return { in: 0, out: 0, pattern: "free" };
|
|
125
|
+
}
|
|
126
|
+
const entries = (table || PRICE_TABLE)[p];
|
|
127
|
+
if (!entries) return null;
|
|
128
|
+
// Longest pattern first so specific beats generic regardless of table order.
|
|
129
|
+
const sorted = [...entries].sort((a, b) => b.match.length - a.match.length);
|
|
130
|
+
for (const e of sorted) {
|
|
131
|
+
if (m.includes(e.match)) {
|
|
132
|
+
return { in: e.in, out: e.out, pattern: e.match };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Estimate cost for a single usage record.
|
|
140
|
+
* @returns {{ inputCost:number, outputCost:number, totalCost:number,
|
|
141
|
+
* currency:string, matched:boolean, free:boolean, rate:object|null }}
|
|
142
|
+
*/
|
|
143
|
+
export function estimateCost({
|
|
144
|
+
provider,
|
|
145
|
+
model,
|
|
146
|
+
inputTokens = 0,
|
|
147
|
+
outputTokens = 0,
|
|
148
|
+
table,
|
|
149
|
+
} = {}) {
|
|
150
|
+
const rate = lookupRate(provider, model, table);
|
|
151
|
+
if (!rate) {
|
|
152
|
+
return {
|
|
153
|
+
inputCost: 0,
|
|
154
|
+
outputCost: 0,
|
|
155
|
+
totalCost: 0,
|
|
156
|
+
currency: "USD",
|
|
157
|
+
matched: false,
|
|
158
|
+
free: false,
|
|
159
|
+
rate: null,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const inputCost = round((Number(inputTokens) / 1e6) * rate.in);
|
|
163
|
+
const outputCost = round((Number(outputTokens) / 1e6) * rate.out);
|
|
164
|
+
return {
|
|
165
|
+
inputCost,
|
|
166
|
+
outputCost,
|
|
167
|
+
totalCost: round(inputCost + outputCost),
|
|
168
|
+
currency: "USD",
|
|
169
|
+
matched: true,
|
|
170
|
+
free: rate.pattern === "free",
|
|
171
|
+
rate: { in: rate.in, out: rate.out, pattern: rate.pattern },
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Augment a session-usage aggregate ({ total, byModel:[...] }) with cost.
|
|
177
|
+
* Returns a new object — does not mutate the input.
|
|
178
|
+
*
|
|
179
|
+
* Each byModel row gains: cost, currency, matched. The returned `cost`
|
|
180
|
+
* top-level sums priced rows; `unpriced` lists provider/model rows that had
|
|
181
|
+
* usage but no matching rate (their tokens are excluded from `cost.totalCost`).
|
|
182
|
+
*/
|
|
183
|
+
export function priceRollup(aggregate, { table } = {}) {
|
|
184
|
+
const byModel = (aggregate?.byModel || []).map((row) => {
|
|
185
|
+
const est = estimateCost({
|
|
186
|
+
provider: row.provider,
|
|
187
|
+
model: row.model,
|
|
188
|
+
inputTokens: row.inputTokens,
|
|
189
|
+
outputTokens: row.outputTokens,
|
|
190
|
+
table,
|
|
191
|
+
});
|
|
192
|
+
return {
|
|
193
|
+
...row,
|
|
194
|
+
cost: est.totalCost,
|
|
195
|
+
inputCost: est.inputCost,
|
|
196
|
+
outputCost: est.outputCost,
|
|
197
|
+
currency: est.currency,
|
|
198
|
+
matched: est.matched,
|
|
199
|
+
free: est.free,
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
let totalCost = 0;
|
|
204
|
+
const unpriced = [];
|
|
205
|
+
for (const row of byModel) {
|
|
206
|
+
if (row.matched) {
|
|
207
|
+
totalCost = round(totalCost + row.cost);
|
|
208
|
+
} else if (row.totalTokens > 0) {
|
|
209
|
+
unpriced.push({
|
|
210
|
+
provider: row.provider,
|
|
211
|
+
model: row.model,
|
|
212
|
+
totalTokens: row.totalTokens,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
...aggregate,
|
|
219
|
+
byModel,
|
|
220
|
+
cost: {
|
|
221
|
+
totalCost,
|
|
222
|
+
currency: "USD",
|
|
223
|
+
unpricedCount: unpriced.length,
|
|
224
|
+
},
|
|
225
|
+
unpriced,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
@@ -263,11 +263,41 @@ async function initHub() {
|
|
|
263
263
|
// Fall back to rule-only — registry still works
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
// ADB one-click readiness for the social platforms (B站/微博/抖音/小红书/
|
|
267
|
+
// 头条/快手): each has a *AdbSync hub method that pulls from a rooted phone
|
|
268
|
+
// over USB. The probe runs one `adb devices` so readiness() can show
|
|
269
|
+
// "已连接手机,点一键采集" vs "请插上 root 手机" instead of the misleading
|
|
270
|
+
// snapshot guidance. Best-effort — never throws into the readiness probe.
|
|
271
|
+
const ADB_ONE_CLICK_NAMES = new Set([
|
|
272
|
+
"social-bilibili",
|
|
273
|
+
"social-weibo",
|
|
274
|
+
"social-douyin",
|
|
275
|
+
"social-xiaohongshu",
|
|
276
|
+
"social-toutiao",
|
|
277
|
+
"social-kuaishou",
|
|
278
|
+
]);
|
|
279
|
+
const adbReadinessProbe = async () => {
|
|
280
|
+
try {
|
|
281
|
+
const { listDevices } = await import("./host-adb-bridge.js");
|
|
282
|
+
const serials = await listDevices();
|
|
283
|
+
return {
|
|
284
|
+
deviceConnected: Array.isArray(serials) && serials.length > 0,
|
|
285
|
+
serial: serials && serials[0],
|
|
286
|
+
};
|
|
287
|
+
} catch (_e) {
|
|
288
|
+
return { deviceConnected: false };
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
266
292
|
const registry = new AdapterRegistry({
|
|
267
293
|
vault,
|
|
268
294
|
kgSink: kgSink ? kgSink.write.bind(kgSink) : null,
|
|
269
295
|
ragSink: ragSink ? ragSink.write.bind(ragSink) : null,
|
|
270
296
|
entityResolver,
|
|
297
|
+
adbReadiness: {
|
|
298
|
+
probe: adbReadinessProbe,
|
|
299
|
+
oneClickNames: ADB_ONE_CLICK_NAMES,
|
|
300
|
+
},
|
|
271
301
|
});
|
|
272
302
|
|
|
273
303
|
const engine = new AnalysisEngine({
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the most-recent session id across both stores (DB + JSONL).
|
|
3
|
+
*
|
|
4
|
+
* Shared by `cc agent --continue` and `cc session resume` (id-less form) so a
|
|
5
|
+
* user can pick up the last conversation with a one-liner — Claude-Code
|
|
6
|
+
* `claude --continue` parity.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { listSessions } from "./session-manager.js";
|
|
10
|
+
import { listJsonlSessions } from "../harness/jsonl-session-store.js";
|
|
11
|
+
import { feature } from "./feature-flags.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Merge both stores into a single newest-first, id-deduped list.
|
|
15
|
+
*
|
|
16
|
+
* @param {object} ctx bootstrap() context (may be null; db is optional)
|
|
17
|
+
* @param {object} [opts]
|
|
18
|
+
* @param {number} [opts.scan=20] how many recent rows to merge per store
|
|
19
|
+
* @returns {Array<object>} session rows (id, title, message_count, updated_at, _store)
|
|
20
|
+
*/
|
|
21
|
+
export function listRecentSessions(ctx, opts = {}) {
|
|
22
|
+
const scan = Math.max(1, opts.scan || 20);
|
|
23
|
+
const sessions = [];
|
|
24
|
+
|
|
25
|
+
if (ctx && ctx.db) {
|
|
26
|
+
try {
|
|
27
|
+
const db = ctx.db.getDatabase();
|
|
28
|
+
sessions.push(
|
|
29
|
+
...listSessions(db, { limit: scan }).map((s) => ({
|
|
30
|
+
...s,
|
|
31
|
+
_store: "db",
|
|
32
|
+
})),
|
|
33
|
+
);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
/* db unavailable — fall through to JSONL */
|
|
36
|
+
void err;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (feature("JSONL_SESSION")) {
|
|
41
|
+
try {
|
|
42
|
+
sessions.push(
|
|
43
|
+
...listJsonlSessions({ limit: scan }).map((s) => ({
|
|
44
|
+
...s,
|
|
45
|
+
_store: "jsonl",
|
|
46
|
+
})),
|
|
47
|
+
);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
void err;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Dedup by id (JSONL precedence via sort+seen), newest updated_at first.
|
|
54
|
+
const seen = new Set();
|
|
55
|
+
return sessions
|
|
56
|
+
.sort((a, b) => ((b.updated_at || "") > (a.updated_at || "") ? 1 : -1))
|
|
57
|
+
.filter((s) => {
|
|
58
|
+
if (!s || !s.id || seen.has(s.id)) return false;
|
|
59
|
+
seen.add(s.id);
|
|
60
|
+
return true;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {object} ctx bootstrap() context (may be null; db is optional)
|
|
66
|
+
* @param {object} [opts] forwarded to {@link listRecentSessions}
|
|
67
|
+
* @returns {string|null} the most-recently-updated session id, or null
|
|
68
|
+
*/
|
|
69
|
+
export function resolveMostRecentSessionId(ctx, opts = {}) {
|
|
70
|
+
const sessions = listRecentSessions(ctx, opts);
|
|
71
|
+
return sessions.length > 0 ? sessions[0].id : null;
|
|
72
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive "which session to resume?" picker — shared by `cc session resume`
|
|
3
|
+
* and `cc agent --resume/--continue` so both render the same list and apply the
|
|
4
|
+
* same fallbacks (Claude-Code `--resume` parity).
|
|
5
|
+
*
|
|
6
|
+
* Behaviour:
|
|
7
|
+
* - 0 sessions → { id: null } (caller decides how to report)
|
|
8
|
+
* - non-TTY / single / noPicker → most-recent, no prompt (claude --continue)
|
|
9
|
+
* - TTY + >1 sessions → inquirer `select`; Ctrl-C falls back to most-recent
|
|
10
|
+
*
|
|
11
|
+
* All non-determinism (the session list, TTY-ness, the prompt fn) is injectable
|
|
12
|
+
* via `deps` so the deterministic branches are unit-testable without a TTY.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import chalk from "chalk";
|
|
16
|
+
import { listRecentSessions } from "./recent-session.js";
|
|
17
|
+
|
|
18
|
+
/** Render one session row for the picker list. */
|
|
19
|
+
export function formatSessionChoice(s) {
|
|
20
|
+
const title = (s.title || "Untitled").slice(0, 50).padEnd(50);
|
|
21
|
+
const msgs = chalk.cyan(`${s.message_count ?? 0} msgs`);
|
|
22
|
+
const when = chalk.gray(s.updated_at || "");
|
|
23
|
+
const store = s._store === "jsonl" ? " " + chalk.yellow("[JSONL]") : "";
|
|
24
|
+
return {
|
|
25
|
+
name: `${title} ${msgs} ${when}${store}`,
|
|
26
|
+
value: s.id,
|
|
27
|
+
short: String(s.id).slice(0, 12),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolve which recent session to resume.
|
|
33
|
+
*
|
|
34
|
+
* @param {object} ctx bootstrap() context (db optional)
|
|
35
|
+
* @param {object} [opts]
|
|
36
|
+
* @param {string} [opts.message] picker prompt
|
|
37
|
+
* @param {boolean} [opts.noPicker] force most-recent, skip the prompt
|
|
38
|
+
* @param {number} [opts.scan] forwarded to listRecentSessions
|
|
39
|
+
* @param {object} [deps] { listRecentSessions, isTTY, select }
|
|
40
|
+
* @returns {Promise<{id:string|null, sessions:Array, picked:boolean}>}
|
|
41
|
+
*/
|
|
42
|
+
export async function pickRecentSession(ctx, opts = {}, deps = {}) {
|
|
43
|
+
const list = deps.listRecentSessions || listRecentSessions;
|
|
44
|
+
const isTTY =
|
|
45
|
+
deps.isTTY !== undefined ? deps.isTTY : Boolean(process.stdin.isTTY);
|
|
46
|
+
|
|
47
|
+
const sessions = list(ctx, { scan: opts.scan });
|
|
48
|
+
if (!sessions || sessions.length === 0) {
|
|
49
|
+
return { id: null, sessions: [], picked: false };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// No interactive prompt possible/wanted → newest wins (claude --continue).
|
|
53
|
+
if (!isTTY || sessions.length === 1 || opts.noPicker) {
|
|
54
|
+
return { id: sessions[0].id, sessions, picked: false };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const select = deps.select || (await import("@inquirer/prompts")).select;
|
|
58
|
+
try {
|
|
59
|
+
const id = await select({
|
|
60
|
+
message: opts.message || "Resume which session?",
|
|
61
|
+
choices: sessions.map(formatSessionChoice),
|
|
62
|
+
});
|
|
63
|
+
return { id, sessions, picked: true };
|
|
64
|
+
} catch {
|
|
65
|
+
// Ctrl-C / non-interactive environment → fall back to most recent.
|
|
66
|
+
return { id: sessions[0].id, sessions, picked: false };
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/repl/agent-repl.js
CHANGED
|
@@ -65,6 +65,9 @@ import {
|
|
|
65
65
|
agentLoop as coreAgentLoop,
|
|
66
66
|
formatToolArgs,
|
|
67
67
|
} from "../runtime/agent-core.js";
|
|
68
|
+
import { expandFileRefs } from "../runtime/file-ref-expander.js";
|
|
69
|
+
import { composeSystemPrompt } from "../runtime/system-prompt.js";
|
|
70
|
+
import { makeFallbackChatFn } from "../runtime/fallback-model.js";
|
|
68
71
|
|
|
69
72
|
/**
|
|
70
73
|
* Reference to the runtime DB for hook execution (set during startAgentRepl)
|
|
@@ -89,8 +92,18 @@ async function executeTool(name, args) {
|
|
|
89
92
|
*/
|
|
90
93
|
async function agentLoop(messages, options) {
|
|
91
94
|
const usageEvents = [];
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
// The REPL runs its own auto-compaction (after each turn, with metrics +
|
|
96
|
+
// persisted compact events), so opt out of the agent loop's in-loop
|
|
97
|
+
// compaction to avoid compacting the same history twice.
|
|
98
|
+
for await (const event of coreAgentLoop(messages, {
|
|
99
|
+
autoCompact: false,
|
|
100
|
+
...options,
|
|
101
|
+
})) {
|
|
102
|
+
if (event.type === "checkpoint") {
|
|
103
|
+
process.stdout.write(
|
|
104
|
+
chalk.gray(` ⎌ checkpoint ${event.id} (before ${event.tool})\n`),
|
|
105
|
+
);
|
|
106
|
+
} else if (event.type === "tool-executing") {
|
|
94
107
|
process.stdout.write(
|
|
95
108
|
chalk.gray(
|
|
96
109
|
` [${event.tool}] ${formatToolArgs(event.tool, event.args)}\n`,
|
|
@@ -150,6 +163,29 @@ export async function startAgentRepl(options = {}) {
|
|
|
150
163
|
let provider = options.provider || "ollama";
|
|
151
164
|
const baseUrl = options.baseUrl || "http://localhost:11434";
|
|
152
165
|
const apiKey = options.apiKey || null;
|
|
166
|
+
// Extra workspace roots (--add-dir): advertised in the system prompt and
|
|
167
|
+
// spanned by search_files.
|
|
168
|
+
const additionalDirectories = Array.isArray(options.additionalDirectories)
|
|
169
|
+
? options.additionalDirectories
|
|
170
|
+
: [];
|
|
171
|
+
// Snapshot the work tree before each mutating tool (git engine) so the user
|
|
172
|
+
// can `cc checkpoint restore` to just before any tool call.
|
|
173
|
+
const autoCheckpoint = options.autoCheckpoint === true;
|
|
174
|
+
|
|
175
|
+
// --fallback-model: retry a turn's LLM call once on a backup model when the
|
|
176
|
+
// primary errors out (overload / network). Built once; passed into every
|
|
177
|
+
// agentLoop call via chatFn. Undefined when no fallback configured.
|
|
178
|
+
const _fallbackChatFn = options.fallbackModel
|
|
179
|
+
? makeFallbackChatFn({
|
|
180
|
+
fallbackModel: options.fallbackModel,
|
|
181
|
+
onFallback: ({ from, to, error }) =>
|
|
182
|
+
logger.info(
|
|
183
|
+
chalk.yellow(
|
|
184
|
+
`[fallback] model "${from}" failed (${error}); retrying with "${to}"`,
|
|
185
|
+
),
|
|
186
|
+
),
|
|
187
|
+
})
|
|
188
|
+
: undefined;
|
|
153
189
|
|
|
154
190
|
// Bootstrap runtime (best-effort, DB not required)
|
|
155
191
|
let db = null;
|
|
@@ -299,7 +335,18 @@ export async function startAgentRepl(options = {}) {
|
|
|
299
335
|
}
|
|
300
336
|
|
|
301
337
|
const messages = [
|
|
302
|
-
{
|
|
338
|
+
{
|
|
339
|
+
role: "system",
|
|
340
|
+
// --system-prompt replaces the built-in prompt; --append-system-prompt
|
|
341
|
+
// extends it (parity with the headless runners).
|
|
342
|
+
content: composeSystemPrompt(
|
|
343
|
+
buildSystemPrompt(process.cwd(), { additionalDirectories }),
|
|
344
|
+
{
|
|
345
|
+
systemPrompt: options.systemPrompt,
|
|
346
|
+
appendSystemPrompt: options.appendSystemPrompt,
|
|
347
|
+
},
|
|
348
|
+
),
|
|
349
|
+
},
|
|
303
350
|
];
|
|
304
351
|
|
|
305
352
|
// Deep Agents Deploy Phase 1 — load agent bundle if --bundle provided.
|
|
@@ -684,8 +731,10 @@ export async function startAgentRepl(options = {}) {
|
|
|
684
731
|
|
|
685
732
|
if (trimmed === "/compact") {
|
|
686
733
|
if (_compressor && messages.length > 3) {
|
|
687
|
-
const { messages: compacted, stats } =
|
|
688
|
-
|
|
734
|
+
const { messages: compacted, stats } = await _compressor.compress(
|
|
735
|
+
messages,
|
|
736
|
+
{ preserveToolPairs: true },
|
|
737
|
+
);
|
|
689
738
|
messages.length = 0;
|
|
690
739
|
messages.push(...compacted);
|
|
691
740
|
recordCompressionMetric(stats, {
|
|
@@ -1418,8 +1467,28 @@ export async function startAgentRepl(options = {}) {
|
|
|
1418
1467
|
logger.verbose(`[hook] prompt rewritten by UserPromptSubmit hook`);
|
|
1419
1468
|
}
|
|
1420
1469
|
|
|
1470
|
+
// Expand @path file references into context blocks (Claude-Code parity),
|
|
1471
|
+
// so `review @src/x.js` injects the file contents. Typo'd paths are warned
|
|
1472
|
+
// about and left as-is.
|
|
1473
|
+
let userContent = effectivePrompt;
|
|
1474
|
+
try {
|
|
1475
|
+
const fileRefs = expandFileRefs(effectivePrompt, { cwd: process.cwd() });
|
|
1476
|
+
userContent = fileRefs.prompt;
|
|
1477
|
+
for (const w of fileRefs.warnings) {
|
|
1478
|
+
logger.info(chalk.yellow(`[@ref] ${w}`));
|
|
1479
|
+
}
|
|
1480
|
+
if (fileRefs.refs.length > 0) {
|
|
1481
|
+
const summary = fileRefs.refs
|
|
1482
|
+
.map((r) => `${r.rel}${r.kind === "dir" ? "/" : ""}`)
|
|
1483
|
+
.join(", ");
|
|
1484
|
+
logger.verbose(`[@ref] injected: ${summary}`);
|
|
1485
|
+
}
|
|
1486
|
+
} catch (err) {
|
|
1487
|
+
logger.verbose(`[@ref] expansion skipped: ${err.message}`);
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1421
1490
|
// Add user message
|
|
1422
|
-
messages.push({ role: "user", content:
|
|
1491
|
+
messages.push({ role: "user", content: userContent });
|
|
1423
1492
|
|
|
1424
1493
|
// Slot-filling: detect intent and fill missing parameters interactively
|
|
1425
1494
|
try {
|
|
@@ -1467,6 +1536,23 @@ export async function startAgentRepl(options = {}) {
|
|
|
1467
1536
|
try {
|
|
1468
1537
|
process.stdout.write("\n");
|
|
1469
1538
|
const iterationBudget = new IterationBudget({ owner: sessionId });
|
|
1539
|
+
// Bind a cross-session goal (cc goal) into this run, if one resolves.
|
|
1540
|
+
// Composes WITH defaultPrepareCall — never replaces it. Best-effort.
|
|
1541
|
+
let prepareCall = defaultPrepareCall;
|
|
1542
|
+
try {
|
|
1543
|
+
const { resolveActiveGoal } = await import("../lib/goal-store.js");
|
|
1544
|
+
const boundGoal = resolveActiveGoal({ sessionId });
|
|
1545
|
+
if (boundGoal) {
|
|
1546
|
+
const { goalPrepareCall, composePrepareCall } =
|
|
1547
|
+
await import("../lib/goal-context.js");
|
|
1548
|
+
prepareCall = composePrepareCall([
|
|
1549
|
+
defaultPrepareCall,
|
|
1550
|
+
goalPrepareCall(boundGoal),
|
|
1551
|
+
]);
|
|
1552
|
+
}
|
|
1553
|
+
} catch (_e) {
|
|
1554
|
+
/* goal binding is best-effort — fall back to defaultPrepareCall */
|
|
1555
|
+
}
|
|
1470
1556
|
const { content: response, usageEvents } = await agentLoop(messages, {
|
|
1471
1557
|
provider,
|
|
1472
1558
|
model: activeModel,
|
|
@@ -1476,9 +1562,13 @@ export async function startAgentRepl(options = {}) {
|
|
|
1476
1562
|
iterationBudget,
|
|
1477
1563
|
sessionId,
|
|
1478
1564
|
cwd: process.cwd(),
|
|
1479
|
-
|
|
1565
|
+
additionalDirectories,
|
|
1566
|
+
autoCheckpoint,
|
|
1567
|
+
checkpointSession: sessionId,
|
|
1568
|
+
prepareCall,
|
|
1480
1569
|
approvalGate: _approvalGate,
|
|
1481
1570
|
mcpClient: _bundleMcpClient || undefined,
|
|
1571
|
+
chatFn: _fallbackChatFn,
|
|
1482
1572
|
});
|
|
1483
1573
|
|
|
1484
1574
|
if (sessionId && usageEvents?.length) {
|
|
@@ -1557,8 +1647,10 @@ export async function startAgentRepl(options = {}) {
|
|
|
1557
1647
|
_compressor.shouldAutoCompact(messages)
|
|
1558
1648
|
) {
|
|
1559
1649
|
try {
|
|
1560
|
-
const { messages: compacted, stats } =
|
|
1561
|
-
|
|
1650
|
+
const { messages: compacted, stats } = await _compressor.compress(
|
|
1651
|
+
messages,
|
|
1652
|
+
{ preserveToolPairs: true },
|
|
1653
|
+
);
|
|
1562
1654
|
messages.length = 0;
|
|
1563
1655
|
messages.push(...compacted);
|
|
1564
1656
|
recordCompressionMetric(stats, {
|
package/src/repl/chat-repl.js
CHANGED
|
@@ -12,6 +12,7 @@ import readline from "readline";
|
|
|
12
12
|
import chalk from "chalk";
|
|
13
13
|
import { logger } from "../lib/logger.js";
|
|
14
14
|
import { BUILT_IN_PROVIDERS } from "../lib/llm-providers.js";
|
|
15
|
+
import { expandFileRefs } from "../runtime/file-ref-expander.js";
|
|
15
16
|
import {
|
|
16
17
|
streamOllama,
|
|
17
18
|
streamOpenAI,
|
|
@@ -153,8 +154,22 @@ export async function startChatRepl(options = {}) {
|
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
156
|
|
|
157
|
+
// Expand @path file references into context blocks (Claude-Code parity),
|
|
158
|
+
// so `summarize @notes.md` injects the file. The model sees the expanded
|
|
159
|
+
// content; the JSONL log keeps the original line for readability.
|
|
160
|
+
let userContent = trimmed;
|
|
161
|
+
try {
|
|
162
|
+
const fileRefs = expandFileRefs(trimmed, { cwd: process.cwd() });
|
|
163
|
+
userContent = fileRefs.prompt;
|
|
164
|
+
for (const w of fileRefs.warnings) {
|
|
165
|
+
logger.info(chalk.yellow(`[@ref] ${w}`));
|
|
166
|
+
}
|
|
167
|
+
} catch (err) {
|
|
168
|
+
logger.verbose?.(`[@ref] expansion skipped: ${err.message}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
156
171
|
// Add user message
|
|
157
|
-
messages.push({ role: "user", content:
|
|
172
|
+
messages.push({ role: "user", content: userContent });
|
|
158
173
|
|
|
159
174
|
// Stream the response
|
|
160
175
|
process.stdout.write(chalk.blue("ai> "));
|