jinzd-ai-cli 0.4.265 → 0.4.267
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/dist/{batch-C7SHXRQ2.js → batch-GY5MAN5F.js} +2 -2
- package/dist/{chunk-TJBG4F2R.js → chunk-5AEOIETL.js} +4 -4
- package/dist/{chunk-NOS3QV7G.js → chunk-5CU72V6P.js} +3 -3
- package/dist/{chunk-IFTUBTDA.js → chunk-6C6FLDGH.js} +1 -1
- package/dist/{chunk-M2RKBTVZ.js → chunk-7HECI3U5.js} +1 -1
- package/dist/{chunk-NJLPA6S4.js → chunk-ARYCWJN6.js} +1 -1
- package/dist/{chunk-X6NWK2YK.js → chunk-EWRZMIQE.js} +1 -1
- package/dist/{chunk-YXYOLPDB.js → chunk-FI6IWUXW.js} +1 -1
- package/dist/{chunk-CVI3LSGR.js → chunk-JH343BDI.js} +105 -2
- package/dist/{chunk-E3JRGJCJ.js → chunk-QN7WD2SM.js} +3 -3
- package/dist/{chunk-V4D7YD24.js → chunk-R7GERZDA.js} +145 -4
- package/dist/{chunk-LZBD2VGJ.js → chunk-RVNL7MQG.js} +1 -1
- package/dist/{chunk-Y7KJPNQI.js → chunk-THHPKZJ3.js} +1 -1
- package/dist/{chunk-2MR7EP24.js → chunk-WPN7LDLM.js} +1 -1
- package/dist/{chunk-YDTYBUAB.js → chunk-XLLUQEGO.js} +1 -1
- package/dist/{ci-TDXLI3OM.js → ci-MUEQSNKU.js} +4 -4
- package/dist/{ci-format-AFX2LP4Y.js → ci-format-2ZGGSOFK.js} +2 -2
- package/dist/{constants-66TQEDGO.js → constants-SZTJESDF.js} +1 -1
- package/dist/{doctor-cli-GMCOGCDR.js → doctor-cli-Z272364P.js} +4 -4
- package/dist/electron-server.js +781 -420
- package/dist/{hub-2YXETHPY.js → hub-MRLGKMRI.js} +1 -1
- package/dist/index.js +51 -93
- package/dist/{persistent-memory-WTZO5CQ7.js → persistent-memory-3FTOC7QT.js} +2 -2
- package/dist/{persistent-memory-2DKCETB7.js → persistent-memory-HZASMDJQ.js} +2 -2
- package/dist/{pr-Y2LDG3R7.js → pr-VLLIALRK.js} +4 -4
- package/dist/{run-tests-FIUGDIDL.js → run-tests-6MWE6D5S.js} +2 -2
- package/dist/{run-tests-PWTNSUVP.js → run-tests-NTDZ6DDU.js} +2 -2
- package/dist/{server-OZ4RFDFH.js → server-IALCWKSU.js} +61 -48
- package/dist/{server-ZCG5QD6P.js → server-SP3AEHRQ.js} +5 -5
- package/dist/{task-orchestrator-Q2L6JEUX.js → task-orchestrator-Q6WFFI4J.js} +5 -5
- package/dist/{usage-SDL5Z4CS.js → usage-DJDLHICZ.js} +4 -6
- package/package.json +1 -1
- package/dist/chunk-YRD6M5XI.js +0 -149
package/dist/electron-server.js
CHANGED
|
@@ -34,14 +34,14 @@ import {
|
|
|
34
34
|
touchMemoryReferences,
|
|
35
35
|
updateMemoryApproval,
|
|
36
36
|
updateMemoryEntry
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-FI6IWUXW.js";
|
|
38
38
|
import {
|
|
39
39
|
redactJson,
|
|
40
40
|
scanString
|
|
41
41
|
} from "./chunk-YVTASHS5.js";
|
|
42
42
|
import {
|
|
43
43
|
runTestsTool
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-ARYCWJN6.js";
|
|
45
45
|
import {
|
|
46
46
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
47
47
|
APP_NAME,
|
|
@@ -73,7 +73,7 @@ import {
|
|
|
73
73
|
SUBAGENT_MAX_ROUNDS_LIMIT,
|
|
74
74
|
VERSION,
|
|
75
75
|
buildUserIdentityPrompt
|
|
76
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-7HECI3U5.js";
|
|
77
77
|
import {
|
|
78
78
|
hasSemanticIndex,
|
|
79
79
|
semanticSearch
|
|
@@ -91,8 +91,8 @@ import {
|
|
|
91
91
|
import express from "express";
|
|
92
92
|
import { createServer } from "http";
|
|
93
93
|
import { WebSocketServer } from "ws";
|
|
94
|
-
import { join as
|
|
95
|
-
import { existsSync as
|
|
94
|
+
import { join as join26, dirname as dirname10, resolve as resolve10, relative as relative6, sep as sep4 } from "path";
|
|
95
|
+
import { existsSync as existsSync31, readFileSync as readFileSync25, readdirSync as readdirSync15, statSync as statSync14, realpathSync as realpathSync3 } from "fs";
|
|
96
96
|
import { networkInterfaces } from "os";
|
|
97
97
|
|
|
98
98
|
// src/config/config-manager.ts
|
|
@@ -709,6 +709,403 @@ ${err}`
|
|
|
709
709
|
}
|
|
710
710
|
};
|
|
711
711
|
|
|
712
|
+
// src/core/cost-tracker.ts
|
|
713
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
714
|
+
import { join as join2 } from "path";
|
|
715
|
+
|
|
716
|
+
// src/core/pricing.ts
|
|
717
|
+
var DEEPSEEK_CNY_PER_USD = 6.9;
|
|
718
|
+
var DEEPSEEK_TIME_PRICING_EFFECTIVE_AT = /* @__PURE__ */ new Date("2026-08-16T16:00:00.000Z");
|
|
719
|
+
function deepSeekCnyPricing(cacheRead, input, output) {
|
|
720
|
+
return {
|
|
721
|
+
cacheRead: cacheRead / DEEPSEEK_CNY_PER_USD,
|
|
722
|
+
input: input / DEEPSEEK_CNY_PER_USD,
|
|
723
|
+
output: output / DEEPSEEK_CNY_PER_USD
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function getDeepSeekV4Pricing(model, at) {
|
|
727
|
+
const isPro = model.startsWith("deepseek-v4-pro");
|
|
728
|
+
const isFlash = model.startsWith("deepseek-v4-flash");
|
|
729
|
+
if (!isPro && !isFlash) return null;
|
|
730
|
+
if (at < DEEPSEEK_TIME_PRICING_EFFECTIVE_AT) {
|
|
731
|
+
return isPro ? deepSeekCnyPricing(0.025, 3, 6) : deepSeekCnyPricing(0.02, 1, 2);
|
|
732
|
+
}
|
|
733
|
+
const beijingHour = (at.getUTCHours() + 8) % 24;
|
|
734
|
+
const isPeak = beijingHour >= 9 && beijingHour < 12 || beijingHour >= 14 && beijingHour < 18;
|
|
735
|
+
if (isPro) {
|
|
736
|
+
return isPeak ? deepSeekCnyPricing(0.3, 9, 27) : deepSeekCnyPricing(0.15, 4.5, 13.5);
|
|
737
|
+
}
|
|
738
|
+
return isPeak ? deepSeekCnyPricing(0.1, 3, 9) : deepSeekCnyPricing(0.05, 1.5, 4.5);
|
|
739
|
+
}
|
|
740
|
+
var PRICING_TABLE = {
|
|
741
|
+
// ── Anthropic Claude ──────────────────────────────────────────
|
|
742
|
+
"claude-opus-4-8": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
743
|
+
"claude-opus-4-7": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
744
|
+
"claude-opus-4-6": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
745
|
+
"claude-opus-4-5": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
746
|
+
"claude-sonnet-4-6": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
|
|
747
|
+
"claude-sonnet-4-5-20250929": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
|
|
748
|
+
"claude-haiku-4-5-20251001": { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1 },
|
|
749
|
+
"claude-haiku-4-5": { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1 },
|
|
750
|
+
// Legacy Claude 3.x families (prefix fallback handles minor date suffixes)
|
|
751
|
+
"claude-3-5-sonnet": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
|
|
752
|
+
"claude-3-5-haiku": { input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08 },
|
|
753
|
+
"claude-3-opus": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
754
|
+
// ── OpenAI ────────────────────────────────────────────────────
|
|
755
|
+
// GAP (v0.4.184): the GPT-5 family (gpt-5, gpt-5-mini/nano, gpt-5.4, gpt-5.4-pro)
|
|
756
|
+
// and o4-mini are in the provider's model list but have NO verified pricing yet —
|
|
757
|
+
// left unpriced on purpose so `aicli usage` shows "—" (unknown) rather than a
|
|
758
|
+
// deceptive $0.00. Add real rates when confirmed from the official pricing page.
|
|
759
|
+
"gpt-4o": { input: 2.5, output: 10, cacheRead: 1.25 },
|
|
760
|
+
"gpt-4o-mini": { input: 0.15, output: 0.6, cacheRead: 0.075 },
|
|
761
|
+
"gpt-4-turbo": { input: 10, output: 30 },
|
|
762
|
+
"gpt-4": { input: 30, output: 60 },
|
|
763
|
+
"gpt-4.1": { input: 2, output: 8, cacheRead: 0.5 },
|
|
764
|
+
"gpt-4.1-mini": { input: 0.4, output: 1.6, cacheRead: 0.1 },
|
|
765
|
+
"gpt-4.1-nano": { input: 0.1, output: 0.4, cacheRead: 0.025 },
|
|
766
|
+
"o1": { input: 15, output: 60, cacheRead: 7.5 },
|
|
767
|
+
"o1-mini": { input: 3, output: 12, cacheRead: 1.5 },
|
|
768
|
+
"o3": { input: 10, output: 40, cacheRead: 2.5 },
|
|
769
|
+
"o3-mini": { input: 1.1, output: 4.4, cacheRead: 0.55 },
|
|
770
|
+
// ── Google Gemini ─────────────────────────────────────────────
|
|
771
|
+
"gemini-2.5-pro": { input: 1.25, output: 10 },
|
|
772
|
+
"gemini-2.5-flash": { input: 0.3, output: 2.5 },
|
|
773
|
+
"gemini-2.0-flash": { input: 0.1, output: 0.4 },
|
|
774
|
+
"gemini-1.5-pro": { input: 1.25, output: 5 },
|
|
775
|
+
"gemini-1.5-flash": { input: 0.075, output: 0.3 },
|
|
776
|
+
// ── DeepSeek ──────────────────────────────────────────────────
|
|
777
|
+
// V4 family (2026-04-23+):1M context, Thinking / Non-Thinking dual-mode, 384K max output
|
|
778
|
+
// 费率 2026-08-01 对 api-docs.deepseek.com/quick_start/pricing 官方 USD 表逐项核对。
|
|
779
|
+
// 修正前 pro 三项整齐高 4×(官方降过价未跟)、flash cacheRead 高 10×(像是按 input 的
|
|
780
|
+
// 20% 拍脑袋而非抄文档)——`aicli usage` 因此系统性高报 DeepSeek 成本。
|
|
781
|
+
// 待办:官方已公告峰谷定价(北京时间 9-12 点、14-18 点为平时 2 倍),本表仍是单一费率,
|
|
782
|
+
// 届时高峰时段会低报;等该策略实际生效后再决定是否引入时段维度。
|
|
783
|
+
// Legacy aliases:retires 2026-07-24 UTC 15:59,官方 route 到 V4 Flash。
|
|
784
|
+
// 这几条只服务历史 session 的成本回算,保留当时的费率口径,不随现价改动。
|
|
785
|
+
"deepseek-chat": { input: 0.14, output: 0.28, cacheRead: 0.028 },
|
|
786
|
+
"deepseek-reasoner": { input: 0.14, output: 0.28, cacheRead: 0.028 },
|
|
787
|
+
"deepseek-v3": { input: 0.27, output: 1.1, cacheRead: 0.07 },
|
|
788
|
+
// ── Moonshot Kimi ─────────────────────────────────────────────
|
|
789
|
+
"moonshot-v1-8k": { input: 0.17, output: 0.17 },
|
|
790
|
+
"moonshot-v1-32k": { input: 0.33, output: 0.33 },
|
|
791
|
+
"moonshot-v1-128k": { input: 0.83, output: 0.83 },
|
|
792
|
+
"kimi-k2": { input: 0.6, output: 2.5 },
|
|
793
|
+
"kimi-latest": { input: 0.6, output: 2.5 },
|
|
794
|
+
// Kimi K3(2026-07 旗舰,1M 上下文)官方定价 platform.kimi.ai/pricing/chat-k3:
|
|
795
|
+
// 输入 cache-miss $3.00 / cache-hit $0.30,输出 $15.00(/MTok)。
|
|
796
|
+
"kimi-k3": { input: 3, output: 15, cacheRead: 0.3 },
|
|
797
|
+
// ── Zhipu GLM ─────────────────────────────────────────────────
|
|
798
|
+
"glm-4-plus": { input: 0.7, output: 0.7 },
|
|
799
|
+
"glm-4": { input: 0.14, output: 0.14 },
|
|
800
|
+
"glm-4-flash": { input: 0, output: 0 },
|
|
801
|
+
"glm-4.5": { input: 0.29, output: 1.14 },
|
|
802
|
+
"glm-4.6": { input: 0.6, output: 2.2 },
|
|
803
|
+
"glm-4.6v": { input: 0.6, output: 2.2 },
|
|
804
|
+
"glm-5": { input: 0.85, output: 2.85 },
|
|
805
|
+
// GLM-5.2(2026-06)官方直连 API 定价 $1.40 / $4.40。
|
|
806
|
+
"glm-5.2": { input: 1.4, output: 4.4 },
|
|
807
|
+
// GLM-5V-Turbo(2026-04 视觉编程旗舰)官方直连 API 定价 $1.20 / $4.00。
|
|
808
|
+
"glm-5v-turbo": { input: 1.2, output: 4 },
|
|
809
|
+
"glm-5.1": { input: 0.95, output: 3.15 },
|
|
810
|
+
"glm-5.1-reasoning": { input: 1.4, output: 4.4 },
|
|
811
|
+
"glm-5.1-air": { input: 0.4, output: 1.2 },
|
|
812
|
+
"glm-z1": { input: 0.5, output: 1.5 },
|
|
813
|
+
"glm-z1-air": { input: 0.2, output: 0.6 },
|
|
814
|
+
"glm-z1-flash": { input: 0, output: 0 }
|
|
815
|
+
// ── MiniMax (海螺) ────────────────────────────────────────────
|
|
816
|
+
// GAP (v0.4.184): MiniMax-M3 / M2.x are unpriced — no verified rate anchor yet.
|
|
817
|
+
// Unpriced → `aicli usage` shows "—" (unknown), not a false $0.00. Add when confirmed.
|
|
818
|
+
// ── OpenRouter (pass-through — actual cost depends on underlying model) ──
|
|
819
|
+
// Left empty by design: model IDs are "vendor/model" (e.g. anthropic/claude-sonnet-4)
|
|
820
|
+
// and the real price is set by the underlying model + OpenRouter markup, not statically
|
|
821
|
+
// resolvable here. Shows "—" (unknown) in `aicli usage`.
|
|
822
|
+
// ── Ollama (local, zero cost) ─────────────────────────────────
|
|
823
|
+
// Handled via provider check below (FREE_PROVIDERS → genuine $0.00, not "unknown").
|
|
824
|
+
};
|
|
825
|
+
var FREE_PROVIDERS = /* @__PURE__ */ new Set(["ollama"]);
|
|
826
|
+
function getPricing(provider, model, at = /* @__PURE__ */ new Date()) {
|
|
827
|
+
if (FREE_PROVIDERS.has(provider.toLowerCase())) {
|
|
828
|
+
return { input: 0, output: 0 };
|
|
829
|
+
}
|
|
830
|
+
const key = model.toLowerCase();
|
|
831
|
+
if (provider.toLowerCase() === "deepseek") {
|
|
832
|
+
const dynamicPricing = getDeepSeekV4Pricing(key, at);
|
|
833
|
+
if (dynamicPricing) return dynamicPricing;
|
|
834
|
+
}
|
|
835
|
+
if (PRICING_TABLE[key]) return PRICING_TABLE[key];
|
|
836
|
+
const keys = Object.keys(PRICING_TABLE).sort((a, b) => b.length - a.length);
|
|
837
|
+
for (const k of keys) {
|
|
838
|
+
if (key.startsWith(k)) return PRICING_TABLE[k];
|
|
839
|
+
}
|
|
840
|
+
return null;
|
|
841
|
+
}
|
|
842
|
+
function computeCost(provider, model, usage, at = /* @__PURE__ */ new Date()) {
|
|
843
|
+
const p = getPricing(provider, model, at);
|
|
844
|
+
if (!p) return null;
|
|
845
|
+
const input = usage.inputTokens * p.input;
|
|
846
|
+
const output = usage.outputTokens * p.output;
|
|
847
|
+
const cacheWrite = (usage.cacheCreationTokens ?? 0) * (p.cacheWrite ?? p.input);
|
|
848
|
+
const cacheRead = (usage.cacheReadTokens ?? 0) * (p.cacheRead ?? p.input);
|
|
849
|
+
return (input + output + cacheWrite + cacheRead) / 1e6;
|
|
850
|
+
}
|
|
851
|
+
function formatCost(amount) {
|
|
852
|
+
if (amount === 0) return "$0.0000";
|
|
853
|
+
if (amount < 0.01) return `$${amount.toFixed(4)}`;
|
|
854
|
+
if (amount < 1) return `$${amount.toFixed(3)}`;
|
|
855
|
+
return `$${amount.toFixed(2)}`;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// src/core/cost-tracker.ts
|
|
859
|
+
var CostTracker = class _CostTracker {
|
|
860
|
+
filePath;
|
|
861
|
+
records = [];
|
|
862
|
+
dirty = false;
|
|
863
|
+
/** P2-PERF-04: debounce timer to batch rapid addCost() calls into a single save. */
|
|
864
|
+
_saveTimer;
|
|
865
|
+
constructor(configDir) {
|
|
866
|
+
this.filePath = join2(configDir, "cost-history.json");
|
|
867
|
+
this.load();
|
|
868
|
+
}
|
|
869
|
+
/** P2-PERF-04: debounce interval (ms) — coalesces multiple rapid addCost calls. */
|
|
870
|
+
static SAVE_DEBOUNCE_MS = 2e3;
|
|
871
|
+
load() {
|
|
872
|
+
try {
|
|
873
|
+
if (existsSync2(this.filePath)) {
|
|
874
|
+
const data = JSON.parse(readFileSync2(this.filePath, "utf-8"));
|
|
875
|
+
if (data.version === 1 && Array.isArray(data.records)) {
|
|
876
|
+
this.records = [...data.records].sort((a, b) => a.date.localeCompare(b.date));
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
} catch {
|
|
880
|
+
this.records = [];
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Save to disk (atomic write).
|
|
885
|
+
*
|
|
886
|
+
* H2: Snapshot records before writing and only clear the dirty flag after
|
|
887
|
+
* the write succeeds. If writeFileSync/renameSync throws, dirty remains
|
|
888
|
+
* true so the next save() retries — prevents silent data loss on transient
|
|
889
|
+
* disk errors. writeFileSync is sync in Node.js so there's no interleaving
|
|
890
|
+
* risk within a single save() call; the snapshot mainly protects against
|
|
891
|
+
* future refactors to async I/O.
|
|
892
|
+
*/
|
|
893
|
+
save() {
|
|
894
|
+
if (!this.dirty) return;
|
|
895
|
+
if (this._saveTimer) {
|
|
896
|
+
clearTimeout(this._saveTimer);
|
|
897
|
+
this._saveTimer = void 0;
|
|
898
|
+
}
|
|
899
|
+
const snapshot = {
|
|
900
|
+
version: 1,
|
|
901
|
+
records: [...this.records]
|
|
902
|
+
// shallow copy — records are plain data
|
|
903
|
+
};
|
|
904
|
+
try {
|
|
905
|
+
atomicWriteFileSync(this.filePath, JSON.stringify(snapshot, null, 2));
|
|
906
|
+
this.dirty = false;
|
|
907
|
+
} catch (err) {
|
|
908
|
+
console.error("[cost-tracker] Failed to persist cost history:", err);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Record cost from a completed session/interaction.
|
|
913
|
+
*
|
|
914
|
+
* 返回本次记账的美元金额(定价未知时返回 null)。调用方据此累计**按调用时刻**
|
|
915
|
+
* 算出的会话成本——2026-08-14 审计 P3-01:DeepSeek V4 自 8/17 起峰谷计价,
|
|
916
|
+
* 展示层若拿聚合用量在「查看时刻」现算,闲时跑的会话在峰时看会被按峰价显示,
|
|
917
|
+
* 跨时段会话更是整段按一个费率算。记账时刻的值才是真实花费。
|
|
918
|
+
*/
|
|
919
|
+
addCost(provider, model, usage) {
|
|
920
|
+
const priced = computeCost(provider, model, usage);
|
|
921
|
+
const cost = priced ?? 0;
|
|
922
|
+
const inTok = usage.inputTokens || 0;
|
|
923
|
+
const outTok = usage.outputTokens || 0;
|
|
924
|
+
if (cost === 0 && inTok === 0 && outTok === 0) return priced;
|
|
925
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
926
|
+
let record = this.records.find((r) => r.date === today);
|
|
927
|
+
if (!record) {
|
|
928
|
+
record = { date: today, cost: 0, sessions: 0, inputTokens: 0, outputTokens: 0 };
|
|
929
|
+
this.records.push(record);
|
|
930
|
+
}
|
|
931
|
+
record.cost += cost;
|
|
932
|
+
record.sessions += 1;
|
|
933
|
+
record.inputTokens += inTok;
|
|
934
|
+
record.outputTokens += outTok;
|
|
935
|
+
const key = `${provider}/${model}`;
|
|
936
|
+
record.byModel ??= {};
|
|
937
|
+
const m = record.byModel[key] ??= { cost: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, calls: 0 };
|
|
938
|
+
m.cost += cost;
|
|
939
|
+
m.inputTokens += inTok;
|
|
940
|
+
m.outputTokens += outTok;
|
|
941
|
+
m.cacheReadTokens += usage.cacheReadTokens ?? 0;
|
|
942
|
+
m.calls += 1;
|
|
943
|
+
this.dirty = true;
|
|
944
|
+
if (!this._saveTimer) {
|
|
945
|
+
this._saveTimer = setTimeout(() => {
|
|
946
|
+
this._saveTimer = void 0;
|
|
947
|
+
this.save();
|
|
948
|
+
}, _CostTracker.SAVE_DEBOUNCE_MS);
|
|
949
|
+
if (this._saveTimer && typeof this._saveTimer === "object" && "unref" in this._saveTimer) {
|
|
950
|
+
this._saveTimer.unref();
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
if (this.records.length > 90) {
|
|
954
|
+
this.records = this.records.slice(-90);
|
|
955
|
+
}
|
|
956
|
+
return priced;
|
|
957
|
+
}
|
|
958
|
+
/** Get total cost for a given month ("2026-04"). */
|
|
959
|
+
getMonthlyCost(yearMonth) {
|
|
960
|
+
const prefix = yearMonth ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
|
|
961
|
+
return this.records.filter((r) => r.date.startsWith(prefix)).reduce((sum, r) => sum + r.cost, 0);
|
|
962
|
+
}
|
|
963
|
+
/** Get today's cost. */
|
|
964
|
+
getTodayCost() {
|
|
965
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
966
|
+
return this.records.find((r) => r.date === today)?.cost ?? 0;
|
|
967
|
+
}
|
|
968
|
+
/** Get total cost for last N days. */
|
|
969
|
+
getRecentCost(days) {
|
|
970
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
971
|
+
cutoff.setDate(cutoff.getDate() - days);
|
|
972
|
+
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
|
973
|
+
return this.records.filter((r) => r.date >= cutoffStr).reduce((sum, r) => sum + r.cost, 0);
|
|
974
|
+
}
|
|
975
|
+
/** Get all records for display. */
|
|
976
|
+
getRecords() {
|
|
977
|
+
return [...this.records];
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Aggregate usage across a date window, grouped by provider/model (v0.4.184+).
|
|
981
|
+
*
|
|
982
|
+
* Window selection (precedence): `month` ("2026-06") > `sinceDays` (N days back,
|
|
983
|
+
* inclusive of today) > all tracked history. Legacy records without a byModel
|
|
984
|
+
* breakdown fold into a single "(unattributed)" row so totals still reconcile
|
|
985
|
+
* with `getMonthlyCost()` etc.
|
|
986
|
+
*
|
|
987
|
+
* @returns rows sorted by cost desc then total tokens desc, plus grand totals
|
|
988
|
+
* and the actual covered date range.
|
|
989
|
+
*/
|
|
990
|
+
getUsageBreakdown(opts) {
|
|
991
|
+
let filtered;
|
|
992
|
+
if (opts?.month) {
|
|
993
|
+
filtered = this.records.filter((r) => r.date.startsWith(opts.month));
|
|
994
|
+
} else if (opts?.sinceDays && opts.sinceDays > 0) {
|
|
995
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
996
|
+
cutoff.setDate(cutoff.getDate() - (opts.sinceDays - 1));
|
|
997
|
+
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
|
998
|
+
filtered = this.records.filter((r) => r.date >= cutoffStr);
|
|
999
|
+
} else {
|
|
1000
|
+
filtered = [...this.records];
|
|
1001
|
+
}
|
|
1002
|
+
const acc = /* @__PURE__ */ new Map();
|
|
1003
|
+
const bump = (key, provider, model, v) => {
|
|
1004
|
+
const row = acc.get(key) ?? { key, provider, model, cost: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, calls: 0 };
|
|
1005
|
+
row.cost += v.cost;
|
|
1006
|
+
row.inputTokens += v.inputTokens;
|
|
1007
|
+
row.outputTokens += v.outputTokens;
|
|
1008
|
+
row.cacheReadTokens += v.cacheReadTokens;
|
|
1009
|
+
row.calls += v.calls;
|
|
1010
|
+
acc.set(key, row);
|
|
1011
|
+
};
|
|
1012
|
+
for (const rec of filtered) {
|
|
1013
|
+
if (rec.byModel && Object.keys(rec.byModel).length > 0) {
|
|
1014
|
+
for (const [key, m] of Object.entries(rec.byModel)) {
|
|
1015
|
+
const slash = key.indexOf("/");
|
|
1016
|
+
const provider = slash >= 0 ? key.slice(0, slash) : key;
|
|
1017
|
+
const model = slash >= 0 ? key.slice(slash + 1) : "";
|
|
1018
|
+
bump(key, provider, model, m);
|
|
1019
|
+
}
|
|
1020
|
+
} else {
|
|
1021
|
+
bump("(unattributed)", "(unattributed)", "", {
|
|
1022
|
+
cost: rec.cost,
|
|
1023
|
+
inputTokens: rec.inputTokens,
|
|
1024
|
+
outputTokens: rec.outputTokens,
|
|
1025
|
+
cacheReadTokens: 0,
|
|
1026
|
+
calls: rec.sessions
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
const rows = [...acc.values()].sort(
|
|
1031
|
+
(a, b) => b.cost - a.cost || b.inputTokens + b.outputTokens - (a.inputTokens + a.outputTokens)
|
|
1032
|
+
);
|
|
1033
|
+
const totals = rows.reduce(
|
|
1034
|
+
(t, r) => {
|
|
1035
|
+
t.cost += r.cost;
|
|
1036
|
+
t.inputTokens += r.inputTokens;
|
|
1037
|
+
t.outputTokens += r.outputTokens;
|
|
1038
|
+
t.cacheReadTokens += r.cacheReadTokens;
|
|
1039
|
+
t.calls += r.calls;
|
|
1040
|
+
return t;
|
|
1041
|
+
},
|
|
1042
|
+
{ cost: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, calls: 0 }
|
|
1043
|
+
);
|
|
1044
|
+
return {
|
|
1045
|
+
rows,
|
|
1046
|
+
totals,
|
|
1047
|
+
fromDate: filtered.length > 0 ? filtered[0].date : null,
|
|
1048
|
+
toDate: filtered.length > 0 ? filtered[filtered.length - 1].date : null,
|
|
1049
|
+
dayCount: filtered.length
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Check if monthly cost exceeds budget, return warning message or null.
|
|
1054
|
+
*/
|
|
1055
|
+
checkBudget(monthlyBudget) {
|
|
1056
|
+
if (!monthlyBudget || monthlyBudget <= 0) return null;
|
|
1057
|
+
const monthlyCost = this.getMonthlyCost();
|
|
1058
|
+
const ratio = monthlyCost / monthlyBudget;
|
|
1059
|
+
if (ratio >= 1) {
|
|
1060
|
+
return `\u{1F6A8} Monthly budget exceeded: ${formatCost(monthlyCost)} / ${formatCost(monthlyBudget)} (${Math.round(ratio * 100)}%)`;
|
|
1061
|
+
}
|
|
1062
|
+
if (ratio >= 0.8) {
|
|
1063
|
+
return `\u26A0 Monthly budget warning: ${formatCost(monthlyCost)} / ${formatCost(monthlyBudget)} (${Math.round(ratio * 100)}%)`;
|
|
1064
|
+
}
|
|
1065
|
+
return null;
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Format a cost summary for display.
|
|
1069
|
+
*/
|
|
1070
|
+
formatSummary(monthlyBudget) {
|
|
1071
|
+
const today = this.getTodayCost();
|
|
1072
|
+
const monthly = this.getMonthlyCost();
|
|
1073
|
+
const yearMonth = (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
|
|
1074
|
+
const lines = [
|
|
1075
|
+
`Today: ${formatCost(today)}`,
|
|
1076
|
+
`This month: ${formatCost(monthly)} (${yearMonth})`
|
|
1077
|
+
];
|
|
1078
|
+
if (monthlyBudget && monthlyBudget > 0) {
|
|
1079
|
+
const ratio = monthly / monthlyBudget;
|
|
1080
|
+
const bar = "\u2588".repeat(Math.min(20, Math.round(ratio * 20))) + "\u2591".repeat(Math.max(0, 20 - Math.round(ratio * 20)));
|
|
1081
|
+
lines.push(`Budget: ${formatCost(monthlyBudget)} [${bar}] ${Math.round(ratio * 100)}%`);
|
|
1082
|
+
}
|
|
1083
|
+
const last7 = [];
|
|
1084
|
+
for (let i = 6; i >= 0; i--) {
|
|
1085
|
+
const d = /* @__PURE__ */ new Date();
|
|
1086
|
+
d.setDate(d.getDate() - i);
|
|
1087
|
+
const dateStr = d.toISOString().slice(0, 10);
|
|
1088
|
+
const record = this.records.find((r) => r.date === dateStr);
|
|
1089
|
+
const dayLabel = dateStr.slice(5);
|
|
1090
|
+
last7.push(`${dayLabel}: ${record ? formatCost(record.cost) : "$0.00"}`);
|
|
1091
|
+
}
|
|
1092
|
+
lines.push(`Last 7 days: ${last7.join(" ")}`);
|
|
1093
|
+
return lines.join("\n");
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* P2-PERF-04: flush pending cost records to disk immediately.
|
|
1097
|
+
* Call this before process exit or before reading cost data for display
|
|
1098
|
+
* to ensure the in-memory buffer is reflected on disk.
|
|
1099
|
+
*/
|
|
1100
|
+
flush() {
|
|
1101
|
+
if (this._saveTimer) {
|
|
1102
|
+
clearTimeout(this._saveTimer);
|
|
1103
|
+
this._saveTimer = void 0;
|
|
1104
|
+
}
|
|
1105
|
+
this.save();
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
|
|
712
1109
|
// src/providers/claude.ts
|
|
713
1110
|
import Anthropic from "@anthropic-ai/sdk";
|
|
714
1111
|
|
|
@@ -3905,18 +4302,18 @@ var CustomProvider = class extends OpenAICompatibleProvider {
|
|
|
3905
4302
|
};
|
|
3906
4303
|
|
|
3907
4304
|
// src/providers/model-cache.ts
|
|
3908
|
-
import { existsSync as
|
|
3909
|
-
import { dirname, join as
|
|
4305
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync } from "fs";
|
|
4306
|
+
import { dirname, join as join3 } from "path";
|
|
3910
4307
|
var CACHE_FILE_NAME = "model-cache.json";
|
|
3911
4308
|
var LIVE_DEFAULT_CONTEXT = 128e3;
|
|
3912
4309
|
function modelCachePath(configDir) {
|
|
3913
|
-
return
|
|
4310
|
+
return join3(configDir, CACHE_FILE_NAME);
|
|
3914
4311
|
}
|
|
3915
4312
|
function loadModelCache(configDir) {
|
|
3916
4313
|
const path3 = modelCachePath(configDir);
|
|
3917
|
-
if (!
|
|
4314
|
+
if (!existsSync3(path3)) return { version: 1, providers: {} };
|
|
3918
4315
|
try {
|
|
3919
|
-
const parsed = JSON.parse(
|
|
4316
|
+
const parsed = JSON.parse(readFileSync3(path3, "utf-8"));
|
|
3920
4317
|
if (parsed.version !== 1 || !parsed.providers || typeof parsed.providers !== "object") {
|
|
3921
4318
|
return { version: 1, providers: {} };
|
|
3922
4319
|
}
|
|
@@ -3961,7 +4358,7 @@ function setCachedModels(configDir, providerId, models, baseUrl) {
|
|
|
3961
4358
|
function clearModelCache(configDir, providerId) {
|
|
3962
4359
|
const path3 = modelCachePath(configDir);
|
|
3963
4360
|
if (!providerId) {
|
|
3964
|
-
if (
|
|
4361
|
+
if (existsSync3(path3)) unlinkSync(path3);
|
|
3965
4362
|
return;
|
|
3966
4363
|
}
|
|
3967
4364
|
const cache = loadModelCache(configDir);
|
|
@@ -4152,8 +4549,8 @@ var ProviderRegistry = class {
|
|
|
4152
4549
|
};
|
|
4153
4550
|
|
|
4154
4551
|
// src/session/session-manager.ts
|
|
4155
|
-
import { readFileSync as
|
|
4156
|
-
import { join as
|
|
4552
|
+
import { readFileSync as readFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync, unlinkSync as unlinkSync2, statSync } from "fs";
|
|
4553
|
+
import { join as join4 } from "path";
|
|
4157
4554
|
import { v4 as uuidv4 } from "uuid";
|
|
4158
4555
|
|
|
4159
4556
|
// src/core/types.ts
|
|
@@ -4738,7 +5135,7 @@ var SessionManager = class {
|
|
|
4738
5135
|
if (!this._current) return;
|
|
4739
5136
|
if (!force && !this._current.dirty) return;
|
|
4740
5137
|
mkdirSync3(this.historyDir, { recursive: true });
|
|
4741
|
-
const filePath =
|
|
5138
|
+
const filePath = join4(this.historyDir, `${this._current.id}.json`);
|
|
4742
5139
|
const raw = this._current.toJSON();
|
|
4743
5140
|
const opts = this.redactOptionsForSave();
|
|
4744
5141
|
const { value: payload, hits } = redactJson(raw, opts);
|
|
@@ -4759,13 +5156,13 @@ var SessionManager = class {
|
|
|
4759
5156
|
}
|
|
4760
5157
|
}
|
|
4761
5158
|
loadSession(id) {
|
|
4762
|
-
const filePath =
|
|
4763
|
-
if (!
|
|
5159
|
+
const filePath = join4(this.historyDir, `${id}.json`);
|
|
5160
|
+
if (!existsSync4(filePath)) {
|
|
4764
5161
|
throw new Error(`Session ${id} not found`);
|
|
4765
5162
|
}
|
|
4766
5163
|
let data;
|
|
4767
5164
|
try {
|
|
4768
|
-
data = JSON.parse(
|
|
5165
|
+
data = JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
4769
5166
|
} catch (err) {
|
|
4770
5167
|
throw new Error(`Session ${id} is corrupted: ${err instanceof Error ? err.message : String(err)}`);
|
|
4771
5168
|
}
|
|
@@ -4774,7 +5171,7 @@ var SessionManager = class {
|
|
|
4774
5171
|
return session;
|
|
4775
5172
|
}
|
|
4776
5173
|
listSessions(opts) {
|
|
4777
|
-
if (!
|
|
5174
|
+
if (!existsSync4(this.historyDir)) return [];
|
|
4778
5175
|
try {
|
|
4779
5176
|
return listFromIndex(this.historyDir, opts);
|
|
4780
5177
|
} catch {
|
|
@@ -4783,7 +5180,7 @@ var SessionManager = class {
|
|
|
4783
5180
|
const metas = [];
|
|
4784
5181
|
for (const file of files) {
|
|
4785
5182
|
try {
|
|
4786
|
-
const filePath =
|
|
5183
|
+
const filePath = join4(this.historyDir, file);
|
|
4787
5184
|
const meta = readSessionMeta(filePath);
|
|
4788
5185
|
if (!meta) continue;
|
|
4789
5186
|
if (opts?.includeFileSize) {
|
|
@@ -4803,8 +5200,8 @@ var SessionManager = class {
|
|
|
4803
5200
|
return metas.sort((a, b) => b.updated.getTime() - a.updated.getTime());
|
|
4804
5201
|
}
|
|
4805
5202
|
deleteSession(id) {
|
|
4806
|
-
const filePath =
|
|
4807
|
-
if (!
|
|
5203
|
+
const filePath = join4(this.historyDir, `${id}.json`);
|
|
5204
|
+
if (!existsSync4(filePath)) return false;
|
|
4808
5205
|
try {
|
|
4809
5206
|
unlinkSync2(filePath);
|
|
4810
5207
|
if (this._current && this._current.id === id) {
|
|
@@ -4845,20 +5242,20 @@ var SessionManager = class {
|
|
|
4845
5242
|
* 每个 session 最多返回 3 条匹配片段,全局最多 maxResults 个 session。
|
|
4846
5243
|
*/
|
|
4847
5244
|
searchMessages(query, maxResults = 20) {
|
|
4848
|
-
if (!
|
|
5245
|
+
if (!existsSync4(this.historyDir)) return [];
|
|
4849
5246
|
const q = query.toLowerCase();
|
|
4850
5247
|
const candidateIds = searchCandidates(this.historyDir, q);
|
|
4851
5248
|
let filesToSearch;
|
|
4852
5249
|
if (candidateIds !== null) {
|
|
4853
|
-
filesToSearch = candidateIds.map((id) =>
|
|
5250
|
+
filesToSearch = candidateIds.map((id) => join4(this.historyDir, `${id}.json`)).filter((fp) => existsSync4(fp));
|
|
4854
5251
|
} else {
|
|
4855
|
-
filesToSearch = readdirSync(this.historyDir).filter((f) => f.endsWith(".json")).map((f) =>
|
|
5252
|
+
filesToSearch = readdirSync(this.historyDir).filter((f) => f.endsWith(".json")).map((f) => join4(this.historyDir, f));
|
|
4856
5253
|
}
|
|
4857
5254
|
const results = [];
|
|
4858
5255
|
for (const filePath of filesToSearch) {
|
|
4859
5256
|
if (results.length >= maxResults) break;
|
|
4860
5257
|
try {
|
|
4861
|
-
const data = JSON.parse(
|
|
5258
|
+
const data = JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
4862
5259
|
const messages = data.messages ?? [];
|
|
4863
5260
|
const matches = [];
|
|
4864
5261
|
for (const msg of messages) {
|
|
@@ -4905,12 +5302,12 @@ var SessionManager = class {
|
|
|
4905
5302
|
|
|
4906
5303
|
// src/tools/builtin/bash.ts
|
|
4907
5304
|
import { spawn as spawn3 } from "child_process";
|
|
4908
|
-
import { existsSync as
|
|
5305
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
4909
5306
|
import { platform as platform3 } from "os";
|
|
4910
5307
|
import { resolve } from "path";
|
|
4911
5308
|
|
|
4912
5309
|
// src/tools/undo-stack.ts
|
|
4913
|
-
import { readFileSync as
|
|
5310
|
+
import { readFileSync as readFileSync5, writeFileSync, unlinkSync as unlinkSync3, rmdirSync, existsSync as existsSync5 } from "fs";
|
|
4914
5311
|
var MAX_UNDO_DEPTH = 20;
|
|
4915
5312
|
var UndoStack = class {
|
|
4916
5313
|
stack = [];
|
|
@@ -4921,9 +5318,9 @@ var UndoStack = class {
|
|
|
4921
5318
|
*/
|
|
4922
5319
|
push(filePath, description) {
|
|
4923
5320
|
let previousContent = null;
|
|
4924
|
-
if (
|
|
5321
|
+
if (existsSync5(filePath)) {
|
|
4925
5322
|
try {
|
|
4926
|
-
previousContent =
|
|
5323
|
+
previousContent = readFileSync5(filePath, "utf-8");
|
|
4927
5324
|
} catch {
|
|
4928
5325
|
return;
|
|
4929
5326
|
}
|
|
@@ -4977,7 +5374,7 @@ var UndoStack = class {
|
|
|
4977
5374
|
try {
|
|
4978
5375
|
if (entry.previousContent === null) {
|
|
4979
5376
|
if (entry.isDirectory) {
|
|
4980
|
-
if (
|
|
5377
|
+
if (existsSync5(entry.filePath)) {
|
|
4981
5378
|
try {
|
|
4982
5379
|
rmdirSync(entry.filePath);
|
|
4983
5380
|
return { entry, result: `Removed newly created directory: ${entry.filePath}` };
|
|
@@ -4987,7 +5384,7 @@ var UndoStack = class {
|
|
|
4987
5384
|
}
|
|
4988
5385
|
return { entry, result: `Directory already removed: ${entry.filePath}` };
|
|
4989
5386
|
} else {
|
|
4990
|
-
if (
|
|
5387
|
+
if (existsSync5(entry.filePath)) {
|
|
4991
5388
|
unlinkSync3(entry.filePath);
|
|
4992
5389
|
}
|
|
4993
5390
|
return { entry, result: `Deleted newly created file: ${entry.filePath}` };
|
|
@@ -5378,7 +5775,7 @@ Important rules:
|
|
|
5378
5775
|
throw new ToolError("bash", blockingHint);
|
|
5379
5776
|
}
|
|
5380
5777
|
let currentCwd = getCwd();
|
|
5381
|
-
if (!
|
|
5778
|
+
if (!existsSync6(currentCwd)) {
|
|
5382
5779
|
const fallback = process.cwd();
|
|
5383
5780
|
process.stderr.write(
|
|
5384
5781
|
`[bash] Previous cwd "${currentCwd}" no longer exists, reset to "${fallback}"
|
|
@@ -5390,7 +5787,7 @@ Important rules:
|
|
|
5390
5787
|
let effectiveCwd = currentCwd;
|
|
5391
5788
|
if (cwdArg) {
|
|
5392
5789
|
const resolved = resolve(currentCwd, cwdArg);
|
|
5393
|
-
if (!
|
|
5790
|
+
if (!existsSync6(resolved)) {
|
|
5394
5791
|
throw new ToolError(
|
|
5395
5792
|
"bash",
|
|
5396
5793
|
`cwd directory does not exist: "${resolved}". Create it first (e.g. mkdir -p "${resolved}") before specifying it as cwd.`
|
|
@@ -5410,7 +5807,7 @@ Important rules:
|
|
|
5410
5807
|
const parsedTargets = parseCreationTargets(command, effectiveCwd);
|
|
5411
5808
|
const parsedTargetsBefore = /* @__PURE__ */ new Map();
|
|
5412
5809
|
for (const t of parsedTargets) {
|
|
5413
|
-
parsedTargetsBefore.set(t,
|
|
5810
|
+
parsedTargetsBefore.set(t, existsSync6(t));
|
|
5414
5811
|
}
|
|
5415
5812
|
let poolResult = null;
|
|
5416
5813
|
try {
|
|
@@ -5707,7 +6104,7 @@ function pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd) {
|
|
|
5707
6104
|
}
|
|
5708
6105
|
}
|
|
5709
6106
|
for (const [target, existedBefore] of parsedTargetsBefore) {
|
|
5710
|
-
if (!existedBefore && !tracked.has(target) &&
|
|
6107
|
+
if (!existedBefore && !tracked.has(target) && existsSync6(target)) {
|
|
5711
6108
|
try {
|
|
5712
6109
|
const st = statSync2(target);
|
|
5713
6110
|
if (st.isDirectory()) {
|
|
@@ -5794,7 +6191,7 @@ function updateCwdFromCommand(command, baseCwd) {
|
|
|
5794
6191
|
if (!target || target.startsWith("$") || target === "~") return;
|
|
5795
6192
|
try {
|
|
5796
6193
|
const newDir = resolve(baseCwd, target);
|
|
5797
|
-
if (
|
|
6194
|
+
if (existsSync6(newDir)) {
|
|
5798
6195
|
setCwd(newDir);
|
|
5799
6196
|
}
|
|
5800
6197
|
} catch {
|
|
@@ -5802,7 +6199,7 @@ function updateCwdFromCommand(command, baseCwd) {
|
|
|
5802
6199
|
}
|
|
5803
6200
|
|
|
5804
6201
|
// src/tools/builtin/read-file.ts
|
|
5805
|
-
import { readFileSync as
|
|
6202
|
+
import { readFileSync as readFileSync6, existsSync as existsSync7, statSync as statSync3, readdirSync as readdirSync3 } from "fs";
|
|
5806
6203
|
import { execFileSync } from "child_process";
|
|
5807
6204
|
import { extname, resolve as resolve2, basename, sep, dirname as dirname2 } from "path";
|
|
5808
6205
|
import { homedir as homedir2 } from "os";
|
|
@@ -6079,7 +6476,7 @@ var readFileTool = {
|
|
|
6079
6476
|
const normalizedPath = resolve2(filePath);
|
|
6080
6477
|
const refusal = getHardRefusal(normalizedPath);
|
|
6081
6478
|
if (refusal) return refusal;
|
|
6082
|
-
if (!
|
|
6479
|
+
if (!existsSync7(normalizedPath)) {
|
|
6083
6480
|
const suggestions = findSimilarFiles(filePath);
|
|
6084
6481
|
if (suggestions.length > 0) {
|
|
6085
6482
|
throw new ToolError(
|
|
@@ -6130,7 +6527,7 @@ ${pdfText}`;
|
|
|
6130
6527
|
}
|
|
6131
6528
|
const dir = dirname2(normalizedPath);
|
|
6132
6529
|
const nameNoExt = basename(normalizedPath, ext);
|
|
6133
|
-
const textAlts = [".md", ".txt", ".html"].map((e) => resolve2(dir, nameNoExt + e)).filter(
|
|
6530
|
+
const textAlts = [".md", ".txt", ".html"].map((e) => resolve2(dir, nameNoExt + e)).filter(existsSync7);
|
|
6134
6531
|
if (textAlts.length > 0) {
|
|
6135
6532
|
return `[PDF file: ${filePath}]
|
|
6136
6533
|
Cannot extract text from this PDF, but found alternative text versions:
|
|
@@ -6143,7 +6540,7 @@ Suggestion: read existing text versions (.md / .txt) in the project, or install
|
|
|
6143
6540
|
}
|
|
6144
6541
|
if (ext === ".ipynb") {
|
|
6145
6542
|
try {
|
|
6146
|
-
const raw =
|
|
6543
|
+
const raw = readFileSync6(normalizedPath, "utf-8");
|
|
6147
6544
|
const nb = parseNotebook(raw);
|
|
6148
6545
|
return renderNotebookAsText(nb, filePath);
|
|
6149
6546
|
} catch (err) {
|
|
@@ -6156,7 +6553,7 @@ ${err.message}`;
|
|
|
6156
6553
|
This is a binary file and cannot be read as text. If there is a text version (.md / .txt) in the project, please read that instead.`;
|
|
6157
6554
|
}
|
|
6158
6555
|
const { readFile: readFile2 } = await import("fs/promises");
|
|
6159
|
-
const buf = size > 1048576 ? await readFile2(normalizedPath) :
|
|
6556
|
+
const buf = size > 1048576 ? await readFile2(normalizedPath) : readFileSync6(normalizedPath);
|
|
6160
6557
|
if (encoding === "base64") {
|
|
6161
6558
|
return `[File: ${filePath} | base64]
|
|
6162
6559
|
|
|
@@ -6194,12 +6591,12 @@ ${content}`;
|
|
|
6194
6591
|
};
|
|
6195
6592
|
|
|
6196
6593
|
// src/tools/builtin/write-file.ts
|
|
6197
|
-
import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as
|
|
6594
|
+
import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync11, statSync as statSync4 } from "fs";
|
|
6198
6595
|
import { dirname as dirname5 } from "path";
|
|
6199
6596
|
|
|
6200
6597
|
// src/tools/executor.ts
|
|
6201
6598
|
import chalk3 from "chalk";
|
|
6202
|
-
import { existsSync as
|
|
6599
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
6203
6600
|
import { tmpdir } from "os";
|
|
6204
6601
|
|
|
6205
6602
|
// src/core/readline-internal.ts
|
|
@@ -6439,8 +6836,8 @@ function simpleDiff(oldLines, newLines) {
|
|
|
6439
6836
|
// src/tools/hooks.ts
|
|
6440
6837
|
import { execSync } from "child_process";
|
|
6441
6838
|
import { createHash } from "crypto";
|
|
6442
|
-
import { existsSync as
|
|
6443
|
-
import { join as
|
|
6839
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7 } from "fs";
|
|
6840
|
+
import { join as join5 } from "path";
|
|
6444
6841
|
var TRUST_FILE = "hooks-trust.json";
|
|
6445
6842
|
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
6446
6843
|
function asArray(entry) {
|
|
@@ -6449,13 +6846,13 @@ function asArray(entry) {
|
|
|
6449
6846
|
return raw.map((item) => typeof item === "string" ? { command: item } : item);
|
|
6450
6847
|
}
|
|
6451
6848
|
function hookTrustPath(configDir) {
|
|
6452
|
-
return
|
|
6849
|
+
return join5(configDir, TRUST_FILE);
|
|
6453
6850
|
}
|
|
6454
6851
|
function loadHookTrustStore(configDir) {
|
|
6455
6852
|
const file = hookTrustPath(configDir);
|
|
6456
|
-
if (!
|
|
6853
|
+
if (!existsSync8(file)) return { records: [] };
|
|
6457
6854
|
try {
|
|
6458
|
-
const parsed = JSON.parse(
|
|
6855
|
+
const parsed = JSON.parse(readFileSync7(file, "utf-8"));
|
|
6459
6856
|
return { records: Array.isArray(parsed.records) ? parsed.records : [] };
|
|
6460
6857
|
} catch {
|
|
6461
6858
|
return { records: [] };
|
|
@@ -6599,7 +6996,7 @@ function runHook(template, vars) {
|
|
|
6599
6996
|
}
|
|
6600
6997
|
|
|
6601
6998
|
// src/tools/permissions.ts
|
|
6602
|
-
import { existsSync as
|
|
6999
|
+
import { existsSync as existsSync9, realpathSync } from "fs";
|
|
6603
7000
|
import { basename as basename2, dirname as dirname3, isAbsolute, resolve as resolve3 } from "path";
|
|
6604
7001
|
function checkPermission(toolName, args, dangerLevel, rules, defaultAction = "confirm") {
|
|
6605
7002
|
for (const rule of rules) {
|
|
@@ -6648,7 +7045,7 @@ function normalizePathForPermission(value) {
|
|
|
6648
7045
|
function canonicalizeWithNearestParent(value) {
|
|
6649
7046
|
const missing = [];
|
|
6650
7047
|
let cursor = resolve3(value);
|
|
6651
|
-
while (!
|
|
7048
|
+
while (!existsSync9(cursor)) {
|
|
6652
7049
|
const parent = dirname3(cursor);
|
|
6653
7050
|
if (parent === cursor) return null;
|
|
6654
7051
|
missing.unshift(basename2(cursor));
|
|
@@ -7013,8 +7410,8 @@ var theme = new Proxy(DARK_THEME, {
|
|
|
7013
7410
|
});
|
|
7014
7411
|
|
|
7015
7412
|
// src/diagnostics/tool-stats.ts
|
|
7016
|
-
import { existsSync as
|
|
7017
|
-
import { join as
|
|
7413
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
7414
|
+
import { join as join6, dirname as dirname4 } from "path";
|
|
7018
7415
|
import { homedir as homedir3 } from "os";
|
|
7019
7416
|
var STATS_FILE_NAME = "tool-stats.json";
|
|
7020
7417
|
var STATS_VERSION = 1;
|
|
@@ -7024,16 +7421,16 @@ var dirty = false;
|
|
|
7024
7421
|
var pendingWrites = 0;
|
|
7025
7422
|
var configDirOverride;
|
|
7026
7423
|
function statsFilePath() {
|
|
7027
|
-
const base = configDirOverride ??
|
|
7028
|
-
return
|
|
7424
|
+
const base = configDirOverride ?? join6(homedir3(), CONFIG_DIR_NAME);
|
|
7425
|
+
return join6(base, STATS_FILE_NAME);
|
|
7029
7426
|
}
|
|
7030
7427
|
function load() {
|
|
7031
7428
|
const path3 = statsFilePath();
|
|
7032
|
-
if (!
|
|
7429
|
+
if (!existsSync10(path3)) {
|
|
7033
7430
|
return { version: STATS_VERSION, startedAt: (/* @__PURE__ */ new Date()).toISOString(), entries: {} };
|
|
7034
7431
|
}
|
|
7035
7432
|
try {
|
|
7036
|
-
const raw = JSON.parse(
|
|
7433
|
+
const raw = JSON.parse(readFileSync8(path3, "utf-8"));
|
|
7037
7434
|
if (raw.version !== STATS_VERSION || !raw.entries) {
|
|
7038
7435
|
return { version: STATS_VERSION, startedAt: (/* @__PURE__ */ new Date()).toISOString(), entries: {} };
|
|
7039
7436
|
}
|
|
@@ -7115,8 +7512,8 @@ function getTopUsedTools(limit = 5) {
|
|
|
7115
7512
|
}
|
|
7116
7513
|
|
|
7117
7514
|
// src/tools/action-classifier.ts
|
|
7118
|
-
import { existsSync as
|
|
7119
|
-
import { isAbsolute as isAbsolute2, join as
|
|
7515
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
7516
|
+
import { isAbsolute as isAbsolute2, join as join7, resolve as resolve4 } from "path";
|
|
7120
7517
|
var RECENT_DENIED_LIMIT = 50;
|
|
7121
7518
|
var recentlyDenied = [];
|
|
7122
7519
|
function recordRecentlyDeniedAutoAction(call, classification) {
|
|
@@ -7157,10 +7554,10 @@ function splitShellWords(command) {
|
|
|
7157
7554
|
return out.filter(Boolean);
|
|
7158
7555
|
}
|
|
7159
7556
|
function readPackageManifest(root) {
|
|
7160
|
-
const p =
|
|
7161
|
-
if (!
|
|
7557
|
+
const p = join7(root, "package.json");
|
|
7558
|
+
if (!existsSync11(p)) return null;
|
|
7162
7559
|
try {
|
|
7163
|
-
return JSON.parse(
|
|
7560
|
+
return JSON.parse(readFileSync9(p, "utf-8"));
|
|
7164
7561
|
} catch {
|
|
7165
7562
|
return null;
|
|
7166
7563
|
}
|
|
@@ -7176,10 +7573,10 @@ function manifestHasDependency(root, name) {
|
|
|
7176
7573
|
}
|
|
7177
7574
|
function lockfileMentions(root, name) {
|
|
7178
7575
|
for (const file of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]) {
|
|
7179
|
-
const p =
|
|
7180
|
-
if (!
|
|
7576
|
+
const p = join7(root, file);
|
|
7577
|
+
if (!existsSync11(p)) continue;
|
|
7181
7578
|
try {
|
|
7182
|
-
if (
|
|
7579
|
+
if (readFileSync9(p, "utf-8").includes(name)) return true;
|
|
7183
7580
|
} catch {
|
|
7184
7581
|
}
|
|
7185
7582
|
}
|
|
@@ -7753,10 +8150,10 @@ var ToolExecutor = class {
|
|
|
7753
8150
|
if (call.name === "write_file") {
|
|
7754
8151
|
const { path: filePath, content: chunk, append } = resolveWriteFileArgs(call.arguments);
|
|
7755
8152
|
if (!filePath) return;
|
|
7756
|
-
if (
|
|
8153
|
+
if (existsSync12(filePath)) {
|
|
7757
8154
|
let oldContent;
|
|
7758
8155
|
try {
|
|
7759
|
-
oldContent =
|
|
8156
|
+
oldContent = readFileSync10(filePath, "utf-8");
|
|
7760
8157
|
} catch {
|
|
7761
8158
|
return;
|
|
7762
8159
|
}
|
|
@@ -7780,7 +8177,7 @@ var ToolExecutor = class {
|
|
|
7780
8177
|
}
|
|
7781
8178
|
} else if (call.name === "edit_file") {
|
|
7782
8179
|
const filePath = String(call.arguments["path"] ?? "");
|
|
7783
|
-
if (!filePath || !
|
|
8180
|
+
if (!filePath || !existsSync12(filePath)) return;
|
|
7784
8181
|
const oldStr = call.arguments["old_str"];
|
|
7785
8182
|
const newStr = call.arguments["new_str"];
|
|
7786
8183
|
if (oldStr !== void 0) {
|
|
@@ -7807,7 +8204,7 @@ var ToolExecutor = class {
|
|
|
7807
8204
|
const to = Number(call.arguments["delete_to_line"] ?? from);
|
|
7808
8205
|
let fileContent;
|
|
7809
8206
|
try {
|
|
7810
|
-
fileContent =
|
|
8207
|
+
fileContent = readFileSync10(filePath, "utf-8");
|
|
7811
8208
|
} catch {
|
|
7812
8209
|
return;
|
|
7813
8210
|
}
|
|
@@ -7993,7 +8390,7 @@ var subAgentGuard = {
|
|
|
7993
8390
|
|
|
7994
8391
|
// src/tools/builtin/write-file.ts
|
|
7995
8392
|
function countFileLines(filePath) {
|
|
7996
|
-
return
|
|
8393
|
+
return readFileSync11(filePath, "utf-8").split("\n").length;
|
|
7997
8394
|
}
|
|
7998
8395
|
var writeFileTool = {
|
|
7999
8396
|
definition: {
|
|
@@ -8076,7 +8473,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
8076
8473
|
};
|
|
8077
8474
|
|
|
8078
8475
|
// src/tools/builtin/edit-file.ts
|
|
8079
|
-
import { readFileSync as
|
|
8476
|
+
import { readFileSync as readFileSync12, existsSync as existsSync13 } from "fs";
|
|
8080
8477
|
|
|
8081
8478
|
// src/tools/builtin/patch-apply.ts
|
|
8082
8479
|
function parseUnifiedDiff(patch) {
|
|
@@ -8444,7 +8841,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
8444
8841
|
const filePath = String(args["path"] ?? args["file"] ?? "");
|
|
8445
8842
|
const encoding = args["encoding"] ?? "utf-8";
|
|
8446
8843
|
if (!filePath) throw new ToolError("edit_file", "path is required");
|
|
8447
|
-
if (!
|
|
8844
|
+
if (!existsSync13(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
|
|
8448
8845
|
const verdict = classifyWritePath(filePath);
|
|
8449
8846
|
if (verdict.sensitive && subAgentGuard.active) {
|
|
8450
8847
|
throw new ToolError(
|
|
@@ -8452,7 +8849,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
8452
8849
|
`Refused: sub-agents cannot edit sensitive paths \u2014 ${verdict.reason}. If this is genuinely needed, ask the parent agent to perform the edit so the user can approve it.`
|
|
8453
8850
|
);
|
|
8454
8851
|
}
|
|
8455
|
-
const original =
|
|
8852
|
+
const original = readFileSync12(filePath, encoding);
|
|
8456
8853
|
if (args["patch"] !== void 0) {
|
|
8457
8854
|
const patchText = String(args["patch"] ?? "");
|
|
8458
8855
|
const stopOnError = args["stop_on_error"] !== false;
|
|
@@ -8623,8 +9020,8 @@ function truncatePreview(str, maxLen = 80) {
|
|
|
8623
9020
|
}
|
|
8624
9021
|
|
|
8625
9022
|
// src/tools/builtin/list-dir.ts
|
|
8626
|
-
import { readdirSync as readdirSync4, statSync as statSync5, existsSync as
|
|
8627
|
-
import { join as
|
|
9023
|
+
import { readdirSync as readdirSync4, statSync as statSync5, existsSync as existsSync14 } from "fs";
|
|
9024
|
+
import { join as join8, basename as basename4 } from "path";
|
|
8628
9025
|
var listDirTool = {
|
|
8629
9026
|
definition: {
|
|
8630
9027
|
name: "list_dir",
|
|
@@ -8646,7 +9043,7 @@ var listDirTool = {
|
|
|
8646
9043
|
async execute(args) {
|
|
8647
9044
|
const dirPath = String(args["path"] ?? process.cwd());
|
|
8648
9045
|
const recursive = Boolean(args["recursive"] ?? false);
|
|
8649
|
-
if (!
|
|
9046
|
+
if (!existsSync14(dirPath)) {
|
|
8650
9047
|
const targetName = basename4(dirPath).toLowerCase();
|
|
8651
9048
|
const cwd = process.cwd();
|
|
8652
9049
|
const suggestions = [];
|
|
@@ -8704,11 +9101,11 @@ function listRecursive(basePath, indent, recursive, lines) {
|
|
|
8704
9101
|
if (entry.isDirectory()) {
|
|
8705
9102
|
lines.push(`${indent}\u{1F4C1} ${entry.name}/`);
|
|
8706
9103
|
if (recursive) {
|
|
8707
|
-
listRecursive(
|
|
9104
|
+
listRecursive(join8(basePath, entry.name), indent + " ", true, lines);
|
|
8708
9105
|
}
|
|
8709
9106
|
} else {
|
|
8710
9107
|
try {
|
|
8711
|
-
const stat = statSync5(
|
|
9108
|
+
const stat = statSync5(join8(basePath, entry.name));
|
|
8712
9109
|
const size = formatSize(stat.size);
|
|
8713
9110
|
lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
|
|
8714
9111
|
} catch {
|
|
@@ -8724,9 +9121,9 @@ function formatSize(bytes) {
|
|
|
8724
9121
|
}
|
|
8725
9122
|
|
|
8726
9123
|
// src/tools/builtin/grep-files.ts
|
|
8727
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
9124
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync13, statSync as statSync6, existsSync as existsSync15 } from "fs";
|
|
8728
9125
|
import { readFile } from "fs/promises";
|
|
8729
|
-
import { join as
|
|
9126
|
+
import { join as join9, relative } from "path";
|
|
8730
9127
|
var grepFilesTool = {
|
|
8731
9128
|
definition: {
|
|
8732
9129
|
name: "grep_files",
|
|
@@ -8777,7 +9174,7 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
|
|
|
8777
9174
|
const contextLines = Math.max(0, Number(args["context_lines"] ?? 0));
|
|
8778
9175
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
|
|
8779
9176
|
if (!pattern) throw new ToolError("grep_files", "pattern is required");
|
|
8780
|
-
if (!
|
|
9177
|
+
if (!existsSync15(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
|
|
8781
9178
|
const MAX_PATTERN_LENGTH = 1e3;
|
|
8782
9179
|
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
8783
9180
|
throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
|
|
@@ -8871,11 +9268,11 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
|
8871
9268
|
if (paths.length >= maxFiles) return;
|
|
8872
9269
|
if (entry.isDirectory()) {
|
|
8873
9270
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
8874
|
-
walk(
|
|
9271
|
+
walk(join9(dirPath, entry.name));
|
|
8875
9272
|
} else if (entry.isFile()) {
|
|
8876
9273
|
if (isBinary(entry.name)) continue;
|
|
8877
9274
|
if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
|
|
8878
|
-
const fullPath =
|
|
9275
|
+
const fullPath = join9(dirPath, entry.name);
|
|
8879
9276
|
try {
|
|
8880
9277
|
if (statSync6(fullPath).size > 1e6) continue;
|
|
8881
9278
|
} catch {
|
|
@@ -8930,7 +9327,7 @@ function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, r
|
|
|
8930
9327
|
}
|
|
8931
9328
|
let content;
|
|
8932
9329
|
try {
|
|
8933
|
-
content =
|
|
9330
|
+
content = readFileSync13(fullPath, "utf-8");
|
|
8934
9331
|
} catch {
|
|
8935
9332
|
return;
|
|
8936
9333
|
}
|
|
@@ -8961,8 +9358,8 @@ function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, r
|
|
|
8961
9358
|
}
|
|
8962
9359
|
|
|
8963
9360
|
// src/tools/builtin/glob-files.ts
|
|
8964
|
-
import { readdirSync as readdirSync6, statSync as statSync7, existsSync as
|
|
8965
|
-
import { join as
|
|
9361
|
+
import { readdirSync as readdirSync6, statSync as statSync7, existsSync as existsSync16 } from "fs";
|
|
9362
|
+
import { join as join10, relative as relative2, basename as basename5 } from "path";
|
|
8966
9363
|
var globFilesTool = {
|
|
8967
9364
|
definition: {
|
|
8968
9365
|
name: "glob_files",
|
|
@@ -8995,7 +9392,7 @@ Results sorted by most recent modification time. Automatically skips node_module
|
|
|
8995
9392
|
const rootPath = String(args["path"] ?? process.cwd());
|
|
8996
9393
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
|
|
8997
9394
|
if (!pattern) throw new ToolError("glob_files", "pattern is required");
|
|
8998
|
-
if (!
|
|
9395
|
+
if (!existsSync16(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
|
|
8999
9396
|
const regex = globToRegex(pattern);
|
|
9000
9397
|
const matches = [];
|
|
9001
9398
|
collectMatchingFiles(rootPath, rootPath, regex, matches, maxResults);
|
|
@@ -9072,7 +9469,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
9072
9469
|
}
|
|
9073
9470
|
for (const entry of entries) {
|
|
9074
9471
|
if (results.length >= maxResults) break;
|
|
9075
|
-
const fullPath =
|
|
9472
|
+
const fullPath = join10(dirPath, entry.name);
|
|
9076
9473
|
if (entry.isDirectory()) {
|
|
9077
9474
|
if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
9078
9475
|
collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
|
|
@@ -9914,10 +10311,10 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
9914
10311
|
};
|
|
9915
10312
|
|
|
9916
10313
|
// src/tools/builtin/save-memory.ts
|
|
9917
|
-
import { join as
|
|
10314
|
+
import { join as join11 } from "path";
|
|
9918
10315
|
import { homedir as homedir5 } from "os";
|
|
9919
10316
|
function getConfigDir() {
|
|
9920
|
-
return
|
|
10317
|
+
return join11(homedir5(), CONFIG_DIR_NAME);
|
|
9921
10318
|
}
|
|
9922
10319
|
function parseSensitivity(value) {
|
|
9923
10320
|
return value === "low" || value === "medium" || value === "high" ? value : void 0;
|
|
@@ -10266,13 +10663,13 @@ function formatResults2(query, data, _requested) {
|
|
|
10266
10663
|
}
|
|
10267
10664
|
|
|
10268
10665
|
// src/agents/agent-config.ts
|
|
10269
|
-
import { existsSync as
|
|
10270
|
-
import { join as
|
|
10666
|
+
import { existsSync as existsSync18, readdirSync as readdirSync8, readFileSync as readFileSync15 } from "fs";
|
|
10667
|
+
import { join as join13 } from "path";
|
|
10271
10668
|
import { homedir as homedir6 } from "os";
|
|
10272
10669
|
|
|
10273
10670
|
// src/plugins/plugin-manager.ts
|
|
10274
|
-
import { existsSync as
|
|
10275
|
-
import { basename as basename6, dirname as dirname7, join as
|
|
10671
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync14, realpathSync as realpathSync2, cpSync, rmSync, statSync as statSync8 } from "fs";
|
|
10672
|
+
import { basename as basename6, dirname as dirname7, join as join12, relative as relative3, resolve as resolve6, sep as sep3 } from "path";
|
|
10276
10673
|
import { createHash as createHash2 } from "crypto";
|
|
10277
10674
|
import { z as z2 } from "zod";
|
|
10278
10675
|
var MANIFEST_RELATIVE = ".aicli-plugin/plugin.json";
|
|
@@ -10332,19 +10729,19 @@ var PluginManifestSchema = z2.object({
|
|
|
10332
10729
|
permissionHints: z2.array(z2.string()).default([])
|
|
10333
10730
|
}).strict();
|
|
10334
10731
|
function pluginRoot(configDir) {
|
|
10335
|
-
return
|
|
10732
|
+
return join12(configDir, PLUGINS_DIR_NAME);
|
|
10336
10733
|
}
|
|
10337
10734
|
function pluginManifestPath(pluginDir) {
|
|
10338
|
-
return
|
|
10735
|
+
return join12(pluginDir, MANIFEST_RELATIVE);
|
|
10339
10736
|
}
|
|
10340
10737
|
function statePath(configDir) {
|
|
10341
|
-
return
|
|
10738
|
+
return join12(pluginRoot(configDir), STATE_FILE);
|
|
10342
10739
|
}
|
|
10343
10740
|
function loadState(configDir) {
|
|
10344
10741
|
const file = statePath(configDir);
|
|
10345
|
-
if (!
|
|
10742
|
+
if (!existsSync17(file)) return { version: 2, plugins: [] };
|
|
10346
10743
|
try {
|
|
10347
|
-
const parsed = JSON.parse(
|
|
10744
|
+
const parsed = JSON.parse(readFileSync14(file, "utf-8"));
|
|
10348
10745
|
const rawPlugins = Array.isArray(parsed.plugins) ? parsed.plugins : [];
|
|
10349
10746
|
return { version: 2, plugins: rawPlugins.map((p) => migrateEntry(p)) };
|
|
10350
10747
|
} catch {
|
|
@@ -10402,7 +10799,7 @@ function extractCommandPaths(command, pluginDir) {
|
|
|
10402
10799
|
try {
|
|
10403
10800
|
const abs = resolve6(resolvedDir, ref);
|
|
10404
10801
|
if (!abs.startsWith(resolvedDir + sep3)) continue;
|
|
10405
|
-
if (
|
|
10802
|
+
if (existsSync17(abs)) {
|
|
10406
10803
|
const real = realpathSync2(abs);
|
|
10407
10804
|
if (!real.startsWith(resolvedDir + sep3)) continue;
|
|
10408
10805
|
valid.push(relative3(resolvedDir, real));
|
|
@@ -10434,8 +10831,8 @@ function hashPluginContent(pluginDir, manifest) {
|
|
|
10434
10831
|
}
|
|
10435
10832
|
const sorted = [...assetRels].sort();
|
|
10436
10833
|
const hasher = createHash2("sha256");
|
|
10437
|
-
const manifestPath =
|
|
10438
|
-
hasher.update(normalizeForHash(
|
|
10834
|
+
const manifestPath = join12(resolvedDir, MANIFEST_RELATIVE);
|
|
10835
|
+
hasher.update(normalizeForHash(readFileSync14(manifestPath)));
|
|
10439
10836
|
for (const rel of sorted) {
|
|
10440
10837
|
const abs = resolve6(resolvedDir, rel);
|
|
10441
10838
|
if (!abs.startsWith(resolvedDir + sep3)) {
|
|
@@ -10455,7 +10852,7 @@ function hashPluginContent(pluginDir, manifest) {
|
|
|
10455
10852
|
if (!realPath.startsWith(resolvedDir + sep3)) {
|
|
10456
10853
|
throw new Error(`plugin asset symlink escapes plugin dir: ${rel} \u2192 ${realPath}`);
|
|
10457
10854
|
}
|
|
10458
|
-
hasher.update(normalizeForHash(
|
|
10855
|
+
hasher.update(normalizeForHash(readFileSync14(abs)));
|
|
10459
10856
|
}
|
|
10460
10857
|
return hasher.digest("hex");
|
|
10461
10858
|
}
|
|
@@ -10463,22 +10860,22 @@ function effectiveHash(entry) {
|
|
|
10463
10860
|
return entry.contentHash ?? entry.hash ?? "";
|
|
10464
10861
|
}
|
|
10465
10862
|
function readPluginManifest(manifestPath) {
|
|
10466
|
-
return PluginManifestSchema.parse(JSON.parse(
|
|
10863
|
+
return PluginManifestSchema.parse(JSON.parse(readFileSync14(manifestPath, "utf-8")));
|
|
10467
10864
|
}
|
|
10468
10865
|
function findPluginSourceDir(inputPath) {
|
|
10469
10866
|
const abs = resolve6(inputPath);
|
|
10470
10867
|
const direct = statSync8(abs).isDirectory() ? abs : dirname7(abs);
|
|
10471
|
-
if (
|
|
10472
|
-
const nested =
|
|
10473
|
-
if (
|
|
10868
|
+
if (existsSync17(pluginManifestPath(direct))) return direct;
|
|
10869
|
+
const nested = join12(direct, ".aicli-plugin");
|
|
10870
|
+
if (existsSync17(join12(nested, "plugin.json"))) return direct;
|
|
10474
10871
|
throw new Error(`plugin manifest not found: ${MANIFEST_RELATIVE}`);
|
|
10475
10872
|
}
|
|
10476
10873
|
function installPlugin(configDir, inputPath) {
|
|
10477
10874
|
const sourceDir = findPluginSourceDir(inputPath);
|
|
10478
10875
|
const manifest = readPluginManifest(pluginManifestPath(sourceDir));
|
|
10479
|
-
const targetDir =
|
|
10876
|
+
const targetDir = join12(pluginRoot(configDir), manifest.name);
|
|
10480
10877
|
mkdirSync8(pluginRoot(configDir), { recursive: true });
|
|
10481
|
-
if (
|
|
10878
|
+
if (existsSync17(targetDir)) rmSync(targetDir, { recursive: true, force: true });
|
|
10482
10879
|
cpSync(sourceDir, targetDir, { recursive: true });
|
|
10483
10880
|
const manifestPath = pluginManifestPath(targetDir);
|
|
10484
10881
|
const contentHash = hashPluginContent(targetDir, manifest);
|
|
@@ -10536,18 +10933,18 @@ function getInstalledPlugin(configDir, name) {
|
|
|
10536
10933
|
}
|
|
10537
10934
|
function listInstalledPlugins(configDir) {
|
|
10538
10935
|
const root = pluginRoot(configDir);
|
|
10539
|
-
if (!
|
|
10936
|
+
if (!existsSync17(root)) return [];
|
|
10540
10937
|
const state2 = loadState(configDir);
|
|
10541
10938
|
const out = [];
|
|
10542
10939
|
for (const entry of readdirSync7(root)) {
|
|
10543
|
-
const dir =
|
|
10940
|
+
const dir = join12(root, entry);
|
|
10544
10941
|
try {
|
|
10545
10942
|
if (!statSync8(dir).isDirectory()) continue;
|
|
10546
10943
|
} catch {
|
|
10547
10944
|
continue;
|
|
10548
10945
|
}
|
|
10549
10946
|
const manifestPath = pluginManifestPath(dir);
|
|
10550
|
-
if (!
|
|
10947
|
+
if (!existsSync17(manifestPath)) continue;
|
|
10551
10948
|
try {
|
|
10552
10949
|
const manifest = readPluginManifest(manifestPath);
|
|
10553
10950
|
const contentHash = hashPluginContent(dir, manifest);
|
|
@@ -10587,7 +10984,7 @@ function uniqueDirs(plugin, rels) {
|
|
|
10587
10984
|
for (const rel of rels) {
|
|
10588
10985
|
const abs = resolve6(plugin.dir, rel);
|
|
10589
10986
|
if (!abs.startsWith(resolve6(plugin.dir))) continue;
|
|
10590
|
-
if (
|
|
10987
|
+
if (existsSync17(abs)) dirs.add(statSync8(abs).isDirectory() ? abs : dirname7(abs));
|
|
10591
10988
|
}
|
|
10592
10989
|
return [...dirs];
|
|
10593
10990
|
}
|
|
@@ -10750,13 +11147,13 @@ function parseAgentConfig(raw, source, path3) {
|
|
|
10750
11147
|
return cfg;
|
|
10751
11148
|
}
|
|
10752
11149
|
function loadAgentDir(dir, source) {
|
|
10753
|
-
if (!
|
|
11150
|
+
if (!existsSync18(dir)) return [];
|
|
10754
11151
|
const out = [];
|
|
10755
11152
|
for (const file of readdirSync8(dir)) {
|
|
10756
11153
|
if (!file.endsWith(".json")) continue;
|
|
10757
|
-
const path3 =
|
|
11154
|
+
const path3 = join13(dir, file);
|
|
10758
11155
|
try {
|
|
10759
|
-
const parsed = JSON.parse(
|
|
11156
|
+
const parsed = JSON.parse(readFileSync15(path3, "utf-8"));
|
|
10760
11157
|
const cfg = parseAgentConfig(parsed, source, path3);
|
|
10761
11158
|
if (cfg) out.push(cfg);
|
|
10762
11159
|
} catch {
|
|
@@ -10766,8 +11163,8 @@ function loadAgentDir(dir, source) {
|
|
|
10766
11163
|
}
|
|
10767
11164
|
function getAgentDirs(configDir, cwd = process.cwd()) {
|
|
10768
11165
|
return {
|
|
10769
|
-
user:
|
|
10770
|
-
project:
|
|
11166
|
+
user: join13(configDir ?? join13(homedir6(), CONFIG_DIR_NAME), "agents"),
|
|
11167
|
+
project: join13(cwd, CONFIG_DIR_NAME, "agents")
|
|
10771
11168
|
};
|
|
10772
11169
|
}
|
|
10773
11170
|
function listAgentConfigs(configDir, cwd = process.cwd()) {
|
|
@@ -11485,14 +11882,14 @@ var taskStopTool = {
|
|
|
11485
11882
|
|
|
11486
11883
|
// src/tools/builtin/git-tools.ts
|
|
11487
11884
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
11488
|
-
import { existsSync as
|
|
11489
|
-
import { join as
|
|
11885
|
+
import { existsSync as existsSync19 } from "fs";
|
|
11886
|
+
import { join as join14 } from "path";
|
|
11490
11887
|
function assertGitRepo(cwd) {
|
|
11491
11888
|
let dir = cwd;
|
|
11492
11889
|
const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
|
|
11493
11890
|
while (dir && dir !== root) {
|
|
11494
|
-
if (
|
|
11495
|
-
const parent =
|
|
11891
|
+
if (existsSync19(join14(dir, ".git"))) return;
|
|
11892
|
+
const parent = join14(dir, "..");
|
|
11496
11893
|
if (parent === dir) break;
|
|
11497
11894
|
dir = parent;
|
|
11498
11895
|
}
|
|
@@ -11757,7 +12154,7 @@ ${commitOutput.trim()}`;
|
|
|
11757
12154
|
};
|
|
11758
12155
|
|
|
11759
12156
|
// src/tools/builtin/notebook-edit.ts
|
|
11760
|
-
import { readFileSync as
|
|
12157
|
+
import { readFileSync as readFileSync16, existsSync as existsSync20 } from "fs";
|
|
11761
12158
|
import { writeFile } from "fs/promises";
|
|
11762
12159
|
import { resolve as resolve7, extname as extname2 } from "path";
|
|
11763
12160
|
var notebookEditTool = {
|
|
@@ -11812,10 +12209,10 @@ var notebookEditTool = {
|
|
|
11812
12209
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
11813
12210
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
11814
12211
|
}
|
|
11815
|
-
if (!
|
|
12212
|
+
if (!existsSync20(absPath)) {
|
|
11816
12213
|
throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
|
|
11817
12214
|
}
|
|
11818
|
-
const raw =
|
|
12215
|
+
const raw = readFileSync16(absPath, "utf-8");
|
|
11819
12216
|
const nb = parseNotebook(raw);
|
|
11820
12217
|
const cellIdx0 = cellIndexRaw - 1;
|
|
11821
12218
|
undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
|
|
@@ -12141,7 +12538,7 @@ ${lines.join("\n")}`;
|
|
|
12141
12538
|
};
|
|
12142
12539
|
|
|
12143
12540
|
// src/tools/builtin/recall-memory.ts
|
|
12144
|
-
import { join as
|
|
12541
|
+
import { join as join15 } from "path";
|
|
12145
12542
|
import { homedir as homedir7 } from "os";
|
|
12146
12543
|
function formatHit(h, i) {
|
|
12147
12544
|
const ts = h.chunk.timestamp.slice(0, 16).replace("T", " ");
|
|
@@ -12192,7 +12589,7 @@ var recallMemoryTool = {
|
|
|
12192
12589
|
const excludeCurrent = Boolean(args["excludeCurrentSession"]);
|
|
12193
12590
|
const currentId = args["currentSessionId"] ? String(args["currentSessionId"]) : void 0;
|
|
12194
12591
|
const minScore = args["minScore"] !== void 0 ? Number(args["minScore"]) : 0.25;
|
|
12195
|
-
const configDir =
|
|
12592
|
+
const configDir = join15(homedir7(), CONFIG_DIR_NAME);
|
|
12196
12593
|
let persistentSection = "";
|
|
12197
12594
|
try {
|
|
12198
12595
|
const persistentHits = searchPersistentMemories(configDir, query, process.cwd(), 3);
|
|
@@ -12246,8 +12643,8 @@ function estimateToolDefinitionTokens(def) {
|
|
|
12246
12643
|
|
|
12247
12644
|
// src/tools/registry.ts
|
|
12248
12645
|
import { pathToFileURL } from "url";
|
|
12249
|
-
import { existsSync as
|
|
12250
|
-
import { join as
|
|
12646
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
|
|
12647
|
+
import { join as join16 } from "path";
|
|
12251
12648
|
var ToolRegistry = class {
|
|
12252
12649
|
tools = /* @__PURE__ */ new Map();
|
|
12253
12650
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
@@ -12408,7 +12805,7 @@ var ToolRegistry = class {
|
|
|
12408
12805
|
* Returns the number of successfully loaded plugins.
|
|
12409
12806
|
*/
|
|
12410
12807
|
async loadPlugins(pluginsDir, allowPlugins = false) {
|
|
12411
|
-
if (!
|
|
12808
|
+
if (!existsSync21(pluginsDir)) {
|
|
12412
12809
|
try {
|
|
12413
12810
|
mkdirSync9(pluginsDir, { recursive: true });
|
|
12414
12811
|
} catch {
|
|
@@ -12434,12 +12831,12 @@ var ToolRegistry = class {
|
|
|
12434
12831
|
process.stderr.write(
|
|
12435
12832
|
`
|
|
12436
12833
|
[plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
|
|
12437
|
-
` + files.map((f) => ` + ${
|
|
12834
|
+
` + files.map((f) => ` + ${join16(pluginsDir, f)}`).join("\n") + "\n\n"
|
|
12438
12835
|
);
|
|
12439
12836
|
let loaded = 0;
|
|
12440
12837
|
for (const file of files) {
|
|
12441
12838
|
try {
|
|
12442
|
-
const fileUrl = pathToFileURL(
|
|
12839
|
+
const fileUrl = pathToFileURL(join16(pluginsDir, file)).href;
|
|
12443
12840
|
const mod = await import(fileUrl);
|
|
12444
12841
|
const tool = mod.tool ?? mod.default?.tool ?? mod.default;
|
|
12445
12842
|
if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
|
|
@@ -12984,11 +13381,11 @@ var McpManager = class {
|
|
|
12984
13381
|
};
|
|
12985
13382
|
|
|
12986
13383
|
// src/skills/manager.ts
|
|
12987
|
-
import { existsSync as
|
|
12988
|
-
import { join as
|
|
13384
|
+
import { existsSync as existsSync22, readdirSync as readdirSync10, mkdirSync as mkdirSync10, statSync as statSync9 } from "fs";
|
|
13385
|
+
import { join as join17 } from "path";
|
|
12989
13386
|
|
|
12990
13387
|
// src/skills/types.ts
|
|
12991
|
-
import { readFileSync as
|
|
13388
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
12992
13389
|
import { basename as basename7 } from "path";
|
|
12993
13390
|
function parseSimpleYaml(yaml) {
|
|
12994
13391
|
const result = {};
|
|
@@ -13010,7 +13407,7 @@ function parseYamlArray(value) {
|
|
|
13010
13407
|
function parseSkillFile(filePath) {
|
|
13011
13408
|
let raw;
|
|
13012
13409
|
try {
|
|
13013
|
-
raw =
|
|
13410
|
+
raw = readFileSync17(filePath, "utf-8");
|
|
13014
13411
|
} catch {
|
|
13015
13412
|
return null;
|
|
13016
13413
|
}
|
|
@@ -13058,7 +13455,7 @@ var SkillManager = class {
|
|
|
13058
13455
|
return this.skills.size;
|
|
13059
13456
|
}
|
|
13060
13457
|
loadSkillDir(dir, createIfMissing) {
|
|
13061
|
-
if (!
|
|
13458
|
+
if (!existsSync22(dir)) {
|
|
13062
13459
|
if (createIfMissing) {
|
|
13063
13460
|
try {
|
|
13064
13461
|
mkdirSync10(dir, { recursive: true });
|
|
@@ -13075,14 +13472,14 @@ var SkillManager = class {
|
|
|
13075
13472
|
}
|
|
13076
13473
|
for (const entry of entries) {
|
|
13077
13474
|
let filePath;
|
|
13078
|
-
const fullPath =
|
|
13475
|
+
const fullPath = join17(dir, entry);
|
|
13079
13476
|
if (entry.endsWith(".md")) {
|
|
13080
13477
|
filePath = fullPath;
|
|
13081
13478
|
} else {
|
|
13082
13479
|
try {
|
|
13083
13480
|
if (statSync9(fullPath).isDirectory()) {
|
|
13084
|
-
const skillMd =
|
|
13085
|
-
if (
|
|
13481
|
+
const skillMd = join17(fullPath, "SKILL.md");
|
|
13482
|
+
if (existsSync22(skillMd)) filePath = skillMd;
|
|
13086
13483
|
else continue;
|
|
13087
13484
|
} else {
|
|
13088
13485
|
continue;
|
|
@@ -13141,7 +13538,7 @@ var SkillManager = class {
|
|
|
13141
13538
|
// src/web/tool-executor-web.ts
|
|
13142
13539
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
13143
13540
|
import { tmpdir as tmpdir2 } from "os";
|
|
13144
|
-
import { existsSync as
|
|
13541
|
+
import { existsSync as existsSync23, readFileSync as readFileSync18 } from "fs";
|
|
13145
13542
|
var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
13146
13543
|
constructor(registry, ws) {
|
|
13147
13544
|
this.registry = registry;
|
|
@@ -13281,9 +13678,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
13281
13678
|
getDiffPreview(call) {
|
|
13282
13679
|
if (call.name === "write_file") {
|
|
13283
13680
|
const { path: filePath, content: chunk, append } = resolveWriteFileArgs(call.arguments);
|
|
13284
|
-
if (filePath &&
|
|
13681
|
+
if (filePath && existsSync23(filePath)) {
|
|
13285
13682
|
try {
|
|
13286
|
-
const old =
|
|
13683
|
+
const old = readFileSync18(filePath, "utf-8");
|
|
13287
13684
|
return renderDiff(old, append ? old + chunk : chunk, { filePath });
|
|
13288
13685
|
} catch {
|
|
13289
13686
|
}
|
|
@@ -13596,16 +13993,16 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
13596
13993
|
};
|
|
13597
13994
|
|
|
13598
13995
|
// src/repl/dev-state.ts
|
|
13599
|
-
import { existsSync as
|
|
13600
|
-
import { join as
|
|
13996
|
+
import { existsSync as existsSync24, readFileSync as readFileSync19, unlinkSync as unlinkSync5, mkdirSync as mkdirSync11 } from "fs";
|
|
13997
|
+
import { join as join18 } from "path";
|
|
13601
13998
|
import { homedir as homedir8 } from "os";
|
|
13602
13999
|
function getDevStatePath() {
|
|
13603
|
-
return
|
|
14000
|
+
return join18(homedir8(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
|
|
13604
14001
|
}
|
|
13605
14002
|
function loadDevState() {
|
|
13606
14003
|
const path3 = getDevStatePath();
|
|
13607
|
-
if (!
|
|
13608
|
-
const content =
|
|
14004
|
+
if (!existsSync24(path3)) return null;
|
|
14005
|
+
const content = readFileSync19(path3, "utf-8").trim();
|
|
13609
14006
|
return content || null;
|
|
13610
14007
|
}
|
|
13611
14008
|
|
|
@@ -13666,148 +14063,6 @@ function loadMemoryContent(configDir, maxChars) {
|
|
|
13666
14063
|
return formatMemoryForPrompt(configDir, process.cwd(), maxChars);
|
|
13667
14064
|
}
|
|
13668
14065
|
|
|
13669
|
-
// src/core/pricing.ts
|
|
13670
|
-
var DEEPSEEK_CNY_PER_USD = 6.9;
|
|
13671
|
-
var DEEPSEEK_TIME_PRICING_EFFECTIVE_AT = /* @__PURE__ */ new Date("2026-08-16T16:00:00.000Z");
|
|
13672
|
-
function deepSeekCnyPricing(cacheRead, input, output) {
|
|
13673
|
-
return {
|
|
13674
|
-
cacheRead: cacheRead / DEEPSEEK_CNY_PER_USD,
|
|
13675
|
-
input: input / DEEPSEEK_CNY_PER_USD,
|
|
13676
|
-
output: output / DEEPSEEK_CNY_PER_USD
|
|
13677
|
-
};
|
|
13678
|
-
}
|
|
13679
|
-
function getDeepSeekV4Pricing(model, at) {
|
|
13680
|
-
const isPro = model.startsWith("deepseek-v4-pro");
|
|
13681
|
-
const isFlash = model.startsWith("deepseek-v4-flash");
|
|
13682
|
-
if (!isPro && !isFlash) return null;
|
|
13683
|
-
if (at < DEEPSEEK_TIME_PRICING_EFFECTIVE_AT) {
|
|
13684
|
-
return isPro ? deepSeekCnyPricing(0.025, 3, 6) : deepSeekCnyPricing(0.02, 1, 2);
|
|
13685
|
-
}
|
|
13686
|
-
const beijingHour = (at.getUTCHours() + 8) % 24;
|
|
13687
|
-
const isPeak = beijingHour >= 9 && beijingHour < 12 || beijingHour >= 14 && beijingHour < 18;
|
|
13688
|
-
if (isPro) {
|
|
13689
|
-
return isPeak ? deepSeekCnyPricing(0.3, 9, 27) : deepSeekCnyPricing(0.15, 4.5, 13.5);
|
|
13690
|
-
}
|
|
13691
|
-
return isPeak ? deepSeekCnyPricing(0.1, 3, 9) : deepSeekCnyPricing(0.05, 1.5, 4.5);
|
|
13692
|
-
}
|
|
13693
|
-
var PRICING_TABLE = {
|
|
13694
|
-
// ── Anthropic Claude ──────────────────────────────────────────
|
|
13695
|
-
"claude-opus-4-8": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
13696
|
-
"claude-opus-4-7": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
13697
|
-
"claude-opus-4-6": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
13698
|
-
"claude-opus-4-5": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
13699
|
-
"claude-sonnet-4-6": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
|
|
13700
|
-
"claude-sonnet-4-5-20250929": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
|
|
13701
|
-
"claude-haiku-4-5-20251001": { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1 },
|
|
13702
|
-
"claude-haiku-4-5": { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1 },
|
|
13703
|
-
// Legacy Claude 3.x families (prefix fallback handles minor date suffixes)
|
|
13704
|
-
"claude-3-5-sonnet": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
|
|
13705
|
-
"claude-3-5-haiku": { input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08 },
|
|
13706
|
-
"claude-3-opus": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
13707
|
-
// ── OpenAI ────────────────────────────────────────────────────
|
|
13708
|
-
// GAP (v0.4.184): the GPT-5 family (gpt-5, gpt-5-mini/nano, gpt-5.4, gpt-5.4-pro)
|
|
13709
|
-
// and o4-mini are in the provider's model list but have NO verified pricing yet —
|
|
13710
|
-
// left unpriced on purpose so `aicli usage` shows "—" (unknown) rather than a
|
|
13711
|
-
// deceptive $0.00. Add real rates when confirmed from the official pricing page.
|
|
13712
|
-
"gpt-4o": { input: 2.5, output: 10, cacheRead: 1.25 },
|
|
13713
|
-
"gpt-4o-mini": { input: 0.15, output: 0.6, cacheRead: 0.075 },
|
|
13714
|
-
"gpt-4-turbo": { input: 10, output: 30 },
|
|
13715
|
-
"gpt-4": { input: 30, output: 60 },
|
|
13716
|
-
"gpt-4.1": { input: 2, output: 8, cacheRead: 0.5 },
|
|
13717
|
-
"gpt-4.1-mini": { input: 0.4, output: 1.6, cacheRead: 0.1 },
|
|
13718
|
-
"gpt-4.1-nano": { input: 0.1, output: 0.4, cacheRead: 0.025 },
|
|
13719
|
-
"o1": { input: 15, output: 60, cacheRead: 7.5 },
|
|
13720
|
-
"o1-mini": { input: 3, output: 12, cacheRead: 1.5 },
|
|
13721
|
-
"o3": { input: 10, output: 40, cacheRead: 2.5 },
|
|
13722
|
-
"o3-mini": { input: 1.1, output: 4.4, cacheRead: 0.55 },
|
|
13723
|
-
// ── Google Gemini ─────────────────────────────────────────────
|
|
13724
|
-
"gemini-2.5-pro": { input: 1.25, output: 10 },
|
|
13725
|
-
"gemini-2.5-flash": { input: 0.3, output: 2.5 },
|
|
13726
|
-
"gemini-2.0-flash": { input: 0.1, output: 0.4 },
|
|
13727
|
-
"gemini-1.5-pro": { input: 1.25, output: 5 },
|
|
13728
|
-
"gemini-1.5-flash": { input: 0.075, output: 0.3 },
|
|
13729
|
-
// ── DeepSeek ──────────────────────────────────────────────────
|
|
13730
|
-
// V4 family (2026-04-23+):1M context, Thinking / Non-Thinking dual-mode, 384K max output
|
|
13731
|
-
// 费率 2026-08-01 对 api-docs.deepseek.com/quick_start/pricing 官方 USD 表逐项核对。
|
|
13732
|
-
// 修正前 pro 三项整齐高 4×(官方降过价未跟)、flash cacheRead 高 10×(像是按 input 的
|
|
13733
|
-
// 20% 拍脑袋而非抄文档)——`aicli usage` 因此系统性高报 DeepSeek 成本。
|
|
13734
|
-
// 待办:官方已公告峰谷定价(北京时间 9-12 点、14-18 点为平时 2 倍),本表仍是单一费率,
|
|
13735
|
-
// 届时高峰时段会低报;等该策略实际生效后再决定是否引入时段维度。
|
|
13736
|
-
// Legacy aliases:retires 2026-07-24 UTC 15:59,官方 route 到 V4 Flash。
|
|
13737
|
-
// 这几条只服务历史 session 的成本回算,保留当时的费率口径,不随现价改动。
|
|
13738
|
-
"deepseek-chat": { input: 0.14, output: 0.28, cacheRead: 0.028 },
|
|
13739
|
-
"deepseek-reasoner": { input: 0.14, output: 0.28, cacheRead: 0.028 },
|
|
13740
|
-
"deepseek-v3": { input: 0.27, output: 1.1, cacheRead: 0.07 },
|
|
13741
|
-
// ── Moonshot Kimi ─────────────────────────────────────────────
|
|
13742
|
-
"moonshot-v1-8k": { input: 0.17, output: 0.17 },
|
|
13743
|
-
"moonshot-v1-32k": { input: 0.33, output: 0.33 },
|
|
13744
|
-
"moonshot-v1-128k": { input: 0.83, output: 0.83 },
|
|
13745
|
-
"kimi-k2": { input: 0.6, output: 2.5 },
|
|
13746
|
-
"kimi-latest": { input: 0.6, output: 2.5 },
|
|
13747
|
-
// Kimi K3(2026-07 旗舰,1M 上下文)官方定价 platform.kimi.ai/pricing/chat-k3:
|
|
13748
|
-
// 输入 cache-miss $3.00 / cache-hit $0.30,输出 $15.00(/MTok)。
|
|
13749
|
-
"kimi-k3": { input: 3, output: 15, cacheRead: 0.3 },
|
|
13750
|
-
// ── Zhipu GLM ─────────────────────────────────────────────────
|
|
13751
|
-
"glm-4-plus": { input: 0.7, output: 0.7 },
|
|
13752
|
-
"glm-4": { input: 0.14, output: 0.14 },
|
|
13753
|
-
"glm-4-flash": { input: 0, output: 0 },
|
|
13754
|
-
"glm-4.5": { input: 0.29, output: 1.14 },
|
|
13755
|
-
"glm-4.6": { input: 0.6, output: 2.2 },
|
|
13756
|
-
"glm-4.6v": { input: 0.6, output: 2.2 },
|
|
13757
|
-
"glm-5": { input: 0.85, output: 2.85 },
|
|
13758
|
-
// GLM-5.2(2026-06)官方直连 API 定价 $1.40 / $4.40。
|
|
13759
|
-
"glm-5.2": { input: 1.4, output: 4.4 },
|
|
13760
|
-
// GLM-5V-Turbo(2026-04 视觉编程旗舰)官方直连 API 定价 $1.20 / $4.00。
|
|
13761
|
-
"glm-5v-turbo": { input: 1.2, output: 4 },
|
|
13762
|
-
"glm-5.1": { input: 0.95, output: 3.15 },
|
|
13763
|
-
"glm-5.1-reasoning": { input: 1.4, output: 4.4 },
|
|
13764
|
-
"glm-5.1-air": { input: 0.4, output: 1.2 },
|
|
13765
|
-
"glm-z1": { input: 0.5, output: 1.5 },
|
|
13766
|
-
"glm-z1-air": { input: 0.2, output: 0.6 },
|
|
13767
|
-
"glm-z1-flash": { input: 0, output: 0 }
|
|
13768
|
-
// ── MiniMax (海螺) ────────────────────────────────────────────
|
|
13769
|
-
// GAP (v0.4.184): MiniMax-M3 / M2.x are unpriced — no verified rate anchor yet.
|
|
13770
|
-
// Unpriced → `aicli usage` shows "—" (unknown), not a false $0.00. Add when confirmed.
|
|
13771
|
-
// ── OpenRouter (pass-through — actual cost depends on underlying model) ──
|
|
13772
|
-
// Left empty by design: model IDs are "vendor/model" (e.g. anthropic/claude-sonnet-4)
|
|
13773
|
-
// and the real price is set by the underlying model + OpenRouter markup, not statically
|
|
13774
|
-
// resolvable here. Shows "—" (unknown) in `aicli usage`.
|
|
13775
|
-
// ── Ollama (local, zero cost) ─────────────────────────────────
|
|
13776
|
-
// Handled via provider check below (FREE_PROVIDERS → genuine $0.00, not "unknown").
|
|
13777
|
-
};
|
|
13778
|
-
var FREE_PROVIDERS = /* @__PURE__ */ new Set(["ollama"]);
|
|
13779
|
-
function getPricing(provider, model, at = /* @__PURE__ */ new Date()) {
|
|
13780
|
-
if (FREE_PROVIDERS.has(provider.toLowerCase())) {
|
|
13781
|
-
return { input: 0, output: 0 };
|
|
13782
|
-
}
|
|
13783
|
-
const key = model.toLowerCase();
|
|
13784
|
-
if (provider.toLowerCase() === "deepseek") {
|
|
13785
|
-
const dynamicPricing = getDeepSeekV4Pricing(key, at);
|
|
13786
|
-
if (dynamicPricing) return dynamicPricing;
|
|
13787
|
-
}
|
|
13788
|
-
if (PRICING_TABLE[key]) return PRICING_TABLE[key];
|
|
13789
|
-
const keys = Object.keys(PRICING_TABLE).sort((a, b) => b.length - a.length);
|
|
13790
|
-
for (const k of keys) {
|
|
13791
|
-
if (key.startsWith(k)) return PRICING_TABLE[k];
|
|
13792
|
-
}
|
|
13793
|
-
return null;
|
|
13794
|
-
}
|
|
13795
|
-
function computeCost(provider, model, usage, at = /* @__PURE__ */ new Date()) {
|
|
13796
|
-
const p = getPricing(provider, model, at);
|
|
13797
|
-
if (!p) return null;
|
|
13798
|
-
const input = usage.inputTokens * p.input;
|
|
13799
|
-
const output = usage.outputTokens * p.output;
|
|
13800
|
-
const cacheWrite = (usage.cacheCreationTokens ?? 0) * (p.cacheWrite ?? p.input);
|
|
13801
|
-
const cacheRead = (usage.cacheReadTokens ?? 0) * (p.cacheRead ?? p.input);
|
|
13802
|
-
return (input + output + cacheWrite + cacheRead) / 1e6;
|
|
13803
|
-
}
|
|
13804
|
-
function formatCost(amount) {
|
|
13805
|
-
if (amount === 0) return "$0.0000";
|
|
13806
|
-
if (amount < 0.01) return `$${amount.toFixed(4)}`;
|
|
13807
|
-
if (amount < 1) return `$${amount.toFixed(3)}`;
|
|
13808
|
-
return `$${amount.toFixed(2)}`;
|
|
13809
|
-
}
|
|
13810
|
-
|
|
13811
14066
|
// src/core/chat-params.ts
|
|
13812
14067
|
function thinkingParamsFor(allModelParams, model) {
|
|
13813
14068
|
const params = allModelParams?.[model];
|
|
@@ -13907,8 +14162,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
|
13907
14162
|
}
|
|
13908
14163
|
|
|
13909
14164
|
// src/core/context-files.ts
|
|
13910
|
-
import { existsSync as
|
|
13911
|
-
import { join as
|
|
14165
|
+
import { existsSync as existsSync25, readFileSync as readFileSync20 } from "fs";
|
|
14166
|
+
import { join as join19, relative as relative4, resolve as resolve8 } from "path";
|
|
13912
14167
|
function uniqueNonEmpty(values) {
|
|
13913
14168
|
const seen = /* @__PURE__ */ new Set();
|
|
13914
14169
|
const out = [];
|
|
@@ -13938,7 +14193,7 @@ function readContextFile(level, filePath, cwd, maxBytes) {
|
|
|
13938
14193
|
const name = filePath.split(/[\\/]/).pop() ?? filePath;
|
|
13939
14194
|
const shown = displayPath(filePath, cwd);
|
|
13940
14195
|
try {
|
|
13941
|
-
const raw =
|
|
14196
|
+
const raw = readFileSync20(filePath);
|
|
13942
14197
|
const byteCount = raw.byteLength;
|
|
13943
14198
|
if (byteCount === 0) {
|
|
13944
14199
|
return {
|
|
@@ -13985,8 +14240,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
|
|
|
13985
14240
|
function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
13986
14241
|
const skipped = [];
|
|
13987
14242
|
for (const candidate of candidates) {
|
|
13988
|
-
const filePath =
|
|
13989
|
-
if (!
|
|
14243
|
+
const filePath = join19(dir, candidate);
|
|
14244
|
+
if (!existsSync25(filePath)) continue;
|
|
13990
14245
|
const result = readContextFile(level, filePath, cwd, maxBytes);
|
|
13991
14246
|
if (result.skipped) skipped.push(result.skipped);
|
|
13992
14247
|
if (result.layer) return { layer: result.layer, skipped };
|
|
@@ -14013,7 +14268,7 @@ function loadContextFiles(options) {
|
|
|
14013
14268
|
});
|
|
14014
14269
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
14015
14270
|
}
|
|
14016
|
-
if (!
|
|
14271
|
+
if (!existsSync25(filePath)) {
|
|
14017
14272
|
skipped.push({
|
|
14018
14273
|
level: "single",
|
|
14019
14274
|
filePath,
|
|
@@ -14068,8 +14323,8 @@ import { mkdirSync as mkdirSync14, createWriteStream as createWriteStream2 } fro
|
|
|
14068
14323
|
import { dirname as dirname9 } from "path";
|
|
14069
14324
|
|
|
14070
14325
|
// src/repl/commands/project-init.ts
|
|
14071
|
-
import { existsSync as
|
|
14072
|
-
import { join as
|
|
14326
|
+
import { existsSync as existsSync26, readFileSync as readFileSync21, readdirSync as readdirSync11, statSync as statSync10 } from "fs";
|
|
14327
|
+
import { join as join20 } from "path";
|
|
14073
14328
|
var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
14074
14329
|
"node_modules",
|
|
14075
14330
|
".git",
|
|
@@ -14113,11 +14368,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
14113
14368
|
const sorted = filtered.sort((a, b) => {
|
|
14114
14369
|
let aIsDir = false, bIsDir = false;
|
|
14115
14370
|
try {
|
|
14116
|
-
aIsDir = statSync10(
|
|
14371
|
+
aIsDir = statSync10(join20(d, a)).isDirectory();
|
|
14117
14372
|
} catch {
|
|
14118
14373
|
}
|
|
14119
14374
|
try {
|
|
14120
|
-
bIsDir = statSync10(
|
|
14375
|
+
bIsDir = statSync10(join20(d, b)).isDirectory();
|
|
14121
14376
|
} catch {
|
|
14122
14377
|
}
|
|
14123
14378
|
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
@@ -14125,7 +14380,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
14125
14380
|
});
|
|
14126
14381
|
for (let i = 0; i < sorted.length && count < maxEntries; i++) {
|
|
14127
14382
|
const name = sorted[i];
|
|
14128
|
-
const fullPath =
|
|
14383
|
+
const fullPath = join20(d, name);
|
|
14129
14384
|
const isLast = i === sorted.length - 1;
|
|
14130
14385
|
const connector = isLast ? "+-- " : "|-- ";
|
|
14131
14386
|
let isDir;
|
|
@@ -14152,7 +14407,7 @@ function scanProject(cwd) {
|
|
|
14152
14407
|
configFiles: [],
|
|
14153
14408
|
directoryStructure: ""
|
|
14154
14409
|
};
|
|
14155
|
-
const check = (file) =>
|
|
14410
|
+
const check = (file) => existsSync26(join20(cwd, file));
|
|
14156
14411
|
const configCandidates = [
|
|
14157
14412
|
"package.json",
|
|
14158
14413
|
"tsconfig.json",
|
|
@@ -14179,7 +14434,7 @@ function scanProject(cwd) {
|
|
|
14179
14434
|
info.type = "node";
|
|
14180
14435
|
info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
|
|
14181
14436
|
try {
|
|
14182
|
-
const pkg = JSON.parse(
|
|
14437
|
+
const pkg = JSON.parse(readFileSync21(join20(cwd, "package.json"), "utf-8"));
|
|
14183
14438
|
const scripts = pkg.scripts ?? {};
|
|
14184
14439
|
info.buildCommand = scripts.build ? "npm run build" : void 0;
|
|
14185
14440
|
info.testCommand = scripts.test ? "npm test" : void 0;
|
|
@@ -14840,7 +15095,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
|
|
|
14840
15095
|
}
|
|
14841
15096
|
|
|
14842
15097
|
// src/hub/persist.ts
|
|
14843
|
-
import { join as
|
|
15098
|
+
import { join as join21 } from "path";
|
|
14844
15099
|
function discussionToMessages(state2) {
|
|
14845
15100
|
const out = [];
|
|
14846
15101
|
const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
|
|
@@ -14878,7 +15133,69 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
|
|
|
14878
15133
|
session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
|
|
14879
15134
|
session.titleAiGenerated = true;
|
|
14880
15135
|
await sm.save();
|
|
14881
|
-
return { id: session.id, path:
|
|
15136
|
+
return { id: session.id, path: join21(config.getHistoryDir(), `${session.id}.json`) };
|
|
15137
|
+
}
|
|
15138
|
+
|
|
15139
|
+
// src/core/think-command.ts
|
|
15140
|
+
var EFFORTS = /* @__PURE__ */ new Set(["low", "high", "max"]);
|
|
15141
|
+
function resolveEffective(state2) {
|
|
15142
|
+
if (state2.runtimeThinking !== null) return { effective: state2.runtimeThinking, origin: "runtime override" };
|
|
15143
|
+
if (state2.configuredThinking !== void 0) return { effective: state2.configuredThinking, origin: "config" };
|
|
15144
|
+
return { effective: false, origin: "default" };
|
|
15145
|
+
}
|
|
15146
|
+
function effortLine(state2, effort) {
|
|
15147
|
+
if (!state2.supportsEffort) return `Reasoning effort: n/a for ${state2.provider}`;
|
|
15148
|
+
const resolved = effort ?? state2.runtimeEffort ?? state2.configuredEffort;
|
|
15149
|
+
return `Reasoning effort: ${resolved ?? "high (provider default)"}`;
|
|
15150
|
+
}
|
|
15151
|
+
function applyThinkCommand(sub, state2) {
|
|
15152
|
+
const key = (sub ?? "toggle").toLowerCase();
|
|
15153
|
+
const current = resolveEffective(state2);
|
|
15154
|
+
const stay = (summary, details = []) => ({
|
|
15155
|
+
runtimeThinking: state2.runtimeThinking,
|
|
15156
|
+
runtimeEffort: state2.runtimeEffort,
|
|
15157
|
+
effective: current.effective,
|
|
15158
|
+
origin: current.origin,
|
|
15159
|
+
summary,
|
|
15160
|
+
details,
|
|
15161
|
+
changed: false
|
|
15162
|
+
});
|
|
15163
|
+
const enable = (effort) => ({
|
|
15164
|
+
runtimeThinking: true,
|
|
15165
|
+
runtimeEffort: effort ?? state2.runtimeEffort,
|
|
15166
|
+
effective: true,
|
|
15167
|
+
origin: "runtime override",
|
|
15168
|
+
summary: effort && state2.supportsEffort ? `Extended Thinking: ON (effort: ${effort})` : "Extended Thinking: ON",
|
|
15169
|
+
details: effort && !state2.supportsEffort ? [`Note: ${state2.provider} has no reasoning-effort levels \u2014 the level is ignored.`] : [],
|
|
15170
|
+
changed: true
|
|
15171
|
+
});
|
|
15172
|
+
const disable = () => ({
|
|
15173
|
+
runtimeThinking: false,
|
|
15174
|
+
// 刻意保留档位:重新开启时不该丢掉用户选过的档
|
|
15175
|
+
runtimeEffort: state2.runtimeEffort,
|
|
15176
|
+
effective: false,
|
|
15177
|
+
origin: "runtime override",
|
|
15178
|
+
summary: "Extended Thinking: OFF",
|
|
15179
|
+
details: [],
|
|
15180
|
+
changed: true
|
|
15181
|
+
});
|
|
15182
|
+
if (key === "status") {
|
|
15183
|
+
const details = current.effective ? [effortLine(state2, null)] : [
|
|
15184
|
+
"Enable with /think on | low | high | max, or set",
|
|
15185
|
+
"modelParams.<model>.thinking in ~/.aicli/config.json"
|
|
15186
|
+
];
|
|
15187
|
+
return stay(
|
|
15188
|
+
`Extended Thinking: ${current.effective ? "ON" : "OFF"} (${current.origin})`,
|
|
15189
|
+
details
|
|
15190
|
+
);
|
|
15191
|
+
}
|
|
15192
|
+
if (key === "on") return enable(null);
|
|
15193
|
+
if (key === "off") return disable();
|
|
15194
|
+
if (EFFORTS.has(key)) return enable(key);
|
|
15195
|
+
if (key === "toggle") {
|
|
15196
|
+
return current.effective ? disable() : enable(null);
|
|
15197
|
+
}
|
|
15198
|
+
return stay(`Unknown /think option "${sub}" \u2014 expected on | off | low | high | max | status`);
|
|
14882
15199
|
}
|
|
14883
15200
|
|
|
14884
15201
|
// src/web/commands/provider-commands.ts
|
|
@@ -14943,30 +15260,24 @@ async function handleModel(args, ctx) {
|
|
|
14943
15260
|
ctx.sendStatus();
|
|
14944
15261
|
}
|
|
14945
15262
|
async function handleThink(args, ctx) {
|
|
14946
|
-
const
|
|
14947
|
-
|
|
14948
|
-
|
|
14949
|
-
|
|
14950
|
-
|
|
14951
|
-
|
|
14952
|
-
|
|
14953
|
-
ctx.
|
|
14954
|
-
|
|
14955
|
-
|
|
14956
|
-
|
|
14957
|
-
|
|
15263
|
+
const modelParams = ctx.config.get("modelParams")[ctx.currentModel];
|
|
15264
|
+
const outcome = applyThinkCommand(args[0], {
|
|
15265
|
+
runtimeThinking: ctx.runtimeThinking,
|
|
15266
|
+
runtimeEffort: ctx.runtimeThinkingEffort,
|
|
15267
|
+
configuredThinking: modelParams?.thinking,
|
|
15268
|
+
configuredEffort: modelParams?.thinkingEffort,
|
|
15269
|
+
supportsEffort: ctx.providers.get(ctx.currentProvider)?.supportsReasoningEffort ?? false,
|
|
15270
|
+
provider: ctx.currentProvider
|
|
15271
|
+
});
|
|
15272
|
+
if (outcome.changed) {
|
|
15273
|
+
ctx.runtimeThinking = outcome.runtimeThinking;
|
|
15274
|
+
ctx.runtimeThinkingEffort = outcome.runtimeEffort;
|
|
14958
15275
|
}
|
|
14959
|
-
if (sub === "on") ctx.runtimeThinking = true;
|
|
14960
|
-
else if (sub === "off") ctx.runtimeThinking = false;
|
|
14961
|
-
else if (sub === "low" || sub === "high" || sub === "max") {
|
|
14962
|
-
ctx.runtimeThinking = true;
|
|
14963
|
-
ctx.runtimeThinkingEffort = sub;
|
|
14964
|
-
} else ctx.runtimeThinking = ctx.runtimeThinking !== true;
|
|
14965
15276
|
ctx.send({
|
|
14966
15277
|
type: "info",
|
|
14967
|
-
message:
|
|
15278
|
+
message: [outcome.summary, ...outcome.details].join(" \u2014 ")
|
|
14968
15279
|
});
|
|
14969
|
-
ctx.sendStatus();
|
|
15280
|
+
if (outcome.changed) ctx.sendStatus();
|
|
14970
15281
|
}
|
|
14971
15282
|
async function handlePlan(args, ctx) {
|
|
14972
15283
|
const sub = args[0];
|
|
@@ -14983,9 +15294,44 @@ async function handlePlan(args, ctx) {
|
|
|
14983
15294
|
ctx.sendStatus();
|
|
14984
15295
|
}
|
|
14985
15296
|
|
|
15297
|
+
// src/core/session-cost.ts
|
|
15298
|
+
function buildSessionCostReport(input) {
|
|
15299
|
+
const { usage } = input;
|
|
15300
|
+
const totalTokens = usage.inputTokens + usage.outputTokens + usage.cacheCreationTokens + usage.cacheReadTokens;
|
|
15301
|
+
if (totalTokens === 0) {
|
|
15302
|
+
return { hasUsage: false, totalTokens: 0, cacheHitPct: null, cost: null, costSource: "unknown", savings: null };
|
|
15303
|
+
}
|
|
15304
|
+
const liveCost = computeCost(input.provider, input.model, usage);
|
|
15305
|
+
const useAccrual = input.accruedUsd > 0;
|
|
15306
|
+
const cost = useAccrual ? input.accruedUsd : liveCost;
|
|
15307
|
+
const costSource = useAccrual ? input.accrualIncomplete ? "accrued-partial" : "accrued" : liveCost != null ? "estimated" : "unknown";
|
|
15308
|
+
let savings = null;
|
|
15309
|
+
if (liveCost != null && usage.cacheReadTokens > 0) {
|
|
15310
|
+
const withoutCache = computeCost(input.provider, input.model, {
|
|
15311
|
+
inputTokens: usage.inputTokens + usage.cacheReadTokens,
|
|
15312
|
+
outputTokens: usage.outputTokens,
|
|
15313
|
+
cacheCreationTokens: usage.cacheCreationTokens,
|
|
15314
|
+
cacheReadTokens: 0
|
|
15315
|
+
});
|
|
15316
|
+
if (withoutCache != null) savings = withoutCache - liveCost;
|
|
15317
|
+
}
|
|
15318
|
+
const cacheHitPct = usage.cacheReadTokens > 0 ? Math.round(usage.cacheReadTokens / (usage.cacheReadTokens + usage.inputTokens) * 100) : null;
|
|
15319
|
+
return { hasUsage: true, totalTokens, cacheHitPct, cost, costSource, savings };
|
|
15320
|
+
}
|
|
15321
|
+
function costSourceNote(source) {
|
|
15322
|
+
switch (source) {
|
|
15323
|
+
case "accrued-partial":
|
|
15324
|
+
return "(partial \u2014 some turns had unknown pricing)";
|
|
15325
|
+
case "estimated":
|
|
15326
|
+
return "(estimated at current rates)";
|
|
15327
|
+
default:
|
|
15328
|
+
return null;
|
|
15329
|
+
}
|
|
15330
|
+
}
|
|
15331
|
+
|
|
14986
15332
|
// src/web/commands/_shared.ts
|
|
14987
|
-
import { existsSync as
|
|
14988
|
-
import { join as
|
|
15333
|
+
import { existsSync as existsSync29, readFileSync as readFileSync23, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, statSync as statSync13, createWriteStream } from "fs";
|
|
15334
|
+
import { join as join24, resolve as resolve9, dirname as dirname8 } from "path";
|
|
14989
15335
|
|
|
14990
15336
|
// src/cli/review-prompts.ts
|
|
14991
15337
|
function buildReviewPrompt(diff, gitContextStr, detailed) {
|
|
@@ -15057,34 +15403,34 @@ function readGitDiff(options = {}) {
|
|
|
15057
15403
|
}
|
|
15058
15404
|
|
|
15059
15405
|
// src/diagnostics/doctor-report.ts
|
|
15060
|
-
import { existsSync as
|
|
15061
|
-
import { join as
|
|
15406
|
+
import { existsSync as existsSync28, statSync as statSync12 } from "fs";
|
|
15407
|
+
import { join as join23 } from "path";
|
|
15062
15408
|
import { arch as arch2, platform as platform7, release as release2 } from "os";
|
|
15063
15409
|
|
|
15064
15410
|
// src/diagnostics/crash-log.ts
|
|
15065
15411
|
import {
|
|
15066
|
-
existsSync as
|
|
15412
|
+
existsSync as existsSync27,
|
|
15067
15413
|
mkdirSync as mkdirSync12,
|
|
15068
15414
|
readdirSync as readdirSync12,
|
|
15069
|
-
readFileSync as
|
|
15415
|
+
readFileSync as readFileSync22,
|
|
15070
15416
|
statSync as statSync11,
|
|
15071
15417
|
unlinkSync as unlinkSync6,
|
|
15072
15418
|
writeFileSync as writeFileSync2
|
|
15073
15419
|
} from "fs";
|
|
15074
|
-
import { join as
|
|
15420
|
+
import { join as join22 } from "path";
|
|
15075
15421
|
import { homedir as homedir9, platform as platform6, release, arch } from "os";
|
|
15076
15422
|
var LOGS_DIR_NAME = "logs";
|
|
15077
15423
|
var CRASH_LOG_PREFIX = "crash-";
|
|
15078
15424
|
function getLogsDir(configDir) {
|
|
15079
|
-
const base = configDir ??
|
|
15080
|
-
return
|
|
15425
|
+
const base = configDir ?? join22(homedir9(), CONFIG_DIR_NAME);
|
|
15426
|
+
return join22(base, LOGS_DIR_NAME);
|
|
15081
15427
|
}
|
|
15082
15428
|
function truncate(s, n) {
|
|
15083
15429
|
return s.length <= n ? s : s.slice(0, n) + "\u2026";
|
|
15084
15430
|
}
|
|
15085
15431
|
function listRecentCrashes(limit = 10, configDir) {
|
|
15086
15432
|
const dir = getLogsDir(configDir);
|
|
15087
|
-
if (!
|
|
15433
|
+
if (!existsSync27(dir)) return [];
|
|
15088
15434
|
const files = [];
|
|
15089
15435
|
let names;
|
|
15090
15436
|
try {
|
|
@@ -15094,10 +15440,10 @@ function listRecentCrashes(limit = 10, configDir) {
|
|
|
15094
15440
|
}
|
|
15095
15441
|
for (const name of names) {
|
|
15096
15442
|
if (!name.startsWith(CRASH_LOG_PREFIX) || !name.endsWith(".log")) continue;
|
|
15097
|
-
const full =
|
|
15443
|
+
const full = join22(dir, name);
|
|
15098
15444
|
try {
|
|
15099
15445
|
const st = statSync11(full);
|
|
15100
|
-
const head =
|
|
15446
|
+
const head = readFileSync22(full, "utf-8").split("\n").slice(0, 20);
|
|
15101
15447
|
const tsLine = head.find((l) => l.startsWith("timestamp:"));
|
|
15102
15448
|
const kindLine = head.find((l) => l.startsWith("kind:"));
|
|
15103
15449
|
const msgIdx = head.findIndex((l) => l.trim() === "## message");
|
|
@@ -15116,8 +15462,8 @@ function listRecentCrashes(limit = 10, configDir) {
|
|
|
15116
15462
|
return files.slice(0, limit);
|
|
15117
15463
|
}
|
|
15118
15464
|
function getConfigDirUsage(configDir) {
|
|
15119
|
-
const base = configDir ??
|
|
15120
|
-
if (!
|
|
15465
|
+
const base = configDir ?? join22(homedir9(), CONFIG_DIR_NAME);
|
|
15466
|
+
if (!existsSync27(base)) return { totalBytes: 0, entries: [] };
|
|
15121
15467
|
const entries = [];
|
|
15122
15468
|
let total = 0;
|
|
15123
15469
|
let names;
|
|
@@ -15127,7 +15473,7 @@ function getConfigDirUsage(configDir) {
|
|
|
15127
15473
|
return { totalBytes: 0, entries: [] };
|
|
15128
15474
|
}
|
|
15129
15475
|
for (const name of names) {
|
|
15130
|
-
const full =
|
|
15476
|
+
const full = join22(base, name);
|
|
15131
15477
|
const bytes = dirSize(full);
|
|
15132
15478
|
entries.push({ name, bytes });
|
|
15133
15479
|
total += bytes;
|
|
@@ -15142,7 +15488,7 @@ function dirSize(path3) {
|
|
|
15142
15488
|
if (!st.isDirectory()) return 0;
|
|
15143
15489
|
let sum = 0;
|
|
15144
15490
|
for (const name of readdirSync12(path3)) {
|
|
15145
|
-
sum += dirSize(
|
|
15491
|
+
sum += dirSize(join22(path3, name));
|
|
15146
15492
|
}
|
|
15147
15493
|
return sum;
|
|
15148
15494
|
} catch {
|
|
@@ -15152,7 +15498,7 @@ function dirSize(path3) {
|
|
|
15152
15498
|
|
|
15153
15499
|
// src/diagnostics/doctor-report.ts
|
|
15154
15500
|
function checkFile(label, path3) {
|
|
15155
|
-
const exists =
|
|
15501
|
+
const exists = existsSync28(path3);
|
|
15156
15502
|
let sizeBytes = null;
|
|
15157
15503
|
if (exists) {
|
|
15158
15504
|
try {
|
|
@@ -15195,12 +15541,12 @@ function buildDoctorReport(options) {
|
|
|
15195
15541
|
configDir,
|
|
15196
15542
|
providers: options.providers.listAll().map((p) => ({ id: p.id, displayName: p.displayName, configured: p.configured })),
|
|
15197
15543
|
files: [
|
|
15198
|
-
checkFile("config.json",
|
|
15199
|
-
checkFile("memory.md",
|
|
15200
|
-
checkFile("memory.jsonl",
|
|
15201
|
-
checkFile("dev-state.md",
|
|
15202
|
-
checkFile("hooks-trust.json",
|
|
15203
|
-
checkFile("plugin-state.json",
|
|
15544
|
+
checkFile("config.json", join23(configDir, "config.json")),
|
|
15545
|
+
checkFile("memory.md", join23(configDir, MEMORY_FILE_NAME)),
|
|
15546
|
+
checkFile("memory.jsonl", join23(configDir, "memory.jsonl")),
|
|
15547
|
+
checkFile("dev-state.md", join23(configDir, DEV_STATE_FILE_NAME)),
|
|
15548
|
+
checkFile("hooks-trust.json", join23(configDir, "hooks-trust.json")),
|
|
15549
|
+
checkFile("plugin-state.json", join23(configDir, "plugins", "plugin-state.json"))
|
|
15204
15550
|
],
|
|
15205
15551
|
context: {
|
|
15206
15552
|
enabled: contextSetting !== false,
|
|
@@ -15235,7 +15581,7 @@ function buildDoctorReport(options) {
|
|
|
15235
15581
|
disabled: hooks.filter((h) => h.disabled).length
|
|
15236
15582
|
},
|
|
15237
15583
|
plugins: {
|
|
15238
|
-
root:
|
|
15584
|
+
root: join23(configDir, "plugins"),
|
|
15239
15585
|
installed: plugins.length,
|
|
15240
15586
|
active: activeAssets.plugins.length,
|
|
15241
15587
|
invalid: plugins.filter((p) => !p.valid).length,
|
|
@@ -15250,7 +15596,7 @@ function buildDoctorReport(options) {
|
|
|
15250
15596
|
mcp: {
|
|
15251
15597
|
enabled: config.get("mcpEnabled"),
|
|
15252
15598
|
configured: Object.keys(mcpServers).length,
|
|
15253
|
-
projectConfigExists:
|
|
15599
|
+
projectConfigExists: existsSync28(join23(projectRoot, MCP_PROJECT_CONFIG_NAME)),
|
|
15254
15600
|
statuses: options.mcpStatuses ?? []
|
|
15255
15601
|
},
|
|
15256
15602
|
recentCrashes: listRecentCrashes(5, configDir),
|
|
@@ -15478,7 +15824,7 @@ Cache: write=${cacheCreate} read=${cacheRead}` : "";
|
|
|
15478
15824
|
Cost: ${formatCost(cost)}` : "";
|
|
15479
15825
|
let memoryLine = "";
|
|
15480
15826
|
try {
|
|
15481
|
-
const { countPendingMemories } = await import("./persistent-memory-
|
|
15827
|
+
const { countPendingMemories } = await import("./persistent-memory-3FTOC7QT.js");
|
|
15482
15828
|
const pending = countPendingMemories(ctx.config.getConfigDir());
|
|
15483
15829
|
if (pending > 0) memoryLine = `
|
|
15484
15830
|
Memory: \u26A0 ${pending} pending approval \u2014 see the Memory panel or /memory`;
|
|
@@ -15686,18 +16032,16 @@ async function handleExport(args, ctx) {
|
|
|
15686
16032
|
}
|
|
15687
16033
|
async function handleCost(_args, ctx) {
|
|
15688
16034
|
const u = ctx.sessionTokenUsage;
|
|
15689
|
-
const
|
|
15690
|
-
|
|
15691
|
-
|
|
15692
|
-
|
|
15693
|
-
|
|
15694
|
-
|
|
15695
|
-
|
|
15696
|
-
|
|
15697
|
-
|
|
15698
|
-
|
|
15699
|
-
if (withoutCache != null) savings = withoutCache - cost;
|
|
15700
|
-
}
|
|
16035
|
+
const report = buildSessionCostReport({
|
|
16036
|
+
provider: ctx.currentProvider,
|
|
16037
|
+
model: ctx.currentModel,
|
|
16038
|
+
usage: u,
|
|
16039
|
+
accruedUsd: ctx.sessionAccruedCostUsd,
|
|
16040
|
+
accrualIncomplete: ctx.sessionCostIncomplete
|
|
16041
|
+
});
|
|
16042
|
+
const total = report.totalTokens;
|
|
16043
|
+
const cost = report.cost;
|
|
16044
|
+
const savings = report.savings;
|
|
15701
16045
|
const lines = [
|
|
15702
16046
|
"\u{1F4B0} Session Cost & Token Usage",
|
|
15703
16047
|
` Provider : ${ctx.currentProvider}`,
|
|
@@ -15706,13 +16050,13 @@ async function handleCost(_args, ctx) {
|
|
|
15706
16050
|
` Output : ${u.outputTokens.toLocaleString()}`
|
|
15707
16051
|
];
|
|
15708
16052
|
if (u.cacheCreationTokens > 0) lines.push(` Cache write : ${u.cacheCreationTokens.toLocaleString()}`);
|
|
15709
|
-
if (
|
|
15710
|
-
|
|
15711
|
-
lines.push(` Cache read : ${u.cacheReadTokens.toLocaleString()} (${pct}% hit rate)`);
|
|
16053
|
+
if (report.cacheHitPct != null) {
|
|
16054
|
+
lines.push(` Cache read : ${u.cacheReadTokens.toLocaleString()} (${report.cacheHitPct}% hit rate)`);
|
|
15712
16055
|
}
|
|
15713
16056
|
lines.push(` Total tokens : ${total.toLocaleString()}`);
|
|
15714
16057
|
if (cost != null) {
|
|
15715
|
-
|
|
16058
|
+
const note = costSourceNote(report.costSource);
|
|
16059
|
+
lines.push(` Cost : ${formatCost(cost)}${note ? ` ${note}` : ""}`);
|
|
15716
16060
|
if (savings != null && savings > 0) {
|
|
15717
16061
|
lines.push(` Cache savings : -${formatCost(savings)} (vs no cache)`);
|
|
15718
16062
|
}
|
|
@@ -16062,7 +16406,7 @@ async function handlePlugin(args, ctx) {
|
|
|
16062
16406
|
lines.push("");
|
|
16063
16407
|
}
|
|
16064
16408
|
if (pluginTools.length > 0) {
|
|
16065
|
-
const legacyDir =
|
|
16409
|
+
const legacyDir = join24(configDir, PLUGINS_DIR_NAME);
|
|
16066
16410
|
lines.push(`**Legacy JS tools** (${pluginTools.length}):`);
|
|
16067
16411
|
lines.push(`Dir: ${legacyDir}`);
|
|
16068
16412
|
for (const t of pluginTools) lines.push(` \u2022 ${t.definition.name} \u2014 ${t.definition.description}`);
|
|
@@ -16186,8 +16530,8 @@ ${activated.meta.description || ""}` });
|
|
|
16186
16530
|
}
|
|
16187
16531
|
async function handleCommands(_args, ctx) {
|
|
16188
16532
|
const configDir = ctx.config.getConfigDir();
|
|
16189
|
-
const commandsDir =
|
|
16190
|
-
if (!
|
|
16533
|
+
const commandsDir = join24(configDir, CUSTOM_COMMANDS_DIR_NAME);
|
|
16534
|
+
if (!existsSync29(commandsDir)) {
|
|
16191
16535
|
ctx.send({ type: "info", message: `No custom commands directory.
|
|
16192
16536
|
Create: ${commandsDir}/ with .md files.` });
|
|
16193
16537
|
return;
|
|
@@ -16293,7 +16637,7 @@ async function handleTest(args, ctx) {
|
|
|
16293
16637
|
const isCommand = argStr.includes(" ") || /^(mvn|gradle|npm|pytest|cargo|go)\b/.test(argStr);
|
|
16294
16638
|
testArgs = isCommand ? { command: argStr } : { filter: argStr };
|
|
16295
16639
|
}
|
|
16296
|
-
const runTests = ctx.runTests ?? (await import("./run-tests-
|
|
16640
|
+
const runTests = ctx.runTests ?? (await import("./run-tests-6MWE6D5S.js")).executeTests;
|
|
16297
16641
|
const report = await runTests(testArgs);
|
|
16298
16642
|
ctx.send({ type: "info", message: report });
|
|
16299
16643
|
} catch (err) {
|
|
@@ -16317,9 +16661,9 @@ async function handleDiff(args, ctx) {
|
|
|
16317
16661
|
let modifiedFiles = 0;
|
|
16318
16662
|
const diffLines2 = [];
|
|
16319
16663
|
for (const [filePath, { earliest }] of fileMap) {
|
|
16320
|
-
const currentContent =
|
|
16664
|
+
const currentContent = existsSync29(filePath) ? (() => {
|
|
16321
16665
|
try {
|
|
16322
|
-
return
|
|
16666
|
+
return readFileSync23(filePath, "utf-8");
|
|
16323
16667
|
} catch {
|
|
16324
16668
|
return null;
|
|
16325
16669
|
}
|
|
@@ -16354,9 +16698,9 @@ async function handleDiff(args, ctx) {
|
|
|
16354
16698
|
}
|
|
16355
16699
|
async function handleInit(args, ctx) {
|
|
16356
16700
|
const cwd = process.cwd();
|
|
16357
|
-
const targetPath =
|
|
16701
|
+
const targetPath = join24(cwd, "AICLI.md");
|
|
16358
16702
|
const force = args.includes("--force");
|
|
16359
|
-
if (
|
|
16703
|
+
if (existsSync29(targetPath) && !force) {
|
|
16360
16704
|
ctx.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
|
|
16361
16705
|
Use /init --force to overwrite.` });
|
|
16362
16706
|
return;
|
|
@@ -16551,7 +16895,7 @@ async function handleMemory(args, ctx) {
|
|
|
16551
16895
|
ctx.handleMemoryManage(sub, args[1], sub === "expire" ? args[2] : void 0);
|
|
16552
16896
|
} else if (sub === "export") {
|
|
16553
16897
|
if (args[1] === "md") {
|
|
16554
|
-
const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-
|
|
16898
|
+
const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-3FTOC7QT.js");
|
|
16555
16899
|
ctx.send({
|
|
16556
16900
|
type: "export_data",
|
|
16557
16901
|
format: "md",
|
|
@@ -16589,7 +16933,7 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
|
|
|
16589
16933
|
return;
|
|
16590
16934
|
}
|
|
16591
16935
|
const dirPath = resolve9(sub);
|
|
16592
|
-
if (!
|
|
16936
|
+
if (!existsSync29(dirPath)) {
|
|
16593
16937
|
ctx.send({ type: "error", message: `Directory not found: ${dirPath}` });
|
|
16594
16938
|
return;
|
|
16595
16939
|
}
|
|
@@ -16972,6 +17316,7 @@ var webCommandHandlers = {
|
|
|
16972
17316
|
var SessionHandler = class {
|
|
16973
17317
|
ws;
|
|
16974
17318
|
config;
|
|
17319
|
+
costTracker;
|
|
16975
17320
|
providers;
|
|
16976
17321
|
sessions;
|
|
16977
17322
|
toolRegistry;
|
|
@@ -16985,12 +17330,22 @@ var SessionHandler = class {
|
|
|
16985
17330
|
runtimeThinkingEffort = null;
|
|
16986
17331
|
sessionTokenUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
|
|
16987
17332
|
unknownPricingWarnings = /* @__PURE__ */ new Set();
|
|
17333
|
+
/**
|
|
17334
|
+
* 本会话按**调用时刻**费率累计的花费(美元)。
|
|
17335
|
+
* 审计 P2-02 第二批:Web 端此前根本不给 CostTracker 记账,aicli usage 与
|
|
17336
|
+
* monthlyBudget 完全看不到 Web UI 的开销;顺带补上 P3-01 的记账口径。
|
|
17337
|
+
*/
|
|
17338
|
+
sessionAccruedCostUsd = 0;
|
|
17339
|
+
sessionCostIncomplete = false;
|
|
16988
17340
|
/** Accumulate a TokenUsage (with optional cache fields) into sessionTokenUsage. */
|
|
16989
17341
|
addWebSessionUsage(u) {
|
|
16990
17342
|
this.sessionTokenUsage.inputTokens += u.inputTokens;
|
|
16991
17343
|
this.sessionTokenUsage.outputTokens += u.outputTokens;
|
|
16992
17344
|
this.sessionTokenUsage.cacheCreationTokens += u.cacheCreationTokens ?? 0;
|
|
16993
17345
|
this.sessionTokenUsage.cacheReadTokens += u.cacheReadTokens ?? 0;
|
|
17346
|
+
const priced = this.costTracker.addCost(this.currentProvider, this.currentModel, u);
|
|
17347
|
+
if (priced === null) this.sessionCostIncomplete = true;
|
|
17348
|
+
else this.sessionAccruedCostUsd += priced;
|
|
16994
17349
|
const totalTokens = u.inputTokens + u.outputTokens + (u.cacheCreationTokens ?? 0) + (u.cacheReadTokens ?? 0);
|
|
16995
17350
|
if (totalTokens > 0 && !getPricing(this.currentProvider, this.currentModel)) {
|
|
16996
17351
|
const key = `${this.currentProvider}/${this.currentModel}`;
|
|
@@ -17002,6 +17357,8 @@ var SessionHandler = class {
|
|
|
17002
17357
|
}
|
|
17003
17358
|
resetWebSessionUsage() {
|
|
17004
17359
|
this.sessionTokenUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
|
|
17360
|
+
this.sessionAccruedCostUsd = 0;
|
|
17361
|
+
this.sessionCostIncomplete = false;
|
|
17005
17362
|
}
|
|
17006
17363
|
abortController = null;
|
|
17007
17364
|
userInterjection = null;
|
|
@@ -17035,6 +17392,7 @@ var SessionHandler = class {
|
|
|
17035
17392
|
constructor(ws, shared) {
|
|
17036
17393
|
this.ws = ws;
|
|
17037
17394
|
this.config = shared.config;
|
|
17395
|
+
this.costTracker = shared.costTracker;
|
|
17038
17396
|
this.providers = shared.providers;
|
|
17039
17397
|
this.sessions = shared.sessions;
|
|
17040
17398
|
this.toolRegistry = shared.toolRegistry;
|
|
@@ -18471,8 +18829,8 @@ async function setupProxy(configProxy) {
|
|
|
18471
18829
|
}
|
|
18472
18830
|
|
|
18473
18831
|
// src/web/auth.ts
|
|
18474
|
-
import { existsSync as
|
|
18475
|
-
import { isAbsolute as isAbsolute3, join as
|
|
18832
|
+
import { existsSync as existsSync30, readFileSync as readFileSync24, writeFileSync as writeFileSync4, mkdirSync as mkdirSync15, readdirSync as readdirSync14, copyFileSync } from "fs";
|
|
18833
|
+
import { isAbsolute as isAbsolute3, join as join25, relative as relative5 } from "path";
|
|
18476
18834
|
import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
|
|
18477
18835
|
var USERS_FILE = "users.json";
|
|
18478
18836
|
var TOKEN_EXPIRY_HOURS = 24;
|
|
@@ -18487,7 +18845,7 @@ var AuthManager = class {
|
|
|
18487
18845
|
db;
|
|
18488
18846
|
constructor(baseDir) {
|
|
18489
18847
|
this.baseDir = baseDir;
|
|
18490
|
-
this.usersFile =
|
|
18848
|
+
this.usersFile = join25(baseDir, USERS_FILE);
|
|
18491
18849
|
this.db = this.loadOrCreate();
|
|
18492
18850
|
}
|
|
18493
18851
|
// ── Public API ─────────────────────────────────────────────────
|
|
@@ -18516,9 +18874,9 @@ var AuthManager = class {
|
|
|
18516
18874
|
}
|
|
18517
18875
|
const salt = randomBytes(16).toString("hex");
|
|
18518
18876
|
const passwordHash = this.hashPassword(password, salt);
|
|
18519
|
-
const dataDir =
|
|
18520
|
-
const fullDataDir =
|
|
18521
|
-
mkdirSync15(
|
|
18877
|
+
const dataDir = join25(USERS_DIR, username);
|
|
18878
|
+
const fullDataDir = join25(this.baseDir, dataDir);
|
|
18879
|
+
mkdirSync15(join25(fullDataDir, "history"), { recursive: true });
|
|
18522
18880
|
const user = {
|
|
18523
18881
|
username,
|
|
18524
18882
|
passwordHash,
|
|
@@ -18633,7 +18991,7 @@ var AuthManager = class {
|
|
|
18633
18991
|
getUserDataDir(username) {
|
|
18634
18992
|
const user = this.db.users.find((u) => u.username === username);
|
|
18635
18993
|
if (!user) throw new Error(`User not found: ${username}`);
|
|
18636
|
-
return
|
|
18994
|
+
return join25(this.baseDir, user.dataDir);
|
|
18637
18995
|
}
|
|
18638
18996
|
/** List all usernames */
|
|
18639
18997
|
listUsers() {
|
|
@@ -18669,30 +19027,30 @@ var AuthManager = class {
|
|
|
18669
19027
|
const err = this.register(username, password);
|
|
18670
19028
|
if (err) return err;
|
|
18671
19029
|
const userDir = this.getUserDataDir(username);
|
|
18672
|
-
const globalConfig =
|
|
18673
|
-
if (
|
|
19030
|
+
const globalConfig = join25(this.baseDir, "config.json");
|
|
19031
|
+
if (existsSync30(globalConfig)) {
|
|
18674
19032
|
try {
|
|
18675
|
-
const content =
|
|
18676
|
-
writeFileSync4(
|
|
19033
|
+
const content = readFileSync24(globalConfig, "utf-8");
|
|
19034
|
+
writeFileSync4(join25(userDir, "config.json"), content, "utf-8");
|
|
18677
19035
|
} catch {
|
|
18678
19036
|
}
|
|
18679
19037
|
}
|
|
18680
|
-
const globalMemory =
|
|
18681
|
-
if (
|
|
19038
|
+
const globalMemory = join25(this.baseDir, "memory.md");
|
|
19039
|
+
if (existsSync30(globalMemory)) {
|
|
18682
19040
|
try {
|
|
18683
|
-
const content =
|
|
18684
|
-
writeFileSync4(
|
|
19041
|
+
const content = readFileSync24(globalMemory, "utf-8");
|
|
19042
|
+
writeFileSync4(join25(userDir, "memory.md"), content, "utf-8");
|
|
18685
19043
|
} catch {
|
|
18686
19044
|
}
|
|
18687
19045
|
}
|
|
18688
|
-
const globalHistory =
|
|
18689
|
-
if (
|
|
19046
|
+
const globalHistory = join25(this.baseDir, "history");
|
|
19047
|
+
if (existsSync30(globalHistory)) {
|
|
18690
19048
|
try {
|
|
18691
19049
|
const files = readdirSync14(globalHistory).filter((f) => f.endsWith(".json"));
|
|
18692
|
-
const userHistory =
|
|
19050
|
+
const userHistory = join25(userDir, "history");
|
|
18693
19051
|
for (const f of files) {
|
|
18694
19052
|
try {
|
|
18695
|
-
copyFileSync(
|
|
19053
|
+
copyFileSync(join25(globalHistory, f), join25(userHistory, f));
|
|
18696
19054
|
} catch {
|
|
18697
19055
|
}
|
|
18698
19056
|
}
|
|
@@ -18703,9 +19061,9 @@ var AuthManager = class {
|
|
|
18703
19061
|
}
|
|
18704
19062
|
// ── Private methods ────────────────────────────────────────────
|
|
18705
19063
|
loadOrCreate() {
|
|
18706
|
-
if (
|
|
19064
|
+
if (existsSync30(this.usersFile)) {
|
|
18707
19065
|
try {
|
|
18708
|
-
const parsed = JSON.parse(
|
|
19066
|
+
const parsed = JSON.parse(readFileSync24(this.usersFile, "utf-8"));
|
|
18709
19067
|
if (!this.isValidUsersDB(parsed)) {
|
|
18710
19068
|
throw new Error("database structure is invalid");
|
|
18711
19069
|
}
|
|
@@ -18739,7 +19097,7 @@ var AuthManager = class {
|
|
|
18739
19097
|
if (typeof record.username !== "string" || !/^[a-z0-9_-]{2,32}$/.test(record.username) || usernames.has(record.username) || typeof record.passwordHash !== "string" || record.passwordHash.length === 0 || typeof record.salt !== "string" || record.salt.length === 0 || typeof record.createdAt !== "string" || typeof record.dataDir !== "string" || record.dataDir.length === 0 || isAbsolute3(record.dataDir)) {
|
|
18740
19098
|
return false;
|
|
18741
19099
|
}
|
|
18742
|
-
const dataDirFromBase = relative5(this.baseDir,
|
|
19100
|
+
const dataDirFromBase = relative5(this.baseDir, join25(this.baseDir, record.dataDir));
|
|
18743
19101
|
if (dataDirFromBase.startsWith("..") || isAbsolute3(dataDirFromBase)) return false;
|
|
18744
19102
|
if (record.hashVersion !== void 0 && record.hashVersion !== 1 && record.hashVersion !== 2) return false;
|
|
18745
19103
|
if (record.tokensRevokedBefore !== void 0 && !Number.isFinite(record.tokensRevokedBefore)) return false;
|
|
@@ -18855,8 +19213,8 @@ async function startWebServer(options = {}) {
|
|
|
18855
19213
|
}
|
|
18856
19214
|
}
|
|
18857
19215
|
let skillManager = null;
|
|
18858
|
-
const skillsDir =
|
|
18859
|
-
if (
|
|
19216
|
+
const skillsDir = join26(config.getConfigDir(), SKILLS_DIR_NAME);
|
|
19217
|
+
if (existsSync31(skillsDir)) {
|
|
18860
19218
|
skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
|
|
18861
19219
|
skillManager.loadSkills(pluginAssets.skillDirs);
|
|
18862
19220
|
const count = skillManager.listSkills().length;
|
|
@@ -18866,6 +19224,7 @@ async function startWebServer(options = {}) {
|
|
|
18866
19224
|
}
|
|
18867
19225
|
const shared = {
|
|
18868
19226
|
config,
|
|
19227
|
+
costTracker: new CostTracker(config.getConfigDir()),
|
|
18869
19228
|
providers,
|
|
18870
19229
|
sessions,
|
|
18871
19230
|
toolRegistry,
|
|
@@ -18931,18 +19290,18 @@ async function startWebServer(options = {}) {
|
|
|
18931
19290
|
next();
|
|
18932
19291
|
};
|
|
18933
19292
|
const moduleDir = getModuleDir();
|
|
18934
|
-
let clientDir =
|
|
18935
|
-
if (!
|
|
18936
|
-
clientDir =
|
|
19293
|
+
let clientDir = join26(moduleDir, "web", "client");
|
|
19294
|
+
if (!existsSync31(clientDir)) {
|
|
19295
|
+
clientDir = join26(moduleDir, "client");
|
|
18937
19296
|
}
|
|
18938
|
-
if (!
|
|
18939
|
-
clientDir =
|
|
19297
|
+
if (!existsSync31(clientDir)) {
|
|
19298
|
+
clientDir = join26(moduleDir, "..", "..", "src", "web", "client");
|
|
18940
19299
|
}
|
|
18941
|
-
if (!
|
|
18942
|
-
clientDir =
|
|
19300
|
+
if (!existsSync31(clientDir)) {
|
|
19301
|
+
clientDir = join26(process.cwd(), "src", "web", "client");
|
|
18943
19302
|
}
|
|
18944
19303
|
console.log(` Static files: ${clientDir}`);
|
|
18945
|
-
app.use("/vendor", express.static(
|
|
19304
|
+
app.use("/vendor", express.static(join26(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
|
|
18946
19305
|
app.use(express.static(clientDir));
|
|
18947
19306
|
app.get("/api/status", (_req, res) => {
|
|
18948
19307
|
res.json({
|
|
@@ -19009,7 +19368,7 @@ async function startWebServer(options = {}) {
|
|
|
19009
19368
|
app.get("/api/files", requireAuth, (req, res) => {
|
|
19010
19369
|
const cwd = process.cwd();
|
|
19011
19370
|
const prefix = req.query.prefix || "";
|
|
19012
|
-
const targetDir =
|
|
19371
|
+
const targetDir = join26(cwd, prefix);
|
|
19013
19372
|
try {
|
|
19014
19373
|
const canonicalTarget = realpathSync3(resolve10(targetDir));
|
|
19015
19374
|
const canonicalCwd = realpathSync3(resolve10(cwd));
|
|
@@ -19026,7 +19385,7 @@ async function startWebServer(options = {}) {
|
|
|
19026
19385
|
const entries = readdirSync15(targetDir, { withFileTypes: true });
|
|
19027
19386
|
const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
|
|
19028
19387
|
name: e.name,
|
|
19029
|
-
path: relative6(cwd,
|
|
19388
|
+
path: relative6(cwd, join26(targetDir, e.name)).replace(/\\/g, "/"),
|
|
19030
19389
|
isDir: e.isDirectory()
|
|
19031
19390
|
}));
|
|
19032
19391
|
res.json({ files });
|
|
@@ -19066,8 +19425,8 @@ async function startWebServer(options = {}) {
|
|
|
19066
19425
|
try {
|
|
19067
19426
|
const authUser = req._authUser;
|
|
19068
19427
|
const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
|
|
19069
|
-
const filePath =
|
|
19070
|
-
if (!
|
|
19428
|
+
const filePath = join26(histDir, `${id}.json`);
|
|
19429
|
+
if (!existsSync31(filePath)) {
|
|
19071
19430
|
res.status(404).json({ error: "Session not found" });
|
|
19072
19431
|
return;
|
|
19073
19432
|
}
|
|
@@ -19082,7 +19441,7 @@ async function startWebServer(options = {}) {
|
|
|
19082
19441
|
res.status(404).json({ error: "Session not found" });
|
|
19083
19442
|
return;
|
|
19084
19443
|
}
|
|
19085
|
-
const data = JSON.parse(
|
|
19444
|
+
const data = JSON.parse(readFileSync25(filePath, "utf-8"));
|
|
19086
19445
|
res.json({ session: data });
|
|
19087
19446
|
} catch (err) {
|
|
19088
19447
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
@@ -19095,7 +19454,7 @@ async function startWebServer(options = {}) {
|
|
|
19095
19454
|
return;
|
|
19096
19455
|
}
|
|
19097
19456
|
const cwd = process.cwd();
|
|
19098
|
-
const fullPath = resolve10(
|
|
19457
|
+
const fullPath = resolve10(join26(cwd, filePath));
|
|
19099
19458
|
try {
|
|
19100
19459
|
const canonicalFull = realpathSync3(fullPath);
|
|
19101
19460
|
const canonicalCwd = realpathSync3(resolve10(cwd));
|
|
@@ -19113,7 +19472,7 @@ async function startWebServer(options = {}) {
|
|
|
19113
19472
|
res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
|
|
19114
19473
|
return;
|
|
19115
19474
|
}
|
|
19116
|
-
const content =
|
|
19475
|
+
const content = readFileSync25(fullPath, "utf-8");
|
|
19117
19476
|
res.json({ content, size: stat.size });
|
|
19118
19477
|
} catch {
|
|
19119
19478
|
res.json({ error: "Cannot read file" });
|
|
@@ -19132,6 +19491,8 @@ async function startWebServer(options = {}) {
|
|
|
19132
19491
|
const userSessions = new SessionManager(userConfig);
|
|
19133
19492
|
const userShared = {
|
|
19134
19493
|
config: userConfig,
|
|
19494
|
+
// 每用户各自的 config 目录 → 各自的 cost-history.json
|
|
19495
|
+
costTracker: new CostTracker(userConfig.getConfigDir()),
|
|
19135
19496
|
providers,
|
|
19136
19497
|
// Shared — providers re-read API keys per call
|
|
19137
19498
|
sessions: userSessions,
|
|
@@ -19368,17 +19729,17 @@ function resolveProjectMcpPath() {
|
|
|
19368
19729
|
const cwd = process.cwd();
|
|
19369
19730
|
const gitRoot = getGitRoot(cwd);
|
|
19370
19731
|
const projectRoot = gitRoot ?? cwd;
|
|
19371
|
-
const configPath =
|
|
19372
|
-
return
|
|
19732
|
+
const configPath = join26(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
19733
|
+
return existsSync31(configPath) ? configPath : null;
|
|
19373
19734
|
}
|
|
19374
19735
|
function loadProjectMcpConfig() {
|
|
19375
19736
|
const cwd = process.cwd();
|
|
19376
19737
|
const gitRoot = getGitRoot(cwd);
|
|
19377
19738
|
const projectRoot = gitRoot ?? cwd;
|
|
19378
|
-
const configPath =
|
|
19379
|
-
if (!
|
|
19739
|
+
const configPath = join26(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
19740
|
+
if (!existsSync31(configPath)) return null;
|
|
19380
19741
|
try {
|
|
19381
|
-
const raw = JSON.parse(
|
|
19742
|
+
const raw = JSON.parse(readFileSync25(configPath, "utf-8"));
|
|
19382
19743
|
return raw.mcpServers ?? raw;
|
|
19383
19744
|
} catch {
|
|
19384
19745
|
return null;
|