jinzd-ai-cli 0.4.266 → 0.4.268
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-FQU6HK3Z.js → batch-2N2YMRX2.js} +2 -2
- package/dist/{chunk-QW76RX6G.js → chunk-2FQGMR34.js} +1 -1
- package/dist/{chunk-Y2X24VYB.js → chunk-74BK22BM.js} +4 -4
- package/dist/{chunk-TJNBSR3I.js → chunk-7US7K3CD.js} +1 -1
- package/dist/{chunk-FA3HGXXM.js → chunk-EPVJS4F6.js} +3 -1
- package/dist/{chunk-V4D7YD24.js → chunk-R7GERZDA.js} +145 -4
- package/dist/{chunk-GWOZET2E.js → chunk-RRQZRSXW.js} +1 -1
- package/dist/{chunk-LGJ7EX2F.js → chunk-UT2WAJJG.js} +1 -1
- package/dist/{chunk-CEJUB4YV.js → chunk-VGVKJTHF.js} +1 -1
- package/dist/{chunk-UCHND2K6.js → chunk-VNCBY4KY.js} +193 -2
- package/dist/{chunk-3O4VLKD4.js → chunk-WNG3YVZX.js} +1 -1
- package/dist/{chunk-N2E6GFLZ.js → chunk-WWLXW4SY.js} +1 -1
- package/dist/{chunk-LHY7ST6X.js → chunk-XDITAGE5.js} +1 -1
- package/dist/{chunk-FIDEG2PG.js → chunk-YLSM7RF6.js} +3 -3
- package/dist/{chunk-XEVURCAM.js → chunk-Z7CZ72RP.js} +3 -3
- package/dist/{ci-GZH3QMME.js → ci-3RWVJSZ3.js} +4 -4
- package/dist/{ci-format-VKP2MGYJ.js → ci-format-M6HVCXCG.js} +2 -2
- package/dist/{constants-ICAF6I3J.js → constants-G6VN7SJL.js} +1 -1
- package/dist/{doctor-cli-CO6BJWRW.js → doctor-cli-Y3SVVJBO.js} +4 -4
- package/dist/electron-server.js +915 -410
- package/dist/{hub-4APIRQ73.js → hub-5T5HKOAR.js} +1 -1
- package/dist/index.js +87 -134
- package/dist/{persistent-memory-YD3SHSUH.js → persistent-memory-XVSSR277.js} +2 -2
- package/dist/{persistent-memory-YPO25MOP.js → persistent-memory-YBWIZWQP.js} +2 -2
- package/dist/{pr-CDOPCAPK.js → pr-NF5IWLH3.js} +4 -4
- package/dist/{run-tests-QNNP2HCD.js → run-tests-33X3D7PV.js} +2 -2
- package/dist/{run-tests-4XRHKHN5.js → run-tests-Y44SCLIO.js} +2 -2
- package/dist/{server-B4A7LHZ5.js → server-3LERX42X.js} +5 -5
- package/dist/{server-53GM3CRH.js → server-NJ3ZNPQW.js} +114 -38
- package/dist/{task-orchestrator-WNV6PT6Z.js → task-orchestrator-JN2WFZSF.js} +5 -5
- package/dist/{usage-E3FEYSBU.js → usage-IFTNXTWC.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-RRQZRSXW.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-XDITAGE5.js";
|
|
45
45
|
import {
|
|
46
46
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
47
47
|
APP_NAME,
|
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
CONFIG_FILE_NAME,
|
|
52
52
|
CONTEXT_FILE_CANDIDATES,
|
|
53
53
|
CONTEXT_FILE_MAX_BYTES,
|
|
54
|
+
CONTEXT_PRESSURE_THRESHOLD,
|
|
54
55
|
CUSTOM_COMMANDS_DIR_NAME,
|
|
55
56
|
DEFAULT_MAX_TOKENS,
|
|
56
57
|
DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP,
|
|
@@ -73,7 +74,7 @@ import {
|
|
|
73
74
|
SUBAGENT_MAX_ROUNDS_LIMIT,
|
|
74
75
|
VERSION,
|
|
75
76
|
buildUserIdentityPrompt
|
|
76
|
-
} from "./chunk-
|
|
77
|
+
} from "./chunk-EPVJS4F6.js";
|
|
77
78
|
import {
|
|
78
79
|
hasSemanticIndex,
|
|
79
80
|
semanticSearch
|
|
@@ -91,8 +92,8 @@ import {
|
|
|
91
92
|
import express from "express";
|
|
92
93
|
import { createServer } from "http";
|
|
93
94
|
import { WebSocketServer } from "ws";
|
|
94
|
-
import { join as
|
|
95
|
-
import { existsSync as
|
|
95
|
+
import { join as join26, dirname as dirname10, resolve as resolve10, relative as relative6, sep as sep4 } from "path";
|
|
96
|
+
import { existsSync as existsSync31, readFileSync as readFileSync25, readdirSync as readdirSync15, statSync as statSync14, realpathSync as realpathSync3 } from "fs";
|
|
96
97
|
import { networkInterfaces } from "os";
|
|
97
98
|
|
|
98
99
|
// src/config/config-manager.ts
|
|
@@ -709,6 +710,403 @@ ${err}`
|
|
|
709
710
|
}
|
|
710
711
|
};
|
|
711
712
|
|
|
713
|
+
// src/core/cost-tracker.ts
|
|
714
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
715
|
+
import { join as join2 } from "path";
|
|
716
|
+
|
|
717
|
+
// src/core/pricing.ts
|
|
718
|
+
var DEEPSEEK_CNY_PER_USD = 6.9;
|
|
719
|
+
var DEEPSEEK_TIME_PRICING_EFFECTIVE_AT = /* @__PURE__ */ new Date("2026-08-16T16:00:00.000Z");
|
|
720
|
+
function deepSeekCnyPricing(cacheRead, input, output) {
|
|
721
|
+
return {
|
|
722
|
+
cacheRead: cacheRead / DEEPSEEK_CNY_PER_USD,
|
|
723
|
+
input: input / DEEPSEEK_CNY_PER_USD,
|
|
724
|
+
output: output / DEEPSEEK_CNY_PER_USD
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
function getDeepSeekV4Pricing(model, at) {
|
|
728
|
+
const isPro = model.startsWith("deepseek-v4-pro");
|
|
729
|
+
const isFlash = model.startsWith("deepseek-v4-flash");
|
|
730
|
+
if (!isPro && !isFlash) return null;
|
|
731
|
+
if (at < DEEPSEEK_TIME_PRICING_EFFECTIVE_AT) {
|
|
732
|
+
return isPro ? deepSeekCnyPricing(0.025, 3, 6) : deepSeekCnyPricing(0.02, 1, 2);
|
|
733
|
+
}
|
|
734
|
+
const beijingHour = (at.getUTCHours() + 8) % 24;
|
|
735
|
+
const isPeak = beijingHour >= 9 && beijingHour < 12 || beijingHour >= 14 && beijingHour < 18;
|
|
736
|
+
if (isPro) {
|
|
737
|
+
return isPeak ? deepSeekCnyPricing(0.3, 9, 27) : deepSeekCnyPricing(0.15, 4.5, 13.5);
|
|
738
|
+
}
|
|
739
|
+
return isPeak ? deepSeekCnyPricing(0.1, 3, 9) : deepSeekCnyPricing(0.05, 1.5, 4.5);
|
|
740
|
+
}
|
|
741
|
+
var PRICING_TABLE = {
|
|
742
|
+
// ── Anthropic Claude ──────────────────────────────────────────
|
|
743
|
+
"claude-opus-4-8": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
744
|
+
"claude-opus-4-7": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
745
|
+
"claude-opus-4-6": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
746
|
+
"claude-opus-4-5": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
747
|
+
"claude-sonnet-4-6": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
|
|
748
|
+
"claude-sonnet-4-5-20250929": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
|
|
749
|
+
"claude-haiku-4-5-20251001": { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1 },
|
|
750
|
+
"claude-haiku-4-5": { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1 },
|
|
751
|
+
// Legacy Claude 3.x families (prefix fallback handles minor date suffixes)
|
|
752
|
+
"claude-3-5-sonnet": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
|
|
753
|
+
"claude-3-5-haiku": { input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08 },
|
|
754
|
+
"claude-3-opus": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
755
|
+
// ── OpenAI ────────────────────────────────────────────────────
|
|
756
|
+
// GAP (v0.4.184): the GPT-5 family (gpt-5, gpt-5-mini/nano, gpt-5.4, gpt-5.4-pro)
|
|
757
|
+
// and o4-mini are in the provider's model list but have NO verified pricing yet —
|
|
758
|
+
// left unpriced on purpose so `aicli usage` shows "—" (unknown) rather than a
|
|
759
|
+
// deceptive $0.00. Add real rates when confirmed from the official pricing page.
|
|
760
|
+
"gpt-4o": { input: 2.5, output: 10, cacheRead: 1.25 },
|
|
761
|
+
"gpt-4o-mini": { input: 0.15, output: 0.6, cacheRead: 0.075 },
|
|
762
|
+
"gpt-4-turbo": { input: 10, output: 30 },
|
|
763
|
+
"gpt-4": { input: 30, output: 60 },
|
|
764
|
+
"gpt-4.1": { input: 2, output: 8, cacheRead: 0.5 },
|
|
765
|
+
"gpt-4.1-mini": { input: 0.4, output: 1.6, cacheRead: 0.1 },
|
|
766
|
+
"gpt-4.1-nano": { input: 0.1, output: 0.4, cacheRead: 0.025 },
|
|
767
|
+
"o1": { input: 15, output: 60, cacheRead: 7.5 },
|
|
768
|
+
"o1-mini": { input: 3, output: 12, cacheRead: 1.5 },
|
|
769
|
+
"o3": { input: 10, output: 40, cacheRead: 2.5 },
|
|
770
|
+
"o3-mini": { input: 1.1, output: 4.4, cacheRead: 0.55 },
|
|
771
|
+
// ── Google Gemini ─────────────────────────────────────────────
|
|
772
|
+
"gemini-2.5-pro": { input: 1.25, output: 10 },
|
|
773
|
+
"gemini-2.5-flash": { input: 0.3, output: 2.5 },
|
|
774
|
+
"gemini-2.0-flash": { input: 0.1, output: 0.4 },
|
|
775
|
+
"gemini-1.5-pro": { input: 1.25, output: 5 },
|
|
776
|
+
"gemini-1.5-flash": { input: 0.075, output: 0.3 },
|
|
777
|
+
// ── DeepSeek ──────────────────────────────────────────────────
|
|
778
|
+
// V4 family (2026-04-23+):1M context, Thinking / Non-Thinking dual-mode, 384K max output
|
|
779
|
+
// 费率 2026-08-01 对 api-docs.deepseek.com/quick_start/pricing 官方 USD 表逐项核对。
|
|
780
|
+
// 修正前 pro 三项整齐高 4×(官方降过价未跟)、flash cacheRead 高 10×(像是按 input 的
|
|
781
|
+
// 20% 拍脑袋而非抄文档)——`aicli usage` 因此系统性高报 DeepSeek 成本。
|
|
782
|
+
// 待办:官方已公告峰谷定价(北京时间 9-12 点、14-18 点为平时 2 倍),本表仍是单一费率,
|
|
783
|
+
// 届时高峰时段会低报;等该策略实际生效后再决定是否引入时段维度。
|
|
784
|
+
// Legacy aliases:retires 2026-07-24 UTC 15:59,官方 route 到 V4 Flash。
|
|
785
|
+
// 这几条只服务历史 session 的成本回算,保留当时的费率口径,不随现价改动。
|
|
786
|
+
"deepseek-chat": { input: 0.14, output: 0.28, cacheRead: 0.028 },
|
|
787
|
+
"deepseek-reasoner": { input: 0.14, output: 0.28, cacheRead: 0.028 },
|
|
788
|
+
"deepseek-v3": { input: 0.27, output: 1.1, cacheRead: 0.07 },
|
|
789
|
+
// ── Moonshot Kimi ─────────────────────────────────────────────
|
|
790
|
+
"moonshot-v1-8k": { input: 0.17, output: 0.17 },
|
|
791
|
+
"moonshot-v1-32k": { input: 0.33, output: 0.33 },
|
|
792
|
+
"moonshot-v1-128k": { input: 0.83, output: 0.83 },
|
|
793
|
+
"kimi-k2": { input: 0.6, output: 2.5 },
|
|
794
|
+
"kimi-latest": { input: 0.6, output: 2.5 },
|
|
795
|
+
// Kimi K3(2026-07 旗舰,1M 上下文)官方定价 platform.kimi.ai/pricing/chat-k3:
|
|
796
|
+
// 输入 cache-miss $3.00 / cache-hit $0.30,输出 $15.00(/MTok)。
|
|
797
|
+
"kimi-k3": { input: 3, output: 15, cacheRead: 0.3 },
|
|
798
|
+
// ── Zhipu GLM ─────────────────────────────────────────────────
|
|
799
|
+
"glm-4-plus": { input: 0.7, output: 0.7 },
|
|
800
|
+
"glm-4": { input: 0.14, output: 0.14 },
|
|
801
|
+
"glm-4-flash": { input: 0, output: 0 },
|
|
802
|
+
"glm-4.5": { input: 0.29, output: 1.14 },
|
|
803
|
+
"glm-4.6": { input: 0.6, output: 2.2 },
|
|
804
|
+
"glm-4.6v": { input: 0.6, output: 2.2 },
|
|
805
|
+
"glm-5": { input: 0.85, output: 2.85 },
|
|
806
|
+
// GLM-5.2(2026-06)官方直连 API 定价 $1.40 / $4.40。
|
|
807
|
+
"glm-5.2": { input: 1.4, output: 4.4 },
|
|
808
|
+
// GLM-5V-Turbo(2026-04 视觉编程旗舰)官方直连 API 定价 $1.20 / $4.00。
|
|
809
|
+
"glm-5v-turbo": { input: 1.2, output: 4 },
|
|
810
|
+
"glm-5.1": { input: 0.95, output: 3.15 },
|
|
811
|
+
"glm-5.1-reasoning": { input: 1.4, output: 4.4 },
|
|
812
|
+
"glm-5.1-air": { input: 0.4, output: 1.2 },
|
|
813
|
+
"glm-z1": { input: 0.5, output: 1.5 },
|
|
814
|
+
"glm-z1-air": { input: 0.2, output: 0.6 },
|
|
815
|
+
"glm-z1-flash": { input: 0, output: 0 }
|
|
816
|
+
// ── MiniMax (海螺) ────────────────────────────────────────────
|
|
817
|
+
// GAP (v0.4.184): MiniMax-M3 / M2.x are unpriced — no verified rate anchor yet.
|
|
818
|
+
// Unpriced → `aicli usage` shows "—" (unknown), not a false $0.00. Add when confirmed.
|
|
819
|
+
// ── OpenRouter (pass-through — actual cost depends on underlying model) ──
|
|
820
|
+
// Left empty by design: model IDs are "vendor/model" (e.g. anthropic/claude-sonnet-4)
|
|
821
|
+
// and the real price is set by the underlying model + OpenRouter markup, not statically
|
|
822
|
+
// resolvable here. Shows "—" (unknown) in `aicli usage`.
|
|
823
|
+
// ── Ollama (local, zero cost) ─────────────────────────────────
|
|
824
|
+
// Handled via provider check below (FREE_PROVIDERS → genuine $0.00, not "unknown").
|
|
825
|
+
};
|
|
826
|
+
var FREE_PROVIDERS = /* @__PURE__ */ new Set(["ollama"]);
|
|
827
|
+
function getPricing(provider, model, at = /* @__PURE__ */ new Date()) {
|
|
828
|
+
if (FREE_PROVIDERS.has(provider.toLowerCase())) {
|
|
829
|
+
return { input: 0, output: 0 };
|
|
830
|
+
}
|
|
831
|
+
const key = model.toLowerCase();
|
|
832
|
+
if (provider.toLowerCase() === "deepseek") {
|
|
833
|
+
const dynamicPricing = getDeepSeekV4Pricing(key, at);
|
|
834
|
+
if (dynamicPricing) return dynamicPricing;
|
|
835
|
+
}
|
|
836
|
+
if (PRICING_TABLE[key]) return PRICING_TABLE[key];
|
|
837
|
+
const keys = Object.keys(PRICING_TABLE).sort((a, b) => b.length - a.length);
|
|
838
|
+
for (const k of keys) {
|
|
839
|
+
if (key.startsWith(k)) return PRICING_TABLE[k];
|
|
840
|
+
}
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
function computeCost(provider, model, usage, at = /* @__PURE__ */ new Date()) {
|
|
844
|
+
const p = getPricing(provider, model, at);
|
|
845
|
+
if (!p) return null;
|
|
846
|
+
const input = usage.inputTokens * p.input;
|
|
847
|
+
const output = usage.outputTokens * p.output;
|
|
848
|
+
const cacheWrite = (usage.cacheCreationTokens ?? 0) * (p.cacheWrite ?? p.input);
|
|
849
|
+
const cacheRead = (usage.cacheReadTokens ?? 0) * (p.cacheRead ?? p.input);
|
|
850
|
+
return (input + output + cacheWrite + cacheRead) / 1e6;
|
|
851
|
+
}
|
|
852
|
+
function formatCost(amount) {
|
|
853
|
+
if (amount === 0) return "$0.0000";
|
|
854
|
+
if (amount < 0.01) return `$${amount.toFixed(4)}`;
|
|
855
|
+
if (amount < 1) return `$${amount.toFixed(3)}`;
|
|
856
|
+
return `$${amount.toFixed(2)}`;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// src/core/cost-tracker.ts
|
|
860
|
+
var CostTracker = class _CostTracker {
|
|
861
|
+
filePath;
|
|
862
|
+
records = [];
|
|
863
|
+
dirty = false;
|
|
864
|
+
/** P2-PERF-04: debounce timer to batch rapid addCost() calls into a single save. */
|
|
865
|
+
_saveTimer;
|
|
866
|
+
constructor(configDir) {
|
|
867
|
+
this.filePath = join2(configDir, "cost-history.json");
|
|
868
|
+
this.load();
|
|
869
|
+
}
|
|
870
|
+
/** P2-PERF-04: debounce interval (ms) — coalesces multiple rapid addCost calls. */
|
|
871
|
+
static SAVE_DEBOUNCE_MS = 2e3;
|
|
872
|
+
load() {
|
|
873
|
+
try {
|
|
874
|
+
if (existsSync2(this.filePath)) {
|
|
875
|
+
const data = JSON.parse(readFileSync2(this.filePath, "utf-8"));
|
|
876
|
+
if (data.version === 1 && Array.isArray(data.records)) {
|
|
877
|
+
this.records = [...data.records].sort((a, b) => a.date.localeCompare(b.date));
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
} catch {
|
|
881
|
+
this.records = [];
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Save to disk (atomic write).
|
|
886
|
+
*
|
|
887
|
+
* H2: Snapshot records before writing and only clear the dirty flag after
|
|
888
|
+
* the write succeeds. If writeFileSync/renameSync throws, dirty remains
|
|
889
|
+
* true so the next save() retries — prevents silent data loss on transient
|
|
890
|
+
* disk errors. writeFileSync is sync in Node.js so there's no interleaving
|
|
891
|
+
* risk within a single save() call; the snapshot mainly protects against
|
|
892
|
+
* future refactors to async I/O.
|
|
893
|
+
*/
|
|
894
|
+
save() {
|
|
895
|
+
if (!this.dirty) return;
|
|
896
|
+
if (this._saveTimer) {
|
|
897
|
+
clearTimeout(this._saveTimer);
|
|
898
|
+
this._saveTimer = void 0;
|
|
899
|
+
}
|
|
900
|
+
const snapshot = {
|
|
901
|
+
version: 1,
|
|
902
|
+
records: [...this.records]
|
|
903
|
+
// shallow copy — records are plain data
|
|
904
|
+
};
|
|
905
|
+
try {
|
|
906
|
+
atomicWriteFileSync(this.filePath, JSON.stringify(snapshot, null, 2));
|
|
907
|
+
this.dirty = false;
|
|
908
|
+
} catch (err) {
|
|
909
|
+
console.error("[cost-tracker] Failed to persist cost history:", err);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Record cost from a completed session/interaction.
|
|
914
|
+
*
|
|
915
|
+
* 返回本次记账的美元金额(定价未知时返回 null)。调用方据此累计**按调用时刻**
|
|
916
|
+
* 算出的会话成本——2026-08-14 审计 P3-01:DeepSeek V4 自 8/17 起峰谷计价,
|
|
917
|
+
* 展示层若拿聚合用量在「查看时刻」现算,闲时跑的会话在峰时看会被按峰价显示,
|
|
918
|
+
* 跨时段会话更是整段按一个费率算。记账时刻的值才是真实花费。
|
|
919
|
+
*/
|
|
920
|
+
addCost(provider, model, usage) {
|
|
921
|
+
const priced = computeCost(provider, model, usage);
|
|
922
|
+
const cost = priced ?? 0;
|
|
923
|
+
const inTok = usage.inputTokens || 0;
|
|
924
|
+
const outTok = usage.outputTokens || 0;
|
|
925
|
+
if (cost === 0 && inTok === 0 && outTok === 0) return priced;
|
|
926
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
927
|
+
let record = this.records.find((r) => r.date === today);
|
|
928
|
+
if (!record) {
|
|
929
|
+
record = { date: today, cost: 0, sessions: 0, inputTokens: 0, outputTokens: 0 };
|
|
930
|
+
this.records.push(record);
|
|
931
|
+
}
|
|
932
|
+
record.cost += cost;
|
|
933
|
+
record.sessions += 1;
|
|
934
|
+
record.inputTokens += inTok;
|
|
935
|
+
record.outputTokens += outTok;
|
|
936
|
+
const key = `${provider}/${model}`;
|
|
937
|
+
record.byModel ??= {};
|
|
938
|
+
const m = record.byModel[key] ??= { cost: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, calls: 0 };
|
|
939
|
+
m.cost += cost;
|
|
940
|
+
m.inputTokens += inTok;
|
|
941
|
+
m.outputTokens += outTok;
|
|
942
|
+
m.cacheReadTokens += usage.cacheReadTokens ?? 0;
|
|
943
|
+
m.calls += 1;
|
|
944
|
+
this.dirty = true;
|
|
945
|
+
if (!this._saveTimer) {
|
|
946
|
+
this._saveTimer = setTimeout(() => {
|
|
947
|
+
this._saveTimer = void 0;
|
|
948
|
+
this.save();
|
|
949
|
+
}, _CostTracker.SAVE_DEBOUNCE_MS);
|
|
950
|
+
if (this._saveTimer && typeof this._saveTimer === "object" && "unref" in this._saveTimer) {
|
|
951
|
+
this._saveTimer.unref();
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
if (this.records.length > 90) {
|
|
955
|
+
this.records = this.records.slice(-90);
|
|
956
|
+
}
|
|
957
|
+
return priced;
|
|
958
|
+
}
|
|
959
|
+
/** Get total cost for a given month ("2026-04"). */
|
|
960
|
+
getMonthlyCost(yearMonth) {
|
|
961
|
+
const prefix = yearMonth ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
|
|
962
|
+
return this.records.filter((r) => r.date.startsWith(prefix)).reduce((sum, r) => sum + r.cost, 0);
|
|
963
|
+
}
|
|
964
|
+
/** Get today's cost. */
|
|
965
|
+
getTodayCost() {
|
|
966
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
967
|
+
return this.records.find((r) => r.date === today)?.cost ?? 0;
|
|
968
|
+
}
|
|
969
|
+
/** Get total cost for last N days. */
|
|
970
|
+
getRecentCost(days) {
|
|
971
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
972
|
+
cutoff.setDate(cutoff.getDate() - days);
|
|
973
|
+
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
|
974
|
+
return this.records.filter((r) => r.date >= cutoffStr).reduce((sum, r) => sum + r.cost, 0);
|
|
975
|
+
}
|
|
976
|
+
/** Get all records for display. */
|
|
977
|
+
getRecords() {
|
|
978
|
+
return [...this.records];
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Aggregate usage across a date window, grouped by provider/model (v0.4.184+).
|
|
982
|
+
*
|
|
983
|
+
* Window selection (precedence): `month` ("2026-06") > `sinceDays` (N days back,
|
|
984
|
+
* inclusive of today) > all tracked history. Legacy records without a byModel
|
|
985
|
+
* breakdown fold into a single "(unattributed)" row so totals still reconcile
|
|
986
|
+
* with `getMonthlyCost()` etc.
|
|
987
|
+
*
|
|
988
|
+
* @returns rows sorted by cost desc then total tokens desc, plus grand totals
|
|
989
|
+
* and the actual covered date range.
|
|
990
|
+
*/
|
|
991
|
+
getUsageBreakdown(opts) {
|
|
992
|
+
let filtered;
|
|
993
|
+
if (opts?.month) {
|
|
994
|
+
filtered = this.records.filter((r) => r.date.startsWith(opts.month));
|
|
995
|
+
} else if (opts?.sinceDays && opts.sinceDays > 0) {
|
|
996
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
997
|
+
cutoff.setDate(cutoff.getDate() - (opts.sinceDays - 1));
|
|
998
|
+
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
|
999
|
+
filtered = this.records.filter((r) => r.date >= cutoffStr);
|
|
1000
|
+
} else {
|
|
1001
|
+
filtered = [...this.records];
|
|
1002
|
+
}
|
|
1003
|
+
const acc = /* @__PURE__ */ new Map();
|
|
1004
|
+
const bump = (key, provider, model, v) => {
|
|
1005
|
+
const row = acc.get(key) ?? { key, provider, model, cost: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, calls: 0 };
|
|
1006
|
+
row.cost += v.cost;
|
|
1007
|
+
row.inputTokens += v.inputTokens;
|
|
1008
|
+
row.outputTokens += v.outputTokens;
|
|
1009
|
+
row.cacheReadTokens += v.cacheReadTokens;
|
|
1010
|
+
row.calls += v.calls;
|
|
1011
|
+
acc.set(key, row);
|
|
1012
|
+
};
|
|
1013
|
+
for (const rec of filtered) {
|
|
1014
|
+
if (rec.byModel && Object.keys(rec.byModel).length > 0) {
|
|
1015
|
+
for (const [key, m] of Object.entries(rec.byModel)) {
|
|
1016
|
+
const slash = key.indexOf("/");
|
|
1017
|
+
const provider = slash >= 0 ? key.slice(0, slash) : key;
|
|
1018
|
+
const model = slash >= 0 ? key.slice(slash + 1) : "";
|
|
1019
|
+
bump(key, provider, model, m);
|
|
1020
|
+
}
|
|
1021
|
+
} else {
|
|
1022
|
+
bump("(unattributed)", "(unattributed)", "", {
|
|
1023
|
+
cost: rec.cost,
|
|
1024
|
+
inputTokens: rec.inputTokens,
|
|
1025
|
+
outputTokens: rec.outputTokens,
|
|
1026
|
+
cacheReadTokens: 0,
|
|
1027
|
+
calls: rec.sessions
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
const rows = [...acc.values()].sort(
|
|
1032
|
+
(a, b) => b.cost - a.cost || b.inputTokens + b.outputTokens - (a.inputTokens + a.outputTokens)
|
|
1033
|
+
);
|
|
1034
|
+
const totals = rows.reduce(
|
|
1035
|
+
(t, r) => {
|
|
1036
|
+
t.cost += r.cost;
|
|
1037
|
+
t.inputTokens += r.inputTokens;
|
|
1038
|
+
t.outputTokens += r.outputTokens;
|
|
1039
|
+
t.cacheReadTokens += r.cacheReadTokens;
|
|
1040
|
+
t.calls += r.calls;
|
|
1041
|
+
return t;
|
|
1042
|
+
},
|
|
1043
|
+
{ cost: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, calls: 0 }
|
|
1044
|
+
);
|
|
1045
|
+
return {
|
|
1046
|
+
rows,
|
|
1047
|
+
totals,
|
|
1048
|
+
fromDate: filtered.length > 0 ? filtered[0].date : null,
|
|
1049
|
+
toDate: filtered.length > 0 ? filtered[filtered.length - 1].date : null,
|
|
1050
|
+
dayCount: filtered.length
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Check if monthly cost exceeds budget, return warning message or null.
|
|
1055
|
+
*/
|
|
1056
|
+
checkBudget(monthlyBudget) {
|
|
1057
|
+
if (!monthlyBudget || monthlyBudget <= 0) return null;
|
|
1058
|
+
const monthlyCost = this.getMonthlyCost();
|
|
1059
|
+
const ratio = monthlyCost / monthlyBudget;
|
|
1060
|
+
if (ratio >= 1) {
|
|
1061
|
+
return `\u{1F6A8} Monthly budget exceeded: ${formatCost(monthlyCost)} / ${formatCost(monthlyBudget)} (${Math.round(ratio * 100)}%)`;
|
|
1062
|
+
}
|
|
1063
|
+
if (ratio >= 0.8) {
|
|
1064
|
+
return `\u26A0 Monthly budget warning: ${formatCost(monthlyCost)} / ${formatCost(monthlyBudget)} (${Math.round(ratio * 100)}%)`;
|
|
1065
|
+
}
|
|
1066
|
+
return null;
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Format a cost summary for display.
|
|
1070
|
+
*/
|
|
1071
|
+
formatSummary(monthlyBudget) {
|
|
1072
|
+
const today = this.getTodayCost();
|
|
1073
|
+
const monthly = this.getMonthlyCost();
|
|
1074
|
+
const yearMonth = (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
|
|
1075
|
+
const lines = [
|
|
1076
|
+
`Today: ${formatCost(today)}`,
|
|
1077
|
+
`This month: ${formatCost(monthly)} (${yearMonth})`
|
|
1078
|
+
];
|
|
1079
|
+
if (monthlyBudget && monthlyBudget > 0) {
|
|
1080
|
+
const ratio = monthly / monthlyBudget;
|
|
1081
|
+
const bar = "\u2588".repeat(Math.min(20, Math.round(ratio * 20))) + "\u2591".repeat(Math.max(0, 20 - Math.round(ratio * 20)));
|
|
1082
|
+
lines.push(`Budget: ${formatCost(monthlyBudget)} [${bar}] ${Math.round(ratio * 100)}%`);
|
|
1083
|
+
}
|
|
1084
|
+
const last7 = [];
|
|
1085
|
+
for (let i = 6; i >= 0; i--) {
|
|
1086
|
+
const d = /* @__PURE__ */ new Date();
|
|
1087
|
+
d.setDate(d.getDate() - i);
|
|
1088
|
+
const dateStr = d.toISOString().slice(0, 10);
|
|
1089
|
+
const record = this.records.find((r) => r.date === dateStr);
|
|
1090
|
+
const dayLabel = dateStr.slice(5);
|
|
1091
|
+
last7.push(`${dayLabel}: ${record ? formatCost(record.cost) : "$0.00"}`);
|
|
1092
|
+
}
|
|
1093
|
+
lines.push(`Last 7 days: ${last7.join(" ")}`);
|
|
1094
|
+
return lines.join("\n");
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* P2-PERF-04: flush pending cost records to disk immediately.
|
|
1098
|
+
* Call this before process exit or before reading cost data for display
|
|
1099
|
+
* to ensure the in-memory buffer is reflected on disk.
|
|
1100
|
+
*/
|
|
1101
|
+
flush() {
|
|
1102
|
+
if (this._saveTimer) {
|
|
1103
|
+
clearTimeout(this._saveTimer);
|
|
1104
|
+
this._saveTimer = void 0;
|
|
1105
|
+
}
|
|
1106
|
+
this.save();
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
|
|
712
1110
|
// src/providers/claude.ts
|
|
713
1111
|
import Anthropic from "@anthropic-ai/sdk";
|
|
714
1112
|
|
|
@@ -3905,18 +4303,18 @@ var CustomProvider = class extends OpenAICompatibleProvider {
|
|
|
3905
4303
|
};
|
|
3906
4304
|
|
|
3907
4305
|
// src/providers/model-cache.ts
|
|
3908
|
-
import { existsSync as
|
|
3909
|
-
import { dirname, join as
|
|
4306
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync } from "fs";
|
|
4307
|
+
import { dirname, join as join3 } from "path";
|
|
3910
4308
|
var CACHE_FILE_NAME = "model-cache.json";
|
|
3911
4309
|
var LIVE_DEFAULT_CONTEXT = 128e3;
|
|
3912
4310
|
function modelCachePath(configDir) {
|
|
3913
|
-
return
|
|
4311
|
+
return join3(configDir, CACHE_FILE_NAME);
|
|
3914
4312
|
}
|
|
3915
4313
|
function loadModelCache(configDir) {
|
|
3916
4314
|
const path3 = modelCachePath(configDir);
|
|
3917
|
-
if (!
|
|
4315
|
+
if (!existsSync3(path3)) return { version: 1, providers: {} };
|
|
3918
4316
|
try {
|
|
3919
|
-
const parsed = JSON.parse(
|
|
4317
|
+
const parsed = JSON.parse(readFileSync3(path3, "utf-8"));
|
|
3920
4318
|
if (parsed.version !== 1 || !parsed.providers || typeof parsed.providers !== "object") {
|
|
3921
4319
|
return { version: 1, providers: {} };
|
|
3922
4320
|
}
|
|
@@ -3961,7 +4359,7 @@ function setCachedModels(configDir, providerId, models, baseUrl) {
|
|
|
3961
4359
|
function clearModelCache(configDir, providerId) {
|
|
3962
4360
|
const path3 = modelCachePath(configDir);
|
|
3963
4361
|
if (!providerId) {
|
|
3964
|
-
if (
|
|
4362
|
+
if (existsSync3(path3)) unlinkSync(path3);
|
|
3965
4363
|
return;
|
|
3966
4364
|
}
|
|
3967
4365
|
const cache = loadModelCache(configDir);
|
|
@@ -4152,8 +4550,8 @@ var ProviderRegistry = class {
|
|
|
4152
4550
|
};
|
|
4153
4551
|
|
|
4154
4552
|
// src/session/session-manager.ts
|
|
4155
|
-
import { readFileSync as
|
|
4156
|
-
import { join as
|
|
4553
|
+
import { readFileSync as readFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync, unlinkSync as unlinkSync2, statSync } from "fs";
|
|
4554
|
+
import { join as join4 } from "path";
|
|
4157
4555
|
import { v4 as uuidv4 } from "uuid";
|
|
4158
4556
|
|
|
4159
4557
|
// src/core/types.ts
|
|
@@ -4738,7 +5136,7 @@ var SessionManager = class {
|
|
|
4738
5136
|
if (!this._current) return;
|
|
4739
5137
|
if (!force && !this._current.dirty) return;
|
|
4740
5138
|
mkdirSync3(this.historyDir, { recursive: true });
|
|
4741
|
-
const filePath =
|
|
5139
|
+
const filePath = join4(this.historyDir, `${this._current.id}.json`);
|
|
4742
5140
|
const raw = this._current.toJSON();
|
|
4743
5141
|
const opts = this.redactOptionsForSave();
|
|
4744
5142
|
const { value: payload, hits } = redactJson(raw, opts);
|
|
@@ -4759,13 +5157,13 @@ var SessionManager = class {
|
|
|
4759
5157
|
}
|
|
4760
5158
|
}
|
|
4761
5159
|
loadSession(id) {
|
|
4762
|
-
const filePath =
|
|
4763
|
-
if (!
|
|
5160
|
+
const filePath = join4(this.historyDir, `${id}.json`);
|
|
5161
|
+
if (!existsSync4(filePath)) {
|
|
4764
5162
|
throw new Error(`Session ${id} not found`);
|
|
4765
5163
|
}
|
|
4766
5164
|
let data;
|
|
4767
5165
|
try {
|
|
4768
|
-
data = JSON.parse(
|
|
5166
|
+
data = JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
4769
5167
|
} catch (err) {
|
|
4770
5168
|
throw new Error(`Session ${id} is corrupted: ${err instanceof Error ? err.message : String(err)}`);
|
|
4771
5169
|
}
|
|
@@ -4774,7 +5172,7 @@ var SessionManager = class {
|
|
|
4774
5172
|
return session;
|
|
4775
5173
|
}
|
|
4776
5174
|
listSessions(opts) {
|
|
4777
|
-
if (!
|
|
5175
|
+
if (!existsSync4(this.historyDir)) return [];
|
|
4778
5176
|
try {
|
|
4779
5177
|
return listFromIndex(this.historyDir, opts);
|
|
4780
5178
|
} catch {
|
|
@@ -4783,7 +5181,7 @@ var SessionManager = class {
|
|
|
4783
5181
|
const metas = [];
|
|
4784
5182
|
for (const file of files) {
|
|
4785
5183
|
try {
|
|
4786
|
-
const filePath =
|
|
5184
|
+
const filePath = join4(this.historyDir, file);
|
|
4787
5185
|
const meta = readSessionMeta(filePath);
|
|
4788
5186
|
if (!meta) continue;
|
|
4789
5187
|
if (opts?.includeFileSize) {
|
|
@@ -4803,8 +5201,8 @@ var SessionManager = class {
|
|
|
4803
5201
|
return metas.sort((a, b) => b.updated.getTime() - a.updated.getTime());
|
|
4804
5202
|
}
|
|
4805
5203
|
deleteSession(id) {
|
|
4806
|
-
const filePath =
|
|
4807
|
-
if (!
|
|
5204
|
+
const filePath = join4(this.historyDir, `${id}.json`);
|
|
5205
|
+
if (!existsSync4(filePath)) return false;
|
|
4808
5206
|
try {
|
|
4809
5207
|
unlinkSync2(filePath);
|
|
4810
5208
|
if (this._current && this._current.id === id) {
|
|
@@ -4845,20 +5243,20 @@ var SessionManager = class {
|
|
|
4845
5243
|
* 每个 session 最多返回 3 条匹配片段,全局最多 maxResults 个 session。
|
|
4846
5244
|
*/
|
|
4847
5245
|
searchMessages(query, maxResults = 20) {
|
|
4848
|
-
if (!
|
|
5246
|
+
if (!existsSync4(this.historyDir)) return [];
|
|
4849
5247
|
const q = query.toLowerCase();
|
|
4850
5248
|
const candidateIds = searchCandidates(this.historyDir, q);
|
|
4851
5249
|
let filesToSearch;
|
|
4852
5250
|
if (candidateIds !== null) {
|
|
4853
|
-
filesToSearch = candidateIds.map((id) =>
|
|
5251
|
+
filesToSearch = candidateIds.map((id) => join4(this.historyDir, `${id}.json`)).filter((fp) => existsSync4(fp));
|
|
4854
5252
|
} else {
|
|
4855
|
-
filesToSearch = readdirSync(this.historyDir).filter((f) => f.endsWith(".json")).map((f) =>
|
|
5253
|
+
filesToSearch = readdirSync(this.historyDir).filter((f) => f.endsWith(".json")).map((f) => join4(this.historyDir, f));
|
|
4856
5254
|
}
|
|
4857
5255
|
const results = [];
|
|
4858
5256
|
for (const filePath of filesToSearch) {
|
|
4859
5257
|
if (results.length >= maxResults) break;
|
|
4860
5258
|
try {
|
|
4861
|
-
const data = JSON.parse(
|
|
5259
|
+
const data = JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
4862
5260
|
const messages = data.messages ?? [];
|
|
4863
5261
|
const matches = [];
|
|
4864
5262
|
for (const msg of messages) {
|
|
@@ -4905,12 +5303,12 @@ var SessionManager = class {
|
|
|
4905
5303
|
|
|
4906
5304
|
// src/tools/builtin/bash.ts
|
|
4907
5305
|
import { spawn as spawn3 } from "child_process";
|
|
4908
|
-
import { existsSync as
|
|
5306
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
4909
5307
|
import { platform as platform3 } from "os";
|
|
4910
5308
|
import { resolve } from "path";
|
|
4911
5309
|
|
|
4912
5310
|
// src/tools/undo-stack.ts
|
|
4913
|
-
import { readFileSync as
|
|
5311
|
+
import { readFileSync as readFileSync5, writeFileSync, unlinkSync as unlinkSync3, rmdirSync, existsSync as existsSync5 } from "fs";
|
|
4914
5312
|
var MAX_UNDO_DEPTH = 20;
|
|
4915
5313
|
var UndoStack = class {
|
|
4916
5314
|
stack = [];
|
|
@@ -4921,9 +5319,9 @@ var UndoStack = class {
|
|
|
4921
5319
|
*/
|
|
4922
5320
|
push(filePath, description) {
|
|
4923
5321
|
let previousContent = null;
|
|
4924
|
-
if (
|
|
5322
|
+
if (existsSync5(filePath)) {
|
|
4925
5323
|
try {
|
|
4926
|
-
previousContent =
|
|
5324
|
+
previousContent = readFileSync5(filePath, "utf-8");
|
|
4927
5325
|
} catch {
|
|
4928
5326
|
return;
|
|
4929
5327
|
}
|
|
@@ -4977,7 +5375,7 @@ var UndoStack = class {
|
|
|
4977
5375
|
try {
|
|
4978
5376
|
if (entry.previousContent === null) {
|
|
4979
5377
|
if (entry.isDirectory) {
|
|
4980
|
-
if (
|
|
5378
|
+
if (existsSync5(entry.filePath)) {
|
|
4981
5379
|
try {
|
|
4982
5380
|
rmdirSync(entry.filePath);
|
|
4983
5381
|
return { entry, result: `Removed newly created directory: ${entry.filePath}` };
|
|
@@ -4987,7 +5385,7 @@ var UndoStack = class {
|
|
|
4987
5385
|
}
|
|
4988
5386
|
return { entry, result: `Directory already removed: ${entry.filePath}` };
|
|
4989
5387
|
} else {
|
|
4990
|
-
if (
|
|
5388
|
+
if (existsSync5(entry.filePath)) {
|
|
4991
5389
|
unlinkSync3(entry.filePath);
|
|
4992
5390
|
}
|
|
4993
5391
|
return { entry, result: `Deleted newly created file: ${entry.filePath}` };
|
|
@@ -5378,7 +5776,7 @@ Important rules:
|
|
|
5378
5776
|
throw new ToolError("bash", blockingHint);
|
|
5379
5777
|
}
|
|
5380
5778
|
let currentCwd = getCwd();
|
|
5381
|
-
if (!
|
|
5779
|
+
if (!existsSync6(currentCwd)) {
|
|
5382
5780
|
const fallback = process.cwd();
|
|
5383
5781
|
process.stderr.write(
|
|
5384
5782
|
`[bash] Previous cwd "${currentCwd}" no longer exists, reset to "${fallback}"
|
|
@@ -5390,7 +5788,7 @@ Important rules:
|
|
|
5390
5788
|
let effectiveCwd = currentCwd;
|
|
5391
5789
|
if (cwdArg) {
|
|
5392
5790
|
const resolved = resolve(currentCwd, cwdArg);
|
|
5393
|
-
if (!
|
|
5791
|
+
if (!existsSync6(resolved)) {
|
|
5394
5792
|
throw new ToolError(
|
|
5395
5793
|
"bash",
|
|
5396
5794
|
`cwd directory does not exist: "${resolved}". Create it first (e.g. mkdir -p "${resolved}") before specifying it as cwd.`
|
|
@@ -5410,7 +5808,7 @@ Important rules:
|
|
|
5410
5808
|
const parsedTargets = parseCreationTargets(command, effectiveCwd);
|
|
5411
5809
|
const parsedTargetsBefore = /* @__PURE__ */ new Map();
|
|
5412
5810
|
for (const t of parsedTargets) {
|
|
5413
|
-
parsedTargetsBefore.set(t,
|
|
5811
|
+
parsedTargetsBefore.set(t, existsSync6(t));
|
|
5414
5812
|
}
|
|
5415
5813
|
let poolResult = null;
|
|
5416
5814
|
try {
|
|
@@ -5707,7 +6105,7 @@ function pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd) {
|
|
|
5707
6105
|
}
|
|
5708
6106
|
}
|
|
5709
6107
|
for (const [target, existedBefore] of parsedTargetsBefore) {
|
|
5710
|
-
if (!existedBefore && !tracked.has(target) &&
|
|
6108
|
+
if (!existedBefore && !tracked.has(target) && existsSync6(target)) {
|
|
5711
6109
|
try {
|
|
5712
6110
|
const st = statSync2(target);
|
|
5713
6111
|
if (st.isDirectory()) {
|
|
@@ -5794,7 +6192,7 @@ function updateCwdFromCommand(command, baseCwd) {
|
|
|
5794
6192
|
if (!target || target.startsWith("$") || target === "~") return;
|
|
5795
6193
|
try {
|
|
5796
6194
|
const newDir = resolve(baseCwd, target);
|
|
5797
|
-
if (
|
|
6195
|
+
if (existsSync6(newDir)) {
|
|
5798
6196
|
setCwd(newDir);
|
|
5799
6197
|
}
|
|
5800
6198
|
} catch {
|
|
@@ -5802,7 +6200,7 @@ function updateCwdFromCommand(command, baseCwd) {
|
|
|
5802
6200
|
}
|
|
5803
6201
|
|
|
5804
6202
|
// src/tools/builtin/read-file.ts
|
|
5805
|
-
import { readFileSync as
|
|
6203
|
+
import { readFileSync as readFileSync6, existsSync as existsSync7, statSync as statSync3, readdirSync as readdirSync3 } from "fs";
|
|
5806
6204
|
import { execFileSync } from "child_process";
|
|
5807
6205
|
import { extname, resolve as resolve2, basename, sep, dirname as dirname2 } from "path";
|
|
5808
6206
|
import { homedir as homedir2 } from "os";
|
|
@@ -6079,7 +6477,7 @@ var readFileTool = {
|
|
|
6079
6477
|
const normalizedPath = resolve2(filePath);
|
|
6080
6478
|
const refusal = getHardRefusal(normalizedPath);
|
|
6081
6479
|
if (refusal) return refusal;
|
|
6082
|
-
if (!
|
|
6480
|
+
if (!existsSync7(normalizedPath)) {
|
|
6083
6481
|
const suggestions = findSimilarFiles(filePath);
|
|
6084
6482
|
if (suggestions.length > 0) {
|
|
6085
6483
|
throw new ToolError(
|
|
@@ -6130,7 +6528,7 @@ ${pdfText}`;
|
|
|
6130
6528
|
}
|
|
6131
6529
|
const dir = dirname2(normalizedPath);
|
|
6132
6530
|
const nameNoExt = basename(normalizedPath, ext);
|
|
6133
|
-
const textAlts = [".md", ".txt", ".html"].map((e) => resolve2(dir, nameNoExt + e)).filter(
|
|
6531
|
+
const textAlts = [".md", ".txt", ".html"].map((e) => resolve2(dir, nameNoExt + e)).filter(existsSync7);
|
|
6134
6532
|
if (textAlts.length > 0) {
|
|
6135
6533
|
return `[PDF file: ${filePath}]
|
|
6136
6534
|
Cannot extract text from this PDF, but found alternative text versions:
|
|
@@ -6143,7 +6541,7 @@ Suggestion: read existing text versions (.md / .txt) in the project, or install
|
|
|
6143
6541
|
}
|
|
6144
6542
|
if (ext === ".ipynb") {
|
|
6145
6543
|
try {
|
|
6146
|
-
const raw =
|
|
6544
|
+
const raw = readFileSync6(normalizedPath, "utf-8");
|
|
6147
6545
|
const nb = parseNotebook(raw);
|
|
6148
6546
|
return renderNotebookAsText(nb, filePath);
|
|
6149
6547
|
} catch (err) {
|
|
@@ -6156,7 +6554,7 @@ ${err.message}`;
|
|
|
6156
6554
|
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
6555
|
}
|
|
6158
6556
|
const { readFile: readFile2 } = await import("fs/promises");
|
|
6159
|
-
const buf = size > 1048576 ? await readFile2(normalizedPath) :
|
|
6557
|
+
const buf = size > 1048576 ? await readFile2(normalizedPath) : readFileSync6(normalizedPath);
|
|
6160
6558
|
if (encoding === "base64") {
|
|
6161
6559
|
return `[File: ${filePath} | base64]
|
|
6162
6560
|
|
|
@@ -6194,12 +6592,12 @@ ${content}`;
|
|
|
6194
6592
|
};
|
|
6195
6593
|
|
|
6196
6594
|
// src/tools/builtin/write-file.ts
|
|
6197
|
-
import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as
|
|
6595
|
+
import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync11, statSync as statSync4 } from "fs";
|
|
6198
6596
|
import { dirname as dirname5 } from "path";
|
|
6199
6597
|
|
|
6200
6598
|
// src/tools/executor.ts
|
|
6201
6599
|
import chalk3 from "chalk";
|
|
6202
|
-
import { existsSync as
|
|
6600
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
6203
6601
|
import { tmpdir } from "os";
|
|
6204
6602
|
|
|
6205
6603
|
// src/core/readline-internal.ts
|
|
@@ -6439,8 +6837,8 @@ function simpleDiff(oldLines, newLines) {
|
|
|
6439
6837
|
// src/tools/hooks.ts
|
|
6440
6838
|
import { execSync } from "child_process";
|
|
6441
6839
|
import { createHash } from "crypto";
|
|
6442
|
-
import { existsSync as
|
|
6443
|
-
import { join as
|
|
6840
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7 } from "fs";
|
|
6841
|
+
import { join as join5 } from "path";
|
|
6444
6842
|
var TRUST_FILE = "hooks-trust.json";
|
|
6445
6843
|
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
6446
6844
|
function asArray(entry) {
|
|
@@ -6449,13 +6847,13 @@ function asArray(entry) {
|
|
|
6449
6847
|
return raw.map((item) => typeof item === "string" ? { command: item } : item);
|
|
6450
6848
|
}
|
|
6451
6849
|
function hookTrustPath(configDir) {
|
|
6452
|
-
return
|
|
6850
|
+
return join5(configDir, TRUST_FILE);
|
|
6453
6851
|
}
|
|
6454
6852
|
function loadHookTrustStore(configDir) {
|
|
6455
6853
|
const file = hookTrustPath(configDir);
|
|
6456
|
-
if (!
|
|
6854
|
+
if (!existsSync8(file)) return { records: [] };
|
|
6457
6855
|
try {
|
|
6458
|
-
const parsed = JSON.parse(
|
|
6856
|
+
const parsed = JSON.parse(readFileSync7(file, "utf-8"));
|
|
6459
6857
|
return { records: Array.isArray(parsed.records) ? parsed.records : [] };
|
|
6460
6858
|
} catch {
|
|
6461
6859
|
return { records: [] };
|
|
@@ -6599,7 +6997,7 @@ function runHook(template, vars) {
|
|
|
6599
6997
|
}
|
|
6600
6998
|
|
|
6601
6999
|
// src/tools/permissions.ts
|
|
6602
|
-
import { existsSync as
|
|
7000
|
+
import { existsSync as existsSync9, realpathSync } from "fs";
|
|
6603
7001
|
import { basename as basename2, dirname as dirname3, isAbsolute, resolve as resolve3 } from "path";
|
|
6604
7002
|
function checkPermission(toolName, args, dangerLevel, rules, defaultAction = "confirm") {
|
|
6605
7003
|
for (const rule of rules) {
|
|
@@ -6648,7 +7046,7 @@ function normalizePathForPermission(value) {
|
|
|
6648
7046
|
function canonicalizeWithNearestParent(value) {
|
|
6649
7047
|
const missing = [];
|
|
6650
7048
|
let cursor = resolve3(value);
|
|
6651
|
-
while (!
|
|
7049
|
+
while (!existsSync9(cursor)) {
|
|
6652
7050
|
const parent = dirname3(cursor);
|
|
6653
7051
|
if (parent === cursor) return null;
|
|
6654
7052
|
missing.unshift(basename2(cursor));
|
|
@@ -7013,8 +7411,8 @@ var theme = new Proxy(DARK_THEME, {
|
|
|
7013
7411
|
});
|
|
7014
7412
|
|
|
7015
7413
|
// src/diagnostics/tool-stats.ts
|
|
7016
|
-
import { existsSync as
|
|
7017
|
-
import { join as
|
|
7414
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
7415
|
+
import { join as join6, dirname as dirname4 } from "path";
|
|
7018
7416
|
import { homedir as homedir3 } from "os";
|
|
7019
7417
|
var STATS_FILE_NAME = "tool-stats.json";
|
|
7020
7418
|
var STATS_VERSION = 1;
|
|
@@ -7024,16 +7422,16 @@ var dirty = false;
|
|
|
7024
7422
|
var pendingWrites = 0;
|
|
7025
7423
|
var configDirOverride;
|
|
7026
7424
|
function statsFilePath() {
|
|
7027
|
-
const base = configDirOverride ??
|
|
7028
|
-
return
|
|
7425
|
+
const base = configDirOverride ?? join6(homedir3(), CONFIG_DIR_NAME);
|
|
7426
|
+
return join6(base, STATS_FILE_NAME);
|
|
7029
7427
|
}
|
|
7030
7428
|
function load() {
|
|
7031
7429
|
const path3 = statsFilePath();
|
|
7032
|
-
if (!
|
|
7430
|
+
if (!existsSync10(path3)) {
|
|
7033
7431
|
return { version: STATS_VERSION, startedAt: (/* @__PURE__ */ new Date()).toISOString(), entries: {} };
|
|
7034
7432
|
}
|
|
7035
7433
|
try {
|
|
7036
|
-
const raw = JSON.parse(
|
|
7434
|
+
const raw = JSON.parse(readFileSync8(path3, "utf-8"));
|
|
7037
7435
|
if (raw.version !== STATS_VERSION || !raw.entries) {
|
|
7038
7436
|
return { version: STATS_VERSION, startedAt: (/* @__PURE__ */ new Date()).toISOString(), entries: {} };
|
|
7039
7437
|
}
|
|
@@ -7115,8 +7513,8 @@ function getTopUsedTools(limit = 5) {
|
|
|
7115
7513
|
}
|
|
7116
7514
|
|
|
7117
7515
|
// src/tools/action-classifier.ts
|
|
7118
|
-
import { existsSync as
|
|
7119
|
-
import { isAbsolute as isAbsolute2, join as
|
|
7516
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
7517
|
+
import { isAbsolute as isAbsolute2, join as join7, resolve as resolve4 } from "path";
|
|
7120
7518
|
var RECENT_DENIED_LIMIT = 50;
|
|
7121
7519
|
var recentlyDenied = [];
|
|
7122
7520
|
function recordRecentlyDeniedAutoAction(call, classification) {
|
|
@@ -7157,10 +7555,10 @@ function splitShellWords(command) {
|
|
|
7157
7555
|
return out.filter(Boolean);
|
|
7158
7556
|
}
|
|
7159
7557
|
function readPackageManifest(root) {
|
|
7160
|
-
const p =
|
|
7161
|
-
if (!
|
|
7558
|
+
const p = join7(root, "package.json");
|
|
7559
|
+
if (!existsSync11(p)) return null;
|
|
7162
7560
|
try {
|
|
7163
|
-
return JSON.parse(
|
|
7561
|
+
return JSON.parse(readFileSync9(p, "utf-8"));
|
|
7164
7562
|
} catch {
|
|
7165
7563
|
return null;
|
|
7166
7564
|
}
|
|
@@ -7176,10 +7574,10 @@ function manifestHasDependency(root, name) {
|
|
|
7176
7574
|
}
|
|
7177
7575
|
function lockfileMentions(root, name) {
|
|
7178
7576
|
for (const file of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]) {
|
|
7179
|
-
const p =
|
|
7180
|
-
if (!
|
|
7577
|
+
const p = join7(root, file);
|
|
7578
|
+
if (!existsSync11(p)) continue;
|
|
7181
7579
|
try {
|
|
7182
|
-
if (
|
|
7580
|
+
if (readFileSync9(p, "utf-8").includes(name)) return true;
|
|
7183
7581
|
} catch {
|
|
7184
7582
|
}
|
|
7185
7583
|
}
|
|
@@ -7753,10 +8151,10 @@ var ToolExecutor = class {
|
|
|
7753
8151
|
if (call.name === "write_file") {
|
|
7754
8152
|
const { path: filePath, content: chunk, append } = resolveWriteFileArgs(call.arguments);
|
|
7755
8153
|
if (!filePath) return;
|
|
7756
|
-
if (
|
|
8154
|
+
if (existsSync12(filePath)) {
|
|
7757
8155
|
let oldContent;
|
|
7758
8156
|
try {
|
|
7759
|
-
oldContent =
|
|
8157
|
+
oldContent = readFileSync10(filePath, "utf-8");
|
|
7760
8158
|
} catch {
|
|
7761
8159
|
return;
|
|
7762
8160
|
}
|
|
@@ -7780,7 +8178,7 @@ var ToolExecutor = class {
|
|
|
7780
8178
|
}
|
|
7781
8179
|
} else if (call.name === "edit_file") {
|
|
7782
8180
|
const filePath = String(call.arguments["path"] ?? "");
|
|
7783
|
-
if (!filePath || !
|
|
8181
|
+
if (!filePath || !existsSync12(filePath)) return;
|
|
7784
8182
|
const oldStr = call.arguments["old_str"];
|
|
7785
8183
|
const newStr = call.arguments["new_str"];
|
|
7786
8184
|
if (oldStr !== void 0) {
|
|
@@ -7807,7 +8205,7 @@ var ToolExecutor = class {
|
|
|
7807
8205
|
const to = Number(call.arguments["delete_to_line"] ?? from);
|
|
7808
8206
|
let fileContent;
|
|
7809
8207
|
try {
|
|
7810
|
-
fileContent =
|
|
8208
|
+
fileContent = readFileSync10(filePath, "utf-8");
|
|
7811
8209
|
} catch {
|
|
7812
8210
|
return;
|
|
7813
8211
|
}
|
|
@@ -7993,7 +8391,7 @@ var subAgentGuard = {
|
|
|
7993
8391
|
|
|
7994
8392
|
// src/tools/builtin/write-file.ts
|
|
7995
8393
|
function countFileLines(filePath) {
|
|
7996
|
-
return
|
|
8394
|
+
return readFileSync11(filePath, "utf-8").split("\n").length;
|
|
7997
8395
|
}
|
|
7998
8396
|
var writeFileTool = {
|
|
7999
8397
|
definition: {
|
|
@@ -8076,7 +8474,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
8076
8474
|
};
|
|
8077
8475
|
|
|
8078
8476
|
// src/tools/builtin/edit-file.ts
|
|
8079
|
-
import { readFileSync as
|
|
8477
|
+
import { readFileSync as readFileSync12, existsSync as existsSync13 } from "fs";
|
|
8080
8478
|
|
|
8081
8479
|
// src/tools/builtin/patch-apply.ts
|
|
8082
8480
|
function parseUnifiedDiff(patch) {
|
|
@@ -8444,7 +8842,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
8444
8842
|
const filePath = String(args["path"] ?? args["file"] ?? "");
|
|
8445
8843
|
const encoding = args["encoding"] ?? "utf-8";
|
|
8446
8844
|
if (!filePath) throw new ToolError("edit_file", "path is required");
|
|
8447
|
-
if (!
|
|
8845
|
+
if (!existsSync13(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
|
|
8448
8846
|
const verdict = classifyWritePath(filePath);
|
|
8449
8847
|
if (verdict.sensitive && subAgentGuard.active) {
|
|
8450
8848
|
throw new ToolError(
|
|
@@ -8452,7 +8850,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
8452
8850
|
`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
8851
|
);
|
|
8454
8852
|
}
|
|
8455
|
-
const original =
|
|
8853
|
+
const original = readFileSync12(filePath, encoding);
|
|
8456
8854
|
if (args["patch"] !== void 0) {
|
|
8457
8855
|
const patchText = String(args["patch"] ?? "");
|
|
8458
8856
|
const stopOnError = args["stop_on_error"] !== false;
|
|
@@ -8623,8 +9021,8 @@ function truncatePreview(str, maxLen = 80) {
|
|
|
8623
9021
|
}
|
|
8624
9022
|
|
|
8625
9023
|
// src/tools/builtin/list-dir.ts
|
|
8626
|
-
import { readdirSync as readdirSync4, statSync as statSync5, existsSync as
|
|
8627
|
-
import { join as
|
|
9024
|
+
import { readdirSync as readdirSync4, statSync as statSync5, existsSync as existsSync14 } from "fs";
|
|
9025
|
+
import { join as join8, basename as basename4 } from "path";
|
|
8628
9026
|
var listDirTool = {
|
|
8629
9027
|
definition: {
|
|
8630
9028
|
name: "list_dir",
|
|
@@ -8646,7 +9044,7 @@ var listDirTool = {
|
|
|
8646
9044
|
async execute(args) {
|
|
8647
9045
|
const dirPath = String(args["path"] ?? process.cwd());
|
|
8648
9046
|
const recursive = Boolean(args["recursive"] ?? false);
|
|
8649
|
-
if (!
|
|
9047
|
+
if (!existsSync14(dirPath)) {
|
|
8650
9048
|
const targetName = basename4(dirPath).toLowerCase();
|
|
8651
9049
|
const cwd = process.cwd();
|
|
8652
9050
|
const suggestions = [];
|
|
@@ -8704,11 +9102,11 @@ function listRecursive(basePath, indent, recursive, lines) {
|
|
|
8704
9102
|
if (entry.isDirectory()) {
|
|
8705
9103
|
lines.push(`${indent}\u{1F4C1} ${entry.name}/`);
|
|
8706
9104
|
if (recursive) {
|
|
8707
|
-
listRecursive(
|
|
9105
|
+
listRecursive(join8(basePath, entry.name), indent + " ", true, lines);
|
|
8708
9106
|
}
|
|
8709
9107
|
} else {
|
|
8710
9108
|
try {
|
|
8711
|
-
const stat = statSync5(
|
|
9109
|
+
const stat = statSync5(join8(basePath, entry.name));
|
|
8712
9110
|
const size = formatSize(stat.size);
|
|
8713
9111
|
lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
|
|
8714
9112
|
} catch {
|
|
@@ -8724,9 +9122,9 @@ function formatSize(bytes) {
|
|
|
8724
9122
|
}
|
|
8725
9123
|
|
|
8726
9124
|
// src/tools/builtin/grep-files.ts
|
|
8727
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
9125
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync13, statSync as statSync6, existsSync as existsSync15 } from "fs";
|
|
8728
9126
|
import { readFile } from "fs/promises";
|
|
8729
|
-
import { join as
|
|
9127
|
+
import { join as join9, relative } from "path";
|
|
8730
9128
|
var grepFilesTool = {
|
|
8731
9129
|
definition: {
|
|
8732
9130
|
name: "grep_files",
|
|
@@ -8777,7 +9175,7 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
|
|
|
8777
9175
|
const contextLines = Math.max(0, Number(args["context_lines"] ?? 0));
|
|
8778
9176
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
|
|
8779
9177
|
if (!pattern) throw new ToolError("grep_files", "pattern is required");
|
|
8780
|
-
if (!
|
|
9178
|
+
if (!existsSync15(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
|
|
8781
9179
|
const MAX_PATTERN_LENGTH = 1e3;
|
|
8782
9180
|
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
8783
9181
|
throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
|
|
@@ -8871,11 +9269,11 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
|
8871
9269
|
if (paths.length >= maxFiles) return;
|
|
8872
9270
|
if (entry.isDirectory()) {
|
|
8873
9271
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
8874
|
-
walk(
|
|
9272
|
+
walk(join9(dirPath, entry.name));
|
|
8875
9273
|
} else if (entry.isFile()) {
|
|
8876
9274
|
if (isBinary(entry.name)) continue;
|
|
8877
9275
|
if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
|
|
8878
|
-
const fullPath =
|
|
9276
|
+
const fullPath = join9(dirPath, entry.name);
|
|
8879
9277
|
try {
|
|
8880
9278
|
if (statSync6(fullPath).size > 1e6) continue;
|
|
8881
9279
|
} catch {
|
|
@@ -8930,7 +9328,7 @@ function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, r
|
|
|
8930
9328
|
}
|
|
8931
9329
|
let content;
|
|
8932
9330
|
try {
|
|
8933
|
-
content =
|
|
9331
|
+
content = readFileSync13(fullPath, "utf-8");
|
|
8934
9332
|
} catch {
|
|
8935
9333
|
return;
|
|
8936
9334
|
}
|
|
@@ -8961,8 +9359,8 @@ function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, r
|
|
|
8961
9359
|
}
|
|
8962
9360
|
|
|
8963
9361
|
// src/tools/builtin/glob-files.ts
|
|
8964
|
-
import { readdirSync as readdirSync6, statSync as statSync7, existsSync as
|
|
8965
|
-
import { join as
|
|
9362
|
+
import { readdirSync as readdirSync6, statSync as statSync7, existsSync as existsSync16 } from "fs";
|
|
9363
|
+
import { join as join10, relative as relative2, basename as basename5 } from "path";
|
|
8966
9364
|
var globFilesTool = {
|
|
8967
9365
|
definition: {
|
|
8968
9366
|
name: "glob_files",
|
|
@@ -8995,7 +9393,7 @@ Results sorted by most recent modification time. Automatically skips node_module
|
|
|
8995
9393
|
const rootPath = String(args["path"] ?? process.cwd());
|
|
8996
9394
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
|
|
8997
9395
|
if (!pattern) throw new ToolError("glob_files", "pattern is required");
|
|
8998
|
-
if (!
|
|
9396
|
+
if (!existsSync16(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
|
|
8999
9397
|
const regex = globToRegex(pattern);
|
|
9000
9398
|
const matches = [];
|
|
9001
9399
|
collectMatchingFiles(rootPath, rootPath, regex, matches, maxResults);
|
|
@@ -9072,7 +9470,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
9072
9470
|
}
|
|
9073
9471
|
for (const entry of entries) {
|
|
9074
9472
|
if (results.length >= maxResults) break;
|
|
9075
|
-
const fullPath =
|
|
9473
|
+
const fullPath = join10(dirPath, entry.name);
|
|
9076
9474
|
if (entry.isDirectory()) {
|
|
9077
9475
|
if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
9078
9476
|
collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
|
|
@@ -9914,10 +10312,10 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
9914
10312
|
};
|
|
9915
10313
|
|
|
9916
10314
|
// src/tools/builtin/save-memory.ts
|
|
9917
|
-
import { join as
|
|
10315
|
+
import { join as join11 } from "path";
|
|
9918
10316
|
import { homedir as homedir5 } from "os";
|
|
9919
10317
|
function getConfigDir() {
|
|
9920
|
-
return
|
|
10318
|
+
return join11(homedir5(), CONFIG_DIR_NAME);
|
|
9921
10319
|
}
|
|
9922
10320
|
function parseSensitivity(value) {
|
|
9923
10321
|
return value === "low" || value === "medium" || value === "high" ? value : void 0;
|
|
@@ -10266,13 +10664,13 @@ function formatResults2(query, data, _requested) {
|
|
|
10266
10664
|
}
|
|
10267
10665
|
|
|
10268
10666
|
// src/agents/agent-config.ts
|
|
10269
|
-
import { existsSync as
|
|
10270
|
-
import { join as
|
|
10667
|
+
import { existsSync as existsSync18, readdirSync as readdirSync8, readFileSync as readFileSync15 } from "fs";
|
|
10668
|
+
import { join as join13 } from "path";
|
|
10271
10669
|
import { homedir as homedir6 } from "os";
|
|
10272
10670
|
|
|
10273
10671
|
// src/plugins/plugin-manager.ts
|
|
10274
|
-
import { existsSync as
|
|
10275
|
-
import { basename as basename6, dirname as dirname7, join as
|
|
10672
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync14, realpathSync as realpathSync2, cpSync, rmSync, statSync as statSync8 } from "fs";
|
|
10673
|
+
import { basename as basename6, dirname as dirname7, join as join12, relative as relative3, resolve as resolve6, sep as sep3 } from "path";
|
|
10276
10674
|
import { createHash as createHash2 } from "crypto";
|
|
10277
10675
|
import { z as z2 } from "zod";
|
|
10278
10676
|
var MANIFEST_RELATIVE = ".aicli-plugin/plugin.json";
|
|
@@ -10332,19 +10730,19 @@ var PluginManifestSchema = z2.object({
|
|
|
10332
10730
|
permissionHints: z2.array(z2.string()).default([])
|
|
10333
10731
|
}).strict();
|
|
10334
10732
|
function pluginRoot(configDir) {
|
|
10335
|
-
return
|
|
10733
|
+
return join12(configDir, PLUGINS_DIR_NAME);
|
|
10336
10734
|
}
|
|
10337
10735
|
function pluginManifestPath(pluginDir) {
|
|
10338
|
-
return
|
|
10736
|
+
return join12(pluginDir, MANIFEST_RELATIVE);
|
|
10339
10737
|
}
|
|
10340
10738
|
function statePath(configDir) {
|
|
10341
|
-
return
|
|
10739
|
+
return join12(pluginRoot(configDir), STATE_FILE);
|
|
10342
10740
|
}
|
|
10343
10741
|
function loadState(configDir) {
|
|
10344
10742
|
const file = statePath(configDir);
|
|
10345
|
-
if (!
|
|
10743
|
+
if (!existsSync17(file)) return { version: 2, plugins: [] };
|
|
10346
10744
|
try {
|
|
10347
|
-
const parsed = JSON.parse(
|
|
10745
|
+
const parsed = JSON.parse(readFileSync14(file, "utf-8"));
|
|
10348
10746
|
const rawPlugins = Array.isArray(parsed.plugins) ? parsed.plugins : [];
|
|
10349
10747
|
return { version: 2, plugins: rawPlugins.map((p) => migrateEntry(p)) };
|
|
10350
10748
|
} catch {
|
|
@@ -10402,7 +10800,7 @@ function extractCommandPaths(command, pluginDir) {
|
|
|
10402
10800
|
try {
|
|
10403
10801
|
const abs = resolve6(resolvedDir, ref);
|
|
10404
10802
|
if (!abs.startsWith(resolvedDir + sep3)) continue;
|
|
10405
|
-
if (
|
|
10803
|
+
if (existsSync17(abs)) {
|
|
10406
10804
|
const real = realpathSync2(abs);
|
|
10407
10805
|
if (!real.startsWith(resolvedDir + sep3)) continue;
|
|
10408
10806
|
valid.push(relative3(resolvedDir, real));
|
|
@@ -10434,8 +10832,8 @@ function hashPluginContent(pluginDir, manifest) {
|
|
|
10434
10832
|
}
|
|
10435
10833
|
const sorted = [...assetRels].sort();
|
|
10436
10834
|
const hasher = createHash2("sha256");
|
|
10437
|
-
const manifestPath =
|
|
10438
|
-
hasher.update(normalizeForHash(
|
|
10835
|
+
const manifestPath = join12(resolvedDir, MANIFEST_RELATIVE);
|
|
10836
|
+
hasher.update(normalizeForHash(readFileSync14(manifestPath)));
|
|
10439
10837
|
for (const rel of sorted) {
|
|
10440
10838
|
const abs = resolve6(resolvedDir, rel);
|
|
10441
10839
|
if (!abs.startsWith(resolvedDir + sep3)) {
|
|
@@ -10455,7 +10853,7 @@ function hashPluginContent(pluginDir, manifest) {
|
|
|
10455
10853
|
if (!realPath.startsWith(resolvedDir + sep3)) {
|
|
10456
10854
|
throw new Error(`plugin asset symlink escapes plugin dir: ${rel} \u2192 ${realPath}`);
|
|
10457
10855
|
}
|
|
10458
|
-
hasher.update(normalizeForHash(
|
|
10856
|
+
hasher.update(normalizeForHash(readFileSync14(abs)));
|
|
10459
10857
|
}
|
|
10460
10858
|
return hasher.digest("hex");
|
|
10461
10859
|
}
|
|
@@ -10463,22 +10861,22 @@ function effectiveHash(entry) {
|
|
|
10463
10861
|
return entry.contentHash ?? entry.hash ?? "";
|
|
10464
10862
|
}
|
|
10465
10863
|
function readPluginManifest(manifestPath) {
|
|
10466
|
-
return PluginManifestSchema.parse(JSON.parse(
|
|
10864
|
+
return PluginManifestSchema.parse(JSON.parse(readFileSync14(manifestPath, "utf-8")));
|
|
10467
10865
|
}
|
|
10468
10866
|
function findPluginSourceDir(inputPath) {
|
|
10469
10867
|
const abs = resolve6(inputPath);
|
|
10470
10868
|
const direct = statSync8(abs).isDirectory() ? abs : dirname7(abs);
|
|
10471
|
-
if (
|
|
10472
|
-
const nested =
|
|
10473
|
-
if (
|
|
10869
|
+
if (existsSync17(pluginManifestPath(direct))) return direct;
|
|
10870
|
+
const nested = join12(direct, ".aicli-plugin");
|
|
10871
|
+
if (existsSync17(join12(nested, "plugin.json"))) return direct;
|
|
10474
10872
|
throw new Error(`plugin manifest not found: ${MANIFEST_RELATIVE}`);
|
|
10475
10873
|
}
|
|
10476
10874
|
function installPlugin(configDir, inputPath) {
|
|
10477
10875
|
const sourceDir = findPluginSourceDir(inputPath);
|
|
10478
10876
|
const manifest = readPluginManifest(pluginManifestPath(sourceDir));
|
|
10479
|
-
const targetDir =
|
|
10877
|
+
const targetDir = join12(pluginRoot(configDir), manifest.name);
|
|
10480
10878
|
mkdirSync8(pluginRoot(configDir), { recursive: true });
|
|
10481
|
-
if (
|
|
10879
|
+
if (existsSync17(targetDir)) rmSync(targetDir, { recursive: true, force: true });
|
|
10482
10880
|
cpSync(sourceDir, targetDir, { recursive: true });
|
|
10483
10881
|
const manifestPath = pluginManifestPath(targetDir);
|
|
10484
10882
|
const contentHash = hashPluginContent(targetDir, manifest);
|
|
@@ -10536,18 +10934,18 @@ function getInstalledPlugin(configDir, name) {
|
|
|
10536
10934
|
}
|
|
10537
10935
|
function listInstalledPlugins(configDir) {
|
|
10538
10936
|
const root = pluginRoot(configDir);
|
|
10539
|
-
if (!
|
|
10937
|
+
if (!existsSync17(root)) return [];
|
|
10540
10938
|
const state2 = loadState(configDir);
|
|
10541
10939
|
const out = [];
|
|
10542
10940
|
for (const entry of readdirSync7(root)) {
|
|
10543
|
-
const dir =
|
|
10941
|
+
const dir = join12(root, entry);
|
|
10544
10942
|
try {
|
|
10545
10943
|
if (!statSync8(dir).isDirectory()) continue;
|
|
10546
10944
|
} catch {
|
|
10547
10945
|
continue;
|
|
10548
10946
|
}
|
|
10549
10947
|
const manifestPath = pluginManifestPath(dir);
|
|
10550
|
-
if (!
|
|
10948
|
+
if (!existsSync17(manifestPath)) continue;
|
|
10551
10949
|
try {
|
|
10552
10950
|
const manifest = readPluginManifest(manifestPath);
|
|
10553
10951
|
const contentHash = hashPluginContent(dir, manifest);
|
|
@@ -10587,7 +10985,7 @@ function uniqueDirs(plugin, rels) {
|
|
|
10587
10985
|
for (const rel of rels) {
|
|
10588
10986
|
const abs = resolve6(plugin.dir, rel);
|
|
10589
10987
|
if (!abs.startsWith(resolve6(plugin.dir))) continue;
|
|
10590
|
-
if (
|
|
10988
|
+
if (existsSync17(abs)) dirs.add(statSync8(abs).isDirectory() ? abs : dirname7(abs));
|
|
10591
10989
|
}
|
|
10592
10990
|
return [...dirs];
|
|
10593
10991
|
}
|
|
@@ -10750,13 +11148,13 @@ function parseAgentConfig(raw, source, path3) {
|
|
|
10750
11148
|
return cfg;
|
|
10751
11149
|
}
|
|
10752
11150
|
function loadAgentDir(dir, source) {
|
|
10753
|
-
if (!
|
|
11151
|
+
if (!existsSync18(dir)) return [];
|
|
10754
11152
|
const out = [];
|
|
10755
11153
|
for (const file of readdirSync8(dir)) {
|
|
10756
11154
|
if (!file.endsWith(".json")) continue;
|
|
10757
|
-
const path3 =
|
|
11155
|
+
const path3 = join13(dir, file);
|
|
10758
11156
|
try {
|
|
10759
|
-
const parsed = JSON.parse(
|
|
11157
|
+
const parsed = JSON.parse(readFileSync15(path3, "utf-8"));
|
|
10760
11158
|
const cfg = parseAgentConfig(parsed, source, path3);
|
|
10761
11159
|
if (cfg) out.push(cfg);
|
|
10762
11160
|
} catch {
|
|
@@ -10766,8 +11164,8 @@ function loadAgentDir(dir, source) {
|
|
|
10766
11164
|
}
|
|
10767
11165
|
function getAgentDirs(configDir, cwd = process.cwd()) {
|
|
10768
11166
|
return {
|
|
10769
|
-
user:
|
|
10770
|
-
project:
|
|
11167
|
+
user: join13(configDir ?? join13(homedir6(), CONFIG_DIR_NAME), "agents"),
|
|
11168
|
+
project: join13(cwd, CONFIG_DIR_NAME, "agents")
|
|
10771
11169
|
};
|
|
10772
11170
|
}
|
|
10773
11171
|
function listAgentConfigs(configDir, cwd = process.cwd()) {
|
|
@@ -11485,14 +11883,14 @@ var taskStopTool = {
|
|
|
11485
11883
|
|
|
11486
11884
|
// src/tools/builtin/git-tools.ts
|
|
11487
11885
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
11488
|
-
import { existsSync as
|
|
11489
|
-
import { join as
|
|
11886
|
+
import { existsSync as existsSync19 } from "fs";
|
|
11887
|
+
import { join as join14 } from "path";
|
|
11490
11888
|
function assertGitRepo(cwd) {
|
|
11491
11889
|
let dir = cwd;
|
|
11492
11890
|
const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
|
|
11493
11891
|
while (dir && dir !== root) {
|
|
11494
|
-
if (
|
|
11495
|
-
const parent =
|
|
11892
|
+
if (existsSync19(join14(dir, ".git"))) return;
|
|
11893
|
+
const parent = join14(dir, "..");
|
|
11496
11894
|
if (parent === dir) break;
|
|
11497
11895
|
dir = parent;
|
|
11498
11896
|
}
|
|
@@ -11757,7 +12155,7 @@ ${commitOutput.trim()}`;
|
|
|
11757
12155
|
};
|
|
11758
12156
|
|
|
11759
12157
|
// src/tools/builtin/notebook-edit.ts
|
|
11760
|
-
import { readFileSync as
|
|
12158
|
+
import { readFileSync as readFileSync16, existsSync as existsSync20 } from "fs";
|
|
11761
12159
|
import { writeFile } from "fs/promises";
|
|
11762
12160
|
import { resolve as resolve7, extname as extname2 } from "path";
|
|
11763
12161
|
var notebookEditTool = {
|
|
@@ -11812,10 +12210,10 @@ var notebookEditTool = {
|
|
|
11812
12210
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
11813
12211
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
11814
12212
|
}
|
|
11815
|
-
if (!
|
|
12213
|
+
if (!existsSync20(absPath)) {
|
|
11816
12214
|
throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
|
|
11817
12215
|
}
|
|
11818
|
-
const raw =
|
|
12216
|
+
const raw = readFileSync16(absPath, "utf-8");
|
|
11819
12217
|
const nb = parseNotebook(raw);
|
|
11820
12218
|
const cellIdx0 = cellIndexRaw - 1;
|
|
11821
12219
|
undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
|
|
@@ -12141,7 +12539,7 @@ ${lines.join("\n")}`;
|
|
|
12141
12539
|
};
|
|
12142
12540
|
|
|
12143
12541
|
// src/tools/builtin/recall-memory.ts
|
|
12144
|
-
import { join as
|
|
12542
|
+
import { join as join15 } from "path";
|
|
12145
12543
|
import { homedir as homedir7 } from "os";
|
|
12146
12544
|
function formatHit(h, i) {
|
|
12147
12545
|
const ts = h.chunk.timestamp.slice(0, 16).replace("T", " ");
|
|
@@ -12192,7 +12590,7 @@ var recallMemoryTool = {
|
|
|
12192
12590
|
const excludeCurrent = Boolean(args["excludeCurrentSession"]);
|
|
12193
12591
|
const currentId = args["currentSessionId"] ? String(args["currentSessionId"]) : void 0;
|
|
12194
12592
|
const minScore = args["minScore"] !== void 0 ? Number(args["minScore"]) : 0.25;
|
|
12195
|
-
const configDir =
|
|
12593
|
+
const configDir = join15(homedir7(), CONFIG_DIR_NAME);
|
|
12196
12594
|
let persistentSection = "";
|
|
12197
12595
|
try {
|
|
12198
12596
|
const persistentHits = searchPersistentMemories(configDir, query, process.cwd(), 3);
|
|
@@ -12246,8 +12644,8 @@ function estimateToolDefinitionTokens(def) {
|
|
|
12246
12644
|
|
|
12247
12645
|
// src/tools/registry.ts
|
|
12248
12646
|
import { pathToFileURL } from "url";
|
|
12249
|
-
import { existsSync as
|
|
12250
|
-
import { join as
|
|
12647
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
|
|
12648
|
+
import { join as join16 } from "path";
|
|
12251
12649
|
var ToolRegistry = class {
|
|
12252
12650
|
tools = /* @__PURE__ */ new Map();
|
|
12253
12651
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
@@ -12408,7 +12806,7 @@ var ToolRegistry = class {
|
|
|
12408
12806
|
* Returns the number of successfully loaded plugins.
|
|
12409
12807
|
*/
|
|
12410
12808
|
async loadPlugins(pluginsDir, allowPlugins = false) {
|
|
12411
|
-
if (!
|
|
12809
|
+
if (!existsSync21(pluginsDir)) {
|
|
12412
12810
|
try {
|
|
12413
12811
|
mkdirSync9(pluginsDir, { recursive: true });
|
|
12414
12812
|
} catch {
|
|
@@ -12434,12 +12832,12 @@ var ToolRegistry = class {
|
|
|
12434
12832
|
process.stderr.write(
|
|
12435
12833
|
`
|
|
12436
12834
|
[plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
|
|
12437
|
-
` + files.map((f) => ` + ${
|
|
12835
|
+
` + files.map((f) => ` + ${join16(pluginsDir, f)}`).join("\n") + "\n\n"
|
|
12438
12836
|
);
|
|
12439
12837
|
let loaded = 0;
|
|
12440
12838
|
for (const file of files) {
|
|
12441
12839
|
try {
|
|
12442
|
-
const fileUrl = pathToFileURL(
|
|
12840
|
+
const fileUrl = pathToFileURL(join16(pluginsDir, file)).href;
|
|
12443
12841
|
const mod = await import(fileUrl);
|
|
12444
12842
|
const tool = mod.tool ?? mod.default?.tool ?? mod.default;
|
|
12445
12843
|
if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
|
|
@@ -12984,11 +13382,11 @@ var McpManager = class {
|
|
|
12984
13382
|
};
|
|
12985
13383
|
|
|
12986
13384
|
// src/skills/manager.ts
|
|
12987
|
-
import { existsSync as
|
|
12988
|
-
import { join as
|
|
13385
|
+
import { existsSync as existsSync22, readdirSync as readdirSync10, mkdirSync as mkdirSync10, statSync as statSync9 } from "fs";
|
|
13386
|
+
import { join as join17 } from "path";
|
|
12989
13387
|
|
|
12990
13388
|
// src/skills/types.ts
|
|
12991
|
-
import { readFileSync as
|
|
13389
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
12992
13390
|
import { basename as basename7 } from "path";
|
|
12993
13391
|
function parseSimpleYaml(yaml) {
|
|
12994
13392
|
const result = {};
|
|
@@ -13010,7 +13408,7 @@ function parseYamlArray(value) {
|
|
|
13010
13408
|
function parseSkillFile(filePath) {
|
|
13011
13409
|
let raw;
|
|
13012
13410
|
try {
|
|
13013
|
-
raw =
|
|
13411
|
+
raw = readFileSync17(filePath, "utf-8");
|
|
13014
13412
|
} catch {
|
|
13015
13413
|
return null;
|
|
13016
13414
|
}
|
|
@@ -13058,7 +13456,7 @@ var SkillManager = class {
|
|
|
13058
13456
|
return this.skills.size;
|
|
13059
13457
|
}
|
|
13060
13458
|
loadSkillDir(dir, createIfMissing) {
|
|
13061
|
-
if (!
|
|
13459
|
+
if (!existsSync22(dir)) {
|
|
13062
13460
|
if (createIfMissing) {
|
|
13063
13461
|
try {
|
|
13064
13462
|
mkdirSync10(dir, { recursive: true });
|
|
@@ -13075,14 +13473,14 @@ var SkillManager = class {
|
|
|
13075
13473
|
}
|
|
13076
13474
|
for (const entry of entries) {
|
|
13077
13475
|
let filePath;
|
|
13078
|
-
const fullPath =
|
|
13476
|
+
const fullPath = join17(dir, entry);
|
|
13079
13477
|
if (entry.endsWith(".md")) {
|
|
13080
13478
|
filePath = fullPath;
|
|
13081
13479
|
} else {
|
|
13082
13480
|
try {
|
|
13083
13481
|
if (statSync9(fullPath).isDirectory()) {
|
|
13084
|
-
const skillMd =
|
|
13085
|
-
if (
|
|
13482
|
+
const skillMd = join17(fullPath, "SKILL.md");
|
|
13483
|
+
if (existsSync22(skillMd)) filePath = skillMd;
|
|
13086
13484
|
else continue;
|
|
13087
13485
|
} else {
|
|
13088
13486
|
continue;
|
|
@@ -13141,7 +13539,7 @@ var SkillManager = class {
|
|
|
13141
13539
|
// src/web/tool-executor-web.ts
|
|
13142
13540
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
13143
13541
|
import { tmpdir as tmpdir2 } from "os";
|
|
13144
|
-
import { existsSync as
|
|
13542
|
+
import { existsSync as existsSync23, readFileSync as readFileSync18 } from "fs";
|
|
13145
13543
|
var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
13146
13544
|
constructor(registry, ws) {
|
|
13147
13545
|
this.registry = registry;
|
|
@@ -13281,9 +13679,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
13281
13679
|
getDiffPreview(call) {
|
|
13282
13680
|
if (call.name === "write_file") {
|
|
13283
13681
|
const { path: filePath, content: chunk, append } = resolveWriteFileArgs(call.arguments);
|
|
13284
|
-
if (filePath &&
|
|
13682
|
+
if (filePath && existsSync23(filePath)) {
|
|
13285
13683
|
try {
|
|
13286
|
-
const old =
|
|
13684
|
+
const old = readFileSync18(filePath, "utf-8");
|
|
13287
13685
|
return renderDiff(old, append ? old + chunk : chunk, { filePath });
|
|
13288
13686
|
} catch {
|
|
13289
13687
|
}
|
|
@@ -13596,16 +13994,16 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
13596
13994
|
};
|
|
13597
13995
|
|
|
13598
13996
|
// src/repl/dev-state.ts
|
|
13599
|
-
import { existsSync as
|
|
13600
|
-
import { join as
|
|
13997
|
+
import { existsSync as existsSync24, readFileSync as readFileSync19, unlinkSync as unlinkSync5, mkdirSync as mkdirSync11 } from "fs";
|
|
13998
|
+
import { join as join18 } from "path";
|
|
13601
13999
|
import { homedir as homedir8 } from "os";
|
|
13602
14000
|
function getDevStatePath() {
|
|
13603
|
-
return
|
|
14001
|
+
return join18(homedir8(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
|
|
13604
14002
|
}
|
|
13605
14003
|
function loadDevState() {
|
|
13606
14004
|
const path3 = getDevStatePath();
|
|
13607
|
-
if (!
|
|
13608
|
-
const content =
|
|
14005
|
+
if (!existsSync24(path3)) return null;
|
|
14006
|
+
const content = readFileSync19(path3, "utf-8").trim();
|
|
13609
14007
|
return content || null;
|
|
13610
14008
|
}
|
|
13611
14009
|
|
|
@@ -13666,148 +14064,6 @@ function loadMemoryContent(configDir, maxChars) {
|
|
|
13666
14064
|
return formatMemoryForPrompt(configDir, process.cwd(), maxChars);
|
|
13667
14065
|
}
|
|
13668
14066
|
|
|
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
14067
|
// src/core/chat-params.ts
|
|
13812
14068
|
function thinkingParamsFor(allModelParams, model) {
|
|
13813
14069
|
const params = allModelParams?.[model];
|
|
@@ -13906,9 +14162,41 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
|
13906
14162
|
return committedSize < originalSize;
|
|
13907
14163
|
}
|
|
13908
14164
|
|
|
14165
|
+
// src/core/context-pressure.ts
|
|
14166
|
+
function estimateConversationTokens(input) {
|
|
14167
|
+
let total = 0;
|
|
14168
|
+
if (input.systemPrompt) total += estimateTokens(input.systemPrompt);
|
|
14169
|
+
for (const msg of input.messages) {
|
|
14170
|
+
if (typeof msg.content === "string") {
|
|
14171
|
+
total += estimateTokens(msg.content);
|
|
14172
|
+
} else if (Array.isArray(msg.content)) {
|
|
14173
|
+
for (const part of msg.content) {
|
|
14174
|
+
if (part.type === "text" && part.text) total += estimateTokens(part.text);
|
|
14175
|
+
}
|
|
14176
|
+
}
|
|
14177
|
+
if (msg.toolCalls) {
|
|
14178
|
+
for (const tc of msg.toolCalls) {
|
|
14179
|
+
total += estimateTokens(JSON.stringify(tc.arguments));
|
|
14180
|
+
}
|
|
14181
|
+
}
|
|
14182
|
+
}
|
|
14183
|
+
return total;
|
|
14184
|
+
}
|
|
14185
|
+
function evaluateContextPressure(input) {
|
|
14186
|
+
if (input.contextWindow <= 0) return { shouldCompact: false, ratio: null, percent: null };
|
|
14187
|
+
const ratio = input.estimatedTokens / input.contextWindow;
|
|
14188
|
+
const threshold = input.threshold ?? CONTEXT_PRESSURE_THRESHOLD;
|
|
14189
|
+
return {
|
|
14190
|
+
shouldCompact: input.autoCompactEnabled && ratio >= threshold,
|
|
14191
|
+
ratio,
|
|
14192
|
+
percent: Math.round(ratio * 100)
|
|
14193
|
+
};
|
|
14194
|
+
}
|
|
14195
|
+
var AUTO_COMPACT_INSTRUCTION = "Preserve key context, compress old content, ensure seamless continuation";
|
|
14196
|
+
|
|
13909
14197
|
// src/core/context-files.ts
|
|
13910
|
-
import { existsSync as
|
|
13911
|
-
import { join as
|
|
14198
|
+
import { existsSync as existsSync25, readFileSync as readFileSync20 } from "fs";
|
|
14199
|
+
import { join as join19, relative as relative4, resolve as resolve8 } from "path";
|
|
13912
14200
|
function uniqueNonEmpty(values) {
|
|
13913
14201
|
const seen = /* @__PURE__ */ new Set();
|
|
13914
14202
|
const out = [];
|
|
@@ -13938,7 +14226,7 @@ function readContextFile(level, filePath, cwd, maxBytes) {
|
|
|
13938
14226
|
const name = filePath.split(/[\\/]/).pop() ?? filePath;
|
|
13939
14227
|
const shown = displayPath(filePath, cwd);
|
|
13940
14228
|
try {
|
|
13941
|
-
const raw =
|
|
14229
|
+
const raw = readFileSync20(filePath);
|
|
13942
14230
|
const byteCount = raw.byteLength;
|
|
13943
14231
|
if (byteCount === 0) {
|
|
13944
14232
|
return {
|
|
@@ -13985,8 +14273,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
|
|
|
13985
14273
|
function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
13986
14274
|
const skipped = [];
|
|
13987
14275
|
for (const candidate of candidates) {
|
|
13988
|
-
const filePath =
|
|
13989
|
-
if (!
|
|
14276
|
+
const filePath = join19(dir, candidate);
|
|
14277
|
+
if (!existsSync25(filePath)) continue;
|
|
13990
14278
|
const result = readContextFile(level, filePath, cwd, maxBytes);
|
|
13991
14279
|
if (result.skipped) skipped.push(result.skipped);
|
|
13992
14280
|
if (result.layer) return { layer: result.layer, skipped };
|
|
@@ -14013,7 +14301,7 @@ function loadContextFiles(options) {
|
|
|
14013
14301
|
});
|
|
14014
14302
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
14015
14303
|
}
|
|
14016
|
-
if (!
|
|
14304
|
+
if (!existsSync25(filePath)) {
|
|
14017
14305
|
skipped.push({
|
|
14018
14306
|
level: "single",
|
|
14019
14307
|
filePath,
|
|
@@ -14068,8 +14356,8 @@ import { mkdirSync as mkdirSync14, createWriteStream as createWriteStream2 } fro
|
|
|
14068
14356
|
import { dirname as dirname9 } from "path";
|
|
14069
14357
|
|
|
14070
14358
|
// src/repl/commands/project-init.ts
|
|
14071
|
-
import { existsSync as
|
|
14072
|
-
import { join as
|
|
14359
|
+
import { existsSync as existsSync26, readFileSync as readFileSync21, readdirSync as readdirSync11, statSync as statSync10 } from "fs";
|
|
14360
|
+
import { join as join20 } from "path";
|
|
14073
14361
|
var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
14074
14362
|
"node_modules",
|
|
14075
14363
|
".git",
|
|
@@ -14113,11 +14401,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
14113
14401
|
const sorted = filtered.sort((a, b) => {
|
|
14114
14402
|
let aIsDir = false, bIsDir = false;
|
|
14115
14403
|
try {
|
|
14116
|
-
aIsDir = statSync10(
|
|
14404
|
+
aIsDir = statSync10(join20(d, a)).isDirectory();
|
|
14117
14405
|
} catch {
|
|
14118
14406
|
}
|
|
14119
14407
|
try {
|
|
14120
|
-
bIsDir = statSync10(
|
|
14408
|
+
bIsDir = statSync10(join20(d, b)).isDirectory();
|
|
14121
14409
|
} catch {
|
|
14122
14410
|
}
|
|
14123
14411
|
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
@@ -14125,7 +14413,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
14125
14413
|
});
|
|
14126
14414
|
for (let i = 0; i < sorted.length && count < maxEntries; i++) {
|
|
14127
14415
|
const name = sorted[i];
|
|
14128
|
-
const fullPath =
|
|
14416
|
+
const fullPath = join20(d, name);
|
|
14129
14417
|
const isLast = i === sorted.length - 1;
|
|
14130
14418
|
const connector = isLast ? "+-- " : "|-- ";
|
|
14131
14419
|
let isDir;
|
|
@@ -14152,7 +14440,7 @@ function scanProject(cwd) {
|
|
|
14152
14440
|
configFiles: [],
|
|
14153
14441
|
directoryStructure: ""
|
|
14154
14442
|
};
|
|
14155
|
-
const check = (file) =>
|
|
14443
|
+
const check = (file) => existsSync26(join20(cwd, file));
|
|
14156
14444
|
const configCandidates = [
|
|
14157
14445
|
"package.json",
|
|
14158
14446
|
"tsconfig.json",
|
|
@@ -14179,7 +14467,7 @@ function scanProject(cwd) {
|
|
|
14179
14467
|
info.type = "node";
|
|
14180
14468
|
info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
|
|
14181
14469
|
try {
|
|
14182
|
-
const pkg = JSON.parse(
|
|
14470
|
+
const pkg = JSON.parse(readFileSync21(join20(cwd, "package.json"), "utf-8"));
|
|
14183
14471
|
const scripts = pkg.scripts ?? {};
|
|
14184
14472
|
info.buildCommand = scripts.build ? "npm run build" : void 0;
|
|
14185
14473
|
info.testCommand = scripts.test ? "npm test" : void 0;
|
|
@@ -14840,7 +15128,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
|
|
|
14840
15128
|
}
|
|
14841
15129
|
|
|
14842
15130
|
// src/hub/persist.ts
|
|
14843
|
-
import { join as
|
|
15131
|
+
import { join as join21 } from "path";
|
|
14844
15132
|
function discussionToMessages(state2) {
|
|
14845
15133
|
const out = [];
|
|
14846
15134
|
const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
|
|
@@ -14878,7 +15166,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
|
|
|
14878
15166
|
session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
|
|
14879
15167
|
session.titleAiGenerated = true;
|
|
14880
15168
|
await sm.save();
|
|
14881
|
-
return { id: session.id, path:
|
|
15169
|
+
return { id: session.id, path: join21(config.getHistoryDir(), `${session.id}.json`) };
|
|
14882
15170
|
}
|
|
14883
15171
|
|
|
14884
15172
|
// src/core/think-command.ts
|
|
@@ -15032,6 +15320,12 @@ async function handlePlan(args, ctx) {
|
|
|
15032
15320
|
} else if (sub === "execute") {
|
|
15033
15321
|
ctx.planMode = false;
|
|
15034
15322
|
ctx.send({ type: "info", message: "Plan mode OFF. Executing with all tools enabled." });
|
|
15323
|
+
} else if (sub === "status" || sub === "show") {
|
|
15324
|
+
ctx.send({ type: "info", message: `Plan mode: ${ctx.planMode ? "ON (read-only tools only)" : "OFF"}` });
|
|
15325
|
+
return;
|
|
15326
|
+
} else if (sub !== void 0) {
|
|
15327
|
+
ctx.send({ type: "error", message: `Unknown /plan option "${sub}" \u2014 expected execute | exit | status` });
|
|
15328
|
+
return;
|
|
15035
15329
|
} else {
|
|
15036
15330
|
ctx.planMode = !ctx.planMode;
|
|
15037
15331
|
ctx.send({ type: "info", message: `Plan mode: ${ctx.planMode ? "ON (read-only tools only)" : "OFF"}` });
|
|
@@ -15039,9 +15333,44 @@ async function handlePlan(args, ctx) {
|
|
|
15039
15333
|
ctx.sendStatus();
|
|
15040
15334
|
}
|
|
15041
15335
|
|
|
15336
|
+
// src/core/session-cost.ts
|
|
15337
|
+
function buildSessionCostReport(input) {
|
|
15338
|
+
const { usage } = input;
|
|
15339
|
+
const totalTokens = usage.inputTokens + usage.outputTokens + usage.cacheCreationTokens + usage.cacheReadTokens;
|
|
15340
|
+
if (totalTokens === 0) {
|
|
15341
|
+
return { hasUsage: false, totalTokens: 0, cacheHitPct: null, cost: null, costSource: "unknown", savings: null };
|
|
15342
|
+
}
|
|
15343
|
+
const liveCost = computeCost(input.provider, input.model, usage);
|
|
15344
|
+
const useAccrual = input.accruedUsd > 0;
|
|
15345
|
+
const cost = useAccrual ? input.accruedUsd : liveCost;
|
|
15346
|
+
const costSource = useAccrual ? input.accrualIncomplete ? "accrued-partial" : "accrued" : liveCost != null ? "estimated" : "unknown";
|
|
15347
|
+
let savings = null;
|
|
15348
|
+
if (liveCost != null && usage.cacheReadTokens > 0) {
|
|
15349
|
+
const withoutCache = computeCost(input.provider, input.model, {
|
|
15350
|
+
inputTokens: usage.inputTokens + usage.cacheReadTokens,
|
|
15351
|
+
outputTokens: usage.outputTokens,
|
|
15352
|
+
cacheCreationTokens: usage.cacheCreationTokens,
|
|
15353
|
+
cacheReadTokens: 0
|
|
15354
|
+
});
|
|
15355
|
+
if (withoutCache != null) savings = withoutCache - liveCost;
|
|
15356
|
+
}
|
|
15357
|
+
const cacheHitPct = usage.cacheReadTokens > 0 ? Math.round(usage.cacheReadTokens / (usage.cacheReadTokens + usage.inputTokens) * 100) : null;
|
|
15358
|
+
return { hasUsage: true, totalTokens, cacheHitPct, cost, costSource, savings };
|
|
15359
|
+
}
|
|
15360
|
+
function costSourceNote(source) {
|
|
15361
|
+
switch (source) {
|
|
15362
|
+
case "accrued-partial":
|
|
15363
|
+
return "(partial \u2014 some turns had unknown pricing)";
|
|
15364
|
+
case "estimated":
|
|
15365
|
+
return "(estimated at current rates)";
|
|
15366
|
+
default:
|
|
15367
|
+
return null;
|
|
15368
|
+
}
|
|
15369
|
+
}
|
|
15370
|
+
|
|
15042
15371
|
// src/web/commands/_shared.ts
|
|
15043
|
-
import { existsSync as
|
|
15044
|
-
import { join as
|
|
15372
|
+
import { existsSync as existsSync29, readFileSync as readFileSync23, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, statSync as statSync13, createWriteStream } from "fs";
|
|
15373
|
+
import { join as join24, resolve as resolve9, dirname as dirname8 } from "path";
|
|
15045
15374
|
|
|
15046
15375
|
// src/cli/review-prompts.ts
|
|
15047
15376
|
function buildReviewPrompt(diff, gitContextStr, detailed) {
|
|
@@ -15113,34 +15442,34 @@ function readGitDiff(options = {}) {
|
|
|
15113
15442
|
}
|
|
15114
15443
|
|
|
15115
15444
|
// src/diagnostics/doctor-report.ts
|
|
15116
|
-
import { existsSync as
|
|
15117
|
-
import { join as
|
|
15445
|
+
import { existsSync as existsSync28, statSync as statSync12 } from "fs";
|
|
15446
|
+
import { join as join23 } from "path";
|
|
15118
15447
|
import { arch as arch2, platform as platform7, release as release2 } from "os";
|
|
15119
15448
|
|
|
15120
15449
|
// src/diagnostics/crash-log.ts
|
|
15121
15450
|
import {
|
|
15122
|
-
existsSync as
|
|
15451
|
+
existsSync as existsSync27,
|
|
15123
15452
|
mkdirSync as mkdirSync12,
|
|
15124
15453
|
readdirSync as readdirSync12,
|
|
15125
|
-
readFileSync as
|
|
15454
|
+
readFileSync as readFileSync22,
|
|
15126
15455
|
statSync as statSync11,
|
|
15127
15456
|
unlinkSync as unlinkSync6,
|
|
15128
15457
|
writeFileSync as writeFileSync2
|
|
15129
15458
|
} from "fs";
|
|
15130
|
-
import { join as
|
|
15459
|
+
import { join as join22 } from "path";
|
|
15131
15460
|
import { homedir as homedir9, platform as platform6, release, arch } from "os";
|
|
15132
15461
|
var LOGS_DIR_NAME = "logs";
|
|
15133
15462
|
var CRASH_LOG_PREFIX = "crash-";
|
|
15134
15463
|
function getLogsDir(configDir) {
|
|
15135
|
-
const base = configDir ??
|
|
15136
|
-
return
|
|
15464
|
+
const base = configDir ?? join22(homedir9(), CONFIG_DIR_NAME);
|
|
15465
|
+
return join22(base, LOGS_DIR_NAME);
|
|
15137
15466
|
}
|
|
15138
15467
|
function truncate(s, n) {
|
|
15139
15468
|
return s.length <= n ? s : s.slice(0, n) + "\u2026";
|
|
15140
15469
|
}
|
|
15141
15470
|
function listRecentCrashes(limit = 10, configDir) {
|
|
15142
15471
|
const dir = getLogsDir(configDir);
|
|
15143
|
-
if (!
|
|
15472
|
+
if (!existsSync27(dir)) return [];
|
|
15144
15473
|
const files = [];
|
|
15145
15474
|
let names;
|
|
15146
15475
|
try {
|
|
@@ -15150,10 +15479,10 @@ function listRecentCrashes(limit = 10, configDir) {
|
|
|
15150
15479
|
}
|
|
15151
15480
|
for (const name of names) {
|
|
15152
15481
|
if (!name.startsWith(CRASH_LOG_PREFIX) || !name.endsWith(".log")) continue;
|
|
15153
|
-
const full =
|
|
15482
|
+
const full = join22(dir, name);
|
|
15154
15483
|
try {
|
|
15155
15484
|
const st = statSync11(full);
|
|
15156
|
-
const head =
|
|
15485
|
+
const head = readFileSync22(full, "utf-8").split("\n").slice(0, 20);
|
|
15157
15486
|
const tsLine = head.find((l) => l.startsWith("timestamp:"));
|
|
15158
15487
|
const kindLine = head.find((l) => l.startsWith("kind:"));
|
|
15159
15488
|
const msgIdx = head.findIndex((l) => l.trim() === "## message");
|
|
@@ -15172,8 +15501,8 @@ function listRecentCrashes(limit = 10, configDir) {
|
|
|
15172
15501
|
return files.slice(0, limit);
|
|
15173
15502
|
}
|
|
15174
15503
|
function getConfigDirUsage(configDir) {
|
|
15175
|
-
const base = configDir ??
|
|
15176
|
-
if (!
|
|
15504
|
+
const base = configDir ?? join22(homedir9(), CONFIG_DIR_NAME);
|
|
15505
|
+
if (!existsSync27(base)) return { totalBytes: 0, entries: [] };
|
|
15177
15506
|
const entries = [];
|
|
15178
15507
|
let total = 0;
|
|
15179
15508
|
let names;
|
|
@@ -15183,7 +15512,7 @@ function getConfigDirUsage(configDir) {
|
|
|
15183
15512
|
return { totalBytes: 0, entries: [] };
|
|
15184
15513
|
}
|
|
15185
15514
|
for (const name of names) {
|
|
15186
|
-
const full =
|
|
15515
|
+
const full = join22(base, name);
|
|
15187
15516
|
const bytes = dirSize(full);
|
|
15188
15517
|
entries.push({ name, bytes });
|
|
15189
15518
|
total += bytes;
|
|
@@ -15198,7 +15527,7 @@ function dirSize(path3) {
|
|
|
15198
15527
|
if (!st.isDirectory()) return 0;
|
|
15199
15528
|
let sum = 0;
|
|
15200
15529
|
for (const name of readdirSync12(path3)) {
|
|
15201
|
-
sum += dirSize(
|
|
15530
|
+
sum += dirSize(join22(path3, name));
|
|
15202
15531
|
}
|
|
15203
15532
|
return sum;
|
|
15204
15533
|
} catch {
|
|
@@ -15208,7 +15537,7 @@ function dirSize(path3) {
|
|
|
15208
15537
|
|
|
15209
15538
|
// src/diagnostics/doctor-report.ts
|
|
15210
15539
|
function checkFile(label, path3) {
|
|
15211
|
-
const exists =
|
|
15540
|
+
const exists = existsSync28(path3);
|
|
15212
15541
|
let sizeBytes = null;
|
|
15213
15542
|
if (exists) {
|
|
15214
15543
|
try {
|
|
@@ -15251,12 +15580,12 @@ function buildDoctorReport(options) {
|
|
|
15251
15580
|
configDir,
|
|
15252
15581
|
providers: options.providers.listAll().map((p) => ({ id: p.id, displayName: p.displayName, configured: p.configured })),
|
|
15253
15582
|
files: [
|
|
15254
|
-
checkFile("config.json",
|
|
15255
|
-
checkFile("memory.md",
|
|
15256
|
-
checkFile("memory.jsonl",
|
|
15257
|
-
checkFile("dev-state.md",
|
|
15258
|
-
checkFile("hooks-trust.json",
|
|
15259
|
-
checkFile("plugin-state.json",
|
|
15583
|
+
checkFile("config.json", join23(configDir, "config.json")),
|
|
15584
|
+
checkFile("memory.md", join23(configDir, MEMORY_FILE_NAME)),
|
|
15585
|
+
checkFile("memory.jsonl", join23(configDir, "memory.jsonl")),
|
|
15586
|
+
checkFile("dev-state.md", join23(configDir, DEV_STATE_FILE_NAME)),
|
|
15587
|
+
checkFile("hooks-trust.json", join23(configDir, "hooks-trust.json")),
|
|
15588
|
+
checkFile("plugin-state.json", join23(configDir, "plugins", "plugin-state.json"))
|
|
15260
15589
|
],
|
|
15261
15590
|
context: {
|
|
15262
15591
|
enabled: contextSetting !== false,
|
|
@@ -15291,7 +15620,7 @@ function buildDoctorReport(options) {
|
|
|
15291
15620
|
disabled: hooks.filter((h) => h.disabled).length
|
|
15292
15621
|
},
|
|
15293
15622
|
plugins: {
|
|
15294
|
-
root:
|
|
15623
|
+
root: join23(configDir, "plugins"),
|
|
15295
15624
|
installed: plugins.length,
|
|
15296
15625
|
active: activeAssets.plugins.length,
|
|
15297
15626
|
invalid: plugins.filter((p) => !p.valid).length,
|
|
@@ -15306,7 +15635,7 @@ function buildDoctorReport(options) {
|
|
|
15306
15635
|
mcp: {
|
|
15307
15636
|
enabled: config.get("mcpEnabled"),
|
|
15308
15637
|
configured: Object.keys(mcpServers).length,
|
|
15309
|
-
projectConfigExists:
|
|
15638
|
+
projectConfigExists: existsSync28(join23(projectRoot, MCP_PROJECT_CONFIG_NAME)),
|
|
15310
15639
|
statuses: options.mcpStatuses ?? []
|
|
15311
15640
|
},
|
|
15312
15641
|
recentCrashes: listRecentCrashes(5, configDir),
|
|
@@ -15534,7 +15863,7 @@ Cache: write=${cacheCreate} read=${cacheRead}` : "";
|
|
|
15534
15863
|
Cost: ${formatCost(cost)}` : "";
|
|
15535
15864
|
let memoryLine = "";
|
|
15536
15865
|
try {
|
|
15537
|
-
const { countPendingMemories } = await import("./persistent-memory-
|
|
15866
|
+
const { countPendingMemories } = await import("./persistent-memory-YBWIZWQP.js");
|
|
15538
15867
|
const pending = countPendingMemories(ctx.config.getConfigDir());
|
|
15539
15868
|
if (pending > 0) memoryLine = `
|
|
15540
15869
|
Memory: \u26A0 ${pending} pending approval \u2014 see the Memory panel or /memory`;
|
|
@@ -15742,18 +16071,16 @@ async function handleExport(args, ctx) {
|
|
|
15742
16071
|
}
|
|
15743
16072
|
async function handleCost(_args, ctx) {
|
|
15744
16073
|
const u = ctx.sessionTokenUsage;
|
|
15745
|
-
const
|
|
15746
|
-
|
|
15747
|
-
|
|
15748
|
-
|
|
15749
|
-
|
|
15750
|
-
|
|
15751
|
-
|
|
15752
|
-
|
|
15753
|
-
|
|
15754
|
-
|
|
15755
|
-
if (withoutCache != null) savings = withoutCache - cost;
|
|
15756
|
-
}
|
|
16074
|
+
const report = buildSessionCostReport({
|
|
16075
|
+
provider: ctx.currentProvider,
|
|
16076
|
+
model: ctx.currentModel,
|
|
16077
|
+
usage: u,
|
|
16078
|
+
accruedUsd: ctx.sessionAccruedCostUsd,
|
|
16079
|
+
accrualIncomplete: ctx.sessionCostIncomplete
|
|
16080
|
+
});
|
|
16081
|
+
const total = report.totalTokens;
|
|
16082
|
+
const cost = report.cost;
|
|
16083
|
+
const savings = report.savings;
|
|
15757
16084
|
const lines = [
|
|
15758
16085
|
"\u{1F4B0} Session Cost & Token Usage",
|
|
15759
16086
|
` Provider : ${ctx.currentProvider}`,
|
|
@@ -15762,13 +16089,13 @@ async function handleCost(_args, ctx) {
|
|
|
15762
16089
|
` Output : ${u.outputTokens.toLocaleString()}`
|
|
15763
16090
|
];
|
|
15764
16091
|
if (u.cacheCreationTokens > 0) lines.push(` Cache write : ${u.cacheCreationTokens.toLocaleString()}`);
|
|
15765
|
-
if (
|
|
15766
|
-
|
|
15767
|
-
lines.push(` Cache read : ${u.cacheReadTokens.toLocaleString()} (${pct}% hit rate)`);
|
|
16092
|
+
if (report.cacheHitPct != null) {
|
|
16093
|
+
lines.push(` Cache read : ${u.cacheReadTokens.toLocaleString()} (${report.cacheHitPct}% hit rate)`);
|
|
15768
16094
|
}
|
|
15769
16095
|
lines.push(` Total tokens : ${total.toLocaleString()}`);
|
|
15770
16096
|
if (cost != null) {
|
|
15771
|
-
|
|
16097
|
+
const note = costSourceNote(report.costSource);
|
|
16098
|
+
lines.push(` Cost : ${formatCost(cost)}${note ? ` ${note}` : ""}`);
|
|
15772
16099
|
if (savings != null && savings > 0) {
|
|
15773
16100
|
lines.push(` Cache savings : -${formatCost(savings)} (vs no cache)`);
|
|
15774
16101
|
}
|
|
@@ -16118,7 +16445,7 @@ async function handlePlugin(args, ctx) {
|
|
|
16118
16445
|
lines.push("");
|
|
16119
16446
|
}
|
|
16120
16447
|
if (pluginTools.length > 0) {
|
|
16121
|
-
const legacyDir =
|
|
16448
|
+
const legacyDir = join24(configDir, PLUGINS_DIR_NAME);
|
|
16122
16449
|
lines.push(`**Legacy JS tools** (${pluginTools.length}):`);
|
|
16123
16450
|
lines.push(`Dir: ${legacyDir}`);
|
|
16124
16451
|
for (const t of pluginTools) lines.push(` \u2022 ${t.definition.name} \u2014 ${t.definition.description}`);
|
|
@@ -16242,8 +16569,8 @@ ${activated.meta.description || ""}` });
|
|
|
16242
16569
|
}
|
|
16243
16570
|
async function handleCommands(_args, ctx) {
|
|
16244
16571
|
const configDir = ctx.config.getConfigDir();
|
|
16245
|
-
const commandsDir =
|
|
16246
|
-
if (!
|
|
16572
|
+
const commandsDir = join24(configDir, CUSTOM_COMMANDS_DIR_NAME);
|
|
16573
|
+
if (!existsSync29(commandsDir)) {
|
|
16247
16574
|
ctx.send({ type: "info", message: `No custom commands directory.
|
|
16248
16575
|
Create: ${commandsDir}/ with .md files.` });
|
|
16249
16576
|
return;
|
|
@@ -16349,7 +16676,7 @@ async function handleTest(args, ctx) {
|
|
|
16349
16676
|
const isCommand = argStr.includes(" ") || /^(mvn|gradle|npm|pytest|cargo|go)\b/.test(argStr);
|
|
16350
16677
|
testArgs = isCommand ? { command: argStr } : { filter: argStr };
|
|
16351
16678
|
}
|
|
16352
|
-
const runTests = ctx.runTests ?? (await import("./run-tests-
|
|
16679
|
+
const runTests = ctx.runTests ?? (await import("./run-tests-Y44SCLIO.js")).executeTests;
|
|
16353
16680
|
const report = await runTests(testArgs);
|
|
16354
16681
|
ctx.send({ type: "info", message: report });
|
|
16355
16682
|
} catch (err) {
|
|
@@ -16373,9 +16700,9 @@ async function handleDiff(args, ctx) {
|
|
|
16373
16700
|
let modifiedFiles = 0;
|
|
16374
16701
|
const diffLines2 = [];
|
|
16375
16702
|
for (const [filePath, { earliest }] of fileMap) {
|
|
16376
|
-
const currentContent =
|
|
16703
|
+
const currentContent = existsSync29(filePath) ? (() => {
|
|
16377
16704
|
try {
|
|
16378
|
-
return
|
|
16705
|
+
return readFileSync23(filePath, "utf-8");
|
|
16379
16706
|
} catch {
|
|
16380
16707
|
return null;
|
|
16381
16708
|
}
|
|
@@ -16410,9 +16737,9 @@ async function handleDiff(args, ctx) {
|
|
|
16410
16737
|
}
|
|
16411
16738
|
async function handleInit(args, ctx) {
|
|
16412
16739
|
const cwd = process.cwd();
|
|
16413
|
-
const targetPath =
|
|
16740
|
+
const targetPath = join24(cwd, "AICLI.md");
|
|
16414
16741
|
const force = args.includes("--force");
|
|
16415
|
-
if (
|
|
16742
|
+
if (existsSync29(targetPath) && !force) {
|
|
16416
16743
|
ctx.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
|
|
16417
16744
|
Use /init --force to overwrite.` });
|
|
16418
16745
|
return;
|
|
@@ -16607,7 +16934,7 @@ async function handleMemory(args, ctx) {
|
|
|
16607
16934
|
ctx.handleMemoryManage(sub, args[1], sub === "expire" ? args[2] : void 0);
|
|
16608
16935
|
} else if (sub === "export") {
|
|
16609
16936
|
if (args[1] === "md") {
|
|
16610
|
-
const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-
|
|
16937
|
+
const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-YBWIZWQP.js");
|
|
16611
16938
|
ctx.send({
|
|
16612
16939
|
type: "export_data",
|
|
16613
16940
|
format: "md",
|
|
@@ -16645,7 +16972,7 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
|
|
|
16645
16972
|
return;
|
|
16646
16973
|
}
|
|
16647
16974
|
const dirPath = resolve9(sub);
|
|
16648
|
-
if (!
|
|
16975
|
+
if (!existsSync29(dirPath)) {
|
|
16649
16976
|
ctx.send({ type: "error", message: `Directory not found: ${dirPath}` });
|
|
16650
16977
|
return;
|
|
16651
16978
|
}
|
|
@@ -16658,6 +16985,119 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
|
|
|
16658
16985
|
It will be included in AI context for subsequent messages.` });
|
|
16659
16986
|
}
|
|
16660
16987
|
|
|
16988
|
+
// src/core/profile-command.ts
|
|
16989
|
+
var PROFILE_FIELDS = ["name", "nickname", "role", "bio", "interests", "locale", "extra"];
|
|
16990
|
+
function stripSurroundingQuotes(value) {
|
|
16991
|
+
const pairs = [['"', '"'], ["'", "'"], ["\u201C", "\u201D"], ["\u2018", "\u2019"], ["\u300C", "\u300D"]];
|
|
16992
|
+
for (const [open, close] of pairs) {
|
|
16993
|
+
if (value.length >= 2 && value.startsWith(open) && value.endsWith(close)) {
|
|
16994
|
+
return value.slice(open.length, value.length - close.length).trim();
|
|
16995
|
+
}
|
|
16996
|
+
}
|
|
16997
|
+
return value;
|
|
16998
|
+
}
|
|
16999
|
+
function showRows(profile) {
|
|
17000
|
+
const rows = [];
|
|
17001
|
+
if (profile.name) rows.push(["Name", profile.name]);
|
|
17002
|
+
if (profile.nickname) rows.push(["Nickname", profile.nickname]);
|
|
17003
|
+
if (profile.role) rows.push(["Role", profile.role]);
|
|
17004
|
+
if (profile.bio) rows.push(["Bio", profile.bio]);
|
|
17005
|
+
if (profile.interests?.length) rows.push(["Interests", profile.interests.join(", ")]);
|
|
17006
|
+
if (profile.locale) rows.push(["Locale", profile.locale]);
|
|
17007
|
+
if (profile.extra) {
|
|
17008
|
+
rows.push(["Extra", profile.extra.length > 80 ? `${profile.extra.slice(0, 80)}\u2026` : profile.extra]);
|
|
17009
|
+
}
|
|
17010
|
+
return rows;
|
|
17011
|
+
}
|
|
17012
|
+
function applyProfileCommand(args, profile) {
|
|
17013
|
+
const sub = (args[0] ?? "show").toLowerCase();
|
|
17014
|
+
if (sub === "show" || sub === "view") {
|
|
17015
|
+
const rows = showRows(profile);
|
|
17016
|
+
if (rows.length === 0) {
|
|
17017
|
+
return {
|
|
17018
|
+
summary: "No profile configured yet.",
|
|
17019
|
+
rows: [],
|
|
17020
|
+
details: ["Set one with /profile set <field> <value> \u2014 it is injected into every conversation."],
|
|
17021
|
+
changed: false,
|
|
17022
|
+
isError: false
|
|
17023
|
+
};
|
|
17024
|
+
}
|
|
17025
|
+
return {
|
|
17026
|
+
summary: "\u{1F464} Your Profile",
|
|
17027
|
+
rows,
|
|
17028
|
+
details: ["This identity is injected into every AI conversation."],
|
|
17029
|
+
changed: false,
|
|
17030
|
+
isError: false
|
|
17031
|
+
};
|
|
17032
|
+
}
|
|
17033
|
+
if (sub === "clear") {
|
|
17034
|
+
return {
|
|
17035
|
+
profile: {},
|
|
17036
|
+
summary: "Profile cleared. The AI will no longer know who you are.",
|
|
17037
|
+
rows: [],
|
|
17038
|
+
details: [],
|
|
17039
|
+
changed: true,
|
|
17040
|
+
isError: false
|
|
17041
|
+
};
|
|
17042
|
+
}
|
|
17043
|
+
if (sub === "set") {
|
|
17044
|
+
const field = args[1];
|
|
17045
|
+
const value = args.slice(2).join(" ").trim();
|
|
17046
|
+
if (!field || !PROFILE_FIELDS.includes(field)) {
|
|
17047
|
+
return {
|
|
17048
|
+
summary: "Usage: /profile set <field> <value>",
|
|
17049
|
+
rows: [],
|
|
17050
|
+
details: [`Fields: ${PROFILE_FIELDS.join(", ")}`],
|
|
17051
|
+
changed: false,
|
|
17052
|
+
isError: true
|
|
17053
|
+
};
|
|
17054
|
+
}
|
|
17055
|
+
if (!value) {
|
|
17056
|
+
return {
|
|
17057
|
+
summary: `Usage: /profile set ${field} <value>`,
|
|
17058
|
+
rows: [],
|
|
17059
|
+
details: [],
|
|
17060
|
+
changed: false,
|
|
17061
|
+
isError: true
|
|
17062
|
+
};
|
|
17063
|
+
}
|
|
17064
|
+
const updated = { ...profile };
|
|
17065
|
+
if (field === "interests") {
|
|
17066
|
+
updated.interests = value.split(",").map((s) => stripSurroundingQuotes(s.trim())).filter(Boolean);
|
|
17067
|
+
} else {
|
|
17068
|
+
updated[field] = stripSurroundingQuotes(value);
|
|
17069
|
+
}
|
|
17070
|
+
const shown = field === "interests" ? (updated.interests ?? []).join(", ") : stripSurroundingQuotes(value);
|
|
17071
|
+
return {
|
|
17072
|
+
profile: updated,
|
|
17073
|
+
summary: `Profile updated: ${field} = ${shown}`,
|
|
17074
|
+
rows: [],
|
|
17075
|
+
details: [],
|
|
17076
|
+
changed: true,
|
|
17077
|
+
isError: false
|
|
17078
|
+
};
|
|
17079
|
+
}
|
|
17080
|
+
if (sub === "edit") {
|
|
17081
|
+
return {
|
|
17082
|
+
summary: "Interactive editing lives in the setup wizard.",
|
|
17083
|
+
rows: [],
|
|
17084
|
+
details: [
|
|
17085
|
+
"Run `aicli config` in a terminal, or use /profile set <field> <value> here.",
|
|
17086
|
+
`Fields: ${PROFILE_FIELDS.join(", ")}`
|
|
17087
|
+
],
|
|
17088
|
+
changed: false,
|
|
17089
|
+
isError: false
|
|
17090
|
+
};
|
|
17091
|
+
}
|
|
17092
|
+
return {
|
|
17093
|
+
summary: `Unknown /profile option "${args[0]}" \u2014 expected show | set | clear | edit`,
|
|
17094
|
+
rows: [],
|
|
17095
|
+
details: [],
|
|
17096
|
+
changed: false,
|
|
17097
|
+
isError: true
|
|
17098
|
+
};
|
|
17099
|
+
}
|
|
17100
|
+
|
|
16661
17101
|
// src/web/commands/security-commands.ts
|
|
16662
17102
|
async function handleConfig(args, ctx) {
|
|
16663
17103
|
const sub = args[0];
|
|
@@ -16719,6 +17159,18 @@ async function handleBug(_args, ctx) {
|
|
|
16719
17159
|
];
|
|
16720
17160
|
ctx.send({ type: "info", message: lines.join("\n") });
|
|
16721
17161
|
}
|
|
17162
|
+
async function handleProfile(args, ctx) {
|
|
17163
|
+
const outcome = applyProfileCommand(args, ctx.config.get("userProfile") ?? {});
|
|
17164
|
+
if (outcome.profile) {
|
|
17165
|
+
ctx.config.set("userProfile", outcome.profile);
|
|
17166
|
+
ctx.config.save();
|
|
17167
|
+
}
|
|
17168
|
+
const lines = [outcome.summary];
|
|
17169
|
+
for (const [label, value] of outcome.rows) lines.push(` ${label}: ${value}`);
|
|
17170
|
+
for (const line of outcome.details) lines.push(line);
|
|
17171
|
+
ctx.send({ type: outcome.isError ? "error" : "info", message: lines.join("\n") });
|
|
17172
|
+
if (outcome.changed) ctx.sendStatus();
|
|
17173
|
+
}
|
|
16722
17174
|
|
|
16723
17175
|
// src/web/commands/agent-commands.ts
|
|
16724
17176
|
async function handleAuto(args, ctx) {
|
|
@@ -16762,6 +17214,17 @@ async function handlePermissions(args, ctx) {
|
|
|
16762
17214
|
}
|
|
16763
17215
|
async function handleYolo(args, ctx) {
|
|
16764
17216
|
const sub = args[0]?.toLowerCase();
|
|
17217
|
+
if (sub === "status") {
|
|
17218
|
+
ctx.send({
|
|
17219
|
+
type: "info",
|
|
17220
|
+
message: `YOLO mode: ${ctx.toolExecutor.sessionAutoApprove ? "ON (write tools auto-approved)" : "OFF"}`
|
|
17221
|
+
});
|
|
17222
|
+
return;
|
|
17223
|
+
}
|
|
17224
|
+
if (sub !== void 0 && sub !== "on" && sub !== "off") {
|
|
17225
|
+
ctx.send({ type: "error", message: `Unknown /yolo option "${sub}" \u2014 expected on | off | status` });
|
|
17226
|
+
return;
|
|
17227
|
+
}
|
|
16765
17228
|
if (sub === "off") {
|
|
16766
17229
|
ctx.toolExecutor.sessionAutoApprove = false;
|
|
16767
17230
|
ctx.send({ type: "info", message: "\u{1F512} Auto-approve disabled \u2014 confirmations restored for this session." });
|
|
@@ -16973,14 +17436,17 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
16973
17436
|
}
|
|
16974
17437
|
|
|
16975
17438
|
// src/web/session-handler.ts
|
|
16976
|
-
var CLI_ONLY_WEB_COMMANDS = /* @__PURE__ */ new
|
|
16977
|
-
"copy",
|
|
16978
|
-
"paste",
|
|
16979
|
-
"exit",
|
|
16980
|
-
"quit",
|
|
16981
|
-
"snapshot",
|
|
16982
|
-
"hooks",
|
|
16983
|
-
"hook"
|
|
17439
|
+
var CLI_ONLY_WEB_COMMANDS = /* @__PURE__ */ new Map([
|
|
17440
|
+
["copy", "It copies to the terminal clipboard; use your browser/OS copy instead."],
|
|
17441
|
+
["paste", "It reads the terminal clipboard; paste directly into the composer instead."],
|
|
17442
|
+
["exit", "Close the browser tab to end this Web session."],
|
|
17443
|
+
["quit", "Close the browser tab to end this Web session."],
|
|
17444
|
+
["snapshot", "Dev-state snapshots are written when the terminal REPL exits."],
|
|
17445
|
+
["hooks", "Hook trust is granted per machine from the terminal."],
|
|
17446
|
+
["hook", "Hook trust is granted per machine from the terminal."],
|
|
17447
|
+
// 以下两条并非「终端才有意义」,而是 Web 侧尚未实现——如实说明,别假装是设计。
|
|
17448
|
+
["route", "Smart model routing is not applied to Web sessions yet (config.routing is REPL-only)."],
|
|
17449
|
+
["security", "Redaction status and toggles are terminal-only for now; /status shows the current profile."]
|
|
16984
17450
|
]);
|
|
16985
17451
|
var webCommandHandlers = {
|
|
16986
17452
|
provider: handleProvider,
|
|
@@ -17016,6 +17482,7 @@ var webCommandHandlers = {
|
|
|
17016
17482
|
config: handleConfig,
|
|
17017
17483
|
doctor: handleDoctor,
|
|
17018
17484
|
bug: handleBug,
|
|
17485
|
+
profile: handleProfile,
|
|
17019
17486
|
auto: handleAuto,
|
|
17020
17487
|
permissions: handlePermissions,
|
|
17021
17488
|
yolo: handleYolo,
|
|
@@ -17028,6 +17495,7 @@ var webCommandHandlers = {
|
|
|
17028
17495
|
var SessionHandler = class {
|
|
17029
17496
|
ws;
|
|
17030
17497
|
config;
|
|
17498
|
+
costTracker;
|
|
17031
17499
|
providers;
|
|
17032
17500
|
sessions;
|
|
17033
17501
|
toolRegistry;
|
|
@@ -17041,12 +17509,22 @@ var SessionHandler = class {
|
|
|
17041
17509
|
runtimeThinkingEffort = null;
|
|
17042
17510
|
sessionTokenUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
|
|
17043
17511
|
unknownPricingWarnings = /* @__PURE__ */ new Set();
|
|
17512
|
+
/**
|
|
17513
|
+
* 本会话按**调用时刻**费率累计的花费(美元)。
|
|
17514
|
+
* 审计 P2-02 第二批:Web 端此前根本不给 CostTracker 记账,aicli usage 与
|
|
17515
|
+
* monthlyBudget 完全看不到 Web UI 的开销;顺带补上 P3-01 的记账口径。
|
|
17516
|
+
*/
|
|
17517
|
+
sessionAccruedCostUsd = 0;
|
|
17518
|
+
sessionCostIncomplete = false;
|
|
17044
17519
|
/** Accumulate a TokenUsage (with optional cache fields) into sessionTokenUsage. */
|
|
17045
17520
|
addWebSessionUsage(u) {
|
|
17046
17521
|
this.sessionTokenUsage.inputTokens += u.inputTokens;
|
|
17047
17522
|
this.sessionTokenUsage.outputTokens += u.outputTokens;
|
|
17048
17523
|
this.sessionTokenUsage.cacheCreationTokens += u.cacheCreationTokens ?? 0;
|
|
17049
17524
|
this.sessionTokenUsage.cacheReadTokens += u.cacheReadTokens ?? 0;
|
|
17525
|
+
const priced = this.costTracker.addCost(this.currentProvider, this.currentModel, u);
|
|
17526
|
+
if (priced === null) this.sessionCostIncomplete = true;
|
|
17527
|
+
else this.sessionAccruedCostUsd += priced;
|
|
17050
17528
|
const totalTokens = u.inputTokens + u.outputTokens + (u.cacheCreationTokens ?? 0) + (u.cacheReadTokens ?? 0);
|
|
17051
17529
|
if (totalTokens > 0 && !getPricing(this.currentProvider, this.currentModel)) {
|
|
17052
17530
|
const key = `${this.currentProvider}/${this.currentModel}`;
|
|
@@ -17058,6 +17536,8 @@ var SessionHandler = class {
|
|
|
17058
17536
|
}
|
|
17059
17537
|
resetWebSessionUsage() {
|
|
17060
17538
|
this.sessionTokenUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
|
|
17539
|
+
this.sessionAccruedCostUsd = 0;
|
|
17540
|
+
this.sessionCostIncomplete = false;
|
|
17061
17541
|
}
|
|
17062
17542
|
abortController = null;
|
|
17063
17543
|
userInterjection = null;
|
|
@@ -17091,6 +17571,7 @@ var SessionHandler = class {
|
|
|
17091
17571
|
constructor(ws, shared) {
|
|
17092
17572
|
this.ws = ws;
|
|
17093
17573
|
this.config = shared.config;
|
|
17574
|
+
this.costTracker = shared.costTracker;
|
|
17094
17575
|
this.providers = shared.providers;
|
|
17095
17576
|
this.sessions = shared.sessions;
|
|
17096
17577
|
this.toolRegistry = shared.toolRegistry;
|
|
@@ -17509,6 +17990,7 @@ var SessionHandler = class {
|
|
|
17509
17990
|
} else {
|
|
17510
17991
|
await this.handleChatSimple(provider, session.messages);
|
|
17511
17992
|
}
|
|
17993
|
+
await this.checkContextPressure();
|
|
17512
17994
|
} catch (err) {
|
|
17513
17995
|
const parts = [];
|
|
17514
17996
|
let current = err;
|
|
@@ -18027,10 +18509,11 @@ ${summaryContent}`,
|
|
|
18027
18509
|
}
|
|
18028
18510
|
// ── Commands ─────────────────────────────────────────────────────
|
|
18029
18511
|
async handleCommand(name, args) {
|
|
18030
|
-
|
|
18512
|
+
const cliOnlyReason = CLI_ONLY_WEB_COMMANDS.get(name);
|
|
18513
|
+
if (cliOnlyReason) {
|
|
18031
18514
|
this.send({
|
|
18032
18515
|
type: "error",
|
|
18033
|
-
message: `/${name} is
|
|
18516
|
+
message: `/${name} is not available in the Web UI. ${cliOnlyReason}`
|
|
18034
18517
|
});
|
|
18035
18518
|
return;
|
|
18036
18519
|
}
|
|
@@ -18041,6 +18524,25 @@ ${summaryContent}`,
|
|
|
18041
18524
|
this.send({ type: "error", message: `Unknown command: /${name}. Type /help for available commands.` });
|
|
18042
18525
|
}
|
|
18043
18526
|
}
|
|
18527
|
+
/**
|
|
18528
|
+
* 到达上下文压力阈值时自动压缩(与 REPL 同一判据,见 core/context-pressure.ts)。
|
|
18529
|
+
* 上下文窗口未知时不动——不基于猜测改用户的对话历史。
|
|
18530
|
+
*/
|
|
18531
|
+
async checkContextPressure() {
|
|
18532
|
+
const contextWindow = this.getContextWindowSize();
|
|
18533
|
+
const session = this.sessions.current;
|
|
18534
|
+
const pressure = evaluateContextPressure({
|
|
18535
|
+
autoCompactEnabled: this.config.get("autoCompact") === true,
|
|
18536
|
+
estimatedTokens: contextWindow > 0 && session ? estimateConversationTokens({ systemPrompt: this.activeSystemPrompt, messages: session.messages }) : 0,
|
|
18537
|
+
contextWindow
|
|
18538
|
+
});
|
|
18539
|
+
if (!pressure.shouldCompact) return;
|
|
18540
|
+
this.send({
|
|
18541
|
+
type: "info",
|
|
18542
|
+
message: `\u26A0 Context pressure: ~${pressure.percent}% of the ${contextWindow.toLocaleString()}-token window used. Auto-compacting\u2026`
|
|
18543
|
+
});
|
|
18544
|
+
await this.compactSession(AUTO_COMPACT_INSTRUCTION);
|
|
18545
|
+
}
|
|
18044
18546
|
async compactSession(instruction) {
|
|
18045
18547
|
const session = this.sessions.current;
|
|
18046
18548
|
if (!session || session.messages.length < 6) {
|
|
@@ -18527,8 +19029,8 @@ async function setupProxy(configProxy) {
|
|
|
18527
19029
|
}
|
|
18528
19030
|
|
|
18529
19031
|
// src/web/auth.ts
|
|
18530
|
-
import { existsSync as
|
|
18531
|
-
import { isAbsolute as isAbsolute3, join as
|
|
19032
|
+
import { existsSync as existsSync30, readFileSync as readFileSync24, writeFileSync as writeFileSync4, mkdirSync as mkdirSync15, readdirSync as readdirSync14, copyFileSync } from "fs";
|
|
19033
|
+
import { isAbsolute as isAbsolute3, join as join25, relative as relative5 } from "path";
|
|
18532
19034
|
import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
|
|
18533
19035
|
var USERS_FILE = "users.json";
|
|
18534
19036
|
var TOKEN_EXPIRY_HOURS = 24;
|
|
@@ -18543,7 +19045,7 @@ var AuthManager = class {
|
|
|
18543
19045
|
db;
|
|
18544
19046
|
constructor(baseDir) {
|
|
18545
19047
|
this.baseDir = baseDir;
|
|
18546
|
-
this.usersFile =
|
|
19048
|
+
this.usersFile = join25(baseDir, USERS_FILE);
|
|
18547
19049
|
this.db = this.loadOrCreate();
|
|
18548
19050
|
}
|
|
18549
19051
|
// ── Public API ─────────────────────────────────────────────────
|
|
@@ -18572,9 +19074,9 @@ var AuthManager = class {
|
|
|
18572
19074
|
}
|
|
18573
19075
|
const salt = randomBytes(16).toString("hex");
|
|
18574
19076
|
const passwordHash = this.hashPassword(password, salt);
|
|
18575
|
-
const dataDir =
|
|
18576
|
-
const fullDataDir =
|
|
18577
|
-
mkdirSync15(
|
|
19077
|
+
const dataDir = join25(USERS_DIR, username);
|
|
19078
|
+
const fullDataDir = join25(this.baseDir, dataDir);
|
|
19079
|
+
mkdirSync15(join25(fullDataDir, "history"), { recursive: true });
|
|
18578
19080
|
const user = {
|
|
18579
19081
|
username,
|
|
18580
19082
|
passwordHash,
|
|
@@ -18689,7 +19191,7 @@ var AuthManager = class {
|
|
|
18689
19191
|
getUserDataDir(username) {
|
|
18690
19192
|
const user = this.db.users.find((u) => u.username === username);
|
|
18691
19193
|
if (!user) throw new Error(`User not found: ${username}`);
|
|
18692
|
-
return
|
|
19194
|
+
return join25(this.baseDir, user.dataDir);
|
|
18693
19195
|
}
|
|
18694
19196
|
/** List all usernames */
|
|
18695
19197
|
listUsers() {
|
|
@@ -18725,30 +19227,30 @@ var AuthManager = class {
|
|
|
18725
19227
|
const err = this.register(username, password);
|
|
18726
19228
|
if (err) return err;
|
|
18727
19229
|
const userDir = this.getUserDataDir(username);
|
|
18728
|
-
const globalConfig =
|
|
18729
|
-
if (
|
|
19230
|
+
const globalConfig = join25(this.baseDir, "config.json");
|
|
19231
|
+
if (existsSync30(globalConfig)) {
|
|
18730
19232
|
try {
|
|
18731
|
-
const content =
|
|
18732
|
-
writeFileSync4(
|
|
19233
|
+
const content = readFileSync24(globalConfig, "utf-8");
|
|
19234
|
+
writeFileSync4(join25(userDir, "config.json"), content, "utf-8");
|
|
18733
19235
|
} catch {
|
|
18734
19236
|
}
|
|
18735
19237
|
}
|
|
18736
|
-
const globalMemory =
|
|
18737
|
-
if (
|
|
19238
|
+
const globalMemory = join25(this.baseDir, "memory.md");
|
|
19239
|
+
if (existsSync30(globalMemory)) {
|
|
18738
19240
|
try {
|
|
18739
|
-
const content =
|
|
18740
|
-
writeFileSync4(
|
|
19241
|
+
const content = readFileSync24(globalMemory, "utf-8");
|
|
19242
|
+
writeFileSync4(join25(userDir, "memory.md"), content, "utf-8");
|
|
18741
19243
|
} catch {
|
|
18742
19244
|
}
|
|
18743
19245
|
}
|
|
18744
|
-
const globalHistory =
|
|
18745
|
-
if (
|
|
19246
|
+
const globalHistory = join25(this.baseDir, "history");
|
|
19247
|
+
if (existsSync30(globalHistory)) {
|
|
18746
19248
|
try {
|
|
18747
19249
|
const files = readdirSync14(globalHistory).filter((f) => f.endsWith(".json"));
|
|
18748
|
-
const userHistory =
|
|
19250
|
+
const userHistory = join25(userDir, "history");
|
|
18749
19251
|
for (const f of files) {
|
|
18750
19252
|
try {
|
|
18751
|
-
copyFileSync(
|
|
19253
|
+
copyFileSync(join25(globalHistory, f), join25(userHistory, f));
|
|
18752
19254
|
} catch {
|
|
18753
19255
|
}
|
|
18754
19256
|
}
|
|
@@ -18759,9 +19261,9 @@ var AuthManager = class {
|
|
|
18759
19261
|
}
|
|
18760
19262
|
// ── Private methods ────────────────────────────────────────────
|
|
18761
19263
|
loadOrCreate() {
|
|
18762
|
-
if (
|
|
19264
|
+
if (existsSync30(this.usersFile)) {
|
|
18763
19265
|
try {
|
|
18764
|
-
const parsed = JSON.parse(
|
|
19266
|
+
const parsed = JSON.parse(readFileSync24(this.usersFile, "utf-8"));
|
|
18765
19267
|
if (!this.isValidUsersDB(parsed)) {
|
|
18766
19268
|
throw new Error("database structure is invalid");
|
|
18767
19269
|
}
|
|
@@ -18795,7 +19297,7 @@ var AuthManager = class {
|
|
|
18795
19297
|
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)) {
|
|
18796
19298
|
return false;
|
|
18797
19299
|
}
|
|
18798
|
-
const dataDirFromBase = relative5(this.baseDir,
|
|
19300
|
+
const dataDirFromBase = relative5(this.baseDir, join25(this.baseDir, record.dataDir));
|
|
18799
19301
|
if (dataDirFromBase.startsWith("..") || isAbsolute3(dataDirFromBase)) return false;
|
|
18800
19302
|
if (record.hashVersion !== void 0 && record.hashVersion !== 1 && record.hashVersion !== 2) return false;
|
|
18801
19303
|
if (record.tokensRevokedBefore !== void 0 && !Number.isFinite(record.tokensRevokedBefore)) return false;
|
|
@@ -18911,8 +19413,8 @@ async function startWebServer(options = {}) {
|
|
|
18911
19413
|
}
|
|
18912
19414
|
}
|
|
18913
19415
|
let skillManager = null;
|
|
18914
|
-
const skillsDir =
|
|
18915
|
-
if (
|
|
19416
|
+
const skillsDir = join26(config.getConfigDir(), SKILLS_DIR_NAME);
|
|
19417
|
+
if (existsSync31(skillsDir)) {
|
|
18916
19418
|
skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
|
|
18917
19419
|
skillManager.loadSkills(pluginAssets.skillDirs);
|
|
18918
19420
|
const count = skillManager.listSkills().length;
|
|
@@ -18922,6 +19424,7 @@ async function startWebServer(options = {}) {
|
|
|
18922
19424
|
}
|
|
18923
19425
|
const shared = {
|
|
18924
19426
|
config,
|
|
19427
|
+
costTracker: new CostTracker(config.getConfigDir()),
|
|
18925
19428
|
providers,
|
|
18926
19429
|
sessions,
|
|
18927
19430
|
toolRegistry,
|
|
@@ -18987,18 +19490,18 @@ async function startWebServer(options = {}) {
|
|
|
18987
19490
|
next();
|
|
18988
19491
|
};
|
|
18989
19492
|
const moduleDir = getModuleDir();
|
|
18990
|
-
let clientDir =
|
|
18991
|
-
if (!
|
|
18992
|
-
clientDir =
|
|
19493
|
+
let clientDir = join26(moduleDir, "web", "client");
|
|
19494
|
+
if (!existsSync31(clientDir)) {
|
|
19495
|
+
clientDir = join26(moduleDir, "client");
|
|
18993
19496
|
}
|
|
18994
|
-
if (!
|
|
18995
|
-
clientDir =
|
|
19497
|
+
if (!existsSync31(clientDir)) {
|
|
19498
|
+
clientDir = join26(moduleDir, "..", "..", "src", "web", "client");
|
|
18996
19499
|
}
|
|
18997
|
-
if (!
|
|
18998
|
-
clientDir =
|
|
19500
|
+
if (!existsSync31(clientDir)) {
|
|
19501
|
+
clientDir = join26(process.cwd(), "src", "web", "client");
|
|
18999
19502
|
}
|
|
19000
19503
|
console.log(` Static files: ${clientDir}`);
|
|
19001
|
-
app.use("/vendor", express.static(
|
|
19504
|
+
app.use("/vendor", express.static(join26(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
|
|
19002
19505
|
app.use(express.static(clientDir));
|
|
19003
19506
|
app.get("/api/status", (_req, res) => {
|
|
19004
19507
|
res.json({
|
|
@@ -19065,7 +19568,7 @@ async function startWebServer(options = {}) {
|
|
|
19065
19568
|
app.get("/api/files", requireAuth, (req, res) => {
|
|
19066
19569
|
const cwd = process.cwd();
|
|
19067
19570
|
const prefix = req.query.prefix || "";
|
|
19068
|
-
const targetDir =
|
|
19571
|
+
const targetDir = join26(cwd, prefix);
|
|
19069
19572
|
try {
|
|
19070
19573
|
const canonicalTarget = realpathSync3(resolve10(targetDir));
|
|
19071
19574
|
const canonicalCwd = realpathSync3(resolve10(cwd));
|
|
@@ -19082,7 +19585,7 @@ async function startWebServer(options = {}) {
|
|
|
19082
19585
|
const entries = readdirSync15(targetDir, { withFileTypes: true });
|
|
19083
19586
|
const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
|
|
19084
19587
|
name: e.name,
|
|
19085
|
-
path: relative6(cwd,
|
|
19588
|
+
path: relative6(cwd, join26(targetDir, e.name)).replace(/\\/g, "/"),
|
|
19086
19589
|
isDir: e.isDirectory()
|
|
19087
19590
|
}));
|
|
19088
19591
|
res.json({ files });
|
|
@@ -19122,8 +19625,8 @@ async function startWebServer(options = {}) {
|
|
|
19122
19625
|
try {
|
|
19123
19626
|
const authUser = req._authUser;
|
|
19124
19627
|
const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
|
|
19125
|
-
const filePath =
|
|
19126
|
-
if (!
|
|
19628
|
+
const filePath = join26(histDir, `${id}.json`);
|
|
19629
|
+
if (!existsSync31(filePath)) {
|
|
19127
19630
|
res.status(404).json({ error: "Session not found" });
|
|
19128
19631
|
return;
|
|
19129
19632
|
}
|
|
@@ -19138,7 +19641,7 @@ async function startWebServer(options = {}) {
|
|
|
19138
19641
|
res.status(404).json({ error: "Session not found" });
|
|
19139
19642
|
return;
|
|
19140
19643
|
}
|
|
19141
|
-
const data = JSON.parse(
|
|
19644
|
+
const data = JSON.parse(readFileSync25(filePath, "utf-8"));
|
|
19142
19645
|
res.json({ session: data });
|
|
19143
19646
|
} catch (err) {
|
|
19144
19647
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
@@ -19151,7 +19654,7 @@ async function startWebServer(options = {}) {
|
|
|
19151
19654
|
return;
|
|
19152
19655
|
}
|
|
19153
19656
|
const cwd = process.cwd();
|
|
19154
|
-
const fullPath = resolve10(
|
|
19657
|
+
const fullPath = resolve10(join26(cwd, filePath));
|
|
19155
19658
|
try {
|
|
19156
19659
|
const canonicalFull = realpathSync3(fullPath);
|
|
19157
19660
|
const canonicalCwd = realpathSync3(resolve10(cwd));
|
|
@@ -19169,7 +19672,7 @@ async function startWebServer(options = {}) {
|
|
|
19169
19672
|
res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
|
|
19170
19673
|
return;
|
|
19171
19674
|
}
|
|
19172
|
-
const content =
|
|
19675
|
+
const content = readFileSync25(fullPath, "utf-8");
|
|
19173
19676
|
res.json({ content, size: stat.size });
|
|
19174
19677
|
} catch {
|
|
19175
19678
|
res.json({ error: "Cannot read file" });
|
|
@@ -19188,6 +19691,8 @@ async function startWebServer(options = {}) {
|
|
|
19188
19691
|
const userSessions = new SessionManager(userConfig);
|
|
19189
19692
|
const userShared = {
|
|
19190
19693
|
config: userConfig,
|
|
19694
|
+
// 每用户各自的 config 目录 → 各自的 cost-history.json
|
|
19695
|
+
costTracker: new CostTracker(userConfig.getConfigDir()),
|
|
19191
19696
|
providers,
|
|
19192
19697
|
// Shared — providers re-read API keys per call
|
|
19193
19698
|
sessions: userSessions,
|
|
@@ -19424,17 +19929,17 @@ function resolveProjectMcpPath() {
|
|
|
19424
19929
|
const cwd = process.cwd();
|
|
19425
19930
|
const gitRoot = getGitRoot(cwd);
|
|
19426
19931
|
const projectRoot = gitRoot ?? cwd;
|
|
19427
|
-
const configPath =
|
|
19428
|
-
return
|
|
19932
|
+
const configPath = join26(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
19933
|
+
return existsSync31(configPath) ? configPath : null;
|
|
19429
19934
|
}
|
|
19430
19935
|
function loadProjectMcpConfig() {
|
|
19431
19936
|
const cwd = process.cwd();
|
|
19432
19937
|
const gitRoot = getGitRoot(cwd);
|
|
19433
19938
|
const projectRoot = gitRoot ?? cwd;
|
|
19434
|
-
const configPath =
|
|
19435
|
-
if (!
|
|
19939
|
+
const configPath = join26(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
19940
|
+
if (!existsSync31(configPath)) return null;
|
|
19436
19941
|
try {
|
|
19437
|
-
const raw = JSON.parse(
|
|
19942
|
+
const raw = JSON.parse(readFileSync25(configPath, "utf-8"));
|
|
19438
19943
|
return raw.mcpServers ?? raw;
|
|
19439
19944
|
} catch {
|
|
19440
19945
|
return null;
|