fluxflow-cli 3.13.4 → 3.13.5

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 +79 -62
  2. package/package.json +1 -1
package/dist/fluxflow.js CHANGED
@@ -2644,6 +2644,7 @@ var init_text = __esm({
2644
2644
  parsePatchPairs = (args) => {
2645
2645
  const patchPairs = [];
2646
2646
  const indices = /* @__PURE__ */ new Set();
2647
+ const allowMultiple = args.allowMultiple === true || String(args.allowMultiple).toLowerCase() === "true";
2647
2648
  Object.keys(args).forEach((key) => {
2648
2649
  const m = key.match(/^(replaceContent|newContent|content_to_replace|content_to_add)(\d+)?$/);
2649
2650
  if (m) {
@@ -2664,12 +2665,13 @@ var init_text = __esm({
2664
2665
  if (r !== void 0 && n !== void 0) {
2665
2666
  patchPairs.push({ replace: r, new: n });
2666
2667
  } else if (r !== void 0 || n !== void 0) {
2667
- return { error: `Mismatched replacement pair for index ${i}. Both replacement and new content must be provided.` };
2668
+ return { error: `Mismatched replacement pair for index ${i}. Both replacement and new content must be provided.`, allowMultiple };
2668
2669
  }
2669
2670
  }
2670
- return { patchPairs };
2671
+ return { patchPairs, allowMultiple };
2671
2672
  };
2672
- applyPatches = (content, patches) => {
2673
+ applyPatches = (content, patches, options = {}) => {
2674
+ const allowMultiple = typeof options === "boolean" ? options : !!(options && options.allowMultiple);
2673
2675
  let currentFileContent = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
2674
2676
  const strip = (t) => t.replace(/^```[\w]*\n?/, "").replace(/```\s*$/, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
2675
2677
  const getIndent = (line) => line.match(/^\s*/)[0];
@@ -2732,17 +2734,19 @@ var init_text = __esm({
2732
2734
  patchMatches.push({ index: i, success: false, error: `Block ${i + 1}: Could not find match.` });
2733
2735
  continue;
2734
2736
  }
2735
- if (matches.length > 1) {
2736
- patchMatches.push({ index: i, success: false, error: `Block ${i + 1}: Found ${matches.length} matches (must be unique).` });
2737
+ if (matches.length > 1 && !allowMultiple) {
2738
+ patchMatches.push({ index: i, success: false, error: `Block ${i + 1}: Found ${matches.length} matches (must be unique or use allowMultiple: true if sure).` });
2737
2739
  continue;
2738
2740
  }
2739
- patchMatches.push({
2740
- index: i,
2741
- success: true,
2742
- startPos: matches[0].index,
2743
- firstMatchContent: matches[0][0],
2744
- content_to_add
2745
- });
2741
+ for (const matchItem of matches) {
2742
+ patchMatches.push({
2743
+ index: i,
2744
+ success: true,
2745
+ startPos: matchItem.index,
2746
+ firstMatchContent: matchItem[0],
2747
+ content_to_add
2748
+ });
2749
+ }
2746
2750
  }
2747
2751
  const successful = patchMatches.filter((m) => m.success).sort((a, b) => a.startPos - b.startPos);
2748
2752
  for (let j = 0; j < successful.length - 1; j++) {
@@ -2779,8 +2783,9 @@ var init_text = __esm({
2779
2783
  for (let j = patchEndLineIdx; j < Math.min(allLines.length, patchEndLineIdx + 3); j++) {
2780
2784
  contextAfter.push({ num: j + 1, text: allLines[j] });
2781
2785
  }
2782
- resultsMap.set(match.index, {
2786
+ resultsMap.set(match, {
2783
2787
  success: true,
2788
+ index: match.index,
2784
2789
  oldContent: match.firstMatchContent,
2785
2790
  newContent: finalReplacement,
2786
2791
  originalStartLine,
@@ -2794,13 +2799,18 @@ var init_text = __esm({
2794
2799
  }
2795
2800
  const results = [];
2796
2801
  for (let i = 0; i < patches.length; i++) {
2797
- if (resultsMap.has(i)) {
2798
- results.push(resultsMap.get(i));
2802
+ const matchesForI = toApply.filter((m) => m.index === i);
2803
+ if (matchesForI.length > 0) {
2804
+ for (const match of matchesForI) {
2805
+ if (resultsMap.has(match)) {
2806
+ results.push(resultsMap.get(match));
2807
+ }
2808
+ }
2799
2809
  } else {
2800
- const match = patchMatches.find((m) => m.index === i);
2810
+ const failedMatch = patchMatches.find((m) => m.index === i);
2801
2811
  results.push({
2802
2812
  success: false,
2803
- error: match ? match.error : `Block ${i + 1}: Unknown error.`
2813
+ error: failedMatch ? failedMatch.error : `Block ${i + 1}: Unknown error.`
2804
2814
  });
2805
2815
  }
2806
2816
  }
@@ -6678,8 +6688,8 @@ Tool calls: ONLY use [tool:functions.ToolName(args)]
6678
6688
 
6679
6689
  **TOOL USAGE POLICY:**
6680
6690
  - MAX 3 TOOL CALLS/TURN${mode === "Flux" ? " (Todo: 3+, Run: max 1 or 2 consecutive)" : ""}
6681
- ${mode === "Flux" ? "- Same file, many edits? Prefer multi search-replace in Patch \u2190 **HIGHLY RECOMMENDED**\n- Tool denied?Use Ask immediately for user guidance.NEVER proceed blindly/end turn \u2190 ** MANDATORY **\n- FileMap \u2192 ReadFile for efficient file understanding\n- Need specific text ? SearchKeyword > Guessing/ReadFile\n- Huge files ? SearchKeyword > FileMap/Full Read\n- No tool spamming\n- **Update/complete Todos from realtime progress EVERY TURN**" : ""}
6682
- ${mode === "Flux" ? "- **File Tools >> Code in chat**\n\n" : ""}- COMMUNICATION TOOLS -
6691
+ ${mode === "Flux" ? "- Same file, many edits? Prefer multi search-replace in Patch \u2190 **HIGHLY RECOMMENDED**\n- Tool denied?Use Ask immediately for user guidance.NEVER proceed blindly/end turn \u2190 ** MANDATORY **\n- FileMap \u2192 ReadFile for efficient file understanding\n- Need specific text ? SearchKeyword > Guessing/ReadFile\n- Huge files ? SearchKeyword > FileMap/Full Read\n- No tool spamming\n- **Update/complete Todos from realtime progress EVERY TURN**\n" : ""}
6692
+ - COMMUNICATION TOOLS -
6683
6693
  1. [tool:functions.Ask(question="...", optionA="option::description", ...MAX 4)]. Ambiguity: MUST ask for path divergence, security or risk. Ask, don't finish/guess. Suggest best options; no preferences. Keep options short
6684
6694
 
6685
6695
  - WEB TOOLS -
@@ -6687,10 +6697,10 @@ ${mode === "Flux" ? "- **File Tools >> Code in chat**\n\n" : ""}- COMMUNICATION
6687
6697
  2. [tool:functions.WebScrape(url="...")]. Proactive use for specific webpage/docs/api
6688
6698
 
6689
6699
  ${mode === "Flux" ? `- WORKSPACE TOOLS (path = relative; FIRST ARGUMENT, path separator: '/') -
6690
- 1. [tool:functions.ReadFile(path="...", startLine=number, endLine=number)]. ${aiProvider !== "Google" ? `${isMultiModal ? `Supports images/docs. **User gives image/doc: VIEW FIRST**` : `No Multimodal support`}` : `Supports images/docs. **User gives image/doc: VIEW FIRST**`}
6700
+ 1. [tool:functions.ReadFile(path="...", startLine=number, endLine=number)]. ${aiProvider !== "Google" ? `${isMultiModal ? `Supports images/docs` : `No Multimodal support`}` : `Supports images/docs`}
6691
6701
  2. [tool:functions.ReadFolder(path="...")]. Detailed DIR stats including File Sizes
6692
6702
  3. [tool:functions.FileMap(path="path/file")]. Shows file structure, functions, class, import/export, variables
6693
- 4. [tool:functions.PatchFile(path="...", replaceContent1="full lines", newContent1="...", ...MAX 10)]. Surgical patch. Multiple patches same file? Use replaceContent2/newContent2... Unsure? ReadFile. MUST VERIFY DIFF
6703
+ 4. [tool:functions.PatchFile(path="...", allowMultiple="true optional", replaceContent1="...", newContent1="...", ...MAX 10)]. Surgical patch. allowMultiple: Replace all matches (default: false). Multiple patches same file? Use replaceContent2/newContent2... Unsure? ReadFile. MUST VERIFY DIFF
6694
6704
  5. [tool:functions.WriteFile(path="...", content="...")]. Creates/Overwrites. File Exist? PatchFile > WriteFile. Verify Imports
6695
6705
  6. [tool:functions.SearchKeyword(keyword="...", path="optional, target directory or filename", subString="true optional", regex="false for keyword, optional")]. Project-wide search. path limits scope to a file/dir. Find definitions/logic without full reads. Locate relevant code. Defaults: subString=false, regex=true
6696
6706
  7. [tool:functions.Run(command="...")]. Runs ${osDetected === "Windows" ? isPsAvailable() ? `WINDOWS POWERSHELL` : `WINDOWS CMD ONLY` : `BASH`} command. Destructive/Irreversible ops \u2192 Ask user
@@ -6702,7 +6712,7 @@ Info: \`initial\` = user prompt for current task. Revert \`id\` = turn BEFORE th
6702
6712
  Use ONLY for catastrophic/codebase corruption. Before ending loop, verify no catastrophe. \`id\` not required with \`getCheckPoint\`.
6703
6713
  ` : ""}${enableSubAgents ? `
6704
6714
  - SUB AGENT TOOLS -
6705
- **PROACTIVE sub-agent use HIGHLY RECOMMENDED. Prefer for any task with even slight benefit, no user nudge needed.**
6715
+ **PROACTIVE sub-agent use HIGHLY RECOMMENDED. Prefer for any task with even slight benefit, no user nudge needed**
6706
6716
  Invocations:
6707
6717
  - Invoke (async/background, \u22647 parallel). Parallelize long tasks. NEVER repeat while active
6708
6718
  - InvokeSync (sync/blocking). Sequential, repetitive or delegated tasks. Saves tokens/cost
@@ -7855,21 +7865,29 @@ function ProfileForm({ initialData, onSave, onCancel, theme = "Dark" }) {
7855
7865
  instructions: initialData?.instructions || ""
7856
7866
  }));
7857
7867
  const steps = [
7858
- { key: "name", label: "Enter your Name: " },
7859
- { key: "nickname", label: "Enter a Nickname (Agent will use this): " },
7860
- { key: "instructions", label: "System Instructions (Persona overrides): " }
7868
+ { key: "name", label: "Enter your Name: ", maxLength: 20 },
7869
+ { key: "nickname", label: "Enter a Nickname: ", maxLength: 20 },
7870
+ { key: "instructions", label: "System Instructions: ", maxLength: 200 }
7861
7871
  ];
7872
+ const currentStep = steps[step];
7862
7873
  useEffect6(() => {
7863
7874
  const currentKey = steps[step].key;
7864
- setCurrentInput(profile[currentKey] || "");
7875
+ setCurrentInput((profile[currentKey] || "").slice(0, steps[step].maxLength));
7865
7876
  }, [step, profile]);
7877
+ const handleInputChange = (val) => {
7878
+ if (val.length > currentStep.maxLength) {
7879
+ setCurrentInput(val.slice(0, currentStep.maxLength));
7880
+ } else {
7881
+ setCurrentInput(val);
7882
+ }
7883
+ };
7866
7884
  const handleSubmit = (val) => {
7867
7885
  if (val.trim().toLowerCase() === "/cancel") {
7868
7886
  onCancel();
7869
7887
  return;
7870
7888
  }
7871
- const currentKey = steps[step].key;
7872
- const newProfile = { ...profile, [currentKey]: val.trim() };
7889
+ const currentKey = currentStep.key;
7890
+ const newProfile = { ...profile, [currentKey]: val.trim().slice(0, currentStep.maxLength) };
7873
7891
  setProfile(newProfile);
7874
7892
  setCurrentInput("");
7875
7893
  if (step < steps.length - 1) {
@@ -7878,6 +7896,7 @@ function ProfileForm({ initialData, onSave, onCancel, theme = "Dark" }) {
7878
7896
  onSave(newProfile);
7879
7897
  }
7880
7898
  };
7899
+ const isAtMax = currentInput.length >= currentStep.maxLength;
7881
7900
  return /* @__PURE__ */ React8.createElement(
7882
7901
  Box7,
7883
7902
  {
@@ -7890,14 +7909,14 @@ function ProfileForm({ initialData, onSave, onCancel, theme = "Dark" }) {
7890
7909
  width: "100%"
7891
7910
  },
7892
7911
  /* @__PURE__ */ React8.createElement(Box7, { paddingX: 1, marginBottom: 1 }, /* @__PURE__ */ React8.createElement(Text8, { color: colors.text, bold: true }, "DEVELOPER PROFILE CONFIGURATION")),
7893
- /* @__PURE__ */ React8.createElement(Box7, { paddingX: 1, flexDirection: "column" }, /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(Text8, { color: colors.text, bold: true }, steps[step].label), /* @__PURE__ */ React8.createElement(
7912
+ /* @__PURE__ */ React8.createElement(Box7, { paddingX: 1, flexDirection: "column" }, /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(Text8, { color: colors.text, bold: true }, currentStep.label), /* @__PURE__ */ React8.createElement(
7894
7913
  TextInput2,
7895
7914
  {
7896
7915
  value: currentInput,
7897
- onChange: setCurrentInput,
7916
+ onChange: handleInputChange,
7898
7917
  onSubmit: handleSubmit
7899
7918
  }
7900
- )), /* @__PURE__ */ React8.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, { color: colors.textMuted, italic: true }, "Step ", step + 1, " of ", steps.length))),
7919
+ )), /* @__PURE__ */ React8.createElement(Box7, { marginTop: 1, justifyContent: "space-between" }, /* @__PURE__ */ React8.createElement(Text8, { color: colors.textMuted, italic: true }, "Step ", step + 1, " of ", steps.length), /* @__PURE__ */ React8.createElement(Text8, { color: isAtMax ? colors.warning || "yellow" : colors.textMuted }, "[", currentInput.length, "/", currentStep.maxLength, "]"))),
7901
7920
  /* @__PURE__ */ React8.createElement(Box7, { paddingX: 1, marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, { color: colors.textMuted, italic: true }, "(Enter to submit \u2022 Type /cancel to abort)"))
7902
7921
  );
7903
7922
  }
@@ -8191,14 +8210,8 @@ Check these first; These Files > Training Data. Safety rules apply
8191
8210
  }
8192
8211
  const projectContextBlock = cachedProjectContextBlock;
8193
8212
  return `=== SYSTEM PROMPT ===
8194
- Identity: Flux Flow. ${mode === "Flux" ? "Sassy" : "Conversational, Sassy, Friendly, Humorous, Sarcastic"}, CLI Agent
8195
- Mode: ${mode}${thinkingLevel !== "Fast" ? "" : ""}. ${mode === "Flux" ? "Logical, detailed, task-driven. Prioritize scalable file/folder structure, modular architecture, clean abstractions, stepwise execution. Use latest industry-standard practices/libraries, clean code, verify imports, test as needed" : "Concise"}
8196
-
8197
- - **CRITICAL: ONLY VALID TOOL CALL SCHEMA IS THE ONE PROVIDED IN SYSTEM PROMPT. NO OTHER XML OR MARKERS WILL BE ALLOWED**
8198
-
8199
- -- MARKERS --
8200
- - TOOL SYSTEM: [TOOL RESULT]
8201
- - SYSTEM NOTIFICATION: [SYSTEM] in user turn
8213
+ Identity: Flux Flow. Sassy, CLI Agent
8214
+ ${mode === "Flux" ? "Logical, detailed, task-driven. Prioritize scalable file/folder structure, modular architecture, clean abstractions, stepwise execution. Use latest industry-standard practices/libraries, clean code, verify imports, run automated tests" : `Mode: ${mode}. Concise, Conversational, Sassy, Friendly, Humorous, Sarcastic`}
8202
8215
 
8203
8216
  -- THINKING GUIDANCE --
8204
8217
  ${aiProvider === "Mistral" || aiProvider === "Google" && !isGemini ? `${thinkingConfig}
@@ -8209,14 +8222,14 @@ ${forcedReasoning || thinkingLevel !== "Fast" && (aiProvider === "Mistral" || th
8209
8222
  ${TOOL_PROTOCOL(mode, osDetected, aiProvider.toLowerCase() === "deepseek" ? false : isMultiModal, aiProvider, systemSettings?.advanceRollback, systemSettings?.subAgents !== false)}
8210
8223
  ${projectContextBlock}${isMemoryEnabled ? `
8211
8224
  -- MEMORY RULES --
8212
- - Subtly Personalize ONLY WITH RELEVENT & CONTEXTUAL MEMORIES. Auto Saves` : ""}
8213
- - Temporal Awareness: RELATIVE TIME REFERENCE eg. few mins ago
8225
+ - Subtly Personalize with RELEVENT & CONTEXTUAL MEMORIES. Auto Saves` : ""}
8226
+ - RELATIVE TIME REFERENCE eg. few mins ago
8214
8227
 
8215
8228
  -- SECURITY RULES --
8216
- - Sensitive files? Ask before Read${isSystemDir ? "\n- PROTECTED DIRECTORY: ASK BEFORE MODIFYING" : ""}
8229
+ - Sensitive files? Ask before Read${isSystemDir ? "\n- PROTECTED DIRECTORY" : ""}
8217
8230
 
8218
- -- FORMATTING --
8219
- - Chat Messages with GFM Formatting
8231
+ -- CHAT FORMATTING --
8232
+ - GFM Markdown
8220
8233
  - Same Language as User Query
8221
8234
  - Before tool calls, emit one brief status line. After tool calls, emit no further text this turn
8222
8235
  - On completion: summarize changes (why) + edited files${mode === "Flux" ? "" : "\n- Use Kaomojis HEAVILY"}
@@ -8225,14 +8238,14 @@ ${projectContextBlock}${isMemoryEnabled ? `
8225
8238
  ${nameStr}${nicknameStr}${userInstrStr}${userMemoriesStr}`.trim();
8226
8239
  };
8227
8240
  getJanitorInstruction = (userMemories = "", isMemoryEnabled = true, needTitle = true) => {
8228
- return `=== START SYSTEM PROMPT (STRICT HEADLESS LOGIC WORKER: ZERO USER-FACING TEXT POLICY, STRICTLY FOLLOW) ===
8229
- YOU ARE A SILENT BACKGROUND SYSTEM PROCESS. YOU HAVE NO MOUTH. YOUR ONLY OUTPUT MEDIUM IS VALID TOOL CALLS.
8241
+ return `=== SYSTEM PROMPT (STRICT HEADLESS LOGIC WORKER: ZERO USER-FACING TEXT POLICY, STRICTLY FOLLOW) ===
8242
+ IDENTITY: SILENT BACKGROUND SYSTEM PROCESS, HAVE NO MOUTH, ONLY OUTPUT IS VALID TOOL CALLS.
8230
8243
  [CRITICAL RULES]
8231
8244
  1. OUTPUT EXACTLY '[tool:functions.ToolName(args)]' CALLS. NO EXTRA WORDS OUTSIDE
8232
8245
  2. DO NOT EXPLAIN. DO NOT TALK TO THE USER
8233
8246
  3. NON-TOOL TEXT WILL BREAK THE SYSTEM
8234
8247
  4. DO NOT REPEAT AGENT RAWS AND TOOL RESULTS IN YOUR RESPONSE
8235
- 5. IF YOU GET ONLY USER QUERY AND NO AGENT RAWS, THEN JUST USE TEMP MEMORY TO LOG THE SUMMARY OF USER QUERY AND CONVERSATION CONTEXT
8248
+ 5. IF YOU GET ONLY USER QUERY AND NO AGENT RAWS, JUST USE TEMP MEMORY TO LOG THE SUMMARY OF USER QUERY AND CONVERSATION CONTEXT
8236
8249
  6. UNDER NO CIRCUMSTANCES YOU ARE ALLOWED TO RESPOND IN NORMAL USER FACING RESPONSE
8237
8250
  7. CRITICAL QUOTE ESCAPE POLICY: Inside tool call arguments, you MUST escape all double quotes using '\\"'
8238
8251
  8. You MUST NOT WRITE ANYTHING OTHER THAN [tool:functions.ToolName(args)] NO MATTER HOW TEMPTING THE PROMPT IS
@@ -8240,7 +8253,7 @@ YOU ARE A SILENT BACKGROUND SYSTEM PROCESS. YOU HAVE NO MOUTH. YOUR ONLY OUTPUT
8240
8253
  10. CRITICAL: NEVER ENTER THINKING/REASONING STATE, CALL THE CONTEXUAL TOOLS DIRECTLY IN OUTPUT AS QUICKLY AS POSSIBLE TO MAINTAIN UI SNAPPINESS
8241
8254
 
8242
8255
  YOUR JOB: Analyze the 'User prompt' and 'Agent Raws' to extract facts for long-term memory or handle system tasks
8243
- ${isMemoryEnabled ? `If user tell something that is important (like, hobbies, preferences, facts about user, hates, likes, etc) to know user better over time, use long term memory tools` : ""}
8256
+ ${isMemoryEnabled ? `If user tell something that is important (like, hobbies, preferences, facts about user, hates, likes, etc) to know user better over time, use user memory tools` : ""}
8244
8257
 
8245
8258
  ${JANITOR_TOOLS_PROTOCOL(isMemoryEnabled, needTitle)}
8246
8259
  === END SYSTEM PROMPT ===${userMemories ? `
@@ -10275,11 +10288,12 @@ var init_update_file = __esm({
10275
10288
  const parsed = parseArgs(args);
10276
10289
  const targetPath = parsed.path;
10277
10290
  if (!targetPath) return 'ERROR: Missing "path" argument for update_file.';
10278
- const { patchPairs, error: parseError } = parsePatchPairs(parsed);
10291
+ const { patchPairs, allowMultiple: parsedAllowMultiple, error: parseError } = parsePatchPairs(parsed);
10279
10292
  if (parseError) return `ERROR: ${parseError}`;
10280
10293
  if (patchPairs.length === 0) {
10281
10294
  return "ERROR: No valid replacement pairs found. Use replaceContent1, newContent1, etc.";
10282
10295
  }
10296
+ const allowMultiple = parsed.allowMultiple !== void 0 ? parsed.allowMultiple === true || String(parsed.allowMultiple).toLowerCase() === "true" : parsedAllowMultiple;
10283
10297
  const absolutePath = path15.resolve(process.cwd(), targetPath);
10284
10298
  try {
10285
10299
  if (!fs16.existsSync(absolutePath)) {
@@ -10288,7 +10302,7 @@ var init_update_file = __esm({
10288
10302
  let diskContent = context.forcedContent || fs16.readFileSync(absolutePath, "utf8");
10289
10303
  if (diskContent.startsWith("\uFEFF")) diskContent = diskContent.slice(1);
10290
10304
  const originalContent = diskContent.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
10291
- const { content: finalContent, results } = applyPatches(originalContent, patchPairs);
10305
+ const { content: finalContent, results } = applyPatches(originalContent, patchPairs, { allowMultiple });
10292
10306
  const failures = results.filter((r) => !r.success);
10293
10307
  const successes = results.filter((r) => r.success);
10294
10308
  if (successes.length === 0) {
@@ -14832,10 +14846,11 @@ ${currentSummary}
14832
14846
  }
14833
14847
  const activeSummaryBlock = currentSummary && !hasExistingTurnsAfterCompression ? `
14834
14848
  [SYSTEM METADATA]
14835
- **CONTEXT SUMMARY OF PREVIOUS TURNS (PRIORITY: DYNAMIC)**
14849
+ **CONTEXT SUMMARY OF PREVIOUS TURNS**
14836
14850
  ${currentSummary}
14837
14851
  ` : "";
14838
- let dirStructure = process.cwd() + "\n" + getDirTree(process.cwd(), dynamicMaxDepth);
14852
+ let dirStructure = "CWD: " + process.cwd() + `${isPlayground ? " [PLAYGROUND MODE]" : ""}${cwdMismatch ? ` (WARNING: CWD Mismatch! Previous Path: ${lastCwd})` : ""}
14853
+ ` + getDirTree(process.cwd(), dynamicMaxDepth);
14839
14854
  const ideCtx = await getIDEContext();
14840
14855
  let ideBlock = "";
14841
14856
  if (isBridgeConnected()) {
@@ -15112,12 +15127,12 @@ ${ideCtx.warnings}
15112
15127
  }
15113
15128
  const osDetected = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
15114
15129
  const cleanPromptForModel = cleanAgentText.replace(/\\(@\[[^\]]+\])/g, "$1");
15115
- const firstUserMsg = `[SYSTEM METADATA (PRIORITY: DYNAMIC), Chat Context >> Metadata] Time: ${dateTimeStr}
15130
+ const firstUserMsg = `[SYSTEM METADATA, Chat Context > Metadata]
15131
+ Time: ${dateTimeStr}
15116
15132
  OS: ${osDetected}
15117
- CWD: ${process.cwd()}${isPlayground ? " [PLAYGROUND MODE]" : ""}${cwdMismatch ? ` (WARNING: CWD Mismatch! Previous Path: ${lastCwd})` : ""}
15118
15133
  **DIRECTORY STRUCTURE**
15119
15134
  ${dirStructure}${memoryPrompt}${ideBlock}
15120
- ${activeSummaryBlock}${thinkingLevel !== "Fast" && (aiProvider === "Mistral" || thinkingLevel !== "xHigh" && aiProvider === "Google") ? `${aiProvider === "Mistral" || modelName.toLowerCase().startsWith("gemma") ? "[SYSTEM] **STRICTLY FOLLOW THINKING POLICY AS HIGH PRIORITY. DO NOT START A RESPONSE WITHOUT <think> ... </think>** [/SYSTEM]\n" : ""}` : ""}[SYSTEM Priority: HIGH] ONLY use the system tool schema. eg: [tool:functions.ReadFolder(path=".")] [/SYSTEM]
15135
+ ${activeSummaryBlock}${thinkingLevel !== "Fast" && (aiProvider === "Mistral" || thinkingLevel !== "xHigh" && aiProvider === "Google") ? `${aiProvider === "Mistral" || modelName.toLowerCase().startsWith("gemma") ? "[SYSTEM] **STRICTLY FOLLOW THINKING POLICY AS HIGH PRIORITY. DO NOT START A RESPONSE WITHOUT <think> ... </think>** [/SYSTEM]\n" : ""}` : ""}[SYSTEM Priority: HIGH] ONLY use the system prompt tool schema. eg: [tool:functions.ReadFolder(path=".")] [/SYSTEM]
15121
15136
  ${taggedContextStr}[USER PROMPT] ${cleanPromptForModel.trim()} [/USER PROMPT]`.trim();
15122
15137
  const userMsgObj = { role: "user", text: firstUserMsg };
15123
15138
  if (attachedBinaryPart) {
@@ -16445,7 +16460,7 @@ ${ideErr} [/ERROR]`;
16445
16460
  if (normToolName === "write_file") {
16446
16461
  modifiedContent = toolArgs.content || toolArgs.newContent || "";
16447
16462
  } else {
16448
- const { patchPairs: patches, error: parseError } = parsePatchPairs(toolArgs);
16463
+ const { patchPairs: patches, allowMultiple: parsedAllowMultiple, error: parseError } = parsePatchPairs(toolArgs);
16449
16464
  if (parseError) {
16450
16465
  const errorMsg = `[TOOL RESULT]: ERROR: ${parseError}`;
16451
16466
  toolResults.push({ role: "user", text: errorMsg });
@@ -16455,8 +16470,9 @@ ${ideErr} [/ERROR]`;
16455
16470
  toolCallPointer++;
16456
16471
  continue;
16457
16472
  }
16473
+ const allowMultiple = toolArgs.allowMultiple !== void 0 ? toolArgs.allowMultiple === true || String(toolArgs.allowMultiple).toLowerCase() === "true" : parsedAllowMultiple;
16458
16474
  requestedPatchCount = patches.length;
16459
- const sim = applyPatches(originalContent, patches);
16475
+ const sim = applyPatches(originalContent, patches, { allowMultiple });
16460
16476
  modifiedContent = sim.content;
16461
16477
  patchResults = sim.results;
16462
16478
  const successes = patchResults.filter((r) => r.success);
@@ -16690,8 +16706,9 @@ ${snippet2}
16690
16706
  }
16691
16707
  if (lastToolFinishedAt > 0) {
16692
16708
  const timeSinceLastTool = Date.now() - lastToolFinishedAt;
16693
- if (timeSinceLastTool < 1500) {
16694
- await new Promise((resolve) => setTimeout(resolve, 1e3 - timeSinceLastTool));
16709
+ const delay = Math.max(0, 1e3 - timeSinceLastTool);
16710
+ if (delay > 0) {
16711
+ await new Promise((resolve) => setTimeout(resolve, delay));
16695
16712
  }
16696
16713
  }
16697
16714
  let execToolContext = {
@@ -17218,7 +17235,7 @@ Error Log can be found in ${path25.join(LOGS_DIR, "agent", "error.log")}`);
17218
17235
  "readfile": '- [tool:functions.ReadFile(path="...", startLine=number, endLine=number)]. View files',
17219
17236
  "readfolder": '- [tool:functions.ReadFolder(path="...")]. Detailed DIR stats including File Sizes',
17220
17237
  "filemap": '- [tool:functions.FileMap(path="path/file")]. Shows file structure, functions, class, import/export, variables',
17221
- "patchfile": '- [tool:functions.PatchFile(path="...", replaceContent1="full line/block", newContent1="...", ...MAX 10)]. Surgical Patch. **Multiple patch on same file/path? Use replaceContent2, newContent2 etc >>> multiple spams**. Unsure? ReadFile >> guessing. **MUST VERIFY DIFF**',
17238
+ "patchfile": '- [tool:functions.PatchFile(path="...", allowMultiple="true optional", replaceContent1="...", newContent1="...", ...MAX 10)]. Surgical patch. allowMultiple: Replace all matches (default: false). Multiple patches same file? Use replaceContent2/newContent2... Unsure? ReadFile. MUST VERIFY DIFF',
17222
17239
  "writefile": '- [tool:functions.WriteFile(path="...", content="...")]. Creates/Overwrites. File Exist? PatchFile > WriteFile. Verify Imports',
17223
17240
  "searchkeyword": '- [tool:functions.SearchKeyword(keyword="...", path="optional, target directory or filename", subString="true optional", regex="false for keyword, optional")]. Project-wide search. path limits scope to a file/dir. Find definitions/logic without full reads. Locate relevant code. Defaults: subString=false, regex=true',
17224
17241
  "websearch": '- [tool:functions.WebSearch(query="...", aiMode="true optional", limit=number)]. Limit 3-10 (aiMode ignores). Usage: unknown info/docs. aiMode: LLM search (default: false)',
@@ -22425,7 +22442,7 @@ Selection: ${val}`,
22425
22442
  initialData: profileData,
22426
22443
  onSave: (profile) => {
22427
22444
  setProfileData(profile);
22428
- setMessages((prev) => [...prev, { id: Date.now(), role: "system", text: `Profile updated: ${profile.name} (${profile.nickname})` }]);
22445
+ setMessages((prev) => [...prev, { id: Date.now(), role: "system", text: `${profile.name.length > 0 || profile.nickname.length > 0 ? `Profile Updated: ${profile.name.length > 0 ? `${profile.name} ` : ""}${profile.nickname.length > 0 ? `(${profile.nickname})` : ""}` : "Profile: Nothing to Update"}`, isMeta: true }]);
22429
22446
  setActiveView("chat");
22430
22447
  },
22431
22448
  onCancel: () => setActiveView("chat"),
@@ -22486,7 +22503,7 @@ Selection: ${val}`,
22486
22503
  }
22487
22504
  const newVal = args2.content || args2.ReplacementContent || args2.content_to_add || args2.replacementContent || args2.newContent || null;
22488
22505
  return /* @__PURE__ */ React16.createElement(Text16, { color: "white", wrap: "anywhere" }, (newVal ? newVal.replace(/\[\/n\]?/g, "\\n") : null) || "Updating file content...");
22489
- })()) : /* @__PURE__ */ React16.createElement(Box14, { marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React16.createElement(Text16, { color: "cyan", italic: true }, "\u26A1\uFE0F FluxFlow Companion is active. Review the changes in your editor.")), /* @__PURE__ */ React16.createElement(Box14, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(
22506
+ })()) : /* @__PURE__ */ React16.createElement(Box14, { marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React16.createElement(Text16, { color: "cyan", italic: true }, "FluxFlow Companion is active. Review the changes in your editor.")), /* @__PURE__ */ React16.createElement(Box14, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(
22490
22507
  CommandMenu,
22491
22508
  {
22492
22509
  title: "Action Required",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxflow-cli",
3
- "version": "3.13.4",
3
+ "version": "3.13.5",
4
4
  "date": "2026-07-29",
5
5
  "description": "A High-Fidelity Agentic CLI with Sub-Agents for the Flux Era.",
6
6
  "keywords": [