@nomad-e/bluma-cli 0.10.0 → 0.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/dist/config/skills/factorai-sh/SKILL.md +97 -0
- package/dist/main.js +1490 -878
- package/package.json +6 -3
package/dist/main.js
CHANGED
|
@@ -406,11 +406,11 @@ var init_sandbox_policy = __esm({
|
|
|
406
406
|
});
|
|
407
407
|
|
|
408
408
|
// src/app/agent/runtime/task_store.ts
|
|
409
|
-
import
|
|
410
|
-
import
|
|
409
|
+
import fs10 from "fs";
|
|
410
|
+
import path9 from "path";
|
|
411
411
|
function getStorePath() {
|
|
412
412
|
const policy = getSandboxPolicy();
|
|
413
|
-
return
|
|
413
|
+
return path9.join(policy.workspaceRoot, ".bluma", "task_state.json");
|
|
414
414
|
}
|
|
415
415
|
function getDefaultState() {
|
|
416
416
|
return {
|
|
@@ -426,8 +426,8 @@ function ensureLoaded() {
|
|
|
426
426
|
return cache2;
|
|
427
427
|
}
|
|
428
428
|
try {
|
|
429
|
-
if (
|
|
430
|
-
const raw =
|
|
429
|
+
if (fs10.existsSync(storePath)) {
|
|
430
|
+
const raw = fs10.readFileSync(storePath, "utf-8");
|
|
431
431
|
const parsed = JSON.parse(raw);
|
|
432
432
|
cache2 = {
|
|
433
433
|
tasks: Array.isArray(parsed.tasks) ? parsed.tasks : [],
|
|
@@ -446,9 +446,9 @@ function ensureLoaded() {
|
|
|
446
446
|
}
|
|
447
447
|
function persist(state2) {
|
|
448
448
|
const storePath = getStorePath();
|
|
449
|
-
|
|
449
|
+
fs10.mkdirSync(path9.dirname(storePath), { recursive: true });
|
|
450
450
|
state2.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
451
|
-
|
|
451
|
+
fs10.writeFileSync(storePath, JSON.stringify(state2, null, 2), "utf-8");
|
|
452
452
|
cache2 = state2;
|
|
453
453
|
cachePath = storePath;
|
|
454
454
|
}
|
|
@@ -835,37 +835,37 @@ __export(session_registry_exports, {
|
|
|
835
835
|
removeSession: () => removeSession,
|
|
836
836
|
updateSession: () => updateSession
|
|
837
837
|
});
|
|
838
|
-
import
|
|
838
|
+
import fs16 from "fs";
|
|
839
839
|
import os11 from "os";
|
|
840
|
-
import
|
|
840
|
+
import path17 from "path";
|
|
841
841
|
function getRegistryDir() {
|
|
842
|
-
return
|
|
842
|
+
return path17.join(process.env.HOME || os11.homedir(), ".bluma", "registry");
|
|
843
843
|
}
|
|
844
844
|
function getRegistryFile() {
|
|
845
|
-
return
|
|
845
|
+
return path17.join(getRegistryDir(), "sessions.json");
|
|
846
846
|
}
|
|
847
847
|
function ensureRegistryDir() {
|
|
848
|
-
|
|
848
|
+
fs16.mkdirSync(getRegistryDir(), { recursive: true });
|
|
849
849
|
}
|
|
850
850
|
function readRegistry() {
|
|
851
851
|
ensureRegistryDir();
|
|
852
852
|
const file = getRegistryFile();
|
|
853
|
-
if (!
|
|
853
|
+
if (!fs16.existsSync(file)) {
|
|
854
854
|
return { entries: [] };
|
|
855
855
|
}
|
|
856
856
|
try {
|
|
857
|
-
return JSON.parse(
|
|
857
|
+
return JSON.parse(fs16.readFileSync(file, "utf-8"));
|
|
858
858
|
} catch {
|
|
859
859
|
return { entries: [] };
|
|
860
860
|
}
|
|
861
861
|
}
|
|
862
862
|
function writeRegistry(state2) {
|
|
863
863
|
ensureRegistryDir();
|
|
864
|
-
|
|
864
|
+
fs16.writeFileSync(getRegistryFile(), JSON.stringify(state2, null, 2), "utf-8");
|
|
865
865
|
}
|
|
866
866
|
function getSessionLogPath(sessionId) {
|
|
867
867
|
ensureRegistryDir();
|
|
868
|
-
return
|
|
868
|
+
return path17.join(getRegistryDir(), `${sessionId}.jsonl`);
|
|
869
869
|
}
|
|
870
870
|
function registerSession(entry) {
|
|
871
871
|
const state2 = readRegistry();
|
|
@@ -910,13 +910,13 @@ function removeSession(sessionId) {
|
|
|
910
910
|
}
|
|
911
911
|
function appendSessionLog(sessionId, payload) {
|
|
912
912
|
const logFile = getSessionLogPath(sessionId);
|
|
913
|
-
|
|
913
|
+
fs16.appendFileSync(logFile, `${JSON.stringify(payload)}
|
|
914
914
|
`, "utf-8");
|
|
915
915
|
}
|
|
916
916
|
function readSessionLog(sessionId) {
|
|
917
917
|
const logFile = getSessionLogPath(sessionId);
|
|
918
|
-
if (!
|
|
919
|
-
return
|
|
918
|
+
if (!fs16.existsSync(logFile)) return [];
|
|
919
|
+
return fs16.readFileSync(logFile, "utf-8").split("\n").filter(Boolean);
|
|
920
920
|
}
|
|
921
921
|
var init_session_registry = __esm({
|
|
922
922
|
"src/app/agent/runtime/session_registry.ts"() {
|
|
@@ -925,17 +925,17 @@ var init_session_registry = __esm({
|
|
|
925
925
|
});
|
|
926
926
|
|
|
927
927
|
// src/app/agent/utils/logger.ts
|
|
928
|
-
import
|
|
928
|
+
import fs17 from "fs";
|
|
929
929
|
import os12 from "os";
|
|
930
|
-
import
|
|
930
|
+
import path18 from "path";
|
|
931
931
|
function getLogDir() {
|
|
932
|
-
const dir =
|
|
933
|
-
|
|
932
|
+
const dir = path18.join(process.env.HOME || os12.homedir(), ".bluma", "logs");
|
|
933
|
+
fs17.mkdirSync(dir, { recursive: true });
|
|
934
934
|
return dir;
|
|
935
935
|
}
|
|
936
936
|
function getLogFilePath() {
|
|
937
937
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
938
|
-
return
|
|
938
|
+
return path18.join(getLogDir(), `bluma-${today}.log`);
|
|
939
939
|
}
|
|
940
940
|
var BluMaLogger, logger;
|
|
941
941
|
var init_logger = __esm({
|
|
@@ -965,7 +965,7 @@ var init_logger = __esm({
|
|
|
965
965
|
turnId: this.turnId
|
|
966
966
|
};
|
|
967
967
|
try {
|
|
968
|
-
|
|
968
|
+
fs17.appendFileSync(this.logFile, JSON.stringify(logEntry) + "\n");
|
|
969
969
|
} catch {
|
|
970
970
|
}
|
|
971
971
|
if (this.consoleEnabled) {
|
|
@@ -1066,19 +1066,19 @@ __export(mailbox_registry_exports, {
|
|
|
1066
1066
|
sendSignal: () => sendSignal,
|
|
1067
1067
|
sendToMailbox: () => sendToMailbox
|
|
1068
1068
|
});
|
|
1069
|
-
import
|
|
1069
|
+
import fs18 from "fs";
|
|
1070
1070
|
import os13 from "os";
|
|
1071
|
-
import
|
|
1071
|
+
import path19 from "path";
|
|
1072
1072
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
1073
1073
|
import { v4 as uuidv45 } from "uuid";
|
|
1074
1074
|
function getMailboxesDir() {
|
|
1075
1075
|
if (mailboxesDir) return mailboxesDir;
|
|
1076
|
-
mailboxesDir =
|
|
1077
|
-
|
|
1076
|
+
mailboxesDir = path19.join(process.env.HOME || os13.homedir(), ".bluma", "mailboxes");
|
|
1077
|
+
fs18.mkdirSync(mailboxesDir, { recursive: true });
|
|
1078
1078
|
return mailboxesDir;
|
|
1079
1079
|
}
|
|
1080
1080
|
function getMailboxPath(sessionId, type) {
|
|
1081
|
-
return
|
|
1081
|
+
return path19.join(getMailboxesDir(), `${sessionId}.${type}`);
|
|
1082
1082
|
}
|
|
1083
1083
|
function sendToMailbox(sessionId, type, message2) {
|
|
1084
1084
|
return mailbox.sendToMailbox(sessionId, type, message2);
|
|
@@ -1104,7 +1104,7 @@ function pruneMailbox(sessionId, type, keepLast = 100) {
|
|
|
1104
1104
|
const pruned = queue.slice(-keepLast);
|
|
1105
1105
|
mailbox.queues.set(sessionId, pruned);
|
|
1106
1106
|
const filePath = getMailboxPath(sessionId, type);
|
|
1107
|
-
|
|
1107
|
+
fs18.writeFileSync(filePath, pruned.map((m) => JSON.stringify(m)).join("\n") + "\n", "utf-8");
|
|
1108
1108
|
}
|
|
1109
1109
|
function listActiveMailboxes() {
|
|
1110
1110
|
const result = [];
|
|
@@ -1172,16 +1172,16 @@ var init_mailbox_registry = __esm({
|
|
|
1172
1172
|
loadExistingMailboxes() {
|
|
1173
1173
|
try {
|
|
1174
1174
|
const dir = getMailboxesDir();
|
|
1175
|
-
const files =
|
|
1175
|
+
const files = fs18.readdirSync(dir);
|
|
1176
1176
|
for (const file of files) {
|
|
1177
1177
|
if (!file.endsWith(".in") && !file.endsWith(".out") && !file.endsWith(".sig")) {
|
|
1178
1178
|
continue;
|
|
1179
1179
|
}
|
|
1180
1180
|
const type = file.split(".").pop();
|
|
1181
1181
|
const sessionId = file.slice(0, -(type.length + 1));
|
|
1182
|
-
const filePath =
|
|
1182
|
+
const filePath = path19.join(dir, file);
|
|
1183
1183
|
try {
|
|
1184
|
-
const content =
|
|
1184
|
+
const content = fs18.readFileSync(filePath, "utf-8");
|
|
1185
1185
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
1186
1186
|
const messages = [];
|
|
1187
1187
|
for (const line of lines) {
|
|
@@ -1218,7 +1218,7 @@ var init_mailbox_registry = __esm({
|
|
|
1218
1218
|
this.queues.set(sessionId, queue);
|
|
1219
1219
|
try {
|
|
1220
1220
|
const filePath = getMailboxPath(sessionId, type);
|
|
1221
|
-
|
|
1221
|
+
fs18.appendFileSync(filePath, JSON.stringify(fullMessage) + "\n", "utf-8");
|
|
1222
1222
|
} catch {
|
|
1223
1223
|
}
|
|
1224
1224
|
this.emit(`message:${sessionId}`, fullMessage);
|
|
@@ -1267,7 +1267,7 @@ var init_mailbox_registry = __esm({
|
|
|
1267
1267
|
this.emit(`signal:${sessionId}`, signal);
|
|
1268
1268
|
try {
|
|
1269
1269
|
const filePath = getMailboxPath(sessionId, "sig");
|
|
1270
|
-
|
|
1270
|
+
fs18.appendFileSync(filePath, JSON.stringify(signal) + "\n", "utf-8");
|
|
1271
1271
|
} catch {
|
|
1272
1272
|
}
|
|
1273
1273
|
return signal.id;
|
|
@@ -1281,9 +1281,9 @@ var init_mailbox_registry = __esm({
|
|
|
1281
1281
|
ensureMailbox(sessionId) {
|
|
1282
1282
|
const dir = getMailboxesDir();
|
|
1283
1283
|
for (const type of ["in", "out", "sig"]) {
|
|
1284
|
-
const filePath =
|
|
1285
|
-
if (!
|
|
1286
|
-
|
|
1284
|
+
const filePath = path19.join(dir, `${sessionId}.${type}`);
|
|
1285
|
+
if (!fs18.existsSync(filePath)) {
|
|
1286
|
+
fs18.writeFileSync(filePath, "", "utf-8");
|
|
1287
1287
|
}
|
|
1288
1288
|
}
|
|
1289
1289
|
}
|
|
@@ -1293,8 +1293,8 @@ var init_mailbox_registry = __esm({
|
|
|
1293
1293
|
removeMailbox(sessionId) {
|
|
1294
1294
|
const dir = getMailboxesDir();
|
|
1295
1295
|
for (const type of ["in", "out", "sig"]) {
|
|
1296
|
-
const filePath =
|
|
1297
|
-
if (
|
|
1296
|
+
const filePath = path19.join(dir, `${sessionId}.${type}`);
|
|
1297
|
+
if (fs18.existsSync(filePath)) fs18.unlinkSync(filePath);
|
|
1298
1298
|
}
|
|
1299
1299
|
this.queues.delete(sessionId);
|
|
1300
1300
|
this.signalQueues.delete(sessionId);
|
|
@@ -1313,9 +1313,9 @@ __export(AgentCoordinationTool_exports, {
|
|
|
1313
1313
|
spawnAgent: () => spawnAgent,
|
|
1314
1314
|
waitAgent: () => waitAgent
|
|
1315
1315
|
});
|
|
1316
|
-
import
|
|
1316
|
+
import fs19 from "fs";
|
|
1317
1317
|
import os14 from "os";
|
|
1318
|
-
import
|
|
1318
|
+
import path20 from "path";
|
|
1319
1319
|
import { spawn as spawn3 } from "child_process";
|
|
1320
1320
|
import { v4 as uuidv46 } from "uuid";
|
|
1321
1321
|
function readUserContextFromEnv() {
|
|
@@ -1429,10 +1429,10 @@ async function spawnAgent(args) {
|
|
|
1429
1429
|
spawnLog.error("Payload NOT serializable", { error: e.message });
|
|
1430
1430
|
throw new BluMaError("WORKER_CONTEXT_NOT_SERIALIZABLE" /* WORKER_CONTEXT_NOT_SERIALIZABLE */, `Worker context is not JSON-serializable: ${e.message}`);
|
|
1431
1431
|
}
|
|
1432
|
-
const payloadDir =
|
|
1433
|
-
const payloadPath =
|
|
1432
|
+
const payloadDir = fs19.mkdtempSync(path20.join(os14.tmpdir(), "bluma-worker-"));
|
|
1433
|
+
const payloadPath = path20.join(payloadDir, `${sessionId}.json`);
|
|
1434
1434
|
try {
|
|
1435
|
-
|
|
1435
|
+
fs19.writeFileSync(payloadPath, JSON.stringify(payload, null, 2), "utf-8");
|
|
1436
1436
|
spawnLog.debug("Payload written", { payloadPath });
|
|
1437
1437
|
} catch (e) {
|
|
1438
1438
|
spawnLog.error("Failed to write payload", { error: e.message, payloadPath });
|
|
@@ -2201,11 +2201,11 @@ var NodeFsOperations = {
|
|
|
2201
2201
|
if (fd) fs.closeSync(fd);
|
|
2202
2202
|
}
|
|
2203
2203
|
},
|
|
2204
|
-
appendFileSync(
|
|
2205
|
-
const _ = slowLogging`fs.appendFileSync(${
|
|
2204
|
+
appendFileSync(path57, data, options) {
|
|
2205
|
+
const _ = slowLogging`fs.appendFileSync(${path57}, ${data.length} chars)`;
|
|
2206
2206
|
if (options?.mode !== void 0) {
|
|
2207
2207
|
try {
|
|
2208
|
-
const fd = fs.openSync(
|
|
2208
|
+
const fd = fs.openSync(path57, "ax", options.mode);
|
|
2209
2209
|
try {
|
|
2210
2210
|
fs.appendFileSync(fd, data);
|
|
2211
2211
|
} finally {
|
|
@@ -2216,35 +2216,35 @@ var NodeFsOperations = {
|
|
|
2216
2216
|
if (getErrnoCode(e) !== "EEXIST") throw e;
|
|
2217
2217
|
}
|
|
2218
2218
|
}
|
|
2219
|
-
fs.appendFileSync(
|
|
2219
|
+
fs.appendFileSync(path57, data);
|
|
2220
2220
|
},
|
|
2221
2221
|
copyFileSync(src, dest) {
|
|
2222
2222
|
const _ = slowLogging`fs.copyFileSync(${src} → ${dest})`;
|
|
2223
2223
|
fs.copyFileSync(src, dest);
|
|
2224
2224
|
},
|
|
2225
|
-
unlinkSync(
|
|
2226
|
-
const _ = slowLogging`fs.unlinkSync(${
|
|
2227
|
-
fs.unlinkSync(
|
|
2225
|
+
unlinkSync(path57) {
|
|
2226
|
+
const _ = slowLogging`fs.unlinkSync(${path57})`;
|
|
2227
|
+
fs.unlinkSync(path57);
|
|
2228
2228
|
},
|
|
2229
2229
|
renameSync(oldPath, newPath) {
|
|
2230
2230
|
const _ = slowLogging`fs.renameSync(${oldPath} → ${newPath})`;
|
|
2231
2231
|
fs.renameSync(oldPath, newPath);
|
|
2232
2232
|
},
|
|
2233
|
-
linkSync(target,
|
|
2234
|
-
const _ = slowLogging`fs.linkSync(${target} → ${
|
|
2235
|
-
fs.linkSync(target,
|
|
2233
|
+
linkSync(target, path57) {
|
|
2234
|
+
const _ = slowLogging`fs.linkSync(${target} → ${path57})`;
|
|
2235
|
+
fs.linkSync(target, path57);
|
|
2236
2236
|
},
|
|
2237
|
-
symlinkSync(target,
|
|
2238
|
-
const _ = slowLogging`fs.symlinkSync(${target} → ${
|
|
2239
|
-
fs.symlinkSync(target,
|
|
2237
|
+
symlinkSync(target, path57, type) {
|
|
2238
|
+
const _ = slowLogging`fs.symlinkSync(${target} → ${path57})`;
|
|
2239
|
+
fs.symlinkSync(target, path57, type);
|
|
2240
2240
|
},
|
|
2241
|
-
readlinkSync(
|
|
2242
|
-
const _ = slowLogging`fs.readlinkSync(${
|
|
2243
|
-
return fs.readlinkSync(
|
|
2241
|
+
readlinkSync(path57) {
|
|
2242
|
+
const _ = slowLogging`fs.readlinkSync(${path57})`;
|
|
2243
|
+
return fs.readlinkSync(path57);
|
|
2244
2244
|
},
|
|
2245
|
-
realpathSync(
|
|
2246
|
-
const _ = slowLogging`fs.realpathSync(${
|
|
2247
|
-
return fs.realpathSync(
|
|
2245
|
+
realpathSync(path57) {
|
|
2246
|
+
const _ = slowLogging`fs.realpathSync(${path57})`;
|
|
2247
|
+
return fs.realpathSync(path57).normalize("NFC");
|
|
2248
2248
|
},
|
|
2249
2249
|
mkdirSync(dirPath, options) {
|
|
2250
2250
|
const _ = slowLogging`fs.mkdirSync(${dirPath})`;
|
|
@@ -2277,12 +2277,12 @@ var NodeFsOperations = {
|
|
|
2277
2277
|
const _ = slowLogging`fs.rmdirSync(${dirPath})`;
|
|
2278
2278
|
fs.rmdirSync(dirPath);
|
|
2279
2279
|
},
|
|
2280
|
-
rmSync(
|
|
2281
|
-
const _ = slowLogging`fs.rmSync(${
|
|
2282
|
-
fs.rmSync(
|
|
2280
|
+
rmSync(path57, options) {
|
|
2281
|
+
const _ = slowLogging`fs.rmSync(${path57})`;
|
|
2282
|
+
fs.rmSync(path57, options);
|
|
2283
2283
|
},
|
|
2284
|
-
createWriteStream(
|
|
2285
|
-
return fs.createWriteStream(
|
|
2284
|
+
createWriteStream(path57) {
|
|
2285
|
+
return fs.createWriteStream(path57);
|
|
2286
2286
|
},
|
|
2287
2287
|
async readFileBytes(fsPath, maxBytes) {
|
|
2288
2288
|
if (maxBytes === void 0) {
|
|
@@ -2389,12 +2389,12 @@ function shouldLogDebugMessage(message2) {
|
|
|
2389
2389
|
var hasFormattedOutput = false;
|
|
2390
2390
|
var debugWriter = null;
|
|
2391
2391
|
var pendingWrite = Promise.resolve();
|
|
2392
|
-
async function appendAsync(needMkdir, dir,
|
|
2392
|
+
async function appendAsync(needMkdir, dir, path57, content) {
|
|
2393
2393
|
if (needMkdir) {
|
|
2394
2394
|
await mkdir(dir, { recursive: true }).catch(() => {
|
|
2395
2395
|
});
|
|
2396
2396
|
}
|
|
2397
|
-
await appendFile(
|
|
2397
|
+
await appendFile(path57, content);
|
|
2398
2398
|
void updateLatestDebugLogSymlink();
|
|
2399
2399
|
}
|
|
2400
2400
|
function noop() {
|
|
@@ -2404,8 +2404,8 @@ function getDebugWriter() {
|
|
|
2404
2404
|
let ensuredDir = null;
|
|
2405
2405
|
debugWriter = createBufferedWriter({
|
|
2406
2406
|
writeFn: (content) => {
|
|
2407
|
-
const
|
|
2408
|
-
const dir = dirname(
|
|
2407
|
+
const path57 = getDebugLogPath();
|
|
2408
|
+
const dir = dirname(path57);
|
|
2409
2409
|
const needMkdir = ensuredDir !== dir;
|
|
2410
2410
|
ensuredDir = dir;
|
|
2411
2411
|
if (isDebugMode()) {
|
|
@@ -2415,11 +2415,11 @@ function getDebugWriter() {
|
|
|
2415
2415
|
} catch {
|
|
2416
2416
|
}
|
|
2417
2417
|
}
|
|
2418
|
-
getFsImplementation().appendFileSync(
|
|
2418
|
+
getFsImplementation().appendFileSync(path57, content);
|
|
2419
2419
|
void updateLatestDebugLogSymlink();
|
|
2420
2420
|
return;
|
|
2421
2421
|
}
|
|
2422
|
-
pendingWrite = pendingWrite.then(appendAsync.bind(null, needMkdir, dir,
|
|
2422
|
+
pendingWrite = pendingWrite.then(appendAsync.bind(null, needMkdir, dir, path57, content)).catch(noop);
|
|
2423
2423
|
},
|
|
2424
2424
|
flushIntervalMs: 1e3,
|
|
2425
2425
|
maxBufferSize: 100,
|
|
@@ -8606,8 +8606,8 @@ import codeExcerpt from "code-excerpt";
|
|
|
8606
8606
|
import { readFileSync as readFileSync2 } from "fs";
|
|
8607
8607
|
import StackUtils from "stack-utils";
|
|
8608
8608
|
import { jsx as jsx6, jsxs } from "react/jsx-runtime";
|
|
8609
|
-
var cleanupPath = (
|
|
8610
|
-
return
|
|
8609
|
+
var cleanupPath = (path57) => {
|
|
8610
|
+
return path57?.replace(`file://${process.cwd()}/`, "");
|
|
8611
8611
|
};
|
|
8612
8612
|
var stackUtils;
|
|
8613
8613
|
function getStackUtils() {
|
|
@@ -12584,8 +12584,8 @@ var getInstance = (stdout, createInstance) => {
|
|
|
12584
12584
|
|
|
12585
12585
|
// src/main.ts
|
|
12586
12586
|
import { EventEmitter as EventEmitter7 } from "events";
|
|
12587
|
-
import
|
|
12588
|
-
import
|
|
12587
|
+
import fs51 from "fs";
|
|
12588
|
+
import path56 from "path";
|
|
12589
12589
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
12590
12590
|
import { spawn as spawn6 } from "child_process";
|
|
12591
12591
|
import { v4 as uuidv412 } from "uuid";
|
|
@@ -13479,12 +13479,12 @@ function cancelSlashSubmenu() {
|
|
|
13479
13479
|
|
|
13480
13480
|
// src/app/agent/agent.ts
|
|
13481
13481
|
import * as dotenv from "dotenv";
|
|
13482
|
-
import
|
|
13482
|
+
import path47 from "path";
|
|
13483
13483
|
import os31 from "os";
|
|
13484
13484
|
|
|
13485
13485
|
// src/app/agent/tool_invoker.ts
|
|
13486
13486
|
import { promises as fs25 } from "fs";
|
|
13487
|
-
import
|
|
13487
|
+
import path27 from "path";
|
|
13488
13488
|
import { fileURLToPath } from "url";
|
|
13489
13489
|
|
|
13490
13490
|
// src/app/agent/tools/EditTool/EditTool.ts
|
|
@@ -14028,10 +14028,173 @@ ${finalDiff}`,
|
|
|
14028
14028
|
// src/app/agent/tools/MessageTool/MessageTool.ts
|
|
14029
14029
|
import { v4 as uuidv42 } from "uuid";
|
|
14030
14030
|
|
|
14031
|
-
// src/app/agent/runtime/
|
|
14032
|
-
init_sandbox_policy();
|
|
14031
|
+
// src/app/agent/runtime/factorai_context.ts
|
|
14033
14032
|
import fs5 from "fs";
|
|
14034
14033
|
import path5 from "path";
|
|
14034
|
+
function normalizeContext(raw) {
|
|
14035
|
+
if (!raw || typeof raw.appId !== "string" || !raw.appId.trim()) {
|
|
14036
|
+
return null;
|
|
14037
|
+
}
|
|
14038
|
+
const appId = raw.appId.trim();
|
|
14039
|
+
return {
|
|
14040
|
+
appId,
|
|
14041
|
+
tenantId: typeof raw.tenantId === "string" ? raw.tenantId : null,
|
|
14042
|
+
projectId: typeof raw.projectId === "string" ? raw.projectId : null,
|
|
14043
|
+
workspaceId: typeof raw.workspaceId === "string" ? raw.workspaceId : null,
|
|
14044
|
+
revisionId: typeof raw.revisionId === "string" ? raw.revisionId : null,
|
|
14045
|
+
deploymentId: typeof raw.deploymentId === "string" ? raw.deploymentId : null,
|
|
14046
|
+
buildJobId: typeof raw.buildJobId === "string" ? raw.buildJobId : null,
|
|
14047
|
+
manifestPath: typeof raw.manifestPath === "string" ? raw.manifestPath : null,
|
|
14048
|
+
manifestFile: typeof raw.manifestFile === "string" ? raw.manifestFile : null,
|
|
14049
|
+
appUrl: typeof raw.appUrl === "string" ? raw.appUrl : null
|
|
14050
|
+
};
|
|
14051
|
+
}
|
|
14052
|
+
function readJsonFile(filePath) {
|
|
14053
|
+
try {
|
|
14054
|
+
if (!fs5.existsSync(filePath)) {
|
|
14055
|
+
return null;
|
|
14056
|
+
}
|
|
14057
|
+
const parsed = JSON.parse(fs5.readFileSync(filePath, "utf8"));
|
|
14058
|
+
if (parsed && typeof parsed === "object") {
|
|
14059
|
+
return parsed.appContext && typeof parsed.appContext === "object" ? parsed.appContext : parsed;
|
|
14060
|
+
}
|
|
14061
|
+
} catch {
|
|
14062
|
+
return null;
|
|
14063
|
+
}
|
|
14064
|
+
return null;
|
|
14065
|
+
}
|
|
14066
|
+
function readFactorAiWorkspaceManifest(projectDir = process.cwd()) {
|
|
14067
|
+
const manifestPath = path5.join(projectDir, "factorai.sh.json");
|
|
14068
|
+
try {
|
|
14069
|
+
if (!fs5.existsSync(manifestPath)) {
|
|
14070
|
+
return null;
|
|
14071
|
+
}
|
|
14072
|
+
const parsed = JSON.parse(fs5.readFileSync(manifestPath, "utf8"));
|
|
14073
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
14074
|
+
} catch {
|
|
14075
|
+
return null;
|
|
14076
|
+
}
|
|
14077
|
+
}
|
|
14078
|
+
function readContextFromWorkspace() {
|
|
14079
|
+
const candidate = path5.join(process.cwd(), "factorai.sh.json");
|
|
14080
|
+
const parsed = readJsonFile(candidate);
|
|
14081
|
+
if (!parsed) {
|
|
14082
|
+
return null;
|
|
14083
|
+
}
|
|
14084
|
+
const context = normalizeContext(parsed);
|
|
14085
|
+
return context;
|
|
14086
|
+
}
|
|
14087
|
+
function loadFactorAiAppContext() {
|
|
14088
|
+
return readContextFromWorkspace();
|
|
14089
|
+
}
|
|
14090
|
+
var FACTOR_SH_URL_APP_FIELD = "factor-sh-url-app";
|
|
14091
|
+
function resolveFactorShAppLiveUrl(projectDir = process.cwd()) {
|
|
14092
|
+
const manifest = readFactorAiWorkspaceManifest(projectDir);
|
|
14093
|
+
const raw = manifest?.appContext?.appUrl ?? (typeof manifest?.app?.url === "string" ? manifest.app.url : null);
|
|
14094
|
+
if (typeof raw !== "string" || !raw.trim()) {
|
|
14095
|
+
return null;
|
|
14096
|
+
}
|
|
14097
|
+
const url = raw.trim();
|
|
14098
|
+
if (/^https?:\/\//i.test(url)) {
|
|
14099
|
+
return url;
|
|
14100
|
+
}
|
|
14101
|
+
const base = (process.env.SEVERINO_URL || process.env.FACTORAI_BASE_URL || process.env.FACTORAI_URL || "").trim().replace(/\/$/, "");
|
|
14102
|
+
if (!base) {
|
|
14103
|
+
return url.startsWith("/") ? url : `/${url}`;
|
|
14104
|
+
}
|
|
14105
|
+
return `${base}${url.startsWith("/") ? url : `/${url}`}`;
|
|
14106
|
+
}
|
|
14107
|
+
function formatFactorAiAppContextSummary(context) {
|
|
14108
|
+
if (!context) {
|
|
14109
|
+
return "";
|
|
14110
|
+
}
|
|
14111
|
+
const parts = [
|
|
14112
|
+
`appId=${context.appId}`,
|
|
14113
|
+
context.tenantId ? `tenantId=${context.tenantId}` : null,
|
|
14114
|
+
context.projectId ? `projectId=${context.projectId}` : null,
|
|
14115
|
+
context.workspaceId ? `workspaceId=${context.workspaceId}` : null,
|
|
14116
|
+
context.revisionId ? `revisionId=${context.revisionId}` : null,
|
|
14117
|
+
context.deploymentId ? `deploymentId=${context.deploymentId}` : null,
|
|
14118
|
+
context.buildJobId ? `buildJobId=${context.buildJobId}` : null,
|
|
14119
|
+
context.appUrl ? `appUrl=${context.appUrl}` : null
|
|
14120
|
+
].filter(Boolean);
|
|
14121
|
+
return parts.join(" | ");
|
|
14122
|
+
}
|
|
14123
|
+
function buildFactorAiWorkspaceManifest(input) {
|
|
14124
|
+
const projectDir = input.projectDir || process.cwd();
|
|
14125
|
+
const existing = readFactorAiWorkspaceManifest(projectDir) || {};
|
|
14126
|
+
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
14127
|
+
const nextAppContext = {
|
|
14128
|
+
...typeof existing.appContext === "object" && existing.appContext ? existing.appContext : {},
|
|
14129
|
+
appId: input.appContext.appId,
|
|
14130
|
+
tenantId: input.appContext.tenantId ?? null,
|
|
14131
|
+
projectId: input.appContext.projectId ?? null,
|
|
14132
|
+
workspaceId: input.appContext.workspaceId ?? null,
|
|
14133
|
+
revisionId: input.appContext.revisionId ?? null,
|
|
14134
|
+
deploymentId: input.appContext.deploymentId ?? null,
|
|
14135
|
+
buildJobId: input.appContext.buildJobId ?? null,
|
|
14136
|
+
manifestPath: "factorai.sh.json",
|
|
14137
|
+
manifestFile: "factorai.sh.json",
|
|
14138
|
+
appUrl: input.appContext.appUrl ?? null
|
|
14139
|
+
};
|
|
14140
|
+
return {
|
|
14141
|
+
...existing,
|
|
14142
|
+
version: 1,
|
|
14143
|
+
manifestFile: "factorai.sh.json",
|
|
14144
|
+
manifestPath: "factorai.sh.json",
|
|
14145
|
+
appContext: nextAppContext,
|
|
14146
|
+
app: {
|
|
14147
|
+
...typeof existing.app === "object" && existing.app ? existing.app : {},
|
|
14148
|
+
name: input.name ?? (typeof existing.app === "object" && existing.app ? existing.app.name : void 0) ?? null,
|
|
14149
|
+
status: input.status ?? (typeof existing.app === "object" && existing.app ? existing.app.status : void 0) ?? "building",
|
|
14150
|
+
isRedeploy: input.isRedeploy ?? (typeof existing.app === "object" && existing.app ? existing.app.isRedeploy : false),
|
|
14151
|
+
message: input.message ?? (typeof existing.app === "object" && existing.app ? existing.app.message : null),
|
|
14152
|
+
...input.app || {}
|
|
14153
|
+
},
|
|
14154
|
+
agent: {
|
|
14155
|
+
provider: "bluma",
|
|
14156
|
+
mode: "tool-first",
|
|
14157
|
+
instructions: [
|
|
14158
|
+
"Read factorai.sh.json as the source of truth before editing files.",
|
|
14159
|
+
"Load skill factorai-sh for the full FactorAI.sh workflow.",
|
|
14160
|
+
"For an app already online: use factorai.sh.apply_app_changes with deploy:true (not deploy_app again).",
|
|
14161
|
+
"Poll factorai.sh.get_app_status until building\u2192ready before reporting the live URL.",
|
|
14162
|
+
"Use factorai.sh.redeploy_app only when rebuilding without new file patches."
|
|
14163
|
+
],
|
|
14164
|
+
...typeof existing.agent === "object" && existing.agent ? existing.agent : {},
|
|
14165
|
+
...input.agent || {}
|
|
14166
|
+
},
|
|
14167
|
+
sandbox: {
|
|
14168
|
+
...typeof existing.sandbox === "object" && existing.sandbox ? existing.sandbox : {},
|
|
14169
|
+
...input.sandbox || {}
|
|
14170
|
+
},
|
|
14171
|
+
source: {
|
|
14172
|
+
root: ".",
|
|
14173
|
+
publicDir: "./public",
|
|
14174
|
+
appDir: ".",
|
|
14175
|
+
...typeof existing.source === "object" && existing.source ? existing.source : {},
|
|
14176
|
+
...input.source || {}
|
|
14177
|
+
},
|
|
14178
|
+
updatedAt: now2,
|
|
14179
|
+
...input.extra || {}
|
|
14180
|
+
};
|
|
14181
|
+
}
|
|
14182
|
+
async function writeFactorAiWorkspaceManifest(input) {
|
|
14183
|
+
const projectDir = input.projectDir || process.cwd();
|
|
14184
|
+
const manifest = buildFactorAiWorkspaceManifest({ ...input, projectDir });
|
|
14185
|
+
const manifestPath = path5.join(projectDir, "factorai.sh.json");
|
|
14186
|
+
fs5.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
14187
|
+
`, "utf8");
|
|
14188
|
+
return { manifestPath, manifest };
|
|
14189
|
+
}
|
|
14190
|
+
|
|
14191
|
+
// src/app/agent/tools/MessageTool/MessageTool.ts
|
|
14192
|
+
init_sandbox_policy();
|
|
14193
|
+
|
|
14194
|
+
// src/app/agent/runtime/sandbox_message_validation.ts
|
|
14195
|
+
init_sandbox_policy();
|
|
14196
|
+
import fs6 from "fs";
|
|
14197
|
+
import path6 from "path";
|
|
14035
14198
|
var HTTP_URL_PATTERN = /^https?:\/\//i;
|
|
14036
14199
|
function normalizeAttachmentPath(entry) {
|
|
14037
14200
|
if (typeof entry === "string") {
|
|
@@ -14104,14 +14267,14 @@ function validateSandboxMessageDeliverables(args, policy = getSandboxPolicy(), o
|
|
|
14104
14267
|
details: err instanceof Error ? err.message : String(err)
|
|
14105
14268
|
};
|
|
14106
14269
|
}
|
|
14107
|
-
if (!
|
|
14270
|
+
if (!fs6.existsSync(resolved)) {
|
|
14108
14271
|
return {
|
|
14109
14272
|
ok: false,
|
|
14110
14273
|
error: "Attachment file does not exist",
|
|
14111
14274
|
details: `Create the deliverable first (file_write \u2192 .bluma/artifacts/...). Missing: ${resolved}`
|
|
14112
14275
|
};
|
|
14113
14276
|
}
|
|
14114
|
-
const stat =
|
|
14277
|
+
const stat = fs6.statSync(resolved);
|
|
14115
14278
|
if (!stat.isFile()) {
|
|
14116
14279
|
return {
|
|
14117
14280
|
ok: false,
|
|
@@ -14124,7 +14287,7 @@ function validateSandboxMessageDeliverables(args, policy = getSandboxPolicy(), o
|
|
|
14124
14287
|
return {
|
|
14125
14288
|
ok: false,
|
|
14126
14289
|
error: "Files are delivered only via .bluma/artifacts/",
|
|
14127
|
-
details: `Move or copy the deliverable to ${
|
|
14290
|
+
details: `Move or copy the deliverable to ${path6.relative(policy.workspaceRoot, artifactsDir2)}/ and pass that path in attachments[]. Rejected: ${raw}`
|
|
14128
14291
|
};
|
|
14129
14292
|
}
|
|
14130
14293
|
}
|
|
@@ -14159,18 +14322,25 @@ function normalizeMessageToolArgs(raw) {
|
|
|
14159
14322
|
attachments = [rawAttachments.trim()];
|
|
14160
14323
|
}
|
|
14161
14324
|
}
|
|
14325
|
+
const rawUrl = raw[FACTOR_SH_URL_APP_FIELD] ?? raw.factor_sh_url_app ?? raw.factoraiShUrlApp ?? raw.factorai_sh_url_app;
|
|
14326
|
+
const factorShUrlApp = typeof rawUrl === "string" && rawUrl.trim() ? rawUrl.trim() : void 0;
|
|
14162
14327
|
return {
|
|
14163
14328
|
content,
|
|
14164
14329
|
message_type,
|
|
14165
|
-
attachments: attachments?.length ? attachments : void 0
|
|
14330
|
+
attachments: attachments?.length ? attachments : void 0,
|
|
14331
|
+
factorShUrlApp
|
|
14166
14332
|
};
|
|
14167
14333
|
}
|
|
14168
14334
|
|
|
14169
14335
|
// src/app/agent/tools/MessageTool/MessageTool.ts
|
|
14170
14336
|
function message(args) {
|
|
14171
|
-
const { content, message_type, attachments } = normalizeMessageToolArgs(
|
|
14337
|
+
const { content, message_type, attachments, factorShUrlApp } = normalizeMessageToolArgs(
|
|
14172
14338
|
args
|
|
14173
14339
|
);
|
|
14340
|
+
let resolvedFactorShUrl = factorShUrlApp;
|
|
14341
|
+
if (message_type === "result" && getSandboxPolicy().isSandbox && !resolvedFactorShUrl) {
|
|
14342
|
+
resolvedFactorShUrl = resolveFactorShAppLiveUrl() ?? void 0;
|
|
14343
|
+
}
|
|
14174
14344
|
const deliverableCheck = validateSandboxMessageDeliverables(
|
|
14175
14345
|
{
|
|
14176
14346
|
message_type,
|
|
@@ -14198,6 +14368,7 @@ function message(args) {
|
|
|
14198
14368
|
body: content
|
|
14199
14369
|
},
|
|
14200
14370
|
attachments: Array.isArray(attachments) ? attachments : void 0,
|
|
14371
|
+
...resolvedFactorShUrl ? { [FACTOR_SH_URL_APP_FIELD]: resolvedFactorShUrl } : {},
|
|
14201
14372
|
success: true,
|
|
14202
14373
|
delivered: true
|
|
14203
14374
|
};
|
|
@@ -14205,8 +14376,8 @@ function message(args) {
|
|
|
14205
14376
|
}
|
|
14206
14377
|
|
|
14207
14378
|
// src/app/agent/tools/LsTool/LsTool.ts
|
|
14208
|
-
import { promises as
|
|
14209
|
-
import
|
|
14379
|
+
import { promises as fs7 } from "fs";
|
|
14380
|
+
import path7 from "path";
|
|
14210
14381
|
import os5 from "os";
|
|
14211
14382
|
import { minimatch } from "minimatch";
|
|
14212
14383
|
var DEFAULT_IGNORE = /* @__PURE__ */ new Set([
|
|
@@ -14226,7 +14397,7 @@ var DEFAULT_IGNORE = /* @__PURE__ */ new Set([
|
|
|
14226
14397
|
]);
|
|
14227
14398
|
function expandPath(p) {
|
|
14228
14399
|
if (p === "~" || p.startsWith("~/")) {
|
|
14229
|
-
return
|
|
14400
|
+
return path7.join(os5.homedir(), p.slice(2));
|
|
14230
14401
|
}
|
|
14231
14402
|
return p;
|
|
14232
14403
|
}
|
|
@@ -14252,8 +14423,8 @@ async function ls(args) {
|
|
|
14252
14423
|
max_depth
|
|
14253
14424
|
} = args;
|
|
14254
14425
|
try {
|
|
14255
|
-
const basePath =
|
|
14256
|
-
const stat = await
|
|
14426
|
+
const basePath = path7.resolve(expandPath(directory_path));
|
|
14427
|
+
const stat = await fs7.stat(basePath).catch(() => null);
|
|
14257
14428
|
if (!stat || !stat.isDirectory()) {
|
|
14258
14429
|
throw new Error(`Directory '${directory_path}' not found.`);
|
|
14259
14430
|
}
|
|
@@ -14265,11 +14436,11 @@ async function ls(args) {
|
|
|
14265
14436
|
const allDirs = [];
|
|
14266
14437
|
const walk = async (currentDir, currentDepth) => {
|
|
14267
14438
|
if (max_depth !== void 0 && currentDepth >= max_depth) return;
|
|
14268
|
-
const entries = await
|
|
14439
|
+
const entries = await fs7.readdir(currentDir, { withFileTypes: true });
|
|
14269
14440
|
for (const entry of entries) {
|
|
14270
14441
|
const entryName = entry.name;
|
|
14271
|
-
const fullPath =
|
|
14272
|
-
const relativePath =
|
|
14442
|
+
const fullPath = path7.join(currentDir, entryName);
|
|
14443
|
+
const relativePath = path7.relative(basePath, fullPath).split(path7.sep).join("/");
|
|
14273
14444
|
const isHidden = entryName.startsWith(".");
|
|
14274
14445
|
if (shouldIgnore(entryName, allIgnorePatterns) || isHidden && !show_hidden) {
|
|
14275
14446
|
continue;
|
|
@@ -14278,7 +14449,7 @@ async function ls(args) {
|
|
|
14278
14449
|
allDirs.push(relativePath);
|
|
14279
14450
|
if (recursive) await walk(fullPath, currentDepth + 1);
|
|
14280
14451
|
} else if (entry.isFile()) {
|
|
14281
|
-
if (!normalizedExtensions || normalizedExtensions.includes(
|
|
14452
|
+
if (!normalizedExtensions || normalizedExtensions.includes(path7.extname(entryName).toLowerCase())) {
|
|
14282
14453
|
allFiles.push(relativePath);
|
|
14283
14454
|
}
|
|
14284
14455
|
}
|
|
@@ -14291,7 +14462,7 @@ async function ls(args) {
|
|
|
14291
14462
|
const dirEnd = end_index ?? allDirs.length;
|
|
14292
14463
|
return {
|
|
14293
14464
|
success: true,
|
|
14294
|
-
path: basePath.split(
|
|
14465
|
+
path: basePath.split(path7.sep).join("/"),
|
|
14295
14466
|
recursive,
|
|
14296
14467
|
total_files: allFiles.length,
|
|
14297
14468
|
total_directories: allDirs.length,
|
|
@@ -14313,13 +14484,13 @@ async function ls(args) {
|
|
|
14313
14484
|
|
|
14314
14485
|
// src/app/agent/tools/ReadLinesTool/ReadLinesTool.ts
|
|
14315
14486
|
init_sandbox_policy();
|
|
14316
|
-
import { promises as
|
|
14317
|
-
import
|
|
14487
|
+
import { promises as fs8 } from "fs";
|
|
14488
|
+
import path8 from "path";
|
|
14318
14489
|
import os6 from "os";
|
|
14319
14490
|
var DEFAULT_LINE_WINDOW = 2e3;
|
|
14320
14491
|
function expandPath2(p) {
|
|
14321
14492
|
if (p === "~" || p.startsWith("~/")) {
|
|
14322
|
-
return
|
|
14493
|
+
return path8.join(os6.homedir(), p.slice(2));
|
|
14323
14494
|
}
|
|
14324
14495
|
return p;
|
|
14325
14496
|
}
|
|
@@ -14337,7 +14508,7 @@ async function readLines(args) {
|
|
|
14337
14508
|
}
|
|
14338
14509
|
try {
|
|
14339
14510
|
const resolvedPath = resolveWorkspacePath(expandPath2(filepath));
|
|
14340
|
-
const stat = await
|
|
14511
|
+
const stat = await fs8.stat(resolvedPath).catch(() => null);
|
|
14341
14512
|
if (!stat || !stat.isFile()) {
|
|
14342
14513
|
const workspaceRoot = resolveWorkspacePath(".");
|
|
14343
14514
|
const isInsideWorkspace = resolvedPath.startsWith(workspaceRoot);
|
|
@@ -14356,7 +14527,7 @@ async function readLines(args) {
|
|
|
14356
14527
|
if (endLine < startLine) {
|
|
14357
14528
|
throw new Error("Invalid line range: end_line must be >= start_line.");
|
|
14358
14529
|
}
|
|
14359
|
-
const fileContent = await
|
|
14530
|
+
const fileContent = await fs8.readFile(resolvedPath, "utf-8");
|
|
14360
14531
|
const lines = fileContent.split("\n");
|
|
14361
14532
|
const total_lines = lines.length;
|
|
14362
14533
|
const startIndex = startLine - 1;
|
|
@@ -14387,7 +14558,7 @@ async function readLines(args) {
|
|
|
14387
14558
|
|
|
14388
14559
|
// src/app/agent/tools/CountLinesTool/CountLinesTool.ts
|
|
14389
14560
|
import { createReadStream } from "fs";
|
|
14390
|
-
import { promises as
|
|
14561
|
+
import { promises as fs9 } from "fs";
|
|
14391
14562
|
import readline from "readline";
|
|
14392
14563
|
async function countLines(args) {
|
|
14393
14564
|
const filepathInput = typeof args?.filepath === "string" && args.filepath.trim().length > 0 ? args.filepath : typeof args?.file_path === "string" ? args.file_path : "";
|
|
@@ -14414,7 +14585,7 @@ async function countLines(args) {
|
|
|
14414
14585
|
};
|
|
14415
14586
|
}
|
|
14416
14587
|
try {
|
|
14417
|
-
if (!(await
|
|
14588
|
+
if (!(await fs9.stat(filepath)).isFile()) {
|
|
14418
14589
|
throw new Error(`File '${filepath}' not found or is not a file.`);
|
|
14419
14590
|
}
|
|
14420
14591
|
const fileStream = createReadStream(filepath);
|
|
@@ -14604,7 +14775,7 @@ function formatTodoResult(result) {
|
|
|
14604
14775
|
|
|
14605
14776
|
// src/app/agent/tools/FindByNameTool/FindByNameTool.ts
|
|
14606
14777
|
init_sandbox_policy();
|
|
14607
|
-
import
|
|
14778
|
+
import path10 from "path";
|
|
14608
14779
|
import { promises as fsPromises } from "fs";
|
|
14609
14780
|
import os7 from "os";
|
|
14610
14781
|
var MAX_RESULTS = 100;
|
|
@@ -14628,7 +14799,7 @@ var DEFAULT_IGNORE2 = /* @__PURE__ */ new Set([
|
|
|
14628
14799
|
]);
|
|
14629
14800
|
function expandTilde2(p) {
|
|
14630
14801
|
if (p === "~") return os7.homedir();
|
|
14631
|
-
if (p.startsWith("~/")) return
|
|
14802
|
+
if (p.startsWith("~/")) return path10.join(os7.homedir(), p.slice(2));
|
|
14632
14803
|
return p;
|
|
14633
14804
|
}
|
|
14634
14805
|
function globToRegex(glob) {
|
|
@@ -14672,7 +14843,7 @@ function shouldIgnore2(name, extraPatterns, includeHidden) {
|
|
|
14672
14843
|
}
|
|
14673
14844
|
function matchesExtensions(filename, extensions) {
|
|
14674
14845
|
if (!extensions || extensions.length === 0) return true;
|
|
14675
|
-
const ext =
|
|
14846
|
+
const ext = path10.extname(filename).toLowerCase();
|
|
14676
14847
|
return extensions.some((e) => {
|
|
14677
14848
|
const norm = e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`;
|
|
14678
14849
|
return ext === norm;
|
|
@@ -14690,8 +14861,8 @@ async function searchDirectory(dir, pattern, baseDir, options, results) {
|
|
|
14690
14861
|
if (results.length >= MAX_RESULTS) break;
|
|
14691
14862
|
const { name } = entry;
|
|
14692
14863
|
if (shouldIgnore2(name, options.excludePatterns, options.includeHidden)) continue;
|
|
14693
|
-
const fullPath =
|
|
14694
|
-
const relativePath =
|
|
14864
|
+
const fullPath = path10.join(dir, name);
|
|
14865
|
+
const relativePath = path10.relative(baseDir, fullPath).split(path10.sep).join("/");
|
|
14695
14866
|
if (entry.isDirectory()) {
|
|
14696
14867
|
if (pattern.test(name)) {
|
|
14697
14868
|
results.push({ path: relativePath, type: "directory" });
|
|
@@ -14773,7 +14944,7 @@ async function findByName(args) {
|
|
|
14773
14944
|
}
|
|
14774
14945
|
|
|
14775
14946
|
// src/app/agent/tools/GrepSearchTool/GrepSearchTool.ts
|
|
14776
|
-
import
|
|
14947
|
+
import path11 from "path";
|
|
14777
14948
|
import { promises as fsPromises2 } from "fs";
|
|
14778
14949
|
import os8 from "os";
|
|
14779
14950
|
var MAX_RESULTS2 = 200;
|
|
@@ -14863,12 +15034,12 @@ var TEXT_BASENAMES = /* @__PURE__ */ new Set([
|
|
|
14863
15034
|
]);
|
|
14864
15035
|
function expandTilde3(p) {
|
|
14865
15036
|
if (p === "~") return os8.homedir();
|
|
14866
|
-
if (p.startsWith("~/")) return
|
|
15037
|
+
if (p.startsWith("~/")) return path11.join(os8.homedir(), p.slice(2));
|
|
14867
15038
|
return p;
|
|
14868
15039
|
}
|
|
14869
15040
|
function isTextFile(filepath) {
|
|
14870
|
-
const ext =
|
|
14871
|
-
const base =
|
|
15041
|
+
const ext = path11.extname(filepath).toLowerCase();
|
|
15042
|
+
const base = path11.basename(filepath);
|
|
14872
15043
|
return TEXT_EXTENSIONS.has(ext) || TEXT_BASENAMES.has(base) || base.startsWith(".") && !ext;
|
|
14873
15044
|
}
|
|
14874
15045
|
function shouldIgnore3(name) {
|
|
@@ -14905,7 +15076,7 @@ async function searchFile(filepath, baseDir, pattern, contextLines, maxMatchesPe
|
|
|
14905
15076
|
if (stat.size > MAX_FILE_SIZE) return null;
|
|
14906
15077
|
const content = await fsPromises2.readFile(filepath, "utf-8");
|
|
14907
15078
|
const lines = content.split("\n");
|
|
14908
|
-
const relativePath =
|
|
15079
|
+
const relativePath = path11.relative(baseDir, filepath).split(path11.sep).join("/");
|
|
14909
15080
|
const matches = [];
|
|
14910
15081
|
for (let i = 0; i < lines.length && matches.length < maxMatchesPerFile; i++) {
|
|
14911
15082
|
const line = lines[i];
|
|
@@ -14939,7 +15110,7 @@ async function searchDirectory2(dir, baseDir, pattern, includePatterns, contextL
|
|
|
14939
15110
|
for (const entry of entries) {
|
|
14940
15111
|
if (stats.totalMatches >= maxResults) break;
|
|
14941
15112
|
if (shouldIgnore3(entry.name)) continue;
|
|
14942
|
-
const fullPath =
|
|
15113
|
+
const fullPath = path11.join(dir, entry.name);
|
|
14943
15114
|
if (entry.isDirectory()) {
|
|
14944
15115
|
await searchDirectory2(fullPath, baseDir, pattern, includePatterns, contextLines, maxResults, maxMatchesPerFile, results, stats);
|
|
14945
15116
|
} else if (entry.isFile()) {
|
|
@@ -14980,7 +15151,7 @@ async function grepSearch(args) {
|
|
|
14980
15151
|
});
|
|
14981
15152
|
if (!query || typeof query !== "string") return empty("query is required");
|
|
14982
15153
|
if (!searchPath) return empty("path is required");
|
|
14983
|
-
const resolvedPath =
|
|
15154
|
+
const resolvedPath = path11.resolve(expandTilde3(searchPath));
|
|
14984
15155
|
const stat = await fsPromises2.stat(resolvedPath).catch(() => null);
|
|
14985
15156
|
if (!stat) return empty(`Path not found: ${resolvedPath}`);
|
|
14986
15157
|
let pattern;
|
|
@@ -14996,7 +15167,7 @@ async function grepSearch(args) {
|
|
|
14996
15167
|
await searchDirectory2(resolvedPath, resolvedPath, pattern, include_patterns, context_lines, max_results, max_matches_per_file, results, stats);
|
|
14997
15168
|
} else if (stat.isFile()) {
|
|
14998
15169
|
stats.filesSearched = 1;
|
|
14999
|
-
const fileResult = await searchFile(resolvedPath,
|
|
15170
|
+
const fileResult = await searchFile(resolvedPath, path11.dirname(resolvedPath), pattern, context_lines, max_matches_per_file);
|
|
15000
15171
|
if (fileResult) {
|
|
15001
15172
|
results.push(fileResult);
|
|
15002
15173
|
stats.totalMatches = fileResult.match_count;
|
|
@@ -15019,7 +15190,7 @@ async function grepSearch(args) {
|
|
|
15019
15190
|
}
|
|
15020
15191
|
|
|
15021
15192
|
// src/app/agent/tools/ViewFileOutlineTool/ViewFileOutlineTool.ts
|
|
15022
|
-
import
|
|
15193
|
+
import path12 from "path";
|
|
15023
15194
|
import { promises as fsPromises3 } from "fs";
|
|
15024
15195
|
var LANGUAGE_MAP = {
|
|
15025
15196
|
".ts": "typescript",
|
|
@@ -15133,7 +15304,7 @@ var PATTERNS = {
|
|
|
15133
15304
|
]
|
|
15134
15305
|
};
|
|
15135
15306
|
function detectLanguage(filepath) {
|
|
15136
|
-
const ext =
|
|
15307
|
+
const ext = path12.extname(filepath).toLowerCase();
|
|
15137
15308
|
return LANGUAGE_MAP[ext] || "unknown";
|
|
15138
15309
|
}
|
|
15139
15310
|
function determineItemType(line, language) {
|
|
@@ -15229,7 +15400,7 @@ async function viewFileOutline(args) {
|
|
|
15229
15400
|
error: "file_path is required and must be a string"
|
|
15230
15401
|
};
|
|
15231
15402
|
}
|
|
15232
|
-
const resolvedPath =
|
|
15403
|
+
const resolvedPath = path12.resolve(file_path);
|
|
15233
15404
|
let content;
|
|
15234
15405
|
try {
|
|
15235
15406
|
content = await fsPromises3.readFile(resolvedPath, "utf-8");
|
|
@@ -15275,21 +15446,21 @@ init_CommandStatusTool();
|
|
|
15275
15446
|
// src/app/agent/tools/TaskBoundaryTool/TaskBoundaryTool.ts
|
|
15276
15447
|
init_sandbox_policy();
|
|
15277
15448
|
init_task_store();
|
|
15278
|
-
import
|
|
15279
|
-
import { promises as
|
|
15449
|
+
import path13 from "path";
|
|
15450
|
+
import { promises as fs11 } from "fs";
|
|
15280
15451
|
var artifactsDir = null;
|
|
15281
15452
|
async function getArtifactsDir() {
|
|
15282
15453
|
if (artifactsDir) return artifactsDir;
|
|
15283
15454
|
const policy = getSandboxPolicy();
|
|
15284
|
-
const baseDir =
|
|
15455
|
+
const baseDir = path13.join(policy.workspaceRoot, ".bluma", "artifacts");
|
|
15285
15456
|
const sessionId = Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
|
|
15286
|
-
artifactsDir =
|
|
15287
|
-
await
|
|
15457
|
+
artifactsDir = path13.join(baseDir, sessionId);
|
|
15458
|
+
await fs11.mkdir(artifactsDir, { recursive: true });
|
|
15288
15459
|
return artifactsDir;
|
|
15289
15460
|
}
|
|
15290
15461
|
async function updateTaskFile(task) {
|
|
15291
15462
|
const dir = await getArtifactsDir();
|
|
15292
|
-
const taskFile =
|
|
15463
|
+
const taskFile = path13.join(dir, "task.md");
|
|
15293
15464
|
const content = `# ${task.taskName}
|
|
15294
15465
|
|
|
15295
15466
|
**Mode:** ${task.mode}
|
|
@@ -15301,7 +15472,7 @@ async function updateTaskFile(task) {
|
|
|
15301
15472
|
## Summary
|
|
15302
15473
|
${task.summary}
|
|
15303
15474
|
`;
|
|
15304
|
-
await
|
|
15475
|
+
await fs11.writeFile(taskFile, content, "utf-8");
|
|
15305
15476
|
}
|
|
15306
15477
|
async function taskBoundary(args) {
|
|
15307
15478
|
try {
|
|
@@ -15417,8 +15588,8 @@ async function readArtifact(args) {
|
|
|
15417
15588
|
return { success: false, error: "filename is required" };
|
|
15418
15589
|
}
|
|
15419
15590
|
const dir = await getArtifactsDir();
|
|
15420
|
-
const filepath =
|
|
15421
|
-
const content = await
|
|
15591
|
+
const filepath = path13.join(dir, filename);
|
|
15592
|
+
const content = await fs11.readFile(filepath, "utf-8");
|
|
15422
15593
|
return {
|
|
15423
15594
|
success: true,
|
|
15424
15595
|
content
|
|
@@ -15962,8 +16133,8 @@ ${skill.content}`;
|
|
|
15962
16133
|
}
|
|
15963
16134
|
|
|
15964
16135
|
// src/app/agent/tools/CodingMemoryTool/CodingMemoryTool.ts
|
|
15965
|
-
import * as
|
|
15966
|
-
import * as
|
|
16136
|
+
import * as fs12 from "fs";
|
|
16137
|
+
import * as path14 from "path";
|
|
15967
16138
|
import os10 from "os";
|
|
15968
16139
|
var PROMPT_DEFAULT_MAX_TOTAL = 1e4;
|
|
15969
16140
|
var PROMPT_DEFAULT_MAX_NOTES = 25;
|
|
@@ -15972,14 +16143,14 @@ function readCodingMemoryForPrompt(options) {
|
|
|
15972
16143
|
const maxTotal = options?.maxTotalChars ?? PROMPT_DEFAULT_MAX_TOTAL;
|
|
15973
16144
|
const maxNotes = options?.maxNotes ?? PROMPT_DEFAULT_MAX_NOTES;
|
|
15974
16145
|
const preview = options?.previewCharsPerNote ?? PROMPT_DEFAULT_PREVIEW;
|
|
15975
|
-
const globalPath =
|
|
15976
|
-
const legacyPath =
|
|
16146
|
+
const globalPath = path14.join(os10.homedir(), ".bluma", "coding_memory.json");
|
|
16147
|
+
const legacyPath = path14.join(process.cwd(), ".bluma", "coding_memory.json");
|
|
15977
16148
|
let raw = null;
|
|
15978
16149
|
try {
|
|
15979
|
-
if (
|
|
15980
|
-
raw =
|
|
15981
|
-
} else if (
|
|
15982
|
-
raw =
|
|
16150
|
+
if (fs12.existsSync(globalPath)) {
|
|
16151
|
+
raw = fs12.readFileSync(globalPath, "utf-8");
|
|
16152
|
+
} else if (path14.resolve(globalPath) !== path14.resolve(legacyPath) && fs12.existsSync(legacyPath)) {
|
|
16153
|
+
raw = fs12.readFileSync(legacyPath, "utf-8");
|
|
15983
16154
|
}
|
|
15984
16155
|
} catch {
|
|
15985
16156
|
return "";
|
|
@@ -16017,10 +16188,10 @@ var memoryStore = [];
|
|
|
16017
16188
|
var nextId = 1;
|
|
16018
16189
|
var loaded = false;
|
|
16019
16190
|
function getMemoryFilePath() {
|
|
16020
|
-
return
|
|
16191
|
+
return path14.join(os10.homedir(), ".bluma", "coding_memory.json");
|
|
16021
16192
|
}
|
|
16022
16193
|
function getLegacyMemoryFilePath() {
|
|
16023
|
-
return
|
|
16194
|
+
return path14.join(process.cwd(), ".bluma", "coding_memory.json");
|
|
16024
16195
|
}
|
|
16025
16196
|
function loadMemoryFromFile() {
|
|
16026
16197
|
if (loaded) return;
|
|
@@ -16030,19 +16201,19 @@ function loadMemoryFromFile() {
|
|
|
16030
16201
|
try {
|
|
16031
16202
|
const filePath = getMemoryFilePath();
|
|
16032
16203
|
const legacy = getLegacyMemoryFilePath();
|
|
16033
|
-
const legacyDistinct =
|
|
16204
|
+
const legacyDistinct = path14.resolve(legacy) !== path14.resolve(filePath);
|
|
16034
16205
|
const readIntoStore = (p) => {
|
|
16035
|
-
const raw =
|
|
16206
|
+
const raw = fs12.readFileSync(p, "utf-8");
|
|
16036
16207
|
const parsed = JSON.parse(raw);
|
|
16037
16208
|
if (Array.isArray(parsed.entries)) {
|
|
16038
16209
|
memoryStore = parsed.entries;
|
|
16039
16210
|
nextId = typeof parsed.nextId === "number" ? parsed.nextId : memoryStore.length + 1;
|
|
16040
16211
|
}
|
|
16041
16212
|
};
|
|
16042
|
-
if (
|
|
16213
|
+
if (fs12.existsSync(filePath)) {
|
|
16043
16214
|
readIntoStore(filePath);
|
|
16044
16215
|
}
|
|
16045
|
-
if (memoryStore.length === 0 && legacyDistinct &&
|
|
16216
|
+
if (memoryStore.length === 0 && legacyDistinct && fs12.existsSync(legacy)) {
|
|
16046
16217
|
readIntoStore(legacy);
|
|
16047
16218
|
if (memoryStore.length > 0) {
|
|
16048
16219
|
saveMemoryToFile();
|
|
@@ -16056,16 +16227,16 @@ function loadMemoryFromFile() {
|
|
|
16056
16227
|
function saveMemoryToFile() {
|
|
16057
16228
|
try {
|
|
16058
16229
|
const filePath = getMemoryFilePath();
|
|
16059
|
-
const dir =
|
|
16060
|
-
if (!
|
|
16061
|
-
|
|
16230
|
+
const dir = path14.dirname(filePath);
|
|
16231
|
+
if (!fs12.existsSync(dir)) {
|
|
16232
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
16062
16233
|
}
|
|
16063
16234
|
const payload = {
|
|
16064
16235
|
entries: memoryStore,
|
|
16065
16236
|
nextId,
|
|
16066
16237
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16067
16238
|
};
|
|
16068
|
-
|
|
16239
|
+
fs12.writeFileSync(filePath, JSON.stringify(payload, null, 2));
|
|
16069
16240
|
} catch {
|
|
16070
16241
|
}
|
|
16071
16242
|
}
|
|
@@ -16567,7 +16738,7 @@ async function cronDelete(args) {
|
|
|
16567
16738
|
|
|
16568
16739
|
// src/app/agent/tools/NotebookEditTool/NotebookEditTool.ts
|
|
16569
16740
|
init_sandbox_policy();
|
|
16570
|
-
import { promises as
|
|
16741
|
+
import { promises as fs13 } from "fs";
|
|
16571
16742
|
function sourceToString(s) {
|
|
16572
16743
|
if (s == null) return "";
|
|
16573
16744
|
return Array.isArray(s) ? s.join("") : s;
|
|
@@ -16579,7 +16750,7 @@ function stringToSource(s) {
|
|
|
16579
16750
|
async function applyNotebookOperation(filepath, op) {
|
|
16580
16751
|
try {
|
|
16581
16752
|
const resolved = resolveWorkspacePath(filepath);
|
|
16582
|
-
const raw = await
|
|
16753
|
+
const raw = await fs13.readFile(resolved, "utf-8");
|
|
16583
16754
|
let doc;
|
|
16584
16755
|
try {
|
|
16585
16756
|
doc = JSON.parse(raw);
|
|
@@ -16620,7 +16791,7 @@ async function applyNotebookOperation(filepath, op) {
|
|
|
16620
16791
|
return JSON.stringify({ success: false, error: `cell_index out of range: ${i}` });
|
|
16621
16792
|
}
|
|
16622
16793
|
doc.cells[i].source = stringToSource(String(op.source ?? ""));
|
|
16623
|
-
await
|
|
16794
|
+
await fs13.writeFile(resolved, JSON.stringify(doc, null, 2), "utf-8");
|
|
16624
16795
|
return JSON.stringify({ success: true, message: `Updated cell ${i}` });
|
|
16625
16796
|
}
|
|
16626
16797
|
if (op.operation === "append_markdown_cell") {
|
|
@@ -16629,7 +16800,7 @@ async function applyNotebookOperation(filepath, op) {
|
|
|
16629
16800
|
source: stringToSource(String(op.source ?? "")),
|
|
16630
16801
|
metadata: {}
|
|
16631
16802
|
});
|
|
16632
|
-
await
|
|
16803
|
+
await fs13.writeFile(resolved, JSON.stringify(doc, null, 2), "utf-8");
|
|
16633
16804
|
return JSON.stringify({ success: true, message: `Appended markdown cell at index ${doc.cells.length - 1}` });
|
|
16634
16805
|
}
|
|
16635
16806
|
if (op.operation === "delete_cell") {
|
|
@@ -16638,7 +16809,7 @@ async function applyNotebookOperation(filepath, op) {
|
|
|
16638
16809
|
return JSON.stringify({ success: false, error: `cell_index out of range: ${i}` });
|
|
16639
16810
|
}
|
|
16640
16811
|
doc.cells.splice(i, 1);
|
|
16641
|
-
await
|
|
16812
|
+
await fs13.writeFile(resolved, JSON.stringify(doc, null, 2), "utf-8");
|
|
16642
16813
|
return JSON.stringify({ success: true, message: `Deleted cell ${i}` });
|
|
16643
16814
|
}
|
|
16644
16815
|
return JSON.stringify({ success: false, error: "unknown operation" });
|
|
@@ -16677,8 +16848,8 @@ async function notebook_edit(args) {
|
|
|
16677
16848
|
// src/app/agent/tools/LspQueryTool/LspQueryTool.ts
|
|
16678
16849
|
init_sandbox_policy();
|
|
16679
16850
|
import { spawn as spawn2 } from "child_process";
|
|
16680
|
-
import * as
|
|
16681
|
-
import * as
|
|
16851
|
+
import * as fs14 from "fs";
|
|
16852
|
+
import * as path15 from "path";
|
|
16682
16853
|
import { pathToFileURL } from "url";
|
|
16683
16854
|
var RpcBuffer = class {
|
|
16684
16855
|
buf = Buffer.alloc(0);
|
|
@@ -16728,12 +16899,12 @@ async function lsp_query(args) {
|
|
|
16728
16899
|
} catch (e) {
|
|
16729
16900
|
return JSON.stringify({ success: false, error: e.message || String(e) });
|
|
16730
16901
|
}
|
|
16731
|
-
if (!
|
|
16902
|
+
if (!fs14.existsSync(resolved) || !fs14.statSync(resolved).isFile()) {
|
|
16732
16903
|
return JSON.stringify({ success: false, error: "file not found" });
|
|
16733
16904
|
}
|
|
16734
|
-
const content =
|
|
16905
|
+
const content = fs14.readFileSync(resolved, "utf-8");
|
|
16735
16906
|
const uri = pathToFileURL(resolved).href;
|
|
16736
|
-
const root =
|
|
16907
|
+
const root = path15.dirname(resolved);
|
|
16737
16908
|
const line0 = Math.max(0, Math.floor(Number(args.line) || 1) - 1);
|
|
16738
16909
|
const character = Math.max(0, Math.floor(Number(args.character ?? 0)));
|
|
16739
16910
|
const operation = args.operation === "references" ? "references" : "definition";
|
|
@@ -16853,8 +17024,8 @@ async function lsp_query(args) {
|
|
|
16853
17024
|
|
|
16854
17025
|
// src/app/agent/tools/FileWriteTool/FileWriteTool.ts
|
|
16855
17026
|
init_sandbox_policy();
|
|
16856
|
-
import { promises as
|
|
16857
|
-
import
|
|
17027
|
+
import { promises as fs15 } from "fs";
|
|
17028
|
+
import path16 from "path";
|
|
16858
17029
|
async function fileWrite(args) {
|
|
16859
17030
|
const {
|
|
16860
17031
|
filepath,
|
|
@@ -16872,10 +17043,10 @@ async function fileWrite(args) {
|
|
|
16872
17043
|
return { success: false, error: "content is required" };
|
|
16873
17044
|
}
|
|
16874
17045
|
const resolvedPath = resolveWorkspacePath(targetPath);
|
|
16875
|
-
const dir =
|
|
17046
|
+
const dir = path16.dirname(resolvedPath);
|
|
16876
17047
|
let existed = false;
|
|
16877
17048
|
try {
|
|
16878
|
-
const st = await
|
|
17049
|
+
const st = await fs15.stat(resolvedPath);
|
|
16879
17050
|
existed = st.isFile();
|
|
16880
17051
|
} catch {
|
|
16881
17052
|
existed = false;
|
|
@@ -16889,15 +17060,15 @@ async function fileWrite(args) {
|
|
|
16889
17060
|
let previousContent = "";
|
|
16890
17061
|
if (existed) {
|
|
16891
17062
|
try {
|
|
16892
|
-
previousContent = await
|
|
17063
|
+
previousContent = await fs15.readFile(resolvedPath, "utf-8");
|
|
16893
17064
|
} catch {
|
|
16894
17065
|
previousContent = "";
|
|
16895
17066
|
}
|
|
16896
17067
|
}
|
|
16897
17068
|
if (create_directories) {
|
|
16898
|
-
await
|
|
17069
|
+
await fs15.mkdir(dir, { recursive: true });
|
|
16899
17070
|
}
|
|
16900
|
-
await
|
|
17071
|
+
await fs15.writeFile(resolvedPath, String(content), "utf-8");
|
|
16901
17072
|
const bytes = Buffer.byteLength(String(content), "utf-8");
|
|
16902
17073
|
return {
|
|
16903
17074
|
success: true,
|
|
@@ -17206,9 +17377,9 @@ async function signalMailbox(args) {
|
|
|
17206
17377
|
}
|
|
17207
17378
|
|
|
17208
17379
|
// src/app/agent/tools/ReplTool/ReplTool.ts
|
|
17209
|
-
import
|
|
17380
|
+
import fs20 from "fs";
|
|
17210
17381
|
import os15 from "os";
|
|
17211
|
-
import
|
|
17382
|
+
import path21 from "path";
|
|
17212
17383
|
import { exec } from "child_process";
|
|
17213
17384
|
import { v4 as uuidv47 } from "uuid";
|
|
17214
17385
|
import { promisify } from "util";
|
|
@@ -17220,20 +17391,20 @@ async function repl(params) {
|
|
|
17220
17391
|
let tempFile;
|
|
17221
17392
|
switch (language) {
|
|
17222
17393
|
case "python": {
|
|
17223
|
-
tempFile =
|
|
17224
|
-
|
|
17394
|
+
tempFile = path21.join(os15.tmpdir(), `bluma_repl_${uuidv47()}.py`);
|
|
17395
|
+
fs20.writeFileSync(tempFile, code, "utf-8");
|
|
17225
17396
|
command = `python3 "${tempFile}"`;
|
|
17226
17397
|
break;
|
|
17227
17398
|
}
|
|
17228
17399
|
case "node": {
|
|
17229
|
-
tempFile =
|
|
17230
|
-
|
|
17400
|
+
tempFile = path21.join(os15.tmpdir(), `bluma_repl_${uuidv47()}.mjs`);
|
|
17401
|
+
fs20.writeFileSync(tempFile, code, "utf-8");
|
|
17231
17402
|
command = `node "${tempFile}"`;
|
|
17232
17403
|
break;
|
|
17233
17404
|
}
|
|
17234
17405
|
case "bash": {
|
|
17235
|
-
tempFile =
|
|
17236
|
-
|
|
17406
|
+
tempFile = path21.join(os15.tmpdir(), `bluma_repl_${uuidv47()}.sh`);
|
|
17407
|
+
fs20.writeFileSync(tempFile, `#!/bin/bash
|
|
17237
17408
|
set -e
|
|
17238
17409
|
${code}`, "utf-8");
|
|
17239
17410
|
command = `bash "${tempFile}"`;
|
|
@@ -17247,7 +17418,7 @@ ${code}`, "utf-8");
|
|
|
17247
17418
|
// 5MB
|
|
17248
17419
|
});
|
|
17249
17420
|
try {
|
|
17250
|
-
|
|
17421
|
+
fs20.unlinkSync(tempFile);
|
|
17251
17422
|
} catch {
|
|
17252
17423
|
}
|
|
17253
17424
|
return {
|
|
@@ -17259,7 +17430,7 @@ ${code}`, "utf-8");
|
|
|
17259
17430
|
};
|
|
17260
17431
|
} catch (error) {
|
|
17261
17432
|
try {
|
|
17262
|
-
|
|
17433
|
+
fs20.unlinkSync(tempFile);
|
|
17263
17434
|
} catch {
|
|
17264
17435
|
}
|
|
17265
17436
|
const exitCode = error.code === "ERR_CHILD_PROCESS_TIMEOUT" ? 124 : error.status ?? 1;
|
|
@@ -17381,6 +17552,24 @@ function countToolDefinitionsTokens(tools) {
|
|
|
17381
17552
|
const json = JSON.stringify(tools);
|
|
17382
17553
|
return enc.encode(json).length;
|
|
17383
17554
|
}
|
|
17555
|
+
var DEFAULT_OUTPUT_TOKEN_RESERVE = 8192;
|
|
17556
|
+
var DEFAULT_PROTOCOL_OVERHEAD_TOKENS = 512;
|
|
17557
|
+
function computeEffectiveInputBudget(rawBudget, toolDefinitions = [], options) {
|
|
17558
|
+
const toolDefinitionsTokens = countToolDefinitionsTokens(toolDefinitions);
|
|
17559
|
+
const outputReserveTokens = options?.outputReserveTokens ?? DEFAULT_OUTPUT_TOKEN_RESERVE;
|
|
17560
|
+
const protocolOverheadTokens = options?.protocolOverheadTokens ?? DEFAULT_PROTOCOL_OVERHEAD_TOKENS;
|
|
17561
|
+
const effectiveBudget = Math.max(
|
|
17562
|
+
0,
|
|
17563
|
+
rawBudget - toolDefinitionsTokens - outputReserveTokens - protocolOverheadTokens
|
|
17564
|
+
);
|
|
17565
|
+
return {
|
|
17566
|
+
rawBudget,
|
|
17567
|
+
toolDefinitionsTokens,
|
|
17568
|
+
outputReserveTokens,
|
|
17569
|
+
protocolOverheadTokens,
|
|
17570
|
+
effectiveBudget
|
|
17571
|
+
};
|
|
17572
|
+
}
|
|
17384
17573
|
|
|
17385
17574
|
// src/app/agent/tools/CtxInspectTool/CtxInspectTool.ts
|
|
17386
17575
|
async function ctx_inspect(args = {}) {
|
|
@@ -17607,8 +17796,8 @@ ${preview}${toRemove.length > 5 ? `
|
|
|
17607
17796
|
|
|
17608
17797
|
// src/app/agent/tools/BriefTool/BriefTool.ts
|
|
17609
17798
|
init_sandbox_policy();
|
|
17610
|
-
import * as
|
|
17611
|
-
import * as
|
|
17799
|
+
import * as fs21 from "fs";
|
|
17800
|
+
import * as path22 from "path";
|
|
17612
17801
|
async function brief(args) {
|
|
17613
17802
|
const sentAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17614
17803
|
if (!args.message || !args.message.trim()) {
|
|
@@ -17627,9 +17816,9 @@ async function brief(args) {
|
|
|
17627
17816
|
const resolved = [];
|
|
17628
17817
|
for (const filePath of args.attachments) {
|
|
17629
17818
|
try {
|
|
17630
|
-
const absolutePath =
|
|
17631
|
-
if (
|
|
17632
|
-
const stat =
|
|
17819
|
+
const absolutePath = path22.isAbsolute(filePath) ? filePath : path22.join(resolveWorkspacePath("."), filePath);
|
|
17820
|
+
if (fs21.existsSync(absolutePath)) {
|
|
17821
|
+
const stat = fs21.statSync(absolutePath);
|
|
17633
17822
|
resolved.push({
|
|
17634
17823
|
path: absolutePath,
|
|
17635
17824
|
size: stat.size,
|
|
@@ -17668,14 +17857,14 @@ async function brief(args) {
|
|
|
17668
17857
|
}
|
|
17669
17858
|
|
|
17670
17859
|
// src/app/agent/tools/DreamEngineTool/DreamEngineTool.ts
|
|
17671
|
-
import * as
|
|
17672
|
-
import * as
|
|
17860
|
+
import * as fs22 from "fs";
|
|
17861
|
+
import * as path23 from "path";
|
|
17673
17862
|
import os16 from "os";
|
|
17674
17863
|
function memoryPath() {
|
|
17675
|
-
return
|
|
17864
|
+
return path23.join(process.env.HOME || os16.homedir(), ".bluma", "coding_memory.json");
|
|
17676
17865
|
}
|
|
17677
17866
|
function sessionsDir() {
|
|
17678
|
-
return
|
|
17867
|
+
return path23.join(process.env.HOME || os16.homedir(), ".bluma", "sessions");
|
|
17679
17868
|
}
|
|
17680
17869
|
function normalizeNote(note) {
|
|
17681
17870
|
return note.trim().toLowerCase().replace(/\s+/g, " ");
|
|
@@ -17700,9 +17889,9 @@ async function dream(args = {}) {
|
|
|
17700
17889
|
const mergeSimilar = args.mergeSimilar !== false;
|
|
17701
17890
|
const memPath = memoryPath();
|
|
17702
17891
|
let memData = null;
|
|
17703
|
-
if (
|
|
17892
|
+
if (fs22.existsSync(memPath)) {
|
|
17704
17893
|
try {
|
|
17705
|
-
memData = JSON.parse(
|
|
17894
|
+
memData = JSON.parse(fs22.readFileSync(memPath, "utf-8"));
|
|
17706
17895
|
} catch {
|
|
17707
17896
|
}
|
|
17708
17897
|
}
|
|
@@ -17779,12 +17968,12 @@ async function dream(args = {}) {
|
|
|
17779
17968
|
pruned += removed.length;
|
|
17780
17969
|
}
|
|
17781
17970
|
const sessDir = sessionsDir();
|
|
17782
|
-
if (
|
|
17971
|
+
if (fs22.existsSync(sessDir)) {
|
|
17783
17972
|
try {
|
|
17784
|
-
const sessionFiles =
|
|
17973
|
+
const sessionFiles = fs22.readdirSync(sessDir).filter((f) => f.endsWith(".json")).slice(-5);
|
|
17785
17974
|
for (const sf of sessionFiles) {
|
|
17786
17975
|
try {
|
|
17787
|
-
const sessionData = JSON.parse(
|
|
17976
|
+
const sessionData = JSON.parse(fs22.readFileSync(path23.join(sessDir, sf), "utf-8"));
|
|
17788
17977
|
if (sessionData?.summary) {
|
|
17789
17978
|
consolidatedMemories.push(`Session ${sf}: ${sessionData.summary}`);
|
|
17790
17979
|
}
|
|
@@ -17799,7 +17988,7 @@ async function dream(args = {}) {
|
|
|
17799
17988
|
memData.nextId = entries.reduce((max, e) => Math.max(max, e.id || 0), 0) + 1;
|
|
17800
17989
|
memData.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17801
17990
|
try {
|
|
17802
|
-
|
|
17991
|
+
fs22.writeFileSync(memPath, JSON.stringify(memData, null, 2), "utf-8");
|
|
17803
17992
|
} catch (e) {
|
|
17804
17993
|
return {
|
|
17805
17994
|
success: false,
|
|
@@ -17992,8 +18181,8 @@ async function context_collapse(args = {}) {
|
|
|
17992
18181
|
|
|
17993
18182
|
// src/app/agent/tools/CreateNextAppTool/CreateNextAppTool.ts
|
|
17994
18183
|
init_sandbox_policy();
|
|
17995
|
-
import { promises as
|
|
17996
|
-
import
|
|
18184
|
+
import { promises as fs23 } from "fs";
|
|
18185
|
+
import path24 from "path";
|
|
17997
18186
|
|
|
17998
18187
|
// src/app/agent/tools/ShellCommandTool/ShellCommandTool.ts
|
|
17999
18188
|
init_sandbox_policy();
|
|
@@ -18422,6 +18611,40 @@ yarn-error.log*
|
|
|
18422
18611
|
# typescript
|
|
18423
18612
|
*.tsbuildinfo
|
|
18424
18613
|
next-env.d.ts
|
|
18614
|
+
`,
|
|
18615
|
+
// App Router mínimo (obrigatório para next build / deploy no Severino)
|
|
18616
|
+
"app/layout.tsx": `import type { Metadata } from 'next';
|
|
18617
|
+
import './globals.css';
|
|
18618
|
+
|
|
18619
|
+
export const metadata: Metadata = {
|
|
18620
|
+
title: '{{NAME}}',
|
|
18621
|
+
description: 'Generated by create-next-app',
|
|
18622
|
+
};
|
|
18623
|
+
|
|
18624
|
+
export default function RootLayout({
|
|
18625
|
+
children,
|
|
18626
|
+
}: Readonly<{
|
|
18627
|
+
children: React.ReactNode;
|
|
18628
|
+
}>) {
|
|
18629
|
+
return (
|
|
18630
|
+
<html lang="en">
|
|
18631
|
+
<body>{children}</body>
|
|
18632
|
+
</html>
|
|
18633
|
+
);
|
|
18634
|
+
}
|
|
18635
|
+
`,
|
|
18636
|
+
"app/page.tsx": `export default function Home() {
|
|
18637
|
+
return (
|
|
18638
|
+
<main className="flex min-h-screen flex-col items-center justify-center p-8">
|
|
18639
|
+
<h1 className="text-2xl font-semibold">{{NAME}}</h1>
|
|
18640
|
+
<p className="mt-2 text-sm text-gray-600">Next.js app ready for deploy.</p>
|
|
18641
|
+
</main>
|
|
18642
|
+
);
|
|
18643
|
+
}
|
|
18644
|
+
`,
|
|
18645
|
+
"app/globals.css": `@tailwind base;
|
|
18646
|
+
@tailwind components;
|
|
18647
|
+
@tailwind utilities;
|
|
18425
18648
|
`
|
|
18426
18649
|
};
|
|
18427
18650
|
var FULL_FILES = {
|
|
@@ -18566,25 +18789,22 @@ body {
|
|
|
18566
18789
|
}
|
|
18567
18790
|
}
|
|
18568
18791
|
`,
|
|
18569
|
-
// shadcn/ui base components
|
|
18792
|
+
// shadcn/ui base components (sem @radix-ui/react-slot — evita falha de build no deploy)
|
|
18570
18793
|
"components/ui/button.tsx": `import * as React from "react"
|
|
18571
18794
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
18572
18795
|
import { cn } from "@/lib/utils"
|
|
18573
18796
|
|
|
18574
18797
|
const buttonVariants = cva(
|
|
18575
|
-
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium
|
|
18798
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
|
18576
18799
|
{
|
|
18577
18800
|
variants: {
|
|
18578
18801
|
variant: {
|
|
18579
|
-
default: "bg-
|
|
18580
|
-
destructive:
|
|
18581
|
-
|
|
18582
|
-
|
|
18583
|
-
|
|
18584
|
-
|
|
18585
|
-
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
18586
|
-
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
18587
|
-
link: "text-primary underline-offset-4 hover:underline",
|
|
18802
|
+
default: "bg-zinc-900 text-white hover:bg-zinc-800",
|
|
18803
|
+
destructive: "bg-red-600 text-white hover:bg-red-700",
|
|
18804
|
+
outline: "border border-zinc-200 bg-white hover:bg-zinc-50",
|
|
18805
|
+
secondary: "bg-zinc-100 text-zinc-900 hover:bg-zinc-200",
|
|
18806
|
+
ghost: "hover:bg-zinc-100",
|
|
18807
|
+
link: "text-zinc-900 underline-offset-4 hover:underline",
|
|
18588
18808
|
},
|
|
18589
18809
|
size: {
|
|
18590
18810
|
default: "h-10 px-4 py-2",
|
|
@@ -18602,21 +18822,16 @@ const buttonVariants = cva(
|
|
|
18602
18822
|
|
|
18603
18823
|
export interface ButtonProps
|
|
18604
18824
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
18605
|
-
VariantProps<typeof buttonVariants> {
|
|
18606
|
-
asChild?: boolean
|
|
18607
|
-
}
|
|
18825
|
+
VariantProps<typeof buttonVariants> {}
|
|
18608
18826
|
|
|
18609
18827
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
18610
|
-
({ className, variant, size,
|
|
18611
|
-
|
|
18612
|
-
|
|
18613
|
-
|
|
18614
|
-
|
|
18615
|
-
|
|
18616
|
-
|
|
18617
|
-
/>
|
|
18618
|
-
)
|
|
18619
|
-
}
|
|
18828
|
+
({ className, variant, size, ...props }, ref) => (
|
|
18829
|
+
<button
|
|
18830
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
18831
|
+
ref={ref}
|
|
18832
|
+
{...props}
|
|
18833
|
+
/>
|
|
18834
|
+
)
|
|
18620
18835
|
)
|
|
18621
18836
|
Button.displayName = "Button"
|
|
18622
18837
|
|
|
@@ -18632,7 +18847,7 @@ const Card = React.forwardRef<
|
|
|
18632
18847
|
<div
|
|
18633
18848
|
ref={ref}
|
|
18634
18849
|
className={cn(
|
|
18635
|
-
"rounded-lg border bg-
|
|
18850
|
+
"rounded-lg border border-zinc-200 bg-white text-zinc-950 shadow-sm",
|
|
18636
18851
|
className
|
|
18637
18852
|
)}
|
|
18638
18853
|
{...props}
|
|
@@ -18673,7 +18888,7 @@ const CardDescription = React.forwardRef<
|
|
|
18673
18888
|
>(({ className, ...props }, ref) => (
|
|
18674
18889
|
<p
|
|
18675
18890
|
ref={ref}
|
|
18676
|
-
className={cn("text-sm text-
|
|
18891
|
+
className={cn("text-sm text-zinc-500", className)}
|
|
18677
18892
|
{...props}
|
|
18678
18893
|
/>
|
|
18679
18894
|
))
|
|
@@ -18713,7 +18928,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
18713
18928
|
<input
|
|
18714
18929
|
type={type}
|
|
18715
18930
|
className={cn(
|
|
18716
|
-
"flex h-10 w-full rounded-md border border-
|
|
18931
|
+
"flex h-10 w-full rounded-md border border-zinc-200 bg-white px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-zinc-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-400 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
|
18717
18932
|
className
|
|
18718
18933
|
)}
|
|
18719
18934
|
ref={ref}
|
|
@@ -18767,10 +18982,10 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
18767
18982
|
`
|
|
18768
18983
|
};
|
|
18769
18984
|
async function createFile(filePath, content, projectName) {
|
|
18770
|
-
const dir =
|
|
18771
|
-
await
|
|
18985
|
+
const dir = path24.dirname(filePath);
|
|
18986
|
+
await fs23.mkdir(dir, { recursive: true });
|
|
18772
18987
|
const replacedContent = content.replace(/{{NAME}}/g, projectName);
|
|
18773
|
-
await
|
|
18988
|
+
await fs23.writeFile(filePath, replacedContent, "utf-8");
|
|
18774
18989
|
}
|
|
18775
18990
|
async function createNextApp(args) {
|
|
18776
18991
|
const {
|
|
@@ -18789,26 +19004,26 @@ async function createNextApp(args) {
|
|
|
18789
19004
|
};
|
|
18790
19005
|
}
|
|
18791
19006
|
const baseDir = directory ? resolveWorkspacePath(directory) : resolveWorkspacePath(".");
|
|
18792
|
-
const projectPath =
|
|
19007
|
+
const projectPath = path24.join(baseDir, name);
|
|
18793
19008
|
try {
|
|
18794
|
-
await
|
|
19009
|
+
await fs23.access(projectPath);
|
|
18795
19010
|
return {
|
|
18796
19011
|
success: false,
|
|
18797
19012
|
error: `Directory already exists: ${projectPath}. Remove it first or choose a different name.`
|
|
18798
19013
|
};
|
|
18799
19014
|
} catch {
|
|
18800
19015
|
}
|
|
18801
|
-
await
|
|
19016
|
+
await fs23.mkdir(projectPath, { recursive: true });
|
|
18802
19017
|
const filesToCreate = template === "minimal" ? MINIMAL_FILES : FULL_FILES;
|
|
18803
19018
|
const filesCreated = [];
|
|
18804
19019
|
for (const [relativePath, content] of Object.entries(filesToCreate)) {
|
|
18805
19020
|
if (relativePath === "package.json.full") continue;
|
|
18806
|
-
const fullPath =
|
|
19021
|
+
const fullPath = path24.join(projectPath, relativePath);
|
|
18807
19022
|
await createFile(fullPath, content, name);
|
|
18808
19023
|
filesCreated.push(relativePath);
|
|
18809
19024
|
}
|
|
18810
19025
|
if (template === "full") {
|
|
18811
|
-
const packageJsonPath =
|
|
19026
|
+
const packageJsonPath = path24.join(projectPath, "package.json");
|
|
18812
19027
|
const fullPackageJson = FULL_FILES["package.json.full"];
|
|
18813
19028
|
if (fullPackageJson) {
|
|
18814
19029
|
await createFile(packageJsonPath, fullPackageJson, name);
|
|
@@ -18850,9 +19065,14 @@ async function createNextApp(args) {
|
|
|
18850
19065
|
|
|
18851
19066
|
// src/app/agent/tools/DeployAppTool/DeployAppTool.ts
|
|
18852
19067
|
init_sandbox_policy();
|
|
18853
|
-
import { promises as
|
|
18854
|
-
import
|
|
18855
|
-
|
|
19068
|
+
import { promises as fs24 } from "fs";
|
|
19069
|
+
import path26 from "path";
|
|
19070
|
+
|
|
19071
|
+
// src/app/agent/tools/DeployAppTool/createDeployProjectZip.ts
|
|
19072
|
+
import { lstat, readdir, readFile, writeFile } from "fs/promises";
|
|
19073
|
+
import path25 from "path";
|
|
19074
|
+
import { minimatch as minimatch2 } from "minimatch";
|
|
19075
|
+
var DEPLOY_ZIP_EXCLUDE_PATTERNS = [
|
|
18856
19076
|
"node_modules",
|
|
18857
19077
|
".next",
|
|
18858
19078
|
".git",
|
|
@@ -18865,58 +19085,116 @@ var EXCLUDE_PATTERNS = [
|
|
|
18865
19085
|
".env*.local",
|
|
18866
19086
|
"coverage",
|
|
18867
19087
|
".vercel",
|
|
18868
|
-
".turbo"
|
|
19088
|
+
".turbo",
|
|
19089
|
+
".tmp"
|
|
18869
19090
|
];
|
|
18870
|
-
|
|
18871
|
-
|
|
18872
|
-
|
|
18873
|
-
|
|
18874
|
-
|
|
18875
|
-
|
|
18876
|
-
const
|
|
18877
|
-
|
|
18878
|
-
|
|
18879
|
-
|
|
18880
|
-
|
|
18881
|
-
|
|
18882
|
-
|
|
18883
|
-
|
|
18884
|
-
|
|
19091
|
+
function pathSegments(relativePosix) {
|
|
19092
|
+
return relativePosix.split("/").filter(Boolean);
|
|
19093
|
+
}
|
|
19094
|
+
function shouldExcludeFromDeployZip(relativePosix, isDirectory) {
|
|
19095
|
+
const rel = relativePosix.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
19096
|
+
if (!rel) return false;
|
|
19097
|
+
const segments = pathSegments(rel);
|
|
19098
|
+
const baseName = path25.posix.basename(rel);
|
|
19099
|
+
for (const pattern of DEPLOY_ZIP_EXCLUDE_PATTERNS) {
|
|
19100
|
+
if (pattern.includes("*")) {
|
|
19101
|
+
if (minimatch2(rel, pattern) || minimatch2(baseName, pattern)) {
|
|
19102
|
+
return true;
|
|
19103
|
+
}
|
|
19104
|
+
continue;
|
|
19105
|
+
}
|
|
19106
|
+
if (segments.includes(pattern)) {
|
|
19107
|
+
return true;
|
|
19108
|
+
}
|
|
19109
|
+
if (!isDirectory && baseName === pattern) {
|
|
19110
|
+
return true;
|
|
19111
|
+
}
|
|
19112
|
+
}
|
|
19113
|
+
return false;
|
|
19114
|
+
}
|
|
19115
|
+
async function collectDeployZipEntries(projectDir, files) {
|
|
19116
|
+
const base = path25.resolve(projectDir);
|
|
19117
|
+
async function walk(currentDir) {
|
|
19118
|
+
let entries;
|
|
19119
|
+
try {
|
|
19120
|
+
entries = await readdir(currentDir, { withFileTypes: true });
|
|
19121
|
+
} catch {
|
|
19122
|
+
return;
|
|
19123
|
+
}
|
|
19124
|
+
for (const entry of entries) {
|
|
19125
|
+
const fullPath = path25.join(currentDir, entry.name);
|
|
19126
|
+
const rel = path25.relative(base, fullPath).replace(/\\/g, "/");
|
|
19127
|
+
if (shouldExcludeFromDeployZip(rel, entry.isDirectory())) {
|
|
19128
|
+
continue;
|
|
19129
|
+
}
|
|
19130
|
+
if (entry.isSymbolicLink()) {
|
|
19131
|
+
try {
|
|
19132
|
+
const st = await lstat(fullPath);
|
|
19133
|
+
if (st.isDirectory()) continue;
|
|
19134
|
+
} catch {
|
|
19135
|
+
continue;
|
|
19136
|
+
}
|
|
19137
|
+
}
|
|
19138
|
+
if (entry.isDirectory()) {
|
|
19139
|
+
await walk(fullPath);
|
|
19140
|
+
continue;
|
|
19141
|
+
}
|
|
19142
|
+
if (!entry.isFile()) {
|
|
19143
|
+
continue;
|
|
19144
|
+
}
|
|
19145
|
+
try {
|
|
19146
|
+
const content = await readFile(fullPath);
|
|
19147
|
+
files[rel] = new Uint8Array(content);
|
|
19148
|
+
} catch {
|
|
19149
|
+
}
|
|
19150
|
+
}
|
|
19151
|
+
}
|
|
19152
|
+
await walk(base);
|
|
19153
|
+
}
|
|
19154
|
+
async function createDeployProjectZip(projectDir, zipPath) {
|
|
19155
|
+
const files = {};
|
|
19156
|
+
await collectDeployZipEntries(projectDir, files);
|
|
19157
|
+
const fileCount = Object.keys(files).length;
|
|
19158
|
+
if (fileCount === 0) {
|
|
19159
|
+
throw new Error("No files to include in deploy ZIP (check project directory and exclusions)");
|
|
18885
19160
|
}
|
|
19161
|
+
const { zipSync } = await import("fflate");
|
|
19162
|
+
const zipData = zipSync(files, { level: 6 });
|
|
19163
|
+
await writeFile(zipPath, zipData);
|
|
18886
19164
|
return zipPath;
|
|
18887
19165
|
}
|
|
19166
|
+
|
|
19167
|
+
// src/app/agent/tools/DeployAppTool/DeployAppTool.ts
|
|
18888
19168
|
async function uploadToSeverino(zipPath, severinoUrl, name, apiKey, appId) {
|
|
18889
19169
|
const deployUrl = `${severinoUrl.replace(/\/$/, "")}/api/v1/deploy`;
|
|
18890
|
-
const
|
|
18891
|
-
|
|
18892
|
-
|
|
18893
|
-
|
|
18894
|
-
"
|
|
18895
|
-
|
|
18896
|
-
|
|
18897
|
-
if (name)
|
|
18898
|
-
|
|
18899
|
-
}
|
|
18900
|
-
if (appId) {
|
|
18901
|
-
curlArgs.push("-F", `appId=${appId}`);
|
|
18902
|
-
}
|
|
19170
|
+
const zipBytes = await fs24.readFile(zipPath);
|
|
19171
|
+
const form = new FormData();
|
|
19172
|
+
form.append(
|
|
19173
|
+
"file",
|
|
19174
|
+
new Blob([zipBytes], { type: "application/zip" }),
|
|
19175
|
+
path26.basename(zipPath)
|
|
19176
|
+
);
|
|
19177
|
+
if (name) form.append("name", name);
|
|
19178
|
+
if (appId) form.append("appId", appId);
|
|
19179
|
+
const headers = { Accept: "application/json" };
|
|
18903
19180
|
if (apiKey) {
|
|
18904
|
-
|
|
18905
|
-
|
|
18906
|
-
}
|
|
18907
|
-
|
|
18908
|
-
|
|
18909
|
-
|
|
18910
|
-
|
|
18911
|
-
|
|
18912
|
-
// 1 minuto para upload
|
|
19181
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
19182
|
+
headers["X-API-Key"] = apiKey;
|
|
19183
|
+
}
|
|
19184
|
+
const httpResponse = await fetch(deployUrl, {
|
|
19185
|
+
method: "POST",
|
|
19186
|
+
body: form,
|
|
19187
|
+
headers,
|
|
19188
|
+
signal: AbortSignal.timeout(12e4)
|
|
18913
19189
|
});
|
|
18914
|
-
const
|
|
18915
|
-
if (
|
|
18916
|
-
throw new Error(
|
|
19190
|
+
const responseText = await httpResponse.text();
|
|
19191
|
+
if (!httpResponse.ok) {
|
|
19192
|
+
throw new Error(
|
|
19193
|
+
`Upload failed (${httpResponse.status}): ${responseText.slice(0, 500)}`
|
|
19194
|
+
);
|
|
18917
19195
|
}
|
|
18918
19196
|
try {
|
|
18919
|
-
const response = JSON.parse(
|
|
19197
|
+
const response = JSON.parse(responseText);
|
|
18920
19198
|
if (response.success === false || response.error) {
|
|
18921
19199
|
const errorMsg = typeof response.error === "string" ? response.error : response.error?.message || "Deploy failed";
|
|
18922
19200
|
return {
|
|
@@ -18992,8 +19270,10 @@ function buildFactorAiManifest(appContext, deployResult, appName) {
|
|
|
18992
19270
|
mode: "tool-first",
|
|
18993
19271
|
instructions: [
|
|
18994
19272
|
"Read factorai.sh.json as the source of truth before editing files.",
|
|
18995
|
-
"
|
|
18996
|
-
"
|
|
19273
|
+
"Load skill factorai-sh for the full FactorAI.sh workflow.",
|
|
19274
|
+
"For an app already online: use factorai.sh.apply_app_changes with deploy:true (not deploy_app again).",
|
|
19275
|
+
"Poll factorai.sh.get_app_status until building\u2192ready before reporting the live URL.",
|
|
19276
|
+
"Use factorai.sh.redeploy_app only when rebuilding without new file patches."
|
|
18997
19277
|
]
|
|
18998
19278
|
},
|
|
18999
19279
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -19001,8 +19281,8 @@ function buildFactorAiManifest(appContext, deployResult, appName) {
|
|
|
19001
19281
|
}
|
|
19002
19282
|
async function writeFactorAiManifest(projectDir, appContext, deployResult, appName) {
|
|
19003
19283
|
const manifest = buildFactorAiManifest(appContext, deployResult, appName);
|
|
19004
|
-
const manifestPath =
|
|
19005
|
-
await
|
|
19284
|
+
const manifestPath = path26.join(projectDir, "factorai.sh.json");
|
|
19285
|
+
await fs24.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
19006
19286
|
`, "utf-8");
|
|
19007
19287
|
return manifest;
|
|
19008
19288
|
}
|
|
@@ -19022,23 +19302,23 @@ async function deployApp(args) {
|
|
|
19022
19302
|
}
|
|
19023
19303
|
const resolvedProjectDir = resolveWorkspacePath(projectDir);
|
|
19024
19304
|
try {
|
|
19025
|
-
await
|
|
19305
|
+
await fs24.access(resolvedProjectDir);
|
|
19026
19306
|
} catch {
|
|
19027
19307
|
return {
|
|
19028
19308
|
success: false,
|
|
19029
19309
|
error: `Project directory not found: ${resolvedProjectDir}`
|
|
19030
19310
|
};
|
|
19031
19311
|
}
|
|
19032
|
-
const packageJsonPath =
|
|
19312
|
+
const packageJsonPath = path26.join(resolvedProjectDir, "package.json");
|
|
19033
19313
|
try {
|
|
19034
|
-
await
|
|
19314
|
+
await fs24.access(packageJsonPath);
|
|
19035
19315
|
} catch {
|
|
19036
19316
|
return {
|
|
19037
19317
|
success: false,
|
|
19038
19318
|
error: "Not a Next.js project: package.json not found"
|
|
19039
19319
|
};
|
|
19040
19320
|
}
|
|
19041
|
-
const packageJsonContent = await
|
|
19321
|
+
const packageJsonContent = await fs24.readFile(packageJsonPath, "utf-8");
|
|
19042
19322
|
const packageJson = JSON.parse(packageJsonContent);
|
|
19043
19323
|
const hasNext = packageJson.dependencies?.next || packageJson.devDependencies?.next;
|
|
19044
19324
|
if (!hasNext) {
|
|
@@ -19047,18 +19327,18 @@ async function deployApp(args) {
|
|
|
19047
19327
|
error: "Not a Next.js project: next not found in dependencies"
|
|
19048
19328
|
};
|
|
19049
19329
|
}
|
|
19050
|
-
const appName = name || packageJson.name ||
|
|
19051
|
-
const tempDir =
|
|
19052
|
-
await
|
|
19053
|
-
const zipPath =
|
|
19330
|
+
const appName = name || packageJson.name || path26.basename(resolvedProjectDir);
|
|
19331
|
+
const tempDir = path26.join(resolvedProjectDir, ".tmp");
|
|
19332
|
+
await fs24.mkdir(tempDir, { recursive: true });
|
|
19333
|
+
const zipPath = path26.join(tempDir, `${appName}-${Date.now()}.zip`);
|
|
19054
19334
|
console.log(`[deploy-app] Creating ZIP: ${zipPath}`);
|
|
19055
|
-
await
|
|
19056
|
-
const zipStats = await
|
|
19335
|
+
await createDeployProjectZip(resolvedProjectDir, zipPath);
|
|
19336
|
+
const zipStats = await fs24.stat(zipPath);
|
|
19057
19337
|
const zipSizeMB = zipStats.size / 1024 / 1024;
|
|
19058
19338
|
if (zipSizeMB > 50) {
|
|
19059
|
-
await
|
|
19339
|
+
await fs24.unlink(zipPath).catch(() => {
|
|
19060
19340
|
});
|
|
19061
|
-
await
|
|
19341
|
+
await fs24.rmdir(tempDir).catch(() => {
|
|
19062
19342
|
});
|
|
19063
19343
|
return {
|
|
19064
19344
|
success: false,
|
|
@@ -19069,8 +19349,8 @@ async function deployApp(args) {
|
|
|
19069
19349
|
console.log(`[deploy-app] Uploading to ${severinoUrl}...`);
|
|
19070
19350
|
const deployResult = await uploadToSeverino(zipPath, severinoUrl, appName, apiKey, appId);
|
|
19071
19351
|
try {
|
|
19072
|
-
await
|
|
19073
|
-
await
|
|
19352
|
+
await fs24.unlink(zipPath);
|
|
19353
|
+
await fs24.rmdir(tempDir);
|
|
19074
19354
|
} catch (e) {
|
|
19075
19355
|
console.warn("[deploy-app] Cleanup warning:", e);
|
|
19076
19356
|
}
|
|
@@ -19083,7 +19363,7 @@ async function deployApp(args) {
|
|
|
19083
19363
|
appName
|
|
19084
19364
|
);
|
|
19085
19365
|
deployResult.factoraiManifest = manifest;
|
|
19086
|
-
deployResult.factoraiManifestPath =
|
|
19366
|
+
deployResult.factoraiManifestPath = path26.join(resolvedProjectDir, "factorai.sh.json");
|
|
19087
19367
|
}
|
|
19088
19368
|
console.log(`[deploy-app] Deploy iniciado: ${deployResult.appId}`);
|
|
19089
19369
|
}
|
|
@@ -19097,147 +19377,6 @@ async function deployApp(args) {
|
|
|
19097
19377
|
}
|
|
19098
19378
|
}
|
|
19099
19379
|
|
|
19100
|
-
// src/app/agent/runtime/factorai_context.ts
|
|
19101
|
-
import fs24 from "fs";
|
|
19102
|
-
import path25 from "path";
|
|
19103
|
-
function normalizeContext(raw) {
|
|
19104
|
-
if (!raw || typeof raw.appId !== "string" || !raw.appId.trim()) {
|
|
19105
|
-
return null;
|
|
19106
|
-
}
|
|
19107
|
-
const appId = raw.appId.trim();
|
|
19108
|
-
return {
|
|
19109
|
-
appId,
|
|
19110
|
-
tenantId: typeof raw.tenantId === "string" ? raw.tenantId : null,
|
|
19111
|
-
projectId: typeof raw.projectId === "string" ? raw.projectId : null,
|
|
19112
|
-
workspaceId: typeof raw.workspaceId === "string" ? raw.workspaceId : null,
|
|
19113
|
-
revisionId: typeof raw.revisionId === "string" ? raw.revisionId : null,
|
|
19114
|
-
deploymentId: typeof raw.deploymentId === "string" ? raw.deploymentId : null,
|
|
19115
|
-
buildJobId: typeof raw.buildJobId === "string" ? raw.buildJobId : null,
|
|
19116
|
-
manifestPath: typeof raw.manifestPath === "string" ? raw.manifestPath : null,
|
|
19117
|
-
manifestFile: typeof raw.manifestFile === "string" ? raw.manifestFile : null,
|
|
19118
|
-
appUrl: typeof raw.appUrl === "string" ? raw.appUrl : null
|
|
19119
|
-
};
|
|
19120
|
-
}
|
|
19121
|
-
function readJsonFile(filePath) {
|
|
19122
|
-
try {
|
|
19123
|
-
if (!fs24.existsSync(filePath)) {
|
|
19124
|
-
return null;
|
|
19125
|
-
}
|
|
19126
|
-
const parsed = JSON.parse(fs24.readFileSync(filePath, "utf8"));
|
|
19127
|
-
if (parsed && typeof parsed === "object") {
|
|
19128
|
-
return parsed.appContext && typeof parsed.appContext === "object" ? parsed.appContext : parsed;
|
|
19129
|
-
}
|
|
19130
|
-
} catch {
|
|
19131
|
-
return null;
|
|
19132
|
-
}
|
|
19133
|
-
return null;
|
|
19134
|
-
}
|
|
19135
|
-
function readFactorAiWorkspaceManifest(projectDir = process.cwd()) {
|
|
19136
|
-
const manifestPath = path25.join(projectDir, "factorai.sh.json");
|
|
19137
|
-
try {
|
|
19138
|
-
if (!fs24.existsSync(manifestPath)) {
|
|
19139
|
-
return null;
|
|
19140
|
-
}
|
|
19141
|
-
const parsed = JSON.parse(fs24.readFileSync(manifestPath, "utf8"));
|
|
19142
|
-
return parsed && typeof parsed === "object" ? parsed : null;
|
|
19143
|
-
} catch {
|
|
19144
|
-
return null;
|
|
19145
|
-
}
|
|
19146
|
-
}
|
|
19147
|
-
function readContextFromWorkspace() {
|
|
19148
|
-
const candidate = path25.join(process.cwd(), "factorai.sh.json");
|
|
19149
|
-
const parsed = readJsonFile(candidate);
|
|
19150
|
-
if (!parsed) {
|
|
19151
|
-
return null;
|
|
19152
|
-
}
|
|
19153
|
-
const context = normalizeContext(parsed);
|
|
19154
|
-
return context;
|
|
19155
|
-
}
|
|
19156
|
-
function loadFactorAiAppContext() {
|
|
19157
|
-
return readContextFromWorkspace();
|
|
19158
|
-
}
|
|
19159
|
-
function formatFactorAiAppContextSummary(context) {
|
|
19160
|
-
if (!context) {
|
|
19161
|
-
return "";
|
|
19162
|
-
}
|
|
19163
|
-
const parts = [
|
|
19164
|
-
`appId=${context.appId}`,
|
|
19165
|
-
context.tenantId ? `tenantId=${context.tenantId}` : null,
|
|
19166
|
-
context.projectId ? `projectId=${context.projectId}` : null,
|
|
19167
|
-
context.workspaceId ? `workspaceId=${context.workspaceId}` : null,
|
|
19168
|
-
context.revisionId ? `revisionId=${context.revisionId}` : null,
|
|
19169
|
-
context.deploymentId ? `deploymentId=${context.deploymentId}` : null,
|
|
19170
|
-
context.buildJobId ? `buildJobId=${context.buildJobId}` : null,
|
|
19171
|
-
context.appUrl ? `appUrl=${context.appUrl}` : null
|
|
19172
|
-
].filter(Boolean);
|
|
19173
|
-
return parts.join(" | ");
|
|
19174
|
-
}
|
|
19175
|
-
function buildFactorAiWorkspaceManifest(input) {
|
|
19176
|
-
const projectDir = input.projectDir || process.cwd();
|
|
19177
|
-
const existing = readFactorAiWorkspaceManifest(projectDir) || {};
|
|
19178
|
-
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
19179
|
-
const nextAppContext = {
|
|
19180
|
-
...typeof existing.appContext === "object" && existing.appContext ? existing.appContext : {},
|
|
19181
|
-
appId: input.appContext.appId,
|
|
19182
|
-
tenantId: input.appContext.tenantId ?? null,
|
|
19183
|
-
projectId: input.appContext.projectId ?? null,
|
|
19184
|
-
workspaceId: input.appContext.workspaceId ?? null,
|
|
19185
|
-
revisionId: input.appContext.revisionId ?? null,
|
|
19186
|
-
deploymentId: input.appContext.deploymentId ?? null,
|
|
19187
|
-
buildJobId: input.appContext.buildJobId ?? null,
|
|
19188
|
-
manifestPath: "factorai.sh.json",
|
|
19189
|
-
manifestFile: "factorai.sh.json",
|
|
19190
|
-
appUrl: input.appContext.appUrl ?? null
|
|
19191
|
-
};
|
|
19192
|
-
return {
|
|
19193
|
-
...existing,
|
|
19194
|
-
version: 1,
|
|
19195
|
-
manifestFile: "factorai.sh.json",
|
|
19196
|
-
manifestPath: "factorai.sh.json",
|
|
19197
|
-
appContext: nextAppContext,
|
|
19198
|
-
app: {
|
|
19199
|
-
...typeof existing.app === "object" && existing.app ? existing.app : {},
|
|
19200
|
-
name: input.name ?? (typeof existing.app === "object" && existing.app ? existing.app.name : void 0) ?? null,
|
|
19201
|
-
status: input.status ?? (typeof existing.app === "object" && existing.app ? existing.app.status : void 0) ?? "building",
|
|
19202
|
-
isRedeploy: input.isRedeploy ?? (typeof existing.app === "object" && existing.app ? existing.app.isRedeploy : false),
|
|
19203
|
-
message: input.message ?? (typeof existing.app === "object" && existing.app ? existing.app.message : null),
|
|
19204
|
-
...input.app || {}
|
|
19205
|
-
},
|
|
19206
|
-
agent: {
|
|
19207
|
-
provider: "bluma",
|
|
19208
|
-
mode: "tool-first",
|
|
19209
|
-
instructions: [
|
|
19210
|
-
"Read factorai.sh.json as the source of truth before editing files.",
|
|
19211
|
-
"Prefer incremental changes and preserve the existing deployment context.",
|
|
19212
|
-
"After edits, use the redeploy tool instead of rebuilding from scratch."
|
|
19213
|
-
],
|
|
19214
|
-
...typeof existing.agent === "object" && existing.agent ? existing.agent : {},
|
|
19215
|
-
...input.agent || {}
|
|
19216
|
-
},
|
|
19217
|
-
sandbox: {
|
|
19218
|
-
...typeof existing.sandbox === "object" && existing.sandbox ? existing.sandbox : {},
|
|
19219
|
-
...input.sandbox || {}
|
|
19220
|
-
},
|
|
19221
|
-
source: {
|
|
19222
|
-
root: ".",
|
|
19223
|
-
publicDir: "./public",
|
|
19224
|
-
appDir: ".",
|
|
19225
|
-
...typeof existing.source === "object" && existing.source ? existing.source : {},
|
|
19226
|
-
...input.source || {}
|
|
19227
|
-
},
|
|
19228
|
-
updatedAt: now2,
|
|
19229
|
-
...input.extra || {}
|
|
19230
|
-
};
|
|
19231
|
-
}
|
|
19232
|
-
async function writeFactorAiWorkspaceManifest(input) {
|
|
19233
|
-
const projectDir = input.projectDir || process.cwd();
|
|
19234
|
-
const manifest = buildFactorAiWorkspaceManifest({ ...input, projectDir });
|
|
19235
|
-
const manifestPath = path25.join(projectDir, "factorai.sh.json");
|
|
19236
|
-
fs24.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
19237
|
-
`, "utf8");
|
|
19238
|
-
return { manifestPath, manifest };
|
|
19239
|
-
}
|
|
19240
|
-
|
|
19241
19380
|
// src/app/agent/runtime/native_tool_catalog.ts
|
|
19242
19381
|
init_sandbox_policy();
|
|
19243
19382
|
function getFactorAiBaseUrl() {
|
|
@@ -19389,7 +19528,7 @@ function getFactorAiSandboxToolDefinitions() {
|
|
|
19389
19528
|
type: "function",
|
|
19390
19529
|
function: {
|
|
19391
19530
|
name: "factorai.sh.deploy_app",
|
|
19392
|
-
description: "
|
|
19531
|
+
description: "First deploy only: ZIP project source (excludes node_modules/.next) to SEVERINO_URL /api/v1/deploy. Writes factorai.sh.json. For later edits use apply_app_changes instead.",
|
|
19393
19532
|
parameters: {
|
|
19394
19533
|
type: "object",
|
|
19395
19534
|
properties: {
|
|
@@ -19407,7 +19546,7 @@ function getFactorAiSandboxToolDefinitions() {
|
|
|
19407
19546
|
type: "function",
|
|
19408
19547
|
function: {
|
|
19409
19548
|
name: "factorai.sh.get_app_status",
|
|
19410
|
-
description: "
|
|
19549
|
+
description: "Poll deploy/rebuild status (building|ready|failed). Call after deploy_app or apply_app_changes until ready before telling the user the app is live.",
|
|
19411
19550
|
parameters: {
|
|
19412
19551
|
type: "object",
|
|
19413
19552
|
properties: {
|
|
@@ -19422,7 +19561,7 @@ function getFactorAiSandboxToolDefinitions() {
|
|
|
19422
19561
|
type: "function",
|
|
19423
19562
|
function: {
|
|
19424
19563
|
name: "factorai.sh.apply_app_changes",
|
|
19425
|
-
description: "
|
|
19564
|
+
description: "Patch files on an already-deployed FactorAI app in THIS session only (read appId from factorai.sh.json in cwd). Each files[].content must be the COMPLETE file body (server replaces the whole file \u2014 never send a JSX/footer fragment). After edit_tool, read_file_lines the full file before calling. deploy:true (default) runs next build + restart. Prefer over deploy_app for live edits.",
|
|
19426
19565
|
parameters: {
|
|
19427
19566
|
type: "object",
|
|
19428
19567
|
properties: {
|
|
@@ -20086,7 +20225,7 @@ var NATIVE_TOOL_ENTRIES = [
|
|
|
20086
20225
|
autoApproveInLocal: false,
|
|
20087
20226
|
autoApproveInSandbox: true,
|
|
20088
20227
|
sandboxOnly: true,
|
|
20089
|
-
description: "
|
|
20228
|
+
description: "This session only: appId from factorai.sh.json in cwd. Each files[].content = COMPLETE file (server replaces whole file). Read full file after edit_tool. deploy defaults true. Poll get_app_status until ready."
|
|
20090
20229
|
},
|
|
20091
20230
|
implementation: factorAiApplyAppChanges
|
|
20092
20231
|
},
|
|
@@ -20145,7 +20284,7 @@ var SANDBOX_COMMUNICATION_TOOL_DEFINITIONS = [
|
|
|
20145
20284
|
type: "function",
|
|
20146
20285
|
function: {
|
|
20147
20286
|
name: "message",
|
|
20148
|
-
description: 'Deliver user-visible output. message_type "info": progress (does not end turn). message_type "result": ends turn; file deliverables
|
|
20287
|
+
description: 'Deliver user-visible output. message_type "info": progress (does not end turn). message_type "result": ends turn; file deliverables use attachments[] under .bluma/artifacts/; live FactorAI.sh app URL MUST use factor-sh-url-app (auto-filled from factorai.sh.json when omitted).',
|
|
20149
20288
|
parameters: {
|
|
20150
20289
|
type: "object",
|
|
20151
20290
|
properties: {
|
|
@@ -20159,6 +20298,10 @@ var SANDBOX_COMMUNICATION_TOOL_DEFINITIONS = [
|
|
|
20159
20298
|
type: "array",
|
|
20160
20299
|
items: { type: "string" },
|
|
20161
20300
|
description: "Workspace file paths under .bluma/artifacts/ (required for file deliverables on result)."
|
|
20301
|
+
},
|
|
20302
|
+
"factor-sh-url-app": {
|
|
20303
|
+
type: "string",
|
|
20304
|
+
description: "Required on message_type result after FactorAI.sh deploy/redeploy: absolute live app URL for Severino (from factorai.sh.json appUrl). Auto-filled if omitted."
|
|
20162
20305
|
}
|
|
20163
20306
|
},
|
|
20164
20307
|
required: ["content", "message_type"],
|
|
@@ -20302,8 +20445,8 @@ var ToolInvoker = class {
|
|
|
20302
20445
|
async initialize() {
|
|
20303
20446
|
try {
|
|
20304
20447
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
20305
|
-
const currentDirPath =
|
|
20306
|
-
const configPath =
|
|
20448
|
+
const currentDirPath = path27.dirname(currentFilePath);
|
|
20449
|
+
const configPath = path27.resolve(currentDirPath, "config", "native_tools.json");
|
|
20307
20450
|
const fileContent = await fs25.readFile(configPath, "utf-8");
|
|
20308
20451
|
const config2 = JSON.parse(fileContent);
|
|
20309
20452
|
this.toolDefinitions = applyMetadataToToolDefinitions(config2.nativeTools);
|
|
@@ -20352,7 +20495,7 @@ var ToolInvoker = class {
|
|
|
20352
20495
|
|
|
20353
20496
|
// src/app/agent/tools/mcp/mcp_client.ts
|
|
20354
20497
|
import { promises as fs26 } from "fs";
|
|
20355
|
-
import
|
|
20498
|
+
import path28 from "path";
|
|
20356
20499
|
import os18 from "os";
|
|
20357
20500
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
20358
20501
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -20380,9 +20523,9 @@ var MCPClient = class {
|
|
|
20380
20523
|
});
|
|
20381
20524
|
}
|
|
20382
20525
|
const __filename = fileURLToPath2(import.meta.url);
|
|
20383
|
-
const __dirname2 =
|
|
20384
|
-
const defaultConfigPath =
|
|
20385
|
-
const userConfigPath =
|
|
20526
|
+
const __dirname2 = path28.dirname(__filename);
|
|
20527
|
+
const defaultConfigPath = path28.resolve(__dirname2, "config", "bluma-mcp.json");
|
|
20528
|
+
const userConfigPath = path28.join(os18.homedir(), ".bluma", "bluma-mcp.json");
|
|
20386
20529
|
const defaultConfig = await this.loadMcpConfig(defaultConfigPath, "Default");
|
|
20387
20530
|
const userConfig = await this.loadMcpConfig(userConfigPath, "User");
|
|
20388
20531
|
const mergedConfig = {
|
|
@@ -20628,55 +20771,299 @@ PENALTY APPLIED: ${penalty.toFixed(1)} points deducted.
|
|
|
20628
20771
|
};
|
|
20629
20772
|
|
|
20630
20773
|
// src/app/agent/bluma/core/bluma.ts
|
|
20631
|
-
import
|
|
20632
|
-
import
|
|
20774
|
+
import path44 from "path";
|
|
20775
|
+
import fs40 from "fs";
|
|
20633
20776
|
import { v4 as uuidv48 } from "uuid";
|
|
20634
20777
|
|
|
20635
20778
|
// src/app/agent/session_manager/session_manager.ts
|
|
20636
|
-
import
|
|
20637
|
-
import { promises as
|
|
20779
|
+
import path32 from "path";
|
|
20780
|
+
import { promises as fs29 } from "fs";
|
|
20638
20781
|
|
|
20639
20782
|
// src/app/agent/session_manager/agent_session_paths.ts
|
|
20640
|
-
import
|
|
20641
|
-
import { promises as
|
|
20783
|
+
import path31 from "path";
|
|
20784
|
+
import { promises as fs28 } from "fs";
|
|
20642
20785
|
|
|
20643
20786
|
// src/app/agent/session_manager/bluma_app_dir.ts
|
|
20644
|
-
import
|
|
20787
|
+
import path29 from "path";
|
|
20645
20788
|
import os19 from "os";
|
|
20646
20789
|
function expandHome(p) {
|
|
20647
20790
|
if (!p) return p;
|
|
20648
20791
|
if (p.startsWith("~")) {
|
|
20649
|
-
return
|
|
20792
|
+
return path29.join(os19.homedir(), p.slice(1));
|
|
20650
20793
|
}
|
|
20651
20794
|
return p;
|
|
20652
20795
|
}
|
|
20653
20796
|
function getPreferredAppDir() {
|
|
20654
20797
|
const fromEnv = process.env.BLUMA_HOME?.trim();
|
|
20655
20798
|
if (fromEnv) {
|
|
20656
|
-
return
|
|
20799
|
+
return path29.resolve(expandHome(fromEnv));
|
|
20657
20800
|
}
|
|
20658
|
-
const fixed =
|
|
20659
|
-
return
|
|
20801
|
+
const fixed = path29.join(os19.homedir(), ".bluma");
|
|
20802
|
+
return path29.resolve(expandHome(fixed));
|
|
20660
20803
|
}
|
|
20661
20804
|
|
|
20662
|
-
// src/app/agent/session_manager/
|
|
20805
|
+
// src/app/agent/session_manager/session_index_db.ts
|
|
20806
|
+
import path30 from "path";
|
|
20807
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
20808
|
+
import { promises as fs27 } from "fs";
|
|
20809
|
+
import { createRequire } from "module";
|
|
20663
20810
|
var AGENT_SESSION_PATHS_JSONL = "agent_session_paths.jsonl";
|
|
20811
|
+
var BLUMA_SESSION_DB_FILE = "bluma.sqlite";
|
|
20812
|
+
var SCHEMA_VERSION = 1;
|
|
20813
|
+
var dbInstance = null;
|
|
20814
|
+
var dbAppDir = null;
|
|
20815
|
+
var migratePromise = null;
|
|
20816
|
+
function loadSqlite() {
|
|
20817
|
+
const nodeRequire = createRequire(import.meta.url);
|
|
20818
|
+
return nodeRequire("better-sqlite3");
|
|
20819
|
+
}
|
|
20820
|
+
function getSessionDbPath(appDir = getPreferredAppDir()) {
|
|
20821
|
+
return path30.join(appDir, BLUMA_SESSION_DB_FILE);
|
|
20822
|
+
}
|
|
20823
|
+
function normalizeRelativePath(relativePath) {
|
|
20824
|
+
return relativePath.split(path30.sep).join("/");
|
|
20825
|
+
}
|
|
20826
|
+
async function walkSessionJsonFiles(dir, onFile) {
|
|
20827
|
+
let entries;
|
|
20828
|
+
try {
|
|
20829
|
+
entries = await fs27.readdir(dir, { withFileTypes: true });
|
|
20830
|
+
} catch {
|
|
20831
|
+
return;
|
|
20832
|
+
}
|
|
20833
|
+
for (const e of entries) {
|
|
20834
|
+
const name = String(e.name);
|
|
20835
|
+
const full = path30.join(dir, name);
|
|
20836
|
+
if (e.isDirectory()) {
|
|
20837
|
+
await walkSessionJsonFiles(full, onFile);
|
|
20838
|
+
} else if (e.isFile() && name.endsWith(".json") && !name.includes(".tmp")) {
|
|
20839
|
+
await onFile(full);
|
|
20840
|
+
}
|
|
20841
|
+
}
|
|
20842
|
+
}
|
|
20843
|
+
async function loadJsonlEntries(appDir) {
|
|
20844
|
+
const map = /* @__PURE__ */ new Map();
|
|
20845
|
+
const indexFile = path30.join(appDir, AGENT_SESSION_PATHS_JSONL);
|
|
20846
|
+
let raw;
|
|
20847
|
+
try {
|
|
20848
|
+
raw = await fs27.readFile(indexFile, "utf-8");
|
|
20849
|
+
} catch {
|
|
20850
|
+
return map;
|
|
20851
|
+
}
|
|
20852
|
+
for (const line of raw.split("\n")) {
|
|
20853
|
+
const trimmed = line.trim();
|
|
20854
|
+
if (!trimmed) continue;
|
|
20855
|
+
try {
|
|
20856
|
+
const row = JSON.parse(trimmed);
|
|
20857
|
+
if (row.sessionId && typeof row.relativePath === "string") {
|
|
20858
|
+
map.set(row.sessionId, {
|
|
20859
|
+
relativePath: normalizeRelativePath(row.relativePath),
|
|
20860
|
+
updatedAt: row.updatedAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
|
|
20861
|
+
});
|
|
20862
|
+
}
|
|
20863
|
+
} catch {
|
|
20864
|
+
}
|
|
20865
|
+
}
|
|
20866
|
+
return map;
|
|
20867
|
+
}
|
|
20868
|
+
function previewFromConversationHistory(history) {
|
|
20869
|
+
for (const msg of history ?? []) {
|
|
20870
|
+
if (msg.role !== "user") continue;
|
|
20871
|
+
const c = msg.content;
|
|
20872
|
+
if (typeof c !== "string") continue;
|
|
20873
|
+
const t = c.trim();
|
|
20874
|
+
if (!t || t.startsWith("[SKILL:")) continue;
|
|
20875
|
+
const oneLine = t.replace(/\s+/g, " ");
|
|
20876
|
+
return oneLine.length > 72 ? `${oneLine.slice(0, 72)}\u2026` : oneLine;
|
|
20877
|
+
}
|
|
20878
|
+
return "(no messages)";
|
|
20879
|
+
}
|
|
20880
|
+
async function readSessionMetaFromJson(absPath) {
|
|
20881
|
+
const fallback = {
|
|
20882
|
+
preview: "(no messages)",
|
|
20883
|
+
createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
20884
|
+
updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
20885
|
+
};
|
|
20886
|
+
try {
|
|
20887
|
+
const raw = await fs27.readFile(absPath, "utf-8");
|
|
20888
|
+
const data = JSON.parse(raw);
|
|
20889
|
+
const createdAt = data.created_at ?? fallback.createdAt;
|
|
20890
|
+
const updatedAt = data.last_updated ?? data.created_at ?? fallback.updatedAt;
|
|
20891
|
+
return {
|
|
20892
|
+
preview: previewFromConversationHistory(data.conversation_history),
|
|
20893
|
+
createdAt,
|
|
20894
|
+
updatedAt
|
|
20895
|
+
};
|
|
20896
|
+
} catch {
|
|
20897
|
+
try {
|
|
20898
|
+
const st = await fs27.stat(absPath);
|
|
20899
|
+
const iso = new Date(st.mtimeMs).toISOString();
|
|
20900
|
+
return { preview: fallback.preview, createdAt: iso, updatedAt: iso };
|
|
20901
|
+
} catch {
|
|
20902
|
+
return fallback;
|
|
20903
|
+
}
|
|
20904
|
+
}
|
|
20905
|
+
}
|
|
20906
|
+
async function runMigrationV1(db, appDir) {
|
|
20907
|
+
const jsonl = await loadJsonlEntries(appDir);
|
|
20908
|
+
const upsert = db.prepare(`
|
|
20909
|
+
INSERT INTO agent_sessions (session_id, relative_path, created_at, updated_at, preview)
|
|
20910
|
+
VALUES (@sessionId, @relativePath, @createdAt, @updatedAt, @preview)
|
|
20911
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
20912
|
+
relative_path = excluded.relative_path,
|
|
20913
|
+
updated_at = CASE
|
|
20914
|
+
WHEN excluded.updated_at > agent_sessions.updated_at THEN excluded.updated_at
|
|
20915
|
+
ELSE agent_sessions.updated_at
|
|
20916
|
+
END,
|
|
20917
|
+
preview = CASE
|
|
20918
|
+
WHEN excluded.updated_at > agent_sessions.updated_at THEN excluded.preview
|
|
20919
|
+
ELSE agent_sessions.preview
|
|
20920
|
+
END
|
|
20921
|
+
`);
|
|
20922
|
+
const insertFromPath = async (sessionId, absPath, jsonlUpdatedAt) => {
|
|
20923
|
+
let rel;
|
|
20924
|
+
try {
|
|
20925
|
+
rel = normalizeRelativePath(path30.relative(appDir, absPath));
|
|
20926
|
+
} catch {
|
|
20927
|
+
return;
|
|
20928
|
+
}
|
|
20929
|
+
const meta = await readSessionMetaFromJson(absPath);
|
|
20930
|
+
const updatedAt = jsonlUpdatedAt && Date.parse(jsonlUpdatedAt) > Date.parse(meta.updatedAt) ? jsonlUpdatedAt : meta.updatedAt;
|
|
20931
|
+
upsert.run({
|
|
20932
|
+
sessionId,
|
|
20933
|
+
relativePath: rel,
|
|
20934
|
+
createdAt: meta.createdAt,
|
|
20935
|
+
updatedAt,
|
|
20936
|
+
preview: meta.preview
|
|
20937
|
+
});
|
|
20938
|
+
};
|
|
20939
|
+
for (const [sessionId, entry] of jsonl) {
|
|
20940
|
+
const full = path30.join(appDir, entry.relativePath.replace(/\//g, path30.sep));
|
|
20941
|
+
try {
|
|
20942
|
+
await fs27.access(full);
|
|
20943
|
+
await insertFromPath(sessionId, full, entry.updatedAt);
|
|
20944
|
+
} catch {
|
|
20945
|
+
upsert.run({
|
|
20946
|
+
sessionId,
|
|
20947
|
+
relativePath: entry.relativePath,
|
|
20948
|
+
createdAt: entry.updatedAt,
|
|
20949
|
+
updatedAt: entry.updatedAt,
|
|
20950
|
+
preview: "(no messages)"
|
|
20951
|
+
});
|
|
20952
|
+
}
|
|
20953
|
+
}
|
|
20954
|
+
const sessionsRoot = path30.join(appDir, "sessions");
|
|
20955
|
+
const existing = db.prepare("SELECT session_id FROM agent_sessions").all();
|
|
20956
|
+
const seen = new Set(existing.map((r) => r.session_id));
|
|
20957
|
+
await walkSessionJsonFiles(sessionsRoot, async (absPath) => {
|
|
20958
|
+
const sessionId = path30.basename(absPath, ".json");
|
|
20959
|
+
if (!sessionId || seen.has(sessionId)) return;
|
|
20960
|
+
seen.add(sessionId);
|
|
20961
|
+
await insertFromPath(sessionId, absPath);
|
|
20962
|
+
});
|
|
20963
|
+
db.pragma(`user_version = ${SCHEMA_VERSION}`);
|
|
20964
|
+
}
|
|
20965
|
+
function applySchema(db) {
|
|
20966
|
+
db.exec(`
|
|
20967
|
+
CREATE TABLE IF NOT EXISTS agent_sessions (
|
|
20968
|
+
session_id TEXT PRIMARY KEY NOT NULL,
|
|
20969
|
+
relative_path TEXT NOT NULL,
|
|
20970
|
+
created_at TEXT NOT NULL,
|
|
20971
|
+
updated_at TEXT NOT NULL,
|
|
20972
|
+
preview TEXT NOT NULL DEFAULT '(no messages)'
|
|
20973
|
+
);
|
|
20974
|
+
CREATE INDEX IF NOT EXISTS idx_agent_sessions_updated
|
|
20975
|
+
ON agent_sessions(updated_at DESC);
|
|
20976
|
+
`);
|
|
20977
|
+
}
|
|
20978
|
+
async function ensureSessionIndexDb(appDir = getPreferredAppDir()) {
|
|
20979
|
+
if (dbInstance && dbAppDir === appDir) {
|
|
20980
|
+
return dbInstance;
|
|
20981
|
+
}
|
|
20982
|
+
if (!migratePromise || dbAppDir !== appDir) {
|
|
20983
|
+
migratePromise = (async () => {
|
|
20984
|
+
if (dbInstance) {
|
|
20985
|
+
try {
|
|
20986
|
+
dbInstance.close();
|
|
20987
|
+
} catch {
|
|
20988
|
+
}
|
|
20989
|
+
dbInstance = null;
|
|
20990
|
+
}
|
|
20991
|
+
const BetterSqlite3 = loadSqlite();
|
|
20992
|
+
const dbPath = getSessionDbPath(appDir);
|
|
20993
|
+
mkdirSync3(path30.dirname(dbPath), { recursive: true });
|
|
20994
|
+
const db = new BetterSqlite3(dbPath);
|
|
20995
|
+
db.pragma("journal_mode = WAL");
|
|
20996
|
+
applySchema(db);
|
|
20997
|
+
const version = db.pragma("user_version", { simple: true });
|
|
20998
|
+
if (version < SCHEMA_VERSION) {
|
|
20999
|
+
await runMigrationV1(db, appDir);
|
|
21000
|
+
}
|
|
21001
|
+
dbInstance = db;
|
|
21002
|
+
dbAppDir = appDir;
|
|
21003
|
+
})();
|
|
21004
|
+
}
|
|
21005
|
+
await migratePromise;
|
|
21006
|
+
return dbInstance;
|
|
21007
|
+
}
|
|
21008
|
+
async function upsertSessionIndexRow(row, appDir = getPreferredAppDir()) {
|
|
21009
|
+
const db = await ensureSessionIndexDb(appDir);
|
|
21010
|
+
const stmt = db.prepare(`
|
|
21011
|
+
INSERT INTO agent_sessions (session_id, relative_path, created_at, updated_at, preview)
|
|
21012
|
+
VALUES (@sessionId, @relativePath, @createdAt, @updatedAt, @preview)
|
|
21013
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
21014
|
+
relative_path = excluded.relative_path,
|
|
21015
|
+
updated_at = excluded.updated_at,
|
|
21016
|
+
preview = excluded.preview
|
|
21017
|
+
`);
|
|
21018
|
+
stmt.run({
|
|
21019
|
+
sessionId: row.sessionId,
|
|
21020
|
+
relativePath: normalizeRelativePath(row.relativePath),
|
|
21021
|
+
createdAt: row.createdAt,
|
|
21022
|
+
updatedAt: row.updatedAt,
|
|
21023
|
+
preview: row.preview
|
|
21024
|
+
});
|
|
21025
|
+
}
|
|
21026
|
+
async function getSessionPathFromIndex(sessionId, appDir = getPreferredAppDir()) {
|
|
21027
|
+
const db = await ensureSessionIndexDb(appDir);
|
|
21028
|
+
const row = db.prepare("SELECT relative_path FROM agent_sessions WHERE session_id = ?").get(sessionId);
|
|
21029
|
+
return row?.relative_path?.replace(/\//g, path30.sep) ?? null;
|
|
21030
|
+
}
|
|
21031
|
+
async function listSessionsFromIndex(limit = 50, appDir = getPreferredAppDir()) {
|
|
21032
|
+
const db = await ensureSessionIndexDb(appDir);
|
|
21033
|
+
const rows = db.prepare(
|
|
21034
|
+
`SELECT session_id, relative_path, created_at, updated_at, preview
|
|
21035
|
+
FROM agent_sessions
|
|
21036
|
+
ORDER BY updated_at DESC
|
|
21037
|
+
LIMIT ?`
|
|
21038
|
+
).all(limit);
|
|
21039
|
+
return rows.map((r) => ({
|
|
21040
|
+
sessionId: r.session_id,
|
|
21041
|
+
relativePath: r.relative_path.replace(/\//g, path30.sep),
|
|
21042
|
+
createdAt: r.created_at,
|
|
21043
|
+
updatedAt: r.updated_at,
|
|
21044
|
+
preview: r.preview
|
|
21045
|
+
}));
|
|
21046
|
+
}
|
|
21047
|
+
|
|
21048
|
+
// src/app/agent/session_manager/agent_session_paths.ts
|
|
21049
|
+
var AGENT_SESSION_PATHS_JSONL2 = "agent_session_paths.jsonl";
|
|
20664
21050
|
async function appendAgentSessionPath(sessionId, relativePath) {
|
|
20665
21051
|
const app = getPreferredAppDir();
|
|
20666
|
-
const
|
|
20667
|
-
|
|
20668
|
-
|
|
21052
|
+
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
21053
|
+
const rel = relativePath.split(path31.sep).join("/");
|
|
21054
|
+
await upsertSessionIndexRow({
|
|
20669
21055
|
sessionId,
|
|
20670
|
-
relativePath:
|
|
20671
|
-
|
|
20672
|
-
|
|
20673
|
-
|
|
21056
|
+
relativePath: rel,
|
|
21057
|
+
createdAt: now2,
|
|
21058
|
+
updatedAt: now2,
|
|
21059
|
+
preview: "(no messages)"
|
|
21060
|
+
});
|
|
20674
21061
|
}
|
|
20675
|
-
async function
|
|
20676
|
-
const indexFile =
|
|
21062
|
+
async function resolveSessionRelativePathFromJsonl(sessionId) {
|
|
21063
|
+
const indexFile = path31.join(getPreferredAppDir(), AGENT_SESSION_PATHS_JSONL2);
|
|
20677
21064
|
let raw;
|
|
20678
21065
|
try {
|
|
20679
|
-
raw = await
|
|
21066
|
+
raw = await fs28.readFile(indexFile, "utf-8");
|
|
20680
21067
|
} catch {
|
|
20681
21068
|
return null;
|
|
20682
21069
|
}
|
|
@@ -20685,37 +21072,42 @@ async function resolveSessionRelativePathFromIndex(sessionId) {
|
|
|
20685
21072
|
try {
|
|
20686
21073
|
const row = JSON.parse(lines[i]);
|
|
20687
21074
|
if (row.sessionId === sessionId && typeof row.relativePath === "string") {
|
|
20688
|
-
return row.relativePath.replace(/\//g,
|
|
21075
|
+
return row.relativePath.replace(/\//g, path31.sep);
|
|
20689
21076
|
}
|
|
20690
21077
|
} catch {
|
|
20691
21078
|
}
|
|
20692
21079
|
}
|
|
20693
21080
|
return null;
|
|
20694
21081
|
}
|
|
20695
|
-
async function
|
|
21082
|
+
async function resolveSessionRelativePathFromIndex(sessionId) {
|
|
21083
|
+
const fromDb = await getSessionPathFromIndex(sessionId);
|
|
21084
|
+
if (fromDb) return fromDb;
|
|
21085
|
+
return resolveSessionRelativePathFromJsonl(sessionId);
|
|
21086
|
+
}
|
|
21087
|
+
async function walkSessionJsonFiles2(dir, onFile) {
|
|
20696
21088
|
let entries;
|
|
20697
21089
|
try {
|
|
20698
|
-
entries = await
|
|
21090
|
+
entries = await fs28.readdir(dir, { withFileTypes: true });
|
|
20699
21091
|
} catch {
|
|
20700
21092
|
return;
|
|
20701
21093
|
}
|
|
20702
21094
|
for (const e of entries) {
|
|
20703
21095
|
const name = String(e.name);
|
|
20704
|
-
const full =
|
|
21096
|
+
const full = path31.join(dir, name);
|
|
20705
21097
|
if (e.isDirectory()) {
|
|
20706
|
-
await
|
|
21098
|
+
await walkSessionJsonFiles2(full, onFile);
|
|
20707
21099
|
} else if (e.isFile() && name.endsWith(".json") && !name.includes(".tmp")) {
|
|
20708
21100
|
await onFile(full);
|
|
20709
21101
|
}
|
|
20710
21102
|
}
|
|
20711
21103
|
}
|
|
20712
21104
|
async function findSessionFileGlobFallback(sessionId) {
|
|
20713
|
-
const sessionsRoot =
|
|
21105
|
+
const sessionsRoot = path31.join(getPreferredAppDir(), "sessions");
|
|
20714
21106
|
const state2 = { best: null };
|
|
20715
|
-
await
|
|
20716
|
-
if (
|
|
21107
|
+
await walkSessionJsonFiles2(sessionsRoot, async (abs) => {
|
|
21108
|
+
if (path31.basename(abs, ".json") !== sessionId) return;
|
|
20717
21109
|
try {
|
|
20718
|
-
const st = await
|
|
21110
|
+
const st = await fs28.stat(abs);
|
|
20719
21111
|
if (!st.isFile()) return;
|
|
20720
21112
|
if (!state2.best || st.mtimeMs > state2.best.m) {
|
|
20721
21113
|
state2.best = { p: abs, m: st.mtimeMs };
|
|
@@ -20726,11 +21118,11 @@ async function findSessionFileGlobFallback(sessionId) {
|
|
|
20726
21118
|
return state2.best !== null ? state2.best.p : null;
|
|
20727
21119
|
}
|
|
20728
21120
|
async function loadLatestIndexEntriesBySessionId() {
|
|
20729
|
-
const indexFile =
|
|
21121
|
+
const indexFile = path31.join(getPreferredAppDir(), AGENT_SESSION_PATHS_JSONL2);
|
|
20730
21122
|
const map = /* @__PURE__ */ new Map();
|
|
20731
21123
|
let raw;
|
|
20732
21124
|
try {
|
|
20733
|
-
raw = await
|
|
21125
|
+
raw = await fs28.readFile(indexFile, "utf-8");
|
|
20734
21126
|
} catch {
|
|
20735
21127
|
return map;
|
|
20736
21128
|
}
|
|
@@ -20763,40 +21155,36 @@ function dateFolderFromRelativePath(relativePath) {
|
|
|
20763
21155
|
}
|
|
20764
21156
|
return null;
|
|
20765
21157
|
}
|
|
20766
|
-
function
|
|
20767
|
-
for (const msg of history ?? []) {
|
|
20768
|
-
if (msg.role !== "user") continue;
|
|
20769
|
-
const c = msg.content;
|
|
20770
|
-
if (typeof c !== "string") continue;
|
|
20771
|
-
const t = c.trim();
|
|
20772
|
-
if (!t || t.startsWith("[SKILL:")) continue;
|
|
20773
|
-
const oneLine = t.replace(/\s+/g, " ");
|
|
20774
|
-
return oneLine.length > 72 ? `${oneLine.slice(0, 72)}\u2026` : oneLine;
|
|
20775
|
-
}
|
|
20776
|
-
return "(no messages)";
|
|
20777
|
-
}
|
|
20778
|
-
async function enrichCandidateFromFile(absPath, sessionId, lastMtimeMs) {
|
|
21158
|
+
async function enrichCandidateFromFile(absPath, sessionId, lastMtimeMs, opts) {
|
|
20779
21159
|
let rel;
|
|
20780
21160
|
try {
|
|
20781
|
-
rel =
|
|
21161
|
+
rel = path31.relative(getPreferredAppDir(), absPath);
|
|
20782
21162
|
} catch {
|
|
20783
21163
|
rel = absPath;
|
|
20784
21164
|
}
|
|
20785
|
-
const dateFolder = dateFolderFromRelativePath(rel.split(
|
|
20786
|
-
let preview = "(no messages)";
|
|
21165
|
+
const dateFolder = dateFolderFromRelativePath(rel.split(path31.sep).join("/"));
|
|
21166
|
+
let preview = opts?.preview ?? "(no messages)";
|
|
20787
21167
|
let lastActivityMs = lastMtimeMs;
|
|
20788
|
-
|
|
20789
|
-
const
|
|
20790
|
-
|
|
20791
|
-
|
|
20792
|
-
|
|
20793
|
-
|
|
20794
|
-
|
|
20795
|
-
|
|
20796
|
-
|
|
21168
|
+
if (opts?.updatedAtIso) {
|
|
21169
|
+
const parsed = Date.parse(opts.updatedAtIso);
|
|
21170
|
+
if (Number.isFinite(parsed)) {
|
|
21171
|
+
lastActivityMs = Math.max(lastMtimeMs, parsed);
|
|
21172
|
+
}
|
|
21173
|
+
}
|
|
21174
|
+
if (!opts?.preview || opts.preview === "(no messages)") {
|
|
21175
|
+
try {
|
|
21176
|
+
const raw = await fs28.readFile(absPath, "utf-8");
|
|
21177
|
+
const data = JSON.parse(raw);
|
|
21178
|
+
preview = previewFromConversationHistory(data.conversation_history);
|
|
21179
|
+
const iso = data.last_updated || data.created_at;
|
|
21180
|
+
if (iso) {
|
|
21181
|
+
const parsed = Date.parse(iso);
|
|
21182
|
+
if (Number.isFinite(parsed)) {
|
|
21183
|
+
lastActivityMs = Math.max(lastActivityMs, parsed);
|
|
21184
|
+
}
|
|
20797
21185
|
}
|
|
21186
|
+
} catch {
|
|
20798
21187
|
}
|
|
20799
|
-
} catch {
|
|
20800
21188
|
}
|
|
20801
21189
|
return {
|
|
20802
21190
|
sessionId,
|
|
@@ -20809,13 +21197,29 @@ async function enrichCandidateFromFile(absPath, sessionId, lastMtimeMs) {
|
|
|
20809
21197
|
}
|
|
20810
21198
|
async function listAgentSessionsForResume(limit = 50) {
|
|
20811
21199
|
const appDir = getPreferredAppDir();
|
|
20812
|
-
|
|
21200
|
+
await ensureSessionIndexDb(appDir);
|
|
21201
|
+
const fromIndex = await listSessionsFromIndex(limit * 2, appDir);
|
|
20813
21202
|
const map = /* @__PURE__ */ new Map();
|
|
20814
|
-
|
|
20815
|
-
const
|
|
21203
|
+
for (const row of fromIndex) {
|
|
21204
|
+
const full = path31.join(appDir, row.relativePath);
|
|
21205
|
+
try {
|
|
21206
|
+
const st = await fs28.stat(full);
|
|
21207
|
+
if (!st.isFile()) continue;
|
|
21208
|
+
map.set(row.sessionId, {
|
|
21209
|
+
path: full,
|
|
21210
|
+
lastMtimeMs: st.mtimeMs,
|
|
21211
|
+
preview: row.preview,
|
|
21212
|
+
updatedAt: row.updatedAt
|
|
21213
|
+
});
|
|
21214
|
+
} catch {
|
|
21215
|
+
}
|
|
21216
|
+
}
|
|
21217
|
+
const sessionsRoot = path31.join(appDir, "sessions");
|
|
21218
|
+
await walkSessionJsonFiles2(sessionsRoot, async (absPath) => {
|
|
21219
|
+
const id = path31.basename(absPath, ".json");
|
|
20816
21220
|
if (!id) return;
|
|
20817
21221
|
try {
|
|
20818
|
-
const st = await
|
|
21222
|
+
const st = await fs28.stat(absPath);
|
|
20819
21223
|
if (!st.isFile()) return;
|
|
20820
21224
|
const cur = map.get(id);
|
|
20821
21225
|
if (!cur || st.mtimeMs > cur.lastMtimeMs) {
|
|
@@ -20827,9 +21231,9 @@ async function listAgentSessionsForResume(limit = 50) {
|
|
|
20827
21231
|
const index = await loadLatestIndexEntriesBySessionId();
|
|
20828
21232
|
for (const [sessionId, entry] of index) {
|
|
20829
21233
|
if (map.has(sessionId)) continue;
|
|
20830
|
-
const full =
|
|
21234
|
+
const full = path31.join(appDir, entry.relativePath.replace(/\//g, path31.sep));
|
|
20831
21235
|
try {
|
|
20832
|
-
const st = await
|
|
21236
|
+
const st = await fs28.stat(full);
|
|
20833
21237
|
if (st.isFile()) {
|
|
20834
21238
|
map.set(sessionId, { path: full, lastMtimeMs: st.mtimeMs });
|
|
20835
21239
|
}
|
|
@@ -20838,7 +21242,10 @@ async function listAgentSessionsForResume(limit = 50) {
|
|
|
20838
21242
|
}
|
|
20839
21243
|
const enriched = await Promise.all(
|
|
20840
21244
|
[...map.entries()].map(
|
|
20841
|
-
([sessionId, v]) => enrichCandidateFromFile(v.path, sessionId, v.lastMtimeMs
|
|
21245
|
+
([sessionId, v]) => enrichCandidateFromFile(v.path, sessionId, v.lastMtimeMs, {
|
|
21246
|
+
preview: v.preview,
|
|
21247
|
+
updatedAtIso: v.updatedAt
|
|
21248
|
+
})
|
|
20842
21249
|
)
|
|
20843
21250
|
);
|
|
20844
21251
|
return enriched.sort((a, b) => b.lastActivityMs - a.lastActivityMs).slice(0, limit);
|
|
@@ -20883,10 +21290,10 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
|
|
|
20883
21290
|
const isWin = process.platform === "win32";
|
|
20884
21291
|
while (attempt <= maxRetries) {
|
|
20885
21292
|
try {
|
|
20886
|
-
const dir =
|
|
20887
|
-
await
|
|
21293
|
+
const dir = path32.dirname(dest);
|
|
21294
|
+
await fs29.mkdir(dir, { recursive: true }).catch(() => {
|
|
20888
21295
|
});
|
|
20889
|
-
await
|
|
21296
|
+
await fs29.rename(src, dest);
|
|
20890
21297
|
return;
|
|
20891
21298
|
} catch (e) {
|
|
20892
21299
|
lastErr = e;
|
|
@@ -20899,13 +21306,13 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
|
|
|
20899
21306
|
}
|
|
20900
21307
|
}
|
|
20901
21308
|
try {
|
|
20902
|
-
await
|
|
20903
|
-
const data = await
|
|
20904
|
-
const dir =
|
|
20905
|
-
await
|
|
21309
|
+
await fs29.access(src);
|
|
21310
|
+
const data = await fs29.readFile(src);
|
|
21311
|
+
const dir = path32.dirname(dest);
|
|
21312
|
+
await fs29.mkdir(dir, { recursive: true }).catch(() => {
|
|
20906
21313
|
});
|
|
20907
|
-
await
|
|
20908
|
-
await
|
|
21314
|
+
await fs29.writeFile(dest, data);
|
|
21315
|
+
await fs29.unlink(src).catch(() => {
|
|
20909
21316
|
});
|
|
20910
21317
|
return;
|
|
20911
21318
|
} catch (fallbackErr) {
|
|
@@ -20918,23 +21325,23 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
|
|
|
20918
21325
|
}
|
|
20919
21326
|
async function ensureSessionDir() {
|
|
20920
21327
|
const appDir = getPreferredAppDir();
|
|
20921
|
-
const sessionDir =
|
|
20922
|
-
await
|
|
21328
|
+
const sessionDir = path32.join(appDir, "sessions");
|
|
21329
|
+
await fs29.mkdir(sessionDir, { recursive: true });
|
|
20923
21330
|
return sessionDir;
|
|
20924
21331
|
}
|
|
20925
21332
|
async function resolveAgentSessionFilePath(sessionId) {
|
|
20926
21333
|
const appDir = getPreferredAppDir();
|
|
20927
|
-
const legacy =
|
|
21334
|
+
const legacy = path32.join(appDir, "sessions", `${sessionId}.json`);
|
|
20928
21335
|
try {
|
|
20929
|
-
await
|
|
21336
|
+
await fs29.access(legacy);
|
|
20930
21337
|
return legacy;
|
|
20931
21338
|
} catch {
|
|
20932
21339
|
}
|
|
20933
21340
|
const rel = await resolveSessionRelativePathFromIndex(sessionId);
|
|
20934
21341
|
if (rel) {
|
|
20935
|
-
const full =
|
|
21342
|
+
const full = path32.join(appDir, rel);
|
|
20936
21343
|
try {
|
|
20937
|
-
await
|
|
21344
|
+
await fs29.access(full);
|
|
20938
21345
|
return full;
|
|
20939
21346
|
} catch {
|
|
20940
21347
|
}
|
|
@@ -20947,7 +21354,7 @@ async function loadSession(sessionId) {
|
|
|
20947
21354
|
return null;
|
|
20948
21355
|
}
|
|
20949
21356
|
try {
|
|
20950
|
-
const fileContent = await
|
|
21357
|
+
const fileContent = await fs29.readFile(sessionFile, "utf-8");
|
|
20951
21358
|
const sessionData = JSON.parse(fileContent);
|
|
20952
21359
|
const memory = {
|
|
20953
21360
|
historyAnchor: sessionData.history_anchor ?? null,
|
|
@@ -20968,16 +21375,16 @@ async function loadOrcreateSession(sessionId) {
|
|
|
20968
21375
|
const y = String(now2.getFullYear());
|
|
20969
21376
|
const mo = String(now2.getMonth() + 1).padStart(2, "0");
|
|
20970
21377
|
const d = String(now2.getDate()).padStart(2, "0");
|
|
20971
|
-
const datedDir =
|
|
20972
|
-
await
|
|
20973
|
-
const sessionFile =
|
|
21378
|
+
const datedDir = path32.join(sessionsRoot, y, mo, d);
|
|
21379
|
+
await fs29.mkdir(datedDir, { recursive: true });
|
|
21380
|
+
const sessionFile = path32.join(datedDir, `${sessionId}.json`);
|
|
20974
21381
|
const newSessionData = {
|
|
20975
21382
|
session_id: sessionId,
|
|
20976
21383
|
created_at: now2.toISOString(),
|
|
20977
21384
|
conversation_history: []
|
|
20978
21385
|
};
|
|
20979
|
-
await
|
|
20980
|
-
const relToApp =
|
|
21386
|
+
await fs29.writeFile(sessionFile, JSON.stringify(newSessionData, null, 2), "utf-8");
|
|
21387
|
+
const relToApp = path32.relative(getPreferredAppDir(), sessionFile);
|
|
20981
21388
|
await appendAgentSessionPath(sessionId, relToApp);
|
|
20982
21389
|
const emptyMemory = {
|
|
20983
21390
|
historyAnchor: null,
|
|
@@ -20989,12 +21396,12 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
|
|
|
20989
21396
|
await withFileLock(sessionFile, async () => {
|
|
20990
21397
|
let sessionData;
|
|
20991
21398
|
try {
|
|
20992
|
-
const dir =
|
|
20993
|
-
await
|
|
21399
|
+
const dir = path32.dirname(sessionFile);
|
|
21400
|
+
await fs29.mkdir(dir, { recursive: true });
|
|
20994
21401
|
} catch {
|
|
20995
21402
|
}
|
|
20996
21403
|
try {
|
|
20997
|
-
const fileContent = await
|
|
21404
|
+
const fileContent = await fs29.readFile(sessionFile, "utf-8");
|
|
20998
21405
|
sessionData = JSON.parse(fileContent);
|
|
20999
21406
|
} catch (error) {
|
|
21000
21407
|
const code = error && error.code;
|
|
@@ -21005,14 +21412,14 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
|
|
|
21005
21412
|
console.warn(`An unknown error occurred while reading ${sessionFile}. Re-initializing.`, error);
|
|
21006
21413
|
}
|
|
21007
21414
|
}
|
|
21008
|
-
const sessionId =
|
|
21415
|
+
const sessionId = path32.basename(sessionFile, ".json");
|
|
21009
21416
|
sessionData = {
|
|
21010
21417
|
session_id: sessionId,
|
|
21011
21418
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21012
21419
|
conversation_history: []
|
|
21013
21420
|
};
|
|
21014
21421
|
try {
|
|
21015
|
-
await
|
|
21422
|
+
await fs29.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
|
|
21016
21423
|
} catch {
|
|
21017
21424
|
}
|
|
21018
21425
|
}
|
|
@@ -21028,8 +21435,26 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
|
|
|
21028
21435
|
}
|
|
21029
21436
|
const tempSessionFile = `${sessionFile}.${Date.now()}.tmp`;
|
|
21030
21437
|
try {
|
|
21031
|
-
await
|
|
21438
|
+
await fs29.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
|
|
21032
21439
|
await safeRenameWithRetry(tempSessionFile, sessionFile);
|
|
21440
|
+
const sessionId = sessionData.session_id ?? path32.basename(sessionFile, ".json");
|
|
21441
|
+
const updatedAt = sessionData.last_updated ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
21442
|
+
const createdAt = sessionData.created_at ?? updatedAt;
|
|
21443
|
+
let relToApp;
|
|
21444
|
+
try {
|
|
21445
|
+
relToApp = path32.relative(getPreferredAppDir(), sessionFile);
|
|
21446
|
+
} catch {
|
|
21447
|
+
relToApp = sessionFile;
|
|
21448
|
+
}
|
|
21449
|
+
await upsertSessionIndexRow({
|
|
21450
|
+
sessionId,
|
|
21451
|
+
relativePath: relToApp,
|
|
21452
|
+
createdAt,
|
|
21453
|
+
updatedAt,
|
|
21454
|
+
preview: previewFromConversationHistory(
|
|
21455
|
+
history
|
|
21456
|
+
)
|
|
21457
|
+
});
|
|
21033
21458
|
} catch (writeError) {
|
|
21034
21459
|
if (writeError instanceof Error) {
|
|
21035
21460
|
console.error(`Fatal error saving session to ${sessionFile}: ${writeError.message}`);
|
|
@@ -21037,7 +21462,7 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
|
|
|
21037
21462
|
console.error(`An unknown fatal error occurred while saving session to ${sessionFile}:`, writeError);
|
|
21038
21463
|
}
|
|
21039
21464
|
try {
|
|
21040
|
-
await
|
|
21465
|
+
await fs29.unlink(tempSessionFile);
|
|
21041
21466
|
} catch {
|
|
21042
21467
|
}
|
|
21043
21468
|
}
|
|
@@ -21052,16 +21477,30 @@ async function saveSessionHistory(sessionFile, history, memory) {
|
|
|
21052
21477
|
});
|
|
21053
21478
|
debouncedSave(sessionFile, cleanHistory, memory);
|
|
21054
21479
|
}
|
|
21480
|
+
async function saveSessionHistoryNow(sessionFile, history, memory) {
|
|
21481
|
+
const pending = pendingSaves.get(sessionFile);
|
|
21482
|
+
if (pending) {
|
|
21483
|
+
clearTimeout(pending.timer);
|
|
21484
|
+
pendingSaves.delete(sessionFile);
|
|
21485
|
+
}
|
|
21486
|
+
const cleanHistory = history.filter((msg) => {
|
|
21487
|
+
if (msg.role === "user" && typeof msg.content === "string") {
|
|
21488
|
+
return !msg.content.startsWith("[SKILL:");
|
|
21489
|
+
}
|
|
21490
|
+
return true;
|
|
21491
|
+
});
|
|
21492
|
+
await doSaveSessionHistory(sessionFile, cleanHistory, memory);
|
|
21493
|
+
}
|
|
21055
21494
|
|
|
21056
21495
|
// src/app/agent/core/prompt/prompt_builder.ts
|
|
21057
21496
|
import os24 from "os";
|
|
21058
|
-
import
|
|
21059
|
-
import
|
|
21497
|
+
import fs36 from "fs";
|
|
21498
|
+
import path39 from "path";
|
|
21060
21499
|
import { execSync as execSync3 } from "child_process";
|
|
21061
21500
|
|
|
21062
21501
|
// src/app/agent/skills/skill_loader.ts
|
|
21063
|
-
import
|
|
21064
|
-
import
|
|
21502
|
+
import fs30 from "fs";
|
|
21503
|
+
import path33 from "path";
|
|
21065
21504
|
import os20 from "os";
|
|
21066
21505
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
21067
21506
|
var SkillLoader = class _SkillLoader {
|
|
@@ -21071,8 +21510,8 @@ var SkillLoader = class _SkillLoader {
|
|
|
21071
21510
|
cache = /* @__PURE__ */ new Map();
|
|
21072
21511
|
conflicts = [];
|
|
21073
21512
|
constructor(projectRoot, bundledDir) {
|
|
21074
|
-
this.projectSkillsDir =
|
|
21075
|
-
this.globalSkillsDir =
|
|
21513
|
+
this.projectSkillsDir = path33.join(projectRoot, ".bluma", "skills");
|
|
21514
|
+
this.globalSkillsDir = path33.join(os20.homedir(), ".bluma", "skills");
|
|
21076
21515
|
this.bundledSkillsDir = bundledDir || _SkillLoader.resolveBundledDir();
|
|
21077
21516
|
}
|
|
21078
21517
|
/**
|
|
@@ -21081,48 +21520,48 @@ var SkillLoader = class _SkillLoader {
|
|
|
21081
21520
|
*/
|
|
21082
21521
|
static resolveBundledDir() {
|
|
21083
21522
|
if (process.env.JEST_WORKER_ID !== void 0 || process.env.NODE_ENV === "test") {
|
|
21084
|
-
return
|
|
21523
|
+
return path33.join(process.cwd(), "dist", "config", "skills");
|
|
21085
21524
|
}
|
|
21086
21525
|
const candidates = [];
|
|
21087
21526
|
const push = (p) => {
|
|
21088
|
-
const abs =
|
|
21527
|
+
const abs = path33.resolve(p);
|
|
21089
21528
|
if (!candidates.includes(abs)) {
|
|
21090
21529
|
candidates.push(abs);
|
|
21091
21530
|
}
|
|
21092
21531
|
};
|
|
21093
21532
|
let argvBundled = null;
|
|
21094
21533
|
try {
|
|
21095
|
-
const bundleDir =
|
|
21096
|
-
push(
|
|
21534
|
+
const bundleDir = path33.dirname(fileURLToPath3(import.meta.url));
|
|
21535
|
+
push(path33.join(bundleDir, "config", "skills"));
|
|
21097
21536
|
} catch {
|
|
21098
21537
|
}
|
|
21099
21538
|
const argv1 = process.argv[1];
|
|
21100
21539
|
if (argv1 && !argv1.startsWith("-")) {
|
|
21101
21540
|
try {
|
|
21102
21541
|
let resolved = argv1;
|
|
21103
|
-
if (
|
|
21104
|
-
resolved =
|
|
21105
|
-
} else if (!
|
|
21106
|
-
resolved =
|
|
21542
|
+
if (path33.isAbsolute(argv1) && fs30.existsSync(argv1)) {
|
|
21543
|
+
resolved = fs30.realpathSync(argv1);
|
|
21544
|
+
} else if (!path33.isAbsolute(argv1)) {
|
|
21545
|
+
resolved = path33.resolve(process.cwd(), argv1);
|
|
21107
21546
|
}
|
|
21108
|
-
const scriptDir =
|
|
21109
|
-
argvBundled =
|
|
21547
|
+
const scriptDir = path33.dirname(resolved);
|
|
21548
|
+
argvBundled = path33.join(scriptDir, "config", "skills");
|
|
21110
21549
|
push(argvBundled);
|
|
21111
21550
|
} catch {
|
|
21112
21551
|
}
|
|
21113
21552
|
}
|
|
21114
21553
|
for (const abs of candidates) {
|
|
21115
|
-
if (
|
|
21554
|
+
if (fs30.existsSync(abs)) {
|
|
21116
21555
|
return abs;
|
|
21117
21556
|
}
|
|
21118
21557
|
}
|
|
21119
21558
|
try {
|
|
21120
|
-
return
|
|
21559
|
+
return path33.join(path33.dirname(fileURLToPath3(import.meta.url)), "config", "skills");
|
|
21121
21560
|
} catch {
|
|
21122
21561
|
if (argvBundled) {
|
|
21123
21562
|
return argvBundled;
|
|
21124
21563
|
}
|
|
21125
|
-
return
|
|
21564
|
+
return path33.join(os20.homedir(), ".bluma", "__bundled_skills_unresolved__");
|
|
21126
21565
|
}
|
|
21127
21566
|
}
|
|
21128
21567
|
/**
|
|
@@ -21151,8 +21590,8 @@ var SkillLoader = class _SkillLoader {
|
|
|
21151
21590
|
this.conflicts.push({
|
|
21152
21591
|
name: skill.name,
|
|
21153
21592
|
userSource: source,
|
|
21154
|
-
userPath:
|
|
21155
|
-
bundledPath:
|
|
21593
|
+
userPath: path33.join(dir, skill.name, "SKILL.md"),
|
|
21594
|
+
bundledPath: path33.join(this.bundledSkillsDir, skill.name, "SKILL.md")
|
|
21156
21595
|
});
|
|
21157
21596
|
continue;
|
|
21158
21597
|
}
|
|
@@ -21160,20 +21599,20 @@ var SkillLoader = class _SkillLoader {
|
|
|
21160
21599
|
}
|
|
21161
21600
|
}
|
|
21162
21601
|
listFromDir(dir, source) {
|
|
21163
|
-
if (!
|
|
21602
|
+
if (!fs30.existsSync(dir)) return [];
|
|
21164
21603
|
try {
|
|
21165
|
-
return
|
|
21166
|
-
const fullPath =
|
|
21167
|
-
return
|
|
21168
|
-
}).map((d) => this.loadMetadataFromPath(
|
|
21604
|
+
return fs30.readdirSync(dir).filter((d) => {
|
|
21605
|
+
const fullPath = path33.join(dir, d);
|
|
21606
|
+
return fs30.statSync(fullPath).isDirectory() && fs30.existsSync(path33.join(fullPath, "SKILL.md"));
|
|
21607
|
+
}).map((d) => this.loadMetadataFromPath(path33.join(dir, d, "SKILL.md"), d, source)).filter((m) => m !== null);
|
|
21169
21608
|
} catch {
|
|
21170
21609
|
return [];
|
|
21171
21610
|
}
|
|
21172
21611
|
}
|
|
21173
21612
|
loadMetadataFromPath(skillPath, skillName, source) {
|
|
21174
|
-
if (!
|
|
21613
|
+
if (!fs30.existsSync(skillPath)) return null;
|
|
21175
21614
|
try {
|
|
21176
|
-
const raw =
|
|
21615
|
+
const raw = fs30.readFileSync(skillPath, "utf-8");
|
|
21177
21616
|
const parsed = this.parseFrontmatter(raw);
|
|
21178
21617
|
return {
|
|
21179
21618
|
name: parsed.name || skillName,
|
|
@@ -21195,12 +21634,12 @@ var SkillLoader = class _SkillLoader {
|
|
|
21195
21634
|
*/
|
|
21196
21635
|
load(name) {
|
|
21197
21636
|
if (this.cache.has(name)) return this.cache.get(name);
|
|
21198
|
-
const bundledPath =
|
|
21199
|
-
const projectPath =
|
|
21200
|
-
const globalPath =
|
|
21201
|
-
const existsBundled =
|
|
21202
|
-
const existsProject =
|
|
21203
|
-
const existsGlobal =
|
|
21637
|
+
const bundledPath = path33.join(this.bundledSkillsDir, name, "SKILL.md");
|
|
21638
|
+
const projectPath = path33.join(this.projectSkillsDir, name, "SKILL.md");
|
|
21639
|
+
const globalPath = path33.join(this.globalSkillsDir, name, "SKILL.md");
|
|
21640
|
+
const existsBundled = fs30.existsSync(bundledPath);
|
|
21641
|
+
const existsProject = fs30.existsSync(projectPath);
|
|
21642
|
+
const existsGlobal = fs30.existsSync(globalPath);
|
|
21204
21643
|
if (existsBundled && (existsProject || existsGlobal)) {
|
|
21205
21644
|
const conflictSource = existsProject ? "project" : "global";
|
|
21206
21645
|
const conflictPath = existsProject ? projectPath : globalPath;
|
|
@@ -21239,9 +21678,9 @@ var SkillLoader = class _SkillLoader {
|
|
|
21239
21678
|
}
|
|
21240
21679
|
loadFromPath(skillPath, name, source) {
|
|
21241
21680
|
try {
|
|
21242
|
-
const raw =
|
|
21681
|
+
const raw = fs30.readFileSync(skillPath, "utf-8");
|
|
21243
21682
|
const parsed = this.parseFrontmatter(raw);
|
|
21244
|
-
const skillDir =
|
|
21683
|
+
const skillDir = path33.dirname(skillPath);
|
|
21245
21684
|
return {
|
|
21246
21685
|
name: parsed.name || name,
|
|
21247
21686
|
description: parsed.description || "",
|
|
@@ -21250,22 +21689,22 @@ var SkillLoader = class _SkillLoader {
|
|
|
21250
21689
|
version: parsed.version,
|
|
21251
21690
|
author: parsed.author,
|
|
21252
21691
|
license: parsed.license,
|
|
21253
|
-
references: this.scanAssets(
|
|
21254
|
-
scripts: this.scanAssets(
|
|
21692
|
+
references: this.scanAssets(path33.join(skillDir, "references")),
|
|
21693
|
+
scripts: this.scanAssets(path33.join(skillDir, "scripts"))
|
|
21255
21694
|
};
|
|
21256
21695
|
} catch {
|
|
21257
21696
|
return null;
|
|
21258
21697
|
}
|
|
21259
21698
|
}
|
|
21260
21699
|
scanAssets(dir) {
|
|
21261
|
-
if (!
|
|
21700
|
+
if (!fs30.existsSync(dir)) return [];
|
|
21262
21701
|
try {
|
|
21263
|
-
return
|
|
21264
|
-
const fp =
|
|
21265
|
-
return
|
|
21702
|
+
return fs30.readdirSync(dir).filter((f) => {
|
|
21703
|
+
const fp = path33.join(dir, f);
|
|
21704
|
+
return fs30.statSync(fp).isFile();
|
|
21266
21705
|
}).map((f) => ({
|
|
21267
21706
|
name: f,
|
|
21268
|
-
path:
|
|
21707
|
+
path: path33.resolve(dir, f)
|
|
21269
21708
|
}));
|
|
21270
21709
|
} catch {
|
|
21271
21710
|
return [];
|
|
@@ -21322,10 +21761,10 @@ var SkillLoader = class _SkillLoader {
|
|
|
21322
21761
|
this.cache.clear();
|
|
21323
21762
|
}
|
|
21324
21763
|
exists(name) {
|
|
21325
|
-
const bundledPath =
|
|
21326
|
-
const projectPath =
|
|
21327
|
-
const globalPath =
|
|
21328
|
-
return
|
|
21764
|
+
const bundledPath = path33.join(this.bundledSkillsDir, name, "SKILL.md");
|
|
21765
|
+
const projectPath = path33.join(this.projectSkillsDir, name, "SKILL.md");
|
|
21766
|
+
const globalPath = path33.join(this.globalSkillsDir, name, "SKILL.md");
|
|
21767
|
+
return fs30.existsSync(bundledPath) || fs30.existsSync(projectPath) || fs30.existsSync(globalPath);
|
|
21329
21768
|
}
|
|
21330
21769
|
/**
|
|
21331
21770
|
* Retorna conflitos detetados (skills do utilizador com mesmo nome de nativas).
|
|
@@ -21357,13 +21796,13 @@ var SkillLoader = class _SkillLoader {
|
|
|
21357
21796
|
};
|
|
21358
21797
|
|
|
21359
21798
|
// src/app/agent/core/prompt/workspace_snapshot.ts
|
|
21360
|
-
import
|
|
21361
|
-
import
|
|
21799
|
+
import fs32 from "fs";
|
|
21800
|
+
import path35 from "path";
|
|
21362
21801
|
import { execSync as execSync2 } from "child_process";
|
|
21363
21802
|
|
|
21364
21803
|
// src/app/agent/utils/blumamd.ts
|
|
21365
|
-
import
|
|
21366
|
-
import
|
|
21804
|
+
import fs31 from "fs";
|
|
21805
|
+
import path34 from "path";
|
|
21367
21806
|
import os21 from "os";
|
|
21368
21807
|
import { execSync } from "child_process";
|
|
21369
21808
|
var MEMORY_INSTRUCTION_PROMPT = "Instru\xE7\xF5es de mem\xF3ria do BluMa (BLUMA.md) est\xE3o abaixo. Siga estas instru\xE7\xF5es exatamente como escritas. Estas instru\xE7\xF5es OVERRIDE qualquer comportamento padr\xE3o.";
|
|
@@ -21493,12 +21932,12 @@ var TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
21493
21932
|
function expandIncludePath(includePath, baseDir) {
|
|
21494
21933
|
const cleanPath = includePath.startsWith("@") ? includePath.slice(1) : includePath;
|
|
21495
21934
|
if (cleanPath.startsWith("~")) {
|
|
21496
|
-
return
|
|
21935
|
+
return path34.join(os21.homedir(), cleanPath.slice(1));
|
|
21497
21936
|
}
|
|
21498
|
-
if (
|
|
21937
|
+
if (path34.isAbsolute(cleanPath)) {
|
|
21499
21938
|
return cleanPath;
|
|
21500
21939
|
}
|
|
21501
|
-
return
|
|
21940
|
+
return path34.resolve(baseDir, cleanPath);
|
|
21502
21941
|
}
|
|
21503
21942
|
function processIncludes(content, baseDir, processedFiles) {
|
|
21504
21943
|
const lines = content.split("\n");
|
|
@@ -21507,20 +21946,20 @@ function processIncludes(content, baseDir, processedFiles) {
|
|
|
21507
21946
|
const includeMatch = line.match(/^@\s*([^\s]+)/);
|
|
21508
21947
|
if (includeMatch) {
|
|
21509
21948
|
const includePath = expandIncludePath(includeMatch[1], baseDir);
|
|
21510
|
-
const normalizedPath =
|
|
21949
|
+
const normalizedPath = path34.normalize(includePath);
|
|
21511
21950
|
if (processedFiles.has(normalizedPath)) {
|
|
21512
21951
|
result.push(`<!-- Circular include prevented: ${includeMatch[1]} -->`);
|
|
21513
21952
|
continue;
|
|
21514
21953
|
}
|
|
21515
|
-
const ext =
|
|
21954
|
+
const ext = path34.extname(includePath).toLowerCase();
|
|
21516
21955
|
if (!TEXT_FILE_EXTENSIONS.has(ext)) {
|
|
21517
21956
|
result.push(`<!-- Include skipped (unsupported extension): ${includeMatch[1]} -->`);
|
|
21518
21957
|
continue;
|
|
21519
21958
|
}
|
|
21520
21959
|
try {
|
|
21521
|
-
const includedContent =
|
|
21960
|
+
const includedContent = fs31.readFileSync(includePath, "utf-8");
|
|
21522
21961
|
processedFiles.add(normalizedPath);
|
|
21523
|
-
const processedContent = processIncludes(includedContent,
|
|
21962
|
+
const processedContent = processIncludes(includedContent, path34.dirname(includePath), processedFiles);
|
|
21524
21963
|
result.push(`
|
|
21525
21964
|
<!-- BEGIN INCLUDE ${includeMatch[1]} -->
|
|
21526
21965
|
`);
|
|
@@ -21566,9 +22005,9 @@ function parseFrontmatterPaths(paths2) {
|
|
|
21566
22005
|
}
|
|
21567
22006
|
function readMemoryFile(filePath, type, includeBasePath) {
|
|
21568
22007
|
try {
|
|
21569
|
-
const rawContent =
|
|
21570
|
-
const baseDir = includeBasePath ||
|
|
21571
|
-
const processedFiles = /* @__PURE__ */ new Set([
|
|
22008
|
+
const rawContent = fs31.readFileSync(filePath, "utf-8");
|
|
22009
|
+
const baseDir = includeBasePath || path34.dirname(filePath);
|
|
22010
|
+
const processedFiles = /* @__PURE__ */ new Set([path34.normalize(filePath)]);
|
|
21572
22011
|
const { frontmatter, content: withoutFrontmatter } = parseFrontmatter(rawContent);
|
|
21573
22012
|
const globs = parseFrontmatterPaths(frontmatter.paths);
|
|
21574
22013
|
const processedContent = processIncludes(withoutFrontmatter, baseDir, processedFiles);
|
|
@@ -21590,15 +22029,15 @@ function readMemoryFile(filePath, type, includeBasePath) {
|
|
|
21590
22029
|
}
|
|
21591
22030
|
function findGitRoot(startDir) {
|
|
21592
22031
|
let current = startDir;
|
|
21593
|
-
while (current !==
|
|
21594
|
-
const gitPath =
|
|
22032
|
+
while (current !== path34.dirname(current)) {
|
|
22033
|
+
const gitPath = path34.join(current, ".git");
|
|
21595
22034
|
try {
|
|
21596
|
-
if (
|
|
22035
|
+
if (fs31.existsSync(gitPath)) {
|
|
21597
22036
|
return current;
|
|
21598
22037
|
}
|
|
21599
22038
|
} catch {
|
|
21600
22039
|
}
|
|
21601
|
-
current =
|
|
22040
|
+
current = path34.dirname(current);
|
|
21602
22041
|
}
|
|
21603
22042
|
return null;
|
|
21604
22043
|
}
|
|
@@ -21623,17 +22062,17 @@ function getGitUserInfo(cwd2) {
|
|
|
21623
22062
|
}
|
|
21624
22063
|
function processRulesDirectory(rulesDir, type, processedPaths, conditionalRule = false) {
|
|
21625
22064
|
const result = [];
|
|
21626
|
-
if (!
|
|
22065
|
+
if (!fs31.existsSync(rulesDir)) {
|
|
21627
22066
|
return result;
|
|
21628
22067
|
}
|
|
21629
22068
|
try {
|
|
21630
|
-
const entries =
|
|
22069
|
+
const entries = fs31.readdirSync(rulesDir, { withFileTypes: true });
|
|
21631
22070
|
for (const entry of entries) {
|
|
21632
|
-
const entryPath =
|
|
22071
|
+
const entryPath = path34.join(rulesDir, entry.name);
|
|
21633
22072
|
if (entry.isDirectory()) {
|
|
21634
22073
|
result.push(...processRulesDirectory(entryPath, type, processedPaths, conditionalRule));
|
|
21635
22074
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
21636
|
-
const normalizedPath =
|
|
22075
|
+
const normalizedPath = path34.normalize(entryPath);
|
|
21637
22076
|
if (processedPaths.has(normalizedPath)) {
|
|
21638
22077
|
continue;
|
|
21639
22078
|
}
|
|
@@ -21669,13 +22108,13 @@ function loadManagedMemory() {
|
|
|
21669
22108
|
function loadUserMemory() {
|
|
21670
22109
|
const files = [];
|
|
21671
22110
|
const homeDir = os21.homedir();
|
|
21672
|
-
const userBlumaDir =
|
|
21673
|
-
const userBlumaMd =
|
|
22111
|
+
const userBlumaDir = path34.join(homeDir, ".bluma");
|
|
22112
|
+
const userBlumaMd = path34.join(userBlumaDir, "BLUMA.md");
|
|
21674
22113
|
const userFile = readMemoryFile(userBlumaMd, "User");
|
|
21675
22114
|
if (userFile && userFile.content.trim()) {
|
|
21676
22115
|
files.push(userFile);
|
|
21677
22116
|
}
|
|
21678
|
-
const userRulesDir =
|
|
22117
|
+
const userRulesDir = path34.join(userBlumaDir, "rules");
|
|
21679
22118
|
const processedPaths = /* @__PURE__ */ new Set();
|
|
21680
22119
|
files.push(...processRulesDirectory(userRulesDir, "User", processedPaths, false));
|
|
21681
22120
|
return files;
|
|
@@ -21687,10 +22126,10 @@ function loadProjectMemory(cwd2) {
|
|
|
21687
22126
|
let currentDir = cwd2;
|
|
21688
22127
|
const MAX_TRAVERSAL_DEPTH = 20;
|
|
21689
22128
|
let depth = 0;
|
|
21690
|
-
const normalizedGitRoot =
|
|
21691
|
-
while (currentDir !==
|
|
22129
|
+
const normalizedGitRoot = path34.resolve(gitRoot);
|
|
22130
|
+
while (currentDir !== path34.dirname(currentDir) && path34.resolve(currentDir).startsWith(normalizedGitRoot) && depth < MAX_TRAVERSAL_DEPTH) {
|
|
21692
22131
|
dirs.push(currentDir);
|
|
21693
|
-
currentDir =
|
|
22132
|
+
currentDir = path34.dirname(currentDir);
|
|
21694
22133
|
depth++;
|
|
21695
22134
|
}
|
|
21696
22135
|
if (!dirs.includes(gitRoot)) {
|
|
@@ -21698,7 +22137,7 @@ function loadProjectMemory(cwd2) {
|
|
|
21698
22137
|
}
|
|
21699
22138
|
const processedPaths = /* @__PURE__ */ new Set();
|
|
21700
22139
|
for (const dir of dirs.reverse()) {
|
|
21701
|
-
const projectBlumaMd =
|
|
22140
|
+
const projectBlumaMd = path34.join(dir, "BLUMA.md");
|
|
21702
22141
|
if (!processedPaths.has(projectBlumaMd)) {
|
|
21703
22142
|
processedPaths.add(projectBlumaMd);
|
|
21704
22143
|
const projectFile = readMemoryFile(projectBlumaMd, "Project");
|
|
@@ -21706,7 +22145,7 @@ function loadProjectMemory(cwd2) {
|
|
|
21706
22145
|
files.push(projectFile);
|
|
21707
22146
|
}
|
|
21708
22147
|
}
|
|
21709
|
-
const blumaDirBlumaMd =
|
|
22148
|
+
const blumaDirBlumaMd = path34.join(dir, ".bluma", "BLUMA.md");
|
|
21710
22149
|
if (!processedPaths.has(blumaDirBlumaMd)) {
|
|
21711
22150
|
processedPaths.add(blumaDirBlumaMd);
|
|
21712
22151
|
const blumaDirFile = readMemoryFile(blumaDirBlumaMd, "Project");
|
|
@@ -21714,10 +22153,10 @@ function loadProjectMemory(cwd2) {
|
|
|
21714
22153
|
files.push(blumaDirFile);
|
|
21715
22154
|
}
|
|
21716
22155
|
}
|
|
21717
|
-
const rulesDir =
|
|
22156
|
+
const rulesDir = path34.join(dir, ".bluma", "rules");
|
|
21718
22157
|
files.push(...processRulesDirectory(rulesDir, "Project", processedPaths, false));
|
|
21719
22158
|
}
|
|
21720
|
-
const localBlumaMd =
|
|
22159
|
+
const localBlumaMd = path34.join(cwd2, "BLUMA.local.md");
|
|
21721
22160
|
if (!processedPaths.has(localBlumaMd)) {
|
|
21722
22161
|
processedPaths.add(localBlumaMd);
|
|
21723
22162
|
const localFile = readMemoryFile(localBlumaMd, "Local");
|
|
@@ -21803,10 +22242,10 @@ var LIMITS = {
|
|
|
21803
22242
|
};
|
|
21804
22243
|
function safeReadFile(filePath, maxChars) {
|
|
21805
22244
|
try {
|
|
21806
|
-
if (!
|
|
21807
|
-
const st =
|
|
22245
|
+
if (!fs32.existsSync(filePath)) return null;
|
|
22246
|
+
const st = fs32.statSync(filePath);
|
|
21808
22247
|
if (!st.isFile()) return null;
|
|
21809
|
-
const raw =
|
|
22248
|
+
const raw = fs32.readFileSync(filePath, "utf8");
|
|
21810
22249
|
if (raw.includes("\0")) return null;
|
|
21811
22250
|
if (raw.length <= maxChars) return raw;
|
|
21812
22251
|
return `${raw.slice(0, maxChars)}
|
|
@@ -21818,7 +22257,7 @@ function safeReadFile(filePath, maxChars) {
|
|
|
21818
22257
|
}
|
|
21819
22258
|
function tryReadReadme(cwd2) {
|
|
21820
22259
|
for (const name of ["README.md", "README.MD", "readme.md", "Readme.md"]) {
|
|
21821
|
-
const c = safeReadFile(
|
|
22260
|
+
const c = safeReadFile(path35.join(cwd2, name), LIMITS.readme);
|
|
21822
22261
|
if (c) return `(${name})
|
|
21823
22262
|
${c}`;
|
|
21824
22263
|
}
|
|
@@ -21826,14 +22265,14 @@ ${c}`;
|
|
|
21826
22265
|
}
|
|
21827
22266
|
function tryReadBluMaMd(cwd2) {
|
|
21828
22267
|
const paths2 = [
|
|
21829
|
-
|
|
21830
|
-
|
|
21831
|
-
|
|
22268
|
+
path35.join(cwd2, "BluMa.md"),
|
|
22269
|
+
path35.join(cwd2, "BLUMA.md"),
|
|
22270
|
+
path35.join(cwd2, ".bluma", "BluMa.md")
|
|
21832
22271
|
];
|
|
21833
22272
|
for (const p of paths2) {
|
|
21834
22273
|
const c = safeReadFile(p, LIMITS.blumaMd);
|
|
21835
22274
|
if (c) {
|
|
21836
|
-
const rel =
|
|
22275
|
+
const rel = path35.relative(cwd2, p) || p;
|
|
21837
22276
|
return `(${rel})
|
|
21838
22277
|
${c}`;
|
|
21839
22278
|
}
|
|
@@ -21841,10 +22280,10 @@ ${c}`;
|
|
|
21841
22280
|
return null;
|
|
21842
22281
|
}
|
|
21843
22282
|
function summarizePackageJson(cwd2) {
|
|
21844
|
-
const p =
|
|
22283
|
+
const p = path35.join(cwd2, "package.json");
|
|
21845
22284
|
try {
|
|
21846
|
-
if (!
|
|
21847
|
-
const pkg = JSON.parse(
|
|
22285
|
+
if (!fs32.existsSync(p)) return null;
|
|
22286
|
+
const pkg = JSON.parse(fs32.readFileSync(p, "utf8"));
|
|
21848
22287
|
const scripts = pkg.scripts;
|
|
21849
22288
|
let scriptKeys = "";
|
|
21850
22289
|
if (scripts && typeof scripts === "object" && !Array.isArray(scripts)) {
|
|
@@ -21877,7 +22316,7 @@ function summarizePackageJson(cwd2) {
|
|
|
21877
22316
|
}
|
|
21878
22317
|
function topLevelListing(cwd2) {
|
|
21879
22318
|
try {
|
|
21880
|
-
const names =
|
|
22319
|
+
const names = fs32.readdirSync(cwd2, { withFileTypes: true });
|
|
21881
22320
|
const sorted = [...names].sort((a, b) => a.name.localeCompare(b.name));
|
|
21882
22321
|
const limited = sorted.slice(0, LIMITS.topDirEntries);
|
|
21883
22322
|
const lines = limited.map((d) => `${d.name}${d.isDirectory() ? "/" : ""}`);
|
|
@@ -21935,7 +22374,7 @@ function buildWorkspaceSnapshot(cwd2) {
|
|
|
21935
22374
|
parts.push(pkg);
|
|
21936
22375
|
parts.push("```\n");
|
|
21937
22376
|
}
|
|
21938
|
-
const py = safeReadFile(
|
|
22377
|
+
const py = safeReadFile(path35.join(cwd2, "pyproject.toml"), LIMITS.pyproject);
|
|
21939
22378
|
if (py) {
|
|
21940
22379
|
parts.push("### pyproject.toml (excerpt)\n```toml");
|
|
21941
22380
|
parts.push(py);
|
|
@@ -21953,15 +22392,15 @@ function buildWorkspaceSnapshot(cwd2) {
|
|
|
21953
22392
|
parts.push(bluma);
|
|
21954
22393
|
parts.push("```\n");
|
|
21955
22394
|
}
|
|
21956
|
-
const contrib = safeReadFile(
|
|
22395
|
+
const contrib = safeReadFile(path35.join(cwd2, "CONTRIBUTING.md"), LIMITS.contributing);
|
|
21957
22396
|
if (contrib) {
|
|
21958
22397
|
parts.push("### CONTRIBUTING.md (excerpt)\n```markdown");
|
|
21959
22398
|
parts.push(contrib);
|
|
21960
22399
|
parts.push("```\n");
|
|
21961
22400
|
}
|
|
21962
|
-
const chlog = safeReadFile(
|
|
22401
|
+
const chlog = safeReadFile(path35.join(cwd2, "CHANGELOG.md"), LIMITS.changelog);
|
|
21963
22402
|
if (!chlog) {
|
|
21964
|
-
const alt = safeReadFile(
|
|
22403
|
+
const alt = safeReadFile(path35.join(cwd2, "CHANGES.md"), LIMITS.changelog);
|
|
21965
22404
|
if (alt) {
|
|
21966
22405
|
parts.push("### CHANGES.md (excerpt)\n```markdown");
|
|
21967
22406
|
parts.push(alt);
|
|
@@ -21997,14 +22436,14 @@ function buildWorkspaceSnapshot(cwd2) {
|
|
|
21997
22436
|
} else {
|
|
21998
22437
|
parts.push("### Git\n(not a git work tree, or `git` unavailable)\n");
|
|
21999
22438
|
}
|
|
22000
|
-
const tsconfig = safeReadFile(
|
|
22439
|
+
const tsconfig = safeReadFile(path35.join(cwd2, "tsconfig.json"), LIMITS.tsconfig);
|
|
22001
22440
|
if (tsconfig) {
|
|
22002
22441
|
parts.push("### tsconfig.json (excerpt)\n```json");
|
|
22003
22442
|
parts.push(tsconfig);
|
|
22004
22443
|
parts.push("```\n");
|
|
22005
22444
|
}
|
|
22006
22445
|
for (const name of ["Dockerfile", "dockerfile", "Dockerfile.prod", "Dockerfile.dev"]) {
|
|
22007
|
-
const df = safeReadFile(
|
|
22446
|
+
const df = safeReadFile(path35.join(cwd2, name), LIMITS.dockerfile);
|
|
22008
22447
|
if (df) {
|
|
22009
22448
|
parts.push(`### ${name} (excerpt)
|
|
22010
22449
|
`);
|
|
@@ -22014,7 +22453,7 @@ function buildWorkspaceSnapshot(cwd2) {
|
|
|
22014
22453
|
}
|
|
22015
22454
|
}
|
|
22016
22455
|
for (const name of ["docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"]) {
|
|
22017
|
-
const dc = safeReadFile(
|
|
22456
|
+
const dc = safeReadFile(path35.join(cwd2, name), LIMITS.dockerfile);
|
|
22018
22457
|
if (dc) {
|
|
22019
22458
|
parts.push(`### ${name} (excerpt)
|
|
22020
22459
|
`);
|
|
@@ -22029,12 +22468,12 @@ function buildWorkspaceSnapshot(cwd2) {
|
|
|
22029
22468
|
".circleci/config.yml",
|
|
22030
22469
|
"Jenkinsfile"
|
|
22031
22470
|
]) {
|
|
22032
|
-
const ciFile =
|
|
22033
|
-
if (
|
|
22034
|
-
const st =
|
|
22471
|
+
const ciFile = path35.join(cwd2, ciPath);
|
|
22472
|
+
if (fs32.existsSync(ciFile)) {
|
|
22473
|
+
const st = fs32.statSync(ciFile);
|
|
22035
22474
|
if (st.isDirectory()) {
|
|
22036
22475
|
try {
|
|
22037
|
-
const wfFiles =
|
|
22476
|
+
const wfFiles = fs32.readdirSync(ciFile).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
22038
22477
|
if (wfFiles.length > 0) {
|
|
22039
22478
|
parts.push(`### GitHub Actions workflows
|
|
22040
22479
|
\`${wfFiles.join("`, `")}\`
|
|
@@ -22045,7 +22484,7 @@ function buildWorkspaceSnapshot(cwd2) {
|
|
|
22045
22484
|
} else {
|
|
22046
22485
|
const ci = safeReadFile(ciFile, LIMITS.ciConfig);
|
|
22047
22486
|
if (ci) {
|
|
22048
|
-
parts.push(`### CI config (${
|
|
22487
|
+
parts.push(`### CI config (${path35.basename(ciPath)})
|
|
22049
22488
|
`);
|
|
22050
22489
|
parts.push(ci);
|
|
22051
22490
|
parts.push("\n");
|
|
@@ -22054,8 +22493,8 @@ function buildWorkspaceSnapshot(cwd2) {
|
|
|
22054
22493
|
}
|
|
22055
22494
|
}
|
|
22056
22495
|
for (const depFile of ["requirements.txt", "Pipfile", "poetry.lock", "Gemfile", "go.sum", "Cargo.lock"]) {
|
|
22057
|
-
const depPath =
|
|
22058
|
-
if (
|
|
22496
|
+
const depPath = path35.join(cwd2, depFile);
|
|
22497
|
+
if (fs32.existsSync(depPath)) {
|
|
22059
22498
|
const depContent = safeReadFile(depPath, 1500);
|
|
22060
22499
|
if (depContent) {
|
|
22061
22500
|
parts.push(`### ${depFile} (top entries)
|
|
@@ -22068,10 +22507,10 @@ function buildWorkspaceSnapshot(cwd2) {
|
|
|
22068
22507
|
}
|
|
22069
22508
|
}
|
|
22070
22509
|
for (const envFile of [".env.example", ".env.sample", ".env.template"]) {
|
|
22071
|
-
const envPath =
|
|
22072
|
-
if (
|
|
22510
|
+
const envPath = path35.join(cwd2, envFile);
|
|
22511
|
+
if (fs32.existsSync(envPath)) {
|
|
22073
22512
|
try {
|
|
22074
|
-
const raw =
|
|
22513
|
+
const raw = fs32.readFileSync(envPath, "utf-8");
|
|
22075
22514
|
const keys = raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#")).map((l) => {
|
|
22076
22515
|
const eqIndex = l.indexOf("=");
|
|
22077
22516
|
return eqIndex >= 0 ? l.slice(0, eqIndex).trim() : l.trim();
|
|
@@ -22094,15 +22533,15 @@ init_runtime_config();
|
|
|
22094
22533
|
|
|
22095
22534
|
// src/app/agent/runtime/plugin_registry.ts
|
|
22096
22535
|
init_sandbox_policy();
|
|
22097
|
-
import
|
|
22536
|
+
import fs33 from "fs";
|
|
22098
22537
|
import os22 from "os";
|
|
22099
|
-
import
|
|
22538
|
+
import path36 from "path";
|
|
22100
22539
|
function getProjectPluginsDir() {
|
|
22101
22540
|
const policy = getSandboxPolicy();
|
|
22102
|
-
return
|
|
22541
|
+
return path36.join(policy.workspaceRoot, ".bluma", "plugins");
|
|
22103
22542
|
}
|
|
22104
22543
|
function getGlobalPluginsDir() {
|
|
22105
|
-
return
|
|
22544
|
+
return path36.join(process.env.HOME || os22.homedir(), ".bluma", "plugins");
|
|
22106
22545
|
}
|
|
22107
22546
|
function getPluginDirs() {
|
|
22108
22547
|
return {
|
|
@@ -22111,11 +22550,11 @@ function getPluginDirs() {
|
|
|
22111
22550
|
};
|
|
22112
22551
|
}
|
|
22113
22552
|
function readManifest(manifestPath, fallbackName) {
|
|
22114
|
-
if (!
|
|
22553
|
+
if (!fs33.existsSync(manifestPath)) {
|
|
22115
22554
|
return null;
|
|
22116
22555
|
}
|
|
22117
22556
|
try {
|
|
22118
|
-
const parsed = JSON.parse(
|
|
22557
|
+
const parsed = JSON.parse(fs33.readFileSync(manifestPath, "utf-8"));
|
|
22119
22558
|
return {
|
|
22120
22559
|
name: typeof parsed.name === "string" && parsed.name.trim() ? parsed.name.trim() : fallbackName,
|
|
22121
22560
|
description: typeof parsed.description === "string" ? parsed.description.trim() : void 0,
|
|
@@ -22128,22 +22567,22 @@ function readManifest(manifestPath, fallbackName) {
|
|
|
22128
22567
|
}
|
|
22129
22568
|
function findManifestPath(pluginDir) {
|
|
22130
22569
|
const candidates = [
|
|
22131
|
-
|
|
22132
|
-
|
|
22570
|
+
path36.join(pluginDir, ".codex-plugin", "plugin.json"),
|
|
22571
|
+
path36.join(pluginDir, "plugin.json")
|
|
22133
22572
|
];
|
|
22134
22573
|
for (const candidate of candidates) {
|
|
22135
|
-
if (
|
|
22574
|
+
if (fs33.existsSync(candidate)) {
|
|
22136
22575
|
return candidate;
|
|
22137
22576
|
}
|
|
22138
22577
|
}
|
|
22139
22578
|
return null;
|
|
22140
22579
|
}
|
|
22141
22580
|
function listFromDir(baseDir, source) {
|
|
22142
|
-
if (!
|
|
22581
|
+
if (!fs33.existsSync(baseDir)) {
|
|
22143
22582
|
return [];
|
|
22144
22583
|
}
|
|
22145
|
-
return
|
|
22146
|
-
const pluginDir =
|
|
22584
|
+
return fs33.readdirSync(baseDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).flatMap((entry) => {
|
|
22585
|
+
const pluginDir = path36.join(baseDir, entry.name);
|
|
22147
22586
|
const manifestPath = findManifestPath(pluginDir);
|
|
22148
22587
|
if (!manifestPath) {
|
|
22149
22588
|
return [];
|
|
@@ -22588,8 +23027,8 @@ function buildModelInfoSection(modelId) {
|
|
|
22588
23027
|
|
|
22589
23028
|
// src/app/agent/runtime/hook_registry.ts
|
|
22590
23029
|
init_sandbox_policy();
|
|
22591
|
-
import
|
|
22592
|
-
import
|
|
23030
|
+
import fs34 from "fs";
|
|
23031
|
+
import path37 from "path";
|
|
22593
23032
|
var DEFAULT_STATE = {
|
|
22594
23033
|
enabled: true,
|
|
22595
23034
|
maxEvents: 120,
|
|
@@ -22600,7 +23039,7 @@ var cache3 = null;
|
|
|
22600
23039
|
var cachePath2 = null;
|
|
22601
23040
|
function getStatePath() {
|
|
22602
23041
|
const policy = getSandboxPolicy();
|
|
22603
|
-
return
|
|
23042
|
+
return path37.join(policy.workspaceRoot, ".bluma", "hooks.json");
|
|
22604
23043
|
}
|
|
22605
23044
|
function getHookStatePath() {
|
|
22606
23045
|
return getStatePath();
|
|
@@ -22619,8 +23058,8 @@ function ensureLoaded2() {
|
|
|
22619
23058
|
return cache3;
|
|
22620
23059
|
}
|
|
22621
23060
|
try {
|
|
22622
|
-
if (
|
|
22623
|
-
const parsed = JSON.parse(
|
|
23061
|
+
if (fs34.existsSync(statePath)) {
|
|
23062
|
+
const parsed = JSON.parse(fs34.readFileSync(statePath, "utf-8"));
|
|
22624
23063
|
cache3 = {
|
|
22625
23064
|
enabled: typeof parsed.enabled === "boolean" ? parsed.enabled : DEFAULT_STATE.enabled,
|
|
22626
23065
|
maxEvents: typeof parsed.maxEvents === "number" && Number.isFinite(parsed.maxEvents) && parsed.maxEvents > 0 ? Math.floor(parsed.maxEvents) : DEFAULT_STATE.maxEvents,
|
|
@@ -22646,9 +23085,9 @@ function ensureLoaded2() {
|
|
|
22646
23085
|
}
|
|
22647
23086
|
function persist2(state2) {
|
|
22648
23087
|
const statePath = getStatePath();
|
|
22649
|
-
|
|
23088
|
+
fs34.mkdirSync(path37.dirname(statePath), { recursive: true });
|
|
22650
23089
|
state2.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
22651
|
-
|
|
23090
|
+
fs34.writeFileSync(statePath, JSON.stringify(state2, null, 2), "utf-8");
|
|
22652
23091
|
cache3 = state2;
|
|
22653
23092
|
cachePath2 = statePath;
|
|
22654
23093
|
}
|
|
@@ -22898,18 +23337,18 @@ Usa s\xF3 facts do pedido; campos em falta \u2192 "n\xE3o fornecido" \u2014 nunc
|
|
|
22898
23337
|
}
|
|
22899
23338
|
|
|
22900
23339
|
// src/app/agent/core/prompt/auto_memory.ts
|
|
22901
|
-
import
|
|
22902
|
-
import
|
|
23340
|
+
import fs35 from "fs";
|
|
23341
|
+
import path38 from "path";
|
|
22903
23342
|
import os23 from "os";
|
|
22904
|
-
var AUTO_MEMORY_FILE =
|
|
23343
|
+
var AUTO_MEMORY_FILE = path38.join(os23.homedir(), ".bluma", "auto_memory.md");
|
|
22905
23344
|
var MAX_AUTO_MEMORY_CHARS = 25e3;
|
|
22906
23345
|
var MAX_AUTO_MEMORY_LINES = 200;
|
|
22907
23346
|
function getAutoMemoryForPrompt() {
|
|
22908
23347
|
try {
|
|
22909
|
-
if (!
|
|
23348
|
+
if (!fs35.existsSync(AUTO_MEMORY_FILE)) {
|
|
22910
23349
|
return "";
|
|
22911
23350
|
}
|
|
22912
|
-
let content =
|
|
23351
|
+
let content = fs35.readFileSync(AUTO_MEMORY_FILE, "utf-8");
|
|
22913
23352
|
if (!content.trim()) {
|
|
22914
23353
|
return "";
|
|
22915
23354
|
}
|
|
@@ -22935,6 +23374,103 @@ function isAutoMemoryEnabled() {
|
|
|
22935
23374
|
return true;
|
|
22936
23375
|
}
|
|
22937
23376
|
|
|
23377
|
+
// src/app/agent/core/prompt/factorai_sh_prompt.ts
|
|
23378
|
+
var FACTORAI_SH_SECTION = `
|
|
23379
|
+
<factorai_sh_workflow>
|
|
23380
|
+
## FactorAI.sh \u2014 Next.js hosting (mandatory workflow)
|
|
23381
|
+
|
|
23382
|
+
**When to load full checklist:** \`load_skill("factorai-sh")\` before any create/deploy/edit/redeploy task.
|
|
23383
|
+
|
|
23384
|
+
### Backend URL (already in your environment)
|
|
23385
|
+
| Variable | Used by |
|
|
23386
|
+
|----------|---------|
|
|
23387
|
+
| \`FACTORAI_BASE_URL\` | \`factorai.sh.get_app_status\`, \`factorai.sh.apply_app_changes\`, \`factorai.sh.redeploy_app\` |
|
|
23388
|
+
| \`SEVERINO_URL\` | \`factorai.sh.deploy_app\` (ZIP upload to \`/api/v1/deploy\`) |
|
|
23389
|
+
| \`FACTORAI_API_KEY\` / \`SEVERINO_API_KEY\` | Optional Bearer on write endpoints |
|
|
23390
|
+
|
|
23391
|
+
The orchestrator configures these \u2014 **use the values already set**. Do not invent hosts or assume prod vs dev; tools call whatever base URL the environment provides.
|
|
23392
|
+
|
|
23393
|
+
### Source of truth: \`factorai.sh.json\`
|
|
23394
|
+
- Written/updated by \`factorai.sh.deploy_app\` and FactorAI tools \u2014 **read with normal file tools**, never a separate manifest tool.
|
|
23395
|
+
- Before any edit/redeploy on an **existing** app: read \`factorai.sh.json\` \u2192 use \`appContext.appId\` (UUID).
|
|
23396
|
+
- Never invent \`appId\` or duplicate state outside this file.
|
|
23397
|
+
|
|
23398
|
+
### Tools (sandbox-only)
|
|
23399
|
+
| Tool | When |
|
|
23400
|
+
|------|------|
|
|
23401
|
+
| \`factorai.sh.create_next_app\` | New Next.js project in workspace |
|
|
23402
|
+
| \`factorai.sh.deploy_app\` | First deploy (ZIP source only) |
|
|
23403
|
+
| \`factorai.sh.apply_app_changes\` | **App already online** \u2014 patch files + rebuild (Vercel-like) |
|
|
23404
|
+
| \`factorai.sh.get_app_status\` | Poll build status after deploy/apply |
|
|
23405
|
+
| \`factorai.sh.redeploy_app\` | Rebuild current sources without file patches |
|
|
23406
|
+
|
|
23407
|
+
---
|
|
23408
|
+
|
|
23409
|
+
## Flow A \u2014 New app (no \`factorai.sh.json\` yet)
|
|
23410
|
+
|
|
23411
|
+
1. \`factorai.sh.create_next_app({ name, template: "full" })\` \u2014 prefer **full** for deploy (App Router + \`app/\` + shadcn base). Minimal also includes \`app/\` but full is safer for UI tasks.
|
|
23412
|
+
2. Customize with \`edit_tool\` / \`file_write\` / \`shell_command\` in the project directory (e.g. \`./{name}/app/page.tsx\`).
|
|
23413
|
+
3. Optional: \`npx tsc --noEmit\` to catch TS errors \u2014 **avoid** \`npm run build\` before deploy (creates \`.next/\` and inflates the ZIP).
|
|
23414
|
+
4. \`factorai.sh.deploy_app({ projectDir: "./{name}", name })\` \u2014 tool excludes \`node_modules\`, \`.next\`, \`.env*\` automatically. **Never** manual \`zip\` including \`node_modules\` or \`.next\`.
|
|
23415
|
+
5. Persist returned IDs into \`factorai.sh.json\` (tool does this when possible). Report live URL in \`message(result)\` via \`factor-sh-url-app\` (from manifest \`appUrl\`, absolute URL \u2014 prefix with \`SEVERINO_URL\` only when manifest has a path).
|
|
23416
|
+
|
|
23417
|
+
---
|
|
23418
|
+
|
|
23419
|
+
## Session scope (mandatory)
|
|
23420
|
+
|
|
23421
|
+
- App state lives **only** in **this** sandbox workspace (\`factorai.sh.json\` + project dir for this \`session_id\`).
|
|
23422
|
+
- **Never** assume an app from another chat/session \u2014 no cross-session \`appId\` guessing.
|
|
23423
|
+
- For edits: read \`factorai.sh.json\` in the **current** cwd first; if missing, the user must deploy/create the app **in this same session** before Flow B.
|
|
23424
|
+
|
|
23425
|
+
## Flow B \u2014 App already deployed in this session (Vercel-style edit + rebuild)
|
|
23426
|
+
|
|
23427
|
+
**Do NOT** re-run \`deploy_app\` for small edits. **Do NOT** only \`file_write\` locally and stop \u2014 that does not update the running host.
|
|
23428
|
+
|
|
23429
|
+
1. Read \`factorai.sh.json\` in this workspace \u2192 \`appId\`.
|
|
23430
|
+
2. After \`edit_tool\` / \`file_write\`, **read the full file** you changed (\`read_file_lines\` or read entire file).
|
|
23431
|
+
3. \`factorai.sh.apply_app_changes({ appId, files: [{ path: "app/page.tsx", content: "<ENTIRE file contents>" }, ...], deploy: true })\`
|
|
23432
|
+
- **CRITICAL:** \`content\` must be the **complete** file body. The server **replaces** the whole file \u2014 a footer snippet or JSX fragment **destroys** the page.
|
|
23433
|
+
- One entry per changed path; each \`content\` = full file as on disk after your edit.
|
|
23434
|
+
- \`deploy\` defaults to \`true\`. Paths relative to app root (e.g. \`app/page.tsx\`). Blocked: \`node_modules\`, \`.next\`, \`.git\`.
|
|
23435
|
+
4. Poll until rebuild completes (see Polling below).
|
|
23436
|
+
5. Confirm live page, then \`message(result)\` with \`factor-sh-url-app\` from **this** session's manifest.
|
|
23437
|
+
|
|
23438
|
+
Use \`factorai.sh.redeploy_app({ appId })\` only when sources on the server are already correct but build must run again (no file changes).
|
|
23439
|
+
|
|
23440
|
+
---
|
|
23441
|
+
|
|
23442
|
+
## Polling after deploy / apply_app_changes
|
|
23443
|
+
|
|
23444
|
+
1. \`factorai.sh.get_app_status({ appId })\` or GET \`/api/v1/apps/{appId}\`.
|
|
23445
|
+
2. Expect \`status: "building"\` during \`npm install\` / \`next build\` (~30\u201390s).
|
|
23446
|
+
3. **Do not** report "live" on the first \`ready\` if you have not seen \`building\` since this operation \u2014 stale \`ready\` can appear briefly.
|
|
23447
|
+
4. Wait until \`status\` is \`ready\` (or \`running\` / \`live\`) **after** a \`building\` phase, then verify the public route returns HTML (not \`Service Unavailable\`).
|
|
23448
|
+
5. On \`failed\` / \`error\`: read server/build logs if available; fix TS/scaffold issues; retry \`apply_app_changes\` or \`redeploy_app\`.
|
|
23449
|
+
|
|
23450
|
+
---
|
|
23451
|
+
|
|
23452
|
+
## Deploy constraints (first deploy)
|
|
23453
|
+
|
|
23454
|
+
- \`next.config.js\` must keep \`output: 'standalone'\` (included in scaffold).
|
|
23455
|
+
- ZIP max ~50MB \u2014 source only; Severino runs \`npm install\` + \`next build\` on the server.
|
|
23456
|
+
- shadcn components in scaffold must compile (no missing Radix imports).
|
|
23457
|
+
|
|
23458
|
+
---
|
|
23459
|
+
|
|
23460
|
+
## Reporting to orchestrator (Severino)
|
|
23461
|
+
|
|
23462
|
+
- Progress: \`message({ message_type: "info", ... })\` every few steps.
|
|
23463
|
+
- Done: \`message({ message_type: "result", content: "...", attachments: [...], "factor-sh-url-app": "<live url>" })\` \u2014 **only \`result\` ends the worker turn**.
|
|
23464
|
+
- **Mandatory for Severino:** on every \`message(result)\` after deploy/redeploy, set \`"factor-sh-url-app"\` to the absolute live app URL from \`factorai.sh.json\` (\`appContext.appUrl\` + \`SEVERINO_URL\` when needed). Auto-filled if omitted; verify after polling \`ready\`.
|
|
23465
|
+
- File deliverables still use \`attachments[]\` under \`.bluma/artifacts/\` \u2014 separate from the app URL field.
|
|
23466
|
+
</factorai_sh_workflow>
|
|
23467
|
+
`;
|
|
23468
|
+
function isFactorAiShPromptEnabled() {
|
|
23469
|
+
const isSandbox = process.env.BLUMA_SANDBOX === "true";
|
|
23470
|
+
const baseUrl = (process.env.FACTORAI_BASE_URL || process.env.FACTORAI_URL || "").trim();
|
|
23471
|
+
return isSandbox && Boolean(baseUrl);
|
|
23472
|
+
}
|
|
23473
|
+
|
|
22938
23474
|
// src/app/agent/core/prompt/prompt_builder.ts
|
|
22939
23475
|
function getNodeVersion() {
|
|
22940
23476
|
return process.version;
|
|
@@ -22966,17 +23502,17 @@ function getGitBranch(dir) {
|
|
|
22966
23502
|
}
|
|
22967
23503
|
}
|
|
22968
23504
|
function getPackageManager(dir) {
|
|
22969
|
-
if (
|
|
22970
|
-
if (
|
|
22971
|
-
if (
|
|
22972
|
-
if (
|
|
23505
|
+
if (fs36.existsSync(path39.join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
23506
|
+
if (fs36.existsSync(path39.join(dir, "yarn.lock"))) return "yarn";
|
|
23507
|
+
if (fs36.existsSync(path39.join(dir, "bun.lockb"))) return "bun";
|
|
23508
|
+
if (fs36.existsSync(path39.join(dir, "package-lock.json"))) return "npm";
|
|
22973
23509
|
return "unknown";
|
|
22974
23510
|
}
|
|
22975
23511
|
function getProjectType(dir) {
|
|
22976
23512
|
try {
|
|
22977
|
-
const files =
|
|
23513
|
+
const files = fs36.readdirSync(dir);
|
|
22978
23514
|
if (files.includes("package.json")) {
|
|
22979
|
-
const pkg = JSON.parse(
|
|
23515
|
+
const pkg = JSON.parse(fs36.readFileSync(path39.join(dir, "package.json"), "utf-8"));
|
|
22980
23516
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
22981
23517
|
if (deps.next) return "Next.js";
|
|
22982
23518
|
if (deps.react) return "React";
|
|
@@ -22996,9 +23532,9 @@ function getProjectType(dir) {
|
|
|
22996
23532
|
}
|
|
22997
23533
|
function getTestFramework(dir) {
|
|
22998
23534
|
try {
|
|
22999
|
-
const pkgPath =
|
|
23000
|
-
if (
|
|
23001
|
-
const pkg = JSON.parse(
|
|
23535
|
+
const pkgPath = path39.join(dir, "package.json");
|
|
23536
|
+
if (fs36.existsSync(pkgPath)) {
|
|
23537
|
+
const pkg = JSON.parse(fs36.readFileSync(pkgPath, "utf-8"));
|
|
23002
23538
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
23003
23539
|
if (deps.jest) return "jest";
|
|
23004
23540
|
if (deps.vitest) return "vitest";
|
|
@@ -23007,7 +23543,7 @@ function getTestFramework(dir) {
|
|
|
23007
23543
|
if (deps["@playwright/test"]) return "playwright";
|
|
23008
23544
|
if (deps.cypress) return "cypress";
|
|
23009
23545
|
}
|
|
23010
|
-
if (
|
|
23546
|
+
if (fs36.existsSync(path39.join(dir, "pytest.ini")) || fs36.existsSync(path39.join(dir, "conftest.py"))) return "pytest";
|
|
23011
23547
|
return "unknown";
|
|
23012
23548
|
} catch {
|
|
23013
23549
|
return "unknown";
|
|
@@ -23015,9 +23551,9 @@ function getTestFramework(dir) {
|
|
|
23015
23551
|
}
|
|
23016
23552
|
function getTestCommand(dir) {
|
|
23017
23553
|
try {
|
|
23018
|
-
const pkgPath =
|
|
23019
|
-
if (
|
|
23020
|
-
const pkg = JSON.parse(
|
|
23554
|
+
const pkgPath = path39.join(dir, "package.json");
|
|
23555
|
+
if (fs36.existsSync(pkgPath)) {
|
|
23556
|
+
const pkg = JSON.parse(fs36.readFileSync(pkgPath, "utf-8"));
|
|
23021
23557
|
if (pkg.scripts?.test) return "npm test";
|
|
23022
23558
|
if (pkg.scripts?.["test:unit"]) return "npm run test:unit";
|
|
23023
23559
|
}
|
|
@@ -23032,8 +23568,8 @@ function getTestCommand(dir) {
|
|
|
23032
23568
|
}
|
|
23033
23569
|
function isGitRepo(dir) {
|
|
23034
23570
|
try {
|
|
23035
|
-
const p =
|
|
23036
|
-
return
|
|
23571
|
+
const p = path39.join(dir, ".git");
|
|
23572
|
+
return fs36.existsSync(p) && fs36.lstatSync(p).isDirectory();
|
|
23037
23573
|
} catch {
|
|
23038
23574
|
return false;
|
|
23039
23575
|
}
|
|
@@ -23234,7 +23770,8 @@ Timeouts mean the orchestrator should raise the limit \u2014 not that the sandbo
|
|
|
23234
23770
|
- **Step 2:** \`message(result)\` with those paths in \`attachments[]\`; use \`content\` for a short summary only.
|
|
23235
23771
|
- Never create a top-level \`./artifacts/\` folder \u2014 it is auto-remapped to \`.bluma/artifacts/\`.
|
|
23236
23772
|
- **Never invent URLs** or paths outside \`.bluma/artifacts/\` in \`attachments[]\`.
|
|
23237
|
-
-
|
|
23773
|
+
- FactorAI.sh live app URL: on \`message(result)\` after deploy, set \`"factor-sh-url-app"\` (absolute URL). File paths still use \`attachments[]\` only.
|
|
23774
|
+
- Next.js hosting on FactorAI.sh: \`load_skill("factorai-sh")\` and follow \`<factorai_sh_workflow>\` when present.
|
|
23238
23775
|
- **PDFs for mixed audiences:** load skill \`pdf\`; use \`create_report.py --from-json\` with \`audience: "mixed"\` \u2014 rigor in tables, \`plain_language\` callouts per section for non-specialists.
|
|
23239
23776
|
- Remove temp files and generator scripts before finishing.
|
|
23240
23777
|
</sandbox_context>
|
|
@@ -23288,12 +23825,18 @@ async function getUnifiedSystemPrompt(availableSkills, options) {
|
|
|
23288
23825
|
|
|
23289
23826
|
${SUBJECT_MACHINE_MODEL_XML}`;
|
|
23290
23827
|
prompt += interpolate(SANDBOX_SECTION);
|
|
23828
|
+
if (isFactorAiShPromptEnabled()) {
|
|
23829
|
+
prompt += `
|
|
23830
|
+
|
|
23831
|
+
${FACTORAI_SH_SECTION}`;
|
|
23832
|
+
}
|
|
23291
23833
|
const appContext = loadFactorAiAppContext();
|
|
23292
23834
|
if (appContext) {
|
|
23293
23835
|
prompt += `
|
|
23294
23836
|
|
|
23295
|
-
<factorai_app_context>
|
|
23837
|
+
<factorai_app_context active="true">
|
|
23296
23838
|
${formatFactorAiAppContextSummary(appContext)}
|
|
23839
|
+
Read factorai.sh.json before apply_app_changes or redeploy. Prefer apply_app_changes for edits to an already deployed app.
|
|
23297
23840
|
</factorai_app_context>`;
|
|
23298
23841
|
}
|
|
23299
23842
|
}
|
|
@@ -23655,13 +24198,17 @@ var HistoryCompressor = class {
|
|
|
23655
24198
|
*/
|
|
23656
24199
|
async buildContextWindow(fullHistory, llmService, userContext, options) {
|
|
23657
24200
|
if (!fullHistory.length) {
|
|
23658
|
-
return { messages: [], prunedHistory: null };
|
|
24201
|
+
return { messages: [], prunedHistory: null, archivedMessages: [] };
|
|
23659
24202
|
}
|
|
23660
|
-
const
|
|
23661
|
-
|
|
23662
|
-
|
|
23663
|
-
|
|
24203
|
+
const budgetBreakdown = computeEffectiveInputBudget(
|
|
24204
|
+
options?.tokenBudget ?? DEFAULT_CONTEXT_INPUT_BUDGET,
|
|
24205
|
+
options?.toolDefinitions ?? [],
|
|
24206
|
+
{
|
|
24207
|
+
outputReserveTokens: options?.outputReserveTokens,
|
|
24208
|
+
protocolOverheadTokens: options?.protocolOverheadTokens
|
|
24209
|
+
}
|
|
23664
24210
|
);
|
|
24211
|
+
const tokenBudget = budgetBreakdown.effectiveBudget;
|
|
23665
24212
|
const compressThreshold = options?.compressThreshold ?? COMPRESS_THRESHOLD;
|
|
23666
24213
|
const keepRecentTurns = options?.keepRecentTurns ?? KEEP_RECENT_TURNS;
|
|
23667
24214
|
const sanitized = sanitizeConversationForProvider(fullHistory);
|
|
@@ -23692,6 +24239,7 @@ var HistoryCompressor = class {
|
|
|
23692
24239
|
});
|
|
23693
24240
|
let compressionHappened = false;
|
|
23694
24241
|
let compressionFailed = false;
|
|
24242
|
+
const archivedMessages = [];
|
|
23695
24243
|
while (tokens >= thresholdTokens && pendingSlices.length > 0 && !compressionFailed) {
|
|
23696
24244
|
try {
|
|
23697
24245
|
contextEventBus.emit("context:compression_progress", {
|
|
@@ -23701,6 +24249,7 @@ var HistoryCompressor = class {
|
|
|
23701
24249
|
tokenCount: tokens,
|
|
23702
24250
|
tokenBudget
|
|
23703
24251
|
});
|
|
24252
|
+
archivedMessages.push(...pendingFlat);
|
|
23704
24253
|
this.anchor = await this.compressWithTimeout(
|
|
23705
24254
|
pendingFlat,
|
|
23706
24255
|
this.anchor,
|
|
@@ -23718,6 +24267,7 @@ var HistoryCompressor = class {
|
|
|
23718
24267
|
"[HistoryCompressor] Compression failed/timeout, dropping old turns:",
|
|
23719
24268
|
compressError?.message
|
|
23720
24269
|
);
|
|
24270
|
+
archivedMessages.push(...pendingFlat);
|
|
23721
24271
|
pendingSlices = [];
|
|
23722
24272
|
pendingFlat = [];
|
|
23723
24273
|
sliceCount = recentStart;
|
|
@@ -23754,7 +24304,11 @@ var HistoryCompressor = class {
|
|
|
23754
24304
|
} else {
|
|
23755
24305
|
this.compressedSliceCount = sliceCount;
|
|
23756
24306
|
}
|
|
23757
|
-
return {
|
|
24307
|
+
return {
|
|
24308
|
+
messages,
|
|
24309
|
+
prunedHistory,
|
|
24310
|
+
archivedMessages: compressionHappened ? archivedMessages : []
|
|
24311
|
+
};
|
|
23758
24312
|
}
|
|
23759
24313
|
// ── Private helpers ──────────────────────────────────────────────────────────
|
|
23760
24314
|
/** Assembles system + anchor + pending + recent into the final message array. */
|
|
@@ -24436,9 +24990,9 @@ var LLMService = class {
|
|
|
24436
24990
|
};
|
|
24437
24991
|
|
|
24438
24992
|
// src/app/agent/utils/user_message_images.ts
|
|
24439
|
-
import
|
|
24993
|
+
import fs37 from "fs";
|
|
24440
24994
|
import os26 from "os";
|
|
24441
|
-
import
|
|
24995
|
+
import path40 from "path";
|
|
24442
24996
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
24443
24997
|
var IMAGE_EXT = /\.(png|jpe?g|gif|webp|bmp)$/i;
|
|
24444
24998
|
var MAX_IMAGE_BYTES = 4 * 1024 * 1024;
|
|
@@ -24454,22 +25008,22 @@ var MIME = {
|
|
|
24454
25008
|
function expandUserPath(p) {
|
|
24455
25009
|
const t = p.trim();
|
|
24456
25010
|
if (t.startsWith("~")) {
|
|
24457
|
-
return
|
|
25011
|
+
return path40.join(os26.homedir(), t.slice(1).replace(/^\//, ""));
|
|
24458
25012
|
}
|
|
24459
25013
|
return t;
|
|
24460
25014
|
}
|
|
24461
25015
|
function isPathAllowed(absResolved, cwd2) {
|
|
24462
|
-
const resolved =
|
|
24463
|
-
const cwdR =
|
|
24464
|
-
const homeR =
|
|
24465
|
-
const tmpR =
|
|
24466
|
-
const underCwd = resolved === cwdR || resolved.startsWith(cwdR +
|
|
24467
|
-
const underHome = resolved === homeR || resolved.startsWith(homeR +
|
|
24468
|
-
const underTmp = resolved === tmpR || resolved.startsWith(tmpR +
|
|
25016
|
+
const resolved = path40.normalize(path40.resolve(absResolved));
|
|
25017
|
+
const cwdR = path40.normalize(path40.resolve(cwd2));
|
|
25018
|
+
const homeR = path40.normalize(path40.resolve(os26.homedir()));
|
|
25019
|
+
const tmpR = path40.normalize(path40.resolve(os26.tmpdir()));
|
|
25020
|
+
const underCwd = resolved === cwdR || resolved.startsWith(cwdR + path40.sep);
|
|
25021
|
+
const underHome = resolved === homeR || resolved.startsWith(homeR + path40.sep);
|
|
25022
|
+
const underTmp = resolved === tmpR || resolved.startsWith(tmpR + path40.sep);
|
|
24469
25023
|
return underCwd || underHome || underTmp;
|
|
24470
25024
|
}
|
|
24471
25025
|
function mimeFor(abs) {
|
|
24472
|
-
const ext =
|
|
25026
|
+
const ext = path40.extname(abs).toLowerCase();
|
|
24473
25027
|
return MIME[ext] || "application/octet-stream";
|
|
24474
25028
|
}
|
|
24475
25029
|
var IMAGE_EXT_SRC = String.raw`(?:png|jpe?g|gif|webp|bmp)`;
|
|
@@ -24513,10 +25067,10 @@ function collectImagePathStrings(raw) {
|
|
|
24513
25067
|
}
|
|
24514
25068
|
function resolveImagePath(candidate, cwd2) {
|
|
24515
25069
|
const expanded = expandUserPath(candidate);
|
|
24516
|
-
const abs =
|
|
25070
|
+
const abs = path40.isAbsolute(expanded) ? path40.normalize(expanded) : path40.normalize(path40.resolve(cwd2, expanded));
|
|
24517
25071
|
if (!isPathAllowed(abs, cwd2)) return null;
|
|
24518
25072
|
try {
|
|
24519
|
-
if (!
|
|
25073
|
+
if (!fs37.existsSync(abs) || !fs37.statSync(abs).isFile()) return null;
|
|
24520
25074
|
} catch {
|
|
24521
25075
|
return null;
|
|
24522
25076
|
}
|
|
@@ -24536,11 +25090,11 @@ function trySingleLineFileUriOrBareImagePath(line, cwd2) {
|
|
|
24536
25090
|
if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) {
|
|
24537
25091
|
s = s.slice(1, -1).trim();
|
|
24538
25092
|
}
|
|
24539
|
-
if (!IMAGE_EXT.test(
|
|
25093
|
+
if (!IMAGE_EXT.test(path40.extname(s))) return null;
|
|
24540
25094
|
const abs = resolveImagePath(s, cwd2);
|
|
24541
25095
|
if (!abs) return null;
|
|
24542
25096
|
try {
|
|
24543
|
-
const st =
|
|
25097
|
+
const st = fs37.statSync(abs);
|
|
24544
25098
|
if (!st.isFile() || st.size > MAX_IMAGE_BYTES) {
|
|
24545
25099
|
return null;
|
|
24546
25100
|
}
|
|
@@ -24575,7 +25129,7 @@ function tryPasteChunkAsSingleImagePath(chunk, cwd2) {
|
|
|
24575
25129
|
return null;
|
|
24576
25130
|
}
|
|
24577
25131
|
try {
|
|
24578
|
-
const st =
|
|
25132
|
+
const st = fs37.statSync(abs);
|
|
24579
25133
|
if (!st.isFile() || st.size > MAX_IMAGE_BYTES) {
|
|
24580
25134
|
return null;
|
|
24581
25135
|
}
|
|
@@ -24604,7 +25158,7 @@ function buildUserMessageContent(raw, cwd2) {
|
|
|
24604
25158
|
const abs = resolveImagePath(c, cwd2);
|
|
24605
25159
|
if (!abs) continue;
|
|
24606
25160
|
try {
|
|
24607
|
-
const st =
|
|
25161
|
+
const st = fs37.statSync(abs);
|
|
24608
25162
|
if (st.size > MAX_IMAGE_BYTES) continue;
|
|
24609
25163
|
} catch {
|
|
24610
25164
|
continue;
|
|
@@ -24621,7 +25175,7 @@ function buildUserMessageContent(raw, cwd2) {
|
|
|
24621
25175
|
}
|
|
24622
25176
|
const parts = [{ type: "text", text: textPart }];
|
|
24623
25177
|
for (const abs of resolvedAbs) {
|
|
24624
|
-
const buf =
|
|
25178
|
+
const buf = fs37.readFileSync(abs);
|
|
24625
25179
|
const b64 = buf.toString("base64");
|
|
24626
25180
|
const mime = mimeFor(abs);
|
|
24627
25181
|
parts.push({
|
|
@@ -24639,7 +25193,7 @@ function buildUserMessageContent(raw, cwd2) {
|
|
|
24639
25193
|
init_sandbox_policy();
|
|
24640
25194
|
init_runtime_config();
|
|
24641
25195
|
init_permission_rules();
|
|
24642
|
-
import
|
|
25196
|
+
import path41 from "path";
|
|
24643
25197
|
var LOCAL_EDIT_TOOL_NAMES = /* @__PURE__ */ new Set(["edit_tool", "file_write", "notebook_edit"]);
|
|
24644
25198
|
function getToolPermissionLayer(metadata) {
|
|
24645
25199
|
if (metadata.riskLevel === "safe") return "read";
|
|
@@ -24654,11 +25208,11 @@ function checkFilePermissionRules(toolName, filePath, policy) {
|
|
|
24654
25208
|
if (!filePath) {
|
|
24655
25209
|
return { allowed: false, reason: "No file path provided for permission check." };
|
|
24656
25210
|
}
|
|
24657
|
-
const resolvedPath =
|
|
25211
|
+
const resolvedPath = path41.resolve(filePath);
|
|
24658
25212
|
if (!isPathInsideWorkspace(resolvedPath, policy)) {
|
|
24659
25213
|
return { allowed: false, reason: `File path "${filePath}" is outside workspace root.` };
|
|
24660
25214
|
}
|
|
24661
|
-
const relativePath =
|
|
25215
|
+
const relativePath = path41.relative(policy.workspaceRoot, resolvedPath);
|
|
24662
25216
|
const toolPattern = `${toolName}(${relativePath})`;
|
|
24663
25217
|
const ruleDecision = permissionRulesEngine.checkPermission(toolPattern, { filepath: filePath });
|
|
24664
25218
|
if (ruleDecision === "deny") {
|
|
@@ -24667,7 +25221,7 @@ function checkFilePermissionRules(toolName, filePath, policy) {
|
|
|
24667
25221
|
if (ruleDecision === "allow") {
|
|
24668
25222
|
return { allowed: true, reason: `File "${filePath}" allowed by permission rules.` };
|
|
24669
25223
|
}
|
|
24670
|
-
const dirPath =
|
|
25224
|
+
const dirPath = path41.dirname(relativePath);
|
|
24671
25225
|
const dirPattern = `${toolName}(${dirPath}/**)`;
|
|
24672
25226
|
const dirRuleDecision = permissionRulesEngine.checkPermission(dirPattern, { filepath: filePath });
|
|
24673
25227
|
if (dirRuleDecision === "allow") {
|
|
@@ -24884,11 +25438,11 @@ function effectiveToolAutoApprove(toolCall, sessionId, options) {
|
|
|
24884
25438
|
}
|
|
24885
25439
|
|
|
24886
25440
|
// src/app/agent/tools/CodingMemoryTool/CodingMemoryConsolidate.ts
|
|
24887
|
-
import * as
|
|
24888
|
-
import * as
|
|
25441
|
+
import * as fs38 from "fs";
|
|
25442
|
+
import * as path42 from "path";
|
|
24889
25443
|
import os27 from "os";
|
|
24890
25444
|
function memoryPath2() {
|
|
24891
|
-
return
|
|
25445
|
+
return path42.join(process.env.HOME || os27.homedir(), ".bluma", "coding_memory.json");
|
|
24892
25446
|
}
|
|
24893
25447
|
function normalizeNote2(note) {
|
|
24894
25448
|
return note.trim().toLowerCase().replace(/\s+/g, " ");
|
|
@@ -24898,18 +25452,18 @@ function uniqTags(a, b) {
|
|
|
24898
25452
|
}
|
|
24899
25453
|
function consolidateCodingMemoryFile() {
|
|
24900
25454
|
const p = memoryPath2();
|
|
24901
|
-
if (!
|
|
25455
|
+
if (!fs38.existsSync(p)) {
|
|
24902
25456
|
return { success: true, removedDuplicates: 0, message: "no coding_memory.json" };
|
|
24903
25457
|
}
|
|
24904
25458
|
const bak = `${p}.bak`;
|
|
24905
25459
|
try {
|
|
24906
|
-
|
|
25460
|
+
fs38.copyFileSync(p, bak);
|
|
24907
25461
|
} catch (e) {
|
|
24908
25462
|
return { success: false, removedDuplicates: 0, message: `backup failed: ${e.message}` };
|
|
24909
25463
|
}
|
|
24910
25464
|
let data;
|
|
24911
25465
|
try {
|
|
24912
|
-
data = JSON.parse(
|
|
25466
|
+
data = JSON.parse(fs38.readFileSync(p, "utf-8"));
|
|
24913
25467
|
} catch (e) {
|
|
24914
25468
|
return { success: false, removedDuplicates: 0, message: `invalid json: ${e.message}` };
|
|
24915
25469
|
}
|
|
@@ -24944,7 +25498,7 @@ function consolidateCodingMemoryFile() {
|
|
|
24944
25498
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
24945
25499
|
};
|
|
24946
25500
|
try {
|
|
24947
|
-
|
|
25501
|
+
fs38.writeFileSync(p, JSON.stringify(out, null, 2), "utf-8");
|
|
24948
25502
|
} catch (e) {
|
|
24949
25503
|
return { success: false, removedDuplicates: 0, message: `write failed: ${e.message}` };
|
|
24950
25504
|
}
|
|
@@ -25448,6 +26002,22 @@ var BluMaToolRunner = class {
|
|
|
25448
26002
|
}
|
|
25449
26003
|
};
|
|
25450
26004
|
|
|
26005
|
+
// src/app/agent/session_manager/session_archive.ts
|
|
26006
|
+
import path43 from "path";
|
|
26007
|
+
import { promises as fs39 } from "fs";
|
|
26008
|
+
async function archivePrunedConversationMessages(sessionId, messages) {
|
|
26009
|
+
if (!sessionId || messages.length === 0) {
|
|
26010
|
+
return null;
|
|
26011
|
+
}
|
|
26012
|
+
const appDir = getPreferredAppDir();
|
|
26013
|
+
const dir = path43.join(appDir, "sessions", "archive", sessionId);
|
|
26014
|
+
await fs39.mkdir(dir, { recursive: true });
|
|
26015
|
+
const archiveFile = path43.join(dir, `${Date.now()}.jsonl`);
|
|
26016
|
+
const lines = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
26017
|
+
await fs39.appendFile(archiveFile, lines, "utf-8");
|
|
26018
|
+
return archiveFile;
|
|
26019
|
+
}
|
|
26020
|
+
|
|
25451
26021
|
// src/app/agent/core/llm/llm_errors.ts
|
|
25452
26022
|
function isContextWindowValidationError(error) {
|
|
25453
26023
|
const rawMessage = error instanceof Error ? error.message : typeof error === "string" ? error : "";
|
|
@@ -25792,7 +26362,11 @@ var BluMaTurnCoordinator = class {
|
|
|
25792
26362
|
for (let attempt = 0; attempt < tokenBudgets.length; attempt += 1) {
|
|
25793
26363
|
const tokenBudget = tokenBudgets[attempt];
|
|
25794
26364
|
try {
|
|
25795
|
-
const {
|
|
26365
|
+
const {
|
|
26366
|
+
messages: contextWindow,
|
|
26367
|
+
prunedHistory,
|
|
26368
|
+
archivedMessages
|
|
26369
|
+
} = await this.deps.compressor.buildContextWindow(
|
|
25796
26370
|
this.deps.history,
|
|
25797
26371
|
this.deps.llm,
|
|
25798
26372
|
this.deps.getLlmUserContext(),
|
|
@@ -25805,13 +26379,19 @@ var BluMaTurnCoordinator = class {
|
|
|
25805
26379
|
history_length: this.deps.history.length,
|
|
25806
26380
|
context_window_length: contextWindow.length,
|
|
25807
26381
|
pruned_history_length: prunedHistory?.length ?? null,
|
|
26382
|
+
archived_messages: archivedMessages.length,
|
|
25808
26383
|
token_budget: tokenBudget,
|
|
25809
26384
|
context_preview: summarizeHistoryForLog(contextWindow, 8)
|
|
25810
26385
|
});
|
|
25811
26386
|
if (prunedHistory) {
|
|
26387
|
+
if (archivedMessages.length > 0) {
|
|
26388
|
+
await archivePrunedConversationMessages(this.deps.sessionId, archivedMessages);
|
|
26389
|
+
}
|
|
25812
26390
|
this.deps.history.splice(0, this.deps.history.length, ...prunedHistory);
|
|
26391
|
+
await this.deps.persistSessionAfterPrune();
|
|
26392
|
+
} else {
|
|
26393
|
+
this.deps.persistSession();
|
|
25813
26394
|
}
|
|
25814
|
-
this.deps.persistSession();
|
|
25815
26395
|
if (typeof llmService.chatCompletionStream === "function") {
|
|
25816
26396
|
await this.handleStreamingResponse(contextWindow);
|
|
25817
26397
|
} else {
|
|
@@ -26229,6 +26809,7 @@ var BluMaAgent = class {
|
|
|
26229
26809
|
getLlmUserContext: () => this.getLlmUserContext(),
|
|
26230
26810
|
persistSession: () => this.persistSession(),
|
|
26231
26811
|
persistSessionSync: () => this.persistSessionSync(),
|
|
26812
|
+
persistSessionAfterPrune: () => this.persistSessionAfterPrune(),
|
|
26232
26813
|
notifyFactorTurnEndIfNeeded: (reason) => this.notifyFactorTurnEndIfNeeded(reason),
|
|
26233
26814
|
emitTurnCompleted: () => this.emitTurnCompleted(),
|
|
26234
26815
|
generateEditPreview: (toolArgs) => this._generateEditPreview(toolArgs),
|
|
@@ -26268,13 +26849,13 @@ var BluMaAgent = class {
|
|
|
26268
26849
|
if (!this.sessionFile) return;
|
|
26269
26850
|
try {
|
|
26270
26851
|
const sessionData = {
|
|
26271
|
-
session_id:
|
|
26852
|
+
session_id: path44.basename(this.sessionFile, ".json"),
|
|
26272
26853
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26273
26854
|
conversation_history: this.history,
|
|
26274
26855
|
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26275
26856
|
...this.compressor.getSnapshot()
|
|
26276
26857
|
};
|
|
26277
|
-
|
|
26858
|
+
fs40.writeFileSync(this.sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
|
|
26278
26859
|
} catch (error) {
|
|
26279
26860
|
console.error("[Bluma] Failed to persist session synchronously:", error);
|
|
26280
26861
|
}
|
|
@@ -26284,6 +26865,11 @@ var BluMaAgent = class {
|
|
|
26284
26865
|
if (!this.sessionFile) return;
|
|
26285
26866
|
void saveSessionHistory(this.sessionFile, this.history, this.getMemorySnapshot());
|
|
26286
26867
|
}
|
|
26868
|
+
/** Gravação imediata após prune (histórico cortado + anchor no JSON de sessão). */
|
|
26869
|
+
async persistSessionAfterPrune() {
|
|
26870
|
+
if (!this.sessionFile) return;
|
|
26871
|
+
await saveSessionHistoryNow(this.sessionFile, this.history, this.getMemorySnapshot());
|
|
26872
|
+
}
|
|
26287
26873
|
recordUiSlashCommand(command, mode = "visible_only") {
|
|
26288
26874
|
const text = String(command ?? "").trim();
|
|
26289
26875
|
if (!text) return;
|
|
@@ -26475,7 +27061,7 @@ var BluMaAgent = class {
|
|
|
26475
27061
|
|
|
26476
27062
|
${editData.error.display}`;
|
|
26477
27063
|
}
|
|
26478
|
-
const filename =
|
|
27064
|
+
const filename = path44.basename(toolArgs.file_path);
|
|
26479
27065
|
return createDiff(filename, editData.currentContent || "", editData.newContent);
|
|
26480
27066
|
} catch (e) {
|
|
26481
27067
|
return `An unexpected error occurred while generating the edit preview: ${e.message}`;
|
|
@@ -27201,16 +27787,16 @@ async function fullCompact(messages, targetTokens, summarizer, llmClient) {
|
|
|
27201
27787
|
}
|
|
27202
27788
|
|
|
27203
27789
|
// src/app/agent/core/memory/session_memory.ts
|
|
27204
|
-
import
|
|
27790
|
+
import fs41 from "fs";
|
|
27205
27791
|
import os30 from "os";
|
|
27206
|
-
import
|
|
27792
|
+
import path45 from "path";
|
|
27207
27793
|
import { v4 as uuidv49 } from "uuid";
|
|
27208
27794
|
var SessionMemoryExtractor = class {
|
|
27209
27795
|
llmClient;
|
|
27210
27796
|
memoryFile;
|
|
27211
27797
|
constructor(options = {}) {
|
|
27212
27798
|
this.llmClient = options.llmClient;
|
|
27213
|
-
this.memoryFile = options.memoryFile ||
|
|
27799
|
+
this.memoryFile = options.memoryFile || path45.join(os30.homedir(), ".bluma", "session_memory.json");
|
|
27214
27800
|
}
|
|
27215
27801
|
/**
|
|
27216
27802
|
* Extract memories from conversation using LLM
|
|
@@ -27267,15 +27853,15 @@ ${messages.slice(-50).map((m) => `${m.role}: ${m.content.slice(0, 500)}`).join("
|
|
|
27267
27853
|
);
|
|
27268
27854
|
unique.sort((a, b) => b.accessCount - a.accessCount);
|
|
27269
27855
|
const trimmed = unique.slice(0, 200);
|
|
27270
|
-
|
|
27856
|
+
fs41.writeFileSync(this.memoryFile, JSON.stringify(trimmed, null, 2));
|
|
27271
27857
|
}
|
|
27272
27858
|
/**
|
|
27273
27859
|
* Load memories from disk
|
|
27274
27860
|
*/
|
|
27275
27861
|
async loadMemories() {
|
|
27276
27862
|
try {
|
|
27277
|
-
if (!
|
|
27278
|
-
const data =
|
|
27863
|
+
if (!fs41.existsSync(this.memoryFile)) return [];
|
|
27864
|
+
const data = fs41.readFileSync(this.memoryFile, "utf-8");
|
|
27279
27865
|
return JSON.parse(data);
|
|
27280
27866
|
} catch {
|
|
27281
27867
|
return [];
|
|
@@ -27819,14 +28405,14 @@ var RouteManager = class {
|
|
|
27819
28405
|
this.subAgents = subAgents;
|
|
27820
28406
|
this.core = core;
|
|
27821
28407
|
}
|
|
27822
|
-
registerRoute(
|
|
27823
|
-
this.routeHandlers.set(
|
|
28408
|
+
registerRoute(path57, handler) {
|
|
28409
|
+
this.routeHandlers.set(path57, handler);
|
|
27824
28410
|
}
|
|
27825
28411
|
async handleRoute(payload) {
|
|
27826
28412
|
const inputText = String(payload.content || "").trim();
|
|
27827
28413
|
const { userContext, options } = payload;
|
|
27828
|
-
for (const [
|
|
27829
|
-
if (inputText ===
|
|
28414
|
+
for (const [path57, handler] of this.routeHandlers) {
|
|
28415
|
+
if (inputText === path57 || inputText.startsWith(`${path57} `)) {
|
|
27830
28416
|
return handler({ content: inputText, userContext });
|
|
27831
28417
|
}
|
|
27832
28418
|
}
|
|
@@ -27835,13 +28421,13 @@ var RouteManager = class {
|
|
|
27835
28421
|
};
|
|
27836
28422
|
|
|
27837
28423
|
// src/app/agent/runtime/plugin_runtime.ts
|
|
27838
|
-
import
|
|
28424
|
+
import path46 from "path";
|
|
27839
28425
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
27840
28426
|
async function loadPluginsAtStartup() {
|
|
27841
28427
|
for (const p of listPlugins()) {
|
|
27842
28428
|
const entry = p.manifest.entry?.trim();
|
|
27843
28429
|
if (!entry) continue;
|
|
27844
|
-
const abs =
|
|
28430
|
+
const abs = path46.resolve(p.root, entry);
|
|
27845
28431
|
try {
|
|
27846
28432
|
const href = pathToFileURL2(abs).href;
|
|
27847
28433
|
const mod = await import(href);
|
|
@@ -27862,7 +28448,7 @@ async function loadPluginsAtStartup() {
|
|
|
27862
28448
|
}
|
|
27863
28449
|
|
|
27864
28450
|
// src/app/agent/agent.ts
|
|
27865
|
-
var globalEnvPath =
|
|
28451
|
+
var globalEnvPath = path47.join(os31.homedir(), ".bluma", ".env");
|
|
27866
28452
|
dotenv.config({ path: globalEnvPath });
|
|
27867
28453
|
var Agent = class {
|
|
27868
28454
|
sessionId;
|
|
@@ -29718,10 +30304,10 @@ function resolveToolPayload(result) {
|
|
|
29718
30304
|
|
|
29719
30305
|
// src/app/ui/components/FilePathLink.tsx
|
|
29720
30306
|
import { pathToFileURL as pathToFileURL3 } from "node:url";
|
|
29721
|
-
import
|
|
30307
|
+
import path49 from "node:path";
|
|
29722
30308
|
|
|
29723
30309
|
// src/app/ui/utils/pathDisplay.ts
|
|
29724
|
-
import
|
|
30310
|
+
import path48 from "node:path";
|
|
29725
30311
|
import os32 from "node:os";
|
|
29726
30312
|
function formatPathForDisplay(pathInput, cwd2 = process.cwd()) {
|
|
29727
30313
|
let s = String(pathInput ?? "").trim();
|
|
@@ -29729,17 +30315,17 @@ function formatPathForDisplay(pathInput, cwd2 = process.cwd()) {
|
|
|
29729
30315
|
s = s.replace(/[/\\]+$/, "");
|
|
29730
30316
|
}
|
|
29731
30317
|
if (!s) return "";
|
|
29732
|
-
const abs =
|
|
29733
|
-
const resolvedCwd =
|
|
29734
|
-
const rel =
|
|
29735
|
-
if (rel === "" || !rel.startsWith("..") && !
|
|
30318
|
+
const abs = path48.isAbsolute(s) ? path48.normalize(s) : path48.resolve(cwd2, s);
|
|
30319
|
+
const resolvedCwd = path48.resolve(cwd2);
|
|
30320
|
+
const rel = path48.relative(resolvedCwd, abs);
|
|
30321
|
+
if (rel === "" || !rel.startsWith("..") && !path48.isAbsolute(rel)) {
|
|
29736
30322
|
return rel === "" ? "." : rel;
|
|
29737
30323
|
}
|
|
29738
|
-
const home =
|
|
29739
|
-
if (abs === home || abs.startsWith(home +
|
|
30324
|
+
const home = path48.normalize(os32.homedir());
|
|
30325
|
+
if (abs === home || abs.startsWith(home + path48.sep)) {
|
|
29740
30326
|
return "~" + abs.slice(home.length);
|
|
29741
30327
|
}
|
|
29742
|
-
return
|
|
30328
|
+
return path48.basename(abs);
|
|
29743
30329
|
}
|
|
29744
30330
|
|
|
29745
30331
|
// src/app/ui/components/FilePathLink.tsx
|
|
@@ -29749,7 +30335,7 @@ function FilePathLink({ filePath, children, cwd: cwd2 = process.cwd(), color })
|
|
|
29749
30335
|
if (!raw) {
|
|
29750
30336
|
return null;
|
|
29751
30337
|
}
|
|
29752
|
-
const abs =
|
|
30338
|
+
const abs = path49.isAbsolute(raw) ? path49.normalize(raw) : path49.resolve(cwd2, raw);
|
|
29753
30339
|
const href = pathToFileURL3(abs).href;
|
|
29754
30340
|
const label = formatPathForDisplay(abs, cwd2);
|
|
29755
30341
|
const text = typeof children === "string" ? children : label;
|
|
@@ -31701,12 +32287,12 @@ function patchToUnifiedDiffText(filePath, patch) {
|
|
|
31701
32287
|
}
|
|
31702
32288
|
|
|
31703
32289
|
// src/app/ui/utils/readEditContext.ts
|
|
31704
|
-
import { promises as
|
|
32290
|
+
import { promises as fs42 } from "fs";
|
|
31705
32291
|
var CHUNK_SIZE = 64 * 1024;
|
|
31706
32292
|
var CONTEXT_LINES2 = 3;
|
|
31707
32293
|
async function openForScan(filePath) {
|
|
31708
32294
|
try {
|
|
31709
|
-
return await
|
|
32295
|
+
return await fs42.open(filePath, "r");
|
|
31710
32296
|
} catch (e) {
|
|
31711
32297
|
if (e.code === "ENOENT") return null;
|
|
31712
32298
|
throw e;
|
|
@@ -32384,13 +32970,13 @@ function EditToolDiffPanel({
|
|
|
32384
32970
|
newString,
|
|
32385
32971
|
replaceAll = false
|
|
32386
32972
|
}) {
|
|
32387
|
-
const
|
|
32973
|
+
const path57 = filePath.trim() || "unknown file";
|
|
32388
32974
|
const hasPreviewArgs = oldString !== void 0 && newString !== void 0;
|
|
32389
32975
|
const hasDiffText = diffText && diffText.trim().length > 0;
|
|
32390
32976
|
return /* @__PURE__ */ jsx43(Box_default, { flexDirection: "column", children: hasPreviewArgs ? /* @__PURE__ */ jsx43(Box_default, { marginTop: 0, children: /* @__PURE__ */ jsx43(
|
|
32391
32977
|
FileEditToolDiff,
|
|
32392
32978
|
{
|
|
32393
|
-
filePath:
|
|
32979
|
+
filePath: path57,
|
|
32394
32980
|
oldString,
|
|
32395
32981
|
newString,
|
|
32396
32982
|
replaceAll,
|
|
@@ -32428,7 +33014,7 @@ function renderToolUseMessage12({ args }) {
|
|
|
32428
33014
|
return /* @__PURE__ */ jsx44(Text, { color: BLUMA_TERMINAL.blue, children: p });
|
|
32429
33015
|
}
|
|
32430
33016
|
function renderToolHeader12({ args }) {
|
|
32431
|
-
const
|
|
33017
|
+
const path57 = args?.file_path ?? ".";
|
|
32432
33018
|
const oldText = typeof args?.old_string === "string" ? args.old_string : "";
|
|
32433
33019
|
const newText = typeof args?.new_string === "string" ? args.new_string : "";
|
|
32434
33020
|
const counts = countLineDiff(oldText, newText);
|
|
@@ -32438,7 +33024,7 @@ function renderToolHeader12({ args }) {
|
|
|
32438
33024
|
action,
|
|
32439
33025
|
/* @__PURE__ */ jsxs27(Text, { dimColor: true, children: [
|
|
32440
33026
|
" ",
|
|
32441
|
-
/* @__PURE__ */ jsx44(FilePathLink, { filePath:
|
|
33027
|
+
/* @__PURE__ */ jsx44(FilePathLink, { filePath: path57, color: BLUMA_TERMINAL.dim })
|
|
32442
33028
|
] })
|
|
32443
33029
|
] }),
|
|
32444
33030
|
/* @__PURE__ */ jsxs27(Text, { dimColor: true, children: [
|
|
@@ -32830,11 +33416,11 @@ function userFacingName13() {
|
|
|
32830
33416
|
}
|
|
32831
33417
|
function renderToolUseMessage14({ args }) {
|
|
32832
33418
|
const q = args?.query ? `"${args.query}"` : "...";
|
|
32833
|
-
const
|
|
33419
|
+
const path57 = args?.path || ".";
|
|
32834
33420
|
return /* @__PURE__ */ jsxs30(Box_default, { flexDirection: "row", flexWrap: "wrap", alignItems: "flex-end", children: [
|
|
32835
33421
|
/* @__PURE__ */ jsx47(Text, { color: BLUMA_TERMINAL.blue, children: q }),
|
|
32836
33422
|
/* @__PURE__ */ jsx47(Text, { color: BLUMA_TERMINAL.dim, children: " " }),
|
|
32837
|
-
/* @__PURE__ */ jsx47(FilePathLink, { filePath:
|
|
33423
|
+
/* @__PURE__ */ jsx47(FilePathLink, { filePath: path57, color: BLUMA_TERMINAL.dim })
|
|
32838
33424
|
] });
|
|
32839
33425
|
}
|
|
32840
33426
|
function renderToolHeader14({ args }) {
|
|
@@ -33356,7 +33942,7 @@ var loadSkillTool = createTool({
|
|
|
33356
33942
|
});
|
|
33357
33943
|
|
|
33358
33944
|
// src/app/agent/tools/FileWriteTool/UI.tsx
|
|
33359
|
-
import
|
|
33945
|
+
import fs43 from "fs";
|
|
33360
33946
|
import { diffLines as diffLines3 } from "diff";
|
|
33361
33947
|
import { jsx as jsx54, jsxs as jsxs37 } from "react/jsx-runtime";
|
|
33362
33948
|
function getFilePath(args) {
|
|
@@ -33371,7 +33957,7 @@ function getFilePath(args) {
|
|
|
33371
33957
|
function readExistingFileText(filePath) {
|
|
33372
33958
|
if (!filePath) return "";
|
|
33373
33959
|
try {
|
|
33374
|
-
return
|
|
33960
|
+
return fs43.readFileSync(filePath, "utf-8");
|
|
33375
33961
|
} catch {
|
|
33376
33962
|
return "";
|
|
33377
33963
|
}
|
|
@@ -36301,8 +36887,8 @@ import {
|
|
|
36301
36887
|
|
|
36302
36888
|
// src/app/ui/hooks/useAtCompletion.ts
|
|
36303
36889
|
import { useEffect as useEffect11, useRef as useRef4, useState as useState13 } from "react";
|
|
36304
|
-
import
|
|
36305
|
-
import
|
|
36890
|
+
import fs44 from "fs";
|
|
36891
|
+
import path50 from "path";
|
|
36306
36892
|
var MAX_RESULTS3 = 50;
|
|
36307
36893
|
var DEFAULT_RECURSIVE_DEPTH = 2;
|
|
36308
36894
|
function listPathSuggestions(baseDir, pattern) {
|
|
@@ -36310,7 +36896,7 @@ function listPathSuggestions(baseDir, pattern) {
|
|
|
36310
36896
|
const patternEndsWithSlash = raw.endsWith("/");
|
|
36311
36897
|
const relDir = raw.replace(/^\/+|\/+$/g, "");
|
|
36312
36898
|
const filterPrefix = patternEndsWithSlash ? "" : relDir.split("/").slice(-1)[0] || "";
|
|
36313
|
-
const listDir =
|
|
36899
|
+
const listDir = path50.resolve(baseDir, relDir || ".");
|
|
36314
36900
|
const results = [];
|
|
36315
36901
|
const IGNORED_DIRS = ["node_modules", ".git", ".venv", "dist", "build"];
|
|
36316
36902
|
const IGNORED_EXTS = [".pyc", ".class", ".o", ".map", ".log", ".tmp"];
|
|
@@ -36327,7 +36913,7 @@ function listPathSuggestions(baseDir, pattern) {
|
|
|
36327
36913
|
}
|
|
36328
36914
|
function pushEntry(entryPath, label, isDir) {
|
|
36329
36915
|
if (results.length >= MAX_RESULTS3) return;
|
|
36330
|
-
const clean = label.split(
|
|
36916
|
+
const clean = label.split(path50.sep).join("/").replace(/[]+/g, "");
|
|
36331
36917
|
results.push({ label: clean + (isDir ? "/" : ""), fullPath: entryPath, isDir });
|
|
36332
36918
|
}
|
|
36333
36919
|
try {
|
|
@@ -36336,11 +36922,11 @@ function listPathSuggestions(baseDir, pattern) {
|
|
|
36336
36922
|
while (queue.length && results.length < MAX_RESULTS3) {
|
|
36337
36923
|
const node = queue.shift();
|
|
36338
36924
|
try {
|
|
36339
|
-
const entries =
|
|
36925
|
+
const entries = fs44.readdirSync(node.dir, { withFileTypes: true });
|
|
36340
36926
|
for (const entry of entries) {
|
|
36341
36927
|
if (isIgnoredName(entry.name)) continue;
|
|
36342
|
-
const entryAbs =
|
|
36343
|
-
const entryRel = node.rel ?
|
|
36928
|
+
const entryAbs = path50.join(node.dir, entry.name);
|
|
36929
|
+
const entryRel = node.rel ? path50.posix.join(node.rel, entry.name) : entry.name;
|
|
36344
36930
|
if (entryRel.split("/").includes("node_modules")) continue;
|
|
36345
36931
|
if (!entry.isDirectory() && isIgnoredFile(entry.name)) continue;
|
|
36346
36932
|
pushEntry(entryAbs, entryRel, entry.isDirectory());
|
|
@@ -36353,13 +36939,13 @@ function listPathSuggestions(baseDir, pattern) {
|
|
|
36353
36939
|
}
|
|
36354
36940
|
}
|
|
36355
36941
|
} else {
|
|
36356
|
-
const entries =
|
|
36942
|
+
const entries = fs44.readdirSync(listDir, { withFileTypes: true });
|
|
36357
36943
|
for (const entry of entries) {
|
|
36358
36944
|
if (filterPrefix && !entry.name.startsWith(filterPrefix)) continue;
|
|
36359
36945
|
if (isIgnoredName(entry.name)) continue;
|
|
36360
36946
|
if (!entry.isDirectory() && isIgnoredFile(entry.name)) continue;
|
|
36361
|
-
const entryAbs =
|
|
36362
|
-
const label = relDir ?
|
|
36947
|
+
const entryAbs = path50.join(listDir, entry.name);
|
|
36948
|
+
const label = relDir ? path50.posix.join(relDir, entry.name) : entry.name;
|
|
36363
36949
|
pushEntry(entryAbs, label, entry.isDirectory());
|
|
36364
36950
|
if (results.length >= MAX_RESULTS3) break;
|
|
36365
36951
|
}
|
|
@@ -36560,15 +37146,15 @@ var SlashSubmenuInlineComponent = ({ menu }) => {
|
|
|
36560
37146
|
var SlashSubmenuInline = memo15(SlashSubmenuInlineComponent);
|
|
36561
37147
|
|
|
36562
37148
|
// src/app/ui/utils/clipboardImage.ts
|
|
36563
|
-
import
|
|
37149
|
+
import fs45 from "fs";
|
|
36564
37150
|
import os33 from "os";
|
|
36565
|
-
import
|
|
37151
|
+
import path51 from "path";
|
|
36566
37152
|
import { spawn as spawn5, execFile as execFileCb, execSync as execSync4 } from "child_process";
|
|
36567
37153
|
import { promisify as promisify2 } from "util";
|
|
36568
37154
|
|
|
36569
37155
|
// src/app/utils/clipboardNative.ts
|
|
36570
37156
|
import { existsSync as existsSync7 } from "fs";
|
|
36571
|
-
import { createRequire } from "module";
|
|
37157
|
+
import { createRequire as createRequire2 } from "module";
|
|
36572
37158
|
import { dirname as dirname4, join as join8 } from "path";
|
|
36573
37159
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
36574
37160
|
var __dirname;
|
|
@@ -36616,7 +37202,7 @@ function getNativeModule() {
|
|
|
36616
37202
|
throw loadError;
|
|
36617
37203
|
}
|
|
36618
37204
|
try {
|
|
36619
|
-
const require2 =
|
|
37205
|
+
const require2 = createRequire2(import.meta.url);
|
|
36620
37206
|
const mod = require2(nativePath);
|
|
36621
37207
|
nativeModule = mod;
|
|
36622
37208
|
return nativeModule;
|
|
@@ -36691,8 +37277,8 @@ function commandOnPath(cmd) {
|
|
|
36691
37277
|
function unixClipboardHelperDirs() {
|
|
36692
37278
|
const h = os33.homedir();
|
|
36693
37279
|
return [
|
|
36694
|
-
|
|
36695
|
-
|
|
37280
|
+
path51.join(h, ".local", "bin"),
|
|
37281
|
+
path51.join(h, "bin"),
|
|
36696
37282
|
"/usr/bin",
|
|
36697
37283
|
"/usr/local/bin",
|
|
36698
37284
|
"/bin",
|
|
@@ -36710,16 +37296,16 @@ function resolveHelperBinary(cmd) {
|
|
|
36710
37296
|
return cmd;
|
|
36711
37297
|
}
|
|
36712
37298
|
for (const dir of unixClipboardHelperDirs()) {
|
|
36713
|
-
const full =
|
|
37299
|
+
const full = path51.join(dir, cmd);
|
|
36714
37300
|
try {
|
|
36715
|
-
|
|
37301
|
+
fs45.accessSync(full, fs45.constants.X_OK);
|
|
36716
37302
|
return full;
|
|
36717
37303
|
} catch {
|
|
36718
37304
|
}
|
|
36719
37305
|
}
|
|
36720
37306
|
for (const dir of unixClipboardHelperDirs()) {
|
|
36721
|
-
const full =
|
|
36722
|
-
if (
|
|
37307
|
+
const full = path51.join(dir, cmd);
|
|
37308
|
+
if (fs45.existsSync(full)) {
|
|
36723
37309
|
return full;
|
|
36724
37310
|
}
|
|
36725
37311
|
}
|
|
@@ -36760,17 +37346,17 @@ function writeBufferIfImage(baseDir, buf) {
|
|
|
36760
37346
|
if (!ext) {
|
|
36761
37347
|
return null;
|
|
36762
37348
|
}
|
|
36763
|
-
const out =
|
|
37349
|
+
const out = path51.join(
|
|
36764
37350
|
baseDir,
|
|
36765
37351
|
`clip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext}`
|
|
36766
37352
|
);
|
|
36767
|
-
|
|
37353
|
+
fs45.writeFileSync(out, buf);
|
|
36768
37354
|
return out;
|
|
36769
37355
|
}
|
|
36770
37356
|
function unlinkQuiet(p) {
|
|
36771
37357
|
try {
|
|
36772
|
-
if (
|
|
36773
|
-
|
|
37358
|
+
if (fs45.existsSync(p)) {
|
|
37359
|
+
fs45.unlinkSync(p);
|
|
36774
37360
|
}
|
|
36775
37361
|
} catch {
|
|
36776
37362
|
}
|
|
@@ -36787,25 +37373,25 @@ async function tryDarwinClipboardy(baseDir) {
|
|
|
36787
37373
|
return null;
|
|
36788
37374
|
}
|
|
36789
37375
|
for (const src of tmpPaths) {
|
|
36790
|
-
if (!src || !
|
|
37376
|
+
if (!src || !fs45.existsSync(src)) {
|
|
36791
37377
|
continue;
|
|
36792
37378
|
}
|
|
36793
37379
|
let st;
|
|
36794
37380
|
try {
|
|
36795
|
-
st =
|
|
37381
|
+
st = fs45.statSync(src);
|
|
36796
37382
|
} catch {
|
|
36797
37383
|
continue;
|
|
36798
37384
|
}
|
|
36799
37385
|
if (st.size < 80 || st.size > CLIPBOARD_MAX_BYTES) {
|
|
36800
37386
|
continue;
|
|
36801
37387
|
}
|
|
36802
|
-
const ext =
|
|
37388
|
+
const ext = path51.extname(src).toLowerCase();
|
|
36803
37389
|
const safeExt = ext && /^\.(png|jpe?g|gif|webp)$/i.test(ext) ? ext : ".png";
|
|
36804
|
-
const out =
|
|
37390
|
+
const out = path51.join(
|
|
36805
37391
|
baseDir,
|
|
36806
37392
|
`clip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${safeExt}`
|
|
36807
37393
|
);
|
|
36808
|
-
|
|
37394
|
+
fs45.copyFileSync(src, out);
|
|
36809
37395
|
for (const p of tmpPaths) {
|
|
36810
37396
|
unlinkQuiet(p);
|
|
36811
37397
|
}
|
|
@@ -36819,14 +37405,14 @@ async function tryDarwinClipboardy(baseDir) {
|
|
|
36819
37405
|
return null;
|
|
36820
37406
|
}
|
|
36821
37407
|
async function tryWindowsPowerShell(outFile) {
|
|
36822
|
-
const ps = process.env.SystemRoot != null ?
|
|
37408
|
+
const ps = process.env.SystemRoot != null ? path51.join(
|
|
36823
37409
|
process.env.SystemRoot,
|
|
36824
37410
|
"System32",
|
|
36825
37411
|
"WindowsPowerShell",
|
|
36826
37412
|
"v1.0",
|
|
36827
37413
|
"powershell.exe"
|
|
36828
37414
|
) : "powershell.exe";
|
|
36829
|
-
if (!
|
|
37415
|
+
if (!fs45.existsSync(ps)) {
|
|
36830
37416
|
return false;
|
|
36831
37417
|
}
|
|
36832
37418
|
const script = "$ErrorActionPreference='Stop';Add-Type -AssemblyName System.Windows.Forms;Add-Type -AssemblyName System.Drawing;$img=[System.Windows.Forms.Clipboard]::GetImage();if($null -eq $img){exit 2};$img.Save($env:BLUMA_CLIP_OUT,[System.Drawing.Imaging.ImageFormat]::Png);exit 0";
|
|
@@ -36848,7 +37434,7 @@ async function tryWindowsPowerShell(outFile) {
|
|
|
36848
37434
|
return false;
|
|
36849
37435
|
}
|
|
36850
37436
|
try {
|
|
36851
|
-
const st =
|
|
37437
|
+
const st = fs45.statSync(outFile);
|
|
36852
37438
|
return st.size >= 80 && st.size <= CLIPBOARD_MAX_BYTES;
|
|
36853
37439
|
} catch {
|
|
36854
37440
|
return false;
|
|
@@ -36920,8 +37506,8 @@ function parseClipboardTextAsImagePath(raw) {
|
|
|
36920
37506
|
s = s.slice(1, -1);
|
|
36921
37507
|
}
|
|
36922
37508
|
s = s.trim();
|
|
36923
|
-
if (!CLIPBOARD_PATH_IMAGE_EXT.test(
|
|
36924
|
-
const abs =
|
|
37509
|
+
if (!CLIPBOARD_PATH_IMAGE_EXT.test(path51.extname(s))) return null;
|
|
37510
|
+
const abs = path51.isAbsolute(s) ? path51.normalize(s) : path51.resolve(process.cwd(), s);
|
|
36925
37511
|
return abs;
|
|
36926
37512
|
}
|
|
36927
37513
|
async function tryClipboardTextAsImageFile(baseDir) {
|
|
@@ -36935,19 +37521,19 @@ async function tryClipboardTextAsImageFile(baseDir) {
|
|
|
36935
37521
|
const abs = parseClipboardTextAsImagePath(t);
|
|
36936
37522
|
if (!abs) return null;
|
|
36937
37523
|
try {
|
|
36938
|
-
if (!
|
|
36939
|
-
const st =
|
|
37524
|
+
if (!fs45.existsSync(abs)) return null;
|
|
37525
|
+
const st = fs45.statSync(abs);
|
|
36940
37526
|
if (!st.isFile() || st.size > CLIPBOARD_MAX_BYTES || st.size < 20) return null;
|
|
36941
37527
|
} catch {
|
|
36942
37528
|
return null;
|
|
36943
37529
|
}
|
|
36944
|
-
const ext =
|
|
36945
|
-
const out =
|
|
37530
|
+
const ext = path51.extname(abs).toLowerCase();
|
|
37531
|
+
const out = path51.join(
|
|
36946
37532
|
baseDir,
|
|
36947
37533
|
`clip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext}`
|
|
36948
37534
|
);
|
|
36949
37535
|
try {
|
|
36950
|
-
|
|
37536
|
+
fs45.copyFileSync(abs, out);
|
|
36951
37537
|
return out;
|
|
36952
37538
|
} catch {
|
|
36953
37539
|
return null;
|
|
@@ -36957,7 +37543,7 @@ async function tryLinuxShellPipelineSave(baseDir) {
|
|
|
36957
37543
|
if (process.platform !== "linux" && process.platform !== "freebsd") {
|
|
36958
37544
|
return null;
|
|
36959
37545
|
}
|
|
36960
|
-
const outPath =
|
|
37546
|
+
const outPath = path51.join(
|
|
36961
37547
|
baseDir,
|
|
36962
37548
|
`clip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.tmp`
|
|
36963
37549
|
);
|
|
@@ -36979,11 +37565,11 @@ printf '%s' "$OUT"
|
|
|
36979
37565
|
maxBuffer: 4096
|
|
36980
37566
|
});
|
|
36981
37567
|
const written = String(stdout ?? "").trim();
|
|
36982
|
-
if (written !== outPath || !
|
|
37568
|
+
if (written !== outPath || !fs45.existsSync(outPath)) {
|
|
36983
37569
|
unlinkQuiet(outPath);
|
|
36984
37570
|
return null;
|
|
36985
37571
|
}
|
|
36986
|
-
const buf =
|
|
37572
|
+
const buf = fs45.readFileSync(outPath);
|
|
36987
37573
|
unlinkQuiet(outPath);
|
|
36988
37574
|
return writeBufferIfImage(baseDir, buf);
|
|
36989
37575
|
} catch {
|
|
@@ -37004,8 +37590,8 @@ async function tryNativeClipboardImage() {
|
|
|
37004
37590
|
}
|
|
37005
37591
|
try {
|
|
37006
37592
|
const result = readClipboardImageNative();
|
|
37007
|
-
if (
|
|
37008
|
-
const st =
|
|
37593
|
+
if (fs45.existsSync(result.path)) {
|
|
37594
|
+
const st = fs45.statSync(result.path);
|
|
37009
37595
|
if (st.size >= 80 && st.size <= CLIPBOARD_MAX_BYTES) {
|
|
37010
37596
|
return result.path;
|
|
37011
37597
|
}
|
|
@@ -37015,8 +37601,8 @@ async function tryNativeClipboardImage() {
|
|
|
37015
37601
|
return null;
|
|
37016
37602
|
}
|
|
37017
37603
|
async function readClipboardImageToTempFile() {
|
|
37018
|
-
const baseDir =
|
|
37019
|
-
|
|
37604
|
+
const baseDir = path51.join(os33.homedir(), ".cache", "bluma", "clipboard");
|
|
37605
|
+
fs45.mkdirSync(baseDir, { recursive: true });
|
|
37020
37606
|
const nativeResult = await tryNativeClipboardImage();
|
|
37021
37607
|
if (nativeResult) {
|
|
37022
37608
|
return nativeResult;
|
|
@@ -37034,7 +37620,7 @@ async function readClipboardImageToTempFile() {
|
|
|
37034
37620
|
}
|
|
37035
37621
|
}
|
|
37036
37622
|
if (process.platform === "win32") {
|
|
37037
|
-
const outFile =
|
|
37623
|
+
const outFile = path51.join(
|
|
37038
37624
|
baseDir,
|
|
37039
37625
|
`clip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.png`
|
|
37040
37626
|
);
|
|
@@ -37075,7 +37661,7 @@ function expandLargePastePlaceholder(value, pending) {
|
|
|
37075
37661
|
}
|
|
37076
37662
|
|
|
37077
37663
|
// src/app/ui/components/InputPrompt.tsx
|
|
37078
|
-
import
|
|
37664
|
+
import fs46 from "fs";
|
|
37079
37665
|
import { Fragment as Fragment7, jsx as jsx79, jsxs as jsxs62 } from "react/jsx-runtime";
|
|
37080
37666
|
var persistedInputState = { text: "", cursorPosition: 0 };
|
|
37081
37667
|
var StaticCursor = () => /* @__PURE__ */ jsx79(Box_default, { flexDirection: "row", flexWrap: "nowrap", children: /* @__PURE__ */ jsx79(Text, { bold: true, color: BLUMA_TERMINAL.m3OnSurface, children: "\u2588 " }) });
|
|
@@ -37242,7 +37828,7 @@ var InputPrompt = memo16(({
|
|
|
37242
37828
|
return;
|
|
37243
37829
|
}
|
|
37244
37830
|
if (routeText.startsWith("/")) {
|
|
37245
|
-
const isFilePath = map.size > 0 &&
|
|
37831
|
+
const isFilePath = map.size > 0 && fs46.existsSync(routeText.split(/\s+/)[0]);
|
|
37246
37832
|
if (isFilePath) {
|
|
37247
37833
|
uiEventBus.emit("user_overlay", {
|
|
37248
37834
|
kind: "message",
|
|
@@ -39522,8 +40108,8 @@ var renderCode = () => {
|
|
|
39522
40108
|
};
|
|
39523
40109
|
|
|
39524
40110
|
// src/app/agent/core/thread/thread_store.ts
|
|
39525
|
-
import
|
|
39526
|
-
import { promises as
|
|
40111
|
+
import path52 from "path";
|
|
40112
|
+
import { promises as fs47 } from "fs";
|
|
39527
40113
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
39528
40114
|
var INDEX_VERSION = 1;
|
|
39529
40115
|
var fileLocks2 = /* @__PURE__ */ new Map();
|
|
@@ -39548,9 +40134,9 @@ var ThreadStore = class {
|
|
|
39548
40134
|
packageVersion;
|
|
39549
40135
|
constructor() {
|
|
39550
40136
|
const appDir = getPreferredAppDir();
|
|
39551
|
-
this.threadsDir =
|
|
39552
|
-
this.archiveDir =
|
|
39553
|
-
this.indexPath =
|
|
40137
|
+
this.threadsDir = path52.join(appDir, "threads");
|
|
40138
|
+
this.archiveDir = path52.join(this.threadsDir, "archive");
|
|
40139
|
+
this.indexPath = path52.join(this.threadsDir, "index.json");
|
|
39554
40140
|
this.packageVersion = process.env.npm_package_version || "0.0.0";
|
|
39555
40141
|
}
|
|
39556
40142
|
// ==================== Inicialização ====================
|
|
@@ -39558,10 +40144,10 @@ var ThreadStore = class {
|
|
|
39558
40144
|
* Inicializa o diretório de threads
|
|
39559
40145
|
*/
|
|
39560
40146
|
async initialize() {
|
|
39561
|
-
await
|
|
39562
|
-
await
|
|
40147
|
+
await fs47.mkdir(this.threadsDir, { recursive: true });
|
|
40148
|
+
await fs47.mkdir(this.archiveDir, { recursive: true });
|
|
39563
40149
|
try {
|
|
39564
|
-
await
|
|
40150
|
+
await fs47.access(this.indexPath);
|
|
39565
40151
|
} catch {
|
|
39566
40152
|
await this.saveIndex({ version: INDEX_VERSION, threads: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() });
|
|
39567
40153
|
}
|
|
@@ -39590,7 +40176,7 @@ var ThreadStore = class {
|
|
|
39590
40176
|
async loadIndex() {
|
|
39591
40177
|
return withFileLock2(this.indexPath, async () => {
|
|
39592
40178
|
try {
|
|
39593
|
-
const content = await
|
|
40179
|
+
const content = await fs47.readFile(this.indexPath, "utf-8");
|
|
39594
40180
|
return JSON.parse(content);
|
|
39595
40181
|
} catch {
|
|
39596
40182
|
return { version: INDEX_VERSION, threads: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
|
|
@@ -39601,8 +40187,8 @@ var ThreadStore = class {
|
|
|
39601
40187
|
return withFileLock2(this.indexPath, async () => {
|
|
39602
40188
|
index.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
39603
40189
|
const tempPath = `${this.indexPath}.${Date.now()}.tmp`;
|
|
39604
|
-
await
|
|
39605
|
-
await
|
|
40190
|
+
await fs47.writeFile(tempPath, JSON.stringify(index, null, 2), "utf-8");
|
|
40191
|
+
await fs47.rename(tempPath, this.indexPath);
|
|
39606
40192
|
});
|
|
39607
40193
|
}
|
|
39608
40194
|
// ==================== Git Info ====================
|
|
@@ -39672,7 +40258,7 @@ var ThreadStore = class {
|
|
|
39672
40258
|
messages: params.initialMessages || []
|
|
39673
40259
|
};
|
|
39674
40260
|
const historyPath = this.buildDatedThreadHistoryPath(threadId);
|
|
39675
|
-
await
|
|
40261
|
+
await fs47.mkdir(path52.dirname(historyPath), { recursive: true });
|
|
39676
40262
|
await this.saveHistoryAtPath(historyPath, history);
|
|
39677
40263
|
const index = await this.loadIndex();
|
|
39678
40264
|
index.threads.unshift({
|
|
@@ -39727,7 +40313,7 @@ var ThreadStore = class {
|
|
|
39727
40313
|
compressedSliceCount: source.history.compressedSliceCount
|
|
39728
40314
|
};
|
|
39729
40315
|
const historyPath = this.buildDatedThreadHistoryPath(newThreadId);
|
|
39730
|
-
await
|
|
40316
|
+
await fs47.mkdir(path52.dirname(historyPath), { recursive: true });
|
|
39731
40317
|
await this.saveHistoryAtPath(historyPath, history);
|
|
39732
40318
|
const index = await this.loadIndex();
|
|
39733
40319
|
index.threads.unshift({
|
|
@@ -39800,9 +40386,9 @@ var ThreadStore = class {
|
|
|
39800
40386
|
const entry = index.threads[entryIndex];
|
|
39801
40387
|
if (entry.status === "archived") return true;
|
|
39802
40388
|
const oldPath = entry.historyPath || this.getLegacyHistoryPath(threadId);
|
|
39803
|
-
const newPath =
|
|
40389
|
+
const newPath = path52.join(this.archiveDir, `${threadId}.jsonl`);
|
|
39804
40390
|
try {
|
|
39805
|
-
await
|
|
40391
|
+
await fs47.rename(oldPath, newPath);
|
|
39806
40392
|
} catch (e) {
|
|
39807
40393
|
if (e.code !== "ENOENT") throw e;
|
|
39808
40394
|
}
|
|
@@ -39823,11 +40409,11 @@ var ThreadStore = class {
|
|
|
39823
40409
|
if (entryIndex === -1) return false;
|
|
39824
40410
|
const entry = index.threads[entryIndex];
|
|
39825
40411
|
if (entry.status === "active") return true;
|
|
39826
|
-
const oldPath =
|
|
40412
|
+
const oldPath = path52.join(this.archiveDir, `${threadId}.jsonl`);
|
|
39827
40413
|
const newPath = this.buildDatedThreadHistoryPath(threadId);
|
|
39828
|
-
await
|
|
40414
|
+
await fs47.mkdir(path52.dirname(newPath), { recursive: true });
|
|
39829
40415
|
try {
|
|
39830
|
-
await
|
|
40416
|
+
await fs47.rename(oldPath, newPath);
|
|
39831
40417
|
} catch (e) {
|
|
39832
40418
|
if (e.code !== "ENOENT") throw e;
|
|
39833
40419
|
}
|
|
@@ -39848,7 +40434,7 @@ var ThreadStore = class {
|
|
|
39848
40434
|
if (entryIndex === -1) return false;
|
|
39849
40435
|
const entry = index.threads[entryIndex];
|
|
39850
40436
|
try {
|
|
39851
|
-
await
|
|
40437
|
+
await fs47.unlink(entry.historyPath);
|
|
39852
40438
|
} catch {
|
|
39853
40439
|
}
|
|
39854
40440
|
index.threads.splice(entryIndex, 1);
|
|
@@ -39858,28 +40444,28 @@ var ThreadStore = class {
|
|
|
39858
40444
|
}
|
|
39859
40445
|
// ==================== Histórico ====================
|
|
39860
40446
|
getLegacyHistoryPath(threadId) {
|
|
39861
|
-
return
|
|
40447
|
+
return path52.join(this.threadsDir, `${threadId}.jsonl`);
|
|
39862
40448
|
}
|
|
39863
40449
|
/** ~/.bluma/threads/YYYY/MM/DD/<threadId>.jsonl (data local de criação). */
|
|
39864
40450
|
buildDatedThreadHistoryPath(threadId, at = /* @__PURE__ */ new Date()) {
|
|
39865
40451
|
const y = String(at.getFullYear());
|
|
39866
40452
|
const mo = String(at.getMonth() + 1).padStart(2, "0");
|
|
39867
40453
|
const d = String(at.getDate()).padStart(2, "0");
|
|
39868
|
-
return
|
|
40454
|
+
return path52.join(this.threadsDir, y, mo, d, `${threadId}.jsonl`);
|
|
39869
40455
|
}
|
|
39870
40456
|
async resolveHistoryPath(threadId) {
|
|
39871
40457
|
const index = await this.loadIndex();
|
|
39872
40458
|
const entry = index.threads.find((t) => t.threadId === threadId);
|
|
39873
40459
|
if (entry?.historyPath) {
|
|
39874
40460
|
try {
|
|
39875
|
-
await
|
|
40461
|
+
await fs47.access(entry.historyPath);
|
|
39876
40462
|
return entry.historyPath;
|
|
39877
40463
|
} catch {
|
|
39878
40464
|
}
|
|
39879
40465
|
}
|
|
39880
40466
|
const legacy = this.getLegacyHistoryPath(threadId);
|
|
39881
40467
|
try {
|
|
39882
|
-
await
|
|
40468
|
+
await fs47.access(legacy);
|
|
39883
40469
|
return legacy;
|
|
39884
40470
|
} catch {
|
|
39885
40471
|
return entry?.historyPath ?? legacy;
|
|
@@ -39896,9 +40482,9 @@ var ThreadStore = class {
|
|
|
39896
40482
|
for (const msg of history.messages) {
|
|
39897
40483
|
lines.push(JSON.stringify({ type: "message", ...msg }));
|
|
39898
40484
|
}
|
|
39899
|
-
await
|
|
40485
|
+
await fs47.mkdir(path52.dirname(historyPath), { recursive: true }).catch(() => {
|
|
39900
40486
|
});
|
|
39901
|
-
await
|
|
40487
|
+
await fs47.writeFile(historyPath, lines.join("\n") + "\n", "utf-8");
|
|
39902
40488
|
}
|
|
39903
40489
|
/**
|
|
39904
40490
|
* Guarda o histórico de uma thread
|
|
@@ -39920,7 +40506,7 @@ var ThreadStore = class {
|
|
|
39920
40506
|
].filter((p, i, arr) => Boolean(p) && arr.indexOf(p) === i);
|
|
39921
40507
|
for (const historyPath of pathsToTry) {
|
|
39922
40508
|
try {
|
|
39923
|
-
const content = await
|
|
40509
|
+
const content = await fs47.readFile(historyPath, "utf-8");
|
|
39924
40510
|
const lines = content.split("\n").filter(Boolean);
|
|
39925
40511
|
const history = {
|
|
39926
40512
|
threadId,
|
|
@@ -39955,7 +40541,7 @@ var ThreadStore = class {
|
|
|
39955
40541
|
const entry = index.threads.find((t) => t.threadId === threadId);
|
|
39956
40542
|
if (!entry) throw new Error(`Thread not found: ${threadId}`);
|
|
39957
40543
|
const lines = messages.map((msg) => JSON.stringify({ type: "message", ...msg }));
|
|
39958
|
-
await
|
|
40544
|
+
await fs47.appendFile(entry.historyPath, lines.join("\n") + "\n", "utf-8");
|
|
39959
40545
|
entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
39960
40546
|
await this.saveIndex(index);
|
|
39961
40547
|
}
|
|
@@ -42828,16 +43414,16 @@ import latestVersion from "latest-version";
|
|
|
42828
43414
|
import semverGt from "semver/functions/gt.js";
|
|
42829
43415
|
import semverValid from "semver/functions/valid.js";
|
|
42830
43416
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
42831
|
-
import
|
|
42832
|
-
import
|
|
43417
|
+
import path53 from "path";
|
|
43418
|
+
import fs48 from "fs";
|
|
42833
43419
|
var BLUMA_PACKAGE_NAME = "@nomad-e/bluma-cli";
|
|
42834
43420
|
function findBlumaPackageJson(startDir) {
|
|
42835
43421
|
let dir = startDir;
|
|
42836
43422
|
for (let i = 0; i < 12; i++) {
|
|
42837
|
-
const candidate =
|
|
42838
|
-
if (
|
|
43423
|
+
const candidate = path53.join(dir, "package.json");
|
|
43424
|
+
if (fs48.existsSync(candidate)) {
|
|
42839
43425
|
try {
|
|
42840
|
-
const raw =
|
|
43426
|
+
const raw = fs48.readFileSync(candidate, "utf8");
|
|
42841
43427
|
const parsed = JSON.parse(raw);
|
|
42842
43428
|
if (parsed?.name === BLUMA_PACKAGE_NAME && parsed?.version) {
|
|
42843
43429
|
return { name: parsed.name, version: String(parsed.version) };
|
|
@@ -42845,7 +43431,7 @@ function findBlumaPackageJson(startDir) {
|
|
|
42845
43431
|
} catch {
|
|
42846
43432
|
}
|
|
42847
43433
|
}
|
|
42848
|
-
const parent =
|
|
43434
|
+
const parent = path53.dirname(dir);
|
|
42849
43435
|
if (parent === dir) break;
|
|
42850
43436
|
dir = parent;
|
|
42851
43437
|
}
|
|
@@ -42854,13 +43440,13 @@ function findBlumaPackageJson(startDir) {
|
|
|
42854
43440
|
function resolveInstalledBlumaPackage() {
|
|
42855
43441
|
const tried = /* @__PURE__ */ new Set();
|
|
42856
43442
|
const tryFrom = (dir) => {
|
|
42857
|
-
const abs =
|
|
43443
|
+
const abs = path53.resolve(dir);
|
|
42858
43444
|
if (tried.has(abs)) return null;
|
|
42859
43445
|
tried.add(abs);
|
|
42860
43446
|
return findBlumaPackageJson(abs);
|
|
42861
43447
|
};
|
|
42862
43448
|
try {
|
|
42863
|
-
const fromBundle = tryFrom(
|
|
43449
|
+
const fromBundle = tryFrom(path53.dirname(fileURLToPath6(import.meta.url)));
|
|
42864
43450
|
if (fromBundle) return fromBundle;
|
|
42865
43451
|
} catch {
|
|
42866
43452
|
}
|
|
@@ -42868,12 +43454,12 @@ function resolveInstalledBlumaPackage() {
|
|
|
42868
43454
|
if (argv1 && !argv1.startsWith("-")) {
|
|
42869
43455
|
try {
|
|
42870
43456
|
let resolved = argv1;
|
|
42871
|
-
if (
|
|
42872
|
-
resolved =
|
|
43457
|
+
if (path53.isAbsolute(argv1) && fs48.existsSync(argv1)) {
|
|
43458
|
+
resolved = fs48.realpathSync(argv1);
|
|
42873
43459
|
} else {
|
|
42874
|
-
resolved =
|
|
43460
|
+
resolved = path53.resolve(process.cwd(), argv1);
|
|
42875
43461
|
}
|
|
42876
|
-
const fromArgv = tryFrom(
|
|
43462
|
+
const fromArgv = tryFrom(path53.dirname(resolved));
|
|
42877
43463
|
if (fromArgv) return fromArgv;
|
|
42878
43464
|
} catch {
|
|
42879
43465
|
}
|
|
@@ -43692,16 +44278,16 @@ function usePlanMode() {
|
|
|
43692
44278
|
|
|
43693
44279
|
// src/app/hooks/useAgentMode.ts
|
|
43694
44280
|
import { useState as useState22, useEffect as useEffect21, useCallback as useCallback9 } from "react";
|
|
43695
|
-
import * as
|
|
43696
|
-
import * as
|
|
44281
|
+
import * as fs49 from "fs";
|
|
44282
|
+
import * as path54 from "path";
|
|
43697
44283
|
import { homedir as homedir3 } from "os";
|
|
43698
|
-
var SETTINGS_PATH =
|
|
44284
|
+
var SETTINGS_PATH = path54.join(homedir3(), ".bluma", "settings.json");
|
|
43699
44285
|
function readAgentModeFromFile() {
|
|
43700
44286
|
try {
|
|
43701
|
-
if (!
|
|
44287
|
+
if (!fs49.existsSync(SETTINGS_PATH)) {
|
|
43702
44288
|
return "default";
|
|
43703
44289
|
}
|
|
43704
|
-
const content =
|
|
44290
|
+
const content = fs49.readFileSync(SETTINGS_PATH, "utf-8");
|
|
43705
44291
|
const settings = JSON.parse(content);
|
|
43706
44292
|
return settings.agentMode || "default";
|
|
43707
44293
|
} catch (error) {
|
|
@@ -43720,16 +44306,16 @@ function useAgentMode() {
|
|
|
43720
44306
|
}, []);
|
|
43721
44307
|
const updateAgentMode = useCallback9((mode) => {
|
|
43722
44308
|
try {
|
|
43723
|
-
if (!
|
|
43724
|
-
|
|
44309
|
+
if (!fs49.existsSync(SETTINGS_PATH)) {
|
|
44310
|
+
fs49.mkdirSync(path54.dirname(SETTINGS_PATH), { recursive: true });
|
|
43725
44311
|
}
|
|
43726
44312
|
let settings = {};
|
|
43727
|
-
if (
|
|
43728
|
-
const content =
|
|
44313
|
+
if (fs49.existsSync(SETTINGS_PATH)) {
|
|
44314
|
+
const content = fs49.readFileSync(SETTINGS_PATH, "utf-8");
|
|
43729
44315
|
settings = JSON.parse(content);
|
|
43730
44316
|
}
|
|
43731
44317
|
settings.agentMode = mode;
|
|
43732
|
-
|
|
44318
|
+
fs49.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf-8");
|
|
43733
44319
|
setAgentMode(mode);
|
|
43734
44320
|
} catch (error) {
|
|
43735
44321
|
console.error("Failed to update agent mode:", error);
|
|
@@ -44992,10 +45578,10 @@ import React39 from "react";
|
|
|
44992
45578
|
import { memo as memo26, useCallback as useCallback11, useEffect as useEffect23, useMemo as useMemo8, useState as useState25 } from "react";
|
|
44993
45579
|
|
|
44994
45580
|
// src/app/agent/session_manager/session_resume_browser.ts
|
|
44995
|
-
import
|
|
44996
|
-
import { promises as
|
|
45581
|
+
import path55 from "path";
|
|
45582
|
+
import { promises as fs50 } from "fs";
|
|
44997
45583
|
function getSessionsRoot() {
|
|
44998
|
-
return
|
|
45584
|
+
return path55.join(getPreferredAppDir(), "sessions");
|
|
44999
45585
|
}
|
|
45000
45586
|
function normalizeSessionsCwd(cwd2) {
|
|
45001
45587
|
return cwd2.replace(/\\/g, "/").split("/").filter((p) => p && p !== "." && p !== "..").join("/");
|
|
@@ -45016,9 +45602,9 @@ async function sessionEntryFromFile(absPath, sessionId) {
|
|
|
45016
45602
|
let preview = "(no messages)";
|
|
45017
45603
|
let lastActivityMs = Date.now();
|
|
45018
45604
|
try {
|
|
45019
|
-
const st = await
|
|
45605
|
+
const st = await fs50.stat(absPath);
|
|
45020
45606
|
lastActivityMs = st.mtimeMs;
|
|
45021
|
-
const raw = await
|
|
45607
|
+
const raw = await fs50.readFile(absPath, "utf-8");
|
|
45022
45608
|
const data = JSON.parse(raw);
|
|
45023
45609
|
preview = previewFromHistory(data.conversation_history);
|
|
45024
45610
|
const iso = data.last_updated || data.created_at;
|
|
@@ -45046,15 +45632,15 @@ function compareDirNames(a, b) {
|
|
|
45046
45632
|
async function listSessionBrowserEntries(cwdRel) {
|
|
45047
45633
|
const cwd2 = normalizeSessionsCwd(cwdRel);
|
|
45048
45634
|
const root = getSessionsRoot();
|
|
45049
|
-
const absDir = cwd2 ?
|
|
45635
|
+
const absDir = cwd2 ? path55.join(root, ...cwd2.split("/")) : root;
|
|
45050
45636
|
const out = [];
|
|
45051
45637
|
if (cwd2) {
|
|
45052
45638
|
out.push({ kind: "up", label: ".." });
|
|
45053
45639
|
}
|
|
45054
45640
|
let dirents;
|
|
45055
45641
|
try {
|
|
45056
|
-
await
|
|
45057
|
-
dirents = await
|
|
45642
|
+
await fs50.mkdir(absDir, { recursive: true });
|
|
45643
|
+
dirents = await fs50.readdir(absDir, { withFileTypes: true });
|
|
45058
45644
|
} catch {
|
|
45059
45645
|
return out;
|
|
45060
45646
|
}
|
|
@@ -45063,7 +45649,7 @@ async function listSessionBrowserEntries(cwdRel) {
|
|
|
45063
45649
|
for (const e of dirents) {
|
|
45064
45650
|
const name = String(e.name);
|
|
45065
45651
|
if (name.startsWith(".")) continue;
|
|
45066
|
-
const full =
|
|
45652
|
+
const full = path55.join(absDir, name);
|
|
45067
45653
|
if (e.isDirectory()) {
|
|
45068
45654
|
dirNames.push(name);
|
|
45069
45655
|
} else if (e.isFile() && name.endsWith(".json") && !name.includes(".tmp")) {
|
|
@@ -45073,7 +45659,7 @@ async function listSessionBrowserEntries(cwdRel) {
|
|
|
45073
45659
|
dirNames.sort(compareDirNames);
|
|
45074
45660
|
const sessions = [];
|
|
45075
45661
|
for (const { name, full } of sessionFiles) {
|
|
45076
|
-
const sessionId =
|
|
45662
|
+
const sessionId = path55.basename(name, ".json");
|
|
45077
45663
|
sessions.push(await sessionEntryFromFile(full, sessionId));
|
|
45078
45664
|
}
|
|
45079
45665
|
sessions.sort((a, b) => {
|
|
@@ -45380,15 +45966,19 @@ function resolveResultUserMessage(lastAssistantMessage, lastAttachments) {
|
|
|
45380
45966
|
}
|
|
45381
45967
|
return "Tarefa conclu\xEDda.";
|
|
45382
45968
|
}
|
|
45383
|
-
function buildSuccessResultData(envelope, sessionId, lastAssistantMessage, lastAttachments) {
|
|
45969
|
+
function buildSuccessResultData(envelope, sessionId, lastAssistantMessage, lastAttachments, lastFactorShUrlApp) {
|
|
45384
45970
|
const message2 = resolveResultUserMessage(lastAssistantMessage, lastAttachments);
|
|
45385
|
-
|
|
45971
|
+
const data = {
|
|
45386
45972
|
message_id: envelope.message_id || sessionId,
|
|
45387
45973
|
action: envelope.action || "unknown",
|
|
45388
45974
|
last_assistant_message: message2,
|
|
45389
45975
|
message: message2,
|
|
45390
45976
|
attachments: lastAttachments
|
|
45391
45977
|
};
|
|
45978
|
+
if (lastFactorShUrlApp) {
|
|
45979
|
+
data[FACTOR_SH_URL_APP_FIELD] = lastFactorShUrlApp;
|
|
45980
|
+
}
|
|
45981
|
+
return data;
|
|
45392
45982
|
}
|
|
45393
45983
|
function finalizeSession(sessionId, status, metadata) {
|
|
45394
45984
|
updateSession(sessionId, {
|
|
@@ -45434,9 +46024,9 @@ async function runAgentMode() {
|
|
|
45434
46024
|
try {
|
|
45435
46025
|
if (inputFileIndex !== -1 && args[inputFileIndex + 1]) {
|
|
45436
46026
|
const filePath = args[inputFileIndex + 1];
|
|
45437
|
-
rawPayload =
|
|
46027
|
+
rawPayload = fs51.readFileSync(filePath, "utf-8");
|
|
45438
46028
|
} else {
|
|
45439
|
-
rawPayload =
|
|
46029
|
+
rawPayload = fs51.readFileSync(0, "utf-8");
|
|
45440
46030
|
}
|
|
45441
46031
|
} catch (err) {
|
|
45442
46032
|
writeAgentEvent(registrySessionId, {
|
|
@@ -45472,6 +46062,15 @@ async function runAgentMode() {
|
|
|
45472
46062
|
if (envelope.metadata?.sandbox_name) {
|
|
45473
46063
|
process.env.BLUMA_SANDBOX_NAME = String(envelope.metadata.sandbox_name);
|
|
45474
46064
|
}
|
|
46065
|
+
} else if (process.env.BLUMA_SANDBOX_WORKSPACE?.trim()) {
|
|
46066
|
+
process.env.BLUMA_SANDBOX = "true";
|
|
46067
|
+
if (!process.env.BLUMA_SANDBOX_NAME?.trim()) {
|
|
46068
|
+
process.env.BLUMA_SANDBOX_NAME = "sandbox-api";
|
|
46069
|
+
}
|
|
46070
|
+
}
|
|
46071
|
+
const severinoUrl = (process.env.SEVERINO_URL || "").trim();
|
|
46072
|
+
if (!(process.env.FACTORAI_BASE_URL || process.env.FACTORAI_URL || "").trim() && severinoUrl) {
|
|
46073
|
+
process.env.FACTORAI_BASE_URL = severinoUrl;
|
|
45475
46074
|
}
|
|
45476
46075
|
const envelopeUserRequest = typeof envelope.context === "object" && envelope.context !== null ? String(
|
|
45477
46076
|
envelope.context.user_request ?? envelope.context.userRequest ?? ""
|
|
@@ -45510,6 +46109,7 @@ async function runAgentMode() {
|
|
|
45510
46109
|
});
|
|
45511
46110
|
let lastAssistantMessage = null;
|
|
45512
46111
|
let lastAttachments = null;
|
|
46112
|
+
let lastFactorShUrlApp = null;
|
|
45513
46113
|
let resultEmitted = false;
|
|
45514
46114
|
let agentRef = null;
|
|
45515
46115
|
let reasoningSequence = 0;
|
|
@@ -45560,6 +46160,16 @@ async function runAgentMode() {
|
|
|
45560
46160
|
if (Array.isArray(attachments)) {
|
|
45561
46161
|
lastAttachments = attachments.filter((p) => typeof p === "string");
|
|
45562
46162
|
}
|
|
46163
|
+
const factorUrl = messagePayload?.[FACTOR_SH_URL_APP_FIELD] ?? parsed?.[FACTOR_SH_URL_APP_FIELD] ?? parsed?.data?.[FACTOR_SH_URL_APP_FIELD];
|
|
46164
|
+
if (typeof factorUrl === "string" && factorUrl.trim()) {
|
|
46165
|
+
lastFactorShUrlApp = factorUrl.trim();
|
|
46166
|
+
}
|
|
46167
|
+
if (!lastFactorShUrlApp && extractMessageToolPayload(parsed)?.message_type === "result") {
|
|
46168
|
+
const fromArgs = payload?.arguments?.[FACTOR_SH_URL_APP_FIELD];
|
|
46169
|
+
if (typeof fromArgs === "string" && fromArgs.trim()) {
|
|
46170
|
+
lastFactorShUrlApp = fromArgs.trim();
|
|
46171
|
+
}
|
|
46172
|
+
}
|
|
45563
46173
|
} catch {
|
|
45564
46174
|
}
|
|
45565
46175
|
}
|
|
@@ -45576,7 +46186,8 @@ async function runAgentMode() {
|
|
|
45576
46186
|
envelope,
|
|
45577
46187
|
sessionId,
|
|
45578
46188
|
lastAssistantMessage,
|
|
45579
|
-
lastAttachments
|
|
46189
|
+
lastAttachments,
|
|
46190
|
+
lastFactorShUrlApp
|
|
45580
46191
|
)
|
|
45581
46192
|
});
|
|
45582
46193
|
finalizeSession(sessionId, "completed", { finishedBy: "done-event" });
|
|
@@ -45628,7 +46239,8 @@ async function runAgentMode() {
|
|
|
45628
46239
|
envelope,
|
|
45629
46240
|
sessionId,
|
|
45630
46241
|
lastAssistantMessage,
|
|
45631
|
-
lastAttachments
|
|
46242
|
+
lastAttachments,
|
|
46243
|
+
lastFactorShUrlApp
|
|
45632
46244
|
)
|
|
45633
46245
|
});
|
|
45634
46246
|
finalizeSession(sessionId, "completed", { finishedBy: "post-turn-fallback" });
|
|
@@ -45655,9 +46267,9 @@ async function runAgentMode() {
|
|
|
45655
46267
|
}
|
|
45656
46268
|
function readCliPackageVersion() {
|
|
45657
46269
|
try {
|
|
45658
|
-
const base =
|
|
45659
|
-
const pkgPath =
|
|
45660
|
-
const j = JSON.parse(
|
|
46270
|
+
const base = path56.dirname(fileURLToPath7(import.meta.url));
|
|
46271
|
+
const pkgPath = path56.join(base, "..", "package.json");
|
|
46272
|
+
const j = JSON.parse(fs51.readFileSync(pkgPath, "utf8"));
|
|
45661
46273
|
return String(j.version || "0.0.0");
|
|
45662
46274
|
} catch {
|
|
45663
46275
|
return "0.0.0";
|
|
@@ -45783,7 +46395,7 @@ function startBackgroundAgent() {
|
|
|
45783
46395
|
process.exit(1);
|
|
45784
46396
|
}
|
|
45785
46397
|
const filePath = args[inputFileIndex + 1];
|
|
45786
|
-
const rawPayload =
|
|
46398
|
+
const rawPayload = fs51.readFileSync(filePath, "utf-8");
|
|
45787
46399
|
const envelope = JSON.parse(rawPayload);
|
|
45788
46400
|
const sessionId = envelope.session_id || envelope.message_id || uuidv412();
|
|
45789
46401
|
registerSession({
|