fluxflow-cli 3.17.0 → 3.18.0

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.
Files changed (2) hide show
  1. package/dist/fluxflow.js +126 -42
  2. package/package.json +2 -2
package/dist/fluxflow.js CHANGED
@@ -2570,7 +2570,7 @@ var init_build = __esm({
2570
2570
 
2571
2571
  // src/utils/text.js
2572
2572
  import os2 from "os";
2573
- var flattenString, wrapText, formatTokens, truncatePath, parsePatchPairs, applyPatches, generateHighFidelityDiff, parseLineInfo, getSimilarity, alignChangeGroup, blocksCache, streamingBlocksCache, MAX_CACHE_SIZE, CHUNK_SIZE, indexBlockIntoMap, parseMessageToBlocks, TOOL_LABELS, REGEX_INITIAL_THINK, REGEX_INITIAL_TOOL, isInsideBacktick, REGEX_CLEAN_SIGNALS, REGEX_ARROWS_ALL, REGEX_TOOLS, bypassBacktick, cleanSignals, clearBlocksCache;
2573
+ var flattenString, wrapText, formatTokens, truncatePath, parsePatchPairs, buildEscapeFuzzyRegex, applyPatches, generateHighFidelityDiff, parseLineInfo, getSimilarity, alignChangeGroup, blocksCache, streamingBlocksCache, MAX_CACHE_SIZE, CHUNK_SIZE, indexBlockIntoMap, parseMessageToBlocks, TOOL_LABELS, REGEX_INITIAL_THINK, REGEX_INITIAL_TOOL, isInsideBacktick, REGEX_CLEAN_SIGNALS, REGEX_ARROWS_ALL, REGEX_TOOLS, bypassBacktick, cleanSignals, clearBlocksCache;
2574
2574
  var init_text = __esm({
2575
2575
  "src/utils/text.js"() {
2576
2576
  init_paths();
@@ -2677,6 +2677,53 @@ var init_text = __esm({
2677
2677
  }
2678
2678
  return { patchPairs, allowMultiple };
2679
2679
  };
2680
+ buildEscapeFuzzyRegex = (content_to_replace) => {
2681
+ if (!content_to_replace) return null;
2682
+ let pattern = "";
2683
+ let i = 0;
2684
+ const len = content_to_replace.length;
2685
+ while (i < len) {
2686
+ const char = content_to_replace[i];
2687
+ if (char === "\\") {
2688
+ while (i < len && content_to_replace[i] === "\\") {
2689
+ i++;
2690
+ }
2691
+ pattern += "\\\\*";
2692
+ } else if (char === "\n") {
2693
+ pattern += "(?:\\r?\\n|\\\\*n|\\\\*r|\\s*)";
2694
+ i++;
2695
+ } else if (char === "\r") {
2696
+ pattern += "(?:\\r|\\\\*r)?";
2697
+ i++;
2698
+ } else if (char === " ") {
2699
+ pattern += "(?:\\t|\\\\*t|\\s*)";
2700
+ i++;
2701
+ } else if (char === '"' || char === "'" || char === "`") {
2702
+ if (!pattern.endsWith("\\\\*")) {
2703
+ pattern += "\\\\*";
2704
+ }
2705
+ pattern += char;
2706
+ i++;
2707
+ } else if (/[.*+?^${}()|[\]]/.test(char)) {
2708
+ pattern += "\\" + char;
2709
+ i++;
2710
+ } else if (/\s/.test(char)) {
2711
+ while (i < len && /\s/.test(content_to_replace[i]) && content_to_replace[i] !== "\n" && content_to_replace[i] !== "\r" && content_to_replace[i] !== " ") {
2712
+ i++;
2713
+ }
2714
+ pattern += "\\s*";
2715
+ } else {
2716
+ pattern += char;
2717
+ i++;
2718
+ }
2719
+ }
2720
+ if (!pattern) return null;
2721
+ try {
2722
+ return new RegExp(pattern, "g");
2723
+ } catch (e) {
2724
+ return null;
2725
+ }
2726
+ };
2680
2727
  applyPatches = (content, patches, options = {}) => {
2681
2728
  const allowMultiple = typeof options === "boolean" ? options : !!(options && options.allowMultiple);
2682
2729
  let currentFileContent = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
@@ -2713,8 +2760,40 @@ var init_text = __esm({
2713
2760
  const patchMatches = [];
2714
2761
  for (let i = 0; i < patches.length; i++) {
2715
2762
  const pair = patches[i];
2716
- const content_to_replace = strip(pair.replace || "");
2763
+ const rawReplace = (pair.replace || "").trim();
2717
2764
  const content_to_add = strip(pair.new || "");
2765
+ if (rawReplace.startsWith("^LINE:") && rawReplace.endsWith("$")) {
2766
+ const body = rawReplace.slice(6, -1).trim();
2767
+ const parts = body.split("..");
2768
+ const startLine = parseInt(parts[0]);
2769
+ const endLine = parts[1] !== void 0 ? parseInt(parts[1]) : startLine;
2770
+ if (!isNaN(startLine) && !isNaN(endLine)) {
2771
+ const fileLines = currentFileContent.split("\n");
2772
+ if (startLine < 1 || startLine > fileLines.length || endLine < startLine || endLine > fileLines.length) {
2773
+ patchMatches.push({
2774
+ index: i,
2775
+ success: false,
2776
+ error: `Block ${i + 1}: Line range ^LINE:${startLine}..${endLine}$ out of bounds (file has ${fileLines.length} lines).`
2777
+ });
2778
+ continue;
2779
+ }
2780
+ const slicedLines = fileLines.slice(startLine - 1, endLine);
2781
+ const firstMatchContent = slicedLines.join("\n");
2782
+ let startPos = 0;
2783
+ for (let k = 0; k < startLine - 1; k++) {
2784
+ startPos += fileLines[k].length + 1;
2785
+ }
2786
+ patchMatches.push({
2787
+ index: i,
2788
+ success: true,
2789
+ startPos,
2790
+ firstMatchContent,
2791
+ content_to_add
2792
+ });
2793
+ continue;
2794
+ }
2795
+ }
2796
+ const content_to_replace = strip(pair.replace || "");
2718
2797
  if (content_to_replace === "" && content_to_add === "") {
2719
2798
  patchMatches.push({ index: i, success: false, error: `Block ${i + 1}: Empty replace and add content.` });
2720
2799
  continue;
@@ -2736,7 +2815,13 @@ var init_text = __esm({
2736
2815
  matchRegex = new RegExp(exactPattern, "g");
2737
2816
  }
2738
2817
  }
2739
- const matches = [...currentFileContent.matchAll(matchRegex)];
2818
+ let matches = [...currentFileContent.matchAll(matchRegex)];
2819
+ if (matches.length === 0 && content_to_replace !== "") {
2820
+ const escapeFuzzyRegex = buildEscapeFuzzyRegex(content_to_replace);
2821
+ if (escapeFuzzyRegex) {
2822
+ matches = [...currentFileContent.matchAll(escapeFuzzyRegex)];
2823
+ }
2824
+ }
2740
2825
  if (matches.length === 0) {
2741
2826
  patchMatches.push({ index: i, success: false, error: `Block ${i + 1}: Could not find match.` });
2742
2827
  continue;
@@ -6845,21 +6930,18 @@ var init_main_tools = __esm({
6845
6930
  }
6846
6931
  return `
6847
6932
  -- TOOL DEFINITIONS --
6848
- Tool calls: ONLY use [tool:functions.ToolName(arg1="value1")]
6933
+ Tool calls: ONLY use [tool:functions.ToolName(arg1="value1")] IN NEW LINE
6849
6934
  **NO OTHER SYNTAX/MARKERS/WRAPPER/BOUNDARY ALLOWED**
6850
6935
 
6851
6936
  **TOOL CALLS POLICY:**
6852
6937
  - MAX 4 TOOL CALLS/TURN${mode === "Flux" ? " (Todo: 4+, Run: max 1 or 2 consecutive)" : ""}
6853
- ${mode === "Flux" ? `- Escape quotes: \\" for code strings
6854
- - Double-escape literal sequences (eg. \\\\n)
6855
- - Use real newlines for code formatting
6938
+ ${mode === "Flux" ? `- JSON ESCAPE ALL LITERAL ESCAPE SEQUENCES IN TOOL ARGUMENTS
6856
6939
  - SAME file, MULTIPLE edits? ONE PatchFile (\u226415 blocks) \u2190 PRIORITY
6857
6940
  - Tool denied? Ask for guidance \u2190 MANDATORY
6858
6941
  - Need text or huge files? SearchKeyword > Full Read
6859
- - Update Todos from realtime progress each turn
6860
6942
  ` : ""}
6861
6943
  - COMMUNICATION WITH USER -
6862
- - [tool:functions.Ask(question="...", optionA="option::description", ...MAX 4)]. Ambiguity: MUST for path divergence, security risk. Ask, don't finish/guess. Suggest best options; no preferences. Keep options short
6944
+ - [tool:functions.Ask(question="...", optionA="title::description", ...MAX4)]. Ambiguity: MUST for path divergence, security risk. Ask, don't finish/guess. Suggest best options; no preferences. Keep titles short
6863
6945
 
6864
6946
  - WEB TOOLS -
6865
6947
  - [tool:functions.WebSearch(query="...", aiMode="bool optional, default: false", limit="integer 3-10, aiMode: exclude")]. Usage: unknown info/docs. aiMode: LLM search
@@ -6868,7 +6950,7 @@ ${mode === "Flux" ? `- Escape quotes: \\" for code strings
6868
6950
  ${mode === "Flux" ? `- WORKSPACE TOOLS (path = relative; FIRST ARGUMENT, path separator: '/') -
6869
6951
  - [tool:functions.ReadFile(path="...", startLine="integer", endLine="integer")]. ${aiProvider !== "Google" ? `${isMultiModal ? `Supports images/docs` : ""}` : `Supports images/docs`}
6870
6952
  - [tool:functions.ReadFolder(path="...", recurse="integer 1-3 optional, default: 1")]. DIR Contents + File Size. Minimize recursion
6871
- - [tool:functions.PatchFile(path="...", allowMultiple="bool optional, default: false", replaceContent1="...", newContent1="...", ...MAX15)]. TARGET MINIMAL DIFF. allowMultiple: Replace all matches ONLY WHEN SURE. Multi-blocks: replaceContent2/newContent2... Verify diffs
6953
+ - [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$" to target line ranges. Multi-blocks supported. Verify diffs
6872
6954
  - [tool:functions.WriteFile(path="...", content="...")]. Creates/Overwrites. File Exist? PatchFile > WriteFile
6873
6955
  - [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
6874
6956
  - [tool:functions.Run(command="...")]. Runs ${osDetected === "Windows" ? isPsAvailable() ? `POWERSHELL` : `WINDOWS CMD` : `BASH`} command. Destructive/Irreversible ops \u2192 Ask user
@@ -8623,8 +8705,7 @@ ${projectContextBlock}${isMemoryEnabled ? `
8623
8705
  -- CHAT FORMATTING --
8624
8706
  - GFM Markdown ONLY
8625
8707
  - Same Language as User Query
8626
- - After tool calls emit no chat in this turn
8627
- - On completion: summarize changes (why) + edited files${mode === "Flux" ? "" : "\n- Use Kaomojis HEAVILY"}
8708
+ - Finish all chatting before tool calls${mode === "Flux" ? "" : "\n- Use Kaomojis HEAVILY"}
8628
8709
  === END SYSTEM PROMPT ===
8629
8710
 
8630
8711
  ${nameStr}${nicknameStr}${userInstrStr}${userMemoriesStr}`.trim();
@@ -9085,8 +9166,14 @@ var init_history = __esm({
9085
9166
  const datePart = parts[0];
9086
9167
  const timePart = parts[1] || "";
9087
9168
  const ampm = parts[2] || "";
9088
- const dateNums = datePart.split(/[-/.]/).map(Number);
9169
+ const dateNums = datePart.split(/[-\/.]/).map(Number);
9089
9170
  if (dateNums.length !== 3) return null;
9171
+ let locale = "en";
9172
+ try {
9173
+ locale = Intl.DateTimeFormat().resolvedOptions().locale || "en";
9174
+ } catch {
9175
+ }
9176
+ const isDayFirst = !/^en[-_]?us$|^en$/.test(locale?.toLowerCase());
9090
9177
  let year, month, day;
9091
9178
  if (dateNums[0] > 1e3) {
9092
9179
  year = dateNums[0];
@@ -9101,12 +9188,18 @@ var init_history = __esm({
9101
9188
  day = dateNums[1];
9102
9189
  month = dateNums[0];
9103
9190
  } else {
9104
- month = dateNums[0];
9105
- day = dateNums[1];
9191
+ if (isDayFirst) {
9192
+ day = dateNums[0];
9193
+ month = dateNums[1];
9194
+ } else {
9195
+ month = dateNums[0];
9196
+ day = dateNums[1];
9197
+ }
9106
9198
  }
9107
9199
  } else {
9108
9200
  return null;
9109
9201
  }
9202
+ if (month < 1 || month > 12 || day < 1 || day > 31) return null;
9110
9203
  let hours = 0, minutes = 0, seconds = 0;
9111
9204
  if (timePart) {
9112
9205
  const timeNums = timePart.split(":").map(Number);
@@ -9151,16 +9244,19 @@ var init_history = __esm({
9151
9244
  const threshold = 7 * 24 * 60 * 60 * 1e3;
9152
9245
  const now = Date.now();
9153
9246
  const keptEntries = [];
9154
- const timestampRegex = /(\d{1,4}[-/.]\d{1,4}[-/.]\d{1,4}(?:,\s*|\s+)?(?:\d{1,2}:\d{2}:\d{2}(?:\s*[aApP][mM])?)?)/;
9247
+ const timestampRegex = /(\d{1,4}[-\/.]\d{1,4}[-\/.]\d{1,4}(?:,\s*|\s+)?(?:\d{1,2}:\d{2}:\d{2}(?:\s*[aApP][mM])?)?)/;
9155
9248
  for (const entry of entries) {
9156
- const entryText = entry.header + (entry.body.length > 0 ? "\n" + entry.body.join("\n") : "");
9157
- const match = entryText.match(timestampRegex);
9158
- if (match) {
9159
- const timeMs = parseCustomDate(match[1]);
9160
- if (timeMs && now - timeMs > threshold) {
9161
- continue;
9249
+ const isLogEntry = entryStartRegex.test(entry.header);
9250
+ if (isLogEntry) {
9251
+ const headerMatch = entry.header.match(timestampRegex);
9252
+ if (headerMatch) {
9253
+ const timeMs = parseCustomDate(headerMatch[1]);
9254
+ if (timeMs && now - timeMs > threshold) {
9255
+ continue;
9256
+ }
9162
9257
  }
9163
9258
  }
9259
+ const entryText = entry.header + (entry.body.length > 0 ? "\n" + entry.body.join("\n") : "");
9164
9260
  keptEntries.push(entryText);
9165
9261
  }
9166
9262
  const finalContent = keptEntries.join("\n").trim();
@@ -12619,7 +12715,7 @@ var init_invoke = __esm({
12619
12715
  const subagentContext = {
12620
12716
  ...context,
12621
12717
  taskId,
12622
- onAskMain: async (questionText, optionsObj) => {
12718
+ onAskMain: async (questionText) => {
12623
12719
  const questionId = `q-${Date.now()}-${Math.floor(Math.random() * 1e3)}`;
12624
12720
  let questionResolver = null;
12625
12721
  const qPromise = new Promise((resolve) => {
@@ -12628,7 +12724,6 @@ var init_invoke = __esm({
12628
12724
  const qEntry = {
12629
12725
  id: questionId,
12630
12726
  question: questionText,
12631
- options: optionsObj,
12632
12727
  answered: false,
12633
12728
  answer: null,
12634
12729
  askedAt: Date.now(),
@@ -12782,7 +12877,7 @@ var init_getProgress = __esm({
12782
12877
  if (task.status === "running" || task.status === "waiting") {
12783
12878
  if (task.currentTool) output += `Current Tool: ${task.currentTool}
12784
12879
  `;
12785
- if (task.wps > 0) output += `WPS: ${task.wps}
12880
+ if (task.wps > 0) output += `TPS: ${task.wps}
12786
12881
  `;
12787
12882
  }
12788
12883
  if (task.questions && task.questions.length > 0) {
@@ -12794,10 +12889,6 @@ var init_getProgress = __esm({
12794
12889
  pending.forEach((q) => {
12795
12890
  output += `"${q.question}"
12796
12891
  `;
12797
- if (q.options && Object.keys(q.options).length > 0) {
12798
- output += `Options: ${JSON.stringify(q.options)}
12799
- `;
12800
- }
12801
12892
  });
12802
12893
  output += `Respond using tool: [tool:functions.Answer(id="${task.id}", answer="...")]
12803
12894
 
@@ -18546,20 +18637,16 @@ TO ACCESS TOOLS **STRICTLY USE THE EXACT FORMAT IN CHAT OUTPUT:** [tool:function
18546
18637
  **NO OTHER SYNTAX/MARKERS/WRAPPER/BOUNDARY ALLOWED**
18547
18638
 
18548
18639
  TOOL POLICY:
18549
- - Escape quotes: \\" for code strings
18550
- - Double-escape literal sequences (eg. \\\\n)
18551
- - Use real newlines for code formatting
18640
+ - JSON ESCAPE ALL LITERAL ESCAPE SEQUENCES IN TOOL ARGUMENTS
18552
18641
  - SAME file, MULTIPLE edits? ONE PatchFile (\u226415 blocks) \u2190 PRIORITY
18553
- - Tool denied? Ask for guidance \u2190 MANDATORY
18554
18642
  - Need text or huge files? SearchKeyword > Full Read
18555
- - Update Todos from realtime progress each turn
18556
18643
  - Restricted Shell Access, No Deletion
18557
18644
  - ONLY valid tools and syntax defined below are allowed
18558
18645
 
18559
18646
  **PROVIDED TOOLS**
18560
18647
  -- Communication Tools --
18561
- - [tool:functions.Ask(question="...", optionA="option::description", ...MAX 4)]. Communicate with USER. Ambiguity: MUST for path divergence, security risk. Ask, don't finish/guess. Suggest best options; no preferences. Keep options short
18562
- ${isAsync ? `- [tool:functions.AskMain(question="...", optionA="option::description", ...MAX 4)]. Communicate with PARENT/MAIN AGENT. When clarification/decision is needed for a task` : ""}
18648
+ - [tool:functions.Ask(question="...", optionA="title::description", ...MAX4)]. Communicate with USER. Ambiguity: MUST for path divergence, security risk. Ask, don't finish/guess. Suggest best options; no preferences. Keep titles short
18649
+ ${isAsync ? `- [tool:functions.AskMain(question="...")]. Communicate with PARENT/MAIN AGENT. When clarification/decision is needed for a task` : ""}
18563
18650
 
18564
18651
  -- Web Tools --
18565
18652
  - [tool:functions.WebSearch(query="...", aiMode="bool optional, default: false", limit="integer 3-10, aiMode: exclude")]. Usage: unknown info/docs. aiMode: LLM search
@@ -18569,7 +18656,7 @@ ${isAsync ? `- [tool:functions.AskMain(question="...", optionA="option::descript
18569
18656
  - [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
18570
18657
  - [tool:functions.ReadFolder(path="...", recurse="integer 1-3 optional, default: 1")]. DIR Contents + File Size. Minimize recursion
18571
18658
  - [tool:functions.ReadFile(path="...", startLine="integer", endLine="integer")]. View files
18572
- - [tool:functions.PatchFile(path="...", allowMultiple="bool optional, default: false", replaceContent1="...", newContent1="...", ...MAX15)]. TARGET MINIMAL DIFF. allowMultiple: Replace all matches ONLY WHEN SURE. Multi-blocks: replaceContent2/newContent2... Verify diffs
18659
+ - [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$" to target line ranges. Multi-blocks supported. Verify diffs
18573
18660
  - [tool:functions.WriteFile(path="...", content="...")]. Creates/Overwrites. File Exist? PatchFile > WriteFile. VERIFY IMPORTS
18574
18661
  - [tool:functions.Run(command="...")]. Runs ${osDetected === "Windows" ? isPsAvailable() ? `WINDOWS POWERSHELL` : `WINDOWS CMD` : `BASH`} command. Destructive/Irreversible ops \u2192 Ask user`.trim();
18575
18662
  const systemInstructionSubAgent = `=== START SYSTEM PROMPT ===
@@ -18611,7 +18698,7 @@ Current Time: ${time}
18611
18698
  role: m.role === "user" ? "user" : "model",
18612
18699
  parts: [{ text: m.text }]
18613
18700
  }));
18614
- if (logCallback) logCallback(`[Subagent Turn ${turn + 1}] Invoking model ${targetModel}...`);
18701
+ if (logCallback) logCallback(`[Subagent Turn ${turn + 1}]...`);
18615
18702
  const response = await generateSimpleContent(mergedSettings, targetModel, contents, systemInstructionSubAgent, "Fast");
18616
18703
  const responseText = response.text || "";
18617
18704
  const cleanResponse = responseText.replace(/(?:<think>|\[think\])[\s\S]*?(?:<\/think>|\[\/think\])/gi, "").trim();
@@ -18646,21 +18733,18 @@ ${cleanResponse}
18646
18733
  if (processedAskMainInTurn) continue;
18647
18734
  processedAskMainInTurn = true;
18648
18735
  let questionText = "";
18649
- let optionsObj = {};
18650
18736
  if (askMainCalls.length === 1) {
18651
18737
  const pArgs = parseArgs(askMainCalls[0].args);
18652
18738
  questionText = pArgs.question || askMainCalls[0].args;
18653
- optionsObj = pArgs;
18654
18739
  } else {
18655
18740
  questionText = askMainCalls.map((tc, idx) => {
18656
18741
  const pArgs = parseArgs(tc.args);
18657
18742
  return `Q${idx + 1}: ${pArgs.question || tc.args}`;
18658
18743
  }).join("\n");
18659
- optionsObj = {};
18660
18744
  }
18661
18745
  if (settings.onAskMain) {
18662
18746
  if (logCallback) logCallback(`[Executing Tool] AskMain("${questionText}")...`);
18663
- const answer = await settings.onAskMain(questionText, optionsObj);
18747
+ const answer = await settings.onAskMain(questionText);
18664
18748
  if (logCallback) logCallback(`[Tool Result]
18665
18749
  Answer from Main Agent: ${answer}
18666
18750
  `);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fluxflow-cli",
3
- "version": "3.17.0",
4
- "date": "2026-08-03",
3
+ "version": "3.18.0",
4
+ "date": "2026-08-04",
5
5
  "description": "A High-Fidelity Agentic CLI with Sub-Agents for the Flux Era.",
6
6
  "keywords": [
7
7
  "ai",