@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.
- package/dist/main.js +924 -426
- 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
|
|
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 =
|
|
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 =
|
|
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
|
|
335
|
-
import
|
|
336
|
-
import { fileURLToPath as
|
|
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
|
|
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 {
|
|
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(
|
|
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", {
|
|
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
|
-
|
|
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
|
|
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
|
|
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({
|
|
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__ */
|
|
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
|
|
1730
|
-
import
|
|
2320
|
+
import path19 from "path";
|
|
2321
|
+
import os14 from "os";
|
|
1731
2322
|
|
|
1732
2323
|
// src/app/agent/tool_invoker.ts
|
|
1733
|
-
import { promises as
|
|
1734
|
-
import
|
|
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
|
|
1739
|
-
import
|
|
1740
|
-
import { promises as
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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) && !
|
|
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 =
|
|
2040
|
-
await
|
|
2041
|
-
await
|
|
2042
|
-
const relativePath =
|
|
2043
|
-
const filename =
|
|
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
|
|
2098
|
-
import
|
|
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 =
|
|
2127
|
-
if (!(await
|
|
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
|
|
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 =
|
|
2140
|
-
const posixPath = fullPath.split(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2241
|
-
import * as
|
|
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
|
|
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 (
|
|
2251
|
-
const data =
|
|
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 =
|
|
2265
|
-
if (!
|
|
2266
|
-
|
|
2855
|
+
const dir = path7.dirname(filePath);
|
|
2856
|
+
if (!fs8.existsSync(dir)) {
|
|
2857
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
2267
2858
|
}
|
|
2268
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
2506
|
-
const relativePath =
|
|
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 =
|
|
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
|
|
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 =
|
|
2707
|
-
const basename =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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,
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
3173
|
-
import { promises as
|
|
3174
|
-
import
|
|
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 =
|
|
3180
|
-
const baseDir =
|
|
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 =
|
|
3183
|
-
await
|
|
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 =
|
|
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
|
|
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 =
|
|
3281
|
-
await
|
|
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 =
|
|
3301
|
-
const content = await
|
|
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
|
|
3671
|
-
import * as
|
|
3672
|
-
import
|
|
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 =
|
|
3681
|
-
const legacyPath =
|
|
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 (
|
|
3685
|
-
raw =
|
|
3686
|
-
} else if (
|
|
3687
|
-
raw =
|
|
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
|
|
4316
|
+
return path12.join(os7.homedir(), ".bluma", "coding_memory.json");
|
|
3726
4317
|
}
|
|
3727
4318
|
function getLegacyMemoryFilePath() {
|
|
3728
|
-
return
|
|
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 =
|
|
4329
|
+
const legacyDistinct = path12.resolve(legacy) !== path12.resolve(filePath);
|
|
3739
4330
|
const readIntoStore = (p) => {
|
|
3740
|
-
const raw =
|
|
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 (
|
|
4338
|
+
if (fs10.existsSync(filePath)) {
|
|
3748
4339
|
readIntoStore(filePath);
|
|
3749
4340
|
}
|
|
3750
|
-
if (memoryStore.length === 0 && legacyDistinct &&
|
|
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 =
|
|
3765
|
-
if (!
|
|
3766
|
-
|
|
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
|
-
|
|
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 =
|
|
3971
|
-
const configPath =
|
|
3972
|
-
const fileContent = await
|
|
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
|
|
4034
|
-
import
|
|
4035
|
-
import
|
|
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 =
|
|
4063
|
-
const defaultConfigPath =
|
|
4064
|
-
const userConfigPath =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
4238
|
-
import
|
|
4239
|
-
import { promises as
|
|
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
|
|
4866
|
+
return path15.join(os9.homedir(), p.slice(1));
|
|
4276
4867
|
}
|
|
4277
4868
|
return p;
|
|
4278
4869
|
}
|
|
4279
4870
|
function getPreferredAppDir() {
|
|
4280
|
-
const fixed =
|
|
4281
|
-
return
|
|
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 =
|
|
4290
|
-
await
|
|
4880
|
+
const dir = path15.dirname(dest);
|
|
4881
|
+
await fs13.mkdir(dir, { recursive: true }).catch(() => {
|
|
4291
4882
|
});
|
|
4292
|
-
await
|
|
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
|
|
4306
|
-
const data = await
|
|
4307
|
-
const dir =
|
|
4308
|
-
await
|
|
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
|
|
4311
|
-
await
|
|
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 =
|
|
4325
|
-
await
|
|
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 =
|
|
4921
|
+
const sessionFile = path15.join(sessionDir, `${sessionId}.json`);
|
|
4331
4922
|
try {
|
|
4332
|
-
await
|
|
4333
|
-
const fileContent = await
|
|
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
|
|
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 =
|
|
4359
|
-
await
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4424
|
-
import
|
|
4425
|
-
import
|
|
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
|
|
4430
|
-
import
|
|
4431
|
-
import
|
|
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 =
|
|
4440
|
-
this.globalSkillsDir =
|
|
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
|
|
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
|
-
|
|
4456
|
-
|
|
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
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
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
|
-
|
|
4466
|
-
|
|
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
|
|
4471
|
-
|
|
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
|
-
|
|
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:
|
|
4505
|
-
bundledPath:
|
|
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 (!
|
|
5131
|
+
if (!fs14.existsSync(dir)) return [];
|
|
4514
5132
|
try {
|
|
4515
|
-
return
|
|
4516
|
-
const fullPath =
|
|
4517
|
-
return
|
|
4518
|
-
}).map((d) => this.loadMetadataFromPath(
|
|
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 (!
|
|
5142
|
+
if (!fs14.existsSync(skillPath)) return null;
|
|
4525
5143
|
try {
|
|
4526
|
-
const raw =
|
|
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 =
|
|
4549
|
-
const projectPath =
|
|
4550
|
-
const globalPath =
|
|
4551
|
-
const existsBundled =
|
|
4552
|
-
const existsProject =
|
|
4553
|
-
const existsGlobal =
|
|
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 =
|
|
5210
|
+
const raw = fs14.readFileSync(skillPath, "utf-8");
|
|
4593
5211
|
const parsed = this.parseFrontmatter(raw);
|
|
4594
|
-
const skillDir =
|
|
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(
|
|
4604
|
-
scripts: this.scanAssets(
|
|
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 (!
|
|
5229
|
+
if (!fs14.existsSync(dir)) return [];
|
|
4612
5230
|
try {
|
|
4613
|
-
return
|
|
4614
|
-
const fp =
|
|
4615
|
-
return
|
|
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:
|
|
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 =
|
|
4676
|
-
const projectPath =
|
|
4677
|
-
const globalPath =
|
|
4678
|
-
return
|
|
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 (
|
|
4738
|
-
if (
|
|
4739
|
-
if (
|
|
4740
|
-
if (
|
|
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 =
|
|
5366
|
+
const files = fs15.readdirSync(dir);
|
|
4749
5367
|
if (files.includes("package.json")) {
|
|
4750
|
-
const pkg = JSON.parse(
|
|
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 =
|
|
4770
|
-
if (
|
|
4771
|
-
const pkg = JSON.parse(
|
|
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 (
|
|
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 =
|
|
4789
|
-
if (
|
|
4790
|
-
const pkg = JSON.parse(
|
|
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:
|
|
5268
|
-
os_version:
|
|
5269
|
-
architecture:
|
|
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:
|
|
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 =
|
|
5458
|
-
return
|
|
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
|
|
5668
|
-
var
|
|
5669
|
-
var
|
|
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 ??
|
|
5695
|
-
const compressThreshold = options?.compressThreshold ??
|
|
5696
|
-
const keepRecentTurns = options?.keepRecentTurns ??
|
|
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
|
|
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 =
|
|
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:${
|
|
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 =
|
|
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 =
|
|
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
|
|
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:
|
|
6938
|
-
os_version:
|
|
6939
|
-
architecture:
|
|
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:
|
|
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(
|
|
7224
|
-
this.routeHandlers.set(
|
|
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 [
|
|
7230
|
-
if (inputText ===
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
8533
|
-
import
|
|
8534
|
-
import
|
|
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 =
|
|
8540
|
-
if (
|
|
9017
|
+
const candidate = path20.join(dir, "package.json");
|
|
9018
|
+
if (fs16.existsSync(candidate)) {
|
|
8541
9019
|
try {
|
|
8542
|
-
const raw =
|
|
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 =
|
|
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 &&
|
|
8564
|
-
pkg = findBlumaPackageJson(
|
|
9041
|
+
if (binPath && fs16.existsSync(binPath)) {
|
|
9042
|
+
pkg = findBlumaPackageJson(path20.dirname(binPath));
|
|
8565
9043
|
}
|
|
8566
9044
|
if (!pkg) {
|
|
8567
|
-
const __filename =
|
|
8568
|
-
const __dirname2 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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__ */
|
|
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
|
|
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(
|
|
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 =
|
|
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, {
|
|
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, {
|
|
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 =
|
|
9874
|
+
rawPayload = fs17.readFileSync(filePath, "utf-8");
|
|
9377
9875
|
} else {
|
|
9378
|
-
rawPayload =
|
|
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 =
|
|
9547
|
-
const pkgPath =
|
|
9548
|
-
const j = JSON.parse(
|
|
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";
|