fluxflow-cli 2.16.9 → 3.0.0

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/ARCHITECTURE.md CHANGED
@@ -61,6 +61,18 @@ To maintain a fast, snappy UI while still performing complex data management, Fl
61
61
  - **Behavior**: After the Main Agent finishes its loop, the entire context (User Prompt + Agent Raws) is sent to the Janitor model.
62
62
  - **Headless Operation**: The Janitor is explicitly instructed to be a "silent background system process" with "no mouth." It *only* outputs valid tool calls (e.g., updating the chat title or saving a new user preference to the persistent memory vault).
63
63
 
64
+ ## The Subagent System
65
+
66
+ Flux Flow provides a robust, multi-agent execution system to delegate sub-tasks and run parallel operations without blocking the main workflow:
67
+
68
+ - **Sync/Async Execution Modes**:
69
+ - **`invokeSync`**: Spawns a blocking subagent. The main loop waits for the subagent to complete its assignment before continuing. Best used for immediate, sequential task delegations.
70
+ - **`invoke`**: Spawns an asynchronous background subagent. The main agent receives a unique Task ID and can continue working or poll for progress asynchronously.
71
+ - **Isolated System Context**: Subagents operate independently and have no direct context of the main conversation chat. They only receive the custom system instructions and the specific task query assigned by the main agent.
72
+ - **Permanent Tool Suite**: Subagents are equipped with a permanent suite of 10 system tools (ReadFile, ReadFolder, FileMap, PatchFile, WriteFile, SearchKeyword, WritePDF, WriteDoc, WebSearch, and WebScrape), but are restricted from executing commands or prompt prompts (`Run`, `Ask`, and `Todo` are disabled for safety).
73
+ - **Silent Logging and Telemetry**: Background subagent operations are logged quietly and report status updates to the CLI's active subagents UI block.
74
+ - **Transaction-Safe Reversion**: All file modifications made by asynchronous background subagents are logged chronologically under the session's active transaction. This ensures that any subagent modifications can be fully reverted/undone by the user at any point.
75
+
64
76
  ## Data Persistence & Safety
65
77
 
66
78
  - **High-Fidelity Lock**: Because both the UI and the Janitor model may attempt to write to the `history.json` file simultaneously, a Promise-based `WRITE_LOCK` (`src/utils/history.js`) is utilized. This prevents race conditions and ensures data integrity.
package/README.md CHANGED
@@ -85,6 +85,9 @@ Security isn't an afterthought; it's a boundary.
85
85
  ### 🧹 **The Background Janitor**
86
86
  While you move at high speed, the Janitor follows behind—refining session titles, compressing data, and ensuring your context window remains at absolute peak performance.
87
87
 
88
+ ### 🤖 **Autonomous Subagent System**
89
+ Delegate complex tasks to subagents. Spawns blocking subagents (`invokeSync`) or asynchronous background subagents (`invoke`) with distinct telemetry and silent background logging. Built-in transaction-safe reversion logs all subagent changes under the active turn, preserving rollback security.
90
+
88
91
  ---
89
92
 
90
93
  ## 🛠️ Key Capabilities
package/dist/fluxflow.js CHANGED
@@ -2846,7 +2846,7 @@ var init_text = __esm({
2846
2846
  "GenerateImage": "GenerateImage"
2847
2847
  };
2848
2848
  REGEX_INITIAL_THINK = /<\/think>(\r?\n){2}/gi;
2849
- REGEX_INITIAL_TOOL = /(\r?\n){2}(?=\[?(?:tool:functions|tool\.functions|\s*turn\s*:))/gi;
2849
+ REGEX_INITIAL_TOOL = /(\r?\n){2}(?=\[?(?:tool:functions|tool\.functions|agent:generalist|agent\.generalist|\s*turn\s*:))/gi;
2850
2850
  REGEX_CLEAN_SIGNALS = /\[SYSTEM\][\s\S]*?\[\/SYSTEM\]|<(think|thought)>[\s\S]*?(?:<\/(think|thought)>|$)|\[ANSWER\][\s\S]*?(?:\[\/ANSWER\]|$)|\[TOOL RESULT\]:?\s*|^\s*(SUCCESS|ERROR):.*(\r?\n)?|\[\s*turn\s*:\s*(continue|finish)\s*\]|\[\[END\]\]|\[\s*turn\s*:?.*?$|\n\s*turn\s*:?.*?$|\[\s*$|\n\nResponded on .*|\n\n\[Prompted on: .*\]|@\[TerminalName:.*?, ProcessId:.*?\]/gmi;
2851
2851
  REGEX_ARROWS_ALL = /(\$?\\?\/?\\rightarrow\$?|\$\\rightarrow\$)|(\$?\\?\/?\\leftarrow\$?|\$\\leftarrow\$)|(\$?\\?\/?\\uparrow\$?|\$\\uparrow\$)|(\$?\\?\/?\\downarrow\$?|\$\\downarrow\$)|(\$?\\?\/?\\leftrightarrow\$?|\$\\leftrightarrow\$)/gi;
2852
2852
  REGEX_TOOLS = /\b(write_file|update_file|read_folder|view_file|exec_command|web_search|web_scrape|search_keyword|write_pdf|write_docx|generate_image)\b/gi;
@@ -2854,14 +2854,22 @@ var init_text = __esm({
2854
2854
  if (!text) return text;
2855
2855
  let result = text.replace(REGEX_INITIAL_THINK, "</think>").replace(REGEX_INITIAL_TOOL, "");
2856
2856
  const trigger = "tool:functions.";
2857
- if (result.toLowerCase().includes(trigger)) {
2857
+ const subagentTrigger = "agent:generalist.";
2858
+ if (result.toLowerCase().includes(trigger) || result.toLowerCase().includes(subagentTrigger)) {
2858
2859
  while (true) {
2859
2860
  const lowerResult = result.toLowerCase();
2860
2861
  let triggerIdx = lowerResult.indexOf(trigger);
2861
- if (triggerIdx === -1) break;
2862
- let startIdx = triggerIdx;
2862
+ let subagentIdx = lowerResult.indexOf(subagentTrigger);
2863
+ let currentTrigger = trigger;
2864
+ let triggerIdxToUse = triggerIdx;
2865
+ if (triggerIdx === -1 || subagentIdx !== -1 && subagentIdx < triggerIdx) {
2866
+ currentTrigger = subagentTrigger;
2867
+ triggerIdxToUse = subagentIdx;
2868
+ }
2869
+ if (triggerIdxToUse === -1) break;
2870
+ let startIdx = triggerIdxToUse;
2863
2871
  let hasOuterBracket = false;
2864
- let k = triggerIdx - 1;
2872
+ let k = triggerIdxToUse - 1;
2865
2873
  while (k >= 0 && /\s/.test(result[k])) k--;
2866
2874
  if (k >= 0 && result[k] === "[") {
2867
2875
  startIdx = k;
@@ -2870,7 +2878,7 @@ var init_text = __esm({
2870
2878
  let balance = 0;
2871
2879
  let foundStart = false;
2872
2880
  let inString = null;
2873
- let j = triggerIdx;
2881
+ let j = triggerIdxToUse;
2874
2882
  while (j < result.length) {
2875
2883
  const char = result[j];
2876
2884
  if (!inString && (char === "'" || char === '"' || char === "`")) {
@@ -5116,7 +5124,13 @@ ${mode === "Flux" ? `- WORKSPACE TOOLS (path = relative to CWD & WILL BE FIRST A
5116
5124
  5. [tool:functions.WriteFile(path="...", content="...")]. Creates/Overwrites. File Exist? PatchFile > WriteFile. Verify Imports
5117
5125
  6. [tool:functions.SearchKeyword(keyword="...", file="optional", subString="true/false optional")]. Global project search. If 'file' is provided, searches only that file. Finds definitions/logic without reading every file. Usage: Can search for relevent lines/logic area to read specifically for edit
5118
5126
  7. [tool:functions.Run(command="...")]. Runs ${osDetected === "Windows" ? isPsAvailable() ? `WINDOWS POWERSHELL ONLY` : `WINDOWS CMD ONLY` : `BASH`} command. Destructive/Irreversible ops \u2192 Ask user
5119
- 8. [tool:functions.Todo(method="create/append/get", tasks=[ARRAY OF STRINGS], markDone=[ARRAY OF TASK STRINGS])]. Task List, NO Markdown IN ARRAY. USAGE: ANALYZE USER REQUEST **IF** MULTIPLE TASK \u2192 BREAK DOWN TASK \u2192 CREATE TODO **BEFORE** DIVING IN. 'tasks' & 'markDone' OPTIONAL PARAMETERS WITH method 'get'. USE 'get' method WITH 'markDone' to mark task completed. **EVERY TURN UPDATE POLICY**`.trim() : `- CREATIVE TOOLS (path = relative to CWD & WILL BE FIRST ARGUMENT, path separator: '/') -
5127
+ 8. [tool:functions.Todo(method="create/append/get", tasks=[ARRAY OF STRINGS], markDone=[ARRAY OF TASK STRINGS])]. Task List, NO Markdown IN ARRAY. USAGE: ANALYZE USER REQUEST **IF** MULTIPLE TASK \u2192 BREAK DOWN TASK \u2192 CREATE TODO **BEFORE** DIVING IN. 'tasks' & 'markDone' OPTIONAL PARAMETERS WITH method 'get'. USE 'get' method WITH 'markDone' to mark task completed. **EVERY TURN UPDATE POLICY**
5128
+
5129
+ -- SUB AGENTS --
5130
+ IN NEW LINE, use when needed
5131
+ - Invocation Types: invoke (async, background worker for parallel tasks), invokeSync (sync, blocking main agent loop, usage: task delegation, repeatetive work)
5132
+ 1. [agent:generalist.invocationType(title="...", task="...")]. Usage: delegate repeatative task or work in background, Task must me detailed, with file paths & required folder structure provided
5133
+ 2. [agent:generalist.getProgress(id="...")]. Usage: Check progress of async subagent task, dont spam`.trim() : `- CREATIVE TOOLS (path = relative to CWD & WILL BE FIRST ARGUMENT, path separator: '/') -
5120
5134
  1. [tool:functions.WritePDF(path="...", content="...", orientation="...")]. PROACTIVE A4 PAGE BREAKS MUST IN CSS. HTML/CSS for PREMIUM layout
5121
5135
  2. [tool:functions.WriteDoc(path="...", content="...")]. A4 Word document
5122
5136
  - WORKSPACE TOOLS ARE NOT AVAILABLE IN FLOW`.trim()}
@@ -6559,15 +6573,17 @@ async function restoreWithRetry(change, tx, maxAttempts = 7) {
6559
6573
  }
6560
6574
  return false;
6561
6575
  }
6562
- var currentTransaction, RevertManager;
6576
+ var currentTransaction, lastChatId, RevertManager;
6563
6577
  var init_revert = __esm({
6564
6578
  "src/utils/revert.js"() {
6565
6579
  init_paths();
6566
6580
  init_crypto();
6567
6581
  fs6.ensureDirSync(BACKUPS_DIR);
6568
6582
  currentTransaction = null;
6583
+ lastChatId = null;
6569
6584
  RevertManager = {
6570
6585
  async startTransaction(chatId, promptText) {
6586
+ lastChatId = chatId;
6571
6587
  currentTransaction = {
6572
6588
  id: `tx_prompt_${Date.now()}`,
6573
6589
  chatId,
@@ -6579,8 +6595,37 @@ var init_revert = __esm({
6579
6595
  writeEncryptedJson(ACTIVE_TX_FILE, currentTransaction);
6580
6596
  },
6581
6597
  async recordFileChange(absolutePath, forcedContent = null) {
6582
- if (!currentTransaction) return;
6583
6598
  try {
6599
+ if (!currentTransaction) {
6600
+ if (lastChatId) {
6601
+ const ledger = readEncryptedJson(LEDGER_FILE, []);
6602
+ const lastTx = [...ledger].reverse().find((tx) => tx.chatId === lastChatId);
6603
+ if (lastTx) {
6604
+ const alreadyBackedUp2 = lastTx.changes.some((c) => c.filePath === absolutePath);
6605
+ if (alreadyBackedUp2) return;
6606
+ const fileExists2 = await fs6.pathExists(absolutePath);
6607
+ let type2 = fileExists2 || forcedContent ? "update" : "create";
6608
+ let backupFile2 = null;
6609
+ if (type2 === "update") {
6610
+ const fileName = path5.basename(absolutePath);
6611
+ backupFile2 = `${lastTx.id}_${fileName}.bak`;
6612
+ const chatBackupDir = path5.join(BACKUPS_DIR, lastTx.chatId);
6613
+ await fs6.ensureDir(chatBackupDir);
6614
+ const backupPath = path5.join(chatBackupDir, backupFile2);
6615
+ let content = forcedContent !== null ? forcedContent : await fs6.readFile(absolutePath, "utf8").catch(() => null);
6616
+ if (content !== null) {
6617
+ writeEncryptedJson(backupPath, { data: encryptAes(content) });
6618
+ } else {
6619
+ type2 = "create";
6620
+ backupFile2 = null;
6621
+ }
6622
+ }
6623
+ lastTx.changes.push({ filePath: absolutePath, type: type2, backupFile: backupFile2 });
6624
+ writeEncryptedJson(LEDGER_FILE, ledger);
6625
+ }
6626
+ }
6627
+ return;
6628
+ }
6584
6629
  const alreadyBackedUp = currentTransaction.changes.some((c) => c.filePath === absolutePath);
6585
6630
  if (alreadyBackedUp) return;
6586
6631
  const fileExists = await fs6.pathExists(absolutePath);
@@ -8666,7 +8711,7 @@ var init_search_keyword = __esm({
8666
8711
  const fileGroups = [];
8667
8712
  let totalMatches = 0;
8668
8713
  for (const result of settledResults) {
8669
- if (!result) continue;
8714
+ if (!result || !result.matches) continue;
8670
8715
  if (totalMatches >= maxMatches) break;
8671
8716
  const remaining = maxMatches - totalMatches;
8672
8717
  const trimmedMatches = result.matches.slice(0, remaining);
@@ -9462,6 +9507,193 @@ ${content}`;
9462
9507
  }
9463
9508
  });
9464
9509
 
9510
+ // src/tools/invokeSync.js
9511
+ var invokeSync;
9512
+ var init_invokeSync = __esm({
9513
+ "src/tools/invokeSync.js"() {
9514
+ init_arg_parser();
9515
+ invokeSync = async (args, context = {}) => {
9516
+ const { runSubagent: runSubagent2 } = await init_ai().then(() => ai_exports);
9517
+ const parsed = parseArgs(args);
9518
+ const task = parsed.task || parsed.instruction || parsed.prompt;
9519
+ const model = parsed.model || null;
9520
+ const toolsRaw = parsed.tools || null;
9521
+ if (!task) {
9522
+ return 'ERROR: Missing "task" argument for invokeSync.';
9523
+ }
9524
+ let allowedTools = null;
9525
+ if (toolsRaw) {
9526
+ try {
9527
+ let cleaned = toolsRaw.trim();
9528
+ if (cleaned.startsWith("[") && cleaned.endsWith("]")) {
9529
+ cleaned = cleaned.substring(1, cleaned.length - 1);
9530
+ }
9531
+ allowedTools = cleaned.split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
9532
+ } catch (e) {
9533
+ }
9534
+ }
9535
+ const title = parsed.title || task.substring(0, 30);
9536
+ try {
9537
+ if (context.onVisualFeedback) {
9538
+ context.onVisualFeedback(`\x1B[95mSubAgent\x1B[0m: \x1B[32mGeneralist\x1B[0m \u2192 ${title}`);
9539
+ }
9540
+ const result = await runSubagent2(task, context, model, allowedTools, 20);
9541
+ if (context.onVisualFeedback) {
9542
+ context.onVisualFeedback(`\x1B[95mSubAgent\x1B[0m: \x1B[32mGeneralist\x1B[0m \u2192 ${title} [COMPLETED]
9543
+ `);
9544
+ }
9545
+ return result;
9546
+ } catch (err) {
9547
+ if (context.onVisualFeedback) {
9548
+ context.onVisualFeedback(`\x1B[95mSubAgent\x1B[0m: \x1B[32mGeneralist\x1B[0m \u2192 ${title} [FAILED]
9549
+ `);
9550
+ }
9551
+ return `ERROR: Subagent execution failed: ${err.message}`;
9552
+ }
9553
+ };
9554
+ }
9555
+ });
9556
+
9557
+ // src/utils/subagent_state.js
9558
+ var subagentProgress;
9559
+ var init_subagent_state = __esm({
9560
+ "src/utils/subagent_state.js"() {
9561
+ subagentProgress = [];
9562
+ }
9563
+ });
9564
+
9565
+ // src/tools/invoke.js
9566
+ var invoke;
9567
+ var init_invoke = __esm({
9568
+ "src/tools/invoke.js"() {
9569
+ init_subagent_state();
9570
+ init_arg_parser();
9571
+ invoke = async (args, context = {}) => {
9572
+ const { runSubagent: runSubagent2 } = await init_ai().then(() => ai_exports);
9573
+ const parsed = parseArgs(args);
9574
+ const task = parsed.task || parsed.instruction || parsed.prompt;
9575
+ const model = parsed.model || null;
9576
+ const title = parsed.title || null;
9577
+ const toolsRaw = parsed.tools || null;
9578
+ if (!task) {
9579
+ return 'ERROR: Missing "task" argument for invoke.';
9580
+ }
9581
+ let allowedTools = null;
9582
+ if (toolsRaw) {
9583
+ try {
9584
+ let cleaned = toolsRaw.trim();
9585
+ if (cleaned.startsWith("[") && cleaned.endsWith("]")) {
9586
+ cleaned = cleaned.substring(1, cleaned.length - 1);
9587
+ }
9588
+ allowedTools = cleaned.split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
9589
+ } catch (e) {
9590
+ }
9591
+ }
9592
+ const taskId = `subagent-${Date.now()}-${Math.floor(Math.random() * 1e3)}`;
9593
+ const taskEntry = {
9594
+ id: taskId,
9595
+ title: title || task.substring(0, 30),
9596
+ task,
9597
+ status: "running",
9598
+ progress: []
9599
+ // Array of arrays containing logs for each turn
9600
+ };
9601
+ subagentProgress.push(taskEntry);
9602
+ if (context.onSubagentUpdate) {
9603
+ context.onSubagentUpdate();
9604
+ }
9605
+ let currentTurnLogs = [];
9606
+ const subagentContext = { ...context, onVisualFeedback: null };
9607
+ runSubagent2(task, subagentContext, model, allowedTools, 20, (logMessage) => {
9608
+ if (logMessage.startsWith("[Subagent Turn")) {
9609
+ if (currentTurnLogs.length > 0) {
9610
+ taskEntry.progress.push([...currentTurnLogs]);
9611
+ currentTurnLogs = [];
9612
+ }
9613
+ }
9614
+ if (logMessage.includes("[Executing Tool]")) {
9615
+ const m = logMessage.match(/\[Executing Tool\]\s*([a-zA-Z0-9_]+)/);
9616
+ if (m) {
9617
+ taskEntry.currentTool = m[1];
9618
+ }
9619
+ }
9620
+ let displayLog = logMessage;
9621
+ if (displayLog.startsWith("[Tool Result]")) {
9622
+ const lines = displayLog.split("\n");
9623
+ if (lines.length > 5) {
9624
+ displayLog = lines.slice(0, 4).join("\n") + "\n... [Content/Diff Truncated from Logs] ...";
9625
+ }
9626
+ }
9627
+ currentTurnLogs.push(displayLog);
9628
+ if (context.onSubagentUpdate) {
9629
+ context.onSubagentUpdate();
9630
+ }
9631
+ }).then((finalAnswer) => {
9632
+ currentTurnLogs.push(`[SUBAGENT SUCCESS] Final Answer:
9633
+ ${finalAnswer}`);
9634
+ taskEntry.progress.push([...currentTurnLogs]);
9635
+ taskEntry.status = "completed";
9636
+ taskEntry.finalAnswer = finalAnswer;
9637
+ if (context.onSubagentUpdate) {
9638
+ context.onSubagentUpdate();
9639
+ }
9640
+ }).catch((err) => {
9641
+ currentTurnLogs.push(`[SUBAGENT FAILURE] Error: ${err.message}`);
9642
+ taskEntry.progress.push([...currentTurnLogs]);
9643
+ taskEntry.status = "failed";
9644
+ taskEntry.error = err.message;
9645
+ if (context.onSubagentUpdate) {
9646
+ context.onSubagentUpdate();
9647
+ }
9648
+ });
9649
+ return `SUCCESS: Background subagent started. Task ID: ${taskId}`;
9650
+ };
9651
+ }
9652
+ });
9653
+
9654
+ // src/tools/getProgress.js
9655
+ var getProgress;
9656
+ var init_getProgress = __esm({
9657
+ "src/tools/getProgress.js"() {
9658
+ init_subagent_state();
9659
+ init_arg_parser();
9660
+ getProgress = async (args, context = {}) => {
9661
+ const parsed = parseArgs(args);
9662
+ const id = parsed.id;
9663
+ if (!id) {
9664
+ return 'ERROR: Missing "id" argument for getProgress.';
9665
+ }
9666
+ const task = subagentProgress.find((t) => t.id === id);
9667
+ if (!task) {
9668
+ return `ERROR: Subagent task with ID [${id}] not found.`;
9669
+ }
9670
+ let output = `Subagent Task Status: ${task.status.toUpperCase()}
9671
+ `;
9672
+ output += `Title: ${task.title}
9673
+ `;
9674
+ output += `Task: ${task.task}
9675
+
9676
+ `;
9677
+ output += `Progress Log:
9678
+ `;
9679
+ task.progress.forEach((turnLogs, index) => {
9680
+ output += `--- Turn ${index + 1} ---
9681
+ `;
9682
+ output += turnLogs.join("\n") + "\n\n";
9683
+ });
9684
+ if (task.status === "completed" && task.finalAnswer) {
9685
+ output += `Final Answer:
9686
+ ${task.finalAnswer}
9687
+ `;
9688
+ } else if (task.status === "failed" && task.error) {
9689
+ output += `Failure Error: ${task.error}
9690
+ `;
9691
+ }
9692
+ return output.trim();
9693
+ };
9694
+ }
9695
+ });
9696
+
9465
9697
  // src/utils/tools.js
9466
9698
  var TOOL_MAP, dispatchTool;
9467
9699
  var init_tools = __esm({
@@ -9484,6 +9716,9 @@ var init_tools = __esm({
9484
9716
  init_addMemScore();
9485
9717
  init_file_map();
9486
9718
  init_todo();
9719
+ init_invokeSync();
9720
+ init_invoke();
9721
+ init_getProgress();
9487
9722
  TOOL_MAP = {
9488
9723
  web_search,
9489
9724
  web_scrape,
@@ -9502,6 +9737,11 @@ var init_tools = __esm({
9502
9737
  addMemScore,
9503
9738
  file_map,
9504
9739
  todo,
9740
+ invokeSync,
9741
+ invoke,
9742
+ getProgress,
9743
+ invoke_sync: invokeSync,
9744
+ get_progress: getProgress,
9505
9745
  ask: ask_user,
9506
9746
  // PascalCase Normalizations for Token Efficiency
9507
9747
  Ask: ask_user,
@@ -9527,7 +9767,10 @@ var init_tools = __esm({
9527
9767
  AddMemoryScore: addMemScore,
9528
9768
  FileMap: file_map,
9529
9769
  Todo: todo,
9530
- TODO: todo
9770
+ TODO: todo,
9771
+ InvokeSync: invokeSync,
9772
+ Invoke: invoke,
9773
+ GetProgress: getProgress
9531
9774
  };
9532
9775
  dispatchTool = async (toolName, args, context = {}) => {
9533
9776
  const mode = context.mode ? context.mode.toLowerCase() : "flux";
@@ -9664,10 +9907,22 @@ var init_editor = __esm({
9664
9907
  });
9665
9908
 
9666
9909
  // src/utils/ai.js
9910
+ var ai_exports = {};
9911
+ __export(ai_exports, {
9912
+ compressHistory: () => compressHistory,
9913
+ deleteChatSummary: () => deleteChatSummary,
9914
+ getAIStream: () => getAIStream,
9915
+ getCleanGroupedLength: () => getCleanGroupedLength,
9916
+ initAI: () => initAI,
9917
+ isModelMultimodal: () => isModelMultimodal,
9918
+ runJanitorTask: () => runJanitorTask,
9919
+ runSubagent: () => runSubagent,
9920
+ signalTermination: () => signalTermination
9921
+ });
9667
9922
  import { GoogleGenAI, ThinkingLevel, HarmBlockThreshold, HarmCategory } from "@google/genai";
9668
9923
  import path19 from "path";
9669
9924
  import fs20 from "fs";
9670
- var client, globalSettings, colorMainWords, TERMINATION_SIGNAL, MULTIMODAL_MODELS, isModelMultimodal, getCleanGroupedLength, stripAnsi2, fetchWithBackoff, getDeepSeekStream, getNVIDIAStream, getOpenRouterStream, signalTermination, TOOL_LABELS2, getToolDetail, runJanitorTask, getActiveToolContext, getContextSafeText, contextSafeReplace, getSanitizedText, translateKimiToolCalls, detectToolCalls, initAI, generateSimpleContent, consolidatePastMemories, compressHistory, deleteChatSummary, getAIStream;
9925
+ var client, globalSettings, colorMainWords, withRetry, TERMINATION_SIGNAL, MULTIMODAL_MODELS, isModelMultimodal, getCleanGroupedLength, stripAnsi2, fetchWithBackoff, getDeepSeekStream, getNVIDIAStream, getOpenRouterStream, signalTermination, TOOL_LABELS2, getToolDetail, runJanitorTask, getActiveToolContext, getContextSafeText, contextSafeReplace, getSanitizedText, translateKimiToolCalls, detectToolCalls, initAI, generateSimpleContent, consolidatePastMemories, compressHistory, deleteChatSummary, getAIStream, runSubagent;
9671
9926
  var init_ai = __esm({
9672
9927
  async "src/utils/ai.js"() {
9673
9928
  await init_prompts();
@@ -9679,6 +9934,7 @@ var init_ai = __esm({
9679
9934
  init_view_file();
9680
9935
  init_terminal();
9681
9936
  init_text();
9937
+ init_settings();
9682
9938
  init_paths();
9683
9939
  init_revert();
9684
9940
  init_editor();
@@ -9686,10 +9942,39 @@ var init_ai = __esm({
9686
9942
  globalSettings = {};
9687
9943
  colorMainWords = (label2) => {
9688
9944
  if (!label2) return label2;
9689
- return label2.replace(/(?:(\x1b\[\d+m))?([✔✗✖🔍📖→➕↻•])(?:(\x1b\[\d+m))?\s*\b(Created|Read|Edited|Viewed|Auto-Read|List|Generated|Written|Searched|Get Map|Write Canceled|Edit Canceled|Write Cancelled|Edit Denied|Visited|Updated|Reviewed)\b/ig, (match, ansiBefore, icon, ansiAfter, word) => {
9945
+ return label2.replace(/(?:(\x1b\[\d+m))?([✔✗✖🔍📖→➕↻•])(?:(\x1b\[\d+m))?\s*\b(Created|Read|Edited|Viewed|Auto-Read|List|Generated|Written|Searched|Get Map|Write Canceled|Edit Canceled|Write Cancelled|Edit Denied|Visited|Updated|Reviewed|Delegated|Background|Checked|Elevating SubAgent|Checking SubAgent Work)\b/ig, (match, ansiBefore, icon, ansiAfter, word) => {
9690
9946
  return `${ansiBefore || ""}${icon}${ansiAfter || ""} \x1B[95m${word}\x1B[0m`;
9691
9947
  });
9692
9948
  };
9949
+ withRetry = async (fn, maxRetries = 8, initialDelayMs = 1e3, maxDelayMs = 8e3, signal = null) => {
9950
+ let attempt = 0;
9951
+ while (true) {
9952
+ if (signal?.aborted) {
9953
+ throw new DOMException("The user aborted a request.", "AbortError");
9954
+ }
9955
+ try {
9956
+ return await fn();
9957
+ } catch (error) {
9958
+ if (signal?.aborted || error?.name === "AbortError") {
9959
+ throw error;
9960
+ }
9961
+ attempt++;
9962
+ if (attempt >= maxRetries) {
9963
+ throw error;
9964
+ }
9965
+ const delay = Math.min(initialDelayMs * Math.pow(2, attempt - 1), maxDelayMs);
9966
+ await new Promise((resolve, reject) => {
9967
+ const timer = setTimeout(resolve, delay);
9968
+ if (signal) {
9969
+ signal.addEventListener("abort", () => {
9970
+ clearTimeout(timer);
9971
+ reject(new DOMException("The user aborted a request.", "AbortError"));
9972
+ });
9973
+ }
9974
+ });
9975
+ }
9976
+ }
9977
+ };
9693
9978
  TERMINATION_SIGNAL = false;
9694
9979
  MULTIMODAL_MODELS = [
9695
9980
  // OpenRouter models
@@ -10242,11 +10527,21 @@ var init_ai = __esm({
10242
10527
  "write_docx": "Creating",
10243
10528
  "generate_image": "Generating",
10244
10529
  "todo": "Planning",
10245
- "Todo": "Planning"
10530
+ "Todo": "Planning",
10531
+ "invoke_sync": "Spawning SubAgent",
10532
+ "invoke": "Spawning SubAgent",
10533
+ "get_progress": "Checking SubAgent"
10246
10534
  };
10247
10535
  getToolDetail = (toolName, argsStr) => {
10248
10536
  try {
10249
10537
  const pArgs = parseArgs(argsStr);
10538
+ const normToolName = toolName.toLowerCase().replace(/_/g, "");
10539
+ if (normToolName === "invokesync" || normToolName === "invoke") {
10540
+ return pArgs.title || (pArgs.task ? pArgs.task.substring(0, 30) : null);
10541
+ }
10542
+ if (normToolName === "getprogress") {
10543
+ return pArgs.id || pArgs.taskId;
10544
+ }
10250
10545
  const filePath = pArgs.path || pArgs.targetFile || pArgs.TargetFile || pArgs.directory;
10251
10546
  return filePath ? path19.basename(filePath.replace(/["']/g, "").replace(/\\/g, "/")) : null;
10252
10547
  } catch (e) {
@@ -10736,7 +11031,7 @@ ${originalTextProcessed.length > USER_CONTEXT_LENGTH ? "... (truncated) ...\n\n"
10736
11031
  const translatedText = translateKimiToolCalls(text);
10737
11032
  const cleanText = translatedText.replace(/(?:<(think|thought|thoughts)>|\[(think|thought|thoughts)\])[\s\S]*?(?:<\/(think|thought|thoughts)>|\[\/(think|thought|thoughts)\]|$)/gi, "");
10738
11033
  const results = [];
10739
- const toolRegex = /\[\s*tool:functions\.([a-z0-9_]+)\s*\(/gi;
11034
+ const toolRegex = /\[\s*(?:tool:functions\.|agent:generalist\.)([a-z0-9_]+)\s*\(/gi;
10740
11035
  let match;
10741
11036
  while ((match = toolRegex.exec(cleanText)) !== null) {
10742
11037
  const toolName = match[1];
@@ -10795,39 +11090,61 @@ ${originalTextProcessed.length > USER_CONTEXT_LENGTH ? "... (truncated) ...\n\n"
10795
11090
  client = new GoogleGenAI({ apiKey });
10796
11091
  return client;
10797
11092
  };
10798
- generateSimpleContent = async (settings, model, contents, systemInstruction, thinkingLevel = "Fast", temperature = 0.75) => {
10799
- const { aiProvider = "Google", apiKey, mode } = settings;
10800
- let fullText = "";
10801
- let usageMetadata = null;
10802
- const normalizedContents = typeof contents === "string" ? [{ role: "user", parts: [{ text: contents }] }] : contents;
10803
- let stream;
10804
- if (aiProvider === "OpenRouter") {
10805
- stream = getOpenRouterStream(apiKey, model, normalizedContents, systemInstruction, thinkingLevel, mode, false, null, temperature);
10806
- } else if (aiProvider === "DeepSeek") {
10807
- stream = getDeepSeekStream(apiKey, model, normalizedContents, systemInstruction, thinkingLevel, mode, false, null, temperature);
10808
- } else if (aiProvider === "NVIDIA") {
10809
- stream = getNVIDIAStream(apiKey, model, normalizedContents, systemInstruction, thinkingLevel, mode, false, null, temperature);
10810
- } else {
10811
- const genStream = await client.models.generateContentStream({
10812
- model,
10813
- contents: normalizedContents,
10814
- config: {
10815
- systemInstruction,
10816
- temperature,
10817
- thinkingConfig: { includeThoughts: false, thinkingLevel: ThinkingLevel.MINIMAL }
11093
+ generateSimpleContent = async (settings, model, contents, systemInstruction, thinkingLevel = "Fast", temperature = 0.75, usageKey = "agent") => {
11094
+ return withRetry(async () => {
11095
+ const { aiProvider = "Google", apiKey, mode } = settings;
11096
+ let fullText = "";
11097
+ let usageMetadata = null;
11098
+ const normalizedContents = typeof contents === "string" ? [{ role: "user", parts: [{ text: contents }] }] : contents;
11099
+ let stream;
11100
+ if (aiProvider === "OpenRouter") {
11101
+ stream = getOpenRouterStream(apiKey, model, normalizedContents, systemInstruction, thinkingLevel, mode, false, null, temperature);
11102
+ } else if (aiProvider === "DeepSeek") {
11103
+ stream = getDeepSeekStream(apiKey, model, normalizedContents, systemInstruction, thinkingLevel, mode, false, null, temperature);
11104
+ } else if (aiProvider === "NVIDIA") {
11105
+ stream = getNVIDIAStream(apiKey, model, normalizedContents, systemInstruction, thinkingLevel, mode, false, null, temperature);
11106
+ } else {
11107
+ const genStream = await client.models.generateContentStream({
11108
+ model,
11109
+ contents: normalizedContents,
11110
+ config: {
11111
+ systemInstruction,
11112
+ temperature,
11113
+ thinkingConfig: { includeThoughts: false, thinkingLevel: ThinkingLevel.MINIMAL }
11114
+ }
11115
+ });
11116
+ stream = genStream;
11117
+ }
11118
+ for await (const chunk of stream) {
11119
+ if (chunk.candidates?.[0]?.content?.parts) {
11120
+ for (const part of chunk.candidates[0].content.parts) {
11121
+ if (part.text && !part.thought) fullText += part.text;
11122
+ }
10818
11123
  }
10819
- });
10820
- stream = genStream;
10821
- }
10822
- for await (const chunk of stream) {
10823
- if (chunk.candidates?.[0]?.content?.parts) {
10824
- for (const part of chunk.candidates[0].content.parts) {
10825
- if (part.text && !part.thought) fullText += part.text;
11124
+ if (chunk.usageMetadata) usageMetadata = chunk.usageMetadata;
11125
+ }
11126
+ if (usageMetadata) {
11127
+ const total = usageMetadata.totalTokenCount || 0;
11128
+ const cached = usageMetadata.cachedContentTokenCount || 0;
11129
+ const candidates = (usageMetadata.candidatesTokenCount || 0) + (usageMetadata.thoughtsTokenCount || 0);
11130
+ await addToUsage("tokens", total, aiProvider, model);
11131
+ if (cached > 0) {
11132
+ await addToUsage("cachedTokens", cached, aiProvider, model);
11133
+ }
11134
+ if (candidates > 0) {
11135
+ await addToUsage("candidateTokens", candidates, aiProvider, model);
11136
+ }
11137
+ if (settings && typeof settings.onUsage === "function") {
11138
+ settings.onUsage({
11139
+ totalTokenCount: total,
11140
+ cachedContentTokenCount: cached,
11141
+ candidatesTokenCount: candidates
11142
+ });
10826
11143
  }
10827
11144
  }
10828
- if (chunk.usageMetadata) usageMetadata = chunk.usageMetadata;
10829
- }
10830
- return { text: fullText, usageMetadata };
11145
+ await incrementUsage(usageKey, aiProvider);
11146
+ return { text: fullText, usageMetadata };
11147
+ });
10831
11148
  };
10832
11149
  consolidatePastMemories = async (currentChatId, settings) => {
10833
11150
  try {
@@ -10885,7 +11202,7 @@ ${newMemoryListStr}
10885
11202
  while (attempts <= maxAttempts && !success) {
10886
11203
  attempts++;
10887
11204
  try {
10888
- const response = await generateSimpleContent(settings, targetModel, prompt, null, "Fast");
11205
+ const response = await generateSimpleContent(settings, targetModel, prompt, null, "Fast", 0.75, "background");
10889
11206
  const responseText = response.text || "";
10890
11207
  const janitorToolCalls = detectToolCalls(responseText);
10891
11208
  if (janitorToolCalls.length === 0) {
@@ -10897,19 +11214,6 @@ ${newMemoryListStr}
10897
11214
  await dispatchTool(toolName, janitorToolCall.args, { chatId: currentChatId });
10898
11215
  }
10899
11216
  }
10900
- if (response.usageMetadata) {
10901
- const meta = response.usageMetadata;
10902
- const total = meta.totalTokenCount || 0;
10903
- const cached = meta.cachedContentTokenCount || 0;
10904
- const candidates = (meta.candidatesTokenCount || 0) + (meta.thoughtsTokenCount || 0);
10905
- await addToUsage("tokens", total, aiProvider, targetModel);
10906
- if (cached > 0) {
10907
- await addToUsage("cachedTokens", cached, aiProvider, targetModel);
10908
- }
10909
- if (candidates > 0) {
10910
- await addToUsage("candidateTokens", candidates, aiProvider, targetModel);
10911
- }
10912
- }
10913
11217
  success = true;
10914
11218
  } catch (err) {
10915
11219
  if (attempts >= maxAttempts) {
@@ -10969,19 +11273,6 @@ Provide a consolidated summary of the entire session.`;
10969
11273
  attempts++;
10970
11274
  try {
10971
11275
  response = await generateSimpleContent(settings, targetModel, prompt, systemInstruction, "Fast");
10972
- if (response && response.usageMetadata) {
10973
- const meta = response.usageMetadata;
10974
- const total = meta.totalTokenCount || 0;
10975
- const cached = meta.cachedContentTokenCount || 0;
10976
- const candidates = (meta.candidatesTokenCount || 0) + (meta.thoughtsTokenCount || 0);
10977
- await addToUsage("tokens", total, aiProvider, targetModel);
10978
- if (cached > 0) {
10979
- await addToUsage("cachedTokens", cached, aiProvider, targetModel);
10980
- }
10981
- if (candidates > 0) {
10982
- await addToUsage("candidateTokens", candidates, aiProvider, targetModel);
10983
- }
10984
- }
10985
11276
  success = true;
10986
11277
  } catch (err) {
10987
11278
  if (attempts > 3) {
@@ -10989,19 +11280,6 @@ Provide a consolidated summary of the entire session.`;
10989
11280
  try {
10990
11281
  const fallbackModel = "gemini-3.1-flash-lite";
10991
11282
  const fallback = await generateSimpleContent(settings, fallbackModel, prompt, systemInstruction, "Fast");
10992
- if (fallback && fallback.usageMetadata) {
10993
- const meta = fallback.usageMetadata;
10994
- const total = meta.totalTokenCount || 0;
10995
- const cached = meta.cachedContentTokenCount || 0;
10996
- const candidates = (meta.candidatesTokenCount || 0) + (meta.thoughtsTokenCount || 0);
10997
- await addToUsage("tokens", total, aiProvider, fallbackModel);
10998
- if (cached > 0) {
10999
- await addToUsage("cachedTokens", cached, aiProvider, fallbackModel);
11000
- }
11001
- if (candidates > 0) {
11002
- await addToUsage("candidateTokens", candidates, aiProvider, fallbackModel);
11003
- }
11004
- }
11005
11283
  return fallback.text || "";
11006
11284
  } catch (e) {
11007
11285
  return "";
@@ -12006,11 +12284,13 @@ ${ideErr} [/ERROR]`;
12006
12284
  while (remaining.length > 0) {
12007
12285
  if (!isBufferingToolCall) {
12008
12286
  const toolIdx = remaining.indexOf("[tool");
12287
+ const agentIdx = remaining.indexOf("[agent");
12009
12288
  const endIdx = remaining.indexOf("[[END]]");
12010
12289
  const kimiSectionIdx = remaining.indexOf("<|tool_calls_section_begin|>");
12011
12290
  const kimiCallIdx = remaining.indexOf("<|tool_call_begin|>");
12012
12291
  const indices = [
12013
12292
  { type: "tool", idx: toolIdx, start: "[tool", end: "]" },
12293
+ { type: "agent", idx: agentIdx, start: "[agent", end: "]" },
12014
12294
  { type: "end", idx: endIdx, start: "[[END]]", end: "[[END]]" },
12015
12295
  { type: "kimi_section", idx: kimiSectionIdx, start: "<|tool_calls_section_begin|>", end: "<|tool_calls_section_end|>" },
12016
12296
  { type: "kimi_call", idx: kimiCallIdx, start: "<|tool_call_begin|>", end: "<|tool_call_end|>" }
@@ -12025,7 +12305,7 @@ ${ideErr} [/ERROR]`;
12025
12305
  toolCallBuffer = "";
12026
12306
  remaining = remaining.substring(match2.idx);
12027
12307
  } else {
12028
- const potentialStarts = ["[tool", "[[END]]", "<|tool_calls_section_begin|>", "<|tool_call_begin|>"];
12308
+ const potentialStarts = ["[tool", "[agent", "[[END]]", "<|tool_calls_section_begin|>", "<|tool_call_begin|>"];
12029
12309
  let splitPoint = -1;
12030
12310
  for (const start of potentialStarts) {
12031
12311
  for (let len = start.length - 1; len > 0; len--) {
@@ -12033,8 +12313,9 @@ ${ideErr} [/ERROR]`;
12033
12313
  splitPoint = remaining.length - len;
12034
12314
  const idx = potentialStarts.indexOf(start);
12035
12315
  if (idx === 0) activeBufferType = "tool";
12036
- else if (idx === 1) activeBufferType = "end";
12037
- else if (idx === 2) activeBufferType = "kimi_section";
12316
+ else if (idx === 1) activeBufferType = "agent";
12317
+ else if (idx === 2) activeBufferType = "end";
12318
+ else if (idx === 3) activeBufferType = "kimi_section";
12038
12319
  else activeBufferType = "kimi_call";
12039
12320
  break;
12040
12321
  }
@@ -12055,9 +12336,10 @@ ${ideErr} [/ERROR]`;
12055
12336
  }
12056
12337
  } else {
12057
12338
  const combined = toolCallBuffer + remaining;
12058
- if (activeBufferType === "tool") {
12059
- const protocolPrefix = "[tool:functions.";
12060
- if (!combined.startsWith("[tool") || combined.length >= protocolPrefix.length && !combined.startsWith(protocolPrefix)) {
12339
+ if (activeBufferType === "tool" || activeBufferType === "agent") {
12340
+ const protocolPrefix = activeBufferType === "tool" ? "[tool:functions." : "[agent:generalist.";
12341
+ const startPrefix = activeBufferType === "tool" ? "[tool" : "[agent";
12342
+ if (!combined.startsWith(startPrefix) || combined.length >= protocolPrefix.length && !combined.startsWith(protocolPrefix)) {
12061
12343
  msgs.push({ type: "text", content: combined });
12062
12344
  toolCallBuffer = "";
12063
12345
  isBufferingToolCall = false;
@@ -12068,7 +12350,7 @@ ${ideErr} [/ERROR]`;
12068
12350
  }
12069
12351
  let endIdx = -1;
12070
12352
  let endTag = "]";
12071
- if (activeBufferType === "tool") {
12353
+ if (activeBufferType === "tool" || activeBufferType === "agent") {
12072
12354
  let balance = 0;
12073
12355
  let inString = null;
12074
12356
  let bracketBalance = 0;
@@ -12226,7 +12508,6 @@ ${ideErr} [/ERROR]`;
12226
12508
  const toolContext = getActiveToolContext(turnText);
12227
12509
  if (toolContext.inside) {
12228
12510
  if (!lastToolEventTime) lastToolEventTime = Date.now();
12229
- const rawToolName = toolContext.toolName;
12230
12511
  const NORMALIZE_MAP = {
12231
12512
  "Ask": "ask",
12232
12513
  "WebSearch": "web_search",
@@ -12245,24 +12526,39 @@ ${ideErr} [/ERROR]`;
12245
12526
  "Chat": "chat",
12246
12527
  "chat": "chat",
12247
12528
  "GenerateImage": "generate_image",
12248
- "generate_image": "generate_image"
12529
+ "generate_image": "generate_image",
12530
+ "todo": "todo",
12531
+ "Todo": "todo",
12532
+ "invoke": "invoke",
12533
+ "invokeSync": "invoke_sync",
12534
+ "getProgress": "get_progress"
12249
12535
  };
12250
- const potentialTool = NORMALIZE_MAP[rawToolName] || rawToolName;
12536
+ const potentialTool = NORMALIZE_MAP[toolContext.toolName] || toolContext.toolName;
12251
12537
  const partialArgs = toolContext.args || "";
12252
12538
  let detail = null;
12253
- if (["write_file", "update_file", "view_file", "read_folder", "write_pdf", "write_docx", "search_keyword", "generate_image", "file_map"].includes(potentialTool)) {
12539
+ if (["write_file", "update_file", "view_file", "read_folder", "write_pdf", "write_docx", "search_keyword", "generate_image", "file_map", "invoke", "invoke_sync", "get_progress"].includes(potentialTool)) {
12254
12540
  const pArgs = parseArgs(partialArgs);
12255
12541
  const filePath = pArgs.path || pArgs.targetFile || pArgs.TargetFile || pArgs.directory;
12256
12542
  const keyword = pArgs.keyword;
12543
+ const title = pArgs.title || pArgs.task;
12544
+ const id = pArgs.id || pArgs.taskId;
12257
12545
  if (keyword) {
12258
12546
  detail = keyword.replace(/["']/g, "");
12259
12547
  } else if (filePath) {
12260
12548
  detail = path19.basename(filePath.replace(/["']/g, "").replace(/\\/g, "/"));
12549
+ } else if (title && (potentialTool === "invoke" || potentialTool === "invoke_sync")) {
12550
+ detail = title.replace(/["']/g, "").substring(0, 30);
12551
+ } else if (id && potentialTool === "get_progress") {
12552
+ detail = id.replace(/["']/g, "");
12261
12553
  } else {
12262
- const m = partialArgs.match(/(?:path|targetFile|TargetFile|directory|keyword)\s*=\s*\\?["']?([^\\"' \),]+)/);
12554
+ const m = partialArgs.match(/(?:path|targetFile|TargetFile|directory|keyword|id|taskId|title|task)\s*=\s*\\?["']?([^\\"' \),]+)/);
12263
12555
  if (m) {
12264
12556
  const val = m[1].replace(/["']/g, "");
12265
- detail = potentialTool === "search_keyword" || potentialTool === "file_map" ? val : path19.basename(val.replace(/\\/g, "/"));
12557
+ if (potentialTool === "invoke" || potentialTool === "invoke_sync" || potentialTool === "get_progress") {
12558
+ detail = val.substring(0, 30);
12559
+ } else {
12560
+ detail = potentialTool === "search_keyword" || potentialTool === "file_map" ? val : path19.basename(val.replace(/\\/g, "/"));
12561
+ }
12266
12562
  }
12267
12563
  }
12268
12564
  }
@@ -12413,7 +12709,10 @@ ${ideErr} [/ERROR]`;
12413
12709
  "GenerateImage": "generate_image",
12414
12710
  "generate_image": "generate_image",
12415
12711
  "todo": "todo",
12416
- "Todo": "todo"
12712
+ "Todo": "todo",
12713
+ "invoke": "invoke",
12714
+ "invokeSync": "invoke_sync",
12715
+ "getProgress": "get_progress"
12417
12716
  };
12418
12717
  const normToolName = NORMALIZE_MAP[toolCall.toolName] || toolCall.toolName;
12419
12718
  const displayLabel = TOOL_LABELS2[normToolName] || toolCall.toolName;
@@ -12422,7 +12721,7 @@ ${ideErr} [/ERROR]`;
12422
12721
  let label2 = "";
12423
12722
  if (normToolName === "web_search") {
12424
12723
  const { query, limit = 10 } = parseArgs(toolCall.args);
12425
- label2 = `\u2714 Searched: ${query}`;
12724
+ label2 = `\u2714 Searched: ${query} \u2192 ${limit}`;
12426
12725
  } else if (normToolName === "web_scrape") {
12427
12726
  const url = parseArgs(toolCall.args).url || "...";
12428
12727
  label2 = `\u2714 Visited: ${url}`;
@@ -12453,7 +12752,7 @@ ${ideErr} [/ERROR]`;
12453
12752
  } else if (isImage) {
12454
12753
  label2 = `\u2714 Viewed: ${targetPath2}`;
12455
12754
  } else {
12456
- label2 = `\u2714 Read: ${targetPath2} \u2192 Lines ${sLine} - ${actualEndLine} of ${totalLines}`;
12755
+ label2 = `${totalLines !== "..." ? "\u2714" : "\u2717"} Read: ${targetPath2} \u2192 ${totalLines !== "..." ? `Lines ${sLine} - ${actualEndLine} of ${totalLines}` : "File Not Found"}`;
12457
12756
  }
12458
12757
  } else if (normToolName === "list_files" || normToolName === "read_folder") {
12459
12758
  const action = normToolName === "list_files" ? "List" : "Viewed";
@@ -12473,7 +12772,13 @@ ${ideErr} [/ERROR]`;
12473
12772
  } else if (normToolName.toLowerCase() === "generate_image") {
12474
12773
  const { path: argPath, outputPath, output } = parseArgs(toolCall.args);
12475
12774
  label2 = `\u2714 Generated: ${argPath || outputPath || output || "generated_image.png"}`;
12476
- } else if (normToolName.toLowerCase() === "exec_command" || normToolName.toLowerCase() === "ask") {
12775
+ } else if (normToolName === "invoke_sync" || normToolName === "invoke") {
12776
+ const detail2 = getToolDetail(normToolName, toolCall.args);
12777
+ label2 = `\u2714 Elevating SubAgent${detail2 ? `: ${detail2}` : ""}`;
12778
+ } else if (normToolName === "get_progress") {
12779
+ const detail2 = getToolDetail(normToolName, toolCall.args);
12780
+ label2 = `\u2714 Checked${detail2 ? `: ${detail2}` : ""}`;
12781
+ } else if (normToolName === "exec_command" || normToolName === "ask") {
12477
12782
  label2 = "";
12478
12783
  } else {
12479
12784
  label2 = `Executed: ${toolCall.toolName}`;
@@ -12994,7 +13299,7 @@ ${snippet2}
12994
13299
  [SYSTEM] Check the content preview for verification [/SYSTEM]`;
12995
13300
  }
12996
13301
  const action = normToolName === "write_file" ? "Created" : "Edited";
12997
- const feedbackLabel = `\u2714 ${action}: ${filePath || "..."}`;
13302
+ const feedbackLabel = `\u2714 ${action}: ${filePath || "..."}`;
12998
13303
  let terminalWidth = 115;
12999
13304
  if (process.stdout.isTTY) {
13000
13305
  terminalWidth = process.stdout.columns - 5 || 120;
@@ -13028,7 +13333,7 @@ ${boxMid}`) };
13028
13333
  }
13029
13334
  if (normToolName === "write_file" || normToolName === "update_file") {
13030
13335
  const action = normToolName === "write_file" ? "Write Cancelled" : "Edit Denied";
13031
- const deniedLabel = `\u2714 ${action}: ${parseArgs(toolCall.args).path || "..."}`.toUpperCase();
13336
+ const deniedLabel = `\u2717 ${action}: ${parseArgs(toolCall.args).path || "..."}`.toUpperCase();
13032
13337
  let terminalWidth = 115;
13033
13338
  if (process.stdout.isTTY) {
13034
13339
  terminalWidth = process.stdout.columns - 5 || 120;
@@ -13080,7 +13385,13 @@ ${boxBottom}`}`) };
13080
13385
  onAskUser: settings.onAskUser,
13081
13386
  systemSettings: settings.systemSettings,
13082
13387
  mode,
13083
- isMultiModal: isModelMultimodal(targetModel)
13388
+ isMultiModal: isModelMultimodal(targetModel),
13389
+ onVisualFeedback: settings.onVisualFeedback,
13390
+ onSubagentUpdate: settings.onSubagentUpdate,
13391
+ modelName: targetModel,
13392
+ aiProvider: settings.aiProvider,
13393
+ apiKey: settings.apiKey,
13394
+ onUsage: settings.onUsage
13084
13395
  };
13085
13396
  if (normToolName === "write_file" || normToolName === "update_file") {
13086
13397
  try {
@@ -13134,7 +13445,8 @@ ${boxBottom}`}`) };
13134
13445
  const boxMid = `${postLabel.padEnd(boxWidth - 2).substring(0, boxWidth - 2)}`;
13135
13446
  const boxBottom = ` ${" ".repeat(boxWidth)} `;
13136
13447
  yield { type: "visual_feedback", content: colorMainWords(`${boxBottom}
13137
- ${boxMid}`) };
13448
+ ${boxMid}
13449
+ `) };
13138
13450
  }
13139
13451
  if (normToolName === "todo") {
13140
13452
  const { method, tasks, markDone } = parseArgs(toolCall.args);
@@ -13534,6 +13846,120 @@ Error Log can be found in ${path19.join(LOGS_DIR, "agent", "error.log")}`);
13534
13846
  }
13535
13847
  yield { type: "status", content: null };
13536
13848
  };
13849
+ runSubagent = async (task, settings, model = null, allowedTools = null, maxTurns = 20, logCallback = null) => {
13850
+ const savedSettings = await loadSettings();
13851
+ const mergedSettings = { ...savedSettings, ...settings };
13852
+ const targetModel = model || settings?.modelName || settings?.activeModel || savedSettings.activeModel;
13853
+ const SUBAGENT_TOOL_DEFINITIONS = {
13854
+ "readfile": '- [tool:functions.ReadFile(path="...", startLine=number, endLine=number)]. View files, supports images/docs.',
13855
+ "readfolder": '- [tool:functions.ReadFolder(path="...")]. Detailed folder contents and stats.',
13856
+ "filemap": '- [tool:functions.FileMap(path="path/file")]. Shows file structure, functions, classes, imports/exports.',
13857
+ "patchfile": '- [tool:functions.PatchFile(path="...", replaceContent1="...", newContent1="...")]. Surgical block replacement for editing files.',
13858
+ "writefile": '- [tool:functions.WriteFile(path="...", content="...")]. Creates or overwrites a file.',
13859
+ "searchkeyword": '- [tool:functions.SearchKeyword(keyword="...", file="optional", subString="true/false")]. Global project text search.',
13860
+ "writepdf": '- [tool:functions.WritePDF(path="...", content="...", orientation="...")]. Generates PDF documents.',
13861
+ "writedoc": '- [tool:functions.WriteDoc(path="...", content="...")]. Generates Word documents.',
13862
+ "websearch": '- [tool:functions.WebSearch(query="...", limit=number)]. Web Search.',
13863
+ "webscrape": '- [tool:functions.WebScrape(url="...")]. Web Scrape.'
13864
+ };
13865
+ const providedToolsSection = `
13866
+
13867
+ -- Provided Tools --
13868
+ ${Object.values(SUBAGENT_TOOL_DEFINITIONS).join("\n")}`;
13869
+ const systemInstruction = `You are a subagent helping the main FluxFlow CLI agent.
13870
+ Your task is: "${task}"
13871
+ You have access to the same tools as the main agent. Execute tools as needed using the [tool:functions.ToolName(args)] syntax${providedToolsSection}
13872
+ Your main focus should be on tools and task, not chatting. Your Chat won't be visible to user
13873
+ Once you have fully completed the task, provide a concise final structured summary preferebly in Tables/Bullet Points. Current Time: ${(/* @__PURE__ */ new Date()).toLocaleString("en-US", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: true }).replace(/(\d+)\/(\d+)\/(\d+),/, "$3-$1-$2").replace(":", "-")}`;
13874
+ const subagentHistory = [
13875
+ { role: "user", text: `Please execute this task: ${task}` }
13876
+ ];
13877
+ let turn = 0;
13878
+ let finalAnswer = "";
13879
+ while (turn < maxTurns) {
13880
+ const contents = subagentHistory.map((m) => ({
13881
+ role: m.role === "user" ? "user" : "model",
13882
+ parts: [{ text: m.text }]
13883
+ }));
13884
+ if (logCallback) logCallback(`[Subagent Turn ${turn + 1}] Invoking model ${targetModel}...`);
13885
+ const response = await generateSimpleContent(mergedSettings, targetModel, contents, systemInstruction, "Fast");
13886
+ const responseText = response.text || "";
13887
+ const cleanResponse = responseText.replace(/(?:<think>|\[think\])[\s\S]*?(?:<\/think>|\[\/think\])/gi, "").trim();
13888
+ finalAnswer = cleanResponse;
13889
+ if (logCallback) logCallback(`[Subagent Response]
13890
+ ${cleanResponse}
13891
+ `);
13892
+ subagentHistory.push({ role: "agent", text: cleanResponse });
13893
+ const toolCalls = detectToolCalls(cleanResponse);
13894
+ if (toolCalls.length === 0) {
13895
+ break;
13896
+ }
13897
+ let toolResultsStr = "";
13898
+ for (const toolCall of toolCalls) {
13899
+ const normalizedToolName = toolCall.toolName.toLowerCase();
13900
+ const allowed = allowedTools ? allowedTools.some((t) => t.toLowerCase() === normalizedToolName) : true;
13901
+ if (!allowed) {
13902
+ const errorMsg = `ERROR: Tool [${toolCall.toolName}] is not in the allowed tools list for this subagent.`;
13903
+ if (logCallback) logCallback(`[Blocked Tool Call] ${toolCall.toolName} - not allowed
13904
+ `);
13905
+ toolResultsStr += `${errorMsg}
13906
+
13907
+ `;
13908
+ continue;
13909
+ }
13910
+ let label2 = "";
13911
+ if (normalizedToolName === "web_search") {
13912
+ const { query, limit = 10 } = parseArgs(toolCall.args);
13913
+ label2 = `\u2714 \x1B[95mSearched\x1B[0m: ${query} \u2192 ${limit}`;
13914
+ } else if (normalizedToolName === "web_scrape") {
13915
+ const url = parseArgs(toolCall.args).url || "...";
13916
+ label2 = `\u2714 \x1B[95mVisited\x1B[0m: ${url}`;
13917
+ } else if (normalizedToolName === "view_file") {
13918
+ const { path: targetPath } = parseArgs(toolCall.args);
13919
+ label2 = `\u2714 \x1B[95mRead\x1B[0m: ${targetPath}`;
13920
+ } else if (normalizedToolName === "list_files" || normalizedToolName === "read_folder") {
13921
+ const path21 = parseArgs(toolCall.args).path || "...";
13922
+ label2 = `\u2714 \x1B[95mViewed\x1B[0m: ${path21}`;
13923
+ } else if (normalizedToolName === "write_file" || normalizedToolName === "writefile") {
13924
+ const path21 = parseArgs(toolCall.args).path || "...";
13925
+ label2 = `\u2714 \x1B[95mCreated\x1B[0m: ${path21}`;
13926
+ } else if (normalizedToolName === "update_file" || normalizedToolName === "updatefile" || normalizedToolName === "patchfile" || normalizedToolName === "patch_file") {
13927
+ const path21 = parseArgs(toolCall.args).path || "...";
13928
+ label2 = `\u2714 \x1B[95mEdited\x1B[0m: ${path21}`;
13929
+ } else if (normalizedToolName === "file_map") {
13930
+ const path21 = parseArgs(toolCall.args).path || "...";
13931
+ label2 = `\u2714 \x1B[95mGet Map\x1B[0m: ${path21}`;
13932
+ } else {
13933
+ const displayLabel = TOOL_LABELS2[normalizedToolName] || toolCall.toolName;
13934
+ const detail = getToolDetail(normalizedToolName, toolCall.args);
13935
+ label2 = `\u2714 \x1B[95m${displayLabel}\x1B[0m${detail ? `: ${detail}` : ""}`;
13936
+ }
13937
+ if (settings.onVisualFeedback && label2) {
13938
+ settings.onVisualFeedback(label2);
13939
+ }
13940
+ if (logCallback) logCallback(`[Executing Tool] ${toolCall.toolName}(${toolCall.args})...`);
13941
+ try {
13942
+ const result = await dispatchTool(toolCall.toolName, toolCall.args, { ...settings, mode: "Flux" });
13943
+ if (logCallback) logCallback(`[Tool Result]
13944
+ ${result}
13945
+ `);
13946
+ toolResultsStr += `[TOOL RESULT for ${toolCall.toolName}]: ${result}
13947
+
13948
+ `;
13949
+ } catch (e) {
13950
+ const errorMsg = `ERROR: Execution failed for [${toolCall.toolName}]: ${e.message}`;
13951
+ if (logCallback) logCallback(`[Tool Error] ${errorMsg}
13952
+ `);
13953
+ toolResultsStr += `${errorMsg}
13954
+
13955
+ `;
13956
+ }
13957
+ }
13958
+ subagentHistory.push({ role: "user", text: toolResultsStr.trim() });
13959
+ turn++;
13960
+ }
13961
+ return finalAnswer;
13962
+ };
13537
13963
  }
13538
13964
  });
13539
13965
 
@@ -14743,6 +15169,7 @@ function App({ args = [] }) {
14743
15169
  const [activeCommand, setActiveCommand] = useState14(null);
14744
15170
  const [execOutput, setExecOutput] = useState14("");
14745
15171
  const [isTerminalFocused, setIsTerminalFocused] = useState14(false);
15172
+ const [activeSubagents, setActiveSubagents] = useState14([]);
14746
15173
  const [tick, setTick] = useState14(0);
14747
15174
  const isFirstRender = useRef3(true);
14748
15175
  const isSecondRender = useRef3(true);
@@ -15830,7 +16257,7 @@ function App({ args = [] }) {
15830
16257
  // --- GLM (Zhipu AI) ---
15831
16258
  {
15832
16259
  cmd: "z-ai/glm-5.1",
15833
- desc: "Text Only"
16260
+ desc: "Text Only [DEPRICATED]"
15834
16261
  },
15835
16262
  // --- MiniMax Family ---
15836
16263
  {
@@ -16822,7 +17249,7 @@ ${timestamp}` };
16822
17249
  id: "cancel-" + Date.now(),
16823
17250
  role: "system",
16824
17251
  text: "\n\n\x1B[33m\u24D8 Request Cancelled\x1B[0m",
16825
- isMeta: true
17252
+ isMeta: false
16826
17253
  }];
16827
17254
  setCompletedIndex(newMsgs.length);
16828
17255
  return newMsgs;
@@ -16882,6 +17309,15 @@ ${timestamp}` };
16882
17309
  apiTier,
16883
17310
  cols: terminalSize.columns - 6,
16884
17311
  rows: 30,
17312
+ onVisualFeedback: (content) => {
17313
+ setMessages((prev) => {
17314
+ const updatedPrev = prev.map((m) => m.isStreaming ? { ...m, isStreaming: false } : m);
17315
+ return [...updatedPrev, { id: "visual-" + Date.now(), role: "system", text: content, isVisualFeedback: true }];
17316
+ });
17317
+ },
17318
+ onSubagentUpdate: () => {
17319
+ setActiveSubagents([...subagentProgress]);
17320
+ },
16885
17321
  onExecStart: (cmd) => {
16886
17322
  setActiveCommand(cmd);
16887
17323
  setExecOutput("");
@@ -16977,6 +17413,20 @@ Selection: ${val}`,
16977
17413
  });
16978
17414
  setActiveView("ask");
16979
17415
  });
17416
+ },
17417
+ onUsage: (usage) => {
17418
+ const total = usage.totalTokenCount || 0;
17419
+ const cached = usage.cachedContentTokenCount || 0;
17420
+ const candidates = usage.candidatesTokenCount || 0;
17421
+ setSessionStats({ tokens: total });
17422
+ setSessionTotalTokens((prev) => prev + total);
17423
+ if (cached > 0) {
17424
+ setSessionTotalCachedTokens((prev) => prev + cached);
17425
+ }
17426
+ if (candidates > 0) {
17427
+ setSessionTotalCandidateTokens((prev) => prev + candidates);
17428
+ }
17429
+ setSessionAgentCalls((prev) => prev + 1);
16980
17430
  }
16981
17431
  },
16982
17432
  async () => {
@@ -17255,11 +17705,11 @@ Selection: ${val}`,
17255
17705
  }
17256
17706
  const chunkLower = chunkText.toLowerCase();
17257
17707
  if (chunkText.includes("```")) inCodeBlock = !inCodeBlock;
17258
- if (chunkLower.includes("tool:functions.")) {
17708
+ if (chunkLower.includes("tool:functions.") || chunkLower.includes("agent:generalist.")) {
17259
17709
  inToolCall = true;
17260
17710
  toolCallBalance = 0;
17261
17711
  inToolCallString = null;
17262
- if (chunkText.includes("[tool:functions.")) toolCallBalance = 0;
17712
+ if (chunkText.includes("[tool:functions.") || chunkText.includes("[agent:generalist.")) toolCallBalance = 0;
17263
17713
  }
17264
17714
  if (inToolCall) {
17265
17715
  for (let j = 0; j < chunkText.length; j++) {
@@ -17351,7 +17801,7 @@ Selection: ${val}`,
17351
17801
  });
17352
17802
  } else if (!inThinkMode) {
17353
17803
  const chunkLower2 = chunkText.toLowerCase();
17354
- if (!toolCallEncounteredInTurn && chunkLower2.includes("tool:functions.")) {
17804
+ if (!toolCallEncounteredInTurn && (chunkLower2.includes("tool:functions.") || chunkLower2.includes("agent:generalist."))) {
17355
17805
  toolCallEncounteredInTurn = true;
17356
17806
  }
17357
17807
  if (!currentAgentId) {
@@ -18433,7 +18883,7 @@ Selection: ${val}`,
18433
18883
  }
18434
18884
  )));
18435
18885
  default:
18436
- return /* @__PURE__ */ React15.createElement(Box14, { flexDirection: "column", marginTop: 1, flexShrink: 0, width: "100%" }, showBtwBox && btwResponse && /* @__PURE__ */ React15.createElement(Box14, { flexDirection: "column", borderStyle: "round", borderColor: "grey", paddingX: 2, paddingY: 1, width: "100%", marginBottom: 1 }, /* @__PURE__ */ React15.createElement(Box14, { justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React15.createElement(Text15, { color: "white", bold: true, underline: true }, "INQUIRY RESPONSE"), /* @__PURE__ */ React15.createElement(Text15, { color: "gray" }, "[ ESC to Close ]")), /* @__PURE__ */ React15.createElement(Box14, { marginTop: 1, width: "100%" }, /* @__PURE__ */ React15.createElement(CodeRenderer, { text: btwResponse, columns: terminalSize.columns - 6 }))), /* @__PURE__ */ React15.createElement(Box14, { paddingX: 1, marginBottom: 0, justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React15.createElement(Box14, null, statusText ? /* @__PURE__ */ React15.createElement(Box14, { gap: 1 }, /* @__PURE__ */ React15.createElement(build_default, null), /* @__PURE__ */ React15.createElement(Text15, { color: "white", bold: true, italic: true }, statusText.trimEnd()), /* @__PURE__ */ React15.createElement(Text15, { color: "gray" }, activeTime > 0 ? `[${activeTime.toFixed(0)}s]` : "")) : /* @__PURE__ */ React15.createElement(Text15, { color: "white", italic: true }, input.length > 0 && escPressCount ? "Press ESC again to clear input" : hasPasteBlock ? "Press CTRL + O to expand" : "Waiting for input...")), /* @__PURE__ */ React15.createElement(Box14, null, wittyPhrase && /* @__PURE__ */ React15.createElement(Box14, null, /* @__PURE__ */ React15.createElement(Text15, { color: "gray", italic: true }, wittyPhrase), /* @__PURE__ */ React15.createElement(Text15, { color: "gray", dimColor: true }, " \u2503 ")), /* @__PURE__ */ React15.createElement(Text15, { color: "white" }, tempModelOverride || activeModel))), /* @__PURE__ */ React15.createElement(Box14, { flexDirection: "column", width: "100%" }, /* @__PURE__ */ React15.createElement(Box14, { width: "100%", height: 1, overflow: "hidden" }, /* @__PURE__ */ React15.createElement(Text15, { color: "#555555" }, "\u2584".repeat(Math.max(1, terminalSize.columns)))), /* @__PURE__ */ React15.createElement(
18886
+ return /* @__PURE__ */ React15.createElement(Box14, { flexDirection: "column", marginTop: 1, flexShrink: 0, width: "100%" }, showBtwBox && btwResponse && /* @__PURE__ */ React15.createElement(Box14, { flexDirection: "column", borderStyle: "round", borderColor: "grey", paddingX: 2, paddingY: 1, width: "100%", marginBottom: 1 }, /* @__PURE__ */ React15.createElement(Box14, { justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React15.createElement(Text15, { color: "white", bold: true, underline: true }, "INQUIRY RESPONSE"), /* @__PURE__ */ React15.createElement(Text15, { color: "gray" }, "[ ESC to Close ]")), /* @__PURE__ */ React15.createElement(Box14, { marginTop: 1, width: "100%" }, /* @__PURE__ */ React15.createElement(CodeRenderer, { text: btwResponse, columns: terminalSize.columns - 6 }))), activeSubagents.filter((sa) => sa.status === "running").length > 0 && /* @__PURE__ */ React15.createElement(Box14, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 2, paddingY: 0, width: "100%", marginBottom: 1 }, /* @__PURE__ */ React15.createElement(Box14, { justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React15.createElement(Text15, { color: "white", bold: true }, "ACTIVE SUBAGENTS")), /* @__PURE__ */ React15.createElement(Box14, { flexDirection: "column", marginTop: 1, width: "100%" }, activeSubagents.filter((sa) => sa.status === "running").map((sa) => /* @__PURE__ */ React15.createElement(Box14, { key: sa.id, justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React15.createElement(Text15, { color: "white" }, " \u2022 ", sa.title, " ", /* @__PURE__ */ React15.createElement(Text15, { color: "white", dimColor: true }, "(", sa.id, ")")), /* @__PURE__ */ React15.createElement(Text15, { color: "white" }, "Executing: ", /* @__PURE__ */ React15.createElement(Text15, { color: "white", dimColor: true, bold: true }, sa.currentTool || "Active")))))), /* @__PURE__ */ React15.createElement(Box14, { paddingX: 1, marginBottom: 0, justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React15.createElement(Box14, null, statusText ? /* @__PURE__ */ React15.createElement(Box14, { gap: 1 }, /* @__PURE__ */ React15.createElement(build_default, null), /* @__PURE__ */ React15.createElement(Text15, { color: "white", bold: true, italic: true }, statusText.trimEnd()), /* @__PURE__ */ React15.createElement(Text15, { color: "gray" }, activeTime > 0 ? `[${activeTime.toFixed(0)}s]` : "")) : /* @__PURE__ */ React15.createElement(Text15, { color: "white", italic: true }, input.length > 0 && escPressCount ? "Press ESC again to clear input" : hasPasteBlock ? "Press CTRL + O to expand" : "Waiting for input...")), /* @__PURE__ */ React15.createElement(Box14, null, wittyPhrase && /* @__PURE__ */ React15.createElement(Box14, null, /* @__PURE__ */ React15.createElement(Text15, { color: "gray", italic: true }, wittyPhrase), /* @__PURE__ */ React15.createElement(Text15, { color: "gray", dimColor: true }, " \u2503 ")), /* @__PURE__ */ React15.createElement(Text15, { color: "white" }, tempModelOverride || activeModel))), /* @__PURE__ */ React15.createElement(Box14, { flexDirection: "column", width: "100%" }, /* @__PURE__ */ React15.createElement(Box14, { width: "100%", height: 1, overflow: "hidden" }, /* @__PURE__ */ React15.createElement(Text15, { color: "#555555" }, "\u2584".repeat(Math.max(1, terminalSize.columns)))), /* @__PURE__ */ React15.createElement(
18437
18887
  Box14,
18438
18888
  {
18439
18889
  backgroundColor: "#555555",
@@ -18612,6 +19062,7 @@ var init_app = __esm({
18612
19062
  init_AskUserModal();
18613
19063
  init_secrets();
18614
19064
  await init_ai();
19065
+ init_subagent_state();
18615
19066
  init_settings();
18616
19067
  init_history();
18617
19068
  init_ResumeModal();
@@ -18751,7 +19202,7 @@ var init_app = __esm({
18751
19202
  )));
18752
19203
  parseAgentText = (text) => {
18753
19204
  const blocks = [];
18754
- const toolRegex = /\[\s*tool:functions\.([a-z0-9_]+)\s*\(/gi;
19205
+ const toolRegex = /\[\s*(?:tool:functions\.|agent:generalist\.)([a-z0-9_]+)\s*\(/gi;
18755
19206
  let lastIdx = 0;
18756
19207
  let match;
18757
19208
  while ((match = toolRegex.exec(text)) !== null) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fluxflow-cli",
3
- "version": "2.16.9",
4
- "date": "2026-07-02",
3
+ "version": "3.0.0",
4
+ "date": "2026-07-03",
5
5
  "description": "A high-fidelity agentic terminal assistant for the Flux Era.",
6
6
  "keywords": [
7
7
  "ai",