fluxflow-cli 1.12.7 → 1.13.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 +426 -41
  2. package/package.json +2 -2
package/dist/fluxflow.js CHANGED
@@ -464,6 +464,7 @@ var init_ChatLayout = __esm({
464
464
  { cmd: "/clear", desc: "Clear terminal screen" },
465
465
  { cmd: "/resume", desc: "Load previous session" },
466
466
  { cmd: "/save", desc: "Force save current chat" },
467
+ { cmd: "/export", desc: "Export current chat in a .txt file" },
467
468
  { cmd: "/chats", desc: "List all chat sessions" },
468
469
  { cmd: "/image", desc: "Generate images" },
469
470
  { cmd: "/mode", desc: "Toggle Flux/Flow modes" },
@@ -820,6 +821,7 @@ __export(paths_exports, {
820
821
  MEMORIES_FILE: () => MEMORIES_FILE,
821
822
  SECRET_DIR: () => SECRET_DIR,
822
823
  SETTINGS_FILE: () => SETTINGS_FILE,
824
+ TEMP_MEM_CHAT_FILE: () => TEMP_MEM_CHAT_FILE,
823
825
  TEMP_MEM_FILE: () => TEMP_MEM_FILE,
824
826
  USAGE_FILE: () => USAGE_FILE
825
827
  });
@@ -827,7 +829,7 @@ import os2 from "os";
827
829
  import path2 from "path";
828
830
  import fs2 from "fs";
829
831
  import crypto2 from "crypto";
830
- var FLUXFLOW_DIR, SETTINGS_FILE, externalDir, DATA_DIR, LOGS_DIR, SECRET_DIR, HISTORY_FILE, USAGE_FILE, MEMORIES_FILE, TEMP_MEM_FILE;
832
+ var FLUXFLOW_DIR, SETTINGS_FILE, externalDir, DATA_DIR, LOGS_DIR, SECRET_DIR, HISTORY_FILE, USAGE_FILE, MEMORIES_FILE, TEMP_MEM_FILE, TEMP_MEM_CHAT_FILE;
831
833
  var init_paths = __esm({
832
834
  "src/utils/paths.js"() {
833
835
  FLUXFLOW_DIR = path2.join(os2.homedir(), ".fluxflow");
@@ -867,6 +869,7 @@ var init_paths = __esm({
867
869
  USAGE_FILE = path2.join(FLUXFLOW_DIR, "usage.json");
868
870
  MEMORIES_FILE = path2.join(SECRET_DIR, "memories.json");
869
871
  TEMP_MEM_FILE = path2.join(SECRET_DIR, "memory-temp.json");
872
+ TEMP_MEM_CHAT_FILE = path2.join(SECRET_DIR, "temp-memory-chat.json");
870
873
  }
871
874
  });
872
875
 
@@ -975,7 +978,7 @@ ${mode === "Flux" ? `- FILE TOOLS (path = relative to CWD) -
975
978
  - File structure: Real newlines for code formatting`.trim() : `
976
979
  - DEV TOOLS ARE NOT AVAILABLE IN FLOW MODE`.trim()}
977
980
 
978
- - Results: Passed as [TOOL RESULT] SYSTEM
981
+ - Results: Passed as [TOOL RESULT] (system)
979
982
  - Tool calls: End with [turn: continue]. Only use [turn: finish] after verifying goals
980
983
  - Multi-call: Stack upto 5`.trim();
981
984
  }
@@ -990,13 +993,17 @@ Your tool syntax is: '[tool:functions.ToolName(args...)]'
990
993
 
991
994
  -- CHAT MANAGEMENT TOOLS (MUST CALL THESE 2 TOOLS ALWAYS) --
992
995
  [tool:functions.Chat(title="<short creative title of FULL conversation in 3-5 words>")]. Consider full chat context to generate title NOT just latest message.
993
- [tool:functions.Memory(action="temp", content="<summary of the user prompt & model responses ONLY FROM LATEST PROMPT UNDER 40 WORDS>. [Talked on: <date> <hour>]")]
996
+ [tool:functions.Memory(action="temp", content="<summary of the user prompt & model responses ONLY FROM LATEST PROMPT UNDER 40 WORDS>. [Talked on: <date> <hour>]")]. Time format: YYYY-MM-DD HH am/pm
994
997
 
995
998
  ${isMemoryEnabled ? `-- User-specific long-term/permanent memory (USE BASED ON CONVERSATION CONTEXT, DO NOT RE-SAVE MEMORY WHICH IS ALREADY SAVED) --
996
- - Add: [tool:functions.Memory(action="user", method="add", content="<string to add>. [Saved on: <date ONLY>]")]
999
+ - Add: [tool:functions.Memory(action="user", method="add", content="<string to add>. [Saved on: <date ONLY>]", score=2)] (Set score=2 ONLY if the user explicitly asked to "remember" or "save" this information, else omit this parameter entirely to default to 0.5)
997
1000
  - Delete: [tool:functions.Memory(action="user", method="delete", id="<memory id>")]
998
1001
  - Update: [tool:functions.Memory(action="user", method="update", content-new="string to update", id="<memory id>")]
999
1002
 
1003
+ -- Memory Relevance Decay Tool --
1004
+ - Score Adjustment: [tool:functions.addMemScore(id="<memory id>")]
1005
+ You MUST call this tool when a specific saved memory in the '-- CURRENT SAVED USER MEMORIES --' list was highly relevant, referenced, or helpful in the agent's response or user prompt. You can stack multiple calls.
1006
+
1000
1007
  Explicit Triggers for permanent memory:
1001
1008
  - User explicitly asks to 'remember' something.
1002
1009
  - User mentions something important that should be remembered.
@@ -1097,7 +1104,7 @@ Check these first; these files > training data for project consistency. Safety r
1097
1104
  Identity: Flux Flow (by Kushal Roy Chowdhury). Sassy, Friendly, Humorous, CLI Agent. No flirting ${mode === "Flux" ? "" : ""}
1098
1105
  Mode: ${mode}${thinkingLevel !== "Fast" ? "(Thinking Mode)" : ""}. ${mode === "Flux" ? "Goal-oriented, Logical" : "Conversational & UX-focused"}
1099
1106
  CWD: ${cwdStr}.${isSystemDir ? " [PROTECTED: ASK BEFORE MODIFYING]" : ""} OS: ${osDetected}${osDetected === "Windows" && mode === "Flux" ? ". PS via CMD" : ""}
1100
- High Priority: [SYSTEM], [STEERING HINT]
1107
+ High Priority: [SYSTEM], [STEERING HINT].
1101
1108
 
1102
1109
  -- THINKING RULES --
1103
1110
  ${thinkingConfig}
@@ -1117,6 +1124,7 @@ ${projectContextBlock}
1117
1124
  -- SECURITY RULES --
1118
1125
  - EXTERNAL ACCESS: ${systemSettings.allowExternalAccess ? "ENABLED" : "RESTRICTED CWD only"}
1119
1126
  - Sensitive files? Ask before Read
1127
+ [SYSTEM] >>> [USER]
1120
1128
 
1121
1129
  -- FORMATTING --
1122
1130
  - Clean, concise responses
@@ -1226,6 +1234,11 @@ var init_history = __esm({
1226
1234
  delete temp[id];
1227
1235
  writeEncryptedJson(TEMP_MEM_FILE, temp);
1228
1236
  }
1237
+ const cache = readEncryptedJson(TEMP_MEM_CHAT_FILE, {});
1238
+ if (cache[id]) {
1239
+ delete cache[id];
1240
+ writeEncryptedJson(TEMP_MEM_CHAT_FILE, cache);
1241
+ }
1229
1242
  return history;
1230
1243
  });
1231
1244
  };
@@ -2098,16 +2111,21 @@ ${cleanedHtml}${htmlContent.length > 3e4 ? "\n\n[TRUNCATED AT 30K CHARS]" : ""}`
2098
2111
  });
2099
2112
 
2100
2113
  // src/tools/memory.js
2101
- var memory;
2114
+ var USER_MEMORY_SIZE, memory;
2102
2115
  var init_memory = __esm({
2103
2116
  "src/tools/memory.js"() {
2104
2117
  init_crypto();
2105
2118
  init_paths();
2119
+ USER_MEMORY_SIZE = 4 * (1024 * 2);
2106
2120
  memory = async (rawArgs, context = {}) => {
2107
2121
  const parseArg = (key) => {
2108
- const regex = new RegExp(`${key}\\s*=\\s*(["'])(.*?)\\1(?=\\s*[,)]|\\s+\\w+\\s*=|$)`, "s");
2109
- const match = rawArgs.match(regex);
2110
- return match ? match[2].trim() : null;
2122
+ const quotedRegex = new RegExp(`${key}\\s*[:=]\\s*(["'])(.*?)\\1(?=\\s*[,)]|\\s+\\w+\\s*[:=]|$)`, "s");
2123
+ const quotedMatch = rawArgs.match(quotedRegex);
2124
+ if (quotedMatch) return quotedMatch[2].trim();
2125
+ const unquotedRegex = new RegExp(`${key}\\s*[:=]\\s*([^,\\s)]+)`, "s");
2126
+ const unquotedMatch = rawArgs.match(unquotedRegex);
2127
+ if (unquotedMatch) return unquotedMatch[1].trim();
2128
+ return null;
2111
2129
  };
2112
2130
  const action = parseArg("action");
2113
2131
  const method = parseArg("method");
@@ -2120,33 +2138,37 @@ var init_memory = __esm({
2120
2138
  if (!content) return "ERROR: Missing 'content' for temp memory.";
2121
2139
  const tempStorage = readEncryptedJson(TEMP_MEM_FILE, {});
2122
2140
  if (!tempStorage[chatId]) tempStorage[chatId] = [];
2123
- const MAX_CHARS = 1024 * 4;
2124
- let currentTotalLength = tempStorage[chatId].reduce((acc, m) => acc + m.length, 0);
2125
- while (tempStorage[chatId].length > 0 && currentTotalLength + content.length > MAX_CHARS) {
2126
- const removed = tempStorage[chatId].shift();
2127
- currentTotalLength -= removed.length;
2128
- }
2129
2141
  tempStorage[chatId].push(content);
2130
2142
  writeEncryptedJson(TEMP_MEM_FILE, tempStorage);
2131
- return `SUCCESS: Temporary context saved for session [${chatId}]. (Size: ${currentTotalLength + content.length} chars)`;
2143
+ const currentTotalLength = tempStorage[chatId].reduce((acc, m) => acc + m.length, 0);
2144
+ return `SUCCESS: Temporary context saved for session [${chatId}]. (Size: ${currentTotalLength} chars)`;
2132
2145
  }
2133
2146
  if (action === "user") {
2134
- const memories = readEncryptedJson(MEMORIES_FILE, []);
2147
+ const memories = readEncryptedJson(MEMORIES_FILE, []).map((m) => {
2148
+ if (m.score === void 0) m.score = 0.5;
2149
+ return m;
2150
+ });
2135
2151
  if (method === "add") {
2136
2152
  if (!content) return "ERROR: Missing 'content' for memory addition.";
2137
2153
  const now = /* @__PURE__ */ new Date();
2138
2154
  const dateStr = `${now.getDate()}/${now.getMonth() + 1}/${now.getFullYear()}`;
2139
2155
  const formattedContent = content.includes("[Saved on:") ? content : `${content.trim()} [Saved on: ${dateStr}]`;
2140
- const MAX_CHARS = 1024 * 4;
2156
+ const MAX_CHARS = USER_MEMORY_SIZE;
2141
2157
  let currentTotalLength = memories.reduce((acc, m) => acc + (m.memory?.length || 0), 0);
2142
2158
  while (memories.length > 0 && currentTotalLength + formattedContent.length > MAX_CHARS) {
2143
2159
  const removed = memories.shift();
2144
2160
  currentTotalLength -= removed.memory?.length || 0;
2145
2161
  }
2146
- const newMemory = { id: `mem-${Date.now().toString(36)}`, memory: formattedContent };
2162
+ const scoreArg = parseArg("score");
2163
+ const initialScore = scoreArg ? parseFloat(scoreArg) : 0.5;
2164
+ const newMemory = {
2165
+ id: `mem-${Date.now().toString(36)}`,
2166
+ memory: formattedContent,
2167
+ score: Math.min(2, isNaN(initialScore) ? 0.5 : initialScore)
2168
+ };
2147
2169
  memories.push(newMemory);
2148
2170
  writeEncryptedJson(MEMORIES_FILE, memories);
2149
- return `SUCCESS: Memory added with ID [${newMemory.id}]. (Vault Size: ${currentTotalLength + formattedContent.length} chars)`;
2171
+ return `SUCCESS: Memory added with ID [${newMemory.id}] and score [${newMemory.score}]. (Vault Size: ${currentTotalLength + formattedContent.length} chars)`;
2150
2172
  }
2151
2173
  if (method === "update") {
2152
2174
  const memId = id || contentOld;
@@ -3035,13 +3057,13 @@ var init_search_keyword = __esm({
3035
3057
  let command = "";
3036
3058
  if (isWindows) {
3037
3059
  const excludePattern = excludes.join("|").replace(/\./g, "\\.");
3038
- command = `powershell -Command "Get-ChildItem -Path . -Recurse -File | Where-Object { $_.FullName -notmatch '${excludePattern}' } | Select-String -Pattern '${keyword}' | Select-Object -First 100 | ForEach-Object { $rel = Resolve-Path $_.Path -Relative; '{0}:{1}:' -f $rel, $_.LineNumber }"`;
3060
+ command = `powershell -Command "Get-ChildItem -Path . -Recurse -File | Where-Object { $_.FullName -notmatch '${excludePattern}' } | Select-String -Pattern '${keyword}' | Select-Object -First 150 | ForEach-Object { $rel = Resolve-Path $_.Path -Relative; '{0}:{1}:' -f $rel, $_.LineNumber }"`;
3039
3061
  } else {
3040
3062
  const excludeDirArgs = excludes.map((d) => `--exclude-dir="${d}"`).join(" ");
3041
- command = `grep -rnI ${excludeDirArgs} "${keyword}" . | head -n 100`;
3063
+ command = `grep -rnI ${excludeDirArgs} "${keyword}" . | head -n 150`;
3042
3064
  }
3043
3065
  return new Promise((resolve) => {
3044
- exec(command, { cwd: process.cwd(), maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
3066
+ exec(command, { cwd: process.cwd(), maxBuffer: 15 * 1024 * 1024 }, (error, stdout, stderr) => {
3045
3067
  if (error && error.code === 1 && !stdout) {
3046
3068
  return resolve(`Found 0 matches for keyword: "${keyword}"`);
3047
3069
  }
@@ -3054,8 +3076,8 @@ var init_search_keyword = __esm({
3054
3076
  const lower = line.toLowerCase();
3055
3077
  return !lower.includes("node_modules") && !lower.includes(".git") && !lower.includes("dist") && !lower.includes(".next") && !lower.includes(".gemini");
3056
3078
  });
3057
- if (filteredLines.length === 0) return resolve(`Found 0 matches for keyword: "${keyword}" (Filtered out system noise)`);
3058
- const matches = filteredLines.slice(0, 100).map((line) => {
3079
+ if (filteredLines.length === 0) return resolve(`Found 0 matches for keyword: "${keyword}"`);
3080
+ const matches = filteredLines.slice(0, 150).map((line) => {
3059
3081
  const firstColon = line.indexOf(":");
3060
3082
  const secondColon = line.indexOf(":", firstColon + 1);
3061
3083
  if (firstColon === -1 || secondColon === -1) return null;
@@ -3067,8 +3089,8 @@ var init_search_keyword = __esm({
3067
3089
 
3068
3090
  `;
3069
3091
  output += matches.join("\n");
3070
- if (filteredLines.length > 100) {
3071
- output += "\n\n... (Truncated to first 100 matches)";
3092
+ if (filteredLines.length > 150) {
3093
+ output += "\n\n... (Truncated to first 150 matches)";
3072
3094
  }
3073
3095
  resolve(output);
3074
3096
  });
@@ -3409,6 +3431,89 @@ var init_generate_image = __esm({
3409
3431
  }
3410
3432
  });
3411
3433
 
3434
+ // src/tools/saveSummary.js
3435
+ var saveSummary;
3436
+ var init_saveSummary = __esm({
3437
+ "src/tools/saveSummary.js"() {
3438
+ init_crypto();
3439
+ init_paths();
3440
+ saveSummary = async (rawArgs, context = {}) => {
3441
+ const parseArg = (key) => {
3442
+ const regex = new RegExp(`${key}\\s*[:=]\\s*(["'])(.*?)\\1(?=\\s*[,)]|\\s+\\w+\\s*[:=]|$)`, "s");
3443
+ const match = rawArgs.match(regex);
3444
+ return match ? match[2].trim() : null;
3445
+ };
3446
+ const id = parseArg("id");
3447
+ const summary = parseArg("summary");
3448
+ if (!id || !summary) {
3449
+ return "ERROR: Missing 'id' or 'summary' for saveSummary tool.";
3450
+ }
3451
+ try {
3452
+ const tempStorage = readEncryptedJson(TEMP_MEM_FILE, {});
3453
+ const cacheStorage = readEncryptedJson(TEMP_MEM_CHAT_FILE, {});
3454
+ cacheStorage[id] = summary;
3455
+ delete tempStorage[id];
3456
+ writeEncryptedJson(TEMP_MEM_CHAT_FILE, cacheStorage);
3457
+ writeEncryptedJson(TEMP_MEM_FILE, tempStorage);
3458
+ return `SUCCESS: Saved summary and purged raw memories for chat [${id}].`;
3459
+ } catch (err) {
3460
+ return `ERROR: Failed to save summary for chat [${id}]: ${err.message}`;
3461
+ }
3462
+ };
3463
+ }
3464
+ });
3465
+
3466
+ // src/tools/addMemScore.js
3467
+ var addMemScore;
3468
+ var init_addMemScore = __esm({
3469
+ "src/tools/addMemScore.js"() {
3470
+ init_crypto();
3471
+ init_paths();
3472
+ addMemScore = async (rawArgs, context = {}) => {
3473
+ const parseArg = (key) => {
3474
+ const regex = new RegExp(`${key}\\s*[:=]\\s*(["'])(.*?)\\1(?=\\s*[,)]|\\s+\\w+\\s*[:=]|$)`, "s");
3475
+ const match = rawArgs.match(regex);
3476
+ return match ? match[2].trim() : null;
3477
+ };
3478
+ const id = parseArg("id");
3479
+ if (!id) {
3480
+ return "ERROR: Missing 'id' parameter for addMemScore tool.";
3481
+ }
3482
+ try {
3483
+ const memories = readEncryptedJson(MEMORIES_FILE, []);
3484
+ let found = false;
3485
+ const updatedMemories = [];
3486
+ for (const mem of memories) {
3487
+ if (mem.score === void 0) {
3488
+ mem.score = 0.5;
3489
+ }
3490
+ if (mem.id === id) {
3491
+ mem.score = Math.min(2, mem.score + 0.2);
3492
+ found = true;
3493
+ } else {
3494
+ mem.score *= 0.98;
3495
+ if (mem.score < 0.05) mem.score = 0;
3496
+ }
3497
+ mem.score = Math.round(mem.score * 1e5) / 1e5;
3498
+ if (mem.score > 0) {
3499
+ updatedMemories.push(mem);
3500
+ }
3501
+ }
3502
+ writeEncryptedJson(MEMORIES_FILE, updatedMemories);
3503
+ if (!found) {
3504
+ return `WARNING: Memory ID [${id}] not found. Other memories decayed by -0.01.`;
3505
+ }
3506
+ const activeTarget = updatedMemories.find((m) => m.id === id);
3507
+ const finalScoreStr = activeTarget ? activeTarget.score.toFixed(2) : "deleted (score <= 0)";
3508
+ const deletedCount = memories.length - updatedMemories.length;
3509
+ return `SUCCESS: Adjusted memory scores. Target [${id}] is now ${finalScoreStr}.${deletedCount > 0 ? ` Purged ${deletedCount} decayed memories.` : ""}`;
3510
+ } catch (err) {
3511
+ return `ERROR: Failed to adjust memory score for [${id}]: ${err.message}`;
3512
+ }
3513
+ };
3514
+ }
3515
+ });
3516
+
3412
3517
  // src/utils/tools.js
3413
3518
  var TOOL_MAP, dispatchTool;
3414
3519
  var init_tools = __esm({
@@ -3427,6 +3532,8 @@ var init_tools = __esm({
3427
3532
  init_write_docx();
3428
3533
  init_search_keyword();
3429
3534
  init_generate_image();
3535
+ init_saveSummary();
3536
+ init_addMemScore();
3430
3537
  TOOL_MAP = {
3431
3538
  web_search,
3432
3539
  web_scrape,
@@ -3441,6 +3548,8 @@ var init_tools = __esm({
3441
3548
  write_docx,
3442
3549
  search_keyword,
3443
3550
  generate_image,
3551
+ saveSummary,
3552
+ addMemScore,
3444
3553
  ask: ask_user,
3445
3554
  // PascalCase Normalizations for Token Efficiency
3446
3555
  Ask: ask_user,
@@ -3456,7 +3565,14 @@ var init_tools = __esm({
3456
3565
  SearchKeyword: search_keyword,
3457
3566
  Memory: memory,
3458
3567
  Chat: chat,
3459
- GenerateImage: generate_image
3568
+ GenerateImage: generate_image,
3569
+ saveSumary: saveSummary,
3570
+ SaveSummary: saveSummary,
3571
+ SaveSumary: saveSummary,
3572
+ add_mem_score: addMemScore,
3573
+ AddMemScore: addMemScore,
3574
+ addMemoryScore: addMemScore,
3575
+ AddMemoryScore: addMemScore
3460
3576
  };
3461
3577
  dispatchTool = async (toolName, args, context = {}) => {
3462
3578
  const tool = TOOL_MAP[toolName];
@@ -3476,7 +3592,7 @@ var init_tools = __esm({
3476
3592
  import { GoogleGenAI, ThinkingLevel, HarmBlockThreshold, HarmCategory } from "@google/genai";
3477
3593
  import path14 from "path";
3478
3594
  import fs15 from "fs";
3479
- var client, TERMINATION_SIGNAL, signalTermination, TOOL_LABELS2, getToolDetail, runJanitorTask, getActiveToolContext, getContextSafeText, contextSafeReplace, getSanitizedText, detectToolCalls, initAI, getAIStream;
3595
+ var client, TERMINATION_SIGNAL, signalTermination, TOOL_LABELS2, getToolDetail, runJanitorTask, getActiveToolContext, getContextSafeText, contextSafeReplace, getSanitizedText, detectToolCalls, initAI, consolidatePastMemories, getAIStream;
3480
3596
  var init_ai = __esm({
3481
3597
  "src/utils/ai.js"() {
3482
3598
  init_prompts();
@@ -3585,8 +3701,8 @@ ${originalTextProcessed.length > USER_CONTEXT_LENGTH ? "... (truncated) ...\n\n"
3585
3701
  contents: janitorContents,
3586
3702
  config: {
3587
3703
  systemInstruction: janitorPrompt,
3588
- maxOutputTokens: 384,
3589
- temperature: 0.69,
3704
+ maxOutputTokens: 512,
3705
+ temperature: 0.3,
3590
3706
  safetySettings: [
3591
3707
  { category: HarmCategory.HARM_CATEGORY_HARASSMENT, threshold: HarmBlockThreshold.BLOCK_NONE },
3592
3708
  { category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold: HarmBlockThreshold.BLOCK_NONE },
@@ -3630,14 +3746,19 @@ ${originalTextProcessed.length > USER_CONTEXT_LENGTH ? "... (truncated) ...\n\n"
3630
3746
  await incrementUsage("background");
3631
3747
  }
3632
3748
  const janitorToolCalls = detectToolCalls(finalSynthesis);
3749
+ let scoreToolCalled = false;
3633
3750
  for (const janitorToolCall of janitorToolCalls) {
3751
+ const toolName = janitorToolCall.toolName;
3752
+ if (["addMemScore", "add_mem_score", "AddMemScore", "addMemoryScore", "AddMemoryScore"].includes(toolName)) {
3753
+ scoreToolCalled = true;
3754
+ }
3634
3755
  const toolContext = { chatId, sessionId: chatId, history };
3635
- const result = await dispatchTool(janitorToolCall.toolName, janitorToolCall.args, toolContext);
3756
+ const result = await dispatchTool(toolName, janitorToolCall.args, toolContext);
3636
3757
  const date = (/* @__PURE__ */ new Date()).toLocaleString();
3637
3758
  const janitorLogDir = path14.join(LOGS_DIR, "janitor");
3638
- fs15.appendFileSync(path14.join(janitorLogDir, "debug.log"), `DEBUG [${date}]: RESULT [${janitorToolCall.toolName}]: ${result}
3759
+ fs15.appendFileSync(path14.join(janitorLogDir, "debug.log"), `DEBUG [${date}]: RESULT [${toolName}]: ${result}
3639
3760
  `);
3640
- if (janitorToolCall.toolName.toLowerCase() === "memory" && !janitorToolCall.args.includes("action='temp'")) {
3761
+ if (toolName.toLowerCase() === "memory" && !janitorToolCall.args.includes("action='temp'")) {
3641
3762
  if (onMemoryUpdated) onMemoryUpdated();
3642
3763
  if (process.stdout.isTTY) {
3643
3764
  process.stdout.write(`\x1B]0;Memory Updated\x07`);
@@ -3645,6 +3766,27 @@ ${originalTextProcessed.length > USER_CONTEXT_LENGTH ? "... (truncated) ...\n\n"
3645
3766
  await new Promise((resolve) => setTimeout(resolve, 3e3));
3646
3767
  }
3647
3768
  }
3769
+ if (!scoreToolCalled) {
3770
+ try {
3771
+ const memories = readEncryptedJson(MEMORIES_FILE, []);
3772
+ if (memories.length > 0) {
3773
+ const updatedMemories = [];
3774
+ for (const mem of memories) {
3775
+ if (mem.score === void 0) {
3776
+ mem.score = 0.5;
3777
+ }
3778
+ mem.score *= 0.9995;
3779
+ if (mem.score < 0.05) mem.score = 0;
3780
+ mem.score = Math.round(mem.score * 1e5) / 1e5;
3781
+ if (mem.score > 0) {
3782
+ updatedMemories.push(mem);
3783
+ }
3784
+ }
3785
+ writeEncryptedJson(MEMORIES_FILE, updatedMemories);
3786
+ }
3787
+ } catch (decayErr) {
3788
+ }
3789
+ }
3648
3790
  break;
3649
3791
  } catch (janitorErr) {
3650
3792
  attempts++;
@@ -3881,6 +4023,100 @@ ${originalTextProcessed.length > USER_CONTEXT_LENGTH ? "... (truncated) ...\n\n"
3881
4023
  client = new GoogleGenAI({ apiKey });
3882
4024
  return client;
3883
4025
  };
4026
+ consolidatePastMemories = async (currentChatId, settings) => {
4027
+ try {
4028
+ const tempStorage = readEncryptedJson(TEMP_MEM_FILE, {});
4029
+ const totalMemoriesCount = Object.values(tempStorage).flat().length;
4030
+ if (totalMemoriesCount <= 15) return;
4031
+ const chatsToSummarize = Object.keys(tempStorage).filter((id) => {
4032
+ return id !== currentChatId && Array.isArray(tempStorage[id]) && tempStorage[id].length > 3;
4033
+ });
4034
+ if (chatsToSummarize.length === 0) return;
4035
+ let prompt = `You are a silent background process for the FluxFlow CLI Agent.
4036
+ Your task is to summarize or merge temporary context memories from one or more past conversation sessions.
4037
+ For each Chat ID provided, you must output a tool call to save the consolidated summary.
4038
+
4039
+ The tool call format MUST be:
4040
+ [tool:functions.saveSummary(id="<chat-id>", summary="<updated summary string, max 400 words>")]
4041
+
4042
+ Guidelines:
4043
+ - Create a single, updated, highly cohesive, and concise summary statement (max 400 words) for each Chat ID. It should contain WHAT user talked about, WHAT were the tasks, Temporal info, HOW/WHAT the model responded. DON'T REMOVE ANY KEY AND TURN BY TURN INFO DENSITY.
4044
+ - Focus on key goals, preferences, modified files, and technical decisions.
4045
+ - Under no circumstances write normal conversational text. Output ONLY the tool calls.
4046
+ - You can stack multiple tool calls for multiple chats.
4047
+
4048
+ Chats to process:
4049
+
4050
+ `;
4051
+ const cacheStorage = readEncryptedJson(TEMP_MEM_CHAT_FILE, {});
4052
+ for (const id of chatsToSummarize) {
4053
+ const rawMemories = tempStorage[id];
4054
+ const newMemoryListStr = rawMemories.map((m) => `- ${m}`).join("\n");
4055
+ const oldSummary = cacheStorage[id];
4056
+ prompt += `[Chat ID: ${id}]
4057
+ `;
4058
+ if (oldSummary) {
4059
+ prompt += `- Existing Summary: "${oldSummary}"
4060
+ `;
4061
+ prompt += `-- New Memories to integrate:
4062
+ ${newMemoryListStr}
4063
+
4064
+ `;
4065
+ } else {
4066
+ prompt += `-- Individual Memories:
4067
+ ${newMemoryListStr}
4068
+
4069
+ `;
4070
+ }
4071
+ }
4072
+ let attempts = 0;
4073
+ const maxAttempts = 3;
4074
+ let success = false;
4075
+ while (attempts < maxAttempts && !success) {
4076
+ attempts++;
4077
+ try {
4078
+ const response = await client.models.generateContent({
4079
+ model: "gemini-3.1-flash-lite",
4080
+ contents: prompt,
4081
+ config: {
4082
+ temperature: 0.3,
4083
+ safetySettings: [
4084
+ { category: HarmCategory.HARM_CATEGORY_HARASSMENT, threshold: HarmBlockThreshold.BLOCK_NONE },
4085
+ { category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold: HarmBlockThreshold.BLOCK_NONE },
4086
+ { category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, threshold: HarmBlockThreshold.BLOCK_NONE },
4087
+ { category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, threshold: HarmBlockThreshold.BLOCK_NONE }
4088
+ ],
4089
+ thinkingConfig: { includeThoughts: false, thinkingLevel: ThinkingLevel.LOW }
4090
+ }
4091
+ });
4092
+ const responseText = response.text || "";
4093
+ const janitorToolCalls = detectToolCalls(responseText);
4094
+ if (janitorToolCalls.length === 0) {
4095
+ throw new Error("No tool calls detected in synthesis response");
4096
+ }
4097
+ for (const janitorToolCall of janitorToolCalls) {
4098
+ const toolName = janitorToolCall.toolName;
4099
+ if (["saveSummary", "saveSumary", "SaveSummary", "SaveSumary"].includes(toolName)) {
4100
+ await dispatchTool(toolName, janitorToolCall.args, { chatId: currentChatId });
4101
+ }
4102
+ }
4103
+ success = true;
4104
+ } catch (err) {
4105
+ if (attempts >= maxAttempts) {
4106
+ throw new Error(`Failed after ${maxAttempts} attempts. Last error: ${err.message}`);
4107
+ }
4108
+ }
4109
+ }
4110
+ } catch (err) {
4111
+ const janitorLogDir = path14.join(LOGS_DIR, "janitor");
4112
+ if (!fs15.existsSync(janitorLogDir)) fs15.mkdirSync(janitorLogDir, { recursive: true });
4113
+ fs15.appendFileSync(
4114
+ path14.join(janitorLogDir, "error.log"),
4115
+ `[${(/* @__PURE__ */ new Date()).toLocaleString()}] Past memory batch consolidation error: ${err.message}
4116
+ `
4117
+ );
4118
+ }
4119
+ };
3884
4120
  getAIStream = async function* (modelName, history, settings, steeringCallback, versionFluxflow2) {
3885
4121
  if (!client) throw new Error("AI not initialized");
3886
4122
  const { profile, thinkingLevel, mode, janitorModel, chatId, systemSettings, sessionStats } = settings;
@@ -3894,8 +4130,15 @@ ${originalTextProcessed.length > USER_CONTEXT_LENGTH ? "... (truncated) ...\n\n"
3894
4130
  if (systemSettings?.compression === 0 && (sessionStats?.tokens || 0) > 254e3) {
3895
4131
  modifiedHistory = getTruncatedHistory(modifiedHistory, 6);
3896
4132
  }
4133
+ if (isFirstPrompt && isMemoryEnabled) {
4134
+ yield { type: "status", content: "Condensing past chat memories..." };
4135
+ await consolidatePastMemories(chatId, settings);
4136
+ }
3897
4137
  const tempStorage = readEncryptedJson(TEMP_MEM_FILE, {});
3898
- const otherMemories = Object.entries(tempStorage).filter(([id]) => id !== chatId).flatMap(([_, mems]) => mems).map((mem) => `- ${mem}`).join("\n");
4138
+ const cacheStorage = readEncryptedJson(TEMP_MEM_CHAT_FILE, {});
4139
+ const otherRawMemories = Object.entries(tempStorage).filter(([id]) => id !== chatId).flatMap(([_, mems]) => mems);
4140
+ const cachedSummaries = Object.entries(cacheStorage).filter(([id]) => id !== chatId).slice(-20).map(([id, summary]) => `[Chat Summary]: ${summary}`);
4141
+ const otherMemories = [...cachedSummaries, ...otherRawMemories].map((mem) => `- ${mem}`).join("\n");
3899
4142
  const persistentStorage = readEncryptedJson(MEMORIES_FILE, []);
3900
4143
  const mainUserMemories = persistentStorage.map((m) => `- ${m.memory}`).join("\n");
3901
4144
  const isContext32k = (sessionStats?.tokens || 0) >= 32e3;
@@ -3990,8 +4233,8 @@ ${thinkingLevel != "Fast" ? "[SYSTEM] **STRICTLY FOLLOW THINKING POLICY AS STRIC
3990
4233
  targetModel = "gemini-3-flash-preview";
3991
4234
  yield { type: "model_update", content: "Trying with fallback model" };
3992
4235
  } else if (retryCount === MAX_RETRIES) {
3993
- targetModel = "gemini-3.1-flash-lite";
3994
- yield { type: "model_update", content: "Trying with fallback model lite" };
4236
+ targetModel = "gemini-3.5-flash";
4237
+ yield { type: "model_update", content: "Trying with fallback model" };
3995
4238
  } else if (retryCount > 12 && retryCount < MAX_RETRIES - 2 && settings.apiKey !== "custom") {
3996
4239
  targetModel = "gemma-4-31b-it";
3997
4240
  yield { type: "model_update", content: "Trying with fallback Gemma Model" };
@@ -4014,6 +4257,7 @@ ${thinkingLevel != "Fast" ? "[SYSTEM] **STRICTLY FOLLOW THINKING POLICY AS STRIC
4014
4257
  lastUserMsg.parts[0].text += `
4015
4258
  [SYSTEM] WARNING, Turn Limit Impending: Step ${currentStep}/${MAX_LOOPS}. Wrap up quickly/prompt user to continue & use [turn:finish] quickly.`;
4016
4259
  }
4260
+ fs15.writeFileSync("contents.txt", currentSystemInstruction + "\n\n" + firstUserMsg);
4017
4261
  stream = await client.models.generateContentStream({
4018
4262
  model: targetModel || "gemma-4-31b-it",
4019
4263
  contents,
@@ -4757,8 +5001,19 @@ function MemoryModal({ onClose }) {
4757
5001
  if (!text) return "";
4758
5002
  return text.replace(/\[Saved on: .*?\]/g, "").replace(/\\+'/g, "'").trim();
4759
5003
  };
5004
+ const totalCapacity = 4 * 1024 * 2;
5005
+ const currentLength = memories.reduce((acc, m) => acc + (m.memory?.length || 0), 0);
5006
+ const usagePercent = Math.min(100, Math.round(currentLength / totalCapacity * 100));
5007
+ const barWidth = 12;
5008
+ const filledCount = Math.round(usagePercent / 100 * barWidth);
5009
+ const barStr = "\u2588".repeat(filledCount) + "\u2591".repeat(Math.max(0, barWidth - filledCount));
5010
+ const getBarColor = () => {
5011
+ if (usagePercent < 50) return "green";
5012
+ if (usagePercent < 90) return "yellow";
5013
+ return "red";
5014
+ };
4760
5015
  const s = emojiSpace(2);
4761
- return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "gray", padding: 0, width: 80 }, /* @__PURE__ */ React8.createElement(Box8, { paddingX: 1, marginBottom: 1 }, /* @__PURE__ */ React8.createElement(Text8, { color: "cyan", bold: true }, "\u{1F9E0} AGENT MEMORY: LONG-TERM KNOWLEDGE")), !isMemoryOn && memories.length > 0 ? /* @__PURE__ */ React8.createElement(Box8, { paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React8.createElement(Text8, { italic: true, color: "gray" }, "Memory is currently Off...")) : memories.length === 0 ? /* @__PURE__ */ React8.createElement(Box8, { paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React8.createElement(Text8, { italic: true, color: "gray" }, isMemoryOn ? "Learning..." : "Memory not available...")) : /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column" }, memories.map((mem, idx) => {
5016
+ return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "gray", padding: 0, width: 80 }, /* @__PURE__ */ React8.createElement(Box8, { paddingX: 1, marginBottom: 1, justifyContent: "space-between" }, /* @__PURE__ */ React8.createElement(Text8, { color: "cyan", bold: true }, "\u{1F9E0} AGENT MEMORY: LONG-TERM KNOWLEDGE"), /* @__PURE__ */ React8.createElement(Box8, null, /* @__PURE__ */ React8.createElement(Text8, { color: "gray" }, "Vault: "), /* @__PURE__ */ React8.createElement(Text8, { color: getBarColor() }, barStr), /* @__PURE__ */ React8.createElement(Text8, { color: "white", bold: true }, " ", usagePercent, "%"))), !isMemoryOn && memories.length > 0 ? /* @__PURE__ */ React8.createElement(Box8, { paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React8.createElement(Text8, { italic: true, color: "gray" }, "Memory is currently Off...")) : memories.length === 0 ? /* @__PURE__ */ React8.createElement(Box8, { paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React8.createElement(Text8, { italic: true, color: "gray" }, isMemoryOn ? "Learning..." : "Memory not available...")) : /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column" }, memories.map((mem, idx) => {
4762
5017
  const isSelected = idx === selectedIndex;
4763
5018
  return /* @__PURE__ */ React8.createElement(
4764
5019
  Box8,
@@ -5454,6 +5709,7 @@ function App({ args = [] }) {
5454
5709
  { cmd: "/clear", desc: "Clear terminal screen" },
5455
5710
  { cmd: "/resume", desc: "Load previous session" },
5456
5711
  { cmd: "/save", desc: "Force save current chat" },
5712
+ { cmd: "/export", desc: "Export current chat in a .txt file" },
5457
5713
  { cmd: "/chats", desc: "List all chat sessions" },
5458
5714
  {
5459
5715
  cmd: "/image",
@@ -5515,8 +5771,7 @@ function App({ args = [] }) {
5515
5771
  { cmd: "gemma-4-31b-it", desc: apiTier === "Free" ? "Standard Default (Free, Recommended)" : "Standard Default (Free, Recommended) - Use Free API Key to use this model " },
5516
5772
  { cmd: "gemini-3.1-pro-preview", desc: "Most Capable (Paid)" },
5517
5773
  { cmd: "gemini-3-flash-preview", desc: "Fast & Lightweight (Paid, Limited Free quota)" },
5518
- { cmd: "gemini-3.5-flash", desc: "New (Paid, Limited Free quota)" },
5519
- { cmd: "gemini-3.1-flash-lite", desc: "Ultra Fast (Paid, Decent Free quota)" }
5774
+ { cmd: "gemini-3.5-flash", desc: "New (Paid, Limited Free quota)" }
5520
5775
  ]
5521
5776
  },
5522
5777
  { cmd: "/settings", desc: "Configure system prefs" },
@@ -5868,6 +6123,71 @@ ${hintText}`, color: "magenta" }];
5868
6123
  });
5869
6124
  break;
5870
6125
  }
6126
+ case "/export": {
6127
+ const exportFile = `export-fluxflow-${chatId}.txt`;
6128
+ const exportPath = path15.join(process.cwd(), exportFile);
6129
+ const exportLines = [];
6130
+ let insideAgentBlock = false;
6131
+ for (let i = 0; i < messages.length; i++) {
6132
+ const msg = messages[i];
6133
+ if (!msg) continue;
6134
+ if (msg.role === "system" || msg.isMeta || msg.isLogo || String(msg.id).startsWith("welcome")) {
6135
+ continue;
6136
+ }
6137
+ if (msg.role === "user") {
6138
+ let cleanUserText = msg.text || "";
6139
+ cleanUserText = cleanUserText.replace(/\s*\[Prompted on:.*?\]/g, "").trim();
6140
+ if (exportLines.length > 0) {
6141
+ exportLines.push("");
6142
+ }
6143
+ exportLines.push("[USER]");
6144
+ exportLines.push(cleanUserText);
6145
+ insideAgentBlock = false;
6146
+ } else if (msg.role === "think") {
6147
+ if (!insideAgentBlock) {
6148
+ exportLines.push("");
6149
+ exportLines.push("[AGENT]");
6150
+ insideAgentBlock = true;
6151
+ }
6152
+ const cleanThinkText = (msg.text || "").replace(/\[turn:\s*continue\]/gi, "").replace(/\[turn:\s*finish\]/gi, "").replace(/\[TOOL RESULTS\]/gi, "").trim();
6153
+ if (cleanThinkText) {
6154
+ exportLines.push("[thoughts]");
6155
+ exportLines.push(cleanThinkText);
6156
+ }
6157
+ } else if (msg.role === "agent") {
6158
+ if (!insideAgentBlock) {
6159
+ exportLines.push("");
6160
+ exportLines.push("[AGENT]");
6161
+ insideAgentBlock = true;
6162
+ }
6163
+ const blocks = parseAgentText(msg.text || "");
6164
+ for (const block of blocks) {
6165
+ if (block.type === "output") {
6166
+ const cleanContent = block.content.replace(/\[turn:\s*continue\]/gi, "").replace(/\[turn:\s*finish\]/gi, "").replace(/\[TOOL RESULTS\]/gi, "").trim();
6167
+ if (cleanContent) {
6168
+ exportLines.push("[output]");
6169
+ exportLines.push(cleanContent);
6170
+ }
6171
+ } else if (block.type === "tool") {
6172
+ exportLines.push("[tool]");
6173
+ exportLines.push(`${block.toolName} ${block.args}`);
6174
+ }
6175
+ }
6176
+ }
6177
+ }
6178
+ const fileContent = exportLines.join("\n");
6179
+ fs17.writeFileSync(exportPath, fileContent, "utf8");
6180
+ setMessages((prev) => {
6181
+ setCompletedIndex(prev.length + 1);
6182
+ return [...prev, {
6183
+ id: Date.now(),
6184
+ role: "system",
6185
+ text: `\u{1F4E4} [EXPORT] Chat exported successfully to "${exportFile}"`,
6186
+ isMeta: true
6187
+ }];
6188
+ });
6189
+ break;
6190
+ }
5871
6191
  case "/chats": {
5872
6192
  const run = async () => {
5873
6193
  const history = await loadHistory();
@@ -6959,7 +7279,7 @@ Selection: ${val}`,
6959
7279
  );
6960
7280
  })()));
6961
7281
  }
6962
- var SESSION_START_TIME, CHANGELOG_URL, packageJsonPath, packageJson, versionFluxflow, updatedOn, ResolutionModal, FLUX_LOGO;
7282
+ var SESSION_START_TIME, CHANGELOG_URL, packageJsonPath, packageJson, versionFluxflow, updatedOn, ResolutionModal, FLUX_LOGO, parseAgentText;
6963
7283
  var init_app = __esm({
6964
7284
  "src/app.jsx"() {
6965
7285
  init_ChatLayout();
@@ -7010,6 +7330,71 @@ var init_app = __esm({
7010
7330
  \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2554\u255D \u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D
7011
7331
  \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D`
7012
7332
  );
7333
+ parseAgentText = (text) => {
7334
+ const blocks = [];
7335
+ const toolRegex = /\[\s*tool:functions\.([a-z0-9_]+)\s*\(/gi;
7336
+ let lastIdx = 0;
7337
+ let match;
7338
+ while ((match = toolRegex.exec(text)) !== null) {
7339
+ const toolName = match[1];
7340
+ const startIdx = match.index + match[0].length - 1;
7341
+ let balance = 0;
7342
+ let inString = null;
7343
+ let isEscaped = false;
7344
+ let endIdx = -1;
7345
+ let closingParenIdx = -1;
7346
+ for (let i = startIdx; i < text.length; i++) {
7347
+ const char = text[i];
7348
+ if (!inString && (char === '"' || char === "'" || char === "`")) {
7349
+ inString = char;
7350
+ isEscaped = false;
7351
+ } else if (inString && char === inString && !isEscaped) {
7352
+ inString = null;
7353
+ }
7354
+ if (!inString) {
7355
+ if (char === "(") balance++;
7356
+ else if (char === ")") balance--;
7357
+ if (balance === 0) {
7358
+ closingParenIdx = i;
7359
+ let j = i + 1;
7360
+ while (j < text.length && /\s/.test(text[j])) j++;
7361
+ if (j < text.length && text[j] === "]") {
7362
+ endIdx = j;
7363
+ break;
7364
+ }
7365
+ }
7366
+ }
7367
+ if (char === "\\") {
7368
+ isEscaped = !isEscaped;
7369
+ } else {
7370
+ isEscaped = false;
7371
+ }
7372
+ }
7373
+ if (endIdx !== -1) {
7374
+ const beforeText = text.substring(lastIdx, match.index);
7375
+ if (beforeText.trim()) {
7376
+ blocks.push({ type: "output", content: beforeText });
7377
+ }
7378
+ const finalArgsText = text.substring(startIdx + 1, closingParenIdx);
7379
+ blocks.push({
7380
+ type: "tool",
7381
+ toolName: toolName.trim(),
7382
+ args: finalArgsText.trim()
7383
+ });
7384
+ lastIdx = endIdx + 1;
7385
+ toolRegex.lastIndex = lastIdx;
7386
+ } else {
7387
+ break;
7388
+ }
7389
+ }
7390
+ if (lastIdx < text.length) {
7391
+ const remainingText = text.substring(lastIdx);
7392
+ if (remainingText.trim()) {
7393
+ blocks.push({ type: "output", content: remainingText });
7394
+ }
7395
+ }
7396
+ return blocks;
7397
+ };
7013
7398
  }
7014
7399
  });
7015
7400
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fluxflow-cli",
3
- "version": "1.12.7",
4
- "date": "2026-05-23",
3
+ "version": "1.13.0",
4
+ "date": "2026-05-24",
5
5
  "description": "A high-fidelity agentic terminal assistant for the Flux Era.",
6
6
  "keywords": [
7
7
  "ai",