@nomad-e/bluma-cli 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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 fs9 from "fs";
410
- import path8 from "path";
409
+ import fs10 from "fs";
410
+ import path9 from "path";
411
411
  function getStorePath() {
412
412
  const policy = getSandboxPolicy();
413
- return path8.join(policy.workspaceRoot, ".bluma", "task_state.json");
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 (fs9.existsSync(storePath)) {
430
- const raw = fs9.readFileSync(storePath, "utf-8");
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
- fs9.mkdirSync(path8.dirname(storePath), { recursive: true });
449
+ fs10.mkdirSync(path9.dirname(storePath), { recursive: true });
450
450
  state2.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
451
- fs9.writeFileSync(storePath, JSON.stringify(state2, null, 2), "utf-8");
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 fs15 from "fs";
838
+ import fs16 from "fs";
839
839
  import os11 from "os";
840
- import path16 from "path";
840
+ import path17 from "path";
841
841
  function getRegistryDir() {
842
- return path16.join(process.env.HOME || os11.homedir(), ".bluma", "registry");
842
+ return path17.join(process.env.HOME || os11.homedir(), ".bluma", "registry");
843
843
  }
844
844
  function getRegistryFile() {
845
- return path16.join(getRegistryDir(), "sessions.json");
845
+ return path17.join(getRegistryDir(), "sessions.json");
846
846
  }
847
847
  function ensureRegistryDir() {
848
- fs15.mkdirSync(getRegistryDir(), { recursive: true });
848
+ fs16.mkdirSync(getRegistryDir(), { recursive: true });
849
849
  }
850
850
  function readRegistry() {
851
851
  ensureRegistryDir();
852
852
  const file = getRegistryFile();
853
- if (!fs15.existsSync(file)) {
853
+ if (!fs16.existsSync(file)) {
854
854
  return { entries: [] };
855
855
  }
856
856
  try {
857
- return JSON.parse(fs15.readFileSync(file, "utf-8"));
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
- fs15.writeFileSync(getRegistryFile(), JSON.stringify(state2, null, 2), "utf-8");
864
+ fs16.writeFileSync(getRegistryFile(), JSON.stringify(state2, null, 2), "utf-8");
865
865
  }
866
866
  function getSessionLogPath(sessionId) {
867
867
  ensureRegistryDir();
868
- return path16.join(getRegistryDir(), `${sessionId}.jsonl`);
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
- fs15.appendFileSync(logFile, `${JSON.stringify(payload)}
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 (!fs15.existsSync(logFile)) return [];
919
- return fs15.readFileSync(logFile, "utf-8").split("\n").filter(Boolean);
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 fs16 from "fs";
928
+ import fs17 from "fs";
929
929
  import os12 from "os";
930
- import path17 from "path";
930
+ import path18 from "path";
931
931
  function getLogDir() {
932
- const dir = path17.join(process.env.HOME || os12.homedir(), ".bluma", "logs");
933
- fs16.mkdirSync(dir, { recursive: true });
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 path17.join(getLogDir(), `bluma-${today}.log`);
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
- fs16.appendFileSync(this.logFile, JSON.stringify(logEntry) + "\n");
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 fs17 from "fs";
1069
+ import fs18 from "fs";
1070
1070
  import os13 from "os";
1071
- import path18 from "path";
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 = path18.join(process.env.HOME || os13.homedir(), ".bluma", "mailboxes");
1077
- fs17.mkdirSync(mailboxesDir, { recursive: true });
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 path18.join(getMailboxesDir(), `${sessionId}.${type}`);
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
- fs17.writeFileSync(filePath, pruned.map((m) => JSON.stringify(m)).join("\n") + "\n", "utf-8");
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 = fs17.readdirSync(dir);
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 = path18.join(dir, file);
1182
+ const filePath = path19.join(dir, file);
1183
1183
  try {
1184
- const content = fs17.readFileSync(filePath, "utf-8");
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
- fs17.appendFileSync(filePath, JSON.stringify(fullMessage) + "\n", "utf-8");
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
- fs17.appendFileSync(filePath, JSON.stringify(signal) + "\n", "utf-8");
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 = path18.join(dir, `${sessionId}.${type}`);
1285
- if (!fs17.existsSync(filePath)) {
1286
- fs17.writeFileSync(filePath, "", "utf-8");
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 = path18.join(dir, `${sessionId}.${type}`);
1297
- if (fs17.existsSync(filePath)) fs17.unlinkSync(filePath);
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 fs18 from "fs";
1316
+ import fs19 from "fs";
1317
1317
  import os14 from "os";
1318
- import path19 from "path";
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 = fs18.mkdtempSync(path19.join(os14.tmpdir(), "bluma-worker-"));
1433
- const payloadPath = path19.join(payloadDir, `${sessionId}.json`);
1432
+ const payloadDir = fs19.mkdtempSync(path20.join(os14.tmpdir(), "bluma-worker-"));
1433
+ const payloadPath = path20.join(payloadDir, `${sessionId}.json`);
1434
1434
  try {
1435
- fs18.writeFileSync(payloadPath, JSON.stringify(payload, null, 2), "utf-8");
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(path54, data, options) {
2205
- const _ = slowLogging`fs.appendFileSync(${path54}, ${data.length} chars)`;
2204
+ appendFileSync(path56, data, options) {
2205
+ const _ = slowLogging`fs.appendFileSync(${path56}, ${data.length} chars)`;
2206
2206
  if (options?.mode !== void 0) {
2207
2207
  try {
2208
- const fd = fs.openSync(path54, "ax", options.mode);
2208
+ const fd = fs.openSync(path56, "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(path54, data);
2219
+ fs.appendFileSync(path56, 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(path54) {
2226
- const _ = slowLogging`fs.unlinkSync(${path54})`;
2227
- fs.unlinkSync(path54);
2225
+ unlinkSync(path56) {
2226
+ const _ = slowLogging`fs.unlinkSync(${path56})`;
2227
+ fs.unlinkSync(path56);
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, path54) {
2234
- const _ = slowLogging`fs.linkSync(${target} → ${path54})`;
2235
- fs.linkSync(target, path54);
2233
+ linkSync(target, path56) {
2234
+ const _ = slowLogging`fs.linkSync(${target} → ${path56})`;
2235
+ fs.linkSync(target, path56);
2236
2236
  },
2237
- symlinkSync(target, path54, type) {
2238
- const _ = slowLogging`fs.symlinkSync(${target} → ${path54})`;
2239
- fs.symlinkSync(target, path54, type);
2237
+ symlinkSync(target, path56, type) {
2238
+ const _ = slowLogging`fs.symlinkSync(${target} → ${path56})`;
2239
+ fs.symlinkSync(target, path56, type);
2240
2240
  },
2241
- readlinkSync(path54) {
2242
- const _ = slowLogging`fs.readlinkSync(${path54})`;
2243
- return fs.readlinkSync(path54);
2241
+ readlinkSync(path56) {
2242
+ const _ = slowLogging`fs.readlinkSync(${path56})`;
2243
+ return fs.readlinkSync(path56);
2244
2244
  },
2245
- realpathSync(path54) {
2246
- const _ = slowLogging`fs.realpathSync(${path54})`;
2247
- return fs.realpathSync(path54).normalize("NFC");
2245
+ realpathSync(path56) {
2246
+ const _ = slowLogging`fs.realpathSync(${path56})`;
2247
+ return fs.realpathSync(path56).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(path54, options) {
2281
- const _ = slowLogging`fs.rmSync(${path54})`;
2282
- fs.rmSync(path54, options);
2280
+ rmSync(path56, options) {
2281
+ const _ = slowLogging`fs.rmSync(${path56})`;
2282
+ fs.rmSync(path56, options);
2283
2283
  },
2284
- createWriteStream(path54) {
2285
- return fs.createWriteStream(path54);
2284
+ createWriteStream(path56) {
2285
+ return fs.createWriteStream(path56);
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, path54, content) {
2392
+ async function appendAsync(needMkdir, dir, path56, content) {
2393
2393
  if (needMkdir) {
2394
2394
  await mkdir(dir, { recursive: true }).catch(() => {
2395
2395
  });
2396
2396
  }
2397
- await appendFile(path54, content);
2397
+ await appendFile(path56, 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 path54 = getDebugLogPath();
2408
- const dir = dirname(path54);
2407
+ const path56 = getDebugLogPath();
2408
+ const dir = dirname(path56);
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(path54, content);
2418
+ getFsImplementation().appendFileSync(path56, content);
2419
2419
  void updateLatestDebugLogSymlink();
2420
2420
  return;
2421
2421
  }
2422
- pendingWrite = pendingWrite.then(appendAsync.bind(null, needMkdir, dir, path54, content)).catch(noop);
2422
+ pendingWrite = pendingWrite.then(appendAsync.bind(null, needMkdir, dir, path56, 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 = (path54) => {
8610
- return path54?.replace(`file://${process.cwd()}/`, "");
8609
+ var cleanupPath = (path56) => {
8610
+ return path56?.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 fs49 from "fs";
12588
- import path53 from "path";
12587
+ import fs51 from "fs";
12588
+ import path55 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,7 +13479,7 @@ function cancelSlashSubmenu() {
13479
13479
 
13480
13480
  // src/app/agent/agent.ts
13481
13481
  import * as dotenv from "dotenv";
13482
- import path44 from "path";
13482
+ import path46 from "path";
13483
13483
  import os31 from "os";
13484
13484
 
13485
13485
  // src/app/agent/tool_invoker.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/sandbox_message_validation.ts
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 (!fs5.existsSync(resolved)) {
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 = fs5.statSync(resolved);
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 ${path5.relative(policy.workspaceRoot, artifactsDir2)}/ and pass that path in attachments[]. Rejected: ${raw}`
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 fs6 } from "fs";
14209
- import path6 from "path";
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 path6.join(os5.homedir(), p.slice(2));
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 = path6.resolve(expandPath(directory_path));
14256
- const stat = await fs6.stat(basePath).catch(() => null);
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 fs6.readdir(currentDir, { withFileTypes: true });
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 = path6.join(currentDir, entryName);
14272
- const relativePath = path6.relative(basePath, fullPath).split(path6.sep).join("/");
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(path6.extname(entryName).toLowerCase())) {
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(path6.sep).join("/"),
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 fs7 } from "fs";
14317
- import path7 from "path";
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 path7.join(os6.homedir(), p.slice(2));
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 fs7.stat(resolvedPath).catch(() => null);
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 fs7.readFile(resolvedPath, "utf-8");
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 fs8 } from "fs";
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 fs8.stat(filepath)).isFile()) {
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 path9 from "path";
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 path9.join(os7.homedir(), p.slice(2));
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 = path9.extname(filename).toLowerCase();
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 = path9.join(dir, name);
14694
- const relativePath = path9.relative(baseDir, fullPath).split(path9.sep).join("/");
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 path10 from "path";
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 path10.join(os8.homedir(), p.slice(2));
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 = path10.extname(filepath).toLowerCase();
14871
- const base = path10.basename(filepath);
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 = path10.relative(baseDir, filepath).split(path10.sep).join("/");
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 = path10.join(dir, entry.name);
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 = path10.resolve(expandTilde3(searchPath));
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, path10.dirname(resolvedPath), pattern, context_lines, max_matches_per_file);
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 path11 from "path";
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 = path11.extname(filepath).toLowerCase();
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 = path11.resolve(file_path);
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 path12 from "path";
15279
- import { promises as fs10 } from "fs";
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 = path12.join(policy.workspaceRoot, ".bluma", "artifacts");
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 = path12.join(baseDir, sessionId);
15287
- await fs10.mkdir(artifactsDir, { recursive: true });
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 = path12.join(dir, "task.md");
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 fs10.writeFile(taskFile, content, "utf-8");
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 = path12.join(dir, filename);
15421
- const content = await fs10.readFile(filepath, "utf-8");
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 fs11 from "fs";
15966
- import * as path13 from "path";
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 = path13.join(os10.homedir(), ".bluma", "coding_memory.json");
15976
- const legacyPath = path13.join(process.cwd(), ".bluma", "coding_memory.json");
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 (fs11.existsSync(globalPath)) {
15980
- raw = fs11.readFileSync(globalPath, "utf-8");
15981
- } else if (path13.resolve(globalPath) !== path13.resolve(legacyPath) && fs11.existsSync(legacyPath)) {
15982
- raw = fs11.readFileSync(legacyPath, "utf-8");
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 path13.join(os10.homedir(), ".bluma", "coding_memory.json");
16191
+ return path14.join(os10.homedir(), ".bluma", "coding_memory.json");
16021
16192
  }
16022
16193
  function getLegacyMemoryFilePath() {
16023
- return path13.join(process.cwd(), ".bluma", "coding_memory.json");
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 = path13.resolve(legacy) !== path13.resolve(filePath);
16204
+ const legacyDistinct = path14.resolve(legacy) !== path14.resolve(filePath);
16034
16205
  const readIntoStore = (p) => {
16035
- const raw = fs11.readFileSync(p, "utf-8");
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 (fs11.existsSync(filePath)) {
16213
+ if (fs12.existsSync(filePath)) {
16043
16214
  readIntoStore(filePath);
16044
16215
  }
16045
- if (memoryStore.length === 0 && legacyDistinct && fs11.existsSync(legacy)) {
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 = path13.dirname(filePath);
16060
- if (!fs11.existsSync(dir)) {
16061
- fs11.mkdirSync(dir, { recursive: true });
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
- fs11.writeFileSync(filePath, JSON.stringify(payload, null, 2));
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 fs12 } from "fs";
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 fs12.readFile(resolved, "utf-8");
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 fs12.writeFile(resolved, JSON.stringify(doc, null, 2), "utf-8");
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 fs12.writeFile(resolved, JSON.stringify(doc, null, 2), "utf-8");
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 fs12.writeFile(resolved, JSON.stringify(doc, null, 2), "utf-8");
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 fs13 from "fs";
16681
- import * as path14 from "path";
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 (!fs13.existsSync(resolved) || !fs13.statSync(resolved).isFile()) {
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 = fs13.readFileSync(resolved, "utf-8");
16905
+ const content = fs14.readFileSync(resolved, "utf-8");
16735
16906
  const uri = pathToFileURL(resolved).href;
16736
- const root = path14.dirname(resolved);
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 fs14 } from "fs";
16857
- import path15 from "path";
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 = path15.dirname(resolvedPath);
17046
+ const dir = path16.dirname(resolvedPath);
16876
17047
  let existed = false;
16877
17048
  try {
16878
- const st = await fs14.stat(resolvedPath);
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 fs14.readFile(resolvedPath, "utf-8");
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 fs14.mkdir(dir, { recursive: true });
17069
+ await fs15.mkdir(dir, { recursive: true });
16899
17070
  }
16900
- await fs14.writeFile(resolvedPath, String(content), "utf-8");
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 fs19 from "fs";
17380
+ import fs20 from "fs";
17210
17381
  import os15 from "os";
17211
- import path20 from "path";
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 = path20.join(os15.tmpdir(), `bluma_repl_${uuidv47()}.py`);
17224
- fs19.writeFileSync(tempFile, code, "utf-8");
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 = path20.join(os15.tmpdir(), `bluma_repl_${uuidv47()}.mjs`);
17230
- fs19.writeFileSync(tempFile, code, "utf-8");
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 = path20.join(os15.tmpdir(), `bluma_repl_${uuidv47()}.sh`);
17236
- fs19.writeFileSync(tempFile, `#!/bin/bash
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
- fs19.unlinkSync(tempFile);
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
- fs19.unlinkSync(tempFile);
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 fs20 from "fs";
17611
- import * as path21 from "path";
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 = path21.isAbsolute(filePath) ? filePath : path21.join(resolveWorkspacePath("."), filePath);
17631
- if (fs20.existsSync(absolutePath)) {
17632
- const stat = fs20.statSync(absolutePath);
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 fs21 from "fs";
17672
- import * as path22 from "path";
17860
+ import * as fs22 from "fs";
17861
+ import * as path23 from "path";
17673
17862
  import os16 from "os";
17674
17863
  function memoryPath() {
17675
- return path22.join(process.env.HOME || os16.homedir(), ".bluma", "coding_memory.json");
17864
+ return path23.join(process.env.HOME || os16.homedir(), ".bluma", "coding_memory.json");
17676
17865
  }
17677
17866
  function sessionsDir() {
17678
- return path22.join(process.env.HOME || os16.homedir(), ".bluma", "sessions");
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 (fs21.existsSync(memPath)) {
17892
+ if (fs22.existsSync(memPath)) {
17704
17893
  try {
17705
- memData = JSON.parse(fs21.readFileSync(memPath, "utf-8"));
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 (fs21.existsSync(sessDir)) {
17971
+ if (fs22.existsSync(sessDir)) {
17783
17972
  try {
17784
- const sessionFiles = fs21.readdirSync(sessDir).filter((f) => f.endsWith(".json")).slice(-5);
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(fs21.readFileSync(path22.join(sessDir, sf), "utf-8"));
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
- fs21.writeFileSync(memPath, JSON.stringify(memData, null, 2), "utf-8");
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 fs22 } from "fs";
17996
- import path23 from "path";
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 ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
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-primary text-primary-foreground hover:bg-primary/90",
18580
- destructive:
18581
- "bg-destructive text-destructive-foreground hover:bg-destructive/90",
18582
- outline:
18583
- "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
18584
- secondary:
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, asChild = false, ...props }, ref) => {
18611
- const Comp = asChild ? Slot : "button"
18612
- return (
18613
- <Comp
18614
- className={cn(buttonVariants({ variant, size, className }))}
18615
- ref={ref}
18616
- {...props}
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-card text-card-foreground shadow-sm",
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-muted-foreground", className)}
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-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
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 = path23.dirname(filePath);
18771
- await fs22.mkdir(dir, { recursive: true });
18985
+ const dir = path24.dirname(filePath);
18986
+ await fs23.mkdir(dir, { recursive: true });
18772
18987
  const replacedContent = content.replace(/{{NAME}}/g, projectName);
18773
- await fs22.writeFile(filePath, replacedContent, "utf-8");
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 = path23.join(baseDir, name);
19007
+ const projectPath = path24.join(baseDir, name);
18793
19008
  try {
18794
- await fs22.access(projectPath);
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 fs22.mkdir(projectPath, { recursive: true });
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 = path23.join(projectPath, relativePath);
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 = path23.join(projectPath, "package.json");
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,8 +19065,11 @@ async function createNextApp(args) {
18850
19065
 
18851
19066
  // src/app/agent/tools/DeployAppTool/DeployAppTool.ts
18852
19067
  init_sandbox_policy();
18853
- import { promises as fs23 } from "fs";
18854
- import path24 from "path";
19068
+ import { promises as fs24 } from "fs";
19069
+ import path25 from "path";
19070
+ function shQuote(value) {
19071
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
19072
+ }
18855
19073
  var EXCLUDE_PATTERNS = [
18856
19074
  "node_modules",
18857
19075
  ".next",
@@ -18867,12 +19085,15 @@ var EXCLUDE_PATTERNS = [
18867
19085
  ".vercel",
18868
19086
  ".turbo"
18869
19087
  ];
18870
- async function createProjectZip(projectDir, zipPath) {
18871
- const excludes = [];
18872
- for (const pattern of EXCLUDE_PATTERNS) {
18873
- excludes.push("--exclude", pattern);
19088
+ function zipExcludeArg(pattern) {
19089
+ if (pattern.includes("*") || pattern.startsWith(".")) {
19090
+ return pattern;
18874
19091
  }
18875
- const zipCommand = `zip -r "${zipPath}" . ${excludes.join(" ")}`;
19092
+ return `${pattern}/*`;
19093
+ }
19094
+ async function createProjectZip(projectDir, zipPath) {
19095
+ const excludeFlags = EXCLUDE_PATTERNS.flatMap((pattern) => ["-x", zipExcludeArg(pattern)]);
19096
+ const zipCommand = ["zip", "-r", shQuote(zipPath), ".", ...excludeFlags.map(shQuote)].join(" ");
18876
19097
  const result = await shellCommand({
18877
19098
  command: zipCommand,
18878
19099
  cwd: projectDir,
@@ -18888,6 +19109,7 @@ async function createProjectZip(projectDir, zipPath) {
18888
19109
  async function uploadToSeverino(zipPath, severinoUrl, name, apiKey, appId) {
18889
19110
  const deployUrl = `${severinoUrl.replace(/\/$/, "")}/api/v1/deploy`;
18890
19111
  const curlArgs = [
19112
+ "-sS",
18891
19113
  "-X",
18892
19114
  "POST",
18893
19115
  deployUrl,
@@ -18905,7 +19127,7 @@ async function uploadToSeverino(zipPath, severinoUrl, name, apiKey, appId) {
18905
19127
  curlArgs.push("-H", `X-API-Key: ${apiKey}`);
18906
19128
  }
18907
19129
  curlArgs.push("-H", "Accept: application/json");
18908
- const curlCommand = `curl ${curlArgs.join(" ")}`;
19130
+ const curlCommand = `curl ${curlArgs.map(shQuote).join(" ")}`;
18909
19131
  const result = await shellCommand({
18910
19132
  command: curlCommand,
18911
19133
  timeout: 60
@@ -18992,8 +19214,10 @@ function buildFactorAiManifest(appContext, deployResult, appName) {
18992
19214
  mode: "tool-first",
18993
19215
  instructions: [
18994
19216
  "Read factorai.sh.json as the source of truth before editing files.",
18995
- "Prefer incremental changes and preserve the existing deployment context.",
18996
- "After edits, use the redeploy tool instead of rebuilding from scratch."
19217
+ "Load skill factorai-sh for the full FactorAI.sh workflow.",
19218
+ "For an app already online: use factorai.sh.apply_app_changes with deploy:true (not deploy_app again).",
19219
+ "Poll factorai.sh.get_app_status until building\u2192ready before reporting the live URL.",
19220
+ "Use factorai.sh.redeploy_app only when rebuilding without new file patches."
18997
19221
  ]
18998
19222
  },
18999
19223
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -19001,8 +19225,8 @@ function buildFactorAiManifest(appContext, deployResult, appName) {
19001
19225
  }
19002
19226
  async function writeFactorAiManifest(projectDir, appContext, deployResult, appName) {
19003
19227
  const manifest = buildFactorAiManifest(appContext, deployResult, appName);
19004
- const manifestPath = path24.join(projectDir, "factorai.sh.json");
19005
- await fs23.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}
19228
+ const manifestPath = path25.join(projectDir, "factorai.sh.json");
19229
+ await fs24.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}
19006
19230
  `, "utf-8");
19007
19231
  return manifest;
19008
19232
  }
@@ -19022,23 +19246,23 @@ async function deployApp(args) {
19022
19246
  }
19023
19247
  const resolvedProjectDir = resolveWorkspacePath(projectDir);
19024
19248
  try {
19025
- await fs23.access(resolvedProjectDir);
19249
+ await fs24.access(resolvedProjectDir);
19026
19250
  } catch {
19027
19251
  return {
19028
19252
  success: false,
19029
19253
  error: `Project directory not found: ${resolvedProjectDir}`
19030
19254
  };
19031
19255
  }
19032
- const packageJsonPath = path24.join(resolvedProjectDir, "package.json");
19256
+ const packageJsonPath = path25.join(resolvedProjectDir, "package.json");
19033
19257
  try {
19034
- await fs23.access(packageJsonPath);
19258
+ await fs24.access(packageJsonPath);
19035
19259
  } catch {
19036
19260
  return {
19037
19261
  success: false,
19038
19262
  error: "Not a Next.js project: package.json not found"
19039
19263
  };
19040
19264
  }
19041
- const packageJsonContent = await fs23.readFile(packageJsonPath, "utf-8");
19265
+ const packageJsonContent = await fs24.readFile(packageJsonPath, "utf-8");
19042
19266
  const packageJson = JSON.parse(packageJsonContent);
19043
19267
  const hasNext = packageJson.dependencies?.next || packageJson.devDependencies?.next;
19044
19268
  if (!hasNext) {
@@ -19047,18 +19271,18 @@ async function deployApp(args) {
19047
19271
  error: "Not a Next.js project: next not found in dependencies"
19048
19272
  };
19049
19273
  }
19050
- const appName = name || packageJson.name || path24.basename(resolvedProjectDir);
19051
- const tempDir = path24.join(resolvedProjectDir, ".tmp");
19052
- await fs23.mkdir(tempDir, { recursive: true });
19053
- const zipPath = path24.join(tempDir, `${appName}-${Date.now()}.zip`);
19274
+ const appName = name || packageJson.name || path25.basename(resolvedProjectDir);
19275
+ const tempDir = path25.join(resolvedProjectDir, ".tmp");
19276
+ await fs24.mkdir(tempDir, { recursive: true });
19277
+ const zipPath = path25.join(tempDir, `${appName}-${Date.now()}.zip`);
19054
19278
  console.log(`[deploy-app] Creating ZIP: ${zipPath}`);
19055
19279
  await createProjectZip(resolvedProjectDir, zipPath);
19056
- const zipStats = await fs23.stat(zipPath);
19280
+ const zipStats = await fs24.stat(zipPath);
19057
19281
  const zipSizeMB = zipStats.size / 1024 / 1024;
19058
19282
  if (zipSizeMB > 50) {
19059
- await fs23.unlink(zipPath).catch(() => {
19283
+ await fs24.unlink(zipPath).catch(() => {
19060
19284
  });
19061
- await fs23.rmdir(tempDir).catch(() => {
19285
+ await fs24.rmdir(tempDir).catch(() => {
19062
19286
  });
19063
19287
  return {
19064
19288
  success: false,
@@ -19069,8 +19293,8 @@ async function deployApp(args) {
19069
19293
  console.log(`[deploy-app] Uploading to ${severinoUrl}...`);
19070
19294
  const deployResult = await uploadToSeverino(zipPath, severinoUrl, appName, apiKey, appId);
19071
19295
  try {
19072
- await fs23.unlink(zipPath);
19073
- await fs23.rmdir(tempDir);
19296
+ await fs24.unlink(zipPath);
19297
+ await fs24.rmdir(tempDir);
19074
19298
  } catch (e) {
19075
19299
  console.warn("[deploy-app] Cleanup warning:", e);
19076
19300
  }
@@ -19083,7 +19307,7 @@ async function deployApp(args) {
19083
19307
  appName
19084
19308
  );
19085
19309
  deployResult.factoraiManifest = manifest;
19086
- deployResult.factoraiManifestPath = path24.join(resolvedProjectDir, "factorai.sh.json");
19310
+ deployResult.factoraiManifestPath = path25.join(resolvedProjectDir, "factorai.sh.json");
19087
19311
  }
19088
19312
  console.log(`[deploy-app] Deploy iniciado: ${deployResult.appId}`);
19089
19313
  }
@@ -19097,147 +19321,6 @@ async function deployApp(args) {
19097
19321
  }
19098
19322
  }
19099
19323
 
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
19324
  // src/app/agent/runtime/native_tool_catalog.ts
19242
19325
  init_sandbox_policy();
19243
19326
  function getFactorAiBaseUrl() {
@@ -19389,7 +19472,7 @@ function getFactorAiSandboxToolDefinitions() {
19389
19472
  type: "function",
19390
19473
  function: {
19391
19474
  name: "factorai.sh.deploy_app",
19392
- description: "Deploy a Next.js project from the sandbox to the FactorAI hosting backend. The returned metadata must be written into factorai.sh.json in the project root for future incremental edits.",
19475
+ 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
19476
  parameters: {
19394
19477
  type: "object",
19395
19478
  properties: {
@@ -19407,7 +19490,7 @@ function getFactorAiSandboxToolDefinitions() {
19407
19490
  type: "function",
19408
19491
  function: {
19409
19492
  name: "factorai.sh.get_app_status",
19410
- description: "Get the current status and contract for a FactorAI deployment in the sandbox.",
19493
+ 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
19494
  parameters: {
19412
19495
  type: "object",
19413
19496
  properties: {
@@ -19422,7 +19505,7 @@ function getFactorAiSandboxToolDefinitions() {
19422
19505
  type: "function",
19423
19506
  function: {
19424
19507
  name: "factorai.sh.apply_app_changes",
19425
- description: "Apply incremental file changes to a FactorAI workspace and optionally redeploy.",
19508
+ 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
19509
  parameters: {
19427
19510
  type: "object",
19428
19511
  properties: {
@@ -20086,7 +20169,7 @@ var NATIVE_TOOL_ENTRIES = [
20086
20169
  autoApproveInLocal: false,
20087
20170
  autoApproveInSandbox: true,
20088
20171
  sandboxOnly: true,
20089
- description: "Sandbox-only FactorAI tool. Apply file changes to a deployed app workspace and redeploy it."
20172
+ 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
20173
  },
20091
20174
  implementation: factorAiApplyAppChanges
20092
20175
  },
@@ -20145,7 +20228,7 @@ var SANDBOX_COMMUNICATION_TOOL_DEFINITIONS = [
20145
20228
  type: "function",
20146
20229
  function: {
20147
20230
  name: "message",
20148
- description: 'Deliver user-visible output. message_type "info": progress (does not end turn). message_type "result": ends turn; file deliverables MUST use attachments[] with paths under .bluma/artifacts/.',
20231
+ 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
20232
  parameters: {
20150
20233
  type: "object",
20151
20234
  properties: {
@@ -20159,6 +20242,10 @@ var SANDBOX_COMMUNICATION_TOOL_DEFINITIONS = [
20159
20242
  type: "array",
20160
20243
  items: { type: "string" },
20161
20244
  description: "Workspace file paths under .bluma/artifacts/ (required for file deliverables on result)."
20245
+ },
20246
+ "factor-sh-url-app": {
20247
+ type: "string",
20248
+ 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
20249
  }
20163
20250
  },
20164
20251
  required: ["content", "message_type"],
@@ -20628,17 +20715,17 @@ PENALTY APPLIED: ${penalty.toFixed(1)} points deducted.
20628
20715
  };
20629
20716
 
20630
20717
  // src/app/agent/bluma/core/bluma.ts
20631
- import path41 from "path";
20632
- import fs38 from "fs";
20718
+ import path43 from "path";
20719
+ import fs40 from "fs";
20633
20720
  import { v4 as uuidv48 } from "uuid";
20634
20721
 
20635
20722
  // src/app/agent/session_manager/session_manager.ts
20636
- import path30 from "path";
20637
- import { promises as fs28 } from "fs";
20723
+ import path31 from "path";
20724
+ import { promises as fs29 } from "fs";
20638
20725
 
20639
20726
  // src/app/agent/session_manager/agent_session_paths.ts
20640
- import path29 from "path";
20641
- import { promises as fs27 } from "fs";
20727
+ import path30 from "path";
20728
+ import { promises as fs28 } from "fs";
20642
20729
 
20643
20730
  // src/app/agent/session_manager/bluma_app_dir.ts
20644
20731
  import path28 from "path";
@@ -20659,24 +20746,268 @@ function getPreferredAppDir() {
20659
20746
  return path28.resolve(expandHome(fixed));
20660
20747
  }
20661
20748
 
20662
- // src/app/agent/session_manager/agent_session_paths.ts
20749
+ // src/app/agent/session_manager/session_index_db.ts
20750
+ import path29 from "path";
20751
+ import { mkdirSync as mkdirSync3 } from "fs";
20752
+ import { promises as fs27 } from "fs";
20753
+ import { createRequire } from "module";
20663
20754
  var AGENT_SESSION_PATHS_JSONL = "agent_session_paths.jsonl";
20755
+ var BLUMA_SESSION_DB_FILE = "bluma.sqlite";
20756
+ var SCHEMA_VERSION = 1;
20757
+ var dbInstance = null;
20758
+ var dbAppDir = null;
20759
+ var migratePromise = null;
20760
+ function loadSqlite() {
20761
+ const nodeRequire = createRequire(import.meta.url);
20762
+ return nodeRequire("better-sqlite3");
20763
+ }
20764
+ function getSessionDbPath(appDir = getPreferredAppDir()) {
20765
+ return path29.join(appDir, BLUMA_SESSION_DB_FILE);
20766
+ }
20767
+ function normalizeRelativePath(relativePath) {
20768
+ return relativePath.split(path29.sep).join("/");
20769
+ }
20770
+ async function walkSessionJsonFiles(dir, onFile) {
20771
+ let entries;
20772
+ try {
20773
+ entries = await fs27.readdir(dir, { withFileTypes: true });
20774
+ } catch {
20775
+ return;
20776
+ }
20777
+ for (const e of entries) {
20778
+ const name = String(e.name);
20779
+ const full = path29.join(dir, name);
20780
+ if (e.isDirectory()) {
20781
+ await walkSessionJsonFiles(full, onFile);
20782
+ } else if (e.isFile() && name.endsWith(".json") && !name.includes(".tmp")) {
20783
+ await onFile(full);
20784
+ }
20785
+ }
20786
+ }
20787
+ async function loadJsonlEntries(appDir) {
20788
+ const map = /* @__PURE__ */ new Map();
20789
+ const indexFile = path29.join(appDir, AGENT_SESSION_PATHS_JSONL);
20790
+ let raw;
20791
+ try {
20792
+ raw = await fs27.readFile(indexFile, "utf-8");
20793
+ } catch {
20794
+ return map;
20795
+ }
20796
+ for (const line of raw.split("\n")) {
20797
+ const trimmed = line.trim();
20798
+ if (!trimmed) continue;
20799
+ try {
20800
+ const row = JSON.parse(trimmed);
20801
+ if (row.sessionId && typeof row.relativePath === "string") {
20802
+ map.set(row.sessionId, {
20803
+ relativePath: normalizeRelativePath(row.relativePath),
20804
+ updatedAt: row.updatedAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
20805
+ });
20806
+ }
20807
+ } catch {
20808
+ }
20809
+ }
20810
+ return map;
20811
+ }
20812
+ function previewFromConversationHistory(history) {
20813
+ for (const msg of history ?? []) {
20814
+ if (msg.role !== "user") continue;
20815
+ const c = msg.content;
20816
+ if (typeof c !== "string") continue;
20817
+ const t = c.trim();
20818
+ if (!t || t.startsWith("[SKILL:")) continue;
20819
+ const oneLine = t.replace(/\s+/g, " ");
20820
+ return oneLine.length > 72 ? `${oneLine.slice(0, 72)}\u2026` : oneLine;
20821
+ }
20822
+ return "(no messages)";
20823
+ }
20824
+ async function readSessionMetaFromJson(absPath) {
20825
+ const fallback = {
20826
+ preview: "(no messages)",
20827
+ createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
20828
+ updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
20829
+ };
20830
+ try {
20831
+ const raw = await fs27.readFile(absPath, "utf-8");
20832
+ const data = JSON.parse(raw);
20833
+ const createdAt = data.created_at ?? fallback.createdAt;
20834
+ const updatedAt = data.last_updated ?? data.created_at ?? fallback.updatedAt;
20835
+ return {
20836
+ preview: previewFromConversationHistory(data.conversation_history),
20837
+ createdAt,
20838
+ updatedAt
20839
+ };
20840
+ } catch {
20841
+ try {
20842
+ const st = await fs27.stat(absPath);
20843
+ const iso = new Date(st.mtimeMs).toISOString();
20844
+ return { preview: fallback.preview, createdAt: iso, updatedAt: iso };
20845
+ } catch {
20846
+ return fallback;
20847
+ }
20848
+ }
20849
+ }
20850
+ async function runMigrationV1(db, appDir) {
20851
+ const jsonl = await loadJsonlEntries(appDir);
20852
+ const upsert = db.prepare(`
20853
+ INSERT INTO agent_sessions (session_id, relative_path, created_at, updated_at, preview)
20854
+ VALUES (@sessionId, @relativePath, @createdAt, @updatedAt, @preview)
20855
+ ON CONFLICT(session_id) DO UPDATE SET
20856
+ relative_path = excluded.relative_path,
20857
+ updated_at = CASE
20858
+ WHEN excluded.updated_at > agent_sessions.updated_at THEN excluded.updated_at
20859
+ ELSE agent_sessions.updated_at
20860
+ END,
20861
+ preview = CASE
20862
+ WHEN excluded.updated_at > agent_sessions.updated_at THEN excluded.preview
20863
+ ELSE agent_sessions.preview
20864
+ END
20865
+ `);
20866
+ const insertFromPath = async (sessionId, absPath, jsonlUpdatedAt) => {
20867
+ let rel;
20868
+ try {
20869
+ rel = normalizeRelativePath(path29.relative(appDir, absPath));
20870
+ } catch {
20871
+ return;
20872
+ }
20873
+ const meta = await readSessionMetaFromJson(absPath);
20874
+ const updatedAt = jsonlUpdatedAt && Date.parse(jsonlUpdatedAt) > Date.parse(meta.updatedAt) ? jsonlUpdatedAt : meta.updatedAt;
20875
+ upsert.run({
20876
+ sessionId,
20877
+ relativePath: rel,
20878
+ createdAt: meta.createdAt,
20879
+ updatedAt,
20880
+ preview: meta.preview
20881
+ });
20882
+ };
20883
+ for (const [sessionId, entry] of jsonl) {
20884
+ const full = path29.join(appDir, entry.relativePath.replace(/\//g, path29.sep));
20885
+ try {
20886
+ await fs27.access(full);
20887
+ await insertFromPath(sessionId, full, entry.updatedAt);
20888
+ } catch {
20889
+ upsert.run({
20890
+ sessionId,
20891
+ relativePath: entry.relativePath,
20892
+ createdAt: entry.updatedAt,
20893
+ updatedAt: entry.updatedAt,
20894
+ preview: "(no messages)"
20895
+ });
20896
+ }
20897
+ }
20898
+ const sessionsRoot = path29.join(appDir, "sessions");
20899
+ const existing = db.prepare("SELECT session_id FROM agent_sessions").all();
20900
+ const seen = new Set(existing.map((r) => r.session_id));
20901
+ await walkSessionJsonFiles(sessionsRoot, async (absPath) => {
20902
+ const sessionId = path29.basename(absPath, ".json");
20903
+ if (!sessionId || seen.has(sessionId)) return;
20904
+ seen.add(sessionId);
20905
+ await insertFromPath(sessionId, absPath);
20906
+ });
20907
+ db.pragma(`user_version = ${SCHEMA_VERSION}`);
20908
+ }
20909
+ function applySchema(db) {
20910
+ db.exec(`
20911
+ CREATE TABLE IF NOT EXISTS agent_sessions (
20912
+ session_id TEXT PRIMARY KEY NOT NULL,
20913
+ relative_path TEXT NOT NULL,
20914
+ created_at TEXT NOT NULL,
20915
+ updated_at TEXT NOT NULL,
20916
+ preview TEXT NOT NULL DEFAULT '(no messages)'
20917
+ );
20918
+ CREATE INDEX IF NOT EXISTS idx_agent_sessions_updated
20919
+ ON agent_sessions(updated_at DESC);
20920
+ `);
20921
+ }
20922
+ async function ensureSessionIndexDb(appDir = getPreferredAppDir()) {
20923
+ if (dbInstance && dbAppDir === appDir) {
20924
+ return dbInstance;
20925
+ }
20926
+ if (!migratePromise || dbAppDir !== appDir) {
20927
+ migratePromise = (async () => {
20928
+ if (dbInstance) {
20929
+ try {
20930
+ dbInstance.close();
20931
+ } catch {
20932
+ }
20933
+ dbInstance = null;
20934
+ }
20935
+ const BetterSqlite3 = loadSqlite();
20936
+ const dbPath = getSessionDbPath(appDir);
20937
+ mkdirSync3(path29.dirname(dbPath), { recursive: true });
20938
+ const db = new BetterSqlite3(dbPath);
20939
+ db.pragma("journal_mode = WAL");
20940
+ applySchema(db);
20941
+ const version = db.pragma("user_version", { simple: true });
20942
+ if (version < SCHEMA_VERSION) {
20943
+ await runMigrationV1(db, appDir);
20944
+ }
20945
+ dbInstance = db;
20946
+ dbAppDir = appDir;
20947
+ })();
20948
+ }
20949
+ await migratePromise;
20950
+ return dbInstance;
20951
+ }
20952
+ async function upsertSessionIndexRow(row, appDir = getPreferredAppDir()) {
20953
+ const db = await ensureSessionIndexDb(appDir);
20954
+ const stmt = db.prepare(`
20955
+ INSERT INTO agent_sessions (session_id, relative_path, created_at, updated_at, preview)
20956
+ VALUES (@sessionId, @relativePath, @createdAt, @updatedAt, @preview)
20957
+ ON CONFLICT(session_id) DO UPDATE SET
20958
+ relative_path = excluded.relative_path,
20959
+ updated_at = excluded.updated_at,
20960
+ preview = excluded.preview
20961
+ `);
20962
+ stmt.run({
20963
+ sessionId: row.sessionId,
20964
+ relativePath: normalizeRelativePath(row.relativePath),
20965
+ createdAt: row.createdAt,
20966
+ updatedAt: row.updatedAt,
20967
+ preview: row.preview
20968
+ });
20969
+ }
20970
+ async function getSessionPathFromIndex(sessionId, appDir = getPreferredAppDir()) {
20971
+ const db = await ensureSessionIndexDb(appDir);
20972
+ const row = db.prepare("SELECT relative_path FROM agent_sessions WHERE session_id = ?").get(sessionId);
20973
+ return row?.relative_path?.replace(/\//g, path29.sep) ?? null;
20974
+ }
20975
+ async function listSessionsFromIndex(limit = 50, appDir = getPreferredAppDir()) {
20976
+ const db = await ensureSessionIndexDb(appDir);
20977
+ const rows = db.prepare(
20978
+ `SELECT session_id, relative_path, created_at, updated_at, preview
20979
+ FROM agent_sessions
20980
+ ORDER BY updated_at DESC
20981
+ LIMIT ?`
20982
+ ).all(limit);
20983
+ return rows.map((r) => ({
20984
+ sessionId: r.session_id,
20985
+ relativePath: r.relative_path.replace(/\//g, path29.sep),
20986
+ createdAt: r.created_at,
20987
+ updatedAt: r.updated_at,
20988
+ preview: r.preview
20989
+ }));
20990
+ }
20991
+
20992
+ // src/app/agent/session_manager/agent_session_paths.ts
20993
+ var AGENT_SESSION_PATHS_JSONL2 = "agent_session_paths.jsonl";
20664
20994
  async function appendAgentSessionPath(sessionId, relativePath) {
20665
20995
  const app = getPreferredAppDir();
20666
- const indexFile = path29.join(app, AGENT_SESSION_PATHS_JSONL);
20667
- await fs27.mkdir(app, { recursive: true });
20668
- const line = JSON.stringify({
20996
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
20997
+ const rel = relativePath.split(path30.sep).join("/");
20998
+ await upsertSessionIndexRow({
20669
20999
  sessionId,
20670
- relativePath: relativePath.split(path29.sep).join("/"),
20671
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
20672
- }) + "\n";
20673
- await fs27.appendFile(indexFile, line, "utf-8");
21000
+ relativePath: rel,
21001
+ createdAt: now2,
21002
+ updatedAt: now2,
21003
+ preview: "(no messages)"
21004
+ });
20674
21005
  }
20675
- async function resolveSessionRelativePathFromIndex(sessionId) {
20676
- const indexFile = path29.join(getPreferredAppDir(), AGENT_SESSION_PATHS_JSONL);
21006
+ async function resolveSessionRelativePathFromJsonl(sessionId) {
21007
+ const indexFile = path30.join(getPreferredAppDir(), AGENT_SESSION_PATHS_JSONL2);
20677
21008
  let raw;
20678
21009
  try {
20679
- raw = await fs27.readFile(indexFile, "utf-8");
21010
+ raw = await fs28.readFile(indexFile, "utf-8");
20680
21011
  } catch {
20681
21012
  return null;
20682
21013
  }
@@ -20685,37 +21016,42 @@ async function resolveSessionRelativePathFromIndex(sessionId) {
20685
21016
  try {
20686
21017
  const row = JSON.parse(lines[i]);
20687
21018
  if (row.sessionId === sessionId && typeof row.relativePath === "string") {
20688
- return row.relativePath.replace(/\//g, path29.sep);
21019
+ return row.relativePath.replace(/\//g, path30.sep);
20689
21020
  }
20690
21021
  } catch {
20691
21022
  }
20692
21023
  }
20693
21024
  return null;
20694
21025
  }
20695
- async function walkSessionJsonFiles(dir, onFile) {
21026
+ async function resolveSessionRelativePathFromIndex(sessionId) {
21027
+ const fromDb = await getSessionPathFromIndex(sessionId);
21028
+ if (fromDb) return fromDb;
21029
+ return resolveSessionRelativePathFromJsonl(sessionId);
21030
+ }
21031
+ async function walkSessionJsonFiles2(dir, onFile) {
20696
21032
  let entries;
20697
21033
  try {
20698
- entries = await fs27.readdir(dir, { withFileTypes: true });
21034
+ entries = await fs28.readdir(dir, { withFileTypes: true });
20699
21035
  } catch {
20700
21036
  return;
20701
21037
  }
20702
21038
  for (const e of entries) {
20703
21039
  const name = String(e.name);
20704
- const full = path29.join(dir, name);
21040
+ const full = path30.join(dir, name);
20705
21041
  if (e.isDirectory()) {
20706
- await walkSessionJsonFiles(full, onFile);
21042
+ await walkSessionJsonFiles2(full, onFile);
20707
21043
  } else if (e.isFile() && name.endsWith(".json") && !name.includes(".tmp")) {
20708
21044
  await onFile(full);
20709
21045
  }
20710
21046
  }
20711
21047
  }
20712
21048
  async function findSessionFileGlobFallback(sessionId) {
20713
- const sessionsRoot = path29.join(getPreferredAppDir(), "sessions");
21049
+ const sessionsRoot = path30.join(getPreferredAppDir(), "sessions");
20714
21050
  const state2 = { best: null };
20715
- await walkSessionJsonFiles(sessionsRoot, async (abs) => {
20716
- if (path29.basename(abs, ".json") !== sessionId) return;
21051
+ await walkSessionJsonFiles2(sessionsRoot, async (abs) => {
21052
+ if (path30.basename(abs, ".json") !== sessionId) return;
20717
21053
  try {
20718
- const st = await fs27.stat(abs);
21054
+ const st = await fs28.stat(abs);
20719
21055
  if (!st.isFile()) return;
20720
21056
  if (!state2.best || st.mtimeMs > state2.best.m) {
20721
21057
  state2.best = { p: abs, m: st.mtimeMs };
@@ -20726,11 +21062,11 @@ async function findSessionFileGlobFallback(sessionId) {
20726
21062
  return state2.best !== null ? state2.best.p : null;
20727
21063
  }
20728
21064
  async function loadLatestIndexEntriesBySessionId() {
20729
- const indexFile = path29.join(getPreferredAppDir(), AGENT_SESSION_PATHS_JSONL);
21065
+ const indexFile = path30.join(getPreferredAppDir(), AGENT_SESSION_PATHS_JSONL2);
20730
21066
  const map = /* @__PURE__ */ new Map();
20731
21067
  let raw;
20732
21068
  try {
20733
- raw = await fs27.readFile(indexFile, "utf-8");
21069
+ raw = await fs28.readFile(indexFile, "utf-8");
20734
21070
  } catch {
20735
21071
  return map;
20736
21072
  }
@@ -20763,40 +21099,36 @@ function dateFolderFromRelativePath(relativePath) {
20763
21099
  }
20764
21100
  return null;
20765
21101
  }
20766
- function previewFromConversationHistory(history) {
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) {
21102
+ async function enrichCandidateFromFile(absPath, sessionId, lastMtimeMs, opts) {
20779
21103
  let rel;
20780
21104
  try {
20781
- rel = path29.relative(getPreferredAppDir(), absPath);
21105
+ rel = path30.relative(getPreferredAppDir(), absPath);
20782
21106
  } catch {
20783
21107
  rel = absPath;
20784
21108
  }
20785
- const dateFolder = dateFolderFromRelativePath(rel.split(path29.sep).join("/"));
20786
- let preview = "(no messages)";
21109
+ const dateFolder = dateFolderFromRelativePath(rel.split(path30.sep).join("/"));
21110
+ let preview = opts?.preview ?? "(no messages)";
20787
21111
  let lastActivityMs = lastMtimeMs;
20788
- try {
20789
- const raw = await fs27.readFile(absPath, "utf-8");
20790
- const data = JSON.parse(raw);
20791
- preview = previewFromConversationHistory(data.conversation_history);
20792
- const iso = data.last_updated || data.created_at;
20793
- if (iso) {
20794
- const parsed = Date.parse(iso);
20795
- if (Number.isFinite(parsed)) {
20796
- lastActivityMs = Math.max(lastMtimeMs, parsed);
21112
+ if (opts?.updatedAtIso) {
21113
+ const parsed = Date.parse(opts.updatedAtIso);
21114
+ if (Number.isFinite(parsed)) {
21115
+ lastActivityMs = Math.max(lastMtimeMs, parsed);
21116
+ }
21117
+ }
21118
+ if (!opts?.preview || opts.preview === "(no messages)") {
21119
+ try {
21120
+ const raw = await fs28.readFile(absPath, "utf-8");
21121
+ const data = JSON.parse(raw);
21122
+ preview = previewFromConversationHistory(data.conversation_history);
21123
+ const iso = data.last_updated || data.created_at;
21124
+ if (iso) {
21125
+ const parsed = Date.parse(iso);
21126
+ if (Number.isFinite(parsed)) {
21127
+ lastActivityMs = Math.max(lastActivityMs, parsed);
21128
+ }
20797
21129
  }
21130
+ } catch {
20798
21131
  }
20799
- } catch {
20800
21132
  }
20801
21133
  return {
20802
21134
  sessionId,
@@ -20809,13 +21141,29 @@ async function enrichCandidateFromFile(absPath, sessionId, lastMtimeMs) {
20809
21141
  }
20810
21142
  async function listAgentSessionsForResume(limit = 50) {
20811
21143
  const appDir = getPreferredAppDir();
20812
- const sessionsRoot = path29.join(appDir, "sessions");
21144
+ await ensureSessionIndexDb(appDir);
21145
+ const fromIndex = await listSessionsFromIndex(limit * 2, appDir);
20813
21146
  const map = /* @__PURE__ */ new Map();
20814
- await walkSessionJsonFiles(sessionsRoot, async (absPath) => {
20815
- const id = path29.basename(absPath, ".json");
21147
+ for (const row of fromIndex) {
21148
+ const full = path30.join(appDir, row.relativePath);
21149
+ try {
21150
+ const st = await fs28.stat(full);
21151
+ if (!st.isFile()) continue;
21152
+ map.set(row.sessionId, {
21153
+ path: full,
21154
+ lastMtimeMs: st.mtimeMs,
21155
+ preview: row.preview,
21156
+ updatedAt: row.updatedAt
21157
+ });
21158
+ } catch {
21159
+ }
21160
+ }
21161
+ const sessionsRoot = path30.join(appDir, "sessions");
21162
+ await walkSessionJsonFiles2(sessionsRoot, async (absPath) => {
21163
+ const id = path30.basename(absPath, ".json");
20816
21164
  if (!id) return;
20817
21165
  try {
20818
- const st = await fs27.stat(absPath);
21166
+ const st = await fs28.stat(absPath);
20819
21167
  if (!st.isFile()) return;
20820
21168
  const cur = map.get(id);
20821
21169
  if (!cur || st.mtimeMs > cur.lastMtimeMs) {
@@ -20827,9 +21175,9 @@ async function listAgentSessionsForResume(limit = 50) {
20827
21175
  const index = await loadLatestIndexEntriesBySessionId();
20828
21176
  for (const [sessionId, entry] of index) {
20829
21177
  if (map.has(sessionId)) continue;
20830
- const full = path29.join(appDir, entry.relativePath.replace(/\//g, path29.sep));
21178
+ const full = path30.join(appDir, entry.relativePath.replace(/\//g, path30.sep));
20831
21179
  try {
20832
- const st = await fs27.stat(full);
21180
+ const st = await fs28.stat(full);
20833
21181
  if (st.isFile()) {
20834
21182
  map.set(sessionId, { path: full, lastMtimeMs: st.mtimeMs });
20835
21183
  }
@@ -20838,7 +21186,10 @@ async function listAgentSessionsForResume(limit = 50) {
20838
21186
  }
20839
21187
  const enriched = await Promise.all(
20840
21188
  [...map.entries()].map(
20841
- ([sessionId, v]) => enrichCandidateFromFile(v.path, sessionId, v.lastMtimeMs)
21189
+ ([sessionId, v]) => enrichCandidateFromFile(v.path, sessionId, v.lastMtimeMs, {
21190
+ preview: v.preview,
21191
+ updatedAtIso: v.updatedAt
21192
+ })
20842
21193
  )
20843
21194
  );
20844
21195
  return enriched.sort((a, b) => b.lastActivityMs - a.lastActivityMs).slice(0, limit);
@@ -20883,10 +21234,10 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
20883
21234
  const isWin = process.platform === "win32";
20884
21235
  while (attempt <= maxRetries) {
20885
21236
  try {
20886
- const dir = path30.dirname(dest);
20887
- await fs28.mkdir(dir, { recursive: true }).catch(() => {
21237
+ const dir = path31.dirname(dest);
21238
+ await fs29.mkdir(dir, { recursive: true }).catch(() => {
20888
21239
  });
20889
- await fs28.rename(src, dest);
21240
+ await fs29.rename(src, dest);
20890
21241
  return;
20891
21242
  } catch (e) {
20892
21243
  lastErr = e;
@@ -20899,13 +21250,13 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
20899
21250
  }
20900
21251
  }
20901
21252
  try {
20902
- await fs28.access(src);
20903
- const data = await fs28.readFile(src);
20904
- const dir = path30.dirname(dest);
20905
- await fs28.mkdir(dir, { recursive: true }).catch(() => {
21253
+ await fs29.access(src);
21254
+ const data = await fs29.readFile(src);
21255
+ const dir = path31.dirname(dest);
21256
+ await fs29.mkdir(dir, { recursive: true }).catch(() => {
20906
21257
  });
20907
- await fs28.writeFile(dest, data);
20908
- await fs28.unlink(src).catch(() => {
21258
+ await fs29.writeFile(dest, data);
21259
+ await fs29.unlink(src).catch(() => {
20909
21260
  });
20910
21261
  return;
20911
21262
  } catch (fallbackErr) {
@@ -20918,23 +21269,23 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
20918
21269
  }
20919
21270
  async function ensureSessionDir() {
20920
21271
  const appDir = getPreferredAppDir();
20921
- const sessionDir = path30.join(appDir, "sessions");
20922
- await fs28.mkdir(sessionDir, { recursive: true });
21272
+ const sessionDir = path31.join(appDir, "sessions");
21273
+ await fs29.mkdir(sessionDir, { recursive: true });
20923
21274
  return sessionDir;
20924
21275
  }
20925
21276
  async function resolveAgentSessionFilePath(sessionId) {
20926
21277
  const appDir = getPreferredAppDir();
20927
- const legacy = path30.join(appDir, "sessions", `${sessionId}.json`);
21278
+ const legacy = path31.join(appDir, "sessions", `${sessionId}.json`);
20928
21279
  try {
20929
- await fs28.access(legacy);
21280
+ await fs29.access(legacy);
20930
21281
  return legacy;
20931
21282
  } catch {
20932
21283
  }
20933
21284
  const rel = await resolveSessionRelativePathFromIndex(sessionId);
20934
21285
  if (rel) {
20935
- const full = path30.join(appDir, rel);
21286
+ const full = path31.join(appDir, rel);
20936
21287
  try {
20937
- await fs28.access(full);
21288
+ await fs29.access(full);
20938
21289
  return full;
20939
21290
  } catch {
20940
21291
  }
@@ -20947,7 +21298,7 @@ async function loadSession(sessionId) {
20947
21298
  return null;
20948
21299
  }
20949
21300
  try {
20950
- const fileContent = await fs28.readFile(sessionFile, "utf-8");
21301
+ const fileContent = await fs29.readFile(sessionFile, "utf-8");
20951
21302
  const sessionData = JSON.parse(fileContent);
20952
21303
  const memory = {
20953
21304
  historyAnchor: sessionData.history_anchor ?? null,
@@ -20968,16 +21319,16 @@ async function loadOrcreateSession(sessionId) {
20968
21319
  const y = String(now2.getFullYear());
20969
21320
  const mo = String(now2.getMonth() + 1).padStart(2, "0");
20970
21321
  const d = String(now2.getDate()).padStart(2, "0");
20971
- const datedDir = path30.join(sessionsRoot, y, mo, d);
20972
- await fs28.mkdir(datedDir, { recursive: true });
20973
- const sessionFile = path30.join(datedDir, `${sessionId}.json`);
21322
+ const datedDir = path31.join(sessionsRoot, y, mo, d);
21323
+ await fs29.mkdir(datedDir, { recursive: true });
21324
+ const sessionFile = path31.join(datedDir, `${sessionId}.json`);
20974
21325
  const newSessionData = {
20975
21326
  session_id: sessionId,
20976
21327
  created_at: now2.toISOString(),
20977
21328
  conversation_history: []
20978
21329
  };
20979
- await fs28.writeFile(sessionFile, JSON.stringify(newSessionData, null, 2), "utf-8");
20980
- const relToApp = path30.relative(getPreferredAppDir(), sessionFile);
21330
+ await fs29.writeFile(sessionFile, JSON.stringify(newSessionData, null, 2), "utf-8");
21331
+ const relToApp = path31.relative(getPreferredAppDir(), sessionFile);
20981
21332
  await appendAgentSessionPath(sessionId, relToApp);
20982
21333
  const emptyMemory = {
20983
21334
  historyAnchor: null,
@@ -20989,12 +21340,12 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
20989
21340
  await withFileLock(sessionFile, async () => {
20990
21341
  let sessionData;
20991
21342
  try {
20992
- const dir = path30.dirname(sessionFile);
20993
- await fs28.mkdir(dir, { recursive: true });
21343
+ const dir = path31.dirname(sessionFile);
21344
+ await fs29.mkdir(dir, { recursive: true });
20994
21345
  } catch {
20995
21346
  }
20996
21347
  try {
20997
- const fileContent = await fs28.readFile(sessionFile, "utf-8");
21348
+ const fileContent = await fs29.readFile(sessionFile, "utf-8");
20998
21349
  sessionData = JSON.parse(fileContent);
20999
21350
  } catch (error) {
21000
21351
  const code = error && error.code;
@@ -21005,14 +21356,14 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
21005
21356
  console.warn(`An unknown error occurred while reading ${sessionFile}. Re-initializing.`, error);
21006
21357
  }
21007
21358
  }
21008
- const sessionId = path30.basename(sessionFile, ".json");
21359
+ const sessionId = path31.basename(sessionFile, ".json");
21009
21360
  sessionData = {
21010
21361
  session_id: sessionId,
21011
21362
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
21012
21363
  conversation_history: []
21013
21364
  };
21014
21365
  try {
21015
- await fs28.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
21366
+ await fs29.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
21016
21367
  } catch {
21017
21368
  }
21018
21369
  }
@@ -21028,8 +21379,26 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
21028
21379
  }
21029
21380
  const tempSessionFile = `${sessionFile}.${Date.now()}.tmp`;
21030
21381
  try {
21031
- await fs28.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
21382
+ await fs29.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
21032
21383
  await safeRenameWithRetry(tempSessionFile, sessionFile);
21384
+ const sessionId = sessionData.session_id ?? path31.basename(sessionFile, ".json");
21385
+ const updatedAt = sessionData.last_updated ?? (/* @__PURE__ */ new Date()).toISOString();
21386
+ const createdAt = sessionData.created_at ?? updatedAt;
21387
+ let relToApp;
21388
+ try {
21389
+ relToApp = path31.relative(getPreferredAppDir(), sessionFile);
21390
+ } catch {
21391
+ relToApp = sessionFile;
21392
+ }
21393
+ await upsertSessionIndexRow({
21394
+ sessionId,
21395
+ relativePath: relToApp,
21396
+ createdAt,
21397
+ updatedAt,
21398
+ preview: previewFromConversationHistory(
21399
+ history
21400
+ )
21401
+ });
21033
21402
  } catch (writeError) {
21034
21403
  if (writeError instanceof Error) {
21035
21404
  console.error(`Fatal error saving session to ${sessionFile}: ${writeError.message}`);
@@ -21037,7 +21406,7 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
21037
21406
  console.error(`An unknown fatal error occurred while saving session to ${sessionFile}:`, writeError);
21038
21407
  }
21039
21408
  try {
21040
- await fs28.unlink(tempSessionFile);
21409
+ await fs29.unlink(tempSessionFile);
21041
21410
  } catch {
21042
21411
  }
21043
21412
  }
@@ -21052,16 +21421,30 @@ async function saveSessionHistory(sessionFile, history, memory) {
21052
21421
  });
21053
21422
  debouncedSave(sessionFile, cleanHistory, memory);
21054
21423
  }
21424
+ async function saveSessionHistoryNow(sessionFile, history, memory) {
21425
+ const pending = pendingSaves.get(sessionFile);
21426
+ if (pending) {
21427
+ clearTimeout(pending.timer);
21428
+ pendingSaves.delete(sessionFile);
21429
+ }
21430
+ const cleanHistory = history.filter((msg) => {
21431
+ if (msg.role === "user" && typeof msg.content === "string") {
21432
+ return !msg.content.startsWith("[SKILL:");
21433
+ }
21434
+ return true;
21435
+ });
21436
+ await doSaveSessionHistory(sessionFile, cleanHistory, memory);
21437
+ }
21055
21438
 
21056
21439
  // src/app/agent/core/prompt/prompt_builder.ts
21057
21440
  import os24 from "os";
21058
- import fs35 from "fs";
21059
- import path37 from "path";
21441
+ import fs36 from "fs";
21442
+ import path38 from "path";
21060
21443
  import { execSync as execSync3 } from "child_process";
21061
21444
 
21062
21445
  // src/app/agent/skills/skill_loader.ts
21063
- import fs29 from "fs";
21064
- import path31 from "path";
21446
+ import fs30 from "fs";
21447
+ import path32 from "path";
21065
21448
  import os20 from "os";
21066
21449
  import { fileURLToPath as fileURLToPath3 } from "node:url";
21067
21450
  var SkillLoader = class _SkillLoader {
@@ -21071,8 +21454,8 @@ var SkillLoader = class _SkillLoader {
21071
21454
  cache = /* @__PURE__ */ new Map();
21072
21455
  conflicts = [];
21073
21456
  constructor(projectRoot, bundledDir) {
21074
- this.projectSkillsDir = path31.join(projectRoot, ".bluma", "skills");
21075
- this.globalSkillsDir = path31.join(os20.homedir(), ".bluma", "skills");
21457
+ this.projectSkillsDir = path32.join(projectRoot, ".bluma", "skills");
21458
+ this.globalSkillsDir = path32.join(os20.homedir(), ".bluma", "skills");
21076
21459
  this.bundledSkillsDir = bundledDir || _SkillLoader.resolveBundledDir();
21077
21460
  }
21078
21461
  /**
@@ -21081,48 +21464,48 @@ var SkillLoader = class _SkillLoader {
21081
21464
  */
21082
21465
  static resolveBundledDir() {
21083
21466
  if (process.env.JEST_WORKER_ID !== void 0 || process.env.NODE_ENV === "test") {
21084
- return path31.join(process.cwd(), "dist", "config", "skills");
21467
+ return path32.join(process.cwd(), "dist", "config", "skills");
21085
21468
  }
21086
21469
  const candidates = [];
21087
21470
  const push = (p) => {
21088
- const abs = path31.resolve(p);
21471
+ const abs = path32.resolve(p);
21089
21472
  if (!candidates.includes(abs)) {
21090
21473
  candidates.push(abs);
21091
21474
  }
21092
21475
  };
21093
21476
  let argvBundled = null;
21094
21477
  try {
21095
- const bundleDir = path31.dirname(fileURLToPath3(import.meta.url));
21096
- push(path31.join(bundleDir, "config", "skills"));
21478
+ const bundleDir = path32.dirname(fileURLToPath3(import.meta.url));
21479
+ push(path32.join(bundleDir, "config", "skills"));
21097
21480
  } catch {
21098
21481
  }
21099
21482
  const argv1 = process.argv[1];
21100
21483
  if (argv1 && !argv1.startsWith("-")) {
21101
21484
  try {
21102
21485
  let resolved = argv1;
21103
- if (path31.isAbsolute(argv1) && fs29.existsSync(argv1)) {
21104
- resolved = fs29.realpathSync(argv1);
21105
- } else if (!path31.isAbsolute(argv1)) {
21106
- resolved = path31.resolve(process.cwd(), argv1);
21486
+ if (path32.isAbsolute(argv1) && fs30.existsSync(argv1)) {
21487
+ resolved = fs30.realpathSync(argv1);
21488
+ } else if (!path32.isAbsolute(argv1)) {
21489
+ resolved = path32.resolve(process.cwd(), argv1);
21107
21490
  }
21108
- const scriptDir = path31.dirname(resolved);
21109
- argvBundled = path31.join(scriptDir, "config", "skills");
21491
+ const scriptDir = path32.dirname(resolved);
21492
+ argvBundled = path32.join(scriptDir, "config", "skills");
21110
21493
  push(argvBundled);
21111
21494
  } catch {
21112
21495
  }
21113
21496
  }
21114
21497
  for (const abs of candidates) {
21115
- if (fs29.existsSync(abs)) {
21498
+ if (fs30.existsSync(abs)) {
21116
21499
  return abs;
21117
21500
  }
21118
21501
  }
21119
21502
  try {
21120
- return path31.join(path31.dirname(fileURLToPath3(import.meta.url)), "config", "skills");
21503
+ return path32.join(path32.dirname(fileURLToPath3(import.meta.url)), "config", "skills");
21121
21504
  } catch {
21122
21505
  if (argvBundled) {
21123
21506
  return argvBundled;
21124
21507
  }
21125
- return path31.join(os20.homedir(), ".bluma", "__bundled_skills_unresolved__");
21508
+ return path32.join(os20.homedir(), ".bluma", "__bundled_skills_unresolved__");
21126
21509
  }
21127
21510
  }
21128
21511
  /**
@@ -21151,8 +21534,8 @@ var SkillLoader = class _SkillLoader {
21151
21534
  this.conflicts.push({
21152
21535
  name: skill.name,
21153
21536
  userSource: source,
21154
- userPath: path31.join(dir, skill.name, "SKILL.md"),
21155
- bundledPath: path31.join(this.bundledSkillsDir, skill.name, "SKILL.md")
21537
+ userPath: path32.join(dir, skill.name, "SKILL.md"),
21538
+ bundledPath: path32.join(this.bundledSkillsDir, skill.name, "SKILL.md")
21156
21539
  });
21157
21540
  continue;
21158
21541
  }
@@ -21160,20 +21543,20 @@ var SkillLoader = class _SkillLoader {
21160
21543
  }
21161
21544
  }
21162
21545
  listFromDir(dir, source) {
21163
- if (!fs29.existsSync(dir)) return [];
21546
+ if (!fs30.existsSync(dir)) return [];
21164
21547
  try {
21165
- return fs29.readdirSync(dir).filter((d) => {
21166
- const fullPath = path31.join(dir, d);
21167
- return fs29.statSync(fullPath).isDirectory() && fs29.existsSync(path31.join(fullPath, "SKILL.md"));
21168
- }).map((d) => this.loadMetadataFromPath(path31.join(dir, d, "SKILL.md"), d, source)).filter((m) => m !== null);
21548
+ return fs30.readdirSync(dir).filter((d) => {
21549
+ const fullPath = path32.join(dir, d);
21550
+ return fs30.statSync(fullPath).isDirectory() && fs30.existsSync(path32.join(fullPath, "SKILL.md"));
21551
+ }).map((d) => this.loadMetadataFromPath(path32.join(dir, d, "SKILL.md"), d, source)).filter((m) => m !== null);
21169
21552
  } catch {
21170
21553
  return [];
21171
21554
  }
21172
21555
  }
21173
21556
  loadMetadataFromPath(skillPath, skillName, source) {
21174
- if (!fs29.existsSync(skillPath)) return null;
21557
+ if (!fs30.existsSync(skillPath)) return null;
21175
21558
  try {
21176
- const raw = fs29.readFileSync(skillPath, "utf-8");
21559
+ const raw = fs30.readFileSync(skillPath, "utf-8");
21177
21560
  const parsed = this.parseFrontmatter(raw);
21178
21561
  return {
21179
21562
  name: parsed.name || skillName,
@@ -21195,12 +21578,12 @@ var SkillLoader = class _SkillLoader {
21195
21578
  */
21196
21579
  load(name) {
21197
21580
  if (this.cache.has(name)) return this.cache.get(name);
21198
- const bundledPath = path31.join(this.bundledSkillsDir, name, "SKILL.md");
21199
- const projectPath = path31.join(this.projectSkillsDir, name, "SKILL.md");
21200
- const globalPath = path31.join(this.globalSkillsDir, name, "SKILL.md");
21201
- const existsBundled = fs29.existsSync(bundledPath);
21202
- const existsProject = fs29.existsSync(projectPath);
21203
- const existsGlobal = fs29.existsSync(globalPath);
21581
+ const bundledPath = path32.join(this.bundledSkillsDir, name, "SKILL.md");
21582
+ const projectPath = path32.join(this.projectSkillsDir, name, "SKILL.md");
21583
+ const globalPath = path32.join(this.globalSkillsDir, name, "SKILL.md");
21584
+ const existsBundled = fs30.existsSync(bundledPath);
21585
+ const existsProject = fs30.existsSync(projectPath);
21586
+ const existsGlobal = fs30.existsSync(globalPath);
21204
21587
  if (existsBundled && (existsProject || existsGlobal)) {
21205
21588
  const conflictSource = existsProject ? "project" : "global";
21206
21589
  const conflictPath = existsProject ? projectPath : globalPath;
@@ -21239,9 +21622,9 @@ var SkillLoader = class _SkillLoader {
21239
21622
  }
21240
21623
  loadFromPath(skillPath, name, source) {
21241
21624
  try {
21242
- const raw = fs29.readFileSync(skillPath, "utf-8");
21625
+ const raw = fs30.readFileSync(skillPath, "utf-8");
21243
21626
  const parsed = this.parseFrontmatter(raw);
21244
- const skillDir = path31.dirname(skillPath);
21627
+ const skillDir = path32.dirname(skillPath);
21245
21628
  return {
21246
21629
  name: parsed.name || name,
21247
21630
  description: parsed.description || "",
@@ -21250,22 +21633,22 @@ var SkillLoader = class _SkillLoader {
21250
21633
  version: parsed.version,
21251
21634
  author: parsed.author,
21252
21635
  license: parsed.license,
21253
- references: this.scanAssets(path31.join(skillDir, "references")),
21254
- scripts: this.scanAssets(path31.join(skillDir, "scripts"))
21636
+ references: this.scanAssets(path32.join(skillDir, "references")),
21637
+ scripts: this.scanAssets(path32.join(skillDir, "scripts"))
21255
21638
  };
21256
21639
  } catch {
21257
21640
  return null;
21258
21641
  }
21259
21642
  }
21260
21643
  scanAssets(dir) {
21261
- if (!fs29.existsSync(dir)) return [];
21644
+ if (!fs30.existsSync(dir)) return [];
21262
21645
  try {
21263
- return fs29.readdirSync(dir).filter((f) => {
21264
- const fp = path31.join(dir, f);
21265
- return fs29.statSync(fp).isFile();
21646
+ return fs30.readdirSync(dir).filter((f) => {
21647
+ const fp = path32.join(dir, f);
21648
+ return fs30.statSync(fp).isFile();
21266
21649
  }).map((f) => ({
21267
21650
  name: f,
21268
- path: path31.resolve(dir, f)
21651
+ path: path32.resolve(dir, f)
21269
21652
  }));
21270
21653
  } catch {
21271
21654
  return [];
@@ -21322,10 +21705,10 @@ var SkillLoader = class _SkillLoader {
21322
21705
  this.cache.clear();
21323
21706
  }
21324
21707
  exists(name) {
21325
- const bundledPath = path31.join(this.bundledSkillsDir, name, "SKILL.md");
21326
- const projectPath = path31.join(this.projectSkillsDir, name, "SKILL.md");
21327
- const globalPath = path31.join(this.globalSkillsDir, name, "SKILL.md");
21328
- return fs29.existsSync(bundledPath) || fs29.existsSync(projectPath) || fs29.existsSync(globalPath);
21708
+ const bundledPath = path32.join(this.bundledSkillsDir, name, "SKILL.md");
21709
+ const projectPath = path32.join(this.projectSkillsDir, name, "SKILL.md");
21710
+ const globalPath = path32.join(this.globalSkillsDir, name, "SKILL.md");
21711
+ return fs30.existsSync(bundledPath) || fs30.existsSync(projectPath) || fs30.existsSync(globalPath);
21329
21712
  }
21330
21713
  /**
21331
21714
  * Retorna conflitos detetados (skills do utilizador com mesmo nome de nativas).
@@ -21357,13 +21740,13 @@ var SkillLoader = class _SkillLoader {
21357
21740
  };
21358
21741
 
21359
21742
  // src/app/agent/core/prompt/workspace_snapshot.ts
21360
- import fs31 from "fs";
21361
- import path33 from "path";
21743
+ import fs32 from "fs";
21744
+ import path34 from "path";
21362
21745
  import { execSync as execSync2 } from "child_process";
21363
21746
 
21364
21747
  // src/app/agent/utils/blumamd.ts
21365
- import fs30 from "fs";
21366
- import path32 from "path";
21748
+ import fs31 from "fs";
21749
+ import path33 from "path";
21367
21750
  import os21 from "os";
21368
21751
  import { execSync } from "child_process";
21369
21752
  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 +21876,12 @@ var TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
21493
21876
  function expandIncludePath(includePath, baseDir) {
21494
21877
  const cleanPath = includePath.startsWith("@") ? includePath.slice(1) : includePath;
21495
21878
  if (cleanPath.startsWith("~")) {
21496
- return path32.join(os21.homedir(), cleanPath.slice(1));
21879
+ return path33.join(os21.homedir(), cleanPath.slice(1));
21497
21880
  }
21498
- if (path32.isAbsolute(cleanPath)) {
21881
+ if (path33.isAbsolute(cleanPath)) {
21499
21882
  return cleanPath;
21500
21883
  }
21501
- return path32.resolve(baseDir, cleanPath);
21884
+ return path33.resolve(baseDir, cleanPath);
21502
21885
  }
21503
21886
  function processIncludes(content, baseDir, processedFiles) {
21504
21887
  const lines = content.split("\n");
@@ -21507,20 +21890,20 @@ function processIncludes(content, baseDir, processedFiles) {
21507
21890
  const includeMatch = line.match(/^@\s*([^\s]+)/);
21508
21891
  if (includeMatch) {
21509
21892
  const includePath = expandIncludePath(includeMatch[1], baseDir);
21510
- const normalizedPath = path32.normalize(includePath);
21893
+ const normalizedPath = path33.normalize(includePath);
21511
21894
  if (processedFiles.has(normalizedPath)) {
21512
21895
  result.push(`<!-- Circular include prevented: ${includeMatch[1]} -->`);
21513
21896
  continue;
21514
21897
  }
21515
- const ext = path32.extname(includePath).toLowerCase();
21898
+ const ext = path33.extname(includePath).toLowerCase();
21516
21899
  if (!TEXT_FILE_EXTENSIONS.has(ext)) {
21517
21900
  result.push(`<!-- Include skipped (unsupported extension): ${includeMatch[1]} -->`);
21518
21901
  continue;
21519
21902
  }
21520
21903
  try {
21521
- const includedContent = fs30.readFileSync(includePath, "utf-8");
21904
+ const includedContent = fs31.readFileSync(includePath, "utf-8");
21522
21905
  processedFiles.add(normalizedPath);
21523
- const processedContent = processIncludes(includedContent, path32.dirname(includePath), processedFiles);
21906
+ const processedContent = processIncludes(includedContent, path33.dirname(includePath), processedFiles);
21524
21907
  result.push(`
21525
21908
  <!-- BEGIN INCLUDE ${includeMatch[1]} -->
21526
21909
  `);
@@ -21566,9 +21949,9 @@ function parseFrontmatterPaths(paths2) {
21566
21949
  }
21567
21950
  function readMemoryFile(filePath, type, includeBasePath) {
21568
21951
  try {
21569
- const rawContent = fs30.readFileSync(filePath, "utf-8");
21570
- const baseDir = includeBasePath || path32.dirname(filePath);
21571
- const processedFiles = /* @__PURE__ */ new Set([path32.normalize(filePath)]);
21952
+ const rawContent = fs31.readFileSync(filePath, "utf-8");
21953
+ const baseDir = includeBasePath || path33.dirname(filePath);
21954
+ const processedFiles = /* @__PURE__ */ new Set([path33.normalize(filePath)]);
21572
21955
  const { frontmatter, content: withoutFrontmatter } = parseFrontmatter(rawContent);
21573
21956
  const globs = parseFrontmatterPaths(frontmatter.paths);
21574
21957
  const processedContent = processIncludes(withoutFrontmatter, baseDir, processedFiles);
@@ -21590,15 +21973,15 @@ function readMemoryFile(filePath, type, includeBasePath) {
21590
21973
  }
21591
21974
  function findGitRoot(startDir) {
21592
21975
  let current = startDir;
21593
- while (current !== path32.dirname(current)) {
21594
- const gitPath = path32.join(current, ".git");
21976
+ while (current !== path33.dirname(current)) {
21977
+ const gitPath = path33.join(current, ".git");
21595
21978
  try {
21596
- if (fs30.existsSync(gitPath)) {
21979
+ if (fs31.existsSync(gitPath)) {
21597
21980
  return current;
21598
21981
  }
21599
21982
  } catch {
21600
21983
  }
21601
- current = path32.dirname(current);
21984
+ current = path33.dirname(current);
21602
21985
  }
21603
21986
  return null;
21604
21987
  }
@@ -21623,17 +22006,17 @@ function getGitUserInfo(cwd2) {
21623
22006
  }
21624
22007
  function processRulesDirectory(rulesDir, type, processedPaths, conditionalRule = false) {
21625
22008
  const result = [];
21626
- if (!fs30.existsSync(rulesDir)) {
22009
+ if (!fs31.existsSync(rulesDir)) {
21627
22010
  return result;
21628
22011
  }
21629
22012
  try {
21630
- const entries = fs30.readdirSync(rulesDir, { withFileTypes: true });
22013
+ const entries = fs31.readdirSync(rulesDir, { withFileTypes: true });
21631
22014
  for (const entry of entries) {
21632
- const entryPath = path32.join(rulesDir, entry.name);
22015
+ const entryPath = path33.join(rulesDir, entry.name);
21633
22016
  if (entry.isDirectory()) {
21634
22017
  result.push(...processRulesDirectory(entryPath, type, processedPaths, conditionalRule));
21635
22018
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
21636
- const normalizedPath = path32.normalize(entryPath);
22019
+ const normalizedPath = path33.normalize(entryPath);
21637
22020
  if (processedPaths.has(normalizedPath)) {
21638
22021
  continue;
21639
22022
  }
@@ -21669,13 +22052,13 @@ function loadManagedMemory() {
21669
22052
  function loadUserMemory() {
21670
22053
  const files = [];
21671
22054
  const homeDir = os21.homedir();
21672
- const userBlumaDir = path32.join(homeDir, ".bluma");
21673
- const userBlumaMd = path32.join(userBlumaDir, "BLUMA.md");
22055
+ const userBlumaDir = path33.join(homeDir, ".bluma");
22056
+ const userBlumaMd = path33.join(userBlumaDir, "BLUMA.md");
21674
22057
  const userFile = readMemoryFile(userBlumaMd, "User");
21675
22058
  if (userFile && userFile.content.trim()) {
21676
22059
  files.push(userFile);
21677
22060
  }
21678
- const userRulesDir = path32.join(userBlumaDir, "rules");
22061
+ const userRulesDir = path33.join(userBlumaDir, "rules");
21679
22062
  const processedPaths = /* @__PURE__ */ new Set();
21680
22063
  files.push(...processRulesDirectory(userRulesDir, "User", processedPaths, false));
21681
22064
  return files;
@@ -21687,10 +22070,10 @@ function loadProjectMemory(cwd2) {
21687
22070
  let currentDir = cwd2;
21688
22071
  const MAX_TRAVERSAL_DEPTH = 20;
21689
22072
  let depth = 0;
21690
- const normalizedGitRoot = path32.resolve(gitRoot);
21691
- while (currentDir !== path32.dirname(currentDir) && path32.resolve(currentDir).startsWith(normalizedGitRoot) && depth < MAX_TRAVERSAL_DEPTH) {
22073
+ const normalizedGitRoot = path33.resolve(gitRoot);
22074
+ while (currentDir !== path33.dirname(currentDir) && path33.resolve(currentDir).startsWith(normalizedGitRoot) && depth < MAX_TRAVERSAL_DEPTH) {
21692
22075
  dirs.push(currentDir);
21693
- currentDir = path32.dirname(currentDir);
22076
+ currentDir = path33.dirname(currentDir);
21694
22077
  depth++;
21695
22078
  }
21696
22079
  if (!dirs.includes(gitRoot)) {
@@ -21698,7 +22081,7 @@ function loadProjectMemory(cwd2) {
21698
22081
  }
21699
22082
  const processedPaths = /* @__PURE__ */ new Set();
21700
22083
  for (const dir of dirs.reverse()) {
21701
- const projectBlumaMd = path32.join(dir, "BLUMA.md");
22084
+ const projectBlumaMd = path33.join(dir, "BLUMA.md");
21702
22085
  if (!processedPaths.has(projectBlumaMd)) {
21703
22086
  processedPaths.add(projectBlumaMd);
21704
22087
  const projectFile = readMemoryFile(projectBlumaMd, "Project");
@@ -21706,7 +22089,7 @@ function loadProjectMemory(cwd2) {
21706
22089
  files.push(projectFile);
21707
22090
  }
21708
22091
  }
21709
- const blumaDirBlumaMd = path32.join(dir, ".bluma", "BLUMA.md");
22092
+ const blumaDirBlumaMd = path33.join(dir, ".bluma", "BLUMA.md");
21710
22093
  if (!processedPaths.has(blumaDirBlumaMd)) {
21711
22094
  processedPaths.add(blumaDirBlumaMd);
21712
22095
  const blumaDirFile = readMemoryFile(blumaDirBlumaMd, "Project");
@@ -21714,10 +22097,10 @@ function loadProjectMemory(cwd2) {
21714
22097
  files.push(blumaDirFile);
21715
22098
  }
21716
22099
  }
21717
- const rulesDir = path32.join(dir, ".bluma", "rules");
22100
+ const rulesDir = path33.join(dir, ".bluma", "rules");
21718
22101
  files.push(...processRulesDirectory(rulesDir, "Project", processedPaths, false));
21719
22102
  }
21720
- const localBlumaMd = path32.join(cwd2, "BLUMA.local.md");
22103
+ const localBlumaMd = path33.join(cwd2, "BLUMA.local.md");
21721
22104
  if (!processedPaths.has(localBlumaMd)) {
21722
22105
  processedPaths.add(localBlumaMd);
21723
22106
  const localFile = readMemoryFile(localBlumaMd, "Local");
@@ -21803,10 +22186,10 @@ var LIMITS = {
21803
22186
  };
21804
22187
  function safeReadFile(filePath, maxChars) {
21805
22188
  try {
21806
- if (!fs31.existsSync(filePath)) return null;
21807
- const st = fs31.statSync(filePath);
22189
+ if (!fs32.existsSync(filePath)) return null;
22190
+ const st = fs32.statSync(filePath);
21808
22191
  if (!st.isFile()) return null;
21809
- const raw = fs31.readFileSync(filePath, "utf8");
22192
+ const raw = fs32.readFileSync(filePath, "utf8");
21810
22193
  if (raw.includes("\0")) return null;
21811
22194
  if (raw.length <= maxChars) return raw;
21812
22195
  return `${raw.slice(0, maxChars)}
@@ -21818,7 +22201,7 @@ function safeReadFile(filePath, maxChars) {
21818
22201
  }
21819
22202
  function tryReadReadme(cwd2) {
21820
22203
  for (const name of ["README.md", "README.MD", "readme.md", "Readme.md"]) {
21821
- const c = safeReadFile(path33.join(cwd2, name), LIMITS.readme);
22204
+ const c = safeReadFile(path34.join(cwd2, name), LIMITS.readme);
21822
22205
  if (c) return `(${name})
21823
22206
  ${c}`;
21824
22207
  }
@@ -21826,14 +22209,14 @@ ${c}`;
21826
22209
  }
21827
22210
  function tryReadBluMaMd(cwd2) {
21828
22211
  const paths2 = [
21829
- path33.join(cwd2, "BluMa.md"),
21830
- path33.join(cwd2, "BLUMA.md"),
21831
- path33.join(cwd2, ".bluma", "BluMa.md")
22212
+ path34.join(cwd2, "BluMa.md"),
22213
+ path34.join(cwd2, "BLUMA.md"),
22214
+ path34.join(cwd2, ".bluma", "BluMa.md")
21832
22215
  ];
21833
22216
  for (const p of paths2) {
21834
22217
  const c = safeReadFile(p, LIMITS.blumaMd);
21835
22218
  if (c) {
21836
- const rel = path33.relative(cwd2, p) || p;
22219
+ const rel = path34.relative(cwd2, p) || p;
21837
22220
  return `(${rel})
21838
22221
  ${c}`;
21839
22222
  }
@@ -21841,10 +22224,10 @@ ${c}`;
21841
22224
  return null;
21842
22225
  }
21843
22226
  function summarizePackageJson(cwd2) {
21844
- const p = path33.join(cwd2, "package.json");
22227
+ const p = path34.join(cwd2, "package.json");
21845
22228
  try {
21846
- if (!fs31.existsSync(p)) return null;
21847
- const pkg = JSON.parse(fs31.readFileSync(p, "utf8"));
22229
+ if (!fs32.existsSync(p)) return null;
22230
+ const pkg = JSON.parse(fs32.readFileSync(p, "utf8"));
21848
22231
  const scripts = pkg.scripts;
21849
22232
  let scriptKeys = "";
21850
22233
  if (scripts && typeof scripts === "object" && !Array.isArray(scripts)) {
@@ -21877,7 +22260,7 @@ function summarizePackageJson(cwd2) {
21877
22260
  }
21878
22261
  function topLevelListing(cwd2) {
21879
22262
  try {
21880
- const names = fs31.readdirSync(cwd2, { withFileTypes: true });
22263
+ const names = fs32.readdirSync(cwd2, { withFileTypes: true });
21881
22264
  const sorted = [...names].sort((a, b) => a.name.localeCompare(b.name));
21882
22265
  const limited = sorted.slice(0, LIMITS.topDirEntries);
21883
22266
  const lines = limited.map((d) => `${d.name}${d.isDirectory() ? "/" : ""}`);
@@ -21935,7 +22318,7 @@ function buildWorkspaceSnapshot(cwd2) {
21935
22318
  parts.push(pkg);
21936
22319
  parts.push("```\n");
21937
22320
  }
21938
- const py = safeReadFile(path33.join(cwd2, "pyproject.toml"), LIMITS.pyproject);
22321
+ const py = safeReadFile(path34.join(cwd2, "pyproject.toml"), LIMITS.pyproject);
21939
22322
  if (py) {
21940
22323
  parts.push("### pyproject.toml (excerpt)\n```toml");
21941
22324
  parts.push(py);
@@ -21953,15 +22336,15 @@ function buildWorkspaceSnapshot(cwd2) {
21953
22336
  parts.push(bluma);
21954
22337
  parts.push("```\n");
21955
22338
  }
21956
- const contrib = safeReadFile(path33.join(cwd2, "CONTRIBUTING.md"), LIMITS.contributing);
22339
+ const contrib = safeReadFile(path34.join(cwd2, "CONTRIBUTING.md"), LIMITS.contributing);
21957
22340
  if (contrib) {
21958
22341
  parts.push("### CONTRIBUTING.md (excerpt)\n```markdown");
21959
22342
  parts.push(contrib);
21960
22343
  parts.push("```\n");
21961
22344
  }
21962
- const chlog = safeReadFile(path33.join(cwd2, "CHANGELOG.md"), LIMITS.changelog);
22345
+ const chlog = safeReadFile(path34.join(cwd2, "CHANGELOG.md"), LIMITS.changelog);
21963
22346
  if (!chlog) {
21964
- const alt = safeReadFile(path33.join(cwd2, "CHANGES.md"), LIMITS.changelog);
22347
+ const alt = safeReadFile(path34.join(cwd2, "CHANGES.md"), LIMITS.changelog);
21965
22348
  if (alt) {
21966
22349
  parts.push("### CHANGES.md (excerpt)\n```markdown");
21967
22350
  parts.push(alt);
@@ -21997,14 +22380,14 @@ function buildWorkspaceSnapshot(cwd2) {
21997
22380
  } else {
21998
22381
  parts.push("### Git\n(not a git work tree, or `git` unavailable)\n");
21999
22382
  }
22000
- const tsconfig = safeReadFile(path33.join(cwd2, "tsconfig.json"), LIMITS.tsconfig);
22383
+ const tsconfig = safeReadFile(path34.join(cwd2, "tsconfig.json"), LIMITS.tsconfig);
22001
22384
  if (tsconfig) {
22002
22385
  parts.push("### tsconfig.json (excerpt)\n```json");
22003
22386
  parts.push(tsconfig);
22004
22387
  parts.push("```\n");
22005
22388
  }
22006
22389
  for (const name of ["Dockerfile", "dockerfile", "Dockerfile.prod", "Dockerfile.dev"]) {
22007
- const df = safeReadFile(path33.join(cwd2, name), LIMITS.dockerfile);
22390
+ const df = safeReadFile(path34.join(cwd2, name), LIMITS.dockerfile);
22008
22391
  if (df) {
22009
22392
  parts.push(`### ${name} (excerpt)
22010
22393
  `);
@@ -22014,7 +22397,7 @@ function buildWorkspaceSnapshot(cwd2) {
22014
22397
  }
22015
22398
  }
22016
22399
  for (const name of ["docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"]) {
22017
- const dc = safeReadFile(path33.join(cwd2, name), LIMITS.dockerfile);
22400
+ const dc = safeReadFile(path34.join(cwd2, name), LIMITS.dockerfile);
22018
22401
  if (dc) {
22019
22402
  parts.push(`### ${name} (excerpt)
22020
22403
  `);
@@ -22029,12 +22412,12 @@ function buildWorkspaceSnapshot(cwd2) {
22029
22412
  ".circleci/config.yml",
22030
22413
  "Jenkinsfile"
22031
22414
  ]) {
22032
- const ciFile = path33.join(cwd2, ciPath);
22033
- if (fs31.existsSync(ciFile)) {
22034
- const st = fs31.statSync(ciFile);
22415
+ const ciFile = path34.join(cwd2, ciPath);
22416
+ if (fs32.existsSync(ciFile)) {
22417
+ const st = fs32.statSync(ciFile);
22035
22418
  if (st.isDirectory()) {
22036
22419
  try {
22037
- const wfFiles = fs31.readdirSync(ciFile).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
22420
+ const wfFiles = fs32.readdirSync(ciFile).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
22038
22421
  if (wfFiles.length > 0) {
22039
22422
  parts.push(`### GitHub Actions workflows
22040
22423
  \`${wfFiles.join("`, `")}\`
@@ -22045,7 +22428,7 @@ function buildWorkspaceSnapshot(cwd2) {
22045
22428
  } else {
22046
22429
  const ci = safeReadFile(ciFile, LIMITS.ciConfig);
22047
22430
  if (ci) {
22048
- parts.push(`### CI config (${path33.basename(ciPath)})
22431
+ parts.push(`### CI config (${path34.basename(ciPath)})
22049
22432
  `);
22050
22433
  parts.push(ci);
22051
22434
  parts.push("\n");
@@ -22054,8 +22437,8 @@ function buildWorkspaceSnapshot(cwd2) {
22054
22437
  }
22055
22438
  }
22056
22439
  for (const depFile of ["requirements.txt", "Pipfile", "poetry.lock", "Gemfile", "go.sum", "Cargo.lock"]) {
22057
- const depPath = path33.join(cwd2, depFile);
22058
- if (fs31.existsSync(depPath)) {
22440
+ const depPath = path34.join(cwd2, depFile);
22441
+ if (fs32.existsSync(depPath)) {
22059
22442
  const depContent = safeReadFile(depPath, 1500);
22060
22443
  if (depContent) {
22061
22444
  parts.push(`### ${depFile} (top entries)
@@ -22068,10 +22451,10 @@ function buildWorkspaceSnapshot(cwd2) {
22068
22451
  }
22069
22452
  }
22070
22453
  for (const envFile of [".env.example", ".env.sample", ".env.template"]) {
22071
- const envPath = path33.join(cwd2, envFile);
22072
- if (fs31.existsSync(envPath)) {
22454
+ const envPath = path34.join(cwd2, envFile);
22455
+ if (fs32.existsSync(envPath)) {
22073
22456
  try {
22074
- const raw = fs31.readFileSync(envPath, "utf-8");
22457
+ const raw = fs32.readFileSync(envPath, "utf-8");
22075
22458
  const keys = raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#")).map((l) => {
22076
22459
  const eqIndex = l.indexOf("=");
22077
22460
  return eqIndex >= 0 ? l.slice(0, eqIndex).trim() : l.trim();
@@ -22094,15 +22477,15 @@ init_runtime_config();
22094
22477
 
22095
22478
  // src/app/agent/runtime/plugin_registry.ts
22096
22479
  init_sandbox_policy();
22097
- import fs32 from "fs";
22480
+ import fs33 from "fs";
22098
22481
  import os22 from "os";
22099
- import path34 from "path";
22482
+ import path35 from "path";
22100
22483
  function getProjectPluginsDir() {
22101
22484
  const policy = getSandboxPolicy();
22102
- return path34.join(policy.workspaceRoot, ".bluma", "plugins");
22485
+ return path35.join(policy.workspaceRoot, ".bluma", "plugins");
22103
22486
  }
22104
22487
  function getGlobalPluginsDir() {
22105
- return path34.join(process.env.HOME || os22.homedir(), ".bluma", "plugins");
22488
+ return path35.join(process.env.HOME || os22.homedir(), ".bluma", "plugins");
22106
22489
  }
22107
22490
  function getPluginDirs() {
22108
22491
  return {
@@ -22111,11 +22494,11 @@ function getPluginDirs() {
22111
22494
  };
22112
22495
  }
22113
22496
  function readManifest(manifestPath, fallbackName) {
22114
- if (!fs32.existsSync(manifestPath)) {
22497
+ if (!fs33.existsSync(manifestPath)) {
22115
22498
  return null;
22116
22499
  }
22117
22500
  try {
22118
- const parsed = JSON.parse(fs32.readFileSync(manifestPath, "utf-8"));
22501
+ const parsed = JSON.parse(fs33.readFileSync(manifestPath, "utf-8"));
22119
22502
  return {
22120
22503
  name: typeof parsed.name === "string" && parsed.name.trim() ? parsed.name.trim() : fallbackName,
22121
22504
  description: typeof parsed.description === "string" ? parsed.description.trim() : void 0,
@@ -22128,22 +22511,22 @@ function readManifest(manifestPath, fallbackName) {
22128
22511
  }
22129
22512
  function findManifestPath(pluginDir) {
22130
22513
  const candidates = [
22131
- path34.join(pluginDir, ".codex-plugin", "plugin.json"),
22132
- path34.join(pluginDir, "plugin.json")
22514
+ path35.join(pluginDir, ".codex-plugin", "plugin.json"),
22515
+ path35.join(pluginDir, "plugin.json")
22133
22516
  ];
22134
22517
  for (const candidate of candidates) {
22135
- if (fs32.existsSync(candidate)) {
22518
+ if (fs33.existsSync(candidate)) {
22136
22519
  return candidate;
22137
22520
  }
22138
22521
  }
22139
22522
  return null;
22140
22523
  }
22141
22524
  function listFromDir(baseDir, source) {
22142
- if (!fs32.existsSync(baseDir)) {
22525
+ if (!fs33.existsSync(baseDir)) {
22143
22526
  return [];
22144
22527
  }
22145
- return fs32.readdirSync(baseDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).flatMap((entry) => {
22146
- const pluginDir = path34.join(baseDir, entry.name);
22528
+ return fs33.readdirSync(baseDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).flatMap((entry) => {
22529
+ const pluginDir = path35.join(baseDir, entry.name);
22147
22530
  const manifestPath = findManifestPath(pluginDir);
22148
22531
  if (!manifestPath) {
22149
22532
  return [];
@@ -22588,8 +22971,8 @@ function buildModelInfoSection(modelId) {
22588
22971
 
22589
22972
  // src/app/agent/runtime/hook_registry.ts
22590
22973
  init_sandbox_policy();
22591
- import fs33 from "fs";
22592
- import path35 from "path";
22974
+ import fs34 from "fs";
22975
+ import path36 from "path";
22593
22976
  var DEFAULT_STATE = {
22594
22977
  enabled: true,
22595
22978
  maxEvents: 120,
@@ -22600,7 +22983,7 @@ var cache3 = null;
22600
22983
  var cachePath2 = null;
22601
22984
  function getStatePath() {
22602
22985
  const policy = getSandboxPolicy();
22603
- return path35.join(policy.workspaceRoot, ".bluma", "hooks.json");
22986
+ return path36.join(policy.workspaceRoot, ".bluma", "hooks.json");
22604
22987
  }
22605
22988
  function getHookStatePath() {
22606
22989
  return getStatePath();
@@ -22619,8 +23002,8 @@ function ensureLoaded2() {
22619
23002
  return cache3;
22620
23003
  }
22621
23004
  try {
22622
- if (fs33.existsSync(statePath)) {
22623
- const parsed = JSON.parse(fs33.readFileSync(statePath, "utf-8"));
23005
+ if (fs34.existsSync(statePath)) {
23006
+ const parsed = JSON.parse(fs34.readFileSync(statePath, "utf-8"));
22624
23007
  cache3 = {
22625
23008
  enabled: typeof parsed.enabled === "boolean" ? parsed.enabled : DEFAULT_STATE.enabled,
22626
23009
  maxEvents: typeof parsed.maxEvents === "number" && Number.isFinite(parsed.maxEvents) && parsed.maxEvents > 0 ? Math.floor(parsed.maxEvents) : DEFAULT_STATE.maxEvents,
@@ -22646,9 +23029,9 @@ function ensureLoaded2() {
22646
23029
  }
22647
23030
  function persist2(state2) {
22648
23031
  const statePath = getStatePath();
22649
- fs33.mkdirSync(path35.dirname(statePath), { recursive: true });
23032
+ fs34.mkdirSync(path36.dirname(statePath), { recursive: true });
22650
23033
  state2.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
22651
- fs33.writeFileSync(statePath, JSON.stringify(state2, null, 2), "utf-8");
23034
+ fs34.writeFileSync(statePath, JSON.stringify(state2, null, 2), "utf-8");
22652
23035
  cache3 = state2;
22653
23036
  cachePath2 = statePath;
22654
23037
  }
@@ -22898,18 +23281,18 @@ Usa s\xF3 facts do pedido; campos em falta \u2192 "n\xE3o fornecido" \u2014 nunc
22898
23281
  }
22899
23282
 
22900
23283
  // src/app/agent/core/prompt/auto_memory.ts
22901
- import fs34 from "fs";
22902
- import path36 from "path";
23284
+ import fs35 from "fs";
23285
+ import path37 from "path";
22903
23286
  import os23 from "os";
22904
- var AUTO_MEMORY_FILE = path36.join(os23.homedir(), ".bluma", "auto_memory.md");
23287
+ var AUTO_MEMORY_FILE = path37.join(os23.homedir(), ".bluma", "auto_memory.md");
22905
23288
  var MAX_AUTO_MEMORY_CHARS = 25e3;
22906
23289
  var MAX_AUTO_MEMORY_LINES = 200;
22907
23290
  function getAutoMemoryForPrompt() {
22908
23291
  try {
22909
- if (!fs34.existsSync(AUTO_MEMORY_FILE)) {
23292
+ if (!fs35.existsSync(AUTO_MEMORY_FILE)) {
22910
23293
  return "";
22911
23294
  }
22912
- let content = fs34.readFileSync(AUTO_MEMORY_FILE, "utf-8");
23295
+ let content = fs35.readFileSync(AUTO_MEMORY_FILE, "utf-8");
22913
23296
  if (!content.trim()) {
22914
23297
  return "";
22915
23298
  }
@@ -22935,6 +23318,103 @@ function isAutoMemoryEnabled() {
22935
23318
  return true;
22936
23319
  }
22937
23320
 
23321
+ // src/app/agent/core/prompt/factorai_sh_prompt.ts
23322
+ var FACTORAI_SH_SECTION = `
23323
+ <factorai_sh_workflow>
23324
+ ## FactorAI.sh \u2014 Next.js hosting (mandatory workflow)
23325
+
23326
+ **When to load full checklist:** \`load_skill("factorai-sh")\` before any create/deploy/edit/redeploy task.
23327
+
23328
+ ### Backend URL (already in your environment)
23329
+ | Variable | Used by |
23330
+ |----------|---------|
23331
+ | \`FACTORAI_BASE_URL\` | \`factorai.sh.get_app_status\`, \`factorai.sh.apply_app_changes\`, \`factorai.sh.redeploy_app\` |
23332
+ | \`SEVERINO_URL\` | \`factorai.sh.deploy_app\` (ZIP upload to \`/api/v1/deploy\`) |
23333
+ | \`FACTORAI_API_KEY\` / \`SEVERINO_API_KEY\` | Optional Bearer on write endpoints |
23334
+
23335
+ 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.
23336
+
23337
+ ### Source of truth: \`factorai.sh.json\`
23338
+ - Written/updated by \`factorai.sh.deploy_app\` and FactorAI tools \u2014 **read with normal file tools**, never a separate manifest tool.
23339
+ - Before any edit/redeploy on an **existing** app: read \`factorai.sh.json\` \u2192 use \`appContext.appId\` (UUID).
23340
+ - Never invent \`appId\` or duplicate state outside this file.
23341
+
23342
+ ### Tools (sandbox-only)
23343
+ | Tool | When |
23344
+ |------|------|
23345
+ | \`factorai.sh.create_next_app\` | New Next.js project in workspace |
23346
+ | \`factorai.sh.deploy_app\` | First deploy (ZIP source only) |
23347
+ | \`factorai.sh.apply_app_changes\` | **App already online** \u2014 patch files + rebuild (Vercel-like) |
23348
+ | \`factorai.sh.get_app_status\` | Poll build status after deploy/apply |
23349
+ | \`factorai.sh.redeploy_app\` | Rebuild current sources without file patches |
23350
+
23351
+ ---
23352
+
23353
+ ## Flow A \u2014 New app (no \`factorai.sh.json\` yet)
23354
+
23355
+ 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.
23356
+ 2. Customize with \`edit_tool\` / \`file_write\` / \`shell_command\` in the project directory (e.g. \`./{name}/app/page.tsx\`).
23357
+ 3. Optional: \`npx tsc --noEmit\` to catch TS errors \u2014 **avoid** \`npm run build\` before deploy (creates \`.next/\` and inflates the ZIP).
23358
+ 4. \`factorai.sh.deploy_app({ projectDir: "./{name}", name })\` \u2014 tool excludes \`node_modules\`, \`.next\`, \`.env*\` automatically. **Never** manual \`zip\` including \`node_modules\` or \`.next\`.
23359
+ 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).
23360
+
23361
+ ---
23362
+
23363
+ ## Session scope (mandatory)
23364
+
23365
+ - App state lives **only** in **this** sandbox workspace (\`factorai.sh.json\` + project dir for this \`session_id\`).
23366
+ - **Never** assume an app from another chat/session \u2014 no cross-session \`appId\` guessing.
23367
+ - 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.
23368
+
23369
+ ## Flow B \u2014 App already deployed in this session (Vercel-style edit + rebuild)
23370
+
23371
+ **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.
23372
+
23373
+ 1. Read \`factorai.sh.json\` in this workspace \u2192 \`appId\`.
23374
+ 2. After \`edit_tool\` / \`file_write\`, **read the full file** you changed (\`read_file_lines\` or read entire file).
23375
+ 3. \`factorai.sh.apply_app_changes({ appId, files: [{ path: "app/page.tsx", content: "<ENTIRE file contents>" }, ...], deploy: true })\`
23376
+ - **CRITICAL:** \`content\` must be the **complete** file body. The server **replaces** the whole file \u2014 a footer snippet or JSX fragment **destroys** the page.
23377
+ - One entry per changed path; each \`content\` = full file as on disk after your edit.
23378
+ - \`deploy\` defaults to \`true\`. Paths relative to app root (e.g. \`app/page.tsx\`). Blocked: \`node_modules\`, \`.next\`, \`.git\`.
23379
+ 4. Poll until rebuild completes (see Polling below).
23380
+ 5. Confirm live page, then \`message(result)\` with \`factor-sh-url-app\` from **this** session's manifest.
23381
+
23382
+ Use \`factorai.sh.redeploy_app({ appId })\` only when sources on the server are already correct but build must run again (no file changes).
23383
+
23384
+ ---
23385
+
23386
+ ## Polling after deploy / apply_app_changes
23387
+
23388
+ 1. \`factorai.sh.get_app_status({ appId })\` or GET \`/api/v1/apps/{appId}\`.
23389
+ 2. Expect \`status: "building"\` during \`npm install\` / \`next build\` (~30\u201390s).
23390
+ 3. **Do not** report "live" on the first \`ready\` if you have not seen \`building\` since this operation \u2014 stale \`ready\` can appear briefly.
23391
+ 4. Wait until \`status\` is \`ready\` (or \`running\` / \`live\`) **after** a \`building\` phase, then verify the public route returns HTML (not \`Service Unavailable\`).
23392
+ 5. On \`failed\` / \`error\`: read server/build logs if available; fix TS/scaffold issues; retry \`apply_app_changes\` or \`redeploy_app\`.
23393
+
23394
+ ---
23395
+
23396
+ ## Deploy constraints (first deploy)
23397
+
23398
+ - \`next.config.js\` must keep \`output: 'standalone'\` (included in scaffold).
23399
+ - ZIP max ~50MB \u2014 source only; Severino runs \`npm install\` + \`next build\` on the server.
23400
+ - shadcn components in scaffold must compile (no missing Radix imports).
23401
+
23402
+ ---
23403
+
23404
+ ## Reporting to orchestrator (Severino)
23405
+
23406
+ - Progress: \`message({ message_type: "info", ... })\` every few steps.
23407
+ - Done: \`message({ message_type: "result", content: "...", attachments: [...], "factor-sh-url-app": "<live url>" })\` \u2014 **only \`result\` ends the worker turn**.
23408
+ - **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\`.
23409
+ - File deliverables still use \`attachments[]\` under \`.bluma/artifacts/\` \u2014 separate from the app URL field.
23410
+ </factorai_sh_workflow>
23411
+ `;
23412
+ function isFactorAiShPromptEnabled() {
23413
+ const isSandbox = process.env.BLUMA_SANDBOX === "true";
23414
+ const baseUrl = (process.env.FACTORAI_BASE_URL || process.env.FACTORAI_URL || "").trim();
23415
+ return isSandbox && Boolean(baseUrl);
23416
+ }
23417
+
22938
23418
  // src/app/agent/core/prompt/prompt_builder.ts
22939
23419
  function getNodeVersion() {
22940
23420
  return process.version;
@@ -22966,17 +23446,17 @@ function getGitBranch(dir) {
22966
23446
  }
22967
23447
  }
22968
23448
  function getPackageManager(dir) {
22969
- if (fs35.existsSync(path37.join(dir, "pnpm-lock.yaml"))) return "pnpm";
22970
- if (fs35.existsSync(path37.join(dir, "yarn.lock"))) return "yarn";
22971
- if (fs35.existsSync(path37.join(dir, "bun.lockb"))) return "bun";
22972
- if (fs35.existsSync(path37.join(dir, "package-lock.json"))) return "npm";
23449
+ if (fs36.existsSync(path38.join(dir, "pnpm-lock.yaml"))) return "pnpm";
23450
+ if (fs36.existsSync(path38.join(dir, "yarn.lock"))) return "yarn";
23451
+ if (fs36.existsSync(path38.join(dir, "bun.lockb"))) return "bun";
23452
+ if (fs36.existsSync(path38.join(dir, "package-lock.json"))) return "npm";
22973
23453
  return "unknown";
22974
23454
  }
22975
23455
  function getProjectType(dir) {
22976
23456
  try {
22977
- const files = fs35.readdirSync(dir);
23457
+ const files = fs36.readdirSync(dir);
22978
23458
  if (files.includes("package.json")) {
22979
- const pkg = JSON.parse(fs35.readFileSync(path37.join(dir, "package.json"), "utf-8"));
23459
+ const pkg = JSON.parse(fs36.readFileSync(path38.join(dir, "package.json"), "utf-8"));
22980
23460
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
22981
23461
  if (deps.next) return "Next.js";
22982
23462
  if (deps.react) return "React";
@@ -22996,9 +23476,9 @@ function getProjectType(dir) {
22996
23476
  }
22997
23477
  function getTestFramework(dir) {
22998
23478
  try {
22999
- const pkgPath = path37.join(dir, "package.json");
23000
- if (fs35.existsSync(pkgPath)) {
23001
- const pkg = JSON.parse(fs35.readFileSync(pkgPath, "utf-8"));
23479
+ const pkgPath = path38.join(dir, "package.json");
23480
+ if (fs36.existsSync(pkgPath)) {
23481
+ const pkg = JSON.parse(fs36.readFileSync(pkgPath, "utf-8"));
23002
23482
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
23003
23483
  if (deps.jest) return "jest";
23004
23484
  if (deps.vitest) return "vitest";
@@ -23007,7 +23487,7 @@ function getTestFramework(dir) {
23007
23487
  if (deps["@playwright/test"]) return "playwright";
23008
23488
  if (deps.cypress) return "cypress";
23009
23489
  }
23010
- if (fs35.existsSync(path37.join(dir, "pytest.ini")) || fs35.existsSync(path37.join(dir, "conftest.py"))) return "pytest";
23490
+ if (fs36.existsSync(path38.join(dir, "pytest.ini")) || fs36.existsSync(path38.join(dir, "conftest.py"))) return "pytest";
23011
23491
  return "unknown";
23012
23492
  } catch {
23013
23493
  return "unknown";
@@ -23015,9 +23495,9 @@ function getTestFramework(dir) {
23015
23495
  }
23016
23496
  function getTestCommand(dir) {
23017
23497
  try {
23018
- const pkgPath = path37.join(dir, "package.json");
23019
- if (fs35.existsSync(pkgPath)) {
23020
- const pkg = JSON.parse(fs35.readFileSync(pkgPath, "utf-8"));
23498
+ const pkgPath = path38.join(dir, "package.json");
23499
+ if (fs36.existsSync(pkgPath)) {
23500
+ const pkg = JSON.parse(fs36.readFileSync(pkgPath, "utf-8"));
23021
23501
  if (pkg.scripts?.test) return "npm test";
23022
23502
  if (pkg.scripts?.["test:unit"]) return "npm run test:unit";
23023
23503
  }
@@ -23032,8 +23512,8 @@ function getTestCommand(dir) {
23032
23512
  }
23033
23513
  function isGitRepo(dir) {
23034
23514
  try {
23035
- const p = path37.join(dir, ".git");
23036
- return fs35.existsSync(p) && fs35.lstatSync(p).isDirectory();
23515
+ const p = path38.join(dir, ".git");
23516
+ return fs36.existsSync(p) && fs36.lstatSync(p).isDirectory();
23037
23517
  } catch {
23038
23518
  return false;
23039
23519
  }
@@ -23234,7 +23714,8 @@ Timeouts mean the orchestrator should raise the limit \u2014 not that the sandbo
23234
23714
  - **Step 2:** \`message(result)\` with those paths in \`attachments[]\`; use \`content\` for a short summary only.
23235
23715
  - Never create a top-level \`./artifacts/\` folder \u2014 it is auto-remapped to \`.bluma/artifacts/\`.
23236
23716
  - **Never invent URLs** or paths outside \`.bluma/artifacts/\` in \`attachments[]\`.
23237
- - Live deploy URLs (if any) may appear in \`content\` when returned by \`factorai.sh.deploy_app\`; downloadable files still go through \`attachments[]\`.
23717
+ - FactorAI.sh live app URL: on \`message(result)\` after deploy, set \`"factor-sh-url-app"\` (absolute URL). File paths still use \`attachments[]\` only.
23718
+ - Next.js hosting on FactorAI.sh: \`load_skill("factorai-sh")\` and follow \`<factorai_sh_workflow>\` when present.
23238
23719
  - **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
23720
  - Remove temp files and generator scripts before finishing.
23240
23721
  </sandbox_context>
@@ -23288,12 +23769,18 @@ async function getUnifiedSystemPrompt(availableSkills, options) {
23288
23769
 
23289
23770
  ${SUBJECT_MACHINE_MODEL_XML}`;
23290
23771
  prompt += interpolate(SANDBOX_SECTION);
23772
+ if (isFactorAiShPromptEnabled()) {
23773
+ prompt += `
23774
+
23775
+ ${FACTORAI_SH_SECTION}`;
23776
+ }
23291
23777
  const appContext = loadFactorAiAppContext();
23292
23778
  if (appContext) {
23293
23779
  prompt += `
23294
23780
 
23295
- <factorai_app_context>
23781
+ <factorai_app_context active="true">
23296
23782
  ${formatFactorAiAppContextSummary(appContext)}
23783
+ Read factorai.sh.json before apply_app_changes or redeploy. Prefer apply_app_changes for edits to an already deployed app.
23297
23784
  </factorai_app_context>`;
23298
23785
  }
23299
23786
  }
@@ -23655,13 +24142,17 @@ var HistoryCompressor = class {
23655
24142
  */
23656
24143
  async buildContextWindow(fullHistory, llmService, userContext, options) {
23657
24144
  if (!fullHistory.length) {
23658
- return { messages: [], prunedHistory: null };
24145
+ return { messages: [], prunedHistory: null, archivedMessages: [] };
23659
24146
  }
23660
- const toolDefinitionsTokens = countToolDefinitionsTokens(options?.toolDefinitions ?? []);
23661
- const tokenBudget = Math.max(
23662
- 0,
23663
- (options?.tokenBudget ?? DEFAULT_CONTEXT_INPUT_BUDGET) - toolDefinitionsTokens
24147
+ const budgetBreakdown = computeEffectiveInputBudget(
24148
+ options?.tokenBudget ?? DEFAULT_CONTEXT_INPUT_BUDGET,
24149
+ options?.toolDefinitions ?? [],
24150
+ {
24151
+ outputReserveTokens: options?.outputReserveTokens,
24152
+ protocolOverheadTokens: options?.protocolOverheadTokens
24153
+ }
23664
24154
  );
24155
+ const tokenBudget = budgetBreakdown.effectiveBudget;
23665
24156
  const compressThreshold = options?.compressThreshold ?? COMPRESS_THRESHOLD;
23666
24157
  const keepRecentTurns = options?.keepRecentTurns ?? KEEP_RECENT_TURNS;
23667
24158
  const sanitized = sanitizeConversationForProvider(fullHistory);
@@ -23692,6 +24183,7 @@ var HistoryCompressor = class {
23692
24183
  });
23693
24184
  let compressionHappened = false;
23694
24185
  let compressionFailed = false;
24186
+ const archivedMessages = [];
23695
24187
  while (tokens >= thresholdTokens && pendingSlices.length > 0 && !compressionFailed) {
23696
24188
  try {
23697
24189
  contextEventBus.emit("context:compression_progress", {
@@ -23701,6 +24193,7 @@ var HistoryCompressor = class {
23701
24193
  tokenCount: tokens,
23702
24194
  tokenBudget
23703
24195
  });
24196
+ archivedMessages.push(...pendingFlat);
23704
24197
  this.anchor = await this.compressWithTimeout(
23705
24198
  pendingFlat,
23706
24199
  this.anchor,
@@ -23718,6 +24211,7 @@ var HistoryCompressor = class {
23718
24211
  "[HistoryCompressor] Compression failed/timeout, dropping old turns:",
23719
24212
  compressError?.message
23720
24213
  );
24214
+ archivedMessages.push(...pendingFlat);
23721
24215
  pendingSlices = [];
23722
24216
  pendingFlat = [];
23723
24217
  sliceCount = recentStart;
@@ -23754,7 +24248,11 @@ var HistoryCompressor = class {
23754
24248
  } else {
23755
24249
  this.compressedSliceCount = sliceCount;
23756
24250
  }
23757
- return { messages, prunedHistory };
24251
+ return {
24252
+ messages,
24253
+ prunedHistory,
24254
+ archivedMessages: compressionHappened ? archivedMessages : []
24255
+ };
23758
24256
  }
23759
24257
  // ── Private helpers ──────────────────────────────────────────────────────────
23760
24258
  /** Assembles system + anchor + pending + recent into the final message array. */
@@ -24436,9 +24934,9 @@ var LLMService = class {
24436
24934
  };
24437
24935
 
24438
24936
  // src/app/agent/utils/user_message_images.ts
24439
- import fs36 from "fs";
24937
+ import fs37 from "fs";
24440
24938
  import os26 from "os";
24441
- import path38 from "path";
24939
+ import path39 from "path";
24442
24940
  import { fileURLToPath as fileURLToPath4 } from "url";
24443
24941
  var IMAGE_EXT = /\.(png|jpe?g|gif|webp|bmp)$/i;
24444
24942
  var MAX_IMAGE_BYTES = 4 * 1024 * 1024;
@@ -24454,22 +24952,22 @@ var MIME = {
24454
24952
  function expandUserPath(p) {
24455
24953
  const t = p.trim();
24456
24954
  if (t.startsWith("~")) {
24457
- return path38.join(os26.homedir(), t.slice(1).replace(/^\//, ""));
24955
+ return path39.join(os26.homedir(), t.slice(1).replace(/^\//, ""));
24458
24956
  }
24459
24957
  return t;
24460
24958
  }
24461
24959
  function isPathAllowed(absResolved, cwd2) {
24462
- const resolved = path38.normalize(path38.resolve(absResolved));
24463
- const cwdR = path38.normalize(path38.resolve(cwd2));
24464
- const homeR = path38.normalize(path38.resolve(os26.homedir()));
24465
- const tmpR = path38.normalize(path38.resolve(os26.tmpdir()));
24466
- const underCwd = resolved === cwdR || resolved.startsWith(cwdR + path38.sep);
24467
- const underHome = resolved === homeR || resolved.startsWith(homeR + path38.sep);
24468
- const underTmp = resolved === tmpR || resolved.startsWith(tmpR + path38.sep);
24960
+ const resolved = path39.normalize(path39.resolve(absResolved));
24961
+ const cwdR = path39.normalize(path39.resolve(cwd2));
24962
+ const homeR = path39.normalize(path39.resolve(os26.homedir()));
24963
+ const tmpR = path39.normalize(path39.resolve(os26.tmpdir()));
24964
+ const underCwd = resolved === cwdR || resolved.startsWith(cwdR + path39.sep);
24965
+ const underHome = resolved === homeR || resolved.startsWith(homeR + path39.sep);
24966
+ const underTmp = resolved === tmpR || resolved.startsWith(tmpR + path39.sep);
24469
24967
  return underCwd || underHome || underTmp;
24470
24968
  }
24471
24969
  function mimeFor(abs) {
24472
- const ext = path38.extname(abs).toLowerCase();
24970
+ const ext = path39.extname(abs).toLowerCase();
24473
24971
  return MIME[ext] || "application/octet-stream";
24474
24972
  }
24475
24973
  var IMAGE_EXT_SRC = String.raw`(?:png|jpe?g|gif|webp|bmp)`;
@@ -24513,10 +25011,10 @@ function collectImagePathStrings(raw) {
24513
25011
  }
24514
25012
  function resolveImagePath(candidate, cwd2) {
24515
25013
  const expanded = expandUserPath(candidate);
24516
- const abs = path38.isAbsolute(expanded) ? path38.normalize(expanded) : path38.normalize(path38.resolve(cwd2, expanded));
25014
+ const abs = path39.isAbsolute(expanded) ? path39.normalize(expanded) : path39.normalize(path39.resolve(cwd2, expanded));
24517
25015
  if (!isPathAllowed(abs, cwd2)) return null;
24518
25016
  try {
24519
- if (!fs36.existsSync(abs) || !fs36.statSync(abs).isFile()) return null;
25017
+ if (!fs37.existsSync(abs) || !fs37.statSync(abs).isFile()) return null;
24520
25018
  } catch {
24521
25019
  return null;
24522
25020
  }
@@ -24536,11 +25034,11 @@ function trySingleLineFileUriOrBareImagePath(line, cwd2) {
24536
25034
  if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) {
24537
25035
  s = s.slice(1, -1).trim();
24538
25036
  }
24539
- if (!IMAGE_EXT.test(path38.extname(s))) return null;
25037
+ if (!IMAGE_EXT.test(path39.extname(s))) return null;
24540
25038
  const abs = resolveImagePath(s, cwd2);
24541
25039
  if (!abs) return null;
24542
25040
  try {
24543
- const st = fs36.statSync(abs);
25041
+ const st = fs37.statSync(abs);
24544
25042
  if (!st.isFile() || st.size > MAX_IMAGE_BYTES) {
24545
25043
  return null;
24546
25044
  }
@@ -24575,7 +25073,7 @@ function tryPasteChunkAsSingleImagePath(chunk, cwd2) {
24575
25073
  return null;
24576
25074
  }
24577
25075
  try {
24578
- const st = fs36.statSync(abs);
25076
+ const st = fs37.statSync(abs);
24579
25077
  if (!st.isFile() || st.size > MAX_IMAGE_BYTES) {
24580
25078
  return null;
24581
25079
  }
@@ -24604,7 +25102,7 @@ function buildUserMessageContent(raw, cwd2) {
24604
25102
  const abs = resolveImagePath(c, cwd2);
24605
25103
  if (!abs) continue;
24606
25104
  try {
24607
- const st = fs36.statSync(abs);
25105
+ const st = fs37.statSync(abs);
24608
25106
  if (st.size > MAX_IMAGE_BYTES) continue;
24609
25107
  } catch {
24610
25108
  continue;
@@ -24621,7 +25119,7 @@ function buildUserMessageContent(raw, cwd2) {
24621
25119
  }
24622
25120
  const parts = [{ type: "text", text: textPart }];
24623
25121
  for (const abs of resolvedAbs) {
24624
- const buf = fs36.readFileSync(abs);
25122
+ const buf = fs37.readFileSync(abs);
24625
25123
  const b64 = buf.toString("base64");
24626
25124
  const mime = mimeFor(abs);
24627
25125
  parts.push({
@@ -24639,7 +25137,7 @@ function buildUserMessageContent(raw, cwd2) {
24639
25137
  init_sandbox_policy();
24640
25138
  init_runtime_config();
24641
25139
  init_permission_rules();
24642
- import path39 from "path";
25140
+ import path40 from "path";
24643
25141
  var LOCAL_EDIT_TOOL_NAMES = /* @__PURE__ */ new Set(["edit_tool", "file_write", "notebook_edit"]);
24644
25142
  function getToolPermissionLayer(metadata) {
24645
25143
  if (metadata.riskLevel === "safe") return "read";
@@ -24654,11 +25152,11 @@ function checkFilePermissionRules(toolName, filePath, policy) {
24654
25152
  if (!filePath) {
24655
25153
  return { allowed: false, reason: "No file path provided for permission check." };
24656
25154
  }
24657
- const resolvedPath = path39.resolve(filePath);
25155
+ const resolvedPath = path40.resolve(filePath);
24658
25156
  if (!isPathInsideWorkspace(resolvedPath, policy)) {
24659
25157
  return { allowed: false, reason: `File path "${filePath}" is outside workspace root.` };
24660
25158
  }
24661
- const relativePath = path39.relative(policy.workspaceRoot, resolvedPath);
25159
+ const relativePath = path40.relative(policy.workspaceRoot, resolvedPath);
24662
25160
  const toolPattern = `${toolName}(${relativePath})`;
24663
25161
  const ruleDecision = permissionRulesEngine.checkPermission(toolPattern, { filepath: filePath });
24664
25162
  if (ruleDecision === "deny") {
@@ -24667,7 +25165,7 @@ function checkFilePermissionRules(toolName, filePath, policy) {
24667
25165
  if (ruleDecision === "allow") {
24668
25166
  return { allowed: true, reason: `File "${filePath}" allowed by permission rules.` };
24669
25167
  }
24670
- const dirPath = path39.dirname(relativePath);
25168
+ const dirPath = path40.dirname(relativePath);
24671
25169
  const dirPattern = `${toolName}(${dirPath}/**)`;
24672
25170
  const dirRuleDecision = permissionRulesEngine.checkPermission(dirPattern, { filepath: filePath });
24673
25171
  if (dirRuleDecision === "allow") {
@@ -24884,11 +25382,11 @@ function effectiveToolAutoApprove(toolCall, sessionId, options) {
24884
25382
  }
24885
25383
 
24886
25384
  // src/app/agent/tools/CodingMemoryTool/CodingMemoryConsolidate.ts
24887
- import * as fs37 from "fs";
24888
- import * as path40 from "path";
25385
+ import * as fs38 from "fs";
25386
+ import * as path41 from "path";
24889
25387
  import os27 from "os";
24890
25388
  function memoryPath2() {
24891
- return path40.join(process.env.HOME || os27.homedir(), ".bluma", "coding_memory.json");
25389
+ return path41.join(process.env.HOME || os27.homedir(), ".bluma", "coding_memory.json");
24892
25390
  }
24893
25391
  function normalizeNote2(note) {
24894
25392
  return note.trim().toLowerCase().replace(/\s+/g, " ");
@@ -24898,18 +25396,18 @@ function uniqTags(a, b) {
24898
25396
  }
24899
25397
  function consolidateCodingMemoryFile() {
24900
25398
  const p = memoryPath2();
24901
- if (!fs37.existsSync(p)) {
25399
+ if (!fs38.existsSync(p)) {
24902
25400
  return { success: true, removedDuplicates: 0, message: "no coding_memory.json" };
24903
25401
  }
24904
25402
  const bak = `${p}.bak`;
24905
25403
  try {
24906
- fs37.copyFileSync(p, bak);
25404
+ fs38.copyFileSync(p, bak);
24907
25405
  } catch (e) {
24908
25406
  return { success: false, removedDuplicates: 0, message: `backup failed: ${e.message}` };
24909
25407
  }
24910
25408
  let data;
24911
25409
  try {
24912
- data = JSON.parse(fs37.readFileSync(p, "utf-8"));
25410
+ data = JSON.parse(fs38.readFileSync(p, "utf-8"));
24913
25411
  } catch (e) {
24914
25412
  return { success: false, removedDuplicates: 0, message: `invalid json: ${e.message}` };
24915
25413
  }
@@ -24944,7 +25442,7 @@ function consolidateCodingMemoryFile() {
24944
25442
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
24945
25443
  };
24946
25444
  try {
24947
- fs37.writeFileSync(p, JSON.stringify(out, null, 2), "utf-8");
25445
+ fs38.writeFileSync(p, JSON.stringify(out, null, 2), "utf-8");
24948
25446
  } catch (e) {
24949
25447
  return { success: false, removedDuplicates: 0, message: `write failed: ${e.message}` };
24950
25448
  }
@@ -25448,6 +25946,22 @@ var BluMaToolRunner = class {
25448
25946
  }
25449
25947
  };
25450
25948
 
25949
+ // src/app/agent/session_manager/session_archive.ts
25950
+ import path42 from "path";
25951
+ import { promises as fs39 } from "fs";
25952
+ async function archivePrunedConversationMessages(sessionId, messages) {
25953
+ if (!sessionId || messages.length === 0) {
25954
+ return null;
25955
+ }
25956
+ const appDir = getPreferredAppDir();
25957
+ const dir = path42.join(appDir, "sessions", "archive", sessionId);
25958
+ await fs39.mkdir(dir, { recursive: true });
25959
+ const archiveFile = path42.join(dir, `${Date.now()}.jsonl`);
25960
+ const lines = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
25961
+ await fs39.appendFile(archiveFile, lines, "utf-8");
25962
+ return archiveFile;
25963
+ }
25964
+
25451
25965
  // src/app/agent/core/llm/llm_errors.ts
25452
25966
  function isContextWindowValidationError(error) {
25453
25967
  const rawMessage = error instanceof Error ? error.message : typeof error === "string" ? error : "";
@@ -25792,7 +26306,11 @@ var BluMaTurnCoordinator = class {
25792
26306
  for (let attempt = 0; attempt < tokenBudgets.length; attempt += 1) {
25793
26307
  const tokenBudget = tokenBudgets[attempt];
25794
26308
  try {
25795
- const { messages: contextWindow, prunedHistory } = await this.deps.compressor.buildContextWindow(
26309
+ const {
26310
+ messages: contextWindow,
26311
+ prunedHistory,
26312
+ archivedMessages
26313
+ } = await this.deps.compressor.buildContextWindow(
25796
26314
  this.deps.history,
25797
26315
  this.deps.llm,
25798
26316
  this.deps.getLlmUserContext(),
@@ -25805,13 +26323,19 @@ var BluMaTurnCoordinator = class {
25805
26323
  history_length: this.deps.history.length,
25806
26324
  context_window_length: contextWindow.length,
25807
26325
  pruned_history_length: prunedHistory?.length ?? null,
26326
+ archived_messages: archivedMessages.length,
25808
26327
  token_budget: tokenBudget,
25809
26328
  context_preview: summarizeHistoryForLog(contextWindow, 8)
25810
26329
  });
25811
26330
  if (prunedHistory) {
26331
+ if (archivedMessages.length > 0) {
26332
+ await archivePrunedConversationMessages(this.deps.sessionId, archivedMessages);
26333
+ }
25812
26334
  this.deps.history.splice(0, this.deps.history.length, ...prunedHistory);
26335
+ await this.deps.persistSessionAfterPrune();
26336
+ } else {
26337
+ this.deps.persistSession();
25813
26338
  }
25814
- this.deps.persistSession();
25815
26339
  if (typeof llmService.chatCompletionStream === "function") {
25816
26340
  await this.handleStreamingResponse(contextWindow);
25817
26341
  } else {
@@ -26229,6 +26753,7 @@ var BluMaAgent = class {
26229
26753
  getLlmUserContext: () => this.getLlmUserContext(),
26230
26754
  persistSession: () => this.persistSession(),
26231
26755
  persistSessionSync: () => this.persistSessionSync(),
26756
+ persistSessionAfterPrune: () => this.persistSessionAfterPrune(),
26232
26757
  notifyFactorTurnEndIfNeeded: (reason) => this.notifyFactorTurnEndIfNeeded(reason),
26233
26758
  emitTurnCompleted: () => this.emitTurnCompleted(),
26234
26759
  generateEditPreview: (toolArgs) => this._generateEditPreview(toolArgs),
@@ -26268,13 +26793,13 @@ var BluMaAgent = class {
26268
26793
  if (!this.sessionFile) return;
26269
26794
  try {
26270
26795
  const sessionData = {
26271
- session_id: path41.basename(this.sessionFile, ".json"),
26796
+ session_id: path43.basename(this.sessionFile, ".json"),
26272
26797
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
26273
26798
  conversation_history: this.history,
26274
26799
  last_updated: (/* @__PURE__ */ new Date()).toISOString(),
26275
26800
  ...this.compressor.getSnapshot()
26276
26801
  };
26277
- fs38.writeFileSync(this.sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
26802
+ fs40.writeFileSync(this.sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
26278
26803
  } catch (error) {
26279
26804
  console.error("[Bluma] Failed to persist session synchronously:", error);
26280
26805
  }
@@ -26284,6 +26809,11 @@ var BluMaAgent = class {
26284
26809
  if (!this.sessionFile) return;
26285
26810
  void saveSessionHistory(this.sessionFile, this.history, this.getMemorySnapshot());
26286
26811
  }
26812
+ /** Gravação imediata após prune (histórico cortado + anchor no JSON de sessão). */
26813
+ async persistSessionAfterPrune() {
26814
+ if (!this.sessionFile) return;
26815
+ await saveSessionHistoryNow(this.sessionFile, this.history, this.getMemorySnapshot());
26816
+ }
26287
26817
  recordUiSlashCommand(command, mode = "visible_only") {
26288
26818
  const text = String(command ?? "").trim();
26289
26819
  if (!text) return;
@@ -26475,7 +27005,7 @@ var BluMaAgent = class {
26475
27005
 
26476
27006
  ${editData.error.display}`;
26477
27007
  }
26478
- const filename = path41.basename(toolArgs.file_path);
27008
+ const filename = path43.basename(toolArgs.file_path);
26479
27009
  return createDiff(filename, editData.currentContent || "", editData.newContent);
26480
27010
  } catch (e) {
26481
27011
  return `An unexpected error occurred while generating the edit preview: ${e.message}`;
@@ -27201,16 +27731,16 @@ async function fullCompact(messages, targetTokens, summarizer, llmClient) {
27201
27731
  }
27202
27732
 
27203
27733
  // src/app/agent/core/memory/session_memory.ts
27204
- import fs39 from "fs";
27734
+ import fs41 from "fs";
27205
27735
  import os30 from "os";
27206
- import path42 from "path";
27736
+ import path44 from "path";
27207
27737
  import { v4 as uuidv49 } from "uuid";
27208
27738
  var SessionMemoryExtractor = class {
27209
27739
  llmClient;
27210
27740
  memoryFile;
27211
27741
  constructor(options = {}) {
27212
27742
  this.llmClient = options.llmClient;
27213
- this.memoryFile = options.memoryFile || path42.join(os30.homedir(), ".bluma", "session_memory.json");
27743
+ this.memoryFile = options.memoryFile || path44.join(os30.homedir(), ".bluma", "session_memory.json");
27214
27744
  }
27215
27745
  /**
27216
27746
  * Extract memories from conversation using LLM
@@ -27267,15 +27797,15 @@ ${messages.slice(-50).map((m) => `${m.role}: ${m.content.slice(0, 500)}`).join("
27267
27797
  );
27268
27798
  unique.sort((a, b) => b.accessCount - a.accessCount);
27269
27799
  const trimmed = unique.slice(0, 200);
27270
- fs39.writeFileSync(this.memoryFile, JSON.stringify(trimmed, null, 2));
27800
+ fs41.writeFileSync(this.memoryFile, JSON.stringify(trimmed, null, 2));
27271
27801
  }
27272
27802
  /**
27273
27803
  * Load memories from disk
27274
27804
  */
27275
27805
  async loadMemories() {
27276
27806
  try {
27277
- if (!fs39.existsSync(this.memoryFile)) return [];
27278
- const data = fs39.readFileSync(this.memoryFile, "utf-8");
27807
+ if (!fs41.existsSync(this.memoryFile)) return [];
27808
+ const data = fs41.readFileSync(this.memoryFile, "utf-8");
27279
27809
  return JSON.parse(data);
27280
27810
  } catch {
27281
27811
  return [];
@@ -27819,14 +28349,14 @@ var RouteManager = class {
27819
28349
  this.subAgents = subAgents;
27820
28350
  this.core = core;
27821
28351
  }
27822
- registerRoute(path54, handler) {
27823
- this.routeHandlers.set(path54, handler);
28352
+ registerRoute(path56, handler) {
28353
+ this.routeHandlers.set(path56, handler);
27824
28354
  }
27825
28355
  async handleRoute(payload) {
27826
28356
  const inputText = String(payload.content || "").trim();
27827
28357
  const { userContext, options } = payload;
27828
- for (const [path54, handler] of this.routeHandlers) {
27829
- if (inputText === path54 || inputText.startsWith(`${path54} `)) {
28358
+ for (const [path56, handler] of this.routeHandlers) {
28359
+ if (inputText === path56 || inputText.startsWith(`${path56} `)) {
27830
28360
  return handler({ content: inputText, userContext });
27831
28361
  }
27832
28362
  }
@@ -27835,13 +28365,13 @@ var RouteManager = class {
27835
28365
  };
27836
28366
 
27837
28367
  // src/app/agent/runtime/plugin_runtime.ts
27838
- import path43 from "path";
28368
+ import path45 from "path";
27839
28369
  import { pathToFileURL as pathToFileURL2 } from "url";
27840
28370
  async function loadPluginsAtStartup() {
27841
28371
  for (const p of listPlugins()) {
27842
28372
  const entry = p.manifest.entry?.trim();
27843
28373
  if (!entry) continue;
27844
- const abs = path43.resolve(p.root, entry);
28374
+ const abs = path45.resolve(p.root, entry);
27845
28375
  try {
27846
28376
  const href = pathToFileURL2(abs).href;
27847
28377
  const mod = await import(href);
@@ -27862,7 +28392,7 @@ async function loadPluginsAtStartup() {
27862
28392
  }
27863
28393
 
27864
28394
  // src/app/agent/agent.ts
27865
- var globalEnvPath = path44.join(os31.homedir(), ".bluma", ".env");
28395
+ var globalEnvPath = path46.join(os31.homedir(), ".bluma", ".env");
27866
28396
  dotenv.config({ path: globalEnvPath });
27867
28397
  var Agent = class {
27868
28398
  sessionId;
@@ -29718,10 +30248,10 @@ function resolveToolPayload(result) {
29718
30248
 
29719
30249
  // src/app/ui/components/FilePathLink.tsx
29720
30250
  import { pathToFileURL as pathToFileURL3 } from "node:url";
29721
- import path46 from "node:path";
30251
+ import path48 from "node:path";
29722
30252
 
29723
30253
  // src/app/ui/utils/pathDisplay.ts
29724
- import path45 from "node:path";
30254
+ import path47 from "node:path";
29725
30255
  import os32 from "node:os";
29726
30256
  function formatPathForDisplay(pathInput, cwd2 = process.cwd()) {
29727
30257
  let s = String(pathInput ?? "").trim();
@@ -29729,17 +30259,17 @@ function formatPathForDisplay(pathInput, cwd2 = process.cwd()) {
29729
30259
  s = s.replace(/[/\\]+$/, "");
29730
30260
  }
29731
30261
  if (!s) return "";
29732
- const abs = path45.isAbsolute(s) ? path45.normalize(s) : path45.resolve(cwd2, s);
29733
- const resolvedCwd = path45.resolve(cwd2);
29734
- const rel = path45.relative(resolvedCwd, abs);
29735
- if (rel === "" || !rel.startsWith("..") && !path45.isAbsolute(rel)) {
30262
+ const abs = path47.isAbsolute(s) ? path47.normalize(s) : path47.resolve(cwd2, s);
30263
+ const resolvedCwd = path47.resolve(cwd2);
30264
+ const rel = path47.relative(resolvedCwd, abs);
30265
+ if (rel === "" || !rel.startsWith("..") && !path47.isAbsolute(rel)) {
29736
30266
  return rel === "" ? "." : rel;
29737
30267
  }
29738
- const home = path45.normalize(os32.homedir());
29739
- if (abs === home || abs.startsWith(home + path45.sep)) {
30268
+ const home = path47.normalize(os32.homedir());
30269
+ if (abs === home || abs.startsWith(home + path47.sep)) {
29740
30270
  return "~" + abs.slice(home.length);
29741
30271
  }
29742
- return path45.basename(abs);
30272
+ return path47.basename(abs);
29743
30273
  }
29744
30274
 
29745
30275
  // src/app/ui/components/FilePathLink.tsx
@@ -29749,7 +30279,7 @@ function FilePathLink({ filePath, children, cwd: cwd2 = process.cwd(), color })
29749
30279
  if (!raw) {
29750
30280
  return null;
29751
30281
  }
29752
- const abs = path46.isAbsolute(raw) ? path46.normalize(raw) : path46.resolve(cwd2, raw);
30282
+ const abs = path48.isAbsolute(raw) ? path48.normalize(raw) : path48.resolve(cwd2, raw);
29753
30283
  const href = pathToFileURL3(abs).href;
29754
30284
  const label = formatPathForDisplay(abs, cwd2);
29755
30285
  const text = typeof children === "string" ? children : label;
@@ -31701,12 +32231,12 @@ function patchToUnifiedDiffText(filePath, patch) {
31701
32231
  }
31702
32232
 
31703
32233
  // src/app/ui/utils/readEditContext.ts
31704
- import { promises as fs40 } from "fs";
32234
+ import { promises as fs42 } from "fs";
31705
32235
  var CHUNK_SIZE = 64 * 1024;
31706
32236
  var CONTEXT_LINES2 = 3;
31707
32237
  async function openForScan(filePath) {
31708
32238
  try {
31709
- return await fs40.open(filePath, "r");
32239
+ return await fs42.open(filePath, "r");
31710
32240
  } catch (e) {
31711
32241
  if (e.code === "ENOENT") return null;
31712
32242
  throw e;
@@ -32384,13 +32914,13 @@ function EditToolDiffPanel({
32384
32914
  newString,
32385
32915
  replaceAll = false
32386
32916
  }) {
32387
- const path54 = filePath.trim() || "unknown file";
32917
+ const path56 = filePath.trim() || "unknown file";
32388
32918
  const hasPreviewArgs = oldString !== void 0 && newString !== void 0;
32389
32919
  const hasDiffText = diffText && diffText.trim().length > 0;
32390
32920
  return /* @__PURE__ */ jsx43(Box_default, { flexDirection: "column", children: hasPreviewArgs ? /* @__PURE__ */ jsx43(Box_default, { marginTop: 0, children: /* @__PURE__ */ jsx43(
32391
32921
  FileEditToolDiff,
32392
32922
  {
32393
- filePath: path54,
32923
+ filePath: path56,
32394
32924
  oldString,
32395
32925
  newString,
32396
32926
  replaceAll,
@@ -32428,7 +32958,7 @@ function renderToolUseMessage12({ args }) {
32428
32958
  return /* @__PURE__ */ jsx44(Text, { color: BLUMA_TERMINAL.blue, children: p });
32429
32959
  }
32430
32960
  function renderToolHeader12({ args }) {
32431
- const path54 = args?.file_path ?? ".";
32961
+ const path56 = args?.file_path ?? ".";
32432
32962
  const oldText = typeof args?.old_string === "string" ? args.old_string : "";
32433
32963
  const newText = typeof args?.new_string === "string" ? args.new_string : "";
32434
32964
  const counts = countLineDiff(oldText, newText);
@@ -32438,7 +32968,7 @@ function renderToolHeader12({ args }) {
32438
32968
  action,
32439
32969
  /* @__PURE__ */ jsxs27(Text, { dimColor: true, children: [
32440
32970
  " ",
32441
- /* @__PURE__ */ jsx44(FilePathLink, { filePath: path54, color: BLUMA_TERMINAL.dim })
32971
+ /* @__PURE__ */ jsx44(FilePathLink, { filePath: path56, color: BLUMA_TERMINAL.dim })
32442
32972
  ] })
32443
32973
  ] }),
32444
32974
  /* @__PURE__ */ jsxs27(Text, { dimColor: true, children: [
@@ -32830,11 +33360,11 @@ function userFacingName13() {
32830
33360
  }
32831
33361
  function renderToolUseMessage14({ args }) {
32832
33362
  const q = args?.query ? `"${args.query}"` : "...";
32833
- const path54 = args?.path || ".";
33363
+ const path56 = args?.path || ".";
32834
33364
  return /* @__PURE__ */ jsxs30(Box_default, { flexDirection: "row", flexWrap: "wrap", alignItems: "flex-end", children: [
32835
33365
  /* @__PURE__ */ jsx47(Text, { color: BLUMA_TERMINAL.blue, children: q }),
32836
33366
  /* @__PURE__ */ jsx47(Text, { color: BLUMA_TERMINAL.dim, children: " " }),
32837
- /* @__PURE__ */ jsx47(FilePathLink, { filePath: path54, color: BLUMA_TERMINAL.dim })
33367
+ /* @__PURE__ */ jsx47(FilePathLink, { filePath: path56, color: BLUMA_TERMINAL.dim })
32838
33368
  ] });
32839
33369
  }
32840
33370
  function renderToolHeader14({ args }) {
@@ -33356,7 +33886,7 @@ var loadSkillTool = createTool({
33356
33886
  });
33357
33887
 
33358
33888
  // src/app/agent/tools/FileWriteTool/UI.tsx
33359
- import fs41 from "fs";
33889
+ import fs43 from "fs";
33360
33890
  import { diffLines as diffLines3 } from "diff";
33361
33891
  import { jsx as jsx54, jsxs as jsxs37 } from "react/jsx-runtime";
33362
33892
  function getFilePath(args) {
@@ -33371,7 +33901,7 @@ function getFilePath(args) {
33371
33901
  function readExistingFileText(filePath) {
33372
33902
  if (!filePath) return "";
33373
33903
  try {
33374
- return fs41.readFileSync(filePath, "utf-8");
33904
+ return fs43.readFileSync(filePath, "utf-8");
33375
33905
  } catch {
33376
33906
  return "";
33377
33907
  }
@@ -36301,8 +36831,8 @@ import {
36301
36831
 
36302
36832
  // src/app/ui/hooks/useAtCompletion.ts
36303
36833
  import { useEffect as useEffect11, useRef as useRef4, useState as useState13 } from "react";
36304
- import fs42 from "fs";
36305
- import path47 from "path";
36834
+ import fs44 from "fs";
36835
+ import path49 from "path";
36306
36836
  var MAX_RESULTS3 = 50;
36307
36837
  var DEFAULT_RECURSIVE_DEPTH = 2;
36308
36838
  function listPathSuggestions(baseDir, pattern) {
@@ -36310,7 +36840,7 @@ function listPathSuggestions(baseDir, pattern) {
36310
36840
  const patternEndsWithSlash = raw.endsWith("/");
36311
36841
  const relDir = raw.replace(/^\/+|\/+$/g, "");
36312
36842
  const filterPrefix = patternEndsWithSlash ? "" : relDir.split("/").slice(-1)[0] || "";
36313
- const listDir = path47.resolve(baseDir, relDir || ".");
36843
+ const listDir = path49.resolve(baseDir, relDir || ".");
36314
36844
  const results = [];
36315
36845
  const IGNORED_DIRS = ["node_modules", ".git", ".venv", "dist", "build"];
36316
36846
  const IGNORED_EXTS = [".pyc", ".class", ".o", ".map", ".log", ".tmp"];
@@ -36327,7 +36857,7 @@ function listPathSuggestions(baseDir, pattern) {
36327
36857
  }
36328
36858
  function pushEntry(entryPath, label, isDir) {
36329
36859
  if (results.length >= MAX_RESULTS3) return;
36330
- const clean = label.split(path47.sep).join("/").replace(/[]+/g, "");
36860
+ const clean = label.split(path49.sep).join("/").replace(/[]+/g, "");
36331
36861
  results.push({ label: clean + (isDir ? "/" : ""), fullPath: entryPath, isDir });
36332
36862
  }
36333
36863
  try {
@@ -36336,11 +36866,11 @@ function listPathSuggestions(baseDir, pattern) {
36336
36866
  while (queue.length && results.length < MAX_RESULTS3) {
36337
36867
  const node = queue.shift();
36338
36868
  try {
36339
- const entries = fs42.readdirSync(node.dir, { withFileTypes: true });
36869
+ const entries = fs44.readdirSync(node.dir, { withFileTypes: true });
36340
36870
  for (const entry of entries) {
36341
36871
  if (isIgnoredName(entry.name)) continue;
36342
- const entryAbs = path47.join(node.dir, entry.name);
36343
- const entryRel = node.rel ? path47.posix.join(node.rel, entry.name) : entry.name;
36872
+ const entryAbs = path49.join(node.dir, entry.name);
36873
+ const entryRel = node.rel ? path49.posix.join(node.rel, entry.name) : entry.name;
36344
36874
  if (entryRel.split("/").includes("node_modules")) continue;
36345
36875
  if (!entry.isDirectory() && isIgnoredFile(entry.name)) continue;
36346
36876
  pushEntry(entryAbs, entryRel, entry.isDirectory());
@@ -36353,13 +36883,13 @@ function listPathSuggestions(baseDir, pattern) {
36353
36883
  }
36354
36884
  }
36355
36885
  } else {
36356
- const entries = fs42.readdirSync(listDir, { withFileTypes: true });
36886
+ const entries = fs44.readdirSync(listDir, { withFileTypes: true });
36357
36887
  for (const entry of entries) {
36358
36888
  if (filterPrefix && !entry.name.startsWith(filterPrefix)) continue;
36359
36889
  if (isIgnoredName(entry.name)) continue;
36360
36890
  if (!entry.isDirectory() && isIgnoredFile(entry.name)) continue;
36361
- const entryAbs = path47.join(listDir, entry.name);
36362
- const label = relDir ? path47.posix.join(relDir, entry.name) : entry.name;
36891
+ const entryAbs = path49.join(listDir, entry.name);
36892
+ const label = relDir ? path49.posix.join(relDir, entry.name) : entry.name;
36363
36893
  pushEntry(entryAbs, label, entry.isDirectory());
36364
36894
  if (results.length >= MAX_RESULTS3) break;
36365
36895
  }
@@ -36560,15 +37090,15 @@ var SlashSubmenuInlineComponent = ({ menu }) => {
36560
37090
  var SlashSubmenuInline = memo15(SlashSubmenuInlineComponent);
36561
37091
 
36562
37092
  // src/app/ui/utils/clipboardImage.ts
36563
- import fs43 from "fs";
37093
+ import fs45 from "fs";
36564
37094
  import os33 from "os";
36565
- import path48 from "path";
37095
+ import path50 from "path";
36566
37096
  import { spawn as spawn5, execFile as execFileCb, execSync as execSync4 } from "child_process";
36567
37097
  import { promisify as promisify2 } from "util";
36568
37098
 
36569
37099
  // src/app/utils/clipboardNative.ts
36570
37100
  import { existsSync as existsSync7 } from "fs";
36571
- import { createRequire } from "module";
37101
+ import { createRequire as createRequire2 } from "module";
36572
37102
  import { dirname as dirname4, join as join8 } from "path";
36573
37103
  import { fileURLToPath as fileURLToPath5 } from "url";
36574
37104
  var __dirname;
@@ -36616,7 +37146,7 @@ function getNativeModule() {
36616
37146
  throw loadError;
36617
37147
  }
36618
37148
  try {
36619
- const require2 = createRequire(import.meta.url);
37149
+ const require2 = createRequire2(import.meta.url);
36620
37150
  const mod = require2(nativePath);
36621
37151
  nativeModule = mod;
36622
37152
  return nativeModule;
@@ -36691,8 +37221,8 @@ function commandOnPath(cmd) {
36691
37221
  function unixClipboardHelperDirs() {
36692
37222
  const h = os33.homedir();
36693
37223
  return [
36694
- path48.join(h, ".local", "bin"),
36695
- path48.join(h, "bin"),
37224
+ path50.join(h, ".local", "bin"),
37225
+ path50.join(h, "bin"),
36696
37226
  "/usr/bin",
36697
37227
  "/usr/local/bin",
36698
37228
  "/bin",
@@ -36710,16 +37240,16 @@ function resolveHelperBinary(cmd) {
36710
37240
  return cmd;
36711
37241
  }
36712
37242
  for (const dir of unixClipboardHelperDirs()) {
36713
- const full = path48.join(dir, cmd);
37243
+ const full = path50.join(dir, cmd);
36714
37244
  try {
36715
- fs43.accessSync(full, fs43.constants.X_OK);
37245
+ fs45.accessSync(full, fs45.constants.X_OK);
36716
37246
  return full;
36717
37247
  } catch {
36718
37248
  }
36719
37249
  }
36720
37250
  for (const dir of unixClipboardHelperDirs()) {
36721
- const full = path48.join(dir, cmd);
36722
- if (fs43.existsSync(full)) {
37251
+ const full = path50.join(dir, cmd);
37252
+ if (fs45.existsSync(full)) {
36723
37253
  return full;
36724
37254
  }
36725
37255
  }
@@ -36760,17 +37290,17 @@ function writeBufferIfImage(baseDir, buf) {
36760
37290
  if (!ext) {
36761
37291
  return null;
36762
37292
  }
36763
- const out = path48.join(
37293
+ const out = path50.join(
36764
37294
  baseDir,
36765
37295
  `clip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext}`
36766
37296
  );
36767
- fs43.writeFileSync(out, buf);
37297
+ fs45.writeFileSync(out, buf);
36768
37298
  return out;
36769
37299
  }
36770
37300
  function unlinkQuiet(p) {
36771
37301
  try {
36772
- if (fs43.existsSync(p)) {
36773
- fs43.unlinkSync(p);
37302
+ if (fs45.existsSync(p)) {
37303
+ fs45.unlinkSync(p);
36774
37304
  }
36775
37305
  } catch {
36776
37306
  }
@@ -36787,25 +37317,25 @@ async function tryDarwinClipboardy(baseDir) {
36787
37317
  return null;
36788
37318
  }
36789
37319
  for (const src of tmpPaths) {
36790
- if (!src || !fs43.existsSync(src)) {
37320
+ if (!src || !fs45.existsSync(src)) {
36791
37321
  continue;
36792
37322
  }
36793
37323
  let st;
36794
37324
  try {
36795
- st = fs43.statSync(src);
37325
+ st = fs45.statSync(src);
36796
37326
  } catch {
36797
37327
  continue;
36798
37328
  }
36799
37329
  if (st.size < 80 || st.size > CLIPBOARD_MAX_BYTES) {
36800
37330
  continue;
36801
37331
  }
36802
- const ext = path48.extname(src).toLowerCase();
37332
+ const ext = path50.extname(src).toLowerCase();
36803
37333
  const safeExt = ext && /^\.(png|jpe?g|gif|webp)$/i.test(ext) ? ext : ".png";
36804
- const out = path48.join(
37334
+ const out = path50.join(
36805
37335
  baseDir,
36806
37336
  `clip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${safeExt}`
36807
37337
  );
36808
- fs43.copyFileSync(src, out);
37338
+ fs45.copyFileSync(src, out);
36809
37339
  for (const p of tmpPaths) {
36810
37340
  unlinkQuiet(p);
36811
37341
  }
@@ -36819,14 +37349,14 @@ async function tryDarwinClipboardy(baseDir) {
36819
37349
  return null;
36820
37350
  }
36821
37351
  async function tryWindowsPowerShell(outFile) {
36822
- const ps = process.env.SystemRoot != null ? path48.join(
37352
+ const ps = process.env.SystemRoot != null ? path50.join(
36823
37353
  process.env.SystemRoot,
36824
37354
  "System32",
36825
37355
  "WindowsPowerShell",
36826
37356
  "v1.0",
36827
37357
  "powershell.exe"
36828
37358
  ) : "powershell.exe";
36829
- if (!fs43.existsSync(ps)) {
37359
+ if (!fs45.existsSync(ps)) {
36830
37360
  return false;
36831
37361
  }
36832
37362
  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 +37378,7 @@ async function tryWindowsPowerShell(outFile) {
36848
37378
  return false;
36849
37379
  }
36850
37380
  try {
36851
- const st = fs43.statSync(outFile);
37381
+ const st = fs45.statSync(outFile);
36852
37382
  return st.size >= 80 && st.size <= CLIPBOARD_MAX_BYTES;
36853
37383
  } catch {
36854
37384
  return false;
@@ -36920,8 +37450,8 @@ function parseClipboardTextAsImagePath(raw) {
36920
37450
  s = s.slice(1, -1);
36921
37451
  }
36922
37452
  s = s.trim();
36923
- if (!CLIPBOARD_PATH_IMAGE_EXT.test(path48.extname(s))) return null;
36924
- const abs = path48.isAbsolute(s) ? path48.normalize(s) : path48.resolve(process.cwd(), s);
37453
+ if (!CLIPBOARD_PATH_IMAGE_EXT.test(path50.extname(s))) return null;
37454
+ const abs = path50.isAbsolute(s) ? path50.normalize(s) : path50.resolve(process.cwd(), s);
36925
37455
  return abs;
36926
37456
  }
36927
37457
  async function tryClipboardTextAsImageFile(baseDir) {
@@ -36935,19 +37465,19 @@ async function tryClipboardTextAsImageFile(baseDir) {
36935
37465
  const abs = parseClipboardTextAsImagePath(t);
36936
37466
  if (!abs) return null;
36937
37467
  try {
36938
- if (!fs43.existsSync(abs)) return null;
36939
- const st = fs43.statSync(abs);
37468
+ if (!fs45.existsSync(abs)) return null;
37469
+ const st = fs45.statSync(abs);
36940
37470
  if (!st.isFile() || st.size > CLIPBOARD_MAX_BYTES || st.size < 20) return null;
36941
37471
  } catch {
36942
37472
  return null;
36943
37473
  }
36944
- const ext = path48.extname(abs).toLowerCase();
36945
- const out = path48.join(
37474
+ const ext = path50.extname(abs).toLowerCase();
37475
+ const out = path50.join(
36946
37476
  baseDir,
36947
37477
  `clip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext}`
36948
37478
  );
36949
37479
  try {
36950
- fs43.copyFileSync(abs, out);
37480
+ fs45.copyFileSync(abs, out);
36951
37481
  return out;
36952
37482
  } catch {
36953
37483
  return null;
@@ -36957,7 +37487,7 @@ async function tryLinuxShellPipelineSave(baseDir) {
36957
37487
  if (process.platform !== "linux" && process.platform !== "freebsd") {
36958
37488
  return null;
36959
37489
  }
36960
- const outPath = path48.join(
37490
+ const outPath = path50.join(
36961
37491
  baseDir,
36962
37492
  `clip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.tmp`
36963
37493
  );
@@ -36979,11 +37509,11 @@ printf '%s' "$OUT"
36979
37509
  maxBuffer: 4096
36980
37510
  });
36981
37511
  const written = String(stdout ?? "").trim();
36982
- if (written !== outPath || !fs43.existsSync(outPath)) {
37512
+ if (written !== outPath || !fs45.existsSync(outPath)) {
36983
37513
  unlinkQuiet(outPath);
36984
37514
  return null;
36985
37515
  }
36986
- const buf = fs43.readFileSync(outPath);
37516
+ const buf = fs45.readFileSync(outPath);
36987
37517
  unlinkQuiet(outPath);
36988
37518
  return writeBufferIfImage(baseDir, buf);
36989
37519
  } catch {
@@ -37004,8 +37534,8 @@ async function tryNativeClipboardImage() {
37004
37534
  }
37005
37535
  try {
37006
37536
  const result = readClipboardImageNative();
37007
- if (fs43.existsSync(result.path)) {
37008
- const st = fs43.statSync(result.path);
37537
+ if (fs45.existsSync(result.path)) {
37538
+ const st = fs45.statSync(result.path);
37009
37539
  if (st.size >= 80 && st.size <= CLIPBOARD_MAX_BYTES) {
37010
37540
  return result.path;
37011
37541
  }
@@ -37015,8 +37545,8 @@ async function tryNativeClipboardImage() {
37015
37545
  return null;
37016
37546
  }
37017
37547
  async function readClipboardImageToTempFile() {
37018
- const baseDir = path48.join(os33.homedir(), ".cache", "bluma", "clipboard");
37019
- fs43.mkdirSync(baseDir, { recursive: true });
37548
+ const baseDir = path50.join(os33.homedir(), ".cache", "bluma", "clipboard");
37549
+ fs45.mkdirSync(baseDir, { recursive: true });
37020
37550
  const nativeResult = await tryNativeClipboardImage();
37021
37551
  if (nativeResult) {
37022
37552
  return nativeResult;
@@ -37034,7 +37564,7 @@ async function readClipboardImageToTempFile() {
37034
37564
  }
37035
37565
  }
37036
37566
  if (process.platform === "win32") {
37037
- const outFile = path48.join(
37567
+ const outFile = path50.join(
37038
37568
  baseDir,
37039
37569
  `clip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.png`
37040
37570
  );
@@ -37075,7 +37605,7 @@ function expandLargePastePlaceholder(value, pending) {
37075
37605
  }
37076
37606
 
37077
37607
  // src/app/ui/components/InputPrompt.tsx
37078
- import fs44 from "fs";
37608
+ import fs46 from "fs";
37079
37609
  import { Fragment as Fragment7, jsx as jsx79, jsxs as jsxs62 } from "react/jsx-runtime";
37080
37610
  var persistedInputState = { text: "", cursorPosition: 0 };
37081
37611
  var StaticCursor = () => /* @__PURE__ */ jsx79(Box_default, { flexDirection: "row", flexWrap: "nowrap", children: /* @__PURE__ */ jsx79(Text, { bold: true, color: BLUMA_TERMINAL.m3OnSurface, children: "\u2588 " }) });
@@ -37242,7 +37772,7 @@ var InputPrompt = memo16(({
37242
37772
  return;
37243
37773
  }
37244
37774
  if (routeText.startsWith("/")) {
37245
- const isFilePath = map.size > 0 && fs44.existsSync(routeText.split(/\s+/)[0]);
37775
+ const isFilePath = map.size > 0 && fs46.existsSync(routeText.split(/\s+/)[0]);
37246
37776
  if (isFilePath) {
37247
37777
  uiEventBus.emit("user_overlay", {
37248
37778
  kind: "message",
@@ -39522,8 +40052,8 @@ var renderCode = () => {
39522
40052
  };
39523
40053
 
39524
40054
  // src/app/agent/core/thread/thread_store.ts
39525
- import path49 from "path";
39526
- import { promises as fs45 } from "fs";
40055
+ import path51 from "path";
40056
+ import { promises as fs47 } from "fs";
39527
40057
  import { randomUUID as randomUUID2 } from "crypto";
39528
40058
  var INDEX_VERSION = 1;
39529
40059
  var fileLocks2 = /* @__PURE__ */ new Map();
@@ -39548,9 +40078,9 @@ var ThreadStore = class {
39548
40078
  packageVersion;
39549
40079
  constructor() {
39550
40080
  const appDir = getPreferredAppDir();
39551
- this.threadsDir = path49.join(appDir, "threads");
39552
- this.archiveDir = path49.join(this.threadsDir, "archive");
39553
- this.indexPath = path49.join(this.threadsDir, "index.json");
40081
+ this.threadsDir = path51.join(appDir, "threads");
40082
+ this.archiveDir = path51.join(this.threadsDir, "archive");
40083
+ this.indexPath = path51.join(this.threadsDir, "index.json");
39554
40084
  this.packageVersion = process.env.npm_package_version || "0.0.0";
39555
40085
  }
39556
40086
  // ==================== Inicialização ====================
@@ -39558,10 +40088,10 @@ var ThreadStore = class {
39558
40088
  * Inicializa o diretório de threads
39559
40089
  */
39560
40090
  async initialize() {
39561
- await fs45.mkdir(this.threadsDir, { recursive: true });
39562
- await fs45.mkdir(this.archiveDir, { recursive: true });
40091
+ await fs47.mkdir(this.threadsDir, { recursive: true });
40092
+ await fs47.mkdir(this.archiveDir, { recursive: true });
39563
40093
  try {
39564
- await fs45.access(this.indexPath);
40094
+ await fs47.access(this.indexPath);
39565
40095
  } catch {
39566
40096
  await this.saveIndex({ version: INDEX_VERSION, threads: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() });
39567
40097
  }
@@ -39590,7 +40120,7 @@ var ThreadStore = class {
39590
40120
  async loadIndex() {
39591
40121
  return withFileLock2(this.indexPath, async () => {
39592
40122
  try {
39593
- const content = await fs45.readFile(this.indexPath, "utf-8");
40123
+ const content = await fs47.readFile(this.indexPath, "utf-8");
39594
40124
  return JSON.parse(content);
39595
40125
  } catch {
39596
40126
  return { version: INDEX_VERSION, threads: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
@@ -39601,8 +40131,8 @@ var ThreadStore = class {
39601
40131
  return withFileLock2(this.indexPath, async () => {
39602
40132
  index.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
39603
40133
  const tempPath = `${this.indexPath}.${Date.now()}.tmp`;
39604
- await fs45.writeFile(tempPath, JSON.stringify(index, null, 2), "utf-8");
39605
- await fs45.rename(tempPath, this.indexPath);
40134
+ await fs47.writeFile(tempPath, JSON.stringify(index, null, 2), "utf-8");
40135
+ await fs47.rename(tempPath, this.indexPath);
39606
40136
  });
39607
40137
  }
39608
40138
  // ==================== Git Info ====================
@@ -39672,7 +40202,7 @@ var ThreadStore = class {
39672
40202
  messages: params.initialMessages || []
39673
40203
  };
39674
40204
  const historyPath = this.buildDatedThreadHistoryPath(threadId);
39675
- await fs45.mkdir(path49.dirname(historyPath), { recursive: true });
40205
+ await fs47.mkdir(path51.dirname(historyPath), { recursive: true });
39676
40206
  await this.saveHistoryAtPath(historyPath, history);
39677
40207
  const index = await this.loadIndex();
39678
40208
  index.threads.unshift({
@@ -39727,7 +40257,7 @@ var ThreadStore = class {
39727
40257
  compressedSliceCount: source.history.compressedSliceCount
39728
40258
  };
39729
40259
  const historyPath = this.buildDatedThreadHistoryPath(newThreadId);
39730
- await fs45.mkdir(path49.dirname(historyPath), { recursive: true });
40260
+ await fs47.mkdir(path51.dirname(historyPath), { recursive: true });
39731
40261
  await this.saveHistoryAtPath(historyPath, history);
39732
40262
  const index = await this.loadIndex();
39733
40263
  index.threads.unshift({
@@ -39800,9 +40330,9 @@ var ThreadStore = class {
39800
40330
  const entry = index.threads[entryIndex];
39801
40331
  if (entry.status === "archived") return true;
39802
40332
  const oldPath = entry.historyPath || this.getLegacyHistoryPath(threadId);
39803
- const newPath = path49.join(this.archiveDir, `${threadId}.jsonl`);
40333
+ const newPath = path51.join(this.archiveDir, `${threadId}.jsonl`);
39804
40334
  try {
39805
- await fs45.rename(oldPath, newPath);
40335
+ await fs47.rename(oldPath, newPath);
39806
40336
  } catch (e) {
39807
40337
  if (e.code !== "ENOENT") throw e;
39808
40338
  }
@@ -39823,11 +40353,11 @@ var ThreadStore = class {
39823
40353
  if (entryIndex === -1) return false;
39824
40354
  const entry = index.threads[entryIndex];
39825
40355
  if (entry.status === "active") return true;
39826
- const oldPath = path49.join(this.archiveDir, `${threadId}.jsonl`);
40356
+ const oldPath = path51.join(this.archiveDir, `${threadId}.jsonl`);
39827
40357
  const newPath = this.buildDatedThreadHistoryPath(threadId);
39828
- await fs45.mkdir(path49.dirname(newPath), { recursive: true });
40358
+ await fs47.mkdir(path51.dirname(newPath), { recursive: true });
39829
40359
  try {
39830
- await fs45.rename(oldPath, newPath);
40360
+ await fs47.rename(oldPath, newPath);
39831
40361
  } catch (e) {
39832
40362
  if (e.code !== "ENOENT") throw e;
39833
40363
  }
@@ -39848,7 +40378,7 @@ var ThreadStore = class {
39848
40378
  if (entryIndex === -1) return false;
39849
40379
  const entry = index.threads[entryIndex];
39850
40380
  try {
39851
- await fs45.unlink(entry.historyPath);
40381
+ await fs47.unlink(entry.historyPath);
39852
40382
  } catch {
39853
40383
  }
39854
40384
  index.threads.splice(entryIndex, 1);
@@ -39858,28 +40388,28 @@ var ThreadStore = class {
39858
40388
  }
39859
40389
  // ==================== Histórico ====================
39860
40390
  getLegacyHistoryPath(threadId) {
39861
- return path49.join(this.threadsDir, `${threadId}.jsonl`);
40391
+ return path51.join(this.threadsDir, `${threadId}.jsonl`);
39862
40392
  }
39863
40393
  /** ~/.bluma/threads/YYYY/MM/DD/<threadId>.jsonl (data local de criação). */
39864
40394
  buildDatedThreadHistoryPath(threadId, at = /* @__PURE__ */ new Date()) {
39865
40395
  const y = String(at.getFullYear());
39866
40396
  const mo = String(at.getMonth() + 1).padStart(2, "0");
39867
40397
  const d = String(at.getDate()).padStart(2, "0");
39868
- return path49.join(this.threadsDir, y, mo, d, `${threadId}.jsonl`);
40398
+ return path51.join(this.threadsDir, y, mo, d, `${threadId}.jsonl`);
39869
40399
  }
39870
40400
  async resolveHistoryPath(threadId) {
39871
40401
  const index = await this.loadIndex();
39872
40402
  const entry = index.threads.find((t) => t.threadId === threadId);
39873
40403
  if (entry?.historyPath) {
39874
40404
  try {
39875
- await fs45.access(entry.historyPath);
40405
+ await fs47.access(entry.historyPath);
39876
40406
  return entry.historyPath;
39877
40407
  } catch {
39878
40408
  }
39879
40409
  }
39880
40410
  const legacy = this.getLegacyHistoryPath(threadId);
39881
40411
  try {
39882
- await fs45.access(legacy);
40412
+ await fs47.access(legacy);
39883
40413
  return legacy;
39884
40414
  } catch {
39885
40415
  return entry?.historyPath ?? legacy;
@@ -39896,9 +40426,9 @@ var ThreadStore = class {
39896
40426
  for (const msg of history.messages) {
39897
40427
  lines.push(JSON.stringify({ type: "message", ...msg }));
39898
40428
  }
39899
- await fs45.mkdir(path49.dirname(historyPath), { recursive: true }).catch(() => {
40429
+ await fs47.mkdir(path51.dirname(historyPath), { recursive: true }).catch(() => {
39900
40430
  });
39901
- await fs45.writeFile(historyPath, lines.join("\n") + "\n", "utf-8");
40431
+ await fs47.writeFile(historyPath, lines.join("\n") + "\n", "utf-8");
39902
40432
  }
39903
40433
  /**
39904
40434
  * Guarda o histórico de uma thread
@@ -39920,7 +40450,7 @@ var ThreadStore = class {
39920
40450
  ].filter((p, i, arr) => Boolean(p) && arr.indexOf(p) === i);
39921
40451
  for (const historyPath of pathsToTry) {
39922
40452
  try {
39923
- const content = await fs45.readFile(historyPath, "utf-8");
40453
+ const content = await fs47.readFile(historyPath, "utf-8");
39924
40454
  const lines = content.split("\n").filter(Boolean);
39925
40455
  const history = {
39926
40456
  threadId,
@@ -39955,7 +40485,7 @@ var ThreadStore = class {
39955
40485
  const entry = index.threads.find((t) => t.threadId === threadId);
39956
40486
  if (!entry) throw new Error(`Thread not found: ${threadId}`);
39957
40487
  const lines = messages.map((msg) => JSON.stringify({ type: "message", ...msg }));
39958
- await fs45.appendFile(entry.historyPath, lines.join("\n") + "\n", "utf-8");
40488
+ await fs47.appendFile(entry.historyPath, lines.join("\n") + "\n", "utf-8");
39959
40489
  entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
39960
40490
  await this.saveIndex(index);
39961
40491
  }
@@ -42828,16 +43358,16 @@ import latestVersion from "latest-version";
42828
43358
  import semverGt from "semver/functions/gt.js";
42829
43359
  import semverValid from "semver/functions/valid.js";
42830
43360
  import { fileURLToPath as fileURLToPath6 } from "url";
42831
- import path50 from "path";
42832
- import fs46 from "fs";
43361
+ import path52 from "path";
43362
+ import fs48 from "fs";
42833
43363
  var BLUMA_PACKAGE_NAME = "@nomad-e/bluma-cli";
42834
43364
  function findBlumaPackageJson(startDir) {
42835
43365
  let dir = startDir;
42836
43366
  for (let i = 0; i < 12; i++) {
42837
- const candidate = path50.join(dir, "package.json");
42838
- if (fs46.existsSync(candidate)) {
43367
+ const candidate = path52.join(dir, "package.json");
43368
+ if (fs48.existsSync(candidate)) {
42839
43369
  try {
42840
- const raw = fs46.readFileSync(candidate, "utf8");
43370
+ const raw = fs48.readFileSync(candidate, "utf8");
42841
43371
  const parsed = JSON.parse(raw);
42842
43372
  if (parsed?.name === BLUMA_PACKAGE_NAME && parsed?.version) {
42843
43373
  return { name: parsed.name, version: String(parsed.version) };
@@ -42845,7 +43375,7 @@ function findBlumaPackageJson(startDir) {
42845
43375
  } catch {
42846
43376
  }
42847
43377
  }
42848
- const parent = path50.dirname(dir);
43378
+ const parent = path52.dirname(dir);
42849
43379
  if (parent === dir) break;
42850
43380
  dir = parent;
42851
43381
  }
@@ -42854,13 +43384,13 @@ function findBlumaPackageJson(startDir) {
42854
43384
  function resolveInstalledBlumaPackage() {
42855
43385
  const tried = /* @__PURE__ */ new Set();
42856
43386
  const tryFrom = (dir) => {
42857
- const abs = path50.resolve(dir);
43387
+ const abs = path52.resolve(dir);
42858
43388
  if (tried.has(abs)) return null;
42859
43389
  tried.add(abs);
42860
43390
  return findBlumaPackageJson(abs);
42861
43391
  };
42862
43392
  try {
42863
- const fromBundle = tryFrom(path50.dirname(fileURLToPath6(import.meta.url)));
43393
+ const fromBundle = tryFrom(path52.dirname(fileURLToPath6(import.meta.url)));
42864
43394
  if (fromBundle) return fromBundle;
42865
43395
  } catch {
42866
43396
  }
@@ -42868,12 +43398,12 @@ function resolveInstalledBlumaPackage() {
42868
43398
  if (argv1 && !argv1.startsWith("-")) {
42869
43399
  try {
42870
43400
  let resolved = argv1;
42871
- if (path50.isAbsolute(argv1) && fs46.existsSync(argv1)) {
42872
- resolved = fs46.realpathSync(argv1);
43401
+ if (path52.isAbsolute(argv1) && fs48.existsSync(argv1)) {
43402
+ resolved = fs48.realpathSync(argv1);
42873
43403
  } else {
42874
- resolved = path50.resolve(process.cwd(), argv1);
43404
+ resolved = path52.resolve(process.cwd(), argv1);
42875
43405
  }
42876
- const fromArgv = tryFrom(path50.dirname(resolved));
43406
+ const fromArgv = tryFrom(path52.dirname(resolved));
42877
43407
  if (fromArgv) return fromArgv;
42878
43408
  } catch {
42879
43409
  }
@@ -43692,16 +44222,16 @@ function usePlanMode() {
43692
44222
 
43693
44223
  // src/app/hooks/useAgentMode.ts
43694
44224
  import { useState as useState22, useEffect as useEffect21, useCallback as useCallback9 } from "react";
43695
- import * as fs47 from "fs";
43696
- import * as path51 from "path";
44225
+ import * as fs49 from "fs";
44226
+ import * as path53 from "path";
43697
44227
  import { homedir as homedir3 } from "os";
43698
- var SETTINGS_PATH = path51.join(homedir3(), ".bluma", "settings.json");
44228
+ var SETTINGS_PATH = path53.join(homedir3(), ".bluma", "settings.json");
43699
44229
  function readAgentModeFromFile() {
43700
44230
  try {
43701
- if (!fs47.existsSync(SETTINGS_PATH)) {
44231
+ if (!fs49.existsSync(SETTINGS_PATH)) {
43702
44232
  return "default";
43703
44233
  }
43704
- const content = fs47.readFileSync(SETTINGS_PATH, "utf-8");
44234
+ const content = fs49.readFileSync(SETTINGS_PATH, "utf-8");
43705
44235
  const settings = JSON.parse(content);
43706
44236
  return settings.agentMode || "default";
43707
44237
  } catch (error) {
@@ -43720,16 +44250,16 @@ function useAgentMode() {
43720
44250
  }, []);
43721
44251
  const updateAgentMode = useCallback9((mode) => {
43722
44252
  try {
43723
- if (!fs47.existsSync(SETTINGS_PATH)) {
43724
- fs47.mkdirSync(path51.dirname(SETTINGS_PATH), { recursive: true });
44253
+ if (!fs49.existsSync(SETTINGS_PATH)) {
44254
+ fs49.mkdirSync(path53.dirname(SETTINGS_PATH), { recursive: true });
43725
44255
  }
43726
44256
  let settings = {};
43727
- if (fs47.existsSync(SETTINGS_PATH)) {
43728
- const content = fs47.readFileSync(SETTINGS_PATH, "utf-8");
44257
+ if (fs49.existsSync(SETTINGS_PATH)) {
44258
+ const content = fs49.readFileSync(SETTINGS_PATH, "utf-8");
43729
44259
  settings = JSON.parse(content);
43730
44260
  }
43731
44261
  settings.agentMode = mode;
43732
- fs47.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf-8");
44262
+ fs49.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf-8");
43733
44263
  setAgentMode(mode);
43734
44264
  } catch (error) {
43735
44265
  console.error("Failed to update agent mode:", error);
@@ -44992,10 +45522,10 @@ import React39 from "react";
44992
45522
  import { memo as memo26, useCallback as useCallback11, useEffect as useEffect23, useMemo as useMemo8, useState as useState25 } from "react";
44993
45523
 
44994
45524
  // src/app/agent/session_manager/session_resume_browser.ts
44995
- import path52 from "path";
44996
- import { promises as fs48 } from "fs";
45525
+ import path54 from "path";
45526
+ import { promises as fs50 } from "fs";
44997
45527
  function getSessionsRoot() {
44998
- return path52.join(getPreferredAppDir(), "sessions");
45528
+ return path54.join(getPreferredAppDir(), "sessions");
44999
45529
  }
45000
45530
  function normalizeSessionsCwd(cwd2) {
45001
45531
  return cwd2.replace(/\\/g, "/").split("/").filter((p) => p && p !== "." && p !== "..").join("/");
@@ -45016,9 +45546,9 @@ async function sessionEntryFromFile(absPath, sessionId) {
45016
45546
  let preview = "(no messages)";
45017
45547
  let lastActivityMs = Date.now();
45018
45548
  try {
45019
- const st = await fs48.stat(absPath);
45549
+ const st = await fs50.stat(absPath);
45020
45550
  lastActivityMs = st.mtimeMs;
45021
- const raw = await fs48.readFile(absPath, "utf-8");
45551
+ const raw = await fs50.readFile(absPath, "utf-8");
45022
45552
  const data = JSON.parse(raw);
45023
45553
  preview = previewFromHistory(data.conversation_history);
45024
45554
  const iso = data.last_updated || data.created_at;
@@ -45046,15 +45576,15 @@ function compareDirNames(a, b) {
45046
45576
  async function listSessionBrowserEntries(cwdRel) {
45047
45577
  const cwd2 = normalizeSessionsCwd(cwdRel);
45048
45578
  const root = getSessionsRoot();
45049
- const absDir = cwd2 ? path52.join(root, ...cwd2.split("/")) : root;
45579
+ const absDir = cwd2 ? path54.join(root, ...cwd2.split("/")) : root;
45050
45580
  const out = [];
45051
45581
  if (cwd2) {
45052
45582
  out.push({ kind: "up", label: ".." });
45053
45583
  }
45054
45584
  let dirents;
45055
45585
  try {
45056
- await fs48.mkdir(absDir, { recursive: true });
45057
- dirents = await fs48.readdir(absDir, { withFileTypes: true });
45586
+ await fs50.mkdir(absDir, { recursive: true });
45587
+ dirents = await fs50.readdir(absDir, { withFileTypes: true });
45058
45588
  } catch {
45059
45589
  return out;
45060
45590
  }
@@ -45063,7 +45593,7 @@ async function listSessionBrowserEntries(cwdRel) {
45063
45593
  for (const e of dirents) {
45064
45594
  const name = String(e.name);
45065
45595
  if (name.startsWith(".")) continue;
45066
- const full = path52.join(absDir, name);
45596
+ const full = path54.join(absDir, name);
45067
45597
  if (e.isDirectory()) {
45068
45598
  dirNames.push(name);
45069
45599
  } else if (e.isFile() && name.endsWith(".json") && !name.includes(".tmp")) {
@@ -45073,7 +45603,7 @@ async function listSessionBrowserEntries(cwdRel) {
45073
45603
  dirNames.sort(compareDirNames);
45074
45604
  const sessions = [];
45075
45605
  for (const { name, full } of sessionFiles) {
45076
- const sessionId = path52.basename(name, ".json");
45606
+ const sessionId = path54.basename(name, ".json");
45077
45607
  sessions.push(await sessionEntryFromFile(full, sessionId));
45078
45608
  }
45079
45609
  sessions.sort((a, b) => {
@@ -45380,15 +45910,19 @@ function resolveResultUserMessage(lastAssistantMessage, lastAttachments) {
45380
45910
  }
45381
45911
  return "Tarefa conclu\xEDda.";
45382
45912
  }
45383
- function buildSuccessResultData(envelope, sessionId, lastAssistantMessage, lastAttachments) {
45913
+ function buildSuccessResultData(envelope, sessionId, lastAssistantMessage, lastAttachments, lastFactorShUrlApp) {
45384
45914
  const message2 = resolveResultUserMessage(lastAssistantMessage, lastAttachments);
45385
- return {
45915
+ const data = {
45386
45916
  message_id: envelope.message_id || sessionId,
45387
45917
  action: envelope.action || "unknown",
45388
45918
  last_assistant_message: message2,
45389
45919
  message: message2,
45390
45920
  attachments: lastAttachments
45391
45921
  };
45922
+ if (lastFactorShUrlApp) {
45923
+ data[FACTOR_SH_URL_APP_FIELD] = lastFactorShUrlApp;
45924
+ }
45925
+ return data;
45392
45926
  }
45393
45927
  function finalizeSession(sessionId, status, metadata) {
45394
45928
  updateSession(sessionId, {
@@ -45434,9 +45968,9 @@ async function runAgentMode() {
45434
45968
  try {
45435
45969
  if (inputFileIndex !== -1 && args[inputFileIndex + 1]) {
45436
45970
  const filePath = args[inputFileIndex + 1];
45437
- rawPayload = fs49.readFileSync(filePath, "utf-8");
45971
+ rawPayload = fs51.readFileSync(filePath, "utf-8");
45438
45972
  } else {
45439
- rawPayload = fs49.readFileSync(0, "utf-8");
45973
+ rawPayload = fs51.readFileSync(0, "utf-8");
45440
45974
  }
45441
45975
  } catch (err) {
45442
45976
  writeAgentEvent(registrySessionId, {
@@ -45472,6 +46006,15 @@ async function runAgentMode() {
45472
46006
  if (envelope.metadata?.sandbox_name) {
45473
46007
  process.env.BLUMA_SANDBOX_NAME = String(envelope.metadata.sandbox_name);
45474
46008
  }
46009
+ } else if (process.env.BLUMA_SANDBOX_WORKSPACE?.trim()) {
46010
+ process.env.BLUMA_SANDBOX = "true";
46011
+ if (!process.env.BLUMA_SANDBOX_NAME?.trim()) {
46012
+ process.env.BLUMA_SANDBOX_NAME = "sandbox-api";
46013
+ }
46014
+ }
46015
+ const severinoUrl = (process.env.SEVERINO_URL || "").trim();
46016
+ if (!(process.env.FACTORAI_BASE_URL || process.env.FACTORAI_URL || "").trim() && severinoUrl) {
46017
+ process.env.FACTORAI_BASE_URL = severinoUrl;
45475
46018
  }
45476
46019
  const envelopeUserRequest = typeof envelope.context === "object" && envelope.context !== null ? String(
45477
46020
  envelope.context.user_request ?? envelope.context.userRequest ?? ""
@@ -45510,6 +46053,7 @@ async function runAgentMode() {
45510
46053
  });
45511
46054
  let lastAssistantMessage = null;
45512
46055
  let lastAttachments = null;
46056
+ let lastFactorShUrlApp = null;
45513
46057
  let resultEmitted = false;
45514
46058
  let agentRef = null;
45515
46059
  let reasoningSequence = 0;
@@ -45560,6 +46104,16 @@ async function runAgentMode() {
45560
46104
  if (Array.isArray(attachments)) {
45561
46105
  lastAttachments = attachments.filter((p) => typeof p === "string");
45562
46106
  }
46107
+ const factorUrl = messagePayload?.[FACTOR_SH_URL_APP_FIELD] ?? parsed?.[FACTOR_SH_URL_APP_FIELD] ?? parsed?.data?.[FACTOR_SH_URL_APP_FIELD];
46108
+ if (typeof factorUrl === "string" && factorUrl.trim()) {
46109
+ lastFactorShUrlApp = factorUrl.trim();
46110
+ }
46111
+ if (!lastFactorShUrlApp && extractMessageToolPayload(parsed)?.message_type === "result") {
46112
+ const fromArgs = payload?.arguments?.[FACTOR_SH_URL_APP_FIELD];
46113
+ if (typeof fromArgs === "string" && fromArgs.trim()) {
46114
+ lastFactorShUrlApp = fromArgs.trim();
46115
+ }
46116
+ }
45563
46117
  } catch {
45564
46118
  }
45565
46119
  }
@@ -45576,7 +46130,8 @@ async function runAgentMode() {
45576
46130
  envelope,
45577
46131
  sessionId,
45578
46132
  lastAssistantMessage,
45579
- lastAttachments
46133
+ lastAttachments,
46134
+ lastFactorShUrlApp
45580
46135
  )
45581
46136
  });
45582
46137
  finalizeSession(sessionId, "completed", { finishedBy: "done-event" });
@@ -45628,7 +46183,8 @@ async function runAgentMode() {
45628
46183
  envelope,
45629
46184
  sessionId,
45630
46185
  lastAssistantMessage,
45631
- lastAttachments
46186
+ lastAttachments,
46187
+ lastFactorShUrlApp
45632
46188
  )
45633
46189
  });
45634
46190
  finalizeSession(sessionId, "completed", { finishedBy: "post-turn-fallback" });
@@ -45655,9 +46211,9 @@ async function runAgentMode() {
45655
46211
  }
45656
46212
  function readCliPackageVersion() {
45657
46213
  try {
45658
- const base = path53.dirname(fileURLToPath7(import.meta.url));
45659
- const pkgPath = path53.join(base, "..", "package.json");
45660
- const j = JSON.parse(fs49.readFileSync(pkgPath, "utf8"));
46214
+ const base = path55.dirname(fileURLToPath7(import.meta.url));
46215
+ const pkgPath = path55.join(base, "..", "package.json");
46216
+ const j = JSON.parse(fs51.readFileSync(pkgPath, "utf8"));
45661
46217
  return String(j.version || "0.0.0");
45662
46218
  } catch {
45663
46219
  return "0.0.0";
@@ -45783,7 +46339,7 @@ function startBackgroundAgent() {
45783
46339
  process.exit(1);
45784
46340
  }
45785
46341
  const filePath = args[inputFileIndex + 1];
45786
- const rawPayload = fs49.readFileSync(filePath, "utf-8");
46342
+ const rawPayload = fs51.readFileSync(filePath, "utf-8");
45787
46343
  const envelope = JSON.parse(rawPayload);
45788
46344
  const sessionId = envelope.session_id || envelope.message_id || uuidv412();
45789
46345
  registerSession({