chainlesschain 0.162.60 → 0.162.65
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-a2cSbSEu.js → AIOps-DjJf_QIn.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DwvSB5Pp.js → ActionButton-BT45g-KL.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BqaRaBDD.js → Analytics-CRaTHble.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-Beck7v8t.js → AppLayout-72r5TM1u.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-UtJhPdXJ.js → Audit-BNlvJ3Yc.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-HVZhcdll.js → Backup-Kuj0-vBg.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DRY_ZGmj.js → BaseInput-_pKOPRf4.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-D7Vuwfe2.js → Chat-CMNhGWK5.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DxJmwLv8.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-B406i7N1.js → Checkbox-B5R2TdAI.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BvTCqHi3.js → Codegen-69RAQ0Gi.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DRiyxTQP.js → Col-DlbssQEY.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DWmhxHQa.js → Community-DU3SAZIS.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DO1HBZEz.js → Compact-BqdNnAZv.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-D4j-VHwS.js → Compliance-D9a9-ihS.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BGBkWtat.js → Cowork-DWBtOBbU.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-Xa9PtMUQ.js → Cron-ClSuf90k.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-wVWs4lqN.js → Crosschain-BFjRKvpa.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DTkqiRuT.js → DID-BwBGRlMm.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-d9STUbrr.js → Dashboard-CHrXGmQ3.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CrdxS-C8.js → Dropdown-C24B5sk2.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-D78XHUEp.js → EmailListRenderer-DaXTSK5p.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-iAeSETIP.js → FamilyGuardDashboard-65d89G5t.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CTV1Sxqs.js → Federation-CkWdqmVs.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BtwNuQKK.js → FormItemContext-BV4W2nrT.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CdEgHjkl.js → GenericCardRenderer-D60KJ0_b.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BTo-PJr_.js → Git-BIKuoGvW.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DquOG94r.js → Governance-CKnJpq5X.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DDtcBxRB.js → Inference-C7G3YGeg.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-KUOmNj5C.js → KnowledgeGraph-D7fCUd4B.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-HKm7kRs7.js → Logs-C0unjcbC.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-IxrOcbFB.js → Marketplace-BzLlnyI8.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-D6a1LM3S.js → McpTools-DSKFRB1-.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-lFkD2ZuM.js → Memory-C_QrLAnt.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-xwuQTps5.js → MobileBridge-DBeaFERD.js} +2 -2
- package/src/assets/web-panel/assets/MobileProjects-C2L_RttC.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-BXpJGrjm.js → Mtc-B3Tdh6-l.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-CWttaim1.js → MtcAudit-B3O_EUvt.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-jKgTuVLS.js → Multisig--60rVmDj.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-xl4RDzQj.js → NLProgramming-D60vxATf.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DPBOvscE.js → Notes-D2gj2uFI.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-8TkIkRo3.js → NotificationSettings-D0DWHNlF.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-Dwa-XtoE.js → OrderTableRenderer-CNi1B7fH.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-CJ0xVwZM.js → Organization-BRcdFgAd.js} +3 -3
- package/src/assets/web-panel/assets/{Overflow-V7VuUslt.js → Overflow-C3_Oap7v.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BxuccEGq.js → P2P-DEbZ93QW.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DaP2Q5kU.js → PdhVaultBrowser-DN_pmo2N.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-CPJFF0zU.js → Permissions-CPj3C9o2.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Cmn2uiuw.js → PersonalDataHub-BZGupZzh.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-0zX89_iz.js → Pipeline-SMLW1BG7.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DROUg3XE.js → Privacy-o24SJ2no.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-c5KESOK4.js → ProjectInit-DxjAXD8f.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BfiCcnb_.js → ProjectSettings-DipynlqL.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BtZH5-Eh.js → Projects-CZ9egQ8r.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-C9Rr_dOk.js → Providers-x3p-wcab.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Du4p90W6.js → QuickAsk-CZ7beKFC.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-B-DQenTl.js → Recommend-VJCd2i9_.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-DvwlAVRZ.js → Reputation-pl12NmBF.js} +1 -1
- package/src/assets/web-panel/assets/{Row-rTnbvkP-.js → Row-NkSeo4Tb.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DwvsqWbB.js → RssFeed-B17vp67R.js} +3 -3
- package/src/assets/web-panel/assets/{Search-U_Xj5SvF.js → Search-Dij0_m6W.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CdjsVDQ8.js → Security-n9CSBX-9.js} +4 -4
- package/src/assets/web-panel/assets/{Services-DUd3mFXk.js → Services-bZOzqHdK.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CA2gCJmY.js → Skeleton-B23D5vJ-.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BIw7Rb-m.js → Skills-IXh-0mk0.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-vySPesC0.js → Sla-QEofxmdK.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-EniuTjBJ.js → SpeechSettings-u68R59ft.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DkrqbzYS.js → SyncSettings-B3tc986U.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-Cp2QxGrr.js +1 -0
- package/src/assets/web-panel/assets/{Templates-C2Kvn60U.js → Templates-CMWiWxiH.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-x6jVMx4D.js → Tenant-M8aPJ3C7.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-CK3zKjIE.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-BF6HAETd.js → TimelineRenderer-DF6aIS-d.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-B-KcVAin.js → Tokens-BcfMMw_e.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-B-Caiptm.js → Trigger-jIbNmxvm.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-_8sq7pJp.js → Trust-ChLil6CZ.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CLveUEgo.js → UkeySign-B1WzYAon.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-iVc9jxt9.js → VideoEditing-Dwm0LyCc.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-D1n5pidD.js → Wallet-DVyxsX-O.js} +3 -3
- package/src/assets/web-panel/assets/{WebAuthn-CA8kubXb.js → WebAuthn-WYPNy2Q7.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-BXWwd_fB.js → WorkflowEditor-Br3dCsmv.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DUNkQr1A.js → chat-fAKHY2HK.js} +1 -1
- package/src/assets/web-panel/assets/{colors-Dz5ozTcp.js → colors-BXqS-Bwi.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CZ5-JSLh.js → compact-item-BgCQhtW3.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CFxlcPug.js → createContext-weZBwqHy.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BpXdFCJ4.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CqWIkHJm.js → hasIn-Dx68UNFL.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5zhjact.js → index-5e-OAZOb.js} +1 -1
- package/src/assets/web-panel/assets/{index-xaZX6ZDL.js → index-B0rgvjX8.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUyhvh0L.js → index-B8-2rQdr.js} +1 -1
- package/src/assets/web-panel/assets/{index-3elHm6lI.js → index-B8zNZ_oH.js} +1 -1
- package/src/assets/web-panel/assets/{index-CD7UjlnE.js → index-B9NeNwHP.js} +1 -1
- package/src/assets/web-panel/assets/{index-CE-gpaY9.js → index-BC-4la9j.js} +1 -1
- package/src/assets/web-panel/assets/{index-CLu3Oyef.js → index-BSQEQCft.js} +1 -1
- package/src/assets/web-panel/assets/{index-CBeASfAD.js → index-BawcE_zG.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dc-5Ulgt.js → index-Bazltj8w.js} +1 -1
- package/src/assets/web-panel/assets/{index-DO6mf95c.js → index-BehvfmYd.js} +1 -1
- package/src/assets/web-panel/assets/{index-rR060KAF.js → index-BjsidvP5.js} +1 -1
- package/src/assets/web-panel/assets/{index-BiAcVeea.js → index-Bo4UTTla.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDq8IVvv.js → index-BqcH_mKR.js} +1 -1
- package/src/assets/web-panel/assets/index-BrPKR2RZ.js +1 -0
- package/src/assets/web-panel/assets/{index-8l5LLWxH.js → index-Bwv_UrNF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BLNgGXeg.js → index-C0ZjD3Ac.js} +1 -1
- package/src/assets/web-panel/assets/{index-GVbsyUQm.js → index-CEjLe8FJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BLLSLAC3.js → index-CJXZDwkf.js} +1 -1
- package/src/assets/web-panel/assets/index-CMEfvACO.js +1 -0
- package/src/assets/web-panel/assets/{index-CL6wt2JN.js → index-CX9cxRnU.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBq1ySIt.js → index-CfqzwaAV.js} +1 -1
- package/src/assets/web-panel/assets/{index-DW18L-o6.js → index-ChdeuOni.js} +1 -1
- package/src/assets/web-panel/assets/{index-FsYDYVUk.js → index-ClfP1Yax.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbcHBDYj.js → index-Co5cQnlv.js} +1 -1
- package/src/assets/web-panel/assets/{index-BI2EU3hC.js → index-CxfVwfub.js} +1 -1
- package/src/assets/web-panel/assets/{index-noQc_RpT.js → index-Cxsfc5Ou.js} +1 -1
- package/src/assets/web-panel/assets/{index-DF0hXW5L.js → index-DCYJDUab.js} +1 -1
- package/src/assets/web-panel/assets/{index-BO-yo7Jv.js → index-DG2KCc8h.js} +1 -1
- package/src/assets/web-panel/assets/{index-DEzYXMgc.js → index-DNF3aCJF.js} +1 -1
- package/src/assets/web-panel/assets/{index-CvMZxZOn.js → index-DXuz90bX.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ci1-8q-g.js → index-Dbv5btEU.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJt6sNTF.js → index-DgUC575c.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6Nkerss.js → index-G_wPnPoA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CguUaiiY.js → index-HIN85jl7.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5XUilwu.js → index-LxcdLeFj.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_WWTpLE.js → index-loaP_41H.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2SoMbLc.js → index-ohVNy7ua.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvUlrMy-.js → index-qUBHSW_3.js} +3 -3
- package/src/assets/web-panel/assets/{index-DGAjS_D9.js → index-u2U9t07r.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-PIetywTX.js → initDefaultProps-M7xH4eUK.js} +1 -1
- package/src/assets/web-panel/assets/{motion-gQJEK3wO.js → motion-BrJP4mFE.js} +1 -1
- package/src/assets/web-panel/assets/{move-ML1nRxts.js → move-BufxEuU9.js} +1 -1
- package/src/assets/web-panel/assets/{omit-wUWsw3YL.js → omit-s6dtQtFP.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-A0Wlomih.js → pickAttrs-BcYdIZqz.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-C5RKYdxT.js → placementArrow-Ds-3Hw3n.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DIxNVSJl.js → responsiveObserve-BRtrRTxl.js} +1 -1
- package/src/assets/web-panel/assets/{slide-B-tNesVu.js → slide-RJzqMQM4.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CftzO200.js → statusUtils-BuBhJXvr.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CMgvWu90.js → styleChecker-so8acGHq.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-sxqoDNhZ.js → useFlexGapSupport-BpkV467K.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CowEXz4v.js → useFs-lES1RctZ.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-6ISRG7V-.js → usePersonalDataHub-DdCNi4bA.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-C2mnXfmw.js → vnode-nAeEg_3h.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DMsur0jx.js → zoom-BWjRAfRy.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +40 -1
- package/src/commands/review.js +463 -0
- package/src/commands/terminal-setup.js +127 -0
- package/src/index.js +4 -0
- package/src/lib/cost-budget.js +109 -0
- package/src/lib/ide-context.js +50 -0
- package/src/lib/image-input.js +8 -2
- package/src/lib/llm-pricing.js +6 -0
- package/src/lib/personal-data-hub-wiring.js +16 -0
- package/src/lib/terminal-setup.js +209 -0
- package/src/repl/agent-repl.js +58 -1
- package/src/repl/tasks-status.js +82 -0
- package/src/runtime/agent-core.js +38 -3
- package/src/runtime/headless-runner.js +51 -3
- package/src/runtime/headless-stream.js +62 -4
- package/src/runtime/mcp-config.js +6 -0
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BS2q_hPX.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-BYr1D3WO.js +0 -1
- package/src/assets/web-panel/assets/Tasks-oyPnWRbw.js +0 -1
- package/src/assets/web-panel/assets/Terminal-C2nZbPBs.js +0 -3
- package/src/assets/web-panel/assets/devWarning-BMRVR8Xp.js +0 -1
- package/src/assets/web-panel/assets/index-BT2s_zxU.js +0 -1
- package/src/assets/web-panel/assets/index-DOTL6NDc.js +0 -1
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cost-budget — a hard USD spend cap for unattended agent runs
|
|
3
|
+
* (Claude-Code `--max-budget-usd` parity).
|
|
4
|
+
*
|
|
5
|
+
* Where IterationBudget caps the number of agent-loop turns, CostBudget caps the
|
|
6
|
+
* estimated dollar cost: it accumulates the per-call cost (via llm-pricing) as
|
|
7
|
+
* token-usage events arrive and reports when the cap is reached, so the runner
|
|
8
|
+
* can stop BEFORE making another paid LLM call. Because a call's cost is only
|
|
9
|
+
* known after it returns, a run may overshoot by at most one call — it never
|
|
10
|
+
* starts a new turn once over budget.
|
|
11
|
+
*
|
|
12
|
+
* Local/free providers (ollama, …) and unpriced models cost $0 here, so a cap
|
|
13
|
+
* can never trigger for them; `shouldWarnInactive()` lets the caller surface a
|
|
14
|
+
* one-time "cap inactive" notice instead of silently doing nothing.
|
|
15
|
+
*
|
|
16
|
+
* Pure + dependency-light (only llm-pricing) so it is unit-testable without a
|
|
17
|
+
* real agent loop.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { estimateCost } from "./llm-pricing.js";
|
|
21
|
+
|
|
22
|
+
const round = (n, dp = 6) => {
|
|
23
|
+
const f = Math.pow(10, dp);
|
|
24
|
+
return Math.round((Number(n) + Number.EPSILON) * f) / f;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Parse a `--max-budget-usd` value into a positive number, or null when unset. */
|
|
28
|
+
export function parseBudgetUsd(value) {
|
|
29
|
+
if (value == null || value === "") return null;
|
|
30
|
+
const n = Number(value);
|
|
31
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Invalid --max-budget-usd "${value}". Expected a positive number of US dollars.`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return n;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class CostBudget {
|
|
40
|
+
/**
|
|
41
|
+
* @param {object} opts
|
|
42
|
+
* @param {number|null} [opts.limitUsd] cap in USD; null/≤0 → disabled
|
|
43
|
+
* @param {object} [opts.table] merged price table (mergePricing output)
|
|
44
|
+
*/
|
|
45
|
+
constructor({ limitUsd = null, table = undefined } = {}) {
|
|
46
|
+
const lim = Number(limitUsd);
|
|
47
|
+
this.limitUsd = Number.isFinite(lim) && lim > 0 ? lim : null;
|
|
48
|
+
this.table = table;
|
|
49
|
+
this.spentUsd = 0;
|
|
50
|
+
this.priced = false; // priced ≥1 non-free usage record
|
|
51
|
+
this.sawUnpriced = false; // saw tokens we couldn't price
|
|
52
|
+
this.sawFree = false; // saw a free/local provider
|
|
53
|
+
this._warned = false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
enabled() {
|
|
57
|
+
return this.limitUsd != null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Fold one token-usage record into the running spend.
|
|
62
|
+
* @returns {object} the estimateCost() result for this record
|
|
63
|
+
*/
|
|
64
|
+
add({ provider, model, usage } = {}) {
|
|
65
|
+
const est = estimateCost({
|
|
66
|
+
provider,
|
|
67
|
+
model,
|
|
68
|
+
inputTokens: usage?.input_tokens || 0,
|
|
69
|
+
outputTokens: usage?.output_tokens || 0,
|
|
70
|
+
table: this.table,
|
|
71
|
+
});
|
|
72
|
+
const tokens = (usage?.input_tokens || 0) + (usage?.output_tokens || 0);
|
|
73
|
+
if (est.free) {
|
|
74
|
+
this.sawFree = true;
|
|
75
|
+
} else if (est.matched) {
|
|
76
|
+
this.spentUsd = round(this.spentUsd + est.totalCost);
|
|
77
|
+
this.priced = true;
|
|
78
|
+
} else if (tokens > 0) {
|
|
79
|
+
this.sawUnpriced = true;
|
|
80
|
+
}
|
|
81
|
+
return est;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** True once the running spend has reached/passed the cap. */
|
|
85
|
+
exceeded() {
|
|
86
|
+
return this.limitUsd != null && this.spentUsd >= this.limitUsd;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** USD left under the cap (Infinity when disabled). */
|
|
90
|
+
remaining() {
|
|
91
|
+
return this.limitUsd == null
|
|
92
|
+
? Infinity
|
|
93
|
+
: Math.max(0, round(this.limitUsd - this.spentUsd));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* True the FIRST time we can tell the cap can't bite — a cap was set but every
|
|
98
|
+
* usage so far has been free/local or unpriced, so spend stays $0. Lets the
|
|
99
|
+
* caller print a one-time "cap inactive" warning instead of a silent no-op.
|
|
100
|
+
*/
|
|
101
|
+
shouldWarnInactive() {
|
|
102
|
+
if (!this.enabled() || this._warned || this.priced) return false;
|
|
103
|
+
if (this.sawUnpriced || this.sawFree) {
|
|
104
|
+
this._warned = true;
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
package/src/lib/ide-context.js
CHANGED
|
@@ -278,6 +278,13 @@ export function hasIdeOpenDiff(mcp) {
|
|
|
278
278
|
* Run one blocking openDiff review in the connected IDE. Returns
|
|
279
279
|
* { outcome:"accepted", finalText|null } — the IDE wrote the file itself
|
|
280
280
|
* { outcome:"rejected" } — nothing was written
|
|
281
|
+
* { outcome:"changes-requested", comments, reviewedText }
|
|
282
|
+
* — the user annotated the diff with
|
|
283
|
+
* revision notes instead of
|
|
284
|
+
* accepting/rejecting; nothing was
|
|
285
|
+
* written and the caller should
|
|
286
|
+
* feed `comments` back to the agent
|
|
287
|
+
* so it revises and re-proposes.
|
|
281
288
|
* null — IDE unavailable / transport
|
|
282
289
|
* error / malformed reply → the
|
|
283
290
|
* caller falls back to its normal
|
|
@@ -309,10 +316,53 @@ export async function requestIdeDiffApproval(mcp, req = {}) {
|
|
|
309
316
|
finalText: typeof data.finalText === "string" ? data.finalText : null,
|
|
310
317
|
};
|
|
311
318
|
}
|
|
319
|
+
if (data?.outcome === "changes-requested") {
|
|
320
|
+
return {
|
|
321
|
+
outcome: "changes-requested",
|
|
322
|
+
comments: Array.isArray(data.comments) ? data.comments : [],
|
|
323
|
+
reviewedText:
|
|
324
|
+
typeof data.reviewedText === "string" ? data.reviewedText : null,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
312
327
|
if (data?.outcome === "rejected") return { outcome: "rejected" };
|
|
313
328
|
return null; // anything else is not a verdict — fail safe to fallback
|
|
314
329
|
}
|
|
315
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Render line-anchored review comments (from an openDiff "changes-requested"
|
|
333
|
+
* verdict) into a compact feedback block the agent can act on. Each comment is
|
|
334
|
+
* `{ line?, endLine?, lineText?, note }` with 0-based editor lines. Returns
|
|
335
|
+
* null when there is no actionable note. Pure — safe to unit-test.
|
|
336
|
+
*/
|
|
337
|
+
export function formatReviewComments(comments, { path: filePath } = {}) {
|
|
338
|
+
if (!Array.isArray(comments) || comments.length === 0) return null;
|
|
339
|
+
const lines = comments
|
|
340
|
+
.map((c) => {
|
|
341
|
+
if (!c || typeof c.note !== "string" || c.note.trim().length === 0) {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
const start = Number.isInteger(c.line) ? c.line + 1 : null; // 0→1-based
|
|
345
|
+
const end = Number.isInteger(c.endLine) ? c.endLine + 1 : start;
|
|
346
|
+
const where =
|
|
347
|
+
start != null
|
|
348
|
+
? end != null && end !== start
|
|
349
|
+
? `lines ${start}-${end}`
|
|
350
|
+
: `line ${start}`
|
|
351
|
+
: "(general)";
|
|
352
|
+
const anchor =
|
|
353
|
+
typeof c.lineText === "string" && c.lineText.trim().length > 0
|
|
354
|
+
? ` ⟪${c.lineText.trim().slice(0, 120)}⟫`
|
|
355
|
+
: "";
|
|
356
|
+
return ` • ${where}: ${c.note.trim()}${anchor}`;
|
|
357
|
+
})
|
|
358
|
+
.filter(Boolean);
|
|
359
|
+
if (lines.length === 0) return null;
|
|
360
|
+
const header = filePath
|
|
361
|
+
? `Review comments on ${filePath}:`
|
|
362
|
+
: "Review comments:";
|
|
363
|
+
return `${header}\n${lines.join("\n")}`;
|
|
364
|
+
}
|
|
365
|
+
|
|
316
366
|
// ─── Explicit @selection / @diagnostics at-mentions (Claude-Code parity) ────
|
|
317
367
|
//
|
|
318
368
|
// The ambient `<ide-context>` block above shares the selection on every turn.
|
package/src/lib/image-input.js
CHANGED
|
@@ -115,8 +115,14 @@ export function imageUrlBlockToAnthropic(block) {
|
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
/**
|
|
119
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Default vision model when none is configured — Volcengine Ark
|
|
120
|
+
* Doubao-Seed-2.0-lite (id `doubao-seed-2-0-lite-260215`, dated YYMMDD =
|
|
121
|
+
* 2026-02-15), a natively multimodal model (text/image/video; 2.0 has no
|
|
122
|
+
* separate "-vision-" SKU). Override via --vision-model or config.llm.visionModel
|
|
123
|
+
* to pin a different snapshot.
|
|
124
|
+
*/
|
|
125
|
+
export const DEFAULT_VISION_MODEL = "doubao-seed-2-0-lite-260215";
|
|
120
126
|
|
|
121
127
|
/**
|
|
122
128
|
* Resolve the effective LLM config for a run. When an image is attached, default
|
package/src/lib/llm-pricing.js
CHANGED
|
@@ -58,6 +58,12 @@ export const PRICE_TABLE = Object.freeze({
|
|
|
58
58
|
],
|
|
59
59
|
// Volcengine Doubao — rough USD conversion of public RMB list pricing.
|
|
60
60
|
volcengine: [
|
|
61
|
+
// Doubao Seed 2.0 family (2026, e.g. doubao-seed-2-0-lite-260215) is
|
|
62
|
+
// natively multimodal. Rates ≈ official CNY ÷ ~7.2 (lite ≤32k is 0.6/3.6
|
|
63
|
+
// CNY → $0.08/$0.50); the generic "seed" rate below underprices 2.0 output.
|
|
64
|
+
{ match: "seed-2-0-pro", in: 0.5, out: 2.5 },
|
|
65
|
+
{ match: "seed-2-0-lite", in: 0.08, out: 0.5 },
|
|
66
|
+
{ match: "seed-2-0-mini", in: 0.03, out: 0.3 },
|
|
61
67
|
{ match: "seed-1-6", in: 0.11, out: 0.28 },
|
|
62
68
|
{ match: "seed", in: 0.11, out: 0.28 },
|
|
63
69
|
{ match: "pro", in: 0.11, out: 0.28 },
|
|
@@ -57,6 +57,8 @@ const {
|
|
|
57
57
|
ZhihuAdapter,
|
|
58
58
|
BossZhipinAdapter,
|
|
59
59
|
CsdnAdapter,
|
|
60
|
+
DongchediAdapter,
|
|
61
|
+
TianyanchaAdapter,
|
|
60
62
|
DouyinAdapter,
|
|
61
63
|
XiaohongshuAdapter,
|
|
62
64
|
ToutiaoAdapter,
|
|
@@ -74,12 +76,18 @@ const {
|
|
|
74
76
|
KugouMusicAdapter,
|
|
75
77
|
IqiyiVideoAdapter,
|
|
76
78
|
TencentVideoAdapter,
|
|
79
|
+
XiguaVideoAdapter,
|
|
77
80
|
WeReadAdapter,
|
|
78
81
|
WpsDocAdapter,
|
|
79
82
|
TencentDocsAdapter,
|
|
80
83
|
BaiduNetdiskAdapter,
|
|
84
|
+
CamScannerDocAdapter,
|
|
85
|
+
IXiamenAdapter,
|
|
86
|
+
MeiyouAdapter,
|
|
87
|
+
TaxAdapter,
|
|
81
88
|
DingTalkPcAdapter,
|
|
82
89
|
FeishuPcAdapter,
|
|
90
|
+
WeWorkPcAdapter,
|
|
83
91
|
BaiduMapAdapter,
|
|
84
92
|
TencentMapAdapter,
|
|
85
93
|
JdAdapter,
|
|
@@ -529,6 +537,8 @@ async function initHub() {
|
|
|
529
537
|
ZhihuAdapter,
|
|
530
538
|
BossZhipinAdapter,
|
|
531
539
|
CsdnAdapter,
|
|
540
|
+
DongchediAdapter,
|
|
541
|
+
TianyanchaAdapter,
|
|
532
542
|
DouyinAdapter,
|
|
533
543
|
XiaohongshuAdapter,
|
|
534
544
|
ToutiaoAdapter,
|
|
@@ -546,12 +556,18 @@ async function initHub() {
|
|
|
546
556
|
KugouMusicAdapter,
|
|
547
557
|
IqiyiVideoAdapter,
|
|
548
558
|
TencentVideoAdapter,
|
|
559
|
+
XiguaVideoAdapter,
|
|
549
560
|
WeReadAdapter,
|
|
550
561
|
WpsDocAdapter,
|
|
551
562
|
TencentDocsAdapter,
|
|
552
563
|
BaiduNetdiskAdapter,
|
|
564
|
+
CamScannerDocAdapter,
|
|
565
|
+
IXiamenAdapter,
|
|
566
|
+
MeiyouAdapter,
|
|
567
|
+
TaxAdapter,
|
|
553
568
|
DingTalkPcAdapter,
|
|
554
569
|
FeishuPcAdapter,
|
|
570
|
+
WeWorkPcAdapter,
|
|
555
571
|
BaiduMapAdapter,
|
|
556
572
|
TencentMapAdapter,
|
|
557
573
|
JdAdapter,
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* terminal-setup — make Shift+Enter insert a newline in the agent REPL
|
|
3
|
+
* (Claude-Code `/terminal-setup` parity).
|
|
4
|
+
*
|
|
5
|
+
* cc's REPL is readline-based, so a bare Enter always submits. Multiline input
|
|
6
|
+
* is reached by ending a line with a continuation backslash (see
|
|
7
|
+
* repl-multiline.js). This module configures the terminal so that pressing
|
|
8
|
+
* Shift+Enter emits the byte sequence `<space>\<CR>` — which cc's
|
|
9
|
+
* backslash-continuation turns into a soft newline — giving the familiar
|
|
10
|
+
* "Shift+Enter = newline, Enter = send" editing without any REPL raw-mode hacks.
|
|
11
|
+
*
|
|
12
|
+
* Everything here is pure (detection + keybindings JSON shaping); the command
|
|
13
|
+
* (commands/terminal-setup.js) does the file I/O.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import os from "node:os";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The bytes Shift+Enter should send: space + backslash + carriage return. The
|
|
21
|
+
* leading space makes the trailing backslash satisfy repl-multiline's
|
|
22
|
+
* whitespace-gated continuation rule (so it is never mistaken for a Windows
|
|
23
|
+
* path), and the backslash is stripped back out when the line is joined.
|
|
24
|
+
*/
|
|
25
|
+
export const SHIFT_ENTER_SEQUENCE = " \\\r";
|
|
26
|
+
|
|
27
|
+
/** Identify the host terminal from environment variables. */
|
|
28
|
+
export function detectTerminal(env = process.env) {
|
|
29
|
+
const tp = String(env.TERM_PROGRAM || "").toLowerCase();
|
|
30
|
+
if (tp === "vscode" || env.VSCODE_PID || env.VSCODE_INJECTION) {
|
|
31
|
+
return { id: "vscode", name: "VS Code integrated terminal" };
|
|
32
|
+
}
|
|
33
|
+
if (tp === "iterm.app") return { id: "iterm2", name: "iTerm2" };
|
|
34
|
+
if (tp === "apple_terminal")
|
|
35
|
+
return { id: "apple-terminal", name: "Apple Terminal" };
|
|
36
|
+
if (tp === "wezterm") return { id: "wezterm", name: "WezTerm" };
|
|
37
|
+
if (env.WT_SESSION)
|
|
38
|
+
return { id: "windows-terminal", name: "Windows Terminal" };
|
|
39
|
+
return {
|
|
40
|
+
id: "unknown",
|
|
41
|
+
name: env.TERM_PROGRAM || env.TERM || "your terminal",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The VS Code keybinding that sends the Shift+Enter sequence in the terminal. */
|
|
46
|
+
export function vscodeKeybinding() {
|
|
47
|
+
return {
|
|
48
|
+
key: "shift+enter",
|
|
49
|
+
command: "workbench.action.terminal.sendSequence",
|
|
50
|
+
when: "terminalFocus",
|
|
51
|
+
args: { text: SHIFT_ENTER_SEQUENCE },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Per-OS path to VS Code's user keybindings.json. */
|
|
56
|
+
export function vscodeKeybindingsPath(
|
|
57
|
+
platform = process.platform,
|
|
58
|
+
env = process.env,
|
|
59
|
+
) {
|
|
60
|
+
const home = os.homedir();
|
|
61
|
+
if (platform === "win32") {
|
|
62
|
+
const appData = env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
63
|
+
return path.join(appData, "Code", "User", "keybindings.json");
|
|
64
|
+
}
|
|
65
|
+
if (platform === "darwin") {
|
|
66
|
+
return path.join(
|
|
67
|
+
home,
|
|
68
|
+
"Library",
|
|
69
|
+
"Application Support",
|
|
70
|
+
"Code",
|
|
71
|
+
"User",
|
|
72
|
+
"keybindings.json",
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return path.join(home, ".config", "Code", "User", "keybindings.json");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Strip JSONC (line + block comments, trailing commas) so a keybindings.json
|
|
80
|
+
* with comments can be parsed. Best-effort and string-aware enough for the
|
|
81
|
+
* common case; callers must tolerate a null parse.
|
|
82
|
+
*/
|
|
83
|
+
export function stripJsonc(text) {
|
|
84
|
+
let out = "";
|
|
85
|
+
const s = String(text || "");
|
|
86
|
+
let inStr = false;
|
|
87
|
+
let strCh = "";
|
|
88
|
+
let i = 0;
|
|
89
|
+
while (i < s.length) {
|
|
90
|
+
const c = s[i];
|
|
91
|
+
const n = s[i + 1];
|
|
92
|
+
if (inStr) {
|
|
93
|
+
out += c;
|
|
94
|
+
if (c === "\\") {
|
|
95
|
+
out += n ?? "";
|
|
96
|
+
i += 2;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (c === strCh) inStr = false;
|
|
100
|
+
i += 1;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (c === '"' || c === "'") {
|
|
104
|
+
inStr = true;
|
|
105
|
+
strCh = c;
|
|
106
|
+
out += c;
|
|
107
|
+
i += 1;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (c === "/" && n === "/") {
|
|
111
|
+
while (i < s.length && s[i] !== "\n") i += 1;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (c === "/" && n === "*") {
|
|
115
|
+
i += 2;
|
|
116
|
+
while (i < s.length && !(s[i] === "*" && s[i + 1] === "/")) i += 1;
|
|
117
|
+
i += 2;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
out += c;
|
|
121
|
+
i += 1;
|
|
122
|
+
}
|
|
123
|
+
// Drop trailing commas before } or ].
|
|
124
|
+
return out.replace(/,(\s*[}\]])/g, "$1");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Parse a JSONC keybindings array → array, or null when it cannot be parsed. */
|
|
128
|
+
export function parseKeybindings(text) {
|
|
129
|
+
const src = String(text || "").trim();
|
|
130
|
+
if (!src) return [];
|
|
131
|
+
try {
|
|
132
|
+
const v = JSON.parse(stripJsonc(src));
|
|
133
|
+
return Array.isArray(v) ? v : null;
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** True when an equivalent keybinding (key+command+when) already exists. */
|
|
140
|
+
export function hasKeybinding(arr, binding) {
|
|
141
|
+
return (Array.isArray(arr) ? arr : []).some(
|
|
142
|
+
(b) =>
|
|
143
|
+
b &&
|
|
144
|
+
b.key === binding.key &&
|
|
145
|
+
b.command === binding.command &&
|
|
146
|
+
(b.when || "") === (binding.when || ""),
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Textually append a keybinding before the closing `]`, preserving the file's
|
|
152
|
+
* existing comments/formatting. Returns the new file text, or null when `text`
|
|
153
|
+
* is non-empty but not a JSON array (caller should not overwrite blindly).
|
|
154
|
+
*/
|
|
155
|
+
export function appendKeybindingText(text, binding) {
|
|
156
|
+
const bindingJson = JSON.stringify(binding, null, 2)
|
|
157
|
+
.split("\n")
|
|
158
|
+
.map((l) => " " + l)
|
|
159
|
+
.join("\n")
|
|
160
|
+
.trimStart();
|
|
161
|
+
const src = String(text || "").trim();
|
|
162
|
+
if (!src || src === "[]") {
|
|
163
|
+
return `[\n ${bindingJson}\n]\n`;
|
|
164
|
+
}
|
|
165
|
+
const close = src.lastIndexOf("]");
|
|
166
|
+
if (close === -1) return null; // not an array
|
|
167
|
+
const before = src.slice(0, close).replace(/\s*$/, "");
|
|
168
|
+
const sep = before.endsWith("[") ? "" : ",";
|
|
169
|
+
return `${before}${sep}\n ${bindingJson}\n]\n`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Manual instructions for terminals cc cannot auto-configure. */
|
|
173
|
+
export function instructionsFor(id) {
|
|
174
|
+
const seq = "a space, then a backslash, then Enter";
|
|
175
|
+
const common = `Bind Shift+Enter to send ${seq} (cc turns a trailing "\\" into a newline).`;
|
|
176
|
+
switch (id) {
|
|
177
|
+
case "iterm2":
|
|
178
|
+
return [
|
|
179
|
+
common,
|
|
180
|
+
"iTerm2 → Settings → Profiles → Keys → Key Mappings → + :",
|
|
181
|
+
' Shortcut: Shift+Return Action: "Send Text" Text: \\ (then a literal Return)',
|
|
182
|
+
];
|
|
183
|
+
case "apple-terminal":
|
|
184
|
+
return [
|
|
185
|
+
common,
|
|
186
|
+
"Terminal → Settings → Profiles → Keyboard → + :",
|
|
187
|
+
' Key: Shift+Return Action: send string " \\015" (space, backslash, CR)',
|
|
188
|
+
];
|
|
189
|
+
case "windows-terminal":
|
|
190
|
+
return [
|
|
191
|
+
common,
|
|
192
|
+
"Windows Terminal → Settings → Actions (settings.json) → add:",
|
|
193
|
+
' { "command": { "action": "sendInput", "input": " \\\\\\r" }, "keys": "shift+enter" }',
|
|
194
|
+
];
|
|
195
|
+
case "wezterm":
|
|
196
|
+
return [
|
|
197
|
+
common,
|
|
198
|
+
"WezTerm (~/.wezterm.lua) keys:",
|
|
199
|
+
' { key="Enter", mods="SHIFT", action=wezterm.action.SendString(" \\\\\\r") }',
|
|
200
|
+
];
|
|
201
|
+
default:
|
|
202
|
+
return [
|
|
203
|
+
common,
|
|
204
|
+
"Most terminals let you remap a key to send custom text/bytes — point",
|
|
205
|
+
"Shift+Enter at the 3 bytes: space (0x20), backslash (0x5C), CR (0x0D).",
|
|
206
|
+
"Or just type a trailing \\ yourself to continue onto the next line.",
|
|
207
|
+
];
|
|
208
|
+
}
|
|
209
|
+
}
|
package/src/repl/agent-repl.js
CHANGED
|
@@ -75,7 +75,10 @@ import {
|
|
|
75
75
|
agentLoop as coreAgentLoop,
|
|
76
76
|
formatToolArgs,
|
|
77
77
|
killAllBackgroundShellTasks,
|
|
78
|
+
killBackgroundShellTask,
|
|
79
|
+
listBackgroundShellTasks,
|
|
78
80
|
} from "../runtime/agent-core.js";
|
|
81
|
+
import { formatBackgroundTasks } from "./tasks-status.js";
|
|
79
82
|
import { expandFileRefs } from "../runtime/file-ref-expander.js";
|
|
80
83
|
import { composeSystemPrompt } from "../runtime/system-prompt.js";
|
|
81
84
|
import {
|
|
@@ -833,6 +836,8 @@ export async function startAgentRepl(options = {}) {
|
|
|
833
836
|
"/statusline",
|
|
834
837
|
"/sub-agents",
|
|
835
838
|
"/task",
|
|
839
|
+
"/tasks",
|
|
840
|
+
"/terminal-setup",
|
|
836
841
|
"/vim",
|
|
837
842
|
],
|
|
838
843
|
getIdeOpenFiles: async () => {
|
|
@@ -1176,6 +1181,9 @@ export async function startAgentRepl(options = {}) {
|
|
|
1176
1181
|
logger.log(
|
|
1177
1182
|
` ${chalk.cyan("/vim")} Toggle vim-mode line editing (/vim [on|off]; Esc → NORMAL)`,
|
|
1178
1183
|
);
|
|
1184
|
+
logger.log(
|
|
1185
|
+
` ${chalk.cyan("/terminal-setup")} Bind Shift+Enter → newline (--apply for VS Code)`,
|
|
1186
|
+
);
|
|
1179
1187
|
logger.log(
|
|
1180
1188
|
` ${chalk.cyan("/statusline")} Context-usage line on/off (/statusline [on|off])`,
|
|
1181
1189
|
);
|
|
@@ -1249,6 +1257,9 @@ export async function startAgentRepl(options = {}) {
|
|
|
1249
1257
|
logger.log(
|
|
1250
1258
|
` ${chalk.cyan("/sub-agents")} Show active/completed sub-agents`,
|
|
1251
1259
|
);
|
|
1260
|
+
logger.log(
|
|
1261
|
+
` ${chalk.cyan("/tasks")} Show background shell tasks (kill <id> · kill-all)`,
|
|
1262
|
+
);
|
|
1252
1263
|
logger.log(
|
|
1253
1264
|
` ${chalk.cyan("/ide")} IDE bridge status (connected editor, tools, or why not)`,
|
|
1254
1265
|
);
|
|
@@ -1288,6 +1299,35 @@ export async function startAgentRepl(options = {}) {
|
|
|
1288
1299
|
return;
|
|
1289
1300
|
}
|
|
1290
1301
|
|
|
1302
|
+
// `/tasks` — user-facing view of the agent's background shell tasks
|
|
1303
|
+
// (run_shell run_in_background). Must precede the `/task` handler below,
|
|
1304
|
+
// which matches with startsWith("/task") and would otherwise swallow it.
|
|
1305
|
+
if (trimmed === "/tasks" || trimmed.startsWith("/tasks ")) {
|
|
1306
|
+
const rest = trimmed.slice("/tasks".length).trim();
|
|
1307
|
+
if (rest === "kill-all") {
|
|
1308
|
+
const n = killAllBackgroundShellTasks();
|
|
1309
|
+
logger.log(chalk.dim(`Killed ${n} background shell task(s).`));
|
|
1310
|
+
} else if (rest.startsWith("kill ")) {
|
|
1311
|
+
const id = rest.slice("kill ".length).trim();
|
|
1312
|
+
const ok = id ? killBackgroundShellTask(id) : false;
|
|
1313
|
+
logger.log(
|
|
1314
|
+
ok
|
|
1315
|
+
? chalk.dim(`Killed background shell task ${id}.`)
|
|
1316
|
+
: chalk.dim(`No running background shell task with id "${id}".`),
|
|
1317
|
+
);
|
|
1318
|
+
} else if (rest === "kill") {
|
|
1319
|
+
logger.log(chalk.dim("Usage: /tasks kill <id> · /tasks kill-all"));
|
|
1320
|
+
} else {
|
|
1321
|
+
logger.log(
|
|
1322
|
+
"
|
|
1323
|
+
" + formatBackgroundTasks(listBackgroundShellTasks()) + "
|
|
1324
|
+
",
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
prompt();
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1291
1331
|
if (trimmed === "/sub-agents" || trimmed === "/subagents") {
|
|
1292
1332
|
try {
|
|
1293
1333
|
const { SubAgentRegistry } =
|
|
@@ -1384,6 +1424,23 @@ export async function startAgentRepl(options = {}) {
|
|
|
1384
1424
|
return;
|
|
1385
1425
|
}
|
|
1386
1426
|
|
|
1427
|
+
if (
|
|
1428
|
+
trimmed === "/terminal-setup" ||
|
|
1429
|
+
trimmed.startsWith("/terminal-setup ")
|
|
1430
|
+
) {
|
|
1431
|
+
try {
|
|
1432
|
+
const arg = trimmed.slice("/terminal-setup".length).trim();
|
|
1433
|
+
const { runTerminalSetup } =
|
|
1434
|
+
await import("../commands/terminal-setup.js");
|
|
1435
|
+
const res = runTerminalSetup({ apply: arg === "--apply" });
|
|
1436
|
+
for (const l of res.lines) logger.log(l);
|
|
1437
|
+
} catch (err) {
|
|
1438
|
+
logger.error(`/terminal-setup failed: ${err.message}`);
|
|
1439
|
+
}
|
|
1440
|
+
prompt();
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1387
1444
|
if (trimmed === "/vim" || trimmed.startsWith("/vim ")) {
|
|
1388
1445
|
const arg = trimmed.slice("/vim".length).trim().toLowerCase();
|
|
1389
1446
|
const turnOn = arg === "on" || (arg === "" && !_vimEnabled);
|
|
@@ -2507,7 +2564,7 @@ export async function startAgentRepl(options = {}) {
|
|
|
2507
2564
|
const roles = {
|
|
2508
2565
|
mainProvider: provider,
|
|
2509
2566
|
mainModel: _curModel || model,
|
|
2510
|
-
visionModel: visionModel || "doubao-seed-
|
|
2567
|
+
visionModel: visionModel || "doubao-seed-2-0-lite-260215",
|
|
2511
2568
|
fallbackModels: _fallbackModels || [],
|
|
2512
2569
|
};
|
|
2513
2570
|
const { renderSessionCost } = await import("./session-cost.js");
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure renderer for the `/tasks` REPL command — a user-facing view of the
|
|
3
|
+
* agent's background shell tasks (the ones it starts with
|
|
4
|
+
* run_shell { run_in_background: true }). The data comes from agent-core's
|
|
5
|
+
* listBackgroundShellTasks(); this module only formats it, so it stays
|
|
6
|
+
* deterministic and unit-testable (inject `now` for stable elapsed time).
|
|
7
|
+
*
|
|
8
|
+
* Background shells are otherwise only visible to the agent (via its
|
|
9
|
+
* check_shell tool) — this surfaces them to the human, the way Claude Code's
|
|
10
|
+
* background-tasks view does. Sub-agents live under a separate registry and
|
|
11
|
+
* are shown by `/sub-agents`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Coerce an ISO-string or epoch-ms timestamp to epoch ms, or null. */
|
|
15
|
+
function toMs(v) {
|
|
16
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
17
|
+
if (typeof v === "string") {
|
|
18
|
+
const t = Date.parse(v);
|
|
19
|
+
return Number.isFinite(t) ? t : null;
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Human-friendly elapsed duration. */
|
|
25
|
+
function fmtElapsed(ms) {
|
|
26
|
+
if (!Number.isFinite(ms) || ms < 0) return "?";
|
|
27
|
+
const s = Math.floor(ms / 1000);
|
|
28
|
+
if (s < 60) return `${s}s`;
|
|
29
|
+
const m = Math.floor(s / 60);
|
|
30
|
+
if (m < 60) return `${m}m${s % 60}s`;
|
|
31
|
+
const h = Math.floor(m / 60);
|
|
32
|
+
return `${h}h${m % 60}m`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A short status badge for one task. */
|
|
36
|
+
export function taskStatusLabel(t) {
|
|
37
|
+
switch (t?.status) {
|
|
38
|
+
case "running":
|
|
39
|
+
return "● running";
|
|
40
|
+
case "exited":
|
|
41
|
+
return `✓ exited${t.exitCode != null ? ` (${t.exitCode})` : ""}`;
|
|
42
|
+
case "failed":
|
|
43
|
+
return `✗ failed${t.exitCode != null ? ` (${t.exitCode})` : ""}`;
|
|
44
|
+
case "error":
|
|
45
|
+
return "✗ error";
|
|
46
|
+
default:
|
|
47
|
+
return t?.status || "?";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Render the background-shell task list as a plain-text block.
|
|
53
|
+
* @param {Array} tasks from listBackgroundShellTasks()
|
|
54
|
+
* @param {object} opts { now?: epoch-ms } — defaults to Date.now()
|
|
55
|
+
*/
|
|
56
|
+
export function formatBackgroundTasks(tasks, { now = Date.now() } = {}) {
|
|
57
|
+
const list = Array.isArray(tasks) ? tasks : [];
|
|
58
|
+
if (list.length === 0) {
|
|
59
|
+
return (
|
|
60
|
+
"No background shell tasks.\n" +
|
|
61
|
+
" (The agent starts these with run_shell run_in_background:true; " +
|
|
62
|
+
"sub-agents are under /sub-agents.)"
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const running = list.filter((t) => t?.status === "running").length;
|
|
66
|
+
const lines = [
|
|
67
|
+
`Background shell tasks (${list.length}, ${running} running):`,
|
|
68
|
+
];
|
|
69
|
+
for (const t of list) {
|
|
70
|
+
const started = toMs(t.startedAt);
|
|
71
|
+
const ended = toMs(t.endedAt);
|
|
72
|
+
const elapsed =
|
|
73
|
+
started != null ? fmtElapsed((ended ?? now) - started) : "?";
|
|
74
|
+
const cmd = String(t.command || "")
|
|
75
|
+
.replace(/\s+/g, " ")
|
|
76
|
+
.trim();
|
|
77
|
+
const cmdShort = cmd.length > 70 ? cmd.slice(0, 70) + "…" : cmd;
|
|
78
|
+
lines.push(` ${taskStatusLabel(t)} ${t.id} ${elapsed} ${cmdShort}`);
|
|
79
|
+
}
|
|
80
|
+
lines.push("Manage: /tasks kill <id> · /tasks kill-all");
|
|
81
|
+
return lines.join("\n");
|
|
82
|
+
}
|