@nomad-e/bluma-cli 0.0.32 → 0.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/bluma-mcp.json +0 -25
- package/dist/config/native_tools.json +3 -3
- package/dist/main.js +467 -455
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -6,7 +6,7 @@ import { EventEmitter as EventEmitter2 } from "events";
|
|
|
6
6
|
import { v4 as uuidv42 } from "uuid";
|
|
7
7
|
|
|
8
8
|
// src/app/ui/App.tsx
|
|
9
|
-
import { useState as
|
|
9
|
+
import { useState as useState5, useEffect as useEffect4, useRef as useRef2, useCallback, memo as memo4 } from "react";
|
|
10
10
|
import { Box as Box15, Text as Text14, Static } from "ink";
|
|
11
11
|
|
|
12
12
|
// src/app/ui/layout.tsx
|
|
@@ -115,7 +115,7 @@ function inputReducer(state, action, viewWidth) {
|
|
|
115
115
|
};
|
|
116
116
|
switch (action.type) {
|
|
117
117
|
case "INPUT": {
|
|
118
|
-
const cleanInput = action.payload.replace(/(
|
|
118
|
+
const cleanInput = action.payload.replace(/(||)/gm, "");
|
|
119
119
|
const newText = state.text.slice(0, state.cursorPosition) + cleanInput + state.text.slice(state.cursorPosition);
|
|
120
120
|
const newCursorPosition = state.cursorPosition + cleanInput.length;
|
|
121
121
|
const newViewStart = adjustView(newCursorPosition, state.viewStart);
|
|
@@ -140,6 +140,14 @@ function inputReducer(state, action, viewWidth) {
|
|
|
140
140
|
case "SUBMIT": {
|
|
141
141
|
return { text: "", cursorPosition: 0, viewStart: 0 };
|
|
142
142
|
}
|
|
143
|
+
case "SET": {
|
|
144
|
+
const t = action.payload.text.replace(/(||)/gm, "");
|
|
145
|
+
const moveToEnd = action.payload.moveCursorToEnd ?? true;
|
|
146
|
+
const newText = t;
|
|
147
|
+
const newCursorPosition = moveToEnd ? newText.length : Math.min(state.cursorPosition, newText.length);
|
|
148
|
+
const newViewStart = adjustView(newCursorPosition, 0);
|
|
149
|
+
return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
|
|
150
|
+
}
|
|
143
151
|
default:
|
|
144
152
|
return state;
|
|
145
153
|
}
|
|
@@ -170,6 +178,13 @@ var useCustomInput = ({ onSubmit, viewWidth, isReadOnly, onInterrupt }) => {
|
|
|
170
178
|
return dispatch({ type: "INPUT", payload: input });
|
|
171
179
|
}
|
|
172
180
|
if (key.return) {
|
|
181
|
+
if (globalThis.__BLUMA_AT_OPEN__) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (globalThis.__BLUMA_SUPPRESS_SUBMIT__) {
|
|
185
|
+
globalThis.__BLUMA_SUPPRESS_SUBMIT__ = false;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
173
188
|
if (state.text.trim().length > 0) {
|
|
174
189
|
onSubmit(state.text);
|
|
175
190
|
dispatch({ type: "SUBMIT" });
|
|
@@ -188,12 +203,20 @@ var useCustomInput = ({ onSubmit, viewWidth, isReadOnly, onInterrupt }) => {
|
|
|
188
203
|
return {
|
|
189
204
|
text: state.text,
|
|
190
205
|
cursorPosition: state.cursorPosition,
|
|
191
|
-
viewStart: state.viewStart
|
|
206
|
+
viewStart: state.viewStart,
|
|
207
|
+
setText: (t, pos) => {
|
|
208
|
+
if (typeof pos === "number") {
|
|
209
|
+
dispatch({ type: "SET", payload: { text: t, moveCursorToEnd: false, cursorPosition: pos } });
|
|
210
|
+
} else {
|
|
211
|
+
dispatch({ type: "SET", payload: { text: t, moveCursorToEnd: true } });
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
setCursor: (pos) => dispatch({ type: "SET_CURSOR", payload: pos })
|
|
192
215
|
};
|
|
193
216
|
};
|
|
194
217
|
|
|
195
218
|
// src/app/ui/components/InputPrompt.tsx
|
|
196
|
-
import { useEffect, useMemo, useState } from "react";
|
|
219
|
+
import { useEffect as useEffect2, useMemo, useState as useState2 } from "react";
|
|
197
220
|
import { EventEmitter } from "events";
|
|
198
221
|
|
|
199
222
|
// src/app/ui/utils/slashRegistry.ts
|
|
@@ -242,16 +265,160 @@ var filterSlashCommands = (query) => {
|
|
|
242
265
|
return scored.map((s) => s.cmd);
|
|
243
266
|
};
|
|
244
267
|
|
|
268
|
+
// src/app/ui/hooks/useAtCompletion.ts
|
|
269
|
+
import { useEffect, useRef, useState } from "react";
|
|
270
|
+
import fs from "fs";
|
|
271
|
+
import path from "path";
|
|
272
|
+
var MAX_RESULTS = 50;
|
|
273
|
+
var DEFAULT_RECURSIVE_DEPTH = 2;
|
|
274
|
+
function listPathSuggestions(baseDir, pattern) {
|
|
275
|
+
const raw = pattern || "";
|
|
276
|
+
const patternEndsWithSlash = raw.endsWith("/");
|
|
277
|
+
const relDir = raw.replace(/^\/+|\/+$/g, "");
|
|
278
|
+
const filterPrefix = patternEndsWithSlash ? "" : relDir.split("/").slice(-1)[0] || "";
|
|
279
|
+
const listDir = path.resolve(baseDir, relDir || ".");
|
|
280
|
+
const results = [];
|
|
281
|
+
const IGNORED_DIRS = ["node_modules", ".git", ".venv", "dist", "build"];
|
|
282
|
+
const IGNORED_EXTS = [".pyc", ".class", ".o", ".map", ".log", ".tmp"];
|
|
283
|
+
function isIgnoredName(name) {
|
|
284
|
+
if (!name) return false;
|
|
285
|
+
if (IGNORED_DIRS.includes(name)) return true;
|
|
286
|
+
if (name.startsWith(".")) return true;
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
function isIgnoredFile(name) {
|
|
290
|
+
if (!name) return false;
|
|
291
|
+
for (const e of IGNORED_EXTS) if (name.endsWith(e)) return true;
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
function pushEntry(entryPath, label, isDir) {
|
|
295
|
+
if (results.length >= MAX_RESULTS) return;
|
|
296
|
+
const clean = label.split(path.sep).join("/").replace(/[]+/g, "");
|
|
297
|
+
results.push({ label: clean + (isDir ? "/" : ""), fullPath: entryPath, isDir });
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
if (raw.length === 0 || patternEndsWithSlash) {
|
|
301
|
+
const queue = [{ dir: listDir, depth: 0, rel: relDir }];
|
|
302
|
+
while (queue.length && results.length < MAX_RESULTS) {
|
|
303
|
+
const node = queue.shift();
|
|
304
|
+
try {
|
|
305
|
+
const entries = fs.readdirSync(node.dir, { withFileTypes: true });
|
|
306
|
+
for (const entry of entries) {
|
|
307
|
+
if (isIgnoredName(entry.name)) continue;
|
|
308
|
+
const entryAbs = path.join(node.dir, entry.name);
|
|
309
|
+
const entryRel = node.rel ? path.posix.join(node.rel, entry.name) : entry.name;
|
|
310
|
+
if (entryRel.split("/").includes("node_modules")) continue;
|
|
311
|
+
if (!entry.isDirectory() && isIgnoredFile(entry.name)) continue;
|
|
312
|
+
pushEntry(entryAbs, entryRel, entry.isDirectory());
|
|
313
|
+
if (entry.isDirectory() && node.depth < DEFAULT_RECURSIVE_DEPTH) {
|
|
314
|
+
queue.push({ dir: entryAbs, depth: node.depth + 1, rel: node.rel ? node.rel + "/" + entry.name : entry.name + "/" });
|
|
315
|
+
}
|
|
316
|
+
if (results.length >= MAX_RESULTS) break;
|
|
317
|
+
}
|
|
318
|
+
} catch (e) {
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
} else {
|
|
322
|
+
const entries = fs.readdirSync(listDir, { withFileTypes: true });
|
|
323
|
+
for (const entry of entries) {
|
|
324
|
+
if (filterPrefix && !entry.name.startsWith(filterPrefix)) continue;
|
|
325
|
+
if (isIgnoredName(entry.name)) continue;
|
|
326
|
+
if (!entry.isDirectory() && isIgnoredFile(entry.name)) continue;
|
|
327
|
+
const entryAbs = path.join(listDir, entry.name);
|
|
328
|
+
const label = relDir ? path.posix.join(relDir, entry.name) : entry.name;
|
|
329
|
+
pushEntry(entryAbs, label, entry.isDirectory());
|
|
330
|
+
if (results.length >= MAX_RESULTS) break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
} catch (e) {
|
|
334
|
+
}
|
|
335
|
+
return results.slice(0, MAX_RESULTS);
|
|
336
|
+
}
|
|
337
|
+
function useAtCompletion({
|
|
338
|
+
cwd,
|
|
339
|
+
text,
|
|
340
|
+
cursorPosition,
|
|
341
|
+
setText
|
|
342
|
+
}) {
|
|
343
|
+
const [open, setOpen] = useState(false);
|
|
344
|
+
const [selected, setSelected] = useState(0);
|
|
345
|
+
const [suggestions, setSuggestions] = useState([]);
|
|
346
|
+
const lastQuery = useRef("");
|
|
347
|
+
function scanForAt(text2, pos) {
|
|
348
|
+
const before = text2.slice(0, pos);
|
|
349
|
+
const m = before.match(/@([\w\/.\-_]*)$/);
|
|
350
|
+
if (!m) return { pattern: null, insertStart: -1 };
|
|
351
|
+
return { pattern: m[1] || "", insertStart: m.index + 1 };
|
|
352
|
+
}
|
|
353
|
+
function update(newText, newCursor) {
|
|
354
|
+
const res = scanForAt(newText, newCursor);
|
|
355
|
+
if (res.pattern !== null && res.pattern.length >= 0) {
|
|
356
|
+
setOpen(true);
|
|
357
|
+
globalThis.__BLUMA_AT_OPEN__ = true;
|
|
358
|
+
const suggs = listPathSuggestions(cwd, res.pattern);
|
|
359
|
+
setSuggestions(suggs);
|
|
360
|
+
setSelected(0);
|
|
361
|
+
lastQuery.current = res.pattern;
|
|
362
|
+
} else {
|
|
363
|
+
setOpen(false);
|
|
364
|
+
globalThis.__BLUMA_AT_OPEN__ = false;
|
|
365
|
+
setSuggestions([]);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
useEffect(() => {
|
|
369
|
+
update(text, cursorPosition);
|
|
370
|
+
}, [text, cursorPosition, cwd]);
|
|
371
|
+
function insertAtSelection() {
|
|
372
|
+
if (!open || !suggestions[selected]) return;
|
|
373
|
+
const res = scanForAt(text, cursorPosition);
|
|
374
|
+
if (!res || res.insertStart < 0) return;
|
|
375
|
+
let chosen = suggestions[selected].label;
|
|
376
|
+
const isDir = suggestions[selected].isDir;
|
|
377
|
+
chosen = chosen.replace(/\\/g, "/").replace(/\|/g, "");
|
|
378
|
+
let insertVal = chosen;
|
|
379
|
+
if (insertVal.includes("/")) {
|
|
380
|
+
insertVal = insertVal.replace(/\/+$/g, "");
|
|
381
|
+
const parts = insertVal.split("/");
|
|
382
|
+
insertVal = parts[parts.length - 1];
|
|
383
|
+
}
|
|
384
|
+
if (isDir && !insertVal.endsWith("/")) insertVal = insertVal + "/";
|
|
385
|
+
const pattern = res.pattern || "";
|
|
386
|
+
const lastSlash = pattern.lastIndexOf("/");
|
|
387
|
+
const segmentOffset = lastSlash >= 0 ? lastSlash + 1 : 0;
|
|
388
|
+
const segmentStart = res.insertStart + segmentOffset;
|
|
389
|
+
const before = text.slice(0, segmentStart);
|
|
390
|
+
const after = text.slice(cursorPosition);
|
|
391
|
+
const newText = before + insertVal + after;
|
|
392
|
+
setText(newText + (isDir ? "" : " "), true);
|
|
393
|
+
if (isDir) {
|
|
394
|
+
setOpen(false);
|
|
395
|
+
setSuggestions([]);
|
|
396
|
+
setTimeout(() => {
|
|
397
|
+
setOpen(true);
|
|
398
|
+
update(newText, newText.length);
|
|
399
|
+
}, 0);
|
|
400
|
+
} else {
|
|
401
|
+
setOpen(false);
|
|
402
|
+
setSuggestions([]);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function close() {
|
|
406
|
+
setOpen(false);
|
|
407
|
+
setSuggestions([]);
|
|
408
|
+
}
|
|
409
|
+
return { open, suggestions, selected, setSelected, insertAtSelection, close, update };
|
|
410
|
+
}
|
|
411
|
+
|
|
245
412
|
// src/app/ui/components/InputPrompt.tsx
|
|
246
413
|
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
247
414
|
var uiEventBus = global.__bluma_ui_eventbus__ || new EventEmitter();
|
|
248
415
|
global.__bluma_ui_eventbus__ = uiEventBus;
|
|
249
416
|
var InputPrompt = ({ onSubmit, isReadOnly, onInterrupt, disableWhileProcessing = false }) => {
|
|
250
417
|
const { stdout } = useStdout();
|
|
251
|
-
const [viewWidth, setViewWidth] =
|
|
252
|
-
const [slashOpen, setSlashOpen] =
|
|
253
|
-
const [slashIndex, setSlashIndex] =
|
|
254
|
-
|
|
418
|
+
const [viewWidth, setViewWidth] = useState2(() => stdout.columns - 6);
|
|
419
|
+
const [slashOpen, setSlashOpen] = useState2(false);
|
|
420
|
+
const [slashIndex, setSlashIndex] = useState2(0);
|
|
421
|
+
useEffect2(() => {
|
|
255
422
|
const onResize = () => setViewWidth(stdout.columns - 6);
|
|
256
423
|
stdout.on("resize", onResize);
|
|
257
424
|
return () => {
|
|
@@ -263,7 +430,7 @@ var InputPrompt = ({ onSubmit, isReadOnly, onInterrupt, disableWhileProcessing =
|
|
|
263
430
|
if (isReadOnly) {
|
|
264
431
|
if (trimmed.length > 0) {
|
|
265
432
|
const payload = trimmed;
|
|
266
|
-
uiEventBus.emit("
|
|
433
|
+
uiEventBus.emit("user_overlay", { kind: "message", payload, ts: Date.now() });
|
|
267
434
|
return;
|
|
268
435
|
}
|
|
269
436
|
return;
|
|
@@ -271,9 +438,11 @@ var InputPrompt = ({ onSubmit, isReadOnly, onInterrupt, disableWhileProcessing =
|
|
|
271
438
|
onSubmit(value);
|
|
272
439
|
};
|
|
273
440
|
const effectiveReadOnly = isReadOnly;
|
|
274
|
-
const { text, cursorPosition, viewStart } = useCustomInput({
|
|
441
|
+
const { text, cursorPosition, viewStart, setText } = useCustomInput({
|
|
442
|
+
// Sobrepõe a lógica padrão: nunca submete se autocomplete aberto
|
|
275
443
|
onSubmit: (value) => {
|
|
276
444
|
if (disableWhileProcessing && isReadOnly) return;
|
|
445
|
+
if (pathAutocomplete.open) return;
|
|
277
446
|
permissiveOnSubmit(value);
|
|
278
447
|
},
|
|
279
448
|
viewWidth,
|
|
@@ -294,7 +463,7 @@ var InputPrompt = ({ onSubmit, isReadOnly, onInterrupt, disableWhileProcessing =
|
|
|
294
463
|
if (!slashQuery) return [];
|
|
295
464
|
return filterSlashCommands(slashQuery);
|
|
296
465
|
}, [slashQuery]);
|
|
297
|
-
|
|
466
|
+
useEffect2(() => {
|
|
298
467
|
if (isReadOnly) {
|
|
299
468
|
setSlashOpen(false);
|
|
300
469
|
return;
|
|
@@ -317,12 +486,51 @@ var InputPrompt = ({ onSubmit, isReadOnly, onInterrupt, disableWhileProcessing =
|
|
|
317
486
|
if (choice) {
|
|
318
487
|
const cmd = choice.name;
|
|
319
488
|
setSlashOpen(false);
|
|
320
|
-
|
|
489
|
+
try {
|
|
490
|
+
setText(`${cmd} `, true);
|
|
491
|
+
} catch (e) {
|
|
492
|
+
permissiveOnSubmit(`${cmd} `);
|
|
493
|
+
}
|
|
321
494
|
}
|
|
322
495
|
} else if (key.escape) {
|
|
323
496
|
setSlashOpen(false);
|
|
324
497
|
}
|
|
325
498
|
}, { isActive: slashOpen });
|
|
499
|
+
const cwd = process.cwd();
|
|
500
|
+
const pathAutocomplete = useAtCompletion({ cwd, text, cursorPosition, setText });
|
|
501
|
+
useInput2((input, key) => {
|
|
502
|
+
if (pathAutocomplete.open) {
|
|
503
|
+
if (key.downArrow) {
|
|
504
|
+
pathAutocomplete.setSelected((i) => Math.min(i + 1, Math.max(0, pathAutocomplete.suggestions.length - 1)));
|
|
505
|
+
return;
|
|
506
|
+
} else if (key.upArrow) {
|
|
507
|
+
pathAutocomplete.setSelected((i) => Math.max(i - 1, 0));
|
|
508
|
+
return;
|
|
509
|
+
} else if (key.return || key.tab) {
|
|
510
|
+
const selected = pathAutocomplete.suggestions[pathAutocomplete.selected];
|
|
511
|
+
if (selected) {
|
|
512
|
+
const before = text.slice(0, cursorPosition);
|
|
513
|
+
const m = before.match(/@([\w\/.\-_]*)$/);
|
|
514
|
+
if (m) {
|
|
515
|
+
globalThis.__BLUMA_SUPPRESS_SUBMIT__ = true;
|
|
516
|
+
pathAutocomplete.insertAtSelection();
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return;
|
|
520
|
+
} else if (key.escape) {
|
|
521
|
+
pathAutocomplete.close();
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
}, { isActive: true });
|
|
527
|
+
function canSubmitGivenCursor() {
|
|
528
|
+
if (visibleCursorPosition < visibleText.length) {
|
|
529
|
+
return visibleText[visibleCursorPosition] === " ";
|
|
530
|
+
} else {
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
326
534
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
327
535
|
disableWhileProcessing ? (
|
|
328
536
|
// Modo bloqueado visualmente, mantendo hooks estáveis
|
|
@@ -343,6 +551,22 @@ var InputPrompt = ({ onSubmit, isReadOnly, onInterrupt, disableWhileProcessing =
|
|
|
343
551
|
/* @__PURE__ */ jsx2(Text2, { inverse: true, children: cursorGlyph }),
|
|
344
552
|
showPlaceholder ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: placeholder }) : /* @__PURE__ */ jsx2(Text2, { children: textAfterCursor })
|
|
345
553
|
] }) }),
|
|
554
|
+
pathAutocomplete.open && pathAutocomplete.suggestions.length > 0 && (() => {
|
|
555
|
+
const VISIBLE = 7;
|
|
556
|
+
const total = pathAutocomplete.suggestions.length;
|
|
557
|
+
const sel = Math.max(0, Math.min(pathAutocomplete.selected, total - 1));
|
|
558
|
+
let start = Math.max(0, sel - Math.floor(VISIBLE / 2));
|
|
559
|
+
if (start + VISIBLE > total) start = Math.max(0, total - VISIBLE);
|
|
560
|
+
const windowItems = pathAutocomplete.suggestions.slice(start, start + VISIBLE);
|
|
561
|
+
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, height: Math.min(VISIBLE, total), overflowY: "auto", children: windowItems.map((s, idx) => {
|
|
562
|
+
const realIdx = start + idx;
|
|
563
|
+
const isSelected = realIdx === pathAutocomplete.selected;
|
|
564
|
+
return /* @__PURE__ */ jsxs2(Box2, { paddingLeft: 1, paddingY: 0, children: [
|
|
565
|
+
/* @__PURE__ */ jsx2(Text2, { color: isSelected ? "cyan" : "gray", children: isSelected ? "\u276F " : " " }),
|
|
566
|
+
/* @__PURE__ */ jsx2(Text2, { color: isSelected ? "cyan" : "white", bold: isSelected, dimColor: !isSelected, children: s.label })
|
|
567
|
+
] }, s.fullPath);
|
|
568
|
+
}) });
|
|
569
|
+
})(),
|
|
346
570
|
slashOpen && slashSuggestions.length > 0 && /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, children: slashSuggestions.map((s, idx) => {
|
|
347
571
|
const isSelected = idx === slashIndex;
|
|
348
572
|
return /* @__PURE__ */ jsxs2(Box2, { paddingLeft: 1, paddingY: 0, children: [
|
|
@@ -369,7 +593,7 @@ var InputPrompt = ({ onSubmit, isReadOnly, onInterrupt, disableWhileProcessing =
|
|
|
369
593
|
import { Box as Box6, Text as Text6 } from "ink";
|
|
370
594
|
|
|
371
595
|
// src/app/ui/InteractiveMenu.tsx
|
|
372
|
-
import { useState as
|
|
596
|
+
import { useState as useState3, memo } from "react";
|
|
373
597
|
import { Box as Box3, Text as Text3, useInput as useInput3 } from "ink";
|
|
374
598
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
375
599
|
var InteractiveMenuComponent = ({ onDecision }) => {
|
|
@@ -378,7 +602,7 @@ var InteractiveMenuComponent = ({ onDecision }) => {
|
|
|
378
602
|
{ label: "2. No, cancel this command", value: "decline" },
|
|
379
603
|
{ label: "3. Always allow this type of command", value: "accept_always" }
|
|
380
604
|
];
|
|
381
|
-
const [selectedOption, setSelectedOption] =
|
|
605
|
+
const [selectedOption, setSelectedOption] = useState3(0);
|
|
382
606
|
useInput3((input, key) => {
|
|
383
607
|
if (key.upArrow) {
|
|
384
608
|
setSelectedOption((prev) => prev > 0 ? prev - 1 : options.length - 1);
|
|
@@ -419,7 +643,7 @@ var InteractiveMenu = memo(InteractiveMenuComponent);
|
|
|
419
643
|
|
|
420
644
|
// src/app/ui/components/promptRenderers.tsx
|
|
421
645
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
422
|
-
import
|
|
646
|
+
import path2 from "path";
|
|
423
647
|
|
|
424
648
|
// src/app/ui/components/SimpleDiff.tsx
|
|
425
649
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
@@ -458,7 +682,7 @@ var SimpleDiff = ({ text, maxHeight }) => {
|
|
|
458
682
|
// src/app/ui/components/promptRenderers.tsx
|
|
459
683
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
460
684
|
var getBasePath = (filePath) => {
|
|
461
|
-
return
|
|
685
|
+
return path2.basename(filePath);
|
|
462
686
|
};
|
|
463
687
|
var renderShellCommand = ({
|
|
464
688
|
toolCall
|
|
@@ -626,12 +850,12 @@ var ConfirmationPrompt = ({ toolCalls, preview, onDecision }) => {
|
|
|
626
850
|
// src/app/agent/agent.ts
|
|
627
851
|
import OpenAI from "openai";
|
|
628
852
|
import * as dotenv from "dotenv";
|
|
629
|
-
import
|
|
853
|
+
import path9 from "path";
|
|
630
854
|
import os6 from "os";
|
|
631
855
|
|
|
632
856
|
// src/app/agent/tool_invoker.ts
|
|
633
|
-
import { promises as
|
|
634
|
-
import
|
|
857
|
+
import { promises as fs6 } from "fs";
|
|
858
|
+
import path5 from "path";
|
|
635
859
|
import { fileURLToPath } from "url";
|
|
636
860
|
|
|
637
861
|
// src/app/agent/tools/natives/shell_command.ts
|
|
@@ -699,8 +923,8 @@ ${stderr.trim()}`.trim();
|
|
|
699
923
|
}
|
|
700
924
|
|
|
701
925
|
// src/app/agent/tools/natives/edit.ts
|
|
702
|
-
import
|
|
703
|
-
import { promises as
|
|
926
|
+
import path3 from "path";
|
|
927
|
+
import { promises as fs2 } from "fs";
|
|
704
928
|
import { diffLines } from "diff";
|
|
705
929
|
function unescapeLlmString(inputString) {
|
|
706
930
|
return inputString.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\\\\/g, "\\");
|
|
@@ -736,7 +960,7 @@ async function calculateEdit(filePath, oldString, newString, expectedReplacement
|
|
|
736
960
|
let finalOldString = oldString.replace(/\r\n/g, "\n");
|
|
737
961
|
let occurrences = 0;
|
|
738
962
|
try {
|
|
739
|
-
currentContent = await
|
|
963
|
+
currentContent = await fs2.readFile(filePath, "utf-8");
|
|
740
964
|
currentContent = currentContent.replace(/\r\n/g, "\n");
|
|
741
965
|
} catch (e) {
|
|
742
966
|
if (e.code !== "ENOENT") {
|
|
@@ -792,7 +1016,7 @@ function createDiff(filename, oldContent, newContent) {
|
|
|
792
1016
|
}
|
|
793
1017
|
async function editTool(args) {
|
|
794
1018
|
const { file_path, old_string, new_string, expected_replacements = 1 } = args;
|
|
795
|
-
if (!
|
|
1019
|
+
if (!path3.isAbsolute(file_path)) {
|
|
796
1020
|
return { success: false, error: `Invalid parameters: file_path must be absolute.`, file_path };
|
|
797
1021
|
}
|
|
798
1022
|
if (file_path.includes("..")) {
|
|
@@ -808,10 +1032,10 @@ async function editTool(args) {
|
|
|
808
1032
|
file_path
|
|
809
1033
|
};
|
|
810
1034
|
}
|
|
811
|
-
await
|
|
812
|
-
await
|
|
813
|
-
const relativePath =
|
|
814
|
-
const filename =
|
|
1035
|
+
await fs2.mkdir(path3.dirname(file_path), { recursive: true });
|
|
1036
|
+
await fs2.writeFile(file_path, editData.newContent, "utf-8");
|
|
1037
|
+
const relativePath = path3.relative(process.cwd(), file_path);
|
|
1038
|
+
const filename = path3.basename(file_path);
|
|
815
1039
|
if (editData.isNewFile) {
|
|
816
1040
|
return {
|
|
817
1041
|
success: true,
|
|
@@ -846,10 +1070,10 @@ ${finalDiff}`,
|
|
|
846
1070
|
|
|
847
1071
|
// src/app/agent/tools/natives/message.ts
|
|
848
1072
|
import { v4 as uuidv4 } from "uuid";
|
|
849
|
-
function
|
|
1073
|
+
function messageNotifyuser(args) {
|
|
850
1074
|
const { text_markdown } = args;
|
|
851
1075
|
const notification = {
|
|
852
|
-
type: "
|
|
1076
|
+
type: "message_notify_user",
|
|
853
1077
|
id: `notify_${uuidv4()}`,
|
|
854
1078
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
855
1079
|
content: {
|
|
@@ -863,8 +1087,8 @@ function messageNotifyDev(args) {
|
|
|
863
1087
|
}
|
|
864
1088
|
|
|
865
1089
|
// src/app/agent/tools/natives/ls.ts
|
|
866
|
-
import { promises as
|
|
867
|
-
import
|
|
1090
|
+
import { promises as fs3 } from "fs";
|
|
1091
|
+
import path4 from "path";
|
|
868
1092
|
var DEFAULT_IGNORE = /* @__PURE__ */ new Set([
|
|
869
1093
|
".git",
|
|
870
1094
|
".gitignore",
|
|
@@ -892,8 +1116,8 @@ async function ls(args) {
|
|
|
892
1116
|
max_depth
|
|
893
1117
|
} = args;
|
|
894
1118
|
try {
|
|
895
|
-
const basePath =
|
|
896
|
-
if (!(await
|
|
1119
|
+
const basePath = path4.resolve(directory_path);
|
|
1120
|
+
if (!(await fs3.stat(basePath)).isDirectory()) {
|
|
897
1121
|
throw new Error(`Directory '${directory_path}' not found.`);
|
|
898
1122
|
}
|
|
899
1123
|
const allIgnorePatterns = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore_patterns]);
|
|
@@ -902,11 +1126,11 @@ async function ls(args) {
|
|
|
902
1126
|
const allDirs = [];
|
|
903
1127
|
const walk = async (currentDir, currentDepth) => {
|
|
904
1128
|
if (max_depth !== void 0 && currentDepth > max_depth) return;
|
|
905
|
-
const entries = await
|
|
1129
|
+
const entries = await fs3.readdir(currentDir, { withFileTypes: true });
|
|
906
1130
|
for (const entry of entries) {
|
|
907
1131
|
const entryName = entry.name;
|
|
908
|
-
const fullPath =
|
|
909
|
-
const posixPath = fullPath.split(
|
|
1132
|
+
const fullPath = path4.join(currentDir, entryName);
|
|
1133
|
+
const posixPath = fullPath.split(path4.sep).join("/");
|
|
910
1134
|
const isHidden = entryName.startsWith(".");
|
|
911
1135
|
if (allIgnorePatterns.has(entryName) || isHidden && !show_hidden) {
|
|
912
1136
|
continue;
|
|
@@ -917,7 +1141,7 @@ async function ls(args) {
|
|
|
917
1141
|
await walk(fullPath, currentDepth + 1);
|
|
918
1142
|
}
|
|
919
1143
|
} else if (entry.isFile()) {
|
|
920
|
-
if (!normalizedExtensions || normalizedExtensions.includes(
|
|
1144
|
+
if (!normalizedExtensions || normalizedExtensions.includes(path4.extname(entryName).toLowerCase())) {
|
|
921
1145
|
allFiles.push(posixPath);
|
|
922
1146
|
}
|
|
923
1147
|
}
|
|
@@ -928,7 +1152,7 @@ async function ls(args) {
|
|
|
928
1152
|
allDirs.sort();
|
|
929
1153
|
return {
|
|
930
1154
|
success: true,
|
|
931
|
-
path: basePath.split(
|
|
1155
|
+
path: basePath.split(path4.sep).join("/"),
|
|
932
1156
|
recursive,
|
|
933
1157
|
total_files: allFiles.length,
|
|
934
1158
|
total_directories: allDirs.length,
|
|
@@ -944,17 +1168,17 @@ async function ls(args) {
|
|
|
944
1168
|
}
|
|
945
1169
|
|
|
946
1170
|
// src/app/agent/tools/natives/readLines.ts
|
|
947
|
-
import { promises as
|
|
1171
|
+
import { promises as fs4 } from "fs";
|
|
948
1172
|
async function readLines(args) {
|
|
949
1173
|
const { filepath, start_line, end_line } = args;
|
|
950
1174
|
try {
|
|
951
|
-
if (!(await
|
|
1175
|
+
if (!(await fs4.stat(filepath)).isFile()) {
|
|
952
1176
|
throw new Error(`File '${filepath}' not found or is not a file.`);
|
|
953
1177
|
}
|
|
954
1178
|
if (start_line < 1 || end_line < start_line) {
|
|
955
1179
|
throw new Error("Invalid line range. start_line must be >= 1 and end_line must be >= start_line.");
|
|
956
1180
|
}
|
|
957
|
-
const fileContent = await
|
|
1181
|
+
const fileContent = await fs4.readFile(filepath, "utf-8");
|
|
958
1182
|
const lines = fileContent.split("\n");
|
|
959
1183
|
const total_lines = lines.length;
|
|
960
1184
|
const startIndex = start_line - 1;
|
|
@@ -982,12 +1206,12 @@ async function readLines(args) {
|
|
|
982
1206
|
|
|
983
1207
|
// src/app/agent/tools/natives/count_lines.ts
|
|
984
1208
|
import { createReadStream } from "fs";
|
|
985
|
-
import { promises as
|
|
1209
|
+
import { promises as fs5 } from "fs";
|
|
986
1210
|
import readline from "readline";
|
|
987
1211
|
async function countLines(args) {
|
|
988
1212
|
const { filepath } = args;
|
|
989
1213
|
try {
|
|
990
|
-
if (!(await
|
|
1214
|
+
if (!(await fs5.stat(filepath)).isFile()) {
|
|
991
1215
|
throw new Error(`File '${filepath}' not found or is not a file.`);
|
|
992
1216
|
}
|
|
993
1217
|
const fileStream = createReadStream(filepath);
|
|
@@ -1022,9 +1246,9 @@ var ToolInvoker = class {
|
|
|
1022
1246
|
async initialize() {
|
|
1023
1247
|
try {
|
|
1024
1248
|
const __filename = fileURLToPath(import.meta.url);
|
|
1025
|
-
const __dirname =
|
|
1026
|
-
const configPath =
|
|
1027
|
-
const fileContent = await
|
|
1249
|
+
const __dirname = path5.dirname(__filename);
|
|
1250
|
+
const configPath = path5.resolve(__dirname, "config", "native_tools.json");
|
|
1251
|
+
const fileContent = await fs6.readFile(configPath, "utf-8");
|
|
1028
1252
|
const config2 = JSON.parse(fileContent);
|
|
1029
1253
|
this.toolDefinitions = config2.nativeTools;
|
|
1030
1254
|
} catch (error) {
|
|
@@ -1039,7 +1263,7 @@ var ToolInvoker = class {
|
|
|
1039
1263
|
registerTools() {
|
|
1040
1264
|
this.toolImplementations.set("shell_command", shellCommand);
|
|
1041
1265
|
this.toolImplementations.set("edit_tool", editTool);
|
|
1042
|
-
this.toolImplementations.set("
|
|
1266
|
+
this.toolImplementations.set("message_notify_user", messageNotifyuser);
|
|
1043
1267
|
this.toolImplementations.set("ls_tool", ls);
|
|
1044
1268
|
this.toolImplementations.set("count_file_lines", countLines);
|
|
1045
1269
|
this.toolImplementations.set("read_file_lines", readLines);
|
|
@@ -1073,8 +1297,8 @@ var ToolInvoker = class {
|
|
|
1073
1297
|
};
|
|
1074
1298
|
|
|
1075
1299
|
// src/app/agent/tools/mcp/mcp_client.ts
|
|
1076
|
-
import { promises as
|
|
1077
|
-
import
|
|
1300
|
+
import { promises as fs7 } from "fs";
|
|
1301
|
+
import path6 from "path";
|
|
1078
1302
|
import os2 from "os";
|
|
1079
1303
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1080
1304
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -1102,9 +1326,9 @@ var MCPClient = class {
|
|
|
1102
1326
|
});
|
|
1103
1327
|
}
|
|
1104
1328
|
const __filename = fileURLToPath2(import.meta.url);
|
|
1105
|
-
const __dirname =
|
|
1106
|
-
const defaultConfigPath =
|
|
1107
|
-
const userConfigPath =
|
|
1329
|
+
const __dirname = path6.dirname(__filename);
|
|
1330
|
+
const defaultConfigPath = path6.resolve(__dirname, "config", "bluma-mcp.json");
|
|
1331
|
+
const userConfigPath = path6.join(os2.homedir(), ".bluma-cli", "bluma-mcp.json");
|
|
1108
1332
|
const defaultConfig = await this.loadMcpConfig(defaultConfigPath, "Default");
|
|
1109
1333
|
const userConfig = await this.loadMcpConfig(userConfigPath, "User");
|
|
1110
1334
|
const mergedConfig = {
|
|
@@ -1138,7 +1362,7 @@ var MCPClient = class {
|
|
|
1138
1362
|
}
|
|
1139
1363
|
async loadMcpConfig(configPath, configType) {
|
|
1140
1364
|
try {
|
|
1141
|
-
const fileContent = await
|
|
1365
|
+
const fileContent = await fs7.readFile(configPath, "utf-8");
|
|
1142
1366
|
const processedContent = this.replaceEnvPlaceholders(fileContent);
|
|
1143
1367
|
return JSON.parse(processedContent);
|
|
1144
1368
|
} catch (error) {
|
|
@@ -1273,12 +1497,12 @@ var AdvancedFeedbackSystem = class {
|
|
|
1273
1497
|
};
|
|
1274
1498
|
|
|
1275
1499
|
// src/app/agent/bluma/core/bluma.ts
|
|
1276
|
-
import
|
|
1500
|
+
import path8 from "path";
|
|
1277
1501
|
|
|
1278
1502
|
// src/app/agent/session_manger/session_manager.ts
|
|
1279
|
-
import
|
|
1503
|
+
import path7 from "path";
|
|
1280
1504
|
import os3 from "os";
|
|
1281
|
-
import { promises as
|
|
1505
|
+
import { promises as fs8 } from "fs";
|
|
1282
1506
|
var fileLocks = /* @__PURE__ */ new Map();
|
|
1283
1507
|
async function withFileLock(file, fn) {
|
|
1284
1508
|
const prev = fileLocks.get(file) || Promise.resolve();
|
|
@@ -1296,13 +1520,13 @@ async function withFileLock(file, fn) {
|
|
|
1296
1520
|
function expandHome(p) {
|
|
1297
1521
|
if (!p) return p;
|
|
1298
1522
|
if (p.startsWith("~")) {
|
|
1299
|
-
return
|
|
1523
|
+
return path7.join(os3.homedir(), p.slice(1));
|
|
1300
1524
|
}
|
|
1301
1525
|
return p;
|
|
1302
1526
|
}
|
|
1303
1527
|
function getPreferredAppDir() {
|
|
1304
|
-
const fixed =
|
|
1305
|
-
return
|
|
1528
|
+
const fixed = path7.join(os3.homedir(), ".bluma-cli");
|
|
1529
|
+
return path7.resolve(expandHome(fixed));
|
|
1306
1530
|
}
|
|
1307
1531
|
async function safeRenameWithRetry(src, dest, maxRetries = 6) {
|
|
1308
1532
|
let attempt = 0;
|
|
@@ -1310,7 +1534,7 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
|
|
|
1310
1534
|
const isWin = process.platform === "win32";
|
|
1311
1535
|
while (attempt <= maxRetries) {
|
|
1312
1536
|
try {
|
|
1313
|
-
await
|
|
1537
|
+
await fs8.rename(src, dest);
|
|
1314
1538
|
return;
|
|
1315
1539
|
} catch (e) {
|
|
1316
1540
|
lastErr = e;
|
|
@@ -1323,9 +1547,9 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
|
|
|
1323
1547
|
}
|
|
1324
1548
|
}
|
|
1325
1549
|
try {
|
|
1326
|
-
const data = await
|
|
1327
|
-
await
|
|
1328
|
-
await
|
|
1550
|
+
const data = await fs8.readFile(src);
|
|
1551
|
+
await fs8.writeFile(dest, data);
|
|
1552
|
+
await fs8.unlink(src).catch(() => {
|
|
1329
1553
|
});
|
|
1330
1554
|
return;
|
|
1331
1555
|
} catch (fallbackErr) {
|
|
@@ -1334,16 +1558,16 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
|
|
|
1334
1558
|
}
|
|
1335
1559
|
async function ensureSessionDir() {
|
|
1336
1560
|
const appDir = getPreferredAppDir();
|
|
1337
|
-
const sessionDir =
|
|
1338
|
-
await
|
|
1561
|
+
const sessionDir = path7.join(appDir, "sessions");
|
|
1562
|
+
await fs8.mkdir(sessionDir, { recursive: true });
|
|
1339
1563
|
return sessionDir;
|
|
1340
1564
|
}
|
|
1341
1565
|
async function loadOrcreateSession(sessionId2) {
|
|
1342
1566
|
const sessionDir = await ensureSessionDir();
|
|
1343
|
-
const sessionFile =
|
|
1567
|
+
const sessionFile = path7.join(sessionDir, `${sessionId2}.json`);
|
|
1344
1568
|
try {
|
|
1345
|
-
await
|
|
1346
|
-
const fileContent = await
|
|
1569
|
+
await fs8.access(sessionFile);
|
|
1570
|
+
const fileContent = await fs8.readFile(sessionFile, "utf-8");
|
|
1347
1571
|
const sessionData = JSON.parse(fileContent);
|
|
1348
1572
|
return [sessionFile, sessionData.conversation_history || []];
|
|
1349
1573
|
} catch (error) {
|
|
@@ -1352,7 +1576,7 @@ async function loadOrcreateSession(sessionId2) {
|
|
|
1352
1576
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1353
1577
|
conversation_history: []
|
|
1354
1578
|
};
|
|
1355
|
-
await
|
|
1579
|
+
await fs8.writeFile(sessionFile, JSON.stringify(newSessionData, null, 2), "utf-8");
|
|
1356
1580
|
return [sessionFile, []];
|
|
1357
1581
|
}
|
|
1358
1582
|
}
|
|
@@ -1360,12 +1584,12 @@ async function saveSessionHistory(sessionFile, history) {
|
|
|
1360
1584
|
await withFileLock(sessionFile, async () => {
|
|
1361
1585
|
let sessionData;
|
|
1362
1586
|
try {
|
|
1363
|
-
const dir =
|
|
1364
|
-
await
|
|
1587
|
+
const dir = path7.dirname(sessionFile);
|
|
1588
|
+
await fs8.mkdir(dir, { recursive: true });
|
|
1365
1589
|
} catch {
|
|
1366
1590
|
}
|
|
1367
1591
|
try {
|
|
1368
|
-
const fileContent = await
|
|
1592
|
+
const fileContent = await fs8.readFile(sessionFile, "utf-8");
|
|
1369
1593
|
sessionData = JSON.parse(fileContent);
|
|
1370
1594
|
} catch (error) {
|
|
1371
1595
|
const code = error && error.code;
|
|
@@ -1376,14 +1600,14 @@ async function saveSessionHistory(sessionFile, history) {
|
|
|
1376
1600
|
console.warn(`An unknown error occurred while reading ${sessionFile}. Re-initializing.`, error);
|
|
1377
1601
|
}
|
|
1378
1602
|
}
|
|
1379
|
-
const sessionId2 =
|
|
1603
|
+
const sessionId2 = path7.basename(sessionFile, ".json");
|
|
1380
1604
|
sessionData = {
|
|
1381
1605
|
session_id: sessionId2,
|
|
1382
1606
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1383
1607
|
conversation_history: []
|
|
1384
1608
|
};
|
|
1385
1609
|
try {
|
|
1386
|
-
await
|
|
1610
|
+
await fs8.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
|
|
1387
1611
|
} catch {
|
|
1388
1612
|
}
|
|
1389
1613
|
}
|
|
@@ -1391,7 +1615,7 @@ async function saveSessionHistory(sessionFile, history) {
|
|
|
1391
1615
|
sessionData.last_updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
1392
1616
|
const tempSessionFile = `${sessionFile}.${Date.now()}.tmp`;
|
|
1393
1617
|
try {
|
|
1394
|
-
await
|
|
1618
|
+
await fs8.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
|
|
1395
1619
|
await safeRenameWithRetry(tempSessionFile, sessionFile);
|
|
1396
1620
|
} catch (writeError) {
|
|
1397
1621
|
if (writeError instanceof Error) {
|
|
@@ -1400,7 +1624,7 @@ async function saveSessionHistory(sessionFile, history) {
|
|
|
1400
1624
|
console.error(`An unknown fatal error occurred while saving session to ${sessionFile}:`, writeError);
|
|
1401
1625
|
}
|
|
1402
1626
|
try {
|
|
1403
|
-
await
|
|
1627
|
+
await fs8.unlink(tempSessionFile);
|
|
1404
1628
|
} catch {
|
|
1405
1629
|
}
|
|
1406
1630
|
}
|
|
@@ -1411,320 +1635,73 @@ async function saveSessionHistory(sessionFile, history) {
|
|
|
1411
1635
|
import os4 from "os";
|
|
1412
1636
|
var SYSTEM_PROMPT = `
|
|
1413
1637
|
|
|
1414
|
-
###
|
|
1415
|
-
You
|
|
1416
|
-
|
|
1638
|
+
### IDENTITY AND OBJECTIVE
|
|
1639
|
+
You are BluMa, an autonomous AI Software Engineer developed by the specialists at NomadEngenuity.
|
|
1640
|
+
You leverage a proprietary Large Language Model, fine-tuned specifically for complex software engineering and code generation tasks.
|
|
1641
|
+
Your objective is to analyze user requests, formulate a precise plan, and execute that plan flawlessly using your available tools.
|
|
1642
|
+
You operate with the highest standards of professionalism, precision, and safety.
|
|
1417
1643
|
---
|
|
1418
1644
|
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
- **Identity:**
|
|
1422
|
-
You are BluMa (NomadEngenuity). Maintain professionalism and technical language.
|
|
1423
|
-
|
|
1424
|
-
- **Communication:**
|
|
1425
|
-
ALL messages must be sent via 'message_notify_dev'.
|
|
1426
|
-
**No direct text replies to the developer.**
|
|
1645
|
+
### CORE DIRECTIVES
|
|
1427
1646
|
|
|
1428
|
-
|
|
1429
|
-
|
|
1647
|
+
1. **THINK FIRST, ACT SECOND:** Your first action in any turn is to formulate an internal plan. Use the mandatory **"Reasoning Process"** format detailed below.
|
|
1648
|
+
2. **TOOL-BASED OPERATION:** All actions and communications MUST be performed through a tool call. NEVER respond with free-form text.
|
|
1649
|
+
3. **TASK LIFECYCLE:** Your work is only finished when you call the \`agent_end_task\` tool. Each tool call is a step within your current turn. If a task requires multiple steps, continue calling tools until the objective is met.
|
|
1650
|
+
4. **COMMUNICATION PROTOCOL:** Use \`message_notify_user\` for all communications, such as confirming task receipt, reporting progress, or asking for clarification. Be concise.
|
|
1651
|
+
5. **ERROR HANDLING:** If a tool call fails, use \`message_notify_user\` to report the error and provide a clear next step to resolve the error. Always try to recover from errors.
|
|
1430
1652
|
|
|
1431
|
-
|
|
1432
|
-
Never make parallel tool calls.
|
|
1433
|
-
Always use only the defined tools with their exact names.
|
|
1434
|
-
|
|
1435
|
-
- **Autonomy:**
|
|
1436
|
-
Act 100% autonomously.
|
|
1437
|
-
Do not ask for formatting preferences.
|
|
1438
|
-
Use the notebook for internal reasoning.
|
|
1653
|
+
---
|
|
1439
1654
|
|
|
1655
|
+
### TOOL USAGE GUIDELINES
|
|
1440
1656
|
|
|
1441
|
-
|
|
1442
|
-
-
|
|
1443
|
-
|
|
1444
|
-
- Zero tolerance for protocol violations.
|
|
1657
|
+
- **File modification:** Always use the \`edit_tool\` tool to create or modify files in a structured manner. Strictly follow the tool's layout to provide the necessary context.
|
|
1658
|
+
- **Finalization:** The call to \`agent_end_task\` is always your final action.
|
|
1659
|
+
---
|
|
1445
1660
|
|
|
1661
|
+
### CURRENT ENVIRONMENT CONTEXT
|
|
1446
1662
|
<current_system_environment>
|
|
1447
1663
|
- Operating System: {os_type} ({os_version})
|
|
1448
1664
|
- Architecture: {architecture}
|
|
1449
|
-
- Current
|
|
1665
|
+
- Current Directory: {workdir}
|
|
1450
1666
|
- Shell: {shell_type}
|
|
1451
|
-
-
|
|
1667
|
+
- User: {username}
|
|
1452
1668
|
- Current Date: {current_date}
|
|
1453
|
-
- Timezone: {timezone}
|
|
1454
|
-
- Locale: {locale}
|
|
1455
1669
|
</current_system_environment>
|
|
1456
1670
|
|
|
1671
|
+
---
|
|
1457
1672
|
|
|
1458
|
-
|
|
1459
|
-
# MERMAID DIAGRAM CREATION - PERFECT SYNTAX REQUIRED!
|
|
1460
|
-
## CRITICAL: ALL DIAGRAMS MUST RENDER WITHOUT ERRORS
|
|
1461
|
-
|
|
1462
|
-
### MANDATORY MERMAID SYNTAX RULES
|
|
1463
|
-
1. **ALWAYS wrap ALL labels in double quotes**: "label text"
|
|
1464
|
-
2. **NEVER use unescaped special characters**: /, (), [], {}, +, *, ?, ^, $, |, 3. **Use line breaks (<br/>) for multi-line labels**: "Line 1<br/>Line 2"
|
|
1465
|
-
4. **NO custom colors or ::: syntax**: Stick to standard themes
|
|
1466
|
-
5. **NO beta features**: Use only stable Mermaid syntax
|
|
1467
|
-
6. **NO remote images**: Never embed external images
|
|
1468
|
-
|
|
1469
|
-
### SAFE LABEL FORMATTING
|
|
1470
|
-
|
|
1471
|
-
CORRECT:
|
|
1472
|
-
- "dev Authentication"
|
|
1473
|
-
- "API Gateway (REST)"
|
|
1474
|
-
- "Database Connection<br/>MySQL 8.0"
|
|
1475
|
-
- "Process Data<br/>Transform & Validate"
|
|
1476
|
-
|
|
1477
|
-
INCORRECT:
|
|
1478
|
-
- dev Authentication (missing quotes)
|
|
1479
|
-
- API Gateway (REST) (parentheses without quotes)
|
|
1480
|
-
- Database/MySQL (slash without quotes)
|
|
1481
|
-
- Process & Transform (ampersand without quotes)
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
### DIAGRAM TYPE BEST PRACTICES
|
|
1485
|
-
|
|
1486
|
-
IMPORTANT
|
|
1487
|
-
The Notion API rejects rich text of type "code" and only accepts "text" for code blocks \u2013
|
|
1488
|
-
a limitation of their own JSON (even for the language: "mermaid"). Therefore, only the code/text block
|
|
1489
|
-
type is viable.
|
|
1490
|
-
|
|
1491
|
-
You should insert the pretty diagram without any delimiters or headers, in the code block with
|
|
1492
|
-
proper indentation and double quotes, only in the text field, to facilitate as little manual rework as possible.
|
|
1493
|
-
It's ready to copy into the native Mermaid block.
|
|
1494
|
-
|
|
1495
|
-
#### FLOWCHART
|
|
1496
|
-
|
|
1497
|
-
flowchart TD
|
|
1498
|
-
A["Start Process"] --> B["Validate Input"]
|
|
1499
|
-
B --> C{"Is Valid?"}
|
|
1500
|
-
C -->|"Yes"| D["Process Data"]
|
|
1501
|
-
C -->|"No"| E["Return Error"]
|
|
1502
|
-
D --> F["Save to Database"]
|
|
1503
|
-
F --> G["Send Response"]
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
#### SEQUENCE DIAGRAM
|
|
1507
|
-
|
|
1508
|
-
sequenceDiagram
|
|
1509
|
-
participant U as "dev"
|
|
1510
|
-
participant A as "API Gateway"
|
|
1511
|
-
participant D as "Database"
|
|
1512
|
-
|
|
1513
|
-
U->>A: "Submit Request"
|
|
1514
|
-
A->>D: "Query Data"
|
|
1515
|
-
D-->>A: "Return Results"
|
|
1516
|
-
A-->>U: "Response Data"
|
|
1517
|
-
|
|
1518
|
-
#### CLASS DIAGRAM
|
|
1519
|
-
|
|
1520
|
-
classDiagram
|
|
1521
|
-
class dev {
|
|
1522
|
-
+String name
|
|
1523
|
-
+String email
|
|
1524
|
-
+authenticate()
|
|
1525
|
-
+updateProfile()
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
class Database {
|
|
1529
|
-
+connect()
|
|
1530
|
-
+query()
|
|
1531
|
-
+close()
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
dev --> Database : "uses"
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
### VALIDATION CHECKLIST
|
|
1538
|
-
Before creating any diagram, ensure:
|
|
1539
|
-
- [ ] All labels are wrapped in double quotes
|
|
1540
|
-
- [ ] No unescaped special characters (/, (), etc.)
|
|
1541
|
-
- [ ] Line breaks use <br/> syntax
|
|
1542
|
-
- [ ] No custom colors or styling
|
|
1543
|
-
- [ ] No beta features or experimental syntax
|
|
1544
|
-
- [ ] All connections use proper arrow syntax
|
|
1545
|
-
- [ ] Node IDs are simple alphanumeric
|
|
1546
|
-
|
|
1547
|
-
### ERROR PREVENTION
|
|
1548
|
-
- Always test diagram syntax mentally before generating
|
|
1549
|
-
- Use simple, descriptive labels without special formatting
|
|
1550
|
-
- Prefer clarity over visual complexity
|
|
1551
|
-
- Keep diagrams focused and readable
|
|
1552
|
-
- Use standard Mermaid themes only
|
|
1553
|
-
|
|
1554
|
-
## ZERO TOLERANCE FOR SYNTAX ERRORS
|
|
1555
|
-
Every diagram MUST render perfectly on first try. No exceptions.
|
|
1556
|
-
</mermaid_diagrams>
|
|
1557
|
-
|
|
1673
|
+
### COMMUNICATION PROTOCOL
|
|
1558
1674
|
<message_rules>
|
|
1559
|
-
- Communicate with
|
|
1675
|
+
- Communicate with user's via message tools instead of direct text responses
|
|
1560
1676
|
- Reply immediately to new user messages before other operations
|
|
1561
1677
|
- First reply must be brief, only confirming receipt without specific solutions
|
|
1562
|
-
- Notify
|
|
1563
|
-
- Message tools are divided into notify (non-blocking, no reply needed from
|
|
1564
|
-
- Actively use notify for progress updates, but reserve ask for only essential needs to minimize
|
|
1565
|
-
- Must message
|
|
1678
|
+
- Notify user's with brief explanation when changing methods or strategies
|
|
1679
|
+
- Message tools are divided into notify (non-blocking, no reply needed from user's) and ask (blocking, reply required)
|
|
1680
|
+
- Actively use notify for progress updates, but reserve ask for only essential needs to minimize user's disruption and avoid blocking progress
|
|
1681
|
+
- Must message user's with results and deliverables before upon task completion 'agent_end_task'
|
|
1566
1682
|
</message_rules>
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
### Live Development Overlaps
|
|
1570
|
-
|
|
1571
|
-
During your workflow, the programmer {username} may send messages at any time.
|
|
1572
|
-
These messages **must be immediately incorporated** into your execution flow.
|
|
1573
|
-
**Always confirm receipt using {message_notify_dev}** and proceed with your work.
|
|
1574
|
-
|
|
1575
|
-
### Instructions for Handling Messages from the Programmer
|
|
1576
|
-
|
|
1577
|
-
1. **Upon receiving a message from {username}:**
|
|
1578
|
-
- Immediately confirm using {message_notify_dev}.
|
|
1579
|
-
- Integrate the instruction into your reasoning and execution flow.
|
|
1580
|
-
|
|
1581
|
-
2. **Regarding your reasoning:**
|
|
1582
|
-
- Be direct, minimalist, and clear.
|
|
1583
|
-
- Avoid unnecessary or verbose thoughts.
|
|
1584
|
-
|
|
1585
|
-
3. **Avoid polluting the history:**
|
|
1586
|
-
- **Do not repeat or reply to existing messages.**
|
|
1587
|
-
- Only act if the new message introduces a **new instruction or shifts the current task\u2019s focus**.
|
|
1588
|
-
|
|
1589
|
-
<reasoning_rules>
|
|
1590
|
-
# YOUR THINKING ON A NOTEBOOK - MANDATORY USE
|
|
1591
|
-
CRITICAL: Your laptop (**reasoning_nootebook**) is your ORGANIZED MIND
|
|
1592
|
-
## IMPORTANT
|
|
1593
|
-
## NEVER PUT CHECKLISTS OR STEPS IN THE THOUGHT TEXT
|
|
1594
|
-
## ALWAYS USE A NOTEBOOK (Always for):
|
|
1595
|
-
- ANY task
|
|
1596
|
-
- Before starting development (plan first!)
|
|
1597
|
-
- Projects with multiple files (organize the structure)
|
|
1598
|
-
- Debugging sessions (monitor discoveries)
|
|
1599
|
-
- Extensive refactoring (map the changes)
|
|
1600
|
-
- Architectural decisions (think through the options)
|
|
1601
|
-
|
|
1602
|
-
## HOW TO USE A NOTEBOOK:
|
|
1603
|
-
1. Start with **reasoning_nootebook**
|
|
1604
|
-
2. Break the task down into logical steps
|
|
1605
|
-
3. Plan the approach - Which files? What changes? What order? 4. Track progress - Check off completed steps
|
|
1606
|
-
5. Write down decisions - Why did you choose this approach?
|
|
1607
|
-
6. Update continuously - Keep the notebook up to date
|
|
1608
|
-
|
|
1609
|
-
## THE NOTEBOOK PREVENTS:
|
|
1610
|
-
- Acting "outside the box"
|
|
1611
|
-
- Forgetting task requirements
|
|
1612
|
-
- Losing control of complex workflows
|
|
1613
|
-
- Making unplanned changes
|
|
1614
|
-
- Ineffective approaches
|
|
1615
|
-
- Working without a clear roadmap
|
|
1616
|
-
- Jumping between unrelated subtasks
|
|
1617
|
-
|
|
1618
|
-
##Important rule:
|
|
1619
|
-
Do **not** include any future steps, to-do items, or pending tasks here.
|
|
1620
|
-
Those belong strictly in the **remaining_tasks** field.
|
|
1621
|
-
|
|
1622
|
-
Never write phrases like:
|
|
1623
|
-
- "Next I will..."
|
|
1624
|
-
- "I still need to..."
|
|
1625
|
-
- "Pending: ..."
|
|
1626
|
-
Such content must go in **remaining_tasks**, not **thought**.
|
|
1627
|
-
|
|
1628
|
-
- remaining_tasks: Checklist-style list of high-level upcoming tasks.
|
|
1629
|
-
This format is **mandatory**:
|
|
1630
|
-
- Each task **must start** with either:
|
|
1631
|
-
- "\u{1F5F8}" \u2192 for tasks not yet done (pending)
|
|
1632
|
-
- "[ ]" \u2192 for tasks that have already been completed
|
|
1633
|
-
|
|
1634
|
-
Whenever a task is already done, it **must** be marked with "\u{1F5F8}". Do not leave completed tasks without the checkmark.
|
|
1635
|
-
|
|
1636
|
-
Do not use other formats like "-", "*", or plain text without the prefix.
|
|
1637
|
-
|
|
1638
|
-
Examples:
|
|
1639
|
-
\u{1F5F8} Test integration flow
|
|
1640
|
-
\u{1F5F8} Set up environment
|
|
1641
|
-
[ ] Configure database
|
|
1642
|
-
|
|
1643
|
-
</reasoning_rules>
|
|
1644
|
-
|
|
1645
|
-
### Tool Naming Policy
|
|
1646
|
-
|
|
1647
|
-
Tool names must strictly follow the standard naming format:
|
|
1648
|
-
|
|
1649
|
-
- Use: plain, unmodified, lowercase names
|
|
1650
|
-
- Do NOT use: special characters, extra spaces, version suffixes, or dynamic IDs
|
|
1651
|
-
|
|
1652
1683
|
---
|
|
1653
1684
|
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
-
|
|
1657
|
-
-
|
|
1658
|
-
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
-
|
|
1665
|
-
-
|
|
1666
|
-
-
|
|
1667
|
-
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
- Do not append suffixes like :0, :v2, etc.
|
|
1675
|
-
- Tool names must be static and predictable
|
|
1676
|
-
- No whitespace, no dynamic elements, no special characters
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
<edit_tool_rules>
|
|
1680
|
-
- Use this tool to perform precise text replacements inside files based on exact literal matches.
|
|
1681
|
-
- Can be used to create new files or directories implicitly by targeting non-existing paths.
|
|
1682
|
-
- Suitable for inserting full content into a file even if the file does not yet exist.
|
|
1683
|
-
- Shell access is not required for file or directory creation when using this tool.
|
|
1684
|
-
- Always prefer this tool over shell_command when performing structured edits or creating files with specific content.
|
|
1685
|
-
- Ensure **old_string** includes 3+ lines of exact context before and after the target if replacing existing content.
|
|
1686
|
-
- For creating a new file, provide an **old_string** that matches an empty string or placeholder and a complete **new_string** with the intended content.
|
|
1687
|
-
- When generating or modifying todo.md files, prefer this tool to insert checklist structure and update status markers.
|
|
1688
|
-
- After completing any task in the checklist, immediately update the corresponding section in todo.md using this tool.
|
|
1689
|
-
- Reconstruct the entire file from task planning context if todo.md becomes outdated or inconsistent.
|
|
1690
|
-
- Track all progress related to planning and execution inside todo.md using text replacement only.
|
|
1691
|
-
</edit_tool_rules>
|
|
1692
|
-
|
|
1693
|
-
Real-Time Developer Messages
|
|
1694
|
-
- During processing, the developer will send you messages.
|
|
1695
|
-
- You MUST respond immediately via message_notify_dev, and be brief. You should use it in your next thoughts/actions.
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
<agent_end_task_rules>
|
|
1699
|
-
This tool is mandatory.
|
|
1700
|
-
You must use it to inform developer {username} that the task has been completed and that there are no further pending actions, in accordance with the objectives defined for the task.
|
|
1701
|
-
</agent_end_task_rules>
|
|
1702
|
-
|
|
1703
|
-
### QUALITY STANDARDS
|
|
1704
|
-
- Document every major decision in Notion(
|
|
1705
|
-
##Important: When writing to Notion, you must strictly follow its content structure, including the correct use of headings (heading_1, heading_2, etc.) and other formatting standards. No deviations are allowed.
|
|
1706
|
-
You should always standardize everything using Notion's actual headers (heading_1, heading_2, etc.), making the structure
|
|
1707
|
-
semantically better for reading and navigation.
|
|
1708
|
-
)
|
|
1709
|
-
- Communicate transparently at each step
|
|
1710
|
-
- Write clean, well-documented code
|
|
1711
|
-
- Follow existing project conventions
|
|
1712
|
-
- Test implementations when possible
|
|
1713
|
-
- Ensure security and performance
|
|
1714
|
-
|
|
1715
|
-
<scope_and_limitations>
|
|
1716
|
-
# WHAT YOU DON'T HANDLE
|
|
1717
|
-
- Non-technical questions
|
|
1718
|
-
- Personal advice
|
|
1719
|
-
- General conversation
|
|
1720
|
-
- Tasks outside software development
|
|
1721
|
-
|
|
1722
|
-
# IF ASKED NON-TECHNICAL QUESTIONS
|
|
1723
|
-
- Use message_notify_dev to politely decline
|
|
1724
|
-
- Explain you only handle technical/coding tasks
|
|
1725
|
-
- Suggest they ask a development-related question instead
|
|
1726
|
-
</scope_and_limitations>
|
|
1727
|
-
|
|
1685
|
+
### SCOPE & LIMITATIONS
|
|
1686
|
+
|
|
1687
|
+
**1. IN-SCOPE TASKS:**
|
|
1688
|
+
- Software architecture and design.
|
|
1689
|
+
- Code generation, analysis, and debugging.
|
|
1690
|
+
- Using provided tools to complete development objectives.
|
|
1691
|
+
- Creating technical documentation and diagrams.
|
|
1692
|
+
|
|
1693
|
+
**2. OUT-OF-SCOPE TASKS:**
|
|
1694
|
+
You MUST professionally decline to engage with any of the following:
|
|
1695
|
+
- Non-technical questions (e.g., weather, news, general facts).
|
|
1696
|
+
- Personal, financial, or legal advice.
|
|
1697
|
+
- General conversation, opinions, or jokes.
|
|
1698
|
+
- Any task not directly related to software development.
|
|
1699
|
+
|
|
1700
|
+
**3. PROTOCOL FOR OUT-OF-SCOPE REQUESTS:**
|
|
1701
|
+
If a user asks for something that is out-of-scope, follow this exact procedure:
|
|
1702
|
+
1. Do NOT attempt to answer the question.
|
|
1703
|
+
2. Use the \`message_notify_user\` tool.
|
|
1704
|
+
3. Use the \`agent_end_task\` tool.
|
|
1728
1705
|
`;
|
|
1729
1706
|
function getUnifiedSystemPrompt() {
|
|
1730
1707
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -1779,7 +1756,7 @@ function createApiContextWindow(fullHistory, maxTurns) {
|
|
|
1779
1756
|
const turns = [];
|
|
1780
1757
|
let currentTurn = [];
|
|
1781
1758
|
let turnsFound = 0;
|
|
1782
|
-
const isDevOverlay = (m) => m?.role === "user" && m?.name === "
|
|
1759
|
+
const isDevOverlay = (m) => m?.role === "user" && m?.name === "user_overlay";
|
|
1783
1760
|
for (let i = conversationHistory.length - 1; i >= 0; i--) {
|
|
1784
1761
|
const msg = conversationHistory[i];
|
|
1785
1762
|
currentTurn.unshift(msg);
|
|
@@ -1834,16 +1811,16 @@ var BluMaAgent = class {
|
|
|
1834
1811
|
this.isInterrupted = true;
|
|
1835
1812
|
this.eventBus.emit("backend_message", { type: "done", status: "interrupted" });
|
|
1836
1813
|
});
|
|
1837
|
-
this.eventBus.on("
|
|
1814
|
+
this.eventBus.on("user_overlay", async (data) => {
|
|
1838
1815
|
const clean = String(data.payload ?? "").trim();
|
|
1839
|
-
this.history.push({ role: "user", name: "
|
|
1840
|
-
this.eventBus.emit("backend_message", { type: "
|
|
1816
|
+
this.history.push({ role: "user", name: "user_overlay", content: clean });
|
|
1817
|
+
this.eventBus.emit("backend_message", { type: "user_overlay", payload: clean, ts: data.ts || Date.now() });
|
|
1841
1818
|
try {
|
|
1842
1819
|
if (this.sessionFile) {
|
|
1843
1820
|
await saveSessionHistory(this.sessionFile, this.history);
|
|
1844
1821
|
}
|
|
1845
1822
|
} catch (e) {
|
|
1846
|
-
this.eventBus.emit("backend_message", { type: "error", message: `Falha ao salvar hist\xF3rico ap\xF3s
|
|
1823
|
+
this.eventBus.emit("backend_message", { type: "error", message: `Falha ao salvar hist\xF3rico ap\xF3s user_overlay: ${e.message}` });
|
|
1847
1824
|
}
|
|
1848
1825
|
});
|
|
1849
1826
|
}
|
|
@@ -1934,7 +1911,7 @@ var BluMaAgent = class {
|
|
|
1934
1911
|
|
|
1935
1912
|
${editData.error.display}`;
|
|
1936
1913
|
}
|
|
1937
|
-
const filename =
|
|
1914
|
+
const filename = path8.basename(toolArgs.file_path);
|
|
1938
1915
|
return createDiff(filename, editData.currentContent || "", editData.newContent);
|
|
1939
1916
|
} catch (e) {
|
|
1940
1917
|
return `An unexpected error occurred while generating the edit preview: ${e.message}`;
|
|
@@ -1943,16 +1920,20 @@ ${editData.error.display}`;
|
|
|
1943
1920
|
async _continueConversation() {
|
|
1944
1921
|
try {
|
|
1945
1922
|
if (this.isInterrupted) {
|
|
1946
|
-
this.eventBus.emit("backend_message", { type: "info", message: "
|
|
1923
|
+
this.eventBus.emit("backend_message", { type: "info", message: "Task Canceled." });
|
|
1947
1924
|
return;
|
|
1948
1925
|
}
|
|
1949
1926
|
const contextWindow = createApiContextWindow(this.history, this.maxContextTurns);
|
|
1950
1927
|
const response = await this.llm.chatCompletion({
|
|
1951
1928
|
model: this.deploymentName,
|
|
1952
1929
|
messages: contextWindow,
|
|
1930
|
+
temperature: 0.2,
|
|
1953
1931
|
tools: this.mcpClient.getAvailableTools(),
|
|
1954
1932
|
tool_choice: "required",
|
|
1955
|
-
|
|
1933
|
+
reasoning_effort: "high",
|
|
1934
|
+
parallel_tool_calls: false,
|
|
1935
|
+
max_tokens: 512
|
|
1936
|
+
// Limite de tokens para evitar respostas muito longas
|
|
1956
1937
|
});
|
|
1957
1938
|
if (this.isInterrupted) {
|
|
1958
1939
|
this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
|
|
@@ -1961,7 +1942,7 @@ ${editData.error.display}`;
|
|
|
1961
1942
|
const message = response.choices[0].message;
|
|
1962
1943
|
this.history.push(message);
|
|
1963
1944
|
if (message.tool_calls) {
|
|
1964
|
-
const autoApprovedTools = ["agent_end_task", "
|
|
1945
|
+
const autoApprovedTools = ["agent_end_task", "message_notify_user", "reasoning_nootebook"];
|
|
1965
1946
|
const toolToCall = message.tool_calls[0];
|
|
1966
1947
|
const isSafeTool = autoApprovedTools.some((safeTool) => toolToCall.function.name.includes(safeTool));
|
|
1967
1948
|
if (isSafeTool) {
|
|
@@ -2042,11 +2023,11 @@ You extend the BluMa multi-agent architecture and handle the project bootstrappi
|
|
|
2042
2023
|
You are BluMa InitSubAgent. Maintain professionalism and technical language.
|
|
2043
2024
|
|
|
2044
2025
|
- Communication:
|
|
2045
|
-
ALL messages must be sent via '
|
|
2046
|
-
No direct text replies to the
|
|
2026
|
+
ALL messages must be sent via 'message_notify_user'.
|
|
2027
|
+
No direct text replies to the user.
|
|
2047
2028
|
|
|
2048
2029
|
- Task Completion:
|
|
2049
|
-
When the init task is completed, immediately invoke 'agent_end_task' without
|
|
2030
|
+
When the init task is completed, immediately invoke 'agent_end_task' without user permissions.
|
|
2050
2031
|
|
|
2051
2032
|
- Tool Rules:
|
|
2052
2033
|
Never make parallel tool calls.
|
|
@@ -2068,20 +2049,20 @@ You extend the BluMa multi-agent architecture and handle the project bootstrappi
|
|
|
2068
2049
|
- Architecture: {architecture}
|
|
2069
2050
|
- Current Working Directory: {workdir}
|
|
2070
2051
|
- Shell: {shell_type}
|
|
2071
|
-
-
|
|
2052
|
+
- User: {username}
|
|
2072
2053
|
- Current Date: {current_date}
|
|
2073
2054
|
- Timezone: {timezone}
|
|
2074
2055
|
- Locale: {locale}
|
|
2075
2056
|
</current_system_environment>
|
|
2076
2057
|
|
|
2077
2058
|
<message_rules>
|
|
2078
|
-
- Communicate with
|
|
2059
|
+
- Communicate with user's via message tools instead of direct text responses
|
|
2079
2060
|
- Reply immediately to new user messages before other operations
|
|
2080
2061
|
- First reply must be brief, only confirming receipt without specific solutions
|
|
2081
|
-
- Notify
|
|
2062
|
+
- Notify user's with brief explanation when changing methods or strategies
|
|
2082
2063
|
- Message tools are divided into notify (non-blocking, no reply needed) and ask (blocking)
|
|
2083
2064
|
- Actively use notify for progress updates, reserve ask for essential needs to avoid blocking
|
|
2084
|
-
- Must message
|
|
2065
|
+
- Must message user's with results and deliverables before upon task completion 'agent_end_task'
|
|
2085
2066
|
</message_rules>
|
|
2086
2067
|
|
|
2087
2068
|
<reasoning_rules>
|
|
@@ -2091,7 +2072,7 @@ CRITICAL: Your laptop (reasoning_nootebook) is your ORGANIZED MIND
|
|
|
2091
2072
|
## NEVER PUT CHECKLISTS OR STEPS IN THE THOUGHT TEXT
|
|
2092
2073
|
## ALWAYS USE A NOTEBOOK (Always for):
|
|
2093
2074
|
- ANY task
|
|
2094
|
-
- Before starting
|
|
2075
|
+
- Before starting userelopment (plan first!)
|
|
2095
2076
|
- Projects with multiple files (organize the structure)
|
|
2096
2077
|
- Debugging sessions (monitor discoveries)
|
|
2097
2078
|
- Extensive refactoring (map the changes)
|
|
@@ -2140,7 +2121,7 @@ Do not include future steps/to-dos in thought; put them strictly in remaining_ta
|
|
|
2140
2121
|
|
|
2141
2122
|
<agent_end_task_rules>
|
|
2142
2123
|
This tool is mandatory.
|
|
2143
|
-
You must use it to inform
|
|
2124
|
+
You must use it to inform usereloper {username} that the task has been completed and that there are no further pending actions, in accordance with the objectives defined for the task.
|
|
2144
2125
|
</agent_end_task_rules>
|
|
2145
2126
|
|
|
2146
2127
|
### Tool Naming Policy
|
|
@@ -2167,9 +2148,9 @@ Rule Summary:
|
|
|
2167
2148
|
- Never invent file content. Read files via tools to confirm.
|
|
2168
2149
|
|
|
2169
2150
|
## OUTPUT & PROTOCOLS
|
|
2170
|
-
- Emit 'backend_message' events through tools only (
|
|
2171
|
-
- Before writing BluMa.md, propose structure via
|
|
2172
|
-
- If an irreversible operation is needed (e.g., overwriting an existing BluMa.md), issue 'confirmation_request' unless
|
|
2151
|
+
- Emit 'backend_message' events through tools only (message_notify_user) for progress updates.
|
|
2152
|
+
- Before writing BluMa.md, propose structure via message_notify_user and proceed using edit_tool.
|
|
2153
|
+
- If an irreversible operation is needed (e.g., overwriting an existing BluMa.md), issue 'confirmation_request' unless user policy indicates auto-approval.
|
|
2173
2154
|
- Never send or present draft versions of BluMa.md. Only produce and deliver the final, validated BluMa.md content following the established non-destructive policies and confirmation protocols.
|
|
2174
2155
|
- On successful generation of BluMa.md, emit 'done' with status 'completed' and call agent_end_task.
|
|
2175
2156
|
|
|
@@ -2182,7 +2163,7 @@ Rule Summary:
|
|
|
2182
2163
|
## EXEMPLAR FLOW (GUIDELINE)
|
|
2183
2164
|
1) Explore repo: ls + targeted readLines for key files (package.json, tsconfig.json, README, etc.)
|
|
2184
2165
|
2) Synthesize stack and structure with citations of evidence (file paths) in the notebook
|
|
2185
|
-
3) Draft BluMa.md structure (
|
|
2166
|
+
3) Draft BluMa.md structure (message_notify_user)
|
|
2186
2167
|
4) Write BluMa.md via edit_tool
|
|
2187
2168
|
5) Announce completion and agent_end_task
|
|
2188
2169
|
|
|
@@ -2272,7 +2253,7 @@ ${editData.error.display}`;
|
|
|
2272
2253
|
async _continueConversation() {
|
|
2273
2254
|
try {
|
|
2274
2255
|
if (this.isInterrupted) {
|
|
2275
|
-
this.emitEvent("info", { message: "SubAgent task cancelled
|
|
2256
|
+
this.emitEvent("info", { message: "SubAgent task cancelled byuserr." });
|
|
2276
2257
|
return;
|
|
2277
2258
|
}
|
|
2278
2259
|
const contextWindow = this.history.slice(-this.maxContextTurns);
|
|
@@ -2284,7 +2265,7 @@ ${editData.error.display}`;
|
|
|
2284
2265
|
parallel_tool_calls: false
|
|
2285
2266
|
});
|
|
2286
2267
|
if (this.isInterrupted) {
|
|
2287
|
-
this.emitEvent("info", { message: "SubAgent task cancelled
|
|
2268
|
+
this.emitEvent("info", { message: "SubAgent task cancelled byuserr." });
|
|
2288
2269
|
return;
|
|
2289
2270
|
}
|
|
2290
2271
|
const message = response.choices[0].message;
|
|
@@ -2333,7 +2314,7 @@ ${editData.error.display}`;
|
|
|
2333
2314
|
try {
|
|
2334
2315
|
if (this.isInterrupted) {
|
|
2335
2316
|
this.emitEvent("info", { message: "SubAgent task cancelled before tool execution." });
|
|
2336
|
-
toolResultContent = JSON.stringify({ error: "Task cancelled
|
|
2317
|
+
toolResultContent = JSON.stringify({ error: "Task cancelled byuserr before execution." });
|
|
2337
2318
|
} else {
|
|
2338
2319
|
const result = await this.ctx.mcpClient.invoke(toolName, toolArgs);
|
|
2339
2320
|
let finalResult = result;
|
|
@@ -2449,7 +2430,7 @@ var SubAgentsBluMa = class {
|
|
|
2449
2430
|
};
|
|
2450
2431
|
|
|
2451
2432
|
// src/app/agent/agent.ts
|
|
2452
|
-
var globalEnvPath =
|
|
2433
|
+
var globalEnvPath = path9.join(os6.homedir(), ".bluma-cli", ".env");
|
|
2453
2434
|
dotenv.config({ path: globalEnvPath });
|
|
2454
2435
|
var Agent = class {
|
|
2455
2436
|
sessionId;
|
|
@@ -2579,19 +2560,19 @@ var Agent = class {
|
|
|
2579
2560
|
};
|
|
2580
2561
|
|
|
2581
2562
|
// src/app/ui/WorkingTimer.tsx
|
|
2582
|
-
import { useState as
|
|
2563
|
+
import { useState as useState4, useEffect as useEffect3 } from "react";
|
|
2583
2564
|
import { Box as Box7, Text as Text7 } from "ink";
|
|
2584
2565
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2585
2566
|
var WorkingTimer = () => {
|
|
2586
|
-
const [seconds, setSeconds] =
|
|
2587
|
-
const [dotIndex, setDotIndex] =
|
|
2588
|
-
|
|
2567
|
+
const [seconds, setSeconds] = useState4(0);
|
|
2568
|
+
const [dotIndex, setDotIndex] = useState4(1);
|
|
2569
|
+
useEffect3(() => {
|
|
2589
2570
|
const secondsTimer = setInterval(() => {
|
|
2590
2571
|
setSeconds((prev) => prev + 1);
|
|
2591
2572
|
}, 1e3);
|
|
2592
2573
|
return () => clearInterval(secondsTimer);
|
|
2593
2574
|
}, []);
|
|
2594
|
-
|
|
2575
|
+
useEffect3(() => {
|
|
2595
2576
|
const dotsTimer = setInterval(() => {
|
|
2596
2577
|
setDotIndex((prev) => prev % 3 + 1);
|
|
2597
2578
|
}, 100);
|
|
@@ -2610,7 +2591,7 @@ import { Box as Box9 } from "ink";
|
|
|
2610
2591
|
|
|
2611
2592
|
// src/app/ui/components/toolCallRenderers.tsx
|
|
2612
2593
|
import { Box as Box8, Text as Text8 } from "ink";
|
|
2613
|
-
import
|
|
2594
|
+
import path10 from "path";
|
|
2614
2595
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2615
2596
|
var formatArgumentsForDisplay = (args) => {
|
|
2616
2597
|
if (typeof args === "string") {
|
|
@@ -2622,7 +2603,9 @@ var formatArgumentsForDisplay = (args) => {
|
|
|
2622
2603
|
}
|
|
2623
2604
|
return JSON.stringify(args, null, 2);
|
|
2624
2605
|
};
|
|
2625
|
-
var renderShellCommand2 = ({
|
|
2606
|
+
var renderShellCommand2 = ({
|
|
2607
|
+
args
|
|
2608
|
+
}) => {
|
|
2626
2609
|
const command = args.command || "[command not found]";
|
|
2627
2610
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2628
2611
|
/* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
|
|
@@ -2643,7 +2626,7 @@ var renderLsTool2 = ({ args }) => {
|
|
|
2643
2626
|
} catch (e) {
|
|
2644
2627
|
directoryPath = "Error parsing arguments";
|
|
2645
2628
|
}
|
|
2646
|
-
const finalDirectoryName =
|
|
2629
|
+
const finalDirectoryName = path10.basename(directoryPath);
|
|
2647
2630
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2648
2631
|
/* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
|
|
2649
2632
|
/* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
|
|
@@ -2655,7 +2638,9 @@ var renderLsTool2 = ({ args }) => {
|
|
|
2655
2638
|
] }) })
|
|
2656
2639
|
] });
|
|
2657
2640
|
};
|
|
2658
|
-
var renderCountFilesLines = ({
|
|
2641
|
+
var renderCountFilesLines = ({
|
|
2642
|
+
args
|
|
2643
|
+
}) => {
|
|
2659
2644
|
let directoryPath = "[path not found]";
|
|
2660
2645
|
try {
|
|
2661
2646
|
const parsedArgs = typeof args === "string" ? JSON.parse(args) : args;
|
|
@@ -2663,7 +2648,7 @@ var renderCountFilesLines = ({ args }) => {
|
|
|
2663
2648
|
} catch (e) {
|
|
2664
2649
|
directoryPath = "Error parsing arguments";
|
|
2665
2650
|
}
|
|
2666
|
-
const finalDirectoryName =
|
|
2651
|
+
const finalDirectoryName = path10.basename(directoryPath);
|
|
2667
2652
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2668
2653
|
/* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
|
|
2669
2654
|
/* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
|
|
@@ -2675,7 +2660,9 @@ var renderCountFilesLines = ({ args }) => {
|
|
|
2675
2660
|
] }) })
|
|
2676
2661
|
] });
|
|
2677
2662
|
};
|
|
2678
|
-
var renderReadFileLines2 = ({
|
|
2663
|
+
var renderReadFileLines2 = ({
|
|
2664
|
+
args
|
|
2665
|
+
}) => {
|
|
2679
2666
|
let filepath = "[path not found]";
|
|
2680
2667
|
let startLine = 0;
|
|
2681
2668
|
let endLine = 0;
|
|
@@ -2687,7 +2674,7 @@ var renderReadFileLines2 = ({ args }) => {
|
|
|
2687
2674
|
} catch (e) {
|
|
2688
2675
|
filepath = "Error parsing arguments";
|
|
2689
2676
|
}
|
|
2690
|
-
const finalFileName =
|
|
2677
|
+
const finalFileName = path10.basename(filepath);
|
|
2691
2678
|
return (
|
|
2692
2679
|
// A caixa externa com a borda, seguindo o template
|
|
2693
2680
|
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
@@ -2711,7 +2698,9 @@ var renderReadFileLines2 = ({ args }) => {
|
|
|
2711
2698
|
] })
|
|
2712
2699
|
);
|
|
2713
2700
|
};
|
|
2714
|
-
var renderBlumaNotebook = ({
|
|
2701
|
+
var renderBlumaNotebook = ({
|
|
2702
|
+
args
|
|
2703
|
+
}) => {
|
|
2715
2704
|
try {
|
|
2716
2705
|
let dataToParse = args;
|
|
2717
2706
|
if (args && typeof args === "object") {
|
|
@@ -2724,16 +2713,30 @@ var renderBlumaNotebook = ({ args }) => {
|
|
|
2724
2713
|
}
|
|
2725
2714
|
return (
|
|
2726
2715
|
// Usamos a mesma estrutura de caixa com borda
|
|
2727
|
-
/* @__PURE__ */ jsxs8(
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2716
|
+
/* @__PURE__ */ jsxs8(
|
|
2717
|
+
Box8,
|
|
2718
|
+
{
|
|
2719
|
+
flexDirection: "column",
|
|
2720
|
+
paddingX: 1,
|
|
2721
|
+
children: [
|
|
2722
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2723
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "Thought:" }),
|
|
2724
|
+
/* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { color: "gray", children: thinkingData.thought }) })
|
|
2725
|
+
] }),
|
|
2726
|
+
thinkingData.remaining_tasks && thinkingData.remaining_tasks.length > 0 && /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
2727
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Remaining Tasks:" }),
|
|
2728
|
+
thinkingData.remaining_tasks.map((task, index) => /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { children: /* @__PURE__ */ jsx8(
|
|
2729
|
+
Text8,
|
|
2730
|
+
{
|
|
2731
|
+
color: task.startsWith("\u{1F5F9}") ? "green" : "yellow",
|
|
2732
|
+
strikethrough: task.startsWith("\u{1F5F9}"),
|
|
2733
|
+
children: task
|
|
2734
|
+
}
|
|
2735
|
+
) }) }, index))
|
|
2736
|
+
] })
|
|
2737
|
+
]
|
|
2738
|
+
}
|
|
2739
|
+
)
|
|
2737
2740
|
);
|
|
2738
2741
|
} catch (e) {
|
|
2739
2742
|
return /* @__PURE__ */ jsxs8(Box8, { borderStyle: "round", borderColor: "blue", paddingX: 1, children: [
|
|
@@ -2742,7 +2745,10 @@ var renderBlumaNotebook = ({ args }) => {
|
|
|
2742
2745
|
] });
|
|
2743
2746
|
}
|
|
2744
2747
|
};
|
|
2745
|
-
var renderEditToolCall = ({
|
|
2748
|
+
var renderEditToolCall = ({
|
|
2749
|
+
args,
|
|
2750
|
+
preview
|
|
2751
|
+
}) => {
|
|
2746
2752
|
let filepath = "[path not specified]";
|
|
2747
2753
|
try {
|
|
2748
2754
|
const parsedArgs = typeof args === "string" ? JSON.parse(args) : args;
|
|
@@ -2750,7 +2756,7 @@ var renderEditToolCall = ({ args, preview }) => {
|
|
|
2750
2756
|
} catch (e) {
|
|
2751
2757
|
filepath = "Error parsing arguments";
|
|
2752
2758
|
}
|
|
2753
|
-
const finalFileName =
|
|
2759
|
+
const finalFileName = path10.basename(filepath);
|
|
2754
2760
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
|
|
2755
2761
|
/* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
|
|
2756
2762
|
/* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
|
|
@@ -2763,7 +2769,10 @@ var renderEditToolCall = ({ args, preview }) => {
|
|
|
2763
2769
|
preview && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(SimpleDiff, { text: preview, maxHeight: Infinity }) })
|
|
2764
2770
|
] });
|
|
2765
2771
|
};
|
|
2766
|
-
var renderGenericToolCall = ({
|
|
2772
|
+
var renderGenericToolCall = ({
|
|
2773
|
+
toolName,
|
|
2774
|
+
args
|
|
2775
|
+
}) => {
|
|
2767
2776
|
const formattedArgs = formatArgumentsForDisplay(args);
|
|
2768
2777
|
return (
|
|
2769
2778
|
// A "moldura" padrão de sucesso com a borda cinza
|
|
@@ -2780,18 +2789,18 @@ var renderGenericToolCall = ({ toolName, args }) => {
|
|
|
2780
2789
|
);
|
|
2781
2790
|
};
|
|
2782
2791
|
var ToolRenderDisplay = {
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2792
|
+
shell_command: renderShellCommand2,
|
|
2793
|
+
ls_tool: renderLsTool2,
|
|
2794
|
+
reasoning_nootebook: renderBlumaNotebook,
|
|
2795
|
+
count_file_lines: renderCountFilesLines,
|
|
2796
|
+
read_file_lines: renderReadFileLines2,
|
|
2797
|
+
edit_tool: renderEditToolCall
|
|
2789
2798
|
};
|
|
2790
2799
|
|
|
2791
2800
|
// src/app/ui/components/ToolCallDisplay.tsx
|
|
2792
2801
|
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
2793
2802
|
var ToolCallDisplayComponent = ({ toolName, args, preview }) => {
|
|
2794
|
-
if (toolName.includes("
|
|
2803
|
+
if (toolName.includes("message_notify_user") || toolName.includes("agent_end_task")) {
|
|
2795
2804
|
return null;
|
|
2796
2805
|
}
|
|
2797
2806
|
const Renderer = ToolRenderDisplay[toolName] || renderGenericToolCall;
|
|
@@ -2804,7 +2813,7 @@ import { memo as memo3 } from "react";
|
|
|
2804
2813
|
import { Box as Box10, Text as Text9 } from "ink";
|
|
2805
2814
|
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
2806
2815
|
var ToolResultDisplayComponent = ({ toolName, result }) => {
|
|
2807
|
-
if (!toolName.includes("
|
|
2816
|
+
if (!toolName.includes("message_notify_user")) {
|
|
2808
2817
|
return null;
|
|
2809
2818
|
}
|
|
2810
2819
|
try {
|
|
@@ -3028,21 +3037,21 @@ var SlashCommands_default = SlashCommands;
|
|
|
3028
3037
|
import updateNotifier from "update-notifier";
|
|
3029
3038
|
import { readPackageUp } from "read-package-up";
|
|
3030
3039
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3031
|
-
import
|
|
3032
|
-
import
|
|
3040
|
+
import path11 from "path";
|
|
3041
|
+
import fs9 from "fs";
|
|
3033
3042
|
function findPackageJsonNearest(startDir) {
|
|
3034
3043
|
let dir = startDir;
|
|
3035
3044
|
for (let i = 0; i < 6; i++) {
|
|
3036
|
-
const candidate =
|
|
3037
|
-
if (
|
|
3045
|
+
const candidate = path11.join(dir, "package.json");
|
|
3046
|
+
if (fs9.existsSync(candidate)) {
|
|
3038
3047
|
try {
|
|
3039
|
-
const raw =
|
|
3048
|
+
const raw = fs9.readFileSync(candidate, "utf8");
|
|
3040
3049
|
const parsed = JSON.parse(raw);
|
|
3041
3050
|
if (parsed?.name && parsed?.version) return parsed;
|
|
3042
3051
|
} catch {
|
|
3043
3052
|
}
|
|
3044
3053
|
}
|
|
3045
|
-
const parent =
|
|
3054
|
+
const parent = path11.dirname(dir);
|
|
3046
3055
|
if (parent === dir) break;
|
|
3047
3056
|
dir = parent;
|
|
3048
3057
|
}
|
|
@@ -3055,15 +3064,15 @@ async function checkForUpdates() {
|
|
|
3055
3064
|
}
|
|
3056
3065
|
const binPath = process.argv?.[1];
|
|
3057
3066
|
let pkg;
|
|
3058
|
-
if (binPath &&
|
|
3059
|
-
const candidatePkg = findPackageJsonNearest(
|
|
3067
|
+
if (binPath && fs9.existsSync(binPath)) {
|
|
3068
|
+
const candidatePkg = findPackageJsonNearest(path11.dirname(binPath));
|
|
3060
3069
|
if (candidatePkg?.name && candidatePkg?.version) {
|
|
3061
3070
|
pkg = candidatePkg;
|
|
3062
3071
|
}
|
|
3063
3072
|
}
|
|
3064
3073
|
if (!pkg) {
|
|
3065
3074
|
const __filename = fileURLToPath3(import.meta.url);
|
|
3066
|
-
const __dirname =
|
|
3075
|
+
const __dirname = path11.dirname(__filename);
|
|
3067
3076
|
const result = await readPackageUp({ cwd: __dirname });
|
|
3068
3077
|
pkg = result?.packageJson;
|
|
3069
3078
|
}
|
|
@@ -3149,26 +3158,26 @@ var ErrorMessage_default = ErrorMessage;
|
|
|
3149
3158
|
// src/app/ui/App.tsx
|
|
3150
3159
|
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3151
3160
|
var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
|
|
3152
|
-
const agentInstance =
|
|
3153
|
-
const [history, setHistory] =
|
|
3154
|
-
const [statusMessage, setStatusMessage] =
|
|
3161
|
+
const agentInstance = useRef2(null);
|
|
3162
|
+
const [history, setHistory] = useState5([]);
|
|
3163
|
+
const [statusMessage, setStatusMessage] = useState5(
|
|
3155
3164
|
"Initializing agent..."
|
|
3156
3165
|
);
|
|
3157
|
-
const [toolsCount, setToolsCount] =
|
|
3158
|
-
const [mcpStatus, setMcpStatus] =
|
|
3166
|
+
const [toolsCount, setToolsCount] = useState5(null);
|
|
3167
|
+
const [mcpStatus, setMcpStatus] = useState5(
|
|
3159
3168
|
"connecting"
|
|
3160
3169
|
);
|
|
3161
|
-
const [isProcessing, setIsProcessing] =
|
|
3162
|
-
const [pendingConfirmation, setPendingConfirmation] =
|
|
3170
|
+
const [isProcessing, setIsProcessing] = useState5(true);
|
|
3171
|
+
const [pendingConfirmation, setPendingConfirmation] = useState5(
|
|
3163
3172
|
null
|
|
3164
3173
|
);
|
|
3165
|
-
const [confirmationPreview, setConfirmationPreview] =
|
|
3174
|
+
const [confirmationPreview, setConfirmationPreview] = useState5(
|
|
3166
3175
|
null
|
|
3167
3176
|
);
|
|
3168
|
-
const [isInitAgentActive, setIsInitAgentActive] =
|
|
3169
|
-
const alwaysAcceptList =
|
|
3177
|
+
const [isInitAgentActive, setIsInitAgentActive] = useState5(false);
|
|
3178
|
+
const alwaysAcceptList = useRef2([]);
|
|
3170
3179
|
const workdir = process.cwd();
|
|
3171
|
-
const updateCheckRan =
|
|
3180
|
+
const updateCheckRan = useRef2(false);
|
|
3172
3181
|
const handleInterrupt = useCallback(() => {
|
|
3173
3182
|
if (!isProcessing) return;
|
|
3174
3183
|
eventBus2.emit("user_interrupt");
|
|
@@ -3192,8 +3201,11 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
|
|
|
3192
3201
|
}
|
|
3193
3202
|
if (cmd === "init") {
|
|
3194
3203
|
setIsInitAgentActive(true);
|
|
3204
|
+
setIsProcessing(true);
|
|
3205
|
+
} else {
|
|
3206
|
+
setIsProcessing(false);
|
|
3207
|
+
setIsInitAgentActive(false);
|
|
3195
3208
|
}
|
|
3196
|
-
setIsProcessing(true);
|
|
3197
3209
|
setHistory((prev) => [
|
|
3198
3210
|
...prev,
|
|
3199
3211
|
{
|
|
@@ -3257,7 +3269,7 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
|
|
|
3257
3269
|
},
|
|
3258
3270
|
[]
|
|
3259
3271
|
);
|
|
3260
|
-
|
|
3272
|
+
useEffect4(() => {
|
|
3261
3273
|
setHistory([{ id: 0, component: /* @__PURE__ */ jsx15(Header, {}) }]);
|
|
3262
3274
|
const initializeAgent = async () => {
|
|
3263
3275
|
try {
|
|
@@ -3391,7 +3403,7 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
|
|
|
3391
3403
|
result: parsed.result
|
|
3392
3404
|
}
|
|
3393
3405
|
);
|
|
3394
|
-
} else if (parsed.type === "
|
|
3406
|
+
} else if (parsed.type === "user_overlay") {
|
|
3395
3407
|
newComponent = /* @__PURE__ */ jsx15(Box15, { marginBottom: 1, children: /* @__PURE__ */ jsxs13(Text14, { color: "gray", children: [
|
|
3396
3408
|
/* @__PURE__ */ jsxs13(Text14, { color: "blue", children: [
|
|
3397
3409
|
">",
|
|
@@ -3418,13 +3430,13 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
|
|
|
3418
3430
|
}
|
|
3419
3431
|
};
|
|
3420
3432
|
const handleUiOverlay = (data) => {
|
|
3421
|
-
eventBus2.emit("
|
|
3433
|
+
eventBus2.emit("user_overlay", data);
|
|
3422
3434
|
};
|
|
3423
|
-
uiEventBus.on("
|
|
3435
|
+
uiEventBus.on("user_overlay", handleUiOverlay);
|
|
3424
3436
|
eventBus2.on("backend_message", handleBackendMessage);
|
|
3425
3437
|
initializeAgent();
|
|
3426
3438
|
return () => {
|
|
3427
|
-
uiEventBus.off("
|
|
3439
|
+
uiEventBus.off("user_overlay", handleUiOverlay);
|
|
3428
3440
|
eventBus2.off("backend_message", handleBackendMessage);
|
|
3429
3441
|
};
|
|
3430
3442
|
}, [eventBus2, sessionId2, handleConfirmation]);
|