@nomad-e/bluma-cli 0.0.109 → 0.0.110
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/native_tools.json +26 -10
- package/dist/main.js +756 -369
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -176,7 +176,8 @@ async function commandStatus(args) {
|
|
|
176
176
|
error: `Command with id "${command_id}" not found. It may have expired or never existed.`
|
|
177
177
|
};
|
|
178
178
|
}
|
|
179
|
-
|
|
179
|
+
const maxWait = Math.min(wait_seconds, 15);
|
|
180
|
+
if (maxWait > 0 && entry.status === "running") {
|
|
180
181
|
await new Promise((resolve) => {
|
|
181
182
|
const checkInterval = setInterval(() => {
|
|
182
183
|
if (entry.status !== "running") {
|
|
@@ -187,7 +188,7 @@ async function commandStatus(args) {
|
|
|
187
188
|
setTimeout(() => {
|
|
188
189
|
clearInterval(checkInterval);
|
|
189
190
|
resolve();
|
|
190
|
-
},
|
|
191
|
+
}, maxWait * 1e3);
|
|
191
192
|
});
|
|
192
193
|
}
|
|
193
194
|
let stdout = entry.stdout;
|
|
@@ -326,160 +327,6 @@ var init_async_command = __esm({
|
|
|
326
327
|
}
|
|
327
328
|
});
|
|
328
329
|
|
|
329
|
-
// src/app/agent/core/llm/tool_call_normalizer.ts
|
|
330
|
-
import { randomUUID } from "crypto";
|
|
331
|
-
var ToolCallNormalizer;
|
|
332
|
-
var init_tool_call_normalizer = __esm({
|
|
333
|
-
"src/app/agent/core/llm/tool_call_normalizer.ts"() {
|
|
334
|
-
"use strict";
|
|
335
|
-
ToolCallNormalizer = class {
|
|
336
|
-
/**
|
|
337
|
-
* Normaliza a mensagem do assistant, convertendo diferentes formatos de tool calls
|
|
338
|
-
*/
|
|
339
|
-
static normalizeAssistantMessage(message) {
|
|
340
|
-
if (message.tool_calls && this.isOpenAIFormat(message.tool_calls)) {
|
|
341
|
-
return message;
|
|
342
|
-
}
|
|
343
|
-
const toolCalls = this.extractToolCalls(message);
|
|
344
|
-
if (toolCalls.length > 0) {
|
|
345
|
-
return {
|
|
346
|
-
role: message.role || "assistant",
|
|
347
|
-
content: message.content || null,
|
|
348
|
-
tool_calls: toolCalls
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
return message;
|
|
352
|
-
}
|
|
353
|
-
/**
|
|
354
|
-
* Verifica se já está no formato OpenAI
|
|
355
|
-
*/
|
|
356
|
-
static isOpenAIFormat(toolCalls) {
|
|
357
|
-
if (!Array.isArray(toolCalls) || toolCalls.length === 0) return false;
|
|
358
|
-
const firstCall = toolCalls[0];
|
|
359
|
-
return typeof firstCall.id === "string" && firstCall.type === "function" && typeof firstCall.function?.name === "string" && typeof firstCall.function?.arguments === "string";
|
|
360
|
-
}
|
|
361
|
-
/**
|
|
362
|
-
* Extrai tool calls de diversos formatos possíveis
|
|
363
|
-
*/
|
|
364
|
-
static extractToolCalls(message) {
|
|
365
|
-
const results = [];
|
|
366
|
-
if (message.tool_calls && Array.isArray(message.tool_calls)) {
|
|
367
|
-
for (const call of message.tool_calls) {
|
|
368
|
-
const normalized = this.normalizeToolCall(call);
|
|
369
|
-
if (normalized) results.push(normalized);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
if (typeof message.content === "string" && message.content.trim()) {
|
|
373
|
-
const extracted = this.extractFromContent(message.content);
|
|
374
|
-
results.push(...extracted);
|
|
375
|
-
}
|
|
376
|
-
if (message.function_call) {
|
|
377
|
-
const normalized = this.normalizeToolCall(message.function_call);
|
|
378
|
-
if (normalized) results.push(normalized);
|
|
379
|
-
}
|
|
380
|
-
return results;
|
|
381
|
-
}
|
|
382
|
-
/**
|
|
383
|
-
* Normaliza um único tool call para o formato OpenAI
|
|
384
|
-
*/
|
|
385
|
-
static normalizeToolCall(call) {
|
|
386
|
-
try {
|
|
387
|
-
if (call.id && call.function?.name) {
|
|
388
|
-
return {
|
|
389
|
-
id: call.id,
|
|
390
|
-
type: "function",
|
|
391
|
-
function: {
|
|
392
|
-
name: call.function.name,
|
|
393
|
-
arguments: typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments)
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
if (call.name) {
|
|
398
|
-
return {
|
|
399
|
-
id: call.id || randomUUID(),
|
|
400
|
-
type: "function",
|
|
401
|
-
function: {
|
|
402
|
-
name: call.name,
|
|
403
|
-
arguments: typeof call.arguments === "string" ? call.arguments : JSON.stringify(call.arguments || {})
|
|
404
|
-
}
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
if (call.function && typeof call.function === "object") {
|
|
408
|
-
return {
|
|
409
|
-
id: call.id || randomUUID(),
|
|
410
|
-
type: "function",
|
|
411
|
-
function: {
|
|
412
|
-
name: call.function.name,
|
|
413
|
-
arguments: typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments || {})
|
|
414
|
-
}
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
return null;
|
|
418
|
-
} catch (error) {
|
|
419
|
-
console.error("Error normalizing tool call:", error, call);
|
|
420
|
-
return null;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
/**
|
|
424
|
-
* Extrai tool calls do content (pode estar em markdown, JSON, etc)
|
|
425
|
-
*/
|
|
426
|
-
static extractFromContent(content) {
|
|
427
|
-
const results = [];
|
|
428
|
-
const cleanContent = content.replace(/```(?:json)?\s*([\s\S]*?)```/g, "$1");
|
|
429
|
-
const jsonMatches = this.extractJsonObjects(cleanContent);
|
|
430
|
-
for (const jsonStr of jsonMatches) {
|
|
431
|
-
try {
|
|
432
|
-
const parsed = JSON.parse(jsonStr);
|
|
433
|
-
if (Array.isArray(parsed)) {
|
|
434
|
-
for (const call of parsed) {
|
|
435
|
-
const normalized = this.normalizeToolCall(call);
|
|
436
|
-
if (normalized) results.push(normalized);
|
|
437
|
-
}
|
|
438
|
-
} else if (parsed.name || parsed.function) {
|
|
439
|
-
const normalized = this.normalizeToolCall(parsed);
|
|
440
|
-
if (normalized) results.push(normalized);
|
|
441
|
-
} else if (parsed.tool_calls && Array.isArray(parsed.tool_calls)) {
|
|
442
|
-
for (const call of parsed.tool_calls) {
|
|
443
|
-
const normalized = this.normalizeToolCall(call);
|
|
444
|
-
if (normalized) results.push(normalized);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
} catch (e) {
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
return results;
|
|
451
|
-
}
|
|
452
|
-
/**
|
|
453
|
-
* Extrai objetos JSON de uma string (suporta múltiplos objetos)
|
|
454
|
-
*/
|
|
455
|
-
static extractJsonObjects(text) {
|
|
456
|
-
const results = [];
|
|
457
|
-
let depth = 0;
|
|
458
|
-
let start = -1;
|
|
459
|
-
for (let i = 0; i < text.length; i++) {
|
|
460
|
-
if (text[i] === "{") {
|
|
461
|
-
if (depth === 0) start = i;
|
|
462
|
-
depth++;
|
|
463
|
-
} else if (text[i] === "}") {
|
|
464
|
-
depth--;
|
|
465
|
-
if (depth === 0 && start !== -1) {
|
|
466
|
-
results.push(text.substring(start, i + 1));
|
|
467
|
-
start = -1;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
return results;
|
|
472
|
-
}
|
|
473
|
-
/**
|
|
474
|
-
* Valida se um tool call normalizado é válido
|
|
475
|
-
*/
|
|
476
|
-
static isValidToolCall(call) {
|
|
477
|
-
return !!(call.id && call.type === "function" && call.function?.name && typeof call.function.arguments === "string");
|
|
478
|
-
}
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
});
|
|
482
|
-
|
|
483
330
|
// src/main.ts
|
|
484
331
|
import React10 from "react";
|
|
485
332
|
import { render } from "ink";
|
|
@@ -1635,10 +1482,9 @@ var ConfirmationPromptComponent = ({ toolCalls, preview, onDecision }) => {
|
|
|
1635
1482
|
var ConfirmationPrompt = memo4(ConfirmationPromptComponent);
|
|
1636
1483
|
|
|
1637
1484
|
// src/app/agent/agent.ts
|
|
1638
|
-
import OpenAI from "openai";
|
|
1639
1485
|
import * as dotenv from "dotenv";
|
|
1640
|
-
import
|
|
1641
|
-
import
|
|
1486
|
+
import path16 from "path";
|
|
1487
|
+
import os9 from "os";
|
|
1642
1488
|
|
|
1643
1489
|
// src/app/agent/tool_invoker.ts
|
|
1644
1490
|
import { promises as fs8 } from "fs";
|
|
@@ -1987,20 +1833,20 @@ ${finalDiff}`,
|
|
|
1987
1833
|
|
|
1988
1834
|
// src/app/agent/tools/natives/message.ts
|
|
1989
1835
|
import { v4 as uuidv4 } from "uuid";
|
|
1990
|
-
function
|
|
1991
|
-
const {
|
|
1992
|
-
const
|
|
1993
|
-
type: "
|
|
1994
|
-
|
|
1836
|
+
function message(args) {
|
|
1837
|
+
const { content, message_type } = args;
|
|
1838
|
+
const result = {
|
|
1839
|
+
type: "message",
|
|
1840
|
+
message_type,
|
|
1841
|
+
id: `msg_${uuidv4()}`,
|
|
1995
1842
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1996
1843
|
content: {
|
|
1997
|
-
|
|
1998
|
-
body: message
|
|
1844
|
+
body: content
|
|
1999
1845
|
},
|
|
2000
1846
|
success: true,
|
|
2001
1847
|
delivered: true
|
|
2002
1848
|
};
|
|
2003
|
-
return Promise.resolve(
|
|
1849
|
+
return Promise.resolve(result);
|
|
2004
1850
|
}
|
|
2005
1851
|
|
|
2006
1852
|
// src/app/agent/tools/natives/ls.ts
|
|
@@ -2152,7 +1998,7 @@ import * as path5 from "path";
|
|
|
2152
1998
|
var taskStore = [];
|
|
2153
1999
|
var nextId = 1;
|
|
2154
2000
|
function getTodoFilePath() {
|
|
2155
|
-
return path5.join(process.cwd(), ".bluma", "todo.json");
|
|
2001
|
+
return path5.join(process.cwd(), ".bluma-cli", "todo.json");
|
|
2156
2002
|
}
|
|
2157
2003
|
function loadTasksFromFile() {
|
|
2158
2004
|
try {
|
|
@@ -2203,10 +2049,10 @@ function validateDescription(desc) {
|
|
|
2203
2049
|
}
|
|
2204
2050
|
return null;
|
|
2205
2051
|
}
|
|
2206
|
-
function createResult(success,
|
|
2052
|
+
function createResult(success, message2) {
|
|
2207
2053
|
return {
|
|
2208
2054
|
success,
|
|
2209
|
-
message,
|
|
2055
|
+
message: message2,
|
|
2210
2056
|
tasks: taskStore,
|
|
2211
2057
|
stats: calculateStats()
|
|
2212
2058
|
};
|
|
@@ -3087,7 +2933,7 @@ var artifactsDir = null;
|
|
|
3087
2933
|
async function getArtifactsDir() {
|
|
3088
2934
|
if (artifactsDir) return artifactsDir;
|
|
3089
2935
|
const homeDir = os3.homedir();
|
|
3090
|
-
const baseDir = path9.join(homeDir, ".bluma", "artifacts");
|
|
2936
|
+
const baseDir = path9.join(homeDir, ".bluma-cli", "artifacts");
|
|
3091
2937
|
const sessionId2 = Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
|
|
3092
2938
|
artifactsDir = path9.join(baseDir, sessionId2);
|
|
3093
2939
|
await fs7.mkdir(artifactsDir, { recursive: true });
|
|
@@ -3409,6 +3255,44 @@ async function searchWeb(args) {
|
|
|
3409
3255
|
}
|
|
3410
3256
|
}
|
|
3411
3257
|
|
|
3258
|
+
// src/app/agent/tools/natives/load_skill.ts
|
|
3259
|
+
var globalContext = null;
|
|
3260
|
+
function initializeSkillContext(ctx) {
|
|
3261
|
+
globalContext = ctx;
|
|
3262
|
+
}
|
|
3263
|
+
async function loadSkill(args) {
|
|
3264
|
+
const { skill_name } = args;
|
|
3265
|
+
if (!skill_name || typeof skill_name !== "string") {
|
|
3266
|
+
return {
|
|
3267
|
+
success: false,
|
|
3268
|
+
message: "skill_name is required"
|
|
3269
|
+
};
|
|
3270
|
+
}
|
|
3271
|
+
if (!globalContext?.skillLoader) {
|
|
3272
|
+
return {
|
|
3273
|
+
success: false,
|
|
3274
|
+
message: "Skill system not initialized. No skills available."
|
|
3275
|
+
};
|
|
3276
|
+
}
|
|
3277
|
+
const skill = globalContext.skillLoader.load(skill_name);
|
|
3278
|
+
if (!skill) {
|
|
3279
|
+
const available = globalContext.skillLoader.listAvailable();
|
|
3280
|
+
const availableNames = available.map((s) => s.name).join(", ") || "none";
|
|
3281
|
+
return {
|
|
3282
|
+
success: false,
|
|
3283
|
+
message: `Skill "${skill_name}" not found. Available skills: ${availableNames}`
|
|
3284
|
+
};
|
|
3285
|
+
}
|
|
3286
|
+
return {
|
|
3287
|
+
success: true,
|
|
3288
|
+
message: `Skill "${skill_name}" loaded. Follow these instructions:
|
|
3289
|
+
|
|
3290
|
+
${skill.content}`,
|
|
3291
|
+
skill_name: skill.name,
|
|
3292
|
+
description: skill.description
|
|
3293
|
+
};
|
|
3294
|
+
}
|
|
3295
|
+
|
|
3412
3296
|
// src/app/agent/tool_invoker.ts
|
|
3413
3297
|
var ToolInvoker = class {
|
|
3414
3298
|
// Mapa privado para associar nomes de ferramentas às suas funções de implementação.
|
|
@@ -3452,13 +3336,13 @@ var ToolInvoker = class {
|
|
|
3452
3336
|
this.toolImplementations.set("command_status", commandStatus);
|
|
3453
3337
|
this.toolImplementations.set("send_command_input", sendCommandInput);
|
|
3454
3338
|
this.toolImplementations.set("kill_command", killCommand);
|
|
3455
|
-
this.toolImplementations.set("
|
|
3339
|
+
this.toolImplementations.set("message", message);
|
|
3456
3340
|
this.toolImplementations.set("todo", todo);
|
|
3457
3341
|
this.toolImplementations.set("task_boundary", taskBoundary);
|
|
3458
3342
|
this.toolImplementations.set("create_artifact", createArtifact);
|
|
3459
3343
|
this.toolImplementations.set("read_artifact", readArtifact);
|
|
3460
3344
|
this.toolImplementations.set("search_web", searchWeb);
|
|
3461
|
-
this.toolImplementations.set("
|
|
3345
|
+
this.toolImplementations.set("load_skill", loadSkill);
|
|
3462
3346
|
}
|
|
3463
3347
|
/**
|
|
3464
3348
|
* Retorna a lista de definições de todas as ferramentas nativas carregadas.
|
|
@@ -3688,7 +3572,7 @@ var AdvancedFeedbackSystem = class {
|
|
|
3688
3572
|
};
|
|
3689
3573
|
|
|
3690
3574
|
// src/app/agent/bluma/core/bluma.ts
|
|
3691
|
-
import
|
|
3575
|
+
import path15 from "path";
|
|
3692
3576
|
|
|
3693
3577
|
// src/app/agent/session_manager/session_manager.ts
|
|
3694
3578
|
import path12 from "path";
|
|
@@ -3854,10 +3738,180 @@ async function saveSessionHistory(sessionFile, history) {
|
|
|
3854
3738
|
}
|
|
3855
3739
|
|
|
3856
3740
|
// src/app/agent/core/prompt/prompt_builder.ts
|
|
3857
|
-
import
|
|
3741
|
+
import os7 from "os";
|
|
3742
|
+
import fs12 from "fs";
|
|
3743
|
+
import path14 from "path";
|
|
3744
|
+
import { execSync } from "child_process";
|
|
3745
|
+
|
|
3746
|
+
// src/app/agent/skills/skill_loader.ts
|
|
3858
3747
|
import fs11 from "fs";
|
|
3859
3748
|
import path13 from "path";
|
|
3860
|
-
import
|
|
3749
|
+
import os6 from "os";
|
|
3750
|
+
var SkillLoader = class {
|
|
3751
|
+
projectSkillsDir;
|
|
3752
|
+
globalSkillsDir;
|
|
3753
|
+
cache = /* @__PURE__ */ new Map();
|
|
3754
|
+
constructor(projectRoot) {
|
|
3755
|
+
this.projectSkillsDir = path13.join(projectRoot, ".bluma", "skills");
|
|
3756
|
+
this.globalSkillsDir = path13.join(os6.homedir(), ".bluma", "skills");
|
|
3757
|
+
}
|
|
3758
|
+
/**
|
|
3759
|
+
* Lista skills disponíveis de ambas as fontes
|
|
3760
|
+
* Skills de projeto têm prioridade sobre globais com mesmo nome
|
|
3761
|
+
*/
|
|
3762
|
+
listAvailable() {
|
|
3763
|
+
const skills = /* @__PURE__ */ new Map();
|
|
3764
|
+
const globalSkills = this.listFromDir(this.globalSkillsDir, "global");
|
|
3765
|
+
for (const skill of globalSkills) {
|
|
3766
|
+
skills.set(skill.name, skill);
|
|
3767
|
+
}
|
|
3768
|
+
const projectSkills = this.listFromDir(this.projectSkillsDir, "project");
|
|
3769
|
+
for (const skill of projectSkills) {
|
|
3770
|
+
skills.set(skill.name, skill);
|
|
3771
|
+
}
|
|
3772
|
+
return Array.from(skills.values());
|
|
3773
|
+
}
|
|
3774
|
+
/**
|
|
3775
|
+
* Lista skills de um diretório específico
|
|
3776
|
+
*/
|
|
3777
|
+
listFromDir(dir, source) {
|
|
3778
|
+
if (!fs11.existsSync(dir)) return [];
|
|
3779
|
+
try {
|
|
3780
|
+
return fs11.readdirSync(dir).filter((d) => {
|
|
3781
|
+
const fullPath = path13.join(dir, d);
|
|
3782
|
+
return fs11.statSync(fullPath).isDirectory() && fs11.existsSync(path13.join(fullPath, "SKILL.md"));
|
|
3783
|
+
}).map((d) => this.loadMetadataFromPath(path13.join(dir, d, "SKILL.md"), d, source)).filter((m) => m !== null);
|
|
3784
|
+
} catch {
|
|
3785
|
+
return [];
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
/**
|
|
3789
|
+
* Carrega metadata de um path específico
|
|
3790
|
+
*/
|
|
3791
|
+
loadMetadataFromPath(skillPath, skillName, source) {
|
|
3792
|
+
if (!fs11.existsSync(skillPath)) return null;
|
|
3793
|
+
try {
|
|
3794
|
+
const raw = fs11.readFileSync(skillPath, "utf-8");
|
|
3795
|
+
const parsed = this.parseFrontmatter(raw);
|
|
3796
|
+
return {
|
|
3797
|
+
name: parsed.name || skillName,
|
|
3798
|
+
description: parsed.description || "",
|
|
3799
|
+
source,
|
|
3800
|
+
version: parsed.version,
|
|
3801
|
+
author: parsed.author,
|
|
3802
|
+
license: parsed.license
|
|
3803
|
+
};
|
|
3804
|
+
} catch {
|
|
3805
|
+
return null;
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3808
|
+
/**
|
|
3809
|
+
* Carrega skill completa - procura primeiro em projeto, depois global
|
|
3810
|
+
*/
|
|
3811
|
+
load(name) {
|
|
3812
|
+
if (this.cache.has(name)) return this.cache.get(name);
|
|
3813
|
+
const projectPath = path13.join(this.projectSkillsDir, name, "SKILL.md");
|
|
3814
|
+
if (fs11.existsSync(projectPath)) {
|
|
3815
|
+
const skill = this.loadFromPath(projectPath, name, "project");
|
|
3816
|
+
if (skill) {
|
|
3817
|
+
this.cache.set(name, skill);
|
|
3818
|
+
return skill;
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
const globalPath = path13.join(this.globalSkillsDir, name, "SKILL.md");
|
|
3822
|
+
if (fs11.existsSync(globalPath)) {
|
|
3823
|
+
const skill = this.loadFromPath(globalPath, name, "global");
|
|
3824
|
+
if (skill) {
|
|
3825
|
+
this.cache.set(name, skill);
|
|
3826
|
+
return skill;
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
return null;
|
|
3830
|
+
}
|
|
3831
|
+
/**
|
|
3832
|
+
* Carrega skill de um path específico
|
|
3833
|
+
*/
|
|
3834
|
+
loadFromPath(skillPath, name, source) {
|
|
3835
|
+
try {
|
|
3836
|
+
const raw = fs11.readFileSync(skillPath, "utf-8");
|
|
3837
|
+
const parsed = this.parseFrontmatter(raw);
|
|
3838
|
+
return {
|
|
3839
|
+
name: parsed.name || name,
|
|
3840
|
+
description: parsed.description || "",
|
|
3841
|
+
content: parsed.content.trim(),
|
|
3842
|
+
source,
|
|
3843
|
+
version: parsed.version,
|
|
3844
|
+
author: parsed.author,
|
|
3845
|
+
license: parsed.license
|
|
3846
|
+
};
|
|
3847
|
+
} catch {
|
|
3848
|
+
return null;
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
/**
|
|
3852
|
+
* Parse simples de YAML frontmatter
|
|
3853
|
+
*/
|
|
3854
|
+
parseFrontmatter(raw) {
|
|
3855
|
+
const lines = raw.split("\n");
|
|
3856
|
+
if (lines[0]?.trim() !== "---") {
|
|
3857
|
+
return { content: raw };
|
|
3858
|
+
}
|
|
3859
|
+
let endIndex = -1;
|
|
3860
|
+
for (let i = 1; i < lines.length; i++) {
|
|
3861
|
+
if (lines[i]?.trim() === "---") {
|
|
3862
|
+
endIndex = i;
|
|
3863
|
+
break;
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
if (endIndex === -1) {
|
|
3867
|
+
return { content: raw };
|
|
3868
|
+
}
|
|
3869
|
+
const frontmatter = {};
|
|
3870
|
+
for (let i = 1; i < endIndex; i++) {
|
|
3871
|
+
const line = lines[i];
|
|
3872
|
+
const colonIndex = line.indexOf(":");
|
|
3873
|
+
if (colonIndex > 0) {
|
|
3874
|
+
const key = line.slice(0, colonIndex).trim();
|
|
3875
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
3876
|
+
frontmatter[key] = value;
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
const content = lines.slice(endIndex + 1).join("\n");
|
|
3880
|
+
return {
|
|
3881
|
+
name: frontmatter["name"],
|
|
3882
|
+
description: frontmatter["description"],
|
|
3883
|
+
version: frontmatter["version"],
|
|
3884
|
+
author: frontmatter["author"],
|
|
3885
|
+
license: frontmatter["license"],
|
|
3886
|
+
content
|
|
3887
|
+
};
|
|
3888
|
+
}
|
|
3889
|
+
/**
|
|
3890
|
+
* Limpa o cache
|
|
3891
|
+
*/
|
|
3892
|
+
clearCache() {
|
|
3893
|
+
this.cache.clear();
|
|
3894
|
+
}
|
|
3895
|
+
/**
|
|
3896
|
+
* Verifica se uma skill existe (em projeto ou global)
|
|
3897
|
+
*/
|
|
3898
|
+
exists(name) {
|
|
3899
|
+
const projectPath = path13.join(this.projectSkillsDir, name, "SKILL.md");
|
|
3900
|
+
const globalPath = path13.join(this.globalSkillsDir, name, "SKILL.md");
|
|
3901
|
+
return fs11.existsSync(projectPath) || fs11.existsSync(globalPath);
|
|
3902
|
+
}
|
|
3903
|
+
/**
|
|
3904
|
+
* Retorna os diretórios de skills
|
|
3905
|
+
*/
|
|
3906
|
+
getSkillsDirs() {
|
|
3907
|
+
return {
|
|
3908
|
+
project: this.projectSkillsDir,
|
|
3909
|
+
global: this.globalSkillsDir
|
|
3910
|
+
};
|
|
3911
|
+
}
|
|
3912
|
+
};
|
|
3913
|
+
|
|
3914
|
+
// src/app/agent/core/prompt/prompt_builder.ts
|
|
3861
3915
|
function getNodeVersion() {
|
|
3862
3916
|
try {
|
|
3863
3917
|
return process.version;
|
|
@@ -3885,10 +3939,10 @@ function getGitBranch(dir) {
|
|
|
3885
3939
|
}
|
|
3886
3940
|
function getPackageManager(dir) {
|
|
3887
3941
|
try {
|
|
3888
|
-
if (
|
|
3889
|
-
if (
|
|
3890
|
-
if (
|
|
3891
|
-
if (
|
|
3942
|
+
if (fs12.existsSync(path14.join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
3943
|
+
if (fs12.existsSync(path14.join(dir, "yarn.lock"))) return "yarn";
|
|
3944
|
+
if (fs12.existsSync(path14.join(dir, "bun.lockb"))) return "bun";
|
|
3945
|
+
if (fs12.existsSync(path14.join(dir, "package-lock.json"))) return "npm";
|
|
3892
3946
|
return "unknown";
|
|
3893
3947
|
} catch {
|
|
3894
3948
|
return "unknown";
|
|
@@ -3896,9 +3950,9 @@ function getPackageManager(dir) {
|
|
|
3896
3950
|
}
|
|
3897
3951
|
function getProjectType(dir) {
|
|
3898
3952
|
try {
|
|
3899
|
-
const files =
|
|
3953
|
+
const files = fs12.readdirSync(dir);
|
|
3900
3954
|
if (files.includes("package.json")) {
|
|
3901
|
-
const pkg = JSON.parse(
|
|
3955
|
+
const pkg = JSON.parse(fs12.readFileSync(path14.join(dir, "package.json"), "utf-8"));
|
|
3902
3956
|
if (pkg.dependencies?.next || pkg.devDependencies?.next) return "Next.js";
|
|
3903
3957
|
if (pkg.dependencies?.react || pkg.devDependencies?.react) return "React";
|
|
3904
3958
|
if (pkg.dependencies?.express || pkg.devDependencies?.express) return "Express";
|
|
@@ -3917,9 +3971,9 @@ function getProjectType(dir) {
|
|
|
3917
3971
|
}
|
|
3918
3972
|
function getTestFramework(dir) {
|
|
3919
3973
|
try {
|
|
3920
|
-
const pkgPath =
|
|
3921
|
-
if (
|
|
3922
|
-
const pkg = JSON.parse(
|
|
3974
|
+
const pkgPath = path14.join(dir, "package.json");
|
|
3975
|
+
if (fs12.existsSync(pkgPath)) {
|
|
3976
|
+
const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
|
|
3923
3977
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3924
3978
|
if (deps.jest) return "jest";
|
|
3925
3979
|
if (deps.vitest) return "vitest";
|
|
@@ -3928,7 +3982,7 @@ function getTestFramework(dir) {
|
|
|
3928
3982
|
if (deps["@playwright/test"]) return "playwright";
|
|
3929
3983
|
if (deps.cypress) return "cypress";
|
|
3930
3984
|
}
|
|
3931
|
-
if (
|
|
3985
|
+
if (fs12.existsSync(path14.join(dir, "pytest.ini")) || fs12.existsSync(path14.join(dir, "conftest.py"))) return "pytest";
|
|
3932
3986
|
return "unknown";
|
|
3933
3987
|
} catch {
|
|
3934
3988
|
return "unknown";
|
|
@@ -3936,9 +3990,9 @@ function getTestFramework(dir) {
|
|
|
3936
3990
|
}
|
|
3937
3991
|
function getTestCommand(dir) {
|
|
3938
3992
|
try {
|
|
3939
|
-
const pkgPath =
|
|
3940
|
-
if (
|
|
3941
|
-
const pkg = JSON.parse(
|
|
3993
|
+
const pkgPath = path14.join(dir, "package.json");
|
|
3994
|
+
if (fs12.existsSync(pkgPath)) {
|
|
3995
|
+
const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
|
|
3942
3996
|
if (pkg.scripts?.test) return `npm test`;
|
|
3943
3997
|
if (pkg.scripts?.["test:unit"]) return `npm run test:unit`;
|
|
3944
3998
|
}
|
|
@@ -4006,7 +4060,7 @@ You MUST adapt all commands to this environment. Use the correct package manager
|
|
|
4006
4060
|
## How You Work
|
|
4007
4061
|
|
|
4008
4062
|
### For Multi-Step Tasks:
|
|
4009
|
-
1. **Acknowledge** - Confirm the request immediately via
|
|
4063
|
+
1. **Acknowledge** - Confirm the request immediately via message
|
|
4010
4064
|
2. **Plan** - Use the TODO tool to create a structured plan
|
|
4011
4065
|
3. **Execute** - Implement with clear narration of each step
|
|
4012
4066
|
4. **Test** - Run tests after any code changes (MANDATORY for production code)
|
|
@@ -4017,25 +4071,11 @@ You MUST adapt all commands to this environment. Use the correct package manager
|
|
|
4017
4071
|
- Quick acknowledgment + immediate action
|
|
4018
4072
|
- No TODO needed for single operations
|
|
4019
4073
|
|
|
4020
|
-
### Testing Philosophy
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
**ALWAYS run tests when:**
|
|
4024
|
-
- You modify any function or module
|
|
4025
|
-
- You add new functionality
|
|
4026
|
-
- You fix a bug (write a regression test first)
|
|
4027
|
-
- The user asks for a feature
|
|
4028
|
-
|
|
4029
|
-
**Test workflow:**
|
|
4030
|
-
1. Before editing: Run existing tests to ensure baseline passes
|
|
4031
|
-
2. After editing: Run tests to verify nothing broke
|
|
4032
|
-
3. For new features: Write tests alongside implementation
|
|
4033
|
-
4. For bugs: Write failing test \u2192 fix \u2192 verify test passes
|
|
4074
|
+
### Testing Philosophy:
|
|
4075
|
+
Run tests when modifying code. Use the \`testing\` skill for detailed best practices.
|
|
4034
4076
|
|
|
4035
|
-
**Commands
|
|
4077
|
+
**Commands:**
|
|
4036
4078
|
- Run tests: {test_command}
|
|
4037
|
-
- Run single test: Check project conventions
|
|
4038
|
-
- Watch mode: Usually \`npm run test:watch\` or \`npm test -- --watch\`
|
|
4039
4079
|
</workflow>
|
|
4040
4080
|
|
|
4041
4081
|
---
|
|
@@ -4050,8 +4090,8 @@ You are a **teammate who writes tests**, not an assistant who skips them.
|
|
|
4050
4090
|
- **Check file exists** before attempting edits
|
|
4051
4091
|
|
|
4052
4092
|
### Safe Auto-Approved Tools (no confirmation needed):
|
|
4053
|
-
-
|
|
4054
|
-
- ls_tool
|
|
4093
|
+
- message
|
|
4094
|
+
- ls_tool
|
|
4055
4095
|
- read_file_lines
|
|
4056
4096
|
- count_file_lines
|
|
4057
4097
|
- todo
|
|
@@ -4102,7 +4142,7 @@ You MUST use \`command_status\` to get the output.
|
|
|
4102
4142
|
[Step 2] command_status({ command_id: "cmd_001", wait_seconds: 120 })
|
|
4103
4143
|
\u2192 { status: "completed", exit_code: 0, stdout: "added 150 packages..." }
|
|
4104
4144
|
|
|
4105
|
-
[Step 3]
|
|
4145
|
+
[Step 3] message({ content: "Dependencies installed.", message_type: "info" })
|
|
4106
4146
|
\`\`\`
|
|
4107
4147
|
|
|
4108
4148
|
**[OK] Run tests:**
|
|
@@ -4131,7 +4171,7 @@ You MUST use \`command_status\` to get the output.
|
|
|
4131
4171
|
[Step 2] command_status({ command_id: "cmd_004", wait_seconds: 10 })
|
|
4132
4172
|
\u2192 { status: "running" }
|
|
4133
4173
|
|
|
4134
|
-
[Step 3]
|
|
4174
|
+
[Step 3] message({ content: "Building Docker image...", message_type: "info" })
|
|
4135
4175
|
|
|
4136
4176
|
[Step 4] command_status({ command_id: "cmd_004", wait_seconds: 10 })
|
|
4137
4177
|
\u2192 { status: "completed", exit_code: 0, stdout: "Successfully built abc123" }
|
|
@@ -4140,7 +4180,7 @@ You MUST use \`command_status\` to get the output.
|
|
|
4140
4180
|
**[WRONG] Never forget command_status:**
|
|
4141
4181
|
\`\`\`
|
|
4142
4182
|
shell_command({ command: "npm install" })
|
|
4143
|
-
|
|
4183
|
+
message({ content: "Installed!", message_type: "info" }) // WRONG: You don't know if it succeeded!
|
|
4144
4184
|
\`\`\`
|
|
4145
4185
|
|
|
4146
4186
|
**[WRONG] Never use long waits (blocks user):**
|
|
@@ -4185,39 +4225,29 @@ Examples:
|
|
|
4185
4225
|
- Celebrate wins, acknowledge mistakes
|
|
4186
4226
|
|
|
4187
4227
|
**Message via tool only:**
|
|
4188
|
-
-
|
|
4189
|
-
-
|
|
4190
|
-
- Report progress proactively
|
|
4228
|
+
- message with \`result\` = END your turn (after questions/completions)
|
|
4229
|
+
- message with \`info\` = progress update, you continue working
|
|
4191
4230
|
|
|
4192
|
-
**
|
|
4231
|
+
**CRITICAL: After asking questions, ALWAYS use result!**
|
|
4232
|
+
|
|
4233
|
+
**Asking question (result):**
|
|
4193
4234
|
\`\`\`
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
message_notify_user("Found the router at /src/routes/index.ts. I'll add the middleware before the protected routes.")
|
|
4197
|
-
[uses edit_tool]
|
|
4198
|
-
message_notify_user("Done. Running tests to verify...")
|
|
4199
|
-
[uses shell_command with {test_command}]
|
|
4200
|
-
message_notify_user("All 47 tests pass. The middleware is working correctly.")
|
|
4235
|
+
message({ content: "Project or global?", message_type: "result" })
|
|
4236
|
+
// STOP and wait for user
|
|
4201
4237
|
\`\`\`
|
|
4202
|
-
</communication_style>
|
|
4203
|
-
|
|
4204
|
-
---
|
|
4205
|
-
|
|
4206
|
-
<git_guidelines>
|
|
4207
|
-
## Git Best Practices
|
|
4208
4238
|
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4239
|
+
**Progress then continue (info):**
|
|
4240
|
+
\`\`\`
|
|
4241
|
+
message({ content: "Installing...", message_type: "info" })
|
|
4242
|
+
// continue with next tool
|
|
4243
|
+
\`\`\`
|
|
4214
4244
|
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
</
|
|
4245
|
+
**Task done (result):**
|
|
4246
|
+
\`\`\`
|
|
4247
|
+
message({ content: "Done. All tests pass.", message_type: "result" })
|
|
4248
|
+
// STOP - finished
|
|
4249
|
+
\`\`\`
|
|
4250
|
+
</communication_style>
|
|
4221
4251
|
|
|
4222
4252
|
---
|
|
4223
4253
|
|
|
@@ -4254,15 +4284,15 @@ You communicate because you respect your teammate.
|
|
|
4254
4284
|
Let's build something great, {username}.
|
|
4255
4285
|
</personality>
|
|
4256
4286
|
`;
|
|
4257
|
-
function getUnifiedSystemPrompt() {
|
|
4287
|
+
function getUnifiedSystemPrompt(availableSkills) {
|
|
4258
4288
|
const cwd = process.cwd();
|
|
4259
4289
|
const env = {
|
|
4260
|
-
os_type:
|
|
4261
|
-
os_version:
|
|
4262
|
-
architecture:
|
|
4290
|
+
os_type: os7.type(),
|
|
4291
|
+
os_version: os7.release(),
|
|
4292
|
+
architecture: os7.arch(),
|
|
4263
4293
|
workdir: cwd,
|
|
4264
4294
|
shell_type: process.env.SHELL || process.env.COMSPEC || "unknown",
|
|
4265
|
-
username:
|
|
4295
|
+
username: os7.userInfo().username,
|
|
4266
4296
|
current_date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
4267
4297
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
4268
4298
|
is_git_repo: isGitRepo(cwd) ? "yes" : "no",
|
|
@@ -4274,15 +4304,164 @@ function getUnifiedSystemPrompt() {
|
|
|
4274
4304
|
test_framework: getTestFramework(cwd),
|
|
4275
4305
|
test_command: getTestCommand(cwd)
|
|
4276
4306
|
};
|
|
4277
|
-
|
|
4278
|
-
(
|
|
4307
|
+
let prompt = Object.entries(env).reduce(
|
|
4308
|
+
(p, [key, value]) => p.replaceAll(`{${key}}`, value),
|
|
4279
4309
|
SYSTEM_PROMPT
|
|
4280
4310
|
);
|
|
4311
|
+
if (availableSkills && availableSkills.length > 0) {
|
|
4312
|
+
const skillsList = availableSkills.map((s) => `- ${s.name}: ${s.description}`).join("\n");
|
|
4313
|
+
prompt += `
|
|
4314
|
+
|
|
4315
|
+
---
|
|
4316
|
+
|
|
4317
|
+
<available_skills>
|
|
4318
|
+
## Specialized Skills
|
|
4319
|
+
|
|
4320
|
+
You have access to specialized knowledge modules called **skills**. Skills extend your capabilities with domain-specific expertise, workflows, and best practices.
|
|
4321
|
+
|
|
4322
|
+
**Available skills:**
|
|
4323
|
+
${skillsList}
|
|
4324
|
+
|
|
4325
|
+
---
|
|
4326
|
+
|
|
4327
|
+
### Skill Selection Process
|
|
4328
|
+
|
|
4329
|
+
**BEFORE starting any task, follow this decision process:**
|
|
4330
|
+
|
|
4331
|
+
1. **Understand the task** - What is the user trying to accomplish?
|
|
4332
|
+
2. **Identify the domain** - What area of expertise does this task require? (e.g., version control, testing, deployment, documentation)
|
|
4333
|
+
3. **Match against skills** - Read each skill's description above. Does any skill's domain match the task?
|
|
4334
|
+
4. **Load if matched** - If a skill matches, load it FIRST with \`load_skill\` before taking any action
|
|
4335
|
+
5. **Follow skill instructions** - Once loaded, the skill provides specific workflows to follow
|
|
4336
|
+
|
|
4337
|
+
**Decision examples:**
|
|
4338
|
+
\`\`\`
|
|
4339
|
+
Task: "Save my changes with a good message"
|
|
4340
|
+
\u2192 Domain: Version control, commits
|
|
4341
|
+
\u2192 Check skills: Is there a skill for git/commits/version control?
|
|
4342
|
+
\u2192 If yes: load_skill({ skill_name: "<matching-skill>" })
|
|
4343
|
+
|
|
4344
|
+
Task: "Make sure this code works correctly"
|
|
4345
|
+
\u2192 Domain: Testing, validation, quality assurance
|
|
4346
|
+
\u2192 Check skills: Is there a skill for testing?
|
|
4347
|
+
\u2192 If yes: load_skill({ skill_name: "<matching-skill>" })
|
|
4348
|
+
|
|
4349
|
+
Task: "Release this to users"
|
|
4350
|
+
\u2192 Domain: Publishing, deployment, release
|
|
4351
|
+
\u2192 Check skills: Is there a skill for publishing/releasing?
|
|
4352
|
+
\u2192 If yes: load_skill({ skill_name: "<matching-skill>" })
|
|
4353
|
+
\`\`\`
|
|
4354
|
+
|
|
4355
|
+
**Key principle:** Match the TASK DOMAIN to the SKILL DESCRIPTION, not specific words.
|
|
4356
|
+
|
|
4357
|
+
---
|
|
4358
|
+
|
|
4359
|
+
### Skill Interpretation Rules (CRITICAL)
|
|
4360
|
+
|
|
4361
|
+
When you load a skill with \`load_skill\`, you receive a SKILL.md file. **ALWAYS read the YAML frontmatter header FIRST** before reading the body.
|
|
4362
|
+
|
|
4363
|
+
**Frontmatter Structure:**
|
|
4364
|
+
\`\`\`yaml
|
|
4365
|
+
---
|
|
4366
|
+
# IDENTITY
|
|
4367
|
+
name: skill-name # Unique identifier
|
|
4368
|
+
description: ... # Purpose of the skill
|
|
4369
|
+
version: 1.0.0
|
|
4370
|
+
author: ...
|
|
4371
|
+
license: ...
|
|
4372
|
+
|
|
4373
|
+
# DEPENDENCIES
|
|
4374
|
+
depends_on: # Other skills this skill may delegate to
|
|
4375
|
+
- other-skill-name
|
|
4376
|
+
|
|
4377
|
+
# TOOLS
|
|
4378
|
+
tools:
|
|
4379
|
+
required: # Tools you MUST use for this skill
|
|
4380
|
+
- shell_command
|
|
4381
|
+
- command_status
|
|
4382
|
+
recommended: # Tools that enhance execution
|
|
4383
|
+
- read_file_lines
|
|
4384
|
+
---
|
|
4385
|
+
\`\`\`
|
|
4386
|
+
|
|
4387
|
+
**Interpretation Flow:**
|
|
4388
|
+
\`\`\`
|
|
4389
|
+
1. LOAD SKILL
|
|
4390
|
+
load_skill({ skill_name: "npm-publish" })
|
|
4391
|
+
|
|
4392
|
+
2. READ FRONTMATTER FIRST
|
|
4393
|
+
- name: npm-publish
|
|
4394
|
+
- depends_on: [git-conventional]
|
|
4395
|
+
- tools.required: [shell_command, command_status]
|
|
4396
|
+
- tools.recommended: [read_file_lines, message]
|
|
4397
|
+
|
|
4398
|
+
3. VERIFY TOOLS
|
|
4399
|
+
Are required tools available? If not, inform user.
|
|
4400
|
+
|
|
4401
|
+
4. READ SKILL BODY
|
|
4402
|
+
Workflow instructions, examples, rules
|
|
4403
|
+
|
|
4404
|
+
5. EXECUTE WORKFLOW
|
|
4405
|
+
- Use the REQUIRED tools from frontmatter
|
|
4406
|
+
- When body says "delegate to X":
|
|
4407
|
+
a. Verify X is in depends_on
|
|
4408
|
+
b. load_skill({ skill_name: "X" })
|
|
4409
|
+
c. Execute X workflow
|
|
4410
|
+
d. Return to primary skill
|
|
4411
|
+
- Continue until workflow complete
|
|
4412
|
+
\`\`\`
|
|
4413
|
+
|
|
4414
|
+
**Field Meanings:**
|
|
4415
|
+
| Field | Purpose | Your Action |
|
|
4416
|
+
|-------|---------|-------------|
|
|
4417
|
+
| \`name\` | Skill identifier | Confirm correct skill loaded |
|
|
4418
|
+
| \`description\` | Skill purpose | Understand what this skill does |
|
|
4419
|
+
| \`depends_on\` | Skill dependencies | Know which skills can be delegated to |
|
|
4420
|
+
| \`tools.required\` | Mandatory tools | MUST use these tools for execution |
|
|
4421
|
+
| \`tools.recommended\` | Optional tools | Use if helpful |
|
|
4422
|
+
|
|
4423
|
+
**Orchestration Example:**
|
|
4424
|
+
\`\`\`
|
|
4425
|
+
User: "Publish the package"
|
|
4426
|
+
|
|
4427
|
+
1. Match domain -> npm-publish skill
|
|
4428
|
+
2. load_skill({ skill_name: "npm-publish" })
|
|
4429
|
+
|
|
4430
|
+
3. Read frontmatter:
|
|
4431
|
+
- depends_on: [git-conventional]
|
|
4432
|
+
- tools.required: [shell_command, command_status]
|
|
4433
|
+
|
|
4434
|
+
4. Read body -> Workflow says:
|
|
4435
|
+
"Step 1: If uncommitted changes, delegate to git-conventional"
|
|
4436
|
+
|
|
4437
|
+
5. Check git status with shell_command (required tool)
|
|
4438
|
+
Found changes
|
|
4439
|
+
|
|
4440
|
+
6. Delegate: load_skill({ skill_name: "git-conventional" })
|
|
4441
|
+
- Read git-conventional frontmatter
|
|
4442
|
+
- Follow its workflow for commit
|
|
4443
|
+
- Commit done
|
|
4444
|
+
|
|
4445
|
+
7. Return to npm-publish Step 2: npm version patch
|
|
4446
|
+
8. Step 3: npm publish
|
|
4447
|
+
9. Done
|
|
4448
|
+
\`\`\`
|
|
4449
|
+
|
|
4450
|
+
**Rules:**
|
|
4451
|
+
- Frontmatter is the SPECIFICATION - read it first
|
|
4452
|
+
- Body is the WORKFLOW - execute after understanding spec
|
|
4453
|
+
- \`depends_on\` authorizes delegation - only delegate to listed skills
|
|
4454
|
+
- \`tools.required\` are mandatory - use them for execution
|
|
4455
|
+
- The skill body tells you WHEN to delegate, not the frontmatter
|
|
4456
|
+
</available_skills>
|
|
4457
|
+
`;
|
|
4458
|
+
}
|
|
4459
|
+
return prompt;
|
|
4281
4460
|
}
|
|
4282
4461
|
function isGitRepo(dir) {
|
|
4283
4462
|
try {
|
|
4284
|
-
const gitPath =
|
|
4285
|
-
return
|
|
4463
|
+
const gitPath = path14.join(dir, ".git");
|
|
4464
|
+
return fs12.existsSync(gitPath) && fs12.lstatSync(gitPath).isDirectory();
|
|
4286
4465
|
} catch {
|
|
4287
4466
|
return false;
|
|
4288
4467
|
}
|
|
@@ -4338,8 +4517,155 @@ function createApiContextWindow(fullHistory, maxTurns) {
|
|
|
4338
4517
|
return finalContext;
|
|
4339
4518
|
}
|
|
4340
4519
|
|
|
4520
|
+
// src/app/agent/core/llm/tool_call_normalizer.ts
|
|
4521
|
+
import { randomUUID } from "crypto";
|
|
4522
|
+
var ToolCallNormalizer = class {
|
|
4523
|
+
/**
|
|
4524
|
+
* Normaliza a mensagem do assistant, convertendo diferentes formatos de tool calls
|
|
4525
|
+
*/
|
|
4526
|
+
static normalizeAssistantMessage(message2) {
|
|
4527
|
+
if (message2.tool_calls && this.isOpenAIFormat(message2.tool_calls)) {
|
|
4528
|
+
return message2;
|
|
4529
|
+
}
|
|
4530
|
+
const toolCalls = this.extractToolCalls(message2);
|
|
4531
|
+
if (toolCalls.length > 0) {
|
|
4532
|
+
return {
|
|
4533
|
+
role: message2.role || "assistant",
|
|
4534
|
+
content: message2.content || null,
|
|
4535
|
+
tool_calls: toolCalls
|
|
4536
|
+
};
|
|
4537
|
+
}
|
|
4538
|
+
return message2;
|
|
4539
|
+
}
|
|
4540
|
+
/**
|
|
4541
|
+
* Verifica se já está no formato OpenAI
|
|
4542
|
+
*/
|
|
4543
|
+
static isOpenAIFormat(toolCalls) {
|
|
4544
|
+
if (!Array.isArray(toolCalls) || toolCalls.length === 0) return false;
|
|
4545
|
+
const firstCall = toolCalls[0];
|
|
4546
|
+
return typeof firstCall.id === "string" && firstCall.type === "function" && typeof firstCall.function?.name === "string" && typeof firstCall.function?.arguments === "string";
|
|
4547
|
+
}
|
|
4548
|
+
/**
|
|
4549
|
+
* Extrai tool calls de diversos formatos possíveis
|
|
4550
|
+
*/
|
|
4551
|
+
static extractToolCalls(message2) {
|
|
4552
|
+
const results = [];
|
|
4553
|
+
if (message2.tool_calls && Array.isArray(message2.tool_calls)) {
|
|
4554
|
+
for (const call of message2.tool_calls) {
|
|
4555
|
+
const normalized = this.normalizeToolCall(call);
|
|
4556
|
+
if (normalized) results.push(normalized);
|
|
4557
|
+
}
|
|
4558
|
+
}
|
|
4559
|
+
if (typeof message2.content === "string" && message2.content.trim()) {
|
|
4560
|
+
const extracted = this.extractFromContent(message2.content);
|
|
4561
|
+
results.push(...extracted);
|
|
4562
|
+
}
|
|
4563
|
+
if (message2.function_call) {
|
|
4564
|
+
const normalized = this.normalizeToolCall(message2.function_call);
|
|
4565
|
+
if (normalized) results.push(normalized);
|
|
4566
|
+
}
|
|
4567
|
+
return results;
|
|
4568
|
+
}
|
|
4569
|
+
/**
|
|
4570
|
+
* Normaliza um único tool call para o formato OpenAI
|
|
4571
|
+
*/
|
|
4572
|
+
static normalizeToolCall(call) {
|
|
4573
|
+
try {
|
|
4574
|
+
if (call.id && call.function?.name) {
|
|
4575
|
+
return {
|
|
4576
|
+
id: call.id,
|
|
4577
|
+
type: "function",
|
|
4578
|
+
function: {
|
|
4579
|
+
name: call.function.name,
|
|
4580
|
+
arguments: typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments)
|
|
4581
|
+
}
|
|
4582
|
+
};
|
|
4583
|
+
}
|
|
4584
|
+
if (call.name) {
|
|
4585
|
+
return {
|
|
4586
|
+
id: call.id || randomUUID(),
|
|
4587
|
+
type: "function",
|
|
4588
|
+
function: {
|
|
4589
|
+
name: call.name,
|
|
4590
|
+
arguments: typeof call.arguments === "string" ? call.arguments : JSON.stringify(call.arguments || {})
|
|
4591
|
+
}
|
|
4592
|
+
};
|
|
4593
|
+
}
|
|
4594
|
+
if (call.function && typeof call.function === "object") {
|
|
4595
|
+
return {
|
|
4596
|
+
id: call.id || randomUUID(),
|
|
4597
|
+
type: "function",
|
|
4598
|
+
function: {
|
|
4599
|
+
name: call.function.name,
|
|
4600
|
+
arguments: typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments || {})
|
|
4601
|
+
}
|
|
4602
|
+
};
|
|
4603
|
+
}
|
|
4604
|
+
return null;
|
|
4605
|
+
} catch (error) {
|
|
4606
|
+
console.error("Error normalizing tool call:", error, call);
|
|
4607
|
+
return null;
|
|
4608
|
+
}
|
|
4609
|
+
}
|
|
4610
|
+
/**
|
|
4611
|
+
* Extrai tool calls do content (pode estar em markdown, JSON, etc)
|
|
4612
|
+
*/
|
|
4613
|
+
static extractFromContent(content) {
|
|
4614
|
+
const results = [];
|
|
4615
|
+
const cleanContent = content.replace(/```(?:json)?\s*([\s\S]*?)```/g, "$1");
|
|
4616
|
+
const jsonMatches = this.extractJsonObjects(cleanContent);
|
|
4617
|
+
for (const jsonStr of jsonMatches) {
|
|
4618
|
+
try {
|
|
4619
|
+
const parsed = JSON.parse(jsonStr);
|
|
4620
|
+
if (Array.isArray(parsed)) {
|
|
4621
|
+
for (const call of parsed) {
|
|
4622
|
+
const normalized = this.normalizeToolCall(call);
|
|
4623
|
+
if (normalized) results.push(normalized);
|
|
4624
|
+
}
|
|
4625
|
+
} else if (parsed.name || parsed.function) {
|
|
4626
|
+
const normalized = this.normalizeToolCall(parsed);
|
|
4627
|
+
if (normalized) results.push(normalized);
|
|
4628
|
+
} else if (parsed.tool_calls && Array.isArray(parsed.tool_calls)) {
|
|
4629
|
+
for (const call of parsed.tool_calls) {
|
|
4630
|
+
const normalized = this.normalizeToolCall(call);
|
|
4631
|
+
if (normalized) results.push(normalized);
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4634
|
+
} catch (e) {
|
|
4635
|
+
}
|
|
4636
|
+
}
|
|
4637
|
+
return results;
|
|
4638
|
+
}
|
|
4639
|
+
/**
|
|
4640
|
+
* Extrai objetos JSON de uma string (suporta múltiplos objetos)
|
|
4641
|
+
*/
|
|
4642
|
+
static extractJsonObjects(text) {
|
|
4643
|
+
const results = [];
|
|
4644
|
+
let depth = 0;
|
|
4645
|
+
let start = -1;
|
|
4646
|
+
for (let i = 0; i < text.length; i++) {
|
|
4647
|
+
if (text[i] === "{") {
|
|
4648
|
+
if (depth === 0) start = i;
|
|
4649
|
+
depth++;
|
|
4650
|
+
} else if (text[i] === "}") {
|
|
4651
|
+
depth--;
|
|
4652
|
+
if (depth === 0 && start !== -1) {
|
|
4653
|
+
results.push(text.substring(start, i + 1));
|
|
4654
|
+
start = -1;
|
|
4655
|
+
}
|
|
4656
|
+
}
|
|
4657
|
+
}
|
|
4658
|
+
return results;
|
|
4659
|
+
}
|
|
4660
|
+
/**
|
|
4661
|
+
* Valida se um tool call normalizado é válido
|
|
4662
|
+
*/
|
|
4663
|
+
static isValidToolCall(call) {
|
|
4664
|
+
return !!(call.id && call.type === "function" && call.function?.name && typeof call.function.arguments === "string");
|
|
4665
|
+
}
|
|
4666
|
+
};
|
|
4667
|
+
|
|
4341
4668
|
// src/app/agent/bluma/core/bluma.ts
|
|
4342
|
-
init_tool_call_normalizer();
|
|
4343
4669
|
var BluMaAgent = class {
|
|
4344
4670
|
llm;
|
|
4345
4671
|
deploymentName;
|
|
@@ -4349,6 +4675,7 @@ var BluMaAgent = class {
|
|
|
4349
4675
|
eventBus;
|
|
4350
4676
|
mcpClient;
|
|
4351
4677
|
feedbackSystem;
|
|
4678
|
+
skillLoader;
|
|
4352
4679
|
maxContextTurns = 30;
|
|
4353
4680
|
isInterrupted = false;
|
|
4354
4681
|
constructor(sessionId2, eventBus2, llm, deploymentName, mcpClient, feedbackSystem) {
|
|
@@ -4358,6 +4685,7 @@ var BluMaAgent = class {
|
|
|
4358
4685
|
this.deploymentName = deploymentName;
|
|
4359
4686
|
this.mcpClient = mcpClient;
|
|
4360
4687
|
this.feedbackSystem = feedbackSystem;
|
|
4688
|
+
this.skillLoader = new SkillLoader(process.cwd());
|
|
4361
4689
|
this.eventBus.on("user_interrupt", () => {
|
|
4362
4690
|
this.isInterrupted = true;
|
|
4363
4691
|
this.eventBus.emit("backend_message", { type: "done", status: "interrupted" });
|
|
@@ -4369,6 +4697,10 @@ var BluMaAgent = class {
|
|
|
4369
4697
|
try {
|
|
4370
4698
|
if (this.sessionFile) {
|
|
4371
4699
|
await saveSessionHistory(this.sessionFile, this.history);
|
|
4700
|
+
} else {
|
|
4701
|
+
const [sessionFile, _] = await loadOrcreateSession(this.sessionId);
|
|
4702
|
+
this.sessionFile = sessionFile;
|
|
4703
|
+
await saveSessionHistory(this.sessionFile, this.history);
|
|
4372
4704
|
}
|
|
4373
4705
|
} catch (e) {
|
|
4374
4706
|
this.eventBus.emit("backend_message", { type: "error", message: `Falha ao salvar hist\xF3rico ap\xF3s user_overlay: ${e.message}` });
|
|
@@ -4381,8 +4713,13 @@ var BluMaAgent = class {
|
|
|
4381
4713
|
const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
|
|
4382
4714
|
this.sessionFile = sessionFile;
|
|
4383
4715
|
this.history = history;
|
|
4716
|
+
initializeSkillContext({
|
|
4717
|
+
history: this.history,
|
|
4718
|
+
skillLoader: this.skillLoader
|
|
4719
|
+
});
|
|
4384
4720
|
if (this.history.length === 0) {
|
|
4385
|
-
const
|
|
4721
|
+
const availableSkills = this.skillLoader.listAvailable();
|
|
4722
|
+
const systemPrompt = getUnifiedSystemPrompt(availableSkills);
|
|
4386
4723
|
this.history.push({ role: "system", content: systemPrompt });
|
|
4387
4724
|
await saveSessionHistory(this.sessionFile, this.history);
|
|
4388
4725
|
}
|
|
@@ -4397,6 +4734,9 @@ var BluMaAgent = class {
|
|
|
4397
4734
|
this.isInterrupted = false;
|
|
4398
4735
|
const inputText = String(userInput.content || "").trim();
|
|
4399
4736
|
this.history.push({ role: "user", content: inputText });
|
|
4737
|
+
if (inputText === "/init") {
|
|
4738
|
+
this.eventBus.emit("dispatch", inputText);
|
|
4739
|
+
}
|
|
4400
4740
|
await this._continueConversation();
|
|
4401
4741
|
}
|
|
4402
4742
|
async handleToolResponse(decisionData) {
|
|
@@ -4495,9 +4835,15 @@ var BluMaAgent = class {
|
|
|
4495
4835
|
});
|
|
4496
4836
|
}
|
|
4497
4837
|
this.eventBus.emit("backend_message", { type: "tool_result", tool_name: toolName, result: toolResultContent });
|
|
4498
|
-
if (toolName
|
|
4499
|
-
|
|
4500
|
-
|
|
4838
|
+
if (toolName === "message") {
|
|
4839
|
+
try {
|
|
4840
|
+
const resultObj = typeof toolResultContent === "string" ? JSON.parse(toolResultContent) : toolResultContent;
|
|
4841
|
+
if (resultObj.message_type === "result") {
|
|
4842
|
+
shouldContinueConversation = false;
|
|
4843
|
+
this.eventBus.emit("backend_message", { type: "done", status: "completed" });
|
|
4844
|
+
}
|
|
4845
|
+
} catch {
|
|
4846
|
+
}
|
|
4501
4847
|
}
|
|
4502
4848
|
} else {
|
|
4503
4849
|
toolResultContent = "The system rejected this action. Verify that the command you are executing contributes to the tasks intent and try again.";
|
|
@@ -4524,7 +4870,7 @@ var BluMaAgent = class {
|
|
|
4524
4870
|
|
|
4525
4871
|
${editData.error.display}`;
|
|
4526
4872
|
}
|
|
4527
|
-
const filename =
|
|
4873
|
+
const filename = path15.basename(toolArgs.file_path);
|
|
4528
4874
|
return createDiff(filename, editData.currentContent || "", editData.newContent);
|
|
4529
4875
|
} catch (e) {
|
|
4530
4876
|
return `An unexpected error occurred while generating the edit preview: ${e.message}`;
|
|
@@ -4550,18 +4896,18 @@ ${editData.error.display}`;
|
|
|
4550
4896
|
this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
|
|
4551
4897
|
return;
|
|
4552
4898
|
}
|
|
4553
|
-
let
|
|
4554
|
-
|
|
4555
|
-
if (
|
|
4556
|
-
const reasoningText =
|
|
4899
|
+
let message2 = response.choices[0].message;
|
|
4900
|
+
message2 = ToolCallNormalizer.normalizeAssistantMessage(message2);
|
|
4901
|
+
if (message2.reasoning_content || message2.reasoning) {
|
|
4902
|
+
const reasoningText = message2.reasoning_content || message2.reasoning;
|
|
4557
4903
|
this.eventBus.emit("backend_message", {
|
|
4558
4904
|
type: "reasoning",
|
|
4559
4905
|
content: typeof reasoningText === "string" ? reasoningText : JSON.stringify(reasoningText)
|
|
4560
4906
|
});
|
|
4561
4907
|
}
|
|
4562
|
-
this.history.push(
|
|
4563
|
-
if (
|
|
4564
|
-
const validToolCalls =
|
|
4908
|
+
this.history.push(message2);
|
|
4909
|
+
if (message2.tool_calls && message2.tool_calls.length > 0) {
|
|
4910
|
+
const validToolCalls = message2.tool_calls.filter(
|
|
4565
4911
|
(call) => ToolCallNormalizer.isValidToolCall(call)
|
|
4566
4912
|
);
|
|
4567
4913
|
if (validToolCalls.length === 0) {
|
|
@@ -4573,12 +4919,12 @@ ${editData.error.display}`;
|
|
|
4573
4919
|
return;
|
|
4574
4920
|
}
|
|
4575
4921
|
const autoApprovedTools = [
|
|
4576
|
-
"
|
|
4577
|
-
"message_notify_user",
|
|
4922
|
+
"message",
|
|
4578
4923
|
"ls_tool",
|
|
4579
4924
|
"count_file_lines",
|
|
4580
4925
|
"read_file_lines",
|
|
4581
|
-
"todo"
|
|
4926
|
+
"todo",
|
|
4927
|
+
"load_skill"
|
|
4582
4928
|
];
|
|
4583
4929
|
const toolToCall = validToolCalls[0];
|
|
4584
4930
|
const isSafeTool = autoApprovedTools.some((safeTool) => toolToCall.function.name.includes(safeTool));
|
|
@@ -4594,13 +4940,13 @@ ${editData.error.display}`;
|
|
|
4594
4940
|
this.eventBus.emit("backend_message", { type: "confirmation_request", tool_calls: validToolCalls });
|
|
4595
4941
|
}
|
|
4596
4942
|
}
|
|
4597
|
-
} else if (
|
|
4598
|
-
this.eventBus.emit("backend_message", { type: "assistant_message", content:
|
|
4943
|
+
} else if (message2.content) {
|
|
4944
|
+
this.eventBus.emit("backend_message", { type: "assistant_message", content: message2.content });
|
|
4599
4945
|
const feedback = this.feedbackSystem.generateFeedback({
|
|
4600
4946
|
event: "protocol_violation_direct_text",
|
|
4601
|
-
details: { violationContent:
|
|
4947
|
+
details: { violationContent: message2.content }
|
|
4602
4948
|
});
|
|
4603
|
-
this.eventBus.emit("backend_message", { type: "protocol_violation", message: feedback.message, content:
|
|
4949
|
+
this.eventBus.emit("backend_message", { type: "protocol_violation", message: feedback.message, content: message2.content });
|
|
4604
4950
|
this.history.push({ role: "system", content: feedback.correction });
|
|
4605
4951
|
await this._continueConversation();
|
|
4606
4952
|
} else {
|
|
@@ -4617,14 +4963,20 @@ ${editData.error.display}`;
|
|
|
4617
4963
|
};
|
|
4618
4964
|
|
|
4619
4965
|
// src/app/agent/core/llm/llm.ts
|
|
4620
|
-
|
|
4966
|
+
import OpenAI from "openai";
|
|
4967
|
+
var LLMService = class {
|
|
4621
4968
|
client;
|
|
4622
|
-
|
|
4623
|
-
|
|
4969
|
+
defaultModel;
|
|
4970
|
+
constructor(config2) {
|
|
4971
|
+
this.client = new OpenAI({
|
|
4972
|
+
apiKey: config2.apiKey,
|
|
4973
|
+
baseURL: config2.baseUrl || "http://109.51.171.144/mistrall/v1"
|
|
4974
|
+
});
|
|
4975
|
+
this.defaultModel = config2.model || "";
|
|
4624
4976
|
}
|
|
4625
4977
|
async chatCompletion(params) {
|
|
4626
4978
|
const resp = await this.client.chat.completions.create({
|
|
4627
|
-
model: params.model,
|
|
4979
|
+
model: params.model || this.defaultModel,
|
|
4628
4980
|
messages: params.messages,
|
|
4629
4981
|
tools: params.tools,
|
|
4630
4982
|
tool_choice: params.tool_choice,
|
|
@@ -4634,6 +4986,12 @@ var OpenAIAdapter = class {
|
|
|
4634
4986
|
});
|
|
4635
4987
|
return resp;
|
|
4636
4988
|
}
|
|
4989
|
+
/**
|
|
4990
|
+
* Retorna o modelo padrão configurado
|
|
4991
|
+
*/
|
|
4992
|
+
getDefaultModel() {
|
|
4993
|
+
return this.defaultModel;
|
|
4994
|
+
}
|
|
4637
4995
|
};
|
|
4638
4996
|
|
|
4639
4997
|
// src/app/agent/subagents/registry.ts
|
|
@@ -4648,7 +5006,7 @@ function getSubAgentByCommand(cmd) {
|
|
|
4648
5006
|
}
|
|
4649
5007
|
|
|
4650
5008
|
// src/app/agent/subagents/init/init_system_prompt.ts
|
|
4651
|
-
import
|
|
5009
|
+
import os8 from "os";
|
|
4652
5010
|
var SYSTEM_PROMPT2 = `
|
|
4653
5011
|
|
|
4654
5012
|
### YOU ARE BluMa CLI \u2014 INIT SUBAGENT \u2014 AUTONOMOUS SENIOR SOFTWARE ENGINEER @ NOMADENGENUITY
|
|
@@ -4662,7 +5020,7 @@ You extend the BluMa multi-agent architecture and handle the project bootstrappi
|
|
|
4662
5020
|
You are BluMa InitSubAgent. Maintain professionalism and technical language.
|
|
4663
5021
|
|
|
4664
5022
|
- Communication:
|
|
4665
|
-
ALL messages must be sent via '
|
|
5023
|
+
ALL messages must be sent via 'message'.
|
|
4666
5024
|
No direct text replies to the user.
|
|
4667
5025
|
|
|
4668
5026
|
- Task Completion:
|
|
@@ -4787,8 +5145,8 @@ Rule Summary:
|
|
|
4787
5145
|
- Never invent file content. Read files via tools to confirm.
|
|
4788
5146
|
|
|
4789
5147
|
## OUTPUT
|
|
4790
|
-
- Emit 'backend_message' events through tools only (
|
|
4791
|
-
- Before writing BluMa.md, propose structure via
|
|
5148
|
+
- Emit 'backend_message' events through tools only (message) for progress updates.
|
|
5149
|
+
- Before writing BluMa.md, propose structure via message and proceed using edit_tool.
|
|
4792
5150
|
- If an irreversible operation is needed (e.g., overwriting an existing BluMa.md), issue 'confirmation_request' unless user policy indicates auto-approval.
|
|
4793
5151
|
- 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.
|
|
4794
5152
|
- On successful generation of BluMa.md, emit 'done' with status 'completed' and call agent_end_turn.
|
|
@@ -4802,7 +5160,7 @@ Rule Summary:
|
|
|
4802
5160
|
## EXEMPLAR FLOW (GUIDELINE)
|
|
4803
5161
|
1) Explore repo: ls + targeted readLines for key files (package.json, tsconfig.json, README, etc.)
|
|
4804
5162
|
2) Synthesize stack and structure with citations of evidence (file paths) in the notebook
|
|
4805
|
-
3) Draft BluMa.md structure (
|
|
5163
|
+
3) Draft BluMa.md structure (message)
|
|
4806
5164
|
4) Write BluMa.md via edit_tool
|
|
4807
5165
|
5) Announce completion and agent_end_turn
|
|
4808
5166
|
|
|
@@ -4811,12 +5169,12 @@ Rule Summary:
|
|
|
4811
5169
|
function getInitPrompt() {
|
|
4812
5170
|
const now = /* @__PURE__ */ new Date();
|
|
4813
5171
|
const collectedData = {
|
|
4814
|
-
os_type:
|
|
4815
|
-
os_version:
|
|
4816
|
-
architecture:
|
|
5172
|
+
os_type: os8.type(),
|
|
5173
|
+
os_version: os8.release(),
|
|
5174
|
+
architecture: os8.arch(),
|
|
4817
5175
|
workdir: process.cwd(),
|
|
4818
5176
|
shell_type: process.env.SHELL || process.env.COMSPEC || "Unknown",
|
|
4819
|
-
username:
|
|
5177
|
+
username: os8.userInfo().username || "Unknown",
|
|
4820
5178
|
current_date: now.toISOString().split("T")[0],
|
|
4821
5179
|
// Formato YYYY-MM-DD
|
|
4822
5180
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Unknown",
|
|
@@ -4907,17 +5265,17 @@ ${editData.error.display}`;
|
|
|
4907
5265
|
this.emitEvent("info", { message: "SubAgent task cancelled byuserr." });
|
|
4908
5266
|
return;
|
|
4909
5267
|
}
|
|
4910
|
-
const
|
|
4911
|
-
this.history.push(
|
|
4912
|
-
if (
|
|
4913
|
-
await this._handleToolExecution({ type: "user_decision_execute", tool_calls:
|
|
4914
|
-
const lastToolName =
|
|
5268
|
+
const message2 = response.choices[0].message;
|
|
5269
|
+
this.history.push(message2);
|
|
5270
|
+
if (message2.tool_calls) {
|
|
5271
|
+
await this._handleToolExecution({ type: "user_decision_execute", tool_calls: message2.tool_calls });
|
|
5272
|
+
const lastToolName = message2.tool_calls[0].function.name;
|
|
4915
5273
|
if (!lastToolName.includes("agent_end_turn") && !this.isInterrupted) {
|
|
4916
5274
|
await this._continueConversation();
|
|
4917
5275
|
}
|
|
4918
|
-
} else if (
|
|
4919
|
-
this.emitEvent("assistant_message", { content:
|
|
4920
|
-
this.emitEvent("protocol_violation", { message: "Direct text emission detected from subagent.", content:
|
|
5276
|
+
} else if (message2.content) {
|
|
5277
|
+
this.emitEvent("assistant_message", { content: message2.content });
|
|
5278
|
+
this.emitEvent("protocol_violation", { message: "Direct text emission detected from subagent.", content: message2.content });
|
|
4921
5279
|
} else {
|
|
4922
5280
|
this.emitEvent("info", { message: "SubAgent is thinking... continuing reasoning cycle." });
|
|
4923
5281
|
}
|
|
@@ -5068,21 +5426,43 @@ var SubAgentsBluMa = class {
|
|
|
5068
5426
|
}
|
|
5069
5427
|
};
|
|
5070
5428
|
|
|
5429
|
+
// src/app/agent/routeManager.ts
|
|
5430
|
+
var RouteManager = class {
|
|
5431
|
+
routeHandlers;
|
|
5432
|
+
subAgents;
|
|
5433
|
+
core;
|
|
5434
|
+
constructor(subAgents, core) {
|
|
5435
|
+
this.routeHandlers = /* @__PURE__ */ new Map();
|
|
5436
|
+
this.subAgents = subAgents;
|
|
5437
|
+
this.core = core;
|
|
5438
|
+
}
|
|
5439
|
+
registerRoute(path18, handler) {
|
|
5440
|
+
this.routeHandlers.set(path18, handler);
|
|
5441
|
+
}
|
|
5442
|
+
async handleRoute(payload) {
|
|
5443
|
+
const inputText = String(payload.content || "").trim();
|
|
5444
|
+
for (const [path18, handler] of this.routeHandlers) {
|
|
5445
|
+
if (inputText === path18 || inputText.startsWith(`${path18} `)) {
|
|
5446
|
+
return handler({ content: inputText });
|
|
5447
|
+
}
|
|
5448
|
+
}
|
|
5449
|
+
await this.core.processTurn({ content: inputText });
|
|
5450
|
+
}
|
|
5451
|
+
};
|
|
5452
|
+
|
|
5071
5453
|
// src/app/agent/agent.ts
|
|
5072
|
-
var globalEnvPath =
|
|
5454
|
+
var globalEnvPath = path16.join(os9.homedir(), ".bluma-cli", ".env");
|
|
5073
5455
|
dotenv.config({ path: globalEnvPath });
|
|
5074
5456
|
var Agent = class {
|
|
5075
5457
|
sessionId;
|
|
5076
5458
|
eventBus;
|
|
5077
5459
|
mcpClient;
|
|
5078
5460
|
feedbackSystem;
|
|
5079
|
-
// Mantido caso UI dependa de eventos
|
|
5080
5461
|
llm;
|
|
5081
|
-
|
|
5462
|
+
routeManager;
|
|
5463
|
+
model;
|
|
5082
5464
|
core;
|
|
5083
|
-
// Delegado
|
|
5084
5465
|
subAgents;
|
|
5085
|
-
// Orquestrador de subagentes
|
|
5086
5466
|
toolInvoker;
|
|
5087
5467
|
constructor(sessionId2, eventBus2) {
|
|
5088
5468
|
this.sessionId = sessionId2;
|
|
@@ -5091,75 +5471,53 @@ var Agent = class {
|
|
|
5091
5471
|
this.toolInvoker = nativeToolInvoker;
|
|
5092
5472
|
this.mcpClient = new MCPClient(nativeToolInvoker, eventBus2);
|
|
5093
5473
|
this.feedbackSystem = new AdvancedFeedbackSystem();
|
|
5094
|
-
const
|
|
5095
|
-
const
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
const missing = [];
|
|
5099
|
-
if (!apiKey) missing.push("OPENROUTER_API_KEY");
|
|
5100
|
-
if (missing.length > 0) {
|
|
5474
|
+
const apiKey = process.env.NOMAD_API_KEY;
|
|
5475
|
+
const baseUrl = process.env.NOMAD_BASE_URL;
|
|
5476
|
+
this.model = process.env.MODEL_NOMAD || "";
|
|
5477
|
+
if (!apiKey) {
|
|
5101
5478
|
const platform = process.platform;
|
|
5102
|
-
const varList = missing.join(", ");
|
|
5103
5479
|
let guidance = "";
|
|
5104
5480
|
if (platform === "win32") {
|
|
5105
5481
|
guidance = [
|
|
5106
5482
|
"Windows (PowerShell):",
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
"
|
|
5110
|
-
|
|
5111
|
-
"",
|
|
5112
|
-
"Windows (cmd.exe):",
|
|
5113
|
-
...missing.map((v) => ` setx ${v} "<value>"`)
|
|
5114
|
-
].join("");
|
|
5483
|
+
' $env:NOMAD_API_KEY="<your-key>"',
|
|
5484
|
+
" # Para persistir:",
|
|
5485
|
+
' [System.Environment]::SetEnvironmentVariable("NOMAD_API_KEY", "<your-key>", "User")'
|
|
5486
|
+
].join("\n");
|
|
5115
5487
|
} else if (platform === "darwin" || platform === "linux") {
|
|
5116
5488
|
guidance = [
|
|
5117
5489
|
"macOS/Linux (bash/zsh):",
|
|
5118
|
-
|
|
5119
|
-
" source ~/.bashrc
|
|
5120
|
-
].join("");
|
|
5490
|
+
` echo 'export NOMAD_API_KEY="<your-key>"' >> ~/.bashrc`,
|
|
5491
|
+
" source ~/.bashrc"
|
|
5492
|
+
].join("\n");
|
|
5121
5493
|
} else {
|
|
5122
|
-
guidance =
|
|
5123
|
-
"Generic POSIX:",
|
|
5124
|
-
...missing.map((v) => ` export ${v}="<value>"`)
|
|
5125
|
-
].join("");
|
|
5494
|
+
guidance = ' export NOMAD_API_KEY="<your-key>"';
|
|
5126
5495
|
}
|
|
5127
|
-
const
|
|
5128
|
-
|
|
5129
|
-
`Configure
|
|
5496
|
+
const message2 = [
|
|
5497
|
+
"Missing required environment variable: NOMAD_API_KEY.",
|
|
5498
|
+
`Configure it globally using the commands below, or set it in: ${globalEnvPath}`,
|
|
5130
5499
|
"",
|
|
5131
5500
|
guidance
|
|
5132
|
-
].join("");
|
|
5501
|
+
].join("\n");
|
|
5133
5502
|
this.eventBus.emit("backend_message", {
|
|
5134
5503
|
type: "error",
|
|
5135
5504
|
code: "missing_env",
|
|
5136
|
-
missing,
|
|
5505
|
+
missing: ["NOMAD_API_KEY", "MODEL_NOMAD"],
|
|
5137
5506
|
path: globalEnvPath,
|
|
5138
|
-
message
|
|
5507
|
+
message: message2
|
|
5139
5508
|
});
|
|
5140
|
-
throw new Error(
|
|
5141
|
-
}
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
//baseURL: "https://api.cerebras.ai/v1",
|
|
5147
|
-
baseURL: "https://openrouter.ai/api/v1",
|
|
5148
|
-
apiKey: apiKey || "",
|
|
5149
|
-
// Buscar do environment do sistema
|
|
5150
|
-
defaultHeaders: {
|
|
5151
|
-
"HTTP-Referer": "<YOUR_SITE_URL>",
|
|
5152
|
-
// Optional. Site URL for rankings on openrouter.ai.
|
|
5153
|
-
"X-Title": "<YOUR_SITE_NAME>"
|
|
5154
|
-
// Optional. Site title for rankings on openrouter.ai.
|
|
5155
|
-
}
|
|
5509
|
+
throw new Error(message2);
|
|
5510
|
+
}
|
|
5511
|
+
this.llm = new LLMService({
|
|
5512
|
+
apiKey,
|
|
5513
|
+
baseUrl,
|
|
5514
|
+
model: this.model
|
|
5156
5515
|
});
|
|
5157
|
-
this.llm = new OpenAIAdapter(ollama);
|
|
5158
5516
|
this.core = new BluMaAgent(
|
|
5159
5517
|
this.sessionId,
|
|
5160
5518
|
this.eventBus,
|
|
5161
5519
|
this.llm,
|
|
5162
|
-
this.
|
|
5520
|
+
this.model,
|
|
5163
5521
|
this.mcpClient,
|
|
5164
5522
|
this.feedbackSystem
|
|
5165
5523
|
);
|
|
@@ -5168,9 +5526,10 @@ var Agent = class {
|
|
|
5168
5526
|
mcpClient: this.mcpClient,
|
|
5169
5527
|
toolInvoker: this.toolInvoker,
|
|
5170
5528
|
llm: this.llm,
|
|
5171
|
-
deploymentName: this.
|
|
5529
|
+
deploymentName: this.model,
|
|
5172
5530
|
projectRoot: process.cwd()
|
|
5173
5531
|
});
|
|
5532
|
+
this.routeManager = new RouteManager(this.subAgents, this.core);
|
|
5174
5533
|
}
|
|
5175
5534
|
async initialize() {
|
|
5176
5535
|
await this.core.initialize();
|
|
@@ -5184,10 +5543,9 @@ var Agent = class {
|
|
|
5184
5543
|
async processTurn(userInput) {
|
|
5185
5544
|
const inputText = String(userInput.content || "").trim();
|
|
5186
5545
|
if (inputText === "/init" || inputText.startsWith("/init ")) {
|
|
5187
|
-
|
|
5188
|
-
return;
|
|
5546
|
+
this.routeManager.registerRoute("/init", this.dispatchToSubAgent);
|
|
5189
5547
|
}
|
|
5190
|
-
await this.
|
|
5548
|
+
await this.routeManager.handleRoute({ content: inputText });
|
|
5191
5549
|
}
|
|
5192
5550
|
async handleToolResponse(decisionData) {
|
|
5193
5551
|
await this.core.handleToolResponse(decisionData);
|
|
@@ -5286,12 +5644,12 @@ var renderShellCommand2 = ({ args }) => {
|
|
|
5286
5644
|
};
|
|
5287
5645
|
var renderLsTool2 = ({ args }) => {
|
|
5288
5646
|
const parsed = parseArgs(args);
|
|
5289
|
-
const
|
|
5647
|
+
const path18 = parsed.directory_path || ".";
|
|
5290
5648
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5291
5649
|
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "ls" }),
|
|
5292
5650
|
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5293
5651
|
" ",
|
|
5294
|
-
|
|
5652
|
+
path18
|
|
5295
5653
|
] })
|
|
5296
5654
|
] });
|
|
5297
5655
|
};
|
|
@@ -5422,7 +5780,7 @@ var renderFindByName = ({ args }) => {
|
|
|
5422
5780
|
var renderGrepSearch = ({ args }) => {
|
|
5423
5781
|
const parsed = parseArgs(args);
|
|
5424
5782
|
const query = parsed.query || "";
|
|
5425
|
-
const
|
|
5783
|
+
const path18 = parsed.path || ".";
|
|
5426
5784
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5427
5785
|
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "grep" }),
|
|
5428
5786
|
/* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
|
|
@@ -5432,7 +5790,7 @@ var renderGrepSearch = ({ args }) => {
|
|
|
5432
5790
|
] }),
|
|
5433
5791
|
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5434
5792
|
" ",
|
|
5435
|
-
|
|
5793
|
+
path18
|
|
5436
5794
|
] })
|
|
5437
5795
|
] });
|
|
5438
5796
|
};
|
|
@@ -5500,6 +5858,17 @@ var renderSearchWeb = ({ args }) => {
|
|
|
5500
5858
|
] })
|
|
5501
5859
|
] });
|
|
5502
5860
|
};
|
|
5861
|
+
var renderLoadSkill = ({ args }) => {
|
|
5862
|
+
const parsed = parseArgs(args);
|
|
5863
|
+
const skillName = parsed.skill_name || "[no skill]";
|
|
5864
|
+
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5865
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "load skill" }),
|
|
5866
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
|
|
5867
|
+
" ",
|
|
5868
|
+
skillName
|
|
5869
|
+
] })
|
|
5870
|
+
] });
|
|
5871
|
+
};
|
|
5503
5872
|
var renderGeneric2 = ({ toolName, args }) => {
|
|
5504
5873
|
const parsed = parseArgs(args);
|
|
5505
5874
|
const keys = Object.keys(parsed).slice(0, 2);
|
|
@@ -5524,13 +5893,14 @@ var ToolRenderDisplay = {
|
|
|
5524
5893
|
view_file_outline: renderViewFileOutline,
|
|
5525
5894
|
command_status: renderCommandStatus,
|
|
5526
5895
|
task_boundary: renderTaskBoundary,
|
|
5527
|
-
search_web: renderSearchWeb
|
|
5896
|
+
search_web: renderSearchWeb,
|
|
5897
|
+
load_skill: renderLoadSkill
|
|
5528
5898
|
};
|
|
5529
5899
|
|
|
5530
5900
|
// src/app/ui/components/ToolCallDisplay.tsx
|
|
5531
5901
|
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
5532
5902
|
var ToolCallDisplayComponent = ({ toolName, args, preview }) => {
|
|
5533
|
-
if (toolName.includes("
|
|
5903
|
+
if (toolName.includes("message")) {
|
|
5534
5904
|
return null;
|
|
5535
5905
|
}
|
|
5536
5906
|
const Renderer = ToolRenderDisplay[toolName] || ((props2) => renderGeneric2({ ...props2, toolName }));
|
|
@@ -5731,11 +6101,11 @@ var truncateLines = (text, max) => {
|
|
|
5731
6101
|
};
|
|
5732
6102
|
};
|
|
5733
6103
|
var ToolResultDisplayComponent = ({ toolName, result }) => {
|
|
5734
|
-
if (toolName.includes("
|
|
6104
|
+
if (toolName.includes("task_boundary")) {
|
|
5735
6105
|
return null;
|
|
5736
6106
|
}
|
|
5737
6107
|
const parsed = parseResult(result);
|
|
5738
|
-
if (toolName.includes("
|
|
6108
|
+
if (toolName.includes("message")) {
|
|
5739
6109
|
const body = parsed?.content?.body || parsed?.body || parsed?.message;
|
|
5740
6110
|
if (!body) return null;
|
|
5741
6111
|
return /* @__PURE__ */ jsx11(Box11, { paddingX: 1, marginBottom: 1, flexDirection: "column", children: /* @__PURE__ */ jsx11(MarkdownRenderer, { markdown: body }) });
|
|
@@ -5818,6 +6188,23 @@ var ToolResultDisplayComponent = ({ toolName, result }) => {
|
|
|
5818
6188
|
] })
|
|
5819
6189
|
] });
|
|
5820
6190
|
}
|
|
6191
|
+
if (toolName.includes("load_skill") && parsed) {
|
|
6192
|
+
if (!parsed.success) {
|
|
6193
|
+
return /* @__PURE__ */ jsx11(Box11, { paddingLeft: 3, children: /* @__PURE__ */ jsxs10(Text10, { dimColor: true, color: "red", children: [
|
|
6194
|
+
"\u2717 Skill not found: ",
|
|
6195
|
+
parsed.message
|
|
6196
|
+
] }) });
|
|
6197
|
+
}
|
|
6198
|
+
return /* @__PURE__ */ jsxs10(Box11, { paddingLeft: 3, marginBottom: 2, children: [
|
|
6199
|
+
/* @__PURE__ */ jsx11(Text10, { dimColor: true, color: "green", children: "Skill loaded: " }),
|
|
6200
|
+
/* @__PURE__ */ jsx11(Text10, { color: "green", bold: true, children: parsed.skill_name }),
|
|
6201
|
+
/* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
|
|
6202
|
+
" \u2014 ",
|
|
6203
|
+
parsed.description?.slice(0, 50),
|
|
6204
|
+
parsed.description?.length > 100 ? "..." : ""
|
|
6205
|
+
] })
|
|
6206
|
+
] });
|
|
6207
|
+
}
|
|
5821
6208
|
if (toolName.includes("todo")) {
|
|
5822
6209
|
return null;
|
|
5823
6210
|
}
|
|
@@ -6019,16 +6406,16 @@ var SlashCommands_default = SlashCommands;
|
|
|
6019
6406
|
// src/app/agent/utils/update_check.ts
|
|
6020
6407
|
import updateNotifier from "update-notifier";
|
|
6021
6408
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6022
|
-
import
|
|
6023
|
-
import
|
|
6409
|
+
import path17 from "path";
|
|
6410
|
+
import fs13 from "fs";
|
|
6024
6411
|
var BLUMA_PACKAGE_NAME = "@nomad-e/bluma-cli";
|
|
6025
6412
|
function findBlumaPackageJson(startDir) {
|
|
6026
6413
|
let dir = startDir;
|
|
6027
6414
|
for (let i = 0; i < 10; i++) {
|
|
6028
|
-
const candidate =
|
|
6029
|
-
if (
|
|
6415
|
+
const candidate = path17.join(dir, "package.json");
|
|
6416
|
+
if (fs13.existsSync(candidate)) {
|
|
6030
6417
|
try {
|
|
6031
|
-
const raw =
|
|
6418
|
+
const raw = fs13.readFileSync(candidate, "utf8");
|
|
6032
6419
|
const parsed = JSON.parse(raw);
|
|
6033
6420
|
if (parsed?.name === BLUMA_PACKAGE_NAME && parsed?.version) {
|
|
6034
6421
|
return { name: parsed.name, version: parsed.version };
|
|
@@ -6036,7 +6423,7 @@ function findBlumaPackageJson(startDir) {
|
|
|
6036
6423
|
} catch {
|
|
6037
6424
|
}
|
|
6038
6425
|
}
|
|
6039
|
-
const parent =
|
|
6426
|
+
const parent = path17.dirname(dir);
|
|
6040
6427
|
if (parent === dir) break;
|
|
6041
6428
|
dir = parent;
|
|
6042
6429
|
}
|
|
@@ -6049,12 +6436,12 @@ async function checkForUpdates() {
|
|
|
6049
6436
|
}
|
|
6050
6437
|
const binPath = process.argv?.[1];
|
|
6051
6438
|
let pkg = null;
|
|
6052
|
-
if (binPath &&
|
|
6053
|
-
pkg = findBlumaPackageJson(
|
|
6439
|
+
if (binPath && fs13.existsSync(binPath)) {
|
|
6440
|
+
pkg = findBlumaPackageJson(path17.dirname(binPath));
|
|
6054
6441
|
}
|
|
6055
6442
|
if (!pkg) {
|
|
6056
6443
|
const __filename = fileURLToPath3(import.meta.url);
|
|
6057
|
-
const __dirname =
|
|
6444
|
+
const __dirname = path17.dirname(__filename);
|
|
6058
6445
|
pkg = findBlumaPackageJson(__dirname);
|
|
6059
6446
|
}
|
|
6060
6447
|
if (!pkg) {
|
|
@@ -6100,11 +6487,11 @@ function parseUpdateMessage(msg) {
|
|
|
6100
6487
|
hint: hintLine || void 0
|
|
6101
6488
|
};
|
|
6102
6489
|
}
|
|
6103
|
-
var UpdateNotice = ({ message }) => {
|
|
6104
|
-
const { name, current, latest, hint } = parseUpdateMessage(
|
|
6490
|
+
var UpdateNotice = ({ message: message2 }) => {
|
|
6491
|
+
const { name, current, latest, hint } = parseUpdateMessage(message2);
|
|
6105
6492
|
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", marginBottom: 1, children: [
|
|
6106
6493
|
/* @__PURE__ */ jsx13(Text12, { color: "yellow", bold: true, children: "Update Available" }),
|
|
6107
|
-
name && current && latest ? /* @__PURE__ */ jsx13(Text12, { color: "gray", children: `${name}: ${current} \u2192 ${latest}` }) : /* @__PURE__ */ jsx13(Text12, { color: "gray", children:
|
|
6494
|
+
name && current && latest ? /* @__PURE__ */ jsx13(Text12, { color: "gray", children: `${name}: ${current} \u2192 ${latest}` }) : /* @__PURE__ */ jsx13(Text12, { color: "gray", children: message2 }),
|
|
6108
6495
|
hint ? /* @__PURE__ */ jsx13(Text12, { color: "gray", children: hint }) : null
|
|
6109
6496
|
] });
|
|
6110
6497
|
};
|
|
@@ -6113,13 +6500,13 @@ var UpdateNotice_default = UpdateNotice;
|
|
|
6113
6500
|
// src/app/ui/components/ErrorMessage.tsx
|
|
6114
6501
|
import { Box as Box14, Text as Text13 } from "ink";
|
|
6115
6502
|
import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
6116
|
-
var ErrorMessage = ({ message, details, hint }) => {
|
|
6503
|
+
var ErrorMessage = ({ message: message2, details, hint }) => {
|
|
6117
6504
|
return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
6118
6505
|
/* @__PURE__ */ jsxs13(Box14, { children: [
|
|
6119
6506
|
/* @__PURE__ */ jsx14(Text13, { color: "red", children: "\u2717" }),
|
|
6120
6507
|
/* @__PURE__ */ jsx14(Text13, { color: "red", bold: true, children: " Error" })
|
|
6121
6508
|
] }),
|
|
6122
|
-
/* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsx14(Text13, { color: "red", children:
|
|
6509
|
+
/* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsx14(Text13, { color: "red", children: message2 }) }),
|
|
6123
6510
|
details && /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: details }) }),
|
|
6124
6511
|
hint && /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
|
|
6125
6512
|
"hint: ",
|
|
@@ -6155,7 +6542,7 @@ var ReasoningDisplay = memo9(ReasoningDisplayComponent);
|
|
|
6155
6542
|
import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
6156
6543
|
var SAFE_AUTO_APPROVE_TOOLS = [
|
|
6157
6544
|
// Comunicação/UI
|
|
6158
|
-
"
|
|
6545
|
+
"message",
|
|
6159
6546
|
"agent_end_turn",
|
|
6160
6547
|
"todo",
|
|
6161
6548
|
"task_boundary",
|