fluxflow-cli 1.10.10 → 1.11.1
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 +355 -56
- 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();
|
|
@@ -1117,7 +1171,7 @@ var init_history = __esm({
|
|
|
1117
1171
|
loadHistory = async () => {
|
|
1118
1172
|
if (await fs5.pathExists(HISTORY_FILE)) {
|
|
1119
1173
|
try {
|
|
1120
|
-
return
|
|
1174
|
+
return readEncryptedJson(HISTORY_FILE, {});
|
|
1121
1175
|
} catch (e) {
|
|
1122
1176
|
return {};
|
|
1123
1177
|
}
|
|
@@ -1135,8 +1189,7 @@ var init_history = __esm({
|
|
|
1135
1189
|
messages: persistentMessages,
|
|
1136
1190
|
updatedAt: Date.now()
|
|
1137
1191
|
};
|
|
1138
|
-
|
|
1139
|
-
await fs5.writeJson(HISTORY_FILE, history, { spaces: 2 });
|
|
1192
|
+
writeEncryptedJson(HISTORY_FILE, history);
|
|
1140
1193
|
});
|
|
1141
1194
|
};
|
|
1142
1195
|
saveChatTitle = async (id, title) => {
|
|
@@ -1148,15 +1201,14 @@ var init_history = __esm({
|
|
|
1148
1201
|
} else {
|
|
1149
1202
|
history[id] = { name: title, messages: [], updatedAt: Date.now() };
|
|
1150
1203
|
}
|
|
1151
|
-
|
|
1152
|
-
await fs5.writeJson(HISTORY_FILE, history, { spaces: 2 });
|
|
1204
|
+
writeEncryptedJson(HISTORY_FILE, history);
|
|
1153
1205
|
});
|
|
1154
1206
|
};
|
|
1155
1207
|
deleteChat = async (id) => {
|
|
1156
1208
|
return withLock(async () => {
|
|
1157
1209
|
const history = await loadHistory();
|
|
1158
1210
|
delete history[id];
|
|
1159
|
-
|
|
1211
|
+
writeEncryptedJson(HISTORY_FILE, history);
|
|
1160
1212
|
const temp = readEncryptedJson(TEMP_MEM_FILE, {});
|
|
1161
1213
|
if (temp[id]) {
|
|
1162
1214
|
delete temp[id];
|
|
@@ -1183,6 +1235,125 @@ var init_history = __esm({
|
|
|
1183
1235
|
}
|
|
1184
1236
|
return deletedCount;
|
|
1185
1237
|
};
|
|
1238
|
+
parseCustomDate = (dateStr) => {
|
|
1239
|
+
const cleanStr = dateStr.replace(/[\[\]]/g, "").trim();
|
|
1240
|
+
const parsed = new Date(cleanStr);
|
|
1241
|
+
if (!isNaN(parsed.getTime())) return parsed.getTime();
|
|
1242
|
+
const parts = cleanStr.split(/,\s*|\s+/);
|
|
1243
|
+
if (parts.length === 0) return null;
|
|
1244
|
+
const datePart = parts[0];
|
|
1245
|
+
const timePart = parts[1] || "";
|
|
1246
|
+
const ampm = parts[2] || "";
|
|
1247
|
+
const dateNums = datePart.split(/[-/.]/).map(Number);
|
|
1248
|
+
if (dateNums.length !== 3) return null;
|
|
1249
|
+
let year, month, day;
|
|
1250
|
+
if (dateNums[0] > 1e3) {
|
|
1251
|
+
year = dateNums[0];
|
|
1252
|
+
month = dateNums[1];
|
|
1253
|
+
day = dateNums[2];
|
|
1254
|
+
} else if (dateNums[2] > 1e3) {
|
|
1255
|
+
year = dateNums[2];
|
|
1256
|
+
if (dateNums[0] > 12) {
|
|
1257
|
+
day = dateNums[0];
|
|
1258
|
+
month = dateNums[1];
|
|
1259
|
+
} else if (dateNums[1] > 12) {
|
|
1260
|
+
day = dateNums[1];
|
|
1261
|
+
month = dateNums[0];
|
|
1262
|
+
} else {
|
|
1263
|
+
month = dateNums[0];
|
|
1264
|
+
day = dateNums[1];
|
|
1265
|
+
}
|
|
1266
|
+
} else {
|
|
1267
|
+
return null;
|
|
1268
|
+
}
|
|
1269
|
+
let hours = 0, minutes = 0, seconds = 0;
|
|
1270
|
+
if (timePart) {
|
|
1271
|
+
const timeNums = timePart.split(":").map(Number);
|
|
1272
|
+
hours = timeNums[0] || 0;
|
|
1273
|
+
minutes = timeNums[1] || 0;
|
|
1274
|
+
seconds = timeNums[2] || 0;
|
|
1275
|
+
if (ampm.toLowerCase() === "pm" && hours < 12) {
|
|
1276
|
+
hours += 12;
|
|
1277
|
+
} else if (ampm.toLowerCase() === "am" && hours === 12) {
|
|
1278
|
+
hours = 0;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
const d = new Date(year, month - 1, day, hours, minutes, seconds);
|
|
1282
|
+
return isNaN(d.getTime()) ? null : d.getTime();
|
|
1283
|
+
};
|
|
1284
|
+
cleanupLogFile = async (filePath) => {
|
|
1285
|
+
try {
|
|
1286
|
+
if (!await fs5.pathExists(filePath)) return;
|
|
1287
|
+
const content = await fs5.readFile(filePath, "utf8");
|
|
1288
|
+
if (!content.trim()) return;
|
|
1289
|
+
const lines = content.split("\n");
|
|
1290
|
+
const entries = [];
|
|
1291
|
+
let currentEntry = null;
|
|
1292
|
+
const entryStartRegex = /^\s*(?:DEBUG|ERROR|SEARCH|PUPPETEER)\b/i;
|
|
1293
|
+
for (const line of lines) {
|
|
1294
|
+
if (entryStartRegex.test(line)) {
|
|
1295
|
+
if (currentEntry) {
|
|
1296
|
+
entries.push(currentEntry);
|
|
1297
|
+
}
|
|
1298
|
+
currentEntry = { header: line, body: [] };
|
|
1299
|
+
} else {
|
|
1300
|
+
if (currentEntry) {
|
|
1301
|
+
currentEntry.body.push(line);
|
|
1302
|
+
} else {
|
|
1303
|
+
entries.push({ header: line, body: [] });
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
if (currentEntry) {
|
|
1308
|
+
entries.push(currentEntry);
|
|
1309
|
+
}
|
|
1310
|
+
const threshold = 7 * 24 * 60 * 60 * 1e3;
|
|
1311
|
+
const now = Date.now();
|
|
1312
|
+
const keptEntries = [];
|
|
1313
|
+
const timestampRegex = /(\d{1,4}[-/.]\d{1,4}[-/.]\d{1,4}(?:,\s*|\s+)?(?:\d{1,2}:\d{2}:\d{2}(?:\s*[aApP][mM])?)?)/;
|
|
1314
|
+
for (const entry of entries) {
|
|
1315
|
+
const entryText = entry.header + (entry.body.length > 0 ? "\n" + entry.body.join("\n") : "");
|
|
1316
|
+
const match = entryText.match(timestampRegex);
|
|
1317
|
+
if (match) {
|
|
1318
|
+
const timeMs = parseCustomDate(match[1]);
|
|
1319
|
+
if (timeMs && now - timeMs > threshold) {
|
|
1320
|
+
continue;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
keptEntries.push(entryText);
|
|
1324
|
+
}
|
|
1325
|
+
const finalContent = keptEntries.join("\n").trim();
|
|
1326
|
+
if (finalContent) {
|
|
1327
|
+
await fs5.writeFile(filePath, finalContent + "\n", "utf8");
|
|
1328
|
+
} else {
|
|
1329
|
+
await fs5.writeFile(filePath, "", "utf8");
|
|
1330
|
+
}
|
|
1331
|
+
} catch (e) {
|
|
1332
|
+
}
|
|
1333
|
+
};
|
|
1334
|
+
cleanupOldLogs = async (logsDir) => {
|
|
1335
|
+
try {
|
|
1336
|
+
if (!await fs5.pathExists(logsDir)) return;
|
|
1337
|
+
const cleanRecursive = async (dir) => {
|
|
1338
|
+
const files = await fs5.readdir(dir);
|
|
1339
|
+
for (const file of files) {
|
|
1340
|
+
const fullPath = path4.join(dir, file);
|
|
1341
|
+
const stat = await fs5.stat(fullPath);
|
|
1342
|
+
if (stat.isDirectory()) {
|
|
1343
|
+
await cleanRecursive(fullPath);
|
|
1344
|
+
const subFiles = await fs5.readdir(fullPath);
|
|
1345
|
+
if (subFiles.length === 0) {
|
|
1346
|
+
await fs5.remove(fullPath);
|
|
1347
|
+
}
|
|
1348
|
+
} else if (file.endsWith(".log")) {
|
|
1349
|
+
await cleanupLogFile(fullPath);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
await cleanRecursive(logsDir);
|
|
1354
|
+
} catch (e) {
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1186
1357
|
getTruncatedHistory = (history, exchangesToRemove = 4) => {
|
|
1187
1358
|
if (history.length <= 1) return history;
|
|
1188
1359
|
const welcome = history[0];
|
|
@@ -1197,10 +1368,11 @@ var init_history = __esm({
|
|
|
1197
1368
|
// src/utils/usage.js
|
|
1198
1369
|
import fs6 from "fs-extra";
|
|
1199
1370
|
import path5 from "path";
|
|
1200
|
-
var cachedUsage, writeTimeout, lastWriteTime, isDirty, defaultStats, loadUsageFromFile, flushUsage, queueFlush, initUsage, forceFlushUsage, getDailyUsage, incrementUsage, addToUsage, checkQuota, checkImageQuota, getImageQuotaStats, recordImageGeneration;
|
|
1371
|
+
var cachedUsage, writeTimeout, lastWriteTime, isDirty, defaultStats, loadUsageFromFile, flushUsage, queueFlush, initUsage, forceFlushUsage, getDailyUsage, incrementUsage, addToUsage, checkQuota, getImageQuotaBuckets, getImageQuotaLimit, checkImageQuota, getImageQuotaStats, recordImageGeneration;
|
|
1201
1372
|
var init_usage = __esm({
|
|
1202
1373
|
"src/utils/usage.js"() {
|
|
1203
1374
|
init_paths();
|
|
1375
|
+
init_crypto();
|
|
1204
1376
|
cachedUsage = null;
|
|
1205
1377
|
writeTimeout = null;
|
|
1206
1378
|
lastWriteTime = 0;
|
|
@@ -1220,7 +1392,13 @@ var init_usage = __esm({
|
|
|
1220
1392
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1221
1393
|
try {
|
|
1222
1394
|
if (await fs6.exists(USAGE_FILE)) {
|
|
1223
|
-
const
|
|
1395
|
+
const rawContent = (await fs6.readFile(USAGE_FILE, "utf8")).trim();
|
|
1396
|
+
let data;
|
|
1397
|
+
if (rawContent.startsWith("{") || rawContent.startsWith("[")) {
|
|
1398
|
+
data = JSON.parse(rawContent);
|
|
1399
|
+
} else {
|
|
1400
|
+
data = JSON.parse(decryptAes(rawContent));
|
|
1401
|
+
}
|
|
1224
1402
|
if (data && data.date === today && data.stats) {
|
|
1225
1403
|
const mergedStats = { ...defaultStats, ...data.stats };
|
|
1226
1404
|
if (!Array.isArray(mergedStats.imageCalls)) {
|
|
@@ -1243,7 +1421,12 @@ var init_usage = __esm({
|
|
|
1243
1421
|
let diskData = null;
|
|
1244
1422
|
try {
|
|
1245
1423
|
if (await fs6.exists(USAGE_FILE)) {
|
|
1246
|
-
|
|
1424
|
+
const rawContent = (await fs6.readFile(USAGE_FILE, "utf8")).trim();
|
|
1425
|
+
if (rawContent.startsWith("{") || rawContent.startsWith("[")) {
|
|
1426
|
+
diskData = JSON.parse(rawContent);
|
|
1427
|
+
} else {
|
|
1428
|
+
diskData = JSON.parse(decryptAes(rawContent));
|
|
1429
|
+
}
|
|
1247
1430
|
}
|
|
1248
1431
|
} catch (e) {
|
|
1249
1432
|
}
|
|
@@ -1267,7 +1450,8 @@ var init_usage = __esm({
|
|
|
1267
1450
|
}
|
|
1268
1451
|
}
|
|
1269
1452
|
const tempFile = USAGE_FILE + ".tmp";
|
|
1270
|
-
|
|
1453
|
+
const encryptedStr = encryptAes(JSON.stringify(cachedUsage, null, 2));
|
|
1454
|
+
await fs6.writeFile(tempFile, encryptedStr, "utf8");
|
|
1271
1455
|
const fd = await fs6.open(tempFile, "r+");
|
|
1272
1456
|
await fs6.fsync(fd);
|
|
1273
1457
|
await fs6.close(fd);
|
|
@@ -1346,6 +1530,111 @@ var init_usage = __esm({
|
|
|
1346
1530
|
}
|
|
1347
1531
|
return true;
|
|
1348
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
|
+
};
|
|
1558
|
+
getImageQuotaLimit = (imageCalls, now) => {
|
|
1559
|
+
const hourMs = 60 * 60 * 1e3;
|
|
1560
|
+
if (!imageCalls || imageCalls.length === 0) {
|
|
1561
|
+
return 0.025;
|
|
1562
|
+
}
|
|
1563
|
+
const buckets = getImageQuotaBuckets(imageCalls);
|
|
1564
|
+
if (buckets.length === 0) {
|
|
1565
|
+
return 0.025;
|
|
1566
|
+
}
|
|
1567
|
+
const history = [];
|
|
1568
|
+
for (const bucket of buckets) {
|
|
1569
|
+
let limit = 0.025;
|
|
1570
|
+
if (history.length > 0) {
|
|
1571
|
+
const prev1 = history[history.length - 1];
|
|
1572
|
+
let consecutiveMax = false;
|
|
1573
|
+
if (history.length >= 2) {
|
|
1574
|
+
const prev2 = history[history.length - 2];
|
|
1575
|
+
if (prev1.ratio >= 0.8 && prev2.ratio >= 0.8) {
|
|
1576
|
+
consecutiveMax = true;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
if (consecutiveMax) {
|
|
1580
|
+
limit = 0.015;
|
|
1581
|
+
} else {
|
|
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);
|
|
1590
|
+
} else {
|
|
1591
|
+
limit = Math.min(0.025, prevLimit2 + 2e-3);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
const ratio = limit > 0 ? bucket.spent / limit : 0;
|
|
1596
|
+
history.push({ limit, spent: bucket.spent, ratio });
|
|
1597
|
+
}
|
|
1598
|
+
const lastBucket = buckets[buckets.length - 1];
|
|
1599
|
+
if (now < lastBucket.end) {
|
|
1600
|
+
return history[history.length - 1].limit;
|
|
1601
|
+
}
|
|
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;
|
|
1637
|
+
};
|
|
1349
1638
|
checkImageQuota = async (settings) => {
|
|
1350
1639
|
const imageSettings = settings.imageSettings || { keyType: "Default", quality: "Low-High" };
|
|
1351
1640
|
if (imageSettings.keyType !== "Default") return true;
|
|
@@ -1364,10 +1653,16 @@ var init_usage = __esm({
|
|
|
1364
1653
|
stats.imageCalls = [];
|
|
1365
1654
|
}
|
|
1366
1655
|
const now = Date.now();
|
|
1367
|
-
const
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
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
|
+
}
|
|
1664
|
+
const currentLimit = getImageQuotaLimit(stats.imageCalls, now);
|
|
1665
|
+
return totalSpent + currentCost <= currentLimit;
|
|
1371
1666
|
};
|
|
1372
1667
|
getImageQuotaStats = async () => {
|
|
1373
1668
|
const stats = await getDailyUsage();
|
|
@@ -1375,24 +1670,28 @@ var init_usage = __esm({
|
|
|
1375
1670
|
stats.imageCalls = [];
|
|
1376
1671
|
}
|
|
1377
1672
|
const now = Date.now();
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
const remaining = Math.max(0, 0.02 - totalSpent);
|
|
1673
|
+
const buckets = getImageQuotaBuckets(stats.imageCalls);
|
|
1674
|
+
let activeCalls = [];
|
|
1675
|
+
let totalSpent = 0;
|
|
1382
1676
|
let nextResetMin = 0;
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
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
|
+
}
|
|
1389
1684
|
}
|
|
1685
|
+
const currentLimit = getImageQuotaLimit(stats.imageCalls, now);
|
|
1686
|
+
const remaining = Math.max(0, currentLimit - totalSpent);
|
|
1687
|
+
const reclaimCost = totalSpent;
|
|
1390
1688
|
return {
|
|
1391
1689
|
totalSpent,
|
|
1392
1690
|
remaining,
|
|
1393
1691
|
activeCallsCount: activeCalls.length,
|
|
1394
1692
|
nextResetMin,
|
|
1395
|
-
reclaimCost
|
|
1693
|
+
reclaimCost,
|
|
1694
|
+
limit: currentLimit
|
|
1396
1695
|
};
|
|
1397
1696
|
};
|
|
1398
1697
|
recordImageGeneration = async (settings) => {
|
|
@@ -2667,6 +2966,7 @@ var DEFAULT_SETTINGS, loadSettings, migrateToExternal, saveSettings;
|
|
|
2667
2966
|
var init_settings = __esm({
|
|
2668
2967
|
"src/utils/settings.js"() {
|
|
2669
2968
|
init_paths();
|
|
2969
|
+
init_crypto();
|
|
2670
2970
|
DEFAULT_SETTINGS = {
|
|
2671
2971
|
mode: "Flux",
|
|
2672
2972
|
thinkingLevel: "Medium",
|
|
@@ -2704,14 +3004,14 @@ var init_settings = __esm({
|
|
|
2704
3004
|
let settingsObj = { ...DEFAULT_SETTINGS };
|
|
2705
3005
|
try {
|
|
2706
3006
|
if (await fs14.exists(SETTINGS_FILE)) {
|
|
2707
|
-
const saved =
|
|
3007
|
+
const saved = readAesEncryptedJson(SETTINGS_FILE);
|
|
2708
3008
|
if (saved.imageSettings && saved.imageSettings.apiKey) {
|
|
2709
3009
|
try {
|
|
2710
3010
|
const legacyKey = saved.imageSettings.apiKey;
|
|
2711
3011
|
const { saveSecret: saveSecret2 } = await Promise.resolve().then(() => (init_secrets(), secrets_exports));
|
|
2712
3012
|
await saveSecret2("POLLINATIONS_API_KEY", legacyKey);
|
|
2713
3013
|
saved.imageSettings.apiKey = "";
|
|
2714
|
-
|
|
3014
|
+
writeAesEncryptedJson(SETTINGS_FILE, saved);
|
|
2715
3015
|
} catch (e) {
|
|
2716
3016
|
}
|
|
2717
3017
|
}
|
|
@@ -2738,7 +3038,7 @@ var init_settings = __esm({
|
|
|
2738
3038
|
if (settingsObj.showFullThinking === false) {
|
|
2739
3039
|
settingsObj.showFullThinking = true;
|
|
2740
3040
|
try {
|
|
2741
|
-
|
|
3041
|
+
writeAesEncryptedJson(SETTINGS_FILE, settingsObj);
|
|
2742
3042
|
} catch (e) {
|
|
2743
3043
|
}
|
|
2744
3044
|
}
|
|
@@ -2780,7 +3080,7 @@ var init_settings = __esm({
|
|
|
2780
3080
|
updated.imageSettings = { ...updated.imageSettings, apiKey: "" };
|
|
2781
3081
|
}
|
|
2782
3082
|
await fs14.ensureDir(path13.dirname(SETTINGS_FILE));
|
|
2783
|
-
|
|
3083
|
+
writeAesEncryptedJson(SETTINGS_FILE, updated);
|
|
2784
3084
|
return true;
|
|
2785
3085
|
} catch (err) {
|
|
2786
3086
|
console.error("Failed to save settings:", err);
|
|
@@ -2790,6 +3090,14 @@ var init_settings = __esm({
|
|
|
2790
3090
|
}
|
|
2791
3091
|
});
|
|
2792
3092
|
|
|
3093
|
+
// src/utils/fallback_key.js
|
|
3094
|
+
var FALLBACK_IMAGE_KEY;
|
|
3095
|
+
var init_fallback_key = __esm({
|
|
3096
|
+
"src/utils/fallback_key.js"() {
|
|
3097
|
+
FALLBACK_IMAGE_KEY = "pk_oH8wkUdQaQK1cmst";
|
|
3098
|
+
}
|
|
3099
|
+
});
|
|
3100
|
+
|
|
2793
3101
|
// src/tools/generate_image.js
|
|
2794
3102
|
import fs15 from "fs-extra";
|
|
2795
3103
|
import path14 from "path";
|
|
@@ -2799,6 +3107,7 @@ var init_generate_image = __esm({
|
|
|
2799
3107
|
init_arg_parser();
|
|
2800
3108
|
init_settings();
|
|
2801
3109
|
init_usage();
|
|
3110
|
+
init_fallback_key();
|
|
2802
3111
|
injectPngMetadata = (buffer, metadata = {}) => {
|
|
2803
3112
|
try {
|
|
2804
3113
|
if (buffer.length < 8 || buffer[0] !== 137 || buffer[1] !== 80 || buffer[2] !== 78 || buffer[3] !== 71) {
|
|
@@ -2871,7 +3180,7 @@ var init_generate_image = __esm({
|
|
|
2871
3180
|
return `ERROR: Insufficient Quota for selected quality. Either tell user reduce quality or wait for next refresh cycle (${stats.nextResetMin || 60}m).`;
|
|
2872
3181
|
}
|
|
2873
3182
|
const imageSettings = settings.imageSettings || { keyType: "Default", quality: "Low-High", apiKey: "" };
|
|
2874
|
-
const apiKey = imageSettings.keyType === "Custom" && imageSettings.apiKey ? imageSettings.apiKey :
|
|
3183
|
+
const apiKey = imageSettings.keyType === "Custom" && imageSettings.apiKey ? imageSettings.apiKey : FALLBACK_IMAGE_KEY;
|
|
2875
3184
|
const qualityMap = {
|
|
2876
3185
|
"Low": "flux",
|
|
2877
3186
|
"Low-High": "zimage",
|
|
@@ -3162,17 +3471,6 @@ ${originalTextProcessed.length > 1500 ? "... (truncated) ...\n\n" : ""}
|
|
|
3162
3471
|
if (fullContent) {
|
|
3163
3472
|
finalSynthesis = fullContent;
|
|
3164
3473
|
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
3474
|
} else {
|
|
3177
3475
|
throw new Error("No synthesis generated by Janitor.");
|
|
3178
3476
|
}
|
|
@@ -3650,7 +3948,7 @@ ${thinkingLevel != "Fast" ? "[SYSTEM] **STRICTLY FOLLOW THINKING POLICY AS STRIC
|
|
|
3650
3948
|
const potentialTool = NORMALIZE_MAP[rawToolName] || rawToolName;
|
|
3651
3949
|
const partialArgs = toolContext.args || "";
|
|
3652
3950
|
let detail = null;
|
|
3653
|
-
if (["write_file", "update_file", "view_file", "read_folder", "write_pdf", "write_docx", "search_keyword"].includes(potentialTool)) {
|
|
3951
|
+
if (["write_file", "update_file", "view_file", "read_folder", "write_pdf", "write_docx", "search_keyword", "generate_image"].includes(potentialTool)) {
|
|
3654
3952
|
const pArgs = parseArgs(partialArgs);
|
|
3655
3953
|
const filePath = pArgs.path || pArgs.targetFile || pArgs.TargetFile || pArgs.directory;
|
|
3656
3954
|
const keyword = pArgs.keyword;
|
|
@@ -4759,6 +5057,7 @@ function App() {
|
|
|
4759
5057
|
if (saved.systemSettings?.autoDeleteHistory) {
|
|
4760
5058
|
cleanupOldHistory(saved.systemSettings.autoDeleteHistory);
|
|
4761
5059
|
}
|
|
5060
|
+
cleanupOldLogs(LOGS_DIR);
|
|
4762
5061
|
performVersionCheck(false, freshSettings);
|
|
4763
5062
|
await initUsage();
|
|
4764
5063
|
setIsInitializing(false);
|
|
@@ -4859,7 +5158,7 @@ function App() {
|
|
|
4859
5158
|
cmd: "key",
|
|
4860
5159
|
desc: "Set API key strategy",
|
|
4861
5160
|
subs: [
|
|
4862
|
-
{ cmd: "default", desc: "Default (Quota:
|
|
5161
|
+
{ cmd: "default", desc: "Default (Quota: Dynamic 25 max/hr)" },
|
|
4863
5162
|
{ cmd: "custom", desc: "Custom Key" }
|
|
4864
5163
|
]
|
|
4865
5164
|
},
|
|
@@ -5064,7 +5363,7 @@ ${hintText}`, color: "magenta" }];
|
|
|
5064
5363
|
id: Date.now(),
|
|
5065
5364
|
role: "system",
|
|
5066
5365
|
isImageStats: true,
|
|
5067
|
-
text: `\u2022 Hourly Limit:
|
|
5366
|
+
text: `\u2022 Hourly Limit: ${Number((stats.limit * 1e3).toFixed(0))} credits
|
|
5068
5367
|
\u2022 Spent (Last 1hr): ${Number((stats.totalSpent * 1e3).toFixed(0))} credits
|
|
5069
5368
|
\u2022 Remaining: ${Number((stats.remaining * 1e3).toFixed(0))} credits
|
|
5070
5369
|
\u2022 Requests (Last 1hr): ${stats.activeCallsCount} requests
|