fluxflow-cli 2.6.5 → 2.6.6

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 +114 -90
  2. package/package.json +1 -1
package/dist/fluxflow.js CHANGED
@@ -12,6 +12,7 @@ var __export = (target, all) => {
12
12
  // src/utils/paths.js
13
13
  var paths_exports = {};
14
14
  __export(paths_exports, {
15
+ ACTIVE_TX_FILE: () => ACTIVE_TX_FILE,
15
16
  BACKUPS_DIR: () => BACKUPS_DIR,
16
17
  CONTEXT_FILE: () => CONTEXT_FILE,
17
18
  DATA_DIR: () => DATA_DIR,
@@ -32,7 +33,7 @@ import os from "os";
32
33
  import path from "path";
33
34
  import fs from "fs";
34
35
  import crypto from "crypto";
35
- 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, BACKUPS_DIR, LEDGER_FILE, PATHS_FILE, CONTEXT_FILE, PARSER_DIR;
36
+ 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, BACKUPS_DIR, LEDGER_FILE, ACTIVE_TX_FILE, PATHS_FILE, CONTEXT_FILE, PARSER_DIR;
36
37
  var init_paths = __esm({
37
38
  "src/utils/paths.js"() {
38
39
  FLUXFLOW_DIR = path.join(os.homedir(), ".fluxflow");
@@ -75,6 +76,7 @@ var init_paths = __esm({
75
76
  TEMP_MEM_CHAT_FILE = path.join(SECRET_DIR, "temp-memory-chat.json");
76
77
  BACKUPS_DIR = path.join(DATA_DIR, "backups");
77
78
  LEDGER_FILE = path.join(SECRET_DIR, "ledger.json");
79
+ ACTIVE_TX_FILE = path.join(SECRET_DIR, "active_tx.json");
78
80
  PATHS_FILE = path.join(SECRET_DIR, "path.json");
79
81
  CONTEXT_FILE = path.join(SECRET_DIR, "context.json");
80
82
  PARSER_DIR = path.join(DATA_DIR, "parsers");
@@ -2146,7 +2148,7 @@ Access to internal tools. MUST use the exact syntax on a new line: [tool:functio
2146
2148
 
2147
2149
  **TOOL USAGE POLICY:**
2148
2150
  - **MAX 3 TOOL CALLS PER TURN. Next Turn, verify tool results, plan next**
2149
- ${mode === "Flux" ? "- USE multiple search & replace on patch tool if editing same file/path with many changes \u2190 **MANDATORY where possible**\n- Tool execution denied? MUST use 'Ask' tool immediately to ask for reason/changes. NEVER END RESPONSE OR PROCEED BLINDLY \u2190 **MANDATORY**\n- FileMap >> ReadFile for understandling files efficiently\n- Want a spefific word/varible to find across project? SearchKeyword >> Guessing/ReadFile" : ""}- No brute force, no spamming of tools
2151
+ ${mode === "Flux" ? "- USE multiple search & replace on patch tool if editing same file/path with many changes \u2190 **HIGHLY PREFERRED**\n- Tool execution denied? MUST use 'Ask' tool immediately to ask for reason/changes. NEVER END RESPONSE OR PROCEED BLINDLY \u2190 **MANDATORY**\n- FileMap >> ReadFile for understandling files efficiently\n- Want a spefific word/varible to find across project? SearchKeyword >> Guessing/ReadFile" : ""}- No brute force, no spamming of tools
2150
2152
  ${mode === "Flux" ? "- **File Tools >> Code in chat**\n" : ""}
2151
2153
  - COMMUNICATION TOOLS -
2152
2154
  1. [tool:functions.Ask(question="...", optionA="option::description", ...MAX 4)]. Ambiguity Resolution. Mandatory Triggers: Path Divergence, Security, Risk Mitigation. ask >> finish. Suggest best options; don't ask for preferences
@@ -3422,6 +3424,51 @@ Current date and Time: ${(/* @__PURE__ */ new Date()).toLocaleString([], { year:
3422
3424
  // src/utils/revert.js
3423
3425
  import fs6 from "fs-extra";
3424
3426
  import path5 from "path";
3427
+ async function performRestoration(change, tx) {
3428
+ try {
3429
+ if (change.type === "create") {
3430
+ if (await fs6.pathExists(change.filePath)) {
3431
+ await fs6.chmod(change.filePath, 438).catch(() => {
3432
+ });
3433
+ await fs6.remove(change.filePath);
3434
+ }
3435
+ } else if (change.type === "update") {
3436
+ if (!change.backupFile) return;
3437
+ const backupPath = path5.join(BACKUPS_DIR, tx.chatId, change.backupFile);
3438
+ if (await fs6.pathExists(backupPath)) {
3439
+ const encrypted = await fs6.readFile(backupPath, "utf8");
3440
+ const decrypted = decryptAes(encrypted);
3441
+ if (await fs6.pathExists(change.filePath)) {
3442
+ await fs6.chmod(change.filePath, 438).catch(() => {
3443
+ });
3444
+ }
3445
+ await fs6.writeFile(change.filePath, decrypted, "utf8");
3446
+ } else {
3447
+ console.warn(`[RevertManager] Backup file missing: ${backupPath}`);
3448
+ }
3449
+ }
3450
+ } catch (err) {
3451
+ throw new Error(`Restoration failed for ${path5.basename(change.filePath)}: ${err.message}`);
3452
+ }
3453
+ }
3454
+ async function restoreWithRetry(change, tx, maxAttempts = 7) {
3455
+ let attempt = 0;
3456
+ while (attempt < maxAttempts) {
3457
+ try {
3458
+ await performRestoration(change, tx);
3459
+ return true;
3460
+ } catch (err) {
3461
+ attempt++;
3462
+ if (attempt >= maxAttempts) {
3463
+ console.error(`[RevertManager] Permanent failure: ${change.filePath}. ${err.message}`);
3464
+ return false;
3465
+ }
3466
+ const delay = Math.min(100 * Math.pow(2, attempt - 1), 5e3);
3467
+ await new Promise((resolve) => setTimeout(resolve, delay));
3468
+ }
3469
+ }
3470
+ return false;
3471
+ }
3425
3472
  var currentTransaction, RevertManager;
3426
3473
  var init_revert = __esm({
3427
3474
  "src/utils/revert.js"() {
@@ -3430,9 +3477,6 @@ var init_revert = __esm({
3430
3477
  fs6.ensureDirSync(BACKUPS_DIR);
3431
3478
  currentTransaction = null;
3432
3479
  RevertManager = {
3433
- /**
3434
- * Initializes a new transaction before a prompt starts processing.
3435
- */
3436
3480
  async startTransaction(chatId, promptText) {
3437
3481
  currentTransaction = {
3438
3482
  id: `tx_prompt_${Date.now()}`,
@@ -3442,122 +3486,101 @@ var init_revert = __esm({
3442
3486
  changes: [],
3443
3487
  reverted: false
3444
3488
  };
3489
+ writeEncryptedJson(ACTIVE_TX_FILE, currentTransaction);
3445
3490
  },
3446
- /**
3447
- * Records a file change under the active prompt transaction.
3448
- */
3449
3491
  async recordFileChange(absolutePath, forcedContent = null) {
3450
3492
  if (!currentTransaction) return;
3451
3493
  try {
3452
3494
  const alreadyBackedUp = currentTransaction.changes.some((c) => c.filePath === absolutePath);
3453
3495
  if (alreadyBackedUp) return;
3454
3496
  const fileExists = await fs6.pathExists(absolutePath);
3455
- let type = fileExists && !forcedContent ? "update" : forcedContent ? "update" : "create";
3456
- if (!fileExists && !forcedContent) type = "create";
3497
+ let type = fileExists || forcedContent ? "update" : "create";
3457
3498
  let backupFile = null;
3458
- if (fileExists || forcedContent) {
3459
- type = "update";
3499
+ if (type === "update") {
3460
3500
  const fileName = path5.basename(absolutePath);
3461
3501
  backupFile = `${currentTransaction.id}_${fileName}.bak`;
3462
3502
  const chatBackupDir = path5.join(BACKUPS_DIR, currentTransaction.chatId);
3463
3503
  await fs6.ensureDir(chatBackupDir);
3464
3504
  const backupPath = path5.join(chatBackupDir, backupFile);
3465
- let content;
3466
- if (forcedContent !== null) {
3467
- content = forcedContent;
3505
+ let content = forcedContent !== null ? forcedContent : await fs6.readFile(absolutePath, "utf8").catch(() => null);
3506
+ if (content !== null) {
3507
+ writeEncryptedJson(backupPath, { data: encryptAes(content) });
3468
3508
  } else {
3469
- try {
3470
- content = await fs6.readFile(absolutePath, "utf8");
3471
- } catch (readErr) {
3472
- console.warn(`[RevertManager] Could not read file for backup: ${absolutePath}. ${readErr.message}`);
3473
- type = "create";
3474
- backupFile = null;
3475
- }
3476
- }
3477
- if (backupFile) {
3478
- const encrypted = encryptAes(content);
3479
- await fs6.writeFile(backupPath, encrypted, "utf8");
3509
+ type = "create";
3510
+ backupFile = null;
3480
3511
  }
3481
3512
  }
3482
- currentTransaction.changes.push({
3483
- filePath: absolutePath,
3484
- type,
3485
- backupFile
3486
- });
3513
+ currentTransaction.changes.push({ filePath: absolutePath, type, backupFile });
3514
+ writeEncryptedJson(ACTIVE_TX_FILE, currentTransaction);
3487
3515
  } catch (err) {
3488
- console.error(`[RevertManager] Error recording file change for ${absolutePath}:`, err.message);
3489
3516
  }
3490
3517
  },
3491
- /**
3492
- * Finalizes the transaction and saves it to ledger.json.
3493
- */
3494
3518
  async commitTransaction() {
3495
3519
  if (!currentTransaction) return;
3496
- const ledger = readEncryptedJson(LEDGER_FILE, []);
3497
- ledger.push(currentTransaction);
3498
- if (ledger.length > 512e3) {
3499
- const removed = ledger.shift();
3500
- if (removed.changes) {
3501
- for (const change of removed.changes) {
3502
- if (change.backupFile) {
3503
- const backupPath = path5.join(BACKUPS_DIR, removed.chatId, change.backupFile);
3504
- await fs6.remove(backupPath);
3520
+ try {
3521
+ const ledger = readEncryptedJson(LEDGER_FILE, []);
3522
+ ledger.push(currentTransaction);
3523
+ if (ledger.length > 512e3) {
3524
+ const removed = ledger.shift();
3525
+ if (removed.changes) {
3526
+ for (const change of removed.changes) {
3527
+ if (change.backupFile) {
3528
+ await fs6.remove(path5.join(BACKUPS_DIR, removed.chatId, change.backupFile)).catch(() => {
3529
+ });
3530
+ }
3505
3531
  }
3506
3532
  }
3507
3533
  }
3534
+ writeEncryptedJson(LEDGER_FILE, ledger);
3535
+ await fs6.remove(ACTIVE_TX_FILE).catch(() => {
3536
+ });
3537
+ } catch (err) {
3538
+ } finally {
3539
+ currentTransaction = null;
3540
+ }
3541
+ },
3542
+ async recoverCrashedTransaction() {
3543
+ try {
3544
+ if (await fs6.pathExists(ACTIVE_TX_FILE)) {
3545
+ const orphanedTx = readEncryptedJson(ACTIVE_TX_FILE, null);
3546
+ if (orphanedTx?.changes?.length > 0) {
3547
+ const ledger = readEncryptedJson(LEDGER_FILE, []);
3548
+ if (!ledger.some((t) => t.id === orphanedTx.id)) {
3549
+ ledger.push(orphanedTx);
3550
+ writeEncryptedJson(LEDGER_FILE, ledger);
3551
+ }
3552
+ }
3553
+ await fs6.remove(ACTIVE_TX_FILE).catch(() => {
3554
+ });
3555
+ }
3556
+ } catch (e) {
3508
3557
  }
3509
- writeEncryptedJson(LEDGER_FILE, ledger);
3510
- currentTransaction = null;
3511
3558
  },
3512
- /**
3513
- * Reverts the codebase to a state immediately before the target transaction.
3514
- * Reverts the target transaction and all subsequent ones in reverse sequential order.
3515
- * Returns the target prompt text so it can be loaded back into the user input.
3516
- */
3517
3559
  async rollbackToBefore(txId) {
3518
- const ledger = readEncryptedJson(LEDGER_FILE, null);
3560
+ let ledger = readEncryptedJson(LEDGER_FILE, null);
3519
3561
  if (!ledger) throw new Error("No transaction ledger found.");
3520
3562
  const targetIndex = ledger.findIndex((t) => t.id === txId);
3521
3563
  if (targetIndex === -1) throw new Error(`Transaction [${txId}] not found.`);
3522
3564
  const chatId = ledger[targetIndex].chatId;
3523
3565
  const targetPrompt = ledger[targetIndex].prompt;
3524
3566
  const toRevert = ledger.slice(targetIndex).filter((t) => t.chatId === chatId && !t.reverted).reverse();
3567
+ console.log(`[RevertManager] Starting sequential rollback of ${toRevert.length} transactions...`);
3525
3568
  for (const tx of toRevert) {
3526
3569
  for (const change of [...tx.changes].reverse()) {
3527
- if (change.type === "create") {
3528
- if (await fs6.pathExists(change.filePath)) {
3529
- await fs6.remove(change.filePath);
3530
- }
3531
- } else if (change.type === "update") {
3532
- const backupPath = path5.join(BACKUPS_DIR, tx.chatId, change.backupFile);
3533
- if (await fs6.pathExists(backupPath)) {
3534
- const encrypted = await fs6.readFile(backupPath, "utf8");
3535
- const decrypted = decryptAes(encrypted);
3536
- await fs6.writeFile(change.filePath, decrypted, "utf8");
3537
- }
3538
- }
3570
+ await restoreWithRetry(change, tx);
3539
3571
  }
3540
- tx.reverted = true;
3541
- }
3542
- for (const tx of toRevert) {
3543
3572
  for (const change of tx.changes) {
3544
3573
  if (change.backupFile) {
3545
3574
  const backupPath = path5.join(BACKUPS_DIR, tx.chatId, change.backupFile);
3546
- await fs6.remove(backupPath);
3575
+ await fs6.remove(backupPath).catch(() => {
3576
+ });
3547
3577
  }
3548
3578
  }
3579
+ ledger = ledger.filter((t) => t.id !== tx.id);
3580
+ writeEncryptedJson(LEDGER_FILE, ledger);
3549
3581
  }
3550
- const updatedLedger = ledger.filter((t) => !toRevert.some((r) => r.id === t.id));
3551
- writeEncryptedJson(LEDGER_FILE, updatedLedger);
3552
- return {
3553
- success: true,
3554
- chatId,
3555
- targetPrompt
3556
- };
3582
+ return { success: true, chatId, targetPrompt };
3557
3583
  },
3558
- /**
3559
- * Gets all non-reverted prompt transactions for a specific chat.
3560
- */
3561
3584
  async getChatHistory(chatId) {
3562
3585
  try {
3563
3586
  const ledger = readEncryptedJson(LEDGER_FILE, []);
@@ -3566,19 +3589,12 @@ var init_revert = __esm({
3566
3589
  return [];
3567
3590
  }
3568
3591
  },
3569
- /**
3570
- * Cleans up all transaction logs and backups associated with a deleted chat.
3571
- */
3572
3592
  async deleteChatBackups(chatId) {
3573
3593
  try {
3574
- const chatBackupDir = path5.join(BACKUPS_DIR, chatId);
3575
- await fs6.remove(chatBackupDir);
3594
+ await fs6.remove(path5.join(BACKUPS_DIR, chatId));
3576
3595
  let ledger = readEncryptedJson(LEDGER_FILE, []);
3577
- const originalLength = ledger.length;
3578
- ledger = ledger.filter((t) => t.chatId !== chatId);
3579
- if (ledger.length !== originalLength) {
3580
- writeEncryptedJson(LEDGER_FILE, ledger);
3581
- }
3596
+ const clean = ledger.filter((t) => t.chatId !== chatId);
3597
+ if (ledger.length !== clean.length) writeEncryptedJson(LEDGER_FILE, clean);
3582
3598
  } catch (e) {
3583
3599
  }
3584
3600
  }
@@ -7978,7 +7994,14 @@ ${activeSummaryBlock}${thinkingLevel != "Fast" && aiProvider === "Google" ? `${m
7978
7994
  activeBufferType = null;
7979
7995
  remaining = combined.substring(endIdx + endTag.length);
7980
7996
  } else {
7981
- toolCallBuffer += remaining;
7997
+ const MAX_BUFFER = 512;
7998
+ if (combined.length > MAX_BUFFER) {
7999
+ msgs.push({ type: "text", content: combined });
8000
+ toolCallBuffer = "";
8001
+ } else {
8002
+ toolCallBuffer = combined;
8003
+ }
8004
+ remaining = "";
7982
8005
  break;
7983
8006
  }
7984
8007
  }
@@ -10676,6 +10699,7 @@ function App({ args = [] }) {
10676
10699
  cleanupOldLogs(LOGS_DIR);
10677
10700
  performVersionCheck(false, freshSettings);
10678
10701
  await initUsage();
10702
+ await RevertManager.recoverCrashedTransaction();
10679
10703
  if (parsedArgs.resume) {
10680
10704
  const h = await loadHistory();
10681
10705
  const id = parsedArgs.resume;
@@ -12575,7 +12599,7 @@ Selection: ${val}`,
12575
12599
  if (result.success) {
12576
12600
  const { targetPrompt } = result;
12577
12601
  deleteChatSummary(chatId);
12578
- const targetIdx = messages.findIndex(
12602
+ const targetIdx = messages.findLastIndex(
12579
12603
  (m) => m.role === "user" && m.text && (m.text.startsWith(targetPrompt) || m.text.includes(targetPrompt))
12580
12604
  );
12581
12605
  let newMsgs = [...messages];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxflow-cli",
3
- "version": "2.6.5",
3
+ "version": "2.6.6",
4
4
  "date": "2026-06-15",
5
5
  "description": "A high-fidelity agentic terminal assistant for the Flux Era.",
6
6
  "keywords": [