fluxflow-cli 1.8.9 → 1.8.10

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.
Files changed (3) hide show
  1. package/TOOLS.md +2 -0
  2. package/dist/fluxflow.js +266 -214
  3. package/package.json +1 -1
package/TOOLS.md CHANGED
@@ -14,6 +14,7 @@ Flux Flow provides a robust set of tools that allow the AI to interact with the
14
14
  | **View/Read Files** | ✅ | ❌ |
15
15
  | **Write/Update Files** | ✅ | ❌ |
16
16
  | **Execute Commands** | ✅ | ❌ |
17
+ | **Search Keyword** | ✅ | ❌ |
17
18
 
18
19
  ---
19
20
 
@@ -37,6 +38,7 @@ Flux Flow provides a robust set of tools that allow the AI to interact with the
37
38
  - **`view_file`**: Reads the content of a file.
38
39
  - **Native Multimodality**: Supports analyzing images (JPG, PNG, WEBP) and PDF documents. The tool automatically detects binary formats and encodes them for AI analysis.
39
40
  - **Text Reading**: Supports specific line ranges (`start_line`, `end_line`) to manage context size efficiently.
41
+ - **`search_keyword`**: Performs a global project search for a specific string or keyword. Returns file paths and line numbers where matches are found, making it essential for navigation and impact analysis.
40
42
 
41
43
  ### ✍️ Code Editing
42
44
  - **`write_file`**: Creates a new file or completely overwrites an existing one with new content.
package/dist/fluxflow.js CHANGED
@@ -721,7 +721,7 @@ Prefer write_file tool to write code instead of chat by default.
721
721
  - DEV & FILE TOOLS ARE NOT AVAILABLE IN FLOW MODE. If you need to access files, tell the user to switch to FLUX MODE (manually by user).`.trim()}
722
722
  -----------------
723
723
 
724
- Results will be provided in the next loop as: [TOOL_RESULT]: [content]
724
+ Results will be provided in the next loop as: [TOOL_RESULT]: [content] under <user> tag. Treat them as SYSTEM MESSAGES. True user messages will be prefixed as 'USER_PROMPT' by the system.
725
725
  WHEN CALLING TOOLS, YOU **MUST** end your response with '[turn: continue]'. NEVER use '[turn: finish]' in the same turn as a tool call. After receiving the [TOOL_RESULT], acknowledge the output and verify if the goal is met; only then may you use '[turn: finish]', otherwise use '[turn: continue]'.
726
726
  Do NOT over-use tools. Use them only when strictly necessary for the user's objective. You can stack multiple tool calls 1-by-1.
727
727
  Distinguish clearly between tool discussion and execution. Use the 'tool:' prefix ONLY when calling a function. When discussing tools with the user, refer to them by name as nouns (e.g., 'write_file', 'list_files') to avoid accidental triggers and context bloat. Even in your <think> ... </think> tags, do not use the 'tool:' prefix when planning to select a tool.
@@ -2550,22 +2550,34 @@ var init_ai = __esm({
2550
2550
  yield { type: "status", content: "Steering Hint Injected." };
2551
2551
  }
2552
2552
  }
2553
- yield { type: "turn_reset", content: true };
2554
- const contents = modifiedHistory.filter((msg) => (msg.role === "user" || msg.role === "agent" || msg.role === "system") && !String(msg.id).startsWith("welcome") && !msg.isMeta).map((msg) => {
2555
- const parts = [{ text: msg.text }];
2556
- if (msg.binaryPart) {
2557
- parts.push(msg.binaryPart);
2558
- }
2559
- return {
2560
- role: msg.role === "user" || msg.role === "system" ? "user" : "model",
2561
- parts
2562
- };
2563
- });
2564
2553
  let stream;
2565
2554
  let success = false;
2566
2555
  let retryCount = 0;
2567
- while (retryCount <= MAX_RETRIES && !success) {
2556
+ let turnText = "";
2557
+ let lastToolSniffed = null;
2558
+ let lastToolEventTime = null;
2559
+ let toolResults = [];
2560
+ let toolCallPointer = 0;
2561
+ let isThinkingLoop = false;
2562
+ let isInitialAttempt = true;
2563
+ let accumulatedContext = "";
2564
+ while (retryCount <= MAX_RETRIES && !success && !TERMINATION_SIGNAL) {
2568
2565
  try {
2566
+ if (isInitialAttempt) {
2567
+ yield { type: "turn_reset", content: true };
2568
+ isInitialAttempt = false;
2569
+ accumulatedContext = "";
2570
+ }
2571
+ const contents = modifiedHistory.filter((msg) => (msg.role === "user" || msg.role === "agent" || msg.role === "system") && !String(msg.id).startsWith("welcome") && !msg.isMeta).map((msg) => {
2572
+ const parts = [{ text: msg.text }];
2573
+ if (msg.binaryPart) {
2574
+ parts.push(msg.binaryPart);
2575
+ }
2576
+ return {
2577
+ role: msg.role === "user" || msg.role === "system" ? "user" : "model",
2578
+ parts
2579
+ };
2580
+ });
2569
2581
  if (!await checkQuota("agent", settings)) {
2570
2582
  throw new Error("Error: Daily Quota Exausted for Agent");
2571
2583
  }
@@ -2613,226 +2625,264 @@ var init_ai = __esm({
2613
2625
  }
2614
2626
  }
2615
2627
  });
2616
- success = true;
2628
+ turnText = "";
2629
+ lastToolSniffed = null;
2630
+ lastToolEventTime = null;
2631
+ toolResults = [];
2632
+ toolCallPointer = 0;
2617
2633
  yield { type: "model_update", content: null };
2618
2634
  yield { type: "status", content: "Working..." };
2619
- } catch (err) {
2620
- const errMsg = err.status || err.error && err.error.message || String(err);
2621
- const errLog = String(err);
2622
- const date = (/* @__PURE__ */ new Date()).toLocaleString();
2623
- const agentErrDir = path16.join(LOGS_DIR, "agent");
2624
- if (!fs16.existsSync(agentErrDir)) fs16.mkdirSync(agentErrDir, { recursive: true });
2625
- fs16.appendFileSync(path16.join(agentErrDir, "error.log"), `ERROR [${date}]: ${errLog}
2626
- `);
2627
- if (retryCount < MAX_RETRIES) {
2628
- retryCount++;
2629
- const waitTime = Math.floor(Math.random() * (2e3 - 800 + 1)) + 800;
2630
- yield { type: "status", content: `Retrying (${retryCount}/${MAX_RETRIES + 1})...` };
2631
- await new Promise((resolve) => setTimeout(resolve, waitTime));
2632
- } else {
2633
- throw new Error(`Model cannot be reached: ${errMsg}. (Failed ${MAX_RETRIES + 1} times)`);
2634
- }
2635
- }
2636
- }
2637
- let turnText = "";
2638
- let lastToolSniffed = null;
2639
- let lastToolEventTime = null;
2640
- let toolResults = [];
2641
- let toolCallPointer = 0;
2642
- for await (const chunk of stream) {
2643
- if (TERMINATION_SIGNAL) break;
2644
- if (chunk.text) {
2645
- turnText += chunk.text;
2646
- yield { type: "text", content: chunk.text };
2647
- if (turnText.includes("tool:functions.")) {
2648
- if (!lastToolEventTime) lastToolEventTime = Date.now();
2649
- const parts = turnText.split("tool:functions.");
2650
- const potentialTool = parts[parts.length - 1].split("(")[0].trim();
2651
- if (potentialTool && /^[a-z_]+$/.test(potentialTool) && potentialTool !== lastToolSniffed) {
2652
- lastToolSniffed = potentialTool;
2653
- yield { type: "status", content: `Working (${potentialTool})...` };
2635
+ let dedupeBuffer = "";
2636
+ let isDedupeActive = accumulatedContext.length > 0;
2637
+ for await (const chunk of stream) {
2638
+ if (TERMINATION_SIGNAL) {
2639
+ yield { type: "status", content: "Termination Signal Received." };
2640
+ await new Promise((resolve) => setTimeout(resolve, 1500));
2641
+ break;
2654
2642
  }
2655
- }
2656
- const thinkBlocks = turnText.match(/<think>([\s\S]*?)(?:<\/think>|$)/gi) || [];
2657
- const thinkContent = thinkBlocks.join("").trim();
2658
- const sentences = thinkContent.split(/[.!?]\s+/);
2659
- const uniqueSentences = new Set(sentences);
2660
- const repetitionRatio = sentences.length > 10 ? (sentences.length - uniqueSentences.size) / sentences.length : 0;
2661
- const wordCount = thinkContent.split(/\s+/).filter((w) => w.length > 0).length;
2662
- let repetitionThreshold = 0.4;
2663
- let isOverVerbose = wordCount > 2500;
2664
- if (repetitionRatio > repetitionThreshold || isOverVerbose) {
2665
- const reason = repetitionRatio > repetitionThreshold ? "Circular Thinking Detected" : "Rambling Detected";
2666
- yield { type: "status", content: `${reason}. Re-centering...` };
2667
- await new Promise((resolve) => setTimeout(resolve, 3e3));
2668
- break;
2669
- }
2670
- const allToolsFound = detectToolCalls(turnText);
2671
- while (allToolsFound.length > toolCallPointer) {
2672
- const toolCall = allToolsFound[toolCallPointer];
2673
- yield { type: "status", content: `Working (${toolCall.toolName})...` };
2674
- let label = "";
2675
- if (toolCall.toolName === "web_search") {
2676
- const { query, limit = 10 } = parseArgs(toolCall.args);
2677
- label = `\u{1F50D} SEARCHING: "${query}" (${limit})`.toUpperCase();
2678
- } else if (toolCall.toolName === "web_scrape") {
2679
- const url = parseArgs(toolCall.args).url || "...";
2680
- label = `\u{1F4D6} READING SITE: ${url}`.toUpperCase();
2681
- } else if (toolCall.toolName === "view_file") {
2682
- const { path: targetPath2, StartLine, EndLine, start_line, end_line } = parseArgs(toolCall.args);
2683
- const rawStart = StartLine || start_line;
2684
- const rawEnd = EndLine || end_line;
2685
- const sLine = parseInt(rawStart) || 1;
2686
- const eLine = parseInt(rawEnd) || (rawStart ? sLine + 800 : 800);
2687
- let totalLines = "...";
2688
- let actualEndLine = eLine;
2689
- try {
2690
- const absPath = path16.resolve(process.cwd(), targetPath2);
2691
- if (fs16.existsSync(absPath)) {
2692
- const content = fs16.readFileSync(absPath, "utf8");
2693
- const lines = content.split("\n").length;
2694
- totalLines = lines;
2695
- actualEndLine = Math.min(eLine, lines);
2643
+ if (chunk.text) {
2644
+ if (isDedupeActive) {
2645
+ dedupeBuffer += chunk.text;
2646
+ if (dedupeBuffer.length >= accumulatedContext.length) {
2647
+ if (dedupeBuffer.startsWith(accumulatedContext)) {
2648
+ const newText = dedupeBuffer.substring(accumulatedContext.length);
2649
+ if (newText) {
2650
+ turnText += newText;
2651
+ yield { type: "text", content: newText };
2652
+ }
2653
+ isDedupeActive = false;
2654
+ } else {
2655
+ turnText += dedupeBuffer;
2656
+ yield { type: "text", content: dedupeBuffer };
2657
+ isDedupeActive = false;
2658
+ }
2696
2659
  }
2697
- } catch (e) {
2698
- }
2699
- const pathLower = targetPath2.toLowerCase();
2700
- const isPdf = pathLower.endsWith(".pdf");
2701
- const isImage = /\.(png|jpg|jpeg|webp|gif|bmp)$/.test(pathLower);
2702
- if (isPdf) {
2703
- label = `\u{1F4C4} ANALYZING PDF: ${targetPath2}`.toUpperCase();
2704
- } else if (isImage) {
2705
- label = `\u{1F4F8} ANALYZING IMAGE: ${targetPath2}`.toUpperCase();
2706
2660
  } else {
2707
- label = `\u{1F4C4} READING FILE: ${targetPath2}. LINES ${sLine} - ${actualEndLine} FROM ${totalLines}`.toUpperCase();
2661
+ turnText += chunk.text;
2662
+ yield { type: "text", content: chunk.text };
2708
2663
  }
2709
- } else if (toolCall.toolName === "list_files" || toolCall.toolName === "read_folder") {
2710
- const action = toolCall.toolName === "list_files" ? "LISTING" : "DISCOVERING";
2711
- label = `\u{1F4C2} ${action} DIRECTORY: ${parseArgs(toolCall.args).path || "."}`.toUpperCase();
2712
- } else if (toolCall.toolName === "write_file" || toolCall.toolName === "update_file") {
2713
- const action = toolCall.toolName === "write_file" ? "WRITING" : "PATCHING";
2714
- label = `\u{1F4BE} ${action} FILE: ${parseArgs(toolCall.args).path || "..."}`.toUpperCase();
2715
- } else if (toolCall.toolName === "write_pdf") {
2716
- label = `\u{1F4D1} GENERATING PDF: ${parseArgs(toolCall.args).path || "..."}`.toUpperCase();
2717
- } else if (toolCall.toolName === "write_docx") {
2718
- label = `\u{1F4DD} GENERATING DOCX: ${parseArgs(toolCall.args).path || "..."}`.toUpperCase();
2719
- } else if (toolCall.toolName === "write_pptx") {
2720
- label = `\u{1F4CA} GENERATING PPTX: ${parseArgs(toolCall.args).path || "..."}`.toUpperCase();
2721
- } else if (toolCall.toolName === "search_keyword") {
2722
- const { keyword } = parseArgs(toolCall.args);
2723
- label = `\u{1F50E} SEARCHING KEYWORD: "${keyword}"`.toUpperCase();
2724
- } else if (toolCall.toolName === "exec_command" || toolCall.toolName === "ask") {
2725
- label = "";
2726
- } else {
2727
- label = `EXECUTING ${toolCall.toolName}`.toUpperCase();
2728
- }
2729
- if (label) {
2730
- const boxWidth = Math.min(label.length + 4, 115);
2731
- const boxTop = `\u256D${"\u2500".repeat(boxWidth)}\u256E`;
2732
- const boxMid = `\u2502 ${label.padEnd(boxWidth - 2).substring(0, boxWidth - 2)} \u2502`;
2733
- const boxBottom = `\u2570${"\u2500".repeat(boxWidth)}\u256F`;
2734
- yield { type: "visual_feedback", content: `
2664
+ const actionableText = turnText.replace(/<think>[\s\S]*?(?:<\/think>|$)/gi, "");
2665
+ if (actionableText.includes("tool:functions.")) {
2666
+ if (!lastToolEventTime) lastToolEventTime = Date.now();
2667
+ const parts = actionableText.split("tool:functions.");
2668
+ const potentialTool = parts[parts.length - 1].split("(")[0].trim();
2669
+ if (potentialTool && /^[a-z_]+$/.test(potentialTool) && potentialTool !== lastToolSniffed) {
2670
+ lastToolSniffed = potentialTool;
2671
+ yield { type: "status", content: `Working (${potentialTool})...` };
2672
+ }
2673
+ }
2674
+ const thinkBlocks = turnText.match(/<think>([\s\S]*?)(?:<\/think>|$)/gi) || [];
2675
+ const thinkContent = thinkBlocks.join("").trim();
2676
+ const sentences = thinkContent.split(/[.!?]\s+/);
2677
+ const uniqueSentences = new Set(sentences);
2678
+ const repetitionRatio = sentences.length > 10 ? (sentences.length - uniqueSentences.size) / sentences.length : 0;
2679
+ const wordCount = thinkContent.split(/\s+/).filter((w) => w.length > 0).length;
2680
+ let repetitionThreshold = 0.4;
2681
+ let isOverVerbose = wordCount > 2500;
2682
+ if (repetitionRatio > repetitionThreshold || isOverVerbose) {
2683
+ const reason = repetitionRatio > repetitionThreshold ? "Thinking Loop Detected" : "Rambling Detected";
2684
+ yield { type: "status", content: `${reason}. Re-centering...` };
2685
+ isThinkingLoop = true;
2686
+ await new Promise((resolve) => setTimeout(resolve, 3e3));
2687
+ break;
2688
+ }
2689
+ const allToolsFound = detectToolCalls(actionableText);
2690
+ while (allToolsFound.length > toolCallPointer) {
2691
+ const toolCall = allToolsFound[toolCallPointer];
2692
+ yield { type: "status", content: `Working (${toolCall.toolName})...` };
2693
+ let label = "";
2694
+ if (toolCall.toolName === "web_search") {
2695
+ const { query, limit = 10 } = parseArgs(toolCall.args);
2696
+ label = `\u{1F50D} SEARCHING: "${query}" (${limit})`.toUpperCase();
2697
+ } else if (toolCall.toolName === "web_scrape") {
2698
+ const url = parseArgs(toolCall.args).url || "...";
2699
+ label = `\u{1F4D6} READING SITE: ${url}`.toUpperCase();
2700
+ } else if (toolCall.toolName === "view_file") {
2701
+ const { path: targetPath2, StartLine, EndLine, start_line, end_line } = parseArgs(toolCall.args);
2702
+ const rawStart = StartLine || start_line;
2703
+ const rawEnd = EndLine || end_line;
2704
+ const sLine = parseInt(rawStart) || 1;
2705
+ const eLine = parseInt(rawEnd) || (rawStart ? sLine + 800 : 800);
2706
+ let totalLines = "...";
2707
+ let actualEndLine = eLine;
2708
+ try {
2709
+ const absPath = path16.resolve(process.cwd(), targetPath2);
2710
+ if (fs16.existsSync(absPath)) {
2711
+ const content = fs16.readFileSync(absPath, "utf8");
2712
+ const lines = content.split("\n").length;
2713
+ totalLines = lines;
2714
+ actualEndLine = Math.min(eLine, lines);
2715
+ }
2716
+ } catch (e) {
2717
+ }
2718
+ const pathLower = targetPath2.toLowerCase();
2719
+ const isPdf = pathLower.endsWith(".pdf");
2720
+ const isImage = /\.(png|jpg|jpeg|webp|gif|bmp)$/.test(pathLower);
2721
+ if (isPdf) {
2722
+ label = `\u{1F4C4} ANALYZING PDF: ${targetPath2}`.toUpperCase();
2723
+ } else if (isImage) {
2724
+ label = `\u{1F4F8} ANALYZING IMAGE: ${targetPath2}`.toUpperCase();
2725
+ } else {
2726
+ label = `\u{1F4C4} READING FILE: ${targetPath2}. LINES ${sLine} - ${actualEndLine} FROM ${totalLines}`.toUpperCase();
2727
+ }
2728
+ } else if (toolCall.toolName === "list_files" || toolCall.toolName === "read_folder") {
2729
+ const action = toolCall.toolName === "list_files" ? "LISTING" : "DISCOVERING";
2730
+ label = `\u{1F4C2} ${action} DIRECTORY: ${parseArgs(toolCall.args).path || "."}`.toUpperCase();
2731
+ } else if (toolCall.toolName === "write_file" || toolCall.toolName === "update_file") {
2732
+ const action = toolCall.toolName === "write_file" ? "WRITING" : "PATCHING";
2733
+ label = `\u{1F4BE} ${action} FILE: ${parseArgs(toolCall.args).path || "..."}`.toUpperCase();
2734
+ } else if (toolCall.toolName === "write_pdf") {
2735
+ label = `\u{1F4D1} GENERATING PDF: ${parseArgs(toolCall.args).path || "..."}`.toUpperCase();
2736
+ } else if (toolCall.toolName === "write_docx") {
2737
+ label = `\u{1F4DD} GENERATING DOCX: ${parseArgs(toolCall.args).path || "..."}`.toUpperCase();
2738
+ } else if (toolCall.toolName === "write_pptx") {
2739
+ label = `\u{1F4CA} GENERATING PPTX: ${parseArgs(toolCall.args).path || "..."}`.toUpperCase();
2740
+ } else if (toolCall.toolName === "search_keyword") {
2741
+ const { keyword } = parseArgs(toolCall.args);
2742
+ label = `\u{1F50E} SEARCHING KEYWORD: "${keyword}"`.toUpperCase();
2743
+ } else if (toolCall.toolName === "exec_command" || toolCall.toolName === "ask") {
2744
+ label = "";
2745
+ } else {
2746
+ label = `EXECUTING ${toolCall.toolName}`.toUpperCase();
2747
+ }
2748
+ if (label) {
2749
+ const boxWidth = Math.min(label.length + 4, 115);
2750
+ const boxTop = `\u256D${"\u2500".repeat(boxWidth)}\u256E`;
2751
+ const boxMid = `\u2502 ${label.padEnd(boxWidth - 2).substring(0, boxWidth - 2)} \u2502`;
2752
+ const boxBottom = `\u2570${"\u2500".repeat(boxWidth)}\u256F`;
2753
+ yield { type: "visual_feedback", content: `
2735
2754
 
2736
2755
  ${boxTop}
2737
2756
  ${boxMid}
2738
2757
  ${boxBottom}
2739
2758
  ` };
2740
- }
2741
- if (toolCall.toolName === "exec_command") {
2742
- const { command } = parseArgs(toolCall.args);
2743
- if (command && settings.systemSettings && settings.systemSettings.allowExternalAccess === false) {
2744
- const riskyPatterns = [/[a-zA-Z]:[\\\/]/i, /^\//, /\.\.[\\\/]/, /\/etc\//, /\/var\//, /\/root\//, /\/bin\//, /\/usr\//];
2745
- const currentDrive = path16.resolve(process.cwd()).substring(0, 3).toLowerCase();
2746
- const isViolating = riskyPatterns.some((pattern) => {
2747
- if (pattern.source === "[a-zA-Z]:[\\\\\\/]") {
2748
- const driveMatch = command.match(/[a-zA-Z]:[\\\/]/i);
2749
- return driveMatch && driveMatch[0].toLowerCase() !== currentDrive;
2759
+ }
2760
+ if (toolCall.toolName === "exec_command") {
2761
+ const { command } = parseArgs(toolCall.args);
2762
+ if (command && settings.systemSettings && settings.systemSettings.allowExternalAccess === false) {
2763
+ const riskyPatterns = [/[a-zA-Z]:[\\\/]/i, /^\//, /\.\.[\\\/]/, /\/etc\//, /\/var\//, /\/root\//, /\/bin\//, /\/usr\//];
2764
+ const currentDrive = path16.resolve(process.cwd()).substring(0, 3).toLowerCase();
2765
+ const isViolating = riskyPatterns.some((pattern) => {
2766
+ if (pattern.source === "[a-zA-Z]:[\\\\\\/]") {
2767
+ const driveMatch = command.match(/[a-zA-Z]:[\\\/]/i);
2768
+ return driveMatch && driveMatch[0].toLowerCase() !== currentDrive;
2769
+ }
2770
+ return pattern.test(command);
2771
+ });
2772
+ if (isViolating) {
2773
+ const denyMsg = `Access Denied. Terminal is prohibited from accessing system drives (C://) or external directories while "External Workspace Access" is disabled.`;
2774
+ toolResults.push({ role: "user", text: `[TOOL_RESULT]: ERROR: ${denyMsg}` });
2775
+ yield { type: "tool_result", content: `[TOOL_RESULT]: ERROR: ${denyMsg}` };
2776
+ toolCallPointer++;
2777
+ continue;
2778
+ }
2779
+ }
2780
+ if (settings.onExecStart) settings.onExecStart(command || "Unknown");
2781
+ yield { type: "exec_start" };
2782
+ }
2783
+ const parsedArgs = parseArgs(toolCall.args);
2784
+ const targetPath = parsedArgs.path || parsedArgs.targetPath || null;
2785
+ if (targetPath) {
2786
+ const isExternalOff = settings.systemSettings && settings.systemSettings.allowExternalAccess === false;
2787
+ const absoluteTarget = path16.resolve(targetPath);
2788
+ const absoluteCwd = path16.resolve(process.cwd());
2789
+ if (isExternalOff && !absoluteTarget.startsWith(absoluteCwd)) {
2790
+ const denyMsg = `Access Denied. You are not allowed to access files outside the current workspace. To enable this, ask the user to turn on "External Workspace Access" in /settings.`;
2791
+ toolResults.push({ role: "user", text: `[TOOL_RESULT]: ERROR: ${denyMsg}` });
2792
+ yield { type: "tool_result", content: `[TOOL_RESULT]: ERROR: ${denyMsg}` };
2793
+ toolCallPointer++;
2794
+ continue;
2750
2795
  }
2751
- return pattern.test(command);
2796
+ }
2797
+ if (settings.onToolApproval) {
2798
+ let shouldPrompt = toolCall.toolName === "write_file" || toolCall.toolName === "update_file" || toolCall.toolName === "exec_command";
2799
+ if (shouldPrompt) {
2800
+ const approval = await settings.onToolApproval(toolCall.toolName, toolCall.args);
2801
+ if (approval === "deny") {
2802
+ if (toolCall.toolName === "exec_command" && settings.onExecEnd) settings.onExecEnd();
2803
+ const denyMsg = `Permission Denied: User rejected the ${toolCall.toolName === "exec_command" ? "terminal execution" : "file edit"}.`;
2804
+ toolResults.push({ role: "user", text: `[TOOL_RESULT]: ERROR: ${denyMsg}` });
2805
+ yield { type: "tool_result", content: `[TOOL_RESULT]: ERROR: ${denyMsg}` };
2806
+ toolCallPointer++;
2807
+ continue;
2808
+ }
2809
+ }
2810
+ }
2811
+ const effectiveStart = lastToolEventTime || Date.now();
2812
+ let result = await dispatchTool(toolCall.toolName, toolCall.args, {
2813
+ chatId,
2814
+ history,
2815
+ onChunk: (chunk2) => settings.onExecChunk ? settings.onExecChunk(chunk2) : null,
2816
+ onAskUser: settings.onAskUser
2752
2817
  });
2753
- if (isViolating) {
2754
- const denyMsg = `Access Denied. Terminal is prohibited from accessing system drives (C://) or external directories while "External Workspace Access" is disabled.`;
2755
- toolResults.push({ role: "user", text: `[TOOL_RESULT]: ERROR: ${denyMsg}` });
2756
- yield { type: "tool_result", content: `[TOOL_RESULT]: ERROR: ${denyMsg}` };
2757
- toolCallPointer++;
2758
- continue;
2818
+ const toolEnd = Date.now();
2819
+ yield { type: "tool_time", content: toolEnd - effectiveStart };
2820
+ lastToolEventTime = toolEnd;
2821
+ let binaryPart = null;
2822
+ if (typeof result === "object" && result.binaryPart) {
2823
+ binaryPart = result.binaryPart;
2824
+ result = result.text;
2759
2825
  }
2760
- }
2761
- if (settings.onExecStart) settings.onExecStart(command || "Unknown");
2762
- yield { type: "exec_start" };
2763
- }
2764
- const parsedArgs = parseArgs(toolCall.args);
2765
- const targetPath = parsedArgs.path || parsedArgs.targetPath || null;
2766
- if (targetPath) {
2767
- const isExternalOff = settings.systemSettings && settings.systemSettings.allowExternalAccess === false;
2768
- const absoluteTarget = path16.resolve(targetPath);
2769
- const absoluteCwd = path16.resolve(process.cwd());
2770
- if (isExternalOff && !absoluteTarget.startsWith(absoluteCwd)) {
2771
- const denyMsg = `Access Denied. You are not allowed to access files outside the current workspace. To enable this, ask the user to turn on "External Workspace Access" in /settings.`;
2772
- toolResults.push({ role: "user", text: `[TOOL_RESULT]: ERROR: ${denyMsg}` });
2773
- yield { type: "tool_result", content: `[TOOL_RESULT]: ERROR: ${denyMsg}` };
2774
- toolCallPointer++;
2775
- continue;
2776
- }
2777
- }
2778
- if (settings.onToolApproval) {
2779
- let shouldPrompt = toolCall.toolName === "write_file" || toolCall.toolName === "update_file" || toolCall.toolName === "exec_command";
2780
- if (shouldPrompt) {
2781
- const approval = await settings.onToolApproval(toolCall.toolName, toolCall.args);
2782
- if (approval === "deny") {
2783
- if (toolCall.toolName === "exec_command" && settings.onExecEnd) settings.onExecEnd();
2784
- const denyMsg = `Permission Denied: User rejected the ${toolCall.toolName === "exec_command" ? "terminal execution" : "file edit"}.`;
2785
- toolResults.push({ role: "user", text: `[TOOL_RESULT]: ERROR: ${denyMsg}` });
2786
- yield { type: "tool_result", content: `[TOOL_RESULT]: ERROR: ${denyMsg}` };
2787
- toolCallPointer++;
2788
- continue;
2826
+ if (toolCall.toolName === "exec_command" && settings.onExecEnd) {
2827
+ await new Promise((resolve) => setTimeout(resolve, 800));
2828
+ settings.onExecEnd();
2829
+ }
2830
+ const isSuccess = result && !result.startsWith("ERROR:");
2831
+ if (isSuccess) {
2832
+ await incrementUsage("toolSuccess");
2833
+ if (settings.onToolResult) settings.onToolResult("success");
2834
+ } else {
2835
+ await incrementUsage("toolFailure");
2836
+ if (settings.onToolResult) settings.onToolResult("failure");
2837
+ }
2838
+ const aiContent = `[TOOL_RESULT]: ${result.split(/\r?\n/).filter((line) => !line.includes("[UI_CONTEXT]")).join("\n")}`;
2839
+ toolResults.push({ role: "user", text: aiContent, binaryPart });
2840
+ let uiContent = `[TOOL_RESULT]: ${result}`;
2841
+ if (toolCall.toolName === "view_file" || toolCall.toolName === "web_scrape") {
2842
+ uiContent = `[TOOL_RESULT]: ${label} (Context Locked for UI Clarity)`;
2789
2843
  }
2844
+ yield { type: "tool_result", content: uiContent, aiContent, binaryPart, toolName: toolCall.toolName };
2845
+ if (toolCall.toolName === "memory" && result.includes("SUCCESS")) yield { type: "memory_updated" };
2846
+ toolCallPointer++;
2790
2847
  }
2791
2848
  }
2792
- const effectiveStart = lastToolEventTime || Date.now();
2793
- let result = await dispatchTool(toolCall.toolName, toolCall.args, {
2794
- chatId,
2795
- history,
2796
- onChunk: (chunk2) => settings.onExecChunk ? settings.onExecChunk(chunk2) : null,
2797
- onAskUser: settings.onAskUser
2798
- });
2799
- const toolEnd = Date.now();
2800
- yield { type: "tool_time", content: toolEnd - effectiveStart };
2801
- lastToolEventTime = toolEnd;
2802
- let binaryPart = null;
2803
- if (typeof result === "object" && result.binaryPart) {
2804
- binaryPart = result.binaryPart;
2805
- result = result.text;
2806
- }
2807
- if (toolCall.toolName === "exec_command" && settings.onExecEnd) {
2808
- await new Promise((resolve) => setTimeout(resolve, 800));
2809
- settings.onExecEnd();
2849
+ lastUsage = chunk.usageMetadata;
2850
+ if (lastUsage) {
2851
+ yield { type: "liveTokens", content: lastUsage.totalTokenCount };
2810
2852
  }
2811
- const isSuccess = result && !result.startsWith("ERROR:");
2812
- if (isSuccess) {
2813
- await incrementUsage("toolSuccess");
2814
- if (settings.onToolResult) settings.onToolResult("success");
2853
+ }
2854
+ if (TERMINATION_SIGNAL) break;
2855
+ success = true;
2856
+ await incrementUsage("agent");
2857
+ } catch (err) {
2858
+ const errMsg = err.status || err.error && err.error.message || String(err);
2859
+ const errLog = String(err);
2860
+ const date = (/* @__PURE__ */ new Date()).toLocaleString();
2861
+ const agentErrDir = path16.join(LOGS_DIR, "agent");
2862
+ if (!fs16.existsSync(agentErrDir)) fs16.mkdirSync(agentErrDir, { recursive: true });
2863
+ fs16.appendFileSync(path16.join(agentErrDir, "error.log"), `ERROR [${date}]: ${errLog}
2864
+ `);
2865
+ if (retryCount < MAX_RETRIES) {
2866
+ retryCount++;
2867
+ const waitTime = Math.floor(Math.random() * (2e3 - 800 + 1)) + 800;
2868
+ if (turnText.trim().length > 0) {
2869
+ modifiedHistory.push({ role: "agent", text: turnText });
2870
+ if (toolResults.length > 0) {
2871
+ toolResults.forEach((tr) => modifiedHistory.push(tr));
2872
+ }
2873
+ modifiedHistory.push({ role: "user", text: "[SYSTEM] Response got cut for internal error, continue from checkpoint seamlessly and DON'T repeat what you already said!" });
2874
+ accumulatedContext += turnText;
2875
+ yield { type: "status", content: `Recovering & Continuing (${retryCount}/${MAX_RETRIES + 1})...` };
2815
2876
  } else {
2816
- await incrementUsage("toolFailure");
2817
- if (settings.onToolResult) settings.onToolResult("failure");
2877
+ isInitialAttempt = true;
2878
+ yield { type: "status", content: `Retrying (${retryCount}/${MAX_RETRIES + 1})...` };
2818
2879
  }
2819
- const aiContent = `[TOOL_RESULT]: ${result.split(/\r?\n/).filter((line) => !line.includes("[UI_CONTEXT]")).join("\n")}`;
2820
- toolResults.push({ role: "user", text: aiContent, binaryPart });
2821
- let uiContent = `[TOOL_RESULT]: ${result}`;
2822
- if (toolCall.toolName === "view_file" || toolCall.toolName === "web_scrape") {
2823
- uiContent = `[TOOL_RESULT]: ${label} (Context Locked for UI Clarity)`;
2824
- }
2825
- yield { type: "tool_result", content: uiContent, aiContent, binaryPart, toolName: toolCall.toolName };
2826
- if (toolCall.toolName === "memory" && result.includes("SUCCESS")) yield { type: "memory_updated" };
2827
- toolCallPointer++;
2880
+ await new Promise((resolve) => setTimeout(resolve, waitTime));
2881
+ } else {
2882
+ throw new Error(`Model cannot be reached: ${errMsg}. (Failed ${MAX_RETRIES + 1} times)`);
2828
2883
  }
2829
2884
  }
2830
- lastUsage = chunk.usageMetadata;
2831
- if (lastUsage) {
2832
- yield { type: "liveTokens", content: lastUsage.totalTokenCount };
2833
- }
2834
2885
  }
2835
- await incrementUsage("agent");
2836
2886
  if (lastUsage) {
2837
2887
  await addToUsage("tokens", lastUsage.totalTokenCount || 0);
2838
2888
  yield { type: "usage", content: lastUsage };
@@ -2843,7 +2893,8 @@ ${boxBottom}
2843
2893
  if (thinkMatch) {
2844
2894
  textToProcess = turnText.replace(/<think>[\s\S]*?<\/think>/i, "");
2845
2895
  }
2846
- const hasFinish = /\[\s*(turn\s*:)?\s*finish\s*\]/i.test(turnText.toLowerCase());
2896
+ const finalActionableText = turnText.replace(/<think>[\s\S]*?(?:<\/think>|$)/gi, "");
2897
+ const hasFinish = /\[\s*(turn\s*:)?\s*finish\s*\]/i.test(finalActionableText.toLowerCase());
2847
2898
  const shouldContinue = toolCallPointer > 0;
2848
2899
  yield { type: "status", content: "Working..." };
2849
2900
  const cleanedTurnText = turnText.replace(/\[\s*(turn\s*:)?\s*(continue|finish)\s*\]/gi, "").trim();
@@ -2956,7 +3007,8 @@ ${timestamp}`;
2956
3007
  if (toolResults.length > 0) {
2957
3008
  toolResults.forEach((tr) => modifiedHistory.push(tr));
2958
3009
  } else {
2959
- modifiedHistory.push({ role: "user", text: "[SYSTEM]: LOOP DETECTED by Internal System. If you have finished your task use [turn: finish] else continue." });
3010
+ modifiedHistory.push({ role: "user", text: `[SYSTEM]: ${isThinkingLoop ? "OVER-THINKING " : ""}LOOP DETECTED by Internal System. ${isThinkingLoop ? "If you have planned the task, prioritize the execution/output. " : "If you have finished your task use [turn: finish] else continue."}` });
3011
+ isThinkingLoop = false;
2960
3012
  }
2961
3013
  }
2962
3014
  yield { type: "status", content: null };
@@ -4869,8 +4921,8 @@ var init_app = __esm({
4869
4921
  init_text();
4870
4922
  SESSION_START_TIME = Date.now();
4871
4923
  CHANGELOG_URL = "https://fluxflow-cli.onrender.com/changelog.html";
4872
- versionFluxflow = "1.8.9";
4873
- updatedOn = "2026-05-08";
4924
+ versionFluxflow = "1.8.10";
4925
+ updatedOn = "2026-05-09";
4874
4926
  ResolutionModal = ({ data, onResolve, onEdit }) => /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 2, paddingY: 1, width: "100%" }, /* @__PURE__ */ React10.createElement(Text10, { color: "magenta", bold: true, underline: true }, "\u{1F7E3} STEERING HINT RESOLUTION"), /* @__PURE__ */ React10.createElement(Text10, { marginTop: 1 }, "The agent already finished the task before your hint was consumed."), /* @__PURE__ */ React10.createElement(Box10, { marginTop: 1, backgroundColor: "#222", paddingX: 1, width: "100%" }, /* @__PURE__ */ React10.createElement(Text10, { italic: true, color: "gray" }, '"', data, '"')), /* @__PURE__ */ React10.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text10, { color: "cyan" }, "How would you like to proceed?")), /* @__PURE__ */ React10.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(
4875
4927
  CommandMenu,
4876
4928
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxflow-cli",
3
- "version": "1.8.9",
3
+ "version": "1.8.10",
4
4
  "description": "A high-fidelity agentic terminal assistant for the Flux Era.",
5
5
  "keywords": [
6
6
  "ai",