fluxflow-cli 1.7.19 β 1.7.21
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/README.md +60 -60
- package/dist/fluxflow.js +76 -70
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,14 +1,38 @@
|
|
|
1
|
-
#
|
|
2
|
-

|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
**A Beautiful, Autonomous Terminal AI Agent**
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Flux Flow is an advanced, fully autonomous AI agent that lives directly in your terminal. Built with Node.js and [Ink](https://github.com/vadimdemedes/ink) (React for interactive command-line apps), it provides a highly responsive, component-based UI powered by a sophisticated dual-model AI architecture.
|
|
7
|
+
|
|
8
|
+
Whether you need a conversational partner or an autonomous developer that can write code, run shell commands, and read your project files, Flux Flow adapts to your needs.
|
|
7
9
|
|
|
8
10
|
---
|
|
9
11
|
|
|
10
|
-
##
|
|
11
|
-
|
|
12
|
+
## β¨ Features
|
|
13
|
+
|
|
14
|
+
- **Native Multimodality**: Flux Flow can now see! Analyze images (JPG, PNG) and PDF documents natively through the `view_file` tool with high-fidelity context retention.
|
|
15
|
+
- **Document Engineering Suite**: Generate professional, branded PDF, DOCX, and PPTX documents on the fly. Features native HTML-to-Office translation for selectable text, high-performance rendering, and automatic watermarking.
|
|
16
|
+
- **External Data Sanctuary**: Redirect your logs, history, and memories to any external path for maximum portability and privacy.
|
|
17
|
+
- **Responsive Terminal UI**: A gorgeous, reactive interface built with React and Ink, featuring multi-line input, status bars, modals, and diff views.
|
|
18
|
+
- **Dual-Model Architecture**: A primary agent interacts with you and executes tasks, while a silent background "Janitor" model handles chat summarization and long-term memory extraction without blocking the main UI.
|
|
19
|
+
- **Two Operating Modes**:
|
|
20
|
+
- **Flux (Dev Mode)**: Full system access. The agent can read/write files, execute shell commands, and run autonomous agentic loops (up to 45 iterations) to solve complex coding tasks.
|
|
21
|
+
- **Flow (Chat Mode)**: Focused on conversation and web research, with limited agentic loops for faster response times.
|
|
22
|
+
- **Advanced Memory System**: Features both temporary session context and persistent, cross-session user memories encrypted locally on your machine.
|
|
23
|
+
- **Agentic Tooling**: Built-in tools for smart file patching, web scraping, web searching, terminal execution and high-fidelity Office document generation (PDF/DOCX/PPTX).
|
|
24
|
+
- **Autonomous Project Alignment**: Automatically detects and adheres to project-specific instructions in `Agent.md`, `Skills.md`, and `Fluxflow.md` for high-fidelity coding standards and complex workflows.
|
|
25
|
+
- **Customizable "Thinking" Levels**: Adjust the depth of the model's reasoning process (from Minimal to Max).
|
|
26
|
+
- **High-Reliability Fallback**: Automatic failover to a lighter, high-concurrency model (Gemini 3.1 Flash Lite) during peak traffic to ensure 100% session persistence.
|
|
27
|
+
|
|
28
|
+
## π Quick Start
|
|
29
|
+
|
|
30
|
+
### Prerequisites
|
|
31
|
+
- [Node.js](https://nodejs.org/) (v18 or higher recommended)
|
|
32
|
+
- `npm`, `yarn`, or `pnpm`
|
|
33
|
+
|
|
34
|
+
### Via NPM (Global & Instant)
|
|
35
|
+
You can run the agent instantly or install it globally for high-speed access:
|
|
12
36
|
|
|
13
37
|
```bash
|
|
14
38
|
# Run instantly (Zero Setup)
|
|
@@ -16,69 +40,45 @@ npx fluxflow-cli
|
|
|
16
40
|
|
|
17
41
|
# OR Install Globally
|
|
18
42
|
npm install -g fluxflow-cli
|
|
19
|
-
fluxflow
|
|
43
|
+
fluxflow
|
|
20
44
|
```
|
|
21
45
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Experience a terminal UI that feels alive. Built with **Ink** and **React**, Flux Flow features:
|
|
30
|
-
- **Dynamic Status Bar**: Real-time telemetry showing your "Neural Headroom" (token usage), Thinking Level, and Session ID.
|
|
46
|
+
### From Source (Local Development)
|
|
47
|
+
1. Clone the repository and install dependencies:
|
|
48
|
+
```bash
|
|
49
|
+
git clone <repository-url>
|
|
50
|
+
cd Flux-Flow
|
|
51
|
+
npm install
|
|
52
|
+
```
|
|
31
53
|
|
|
32
|
-
|
|
33
|
-
|
|
54
|
+
2. Start the agent:
|
|
55
|
+
```bash
|
|
56
|
+
npm start
|
|
57
|
+
```
|
|
34
58
|
|
|
35
|
-
|
|
36
|
-
Need a report or a presentation? Just ask. Flux Flow features a high-fidelity "Printing Press" that generates professional, branded documents natively:
|
|
37
|
-
- **PDF**: Branded documents from HTML/CSS with automatic watermarking.
|
|
38
|
-
- **DOCX**: Native Word documents with multi-page support and automatic numbering.
|
|
39
|
-
- **PPTX**: High-fidelity PowerPoint presentations using native elements (selectable text, shapes) translated directly from HTML.
|
|
59
|
+
## π Documentation
|
|
40
60
|
|
|
41
|
-
|
|
42
|
-
Zero setup means zero setup. On first run, Flux Flow performs an integrity check and autonomously installs its own Chromium engine if needed, ensuring features like PDF generation work 100% of the time without manual intervention.
|
|
61
|
+
To keep this README concise, detailed information about specific components of Flux Flow has been split into separate documents:
|
|
43
62
|
|
|
44
|
-
- **
|
|
45
|
-
- **
|
|
63
|
+
- **[Architecture & Design](./ARCHITECTURE.md)**: Deep dive into the React/Ink rendering, the Agentic Loop, and the Janitor background process.
|
|
64
|
+
- **[Agent Tools & Capabilities](./TOOLS.md)**: A comprehensive list of the tools available to the agent (e.g., File I/O, Execution, Web tools).
|
|
65
|
+
- **[UI & Interaction Features](./UI_FEATURES.md)**: Details on commands, thinking levels, and human-in-the-loop verification.
|
|
46
66
|
|
|
47
|
-
|
|
48
|
-
- **Flux Mode (Dev)**: High-speed, agentic problem solving with a 50-turn persistent loop for massive coding tasks.
|
|
49
|
-
- **Flow Mode (Chat)**: Optimized for deep research, high-quality conversation, and web-assisted reasoning.
|
|
67
|
+
## π Security & Privacy
|
|
50
68
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
- **
|
|
54
|
-
- **
|
|
55
|
-
- **
|
|
56
|
-
- **Adaptive Failover**: Automatic multi-stage retry logic with high-concurrency fallback model switching (Gemini 3.1 Flash Lite) during peak API congestion.
|
|
69
|
+
Flux Flow runs entirely locally on your machine.
|
|
70
|
+
- **Global Storage**: All history, memories, and API keys are stored securely in your home directory at `~/.fluxflow`. Sensitive data is encrypted.
|
|
71
|
+
- **Nuclear Reset**: Use the `/reset` command to instantly purge all logs, secrets, and settings from the global storage directory.
|
|
72
|
+
- **Configurable Boundaries**: In Flux mode, file access can be strictly confined to the Current Working Directory, or expanded globally via settings.
|
|
73
|
+
- **API Keys**: You supply your own Gemini/Google AI Studio API keys.
|
|
57
74
|
|
|
58
|
-
|
|
59
|
-
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.
|
|
60
|
-
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
## π οΈ Key Capabilities
|
|
64
|
-
- **Deep File-System Interaction**: Edit, move, and refactor code across multiple files with atomic precision.
|
|
65
|
-
- **Real-Time Web Intelligence**: Autonomous web-searching via DuckDuckGo for live news and technical research.
|
|
66
|
-
- **Autonomous Project Alignment**: Automatically detects and adheres to project-specific instructions in `Agent.md`, `Skills.md`, and `Fluxflow.md` for high-fidelity alignment with your coding standards and custom workflows.
|
|
67
|
-
- **High-Reliability Fallback**: Automatic failover to a lighter, high-concurrency model during peak traffic to ensure zero session loss.
|
|
68
|
-
- **Persistent Memory**: The agent learns from your preferences and project requirements across sessions.
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## βοΈ Configuration
|
|
73
|
-
Type `/settings` in-app to live-configure:
|
|
74
|
-
- **Thinking Level**: Low, Medium, or High (Deep-Reasoning).
|
|
75
|
-
- **Auto-Execution**: For ultimate high-speed flow (Advanced users only).
|
|
76
|
-
- **Security Perimeter**: Toggle External Workspace access.
|
|
77
|
-
|
|
78
|
-
---
|
|
75
|
+
## π οΈ Built With
|
|
79
76
|
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
- **[React](https://react.dev/) & [Ink](https://github.com/vadimdemedes/ink)**: For the interactive CLI rendering.
|
|
78
|
+
- **[@google/genai](https://www.npmjs.com/package/@google/genai)**: The core AI SDK powering the agent's intelligence.
|
|
79
|
+
- **[chalk](https://www.npmjs.com/package/chalk) & [gradient-string](https://www.npmjs.com/package/gradient-string)**: For terminal styling and aesthetics.
|
|
80
|
+
- **[fs-extra](https://www.npmjs.com/package/fs-extra)**: For robust file system operations.
|
|
81
|
+
- **[pptxgenjs](https://www.npmjs.com/package/pptxgenjs) & [html-to-docx](https://www.npmjs.com/package/html-to-docx)**: For native Office document generation.
|
|
82
82
|
|
|
83
83
|
---
|
|
84
|
-
*
|
|
84
|
+
*Created as a demonstration of highly capable AI tooling.*
|
package/dist/fluxflow.js
CHANGED
|
@@ -75,47 +75,11 @@ var init_TerminalBox = __esm({
|
|
|
75
75
|
// src/components/ChatLayout.jsx
|
|
76
76
|
import React2, { useState, useEffect, useRef } from "react";
|
|
77
77
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
78
|
-
var
|
|
78
|
+
var cleanSignals, formatThinkText, parseMathSymbols, InlineMarkdown, TableRenderer, MarkdownText, DiffLine, DiffBlock, CodeRenderer, MessageItem, ChatLayout, ChatLayout_default;
|
|
79
79
|
var init_ChatLayout = __esm({
|
|
80
80
|
"src/components/ChatLayout.jsx"() {
|
|
81
81
|
init_TerminalBox();
|
|
82
82
|
init_text();
|
|
83
|
-
TypewriterText = ({ text, isStreaming, onComplete, columns = 80, color = "white", speed = 50, render }) => {
|
|
84
|
-
const [displayedText, setDisplayedText] = useState("");
|
|
85
|
-
const fullTextRef = useRef(text);
|
|
86
|
-
const displayedTextRef = useRef("");
|
|
87
|
-
useEffect(() => {
|
|
88
|
-
fullTextRef.current = text;
|
|
89
|
-
}, [text]);
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
const timer = setInterval(() => {
|
|
92
|
-
const currentFull = fullTextRef.current;
|
|
93
|
-
const currentDisp = displayedTextRef.current;
|
|
94
|
-
if (currentDisp.length < currentFull.length) {
|
|
95
|
-
const remaining = currentFull.slice(currentDisp.length);
|
|
96
|
-
const match = remaining.match(/^(\S+\s*|\s+)/);
|
|
97
|
-
if (match) {
|
|
98
|
-
const chunk = match[0];
|
|
99
|
-
const gap = currentFull.length - currentDisp.length;
|
|
100
|
-
let revealedChunk = chunk;
|
|
101
|
-
if (gap > 100) {
|
|
102
|
-
const extraMatch = remaining.slice(chunk.length).match(/^(\S+\s*|\s+){0,2}/);
|
|
103
|
-
if (extraMatch) revealedChunk += extraMatch[0];
|
|
104
|
-
}
|
|
105
|
-
const nextText = currentDisp + revealedChunk;
|
|
106
|
-
setDisplayedText(nextText);
|
|
107
|
-
displayedTextRef.current = nextText;
|
|
108
|
-
}
|
|
109
|
-
} else if (!isStreaming) {
|
|
110
|
-
if (onComplete) onComplete();
|
|
111
|
-
clearInterval(timer);
|
|
112
|
-
}
|
|
113
|
-
}, speed);
|
|
114
|
-
return () => clearInterval(timer);
|
|
115
|
-
}, [isStreaming, speed]);
|
|
116
|
-
if (render) return render(displayedText);
|
|
117
|
-
return /* @__PURE__ */ React2.createElement(CodeRenderer, { text: displayedText, columns });
|
|
118
|
-
};
|
|
119
83
|
cleanSignals = (text) => {
|
|
120
84
|
if (!text) return text;
|
|
121
85
|
let result = text;
|
|
@@ -455,24 +419,7 @@ var init_ChatLayout = __esm({
|
|
|
455
419
|
finalContent.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\\\n/g, "\n").replace(/\\$/, ""),
|
|
456
420
|
columns - 6
|
|
457
421
|
).split("\n").map((line, lineIdx) => /* @__PURE__ */ React2.createElement(Box2, { key: lineIdx, flexDirection: "row", width: "100%" }, /* @__PURE__ */ React2.createElement(Box2, { flexShrink: 0, width: 2 }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, lineIdx === 0 ? "\u276F" : " ")), /* @__PURE__ */ React2.createElement(Box2, { flexGrow: 1, marginLeft: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: msg.color || "white", wrap: "anywhere" }, line))))
|
|
458
|
-
) : msg.role === "think" ? /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginTop: 1, paddingX: 1, width: "100%" }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, "Thinking..."), /* @__PURE__ */ React2.createElement(Box2, { borderStyle: "single", borderLeft: true, borderRight: false, borderTop: false, borderBottom: false, paddingLeft: 2, flexDirection: "column", width: "100%" },
|
|
459
|
-
TypewriterText,
|
|
460
|
-
{
|
|
461
|
-
text: finalContent,
|
|
462
|
-
isStreaming: msg.isStreaming,
|
|
463
|
-
onComplete: () => setAnimationDone(true),
|
|
464
|
-
speed: 25,
|
|
465
|
-
render: (t) => formatThinkText(t, columns)
|
|
466
|
-
}
|
|
467
|
-
) : formatThinkText(finalContent, columns))) : /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", paddingX: 1, marginTop: 1, width: "100%" }, !animationDone ? /* @__PURE__ */ React2.createElement(
|
|
468
|
-
TypewriterText,
|
|
469
|
-
{
|
|
470
|
-
text: finalContent,
|
|
471
|
-
isStreaming: msg.isStreaming,
|
|
472
|
-
onComplete: () => setAnimationDone(true),
|
|
473
|
-
columns
|
|
474
|
-
}
|
|
475
|
-
) : /* @__PURE__ */ React2.createElement(CodeRenderer, { text: finalContent, columns }), msg.memoryUpdated && /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1, width: "100%" }, /* @__PURE__ */ React2.createElement(Text2, { color: "yellow", italic: true }, "\u2728 [Memory Updated]"))));
|
|
422
|
+
) : msg.role === "think" ? /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginTop: 1, paddingX: 1, width: "100%" }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, "Thinking..."), /* @__PURE__ */ React2.createElement(Box2, { borderStyle: "single", borderLeft: true, borderRight: false, borderTop: false, borderBottom: false, paddingLeft: 2, flexDirection: "column", width: "100%" }, formatThinkText(finalContent, columns))) : /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", paddingX: 1, marginTop: 1, width: "100%" }, /* @__PURE__ */ React2.createElement(CodeRenderer, { text: finalContent, columns }), msg.memoryUpdated && /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1, width: "100%" }, /* @__PURE__ */ React2.createElement(Text2, { color: "yellow", italic: true }, "\u2728 [Memory Updated]"))));
|
|
476
423
|
});
|
|
477
424
|
ChatLayout = React2.memo(({ messages, showFullThinking, columns = 80 }) => {
|
|
478
425
|
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", width: "100%" }, messages.map((msg, idx) => /* @__PURE__ */ React2.createElement(
|
|
@@ -769,7 +716,7 @@ ${mode === "Flux" ? `
|
|
|
769
716
|
2. List Files: tool:functions.list_files(path="relative/path"). Lists content of a directory.
|
|
770
717
|
3. Read Folder: tool:functions.read_folder(path="relative/path"). Detailed stats of a directory. Prefer this one over list_files.
|
|
771
718
|
4. Write File: tool:functions.write_file(path="path", content="content"). Creates/Overwrites. NO CODE BLOCKS. RETURNS: Disk verification + original content (if overwritten) for 100% reversibility. Escape your double quotes '"' using backslash.
|
|
772
|
-
5. Update File: tool:functions.update_file(path="relative/path", content_to_replace="old", content_to_add="new"). Surgical patching. RETURNS: High-fidelity visual diff and old code block. You MUST verify that the change specifically matches your intent using the returned diff. PREFFER UPDATE FILE OVER WRITE FILE if file already exists for better reversal tracking (if a file has 500+ lines, try to stick with update_file over full-rewrite). DONT WRAP UPDATE FILE CALL CONTENT IN MARKDOWN CODE BLOCKS.
|
|
719
|
+
5. Update File: tool:functions.update_file(path="relative/path", content_to_replace="old/fuzzy indentation matching supported", content_to_add="new"). Surgical patching. RETURNS: High-fidelity visual diff and old code block. You MUST verify that the change specifically matches your intent using the returned diff. PREFFER UPDATE FILE OVER WRITE FILE if file already exists for better reversal tracking (if a file has 500+ lines, try to stick with update_file over full-rewrite). DONT WRAP UPDATE FILE CALL CONTENT IN MARKDOWN CODE BLOCKS. KEEP YOUR PATCH INDENTATION AT BASE LEVEL WITH INTERNAL NESTED INDENTATION IN YOUR PROVIDED CODE INTACT, THE SYSTEM WILL HANDLE FILE LEVEL INDENTATION.
|
|
773
720
|
6. Write PDF: tool:functions.write_pdf(path="path", content="<html/css content>", orientation="portrait/landscape"). Generates a professional PDF document. Orientation are optional. A4 size page will be used, so any multi-page PDFs calculate your alightment and page breaks to not mess up A4 page layout. DO NOT ADD FOOTER MANUALLY, the system will handle it automatically. USE CSS TO VISUALLY BEAUTIFY THE DOCUMENT, USE full 100vh & 100vw for page area. ENSURE THE CONTENT IS NEVER BROKEN IN BETWEEN PAGES, USE PAGE BREAKS PROACTIVELY FOR A A4 PAGE LAYOUT. Keep generous margins for better redability.
|
|
774
721
|
7. Write DOCX: tool:functions.write_docx(path="path", content="<html content>"). Generates a professional Word document (.docx) from HTML. You can make multiple pages. Default Page dimentions will be A4, use proper margins and page break strategy.
|
|
775
722
|
8. Write PPTX: tool:functions.write_pptx(path="path", content="<h1 style='color: #0088CC;'>Title</h1><ul style='font-size: 14pt;'><li>Point A</li></ul>
|
|
@@ -781,7 +728,8 @@ ${mode === "Flux" ? `
|
|
|
781
728
|
|
|
782
729
|
AFTER GETTING THE TOOL RESULT, YOU MUST VERIFY THAT ITS A SUCCESS, IF IT GIVES A ERROR, TELL THE USER AND TRY TO FIX IF YOU CAN. DO NOT HALLUCINATE SUCCESS IF TOOL RETURNS ERROR.
|
|
783
730
|
NEVER GUESS A CODE, IF UNSURE READ THE FILE FIRST BEFORE EDITING IT.
|
|
784
|
-
**CRITICAL POLICY: WHEN WRITING/UPDATING FILES, ALWAYS USE ACTUAL NEW LINE CONTROL CHARACTER (LF) FOR LINE BREAKS
|
|
731
|
+
**CRITICAL POLICY: WHEN WRITING/UPDATING FILES, ALWAYS USE ACTUAL NEW LINE CONTROL CHARACTER (LF) FOR LINE BREAKS. WHEN YOU WANT TO WRITE '
|
|
732
|
+
' IN THE FILE PROPERLY ESPACE IT WITH DOUBLE BACKSLASHES.**`.trim() : `
|
|
785
733
|
- 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()}
|
|
786
734
|
-----------------
|
|
787
735
|
|
|
@@ -912,6 +860,7 @@ Every ${isMemoryEnabled ? "Prompt, Responses & Memories" : "Prompt & Responses"}
|
|
|
912
860
|
- **CRITICAL**: NEVER USE LaTeX IN RESPONSES.
|
|
913
861
|
- Keep Poems & Literature in Code Block.
|
|
914
862
|
- Use emojis & Kaomojis. Prefer Kaomojis more.
|
|
863
|
+
- Keep your in-chat responses shorter and concise.
|
|
915
864
|
-- END FORMATTING RULES --
|
|
916
865
|
|
|
917
866
|
-- START REPONSE FINISH PROTOCOL --
|
|
@@ -947,6 +896,7 @@ YOU ARE A SILENT BACKGROUND SYSTEM PROCESS. YOU HAVE NO MOUTH. YOUR ONLY OUTPUT
|
|
|
947
896
|
3. NON-TOOL TEXT WILL BREAK THE SYSTEM.
|
|
948
897
|
4. DO NOT REPEAT AGENT RAWS AND TOOL RESULTS IN YOUR RESPONSE.
|
|
949
898
|
5. IF YOU GET ONLY USER QUERY AND NO AGENT RAWS, THEN JUST USE TEMP MEMORY TO LOG THE SUMMARY OF USER QUERY.
|
|
899
|
+
6. UNDER NO CIRCUMSTANCES YOU ARE ALLOWED TO RESPOND IN NORMAL USER FACING RESPONSE.
|
|
950
900
|
|
|
951
901
|
YOUR JOB: Analyze the 'User prompt' and 'Agent Raws' to extract facts for long-term memory or handle system tasks.
|
|
952
902
|
${isMemoryEnabled ? `If user tell something that is important (like, hobbies, preferences, facts about user, hates, likes, etc) to know user better over time, use long term memory tools.` : ""}
|
|
@@ -1859,22 +1809,77 @@ var init_update_file = __esm({
|
|
|
1859
1809
|
diskContent = normalizedDisk;
|
|
1860
1810
|
}
|
|
1861
1811
|
const currentContent = diskContent;
|
|
1862
|
-
|
|
1812
|
+
const adjustIndentation = (newText, originalMatch, leadingContext = "") => {
|
|
1813
|
+
if (!newText || originalMatch === void 0) return newText;
|
|
1814
|
+
const getMinIndent = (text) => {
|
|
1815
|
+
const lines = text.split("\n").filter((l) => l.trim() !== "");
|
|
1816
|
+
if (lines.length === 0) return "";
|
|
1817
|
+
let min = lines[0].match(/^\s*/)[0];
|
|
1818
|
+
for (const line of lines) {
|
|
1819
|
+
const indent = line.match(/^\s*/)[0];
|
|
1820
|
+
if (indent.length < min.length) min = indent;
|
|
1821
|
+
}
|
|
1822
|
+
return min;
|
|
1823
|
+
};
|
|
1824
|
+
const originalMinIndent = getMinIndent(leadingContext + originalMatch);
|
|
1825
|
+
const newMinIndent = getMinIndent(newText);
|
|
1826
|
+
const newLines = newText.split("\n");
|
|
1827
|
+
return newLines.map((line, i) => {
|
|
1828
|
+
if (line.trim() === "" && i !== 0) return "";
|
|
1829
|
+
const currentOriginalIndent = i === 0 ? originalMinIndent.substring(Math.min(originalMinIndent.length, leadingContext.length)) : originalMinIndent;
|
|
1830
|
+
const lineIndent = line.match(/^\s*/)[0];
|
|
1831
|
+
if (lineIndent.startsWith(newMinIndent)) {
|
|
1832
|
+
return currentOriginalIndent + line.substring(newMinIndent.length);
|
|
1833
|
+
}
|
|
1834
|
+
if (newMinIndent.startsWith(lineIndent)) {
|
|
1835
|
+
const diff = newMinIndent.length - lineIndent.length;
|
|
1836
|
+
const adjustedIndent = currentOriginalIndent.substring(0, Math.max(0, currentOriginalIndent.length - diff));
|
|
1837
|
+
return adjustedIndent + line.trimStart();
|
|
1838
|
+
}
|
|
1839
|
+
return currentOriginalIndent + line.trimStart();
|
|
1840
|
+
}).join("\n");
|
|
1841
|
+
};
|
|
1842
|
+
let instances = 0;
|
|
1843
|
+
let startPos = -1;
|
|
1844
|
+
let matchRegex = null;
|
|
1845
|
+
const escaped = content_to_replace.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1846
|
+
if (content_to_replace !== "" && currentContent.includes(content_to_replace)) {
|
|
1847
|
+
matchRegex = new RegExp(escaped, "g");
|
|
1848
|
+
} else {
|
|
1849
|
+
const fuzzyPattern = escaped.trim().replace(/\s+/g, "\\s*");
|
|
1850
|
+
try {
|
|
1851
|
+
matchRegex = new RegExp(fuzzyPattern, "g");
|
|
1852
|
+
} catch (e) {
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
const matches = matchRegex ? [...currentContent.matchAll(matchRegex)] : [];
|
|
1856
|
+
instances = matches.length;
|
|
1857
|
+
if (instances === 0) {
|
|
1863
1858
|
const diskLen = currentContent.length;
|
|
1864
1859
|
const matchLen = content_to_replace.length;
|
|
1865
|
-
return `ERROR: Could not find
|
|
1860
|
+
return `ERROR: Could not find match (even fuzzy) for the specified "content_to_replace" in [${targetPath}].
|
|
1866
1861
|
- Disk Content Length (Normalized): ${diskLen}
|
|
1867
1862
|
- Match String Length (Normalized): ${matchLen}
|
|
1868
1863
|
- Check indentation/whitespace. Try re-reading the file for latest changes.`;
|
|
1869
1864
|
}
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1865
|
+
if (instances > 1) {
|
|
1866
|
+
return `ERROR: Unable to find unique match. [${instances}] instances of the specified "content_to_replace" were found in [${targetPath}]. Try providing more context (surrounding lines) to make the match unique.`;
|
|
1867
|
+
}
|
|
1868
|
+
startPos = matches[0].index;
|
|
1869
|
+
const firstMatchContent = matches[0][0];
|
|
1870
|
+
const newFileContent = currentContent.replace(matchRegex, (match, offset) => {
|
|
1871
|
+
const lineStart = currentContent.lastIndexOf("\n", offset) + 1;
|
|
1872
|
+
const leadingContext = currentContent.substring(lineStart, offset);
|
|
1873
|
+
return adjustIndentation(content_to_add, match, leadingContext);
|
|
1874
|
+
});
|
|
1875
|
+
const firstLineStart = currentContent.lastIndexOf("\n", startPos) + 1;
|
|
1876
|
+
const firstLeadingContext = currentContent.substring(firstLineStart, startPos);
|
|
1877
|
+
const finalContentToAdd = adjustIndentation(content_to_add, firstMatchContent, firstLeadingContext);
|
|
1878
|
+
const finalContentToReplace = firstMatchContent;
|
|
1874
1879
|
fs11.writeFileSync(absolutePath, newFileContent, "utf8");
|
|
1875
1880
|
const allOriginalLines = currentContent.split(/\r?\n/);
|
|
1881
|
+
const startLine = currentContent.substring(0, startPos).split(/\r?\n/).length;
|
|
1876
1882
|
const oldLines = content_to_replace.split(/\r?\n/);
|
|
1877
|
-
const newLines = content_to_add.split(/\r?\n/);
|
|
1878
1883
|
const endLine = startLine + oldLines.length - 1;
|
|
1879
1884
|
let diffText = `SUCCESS: File [${targetPath}] updated. [${instances}] instances replaced.
|
|
1880
1885
|
|
|
@@ -2603,7 +2608,7 @@ USER_PROMPT: ${agentText}`.trim();
|
|
|
2603
2608
|
const headingSections = thinkContent.split(/^\s*\*\*.*?\*\*\s*$/gm);
|
|
2604
2609
|
const isOverVerbose = headingSections.some((section) => {
|
|
2605
2610
|
const wordCount = section.trim().split(/\s+/).filter((w) => w.length > 0).length;
|
|
2606
|
-
return wordCount >
|
|
2611
|
+
return wordCount > 500;
|
|
2607
2612
|
});
|
|
2608
2613
|
if (headingsCount > 25 || isOverVerbose) {
|
|
2609
2614
|
const reason = headingsCount > 25 ? "Loop Detected" : "Noise Detected";
|
|
@@ -2811,6 +2816,7 @@ ${boxBottom}
|
|
|
2811
2816
|
model: janitorModel || "gemma-4-26b-a4b-it",
|
|
2812
2817
|
contents: janitorContents,
|
|
2813
2818
|
config: {
|
|
2819
|
+
maxOutputTokens: 512,
|
|
2814
2820
|
thinkingConfig: {
|
|
2815
2821
|
includeThoughts: false,
|
|
2816
2822
|
thinkingLevel: ThinkingLevel.MINIMAL
|
|
@@ -2873,7 +2879,7 @@ ${timestamp}`;
|
|
|
2873
2879
|
if (toolResults.length > 0) {
|
|
2874
2880
|
toolResults.forEach((tr) => modifiedHistory.push(tr));
|
|
2875
2881
|
} else {
|
|
2876
|
-
modifiedHistory.push({ role: "user", text: "[SYSTEM]: LOOP DETECTED by Internal System. If you have finished your task use [turn: finish] else continue." });
|
|
2882
|
+
modifiedHistory.push({ role: "user", text: "[SYSTEM]: LOOP DETECTED by Internal System. Your thinking process seems to be repeating and excreeding allocated budget for single turn. If you have finished your task use [turn: finish] else continue." });
|
|
2877
2883
|
}
|
|
2878
2884
|
}
|
|
2879
2885
|
yield { type: "status", content: null };
|
|
@@ -3297,7 +3303,7 @@ Check what's new using \`/changelog\` command.`,
|
|
|
3297
3303
|
const queuedPromptRef = useRef2(null);
|
|
3298
3304
|
const [completedIndex, setCompletedIndex] = useState7(1);
|
|
3299
3305
|
const windowedHistory = useMemo(() => {
|
|
3300
|
-
const MAX_LINES =
|
|
3306
|
+
const MAX_LINES = 2e3;
|
|
3301
3307
|
const width = stdout?.columns || 80;
|
|
3302
3308
|
let totalLines = 0;
|
|
3303
3309
|
let startIdx = 0;
|
|
@@ -4687,7 +4693,7 @@ Selection: ${val}`,
|
|
|
4687
4693
|
)))))));
|
|
4688
4694
|
}
|
|
4689
4695
|
};
|
|
4690
|
-
return /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "column", width: "100%" },
|
|
4696
|
+
return /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "column", width: "100%" }, /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "column", width: "100%", flexGrow: 1 }, windowedHistory.items.map((msg, idx) => /* @__PURE__ */ React10.createElement(MessageItem, { key: msg.id || idx, msg, showFullThinking, columns: stdout?.columns || 80 }))), /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "column", padding: 1, width: "100%" }, (activeView === "chat" || ["ask", "approval", "terminalApproval"].includes(activeView)) && /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "column", width: "100%" }, /* @__PURE__ */ React10.createElement(
|
|
4691
4697
|
ChatLayout_default,
|
|
4692
4698
|
{
|
|
4693
4699
|
messages: messages.slice(completedIndex),
|
|
@@ -4773,8 +4779,8 @@ var init_app = __esm({
|
|
|
4773
4779
|
init_text();
|
|
4774
4780
|
SESSION_START_TIME = Date.now();
|
|
4775
4781
|
CHANGELOG_URL = "https://fluxflow-cli.onrender.com/changelog.html";
|
|
4776
|
-
versionFluxflow = "1.7.
|
|
4777
|
-
updatedOn = "2026-05-
|
|
4782
|
+
versionFluxflow = "1.7.21";
|
|
4783
|
+
updatedOn = "2026-05-07";
|
|
4778
4784
|
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(
|
|
4779
4785
|
CommandMenu,
|
|
4780
4786
|
{
|