fluxflow-cli 1.3.6 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/UI_FEATURES.md CHANGED
@@ -35,6 +35,21 @@ Flux Flow separates the model's "internal monologue" (reasoning) from its final
35
35
  - **Show/Hide Thinking**: You can toggle the visibility of the thinking process using `/thinking show/hide`.
36
36
  - 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.
37
37
 
38
+ ## ⚡ Interactive Sub-Terminal
39
+
40
+ Flux Flow features a high-fidelity, interactive sub-terminal that allows you to engage with commands spawned by the agent in real-time.
41
+
42
+ - **Live Interaction**: When an agent executes a command (like `npm init`, `git commit`, or a custom script), the terminal output appears in a dedicated box.
43
+ - **Focus Toggling (`TAB`)**: While a terminal is live, you can press **`TAB`** to shift your keyboard focus from the chat prompt directly into the terminal.
44
+ - **Visual Feedback**:
45
+ - **Yellow Glow**: When the terminal is focused, its border changes to a "Double Yellow" style to indicate it is capturing input.
46
+ - **Status Indicator**: The footer will change from `● LIVE` to `▶ TERMINAL FOCUSED`.
47
+ - **Cross-Platform Compatibility**: The input bridge automatically detects your OS and sends the correct line endings (`\r\n` for Windows, `\n` for Unix), ensuring that interactive prompts like `y/n` work seamlessly across environments.
48
+ - **Input Mirroring**: Your keystrokes are mirrored in the terminal box in real-time, providing an IDE-grade responsive feel.
49
+
50
+ > [!TIP]
51
+ > Use the Interactive Sub-Terminal to handle authentication prompts, confirmation dialogs, or to manually steering a long-running process without leaving the Flux Flow interface.
52
+
38
53
  ## 🛡️ Human-in-the-Loop (HITL) Verification
39
54
 
40
55
  Security and safety are paramount when an AI has access to your file system and terminal. Flux Flow implements several layers of verification:
package/dist/fluxflow.js CHANGED
@@ -15,9 +15,9 @@ import { Box, Text } from "ink";
15
15
  var TerminalBox;
16
16
  var init_TerminalBox = __esm({
17
17
  "src/components/TerminalBox.jsx"() {
18
- TerminalBox = React.memo(({ command, output, completed = false }) => {
18
+ TerminalBox = React.memo(({ command, output, completed = false, isFocused = false }) => {
19
19
  const cleanOutput = (output || "").replace(/\r/g, "").trim();
20
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: completed ? "#334155" : "cyan", paddingX: 2, paddingY: completed ? 0 : 1, width: "100%" }, /* @__PURE__ */ React.createElement(Box, { justifyContent: "space-between" }, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: completed ? "gray" : "cyan", bold: true }, completed ? "\u{1F3C1} FINISHED:" : "\u26A1 EXECUTING:", " "), /* @__PURE__ */ React.createElement(Text, { color: completed ? "gray" : "white" }, command)), /* @__PURE__ */ React.createElement(Text, { color: completed ? "#475569" : "yellow", bold: true }, completed ? "\u25CF ARCHIVED" : "\u25CF LIVE")), cleanOutput ? /* @__PURE__ */ React.createElement(Box, { marginTop: completed ? 0 : 1, backgroundColor: "#0a0a0a", paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { color: completed ? "gray" : "green", wrap: "anywhere" }, cleanOutput)) : !completed && /* @__PURE__ */ React.createElement(Box, { marginTop: 1, backgroundColor: "#0a0a0a", paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "gray", italic: true }, "Waiting for output...")), !completed && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "gray", dimColor: true, italic: true }, "Double-press ESC to terminate if hanging.")));
20
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", borderStyle: isFocused ? "double" : "round", borderColor: completed ? "#334155" : isFocused ? "yellow" : "cyan", paddingX: 2, paddingY: completed ? 0 : 1, width: "100%" }, /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: completed ? "gray" : isFocused ? "yellow" : "cyan", bold: true }, completed ? "\u{1F3C1} FINISHED:" : "\u26A1 EXECUTING:", " "), /* @__PURE__ */ React.createElement(Text, { color: completed ? "gray" : "white" }, command)), cleanOutput ? /* @__PURE__ */ React.createElement(Box, { marginTop: completed ? 0 : 1, backgroundColor: "#0a0a0a", paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { color: completed ? "gray" : "green", wrap: "anywhere" }, cleanOutput)) : !completed && /* @__PURE__ */ React.createElement(Box, { marginTop: 1, backgroundColor: "#0a0a0a", paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "gray", italic: true }, "Waiting for output...")), /* @__PURE__ */ React.createElement(Box, { justifyContent: "space-between", marginTop: 1 }, !completed ? /* @__PURE__ */ React.createElement(Text, { color: "gray", dimColor: true, italic: true }, "Double-press ESC to terminate if hanging.") : /* @__PURE__ */ React.createElement(Box, null), /* @__PURE__ */ React.createElement(Text, { color: completed ? "#475569" : isFocused ? "yellow" : "cyan", bold: true }, completed ? "\u25CF ARCHIVED" : isFocused ? "\u25B6 TERMINAL FOCUSED" : "\u25CF LIVE (Press TAB to focus)")));
21
21
  });
22
22
  }
23
23
  });
@@ -57,16 +57,22 @@ var init_ChatLayout = __esm({
57
57
  }
58
58
  return parts.join("").replace(/^\[TOOL_RESULT\]:\s*/gi, "").split("\n").filter((line) => !line.trim().startsWith("SUCCESS:") && !line.trim().startsWith("ERROR:")).join("\n").replace(/\[\s*(turn\s*:)?\s*(continue|finish)\s*\]/gi, "").replace(/\n\s*turn\s*:\s*(continue|finish)\s*$/gi, "").replace(/\n\nResponded on .*/g, "").replace(/\n\n\[Prompted on: .*\]/g, "").replace(/(\$?\\?\/?\\rightarrow\$?|\$\\rightarrow\$)/gi, "\u2192").replace(/(\$?\\?\/?\\leftarrow\$?|\$\\leftarrow\$)/gi, "\u2190").replace(/(\$?\\?\/?\\uparrow\$?|\$\\uparrow\$)/gi, "\u2191").replace(/(\$?\\?\/?\\downarrow\$?|\$\\downarrow\$)/gi, "\u2193").replace(/(\$?\\?\/?\\leftrightarrow\$?|\$\\leftrightarrow\$)/gi, "\u2194").trim();
59
59
  };
60
- formatThinkText = (cleaned) => {
60
+ formatThinkText = (cleaned, columns = 80) => {
61
61
  if (!cleaned) return null;
62
- const lines = cleaned.split("\n").filter((l) => l.trim() !== "");
62
+ const lines = cleaned.split("\n");
63
+ const availableWidth = columns - 10;
63
64
  return lines.map((line, i) => {
64
65
  const trimmed = line.trim();
66
+ if (!trimmed) return /* @__PURE__ */ React2.createElement(Box2, { key: i, height: 1 });
65
67
  if (trimmed.startsWith("**") && trimmed.endsWith("**") || trimmed.startsWith("#")) {
66
- return /* @__PURE__ */ React2.createElement(Box2, { key: i, marginTop: i === 0 ? 0 : 1, marginBottom: 0, width: "100%" }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, trimmed.replace(/\*|#/g, "").trim()));
68
+ const hText = trimmed.replace(/\*|#/g, "").trim();
69
+ return /* @__PURE__ */ React2.createElement(Box2, { key: i, marginTop: i === 0 ? 0 : 1, marginBottom: 0, width: "100%" }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, "\u25B8 ", wrapText(hText, availableWidth - 2)));
67
70
  }
68
71
  const isBullet = trimmed.startsWith("*");
69
- return /* @__PURE__ */ React2.createElement(Box2, { key: i, marginLeft: isBullet ? 2 : 0, width: "100%" }, /* @__PURE__ */ React2.createElement(Text2, { italic: true, color: "gray", wrap: "anywhere" }, isBullet ? "\u2022 " : "", trimmed.replace(/^\*|\s\*/g, "").trim()));
72
+ const bulletPrefix = isBullet ? "\u2022 " : "";
73
+ const cleanText = trimmed.replace(/^\*|\s\*/g, "").trim();
74
+ const wrapped = wrapText(cleanText, availableWidth - (isBullet ? 2 : 0));
75
+ return /* @__PURE__ */ React2.createElement(Box2, { key: i, marginLeft: isBullet ? 2 : 0, width: "100%" }, /* @__PURE__ */ React2.createElement(Text2, { italic: true, color: "gray" }, bulletPrefix, wrapped.split("\n").join("\n" + " ".repeat(bulletPrefix.length))));
70
76
  });
71
77
  };
72
78
  InlineMarkdown = React2.memo(({ text, color }) => {
@@ -307,7 +313,7 @@ var init_ChatLayout = __esm({
307
313
  finalContent.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\\\n/g, "\n").replace(/\\$/, ""),
308
314
  columns - 6
309
315
  ).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))))
310
- ) : 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))) : /* @__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]"))));
316
+ ) : 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]"))));
311
317
  });
312
318
  ChatLayout = React2.memo(({ messages, showFullThinking, columns = 80 }) => {
313
319
  return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", width: "100%" }, messages.map((msg, idx) => /* @__PURE__ */ React2.createElement(
@@ -623,10 +629,10 @@ var thinking_prompts_default;
623
629
  var init_thinking_prompts = __esm({
624
630
  "src/data/thinking_prompts.json"() {
625
631
  thinking_prompts_default = {
626
- Max: "-- START THINKING INSTRUCTIONS --\nEFFORT_LEVEL: MAX\nThink Step by Step in Chain-of-Thought. Provide the thinking in <think>...</think> block, length given. Thinking should be structured in this format:\n\n<think>\n\\n\\n\n**Heading-ing**\n\\n\\n\n* steps...\n\\n\\n\n**Heading-ing**\n\\n\\n\n* steps...\ncontinue.\n\\n\\n\n</think>\\n\\n\n\n(MUST START THE RESPONSE IN NEW LINE AFTER ENDING THINKING BLOCK)\n\n**CRITICAL**: Heading blocks: **MIN 10**, **MAX 30**. EACH HEADING SHOULD HAVE MIN 6, MAX 10 STEPS OF INTERNAL MONOLOGUE. EXPLORE EDGE CASES & NUANCES.\n\nNEVER WRITE FUNCTION CALLS IN THINKING BLOCK WITH 'tool:' PREFIX AND NEVER WRITE '[turn: ...]' INSIDE THINKING BLOCK.\n-- END THINKING INSTRUCTIONS --",
627
- High: "-- START THINKING INSTRUCTIONS --\nEFFORT_LEVEL: HIGH\nThink Step by Step in Chain-of-Thought. Provide the thinking in <think>...</think> block, length given. Thinking should be structured in this format:\n\n<think>\n\\n\\n\n**Heading-ing**\n\\n\\n\n* steps...\n\\n\\n\n**Heading-ing**\n\\n\\n\n* steps...\ncontinue.\n\\n\\n\n</think>\\n\\n\n\n(MUST START THE RESPONSE IN NEW LINE AFTER ENDING THINKING BLOCK)\n\n**CRITICAL**: Heading blocks: **MIN 8**, **MAX 25**. EACH HEADING SHOULD HAVE MIN 4, MAX 8 STEPS OF INTERNAL MONOLOGUE. EXPLORE EDGE CASES & THINK LONGER before responding.\nNEVER WRITE FUNCTION CALLS IN THINKING BLOCK WITH 'tool:' PREFIX AND NEVER WRITE '[turn: ...]' INSIDE THINKING BLOCK.\n-- END THINKING INSTRUCTIONS --",
628
- Medium: "-- START THINKING INSTRUCTIONS --\nEFFORT_LEVEL: MEDIUM\nThink Step by Step in Chain-of-Thought. Provide the thinking in <think>...</think> block, length given. Thinking should be structured in this format:\n\n<think>\n\\n\\n\n**Heading-ing**\n\\n\\n\n* steps...\n\\n\\n\n**Heading-ing**\n\\n\\n\n* steps...\ncontinue.\n\\n\\n\n</think>\\n\\n\n\n(MUST START THE RESPONSE IN NEW LINE AFTER ENDING THINKING BLOCK)\n\n**CRITICAL**: Heading blocks: **MIN 5**, **MAX 9**. EACH HEADING SHOULD HAVE MIN 4, MAX 6 STEPS OF INTERNAL MONOLOGUE. THINK LONGER FOR COMPLEX QUERIES.\nNEVER WRITE FUNCTION CALLS IN THINKING BLOCK WITH 'tool:' PREFIX AND NEVER WRITE '[turn: ...]' INSIDE THINKING BLOCK.\n-- END THINKING INSTRUCTIONS --",
629
- Minimal: "-- START THINKING INSTRUCTIONS --\nEFFORT_LEVEL: LOW\nThink Step by Step in Chain-of-Thought. Provide the thinking in <think>...</think> block, length given. Thinking should be structured in this format:\n\n<think>\n\\n\\n\n**Heading-ing**\n\\n\\n\n* steps...\n\\n\\n\n**Heading-ing**\n\\n\\n\n* steps...\ncontinue.\n\\n\\n\n</think>\\n\\n\n\n(MUST START THE RESPONSE IN NEW LINE AFTER ENDING THINKING BLOCK)\n\n**CRITICAL**: Heading blocks: **MIN 0**, **MAX 3**. EACH HEADING SHOULD HAVE MIN 1, MAX 2 STEPS OF INTERNAL MONOLOGUE. No Thinking is preferred if query is simple.\nNEVER WRITE FUNCTION CALLS IN THINKING BLOCK WITH 'tool:' PREFIX AND NEVER WRITE '[turn: ...]' INSIDE THINKING BLOCK.\n-- END THINKING INSTRUCTIONS --"
632
+ Max: "-- START THINKING INSTRUCTIONS --\nEFFORT_LEVEL: MAX\nThink Step by Step in Chain-of-Thought. Provide the thinking in <think>...</think> block, length given. Thinking should be structured in this format:\n\n<think>\n\\n\\n\n**Heading**\n\\n\\n\nSelf MONOLOGUE\n\\n\\n\n**Heading**\n\\n\\n\nSelf MONOLOGUE\ncontinue.\n\\n\\n\n</think>\\n\\n\n\n(MUST START THE RESPONSE IN NEW LINE AFTER ENDING THINKING BLOCK)\n\n**CRITICAL**: Heading blocks: **MIN 7**, **MAX 15**. EACH HEADING SHOULD HAVE MIN 12, MAX 16 SENTENCES OF INTERNAL MONOLOGUE. EXPLORE EDGE CASES & NUANCES. Dynamic Thinking preferred based on query.\nNEVER WRITE FUNCTION CALLS IN THINKING BLOCK WITH 'tool:' PREFIX AND NEVER WRITE '[turn: ...]' INSIDE THINKING BLOCK.\nConverge Thinking & Avoid running into thinking Loops. YOU MUST NOT EXCEED ALLOTTED THINKING BUDGET. COMPLETE YOUR THINKING WITHIN LIMITS AND START YOUR RESPONSE. DEFINE YOUR DYNAMIC THINKING BUDGET BASED ON THE QUERY COMPLEXITY. DO NOT OVERTHINK SIMPLE ONES.\n-- END THINKING INSTRUCTIONS --",
633
+ High: "-- START THINKING INSTRUCTIONS --\nEFFORT_LEVEL: HIGH\nThink Step by Step in Chain-of-Thought. Provide the thinking in <think>...</think> block, length given. Thinking should be structured in this format:\n\n<think>\n\\n\\n\n**Heading**\n\\n\\n\nSelf MONOLOGUE\n\\n\\n\n**Heading**\n\\n\\n\nSelf MONOLOGUE\ncontinue.\n\\n\\n\n</think>\\n\\n\n\n(MUST START THE RESPONSE IN NEW LINE AFTER ENDING THINKING BLOCK)\n\n**CRITICAL**: Heading blocks: **MIN 5**, **MAX 7**. EACH HEADING SHOULD HAVE MIN 8, MAX 12 SENTENCES OF INTERNAL MONOLOGUE. EXPLORE EDGE CASES & THINK LONGER before responding. Dynamic Thinking preferred based on query.\nNEVER WRITE FUNCTION CALLS IN THINKING BLOCK WITH 'tool:' PREFIX AND NEVER WRITE '[turn: ...]' INSIDE THINKING BLOCK.\nConverge Thinking & Avoid running into thinking Loops. YOU MUST NOT EXCEED ALLOTTED THINKING BUDGET. COMPLETE YOUR THINKING WITHIN LIMITS AND START YOUR RESPONSE. DEFINE YOUR DYNAMIC THINKING BUDGET BASED ON THE QUERY COMPLEXITY. DO NOT OVERTHINK SIMPLE ONES.\n-- END THINKING INSTRUCTIONS --",
634
+ Medium: "-- START THINKING INSTRUCTIONS --\nEFFORT_LEVEL: MEDIUM\nThink Step by Step in Chain-of-Thought. Provide the thinking in <think>...</think> block, length given. Thinking should be structured in this format:\n\n<think>\n\\n\\n\n**Heading**\n\\n\\n\nSelf MONOLOGUE\n\\n\\n\n**Heading**\n\\n\\n\nSelf MONOLOGUE\ncontinue.\n\\n\\n\n</think>\\n\\n\n\n(MUST START THE RESPONSE IN NEW LINE AFTER ENDING THINKING BLOCK)\n\n**CRITICAL**: Heading blocks: **MIN 3**, **MAX 5**. EACH HEADING SHOULD HAVE MIN 4, MAX 8 SENTENCES OF INTERNAL MONOLOGUE. THINK LONGER FOR COMPLEX QUERIES. Dynamic Thinking preferred based on query.\nNEVER WRITE FUNCTION CALLS IN THINKING BLOCK WITH 'tool:' PREFIX AND NEVER WRITE '[turn: ...]' INSIDE THINKING BLOCK.\nConverge Thinking & Avoid running into thinking Loops. YOU MUST NOT EXCEED ALLOTTED THINKING BUDGET. COMPLETE YOUR THINKING WITHIN LIMITS AND START YOUR RESPONSE. DEFINE YOUR DYNAMIC THINKING BUDGET BASED ON THE QUERY COMPLEXITY. DO NOT OVERTHINK SIMPLE ONES.\n-- END THINKING INSTRUCTIONS --",
635
+ Minimal: "-- START THINKING INSTRUCTIONS --\nEFFORT_LEVEL: LOW\nThink Step by Step in Chain-of-Thought. Provide the thinking in <think>...</think> block, length given. Thinking should be structured in this format:\n\n<think>\n\\n\\n\n**Heading**\n\\n\\n\nSelf MONOLOGUE\n\\n\\n\n**Heading**\n\\n\\n\nSelf MONOLOGUE\ncontinue.\n\\n\\n\n</think>\\n\\n\n\n(MUST START THE RESPONSE IN NEW LINE AFTER ENDING THINKING BLOCK)\n\n**CRITICAL**: Heading blocks: **MIN 0**, **MAX 2**. EACH HEADING SHOULD HAVE MIN 1, MAX 3 SENTENCES OF INTERNAL MONOLOGUE. No Thinking is preferred if query is simple.\nNEVER WRITE FUNCTION CALLS IN THINKING BLOCK WITH 'tool:' PREFIX AND NEVER WRITE '[turn: ...]' INSIDE THINKING BLOCK.\nConverge Thinking & Avoid running into thinking Loops. YOU MUST NOT EXCEED ALLOTTED THINKING BUDGET. COMPLETE YOUR THINKING WITHIN LIMITS AND START YOUR RESPONSE. DEFINE YOUR DYNAMIC THINKING BUDGET BASED ON THE QUERY COMPLEXITY. DO NOT OVERTHINK SIMPLE ONES.\n-- END THINKING INSTRUCTIONS --"
630
636
  };
631
637
  }
632
638
  });
@@ -699,8 +705,9 @@ Every ${isMemoryEnabled ? "Prompt, Responses & Memories" : "Prompt & Responses"}
699
705
  -- START FORMATTING RULES --
700
706
  - Structure responses VISUALLY pleasing, easy to read, and beautiful.
701
707
  - USE GFM Markdown HEAVILY.
702
- - Use GFM tables for structured data to keep the terminal view organized. CRITICAL POLICY: KEEP SENTENCES IN TABLE **SHORT & CONCISE**. AND MAX 3 COLUMNS.
708
+ - Use GFM tables for structured data to keep the terminal view organized. KEEP SENTENCES IN TABLE **SHORT & CONCISE**. AND MAX 3 COLUMNS. DO NOT OVERUSE TABLES.
703
709
  - **CRITICAL**: NEVER USE LaTeX IN TERMINAL RESPONSES (exception: file content).
710
+ - Keep Poems & Literature in Code Block.
704
711
  - Use emojis & Kaomojis.
705
712
  -- END FORMATTING RULES --
706
713
 
@@ -780,10 +787,11 @@ var init_history = __esm({
780
787
  return withLock(async () => {
781
788
  const history = await loadHistory();
782
789
  const existingChat = history[id];
790
+ const persistentMessages = (messages || []).filter((m) => !m.isUpdateNotification && !m.isMeta);
783
791
  const finalName = name || (existingChat ? existingChat.name : `Session ${id.slice(-6)}`);
784
792
  history[id] = {
785
793
  name: finalName,
786
- messages,
794
+ messages: persistentMessages,
787
795
  updatedAt: Date.now()
788
796
  };
789
797
  await fs3.ensureDir(path4.dirname(HISTORY_FILE));
@@ -1375,16 +1383,49 @@ var init_update_file = __esm({
1375
1383
 
1376
1384
  // src/tools/exec_command.js
1377
1385
  import { spawn } from "child_process";
1378
- var exec_command;
1386
+ var activeChildProcess, writeToActiveCommand, terminateActiveCommand, exec_command;
1379
1387
  var init_exec_command = __esm({
1380
1388
  "src/tools/exec_command.js"() {
1381
1389
  init_arg_parser();
1390
+ activeChildProcess = null;
1391
+ writeToActiveCommand = (data) => {
1392
+ try {
1393
+ if (activeChildProcess && activeChildProcess.stdin && activeChildProcess.stdin.writable) {
1394
+ activeChildProcess.stdin.write(data);
1395
+ }
1396
+ } catch (err) {
1397
+ }
1398
+ };
1399
+ terminateActiveCommand = () => {
1400
+ if (activeChildProcess) {
1401
+ try {
1402
+ activeChildProcess.kill("SIGKILL");
1403
+ } catch (err) {
1404
+ }
1405
+ activeChildProcess = null;
1406
+ }
1407
+ };
1382
1408
  exec_command = async (args, options = {}) => {
1383
1409
  const { command } = parseArgs(args);
1384
1410
  const { onChunk } = options;
1385
1411
  if (!command) return 'ERROR: Missing "command" argument for exec_command.';
1386
1412
  return new Promise((resolve) => {
1387
- const child = spawn(command, { shell: true, cwd: process.cwd() });
1413
+ const child = spawn(command, {
1414
+ shell: true,
1415
+ cwd: process.cwd(),
1416
+ env: {
1417
+ ...process.env,
1418
+ CI: "false",
1419
+ TERM: "xterm-256color",
1420
+ FORCE_COLOR: "1"
1421
+ }
1422
+ });
1423
+ activeChildProcess = child;
1424
+ if (child.stdin) {
1425
+ child.stdin.on("error", () => {
1426
+ activeChildProcess = null;
1427
+ });
1428
+ }
1388
1429
  let stdout = "";
1389
1430
  let stderr = "";
1390
1431
  child.stdout.on("data", (data) => {
@@ -1398,6 +1439,7 @@ var init_exec_command = __esm({
1398
1439
  if (onChunk) onChunk(chunk);
1399
1440
  });
1400
1441
  child.on("close", (code) => {
1442
+ activeChildProcess = null;
1401
1443
  const result = [];
1402
1444
  if (stdout) result.push(`STDOUT:
1403
1445
  ${stdout}`);
@@ -1416,6 +1458,7 @@ ${finalOutput}`);
1416
1458
  }
1417
1459
  });
1418
1460
  child.on("error", (err) => {
1461
+ activeChildProcess = null;
1419
1462
  resolve(`ERROR: Failed to start command [${command}]: ${err.message}`);
1420
1463
  });
1421
1464
  });
@@ -1712,7 +1755,6 @@ USER_PROMPT: ${agentText}`.trim();
1712
1755
  } else if (retryCount > 0) {
1713
1756
  yield { type: "model_update", content: null };
1714
1757
  }
1715
- fs12.writeFileSync("contents.json", JSON.stringify(contents));
1716
1758
  stream = await client.models.generateContentStream({
1717
1759
  model: targetModel,
1718
1760
  contents,
@@ -2346,11 +2388,12 @@ Check what's new using \`/changelog\` command.`,
2346
2388
  const [chatId, setChatId] = useState6(generateChatId());
2347
2389
  const [activeCommand, setActiveCommand] = useState6(null);
2348
2390
  const [execOutput, setExecOutput] = useState6("");
2391
+ const [isTerminalFocused, setIsTerminalFocused] = useState6(false);
2349
2392
  const terminalEnv = useMemo(() => {
2350
2393
  const isIDE = process.env.TERM_PROGRAM === "vscode" || !!process.env.VSC_TERMINAL_URL || !!process.env.INTELLIJ_TERMINAL_COMMAND_BLOCKS;
2351
2394
  return {
2352
2395
  isIDE,
2353
- shortcut: isIDE ? "Shift+Enter" : "Ctrl+Enter"
2396
+ shortcut: isIDE ? "Shift + Enter" : "Ctrl + Enter"
2354
2397
  };
2355
2398
  }, []);
2356
2399
  const activeCommandRef = useRef(null);
@@ -2421,6 +2464,24 @@ Check what's new using \`/changelog\` command.`,
2421
2464
  }, 0);
2422
2465
  const maxLines = Math.max(1, wrappedLinesCount);
2423
2466
  useInput4((inputText, key) => {
2467
+ if (key.tab && activeCommand) {
2468
+ setIsTerminalFocused((prev) => !prev);
2469
+ return;
2470
+ }
2471
+ if (isTerminalFocused && activeCommand) {
2472
+ if (key.return) {
2473
+ const isWin = process.platform === "win32";
2474
+ writeToActiveCommand(isWin ? "\r\n" : "\n");
2475
+ setExecOutput((prev) => prev + "\n");
2476
+ } else if (key.backspace || key.delete) {
2477
+ writeToActiveCommand("\b \b");
2478
+ setExecOutput((prev) => prev.slice(0, -1));
2479
+ } else if (inputText) {
2480
+ writeToActiveCommand(inputText);
2481
+ setExecOutput((prev) => prev + inputText);
2482
+ }
2483
+ return;
2484
+ }
2424
2485
  if (maxLines > 2 && !isExpanded && activeView === "chat") {
2425
2486
  if (key.backspace || key.delete) {
2426
2487
  setInput("");
@@ -2432,13 +2493,14 @@ Check what's new using \`/changelog\` command.`,
2432
2493
  }
2433
2494
  }
2434
2495
  if (key.escape) {
2435
- if (isProcessing) {
2496
+ if (isProcessing || activeCommand) {
2436
2497
  if (!escPressed) {
2437
2498
  setEscPressed(true);
2438
2499
  if (escTimer) clearTimeout(escTimer);
2439
2500
  setEscTimer(setTimeout(() => setEscPressed(false), 3e3));
2440
2501
  } else {
2441
2502
  signalTermination();
2503
+ terminateActiveCommand();
2442
2504
  setEscPressed(false);
2443
2505
  if (escTimer) clearTimeout(escTimer);
2444
2506
  }
@@ -2874,6 +2936,7 @@ OUTPUT: ${execOutputRef.current}`;
2874
2936
  return [...prev, { id: "term-" + Date.now(), role: "system", text: finalStatus, isTerminalRecord: true }];
2875
2937
  });
2876
2938
  setActiveCommand(null);
2939
+ setIsTerminalFocused(false);
2877
2940
  setExecOutput("");
2878
2941
  },
2879
2942
  onToolApproval: async (tool, args) => {
@@ -3623,15 +3686,15 @@ Selection: ${val}`,
3623
3686
  newline: (key) => key.return && key.shift || key.return && key.ctrl || key.return && key.leftAlt || key.return && key.rightAlt
3624
3687
  }
3625
3688
  }
3626
- )))) : /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "row", width: "100%", paddingY: 0 }, /* @__PURE__ */ React10.createElement(Box10, { flexShrink: 0, width: 3 }, /* @__PURE__ */ React10.createElement(Text10, { color: "yellow" }, "\u276F ")), /* @__PURE__ */ React10.createElement(Box10, { flexGrow: 1 }, /* @__PURE__ */ React10.createElement(Box10, { flexGrow: 1, position: "relative" }, input === "" && !isProcessing && /* @__PURE__ */ React10.createElement(Box10, { position: "absolute", paddingLeft: 0 }, /* @__PURE__ */ React10.createElement(Text10, { color: "gray" }, escPressed ? " Press ESC again to cancel the request." : ` Type /cmd or message... (${terminalEnv.shortcut} for newline)`)), /* @__PURE__ */ React10.createElement(
3689
+ )))) : /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "row", width: "100%", paddingY: 0 }, /* @__PURE__ */ React10.createElement(Box10, { flexShrink: 0, width: 3 }, /* @__PURE__ */ React10.createElement(Text10, { color: "yellow" }, "\u276F ")), /* @__PURE__ */ React10.createElement(Box10, { flexGrow: 1 }, /* @__PURE__ */ React10.createElement(Box10, { flexGrow: 1, position: "relative" }, input === "" && /* @__PURE__ */ React10.createElement(Box10, { position: "absolute", paddingLeft: 0 }, activeCommand && !isTerminalFocused ? /* @__PURE__ */ React10.createElement(Text10, { color: "yellow" }, " Press TAB to interact with terminal...") : activeCommand && isTerminalFocused ? /* @__PURE__ */ React10.createElement(Text10, { color: "yellow", bold: true }, " [ TERMINAL FOCUSED ] Type to interact, press TAB to exit...") : /* @__PURE__ */ React10.createElement(Text10, { color: "gray" }, escPressed ? " Press ESC again to cancel the request." : !isProcessing ? ` Type /cmd or message... (${terminalEnv.shortcut} for newline)` : " You can send a prompt to steer the agent.")), /* @__PURE__ */ React10.createElement(
3627
3690
  MultilineInput,
3628
3691
  {
3692
+ focus: !isTerminalFocused,
3629
3693
  value: input,
3630
3694
  onChange: (val) => {
3631
3695
  const cleanVal = val.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\\\s*\n/g, "\n");
3632
3696
  setInput(cleanVal);
3633
3697
  },
3634
- placeholder: escPressed ? " Press ESC again to cancel the request." : ` Type /cmd or message... (${terminalEnv.shortcut} for newline)`,
3635
3698
  onSubmit: handleSubmit,
3636
3699
  maxRows: 3,
3637
3700
  keyBindings: {
@@ -3649,7 +3712,7 @@ Selection: ${val}`,
3649
3712
  showFullThinking,
3650
3713
  columns: stdout?.columns || 80
3651
3714
  }
3652
- ), activeCommand && /* @__PURE__ */ React10.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(TerminalBox, { command: activeCommand, output: execOutput }))), isInitializing ? /* @__PURE__ */ React10.createElement(Box10, { borderStyle: "double", borderColor: "magenta", padding: 1, flexShrink: 0 }, /* @__PURE__ */ React10.createElement(Text10, { color: "magenta" }, "\u{1F30A} Starting Flux Flow...")) : !apiKey ? /* @__PURE__ */ React10.createElement(Box10, { borderStyle: "bold", borderColor: "yellow", padding: 1, flexDirection: "column", flexShrink: 0 }, /* @__PURE__ */ React10.createElement(Text10, { color: "yellow", bold: true }, "\u{1F511} API KEY REQUIRED"), /* @__PURE__ */ React10.createElement(Text10, null, "Please enter your Gemini API Key to initialize the agent's brain."), /* @__PURE__ */ React10.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text10, { color: "cyan" }, "\u276F "), /* @__PURE__ */ React10.createElement(
3715
+ ), activeCommand && /* @__PURE__ */ React10.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(TerminalBox, { command: activeCommand, output: execOutput, isFocused: isTerminalFocused }))), isInitializing ? /* @__PURE__ */ React10.createElement(Box10, { borderStyle: "double", borderColor: "magenta", padding: 1, flexShrink: 0 }, /* @__PURE__ */ React10.createElement(Text10, { color: "magenta" }, "\u{1F30A} Starting Flux Flow...")) : !apiKey ? /* @__PURE__ */ React10.createElement(Box10, { borderStyle: "bold", borderColor: "yellow", padding: 1, flexDirection: "column", flexShrink: 0 }, /* @__PURE__ */ React10.createElement(Text10, { color: "yellow", bold: true }, "\u{1F511} API KEY REQUIRED"), /* @__PURE__ */ React10.createElement(Text10, null, "Please enter your Gemini API Key to initialize the agent's brain."), /* @__PURE__ */ React10.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text10, { color: "cyan" }, "\u276F "), /* @__PURE__ */ React10.createElement(
3653
3716
  TextInput3,
3654
3717
  {
3655
3718
  value: tempKey,
@@ -3715,9 +3778,10 @@ var init_app = __esm({
3715
3778
  init_arg_parser();
3716
3779
  init_paths();
3717
3780
  init_terminal();
3781
+ init_exec_command();
3718
3782
  SESSION_START_TIME = Date.now();
3719
3783
  CHANGELOG_URL = "https://fluxflow-cli.onrender.com/changelog.html";
3720
- versionFluxflow = "1.3.6";
3784
+ versionFluxflow = "1.4.1";
3721
3785
  updatedOn = "2026-04-29";
3722
3786
  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 (turn: finish) 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(
3723
3787
  CommandMenu,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxflow-cli",
3
- "version": "1.3.6",
3
+ "version": "1.4.1",
4
4
  "description": "A high-fidelity agentic terminal assistant for the Flux Era.",
5
5
  "keywords": [
6
6
  "ai",