fluxflow-cli 1.0.7 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ARCHITECTURE.md CHANGED
@@ -25,7 +25,9 @@ The execution flow of a single user prompt follows this loop:
25
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
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
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. The loop also features built-in retry logic (with exponential backoff) for handling transient API errors like 429s or 503s.
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.
29
31
 
30
32
  ## The Dual-Model System
31
33
 
package/README.md CHANGED
@@ -40,6 +40,7 @@ Security isn't an afterthought; it's a boundary.
40
40
  - **External Path Hardlock**: Restricts the agent to your Current Working Directory (CWD) unless you explicitly unlock it.
41
41
  - **Human-in-the-Loop (HITL)**: Every file write and terminal command requires your high-fidelity approval.
42
42
  - **XOR Vaulting**: All local session histories, memories, and API keys are obfuscated and encrypted at rest.
43
+ - **Adaptive Failover**: Automatic multi-stage retry logic with high-concurrency fallback model switching (Gemini 3.1 Flash Lite) during peak API congestion.
43
44
 
44
45
  ### 🧹 **The Background Janitor**
45
46
  While you move at high speed, the Janitor follows behind—refining session titles, compressing data, and ensuring your context window remains at absolute peak performance.
@@ -50,6 +51,7 @@ While you move at high speed, the Janitor follows behind—refining session titl
50
51
  - **Deep File-System Interaction**: Edit, move, and refactor code across multiple files with atomic precision.
51
52
  - **Real-Time Web Intelligence**: Autonomous web-searching via DuckDuckGo for live news and technical research.
52
53
  - **Autonomous Project Alignment**: Automatically detects and adheres to project-specific instructions in `Agent.md`, `Skills.md`, and `Fluxflow.md` for high-fidelity alignment with your coding standards and custom workflows.
54
+ - **High-Reliability Fallback**: Automatic failover to a lighter, high-concurrency model during peak traffic to ensure zero session loss.
53
55
  - **Persistent Memory**: The agent learns from your preferences and project requirements across sessions.
54
56
 
55
57
  ---
package/dist/fluxflow.js CHANGED
@@ -1284,8 +1284,8 @@ var getAIStream = async function* (modelName, history, settings, steeringCallbac
1284
1284
  USER_PROMPT: ${agentText}`.trim();
1285
1285
  modifiedHistory.push({ role: "user", text: firstUserMsg });
1286
1286
  let lastUsage = null;
1287
- const MAX_LOOPS = mode === "Flux" ? 50 : 5;
1288
- const MAX_RETRIES = 3;
1287
+ const MAX_LOOPS = mode === "Flux" ? 50 : 7;
1288
+ const MAX_RETRIES = 7;
1289
1289
  yield { type: "status", content: "Working..." };
1290
1290
  TERMINATION_SIGNAL = false;
1291
1291
  let fullAgentResponse = "";
@@ -1314,8 +1314,15 @@ USER_PROMPT: ${agentText}`.trim();
1314
1314
  if (!await checkQuota("agent", settings)) {
1315
1315
  throw new Error("Error: Daily Quota Exausted for Agent");
1316
1316
  }
1317
+ let targetModel = modelName;
1318
+ if (retryCount >= 5) {
1319
+ targetModel = "gemini-3.1-flash-lite-preview";
1320
+ yield { type: "model_update", content: "Trying with fallback model" };
1321
+ } else if (retryCount > 0) {
1322
+ yield { type: "model_update", content: null };
1323
+ }
1317
1324
  stream = await client.models.generateContentStream({
1318
- model: modelName,
1325
+ model: targetModel,
1319
1326
  contents,
1320
1327
  config: {
1321
1328
  temperature: mode === "Flux" ? 0.9 : 1.3,
@@ -1326,31 +1333,39 @@ USER_PROMPT: ${agentText}`.trim();
1326
1333
  }
1327
1334
  });
1328
1335
  success = true;
1336
+ yield { type: "model_update", content: null };
1329
1337
  } catch (err) {
1330
- const errMsg = err.message || String(err);
1338
+ const errMsg = err.status || err.error && err.error.message || String(err);
1331
1339
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
1332
1340
  const agentErrDir = path13.join(LOGS_DIR, "agent");
1333
- if (!fs12.existsSync(agentErrDir)) {
1334
- fs12.mkdirSync(agentErrDir, { recursive: true });
1335
- }
1341
+ if (!fs12.existsSync(agentErrDir)) fs12.mkdirSync(agentErrDir, { recursive: true });
1336
1342
  fs12.appendFileSync(path13.join(agentErrDir, "error.log"), `ERROR [${date}]: ${errMsg}
1337
1343
  `);
1338
- const isRetryable = errMsg.includes("429") || errMsg.includes("503") || errMsg.includes("overloaded") || errMsg.includes("deadline");
1339
- if (isRetryable && retryCount < MAX_RETRIES) {
1344
+ if (retryCount < MAX_RETRIES) {
1340
1345
  retryCount++;
1346
+ const waitTime = Math.floor(Math.random() * (2e3 - 800 + 1)) + 800;
1341
1347
  yield { type: "status", content: `Retrying (${retryCount}/${MAX_RETRIES})...` };
1342
- await new Promise((resolve) => setTimeout(resolve, 2e3 * retryCount));
1348
+ await new Promise((resolve) => setTimeout(resolve, waitTime));
1343
1349
  } else {
1344
1350
  throw new Error(`Model cannot be reached: ${errMsg}`);
1345
1351
  }
1346
1352
  }
1347
1353
  }
1348
1354
  let turnText = "";
1355
+ let lastToolSniffed = null;
1349
1356
  for await (const chunk of stream) {
1350
1357
  if (TERMINATION_SIGNAL) break;
1351
1358
  if (chunk.text) {
1352
1359
  turnText += chunk.text;
1353
1360
  yield { type: "text", content: chunk.text };
1361
+ if (turnText.includes("tool:functions.")) {
1362
+ const parts = turnText.split("tool:functions.");
1363
+ const potentialTool = parts[parts.length - 1].split("(")[0].trim();
1364
+ if (potentialTool && /^[a-z_]+$/.test(potentialTool) && potentialTool !== lastToolSniffed) {
1365
+ lastToolSniffed = potentialTool;
1366
+ yield { type: "status", content: `Working (${potentialTool})...` };
1367
+ }
1368
+ }
1354
1369
  }
1355
1370
  if (chunk.usageMetadata) {
1356
1371
  lastUsage = chunk.usageMetadata;
@@ -1510,6 +1525,7 @@ ${boxBottom}
1510
1525
  yield { type: "memory_updated" };
1511
1526
  }
1512
1527
  }
1528
+ yield { type: "status", content: "Working..." };
1513
1529
  }
1514
1530
  const cleanedTurnText = turnText.replace(/<think>[\s\S]*?<\/think>/g, "").replace(/\[?\s*(turn\s*:)?\s*(continue|finish)\s*\]?/gi, "").trim();
1515
1531
  if (hasFinish || !shouldContinue && toolResults.length === 0) {
@@ -1831,6 +1847,7 @@ function App() {
1831
1847
  const [escTimer, setEscTimer] = useState4(null);
1832
1848
  const [queuedPrompt, setQueuedPrompt] = useState4(null);
1833
1849
  const [resolutionData, setResolutionData] = useState4(null);
1850
+ const [tempModelOverride, setTempModelOverride] = useState4(null);
1834
1851
  const [messages, setMessages] = useState4([
1835
1852
  { id: "welcome", role: "system", text: FLUX_LOGO + "\n\n\u{1F30A}\u26A1 Welcome to Flux Flow! Type /help for commands.\n" }
1836
1853
  ]);
@@ -2201,6 +2218,10 @@ OUTPUT: ${execOutputRef.current}`;
2201
2218
  setStatusText(packet.content);
2202
2219
  continue;
2203
2220
  }
2221
+ if (packet.type === "model_update") {
2222
+ setTempModelOverride(packet.content);
2223
+ continue;
2224
+ }
2204
2225
  if (packet.type === "turn_reset") {
2205
2226
  currentThinkId = null;
2206
2227
  currentAgentId = null;
@@ -2371,7 +2392,7 @@ OUTPUT: ${execOutputRef.current}`;
2371
2392
  CommandMenu,
2372
2393
  {
2373
2394
  title: "\u{1F916} Select AI Model",
2374
- items: [{ label: "Gemma 4 31B (Default)", value: "gemma-4-31b-it" }, { label: "Gemini 3.1 Pro (Req. paid API Key)", value: "gemini-3.1-pro-preview" }, { label: "Gemini 3 Flash", value: "gemini-3-flash-preview" }, { label: "Gemini 3.1 Flash Lite", value: "gemini-3.1-flash-lite" }, { label: "Cancel", value: "Cancel" }],
2395
+ items: [{ label: "Gemma 4 31B (Default)", value: "gemma-4-31b-it" }, { label: "Gemini 3.1 Pro (Req. paid API Key)", value: "gemini-3.1-pro-preview" }, { label: "Gemini 3 Flash", value: "gemini-3-flash-preview" }, { label: "Gemini 3.1 Flash Lite", value: "gemini-3.1-flash-lite-preview" }, { label: "Cancel", value: "Cancel" }],
2375
2396
  onSelect: (item) => {
2376
2397
  if (item.value !== "Cancel") setActiveModel(item.value);
2377
2398
  setActiveView("chat");
@@ -2737,7 +2758,7 @@ OUTPUT: ${execOutputRef.current}`;
2737
2758
  return acc + Math.max(1, Math.ceil(line.length / wrapWidth));
2738
2759
  }, 0);
2739
2760
  const maxLines = Math.max(1, wrappedLines);
2740
- return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", marginTop: 1, flexShrink: 0, width: "100%" }, statusText && /* @__PURE__ */ React8.createElement(Box8, { paddingX: 1, marginBottom: 0 }, /* @__PURE__ */ React8.createElement(Text8, { color: "magenta", italic: true }, "\u23F3 ", statusText)), suggestions.length > 0 && /* @__PURE__ */ React8.createElement(Box8, { paddingX: 1, marginBottom: 0 }, /* @__PURE__ */ React8.createElement(Text8, { color: "gray" }, "\u{1F4A1} Suggestions: "), suggestions.map((s, i) => /* @__PURE__ */ React8.createElement(Text8, { key: s, color: "yellow", bold: i === 0 }, " ", s, " "))), /* @__PURE__ */ React8.createElement(Box8, { backgroundColor: "#333333", paddingX: 1, paddingY: 1, width: "100%" }, /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", width: "100%" }, maxLines > 3 ? /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", width: "100%", paddingY: 0 }, /* @__PURE__ */ React8.createElement(Text8, { color: "gray", dimColor: true }, "[\u{1F4E6} ", maxLines, " lines of text in buffer - Full content will be sent]"), /* @__PURE__ */ React8.createElement(
2761
+ return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", marginTop: 1, flexShrink: 0, width: "100%" }, /* @__PURE__ */ React8.createElement(Box8, { paddingX: 1, marginBottom: 0, justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React8.createElement(Box8, null, statusText && /* @__PURE__ */ React8.createElement(Text8, { color: "magenta", italic: true }, "\u23F3 ", statusText)), /* @__PURE__ */ React8.createElement(Text8, { color: "gray", dimColor: true }, "(", tempModelOverride || activeModel, ")")), suggestions.length > 0 && /* @__PURE__ */ React8.createElement(Box8, { paddingX: 1, marginBottom: 0 }, /* @__PURE__ */ React8.createElement(Text8, { color: "gray" }, "\u{1F4A1} Suggestions: "), suggestions.map((s, i) => /* @__PURE__ */ React8.createElement(Text8, { key: s, color: "yellow", bold: i === 0 }, " ", s, " "))), /* @__PURE__ */ React8.createElement(Box8, { backgroundColor: "#333333", paddingX: 1, paddingY: 1, width: "100%" }, /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", width: "100%" }, maxLines > 3 ? /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", width: "100%", paddingY: 0 }, /* @__PURE__ */ React8.createElement(Text8, { color: "gray", dimColor: true }, "[\u{1F4E6} ", maxLines, " lines of text in buffer - Full content will be sent]"), /* @__PURE__ */ React8.createElement(
2741
2762
  Box8,
2742
2763
  {
2743
2764
  flexDirection: "row",
@@ -2747,7 +2768,7 @@ OUTPUT: ${execOutputRef.current}`;
2747
2768
  alignItems: "flex-end"
2748
2769
  },
2749
2770
  /* @__PURE__ */ React8.createElement(Box8, { flexShrink: 0, width: 3 }, /* @__PURE__ */ React8.createElement(Text8, { color: "yellow" }, "\u276F ")),
2750
- /* @__PURE__ */ React8.createElement(Box8, { flexGrow: 1 }, /* @__PURE__ */ React8.createElement(
2771
+ /* @__PURE__ */ React8.createElement(Box8, { flexGrow: 1 }, /* @__PURE__ */ React8.createElement(Box8, { flexGrow: 1, position: "relative" }, input.split("\n").pop() === "" && !isProcessing && /* @__PURE__ */ React8.createElement(Box8, { position: "absolute", paddingLeft: 0 }, /* @__PURE__ */ React8.createElement(Text8, { color: "gray", dimColor: true }, "Type your message...")), /* @__PURE__ */ React8.createElement(
2751
2772
  MultilineInput,
2752
2773
  {
2753
2774
  value: input.split("\n").pop() || "",
@@ -2758,14 +2779,13 @@ OUTPUT: ${execOutputRef.current}`;
2758
2779
  setInput(lines.join("\n"));
2759
2780
  },
2760
2781
  onSubmit: handleSubmit,
2761
- placeholder: escPressed ? "Press ESC again to cancel the request." : isProcessing ? "Flux Flow is thinking..." : "Type your message or /command...",
2762
2782
  keyBindings: {
2763
2783
  submit: (key) => key.return && !key.shift && !key.ctrl,
2764
2784
  newline: (key) => key.return && key.shift || key.return && key.ctrl
2765
2785
  }
2766
2786
  }
2767
- ))
2768
- )) : /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "row", width: "100%", paddingY: 0 }, /* @__PURE__ */ React8.createElement(Box8, { flexShrink: 0, width: 3 }, /* @__PURE__ */ React8.createElement(Text8, { color: "yellow" }, "\u276F ")), /* @__PURE__ */ React8.createElement(Box8, { flexGrow: 1 }, /* @__PURE__ */ React8.createElement(
2787
+ )))
2788
+ )) : /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "row", width: "100%", paddingY: 0 }, /* @__PURE__ */ React8.createElement(Box8, { flexShrink: 0, width: 3 }, /* @__PURE__ */ React8.createElement(Text8, { color: "yellow" }, "\u276F ")), /* @__PURE__ */ React8.createElement(Box8, { flexGrow: 1 }, /* @__PURE__ */ React8.createElement(Box8, { flexGrow: 1, position: "relative" }, input === "" && !isProcessing && /* @__PURE__ */ React8.createElement(Box8, { position: "absolute", paddingLeft: 0 }, /* @__PURE__ */ React8.createElement(Text8, { color: "gray", dimColor: true }, escPressed ? " Press ESC again to cancel the request." : " Type your message or /command...")), /* @__PURE__ */ React8.createElement(
2769
2789
  MultilineInput,
2770
2790
  {
2771
2791
  value: input,
@@ -2774,14 +2794,13 @@ OUTPUT: ${execOutputRef.current}`;
2774
2794
  setInput(cleanVal);
2775
2795
  },
2776
2796
  onSubmit: handleSubmit,
2777
- placeholder: escPressed ? "Press ESC again to cancel the request." : isProcessing ? "Flux Flow is thinking..." : "Type your message or /command...",
2778
2797
  maxRows: 3,
2779
2798
  keyBindings: {
2780
2799
  submit: (key) => key.return && !key.shift && !key.ctrl,
2781
2800
  newline: (key) => key.return && key.shift || key.return && key.ctrl
2782
2801
  }
2783
2802
  }
2784
- ))))));
2803
+ )))))));
2785
2804
  }
2786
2805
  };
2787
2806
  return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", width: "100%" }, /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column" }, messages.slice(0, completedIndex).map((msg, idx) => /* @__PURE__ */ React8.createElement(ChatLayout, { key: msg.id || idx, messages: [msg], showFullThinking }))), /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", padding: 1, width: "100%" }, activeView === "chat" && /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", width: "100%" }, /* @__PURE__ */ React8.createElement(ChatLayout, { messages: messages.slice(completedIndex), showFullThinking }), activeCommand && /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(TerminalBox, { command: activeCommand, output: execOutput }))), isInitializing ? /* @__PURE__ */ React8.createElement(Box8, { borderStyle: "double", borderColor: "magenta", padding: 1, flexShrink: 0 }, /* @__PURE__ */ React8.createElement(Text8, { color: "magenta" }, "\u{1F30A} Starting Flux Flow...")) : !apiKey ? /* @__PURE__ */ React8.createElement(Box8, { borderStyle: "bold", borderColor: "yellow", padding: 1, flexDirection: "column", flexShrink: 0 }, /* @__PURE__ */ React8.createElement(Text8, { color: "yellow", bold: true }, "\u{1F511} API KEY REQUIRED"), /* @__PURE__ */ React8.createElement(Text8, null, "Please enter your Gemini API Key to initialize the agent's brain."), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, { color: "cyan" }, "\u276F "), /* @__PURE__ */ React8.createElement(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxflow-cli",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "A high-fidelity agentic terminal assistant for the Flux Era.",
5
5
  "keywords": [
6
6
  "ai",