@nomad-e/bluma-cli 0.6.8 → 0.6.9
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 +0 -1
- package/dist/main.js +1716 -1242
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -12575,7 +12575,7 @@ var getInstance = (stdout, createInstance) => {
|
|
|
12575
12575
|
|
|
12576
12576
|
// src/main.ts
|
|
12577
12577
|
import { EventEmitter as EventEmitter7 } from "events";
|
|
12578
|
-
import
|
|
12578
|
+
import fs46 from "fs";
|
|
12579
12579
|
import path49 from "path";
|
|
12580
12580
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
12581
12581
|
import { spawn as spawn6 } from "child_process";
|
|
@@ -12638,9 +12638,9 @@ var BLUMA_TERMINAL = {
|
|
|
12638
12638
|
// red-500
|
|
12639
12639
|
warn: "#eab308",
|
|
12640
12640
|
// yellow-500
|
|
12641
|
-
diffAdded: "#
|
|
12641
|
+
diffAdded: "#203a2b",
|
|
12642
12642
|
// green-900
|
|
12643
|
-
diffRemoved: "#
|
|
12643
|
+
diffRemoved: "#4a221d",
|
|
12644
12644
|
// red-900
|
|
12645
12645
|
diffAddedWord: "#22c55e",
|
|
12646
12646
|
diffRemovedWord: "#ef4444",
|
|
@@ -12686,14 +12686,18 @@ var HeaderComponent = ({ workdir, cliVersion = "?" }) => /* @__PURE__ */ jsxs2(
|
|
|
12686
12686
|
Box_default,
|
|
12687
12687
|
{
|
|
12688
12688
|
flexDirection: "column",
|
|
12689
|
-
|
|
12689
|
+
marginBottom: 1,
|
|
12690
|
+
width: "100%",
|
|
12690
12691
|
children: [
|
|
12691
12692
|
/* @__PURE__ */ jsxs2(Box_default, { flexDirection: "row", flexWrap: "wrap", children: [
|
|
12692
|
-
/* @__PURE__ */
|
|
12693
|
+
/* @__PURE__ */ jsxs2(Text, { color: BLUMA_TERMINAL.dim, children: [
|
|
12694
|
+
"$_",
|
|
12695
|
+
" "
|
|
12696
|
+
] }),
|
|
12693
12697
|
/* @__PURE__ */ jsx9(Text, { color: BLUMA_TERMINAL.m3OnSurface, bold: true, children: "Blu" }),
|
|
12694
12698
|
/* @__PURE__ */ jsx9(Text, { color: BLUMA_TERMINAL.m3OnSurface, bold: true, children: "Ma" })
|
|
12695
12699
|
] }),
|
|
12696
|
-
/* @__PURE__ */ jsx9(Box_default, { flexDirection: "row", flexWrap: "wrap",
|
|
12700
|
+
/* @__PURE__ */ jsx9(Box_default, { flexDirection: "row", flexWrap: "wrap", width: "100%", children: /* @__PURE__ */ jsx9(Text, { color: BLUMA_TERMINAL.dim, children: "Base Language Unit \xB7 Model Agent" }) })
|
|
12697
12701
|
]
|
|
12698
12702
|
}
|
|
12699
12703
|
);
|
|
@@ -12701,7 +12705,7 @@ var Header = memo(HeaderComponent);
|
|
|
12701
12705
|
var SessionInfoComponent = ({
|
|
12702
12706
|
toolsCount,
|
|
12703
12707
|
mcpStatus
|
|
12704
|
-
}) => /* @__PURE__ */ jsxs2(Box_default, {
|
|
12708
|
+
}) => /* @__PURE__ */ jsxs2(Box_default, { children: [
|
|
12705
12709
|
/* @__PURE__ */ jsx9(Text, { dimColor: true, children: "mcp " }),
|
|
12706
12710
|
/* @__PURE__ */ jsx9(Text, { color: mcpStatus === "connected" ? BLUMA_TERMINAL.success : BLUMA_TERMINAL.warn, children: mcpStatus === "connected" ? "on" : "\u2026" }),
|
|
12707
12711
|
toolsCount !== null ? /* @__PURE__ */ jsxs2(Text, { dimColor: true, children: [
|
|
@@ -12721,7 +12725,7 @@ var TaskStatusBarComponent = ({
|
|
|
12721
12725
|
EXECUTION: BLUMA_TERMINAL.magenta,
|
|
12722
12726
|
VERIFICATION: BLUMA_TERMINAL.orange
|
|
12723
12727
|
};
|
|
12724
|
-
return /* @__PURE__ */ jsxs2(Box_default, {
|
|
12728
|
+
return /* @__PURE__ */ jsxs2(Box_default, { children: [
|
|
12725
12729
|
/* @__PURE__ */ jsxs2(Text, { color: modeColors[mode], bold: true, children: [
|
|
12726
12730
|
"[",
|
|
12727
12731
|
mode.charAt(0),
|
|
@@ -17828,9 +17832,6 @@ async function exitPlanMode(args) {
|
|
|
17828
17832
|
// src/app/agent/tools/TaskToolsTool/TaskToolsTool.ts
|
|
17829
17833
|
var bySession = /* @__PURE__ */ new Map();
|
|
17830
17834
|
var currentSessionId = "";
|
|
17831
|
-
function setTaskToolSessionContext(sessionId) {
|
|
17832
|
-
currentSessionId = String(sessionId || "").trim() || "default";
|
|
17833
|
-
}
|
|
17834
17835
|
function sessionMap() {
|
|
17835
17836
|
const sid2 = currentSessionId || "default";
|
|
17836
17837
|
let m = bySession.get(sid2);
|
|
@@ -18366,6 +18367,14 @@ async function fileWrite(args) {
|
|
|
18366
18367
|
error: `File already exists and overwrite=false: ${resolvedPath}`
|
|
18367
18368
|
};
|
|
18368
18369
|
}
|
|
18370
|
+
let previousContent = "";
|
|
18371
|
+
if (existed) {
|
|
18372
|
+
try {
|
|
18373
|
+
previousContent = await fs17.readFile(resolvedPath, "utf-8");
|
|
18374
|
+
} catch {
|
|
18375
|
+
previousContent = "";
|
|
18376
|
+
}
|
|
18377
|
+
}
|
|
18369
18378
|
if (create_directories) {
|
|
18370
18379
|
await fs17.mkdir(dir, { recursive: true });
|
|
18371
18380
|
}
|
|
@@ -18374,7 +18383,9 @@ async function fileWrite(args) {
|
|
|
18374
18383
|
return {
|
|
18375
18384
|
success: true,
|
|
18376
18385
|
filepath: resolvedPath,
|
|
18386
|
+
content: String(content),
|
|
18377
18387
|
bytes_written: bytes,
|
|
18388
|
+
previous_content: existed ? previousContent : "",
|
|
18378
18389
|
created: !existed,
|
|
18379
18390
|
overwritten: existed
|
|
18380
18391
|
};
|
|
@@ -24540,8 +24551,7 @@ Rules:
|
|
|
24540
24551
|
],
|
|
24541
24552
|
temperature: 0,
|
|
24542
24553
|
max_tokens: 4096,
|
|
24543
|
-
userContext
|
|
24544
|
-
response_format: { type: "json_object" }
|
|
24554
|
+
userContext
|
|
24545
24555
|
};
|
|
24546
24556
|
const resp = await llmService.chatCompletion(requestBase);
|
|
24547
24557
|
const text = resp.choices[0]?.message?.content;
|
|
@@ -24836,6 +24846,39 @@ function sanitizeConversationForProvider(conversationHistory) {
|
|
|
24836
24846
|
droppingCorruptTurn = true;
|
|
24837
24847
|
continue;
|
|
24838
24848
|
}
|
|
24849
|
+
const expectedToolIds = new Set(
|
|
24850
|
+
toolCalls.map((call) => String(call?.id ?? "").trim()).filter(Boolean)
|
|
24851
|
+
);
|
|
24852
|
+
const turnBuffer = [conversationHistory[index]];
|
|
24853
|
+
const seenToolIds = /* @__PURE__ */ new Set();
|
|
24854
|
+
let j = index + 1;
|
|
24855
|
+
let turnValid = true;
|
|
24856
|
+
while (j < conversationHistory.length) {
|
|
24857
|
+
const next = conversationHistory[j];
|
|
24858
|
+
if (next?.role !== "tool") {
|
|
24859
|
+
break;
|
|
24860
|
+
}
|
|
24861
|
+
const toolCallId = String(next?.tool_call_id ?? "").trim();
|
|
24862
|
+
if (!toolCallId || !expectedToolIds.has(toolCallId) || seenToolIds.has(toolCallId)) {
|
|
24863
|
+
issues.push({
|
|
24864
|
+
index,
|
|
24865
|
+
reason: "assistant tool_calls were not followed by a valid tool turn",
|
|
24866
|
+
toolNames: toolCalls.map((call) => String(call?.function?.name ?? "unknown"))
|
|
24867
|
+
});
|
|
24868
|
+
droppingCorruptTurn = true;
|
|
24869
|
+
turnValid = false;
|
|
24870
|
+
break;
|
|
24871
|
+
}
|
|
24872
|
+
seenToolIds.add(toolCallId);
|
|
24873
|
+
turnBuffer.push(conversationHistory[j]);
|
|
24874
|
+
j += 1;
|
|
24875
|
+
}
|
|
24876
|
+
if (turnValid) {
|
|
24877
|
+
cleaned.push(...turnBuffer);
|
|
24878
|
+
index = j - 1;
|
|
24879
|
+
continue;
|
|
24880
|
+
}
|
|
24881
|
+
continue;
|
|
24839
24882
|
}
|
|
24840
24883
|
cleaned.push(conversationHistory[index]);
|
|
24841
24884
|
}
|
|
@@ -24858,50 +24901,9 @@ function partitionConversationIntoTurnSlices(conversationHistory) {
|
|
|
24858
24901
|
}
|
|
24859
24902
|
return turns;
|
|
24860
24903
|
}
|
|
24861
|
-
var
|
|
24862
|
-
|
|
24863
|
-
|
|
24864
|
-
"gpt-4o-2024-08-06": 128e3,
|
|
24865
|
-
"gpt-4-turbo": 128e3,
|
|
24866
|
-
"gpt-4-0125-preview": 128e3,
|
|
24867
|
-
"gpt-4-1106-preview": 128e3,
|
|
24868
|
-
"claude-3-5-sonnet": 2e5,
|
|
24869
|
-
"claude-3-opus": 2e5,
|
|
24870
|
-
"claude-3-sonnet": 2e5,
|
|
24871
|
-
"claude-3-haiku": 2e5,
|
|
24872
|
-
"claude-sonnet-4-6-1m": 1e6,
|
|
24873
|
-
"claude-opus-4-6-1m": 1e6
|
|
24874
|
-
};
|
|
24875
|
-
function getContextWindowForModel(modelName = "") {
|
|
24876
|
-
if (!modelName) {
|
|
24877
|
-
return MODEL_CONTEXT_WINDOW_DEFAULT;
|
|
24878
|
-
}
|
|
24879
|
-
const normalizedModel = modelName.toLowerCase();
|
|
24880
|
-
const envOverride = process.env.BLUMA_CONTEXT_WINDOW_OVERRIDE;
|
|
24881
|
-
if (envOverride) {
|
|
24882
|
-
const override = parseInt(envOverride, 10);
|
|
24883
|
-
if (!isNaN(override) && override > 0) {
|
|
24884
|
-
return override;
|
|
24885
|
-
}
|
|
24886
|
-
}
|
|
24887
|
-
for (const [modelPattern, contextWindow] of Object.entries(MODEL_CONTEXT_WINDOWS)) {
|
|
24888
|
-
if (normalizedModel.includes(modelPattern)) {
|
|
24889
|
-
return contextWindow;
|
|
24890
|
-
}
|
|
24891
|
-
}
|
|
24892
|
-
if (normalizedModel.includes("[1m]") || normalizedModel.includes("1m")) {
|
|
24893
|
-
return 1e6;
|
|
24894
|
-
}
|
|
24895
|
-
return MODEL_CONTEXT_WINDOW_DEFAULT;
|
|
24896
|
-
}
|
|
24897
|
-
function getContextInputBudgetForModel(modelName = "") {
|
|
24898
|
-
const contextWindow = getContextWindowForModel(modelName);
|
|
24899
|
-
const outputReserve = Math.max(8e3, Math.floor(contextWindow * 0.1));
|
|
24900
|
-
const effectiveContextPercent = 0.95;
|
|
24901
|
-
const inputBudget = Math.floor(
|
|
24902
|
-
(contextWindow - outputReserve) * effectiveContextPercent
|
|
24903
|
-
);
|
|
24904
|
-
return Math.max(inputBudget, 32e3);
|
|
24904
|
+
var STATIC_CONTEXT_INPUT_BUDGET = 96e3;
|
|
24905
|
+
function getContextInputBudgetForModel(_modelName = "") {
|
|
24906
|
+
return STATIC_CONTEXT_INPUT_BUDGET;
|
|
24905
24907
|
}
|
|
24906
24908
|
|
|
24907
24909
|
// src/app/agent/core/llm/llm.ts
|
|
@@ -24932,6 +24934,8 @@ function extractStreamingDelta(previous, next) {
|
|
|
24932
24934
|
}
|
|
24933
24935
|
|
|
24934
24936
|
// src/app/agent/core/llm/llm.ts
|
|
24937
|
+
init_logger();
|
|
24938
|
+
var llmLog = logger.child("llm");
|
|
24935
24939
|
function defaultBlumaUserContextInput(sessionId, userMessage) {
|
|
24936
24940
|
const msg = String(userMessage || "").slice(0, 300);
|
|
24937
24941
|
return {
|
|
@@ -25009,6 +25013,7 @@ var THINKING_TOKEN_BUDGET_BY_EFFORT = {
|
|
|
25009
25013
|
medium: 1024,
|
|
25010
25014
|
high: 2048
|
|
25011
25015
|
};
|
|
25016
|
+
var DEFAULT_REQUEST_MAX_TOKENS = 8192;
|
|
25012
25017
|
function getThinkingTokenBudgetForEffort(effort) {
|
|
25013
25018
|
if (!effort) return void 0;
|
|
25014
25019
|
return THINKING_TOKEN_BUDGET_BY_EFFORT[effort];
|
|
@@ -25022,6 +25027,13 @@ function buildVllmReasoningPayload(effort) {
|
|
|
25022
25027
|
}
|
|
25023
25028
|
};
|
|
25024
25029
|
}
|
|
25030
|
+
function resolveRequestMaxTokens(params) {
|
|
25031
|
+
const requested = params.max_tokens;
|
|
25032
|
+
if (typeof requested === "number" && Number.isFinite(requested) && requested > 0) {
|
|
25033
|
+
return Math.floor(requested);
|
|
25034
|
+
}
|
|
25035
|
+
return DEFAULT_REQUEST_MAX_TOKENS;
|
|
25036
|
+
}
|
|
25025
25037
|
function resolveReasoningEffortForRequest(params, runtimeConfig = getRuntimeConfig()) {
|
|
25026
25038
|
const policy = getSandboxPolicy();
|
|
25027
25039
|
if (policy.isSandbox) {
|
|
@@ -25029,24 +25041,188 @@ function resolveReasoningEffortForRequest(params, runtimeConfig = getRuntimeConf
|
|
|
25029
25041
|
}
|
|
25030
25042
|
return params.reasoning?.effort ?? runtimeConfig.reasoningEffort;
|
|
25031
25043
|
}
|
|
25032
|
-
function buildChatCompletionRequestBody(params, runtimeConfig = getRuntimeConfig(), stream = false) {
|
|
25044
|
+
function buildChatCompletionRequestBody(params, runtimeConfig = getRuntimeConfig(), stream = false, options) {
|
|
25033
25045
|
const tools = params.tools;
|
|
25034
25046
|
const hasTools = Array.isArray(tools) && tools.length > 0;
|
|
25035
25047
|
const effort = resolveReasoningEffortForRequest(params, runtimeConfig);
|
|
25036
|
-
const reasoningPayload = buildVllmReasoningPayload(effort);
|
|
25048
|
+
const reasoningPayload = options?.disableReasoning ? void 0 : buildVllmReasoningPayload(effort);
|
|
25049
|
+
const messages = sanitizeMessagesForRequest(params.messages, options);
|
|
25037
25050
|
return {
|
|
25038
25051
|
model: params.model || runtimeConfig.model || "auto",
|
|
25039
|
-
messages
|
|
25040
|
-
tools: hasTools ? tools : void 0,
|
|
25041
|
-
tool_choice: hasTools ? "auto" : void 0,
|
|
25042
|
-
parallel_tool_calls: params.parallel_tool_calls ?? false,
|
|
25052
|
+
messages,
|
|
25053
|
+
tools: hasTools && !options?.disableTools ? tools : void 0,
|
|
25054
|
+
tool_choice: hasTools && !options?.disableTools ? "auto" : void 0,
|
|
25055
|
+
parallel_tool_calls: options?.disableParallelToolCalls ? void 0 : params.parallel_tool_calls ?? false,
|
|
25043
25056
|
temperature: params.temperature ?? 0,
|
|
25044
25057
|
...reasoningPayload ?? {},
|
|
25045
|
-
max_tokens: params
|
|
25058
|
+
max_tokens: resolveRequestMaxTokens(params),
|
|
25046
25059
|
response_format: params.response_format,
|
|
25047
25060
|
...stream ? { stream: true } : {}
|
|
25048
25061
|
};
|
|
25049
25062
|
}
|
|
25063
|
+
function isBadRequestLike(error) {
|
|
25064
|
+
const anyError = error;
|
|
25065
|
+
if (anyError && typeof anyError.status === "number" && anyError.status === 400) {
|
|
25066
|
+
return true;
|
|
25067
|
+
}
|
|
25068
|
+
const message2 = String(anyError?.message ?? error ?? "").toLowerCase();
|
|
25069
|
+
return message2.includes("400 status code") || message2.includes("bad request");
|
|
25070
|
+
}
|
|
25071
|
+
function describeFallback(options) {
|
|
25072
|
+
const parts = [];
|
|
25073
|
+
if (options.disableReasoning) parts.push("no_reasoning");
|
|
25074
|
+
if (options.disableParallelToolCalls) parts.push("no_parallel");
|
|
25075
|
+
if (options.disableTools) parts.push("no_tools");
|
|
25076
|
+
if (options.stripToolTurns) parts.push("no_tool_turns");
|
|
25077
|
+
if (options.stripImages) parts.push("no_images");
|
|
25078
|
+
return parts.length > 0 ? parts.join("+") : "base";
|
|
25079
|
+
}
|
|
25080
|
+
function summarizeError(error) {
|
|
25081
|
+
if (!error || typeof error !== "object") {
|
|
25082
|
+
return { message: String(error) };
|
|
25083
|
+
}
|
|
25084
|
+
const anyError = error;
|
|
25085
|
+
return {
|
|
25086
|
+
name: typeof anyError.name === "string" ? anyError.name : void 0,
|
|
25087
|
+
message: typeof anyError.message === "string" ? anyError.message : String(error),
|
|
25088
|
+
status: typeof anyError.status === "number" ? anyError.status : void 0,
|
|
25089
|
+
code: typeof anyError.code === "string" || typeof anyError.code === "number" ? anyError.code : void 0,
|
|
25090
|
+
type: typeof anyError.type === "string" ? anyError.type : void 0
|
|
25091
|
+
};
|
|
25092
|
+
}
|
|
25093
|
+
function summarizeRequestForLog(params, runtimeConfig = getRuntimeConfig()) {
|
|
25094
|
+
const messageCounts = {
|
|
25095
|
+
total: params.messages.length,
|
|
25096
|
+
system: 0,
|
|
25097
|
+
user: 0,
|
|
25098
|
+
assistant: 0,
|
|
25099
|
+
tool: 0,
|
|
25100
|
+
function: 0
|
|
25101
|
+
};
|
|
25102
|
+
let toolCallMessages = 0;
|
|
25103
|
+
let imageMessages = 0;
|
|
25104
|
+
for (const message2 of params.messages) {
|
|
25105
|
+
if (message2.role in messageCounts) {
|
|
25106
|
+
messageCounts[message2.role] += 1;
|
|
25107
|
+
}
|
|
25108
|
+
if (message2.role === "assistant" && Array.isArray(message2.tool_calls) && message2.tool_calls.length > 0) {
|
|
25109
|
+
toolCallMessages += 1;
|
|
25110
|
+
}
|
|
25111
|
+
if (Array.isArray(message2.content)) {
|
|
25112
|
+
imageMessages += 1;
|
|
25113
|
+
}
|
|
25114
|
+
}
|
|
25115
|
+
const messagePreview = params.messages.slice(0, 8).map((message2) => {
|
|
25116
|
+
const content = message2.content;
|
|
25117
|
+
const toolCalls = Array.isArray(message2.tool_calls) ? message2.tool_calls : [];
|
|
25118
|
+
const preview = typeof content === "string" ? content.slice(0, 120) : Array.isArray(content) ? content.filter((part) => part && typeof part === "object" && part.type === "text" && typeof part.text === "string").map((part) => part.text).join(" ").slice(0, 120) : null;
|
|
25119
|
+
return {
|
|
25120
|
+
role: message2.role,
|
|
25121
|
+
name: message2.name ?? null,
|
|
25122
|
+
content_type: Array.isArray(content) ? "array" : typeof content,
|
|
25123
|
+
preview: preview || null,
|
|
25124
|
+
tool_calls: toolCalls.length,
|
|
25125
|
+
tool_call_id: message2.tool_call_id ?? null
|
|
25126
|
+
};
|
|
25127
|
+
});
|
|
25128
|
+
return {
|
|
25129
|
+
model: params.model || runtimeConfig.model || "auto",
|
|
25130
|
+
temperature: params.temperature ?? 0,
|
|
25131
|
+
max_tokens: resolveRequestMaxTokens(params),
|
|
25132
|
+
parallel_tool_calls: params.parallel_tool_calls ?? false,
|
|
25133
|
+
has_tools: Boolean(params.tools?.length),
|
|
25134
|
+
tool_count: params.tools?.length ?? 0,
|
|
25135
|
+
message_counts: messageCounts,
|
|
25136
|
+
assistant_tool_call_messages: toolCallMessages,
|
|
25137
|
+
multimodal_messages: imageMessages,
|
|
25138
|
+
reasoning: params.reasoning?.effort ?? runtimeConfig.reasoningEffort,
|
|
25139
|
+
message_preview: messagePreview
|
|
25140
|
+
};
|
|
25141
|
+
}
|
|
25142
|
+
function logLLMRetry(stage, params, fallbackIndex, totalFallbacks, error, stream) {
|
|
25143
|
+
llmLog.warn("LLM request fallback failed", {
|
|
25144
|
+
stage,
|
|
25145
|
+
fallback: `${fallbackIndex + 1}/${totalFallbacks}`,
|
|
25146
|
+
stream,
|
|
25147
|
+
request: summarizeRequestForLog(params),
|
|
25148
|
+
error: summarizeError(error)
|
|
25149
|
+
});
|
|
25150
|
+
}
|
|
25151
|
+
function getRequestFallbacks() {
|
|
25152
|
+
return [
|
|
25153
|
+
{},
|
|
25154
|
+
{ disableReasoning: true },
|
|
25155
|
+
{ disableReasoning: true, disableParallelToolCalls: true },
|
|
25156
|
+
{ disableReasoning: true, disableParallelToolCalls: true, disableTools: true },
|
|
25157
|
+
{
|
|
25158
|
+
disableReasoning: true,
|
|
25159
|
+
disableParallelToolCalls: true,
|
|
25160
|
+
disableTools: true,
|
|
25161
|
+
stripToolTurns: true,
|
|
25162
|
+
stripImages: true
|
|
25163
|
+
}
|
|
25164
|
+
];
|
|
25165
|
+
}
|
|
25166
|
+
function sanitizeTextOnlyMessageContent(content) {
|
|
25167
|
+
if (typeof content === "string") {
|
|
25168
|
+
const trimmed = content.trim();
|
|
25169
|
+
return trimmed || void 0;
|
|
25170
|
+
}
|
|
25171
|
+
if (!Array.isArray(content)) {
|
|
25172
|
+
return void 0;
|
|
25173
|
+
}
|
|
25174
|
+
const parts = [];
|
|
25175
|
+
for (const part of content) {
|
|
25176
|
+
if (!part || typeof part !== "object") continue;
|
|
25177
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
25178
|
+
const text = part.text.trim();
|
|
25179
|
+
if (text) parts.push(text);
|
|
25180
|
+
}
|
|
25181
|
+
}
|
|
25182
|
+
const joined = parts.join("\n").trim();
|
|
25183
|
+
return joined || void 0;
|
|
25184
|
+
}
|
|
25185
|
+
function sanitizeMessagesForRequest(messages, options) {
|
|
25186
|
+
if (!options?.stripToolTurns && !options?.stripImages) {
|
|
25187
|
+
return messages;
|
|
25188
|
+
}
|
|
25189
|
+
const out = [];
|
|
25190
|
+
for (const msg of messages) {
|
|
25191
|
+
if (options.stripToolTurns && (msg.role === "tool" || msg.role === "function")) {
|
|
25192
|
+
continue;
|
|
25193
|
+
}
|
|
25194
|
+
if (options.stripToolTurns && msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
|
|
25195
|
+
const textOnly = sanitizeTextOnlyMessageContent(msg.content);
|
|
25196
|
+
if (!textOnly) {
|
|
25197
|
+
continue;
|
|
25198
|
+
}
|
|
25199
|
+
out.push({
|
|
25200
|
+
role: "assistant",
|
|
25201
|
+
content: textOnly
|
|
25202
|
+
});
|
|
25203
|
+
continue;
|
|
25204
|
+
}
|
|
25205
|
+
if (options.stripImages && msg.role === "user" && Array.isArray(msg.content)) {
|
|
25206
|
+
const textOnly = sanitizeTextOnlyMessageContent(msg.content);
|
|
25207
|
+
out.push({
|
|
25208
|
+
...msg,
|
|
25209
|
+
content: textOnly || "(image omitted)"
|
|
25210
|
+
});
|
|
25211
|
+
continue;
|
|
25212
|
+
}
|
|
25213
|
+
if (options.stripImages && msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
25214
|
+
const textOnly = sanitizeTextOnlyMessageContent(msg.content);
|
|
25215
|
+
if (!textOnly) continue;
|
|
25216
|
+
out.push({
|
|
25217
|
+
...msg,
|
|
25218
|
+
content: textOnly
|
|
25219
|
+
});
|
|
25220
|
+
continue;
|
|
25221
|
+
}
|
|
25222
|
+
out.push(msg);
|
|
25223
|
+
}
|
|
25224
|
+
return out;
|
|
25225
|
+
}
|
|
25050
25226
|
function applyDeltaToolCallsToAccumulator(toolCallsAccumulator, deltaToolCalls) {
|
|
25051
25227
|
if (!deltaToolCalls?.length) {
|
|
25052
25228
|
return false;
|
|
@@ -25128,11 +25304,43 @@ var LLMService = class {
|
|
|
25128
25304
|
throw new Error("LLMService.chatCompletion: userContext \xE9 obrigat\xF3rio");
|
|
25129
25305
|
}
|
|
25130
25306
|
const runtimeConfig = getRuntimeConfig();
|
|
25131
|
-
const
|
|
25132
|
-
|
|
25133
|
-
|
|
25307
|
+
const requestHeaders = this.requestHeaders(params.userContext);
|
|
25308
|
+
let lastError = null;
|
|
25309
|
+
const fallbacks = getRequestFallbacks();
|
|
25310
|
+
for (let i = 0; i < fallbacks.length; i += 1) {
|
|
25311
|
+
const fallback = fallbacks[i];
|
|
25312
|
+
const stage = describeFallback(fallback);
|
|
25313
|
+
try {
|
|
25314
|
+
llmLog.debug("LLM request attempt", {
|
|
25315
|
+
stream: false,
|
|
25316
|
+
stage,
|
|
25317
|
+
fallback: `${i + 1}/${fallbacks.length}`,
|
|
25318
|
+
request: summarizeRequestForLog(params, runtimeConfig)
|
|
25319
|
+
});
|
|
25320
|
+
const resp = await this.client.chat.completions.create(
|
|
25321
|
+
buildChatCompletionRequestBody(params, runtimeConfig, false, fallback),
|
|
25322
|
+
{ headers: requestHeaders }
|
|
25323
|
+
);
|
|
25324
|
+
return resp;
|
|
25325
|
+
} catch (error) {
|
|
25326
|
+
lastError = error;
|
|
25327
|
+
logLLMRetry(stage, params, i, fallbacks.length, error, false);
|
|
25328
|
+
if (!isBadRequestLike(error)) {
|
|
25329
|
+
throw error;
|
|
25330
|
+
}
|
|
25331
|
+
}
|
|
25332
|
+
}
|
|
25333
|
+
const lastStage = describeFallback(fallbacks[fallbacks.length - 1] ?? {});
|
|
25334
|
+
llmLog.error("LLM request failed after retries", {
|
|
25335
|
+
stream: false,
|
|
25336
|
+
last_stage: lastStage,
|
|
25337
|
+
attempts: fallbacks.length,
|
|
25338
|
+
request: summarizeRequestForLog(params, runtimeConfig),
|
|
25339
|
+
error: summarizeError(lastError)
|
|
25340
|
+
});
|
|
25341
|
+
throw new Error(
|
|
25342
|
+
`LLM request failed after ${fallbacks.length} attempts [last_stage=${lastStage}]: ${lastError instanceof Error ? lastError.message : String(lastError ?? "unknown error")}`
|
|
25134
25343
|
);
|
|
25135
|
-
return resp;
|
|
25136
25344
|
}
|
|
25137
25345
|
/**
|
|
25138
25346
|
* Streaming — mesmo turnId em todas as chamadas do loop de tools (via params.userContext).
|
|
@@ -25142,10 +25350,46 @@ var LLMService = class {
|
|
|
25142
25350
|
throw new Error("LLMService.chatCompletionStream: userContext \xE9 obrigat\xF3rio");
|
|
25143
25351
|
}
|
|
25144
25352
|
const runtimeConfig = getRuntimeConfig();
|
|
25145
|
-
const
|
|
25146
|
-
|
|
25147
|
-
|
|
25148
|
-
);
|
|
25353
|
+
const requestHeaders = this.requestHeaders(params.userContext);
|
|
25354
|
+
let stream = null;
|
|
25355
|
+
let lastError = null;
|
|
25356
|
+
const fallbacks = getRequestFallbacks();
|
|
25357
|
+
for (let i = 0; i < fallbacks.length; i += 1) {
|
|
25358
|
+
const fallback = fallbacks[i];
|
|
25359
|
+
const stage = describeFallback(fallback);
|
|
25360
|
+
try {
|
|
25361
|
+
llmLog.debug("LLM stream request attempt", {
|
|
25362
|
+
stream: true,
|
|
25363
|
+
stage,
|
|
25364
|
+
fallback: `${i + 1}/${fallbacks.length}`,
|
|
25365
|
+
request: summarizeRequestForLog(params, runtimeConfig)
|
|
25366
|
+
});
|
|
25367
|
+
stream = await this.client.chat.completions.create(
|
|
25368
|
+
buildChatCompletionRequestBody(params, runtimeConfig, true, fallback),
|
|
25369
|
+
{ headers: requestHeaders }
|
|
25370
|
+
);
|
|
25371
|
+
break;
|
|
25372
|
+
} catch (error) {
|
|
25373
|
+
lastError = error;
|
|
25374
|
+
logLLMRetry(stage, params, i, fallbacks.length, error, true);
|
|
25375
|
+
if (!isBadRequestLike(error)) {
|
|
25376
|
+
throw error;
|
|
25377
|
+
}
|
|
25378
|
+
}
|
|
25379
|
+
}
|
|
25380
|
+
if (!stream) {
|
|
25381
|
+
const lastStage = describeFallback(fallbacks[fallbacks.length - 1] ?? {});
|
|
25382
|
+
llmLog.error("LLM stream request failed after retries", {
|
|
25383
|
+
stream: true,
|
|
25384
|
+
last_stage: lastStage,
|
|
25385
|
+
attempts: fallbacks.length,
|
|
25386
|
+
request: summarizeRequestForLog(params, runtimeConfig),
|
|
25387
|
+
error: summarizeError(lastError)
|
|
25388
|
+
});
|
|
25389
|
+
throw new Error(
|
|
25390
|
+
`LLM request failed after ${fallbacks.length} attempts [last_stage=${lastStage}]: ${lastError instanceof Error ? lastError.message : String(lastError ?? "unknown error")}`
|
|
25391
|
+
);
|
|
25392
|
+
}
|
|
25149
25393
|
const toolCallsAccumulator = /* @__PURE__ */ new Map();
|
|
25150
25394
|
let reasoningSnapshot = "";
|
|
25151
25395
|
for await (const chunk of stream) {
|
|
@@ -25184,231 +25428,37 @@ var LLMService = class {
|
|
|
25184
25428
|
}
|
|
25185
25429
|
};
|
|
25186
25430
|
|
|
25187
|
-
// src/app/agent/
|
|
25188
|
-
|
|
25189
|
-
|
|
25190
|
-
|
|
25191
|
-
|
|
25192
|
-
|
|
25193
|
-
|
|
25194
|
-
|
|
25195
|
-
|
|
25196
|
-
|
|
25197
|
-
|
|
25198
|
-
} else if (lower.includes("api")) {
|
|
25199
|
-
message2 = "Erro de comunica\xE7\xE3o com a API do modelo.";
|
|
25200
|
-
}
|
|
25201
|
-
return {
|
|
25202
|
-
message: message2,
|
|
25203
|
-
rawMessage
|
|
25204
|
-
};
|
|
25431
|
+
// src/app/agent/runtime/tool_execution_policy.ts
|
|
25432
|
+
init_sandbox_policy();
|
|
25433
|
+
init_runtime_config();
|
|
25434
|
+
init_permission_rules();
|
|
25435
|
+
import path38 from "path";
|
|
25436
|
+
var LOCAL_EDIT_TOOL_NAMES = /* @__PURE__ */ new Set(["edit_tool", "file_write", "notebook_edit"]);
|
|
25437
|
+
function getToolPermissionLayer(metadata) {
|
|
25438
|
+
if (metadata.riskLevel === "safe") return "read";
|
|
25439
|
+
if (metadata.riskLevel === "write") return "write";
|
|
25440
|
+
if (metadata.riskLevel === "network") return "network";
|
|
25441
|
+
return "execute";
|
|
25205
25442
|
}
|
|
25206
|
-
|
|
25207
|
-
|
|
25208
|
-
|
|
25209
|
-
|
|
25210
|
-
|
|
25211
|
-
|
|
25212
|
-
*/
|
|
25213
|
-
static assistantContentWithToolCalls(content) {
|
|
25214
|
-
if (content === void 0 || content === null) return null;
|
|
25215
|
-
if (typeof content === "string") return content.trim() === "" ? null : content;
|
|
25216
|
-
return null;
|
|
25443
|
+
function isLocalEditTool(toolName) {
|
|
25444
|
+
return LOCAL_EDIT_TOOL_NAMES.has(toolName);
|
|
25445
|
+
}
|
|
25446
|
+
function checkFilePermissionRules(toolName, filePath, policy) {
|
|
25447
|
+
if (!filePath) {
|
|
25448
|
+
return { allowed: false, reason: "No file path provided for permission check." };
|
|
25217
25449
|
}
|
|
25218
|
-
|
|
25219
|
-
|
|
25220
|
-
|
|
25221
|
-
static normalizeAssistantMessage(message2) {
|
|
25222
|
-
if (message2.tool_calls && this.isOpenAIFormat(message2.tool_calls)) {
|
|
25223
|
-
return {
|
|
25224
|
-
...message2,
|
|
25225
|
-
content: this.assistantContentWithToolCalls(message2.content)
|
|
25226
|
-
};
|
|
25227
|
-
}
|
|
25228
|
-
const toolCalls = this.extractToolCalls(message2);
|
|
25229
|
-
if (toolCalls.length > 0) {
|
|
25230
|
-
return {
|
|
25231
|
-
role: message2.role || "assistant",
|
|
25232
|
-
content: this.assistantContentWithToolCalls(message2.content),
|
|
25233
|
-
tool_calls: toolCalls
|
|
25234
|
-
};
|
|
25235
|
-
}
|
|
25236
|
-
return message2;
|
|
25450
|
+
const resolvedPath = path38.resolve(filePath);
|
|
25451
|
+
if (!isPathInsideWorkspace(resolvedPath, policy)) {
|
|
25452
|
+
return { allowed: false, reason: `File path "${filePath}" is outside workspace root.` };
|
|
25237
25453
|
}
|
|
25238
|
-
|
|
25239
|
-
|
|
25240
|
-
|
|
25241
|
-
|
|
25242
|
-
|
|
25243
|
-
const firstCall = toolCalls[0];
|
|
25244
|
-
return typeof firstCall.id === "string" && firstCall.type === "function" && typeof firstCall.function?.name === "string" && typeof firstCall.function?.arguments === "string";
|
|
25454
|
+
const relativePath = path38.relative(policy.workspaceRoot, resolvedPath);
|
|
25455
|
+
const toolPattern = `${toolName}(${relativePath})`;
|
|
25456
|
+
const ruleDecision = permissionRulesEngine.checkPermission(toolPattern, { filepath: filePath });
|
|
25457
|
+
if (ruleDecision === "deny") {
|
|
25458
|
+
return { allowed: false, reason: `File "${filePath}" denied by permission rules.` };
|
|
25245
25459
|
}
|
|
25246
|
-
|
|
25247
|
-
|
|
25248
|
-
*/
|
|
25249
|
-
static extractToolCalls(message2) {
|
|
25250
|
-
const results = [];
|
|
25251
|
-
if (message2.tool_calls && Array.isArray(message2.tool_calls)) {
|
|
25252
|
-
for (const call of message2.tool_calls) {
|
|
25253
|
-
const normalized = this.normalizeToolCall(call);
|
|
25254
|
-
if (normalized) results.push(normalized);
|
|
25255
|
-
}
|
|
25256
|
-
}
|
|
25257
|
-
if (typeof message2.content === "string" && message2.content.trim()) {
|
|
25258
|
-
const extracted = this.extractFromContent(message2.content);
|
|
25259
|
-
results.push(...extracted);
|
|
25260
|
-
}
|
|
25261
|
-
if (message2.function_call) {
|
|
25262
|
-
const normalized = this.normalizeToolCall(message2.function_call);
|
|
25263
|
-
if (normalized) results.push(normalized);
|
|
25264
|
-
}
|
|
25265
|
-
return results;
|
|
25266
|
-
}
|
|
25267
|
-
/**
|
|
25268
|
-
* Normaliza um único tool call para o formato OpenAI
|
|
25269
|
-
*/
|
|
25270
|
-
static normalizeToolCall(call) {
|
|
25271
|
-
try {
|
|
25272
|
-
if (call.id && call.function?.name) {
|
|
25273
|
-
return {
|
|
25274
|
-
id: call.id,
|
|
25275
|
-
type: "function",
|
|
25276
|
-
function: {
|
|
25277
|
-
name: call.function.name,
|
|
25278
|
-
arguments: typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments)
|
|
25279
|
-
}
|
|
25280
|
-
};
|
|
25281
|
-
}
|
|
25282
|
-
if (call.name) {
|
|
25283
|
-
return {
|
|
25284
|
-
id: call.id || randomUUID(),
|
|
25285
|
-
type: "function",
|
|
25286
|
-
function: {
|
|
25287
|
-
name: call.name,
|
|
25288
|
-
arguments: typeof call.arguments === "string" ? call.arguments : JSON.stringify(call.arguments || {})
|
|
25289
|
-
}
|
|
25290
|
-
};
|
|
25291
|
-
}
|
|
25292
|
-
if (call.function && typeof call.function === "object") {
|
|
25293
|
-
return {
|
|
25294
|
-
id: call.id || randomUUID(),
|
|
25295
|
-
type: "function",
|
|
25296
|
-
function: {
|
|
25297
|
-
name: call.function.name,
|
|
25298
|
-
arguments: typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments || {})
|
|
25299
|
-
}
|
|
25300
|
-
};
|
|
25301
|
-
}
|
|
25302
|
-
return null;
|
|
25303
|
-
} catch (error) {
|
|
25304
|
-
console.error("Error normalizing tool call:", error, call);
|
|
25305
|
-
return null;
|
|
25306
|
-
}
|
|
25307
|
-
}
|
|
25308
|
-
/**
|
|
25309
|
-
* Extrai tool calls do content (pode estar em markdown, JSON, etc)
|
|
25310
|
-
*/
|
|
25311
|
-
static extractFromContent(content) {
|
|
25312
|
-
const results = [];
|
|
25313
|
-
const cleanContent2 = content.replace(/```(?:json)?\s*([\s\S]*?)```/g, "$1");
|
|
25314
|
-
const jsonMatches = this.extractJsonObjects(cleanContent2);
|
|
25315
|
-
for (const jsonStr of jsonMatches) {
|
|
25316
|
-
try {
|
|
25317
|
-
const parsed = JSON.parse(jsonStr);
|
|
25318
|
-
if (Array.isArray(parsed)) {
|
|
25319
|
-
for (const call of parsed) {
|
|
25320
|
-
const normalized = this.normalizeToolCall(call);
|
|
25321
|
-
if (normalized) results.push(normalized);
|
|
25322
|
-
}
|
|
25323
|
-
} else if (parsed.name || parsed.function) {
|
|
25324
|
-
const normalized = this.normalizeToolCall(parsed);
|
|
25325
|
-
if (normalized) results.push(normalized);
|
|
25326
|
-
} else if (parsed.tool_calls && Array.isArray(parsed.tool_calls)) {
|
|
25327
|
-
for (const call of parsed.tool_calls) {
|
|
25328
|
-
const normalized = this.normalizeToolCall(call);
|
|
25329
|
-
if (normalized) results.push(normalized);
|
|
25330
|
-
}
|
|
25331
|
-
}
|
|
25332
|
-
} catch (e) {
|
|
25333
|
-
}
|
|
25334
|
-
}
|
|
25335
|
-
return results;
|
|
25336
|
-
}
|
|
25337
|
-
/**
|
|
25338
|
-
* Extrai objetos JSON de uma string (suporta múltiplos objetos)
|
|
25339
|
-
*/
|
|
25340
|
-
static extractJsonObjects(text) {
|
|
25341
|
-
const results = [];
|
|
25342
|
-
let depth = 0;
|
|
25343
|
-
let start = -1;
|
|
25344
|
-
for (let i = 0; i < text.length; i++) {
|
|
25345
|
-
if (text[i] === "{") {
|
|
25346
|
-
if (depth === 0) start = i;
|
|
25347
|
-
depth++;
|
|
25348
|
-
} else if (text[i] === "}") {
|
|
25349
|
-
depth--;
|
|
25350
|
-
if (depth === 0 && start !== -1) {
|
|
25351
|
-
results.push(text.substring(start, i + 1));
|
|
25352
|
-
start = -1;
|
|
25353
|
-
}
|
|
25354
|
-
}
|
|
25355
|
-
}
|
|
25356
|
-
return results;
|
|
25357
|
-
}
|
|
25358
|
-
/**
|
|
25359
|
-
* Valida se um tool call normalizado é válido
|
|
25360
|
-
* Validação dupla:
|
|
25361
|
-
* 1. JSON dos argumentos é válido
|
|
25362
|
-
* 2. A ferramenta existe no catálogo de ferramentas nativas
|
|
25363
|
-
*/
|
|
25364
|
-
static isValidToolCall(call) {
|
|
25365
|
-
if (!(call.id && call.type === "function" && call.function?.name && typeof call.function.arguments === "string")) {
|
|
25366
|
-
return false;
|
|
25367
|
-
}
|
|
25368
|
-
try {
|
|
25369
|
-
JSON.parse(call.function.arguments);
|
|
25370
|
-
} catch {
|
|
25371
|
-
return false;
|
|
25372
|
-
}
|
|
25373
|
-
const toolMetadata = getNativeToolMetadata(call.function.name);
|
|
25374
|
-
if (!toolMetadata) {
|
|
25375
|
-
return false;
|
|
25376
|
-
}
|
|
25377
|
-
return true;
|
|
25378
|
-
}
|
|
25379
|
-
};
|
|
25380
|
-
|
|
25381
|
-
// src/app/agent/runtime/tool_execution_policy.ts
|
|
25382
|
-
init_sandbox_policy();
|
|
25383
|
-
init_runtime_config();
|
|
25384
|
-
init_permission_rules();
|
|
25385
|
-
import path38 from "path";
|
|
25386
|
-
var LOCAL_EDIT_TOOL_NAMES = /* @__PURE__ */ new Set(["edit_tool", "file_write", "notebook_edit"]);
|
|
25387
|
-
function getToolPermissionLayer(metadata) {
|
|
25388
|
-
if (metadata.riskLevel === "safe") return "read";
|
|
25389
|
-
if (metadata.riskLevel === "write") return "write";
|
|
25390
|
-
if (metadata.riskLevel === "network") return "network";
|
|
25391
|
-
return "execute";
|
|
25392
|
-
}
|
|
25393
|
-
function isLocalEditTool(toolName) {
|
|
25394
|
-
return LOCAL_EDIT_TOOL_NAMES.has(toolName);
|
|
25395
|
-
}
|
|
25396
|
-
function checkFilePermissionRules(toolName, filePath, policy) {
|
|
25397
|
-
if (!filePath) {
|
|
25398
|
-
return { allowed: false, reason: "No file path provided for permission check." };
|
|
25399
|
-
}
|
|
25400
|
-
const resolvedPath = path38.resolve(filePath);
|
|
25401
|
-
if (!isPathInsideWorkspace(resolvedPath, policy)) {
|
|
25402
|
-
return { allowed: false, reason: `File path "${filePath}" is outside workspace root.` };
|
|
25403
|
-
}
|
|
25404
|
-
const relativePath = path38.relative(policy.workspaceRoot, resolvedPath);
|
|
25405
|
-
const toolPattern = `${toolName}(${relativePath})`;
|
|
25406
|
-
const ruleDecision = permissionRulesEngine.checkPermission(toolPattern, { filepath: filePath });
|
|
25407
|
-
if (ruleDecision === "deny") {
|
|
25408
|
-
return { allowed: false, reason: `File "${filePath}" denied by permission rules.` };
|
|
25409
|
-
}
|
|
25410
|
-
if (ruleDecision === "allow") {
|
|
25411
|
-
return { allowed: true, reason: `File "${filePath}" allowed by permission rules.` };
|
|
25460
|
+
if (ruleDecision === "allow") {
|
|
25461
|
+
return { allowed: true, reason: `File "${filePath}" allowed by permission rules.` };
|
|
25412
25462
|
}
|
|
25413
25463
|
const dirPath = path38.dirname(relativePath);
|
|
25414
25464
|
const dirPattern = `${toolName}(${dirPath}/**)`;
|
|
@@ -25699,28 +25749,6 @@ function consolidateCodingMemoryFile() {
|
|
|
25699
25749
|
};
|
|
25700
25750
|
}
|
|
25701
25751
|
|
|
25702
|
-
// src/app/agent/runtime/tool_orchestration.ts
|
|
25703
|
-
var PARALLEL_SAFE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
25704
|
-
"ls_tool",
|
|
25705
|
-
"read_file_lines",
|
|
25706
|
-
"count_file_lines",
|
|
25707
|
-
"find_by_name",
|
|
25708
|
-
"grep_search",
|
|
25709
|
-
"view_file_outline",
|
|
25710
|
-
"read_artifact",
|
|
25711
|
-
"list_agents",
|
|
25712
|
-
"list_mcp_resources",
|
|
25713
|
-
"read_mcp_resource",
|
|
25714
|
-
"todo"
|
|
25715
|
-
]);
|
|
25716
|
-
function toolEligibleForParallelRead(toolName, sessionId) {
|
|
25717
|
-
const name = String(toolName || "").trim();
|
|
25718
|
-
if (!name || !PARALLEL_SAFE_TOOL_NAMES.has(name)) return false;
|
|
25719
|
-
if (!decideToolExecution(name, void 0, sessionId).autoApprove) return false;
|
|
25720
|
-
const meta = getNativeToolMetadata(name);
|
|
25721
|
-
return meta?.riskLevel === "safe";
|
|
25722
|
-
}
|
|
25723
|
-
|
|
25724
25752
|
// src/app/agent/bluma/turn_start_payload.ts
|
|
25725
25753
|
function buildUserPromptPreview(inputText) {
|
|
25726
25754
|
const t = String(inputText ?? "").trim();
|
|
@@ -25785,276 +25813,153 @@ function subscribeToolResultStatuses(listener) {
|
|
|
25785
25813
|
return changed.subscribe(listener);
|
|
25786
25814
|
}
|
|
25787
25815
|
|
|
25788
|
-
// src/
|
|
25789
|
-
|
|
25790
|
-
|
|
25791
|
-
|
|
25792
|
-
sessionId;
|
|
25793
|
-
sessionFile = "";
|
|
25794
|
-
history = [];
|
|
25795
|
-
eventBus;
|
|
25796
|
-
mcpClient;
|
|
25797
|
-
feedbackSystem;
|
|
25798
|
-
skillLoader;
|
|
25799
|
-
compressor;
|
|
25800
|
-
isInterrupted = false;
|
|
25801
|
-
/** Mesmo turnId durante processTurn + todo o loop de tool_calls (FactorRouter). */
|
|
25802
|
-
activeTurnContext = null;
|
|
25803
|
-
/** Evita POST /turns/.../end duplicado no mesmo turno (ex.: Esc após message_result). */
|
|
25804
|
-
factorRouterTurnClosed = false;
|
|
25805
|
-
/** Passos seguidos sem tool_calls nem texto visível (só raciocínio) — evita loop lento no mesmo turno. */
|
|
25806
|
-
emptyAssistantReplySteps = 0;
|
|
25807
|
-
/** Reintentos consecutivos por tool call inválido. */
|
|
25808
|
-
invalidToolCallRetrySteps = 0;
|
|
25809
|
-
constructor(sessionId, eventBus, llm, mcpClient, feedbackSystem) {
|
|
25810
|
-
this.sessionId = sessionId;
|
|
25811
|
-
this.eventBus = eventBus;
|
|
25812
|
-
this.llm = llm;
|
|
25813
|
-
this.mcpClient = mcpClient;
|
|
25814
|
-
this.feedbackSystem = feedbackSystem;
|
|
25815
|
-
this.skillLoader = new SkillLoader(process.cwd());
|
|
25816
|
-
this.compressor = new HistoryCompressor();
|
|
25817
|
-
this.eventBus.on("user_interrupt", async () => {
|
|
25818
|
-
this.isInterrupted = true;
|
|
25819
|
-
await this.notifyFactorTurnEndIfNeeded("user_interrupt");
|
|
25820
|
-
this.eventBus.emit("backend_message", { type: "done", status: "interrupted" });
|
|
25821
|
-
});
|
|
25822
|
-
this.eventBus.on("user_overlay", async (data) => {
|
|
25823
|
-
const clean = String(data.payload ?? "").trim();
|
|
25824
|
-
this.history.push({ role: "user", content: clean });
|
|
25825
|
-
this.eventBus.emit("backend_message", { type: "user_overlay", payload: clean, ts: data.ts || Date.now() });
|
|
25826
|
-
try {
|
|
25827
|
-
if (this.sessionFile) {
|
|
25828
|
-
this.persistSession();
|
|
25829
|
-
} else {
|
|
25830
|
-
const [sessionFile, , mem] = await loadOrcreateSession(this.sessionId);
|
|
25831
|
-
this.sessionFile = sessionFile;
|
|
25832
|
-
this.compressor.restoreSnapshot(mem);
|
|
25833
|
-
this.persistSession();
|
|
25834
|
-
}
|
|
25835
|
-
} catch (e) {
|
|
25836
|
-
this.eventBus.emit("backend_message", { type: "error", message: `Falha ao salvar hist\xF3rico ap\xF3s user_overlay: ${e.message}` });
|
|
25837
|
-
}
|
|
25838
|
-
});
|
|
25839
|
-
}
|
|
25840
|
-
getMemorySnapshot() {
|
|
25841
|
-
return this.compressor.getSnapshot();
|
|
25842
|
-
}
|
|
25843
|
-
/**
|
|
25844
|
-
* Salvar histórico de forma SÍNCRONA - usado em situações críticas (erros, shutdown)
|
|
25845
|
-
* para garantir que nada se perde. NÃO usa debounce.
|
|
25846
|
-
*/
|
|
25847
|
-
persistSessionSync() {
|
|
25848
|
-
if (!this.sessionFile) return;
|
|
25816
|
+
// src/utils/toolActionStatus.ts
|
|
25817
|
+
function parseArgsRecord(args) {
|
|
25818
|
+
if (args == null) return {};
|
|
25819
|
+
if (typeof args === "string") {
|
|
25849
25820
|
try {
|
|
25850
|
-
|
|
25851
|
-
|
|
25852
|
-
|
|
25853
|
-
conversation_history: this.history,
|
|
25854
|
-
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25855
|
-
...this.compressor.getSnapshot()
|
|
25856
|
-
};
|
|
25857
|
-
fs39.writeFileSync(this.sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
|
|
25858
|
-
} catch (error) {
|
|
25859
|
-
console.error("[Bluma] Failed to persist session synchronously:", error);
|
|
25821
|
+
return JSON.parse(args);
|
|
25822
|
+
} catch {
|
|
25823
|
+
return {};
|
|
25860
25824
|
}
|
|
25861
25825
|
}
|
|
25862
|
-
|
|
25863
|
-
|
|
25864
|
-
if (!this.sessionFile) return;
|
|
25865
|
-
void saveSessionHistory(this.sessionFile, this.history, this.getMemorySnapshot());
|
|
25866
|
-
}
|
|
25867
|
-
recordUiSlashCommand(command, mode = "visible_only") {
|
|
25868
|
-
const text = String(command ?? "").trim();
|
|
25869
|
-
if (!text) return;
|
|
25870
|
-
this.history.push({
|
|
25871
|
-
role: "user",
|
|
25872
|
-
name: mode === "with_internal_prompt" ? "ui_slash_command" : "ui_slash_command_local",
|
|
25873
|
-
content: text
|
|
25874
|
-
});
|
|
25875
|
-
this.persistSession();
|
|
25826
|
+
if (typeof args === "object") {
|
|
25827
|
+
return args;
|
|
25876
25828
|
}
|
|
25877
|
-
|
|
25878
|
-
|
|
25879
|
-
|
|
25880
|
-
|
|
25881
|
-
|
|
25882
|
-
|
|
25883
|
-
|
|
25884
|
-
|
|
25885
|
-
|
|
25886
|
-
|
|
25887
|
-
|
|
25888
|
-
|
|
25889
|
-
|
|
25890
|
-
|
|
25891
|
-
|
|
25892
|
-
|
|
25893
|
-
|
|
25894
|
-
|
|
25895
|
-
|
|
25829
|
+
return {};
|
|
25830
|
+
}
|
|
25831
|
+
function truncate2(value, max = 44) {
|
|
25832
|
+
const text = value.trim().replace(/\s+/g, " ");
|
|
25833
|
+
if (!text) return "";
|
|
25834
|
+
return text.length <= max ? text : `${text.slice(0, max - 1)}\u2026`;
|
|
25835
|
+
}
|
|
25836
|
+
function shortFileName(pathLike) {
|
|
25837
|
+
const trimmed = pathLike.trim();
|
|
25838
|
+
if (!trimmed) return "";
|
|
25839
|
+
return trimmed.split("/").pop() || trimmed;
|
|
25840
|
+
}
|
|
25841
|
+
function formatCommand(command) {
|
|
25842
|
+
const compact = truncate2(command, 42);
|
|
25843
|
+
return compact ? `shell command: ${compact}` : "shell command";
|
|
25844
|
+
}
|
|
25845
|
+
function formatSearchQuery(prefix, query) {
|
|
25846
|
+
const compact = truncate2(query, 36);
|
|
25847
|
+
return compact ? `${prefix}: ${compact}` : prefix;
|
|
25848
|
+
}
|
|
25849
|
+
function formatResponseLabel(toolName, args) {
|
|
25850
|
+
switch (toolName) {
|
|
25851
|
+
case "shell_command":
|
|
25852
|
+
case "run_command": {
|
|
25853
|
+
const cmd = typeof args.command === "string" ? args.command : "";
|
|
25854
|
+
return cmd ? `Running ${formatCommand(cmd)}` : "Running shell command";
|
|
25896
25855
|
}
|
|
25897
|
-
|
|
25898
|
-
|
|
25899
|
-
|
|
25900
|
-
|
|
25901
|
-
|
|
25902
|
-
|
|
25903
|
-
|
|
25904
|
-
|
|
25905
|
-
|
|
25906
|
-
|
|
25907
|
-
|
|
25908
|
-
|
|
25909
|
-
|
|
25910
|
-
|
|
25911
|
-
|
|
25912
|
-
|
|
25913
|
-
|
|
25914
|
-
|
|
25915
|
-
|
|
25916
|
-
|
|
25917
|
-
|
|
25918
|
-
|
|
25919
|
-
|
|
25920
|
-
|
|
25921
|
-
|
|
25922
|
-
|
|
25923
|
-
|
|
25924
|
-
|
|
25925
|
-
|
|
25926
|
-
|
|
25927
|
-
|
|
25928
|
-
|
|
25929
|
-
|
|
25930
|
-
|
|
25931
|
-
|
|
25932
|
-
|
|
25933
|
-
|
|
25934
|
-
|
|
25935
|
-
|
|
25936
|
-
|
|
25937
|
-
|
|
25938
|
-
|
|
25939
|
-
|
|
25940
|
-
|
|
25941
|
-
|
|
25942
|
-
|
|
25943
|
-
|
|
25944
|
-
|
|
25945
|
-
|
|
25946
|
-
|
|
25947
|
-
|
|
25948
|
-
|
|
25949
|
-
|
|
25950
|
-
|
|
25951
|
-
|
|
25952
|
-
|
|
25953
|
-
|
|
25954
|
-
|
|
25955
|
-
|
|
25956
|
-
|
|
25957
|
-
|
|
25958
|
-
|
|
25959
|
-
|
|
25960
|
-
|
|
25961
|
-
|
|
25962
|
-
|
|
25963
|
-
|
|
25964
|
-
|
|
25965
|
-
|
|
25966
|
-
|
|
25967
|
-
|
|
25968
|
-
|
|
25969
|
-
|
|
25970
|
-
|
|
25971
|
-
|
|
25972
|
-
|
|
25973
|
-
|
|
25974
|
-
listAvailableSkills() {
|
|
25975
|
-
return this.skillLoader.listAvailable();
|
|
25976
|
-
}
|
|
25977
|
-
getSkillsDirs() {
|
|
25978
|
-
return this.skillLoader.getSkillsDirs();
|
|
25979
|
-
}
|
|
25980
|
-
getSkillConflictWarnings() {
|
|
25981
|
-
return this.skillLoader.formatConflictWarnings();
|
|
25982
|
-
}
|
|
25983
|
-
getFeedbackScore() {
|
|
25984
|
-
return this.feedbackSystem.getCumulativeScore();
|
|
25985
|
-
}
|
|
25986
|
-
/**
|
|
25987
|
-
* Contrato de ciclo de turno (UI, hooks, Factor Router):
|
|
25988
|
-
*
|
|
25989
|
-
* - `backend_message` `{ type: 'turn_start', turnId, sessionId, userPromptPreview }` — início;
|
|
25990
|
-
* payload estável em {@link buildTurnStartBackendMessage}.
|
|
25991
|
-
* - Stream: `stream_start` / `stream_reasoning_chunk` / `stream_chunk` / `stream_end`.
|
|
25992
|
-
* Se a resposta incluir tool `message`, `stream_end` leva `{ omitAssistantFlush: true }` para não
|
|
25993
|
-
* duplicar texto no histórico (o utilizador vê o `tool_result` com gutter). Ver `applyStreamEndFlush`.
|
|
25994
|
-
* - Fim: `backend_message` `{ type: 'done', status }` (`completed` | `failed` | `interrupted` | …).
|
|
25995
|
-
* - Factor Router: um POST `/turns/{turnId}/end` por turno via `notifyFactorTurnEndIfNeeded` (razões abaixo).
|
|
25996
|
-
*
|
|
25997
|
-
* Smoke manual sugerido: mensagem normal com `message`+`result`; Ctrl+C; erro LLM; loop sem resposta útil.
|
|
25998
|
-
*/
|
|
25999
|
-
async processTurn(userInput, userContextInput, options) {
|
|
26000
|
-
this.isInterrupted = false;
|
|
26001
|
-
this.factorRouterTurnClosed = false;
|
|
26002
|
-
const inputText = String(userInput.content || "").trim();
|
|
26003
|
-
const turnId = uuidv48();
|
|
26004
|
-
this.activeTurnContext = {
|
|
26005
|
-
...userContextInput,
|
|
26006
|
-
turnId,
|
|
26007
|
-
sessionId: userContextInput.sessionId || this.sessionId
|
|
26008
|
-
};
|
|
26009
|
-
process.env.BLUMA_USER_CONTEXT_JSON = JSON.stringify({
|
|
26010
|
-
conversationId: this.activeTurnContext.conversationId ?? null,
|
|
26011
|
-
userId: this.activeTurnContext.userId ?? null,
|
|
26012
|
-
userName: this.activeTurnContext.userName ?? null,
|
|
26013
|
-
userEmail: this.activeTurnContext.userEmail ?? null,
|
|
26014
|
-
companyId: this.activeTurnContext.companyId ?? null,
|
|
26015
|
-
companyName: this.activeTurnContext.companyName ?? null
|
|
26016
|
-
});
|
|
26017
|
-
const userContent = buildUserMessageContent(inputText, process.cwd());
|
|
26018
|
-
this.history.push({
|
|
26019
|
-
role: "user",
|
|
26020
|
-
content: userContent,
|
|
26021
|
-
...options?.historyName ? { name: options.historyName } : {}
|
|
26022
|
-
});
|
|
26023
|
-
this.emptyAssistantReplySteps = 0;
|
|
26024
|
-
this.invalidToolCallRetrySteps = 0;
|
|
26025
|
-
this.eventBus.emit(
|
|
26026
|
-
"backend_message",
|
|
26027
|
-
buildTurnStartBackendMessage({
|
|
26028
|
-
turnId: this.activeTurnContext.turnId,
|
|
26029
|
-
sessionId: this.activeTurnContext.sessionId,
|
|
26030
|
-
inputText,
|
|
26031
|
-
appContext: this.activeTurnContext.appContext ?? null
|
|
26032
|
-
})
|
|
26033
|
-
);
|
|
26034
|
-
if (inputText === "/init") {
|
|
26035
|
-
this.eventBus.emit("dispatch", inputText);
|
|
26036
|
-
}
|
|
26037
|
-
await this._continueConversation();
|
|
25856
|
+
case "command_status":
|
|
25857
|
+
return "Checking command status";
|
|
25858
|
+
case "send_command_input":
|
|
25859
|
+
return "Sending command input";
|
|
25860
|
+
case "kill_command":
|
|
25861
|
+
return "Stopping command";
|
|
25862
|
+
case "read_file_lines":
|
|
25863
|
+
return `Reading ${shortFileName(String(args.filepath ?? args.file_path ?? "")) || "file"}`;
|
|
25864
|
+
case "count_file_lines":
|
|
25865
|
+
return `Counting lines in ${shortFileName(String(args.filepath ?? args.file_path ?? "")) || "file"}`;
|
|
25866
|
+
case "ls_tool":
|
|
25867
|
+
return "Listing files";
|
|
25868
|
+
case "edit_tool": {
|
|
25869
|
+
const edits = Array.isArray(args.edits) ? args.edits : [];
|
|
25870
|
+
const count = edits.length || 1;
|
|
25871
|
+
const filePath = typeof args.file_path === "string" ? args.file_path : typeof edits[0]?.file_path === "string" ? String(edits[0].file_path) : "";
|
|
25872
|
+
const file = shortFileName(filePath) || "file";
|
|
25873
|
+
return count === 1 ? `Editing ${file}` : `Editing ${count} files`;
|
|
25874
|
+
}
|
|
25875
|
+
case "file_write":
|
|
25876
|
+
return `Writing ${shortFileName(String(args.filepath ?? args.file_path ?? "")) || "file"}`;
|
|
25877
|
+
case "grep_search":
|
|
25878
|
+
return formatSearchQuery("Searching code", String(args.query ?? ""));
|
|
25879
|
+
case "find_by_name":
|
|
25880
|
+
return formatSearchQuery("Finding files", String(args.pattern ?? ""));
|
|
25881
|
+
case "view_file_outline":
|
|
25882
|
+
return `Inspecting ${shortFileName(String(args.file_path ?? "")) || "file outline"}`;
|
|
25883
|
+
case "web_fetch":
|
|
25884
|
+
return formatSearchQuery("Fetching URL", String(args.url ?? ""));
|
|
25885
|
+
case "search_web":
|
|
25886
|
+
return formatSearchQuery("Searching the web", String(args.query ?? ""));
|
|
25887
|
+
case "spawn_agent":
|
|
25888
|
+
return "Spawning a worker";
|
|
25889
|
+
case "wait_agent":
|
|
25890
|
+
return "Waiting for worker";
|
|
25891
|
+
case "list_agents":
|
|
25892
|
+
return "Reviewing workers";
|
|
25893
|
+
case "todo":
|
|
25894
|
+
return "Updating task list";
|
|
25895
|
+
case "task_boundary":
|
|
25896
|
+
return "Switching task boundary";
|
|
25897
|
+
case "task_create":
|
|
25898
|
+
case "task_list":
|
|
25899
|
+
case "task_get":
|
|
25900
|
+
case "task_update":
|
|
25901
|
+
case "task_stop":
|
|
25902
|
+
return "Managing task";
|
|
25903
|
+
case "load_skill":
|
|
25904
|
+
return "Loading skill context";
|
|
25905
|
+
case "coding_memory":
|
|
25906
|
+
return "Updating working memory";
|
|
25907
|
+
case "read_artifact":
|
|
25908
|
+
return `Reading ${String(args.filename ?? "artifact")}`;
|
|
25909
|
+
case "message":
|
|
25910
|
+
return "Preparing assistant reply";
|
|
25911
|
+
case "ask_user_question":
|
|
25912
|
+
return "Waiting for your answer";
|
|
25913
|
+
case "enter_plan_mode":
|
|
25914
|
+
return "Entering plan mode";
|
|
25915
|
+
case "exit_plan_mode":
|
|
25916
|
+
return "Leaving plan mode";
|
|
25917
|
+
case "list_mcp_resources":
|
|
25918
|
+
return "Browsing MCP resources";
|
|
25919
|
+
case "read_mcp_resource":
|
|
25920
|
+
return "Reading MCP resource";
|
|
25921
|
+
case "cron_create":
|
|
25922
|
+
return "Scheduling reminder";
|
|
25923
|
+
case "cron_list":
|
|
25924
|
+
return "Listing reminders";
|
|
25925
|
+
case "cron_delete":
|
|
25926
|
+
return "Cancelling reminder";
|
|
25927
|
+
case "notebook_edit":
|
|
25928
|
+
return `Editing ${shortFileName(String(args.filepath ?? "")) || "notebook"}`;
|
|
25929
|
+
case "lsp_query":
|
|
25930
|
+
return "Querying language server";
|
|
25931
|
+
default:
|
|
25932
|
+
return toolName.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase()) || "Working";
|
|
26038
25933
|
}
|
|
26039
|
-
|
|
26040
|
-
|
|
26041
|
-
|
|
26042
|
-
|
|
26043
|
-
|
|
26044
|
-
|
|
26045
|
-
|
|
26046
|
-
|
|
26047
|
-
|
|
26048
|
-
|
|
26049
|
-
|
|
25934
|
+
}
|
|
25935
|
+
function getToolActionLabel(toolName, args) {
|
|
25936
|
+
return formatResponseLabel(toolName, parseArgsRecord(args));
|
|
25937
|
+
}
|
|
25938
|
+
function getToolActionStatus(toolName, args) {
|
|
25939
|
+
return {
|
|
25940
|
+
action: getToolActionLabel(toolName, args),
|
|
25941
|
+
phase: "tool"
|
|
25942
|
+
};
|
|
25943
|
+
}
|
|
25944
|
+
function getReasoningActionStatus() {
|
|
25945
|
+
return {
|
|
25946
|
+
action: "Reasoning",
|
|
25947
|
+
phase: "thinking"
|
|
25948
|
+
};
|
|
25949
|
+
}
|
|
25950
|
+
function getRespondingActionStatus() {
|
|
25951
|
+
return {
|
|
25952
|
+
action: "Writing",
|
|
25953
|
+
phase: "responding"
|
|
25954
|
+
};
|
|
25955
|
+
}
|
|
25956
|
+
|
|
25957
|
+
// src/app/agent/bluma/core/bluma_tool_runner.ts
|
|
25958
|
+
var BluMaToolRunner = class {
|
|
25959
|
+
constructor(deps) {
|
|
25960
|
+
this.deps = deps;
|
|
26050
25961
|
}
|
|
26051
|
-
|
|
26052
|
-
* Executa uma tool aprovada: emit tool_call → invoke → tool_result → histórico.
|
|
26053
|
-
* `backend_message` tool_call / tool_result incluem `tool_call_id` (id do tool call no modelo)
|
|
26054
|
-
* para a UI correlacionar a linha de invocação (•) com o resultado compacto.
|
|
26055
|
-
* @returns shouldContinue — false se `message` com message_type result terminou o turno.
|
|
26056
|
-
*/
|
|
26057
|
-
async executeApprovedToolInvocation(toolCall, decisionData) {
|
|
25962
|
+
async executeApprovedToolInvocation(toolCall, _decisionData) {
|
|
26058
25963
|
const toolName = toolCall.function.name;
|
|
26059
25964
|
let toolResultContent;
|
|
26060
25965
|
let shouldContinueConversation = true;
|
|
@@ -26068,8 +25973,8 @@ var BluMaAgent = class _BluMaAgent {
|
|
|
26068
25973
|
if (toolArgs === void 0 || toolArgs === null) {
|
|
26069
25974
|
toolArgs = {};
|
|
26070
25975
|
}
|
|
26071
|
-
} catch
|
|
26072
|
-
this.eventBus.emit("backend_message", {
|
|
25976
|
+
} catch {
|
|
25977
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26073
25978
|
type: "info",
|
|
26074
25979
|
message: "O BluMa encontrou um erro ao processar. A tentar recuperar a sess\xE3o..."
|
|
26075
25980
|
});
|
|
@@ -26078,12 +25983,12 @@ var BluMaAgent = class _BluMaAgent {
|
|
|
26078
25983
|
error: "Tool arguments could not be parsed",
|
|
26079
25984
|
recovery: "Session recovered automatically"
|
|
26080
25985
|
});
|
|
26081
|
-
this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
26082
|
-
this.history.push({
|
|
25986
|
+
this.deps.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
25987
|
+
this.deps.history.push({
|
|
26083
25988
|
role: "system",
|
|
26084
25989
|
content: "The previous tool call had invalid JSON arguments. Please retry with properly formatted JSON arguments."
|
|
26085
25990
|
});
|
|
26086
|
-
this.persistSession();
|
|
25991
|
+
this.deps.persistSession();
|
|
26087
25992
|
return true;
|
|
26088
25993
|
}
|
|
26089
25994
|
const toolMetadata = getNativeToolMetadata(toolName);
|
|
@@ -26093,18 +25998,18 @@ var BluMaAgent = class _BluMaAgent {
|
|
|
26093
25998
|
error: `Tool "${toolName}" not found in agent catalog.`,
|
|
26094
25999
|
suggestion: "This tool does not exist. Please use a valid tool name from the available tools list."
|
|
26095
26000
|
});
|
|
26096
|
-
this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
26097
|
-
this.history.push({
|
|
26001
|
+
this.deps.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
26002
|
+
this.deps.history.push({
|
|
26098
26003
|
role: "system",
|
|
26099
26004
|
content: `[SYSTEM] Tool "${toolName}" is not found in the agent's available tools. Please use only tools that are listed in your tool catalog.`
|
|
26100
26005
|
});
|
|
26101
|
-
this.persistSession();
|
|
26006
|
+
this.deps.persistSession();
|
|
26102
26007
|
return true;
|
|
26103
26008
|
}
|
|
26104
26009
|
if (toolName === "edit_tool") {
|
|
26105
26010
|
const batch = toolArgs.edits;
|
|
26106
26011
|
if (Array.isArray(batch) && batch.length > 0) {
|
|
26107
|
-
for (let i = 0; i < batch.length; i
|
|
26012
|
+
for (let i = 0; i < batch.length; i += 1) {
|
|
26108
26013
|
const entry = batch[i];
|
|
26109
26014
|
if (!entry?.file_path || typeof entry.file_path !== "string") {
|
|
26110
26015
|
toolResultContent = JSON.stringify({
|
|
@@ -26113,8 +26018,8 @@ var BluMaAgent = class _BluMaAgent {
|
|
|
26113
26018
|
details: `edits[${i}].file_path is required`,
|
|
26114
26019
|
received: toolArgs
|
|
26115
26020
|
});
|
|
26116
|
-
this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
26117
|
-
this.persistSession();
|
|
26021
|
+
this.deps.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
26022
|
+
this.deps.persistSession();
|
|
26118
26023
|
return true;
|
|
26119
26024
|
}
|
|
26120
26025
|
if (entry.old_string === void 0 || entry.new_string === void 0) {
|
|
@@ -26124,8 +26029,8 @@ var BluMaAgent = class _BluMaAgent {
|
|
|
26124
26029
|
details: `edits[${i}]: old_string and new_string are required`,
|
|
26125
26030
|
received: toolArgs
|
|
26126
26031
|
});
|
|
26127
|
-
this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
26128
|
-
this.persistSession();
|
|
26032
|
+
this.deps.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
26033
|
+
this.deps.persistSession();
|
|
26129
26034
|
return true;
|
|
26130
26035
|
}
|
|
26131
26036
|
}
|
|
@@ -26137,8 +26042,8 @@ var BluMaAgent = class _BluMaAgent {
|
|
|
26137
26042
|
details: "file_path is required and must be a string (or provide non-empty edits[])",
|
|
26138
26043
|
received: toolArgs
|
|
26139
26044
|
});
|
|
26140
|
-
this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
26141
|
-
this.persistSession();
|
|
26045
|
+
this.deps.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
26046
|
+
this.deps.persistSession();
|
|
26142
26047
|
return true;
|
|
26143
26048
|
}
|
|
26144
26049
|
if (toolArgs.old_string === void 0 || toolArgs.new_string === void 0) {
|
|
@@ -26148,8 +26053,8 @@ var BluMaAgent = class _BluMaAgent {
|
|
|
26148
26053
|
details: "old_string and new_string are required (or provide edits[])",
|
|
26149
26054
|
received: toolArgs
|
|
26150
26055
|
});
|
|
26151
|
-
this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
26152
|
-
this.persistSession();
|
|
26056
|
+
this.deps.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
26057
|
+
this.deps.persistSession();
|
|
26153
26058
|
return true;
|
|
26154
26059
|
}
|
|
26155
26060
|
}
|
|
@@ -26157,61 +26062,34 @@ var BluMaAgent = class _BluMaAgent {
|
|
|
26157
26062
|
let previewContent;
|
|
26158
26063
|
if (toolName === "edit_tool") {
|
|
26159
26064
|
try {
|
|
26160
|
-
previewContent = await this.
|
|
26065
|
+
previewContent = await this.deps.generateEditPreview(toolArgs);
|
|
26161
26066
|
} catch (previewError) {
|
|
26162
26067
|
previewContent = `Failed to generate preview: ${previewError.message}`;
|
|
26163
26068
|
}
|
|
26164
26069
|
} else if (toolName === "file_write") {
|
|
26165
26070
|
try {
|
|
26166
|
-
previewContent = this.
|
|
26071
|
+
previewContent = this.deps.generateFileWritePreview(toolArgs);
|
|
26167
26072
|
} catch (previewError) {
|
|
26168
26073
|
previewContent = `Failed to generate preview: ${previewError.message}`;
|
|
26169
26074
|
}
|
|
26170
26075
|
}
|
|
26171
|
-
this.eventBus.emit("backend_message", {
|
|
26076
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26172
26077
|
type: "tool_call",
|
|
26173
26078
|
tool_call_id: String(toolCall.id),
|
|
26174
26079
|
tool_name: toolName,
|
|
26175
26080
|
arguments: toolArgs,
|
|
26176
26081
|
preview: previewContent,
|
|
26177
26082
|
suppress_edit_diff_preview: toolName === "edit_tool",
|
|
26178
|
-
tool_policy: this.buildToolPolicyForUi(toolName, toolArgs)
|
|
26083
|
+
tool_policy: this.deps.buildToolPolicyForUi(toolName, toolArgs)
|
|
26179
26084
|
});
|
|
26180
26085
|
setToolResultStatus(String(toolCall.id), "running");
|
|
26181
26086
|
try {
|
|
26182
|
-
if (this.isInterrupted) {
|
|
26183
|
-
this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled before tool execution." });
|
|
26087
|
+
if (this.deps.isInterrupted()) {
|
|
26088
|
+
this.deps.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled before tool execution." });
|
|
26184
26089
|
return false;
|
|
26185
26090
|
}
|
|
26186
|
-
|
|
26187
|
-
|
|
26188
|
-
shell_command: "Executing",
|
|
26189
|
-
command_status: "Waiting",
|
|
26190
|
-
ls_tool: "Reading",
|
|
26191
|
-
read_file_lines: "Reading",
|
|
26192
|
-
message: "Responding",
|
|
26193
|
-
load_skill: "Loading skill",
|
|
26194
|
-
search_web: "Searching",
|
|
26195
|
-
todo: "Planning",
|
|
26196
|
-
ask_user_question: "Question",
|
|
26197
|
-
enter_plan_mode: "Planning",
|
|
26198
|
-
exit_plan_mode: "Planning",
|
|
26199
|
-
task_create: "Planning",
|
|
26200
|
-
task_list: "Planning",
|
|
26201
|
-
task_get: "Planning",
|
|
26202
|
-
task_update: "Planning",
|
|
26203
|
-
task_stop: "Planning",
|
|
26204
|
-
list_mcp_resources: "Reading",
|
|
26205
|
-
read_mcp_resource: "Reading",
|
|
26206
|
-
cron_create: "Scheduling",
|
|
26207
|
-
cron_list: "Reading",
|
|
26208
|
-
cron_delete: "Scheduling",
|
|
26209
|
-
notebook_edit: "Editing",
|
|
26210
|
-
lsp_query: "Reading"
|
|
26211
|
-
};
|
|
26212
|
-
const action = actionMap[toolName] || "Processing";
|
|
26213
|
-
this.eventBus.emit("action_status", { action });
|
|
26214
|
-
const result = await this.mcpClient.invoke(toolName, toolArgs);
|
|
26091
|
+
this.deps.eventBus.emit("action_status", getToolActionStatus(toolName, toolArgs));
|
|
26092
|
+
const result = await this.deps.mcpClient.invoke(toolName, toolArgs);
|
|
26215
26093
|
const normalized = normalizeToolResult(result);
|
|
26216
26094
|
setToolResultStatus(String(toolCall.id), normalized.status);
|
|
26217
26095
|
toolResultContent = toolResultToString(normalized);
|
|
@@ -26225,12 +26103,12 @@ var BluMaAgent = class _BluMaAgent {
|
|
|
26225
26103
|
null,
|
|
26226
26104
|
2
|
|
26227
26105
|
);
|
|
26228
|
-
this.eventBus.emit("backend_message", {
|
|
26106
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26229
26107
|
type: "error",
|
|
26230
26108
|
message: `Tool "${toolName}" failed: ${error.message}`
|
|
26231
26109
|
});
|
|
26232
26110
|
}
|
|
26233
|
-
this.eventBus.emit("backend_message", {
|
|
26111
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26234
26112
|
type: "tool_result",
|
|
26235
26113
|
tool_call_id: String(toolCall.id),
|
|
26236
26114
|
tool_name: toolName,
|
|
@@ -26242,21 +26120,20 @@ var BluMaAgent = class _BluMaAgent {
|
|
|
26242
26120
|
try {
|
|
26243
26121
|
const resultObj = typeof toolResultContent === "string" ? JSON.parse(toolResultContent) : toolResultContent;
|
|
26244
26122
|
if (resultObj.message_type === "result") {
|
|
26245
|
-
await this.notifyFactorTurnEndIfNeeded("message_result");
|
|
26123
|
+
await this.deps.notifyFactorTurnEndIfNeeded("message_result");
|
|
26246
26124
|
shouldContinueConversation = false;
|
|
26247
|
-
this.emitTurnCompleted();
|
|
26125
|
+
this.deps.emitTurnCompleted();
|
|
26248
26126
|
}
|
|
26249
26127
|
} catch {
|
|
26250
26128
|
}
|
|
26251
26129
|
}
|
|
26252
|
-
this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
26253
|
-
this.persistSession();
|
|
26130
|
+
this.deps.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
26131
|
+
this.deps.persistSession();
|
|
26254
26132
|
return shouldContinueConversation;
|
|
26255
26133
|
}
|
|
26256
|
-
/** Várias leituras seguras em paralelo (grep, ls, read_file, …). */
|
|
26257
26134
|
async executeParallelReadBatch(batch, _decisionData) {
|
|
26258
|
-
if (this.isInterrupted) {
|
|
26259
|
-
this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled before tool execution." });
|
|
26135
|
+
if (this.deps.isInterrupted()) {
|
|
26136
|
+
this.deps.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled before tool execution." });
|
|
26260
26137
|
return false;
|
|
26261
26138
|
}
|
|
26262
26139
|
const parsed = [];
|
|
@@ -26279,59 +26156,46 @@ var BluMaAgent = class _BluMaAgent {
|
|
|
26279
26156
|
error: `Tool "${toolName}" not found in agent catalog.`,
|
|
26280
26157
|
suggestion: "This tool does not exist. Please use a valid tool name from the available tools list."
|
|
26281
26158
|
});
|
|
26282
|
-
this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
26283
|
-
this.history.push({
|
|
26159
|
+
this.deps.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
26160
|
+
this.deps.history.push({
|
|
26284
26161
|
role: "system",
|
|
26285
26162
|
content: `[SYSTEM] Tool "${toolName}" is not found in the agent's available tools. Please use only tools that are listed in your tool catalog.`
|
|
26286
26163
|
});
|
|
26287
|
-
this.persistSession();
|
|
26164
|
+
this.deps.persistSession();
|
|
26288
26165
|
return true;
|
|
26289
26166
|
}
|
|
26290
26167
|
parsed.push({ toolCall, toolName, toolArgs });
|
|
26291
|
-
} catch
|
|
26168
|
+
} catch {
|
|
26292
26169
|
const toolResultContent = JSON.stringify({
|
|
26293
26170
|
error: "Tool arguments could not be parsed",
|
|
26294
26171
|
recovery: "Session recovered automatically"
|
|
26295
26172
|
});
|
|
26296
|
-
this.eventBus.emit("backend_message", {
|
|
26173
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26297
26174
|
type: "info",
|
|
26298
26175
|
message: "O BluMa encontrou um erro ao processar. A tentar recuperar a sess\xE3o..."
|
|
26299
26176
|
});
|
|
26300
|
-
this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
26301
|
-
this.persistSession();
|
|
26177
|
+
this.deps.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
26178
|
+
this.deps.persistSession();
|
|
26302
26179
|
return true;
|
|
26303
26180
|
}
|
|
26304
26181
|
}
|
|
26305
|
-
const actionMap = {
|
|
26306
|
-
ls_tool: "Reading",
|
|
26307
|
-
read_file_lines: "Reading",
|
|
26308
|
-
count_file_lines: "Reading",
|
|
26309
|
-
find_by_name: "Reading",
|
|
26310
|
-
grep_search: "Reading",
|
|
26311
|
-
view_file_outline: "Reading",
|
|
26312
|
-
read_artifact: "Reading",
|
|
26313
|
-
list_agents: "Reading",
|
|
26314
|
-
list_mcp_resources: "Reading",
|
|
26315
|
-
read_mcp_resource: "Reading"
|
|
26316
|
-
};
|
|
26317
26182
|
const settled = await Promise.allSettled(
|
|
26318
26183
|
parsed.map(async (p) => {
|
|
26319
|
-
this.eventBus.emit("backend_message", {
|
|
26184
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26320
26185
|
type: "tool_call",
|
|
26321
26186
|
tool_call_id: String(p.toolCall.id),
|
|
26322
26187
|
tool_name: p.toolName,
|
|
26323
26188
|
arguments: p.toolArgs,
|
|
26324
|
-
tool_policy: this.buildToolPolicyForUi(p.toolName, p.toolArgs)
|
|
26189
|
+
tool_policy: this.deps.buildToolPolicyForUi(p.toolName, p.toolArgs)
|
|
26325
26190
|
});
|
|
26326
26191
|
setToolResultStatus(String(p.toolCall.id), "pending");
|
|
26327
26192
|
setToolResultStatus(String(p.toolCall.id), "running");
|
|
26328
|
-
|
|
26329
|
-
this.
|
|
26330
|
-
return this.mcpClient.invoke(p.toolName, p.toolArgs);
|
|
26193
|
+
this.deps.eventBus.emit("action_status", getToolActionStatus(p.toolName, p.toolArgs));
|
|
26194
|
+
return this.deps.mcpClient.invoke(p.toolName, p.toolArgs);
|
|
26331
26195
|
})
|
|
26332
26196
|
);
|
|
26333
26197
|
let shouldContinue = true;
|
|
26334
|
-
for (let k = 0; k < parsed.length; k
|
|
26198
|
+
for (let k = 0; k < parsed.length; k += 1) {
|
|
26335
26199
|
const p = parsed[k];
|
|
26336
26200
|
const outcome = settled[k];
|
|
26337
26201
|
let toolResultContent;
|
|
@@ -26343,7 +26207,7 @@ var BluMaAgent = class _BluMaAgent {
|
|
|
26343
26207
|
2
|
|
26344
26208
|
);
|
|
26345
26209
|
setToolResultStatus(String(p.toolCall.id), "error");
|
|
26346
|
-
this.eventBus.emit("backend_message", {
|
|
26210
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26347
26211
|
type: "error",
|
|
26348
26212
|
message: `Tool "${p.toolName}" failed: ${errMsg}`
|
|
26349
26213
|
});
|
|
@@ -26352,7 +26216,7 @@ var BluMaAgent = class _BluMaAgent {
|
|
|
26352
26216
|
setToolResultStatus(String(p.toolCall.id), normalized.status);
|
|
26353
26217
|
toolResultContent = toolResultToString(normalized);
|
|
26354
26218
|
}
|
|
26355
|
-
this.eventBus.emit("backend_message", {
|
|
26219
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26356
26220
|
type: "tool_result",
|
|
26357
26221
|
tool_call_id: String(p.toolCall.id),
|
|
26358
26222
|
tool_name: p.toolName,
|
|
@@ -26363,35 +26227,263 @@ var BluMaAgent = class _BluMaAgent {
|
|
|
26363
26227
|
try {
|
|
26364
26228
|
const resultObj = typeof toolResultContent === "string" ? JSON.parse(toolResultContent) : toolResultContent;
|
|
26365
26229
|
if (resultObj.message_type === "result") {
|
|
26366
|
-
await this.notifyFactorTurnEndIfNeeded("message_result");
|
|
26230
|
+
await this.deps.notifyFactorTurnEndIfNeeded("message_result");
|
|
26367
26231
|
shouldContinue = false;
|
|
26368
|
-
this.emitTurnCompleted();
|
|
26232
|
+
this.deps.emitTurnCompleted();
|
|
26369
26233
|
}
|
|
26370
26234
|
} catch {
|
|
26371
26235
|
}
|
|
26372
26236
|
}
|
|
26373
|
-
this.history.push({ role: "tool", tool_call_id: p.toolCall.id, content: toolResultContent });
|
|
26237
|
+
this.deps.history.push({ role: "tool", tool_call_id: p.toolCall.id, content: toolResultContent });
|
|
26374
26238
|
}
|
|
26375
|
-
this.persistSession();
|
|
26239
|
+
this.deps.persistSession();
|
|
26376
26240
|
return shouldContinue;
|
|
26377
26241
|
}
|
|
26378
|
-
|
|
26379
|
-
|
|
26380
|
-
|
|
26381
|
-
|
|
26382
|
-
|
|
26383
|
-
|
|
26384
|
-
|
|
26385
|
-
|
|
26386
|
-
|
|
26387
|
-
|
|
26388
|
-
|
|
26242
|
+
};
|
|
26243
|
+
|
|
26244
|
+
// src/app/agent/core/llm/llm_errors.ts
|
|
26245
|
+
function isContextWindowValidationError(error) {
|
|
26246
|
+
const rawMessage = error instanceof Error ? error.message : typeof error === "string" ? error : "";
|
|
26247
|
+
const lower = rawMessage.toLowerCase();
|
|
26248
|
+
return lower.includes("vllmvalidationerror") || lower.includes("maximum context length") || lower.includes("input prompt contains at least") || lower.includes("requested 0 output tokens") || lower.includes("context length");
|
|
26249
|
+
}
|
|
26250
|
+
function formatLlmUiError(error) {
|
|
26251
|
+
const rawMessage = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error during LLM request.";
|
|
26252
|
+
const lower = rawMessage.toLowerCase();
|
|
26253
|
+
let message2 = "Ocorreu um erro inesperado ao contactar o modelo.";
|
|
26254
|
+
if (lower.includes("timeout") || lower.includes("etimedout") || lower.includes("upstream_timeout")) {
|
|
26255
|
+
message2 = "O BluMa demorou demasiado a responder.";
|
|
26256
|
+
} else if (lower.includes("connection") || lower.includes("econnrefused") || lower.includes("ehostunreach") || lower.includes("enotfound")) {
|
|
26257
|
+
message2 = "N\xE3o foi poss\xEDvel conectar ao servi\xE7o do modelo.";
|
|
26258
|
+
} else if (lower.includes("401") || lower.includes("403") || lower.includes("unauthorized") || lower.includes("forbidden")) {
|
|
26259
|
+
message2 = "Falha de autentica\xE7\xE3o/autoriza\xE7\xE3o ao contactar o modelo.";
|
|
26260
|
+
} else if (lower.includes("api")) {
|
|
26261
|
+
message2 = "Erro de comunica\xE7\xE3o com a API do modelo.";
|
|
26262
|
+
}
|
|
26263
|
+
return {
|
|
26264
|
+
message: message2,
|
|
26265
|
+
rawMessage
|
|
26266
|
+
};
|
|
26267
|
+
}
|
|
26268
|
+
|
|
26269
|
+
// src/app/agent/core/llm/tool_call_normalizer.ts
|
|
26270
|
+
import { randomUUID } from "crypto";
|
|
26271
|
+
var ToolCallNormalizer = class {
|
|
26272
|
+
/**
|
|
26273
|
+
* Com tool_calls e sem texto visível: content deve ser null (API OpenAI-compatible), nunca "" nem undefined.
|
|
26274
|
+
*/
|
|
26275
|
+
static assistantContentWithToolCalls(content) {
|
|
26276
|
+
if (content === void 0 || content === null) return null;
|
|
26277
|
+
if (typeof content === "string") return content.trim() === "" ? null : content;
|
|
26278
|
+
return null;
|
|
26279
|
+
}
|
|
26280
|
+
/**
|
|
26281
|
+
* Normaliza a mensagem do assistant, convertendo diferentes formatos de tool calls
|
|
26282
|
+
*/
|
|
26283
|
+
static normalizeAssistantMessage(message2) {
|
|
26284
|
+
if (message2.tool_calls && this.isOpenAIFormat(message2.tool_calls)) {
|
|
26285
|
+
return {
|
|
26286
|
+
...message2,
|
|
26287
|
+
content: this.assistantContentWithToolCalls(message2.content)
|
|
26288
|
+
};
|
|
26389
26289
|
}
|
|
26390
|
-
|
|
26391
|
-
|
|
26392
|
-
|
|
26393
|
-
|
|
26394
|
-
|
|
26290
|
+
const toolCalls = this.extractToolCalls(message2);
|
|
26291
|
+
if (toolCalls.length > 0) {
|
|
26292
|
+
return {
|
|
26293
|
+
role: message2.role || "assistant",
|
|
26294
|
+
content: this.assistantContentWithToolCalls(message2.content),
|
|
26295
|
+
tool_calls: toolCalls
|
|
26296
|
+
};
|
|
26297
|
+
}
|
|
26298
|
+
return message2;
|
|
26299
|
+
}
|
|
26300
|
+
/**
|
|
26301
|
+
* Verifica se já está no formato OpenAI
|
|
26302
|
+
*/
|
|
26303
|
+
static isOpenAIFormat(toolCalls) {
|
|
26304
|
+
if (!Array.isArray(toolCalls) || toolCalls.length === 0) return false;
|
|
26305
|
+
const firstCall = toolCalls[0];
|
|
26306
|
+
return typeof firstCall.id === "string" && firstCall.type === "function" && typeof firstCall.function?.name === "string" && typeof firstCall.function?.arguments === "string";
|
|
26307
|
+
}
|
|
26308
|
+
/**
|
|
26309
|
+
* Extrai tool calls de diversos formatos possíveis
|
|
26310
|
+
*/
|
|
26311
|
+
static extractToolCalls(message2) {
|
|
26312
|
+
const results = [];
|
|
26313
|
+
if (message2.tool_calls && Array.isArray(message2.tool_calls)) {
|
|
26314
|
+
for (const call of message2.tool_calls) {
|
|
26315
|
+
const normalized = this.normalizeToolCall(call);
|
|
26316
|
+
if (normalized) results.push(normalized);
|
|
26317
|
+
}
|
|
26318
|
+
}
|
|
26319
|
+
if (typeof message2.content === "string" && message2.content.trim()) {
|
|
26320
|
+
const extracted = this.extractFromContent(message2.content);
|
|
26321
|
+
results.push(...extracted);
|
|
26322
|
+
}
|
|
26323
|
+
if (message2.function_call) {
|
|
26324
|
+
const normalized = this.normalizeToolCall(message2.function_call);
|
|
26325
|
+
if (normalized) results.push(normalized);
|
|
26326
|
+
}
|
|
26327
|
+
return results;
|
|
26328
|
+
}
|
|
26329
|
+
/**
|
|
26330
|
+
* Normaliza um único tool call para o formato OpenAI
|
|
26331
|
+
*/
|
|
26332
|
+
static normalizeToolCall(call) {
|
|
26333
|
+
try {
|
|
26334
|
+
if (call.id && call.function?.name) {
|
|
26335
|
+
return {
|
|
26336
|
+
id: call.id,
|
|
26337
|
+
type: "function",
|
|
26338
|
+
function: {
|
|
26339
|
+
name: call.function.name,
|
|
26340
|
+
arguments: typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments)
|
|
26341
|
+
}
|
|
26342
|
+
};
|
|
26343
|
+
}
|
|
26344
|
+
if (call.name) {
|
|
26345
|
+
return {
|
|
26346
|
+
id: call.id || randomUUID(),
|
|
26347
|
+
type: "function",
|
|
26348
|
+
function: {
|
|
26349
|
+
name: call.name,
|
|
26350
|
+
arguments: typeof call.arguments === "string" ? call.arguments : JSON.stringify(call.arguments || {})
|
|
26351
|
+
}
|
|
26352
|
+
};
|
|
26353
|
+
}
|
|
26354
|
+
if (call.function && typeof call.function === "object") {
|
|
26355
|
+
return {
|
|
26356
|
+
id: call.id || randomUUID(),
|
|
26357
|
+
type: "function",
|
|
26358
|
+
function: {
|
|
26359
|
+
name: call.function.name,
|
|
26360
|
+
arguments: typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments || {})
|
|
26361
|
+
}
|
|
26362
|
+
};
|
|
26363
|
+
}
|
|
26364
|
+
return null;
|
|
26365
|
+
} catch (error) {
|
|
26366
|
+
console.error("Error normalizing tool call:", error, call);
|
|
26367
|
+
return null;
|
|
26368
|
+
}
|
|
26369
|
+
}
|
|
26370
|
+
/**
|
|
26371
|
+
* Extrai tool calls do content (pode estar em markdown, JSON, etc)
|
|
26372
|
+
*/
|
|
26373
|
+
static extractFromContent(content) {
|
|
26374
|
+
const results = [];
|
|
26375
|
+
const cleanContent2 = content.replace(/```(?:json)?\s*([\s\S]*?)```/g, "$1");
|
|
26376
|
+
const jsonMatches = this.extractJsonObjects(cleanContent2);
|
|
26377
|
+
for (const jsonStr of jsonMatches) {
|
|
26378
|
+
try {
|
|
26379
|
+
const parsed = JSON.parse(jsonStr);
|
|
26380
|
+
if (Array.isArray(parsed)) {
|
|
26381
|
+
for (const call of parsed) {
|
|
26382
|
+
const normalized = this.normalizeToolCall(call);
|
|
26383
|
+
if (normalized) results.push(normalized);
|
|
26384
|
+
}
|
|
26385
|
+
} else if (parsed.name || parsed.function) {
|
|
26386
|
+
const normalized = this.normalizeToolCall(parsed);
|
|
26387
|
+
if (normalized) results.push(normalized);
|
|
26388
|
+
} else if (parsed.tool_calls && Array.isArray(parsed.tool_calls)) {
|
|
26389
|
+
for (const call of parsed.tool_calls) {
|
|
26390
|
+
const normalized = this.normalizeToolCall(call);
|
|
26391
|
+
if (normalized) results.push(normalized);
|
|
26392
|
+
}
|
|
26393
|
+
}
|
|
26394
|
+
} catch (e) {
|
|
26395
|
+
}
|
|
26396
|
+
}
|
|
26397
|
+
return results;
|
|
26398
|
+
}
|
|
26399
|
+
/**
|
|
26400
|
+
* Extrai objetos JSON de uma string (suporta múltiplos objetos)
|
|
26401
|
+
*/
|
|
26402
|
+
static extractJsonObjects(text) {
|
|
26403
|
+
const results = [];
|
|
26404
|
+
let depth = 0;
|
|
26405
|
+
let start = -1;
|
|
26406
|
+
for (let i = 0; i < text.length; i++) {
|
|
26407
|
+
if (text[i] === "{") {
|
|
26408
|
+
if (depth === 0) start = i;
|
|
26409
|
+
depth++;
|
|
26410
|
+
} else if (text[i] === "}") {
|
|
26411
|
+
depth--;
|
|
26412
|
+
if (depth === 0 && start !== -1) {
|
|
26413
|
+
results.push(text.substring(start, i + 1));
|
|
26414
|
+
start = -1;
|
|
26415
|
+
}
|
|
26416
|
+
}
|
|
26417
|
+
}
|
|
26418
|
+
return results;
|
|
26419
|
+
}
|
|
26420
|
+
/**
|
|
26421
|
+
* Valida se um tool call normalizado é válido
|
|
26422
|
+
* Validação dupla:
|
|
26423
|
+
* 1. JSON dos argumentos é válido
|
|
26424
|
+
* 2. A ferramenta existe no catálogo de ferramentas nativas
|
|
26425
|
+
*/
|
|
26426
|
+
static isValidToolCall(call) {
|
|
26427
|
+
if (!(call.id && call.type === "function" && call.function?.name && typeof call.function.arguments === "string")) {
|
|
26428
|
+
return false;
|
|
26429
|
+
}
|
|
26430
|
+
try {
|
|
26431
|
+
JSON.parse(call.function.arguments);
|
|
26432
|
+
} catch {
|
|
26433
|
+
return false;
|
|
26434
|
+
}
|
|
26435
|
+
const toolMetadata = getNativeToolMetadata(call.function.name);
|
|
26436
|
+
if (!toolMetadata) {
|
|
26437
|
+
return false;
|
|
26438
|
+
}
|
|
26439
|
+
return true;
|
|
26440
|
+
}
|
|
26441
|
+
};
|
|
26442
|
+
|
|
26443
|
+
// src/app/agent/runtime/tool_orchestration.ts
|
|
26444
|
+
var PARALLEL_SAFE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
26445
|
+
"ls_tool",
|
|
26446
|
+
"read_file_lines",
|
|
26447
|
+
"count_file_lines",
|
|
26448
|
+
"find_by_name",
|
|
26449
|
+
"grep_search",
|
|
26450
|
+
"view_file_outline",
|
|
26451
|
+
"read_artifact",
|
|
26452
|
+
"list_agents",
|
|
26453
|
+
"list_mcp_resources",
|
|
26454
|
+
"read_mcp_resource",
|
|
26455
|
+
"todo"
|
|
26456
|
+
]);
|
|
26457
|
+
function toolEligibleForParallelRead(toolName, sessionId) {
|
|
26458
|
+
const name = String(toolName || "").trim();
|
|
26459
|
+
if (!name || !PARALLEL_SAFE_TOOL_NAMES.has(name)) return false;
|
|
26460
|
+
if (!decideToolExecution(name, void 0, sessionId).autoApprove) return false;
|
|
26461
|
+
const meta = getNativeToolMetadata(name);
|
|
26462
|
+
return meta?.riskLevel === "safe";
|
|
26463
|
+
}
|
|
26464
|
+
|
|
26465
|
+
// src/app/agent/bluma/core/bluma_turn_coordinator.ts
|
|
26466
|
+
init_logger();
|
|
26467
|
+
var BluMaTurnCoordinator = class {
|
|
26468
|
+
constructor(deps) {
|
|
26469
|
+
this.deps = deps;
|
|
26470
|
+
}
|
|
26471
|
+
emptyAssistantReplySteps = 0;
|
|
26472
|
+
invalidToolCallRetrySteps = 0;
|
|
26473
|
+
turnLog = logger.child("turn_coordinator");
|
|
26474
|
+
resetTurnState() {
|
|
26475
|
+
this.emptyAssistantReplySteps = 0;
|
|
26476
|
+
this.invalidToolCallRetrySteps = 0;
|
|
26477
|
+
}
|
|
26478
|
+
async handleToolResponse(decisionData) {
|
|
26479
|
+
const calls = decisionData.tool_calls;
|
|
26480
|
+
if (!calls?.length) return;
|
|
26481
|
+
const shouldContinueConversation = decisionData.continueConversation !== false;
|
|
26482
|
+
if (decisionData.type === "user_decision_decline") {
|
|
26483
|
+
const declineMsg = "The system rejected this action. Verify that the command you are executing contributes to the tasks intent and try again.";
|
|
26484
|
+
for (const toolCall of calls) {
|
|
26485
|
+
const toolName = toolCall.function?.name || "tool";
|
|
26486
|
+
let args = {};
|
|
26395
26487
|
try {
|
|
26396
26488
|
if (typeof toolCall.function?.arguments === "string") {
|
|
26397
26489
|
args = JSON.parse(toolCall.function.arguments);
|
|
@@ -26401,276 +26493,254 @@ var BluMaAgent = class _BluMaAgent {
|
|
|
26401
26493
|
} catch {
|
|
26402
26494
|
args = {};
|
|
26403
26495
|
}
|
|
26404
|
-
|
|
26405
|
-
setToolResultStatus(String(toolCall.id), "error");
|
|
26406
|
-
}
|
|
26407
|
-
this.eventBus.emit("backend_message", {
|
|
26496
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26408
26497
|
type: "tool_result",
|
|
26409
26498
|
tool_call_id: String(toolCall.id),
|
|
26410
26499
|
tool_name: toolName,
|
|
26411
26500
|
arguments: args,
|
|
26412
26501
|
result: JSON.stringify({ status: "error", error: declineMsg }, null, 2)
|
|
26413
26502
|
});
|
|
26414
|
-
this.history.push({
|
|
26503
|
+
this.deps.history.push({
|
|
26415
26504
|
role: "tool",
|
|
26416
26505
|
tool_call_id: toolCall.id,
|
|
26417
26506
|
content: JSON.stringify({ status: "error", error: declineMsg }, null, 2)
|
|
26418
26507
|
});
|
|
26419
26508
|
}
|
|
26420
|
-
this.persistSession();
|
|
26421
|
-
if (shouldContinueConversation && !this.isInterrupted) {
|
|
26422
|
-
await this.
|
|
26509
|
+
this.deps.persistSession();
|
|
26510
|
+
if (shouldContinueConversation && !this.deps.isInterrupted()) {
|
|
26511
|
+
await this.continueConversation();
|
|
26423
26512
|
}
|
|
26424
26513
|
return;
|
|
26425
26514
|
}
|
|
26426
26515
|
if (decisionData.type !== "user_decision_execute") {
|
|
26427
26516
|
return;
|
|
26428
26517
|
}
|
|
26429
|
-
setTaskToolSessionContext(this.sessionId);
|
|
26430
26518
|
let shouldContinue = true;
|
|
26431
26519
|
let i = 0;
|
|
26432
|
-
while (i < calls.length && shouldContinue && !this.isInterrupted) {
|
|
26520
|
+
while (i < calls.length && shouldContinue && !this.deps.isInterrupted()) {
|
|
26433
26521
|
const name = calls[i].function.name;
|
|
26434
|
-
if (!toolEligibleForParallelRead(name, this.sessionId)) {
|
|
26435
|
-
shouldContinue = await this.executeApprovedToolInvocation(calls[i], decisionData);
|
|
26522
|
+
if (!toolEligibleForParallelRead(name, this.deps.sessionId)) {
|
|
26523
|
+
shouldContinue = await this.deps.toolRunner.executeApprovedToolInvocation(calls[i], decisionData);
|
|
26436
26524
|
i += 1;
|
|
26437
26525
|
continue;
|
|
26438
26526
|
}
|
|
26439
26527
|
let j = i;
|
|
26440
|
-
while (j < calls.length && toolEligibleForParallelRead(calls[j].function.name, this.sessionId)) {
|
|
26528
|
+
while (j < calls.length && toolEligibleForParallelRead(calls[j].function.name, this.deps.sessionId)) {
|
|
26441
26529
|
j += 1;
|
|
26442
26530
|
}
|
|
26443
26531
|
const slice = calls.slice(i, j);
|
|
26444
26532
|
if (slice.length === 1) {
|
|
26445
|
-
shouldContinue = await this.executeApprovedToolInvocation(slice[0], decisionData);
|
|
26533
|
+
shouldContinue = await this.deps.toolRunner.executeApprovedToolInvocation(slice[0], decisionData);
|
|
26446
26534
|
} else {
|
|
26447
|
-
shouldContinue = await this.executeParallelReadBatch(slice, decisionData);
|
|
26535
|
+
shouldContinue = await this.deps.toolRunner.executeParallelReadBatch(slice, decisionData);
|
|
26448
26536
|
}
|
|
26449
26537
|
i = j;
|
|
26450
26538
|
}
|
|
26451
|
-
if (shouldContinueConversation && shouldContinue && !this.isInterrupted) {
|
|
26452
|
-
await this.
|
|
26539
|
+
if (shouldContinueConversation && shouldContinue && !this.deps.isInterrupted()) {
|
|
26540
|
+
await this.continueConversation();
|
|
26453
26541
|
}
|
|
26454
26542
|
}
|
|
26455
|
-
async
|
|
26543
|
+
async continueConversation() {
|
|
26456
26544
|
try {
|
|
26457
|
-
if (
|
|
26458
|
-
|
|
26545
|
+
if (this.deps.isInterrupted()) {
|
|
26546
|
+
this.deps.eventBus.emit("backend_message", { type: "info", message: "Task Canceled." });
|
|
26547
|
+
return;
|
|
26459
26548
|
}
|
|
26460
|
-
|
|
26461
|
-
|
|
26549
|
+
const mailboxUpdate = await this.pollWorkerMailbox();
|
|
26550
|
+
if (mailboxUpdate && mailboxUpdate.followUp) {
|
|
26551
|
+
this.deps.history.push({
|
|
26552
|
+
role: "user",
|
|
26553
|
+
content: mailboxUpdate.followUp.message
|
|
26554
|
+
});
|
|
26555
|
+
this.deps.persistSession();
|
|
26556
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26557
|
+
type: "info",
|
|
26558
|
+
message: `Received follow-up from coordinator (priority: ${mailboxUpdate.followUp.priority})`
|
|
26559
|
+
});
|
|
26462
26560
|
}
|
|
26463
|
-
const
|
|
26464
|
-
|
|
26465
|
-
|
|
26466
|
-
|
|
26467
|
-
|
|
26468
|
-
|
|
26469
|
-
|
|
26470
|
-
|
|
26471
|
-
|
|
26472
|
-
|
|
26561
|
+
const sanitized = sanitizeConversationForProvider(this.deps.history);
|
|
26562
|
+
if (sanitized.issues.length > 0) {
|
|
26563
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26564
|
+
type: "info",
|
|
26565
|
+
message: `Sanitizing ${sanitized.issues.length} messages from history`
|
|
26566
|
+
});
|
|
26567
|
+
this.deps.history.splice(0, this.deps.history.length, ...sanitized.messages);
|
|
26568
|
+
this.deps.persistSession();
|
|
26569
|
+
}
|
|
26570
|
+
this.turnLog.debug("Conversation sanitized", {
|
|
26571
|
+
history_length: this.deps.history.length,
|
|
26572
|
+
sanitized_length: sanitized.messages.length,
|
|
26573
|
+
issue_count: sanitized.issues.length,
|
|
26574
|
+
issues: sanitized.issues.slice(0, 5),
|
|
26575
|
+
history_preview: summarizeHistoryForLog(this.deps.history, 6)
|
|
26576
|
+
});
|
|
26577
|
+
const modelName = this.deps.llm.getModelName ? this.deps.llm.getModelName() : "auto";
|
|
26578
|
+
const baseTokenBudget = getContextInputBudgetForModel(modelName);
|
|
26579
|
+
const retryTokenBudget = Math.max(48e3, Math.floor(baseTokenBudget * 0.75));
|
|
26580
|
+
const tokenBudgets = [baseTokenBudget, retryTokenBudget];
|
|
26581
|
+
const llmService = this.deps.llm;
|
|
26582
|
+
for (let attempt = 0; attempt < tokenBudgets.length; attempt += 1) {
|
|
26583
|
+
const tokenBudget = tokenBudgets[attempt];
|
|
26584
|
+
try {
|
|
26585
|
+
const { messages: contextWindow, prunedHistory } = await this.deps.compressor.buildContextWindow(
|
|
26586
|
+
this.deps.history,
|
|
26587
|
+
this.deps.llm,
|
|
26588
|
+
this.deps.getLlmUserContext(),
|
|
26589
|
+
{
|
|
26590
|
+
tokenBudget,
|
|
26591
|
+
toolDefinitions: this.deps.mcpClient.getAvailableTools()
|
|
26592
|
+
}
|
|
26593
|
+
);
|
|
26594
|
+
this.turnLog.debug("Context window prepared", {
|
|
26595
|
+
history_length: this.deps.history.length,
|
|
26596
|
+
context_window_length: contextWindow.length,
|
|
26597
|
+
pruned_history_length: prunedHistory?.length ?? null,
|
|
26598
|
+
token_budget: tokenBudget,
|
|
26599
|
+
context_preview: summarizeHistoryForLog(contextWindow, 8)
|
|
26600
|
+
});
|
|
26601
|
+
if (prunedHistory) {
|
|
26602
|
+
this.deps.history.splice(0, this.deps.history.length, ...prunedHistory);
|
|
26603
|
+
}
|
|
26604
|
+
this.deps.persistSession();
|
|
26605
|
+
if (typeof llmService.chatCompletionStream === "function") {
|
|
26606
|
+
await this.handleStreamingResponse(contextWindow);
|
|
26607
|
+
} else {
|
|
26608
|
+
await this.handleNonStreamingResponse(contextWindow);
|
|
26609
|
+
}
|
|
26610
|
+
return;
|
|
26611
|
+
} catch (error) {
|
|
26612
|
+
const retryable = isContextWindowValidationError(error);
|
|
26613
|
+
const hasRetryLeft = attempt < tokenBudgets.length - 1;
|
|
26614
|
+
if (retryable && hasRetryLeft) {
|
|
26615
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26616
|
+
type: "info",
|
|
26617
|
+
message: "The model rejected the prompt size. Retrying with a smaller context window."
|
|
26618
|
+
});
|
|
26619
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26620
|
+
type: "log",
|
|
26621
|
+
message: "Context validation retry",
|
|
26622
|
+
payload: `attempt=${attempt + 1} budget=${tokenBudget} nextBudget=${tokenBudgets[attempt + 1]}`
|
|
26623
|
+
});
|
|
26624
|
+
continue;
|
|
26625
|
+
}
|
|
26626
|
+
throw error;
|
|
26627
|
+
}
|
|
26473
26628
|
}
|
|
26474
|
-
|
|
26475
|
-
|
|
26476
|
-
|
|
26477
|
-
|
|
26629
|
+
} catch (error) {
|
|
26630
|
+
this.deps.persistSessionSync();
|
|
26631
|
+
const uiError = formatLlmUiError(error);
|
|
26632
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26633
|
+
type: "error",
|
|
26634
|
+
message: "Communication error with the BluMa"
|
|
26635
|
+
});
|
|
26636
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26637
|
+
type: "log",
|
|
26638
|
+
message: "LLM request failed",
|
|
26639
|
+
payload: uiError.rawMessage
|
|
26640
|
+
});
|
|
26641
|
+
await this.deps.notifyFactorTurnEndIfNeeded("llm_error");
|
|
26642
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26643
|
+
type: "done",
|
|
26644
|
+
status: "error",
|
|
26645
|
+
canRecover: true,
|
|
26646
|
+
message: "Communication error with the BluMa"
|
|
26647
|
+
});
|
|
26648
|
+
} finally {
|
|
26649
|
+
this.deps.persistSession();
|
|
26478
26650
|
}
|
|
26479
26651
|
}
|
|
26480
|
-
_generateFileWritePreview(toolArgs) {
|
|
26481
|
-
try {
|
|
26482
|
-
const content = typeof toolArgs?.content === "string" ? toolArgs.content : String(toolArgs?.content ?? "");
|
|
26483
|
-
if (!content) {
|
|
26484
|
-
return "(No content)";
|
|
26485
|
-
}
|
|
26486
|
-
const normalized = content.replace(/\r\n/g, "\n");
|
|
26487
|
-
const lines = normalized.split("\n");
|
|
26488
|
-
const maxPreviewLines = 20;
|
|
26489
|
-
const visibleLines = lines.slice(0, maxPreviewLines);
|
|
26490
|
-
const hiddenLines = lines.length - visibleLines.length;
|
|
26491
|
-
return hiddenLines > 0 ? `${visibleLines.join("\n")}
|
|
26492
|
-
\u2026 +${hiddenLines} more lines` : normalized;
|
|
26493
|
-
} catch (e) {
|
|
26494
|
-
return `An unexpected error occurred while generating the file write preview: ${e.message}`;
|
|
26495
|
-
}
|
|
26496
|
-
}
|
|
26497
|
-
getLlmUserContext() {
|
|
26498
|
-
if (!this.activeTurnContext) {
|
|
26499
|
-
throw new Error("BluMaAgent: activeTurnContext ausente (processTurn n\xE3o iniciou o turno).");
|
|
26500
|
-
}
|
|
26501
|
-
return this.activeTurnContext;
|
|
26502
|
-
}
|
|
26503
|
-
/**
|
|
26504
|
-
* Um único POST ao Factor Router por turno (`notifyFactorRouterTurnEnd`).
|
|
26505
|
-
* `reason` típicos: `message_result`, `user_interrupt`, `empty_reply_exhausted`, `llm_error`.
|
|
26506
|
-
*/
|
|
26507
|
-
async notifyFactorTurnEndIfNeeded(reason) {
|
|
26508
|
-
if (this.factorRouterTurnClosed || !this.activeTurnContext) return;
|
|
26509
|
-
this.factorRouterTurnClosed = true;
|
|
26510
|
-
const ctx = this.activeTurnContext;
|
|
26511
|
-
await notifyFactorRouterTurnEnd({
|
|
26512
|
-
turnId: ctx.turnId,
|
|
26513
|
-
userContext: ctx,
|
|
26514
|
-
reason
|
|
26515
|
-
});
|
|
26516
|
-
}
|
|
26517
|
-
/** Fecho explícito de turno (útil para workers antes de process.exit). */
|
|
26518
|
-
async closeActiveTurn(reason = "worker_exit") {
|
|
26519
|
-
await this.notifyFactorTurnEndIfNeeded(reason);
|
|
26520
|
-
}
|
|
26521
|
-
async _continueConversation() {
|
|
26522
|
-
try {
|
|
26523
|
-
if (this.isInterrupted) {
|
|
26524
|
-
this.eventBus.emit("backend_message", { type: "info", message: "Task Canceled." });
|
|
26525
|
-
return;
|
|
26526
|
-
}
|
|
26527
|
-
const mailboxUpdate = await this._pollWorkerMailbox();
|
|
26528
|
-
if (mailboxUpdate && mailboxUpdate.followUp) {
|
|
26529
|
-
this.history.push({
|
|
26530
|
-
role: "user",
|
|
26531
|
-
content: mailboxUpdate.followUp.message
|
|
26532
|
-
});
|
|
26533
|
-
this.persistSession();
|
|
26534
|
-
this.eventBus.emit("backend_message", {
|
|
26535
|
-
type: "info",
|
|
26536
|
-
message: `Received follow-up from coordinator (priority: ${mailboxUpdate.followUp.priority})`
|
|
26537
|
-
});
|
|
26538
|
-
}
|
|
26539
|
-
const sanitized = sanitizeConversationForProvider(this.history);
|
|
26540
|
-
if (sanitized.issues.length > 0) {
|
|
26541
|
-
this.eventBus.emit("backend_message", {
|
|
26542
|
-
type: "info",
|
|
26543
|
-
message: `Sanitizing ${sanitized.issues.length} messages from history`
|
|
26544
|
-
});
|
|
26545
|
-
this.history = sanitized.messages;
|
|
26546
|
-
this.persistSession();
|
|
26547
|
-
}
|
|
26548
|
-
const modelName = this.llm.getModelName ? this.llm.getModelName() : "auto";
|
|
26549
|
-
const tokenBudget = getContextInputBudgetForModel(modelName);
|
|
26550
|
-
const { messages: contextWindow, prunedHistory } = await this.compressor.buildContextWindow(
|
|
26551
|
-
this.history,
|
|
26552
|
-
this.llm,
|
|
26553
|
-
this.getLlmUserContext(),
|
|
26554
|
-
{
|
|
26555
|
-
tokenBudget,
|
|
26556
|
-
toolDefinitions: this.mcpClient.getAvailableTools()
|
|
26557
|
-
}
|
|
26558
|
-
);
|
|
26559
|
-
if (prunedHistory) {
|
|
26560
|
-
this.history = prunedHistory;
|
|
26561
|
-
}
|
|
26562
|
-
this.persistSession();
|
|
26563
|
-
const llmService = this.llm;
|
|
26564
|
-
if (typeof llmService.chatCompletionStream === "function") {
|
|
26565
|
-
await this._handleStreamingResponse(contextWindow);
|
|
26566
|
-
} else {
|
|
26567
|
-
await this._handleNonStreamingResponse(contextWindow);
|
|
26568
|
-
}
|
|
26569
|
-
} catch (error) {
|
|
26570
|
-
this.persistSessionSync();
|
|
26571
|
-
this.history.push({
|
|
26572
|
-
role: "assistant",
|
|
26573
|
-
content: "[<=================== The conversation will continue from here ==================>]"
|
|
26574
|
-
});
|
|
26575
|
-
this.persistSessionSync();
|
|
26576
|
-
const uiError = formatLlmUiError(error);
|
|
26577
|
-
this.eventBus.emit("backend_message", {
|
|
26578
|
-
type: "error",
|
|
26579
|
-
message: "Communication error with the BluMa"
|
|
26580
|
-
//details: 'The connection was temporarily interrupted. Your history has been safely saved.',
|
|
26581
|
-
//hint: 'You can continue from where you left off. No information was lost.',
|
|
26582
|
-
});
|
|
26583
|
-
this.eventBus.emit("backend_message", {
|
|
26584
|
-
type: "log",
|
|
26585
|
-
message: "LLM request failed",
|
|
26586
|
-
payload: uiError.rawMessage
|
|
26587
|
-
});
|
|
26588
|
-
await this.notifyFactorTurnEndIfNeeded("llm_error");
|
|
26589
|
-
this.eventBus.emit("backend_message", {
|
|
26590
|
-
type: "done",
|
|
26591
|
-
status: "error",
|
|
26592
|
-
canRecover: true,
|
|
26593
|
-
message: "Communication error with the BluMa"
|
|
26594
|
-
});
|
|
26595
|
-
} finally {
|
|
26596
|
-
this.persistSession();
|
|
26597
|
-
}
|
|
26598
|
-
}
|
|
26599
|
-
/**
|
|
26600
|
-
* O modelo devolveu mensagem sem texto útil nem tool_calls (só raciocínio interno).
|
|
26601
|
-
* Nudge único + teto de tentativas para não ficar preso em "Thinking..." como no olá.
|
|
26602
|
-
*/
|
|
26603
26652
|
async continueAfterEmptyAssistantResponse() {
|
|
26604
26653
|
this.emptyAssistantReplySteps += 1;
|
|
26605
26654
|
if (this.emptyAssistantReplySteps === 1) {
|
|
26606
|
-
this.history.push({
|
|
26655
|
+
this.deps.history.push({
|
|
26607
26656
|
role: "system",
|
|
26608
26657
|
content: `You did not call any tool and produced no user-visible reply. Respond using the message tool with message_type "result". Keep it concise and in the user's language.`
|
|
26609
26658
|
});
|
|
26610
26659
|
} else if (this.emptyAssistantReplySteps === 2) {
|
|
26611
|
-
this.history.push({
|
|
26660
|
+
this.deps.history.push({
|
|
26612
26661
|
role: "system",
|
|
26613
26662
|
content: 'Retry: call message tool with message_type "result" or use a tool.'
|
|
26614
26663
|
});
|
|
26615
26664
|
} else if (this.emptyAssistantReplySteps >= 6) {
|
|
26616
|
-
this.eventBus.emit("backend_message", {
|
|
26665
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26617
26666
|
type: "info",
|
|
26618
26667
|
message: "The BluMa is having difficulty processing yor task."
|
|
26619
26668
|
});
|
|
26620
|
-
await this.notifyFactorTurnEndIfNeeded("empty_reply_exhausted");
|
|
26621
|
-
this.eventBus.emit("backend_message", { type: "done", status: "failed" });
|
|
26669
|
+
await this.deps.notifyFactorTurnEndIfNeeded("empty_reply_exhausted");
|
|
26670
|
+
this.deps.eventBus.emit("backend_message", { type: "done", status: "failed" });
|
|
26622
26671
|
this.emptyAssistantReplySteps = 0;
|
|
26623
26672
|
return;
|
|
26624
26673
|
}
|
|
26625
|
-
await this.
|
|
26674
|
+
await this.continueConversation();
|
|
26626
26675
|
}
|
|
26627
|
-
|
|
26628
|
-
|
|
26629
|
-
|
|
26630
|
-
|
|
26631
|
-
const hasContent = content.trim().length > 0;
|
|
26632
|
-
if (!hasContent && !hasTools) {
|
|
26633
|
-
return;
|
|
26676
|
+
async handleInvalidToolCallRetry(message2) {
|
|
26677
|
+
this.invalidToolCallRetrySteps += 1;
|
|
26678
|
+
if (this.deps.history[this.deps.history.length - 1] === message2) {
|
|
26679
|
+
this.deps.history.pop();
|
|
26634
26680
|
}
|
|
26635
|
-
this.
|
|
26636
|
-
|
|
26637
|
-
|
|
26681
|
+
if (this.invalidToolCallRetrySteps >= 3) {
|
|
26682
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26683
|
+
type: "error",
|
|
26684
|
+
message: "The model kept returning invalid tool calls. Closing the turn to avoid a retry loop."
|
|
26685
|
+
});
|
|
26686
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26687
|
+
type: "log",
|
|
26688
|
+
message: "Invalid tool call retry limit reached",
|
|
26689
|
+
payload: String(this.invalidToolCallRetrySteps)
|
|
26690
|
+
});
|
|
26691
|
+
await this.deps.notifyFactorTurnEndIfNeeded("invalid_tool_calls_exhausted");
|
|
26692
|
+
this.deps.eventBus.emit("backend_message", { type: "done", status: "failed" });
|
|
26693
|
+
this.invalidToolCallRetrySteps = 0;
|
|
26694
|
+
return;
|
|
26638
26695
|
}
|
|
26639
|
-
|
|
26640
|
-
|
|
26696
|
+
this.deps.history.push({
|
|
26697
|
+
role: "system",
|
|
26698
|
+
content: "Previous assistant tool_calls were invalid. Retry with valid JSON arguments only, or answer without tools."
|
|
26699
|
+
});
|
|
26700
|
+
this.deps.persistSession();
|
|
26701
|
+
await this.continueConversation();
|
|
26641
26702
|
}
|
|
26642
|
-
async
|
|
26643
|
-
const llmService = this.llm;
|
|
26703
|
+
async handleStreamingResponse(contextWindow) {
|
|
26704
|
+
const llmService = this.deps.llm;
|
|
26644
26705
|
let accumulatedContent = "";
|
|
26645
26706
|
let toolCalls;
|
|
26646
26707
|
let hasEmittedStart = false;
|
|
26708
|
+
let currentStreamPhase = "idle";
|
|
26647
26709
|
const stream = llmService.chatCompletionStream({
|
|
26648
26710
|
messages: contextWindow,
|
|
26649
26711
|
temperature: 0,
|
|
26650
|
-
tools: this.mcpClient.getAvailableTools(),
|
|
26712
|
+
tools: this.deps.mcpClient.getAvailableTools(),
|
|
26651
26713
|
parallel_tool_calls: true,
|
|
26652
|
-
userContext: this.getLlmUserContext()
|
|
26714
|
+
userContext: this.deps.getLlmUserContext()
|
|
26653
26715
|
});
|
|
26654
26716
|
for await (const chunk of stream) {
|
|
26655
|
-
if (this.isInterrupted) {
|
|
26656
|
-
this.eventBus.emit("stream_end", {});
|
|
26657
|
-
this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
|
|
26717
|
+
if (this.deps.isInterrupted()) {
|
|
26718
|
+
this.deps.eventBus.emit("stream_end", {});
|
|
26719
|
+
this.deps.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
|
|
26658
26720
|
return;
|
|
26659
26721
|
}
|
|
26660
26722
|
if (chunk.reasoning) {
|
|
26661
26723
|
if (!hasEmittedStart) {
|
|
26662
|
-
this.eventBus.emit("stream_start", {});
|
|
26724
|
+
this.deps.eventBus.emit("stream_start", {});
|
|
26663
26725
|
hasEmittedStart = true;
|
|
26664
26726
|
}
|
|
26665
|
-
|
|
26727
|
+
if (currentStreamPhase !== "reasoning") {
|
|
26728
|
+
this.deps.eventBus.emit("action_status", getReasoningActionStatus());
|
|
26729
|
+
currentStreamPhase = "reasoning";
|
|
26730
|
+
}
|
|
26731
|
+
this.deps.eventBus.emit("stream_reasoning_chunk", { delta: chunk.reasoning });
|
|
26666
26732
|
}
|
|
26667
26733
|
if (chunk.delta) {
|
|
26668
26734
|
if (!hasEmittedStart) {
|
|
26669
|
-
this.eventBus.emit("stream_start", {});
|
|
26735
|
+
this.deps.eventBus.emit("stream_start", {});
|
|
26670
26736
|
hasEmittedStart = true;
|
|
26671
26737
|
}
|
|
26738
|
+
if (currentStreamPhase !== "responding") {
|
|
26739
|
+
this.deps.eventBus.emit("action_status", getRespondingActionStatus());
|
|
26740
|
+
currentStreamPhase = "responding";
|
|
26741
|
+
}
|
|
26672
26742
|
accumulatedContent += chunk.delta;
|
|
26673
|
-
this.eventBus.emit("stream_chunk", { delta: chunk.delta });
|
|
26743
|
+
this.deps.eventBus.emit("stream_chunk", { delta: chunk.delta });
|
|
26674
26744
|
}
|
|
26675
26745
|
if (chunk.tool_calls && chunk.tool_calls.length > 0) {
|
|
26676
26746
|
toolCalls = chunk.tool_calls;
|
|
@@ -26678,14 +26748,14 @@ ${editData.error.display}`;
|
|
|
26678
26748
|
const toolName = tc?.function?.name ?? "";
|
|
26679
26749
|
const argsRaw = tc?.function?.arguments ?? "{}";
|
|
26680
26750
|
if (toolName) {
|
|
26681
|
-
this.eventBus.emit("tool_stream_start", { toolName, argsRaw });
|
|
26751
|
+
this.deps.eventBus.emit("tool_stream_start", { toolName, argsRaw });
|
|
26682
26752
|
}
|
|
26683
26753
|
}
|
|
26684
26754
|
}
|
|
26685
26755
|
}
|
|
26686
26756
|
const omitAssistantFlush = Array.isArray(toolCalls) && toolCalls.some((tc) => String(tc?.function?.name ?? "").includes("message"));
|
|
26687
26757
|
if (hasEmittedStart) {
|
|
26688
|
-
this.eventBus.emit("stream_end", { omitAssistantFlush });
|
|
26758
|
+
this.deps.eventBus.emit("stream_end", { omitAssistantFlush });
|
|
26689
26759
|
}
|
|
26690
26760
|
const trimmedText = accumulatedContent.trim();
|
|
26691
26761
|
const hasToolCalls = Boolean(toolCalls && toolCalls.length > 0);
|
|
@@ -26698,7 +26768,7 @@ ${editData.error.display}`;
|
|
|
26698
26768
|
message2.tool_calls = toolCalls;
|
|
26699
26769
|
}
|
|
26700
26770
|
const normalizedMessage = ToolCallNormalizer.normalizeAssistantMessage(message2);
|
|
26701
|
-
this.history.push(normalizedMessage);
|
|
26771
|
+
this.deps.history.push(normalizedMessage);
|
|
26702
26772
|
if (normalizedMessage.tool_calls && normalizedMessage.tool_calls.length > 0) {
|
|
26703
26773
|
this.emptyAssistantReplySteps = 0;
|
|
26704
26774
|
this.invalidToolCallRetrySteps = 0;
|
|
@@ -26710,10 +26780,10 @@ ${editData.error.display}`;
|
|
|
26710
26780
|
return;
|
|
26711
26781
|
}
|
|
26712
26782
|
const autoApprovedToolCalls = validToolCalls.filter(
|
|
26713
|
-
(tc) => effectiveToolAutoApprove(tc, this.sessionId)
|
|
26783
|
+
(tc) => effectiveToolAutoApprove(tc, this.deps.sessionId)
|
|
26714
26784
|
);
|
|
26715
26785
|
const confirmationToolCalls = validToolCalls.filter(
|
|
26716
|
-
(tc) => !effectiveToolAutoApprove(tc, this.sessionId)
|
|
26786
|
+
(tc) => !effectiveToolAutoApprove(tc, this.deps.sessionId)
|
|
26717
26787
|
);
|
|
26718
26788
|
if (autoApprovedToolCalls.length > 0) {
|
|
26719
26789
|
await this.handleToolResponse({
|
|
@@ -26724,17 +26794,17 @@ ${editData.error.display}`;
|
|
|
26724
26794
|
}
|
|
26725
26795
|
if (confirmationToolCalls.length > 0) {
|
|
26726
26796
|
const toolToCall = confirmationToolCalls[0];
|
|
26727
|
-
const decision = decideToolExecution(toolToCall.function.name, void 0, this.sessionId);
|
|
26797
|
+
const decision = decideToolExecution(toolToCall.function.name, void 0, this.deps.sessionId);
|
|
26728
26798
|
const toolName = toolToCall.function.name;
|
|
26729
26799
|
let previewContent;
|
|
26730
26800
|
if (toolName === "edit_tool") {
|
|
26731
26801
|
const args = JSON.parse(toolToCall.function.arguments);
|
|
26732
|
-
previewContent = await this.
|
|
26802
|
+
previewContent = await this.deps.generateEditPreview(args);
|
|
26733
26803
|
} else if (toolName === "file_write") {
|
|
26734
26804
|
const args = JSON.parse(toolToCall.function.arguments);
|
|
26735
|
-
previewContent = this.
|
|
26805
|
+
previewContent = this.deps.generateFileWritePreview(args);
|
|
26736
26806
|
}
|
|
26737
|
-
this.eventBus.emit("backend_message", {
|
|
26807
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26738
26808
|
type: "confirmation_request",
|
|
26739
26809
|
tool_calls: confirmationToolCalls,
|
|
26740
26810
|
preview: previewContent,
|
|
@@ -26743,37 +26813,37 @@ ${editData.error.display}`;
|
|
|
26743
26813
|
}
|
|
26744
26814
|
} else if (trimmedText) {
|
|
26745
26815
|
this.emptyAssistantReplySteps = 0;
|
|
26746
|
-
this.
|
|
26747
|
-
|
|
26748
|
-
this.
|
|
26816
|
+
this.invalidToolCallRetrySteps = 0;
|
|
26817
|
+
this.deps.eventBus.emit("backend_message", { type: "assistant_message", content: accumulatedContent });
|
|
26818
|
+
await this.deps.notifyFactorTurnEndIfNeeded("assistant_text_without_tool_call");
|
|
26819
|
+
this.deps.emitTurnCompleted();
|
|
26749
26820
|
return;
|
|
26750
26821
|
} else {
|
|
26751
26822
|
await this.continueAfterEmptyAssistantResponse();
|
|
26752
26823
|
}
|
|
26753
26824
|
}
|
|
26754
|
-
async
|
|
26755
|
-
const response = await this.llm.chatCompletion({
|
|
26825
|
+
async handleNonStreamingResponse(contextWindow) {
|
|
26826
|
+
const response = await this.deps.llm.chatCompletion({
|
|
26756
26827
|
messages: contextWindow,
|
|
26757
26828
|
temperature: 0,
|
|
26758
|
-
tools: this.mcpClient.getAvailableTools(),
|
|
26829
|
+
tools: this.deps.mcpClient.getAvailableTools(),
|
|
26759
26830
|
parallel_tool_calls: true,
|
|
26760
|
-
userContext: this.getLlmUserContext()
|
|
26831
|
+
userContext: this.deps.getLlmUserContext()
|
|
26761
26832
|
});
|
|
26762
|
-
if (this.isInterrupted) {
|
|
26763
|
-
this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
|
|
26833
|
+
if (this.deps.isInterrupted()) {
|
|
26834
|
+
this.deps.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
|
|
26764
26835
|
return;
|
|
26765
26836
|
}
|
|
26766
26837
|
let message2 = response.choices[0].message;
|
|
26767
26838
|
message2 = ToolCallNormalizer.normalizeAssistantMessage(message2);
|
|
26768
26839
|
if (message2.reasoning_content || message2.reasoning) {
|
|
26769
26840
|
const reasoningText = message2.reasoning_content || message2.reasoning;
|
|
26770
|
-
this.eventBus.emit("backend_message", {
|
|
26841
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26771
26842
|
type: "reasoning",
|
|
26772
26843
|
content: typeof reasoningText === "string" ? reasoningText : JSON.stringify(reasoningText)
|
|
26773
26844
|
});
|
|
26774
26845
|
}
|
|
26775
|
-
this.
|
|
26776
|
-
this.history.push(message2);
|
|
26846
|
+
this.deps.history.push(message2);
|
|
26777
26847
|
if (message2.tool_calls && message2.tool_calls.length > 0) {
|
|
26778
26848
|
this.emptyAssistantReplySteps = 0;
|
|
26779
26849
|
this.invalidToolCallRetrySteps = 0;
|
|
@@ -26785,10 +26855,10 @@ ${editData.error.display}`;
|
|
|
26785
26855
|
return;
|
|
26786
26856
|
}
|
|
26787
26857
|
const autoApprovedToolCalls = validToolCalls.filter(
|
|
26788
|
-
(tc) => effectiveToolAutoApprove(tc, this.sessionId)
|
|
26858
|
+
(tc) => effectiveToolAutoApprove(tc, this.deps.sessionId)
|
|
26789
26859
|
);
|
|
26790
26860
|
const confirmationToolCalls = validToolCalls.filter(
|
|
26791
|
-
(tc) => !effectiveToolAutoApprove(tc, this.sessionId)
|
|
26861
|
+
(tc) => !effectiveToolAutoApprove(tc, this.deps.sessionId)
|
|
26792
26862
|
);
|
|
26793
26863
|
if (autoApprovedToolCalls.length > 0) {
|
|
26794
26864
|
await this.handleToolResponse({
|
|
@@ -26799,33 +26869,420 @@ ${editData.error.display}`;
|
|
|
26799
26869
|
}
|
|
26800
26870
|
if (confirmationToolCalls.length > 0) {
|
|
26801
26871
|
const toolToCall = confirmationToolCalls[0];
|
|
26802
|
-
const decision = decideToolExecution(toolToCall.function.name, void 0, this.sessionId);
|
|
26872
|
+
const decision = decideToolExecution(toolToCall.function.name, void 0, this.deps.sessionId);
|
|
26803
26873
|
const toolName = toolToCall.function.name;
|
|
26804
26874
|
let previewContent;
|
|
26805
26875
|
if (toolName === "edit_tool") {
|
|
26806
26876
|
const args = JSON.parse(toolToCall.function.arguments);
|
|
26807
|
-
previewContent = await this.
|
|
26877
|
+
previewContent = await this.deps.generateEditPreview(args);
|
|
26808
26878
|
} else if (toolName === "file_write") {
|
|
26809
26879
|
const args = JSON.parse(toolToCall.function.arguments);
|
|
26810
|
-
previewContent = this.
|
|
26880
|
+
previewContent = this.deps.generateFileWritePreview(args);
|
|
26811
26881
|
}
|
|
26812
|
-
this.eventBus.emit("backend_message", {
|
|
26882
|
+
this.deps.eventBus.emit("backend_message", {
|
|
26813
26883
|
type: "confirmation_request",
|
|
26814
26884
|
tool_calls: confirmationToolCalls,
|
|
26815
26885
|
preview: previewContent,
|
|
26816
26886
|
tool_policy: decision
|
|
26817
26887
|
});
|
|
26818
26888
|
}
|
|
26819
|
-
} else if (typeof message2.content === "string" && message2.content.trim()) {
|
|
26820
|
-
this.emptyAssistantReplySteps = 0;
|
|
26821
|
-
this.invalidToolCallRetrySteps = 0;
|
|
26822
|
-
this.eventBus.emit("backend_message", { type: "assistant_message", content: message2.content });
|
|
26823
|
-
await this.notifyFactorTurnEndIfNeeded("assistant_text_without_tool_call");
|
|
26824
|
-
this.emitTurnCompleted();
|
|
26825
|
-
return;
|
|
26826
|
-
} else {
|
|
26827
|
-
await this.continueAfterEmptyAssistantResponse();
|
|
26889
|
+
} else if (typeof message2.content === "string" && message2.content.trim()) {
|
|
26890
|
+
this.emptyAssistantReplySteps = 0;
|
|
26891
|
+
this.invalidToolCallRetrySteps = 0;
|
|
26892
|
+
this.deps.eventBus.emit("backend_message", { type: "assistant_message", content: message2.content });
|
|
26893
|
+
await this.deps.notifyFactorTurnEndIfNeeded("assistant_text_without_tool_call");
|
|
26894
|
+
this.deps.emitTurnCompleted();
|
|
26895
|
+
return;
|
|
26896
|
+
} else {
|
|
26897
|
+
await this.continueAfterEmptyAssistantResponse();
|
|
26898
|
+
}
|
|
26899
|
+
}
|
|
26900
|
+
async pollWorkerMailbox() {
|
|
26901
|
+
if (!process.env.BLUMA_PARENT_SESSION_ID) {
|
|
26902
|
+
return null;
|
|
26903
|
+
}
|
|
26904
|
+
try {
|
|
26905
|
+
const { pollMailbox: pollMailbox2 } = await import("../../tools/natives/poll_mailbox.js");
|
|
26906
|
+
const result = await pollMailbox2({
|
|
26907
|
+
timeout: 0,
|
|
26908
|
+
pollInterval: 500,
|
|
26909
|
+
types: ["follow_up", "cancel_request", "permission_response"],
|
|
26910
|
+
includeSignals: false
|
|
26911
|
+
});
|
|
26912
|
+
if (!result.success || !result.hasNewMessages) {
|
|
26913
|
+
return null;
|
|
26914
|
+
}
|
|
26915
|
+
return {
|
|
26916
|
+
followUp: result.followUp ? {
|
|
26917
|
+
message: result.followUp.message,
|
|
26918
|
+
priority: result.followUp.priority
|
|
26919
|
+
} : void 0,
|
|
26920
|
+
cancelRequested: result.cancelRequested
|
|
26921
|
+
};
|
|
26922
|
+
} catch {
|
|
26923
|
+
return null;
|
|
26924
|
+
}
|
|
26925
|
+
}
|
|
26926
|
+
};
|
|
26927
|
+
function summarizeHistoryForLog(messages, limit = 8) {
|
|
26928
|
+
return messages.slice(0, limit).map((message2) => {
|
|
26929
|
+
const content = message2?.content;
|
|
26930
|
+
const contentType = Array.isArray(content) ? "array" : typeof content;
|
|
26931
|
+
const preview = typeof content === "string" ? content.slice(0, 120) : Array.isArray(content) ? content.filter((part) => part && typeof part === "object" && part.type === "text" && typeof part.text === "string").map((part) => part.text).join(" ").slice(0, 120) : void 0;
|
|
26932
|
+
return {
|
|
26933
|
+
role: message2?.role,
|
|
26934
|
+
name: message2?.name ?? null,
|
|
26935
|
+
content_type: contentType,
|
|
26936
|
+
preview: preview || null,
|
|
26937
|
+
has_tool_calls: Array.isArray(message2?.tool_calls) && message2.tool_calls.length > 0,
|
|
26938
|
+
tool_call_id: message2?.tool_call_id ?? null
|
|
26939
|
+
};
|
|
26940
|
+
});
|
|
26941
|
+
}
|
|
26942
|
+
|
|
26943
|
+
// src/app/agent/bluma/core/bluma.ts
|
|
26944
|
+
var BluMaAgent = class {
|
|
26945
|
+
llm;
|
|
26946
|
+
sessionId;
|
|
26947
|
+
sessionFile = "";
|
|
26948
|
+
history = [];
|
|
26949
|
+
eventBus;
|
|
26950
|
+
mcpClient;
|
|
26951
|
+
feedbackSystem;
|
|
26952
|
+
skillLoader;
|
|
26953
|
+
compressor;
|
|
26954
|
+
toolRunner;
|
|
26955
|
+
turnCoordinator;
|
|
26956
|
+
isInterrupted = false;
|
|
26957
|
+
/** Mesmo turnId durante processTurn + todo o loop de tool_calls (FactorRouter). */
|
|
26958
|
+
activeTurnContext = null;
|
|
26959
|
+
/** Evita POST /turns/.../end duplicado no mesmo turno (ex.: Esc após message_result). */
|
|
26960
|
+
factorRouterTurnClosed = false;
|
|
26961
|
+
constructor(sessionId, eventBus, llm, mcpClient, feedbackSystem) {
|
|
26962
|
+
this.sessionId = sessionId;
|
|
26963
|
+
this.eventBus = eventBus;
|
|
26964
|
+
this.llm = llm;
|
|
26965
|
+
this.mcpClient = mcpClient;
|
|
26966
|
+
this.feedbackSystem = feedbackSystem;
|
|
26967
|
+
this.skillLoader = new SkillLoader(process.cwd());
|
|
26968
|
+
this.compressor = new HistoryCompressor();
|
|
26969
|
+
this.toolRunner = new BluMaToolRunner({
|
|
26970
|
+
eventBus: this.eventBus,
|
|
26971
|
+
history: this.history,
|
|
26972
|
+
sessionId: this.sessionId,
|
|
26973
|
+
mcpClient: this.mcpClient,
|
|
26974
|
+
isInterrupted: () => this.isInterrupted,
|
|
26975
|
+
persistSession: () => this.persistSession(),
|
|
26976
|
+
notifyFactorTurnEndIfNeeded: (reason) => this.notifyFactorTurnEndIfNeeded(reason),
|
|
26977
|
+
emitTurnCompleted: () => this.emitTurnCompleted(),
|
|
26978
|
+
buildToolPolicyForUi: (toolName, toolArgs) => this.buildToolPolicyForUi(toolName, toolArgs),
|
|
26979
|
+
generateEditPreview: (toolArgs) => this._generateEditPreview(toolArgs),
|
|
26980
|
+
generateFileWritePreview: (toolArgs) => this._generateFileWritePreview(toolArgs)
|
|
26981
|
+
});
|
|
26982
|
+
this.turnCoordinator = new BluMaTurnCoordinator({
|
|
26983
|
+
eventBus: this.eventBus,
|
|
26984
|
+
history: this.history,
|
|
26985
|
+
sessionId: this.sessionId,
|
|
26986
|
+
llm: this.llm,
|
|
26987
|
+
mcpClient: this.mcpClient,
|
|
26988
|
+
compressor: this.compressor,
|
|
26989
|
+
toolRunner: this.toolRunner,
|
|
26990
|
+
isInterrupted: () => this.isInterrupted,
|
|
26991
|
+
getLlmUserContext: () => this.getLlmUserContext(),
|
|
26992
|
+
persistSession: () => this.persistSession(),
|
|
26993
|
+
persistSessionSync: () => this.persistSessionSync(),
|
|
26994
|
+
notifyFactorTurnEndIfNeeded: (reason) => this.notifyFactorTurnEndIfNeeded(reason),
|
|
26995
|
+
emitTurnCompleted: () => this.emitTurnCompleted(),
|
|
26996
|
+
generateEditPreview: (toolArgs) => this._generateEditPreview(toolArgs),
|
|
26997
|
+
generateFileWritePreview: (toolArgs) => this._generateFileWritePreview(toolArgs)
|
|
26998
|
+
});
|
|
26999
|
+
this.eventBus.on("user_interrupt", async () => {
|
|
27000
|
+
this.isInterrupted = true;
|
|
27001
|
+
await this.notifyFactorTurnEndIfNeeded("user_interrupt");
|
|
27002
|
+
this.eventBus.emit("backend_message", { type: "done", status: "interrupted" });
|
|
27003
|
+
});
|
|
27004
|
+
this.eventBus.on("user_overlay", async (data) => {
|
|
27005
|
+
const clean = String(data.payload ?? "").trim();
|
|
27006
|
+
this.history.push({ role: "user", content: clean });
|
|
27007
|
+
this.eventBus.emit("backend_message", { type: "user_overlay", payload: clean, ts: data.ts || Date.now() });
|
|
27008
|
+
try {
|
|
27009
|
+
if (this.sessionFile) {
|
|
27010
|
+
this.persistSession();
|
|
27011
|
+
} else {
|
|
27012
|
+
const [sessionFile, , mem] = await loadOrcreateSession(this.sessionId);
|
|
27013
|
+
this.sessionFile = sessionFile;
|
|
27014
|
+
this.compressor.restoreSnapshot(mem);
|
|
27015
|
+
this.persistSession();
|
|
27016
|
+
}
|
|
27017
|
+
} catch (e) {
|
|
27018
|
+
this.eventBus.emit("backend_message", { type: "error", message: `Falha ao salvar hist\xF3rico ap\xF3s user_overlay: ${e.message}` });
|
|
27019
|
+
}
|
|
27020
|
+
});
|
|
27021
|
+
}
|
|
27022
|
+
getMemorySnapshot() {
|
|
27023
|
+
return this.compressor.getSnapshot();
|
|
27024
|
+
}
|
|
27025
|
+
/**
|
|
27026
|
+
* Salvar histórico de forma SÍNCRONA - usado em situações críticas (erros, shutdown)
|
|
27027
|
+
* para garantir que nada se perde. NÃO usa debounce.
|
|
27028
|
+
*/
|
|
27029
|
+
persistSessionSync() {
|
|
27030
|
+
if (!this.sessionFile) return;
|
|
27031
|
+
try {
|
|
27032
|
+
const sessionData = {
|
|
27033
|
+
session_id: path40.basename(this.sessionFile, ".json"),
|
|
27034
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27035
|
+
conversation_history: this.history,
|
|
27036
|
+
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27037
|
+
...this.compressor.getSnapshot()
|
|
27038
|
+
};
|
|
27039
|
+
fs39.writeFileSync(this.sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
|
|
27040
|
+
} catch (error) {
|
|
27041
|
+
console.error("[Bluma] Failed to persist session synchronously:", error);
|
|
27042
|
+
}
|
|
27043
|
+
}
|
|
27044
|
+
/** Debounced: grava histórico + estado de compressão no mesmo ficheiro de sessão. */
|
|
27045
|
+
persistSession() {
|
|
27046
|
+
if (!this.sessionFile) return;
|
|
27047
|
+
void saveSessionHistory(this.sessionFile, this.history, this.getMemorySnapshot());
|
|
27048
|
+
}
|
|
27049
|
+
recordUiSlashCommand(command, mode = "visible_only") {
|
|
27050
|
+
const text = String(command ?? "").trim();
|
|
27051
|
+
if (!text) return;
|
|
27052
|
+
this.history.push({
|
|
27053
|
+
role: "user",
|
|
27054
|
+
name: mode === "with_internal_prompt" ? "ui_slash_command" : "ui_slash_command_local",
|
|
27055
|
+
content: text
|
|
27056
|
+
});
|
|
27057
|
+
this.persistSession();
|
|
27058
|
+
}
|
|
27059
|
+
async initialize() {
|
|
27060
|
+
await this.mcpClient.nativeToolInvoker.initialize();
|
|
27061
|
+
await this.mcpClient.initialize();
|
|
27062
|
+
const [sessionFile, history, mem] = await loadOrcreateSession(this.sessionId);
|
|
27063
|
+
this.sessionFile = sessionFile;
|
|
27064
|
+
this.history.splice(0, this.history.length, ...history);
|
|
27065
|
+
this.compressor.restoreSnapshot(mem);
|
|
27066
|
+
initializeSkillContext({
|
|
27067
|
+
history: this.history,
|
|
27068
|
+
skillLoader: this.skillLoader
|
|
27069
|
+
});
|
|
27070
|
+
registerSessionCronBridge({
|
|
27071
|
+
eventBus: this.eventBus,
|
|
27072
|
+
getSessionId: () => this.sessionId
|
|
27073
|
+
});
|
|
27074
|
+
const dirs = this.skillLoader.getSkillsDirs();
|
|
27075
|
+
this.eventBus.emit("backend_message", {
|
|
27076
|
+
type: "info",
|
|
27077
|
+
message: `Skills dirs \u2014 bundled: ${dirs.bundled} | project: ${dirs.project} | global: ${dirs.global}`
|
|
27078
|
+
});
|
|
27079
|
+
const availableSkills = this.skillLoader.listAvailable();
|
|
27080
|
+
this.eventBus.emit("backend_message", {
|
|
27081
|
+
type: "info",
|
|
27082
|
+
message: `Skills loaded: ${availableSkills.length} \u2014 ${availableSkills.map((s) => `${s.name} (${s.source})`).join(", ") || "none"}`
|
|
27083
|
+
});
|
|
27084
|
+
if (this.skillLoader.hasConflicts()) {
|
|
27085
|
+
for (const warning of this.skillLoader.formatConflictWarnings()) {
|
|
27086
|
+
this.eventBus.emit("backend_message", {
|
|
27087
|
+
type: "warning",
|
|
27088
|
+
message: warning
|
|
27089
|
+
});
|
|
27090
|
+
}
|
|
27091
|
+
}
|
|
27092
|
+
const toolsDetailed = this.mcpClient.getAvailableToolsDetailed();
|
|
27093
|
+
const availableToolNames = toolsDetailed.map((t) => t.function?.name).filter(Boolean);
|
|
27094
|
+
const systemPrompt = await getUnifiedSystemPrompt(availableSkills, {
|
|
27095
|
+
availableToolNames,
|
|
27096
|
+
toolsDetailed,
|
|
27097
|
+
mcpClient: this.mcpClient,
|
|
27098
|
+
sessionId: this.sessionId
|
|
27099
|
+
});
|
|
27100
|
+
if (this.history.length === 0) {
|
|
27101
|
+
this.history.push({ role: "system", content: systemPrompt });
|
|
27102
|
+
} else {
|
|
27103
|
+
const sysIdx = this.history.findIndex(
|
|
27104
|
+
(m) => m.role === "system" && typeof m.content === "string" && String(m.content).includes("<identity>")
|
|
27105
|
+
);
|
|
27106
|
+
if (sysIdx >= 0) {
|
|
27107
|
+
this.history[sysIdx] = { ...this.history[sysIdx], content: systemPrompt };
|
|
27108
|
+
} else {
|
|
27109
|
+
this.history.unshift({ role: "system", content: systemPrompt });
|
|
27110
|
+
}
|
|
27111
|
+
}
|
|
27112
|
+
this.persistSession();
|
|
27113
|
+
const uiHistory = this.history.filter(
|
|
27114
|
+
(m) => m.role !== "system"
|
|
27115
|
+
);
|
|
27116
|
+
if (uiHistory.length > 0) {
|
|
27117
|
+
this.eventBus.emit("backend_message", {
|
|
27118
|
+
type: "session_history",
|
|
27119
|
+
messages: uiHistory
|
|
27120
|
+
});
|
|
27121
|
+
}
|
|
27122
|
+
}
|
|
27123
|
+
getAvailableTools() {
|
|
27124
|
+
return this.mcpClient.getAvailableTools();
|
|
27125
|
+
}
|
|
27126
|
+
getUiToolsDetailed() {
|
|
27127
|
+
return this.mcpClient.getAvailableToolsDetailed();
|
|
27128
|
+
}
|
|
27129
|
+
listAvailableSkills() {
|
|
27130
|
+
return this.skillLoader.listAvailable();
|
|
27131
|
+
}
|
|
27132
|
+
getSkillsDirs() {
|
|
27133
|
+
return this.skillLoader.getSkillsDirs();
|
|
27134
|
+
}
|
|
27135
|
+
getSkillConflictWarnings() {
|
|
27136
|
+
return this.skillLoader.formatConflictWarnings();
|
|
27137
|
+
}
|
|
27138
|
+
getFeedbackScore() {
|
|
27139
|
+
return this.feedbackSystem.getCumulativeScore();
|
|
27140
|
+
}
|
|
27141
|
+
/**
|
|
27142
|
+
* Contrato de ciclo de turno (UI, hooks, Factor Router):
|
|
27143
|
+
*
|
|
27144
|
+
* - `backend_message` `{ type: 'turn_start', turnId, sessionId, userPromptPreview }` — início;
|
|
27145
|
+
* payload estável em {@link buildTurnStartBackendMessage}.
|
|
27146
|
+
* - Stream: `stream_start` / `stream_reasoning_chunk` / `stream_chunk` / `stream_end`.
|
|
27147
|
+
* Se a resposta incluir tool `message`, `stream_end` leva `{ omitAssistantFlush: true }` para não
|
|
27148
|
+
* duplicar texto no histórico (o utilizador vê o `tool_result` com gutter). Ver `applyStreamEndFlush`.
|
|
27149
|
+
* - Fim: `backend_message` `{ type: 'done', status }` (`completed` | `failed` | `interrupted` | …).
|
|
27150
|
+
* - Factor Router: um POST `/turns/{turnId}/end` por turno via `notifyFactorTurnEndIfNeeded` (razões abaixo).
|
|
27151
|
+
*
|
|
27152
|
+
* Smoke manual sugerido: mensagem normal com `message`+`result`; Ctrl+C; erro LLM; loop sem resposta útil.
|
|
27153
|
+
*/
|
|
27154
|
+
async processTurn(userInput, userContextInput, options) {
|
|
27155
|
+
this.isInterrupted = false;
|
|
27156
|
+
this.factorRouterTurnClosed = false;
|
|
27157
|
+
this.turnCoordinator.resetTurnState();
|
|
27158
|
+
const inputText = String(userInput.content || "").trim();
|
|
27159
|
+
const turnId = uuidv48();
|
|
27160
|
+
this.activeTurnContext = {
|
|
27161
|
+
...userContextInput,
|
|
27162
|
+
turnId,
|
|
27163
|
+
sessionId: userContextInput.sessionId || this.sessionId
|
|
27164
|
+
};
|
|
27165
|
+
process.env.BLUMA_USER_CONTEXT_JSON = JSON.stringify({
|
|
27166
|
+
conversationId: this.activeTurnContext.conversationId ?? null,
|
|
27167
|
+
userId: this.activeTurnContext.userId ?? null,
|
|
27168
|
+
userName: this.activeTurnContext.userName ?? null,
|
|
27169
|
+
userEmail: this.activeTurnContext.userEmail ?? null,
|
|
27170
|
+
companyId: this.activeTurnContext.companyId ?? null,
|
|
27171
|
+
companyName: this.activeTurnContext.companyName ?? null
|
|
27172
|
+
});
|
|
27173
|
+
const userContent = buildUserMessageContent(inputText, process.cwd());
|
|
27174
|
+
this.history.push({
|
|
27175
|
+
role: "user",
|
|
27176
|
+
content: userContent,
|
|
27177
|
+
...options?.historyName ? { name: options.historyName } : {}
|
|
27178
|
+
});
|
|
27179
|
+
this.eventBus.emit(
|
|
27180
|
+
"backend_message",
|
|
27181
|
+
buildTurnStartBackendMessage({
|
|
27182
|
+
turnId: this.activeTurnContext.turnId,
|
|
27183
|
+
sessionId: this.activeTurnContext.sessionId,
|
|
27184
|
+
inputText,
|
|
27185
|
+
appContext: this.activeTurnContext.appContext ?? null
|
|
27186
|
+
})
|
|
27187
|
+
);
|
|
27188
|
+
if (inputText === "/init") {
|
|
27189
|
+
this.eventBus.emit("dispatch", inputText);
|
|
27190
|
+
}
|
|
27191
|
+
await this.turnCoordinator.continueConversation();
|
|
27192
|
+
}
|
|
27193
|
+
/** Política mostrada na UI: combina metadata base com auto-approve efectivo (incl. permission_ml). */
|
|
27194
|
+
buildToolPolicyForUi(toolName, toolArgs) {
|
|
27195
|
+
const base = decideToolExecution(toolName, void 0, this.sessionId);
|
|
27196
|
+
const argsStr = typeof toolArgs === "string" ? toolArgs : JSON.stringify(toolArgs ?? {});
|
|
27197
|
+
const synthetic = { function: { name: toolName, arguments: argsStr } };
|
|
27198
|
+
const auto = effectiveToolAutoApprove(synthetic, this.sessionId, { logClassifier: false });
|
|
27199
|
+
return {
|
|
27200
|
+
...base,
|
|
27201
|
+
autoApprove: auto,
|
|
27202
|
+
requiresConfirmation: !auto
|
|
27203
|
+
};
|
|
27204
|
+
}
|
|
27205
|
+
/**
|
|
27206
|
+
* Executa uma tool aprovada: emit tool_call → invoke → tool_result → histórico.
|
|
27207
|
+
* `backend_message` tool_call / tool_result incluem `tool_call_id` (id do tool call no modelo)
|
|
27208
|
+
* para a UI correlacionar a linha de invocação (•) com o resultado compacto.
|
|
27209
|
+
* @returns shouldContinue — false se `message` com message_type result terminou o turno.
|
|
27210
|
+
*/
|
|
27211
|
+
async executeApprovedToolInvocation(toolCall, decisionData) {
|
|
27212
|
+
return this.toolRunner.executeApprovedToolInvocation(toolCall, decisionData);
|
|
27213
|
+
}
|
|
27214
|
+
/** Várias leituras seguras em paralelo (grep, ls, read_file, …). */
|
|
27215
|
+
async executeParallelReadBatch(batch, _decisionData) {
|
|
27216
|
+
return this.toolRunner.executeParallelReadBatch(batch, _decisionData);
|
|
27217
|
+
}
|
|
27218
|
+
async handleToolResponse(decisionData) {
|
|
27219
|
+
return this.turnCoordinator.handleToolResponse(decisionData);
|
|
27220
|
+
}
|
|
27221
|
+
async _generateEditPreview(toolArgs) {
|
|
27222
|
+
try {
|
|
27223
|
+
if (Array.isArray(toolArgs.edits) && toolArgs.edits.length > 0) {
|
|
27224
|
+
return previewEditBatch(toolArgs.edits);
|
|
27225
|
+
}
|
|
27226
|
+
if (!toolArgs.file_path) {
|
|
27227
|
+
return "Error: file_path is required";
|
|
27228
|
+
}
|
|
27229
|
+
const editData = await calculateEdit(
|
|
27230
|
+
toolArgs.file_path,
|
|
27231
|
+
toolArgs.old_string || "",
|
|
27232
|
+
toolArgs.new_string || "",
|
|
27233
|
+
toolArgs.expected_replacements || 1
|
|
27234
|
+
);
|
|
27235
|
+
if (editData.error) {
|
|
27236
|
+
return `Failed to generate diff:
|
|
27237
|
+
|
|
27238
|
+
${editData.error.display}`;
|
|
27239
|
+
}
|
|
27240
|
+
const filename = path40.basename(toolArgs.file_path);
|
|
27241
|
+
return createDiff(filename, editData.currentContent || "", editData.newContent);
|
|
27242
|
+
} catch (e) {
|
|
27243
|
+
return `An unexpected error occurred while generating the edit preview: ${e.message}`;
|
|
27244
|
+
}
|
|
27245
|
+
}
|
|
27246
|
+
_generateFileWritePreview(toolArgs) {
|
|
27247
|
+
try {
|
|
27248
|
+
const content = typeof toolArgs?.content === "string" ? toolArgs.content : String(toolArgs?.content ?? "");
|
|
27249
|
+
if (!content) {
|
|
27250
|
+
return "(No content)";
|
|
27251
|
+
}
|
|
27252
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
27253
|
+
const lines = normalized.split("\n");
|
|
27254
|
+
const maxPreviewLines = 20;
|
|
27255
|
+
const visibleLines = lines.slice(0, maxPreviewLines);
|
|
27256
|
+
const hiddenLines = lines.length - visibleLines.length;
|
|
27257
|
+
return hiddenLines > 0 ? `${visibleLines.join("\n")}
|
|
27258
|
+
\u2026 +${hiddenLines} more lines` : normalized;
|
|
27259
|
+
} catch (e) {
|
|
27260
|
+
return `An unexpected error occurred while generating the file write preview: ${e.message}`;
|
|
27261
|
+
}
|
|
27262
|
+
}
|
|
27263
|
+
getLlmUserContext() {
|
|
27264
|
+
if (!this.activeTurnContext) {
|
|
27265
|
+
throw new Error("BluMaAgent: activeTurnContext ausente (processTurn n\xE3o iniciou o turno).");
|
|
26828
27266
|
}
|
|
27267
|
+
return this.activeTurnContext;
|
|
27268
|
+
}
|
|
27269
|
+
/**
|
|
27270
|
+
* Um único POST ao Factor Router por turno (`notifyFactorRouterTurnEnd`).
|
|
27271
|
+
* `reason` típicos: `message_result`, `user_interrupt`, `empty_reply_exhausted`, `llm_error`.
|
|
27272
|
+
*/
|
|
27273
|
+
async notifyFactorTurnEndIfNeeded(reason) {
|
|
27274
|
+
if (this.factorRouterTurnClosed || !this.activeTurnContext) return;
|
|
27275
|
+
this.factorRouterTurnClosed = true;
|
|
27276
|
+
const ctx = this.activeTurnContext;
|
|
27277
|
+
await notifyFactorRouterTurnEnd({
|
|
27278
|
+
turnId: ctx.turnId,
|
|
27279
|
+
userContext: ctx,
|
|
27280
|
+
reason
|
|
27281
|
+
});
|
|
27282
|
+
}
|
|
27283
|
+
/** Fecho explícito de turno (útil para workers antes de process.exit). */
|
|
27284
|
+
async closeActiveTurn(reason = "worker_exit") {
|
|
27285
|
+
await this.notifyFactorTurnEndIfNeeded(reason);
|
|
26829
27286
|
}
|
|
26830
27287
|
emitTurnCompleted() {
|
|
26831
27288
|
this.eventBus.emit("backend_message", { type: "done", status: "completed" });
|
|
@@ -26838,37 +27295,6 @@ ${editData.error.display}`;
|
|
|
26838
27295
|
} catch {
|
|
26839
27296
|
}
|
|
26840
27297
|
}
|
|
26841
|
-
/**
|
|
26842
|
-
* WORKER: Poll mailbox para checkar follow-ups do coordinator
|
|
26843
|
-
* Retorna null se não há novas mensagens ou se não é um worker
|
|
26844
|
-
*/
|
|
26845
|
-
async _pollWorkerMailbox() {
|
|
26846
|
-
if (!process.env.BLUMA_PARENT_SESSION_ID) {
|
|
26847
|
-
return null;
|
|
26848
|
-
}
|
|
26849
|
-
try {
|
|
26850
|
-
const { pollMailbox: pollMailbox2 } = await import("../../tools/natives/poll_mailbox.js");
|
|
26851
|
-
const result = await pollMailbox2({
|
|
26852
|
-
timeout: 0,
|
|
26853
|
-
// Non-blocking
|
|
26854
|
-
pollInterval: 500,
|
|
26855
|
-
types: ["follow_up", "cancel_request", "permission_response"],
|
|
26856
|
-
includeSignals: false
|
|
26857
|
-
});
|
|
26858
|
-
if (!result.success || !result.hasNewMessages) {
|
|
26859
|
-
return null;
|
|
26860
|
-
}
|
|
26861
|
-
return {
|
|
26862
|
-
followUp: result.followUp ? {
|
|
26863
|
-
message: result.followUp.message,
|
|
26864
|
-
priority: result.followUp.priority
|
|
26865
|
-
} : void 0,
|
|
26866
|
-
cancelRequested: result.cancelRequested
|
|
26867
|
-
};
|
|
26868
|
-
} catch (error) {
|
|
26869
|
-
return null;
|
|
26870
|
-
}
|
|
26871
|
-
}
|
|
26872
27298
|
};
|
|
26873
27299
|
|
|
26874
27300
|
// src/app/agent/subagents/registry.ts
|
|
@@ -28482,7 +28908,7 @@ function ChatUserImageBlock({
|
|
|
28482
28908
|
if (imageCount < 1) return null;
|
|
28483
28909
|
const cap = caption?.trim() ?? "";
|
|
28484
28910
|
const capLines = cap.length > 0 ? cap.split("\n") : [];
|
|
28485
|
-
return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column",
|
|
28911
|
+
return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", children: [
|
|
28486
28912
|
Array.from({ length: imageCount }, (_, i) => /* @__PURE__ */ jsx11(Text, { color: BLUMA_TERMINAL.blue, children: `[IMAGE #${i + 1}]` }, `img-${i}`)),
|
|
28487
28913
|
capLines.map((line, i) => /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "row", flexWrap: "wrap", children: [
|
|
28488
28914
|
/* @__PURE__ */ jsx11(Text, { color: BLUMA_TERMINAL.subtle, children: i === 0 ? " L " : " " }),
|
|
@@ -28656,15 +29082,14 @@ var HighlightedCodeComponent = ({ code, filePath, maxLines }) => {
|
|
|
28656
29082
|
const visible = maxLines ? lines.slice(0, maxLines) : lines;
|
|
28657
29083
|
const hidden = lines.length - visible.length;
|
|
28658
29084
|
const gutterWidth = String(lines.length).length;
|
|
28659
|
-
return /* @__PURE__ */ jsxs5(Box_default, { flexDirection: "column", children: [
|
|
29085
|
+
return /* @__PURE__ */ jsxs5(Box_default, { flexDirection: "column", width: "100%", alignItems: "stretch", children: [
|
|
28660
29086
|
visible.map((line, idx) => {
|
|
28661
29087
|
const tokens = tokenizeLine(line, language);
|
|
28662
29088
|
const lineNum = String(idx + 1).padStart(gutterWidth, " ");
|
|
28663
|
-
return /* @__PURE__ */
|
|
28664
|
-
/* @__PURE__ */ jsx12(Text, { color: THEME.lineNumber, children: lineNum }),
|
|
28665
|
-
/* @__PURE__ */ jsx12(Text, { color:
|
|
28666
|
-
|
|
28667
|
-
] }) }, idx);
|
|
29089
|
+
return /* @__PURE__ */ jsxs5(Box_default, { width: "100%", flexDirection: "row", children: [
|
|
29090
|
+
/* @__PURE__ */ jsx12(Box_default, { marginRight: 2, children: /* @__PURE__ */ jsx12(Text, { color: THEME.lineNumber, children: lineNum }) }),
|
|
29091
|
+
/* @__PURE__ */ jsx12(Text, { children: tokens.map((t, ti) => /* @__PURE__ */ jsx12(Text, { color: t.color, children: t.text }, ti)) })
|
|
29092
|
+
] }, idx);
|
|
28668
29093
|
}),
|
|
28669
29094
|
hidden > 0 && /* @__PURE__ */ jsxs5(Text, { color: THEME.lineNumber, dimColor: true, children: [
|
|
28670
29095
|
"\u2026 +",
|
|
@@ -28735,6 +29160,11 @@ function headingAccent(depth) {
|
|
|
28735
29160
|
if (depth === 3) return BLUMA_TERMINAL.magenta;
|
|
28736
29161
|
return BLUMA_TERMINAL.inactive;
|
|
28737
29162
|
}
|
|
29163
|
+
function normalizeMarkdownText(content) {
|
|
29164
|
+
const trimmed = content.trim();
|
|
29165
|
+
if (!trimmed) return "";
|
|
29166
|
+
return trimmed.replace(/\r\n/g, "\n").replace(/\n{3,}/g, "\n\n");
|
|
29167
|
+
}
|
|
28738
29168
|
function listMarker(ordered, index, start, task, checked) {
|
|
28739
29169
|
if (task) return checked ? "[x]" : "[ ]";
|
|
28740
29170
|
if (ordered) {
|
|
@@ -28928,6 +29358,46 @@ function renderParagraph(p, key, compact) {
|
|
|
28928
29358
|
const nodes = walkInline(p.tokens, key);
|
|
28929
29359
|
return /* @__PURE__ */ jsx13(Box_default, { marginBottom: compact ? 0 : 1, flexDirection: "row", flexWrap: "wrap", children: nodes.length > 0 ? nodes : /* @__PURE__ */ jsx13(Text, { color: BLUMA_TERMINAL.muted, wrap: "wrap", children: p.text }) }, key);
|
|
28930
29360
|
}
|
|
29361
|
+
function renderCodeBlock(code, key) {
|
|
29362
|
+
const raw = code.text.replace(/\n$/, "");
|
|
29363
|
+
return /* @__PURE__ */ jsxs6(
|
|
29364
|
+
Box_default,
|
|
29365
|
+
{
|
|
29366
|
+
flexDirection: "column",
|
|
29367
|
+
marginBottom: 1,
|
|
29368
|
+
paddingX: 1,
|
|
29369
|
+
paddingY: 0,
|
|
29370
|
+
backgroundColor: BLUMA_TERMINAL.surfaceContainer,
|
|
29371
|
+
borderStyle: "round",
|
|
29372
|
+
borderColor: BLUMA_TERMINAL.outlineVariant,
|
|
29373
|
+
children: [
|
|
29374
|
+
code.lang ? /* @__PURE__ */ jsx13(Box_default, { marginBottom: 0, children: /* @__PURE__ */ jsx13(Text, { color: BLUMA_TERMINAL.blue, bold: true, children: code.lang }) }) : null,
|
|
29375
|
+
/* @__PURE__ */ jsx13(HighlightedCode, { code: raw, filePath: code.lang || "snippet" })
|
|
29376
|
+
]
|
|
29377
|
+
},
|
|
29378
|
+
key
|
|
29379
|
+
);
|
|
29380
|
+
}
|
|
29381
|
+
function renderBlockquote(q, key) {
|
|
29382
|
+
const inner = q.tokens && q.tokens.length > 0 ? renderBlockTokens(q.tokens, `${key}-inner`) : /* @__PURE__ */ jsx13(Text, { color: BLUMA_TERMINAL.blue, italic: true, wrap: "wrap", children: q.text });
|
|
29383
|
+
return /* @__PURE__ */ jsxs6(
|
|
29384
|
+
Box_default,
|
|
29385
|
+
{
|
|
29386
|
+
flexDirection: "row",
|
|
29387
|
+
marginBottom: 1,
|
|
29388
|
+
alignItems: "flex-start",
|
|
29389
|
+
paddingX: 1,
|
|
29390
|
+
borderStyle: "round",
|
|
29391
|
+
borderColor: BLUMA_TERMINAL.outlineVariant,
|
|
29392
|
+
backgroundColor: BLUMA_TERMINAL.surfaceContainer,
|
|
29393
|
+
children: [
|
|
29394
|
+
/* @__PURE__ */ jsx13(Text, { color: BLUMA_TERMINAL.blue, children: "\u2502 " }),
|
|
29395
|
+
/* @__PURE__ */ jsx13(Box_default, { flexDirection: "column", flexGrow: 1, children: inner })
|
|
29396
|
+
]
|
|
29397
|
+
},
|
|
29398
|
+
key
|
|
29399
|
+
);
|
|
29400
|
+
}
|
|
28931
29401
|
function renderListItemBlocks(item, depth, keyBase) {
|
|
28932
29402
|
if (!item.tokens?.length) {
|
|
28933
29403
|
const nodes = walkInline(
|
|
@@ -28963,13 +29433,6 @@ function renderListBlock(list, depth, keyBase) {
|
|
|
28963
29433
|
] }, `${keyBase}-row-${idx}`);
|
|
28964
29434
|
}) }, keyBase);
|
|
28965
29435
|
}
|
|
28966
|
-
function renderBlockquote(q, key) {
|
|
28967
|
-
const inner = q.tokens && q.tokens.length > 0 ? renderBlockTokens(q.tokens, `${key}-inner`) : /* @__PURE__ */ jsx13(Text, { color: BLUMA_TERMINAL.blue, italic: true, wrap: "wrap", children: q.text });
|
|
28968
|
-
return /* @__PURE__ */ jsxs6(Box_default, { flexDirection: "row", marginBottom: 1, alignItems: "flex-start", children: [
|
|
28969
|
-
/* @__PURE__ */ jsx13(Text, { color: BLUMA_TERMINAL.blue, children: "\u2502 " }),
|
|
28970
|
-
/* @__PURE__ */ jsx13(Box_default, { flexDirection: "column", flexGrow: 1, children: inner })
|
|
28971
|
-
] }, key);
|
|
28972
|
-
}
|
|
28973
29436
|
function renderBlockTokens(tokens, keyRoot) {
|
|
28974
29437
|
const elements = [];
|
|
28975
29438
|
for (let i = 0; i < tokens.length; i++) {
|
|
@@ -28992,17 +29455,9 @@ function renderBlockTokens(tokens, keyRoot) {
|
|
|
28992
29455
|
case "list":
|
|
28993
29456
|
elements.push(renderListBlock(token, 0, key));
|
|
28994
29457
|
break;
|
|
28995
|
-
case "code":
|
|
28996
|
-
|
|
28997
|
-
const raw = code.text.replace(/\n$/, "");
|
|
28998
|
-
elements.push(
|
|
28999
|
-
/* @__PURE__ */ jsxs6(Box_default, { flexDirection: "column", marginBottom: 1, children: [
|
|
29000
|
-
code.lang ? /* @__PURE__ */ jsx13(Text, { color: BLUMA_TERMINAL.blue, wrap: "wrap", children: code.lang }) : null,
|
|
29001
|
-
/* @__PURE__ */ jsx13(HighlightedCode, { code: raw, filePath: code.lang || "snippet" })
|
|
29002
|
-
] }, key)
|
|
29003
|
-
);
|
|
29458
|
+
case "code":
|
|
29459
|
+
elements.push(renderCodeBlock(token, key));
|
|
29004
29460
|
break;
|
|
29005
|
-
}
|
|
29006
29461
|
case "blockquote":
|
|
29007
29462
|
elements.push(renderBlockquote(token, key));
|
|
29008
29463
|
break;
|
|
@@ -29059,7 +29514,8 @@ var MarkdownRendererComponent = ({ markdown }) => {
|
|
|
29059
29514
|
if (!markdown || markdown.trim() === "") {
|
|
29060
29515
|
return null;
|
|
29061
29516
|
}
|
|
29062
|
-
const
|
|
29517
|
+
const normalized = normalizeMarkdownText(markdown);
|
|
29518
|
+
const tokens = cachedLexer(normalized);
|
|
29063
29519
|
return /* @__PURE__ */ jsx13(Box_default, { flexDirection: "column", paddingRight: 1, children: renderBlockTokens(tokens, "md") });
|
|
29064
29520
|
};
|
|
29065
29521
|
var MarkdownRenderer = memo4(MarkdownRendererComponent, (prevProps, nextProps) => {
|
|
@@ -30618,9 +31074,12 @@ import { jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
|
30618
31074
|
function renderToolHeader6({ args }) {
|
|
30619
31075
|
const cmd = args?.command ?? "";
|
|
30620
31076
|
const displayCmd = cmd.length > 50 ? cmd.substring(0, 50) + "\u2026" : cmd;
|
|
30621
|
-
return /* @__PURE__ */ jsxs20(Box_default, { flexDirection: "row",
|
|
31077
|
+
return /* @__PURE__ */ jsxs20(Box_default, { flexDirection: "row", alignItems: "flex-end", children: [
|
|
30622
31078
|
/* @__PURE__ */ jsx33(Text, { bold: true, color: BLUMA_TERMINAL.m3OnSurface, children: "Ran" }),
|
|
30623
|
-
/* @__PURE__ */
|
|
31079
|
+
/* @__PURE__ */ jsxs20(Text, { color: BLUMA_TERMINAL.dim, children: [
|
|
31080
|
+
" ",
|
|
31081
|
+
displayCmd
|
|
31082
|
+
] })
|
|
30624
31083
|
] });
|
|
30625
31084
|
}
|
|
30626
31085
|
function renderToolUseMessage6({ args }) {
|
|
@@ -31965,6 +32424,46 @@ function getPatchForEdit({
|
|
|
31965
32424
|
}));
|
|
31966
32425
|
return { patch, updatedFile: unescapeFromDiff(updatedFile) };
|
|
31967
32426
|
}
|
|
32427
|
+
function getPatchFromContents({
|
|
32428
|
+
filePath,
|
|
32429
|
+
oldContent,
|
|
32430
|
+
newContent
|
|
32431
|
+
}) {
|
|
32432
|
+
const result = structuredPatch(
|
|
32433
|
+
filePath,
|
|
32434
|
+
filePath,
|
|
32435
|
+
escapeForDiff(oldContent),
|
|
32436
|
+
escapeForDiff(newContent),
|
|
32437
|
+
void 0,
|
|
32438
|
+
void 0,
|
|
32439
|
+
{
|
|
32440
|
+
context: CONTEXT_LINES,
|
|
32441
|
+
timeout: DIFF_TIMEOUT_MS
|
|
32442
|
+
}
|
|
32443
|
+
);
|
|
32444
|
+
if (!result) {
|
|
32445
|
+
return [];
|
|
32446
|
+
}
|
|
32447
|
+
return result.hunks.map((h) => ({
|
|
32448
|
+
...h,
|
|
32449
|
+
lines: h.lines.map(unescapeFromDiff)
|
|
32450
|
+
}));
|
|
32451
|
+
}
|
|
32452
|
+
function patchToUnifiedDiffText(filePath, patch) {
|
|
32453
|
+
if (patch.length === 0) return "";
|
|
32454
|
+
let text = `--- a/${filePath}
|
|
32455
|
+
+++ b/${filePath}
|
|
32456
|
+
`;
|
|
32457
|
+
for (const hunk of patch) {
|
|
32458
|
+
text += `@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@
|
|
32459
|
+
`;
|
|
32460
|
+
for (const line of hunk.lines) {
|
|
32461
|
+
text += `${line}
|
|
32462
|
+
`;
|
|
32463
|
+
}
|
|
32464
|
+
}
|
|
32465
|
+
return text;
|
|
32466
|
+
}
|
|
31968
32467
|
|
|
31969
32468
|
// src/app/ui/utils/readEditContext.ts
|
|
31970
32469
|
import { promises as fs41 } from "fs";
|
|
@@ -32138,21 +32637,21 @@ function rowLineCounts(rows) {
|
|
|
32138
32637
|
}
|
|
32139
32638
|
return { additions, removals };
|
|
32140
32639
|
}
|
|
32141
|
-
function gutterCtx(wOld, wNew, oldN, newN) {
|
|
32142
|
-
return `${String(oldN).padStart(wOld)} ${String(newN).padStart(wNew)} \u2502 `;
|
|
32640
|
+
function gutterCtx(wOld, wNew, oldN, newN, showSeparator) {
|
|
32641
|
+
return `${String(oldN).padStart(wOld)} ${String(newN).padStart(wNew)}${showSeparator ? " \u2502 " : " "}`;
|
|
32143
32642
|
}
|
|
32144
|
-
function gutterRem(wOld, wNew, oldN) {
|
|
32145
|
-
return `${String(oldN).padStart(wOld)} ${" ".repeat(wNew)} \u2502
|
|
32643
|
+
function gutterRem(wOld, wNew, oldN, showSeparator) {
|
|
32644
|
+
return `${String(oldN).padStart(wOld)} ${" ".repeat(wNew)}${showSeparator ? " \u2502 " : " "}`;
|
|
32146
32645
|
}
|
|
32147
|
-
function gutterAdd(wOld, wNew, newN) {
|
|
32148
|
-
return `${" ".repeat(wOld)} ${String(newN).padStart(wNew)} \u2502
|
|
32646
|
+
function gutterAdd(wOld, wNew, newN, showSeparator) {
|
|
32647
|
+
return `${" ".repeat(wOld)} ${String(newN).padStart(wNew)}${showSeparator ? " \u2502 " : " "}`;
|
|
32149
32648
|
}
|
|
32150
|
-
function gutterOmit(wOld, wNew) {
|
|
32649
|
+
function gutterOmit(wOld, wNew, showSeparator) {
|
|
32151
32650
|
const cols = wOld + 1 + wNew;
|
|
32152
32651
|
const dot = "\u22EE";
|
|
32153
32652
|
const padL = Math.max(0, Math.floor((cols - dot.length) / 2));
|
|
32154
32653
|
const padR = Math.max(0, cols - dot.length - padL);
|
|
32155
|
-
return `${" ".repeat(padL)}${dot}${" ".repeat(padR)} \u2502 `;
|
|
32654
|
+
return `${" ".repeat(padL)}${dot}${" ".repeat(padR)}${showSeparator ? " \u2502 " : " "}`;
|
|
32156
32655
|
}
|
|
32157
32656
|
function WordDiffChunks({
|
|
32158
32657
|
parts,
|
|
@@ -32170,9 +32669,9 @@ function WordDiffChunks({
|
|
|
32170
32669
|
/* @__PURE__ */ jsx41(Text, { backgroundColor: bg, color: BLUMA_TERMINAL.m3OnSurface, wrap: "wrap", children: p.value }, k++)
|
|
32171
32670
|
);
|
|
32172
32671
|
}
|
|
32173
|
-
return /* @__PURE__ */ jsx41(Box_default, { flexDirection: "row", flexWrap: "wrap", children: chunks });
|
|
32672
|
+
return /* @__PURE__ */ jsx41(Box_default, { flexDirection: "row", flexWrap: "wrap", width: "100%", flexGrow: 1, children: chunks });
|
|
32174
32673
|
}
|
|
32175
|
-
function renderStructuredRows(rows, maxHeight) {
|
|
32674
|
+
function renderStructuredRows(rows, maxHeight, showGutterSeparator) {
|
|
32176
32675
|
const paired = collapseContextRows(pairWordDiffs(rows));
|
|
32177
32676
|
const { wOld, wNew } = maxLineNums(paired);
|
|
32178
32677
|
const { additions, removals } = rowLineCounts(paired);
|
|
@@ -32191,8 +32690,8 @@ function renderStructuredRows(rows, maxHeight) {
|
|
|
32191
32690
|
for (const r of paired) {
|
|
32192
32691
|
if (r.kind === "omit") {
|
|
32193
32692
|
push(
|
|
32194
|
-
/* @__PURE__ */ jsxs26(Box_default, { flexDirection: "row", width: "100%", children: [
|
|
32195
|
-
/* @__PURE__ */ jsx41(Text, { dimColor: true, children: gutterOmit(wOld, wNew) }),
|
|
32693
|
+
/* @__PURE__ */ jsxs26(Box_default, { flexDirection: "row", width: "100%", flexGrow: 1, children: [
|
|
32694
|
+
/* @__PURE__ */ jsx41(Text, { dimColor: true, children: gutterOmit(wOld, wNew, showGutterSeparator) }),
|
|
32196
32695
|
r.count > 1 ? /* @__PURE__ */ jsxs26(Text, { dimColor: true, wrap: "wrap", children: [
|
|
32197
32696
|
" ",
|
|
32198
32697
|
"\xB7 ",
|
|
@@ -32204,14 +32703,14 @@ function renderStructuredRows(rows, maxHeight) {
|
|
|
32204
32703
|
}
|
|
32205
32704
|
if (r.kind === "meta") {
|
|
32206
32705
|
push(
|
|
32207
|
-
/* @__PURE__ */ jsx41(Box_default, { flexDirection: "row", width: "100%", children: /* @__PURE__ */ jsx41(Text, { dimColor: true, wrap: "wrap", children: r.text }) }, `m-${idx++}`)
|
|
32706
|
+
/* @__PURE__ */ jsx41(Box_default, { flexDirection: "row", width: "100%", flexGrow: 1, children: /* @__PURE__ */ jsx41(Text, { dimColor: true, wrap: "wrap", children: r.text }) }, `m-${idx++}`)
|
|
32208
32707
|
);
|
|
32209
32708
|
continue;
|
|
32210
32709
|
}
|
|
32211
32710
|
if (r.kind === "ctx") {
|
|
32212
32711
|
push(
|
|
32213
|
-
/* @__PURE__ */ jsxs26(Box_default, { flexDirection: "row", flexWrap: "wrap", width: "100%", children: [
|
|
32214
|
-
/* @__PURE__ */ jsx41(Text, { dimColor: true, children: gutterCtx(wOld, wNew, r.old, r.new) }),
|
|
32712
|
+
/* @__PURE__ */ jsxs26(Box_default, { flexDirection: "row", flexWrap: "wrap", width: "100%", flexGrow: 1, children: [
|
|
32713
|
+
/* @__PURE__ */ jsx41(Text, { dimColor: true, children: gutterCtx(wOld, wNew, r.old, r.new, showGutterSeparator) }),
|
|
32215
32714
|
/* @__PURE__ */ jsx41(Text, { color: BLUMA_TERMINAL.muted, wrap: "wrap", children: r.text || " " })
|
|
32216
32715
|
] }, `c-${idx++}`)
|
|
32217
32716
|
);
|
|
@@ -32219,18 +32718,20 @@ function renderStructuredRows(rows, maxHeight) {
|
|
|
32219
32718
|
}
|
|
32220
32719
|
if (r.kind === "rem") {
|
|
32221
32720
|
push(
|
|
32222
|
-
/* @__PURE__ */ jsxs26(Box_default, { flexDirection: "row", width: "100%", flexWrap: "wrap", children: [
|
|
32223
|
-
/* @__PURE__ */ jsx41(Text, { dimColor: true, children: gutterRem(wOld, wNew, r.old) }),
|
|
32224
|
-
|
|
32721
|
+
/* @__PURE__ */ jsxs26(Box_default, { flexDirection: "row", width: "100%", flexGrow: 1, flexWrap: "wrap", backgroundColor: BLUMA_TERMINAL.diffRemoved, children: [
|
|
32722
|
+
/* @__PURE__ */ jsx41(Text, { dimColor: true, children: gutterRem(wOld, wNew, r.old, showGutterSeparator) }),
|
|
32723
|
+
/* @__PURE__ */ jsx41(Text, { color: BLUMA_TERMINAL.diffRemovedWord, children: "-" }),
|
|
32724
|
+
r.wordParts && r.wordParts.length > 0 ? /* @__PURE__ */ jsx41(Box_default, { flexDirection: "row", flexWrap: "wrap", flexGrow: 1, marginLeft: 1, children: /* @__PURE__ */ jsx41(WordDiffChunks, { parts: r.wordParts, mode: "rem" }) }) : /* @__PURE__ */ jsx41(Box_default, { flexDirection: "row", flexWrap: "wrap", flexGrow: 1, marginLeft: 1, children: /* @__PURE__ */ jsx41(Text, { color: BLUMA_TERMINAL.m3OnSurface, wrap: "wrap", children: r.text || " " }) })
|
|
32225
32725
|
] }, `r-${idx++}`)
|
|
32226
32726
|
);
|
|
32227
32727
|
continue;
|
|
32228
32728
|
}
|
|
32229
32729
|
if (r.kind === "add") {
|
|
32230
32730
|
push(
|
|
32231
|
-
/* @__PURE__ */ jsxs26(Box_default, { flexDirection: "row", width: "100%", flexWrap: "wrap", children: [
|
|
32232
|
-
/* @__PURE__ */ jsx41(Text, { dimColor: true, children: gutterAdd(wOld, wNew, r.new) }),
|
|
32233
|
-
|
|
32731
|
+
/* @__PURE__ */ jsxs26(Box_default, { flexDirection: "row", width: "100%", flexGrow: 1, flexWrap: "wrap", backgroundColor: BLUMA_TERMINAL.diffAdded, children: [
|
|
32732
|
+
/* @__PURE__ */ jsx41(Text, { dimColor: true, children: gutterAdd(wOld, wNew, r.new, showGutterSeparator) }),
|
|
32733
|
+
/* @__PURE__ */ jsx41(Text, { color: BLUMA_TERMINAL.diffAddedWord, children: "+" }),
|
|
32734
|
+
r.wordParts && r.wordParts.length > 0 ? /* @__PURE__ */ jsx41(Box_default, { flexDirection: "row", flexWrap: "wrap", flexGrow: 1, marginLeft: 1, children: /* @__PURE__ */ jsx41(WordDiffChunks, { parts: r.wordParts, mode: "add" }) }) : /* @__PURE__ */ jsx41(Box_default, { flexDirection: "row", flexWrap: "wrap", flexGrow: 1, marginLeft: 1, children: /* @__PURE__ */ jsx41(Text, { color: BLUMA_TERMINAL.m3OnSurface, wrap: "wrap", children: r.text || " " }) })
|
|
32234
32735
|
] }, `a-${idx++}`)
|
|
32235
32736
|
);
|
|
32236
32737
|
}
|
|
@@ -32348,7 +32849,8 @@ function collapseContextRows(rows) {
|
|
|
32348
32849
|
}
|
|
32349
32850
|
function LegacyDiffBody({
|
|
32350
32851
|
lines,
|
|
32351
|
-
maxHeight
|
|
32852
|
+
maxHeight,
|
|
32853
|
+
showGutterSeparator
|
|
32352
32854
|
}) {
|
|
32353
32855
|
const segs = legacyDiffToSegs(lines);
|
|
32354
32856
|
const { wOld, wNew } = legacySegWidths(segs);
|
|
@@ -32357,14 +32859,14 @@ function LegacyDiffBody({
|
|
|
32357
32859
|
const hiddenBelow = truncated ? segs.length - toRender.length : 0;
|
|
32358
32860
|
const { additions, removals } = legacySegLineCounts(lines);
|
|
32359
32861
|
const summary = diffSummary(additions, removals, hiddenBelow);
|
|
32360
|
-
return /* @__PURE__ */ jsxs26(Box_default, { flexDirection: "column", children: [
|
|
32862
|
+
return /* @__PURE__ */ jsxs26(Box_default, { flexDirection: "column", width: "100%", alignItems: "stretch", children: [
|
|
32361
32863
|
toRender.map((seg, index) => {
|
|
32362
32864
|
if (seg.kind === "header") {
|
|
32363
32865
|
return /* @__PURE__ */ jsx41(Text, { dimColor: true, wrap: "wrap", children: seg.line }, index);
|
|
32364
32866
|
}
|
|
32365
32867
|
if (seg.kind === "omit") {
|
|
32366
|
-
return /* @__PURE__ */ jsxs26(Box_default, { flexDirection: "row", width: "100%", children: [
|
|
32367
|
-
/* @__PURE__ */ jsx41(Text, { dimColor: true, children: gutterOmit(wOld, wNew) }),
|
|
32868
|
+
return /* @__PURE__ */ jsxs26(Box_default, { flexDirection: "row", width: "100%", flexGrow: 1, children: [
|
|
32869
|
+
/* @__PURE__ */ jsx41(Text, { dimColor: true, children: gutterOmit(wOld, wNew, showGutterSeparator) }),
|
|
32368
32870
|
seg.count > 1 ? /* @__PURE__ */ jsxs26(Text, { dimColor: true, wrap: "wrap", children: [
|
|
32369
32871
|
" ",
|
|
32370
32872
|
"\xB7 ",
|
|
@@ -32376,15 +32878,23 @@ function LegacyDiffBody({
|
|
|
32376
32878
|
return /* @__PURE__ */ jsx41(Text, { dimColor: true, wrap: "wrap", children: seg.line }, index);
|
|
32377
32879
|
}
|
|
32378
32880
|
if (seg.kind === "rem") {
|
|
32379
|
-
return /* @__PURE__ */ jsxs26(Box_default, { flexDirection: "row", width: "100%", flexWrap: "wrap", children: [
|
|
32380
|
-
/* @__PURE__ */ jsx41(Text, { dimColor: true, children: gutterRem(wOld, wNew, seg.oldLine) }),
|
|
32381
|
-
/* @__PURE__ */ jsx41(Text, {
|
|
32881
|
+
return /* @__PURE__ */ jsxs26(Box_default, { flexDirection: "row", width: "100%", flexGrow: 1, flexWrap: "wrap", backgroundColor: BLUMA_TERMINAL.diffRemoved, children: [
|
|
32882
|
+
/* @__PURE__ */ jsx41(Text, { dimColor: true, children: gutterRem(wOld, wNew, seg.oldLine, showGutterSeparator) }),
|
|
32883
|
+
/* @__PURE__ */ jsx41(Text, { color: BLUMA_TERMINAL.diffRemovedWord, children: "-" }),
|
|
32884
|
+
/* @__PURE__ */ jsxs26(Text, { color: BLUMA_TERMINAL.m3OnSurface, wrap: "wrap", children: [
|
|
32885
|
+
" ",
|
|
32886
|
+
seg.body || " "
|
|
32887
|
+
] })
|
|
32382
32888
|
] }, index);
|
|
32383
32889
|
}
|
|
32384
32890
|
if (seg.kind === "add") {
|
|
32385
|
-
return /* @__PURE__ */ jsxs26(Box_default, { flexDirection: "row", width: "100%", flexWrap: "wrap", children: [
|
|
32386
|
-
/* @__PURE__ */ jsx41(Text, { dimColor: true, children: gutterAdd(wOld, wNew, seg.newLine) }),
|
|
32387
|
-
/* @__PURE__ */ jsx41(Text, {
|
|
32891
|
+
return /* @__PURE__ */ jsxs26(Box_default, { flexDirection: "row", width: "100%", flexGrow: 1, flexWrap: "wrap", backgroundColor: BLUMA_TERMINAL.diffAdded, children: [
|
|
32892
|
+
/* @__PURE__ */ jsx41(Text, { dimColor: true, children: gutterAdd(wOld, wNew, seg.newLine, showGutterSeparator) }),
|
|
32893
|
+
/* @__PURE__ */ jsx41(Text, { color: BLUMA_TERMINAL.diffAddedWord, children: "+" }),
|
|
32894
|
+
/* @__PURE__ */ jsxs26(Text, { color: BLUMA_TERMINAL.m3OnSurface, wrap: "wrap", children: [
|
|
32895
|
+
" ",
|
|
32896
|
+
seg.body || " "
|
|
32897
|
+
] })
|
|
32388
32898
|
] }, index);
|
|
32389
32899
|
}
|
|
32390
32900
|
return null;
|
|
@@ -32392,7 +32902,7 @@ function LegacyDiffBody({
|
|
|
32392
32902
|
summary ? /* @__PURE__ */ jsx41(Text, { dimColor: true, children: summary }) : null
|
|
32393
32903
|
] });
|
|
32394
32904
|
}
|
|
32395
|
-
var SimpleDiff = ({ text, maxHeight, frame = false }) => {
|
|
32905
|
+
var SimpleDiff = ({ text, maxHeight, frame = false, showGutterSeparator = true }) => {
|
|
32396
32906
|
const raw = stripOptionalMarkdownFence(String(text ?? ""));
|
|
32397
32907
|
let patches = [];
|
|
32398
32908
|
try {
|
|
@@ -32404,7 +32914,7 @@ var SimpleDiff = ({ text, maxHeight, frame = false }) => {
|
|
|
32404
32914
|
const rule = "\u2500".repeat(diffRuleWidth());
|
|
32405
32915
|
if (hunks.length === 0) {
|
|
32406
32916
|
const allLines = raw.split("\n");
|
|
32407
|
-
const body2 = /* @__PURE__ */ jsx41(LegacyDiffBody, { lines: allLines, maxHeight });
|
|
32917
|
+
const body2 = /* @__PURE__ */ jsx41(LegacyDiffBody, { lines: allLines, maxHeight, showGutterSeparator });
|
|
32408
32918
|
if (!frame) return body2;
|
|
32409
32919
|
return /* @__PURE__ */ jsxs26(Box_default, { flexDirection: "column", children: [
|
|
32410
32920
|
/* @__PURE__ */ jsx41(Text, { dimColor: true, children: rule }),
|
|
@@ -32418,16 +32928,17 @@ var SimpleDiff = ({ text, maxHeight, frame = false }) => {
|
|
|
32418
32928
|
}
|
|
32419
32929
|
const { nodes, hidden, additions, removals } = renderStructuredRows(
|
|
32420
32930
|
rows,
|
|
32421
|
-
maxHeight
|
|
32931
|
+
maxHeight,
|
|
32932
|
+
showGutterSeparator
|
|
32422
32933
|
);
|
|
32423
|
-
const body = /* @__PURE__ */ jsxs26(Box_default, { flexDirection: "column", children: [
|
|
32934
|
+
const body = /* @__PURE__ */ jsxs26(Box_default, { flexDirection: "column", width: "100%", alignItems: "stretch", children: [
|
|
32424
32935
|
(hidden > 0 || additions > 0 || removals > 0) && /* @__PURE__ */ jsx41(Text, { dimColor: true, children: diffSummary(additions, removals, hidden) }),
|
|
32425
32936
|
nodes
|
|
32426
32937
|
] });
|
|
32427
32938
|
if (!frame) {
|
|
32428
32939
|
return body;
|
|
32429
32940
|
}
|
|
32430
|
-
return /* @__PURE__ */ jsxs26(Box_default, { flexDirection: "column", children: [
|
|
32941
|
+
return /* @__PURE__ */ jsxs26(Box_default, { flexDirection: "column", width: "100%", alignItems: "stretch", children: [
|
|
32431
32942
|
/* @__PURE__ */ jsx41(Text, { dimColor: true, children: rule }),
|
|
32432
32943
|
/* @__PURE__ */ jsx41(Box_default, { flexDirection: "column", paddingX: 1, children: body }),
|
|
32433
32944
|
/* @__PURE__ */ jsx41(Text, { dimColor: true, children: rule })
|
|
@@ -32529,6 +33040,7 @@ function EditToolDiffPanel({
|
|
|
32529
33040
|
description,
|
|
32530
33041
|
diffText,
|
|
32531
33042
|
maxHeight = EDIT_DIFF_PREVIEW_MAX_LINES,
|
|
33043
|
+
showGutterSeparator = true,
|
|
32532
33044
|
oldString,
|
|
32533
33045
|
newString,
|
|
32534
33046
|
replaceAll = false
|
|
@@ -32545,11 +33057,27 @@ function EditToolDiffPanel({
|
|
|
32545
33057
|
replaceAll,
|
|
32546
33058
|
maxHeight
|
|
32547
33059
|
}
|
|
32548
|
-
) }) : hasDiffText ? /* @__PURE__ */ jsx43(Box_default, { marginTop: 0, children: /* @__PURE__ */ jsx43(SimpleDiff, { text: diffText, maxHeight }) }) : /* @__PURE__ */ jsx43(Box_default, { marginTop: 0, children: /* @__PURE__ */ jsx43(Text, { dimColor: true, wrap: "wrap", children: "Diff preview unavailable" }) }) });
|
|
33060
|
+
) }) : hasDiffText ? /* @__PURE__ */ jsx43(Box_default, { marginTop: 0, children: /* @__PURE__ */ jsx43(SimpleDiff, { text: diffText, maxHeight, showGutterSeparator }) }) : /* @__PURE__ */ jsx43(Box_default, { marginTop: 0, children: /* @__PURE__ */ jsx43(Text, { dimColor: true, wrap: "wrap", children: "Diff preview unavailable" }) }) });
|
|
32549
33061
|
}
|
|
32550
33062
|
|
|
32551
33063
|
// src/app/agent/tools/EditTool/UI.tsx
|
|
33064
|
+
import { diffLines as diffLines2 } from "diff";
|
|
32552
33065
|
import { Fragment as Fragment5, jsx as jsx44, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
33066
|
+
function countLineDiff(oldText, newText) {
|
|
33067
|
+
const parts = diffLines2(oldText.replace(/\r\n/g, "\n"), newText.replace(/\r\n/g, "\n"));
|
|
33068
|
+
let added = 0;
|
|
33069
|
+
let removed = 0;
|
|
33070
|
+
for (const part of parts) {
|
|
33071
|
+
const lines = part.value.replace(/\n$/, "").split("\n");
|
|
33072
|
+
const lineCount = part.value === "" ? 0 : lines.length;
|
|
33073
|
+
if (part.added) added += lineCount;
|
|
33074
|
+
if (part.removed) removed += lineCount;
|
|
33075
|
+
}
|
|
33076
|
+
return { added, removed };
|
|
33077
|
+
}
|
|
33078
|
+
function stripUnifiedDiffFileHeaders(diffText) {
|
|
33079
|
+
return diffText.replace(/^--- .*\n/m, "").replace(/^\+\+\+ .*\n/m, "");
|
|
33080
|
+
}
|
|
32553
33081
|
function userFacingName11(args) {
|
|
32554
33082
|
if (!args) return "Updated";
|
|
32555
33083
|
if (args.edits && Array.isArray(args.edits) && args.edits.length > 0) {
|
|
@@ -32564,11 +33092,31 @@ function renderToolUseMessage12({ args }) {
|
|
|
32564
33092
|
return /* @__PURE__ */ jsx44(Text, { color: BLUMA_TERMINAL.blue, children: p });
|
|
32565
33093
|
}
|
|
32566
33094
|
function renderToolHeader12({ args }) {
|
|
32567
|
-
const label = userFacingName11(args);
|
|
32568
33095
|
const path50 = args?.file_path ?? ".";
|
|
33096
|
+
const oldText = typeof args?.old_string === "string" ? args.old_string : "";
|
|
33097
|
+
const newText = typeof args?.new_string === "string" ? args.new_string : "";
|
|
33098
|
+
const counts = countLineDiff(oldText, newText);
|
|
33099
|
+
const fileName = String(path50).split("/").pop() || String(path50);
|
|
33100
|
+
const action = oldText === "" ? "Created" : "Update";
|
|
32569
33101
|
return /* @__PURE__ */ jsxs27(Box_default, { flexDirection: "row", gap: 1, alignItems: "flex-end", children: [
|
|
32570
|
-
/* @__PURE__ */
|
|
32571
|
-
|
|
33102
|
+
/* @__PURE__ */ jsxs27(Text, { bold: true, color: BLUMA_TERMINAL.m3OnSurface, children: [
|
|
33103
|
+
action,
|
|
33104
|
+
" ",
|
|
33105
|
+
/* @__PURE__ */ jsx44(FilePathLink, { filePath: path50, color: BLUMA_TERMINAL.dim })
|
|
33106
|
+
] }),
|
|
33107
|
+
/* @__PURE__ */ jsxs27(Text, { dimColor: true, children: [
|
|
33108
|
+
"(",
|
|
33109
|
+
/* @__PURE__ */ jsxs27(Text, { color: BLUMA_TERMINAL.diffAddedWord, children: [
|
|
33110
|
+
"+",
|
|
33111
|
+
counts.added
|
|
33112
|
+
] }),
|
|
33113
|
+
" ",
|
|
33114
|
+
/* @__PURE__ */ jsxs27(Text, { color: BLUMA_TERMINAL.diffRemovedWord, children: [
|
|
33115
|
+
"-",
|
|
33116
|
+
counts.removed
|
|
33117
|
+
] }),
|
|
33118
|
+
")"
|
|
33119
|
+
] })
|
|
32572
33120
|
] });
|
|
32573
33121
|
}
|
|
32574
33122
|
function renderSingleEditDiff(edit, index, total, state2) {
|
|
@@ -32584,7 +33132,20 @@ function renderSingleEditDiff(edit, index, total, state2) {
|
|
|
32584
33132
|
total
|
|
32585
33133
|
] }),
|
|
32586
33134
|
/* @__PURE__ */ jsx44(Text, { bold: true, color: isNewFile ? BLUMA_TERMINAL.success : BLUMA_TERMINAL.blue, children: isNewFile ? "Create" : "Update" }),
|
|
32587
|
-
/* @__PURE__ */ jsx44(FilePathLink, { filePath, color: BLUMA_TERMINAL.dim })
|
|
33135
|
+
/* @__PURE__ */ jsx44(FilePathLink, { filePath, color: BLUMA_TERMINAL.dim }),
|
|
33136
|
+
/* @__PURE__ */ jsxs27(Text, { dimColor: true, children: [
|
|
33137
|
+
"(",
|
|
33138
|
+
/* @__PURE__ */ jsxs27(Text, { color: BLUMA_TERMINAL.diffAddedWord, children: [
|
|
33139
|
+
"+",
|
|
33140
|
+
countLineDiff(oldString, newString).added
|
|
33141
|
+
] }),
|
|
33142
|
+
" ",
|
|
33143
|
+
/* @__PURE__ */ jsxs27(Text, { color: BLUMA_TERMINAL.diffRemovedWord, children: [
|
|
33144
|
+
"-",
|
|
33145
|
+
countLineDiff(oldString, newString).removed
|
|
33146
|
+
] }),
|
|
33147
|
+
")"
|
|
33148
|
+
] })
|
|
32588
33149
|
] }),
|
|
32589
33150
|
/* @__PURE__ */ jsx44(Box_default, { marginLeft: 2, marginTop: 0, children: /* @__PURE__ */ jsx44(
|
|
32590
33151
|
EditToolDiffPanel,
|
|
@@ -32653,7 +33214,8 @@ function renderToolResultMessage12(result) {
|
|
|
32653
33214
|
{
|
|
32654
33215
|
filePath: edit.file_path,
|
|
32655
33216
|
isNewFile: edit.is_new_file ?? false,
|
|
32656
|
-
diffText: edit.diff
|
|
33217
|
+
diffText: typeof edit.diff === "string" ? stripUnifiedDiffFileHeaders(edit.diff) : edit.diff,
|
|
33218
|
+
showGutterSeparator: false
|
|
32657
33219
|
}
|
|
32658
33220
|
) }, `result-edit-${idx}`)) });
|
|
32659
33221
|
}
|
|
@@ -32662,7 +33224,8 @@ function renderToolResultMessage12(result) {
|
|
|
32662
33224
|
{
|
|
32663
33225
|
filePath: result.file_path,
|
|
32664
33226
|
isNewFile: result.is_new_file ?? false,
|
|
32665
|
-
diffText: result.diff
|
|
33227
|
+
diffText: stripUnifiedDiffFileHeaders(result.diff),
|
|
33228
|
+
showGutterSeparator: false
|
|
32666
33229
|
}
|
|
32667
33230
|
) }) });
|
|
32668
33231
|
}
|
|
@@ -32691,7 +33254,7 @@ function prettifyToolToken(value) {
|
|
|
32691
33254
|
if (!s) return "";
|
|
32692
33255
|
return s.replace(/[_-]+/g, " ").replace(/\s+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
32693
33256
|
}
|
|
32694
|
-
function
|
|
33257
|
+
function parseArgsRecord2(args) {
|
|
32695
33258
|
if (args == null) return {};
|
|
32696
33259
|
if (typeof args === "string") {
|
|
32697
33260
|
try {
|
|
@@ -32748,7 +33311,7 @@ var TOOL_INVOCATION_TITLES = {
|
|
|
32748
33311
|
function getToolInvocationTitle(internalName, args) {
|
|
32749
33312
|
const key = internalName.trim();
|
|
32750
33313
|
if (key === "edit_tool") {
|
|
32751
|
-
const p =
|
|
33314
|
+
const p = parseArgsRecord2(args);
|
|
32752
33315
|
const edits = p.edits;
|
|
32753
33316
|
if (Array.isArray(edits) && edits.length > 0) {
|
|
32754
33317
|
const allCreate = edits.every(
|
|
@@ -33458,6 +34021,8 @@ var loadSkillTool = createTool({
|
|
|
33458
34021
|
});
|
|
33459
34022
|
|
|
33460
34023
|
// src/app/agent/tools/FileWriteTool/UI.tsx
|
|
34024
|
+
import fs42 from "fs";
|
|
34025
|
+
import { diffLines as diffLines3 } from "diff";
|
|
33461
34026
|
import { jsx as jsx54, jsxs as jsxs37 } from "react/jsx-runtime";
|
|
33462
34027
|
function getFilePath(args) {
|
|
33463
34028
|
if (typeof args?.filepath === "string" && args.filepath.trim()) {
|
|
@@ -33468,6 +34033,49 @@ function getFilePath(args) {
|
|
|
33468
34033
|
}
|
|
33469
34034
|
return void 0;
|
|
33470
34035
|
}
|
|
34036
|
+
function readExistingFileText(filePath) {
|
|
34037
|
+
if (!filePath) return "";
|
|
34038
|
+
try {
|
|
34039
|
+
return fs42.readFileSync(filePath, "utf-8");
|
|
34040
|
+
} catch {
|
|
34041
|
+
return "";
|
|
34042
|
+
}
|
|
34043
|
+
}
|
|
34044
|
+
function countLineDiff2(oldText, newText) {
|
|
34045
|
+
const parts = diffLines3(oldText.replace(/\r\n/g, "\n"), newText.replace(/\r\n/g, "\n"));
|
|
34046
|
+
let added = 0;
|
|
34047
|
+
let removed = 0;
|
|
34048
|
+
for (const part of parts) {
|
|
34049
|
+
const lines = part.value.replace(/\n$/, "").split("\n");
|
|
34050
|
+
const lineCount = part.value === "" ? 0 : lines.length;
|
|
34051
|
+
if (part.added) added += lineCount;
|
|
34052
|
+
if (part.removed) removed += lineCount;
|
|
34053
|
+
}
|
|
34054
|
+
return { added, removed };
|
|
34055
|
+
}
|
|
34056
|
+
function renderChangeCount(added, removed) {
|
|
34057
|
+
return /* @__PURE__ */ jsxs37(Text, { dimColor: true, children: [
|
|
34058
|
+
"(",
|
|
34059
|
+
/* @__PURE__ */ jsxs37(Text, { color: BLUMA_TERMINAL.diffAddedWord, children: [
|
|
34060
|
+
"+",
|
|
34061
|
+
added
|
|
34062
|
+
] }),
|
|
34063
|
+
" ",
|
|
34064
|
+
/* @__PURE__ */ jsxs37(Text, { color: BLUMA_TERMINAL.diffRemovedWord, children: [
|
|
34065
|
+
"-",
|
|
34066
|
+
removed
|
|
34067
|
+
] }),
|
|
34068
|
+
")"
|
|
34069
|
+
] });
|
|
34070
|
+
}
|
|
34071
|
+
function buildUnifiedDiffText(filePath, oldContent, newContent) {
|
|
34072
|
+
const patch = getPatchFromContents({
|
|
34073
|
+
filePath,
|
|
34074
|
+
oldContent,
|
|
34075
|
+
newContent
|
|
34076
|
+
});
|
|
34077
|
+
return patchToUnifiedDiffText(filePath, patch);
|
|
34078
|
+
}
|
|
33471
34079
|
function userFacingName20(args) {
|
|
33472
34080
|
if (!args) return "Wrote";
|
|
33473
34081
|
const p = getFilePath(args)?.split("/").pop() ?? "...";
|
|
@@ -33480,9 +34088,12 @@ function renderToolUseMessage21({ args }) {
|
|
|
33480
34088
|
function renderToolHeader21({ args }) {
|
|
33481
34089
|
const filePath = getFilePath(args) ?? "";
|
|
33482
34090
|
const isNewFile = !args?.hasExistingContent;
|
|
34091
|
+
const content = typeof args?.content === "string" ? args.content : "";
|
|
34092
|
+
const counts = content ? countLineDiff2(isNewFile ? "" : readExistingFileText(filePath), content) : { added: 0, removed: 0 };
|
|
33483
34093
|
return /* @__PURE__ */ jsxs37(Box_default, { flexDirection: "row", flexWrap: "wrap", alignItems: "flex-end", gap: 1, children: [
|
|
33484
|
-
/* @__PURE__ */ jsx54(Text, { bold: true, color: isNewFile ? BLUMA_TERMINAL.
|
|
33485
|
-
/* @__PURE__ */ jsx54(FilePathLink, { filePath, color: BLUMA_TERMINAL.dim })
|
|
34094
|
+
/* @__PURE__ */ jsx54(Text, { bold: true, color: isNewFile ? BLUMA_TERMINAL.m3OnSurface : BLUMA_TERMINAL.err, children: isNewFile ? "Created" : "Updated" }),
|
|
34095
|
+
/* @__PURE__ */ jsx54(FilePathLink, { filePath, color: isNewFile ? BLUMA_TERMINAL.dim : BLUMA_TERMINAL.err }),
|
|
34096
|
+
content ? renderChangeCount(counts.added, counts.removed) : null
|
|
33486
34097
|
] });
|
|
33487
34098
|
}
|
|
33488
34099
|
function renderToolMessage20({
|
|
@@ -33494,12 +34105,15 @@ function renderToolMessage20({
|
|
|
33494
34105
|
const filePath = getFilePath(p);
|
|
33495
34106
|
const payload = resolveToolPayload(result);
|
|
33496
34107
|
const content = typeof p?.content === "string" ? p.content : "";
|
|
34108
|
+
const isNewFile = !p?.hasExistingContent;
|
|
34109
|
+
const previousContent = isNewFile ? "" : readExistingFileText(filePath);
|
|
34110
|
+
const diffText = content && filePath ? buildUnifiedDiffText(filePath, previousContent, content) : "";
|
|
33497
34111
|
return /* @__PURE__ */ jsx54(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx54(MessageResponse, { children: /* @__PURE__ */ jsxs37(Box_default, { flexDirection: "column", marginTop: 1, children: [
|
|
33498
34112
|
/* @__PURE__ */ jsxs37(Box_default, { flexDirection: "row", flexWrap: "wrap", alignItems: "flex-end", children: [
|
|
33499
34113
|
/* @__PURE__ */ jsx54(ToolUseLoader, { state: state2 ?? "pending" }),
|
|
33500
34114
|
renderToolHeader21({ args: p })
|
|
33501
34115
|
] }),
|
|
33502
|
-
/* @__PURE__ */ jsx54(Box_default, { flexDirection: "column", paddingLeft: 1,
|
|
34116
|
+
/* @__PURE__ */ jsx54(Box_default, { flexDirection: "column", paddingLeft: 1, children: payload != null ? renderToolResultMessage21(payload, { filePath, content }) : /* @__PURE__ */ jsx54(Box_default, { flexDirection: "column", children: /* @__PURE__ */ jsx54(Box_default, { marginTop: 0, width: "100%", children: content ? /* @__PURE__ */ jsx54(Box_default, { width: "100%", flexGrow: 1, flexDirection: "column", alignItems: "stretch", children: /* @__PURE__ */ jsx54(SimpleDiff, { text: diffText, maxHeight: 4 }) }) : /* @__PURE__ */ jsxs37(Text, { dimColor: true, children: [
|
|
33503
34117
|
"Writing to ",
|
|
33504
34118
|
filePath ?? "...",
|
|
33505
34119
|
" (",
|
|
@@ -33520,15 +34134,32 @@ function renderToolResultMessage21(result, context) {
|
|
|
33520
34134
|
}
|
|
33521
34135
|
const filePath = payload.filepath ?? payload.file_path ?? "";
|
|
33522
34136
|
const bytes = payload.bytes_written ?? 0;
|
|
33523
|
-
const
|
|
33524
|
-
|
|
33525
|
-
|
|
34137
|
+
const resultFilePath = filePath || context?.filePath || "file.txt";
|
|
34138
|
+
const previousContent = typeof payload.previous_content === "string" ? payload.previous_content : void 0;
|
|
34139
|
+
const currentContent = typeof payload.content === "string" ? payload.content : typeof context?.args?.content === "string" ? (context?.args).content : typeof context?.content === "string" ? context.content : void 0;
|
|
34140
|
+
const canRenderDiff = typeof currentContent === "string" && currentContent.trim() && typeof previousContent === "string";
|
|
34141
|
+
return /* @__PURE__ */ jsx54(Box_default, { flexDirection: "column", children: canRenderDiff ? /* @__PURE__ */ jsx54(Box_default, { marginTop: 0, width: "100%", flexDirection: "column", alignItems: "stretch", flexGrow: 1, children: /* @__PURE__ */ jsx54(
|
|
34142
|
+
SimpleDiff,
|
|
34143
|
+
{
|
|
34144
|
+
text: buildUnifiedDiffText(resultFilePath, previousContent ?? "", currentContent ?? ""),
|
|
34145
|
+
maxHeight: 200
|
|
34146
|
+
}
|
|
34147
|
+
) }) : typeof currentContent === "string" && currentContent.trim() ? /* @__PURE__ */ jsx54(
|
|
34148
|
+
Box_default,
|
|
33526
34149
|
{
|
|
33527
|
-
|
|
33528
|
-
|
|
33529
|
-
|
|
34150
|
+
width: "100%",
|
|
34151
|
+
backgroundColor: BLUMA_TERMINAL.diffAdded,
|
|
34152
|
+
marginTop: 1,
|
|
34153
|
+
children: /* @__PURE__ */ jsx54(
|
|
34154
|
+
HighlightedCode,
|
|
34155
|
+
{
|
|
34156
|
+
code: currentContent,
|
|
34157
|
+
filePath: resultFilePath,
|
|
34158
|
+
maxLines: 2e3
|
|
34159
|
+
}
|
|
34160
|
+
)
|
|
33530
34161
|
}
|
|
33531
|
-
)
|
|
34162
|
+
) : null });
|
|
33532
34163
|
}
|
|
33533
34164
|
|
|
33534
34165
|
// src/app/agent/tools/FileWriteTool/index.ts
|
|
@@ -35961,162 +36592,6 @@ var ConfirmationPrompt = memo13(ConfirmationPromptComponent);
|
|
|
35961
36592
|
// src/app/ui/WorkingTimer.tsx
|
|
35962
36593
|
import { useState as useState15, useEffect as useEffect13, memo as memo14, useRef as useRef6 } from "react";
|
|
35963
36594
|
import { EventEmitter as EventEmitter6 } from "events";
|
|
35964
|
-
|
|
35965
|
-
// src/app/ui/utils/toolActionLabels.ts
|
|
35966
|
-
function parseArgsRecord2(args) {
|
|
35967
|
-
if (args == null) return {};
|
|
35968
|
-
if (typeof args === "string") {
|
|
35969
|
-
try {
|
|
35970
|
-
return JSON.parse(args);
|
|
35971
|
-
} catch {
|
|
35972
|
-
return {};
|
|
35973
|
-
}
|
|
35974
|
-
}
|
|
35975
|
-
if (typeof args === "object") {
|
|
35976
|
-
return args;
|
|
35977
|
-
}
|
|
35978
|
-
return {};
|
|
35979
|
-
}
|
|
35980
|
-
function getToolActionLabel(toolName, args) {
|
|
35981
|
-
const p = parseArgsRecord2(args);
|
|
35982
|
-
switch (toolName) {
|
|
35983
|
-
case "shell_command":
|
|
35984
|
-
case "run_command": {
|
|
35985
|
-
const cmd = typeof p.command === "string" ? p.command : "";
|
|
35986
|
-
const truncated = cmd.length > 40 ? `${cmd.slice(0, 40)}\u2026` : cmd;
|
|
35987
|
-
return truncated ? `Bash ${truncated}` : "Bash";
|
|
35988
|
-
}
|
|
35989
|
-
case "command_status":
|
|
35990
|
-
return "Status";
|
|
35991
|
-
case "send_command_input":
|
|
35992
|
-
return "Input";
|
|
35993
|
-
case "kill_command":
|
|
35994
|
-
return "Kill";
|
|
35995
|
-
case "read_file_lines": {
|
|
35996
|
-
const filepath = typeof p.filepath === "string" ? p.filepath : "";
|
|
35997
|
-
const file = filepath.split("/").pop() || filepath;
|
|
35998
|
-
return file ? `Read ${file}` : "Read";
|
|
35999
|
-
}
|
|
36000
|
-
case "ls_tool":
|
|
36001
|
-
return "List";
|
|
36002
|
-
case "count_file_lines": {
|
|
36003
|
-
const filepath = typeof p.filepath === "string" ? p.filepath : "";
|
|
36004
|
-
const file = filepath.split("/").pop() || filepath;
|
|
36005
|
-
return file ? `Count ${file}` : "Count";
|
|
36006
|
-
}
|
|
36007
|
-
case "edit_tool": {
|
|
36008
|
-
const edits = p.edits;
|
|
36009
|
-
const count = Array.isArray(edits) ? edits.length : 1;
|
|
36010
|
-
const filepath = typeof p.file_path === "string" ? p.file_path : Array.isArray(edits) && edits[0]?.file_path ? edits[0].file_path : "";
|
|
36011
|
-
const file = filepath ? filepath.split("/").pop() : "file";
|
|
36012
|
-
return count === 1 ? `Edit ${file}` : `Edit ${count} files`;
|
|
36013
|
-
}
|
|
36014
|
-
case "file_write": {
|
|
36015
|
-
const filepath = typeof p.filepath === "string" ? p.filepath : "";
|
|
36016
|
-
const file = filepath.split("/").pop() || filepath;
|
|
36017
|
-
return file ? `Write ${file}` : "Write";
|
|
36018
|
-
}
|
|
36019
|
-
case "grep_search": {
|
|
36020
|
-
const query = typeof p.query === "string" ? p.query : "";
|
|
36021
|
-
const truncated = query.length > 30 ? `${query.slice(0, 30)}\u2026` : query;
|
|
36022
|
-
return truncated ? `Grep ${truncated}` : "Grep";
|
|
36023
|
-
}
|
|
36024
|
-
case "find_by_name": {
|
|
36025
|
-
const pattern = typeof p.pattern === "string" ? p.pattern : "";
|
|
36026
|
-
return pattern ? `Find ${pattern}` : "Find files";
|
|
36027
|
-
}
|
|
36028
|
-
case "view_file_outline": {
|
|
36029
|
-
const filepath = typeof p.file_path === "string" ? p.file_path : "";
|
|
36030
|
-
const file = filepath.split("/").pop() || filepath;
|
|
36031
|
-
return file ? `Outline ${file}` : "Outline";
|
|
36032
|
-
}
|
|
36033
|
-
case "web_fetch": {
|
|
36034
|
-
const url = typeof p.url === "string" ? p.url : "";
|
|
36035
|
-
const truncated = url.length > 40 ? `${url.slice(0, 40)}\u2026` : url;
|
|
36036
|
-
return truncated ? `Fetch ${truncated}` : "Fetch URL";
|
|
36037
|
-
}
|
|
36038
|
-
case "search_web": {
|
|
36039
|
-
const query = typeof p.query === "string" ? p.query : "";
|
|
36040
|
-
const truncated = query.length > 30 ? `${query.slice(0, 30)}\u2026` : query;
|
|
36041
|
-
return truncated ? `Web ${truncated}` : "Web search";
|
|
36042
|
-
}
|
|
36043
|
-
case "spawn_agent": {
|
|
36044
|
-
const title = typeof p.title === "string" ? p.title : "";
|
|
36045
|
-
const task = typeof p.task === "string" ? p.task : "";
|
|
36046
|
-
const label = title ? prettifyToolToken(title) : task ? task.slice(0, 40) : "task";
|
|
36047
|
-
return `Spawn ${label}`;
|
|
36048
|
-
}
|
|
36049
|
-
case "wait_agent":
|
|
36050
|
-
return "Wait";
|
|
36051
|
-
case "list_agents":
|
|
36052
|
-
return "Agents";
|
|
36053
|
-
case "todo": {
|
|
36054
|
-
const action = typeof p.action === "string" ? p.action : "update";
|
|
36055
|
-
return `Todo ${action}`;
|
|
36056
|
-
}
|
|
36057
|
-
case "task_boundary": {
|
|
36058
|
-
const mode = typeof p.mode === "string" ? p.mode : "";
|
|
36059
|
-
const taskName = typeof p.task_name === "string" ? p.task_name : "";
|
|
36060
|
-
return taskName ? `${mode} ${taskName}` : `Task ${mode}`;
|
|
36061
|
-
}
|
|
36062
|
-
case "task_create":
|
|
36063
|
-
case "task_list":
|
|
36064
|
-
case "task_get":
|
|
36065
|
-
case "task_update":
|
|
36066
|
-
case "task_stop": {
|
|
36067
|
-
const title = typeof p.title === "string" ? p.title : "";
|
|
36068
|
-
return title ? `Task ${prettifyToolToken(title)}` : "Task";
|
|
36069
|
-
}
|
|
36070
|
-
case "load_skill": {
|
|
36071
|
-
const skill = typeof p.skill_name === "string" ? p.skill_name : "";
|
|
36072
|
-
return skill ? `Skill ${skill.replace(/[_-]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())}` : "Skill";
|
|
36073
|
-
}
|
|
36074
|
-
case "coding_memory": {
|
|
36075
|
-
const action = typeof p.action === "string" ? p.action : "update";
|
|
36076
|
-
return `Memory ${action}`;
|
|
36077
|
-
}
|
|
36078
|
-
case "read_artifact": {
|
|
36079
|
-
const filename = typeof p.filename === "string" ? p.filename : "";
|
|
36080
|
-
return filename ? `Read ${filename}` : "Read artifact";
|
|
36081
|
-
}
|
|
36082
|
-
case "message":
|
|
36083
|
-
return "Message";
|
|
36084
|
-
case "ask_user_question":
|
|
36085
|
-
return "Question";
|
|
36086
|
-
case "enter_plan_mode":
|
|
36087
|
-
return "Plan mode";
|
|
36088
|
-
case "exit_plan_mode":
|
|
36089
|
-
return "Exit plan";
|
|
36090
|
-
case "list_mcp_resources":
|
|
36091
|
-
return "MCP resources";
|
|
36092
|
-
case "read_mcp_resource": {
|
|
36093
|
-
const server = typeof p.server === "string" ? p.server : "";
|
|
36094
|
-
const uri = typeof p.uri === "string" ? p.uri : "";
|
|
36095
|
-
return `MCP ${server || uri || "resource"}`;
|
|
36096
|
-
}
|
|
36097
|
-
case "cron_create":
|
|
36098
|
-
return "Schedule";
|
|
36099
|
-
case "cron_list":
|
|
36100
|
-
return "Reminders";
|
|
36101
|
-
case "cron_delete":
|
|
36102
|
-
return "Cancel reminder";
|
|
36103
|
-
case "notebook_edit": {
|
|
36104
|
-
const filepath = typeof p.filepath === "string" ? p.filepath : "";
|
|
36105
|
-
const file = filepath.split("/").pop() || filepath;
|
|
36106
|
-
return file ? `Notebook ${file}` : "Notebook";
|
|
36107
|
-
}
|
|
36108
|
-
case "lsp_query": {
|
|
36109
|
-
const operation = typeof p.operation === "string" ? p.operation : "";
|
|
36110
|
-
return `LSP ${operation || "query"}`;
|
|
36111
|
-
}
|
|
36112
|
-
default: {
|
|
36113
|
-
const pretty = toolName.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
36114
|
-
return pretty || "Working";
|
|
36115
|
-
}
|
|
36116
|
-
}
|
|
36117
|
-
}
|
|
36118
|
-
|
|
36119
|
-
// src/app/ui/WorkingTimer.tsx
|
|
36120
36595
|
import { Fragment as Fragment8, jsx as jsx77, jsxs as jsxs60 } from "react/jsx-runtime";
|
|
36121
36596
|
var SHIMMER_CYCLE_MS = 2e3;
|
|
36122
36597
|
var SHIMMER_TICK_MS = 80;
|
|
@@ -36163,9 +36638,6 @@ var WorkingTimerComponent = ({
|
|
|
36163
36638
|
finalElapsedMs: externalFinalElapsedMs
|
|
36164
36639
|
// External final elapsed time (from parent)
|
|
36165
36640
|
}) => {
|
|
36166
|
-
if (startedAtMs == null && externalFinalElapsedMs == null) {
|
|
36167
|
-
return null;
|
|
36168
|
-
}
|
|
36169
36641
|
const [currentAction, setCurrentAction] = useState15("working");
|
|
36170
36642
|
const [nowTick, setNowTick] = useState15(() => Date.now());
|
|
36171
36643
|
const [internalFinalElapsedMs, setInternalFinalElapsedMs] = useState15(null);
|
|
@@ -36206,11 +36678,10 @@ var WorkingTimerComponent = ({
|
|
|
36206
36678
|
}, [startedAtMs]);
|
|
36207
36679
|
useEffect13(() => {
|
|
36208
36680
|
const localBus = localEventBusRef.current;
|
|
36209
|
-
if (startedAtMs == null) return;
|
|
36210
36681
|
let isMounted = true;
|
|
36211
36682
|
completionHandledRef.current = false;
|
|
36212
36683
|
const handleTurnComplete = (data) => {
|
|
36213
|
-
if (!isMounted || completionHandledRef.current) return;
|
|
36684
|
+
if (!isMounted || completionHandledRef.current || startedAtMs == null) return;
|
|
36214
36685
|
const elapsedMs2 = typeof data?.elapsedMs === "number" ? data.elapsedMs : null;
|
|
36215
36686
|
if (elapsedMs2 == null) return;
|
|
36216
36687
|
completionHandledRef.current = true;
|
|
@@ -36235,7 +36706,10 @@ var WorkingTimerComponent = ({
|
|
|
36235
36706
|
localBus.removeAllListeners();
|
|
36236
36707
|
};
|
|
36237
36708
|
}, [eventBus, startedAtMs]);
|
|
36238
|
-
|
|
36709
|
+
if (startedAtMs == null && externalFinalElapsedMs == null) {
|
|
36710
|
+
return null;
|
|
36711
|
+
}
|
|
36712
|
+
const displayAction = dynamicActionLabel || (taskStatus || (currentAction !== "working" ? currentAction : isReasoning ? getReasoningActionStatus().action : currentAction)).trim() || "working";
|
|
36239
36713
|
const actionLine = `${displayAction}`;
|
|
36240
36714
|
const elapsedMs = finalElapsedMs != null ? finalElapsedMs : startedAtMs != null ? Math.max(0, nowTick - startedAtMs) : 0;
|
|
36241
36715
|
const elapsedLabel = formatTurnDurationMs(elapsedMs);
|
|
@@ -36379,13 +36853,13 @@ var StatusBar = memo16(() => {
|
|
|
36379
36853
|
} = snapshot;
|
|
36380
36854
|
void renderKey;
|
|
36381
36855
|
const colors = {
|
|
36382
|
-
primary: BLUMA_TERMINAL.
|
|
36383
|
-
secondary: BLUMA_TERMINAL.
|
|
36384
|
-
accent: BLUMA_TERMINAL.
|
|
36385
|
-
success: BLUMA_TERMINAL.
|
|
36386
|
-
warning: BLUMA_TERMINAL.
|
|
36387
|
-
error: BLUMA_TERMINAL.
|
|
36388
|
-
muted: BLUMA_TERMINAL.
|
|
36856
|
+
primary: BLUMA_TERMINAL.subtle,
|
|
36857
|
+
secondary: BLUMA_TERMINAL.subtle,
|
|
36858
|
+
accent: BLUMA_TERMINAL.subtle,
|
|
36859
|
+
success: BLUMA_TERMINAL.subtle,
|
|
36860
|
+
warning: BLUMA_TERMINAL.subtle,
|
|
36861
|
+
error: BLUMA_TERMINAL.subtle,
|
|
36862
|
+
muted: BLUMA_TERMINAL.subtle
|
|
36389
36863
|
};
|
|
36390
36864
|
const remainingPercent = tokenBudget > 0 ? Math.max(0, Math.round((tokenBudget - tokenCount) / tokenBudget * 100)) : 100;
|
|
36391
36865
|
let remainingColor = colors.muted;
|
|
@@ -36399,27 +36873,27 @@ var StatusBar = memo16(() => {
|
|
|
36399
36873
|
const shortWorkdir = workdir ? workdir.split("/").filter(Boolean).pop() || workdir : "";
|
|
36400
36874
|
const compressionText = isCompressing ? ` \xB7 compressing ${compressionProgress}%` : "";
|
|
36401
36875
|
const planModeText = isPlanMode ? /* @__PURE__ */ jsxs62(Fragment9, { children: [
|
|
36402
|
-
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.
|
|
36403
|
-
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.
|
|
36876
|
+
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.subtle, children: "\xB7" }),
|
|
36877
|
+
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.subtle, children: "Plan Mode" })
|
|
36404
36878
|
] }) : null;
|
|
36405
36879
|
const agentModeIndicator = (() => {
|
|
36406
36880
|
if (!agentMode || agentMode === "default") return null;
|
|
36407
36881
|
const mode = agentMode.toLowerCase();
|
|
36408
36882
|
if (mode === "coordinator") {
|
|
36409
36883
|
return /* @__PURE__ */ jsxs62(Fragment9, { children: [
|
|
36410
|
-
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.
|
|
36411
|
-
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.
|
|
36884
|
+
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.subtle, children: "\xB7" }),
|
|
36885
|
+
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.subtle, children: "Coordinator" })
|
|
36412
36886
|
] });
|
|
36413
36887
|
}
|
|
36414
36888
|
if (mode === "plan") {
|
|
36415
36889
|
return /* @__PURE__ */ jsxs62(Fragment9, { children: [
|
|
36416
|
-
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.
|
|
36417
|
-
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.
|
|
36890
|
+
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.subtle, children: "\xB7" }),
|
|
36891
|
+
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.subtle, children: "Plan" })
|
|
36418
36892
|
] });
|
|
36419
36893
|
}
|
|
36420
36894
|
return /* @__PURE__ */ jsxs62(Fragment9, { children: [
|
|
36421
|
-
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.
|
|
36422
|
-
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.
|
|
36895
|
+
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.subtle, children: "\xB7" }),
|
|
36896
|
+
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.subtle, children: agentMode })
|
|
36423
36897
|
] });
|
|
36424
36898
|
})();
|
|
36425
36899
|
const modelText = modelName ? ` \xB7 model: ${modelName}` : "";
|
|
@@ -36435,25 +36909,25 @@ var StatusBar = memo16(() => {
|
|
|
36435
36909
|
flexShrink: 0,
|
|
36436
36910
|
children: [
|
|
36437
36911
|
shortWorkdir && /* @__PURE__ */ jsxs62(Fragment9, { children: [
|
|
36438
|
-
/* @__PURE__ */ jsxs62(Text, { color: BLUMA_TERMINAL.
|
|
36912
|
+
/* @__PURE__ */ jsxs62(Text, { color: BLUMA_TERMINAL.subtle, children: [
|
|
36439
36913
|
"~/",
|
|
36440
36914
|
shortWorkdir
|
|
36441
36915
|
] }),
|
|
36442
|
-
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.
|
|
36916
|
+
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.subtle, children: "\xB7" })
|
|
36443
36917
|
] }),
|
|
36444
|
-
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.
|
|
36918
|
+
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.subtle, children: "Context" }),
|
|
36445
36919
|
/* @__PURE__ */ jsxs62(Text, { color: remainingColor, children: [
|
|
36446
36920
|
remainingPercent,
|
|
36447
36921
|
"% left"
|
|
36448
36922
|
] }),
|
|
36449
36923
|
modelText && /* @__PURE__ */ jsxs62(Fragment9, { children: [
|
|
36450
|
-
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.
|
|
36451
|
-
/* @__PURE__ */ jsx79(Text, {
|
|
36924
|
+
/* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.subtle, children: modelText }),
|
|
36925
|
+
/* @__PURE__ */ jsx79(Text, { subtleColor: true, children: "\xB7" })
|
|
36452
36926
|
] }),
|
|
36453
|
-
compressionText && /* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.
|
|
36927
|
+
compressionText && /* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.subtle, children: compressionText }),
|
|
36454
36928
|
agentModeIndicator,
|
|
36455
36929
|
planModeText,
|
|
36456
|
-
projectsText && /* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.
|
|
36930
|
+
projectsText && /* @__PURE__ */ jsx79(Text, { color: BLUMA_TERMINAL.subtle, children: projectsText })
|
|
36457
36931
|
]
|
|
36458
36932
|
}
|
|
36459
36933
|
);
|
|
@@ -36841,7 +37315,7 @@ function ToolMessageComponent({
|
|
|
36841
37315
|
if (!normalizedResult) return null;
|
|
36842
37316
|
if (entry?.renderToolResultMessage) {
|
|
36843
37317
|
const payload = normalizedResult.status === "success" ? normalizedResult.data : normalizedResult;
|
|
36844
|
-
return entry.renderToolResultMessage(payload, { verbose: false });
|
|
37318
|
+
return entry.renderToolResultMessage(payload, { verbose: false, args });
|
|
36845
37319
|
}
|
|
36846
37320
|
return renderRawJson(normalizedResult);
|
|
36847
37321
|
})();
|
|
@@ -38279,7 +38753,7 @@ var renderCode = () => {
|
|
|
38279
38753
|
// src/app/agent/core/thread/thread_store.ts
|
|
38280
38754
|
import path46 from "path";
|
|
38281
38755
|
import os34 from "os";
|
|
38282
|
-
import { promises as
|
|
38756
|
+
import { promises as fs43 } from "fs";
|
|
38283
38757
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
38284
38758
|
var INDEX_VERSION = 1;
|
|
38285
38759
|
var fileLocks2 = /* @__PURE__ */ new Map();
|
|
@@ -38314,10 +38788,10 @@ var ThreadStore = class {
|
|
|
38314
38788
|
* Inicializa o diretório de threads
|
|
38315
38789
|
*/
|
|
38316
38790
|
async initialize() {
|
|
38317
|
-
await
|
|
38318
|
-
await
|
|
38791
|
+
await fs43.mkdir(this.threadsDir, { recursive: true });
|
|
38792
|
+
await fs43.mkdir(this.archiveDir, { recursive: true });
|
|
38319
38793
|
try {
|
|
38320
|
-
await
|
|
38794
|
+
await fs43.access(this.indexPath);
|
|
38321
38795
|
} catch {
|
|
38322
38796
|
await this.saveIndex({ version: INDEX_VERSION, threads: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() });
|
|
38323
38797
|
}
|
|
@@ -38346,7 +38820,7 @@ var ThreadStore = class {
|
|
|
38346
38820
|
async loadIndex() {
|
|
38347
38821
|
return withFileLock2(this.indexPath, async () => {
|
|
38348
38822
|
try {
|
|
38349
|
-
const content = await
|
|
38823
|
+
const content = await fs43.readFile(this.indexPath, "utf-8");
|
|
38350
38824
|
return JSON.parse(content);
|
|
38351
38825
|
} catch {
|
|
38352
38826
|
return { version: INDEX_VERSION, threads: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
|
|
@@ -38357,8 +38831,8 @@ var ThreadStore = class {
|
|
|
38357
38831
|
return withFileLock2(this.indexPath, async () => {
|
|
38358
38832
|
index.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
38359
38833
|
const tempPath = `${this.indexPath}.${Date.now()}.tmp`;
|
|
38360
|
-
await
|
|
38361
|
-
await
|
|
38834
|
+
await fs43.writeFile(tempPath, JSON.stringify(index, null, 2), "utf-8");
|
|
38835
|
+
await fs43.rename(tempPath, this.indexPath);
|
|
38362
38836
|
});
|
|
38363
38837
|
}
|
|
38364
38838
|
// ==================== Git Info ====================
|
|
@@ -38554,7 +39028,7 @@ var ThreadStore = class {
|
|
|
38554
39028
|
const oldPath = this.getHistoryPath(threadId);
|
|
38555
39029
|
const newPath = path46.join(this.archiveDir, `${threadId}.jsonl`);
|
|
38556
39030
|
try {
|
|
38557
|
-
await
|
|
39031
|
+
await fs43.rename(oldPath, newPath);
|
|
38558
39032
|
} catch (e) {
|
|
38559
39033
|
if (e.code !== "ENOENT") throw e;
|
|
38560
39034
|
}
|
|
@@ -38578,7 +39052,7 @@ var ThreadStore = class {
|
|
|
38578
39052
|
const oldPath = path46.join(this.archiveDir, `${threadId}.jsonl`);
|
|
38579
39053
|
const newPath = this.getHistoryPath(threadId);
|
|
38580
39054
|
try {
|
|
38581
|
-
await
|
|
39055
|
+
await fs43.rename(oldPath, newPath);
|
|
38582
39056
|
} catch (e) {
|
|
38583
39057
|
if (e.code !== "ENOENT") throw e;
|
|
38584
39058
|
}
|
|
@@ -38599,7 +39073,7 @@ var ThreadStore = class {
|
|
|
38599
39073
|
if (entryIndex === -1) return false;
|
|
38600
39074
|
const entry = index.threads[entryIndex];
|
|
38601
39075
|
try {
|
|
38602
|
-
await
|
|
39076
|
+
await fs43.unlink(entry.historyPath);
|
|
38603
39077
|
} catch {
|
|
38604
39078
|
}
|
|
38605
39079
|
index.threads.splice(entryIndex, 1);
|
|
@@ -38626,7 +39100,7 @@ var ThreadStore = class {
|
|
|
38626
39100
|
for (const msg of history.messages) {
|
|
38627
39101
|
lines.push(JSON.stringify({ type: "message", ...msg }));
|
|
38628
39102
|
}
|
|
38629
|
-
await
|
|
39103
|
+
await fs43.writeFile(historyPath, lines.join("\n") + "\n", "utf-8");
|
|
38630
39104
|
}
|
|
38631
39105
|
/**
|
|
38632
39106
|
* Carrega o histórico de uma thread
|
|
@@ -38636,7 +39110,7 @@ var ThreadStore = class {
|
|
|
38636
39110
|
const entry = index.threads.find((t) => t.threadId === threadId);
|
|
38637
39111
|
if (!entry) return null;
|
|
38638
39112
|
try {
|
|
38639
|
-
const content = await
|
|
39113
|
+
const content = await fs43.readFile(entry.historyPath, "utf-8");
|
|
38640
39114
|
const lines = content.split("\n").filter(Boolean);
|
|
38641
39115
|
const history = {
|
|
38642
39116
|
threadId,
|
|
@@ -38669,7 +39143,7 @@ var ThreadStore = class {
|
|
|
38669
39143
|
const entry = index.threads.find((t) => t.threadId === threadId);
|
|
38670
39144
|
if (!entry) throw new Error(`Thread not found: ${threadId}`);
|
|
38671
39145
|
const lines = messages.map((msg) => JSON.stringify({ type: "message", ...msg }));
|
|
38672
|
-
await
|
|
39146
|
+
await fs43.appendFile(entry.historyPath, lines.join("\n") + "\n", "utf-8");
|
|
38673
39147
|
entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
38674
39148
|
await this.saveIndex(index);
|
|
38675
39149
|
}
|
|
@@ -38957,7 +39431,7 @@ function formatRelativeTime(iso) {
|
|
|
38957
39431
|
if (diffDays < 7) return `h\xE1 ${diffDays}d`;
|
|
38958
39432
|
return formatDate(iso);
|
|
38959
39433
|
}
|
|
38960
|
-
function
|
|
39434
|
+
function truncate4(str, max) {
|
|
38961
39435
|
if (str.length <= max) return str;
|
|
38962
39436
|
return str.slice(0, max - 3) + "...";
|
|
38963
39437
|
}
|
|
@@ -38983,7 +39457,7 @@ async function renderCurrentThread() {
|
|
|
38983
39457
|
] }),
|
|
38984
39458
|
/* @__PURE__ */ jsxs79(Text, { children: [
|
|
38985
39459
|
/* @__PURE__ */ jsx96(Text, { dimColor: true, children: "Preview: " }),
|
|
38986
|
-
/* @__PURE__ */ jsx96(Text, { dimColor: true, children:
|
|
39460
|
+
/* @__PURE__ */ jsx96(Text, { dimColor: true, children: truncate4(metadata.preview, 60) })
|
|
38987
39461
|
] }),
|
|
38988
39462
|
/* @__PURE__ */ jsxs79(Text, { children: [
|
|
38989
39463
|
/* @__PURE__ */ jsx96(Text, { dimColor: true, children: "Modelo: " }),
|
|
@@ -38998,7 +39472,7 @@ async function renderCurrentThread() {
|
|
|
38998
39472
|
/* @__PURE__ */ jsxs79(Text, { dimColor: true, children: [
|
|
38999
39473
|
metadata.gitInfo.branch || "?",
|
|
39000
39474
|
" @ ",
|
|
39001
|
-
|
|
39475
|
+
truncate4(metadata.gitInfo.sha || "?", 8)
|
|
39002
39476
|
] })
|
|
39003
39477
|
] }),
|
|
39004
39478
|
/* @__PURE__ */ jsxs79(Text, { children: [
|
|
@@ -39066,7 +39540,7 @@ async function renderThreadList(args) {
|
|
|
39066
39540
|
}
|
|
39067
39541
|
var ThreadListItem = ({ thread, isActive, index }) => {
|
|
39068
39542
|
const statusColor = thread.status === "archived" ? BLUMA_TERMINAL.warning : BLUMA_TERMINAL.success;
|
|
39069
|
-
const name = thread.name ||
|
|
39543
|
+
const name = thread.name || truncate4(thread.preview, 40);
|
|
39070
39544
|
return /* @__PURE__ */ jsxs79(Box_default, { flexDirection: "column", children: [
|
|
39071
39545
|
/* @__PURE__ */ jsxs79(Box_default, { gap: 1, children: [
|
|
39072
39546
|
/* @__PURE__ */ jsxs79(Text, { dimColor: true, children: [
|
|
@@ -41535,15 +42009,15 @@ import semverGt from "semver/functions/gt.js";
|
|
|
41535
42009
|
import semverValid from "semver/functions/valid.js";
|
|
41536
42010
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
41537
42011
|
import path47 from "path";
|
|
41538
|
-
import
|
|
42012
|
+
import fs44 from "fs";
|
|
41539
42013
|
var BLUMA_PACKAGE_NAME = "@nomad-e/bluma-cli";
|
|
41540
42014
|
function findBlumaPackageJson(startDir) {
|
|
41541
42015
|
let dir = startDir;
|
|
41542
42016
|
for (let i = 0; i < 12; i++) {
|
|
41543
42017
|
const candidate = path47.join(dir, "package.json");
|
|
41544
|
-
if (
|
|
42018
|
+
if (fs44.existsSync(candidate)) {
|
|
41545
42019
|
try {
|
|
41546
|
-
const raw =
|
|
42020
|
+
const raw = fs44.readFileSync(candidate, "utf8");
|
|
41547
42021
|
const parsed = JSON.parse(raw);
|
|
41548
42022
|
if (parsed?.name === BLUMA_PACKAGE_NAME && parsed?.version) {
|
|
41549
42023
|
return { name: parsed.name, version: String(parsed.version) };
|
|
@@ -41574,8 +42048,8 @@ function resolveInstalledBlumaPackage() {
|
|
|
41574
42048
|
if (argv1 && !argv1.startsWith("-")) {
|
|
41575
42049
|
try {
|
|
41576
42050
|
let resolved = argv1;
|
|
41577
|
-
if (path47.isAbsolute(argv1) &&
|
|
41578
|
-
resolved =
|
|
42051
|
+
if (path47.isAbsolute(argv1) && fs44.existsSync(argv1)) {
|
|
42052
|
+
resolved = fs44.realpathSync(argv1);
|
|
41579
42053
|
} else {
|
|
41580
42054
|
resolved = path47.resolve(process.cwd(), argv1);
|
|
41581
42055
|
}
|
|
@@ -42398,16 +42872,16 @@ function usePlanMode() {
|
|
|
42398
42872
|
|
|
42399
42873
|
// src/app/hooks/useAgentMode.ts
|
|
42400
42874
|
import { useState as useState22, useEffect as useEffect20, useCallback as useCallback9 } from "react";
|
|
42401
|
-
import * as
|
|
42875
|
+
import * as fs45 from "fs";
|
|
42402
42876
|
import * as path48 from "path";
|
|
42403
42877
|
import { homedir as homedir3 } from "os";
|
|
42404
42878
|
var SETTINGS_PATH = path48.join(homedir3(), ".bluma", "settings.json");
|
|
42405
42879
|
function readAgentModeFromFile() {
|
|
42406
42880
|
try {
|
|
42407
|
-
if (!
|
|
42881
|
+
if (!fs45.existsSync(SETTINGS_PATH)) {
|
|
42408
42882
|
return "default";
|
|
42409
42883
|
}
|
|
42410
|
-
const content =
|
|
42884
|
+
const content = fs45.readFileSync(SETTINGS_PATH, "utf-8");
|
|
42411
42885
|
const settings = JSON.parse(content);
|
|
42412
42886
|
return settings.agentMode || "default";
|
|
42413
42887
|
} catch (error) {
|
|
@@ -42426,16 +42900,16 @@ function useAgentMode() {
|
|
|
42426
42900
|
}, []);
|
|
42427
42901
|
const updateAgentMode = useCallback9((mode) => {
|
|
42428
42902
|
try {
|
|
42429
|
-
if (!
|
|
42430
|
-
|
|
42903
|
+
if (!fs45.existsSync(SETTINGS_PATH)) {
|
|
42904
|
+
fs45.mkdirSync(path48.dirname(SETTINGS_PATH), { recursive: true });
|
|
42431
42905
|
}
|
|
42432
42906
|
let settings = {};
|
|
42433
|
-
if (
|
|
42434
|
-
const content =
|
|
42907
|
+
if (fs45.existsSync(SETTINGS_PATH)) {
|
|
42908
|
+
const content = fs45.readFileSync(SETTINGS_PATH, "utf-8");
|
|
42435
42909
|
settings = JSON.parse(content);
|
|
42436
42910
|
}
|
|
42437
42911
|
settings.agentMode = mode;
|
|
42438
|
-
|
|
42912
|
+
fs45.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf-8");
|
|
42439
42913
|
setAgentMode(mode);
|
|
42440
42914
|
} catch (error) {
|
|
42441
42915
|
console.error("Failed to update agent mode:", error);
|
|
@@ -43691,9 +44165,9 @@ async function runAgentMode() {
|
|
|
43691
44165
|
try {
|
|
43692
44166
|
if (inputFileIndex !== -1 && args[inputFileIndex + 1]) {
|
|
43693
44167
|
const filePath = args[inputFileIndex + 1];
|
|
43694
|
-
rawPayload =
|
|
44168
|
+
rawPayload = fs46.readFileSync(filePath, "utf-8");
|
|
43695
44169
|
} else {
|
|
43696
|
-
rawPayload =
|
|
44170
|
+
rawPayload = fs46.readFileSync(0, "utf-8");
|
|
43697
44171
|
}
|
|
43698
44172
|
} catch (err) {
|
|
43699
44173
|
writeAgentEvent(registrySessionId, {
|
|
@@ -43893,7 +44367,7 @@ function readCliPackageVersion() {
|
|
|
43893
44367
|
try {
|
|
43894
44368
|
const base = path49.dirname(fileURLToPath7(import.meta.url));
|
|
43895
44369
|
const pkgPath = path49.join(base, "..", "package.json");
|
|
43896
|
-
const j = JSON.parse(
|
|
44370
|
+
const j = JSON.parse(fs46.readFileSync(pkgPath, "utf8"));
|
|
43897
44371
|
return String(j.version || "0.0.0");
|
|
43898
44372
|
} catch {
|
|
43899
44373
|
return "0.0.0";
|
|
@@ -44019,7 +44493,7 @@ function startBackgroundAgent() {
|
|
|
44019
44493
|
process.exit(1);
|
|
44020
44494
|
}
|
|
44021
44495
|
const filePath = args[inputFileIndex + 1];
|
|
44022
|
-
const rawPayload =
|
|
44496
|
+
const rawPayload = fs46.readFileSync(filePath, "utf-8");
|
|
44023
44497
|
const envelope = JSON.parse(rawPayload);
|
|
44024
44498
|
const sessionId = envelope.session_id || envelope.message_id || uuidv412();
|
|
44025
44499
|
registerSession({
|