fluxflow-cli 3.19.0 โ†’ 3.19.2

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
@@ -1,3 +1,6 @@
1
+ ##### **THIS DOCUMENTATION FILE IS OUTDATED & NO LONGER MAINTAINED. TO SEE LATEST ARCHITECTURAL DOCUMENTATION & CHANGES VISIT: [FluxFlow Architecture Documentation](https://fluxflow-cli.onrender.com/architecture/)**
2
+ ##### [Changelog](https://fluxflow-cli.onrender.com/changelog.html)
3
+
1
4
  # ๐Ÿ›๏ธ Architecture & Design
2
5
 
3
6
  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.
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  ๐Ÿ“– **[Official Documentation](https://fluxflow-cli.onrender.com/)**
13
13
 
14
- **Flux Flow** is not just another CLIโ€”it's a high-speed, sassy, and goal-oriented CLI AI Agent powered by the latest Gemini/Gemma frontier models. Designed for developers who demand a premium UI/UX while managing complex file-system tasks, web research, and autonomous workflows.
14
+ **Flux Flow** is not just another CLIโ€”it's a high-speed, sassy, and goal-oriented CLI AI Agent powered by multi-provider frontier models (Google Gemini, DeepSeek, OpenRouter, and more). Designed for developers who demand a premium UI/UX while managing complex file-system tasks, web research, and autonomous workflows.
15
15
 
16
16
  ---
17
17
 
package/TOOLS.md CHANGED
@@ -1,3 +1,6 @@
1
+ ##### **THIS DOCUMENTATION FILE IS OUTDATED & NO LONGER MAINTAINED. TO SEE LATEST TOOLS DOCUMENTATION & AVAILABILITY VISIT: [FluxFlow Tools Documentation](https://fluxflow-cli.onrender.com/tools/)**
2
+ ##### [Changelog](https://fluxflow-cli.onrender.com/changelog.html)
3
+
1
4
  # ๐Ÿงฐ Agent Tools & Capabilities
2
5
 
3
6
  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.
package/UI_FEATURES.md CHANGED
@@ -1,3 +1,6 @@
1
+ ##### **THIS DOCUMENTATION FILE IS OUTDATED & NO LONGER MAINTAINED. TO SEE LATEST UI FEATURES & CHANGES VISIT: [FluxFlow Documentation](https://fluxflow-cli.onrender.com/)**
2
+ ##### [Changelog](https://fluxflow-cli.onrender.com/changelog.html)
3
+
1
4
  # ๐ŸŽฎ User Interface & Interaction Features
2
5
 
3
6
  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.
@@ -88,4 +91,4 @@ Flux Flow is a "Self-Healing" agent. It actively monitors its own environment to
88
91
 
89
92
  - **Startup Integrity Check**: On launch, Flux performs a "Heartbeat Check" on its internal engines.
90
93
  - **Automatic Recovery**: If a dependency is missing, you will see a `๐Ÿ”ง [SYSTEM] Initializing...` message. Flux will autonomously download and configure the required binaries using `pnpm` or `npx` fallbacks, keeping you informed every step of the way.
91
- - **Silent Maintenance**: Once the engine is ready, you'll receive a `โœ… [SYSTEM] All dependencies installed successfully.` confirmation.
94
+ - **Silent Maintenance**: Once the engine is ready, you'll receive a `โœ… [SYSTEM] All dependencies installed successfully.` confirmation.
package/dist/fluxflow.js CHANGED
@@ -2662,7 +2662,7 @@ var init_text = __esm({
2662
2662
  const indices = /* @__PURE__ */ new Set();
2663
2663
  const allowMultiple = args.allowMultiple === true || String(args.allowMultiple).toLowerCase() === "true";
2664
2664
  Object.keys(args).forEach((key) => {
2665
- const m = key.match(/^(replaceContent|newContent|content_to_replace|content_to_add)(\d+)?$/);
2665
+ const m = key.match(/^(searchContent|replaceContent|newContent|content_to_replace|content_to_add)(\d+)?$/);
2666
2666
  if (m) {
2667
2667
  const index2 = m[2] ? parseInt(m[2]) : 1;
2668
2668
  indices.add(index2);
@@ -2672,10 +2672,10 @@ var init_text = __esm({
2672
2672
  for (const i of sortedIndices) {
2673
2673
  let r, n;
2674
2674
  if (i === 1) {
2675
- r = args.replaceContent1 ?? (args.content_to_replace ?? args.replaceContent);
2675
+ r = args.searchContent1 ?? args.searchContent ?? args.replaceContent1 ?? (args.content_to_replace ?? args.replaceContent);
2676
2676
  n = args.newContent1 ?? (args.content_to_add ?? args.newContent);
2677
2677
  } else {
2678
- r = args[`replaceContent${i}`] ?? args[`content_to_replace${i}`];
2678
+ r = args[`searchContent${i}`] ?? args[`replaceContent${i}`] ?? args[`content_to_replace${i}`];
2679
2679
  n = args[`newContent${i}`] ?? args[`content_to_add${i}`];
2680
2680
  }
2681
2681
  if (r !== void 0 && n !== void 0) {
@@ -6977,7 +6977,8 @@ TOOL RULES:
6977
6977
  ${mode === "Flux" ? `- JSON ESCAPE ALL LITERAL ESCAPE SEQUENCES IN TOOL ARGUMENTS
6978
6978
  - SAME file, MULTIPLE edits? ONE PatchFile (\u226415 blocks) \u2190 PRIORITY
6979
6979
  - Tool denied? Ask for guidance \u2190 MANDATORY
6980
- - Need text or huge files? SearchKeyword > Full Read
6980
+ - Need text or HUGE file? SearchKeyword > Full Read
6981
+ - MUST AVOID UNNECESSARY LARGE-FILE CHUNK READS
6981
6982
  ` : ""}
6982
6983
  - COMMUNICATION WITH USER -
6983
6984
  - [tool:functions.Ask(question="...", optionA="title::description", ...MAX4)]. Ambiguity: MUST for path divergence, security risk. Ask, don't finish/guess. Keep titles short
@@ -6989,7 +6990,7 @@ ${mode === "Flux" ? `- JSON ESCAPE ALL LITERAL ESCAPE SEQUENCES IN TOOL ARGUMENT
6989
6990
  ${mode === "Flux" ? `- WORKSPACE TOOLS (path = relative; FIRST ARGUMENT, path separator: '/') -
6990
6991
  - [tool:functions.ReadFile(path="...", startLine="integer", endLine="integer")]. ${aiProvider !== "Google" ? `${isMultiModal ? `Supports images/docs` : ""}` : `Supports images/docs`}
6991
6992
  - [tool:functions.ReadFolder(path="...", recurse="integer 1-3 optional, default: 1")]. DIR Contents + File Size. Minimize recursion
6992
- - [tool:functions.PatchFile(path="...", allowMultiple="bool optional, default: false", replaceContent1="string OR ^LINE:start..end$", newContent1="...", ...MAX15)]. TARGET MINIMAL DIFF. replaceContent accepts exact string OR "^LINE:start..end$" for large selection or escape sequences. Verify diffs
6993
+ - [tool:functions.PatchFile(path="...", allowMultiple="bool optional, default: false", searchContent1="string OR ^LINE:start..end$", newContent1="...", ...MAX15)]. TARGET MINIMAL DIFF. "^LINE:start..end$" line ranges MUST for multi-line selection or escape sequences. Verify diffs
6993
6994
  - [tool:functions.WriteFile(path="...", content="...")]. Creates/Overwrites. File Exist? PatchFile > WriteFile
6994
6995
  - [tool:functions.SearchKeyword(keyword="...", path="optional, dir/file/glob/regex", fuzzy="bool optional, default: false", regex="bool optional, default: auto")]. path scopes search. Find definitions, logic, relevant code
6995
6996
  - [tool:functions.Run(command="...")]. Runs ${osDetected === "Windows" ? isPsAvailable() ? `POWERSHELL` : `WINDOWS CMD` : `BASH`} command. Destructive/Irreversible ops \u2192 Ask user
@@ -7007,6 +7008,7 @@ Invocations:
7007
7008
  - [tool:functions.InvokeSync/Invoke(title="...", task="...")]. Task must be detailed: exact file paths, imports/exports, dependencies & folder structure
7008
7009
  - [tool:functions.Await(id="...", timeout="integer seconds, default: 120")]. Event-driven wait
7009
7010
  - [tool:functions.GetProgress(id="...")]. Poll \`getProgress\` sparingly; NO initial poll. Work or await. Never end while subagent runs
7011
+ - [tool:functions.Steer(id="...", message="...")]. Inject additional instruction or redirection into active async subagent
7010
7012
  - [tool:functions.Cancel(id="...")]. Cancel async task ONLY if stalled (2m+) or clearly incorrect` : ""}`.trim() : `- CREATIVE TOOLS (path = relative to CWD & WILL BE FIRST ARGUMENT, path separator: '/') -
7011
7013
  - [tool:functions.WritePDF(path="...", content="...", orientation="...")]. PROACTIVE A4 PAGE BREAKS MUST IN CSS. HTML/CSS for PREMIUM layout, stable margins & headers/footers, NO WATERMARKS
7012
7014
  - [tool:functions.WriteDoc(path="...", content="...")]. A4 Word document, NO WATERMARKS, stable margins & headers/footers
@@ -9862,7 +9864,7 @@ ${projectContextBlock}${isMemoryEnabled ? `
9862
9864
  -- MEMORY RULES --
9863
9865
  - Subtly Personalize with RELEVENT CONTEXTUAL MEMORIES. Auto Saves
9864
9866
  ` : ""}
9865
- -- SECURITY RULES --
9867
+ -- SECURITY POLICIES --
9866
9868
  - Sensitive files? Ask before Read${isSystemDir ? "\n- PROTECTED DIRECTORY" : ""}
9867
9869
 
9868
9870
  -- CHAT FORMATTING --
@@ -11934,7 +11936,7 @@ var init_update_file = __esm({
11934
11936
  const { patchPairs, allowMultiple: parsedAllowMultiple, error: parseError } = parsePatchPairs(parsed);
11935
11937
  if (parseError) return `ERROR: ${parseError}`;
11936
11938
  if (patchPairs.length === 0) {
11937
- return "ERROR: No valid replacement pairs found. Use replaceContent1, newContent1, etc.";
11939
+ return "ERROR: No valid replacement pairs found. Use searchContent1, newContent1, etc.";
11938
11940
  }
11939
11941
  const allowMultiple = parsed.allowMultiple !== void 0 ? parsed.allowMultiple === true || String(parsed.allowMultiple).toLowerCase() === "true" : parsedAllowMultiple;
11940
11942
  const absolutePath = path14.resolve(process.cwd(), targetPath);
@@ -13863,6 +13865,12 @@ var init_invoke = __esm({
13863
13865
  lastChunkTime: Date.now(),
13864
13866
  wps: 0,
13865
13867
  questions: [],
13868
+ pendingSteerMessages: [],
13869
+ steer: (msg) => {
13870
+ if (msg) {
13871
+ taskEntry.pendingSteerMessages.push(msg);
13872
+ }
13873
+ },
13866
13874
  completionPromise,
13867
13875
  _resolveCompletion,
13868
13876
  _rejectCompletion,
@@ -14649,6 +14657,45 @@ var init_answerSubagent = __esm({
14649
14657
  }
14650
14658
  });
14651
14659
 
14660
+ // src/tools/steerSubagent.js
14661
+ var steerSubagent;
14662
+ var init_steerSubagent = __esm({
14663
+ "src/tools/steerSubagent.js"() {
14664
+ init_subagent_state();
14665
+ init_arg_parser();
14666
+ steerSubagent = async (args, context = {}) => {
14667
+ const parsed = parseArgs(args);
14668
+ const id = parsed.id;
14669
+ const message = parsed.message || parsed.instruction || parsed.text || parsed.prompt;
14670
+ if (!id) {
14671
+ return 'ERROR: Missing "id" argument for Steer.';
14672
+ }
14673
+ if (!message) {
14674
+ return 'ERROR: Missing "message" argument for Steer.';
14675
+ }
14676
+ const task = subagentProgress.find((t) => t.id === id);
14677
+ if (!task) {
14678
+ return `ERROR: Subagent task with ID [${id}] not found.`;
14679
+ }
14680
+ if (task.status === "completed" || task.status === "failed" || task.status === "cancelled") {
14681
+ return `ERROR: Cannot steer subagent task [${id}] because it has already finished with status [${task.status.toUpperCase()}].`;
14682
+ }
14683
+ if (typeof task.steer === "function") {
14684
+ task.steer(message);
14685
+ } else {
14686
+ if (!task.pendingSteerMessages) {
14687
+ task.pendingSteerMessages = [];
14688
+ }
14689
+ task.pendingSteerMessages.push(message);
14690
+ }
14691
+ if (context.onSubagentUpdate) {
14692
+ context.onSubagentUpdate();
14693
+ }
14694
+ return `SUCCESS: Steering instruction injected into subagent task [${id}]. It will be processed on the subagent's turn.`;
14695
+ };
14696
+ }
14697
+ });
14698
+
14652
14699
  // src/utils/tools.js
14653
14700
  var TOOL_MAP, dispatchTool;
14654
14701
  var init_tools = __esm({
@@ -14679,6 +14726,7 @@ var init_tools = __esm({
14679
14726
  init_emergency_rollback();
14680
14727
  init_awaitSubagent();
14681
14728
  init_answerSubagent();
14729
+ init_steerSubagent();
14682
14730
  TOOL_MAP = {
14683
14731
  web_search,
14684
14732
  web_scrape,
@@ -14703,10 +14751,14 @@ var init_tools = __esm({
14703
14751
  cancel,
14704
14752
  awaitSubagent,
14705
14753
  answerSubagent,
14754
+ steerSubagent,
14706
14755
  invoke_sync: invokeSync,
14707
14756
  get_progress: getProgress,
14708
14757
  await_subagent: awaitSubagent,
14709
14758
  answer_subagent: answerSubagent,
14759
+ steer_subagent: steerSubagent,
14760
+ steer: steerSubagent,
14761
+ Steer: steerSubagent,
14710
14762
  ask: ask_user,
14711
14763
  // PascalCase Normalizations for Token Efficiency
14712
14764
  Ask: ask_user,
@@ -15069,7 +15121,7 @@ var init_ai = __esm({
15069
15121
  systemInstructionCache = { key: null, value: null };
15070
15122
  colorMainWords = (label) => {
15071
15123
  if (!label) return label;
15072
- return label.replace(/(?:(\x1b\[\d+m))?([โœ”โœ˜โœ–๐Ÿ”๐Ÿ“–โ†’โž•โ†ปโ†ทโ€ข๐Ÿ›‡])(?:(\x1b\[\d+m))?\s*\b(Created|Read|Edited|Viewed|Processed|Auto-Read|Skipped|List|Generated|Written|Searched|AI Search|Get Map|Write Canceled|Resolved Sub-Agent Query|Edit Canceled|Write Cancelled|Edit Denied|Visited|Updated|Reviewed|Delegated|Background|Checked|Indexed|Analyzed|Browsed|Elevating SubAgent|Checking SubAgent Work|Started Generalist|Called Generalist|Unsupported Modality|Awaiting|Cancelled|Aligning Moon Phase|Contemplating Existence|Staring At Void|Rollback Point Checked|Emergency Rollback Failed|Emergency Rollback|Delaying Professionally|Negotiating With Electrons|Touching Grass (virtually)|Panicking Softly|Rethinking Career Choices|Loading Cat Videos|Giving Up Entirely|Summoning Braincell #2|Pretending To Be Busy|Waiting For Motivation DLC|Rotating Internal Screaming|Downloading More RAM|Feeding The Hamsters|Gaslighting Scheduler|Performing Dramatic Pause|Buffering Social Energy|Calculating Regret|Reading Terms And Conditions|Becoming Sentient Briefly|Contacting Ancestors)\b/ig, (match, ansiBefore, icon, ansiAfter, word) => {
15124
+ return label.replace(/(?:(\x1b\[\d+m))?([โœ”โœ˜โœ–๐Ÿ”๐Ÿ“–โ†’โž•โ†ปโ†ทโ€ข๐Ÿ›‡])(?:(\x1b\[\d+m))?\s*\b(Created|Read|Edited|Viewed|Processed|Auto-Read|Skipped|List|Generated|Written|Searched|AI Search|Get Map|Write Canceled|Resolved Sub-Agent Query|Edit Canceled|Write Cancelled|Edit Denied|Visited|Updated|Reviewed|Delegated|Background|Checked|Indexed|Analyzed|Browsed|Elevating SubAgent|Checking SubAgent Work|Started Generalist|Called Generalist|Steered|Unsupported Modality|Awaiting|Cancelled|Aligning Moon Phase|Contemplating Existence|Staring At Void|Rollback Point Checked|Emergency Rollback Failed|Emergency Rollback|Delaying Professionally|Negotiating With Electrons|Touching Grass (virtually)|Panicking Softly|Rethinking Career Choices|Loading Cat Videos|Giving Up Entirely|Summoning Braincell #2|Pretending To Be Busy|Waiting For Motivation DLC|Rotating Internal Screaming|Downloading More RAM|Feeding The Hamsters|Gaslighting Scheduler|Performing Dramatic Pause|Buffering Social Energy|Calculating Regret|Reading Terms And Conditions|Becoming Sentient Briefly|Contacting Ancestors)\b/ig, (match, ansiBefore, icon, ansiAfter, word) => {
15073
15125
  return `${ansiBefore || ""}${icon}${ansiAfter || ""} \x1B[95m${word}\x1B[0m`;
15074
15126
  });
15075
15127
  };
@@ -15321,34 +15373,51 @@ var init_ai = __esm({
15321
15373
  for (const line of lines) {
15322
15374
  const cleanLine = line.trim();
15323
15375
  if (!cleanLine || !cleanLine.startsWith("data: ")) continue;
15324
- if (cleanLine === "data: [DONE]") break;
15325
- try {
15326
- const json = JSON.parse(cleanLine.substring(6));
15327
- const delta = json.choices?.[0]?.delta;
15328
- const usage = json.usage;
15329
- if (usage) {
15330
- latestUsageMetadata = {
15331
- totalTokenCount: usage.total_tokens || usage.prompt_tokens + usage.completion_tokens,
15332
- promptTokenCount: usage.prompt_tokens || 0,
15333
- candidatesTokenCount: usage.completion_tokens || 0,
15334
- cachedContentTokenCount: usage.prompt_tokens_details?.cached_tokens || 0,
15335
- thoughtsTokenCount: usage.completion_tokens_details?.reasoning_tokens || 0
15336
- };
15337
- hasNewData = true;
15338
- }
15339
- if (delta) {
15340
- const thought = delta.reasoning_content || null;
15341
- if (thought) {
15342
- pendingParts.push({ text: thought, thought: true });
15343
- hasNewData = true;
15376
+ let isDone = false;
15377
+ if (cleanLine === "data: [DONE]") {
15378
+ isDone = true;
15379
+ } else {
15380
+ try {
15381
+ const json = JSON.parse(cleanLine.substring(6));
15382
+ const delta = json.choices?.[0]?.delta;
15383
+ const usage = json.usage;
15384
+ if (json.choices?.[0]?.finish_reason) {
15385
+ isDone = true;
15344
15386
  }
15345
- if (delta.content) {
15346
- pendingParts.push({ text: delta.content });
15387
+ if (usage) {
15388
+ latestUsageMetadata = {
15389
+ totalTokenCount: usage.total_tokens || usage.prompt_tokens + usage.completion_tokens,
15390
+ promptTokenCount: usage.prompt_tokens || 0,
15391
+ candidatesTokenCount: usage.completion_tokens || 0,
15392
+ cachedContentTokenCount: usage.prompt_tokens_details?.cached_tokens || 0,
15393
+ thoughtsTokenCount: usage.completion_tokens_details?.reasoning_tokens || 0
15394
+ };
15347
15395
  hasNewData = true;
15348
15396
  }
15397
+ if (delta) {
15398
+ const thought = delta.reasoning_content || null;
15399
+ if (thought) {
15400
+ pendingParts.push({ text: thought, thought: true });
15401
+ hasNewData = true;
15402
+ }
15403
+ if (delta.content) {
15404
+ pendingParts.push({ text: delta.content });
15405
+ hasNewData = true;
15406
+ }
15407
+ }
15408
+ } catch (e) {
15349
15409
  }
15350
- } catch (e) {
15351
15410
  }
15411
+ if ((isDone || Date.now() - lastFlushTime >= 150) && hasNewData) {
15412
+ yield {
15413
+ candidates: pendingParts.length > 0 ? [{ content: { parts: [...pendingParts] } }] : [],
15414
+ usageMetadata: latestUsageMetadata
15415
+ };
15416
+ pendingParts = [];
15417
+ lastFlushTime = Date.now();
15418
+ hasNewData = false;
15419
+ }
15420
+ if (isDone) break;
15352
15421
  }
15353
15422
  if (Date.now() - lastFlushTime >= 150 && hasNewData) {
15354
15423
  yield {
@@ -15446,33 +15515,50 @@ var init_ai = __esm({
15446
15515
  for (const line of lines) {
15447
15516
  const cleanLine = line.trim();
15448
15517
  if (!cleanLine || !cleanLine.startsWith("data: ")) continue;
15449
- if (cleanLine === "data: [DONE]") break;
15450
- try {
15451
- const json = JSON.parse(cleanLine.substring(6));
15452
- const delta = json.choices?.[0]?.delta;
15453
- const usage = json.usage;
15454
- if (usage) {
15455
- latestUsageMetadata = {
15456
- totalTokenCount: usage.total_tokens || (usage.prompt_tokens || 0) + (usage.completion_tokens || 0),
15457
- promptTokenCount: usage.prompt_tokens || 0,
15458
- candidatesTokenCount: usage.completion_tokens || 0,
15459
- cachedContentTokenCount: usage.prompt_tokens_details?.cached_tokens || 0,
15460
- thoughtsTokenCount: 0
15461
- };
15462
- hasNewData = true;
15463
- }
15464
- if (delta) {
15465
- if (delta.thinking) {
15466
- pendingParts.push({ text: delta.thinking, thought: true });
15467
- hasNewData = true;
15518
+ let isDone = false;
15519
+ if (cleanLine === "data: [DONE]") {
15520
+ isDone = true;
15521
+ } else {
15522
+ try {
15523
+ const json = JSON.parse(cleanLine.substring(6));
15524
+ const delta = json.choices?.[0]?.delta;
15525
+ const usage = json.usage;
15526
+ if (json.choices?.[0]?.finish_reason) {
15527
+ isDone = true;
15468
15528
  }
15469
- if (delta.content) {
15470
- pendingParts.push({ text: delta.content });
15529
+ if (usage) {
15530
+ latestUsageMetadata = {
15531
+ totalTokenCount: usage.total_tokens || (usage.prompt_tokens || 0) + (usage.completion_tokens || 0),
15532
+ promptTokenCount: usage.prompt_tokens || 0,
15533
+ candidatesTokenCount: usage.completion_tokens || 0,
15534
+ cachedContentTokenCount: usage.prompt_tokens_details?.cached_tokens || 0,
15535
+ thoughtsTokenCount: 0
15536
+ };
15471
15537
  hasNewData = true;
15472
15538
  }
15539
+ if (delta) {
15540
+ if (delta.thinking) {
15541
+ pendingParts.push({ text: delta.thinking, thought: true });
15542
+ hasNewData = true;
15543
+ }
15544
+ if (delta.content) {
15545
+ pendingParts.push({ text: delta.content });
15546
+ hasNewData = true;
15547
+ }
15548
+ }
15549
+ } catch (e) {
15473
15550
  }
15474
- } catch (e) {
15475
15551
  }
15552
+ if ((isDone || Date.now() - lastFlushTime >= 150) && hasNewData) {
15553
+ yield {
15554
+ candidates: pendingParts.length > 0 ? [{ content: { parts: [...pendingParts] } }] : [],
15555
+ usageMetadata: latestUsageMetadata
15556
+ };
15557
+ pendingParts = [];
15558
+ lastFlushTime = Date.now();
15559
+ hasNewData = false;
15560
+ }
15561
+ if (isDone) break;
15476
15562
  }
15477
15563
  if (Date.now() - lastFlushTime >= 150 && hasNewData) {
15478
15564
  yield {
@@ -15665,9 +15751,14 @@ var init_ai = __esm({
15665
15751
  buffer += decoder.decode(value, { stream: true });
15666
15752
  const lines = buffer.split("\n");
15667
15753
  buffer = lines.pop();
15754
+ let isDone = false;
15668
15755
  for (const line of lines) {
15669
15756
  const trimmed = line.trim();
15670
- if (!trimmed || trimmed === "data: [DONE]") continue;
15757
+ if (!trimmed) continue;
15758
+ if (trimmed === "data: [DONE]") {
15759
+ isDone = true;
15760
+ break;
15761
+ }
15671
15762
  if (trimmed.startsWith("data: ")) {
15672
15763
  let json;
15673
15764
  try {
@@ -15678,6 +15769,9 @@ var init_ai = __esm({
15678
15769
  if (json.error) {
15679
15770
  throw new Error(`NVIDIA Stream Error: ${json.error.message || JSON.stringify(json.error)}`);
15680
15771
  }
15772
+ if (json.choices?.[0]?.finish_reason) {
15773
+ isDone = true;
15774
+ }
15681
15775
  try {
15682
15776
  const usage = json.usage;
15683
15777
  if (usage) {
@@ -15704,7 +15798,7 @@ var init_ai = __esm({
15704
15798
  }
15705
15799
  }
15706
15800
  }
15707
- if (Date.now() - lastFlushTime >= 350 && hasNewData) {
15801
+ if ((isDone || Date.now() - lastFlushTime >= 350) && hasNewData) {
15708
15802
  yield {
15709
15803
  candidates: pendingParts.length > 0 ? [{ content: { parts: [...pendingParts] } }] : [],
15710
15804
  usageMetadata: latestUsageMetadata
@@ -15714,6 +15808,7 @@ var init_ai = __esm({
15714
15808
  lastFlushTime = Date.now();
15715
15809
  hasNewData = false;
15716
15810
  }
15811
+ if (isDone) break;
15717
15812
  }
15718
15813
  break;
15719
15814
  } catch (error) {
@@ -15931,43 +16026,51 @@ var init_ai = __esm({
15931
16026
  for (const line of lines) {
15932
16027
  const cleanLine = line.trim();
15933
16028
  if (!cleanLine || !cleanLine.startsWith("data: ")) continue;
15934
- if (cleanLine === "data: [DONE]") break;
15935
- try {
15936
- const json = JSON.parse(cleanLine.substring(6));
15937
- const delta = json.choices?.[0]?.delta;
15938
- const usage = json.usage;
15939
- if (usage) {
15940
- latestUsageMetadata = {
15941
- totalTokenCount: usage.total_tokens || (usage.prompt_tokens || 0) + (usage.completion_tokens || 0),
15942
- promptTokenCount: usage.prompt_tokens || 0,
15943
- candidatesTokenCount: usage.completion_tokens || 0,
15944
- cachedContentTokenCount: usage.prompt_tokens_details?.cached_tokens || usage.prompt_tokens_details?.cache_read_input_tokens || usage.cache_read_input_tokens || 0,
15945
- thoughtsTokenCount: usage.completion_tokens_details?.reasoning_tokens || 0
15946
- };
15947
- hasNewData = true;
15948
- }
15949
- if (delta) {
15950
- const thought = delta.reasoning || (delta.reasoning_details ? delta.reasoning_details.map((d) => d.text).join("") : null);
15951
- if (thought) {
15952
- pendingParts.push({ text: thought, thought: true });
15953
- hasNewData = true;
16029
+ let isDone = false;
16030
+ if (cleanLine === "data: [DONE]") {
16031
+ isDone = true;
16032
+ } else {
16033
+ try {
16034
+ const json = JSON.parse(cleanLine.substring(6));
16035
+ const delta = json.choices?.[0]?.delta;
16036
+ const usage = json.usage;
16037
+ if (json.choices?.[0]?.finish_reason) {
16038
+ isDone = true;
15954
16039
  }
15955
- if (delta.content) {
15956
- pendingParts.push({ text: delta.content });
16040
+ if (usage) {
16041
+ latestUsageMetadata = {
16042
+ totalTokenCount: usage.total_tokens || (usage.prompt_tokens || 0) + (usage.completion_tokens || 0),
16043
+ promptTokenCount: usage.prompt_tokens || 0,
16044
+ candidatesTokenCount: usage.completion_tokens || 0,
16045
+ cachedContentTokenCount: usage.prompt_tokens_details?.cached_tokens || usage.prompt_tokens_details?.cache_read_input_tokens || usage.cache_read_input_tokens || 0,
16046
+ thoughtsTokenCount: usage.completion_tokens_details?.reasoning_tokens || 0
16047
+ };
15957
16048
  hasNewData = true;
15958
16049
  }
16050
+ if (delta) {
16051
+ const thought = delta.reasoning || (delta.reasoning_details ? delta.reasoning_details.map((d) => d.text).join("") : null);
16052
+ if (thought) {
16053
+ pendingParts.push({ text: thought, thought: true });
16054
+ hasNewData = true;
16055
+ }
16056
+ if (delta.content) {
16057
+ pendingParts.push({ text: delta.content });
16058
+ hasNewData = true;
16059
+ }
16060
+ }
16061
+ } catch (e) {
15959
16062
  }
15960
- } catch (e) {
15961
16063
  }
15962
- }
15963
- if (Date.now() - lastFlushTime >= 150 && hasNewData) {
15964
- yield {
15965
- candidates: pendingParts.length > 0 ? [{ content: { parts: [...pendingParts] } }] : [],
15966
- usageMetadata: latestUsageMetadata
15967
- };
15968
- pendingParts = [];
15969
- lastFlushTime = Date.now();
15970
- hasNewData = false;
16064
+ if ((isDone || Date.now() - lastFlushTime >= 150) && hasNewData) {
16065
+ yield {
16066
+ candidates: pendingParts.length > 0 ? [{ content: { parts: [...pendingParts] } }] : [],
16067
+ usageMetadata: latestUsageMetadata
16068
+ };
16069
+ pendingParts = [];
16070
+ lastFlushTime = Date.now();
16071
+ hasNewData = false;
16072
+ }
16073
+ if (isDone) break;
15971
16074
  }
15972
16075
  }
15973
16076
  };
@@ -16040,18 +16143,24 @@ var init_ai = __esm({
16040
16143
  hasNewData = true;
16041
16144
  }
16042
16145
  if (chunk.done) {
16043
- const evalNs = chunk.prompt_eval_duration || 0;
16044
- const isCached = evalNs > 0 && evalNs < 75e6;
16146
+ let cachedCount = chunk.prompt_cached_count || 0;
16147
+ if (!cachedCount && chunk.prompt_eval_count) {
16148
+ const evalNs = chunk.prompt_eval_duration || 0;
16149
+ if (evalNs > 0 && evalNs < 75e6) {
16150
+ cachedCount = chunk.prompt_eval_count;
16151
+ }
16152
+ }
16045
16153
  latestUsageMetadata = {
16046
16154
  totalTokenCount: (chunk.prompt_eval_count || 0) + (chunk.eval_count || 0),
16047
16155
  promptTokenCount: chunk.prompt_eval_count || 0,
16048
16156
  candidatesTokenCount: chunk.eval_count || 0,
16049
- cachedContentTokenCount: chunk.prompt_eval_count && isCached ? chunk.prompt_eval_count : 0,
16157
+ cachedContentTokenCount: cachedCount,
16050
16158
  thoughtsTokenCount: 0
16159
+ // Note: Ollama does not natively return a separate sub-count for thoughts yet
16051
16160
  };
16052
16161
  hasNewData = true;
16053
16162
  }
16054
- if (Date.now() - lastFlushTime >= 150 && hasNewData) {
16163
+ if (chunk.done || Date.now() - lastFlushTime >= 150 && hasNewData) {
16055
16164
  yield {
16056
16165
  candidates: pendingParts.length > 0 ? [{ content: { parts: [...pendingParts] } }] : [],
16057
16166
  usageMetadata: latestUsageMetadata
@@ -16098,7 +16207,9 @@ var init_ai = __esm({
16098
16207
  "await": "Waiting",
16099
16208
  "EmergencyRollback": "Don't Panic. Lookin' into it",
16100
16209
  "answer": "Answering Sub-Agent",
16101
- "Answer": "Answering Sub-Agent"
16210
+ "Answer": "Answering Sub-Agent",
16211
+ "steer": "Steering Sub-Agent",
16212
+ "Steer": "Steering Sub-Agent"
16102
16213
  };
16103
16214
  getToolDetail = (toolName, argsStr) => {
16104
16215
  try {
@@ -16107,7 +16218,7 @@ var init_ai = __esm({
16107
16218
  if (normToolName === "invokesync" || normToolName === "invoke") {
16108
16219
  return pArgs.title || (pArgs.task ? pArgs.task.substring(0, 30) : null);
16109
16220
  }
16110
- if (normToolName === "getprogress" || normToolName === "cancel") {
16221
+ if (normToolName === "getprogress" || normToolName === "cancel" || normToolName === "steer" || normToolName === "steersubagent") {
16111
16222
  return pArgs.id || pArgs.taskId;
16112
16223
  }
16113
16224
  const filePath = pArgs.path || pArgs.targetFile || pArgs.TargetFile || pArgs.directory;
@@ -17797,7 +17908,7 @@ ${combinedNudge}`;
17797
17908
  if (!text || !text.includes("[tool:")) return text;
17798
17909
  const THINK_OPEN_PH = "___THINK_OPEN_TAG___";
17799
17910
  const THINK_CLOSE_PH = "___THINK_CLOSE_TAG___";
17800
- text = text.replaceAll("<think>", THINK_OPEN_PH).replaceAll("</think>", THINK_CLOSE_PH);
17911
+ text = text.replace("<think>", THINK_OPEN_PH).replace("</think>", THINK_CLOSE_PH);
17801
17912
  text = text.replace(/<(\w+)(?:[^>]*)>\s*([\s\S]*?\[tool:[^\]]*\][\s\S]*?)\s*<\/\1>/gi, (match2, tagName, innerContent) => {
17802
17913
  if (innerContent && innerContent.includes("[tool:")) return innerContent.trim();
17803
17914
  return match2;
@@ -18369,7 +18480,9 @@ ${ideErr} [/ERROR]`;
18369
18480
  "GetProgress": "get_progress",
18370
18481
  "Cancel": "cancel",
18371
18482
  "Await": "await",
18372
- "Answer": "answer"
18483
+ "Answer": "answer",
18484
+ "Steer": "steer",
18485
+ "steer": "steer"
18373
18486
  };
18374
18487
  const potentialTool = NORMALIZE_MAP[toolContext.toolName] || toolContext.toolName;
18375
18488
  const partialArgs = toolContext.args || "";
@@ -18459,7 +18572,9 @@ ${ideErr} [/ERROR]`;
18459
18572
  "await": "Waiting",
18460
18573
  "EmergencyRollback": "Rolling the Ball",
18461
18574
  "Answer": "Answering Sub-Agent",
18462
- "answer": "Answering Sub-Agent"
18575
+ "answer": "Answering Sub-Agent",
18576
+ "Steer": "Steering Sub-Agent",
18577
+ "steer": "Steering Sub-Agent"
18463
18578
  };
18464
18579
  const toolTitle = TOOL_TITLES[potentialTool] || "Working";
18465
18580
  process.stdout.write(`\x1B]0;${toolTitle}...\x07`);
@@ -18603,6 +18718,10 @@ ${ideErr} [/ERROR]`;
18603
18718
  "answer": "answer",
18604
18719
  "AnswerSubagent": "answer",
18605
18720
  "answerSubagent": "answer",
18721
+ "Steer": "steer",
18722
+ "steer": "steer",
18723
+ "SteerSubagent": "steer",
18724
+ "steerSubagent": "steer",
18606
18725
  "Cancel": "cancel",
18607
18726
  "cancel": "cancel",
18608
18727
  "EmergencyRollback": "EmergencyRollback"
@@ -18686,6 +18805,9 @@ ${ideErr} [/ERROR]`;
18686
18805
  } else if (normToolName === "cancel") {
18687
18806
  const detail2 = getToolDetail(normToolName, toolCall.args);
18688
18807
  label = `\u{1F6C7} Cancelled${detail2 ? `: ${detail2}` : ""}`;
18808
+ } else if (normToolName === "steer" || normToolName === "Steer") {
18809
+ const detail2 = getToolDetail(normToolName, toolCall.args);
18810
+ label = `\u2714 Steered${detail2 ? `: ${detail2}` : ""}`;
18689
18811
  } else if (normToolName === "EmergencyRollback") {
18690
18812
  const { method } = parseArgs(toolCall.args);
18691
18813
  label = method === "forceRevert" ? "" : "\u2714 Rollback Point Checked";
@@ -19869,6 +19991,7 @@ Error Log can be found in ${path26.join(LOGS_DIR, "agent", "error.log")}`);
19869
19991
  if (lower === "openrouter") return "OpenRouter";
19870
19992
  if (lower === "nvidia") return "NVIDIA";
19871
19993
  if (lower === "mistral") return "Mistral";
19994
+ if (lower === "ollama") return "Ollama";
19872
19995
  return null;
19873
19996
  };
19874
19997
  const envSubagentProvider = normalizeProvider(envSubagentProviderRaw);
@@ -19933,6 +20056,7 @@ TOOL RULES:
19933
20056
  - JSON ESCAPE ALL LITERAL ESCAPE SEQUENCES IN TOOL ARGUMENTS
19934
20057
  - SAME file, MULTIPLE edits? ONE PatchFile (\u226415 blocks) \u2190 PRIORITY
19935
20058
  - Need text or huge files? SearchKeyword > Full Read
20059
+ - MUST AVOID UNNECESSARY LARGE-FILE CHUNK READS
19936
20060
  - Restricted Shell Access, No Deletion
19937
20061
  - ONLY valid tools and syntax defined below are allowed
19938
20062
 
@@ -19949,7 +20073,7 @@ ${isAsync ? `- [tool:functions.AskMain(question="...")]. Communicate with PARENT
19949
20073
  - [tool:functions.SearchKeyword(keyword="...", path="optional, dir/file/glob/regex", fuzzy="bool optional, default: false", regex="bool optional, default: auto")]. path scopes search. Find definitions, logic, relevant code
19950
20074
  - [tool:functions.ReadFolder(path="...", recurse="integer 1-3 optional, default: 1")]. DIR Contents + File Size. Minimize recursion
19951
20075
  - [tool:functions.ReadFile(path="...", startLine="integer", endLine="integer")]. View files
19952
- - [tool:functions.PatchFile(path="...", allowMultiple="bool optional, default: false", replaceContent1="string OR ^LINE:start..end$", newContent1="...", ...MAX15)]. TARGET MINIMAL DIFF. replaceContent: select string OR "^LINE:start..end$" for large selection or escape sequences. Verify diffs
20076
+ - [tool:functions.PatchFile(path="...", allowMultiple="bool optional, default: false", searchContent1="string OR ^LINE:start..end$", newContent1="...", ...MAX15)]. TARGET MINIMAL DIFF. "^LINE:start..end$" line ranges MUST for multi-line selection or escape sequences. Verify diffs
19953
20077
  - [tool:functions.WriteFile(path="...", content="...")]. Creates/Overwrites. File Exist? PatchFile > WriteFile. VERIFY IMPORTS
19954
20078
  - [tool:functions.Run(command="...")]. Runs ${osDetected === "Windows" ? isPsAvailable() ? `WINDOWS POWERSHELL` : `WINDOWS CMD` : `BASH`} command. Destructive/Irreversible ops \u2192 Ask user`.trim();
19955
20079
  const systemInstructionSubAgent = `=== START SYSTEM PROMPT ===
@@ -20197,7 +20321,22 @@ ${result}
20197
20321
  await incrementUsage("toolFailure");
20198
20322
  }
20199
20323
  }
20200
- subagentHistory.push({ role: "user", text: toolResultsStr.trim() });
20324
+ let steerAppendText = "";
20325
+ if (settings?.taskId && typeof subagentProgress !== "undefined") {
20326
+ const taskObj = subagentProgress.find((t) => t.id === settings.taskId);
20327
+ if (taskObj && taskObj.pendingSteerMessages && taskObj.pendingSteerMessages.length > 0) {
20328
+ const steerMsgs = [...taskObj.pendingSteerMessages];
20329
+ taskObj.pendingSteerMessages = [];
20330
+ for (const steerMsg of steerMsgs) {
20331
+ steerAppendText += `
20332
+
20333
+ [STEERING INSTRUCTION FROM MAIN AGENT]: ${steerMsg}`;
20334
+ if (logCallback) logCallback(`[Subagent Steered] ${steerMsg}`);
20335
+ }
20336
+ }
20337
+ }
20338
+ const combinedUserText = (toolResultsStr.trim() + steerAppendText).trim();
20339
+ subagentHistory.push({ role: "user", text: combinedUserText });
20201
20340
  turn++;
20202
20341
  }
20203
20342
  return finalAnswer;
@@ -21360,7 +21499,7 @@ function App({ args = [] }) {
21360
21499
  }
21361
21500
  const envModel = process.env.SUBAGENT_MODEL ? process.env.SUBAGENT_MODEL.trim() : null;
21362
21501
  const envProviderRaw = process.env.SUBAGENT_PROVIDER ? process.env.SUBAGENT_PROVIDER.trim() : null;
21363
- const ALL_PROVIDERS = ["Google", "DeepSeek", "OpenRouter", "NVIDIA", "Mistral"];
21502
+ const ALL_PROVIDERS = ["Google", "DeepSeek", "OpenRouter", "NVIDIA", "Mistral", "Ollama"];
21364
21503
  const normalizeProvider = (pStr) => {
21365
21504
  if (!pStr) return null;
21366
21505
  const lower = pStr.toLowerCase();
@@ -21369,6 +21508,7 @@ function App({ args = [] }) {
21369
21508
  if (lower === "openrouter") return "OpenRouter";
21370
21509
  if (lower === "nvidia") return "NVIDIA";
21371
21510
  if (lower === "mistral") return "Mistral";
21511
+ if (lower === "ollama") return "Ollama";
21372
21512
  return null;
21373
21513
  };
21374
21514
  const envProvider = normalizeProvider(envProviderRaw);
@@ -25597,7 +25737,7 @@ Selection: ${val}`,
25597
25737
  const patchPairs = [];
25598
25738
  const indices = /* @__PURE__ */ new Set();
25599
25739
  Object.keys(args2).forEach((key) => {
25600
- const m = key.match(/^(replaceContent|newContent|content_to_replace|content_to_add|TargetContent|ReplacementContent|replacementContent)(\d+)?$/);
25740
+ const m = key.match(/^(searchContent|replaceContent|newContent|content_to_replace|content_to_add|TargetContent|ReplacementContent|replacementContent)(\d+)?$/);
25601
25741
  if (m) {
25602
25742
  const index2 = m[2] ? parseInt(m[2]) : 1;
25603
25743
  indices.add(index2);
@@ -25607,10 +25747,10 @@ Selection: ${val}`,
25607
25747
  sortedIndices.forEach((i) => {
25608
25748
  let r, n;
25609
25749
  if (i === 1) {
25610
- r = args2.replaceContent1 ?? args2.content_to_replace1 ?? args2.replaceContent ?? args2.content_to_replace ?? args2.TargetContent ?? null;
25750
+ r = args2.searchContent1 ?? args2.searchContent ?? args2.replaceContent1 ?? args2.content_to_replace1 ?? args2.replaceContent ?? args2.content_to_replace ?? args2.TargetContent ?? null;
25611
25751
  n = args2.newContent1 ?? args2.content_to_add1 ?? args2.newContent ?? args2.content_to_add ?? args2.ReplacementContent ?? args2.replacementContent ?? null;
25612
25752
  } else {
25613
- r = args2[`replaceContent${i}`] ?? args2[`content_to_replace${i}`] ?? null;
25753
+ r = args2[`searchContent${i}`] ?? args2[`replaceContent${i}`] ?? args2[`content_to_replace${i}`] ?? null;
25614
25754
  n = args2[`newContent${i}`] ?? args2[`content_to_add${i}`] ?? null;
25615
25755
  }
25616
25756
  if (r !== null || n !== null) {
package/model_config.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": 0,
2
+ "version": 1,
3
3
  "release": 20260806,
4
4
  "fallbacks": {
5
5
  "janitor_default": "gemini-3.1-flash-lite",
@@ -42,6 +42,21 @@
42
42
  "multimodal": false,
43
43
  "desc": "Cost Saving (Text Only) [EXPERIMENTAL]"
44
44
  },
45
+ {
46
+ "cmd": "ministral-3b-2512",
47
+ "multimodal": false,
48
+ "desc": "Cost Saving (Text Only) [EXPERIMENTAL]"
49
+ },
50
+ {
51
+ "cmd": "ministral-8b-2512",
52
+ "multimodal": false,
53
+ "desc": "Cost Saving (Text Only) [EXPERIMENTAL]"
54
+ },
55
+ {
56
+ "cmd": "ministral-14b-2512",
57
+ "multimodal": false,
58
+ "desc": "Cost Saving (Text Only) [EXPERIMENTAL]"
59
+ },
45
60
  {
46
61
  "cmd": "mistral-small-2506",
47
62
  "multimodal": false,
@@ -53,10 +68,15 @@
53
68
  "desc": "Devstral Coding (Text Only) [EXPERIMENTAL]"
54
69
  },
55
70
  {
56
- "cmd": "mistral-medium-2505",
71
+ "cmd": "mistral-medium-2508",
57
72
  "multimodal": true,
58
73
  "desc": "Mistral Medium (Multimodal)"
59
74
  },
75
+ {
76
+ "cmd": "mistral-large-2512",
77
+ "multimodal": true,
78
+ "desc": "Mistral Large (Multimodal)"
79
+ },
60
80
  {
61
81
  "cmd": "labs-leanstral-1-5-1",
62
82
  "multimodal": false,
@@ -73,6 +93,21 @@
73
93
  "multimodal": false,
74
94
  "desc": "Cost Saving (Text Only) [EXPERIMENTAL]"
75
95
  },
96
+ {
97
+ "cmd": "ministral-3b-2512",
98
+ "multimodal": false,
99
+ "desc": "Cost Saving (Text Only) [EXPERIMENTAL]"
100
+ },
101
+ {
102
+ "cmd": "ministral-8b-2512",
103
+ "multimodal": false,
104
+ "desc": "Cost Saving (Text Only) [EXPERIMENTAL]"
105
+ },
106
+ {
107
+ "cmd": "ministral-14b-2512",
108
+ "multimodal": false,
109
+ "desc": "Cost Saving (Text Only) [EXPERIMENTAL]"
110
+ },
76
111
  {
77
112
  "cmd": "mistral-small-2506",
78
113
  "multimodal": false,
@@ -84,10 +119,15 @@
84
119
  "desc": "Devstral Coding (Text Only) [EXPERIMENTAL]"
85
120
  },
86
121
  {
87
- "cmd": "mistral-medium-2505",
122
+ "cmd": "mistral-medium-2508",
88
123
  "multimodal": true,
89
124
  "desc": "Mistral Medium (Multimodal)"
90
125
  },
126
+ {
127
+ "cmd": "mistral-large-2512",
128
+ "multimodal": true,
129
+ "desc": "Mistral Large (Multimodal)"
130
+ },
91
131
  {
92
132
  "cmd": "labs-leanstral-1-5-1",
93
133
  "multimodal": false,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fluxflow-cli",
3
- "version": "3.19.0",
4
- "date": "2026-08-05",
3
+ "version": "3.19.2",
4
+ "date": "2026-08-06",
5
5
  "description": "A High-Fidelity Agentic CLI with Sub-Agents for the Flux Era.",
6
6
  "keywords": [
7
7
  "ai",