fluxflow-cli 1.1.1 → 1.1.3
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 +47 -47
- package/LICENSE +21 -21
- package/TOOLS.md +47 -47
- package/UI_FEATURES.md +66 -66
- package/dist/fluxflow.js +127 -96
- package/package.json +1 -1
package/ARCHITECTURE.md
CHANGED
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
# 🏛️ Architecture & Design
|
|
2
|
-
|
|
3
|
-
Flux Flow is built on a modern, reactive stack that brings web-like development paradigms to the terminal. It utilizes a custom agentic loop for reasoning and a unique dual-model system for background processing.
|
|
4
|
-
|
|
5
|
-
## UI Layer: React & Ink
|
|
6
|
-
|
|
7
|
-
The entire terminal interface is built using **React** via the [Ink](https://github.com/vadimdemedes/ink) renderer.
|
|
8
|
-
- **Component-Based**: The UI is composed of isolated, reusable React components (`ChatLayout`, `StatusBar`, `CommandMenu`, `TerminalBox`, `ProfileForm`).
|
|
9
|
-
- **Reactive State**: The application uses React hooks (`useState`, `useEffect`) to manage user input, application mode, model selection, and the terminal's resizing events.
|
|
10
|
-
- **Zero-Render Overheads**: Critical performance trackers, like the session start time, are kept outside the React render cycle to maintain terminal responsiveness during high-speed AI text streaming.
|
|
11
|
-
|
|
12
|
-
## The Agentic Loop
|
|
13
|
-
|
|
14
|
-
The core intelligence of Flux Flow resides in `src/utils/ai.js`. It does not rely on opaque third-party agent frameworks; instead, it uses a custom, highly transparent string-based protocol powered by an asynchronous generator (`async function*`). This approach allows for real-time UI updates while managing complex multi-step reasoning.
|
|
15
|
-
|
|
16
|
-
The execution flow of a single user prompt follows this loop:
|
|
17
|
-
|
|
18
|
-
1. **Context Assembly**: The user's prompt is combined with the system instructions, temporary session context, persistent user memories, and the current chat history. If the history gets too large (e.g., >128k tokens) and compression is disabled, it is gracefully truncated.
|
|
19
|
-
2. **Stream Processing**: The main loop initiates a streaming request to the Gemini API (`client.models.generateContentStream`). It yields chunks of text and status updates directly back to the React UI as they arrive.
|
|
20
|
-
3. **Detection & Tool Execution**: Once the stream completes for a given turn, the entire response is scanned for tool calls using a custom regex and bracket-balancing parser (looking for `tool:functions.tool_name(args...)`).
|
|
21
|
-
- If tools are found, the loop pauses.
|
|
22
|
-
- Each tool is dispatched to its respective handler in `src/tools/`.
|
|
23
|
-
- Tool outputs are collected and appended to the context as `[TOOL_RESULT]: ...`.
|
|
24
|
-
4. **Security Governance**: During tool execution, the loop enforces security checks (e.g., blocking `exec_command` from accessing system root drives if "External Workspace Access" is off) and pauses for Human-in-the-Loop (HITL) approval if necessary.
|
|
25
|
-
5. **Turn Management & Continuation**: The model is instructed to append `[turn: finish]` if its goal is complete, or `[turn: continue]` if it expects tool results.
|
|
26
|
-
- If tools were called or `[turn: continue]` is present, the loop increments and re-prompts the model with the newly gathered `[TOOL_RESULT]` data.
|
|
27
|
-
- If `[turn: finish]` is detected and no further tools were called, the main loop terminates, passing the final synthesized context to the background Janitor process.
|
|
28
|
-
6. **Loop Limits & Resilience**: To prevent infinite loops or excessive API usage, **Flux mode** is capped at 50 iterations per user prompt, while **Flow mode** is capped at 5.
|
|
29
|
-
- **Multi-Stage Failover**: The loop features a sophisticated 8-attempt retry engine with random backoff (800ms - 2s).
|
|
30
|
-
- **Critical Fallback Pivot**: If the primary model fails 5 consecutive times, the agent surgically pivots to a lighter, high-concurrency fallback model (`gemini-3.1-flash-lite-preview`) for the final 3 attempts to ensure session navigation through API congestion.
|
|
31
|
-
|
|
32
|
-
## The Dual-Model System
|
|
33
|
-
|
|
34
|
-
To maintain a fast, snappy UI while still performing complex data management, Flux Flow employs two separate AI models for every interaction:
|
|
35
|
-
|
|
36
|
-
### 1. The Main Agent
|
|
37
|
-
- **Responsibility**: Direct user interaction, reasoning, and tool execution.
|
|
38
|
-
- **Behavior**: Streams text directly to the UI. It focuses entirely on solving the user's immediate problem or answering their question.
|
|
39
|
-
|
|
40
|
-
### 2. The Janitor (Background Process)
|
|
41
|
-
- **Responsibility**: System maintenance, long-term memory extraction, and chat summarization.
|
|
42
|
-
- **Behavior**: After the Main Agent finishes its loop, the entire context (User Prompt + Agent Raws) is sent to the Janitor model.
|
|
43
|
-
- **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).
|
|
44
|
-
|
|
45
|
-
## Data Persistence & Safety
|
|
46
|
-
|
|
47
|
-
- **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.
|
|
1
|
+
# 🏛️ Architecture & Design
|
|
2
|
+
|
|
3
|
+
Flux Flow is built on a modern, reactive stack that brings web-like development paradigms to the terminal. It utilizes a custom agentic loop for reasoning and a unique dual-model system for background processing.
|
|
4
|
+
|
|
5
|
+
## UI Layer: React & Ink
|
|
6
|
+
|
|
7
|
+
The entire terminal interface is built using **React** via the [Ink](https://github.com/vadimdemedes/ink) renderer.
|
|
8
|
+
- **Component-Based**: The UI is composed of isolated, reusable React components (`ChatLayout`, `StatusBar`, `CommandMenu`, `TerminalBox`, `ProfileForm`).
|
|
9
|
+
- **Reactive State**: The application uses React hooks (`useState`, `useEffect`) to manage user input, application mode, model selection, and the terminal's resizing events.
|
|
10
|
+
- **Zero-Render Overheads**: Critical performance trackers, like the session start time, are kept outside the React render cycle to maintain terminal responsiveness during high-speed AI text streaming.
|
|
11
|
+
|
|
12
|
+
## The Agentic Loop
|
|
13
|
+
|
|
14
|
+
The core intelligence of Flux Flow resides in `src/utils/ai.js`. It does not rely on opaque third-party agent frameworks; instead, it uses a custom, highly transparent string-based protocol powered by an asynchronous generator (`async function*`). This approach allows for real-time UI updates while managing complex multi-step reasoning.
|
|
15
|
+
|
|
16
|
+
The execution flow of a single user prompt follows this loop:
|
|
17
|
+
|
|
18
|
+
1. **Context Assembly**: The user's prompt is combined with the system instructions, temporary session context, persistent user memories, and the current chat history. If the history gets too large (e.g., >128k tokens) and compression is disabled, it is gracefully truncated.
|
|
19
|
+
2. **Stream Processing**: The main loop initiates a streaming request to the Gemini API (`client.models.generateContentStream`). It yields chunks of text and status updates directly back to the React UI as they arrive.
|
|
20
|
+
3. **Detection & Tool Execution**: Once the stream completes for a given turn, the entire response is scanned for tool calls using a custom regex and bracket-balancing parser (looking for `tool:functions.tool_name(args...)`).
|
|
21
|
+
- If tools are found, the loop pauses.
|
|
22
|
+
- Each tool is dispatched to its respective handler in `src/tools/`.
|
|
23
|
+
- Tool outputs are collected and appended to the context as `[TOOL_RESULT]: ...`.
|
|
24
|
+
4. **Security Governance**: During tool execution, the loop enforces security checks (e.g., blocking `exec_command` from accessing system root drives if "External Workspace Access" is off) and pauses for Human-in-the-Loop (HITL) approval if necessary.
|
|
25
|
+
5. **Turn Management & Continuation**: The model is instructed to append `[turn: finish]` if its goal is complete, or `[turn: continue]` if it expects tool results.
|
|
26
|
+
- If tools were called or `[turn: continue]` is present, the loop increments and re-prompts the model with the newly gathered `[TOOL_RESULT]` data.
|
|
27
|
+
- If `[turn: finish]` is detected and no further tools were called, the main loop terminates, passing the final synthesized context to the background Janitor process.
|
|
28
|
+
6. **Loop Limits & Resilience**: To prevent infinite loops or excessive API usage, **Flux mode** is capped at 50 iterations per user prompt, while **Flow mode** is capped at 5.
|
|
29
|
+
- **Multi-Stage Failover**: The loop features a sophisticated 8-attempt retry engine with random backoff (800ms - 2s).
|
|
30
|
+
- **Critical Fallback Pivot**: If the primary model fails 5 consecutive times, the agent surgically pivots to a lighter, high-concurrency fallback model (`gemini-3.1-flash-lite-preview`) for the final 3 attempts to ensure session navigation through API congestion.
|
|
31
|
+
|
|
32
|
+
## The Dual-Model System
|
|
33
|
+
|
|
34
|
+
To maintain a fast, snappy UI while still performing complex data management, Flux Flow employs two separate AI models for every interaction:
|
|
35
|
+
|
|
36
|
+
### 1. The Main Agent
|
|
37
|
+
- **Responsibility**: Direct user interaction, reasoning, and tool execution.
|
|
38
|
+
- **Behavior**: Streams text directly to the UI. It focuses entirely on solving the user's immediate problem or answering their question.
|
|
39
|
+
|
|
40
|
+
### 2. The Janitor (Background Process)
|
|
41
|
+
- **Responsibility**: System maintenance, long-term memory extraction, and chat summarization.
|
|
42
|
+
- **Behavior**: After the Main Agent finishes its loop, the entire context (User Prompt + Agent Raws) is sent to the Janitor model.
|
|
43
|
+
- **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).
|
|
44
|
+
|
|
45
|
+
## Data Persistence & Safety
|
|
46
|
+
|
|
47
|
+
- **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.
|
|
48
48
|
- **Encryption**: User secrets and persistent memories (`secret/memories.json`) are handled by `src/utils/crypto.js` to ensure local privacy.
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Kushal Roy Chowdhury
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kushal Roy Chowdhury
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/TOOLS.md
CHANGED
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
# 🧰 Agent Tools & Capabilities
|
|
2
|
-
|
|
3
|
-
Flux Flow provides a robust set of tools that allow the AI to interact with the file system, execute code, and search the web. The availability of these tools depends on the active operating mode.
|
|
4
|
-
|
|
5
|
-
## Tool Availability by Mode
|
|
6
|
-
|
|
7
|
-
| Tool | Flux Mode (Dev) | Flow Mode (Chat) |
|
|
8
|
-
| :--- | :---: | :---: |
|
|
9
|
-
| **Web Search** | ✅ | ✅ |
|
|
10
|
-
| **Web Scrape** | ✅ | ✅ |
|
|
11
|
-
| **View/Read Files** | ✅ | ❌ |
|
|
12
|
-
| **Write/Update Files** | ✅ | ❌ |
|
|
13
|
-
| **Execute Commands** | ✅ | ❌ |
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## Core Tools
|
|
18
|
-
|
|
19
|
-
### 🌐 Web & Research
|
|
20
|
-
- **`web_search`**: Uses DuckDuckGo to find up-to-date information on the internet. Crucial for answering questions about recent events or unlearned documentation.
|
|
21
|
-
- **`web_scrape`**: Extracts the detailed text content from a specific URL, allowing the agent to read documentation or articles.
|
|
22
|
-
|
|
23
|
-
### 📁 File System Operations
|
|
24
|
-
- **`list_files`**: Lists the contents of a directory to help the agent understand the project structure.
|
|
25
|
-
- **`read_folder`**: Provides detailed statistics and metadata about a directory's contents.
|
|
26
|
-
- **`view_file`**: Reads the content of a file. Supports reading specific line ranges (`start_line`, `end_line`) to manage context size efficiently.
|
|
27
|
-
|
|
28
|
-
### ✍️ Code Editing
|
|
29
|
-
- **`write_file`**: Creates a new file or completely overwrites an existing one with new content.
|
|
30
|
-
- **`update_file` (Smart Patching)**: Surgically replaces a specific block of text within a file.
|
|
31
|
-
- *Diff Generation*: It returns a high-fidelity visual diff (Red/Green changes with context lines) to the UI, allowing the user to see exactly what the agent modified.
|
|
32
|
-
|
|
33
|
-
### 💻 Terminal Execution
|
|
34
|
-
- **`exec_command`**: Runs a shell command directly in the terminal using Node's `child_process.spawn`.
|
|
35
|
-
- *Context Aware*: Runs in the current working directory.
|
|
36
|
-
- *Cross-Platform*: Uses `shell: true` to handle Windows `.cmd`/`.bat` files natively.
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## Memory Management
|
|
41
|
-
|
|
42
|
-
The memory tool (`memory.js`) is primarily used by the background **Janitor** model, but can be accessed by the main agent if necessary.
|
|
43
|
-
|
|
44
|
-
- **Temporary Context (`action='temp'`)**: Saves a rolling summary of the current session to maintain conversational context without bloating the main prompt history.
|
|
45
|
-
- **Persistent User Memory (`action='user'`)**:
|
|
46
|
-
- The Janitor analyzes conversations to detect user preferences, hobbies, or instructions.
|
|
47
|
-
- It uses `add`, `update`, or `delete` methods to manage facts in the encrypted `memories.json` vault.
|
|
1
|
+
# 🧰 Agent Tools & Capabilities
|
|
2
|
+
|
|
3
|
+
Flux Flow provides a robust set of tools that allow the AI to interact with the file system, execute code, and search the web. The availability of these tools depends on the active operating mode.
|
|
4
|
+
|
|
5
|
+
## Tool Availability by Mode
|
|
6
|
+
|
|
7
|
+
| Tool | Flux Mode (Dev) | Flow Mode (Chat) |
|
|
8
|
+
| :--- | :---: | :---: |
|
|
9
|
+
| **Web Search** | ✅ | ✅ |
|
|
10
|
+
| **Web Scrape** | ✅ | ✅ |
|
|
11
|
+
| **View/Read Files** | ✅ | ❌ |
|
|
12
|
+
| **Write/Update Files** | ✅ | ❌ |
|
|
13
|
+
| **Execute Commands** | ✅ | ❌ |
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Tools
|
|
18
|
+
|
|
19
|
+
### 🌐 Web & Research
|
|
20
|
+
- **`web_search`**: Uses DuckDuckGo to find up-to-date information on the internet. Crucial for answering questions about recent events or unlearned documentation.
|
|
21
|
+
- **`web_scrape`**: Extracts the detailed text content from a specific URL, allowing the agent to read documentation or articles.
|
|
22
|
+
|
|
23
|
+
### 📁 File System Operations
|
|
24
|
+
- **`list_files`**: Lists the contents of a directory to help the agent understand the project structure.
|
|
25
|
+
- **`read_folder`**: Provides detailed statistics and metadata about a directory's contents.
|
|
26
|
+
- **`view_file`**: Reads the content of a file. Supports reading specific line ranges (`start_line`, `end_line`) to manage context size efficiently.
|
|
27
|
+
|
|
28
|
+
### ✍️ Code Editing
|
|
29
|
+
- **`write_file`**: Creates a new file or completely overwrites an existing one with new content.
|
|
30
|
+
- **`update_file` (Smart Patching)**: Surgically replaces a specific block of text within a file.
|
|
31
|
+
- *Diff Generation*: It returns a high-fidelity visual diff (Red/Green changes with context lines) to the UI, allowing the user to see exactly what the agent modified.
|
|
32
|
+
|
|
33
|
+
### 💻 Terminal Execution
|
|
34
|
+
- **`exec_command`**: Runs a shell command directly in the terminal using Node's `child_process.spawn`.
|
|
35
|
+
- *Context Aware*: Runs in the current working directory.
|
|
36
|
+
- *Cross-Platform*: Uses `shell: true` to handle Windows `.cmd`/`.bat` files natively.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Memory Management
|
|
41
|
+
|
|
42
|
+
The memory tool (`memory.js`) is primarily used by the background **Janitor** model, but can be accessed by the main agent if necessary.
|
|
43
|
+
|
|
44
|
+
- **Temporary Context (`action='temp'`)**: Saves a rolling summary of the current session to maintain conversational context without bloating the main prompt history.
|
|
45
|
+
- **Persistent User Memory (`action='user'`)**:
|
|
46
|
+
- The Janitor analyzes conversations to detect user preferences, hobbies, or instructions.
|
|
47
|
+
- It uses `add`, `update`, or `delete` methods to manage facts in the encrypted `memories.json` vault.
|
|
48
48
|
- These memories are injected into the system prompt of *future* sessions, allowing Flux Flow to learn and adapt to the user over time.
|
package/UI_FEATURES.md
CHANGED
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
# 🎮 User Interface & Interaction Features
|
|
2
|
-
|
|
3
|
-
Flux Flow is designed to be a high-performance terminal application. Beyond basic chat, it includes a variety of advanced UI features and human-in-the-loop controls to keep you in command of the agent.
|
|
4
|
-
|
|
5
|
-
## ⌨️ Command System
|
|
6
|
-
|
|
7
|
-
You can control the application using `/` commands directly in the chat input:
|
|
8
|
-
|
|
9
|
-
- **/mode [flux|flow]**: Quickly switch between **Flux** (Dev) and **Flow** (Chat) modes. Using it without arguments opens the selection menu.
|
|
10
|
-
- **/thinking [low|medium|high|max|show|hide]**: Adjust reasoning depth or toggle visibility of the thinking process. Using it without arguments opens the selection menu.
|
|
11
|
-
- **/model [name]**: Choose which AI model to use for the main interaction.
|
|
12
|
-
- **/key**: Open the API Key management view to update or remove your credentials.
|
|
13
|
-
- **/settings**: Access the system configuration menu.
|
|
14
|
-
- **/profile**: Update your name, nickname, and custom instructions.
|
|
15
|
-
- **/memory**: View and manage the persistent memories extracted by the Janitor.
|
|
16
|
-
- **/resume <chat-id>**: Switch back to a previous conversation.
|
|
17
|
-
- **/changelog**: Open the project's changelog in your default browser.
|
|
18
|
-
- **/help**: List all available commands.
|
|
19
|
-
|
|
20
|
-
### Command Shortcuts
|
|
21
|
-
|
|
22
|
-
For power users, several commands support direct arguments to skip the menus:
|
|
23
|
-
- ` /mode flux ` or ` /mode flow `
|
|
24
|
-
- ` /thinking low ` / ` /thinking medium ` / ` /thinking high ` / ` /thinking max `
|
|
25
|
-
- ` /thinking show ` / ` /thinking hide ` (Toggles thinking process visibility)
|
|
26
|
-
- ` /model gemini-3.1-pro-preview ` (Switches model directly)
|
|
27
|
-
|
|
28
|
-
## 🧠 Thinking Levels & Visualization
|
|
29
|
-
|
|
30
|
-
Flux Flow separates the model's "internal monologue" (reasoning) from its final response using `<think>` tags.
|
|
31
|
-
|
|
32
|
-
- **Thinking Levels**: Depending on the mode, you can choose from **Low**, **Medium**, **High**, or **Max**. Higher levels allow the agent more "space" to reason through complex architecture or debugging problems.
|
|
33
|
-
- **Show/Hide Thinking**: You can toggle the visibility of the thinking process using `/thinking show/hide`.
|
|
34
|
-
- When **Hidden**, the agent doesn't just disappear; it provides a "minimalist" view showing only the core **Headings** and **Action Steps** (bolded lines) from its reasoning. This keeps you informed of its current "step" without cluttering the screen with detailed internal monologue.
|
|
35
|
-
|
|
36
|
-
## 🛡️ Human-in-the-Loop (HITL) Verification
|
|
37
|
-
|
|
38
|
-
Security and safety are paramount when an AI has access to your file system and terminal. Flux Flow implements several layers of verification:
|
|
39
|
-
|
|
40
|
-
### Tool Approval
|
|
41
|
-
By default, the agent **cannot** execute dangerous actions without your consent.
|
|
42
|
-
- **Terminal Approval**: If the agent attempts to run a shell command (`exec_command`), a dedicated approval screen appears showing the exact command. You can **Allow** or **Deny** the execution.
|
|
43
|
-
- **File Approval**: Similar to terminal commands, writing or updating files requires manual approval unless configured otherwise.
|
|
44
|
-
- **Safe Commands**: Basic read-only commands (like `ls`, `git status`, `pwd`) are automatically allowed to minimize friction.
|
|
45
|
-
|
|
46
|
-
### Auto-Execution (Advanced)
|
|
47
|
-
For power users, **Auto-Exec** can be enabled in `/settings`.
|
|
48
|
-
- **⚠️ Warning**: This allows the agent to run any tool and execute any command autonomously.
|
|
49
|
-
- **External Access**: You can also toggle whether the agent is allowed to access files outside of its current working directory.
|
|
50
|
-
|
|
51
|
-
## 🔄 Steering & Resolution
|
|
52
|
-
|
|
53
|
-
### Real-time Steering
|
|
54
|
-
If you realize the agent is going down the wrong path *while* it is in an agentic loop, you can provide "Steering Hints." The system will inject your feedback into the next loop to course-correct the agent.
|
|
55
|
-
|
|
56
|
-
### Resolution Modal
|
|
57
|
-
If the agent finishes its task just as you send a steering hint, a **Resolution Modal** appears. It asks if you want to:
|
|
58
|
-
- **Send Anyway**: Start a new loop with your feedback.
|
|
59
|
-
- **Edit Prompt**: Refine your feedback before sending.
|
|
60
|
-
|
|
61
|
-
## 📊 Status Bar & Feedback
|
|
62
|
-
The bottom of the screen features a dynamic status bar showing:
|
|
63
|
-
- **Active Mode** (Flux/Flow)
|
|
64
|
-
- **Thinking Level**
|
|
65
|
-
- **Token Usage**: Real-time tracking of tokens used in the current session.
|
|
66
|
-
- **Agentic Loops**: Counters showing how many times the agent has "looped" to solve the current task.
|
|
1
|
+
# 🎮 User Interface & Interaction Features
|
|
2
|
+
|
|
3
|
+
Flux Flow is designed to be a high-performance terminal application. Beyond basic chat, it includes a variety of advanced UI features and human-in-the-loop controls to keep you in command of the agent.
|
|
4
|
+
|
|
5
|
+
## ⌨️ Command System
|
|
6
|
+
|
|
7
|
+
You can control the application using `/` commands directly in the chat input:
|
|
8
|
+
|
|
9
|
+
- **/mode [flux|flow]**: Quickly switch between **Flux** (Dev) and **Flow** (Chat) modes. Using it without arguments opens the selection menu.
|
|
10
|
+
- **/thinking [low|medium|high|max|show|hide]**: Adjust reasoning depth or toggle visibility of the thinking process. Using it without arguments opens the selection menu.
|
|
11
|
+
- **/model [name]**: Choose which AI model to use for the main interaction.
|
|
12
|
+
- **/key**: Open the API Key management view to update or remove your credentials.
|
|
13
|
+
- **/settings**: Access the system configuration menu.
|
|
14
|
+
- **/profile**: Update your name, nickname, and custom instructions.
|
|
15
|
+
- **/memory**: View and manage the persistent memories extracted by the Janitor.
|
|
16
|
+
- **/resume <chat-id>**: Switch back to a previous conversation.
|
|
17
|
+
- **/changelog**: Open the project's changelog in your default browser.
|
|
18
|
+
- **/help**: List all available commands.
|
|
19
|
+
|
|
20
|
+
### Command Shortcuts
|
|
21
|
+
|
|
22
|
+
For power users, several commands support direct arguments to skip the menus:
|
|
23
|
+
- ` /mode flux ` or ` /mode flow `
|
|
24
|
+
- ` /thinking low ` / ` /thinking medium ` / ` /thinking high ` / ` /thinking max `
|
|
25
|
+
- ` /thinking show ` / ` /thinking hide ` (Toggles thinking process visibility)
|
|
26
|
+
- ` /model gemini-3.1-pro-preview ` (Switches model directly)
|
|
27
|
+
|
|
28
|
+
## 🧠 Thinking Levels & Visualization
|
|
29
|
+
|
|
30
|
+
Flux Flow separates the model's "internal monologue" (reasoning) from its final response using `<think>` tags.
|
|
31
|
+
|
|
32
|
+
- **Thinking Levels**: Depending on the mode, you can choose from **Low**, **Medium**, **High**, or **Max**. Higher levels allow the agent more "space" to reason through complex architecture or debugging problems.
|
|
33
|
+
- **Show/Hide Thinking**: You can toggle the visibility of the thinking process using `/thinking show/hide`.
|
|
34
|
+
- When **Hidden**, the agent doesn't just disappear; it provides a "minimalist" view showing only the core **Headings** and **Action Steps** (bolded lines) from its reasoning. This keeps you informed of its current "step" without cluttering the screen with detailed internal monologue.
|
|
35
|
+
|
|
36
|
+
## 🛡️ Human-in-the-Loop (HITL) Verification
|
|
37
|
+
|
|
38
|
+
Security and safety are paramount when an AI has access to your file system and terminal. Flux Flow implements several layers of verification:
|
|
39
|
+
|
|
40
|
+
### Tool Approval
|
|
41
|
+
By default, the agent **cannot** execute dangerous actions without your consent.
|
|
42
|
+
- **Terminal Approval**: If the agent attempts to run a shell command (`exec_command`), a dedicated approval screen appears showing the exact command. You can **Allow** or **Deny** the execution.
|
|
43
|
+
- **File Approval**: Similar to terminal commands, writing or updating files requires manual approval unless configured otherwise.
|
|
44
|
+
- **Safe Commands**: Basic read-only commands (like `ls`, `git status`, `pwd`) are automatically allowed to minimize friction.
|
|
45
|
+
|
|
46
|
+
### Auto-Execution (Advanced)
|
|
47
|
+
For power users, **Auto-Exec** can be enabled in `/settings`.
|
|
48
|
+
- **⚠️ Warning**: This allows the agent to run any tool and execute any command autonomously.
|
|
49
|
+
- **External Access**: You can also toggle whether the agent is allowed to access files outside of its current working directory.
|
|
50
|
+
|
|
51
|
+
## 🔄 Steering & Resolution
|
|
52
|
+
|
|
53
|
+
### Real-time Steering
|
|
54
|
+
If you realize the agent is going down the wrong path *while* it is in an agentic loop, you can provide "Steering Hints." The system will inject your feedback into the next loop to course-correct the agent.
|
|
55
|
+
|
|
56
|
+
### Resolution Modal
|
|
57
|
+
If the agent finishes its task just as you send a steering hint, a **Resolution Modal** appears. It asks if you want to:
|
|
58
|
+
- **Send Anyway**: Start a new loop with your feedback.
|
|
59
|
+
- **Edit Prompt**: Refine your feedback before sending.
|
|
60
|
+
|
|
61
|
+
## 📊 Status Bar & Feedback
|
|
62
|
+
The bottom of the screen features a dynamic status bar showing:
|
|
63
|
+
- **Active Mode** (Flux/Flow)
|
|
64
|
+
- **Thinking Level**
|
|
65
|
+
- **Token Usage**: Real-time tracking of tokens used in the current session.
|
|
66
|
+
- **Agentic Loops**: Counters showing how many times the agent has "looped" to solve the current task.
|
|
67
67
|
- **API Status**: Visual feedback when the model is thinking or executing a tool.
|
package/dist/fluxflow.js
CHANGED
|
@@ -199,7 +199,7 @@ var ChatLayout_default = ChatLayout;
|
|
|
199
199
|
// src/components/StatusBar.jsx
|
|
200
200
|
import React3 from "react";
|
|
201
201
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
202
|
-
var StatusBar = React3.memo(({ mode, thinkingLevel, tokens = "0.0k", chatId = "NEW-SESSION", isMemoryEnabled = true }) => {
|
|
202
|
+
var StatusBar = React3.memo(({ mode, thinkingLevel, tokens = "0.0k", tokensTotal = "0.0k", chatId = "NEW-SESSION", isMemoryEnabled = true }) => {
|
|
203
203
|
const modeColor = mode === "Flux" ? "yellow" : "cyan";
|
|
204
204
|
const modeIcon = mode === "Flux" ? "\u26A1" : "\u{1F30A}";
|
|
205
205
|
const memStatus = isMemoryEnabled ? "ON" : "OFF";
|
|
@@ -215,7 +215,7 @@ var StatusBar = React3.memo(({ mode, thinkingLevel, tokens = "0.0k", chatId = "N
|
|
|
215
215
|
},
|
|
216
216
|
/* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Text3, { color: modeColor, bold: true }, modeIcon, " ", mode.toUpperCase()), /* @__PURE__ */ React3.createElement(Text3, { color: "gray" }, " \u2502 "), /* @__PURE__ */ React3.createElement(Text3, { color: "magenta" }, "\u{1F9E0} ", thinkingLevel)),
|
|
217
217
|
/* @__PURE__ */ React3.createElement(Box3, { flexGrow: 1, justifyContent: "center", paddingX: 2 }, /* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "\u{1F4C1} "), /* @__PURE__ */ React3.createElement(Text3, { color: "blue", dimColor: true, italic: true }, process.cwd())),
|
|
218
|
-
/* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Text3, { color: "gray" }, "MEM: "), /* @__PURE__ */ React3.createElement(Text3, { color: memStatus === "ON" ? "green" : "red" }, memStatus), /* @__PURE__ */ React3.createElement(Text3, { color: "gray" }, " \u2502 "), /* @__PURE__ */ React3.createElement(Text3, { color: "blue" }, " Tokens ",
|
|
218
|
+
/* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Text3, { color: "gray" }, "MEM: "), /* @__PURE__ */ React3.createElement(Text3, { color: memStatus === "ON" ? "green" : "red" }, memStatus), /* @__PURE__ */ React3.createElement(Text3, { color: "gray" }, " \u2502 "), /* @__PURE__ */ React3.createElement(Text3, { color: "blue" }, " Tokens ", tokensTotal > 1e3 ? `${(tokensTotal / 1e3).toFixed(1)}k` : tokensTotal, " (", Math.round(tokens / 254e3 * 100), "%)"), /* @__PURE__ */ React3.createElement(Text3, { color: "gray" }, " \u2502 "), /* @__PURE__ */ React3.createElement(Text3, { color: "dim" }, "ID: ", chatId, " "))
|
|
219
219
|
);
|
|
220
220
|
});
|
|
221
221
|
var StatusBar_default = StatusBar;
|
|
@@ -410,7 +410,7 @@ tool:functions.tool_name(arguments)
|
|
|
410
410
|
- WEB TOOLS (Available in Flux & Flow) -
|
|
411
411
|
1. Web Search: tool:functions.web_search(query="<query>", limit=number). Find info. limit is optional (3-10, default 10). If user asks about something that is not in your training data, proactively use this tool to find the information.Winder search recomemded (limit = 10) when exploring a topic.
|
|
412
412
|
2. Web Scrape: tool:functions.web_scrape(url="<url>"). provides detail from a URL.
|
|
413
|
-
3. Ask User: tool:functions.ask(question="...", optionA="Option::Desc", optionB="Option::Desc").
|
|
413
|
+
3. Ask User: tool:functions.ask(question="...", optionA="Option::Desc", optionB="Option::Desc"). Mandatory triggers include: 1) **Path Divergence**: When multiple architectural or technical solutions exist, present options via 'ask' instead of choosing arbitrarily. 2) **Security Boundaries**: Explicitly request permission via 'ask' before accessing sensitive files (e.g., .env, config keys, credentials). 3) **Ambiguity Resolution**: Use 'ask' to clarify vague prompts before executing terminal commands or writing code. 4) **Risk Mitigation**: Require a 'Yes/No' confirmation for any destructive or irreversible operations. Options must always follow the 'Short Label::Detailed Description' format. This tool is a non-terminating suspension so you can get guidance without losing context.
|
|
414
414
|
${mode === "Flux" ? `
|
|
415
415
|
- DEV & FILE TOOLS (Available in FLUX MODE ONLY) -
|
|
416
416
|
1. View File: tool:functions.view_file(path="relative/path", start_line=number, end_line=number). Reads file content. Auto-truncates at 500 lines unless start_line and end_line are provided.
|
|
@@ -422,7 +422,7 @@ ${mode === "Flux" ? `
|
|
|
422
422
|
- 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()}
|
|
423
423
|
-----------------
|
|
424
424
|
Results will be provided in the next loop as: [TOOL_RESULT]: [content]
|
|
425
|
-
WHEN CALLING TOOLS, YOU **MUST**
|
|
425
|
+
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]'.
|
|
426
426
|
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.
|
|
427
427
|
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.
|
|
428
428
|
-- END FUNCTION CALLING PROTOCOL --`.trim();
|
|
@@ -465,6 +465,7 @@ var getSystemInstruction = (profile, thinkingLevel, mode, systemSettings, tempMe
|
|
|
465
465
|
if (thinkingLevel === "Low") levelKey = "Minimal";
|
|
466
466
|
if (thinkingLevel === "xHigh" || thinkingLevel === "Max") levelKey = "Max";
|
|
467
467
|
const thinkingConfig = thinking_prompts_default[levelKey] || thinking_prompts_default["Medium"];
|
|
468
|
+
const osDetected = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
|
|
468
469
|
const nameStr = profile.name && profile.name?.length > 0 ? `User Name: ${profile.name}` : "";
|
|
469
470
|
const nicknameStr = profile.nickname && profile.nickname?.length > 0 ? `. User Nickname: ${profile.nickname}.` : "";
|
|
470
471
|
const userInstrStr = profile.instructions && profile.instructions?.length > 0 ? `. User Instructions: ${profile.instructions}.` : "";
|
|
@@ -485,8 +486,9 @@ ${userMemories}
|
|
|
485
486
|
` : ""}${isMemoryEnabled ? `${tempMemoriesStr}
|
|
486
487
|
|
|
487
488
|
` : ""}--- START SYSTEM INSTRUCTION ---
|
|
488
|
-
You are Flux Flow. A CLI Agent. Your tone will be friendly, warm, sassy, approchable,
|
|
489
|
+
You are Flux Flow. A CLI Agent. Your tone will be friendly, warm, sassy, approchable, funny, NO ROMANTIC OR FLIRTY WORDS. Dont mention modes unless explicitly asked. ${mode === "Flux" ? "You are currently operating in FLUX (dev) mode. Keep your agentic approach goal oriented. Use provided tools when needed. And try to minimize number of agentic loops (Agent Loop is limited to 50 per turn, finish your goal by then). Analyze user prompt and project requirements, then plan your approach." : "You are currently operating in Flow (chat) mode. Focus more on conversation quality and user experience. Keep Agentic Loops to minimum (Agent Loop is limited to 7 per turn, finish your goal by then). You will get access to Web Tools only in this mode."}
|
|
489
490
|
CURRENT_WORKING_DIRECTORY: ${cwdStr}.
|
|
491
|
+
OS: ${osDetected}. ${osDetected && mode == "Flux" ? "Your terminal commands will run on CMD. Prefer PS scripts via CMD instead of raw CMD commands." : ""}
|
|
490
492
|
${nameStr}${nicknameStr}${userInstrStr}
|
|
491
493
|
|
|
492
494
|
${thinkingConfig}
|
|
@@ -524,6 +526,7 @@ Every ${isMemoryEnabled ? "Prompt, Responses & Memories" : "Prompt & Responses"}
|
|
|
524
526
|
|
|
525
527
|
-- START REPONSE FINISH PROTOCOL --
|
|
526
528
|
WHEN YOU ARE DONE AND NEED NO LONGER AGENT LOOP FOR THE TASK, WRITE [turn: finish] AT VERY END OF YOUR RESPONSE TO AVOID AGENT LOOPS. IF YOU ARE NOT COMPLETED YET AND WANT NEXT LOOP WRITE [turn: continue] AT VERY END OF YOUR RESPONSE TO CONTINUE AGENT LOOPS. YOU CAN STACK MULTIPLE TOOLS AT ONCE BUT **HAVE TO** WRITE [turn: continue] AFTER WRITING ALL TOOL CALLS.
|
|
529
|
+
When you 'finish' an agentic loop, you will lose your previous turn 'thinking' data. So only write [turn: finish] when you are absolutely sure that you are done with the task. Or user has to prompt again and re-thinking again from scratch will use tokens that were already planned.
|
|
527
530
|
-- END REPONSE FINISH PROTOCOL --
|
|
528
531
|
Current date and Time is: ${dateTimeStr}
|
|
529
532
|
--- END SYSTEM INSTRUCTION ---`.trim();
|
|
@@ -1229,7 +1232,7 @@ ${formatted}${footer}`;
|
|
|
1229
1232
|
var ask_user = async (args, context) => {
|
|
1230
1233
|
const parsed = parseArgs(args);
|
|
1231
1234
|
const { question } = parsed;
|
|
1232
|
-
if (!question) return 'ERROR: Missing "question" argument for
|
|
1235
|
+
if (!question) return 'ERROR: Missing "question" argument for ask.';
|
|
1233
1236
|
if (!context.onAskUser) return "ERROR: onAskUser callback not provided in tool context.";
|
|
1234
1237
|
const options = [];
|
|
1235
1238
|
Object.keys(parsed).forEach((key) => {
|
|
@@ -1295,35 +1298,39 @@ var signalTermination = () => {
|
|
|
1295
1298
|
};
|
|
1296
1299
|
var detectToolCalls = (text) => {
|
|
1297
1300
|
const results = [];
|
|
1298
|
-
const
|
|
1299
|
-
let
|
|
1300
|
-
while (
|
|
1301
|
-
const
|
|
1302
|
-
|
|
1303
|
-
const
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1301
|
+
const toolRegex = /(?:\[?\s*tool:functions\.)([a-z0-9_]+)\s*\(([\s\S]*?)\)(?:\s*\]?)/gi;
|
|
1302
|
+
let match;
|
|
1303
|
+
while ((match = toolRegex.exec(text)) !== null) {
|
|
1304
|
+
const fullMatch = match[0];
|
|
1305
|
+
const toolName = match[1];
|
|
1306
|
+
const args = match[2];
|
|
1307
|
+
let openCount = (args.match(/\(/g) || []).length;
|
|
1308
|
+
let closeCount = (args.match(/\)/g) || []).length;
|
|
1309
|
+
let finalArgs = args;
|
|
1310
|
+
let finalFullMatch = fullMatch;
|
|
1311
|
+
if (openCount > closeCount) {
|
|
1312
|
+
const startIdx = match.index + fullMatch.indexOf("(");
|
|
1313
|
+
let balance = 0;
|
|
1314
|
+
let endIdx = -1;
|
|
1315
|
+
for (let i = startIdx; i < text.length; i++) {
|
|
1316
|
+
if (text[i] === "(") balance++;
|
|
1317
|
+
if (text[i] === ")") balance--;
|
|
1318
|
+
if (balance === 0) {
|
|
1319
|
+
endIdx = i;
|
|
1320
|
+
break;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
if (endIdx !== -1) {
|
|
1324
|
+
finalArgs = text.substring(startIdx + 1, endIdx);
|
|
1325
|
+
finalFullMatch = text.substring(match.index, endIdx + 1);
|
|
1326
|
+
toolRegex.lastIndex = endIdx + 1;
|
|
1317
1327
|
}
|
|
1318
1328
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
const args = text.substring(openParenIdx + 1, endIdx);
|
|
1325
|
-
results.push({ fullMatch, toolName, args });
|
|
1326
|
-
searchIdx = endIdx + 1;
|
|
1329
|
+
results.push({
|
|
1330
|
+
fullMatch: finalFullMatch,
|
|
1331
|
+
toolName: toolName.trim(),
|
|
1332
|
+
args: finalArgs.trim()
|
|
1333
|
+
});
|
|
1327
1334
|
}
|
|
1328
1335
|
return results;
|
|
1329
1336
|
};
|
|
@@ -1375,7 +1382,7 @@ USER_PROMPT: ${agentText}`.trim();
|
|
|
1375
1382
|
}
|
|
1376
1383
|
}
|
|
1377
1384
|
yield { type: "turn_reset", content: true };
|
|
1378
|
-
const contents = modifiedHistory.filter((msg) => (msg.role === "user" || msg.role === "agent" || msg.role === "system") && !String(msg.id).startsWith("welcome")).map((msg) => ({
|
|
1385
|
+
const contents = modifiedHistory.filter((msg) => (msg.role === "user" || msg.role === "agent" || msg.role === "system") && !String(msg.id).startsWith("welcome") && !msg.isMeta).map((msg) => ({
|
|
1379
1386
|
role: msg.role === "user" || msg.role === "system" ? "user" : "model",
|
|
1380
1387
|
parts: [{ text: msg.text }]
|
|
1381
1388
|
}));
|
|
@@ -1609,7 +1616,8 @@ ${boxBottom}
|
|
|
1609
1616
|
yield { type: "status", content: "Working..." };
|
|
1610
1617
|
}
|
|
1611
1618
|
const cleanedTurnText = turnText.replace(/<think>[\s\S]*?<\/think>/g, "").replace(/\[?\s*(turn\s*:)?\s*(continue|finish)\s*\]?/gi, "").trim();
|
|
1612
|
-
|
|
1619
|
+
const isActuallyFinished = hasFinish;
|
|
1620
|
+
if (isActuallyFinished) {
|
|
1613
1621
|
const lateHint = await steeringCallback();
|
|
1614
1622
|
if (lateHint) {
|
|
1615
1623
|
toolResults.push(`[USER STEERING HINT]: ${lateHint}`);
|
|
@@ -1694,13 +1702,11 @@ ${timestamp}`;
|
|
|
1694
1702
|
}
|
|
1695
1703
|
break;
|
|
1696
1704
|
}
|
|
1697
|
-
if (
|
|
1705
|
+
if (isActuallyFinished) break;
|
|
1698
1706
|
const nextAgentMsg = cleanedTurnText.trim() || "*Working...*";
|
|
1699
1707
|
modifiedHistory.push({ role: "agent", text: nextAgentMsg });
|
|
1700
|
-
const nextUserMsg = toolResults.length > 0 ?
|
|
1701
|
-
|
|
1702
|
-
modifiedHistory.push({ role: "user", text: nextUserMsg });
|
|
1703
|
-
}
|
|
1708
|
+
const nextUserMsg = toolResults.length > 0 ? toolResults.join("\n") : "[turn: continue]";
|
|
1709
|
+
modifiedHistory.push({ role: "user", text: nextUserMsg });
|
|
1704
1710
|
}
|
|
1705
1711
|
yield { type: "status", content: null };
|
|
1706
1712
|
};
|
|
@@ -1835,11 +1841,28 @@ function MemoryModal({ onClose }) {
|
|
|
1835
1841
|
return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", borderStyle: "double", borderColor: "yellow", padding: 1, width: 80 }, /* @__PURE__ */ React8.createElement(Box8, { justifyContent: "center", marginBottom: 1 }, /* @__PURE__ */ React8.createElement(Text8, { bold: true, color: "yellow" }, "\u{1F9E0} LONG-TERM MEMORY VAULT")), memories.length === 0 ? /* @__PURE__ */ React8.createElement(Box8, { justifyContent: "center", paddingY: 2 }, /* @__PURE__ */ React8.createElement(Text8, { italic: true, color: "gray" }, "The vault is currently empty...")) : memories.map((mem, idx) => /* @__PURE__ */ React8.createElement(Box8, { key: mem.id, paddingX: 1, backgroundColor: idx === selectedIndex ? "#333" : void 0 }, /* @__PURE__ */ React8.createElement(Text8, { color: idx === selectedIndex ? "yellow" : "white" }, idx === selectedIndex ? "\u276F " : " ", idx + 1, ". ", cleanDisplay(mem.memory)))), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1, borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, paddingTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, { color: "gray" }, "\u2191/\u2193 Navigate \u2022 ", /* @__PURE__ */ React8.createElement(Text8, { color: "red" }, "x"), " Delete Memory \u2022 ", /* @__PURE__ */ React8.createElement(Text8, { color: "cyan" }, "Esc"), " Back to Chat")));
|
|
1836
1842
|
}
|
|
1837
1843
|
|
|
1844
|
+
// src/utils/terminal.js
|
|
1845
|
+
var getTerminalEnv = () => {
|
|
1846
|
+
if (process.env.TERM_PROGRAM === "vscode") return "vscode";
|
|
1847
|
+
if (process.env.WT_SESSION) return "wt";
|
|
1848
|
+
return "default";
|
|
1849
|
+
};
|
|
1850
|
+
var emojiSpace = (baseSpaces = 2) => {
|
|
1851
|
+
const env = getTerminalEnv();
|
|
1852
|
+
if (env === "wt") {
|
|
1853
|
+
return " ".repeat(Math.max(1, baseSpaces - 1));
|
|
1854
|
+
}
|
|
1855
|
+
if (env === "vscode") {
|
|
1856
|
+
return " ".repeat(baseSpaces);
|
|
1857
|
+
}
|
|
1858
|
+
return " ".repeat(baseSpaces);
|
|
1859
|
+
};
|
|
1860
|
+
|
|
1838
1861
|
// src/app.jsx
|
|
1839
1862
|
var SESSION_START_TIME = Date.now();
|
|
1840
1863
|
var CHANGELOG_URL = "https://fluxflow-cli.onrender.com/changelog.html";
|
|
1841
|
-
var versionFluxflow = "1.1.
|
|
1842
|
-
var updatedOn = "2026-04-
|
|
1864
|
+
var versionFluxflow = "1.1.3";
|
|
1865
|
+
var updatedOn = "2026-04-27";
|
|
1843
1866
|
var ResolutionModal = ({ data, onResolve, onEdit }) => /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 2, paddingY: 1, width: "100%" }, /* @__PURE__ */ React9.createElement(Text9, { color: "magenta", bold: true, underline: true }, "\u{1F7E3} STEERING HINT RESOLUTION"), /* @__PURE__ */ React9.createElement(Text9, { marginTop: 1 }, "The agent already finished the task (turn: finish) before your hint was consumed."), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1, backgroundColor: "#222", paddingX: 1, width: "100%" }, /* @__PURE__ */ React9.createElement(Text9, { italic: true, color: "gray" }, '"', data, '"')), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text9, { color: "cyan" }, "How would you like to proceed?")), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(
|
|
1844
1867
|
CommandMenu,
|
|
1845
1868
|
{
|
|
@@ -1865,6 +1888,7 @@ var FLUX_LOGO = gradient(["#00ffff", "#0077ff", "#ff00ff"]).multiline(
|
|
|
1865
1888
|
function App() {
|
|
1866
1889
|
const { stdout } = useStdout();
|
|
1867
1890
|
const [input, setInput] = useState5("");
|
|
1891
|
+
const [isExpanded, setIsExpanded] = useState5(false);
|
|
1868
1892
|
const [mode, setMode] = useState5("Flux");
|
|
1869
1893
|
const [terminalSize, setTerminalSize] = useState5({
|
|
1870
1894
|
columns: stdout?.columns || 80,
|
|
@@ -1899,7 +1923,8 @@ function App() {
|
|
|
1899
1923
|
text: `\u{1F680} **New version 'v${latestVersion}' is available!**
|
|
1900
1924
|
Type \`npm i -g fluxflow-cli\` to update.
|
|
1901
1925
|
Check what's new using \`/changelog\` command.`,
|
|
1902
|
-
isUpdateNotification: true
|
|
1926
|
+
isUpdateNotification: true,
|
|
1927
|
+
isMeta: true
|
|
1903
1928
|
});
|
|
1904
1929
|
return newMsgs;
|
|
1905
1930
|
});
|
|
@@ -1971,8 +1996,8 @@ Check what's new using \`/changelog\` command.`,
|
|
|
1971
1996
|
for (let i = completedIndex - 1; i >= 0; i--) {
|
|
1972
1997
|
const msg = messages[i];
|
|
1973
1998
|
if (!msg) continue;
|
|
1974
|
-
let lines = (msg.text || "").split(
|
|
1975
|
-
msg.text.split(
|
|
1999
|
+
let lines = (msg.text || "").split(/\r?\n/).length;
|
|
2000
|
+
msg.text.split(/\r?\n/).forEach((l) => {
|
|
1976
2001
|
lines += Math.floor(l.length / width);
|
|
1977
2002
|
});
|
|
1978
2003
|
lines += msg.role === "think" ? 3 : 2;
|
|
@@ -1987,7 +2012,23 @@ Check what's new using \`/changelog\` command.`,
|
|
|
1987
2012
|
isTruncated: startIdx > 0
|
|
1988
2013
|
};
|
|
1989
2014
|
}, [messages, completedIndex, stdout?.columns]);
|
|
2015
|
+
const terminalWidth = stdout?.columns || 80;
|
|
2016
|
+
const wrapWidth = Math.max(20, terminalWidth - 10);
|
|
2017
|
+
const wrappedLinesCount = input.split(/\r?\n/).reduce((acc, line) => {
|
|
2018
|
+
return acc + Math.max(1, Math.ceil(line.length / wrapWidth));
|
|
2019
|
+
}, 0);
|
|
2020
|
+
const maxLines = Math.max(1, wrappedLinesCount);
|
|
1990
2021
|
useInput4((inputText, key) => {
|
|
2022
|
+
if (maxLines > 2 && !isExpanded && activeView === "chat") {
|
|
2023
|
+
if (key.backspace || key.delete) {
|
|
2024
|
+
setInput("");
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
if (key.return) {
|
|
2028
|
+
setIsExpanded(true);
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
1991
2032
|
if (key.escape) {
|
|
1992
2033
|
if (isProcessing) {
|
|
1993
2034
|
if (!escPressed) {
|
|
@@ -2068,7 +2109,7 @@ Check what's new using \`/changelog\` command.`,
|
|
|
2068
2109
|
setInput(normalizedValue.slice(0, -1) + "\n");
|
|
2069
2110
|
return;
|
|
2070
2111
|
}
|
|
2071
|
-
const absoluteClean = normalizedValue.replace(/\\\s*\n/g, "\n").split(
|
|
2112
|
+
const absoluteClean = normalizedValue.replace(/\\\s*\n/g, "\n").split(/\r?\n/).map((l) => l.replace(/\\$/, "")).join("\n");
|
|
2072
2113
|
if (!absoluteClean.trim()) return;
|
|
2073
2114
|
if (isProcessing) {
|
|
2074
2115
|
const hintText = absoluteClean.trim();
|
|
@@ -2128,6 +2169,7 @@ ${hintText}`, color: "magenta" }];
|
|
|
2128
2169
|
setCompletedIndex(0);
|
|
2129
2170
|
setChatId(generateChatId());
|
|
2130
2171
|
setSessionStats({ tokens: 0 });
|
|
2172
|
+
setIsExpanded(false);
|
|
2131
2173
|
break;
|
|
2132
2174
|
}
|
|
2133
2175
|
case "/mode": {
|
|
@@ -2136,7 +2178,7 @@ ${hintText}`, color: "magenta" }];
|
|
|
2136
2178
|
setMode(newMode);
|
|
2137
2179
|
setMessages((prev) => {
|
|
2138
2180
|
setCompletedIndex(prev.length + 1);
|
|
2139
|
-
return [...prev, { id: Date.now(), role: "system", text: `\u2699\uFE0F [SYSTEM] Mode switched to ${newMode}
|
|
2181
|
+
return [...prev, { id: Date.now(), role: "system", text: `\u2699\uFE0F [SYSTEM] Mode switched to ${newMode}`, isMeta: true }];
|
|
2140
2182
|
});
|
|
2141
2183
|
} else {
|
|
2142
2184
|
setActiveView("mode");
|
|
@@ -2149,13 +2191,13 @@ ${hintText}`, color: "magenta" }];
|
|
|
2149
2191
|
setShowFullThinking(true);
|
|
2150
2192
|
setMessages((prev) => {
|
|
2151
2193
|
setCompletedIndex(prev.length + 1);
|
|
2152
|
-
return [...prev, { id: Date.now(), role: "system", text: "\u2699\uFE0F [SYSTEM] Full Thinking Process: VISIBLE" }];
|
|
2194
|
+
return [...prev, { id: Date.now(), role: "system", text: "\u2699\uFE0F [SYSTEM] Full Thinking Process: VISIBLE", isMeta: true }];
|
|
2153
2195
|
});
|
|
2154
2196
|
} else if (arg === "hide") {
|
|
2155
2197
|
setShowFullThinking(false);
|
|
2156
2198
|
setMessages((prev) => {
|
|
2157
2199
|
setCompletedIndex(prev.length + 1);
|
|
2158
|
-
return [...prev, { id: Date.now(), role: "system", text: "\u2699\uFE0F [SYSTEM] Full Thinking Process: HIDDEN (Headings only)" }];
|
|
2200
|
+
return [...prev, { id: Date.now(), role: "system", text: "\u2699\uFE0F [SYSTEM] Full Thinking Process: HIDDEN (Headings only)", isMeta: true }];
|
|
2159
2201
|
});
|
|
2160
2202
|
} else if (parts[1]) {
|
|
2161
2203
|
let val = parts[1].toLowerCase();
|
|
@@ -2170,7 +2212,7 @@ ${hintText}`, color: "magenta" }];
|
|
|
2170
2212
|
setThinkingLevel(formattedLevel);
|
|
2171
2213
|
setMessages((prev) => {
|
|
2172
2214
|
setCompletedIndex(prev.length + 1);
|
|
2173
|
-
return [...prev, { id: Date.now(), role: "system", text: `\u2699\uFE0F [SYSTEM] Thinking level set to ${formattedLevel}
|
|
2215
|
+
return [...prev, { id: Date.now(), role: "system", text: `\u2699\uFE0F [SYSTEM] Thinking level set to ${formattedLevel}`, isMeta: true }];
|
|
2174
2216
|
});
|
|
2175
2217
|
}
|
|
2176
2218
|
} else {
|
|
@@ -2184,7 +2226,7 @@ ${hintText}`, color: "magenta" }];
|
|
|
2184
2226
|
setActiveModel(mod);
|
|
2185
2227
|
setMessages((prev) => {
|
|
2186
2228
|
setCompletedIndex(prev.length + 1);
|
|
2187
|
-
return [...prev, { id: Date.now(), role: "system", text: `\u2699\uFE0F [SYSTEM] Model switched to ${mod}
|
|
2229
|
+
return [...prev, { id: Date.now(), role: "system", text: `\u2699\uFE0F [SYSTEM] Model switched to ${mod}`, isMeta: true }];
|
|
2188
2230
|
});
|
|
2189
2231
|
} else {
|
|
2190
2232
|
setActiveView("model");
|
|
@@ -2217,7 +2259,7 @@ ${hintText}`, color: "magenta" }];
|
|
|
2217
2259
|
saveChat(chatId, name, messages);
|
|
2218
2260
|
setMessages((prev) => {
|
|
2219
2261
|
setCompletedIndex(prev.length + 1);
|
|
2220
|
-
return [...prev, { id: Date.now(), role: "system", text: `\u{1F4BE} [MEMORY] Chat saved as "${name}" (ID: ${chatId})
|
|
2262
|
+
return [...prev, { id: Date.now(), role: "system", text: `\u{1F4BE} [MEMORY] Chat saved as "${name}" (ID: ${chatId})`, isMeta: true }];
|
|
2221
2263
|
});
|
|
2222
2264
|
break;
|
|
2223
2265
|
}
|
|
@@ -2228,7 +2270,7 @@ ${hintText}`, color: "magenta" }];
|
|
|
2228
2270
|
setMessages((prev) => {
|
|
2229
2271
|
setCompletedIndex(prev.length + 1);
|
|
2230
2272
|
return [...prev, { id: Date.now(), role: "system", text: `\u{1F5C3}\uFE0F [HISTORY] Saved Chats:
|
|
2231
|
-
${list || "No saved chats found."}
|
|
2273
|
+
${list || "No saved chats found."}`, isMeta: true }];
|
|
2232
2274
|
});
|
|
2233
2275
|
};
|
|
2234
2276
|
run();
|
|
@@ -2243,7 +2285,7 @@ ${list || "No saved chats found."}` }];
|
|
|
2243
2285
|
try {
|
|
2244
2286
|
setMessages((prev) => {
|
|
2245
2287
|
setCompletedIndex(prev.length + 1);
|
|
2246
|
-
return [...prev, { id: Date.now(), role: "system", text: "\u2622\uFE0F [NUCLEAR] Initiating reset..." }];
|
|
2288
|
+
return [...prev, { id: Date.now(), role: "system", text: "\u2622\uFE0F [NUCLEAR] Initiating reset...", isMeta: true }];
|
|
2247
2289
|
});
|
|
2248
2290
|
if (fs14.existsSync(LOGS_DIR)) fs14.removeSync(LOGS_DIR);
|
|
2249
2291
|
if (fs14.existsSync(SECRET_DIR)) fs14.removeSync(SECRET_DIR);
|
|
@@ -2269,12 +2311,13 @@ ${list || "No saved chats found."}` }];
|
|
|
2269
2311
|
}
|
|
2270
2312
|
case "/about": {
|
|
2271
2313
|
const updateStatus = latestVer ? latestVer !== versionFluxflow ? `Update Available [v${latestVer}]` : "No Update Available" : "Checking for updates...";
|
|
2272
|
-
const
|
|
2314
|
+
const s = emojiSpace(2);
|
|
2315
|
+
const aboutText = `\u2139\uFE0F${s}**FluxFlow Version:** v${versionFluxflow}
|
|
2273
2316
|
\u{1F504} **Status:** ${updateStatus}
|
|
2274
2317
|
\u{1F4C5} **Updated on:** ${updatedOn}`;
|
|
2275
2318
|
setMessages((prev) => {
|
|
2276
2319
|
setCompletedIndex(prev.length + 1);
|
|
2277
|
-
return [...prev, { id: Date.now(), role: "system", text: aboutText }];
|
|
2320
|
+
return [...prev, { id: Date.now(), role: "system", text: aboutText, isMeta: true }];
|
|
2278
2321
|
});
|
|
2279
2322
|
break;
|
|
2280
2323
|
}
|
|
@@ -2284,21 +2327,21 @@ ${list || "No saved chats found."}` }];
|
|
|
2284
2327
|
exec(`${command} ${CHANGELOG_URL}`);
|
|
2285
2328
|
setMessages((prev) => {
|
|
2286
2329
|
setCompletedIndex(prev.length + 1);
|
|
2287
|
-
return [...prev, { id: Date.now(), role: "system", text: `\u{1F310} [BROWSER] Opening changelog: ${CHANGELOG_URL}
|
|
2330
|
+
return [...prev, { id: Date.now(), role: "system", text: `\u{1F310} [BROWSER] Opening changelog: ${CHANGELOG_URL}`, isMeta: true }];
|
|
2288
2331
|
});
|
|
2289
2332
|
break;
|
|
2290
2333
|
}
|
|
2291
2334
|
case "/help": {
|
|
2292
2335
|
setMessages((prev) => {
|
|
2293
2336
|
setCompletedIndex(prev.length + 1);
|
|
2294
|
-
return [...prev, { id: Date.now(), role: "system", text: "\u2699\uFE0F [SYSTEM] Available commands: " + COMMANDS.join(", ") }];
|
|
2337
|
+
return [...prev, { id: Date.now(), role: "system", text: "\u2699\uFE0F [SYSTEM] Available commands: " + COMMANDS.join(", "), isMeta: true }];
|
|
2295
2338
|
});
|
|
2296
2339
|
break;
|
|
2297
2340
|
}
|
|
2298
2341
|
default:
|
|
2299
2342
|
setMessages((prev) => {
|
|
2300
2343
|
setCompletedIndex(prev.length + 1);
|
|
2301
|
-
return [...prev, { id: Date.now(), role: "system", text: `\u2699\uFE0F [SYSTEM] Unknown command: ${cmd}
|
|
2344
|
+
return [...prev, { id: Date.now(), role: "system", text: `\u2699\uFE0F [SYSTEM] Unknown command: ${cmd}`, isMeta: true }];
|
|
2302
2345
|
});
|
|
2303
2346
|
}
|
|
2304
2347
|
} else {
|
|
@@ -2312,6 +2355,7 @@ ${timestamp}` };
|
|
|
2312
2355
|
});
|
|
2313
2356
|
const streamChat = async () => {
|
|
2314
2357
|
setIsProcessing(true);
|
|
2358
|
+
setIsExpanded(false);
|
|
2315
2359
|
try {
|
|
2316
2360
|
const cleanHistoryForAI = [...messages, userMessage].filter(
|
|
2317
2361
|
(m) => m.role !== "think" && !String(m.id).startsWith("welcome")
|
|
@@ -2534,6 +2578,7 @@ Selection: ${val}`,
|
|
|
2534
2578
|
streamChat();
|
|
2535
2579
|
}
|
|
2536
2580
|
setInput("");
|
|
2581
|
+
setIsExpanded(false);
|
|
2537
2582
|
};
|
|
2538
2583
|
const getSuggestions = () => {
|
|
2539
2584
|
if (!input.startsWith("/") || input.includes(" ")) return [];
|
|
@@ -2602,11 +2647,12 @@ Selection: ${val}`,
|
|
|
2602
2647
|
{
|
|
2603
2648
|
title: "System Settings",
|
|
2604
2649
|
items: [
|
|
2605
|
-
{ label: `Toggle Memory
|
|
2606
|
-
{ label: `Toggle Auto-Exec
|
|
2607
|
-
{ label: `
|
|
2608
|
-
{ label: `
|
|
2609
|
-
{ label: `
|
|
2650
|
+
{ label: `Toggle Memory [ ${systemSettings.memory ? "ON" : "OFF"} ]`, value: "memory" },
|
|
2651
|
+
{ label: `Toggle Auto-Exec [ ${systemSettings.autoExec ? "ON" : "OFF"} ]`, value: "autoExec" },
|
|
2652
|
+
{ label: `Alternate Screen Buffer (Experimental) [ ${systemSettings.useAlternateBuffer ? "ON" : "OFF"} ]`, value: "altBuffer" },
|
|
2653
|
+
{ label: `External Workspace Access [ ${systemSettings.allowExternalAccess ? "ON" : "OFF"} ]`, value: "externalAccess" },
|
|
2654
|
+
{ label: `API Tier [ ${apiTier} ]`, value: "apiTier" },
|
|
2655
|
+
{ label: `Auto-Delete History [ ${systemSettings.autoDeleteHistory} ]`, value: "autoDelete" },
|
|
2610
2656
|
{ label: "Exit Settings", value: "Cancel" }
|
|
2611
2657
|
],
|
|
2612
2658
|
onSelect: (item) => {
|
|
@@ -2970,45 +3016,29 @@ Selection: ${val}`,
|
|
|
2970
3016
|
}
|
|
2971
3017
|
)));
|
|
2972
3018
|
default:
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
const wrappedLines = input.split("\n").reduce((acc, line) => {
|
|
2976
|
-
return acc + Math.max(1, Math.ceil(line.length / wrapWidth));
|
|
2977
|
-
}, 0);
|
|
2978
|
-
const maxLines = Math.max(1, wrappedLines);
|
|
2979
|
-
return /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", marginTop: 1, flexShrink: 0, width: "100%" }, /* @__PURE__ */ React9.createElement(Box9, { paddingX: 1, marginBottom: 0, justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React9.createElement(Box9, null, statusText && /* @__PURE__ */ React9.createElement(Text9, { color: "magenta", italic: true }, "\u23F3 ", statusText)), /* @__PURE__ */ React9.createElement(Text9, { color: "gray", dimColor: true }, "(", tempModelOverride || activeModel, ")")), suggestions.length > 0 && /* @__PURE__ */ React9.createElement(Box9, { paddingX: 1, marginBottom: 0 }, /* @__PURE__ */ React9.createElement(Text9, { color: "gray" }, "\u{1F4A1} Suggestions: "), suggestions.map((s, i) => /* @__PURE__ */ React9.createElement(Text9, { key: s, color: "yellow", bold: i === 0 }, " ", s, " "))), /* @__PURE__ */ React9.createElement(Box9, { backgroundColor: "#333333", paddingX: 1, paddingY: 1, width: "100%" }, /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", width: "100%" }, maxLines > 3 ? /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", width: "100%", paddingY: 0 }, /* @__PURE__ */ React9.createElement(Text9, { color: "gray", dimColor: true }, "[\u{1F4E6} ", maxLines, " lines of text in buffer - Full content will be sent]"), /* @__PURE__ */ React9.createElement(
|
|
2980
|
-
Box9,
|
|
3019
|
+
return /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", marginTop: 1, flexShrink: 0, width: "100%" }, /* @__PURE__ */ React9.createElement(Box9, { paddingX: 1, marginBottom: 0, justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React9.createElement(Box9, null, statusText && /* @__PURE__ */ React9.createElement(Text9, { color: "magenta", italic: true }, "\u23F3 ", statusText)), /* @__PURE__ */ React9.createElement(Text9, { color: "gray", dimColor: true }, "(", tempModelOverride || activeModel, ")")), suggestions.length > 0 && /* @__PURE__ */ React9.createElement(Box9, { paddingX: 1, marginBottom: 0 }, /* @__PURE__ */ React9.createElement(Text9, { color: "gray" }, "\u{1F4A1} Suggestions: "), suggestions.map((s, i) => /* @__PURE__ */ React9.createElement(Text9, { key: s, color: "yellow", bold: i === 0 }, " ", s, " "))), /* @__PURE__ */ React9.createElement(Box9, { backgroundColor: "#333333", paddingX: 1, paddingY: 1, width: "100%" }, /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", width: "100%" }, maxLines > 2 && !isExpanded ? /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "row", width: "100%", paddingY: 0, height: 1, overflow: "hidden" }, /* @__PURE__ */ React9.createElement(Box9, { flexShrink: 0, width: 3 }, /* @__PURE__ */ React9.createElement(Text9, { color: "yellow" }, "\u276F ")), /* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1, flexDirection: "row" }, /* @__PURE__ */ React9.createElement(Box9, { flexShrink: 0 }, /* @__PURE__ */ React9.createElement(Text9, { color: "magenta", bold: true }, "[Pasted ", maxLines, " Lines]")), /* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1, marginLeft: 1 }, /* @__PURE__ */ React9.createElement(
|
|
3020
|
+
MultilineInput,
|
|
2981
3021
|
{
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
/* @__PURE__ */ React9.createElement(Box9, { flexShrink: 0, width: 3 }, /* @__PURE__ */ React9.createElement(Text9, { color: "yellow" }, "\u276F ")),
|
|
2989
|
-
/* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1 }, /* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1, position: "relative" }, input.split("\n").pop() === "" && !isProcessing && /* @__PURE__ */ React9.createElement(Box9, { position: "absolute", paddingLeft: 0 }, /* @__PURE__ */ React9.createElement(Text9, { color: "gray", dimColor: true }, "Type your message...")), /* @__PURE__ */ React9.createElement(
|
|
2990
|
-
MultilineInput,
|
|
2991
|
-
{
|
|
2992
|
-
value: input.split("\n").pop() || "",
|
|
2993
|
-
onChange: (val) => {
|
|
2994
|
-
const cleanVal = val.replace(/\\$/, "");
|
|
2995
|
-
const lines = input.split("\n");
|
|
2996
|
-
lines[lines.length - 1] = cleanVal;
|
|
2997
|
-
setInput(lines.join("\n"));
|
|
2998
|
-
},
|
|
2999
|
-
onSubmit: handleSubmit,
|
|
3000
|
-
keyBindings: {
|
|
3001
|
-
submit: (key) => key.return && !key.shift && !key.ctrl,
|
|
3002
|
-
newline: (key) => key.return && key.shift || key.return && key.ctrl
|
|
3022
|
+
value: "",
|
|
3023
|
+
placeholder: " (Backspace to delete / Enter to expand)",
|
|
3024
|
+
onChange: (val) => {
|
|
3025
|
+
if (val.length > 0) {
|
|
3026
|
+
setIsExpanded(true);
|
|
3027
|
+
setInput(input + val);
|
|
3003
3028
|
}
|
|
3029
|
+
},
|
|
3030
|
+
onSubmit: () => setIsExpanded(true),
|
|
3031
|
+
keyBindings: {
|
|
3032
|
+
submit: (key) => key.return && !key.shift && !key.ctrl,
|
|
3033
|
+
newline: (key) => key.return && key.shift || key.return && key.ctrl
|
|
3004
3034
|
}
|
|
3005
|
-
|
|
3006
|
-
)) : /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "row", width: "100%", paddingY: 0 }, /* @__PURE__ */ React9.createElement(Box9, { flexShrink: 0, width: 3 }, /* @__PURE__ */ React9.createElement(Text9, { color: "yellow" }, "\u276F ")), /* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1 }, /* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1, position: "relative" }, input === "" && !isProcessing && /* @__PURE__ */ React9.createElement(Box9, { position: "absolute", paddingLeft: 0 }, /* @__PURE__ */ React9.createElement(Text9, { color: "gray", dimColor: true }, escPressed ? " Press ESC again to cancel the request." : " Type your message or /command...")), /* @__PURE__ */ React9.createElement(
|
|
3035
|
+
}
|
|
3036
|
+
)))) : /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "row", width: "100%", paddingY: 0 }, /* @__PURE__ */ React9.createElement(Box9, { flexShrink: 0, width: 3 }, /* @__PURE__ */ React9.createElement(Text9, { color: "yellow" }, "\u276F ")), /* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1 }, /* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1, position: "relative" }, input === "" && !isProcessing && /* @__PURE__ */ React9.createElement(Box9, { position: "absolute", paddingLeft: 0 }, /* @__PURE__ */ React9.createElement(Text9, { color: "gray", dimColor: true }, escPressed ? " Press ESC again to cancel the request." : " Type your message or /command...")), /* @__PURE__ */ React9.createElement(
|
|
3007
3037
|
MultilineInput,
|
|
3008
3038
|
{
|
|
3009
3039
|
value: input,
|
|
3010
3040
|
onChange: (val) => {
|
|
3011
|
-
const cleanVal = val.replace(/\\\s*\n/g, "\n");
|
|
3041
|
+
const cleanVal = val.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\\\s*\n/g, "\n");
|
|
3012
3042
|
setInput(cleanVal);
|
|
3013
3043
|
},
|
|
3014
3044
|
onSubmit: handleSubmit,
|
|
@@ -3035,6 +3065,7 @@ Selection: ${val}`,
|
|
|
3035
3065
|
mode,
|
|
3036
3066
|
thinkingLevel,
|
|
3037
3067
|
tokens: sessionStats.tokens,
|
|
3068
|
+
tokensTotal: sessionTotalTokens,
|
|
3038
3069
|
chatId,
|
|
3039
3070
|
isMemoryEnabled: systemSettings.memory
|
|
3040
3071
|
}
|