fluxflow-cli 3.15.2 → 3.16.1

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/fluxflow.js CHANGED
@@ -52,13 +52,14 @@ __export(paths_exports, {
52
52
  SETTINGS_FILE: () => SETTINGS_FILE,
53
53
  TEMP_MEM_CHAT_FILE: () => TEMP_MEM_CHAT_FILE,
54
54
  TEMP_MEM_FILE: () => TEMP_MEM_FILE,
55
- USAGE_FILE: () => USAGE_FILE
55
+ USAGE_FILE: () => USAGE_FILE,
56
+ USAGE_FILE_OLD: () => USAGE_FILE_OLD
56
57
  });
57
58
  import os from "os";
58
59
  import path from "path";
59
60
  import fs from "fs";
60
61
  import crypto from "crypto";
61
- var FLUXFLOW_DIR, SETTINGS_FILE, externalDir, DATA_DIR, LOGS_DIR, SECRET_DIR, HISTORY_FILE, HISTORY_DIR, USAGE_FILE, MEMORIES_FILE, TEMP_MEM_FILE, TEMP_MEM_CHAT_FILE, BACKUPS_DIR, LEDGER_FILE, LEDGER_ADVANCE_FILE, ACTIVE_TX_FILE, PATHS_FILE, CONTEXT_FILE, PARSER_DIR;
62
+ var FLUXFLOW_DIR, SETTINGS_FILE, externalDir, DATA_DIR, LOGS_DIR, SECRET_DIR, HISTORY_FILE, HISTORY_DIR, USAGE_FILE_OLD, USAGE_FILE, MEMORIES_FILE, TEMP_MEM_FILE, TEMP_MEM_CHAT_FILE, BACKUPS_DIR, LEDGER_FILE, LEDGER_ADVANCE_FILE, ACTIVE_TX_FILE, PATHS_FILE, CONTEXT_FILE, PARSER_DIR;
62
63
  var init_paths = __esm({
63
64
  "src/utils/paths.js"() {
64
65
  FLUXFLOW_DIR = path.join(os.homedir(), ".fluxflow");
@@ -96,7 +97,8 @@ var init_paths = __esm({
96
97
  SECRET_DIR = path.join(DATA_DIR, "secret");
97
98
  HISTORY_FILE = path.join(SECRET_DIR, "history.json");
98
99
  HISTORY_DIR = path.join(SECRET_DIR, "history");
99
- USAGE_FILE = path.join(FLUXFLOW_DIR, "usage.json");
100
+ USAGE_FILE_OLD = path.join(FLUXFLOW_DIR, "usage.json");
101
+ USAGE_FILE = path.join(SECRET_DIR, "usage.json");
100
102
  MEMORIES_FILE = path.join(SECRET_DIR, "memories.json");
101
103
  TEMP_MEM_FILE = path.join(SECRET_DIR, "memory-temp.json");
102
104
  TEMP_MEM_CHAT_FILE = path.join(SECRET_DIR, "temp-memory-chat.json");
@@ -780,6 +782,9 @@ var init_settings = __esm({
780
782
  progressiveRendering: true,
781
783
  showTPMEstimate: false,
782
784
  subAgents: true,
785
+ CustomSubAgent: false,
786
+ SubAgentModel: "Default",
787
+ SubAgentProvider: "",
783
788
  dynamicDirAwareness: false,
784
789
  indentationTree: true
785
790
  },
@@ -5675,7 +5680,7 @@ var init_ChatLayout = __esm({
5675
5680
  return;
5676
5681
  }
5677
5682
  if (trimmed === "---" || trimmed === "***" || trimmed === "___") {
5678
- result.push(/* @__PURE__ */ React4.createElement(Box3, { key: i, marginY: 1, borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, width: "100%", borderColor: colors.borderMuted }));
5683
+ result.push(/* @__PURE__ */ React4.createElement(Box3, { key: i, marginY: 0, borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, width: "100%", borderColor: colors.borderMuted }));
5679
5684
  return;
5680
5685
  }
5681
5686
  const headingMatch = trimmed.match(/^(#{1,6})\s+(.*)/);
@@ -5740,37 +5745,138 @@ var init_ChatLayout = __esm({
5740
5745
  const displayPrefix = isRemoval ? "-" : isAddition ? "+" : " ";
5741
5746
  const renderInlineDiff = () => {
5742
5747
  if (isPureUnpairedBlock) {
5743
- const blockColor = isRemoval ? colors.diffRemovalHighlightColor : colors.diffAdditionHighlightColor;
5744
5748
  const textBgColor = isRemoval ? colors.diffRemovalHighlightBg : colors.diffAdditionHighlightBg;
5745
- const wrappedLines = wrapText(content, columns - 15).split("\n");
5746
- return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column" }, wrappedLines.map((wl, idx) => /* @__PURE__ */ React4.createElement(Box3, { key: idx }, renderHighlightedLine(wl, extension, blockColor, textBgColor))));
5749
+ return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column" }, renderHighlightedLine(wrapText(content, columns - 15), extension, void 0, textBgColor));
5747
5750
  }
5748
5751
  if (!(isRemoval || isAddition) || words.length === 0 || !hasInlineChange) {
5749
5752
  const textColor = isRemoval ? colors.diffRemovalText : isAddition ? colors.diffAdditionText : colors.textMuted;
5750
5753
  const textBgColor = void 0;
5751
- const wrappedLines = wrapText(content, columns - 15).split("\n");
5752
- return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column" }, wrappedLines.map((wl, idx) => /* @__PURE__ */ React4.createElement(Box3, { key: idx }, renderHighlightedLine(wl, extension, textColor, textBgColor))));
5754
+ return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column" }, renderHighlightedLine(wrapText(content, columns - 15), extension, textColor, textBgColor));
5753
5755
  }
5754
- return /* @__PURE__ */ React4.createElement(Text4, { wrap: "anywhere" }, words.map((part, idx) => {
5756
+ const maxLen = Math.max(10, columns - 15);
5757
+ const wrappedLines = wrapText(content, maxLen).split("\n");
5758
+ const validWords = [];
5759
+ words.forEach((part, idx) => {
5755
5760
  const isWhitespace = /^\s+$/.test(part.value);
5756
5761
  if (isRemoval) {
5757
5762
  const isSurroundedByRemoval = words[idx - 1]?.removed || words[idx + 1]?.removed;
5758
5763
  if (part.removed || isWhitespace && isSurroundedByRemoval) {
5759
- return /* @__PURE__ */ React4.createElement(Text4, { key: idx, color: colors.diffRemovalHighlightColor, backgroundColor: colors.diffRemovalHighlightBg }, part.value);
5764
+ validWords.push({ text: part.value, isHighlight: true });
5765
+ } else if (!part.added) {
5766
+ validWords.push({ text: part.value, isHighlight: false });
5760
5767
  }
5761
- if (part.added) return null;
5762
- return /* @__PURE__ */ React4.createElement(Text4, { key: idx, color: colors.diffRemovalText }, part.value);
5763
- }
5764
- if (isAddition) {
5768
+ } else if (isAddition) {
5765
5769
  const isSurroundedByAddition = words[idx - 1]?.added || words[idx + 1]?.added;
5766
5770
  if (part.added || isWhitespace && isSurroundedByAddition) {
5767
- return /* @__PURE__ */ React4.createElement(Text4, { key: idx, color: colors.diffAdditionHighlightColor, backgroundColor: colors.diffAdditionHighlightBg }, part.value);
5771
+ validWords.push({ text: part.value, isHighlight: true });
5772
+ } else if (!part.removed) {
5773
+ validWords.push({ text: part.value, isHighlight: false });
5768
5774
  }
5769
- if (part.removed) return null;
5770
- return /* @__PURE__ */ React4.createElement(Text4, { key: idx, color: colors.diffAdditionText }, part.value);
5771
5775
  }
5772
- return /* @__PURE__ */ React4.createElement(Text4, { key: idx, color: colors.textMuted }, part.value);
5773
- }));
5776
+ });
5777
+ if (wrappedLines.length <= 1) {
5778
+ return /* @__PURE__ */ React4.createElement(Text4, { wrap: "wrap" }, validWords.map((part, idx) => {
5779
+ if (isRemoval) {
5780
+ if (part.isHighlight) {
5781
+ return /* @__PURE__ */ React4.createElement(React4.Fragment, { key: idx }, renderHighlightedLine(part.text, extension, colors.diffRemovalHighlightColor, colors.diffRemovalHighlightBg));
5782
+ }
5783
+ return /* @__PURE__ */ React4.createElement(React4.Fragment, { key: idx }, renderHighlightedLine(part.text, extension, colors.diffRemovalText));
5784
+ }
5785
+ if (isAddition) {
5786
+ if (part.isHighlight) {
5787
+ return /* @__PURE__ */ React4.createElement(React4.Fragment, { key: idx }, renderHighlightedLine(part.text, extension, colors.diffAdditionHighlightColor, colors.diffAdditionHighlightBg));
5788
+ }
5789
+ return /* @__PURE__ */ React4.createElement(React4.Fragment, { key: idx }, renderHighlightedLine(part.text, extension, colors.diffAdditionText));
5790
+ }
5791
+ return /* @__PURE__ */ React4.createElement(React4.Fragment, { key: idx }, renderHighlightedLine(part.text, extension, colors.textMuted));
5792
+ }));
5793
+ }
5794
+ let wordIdx = 0;
5795
+ let charIdx = 0;
5796
+ const leadingSpaceMatch = content.match(/^(\s*)/);
5797
+ const indent = leadingSpaceMatch ? leadingSpaceMatch[1] : "";
5798
+ const cappedIndent = indent.substring(0, Math.min(indent.length, 8));
5799
+ const lineSpans = wrappedLines.map((wl, lineIdx) => {
5800
+ const spans = [];
5801
+ let lineTextToMatch = wl;
5802
+ if (lineIdx > 0 && cappedIndent && wl.startsWith(cappedIndent)) {
5803
+ const currentAvail = validWords[wordIdx] ? validWords[wordIdx].text.substring(charIdx) : "";
5804
+ if (!currentAvail.startsWith(cappedIndent)) {
5805
+ spans.push({ text: cappedIndent, isHighlight: false });
5806
+ lineTextToMatch = wl.substring(cappedIndent.length);
5807
+ }
5808
+ }
5809
+ let neededLength = lineTextToMatch.length;
5810
+ while (neededLength > 0 && wordIdx < validWords.length) {
5811
+ const vw = validWords[wordIdx];
5812
+ const avail = vw.text.length - charIdx;
5813
+ if (avail <= 0) {
5814
+ wordIdx++;
5815
+ charIdx = 0;
5816
+ continue;
5817
+ }
5818
+ const takeLen = Math.min(neededLength, avail);
5819
+ spans.push({
5820
+ text: vw.text.substring(charIdx, charIdx + takeLen),
5821
+ isHighlight: vw.isHighlight
5822
+ });
5823
+ charIdx += takeLen;
5824
+ neededLength -= takeLen;
5825
+ if (charIdx >= vw.text.length) {
5826
+ wordIdx++;
5827
+ charIdx = 0;
5828
+ }
5829
+ }
5830
+ while (wordIdx < validWords.length) {
5831
+ const vw = validWords[wordIdx];
5832
+ const rem = vw.text.substring(charIdx);
5833
+ if (/^\s+$/.test(rem)) {
5834
+ wordIdx++;
5835
+ charIdx = 0;
5836
+ } else if (rem.startsWith(" ") || rem.startsWith(" ")) {
5837
+ let skipCount = 0;
5838
+ while (skipCount < rem.length && (rem[skipCount] === " " || rem[skipCount] === " ")) {
5839
+ skipCount++;
5840
+ }
5841
+ charIdx += skipCount;
5842
+ if (charIdx >= vw.text.length) {
5843
+ wordIdx++;
5844
+ charIdx = 0;
5845
+ }
5846
+ break;
5847
+ } else {
5848
+ break;
5849
+ }
5850
+ }
5851
+ return spans;
5852
+ });
5853
+ if (wordIdx < validWords.length) {
5854
+ const lastSpans = lineSpans[lineSpans.length - 1];
5855
+ while (wordIdx < validWords.length) {
5856
+ const vw = validWords[wordIdx];
5857
+ lastSpans.push({
5858
+ text: vw.text.substring(charIdx),
5859
+ isHighlight: vw.isHighlight
5860
+ });
5861
+ wordIdx++;
5862
+ charIdx = 0;
5863
+ }
5864
+ }
5865
+ return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column" }, lineSpans.map((spans, lIdx) => /* @__PURE__ */ React4.createElement(Box3, { key: lIdx }, /* @__PURE__ */ React4.createElement(Text4, { wrap: "wrap" }, spans.map((part, sIdx) => {
5866
+ if (isRemoval) {
5867
+ if (part.isHighlight) {
5868
+ return /* @__PURE__ */ React4.createElement(React4.Fragment, { key: sIdx }, renderHighlightedLine(part.text, extension, colors.diffRemovalHighlightColor, colors.diffRemovalHighlightBg));
5869
+ }
5870
+ return /* @__PURE__ */ React4.createElement(React4.Fragment, { key: sIdx }, renderHighlightedLine(part.text, extension, colors.diffRemovalText));
5871
+ }
5872
+ if (isAddition) {
5873
+ if (part.isHighlight) {
5874
+ return /* @__PURE__ */ React4.createElement(React4.Fragment, { key: sIdx }, renderHighlightedLine(part.text, extension, colors.diffAdditionHighlightColor, colors.diffAdditionHighlightBg));
5875
+ }
5876
+ return /* @__PURE__ */ React4.createElement(React4.Fragment, { key: sIdx }, renderHighlightedLine(part.text, extension, colors.diffAdditionText));
5877
+ }
5878
+ return /* @__PURE__ */ React4.createElement(React4.Fragment, { key: sIdx }, renderHighlightedLine(part.text, extension, colors.textMuted));
5879
+ })))));
5774
5880
  };
5775
5881
  return /* @__PURE__ */ React4.createElement(Box3, { backgroundColor: colors.codeBg, paddingX: 1, width: columns }, /* @__PURE__ */ React4.createElement(Box3, { width: 4, flexShrink: 0, justifyContent: "flex-end" }, /* @__PURE__ */ React4.createElement(Text4, { color: finalNumColor }, lineNum)), /* @__PURE__ */ React4.createElement(Box3, { width: 1, flexShrink: 0, marginLeft: 1 }, /* @__PURE__ */ React4.createElement(Text4, { color: finalPrefixColor }, displayPrefix)), /* @__PURE__ */ React4.createElement(Box3, { marginLeft: 1, backgroundColor: innerBgColor, flexGrow: 1 }, renderInlineDiff()));
5776
5882
  });
@@ -6704,10 +6810,10 @@ var init_main_tools = __esm({
6704
6810
  Tool calls: ONLY use [tool:functions.ToolName(args)]
6705
6811
  **NO OTHER SYNTAX/MARKERS/BOUNDARY ALLOWED**
6706
6812
 
6707
- **TOOL USAGE POLICY:**
6813
+ **CRITICAL TOOL USAGE RULES:**
6708
6814
  - MAX 4 TOOL CALLS/TURN${mode === "Flux" ? " (Todo: 4+, Run: max 1 or 2 consecutive)" : ""}
6709
- ${mode === "Flux" ? '- **Escape quotes: \\" for code strings **\n- ** Literal escapes: Double - escape sequences(e.g., \\\\n) **\n- ** File structure: Real newlines for code formatting**\n- Same file, many edits? Prefer multi search-replace in Patch \u2190 **HIGHLY RECOMMENDED**\n- Tool denied?Use `Ask` immediately for user guidance \u2190 ** MANDATORY **\n- FileMap > ReadFile for efficient file understanding\n- Need specific text ? SearchKeyword > Guessing/ReadFile\n- Huge files ? SearchKeyword > Full Read\n- **Update Todos from realtime progress EVERY TURN**\n' : ""}
6710
- - COMMUNICATION TOOLS -
6815
+ ${mode === "Flux" ? '- **Escape quotes: \\" for code strings **\n- ** Literal escapes: Double - escape sequences(e.g., \\\\n) **\n- ** File structure: Real newlines for code formatting**\n- SAME file, MULTIPLE edits? Use ONE PatchFile call with up to 15 blocks \u2190 **PRIORITY**\n- Tool denied? Use `Ask` immediately for user guidance \u2190 **MANDATORY**\n- Need specific text ? SearchKeyword > Guessing/ReadFile\n- Huge files ? SearchKeyword > Full Read\n- **Update Todos from realtime progress every turn when created**\n' : ""}
6816
+ - COMMUNICATION WITH USER -
6711
6817
  - [tool:functions.Ask(question="...", optionA="option::description", ...MAX 4)]. Ambiguity: MUST for path divergence, security risk. Ask, don't finish/guess. Suggest best options; no preferences. Keep options short
6712
6818
 
6713
6819
  - WEB TOOLS -
@@ -6716,13 +6822,12 @@ ${mode === "Flux" ? '- **Escape quotes: \\" for code strings **\n- ** Literal es
6716
6822
 
6717
6823
  ${mode === "Flux" ? `- WORKSPACE TOOLS (path = relative; FIRST ARGUMENT, path separator: '/') -
6718
6824
  - [tool:functions.ReadFile(path="...", startLine="integer", endLine="integer")]. ${aiProvider !== "Google" ? `${isMultiModal ? `Supports images/docs` : ""}` : `Supports images/docs`}
6719
- - [tool:functions.ReadFolder(path="...", recurse="integer 0-4 optional, default: 0")]. Detailed DIR stats & metadata
6720
- - [tool:functions.FileMap(path="...")]. Shows file's code structure
6721
- - [tool:functions.PatchFile(path="...", allowMultiple="bool optional, default: false", replaceContent1="...", newContent1="...", ...MAX 15)]. Surgical patchs, TARGET SMALLEST LINES/SUB-STRINGS. allowMultiple: Replace all matches. Use replaceContent2/newContent2... for multi blocks. Verify DIFFs
6825
+ - [tool:functions.ReadFolder(path="...", recurse="integer 1-3 optional, default: 1")]. DIR Contents + File Size. Minimize recursion
6826
+ - [tool:functions.PatchFile(path="...", allowMultiple="bool optional, default: false", replaceContent1="...", newContent1="...", ...MAX 15)]. Surgical patchs, TARGET SMALLEST LINES. allowMultiple: Replace all matches ONLY WHEN SURE. Use replaceContent2/newContent2... for multi blocks. Verify DIFFs
6722
6827
  - [tool:functions.WriteFile(path="...", content="...")]. Creates/Overwrites. File Exist? PatchFile > WriteFile
6723
- - [tool:functions.SearchKeyword(keyword="...", path="optional, target directory/filename", subString="bool optional, default: false", regex="bool optional, default: auto")]. path limits scope to a file/dir. Find definitions/logic without full reads. Locate relevant code
6828
+ - [tool:functions.SearchKeyword(keyword="...", path="optional, dir/file/glob/regex", fuzzy="bool optional, default: false", regex="bool optional, default: auto")]. path limits search scope. Find definitions/logic without full reads. Locate relevant code
6724
6829
  - [tool:functions.Run(command="...")]. Runs ${osDetected === "Windows" ? isPsAvailable() ? `WINDOWS POWERSHELL` : `WINDOWS CMD` : `BASH`} command. Destructive/Irreversible ops \u2192 Ask user
6725
- - [tool:functions.Todo(method="create/append/get", tasks=[ARRAY OF STRINGS], markDone=[ARRAY OF TASKS])]. Task list, no Markdown in arrays. Analyze request: if long multi-task, break it down & create Todos BEFORE starting. \`tasks\` & \`markDone\` optional with \`get\`. Use \`get + markDone\` to complete tasks, or \`create + markDone\` to create completed tasks. **UPDATE EVERY TURN**${enableSubAgents ? '\n- [tool:functions.Await(time="integer 15-180")]. For waiting without exiting agent loop' : ""}
6830
+ - [tool:functions.Todo(method="create/append/get", tasks=[ARRAY OF STRINGS], markDone=[ARRAY OF TASKS])]. Task list, no Markdown in arrays. Analyze request: ONLY if long multi-task, break it down & create Todos BEFORE starting. \`tasks\` & \`markDone\` optional with \`get\`. Use \`get + markDone\` to complete tasks. **UPDATE EVERY TURN WHEN CREATED**${enableSubAgents ? '\n- [tool:functions.Await(time="integer 15-180")]. For waiting without exiting agent loop' : ""}
6726
6831
  ${_cachedAdvanceRollback ? `
6727
6832
  - EMERGENCY SAFETY TOOLS -
6728
6833
  Info: \`initial\` = user prompt for current task. Revert \`id\` = turn BEFORE the disaster tool (e.g. disaster:\`turn_3\` \u2192 revert:\`turn_2\`). Reason explicitly
@@ -6732,10 +6837,10 @@ Use ONLY for catastrophic/codebase corruption. Before ending loop, verify no cat
6732
6837
  - SUB AGENT TOOLS -
6733
6838
  **PROACTIVE sub-agent use HIGHLY RECOMMENDED. Prefer for any task with even slight benefit, no user nudge needed**
6734
6839
  Invocations:
6735
- - Invoke (async/background, \u22647 parallel). Parallelize long tasks. NEVER repeat while active
6840
+ - Invoke (async/background, \u22647 parallel). Parallelize long tasks. NEVER repeat while active, meantime, do your OWN work
6736
6841
  - InvokeSync (sync/blocking). Sequential, repetitive or delegated tasks. Saves tokens/cost
6737
6842
  - [agent:generalist.InvokeSync/Invoke(title="...", task="...")]. Task must be detailed: exact file paths, imports/exports, dependencies & folder structure
6738
- - [agent:generalist.GetProgress(id="...")]. Check async task progress. If still running, continue your work. Wait exponentially longer between checks
6843
+ - [agent:generalist.GetProgress(id="...")]. Poll \`getProgress\` sparingly (exp backoff Await); **NO IMMEDIATE FIRST POLL**
6739
6844
  - [agent:generalist.Cancel(id="...")]. Cancel async task ONLY if stalled (2m+) or clearly incorrect` : ""}`.trim() : `- CREATIVE TOOLS (path = relative to CWD & WILL BE FIRST ARGUMENT, path separator: '/') -
6740
6845
  - [tool:functions.WritePDF(path="...", content="...", orientation="...")]. PROACTIVE A4 PAGE BREAKS MUST IN CSS. HTML/CSS for PREMIUM layout, stable margins & headers/footers, NO WATERMARKS
6741
6846
  - [tool:functions.WriteDoc(path="...", content="...")]. A4 Word document, NO WATERMARKS, stable margins & headers/footers
@@ -7448,6 +7553,104 @@ function SettingsMenu({
7448
7553
  const [isSelectingTheme, setIsSelectingTheme] = useState6(initialSelectingTheme);
7449
7554
  const [themeIndex, setThemeIndex] = useState6(defaultIdx >= 0 ? defaultIdx : 0);
7450
7555
  const [initialTheme, setInitialTheme] = useState6(systemSettings.theme || "Dark");
7556
+ const [isSelectingSubAgentModel, setIsSelectingSubAgentModel] = useState6(false);
7557
+ const [subAgentModelIndex, setSubAgentModelIndex] = useState6(0);
7558
+ const [subAgentScrollOffset, setSubAgentScrollOffset] = useState6(0);
7559
+ const [subAgentSearchQuery, setSubAgentSearchQuery] = useState6("");
7560
+ const [subAgentFocusMode, setSubAgentFocusMode] = useState6("list");
7561
+ const [activeProviderKeys, setActiveProviderKeys] = useState6({});
7562
+ useEffect5(() => {
7563
+ const checkKeys = async () => {
7564
+ const providers = ["Google", "DeepSeek", "OpenRouter", "NVIDIA", "Mistral"];
7565
+ const keyMap = {};
7566
+ for (const p of providers) {
7567
+ try {
7568
+ const k = await getProviderAPIKey(p);
7569
+ if (k) keyMap[p] = true;
7570
+ } catch (e) {
7571
+ }
7572
+ }
7573
+ setActiveProviderKeys(keyMap);
7574
+ };
7575
+ checkKeys();
7576
+ }, []);
7577
+ const allSubAgentItems = React7.useMemo(() => {
7578
+ const ALL_PROVIDERS = ["Google", "DeepSeek", "OpenRouter", "NVIDIA", "Mistral"];
7579
+ const hasEnv = !!(process.env.SUBAGENT_MODEL && process.env.SUBAGENT_MODEL.trim());
7580
+ const envLabel = hasEnv ? `ENV (${process.env.SUBAGENT_MODEL.trim()})` : "ENV";
7581
+ const items = [
7582
+ { label: "Default (use the current model)", value: "Default", isHeader: false },
7583
+ { label: envLabel, value: "ENV", isHeader: false }
7584
+ ];
7585
+ const activeTier = quotas?.providerTiers?.[aiProvider] || apiTier || "Free";
7586
+ const currentModels = getModels(aiProvider, activeTier) || [];
7587
+ if (currentModels.length > 0) {
7588
+ items.push({ label: `\u2500\u2500 ${aiProvider.toUpperCase()}${activeTier !== "Free" ? ` (${activeTier})` : ""} \u2500\u2500`, isHeader: true });
7589
+ currentModels.forEach((m) => {
7590
+ const name = typeof m === "string" ? m : m.cmd || m.name || m.id || String(m);
7591
+ if (name && !name.trim().startsWith("---") && !name.startsWith("\n---")) {
7592
+ items.push({ label: name, value: name, isHeader: false, provider: aiProvider });
7593
+ }
7594
+ });
7595
+ }
7596
+ for (const p of ALL_PROVIDERS) {
7597
+ if (p === aiProvider) continue;
7598
+ if (activeProviderKeys[p]) {
7599
+ const tier = quotas?.providerTiers?.[p] || "Free";
7600
+ const models = getModels(p, tier) || [];
7601
+ if (models.length > 0) {
7602
+ items.push({ label: `\u2500\u2500 ${p.toUpperCase()}${tier !== "Free" ? ` (${tier})` : ""} \u2500\u2500`, isHeader: true });
7603
+ models.forEach((m) => {
7604
+ const name = typeof m === "string" ? m : m.cmd || m.name || m.id || String(m);
7605
+ if (name && !name.trim().startsWith("---") && !name.startsWith("\n---")) {
7606
+ items.push({ label: name, value: name, isHeader: false, provider: p });
7607
+ }
7608
+ });
7609
+ }
7610
+ }
7611
+ }
7612
+ return items;
7613
+ }, [aiProvider, apiTier, quotas, activeProviderKeys]);
7614
+ const availableModels = React7.useMemo(() => {
7615
+ if (!subAgentSearchQuery.trim()) return allSubAgentItems;
7616
+ const q = subAgentSearchQuery.trim().toLowerCase();
7617
+ const filtered = [];
7618
+ let currentHeader = null;
7619
+ for (const item of allSubAgentItems) {
7620
+ if (item.isHeader) {
7621
+ currentHeader = item;
7622
+ } else {
7623
+ const matches = item.label.toLowerCase().includes(q) || item.value && item.value.toLowerCase().includes(q);
7624
+ if (matches) {
7625
+ if (currentHeader && !filtered.includes(currentHeader)) {
7626
+ filtered.push(currentHeader);
7627
+ }
7628
+ filtered.push(item);
7629
+ }
7630
+ }
7631
+ }
7632
+ return filtered;
7633
+ }, [allSubAgentItems, subAgentSearchQuery]);
7634
+ useEffect5(() => {
7635
+ if (isSelectingSubAgentModel) {
7636
+ let firstValid = availableModels.findIndex((item) => !item.isHeader);
7637
+ setSubAgentModelIndex(firstValid >= 0 ? firstValid : 0);
7638
+ setSubAgentScrollOffset(0);
7639
+ }
7640
+ }, [subAgentSearchQuery]);
7641
+ useEffect5(() => {
7642
+ if (isSelectingSubAgentModel) {
7643
+ if (availableModels.length === 0) {
7644
+ setSubAgentModelIndex(0);
7645
+ setSubAgentScrollOffset(0);
7646
+ return;
7647
+ }
7648
+ if (subAgentModelIndex >= availableModels.length || availableModels[subAgentModelIndex]?.isHeader) {
7649
+ let firstValid = availableModels.findIndex((item) => !item.isHeader);
7650
+ setSubAgentModelIndex(firstValid >= 0 ? firstValid : 0);
7651
+ }
7652
+ }
7653
+ }, [availableModels, isSelectingSubAgentModel]);
7451
7654
  const [currentMemory, setCurrentMemory] = useState6(0);
7452
7655
  const [maxMemory, setMaxMemory] = useState6(0);
7453
7656
  const [memoryUnit, setMemoryUnit] = useState6("MB");
@@ -7512,10 +7715,11 @@ function SettingsMenu({
7512
7715
  case "other":
7513
7716
  return [
7514
7717
  { label: "Sub-Agents", value: "subAgents", status: systemSettings.subAgents !== false ? "ON" : "OFF" },
7718
+ { label: "Sub-Agent Model", value: "subAgentModel", status: systemSettings.CustomSubAgent && systemSettings.SubAgentModel ? systemSettings.SubAgentModel : "Default" },
7515
7719
  { label: "Preserve Thinking", value: "preserveThinking", status: systemSettings.preserveThinking !== false ? "ON" : "OFF" },
7516
7720
  { label: "Dynamic Directory Awareness", value: "dynamicDirAwareness", status: systemSettings.dynamicDirAwareness ? "ON" : "OFF" },
7517
- { label: "Directory Tree Design", value: "indentationTree", status: systemSettings.indentationTree !== false ? "Modern" : "Classic (deprecated)" },
7518
- { label: "Download Language Parsers", value: "parserDownload", status: "ACTION" }
7721
+ { label: "Directory Tree Design", value: "indentationTree", status: systemSettings.indentationTree !== false ? "Modern" : "Classic (deprecated)" }
7722
+ // { label: 'Download Language Parsers', value: 'parserDownload', status: 'ACTION' } // Dont remove this comment
7519
7723
  ];
7520
7724
  default:
7521
7725
  return [];
@@ -7524,6 +7728,69 @@ function SettingsMenu({
7524
7728
  const currentCatId = CATEGORIES[selectedCategoryIndex].id;
7525
7729
  const currentItems = getCategoryItems(currentCatId);
7526
7730
  useInput3((input, key) => {
7731
+ if (isSelectingSubAgentModel) {
7732
+ if (key.tab) {
7733
+ setSubAgentFocusMode((prev) => prev === "search" ? "list" : "search");
7734
+ return;
7735
+ }
7736
+ if (subAgentFocusMode === "search") {
7737
+ if (key.escape) {
7738
+ setIsSelectingSubAgentModel(false);
7739
+ } else if (key.downArrow || key.return) {
7740
+ setSubAgentFocusMode("list");
7741
+ } else if (key.backspace || key.delete) {
7742
+ setSubAgentSearchQuery((q) => q.slice(0, -1));
7743
+ } else if (input && !key.ctrl && !key.meta && input.length === 1) {
7744
+ setSubAgentSearchQuery((q) => q + input);
7745
+ }
7746
+ return;
7747
+ }
7748
+ if (key.upArrow) {
7749
+ setSubAgentModelIndex((prev) => {
7750
+ if (availableModels.length === 0) return 0;
7751
+ let next = (prev - 1 + availableModels.length) % availableModels.length;
7752
+ let count = 0;
7753
+ while (availableModels[next]?.isHeader && count < availableModels.length) {
7754
+ next = (next - 1 + availableModels.length) % availableModels.length;
7755
+ count++;
7756
+ }
7757
+ return next;
7758
+ });
7759
+ } else if (key.downArrow) {
7760
+ setSubAgentModelIndex((prev) => {
7761
+ if (availableModels.length === 0) return 0;
7762
+ let next = (prev + 1) % availableModels.length;
7763
+ let count = 0;
7764
+ while (availableModels[next]?.isHeader && count < availableModels.length) {
7765
+ next = (next + 1) % availableModels.length;
7766
+ count++;
7767
+ }
7768
+ return next;
7769
+ });
7770
+ } else if (key.return) {
7771
+ const selectedOpt = availableModels[subAgentModelIndex];
7772
+ if (selectedOpt && !selectedOpt.isHeader) {
7773
+ setSystemSettings((s) => {
7774
+ const isDefault = selectedOpt.value === "Default";
7775
+ const newSysSettings = {
7776
+ ...s,
7777
+ CustomSubAgent: !isDefault,
7778
+ SubAgentModel: selectedOpt.value,
7779
+ SubAgentProvider: isDefault ? "" : selectedOpt.provider || ""
7780
+ };
7781
+ saveSettings2({ systemSettings: newSysSettings, apiTier, quotas });
7782
+ return newSysSettings;
7783
+ });
7784
+ setIsSelectingSubAgentModel(false);
7785
+ }
7786
+ } else if (key.escape) {
7787
+ setIsSelectingSubAgentModel(false);
7788
+ } else if (input && !key.ctrl && !key.meta && input.length === 1) {
7789
+ setSubAgentSearchQuery((q) => q + input);
7790
+ setSubAgentFocusMode("search");
7791
+ }
7792
+ return;
7793
+ }
7527
7794
  if (isSelectingTheme) {
7528
7795
  if (key.upArrow) {
7529
7796
  const nextIdx = (themeIndex - 1 + themeOptions.length) % themeOptions.length;
@@ -7711,6 +7978,11 @@ function SettingsMenu({
7711
7978
  saveSettings2({ systemSettings: newSysSettings, apiTier, quotas });
7712
7979
  return newSysSettings;
7713
7980
  });
7981
+ } else if (item.value === "subAgentModel") {
7982
+ const currentSubAgentModel = systemSettings.CustomSubAgent && systemSettings.SubAgentModel ? systemSettings.SubAgentModel : "Default";
7983
+ const curIdx = availableModels.findIndex((m) => m.value === currentSubAgentModel);
7984
+ setSubAgentModelIndex(curIdx >= 0 ? curIdx : 0);
7985
+ setIsSelectingSubAgentModel(true);
7714
7986
  } else if (item.value === "preserveThinking") {
7715
7987
  setSystemSettings((s) => {
7716
7988
  const newSysSettings = { ...s, preserveThinking: s.preserveThinking === false ? true : false };
@@ -7754,6 +8026,42 @@ function SettingsMenu({
7754
8026
  }
7755
8027
  };
7756
8028
  const colors = getThemeColors(systemSettings.theme);
8029
+ if (isSelectingSubAgentModel) {
8030
+ const currentSavedModel = systemSettings.CustomSubAgent && systemSettings.SubAgentModel ? systemSettings.SubAgentModel : "Default";
8031
+ const VISIBLE_COUNT = 15;
8032
+ let startIndex = subAgentScrollOffset;
8033
+ if (subAgentModelIndex < startIndex) {
8034
+ startIndex = subAgentModelIndex;
8035
+ } else if (subAgentModelIndex >= startIndex + VISIBLE_COUNT) {
8036
+ startIndex = subAgentModelIndex - VISIBLE_COUNT + 1;
8037
+ }
8038
+ startIndex = Math.max(0, Math.min(startIndex, Math.max(0, availableModels.length - VISIBLE_COUNT)));
8039
+ if (startIndex !== subAgentScrollOffset) {
8040
+ setSubAgentScrollOffset(startIndex);
8041
+ }
8042
+ const visibleItems = availableModels.slice(startIndex, startIndex + VISIBLE_COUNT);
8043
+ return /* @__PURE__ */ React7.createElement(Box6, { flexDirection: "column", borderStyle: "round", borderColor: colors.border, padding: 1, width: "100%", minHeight: 32 }, /* @__PURE__ */ React7.createElement(Box6, { marginBottom: 1, flexDirection: "row", justifyContent: "space-between" }, /* @__PURE__ */ React7.createElement(Text7, { color: colors.text, bold: true, underline: true }, "Select Sub-Agent Model:"), availableModels.length > 0 && /* @__PURE__ */ React7.createElement(Text7, { color: "gray" }, subAgentModelIndex + 1, "/", availableModels.length)), /* @__PURE__ */ React7.createElement(
8044
+ Box6,
8045
+ {
8046
+ borderStyle: "single",
8047
+ borderColor: subAgentFocusMode === "search" ? colors.primary || "cyan" : "gray",
8048
+ paddingX: 1,
8049
+ marginBottom: 1
8050
+ },
8051
+ /* @__PURE__ */ React7.createElement(Text7, { color: subAgentFocusMode === "search" ? colors.primary || "cyan" : "gray", bold: true }, "\u{1F50D} Search: ", " "),
8052
+ /* @__PURE__ */ React7.createElement(Text7, { color: colors.text }, subAgentSearchQuery),
8053
+ subAgentFocusMode === "search" && /* @__PURE__ */ React7.createElement(Text7, { color: colors.primary || "cyan" }, "\u2588"),
8054
+ !subAgentSearchQuery && subAgentFocusMode !== "search" && /* @__PURE__ */ React7.createElement(Text7, { color: "gray", italic: true }, "(Press TAB or type to filter models...)")
8055
+ ), /* @__PURE__ */ React7.createElement(Box6, { flexDirection: "column", flexGrow: 1, height: VISIBLE_COUNT }, visibleItems.length > 0 ? visibleItems.map((opt, idx) => {
8056
+ const actualIndex = startIndex + idx;
8057
+ if (opt.isHeader) {
8058
+ return /* @__PURE__ */ React7.createElement(Box6, { key: `hdr-${actualIndex}`, paddingX: 1 }, /* @__PURE__ */ React7.createElement(Text7, { color: "gray", bold: true, underline: true }, opt.label));
8059
+ }
8060
+ const isSelected = subAgentModelIndex === actualIndex && subAgentFocusMode === "list";
8061
+ const isSaved = currentSavedModel === opt.value;
8062
+ return /* @__PURE__ */ React7.createElement(Box6, { key: `item-${opt.value}-${actualIndex}`, paddingX: 1, backgroundColor: isSelected ? colors.highlightBg : void 0 }, /* @__PURE__ */ React7.createElement(Text7, { color: isSelected ? colors.text : colors.textDim, bold: isSelected }, isSelected ? "\u276F " : " ", opt.label, isSaved ? /* @__PURE__ */ React7.createElement(Text7, { color: colors.primary || "cyan", italic: true }, " (active)") : ""));
8063
+ }) : /* @__PURE__ */ React7.createElement(Box6, { paddingX: 1 }, /* @__PURE__ */ React7.createElement(Text7, { color: "gray", italic: true }, 'No models matching "', subAgentSearchQuery, '"'))), /* @__PURE__ */ React7.createElement(Box6, { paddingX: 1, marginTop: 1, flexDirection: "row", justifyContent: "space-between" }, /* @__PURE__ */ React7.createElement(Text7, { color: "gray", italic: true }, "TAB to switch search/list \u2022 \u25B2\u25BC Navigate \u2022 ENTER to Select \u2022 ESC to Cancel"), /* @__PURE__ */ React7.createElement(Text7, { color: subAgentFocusMode === "search" ? colors.primary || "cyan" : "gray", bold: true }, "[", subAgentFocusMode.toUpperCase(), " MODE]")));
8064
+ }
7757
8065
  if (isSelectingTheme) {
7758
8066
  const previewThemeName = themeOptions[themeIndex];
7759
8067
  const previewColors = getThemeColors(previewThemeName);
@@ -7857,6 +8165,8 @@ var init_SettingsMenu = __esm({
7857
8165
  async "src/components/SettingsMenu.jsx"() {
7858
8166
  await init_exec_command();
7859
8167
  init_theme();
8168
+ init_model_config();
8169
+ init_secrets();
7860
8170
  themeOptions = [...Object.keys(THEMES), "Mystery"];
7861
8171
  CATEGORIES = [
7862
8172
  { id: "appearance", label: "Appearance", desc: "Customize UI theme & rendering options" },
@@ -8254,12 +8564,14 @@ ${forcedReasoning || thinkingLevel !== "Fast" && (aiProvider === "Mistral" || th
8254
8564
  - Use <think> ... </think> for reasoning before responding, even with simple queries/greetings
8255
8565
  ` : ""}` : `${thinkingConfig}
8256
8566
  `}
8567
+ - **USE PROVIDED DIRECTORY STRUCTURE FOR FILES/PATHS**
8568
+ - RELATIVE TIME REFERENCE eg. few mins ago
8569
+
8257
8570
  ${TOOL_PROTOCOL(mode, osDetected, aiProvider.toLowerCase() === "deepseek" ? false : isMultiModal, aiProvider, systemSettings?.advanceRollback, systemSettings?.subAgents !== false)}
8258
8571
  ${projectContextBlock}${isMemoryEnabled ? `
8259
8572
  -- MEMORY RULES --
8260
- - Subtly Personalize with RELEVENT CONTEXTUAL MEMORIES. Auto Saves` : ""}
8261
- - RELATIVE TIME REFERENCE eg. few mins ago
8262
-
8573
+ - Subtly Personalize with RELEVENT CONTEXTUAL MEMORIES. Auto Saves
8574
+ ` : ""}
8263
8575
  -- SECURITY RULES --
8264
8576
  - Sensitive files? Ask before Read${isSystemDir ? "\n- PROTECTED DIRECTORY" : ""}
8265
8577
 
@@ -8877,24 +9189,11 @@ var init_history = __esm({
8877
9189
  // src/utils/usage.js
8878
9190
  import fs10 from "fs-extra";
8879
9191
  import path9 from "path";
8880
- import os3 from "os";
8881
- var getLocalBackupPath, BACKUP_FILE, generateSaveId, cachedUsage, writeTimeout, lastWriteTime, isDirty, defaultStats, purgeOldHistory, loadUsageFromFile, flushUsage, queueFlush, initUsage, forceFlushUsage, getDailyUsage, getMonthlyUsage, incrementUsage, runtimeSession, addToUsage, getCustomPeriodUsage, checkQuota, getImageQuotaBuckets, getImageQuotaLimit, checkImageQuota, getImageQuotaStats, recordImageGeneration;
9192
+ var generateSaveId, cachedUsage, writeTimeout, lastWriteTime, isDirty, defaultStats, purgeOldHistory, loadUsageFromFile, flushUsage, queueFlush, initUsage, forceFlushUsage, getDailyUsage, getMonthlyUsage, incrementUsage, runtimeSession, addToUsage, getCustomPeriodUsage, checkQuota, getImageQuotaBuckets, getImageQuotaLimit, checkImageQuota, getImageQuotaStats, recordImageGeneration;
8882
9193
  var init_usage = __esm({
8883
9194
  "src/utils/usage.js"() {
8884
9195
  init_paths();
8885
9196
  init_crypto();
8886
- getLocalBackupPath = () => {
8887
- if (process.platform === "win32") {
8888
- const localAppData = process.env.LOCALAPPDATA || path9.join(os3.homedir(), "AppData", "Local");
8889
- return path9.join(localAppData, "FxFl", "backups", "backup.json");
8890
- }
8891
- if (process.platform === "darwin") {
8892
- return path9.join(os3.homedir(), "Library", "Application Support", "FxFl", "backups", "backup.json");
8893
- }
8894
- const xdgDataHome = process.env.XDG_DATA_HOME || path9.join(os3.homedir(), ".local", "share");
8895
- return path9.join(xdgDataHome, "fxfl", "backups", "backup.json");
8896
- };
8897
- BACKUP_FILE = getLocalBackupPath();
8898
9197
  generateSaveId = () => Math.random().toString(36).substring(2) + Date.now().toString(36);
8899
9198
  cachedUsage = null;
8900
9199
  writeTimeout = null;
@@ -8929,10 +9228,16 @@ var init_usage = __esm({
8929
9228
  return purged;
8930
9229
  };
8931
9230
  loadUsageFromFile = async () => {
8932
- const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
9231
+ const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
9232
+ try {
9233
+ if (!await fs10.exists(USAGE_FILE) && await fs10.exists(USAGE_FILE_OLD)) {
9234
+ await fs10.ensureDir(path9.dirname(USAGE_FILE));
9235
+ await fs10.move(USAGE_FILE_OLD, USAGE_FILE);
9236
+ }
9237
+ } catch (err) {
9238
+ }
8933
9239
  const tempFile = USAGE_FILE + ".tmp";
8934
9240
  let primaryData = null;
8935
- let backupData = null;
8936
9241
  try {
8937
9242
  if (await fs10.exists(tempFile)) {
8938
9243
  const rawContent = (await fs10.readFile(tempFile, "utf8")).trim();
@@ -8974,44 +9279,7 @@ var init_usage = __esm({
8974
9279
  } catch (err) {
8975
9280
  }
8976
9281
  }
8977
- try {
8978
- if (await fs10.exists(BACKUP_FILE)) {
8979
- const rawContent = (await fs10.readFile(BACKUP_FILE, "utf8")).trim();
8980
- if (rawContent.startsWith("{") || rawContent.startsWith("[")) {
8981
- backupData = JSON.parse(rawContent);
8982
- } else {
8983
- backupData = JSON.parse(decryptAes(rawContent));
8984
- }
8985
- }
8986
- } catch (err) {
8987
- }
8988
- let resolvedData = null;
8989
- if (primaryData && backupData) {
8990
- if (primaryData.saveId !== backupData.saveId) {
8991
- resolvedData = primaryData;
8992
- try {
8993
- await fs10.ensureDir(path9.dirname(BACKUP_FILE));
8994
- await fs10.copy(USAGE_FILE, BACKUP_FILE);
8995
- } catch (e) {
8996
- }
8997
- } else {
8998
- resolvedData = primaryData;
8999
- }
9000
- } else if (primaryData && !backupData) {
9001
- resolvedData = primaryData;
9002
- try {
9003
- await fs10.ensureDir(path9.dirname(BACKUP_FILE));
9004
- await fs10.copy(USAGE_FILE, BACKUP_FILE);
9005
- } catch (e) {
9006
- }
9007
- } else if (!primaryData && backupData) {
9008
- resolvedData = backupData;
9009
- try {
9010
- await fs10.ensureDir(path9.dirname(USAGE_FILE));
9011
- await fs10.copy(BACKUP_FILE, USAGE_FILE);
9012
- } catch (e) {
9013
- }
9014
- }
9282
+ let resolvedData = primaryData;
9015
9283
  if (resolvedData) {
9016
9284
  const stats = resolvedData.stats || { ...defaultStats };
9017
9285
  const mergedStats = { ...defaultStats, ...stats };
@@ -9019,28 +9287,32 @@ var init_usage = __esm({
9019
9287
  mergedStats.imageCalls = [];
9020
9288
  }
9021
9289
  const history = resolvedData.history || {};
9022
- if (resolvedData.date === today) {
9290
+ const purgedHistory = purgeOldHistory(history, today2);
9291
+ if (Object.keys(history).length !== Object.keys(purgedHistory).length) {
9292
+ isDirty = true;
9293
+ }
9294
+ if (resolvedData.date === today2) {
9023
9295
  return {
9024
9296
  ...resolvedData,
9025
9297
  stats: mergedStats,
9026
- history
9298
+ history: purgedHistory
9027
9299
  };
9028
9300
  } else {
9029
9301
  const oldDate = resolvedData.date;
9030
9302
  const oldStats = mergedStats;
9031
- const updatedHistory = { ...history };
9303
+ const updatedHistory = { ...purgedHistory };
9032
9304
  if (oldDate) {
9033
9305
  updatedHistory[oldDate] = oldStats;
9034
9306
  }
9035
9307
  return {
9036
- date: today,
9308
+ date: today2,
9037
9309
  stats: { ...defaultStats },
9038
- history: purgeOldHistory(updatedHistory, today)
9310
+ history: purgeOldHistory(updatedHistory, today2)
9039
9311
  };
9040
9312
  }
9041
9313
  }
9042
9314
  return {
9043
- date: today,
9315
+ date: today2,
9044
9316
  stats: { ...defaultStats },
9045
9317
  history: {}
9046
9318
  };
@@ -9123,7 +9395,10 @@ var init_usage = __esm({
9123
9395
  mergedHistory[dateKey] = diskData.history[dateKey];
9124
9396
  }
9125
9397
  }
9126
- cachedUsage.history = mergedHistory;
9398
+ cachedUsage.history = purgeOldHistory(mergedHistory, cachedUsage.date || today);
9399
+ } else if (cachedUsage && cachedUsage.history) {
9400
+ const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
9401
+ cachedUsage.history = purgeOldHistory(cachedUsage.history, today2);
9127
9402
  }
9128
9403
  cachedUsage.saveId = generateSaveId();
9129
9404
  const tempFile = USAGE_FILE + ".tmp";
@@ -9133,11 +9408,6 @@ var init_usage = __esm({
9133
9408
  await fs10.fsync(fd);
9134
9409
  await fs10.close(fd);
9135
9410
  await fs10.rename(tempFile, USAGE_FILE);
9136
- try {
9137
- await fs10.ensureDir(path9.dirname(BACKUP_FILE));
9138
- await fs10.copy(USAGE_FILE, BACKUP_FILE);
9139
- } catch (backupErr) {
9140
- }
9141
9411
  isDirty = false;
9142
9412
  lastWriteTime = Date.now();
9143
9413
  } catch (e) {
@@ -9156,6 +9426,9 @@ var init_usage = __esm({
9156
9426
  };
9157
9427
  initUsage = async () => {
9158
9428
  cachedUsage = await loadUsageFromFile();
9429
+ if (isDirty) {
9430
+ queueFlush();
9431
+ }
9159
9432
  };
9160
9433
  forceFlushUsage = async () => {
9161
9434
  if (writeTimeout) {
@@ -9165,10 +9438,10 @@ var init_usage = __esm({
9165
9438
  await flushUsage();
9166
9439
  };
9167
9440
  getDailyUsage = async () => {
9168
- const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
9441
+ const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
9169
9442
  if (!cachedUsage) {
9170
9443
  cachedUsage = await loadUsageFromFile();
9171
- } else if (cachedUsage.date !== today) {
9444
+ } else if (cachedUsage.date !== today2) {
9172
9445
  const oldDate = cachedUsage.date;
9173
9446
  const oldStats = cachedUsage.stats;
9174
9447
  const history = cachedUsage.history || {};
@@ -9176,9 +9449,9 @@ var init_usage = __esm({
9176
9449
  history[oldDate] = oldStats;
9177
9450
  }
9178
9451
  cachedUsage = {
9179
- date: today,
9452
+ date: today2,
9180
9453
  stats: { ...defaultStats },
9181
- history: purgeOldHistory(history, today)
9454
+ history: purgeOldHistory(history, today2)
9182
9455
  };
9183
9456
  isDirty = true;
9184
9457
  await flushUsage();
@@ -9189,15 +9462,15 @@ var init_usage = __esm({
9189
9462
  return cachedUsage.stats;
9190
9463
  };
9191
9464
  getMonthlyUsage = async () => {
9192
- const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
9465
+ const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
9193
9466
  if (!cachedUsage) {
9194
9467
  cachedUsage = await loadUsageFromFile();
9195
9468
  }
9196
- if (cachedUsage.date !== today) {
9469
+ if (cachedUsage.date !== today2) {
9197
9470
  await getDailyUsage();
9198
9471
  }
9199
9472
  const history = cachedUsage.history || {};
9200
- const purgedHistory = purgeOldHistory(history, today);
9473
+ const purgedHistory = purgeOldHistory(history, today2);
9201
9474
  cachedUsage.history = purgedHistory;
9202
9475
  const todayStats = cachedUsage.stats || { ...defaultStats };
9203
9476
  const summed = { ...defaultStats };
@@ -9294,17 +9567,17 @@ var init_usage = __esm({
9294
9567
  queueFlush();
9295
9568
  };
9296
9569
  getCustomPeriodUsage = async (resetDay = 1) => {
9297
- const today = /* @__PURE__ */ new Date();
9298
- const todayStr = today.toISOString().split("T")[0];
9570
+ const today2 = /* @__PURE__ */ new Date();
9571
+ const todayStr = today2.toISOString().split("T")[0];
9299
9572
  if (!cachedUsage) {
9300
9573
  cachedUsage = await loadUsageFromFile();
9301
9574
  }
9302
9575
  if (cachedUsage.date !== todayStr) {
9303
9576
  await getDailyUsage();
9304
9577
  }
9305
- let startYear = today.getFullYear();
9306
- let startMonth = today.getMonth();
9307
- const todayDay = today.getDate();
9578
+ let startYear = today2.getFullYear();
9579
+ let startMonth = today2.getMonth();
9580
+ const todayDay = today2.getDate();
9308
9581
  if (todayDay < resetDay) {
9309
9582
  startMonth -= 1;
9310
9583
  if (startMonth < 0) {
@@ -9618,14 +9891,14 @@ var init_usage = __esm({
9618
9891
  });
9619
9892
 
9620
9893
  // src/utils/puppeteer_helper.js
9621
- import os4 from "os";
9894
+ import os3 from "os";
9622
9895
  import path10 from "path";
9623
9896
  import fs11 from "fs";
9624
9897
  import { createRequire } from "module";
9625
9898
  import { fileURLToPath as fileURLToPath2 } from "url";
9626
9899
  function getPuppeteerConfig() {
9627
- const platform = os4.platform();
9628
- const arch = os4.arch();
9900
+ const platform = os3.platform();
9901
+ const arch = os3.arch();
9629
9902
  let pptrPlatform = "";
9630
9903
  let execName = "";
9631
9904
  let subDir = "";
@@ -9962,6 +10235,7 @@ ${finalResults}`;
9962
10235
  import puppeteer2 from "puppeteer";
9963
10236
  import fs13 from "fs";
9964
10237
  import path12 from "path";
10238
+ import TurndownService from "turndown";
9965
10239
  var web_scrape;
9966
10240
  var init_web_scrape = __esm({
9967
10241
  "src/tools/web_scrape.js"() {
@@ -10019,15 +10293,20 @@ var init_web_scrape = __esm({
10019
10293
  el.removeAttribute(attrName);
10020
10294
  }
10021
10295
  }
10022
- if ((el.tagName === "SPAN" || el.tagName === "DIV" || el.tagName === "SECTION") && el.attributes.length === 0) {
10023
- if (el.tagName === "SPAN" || el.tagName === "DIV" && el.childNodes.length === 1 && el.childNodes[0].nodeType === Node.TEXT_NODE) {
10296
+ });
10297
+ while (document.querySelector("div, span")) {
10298
+ document.querySelectorAll("div, span").forEach((el) => {
10299
+ if (el.parentNode) {
10024
10300
  el.replaceWith(...el.childNodes);
10025
10301
  }
10026
- }
10302
+ });
10303
+ }
10304
+ document.querySelectorAll("br").forEach((br) => {
10305
+ br.replaceWith(document.createTextNode("\n\n"));
10027
10306
  });
10028
10307
  const pruneEmpty = () => {
10029
10308
  let found = false;
10030
- document.querySelectorAll("*:not(br)").forEach((el) => {
10309
+ document.querySelectorAll("*").forEach((el) => {
10031
10310
  if (el.childNodes.length === 0 && !el.innerText.trim()) {
10032
10311
  el.remove();
10033
10312
  found = true;
@@ -10039,11 +10318,17 @@ var init_web_scrape = __esm({
10039
10318
  return document.body.innerHTML;
10040
10319
  });
10041
10320
  if (!htmlContent) throw new Error("EMPTY_RENDER_RESULT");
10042
- const cleanedHtml = htmlContent.replace(/\s+/g, " ").replace(/>\s+</g, "><").trim().substring(0, 5e4);
10321
+ const cleanedHtml = htmlContent.replace(/<br\s*\/?>/gi, "\n\n").replace(/[ \t]+/g, " ").replace(/>[ \t]+</g, "><").replace(/\n\s+/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
10322
+ const turndownService = new TurndownService({
10323
+ headingStyle: "atx",
10324
+ codeBlockStyle: "fenced"
10325
+ });
10326
+ const rawMarkdown = turndownService.turndown(cleanedHtml).replace(/\.\s*\n/g, "\n").replace(/ +/g, " ").replace(/\t/g, " ").replace(/\n\s+/g, "\n").replace(/\n{3,}/g, "\n\n");
10327
+ const markdown = rawMarkdown.substring(0, 5e4);
10043
10328
  await browser.close();
10044
- return `CLEANED HTML FROM [${url}]:
10329
+ return `Markdown parsed from [${url}]:
10045
10330
 
10046
- ${cleanedHtml}${htmlContent.length > 5e4 ? "\n\n[TRUNCATED AT 50K CHARS]" : ""}`;
10331
+ ${markdown}${rawMarkdown.length > 5e4 ? "\n\n[TRUNCATED AT 50K CHARS]" : ""}`;
10047
10332
  } catch (err) {
10048
10333
  lastError = err;
10049
10334
  if (browser) await browser.close();
@@ -10243,7 +10528,7 @@ var init_view_file = __esm({
10243
10528
  const end = Math.min(totalLines, finalEnd);
10244
10529
  const resultLines = lines.slice(start, end);
10245
10530
  const header = `File: [${targetPath}] (Showing lines ${start + 1}-${end} of ${totalLines}).`;
10246
- const code = resultLines.map((line, i) => `${String(start + i + 1).padStart(4)}: ${line}`).join("\n");
10531
+ const code = resultLines.map((line, i) => `${String(start + i + 1).padStart(4)}: ${line.trimEnd()}`).join("\n");
10247
10532
  return `${header}
10248
10533
 
10249
10534
  ${code}`;
@@ -10342,7 +10627,7 @@ var init_update_file = __esm({
10342
10627
  update_file = async (args, context = {}) => {
10343
10628
  const parsed = parseArgs(args);
10344
10629
  const targetPath = parsed.path;
10345
- if (!targetPath) return 'ERROR: Missing "path" argument for update_file.';
10630
+ if (!targetPath) return 'ERROR: Missing "path" argument for PatchFile.';
10346
10631
  const { patchPairs, allowMultiple: parsedAllowMultiple, error: parseError } = parsePatchPairs(parsed);
10347
10632
  if (parseError) return `ERROR: ${parseError}`;
10348
10633
  if (patchPairs.length === 0) {
@@ -10352,7 +10637,7 @@ var init_update_file = __esm({
10352
10637
  const absolutePath = path15.resolve(process.cwd(), targetPath);
10353
10638
  try {
10354
10639
  if (!fs16.existsSync(absolutePath)) {
10355
- return `ERROR: File [${targetPath}] does not exist. Use write_file instead.`;
10640
+ return `ERROR: File [${targetPath}] does not exist. Use WriteFile instead.`;
10356
10641
  }
10357
10642
  let diskContent = context.forcedContent || fs16.readFileSync(absolutePath, "utf8");
10358
10643
  if (diskContent.startsWith("\uFEFF")) diskContent = diskContent.slice(1);
@@ -10389,7 +10674,7 @@ ${diffText}`;
10389
10674
  // src/tools/read_folder.js
10390
10675
  import fs17 from "fs";
10391
10676
  import path16 from "path";
10392
- var EXCLUDED_DIRS, isExcludedDir, read_folder;
10677
+ var EXCLUDED_DIRS, isExcludedDir, formatMtime, read_folder;
10393
10678
  var init_read_folder = __esm({
10394
10679
  "src/tools/read_folder.js"() {
10395
10680
  init_arg_parser();
@@ -10536,24 +10821,35 @@ var init_read_folder = __esm({
10536
10821
  ".VSCodeCounter"
10537
10822
  ]);
10538
10823
  isExcludedDir = (dirName) => EXCLUDED_DIRS.has(dirName) || dirName.startsWith(".pnpm");
10824
+ formatMtime = (d) => {
10825
+ try {
10826
+ const mm = String(d.getMonth() + 1).padStart(2, "0");
10827
+ const dd = String(d.getDate()).padStart(2, "0");
10828
+ const hh = String(d.getHours()).padStart(2, "0");
10829
+ const min = String(d.getMinutes()).padStart(2, "0");
10830
+ return `${mm}-${dd} ${hh}:${min}`;
10831
+ } catch {
10832
+ return "N/A";
10833
+ }
10834
+ };
10539
10835
  read_folder = async (args) => {
10540
10836
  const parsed = parseArgs(args);
10541
10837
  const targetPath = parsed.path || null;
10542
10838
  if (!targetPath) {
10543
10839
  return "ERROR: No directory path provided.";
10544
10840
  }
10545
- let recurseDepth = 0;
10841
+ let recurseDepth = 1;
10546
10842
  if (parsed.recurse !== void 0 && parsed.recurse !== null) {
10547
10843
  if (typeof parsed.recurse === "number") {
10548
10844
  recurseDepth = parsed.recurse;
10549
10845
  } else if (typeof parsed.recurse === "boolean") {
10550
- recurseDepth = parsed.recurse ? 1 : 0;
10846
+ recurseDepth = parsed.recurse ? 2 : 1;
10551
10847
  } else {
10552
10848
  const val = parseInt(String(parsed.recurse).trim(), 10);
10553
- recurseDepth = isNaN(val) ? 0 : val;
10849
+ recurseDepth = isNaN(val) ? 1 : val;
10554
10850
  }
10555
10851
  }
10556
- recurseDepth = Math.max(0, Math.min(5, recurseDepth));
10852
+ recurseDepth = Math.max(1, Math.min(3, recurseDepth));
10557
10853
  const absolutePath = path16.resolve(process.cwd(), targetPath);
10558
10854
  try {
10559
10855
  if (!fs17.existsSync(absolutePath)) {
@@ -10563,7 +10859,7 @@ var init_read_folder = __esm({
10563
10859
  if (!stats.isDirectory()) {
10564
10860
  return `ERROR: Path [${targetPath}] is a file, not a directory. Use ReadFile instead.`;
10565
10861
  }
10566
- if (recurseDepth === 0) {
10862
+ if (recurseDepth === 1) {
10567
10863
  const files = fs17.readdirSync(absolutePath);
10568
10864
  const totalItems = files.length;
10569
10865
  const maxDisplay = 150;
@@ -10577,8 +10873,8 @@ var init_read_folder = __esm({
10577
10873
  info = {
10578
10874
  name: file,
10579
10875
  type: fStats.isDirectory() ? "directory" : "file",
10580
- size: (fStats.size / 1024).toFixed(1) + " KB",
10581
- mtime: fStats.mtime.toLocaleString()
10876
+ size: (fStats.size / 1024).toFixed(1) + "KB",
10877
+ mtime: formatMtime(fStats.mtime)
10582
10878
  };
10583
10879
  } catch (e) {
10584
10880
  info.type = "inaccessible";
@@ -10586,11 +10882,10 @@ var init_read_folder = __esm({
10586
10882
  folderData.push(info);
10587
10883
  }
10588
10884
  const formatted = folderData.map((f) => {
10589
- const indicator = f.type === "directory" ? "\u{1F4C1}" : f.type === "file" ? "\u{1F4C4}" : "\u2753";
10590
10885
  if (f.type === "directory") {
10591
- return `${indicator} ${f.name} - [DIR] - [Modified: ${f.mtime}]`;
10886
+ return `${f.name}/`;
10592
10887
  }
10593
- return `${indicator} ${f.name} - [Size: ${f.size}] - [Modified: ${f.mtime}]`;
10888
+ return `${f.name} (${f.size}, ${f.mtime})`;
10594
10889
  }).join("\n");
10595
10890
  let footer2 = `
10596
10891
 
@@ -10598,7 +10893,7 @@ var init_read_folder = __esm({
10598
10893
  if (totalItems > maxDisplay) {
10599
10894
  footer2 = `
10600
10895
 
10601
- \u26A0\uFE0F TRUNCATED: Showing first ${maxDisplay} of ${totalItems} items.`;
10896
+ TRUNCATED: Showing first ${maxDisplay} of ${totalItems} items.`;
10602
10897
  }
10603
10898
  files.length = 0;
10604
10899
  displayItems.length = 0;
@@ -10612,15 +10907,17 @@ ${formatted}${footer2}`;
10612
10907
  let totalItemsScanned = 0;
10613
10908
  const maxTotalItems = 500;
10614
10909
  let truncated = false;
10615
- const buildTree = (dirPath, currentDepth, prefix = "") => {
10616
- if (currentDepth > recurseDepth + 1 || truncated) return [];
10910
+ const buildTree = (dirPath, currentDepth, depth = 1) => {
10911
+ if (currentDepth > recurseDepth || truncated) return [];
10617
10912
  let entries = [];
10618
10913
  try {
10619
10914
  entries = fs17.readdirSync(dirPath);
10620
10915
  } catch (e) {
10621
- return [`${prefix}\u26A0\uFE0F [Inaccessible Directory]`];
10916
+ const indent2 = " ".repeat(depth - 1);
10917
+ return [`${indent2}[Inaccessible Directory]`];
10622
10918
  }
10623
- const sortedEntries = [];
10919
+ const subDirs = [];
10920
+ const fileEntries = [];
10624
10921
  for (const name of entries) {
10625
10922
  const fullPath = path16.join(dirPath, name);
10626
10923
  let isDir = false;
@@ -10628,60 +10925,53 @@ ${formatted}${footer2}`;
10628
10925
  isDir = fs17.statSync(fullPath).isDirectory();
10629
10926
  } catch (e) {
10630
10927
  }
10631
- sortedEntries.push({ name, fullPath, isDir });
10928
+ if (isDir) {
10929
+ subDirs.push({ name, fullPath });
10930
+ } else {
10931
+ fileEntries.push({ name, fullPath });
10932
+ }
10632
10933
  }
10633
- sortedEntries.sort((a, b) => {
10634
- if (a.isDir && !b.isDir) return -1;
10635
- if (!a.isDir && b.isDir) return 1;
10636
- return a.name.localeCompare(b.name);
10637
- });
10934
+ subDirs.sort((a, b) => a.name.localeCompare(b.name));
10935
+ fileEntries.sort((a, b) => a.name.localeCompare(b.name));
10638
10936
  const lines = [];
10639
- const count = sortedEntries.length;
10640
- for (let i = 0; i < count; i++) {
10937
+ const indent = " ".repeat(depth - 1);
10938
+ for (const subDir of subDirs) {
10939
+ if (totalItemsScanned >= maxTotalItems) {
10940
+ truncated = true;
10941
+ lines.push(`${indent}[Truncated - Maximum item limit reached (${maxTotalItems})]`);
10942
+ break;
10943
+ }
10944
+ totalItemsScanned++;
10945
+ totalDirectories++;
10946
+ lines.push(`${indent}${subDir.name}/`);
10947
+ if (currentDepth < recurseDepth && !isExcludedDir(subDir.name)) {
10948
+ const childLines = buildTree(subDir.fullPath, currentDepth + 1, depth + 1);
10949
+ lines.push(...childLines);
10950
+ }
10951
+ }
10952
+ const formattedFiles = [];
10953
+ for (const file of fileEntries) {
10641
10954
  if (totalItemsScanned >= maxTotalItems) {
10642
10955
  truncated = true;
10643
- lines.push(`${prefix}\u26A0\uFE0F [Truncated - Maximum item limit reached (${maxTotalItems})]`);
10956
+ lines.push(`${indent}[Truncated - Maximum item limit reached (${maxTotalItems})]`);
10644
10957
  break;
10645
10958
  }
10646
- const item = sortedEntries[i];
10647
- const isLast = i === count - 1;
10648
- const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
10649
- const childPrefix = prefix + (isLast ? " " : "\u2502 ");
10650
10959
  totalItemsScanned++;
10651
- let itemType = "unknown";
10960
+ totalFiles++;
10652
10961
  let sizeStr = "N/A";
10653
- let mtimeStr = "N/A";
10654
10962
  try {
10655
- const fStats = fs17.statSync(item.fullPath);
10656
- if (fStats.isDirectory()) {
10657
- itemType = "directory";
10658
- mtimeStr = fStats.mtime.toLocaleString();
10659
- totalDirectories++;
10660
- } else {
10661
- itemType = "file";
10662
- sizeStr = (fStats.size / 1024).toFixed(1) + " KB";
10663
- mtimeStr = fStats.mtime.toLocaleString();
10664
- totalFiles++;
10665
- }
10963
+ const fStats = fs17.statSync(file.fullPath);
10964
+ sizeStr = (fStats.size / 1024).toFixed(1) + "KB";
10666
10965
  } catch (e) {
10667
- itemType = "inaccessible";
10668
- }
10669
- const indicator = itemType === "directory" ? "\u{1F4C1}" : itemType === "file" ? "\u{1F4C4}" : "\u2753";
10670
- let lineText = "";
10671
- if (itemType === "directory") {
10672
- lineText = `${prefix}${connector}${indicator} ${item.name} - [DIR] - [Modified: ${mtimeStr}]`;
10673
- } else {
10674
- lineText = `${prefix}${connector}${indicator} ${item.name} - [Size: ${sizeStr}] - [Modified: ${mtimeStr}]`;
10675
- }
10676
- lines.push(lineText);
10677
- if (itemType === "directory" && currentDepth <= recurseDepth && !isExcludedDir(item.name)) {
10678
- const childLines = buildTree(item.fullPath, currentDepth + 1, childPrefix);
10679
- lines.push(...childLines);
10680
10966
  }
10967
+ formattedFiles.push(`${file.name} (${sizeStr})`);
10968
+ }
10969
+ if (formattedFiles.length > 0) {
10970
+ lines.push(`${indent}${formattedFiles.join("; ")}`);
10681
10971
  }
10682
10972
  return lines;
10683
10973
  };
10684
- const treeLines = buildTree(absolutePath, 1, "");
10974
+ const treeLines = buildTree(absolutePath, 1, 1);
10685
10975
  const formattedTree = treeLines.join("\n");
10686
10976
  let footer = `
10687
10977
 
@@ -10689,9 +10979,9 @@ ${formatted}${footer2}`;
10689
10979
  if (truncated) {
10690
10980
  footer = `
10691
10981
 
10692
- \u26A0\uFE0F TRUNCATED: Scan capped at ${maxTotalItems} items. (Directories: ${totalDirectories}, Files: ${totalFiles})`;
10982
+ TRUNCATED: Scan capped at ${maxTotalItems} items. (Directories: ${totalDirectories}, Files: ${totalFiles})`;
10693
10983
  }
10694
- return `Detailed directory tree for [${targetPath}] (recurse depth: ${recurseDepth}):
10984
+ return `Detailed directory tree for [${targetPath}] (recursive depth: ${recurseDepth}):
10695
10985
 
10696
10986
  ${formattedTree}${footer}`;
10697
10987
  } catch (err) {
@@ -10965,6 +11255,7 @@ var init_write_docx = __esm({
10965
11255
  // src/tools/search_keyword.js
10966
11256
  import fs20 from "fs/promises";
10967
11257
  import path19 from "path";
11258
+ import fg from "fast-glob";
10968
11259
  async function getFilesRecursively(dir, excludes, baseDir = dir, depth = 1) {
10969
11260
  if (depth > 12) return [];
10970
11261
  let results = [];
@@ -10996,35 +11287,50 @@ async function getFilesRecursively(dir, excludes, baseDir = dir, depth = 1) {
10996
11287
  }
10997
11288
  return results;
10998
11289
  }
10999
- function normStr(s) {
11000
- return s.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
11001
- }
11002
- function levenshtein(a, b) {
11290
+ function levenshtein(a, b, cap = Infinity) {
11003
11291
  if (a === b) return 0;
11004
11292
  if (a.length === 0) return b.length;
11005
11293
  if (b.length === 0) return a.length;
11006
- const cap = Math.floor(Math.max(a.length, b.length) / 2) + 1;
11007
- const dp = Array.from({ length: a.length + 1 }, (_, i) => i);
11008
- for (let j = 1; j <= b.length; j++) {
11009
- let prev = dp[0];
11010
- dp[0] = j;
11011
- for (let i = 1; i <= a.length; i++) {
11012
- const tmp = dp[i];
11013
- dp[i] = b[j - 1] === a[i - 1] ? prev : 1 + Math.min(prev, dp[i], dp[i - 1]);
11014
- prev = tmp;
11294
+ if (Math.abs(a.length - b.length) > cap) return cap + 1;
11295
+ let row = Array.from({ length: b.length + 1 }, (_, j) => j);
11296
+ for (let i = 1; i <= a.length; i++) {
11297
+ let nextRow = [i];
11298
+ let minInRow = i;
11299
+ for (let j = 1; j <= b.length; j++) {
11300
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
11301
+ const dist = Math.min(
11302
+ nextRow[j - 1] + 1,
11303
+ // insertion
11304
+ row[j] + 1,
11305
+ // deletion
11306
+ row[j - 1] + cost
11307
+ // substitution
11308
+ );
11309
+ nextRow.push(dist);
11310
+ if (dist < minInRow) minInRow = dist;
11015
11311
  }
11016
- if (Math.min(...dp) > cap) return cap + 1;
11312
+ row = nextRow;
11313
+ if (minInRow > cap) return cap + 1;
11017
11314
  }
11018
- return dp[a.length];
11315
+ return row[b.length];
11019
11316
  }
11020
11317
  function fuzzyMatch(line, keyword) {
11021
- const normLine = normStr(line);
11022
- const lineWords = normLine.split(" ");
11023
- const kwTokens = normStr(keyword).split(" ").filter(Boolean);
11024
- if (normLine.includes(normStr(keyword))) return true;
11025
- return kwTokens.every((token) => {
11026
- const maxDist = token.length <= 2 ? 0 : token.length <= 5 ? 1 : 2;
11027
- return lineWords.some((word) => levenshtein(token, word) <= maxDist);
11318
+ if (!line || !keyword) return false;
11319
+ const normLine = line.toLowerCase();
11320
+ const normKw = keyword.toLowerCase();
11321
+ if (normLine.includes(normKw)) return true;
11322
+ const lineWords = normLine.split(/[^a-z0-9]+/).filter((w) => w.length > 0);
11323
+ const kwTokens = normKw.split(/[^a-z0-9]+/).filter((t) => t.length > 1 || normKw.length === 1 && t.length > 0);
11324
+ if (kwTokens.length === 0) return false;
11325
+ return kwTokens.every((kwToken) => {
11326
+ const maxDist = kwToken.length <= 2 ? 0 : kwToken.length <= 5 ? 1 : 2;
11327
+ for (const lineWord of lineWords) {
11328
+ if (lineWord.includes(kwToken)) return true;
11329
+ if (kwToken.length >= 3 && lineWord.length >= 3 && Math.abs(lineWord.length - kwToken.length) <= maxDist) {
11330
+ if (levenshtein(kwToken, lineWord, maxDist) <= maxDist) return true;
11331
+ }
11332
+ }
11333
+ return false;
11028
11334
  });
11029
11335
  }
11030
11336
  var search_keyword;
@@ -11032,17 +11338,17 @@ var init_search_keyword = __esm({
11032
11338
  "src/tools/search_keyword.js"() {
11033
11339
  init_arg_parser();
11034
11340
  search_keyword = async (args) => {
11035
- const { keyword: rawKeyword, path: pathArg, subString, regex } = parseArgs(args);
11341
+ const { keyword: rawKeyword, path: pathArg, fuzzy, subString, regex } = parseArgs(args);
11036
11342
  if (rawKeyword === void 0 || rawKeyword === null) return 'ERROR: Missing "keyword" argument.';
11037
11343
  const keyword = String(rawKeyword);
11038
11344
  const toBool = (v) => v === true || v === "true" || v === 1 || v === "1" || v === "yes";
11039
11345
  const regexExplicitlyFalse = regex === false || regex === "false" || regex === 0 || regex === "0" || regex === "no";
11040
11346
  const regexExplicitlyTrue = regex === true || regex === "true" || regex === 1 || regex === "1" || regex === "yes";
11041
- let matchSubstring = regexExplicitlyFalse && toBool(subString);
11347
+ const isFuzzy = toBool(fuzzy) || toBool(subString);
11042
11348
  let regexPattern = null;
11043
11349
  let wordRegex = null;
11044
11350
  if (regexExplicitlyFalse) {
11045
- if (!matchSubstring) {
11351
+ if (!isFuzzy) {
11046
11352
  wordRegex = new RegExp(`(?<![\\w])${keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w])`, "i");
11047
11353
  }
11048
11354
  } else {
@@ -11233,21 +11539,67 @@ var init_search_keyword = __esm({
11233
11539
  const rootDir = process.cwd();
11234
11540
  let pathArgType = null;
11235
11541
  if (pathArg) {
11236
- const normalised = pathArg.replace(/[\/\\]+$/, "");
11237
- const fullPath = path19.resolve(rootDir, normalised);
11238
- try {
11239
- const stat = await fs20.stat(fullPath);
11240
- if (stat.isDirectory()) {
11241
- pathArgType = "dir";
11242
- filesToSearch = await getFilesRecursively(fullPath, excludes, rootDir);
11243
- } else if (stat.isFile()) {
11244
- pathArgType = "file";
11245
- filesToSearch.push({ fullPath, relativePath: path19.relative(rootDir, fullPath) });
11542
+ const isGlob = fg.isDynamicPattern(pathArg) || /[*?{}[\]()|+]/.test(pathArg);
11543
+ if (isGlob) {
11544
+ pathArgType = "glob";
11545
+ const posixPath = pathArg.replace(/\\/g, "/");
11546
+ const globExcludes = excludes.map((ex) => ex.startsWith(".") ? `**/*${ex}` : `**/${ex}/**`);
11547
+ const hasRegexSyntax = /[\(\)\|]|\.\*/.test(posixPath);
11548
+ let matchedPaths = [];
11549
+ if (!hasRegexSyntax) {
11550
+ try {
11551
+ matchedPaths = await fg(posixPath, {
11552
+ cwd: rootDir,
11553
+ ignore: globExcludes,
11554
+ dot: true,
11555
+ onlyFiles: true,
11556
+ absolute: false
11557
+ });
11558
+ } catch {
11559
+ matchedPaths = [];
11560
+ }
11561
+ }
11562
+ if (matchedPaths.length === 0 && (hasRegexSyntax || fg.isDynamicPattern(posixPath))) {
11563
+ const baseDirMatch = posixPath.match(/^([^\*\?\(\)\|\[\]\s]+)\//);
11564
+ const scanDir = baseDirMatch && !/[\*\?\(\)\|\[\]]/.test(baseDirMatch[1]) ? path19.resolve(rootDir, baseDirMatch[1]) : rootDir;
11565
+ const allFiles = await getFilesRecursively(scanDir, excludes, rootDir);
11566
+ try {
11567
+ let cleanRegexStr = posixPath.replace(/^\.\//, "");
11568
+ cleanRegexStr = cleanRegexStr.replace(/\.\*\/(\\\.|[^\/])/g, ".*$1");
11569
+ if (!cleanRegexStr.startsWith("^") && !cleanRegexStr.startsWith(".*")) {
11570
+ cleanRegexStr = `.*${cleanRegexStr}`;
11571
+ }
11572
+ const pathRegex = new RegExp(cleanRegexStr.endsWith("$") ? cleanRegexStr : `${cleanRegexStr}$`, "i");
11573
+ filesToSearch = allFiles.filter((f) => {
11574
+ const rel = f.relativePath.replace(/\\/g, "/");
11575
+ return pathRegex.test(rel);
11576
+ });
11577
+ } catch {
11578
+ filesToSearch = [];
11579
+ }
11246
11580
  } else {
11247
- return `ERROR: Path is neither a file nor a directory: ${pathArg}`;
11581
+ filesToSearch = matchedPaths.map((relP) => ({
11582
+ fullPath: path19.resolve(rootDir, relP),
11583
+ relativePath: relP
11584
+ }));
11585
+ }
11586
+ } else {
11587
+ const normalised = pathArg.replace(/[\/\\]+$/, "");
11588
+ const fullPath = path19.resolve(rootDir, normalised);
11589
+ try {
11590
+ const stat = await fs20.stat(fullPath);
11591
+ if (stat.isDirectory()) {
11592
+ pathArgType = "dir";
11593
+ filesToSearch = await getFilesRecursively(fullPath, excludes, rootDir);
11594
+ } else if (stat.isFile()) {
11595
+ pathArgType = "file";
11596
+ filesToSearch.push({ fullPath, relativePath: path19.relative(rootDir, fullPath) });
11597
+ } else {
11598
+ return `ERROR: Path is neither a file nor a directory: ${pathArg}`;
11599
+ }
11600
+ } catch {
11601
+ return `ERROR: Path not found: ${pathArg}`;
11248
11602
  }
11249
- } catch {
11250
- return `ERROR: Path not found: ${pathArg}`;
11251
11603
  }
11252
11604
  } else {
11253
11605
  filesToSearch = await getFilesRecursively(rootDir, excludes);
@@ -11259,7 +11611,7 @@ var init_search_keyword = __esm({
11259
11611
  const lines = content.split(/\r?\n/);
11260
11612
  const fileMatches = [];
11261
11613
  for (let i = 0; i < lines.length; i++) {
11262
- const matched = regexExplicitlyFalse ? matchSubstring ? lines[i].toLowerCase().includes(keyword.toLowerCase()) || fuzzyMatch(lines[i], keyword) : wordRegex && wordRegex.test(lines[i]) : regexPattern && regexPattern.test(lines[i]) || wordRegex && wordRegex.test(lines[i]);
11614
+ const matched = isFuzzy ? lines[i].toLowerCase().includes(keyword.toLowerCase()) || fuzzyMatch(lines[i], keyword) : regexExplicitlyFalse ? wordRegex && wordRegex.test(lines[i]) : regexPattern && regexPattern.test(lines[i]) || wordRegex && wordRegex.test(lines[i]);
11263
11615
  if (matched) {
11264
11616
  fileMatches.push({ line: i + 1, content: lines[i].trim() });
11265
11617
  }
@@ -11285,11 +11637,11 @@ var init_search_keyword = __esm({
11285
11637
  if (typeof global.gc === "function") {
11286
11638
  global.gc();
11287
11639
  }
11288
- const modeLabel = regexExplicitlyFalse ? matchSubstring ? "(subString mode)" : "(keyword mode)" : regexExplicitlyTrue ? "(regex mode)" : "(standard mode)";
11640
+ const modeLabel = isFuzzy ? "(fuzzy mode)" : regexExplicitlyTrue ? "(regex mode)" : regexExplicitlyFalse ? "(keyword mode)" : "(standard mode)";
11289
11641
  if (fileGroups.length === 0) {
11290
- const zeroLocation = pathArgType === "file" ? ` in '${pathArg}'` : pathArgType === "dir" ? ` in '${pathArg}'` : ". Try to specify files";
11291
- const dirPrefix2 = pathArgType === "dir" ? "[DIR]" : "";
11292
- return `${dirPrefix2}Found 0 matches of '${keyword}'${zeroLocation}${modeLabel ? ` ${modeLabel}` : ""}`;
11642
+ const zeroLocation = pathArgType === "file" ? ` in '${pathArg}'` : pathArgType === "dir" || pathArgType === "glob" ? ` in '${pathArg}'` : ". Try to specify files";
11643
+ const dirPrefix2 = pathArgType === "dir" ? "[DIR]" : pathArgType === "glob" ? "[GLOB]" : "";
11644
+ return `${dirPrefix2}${dirPrefix2 ? " " : ""}Found 0 matches of '${keyword}'${zeroLocation}${modeLabel ? ` ${modeLabel}` : ""}`;
11293
11645
  }
11294
11646
  const ml = modeLabel ? ` ${modeLabel}` : "";
11295
11647
  const fileCount = `${fileGroups.length} file${fileGroups.length === 1 ? "" : "s"}`;
@@ -11297,22 +11649,20 @@ var init_search_keyword = __esm({
11297
11649
  let outputHeader;
11298
11650
  if (pathArgType === "file") {
11299
11651
  outputHeader = `Found ${matchCount} of '${keyword}' in '${pathArg}'${ml}:`;
11300
- } else if (pathArgType === "dir") {
11652
+ } else if (pathArgType === "dir" || pathArgType === "glob") {
11301
11653
  outputHeader = `Found ${matchCount} of '${keyword}' in '${pathArg}' across ${fileCount}${ml}:`;
11302
11654
  } else {
11303
11655
  outputHeader = `Found ${matchCount} of '${keyword}' across ${fileCount}${ml}:`;
11304
11656
  }
11305
- const dirPrefix = pathArgType === "dir" ? "[DIR]" : "";
11306
- let output = `${dirPrefix}${outputHeader}
11657
+ const dirPrefix = pathArgType === "dir" ? "[DIR]" : pathArgType === "glob" ? "[GLOB]" : "";
11658
+ let output = `${dirPrefix}${dirPrefix ? " " : ""}${outputHeader}
11307
11659
 
11308
11660
  `;
11309
11661
  for (const group of fileGroups) {
11310
11662
  output += `${group.path}
11311
11663
  `;
11312
- for (let i = 0; i < group.matches.length; i++) {
11313
- const isLast = i === group.matches.length - 1;
11314
- const prefix = isLast ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
11315
- output += `${prefix} ${group.matches[i].line}: ${group.matches[i].content}
11664
+ for (const m of group.matches) {
11665
+ output += ` ${m.line}: ${m.content}
11316
11666
  `;
11317
11667
  }
11318
11668
  output += "\n";
@@ -13176,7 +13526,7 @@ import dotenv from "dotenv";
13176
13526
  import { GoogleGenAI, ThinkingLevel, HarmBlockThreshold, HarmCategory } from "@google/genai";
13177
13527
  import path27, { normalize } from "path";
13178
13528
  import fs28 from "fs";
13179
- var RE_STUTTER_CODE_BLOCK_CLOSED, RE_STUTTER_CODE_BLOCK_OPEN, RE_STUTTER_INLINE_CODE, RE_STUTTER_TABLE_ROW, RE_STUTTER_WORD_BOUNDARY, RE_STUTTER_NON_ALNUM, RE_TOOL_CALL_FUNC, RE_TOOL_PARTIAL_ARGS_FALLBACK, RE_STRIP_QUOTES, RE_BACKSLASH_SLASH, client, globalSettings, dirTreeCache, cachedChatId2, cachedIndentationTree, getCachedDirTree, colorMainWords, withRetry, TERMINATION_SIGNAL, getCleanGroupedLength, stripAnsi2, fetchWithBackoff, getDeepSeekStream, getMistralStream, getNVIDIAStream, wrapNvidiaStreamWithQueueDepth, getOpenRouterStream, signalTermination, isTerminationSignaled, TOOL_LABELS2, getToolDetail, runJanitorTask, getActiveToolContext, getContextSafeText, contextSafeReplace, getSanitizedText, translateKimiToolCalls, detectToolCalls, initAI, generateSimpleContent, consolidatePastMemories, compressHistory, deleteChatSummary, getAIStream, runSubagent;
13529
+ var RE_STUTTER_CODE_BLOCK_CLOSED, RE_STUTTER_CODE_BLOCK_OPEN, RE_STUTTER_INLINE_CODE, RE_STUTTER_TABLE_ROW, RE_STUTTER_WORD_BOUNDARY, RE_STUTTER_NON_ALNUM, RE_TOOL_CALL_FUNC, RE_TOOL_PARTIAL_ARGS_FALLBACK, RE_STRIP_QUOTES, RE_BACKSLASH_SLASH, client, globalSettings, systemInstructionCache, colorMainWords, withRetry, TERMINATION_SIGNAL, getCleanGroupedLength, stripAnsi2, fetchWithBackoff, getDeepSeekStream, getMistralStream, getNVIDIAStream, wrapNvidiaStreamWithQueueDepth, getOpenRouterStream, signalTermination, isTerminationSignaled, TOOL_LABELS2, getToolDetail, runJanitorTask, getActiveToolContext, getContextSafeText, contextSafeReplace, getSanitizedText, translateKimiToolCalls, REGEX_PLACEHOLDER_ARG, REGEX_PLACEHOLDER_VAL, isPlaceholderVal, detectToolCalls, initAI, generateSimpleContent, consolidatePastMemories, compressHistory, deleteChatSummary, getAIStream, runSubagent;
13180
13530
  var init_ai = __esm({
13181
13531
  async "src/utils/ai.js"() {
13182
13532
  await init_prompts();
@@ -13198,6 +13548,7 @@ var init_ai = __esm({
13198
13548
  init_editor();
13199
13549
  init_indentation();
13200
13550
  init_box();
13551
+ await init_main_tools();
13201
13552
  dotenv.config({ quiet: true });
13202
13553
  RE_STUTTER_CODE_BLOCK_CLOSED = /```[\s\S]*?```/g;
13203
13554
  RE_STUTTER_CODE_BLOCK_OPEN = /```[\s\S]*$/g;
@@ -13211,26 +13562,7 @@ var init_ai = __esm({
13211
13562
  RE_BACKSLASH_SLASH = /\\/g;
13212
13563
  client = null;
13213
13564
  globalSettings = {};
13214
- dirTreeCache = null;
13215
- cachedChatId2 = null;
13216
- cachedIndentationTree = null;
13217
- getCachedDirTree = (fn, chatId, isDynamicDirAwareness, indentationTree) => {
13218
- if (isDynamicDirAwareness) {
13219
- dirTreeCache = null;
13220
- cachedChatId2 = chatId;
13221
- cachedIndentationTree = indentationTree;
13222
- return fn();
13223
- }
13224
- if (cachedChatId2 !== chatId || cachedIndentationTree !== indentationTree) {
13225
- dirTreeCache = null;
13226
- cachedChatId2 = chatId;
13227
- cachedIndentationTree = indentationTree;
13228
- }
13229
- if (dirTreeCache === null) {
13230
- dirTreeCache = fn();
13231
- }
13232
- return dirTreeCache;
13233
- };
13565
+ systemInstructionCache = { key: null, value: null };
13234
13566
  colorMainWords = (label) => {
13235
13567
  if (!label) return label;
13236
13568
  return label.replace(/(?:(\x1b\[\d+m))?([✔✘✖🔍📖→➕↻↷•🛇])(?:(\x1b\[\d+m))?\s*\b(Created|Read|Edited|Viewed|Processed|Auto-Read|Skipped|List|Generated|Written|Searched|AI Search|Get Map|Write Canceled|Edit Canceled|Write Cancelled|Edit Denied|Visited|Updated|Reviewed|Delegated|Background|Checked|Indexed|Analyzed|Browsed|Elevating SubAgent|Checking SubAgent Work|Started Generalist|Called Generalist|Unsupported Modality|Awaiting|Cancelled|Aligning Moon Phase|Contemplating Existence|Staring At Void|Rollback Point Checked|Emergency Rollback Failed|Emergency Rollback|Delaying Professionally|Negotiating With Electrons|Touching Grass (virtually)|Panicking Softly|Rethinking Career Choices|Loading Cat Videos|Giving Up Entirely|Summoning Braincell #2|Pretending To Be Busy|Waiting For Motivation DLC|Rotating Internal Screaming|Downloading More RAM|Feeding The Hamsters|Gaslighting Scheduler|Performing Dramatic Pause|Buffering Social Energy|Calculating Regret|Reading Terms And Conditions|Becoming Sentient Briefly|Contacting Ancestors)\b/ig, (match, ansiBefore, icon, ansiAfter, word) => {
@@ -14220,7 +14552,9 @@ var init_ai = __esm({
14220
14552
  }
14221
14553
  let originalTextProcessed = agentText.replace(/\[Prompted on:.*?\]/g, "").trim();
14222
14554
  agentRes = agentRes.replace(/\r?\n\r?\n/g, "\n").replace(/\n\n/g, "\n").replace(/\\n\\n/g, "").trim();
14223
- let userPrompt = `[METADATA] Current date and Time: ${(/* @__PURE__ */ new Date()).toLocaleString([], { year: "numeric", month: "numeric", day: "numeric", hour: "2-digit", hour12: true })}
14555
+ const now1223 = /* @__PURE__ */ new Date();
14556
+ const dateTimeStr1223 = `${now1223.getFullYear()}-${now1223.toLocaleString("en-US", { month: "short" }).toUpperCase()}-${String(now1223.getDate()).padStart(2, "0")}, ${now1223.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: true })}`;
14557
+ let userPrompt = `[METADATA] Current date and Time: ${dateTimeStr1223}
14224
14558
 
14225
14559
  [USER]: ${originalTextProcessed.substring(0, USER_CONTEXT_LENGTH)}
14226
14560
  ${originalTextProcessed.length > USER_CONTEXT_LENGTH ? "... (truncated) ...\n\n" : ""}
@@ -14687,6 +15021,13 @@ ${originalTextProcessed.length > USER_CONTEXT_LENGTH ? "... (truncated) ...\n\n"
14687
15021
  result = result.replace(/<\|\s*tool_calls_section_end\s*\|>/gi, "");
14688
15022
  return result;
14689
15023
  };
15024
+ REGEX_PLACEHOLDER_ARG = /(?:path|query|url|keyword|command|method|title|task|id)\s*=\s*['"`]?\s*\.\.\.\s*['"`]?/i;
15025
+ REGEX_PLACEHOLDER_VAL = /^['"`]?\s*\.\.\.\s*['"`]?$/;
15026
+ isPlaceholderVal = (val) => {
15027
+ if (val === void 0 || val === null) return false;
15028
+ const str = String(val).trim();
15029
+ return str === "..." || str === "\u2026" || REGEX_PLACEHOLDER_VAL.test(str);
15030
+ };
14690
15031
  detectToolCalls = (text) => {
14691
15032
  if (!text) return [];
14692
15033
  const translatedText = translateKimiToolCalls(text);
@@ -14735,11 +15076,15 @@ ${originalTextProcessed.length > USER_CONTEXT_LENGTH ? "... (truncated) ...\n\n"
14735
15076
  if (endIdx !== -1) {
14736
15077
  const finalArgsText = cleanText.substring(startIdx + 1, closingParenIdx);
14737
15078
  const finalFullMatch = cleanText.substring(match.index, endIdx + 1);
14738
- results.push({
14739
- fullMatch: finalFullMatch,
14740
- toolName: toolName.trim(),
14741
- args: finalArgsText.trim()
14742
- });
15079
+ const parsed = parseArgs(finalArgsText);
15080
+ const hasPlaceholderArg = isPlaceholderVal(parsed.path) || isPlaceholderVal(parsed.query) || isPlaceholderVal(parsed.url) || isPlaceholderVal(parsed.keyword) || isPlaceholderVal(parsed.command) || isPlaceholderVal(parsed.method) || isPlaceholderVal(parsed.title) || isPlaceholderVal(parsed.task) || isPlaceholderVal(parsed.id) || REGEX_PLACEHOLDER_ARG.test(finalArgsText);
15081
+ if (!hasPlaceholderArg) {
15082
+ results.push({
15083
+ fullMatch: finalFullMatch,
15084
+ toolName: toolName.trim(),
15085
+ args: finalArgsText.trim()
15086
+ });
15087
+ }
14743
15088
  toolRegex.lastIndex = endIdx + 1;
14744
15089
  }
14745
15090
  }
@@ -14937,7 +15282,8 @@ Chats to process:
14937
15282
  if (oldSummary) {
14938
15283
  prompt += `- Existing Summary: "${oldSummary}"
14939
15284
  `;
14940
- prompt += `-- New Memories to integrate:
15285
+ prompt += `
15286
+ -- New Memories to integrate:
14941
15287
  ${newMemoryListStr}
14942
15288
 
14943
15289
  `;
@@ -15179,7 +15525,12 @@ Provide a consolidated summary of the entire session.`;
15179
15525
  const mainUserMemories = persistentStorage.map((m) => `- ${m.memory}`).join("\n");
15180
15526
  const isContext32k = (sessionStats?.tokens || 0) >= 1e4;
15181
15527
  const memoryPrompt = getMemoryPrompt(otherMemories, mainUserMemories, isMemoryEnabled, isContext32k);
15182
- const dateTimeStr = (/* @__PURE__ */ new Date()).toLocaleString([], { year: "numeric", month: "numeric", day: "numeric", hour: "2-digit", minute: "2-digit", hour12: true });
15528
+ const now = /* @__PURE__ */ new Date();
15529
+ const year = now.getFullYear();
15530
+ const month = now.toLocaleString("en-US", { month: "short" }).toUpperCase();
15531
+ const day = String(now.getDate()).padStart(2, "0");
15532
+ const timeStr = now.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: true });
15533
+ const dateTimeStr = `${year}-${month}-${day}, ${timeStr}`;
15183
15534
  const COLLAPSED_DIRS_GLOBAL = [
15184
15535
  // --- The OG Clutter ---
15185
15536
  ".git",
@@ -15406,8 +15757,11 @@ ${currentSummary}
15406
15757
  **CONTEXT SUMMARY OF PREVIOUS TURNS**
15407
15758
  ${currentSummary}
15408
15759
  ` : "";
15409
- let dirStructure = "\n**DIRECTORY STRUCTURE**\nCWD: " + process.cwd() + `${isPlayground ? " [PLAYGROUND MODE]" : ""}${cwdMismatch ? ` (WARNING: CWD Mismatch! Previous Path: ${lastCwd})` : ""}
15410
- ` + getCachedDirTree(() => getDirTree(process.cwd(), dynamicMaxDepth), chatId, systemSettings?.dynamicDirAwareness, systemSettings?.indentationTree);
15760
+ const dynamicDirAwareness = !!systemSettings?.dynamicDirAwareness;
15761
+ const sysInstructionCacheKey = `${chatId}|${aiProvider}|${thinkingLevel}|${modelName}|${profile}|${dynamicDirAwareness}`;
15762
+ const isSysInstructionCached = !dynamicDirAwareness && systemInstructionCache.key === sysInstructionCacheKey && systemInstructionCache.value;
15763
+ let dirStructure = isSysInstructionCached ? "" : "\n**DIRECTORY STRUCTURE**\nCWD: " + process.cwd() + `${isPlayground ? " [PLAYGROUND MODE]" : ""}${cwdMismatch ? ` (WARNING: CWD Mismatch! Previous Path: ${lastCwd})` : ""}
15764
+ ` + getDirTree(process.cwd(), dynamicMaxDepth);
15411
15765
  const ideCtx = await getIDEContext();
15412
15766
  let ideBlock = "";
15413
15767
  if (isBridgeConnected()) {
@@ -15859,10 +16213,18 @@ ${cleanPromptForModel.trim()}
15859
16213
  throw new Error("Error: Quota Exausted for Agent");
15860
16214
  }
15861
16215
  targetModel = modelName;
15862
- currentSystemInstruction = getSystemInstruction(profile, !(targetModel || "gemma").toLowerCase().startsWith("gemma") ? thinkingLevel : thinkingLevel, mode, systemSettings, isMemoryEnabled, isFirstPrompt, aiProvider, aiProvider === "Google" ? true : isMultiModal, !(targetModel || "gemma").toLowerCase().startsWith("gemma") ? true : false, chatId);
15863
- if (!systemSettings?.dynamicDirAwareness) {
15864
- currentSystemInstruction += `
16216
+ const sysInstructionCacheKey2 = `${chatId}|${aiProvider}|${thinkingLevel}|${targetModel}|${JSON.stringify(profile)}|${!!systemSettings?.dynamicDirAwareness}|${!!systemSettings?.subAgents}`;
16217
+ let isCacheHit = systemInstructionCache.key === sysInstructionCacheKey2 && systemInstructionCache.value;
16218
+ if (isCacheHit) {
16219
+ currentSystemInstruction = systemInstructionCache.value;
16220
+ } else {
16221
+ currentSystemInstruction = getSystemInstruction(profile, !(targetModel || "gemma").toLowerCase().startsWith("gemma") ? thinkingLevel : thinkingLevel, mode, systemSettings, isMemoryEnabled, isFirstPrompt, aiProvider, aiProvider === "Google" ? true : isMultiModal, !(targetModel || "gemma").toLowerCase().startsWith("gemma") ? true : false, chatId);
16222
+ if (!systemSettings?.dynamicDirAwareness) {
16223
+ currentSystemInstruction += `
15865
16224
  ${dirStructure.replace("\n**DIRECTORY STRUCTURE**", "\n**DIRECTORY STRUCTURE**")}`;
16225
+ }
16226
+ systemInstructionCache.key = sysInstructionCacheKey2;
16227
+ systemInstructionCache.value = currentSystemInstruction;
15866
16228
  }
15867
16229
  const lastUserMsg = contents[contents.length - 1];
15868
16230
  if (isBridgeConnected() & loop > 0) {
@@ -16599,8 +16961,8 @@ ${ideErr} [/ERROR]`;
16599
16961
  } else if (normToolName === "list_files" || normToolName === "read_folder") {
16600
16962
  const action = normToolName === "list_files" ? "List" : "Browsed";
16601
16963
  const path29 = parseArgs(toolCall.args).path || null;
16602
- const recurse = parseArgs(toolCall.args).recurse || 0;
16603
- label = `${path29 ? "\u2714" : "\u2718"} ${action}: ${path29 ? `${path29 === "." ? "./" : `${path29.replaceAll("\\", "/")}${recurse > 0 ? `${path29.endsWith("/") ? `*${recurse}` : `/*${recurse}`}` : `${path29.endsWith("/") ? "" : "/"}`}`}` : "No Folder Selected"}`;
16964
+ const recurse = parseArgs(toolCall.args).recurse || 1;
16965
+ label = `${path29 ? "\u2714" : "\u2718"} ${action}: ${path29 ? `${path29 === "." ? `./${recurse > 1 ? "*" : ""}` : `${path29.replaceAll("\\", "/")}${recurse > 1 ? `${path29.endsWith("/") ? `*` : `/*`}` : `${path29.endsWith("/") ? "" : "/"}`}`}` : "No Folder Selected"}`;
16604
16966
  } else if (normToolName === "write_file" || normToolName === "update_file") {
16605
16967
  const action = normToolName === "write_file" ? "Created" : "Edited";
16606
16968
  const path29 = parseArgs(toolCall.args).path || null;
@@ -16659,7 +17021,6 @@ ${ideErr} [/ERROR]`;
16659
17021
  "Panicking Softly",
16660
17022
  "Rethinking Career Choices",
16661
17023
  "Loading Cat Videos",
16662
- "Giving Up Entirely",
16663
17024
  // --- The New Chaos Pack ---
16664
17025
  "Summoning Braincell #2",
16665
17026
  "Pretending To Be Busy",
@@ -17333,8 +17694,10 @@ ${snippet2}`;
17333
17694
  }
17334
17695
  if (normToolName === "search_keyword") {
17335
17696
  const { keyword, path: path29 } = parseArgs(toolCall.args);
17697
+ const _isGlob = typeof result === "string" && result.startsWith("[GLOB]");
17698
+ if (_isGlob) result = result.slice(6).trimStart();
17336
17699
  const _isDir = typeof result === "string" && result.startsWith("[DIR]");
17337
- if (_isDir) result = result.slice(5);
17700
+ if (_isDir) result = result.slice(5).trimStart();
17338
17701
  let matchCount = 0;
17339
17702
  if (result) {
17340
17703
  const m = result.match(/Found (\d+) match/i);
@@ -17343,7 +17706,7 @@ ${snippet2}`;
17343
17706
  }
17344
17707
  }
17345
17708
  const _sp = path29 ? path29.replace(/[\/\\]+$/, "") : null;
17346
- const displayPath = _sp && _sp !== "." ? `"${_isDir ? `${_sp}/*` : _sp}"` : "./";
17709
+ const displayPath = _sp && _sp !== "." ? `"${_isGlob ? path29 : _isDir ? `${_sp}/*` : _sp}"` : "./";
17347
17710
  const postLabel = `${keyword ? "\u2714" : "\u2718"} Searched: "${keyword ? keyword : ""}" in ${displayPath.replaceAll("\\", "/")} \u2192 ${matchCount} Match${matchCount === 1 ? "" : "es"}`;
17348
17711
  let terminalWidth = 115;
17349
17712
  if (process.stdout.isTTY) {
@@ -17800,18 +18163,76 @@ Error Log can be found in ${path27.join(LOGS_DIR, "agent", "error.log")}`);
17800
18163
  runSubagent = async (task, settings, model = null, allowedTools = null, maxTurns = 50, logCallback = null) => {
17801
18164
  const savedSettings = await loadSettings();
17802
18165
  const mergedSettings = { ...savedSettings, ...settings };
17803
- const targetModel = model || settings?.modelName || settings?.activeModel || savedSettings.activeModel;
17804
- const SUBAGENT_TOOL_DEFINITIONS = {
17805
- "readfile": '- [tool:functions.ReadFile(path="...", startLine="integer", endLine="integer")]. View files',
17806
- "readfolder": '- [tool:functions.ReadFolder(path="...", recurse="integer 0-4 optional, default: 0")]. Detailed DIR stats including File Sizes',
17807
- "filemap": '- [tool:functions.FileMap(path="file")]. Shows file structure, functions, class, import/export, variables',
17808
- "patchfile": '- [tool:functions.PatchFile(path="...", allowMultiple="bool optional, default: false", replaceContent1="...", newContent1="...", ...MAX 15)]. Surgical patch. allowMultiple: Replace all matches. Multiple patches same file? Use replaceContent2/newContent2... Verify DIFFs',
17809
- "writefile": '- [tool:functions.WriteFile(path="...", content="...")]. Creates/Overwrites. File Exist? PatchFile > WriteFile. VERIFY IMPORTS',
17810
- "searchkeyword": '- [tool:functions.SearchKeyword(keyword="...", path="optional, target directory/filename", subString="bool optional, default: false", regex="bool optional, default: auto")]. path limits scope to a file/dir. Find definitions/logic without full reads. Locate relevant code',
17811
- "websearch": '- [tool:functions.WebSearch(query="...", aiMode="bool optional, default: false", limit="integer 3-10, aiMode: exclude")]. Usage: unknown info/docs. aiMode: LLM search',
17812
- "webscrape": '- [tool:functions.WebScrape(url="...")]. Proactive use for specific webpage/docs/api',
17813
- "ask": `- [tool:functions.Ask(question="...", optionA="option::description", ...MAX 4)]. Ambiguity: MUST for path divergence, security risk. Ask, don't finish/guess. Suggest best options; no preferences. Keep options short`
18166
+ const envSubagentModel = process.env.SUBAGENT_MODEL ? process.env.SUBAGENT_MODEL.trim() : null;
18167
+ const envSubagentProviderRaw = process.env.SUBAGENT_PROVIDER ? process.env.SUBAGENT_PROVIDER.trim() : null;
18168
+ const subagentNow = /* @__PURE__ */ new Date();
18169
+ const time = `${subagentNow.getFullYear()}-${subagentNow.toLocaleString("en-US", { month: "short" }).toUpperCase()}-${String(subagentNow.getDate()).padStart(2, "0")}, ${subagentNow.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: true }).replace(":", "-")}`;
18170
+ const normalizeProvider = (pStr) => {
18171
+ if (!pStr) return null;
18172
+ const lower = pStr.toLowerCase();
18173
+ if (lower === "google") return "Google";
18174
+ if (lower === "deepseek") return "DeepSeek";
18175
+ if (lower === "openrouter") return "OpenRouter";
18176
+ if (lower === "nvidia") return "NVIDIA";
18177
+ if (lower === "mistral") return "Mistral";
18178
+ return null;
18179
+ };
18180
+ const envSubagentProvider = normalizeProvider(envSubagentProviderRaw);
18181
+ const configuredSubAgentModel = mergedSettings?.systemSettings?.CustomSubAgent ? mergedSettings?.systemSettings?.SubAgentModel : null;
18182
+ const configuredSubAgentProvider = mergedSettings?.systemSettings?.CustomSubAgent ? mergedSettings?.systemSettings?.SubAgentProvider : null;
18183
+ let subAgentCustomModel = null;
18184
+ if (configuredSubAgentModel === "ENV") {
18185
+ subAgentCustomModel = envSubagentModel;
18186
+ if (envSubagentModel && envSubagentProvider) {
18187
+ mergedSettings.aiProvider = envSubagentProvider;
18188
+ }
18189
+ } else if (configuredSubAgentModel && configuredSubAgentModel !== "Default") {
18190
+ subAgentCustomModel = configuredSubAgentModel;
18191
+ if (configuredSubAgentProvider) {
18192
+ mergedSettings.aiProvider = configuredSubAgentProvider;
18193
+ }
18194
+ } else if (envSubagentModel) {
18195
+ if (envSubagentProvider) {
18196
+ mergedSettings.aiProvider = envSubagentProvider;
18197
+ }
18198
+ }
18199
+ if (mergedSettings.aiProvider) {
18200
+ const providerApiKey = await getProviderAPIKey(mergedSettings.aiProvider);
18201
+ if (providerApiKey) {
18202
+ mergedSettings.apiKey = providerApiKey;
18203
+ }
18204
+ }
18205
+ const isSubagentCommandAllowed = (cmdString) => {
18206
+ if (!cmdString || typeof cmdString !== "string") return { allowed: true };
18207
+ const DANGEROUS_PATTERNS = [
18208
+ // Destructive file deletion / formatting
18209
+ /rm\s+-[rf]{1,2}\s+[\/*.]/i,
18210
+ /rmdir\s+\/[sq]/i,
18211
+ /del\s+\/[fsq]/i,
18212
+ /\bformat\b\s+[a-z]:/i,
18213
+ /mkfs/i,
18214
+ /dd\s+if=/i,
18215
+ // System shutdown / reboot / killall
18216
+ /\b(shutdown|reboot|poweroff|init\s+0|init\s+6)\b/i,
18217
+ // Low level disk / raw write / partition / chmod dangerous
18218
+ /chmod\s+(-R\s+)?777\s+[\/*.]/i,
18219
+ /chown\s+(-R\s+)?root/i,
18220
+ // Dangerous git force / reset operations on remote / system
18221
+ /git\s+push\s+.*--force/i,
18222
+ /git\s+clean\s+-fdx/i,
18223
+ // System-level privilege escalation
18224
+ /\bsudo\s+su\b/i,
18225
+ /\bsu\s+-\b/i
18226
+ ];
18227
+ for (const pattern of DANGEROUS_PATTERNS) {
18228
+ if (pattern.test(cmdString)) {
18229
+ return { allowed: false, reason: `Blocked potentially destructive or unsafe command pattern: "${pattern.source}"` };
18230
+ }
18231
+ }
18232
+ return { allowed: true };
17814
18233
  };
18234
+ const targetModel = model || subAgentCustomModel || settings?.modelName || settings?.activeModel || savedSettings.activeModel;
18235
+ const osDetected = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
17815
18236
  const providedToolsSection = `-- TOOL DEFINITIONS (path = relative to CWD, path separator: '/') --
17816
18237
  TO ACCESS TOOLS **STRICTLY USE THE EXACT FORMAT IN CHAT OUTPUT:** [tool:functions.ToolName(args)]
17817
18238
  **NO OTHER SYNTAX/MARKERS/BOUNDARY ALLOWED**
@@ -17819,15 +18240,26 @@ TO ACCESS TOOLS **STRICTLY USE THE EXACT FORMAT IN CHAT OUTPUT:** [tool:function
17819
18240
  TOOL POLICY:
17820
18241
  - MAX 3 TOOL CALLS PER TURN
17821
18242
  - Same file, many edits? Prefer multi search-replace in Patch \u2190 **HIGHLY RECOMMENDED**
17822
- - FileMap \u2192 ReadFile for efficient file understanding
17823
- - Need specific text ? SearchKeyword > Guessing/ReadFile
17824
- - Huge files ? SearchKeyword > FileMap/Full Read
17825
- - NO Shell Access
18243
+ - Need specific text OR huge file ? SearchKeyword > ReadFile
18244
+ - Tool denied? Use \`Ask\` immediately for user guidance \u2190 **MANDATORY**
18245
+ - Restricted Shell Access, NO DELETION
18246
+
18247
+ **PROVIDED TOOLS**
18248
+ -- Communication with USER --
18249
+ - [tool:functions.Ask(question="...", optionA="option::description", ...MAX 4)]. Ambiguity: MUST for path divergence, security risk. Ask, don't finish/guess. Suggest best options; no preferences. Keep options short
17826
18250
 
17827
- -- PROVIDED TOOLS --
17828
- ${Object.values(SUBAGENT_TOOL_DEFINITIONS).join("\n")}
18251
+ -- Web Tools --
18252
+ - [tool:functions.WebSearch(query="...", aiMode="bool optional, default: false", limit="integer 3-10, aiMode: exclude")]. Usage: unknown info/docs. aiMode: LLM search
18253
+ - [tool:functions.WebScrape(url="...")]. Proactive use for specific webpage/docs/api
18254
+
18255
+ -- Workspace Tools --
18256
+ - [tool:functions.SearchKeyword(keyword="...", path="optional, dir/file/glob/regex", fuzzy="bool optional, default: false", regex="bool optional, default: auto")]. path limits search scope. Find definitions/logic without full reads. Locate relevant code
18257
+ - [tool:functions.ReadFolder(path="...", recurse="integer 1-3 optional, default: 1")]. DIR Contents + File Size. Minimize recursion
18258
+ - [tool:functions.ReadFile(path="...", startLine="integer", endLine="integer")]. View files
18259
+ - [tool:functions.PatchFile(path="...", allowMultiple="bool optional, default: false", replaceContent1="...", newContent1="...", ...MAX 15)]. Surgical patchs, TARGET SMALLEST LINES. allowMultiple: Replace all matches ONLY WHERE SURE. Use replaceContent2/newContent2... for multi blocks. Verify DIFFs
18260
+ - [tool:functions.WriteFile(path="...", content="...")]. Creates/Overwrites. File Exist? PatchFile > WriteFile. VERIFY IMPORTS
18261
+ - [tool:functions.Run(command="...")]. Runs ${osDetected === "Windows" ? isPsAvailable() ? `WINDOWS POWERSHELL` : `WINDOWS CMD` : `BASH`} command. Destructive/Irreversible ops \u2192 Ask user
17829
18262
 
17830
- - VERIFY TOOL RESULT CONTENTS. Fix errors. No hallucinations
17831
18263
  - **Escape quotes: \\" for code strings**
17832
18264
  - **Literal escapes: Double-escape sequences (e.g., \\\\n)**
17833
18265
  - **File structure: Real newlines for code formatting**`.trim();
@@ -17838,12 +18270,12 @@ Your task is: "${task}"
17838
18270
  ${providedToolsSection.trimEnd()}
17839
18271
 
17840
18272
  -- THINKING GUIDANCE --
17841
- NO EXPLICIT THINKING REQUIRED. FOCUS ON COMPLETING THE TASK DIRECTLY
17842
-
18273
+ NO EXPLICIT THINKING REQUIRED. FOCUS ON TASK COMPLETION
17843
18274
  Keep main focus on tools and task, not chatting
17844
- Once you have fully completed the task, provide a detailed structured summary preferebly in Tables/Bullet Points with file modified info, if any task failed report back in detail, no hallucination
18275
+ On task completion, provide a detailed structured summary preferebly in Tables/Bullet Points with file modified info, if any task failed report back in detail, no hallucination
17845
18276
 
17846
18277
  CWD: ${process.cwd()}
18278
+ Current Time: ${time}
17847
18279
  === END SYSTEM PROMPT ===`;
17848
18280
  const subagentHistory = [
17849
18281
  { role: "user", text: `Complete this task: ${task}` }
@@ -17909,11 +18341,26 @@ ${cleanResponse}
17909
18341
  `;
17910
18342
  continue;
17911
18343
  }
18344
+ if (normalizedToolName === "exec_command" || normalizedToolName === "execcommand" || normalizedToolName === "run") {
18345
+ const cmdArg = parseArgs(toolCall.args).command || "";
18346
+ const cmdCheck = isSubagentCommandAllowed(cmdArg);
18347
+ if (!cmdCheck.allowed) {
18348
+ const blockMsg = `ERROR: [SECURITY RESTRICTION] Subagent execution blocked command: ${cmdCheck.reason}`;
18349
+ if (logCallback) logCallback(`[Blocked Command] ${cmdArg} - ${cmdCheck.reason}
18350
+ `);
18351
+ toolResultsStr += `${blockMsg}
18352
+
18353
+ `;
18354
+ continue;
18355
+ }
18356
+ }
17912
18357
  let label = "";
17913
18358
  if (normalizedToolName === "web_search" || normalizedToolName === "websearch") {
17914
- label = `\u2714 \x1B[95mSearched\x1B[0m`;
18359
+ const query = parseArgs(toolCall.args).query || "";
18360
+ label = `\u2714 \x1B[95mSearched\x1B[0m: ${query}`;
17915
18361
  } else if (normalizedToolName === "web_scrape" || normalizedToolName === "webscrape") {
17916
- label = `\u2714 \x1B[95mScraped\x1B[0m`;
18362
+ const url = parseArgs(toolCall.args).url || "";
18363
+ label = `\u2714 \x1B[95mScraped\x1B[0m: ${url}`;
17917
18364
  } else if (normalizedToolName === "search_keyword" || normalizedToolName === "searchkeyword") {
17918
18365
  const pArgs = parseArgs(toolCall.args);
17919
18366
  const keyword = pArgs.keyword || "";
@@ -17925,7 +18372,7 @@ ${cleanResponse}
17925
18372
  } else if (normalizedToolName === "list_files" || normalizedToolName === "read_folder" || normalizedToolName === "readfolder") {
17926
18373
  const path29 = parseArgs(toolCall.args).path || null;
17927
18374
  const recurse = parseArgs(toolCall.args).recurse || 0;
17928
- label = `${path29 ? "\u2714" : "\u2718"} \x1B[95mBrowsed\x1B[0m: ${path29 ? `${path29.replaceAll("\\", "/")}${recurse > 0 ? `${path29.endsWith("/") ? `*${recurse}` : `/*${recurse}`}` : `${path29.endsWith("/") ? "" : "/"}`}` : ""}`;
18375
+ label = `${path29 ? "\u2714" : "\u2718"} \x1B[95mBrowsed\x1B[0m: ${path29 ? `${path29.replaceAll("\\", "/")}${recurse > 0 ? `${path29.endsWith("/") ? `*${recurse}` : `/*${recurse}`}` : `${path29.endsWith("/") ? "" : "/"}`}` : ""}`;
17929
18376
  } else if (normalizedToolName === "write_file" || normalizedToolName === "writefile") {
17930
18377
  const path29 = parseArgs(toolCall.args).path || null;
17931
18378
  label = `${path29 ? "\u2714" : "\u2718"} \x1B[95mCreated\x1B[0m: ${path29 ? `${path29.replaceAll("\\", "/")}` : "No File Changes"}`;
@@ -17933,12 +18380,15 @@ ${cleanResponse}
17933
18380
  const path29 = parseArgs(toolCall.args).path || null;
17934
18381
  const content = parseArgs(toolCall.args).content || null;
17935
18382
  label = `${path29 ? "\u2714" : "\u2718"} \x1B[95mEdited\x1B[0m: ${path29 ? `${path29.replaceAll("\\", "/")}` : "No File Changes"}`;
18383
+ } else if (normalizedToolName === "exec_command" || normalizedToolName === "execcommand" || normalizedToolName === "run") {
18384
+ const command = parseArgs(toolCall.args).command || null;
18385
+ label = `${command ? "\u2714" : "\u2718"} \x1B[95mExecuted\x1B[0m: ${command ? command.slice(0, 100) + (command.length > 100 ? "..." : "") : "No Command"}`;
17936
18386
  } else if (normalizedToolName === "file_map" || normalizedToolName === "filemap") {
17937
18387
  const path29 = parseArgs(toolCall.args).path || "";
17938
18388
  label = `${path29 ? "\u2714" : "\u2718"} \x1B[95mIndexed\x1B[0m: ${path29 ? `${path29.replaceAll("\\", "/")}` : "File Not Found"}`;
17939
18389
  } else if (normalizedToolName === "await") {
17940
- const { time } = parseArgs(toolCall.args);
17941
- let sec = parseFloat(time) || 0;
18390
+ const { time: time2 } = parseArgs(toolCall.args);
18391
+ let sec = parseFloat(time2) || 0;
17942
18392
  if (sec < 10) sec = 10;
17943
18393
  if (sec > 180) sec = 180;
17944
18394
  const formatTime = (s) => {
@@ -19028,7 +19478,7 @@ var app_exports = {};
19028
19478
  __export(app_exports, {
19029
19479
  default: () => App
19030
19480
  });
19031
- import os5 from "os";
19481
+ import os4 from "os";
19032
19482
  import React16, { useState as useState15, useEffect as useEffect12, useRef as useRef4, useMemo as useMemo2 } from "react";
19033
19483
  import { Box as Box14, Text as Text16, useInput as useInput9, useStdout as useStdout2, Static } from "ink";
19034
19484
  import fs30 from "fs-extra";
@@ -19056,6 +19506,7 @@ function App({ args = [] }) {
19056
19506
  const [showBridgePromo, setShowBridgePromo] = useState15(false);
19057
19507
  const [promoSelectedIndex, setPromoSelectedIndex] = useState15(0);
19058
19508
  const suggestionOffsetRef = useRef4(0);
19509
+ const maxScrollRef = useRef4(0);
19059
19510
  const persistedModelRef = useRef4(null);
19060
19511
  const activeStreamingMsgRef = useRef4(null);
19061
19512
  const [renderTick, setRenderTick] = useState15(0);
@@ -19172,6 +19623,108 @@ function App({ args = [] }) {
19172
19623
  clearInterval(memInterval);
19173
19624
  };
19174
19625
  }, []);
19626
+ useEffect12(() => {
19627
+ const checkSubAgentModelOnStartup = async () => {
19628
+ try {
19629
+ const settings = await loadSettings();
19630
+ const sysSettings = settings?.systemSettings || {};
19631
+ const customSubAgent = sysSettings.CustomSubAgent;
19632
+ const configuredModel = sysSettings.SubAgentModel;
19633
+ if (!customSubAgent || !configuredModel || configuredModel === "Default") {
19634
+ return;
19635
+ }
19636
+ const envModel = process.env.SUBAGENT_MODEL ? process.env.SUBAGENT_MODEL.trim() : null;
19637
+ const envProviderRaw = process.env.SUBAGENT_PROVIDER ? process.env.SUBAGENT_PROVIDER.trim() : null;
19638
+ const ALL_PROVIDERS = ["Google", "DeepSeek", "OpenRouter", "NVIDIA", "Mistral"];
19639
+ const normalizeProvider = (pStr) => {
19640
+ if (!pStr) return null;
19641
+ const lower = pStr.toLowerCase();
19642
+ if (lower === "google") return "Google";
19643
+ if (lower === "deepseek") return "DeepSeek";
19644
+ if (lower === "openrouter") return "OpenRouter";
19645
+ if (lower === "nvidia") return "NVIDIA";
19646
+ if (lower === "mistral") return "Mistral";
19647
+ return null;
19648
+ };
19649
+ const envProvider = normalizeProvider(envProviderRaw);
19650
+ if (envModel && !envProvider) {
19651
+ const currentActiveProv = settings.aiProvider || aiProvider || "Google";
19652
+ setMessages((prev) => {
19653
+ setCompletedIndex(prev.length + 1);
19654
+ return [...prev, {
19655
+ id: "subagent-env-noprov-" + Date.now(),
19656
+ role: "system",
19657
+ text: `[SUBAGENT CONFIG] SUBAGENT_MODEL found in ENV but SUBAGENT_PROVIDER is missing/invalid. Active provider (${currentActiveProv}) will be used.`,
19658
+ isMeta: true
19659
+ }];
19660
+ });
19661
+ }
19662
+ if (configuredModel === "ENV") {
19663
+ if (!envModel) {
19664
+ setMessages((prev) => {
19665
+ setCompletedIndex(prev.length + 1);
19666
+ return [...prev, {
19667
+ id: "subagent-model-noenv-" + Date.now(),
19668
+ role: "system",
19669
+ text: "No SubAgent model is found in ENV, Using Deafult until changed",
19670
+ isMeta: true
19671
+ }];
19672
+ });
19673
+ }
19674
+ return;
19675
+ }
19676
+ const currentProvider = settings.aiProvider || aiProvider || "Google";
19677
+ const currentTier = settings.apiTier || apiTier || "Free";
19678
+ const quotasObj = settings.quotas || quotas || {};
19679
+ const availableModelNamesSet = /* @__PURE__ */ new Set();
19680
+ const currentModelsRaw = getModels(currentProvider, currentTier) || [];
19681
+ currentModelsRaw.forEach((m) => {
19682
+ const name = typeof m === "string" ? m : m.cmd || m.name || m.id || String(m);
19683
+ if (name) availableModelNamesSet.add(name);
19684
+ });
19685
+ for (const p of ALL_PROVIDERS) {
19686
+ try {
19687
+ const key = await getProviderAPIKey(p);
19688
+ if (key) {
19689
+ const tier = quotasObj?.providerTiers?.[p] || "Free";
19690
+ const pModels = getModels(p, tier) || [];
19691
+ pModels.forEach((m) => {
19692
+ const name = typeof m === "string" ? m : m.cmd || m.name || m.id || String(m);
19693
+ if (name) availableModelNamesSet.add(name);
19694
+ });
19695
+ }
19696
+ } catch (e) {
19697
+ }
19698
+ }
19699
+ const isModelAvailable = availableModelNamesSet.has(configuredModel);
19700
+ if (envModel) {
19701
+ if (envModel !== configuredModel) {
19702
+ setMessages((prev) => {
19703
+ setCompletedIndex(prev.length + 1);
19704
+ return [...prev, {
19705
+ id: "subagent-model-env-" + Date.now(),
19706
+ role: "system",
19707
+ text: "Current Seleted Sub-Agent model is not available in this provider. Using model from ENV unless changed.",
19708
+ isMeta: true
19709
+ }];
19710
+ });
19711
+ }
19712
+ } else if (!isModelAvailable) {
19713
+ setMessages((prev) => {
19714
+ setCompletedIndex(prev.length + 1);
19715
+ return [...prev, {
19716
+ id: "subagent-model-err-" + Date.now(),
19717
+ role: "system",
19718
+ text: "Current Seleted Sub-Agent model is not available in this provider. Using Default until changed",
19719
+ isMeta: true
19720
+ }];
19721
+ });
19722
+ }
19723
+ } catch (err) {
19724
+ }
19725
+ };
19726
+ checkSubAgentModelOnStartup();
19727
+ }, []);
19175
19728
  const parsedArgs = useMemo2(() => {
19176
19729
  const parsed = {};
19177
19730
  for (let i = 0; i < args.length; i++) {
@@ -19441,6 +19994,7 @@ function App({ args = [] }) {
19441
19994
  const [monthlyUsage, setMonthlyUsage] = useState15(null);
19442
19995
  const [customPeriodUsage, setCustomPeriodUsage] = useState15(null);
19443
19996
  const [statsMode, setStatsMode] = useState15("daily");
19997
+ const [statsScrollOffset, setStatsScrollOffset] = useState15(0);
19444
19998
  const PLAYGROUND_CHAT_ID = "flow-playground";
19445
19999
  const [chatId, setChatId] = useState15(args.includes("--playground") ? PLAYGROUND_CHAT_ID : generateChatId());
19446
20000
  useEffect12(() => {
@@ -19615,7 +20169,7 @@ function App({ args = [] }) {
19615
20169
  useEffect12(() => setEscPressCount(0), [input]);
19616
20170
  const [messages, rawSetMessages] = useState15(() => {
19617
20171
  const logoMsg = { id: "logo-" + Date.now(), role: "system", isLogo: true, isMeta: true };
19618
- const isHomeDir = process.cwd() === os5.homedir();
20172
+ const isHomeDir = process.cwd() === os4.homedir();
19619
20173
  const isSystemDir = (() => {
19620
20174
  const cwd = process.cwd().toLowerCase();
19621
20175
  if (process.platform === "win32") {
@@ -19642,7 +20196,7 @@ function App({ args = [] }) {
19642
20196
  id: "home-warning",
19643
20197
  role: "system",
19644
20198
  text: `[SECURITY ALERT] HOME DIRECTORY DETECTED`,
19645
- subText: `You are currently in ${os5.homedir()}. Working here is high-risk as the agent may modify system-sensitive configurations. Please open FluxFlow in project folder.`,
20199
+ subText: `You are currently in ${os4.homedir()}. Working here is high-risk as the agent may modify system-sensitive configurations. Please open FluxFlow in project folder.`,
19646
20200
  isHomeWarning: true
19647
20201
  });
19648
20202
  }
@@ -19821,10 +20375,20 @@ function App({ args = [] }) {
19821
20375
  if (prev === "modelBreakdown") return "daily";
19822
20376
  return prev === "daily" ? "monthly" : "daily";
19823
20377
  });
20378
+ setStatsScrollOffset(0);
19824
20379
  return;
19825
20380
  }
19826
20381
  if (key.space || inputText === " ") {
19827
20382
  setStatsMode((prev) => prev === "modelBreakdown" ? "daily" : "modelBreakdown");
20383
+ setStatsScrollOffset(0);
20384
+ return;
20385
+ }
20386
+ if (key.upArrow) {
20387
+ setStatsScrollOffset((prev) => Math.max(0, prev - 1));
20388
+ return;
20389
+ }
20390
+ if (key.downArrow) {
20391
+ setStatsScrollOffset((prev) => Math.min(maxScrollRef.current, prev + 1));
19828
20392
  return;
19829
20393
  }
19830
20394
  }
@@ -22619,13 +23183,13 @@ Selection: ${val}`,
22619
23183
  const limitsNotSet = !usingProviderBudgets && (shouldClearValue(reqLimit) || shouldClearValue(tokenLimit) || shouldClearValue(monthlyLimit));
22620
23184
  let resetInfo = "";
22621
23185
  if (quotas.resetMode === "Custom") {
22622
- const today = /* @__PURE__ */ new Date();
23186
+ const today2 = /* @__PURE__ */ new Date();
22623
23187
  const resetDay = quotas.resetDay || 1;
22624
- let resetMonth = today.getMonth();
22625
- if (today.getDate() >= resetDay) {
23188
+ let resetMonth = today2.getMonth();
23189
+ if (today2.getDate() >= resetDay) {
22626
23190
  resetMonth += 1;
22627
23191
  }
22628
- const resetDate = new Date(today.getFullYear(), resetMonth, resetDay);
23192
+ const resetDate = new Date(today2.getFullYear(), resetMonth, resetDay);
22629
23193
  const monthName = resetDate.toLocaleString("default", { month: "short" });
22630
23194
  resetInfo = `${monthName}-${resetDay}`;
22631
23195
  }
@@ -22757,10 +23321,62 @@ Selection: ${val}`,
22757
23321
  const imageCreditsLabel = statsMode === "monthly" ? "Image Credits:" : "Image Credits:";
22758
23322
  const codeChangesLabel = statsMode === "monthly" ? "Code Changes:" : "Code Changes:";
22759
23323
  const toolCallsLabel = statsMode === "monthly" ? "Tool Calls:" : "Tool Calls:";
22760
- return /* @__PURE__ */ React16.createElement(Box14, { flexDirection: "column", borderStyle: "round", borderColor: colors.borderMuted, paddingX: 3, paddingY: 1, paddingBottom: 0, width: Math.min(125, (stdout?.columns || 100) - 2) }, statsMode === "modelBreakdown" ? /* @__PURE__ */ React16.createElement(Box14, { flexDirection: "column" }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.text, bold: true, underline: true }, "30-DAY MODEL TOKEN BREAKDOWN"), !monthlyUsage?.models || Object.keys(monthlyUsage.models).length === 0 ? /* @__PURE__ */ React16.createElement(Box14, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted, italic: true }, "No model token usage recorded in the last 30 days.")) : Object.entries(monthlyUsage.models).map(([provider, models]) => {
22761
- const providerTotalTokens = Object.values(models).reduce((sum, m) => sum + (m.tokens || 0), 0);
22762
- return /* @__PURE__ */ React16.createElement(Box14, { key: provider, flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 40 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.primary, bold: true }, provider, ":")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text, bold: true }, formatTokens(providerTotalTokens))), Object.entries(models).map(([modelName, stats]) => /* @__PURE__ */ React16.createElement(Box14, { key: modelName, flexDirection: "column", marginLeft: 4, marginTop: 1 }, /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 36 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "\xBB ", modelName, ":")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens(stats.tokens || 0))), /* @__PURE__ */ React16.createElement(Box14, { marginLeft: 4 }, /* @__PURE__ */ React16.createElement(Box14, { width: 32 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\xBB Input Tokens:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens((stats.tokens || 0) - (stats.candidateTokens || 0)))), (stats.cachedTokens || 0) > 0 && /* @__PURE__ */ React16.createElement(Box14, { marginLeft: 5 }, /* @__PURE__ */ React16.createElement(Box14, { width: 31 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\xBB Cached:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens(stats.cachedTokens))), /* @__PURE__ */ React16.createElement(Box14, { marginLeft: 4 }, /* @__PURE__ */ React16.createElement(Box14, { width: 32 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\xBB Output Tokens:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens(stats.candidateTokens || 0))))));
22763
- })) : /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement(Box14, { marginBottom: 1 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.text, bold: true, underline: true }, "SESSION TELEMETRY")), /* @__PURE__ */ React16.createElement(Box14, { flexDirection: "column" }, /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Session Duration:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatMsDuration(Date.now() - SESSION_START_TIME))), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Model Requests:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, sessionAgentCalls)), /* @__PURE__ */ React16.createElement(Box14, { marginLeft: 2 }, /* @__PURE__ */ React16.createElement(Box14, { width: 23 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\xBB API Time:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatMsDuration(sessionApiTime))), /* @__PURE__ */ React16.createElement(Box14, { marginLeft: 2 }, /* @__PURE__ */ React16.createElement(Box14, { width: 23 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\xBB Tool Time:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatMsDuration(sessionToolTime))), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Memory Agent:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, sessionBackgroundCalls)), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Tokens Consumed:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens(sessionTotalTokens))), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Active Context:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens(sessionStats.tokens))), sessionTotalTokens > 0 && /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement(Box14, { marginLeft: 2 }, /* @__PURE__ */ React16.createElement(Box14, { width: 23 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\xBB Input Tokens:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens(sessionTotalTokens - sessionTotalCandidateTokens))), sessionTotalCachedTokens > 0 && /* @__PURE__ */ React16.createElement(Box14, { marginLeft: 4 }, /* @__PURE__ */ React16.createElement(Box14, { width: 21 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\xBB Cached:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens(sessionTotalCachedTokens))), sessionTotalCandidateTokens > 0 && /* @__PURE__ */ React16.createElement(Box14, { marginLeft: 2 }, /* @__PURE__ */ React16.createElement(Box14, { width: 23 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\xBB Output Tokens:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens(sessionTotalCandidateTokens)))), sessionImageCount > 0 && /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Images Made:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, sessionImageCount)), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Image Credits:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, Number(((sessionImageCredits || 0) * 1e3).toFixed(0)), " credits"))), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Code Changes (Sess):")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.success || "green" }, "+", runtimeSession.linesAdded), " ", /* @__PURE__ */ React16.createElement(Text16, { color: colors.danger || "red" }, "-", runtimeSession.linesRemoved))), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Tool Calls (Sess):")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, runtimeSession.toolSuccess + runtimeSession.toolFailure + runtimeSession.toolDenied, " ( "), /* @__PURE__ */ React16.createElement(Text16, { color: colors.success || "green" }, "\u2714 ", runtimeSession.toolSuccess), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, " "), /* @__PURE__ */ React16.createElement(Text16, { color: colors.warning || "yellow" }, "\u{1F6C7} ", runtimeSession.toolDenied), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, " "), /* @__PURE__ */ React16.createElement(Text16, { color: colors.danger || "red" }, "\u2718 ", runtimeSession.toolFailure), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, " )"))), /* @__PURE__ */ React16.createElement(Box14, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.text, bold: true, underline: true }, trackerTitle), /* @__PURE__ */ React16.createElement(Box14, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, timeLabel)), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatDuration(u?.duration || 0))), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Model Requests:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, u?.agent || 0)), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Memory Agent:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, u?.background || 0)), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, tokensLabel)), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens(u?.tokens || 0))), (u?.tokens || 0) > 0 && /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement(Box14, { marginLeft: 2 }, /* @__PURE__ */ React16.createElement(Box14, { width: 23 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\xBB Input Tokens:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens((u?.tokens || 0) - (u?.candidateTokens || 0)))), (u?.cachedTokens || 0) > 0 && /* @__PURE__ */ React16.createElement(Box14, { marginLeft: 4 }, /* @__PURE__ */ React16.createElement(Box14, { width: 21 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\xBB Cached:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens(u.cachedTokens))), (u?.candidateTokens || 0) > 0 && /* @__PURE__ */ React16.createElement(Box14, { marginLeft: 2 }, /* @__PURE__ */ React16.createElement(Box14, { width: 23 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\xBB Output Tokens:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens(u.candidateTokens)))), (u?.imageCalls?.length || 0) > 0 && /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, imagesLabel)), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, u.imageCalls.length)), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, imageCreditsLabel)), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, Number(((u.imageCalls.reduce((sum, c) => sum + c.cost, 0) || 0) * 1e3).toFixed(0)), " credits"))), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, codeChangesLabel)), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.success || "green" }, "+", u?.linesAdded || 0), " ", /* @__PURE__ */ React16.createElement(Text16, { color: colors.danger || "red" }, "-", u?.linesRemoved || 0))), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, toolCallsLabel)), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, (u?.toolSuccess || 0) + (u?.toolFailure || 0) + (u?.toolDenied || 0), " ( "), /* @__PURE__ */ React16.createElement(Text16, { color: colors.success || "green" }, "\u2714 ", u?.toolSuccess || 0), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, " "), /* @__PURE__ */ React16.createElement(Text16, { color: colors.warning || "yellow" }, "\u{1F6C7} ", u?.toolDenied || 0), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, " "), /* @__PURE__ */ React16.createElement(Text16, { color: colors.danger || "red" }, "\u2718 ", u?.toolFailure || 0), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, " )")))), /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted, dimColor: true, marginTop: 1, italic: true }, "(Press TAB to toggle Daily/Monthly views, SPACE for Model Breakdown, ESC to return)"));
23324
+ const maxRows = Math.max(4, (stdout?.rows || terminalSize?.rows || 24) - 15);
23325
+ const renderLeaderRow = (key, leftText, rightText, leftColor, rightColor, indent = 0, isBold = false) => {
23326
+ const cols = stdout?.columns || terminalSize?.columns || 80;
23327
+ const boxWidth = Math.min(125, cols - 2);
23328
+ const lineWidth = Math.max(20, boxWidth - 6);
23329
+ const maxLeftLen = Math.max(5, lineWidth - indent - rightText.length - 5);
23330
+ let cleanLeftText = leftText;
23331
+ if (cleanLeftText.length > maxLeftLen) {
23332
+ cleanLeftText = cleanLeftText.substring(0, maxLeftLen - 1) + "\u2026";
23333
+ }
23334
+ const dotsCount = Math.max(2, lineWidth - indent - cleanLeftText.length - rightText.length - 2);
23335
+ const dotsStr = " " + ".".repeat(dotsCount) + " ";
23336
+ const indentStr = " ".repeat(indent);
23337
+ return /* @__PURE__ */ React16.createElement(Box14, { key, width: lineWidth }, /* @__PURE__ */ React16.createElement(Text16, { wrap: "truncate" }, /* @__PURE__ */ React16.createElement(Text16, null, indentStr), /* @__PURE__ */ React16.createElement(Text16, { color: leftColor, bold: isBold }, cleanLeftText), /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted, dimColor: true }, dotsStr), /* @__PURE__ */ React16.createElement(Text16, { color: rightColor, bold: isBold }, rightText)));
23338
+ };
23339
+ const breakdownRows = [];
23340
+ if (!monthlyUsage?.models || Object.keys(monthlyUsage.models).length === 0) {
23341
+ breakdownRows.push(
23342
+ /* @__PURE__ */ React16.createElement(Box14, { key: "empty", marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted, italic: true }, "No model token usage recorded in the last 30 days."))
23343
+ );
23344
+ } else {
23345
+ Object.entries(monthlyUsage.models).forEach(([provider, models], pIdx) => {
23346
+ const providerTotalTokens = Object.values(models).reduce((sum, m) => sum + (m.tokens || 0), 0);
23347
+ if (pIdx > 0) {
23348
+ breakdownRows.push(/* @__PURE__ */ React16.createElement(Box14, { key: `space-prov-${provider}` }, /* @__PURE__ */ React16.createElement(Text16, null, " ")));
23349
+ }
23350
+ breakdownRows.push(
23351
+ renderLeaderRow(`prov-${provider}`, `${provider}:`, formatTokens(providerTotalTokens), colors.primary, colors.text, 0, true)
23352
+ );
23353
+ Object.entries(models).forEach(([modelName, stats], mIdx) => {
23354
+ if (mIdx > 0) {
23355
+ breakdownRows.push(/* @__PURE__ */ React16.createElement(Box14, { key: `space-mod-${provider}-${modelName}` }, /* @__PURE__ */ React16.createElement(Text16, null, " ")));
23356
+ }
23357
+ breakdownRows.push(
23358
+ renderLeaderRow(`mod-${provider}-${modelName}`, `\xBB ${modelName}:`, formatTokens(stats.tokens || 0), colors.secondary, colors.text, 2, true)
23359
+ );
23360
+ breakdownRows.push(
23361
+ renderLeaderRow(`in-${provider}-${modelName}`, "\xBB Input Tokens:", formatTokens((stats.tokens || 0) - (stats.candidateTokens || 0)), colors.textMuted, colors.text, 5, false)
23362
+ );
23363
+ if ((stats.cachedTokens || 0) > 0) {
23364
+ breakdownRows.push(
23365
+ renderLeaderRow(`cache-${provider}-${modelName}`, "\xBB Cached:", formatTokens(stats.cachedTokens), colors.textMuted, colors.text, 7, false)
23366
+ );
23367
+ }
23368
+ breakdownRows.push(
23369
+ renderLeaderRow(`out-${provider}-${modelName}`, "\xBB Output Tokens:", formatTokens(stats.candidateTokens || 0), colors.textMuted, colors.text, 5, false)
23370
+ );
23371
+ });
23372
+ });
23373
+ }
23374
+ const totalRows = breakdownRows.length;
23375
+ const maxScroll = Math.max(0, totalRows - maxRows);
23376
+ maxScrollRef.current = maxScroll;
23377
+ const effectiveScroll = Math.min(statsScrollOffset, maxScroll);
23378
+ const visibleRows = breakdownRows.slice(effectiveScroll, effectiveScroll + maxRows);
23379
+ return /* @__PURE__ */ React16.createElement(Box14, { flexDirection: "column", borderStyle: "round", borderColor: colors.borderMuted, paddingX: 3, paddingY: 1, paddingBottom: 0, width: Math.min(125, (stdout?.columns || 100) - 2) }, statsMode === "modelBreakdown" ? /* @__PURE__ */ React16.createElement(Box14, { flexDirection: "column" }, /* @__PURE__ */ React16.createElement(Box14, { justifyContent: "space-between" }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.text, bold: true, underline: true }, "30-DAY MODEL TOKEN BREAKDOWN"), totalRows > maxRows && /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted, dimColor: true }, "[", effectiveScroll + 1, "-", Math.min(totalRows, effectiveScroll + maxRows), " of ", totalRows, "] \u25B2\u25BC")), /* @__PURE__ */ React16.createElement(Box14, { flexDirection: "column", height: maxRows, marginTop: 1 }, visibleRows)) : /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement(Box14, { marginBottom: 1 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.text, bold: true, underline: true }, "SESSION TELEMETRY")), /* @__PURE__ */ React16.createElement(Box14, { flexDirection: "column" }, /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Session Duration:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatMsDuration(Date.now() - SESSION_START_TIME))), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Model Requests:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, sessionAgentCalls)), /* @__PURE__ */ React16.createElement(Box14, { marginLeft: 2 }, /* @__PURE__ */ React16.createElement(Box14, { width: 23 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\xBB API Time:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatMsDuration(sessionApiTime))), /* @__PURE__ */ React16.createElement(Box14, { marginLeft: 2 }, /* @__PURE__ */ React16.createElement(Box14, { width: 23 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\xBB Tool Time:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatMsDuration(sessionToolTime))), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Memory Agent:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, sessionBackgroundCalls)), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Tokens Consumed:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens(sessionTotalTokens))), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Active Context:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens(sessionStats.tokens))), sessionTotalTokens > 0 && /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement(Box14, { marginLeft: 2 }, /* @__PURE__ */ React16.createElement(Box14, { width: 23 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\xBB Input Tokens:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens(sessionTotalTokens - sessionTotalCandidateTokens))), sessionTotalCachedTokens > 0 && /* @__PURE__ */ React16.createElement(Box14, { marginLeft: 4 }, /* @__PURE__ */ React16.createElement(Box14, { width: 21 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\xBB Cached:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens(sessionTotalCachedTokens))), sessionTotalCandidateTokens > 0 && /* @__PURE__ */ React16.createElement(Box14, { marginLeft: 2 }, /* @__PURE__ */ React16.createElement(Box14, { width: 23 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\xBB Output Tokens:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens(sessionTotalCandidateTokens)))), sessionImageCount > 0 && /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Images Made:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, sessionImageCount)), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Image Credits:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, Number(((sessionImageCredits || 0) * 1e3).toFixed(0)), " credits"))), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Code Changes (Sess):")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.success || "green" }, "+", runtimeSession.linesAdded), " ", /* @__PURE__ */ React16.createElement(Text16, { color: colors.danger || "red" }, "-", runtimeSession.linesRemoved))), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Tool Calls (Sess):")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, runtimeSession.toolSuccess + runtimeSession.toolFailure + runtimeSession.toolDenied, " ( "), /* @__PURE__ */ React16.createElement(Text16, { color: colors.success || "green" }, "\u2714 ", runtimeSession.toolSuccess), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, " "), /* @__PURE__ */ React16.createElement(Text16, { color: colors.warning || "yellow" }, "\u{1F6C7} ", runtimeSession.toolDenied), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, " "), /* @__PURE__ */ React16.createElement(Text16, { color: colors.danger || "red" }, "\u2718 ", runtimeSession.toolFailure), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, " )"))), /* @__PURE__ */ React16.createElement(Box14, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.text, bold: true, underline: true }, trackerTitle), /* @__PURE__ */ React16.createElement(Box14, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, timeLabel)), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatDuration(u?.duration || 0))), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Model Requests:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, u?.agent || 0)), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, "Memory Agent:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, u?.background || 0)), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, tokensLabel)), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens(u?.tokens || 0))), (u?.tokens || 0) > 0 && /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement(Box14, { marginLeft: 2 }, /* @__PURE__ */ React16.createElement(Box14, { width: 23 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\xBB Input Tokens:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens((u?.tokens || 0) - (u?.candidateTokens || 0)))), (u?.cachedTokens || 0) > 0 && /* @__PURE__ */ React16.createElement(Box14, { marginLeft: 4 }, /* @__PURE__ */ React16.createElement(Box14, { width: 21 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\xBB Cached:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens(u.cachedTokens))), (u?.candidateTokens || 0) > 0 && /* @__PURE__ */ React16.createElement(Box14, { marginLeft: 2 }, /* @__PURE__ */ React16.createElement(Box14, { width: 23 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\xBB Output Tokens:")), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, formatTokens(u.candidateTokens)))), (u?.imageCalls?.length || 0) > 0 && /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, imagesLabel)), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, u.imageCalls.length)), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, imageCreditsLabel)), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, Number(((u.imageCalls.reduce((sum, c) => sum + c.cost, 0) || 0) * 1e3).toFixed(0)), " credits"))), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, codeChangesLabel)), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.success || "green" }, "+", u?.linesAdded || 0), " ", /* @__PURE__ */ React16.createElement(Text16, { color: colors.danger || "red" }, "-", u?.linesRemoved || 0))), /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Box14, { width: 25 }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.secondary }, toolCallsLabel)), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, (u?.toolSuccess || 0) + (u?.toolFailure || 0) + (u?.toolDenied || 0), " ( "), /* @__PURE__ */ React16.createElement(Text16, { color: colors.success || "green" }, "\u2714 ", u?.toolSuccess || 0), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, " "), /* @__PURE__ */ React16.createElement(Text16, { color: colors.warning || "yellow" }, "\u{1F6C7} ", u?.toolDenied || 0), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, " "), /* @__PURE__ */ React16.createElement(Text16, { color: colors.danger || "red" }, "\u2718 ", u?.toolFailure || 0), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, " )")))), /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted, dimColor: true, italic: true }, "\n", "(Press TAB to toggle Daily/Monthly views, SPACE for Model Breakdown, ESC to return)"));
22764
23380
  }
22765
23381
  case "dynamicDirDanger":
22766
23382
  return /* @__PURE__ */ React16.createElement(Box14, { flexDirection: "column", borderStyle: "round", borderColor: colors.borderMuted, paddingX: 2, paddingY: 1, width: "100%" }, /* @__PURE__ */ React16.createElement(Text16, { color: colors.warning || "yellow", bold: true, underline: true }, "DYNAMIC DIRECTORY AWARENESS"), /* @__PURE__ */ React16.createElement(Text16, { marginTop: 1, color: colors.text }, "Enabling this keeps the agent aware of filesystem state in real time, but may reduce prompt cache efficiency."), /* @__PURE__ */ React16.createElement(Text16, { color: colors.text }, "\n", "RECOMMENDED SCENARIOS TO TURN ON:"), /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\u2022 Repo is small."), /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\u2022 The task benefits from real-time filesystem awareness."), /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\u2022 Files are often created, renamed, or deleted."), /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\u2022 You know exactly what you're signing up for."), /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\u2022 You don't have conflicting decisions regarding token bills."), /* @__PURE__ */ React16.createElement(Text16, { color: colors.textMuted }, "\u2022 You want to see your wallet crying at 3am."), /* @__PURE__ */ React16.createElement(Box14, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(
@@ -23565,7 +24181,7 @@ var init_app = __esm({
23565
24181
  };
23566
24182
  getKeybindingsPath = (ideName) => {
23567
24183
  const dirName = getIDEDirName(ideName);
23568
- const home = os5.homedir();
24184
+ const home = os4.homedir();
23569
24185
  if (process.platform === "win32") {
23570
24186
  const appData = process.env.APPDATA;
23571
24187
  if (!appData) return null;
@@ -23824,10 +24440,10 @@ var init_app = __esm({
23824
24440
  // src/cli.jsx
23825
24441
  import { spawn as spawn3 } from "child_process";
23826
24442
  import { fileURLToPath as fileURLToPath4 } from "url";
23827
- import os6 from "os";
24443
+ import os5 from "os";
23828
24444
  import dotenv2 from "dotenv";
23829
24445
  dotenv2.config({ quiet: true });
23830
- var totalSystemRamBytes = os6.totalmem();
24446
+ var totalSystemRamBytes = os5.totalmem();
23831
24447
  var totalSystemRamMB = totalSystemRamBytes / (1024 * 1024);
23832
24448
  var SAFETY_MARGIN = 0.5;
23833
24449
  var calculatedLimit = Math.floor(totalSystemRamMB * SAFETY_MARGIN);