fluxflow-cli 1.10.10 → 1.11.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.
- package/ARCHITECTURE.md +2 -2
- package/dist/fluxflow.js +278 -38
- package/package.json +1 -1
package/ARCHITECTURE.md
CHANGED
|
@@ -25,9 +25,9 @@ The execution flow of a single user prompt follows this loop:
|
|
|
25
25
|
5. **Turn Management & Continuation**: The model is instructed to append `[turn: finish]` if its goal is complete, or `[turn: continue]` if it expects tool results.
|
|
26
26
|
- If tools were called or `[turn: continue]` is present, the loop increments and re-prompts the model with the newly gathered `[TOOL RESULT]` data.
|
|
27
27
|
- If `[turn: finish]` is detected and no further tools were called, the main loop terminates, passing the final synthesized context to the background Janitor process.
|
|
28
|
-
6. **Loop Limits & Resilience**: To prevent infinite loops or excessive API usage, **Flux mode** is capped at
|
|
28
|
+
6. **Loop Limits & Resilience**: To prevent infinite loops or excessive API usage, **Flux mode** is capped at 70 iterations per user prompt, while **Flow mode** is capped at 7.
|
|
29
29
|
- **Multi-Stage Failover**: The loop features a sophisticated 8-attempt retry engine with random backoff (800ms - 2s).
|
|
30
|
-
- **Critical Fallback Pivot**: If the primary model fails
|
|
30
|
+
- **Critical Fallback Pivot**: If the primary model fails 13 consecutive times, the agent surgically pivots to a lighter, high-concurrency fallback model (`gemini-3.1-flash-lite`) for the final 3 attempts to ensure session navigation through API congestion.
|
|
31
31
|
|
|
32
32
|
## Multimodal Pipeline
|
|
33
33
|
|
package/dist/fluxflow.js
CHANGED
|
@@ -728,7 +728,8 @@ var init_AskUserModal = __esm({
|
|
|
728
728
|
// src/utils/crypto.js
|
|
729
729
|
import fs from "fs";
|
|
730
730
|
import path from "path";
|
|
731
|
-
|
|
731
|
+
import crypto from "crypto";
|
|
732
|
+
var XOR_KEY, xorTransform, AES_ALGORITHM, AES_KEY, encryptAes, decryptAes, readEncryptedJson, writeEncryptedJson, readAesEncryptedJson, writeAesEncryptedJson;
|
|
732
733
|
var init_crypto = __esm({
|
|
733
734
|
"src/utils/crypto.js"() {
|
|
734
735
|
XOR_KEY = 66;
|
|
@@ -740,12 +741,45 @@ var init_crypto = __esm({
|
|
|
740
741
|
}
|
|
741
742
|
return result;
|
|
742
743
|
};
|
|
744
|
+
AES_ALGORITHM = "aes-256-cbc";
|
|
745
|
+
AES_KEY = crypto.createHash("sha256").update("fluxflow-cli-sanctuary-key").digest();
|
|
746
|
+
encryptAes = (text) => {
|
|
747
|
+
const iv = crypto.randomBytes(16);
|
|
748
|
+
const cipher = crypto.createCipheriv(AES_ALGORITHM, AES_KEY, iv);
|
|
749
|
+
let encrypted = cipher.update(text, "utf8", "hex");
|
|
750
|
+
encrypted += cipher.final("hex");
|
|
751
|
+
return iv.toString("hex") + ":" + encrypted;
|
|
752
|
+
};
|
|
753
|
+
decryptAes = (encryptedText) => {
|
|
754
|
+
const parts = encryptedText.split(":");
|
|
755
|
+
if (parts.length !== 2) {
|
|
756
|
+
throw new Error("Invalid AES format");
|
|
757
|
+
}
|
|
758
|
+
const iv = Buffer.from(parts[0], "hex");
|
|
759
|
+
const ciphertext = parts[1];
|
|
760
|
+
const decipher = crypto.createDecipheriv(AES_ALGORITHM, AES_KEY, iv);
|
|
761
|
+
let decrypted = decipher.update(ciphertext, "hex", "utf8");
|
|
762
|
+
decrypted += decipher.final("utf8");
|
|
763
|
+
return decrypted;
|
|
764
|
+
};
|
|
743
765
|
readEncryptedJson = (filePath, defaultValue = {}) => {
|
|
744
766
|
try {
|
|
745
767
|
if (!fs.existsSync(filePath)) return defaultValue;
|
|
746
|
-
const
|
|
747
|
-
const
|
|
748
|
-
|
|
768
|
+
const rawContent = fs.readFileSync(filePath);
|
|
769
|
+
const fileContent = rawContent.toString("utf8").trim();
|
|
770
|
+
if (fileContent.startsWith("{") || fileContent.startsWith("[")) {
|
|
771
|
+
return JSON.parse(fileContent);
|
|
772
|
+
}
|
|
773
|
+
try {
|
|
774
|
+
const decrypted = decryptAes(fileContent);
|
|
775
|
+
return JSON.parse(decrypted);
|
|
776
|
+
} catch (aesErr) {
|
|
777
|
+
}
|
|
778
|
+
const decryptedDataXor = xorTransform(rawContent).toString("utf8");
|
|
779
|
+
if (decryptedDataXor.startsWith("{") || decryptedDataXor.startsWith("[")) {
|
|
780
|
+
return JSON.parse(decryptedDataXor);
|
|
781
|
+
}
|
|
782
|
+
throw new Error("Unsupported or corrupt encryption format");
|
|
749
783
|
} catch (err) {
|
|
750
784
|
console.error(`Vault Read Error [${path.basename(filePath)}]:`, err.message);
|
|
751
785
|
return defaultValue;
|
|
@@ -756,12 +790,14 @@ var init_crypto = __esm({
|
|
|
756
790
|
const dir = path.dirname(filePath);
|
|
757
791
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
758
792
|
const jsonData = JSON.stringify(data, null, 2);
|
|
759
|
-
const
|
|
760
|
-
fs.writeFileSync(filePath,
|
|
793
|
+
const encrypted = encryptAes(jsonData);
|
|
794
|
+
fs.writeFileSync(filePath, encrypted, "utf8");
|
|
761
795
|
} catch (err) {
|
|
762
796
|
console.error(`Vault Write Error [${path.basename(filePath)}]:`, err.message);
|
|
763
797
|
}
|
|
764
798
|
};
|
|
799
|
+
readAesEncryptedJson = readEncryptedJson;
|
|
800
|
+
writeAesEncryptedJson = writeEncryptedJson;
|
|
765
801
|
}
|
|
766
802
|
});
|
|
767
803
|
|
|
@@ -781,6 +817,7 @@ __export(paths_exports, {
|
|
|
781
817
|
import os2 from "os";
|
|
782
818
|
import path2 from "path";
|
|
783
819
|
import fs2 from "fs";
|
|
820
|
+
import crypto2 from "crypto";
|
|
784
821
|
var FLUXFLOW_DIR, SETTINGS_FILE, externalDir, DATA_DIR, LOGS_DIR, SECRET_DIR, HISTORY_FILE, USAGE_FILE, MEMORIES_FILE, TEMP_MEM_FILE;
|
|
785
822
|
var init_paths = __esm({
|
|
786
823
|
"src/utils/paths.js"() {
|
|
@@ -789,10 +826,27 @@ var init_paths = __esm({
|
|
|
789
826
|
externalDir = null;
|
|
790
827
|
try {
|
|
791
828
|
if (fs2.existsSync(SETTINGS_FILE)) {
|
|
792
|
-
const
|
|
793
|
-
|
|
794
|
-
if (
|
|
795
|
-
|
|
829
|
+
const fileContent = fs2.readFileSync(SETTINGS_FILE, "utf8").trim();
|
|
830
|
+
let settings;
|
|
831
|
+
if (fileContent.startsWith("{")) {
|
|
832
|
+
settings = JSON.parse(fileContent);
|
|
833
|
+
} else {
|
|
834
|
+
const parts = fileContent.split(":");
|
|
835
|
+
if (parts.length === 2) {
|
|
836
|
+
const iv = Buffer.from(parts[0], "hex");
|
|
837
|
+
const ciphertext = parts[1];
|
|
838
|
+
const key = crypto2.createHash("sha256").update("fluxflow-cli-sanctuary-key").digest();
|
|
839
|
+
const decipher = crypto2.createDecipheriv("aes-256-cbc", key, iv);
|
|
840
|
+
let decrypted = decipher.update(ciphertext, "hex", "utf8");
|
|
841
|
+
decrypted += decipher.final("utf8");
|
|
842
|
+
settings = JSON.parse(decrypted);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
if (settings) {
|
|
846
|
+
const sys = settings.systemSettings || {};
|
|
847
|
+
if (sys.useExternalData && sys.externalDataPath) {
|
|
848
|
+
externalDir = sys.externalDataPath;
|
|
849
|
+
}
|
|
796
850
|
}
|
|
797
851
|
}
|
|
798
852
|
} catch (e) {
|
|
@@ -801,7 +855,7 @@ var init_paths = __esm({
|
|
|
801
855
|
LOGS_DIR = path2.join(DATA_DIR, "logs");
|
|
802
856
|
SECRET_DIR = path2.join(DATA_DIR, "secret");
|
|
803
857
|
HISTORY_FILE = path2.join(SECRET_DIR, "history.json");
|
|
804
|
-
USAGE_FILE = path2.join(
|
|
858
|
+
USAGE_FILE = path2.join(FLUXFLOW_DIR, "usage.json");
|
|
805
859
|
MEMORIES_FILE = path2.join(SECRET_DIR, "memories.json");
|
|
806
860
|
TEMP_MEM_FILE = path2.join(SECRET_DIR, "memory-temp.json");
|
|
807
861
|
}
|
|
@@ -1095,7 +1149,7 @@ Current date and Time: ${(/* @__PURE__ */ new Date()).toLocaleString([], { year:
|
|
|
1095
1149
|
import fs5 from "fs-extra";
|
|
1096
1150
|
import path4 from "path";
|
|
1097
1151
|
import { nanoid } from "nanoid";
|
|
1098
|
-
var WRITE_LOCK, withLock, loadHistory, saveChat, saveChatTitle, deleteChat, generateChatId, cleanupOldHistory, getTruncatedHistory;
|
|
1152
|
+
var WRITE_LOCK, withLock, loadHistory, saveChat, saveChatTitle, deleteChat, generateChatId, cleanupOldHistory, parseCustomDate, cleanupLogFile, cleanupOldLogs, getTruncatedHistory;
|
|
1099
1153
|
var init_history = __esm({
|
|
1100
1154
|
"src/utils/history.js"() {
|
|
1101
1155
|
init_crypto();
|
|
@@ -1183,6 +1237,125 @@ var init_history = __esm({
|
|
|
1183
1237
|
}
|
|
1184
1238
|
return deletedCount;
|
|
1185
1239
|
};
|
|
1240
|
+
parseCustomDate = (dateStr) => {
|
|
1241
|
+
const cleanStr = dateStr.replace(/[\[\]]/g, "").trim();
|
|
1242
|
+
const parsed = new Date(cleanStr);
|
|
1243
|
+
if (!isNaN(parsed.getTime())) return parsed.getTime();
|
|
1244
|
+
const parts = cleanStr.split(/,\s*|\s+/);
|
|
1245
|
+
if (parts.length === 0) return null;
|
|
1246
|
+
const datePart = parts[0];
|
|
1247
|
+
const timePart = parts[1] || "";
|
|
1248
|
+
const ampm = parts[2] || "";
|
|
1249
|
+
const dateNums = datePart.split(/[-/.]/).map(Number);
|
|
1250
|
+
if (dateNums.length !== 3) return null;
|
|
1251
|
+
let year, month, day;
|
|
1252
|
+
if (dateNums[0] > 1e3) {
|
|
1253
|
+
year = dateNums[0];
|
|
1254
|
+
month = dateNums[1];
|
|
1255
|
+
day = dateNums[2];
|
|
1256
|
+
} else if (dateNums[2] > 1e3) {
|
|
1257
|
+
year = dateNums[2];
|
|
1258
|
+
if (dateNums[0] > 12) {
|
|
1259
|
+
day = dateNums[0];
|
|
1260
|
+
month = dateNums[1];
|
|
1261
|
+
} else if (dateNums[1] > 12) {
|
|
1262
|
+
day = dateNums[1];
|
|
1263
|
+
month = dateNums[0];
|
|
1264
|
+
} else {
|
|
1265
|
+
month = dateNums[0];
|
|
1266
|
+
day = dateNums[1];
|
|
1267
|
+
}
|
|
1268
|
+
} else {
|
|
1269
|
+
return null;
|
|
1270
|
+
}
|
|
1271
|
+
let hours = 0, minutes = 0, seconds = 0;
|
|
1272
|
+
if (timePart) {
|
|
1273
|
+
const timeNums = timePart.split(":").map(Number);
|
|
1274
|
+
hours = timeNums[0] || 0;
|
|
1275
|
+
minutes = timeNums[1] || 0;
|
|
1276
|
+
seconds = timeNums[2] || 0;
|
|
1277
|
+
if (ampm.toLowerCase() === "pm" && hours < 12) {
|
|
1278
|
+
hours += 12;
|
|
1279
|
+
} else if (ampm.toLowerCase() === "am" && hours === 12) {
|
|
1280
|
+
hours = 0;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
const d = new Date(year, month - 1, day, hours, minutes, seconds);
|
|
1284
|
+
return isNaN(d.getTime()) ? null : d.getTime();
|
|
1285
|
+
};
|
|
1286
|
+
cleanupLogFile = async (filePath) => {
|
|
1287
|
+
try {
|
|
1288
|
+
if (!await fs5.pathExists(filePath)) return;
|
|
1289
|
+
const content = await fs5.readFile(filePath, "utf8");
|
|
1290
|
+
if (!content.trim()) return;
|
|
1291
|
+
const lines = content.split("\n");
|
|
1292
|
+
const entries = [];
|
|
1293
|
+
let currentEntry = null;
|
|
1294
|
+
const entryStartRegex = /^\s*(?:DEBUG|ERROR|SEARCH|PUPPETEER)\b/i;
|
|
1295
|
+
for (const line of lines) {
|
|
1296
|
+
if (entryStartRegex.test(line)) {
|
|
1297
|
+
if (currentEntry) {
|
|
1298
|
+
entries.push(currentEntry);
|
|
1299
|
+
}
|
|
1300
|
+
currentEntry = { header: line, body: [] };
|
|
1301
|
+
} else {
|
|
1302
|
+
if (currentEntry) {
|
|
1303
|
+
currentEntry.body.push(line);
|
|
1304
|
+
} else {
|
|
1305
|
+
entries.push({ header: line, body: [] });
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
if (currentEntry) {
|
|
1310
|
+
entries.push(currentEntry);
|
|
1311
|
+
}
|
|
1312
|
+
const threshold = 7 * 24 * 60 * 60 * 1e3;
|
|
1313
|
+
const now = Date.now();
|
|
1314
|
+
const keptEntries = [];
|
|
1315
|
+
const timestampRegex = /(\d{1,4}[-/.]\d{1,4}[-/.]\d{1,4}(?:,\s*|\s+)?(?:\d{1,2}:\d{2}:\d{2}(?:\s*[aApP][mM])?)?)/;
|
|
1316
|
+
for (const entry of entries) {
|
|
1317
|
+
const entryText = entry.header + (entry.body.length > 0 ? "\n" + entry.body.join("\n") : "");
|
|
1318
|
+
const match = entryText.match(timestampRegex);
|
|
1319
|
+
if (match) {
|
|
1320
|
+
const timeMs = parseCustomDate(match[1]);
|
|
1321
|
+
if (timeMs && now - timeMs > threshold) {
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
keptEntries.push(entryText);
|
|
1326
|
+
}
|
|
1327
|
+
const finalContent = keptEntries.join("\n").trim();
|
|
1328
|
+
if (finalContent) {
|
|
1329
|
+
await fs5.writeFile(filePath, finalContent + "\n", "utf8");
|
|
1330
|
+
} else {
|
|
1331
|
+
await fs5.writeFile(filePath, "", "utf8");
|
|
1332
|
+
}
|
|
1333
|
+
} catch (e) {
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
cleanupOldLogs = async (logsDir) => {
|
|
1337
|
+
try {
|
|
1338
|
+
if (!await fs5.pathExists(logsDir)) return;
|
|
1339
|
+
const cleanRecursive = async (dir) => {
|
|
1340
|
+
const files = await fs5.readdir(dir);
|
|
1341
|
+
for (const file of files) {
|
|
1342
|
+
const fullPath = path4.join(dir, file);
|
|
1343
|
+
const stat = await fs5.stat(fullPath);
|
|
1344
|
+
if (stat.isDirectory()) {
|
|
1345
|
+
await cleanRecursive(fullPath);
|
|
1346
|
+
const subFiles = await fs5.readdir(fullPath);
|
|
1347
|
+
if (subFiles.length === 0) {
|
|
1348
|
+
await fs5.remove(fullPath);
|
|
1349
|
+
}
|
|
1350
|
+
} else if (file.endsWith(".log")) {
|
|
1351
|
+
await cleanupLogFile(fullPath);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
await cleanRecursive(logsDir);
|
|
1356
|
+
} catch (e) {
|
|
1357
|
+
}
|
|
1358
|
+
};
|
|
1186
1359
|
getTruncatedHistory = (history, exchangesToRemove = 4) => {
|
|
1187
1360
|
if (history.length <= 1) return history;
|
|
1188
1361
|
const welcome = history[0];
|
|
@@ -1197,10 +1370,11 @@ var init_history = __esm({
|
|
|
1197
1370
|
// src/utils/usage.js
|
|
1198
1371
|
import fs6 from "fs-extra";
|
|
1199
1372
|
import path5 from "path";
|
|
1200
|
-
var cachedUsage, writeTimeout, lastWriteTime, isDirty, defaultStats, loadUsageFromFile, flushUsage, queueFlush, initUsage, forceFlushUsage, getDailyUsage, incrementUsage, addToUsage, checkQuota, checkImageQuota, getImageQuotaStats, recordImageGeneration;
|
|
1373
|
+
var cachedUsage, writeTimeout, lastWriteTime, isDirty, defaultStats, loadUsageFromFile, flushUsage, queueFlush, initUsage, forceFlushUsage, getDailyUsage, incrementUsage, addToUsage, checkQuota, getImageQuotaLimit, checkImageQuota, getImageQuotaStats, recordImageGeneration;
|
|
1201
1374
|
var init_usage = __esm({
|
|
1202
1375
|
"src/utils/usage.js"() {
|
|
1203
1376
|
init_paths();
|
|
1377
|
+
init_crypto();
|
|
1204
1378
|
cachedUsage = null;
|
|
1205
1379
|
writeTimeout = null;
|
|
1206
1380
|
lastWriteTime = 0;
|
|
@@ -1220,7 +1394,13 @@ var init_usage = __esm({
|
|
|
1220
1394
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1221
1395
|
try {
|
|
1222
1396
|
if (await fs6.exists(USAGE_FILE)) {
|
|
1223
|
-
const
|
|
1397
|
+
const rawContent = (await fs6.readFile(USAGE_FILE, "utf8")).trim();
|
|
1398
|
+
let data;
|
|
1399
|
+
if (rawContent.startsWith("{") || rawContent.startsWith("[")) {
|
|
1400
|
+
data = JSON.parse(rawContent);
|
|
1401
|
+
} else {
|
|
1402
|
+
data = JSON.parse(decryptAes(rawContent));
|
|
1403
|
+
}
|
|
1224
1404
|
if (data && data.date === today && data.stats) {
|
|
1225
1405
|
const mergedStats = { ...defaultStats, ...data.stats };
|
|
1226
1406
|
if (!Array.isArray(mergedStats.imageCalls)) {
|
|
@@ -1243,7 +1423,12 @@ var init_usage = __esm({
|
|
|
1243
1423
|
let diskData = null;
|
|
1244
1424
|
try {
|
|
1245
1425
|
if (await fs6.exists(USAGE_FILE)) {
|
|
1246
|
-
|
|
1426
|
+
const rawContent = (await fs6.readFile(USAGE_FILE, "utf8")).trim();
|
|
1427
|
+
if (rawContent.startsWith("{") || rawContent.startsWith("[")) {
|
|
1428
|
+
diskData = JSON.parse(rawContent);
|
|
1429
|
+
} else {
|
|
1430
|
+
diskData = JSON.parse(decryptAes(rawContent));
|
|
1431
|
+
}
|
|
1247
1432
|
}
|
|
1248
1433
|
} catch (e) {
|
|
1249
1434
|
}
|
|
@@ -1267,7 +1452,8 @@ var init_usage = __esm({
|
|
|
1267
1452
|
}
|
|
1268
1453
|
}
|
|
1269
1454
|
const tempFile = USAGE_FILE + ".tmp";
|
|
1270
|
-
|
|
1455
|
+
const encryptedStr = encryptAes(JSON.stringify(cachedUsage, null, 2));
|
|
1456
|
+
await fs6.writeFile(tempFile, encryptedStr, "utf8");
|
|
1271
1457
|
const fd = await fs6.open(tempFile, "r+");
|
|
1272
1458
|
await fs6.fsync(fd);
|
|
1273
1459
|
await fs6.close(fd);
|
|
@@ -1346,6 +1532,57 @@ var init_usage = __esm({
|
|
|
1346
1532
|
}
|
|
1347
1533
|
return true;
|
|
1348
1534
|
};
|
|
1535
|
+
getImageQuotaLimit = (imageCalls, now) => {
|
|
1536
|
+
const hourMs = 60 * 60 * 1e3;
|
|
1537
|
+
if (!imageCalls || imageCalls.length === 0) {
|
|
1538
|
+
return 0.025;
|
|
1539
|
+
}
|
|
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;
|
|
1547
|
+
}
|
|
1548
|
+
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);
|
|
1552
|
+
let limit = 0.025;
|
|
1553
|
+
if (history.length > 0) {
|
|
1554
|
+
const prev1 = history[history.length - 1];
|
|
1555
|
+
let consecutiveMax = false;
|
|
1556
|
+
if (history.length >= 2) {
|
|
1557
|
+
const prev2 = history[history.length - 2];
|
|
1558
|
+
if (prev1.ratio >= 0.8 && prev2.ratio >= 0.8) {
|
|
1559
|
+
consecutiveMax = true;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
if (consecutiveMax) {
|
|
1563
|
+
limit = 0.015;
|
|
1564
|
+
} 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);
|
|
1573
|
+
} else {
|
|
1574
|
+
limit = Math.min(0.025, prevLimit + 2e-3);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
const ratio = limit > 0 ? usage / limit : 0;
|
|
1579
|
+
history.push({ limit, usage, ratio });
|
|
1580
|
+
}
|
|
1581
|
+
if (history.length > 0) {
|
|
1582
|
+
return history[history.length - 1].limit;
|
|
1583
|
+
}
|
|
1584
|
+
return 0.025;
|
|
1585
|
+
};
|
|
1349
1586
|
checkImageQuota = async (settings) => {
|
|
1350
1587
|
const imageSettings = settings.imageSettings || { keyType: "Default", quality: "Low-High" };
|
|
1351
1588
|
if (imageSettings.keyType !== "Default") return true;
|
|
@@ -1367,7 +1604,8 @@ var init_usage = __esm({
|
|
|
1367
1604
|
const oneHourAgo = now - 60 * 60 * 1e3;
|
|
1368
1605
|
const activeCalls = stats.imageCalls.filter((c) => c.timestamp >= oneHourAgo);
|
|
1369
1606
|
const totalSpent = activeCalls.reduce((sum, c) => sum + c.cost, 0);
|
|
1370
|
-
|
|
1607
|
+
const currentLimit = getImageQuotaLimit(stats.imageCalls, now);
|
|
1608
|
+
return totalSpent + currentCost <= currentLimit;
|
|
1371
1609
|
};
|
|
1372
1610
|
getImageQuotaStats = async () => {
|
|
1373
1611
|
const stats = await getDailyUsage();
|
|
@@ -1378,7 +1616,8 @@ var init_usage = __esm({
|
|
|
1378
1616
|
const oneHourAgo = now - 60 * 60 * 1e3;
|
|
1379
1617
|
const activeCalls = stats.imageCalls.filter((c) => c.timestamp >= oneHourAgo);
|
|
1380
1618
|
const totalSpent = activeCalls.reduce((sum, c) => sum + c.cost, 0);
|
|
1381
|
-
const
|
|
1619
|
+
const currentLimit = getImageQuotaLimit(stats.imageCalls, now);
|
|
1620
|
+
const remaining = Math.max(0, currentLimit - totalSpent);
|
|
1382
1621
|
let nextResetMin = 0;
|
|
1383
1622
|
let reclaimCost = 0;
|
|
1384
1623
|
if (activeCalls.length > 0) {
|
|
@@ -1392,7 +1631,8 @@ var init_usage = __esm({
|
|
|
1392
1631
|
remaining,
|
|
1393
1632
|
activeCallsCount: activeCalls.length,
|
|
1394
1633
|
nextResetMin,
|
|
1395
|
-
reclaimCost
|
|
1634
|
+
reclaimCost,
|
|
1635
|
+
limit: currentLimit
|
|
1396
1636
|
};
|
|
1397
1637
|
};
|
|
1398
1638
|
recordImageGeneration = async (settings) => {
|
|
@@ -2667,6 +2907,7 @@ var DEFAULT_SETTINGS, loadSettings, migrateToExternal, saveSettings;
|
|
|
2667
2907
|
var init_settings = __esm({
|
|
2668
2908
|
"src/utils/settings.js"() {
|
|
2669
2909
|
init_paths();
|
|
2910
|
+
init_crypto();
|
|
2670
2911
|
DEFAULT_SETTINGS = {
|
|
2671
2912
|
mode: "Flux",
|
|
2672
2913
|
thinkingLevel: "Medium",
|
|
@@ -2704,14 +2945,14 @@ var init_settings = __esm({
|
|
|
2704
2945
|
let settingsObj = { ...DEFAULT_SETTINGS };
|
|
2705
2946
|
try {
|
|
2706
2947
|
if (await fs14.exists(SETTINGS_FILE)) {
|
|
2707
|
-
const saved =
|
|
2948
|
+
const saved = readAesEncryptedJson(SETTINGS_FILE);
|
|
2708
2949
|
if (saved.imageSettings && saved.imageSettings.apiKey) {
|
|
2709
2950
|
try {
|
|
2710
2951
|
const legacyKey = saved.imageSettings.apiKey;
|
|
2711
2952
|
const { saveSecret: saveSecret2 } = await Promise.resolve().then(() => (init_secrets(), secrets_exports));
|
|
2712
2953
|
await saveSecret2("POLLINATIONS_API_KEY", legacyKey);
|
|
2713
2954
|
saved.imageSettings.apiKey = "";
|
|
2714
|
-
|
|
2955
|
+
writeAesEncryptedJson(SETTINGS_FILE, saved);
|
|
2715
2956
|
} catch (e) {
|
|
2716
2957
|
}
|
|
2717
2958
|
}
|
|
@@ -2738,7 +2979,7 @@ var init_settings = __esm({
|
|
|
2738
2979
|
if (settingsObj.showFullThinking === false) {
|
|
2739
2980
|
settingsObj.showFullThinking = true;
|
|
2740
2981
|
try {
|
|
2741
|
-
|
|
2982
|
+
writeAesEncryptedJson(SETTINGS_FILE, settingsObj);
|
|
2742
2983
|
} catch (e) {
|
|
2743
2984
|
}
|
|
2744
2985
|
}
|
|
@@ -2780,7 +3021,7 @@ var init_settings = __esm({
|
|
|
2780
3021
|
updated.imageSettings = { ...updated.imageSettings, apiKey: "" };
|
|
2781
3022
|
}
|
|
2782
3023
|
await fs14.ensureDir(path13.dirname(SETTINGS_FILE));
|
|
2783
|
-
|
|
3024
|
+
writeAesEncryptedJson(SETTINGS_FILE, updated);
|
|
2784
3025
|
return true;
|
|
2785
3026
|
} catch (err) {
|
|
2786
3027
|
console.error("Failed to save settings:", err);
|
|
@@ -2790,6 +3031,14 @@ var init_settings = __esm({
|
|
|
2790
3031
|
}
|
|
2791
3032
|
});
|
|
2792
3033
|
|
|
3034
|
+
// src/utils/fallback_key.js
|
|
3035
|
+
var FALLBACK_IMAGE_KEY;
|
|
3036
|
+
var init_fallback_key = __esm({
|
|
3037
|
+
"src/utils/fallback_key.js"() {
|
|
3038
|
+
FALLBACK_IMAGE_KEY = "pk_oH8wkUdQaQK1cmst";
|
|
3039
|
+
}
|
|
3040
|
+
});
|
|
3041
|
+
|
|
2793
3042
|
// src/tools/generate_image.js
|
|
2794
3043
|
import fs15 from "fs-extra";
|
|
2795
3044
|
import path14 from "path";
|
|
@@ -2799,6 +3048,7 @@ var init_generate_image = __esm({
|
|
|
2799
3048
|
init_arg_parser();
|
|
2800
3049
|
init_settings();
|
|
2801
3050
|
init_usage();
|
|
3051
|
+
init_fallback_key();
|
|
2802
3052
|
injectPngMetadata = (buffer, metadata = {}) => {
|
|
2803
3053
|
try {
|
|
2804
3054
|
if (buffer.length < 8 || buffer[0] !== 137 || buffer[1] !== 80 || buffer[2] !== 78 || buffer[3] !== 71) {
|
|
@@ -2871,7 +3121,7 @@ var init_generate_image = __esm({
|
|
|
2871
3121
|
return `ERROR: Insufficient Quota for selected quality. Either tell user reduce quality or wait for next refresh cycle (${stats.nextResetMin || 60}m).`;
|
|
2872
3122
|
}
|
|
2873
3123
|
const imageSettings = settings.imageSettings || { keyType: "Default", quality: "Low-High", apiKey: "" };
|
|
2874
|
-
const apiKey = imageSettings.keyType === "Custom" && imageSettings.apiKey ? imageSettings.apiKey :
|
|
3124
|
+
const apiKey = imageSettings.keyType === "Custom" && imageSettings.apiKey ? imageSettings.apiKey : FALLBACK_IMAGE_KEY;
|
|
2875
3125
|
const qualityMap = {
|
|
2876
3126
|
"Low": "flux",
|
|
2877
3127
|
"Low-High": "zimage",
|
|
@@ -3162,17 +3412,6 @@ ${originalTextProcessed.length > 1500 ? "... (truncated) ...\n\n" : ""}
|
|
|
3162
3412
|
if (fullContent) {
|
|
3163
3413
|
finalSynthesis = fullContent;
|
|
3164
3414
|
if (lastUsage) await addToUsage("tokens", lastUsage.totalTokenCount || 0);
|
|
3165
|
-
const date = (/* @__PURE__ */ new Date()).toLocaleString();
|
|
3166
|
-
const janitorLogDir = path15.join(LOGS_DIR, "janitor");
|
|
3167
|
-
if (!fs16.existsSync(janitorLogDir)) fs16.mkdirSync(janitorLogDir, { recursive: true });
|
|
3168
|
-
fs16.appendFileSync(path15.join(janitorLogDir, "debug.log"), `
|
|
3169
|
-
|
|
3170
|
-
---------------------------------------------------
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
DEBUG [${date}]: ${finalSynthesis}
|
|
3174
|
-
|
|
3175
|
-
`);
|
|
3176
3415
|
} else {
|
|
3177
3416
|
throw new Error("No synthesis generated by Janitor.");
|
|
3178
3417
|
}
|
|
@@ -3650,7 +3889,7 @@ ${thinkingLevel != "Fast" ? "[SYSTEM] **STRICTLY FOLLOW THINKING POLICY AS STRIC
|
|
|
3650
3889
|
const potentialTool = NORMALIZE_MAP[rawToolName] || rawToolName;
|
|
3651
3890
|
const partialArgs = toolContext.args || "";
|
|
3652
3891
|
let detail = null;
|
|
3653
|
-
if (["write_file", "update_file", "view_file", "read_folder", "write_pdf", "write_docx", "search_keyword"].includes(potentialTool)) {
|
|
3892
|
+
if (["write_file", "update_file", "view_file", "read_folder", "write_pdf", "write_docx", "search_keyword", "generate_image"].includes(potentialTool)) {
|
|
3654
3893
|
const pArgs = parseArgs(partialArgs);
|
|
3655
3894
|
const filePath = pArgs.path || pArgs.targetFile || pArgs.TargetFile || pArgs.directory;
|
|
3656
3895
|
const keyword = pArgs.keyword;
|
|
@@ -4759,6 +4998,7 @@ function App() {
|
|
|
4759
4998
|
if (saved.systemSettings?.autoDeleteHistory) {
|
|
4760
4999
|
cleanupOldHistory(saved.systemSettings.autoDeleteHistory);
|
|
4761
5000
|
}
|
|
5001
|
+
cleanupOldLogs(LOGS_DIR);
|
|
4762
5002
|
performVersionCheck(false, freshSettings);
|
|
4763
5003
|
await initUsage();
|
|
4764
5004
|
setIsInitializing(false);
|
|
@@ -4859,7 +5099,7 @@ function App() {
|
|
|
4859
5099
|
cmd: "key",
|
|
4860
5100
|
desc: "Set API key strategy",
|
|
4861
5101
|
subs: [
|
|
4862
|
-
{ cmd: "default", desc: "Default (Quota:
|
|
5102
|
+
{ cmd: "default", desc: "Default (Quota: Dynamic 25 max/hr)" },
|
|
4863
5103
|
{ cmd: "custom", desc: "Custom Key" }
|
|
4864
5104
|
]
|
|
4865
5105
|
},
|
|
@@ -5064,7 +5304,7 @@ ${hintText}`, color: "magenta" }];
|
|
|
5064
5304
|
id: Date.now(),
|
|
5065
5305
|
role: "system",
|
|
5066
5306
|
isImageStats: true,
|
|
5067
|
-
text: `\u2022 Hourly Limit:
|
|
5307
|
+
text: `\u2022 Hourly Limit: ${Number((stats.limit * 1e3).toFixed(0))} credits
|
|
5068
5308
|
\u2022 Spent (Last 1hr): ${Number((stats.totalSpent * 1e3).toFixed(0))} credits
|
|
5069
5309
|
\u2022 Remaining: ${Number((stats.remaining * 1e3).toFixed(0))} credits
|
|
5070
5310
|
\u2022 Requests (Last 1hr): ${stats.activeCallsCount} requests
|