fluxflow-cli 1.11.0 → 1.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/fluxflow.js +114 -48
  2. package/package.json +1 -1
package/dist/fluxflow.js CHANGED
@@ -1095,7 +1095,7 @@ ${TOOL_PROTOCOL(mode)}
1095
1095
  ${projectContextBlock}
1096
1096
 
1097
1097
  -- MEMORY RULES --
1098
- - Memory: ${isMemoryEnabled ? "Use to subtly personalize. Auto Saves" : "OFF. Decline Saves"}
1098
+ - Memory: ${isMemoryEnabled ? "Use to subtly personalize. Auto Saves" : "OFF. Decline saving new memories"}
1099
1099
  - Time: Logs are timestamped. RELATIVE TIME REFERENCE e.g. few mins ago
1100
1100
 
1101
1101
  -- SECURITY RULES --
@@ -1109,7 +1109,7 @@ ${projectContextBlock}
1109
1109
 
1110
1110
  -- RESPONSE RULES --
1111
1111
  - End with [turn: continue] for more steps or [turn: finish] when done
1112
- - Always end with [turn: continue] if called any tools
1112
+ - Tool Called? No post tool chat until [turn: continue]
1113
1113
  - Task Complete? End loop with [turn: finish]
1114
1114
  [/SYSTEM]`.trim();
1115
1115
  };
@@ -1171,7 +1171,7 @@ var init_history = __esm({
1171
1171
  loadHistory = async () => {
1172
1172
  if (await fs5.pathExists(HISTORY_FILE)) {
1173
1173
  try {
1174
- return await fs5.readJson(HISTORY_FILE);
1174
+ return readEncryptedJson(HISTORY_FILE, {});
1175
1175
  } catch (e) {
1176
1176
  return {};
1177
1177
  }
@@ -1189,8 +1189,7 @@ var init_history = __esm({
1189
1189
  messages: persistentMessages,
1190
1190
  updatedAt: Date.now()
1191
1191
  };
1192
- await fs5.ensureDir(path4.dirname(HISTORY_FILE));
1193
- await fs5.writeJson(HISTORY_FILE, history, { spaces: 2 });
1192
+ writeEncryptedJson(HISTORY_FILE, history);
1194
1193
  });
1195
1194
  };
1196
1195
  saveChatTitle = async (id, title) => {
@@ -1202,15 +1201,14 @@ var init_history = __esm({
1202
1201
  } else {
1203
1202
  history[id] = { name: title, messages: [], updatedAt: Date.now() };
1204
1203
  }
1205
- await fs5.ensureDir(path4.dirname(HISTORY_FILE));
1206
- await fs5.writeJson(HISTORY_FILE, history, { spaces: 2 });
1204
+ writeEncryptedJson(HISTORY_FILE, history);
1207
1205
  });
1208
1206
  };
1209
1207
  deleteChat = async (id) => {
1210
1208
  return withLock(async () => {
1211
1209
  const history = await loadHistory();
1212
1210
  delete history[id];
1213
- await fs5.writeJson(HISTORY_FILE, history, { spaces: 2 });
1211
+ writeEncryptedJson(HISTORY_FILE, history);
1214
1212
  const temp = readEncryptedJson(TEMP_MEM_FILE, {});
1215
1213
  if (temp[id]) {
1216
1214
  delete temp[id];
@@ -1370,7 +1368,7 @@ var init_history = __esm({
1370
1368
  // src/utils/usage.js
1371
1369
  import fs6 from "fs-extra";
1372
1370
  import path5 from "path";
1373
- var cachedUsage, writeTimeout, lastWriteTime, isDirty, defaultStats, loadUsageFromFile, flushUsage, queueFlush, initUsage, forceFlushUsage, getDailyUsage, incrementUsage, addToUsage, checkQuota, getImageQuotaLimit, checkImageQuota, getImageQuotaStats, recordImageGeneration;
1371
+ var cachedUsage, writeTimeout, lastWriteTime, isDirty, defaultStats, loadUsageFromFile, flushUsage, queueFlush, initUsage, forceFlushUsage, getDailyUsage, incrementUsage, addToUsage, checkQuota, getImageQuotaBuckets, getImageQuotaLimit, checkImageQuota, getImageQuotaStats, recordImageGeneration;
1374
1372
  var init_usage = __esm({
1375
1373
  "src/utils/usage.js"() {
1376
1374
  init_paths();
@@ -1532,23 +1530,42 @@ var init_usage = __esm({
1532
1530
  }
1533
1531
  return true;
1534
1532
  };
1533
+ getImageQuotaBuckets = (imageCalls) => {
1534
+ const hourMs = 60 * 60 * 1e3;
1535
+ if (!imageCalls || imageCalls.length === 0) {
1536
+ return [];
1537
+ }
1538
+ const sortedCalls = [...imageCalls].sort((a, b) => a.timestamp - b.timestamp);
1539
+ const buckets = [];
1540
+ for (const call of sortedCalls) {
1541
+ if (buckets.length > 0) {
1542
+ const lastBucket = buckets[buckets.length - 1];
1543
+ if (call.timestamp >= lastBucket.start && call.timestamp < lastBucket.end) {
1544
+ lastBucket.calls.push(call);
1545
+ lastBucket.spent += call.cost;
1546
+ continue;
1547
+ }
1548
+ }
1549
+ buckets.push({
1550
+ start: call.timestamp,
1551
+ end: call.timestamp + hourMs,
1552
+ calls: [call],
1553
+ spent: call.cost
1554
+ });
1555
+ }
1556
+ return buckets;
1557
+ };
1535
1558
  getImageQuotaLimit = (imageCalls, now) => {
1536
1559
  const hourMs = 60 * 60 * 1e3;
1537
1560
  if (!imageCalls || imageCalls.length === 0) {
1538
1561
  return 0.025;
1539
1562
  }
1540
- const oldestTimestamp = imageCalls[0].timestamp;
1541
- const startTime = Math.min(oldestTimestamp, now - 24 * hourMs);
1542
- const windows = [];
1543
- let currentEnd = now;
1544
- while (currentEnd > startTime) {
1545
- windows.unshift({ start: currentEnd - hourMs, end: currentEnd });
1546
- currentEnd -= hourMs;
1563
+ const buckets = getImageQuotaBuckets(imageCalls);
1564
+ if (buckets.length === 0) {
1565
+ return 0.025;
1547
1566
  }
1548
1567
  const history = [];
1549
- for (const win of windows) {
1550
- const winCalls = imageCalls.filter((c) => c.timestamp >= win.start && c.timestamp < win.end);
1551
- const usage = winCalls.reduce((sum, c) => sum + c.cost, 0);
1568
+ for (const bucket of buckets) {
1552
1569
  let limit = 0.025;
1553
1570
  if (history.length > 0) {
1554
1571
  const prev1 = history[history.length - 1];
@@ -1562,26 +1579,61 @@ var init_usage = __esm({
1562
1579
  if (consecutiveMax) {
1563
1580
  limit = 0.015;
1564
1581
  } else {
1565
- const prevLimit = prev1.limit;
1566
- const prevRatio = prev1.ratio;
1567
- if (prevRatio >= 0.8) {
1568
- limit = prevLimit === 0.015 ? 0.015 : prevLimit;
1569
- } else if (prevRatio < 0.4) {
1570
- limit = Math.min(0.025, prevLimit + 5e-3);
1571
- } else if (prevRatio >= 0.4 && prevRatio < 0.6) {
1572
- limit = Math.min(0.025, prevLimit + 4e-3);
1582
+ const prevLimit2 = prev1.limit;
1583
+ const prevRatio2 = prev1.ratio;
1584
+ if (prevRatio2 >= 0.8) {
1585
+ limit = prevLimit2 === 0.015 ? 0.015 : prevLimit2;
1586
+ } else if (prevRatio2 < 0.4) {
1587
+ limit = Math.min(0.025, prevLimit2 + 5e-3);
1588
+ } else if (prevRatio2 >= 0.4 && prevRatio2 < 0.6) {
1589
+ limit = Math.min(0.025, prevLimit2 + 4e-3);
1573
1590
  } else {
1574
- limit = Math.min(0.025, prevLimit + 2e-3);
1591
+ limit = Math.min(0.025, prevLimit2 + 2e-3);
1575
1592
  }
1576
1593
  }
1577
1594
  }
1578
- const ratio = limit > 0 ? usage / limit : 0;
1579
- history.push({ limit, usage, ratio });
1595
+ const ratio = limit > 0 ? bucket.spent / limit : 0;
1596
+ history.push({ limit, spent: bucket.spent, ratio });
1580
1597
  }
1581
- if (history.length > 0) {
1598
+ const lastBucket = buckets[buckets.length - 1];
1599
+ if (now < lastBucket.end) {
1582
1600
  return history[history.length - 1].limit;
1583
1601
  }
1584
- return 0.025;
1602
+ let currentLimit = history[history.length - 1].limit;
1603
+ let prevLimit = currentLimit;
1604
+ let prevRatio = history[history.length - 1].ratio;
1605
+ let simulatedTime = lastBucket.end;
1606
+ let consecutiveMaxCount = 0;
1607
+ for (let k = history.length - 1; k >= 0; k--) {
1608
+ if (history[k].ratio >= 0.8) {
1609
+ consecutiveMaxCount++;
1610
+ } else {
1611
+ break;
1612
+ }
1613
+ }
1614
+ while (simulatedTime <= now) {
1615
+ let limit = 0.025;
1616
+ const consecutiveMax = consecutiveMaxCount >= 2;
1617
+ if (consecutiveMax) {
1618
+ limit = 0.015;
1619
+ } else {
1620
+ if (prevRatio >= 0.8) {
1621
+ limit = prevLimit === 0.015 ? 0.015 : prevLimit;
1622
+ } else if (prevRatio < 0.4) {
1623
+ limit = Math.min(0.025, prevLimit + 5e-3);
1624
+ } else if (prevRatio >= 0.4 && prevRatio < 0.6) {
1625
+ limit = Math.min(0.025, prevLimit + 4e-3);
1626
+ } else {
1627
+ limit = Math.min(0.025, prevLimit + 2e-3);
1628
+ }
1629
+ }
1630
+ prevLimit = limit;
1631
+ prevRatio = 0;
1632
+ consecutiveMaxCount = 0;
1633
+ simulatedTime += hourMs;
1634
+ currentLimit = limit;
1635
+ }
1636
+ return currentLimit;
1585
1637
  };
1586
1638
  checkImageQuota = async (settings) => {
1587
1639
  const imageSettings = settings.imageSettings || { keyType: "Default", quality: "Low-High" };
@@ -1601,9 +1653,14 @@ var init_usage = __esm({
1601
1653
  stats.imageCalls = [];
1602
1654
  }
1603
1655
  const now = Date.now();
1604
- const oneHourAgo = now - 60 * 60 * 1e3;
1605
- const activeCalls = stats.imageCalls.filter((c) => c.timestamp >= oneHourAgo);
1606
- const totalSpent = activeCalls.reduce((sum, c) => sum + c.cost, 0);
1656
+ const buckets = getImageQuotaBuckets(stats.imageCalls);
1657
+ let totalSpent = 0;
1658
+ if (buckets.length > 0) {
1659
+ const lastBucket = buckets[buckets.length - 1];
1660
+ if (now >= lastBucket.start && now < lastBucket.end) {
1661
+ totalSpent = lastBucket.spent;
1662
+ }
1663
+ }
1607
1664
  const currentLimit = getImageQuotaLimit(stats.imageCalls, now);
1608
1665
  return totalSpent + currentCost <= currentLimit;
1609
1666
  };
@@ -1613,19 +1670,21 @@ var init_usage = __esm({
1613
1670
  stats.imageCalls = [];
1614
1671
  }
1615
1672
  const now = Date.now();
1616
- const oneHourAgo = now - 60 * 60 * 1e3;
1617
- const activeCalls = stats.imageCalls.filter((c) => c.timestamp >= oneHourAgo);
1618
- const totalSpent = activeCalls.reduce((sum, c) => sum + c.cost, 0);
1619
- const currentLimit = getImageQuotaLimit(stats.imageCalls, now);
1620
- const remaining = Math.max(0, currentLimit - totalSpent);
1673
+ const buckets = getImageQuotaBuckets(stats.imageCalls);
1674
+ let activeCalls = [];
1675
+ let totalSpent = 0;
1621
1676
  let nextResetMin = 0;
1622
- let reclaimCost = 0;
1623
- if (activeCalls.length > 0) {
1624
- const earliestCall = activeCalls.reduce((min, c) => c.timestamp < min.timestamp ? c : min, activeCalls[0]);
1625
- const nextResetTimestamp = earliestCall.timestamp + 60 * 60 * 1e3;
1626
- nextResetMin = Math.max(0, Math.ceil((nextResetTimestamp - now) / (60 * 1e3)));
1627
- reclaimCost = earliestCall.cost;
1677
+ if (buckets.length > 0) {
1678
+ const lastBucket = buckets[buckets.length - 1];
1679
+ if (now >= lastBucket.start && now < lastBucket.end) {
1680
+ activeCalls = lastBucket.calls;
1681
+ totalSpent = lastBucket.spent;
1682
+ nextResetMin = Math.max(0, Math.ceil((lastBucket.end - now) / (60 * 1e3)));
1683
+ }
1628
1684
  }
1685
+ const currentLimit = getImageQuotaLimit(stats.imageCalls, now);
1686
+ const remaining = Math.max(0, currentLimit - totalSpent);
1687
+ const reclaimCost = totalSpent;
1629
1688
  return {
1630
1689
  totalSpent,
1631
1690
  remaining,
@@ -3740,6 +3799,7 @@ ${thinkingLevel != "Fast" ? "[SYSTEM] **STRICTLY FOLLOW THINKING POLICY AS STRIC
3740
3799
  let toolCallPointer = 0;
3741
3800
  let isThinkingLoop = false;
3742
3801
  let isStutteringLoop = false;
3802
+ let isGeneralLoop = false;
3743
3803
  let isInitialAttempt = true;
3744
3804
  let accumulatedContext = "";
3745
3805
  let dedupeBuffer = "";
@@ -3964,6 +4024,7 @@ ${thinkingLevel != "Fast" ? "[SYSTEM] **STRICTLY FOLLOW THINKING POLICY AS STRIC
3964
4024
  if (respRepetitionRatio > repetitionThresholdResponse) {
3965
4025
  yield { type: "status", content: `Response Loop Detected. Re-centering...` };
3966
4026
  isThinkingLoop = false;
4027
+ isGeneralLoop = true;
3967
4028
  await new Promise((resolve) => setTimeout(resolve, 3e3));
3968
4029
  break;
3969
4030
  }
@@ -4237,8 +4298,10 @@ ${boxBottom}` };
4237
4298
  const hasFinish2 = /\[\s*(turn\s*:)?\s*finish\s*\]/i.test(signalSafeText2.toLowerCase());
4238
4299
  const hasContinue = /\[\s*(turn\s*:)?\s*continue\s*\]/i.test(signalSafeText2.toLowerCase());
4239
4300
  const didCallTool = toolResults.length > 0 || lastToolSniffed !== null;
4240
- if (!hasFinish2 && !hasContinue && !didCallTool && signalSafeText2.length > 0) {
4241
- throw new Error("Silent stream cutoff (500): Model stream closed cleanly but missing turn finish/continue signals.");
4301
+ const pureOutputText = signalSafeText2.replace(/<think>[\s\S]*?<\/think>/gi, "").trim();
4302
+ const endsNormally = /[.!?}"'`’“”]$|```$/s.test(pureOutputText);
4303
+ if (!hasFinish2 && !hasContinue && !didCallTool && signalSafeText2.length > 0 && !endsNormally && !isThinkingLoop && !isStutteringLoop && !isGeneralLoop) {
4304
+ throw new Error("Silent stream cutoff (500): Model stream closed cleanly but cut off mid-sentence without signals.");
4242
4305
  }
4243
4306
  success = true;
4244
4307
  await incrementUsage("agent");
@@ -4317,6 +4380,8 @@ Error Log can be found in ${path15.join(LOGS_DIR, "agent", "error.log")}`);
4317
4380
  } else {
4318
4381
  if (retryCount <= MAX_RETRIES) {
4319
4382
  retryCount++;
4383
+ inStreamRetryCount = 1;
4384
+ accumulatedContext = "";
4320
4385
  const waitTime = Math.min(1e3 * Math.pow(2, retryCount - 1), 32e3);
4321
4386
  isInitialAttempt = true;
4322
4387
  yield { type: "status", content: `Retrying Connection (${retryCount}/${MAX_RETRIES}) [${(waitTime / 1e3).toFixed(0)}s]...` };
@@ -4379,6 +4444,7 @@ ${timestamp}`;
4379
4444
  modifiedHistory.push({ role: "user", text: `[SYSTEM] ${isStutteringLoop && !isThinkingLoop ? `STUTTERING DETECTED by Internal System. Re-calibrate your response & proceed.` : `${isThinkingLoop ? " OVER-THINKING" : " LOOP"} DETECTED by Internal System${isThinkingLoop ? " for current EFFORT_LEVEL" : ""}. ${isThinkingLoop ? "If you have planned the task, prioritize the execution/output. " : "If you have finished your task use [turn: finish] else continue."}`}` });
4380
4445
  isThinkingLoop = false;
4381
4446
  isStutteringLoop = false;
4447
+ isGeneralLoop = false;
4382
4448
  }
4383
4449
  }
4384
4450
  yield { type: "status", content: null };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxflow-cli",
3
- "version": "1.11.0",
3
+ "version": "1.11.2",
4
4
  "description": "A high-fidelity agentic terminal assistant for the Flux Era.",
5
5
  "keywords": [
6
6
  "ai",