@nomad-e/bluma-cli 0.1.33 → 0.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.js +924 -426
  2. package/package.json +5 -1
package/dist/main.js CHANGED
@@ -17,8 +17,8 @@ __export(async_command_exports, {
17
17
  runCommandAsync: () => runCommandAsync,
18
18
  sendCommandInput: () => sendCommandInput
19
19
  });
20
- import os3 from "os";
21
- import { spawn } from "child_process";
20
+ import os5 from "os";
21
+ import { spawn as spawn2 } from "child_process";
22
22
  import { v4 as uuidv42 } from "uuid";
23
23
  function cleanupOldCommands() {
24
24
  if (runningCommands.size <= MAX_STORED_COMMANDS) return;
@@ -63,7 +63,7 @@ async function runCommandAsync(args) {
63
63
  };
64
64
  }
65
65
  const commandId = uuidv42().substring(0, 8);
66
- const platform = os3.platform();
66
+ const platform = os5.platform();
67
67
  let shellCmd;
68
68
  let shellArgs;
69
69
  if (platform === "win32") {
@@ -83,7 +83,7 @@ async function runCommandAsync(args) {
83
83
  startTime: Date.now(),
84
84
  process: null
85
85
  };
86
- const child = spawn(shellCmd, shellArgs, {
86
+ const child = spawn2(shellCmd, shellArgs, {
87
87
  cwd,
88
88
  env: process.env,
89
89
  windowsHide: true
@@ -331,13 +331,13 @@ var init_async_command = __esm({
331
331
  import React12 from "react";
332
332
  import { render } from "ink";
333
333
  import { EventEmitter as EventEmitter3 } from "events";
334
- import fs16 from "fs";
335
- import path20 from "path";
336
- import { fileURLToPath as fileURLToPath4 } from "url";
334
+ import fs17 from "fs";
335
+ import path21 from "path";
336
+ import { fileURLToPath as fileURLToPath5 } from "url";
337
337
  import { v4 as uuidv46 } from "uuid";
338
338
 
339
339
  // src/app/ui/App.tsx
340
- import { useState as useState6, useEffect as useEffect7, useRef as useRef5, useCallback as useCallback2, memo as memo12 } from "react";
340
+ import { useState as useState6, useEffect as useEffect7, useRef as useRef5, useCallback as useCallback3, memo as memo12 } from "react";
341
341
  import { Box as Box20, Text as Text19, Static } from "ink";
342
342
 
343
343
  // src/app/ui/layout.tsx
@@ -590,6 +590,13 @@ function inputReducer(state, action, viewWidth) {
590
590
  }
591
591
  return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
592
592
  }
593
+ case "INSERT_AT_CURSOR": {
594
+ const cleanInput = action.payload.replace(/(\r\n|\r)/gm, "\n");
595
+ const newText = state.text.slice(0, state.cursorPosition) + cleanInput + state.text.slice(state.cursorPosition);
596
+ const newCursorPosition = state.cursorPosition + cleanInput.length;
597
+ const newViewStart = adjustView(newCursorPosition, state.viewStart);
598
+ return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
599
+ }
593
600
  case "NEWLINE": {
594
601
  const newText = state.text.slice(0, state.cursorPosition) + "\n" + state.text.slice(state.cursorPosition);
595
602
  const newCursorPosition = state.cursorPosition + 1;
@@ -675,11 +682,28 @@ function inputReducer(state, action, viewWidth) {
675
682
  return state;
676
683
  }
677
684
  }
685
+ function isClipboardPasteChord(input, key) {
686
+ if (key.ctrl && key.shift && input === "i") {
687
+ return true;
688
+ }
689
+ if (key.meta && (input === "v" || input === "V")) {
690
+ return true;
691
+ }
692
+ if (key.ctrl && (input === "v" || input === "V")) {
693
+ return true;
694
+ }
695
+ if (key.ctrl && input.length >= 1 && input.charCodeAt(0) === 22) {
696
+ return true;
697
+ }
698
+ return false;
699
+ }
678
700
  var useCustomInput = ({
679
701
  onSubmit,
680
702
  viewWidth,
681
703
  isReadOnly,
682
- onInterrupt
704
+ onInterrupt,
705
+ onChordPaste,
706
+ transformInputChunk
683
707
  }) => {
684
708
  const [state, dispatch] = useReducer(
685
709
  (s, a) => inputReducer(s, a, viewWidth),
@@ -687,14 +711,31 @@ var useCustomInput = ({
687
711
  );
688
712
  const inputBuffer = useRef("");
689
713
  const flushScheduled = useRef(false);
714
+ const applyChunkTransform = useCallback(
715
+ (buffered) => transformInputChunk ? transformInputChunk(buffered) : buffered,
716
+ [transformInputChunk]
717
+ );
690
718
  const flushInputBuffer = useCallback(() => {
691
719
  if (inputBuffer.current.length > 0) {
692
720
  const buffered = inputBuffer.current;
693
721
  inputBuffer.current = "";
694
- dispatch({ type: "INPUT", payload: buffered });
722
+ dispatch({ type: "INPUT", payload: applyChunkTransform(buffered) });
695
723
  }
696
724
  flushScheduled.current = false;
697
- }, []);
725
+ }, [applyChunkTransform]);
726
+ const insertAtCursor = useCallback(
727
+ (s) => {
728
+ if (inputBuffer.current.length > 0) {
729
+ const buffered = inputBuffer.current;
730
+ inputBuffer.current = "";
731
+ flushScheduled.current = false;
732
+ dispatch({ type: "INPUT", payload: applyChunkTransform(buffered) });
733
+ }
734
+ const clean = s.replace(/(\r\n|\r)/gm, "\n");
735
+ dispatch({ type: "INSERT_AT_CURSOR", payload: clean });
736
+ },
737
+ [applyChunkTransform]
738
+ );
698
739
  useEffect(() => {
699
740
  return () => {
700
741
  if (flushScheduled.current) {
@@ -755,6 +796,13 @@ var useCustomInput = ({
755
796
  if (key.rightArrow) return dispatch({ type: "MOVE_CURSOR", direction: "right" });
756
797
  if (key.upArrow) return dispatch({ type: "MOVE_CURSOR", direction: "up" });
757
798
  if (key.downArrow) return dispatch({ type: "MOVE_CURSOR", direction: "down" });
799
+ if (isClipboardPasteChord(input, key) && onChordPaste) {
800
+ if (inputBuffer.current.length > 0) {
801
+ flushInputBuffer();
802
+ }
803
+ void Promise.resolve(onChordPaste(insertAtCursor));
804
+ return;
805
+ }
758
806
  if (key.ctrl || key.meta || key.tab) return;
759
807
  inputBuffer.current += input;
760
808
  if (!flushScheduled.current) {
@@ -789,6 +837,13 @@ var useCustomInput = ({
789
837
  if (key.ctrl && input === "e") {
790
838
  return dispatch({ type: "MOVE_LINE_END" });
791
839
  }
840
+ if (isClipboardPasteChord(input, key) && onChordPaste) {
841
+ if (inputBuffer.current.length > 0) {
842
+ flushInputBuffer();
843
+ }
844
+ void Promise.resolve(onChordPaste(insertAtCursor));
845
+ return;
846
+ }
792
847
  if (key.ctrl || key.meta || key.tab) return;
793
848
  inputBuffer.current += input;
794
849
  if (!flushScheduled.current) {
@@ -817,16 +872,35 @@ var useCustomInput = ({
817
872
  flushInputBuffer();
818
873
  }
819
874
  dispatch({ type: "SET_CURSOR", payload: pos });
820
- }, [flushInputBuffer])
875
+ }, [flushInputBuffer]),
876
+ insertAtCursor
821
877
  };
822
878
  };
823
879
 
824
880
  // src/app/ui/components/InputPrompt.tsx
825
- import { useEffect as useEffect3, useMemo, useState as useState2, memo as memo2 } from "react";
881
+ import {
882
+ useEffect as useEffect3,
883
+ useMemo,
884
+ useState as useState2,
885
+ useRef as useRef3,
886
+ memo as memo2,
887
+ useLayoutEffect,
888
+ useCallback as useCallback2
889
+ } from "react";
826
890
  import { EventEmitter as EventEmitter2 } from "events";
827
891
 
828
892
  // src/app/ui/utils/slashRegistry.ts
829
893
  var getSlashCommands = () => [
894
+ {
895
+ name: "Ctrl+V / Cmd+V",
896
+ description: "paste from clipboard: image \u2192 file path under ~/.cache/bluma/clipboard; else text (all OS via clipboardy)",
897
+ category: "input"
898
+ },
899
+ {
900
+ name: "Ctrl+Shift+I",
901
+ description: "same as Ctrl+V / Cmd+V (paste image or text)",
902
+ category: "input"
903
+ },
830
904
  {
831
905
  name: "/help",
832
906
  description: "list all slash commands (grouped)",
@@ -868,17 +942,36 @@ var getSlashCommands = () => [
868
942
  category: "agent"
869
943
  }
870
944
  ];
945
+ var SLASH_ROUTE_KEYWORDS = new Set(
946
+ getSlashCommands().filter((c) => c.name.startsWith("/")).map((c) => c.name.slice(1).toLowerCase())
947
+ );
948
+ function isSlashRoutingLine(text) {
949
+ const t = text.trimStart();
950
+ if (!t.startsWith("/")) {
951
+ return false;
952
+ }
953
+ const rest = t.slice(1).trim();
954
+ if (!rest) {
955
+ return true;
956
+ }
957
+ const first = rest.split(/\s+/)[0];
958
+ if (first.includes("/") || first.includes("\\")) {
959
+ return false;
960
+ }
961
+ return SLASH_ROUTE_KEYWORDS.has(first.toLowerCase());
962
+ }
871
963
  var CATEGORY_LABEL = {
872
964
  session: "Session & UI",
873
965
  inspect: "Inspect",
874
966
  agent: "Agent",
875
- help: "Help"
967
+ help: "Help",
968
+ input: "Input"
876
969
  };
877
970
  function formatSlashHelpLines() {
878
971
  const cmds = getSlashCommands();
879
972
  const byCat = (cat) => cmds.filter((c) => c.category === cat);
880
973
  const lines = [];
881
- for (const cat of ["help", "session", "agent", "inspect"]) {
974
+ for (const cat of ["help", "session", "input", "agent", "inspect"]) {
882
975
  const group = byCat(cat);
883
976
  if (group.length === 0) continue;
884
977
  lines.push(`${CATEGORY_LABEL[cat]}:`);
@@ -1083,6 +1176,390 @@ var TOOL_PREVIEW_MAX_LINES = 8;
1083
1176
  var EXPAND_OVERLAY_MAX_LINES = 60;
1084
1177
  var TERMINAL_RULE_CHAR = "\u2500";
1085
1178
 
1179
+ // src/app/ui/utils/clipboardImage.ts
1180
+ import fs2 from "fs";
1181
+ import os2 from "os";
1182
+ import path2 from "path";
1183
+ import { spawn, execFile as execFileCb } from "child_process";
1184
+ import { promisify } from "util";
1185
+ var execFile = promisify(execFileCb);
1186
+ var CLIPBOARD_MAX_BYTES = 4 * 1024 * 1024;
1187
+ function extFromMagic(buf) {
1188
+ if (buf.length < 12) return null;
1189
+ if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) {
1190
+ return ".png";
1191
+ }
1192
+ if (buf[0] === 255 && buf[1] === 216 && buf[2] === 255) {
1193
+ return ".jpg";
1194
+ }
1195
+ if (buf.slice(0, 6).toString("ascii") === "GIF87a" || buf.slice(0, 6).toString("ascii") === "GIF89a") {
1196
+ return ".gif";
1197
+ }
1198
+ if (buf.slice(0, 4).toString("ascii") === "RIFF" && buf.slice(8, 12).toString("ascii") === "WEBP") {
1199
+ return ".webp";
1200
+ }
1201
+ return null;
1202
+ }
1203
+ function runCapture(cmd, args) {
1204
+ return new Promise((resolve2) => {
1205
+ const chunks = [];
1206
+ const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
1207
+ child.stdout.on("data", (c) => chunks.push(c));
1208
+ child.stderr.on("data", () => {
1209
+ });
1210
+ child.on("error", () => resolve2(Buffer.alloc(0)));
1211
+ child.on("close", () => {
1212
+ resolve2(Buffer.concat(chunks));
1213
+ });
1214
+ });
1215
+ }
1216
+ async function tryWlPaste(mime) {
1217
+ const args = mime ? ["--type", mime] : [];
1218
+ return runCapture("wl-paste", args);
1219
+ }
1220
+ async function tryXclip(mime) {
1221
+ return runCapture("xclip", ["-selection", "clipboard", "-t", mime, "-o"]);
1222
+ }
1223
+ async function tryPngpaste() {
1224
+ return runCapture("pngpaste", ["-"]);
1225
+ }
1226
+ function writeBufferIfImage(baseDir, buf) {
1227
+ if (buf.length < 80 || buf.length > CLIPBOARD_MAX_BYTES) {
1228
+ return null;
1229
+ }
1230
+ const ext = extFromMagic(buf);
1231
+ if (!ext) {
1232
+ return null;
1233
+ }
1234
+ const out = path2.join(
1235
+ baseDir,
1236
+ `clip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext}`
1237
+ );
1238
+ fs2.writeFileSync(out, buf);
1239
+ return out;
1240
+ }
1241
+ function unlinkQuiet(p) {
1242
+ try {
1243
+ if (fs2.existsSync(p)) {
1244
+ fs2.unlinkSync(p);
1245
+ }
1246
+ } catch {
1247
+ }
1248
+ }
1249
+ async function tryDarwinClipboardy(baseDir) {
1250
+ let tmpPaths = [];
1251
+ try {
1252
+ const { default: cb } = await import("clipboardy");
1253
+ if (typeof cb.hasImages !== "function" || !await cb.hasImages()) {
1254
+ return null;
1255
+ }
1256
+ tmpPaths = await cb.readImages();
1257
+ if (!tmpPaths?.length) {
1258
+ return null;
1259
+ }
1260
+ for (const src of tmpPaths) {
1261
+ if (!src || !fs2.existsSync(src)) {
1262
+ continue;
1263
+ }
1264
+ let st;
1265
+ try {
1266
+ st = fs2.statSync(src);
1267
+ } catch {
1268
+ continue;
1269
+ }
1270
+ if (st.size < 80 || st.size > CLIPBOARD_MAX_BYTES) {
1271
+ continue;
1272
+ }
1273
+ const ext = path2.extname(src).toLowerCase();
1274
+ const safeExt = ext && /^\.(png|jpe?g|gif|webp)$/i.test(ext) ? ext : ".png";
1275
+ const out = path2.join(
1276
+ baseDir,
1277
+ `clip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${safeExt}`
1278
+ );
1279
+ fs2.copyFileSync(src, out);
1280
+ for (const p of tmpPaths) {
1281
+ unlinkQuiet(p);
1282
+ }
1283
+ return out;
1284
+ }
1285
+ } catch {
1286
+ }
1287
+ for (const p of tmpPaths) {
1288
+ unlinkQuiet(p);
1289
+ }
1290
+ return null;
1291
+ }
1292
+ async function tryWindowsPowerShell(outFile) {
1293
+ const ps = process.env.SystemRoot != null ? path2.join(
1294
+ process.env.SystemRoot,
1295
+ "System32",
1296
+ "WindowsPowerShell",
1297
+ "v1.0",
1298
+ "powershell.exe"
1299
+ ) : "powershell.exe";
1300
+ if (!fs2.existsSync(ps)) {
1301
+ return false;
1302
+ }
1303
+ 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";
1304
+ try {
1305
+ await execFile(
1306
+ ps,
1307
+ ["-NoProfile", "-Sta", "-WindowStyle", "Hidden", "-Command", script],
1308
+ {
1309
+ env: { ...process.env, BLUMA_CLIP_OUT: outFile },
1310
+ windowsHide: true,
1311
+ timeout: 25e3
1312
+ }
1313
+ );
1314
+ } catch (e) {
1315
+ const status = e && typeof e === "object" && "status" in e ? e.status : null;
1316
+ if (status === 2) {
1317
+ return false;
1318
+ }
1319
+ return false;
1320
+ }
1321
+ try {
1322
+ const st = fs2.statSync(outFile);
1323
+ return st.size >= 80 && st.size <= CLIPBOARD_MAX_BYTES;
1324
+ } catch {
1325
+ return false;
1326
+ }
1327
+ }
1328
+ async function tryUnixLikeClipboard(baseDir) {
1329
+ const attempts = [];
1330
+ if (process.env.WAYLAND_DISPLAY) {
1331
+ attempts.push(() => tryWlPaste("image/png"));
1332
+ attempts.push(() => tryWlPaste("image/jpeg"));
1333
+ attempts.push(() => tryWlPaste());
1334
+ }
1335
+ attempts.push(() => tryXclip("image/png"));
1336
+ attempts.push(() => tryXclip("image/jpeg"));
1337
+ attempts.push(() => tryXclip("image/webp"));
1338
+ for (const run of attempts) {
1339
+ try {
1340
+ const buf = await run();
1341
+ const saved = writeBufferIfImage(baseDir, buf);
1342
+ if (saved) {
1343
+ return saved;
1344
+ }
1345
+ } catch {
1346
+ }
1347
+ }
1348
+ try {
1349
+ const buf = await tryPngpaste();
1350
+ return writeBufferIfImage(baseDir, buf);
1351
+ } catch {
1352
+ return null;
1353
+ }
1354
+ }
1355
+ async function readClipboardImageToTempFile() {
1356
+ const baseDir = path2.join(os2.homedir(), ".cache", "bluma", "clipboard");
1357
+ fs2.mkdirSync(baseDir, { recursive: true });
1358
+ if (process.platform === "darwin") {
1359
+ const fromCb = await tryDarwinClipboardy(baseDir);
1360
+ if (fromCb) {
1361
+ return fromCb;
1362
+ }
1363
+ try {
1364
+ const buf = await tryPngpaste();
1365
+ return writeBufferIfImage(baseDir, buf);
1366
+ } catch {
1367
+ return null;
1368
+ }
1369
+ }
1370
+ if (process.platform === "win32") {
1371
+ const outFile = path2.join(
1372
+ baseDir,
1373
+ `clip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.png`
1374
+ );
1375
+ const ok = await tryWindowsPowerShell(outFile);
1376
+ return ok ? outFile : null;
1377
+ }
1378
+ return tryUnixLikeClipboard(baseDir);
1379
+ }
1380
+
1381
+ // src/app/ui/utils/inlineImageInputLabels.ts
1382
+ function resolveInlineImageLabels(raw, idToPath) {
1383
+ return raw.replace(/\[IMAGE #(\d+)\]/g, (full, numStr) => {
1384
+ const id = Number.parseInt(numStr, 10);
1385
+ if (Number.isNaN(id)) {
1386
+ return full;
1387
+ }
1388
+ const p = idToPath.get(id);
1389
+ if (!p) {
1390
+ return full;
1391
+ }
1392
+ return p.includes(" ") ? `"${p}"` : p;
1393
+ });
1394
+ }
1395
+
1396
+ // src/app/agent/utils/user_message_images.ts
1397
+ import fs3 from "fs";
1398
+ import os3 from "os";
1399
+ import path3 from "path";
1400
+ var IMAGE_EXT = /\.(png|jpe?g|gif|webp|bmp)$/i;
1401
+ var MAX_IMAGE_BYTES = 4 * 1024 * 1024;
1402
+ var MAX_IMAGES = 6;
1403
+ var MIME = {
1404
+ ".png": "image/png",
1405
+ ".jpg": "image/jpeg",
1406
+ ".jpeg": "image/jpeg",
1407
+ ".gif": "image/gif",
1408
+ ".webp": "image/webp",
1409
+ ".bmp": "image/bmp"
1410
+ };
1411
+ function expandUserPath(p) {
1412
+ const t = p.trim();
1413
+ if (t.startsWith("~")) {
1414
+ return path3.join(os3.homedir(), t.slice(1).replace(/^\//, ""));
1415
+ }
1416
+ return t;
1417
+ }
1418
+ function isPathAllowed(absResolved, cwd) {
1419
+ const resolved = path3.normalize(path3.resolve(absResolved));
1420
+ const cwdR = path3.normalize(path3.resolve(cwd));
1421
+ const homeR = path3.normalize(path3.resolve(os3.homedir()));
1422
+ const underCwd = resolved === cwdR || resolved.startsWith(cwdR + path3.sep);
1423
+ const underHome = resolved === homeR || resolved.startsWith(homeR + path3.sep);
1424
+ return underCwd || underHome;
1425
+ }
1426
+ function mimeFor(abs) {
1427
+ const ext = path3.extname(abs).toLowerCase();
1428
+ return MIME[ext] || "application/octet-stream";
1429
+ }
1430
+ var IMAGE_EXT_SRC = String.raw`(?:png|jpe?g|gif|webp|bmp)`;
1431
+ var UNIX_PATH_WITH_IMAGE_EXT = new RegExp(
1432
+ String.raw`(?:^|[\s"'(])(\/(?:[^\s/'"<>|]+\/)*[^\s/'"<>|]+\.${IMAGE_EXT_SRC})\b`,
1433
+ "gi"
1434
+ );
1435
+ var WIN_PATH_WITH_IMAGE_EXT = new RegExp(
1436
+ String.raw`(?:^|[\s"'(])(([A-Za-z]:(?:\\[^\\/:*?"<>|\r\n]+)+\.${IMAGE_EXT_SRC}))\b`,
1437
+ "gi"
1438
+ );
1439
+ function collectImagePathStrings(raw) {
1440
+ const found = [];
1441
+ const seen = /* @__PURE__ */ new Set();
1442
+ const push = (p) => {
1443
+ const t = p.trim();
1444
+ if (!t || seen.has(t)) return;
1445
+ seen.add(t);
1446
+ found.push(t);
1447
+ };
1448
+ const quoted = /(["'])((?:(?!\1).)*?\.(?:png|jpe?g|gif|webp|bmp))\1/gi;
1449
+ for (const m of raw.matchAll(quoted)) {
1450
+ push(m[2].trim());
1451
+ }
1452
+ const withoutQuoted = raw.replace(quoted, " ");
1453
+ const tokens = withoutQuoted.split(/\s+/);
1454
+ for (let tok of tokens) {
1455
+ tok = tok.replace(/^[([{]+/, "").replace(/[)\]},;:]+$/, "");
1456
+ if (!tok || !IMAGE_EXT.test(tok)) continue;
1457
+ push(tok);
1458
+ }
1459
+ for (const m of raw.matchAll(UNIX_PATH_WITH_IMAGE_EXT)) {
1460
+ if (m[1]) {
1461
+ push(m[1]);
1462
+ }
1463
+ }
1464
+ for (const m of raw.matchAll(WIN_PATH_WITH_IMAGE_EXT)) {
1465
+ push(m[1]);
1466
+ }
1467
+ return found;
1468
+ }
1469
+ function resolveImagePath(candidate, cwd) {
1470
+ const expanded = expandUserPath(candidate);
1471
+ const abs = path3.isAbsolute(expanded) ? path3.normalize(expanded) : path3.normalize(path3.resolve(cwd, expanded));
1472
+ if (!isPathAllowed(abs, cwd)) return null;
1473
+ try {
1474
+ if (!fs3.existsSync(abs) || !fs3.statSync(abs).isFile()) return null;
1475
+ } catch {
1476
+ return null;
1477
+ }
1478
+ if (!IMAGE_EXT.test(abs)) return null;
1479
+ return abs;
1480
+ }
1481
+ function tryPasteChunkAsSingleImagePath(chunk, cwd) {
1482
+ const normalized = chunk.replace(/\r\n/g, "\n");
1483
+ const lines = normalized.split("\n");
1484
+ const t = (lines[0] ?? "").trim();
1485
+ if (!t) {
1486
+ return null;
1487
+ }
1488
+ if (lines.length > 1 && lines.slice(1).some((l) => l.trim().length > 0)) {
1489
+ return null;
1490
+ }
1491
+ const paths = collectImagePathStrings(t);
1492
+ if (paths.length !== 1) {
1493
+ return null;
1494
+ }
1495
+ if (stripImagePathStrings(t, paths).trim().length > 0) {
1496
+ return null;
1497
+ }
1498
+ const abs = resolveImagePath(paths[0], cwd);
1499
+ if (!abs) {
1500
+ return null;
1501
+ }
1502
+ try {
1503
+ const st = fs3.statSync(abs);
1504
+ if (!st.isFile() || st.size > MAX_IMAGE_BYTES) {
1505
+ return null;
1506
+ }
1507
+ } catch {
1508
+ return null;
1509
+ }
1510
+ return abs;
1511
+ }
1512
+ function stripImagePathStrings(text, paths) {
1513
+ let out = text;
1514
+ for (const p of paths) {
1515
+ const q = p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1516
+ out = out.replace(new RegExp(`["']${q}["']`, "gi"), " ");
1517
+ out = out.replace(new RegExp(`(?<=^|\\s)${q}(?=\\s|$)`, "gi"), " ");
1518
+ }
1519
+ return out.replace(/\s{2,}/g, " ").trim();
1520
+ }
1521
+ function buildUserMessageContent(raw, cwd) {
1522
+ const trimmed = raw.trim();
1523
+ if (!trimmed) return trimmed;
1524
+ const candidates = collectImagePathStrings(trimmed);
1525
+ const resolvedAbs = [];
1526
+ const usedStrings = [];
1527
+ for (const c of candidates) {
1528
+ if (resolvedAbs.length >= MAX_IMAGES) break;
1529
+ const abs = resolveImagePath(c, cwd);
1530
+ if (!abs) continue;
1531
+ try {
1532
+ const st = fs3.statSync(abs);
1533
+ if (st.size > MAX_IMAGE_BYTES) continue;
1534
+ } catch {
1535
+ continue;
1536
+ }
1537
+ resolvedAbs.push(abs);
1538
+ usedStrings.push(c);
1539
+ }
1540
+ if (resolvedAbs.length === 0) {
1541
+ return trimmed;
1542
+ }
1543
+ let textPart = stripImagePathStrings(trimmed, usedStrings);
1544
+ if (!textPart) {
1545
+ textPart = "(User attached image(s) only \u2014 describe, compare, or answer using the image(s).)";
1546
+ }
1547
+ const parts = [{ type: "text", text: textPart }];
1548
+ for (const abs of resolvedAbs) {
1549
+ const buf = fs3.readFileSync(abs);
1550
+ const b64 = buf.toString("base64");
1551
+ const mime = mimeFor(abs);
1552
+ parts.push({
1553
+ type: "image_url",
1554
+ image_url: {
1555
+ url: `data:${mime};base64,${b64}`,
1556
+ detail: "auto"
1557
+ }
1558
+ });
1559
+ }
1560
+ return parts;
1561
+ }
1562
+
1086
1563
  // src/app/ui/components/InputPrompt.tsx
1087
1564
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1088
1565
  var uiEventBus = global.__bluma_ui_eventbus__ || new EventEmitter2();
@@ -1163,7 +1640,15 @@ var InputRuleLine = memo2(() => {
1163
1640
  return /* @__PURE__ */ jsx2(Text2, { color: "white", children: TERMINAL_RULE_CHAR.repeat(n) });
1164
1641
  });
1165
1642
  InputRuleLine.displayName = "InputRuleLine";
1166
- var Footer = memo2(({ isReadOnly }) => /* @__PURE__ */ jsx2(Box2, { justifyContent: "center", children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: isReadOnly ? "ctrl+c to exit | Enter to send message | Shift+Enter for new line | esc interrupt | Ctrl+O expand last clip" : "ctrl+c to exit | Enter to submit | Shift+Enter for new line | ? /help \xB7 /img \xB7 esc \xB7 Ctrl+O expand" }) }));
1643
+ var Footer = memo2(
1644
+ ({
1645
+ isReadOnly,
1646
+ clipHint
1647
+ }) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", alignItems: "center", children: [
1648
+ clipHint ? /* @__PURE__ */ jsx2(Box2, { marginBottom: 0, children: /* @__PURE__ */ jsx2(Text2, { color: "yellow", dimColor: true, children: clipHint }) }) : null,
1649
+ /* @__PURE__ */ jsx2(Box2, { justifyContent: "center", children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: isReadOnly ? "ctrl+c to exit | Enter to send message | Shift+Enter for new line | esc interrupt | Ctrl+O expand last clip" : "ctrl+c to exit | Enter submit | Shift+Enter newline | Ctrl+V / Cmd+V paste | /help \xB7 /img \xB7 esc \xB7 Ctrl+O" }) })
1650
+ ] })
1651
+ );
1167
1652
  Footer.displayName = "Footer";
1168
1653
  var TextLinesRenderer = memo2(({
1169
1654
  lines,
@@ -1205,15 +1690,48 @@ var InputPrompt = memo2(({
1205
1690
  const [viewWidth] = useState2(() => stdout.columns - 6);
1206
1691
  const [slashOpen, setSlashOpen] = useState2(false);
1207
1692
  const [slashIndex, setSlashIndex] = useState2(0);
1693
+ const [clipHint, setClipHint] = useState2(null);
1694
+ const textRef = useRef3("");
1695
+ const cursorRef = useRef3(0);
1696
+ const inlineImageByIdRef = useRef3(/* @__PURE__ */ new Map());
1697
+ const nextInlineImageIdRef = useRef3(1);
1698
+ const clearInlineImageSlots = () => {
1699
+ inlineImageByIdRef.current.clear();
1700
+ nextInlineImageIdRef.current = 1;
1701
+ };
1702
+ const cwd = process.cwd();
1703
+ const transformInputChunk = useCallback2(
1704
+ (chunk) => {
1705
+ const abs = tryPasteChunkAsSingleImagePath(chunk, cwd);
1706
+ if (!abs) {
1707
+ return chunk;
1708
+ }
1709
+ const id = nextInlineImageIdRef.current++;
1710
+ inlineImageByIdRef.current.set(id, abs);
1711
+ return /\n$/.test(chunk) ? `[IMAGE #${id}]
1712
+ ` : `[IMAGE #${id}] `;
1713
+ },
1714
+ [cwd]
1715
+ );
1208
1716
  const permissiveOnSubmit = (value) => {
1209
1717
  const trimmed = (value || "").trim();
1718
+ const map = inlineImageByIdRef.current;
1719
+ const resolved = resolveInlineImageLabels(value, map);
1210
1720
  if (isReadOnly) {
1211
1721
  if (trimmed.length > 0) {
1212
- uiEventBus.emit("user_overlay", { kind: "message", payload: trimmed, ts: Date.now() });
1722
+ uiEventBus.emit("user_overlay", {
1723
+ kind: "message",
1724
+ payload: resolved.trim(),
1725
+ ts: Date.now()
1726
+ });
1727
+ clearInlineImageSlots();
1213
1728
  }
1214
1729
  return;
1215
1730
  }
1216
- onSubmit(value);
1731
+ if (trimmed.length > 0) {
1732
+ onSubmit(resolved);
1733
+ clearInlineImageSlots();
1734
+ }
1217
1735
  };
1218
1736
  const { text, cursorPosition, setText } = useCustomInput({
1219
1737
  onSubmit: (value) => {
@@ -1223,8 +1741,48 @@ var InputPrompt = memo2(({
1223
1741
  },
1224
1742
  viewWidth,
1225
1743
  isReadOnly,
1226
- onInterrupt
1744
+ onInterrupt,
1745
+ transformInputChunk: !disableWhileProcessing ? transformInputChunk : void 0,
1746
+ onChordPaste: !disableWhileProcessing ? (insertAtCursor) => {
1747
+ void (async () => {
1748
+ const padBefore = () => {
1749
+ const t = textRef.current;
1750
+ const c = cursorRef.current;
1751
+ return t.length > 0 && c > 0 && t[c - 1] !== "\n" && !/\s/.test(t[c - 1]) ? " " : "";
1752
+ };
1753
+ const filePath = await readClipboardImageToTempFile();
1754
+ if (filePath) {
1755
+ const id = nextInlineImageIdRef.current++;
1756
+ inlineImageByIdRef.current.set(id, filePath);
1757
+ insertAtCursor(`${padBefore()}[IMAGE #${id}] `);
1758
+ return;
1759
+ }
1760
+ try {
1761
+ const { default: cb } = await import("clipboardy");
1762
+ const pasted = await cb.read();
1763
+ const t = pasted != null ? String(pasted).replace(/\r\n/g, "\n") : "";
1764
+ if (t.length > 0) {
1765
+ const abs = tryPasteChunkAsSingleImagePath(t, cwd);
1766
+ if (abs) {
1767
+ const id = nextInlineImageIdRef.current++;
1768
+ inlineImageByIdRef.current.set(id, abs);
1769
+ insertAtCursor(`${padBefore()}[IMAGE #${id}] `);
1770
+ return;
1771
+ }
1772
+ insertAtCursor(`${padBefore()}${t}`);
1773
+ return;
1774
+ }
1775
+ } catch {
1776
+ }
1777
+ setClipHint("Clipboard: empty or unreadable (GUI session; Linux: wl-clipboard/xclip).");
1778
+ setTimeout(() => setClipHint(null), 4e3);
1779
+ })();
1780
+ } : void 0
1227
1781
  });
1782
+ useLayoutEffect(() => {
1783
+ textRef.current = text;
1784
+ cursorRef.current = cursorPosition;
1785
+ }, [text, cursorPosition]);
1228
1786
  const linesData = useMemo(() => {
1229
1787
  const lines = text.split("\n");
1230
1788
  let remainingChars = cursorPosition;
@@ -1291,7 +1849,6 @@ var InputPrompt = memo2(({
1291
1849
  delete globalThis.__BLUMA_FORCE_CURSOR_END__;
1292
1850
  }
1293
1851
  }, [text, setText]);
1294
- const cwd = process.cwd();
1295
1852
  const pathAutocomplete = useAtCompletion({ cwd, text, cursorPosition, setText });
1296
1853
  useInput2((input, key) => {
1297
1854
  if (key.backspace || key.delete || key.ctrl || key.meta) return;
@@ -1351,7 +1908,7 @@ var InputPrompt = memo2(({
1351
1908
  slashOpen && slashSuggestions.length > 0 && /* @__PURE__ */ jsx2(SlashSuggestions, { suggestions: slashSuggestions, selectedIndex: slashIndex })
1352
1909
  ] }) }),
1353
1910
  /* @__PURE__ */ jsx2(InputRuleLine, {}),
1354
- /* @__PURE__ */ jsx2(Footer, { isReadOnly })
1911
+ /* @__PURE__ */ jsx2(Footer, { isReadOnly, clipHint })
1355
1912
  ] });
1356
1913
  });
1357
1914
  InputPrompt.displayName = "InputPrompt";
@@ -1409,7 +1966,7 @@ var InteractiveMenu = memo3(InteractiveMenuComponent);
1409
1966
 
1410
1967
  // src/app/ui/components/promptRenderers.tsx
1411
1968
  import { Box as Box5, Text as Text5 } from "ink";
1412
- import path2 from "path";
1969
+ import path4 from "path";
1413
1970
 
1414
1971
  // src/app/ui/components/SimpleDiff.tsx
1415
1972
  import { Box as Box4, Text as Text4 } from "ink";
@@ -1455,7 +2012,7 @@ var SimpleDiff = ({ text, maxHeight }) => {
1455
2012
  // src/app/ui/components/promptRenderers.tsx
1456
2013
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1457
2014
  var getBasePath = (filePath) => {
1458
- return path2.basename(filePath);
2015
+ return path4.basename(filePath);
1459
2016
  };
1460
2017
  var renderShellCommand = ({ toolCall }) => {
1461
2018
  let command = "";
@@ -1669,6 +2226,25 @@ var promptRenderers = {
1669
2226
 
1670
2227
  // src/app/ui/theme/m3Layout.tsx
1671
2228
  import { Box as Box6, Text as Text6 } from "ink";
2229
+
2230
+ // src/app/ui/utils/formatTurnDurationMs.ts
2231
+ function formatTurnDurationMs(ms) {
2232
+ if (ms < 0) {
2233
+ return "0s";
2234
+ }
2235
+ const secTotal = Math.round(ms / 1e3);
2236
+ if (secTotal < 60) {
2237
+ if (ms < 1e4) {
2238
+ return `${(ms / 1e3).toFixed(1)}s`;
2239
+ }
2240
+ return `${secTotal}s`;
2241
+ }
2242
+ const min = Math.floor(secTotal / 60);
2243
+ const sec = secTotal % 60;
2244
+ return `${min}min \u25CF ${sec}s`;
2245
+ }
2246
+
2247
+ // src/app/ui/theme/m3Layout.tsx
1672
2248
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1673
2249
  function ChatBlock({
1674
2250
  children,
@@ -1676,6 +2252,24 @@ function ChatBlock({
1676
2252
  }) {
1677
2253
  return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", marginBottom, children });
1678
2254
  }
2255
+ function ChatUserImageBlock({
2256
+ imageCount,
2257
+ caption,
2258
+ captionDim = false
2259
+ }) {
2260
+ if (imageCount < 1) {
2261
+ return null;
2262
+ }
2263
+ const cap = caption?.trim() ?? "";
2264
+ const capLines = cap.length > 0 ? cap.split("\n") : [];
2265
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginBottom: 0, children: [
2266
+ Array.from({ length: imageCount }, (_, i) => /* @__PURE__ */ jsx6(Text6, { bold: true, color: BLUMA_TERMINAL.brandMagenta, children: `[IMAGE #${i + 1}]` }, `img-${i}`)),
2267
+ capLines.map((line, i) => /* @__PURE__ */ jsxs6(Box6, { flexDirection: "row", flexWrap: "wrap", children: [
2268
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: i === 0 ? "\u2514\u2500 " : " " }),
2269
+ captionDim ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, wrap: "wrap", children: line }) : /* @__PURE__ */ jsx6(Text6, { wrap: "wrap", children: line })
2270
+ ] }, `cap-${i}`))
2271
+ ] });
2272
+ }
1679
2273
  function ChatUserMessage({ children }) {
1680
2274
  return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "row", marginBottom: 1, flexWrap: "wrap", alignItems: "flex-start", children: [
1681
2275
  /* @__PURE__ */ jsxs6(Text6, { color: "white", bold: true, children: [
@@ -1697,13 +2291,10 @@ function ChatStatusRow({ children }) {
1697
2291
  children
1698
2292
  ] });
1699
2293
  }
1700
- function ChatTurnDuration({ secondsFormatted }) {
2294
+ function ChatTurnDuration({ durationMs }) {
1701
2295
  return /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1702
2296
  /* @__PURE__ */ jsx6(Text6, { color: BLUMA_TERMINAL.brandBlue, children: "\xB7 " }),
1703
- /* @__PURE__ */ jsxs6(Text6, { color: BLUMA_TERMINAL.brandMagenta, children: [
1704
- secondsFormatted,
1705
- "s"
1706
- ] })
2297
+ /* @__PURE__ */ jsx6(Text6, { color: BLUMA_TERMINAL.brandMagenta, children: formatTurnDurationMs(durationMs) })
1707
2298
  ] }) });
1708
2299
  }
1709
2300
  var M3StatusStrip = ChatStatusRow;
@@ -1726,25 +2317,25 @@ var ConfirmationPrompt = memo4(ConfirmationPromptComponent);
1726
2317
 
1727
2318
  // src/app/agent/agent.ts
1728
2319
  import * as dotenv from "dotenv";
1729
- import path18 from "path";
1730
- import os13 from "os";
2320
+ import path19 from "path";
2321
+ import os14 from "os";
1731
2322
 
1732
2323
  // src/app/agent/tool_invoker.ts
1733
- import { promises as fs9 } from "fs";
1734
- import path11 from "path";
2324
+ import { promises as fs11 } from "fs";
2325
+ import path13 from "path";
1735
2326
  import { fileURLToPath } from "url";
1736
2327
 
1737
2328
  // src/app/agent/tools/natives/edit.ts
1738
- import path3 from "path";
1739
- import os2 from "os";
1740
- import { promises as fs2 } from "fs";
2329
+ import path5 from "path";
2330
+ import os4 from "os";
2331
+ import { promises as fs4 } from "fs";
1741
2332
  import { diffLines } from "diff";
1742
2333
  var MAX_DIFF_SIZE = 5e4;
1743
2334
  var MAX_FILE_SIZE = 10 * 1024 * 1024;
1744
2335
  function normalizePath(filePath) {
1745
2336
  try {
1746
2337
  filePath = filePath.trim();
1747
- if (os2.platform() === "win32") {
2338
+ if (os4.platform() === "win32") {
1748
2339
  const winDriveRegex = /^\/([a-zA-Z])[:/]/;
1749
2340
  const match = filePath.match(winDriveRegex);
1750
2341
  if (match) {
@@ -1754,7 +2345,7 @@ function normalizePath(filePath) {
1754
2345
  }
1755
2346
  filePath = filePath.replace(/\//g, "\\");
1756
2347
  }
1757
- return path3.normalize(path3.resolve(filePath));
2348
+ return path5.normalize(path5.resolve(filePath));
1758
2349
  } catch (e) {
1759
2350
  throw new Error(`Failed to normalize path "${filePath}": ${e.message}`);
1760
2351
  }
@@ -1878,7 +2469,7 @@ async function calculateEdit(filePath, oldString, newString, expectedReplacement
1878
2469
  let normalizedOldString = oldString.replace(/\r\n/g, "\n");
1879
2470
  let occurrences = 0;
1880
2471
  try {
1881
- const stats = await fs2.stat(normalizedFilePath);
2472
+ const stats = await fs4.stat(normalizedFilePath);
1882
2473
  if (stats.size > MAX_FILE_SIZE) {
1883
2474
  error = {
1884
2475
  display: `File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Maximum allowed: ${MAX_FILE_SIZE / 1024 / 1024}MB`,
@@ -1886,7 +2477,7 @@ async function calculateEdit(filePath, oldString, newString, expectedReplacement
1886
2477
  };
1887
2478
  return { currentContent, newContent: "", occurrences: 0, error, isNewFile };
1888
2479
  }
1889
- currentContent = await fs2.readFile(normalizedFilePath, "utf-8");
2480
+ currentContent = await fs4.readFile(normalizedFilePath, "utf-8");
1890
2481
  currentContent = currentContent.replace(/\r\n/g, "\n");
1891
2482
  } catch (e) {
1892
2483
  if (e.code !== "ENOENT") {
@@ -2015,7 +2606,7 @@ async function editTool(args) {
2015
2606
  };
2016
2607
  }
2017
2608
  const cwd = process.cwd();
2018
- if (!normalizedFilePath.startsWith(cwd) && !path3.isAbsolute(file_path)) {
2609
+ if (!normalizedFilePath.startsWith(cwd) && !path5.isAbsolute(file_path)) {
2019
2610
  return {
2020
2611
  success: false,
2021
2612
  error: `Invalid parameters: file_path must be within the current working directory or be an absolute path.`,
@@ -2036,11 +2627,11 @@ async function editTool(args) {
2036
2627
  file_path: normalizedFilePath
2037
2628
  };
2038
2629
  }
2039
- const dirPath = path3.dirname(normalizedFilePath);
2040
- await fs2.mkdir(dirPath, { recursive: true });
2041
- await fs2.writeFile(normalizedFilePath, editData.newContent, "utf-8");
2042
- const relativePath = path3.relative(process.cwd(), normalizedFilePath);
2043
- const filename = path3.basename(normalizedFilePath);
2630
+ const dirPath = path5.dirname(normalizedFilePath);
2631
+ await fs4.mkdir(dirPath, { recursive: true });
2632
+ await fs4.writeFile(normalizedFilePath, editData.newContent, "utf-8");
2633
+ const relativePath = path5.relative(process.cwd(), normalizedFilePath);
2634
+ const filename = path5.basename(normalizedFilePath);
2044
2635
  if (editData.isNewFile) {
2045
2636
  return {
2046
2637
  success: true,
@@ -2094,8 +2685,8 @@ function message(args) {
2094
2685
  }
2095
2686
 
2096
2687
  // src/app/agent/tools/natives/ls.ts
2097
- import { promises as fs3 } from "fs";
2098
- import path4 from "path";
2688
+ import { promises as fs5 } from "fs";
2689
+ import path6 from "path";
2099
2690
  var DEFAULT_IGNORE = /* @__PURE__ */ new Set([
2100
2691
  ".git",
2101
2692
  ".gitignore",
@@ -2123,8 +2714,8 @@ async function ls(args) {
2123
2714
  max_depth
2124
2715
  } = args;
2125
2716
  try {
2126
- const basePath = path4.resolve(directory_path);
2127
- if (!(await fs3.stat(basePath)).isDirectory()) {
2717
+ const basePath = path6.resolve(directory_path);
2718
+ if (!(await fs5.stat(basePath)).isDirectory()) {
2128
2719
  throw new Error(`Directory '${directory_path}' not found.`);
2129
2720
  }
2130
2721
  const allIgnorePatterns = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore_patterns]);
@@ -2133,11 +2724,11 @@ async function ls(args) {
2133
2724
  const allDirs = [];
2134
2725
  const walk = async (currentDir, currentDepth) => {
2135
2726
  if (max_depth !== void 0 && currentDepth > max_depth) return;
2136
- const entries = await fs3.readdir(currentDir, { withFileTypes: true });
2727
+ const entries = await fs5.readdir(currentDir, { withFileTypes: true });
2137
2728
  for (const entry of entries) {
2138
2729
  const entryName = entry.name;
2139
- const fullPath = path4.join(currentDir, entryName);
2140
- const posixPath = fullPath.split(path4.sep).join("/");
2730
+ const fullPath = path6.join(currentDir, entryName);
2731
+ const posixPath = fullPath.split(path6.sep).join("/");
2141
2732
  const isHidden = entryName.startsWith(".");
2142
2733
  if (allIgnorePatterns.has(entryName) || isHidden && !show_hidden) {
2143
2734
  continue;
@@ -2148,7 +2739,7 @@ async function ls(args) {
2148
2739
  await walk(fullPath, currentDepth + 1);
2149
2740
  }
2150
2741
  } else if (entry.isFile()) {
2151
- if (!normalizedExtensions || normalizedExtensions.includes(path4.extname(entryName).toLowerCase())) {
2742
+ if (!normalizedExtensions || normalizedExtensions.includes(path6.extname(entryName).toLowerCase())) {
2152
2743
  allFiles.push(posixPath);
2153
2744
  }
2154
2745
  }
@@ -2159,7 +2750,7 @@ async function ls(args) {
2159
2750
  allDirs.sort();
2160
2751
  return {
2161
2752
  success: true,
2162
- path: basePath.split(path4.sep).join("/"),
2753
+ path: basePath.split(path6.sep).join("/"),
2163
2754
  recursive,
2164
2755
  total_files: allFiles.length,
2165
2756
  total_directories: allDirs.length,
@@ -2175,17 +2766,17 @@ async function ls(args) {
2175
2766
  }
2176
2767
 
2177
2768
  // src/app/agent/tools/natives/readLines.ts
2178
- import { promises as fs4 } from "fs";
2769
+ import { promises as fs6 } from "fs";
2179
2770
  async function readLines(args) {
2180
2771
  const { filepath, start_line, end_line } = args;
2181
2772
  try {
2182
- if (!(await fs4.stat(filepath)).isFile()) {
2773
+ if (!(await fs6.stat(filepath)).isFile()) {
2183
2774
  throw new Error(`File '${filepath}' not found or is not a file.`);
2184
2775
  }
2185
2776
  if (start_line < 1 || end_line < start_line) {
2186
2777
  throw new Error("Invalid line range. start_line must be >= 1 and end_line must be >= start_line.");
2187
2778
  }
2188
- const fileContent = await fs4.readFile(filepath, "utf-8");
2779
+ const fileContent = await fs6.readFile(filepath, "utf-8");
2189
2780
  const lines = fileContent.split("\n");
2190
2781
  const total_lines = lines.length;
2191
2782
  const startIndex = start_line - 1;
@@ -2213,12 +2804,12 @@ async function readLines(args) {
2213
2804
 
2214
2805
  // src/app/agent/tools/natives/count_lines.ts
2215
2806
  import { createReadStream } from "fs";
2216
- import { promises as fs5 } from "fs";
2807
+ import { promises as fs7 } from "fs";
2217
2808
  import readline from "readline";
2218
2809
  async function countLines(args) {
2219
2810
  const { filepath } = args;
2220
2811
  try {
2221
- if (!(await fs5.stat(filepath)).isFile()) {
2812
+ if (!(await fs7.stat(filepath)).isFile()) {
2222
2813
  throw new Error(`File '${filepath}' not found or is not a file.`);
2223
2814
  }
2224
2815
  const fileStream = createReadStream(filepath);
@@ -2237,18 +2828,18 @@ async function countLines(args) {
2237
2828
  }
2238
2829
 
2239
2830
  // src/app/agent/tools/natives/todo.ts
2240
- import * as fs6 from "fs";
2241
- import * as path5 from "path";
2831
+ import * as fs8 from "fs";
2832
+ import * as path7 from "path";
2242
2833
  var taskStore = [];
2243
2834
  var nextId = 1;
2244
2835
  function getTodoFilePath() {
2245
- return path5.join(process.cwd(), ".bluma", "todo.json");
2836
+ return path7.join(process.cwd(), ".bluma", "todo.json");
2246
2837
  }
2247
2838
  function loadTasksFromFile() {
2248
2839
  try {
2249
2840
  const filePath = getTodoFilePath();
2250
- if (fs6.existsSync(filePath)) {
2251
- const data = fs6.readFileSync(filePath, "utf-8");
2841
+ if (fs8.existsSync(filePath)) {
2842
+ const data = fs8.readFileSync(filePath, "utf-8");
2252
2843
  const parsed = JSON.parse(data);
2253
2844
  if (Array.isArray(parsed.tasks)) {
2254
2845
  taskStore = parsed.tasks;
@@ -2261,11 +2852,11 @@ function loadTasksFromFile() {
2261
2852
  function saveTasksToFile() {
2262
2853
  try {
2263
2854
  const filePath = getTodoFilePath();
2264
- const dir = path5.dirname(filePath);
2265
- if (!fs6.existsSync(dir)) {
2266
- fs6.mkdirSync(dir, { recursive: true });
2855
+ const dir = path7.dirname(filePath);
2856
+ if (!fs8.existsSync(dir)) {
2857
+ fs8.mkdirSync(dir, { recursive: true });
2267
2858
  }
2268
- fs6.writeFileSync(filePath, JSON.stringify({
2859
+ fs8.writeFileSync(filePath, JSON.stringify({
2269
2860
  tasks: taskStore,
2270
2861
  nextId,
2271
2862
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -2440,7 +3031,7 @@ async function todo(args) {
2440
3031
  }
2441
3032
 
2442
3033
  // src/app/agent/tools/natives/find_by_name.ts
2443
- import path6 from "path";
3034
+ import path8 from "path";
2444
3035
  import { promises as fsPromises } from "fs";
2445
3036
  var DEFAULT_IGNORE_PATTERNS = [
2446
3037
  "node_modules",
@@ -2480,7 +3071,7 @@ function shouldIgnore(name, ignorePatterns, includeHidden) {
2480
3071
  }
2481
3072
  function matchesExtensions(filename, extensions) {
2482
3073
  if (!extensions || extensions.length === 0) return true;
2483
- const ext = path6.extname(filename).toLowerCase();
3074
+ const ext = path8.extname(filename).toLowerCase();
2484
3075
  return extensions.some((e) => {
2485
3076
  const normalizedExt = e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`;
2486
3077
  return ext === normalizedExt;
@@ -2502,8 +3093,8 @@ async function searchDirectory(dir, pattern, baseDir, options, results) {
2502
3093
  if (shouldIgnore(name, options.ignorePatterns, options.includeHidden)) {
2503
3094
  continue;
2504
3095
  }
2505
- const fullPath = path6.join(dir, name);
2506
- const relativePath = path6.relative(baseDir, fullPath);
3096
+ const fullPath = path8.join(dir, name);
3097
+ const relativePath = path8.relative(baseDir, fullPath);
2507
3098
  if (entry.isDirectory()) {
2508
3099
  if (pattern.test(name)) {
2509
3100
  results.push({
@@ -2559,7 +3150,7 @@ async function findByName(args) {
2559
3150
  error: "Pattern is required and must be a string"
2560
3151
  };
2561
3152
  }
2562
- const resolvedDir = path6.resolve(directory);
3153
+ const resolvedDir = path8.resolve(directory);
2563
3154
  try {
2564
3155
  const stats = await fsPromises.stat(resolvedDir);
2565
3156
  if (!stats.isDirectory()) {
@@ -2620,7 +3211,7 @@ async function findByName(args) {
2620
3211
  }
2621
3212
 
2622
3213
  // src/app/agent/tools/natives/grep_search.ts
2623
- import path7 from "path";
3214
+ import path9 from "path";
2624
3215
  import { promises as fsPromises2 } from "fs";
2625
3216
  var MAX_RESULTS3 = 100;
2626
3217
  var MAX_FILE_SIZE2 = 1024 * 1024;
@@ -2703,8 +3294,8 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
2703
3294
  "Rakefile"
2704
3295
  ]);
2705
3296
  function isTextFile(filepath) {
2706
- const ext = path7.extname(filepath).toLowerCase();
2707
- const basename = path7.basename(filepath);
3297
+ const ext = path9.extname(filepath).toLowerCase();
3298
+ const basename = path9.basename(filepath);
2708
3299
  if (TEXT_EXTENSIONS.has(ext)) return true;
2709
3300
  if (TEXT_EXTENSIONS.has(basename)) return true;
2710
3301
  if (basename.startsWith(".") && !ext) return true;
@@ -2749,7 +3340,7 @@ async function searchFile(filepath, baseDir, pattern, contextLines, matches, max
2749
3340
  if (stats.size > MAX_FILE_SIZE2) return 0;
2750
3341
  const content = await fsPromises2.readFile(filepath, "utf-8");
2751
3342
  const lines = content.split("\n");
2752
- const relativePath = path7.relative(baseDir, filepath);
3343
+ const relativePath = path9.relative(baseDir, filepath);
2753
3344
  for (let i = 0; i < lines.length && matches.length < maxResults; i++) {
2754
3345
  const line = lines[i];
2755
3346
  pattern.lastIndex = 0;
@@ -2789,7 +3380,7 @@ async function searchDirectory2(dir, baseDir, pattern, includePatterns, contextL
2789
3380
  if (matches.length >= maxResults) break;
2790
3381
  const name = entry.name;
2791
3382
  if (shouldIgnore2(name)) continue;
2792
- const fullPath = path7.join(dir, name);
3383
+ const fullPath = path9.join(dir, name);
2793
3384
  if (entry.isDirectory()) {
2794
3385
  await searchDirectory2(fullPath, baseDir, pattern, includePatterns, contextLines, matches, maxResults, stats);
2795
3386
  } else if (entry.isFile()) {
@@ -2838,7 +3429,7 @@ async function grepSearch(args) {
2838
3429
  error: "Search path is required"
2839
3430
  };
2840
3431
  }
2841
- const resolvedPath = path7.resolve(searchPath);
3432
+ const resolvedPath = path9.resolve(searchPath);
2842
3433
  let stats;
2843
3434
  try {
2844
3435
  stats = await fsPromises2.stat(resolvedPath);
@@ -2886,7 +3477,7 @@ async function grepSearch(args) {
2886
3477
  );
2887
3478
  } else if (stats.isFile()) {
2888
3479
  searchStats.filesSearched = 1;
2889
- const found = await searchFile(resolvedPath, path7.dirname(resolvedPath), pattern, context_lines, matches, max_results);
3480
+ const found = await searchFile(resolvedPath, path9.dirname(resolvedPath), pattern, context_lines, matches, max_results);
2890
3481
  if (found > 0) searchStats.filesWithMatches = 1;
2891
3482
  }
2892
3483
  return {
@@ -2915,7 +3506,7 @@ async function grepSearch(args) {
2915
3506
  }
2916
3507
 
2917
3508
  // src/app/agent/tools/natives/view_file_outline.ts
2918
- import path8 from "path";
3509
+ import path10 from "path";
2919
3510
  import { promises as fsPromises3 } from "fs";
2920
3511
  var LANGUAGE_MAP = {
2921
3512
  ".ts": "typescript",
@@ -3029,7 +3620,7 @@ var PATTERNS = {
3029
3620
  ]
3030
3621
  };
3031
3622
  function detectLanguage(filepath) {
3032
- const ext = path8.extname(filepath).toLowerCase();
3623
+ const ext = path10.extname(filepath).toLowerCase();
3033
3624
  return LANGUAGE_MAP[ext] || "unknown";
3034
3625
  }
3035
3626
  function determineItemType(line, language) {
@@ -3125,7 +3716,7 @@ async function viewFileOutline(args) {
3125
3716
  error: "file_path is required and must be a string"
3126
3717
  };
3127
3718
  }
3128
- const resolvedPath = path8.resolve(file_path);
3719
+ const resolvedPath = path10.resolve(file_path);
3129
3720
  let content;
3130
3721
  try {
3131
3722
  content = await fsPromises3.readFile(resolvedPath, "utf-8");
@@ -3169,23 +3760,23 @@ async function viewFileOutline(args) {
3169
3760
  init_async_command();
3170
3761
 
3171
3762
  // src/app/agent/tools/natives/task_boundary.ts
3172
- import path9 from "path";
3173
- import { promises as fs7 } from "fs";
3174
- import os4 from "os";
3763
+ import path11 from "path";
3764
+ import { promises as fs9 } from "fs";
3765
+ import os6 from "os";
3175
3766
  var currentTask = null;
3176
3767
  var artifactsDir = null;
3177
3768
  async function getArtifactsDir() {
3178
3769
  if (artifactsDir) return artifactsDir;
3179
- const homeDir = os4.homedir();
3180
- const baseDir = path9.join(homeDir, ".bluma", "artifacts");
3770
+ const homeDir = os6.homedir();
3771
+ const baseDir = path11.join(homeDir, ".bluma", "artifacts");
3181
3772
  const sessionId = Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
3182
- artifactsDir = path9.join(baseDir, sessionId);
3183
- await fs7.mkdir(artifactsDir, { recursive: true });
3773
+ artifactsDir = path11.join(baseDir, sessionId);
3774
+ await fs9.mkdir(artifactsDir, { recursive: true });
3184
3775
  return artifactsDir;
3185
3776
  }
3186
3777
  async function updateTaskFile(task) {
3187
3778
  const dir = await getArtifactsDir();
3188
- const taskFile = path9.join(dir, "task.md");
3779
+ const taskFile = path11.join(dir, "task.md");
3189
3780
  const content = `# ${task.taskName}
3190
3781
 
3191
3782
  **Mode:** ${task.mode}
@@ -3197,7 +3788,7 @@ async function updateTaskFile(task) {
3197
3788
  ## Summary
3198
3789
  ${task.summary}
3199
3790
  `;
3200
- await fs7.writeFile(taskFile, content, "utf-8");
3791
+ await fs9.writeFile(taskFile, content, "utf-8");
3201
3792
  }
3202
3793
  async function taskBoundary(args) {
3203
3794
  try {
@@ -3277,8 +3868,8 @@ async function createArtifact(args) {
3277
3868
  return { success: false, error: "content is required" };
3278
3869
  }
3279
3870
  const dir = await getArtifactsDir();
3280
- const filepath = path9.join(dir, filename);
3281
- await fs7.writeFile(filepath, content, "utf-8");
3871
+ const filepath = path11.join(dir, filename);
3872
+ await fs9.writeFile(filepath, content, "utf-8");
3282
3873
  return {
3283
3874
  success: true,
3284
3875
  path: filepath
@@ -3297,8 +3888,8 @@ async function readArtifact(args) {
3297
3888
  return { success: false, error: "filename is required" };
3298
3889
  }
3299
3890
  const dir = await getArtifactsDir();
3300
- const filepath = path9.join(dir, filename);
3301
- const content = await fs7.readFile(filepath, "utf-8");
3891
+ const filepath = path11.join(dir, filename);
3892
+ const content = await fs9.readFile(filepath, "utf-8");
3302
3893
  return {
3303
3894
  success: true,
3304
3895
  content
@@ -3667,9 +4258,9 @@ ${skill.content}`;
3667
4258
  }
3668
4259
 
3669
4260
  // src/app/agent/tools/natives/coding_memory.ts
3670
- import * as fs8 from "fs";
3671
- import * as path10 from "path";
3672
- import os5 from "os";
4261
+ import * as fs10 from "fs";
4262
+ import * as path12 from "path";
4263
+ import os7 from "os";
3673
4264
  var PROMPT_DEFAULT_MAX_TOTAL = 1e4;
3674
4265
  var PROMPT_DEFAULT_MAX_NOTES = 25;
3675
4266
  var PROMPT_DEFAULT_PREVIEW = 500;
@@ -3677,14 +4268,14 @@ function readCodingMemoryForPrompt(options) {
3677
4268
  const maxTotal = options?.maxTotalChars ?? PROMPT_DEFAULT_MAX_TOTAL;
3678
4269
  const maxNotes = options?.maxNotes ?? PROMPT_DEFAULT_MAX_NOTES;
3679
4270
  const preview = options?.previewCharsPerNote ?? PROMPT_DEFAULT_PREVIEW;
3680
- const globalPath = path10.join(os5.homedir(), ".bluma", "coding_memory.json");
3681
- const legacyPath = path10.join(process.cwd(), ".bluma", "coding_memory.json");
4271
+ const globalPath = path12.join(os7.homedir(), ".bluma", "coding_memory.json");
4272
+ const legacyPath = path12.join(process.cwd(), ".bluma", "coding_memory.json");
3682
4273
  let raw = null;
3683
4274
  try {
3684
- if (fs8.existsSync(globalPath)) {
3685
- raw = fs8.readFileSync(globalPath, "utf-8");
3686
- } else if (path10.resolve(globalPath) !== path10.resolve(legacyPath) && fs8.existsSync(legacyPath)) {
3687
- raw = fs8.readFileSync(legacyPath, "utf-8");
4275
+ if (fs10.existsSync(globalPath)) {
4276
+ raw = fs10.readFileSync(globalPath, "utf-8");
4277
+ } else if (path12.resolve(globalPath) !== path12.resolve(legacyPath) && fs10.existsSync(legacyPath)) {
4278
+ raw = fs10.readFileSync(legacyPath, "utf-8");
3688
4279
  }
3689
4280
  } catch {
3690
4281
  return "";
@@ -3722,10 +4313,10 @@ var memoryStore = [];
3722
4313
  var nextId2 = 1;
3723
4314
  var loaded = false;
3724
4315
  function getMemoryFilePath() {
3725
- return path10.join(os5.homedir(), ".bluma", "coding_memory.json");
4316
+ return path12.join(os7.homedir(), ".bluma", "coding_memory.json");
3726
4317
  }
3727
4318
  function getLegacyMemoryFilePath() {
3728
- return path10.join(process.cwd(), ".bluma", "coding_memory.json");
4319
+ return path12.join(process.cwd(), ".bluma", "coding_memory.json");
3729
4320
  }
3730
4321
  function loadMemoryFromFile() {
3731
4322
  if (loaded) return;
@@ -3735,19 +4326,19 @@ function loadMemoryFromFile() {
3735
4326
  try {
3736
4327
  const filePath = getMemoryFilePath();
3737
4328
  const legacy = getLegacyMemoryFilePath();
3738
- const legacyDistinct = path10.resolve(legacy) !== path10.resolve(filePath);
4329
+ const legacyDistinct = path12.resolve(legacy) !== path12.resolve(filePath);
3739
4330
  const readIntoStore = (p) => {
3740
- const raw = fs8.readFileSync(p, "utf-8");
4331
+ const raw = fs10.readFileSync(p, "utf-8");
3741
4332
  const parsed = JSON.parse(raw);
3742
4333
  if (Array.isArray(parsed.entries)) {
3743
4334
  memoryStore = parsed.entries;
3744
4335
  nextId2 = typeof parsed.nextId === "number" ? parsed.nextId : memoryStore.length + 1;
3745
4336
  }
3746
4337
  };
3747
- if (fs8.existsSync(filePath)) {
4338
+ if (fs10.existsSync(filePath)) {
3748
4339
  readIntoStore(filePath);
3749
4340
  }
3750
- if (memoryStore.length === 0 && legacyDistinct && fs8.existsSync(legacy)) {
4341
+ if (memoryStore.length === 0 && legacyDistinct && fs10.existsSync(legacy)) {
3751
4342
  readIntoStore(legacy);
3752
4343
  if (memoryStore.length > 0) {
3753
4344
  saveMemoryToFile();
@@ -3761,16 +4352,16 @@ function loadMemoryFromFile() {
3761
4352
  function saveMemoryToFile() {
3762
4353
  try {
3763
4354
  const filePath = getMemoryFilePath();
3764
- const dir = path10.dirname(filePath);
3765
- if (!fs8.existsSync(dir)) {
3766
- fs8.mkdirSync(dir, { recursive: true });
4355
+ const dir = path12.dirname(filePath);
4356
+ if (!fs10.existsSync(dir)) {
4357
+ fs10.mkdirSync(dir, { recursive: true });
3767
4358
  }
3768
4359
  const payload = {
3769
4360
  entries: memoryStore,
3770
4361
  nextId: nextId2,
3771
4362
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3772
4363
  };
3773
- fs8.writeFileSync(filePath, JSON.stringify(payload, null, 2));
4364
+ fs10.writeFileSync(filePath, JSON.stringify(payload, null, 2));
3774
4365
  } catch {
3775
4366
  }
3776
4367
  }
@@ -3967,9 +4558,9 @@ var ToolInvoker = class {
3967
4558
  async initialize() {
3968
4559
  try {
3969
4560
  const __filename = fileURLToPath(import.meta.url);
3970
- const __dirname2 = path11.dirname(__filename);
3971
- const configPath = path11.resolve(__dirname2, "config", "native_tools.json");
3972
- const fileContent = await fs9.readFile(configPath, "utf-8");
4561
+ const __dirname2 = path13.dirname(__filename);
4562
+ const configPath = path13.resolve(__dirname2, "config", "native_tools.json");
4563
+ const fileContent = await fs11.readFile(configPath, "utf-8");
3973
4564
  const config2 = JSON.parse(fileContent);
3974
4565
  this.toolDefinitions = config2.nativeTools;
3975
4566
  } catch (error) {
@@ -4030,9 +4621,9 @@ var ToolInvoker = class {
4030
4621
  };
4031
4622
 
4032
4623
  // src/app/agent/tools/mcp/mcp_client.ts
4033
- import { promises as fs10 } from "fs";
4034
- import path12 from "path";
4035
- import os6 from "os";
4624
+ import { promises as fs12 } from "fs";
4625
+ import path14 from "path";
4626
+ import os8 from "os";
4036
4627
  import { fileURLToPath as fileURLToPath2 } from "url";
4037
4628
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4038
4629
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
@@ -4059,9 +4650,9 @@ var MCPClient = class {
4059
4650
  });
4060
4651
  }
4061
4652
  const __filename = fileURLToPath2(import.meta.url);
4062
- const __dirname2 = path12.dirname(__filename);
4063
- const defaultConfigPath = path12.resolve(__dirname2, "config", "bluma-mcp.json");
4064
- const userConfigPath = path12.join(os6.homedir(), ".bluma", "bluma-mcp.json");
4653
+ const __dirname2 = path14.dirname(__filename);
4654
+ const defaultConfigPath = path14.resolve(__dirname2, "config", "bluma-mcp.json");
4655
+ const userConfigPath = path14.join(os8.homedir(), ".bluma", "bluma-mcp.json");
4065
4656
  const defaultConfig = await this.loadMcpConfig(defaultConfigPath, "Default");
4066
4657
  const userConfig = await this.loadMcpConfig(userConfigPath, "User");
4067
4658
  const mergedConfig = {
@@ -4095,7 +4686,7 @@ var MCPClient = class {
4095
4686
  }
4096
4687
  async loadMcpConfig(configPath, configType) {
4097
4688
  try {
4098
- const fileContent = await fs10.readFile(configPath, "utf-8");
4689
+ const fileContent = await fs12.readFile(configPath, "utf-8");
4099
4690
  const processedContent = this.replaceEnvPlaceholders(fileContent);
4100
4691
  return JSON.parse(processedContent);
4101
4692
  } catch (error) {
@@ -4114,7 +4705,7 @@ var MCPClient = class {
4114
4705
  async connectToStdioServer(serverName, config2) {
4115
4706
  let commandToExecute = config2.command;
4116
4707
  let argsToExecute = config2.args || [];
4117
- const isWindows = os6.platform() === "win32";
4708
+ const isWindows = os8.platform() === "win32";
4118
4709
  if (!isWindows && commandToExecute.toLowerCase() === "cmd") {
4119
4710
  if (argsToExecute.length >= 2 && argsToExecute[0].toLowerCase() === "/c") {
4120
4711
  commandToExecute = argsToExecute[1];
@@ -4230,13 +4821,13 @@ var AdvancedFeedbackSystem = class {
4230
4821
  };
4231
4822
 
4232
4823
  // src/app/agent/bluma/core/bluma.ts
4233
- import path17 from "path";
4824
+ import path18 from "path";
4234
4825
  import { v4 as uuidv43 } from "uuid";
4235
4826
 
4236
4827
  // src/app/agent/session_manager/session_manager.ts
4237
- import path13 from "path";
4238
- import os7 from "os";
4239
- import { promises as fs11 } from "fs";
4828
+ import path15 from "path";
4829
+ import os9 from "os";
4830
+ import { promises as fs13 } from "fs";
4240
4831
  var fileLocks = /* @__PURE__ */ new Map();
4241
4832
  async function withFileLock(file, fn) {
4242
4833
  const prev = fileLocks.get(file) || Promise.resolve();
@@ -4272,13 +4863,13 @@ function debouncedSave(sessionFile, history, memory) {
4272
4863
  function expandHome(p) {
4273
4864
  if (!p) return p;
4274
4865
  if (p.startsWith("~")) {
4275
- return path13.join(os7.homedir(), p.slice(1));
4866
+ return path15.join(os9.homedir(), p.slice(1));
4276
4867
  }
4277
4868
  return p;
4278
4869
  }
4279
4870
  function getPreferredAppDir() {
4280
- const fixed = path13.join(os7.homedir(), ".bluma");
4281
- return path13.resolve(expandHome(fixed));
4871
+ const fixed = path15.join(os9.homedir(), ".bluma");
4872
+ return path15.resolve(expandHome(fixed));
4282
4873
  }
4283
4874
  async function safeRenameWithRetry(src, dest, maxRetries = 6) {
4284
4875
  let attempt = 0;
@@ -4286,10 +4877,10 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
4286
4877
  const isWin = process.platform === "win32";
4287
4878
  while (attempt <= maxRetries) {
4288
4879
  try {
4289
- const dir = path13.dirname(dest);
4290
- await fs11.mkdir(dir, { recursive: true }).catch(() => {
4880
+ const dir = path15.dirname(dest);
4881
+ await fs13.mkdir(dir, { recursive: true }).catch(() => {
4291
4882
  });
4292
- await fs11.rename(src, dest);
4883
+ await fs13.rename(src, dest);
4293
4884
  return;
4294
4885
  } catch (e) {
4295
4886
  lastErr = e;
@@ -4302,13 +4893,13 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
4302
4893
  }
4303
4894
  }
4304
4895
  try {
4305
- await fs11.access(src);
4306
- const data = await fs11.readFile(src);
4307
- const dir = path13.dirname(dest);
4308
- await fs11.mkdir(dir, { recursive: true }).catch(() => {
4896
+ await fs13.access(src);
4897
+ const data = await fs13.readFile(src);
4898
+ const dir = path15.dirname(dest);
4899
+ await fs13.mkdir(dir, { recursive: true }).catch(() => {
4309
4900
  });
4310
- await fs11.writeFile(dest, data);
4311
- await fs11.unlink(src).catch(() => {
4901
+ await fs13.writeFile(dest, data);
4902
+ await fs13.unlink(src).catch(() => {
4312
4903
  });
4313
4904
  return;
4314
4905
  } catch (fallbackErr) {
@@ -4321,16 +4912,16 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
4321
4912
  }
4322
4913
  async function ensureSessionDir() {
4323
4914
  const appDir = getPreferredAppDir();
4324
- const sessionDir = path13.join(appDir, "sessions");
4325
- await fs11.mkdir(sessionDir, { recursive: true });
4915
+ const sessionDir = path15.join(appDir, "sessions");
4916
+ await fs13.mkdir(sessionDir, { recursive: true });
4326
4917
  return sessionDir;
4327
4918
  }
4328
4919
  async function loadOrcreateSession(sessionId) {
4329
4920
  const sessionDir = await ensureSessionDir();
4330
- const sessionFile = path13.join(sessionDir, `${sessionId}.json`);
4921
+ const sessionFile = path15.join(sessionDir, `${sessionId}.json`);
4331
4922
  try {
4332
- await fs11.access(sessionFile);
4333
- const fileContent = await fs11.readFile(sessionFile, "utf-8");
4923
+ await fs13.access(sessionFile);
4924
+ const fileContent = await fs13.readFile(sessionFile, "utf-8");
4334
4925
  const sessionData = JSON.parse(fileContent);
4335
4926
  const memory = {
4336
4927
  historyAnchor: sessionData.history_anchor ?? null,
@@ -4343,7 +4934,7 @@ async function loadOrcreateSession(sessionId) {
4343
4934
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
4344
4935
  conversation_history: []
4345
4936
  };
4346
- await fs11.writeFile(sessionFile, JSON.stringify(newSessionData, null, 2), "utf-8");
4937
+ await fs13.writeFile(sessionFile, JSON.stringify(newSessionData, null, 2), "utf-8");
4347
4938
  const emptyMemory = {
4348
4939
  historyAnchor: null,
4349
4940
  compressedTurnSliceCount: 0
@@ -4355,12 +4946,12 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
4355
4946
  await withFileLock(sessionFile, async () => {
4356
4947
  let sessionData;
4357
4948
  try {
4358
- const dir = path13.dirname(sessionFile);
4359
- await fs11.mkdir(dir, { recursive: true });
4949
+ const dir = path15.dirname(sessionFile);
4950
+ await fs13.mkdir(dir, { recursive: true });
4360
4951
  } catch {
4361
4952
  }
4362
4953
  try {
4363
- const fileContent = await fs11.readFile(sessionFile, "utf-8");
4954
+ const fileContent = await fs13.readFile(sessionFile, "utf-8");
4364
4955
  sessionData = JSON.parse(fileContent);
4365
4956
  } catch (error) {
4366
4957
  const code = error && error.code;
@@ -4371,14 +4962,14 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
4371
4962
  console.warn(`An unknown error occurred while reading ${sessionFile}. Re-initializing.`, error);
4372
4963
  }
4373
4964
  }
4374
- const sessionId = path13.basename(sessionFile, ".json");
4965
+ const sessionId = path15.basename(sessionFile, ".json");
4375
4966
  sessionData = {
4376
4967
  session_id: sessionId,
4377
4968
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
4378
4969
  conversation_history: []
4379
4970
  };
4380
4971
  try {
4381
- await fs11.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
4972
+ await fs13.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
4382
4973
  } catch {
4383
4974
  }
4384
4975
  }
@@ -4394,7 +4985,7 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
4394
4985
  }
4395
4986
  const tempSessionFile = `${sessionFile}.${Date.now()}.tmp`;
4396
4987
  try {
4397
- await fs11.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
4988
+ await fs13.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
4398
4989
  await safeRenameWithRetry(tempSessionFile, sessionFile);
4399
4990
  } catch (writeError) {
4400
4991
  if (writeError instanceof Error) {
@@ -4403,7 +4994,7 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
4403
4994
  console.error(`An unknown fatal error occurred while saving session to ${sessionFile}:`, writeError);
4404
4995
  }
4405
4996
  try {
4406
- await fs11.unlink(tempSessionFile);
4997
+ await fs13.unlink(tempSessionFile);
4407
4998
  } catch {
4408
4999
  }
4409
5000
  }
@@ -4420,15 +5011,16 @@ async function saveSessionHistory(sessionFile, history, memory) {
4420
5011
  }
4421
5012
 
4422
5013
  // src/app/agent/core/prompt/prompt_builder.ts
4423
- import os9 from "os";
4424
- import fs13 from "fs";
4425
- import path15 from "path";
5014
+ import os11 from "os";
5015
+ import fs15 from "fs";
5016
+ import path17 from "path";
4426
5017
  import { execSync } from "child_process";
4427
5018
 
4428
5019
  // src/app/agent/skills/skill_loader.ts
4429
- import fs12 from "fs";
4430
- import path14 from "path";
4431
- import os8 from "os";
5020
+ import fs14 from "fs";
5021
+ import path16 from "path";
5022
+ import os10 from "os";
5023
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
4432
5024
  var SkillLoader = class _SkillLoader {
4433
5025
  bundledSkillsDir;
4434
5026
  projectSkillsDir;
@@ -4436,8 +5028,8 @@ var SkillLoader = class _SkillLoader {
4436
5028
  cache = /* @__PURE__ */ new Map();
4437
5029
  conflicts = [];
4438
5030
  constructor(projectRoot, bundledDir) {
4439
- this.projectSkillsDir = path14.join(projectRoot, ".bluma", "skills");
4440
- this.globalSkillsDir = path14.join(os8.homedir(), ".bluma", "skills");
5031
+ this.projectSkillsDir = path16.join(projectRoot, ".bluma", "skills");
5032
+ this.globalSkillsDir = path16.join(os10.homedir(), ".bluma", "skills");
4441
5033
  this.bundledSkillsDir = bundledDir || _SkillLoader.resolveBundledDir();
4442
5034
  }
4443
5035
  /**
@@ -4446,34 +5038,60 @@ var SkillLoader = class _SkillLoader {
4446
5038
  */
4447
5039
  static resolveBundledDir() {
4448
5040
  if (process.env.JEST_WORKER_ID !== void 0 || process.env.NODE_ENV === "test") {
4449
- return path14.join(process.cwd(), "dist", "config", "skills");
5041
+ return path16.join(process.cwd(), "dist", "config", "skills");
4450
5042
  }
4451
5043
  const candidates = [];
5044
+ const push = (p) => {
5045
+ const abs = path16.resolve(p);
5046
+ if (!candidates.includes(abs)) {
5047
+ candidates.push(abs);
5048
+ }
5049
+ };
5050
+ try {
5051
+ const bundleDir = path16.dirname(fileURLToPath3(import.meta.url));
5052
+ push(path16.join(bundleDir, "config", "skills"));
5053
+ } catch {
5054
+ }
4452
5055
  const argv1 = process.argv[1];
4453
5056
  if (argv1 && !argv1.startsWith("-")) {
4454
5057
  try {
4455
- const scriptDir = path14.dirname(path14.resolve(argv1));
4456
- candidates.push(path14.join(scriptDir, "config", "skills"));
5058
+ let resolved = argv1;
5059
+ if (path16.isAbsolute(argv1) && fs14.existsSync(argv1)) {
5060
+ resolved = fs14.realpathSync(argv1);
5061
+ } else if (!path16.isAbsolute(argv1)) {
5062
+ resolved = path16.resolve(process.cwd(), argv1);
5063
+ }
5064
+ const scriptDir = path16.dirname(resolved);
5065
+ push(path16.join(scriptDir, "config", "skills"));
4457
5066
  } catch {
4458
5067
  }
4459
5068
  }
4460
- candidates.push(
4461
- path14.join(process.cwd(), "dist", "config", "skills"),
4462
- path14.join(process.cwd(), "node_modules", "@nomad-e", "bluma-cli", "dist", "config", "skills")
5069
+ push(path16.join(process.cwd(), "dist", "config", "skills"));
5070
+ push(
5071
+ path16.join(
5072
+ process.cwd(),
5073
+ "node_modules",
5074
+ "@nomad-e",
5075
+ "bluma-cli",
5076
+ "dist",
5077
+ "config",
5078
+ "skills"
5079
+ )
4463
5080
  );
4464
5081
  if (typeof __dirname !== "undefined") {
4465
- candidates.push(
4466
- path14.join(__dirname, "config", "skills"),
4467
- path14.join(__dirname, "..", "..", "..", "config", "skills")
4468
- );
5082
+ push(path16.join(__dirname, "config", "skills"));
5083
+ push(path16.join(__dirname, "..", "..", "..", "config", "skills"));
4469
5084
  }
4470
- for (const c of candidates) {
4471
- const abs = path14.resolve(c);
4472
- if (fs12.existsSync(abs)) {
5085
+ for (const abs of candidates) {
5086
+ if (fs14.existsSync(abs)) {
4473
5087
  return abs;
4474
5088
  }
4475
5089
  }
4476
- return path14.join(process.cwd(), "dist", "config", "skills");
5090
+ try {
5091
+ return path16.join(path16.dirname(fileURLToPath3(import.meta.url)), "config", "skills");
5092
+ } catch {
5093
+ return path16.join(process.cwd(), "dist", "config", "skills");
5094
+ }
4477
5095
  }
4478
5096
  /**
4479
5097
  * Lista skills disponíveis de todas as fontes.
@@ -4501,8 +5119,8 @@ var SkillLoader = class _SkillLoader {
4501
5119
  this.conflicts.push({
4502
5120
  name: skill.name,
4503
5121
  userSource: source,
4504
- userPath: path14.join(dir, skill.name, "SKILL.md"),
4505
- bundledPath: path14.join(this.bundledSkillsDir, skill.name, "SKILL.md")
5122
+ userPath: path16.join(dir, skill.name, "SKILL.md"),
5123
+ bundledPath: path16.join(this.bundledSkillsDir, skill.name, "SKILL.md")
4506
5124
  });
4507
5125
  continue;
4508
5126
  }
@@ -4510,20 +5128,20 @@ var SkillLoader = class _SkillLoader {
4510
5128
  }
4511
5129
  }
4512
5130
  listFromDir(dir, source) {
4513
- if (!fs12.existsSync(dir)) return [];
5131
+ if (!fs14.existsSync(dir)) return [];
4514
5132
  try {
4515
- return fs12.readdirSync(dir).filter((d) => {
4516
- const fullPath = path14.join(dir, d);
4517
- return fs12.statSync(fullPath).isDirectory() && fs12.existsSync(path14.join(fullPath, "SKILL.md"));
4518
- }).map((d) => this.loadMetadataFromPath(path14.join(dir, d, "SKILL.md"), d, source)).filter((m) => m !== null);
5133
+ return fs14.readdirSync(dir).filter((d) => {
5134
+ const fullPath = path16.join(dir, d);
5135
+ return fs14.statSync(fullPath).isDirectory() && fs14.existsSync(path16.join(fullPath, "SKILL.md"));
5136
+ }).map((d) => this.loadMetadataFromPath(path16.join(dir, d, "SKILL.md"), d, source)).filter((m) => m !== null);
4519
5137
  } catch {
4520
5138
  return [];
4521
5139
  }
4522
5140
  }
4523
5141
  loadMetadataFromPath(skillPath, skillName, source) {
4524
- if (!fs12.existsSync(skillPath)) return null;
5142
+ if (!fs14.existsSync(skillPath)) return null;
4525
5143
  try {
4526
- const raw = fs12.readFileSync(skillPath, "utf-8");
5144
+ const raw = fs14.readFileSync(skillPath, "utf-8");
4527
5145
  const parsed = this.parseFrontmatter(raw);
4528
5146
  return {
4529
5147
  name: parsed.name || skillName,
@@ -4545,12 +5163,12 @@ var SkillLoader = class _SkillLoader {
4545
5163
  */
4546
5164
  load(name) {
4547
5165
  if (this.cache.has(name)) return this.cache.get(name);
4548
- const bundledPath = path14.join(this.bundledSkillsDir, name, "SKILL.md");
4549
- const projectPath = path14.join(this.projectSkillsDir, name, "SKILL.md");
4550
- const globalPath = path14.join(this.globalSkillsDir, name, "SKILL.md");
4551
- const existsBundled = fs12.existsSync(bundledPath);
4552
- const existsProject = fs12.existsSync(projectPath);
4553
- const existsGlobal = fs12.existsSync(globalPath);
5166
+ const bundledPath = path16.join(this.bundledSkillsDir, name, "SKILL.md");
5167
+ const projectPath = path16.join(this.projectSkillsDir, name, "SKILL.md");
5168
+ const globalPath = path16.join(this.globalSkillsDir, name, "SKILL.md");
5169
+ const existsBundled = fs14.existsSync(bundledPath);
5170
+ const existsProject = fs14.existsSync(projectPath);
5171
+ const existsGlobal = fs14.existsSync(globalPath);
4554
5172
  if (existsBundled && (existsProject || existsGlobal)) {
4555
5173
  const conflictSource = existsProject ? "project" : "global";
4556
5174
  const conflictPath = existsProject ? projectPath : globalPath;
@@ -4589,9 +5207,9 @@ var SkillLoader = class _SkillLoader {
4589
5207
  }
4590
5208
  loadFromPath(skillPath, name, source) {
4591
5209
  try {
4592
- const raw = fs12.readFileSync(skillPath, "utf-8");
5210
+ const raw = fs14.readFileSync(skillPath, "utf-8");
4593
5211
  const parsed = this.parseFrontmatter(raw);
4594
- const skillDir = path14.dirname(skillPath);
5212
+ const skillDir = path16.dirname(skillPath);
4595
5213
  return {
4596
5214
  name: parsed.name || name,
4597
5215
  description: parsed.description || "",
@@ -4600,22 +5218,22 @@ var SkillLoader = class _SkillLoader {
4600
5218
  version: parsed.version,
4601
5219
  author: parsed.author,
4602
5220
  license: parsed.license,
4603
- references: this.scanAssets(path14.join(skillDir, "references")),
4604
- scripts: this.scanAssets(path14.join(skillDir, "scripts"))
5221
+ references: this.scanAssets(path16.join(skillDir, "references")),
5222
+ scripts: this.scanAssets(path16.join(skillDir, "scripts"))
4605
5223
  };
4606
5224
  } catch {
4607
5225
  return null;
4608
5226
  }
4609
5227
  }
4610
5228
  scanAssets(dir) {
4611
- if (!fs12.existsSync(dir)) return [];
5229
+ if (!fs14.existsSync(dir)) return [];
4612
5230
  try {
4613
- return fs12.readdirSync(dir).filter((f) => {
4614
- const fp = path14.join(dir, f);
4615
- return fs12.statSync(fp).isFile();
5231
+ return fs14.readdirSync(dir).filter((f) => {
5232
+ const fp = path16.join(dir, f);
5233
+ return fs14.statSync(fp).isFile();
4616
5234
  }).map((f) => ({
4617
5235
  name: f,
4618
- path: path14.resolve(dir, f)
5236
+ path: path16.resolve(dir, f)
4619
5237
  }));
4620
5238
  } catch {
4621
5239
  return [];
@@ -4672,10 +5290,10 @@ var SkillLoader = class _SkillLoader {
4672
5290
  this.cache.clear();
4673
5291
  }
4674
5292
  exists(name) {
4675
- const bundledPath = path14.join(this.bundledSkillsDir, name, "SKILL.md");
4676
- const projectPath = path14.join(this.projectSkillsDir, name, "SKILL.md");
4677
- const globalPath = path14.join(this.globalSkillsDir, name, "SKILL.md");
4678
- return fs12.existsSync(bundledPath) || fs12.existsSync(projectPath) || fs12.existsSync(globalPath);
5293
+ const bundledPath = path16.join(this.bundledSkillsDir, name, "SKILL.md");
5294
+ const projectPath = path16.join(this.projectSkillsDir, name, "SKILL.md");
5295
+ const globalPath = path16.join(this.globalSkillsDir, name, "SKILL.md");
5296
+ return fs14.existsSync(bundledPath) || fs14.existsSync(projectPath) || fs14.existsSync(globalPath);
4679
5297
  }
4680
5298
  /**
4681
5299
  * Retorna conflitos detetados (skills do utilizador com mesmo nome de nativas).
@@ -4734,10 +5352,10 @@ function getGitBranch(dir) {
4734
5352
  }
4735
5353
  function getPackageManager(dir) {
4736
5354
  try {
4737
- if (fs13.existsSync(path15.join(dir, "pnpm-lock.yaml"))) return "pnpm";
4738
- if (fs13.existsSync(path15.join(dir, "yarn.lock"))) return "yarn";
4739
- if (fs13.existsSync(path15.join(dir, "bun.lockb"))) return "bun";
4740
- if (fs13.existsSync(path15.join(dir, "package-lock.json"))) return "npm";
5355
+ if (fs15.existsSync(path17.join(dir, "pnpm-lock.yaml"))) return "pnpm";
5356
+ if (fs15.existsSync(path17.join(dir, "yarn.lock"))) return "yarn";
5357
+ if (fs15.existsSync(path17.join(dir, "bun.lockb"))) return "bun";
5358
+ if (fs15.existsSync(path17.join(dir, "package-lock.json"))) return "npm";
4741
5359
  return "unknown";
4742
5360
  } catch {
4743
5361
  return "unknown";
@@ -4745,9 +5363,9 @@ function getPackageManager(dir) {
4745
5363
  }
4746
5364
  function getProjectType(dir) {
4747
5365
  try {
4748
- const files = fs13.readdirSync(dir);
5366
+ const files = fs15.readdirSync(dir);
4749
5367
  if (files.includes("package.json")) {
4750
- const pkg = JSON.parse(fs13.readFileSync(path15.join(dir, "package.json"), "utf-8"));
5368
+ const pkg = JSON.parse(fs15.readFileSync(path17.join(dir, "package.json"), "utf-8"));
4751
5369
  if (pkg.dependencies?.next || pkg.devDependencies?.next) return "Next.js";
4752
5370
  if (pkg.dependencies?.react || pkg.devDependencies?.react) return "React";
4753
5371
  if (pkg.dependencies?.express || pkg.devDependencies?.express) return "Express";
@@ -4766,9 +5384,9 @@ function getProjectType(dir) {
4766
5384
  }
4767
5385
  function getTestFramework(dir) {
4768
5386
  try {
4769
- const pkgPath = path15.join(dir, "package.json");
4770
- if (fs13.existsSync(pkgPath)) {
4771
- const pkg = JSON.parse(fs13.readFileSync(pkgPath, "utf-8"));
5387
+ const pkgPath = path17.join(dir, "package.json");
5388
+ if (fs15.existsSync(pkgPath)) {
5389
+ const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf-8"));
4772
5390
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
4773
5391
  if (deps.jest) return "jest";
4774
5392
  if (deps.vitest) return "vitest";
@@ -4777,7 +5395,7 @@ function getTestFramework(dir) {
4777
5395
  if (deps["@playwright/test"]) return "playwright";
4778
5396
  if (deps.cypress) return "cypress";
4779
5397
  }
4780
- if (fs13.existsSync(path15.join(dir, "pytest.ini")) || fs13.existsSync(path15.join(dir, "conftest.py"))) return "pytest";
5398
+ if (fs15.existsSync(path17.join(dir, "pytest.ini")) || fs15.existsSync(path17.join(dir, "conftest.py"))) return "pytest";
4781
5399
  return "unknown";
4782
5400
  } catch {
4783
5401
  return "unknown";
@@ -4785,9 +5403,9 @@ function getTestFramework(dir) {
4785
5403
  }
4786
5404
  function getTestCommand(dir) {
4787
5405
  try {
4788
- const pkgPath = path15.join(dir, "package.json");
4789
- if (fs13.existsSync(pkgPath)) {
4790
- const pkg = JSON.parse(fs13.readFileSync(pkgPath, "utf-8"));
5406
+ const pkgPath = path17.join(dir, "package.json");
5407
+ if (fs15.existsSync(pkgPath)) {
5408
+ const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf-8"));
4791
5409
  if (pkg.scripts?.test) return `npm test`;
4792
5410
  if (pkg.scripts?.["test:unit"]) return `npm run test:unit`;
4793
5411
  }
@@ -5264,12 +5882,12 @@ In sandbox mode you are a Python-focused, non-interactive, deterministic agent t
5264
5882
  function getUnifiedSystemPrompt(availableSkills) {
5265
5883
  const cwd = process.cwd();
5266
5884
  const env = {
5267
- os_type: os9.type(),
5268
- os_version: os9.release(),
5269
- architecture: os9.arch(),
5885
+ os_type: os11.type(),
5886
+ os_version: os11.release(),
5887
+ architecture: os11.arch(),
5270
5888
  workdir: cwd,
5271
5889
  shell_type: process.env.SHELL || process.env.COMSPEC || "unknown",
5272
- username: os9.userInfo().username,
5890
+ username: os11.userInfo().username,
5273
5891
  current_date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
5274
5892
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
5275
5893
  is_git_repo: isGitRepo(cwd) ? "yes" : "no",
@@ -5454,8 +6072,8 @@ ${memorySnapshot.trim().length > 0 ? memorySnapshot : "(No entries yet. Use codi
5454
6072
  }
5455
6073
  function isGitRepo(dir) {
5456
6074
  try {
5457
- const gitPath = path15.join(dir, ".git");
5458
- return fs13.existsSync(gitPath) && fs13.lstatSync(gitPath).isDirectory();
6075
+ const gitPath = path17.join(dir, ".git");
6076
+ return fs15.existsSync(gitPath) && fs15.lstatSync(gitPath).isDirectory();
5459
6077
  } catch {
5460
6078
  return false;
5461
6079
  }
@@ -5664,21 +6282,9 @@ function partitionConversationIntoTurnSlices(conversationHistory) {
5664
6282
  }
5665
6283
  return turns;
5666
6284
  }
5667
- var DEFAULT_TOKEN_BUDGET = 6e4;
5668
- var DEFAULT_COMPRESS_THRESHOLD = 0.7;
5669
- var DEFAULT_KEEP_RECENT_TURNS = 8;
5670
- function readContextOptionInt(envKey, fallback) {
5671
- const raw = (process.env[envKey] ?? "").trim();
5672
- if (!raw) return fallback;
5673
- const n = Number(raw);
5674
- return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
5675
- }
5676
- function readContextOptionFloat(envKey, fallback) {
5677
- const raw = (process.env[envKey] ?? "").trim();
5678
- if (!raw) return fallback;
5679
- const n = Number(raw);
5680
- return Number.isFinite(n) && n > 0 && n <= 1 ? n : fallback;
5681
- }
6285
+ var CONTEXT_TOKEN_BUDGET = 24e4;
6286
+ var COMPRESS_THRESHOLD = 0.7;
6287
+ var KEEP_RECENT_TURNS = 10;
5682
6288
  function buildContextMessages(systemMessages, anchor, pendingRaw, recentFlat) {
5683
6289
  const anchorMsg = anchor ? [anchorToSystemMessage(anchor)] : [];
5684
6290
  return [...systemMessages, ...anchorMsg, ...pendingRaw, ...recentFlat];
@@ -5691,9 +6297,9 @@ async function createApiContextWindow(fullHistory, currentAnchor, compressedTurn
5691
6297
  newCompressedTurnSliceCount: compressedTurnSliceCount
5692
6298
  };
5693
6299
  }
5694
- const tokenBudget = options?.tokenBudget ?? readContextOptionInt("BLUMA_CONTEXT_TOKEN_BUDGET", DEFAULT_TOKEN_BUDGET);
5695
- const compressThreshold = options?.compressThreshold ?? readContextOptionFloat("BLUMA_COMPRESS_THRESHOLD", DEFAULT_COMPRESS_THRESHOLD);
5696
- const keepRecentTurns = options?.keepRecentTurns ?? readContextOptionInt("BLUMA_KEEP_RECENT_TURNS", DEFAULT_KEEP_RECENT_TURNS);
6300
+ const tokenBudget = options?.tokenBudget ?? CONTEXT_TOKEN_BUDGET;
6301
+ const compressThreshold = options?.compressThreshold ?? COMPRESS_THRESHOLD;
6302
+ const keepRecentTurns = options?.keepRecentTurns ?? KEEP_RECENT_TURNS;
5697
6303
  const systemMessages = [];
5698
6304
  let historyStartIndex = 0;
5699
6305
  while (historyStartIndex < fullHistory.length && fullHistory[historyStartIndex].role === "system") {
@@ -5735,7 +6341,7 @@ async function createApiContextWindow(fullHistory, currentAnchor, compressedTurn
5735
6341
  }
5736
6342
 
5737
6343
  // src/app/agent/core/llm/llm.ts
5738
- import os10 from "os";
6344
+ import os12 from "os";
5739
6345
  import OpenAI from "openai";
5740
6346
  function defaultBlumaUserContextInput(sessionId, userMessage) {
5741
6347
  const msg = String(userMessage || "").slice(0, 300);
@@ -5752,7 +6358,7 @@ function defaultBlumaUserContextInput(sessionId, userMessage) {
5752
6358
  }
5753
6359
  function getPreferredMacAddress() {
5754
6360
  try {
5755
- const ifaces = os10.networkInterfaces();
6361
+ const ifaces = os12.networkInterfaces();
5756
6362
  for (const name of Object.keys(ifaces)) {
5757
6363
  const addrs = ifaces[name];
5758
6364
  if (!addrs) continue;
@@ -5767,7 +6373,7 @@ function getPreferredMacAddress() {
5767
6373
  } catch {
5768
6374
  }
5769
6375
  try {
5770
- return `host:${os10.hostname()}`;
6376
+ return `host:${os12.hostname()}`;
5771
6377
  } catch {
5772
6378
  return "unknown";
5773
6379
  }
@@ -5777,7 +6383,7 @@ function defaultInteractiveCliUserContextInput(sessionId, userMessage) {
5777
6383
  const machineId = getPreferredMacAddress();
5778
6384
  let userName = null;
5779
6385
  try {
5780
- userName = os10.userInfo().username || null;
6386
+ userName = os12.userInfo().username || null;
5781
6387
  } catch {
5782
6388
  userName = null;
5783
6389
  }
@@ -6104,134 +6710,6 @@ var ToolCallNormalizer = class {
6104
6710
  }
6105
6711
  };
6106
6712
 
6107
- // src/app/agent/utils/user_message_images.ts
6108
- import fs14 from "fs";
6109
- import os11 from "os";
6110
- import path16 from "path";
6111
- var IMAGE_EXT = /\.(png|jpe?g|gif|webp|bmp)$/i;
6112
- var MAX_IMAGE_BYTES = 4 * 1024 * 1024;
6113
- var MAX_IMAGES = 6;
6114
- var MIME = {
6115
- ".png": "image/png",
6116
- ".jpg": "image/jpeg",
6117
- ".jpeg": "image/jpeg",
6118
- ".gif": "image/gif",
6119
- ".webp": "image/webp",
6120
- ".bmp": "image/bmp"
6121
- };
6122
- function expandUserPath(p) {
6123
- const t = p.trim();
6124
- if (t.startsWith("~")) {
6125
- return path16.join(os11.homedir(), t.slice(1).replace(/^\//, ""));
6126
- }
6127
- return t;
6128
- }
6129
- function isPathAllowed(absResolved, cwd) {
6130
- const resolved = path16.normalize(path16.resolve(absResolved));
6131
- const cwdR = path16.normalize(path16.resolve(cwd));
6132
- const homeR = path16.normalize(path16.resolve(os11.homedir()));
6133
- const underCwd = resolved === cwdR || resolved.startsWith(cwdR + path16.sep);
6134
- const underHome = resolved === homeR || resolved.startsWith(homeR + path16.sep);
6135
- return underCwd || underHome;
6136
- }
6137
- function mimeFor(abs) {
6138
- const ext = path16.extname(abs).toLowerCase();
6139
- return MIME[ext] || "application/octet-stream";
6140
- }
6141
- function collectImagePathStrings(raw) {
6142
- const found = [];
6143
- const seen = /* @__PURE__ */ new Set();
6144
- const quoted = /(["'])((?:(?!\1).)*?\.(?:png|jpe?g|gif|webp|bmp))\1/gi;
6145
- for (const m of raw.matchAll(quoted)) {
6146
- const p = m[2].trim();
6147
- if (p && !seen.has(p)) {
6148
- seen.add(p);
6149
- found.push(p);
6150
- }
6151
- }
6152
- const withoutQuoted = raw.replace(quoted, " ");
6153
- const tokens = withoutQuoted.split(/\s+/);
6154
- for (let tok of tokens) {
6155
- tok = tok.replace(/^[([{]+/, "").replace(/[)\]},;:]+$/, "");
6156
- if (!tok || !IMAGE_EXT.test(tok)) continue;
6157
- if (!seen.has(tok)) {
6158
- seen.add(tok);
6159
- found.push(tok);
6160
- }
6161
- }
6162
- return found;
6163
- }
6164
- function resolveImagePath(candidate, cwd) {
6165
- const expanded = expandUserPath(candidate);
6166
- const abs = path16.isAbsolute(expanded) ? path16.normalize(expanded) : path16.normalize(path16.resolve(cwd, expanded));
6167
- if (!isPathAllowed(abs, cwd)) return null;
6168
- try {
6169
- if (!fs14.existsSync(abs) || !fs14.statSync(abs).isFile()) return null;
6170
- } catch {
6171
- return null;
6172
- }
6173
- if (!IMAGE_EXT.test(abs)) return null;
6174
- return abs;
6175
- }
6176
- function stripImagePathStrings(text, paths) {
6177
- let out = text;
6178
- for (const p of paths) {
6179
- const q = p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6180
- out = out.replace(new RegExp(`["']${q}["']`, "gi"), " ");
6181
- out = out.replace(new RegExp(`(?<=^|\\s)${q}(?=\\s|$)`, "gi"), " ");
6182
- }
6183
- return out.replace(/\s{2,}/g, " ").trim();
6184
- }
6185
- function buildUserMessageContent(raw, cwd) {
6186
- const trimmed = raw.trim();
6187
- if (!trimmed) return trimmed;
6188
- const candidates = collectImagePathStrings(trimmed);
6189
- const resolvedAbs = [];
6190
- const usedStrings = [];
6191
- for (const c of candidates) {
6192
- if (resolvedAbs.length >= MAX_IMAGES) break;
6193
- const abs = resolveImagePath(c, cwd);
6194
- if (!abs) continue;
6195
- try {
6196
- const st = fs14.statSync(abs);
6197
- if (st.size > MAX_IMAGE_BYTES) continue;
6198
- } catch {
6199
- continue;
6200
- }
6201
- resolvedAbs.push(abs);
6202
- usedStrings.push(c);
6203
- }
6204
- if (resolvedAbs.length === 0) {
6205
- return trimmed;
6206
- }
6207
- let textPart = stripImagePathStrings(trimmed, usedStrings);
6208
- if (!textPart) {
6209
- textPart = "(User attached image(s) only \u2014 describe, compare, or answer using the image(s).)";
6210
- }
6211
- const note = resolvedAbs.map((p) => path16.basename(p)).join(", ");
6212
- const parts = [
6213
- {
6214
- type: "text",
6215
- text: `${textPart}
6216
-
6217
- [Attached local image file(s): ${note}]`
6218
- }
6219
- ];
6220
- for (const abs of resolvedAbs) {
6221
- const buf = fs14.readFileSync(abs);
6222
- const b64 = buf.toString("base64");
6223
- const mime = mimeFor(abs);
6224
- parts.push({
6225
- type: "image_url",
6226
- image_url: {
6227
- url: `data:${mime};base64,${b64}`,
6228
- detail: "auto"
6229
- }
6230
- });
6231
- }
6232
- return parts;
6233
- }
6234
-
6235
6713
  // src/app/agent/bluma/core/bluma.ts
6236
6714
  var BluMaAgent = class {
6237
6715
  llm;
@@ -6513,7 +6991,7 @@ var BluMaAgent = class {
6513
6991
 
6514
6992
  ${editData.error.display}`;
6515
6993
  }
6516
- const filename = path17.basename(toolArgs.file_path);
6994
+ const filename = path18.basename(toolArgs.file_path);
6517
6995
  return createDiff(filename, editData.currentContent || "", editData.newContent);
6518
6996
  } catch (e) {
6519
6997
  return `An unexpected error occurred while generating the edit preview: ${e.message}`;
@@ -6771,7 +7249,7 @@ import { v4 as uuidv45 } from "uuid";
6771
7249
  import { v4 as uuidv44 } from "uuid";
6772
7250
 
6773
7251
  // src/app/agent/subagents/init/init_system_prompt.ts
6774
- import os12 from "os";
7252
+ import os13 from "os";
6775
7253
  var SYSTEM_PROMPT2 = `
6776
7254
 
6777
7255
  ### YOU ARE BluMa CLI \u2014 INIT SUBAGENT \u2014 AUTONOMOUS SENIOR SOFTWARE ENGINEER @ NOMADENGENUITY
@@ -6934,12 +7412,12 @@ Rule Summary:
6934
7412
  function getInitPrompt() {
6935
7413
  const now = /* @__PURE__ */ new Date();
6936
7414
  const collectedData = {
6937
- os_type: os12.type(),
6938
- os_version: os12.release(),
6939
- architecture: os12.arch(),
7415
+ os_type: os13.type(),
7416
+ os_version: os13.release(),
7417
+ architecture: os13.arch(),
6940
7418
  workdir: process.cwd(),
6941
7419
  shell_type: process.env.SHELL || process.env.COMSPEC || "Unknown",
6942
- username: os12.userInfo().username || "Unknown",
7420
+ username: os13.userInfo().username || "Unknown",
6943
7421
  current_date: now.toISOString().split("T")[0],
6944
7422
  // Formato YYYY-MM-DD
6945
7423
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Unknown",
@@ -7220,14 +7698,14 @@ var RouteManager = class {
7220
7698
  this.subAgents = subAgents;
7221
7699
  this.core = core;
7222
7700
  }
7223
- registerRoute(path21, handler) {
7224
- this.routeHandlers.set(path21, handler);
7701
+ registerRoute(path22, handler) {
7702
+ this.routeHandlers.set(path22, handler);
7225
7703
  }
7226
7704
  async handleRoute(payload) {
7227
7705
  const inputText = String(payload.content || "").trim();
7228
7706
  const { userContext } = payload;
7229
- for (const [path21, handler] of this.routeHandlers) {
7230
- if (inputText === path21 || inputText.startsWith(`${path21} `)) {
7707
+ for (const [path22, handler] of this.routeHandlers) {
7708
+ if (inputText === path22 || inputText.startsWith(`${path22} `)) {
7231
7709
  return handler({ content: inputText, userContext });
7232
7710
  }
7233
7711
  }
@@ -7236,7 +7714,7 @@ var RouteManager = class {
7236
7714
  };
7237
7715
 
7238
7716
  // src/app/agent/agent.ts
7239
- var globalEnvPath = path18.join(os13.homedir(), ".bluma", ".env");
7717
+ var globalEnvPath = path19.join(os14.homedir(), ".bluma", ".env");
7240
7718
  dotenv.config({ path: globalEnvPath });
7241
7719
  var Agent = class {
7242
7720
  sessionId;
@@ -7460,12 +7938,12 @@ var renderShellCommand2 = ({ args }) => {
7460
7938
  };
7461
7939
  var renderLsTool2 = ({ args }) => {
7462
7940
  const parsed = parseArgs(args);
7463
- const path21 = parsed.directory_path || ".";
7941
+ const path22 = parsed.directory_path || ".";
7464
7942
  return /* @__PURE__ */ jsxs9(Box9, { children: [
7465
7943
  /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "ls" }),
7466
7944
  /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
7467
7945
  " ",
7468
- path21
7946
+ path22
7469
7947
  ] })
7470
7948
  ] });
7471
7949
  };
@@ -7601,7 +8079,7 @@ var renderFindByName = ({ args }) => {
7601
8079
  var renderGrepSearch = ({ args }) => {
7602
8080
  const parsed = parseArgs(args);
7603
8081
  const query = parsed.query || "";
7604
- const path21 = parsed.path || ".";
8082
+ const path22 = parsed.path || ".";
7605
8083
  return /* @__PURE__ */ jsxs9(Box9, { children: [
7606
8084
  /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "grep" }),
7607
8085
  /* @__PURE__ */ jsxs9(Text9, { color: BLUMA_TERMINAL.brandMagenta, children: [
@@ -7611,7 +8089,7 @@ var renderGrepSearch = ({ args }) => {
7611
8089
  ] }),
7612
8090
  /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
7613
8091
  " ",
7614
- path21
8092
+ path22
7615
8093
  ] })
7616
8094
  ] });
7617
8095
  };
@@ -8180,10 +8658,10 @@ var ToolResultDisplayComponent = ({ toolName, result }) => {
8180
8658
  ] }),
8181
8659
  matches.slice(0, 5).map((m, i) => {
8182
8660
  const row = m;
8183
- const path21 = row.file || row.path || row.name || m;
8661
+ const path22 = row.file || row.path || row.name || m;
8184
8662
  const line = row.line;
8185
8663
  return /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
8186
- String(path21),
8664
+ String(path22),
8187
8665
  line != null ? `:${line}` : ""
8188
8666
  ] }, i);
8189
8667
  }),
@@ -8529,17 +9007,17 @@ var SlashCommands_default = SlashCommands;
8529
9007
 
8530
9008
  // src/app/agent/utils/update_check.ts
8531
9009
  import updateNotifier from "update-notifier";
8532
- import { fileURLToPath as fileURLToPath3 } from "url";
8533
- import path19 from "path";
8534
- import fs15 from "fs";
9010
+ import { fileURLToPath as fileURLToPath4 } from "url";
9011
+ import path20 from "path";
9012
+ import fs16 from "fs";
8535
9013
  var BLUMA_PACKAGE_NAME = "@nomad-e/bluma-cli";
8536
9014
  function findBlumaPackageJson(startDir) {
8537
9015
  let dir = startDir;
8538
9016
  for (let i = 0; i < 10; i++) {
8539
- const candidate = path19.join(dir, "package.json");
8540
- if (fs15.existsSync(candidate)) {
9017
+ const candidate = path20.join(dir, "package.json");
9018
+ if (fs16.existsSync(candidate)) {
8541
9019
  try {
8542
- const raw = fs15.readFileSync(candidate, "utf8");
9020
+ const raw = fs16.readFileSync(candidate, "utf8");
8543
9021
  const parsed = JSON.parse(raw);
8544
9022
  if (parsed?.name === BLUMA_PACKAGE_NAME && parsed?.version) {
8545
9023
  return { name: parsed.name, version: parsed.version };
@@ -8547,7 +9025,7 @@ function findBlumaPackageJson(startDir) {
8547
9025
  } catch {
8548
9026
  }
8549
9027
  }
8550
- const parent = path19.dirname(dir);
9028
+ const parent = path20.dirname(dir);
8551
9029
  if (parent === dir) break;
8552
9030
  dir = parent;
8553
9031
  }
@@ -8560,12 +9038,12 @@ async function checkForUpdates() {
8560
9038
  }
8561
9039
  const binPath = process.argv?.[1];
8562
9040
  let pkg = null;
8563
- if (binPath && fs15.existsSync(binPath)) {
8564
- pkg = findBlumaPackageJson(path19.dirname(binPath));
9041
+ if (binPath && fs16.existsSync(binPath)) {
9042
+ pkg = findBlumaPackageJson(path20.dirname(binPath));
8565
9043
  }
8566
9044
  if (!pkg) {
8567
- const __filename = fileURLToPath3(import.meta.url);
8568
- const __dirname2 = path19.dirname(__filename);
9045
+ const __filename = fileURLToPath4(import.meta.url);
9046
+ const __dirname2 = path20.dirname(__filename);
8569
9047
  pkg = findBlumaPackageJson(__dirname2);
8570
9048
  }
8571
9049
  if (!pkg) {
@@ -8800,6 +9278,36 @@ function trimRecentActivity(s, max = 72) {
8800
9278
  if (!t) return "";
8801
9279
  return t.length <= max ? t : `${t.slice(0, max - 1)}\u2026`;
8802
9280
  }
9281
+ function UserMessageWithOptionalImages({
9282
+ raw,
9283
+ variant
9284
+ }) {
9285
+ if (variant === "slash-img") {
9286
+ const pathStrs = collectImagePathStrings(raw);
9287
+ const stripped2 = pathStrs.length > 0 ? stripImagePathStrings(raw, pathStrs) : raw;
9288
+ const cap = stripped2.trim();
9289
+ const capDisp = cap.length > 800 ? `${cap.slice(0, 800)}\u2026` : cap;
9290
+ const fallbackDisp = raw.length > 800 ? `${raw.slice(0, 800)}\u2026` : raw;
9291
+ return /* @__PURE__ */ jsx20(ChatUserMessage, { children: pathStrs.length > 0 ? /* @__PURE__ */ jsx20(
9292
+ ChatUserImageBlock,
9293
+ {
9294
+ imageCount: pathStrs.length,
9295
+ caption: cap.length > 0 ? capDisp : null,
9296
+ captionDim: true
9297
+ }
9298
+ ) : /* @__PURE__ */ jsx20(Text19, { dimColor: true, wrap: "wrap", children: fallbackDisp }) });
9299
+ }
9300
+ const displayRaw = raw.length > 1e4 ? `${raw.substring(0, 1e4)}...` : raw;
9301
+ const paths = collectImagePathStrings(displayRaw);
9302
+ const stripped = paths.length > 0 ? stripImagePathStrings(displayRaw, paths) : displayRaw;
9303
+ return /* @__PURE__ */ jsx20(ChatUserMessage, { children: paths.length > 0 ? /* @__PURE__ */ jsx20(
9304
+ ChatUserImageBlock,
9305
+ {
9306
+ imageCount: paths.length,
9307
+ caption: stripped.trim().length > 0 ? stripped : null
9308
+ }
9309
+ ) : /* @__PURE__ */ jsx20(Text19, { wrap: "wrap", children: displayRaw }) });
9310
+ }
8803
9311
  var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
8804
9312
  const agentInstance = useRef5(null);
8805
9313
  const [history, setHistory] = useState6([]);
@@ -8823,7 +9331,7 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
8823
9331
  const workdir = process.cwd();
8824
9332
  const updateCheckRan = useRef5(false);
8825
9333
  const turnStartedAtRef = useRef5(null);
8826
- const appendExpandPreviewToHistory = useCallback2(() => {
9334
+ const appendExpandPreviewToHistory = useCallback3(() => {
8827
9335
  const p = peekLatestExpandable();
8828
9336
  setHistory((prev) => {
8829
9337
  const id = prev.length;
@@ -8871,7 +9379,7 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
8871
9379
  ];
8872
9380
  });
8873
9381
  }, [sessionId, workdir, cliVersion, recentActivityLine]);
8874
- const handleInterrupt = useCallback2(() => {
9382
+ const handleInterrupt = useCallback3(() => {
8875
9383
  if (!isProcessing) return;
8876
9384
  eventBus.emit("user_interrupt");
8877
9385
  turnStartedAtRef.current = null;
@@ -8884,7 +9392,7 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
8884
9392
  }
8885
9393
  ]);
8886
9394
  }, [isProcessing, eventBus]);
8887
- const handleSubmit = useCallback2(
9395
+ const handleSubmit = useCallback3(
8888
9396
  (text) => {
8889
9397
  if (!text || isProcessing || !agentInstance.current) return;
8890
9398
  if (/^\/img\s+/i.test(text) || /^\/image\s+/i.test(text)) {
@@ -8901,25 +9409,18 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
8901
9409
  }
8902
9410
  setIsProcessing(true);
8903
9411
  turnStartedAtRef.current = Date.now();
8904
- const shown = payload.length > 800 ? `${payload.slice(0, 800)}\u2026` : payload;
8905
9412
  setHistory((prev) => [
8906
9413
  ...prev,
8907
9414
  {
8908
9415
  id: prev.length,
8909
- component: /* @__PURE__ */ jsxs19(ChatUserMessage, { children: [
8910
- /* @__PURE__ */ jsxs19(Text19, { bold: true, color: "white", children: [
8911
- "/img",
8912
- " "
8913
- ] }),
8914
- /* @__PURE__ */ jsx20(Text19, { dimColor: true, children: shown })
8915
- ] })
9416
+ component: /* @__PURE__ */ jsx20(UserMessageWithOptionalImages, { raw: payload, variant: "slash-img" })
8916
9417
  }
8917
9418
  ]);
8918
9419
  setRecentActivityLine(`Prompt: ${trimRecentActivity(payload)}`);
8919
9420
  agentInstance.current.processTurn({ content: payload });
8920
9421
  return;
8921
9422
  }
8922
- if (text.startsWith("/")) {
9423
+ if (isSlashRoutingLine(text)) {
8923
9424
  const [cmd] = text.slice(1).trim().split(/\s+/);
8924
9425
  if (!cmd) {
8925
9426
  setIsProcessing(false);
@@ -9017,12 +9518,11 @@ Please use command_status to check the result and report back to the user.`;
9017
9518
  }
9018
9519
  setIsProcessing(true);
9019
9520
  turnStartedAtRef.current = Date.now();
9020
- const displayText = text.length > 1e4 ? text.substring(0, 1e4) + "..." : text;
9021
9521
  setHistory((prev) => [
9022
9522
  ...prev,
9023
9523
  {
9024
9524
  id: prev.length,
9025
- component: /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsx20(Text19, { children: displayText }) })
9525
+ component: /* @__PURE__ */ jsx20(UserMessageWithOptionalImages, { raw: text, variant: "plain" })
9026
9526
  }
9027
9527
  ]);
9028
9528
  setRecentActivityLine(`You: ${trimRecentActivity(text)}`);
@@ -9030,7 +9530,7 @@ Please use command_status to check the result and report back to the user.`;
9030
9530
  },
9031
9531
  [isProcessing]
9032
9532
  );
9033
- const handleConfirmation = useCallback2(
9533
+ const handleConfirmation = useCallback3(
9034
9534
  (decision, toolCalls) => {
9035
9535
  if (!agentInstance.current) return;
9036
9536
  setPendingConfirmation(null);
@@ -9076,12 +9576,11 @@ Please use command_status to check the result and report back to the user.`;
9076
9576
  if (t == null) return;
9077
9577
  turnStartedAtRef.current = null;
9078
9578
  const ms = Date.now() - t;
9079
- const sec = ms < 1e4 ? (ms / 1e3).toFixed(1) : String(Math.round(ms / 1e3));
9080
9579
  setHistory((prev) => [
9081
9580
  ...prev,
9082
9581
  {
9083
9582
  id: prev.length,
9084
- component: /* @__PURE__ */ jsx20(ChatTurnDuration, { secondsFormatted: sec })
9583
+ component: /* @__PURE__ */ jsx20(ChatTurnDuration, { durationMs: ms })
9085
9584
  }
9086
9585
  ]);
9087
9586
  };
@@ -9205,10 +9704,9 @@ Please use command_status to check the result and report back to the user.`;
9205
9704
  if (t != null) {
9206
9705
  turnStartedAtRef.current = null;
9207
9706
  const ms = Date.now() - t;
9208
- const sec = ms < 1e4 ? (ms / 1e3).toFixed(1) : String(Math.round(ms / 1e3));
9209
9707
  next.push({
9210
9708
  id: next.length,
9211
- component: /* @__PURE__ */ jsx20(ChatTurnDuration, { secondsFormatted: sec })
9709
+ component: /* @__PURE__ */ jsx20(ChatTurnDuration, { durationMs: ms })
9212
9710
  });
9213
9711
  }
9214
9712
  }
@@ -9373,9 +9871,9 @@ async function runAgentMode() {
9373
9871
  try {
9374
9872
  if (inputFileIndex !== -1 && args[inputFileIndex + 1]) {
9375
9873
  const filePath = args[inputFileIndex + 1];
9376
- rawPayload = fs16.readFileSync(filePath, "utf-8");
9874
+ rawPayload = fs17.readFileSync(filePath, "utf-8");
9377
9875
  } else {
9378
- rawPayload = fs16.readFileSync(0, "utf-8");
9876
+ rawPayload = fs17.readFileSync(0, "utf-8");
9379
9877
  }
9380
9878
  } catch (err) {
9381
9879
  writeJsonl({
@@ -9543,9 +10041,9 @@ async function runAgentMode() {
9543
10041
  }
9544
10042
  function readCliPackageVersion() {
9545
10043
  try {
9546
- const base = path20.dirname(fileURLToPath4(import.meta.url));
9547
- const pkgPath = path20.join(base, "..", "package.json");
9548
- const j = JSON.parse(fs16.readFileSync(pkgPath, "utf8"));
10044
+ const base = path21.dirname(fileURLToPath5(import.meta.url));
10045
+ const pkgPath = path21.join(base, "..", "package.json");
10046
+ const j = JSON.parse(fs17.readFileSync(pkgPath, "utf8"));
9549
10047
  return String(j.version || "0.0.0");
9550
10048
  } catch {
9551
10049
  return "0.0.0";