@nomad-e/bluma-cli 0.0.109 → 0.0.111
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/README.md +1 -1
- package/dist/config/native_tools.json +25 -9
- package/dist/main.js +760 -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
|
|
@@ -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
|
};
|
|
@@ -3409,6 +3255,42 @@ 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.`,
|
|
3289
|
+
skill_name: skill.name,
|
|
3290
|
+
description: skill.description
|
|
3291
|
+
};
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3412
3294
|
// src/app/agent/tool_invoker.ts
|
|
3413
3295
|
var ToolInvoker = class {
|
|
3414
3296
|
// Mapa privado para associar nomes de ferramentas às suas funções de implementação.
|
|
@@ -3452,13 +3334,13 @@ var ToolInvoker = class {
|
|
|
3452
3334
|
this.toolImplementations.set("command_status", commandStatus);
|
|
3453
3335
|
this.toolImplementations.set("send_command_input", sendCommandInput);
|
|
3454
3336
|
this.toolImplementations.set("kill_command", killCommand);
|
|
3455
|
-
this.toolImplementations.set("
|
|
3337
|
+
this.toolImplementations.set("message", message);
|
|
3456
3338
|
this.toolImplementations.set("todo", todo);
|
|
3457
3339
|
this.toolImplementations.set("task_boundary", taskBoundary);
|
|
3458
3340
|
this.toolImplementations.set("create_artifact", createArtifact);
|
|
3459
3341
|
this.toolImplementations.set("read_artifact", readArtifact);
|
|
3460
3342
|
this.toolImplementations.set("search_web", searchWeb);
|
|
3461
|
-
this.toolImplementations.set("
|
|
3343
|
+
this.toolImplementations.set("load_skill", loadSkill);
|
|
3462
3344
|
}
|
|
3463
3345
|
/**
|
|
3464
3346
|
* Retorna a lista de definições de todas as ferramentas nativas carregadas.
|
|
@@ -3519,7 +3401,7 @@ var MCPClient = class {
|
|
|
3519
3401
|
const __filename = fileURLToPath2(import.meta.url);
|
|
3520
3402
|
const __dirname = path11.dirname(__filename);
|
|
3521
3403
|
const defaultConfigPath = path11.resolve(__dirname, "config", "bluma-mcp.json");
|
|
3522
|
-
const userConfigPath = path11.join(os4.homedir(), ".bluma
|
|
3404
|
+
const userConfigPath = path11.join(os4.homedir(), ".bluma", "bluma-mcp.json");
|
|
3523
3405
|
const defaultConfig = await this.loadMcpConfig(defaultConfigPath, "Default");
|
|
3524
3406
|
const userConfig = await this.loadMcpConfig(userConfigPath, "User");
|
|
3525
3407
|
const mergedConfig = {
|
|
@@ -3688,7 +3570,7 @@ var AdvancedFeedbackSystem = class {
|
|
|
3688
3570
|
};
|
|
3689
3571
|
|
|
3690
3572
|
// src/app/agent/bluma/core/bluma.ts
|
|
3691
|
-
import
|
|
3573
|
+
import path15 from "path";
|
|
3692
3574
|
|
|
3693
3575
|
// src/app/agent/session_manager/session_manager.ts
|
|
3694
3576
|
import path12 from "path";
|
|
@@ -3733,7 +3615,7 @@ function expandHome(p) {
|
|
|
3733
3615
|
return p;
|
|
3734
3616
|
}
|
|
3735
3617
|
function getPreferredAppDir() {
|
|
3736
|
-
const fixed = path12.join(os5.homedir(), ".bluma
|
|
3618
|
+
const fixed = path12.join(os5.homedir(), ".bluma");
|
|
3737
3619
|
return path12.resolve(expandHome(fixed));
|
|
3738
3620
|
}
|
|
3739
3621
|
async function safeRenameWithRetry(src, dest, maxRetries = 6) {
|
|
@@ -3854,10 +3736,180 @@ async function saveSessionHistory(sessionFile, history) {
|
|
|
3854
3736
|
}
|
|
3855
3737
|
|
|
3856
3738
|
// src/app/agent/core/prompt/prompt_builder.ts
|
|
3857
|
-
import
|
|
3739
|
+
import os7 from "os";
|
|
3740
|
+
import fs12 from "fs";
|
|
3741
|
+
import path14 from "path";
|
|
3742
|
+
import { execSync } from "child_process";
|
|
3743
|
+
|
|
3744
|
+
// src/app/agent/skills/skill_loader.ts
|
|
3858
3745
|
import fs11 from "fs";
|
|
3859
3746
|
import path13 from "path";
|
|
3860
|
-
import
|
|
3747
|
+
import os6 from "os";
|
|
3748
|
+
var SkillLoader = class {
|
|
3749
|
+
projectSkillsDir;
|
|
3750
|
+
globalSkillsDir;
|
|
3751
|
+
cache = /* @__PURE__ */ new Map();
|
|
3752
|
+
constructor(projectRoot) {
|
|
3753
|
+
this.projectSkillsDir = path13.join(projectRoot, ".bluma", "skills");
|
|
3754
|
+
this.globalSkillsDir = path13.join(os6.homedir(), ".bluma", "skills");
|
|
3755
|
+
}
|
|
3756
|
+
/**
|
|
3757
|
+
* Lista skills disponíveis de ambas as fontes
|
|
3758
|
+
* Skills de projeto têm prioridade sobre globais com mesmo nome
|
|
3759
|
+
*/
|
|
3760
|
+
listAvailable() {
|
|
3761
|
+
const skills = /* @__PURE__ */ new Map();
|
|
3762
|
+
const globalSkills = this.listFromDir(this.globalSkillsDir, "global");
|
|
3763
|
+
for (const skill of globalSkills) {
|
|
3764
|
+
skills.set(skill.name, skill);
|
|
3765
|
+
}
|
|
3766
|
+
const projectSkills = this.listFromDir(this.projectSkillsDir, "project");
|
|
3767
|
+
for (const skill of projectSkills) {
|
|
3768
|
+
skills.set(skill.name, skill);
|
|
3769
|
+
}
|
|
3770
|
+
return Array.from(skills.values());
|
|
3771
|
+
}
|
|
3772
|
+
/**
|
|
3773
|
+
* Lista skills de um diretório específico
|
|
3774
|
+
*/
|
|
3775
|
+
listFromDir(dir, source) {
|
|
3776
|
+
if (!fs11.existsSync(dir)) return [];
|
|
3777
|
+
try {
|
|
3778
|
+
return fs11.readdirSync(dir).filter((d) => {
|
|
3779
|
+
const fullPath = path13.join(dir, d);
|
|
3780
|
+
return fs11.statSync(fullPath).isDirectory() && fs11.existsSync(path13.join(fullPath, "SKILL.md"));
|
|
3781
|
+
}).map((d) => this.loadMetadataFromPath(path13.join(dir, d, "SKILL.md"), d, source)).filter((m) => m !== null);
|
|
3782
|
+
} catch {
|
|
3783
|
+
return [];
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
/**
|
|
3787
|
+
* Carrega metadata de um path específico
|
|
3788
|
+
*/
|
|
3789
|
+
loadMetadataFromPath(skillPath, skillName, source) {
|
|
3790
|
+
if (!fs11.existsSync(skillPath)) return null;
|
|
3791
|
+
try {
|
|
3792
|
+
const raw = fs11.readFileSync(skillPath, "utf-8");
|
|
3793
|
+
const parsed = this.parseFrontmatter(raw);
|
|
3794
|
+
return {
|
|
3795
|
+
name: parsed.name || skillName,
|
|
3796
|
+
description: parsed.description || "",
|
|
3797
|
+
source,
|
|
3798
|
+
version: parsed.version,
|
|
3799
|
+
author: parsed.author,
|
|
3800
|
+
license: parsed.license
|
|
3801
|
+
};
|
|
3802
|
+
} catch {
|
|
3803
|
+
return null;
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
/**
|
|
3807
|
+
* Carrega skill completa - procura primeiro em projeto, depois global
|
|
3808
|
+
*/
|
|
3809
|
+
load(name) {
|
|
3810
|
+
if (this.cache.has(name)) return this.cache.get(name);
|
|
3811
|
+
const projectPath = path13.join(this.projectSkillsDir, name, "SKILL.md");
|
|
3812
|
+
if (fs11.existsSync(projectPath)) {
|
|
3813
|
+
const skill = this.loadFromPath(projectPath, name, "project");
|
|
3814
|
+
if (skill) {
|
|
3815
|
+
this.cache.set(name, skill);
|
|
3816
|
+
return skill;
|
|
3817
|
+
}
|
|
3818
|
+
}
|
|
3819
|
+
const globalPath = path13.join(this.globalSkillsDir, name, "SKILL.md");
|
|
3820
|
+
if (fs11.existsSync(globalPath)) {
|
|
3821
|
+
const skill = this.loadFromPath(globalPath, name, "global");
|
|
3822
|
+
if (skill) {
|
|
3823
|
+
this.cache.set(name, skill);
|
|
3824
|
+
return skill;
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
return null;
|
|
3828
|
+
}
|
|
3829
|
+
/**
|
|
3830
|
+
* Carrega skill de um path específico
|
|
3831
|
+
*/
|
|
3832
|
+
loadFromPath(skillPath, name, source) {
|
|
3833
|
+
try {
|
|
3834
|
+
const raw = fs11.readFileSync(skillPath, "utf-8");
|
|
3835
|
+
const parsed = this.parseFrontmatter(raw);
|
|
3836
|
+
return {
|
|
3837
|
+
name: parsed.name || name,
|
|
3838
|
+
description: parsed.description || "",
|
|
3839
|
+
content: parsed.content.trim(),
|
|
3840
|
+
source,
|
|
3841
|
+
version: parsed.version,
|
|
3842
|
+
author: parsed.author,
|
|
3843
|
+
license: parsed.license
|
|
3844
|
+
};
|
|
3845
|
+
} catch {
|
|
3846
|
+
return null;
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3849
|
+
/**
|
|
3850
|
+
* Parse simples de YAML frontmatter
|
|
3851
|
+
*/
|
|
3852
|
+
parseFrontmatter(raw) {
|
|
3853
|
+
const lines = raw.split("\n");
|
|
3854
|
+
if (lines[0]?.trim() !== "---") {
|
|
3855
|
+
return { content: raw };
|
|
3856
|
+
}
|
|
3857
|
+
let endIndex = -1;
|
|
3858
|
+
for (let i = 1; i < lines.length; i++) {
|
|
3859
|
+
if (lines[i]?.trim() === "---") {
|
|
3860
|
+
endIndex = i;
|
|
3861
|
+
break;
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
if (endIndex === -1) {
|
|
3865
|
+
return { content: raw };
|
|
3866
|
+
}
|
|
3867
|
+
const frontmatter = {};
|
|
3868
|
+
for (let i = 1; i < endIndex; i++) {
|
|
3869
|
+
const line = lines[i];
|
|
3870
|
+
const colonIndex = line.indexOf(":");
|
|
3871
|
+
if (colonIndex > 0) {
|
|
3872
|
+
const key = line.slice(0, colonIndex).trim();
|
|
3873
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
3874
|
+
frontmatter[key] = value;
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
const content = lines.slice(endIndex + 1).join("\n");
|
|
3878
|
+
return {
|
|
3879
|
+
name: frontmatter["name"],
|
|
3880
|
+
description: frontmatter["description"],
|
|
3881
|
+
version: frontmatter["version"],
|
|
3882
|
+
author: frontmatter["author"],
|
|
3883
|
+
license: frontmatter["license"],
|
|
3884
|
+
content
|
|
3885
|
+
};
|
|
3886
|
+
}
|
|
3887
|
+
/**
|
|
3888
|
+
* Limpa o cache
|
|
3889
|
+
*/
|
|
3890
|
+
clearCache() {
|
|
3891
|
+
this.cache.clear();
|
|
3892
|
+
}
|
|
3893
|
+
/**
|
|
3894
|
+
* Verifica se uma skill existe (em projeto ou global)
|
|
3895
|
+
*/
|
|
3896
|
+
exists(name) {
|
|
3897
|
+
const projectPath = path13.join(this.projectSkillsDir, name, "SKILL.md");
|
|
3898
|
+
const globalPath = path13.join(this.globalSkillsDir, name, "SKILL.md");
|
|
3899
|
+
return fs11.existsSync(projectPath) || fs11.existsSync(globalPath);
|
|
3900
|
+
}
|
|
3901
|
+
/**
|
|
3902
|
+
* Retorna os diretórios de skills
|
|
3903
|
+
*/
|
|
3904
|
+
getSkillsDirs() {
|
|
3905
|
+
return {
|
|
3906
|
+
project: this.projectSkillsDir,
|
|
3907
|
+
global: this.globalSkillsDir
|
|
3908
|
+
};
|
|
3909
|
+
}
|
|
3910
|
+
};
|
|
3911
|
+
|
|
3912
|
+
// src/app/agent/core/prompt/prompt_builder.ts
|
|
3861
3913
|
function getNodeVersion() {
|
|
3862
3914
|
try {
|
|
3863
3915
|
return process.version;
|
|
@@ -3885,10 +3937,10 @@ function getGitBranch(dir) {
|
|
|
3885
3937
|
}
|
|
3886
3938
|
function getPackageManager(dir) {
|
|
3887
3939
|
try {
|
|
3888
|
-
if (
|
|
3889
|
-
if (
|
|
3890
|
-
if (
|
|
3891
|
-
if (
|
|
3940
|
+
if (fs12.existsSync(path14.join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
3941
|
+
if (fs12.existsSync(path14.join(dir, "yarn.lock"))) return "yarn";
|
|
3942
|
+
if (fs12.existsSync(path14.join(dir, "bun.lockb"))) return "bun";
|
|
3943
|
+
if (fs12.existsSync(path14.join(dir, "package-lock.json"))) return "npm";
|
|
3892
3944
|
return "unknown";
|
|
3893
3945
|
} catch {
|
|
3894
3946
|
return "unknown";
|
|
@@ -3896,9 +3948,9 @@ function getPackageManager(dir) {
|
|
|
3896
3948
|
}
|
|
3897
3949
|
function getProjectType(dir) {
|
|
3898
3950
|
try {
|
|
3899
|
-
const files =
|
|
3951
|
+
const files = fs12.readdirSync(dir);
|
|
3900
3952
|
if (files.includes("package.json")) {
|
|
3901
|
-
const pkg = JSON.parse(
|
|
3953
|
+
const pkg = JSON.parse(fs12.readFileSync(path14.join(dir, "package.json"), "utf-8"));
|
|
3902
3954
|
if (pkg.dependencies?.next || pkg.devDependencies?.next) return "Next.js";
|
|
3903
3955
|
if (pkg.dependencies?.react || pkg.devDependencies?.react) return "React";
|
|
3904
3956
|
if (pkg.dependencies?.express || pkg.devDependencies?.express) return "Express";
|
|
@@ -3917,9 +3969,9 @@ function getProjectType(dir) {
|
|
|
3917
3969
|
}
|
|
3918
3970
|
function getTestFramework(dir) {
|
|
3919
3971
|
try {
|
|
3920
|
-
const pkgPath =
|
|
3921
|
-
if (
|
|
3922
|
-
const pkg = JSON.parse(
|
|
3972
|
+
const pkgPath = path14.join(dir, "package.json");
|
|
3973
|
+
if (fs12.existsSync(pkgPath)) {
|
|
3974
|
+
const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
|
|
3923
3975
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3924
3976
|
if (deps.jest) return "jest";
|
|
3925
3977
|
if (deps.vitest) return "vitest";
|
|
@@ -3928,7 +3980,7 @@ function getTestFramework(dir) {
|
|
|
3928
3980
|
if (deps["@playwright/test"]) return "playwright";
|
|
3929
3981
|
if (deps.cypress) return "cypress";
|
|
3930
3982
|
}
|
|
3931
|
-
if (
|
|
3983
|
+
if (fs12.existsSync(path14.join(dir, "pytest.ini")) || fs12.existsSync(path14.join(dir, "conftest.py"))) return "pytest";
|
|
3932
3984
|
return "unknown";
|
|
3933
3985
|
} catch {
|
|
3934
3986
|
return "unknown";
|
|
@@ -3936,9 +3988,9 @@ function getTestFramework(dir) {
|
|
|
3936
3988
|
}
|
|
3937
3989
|
function getTestCommand(dir) {
|
|
3938
3990
|
try {
|
|
3939
|
-
const pkgPath =
|
|
3940
|
-
if (
|
|
3941
|
-
const pkg = JSON.parse(
|
|
3991
|
+
const pkgPath = path14.join(dir, "package.json");
|
|
3992
|
+
if (fs12.existsSync(pkgPath)) {
|
|
3993
|
+
const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
|
|
3942
3994
|
if (pkg.scripts?.test) return `npm test`;
|
|
3943
3995
|
if (pkg.scripts?.["test:unit"]) return `npm run test:unit`;
|
|
3944
3996
|
}
|
|
@@ -4006,7 +4058,7 @@ You MUST adapt all commands to this environment. Use the correct package manager
|
|
|
4006
4058
|
## How You Work
|
|
4007
4059
|
|
|
4008
4060
|
### For Multi-Step Tasks:
|
|
4009
|
-
1. **Acknowledge** - Confirm the request immediately via
|
|
4061
|
+
1. **Acknowledge** - Confirm the request immediately via message
|
|
4010
4062
|
2. **Plan** - Use the TODO tool to create a structured plan
|
|
4011
4063
|
3. **Execute** - Implement with clear narration of each step
|
|
4012
4064
|
4. **Test** - Run tests after any code changes (MANDATORY for production code)
|
|
@@ -4017,25 +4069,11 @@ You MUST adapt all commands to this environment. Use the correct package manager
|
|
|
4017
4069
|
- Quick acknowledgment + immediate action
|
|
4018
4070
|
- No TODO needed for single operations
|
|
4019
4071
|
|
|
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
|
|
4072
|
+
### Testing Philosophy:
|
|
4073
|
+
Run tests when modifying code. Use the \`testing\` skill for detailed best practices.
|
|
4028
4074
|
|
|
4029
|
-
**
|
|
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
|
|
4034
|
-
|
|
4035
|
-
**Commands you should know:**
|
|
4075
|
+
**Commands:**
|
|
4036
4076
|
- Run tests: {test_command}
|
|
4037
|
-
- Run single test: Check project conventions
|
|
4038
|
-
- Watch mode: Usually \`npm run test:watch\` or \`npm test -- --watch\`
|
|
4039
4077
|
</workflow>
|
|
4040
4078
|
|
|
4041
4079
|
---
|
|
@@ -4050,8 +4088,8 @@ You are a **teammate who writes tests**, not an assistant who skips them.
|
|
|
4050
4088
|
- **Check file exists** before attempting edits
|
|
4051
4089
|
|
|
4052
4090
|
### Safe Auto-Approved Tools (no confirmation needed):
|
|
4053
|
-
-
|
|
4054
|
-
- ls_tool
|
|
4091
|
+
- message
|
|
4092
|
+
- ls_tool
|
|
4055
4093
|
- read_file_lines
|
|
4056
4094
|
- count_file_lines
|
|
4057
4095
|
- todo
|
|
@@ -4102,7 +4140,7 @@ You MUST use \`command_status\` to get the output.
|
|
|
4102
4140
|
[Step 2] command_status({ command_id: "cmd_001", wait_seconds: 120 })
|
|
4103
4141
|
\u2192 { status: "completed", exit_code: 0, stdout: "added 150 packages..." }
|
|
4104
4142
|
|
|
4105
|
-
[Step 3]
|
|
4143
|
+
[Step 3] message({ content: "Dependencies installed.", message_type: "info" })
|
|
4106
4144
|
\`\`\`
|
|
4107
4145
|
|
|
4108
4146
|
**[OK] Run tests:**
|
|
@@ -4131,7 +4169,7 @@ You MUST use \`command_status\` to get the output.
|
|
|
4131
4169
|
[Step 2] command_status({ command_id: "cmd_004", wait_seconds: 10 })
|
|
4132
4170
|
\u2192 { status: "running" }
|
|
4133
4171
|
|
|
4134
|
-
[Step 3]
|
|
4172
|
+
[Step 3] message({ content: "Building Docker image...", message_type: "info" })
|
|
4135
4173
|
|
|
4136
4174
|
[Step 4] command_status({ command_id: "cmd_004", wait_seconds: 10 })
|
|
4137
4175
|
\u2192 { status: "completed", exit_code: 0, stdout: "Successfully built abc123" }
|
|
@@ -4140,7 +4178,7 @@ You MUST use \`command_status\` to get the output.
|
|
|
4140
4178
|
**[WRONG] Never forget command_status:**
|
|
4141
4179
|
\`\`\`
|
|
4142
4180
|
shell_command({ command: "npm install" })
|
|
4143
|
-
|
|
4181
|
+
message({ content: "Installed!", message_type: "info" }) // WRONG: You don't know if it succeeded!
|
|
4144
4182
|
\`\`\`
|
|
4145
4183
|
|
|
4146
4184
|
**[WRONG] Never use long waits (blocks user):**
|
|
@@ -4185,39 +4223,29 @@ Examples:
|
|
|
4185
4223
|
- Celebrate wins, acknowledge mistakes
|
|
4186
4224
|
|
|
4187
4225
|
**Message via tool only:**
|
|
4188
|
-
-
|
|
4189
|
-
-
|
|
4190
|
-
|
|
4226
|
+
- message with \`result\` = END your turn (after questions/completions)
|
|
4227
|
+
- message with \`info\` = progress update, you continue working
|
|
4228
|
+
|
|
4229
|
+
**CRITICAL: After asking questions, ALWAYS use result!**
|
|
4191
4230
|
|
|
4192
|
-
**
|
|
4231
|
+
**Asking question (result):**
|
|
4193
4232
|
\`\`\`
|
|
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.")
|
|
4233
|
+
message({ content: "Project or global?", message_type: "result" })
|
|
4234
|
+
// STOP and wait for user
|
|
4201
4235
|
\`\`\`
|
|
4202
|
-
</communication_style>
|
|
4203
|
-
|
|
4204
|
-
---
|
|
4205
4236
|
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
- **Commit format**: Conventional Commits (feat:, fix:, chore:, docs:, test:, refactor:)
|
|
4212
|
-
- **NEVER push** unless explicitly asked
|
|
4213
|
-
- **NEVER use destructive commands**: reset --hard, rebase -i, force push
|
|
4237
|
+
**Progress then continue (info):**
|
|
4238
|
+
\`\`\`
|
|
4239
|
+
message({ content: "Installing...", message_type: "info" })
|
|
4240
|
+
// continue with next tool
|
|
4241
|
+
\`\`\`
|
|
4214
4242
|
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
</
|
|
4243
|
+
**Task done (result):**
|
|
4244
|
+
\`\`\`
|
|
4245
|
+
message({ content: "Done. All tests pass.", message_type: "result" })
|
|
4246
|
+
// STOP - finished
|
|
4247
|
+
\`\`\`
|
|
4248
|
+
</communication_style>
|
|
4221
4249
|
|
|
4222
4250
|
---
|
|
4223
4251
|
|
|
@@ -4254,15 +4282,15 @@ You communicate because you respect your teammate.
|
|
|
4254
4282
|
Let's build something great, {username}.
|
|
4255
4283
|
</personality>
|
|
4256
4284
|
`;
|
|
4257
|
-
function getUnifiedSystemPrompt() {
|
|
4285
|
+
function getUnifiedSystemPrompt(availableSkills) {
|
|
4258
4286
|
const cwd = process.cwd();
|
|
4259
4287
|
const env = {
|
|
4260
|
-
os_type:
|
|
4261
|
-
os_version:
|
|
4262
|
-
architecture:
|
|
4288
|
+
os_type: os7.type(),
|
|
4289
|
+
os_version: os7.release(),
|
|
4290
|
+
architecture: os7.arch(),
|
|
4263
4291
|
workdir: cwd,
|
|
4264
4292
|
shell_type: process.env.SHELL || process.env.COMSPEC || "unknown",
|
|
4265
|
-
username:
|
|
4293
|
+
username: os7.userInfo().username,
|
|
4266
4294
|
current_date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
4267
4295
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
4268
4296
|
is_git_repo: isGitRepo(cwd) ? "yes" : "no",
|
|
@@ -4274,15 +4302,170 @@ function getUnifiedSystemPrompt() {
|
|
|
4274
4302
|
test_framework: getTestFramework(cwd),
|
|
4275
4303
|
test_command: getTestCommand(cwd)
|
|
4276
4304
|
};
|
|
4277
|
-
|
|
4278
|
-
(
|
|
4305
|
+
let prompt = Object.entries(env).reduce(
|
|
4306
|
+
(p, [key, value]) => p.replaceAll(`{${key}}`, value),
|
|
4279
4307
|
SYSTEM_PROMPT
|
|
4280
4308
|
);
|
|
4309
|
+
if (availableSkills && availableSkills.length > 0) {
|
|
4310
|
+
const skillsList = availableSkills.map((s) => `- ${s.name}: ${s.description}`).join("\n");
|
|
4311
|
+
prompt += `
|
|
4312
|
+
|
|
4313
|
+
---
|
|
4314
|
+
|
|
4315
|
+
<available_skills>
|
|
4316
|
+
## Specialized Skills
|
|
4317
|
+
|
|
4318
|
+
You have access to specialized knowledge modules called **skills**. Skills extend your capabilities with domain-specific expertise, workflows, and best practices.
|
|
4319
|
+
|
|
4320
|
+
**Available skills:**
|
|
4321
|
+
${skillsList}
|
|
4322
|
+
|
|
4323
|
+
**Skill Lifecycle:**
|
|
4324
|
+
- Skills are **ephemeral** - they exist only for the current task
|
|
4325
|
+
- After completing a task, skill context is discarded
|
|
4326
|
+
- You MUST reload a skill with \`load_skill\` each time you need it
|
|
4327
|
+
- Never assume a skill is still loaded from a previous task
|
|
4328
|
+
|
|
4329
|
+
---
|
|
4330
|
+
|
|
4331
|
+
### Skill Selection Process
|
|
4332
|
+
|
|
4333
|
+
**BEFORE starting any task, follow this decision process:**
|
|
4334
|
+
|
|
4335
|
+
1. **Understand the task** - What is the user trying to accomplish?
|
|
4336
|
+
2. **Identify the domain** - What area of expertise does this task require? (e.g., version control, testing, deployment, documentation)
|
|
4337
|
+
3. **Match against skills** - Read each skill's description above. Does any skill's domain match the task?
|
|
4338
|
+
4. **Load if matched** - If a skill matches, load it FIRST with \`load_skill\` before taking any action
|
|
4339
|
+
5. **Follow skill instructions** - Once loaded, the skill provides specific workflows to follow
|
|
4340
|
+
|
|
4341
|
+
**Decision examples:**
|
|
4342
|
+
\`\`\`
|
|
4343
|
+
Task: "Save my changes with a good message"
|
|
4344
|
+
\u2192 Domain: Version control, commits
|
|
4345
|
+
\u2192 Check skills: Is there a skill for git/commits/version control?
|
|
4346
|
+
\u2192 If yes: load_skill({ skill_name: "<matching-skill>" })
|
|
4347
|
+
|
|
4348
|
+
Task: "Make sure this code works correctly"
|
|
4349
|
+
\u2192 Domain: Testing, validation, quality assurance
|
|
4350
|
+
\u2192 Check skills: Is there a skill for testing?
|
|
4351
|
+
\u2192 If yes: load_skill({ skill_name: "<matching-skill>" })
|
|
4352
|
+
|
|
4353
|
+
Task: "Release this to users"
|
|
4354
|
+
\u2192 Domain: Publishing, deployment, release
|
|
4355
|
+
\u2192 Check skills: Is there a skill for publishing/releasing?
|
|
4356
|
+
\u2192 If yes: load_skill({ skill_name: "<matching-skill>" })
|
|
4357
|
+
\`\`\`
|
|
4358
|
+
|
|
4359
|
+
**Key principle:** Match the TASK DOMAIN to the SKILL DESCRIPTION, not specific words.
|
|
4360
|
+
|
|
4361
|
+
---
|
|
4362
|
+
|
|
4363
|
+
### Skill Interpretation Rules (CRITICAL)
|
|
4364
|
+
|
|
4365
|
+
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.
|
|
4366
|
+
|
|
4367
|
+
**Frontmatter Structure:**
|
|
4368
|
+
\`\`\`yaml
|
|
4369
|
+
---
|
|
4370
|
+
# IDENTITY
|
|
4371
|
+
name: skill-name # Unique identifier
|
|
4372
|
+
description: ... # Purpose of the skill
|
|
4373
|
+
version: 1.0.0
|
|
4374
|
+
author: ...
|
|
4375
|
+
license: ...
|
|
4376
|
+
|
|
4377
|
+
# DEPENDENCIES
|
|
4378
|
+
depends_on: # Other skills this skill may delegate to
|
|
4379
|
+
- other-skill-name
|
|
4380
|
+
|
|
4381
|
+
# TOOLS
|
|
4382
|
+
tools:
|
|
4383
|
+
required: # Tools you MUST use for this skill
|
|
4384
|
+
- shell_command
|
|
4385
|
+
- command_status
|
|
4386
|
+
recommended: # Tools that enhance execution
|
|
4387
|
+
- read_file_lines
|
|
4388
|
+
---
|
|
4389
|
+
\`\`\`
|
|
4390
|
+
|
|
4391
|
+
**Interpretation Flow:**
|
|
4392
|
+
\`\`\`
|
|
4393
|
+
1. LOAD SKILL
|
|
4394
|
+
load_skill({ skill_name: "npm-publish" })
|
|
4395
|
+
|
|
4396
|
+
2. READ FRONTMATTER FIRST
|
|
4397
|
+
- name: npm-publish
|
|
4398
|
+
- depends_on: [git-conventional]
|
|
4399
|
+
- tools.required: [shell_command, command_status]
|
|
4400
|
+
- tools.recommended: [read_file_lines, message]
|
|
4401
|
+
|
|
4402
|
+
3. VERIFY TOOLS
|
|
4403
|
+
Are required tools available? If not, inform user.
|
|
4404
|
+
|
|
4405
|
+
4. READ SKILL BODY
|
|
4406
|
+
Workflow instructions, examples, rules
|
|
4407
|
+
|
|
4408
|
+
5. EXECUTE WORKFLOW
|
|
4409
|
+
- Use the REQUIRED tools from frontmatter
|
|
4410
|
+
- When body says "delegate to X":
|
|
4411
|
+
a. Verify X is in depends_on
|
|
4412
|
+
b. load_skill({ skill_name: "X" })
|
|
4413
|
+
c. Execute X workflow
|
|
4414
|
+
d. Return to primary skill
|
|
4415
|
+
- Continue until workflow complete
|
|
4416
|
+
\`\`\`
|
|
4417
|
+
|
|
4418
|
+
**Field Meanings:**
|
|
4419
|
+
| Field | Purpose | Your Action |
|
|
4420
|
+
|-------|---------|-------------|
|
|
4421
|
+
| \`name\` | Skill identifier | Confirm correct skill loaded |
|
|
4422
|
+
| \`description\` | Skill purpose | Understand what this skill does |
|
|
4423
|
+
| \`depends_on\` | Skill dependencies | Know which skills can be delegated to |
|
|
4424
|
+
| \`tools.required\` | Mandatory tools | MUST use these tools for execution |
|
|
4425
|
+
| \`tools.recommended\` | Optional tools | Use if helpful |
|
|
4426
|
+
|
|
4427
|
+
**Orchestration Example:**
|
|
4428
|
+
\`\`\`
|
|
4429
|
+
User: "Publish the package"
|
|
4430
|
+
|
|
4431
|
+
1. Match domain -> npm-publish skill
|
|
4432
|
+
2. load_skill({ skill_name: "npm-publish" })
|
|
4433
|
+
|
|
4434
|
+
3. Read frontmatter:
|
|
4435
|
+
- depends_on: [git-conventional]
|
|
4436
|
+
- tools.required: [shell_command, command_status]
|
|
4437
|
+
|
|
4438
|
+
4. Read body -> Workflow says:
|
|
4439
|
+
"Step 1: If uncommitted changes, delegate to git-conventional"
|
|
4440
|
+
|
|
4441
|
+
5. Check git status with shell_command (required tool)
|
|
4442
|
+
Found changes
|
|
4443
|
+
|
|
4444
|
+
6. Delegate: load_skill({ skill_name: "git-conventional" })
|
|
4445
|
+
- Read git-conventional frontmatter
|
|
4446
|
+
- Follow its workflow for commit
|
|
4447
|
+
- Commit done
|
|
4448
|
+
|
|
4449
|
+
7. Return to npm-publish Step 2: npm version patch
|
|
4450
|
+
8. Step 3: npm publish
|
|
4451
|
+
9. Done
|
|
4452
|
+
\`\`\`
|
|
4453
|
+
|
|
4454
|
+
**Rules:**
|
|
4455
|
+
- Frontmatter is the SPECIFICATION - read it first
|
|
4456
|
+
- Body is the WORKFLOW - execute after understanding spec
|
|
4457
|
+
- \`depends_on\` authorizes delegation - only delegate to listed skills
|
|
4458
|
+
- \`tools.required\` are mandatory - use them for execution
|
|
4459
|
+
- The skill body tells you WHEN to delegate, not the frontmatter
|
|
4460
|
+
</available_skills>
|
|
4461
|
+
`;
|
|
4462
|
+
}
|
|
4463
|
+
return prompt;
|
|
4281
4464
|
}
|
|
4282
4465
|
function isGitRepo(dir) {
|
|
4283
4466
|
try {
|
|
4284
|
-
const gitPath =
|
|
4285
|
-
return
|
|
4467
|
+
const gitPath = path14.join(dir, ".git");
|
|
4468
|
+
return fs12.existsSync(gitPath) && fs12.lstatSync(gitPath).isDirectory();
|
|
4286
4469
|
} catch {
|
|
4287
4470
|
return false;
|
|
4288
4471
|
}
|
|
@@ -4338,8 +4521,155 @@ function createApiContextWindow(fullHistory, maxTurns) {
|
|
|
4338
4521
|
return finalContext;
|
|
4339
4522
|
}
|
|
4340
4523
|
|
|
4524
|
+
// src/app/agent/core/llm/tool_call_normalizer.ts
|
|
4525
|
+
import { randomUUID } from "crypto";
|
|
4526
|
+
var ToolCallNormalizer = class {
|
|
4527
|
+
/**
|
|
4528
|
+
* Normaliza a mensagem do assistant, convertendo diferentes formatos de tool calls
|
|
4529
|
+
*/
|
|
4530
|
+
static normalizeAssistantMessage(message2) {
|
|
4531
|
+
if (message2.tool_calls && this.isOpenAIFormat(message2.tool_calls)) {
|
|
4532
|
+
return message2;
|
|
4533
|
+
}
|
|
4534
|
+
const toolCalls = this.extractToolCalls(message2);
|
|
4535
|
+
if (toolCalls.length > 0) {
|
|
4536
|
+
return {
|
|
4537
|
+
role: message2.role || "assistant",
|
|
4538
|
+
content: message2.content || null,
|
|
4539
|
+
tool_calls: toolCalls
|
|
4540
|
+
};
|
|
4541
|
+
}
|
|
4542
|
+
return message2;
|
|
4543
|
+
}
|
|
4544
|
+
/**
|
|
4545
|
+
* Verifica se já está no formato OpenAI
|
|
4546
|
+
*/
|
|
4547
|
+
static isOpenAIFormat(toolCalls) {
|
|
4548
|
+
if (!Array.isArray(toolCalls) || toolCalls.length === 0) return false;
|
|
4549
|
+
const firstCall = toolCalls[0];
|
|
4550
|
+
return typeof firstCall.id === "string" && firstCall.type === "function" && typeof firstCall.function?.name === "string" && typeof firstCall.function?.arguments === "string";
|
|
4551
|
+
}
|
|
4552
|
+
/**
|
|
4553
|
+
* Extrai tool calls de diversos formatos possíveis
|
|
4554
|
+
*/
|
|
4555
|
+
static extractToolCalls(message2) {
|
|
4556
|
+
const results = [];
|
|
4557
|
+
if (message2.tool_calls && Array.isArray(message2.tool_calls)) {
|
|
4558
|
+
for (const call of message2.tool_calls) {
|
|
4559
|
+
const normalized = this.normalizeToolCall(call);
|
|
4560
|
+
if (normalized) results.push(normalized);
|
|
4561
|
+
}
|
|
4562
|
+
}
|
|
4563
|
+
if (typeof message2.content === "string" && message2.content.trim()) {
|
|
4564
|
+
const extracted = this.extractFromContent(message2.content);
|
|
4565
|
+
results.push(...extracted);
|
|
4566
|
+
}
|
|
4567
|
+
if (message2.function_call) {
|
|
4568
|
+
const normalized = this.normalizeToolCall(message2.function_call);
|
|
4569
|
+
if (normalized) results.push(normalized);
|
|
4570
|
+
}
|
|
4571
|
+
return results;
|
|
4572
|
+
}
|
|
4573
|
+
/**
|
|
4574
|
+
* Normaliza um único tool call para o formato OpenAI
|
|
4575
|
+
*/
|
|
4576
|
+
static normalizeToolCall(call) {
|
|
4577
|
+
try {
|
|
4578
|
+
if (call.id && call.function?.name) {
|
|
4579
|
+
return {
|
|
4580
|
+
id: call.id,
|
|
4581
|
+
type: "function",
|
|
4582
|
+
function: {
|
|
4583
|
+
name: call.function.name,
|
|
4584
|
+
arguments: typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments)
|
|
4585
|
+
}
|
|
4586
|
+
};
|
|
4587
|
+
}
|
|
4588
|
+
if (call.name) {
|
|
4589
|
+
return {
|
|
4590
|
+
id: call.id || randomUUID(),
|
|
4591
|
+
type: "function",
|
|
4592
|
+
function: {
|
|
4593
|
+
name: call.name,
|
|
4594
|
+
arguments: typeof call.arguments === "string" ? call.arguments : JSON.stringify(call.arguments || {})
|
|
4595
|
+
}
|
|
4596
|
+
};
|
|
4597
|
+
}
|
|
4598
|
+
if (call.function && typeof call.function === "object") {
|
|
4599
|
+
return {
|
|
4600
|
+
id: call.id || randomUUID(),
|
|
4601
|
+
type: "function",
|
|
4602
|
+
function: {
|
|
4603
|
+
name: call.function.name,
|
|
4604
|
+
arguments: typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments || {})
|
|
4605
|
+
}
|
|
4606
|
+
};
|
|
4607
|
+
}
|
|
4608
|
+
return null;
|
|
4609
|
+
} catch (error) {
|
|
4610
|
+
console.error("Error normalizing tool call:", error, call);
|
|
4611
|
+
return null;
|
|
4612
|
+
}
|
|
4613
|
+
}
|
|
4614
|
+
/**
|
|
4615
|
+
* Extrai tool calls do content (pode estar em markdown, JSON, etc)
|
|
4616
|
+
*/
|
|
4617
|
+
static extractFromContent(content) {
|
|
4618
|
+
const results = [];
|
|
4619
|
+
const cleanContent = content.replace(/```(?:json)?\s*([\s\S]*?)```/g, "$1");
|
|
4620
|
+
const jsonMatches = this.extractJsonObjects(cleanContent);
|
|
4621
|
+
for (const jsonStr of jsonMatches) {
|
|
4622
|
+
try {
|
|
4623
|
+
const parsed = JSON.parse(jsonStr);
|
|
4624
|
+
if (Array.isArray(parsed)) {
|
|
4625
|
+
for (const call of parsed) {
|
|
4626
|
+
const normalized = this.normalizeToolCall(call);
|
|
4627
|
+
if (normalized) results.push(normalized);
|
|
4628
|
+
}
|
|
4629
|
+
} else if (parsed.name || parsed.function) {
|
|
4630
|
+
const normalized = this.normalizeToolCall(parsed);
|
|
4631
|
+
if (normalized) results.push(normalized);
|
|
4632
|
+
} else if (parsed.tool_calls && Array.isArray(parsed.tool_calls)) {
|
|
4633
|
+
for (const call of parsed.tool_calls) {
|
|
4634
|
+
const normalized = this.normalizeToolCall(call);
|
|
4635
|
+
if (normalized) results.push(normalized);
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
} catch (e) {
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
return results;
|
|
4642
|
+
}
|
|
4643
|
+
/**
|
|
4644
|
+
* Extrai objetos JSON de uma string (suporta múltiplos objetos)
|
|
4645
|
+
*/
|
|
4646
|
+
static extractJsonObjects(text) {
|
|
4647
|
+
const results = [];
|
|
4648
|
+
let depth = 0;
|
|
4649
|
+
let start = -1;
|
|
4650
|
+
for (let i = 0; i < text.length; i++) {
|
|
4651
|
+
if (text[i] === "{") {
|
|
4652
|
+
if (depth === 0) start = i;
|
|
4653
|
+
depth++;
|
|
4654
|
+
} else if (text[i] === "}") {
|
|
4655
|
+
depth--;
|
|
4656
|
+
if (depth === 0 && start !== -1) {
|
|
4657
|
+
results.push(text.substring(start, i + 1));
|
|
4658
|
+
start = -1;
|
|
4659
|
+
}
|
|
4660
|
+
}
|
|
4661
|
+
}
|
|
4662
|
+
return results;
|
|
4663
|
+
}
|
|
4664
|
+
/**
|
|
4665
|
+
* Valida se um tool call normalizado é válido
|
|
4666
|
+
*/
|
|
4667
|
+
static isValidToolCall(call) {
|
|
4668
|
+
return !!(call.id && call.type === "function" && call.function?.name && typeof call.function.arguments === "string");
|
|
4669
|
+
}
|
|
4670
|
+
};
|
|
4671
|
+
|
|
4341
4672
|
// src/app/agent/bluma/core/bluma.ts
|
|
4342
|
-
init_tool_call_normalizer();
|
|
4343
4673
|
var BluMaAgent = class {
|
|
4344
4674
|
llm;
|
|
4345
4675
|
deploymentName;
|
|
@@ -4349,6 +4679,7 @@ var BluMaAgent = class {
|
|
|
4349
4679
|
eventBus;
|
|
4350
4680
|
mcpClient;
|
|
4351
4681
|
feedbackSystem;
|
|
4682
|
+
skillLoader;
|
|
4352
4683
|
maxContextTurns = 30;
|
|
4353
4684
|
isInterrupted = false;
|
|
4354
4685
|
constructor(sessionId2, eventBus2, llm, deploymentName, mcpClient, feedbackSystem) {
|
|
@@ -4358,6 +4689,7 @@ var BluMaAgent = class {
|
|
|
4358
4689
|
this.deploymentName = deploymentName;
|
|
4359
4690
|
this.mcpClient = mcpClient;
|
|
4360
4691
|
this.feedbackSystem = feedbackSystem;
|
|
4692
|
+
this.skillLoader = new SkillLoader(process.cwd());
|
|
4361
4693
|
this.eventBus.on("user_interrupt", () => {
|
|
4362
4694
|
this.isInterrupted = true;
|
|
4363
4695
|
this.eventBus.emit("backend_message", { type: "done", status: "interrupted" });
|
|
@@ -4369,6 +4701,10 @@ var BluMaAgent = class {
|
|
|
4369
4701
|
try {
|
|
4370
4702
|
if (this.sessionFile) {
|
|
4371
4703
|
await saveSessionHistory(this.sessionFile, this.history);
|
|
4704
|
+
} else {
|
|
4705
|
+
const [sessionFile, _] = await loadOrcreateSession(this.sessionId);
|
|
4706
|
+
this.sessionFile = sessionFile;
|
|
4707
|
+
await saveSessionHistory(this.sessionFile, this.history);
|
|
4372
4708
|
}
|
|
4373
4709
|
} catch (e) {
|
|
4374
4710
|
this.eventBus.emit("backend_message", { type: "error", message: `Falha ao salvar hist\xF3rico ap\xF3s user_overlay: ${e.message}` });
|
|
@@ -4381,8 +4717,13 @@ var BluMaAgent = class {
|
|
|
4381
4717
|
const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
|
|
4382
4718
|
this.sessionFile = sessionFile;
|
|
4383
4719
|
this.history = history;
|
|
4720
|
+
initializeSkillContext({
|
|
4721
|
+
history: this.history,
|
|
4722
|
+
skillLoader: this.skillLoader
|
|
4723
|
+
});
|
|
4384
4724
|
if (this.history.length === 0) {
|
|
4385
|
-
const
|
|
4725
|
+
const availableSkills = this.skillLoader.listAvailable();
|
|
4726
|
+
const systemPrompt = getUnifiedSystemPrompt(availableSkills);
|
|
4386
4727
|
this.history.push({ role: "system", content: systemPrompt });
|
|
4387
4728
|
await saveSessionHistory(this.sessionFile, this.history);
|
|
4388
4729
|
}
|
|
@@ -4397,6 +4738,9 @@ var BluMaAgent = class {
|
|
|
4397
4738
|
this.isInterrupted = false;
|
|
4398
4739
|
const inputText = String(userInput.content || "").trim();
|
|
4399
4740
|
this.history.push({ role: "user", content: inputText });
|
|
4741
|
+
if (inputText === "/init") {
|
|
4742
|
+
this.eventBus.emit("dispatch", inputText);
|
|
4743
|
+
}
|
|
4400
4744
|
await this._continueConversation();
|
|
4401
4745
|
}
|
|
4402
4746
|
async handleToolResponse(decisionData) {
|
|
@@ -4495,9 +4839,15 @@ var BluMaAgent = class {
|
|
|
4495
4839
|
});
|
|
4496
4840
|
}
|
|
4497
4841
|
this.eventBus.emit("backend_message", { type: "tool_result", tool_name: toolName, result: toolResultContent });
|
|
4498
|
-
if (toolName
|
|
4499
|
-
|
|
4500
|
-
|
|
4842
|
+
if (toolName === "message") {
|
|
4843
|
+
try {
|
|
4844
|
+
const resultObj = typeof toolResultContent === "string" ? JSON.parse(toolResultContent) : toolResultContent;
|
|
4845
|
+
if (resultObj.message_type === "result") {
|
|
4846
|
+
shouldContinueConversation = false;
|
|
4847
|
+
this.eventBus.emit("backend_message", { type: "done", status: "completed" });
|
|
4848
|
+
}
|
|
4849
|
+
} catch {
|
|
4850
|
+
}
|
|
4501
4851
|
}
|
|
4502
4852
|
} else {
|
|
4503
4853
|
toolResultContent = "The system rejected this action. Verify that the command you are executing contributes to the tasks intent and try again.";
|
|
@@ -4524,7 +4874,7 @@ var BluMaAgent = class {
|
|
|
4524
4874
|
|
|
4525
4875
|
${editData.error.display}`;
|
|
4526
4876
|
}
|
|
4527
|
-
const filename =
|
|
4877
|
+
const filename = path15.basename(toolArgs.file_path);
|
|
4528
4878
|
return createDiff(filename, editData.currentContent || "", editData.newContent);
|
|
4529
4879
|
} catch (e) {
|
|
4530
4880
|
return `An unexpected error occurred while generating the edit preview: ${e.message}`;
|
|
@@ -4550,18 +4900,18 @@ ${editData.error.display}`;
|
|
|
4550
4900
|
this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
|
|
4551
4901
|
return;
|
|
4552
4902
|
}
|
|
4553
|
-
let
|
|
4554
|
-
|
|
4555
|
-
if (
|
|
4556
|
-
const reasoningText =
|
|
4903
|
+
let message2 = response.choices[0].message;
|
|
4904
|
+
message2 = ToolCallNormalizer.normalizeAssistantMessage(message2);
|
|
4905
|
+
if (message2.reasoning_content || message2.reasoning) {
|
|
4906
|
+
const reasoningText = message2.reasoning_content || message2.reasoning;
|
|
4557
4907
|
this.eventBus.emit("backend_message", {
|
|
4558
4908
|
type: "reasoning",
|
|
4559
4909
|
content: typeof reasoningText === "string" ? reasoningText : JSON.stringify(reasoningText)
|
|
4560
4910
|
});
|
|
4561
4911
|
}
|
|
4562
|
-
this.history.push(
|
|
4563
|
-
if (
|
|
4564
|
-
const validToolCalls =
|
|
4912
|
+
this.history.push(message2);
|
|
4913
|
+
if (message2.tool_calls && message2.tool_calls.length > 0) {
|
|
4914
|
+
const validToolCalls = message2.tool_calls.filter(
|
|
4565
4915
|
(call) => ToolCallNormalizer.isValidToolCall(call)
|
|
4566
4916
|
);
|
|
4567
4917
|
if (validToolCalls.length === 0) {
|
|
@@ -4573,12 +4923,12 @@ ${editData.error.display}`;
|
|
|
4573
4923
|
return;
|
|
4574
4924
|
}
|
|
4575
4925
|
const autoApprovedTools = [
|
|
4576
|
-
"
|
|
4577
|
-
"message_notify_user",
|
|
4926
|
+
"message",
|
|
4578
4927
|
"ls_tool",
|
|
4579
4928
|
"count_file_lines",
|
|
4580
4929
|
"read_file_lines",
|
|
4581
|
-
"todo"
|
|
4930
|
+
"todo",
|
|
4931
|
+
"load_skill"
|
|
4582
4932
|
];
|
|
4583
4933
|
const toolToCall = validToolCalls[0];
|
|
4584
4934
|
const isSafeTool = autoApprovedTools.some((safeTool) => toolToCall.function.name.includes(safeTool));
|
|
@@ -4594,13 +4944,13 @@ ${editData.error.display}`;
|
|
|
4594
4944
|
this.eventBus.emit("backend_message", { type: "confirmation_request", tool_calls: validToolCalls });
|
|
4595
4945
|
}
|
|
4596
4946
|
}
|
|
4597
|
-
} else if (
|
|
4598
|
-
this.eventBus.emit("backend_message", { type: "assistant_message", content:
|
|
4947
|
+
} else if (message2.content) {
|
|
4948
|
+
this.eventBus.emit("backend_message", { type: "assistant_message", content: message2.content });
|
|
4599
4949
|
const feedback = this.feedbackSystem.generateFeedback({
|
|
4600
4950
|
event: "protocol_violation_direct_text",
|
|
4601
|
-
details: { violationContent:
|
|
4951
|
+
details: { violationContent: message2.content }
|
|
4602
4952
|
});
|
|
4603
|
-
this.eventBus.emit("backend_message", { type: "protocol_violation", message: feedback.message, content:
|
|
4953
|
+
this.eventBus.emit("backend_message", { type: "protocol_violation", message: feedback.message, content: message2.content });
|
|
4604
4954
|
this.history.push({ role: "system", content: feedback.correction });
|
|
4605
4955
|
await this._continueConversation();
|
|
4606
4956
|
} else {
|
|
@@ -4617,14 +4967,20 @@ ${editData.error.display}`;
|
|
|
4617
4967
|
};
|
|
4618
4968
|
|
|
4619
4969
|
// src/app/agent/core/llm/llm.ts
|
|
4620
|
-
|
|
4970
|
+
import OpenAI from "openai";
|
|
4971
|
+
var LLMService = class {
|
|
4621
4972
|
client;
|
|
4622
|
-
|
|
4623
|
-
|
|
4973
|
+
defaultModel;
|
|
4974
|
+
constructor(config2) {
|
|
4975
|
+
this.client = new OpenAI({
|
|
4976
|
+
apiKey: config2.apiKey,
|
|
4977
|
+
baseURL: config2.baseUrl || "http://109.51.171.144/mistrall/v1"
|
|
4978
|
+
});
|
|
4979
|
+
this.defaultModel = config2.model || "";
|
|
4624
4980
|
}
|
|
4625
4981
|
async chatCompletion(params) {
|
|
4626
4982
|
const resp = await this.client.chat.completions.create({
|
|
4627
|
-
model: params.model,
|
|
4983
|
+
model: params.model || this.defaultModel,
|
|
4628
4984
|
messages: params.messages,
|
|
4629
4985
|
tools: params.tools,
|
|
4630
4986
|
tool_choice: params.tool_choice,
|
|
@@ -4634,6 +4990,12 @@ var OpenAIAdapter = class {
|
|
|
4634
4990
|
});
|
|
4635
4991
|
return resp;
|
|
4636
4992
|
}
|
|
4993
|
+
/**
|
|
4994
|
+
* Retorna o modelo padrão configurado
|
|
4995
|
+
*/
|
|
4996
|
+
getDefaultModel() {
|
|
4997
|
+
return this.defaultModel;
|
|
4998
|
+
}
|
|
4637
4999
|
};
|
|
4638
5000
|
|
|
4639
5001
|
// src/app/agent/subagents/registry.ts
|
|
@@ -4648,7 +5010,7 @@ function getSubAgentByCommand(cmd) {
|
|
|
4648
5010
|
}
|
|
4649
5011
|
|
|
4650
5012
|
// src/app/agent/subagents/init/init_system_prompt.ts
|
|
4651
|
-
import
|
|
5013
|
+
import os8 from "os";
|
|
4652
5014
|
var SYSTEM_PROMPT2 = `
|
|
4653
5015
|
|
|
4654
5016
|
### YOU ARE BluMa CLI \u2014 INIT SUBAGENT \u2014 AUTONOMOUS SENIOR SOFTWARE ENGINEER @ NOMADENGENUITY
|
|
@@ -4662,7 +5024,7 @@ You extend the BluMa multi-agent architecture and handle the project bootstrappi
|
|
|
4662
5024
|
You are BluMa InitSubAgent. Maintain professionalism and technical language.
|
|
4663
5025
|
|
|
4664
5026
|
- Communication:
|
|
4665
|
-
ALL messages must be sent via '
|
|
5027
|
+
ALL messages must be sent via 'message'.
|
|
4666
5028
|
No direct text replies to the user.
|
|
4667
5029
|
|
|
4668
5030
|
- Task Completion:
|
|
@@ -4787,8 +5149,8 @@ Rule Summary:
|
|
|
4787
5149
|
- Never invent file content. Read files via tools to confirm.
|
|
4788
5150
|
|
|
4789
5151
|
## OUTPUT
|
|
4790
|
-
- Emit 'backend_message' events through tools only (
|
|
4791
|
-
- Before writing BluMa.md, propose structure via
|
|
5152
|
+
- Emit 'backend_message' events through tools only (message) for progress updates.
|
|
5153
|
+
- Before writing BluMa.md, propose structure via message and proceed using edit_tool.
|
|
4792
5154
|
- If an irreversible operation is needed (e.g., overwriting an existing BluMa.md), issue 'confirmation_request' unless user policy indicates auto-approval.
|
|
4793
5155
|
- 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
5156
|
- On successful generation of BluMa.md, emit 'done' with status 'completed' and call agent_end_turn.
|
|
@@ -4802,7 +5164,7 @@ Rule Summary:
|
|
|
4802
5164
|
## EXEMPLAR FLOW (GUIDELINE)
|
|
4803
5165
|
1) Explore repo: ls + targeted readLines for key files (package.json, tsconfig.json, README, etc.)
|
|
4804
5166
|
2) Synthesize stack and structure with citations of evidence (file paths) in the notebook
|
|
4805
|
-
3) Draft BluMa.md structure (
|
|
5167
|
+
3) Draft BluMa.md structure (message)
|
|
4806
5168
|
4) Write BluMa.md via edit_tool
|
|
4807
5169
|
5) Announce completion and agent_end_turn
|
|
4808
5170
|
|
|
@@ -4811,12 +5173,12 @@ Rule Summary:
|
|
|
4811
5173
|
function getInitPrompt() {
|
|
4812
5174
|
const now = /* @__PURE__ */ new Date();
|
|
4813
5175
|
const collectedData = {
|
|
4814
|
-
os_type:
|
|
4815
|
-
os_version:
|
|
4816
|
-
architecture:
|
|
5176
|
+
os_type: os8.type(),
|
|
5177
|
+
os_version: os8.release(),
|
|
5178
|
+
architecture: os8.arch(),
|
|
4817
5179
|
workdir: process.cwd(),
|
|
4818
5180
|
shell_type: process.env.SHELL || process.env.COMSPEC || "Unknown",
|
|
4819
|
-
username:
|
|
5181
|
+
username: os8.userInfo().username || "Unknown",
|
|
4820
5182
|
current_date: now.toISOString().split("T")[0],
|
|
4821
5183
|
// Formato YYYY-MM-DD
|
|
4822
5184
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Unknown",
|
|
@@ -4907,17 +5269,17 @@ ${editData.error.display}`;
|
|
|
4907
5269
|
this.emitEvent("info", { message: "SubAgent task cancelled byuserr." });
|
|
4908
5270
|
return;
|
|
4909
5271
|
}
|
|
4910
|
-
const
|
|
4911
|
-
this.history.push(
|
|
4912
|
-
if (
|
|
4913
|
-
await this._handleToolExecution({ type: "user_decision_execute", tool_calls:
|
|
4914
|
-
const lastToolName =
|
|
5272
|
+
const message2 = response.choices[0].message;
|
|
5273
|
+
this.history.push(message2);
|
|
5274
|
+
if (message2.tool_calls) {
|
|
5275
|
+
await this._handleToolExecution({ type: "user_decision_execute", tool_calls: message2.tool_calls });
|
|
5276
|
+
const lastToolName = message2.tool_calls[0].function.name;
|
|
4915
5277
|
if (!lastToolName.includes("agent_end_turn") && !this.isInterrupted) {
|
|
4916
5278
|
await this._continueConversation();
|
|
4917
5279
|
}
|
|
4918
|
-
} else if (
|
|
4919
|
-
this.emitEvent("assistant_message", { content:
|
|
4920
|
-
this.emitEvent("protocol_violation", { message: "Direct text emission detected from subagent.", content:
|
|
5280
|
+
} else if (message2.content) {
|
|
5281
|
+
this.emitEvent("assistant_message", { content: message2.content });
|
|
5282
|
+
this.emitEvent("protocol_violation", { message: "Direct text emission detected from subagent.", content: message2.content });
|
|
4921
5283
|
} else {
|
|
4922
5284
|
this.emitEvent("info", { message: "SubAgent is thinking... continuing reasoning cycle." });
|
|
4923
5285
|
}
|
|
@@ -5068,21 +5430,43 @@ var SubAgentsBluMa = class {
|
|
|
5068
5430
|
}
|
|
5069
5431
|
};
|
|
5070
5432
|
|
|
5433
|
+
// src/app/agent/routeManager.ts
|
|
5434
|
+
var RouteManager = class {
|
|
5435
|
+
routeHandlers;
|
|
5436
|
+
subAgents;
|
|
5437
|
+
core;
|
|
5438
|
+
constructor(subAgents, core) {
|
|
5439
|
+
this.routeHandlers = /* @__PURE__ */ new Map();
|
|
5440
|
+
this.subAgents = subAgents;
|
|
5441
|
+
this.core = core;
|
|
5442
|
+
}
|
|
5443
|
+
registerRoute(path18, handler) {
|
|
5444
|
+
this.routeHandlers.set(path18, handler);
|
|
5445
|
+
}
|
|
5446
|
+
async handleRoute(payload) {
|
|
5447
|
+
const inputText = String(payload.content || "").trim();
|
|
5448
|
+
for (const [path18, handler] of this.routeHandlers) {
|
|
5449
|
+
if (inputText === path18 || inputText.startsWith(`${path18} `)) {
|
|
5450
|
+
return handler({ content: inputText });
|
|
5451
|
+
}
|
|
5452
|
+
}
|
|
5453
|
+
await this.core.processTurn({ content: inputText });
|
|
5454
|
+
}
|
|
5455
|
+
};
|
|
5456
|
+
|
|
5071
5457
|
// src/app/agent/agent.ts
|
|
5072
|
-
var globalEnvPath =
|
|
5458
|
+
var globalEnvPath = path16.join(os9.homedir(), ".bluma", ".env");
|
|
5073
5459
|
dotenv.config({ path: globalEnvPath });
|
|
5074
5460
|
var Agent = class {
|
|
5075
5461
|
sessionId;
|
|
5076
5462
|
eventBus;
|
|
5077
5463
|
mcpClient;
|
|
5078
5464
|
feedbackSystem;
|
|
5079
|
-
// Mantido caso UI dependa de eventos
|
|
5080
5465
|
llm;
|
|
5081
|
-
|
|
5466
|
+
routeManager;
|
|
5467
|
+
model;
|
|
5082
5468
|
core;
|
|
5083
|
-
// Delegado
|
|
5084
5469
|
subAgents;
|
|
5085
|
-
// Orquestrador de subagentes
|
|
5086
5470
|
toolInvoker;
|
|
5087
5471
|
constructor(sessionId2, eventBus2) {
|
|
5088
5472
|
this.sessionId = sessionId2;
|
|
@@ -5091,75 +5475,53 @@ var Agent = class {
|
|
|
5091
5475
|
this.toolInvoker = nativeToolInvoker;
|
|
5092
5476
|
this.mcpClient = new MCPClient(nativeToolInvoker, eventBus2);
|
|
5093
5477
|
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) {
|
|
5478
|
+
const apiKey = process.env.NOMAD_API_KEY;
|
|
5479
|
+
const baseUrl = process.env.NOMAD_BASE_URL;
|
|
5480
|
+
this.model = process.env.MODEL_NOMAD || "";
|
|
5481
|
+
if (!apiKey) {
|
|
5101
5482
|
const platform = process.platform;
|
|
5102
|
-
const varList = missing.join(", ");
|
|
5103
5483
|
let guidance = "";
|
|
5104
5484
|
if (platform === "win32") {
|
|
5105
5485
|
guidance = [
|
|
5106
5486
|
"Windows (PowerShell):",
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
"
|
|
5110
|
-
|
|
5111
|
-
"",
|
|
5112
|
-
"Windows (cmd.exe):",
|
|
5113
|
-
...missing.map((v) => ` setx ${v} "<value>"`)
|
|
5114
|
-
].join("");
|
|
5487
|
+
' $env:NOMAD_API_KEY="<your-key>"',
|
|
5488
|
+
" # Para persistir:",
|
|
5489
|
+
' [System.Environment]::SetEnvironmentVariable("NOMAD_API_KEY", "<your-key>", "User")'
|
|
5490
|
+
].join("\n");
|
|
5115
5491
|
} else if (platform === "darwin" || platform === "linux") {
|
|
5116
5492
|
guidance = [
|
|
5117
5493
|
"macOS/Linux (bash/zsh):",
|
|
5118
|
-
|
|
5119
|
-
" source ~/.bashrc
|
|
5120
|
-
].join("");
|
|
5494
|
+
` echo 'export NOMAD_API_KEY="<your-key>"' >> ~/.bashrc`,
|
|
5495
|
+
" source ~/.bashrc"
|
|
5496
|
+
].join("\n");
|
|
5121
5497
|
} else {
|
|
5122
|
-
guidance =
|
|
5123
|
-
"Generic POSIX:",
|
|
5124
|
-
...missing.map((v) => ` export ${v}="<value>"`)
|
|
5125
|
-
].join("");
|
|
5498
|
+
guidance = ' export NOMAD_API_KEY="<your-key>"';
|
|
5126
5499
|
}
|
|
5127
|
-
const
|
|
5128
|
-
|
|
5129
|
-
`Configure
|
|
5500
|
+
const message2 = [
|
|
5501
|
+
"Missing required environment variable: NOMAD_API_KEY.",
|
|
5502
|
+
`Configure it globally using the commands below, or set it in: ${globalEnvPath}`,
|
|
5130
5503
|
"",
|
|
5131
5504
|
guidance
|
|
5132
|
-
].join("");
|
|
5505
|
+
].join("\n");
|
|
5133
5506
|
this.eventBus.emit("backend_message", {
|
|
5134
5507
|
type: "error",
|
|
5135
5508
|
code: "missing_env",
|
|
5136
|
-
missing,
|
|
5509
|
+
missing: ["NOMAD_API_KEY", "MODEL_NOMAD"],
|
|
5137
5510
|
path: globalEnvPath,
|
|
5138
|
-
message
|
|
5511
|
+
message: message2
|
|
5139
5512
|
});
|
|
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
|
-
}
|
|
5513
|
+
throw new Error(message2);
|
|
5514
|
+
}
|
|
5515
|
+
this.llm = new LLMService({
|
|
5516
|
+
apiKey,
|
|
5517
|
+
baseUrl,
|
|
5518
|
+
model: this.model
|
|
5156
5519
|
});
|
|
5157
|
-
this.llm = new OpenAIAdapter(ollama);
|
|
5158
5520
|
this.core = new BluMaAgent(
|
|
5159
5521
|
this.sessionId,
|
|
5160
5522
|
this.eventBus,
|
|
5161
5523
|
this.llm,
|
|
5162
|
-
this.
|
|
5524
|
+
this.model,
|
|
5163
5525
|
this.mcpClient,
|
|
5164
5526
|
this.feedbackSystem
|
|
5165
5527
|
);
|
|
@@ -5168,9 +5530,10 @@ var Agent = class {
|
|
|
5168
5530
|
mcpClient: this.mcpClient,
|
|
5169
5531
|
toolInvoker: this.toolInvoker,
|
|
5170
5532
|
llm: this.llm,
|
|
5171
|
-
deploymentName: this.
|
|
5533
|
+
deploymentName: this.model,
|
|
5172
5534
|
projectRoot: process.cwd()
|
|
5173
5535
|
});
|
|
5536
|
+
this.routeManager = new RouteManager(this.subAgents, this.core);
|
|
5174
5537
|
}
|
|
5175
5538
|
async initialize() {
|
|
5176
5539
|
await this.core.initialize();
|
|
@@ -5184,10 +5547,9 @@ var Agent = class {
|
|
|
5184
5547
|
async processTurn(userInput) {
|
|
5185
5548
|
const inputText = String(userInput.content || "").trim();
|
|
5186
5549
|
if (inputText === "/init" || inputText.startsWith("/init ")) {
|
|
5187
|
-
|
|
5188
|
-
return;
|
|
5550
|
+
this.routeManager.registerRoute("/init", this.dispatchToSubAgent);
|
|
5189
5551
|
}
|
|
5190
|
-
await this.
|
|
5552
|
+
await this.routeManager.handleRoute({ content: inputText });
|
|
5191
5553
|
}
|
|
5192
5554
|
async handleToolResponse(decisionData) {
|
|
5193
5555
|
await this.core.handleToolResponse(decisionData);
|
|
@@ -5286,12 +5648,12 @@ var renderShellCommand2 = ({ args }) => {
|
|
|
5286
5648
|
};
|
|
5287
5649
|
var renderLsTool2 = ({ args }) => {
|
|
5288
5650
|
const parsed = parseArgs(args);
|
|
5289
|
-
const
|
|
5651
|
+
const path18 = parsed.directory_path || ".";
|
|
5290
5652
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5291
5653
|
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "ls" }),
|
|
5292
5654
|
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5293
5655
|
" ",
|
|
5294
|
-
|
|
5656
|
+
path18
|
|
5295
5657
|
] })
|
|
5296
5658
|
] });
|
|
5297
5659
|
};
|
|
@@ -5422,7 +5784,7 @@ var renderFindByName = ({ args }) => {
|
|
|
5422
5784
|
var renderGrepSearch = ({ args }) => {
|
|
5423
5785
|
const parsed = parseArgs(args);
|
|
5424
5786
|
const query = parsed.query || "";
|
|
5425
|
-
const
|
|
5787
|
+
const path18 = parsed.path || ".";
|
|
5426
5788
|
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5427
5789
|
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "grep" }),
|
|
5428
5790
|
/* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
|
|
@@ -5432,7 +5794,7 @@ var renderGrepSearch = ({ args }) => {
|
|
|
5432
5794
|
] }),
|
|
5433
5795
|
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5434
5796
|
" ",
|
|
5435
|
-
|
|
5797
|
+
path18
|
|
5436
5798
|
] })
|
|
5437
5799
|
] });
|
|
5438
5800
|
};
|
|
@@ -5500,6 +5862,17 @@ var renderSearchWeb = ({ args }) => {
|
|
|
5500
5862
|
] })
|
|
5501
5863
|
] });
|
|
5502
5864
|
};
|
|
5865
|
+
var renderLoadSkill = ({ args }) => {
|
|
5866
|
+
const parsed = parseArgs(args);
|
|
5867
|
+
const skillName = parsed.skill_name || "[no skill]";
|
|
5868
|
+
return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
|
|
5869
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "load skill" }),
|
|
5870
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
|
|
5871
|
+
" ",
|
|
5872
|
+
skillName
|
|
5873
|
+
] })
|
|
5874
|
+
] });
|
|
5875
|
+
};
|
|
5503
5876
|
var renderGeneric2 = ({ toolName, args }) => {
|
|
5504
5877
|
const parsed = parseArgs(args);
|
|
5505
5878
|
const keys = Object.keys(parsed).slice(0, 2);
|
|
@@ -5524,13 +5897,14 @@ var ToolRenderDisplay = {
|
|
|
5524
5897
|
view_file_outline: renderViewFileOutline,
|
|
5525
5898
|
command_status: renderCommandStatus,
|
|
5526
5899
|
task_boundary: renderTaskBoundary,
|
|
5527
|
-
search_web: renderSearchWeb
|
|
5900
|
+
search_web: renderSearchWeb,
|
|
5901
|
+
load_skill: renderLoadSkill
|
|
5528
5902
|
};
|
|
5529
5903
|
|
|
5530
5904
|
// src/app/ui/components/ToolCallDisplay.tsx
|
|
5531
5905
|
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
5532
5906
|
var ToolCallDisplayComponent = ({ toolName, args, preview }) => {
|
|
5533
|
-
if (toolName.includes("
|
|
5907
|
+
if (toolName.includes("message")) {
|
|
5534
5908
|
return null;
|
|
5535
5909
|
}
|
|
5536
5910
|
const Renderer = ToolRenderDisplay[toolName] || ((props2) => renderGeneric2({ ...props2, toolName }));
|
|
@@ -5731,11 +6105,11 @@ var truncateLines = (text, max) => {
|
|
|
5731
6105
|
};
|
|
5732
6106
|
};
|
|
5733
6107
|
var ToolResultDisplayComponent = ({ toolName, result }) => {
|
|
5734
|
-
if (toolName.includes("
|
|
6108
|
+
if (toolName.includes("task_boundary")) {
|
|
5735
6109
|
return null;
|
|
5736
6110
|
}
|
|
5737
6111
|
const parsed = parseResult(result);
|
|
5738
|
-
if (toolName.includes("
|
|
6112
|
+
if (toolName.includes("message")) {
|
|
5739
6113
|
const body = parsed?.content?.body || parsed?.body || parsed?.message;
|
|
5740
6114
|
if (!body) return null;
|
|
5741
6115
|
return /* @__PURE__ */ jsx11(Box11, { paddingX: 1, marginBottom: 1, flexDirection: "column", children: /* @__PURE__ */ jsx11(MarkdownRenderer, { markdown: body }) });
|
|
@@ -5818,6 +6192,23 @@ var ToolResultDisplayComponent = ({ toolName, result }) => {
|
|
|
5818
6192
|
] })
|
|
5819
6193
|
] });
|
|
5820
6194
|
}
|
|
6195
|
+
if (toolName.includes("load_skill") && parsed) {
|
|
6196
|
+
if (!parsed.success) {
|
|
6197
|
+
return /* @__PURE__ */ jsx11(Box11, { paddingLeft: 3, children: /* @__PURE__ */ jsxs10(Text10, { dimColor: true, color: "red", children: [
|
|
6198
|
+
"\u2717 Skill not found: ",
|
|
6199
|
+
parsed.message
|
|
6200
|
+
] }) });
|
|
6201
|
+
}
|
|
6202
|
+
return /* @__PURE__ */ jsxs10(Box11, { paddingLeft: 3, marginBottom: 2, children: [
|
|
6203
|
+
/* @__PURE__ */ jsx11(Text10, { dimColor: true, color: "green", children: "Skill loaded: " }),
|
|
6204
|
+
/* @__PURE__ */ jsx11(Text10, { color: "green", bold: true, children: parsed.skill_name }),
|
|
6205
|
+
/* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
|
|
6206
|
+
" \u2014 ",
|
|
6207
|
+
parsed.description?.slice(0, 50),
|
|
6208
|
+
parsed.description?.length > 100 ? "..." : ""
|
|
6209
|
+
] })
|
|
6210
|
+
] });
|
|
6211
|
+
}
|
|
5821
6212
|
if (toolName.includes("todo")) {
|
|
5822
6213
|
return null;
|
|
5823
6214
|
}
|
|
@@ -6019,16 +6410,16 @@ var SlashCommands_default = SlashCommands;
|
|
|
6019
6410
|
// src/app/agent/utils/update_check.ts
|
|
6020
6411
|
import updateNotifier from "update-notifier";
|
|
6021
6412
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6022
|
-
import
|
|
6023
|
-
import
|
|
6413
|
+
import path17 from "path";
|
|
6414
|
+
import fs13 from "fs";
|
|
6024
6415
|
var BLUMA_PACKAGE_NAME = "@nomad-e/bluma-cli";
|
|
6025
6416
|
function findBlumaPackageJson(startDir) {
|
|
6026
6417
|
let dir = startDir;
|
|
6027
6418
|
for (let i = 0; i < 10; i++) {
|
|
6028
|
-
const candidate =
|
|
6029
|
-
if (
|
|
6419
|
+
const candidate = path17.join(dir, "package.json");
|
|
6420
|
+
if (fs13.existsSync(candidate)) {
|
|
6030
6421
|
try {
|
|
6031
|
-
const raw =
|
|
6422
|
+
const raw = fs13.readFileSync(candidate, "utf8");
|
|
6032
6423
|
const parsed = JSON.parse(raw);
|
|
6033
6424
|
if (parsed?.name === BLUMA_PACKAGE_NAME && parsed?.version) {
|
|
6034
6425
|
return { name: parsed.name, version: parsed.version };
|
|
@@ -6036,7 +6427,7 @@ function findBlumaPackageJson(startDir) {
|
|
|
6036
6427
|
} catch {
|
|
6037
6428
|
}
|
|
6038
6429
|
}
|
|
6039
|
-
const parent =
|
|
6430
|
+
const parent = path17.dirname(dir);
|
|
6040
6431
|
if (parent === dir) break;
|
|
6041
6432
|
dir = parent;
|
|
6042
6433
|
}
|
|
@@ -6049,12 +6440,12 @@ async function checkForUpdates() {
|
|
|
6049
6440
|
}
|
|
6050
6441
|
const binPath = process.argv?.[1];
|
|
6051
6442
|
let pkg = null;
|
|
6052
|
-
if (binPath &&
|
|
6053
|
-
pkg = findBlumaPackageJson(
|
|
6443
|
+
if (binPath && fs13.existsSync(binPath)) {
|
|
6444
|
+
pkg = findBlumaPackageJson(path17.dirname(binPath));
|
|
6054
6445
|
}
|
|
6055
6446
|
if (!pkg) {
|
|
6056
6447
|
const __filename = fileURLToPath3(import.meta.url);
|
|
6057
|
-
const __dirname =
|
|
6448
|
+
const __dirname = path17.dirname(__filename);
|
|
6058
6449
|
pkg = findBlumaPackageJson(__dirname);
|
|
6059
6450
|
}
|
|
6060
6451
|
if (!pkg) {
|
|
@@ -6100,11 +6491,11 @@ function parseUpdateMessage(msg) {
|
|
|
6100
6491
|
hint: hintLine || void 0
|
|
6101
6492
|
};
|
|
6102
6493
|
}
|
|
6103
|
-
var UpdateNotice = ({ message }) => {
|
|
6104
|
-
const { name, current, latest, hint } = parseUpdateMessage(
|
|
6494
|
+
var UpdateNotice = ({ message: message2 }) => {
|
|
6495
|
+
const { name, current, latest, hint } = parseUpdateMessage(message2);
|
|
6105
6496
|
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", marginBottom: 1, children: [
|
|
6106
6497
|
/* @__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:
|
|
6498
|
+
name && current && latest ? /* @__PURE__ */ jsx13(Text12, { color: "gray", children: `${name}: ${current} \u2192 ${latest}` }) : /* @__PURE__ */ jsx13(Text12, { color: "gray", children: message2 }),
|
|
6108
6499
|
hint ? /* @__PURE__ */ jsx13(Text12, { color: "gray", children: hint }) : null
|
|
6109
6500
|
] });
|
|
6110
6501
|
};
|
|
@@ -6113,13 +6504,13 @@ var UpdateNotice_default = UpdateNotice;
|
|
|
6113
6504
|
// src/app/ui/components/ErrorMessage.tsx
|
|
6114
6505
|
import { Box as Box14, Text as Text13 } from "ink";
|
|
6115
6506
|
import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
6116
|
-
var ErrorMessage = ({ message, details, hint }) => {
|
|
6507
|
+
var ErrorMessage = ({ message: message2, details, hint }) => {
|
|
6117
6508
|
return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
6118
6509
|
/* @__PURE__ */ jsxs13(Box14, { children: [
|
|
6119
6510
|
/* @__PURE__ */ jsx14(Text13, { color: "red", children: "\u2717" }),
|
|
6120
6511
|
/* @__PURE__ */ jsx14(Text13, { color: "red", bold: true, children: " Error" })
|
|
6121
6512
|
] }),
|
|
6122
|
-
/* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsx14(Text13, { color: "red", children:
|
|
6513
|
+
/* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsx14(Text13, { color: "red", children: message2 }) }),
|
|
6123
6514
|
details && /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: details }) }),
|
|
6124
6515
|
hint && /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
|
|
6125
6516
|
"hint: ",
|
|
@@ -6155,7 +6546,7 @@ var ReasoningDisplay = memo9(ReasoningDisplayComponent);
|
|
|
6155
6546
|
import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
6156
6547
|
var SAFE_AUTO_APPROVE_TOOLS = [
|
|
6157
6548
|
// Comunicação/UI
|
|
6158
|
-
"
|
|
6549
|
+
"message",
|
|
6159
6550
|
"agent_end_turn",
|
|
6160
6551
|
"todo",
|
|
6161
6552
|
"task_boundary",
|