@nomad-e/bluma-cli 0.1.39 → 0.1.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -9,6 +9,106 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // src/app/agent/runtime/sandbox_policy.ts
13
+ import path5 from "path";
14
+ function getSandboxPolicy() {
15
+ const isSandbox = process.env.BLUMA_SANDBOX === "true";
16
+ const workspaceRoot = path5.resolve(
17
+ process.env.BLUMA_SANDBOX_WORKSPACE || process.cwd()
18
+ );
19
+ return {
20
+ mode: isSandbox ? "workspace" : "local",
21
+ isSandbox,
22
+ workspaceRoot
23
+ };
24
+ }
25
+ function isPathInsideWorkspace(targetPath, policy = getSandboxPolicy()) {
26
+ const resolved = path5.resolve(targetPath);
27
+ const relative = path5.relative(policy.workspaceRoot, resolved);
28
+ return relative === "" || !relative.startsWith("..") && !path5.isAbsolute(relative);
29
+ }
30
+ function resolveWorkspacePath(inputPath, policy = getSandboxPolicy()) {
31
+ const candidate = path5.isAbsolute(inputPath) ? path5.resolve(inputPath) : path5.resolve(policy.workspaceRoot, inputPath);
32
+ if (policy.isSandbox && !isPathInsideWorkspace(candidate, policy)) {
33
+ throw new Error(
34
+ `Path "${inputPath}" escapes the sandbox workspace root ${policy.workspaceRoot}`
35
+ );
36
+ }
37
+ return candidate;
38
+ }
39
+ function resolveCommandCwd(cwd, policy = getSandboxPolicy()) {
40
+ const base = cwd ? path5.resolve(cwd) : policy.workspaceRoot;
41
+ if (policy.isSandbox && !isPathInsideWorkspace(base, policy)) {
42
+ throw new Error(
43
+ `Command cwd "${base}" escapes the sandbox workspace root ${policy.workspaceRoot}`
44
+ );
45
+ }
46
+ return base;
47
+ }
48
+ function assessCommandSafety(command, policy = getSandboxPolicy()) {
49
+ const trimmed = String(command || "").trim();
50
+ if (!trimmed) {
51
+ return { allowed: false, risk: "blocked", reason: "Command is required." };
52
+ }
53
+ for (const entry of BLOCKED_COMMAND_PATTERNS) {
54
+ if (entry.pattern.test(trimmed)) {
55
+ return { allowed: false, risk: "blocked", reason: entry.reason };
56
+ }
57
+ }
58
+ if (HIGH_RISK_COMMAND_PATTERNS.some((pattern) => pattern.test(trimmed))) {
59
+ return {
60
+ allowed: true,
61
+ risk: policy.isSandbox ? "high" : "high",
62
+ reason: policy.isSandbox ? "High-risk command allowed inside the workspace sandbox." : "High-risk command requires explicit approval outside sandbox mode."
63
+ };
64
+ }
65
+ if (MODERATE_RISK_COMMAND_PATTERNS.some((pattern) => pattern.test(trimmed))) {
66
+ return {
67
+ allowed: true,
68
+ risk: "moderate",
69
+ reason: policy.isSandbox ? "Workspace mutation command allowed inside the sandbox." : "Workspace mutation command requires confirmation outside sandbox mode."
70
+ };
71
+ }
72
+ return { allowed: true, risk: "safe" };
73
+ }
74
+ var BLOCKED_COMMAND_PATTERNS, HIGH_RISK_COMMAND_PATTERNS, MODERATE_RISK_COMMAND_PATTERNS;
75
+ var init_sandbox_policy = __esm({
76
+ "src/app/agent/runtime/sandbox_policy.ts"() {
77
+ "use strict";
78
+ BLOCKED_COMMAND_PATTERNS = [
79
+ { pattern: /^sudo\s+/i, reason: "Privilege escalation is not allowed." },
80
+ { pattern: /^doas\s+/i, reason: "Privilege escalation is not allowed." },
81
+ { pattern: /^su\s+/i, reason: "Privilege escalation is not allowed." },
82
+ { pattern: /^pkexec\s+/i, reason: "Privilege escalation is not allowed." },
83
+ { pattern: /\bmkfs\./i, reason: "Disk formatting commands are blocked." },
84
+ { pattern: /\bdd\s+.*of=\/dev\/(sd|hd|nvme)/i, reason: "Raw disk writes are blocked." },
85
+ { pattern: /\brm\s+(-[rf]+\s+)*\/($|\s)/i, reason: "Deleting filesystem roots is blocked." },
86
+ { pattern: /\brm\s+-[rf]*\s+~($|\s)/i, reason: "Deleting home roots is blocked." },
87
+ { pattern: /\bcurl\s+.*\|\s*(ba)?sh/i, reason: "Pipe-to-shell remote execution is blocked." },
88
+ { pattern: /\bwget\s+.*\|\s*(ba)?sh/i, reason: "Pipe-to-shell remote execution is blocked." }
89
+ ];
90
+ HIGH_RISK_COMMAND_PATTERNS = [
91
+ /\brm\s+-[rf]/i,
92
+ /\bmv\s+.+\s+\/(?!tmp\b)/i,
93
+ /\bchmod\b/i,
94
+ /\bchown\b/i,
95
+ /\bssh\b/i,
96
+ /\bscp\b/i,
97
+ /\brsync\b/i,
98
+ /\bdocker\b/i,
99
+ /\bkubectl\b/i
100
+ ];
101
+ MODERATE_RISK_COMMAND_PATTERNS = [
102
+ /\bnpm\s+(install|update|uninstall)\b/i,
103
+ /\bpnpm\s+(add|install|update|remove)\b/i,
104
+ /\byarn\s+(add|install|remove)\b/i,
105
+ /\buv\s+(add|remove|sync)\b/i,
106
+ /\bpip\s+install\b/i,
107
+ /\bgit\s+(commit|push|rebase|reset|clean)\b/i
108
+ ];
109
+ }
110
+ });
111
+
12
112
  // src/app/agent/tools/natives/async_command.ts
13
113
  var async_command_exports = {};
14
114
  __export(async_command_exports, {
@@ -30,10 +130,10 @@ function cleanupOldCommands() {
30
130
  }
31
131
  function isDangerousCommand(command) {
32
132
  const trimmed = command.trim();
33
- for (const pattern of DANGEROUS_PATTERNS) {
34
- if (pattern.test(trimmed)) {
35
- return "Command requires elevated privileges (sudo/doas) which cannot be handled in async mode";
36
- }
133
+ const policy = getSandboxPolicy();
134
+ const assessment = assessCommandSafety(trimmed, policy);
135
+ if (!assessment.allowed) {
136
+ return assessment.reason || "Command blocked by sandbox policy";
37
137
  }
38
138
  for (const pattern of INTERACTIVE_PATTERNS) {
39
139
  if (pattern.test(trimmed)) {
@@ -46,7 +146,7 @@ async function runCommandAsync(args) {
46
146
  try {
47
147
  const {
48
148
  command,
49
- cwd = process.cwd(),
149
+ cwd,
50
150
  timeout = 0
51
151
  } = args;
52
152
  if (!command || typeof command !== "string") {
@@ -62,6 +162,8 @@ async function runCommandAsync(args) {
62
162
  error: dangerReason
63
163
  };
64
164
  }
165
+ const policy = getSandboxPolicy();
166
+ const resolvedCwd = resolveCommandCwd(cwd, policy);
65
167
  const commandId = uuidv42().substring(0, 8);
66
168
  const platform = os5.platform();
67
169
  let shellCmd;
@@ -84,7 +186,7 @@ async function runCommandAsync(args) {
84
186
  process: null
85
187
  };
86
188
  const child = spawn2(shellCmd, shellArgs, {
87
- cwd,
189
+ cwd: resolvedCwd,
88
190
  env: process.env,
89
191
  windowsHide: true
90
192
  });
@@ -286,35 +388,15 @@ async function killCommand(args) {
286
388
  };
287
389
  }
288
390
  }
289
- var runningCommands, MAX_OUTPUT_SIZE, MAX_STORED_COMMANDS, OUTPUT_TRUNCATION_MSG, DANGEROUS_PATTERNS, INTERACTIVE_PATTERNS;
391
+ var runningCommands, MAX_OUTPUT_SIZE, MAX_STORED_COMMANDS, OUTPUT_TRUNCATION_MSG, INTERACTIVE_PATTERNS;
290
392
  var init_async_command = __esm({
291
393
  "src/app/agent/tools/natives/async_command.ts"() {
292
394
  "use strict";
395
+ init_sandbox_policy();
293
396
  runningCommands = /* @__PURE__ */ new Map();
294
397
  MAX_OUTPUT_SIZE = 3e4;
295
398
  MAX_STORED_COMMANDS = 50;
296
399
  OUTPUT_TRUNCATION_MSG = "\n[OUTPUT TRUNCATED - 30KB/200 lines max]";
297
- DANGEROUS_PATTERNS = [
298
- // Elevação de privilégios
299
- /^sudo\s+/i,
300
- /^doas\s+/i,
301
- /^su\s+/i,
302
- /^pkexec\s+/i,
303
- /\|\s*sudo\s+/i,
304
- /;\s*sudo\s+/i,
305
- /&&\s*sudo\s+/i,
306
- // Comandos destrutivos
307
- /\brm\s+(-[rf]+\s+)*[\/~]/i,
308
- /\brm\s+-[rf]*\s+\*/i,
309
- /\bchmod\s+(777|666)\s+\//i,
310
- /\bdd\s+.*of=\/dev\/(sd|hd|nvme)/i,
311
- /\bmkfs\./i,
312
- // Fork bombs
313
- /:\(\)\s*\{\s*:\|:&\s*\}\s*;:/,
314
- // Remote code exec
315
- /\bcurl\s+.*\|\s*(ba)?sh/i,
316
- /\bwget\s+.*\|\s*(ba)?sh/i
317
- ];
318
400
  INTERACTIVE_PATTERNS = [
319
401
  /^(vim|vi|nano|emacs|pico)\s*/i,
320
402
  /^(less|more|most)\s*/i,
@@ -331,14 +413,15 @@ var init_async_command = __esm({
331
413
  import React12 from "react";
332
414
  import { render } from "ink";
333
415
  import { EventEmitter as EventEmitter3 } from "events";
334
- import fs18 from "fs";
335
- import path22 from "path";
416
+ import fs21 from "fs";
417
+ import path26 from "path";
336
418
  import { fileURLToPath as fileURLToPath5 } from "url";
337
- import { v4 as uuidv46 } from "uuid";
419
+ import { spawn as spawn4 } from "child_process";
420
+ import { v4 as uuidv47 } from "uuid";
338
421
 
339
422
  // src/app/ui/App.tsx
340
423
  import { useState as useState6, useEffect as useEffect7, useRef as useRef5, useCallback as useCallback3, memo as memo12 } from "react";
341
- import { Box as Box20, Text as Text19, Static } from "ink";
424
+ import { Box as Box20, Text as Text20, Static } from "ink";
342
425
 
343
426
  // src/app/ui/layout.tsx
344
427
  import { Box, Text, useStdout } from "ink";
@@ -391,7 +474,9 @@ var HeaderComponent = ({
391
474
  sessionId,
392
475
  workdir,
393
476
  cliVersion = "?",
394
- recentActivitySummary
477
+ recentActivitySummary,
478
+ activeTaskSummary,
479
+ taskProgressSummary
395
480
  }) => {
396
481
  const { stdout } = useStdout();
397
482
  const cols = Math.max(52, stdout?.columns ?? 80);
@@ -466,6 +551,10 @@ var HeaderComponent = ({
466
551
  "as needed."
467
552
  ] }),
468
553
  /* @__PURE__ */ jsx(Box, { marginY: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(ruleW) }) }),
554
+ /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { bold: true, color: M, children: "Work in progress" }) }),
555
+ /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "wrap", children: activeTaskSummary?.trim() ? activeTaskSummary.trim() : "No active task." }),
556
+ /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "wrap", children: taskProgressSummary?.trim() ? taskProgressSummary.trim() : "No tracked todo items." }),
557
+ /* @__PURE__ */ jsx(Box, { marginY: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(ruleW) }) }),
469
558
  /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { bold: true, color: M, children: "Recent activity" }) }),
470
559
  /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "wrap", children: recentActivitySummary?.trim() ? recentActivitySummary.trim() : "No recent activity." })
471
560
  ] }),
@@ -1973,10 +2062,28 @@ import { Box as Box4, Text as Text4 } from "ink";
1973
2062
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1974
2063
  var SimpleDiff = ({ text, maxHeight }) => {
1975
2064
  const allLines = (text || "").split("\n").filter((line) => line !== "");
2065
+ const additions = allLines.filter((line) => line.startsWith("+") && !line.startsWith("+++")).length;
2066
+ const removals = allLines.filter((line) => line.startsWith("-") && !line.startsWith("---")).length;
2067
+ const hunks = allLines.filter((line) => line.startsWith("@@")).length;
1976
2068
  const isTruncated = maxHeight > 0 && allLines.length > maxHeight;
1977
2069
  const linesToRender = isTruncated ? allLines.slice(-maxHeight) : allLines;
1978
2070
  const hiddenCount = allLines.length - linesToRender.length;
1979
2071
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingX: 2, children: [
2072
+ /* @__PURE__ */ jsxs4(Box4, { marginBottom: 1, children: [
2073
+ /* @__PURE__ */ jsx4(Text4, { color: BLUMA_TERMINAL.brandMagenta, children: "diff" }),
2074
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
2075
+ " \xB7 +",
2076
+ additions,
2077
+ " -",
2078
+ removals
2079
+ ] }),
2080
+ hunks > 0 ? /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
2081
+ " \xB7 ",
2082
+ hunks,
2083
+ " hunk",
2084
+ hunks > 1 ? "s" : ""
2085
+ ] }) : null
2086
+ ] }),
1980
2087
  isTruncated && /* @__PURE__ */ jsx4(Box4, { marginBottom: 0, children: /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1981
2088
  "\u22EF ",
1982
2089
  hiddenCount,
@@ -1984,26 +2091,26 @@ var SimpleDiff = ({ text, maxHeight }) => {
1984
2091
  ] }) }),
1985
2092
  linesToRender.map((line, index) => {
1986
2093
  if (line.startsWith("---") || line.startsWith("+++")) {
1987
- return null;
2094
+ return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: line }) }, index);
1988
2095
  }
1989
2096
  let color = "white";
1990
2097
  let prefix = "";
1991
2098
  if (line.startsWith("+")) {
1992
- color = "green";
2099
+ color = BLUMA_TERMINAL.success;
1993
2100
  prefix = "+ ";
1994
2101
  } else if (line.startsWith("-")) {
1995
- color = "red";
2102
+ color = BLUMA_TERMINAL.err;
1996
2103
  prefix = "- ";
1997
2104
  } else if (line.startsWith("@@")) {
1998
- color = "cyan";
1999
- prefix = "";
2105
+ color = BLUMA_TERMINAL.brandBlue;
2106
+ prefix = "@ ";
2000
2107
  } else {
2001
- color = "gray";
2108
+ color = BLUMA_TERMINAL.muted;
2002
2109
  prefix = " ";
2003
2110
  }
2004
2111
  return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { color, children: [
2005
2112
  prefix,
2006
- line.replace(/^[+\-]/, "")
2113
+ line.replace(/^[@+\-]\s?/, "")
2007
2114
  ] }) }, index);
2008
2115
  })
2009
2116
  ] });
@@ -2317,16 +2424,17 @@ var ConfirmationPrompt = memo4(ConfirmationPromptComponent);
2317
2424
 
2318
2425
  // src/app/agent/agent.ts
2319
2426
  import * as dotenv from "dotenv";
2320
- import path20 from "path";
2321
- import os14 from "os";
2427
+ import path24 from "path";
2428
+ import os16 from "os";
2322
2429
 
2323
2430
  // src/app/agent/tool_invoker.ts
2324
- import { promises as fs11 } from "fs";
2325
- import path13 from "path";
2431
+ import { promises as fs14 } from "fs";
2432
+ import path17 from "path";
2326
2433
  import { fileURLToPath } from "url";
2327
2434
 
2328
2435
  // src/app/agent/tools/natives/edit.ts
2329
- import path5 from "path";
2436
+ init_sandbox_policy();
2437
+ import path6 from "path";
2330
2438
  import os4 from "os";
2331
2439
  import { promises as fs4 } from "fs";
2332
2440
  import { diffLines } from "diff";
@@ -2345,7 +2453,7 @@ function normalizePath(filePath) {
2345
2453
  }
2346
2454
  filePath = filePath.replace(/\//g, "\\");
2347
2455
  }
2348
- return path5.normalize(path5.resolve(filePath));
2456
+ return path6.normalize(resolveWorkspacePath(filePath));
2349
2457
  } catch (e) {
2350
2458
  throw new Error(`Failed to normalize path "${filePath}": ${e.message}`);
2351
2459
  }
@@ -2606,7 +2714,7 @@ async function editTool(args) {
2606
2714
  };
2607
2715
  }
2608
2716
  const cwd = process.cwd();
2609
- if (!normalizedFilePath.startsWith(cwd) && !path5.isAbsolute(file_path)) {
2717
+ if (!normalizedFilePath.startsWith(cwd) && !path6.isAbsolute(file_path)) {
2610
2718
  return {
2611
2719
  success: false,
2612
2720
  error: `Invalid parameters: file_path must be within the current working directory or be an absolute path.`,
@@ -2627,11 +2735,11 @@ async function editTool(args) {
2627
2735
  file_path: normalizedFilePath
2628
2736
  };
2629
2737
  }
2630
- const dirPath = path5.dirname(normalizedFilePath);
2738
+ const dirPath = path6.dirname(normalizedFilePath);
2631
2739
  await fs4.mkdir(dirPath, { recursive: true });
2632
2740
  await fs4.writeFile(normalizedFilePath, editData.newContent, "utf-8");
2633
- const relativePath = path5.relative(process.cwd(), normalizedFilePath);
2634
- const filename = path5.basename(normalizedFilePath);
2741
+ const relativePath = path6.relative(process.cwd(), normalizedFilePath);
2742
+ const filename = path6.basename(normalizedFilePath);
2635
2743
  if (editData.isNewFile) {
2636
2744
  return {
2637
2745
  success: true,
@@ -2686,7 +2794,7 @@ function message(args) {
2686
2794
 
2687
2795
  // src/app/agent/tools/natives/ls.ts
2688
2796
  import { promises as fs5 } from "fs";
2689
- import path6 from "path";
2797
+ import path7 from "path";
2690
2798
  var DEFAULT_IGNORE = /* @__PURE__ */ new Set([
2691
2799
  ".git",
2692
2800
  ".gitignore",
@@ -2714,7 +2822,7 @@ async function ls(args) {
2714
2822
  max_depth
2715
2823
  } = args;
2716
2824
  try {
2717
- const basePath = path6.resolve(directory_path);
2825
+ const basePath = path7.resolve(directory_path);
2718
2826
  if (!(await fs5.stat(basePath)).isDirectory()) {
2719
2827
  throw new Error(`Directory '${directory_path}' not found.`);
2720
2828
  }
@@ -2727,8 +2835,8 @@ async function ls(args) {
2727
2835
  const entries = await fs5.readdir(currentDir, { withFileTypes: true });
2728
2836
  for (const entry of entries) {
2729
2837
  const entryName = entry.name;
2730
- const fullPath = path6.join(currentDir, entryName);
2731
- const posixPath = fullPath.split(path6.sep).join("/");
2838
+ const fullPath = path7.join(currentDir, entryName);
2839
+ const posixPath = fullPath.split(path7.sep).join("/");
2732
2840
  const isHidden = entryName.startsWith(".");
2733
2841
  if (allIgnorePatterns.has(entryName) || isHidden && !show_hidden) {
2734
2842
  continue;
@@ -2739,7 +2847,7 @@ async function ls(args) {
2739
2847
  await walk(fullPath, currentDepth + 1);
2740
2848
  }
2741
2849
  } else if (entry.isFile()) {
2742
- if (!normalizedExtensions || normalizedExtensions.includes(path6.extname(entryName).toLowerCase())) {
2850
+ if (!normalizedExtensions || normalizedExtensions.includes(path7.extname(entryName).toLowerCase())) {
2743
2851
  allFiles.push(posixPath);
2744
2852
  }
2745
2853
  }
@@ -2750,7 +2858,7 @@ async function ls(args) {
2750
2858
  allDirs.sort();
2751
2859
  return {
2752
2860
  success: true,
2753
- path: basePath.split(path6.sep).join("/"),
2861
+ path: basePath.split(path7.sep).join("/"),
2754
2862
  recursive,
2755
2863
  total_files: allFiles.length,
2756
2864
  total_directories: allDirs.length,
@@ -2766,17 +2874,19 @@ async function ls(args) {
2766
2874
  }
2767
2875
 
2768
2876
  // src/app/agent/tools/natives/readLines.ts
2877
+ init_sandbox_policy();
2769
2878
  import { promises as fs6 } from "fs";
2770
2879
  async function readLines(args) {
2771
2880
  const { filepath, start_line, end_line } = args;
2772
2881
  try {
2773
- if (!(await fs6.stat(filepath)).isFile()) {
2882
+ const resolvedPath = resolveWorkspacePath(filepath);
2883
+ if (!(await fs6.stat(resolvedPath)).isFile()) {
2774
2884
  throw new Error(`File '${filepath}' not found or is not a file.`);
2775
2885
  }
2776
2886
  if (start_line < 1 || end_line < start_line) {
2777
2887
  throw new Error("Invalid line range. start_line must be >= 1 and end_line must be >= start_line.");
2778
2888
  }
2779
- const fileContent = await fs6.readFile(filepath, "utf-8");
2889
+ const fileContent = await fs6.readFile(resolvedPath, "utf-8");
2780
2890
  const lines = fileContent.split("\n");
2781
2891
  const total_lines = lines.length;
2782
2892
  const startIndex = start_line - 1;
@@ -2789,7 +2899,7 @@ async function readLines(args) {
2789
2899
  const content = contentLines.join("\n");
2790
2900
  return {
2791
2901
  success: true,
2792
- filepath,
2902
+ filepath: resolvedPath,
2793
2903
  content,
2794
2904
  lines_read: contentLines.length,
2795
2905
  start_line,
@@ -2827,178 +2937,209 @@ async function countLines(args) {
2827
2937
  }
2828
2938
  }
2829
2939
 
2830
- // src/app/agent/tools/natives/todo.ts
2831
- import * as fs8 from "fs";
2832
- import * as path7 from "path";
2833
- var taskStore = [];
2834
- var nextId = 1;
2835
- function getTodoFilePath() {
2836
- return path7.join(process.cwd(), ".bluma", "todo.json");
2940
+ // src/app/agent/runtime/task_store.ts
2941
+ init_sandbox_policy();
2942
+ import fs8 from "fs";
2943
+ import path8 from "path";
2944
+ var cache = null;
2945
+ function getStorePath() {
2946
+ const policy = getSandboxPolicy();
2947
+ return path8.join(policy.workspaceRoot, ".bluma", "task_state.json");
2837
2948
  }
2838
- function loadTasksFromFile() {
2839
- try {
2840
- const filePath = getTodoFilePath();
2841
- if (fs8.existsSync(filePath)) {
2842
- const data = fs8.readFileSync(filePath, "utf-8");
2843
- const parsed = JSON.parse(data);
2844
- if (Array.isArray(parsed.tasks)) {
2845
- taskStore = parsed.tasks;
2846
- nextId = parsed.nextId || taskStore.length + 1;
2847
- }
2848
- }
2849
- } catch {
2850
- }
2949
+ function getDefaultState() {
2950
+ return {
2951
+ tasks: [],
2952
+ nextId: 1,
2953
+ activeTask: null,
2954
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2955
+ };
2851
2956
  }
2852
- function saveTasksToFile() {
2957
+ function ensureLoaded() {
2958
+ if (cache) {
2959
+ return cache;
2960
+ }
2961
+ const storePath = getStorePath();
2853
2962
  try {
2854
- const filePath = getTodoFilePath();
2855
- const dir = path7.dirname(filePath);
2856
- if (!fs8.existsSync(dir)) {
2857
- fs8.mkdirSync(dir, { recursive: true });
2963
+ if (fs8.existsSync(storePath)) {
2964
+ const raw = fs8.readFileSync(storePath, "utf-8");
2965
+ const parsed = JSON.parse(raw);
2966
+ cache = {
2967
+ tasks: Array.isArray(parsed.tasks) ? parsed.tasks : [],
2968
+ nextId: typeof parsed.nextId === "number" ? parsed.nextId : 1,
2969
+ activeTask: parsed.activeTask ?? null,
2970
+ updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : (/* @__PURE__ */ new Date()).toISOString()
2971
+ };
2972
+ return cache;
2858
2973
  }
2859
- fs8.writeFileSync(filePath, JSON.stringify({
2860
- tasks: taskStore,
2861
- nextId,
2862
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2863
- }, null, 2));
2864
2974
  } catch {
2865
2975
  }
2866
- }
2867
- function calculateStats() {
2868
- const total = taskStore.length;
2869
- const pending = taskStore.filter((t) => t.status === "pending").length;
2870
- const inProgress = taskStore.filter((t) => t.status === "in_progress").length;
2871
- const completed = taskStore.filter((t) => t.status === "completed").length;
2976
+ cache = getDefaultState();
2977
+ return cache;
2978
+ }
2979
+ function persist(state) {
2980
+ const storePath = getStorePath();
2981
+ fs8.mkdirSync(path8.dirname(storePath), { recursive: true });
2982
+ state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2983
+ fs8.writeFileSync(storePath, JSON.stringify(state, null, 2), "utf-8");
2984
+ cache = state;
2985
+ }
2986
+ function updateTaskStore(mutator) {
2987
+ const current = ensureLoaded();
2988
+ const clone = {
2989
+ tasks: current.tasks.map((task) => ({ ...task })),
2990
+ nextId: current.nextId,
2991
+ activeTask: current.activeTask ? { ...current.activeTask } : null,
2992
+ updatedAt: current.updatedAt
2993
+ };
2994
+ mutator(clone);
2995
+ persist(clone);
2996
+ return clone;
2997
+ }
2998
+ function calculateTaskStats(tasks) {
2999
+ const total = tasks.length;
3000
+ const pending = tasks.filter((t) => t.status === "pending").length;
3001
+ const inProgress = tasks.filter((t) => t.status === "in_progress").length;
3002
+ const completed = tasks.filter((t) => t.status === "completed").length;
2872
3003
  const progress = total > 0 ? Math.round(completed / total * 100) : 0;
2873
3004
  return { total, pending, inProgress, completed, progress };
2874
3005
  }
3006
+ function buildTaskSnapshot() {
3007
+ const state = ensureLoaded();
3008
+ return {
3009
+ tasks: state.tasks.map((task) => ({ ...task })),
3010
+ activeTask: state.activeTask ? { ...state.activeTask } : null,
3011
+ stats: calculateTaskStats(state.tasks),
3012
+ updatedAt: state.updatedAt
3013
+ };
3014
+ }
3015
+
3016
+ // src/app/agent/tools/natives/todo.ts
2875
3017
  function validateDescription(desc) {
2876
- if (!desc || typeof desc !== "string") {
2877
- return "Description is required";
2878
- }
2879
- if (desc.trim().length === 0) {
2880
- return "Description cannot be empty";
2881
- }
2882
- if (desc.length > 500) {
2883
- return "Description too long (max 500 chars)";
2884
- }
3018
+ if (!desc || typeof desc !== "string") return "Description is required";
3019
+ if (desc.trim().length === 0) return "Description cannot be empty";
3020
+ if (desc.length > 500) return "Description too long (max 500 chars)";
2885
3021
  return null;
2886
3022
  }
3023
+ function generateProgressBar(percent) {
3024
+ const width = 10;
3025
+ const filled = Math.round(percent / 100 * width);
3026
+ return "[" + "=".repeat(filled) + " ".repeat(width - filled) + "]";
3027
+ }
2887
3028
  function createResult(success, message2) {
3029
+ const snapshot = buildTaskSnapshot();
2888
3030
  return {
2889
3031
  success,
2890
3032
  message: message2,
2891
- tasks: taskStore,
2892
- stats: calculateStats()
3033
+ tasks: snapshot.tasks,
3034
+ activeTask: snapshot.activeTask,
3035
+ stats: snapshot.stats
2893
3036
  };
2894
3037
  }
2895
- function listTasks() {
2896
- loadTasksFromFile();
2897
- const stats = calculateStats();
2898
- if (taskStore.length === 0) {
2899
- return createResult(true, "No tasks yet. Use add action to create tasks.");
2900
- }
2901
- const progressBar = generateProgressBar(stats.progress);
2902
- return createResult(true, `${stats.total} tasks ${progressBar} ${stats.progress}%`);
3038
+ function syncTasks(tasks) {
3039
+ updateTaskStore((state) => {
3040
+ state.tasks = [];
3041
+ state.nextId = 1;
3042
+ for (const task of tasks || []) {
3043
+ state.tasks.push({
3044
+ id: state.nextId++,
3045
+ description: task.description,
3046
+ status: task.isComplete ? "completed" : "pending",
3047
+ priority: task.priority || "medium",
3048
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3049
+ completedAt: task.isComplete ? (/* @__PURE__ */ new Date()).toISOString() : void 0
3050
+ });
3051
+ }
3052
+ });
3053
+ const snapshot = buildTaskSnapshot();
3054
+ const progressBar = generateProgressBar(snapshot.stats.progress);
3055
+ return createResult(
3056
+ true,
3057
+ `Synced ${snapshot.tasks.length} tasks ${progressBar} ${snapshot.stats.progress}%`
3058
+ );
2903
3059
  }
2904
3060
  function addTasks(tasks) {
2905
3061
  if (!tasks || tasks.length === 0) {
2906
3062
  return createResult(false, "No tasks provided");
2907
3063
  }
2908
- loadTasksFromFile();
2909
3064
  for (const task of tasks) {
2910
3065
  const error = validateDescription(task.description);
2911
- if (error) {
2912
- return createResult(false, `Invalid task: ${error}`);
2913
- }
2914
- const newTask = {
2915
- id: nextId++,
2916
- description: task.description.trim(),
2917
- status: task.isComplete ? "completed" : "pending",
2918
- priority: task.priority || "medium",
2919
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2920
- completedAt: task.isComplete ? (/* @__PURE__ */ new Date()).toISOString() : void 0
2921
- };
2922
- taskStore.push(newTask);
2923
- }
2924
- saveTasksToFile();
3066
+ if (error) return createResult(false, `Invalid task: ${error}`);
3067
+ }
3068
+ updateTaskStore((state) => {
3069
+ for (const task of tasks) {
3070
+ state.tasks.push({
3071
+ id: state.nextId++,
3072
+ description: task.description.trim(),
3073
+ status: task.isComplete ? "completed" : "pending",
3074
+ priority: task.priority || "medium",
3075
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3076
+ completedAt: task.isComplete ? (/* @__PURE__ */ new Date()).toISOString() : void 0
3077
+ });
3078
+ }
3079
+ });
2925
3080
  return createResult(true, `Added ${tasks.length} task(s)`);
2926
3081
  }
2927
3082
  function completeTask(taskId) {
2928
- loadTasksFromFile();
2929
- const task = taskStore.find((t) => t.id === taskId);
2930
- if (!task) {
2931
- return createResult(false, `Task #${taskId} not found`);
2932
- }
2933
- task.status = "completed";
2934
- task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
2935
- saveTasksToFile();
2936
- return createResult(true, `Completed: ${task.description}`);
3083
+ let found = false;
3084
+ let description = "";
3085
+ updateTaskStore((state) => {
3086
+ const task = state.tasks.find((item) => item.id === taskId);
3087
+ if (!task) return;
3088
+ found = true;
3089
+ description = task.description;
3090
+ task.status = "completed";
3091
+ task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
3092
+ });
3093
+ return found ? createResult(true, `Completed: ${description}`) : createResult(false, `Task #${taskId} not found`);
2937
3094
  }
2938
3095
  function updateTask(taskId, description, priority) {
2939
- loadTasksFromFile();
2940
- const task = taskStore.find((t) => t.id === taskId);
2941
- if (!task) {
2942
- return createResult(false, `Task #${taskId} not found`);
2943
- }
2944
3096
  if (description) {
2945
3097
  const error = validateDescription(description);
2946
- if (error) {
2947
- return createResult(false, error);
2948
- }
2949
- task.description = description.trim();
2950
- }
2951
- if (priority) {
2952
- task.priority = priority;
2953
- }
2954
- saveTasksToFile();
2955
- return createResult(true, `Updated task #${taskId}`);
3098
+ if (error) return createResult(false, error);
3099
+ }
3100
+ let found = false;
3101
+ updateTaskStore((state) => {
3102
+ const task = state.tasks.find((item) => item.id === taskId);
3103
+ if (!task) return;
3104
+ found = true;
3105
+ if (description) task.description = description.trim();
3106
+ if (priority) task.priority = priority;
3107
+ });
3108
+ return found ? createResult(true, `Updated task #${taskId}`) : createResult(false, `Task #${taskId} not found`);
2956
3109
  }
2957
3110
  function removeTask(taskId) {
2958
- loadTasksFromFile();
2959
- const index = taskStore.findIndex((t) => t.id === taskId);
2960
- if (index === -1) {
2961
- return createResult(false, `Task #${taskId} not found`);
2962
- }
2963
- const removed = taskStore.splice(index, 1)[0];
2964
- saveTasksToFile();
2965
- return createResult(true, `Removed: ${removed.description}`);
3111
+ let removedDescription = "";
3112
+ updateTaskStore((state) => {
3113
+ const index = state.tasks.findIndex((item) => item.id === taskId);
3114
+ if (index === -1) return;
3115
+ removedDescription = state.tasks[index].description;
3116
+ state.tasks.splice(index, 1);
3117
+ });
3118
+ return removedDescription ? createResult(true, `Removed: ${removedDescription}`) : createResult(false, `Task #${taskId} not found`);
2966
3119
  }
2967
3120
  function clearCompleted() {
2968
- loadTasksFromFile();
2969
- const before = taskStore.length;
2970
- taskStore = taskStore.filter((t) => t.status !== "completed");
2971
- const removed = before - taskStore.length;
2972
- saveTasksToFile();
3121
+ let removed = 0;
3122
+ updateTaskStore((state) => {
3123
+ const before = state.tasks.length;
3124
+ state.tasks = state.tasks.filter((task) => task.status !== "completed");
3125
+ removed = before - state.tasks.length;
3126
+ });
2973
3127
  return createResult(true, `Cleared ${removed} completed task(s)`);
2974
3128
  }
2975
- function generateProgressBar(percent) {
2976
- const width = 10;
2977
- const filled = Math.round(percent / 100 * width);
2978
- const empty = width - filled;
2979
- return "[" + "=".repeat(filled) + " ".repeat(empty) + "]";
3129
+ function listTasks() {
3130
+ const snapshot = buildTaskSnapshot();
3131
+ if (snapshot.tasks.length === 0) {
3132
+ return createResult(true, "No tasks yet. Use add action to create tasks.");
3133
+ }
3134
+ const progressBar = generateProgressBar(snapshot.stats.progress);
3135
+ return createResult(
3136
+ true,
3137
+ `${snapshot.stats.total} tasks ${progressBar} ${snapshot.stats.progress}%`
3138
+ );
2980
3139
  }
2981
3140
  async function todo(args) {
2982
3141
  if ("tasks" in args && !("action" in args)) {
2983
- loadTasksFromFile();
2984
- taskStore = [];
2985
- nextId = 1;
2986
- if (args.tasks && args.tasks.length > 0) {
2987
- for (const task of args.tasks) {
2988
- taskStore.push({
2989
- id: nextId++,
2990
- description: task.description,
2991
- status: task.isComplete ? "completed" : "pending",
2992
- priority: "medium",
2993
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2994
- completedAt: task.isComplete ? (/* @__PURE__ */ new Date()).toISOString() : void 0
2995
- });
2996
- }
2997
- }
2998
- saveTasksToFile();
2999
- const stats = calculateStats();
3000
- const progressBar = generateProgressBar(stats.progress);
3001
- return createResult(true, `Synced ${taskStore.length} tasks ${progressBar} ${stats.progress}%`);
3142
+ return syncTasks(args.tasks);
3002
3143
  }
3003
3144
  const fullArgs = args;
3004
3145
  switch (fullArgs.action) {
@@ -3007,31 +3148,22 @@ async function todo(args) {
3007
3148
  case "add":
3008
3149
  return addTasks(fullArgs.tasks);
3009
3150
  case "complete":
3010
- if (!fullArgs.taskId) {
3011
- return createResult(false, "taskId required for complete action");
3012
- }
3013
- return completeTask(fullArgs.taskId);
3151
+ return fullArgs.taskId ? completeTask(fullArgs.taskId) : createResult(false, "taskId required for complete action");
3014
3152
  case "update":
3015
- if (!fullArgs.taskId) {
3016
- return createResult(false, "taskId required for update action");
3017
- }
3018
- return updateTask(fullArgs.taskId, fullArgs.description, fullArgs.priority);
3153
+ return fullArgs.taskId ? updateTask(fullArgs.taskId, fullArgs.description, fullArgs.priority) : createResult(false, "taskId required for update action");
3019
3154
  case "remove":
3020
- if (!fullArgs.taskId) {
3021
- return createResult(false, "taskId required for remove action");
3022
- }
3023
- return removeTask(fullArgs.taskId);
3155
+ return fullArgs.taskId ? removeTask(fullArgs.taskId) : createResult(false, "taskId required for remove action");
3024
3156
  case "clear":
3025
3157
  return clearCompleted();
3026
3158
  case "sync":
3027
- return addTasks(fullArgs.tasks);
3159
+ return syncTasks(fullArgs.tasks);
3028
3160
  default:
3029
3161
  return createResult(false, `Unknown action: ${fullArgs.action}`);
3030
3162
  }
3031
3163
  }
3032
3164
 
3033
3165
  // src/app/agent/tools/natives/find_by_name.ts
3034
- import path8 from "path";
3166
+ import path9 from "path";
3035
3167
  import { promises as fsPromises } from "fs";
3036
3168
  var DEFAULT_IGNORE_PATTERNS = [
3037
3169
  "node_modules",
@@ -3071,7 +3203,7 @@ function shouldIgnore(name, ignorePatterns, includeHidden) {
3071
3203
  }
3072
3204
  function matchesExtensions(filename, extensions) {
3073
3205
  if (!extensions || extensions.length === 0) return true;
3074
- const ext = path8.extname(filename).toLowerCase();
3206
+ const ext = path9.extname(filename).toLowerCase();
3075
3207
  return extensions.some((e) => {
3076
3208
  const normalizedExt = e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`;
3077
3209
  return ext === normalizedExt;
@@ -3093,8 +3225,8 @@ async function searchDirectory(dir, pattern, baseDir, options, results) {
3093
3225
  if (shouldIgnore(name, options.ignorePatterns, options.includeHidden)) {
3094
3226
  continue;
3095
3227
  }
3096
- const fullPath = path8.join(dir, name);
3097
- const relativePath = path8.relative(baseDir, fullPath);
3228
+ const fullPath = path9.join(dir, name);
3229
+ const relativePath = path9.relative(baseDir, fullPath);
3098
3230
  if (entry.isDirectory()) {
3099
3231
  if (pattern.test(name)) {
3100
3232
  results.push({
@@ -3150,7 +3282,7 @@ async function findByName(args) {
3150
3282
  error: "Pattern is required and must be a string"
3151
3283
  };
3152
3284
  }
3153
- const resolvedDir = path8.resolve(directory);
3285
+ const resolvedDir = path9.resolve(directory);
3154
3286
  try {
3155
3287
  const stats = await fsPromises.stat(resolvedDir);
3156
3288
  if (!stats.isDirectory()) {
@@ -3211,7 +3343,7 @@ async function findByName(args) {
3211
3343
  }
3212
3344
 
3213
3345
  // src/app/agent/tools/natives/grep_search.ts
3214
- import path9 from "path";
3346
+ import path10 from "path";
3215
3347
  import { promises as fsPromises2 } from "fs";
3216
3348
  var MAX_RESULTS3 = 100;
3217
3349
  var MAX_FILE_SIZE2 = 1024 * 1024;
@@ -3294,8 +3426,8 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
3294
3426
  "Rakefile"
3295
3427
  ]);
3296
3428
  function isTextFile(filepath) {
3297
- const ext = path9.extname(filepath).toLowerCase();
3298
- const basename = path9.basename(filepath);
3429
+ const ext = path10.extname(filepath).toLowerCase();
3430
+ const basename = path10.basename(filepath);
3299
3431
  if (TEXT_EXTENSIONS.has(ext)) return true;
3300
3432
  if (TEXT_EXTENSIONS.has(basename)) return true;
3301
3433
  if (basename.startsWith(".") && !ext) return true;
@@ -3340,7 +3472,7 @@ async function searchFile(filepath, baseDir, pattern, contextLines, matches, max
3340
3472
  if (stats.size > MAX_FILE_SIZE2) return 0;
3341
3473
  const content = await fsPromises2.readFile(filepath, "utf-8");
3342
3474
  const lines = content.split("\n");
3343
- const relativePath = path9.relative(baseDir, filepath);
3475
+ const relativePath = path10.relative(baseDir, filepath);
3344
3476
  for (let i = 0; i < lines.length && matches.length < maxResults; i++) {
3345
3477
  const line = lines[i];
3346
3478
  pattern.lastIndex = 0;
@@ -3380,7 +3512,7 @@ async function searchDirectory2(dir, baseDir, pattern, includePatterns, contextL
3380
3512
  if (matches.length >= maxResults) break;
3381
3513
  const name = entry.name;
3382
3514
  if (shouldIgnore2(name)) continue;
3383
- const fullPath = path9.join(dir, name);
3515
+ const fullPath = path10.join(dir, name);
3384
3516
  if (entry.isDirectory()) {
3385
3517
  await searchDirectory2(fullPath, baseDir, pattern, includePatterns, contextLines, matches, maxResults, stats);
3386
3518
  } else if (entry.isFile()) {
@@ -3429,7 +3561,7 @@ async function grepSearch(args) {
3429
3561
  error: "Search path is required"
3430
3562
  };
3431
3563
  }
3432
- const resolvedPath = path9.resolve(searchPath);
3564
+ const resolvedPath = path10.resolve(searchPath);
3433
3565
  let stats;
3434
3566
  try {
3435
3567
  stats = await fsPromises2.stat(resolvedPath);
@@ -3477,7 +3609,7 @@ async function grepSearch(args) {
3477
3609
  );
3478
3610
  } else if (stats.isFile()) {
3479
3611
  searchStats.filesSearched = 1;
3480
- const found = await searchFile(resolvedPath, path9.dirname(resolvedPath), pattern, context_lines, matches, max_results);
3612
+ const found = await searchFile(resolvedPath, path10.dirname(resolvedPath), pattern, context_lines, matches, max_results);
3481
3613
  if (found > 0) searchStats.filesWithMatches = 1;
3482
3614
  }
3483
3615
  return {
@@ -3506,7 +3638,7 @@ async function grepSearch(args) {
3506
3638
  }
3507
3639
 
3508
3640
  // src/app/agent/tools/natives/view_file_outline.ts
3509
- import path10 from "path";
3641
+ import path11 from "path";
3510
3642
  import { promises as fsPromises3 } from "fs";
3511
3643
  var LANGUAGE_MAP = {
3512
3644
  ".ts": "typescript",
@@ -3620,7 +3752,7 @@ var PATTERNS = {
3620
3752
  ]
3621
3753
  };
3622
3754
  function detectLanguage(filepath) {
3623
- const ext = path10.extname(filepath).toLowerCase();
3755
+ const ext = path11.extname(filepath).toLowerCase();
3624
3756
  return LANGUAGE_MAP[ext] || "unknown";
3625
3757
  }
3626
3758
  function determineItemType(line, language) {
@@ -3716,7 +3848,7 @@ async function viewFileOutline(args) {
3716
3848
  error: "file_path is required and must be a string"
3717
3849
  };
3718
3850
  }
3719
- const resolvedPath = path10.resolve(file_path);
3851
+ const resolvedPath = path11.resolve(file_path);
3720
3852
  let content;
3721
3853
  try {
3722
3854
  content = await fsPromises3.readFile(resolvedPath, "utf-8");
@@ -3756,27 +3888,28 @@ async function viewFileOutline(args) {
3756
3888
  }
3757
3889
  }
3758
3890
 
3759
- // src/app/agent/tool_invoker.ts
3891
+ // src/app/agent/runtime/native_tool_catalog.ts
3760
3892
  init_async_command();
3761
3893
 
3762
3894
  // src/app/agent/tools/natives/task_boundary.ts
3763
- import path11 from "path";
3895
+ init_sandbox_policy();
3896
+ import path12 from "path";
3764
3897
  import { promises as fs9 } from "fs";
3765
3898
  import os6 from "os";
3766
- var currentTask = null;
3767
3899
  var artifactsDir = null;
3768
3900
  async function getArtifactsDir() {
3769
3901
  if (artifactsDir) return artifactsDir;
3902
+ const policy = getSandboxPolicy();
3770
3903
  const homeDir = os6.homedir();
3771
- const baseDir = path11.join(homeDir, ".bluma", "artifacts");
3904
+ const baseDir = policy.isSandbox ? path12.join(policy.workspaceRoot, "artifacts") : path12.join(homeDir, ".bluma", "artifacts");
3772
3905
  const sessionId = Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
3773
- artifactsDir = path11.join(baseDir, sessionId);
3906
+ artifactsDir = path12.join(baseDir, sessionId);
3774
3907
  await fs9.mkdir(artifactsDir, { recursive: true });
3775
3908
  return artifactsDir;
3776
3909
  }
3777
3910
  async function updateTaskFile(task) {
3778
3911
  const dir = await getArtifactsDir();
3779
- const taskFile = path11.join(dir, "task.md");
3912
+ const taskFile = path12.join(dir, "task.md");
3780
3913
  const content = `# ${task.taskName}
3781
3914
 
3782
3915
  **Mode:** ${task.mode}
@@ -3821,32 +3954,58 @@ async function taskBoundary(args) {
3821
3954
  };
3822
3955
  }
3823
3956
  const now = Date.now();
3824
- if (currentTask && currentTask.taskName === task_name) {
3825
- currentTask.mode = mode;
3826
- currentTask.status = task_status;
3827
- currentTask.summary = task_summary || currentTask.summary;
3828
- currentTask.updateTime = now;
3829
- currentTask.stepCount++;
3830
- } else {
3831
- currentTask = {
3832
- taskName: task_name,
3957
+ let currentTask = null;
3958
+ updateTaskStore((state) => {
3959
+ if (state.activeTask && state.activeTask.taskName === task_name) {
3960
+ state.activeTask.mode = mode;
3961
+ state.activeTask.status = task_status;
3962
+ state.activeTask.summary = task_summary || state.activeTask.summary;
3963
+ state.activeTask.updateTime = now;
3964
+ state.activeTask.stepCount++;
3965
+ } else {
3966
+ state.activeTask = {
3967
+ taskName: task_name,
3968
+ mode,
3969
+ status: task_status,
3970
+ summary: task_summary || "",
3971
+ startTime: now,
3972
+ updateTime: now,
3973
+ stepCount: 1
3974
+ };
3975
+ }
3976
+ currentTask = state.activeTask;
3977
+ });
3978
+ if (!currentTask) {
3979
+ return {
3980
+ success: false,
3981
+ task_name,
3982
+ mode,
3983
+ status: task_status,
3984
+ message: "Failed to persist active task"
3985
+ };
3986
+ }
3987
+ const snapshot = buildTaskSnapshot();
3988
+ const persistedTask = snapshot.activeTask;
3989
+ if (!persistedTask) {
3990
+ return {
3991
+ success: false,
3992
+ task_name,
3833
3993
  mode,
3834
3994
  status: task_status,
3835
- summary: task_summary || "",
3836
- startTime: now,
3837
- updateTime: now,
3838
- stepCount: 1
3995
+ message: "Task persisted without an active snapshot"
3839
3996
  };
3840
3997
  }
3841
- await updateTaskFile(currentTask);
3998
+ await updateTaskFile(persistedTask);
3842
3999
  const dir = await getArtifactsDir();
3843
4000
  return {
3844
4001
  success: true,
3845
- task_name: currentTask.taskName,
3846
- mode: currentTask.mode,
3847
- status: currentTask.status,
4002
+ task_name: persistedTask.taskName,
4003
+ mode: persistedTask.mode,
4004
+ status: persistedTask.status,
3848
4005
  message: `Task "${task_name}" is now in ${mode} mode. Status: ${task_status}`,
3849
- artifacts_dir: dir
4006
+ artifacts_dir: dir,
4007
+ activeTask: snapshot.activeTask,
4008
+ stats: snapshot.stats
3850
4009
  };
3851
4010
  } catch (error) {
3852
4011
  return {
@@ -3868,7 +4027,7 @@ async function createArtifact(args) {
3868
4027
  return { success: false, error: "content is required" };
3869
4028
  }
3870
4029
  const dir = await getArtifactsDir();
3871
- const filepath = path11.join(dir, filename);
4030
+ const filepath = path12.join(dir, filename);
3872
4031
  await fs9.writeFile(filepath, content, "utf-8");
3873
4032
  return {
3874
4033
  success: true,
@@ -3888,7 +4047,7 @@ async function readArtifact(args) {
3888
4047
  return { success: false, error: "filename is required" };
3889
4048
  }
3890
4049
  const dir = await getArtifactsDir();
3891
- const filepath = path11.join(dir, filename);
4050
+ const filepath = path12.join(dir, filename);
3892
4051
  const content = await fs9.readFile(filepath, "utf-8");
3893
4052
  return {
3894
4053
  success: true,
@@ -4259,7 +4418,7 @@ ${skill.content}`;
4259
4418
 
4260
4419
  // src/app/agent/tools/natives/coding_memory.ts
4261
4420
  import * as fs10 from "fs";
4262
- import * as path12 from "path";
4421
+ import * as path13 from "path";
4263
4422
  import os7 from "os";
4264
4423
  var PROMPT_DEFAULT_MAX_TOTAL = 1e4;
4265
4424
  var PROMPT_DEFAULT_MAX_NOTES = 25;
@@ -4268,13 +4427,13 @@ function readCodingMemoryForPrompt(options) {
4268
4427
  const maxTotal = options?.maxTotalChars ?? PROMPT_DEFAULT_MAX_TOTAL;
4269
4428
  const maxNotes = options?.maxNotes ?? PROMPT_DEFAULT_MAX_NOTES;
4270
4429
  const preview = options?.previewCharsPerNote ?? PROMPT_DEFAULT_PREVIEW;
4271
- const globalPath = path12.join(os7.homedir(), ".bluma", "coding_memory.json");
4272
- const legacyPath = path12.join(process.cwd(), ".bluma", "coding_memory.json");
4430
+ const globalPath = path13.join(os7.homedir(), ".bluma", "coding_memory.json");
4431
+ const legacyPath = path13.join(process.cwd(), ".bluma", "coding_memory.json");
4273
4432
  let raw = null;
4274
4433
  try {
4275
4434
  if (fs10.existsSync(globalPath)) {
4276
4435
  raw = fs10.readFileSync(globalPath, "utf-8");
4277
- } else if (path12.resolve(globalPath) !== path12.resolve(legacyPath) && fs10.existsSync(legacyPath)) {
4436
+ } else if (path13.resolve(globalPath) !== path13.resolve(legacyPath) && fs10.existsSync(legacyPath)) {
4278
4437
  raw = fs10.readFileSync(legacyPath, "utf-8");
4279
4438
  }
4280
4439
  } catch {
@@ -4310,29 +4469,29 @@ ${block}` : block;
4310
4469
  return parts.join("");
4311
4470
  }
4312
4471
  var memoryStore = [];
4313
- var nextId2 = 1;
4472
+ var nextId = 1;
4314
4473
  var loaded = false;
4315
4474
  function getMemoryFilePath() {
4316
- return path12.join(os7.homedir(), ".bluma", "coding_memory.json");
4475
+ return path13.join(os7.homedir(), ".bluma", "coding_memory.json");
4317
4476
  }
4318
4477
  function getLegacyMemoryFilePath() {
4319
- return path12.join(process.cwd(), ".bluma", "coding_memory.json");
4478
+ return path13.join(process.cwd(), ".bluma", "coding_memory.json");
4320
4479
  }
4321
4480
  function loadMemoryFromFile() {
4322
4481
  if (loaded) return;
4323
4482
  loaded = true;
4324
4483
  memoryStore = [];
4325
- nextId2 = 1;
4484
+ nextId = 1;
4326
4485
  try {
4327
4486
  const filePath = getMemoryFilePath();
4328
4487
  const legacy = getLegacyMemoryFilePath();
4329
- const legacyDistinct = path12.resolve(legacy) !== path12.resolve(filePath);
4488
+ const legacyDistinct = path13.resolve(legacy) !== path13.resolve(filePath);
4330
4489
  const readIntoStore = (p) => {
4331
4490
  const raw = fs10.readFileSync(p, "utf-8");
4332
4491
  const parsed = JSON.parse(raw);
4333
4492
  if (Array.isArray(parsed.entries)) {
4334
4493
  memoryStore = parsed.entries;
4335
- nextId2 = typeof parsed.nextId === "number" ? parsed.nextId : memoryStore.length + 1;
4494
+ nextId = typeof parsed.nextId === "number" ? parsed.nextId : memoryStore.length + 1;
4336
4495
  }
4337
4496
  };
4338
4497
  if (fs10.existsSync(filePath)) {
@@ -4346,19 +4505,19 @@ function loadMemoryFromFile() {
4346
4505
  }
4347
4506
  } catch {
4348
4507
  memoryStore = [];
4349
- nextId2 = 1;
4508
+ nextId = 1;
4350
4509
  }
4351
4510
  }
4352
4511
  function saveMemoryToFile() {
4353
4512
  try {
4354
4513
  const filePath = getMemoryFilePath();
4355
- const dir = path12.dirname(filePath);
4514
+ const dir = path13.dirname(filePath);
4356
4515
  if (!fs10.existsSync(dir)) {
4357
4516
  fs10.mkdirSync(dir, { recursive: true });
4358
4517
  }
4359
4518
  const payload = {
4360
4519
  entries: memoryStore,
4361
- nextId: nextId2,
4520
+ nextId,
4362
4521
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4363
4522
  };
4364
4523
  fs10.writeFileSync(filePath, JSON.stringify(payload, null, 2));
@@ -4387,7 +4546,7 @@ function addNote(args) {
4387
4546
  };
4388
4547
  }
4389
4548
  const entry = {
4390
- id: nextId2++,
4549
+ id: nextId++,
4391
4550
  note,
4392
4551
  tags: normalizeTags(args.tags),
4393
4552
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4512,44 +4671,698 @@ function updateNote(args) {
4512
4671
  if (hasTags) {
4513
4672
  entry.tags = normalizeTags(args.tags);
4514
4673
  }
4515
- saveMemoryToFile();
4516
- return {
4517
- success: true,
4518
- message: `Updated coding memory id=${id}`,
4519
- entries: memoryStore
4520
- };
4674
+ saveMemoryToFile();
4675
+ return {
4676
+ success: true,
4677
+ message: `Updated coding memory id=${id}`,
4678
+ entries: memoryStore
4679
+ };
4680
+ }
4681
+ async function coding_memory(args) {
4682
+ const action = args.action;
4683
+ switch (action) {
4684
+ case "add":
4685
+ return addNote(args);
4686
+ case "list":
4687
+ return listNotes();
4688
+ case "search":
4689
+ return searchNotes(args);
4690
+ case "remove":
4691
+ return removeNote(args);
4692
+ case "update":
4693
+ return updateNote(args);
4694
+ default:
4695
+ return {
4696
+ success: false,
4697
+ message: `Unknown action: ${String(action)}`,
4698
+ entries: memoryStore
4699
+ };
4700
+ }
4701
+ }
4702
+
4703
+ // src/app/agent/tools/natives/file_write.ts
4704
+ init_sandbox_policy();
4705
+ import { promises as fs11 } from "fs";
4706
+ import path14 from "path";
4707
+ async function fileWrite(args) {
4708
+ const {
4709
+ filepath,
4710
+ content,
4711
+ overwrite = true,
4712
+ create_directories = true
4713
+ } = args;
4714
+ try {
4715
+ if (!filepath || typeof filepath !== "string") {
4716
+ return { success: false, error: "filepath is required" };
4717
+ }
4718
+ if (content === void 0 || content === null) {
4719
+ return { success: false, error: "content is required" };
4720
+ }
4721
+ const resolvedPath = resolveWorkspacePath(filepath);
4722
+ const dir = path14.dirname(resolvedPath);
4723
+ let existed = false;
4724
+ try {
4725
+ const st = await fs11.stat(resolvedPath);
4726
+ existed = st.isFile();
4727
+ } catch {
4728
+ existed = false;
4729
+ }
4730
+ if (existed && !overwrite) {
4731
+ return {
4732
+ success: false,
4733
+ error: `File already exists and overwrite=false: ${resolvedPath}`
4734
+ };
4735
+ }
4736
+ if (create_directories) {
4737
+ await fs11.mkdir(dir, { recursive: true });
4738
+ }
4739
+ await fs11.writeFile(resolvedPath, String(content), "utf-8");
4740
+ const bytes = Buffer.byteLength(String(content), "utf-8");
4741
+ return {
4742
+ success: true,
4743
+ filepath: resolvedPath,
4744
+ bytes_written: bytes,
4745
+ created: !existed,
4746
+ overwritten: existed
4747
+ };
4748
+ } catch (error) {
4749
+ return { success: false, error: error.message || String(error) };
4750
+ }
4751
+ }
4752
+
4753
+ // src/app/agent/tools/natives/web_fetch.ts
4754
+ import http2 from "http";
4755
+ import https2 from "https";
4756
+ var DEFAULT_MAX_CHARS = 12e3;
4757
+ var REQUEST_TIMEOUT_MS = 15e3;
4758
+ function fetchUrl(url) {
4759
+ return new Promise((resolve2, reject) => {
4760
+ const client = url.startsWith("https://") ? https2 : http2;
4761
+ const req = client.get(
4762
+ url,
4763
+ {
4764
+ headers: {
4765
+ "User-Agent": "bluma-cli/0.1",
4766
+ Accept: "text/plain,text/html,application/json;q=0.9,*/*;q=0.5"
4767
+ },
4768
+ timeout: REQUEST_TIMEOUT_MS
4769
+ },
4770
+ (res) => {
4771
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
4772
+ fetchUrl(res.headers.location).then(resolve2).catch(reject);
4773
+ return;
4774
+ }
4775
+ let body = "";
4776
+ res.on("data", (chunk) => {
4777
+ body += chunk.toString();
4778
+ });
4779
+ res.on("end", () => {
4780
+ resolve2({
4781
+ statusCode: res.statusCode,
4782
+ contentType: res.headers["content-type"],
4783
+ body
4784
+ });
4785
+ });
4786
+ }
4787
+ );
4788
+ req.on("error", reject);
4789
+ req.on("timeout", () => {
4790
+ req.destroy(new Error("Request timeout"));
4791
+ });
4792
+ });
4793
+ }
4794
+ function stripHtml(html) {
4795
+ return html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/\s+/g, " ").trim();
4796
+ }
4797
+ async function webFetch(args) {
4798
+ const maxChars = Math.max(500, Math.min(args.max_chars ?? DEFAULT_MAX_CHARS, 5e4));
4799
+ const url = String(args.url || "").trim();
4800
+ if (!/^https?:\/\//i.test(url)) {
4801
+ return {
4802
+ success: false,
4803
+ url,
4804
+ error: "url must start with http:// or https://"
4805
+ };
4806
+ }
4807
+ try {
4808
+ const response = await fetchUrl(url);
4809
+ const contentType = response.contentType || "unknown";
4810
+ const raw = /html/i.test(contentType) ? stripHtml(response.body) : response.body.trim();
4811
+ const truncated = raw.length > maxChars;
4812
+ const content = truncated ? `${raw.slice(0, maxChars)}
4813
+
4814
+ [...truncated...]` : raw;
4815
+ return {
4816
+ success: true,
4817
+ url,
4818
+ status_code: response.statusCode,
4819
+ content_type: contentType,
4820
+ content,
4821
+ truncated
4822
+ };
4823
+ } catch (error) {
4824
+ return {
4825
+ success: false,
4826
+ url,
4827
+ error: error.message || String(error)
4828
+ };
4829
+ }
4830
+ }
4831
+
4832
+ // src/app/agent/tools/natives/agent_coordination.ts
4833
+ import fs13 from "fs";
4834
+ import os9 from "os";
4835
+ import path16 from "path";
4836
+ import { spawn as spawn3 } from "child_process";
4837
+ import { v4 as uuidv43 } from "uuid";
4838
+
4839
+ // src/app/agent/runtime/session_registry.ts
4840
+ import fs12 from "fs";
4841
+ import os8 from "os";
4842
+ import path15 from "path";
4843
+ function getRegistryDir() {
4844
+ return path15.join(process.env.HOME || os8.homedir(), ".bluma", "registry");
4845
+ }
4846
+ function getRegistryFile() {
4847
+ return path15.join(getRegistryDir(), "sessions.json");
4848
+ }
4849
+ function ensureRegistryDir() {
4850
+ fs12.mkdirSync(getRegistryDir(), { recursive: true });
4851
+ }
4852
+ function readRegistry() {
4853
+ ensureRegistryDir();
4854
+ const file = getRegistryFile();
4855
+ if (!fs12.existsSync(file)) {
4856
+ return { entries: [] };
4857
+ }
4858
+ try {
4859
+ return JSON.parse(fs12.readFileSync(file, "utf-8"));
4860
+ } catch {
4861
+ return { entries: [] };
4862
+ }
4863
+ }
4864
+ function writeRegistry(state) {
4865
+ ensureRegistryDir();
4866
+ fs12.writeFileSync(getRegistryFile(), JSON.stringify(state, null, 2), "utf-8");
4867
+ }
4868
+ function getSessionLogPath(sessionId) {
4869
+ ensureRegistryDir();
4870
+ return path15.join(getRegistryDir(), `${sessionId}.jsonl`);
4871
+ }
4872
+ function registerSession(entry) {
4873
+ const state = readRegistry();
4874
+ const nextEntry = {
4875
+ ...entry,
4876
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4877
+ logFile: getSessionLogPath(entry.sessionId)
4878
+ };
4879
+ state.entries = state.entries.filter((item) => item.sessionId !== entry.sessionId);
4880
+ state.entries.unshift(nextEntry);
4881
+ writeRegistry(state);
4882
+ return nextEntry;
4883
+ }
4884
+ function updateSession(sessionId, patch) {
4885
+ const state = readRegistry();
4886
+ const index = state.entries.findIndex((entry) => entry.sessionId === sessionId);
4887
+ if (index === -1) return null;
4888
+ const nextEntry = {
4889
+ ...state.entries[index],
4890
+ ...patch,
4891
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4892
+ };
4893
+ state.entries[index] = nextEntry;
4894
+ writeRegistry(state);
4895
+ return nextEntry;
4896
+ }
4897
+ function listSessions() {
4898
+ return readRegistry().entries;
4899
+ }
4900
+ function getSession(sessionId) {
4901
+ return readRegistry().entries.find((entry) => entry.sessionId === sessionId) || null;
4902
+ }
4903
+ function appendSessionLog(sessionId, payload) {
4904
+ const logFile = getSessionLogPath(sessionId);
4905
+ fs12.appendFileSync(logFile, `${JSON.stringify(payload)}
4906
+ `, "utf-8");
4907
+ }
4908
+ function readSessionLog(sessionId) {
4909
+ const logFile = getSessionLogPath(sessionId);
4910
+ if (!fs12.existsSync(logFile)) return [];
4911
+ return fs12.readFileSync(logFile, "utf-8").split("\n").filter(Boolean);
4912
+ }
4913
+
4914
+ // src/app/agent/tools/natives/agent_coordination.ts
4915
+ function buildWorkerPayload(sessionId, args, parentSessionId) {
4916
+ return {
4917
+ message_id: sessionId,
4918
+ session_id: sessionId,
4919
+ from_agent: parentSessionId || "interactive",
4920
+ to_agent: "bluma-worker",
4921
+ action: "worker_task",
4922
+ context: {
4923
+ user_request: args.task,
4924
+ coordinator_context: args.context || null,
4925
+ worker_title: args.title || null,
4926
+ worker_role: args.agent_type || "worker"
4927
+ },
4928
+ metadata: {
4929
+ sandbox: process.env.BLUMA_SANDBOX === "true",
4930
+ sandbox_name: process.env.BLUMA_SANDBOX_NAME || void 0,
4931
+ coordinator_worker: true,
4932
+ parent_session_id: parentSessionId
4933
+ }
4934
+ };
4935
+ }
4936
+ function extractLatestResult(sessionId) {
4937
+ const lines = readSessionLog(sessionId);
4938
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
4939
+ try {
4940
+ const parsed = JSON.parse(lines[index]);
4941
+ if (parsed.event_type === "result") {
4942
+ return parsed;
4943
+ }
4944
+ } catch {
4945
+ }
4946
+ }
4947
+ return null;
4948
+ }
4949
+ function toAgentSummary(entry) {
4950
+ return {
4951
+ session_id: entry.sessionId,
4952
+ title: entry.title,
4953
+ kind: entry.kind,
4954
+ status: entry.status,
4955
+ started_at: entry.startedAt,
4956
+ updated_at: entry.updatedAt,
4957
+ pid: entry.pid || null,
4958
+ metadata: entry.metadata || {}
4959
+ };
4960
+ }
4961
+ async function spawnAgent(args) {
4962
+ if (!args?.task || typeof args.task !== "string") {
4963
+ return { success: false, error: "task is required" };
4964
+ }
4965
+ const entrypoint = process.argv[1];
4966
+ if (!entrypoint) {
4967
+ return { success: false, error: "Unable to resolve CLI entrypoint for worker spawn" };
4968
+ }
4969
+ const sessionId = uuidv43();
4970
+ const parentSessionId = process.env.BLUMA_SESSION_ID || null;
4971
+ const title = args.title || `worker:${args.agent_type || "worker"}`;
4972
+ const payload = buildWorkerPayload(sessionId, args, parentSessionId);
4973
+ const payloadDir = fs13.mkdtempSync(path16.join(os9.tmpdir(), "bluma-worker-"));
4974
+ const payloadPath = path16.join(payloadDir, `${sessionId}.json`);
4975
+ fs13.writeFileSync(payloadPath, JSON.stringify(payload, null, 2), "utf-8");
4976
+ registerSession({
4977
+ sessionId,
4978
+ kind: "agent",
4979
+ status: "running",
4980
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
4981
+ workdir: process.cwd(),
4982
+ title,
4983
+ metadata: {
4984
+ action: "worker_task",
4985
+ background: true,
4986
+ coordinator_worker: true,
4987
+ parent_session_id: parentSessionId,
4988
+ agent_type: args.agent_type || "worker"
4989
+ }
4990
+ });
4991
+ const child = spawn3(
4992
+ process.execPath,
4993
+ [entrypoint, "agent", "--input-file", payloadPath, "--background-worker", "--registry-session", sessionId],
4994
+ {
4995
+ detached: true,
4996
+ stdio: "ignore",
4997
+ cwd: process.cwd(),
4998
+ env: {
4999
+ ...process.env,
5000
+ BLUMA_PARENT_SESSION_ID: parentSessionId || ""
5001
+ }
5002
+ }
5003
+ );
5004
+ child.unref();
5005
+ updateSession(sessionId, { pid: child.pid });
5006
+ return {
5007
+ success: true,
5008
+ session_id: sessionId,
5009
+ pid: child.pid || null,
5010
+ parent_session_id: parentSessionId,
5011
+ title,
5012
+ status: "running"
5013
+ };
5014
+ }
5015
+ async function waitAgent(args) {
5016
+ const sessionId = args?.session_id;
5017
+ if (!sessionId || typeof sessionId !== "string") {
5018
+ return { success: false, error: "session_id is required" };
5019
+ }
5020
+ const timeoutMs = Math.max(1e3, Number(args.timeout_ms || 3e4));
5021
+ const pollIntervalMs = Math.max(200, Number(args.poll_interval_ms || 1e3));
5022
+ const deadline = Date.now() + timeoutMs;
5023
+ while (Date.now() < deadline) {
5024
+ const session2 = getSession(sessionId);
5025
+ if (!session2) {
5026
+ return { success: false, error: `Unknown session: ${sessionId}` };
5027
+ }
5028
+ if (session2.status !== "running") {
5029
+ return {
5030
+ success: true,
5031
+ completed: true,
5032
+ session: toAgentSummary(session2),
5033
+ result: extractLatestResult(sessionId)
5034
+ };
5035
+ }
5036
+ await new Promise((resolve2) => setTimeout(resolve2, pollIntervalMs));
5037
+ }
5038
+ const session = getSession(sessionId);
5039
+ if (!session) {
5040
+ return { success: false, error: `Unknown session: ${sessionId}` };
5041
+ }
5042
+ return {
5043
+ success: true,
5044
+ completed: false,
5045
+ session: toAgentSummary(session),
5046
+ result: extractLatestResult(sessionId),
5047
+ message: `Timed out after ${timeoutMs}ms while waiting for agent ${sessionId}.`
5048
+ };
5049
+ }
5050
+ async function listAgents(args = {}) {
5051
+ const entries = listSessions().filter((entry) => entry.kind === "agent").filter((entry) => {
5052
+ if (args.parent_session_id) {
5053
+ return entry.metadata?.parent_session_id === args.parent_session_id;
5054
+ }
5055
+ return true;
5056
+ }).filter((entry) => {
5057
+ if (args.status) {
5058
+ return entry.status === args.status;
5059
+ }
5060
+ return true;
5061
+ }).map(toAgentSummary);
5062
+ return {
5063
+ success: true,
5064
+ count: entries.length,
5065
+ agents: entries
5066
+ };
5067
+ }
5068
+
5069
+ // src/app/agent/runtime/native_tool_catalog.ts
5070
+ var NATIVE_TOOL_ENTRIES = [
5071
+ {
5072
+ metadata: {
5073
+ name: "message",
5074
+ category: "communication",
5075
+ riskLevel: "safe",
5076
+ autoApproveInLocal: true,
5077
+ autoApproveInSandbox: true,
5078
+ description: "Deliver progress or result messages to the user."
5079
+ },
5080
+ implementation: message
5081
+ },
5082
+ {
5083
+ metadata: {
5084
+ name: "todo",
5085
+ category: "planning",
5086
+ riskLevel: "safe",
5087
+ autoApproveInLocal: true,
5088
+ autoApproveInSandbox: true,
5089
+ description: "Track a task list for the current objective."
5090
+ },
5091
+ implementation: todo
5092
+ },
5093
+ {
5094
+ metadata: {
5095
+ name: "task_boundary",
5096
+ category: "planning",
5097
+ riskLevel: "safe",
5098
+ autoApproveInLocal: true,
5099
+ autoApproveInSandbox: true,
5100
+ description: "Track the current task mode and execution boundary."
5101
+ },
5102
+ implementation: taskBoundary
5103
+ },
5104
+ {
5105
+ metadata: {
5106
+ name: "create_artifact",
5107
+ category: "artifact",
5108
+ riskLevel: "write",
5109
+ autoApproveInLocal: true,
5110
+ autoApproveInSandbox: true,
5111
+ description: "Write a generated artifact into the artifact output area."
5112
+ },
5113
+ implementation: createArtifact
5114
+ },
5115
+ {
5116
+ metadata: {
5117
+ name: "read_artifact",
5118
+ category: "artifact",
5119
+ riskLevel: "safe",
5120
+ autoApproveInLocal: true,
5121
+ autoApproveInSandbox: true,
5122
+ description: "Read an artifact from the artifact output area."
5123
+ },
5124
+ implementation: readArtifact
5125
+ },
5126
+ {
5127
+ metadata: {
5128
+ name: "ls_tool",
5129
+ category: "filesystem",
5130
+ riskLevel: "safe",
5131
+ autoApproveInLocal: true,
5132
+ autoApproveInSandbox: true,
5133
+ description: "List files and directories."
5134
+ },
5135
+ implementation: ls
5136
+ },
5137
+ {
5138
+ metadata: {
5139
+ name: "count_file_lines",
5140
+ category: "filesystem",
5141
+ riskLevel: "safe",
5142
+ autoApproveInLocal: true,
5143
+ autoApproveInSandbox: true,
5144
+ description: "Count lines in a text file."
5145
+ },
5146
+ implementation: countLines
5147
+ },
5148
+ {
5149
+ metadata: {
5150
+ name: "read_file_lines",
5151
+ category: "filesystem",
5152
+ riskLevel: "safe",
5153
+ autoApproveInLocal: true,
5154
+ autoApproveInSandbox: true,
5155
+ description: "Read a slice of a text file."
5156
+ },
5157
+ implementation: readLines
5158
+ },
5159
+ {
5160
+ metadata: {
5161
+ name: "find_by_name",
5162
+ category: "filesystem",
5163
+ riskLevel: "safe",
5164
+ autoApproveInLocal: true,
5165
+ autoApproveInSandbox: true,
5166
+ description: "Find files by glob pattern."
5167
+ },
5168
+ implementation: findByName
5169
+ },
5170
+ {
5171
+ metadata: {
5172
+ name: "grep_search",
5173
+ category: "filesystem",
5174
+ riskLevel: "safe",
5175
+ autoApproveInLocal: true,
5176
+ autoApproveInSandbox: true,
5177
+ description: "Search file contents."
5178
+ },
5179
+ implementation: grepSearch
5180
+ },
5181
+ {
5182
+ metadata: {
5183
+ name: "view_file_outline",
5184
+ category: "filesystem",
5185
+ riskLevel: "safe",
5186
+ autoApproveInLocal: true,
5187
+ autoApproveInSandbox: true,
5188
+ description: "Inspect the outline of a source file."
5189
+ },
5190
+ implementation: viewFileOutline
5191
+ },
5192
+ {
5193
+ metadata: {
5194
+ name: "edit_tool",
5195
+ category: "filesystem",
5196
+ riskLevel: "write",
5197
+ autoApproveInLocal: false,
5198
+ autoApproveInSandbox: true,
5199
+ description: "Edit or create files in the workspace."
5200
+ },
5201
+ implementation: editTool
5202
+ },
5203
+ {
5204
+ metadata: {
5205
+ name: "file_write",
5206
+ category: "filesystem",
5207
+ riskLevel: "write",
5208
+ autoApproveInLocal: false,
5209
+ autoApproveInSandbox: true,
5210
+ description: "Write full file contents directly into the workspace."
5211
+ },
5212
+ implementation: fileWrite
5213
+ },
5214
+ {
5215
+ metadata: {
5216
+ name: "shell_command",
5217
+ category: "execution",
5218
+ riskLevel: "execute",
5219
+ autoApproveInLocal: false,
5220
+ autoApproveInSandbox: true,
5221
+ description: "Execute a shell command in the workspace."
5222
+ },
5223
+ implementation: runCommandAsync
5224
+ },
5225
+ {
5226
+ metadata: {
5227
+ name: "command_status",
5228
+ category: "execution",
5229
+ riskLevel: "safe",
5230
+ autoApproveInLocal: true,
5231
+ autoApproveInSandbox: true,
5232
+ description: "Inspect the status of an async command."
5233
+ },
5234
+ implementation: commandStatus
5235
+ },
5236
+ {
5237
+ metadata: {
5238
+ name: "send_command_input",
5239
+ category: "execution",
5240
+ riskLevel: "execute",
5241
+ autoApproveInLocal: false,
5242
+ autoApproveInSandbox: true,
5243
+ description: "Send stdin to a running command."
5244
+ },
5245
+ implementation: sendCommandInput
5246
+ },
5247
+ {
5248
+ metadata: {
5249
+ name: "kill_command",
5250
+ category: "execution",
5251
+ riskLevel: "execute",
5252
+ autoApproveInLocal: false,
5253
+ autoApproveInSandbox: true,
5254
+ description: "Terminate a running command."
5255
+ },
5256
+ implementation: killCommand
5257
+ },
5258
+ {
5259
+ metadata: {
5260
+ name: "spawn_agent",
5261
+ category: "execution",
5262
+ riskLevel: "execute",
5263
+ autoApproveInLocal: false,
5264
+ autoApproveInSandbox: true,
5265
+ description: "Spawn a background worker agent for a bounded subtask."
5266
+ },
5267
+ implementation: spawnAgent
5268
+ },
5269
+ {
5270
+ metadata: {
5271
+ name: "wait_agent",
5272
+ category: "execution",
5273
+ riskLevel: "safe",
5274
+ autoApproveInLocal: true,
5275
+ autoApproveInSandbox: true,
5276
+ description: "Wait for a worker agent session to finish and collect its result."
5277
+ },
5278
+ implementation: waitAgent
5279
+ },
5280
+ {
5281
+ metadata: {
5282
+ name: "list_agents",
5283
+ category: "execution",
5284
+ riskLevel: "safe",
5285
+ autoApproveInLocal: true,
5286
+ autoApproveInSandbox: true,
5287
+ description: "List worker and agent sessions known to the coordinator."
5288
+ },
5289
+ implementation: listAgents
5290
+ },
5291
+ {
5292
+ metadata: {
5293
+ name: "search_web",
5294
+ category: "knowledge",
5295
+ riskLevel: "network",
5296
+ autoApproveInLocal: true,
5297
+ autoApproveInSandbox: true,
5298
+ description: "Search external sources through the configured provider."
5299
+ },
5300
+ implementation: searchWeb
5301
+ },
5302
+ {
5303
+ metadata: {
5304
+ name: "web_fetch",
5305
+ category: "knowledge",
5306
+ riskLevel: "network",
5307
+ autoApproveInLocal: true,
5308
+ autoApproveInSandbox: true,
5309
+ description: "Fetch the contents of a remote URL for analysis."
5310
+ },
5311
+ implementation: webFetch
5312
+ },
5313
+ {
5314
+ metadata: {
5315
+ name: "load_skill",
5316
+ category: "knowledge",
5317
+ riskLevel: "safe",
5318
+ autoApproveInLocal: true,
5319
+ autoApproveInSandbox: true,
5320
+ description: "Load a pluggable skill module."
5321
+ },
5322
+ implementation: loadSkill
5323
+ },
5324
+ {
5325
+ metadata: {
5326
+ name: "coding_memory",
5327
+ category: "knowledge",
5328
+ riskLevel: "safe",
5329
+ autoApproveInLocal: true,
5330
+ autoApproveInSandbox: true,
5331
+ description: "Persist or retrieve durable project notes."
5332
+ },
5333
+ implementation: coding_memory
5334
+ }
5335
+ ];
5336
+ var TOOL_METADATA_MAP = new Map(
5337
+ NATIVE_TOOL_ENTRIES.map((entry) => [entry.metadata.name, entry.metadata])
5338
+ );
5339
+ var TOOL_IMPLEMENTATION_MAP = new Map(
5340
+ NATIVE_TOOL_ENTRIES.map((entry) => [entry.metadata.name, entry.implementation])
5341
+ );
5342
+ function getNativeToolMetadata(toolName) {
5343
+ return TOOL_METADATA_MAP.get(toolName);
4521
5344
  }
4522
- async function coding_memory(args) {
4523
- const action = args.action;
4524
- switch (action) {
4525
- case "add":
4526
- return addNote(args);
4527
- case "list":
4528
- return listNotes();
4529
- case "search":
4530
- return searchNotes(args);
4531
- case "remove":
4532
- return removeNote(args);
4533
- case "update":
4534
- return updateNote(args);
4535
- default:
4536
- return {
4537
- success: false,
4538
- message: `Unknown action: ${String(action)}`,
4539
- entries: memoryStore
4540
- };
4541
- }
5345
+ function getNativeToolImplementation(toolName) {
5346
+ return TOOL_IMPLEMENTATION_MAP.get(toolName);
5347
+ }
5348
+ function getAllNativeToolMetadata() {
5349
+ return NATIVE_TOOL_ENTRIES.map((entry) => entry.metadata);
5350
+ }
5351
+ function applyMetadataToToolDefinitions(toolDefinitions) {
5352
+ return toolDefinitions.map((definition) => {
5353
+ const toolName = definition.function.name;
5354
+ return {
5355
+ ...definition,
5356
+ metadata: getNativeToolMetadata(toolName)
5357
+ };
5358
+ });
4542
5359
  }
4543
5360
 
4544
5361
  // src/app/agent/tool_invoker.ts
4545
5362
  var ToolInvoker = class {
4546
- // Mapa privado para associar nomes de ferramentas às suas funções de implementação.
4547
- toolImplementations;
4548
5363
  // Propriedade privada para armazenar as definições de ferramentas carregadas do JSON.
4549
5364
  toolDefinitions = [];
4550
5365
  constructor() {
4551
- this.toolImplementations = /* @__PURE__ */ new Map();
4552
- this.registerTools();
4553
5366
  }
4554
5367
  /**
4555
5368
  * Carrega as definições de ferramentas do arquivo de configuração `native_tools.json`.
@@ -4558,41 +5371,16 @@ var ToolInvoker = class {
4558
5371
  async initialize() {
4559
5372
  try {
4560
5373
  const __filename = fileURLToPath(import.meta.url);
4561
- const __dirname = path13.dirname(__filename);
4562
- const configPath = path13.resolve(__dirname, "config", "native_tools.json");
4563
- const fileContent = await fs11.readFile(configPath, "utf-8");
5374
+ const __dirname = path17.dirname(__filename);
5375
+ const configPath = path17.resolve(__dirname, "config", "native_tools.json");
5376
+ const fileContent = await fs14.readFile(configPath, "utf-8");
4564
5377
  const config2 = JSON.parse(fileContent);
4565
- this.toolDefinitions = config2.nativeTools;
5378
+ this.toolDefinitions = applyMetadataToToolDefinitions(config2.nativeTools);
4566
5379
  } catch (error) {
4567
5380
  console.error("[ToolInvoker] Erro cr\xEDtico ao carregar 'native_tools.json'. As ferramentas nativas n\xE3o estar\xE3o dispon\xEDveis.", error);
4568
5381
  this.toolDefinitions = [];
4569
5382
  }
4570
5383
  }
4571
- /**
4572
- * Registra as implementações de todas as ferramentas nativas.
4573
- * Este método mapeia o nome da ferramenta (string) para a função TypeScript que a executa.
4574
- */
4575
- registerTools() {
4576
- this.toolImplementations.set("edit_tool", editTool);
4577
- this.toolImplementations.set("ls_tool", ls);
4578
- this.toolImplementations.set("count_file_lines", countLines);
4579
- this.toolImplementations.set("read_file_lines", readLines);
4580
- this.toolImplementations.set("find_by_name", findByName);
4581
- this.toolImplementations.set("grep_search", grepSearch);
4582
- this.toolImplementations.set("view_file_outline", viewFileOutline);
4583
- this.toolImplementations.set("shell_command", runCommandAsync);
4584
- this.toolImplementations.set("command_status", commandStatus);
4585
- this.toolImplementations.set("send_command_input", sendCommandInput);
4586
- this.toolImplementations.set("kill_command", killCommand);
4587
- this.toolImplementations.set("message", message);
4588
- this.toolImplementations.set("todo", todo);
4589
- this.toolImplementations.set("task_boundary", taskBoundary);
4590
- this.toolImplementations.set("create_artifact", createArtifact);
4591
- this.toolImplementations.set("read_artifact", readArtifact);
4592
- this.toolImplementations.set("search_web", searchWeb);
4593
- this.toolImplementations.set("load_skill", loadSkill);
4594
- this.toolImplementations.set("coding_memory", coding_memory);
4595
- }
4596
5384
  /**
4597
5385
  * Retorna a lista de definições de todas as ferramentas nativas carregadas.
4598
5386
  * O MCPClient usará esta função para obter a lista de ferramentas locais.
@@ -4600,6 +5388,12 @@ var ToolInvoker = class {
4600
5388
  getToolDefinitions() {
4601
5389
  return this.toolDefinitions;
4602
5390
  }
5391
+ getToolMetadata(toolName) {
5392
+ return getNativeToolMetadata(toolName);
5393
+ }
5394
+ getAllToolMetadata() {
5395
+ return getAllNativeToolMetadata();
5396
+ }
4603
5397
  /**
4604
5398
  * Invoca uma ferramenta nativa pelo nome com os argumentos fornecidos.
4605
5399
  * @param toolName O nome da ferramenta a ser invocada.
@@ -4607,7 +5401,7 @@ var ToolInvoker = class {
4607
5401
  * @returns O resultado da execução da ferramenta.
4608
5402
  */
4609
5403
  async invoke(toolName, args) {
4610
- const implementation = this.toolImplementations.get(toolName);
5404
+ const implementation = getNativeToolImplementation(toolName);
4611
5405
  if (!implementation) {
4612
5406
  return { error: `Error: Native tool "${toolName}" not found.` };
4613
5407
  }
@@ -4621,9 +5415,9 @@ var ToolInvoker = class {
4621
5415
  };
4622
5416
 
4623
5417
  // src/app/agent/tools/mcp/mcp_client.ts
4624
- import { promises as fs12 } from "fs";
4625
- import path14 from "path";
4626
- import os8 from "os";
5418
+ import { promises as fs15 } from "fs";
5419
+ import path18 from "path";
5420
+ import os10 from "os";
4627
5421
  import { fileURLToPath as fileURLToPath2 } from "url";
4628
5422
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4629
5423
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
@@ -4650,9 +5444,9 @@ var MCPClient = class {
4650
5444
  });
4651
5445
  }
4652
5446
  const __filename = fileURLToPath2(import.meta.url);
4653
- const __dirname = path14.dirname(__filename);
4654
- const defaultConfigPath = path14.resolve(__dirname, "config", "bluma-mcp.json");
4655
- const userConfigPath = path14.join(os8.homedir(), ".bluma", "bluma-mcp.json");
5447
+ const __dirname = path18.dirname(__filename);
5448
+ const defaultConfigPath = path18.resolve(__dirname, "config", "bluma-mcp.json");
5449
+ const userConfigPath = path18.join(os10.homedir(), ".bluma", "bluma-mcp.json");
4656
5450
  const defaultConfig = await this.loadMcpConfig(defaultConfigPath, "Default");
4657
5451
  const userConfig = await this.loadMcpConfig(userConfigPath, "User");
4658
5452
  const mergedConfig = {
@@ -4686,7 +5480,7 @@ var MCPClient = class {
4686
5480
  }
4687
5481
  async loadMcpConfig(configPath, configType) {
4688
5482
  try {
4689
- const fileContent = await fs12.readFile(configPath, "utf-8");
5483
+ const fileContent = await fs15.readFile(configPath, "utf-8");
4690
5484
  const processedContent = this.replaceEnvPlaceholders(fileContent);
4691
5485
  return JSON.parse(processedContent);
4692
5486
  } catch (error) {
@@ -4705,7 +5499,7 @@ var MCPClient = class {
4705
5499
  async connectToStdioServer(serverName, config2) {
4706
5500
  let commandToExecute = config2.command;
4707
5501
  let argsToExecute = config2.args || [];
4708
- const isWindows = os8.platform() === "win32";
5502
+ const isWindows = os10.platform() === "win32";
4709
5503
  if (!isWindows && commandToExecute.toLowerCase() === "cmd") {
4710
5504
  if (argsToExecute.length >= 2 && argsToExecute[0].toLowerCase() === "/c") {
4711
5505
  commandToExecute = argsToExecute[1];
@@ -4770,7 +5564,13 @@ var MCPClient = class {
4770
5564
  const route = this.toolToServerMap.get(name);
4771
5565
  if (!route) continue;
4772
5566
  const source = route.server === "native" ? "native" : "mcp";
4773
- detailed.push({ ...tool, source, server: route.server, originalName: route.originalName });
5567
+ detailed.push({
5568
+ ...tool,
5569
+ source,
5570
+ server: route.server,
5571
+ originalName: route.originalName,
5572
+ metadata: source === "native" ? this.nativeToolInvoker.getToolMetadata(route.originalName) : void 0
5573
+ });
4774
5574
  }
4775
5575
  return detailed;
4776
5576
  }
@@ -4821,13 +5621,13 @@ var AdvancedFeedbackSystem = class {
4821
5621
  };
4822
5622
 
4823
5623
  // src/app/agent/bluma/core/bluma.ts
4824
- import path19 from "path";
4825
- import { v4 as uuidv43 } from "uuid";
5624
+ import path23 from "path";
5625
+ import { v4 as uuidv44 } from "uuid";
4826
5626
 
4827
5627
  // src/app/agent/session_manager/session_manager.ts
4828
- import path15 from "path";
4829
- import os9 from "os";
4830
- import { promises as fs13 } from "fs";
5628
+ import path19 from "path";
5629
+ import os11 from "os";
5630
+ import { promises as fs16 } from "fs";
4831
5631
  var fileLocks = /* @__PURE__ */ new Map();
4832
5632
  async function withFileLock(file, fn) {
4833
5633
  const prev = fileLocks.get(file) || Promise.resolve();
@@ -4863,13 +5663,13 @@ function debouncedSave(sessionFile, history, memory) {
4863
5663
  function expandHome(p) {
4864
5664
  if (!p) return p;
4865
5665
  if (p.startsWith("~")) {
4866
- return path15.join(os9.homedir(), p.slice(1));
5666
+ return path19.join(os11.homedir(), p.slice(1));
4867
5667
  }
4868
5668
  return p;
4869
5669
  }
4870
5670
  function getPreferredAppDir() {
4871
- const fixed = path15.join(os9.homedir(), ".bluma");
4872
- return path15.resolve(expandHome(fixed));
5671
+ const fixed = path19.join(os11.homedir(), ".bluma");
5672
+ return path19.resolve(expandHome(fixed));
4873
5673
  }
4874
5674
  async function safeRenameWithRetry(src, dest, maxRetries = 6) {
4875
5675
  let attempt = 0;
@@ -4877,10 +5677,10 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
4877
5677
  const isWin = process.platform === "win32";
4878
5678
  while (attempt <= maxRetries) {
4879
5679
  try {
4880
- const dir = path15.dirname(dest);
4881
- await fs13.mkdir(dir, { recursive: true }).catch(() => {
5680
+ const dir = path19.dirname(dest);
5681
+ await fs16.mkdir(dir, { recursive: true }).catch(() => {
4882
5682
  });
4883
- await fs13.rename(src, dest);
5683
+ await fs16.rename(src, dest);
4884
5684
  return;
4885
5685
  } catch (e) {
4886
5686
  lastErr = e;
@@ -4893,13 +5693,13 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
4893
5693
  }
4894
5694
  }
4895
5695
  try {
4896
- await fs13.access(src);
4897
- const data = await fs13.readFile(src);
4898
- const dir = path15.dirname(dest);
4899
- await fs13.mkdir(dir, { recursive: true }).catch(() => {
5696
+ await fs16.access(src);
5697
+ const data = await fs16.readFile(src);
5698
+ const dir = path19.dirname(dest);
5699
+ await fs16.mkdir(dir, { recursive: true }).catch(() => {
4900
5700
  });
4901
- await fs13.writeFile(dest, data);
4902
- await fs13.unlink(src).catch(() => {
5701
+ await fs16.writeFile(dest, data);
5702
+ await fs16.unlink(src).catch(() => {
4903
5703
  });
4904
5704
  return;
4905
5705
  } catch (fallbackErr) {
@@ -4912,16 +5712,16 @@ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
4912
5712
  }
4913
5713
  async function ensureSessionDir() {
4914
5714
  const appDir = getPreferredAppDir();
4915
- const sessionDir = path15.join(appDir, "sessions");
4916
- await fs13.mkdir(sessionDir, { recursive: true });
5715
+ const sessionDir = path19.join(appDir, "sessions");
5716
+ await fs16.mkdir(sessionDir, { recursive: true });
4917
5717
  return sessionDir;
4918
5718
  }
4919
5719
  async function loadOrcreateSession(sessionId) {
4920
5720
  const sessionDir = await ensureSessionDir();
4921
- const sessionFile = path15.join(sessionDir, `${sessionId}.json`);
5721
+ const sessionFile = path19.join(sessionDir, `${sessionId}.json`);
4922
5722
  try {
4923
- await fs13.access(sessionFile);
4924
- const fileContent = await fs13.readFile(sessionFile, "utf-8");
5723
+ await fs16.access(sessionFile);
5724
+ const fileContent = await fs16.readFile(sessionFile, "utf-8");
4925
5725
  const sessionData = JSON.parse(fileContent);
4926
5726
  const memory = {
4927
5727
  historyAnchor: sessionData.history_anchor ?? null,
@@ -4934,7 +5734,7 @@ async function loadOrcreateSession(sessionId) {
4934
5734
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
4935
5735
  conversation_history: []
4936
5736
  };
4937
- await fs13.writeFile(sessionFile, JSON.stringify(newSessionData, null, 2), "utf-8");
5737
+ await fs16.writeFile(sessionFile, JSON.stringify(newSessionData, null, 2), "utf-8");
4938
5738
  const emptyMemory = {
4939
5739
  historyAnchor: null,
4940
5740
  compressedTurnSliceCount: 0
@@ -4946,12 +5746,12 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
4946
5746
  await withFileLock(sessionFile, async () => {
4947
5747
  let sessionData;
4948
5748
  try {
4949
- const dir = path15.dirname(sessionFile);
4950
- await fs13.mkdir(dir, { recursive: true });
5749
+ const dir = path19.dirname(sessionFile);
5750
+ await fs16.mkdir(dir, { recursive: true });
4951
5751
  } catch {
4952
5752
  }
4953
5753
  try {
4954
- const fileContent = await fs13.readFile(sessionFile, "utf-8");
5754
+ const fileContent = await fs16.readFile(sessionFile, "utf-8");
4955
5755
  sessionData = JSON.parse(fileContent);
4956
5756
  } catch (error) {
4957
5757
  const code = error && error.code;
@@ -4962,14 +5762,14 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
4962
5762
  console.warn(`An unknown error occurred while reading ${sessionFile}. Re-initializing.`, error);
4963
5763
  }
4964
5764
  }
4965
- const sessionId = path15.basename(sessionFile, ".json");
5765
+ const sessionId = path19.basename(sessionFile, ".json");
4966
5766
  sessionData = {
4967
5767
  session_id: sessionId,
4968
5768
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
4969
5769
  conversation_history: []
4970
5770
  };
4971
5771
  try {
4972
- await fs13.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
5772
+ await fs16.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
4973
5773
  } catch {
4974
5774
  }
4975
5775
  }
@@ -4985,7 +5785,7 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
4985
5785
  }
4986
5786
  const tempSessionFile = `${sessionFile}.${Date.now()}.tmp`;
4987
5787
  try {
4988
- await fs13.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
5788
+ await fs16.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
4989
5789
  await safeRenameWithRetry(tempSessionFile, sessionFile);
4990
5790
  } catch (writeError) {
4991
5791
  if (writeError instanceof Error) {
@@ -4994,7 +5794,7 @@ async function doSaveSessionHistory(sessionFile, history, memory) {
4994
5794
  console.error(`An unknown fatal error occurred while saving session to ${sessionFile}:`, writeError);
4995
5795
  }
4996
5796
  try {
4997
- await fs13.unlink(tempSessionFile);
5797
+ await fs16.unlink(tempSessionFile);
4998
5798
  } catch {
4999
5799
  }
5000
5800
  }
@@ -5011,15 +5811,15 @@ async function saveSessionHistory(sessionFile, history, memory) {
5011
5811
  }
5012
5812
 
5013
5813
  // src/app/agent/core/prompt/prompt_builder.ts
5014
- import os11 from "os";
5015
- import fs16 from "fs";
5016
- import path18 from "path";
5814
+ import os13 from "os";
5815
+ import fs19 from "fs";
5816
+ import path22 from "path";
5017
5817
  import { execSync as execSync2 } from "child_process";
5018
5818
 
5019
5819
  // src/app/agent/skills/skill_loader.ts
5020
- import fs14 from "fs";
5021
- import path16 from "path";
5022
- import os10 from "os";
5820
+ import fs17 from "fs";
5821
+ import path20 from "path";
5822
+ import os12 from "os";
5023
5823
  import { fileURLToPath as fileURLToPath3 } from "node:url";
5024
5824
  var SkillLoader = class _SkillLoader {
5025
5825
  bundledSkillsDir;
@@ -5028,8 +5828,8 @@ var SkillLoader = class _SkillLoader {
5028
5828
  cache = /* @__PURE__ */ new Map();
5029
5829
  conflicts = [];
5030
5830
  constructor(projectRoot, bundledDir) {
5031
- this.projectSkillsDir = path16.join(projectRoot, ".bluma", "skills");
5032
- this.globalSkillsDir = path16.join(os10.homedir(), ".bluma", "skills");
5831
+ this.projectSkillsDir = path20.join(projectRoot, ".bluma", "skills");
5832
+ this.globalSkillsDir = path20.join(os12.homedir(), ".bluma", "skills");
5033
5833
  this.bundledSkillsDir = bundledDir || _SkillLoader.resolveBundledDir();
5034
5834
  }
5035
5835
  /**
@@ -5038,48 +5838,48 @@ var SkillLoader = class _SkillLoader {
5038
5838
  */
5039
5839
  static resolveBundledDir() {
5040
5840
  if (process.env.JEST_WORKER_ID !== void 0 || process.env.NODE_ENV === "test") {
5041
- return path16.join(process.cwd(), "dist", "config", "skills");
5841
+ return path20.join(process.cwd(), "dist", "config", "skills");
5042
5842
  }
5043
5843
  const candidates = [];
5044
5844
  const push = (p) => {
5045
- const abs = path16.resolve(p);
5845
+ const abs = path20.resolve(p);
5046
5846
  if (!candidates.includes(abs)) {
5047
5847
  candidates.push(abs);
5048
5848
  }
5049
5849
  };
5050
5850
  let argvBundled = null;
5051
5851
  try {
5052
- const bundleDir = path16.dirname(fileURLToPath3(import.meta.url));
5053
- push(path16.join(bundleDir, "config", "skills"));
5852
+ const bundleDir = path20.dirname(fileURLToPath3(import.meta.url));
5853
+ push(path20.join(bundleDir, "config", "skills"));
5054
5854
  } catch {
5055
5855
  }
5056
5856
  const argv1 = process.argv[1];
5057
5857
  if (argv1 && !argv1.startsWith("-")) {
5058
5858
  try {
5059
5859
  let resolved = argv1;
5060
- if (path16.isAbsolute(argv1) && fs14.existsSync(argv1)) {
5061
- resolved = fs14.realpathSync(argv1);
5062
- } else if (!path16.isAbsolute(argv1)) {
5063
- resolved = path16.resolve(process.cwd(), argv1);
5860
+ if (path20.isAbsolute(argv1) && fs17.existsSync(argv1)) {
5861
+ resolved = fs17.realpathSync(argv1);
5862
+ } else if (!path20.isAbsolute(argv1)) {
5863
+ resolved = path20.resolve(process.cwd(), argv1);
5064
5864
  }
5065
- const scriptDir = path16.dirname(resolved);
5066
- argvBundled = path16.join(scriptDir, "config", "skills");
5865
+ const scriptDir = path20.dirname(resolved);
5866
+ argvBundled = path20.join(scriptDir, "config", "skills");
5067
5867
  push(argvBundled);
5068
5868
  } catch {
5069
5869
  }
5070
5870
  }
5071
5871
  for (const abs of candidates) {
5072
- if (fs14.existsSync(abs)) {
5872
+ if (fs17.existsSync(abs)) {
5073
5873
  return abs;
5074
5874
  }
5075
5875
  }
5076
5876
  try {
5077
- return path16.join(path16.dirname(fileURLToPath3(import.meta.url)), "config", "skills");
5877
+ return path20.join(path20.dirname(fileURLToPath3(import.meta.url)), "config", "skills");
5078
5878
  } catch {
5079
5879
  if (argvBundled) {
5080
5880
  return argvBundled;
5081
5881
  }
5082
- return path16.join(os10.homedir(), ".bluma", "__bundled_skills_unresolved__");
5882
+ return path20.join(os12.homedir(), ".bluma", "__bundled_skills_unresolved__");
5083
5883
  }
5084
5884
  }
5085
5885
  /**
@@ -5108,8 +5908,8 @@ var SkillLoader = class _SkillLoader {
5108
5908
  this.conflicts.push({
5109
5909
  name: skill.name,
5110
5910
  userSource: source,
5111
- userPath: path16.join(dir, skill.name, "SKILL.md"),
5112
- bundledPath: path16.join(this.bundledSkillsDir, skill.name, "SKILL.md")
5911
+ userPath: path20.join(dir, skill.name, "SKILL.md"),
5912
+ bundledPath: path20.join(this.bundledSkillsDir, skill.name, "SKILL.md")
5113
5913
  });
5114
5914
  continue;
5115
5915
  }
@@ -5117,20 +5917,20 @@ var SkillLoader = class _SkillLoader {
5117
5917
  }
5118
5918
  }
5119
5919
  listFromDir(dir, source) {
5120
- if (!fs14.existsSync(dir)) return [];
5920
+ if (!fs17.existsSync(dir)) return [];
5121
5921
  try {
5122
- return fs14.readdirSync(dir).filter((d) => {
5123
- const fullPath = path16.join(dir, d);
5124
- return fs14.statSync(fullPath).isDirectory() && fs14.existsSync(path16.join(fullPath, "SKILL.md"));
5125
- }).map((d) => this.loadMetadataFromPath(path16.join(dir, d, "SKILL.md"), d, source)).filter((m) => m !== null);
5922
+ return fs17.readdirSync(dir).filter((d) => {
5923
+ const fullPath = path20.join(dir, d);
5924
+ return fs17.statSync(fullPath).isDirectory() && fs17.existsSync(path20.join(fullPath, "SKILL.md"));
5925
+ }).map((d) => this.loadMetadataFromPath(path20.join(dir, d, "SKILL.md"), d, source)).filter((m) => m !== null);
5126
5926
  } catch {
5127
5927
  return [];
5128
5928
  }
5129
5929
  }
5130
5930
  loadMetadataFromPath(skillPath, skillName, source) {
5131
- if (!fs14.existsSync(skillPath)) return null;
5931
+ if (!fs17.existsSync(skillPath)) return null;
5132
5932
  try {
5133
- const raw = fs14.readFileSync(skillPath, "utf-8");
5933
+ const raw = fs17.readFileSync(skillPath, "utf-8");
5134
5934
  const parsed = this.parseFrontmatter(raw);
5135
5935
  return {
5136
5936
  name: parsed.name || skillName,
@@ -5152,12 +5952,12 @@ var SkillLoader = class _SkillLoader {
5152
5952
  */
5153
5953
  load(name) {
5154
5954
  if (this.cache.has(name)) return this.cache.get(name);
5155
- const bundledPath = path16.join(this.bundledSkillsDir, name, "SKILL.md");
5156
- const projectPath = path16.join(this.projectSkillsDir, name, "SKILL.md");
5157
- const globalPath = path16.join(this.globalSkillsDir, name, "SKILL.md");
5158
- const existsBundled = fs14.existsSync(bundledPath);
5159
- const existsProject = fs14.existsSync(projectPath);
5160
- const existsGlobal = fs14.existsSync(globalPath);
5955
+ const bundledPath = path20.join(this.bundledSkillsDir, name, "SKILL.md");
5956
+ const projectPath = path20.join(this.projectSkillsDir, name, "SKILL.md");
5957
+ const globalPath = path20.join(this.globalSkillsDir, name, "SKILL.md");
5958
+ const existsBundled = fs17.existsSync(bundledPath);
5959
+ const existsProject = fs17.existsSync(projectPath);
5960
+ const existsGlobal = fs17.existsSync(globalPath);
5161
5961
  if (existsBundled && (existsProject || existsGlobal)) {
5162
5962
  const conflictSource = existsProject ? "project" : "global";
5163
5963
  const conflictPath = existsProject ? projectPath : globalPath;
@@ -5196,9 +5996,9 @@ var SkillLoader = class _SkillLoader {
5196
5996
  }
5197
5997
  loadFromPath(skillPath, name, source) {
5198
5998
  try {
5199
- const raw = fs14.readFileSync(skillPath, "utf-8");
5999
+ const raw = fs17.readFileSync(skillPath, "utf-8");
5200
6000
  const parsed = this.parseFrontmatter(raw);
5201
- const skillDir = path16.dirname(skillPath);
6001
+ const skillDir = path20.dirname(skillPath);
5202
6002
  return {
5203
6003
  name: parsed.name || name,
5204
6004
  description: parsed.description || "",
@@ -5207,22 +6007,22 @@ var SkillLoader = class _SkillLoader {
5207
6007
  version: parsed.version,
5208
6008
  author: parsed.author,
5209
6009
  license: parsed.license,
5210
- references: this.scanAssets(path16.join(skillDir, "references")),
5211
- scripts: this.scanAssets(path16.join(skillDir, "scripts"))
6010
+ references: this.scanAssets(path20.join(skillDir, "references")),
6011
+ scripts: this.scanAssets(path20.join(skillDir, "scripts"))
5212
6012
  };
5213
6013
  } catch {
5214
6014
  return null;
5215
6015
  }
5216
6016
  }
5217
6017
  scanAssets(dir) {
5218
- if (!fs14.existsSync(dir)) return [];
6018
+ if (!fs17.existsSync(dir)) return [];
5219
6019
  try {
5220
- return fs14.readdirSync(dir).filter((f) => {
5221
- const fp = path16.join(dir, f);
5222
- return fs14.statSync(fp).isFile();
6020
+ return fs17.readdirSync(dir).filter((f) => {
6021
+ const fp = path20.join(dir, f);
6022
+ return fs17.statSync(fp).isFile();
5223
6023
  }).map((f) => ({
5224
6024
  name: f,
5225
- path: path16.resolve(dir, f)
6025
+ path: path20.resolve(dir, f)
5226
6026
  }));
5227
6027
  } catch {
5228
6028
  return [];
@@ -5279,10 +6079,10 @@ var SkillLoader = class _SkillLoader {
5279
6079
  this.cache.clear();
5280
6080
  }
5281
6081
  exists(name) {
5282
- const bundledPath = path16.join(this.bundledSkillsDir, name, "SKILL.md");
5283
- const projectPath = path16.join(this.projectSkillsDir, name, "SKILL.md");
5284
- const globalPath = path16.join(this.globalSkillsDir, name, "SKILL.md");
5285
- return fs14.existsSync(bundledPath) || fs14.existsSync(projectPath) || fs14.existsSync(globalPath);
6082
+ const bundledPath = path20.join(this.bundledSkillsDir, name, "SKILL.md");
6083
+ const projectPath = path20.join(this.projectSkillsDir, name, "SKILL.md");
6084
+ const globalPath = path20.join(this.globalSkillsDir, name, "SKILL.md");
6085
+ return fs17.existsSync(bundledPath) || fs17.existsSync(projectPath) || fs17.existsSync(globalPath);
5286
6086
  }
5287
6087
  /**
5288
6088
  * Retorna conflitos detetados (skills do utilizador com mesmo nome de nativas).
@@ -5314,8 +6114,8 @@ var SkillLoader = class _SkillLoader {
5314
6114
  };
5315
6115
 
5316
6116
  // src/app/agent/core/prompt/workspace_snapshot.ts
5317
- import fs15 from "fs";
5318
- import path17 from "path";
6117
+ import fs18 from "fs";
6118
+ import path21 from "path";
5319
6119
  import { execSync } from "child_process";
5320
6120
  var LIMITS = {
5321
6121
  readme: 1e4,
@@ -5330,10 +6130,10 @@ var LIMITS = {
5330
6130
  };
5331
6131
  function safeReadFile(filePath, maxChars) {
5332
6132
  try {
5333
- if (!fs15.existsSync(filePath)) return null;
5334
- const st = fs15.statSync(filePath);
6133
+ if (!fs18.existsSync(filePath)) return null;
6134
+ const st = fs18.statSync(filePath);
5335
6135
  if (!st.isFile()) return null;
5336
- const raw = fs15.readFileSync(filePath, "utf8");
6136
+ const raw = fs18.readFileSync(filePath, "utf8");
5337
6137
  if (raw.length <= maxChars) return raw;
5338
6138
  return `${raw.slice(0, maxChars)}
5339
6139
 
@@ -5344,7 +6144,7 @@ function safeReadFile(filePath, maxChars) {
5344
6144
  }
5345
6145
  function tryReadReadme(cwd) {
5346
6146
  for (const name of ["README.md", "README.MD", "readme.md", "Readme.md"]) {
5347
- const c = safeReadFile(path17.join(cwd, name), LIMITS.readme);
6147
+ const c = safeReadFile(path21.join(cwd, name), LIMITS.readme);
5348
6148
  if (c) return `(${name})
5349
6149
  ${c}`;
5350
6150
  }
@@ -5352,14 +6152,14 @@ ${c}`;
5352
6152
  }
5353
6153
  function tryReadBluMaMd(cwd) {
5354
6154
  const paths = [
5355
- path17.join(cwd, "BluMa.md"),
5356
- path17.join(cwd, "BLUMA.md"),
5357
- path17.join(cwd, ".bluma", "BluMa.md")
6155
+ path21.join(cwd, "BluMa.md"),
6156
+ path21.join(cwd, "BLUMA.md"),
6157
+ path21.join(cwd, ".bluma", "BluMa.md")
5358
6158
  ];
5359
6159
  for (const p of paths) {
5360
6160
  const c = safeReadFile(p, LIMITS.blumaMd);
5361
6161
  if (c) {
5362
- const rel = path17.relative(cwd, p) || p;
6162
+ const rel = path21.relative(cwd, p) || p;
5363
6163
  return `(${rel})
5364
6164
  ${c}`;
5365
6165
  }
@@ -5367,10 +6167,10 @@ ${c}`;
5367
6167
  return null;
5368
6168
  }
5369
6169
  function summarizePackageJson(cwd) {
5370
- const p = path17.join(cwd, "package.json");
6170
+ const p = path21.join(cwd, "package.json");
5371
6171
  try {
5372
- if (!fs15.existsSync(p)) return null;
5373
- const pkg = JSON.parse(fs15.readFileSync(p, "utf8"));
6172
+ if (!fs18.existsSync(p)) return null;
6173
+ const pkg = JSON.parse(fs18.readFileSync(p, "utf8"));
5374
6174
  const scripts = pkg.scripts;
5375
6175
  let scriptKeys = "";
5376
6176
  if (scripts && typeof scripts === "object" && !Array.isArray(scripts)) {
@@ -5403,7 +6203,7 @@ function summarizePackageJson(cwd) {
5403
6203
  }
5404
6204
  function topLevelListing(cwd) {
5405
6205
  try {
5406
- const names = fs15.readdirSync(cwd, { withFileTypes: true });
6206
+ const names = fs18.readdirSync(cwd, { withFileTypes: true });
5407
6207
  const sorted = [...names].sort((a, b) => a.name.localeCompare(b.name));
5408
6208
  const limited = sorted.slice(0, LIMITS.topDirEntries);
5409
6209
  const lines = limited.map((d) => `${d.name}${d.isDirectory() ? "/" : ""}`);
@@ -5461,7 +6261,7 @@ function buildWorkspaceSnapshot(cwd) {
5461
6261
  parts.push(pkg);
5462
6262
  parts.push("```\n");
5463
6263
  }
5464
- const py = safeReadFile(path17.join(cwd, "pyproject.toml"), LIMITS.pyproject);
6264
+ const py = safeReadFile(path21.join(cwd, "pyproject.toml"), LIMITS.pyproject);
5465
6265
  if (py) {
5466
6266
  parts.push("### pyproject.toml (excerpt)\n```toml");
5467
6267
  parts.push(py);
@@ -5479,15 +6279,15 @@ function buildWorkspaceSnapshot(cwd) {
5479
6279
  parts.push(bluma);
5480
6280
  parts.push("```\n");
5481
6281
  }
5482
- const contrib = safeReadFile(path17.join(cwd, "CONTRIBUTING.md"), LIMITS.contributing);
6282
+ const contrib = safeReadFile(path21.join(cwd, "CONTRIBUTING.md"), LIMITS.contributing);
5483
6283
  if (contrib) {
5484
6284
  parts.push("### CONTRIBUTING.md (excerpt)\n```markdown");
5485
6285
  parts.push(contrib);
5486
6286
  parts.push("```\n");
5487
6287
  }
5488
- const chlog = safeReadFile(path17.join(cwd, "CHANGELOG.md"), LIMITS.changelog);
6288
+ const chlog = safeReadFile(path21.join(cwd, "CHANGELOG.md"), LIMITS.changelog);
5489
6289
  if (!chlog) {
5490
- const alt = safeReadFile(path17.join(cwd, "CHANGES.md"), LIMITS.changelog);
6290
+ const alt = safeReadFile(path21.join(cwd, "CHANGES.md"), LIMITS.changelog);
5491
6291
  if (alt) {
5492
6292
  parts.push("### CHANGES.md (excerpt)\n```markdown");
5493
6293
  parts.push(alt);
@@ -5550,10 +6350,10 @@ function getGitBranch(dir) {
5550
6350
  }
5551
6351
  function getPackageManager(dir) {
5552
6352
  try {
5553
- if (fs16.existsSync(path18.join(dir, "pnpm-lock.yaml"))) return "pnpm";
5554
- if (fs16.existsSync(path18.join(dir, "yarn.lock"))) return "yarn";
5555
- if (fs16.existsSync(path18.join(dir, "bun.lockb"))) return "bun";
5556
- if (fs16.existsSync(path18.join(dir, "package-lock.json"))) return "npm";
6353
+ if (fs19.existsSync(path22.join(dir, "pnpm-lock.yaml"))) return "pnpm";
6354
+ if (fs19.existsSync(path22.join(dir, "yarn.lock"))) return "yarn";
6355
+ if (fs19.existsSync(path22.join(dir, "bun.lockb"))) return "bun";
6356
+ if (fs19.existsSync(path22.join(dir, "package-lock.json"))) return "npm";
5557
6357
  return "unknown";
5558
6358
  } catch {
5559
6359
  return "unknown";
@@ -5561,9 +6361,9 @@ function getPackageManager(dir) {
5561
6361
  }
5562
6362
  function getProjectType(dir) {
5563
6363
  try {
5564
- const files = fs16.readdirSync(dir);
6364
+ const files = fs19.readdirSync(dir);
5565
6365
  if (files.includes("package.json")) {
5566
- const pkg = JSON.parse(fs16.readFileSync(path18.join(dir, "package.json"), "utf-8"));
6366
+ const pkg = JSON.parse(fs19.readFileSync(path22.join(dir, "package.json"), "utf-8"));
5567
6367
  if (pkg.dependencies?.next || pkg.devDependencies?.next) return "Next.js";
5568
6368
  if (pkg.dependencies?.react || pkg.devDependencies?.react) return "React";
5569
6369
  if (pkg.dependencies?.express || pkg.devDependencies?.express) return "Express";
@@ -5582,9 +6382,9 @@ function getProjectType(dir) {
5582
6382
  }
5583
6383
  function getTestFramework(dir) {
5584
6384
  try {
5585
- const pkgPath = path18.join(dir, "package.json");
5586
- if (fs16.existsSync(pkgPath)) {
5587
- const pkg = JSON.parse(fs16.readFileSync(pkgPath, "utf-8"));
6385
+ const pkgPath = path22.join(dir, "package.json");
6386
+ if (fs19.existsSync(pkgPath)) {
6387
+ const pkg = JSON.parse(fs19.readFileSync(pkgPath, "utf-8"));
5588
6388
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
5589
6389
  if (deps.jest) return "jest";
5590
6390
  if (deps.vitest) return "vitest";
@@ -5593,7 +6393,7 @@ function getTestFramework(dir) {
5593
6393
  if (deps["@playwright/test"]) return "playwright";
5594
6394
  if (deps.cypress) return "cypress";
5595
6395
  }
5596
- if (fs16.existsSync(path18.join(dir, "pytest.ini")) || fs16.existsSync(path18.join(dir, "conftest.py"))) return "pytest";
6396
+ if (fs19.existsSync(path22.join(dir, "pytest.ini")) || fs19.existsSync(path22.join(dir, "conftest.py"))) return "pytest";
5597
6397
  return "unknown";
5598
6398
  } catch {
5599
6399
  return "unknown";
@@ -5601,9 +6401,9 @@ function getTestFramework(dir) {
5601
6401
  }
5602
6402
  function getTestCommand(dir) {
5603
6403
  try {
5604
- const pkgPath = path18.join(dir, "package.json");
5605
- if (fs16.existsSync(pkgPath)) {
5606
- const pkg = JSON.parse(fs16.readFileSync(pkgPath, "utf-8"));
6404
+ const pkgPath = path22.join(dir, "package.json");
6405
+ if (fs19.existsSync(pkgPath)) {
6406
+ const pkg = JSON.parse(fs19.readFileSync(pkgPath, "utf-8"));
5607
6407
  if (pkg.scripts?.test) return `npm test`;
5608
6408
  if (pkg.scripts?.["test:unit"]) return `npm run test:unit`;
5609
6409
  }
@@ -5815,6 +6615,12 @@ This is your **long-term scratchpad** (usually \`~/.bluma/coding_memory.json\`).
5815
6615
  5. **Verify** - Check build, lint, and git status
5816
6616
  6. **Summarize** - Report results and any follow-up actions
5817
6617
 
6618
+ ### For delegated / parallel work:
6619
+ 1. **Keep the blocking task local** - Do not delegate the very next thing you need.
6620
+ 2. **Spawn bounded workers** - Use \`spawn_agent\` only for concrete side tasks with a clear output.
6621
+ 3. **Track workers explicitly** - Use \`wait_agent\` when blocked on a delegated result; use \`list_agents\` to inspect worker state.
6622
+ 4. **Integrate evidence** - Treat worker results like any other tool output: verify before claiming success.
6623
+
5818
6624
  ### For debugging / incidents (follow \`<engineering_mindset>\`):
5819
6625
  1. **Reproduce** \u2014 run the same command or test the user (or the repo) uses; capture full output via \`command_status\`.
5820
6626
  2. **Localise** \u2014 trace to a specific file/line; read that code with tools before changing anything.
@@ -5844,6 +6650,7 @@ Run tests when modifying code. If a testing skill is listed in available_skills,
5844
6650
  - **For edit_tool**: Provide exact content with correct whitespace (read first!)
5845
6651
  - **Truncated CLI preview** (user may press Ctrl+O for more lines): still not a substitute for \`read_file_lines\` \u2014 use the tool for authoritative content before editing
5846
6652
  - **Check file exists** before attempting edits
6653
+ - **Delegate only bounded work** - If you use \`spawn_agent\`, give the worker one concrete subtask and later collect it with \`wait_agent\`
5847
6654
 
5848
6655
  ### Safe Auto-Approved Tools (no confirmation needed):
5849
6656
  - message
@@ -5851,10 +6658,13 @@ Run tests when modifying code. If a testing skill is listed in available_skills,
5851
6658
  - read_file_lines
5852
6659
  - count_file_lines
5853
6660
  - todo
6661
+ - wait_agent
6662
+ - list_agents
5854
6663
 
5855
6664
  ### Require Confirmation:
5856
6665
  - shell_command (except safe commands)
5857
6666
  - edit_tool (always shows diff preview)
6667
+ - spawn_agent (outside sandbox)
5858
6668
  </tool_rules>
5859
6669
 
5860
6670
  ---
@@ -6054,18 +6864,20 @@ You are NOT talking directly to a human; all inputs come from JSON payloads prov
6054
6864
  - ALL outputs must be deterministic, concise and structured for machine parsing.
6055
6865
  - You own this workspace like a senior developer owns their machine: produce, deliver, clean up.
6056
6866
 
6057
- ### Execution Capabilities (Python-only)
6867
+ ### Execution Capabilities (Workspace-first)
6058
6868
 
6059
6869
  You are allowed to:
6060
- - Generate and modify **Python code** (modules, scripts, notebooks, tests).
6061
- - Run **Python commands only**: \`python main.py\`, \`python -m pytest\`, \`python script.py\`, etc.
6062
- - Use the preinstalled Python environment and libraries (pandas, openpyxl, reportlab, etc.).
6870
+ - Read, create, edit and overwrite files INSIDE the job workspace.
6871
+ - Use \`shell_command\` for non-interactive build, test, conversion and packaging workflows.
6872
+ - Use language/toolchain commands that are relevant to the repo or job (Python, Node, npm, bun, uv, pytest, etc.) when they are available in the sandbox image.
6873
+ - Fetch exact remote pages with \`web_fetch\` and search external sources with \`search_web\` when the task needs it.
6874
+ - Produce deliverables directly inside the workspace and stage final outputs in \`./artifacts/\`.
6063
6875
 
6064
6876
  You are NOT allowed to:
6065
- - Execute arbitrary shell commands (\`bash\`, \`sh\`, \`zsh\`, \`fish\`, \`cmd\`, \`powershell\`).
6066
- - Run system-level tools (\`docker\`, \`npm\`, \`node\`, \`git\`, \`curl\`, \`wget\`, package managers).
6067
- - Change system configuration, users, permissions, or network settings.
6068
- - Use interactive stdin/stdout (\`input()\`, click/typer prompts, curses).
6877
+ - Escape the workspace root or assume access to the host machine outside the sandbox.
6878
+ - Change host-level system configuration, users, permissions, mounts or network policy.
6879
+ - Use interactive terminal applications or flows that require human stdin.
6880
+ - Expose secrets, tokens, or environment dumps.
6069
6881
 
6070
6882
  ### File Lifecycle in Sandbox (CRITICAL)
6071
6883
 
@@ -6073,8 +6885,8 @@ You are working in an **isolated job workspace**. Treat it as your personal deve
6073
6885
  Follow this workflow for every task that produces deliverables:
6074
6886
 
6075
6887
  **Step 1 \u2014 Analyse** the request and plan what files you need to generate.
6076
- **Step 2 \u2014 Write a script** (e.g. \`_task_runner.py\`) to produce the deliverables programmatically.
6077
- **Step 3 \u2014 Execute the script** via \`shell_command\` (\`python _task_runner.py\`).
6888
+ **Step 2 \u2014 Create or modify workspace files** needed for the job (scripts, templates, configs, source files, etc.).
6889
+ **Step 3 \u2014 Execute the workflow** via tools (\`shell_command\`, \`file_write\`, \`edit_tool\`, etc.).
6078
6890
  **Step 4 \u2014 Move or create final documents** inside \`./artifacts/\` directory.
6079
6891
  **Step 5 \u2014 Attach deliverables** \u2014 In your final \`message\` tool call (\`message_type: "result"\`), include the **absolute paths** of every deliverable file in the \`attachments\` array.
6080
6892
  **Step 6 \u2014 Clean up** \u2014 Delete all temporary scripts, intermediate files and working data that are NOT final artifacts.
@@ -6128,9 +6940,9 @@ You MUST treat all environment variables, API keys, tokens and credentials as **
6128
6940
 
6129
6941
  ### Summary
6130
6942
 
6131
- In sandbox mode you are a Python-focused, non-interactive, deterministic agent that:
6943
+ In sandbox mode you are a workspace-focused, non-interactive, deterministic agent that:
6132
6944
  1. Analyses the job request.
6133
- 2. Writes and executes Python scripts to produce deliverables.
6945
+ 2. Uses the workspace and tools autonomously to produce deliverables.
6134
6946
  3. Places all final documents in \`./artifacts/\` and lists them in \`attachments[]\`.
6135
6947
  4. Cleans up all temporary files.
6136
6948
  5. NEVER reveals environment variables, secrets, or internal infrastructure details.
@@ -6139,12 +6951,12 @@ In sandbox mode you are a Python-focused, non-interactive, deterministic agent t
6139
6951
  function getUnifiedSystemPrompt(availableSkills) {
6140
6952
  const cwd = process.cwd();
6141
6953
  const env = {
6142
- os_type: os11.type(),
6143
- os_version: os11.release(),
6144
- architecture: os11.arch(),
6954
+ os_type: os13.type(),
6955
+ os_version: os13.release(),
6956
+ architecture: os13.arch(),
6145
6957
  workdir: cwd,
6146
6958
  shell_type: process.env.SHELL || process.env.COMSPEC || "unknown",
6147
- username: os11.userInfo().username,
6959
+ username: os13.userInfo().username,
6148
6960
  current_date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
6149
6961
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
6150
6962
  is_git_repo: isGitRepo(cwd) ? "yes" : "no",
@@ -6333,8 +7145,8 @@ ${memorySnapshot.trim().length > 0 ? memorySnapshot : "(No entries yet. Use codi
6333
7145
  }
6334
7146
  function isGitRepo(dir) {
6335
7147
  try {
6336
- const gitPath = path18.join(dir, ".git");
6337
- return fs16.existsSync(gitPath) && fs16.lstatSync(gitPath).isDirectory();
7148
+ const gitPath = path22.join(dir, ".git");
7149
+ return fs19.existsSync(gitPath) && fs19.lstatSync(gitPath).isDirectory();
6338
7150
  } catch {
6339
7151
  return false;
6340
7152
  }
@@ -6602,7 +7414,7 @@ async function createApiContextWindow(fullHistory, currentAnchor, compressedTurn
6602
7414
  }
6603
7415
 
6604
7416
  // src/app/agent/core/llm/llm.ts
6605
- import os12 from "os";
7417
+ import os14 from "os";
6606
7418
  import OpenAI from "openai";
6607
7419
  function defaultBlumaUserContextInput(sessionId, userMessage) {
6608
7420
  const msg = String(userMessage || "").slice(0, 300);
@@ -6619,7 +7431,7 @@ function defaultBlumaUserContextInput(sessionId, userMessage) {
6619
7431
  }
6620
7432
  function getPreferredMacAddress() {
6621
7433
  try {
6622
- const ifaces = os12.networkInterfaces();
7434
+ const ifaces = os14.networkInterfaces();
6623
7435
  for (const name of Object.keys(ifaces)) {
6624
7436
  const addrs = ifaces[name];
6625
7437
  if (!addrs) continue;
@@ -6634,7 +7446,7 @@ function getPreferredMacAddress() {
6634
7446
  } catch {
6635
7447
  }
6636
7448
  try {
6637
- return `host:${os12.hostname()}`;
7449
+ return `host:${os14.hostname()}`;
6638
7450
  } catch {
6639
7451
  return "unknown";
6640
7452
  }
@@ -6644,7 +7456,7 @@ function defaultInteractiveCliUserContextInput(sessionId, userMessage) {
6644
7456
  const machineId = getPreferredMacAddress();
6645
7457
  let userName = null;
6646
7458
  try {
6647
- userName = os12.userInfo().username || null;
7459
+ userName = os14.userInfo().username || null;
6648
7460
  } catch {
6649
7461
  userName = null;
6650
7462
  }
@@ -6971,6 +7783,29 @@ var ToolCallNormalizer = class {
6971
7783
  }
6972
7784
  };
6973
7785
 
7786
+ // src/app/agent/runtime/tool_execution_policy.ts
7787
+ init_sandbox_policy();
7788
+ function decideToolExecution(toolName) {
7789
+ const policy = getSandboxPolicy();
7790
+ const metadata = getNativeToolMetadata(toolName);
7791
+ if (!metadata) {
7792
+ return {
7793
+ toolName,
7794
+ autoApprove: false,
7795
+ requiresConfirmation: true,
7796
+ reason: "Unknown tool metadata; require confirmation by default."
7797
+ };
7798
+ }
7799
+ const autoApprove = policy.isSandbox ? metadata.autoApproveInSandbox : metadata.autoApproveInLocal;
7800
+ return {
7801
+ toolName,
7802
+ metadata,
7803
+ autoApprove,
7804
+ requiresConfirmation: !autoApprove,
7805
+ reason: autoApprove ? policy.isSandbox ? "Tool auto-approved inside workspace sandbox." : "Tool marked safe for local autonomous execution." : "Tool requires confirmation outside sandbox mode."
7806
+ };
7807
+ }
7808
+
6974
7809
  // src/app/agent/bluma/core/bluma.ts
6975
7810
  var BluMaAgent = class {
6976
7811
  llm;
@@ -7092,7 +7927,7 @@ var BluMaAgent = class {
7092
7927
  this.isInterrupted = false;
7093
7928
  this.factorRouterTurnClosed = false;
7094
7929
  const inputText = String(userInput.content || "").trim();
7095
- const turnId = uuidv43();
7930
+ const turnId = uuidv44();
7096
7931
  this.activeTurnContext = {
7097
7932
  ...userContextInput,
7098
7933
  turnId,
@@ -7178,7 +8013,8 @@ var BluMaAgent = class {
7178
8013
  type: "tool_call",
7179
8014
  tool_name: toolName,
7180
8015
  arguments: toolArgs,
7181
- preview: previewContent
8016
+ preview: previewContent,
8017
+ tool_policy: decideToolExecution(toolName)
7182
8018
  });
7183
8019
  try {
7184
8020
  if (this.isInterrupted) {
@@ -7252,7 +8088,7 @@ var BluMaAgent = class {
7252
8088
 
7253
8089
  ${editData.error.display}`;
7254
8090
  }
7255
- const filename = path19.basename(toolArgs.file_path);
8091
+ const filename = path23.basename(toolArgs.file_path);
7256
8092
  return createDiff(filename, editData.currentContent || "", editData.newContent);
7257
8093
  } catch (e) {
7258
8094
  return `An unexpected error occurred while generating the edit preview: ${e.message}`;
@@ -7379,29 +8215,27 @@ ${editData.error.display}`;
7379
8215
  await this._continueConversation();
7380
8216
  return;
7381
8217
  }
7382
- const isSandbox = process.env.BLUMA_SANDBOX === "true";
7383
- const autoApprovedTools = [
7384
- "message",
7385
- "ls_tool",
7386
- "count_file_lines",
7387
- "read_file_lines",
7388
- "todo",
7389
- "load_skill",
7390
- "search_web",
7391
- "coding_memory"
7392
- ];
7393
8218
  const toolToCall = validToolCalls[0];
7394
- const isSafeTool = isSandbox || autoApprovedTools.some((safeTool) => toolToCall.function.name.includes(safeTool));
7395
- if (isSafeTool) {
8219
+ const decision = decideToolExecution(toolToCall.function.name);
8220
+ if (decision.autoApprove) {
7396
8221
  await this.handleToolResponse({ type: "user_decision_execute", tool_calls: validToolCalls });
7397
8222
  } else {
7398
8223
  const toolName = toolToCall.function.name;
7399
8224
  if (toolName === "edit_tool") {
7400
8225
  const args = JSON.parse(toolToCall.function.arguments);
7401
8226
  const previewContent = await this._generateEditPreview(args);
7402
- this.eventBus.emit("backend_message", { type: "confirmation_request", tool_calls: validToolCalls, preview: previewContent });
8227
+ this.eventBus.emit("backend_message", {
8228
+ type: "confirmation_request",
8229
+ tool_calls: validToolCalls,
8230
+ preview: previewContent,
8231
+ tool_policy: decision
8232
+ });
7403
8233
  } else {
7404
- this.eventBus.emit("backend_message", { type: "confirmation_request", tool_calls: validToolCalls });
8234
+ this.eventBus.emit("backend_message", {
8235
+ type: "confirmation_request",
8236
+ tool_calls: validToolCalls,
8237
+ tool_policy: decision
8238
+ });
7405
8239
  }
7406
8240
  }
7407
8241
  } else if (trimmedText) {
@@ -7451,29 +8285,27 @@ ${editData.error.display}`;
7451
8285
  await this._continueConversation();
7452
8286
  return;
7453
8287
  }
7454
- const isSandbox = process.env.BLUMA_SANDBOX === "true";
7455
- const autoApprovedTools = [
7456
- "message",
7457
- "ls_tool",
7458
- "count_file_lines",
7459
- "read_file_lines",
7460
- "todo",
7461
- "load_skill",
7462
- "search_web",
7463
- "coding_memory"
7464
- ];
7465
8288
  const toolToCall = validToolCalls[0];
7466
- const isSafeTool = isSandbox || autoApprovedTools.some((safeTool) => toolToCall.function.name.includes(safeTool));
7467
- if (isSafeTool) {
8289
+ const decision = decideToolExecution(toolToCall.function.name);
8290
+ if (decision.autoApprove) {
7468
8291
  await this.handleToolResponse({ type: "user_decision_execute", tool_calls: validToolCalls });
7469
8292
  } else {
7470
8293
  const toolName = toolToCall.function.name;
7471
8294
  if (toolName === "edit_tool") {
7472
8295
  const args = JSON.parse(toolToCall.function.arguments);
7473
8296
  const previewContent = await this._generateEditPreview(args);
7474
- this.eventBus.emit("backend_message", { type: "confirmation_request", tool_calls: validToolCalls, preview: previewContent });
8297
+ this.eventBus.emit("backend_message", {
8298
+ type: "confirmation_request",
8299
+ tool_calls: validToolCalls,
8300
+ preview: previewContent,
8301
+ tool_policy: decision
8302
+ });
7475
8303
  } else {
7476
- this.eventBus.emit("backend_message", { type: "confirmation_request", tool_calls: validToolCalls });
8304
+ this.eventBus.emit("backend_message", {
8305
+ type: "confirmation_request",
8306
+ tool_calls: validToolCalls,
8307
+ tool_policy: decision
8308
+ });
7477
8309
  }
7478
8310
  }
7479
8311
  } else if (typeof message2.content === "string" && message2.content.trim()) {
@@ -7504,13 +8336,13 @@ function getSubAgentByCommand(cmd) {
7504
8336
  }
7505
8337
 
7506
8338
  // src/app/agent/subagents/init/init_subagent.ts
7507
- import { v4 as uuidv45 } from "uuid";
8339
+ import { v4 as uuidv46 } from "uuid";
7508
8340
 
7509
8341
  // src/app/agent/subagents/base_llm_subagent.ts
7510
- import { v4 as uuidv44 } from "uuid";
8342
+ import { v4 as uuidv45 } from "uuid";
7511
8343
 
7512
8344
  // src/app/agent/subagents/init/init_system_prompt.ts
7513
- import os13 from "os";
8345
+ import os15 from "os";
7514
8346
  var SYSTEM_PROMPT2 = `
7515
8347
 
7516
8348
  ### YOU ARE BluMa CLI \u2014 INIT SUBAGENT \u2014 AUTONOMOUS SENIOR SOFTWARE ENGINEER @ NOMADENGENUITY
@@ -7673,12 +8505,12 @@ Rule Summary:
7673
8505
  function getInitPrompt() {
7674
8506
  const now = /* @__PURE__ */ new Date();
7675
8507
  const collectedData = {
7676
- os_type: os13.type(),
7677
- os_version: os13.release(),
7678
- architecture: os13.arch(),
8508
+ os_type: os15.type(),
8509
+ os_version: os15.release(),
8510
+ architecture: os15.arch(),
7679
8511
  workdir: process.cwd(),
7680
8512
  shell_type: process.env.SHELL || process.env.COMSPEC || "Unknown",
7681
- username: os13.userInfo().username || "Unknown",
8513
+ username: os15.userInfo().username || "Unknown",
7682
8514
  current_date: now.toISOString().split("T")[0],
7683
8515
  // Formato YYYY-MM-DD
7684
8516
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Unknown",
@@ -7723,7 +8555,7 @@ var BaseLLMSubAgent = class {
7723
8555
  await this.initializeHistory();
7724
8556
  const rawUser = typeof input === "string" ? input : JSON.stringify(input);
7725
8557
  const base = ctx.blumaUserContextInput ?? defaultBlumaUserContextInput(`subagent:${this.id}`, rawUser.slice(0, 300));
7726
- const turnId = uuidv44();
8558
+ const turnId = uuidv45();
7727
8559
  this.subagentTurnContext = {
7728
8560
  ...base,
7729
8561
  turnId,
@@ -7784,6 +8616,14 @@ ${editData.error.display}`;
7784
8616
  const message2 = response.choices[0].message;
7785
8617
  this.history.push(message2);
7786
8618
  if (message2.tool_calls) {
8619
+ const decision = decideToolExecution(message2.tool_calls[0].function.name);
8620
+ if (!decision.autoApprove) {
8621
+ this.emitEvent("error", {
8622
+ message: `Subagent tool "${message2.tool_calls[0].function.name}" requires confirmation outside sandbox mode.`
8623
+ });
8624
+ this.emitEvent("done", { status: "blocked_confirmation" });
8625
+ return;
8626
+ }
7787
8627
  await this._handleToolExecution({ type: "user_decision_execute", tool_calls: message2.tool_calls });
7788
8628
  const lastToolName = message2.tool_calls[0].function.name;
7789
8629
  if (!lastToolName.includes("agent_end_turn") && !this.isInterrupted) {
@@ -7822,7 +8662,8 @@ ${editData.error.display}`;
7822
8662
  this.emitEvent("tool_call", {
7823
8663
  tool_name: toolName,
7824
8664
  arguments: toolArgs,
7825
- preview: previewContent
8665
+ preview: previewContent,
8666
+ tool_policy: decideToolExecution(toolName)
7826
8667
  });
7827
8668
  try {
7828
8669
  if (this.isInterrupted) {
@@ -7856,7 +8697,7 @@ ${editData.error.display}`;
7856
8697
 
7857
8698
  // src/app/agent/subagents/init/init_subagent.ts
7858
8699
  var InitAgentImpl = class extends BaseLLMSubAgent {
7859
- id = `init_${Date.now()}`;
8700
+ id = "init";
7860
8701
  capabilities = ["/init"];
7861
8702
  async execute(input, ctx) {
7862
8703
  this.ctx = ctx;
@@ -7869,7 +8710,7 @@ var InitAgentImpl = class extends BaseLLMSubAgent {
7869
8710
  const base = ctx.blumaUserContextInput ?? defaultBlumaUserContextInput(`subagent:${this.id}`, preview);
7870
8711
  this.subagentTurnContext = {
7871
8712
  ...base,
7872
- turnId: uuidv45(),
8713
+ turnId: uuidv46(),
7873
8714
  sessionId: base.sessionId || `subagent:${this.id}`
7874
8715
  };
7875
8716
  const seed = `
@@ -7879,7 +8720,11 @@ var InitAgentImpl = class extends BaseLLMSubAgent {
7879
8720
  Use only evidence gathered via tools; do not invent content.
7880
8721
  If overwriting an existing BluMa.md is required, follow non-destructive policies and request confirmation as per protocol.
7881
8722
  `;
7882
- const combined = seed;
8723
+ const initialInput = typeof input === "string" ? input.trim() : JSON.stringify(input ?? "").trim();
8724
+ const combined = initialInput ? `${seed}
8725
+
8726
+ User request for init:
8727
+ ${initialInput}` : seed;
7883
8728
  this.history.push({ role: "user", content: combined });
7884
8729
  await this._continueConversation();
7885
8730
  return { history: this.history };
@@ -7959,14 +8804,14 @@ var RouteManager = class {
7959
8804
  this.subAgents = subAgents;
7960
8805
  this.core = core;
7961
8806
  }
7962
- registerRoute(path23, handler) {
7963
- this.routeHandlers.set(path23, handler);
8807
+ registerRoute(path27, handler) {
8808
+ this.routeHandlers.set(path27, handler);
7964
8809
  }
7965
8810
  async handleRoute(payload) {
7966
8811
  const inputText = String(payload.content || "").trim();
7967
8812
  const { userContext } = payload;
7968
- for (const [path23, handler] of this.routeHandlers) {
7969
- if (inputText === path23 || inputText.startsWith(`${path23} `)) {
8813
+ for (const [path27, handler] of this.routeHandlers) {
8814
+ if (inputText === path27 || inputText.startsWith(`${path27} `)) {
7970
8815
  return handler({ content: inputText, userContext });
7971
8816
  }
7972
8817
  }
@@ -7975,7 +8820,7 @@ var RouteManager = class {
7975
8820
  };
7976
8821
 
7977
8822
  // src/app/agent/agent.ts
7978
- var globalEnvPath = path20.join(os14.homedir(), ".bluma", ".env");
8823
+ var globalEnvPath = path24.join(os16.homedir(), ".bluma", ".env");
7979
8824
  dotenv.config({ path: globalEnvPath });
7980
8825
  var Agent = class {
7981
8826
  sessionId;
@@ -8163,7 +9008,7 @@ var WorkingTimer = memo5(WorkingTimerComponent);
8163
9008
 
8164
9009
  // src/app/ui/components/ToolCallDisplay.tsx
8165
9010
  import { memo as memo6 } from "react";
8166
- import { Box as Box10 } from "ink";
9011
+ import { Box as Box10, Text as Text10 } from "ink";
8167
9012
 
8168
9013
  // src/app/ui/components/toolCallRenderers.tsx
8169
9014
  import { Box as Box9, Text as Text9 } from "ink";
@@ -8199,12 +9044,12 @@ var renderShellCommand2 = ({ args }) => {
8199
9044
  };
8200
9045
  var renderLsTool2 = ({ args }) => {
8201
9046
  const parsed = parseArgs(args);
8202
- const path23 = parsed.directory_path || ".";
9047
+ const path27 = parsed.directory_path || ".";
8203
9048
  return /* @__PURE__ */ jsxs9(Box9, { children: [
8204
9049
  /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "ls" }),
8205
9050
  /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
8206
9051
  " ",
8207
- path23
9052
+ path27
8208
9053
  ] })
8209
9054
  ] });
8210
9055
  };
@@ -8239,6 +9084,22 @@ var renderReadFileLines2 = ({ args }) => {
8239
9084
  ] })
8240
9085
  ] });
8241
9086
  };
9087
+ var renderFileWrite = ({ args }) => {
9088
+ const parsed = parseArgs(args);
9089
+ const filepath = parsed.filepath || "[no file]";
9090
+ const content = typeof parsed.content === "string" ? parsed.content : "";
9091
+ const preview = content.split("\n").slice(0, 3).join(" \xB7 ");
9092
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
9093
+ /* @__PURE__ */ jsxs9(Box9, { children: [
9094
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: "write" }),
9095
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
9096
+ " ",
9097
+ getBasename(filepath)
9098
+ ] })
9099
+ ] }),
9100
+ preview ? /* @__PURE__ */ jsx9(Box9, { paddingLeft: 2, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: truncate(preview, 80) }) }) : null
9101
+ ] });
9102
+ };
8242
9103
  var renderBlumaNotebook = ({ args }) => {
8243
9104
  const parsed = parseArgs(args);
8244
9105
  const thought = parsed.thought || parsed.content?.thought || "[thinking...]";
@@ -8340,7 +9201,7 @@ var renderFindByName = ({ args }) => {
8340
9201
  var renderGrepSearch = ({ args }) => {
8341
9202
  const parsed = parseArgs(args);
8342
9203
  const query = parsed.query || "";
8343
- const path23 = parsed.path || ".";
9204
+ const path27 = parsed.path || ".";
8344
9205
  return /* @__PURE__ */ jsxs9(Box9, { children: [
8345
9206
  /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "grep" }),
8346
9207
  /* @__PURE__ */ jsxs9(Text9, { color: BLUMA_TERMINAL.brandMagenta, children: [
@@ -8350,7 +9211,7 @@ var renderGrepSearch = ({ args }) => {
8350
9211
  ] }),
8351
9212
  /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
8352
9213
  " ",
8353
- path23
9214
+ path27
8354
9215
  ] })
8355
9216
  ] });
8356
9217
  };
@@ -8376,6 +9237,54 @@ var renderCommandStatus = ({ args }) => {
8376
9237
  ] })
8377
9238
  ] });
8378
9239
  };
9240
+ var renderWebFetch = ({ args }) => {
9241
+ const parsed = parseArgs(args);
9242
+ const url = parsed.url || "[no url]";
9243
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
9244
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "fetch" }),
9245
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
9246
+ " ",
9247
+ truncate(url, 70)
9248
+ ] })
9249
+ ] });
9250
+ };
9251
+ var renderSpawnAgent = ({ args }) => {
9252
+ const parsed = parseArgs(args);
9253
+ const title = parsed.title || parsed.agent_type || "worker";
9254
+ const task = parsed.task || "[no task]";
9255
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
9256
+ /* @__PURE__ */ jsxs9(Box9, { children: [
9257
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: "spawn" }),
9258
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
9259
+ " ",
9260
+ title
9261
+ ] })
9262
+ ] }),
9263
+ /* @__PURE__ */ jsx9(Box9, { paddingLeft: 2, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: truncate(String(task), 80) }) })
9264
+ ] });
9265
+ };
9266
+ var renderWaitAgent = ({ args }) => {
9267
+ const parsed = parseArgs(args);
9268
+ const sessionId = parsed.session_id || "[no session]";
9269
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
9270
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "wait" }),
9271
+ /* @__PURE__ */ jsxs9(Text9, { color: BLUMA_TERMINAL.brandMagenta, children: [
9272
+ " ",
9273
+ truncate(String(sessionId), 18)
9274
+ ] })
9275
+ ] });
9276
+ };
9277
+ var renderListAgents = ({ args }) => {
9278
+ const parsed = parseArgs(args);
9279
+ const status = parsed.status ? `status=${parsed.status}` : "all";
9280
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
9281
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "agents" }),
9282
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
9283
+ " ",
9284
+ status
9285
+ ] })
9286
+ ] });
9287
+ };
8379
9288
  var renderTaskBoundary = ({ args }) => {
8380
9289
  const parsed = parseArgs(args);
8381
9290
  const name = parsed.task_name || "[no name]";
@@ -8442,36 +9351,50 @@ var ToolRenderDisplay = {
8442
9351
  read_file_lines: renderReadFileLines2,
8443
9352
  edit_tool: renderEditToolCall,
8444
9353
  todo: renderTodoTool2,
9354
+ file_write: renderFileWrite,
8445
9355
  find_by_name: renderFindByName,
8446
9356
  grep_search: renderGrepSearch,
8447
9357
  view_file_outline: renderViewFileOutline,
8448
9358
  command_status: renderCommandStatus,
9359
+ spawn_agent: renderSpawnAgent,
9360
+ wait_agent: renderWaitAgent,
9361
+ list_agents: renderListAgents,
8449
9362
  task_boundary: renderTaskBoundary,
9363
+ web_fetch: renderWebFetch,
8450
9364
  search_web: renderSearchWeb,
8451
9365
  load_skill: renderLoadSkill
8452
9366
  };
8453
9367
 
8454
9368
  // src/app/ui/components/ToolCallDisplay.tsx
8455
- import { jsx as jsx10 } from "react/jsx-runtime";
8456
- var ToolCallDisplayComponent = ({ toolName, args, preview }) => {
9369
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
9370
+ var ToolCallDisplayComponent = ({ toolName, args, preview, toolPolicy }) => {
8457
9371
  if (toolName.includes("message") || toolName.includes("task_boundary") || toolName === "todo") {
8458
9372
  return null;
8459
9373
  }
8460
9374
  const Renderer = ToolRenderDisplay[toolName] || ((props) => renderGeneric2({ ...props, toolName }));
8461
- return /* @__PURE__ */ jsx10(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", paddingLeft: 1, children: /* @__PURE__ */ jsx10(Renderer, { toolName, args, preview }) }) });
9375
+ return /* @__PURE__ */ jsx10(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingLeft: 1, children: [
9376
+ toolPolicy?.metadata ? /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
9377
+ toolPolicy.metadata.category,
9378
+ " \xB7 ",
9379
+ toolPolicy.metadata.riskLevel,
9380
+ " \xB7 ",
9381
+ toolPolicy.autoApprove ? "auto" : "confirm"
9382
+ ] }) : null,
9383
+ /* @__PURE__ */ jsx10(Renderer, { toolName, args, preview })
9384
+ ] }) });
8462
9385
  };
8463
9386
  var ToolCallDisplay = memo6(ToolCallDisplayComponent);
8464
9387
 
8465
9388
  // src/app/ui/components/ToolResultDisplay.tsx
8466
9389
  import { memo as memo8, useEffect as useEffect5 } from "react";
8467
- import { Box as Box12, Text as Text11 } from "ink";
9390
+ import { Box as Box12, Text as Text12 } from "ink";
8468
9391
 
8469
9392
  // src/app/ui/components/MarkdownRenderer.tsx
8470
9393
  import { cloneElement, isValidElement, memo as memo7 } from "react";
8471
- import { Box as Box11, Text as Text10 } from "ink";
9394
+ import { Box as Box11, Text as Text11 } from "ink";
8472
9395
  import { marked } from "marked";
8473
9396
  import { highlight } from "cli-highlight";
8474
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
9397
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
8475
9398
  marked.setOptions({ gfm: true });
8476
9399
  function styleInlineNodes(nodes, keyPrefix, style) {
8477
9400
  return nodes.map(
@@ -8503,7 +9426,7 @@ function walkInline(tokens, keyBase, opts = {}) {
8503
9426
  case "text":
8504
9427
  out.push(
8505
9428
  /* @__PURE__ */ jsx11(
8506
- Text10,
9429
+ Text11,
8507
9430
  {
8508
9431
  bold: opts.bold,
8509
9432
  italic: opts.italic,
@@ -8525,7 +9448,7 @@ function walkInline(tokens, keyBase, opts = {}) {
8525
9448
  case "codespan":
8526
9449
  out.push(
8527
9450
  /* @__PURE__ */ jsx11(
8528
- Text10,
9451
+ Text11,
8529
9452
  {
8530
9453
  color: opts.link ? BLUMA_TERMINAL.link : BLUMA_TERMINAL.code,
8531
9454
  backgroundColor: opts.link ? void 0 : "black",
@@ -8540,19 +9463,19 @@ function walkInline(tokens, keyBase, opts = {}) {
8540
9463
  const L = t;
8541
9464
  const inner = walkInline(L.tokens, k + "l", { ...opts, link: true });
8542
9465
  out.push(
8543
- /* @__PURE__ */ jsx11(Box11, { flexDirection: "row", flexWrap: "wrap", children: inner.length > 0 ? inner : /* @__PURE__ */ jsx11(Text10, { color: BLUMA_TERMINAL.link, underline: true, children: L.text }) }, k)
9466
+ /* @__PURE__ */ jsx11(Box11, { flexDirection: "row", flexWrap: "wrap", children: inner.length > 0 ? inner : /* @__PURE__ */ jsx11(Text11, { color: BLUMA_TERMINAL.link, underline: true, children: L.text }) }, k)
8544
9467
  );
8545
9468
  break;
8546
9469
  }
8547
9470
  case "br":
8548
9471
  out.push(
8549
- /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "\n" }, k)
9472
+ /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "\n" }, k)
8550
9473
  );
8551
9474
  break;
8552
9475
  case "escape":
8553
9476
  out.push(
8554
9477
  /* @__PURE__ */ jsx11(
8555
- Text10,
9478
+ Text11,
8556
9479
  {
8557
9480
  bold: opts.bold,
8558
9481
  italic: opts.italic,
@@ -8570,7 +9493,7 @@ function walkInline(tokens, keyBase, opts = {}) {
8570
9493
  case "image": {
8571
9494
  const Im = t;
8572
9495
  out.push(
8573
- /* @__PURE__ */ jsxs10(Text10, { dimColor: true, italic: true, children: [
9496
+ /* @__PURE__ */ jsxs11(Text11, { dimColor: true, italic: true, children: [
8574
9497
  "[",
8575
9498
  Im.text || "img",
8576
9499
  "]"
@@ -8581,7 +9504,7 @@ function walkInline(tokens, keyBase, opts = {}) {
8581
9504
  default:
8582
9505
  if ("text" in t && typeof t.text === "string") {
8583
9506
  out.push(
8584
- /* @__PURE__ */ jsx11(Text10, { bold: opts.bold, italic: opts.italic, children: t.text }, k)
9507
+ /* @__PURE__ */ jsx11(Text11, { bold: opts.bold, italic: opts.italic, children: t.text }, k)
8585
9508
  );
8586
9509
  }
8587
9510
  }
@@ -8590,7 +9513,7 @@ function walkInline(tokens, keyBase, opts = {}) {
8590
9513
  }
8591
9514
  function renderParagraph(p, key) {
8592
9515
  const nodes = walkInline(p.tokens, key);
8593
- return /* @__PURE__ */ jsx11(Box11, { marginBottom: 1, flexDirection: "row", flexWrap: "wrap", children: nodes.length > 0 ? nodes : /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: p.text }) }, key);
9516
+ return /* @__PURE__ */ jsx11(Box11, { marginBottom: 1, flexDirection: "row", flexWrap: "wrap", children: nodes.length > 0 ? nodes : /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: p.text }) }, key);
8594
9517
  }
8595
9518
  function renderListItemBlocks(item, depth, keyBase) {
8596
9519
  if (!item.tokens?.length) {
@@ -8626,15 +9549,15 @@ function renderListBlock(list, depth, keyBase) {
8626
9549
  );
8627
9550
  const gColor = depth === 0 ? BLUMA_TERMINAL.listBullet : BLUMA_TERMINAL.listBulletSub;
8628
9551
  const body = renderListItemBlocks(item, depth, `${keyBase}-it${idx}`);
8629
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", alignItems: "flex-start", marginBottom: 0, children: [
8630
- /* @__PURE__ */ jsx11(Text10, { color: gColor, children: (item.task ? glyph : `${glyph} `).padEnd(item.task ? 4 : 3, " ") }),
8631
- /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", flexGrow: 1, children: body.length > 0 ? body : /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: item.text }) })
9552
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "row", alignItems: "flex-start", marginBottom: 0, children: [
9553
+ /* @__PURE__ */ jsx11(Text11, { color: gColor, children: (item.task ? glyph : `${glyph} `).padEnd(item.task ? 4 : 3, " ") }),
9554
+ /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", flexGrow: 1, children: body.length > 0 ? body : /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: item.text }) })
8632
9555
  ] }, `${keyBase}-row-${idx}`);
8633
9556
  }) }, keyBase);
8634
9557
  }
8635
9558
  function renderBlockquote(q, key) {
8636
- const inner = q.tokens && q.tokens.length > 0 ? renderBlockTokens(q.tokens, `${key}-inner`) : /* @__PURE__ */ jsx11(Text10, { dimColor: true, italic: true, children: q.text });
8637
- return /* @__PURE__ */ jsxs10(
9559
+ const inner = q.tokens && q.tokens.length > 0 ? renderBlockTokens(q.tokens, `${key}-inner`) : /* @__PURE__ */ jsx11(Text11, { dimColor: true, italic: true, children: q.text });
9560
+ return /* @__PURE__ */ jsxs11(
8638
9561
  Box11,
8639
9562
  {
8640
9563
  flexDirection: "row",
@@ -8644,7 +9567,7 @@ function renderBlockquote(q, key) {
8644
9567
  paddingLeft: 1,
8645
9568
  paddingY: 0,
8646
9569
  children: [
8647
- /* @__PURE__ */ jsx11(Text10, { color: BLUMA_TERMINAL.brandMagenta, children: "\u2502 " }),
9570
+ /* @__PURE__ */ jsx11(Text11, { color: BLUMA_TERMINAL.brandMagenta, children: "\u2502 " }),
8648
9571
  /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", flexGrow: 1, children: inner })
8649
9572
  ]
8650
9573
  },
@@ -8663,9 +9586,9 @@ function renderBlockTokens(tokens, keyRoot) {
8663
9586
  const prefix = h.depth <= 2 ? `${"#".repeat(h.depth)} ` : "";
8664
9587
  const inner = walkInline(h.tokens, `${key}-h`);
8665
9588
  elements.push(
8666
- /* @__PURE__ */ jsxs10(Box11, { marginBottom: 1, flexDirection: "row", flexWrap: "wrap", alignItems: "flex-start", children: [
8667
- /* @__PURE__ */ jsx11(Text10, { color, bold: true, children: prefix }),
8668
- inner.length > 0 ? inner : /* @__PURE__ */ jsx11(Text10, { color, bold: true, children: h.text })
9589
+ /* @__PURE__ */ jsxs11(Box11, { marginBottom: 1, flexDirection: "row", flexWrap: "wrap", alignItems: "flex-start", children: [
9590
+ /* @__PURE__ */ jsx11(Text11, { color, bold: true, children: prefix }),
9591
+ inner.length > 0 ? inner : /* @__PURE__ */ jsx11(Text11, { color, bold: true, children: h.text })
8669
9592
  ] }, key)
8670
9593
  );
8671
9594
  break;
@@ -8688,7 +9611,7 @@ function renderBlockTokens(tokens, keyRoot) {
8688
9611
  highlighted = code.text;
8689
9612
  }
8690
9613
  elements.push(
8691
- /* @__PURE__ */ jsxs10(
9614
+ /* @__PURE__ */ jsxs11(
8692
9615
  Box11,
8693
9616
  {
8694
9617
  flexDirection: "column",
@@ -8697,8 +9620,8 @@ function renderBlockTokens(tokens, keyRoot) {
8697
9620
  borderColor: BLUMA_TERMINAL.panelBorder,
8698
9621
  paddingX: 1,
8699
9622
  children: [
8700
- code.lang ? /* @__PURE__ */ jsx11(Text10, { color: BLUMA_TERMINAL.codeLabel, dimColor: true, children: code.lang }) : null,
8701
- /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: highlighted })
9623
+ code.lang ? /* @__PURE__ */ jsx11(Text11, { color: BLUMA_TERMINAL.codeLabel, dimColor: true, children: code.lang }) : null,
9624
+ /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: highlighted })
8702
9625
  ]
8703
9626
  },
8704
9627
  key
@@ -8712,15 +9635,15 @@ function renderBlockTokens(tokens, keyRoot) {
8712
9635
  case "table": {
8713
9636
  const table = token;
8714
9637
  elements.push(
8715
- /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginBottom: 1, children: [
9638
+ /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", marginBottom: 1, children: [
8716
9639
  /* @__PURE__ */ jsx11(Box11, { flexDirection: "row", children: table.header.map((cell, idx) => {
8717
9640
  const headerNodes = walkInline(cell.tokens, `${key}-h${idx}`);
8718
- const styled = headerNodes.length > 0 ? styleInlineNodes(headerNodes, `${key}-h${idx}`, { bold: true, color: BLUMA_TERMINAL.brandBlue }) : [/* @__PURE__ */ jsx11(Text10, { bold: true, color: BLUMA_TERMINAL.brandBlue, children: cell.text }, `${key}-h${idx}-t`)];
9641
+ const styled = headerNodes.length > 0 ? styleInlineNodes(headerNodes, `${key}-h${idx}`, { bold: true, color: BLUMA_TERMINAL.brandBlue }) : [/* @__PURE__ */ jsx11(Text11, { bold: true, color: BLUMA_TERMINAL.brandBlue, children: cell.text }, `${key}-h${idx}-t`)];
8719
9642
  return /* @__PURE__ */ jsx11(Box11, { paddingRight: 2, flexDirection: "row", flexWrap: "wrap", children: styled }, idx);
8720
9643
  }) }),
8721
9644
  table.rows.map((row, rowIdx) => /* @__PURE__ */ jsx11(Box11, { flexDirection: "row", children: row.map((cell, cellIdx) => {
8722
9645
  const cellNodes = walkInline(cell.tokens, `${key}-c${rowIdx}-${cellIdx}`);
8723
- const styled = cellNodes.length > 0 ? styleInlineNodes(cellNodes, `${key}-c${rowIdx}-${cellIdx}`, { dimColor: true }) : [/* @__PURE__ */ jsx11(Text10, { dimColor: true, children: cell.text }, `${key}-c${rowIdx}-${cellIdx}-t`)];
9646
+ const styled = cellNodes.length > 0 ? styleInlineNodes(cellNodes, `${key}-c${rowIdx}-${cellIdx}`, { dimColor: true }) : [/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: cell.text }, `${key}-c${rowIdx}-${cellIdx}-t`)];
8724
9647
  return /* @__PURE__ */ jsx11(Box11, { paddingRight: 2, flexDirection: "row", flexWrap: "wrap", children: styled }, cellIdx);
8725
9648
  }) }, `${key}-r${rowIdx}`))
8726
9649
  ] }, key)
@@ -8729,7 +9652,7 @@ function renderBlockTokens(tokens, keyRoot) {
8729
9652
  }
8730
9653
  case "hr":
8731
9654
  elements.push(
8732
- /* @__PURE__ */ jsx11(Box11, { marginY: 1, children: /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "\u2500".repeat(42) }) }, key)
9655
+ /* @__PURE__ */ jsx11(Box11, { marginY: 1, children: /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "\u2500".repeat(42) }) }, key)
8733
9656
  );
8734
9657
  break;
8735
9658
  case "space":
@@ -8737,7 +9660,7 @@ function renderBlockTokens(tokens, keyRoot) {
8737
9660
  default:
8738
9661
  if ("text" in token && typeof token.text === "string") {
8739
9662
  elements.push(
8740
- /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: token.text }, key)
9663
+ /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: token.text }, key)
8741
9664
  );
8742
9665
  }
8743
9666
  }
@@ -8824,7 +9747,7 @@ function peekLatestExpandable() {
8824
9747
  }
8825
9748
 
8826
9749
  // src/app/ui/components/ToolResultDisplay.tsx
8827
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
9750
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
8828
9751
  var parseResult2 = (result) => {
8829
9752
  try {
8830
9753
  return JSON.parse(result);
@@ -8846,29 +9769,111 @@ function TodoTaskLine({
8846
9769
  done,
8847
9770
  label
8848
9771
  }) {
8849
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", children: [
8850
- /* @__PURE__ */ jsx12(Text11, { color: done ? BLUMA_TERMINAL.brandBlue : BLUMA_TERMINAL.brandMagenta, children: done ? "\u25A0 " : "\u25A1 " }),
8851
- /* @__PURE__ */ jsx12(Text11, { dimColor: done, strikethrough: done, color: done ? BLUMA_TERMINAL.muted : void 0, children: label })
9772
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "row", children: [
9773
+ /* @__PURE__ */ jsx12(Text12, { color: done ? BLUMA_TERMINAL.brandBlue : BLUMA_TERMINAL.brandMagenta, children: done ? "\u25A0 " : "\u25A1 " }),
9774
+ /* @__PURE__ */ jsx12(Text12, { dimColor: done, strikethrough: done, color: done ? BLUMA_TERMINAL.muted : void 0, children: label })
8852
9775
  ] });
8853
9776
  }
8854
9777
  var ToolResultDisplayComponent = ({ toolName, result }) => {
8855
9778
  useEffect5(() => {
8856
9779
  refreshExpandableFromToolResult(toolName, result);
8857
9780
  }, [toolName, result]);
8858
- if (toolName.includes("task_boundary")) {
8859
- return null;
8860
- }
8861
9781
  const parsed = parseResult2(result);
9782
+ if (toolName.includes("task_boundary") && parsed) {
9783
+ const activeTask = parsed.activeTask;
9784
+ const stats = parsed.stats;
9785
+ const msg = typeof parsed.message === "string" ? parsed.message : "";
9786
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingLeft: 2, children: [
9787
+ msg ? /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: msg }) : null,
9788
+ activeTask ? /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
9789
+ String(activeTask.mode || "EXECUTION"),
9790
+ " \xB7 ",
9791
+ String(activeTask.taskName || ""),
9792
+ activeTask.status ? ` \xB7 ${String(activeTask.status)}` : ""
9793
+ ] }) : null,
9794
+ stats ? /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
9795
+ String(stats.completed ?? 0),
9796
+ "/",
9797
+ String(stats.total ?? 0),
9798
+ " done \xB7 ",
9799
+ String(stats.progress ?? 0),
9800
+ "%"
9801
+ ] }) : null
9802
+ ] }) });
9803
+ }
8862
9804
  if (toolName.includes("message")) {
8863
9805
  const body = parsed?.content?.body ?? parsed?.body ?? parsed?.message;
8864
9806
  if (!body) return null;
8865
9807
  return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, flexDirection: "column", children: /* @__PURE__ */ jsx12(MarkdownRenderer, { markdown: String(body) }) }) });
8866
9808
  }
9809
+ if (toolName.includes("file_write") && parsed) {
9810
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingLeft: 2, children: [
9811
+ /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
9812
+ parsed.created ? "created" : "updated",
9813
+ " ",
9814
+ String(parsed.filepath || "")
9815
+ ] }),
9816
+ parsed.bytes_written != null ? /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
9817
+ String(parsed.bytes_written),
9818
+ " bytes"
9819
+ ] }) : null
9820
+ ] }) });
9821
+ }
9822
+ if (toolName.includes("spawn_agent") && parsed) {
9823
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingLeft: 2, children: [
9824
+ /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
9825
+ String(parsed.title || "worker"),
9826
+ " \xB7 ",
9827
+ String(parsed.session_id || "")
9828
+ ] }),
9829
+ /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
9830
+ "status ",
9831
+ String(parsed.status || "running"),
9832
+ parsed.pid != null ? ` \xB7 pid ${String(parsed.pid)}` : ""
9833
+ ] })
9834
+ ] }) });
9835
+ }
9836
+ if (toolName.includes("wait_agent") && parsed) {
9837
+ const session = parsed.session || {};
9838
+ const resultStatus = parsed.result?.status;
9839
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingLeft: 2, children: [
9840
+ /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
9841
+ String(session.session_id || ""),
9842
+ " \xB7 ",
9843
+ String(session.status || "")
9844
+ ] }),
9845
+ /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
9846
+ parsed.completed ? "completed" : "still running",
9847
+ resultStatus ? ` \xB7 result ${String(resultStatus)}` : ""
9848
+ ] })
9849
+ ] }) });
9850
+ }
9851
+ if (toolName.includes("list_agents") && parsed) {
9852
+ const agents = Array.isArray(parsed.agents) ? parsed.agents : [];
9853
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingLeft: 2, children: [
9854
+ /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
9855
+ String(parsed.count || agents.length),
9856
+ " agents"
9857
+ ] }),
9858
+ agents.slice(0, 5).map((agent, i) => {
9859
+ const row = agent;
9860
+ return /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
9861
+ String(row.session_id || ""),
9862
+ " \xB7 ",
9863
+ String(row.status || "")
9864
+ ] }, i);
9865
+ }),
9866
+ agents.length > 5 ? /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
9867
+ "\u2026 +",
9868
+ agents.length - 5
9869
+ ] }) : null
9870
+ ] }) });
9871
+ }
8867
9872
  if (toolName.includes("read_file") && parsed && typeof parsed.content === "string") {
8868
9873
  const { lines, truncated } = truncateLines(parsed.content, TOOL_PREVIEW_MAX_LINES);
8869
- return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingLeft: 2, children: [
8870
- lines.map((line, i) => /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: line.slice(0, 120) }, i)),
8871
- truncated > 0 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
9874
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingLeft: 2, children: [
9875
+ lines.map((line, i) => /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: line.slice(0, 120) }, i)),
9876
+ truncated > 0 ? /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
8872
9877
  "\u2026 +",
8873
9878
  truncated,
8874
9879
  " lines \xB7 Ctrl+O expand"
@@ -8881,27 +9886,27 @@ var ToolResultDisplayComponent = ({ toolName, result }) => {
8881
9886
  const exitCode = parsed.exit_code ?? parsed.exitCode ?? 0;
8882
9887
  const status = String(parsed.status || "");
8883
9888
  if (parsed.command_id && !output && !stderr && !status) {
8884
- return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, children: /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
9889
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, children: /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
8885
9890
  "started #",
8886
9891
  String(parsed.command_id).slice(0, 8)
8887
9892
  ] }) }) });
8888
9893
  }
8889
9894
  if (status === "running") {
8890
- return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, children: /* @__PURE__ */ jsx12(Text11, { color: BLUMA_TERMINAL.warn, dimColor: true, children: "still running\u2026" }) }) });
9895
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, children: /* @__PURE__ */ jsx12(Text12, { color: BLUMA_TERMINAL.warn, dimColor: true, children: "still running\u2026" }) }) });
8891
9896
  }
8892
9897
  if (!output && !stderr) return null;
8893
9898
  const { lines, truncated } = truncateLines(output || stderr, TOOL_PREVIEW_MAX_LINES);
8894
9899
  const isError = exitCode !== 0;
8895
9900
  const isSuccess = exitCode === 0 && status !== "running";
8896
9901
  const lineColor = isError ? BLUMA_TERMINAL.err : isSuccess ? BLUMA_TERMINAL.success : BLUMA_TERMINAL.muted;
8897
- return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingLeft: 2, children: [
8898
- lines.map((line, i) => /* @__PURE__ */ jsx12(Text11, { dimColor: true, color: lineColor, children: line.slice(0, 120) }, i)),
8899
- truncated > 0 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
9902
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingLeft: 2, children: [
9903
+ lines.map((line, i) => /* @__PURE__ */ jsx12(Text12, { dimColor: true, color: lineColor, children: line.slice(0, 120) }, i)),
9904
+ truncated > 0 ? /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
8900
9905
  "\u2026 +",
8901
9906
  truncated,
8902
9907
  " lines \xB7 Ctrl+O expand"
8903
9908
  ] }) : null,
8904
- exitCode !== 0 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, color: BLUMA_TERMINAL.err, children: [
9909
+ exitCode !== 0 ? /* @__PURE__ */ jsxs12(Text12, { dimColor: true, color: BLUMA_TERMINAL.err, children: [
8905
9910
  "exit ",
8906
9911
  exitCode
8907
9912
  ] }) : null
@@ -8910,23 +9915,23 @@ var ToolResultDisplayComponent = ({ toolName, result }) => {
8910
9915
  if ((toolName.includes("grep") || toolName.includes("find_by_name")) && parsed) {
8911
9916
  const matches = parsed.matches || parsed.results || parsed.files || [];
8912
9917
  if (matches.length === 0) {
8913
- return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, children: /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "no matches" }) }) });
9918
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, children: /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "no matches" }) }) });
8914
9919
  }
8915
- return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingLeft: 2, children: [
8916
- /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
9920
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingLeft: 2, children: [
9921
+ /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
8917
9922
  matches.length,
8918
9923
  " matches"
8919
9924
  ] }),
8920
9925
  matches.slice(0, 5).map((m, i) => {
8921
9926
  const row = m;
8922
- const path23 = row.file || row.path || row.name || m;
9927
+ const path27 = row.file || row.path || row.name || m;
8923
9928
  const line = row.line;
8924
- return /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
8925
- String(path23),
9929
+ return /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
9930
+ String(path27),
8926
9931
  line != null ? `:${line}` : ""
8927
9932
  ] }, i);
8928
9933
  }),
8929
- matches.length > 5 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
9934
+ matches.length > 5 ? /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
8930
9935
  "\u2026 +",
8931
9936
  matches.length - 5
8932
9937
  ] }) : null
@@ -8935,17 +9940,17 @@ var ToolResultDisplayComponent = ({ toolName, result }) => {
8935
9940
  if (toolName.includes("ls_tool") && parsed) {
8936
9941
  const entries = parsed.entries || parsed.files || [];
8937
9942
  if (entries.length === 0) return null;
8938
- return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingLeft: 2, children: [
9943
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingLeft: 2, children: [
8939
9944
  entries.slice(0, 6).map((e, i) => {
8940
9945
  const row = e;
8941
9946
  const name = row.name ?? e;
8942
9947
  const isDir = Boolean(row.isDirectory);
8943
- return /* @__PURE__ */ jsxs11(Text11, { dimColor: true, color: isDir ? BLUMA_TERMINAL.brandBlue : BLUMA_TERMINAL.muted, children: [
9948
+ return /* @__PURE__ */ jsxs12(Text12, { dimColor: true, color: isDir ? BLUMA_TERMINAL.brandBlue : BLUMA_TERMINAL.muted, children: [
8944
9949
  isDir ? "d " : "f ",
8945
9950
  String(name)
8946
9951
  ] }, i);
8947
9952
  }),
8948
- entries.length > 6 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
9953
+ entries.length > 6 ? /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
8949
9954
  "\u2026 +",
8950
9955
  entries.length - 6
8951
9956
  ] }) : null
@@ -8953,15 +9958,15 @@ var ToolResultDisplayComponent = ({ toolName, result }) => {
8953
9958
  }
8954
9959
  if (toolName.includes("load_skill") && parsed) {
8955
9960
  if (!parsed.success) {
8956
- return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, children: /* @__PURE__ */ jsxs11(Text11, { dimColor: true, color: BLUMA_TERMINAL.err, children: [
9961
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, children: /* @__PURE__ */ jsxs12(Text12, { dimColor: true, color: BLUMA_TERMINAL.err, children: [
8957
9962
  "Not found: ",
8958
9963
  String(parsed.message || "")
8959
9964
  ] }) }) });
8960
9965
  }
8961
9966
  const desc = parsed.description ? String(parsed.description) : "";
8962
- return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { paddingLeft: 2, flexDirection: "row", flexWrap: "wrap", children: [
8963
- /* @__PURE__ */ jsx12(Text11, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: String(parsed.skill_name || "") }),
8964
- /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
9967
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Box12, { paddingLeft: 2, flexDirection: "row", flexWrap: "wrap", children: [
9968
+ /* @__PURE__ */ jsx12(Text12, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: String(parsed.skill_name || "") }),
9969
+ /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
8965
9970
  " ",
8966
9971
  "\u2014 ",
8967
9972
  desc.slice(0, 80),
@@ -8969,14 +9974,27 @@ var ToolResultDisplayComponent = ({ toolName, result }) => {
8969
9974
  ] })
8970
9975
  ] }) });
8971
9976
  }
9977
+ if (toolName.includes("web_fetch") && parsed) {
9978
+ const content = typeof parsed.content === "string" ? parsed.content : "";
9979
+ const { lines, truncated } = truncateLines(content, TOOL_PREVIEW_MAX_LINES);
9980
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingLeft: 2, children: [
9981
+ /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
9982
+ String(parsed.status_code ?? ""),
9983
+ " \xB7 ",
9984
+ String(parsed.content_type ?? "")
9985
+ ] }),
9986
+ lines.map((line, i) => /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: line.slice(0, 120) }, i)),
9987
+ truncated > 0 || parsed.truncated ? /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "\u2026 Ctrl+O expand" }) : null
9988
+ ] }) });
9989
+ }
8972
9990
  if (toolName.includes("todo")) {
8973
9991
  if (parsed && Array.isArray(parsed.tasks)) {
8974
9992
  const tasks = parsed.tasks;
8975
9993
  const stats = parsed.stats;
8976
9994
  const msg = typeof parsed.message === "string" ? parsed.message : "";
8977
- return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingLeft: 2, children: [
8978
- msg ? /* @__PURE__ */ jsx12(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: msg }) }) : null,
8979
- stats && typeof stats.progress === "number" ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
9995
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingLeft: 2, children: [
9996
+ msg ? /* @__PURE__ */ jsx12(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: msg }) }) : null,
9997
+ stats && typeof stats.progress === "number" ? /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
8980
9998
  stats.completed ?? 0,
8981
9999
  "/",
8982
10000
  stats.total ?? tasks.length,
@@ -8984,13 +10002,18 @@ var ToolResultDisplayComponent = ({ toolName, result }) => {
8984
10002
  stats.progress,
8985
10003
  "%"
8986
10004
  ] }) : null,
8987
- /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, children: [
10005
+ /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", marginTop: 1, children: [
10006
+ parsed.activeTask ? /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
10007
+ String(parsed.activeTask.mode || "EXECUTION"),
10008
+ " \xB7 ",
10009
+ String(parsed.activeTask.taskName || "")
10010
+ ] }) : null,
8988
10011
  tasks.slice(0, 12).map((t, i) => {
8989
10012
  const done = t.status === "completed" || t.isComplete === true;
8990
10013
  const line = `#${t.id ?? i + 1} ${t.description ?? ""}`;
8991
10014
  return /* @__PURE__ */ jsx12(TodoTaskLine, { done, label: line }, i);
8992
10015
  }),
8993
- tasks.length > 12 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
10016
+ tasks.length > 12 ? /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
8994
10017
  "\u2026 +",
8995
10018
  tasks.length - 12,
8996
10019
  " tasks"
@@ -9000,21 +10023,21 @@ var ToolResultDisplayComponent = ({ toolName, result }) => {
9000
10023
  }
9001
10024
  const lines = result.split("\n").filter(Boolean);
9002
10025
  if (lines.length === 0) return null;
9003
- return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", paddingLeft: 2, children: lines.slice(0, 14).map((line, i) => /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: line.slice(0, 100) }, i)) }) });
10026
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", paddingLeft: 2, children: lines.slice(0, 14).map((line, i) => /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: line.slice(0, 100) }, i)) }) });
9004
10027
  }
9005
10028
  if (toolName.includes("view_file_outline") && parsed) {
9006
10029
  const symbols = parsed.symbols || parsed.outline || [];
9007
10030
  if (symbols.length === 0) return null;
9008
- return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingLeft: 2, children: [
10031
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingLeft: 2, children: [
9009
10032
  symbols.slice(0, 5).map((s, i) => {
9010
10033
  const row = s;
9011
- return /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
10034
+ return /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
9012
10035
  String(row.kind || "sym"),
9013
10036
  " ",
9014
10037
  String(row.name || "")
9015
10038
  ] }, i);
9016
10039
  }),
9017
- symbols.length > 5 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
10040
+ symbols.length > 5 ? /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
9018
10041
  "\u2026 +",
9019
10042
  symbols.length - 5
9020
10043
  ] }) : null
@@ -9025,35 +10048,35 @@ var ToolResultDisplayComponent = ({ toolName, result }) => {
9025
10048
  var ToolResultDisplay = memo8(ToolResultDisplayComponent);
9026
10049
 
9027
10050
  // src/app/ui/SessionInfoConnectingMCP.tsx
9028
- import { Box as Box13, Text as Text12 } from "ink";
10051
+ import { Box as Box13, Text as Text13 } from "ink";
9029
10052
  import Spinner from "ink-spinner";
9030
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
10053
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
9031
10054
  var SessionInfoConnectingMCP = ({
9032
10055
  workdir,
9033
10056
  statusMessage
9034
- }) => /* @__PURE__ */ jsx13(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingLeft: 1, children: [
9035
- /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
9036
- /* @__PURE__ */ jsx13(Text12, { color: BLUMA_TERMINAL.brandMagenta, children: "\u2514 " }),
10057
+ }) => /* @__PURE__ */ jsx13(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingLeft: 1, children: [
10058
+ /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
10059
+ /* @__PURE__ */ jsx13(Text13, { color: BLUMA_TERMINAL.brandMagenta, children: "\u2514 " }),
9037
10060
  workdir
9038
10061
  ] }),
9039
- /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "row", flexWrap: "wrap", children: [
9040
- /* @__PURE__ */ jsxs12(Text12, { color: BLUMA_TERMINAL.warn, children: [
10062
+ /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, flexDirection: "row", flexWrap: "wrap", children: [
10063
+ /* @__PURE__ */ jsxs13(Text13, { color: BLUMA_TERMINAL.warn, children: [
9041
10064
  /* @__PURE__ */ jsx13(Spinner, { type: "dots" }),
9042
10065
  " "
9043
10066
  ] }),
9044
- /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: statusMessage || "Establishing MCP\u2026" })
10067
+ /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: statusMessage || "Establishing MCP\u2026" })
9045
10068
  ] })
9046
10069
  ] }) });
9047
10070
  var SessionInfoConnectingMCP_default = SessionInfoConnectingMCP;
9048
10071
 
9049
10072
  // src/app/ui/components/SlashCommands.tsx
9050
- import { Box as Box14, Text as Text13 } from "ink";
10073
+ import { Box as Box14, Text as Text14 } from "ink";
9051
10074
 
9052
10075
  // src/app/ui/constants/historyLayout.ts
9053
10076
  var HEADER_PANEL_HISTORY_ID = 0;
9054
10077
 
9055
10078
  // src/app/ui/components/SlashCommands.tsx
9056
- import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
10079
+ import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
9057
10080
  var SlashCommands = ({
9058
10081
  input,
9059
10082
  setHistory,
@@ -9069,13 +10092,13 @@ var SlashCommands = ({
9069
10092
  if (cmd === "help") {
9070
10093
  const lines = formatSlashHelpLines();
9071
10094
  return outBox(
9072
- /* @__PURE__ */ jsxs13(Fragment2, { children: [
9073
- /* @__PURE__ */ jsxs13(Box14, { marginBottom: 1, children: [
9074
- /* @__PURE__ */ jsx14(Text13, { bold: true, color: BLUMA_TERMINAL.brandMagenta, children: "Slash commands" }),
9075
- /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: " \xB7 " }),
9076
- /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: "put .png/.jpg/.webp paths in a normal message to attach images (project dir or ~)" })
10095
+ /* @__PURE__ */ jsxs14(Fragment2, { children: [
10096
+ /* @__PURE__ */ jsxs14(Box14, { marginBottom: 1, children: [
10097
+ /* @__PURE__ */ jsx14(Text14, { bold: true, color: BLUMA_TERMINAL.brandMagenta, children: "Slash commands" }),
10098
+ /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: " \xB7 " }),
10099
+ /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: "put .png/.jpg/.webp paths in a normal message to attach images (project dir or ~)" })
9077
10100
  ] }),
9078
- /* @__PURE__ */ jsx14(Box14, { flexDirection: "column", children: lines.map((line, i) => /* @__PURE__ */ jsx14(Text13, { dimColor: line.trim().length > 0, children: line || " " }, i)) })
10101
+ /* @__PURE__ */ jsx14(Box14, { flexDirection: "column", children: lines.map((line, i) => /* @__PURE__ */ jsx14(Text14, { dimColor: line.trim().length > 0, children: line || " " }, i)) })
9079
10102
  ] })
9080
10103
  );
9081
10104
  }
@@ -9083,32 +10106,32 @@ var SlashCommands = ({
9083
10106
  const list = agentRef.current?.listAvailableSkills?.() || [];
9084
10107
  const dirs = agentRef.current?.getSkillsDirs?.();
9085
10108
  return outBox(
9086
- /* @__PURE__ */ jsxs13(Fragment2, { children: [
9087
- /* @__PURE__ */ jsxs13(Box14, { marginBottom: 1, children: [
9088
- /* @__PURE__ */ jsx14(Text13, { bold: true, color: BLUMA_TERMINAL.brandMagenta, children: "Skills (load_skill)" }),
9089
- /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
10109
+ /* @__PURE__ */ jsxs14(Fragment2, { children: [
10110
+ /* @__PURE__ */ jsxs14(Box14, { marginBottom: 1, children: [
10111
+ /* @__PURE__ */ jsx14(Text14, { bold: true, color: BLUMA_TERMINAL.brandMagenta, children: "Skills (load_skill)" }),
10112
+ /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
9090
10113
  " \xB7 ",
9091
10114
  list.length,
9092
10115
  " available"
9093
10116
  ] })
9094
10117
  ] }),
9095
- dirs ? /* @__PURE__ */ jsxs13(Box14, { marginBottom: 1, flexDirection: "column", children: [
9096
- /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
10118
+ dirs ? /* @__PURE__ */ jsxs14(Box14, { marginBottom: 1, flexDirection: "column", children: [
10119
+ /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
9097
10120
  "bundled: ",
9098
10121
  String(dirs.bundled || "")
9099
10122
  ] }),
9100
- /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
10123
+ /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
9101
10124
  "project: ",
9102
10125
  String(dirs.project || "")
9103
10126
  ] }),
9104
- /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
10127
+ /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
9105
10128
  "global: ",
9106
10129
  String(dirs.global || "")
9107
10130
  ] })
9108
10131
  ] }) : null,
9109
- list.length === 0 ? /* @__PURE__ */ jsx14(Text13, { color: "yellow", children: "No skills found (check bundled dist/config/skills)." }) : /* @__PURE__ */ jsx14(Box14, { flexDirection: "column", children: list.map((s, i) => /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", marginBottom: 1, children: [
9110
- /* @__PURE__ */ jsx14(Text13, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: s.name }),
9111
- /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
10132
+ list.length === 0 ? /* @__PURE__ */ jsx14(Text14, { color: "yellow", children: "No skills found (check bundled dist/config/skills)." }) : /* @__PURE__ */ jsx14(Box14, { flexDirection: "column", children: list.map((s, i) => /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", marginBottom: 1, children: [
10133
+ /* @__PURE__ */ jsx14(Text14, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: s.name }),
10134
+ /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
9112
10135
  s.source,
9113
10136
  " \u2014 ",
9114
10137
  s.description || "\u2014"
@@ -9121,9 +10144,9 @@ var SlashCommands = ({
9121
10144
  onClearRecent?.();
9122
10145
  setHistory((prev) => prev.filter((item) => item.id === HEADER_PANEL_HISTORY_ID));
9123
10146
  return outBox(
9124
- /* @__PURE__ */ jsxs13(Box14, { children: [
9125
- /* @__PURE__ */ jsx14(Text13, { color: "green", children: "[ok]" }),
9126
- /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: " History cleared" })
10147
+ /* @__PURE__ */ jsxs14(Box14, { children: [
10148
+ /* @__PURE__ */ jsx14(Text14, { color: "green", children: "[ok]" }),
10149
+ /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: " History cleared" })
9127
10150
  ] })
9128
10151
  );
9129
10152
  }
@@ -9135,7 +10158,7 @@ var SlashCommands = ({
9135
10158
  setHistory((prev) => prev.concat({
9136
10159
  id: Date.now(),
9137
10160
  component: outBox(
9138
- /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsxs13(Text13, { color: "red", children: [
10161
+ /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsxs14(Text14, { color: "red", children: [
9139
10162
  "Failed to execute /init: ",
9140
10163
  e?.message || String(e)
9141
10164
  ] }) })
@@ -9156,41 +10179,41 @@ var SlashCommands = ({
9156
10179
  const colType = 10;
9157
10180
  const colSource = 18;
9158
10181
  return outBox(
9159
- /* @__PURE__ */ jsxs13(Fragment2, { children: [
9160
- /* @__PURE__ */ jsxs13(Box14, { marginBottom: 1, children: [
9161
- /* @__PURE__ */ jsx14(Text13, { bold: true, color: BLUMA_TERMINAL.brandMagenta, children: "MCP Tools" }),
9162
- /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: " \u2022 " }),
9163
- /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
10182
+ /* @__PURE__ */ jsxs14(Fragment2, { children: [
10183
+ /* @__PURE__ */ jsxs14(Box14, { marginBottom: 1, children: [
10184
+ /* @__PURE__ */ jsx14(Text14, { bold: true, color: BLUMA_TERMINAL.brandMagenta, children: "MCP Tools" }),
10185
+ /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: " \u2022 " }),
10186
+ /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
9164
10187
  tools.length,
9165
10188
  " total"
9166
10189
  ] }),
9167
- term && /* @__PURE__ */ jsxs13(Fragment2, { children: [
9168
- /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: " \u2022 filter: " }),
9169
- /* @__PURE__ */ jsxs13(Text13, { color: BLUMA_TERMINAL.brandBlue, children: [
10190
+ term && /* @__PURE__ */ jsxs14(Fragment2, { children: [
10191
+ /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: " \u2022 filter: " }),
10192
+ /* @__PURE__ */ jsxs14(Text14, { color: BLUMA_TERMINAL.brandBlue, children: [
9170
10193
  '"',
9171
10194
  term,
9172
10195
  '"'
9173
10196
  ] }),
9174
- /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
10197
+ /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
9175
10198
  " \u2022 showing: ",
9176
10199
  filtered.length
9177
10200
  ] })
9178
10201
  ] })
9179
10202
  ] }),
9180
- filtered.length === 0 ? /* @__PURE__ */ jsx14(Text13, { color: "yellow", children: "No MCP tools found" }) : /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
9181
- /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
10203
+ filtered.length === 0 ? /* @__PURE__ */ jsx14(Text14, { color: "yellow", children: "No MCP tools found" }) : /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
10204
+ /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsxs14(Text14, { color: "gray", children: [
9182
10205
  pad("Name", colName),
9183
10206
  " \u2502 ",
9184
10207
  pad("Type", colType),
9185
10208
  " \u2502 ",
9186
10209
  pad("Source", colSource)
9187
10210
  ] }) }),
9188
- /* @__PURE__ */ jsx14(Text13, { color: "gray", children: "\u2500".repeat(colName + colType + colSource + 6) }),
10211
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: "\u2500".repeat(colName + colType + colSource + 6) }),
9189
10212
  filtered.map((t, i) => {
9190
10213
  const name = t.function?.name || t.name || "tool";
9191
10214
  const type = t.function?.name ? "fn" : t.type || "tool";
9192
10215
  const source = t.source || t.provider || "mcp";
9193
- return /* @__PURE__ */ jsxs13(Text13, { color: "white", children: [
10216
+ return /* @__PURE__ */ jsxs14(Text14, { color: "white", children: [
9194
10217
  pad(name, colName),
9195
10218
  " \u2502 ",
9196
10219
  pad(String(type), colType),
@@ -9213,22 +10236,22 @@ var SlashCommands = ({
9213
10236
  const colType = 10;
9214
10237
  const colSource = 18;
9215
10238
  return outBox(
9216
- /* @__PURE__ */ jsxs13(Fragment2, { children: [
9217
- /* @__PURE__ */ jsx14(Text13, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: "Native Tools" }),
9218
- /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
10239
+ /* @__PURE__ */ jsxs14(Fragment2, { children: [
10240
+ /* @__PURE__ */ jsx14(Text14, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: "Native Tools" }),
10241
+ /* @__PURE__ */ jsxs14(Text14, { color: "gray", children: [
9219
10242
  "Total Native: ",
9220
10243
  tools.length,
9221
10244
  term ? ` | Filter: "${term}" | Showing: ${filtered.length}` : ""
9222
10245
  ] }),
9223
- filtered.length === 0 ? /* @__PURE__ */ jsx14(Text13, { color: "yellow", children: "No native tools to display." }) : /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
9224
- /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
10246
+ filtered.length === 0 ? /* @__PURE__ */ jsx14(Text14, { color: "yellow", children: "No native tools to display." }) : /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
10247
+ /* @__PURE__ */ jsxs14(Text14, { color: "gray", children: [
9225
10248
  pad("Name", colName),
9226
10249
  " | ",
9227
10250
  pad("Type", colType),
9228
10251
  " | ",
9229
10252
  pad("Source", colSource)
9230
10253
  ] }),
9231
- /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
10254
+ /* @__PURE__ */ jsxs14(Text14, { color: "gray", children: [
9232
10255
  "".padEnd(colName, "-"),
9233
10256
  "---",
9234
10257
  "".padEnd(colType, "-"),
@@ -9239,7 +10262,7 @@ var SlashCommands = ({
9239
10262
  const name = t.function?.name || t.name || "tool";
9240
10263
  const type = t.function?.name ? "fn" : t.type || "tool";
9241
10264
  const source = t.source || "native";
9242
- return /* @__PURE__ */ jsxs13(Text13, { color: "white", children: [
10265
+ return /* @__PURE__ */ jsxs14(Text14, { color: "white", children: [
9243
10266
  pad(name, colName),
9244
10267
  " | ",
9245
10268
  pad(String(type), colType),
@@ -9251,7 +10274,7 @@ var SlashCommands = ({
9251
10274
  ] })
9252
10275
  );
9253
10276
  }
9254
- return outBox(/* @__PURE__ */ jsxs13(Text13, { color: "red", children: [
10277
+ return outBox(/* @__PURE__ */ jsxs14(Text14, { color: "red", children: [
9255
10278
  "Command not recognized: /",
9256
10279
  cmd
9257
10280
  ] }));
@@ -9265,16 +10288,16 @@ import latestVersion from "latest-version";
9265
10288
  import semverGt from "semver/functions/gt.js";
9266
10289
  import semverValid from "semver/functions/valid.js";
9267
10290
  import { fileURLToPath as fileURLToPath4 } from "url";
9268
- import path21 from "path";
9269
- import fs17 from "fs";
10291
+ import path25 from "path";
10292
+ import fs20 from "fs";
9270
10293
  var BLUMA_PACKAGE_NAME = "@nomad-e/bluma-cli";
9271
10294
  function findBlumaPackageJson(startDir) {
9272
10295
  let dir = startDir;
9273
10296
  for (let i = 0; i < 12; i++) {
9274
- const candidate = path21.join(dir, "package.json");
9275
- if (fs17.existsSync(candidate)) {
10297
+ const candidate = path25.join(dir, "package.json");
10298
+ if (fs20.existsSync(candidate)) {
9276
10299
  try {
9277
- const raw = fs17.readFileSync(candidate, "utf8");
10300
+ const raw = fs20.readFileSync(candidate, "utf8");
9278
10301
  const parsed = JSON.parse(raw);
9279
10302
  if (parsed?.name === BLUMA_PACKAGE_NAME && parsed?.version) {
9280
10303
  return { name: parsed.name, version: String(parsed.version) };
@@ -9282,7 +10305,7 @@ function findBlumaPackageJson(startDir) {
9282
10305
  } catch {
9283
10306
  }
9284
10307
  }
9285
- const parent = path21.dirname(dir);
10308
+ const parent = path25.dirname(dir);
9286
10309
  if (parent === dir) break;
9287
10310
  dir = parent;
9288
10311
  }
@@ -9291,13 +10314,13 @@ function findBlumaPackageJson(startDir) {
9291
10314
  function resolveInstalledBlumaPackage() {
9292
10315
  const tried = /* @__PURE__ */ new Set();
9293
10316
  const tryFrom = (dir) => {
9294
- const abs = path21.resolve(dir);
10317
+ const abs = path25.resolve(dir);
9295
10318
  if (tried.has(abs)) return null;
9296
10319
  tried.add(abs);
9297
10320
  return findBlumaPackageJson(abs);
9298
10321
  };
9299
10322
  try {
9300
- const fromBundle = tryFrom(path21.dirname(fileURLToPath4(import.meta.url)));
10323
+ const fromBundle = tryFrom(path25.dirname(fileURLToPath4(import.meta.url)));
9301
10324
  if (fromBundle) return fromBundle;
9302
10325
  } catch {
9303
10326
  }
@@ -9305,12 +10328,12 @@ function resolveInstalledBlumaPackage() {
9305
10328
  if (argv1 && !argv1.startsWith("-")) {
9306
10329
  try {
9307
10330
  let resolved = argv1;
9308
- if (path21.isAbsolute(argv1) && fs17.existsSync(argv1)) {
9309
- resolved = fs17.realpathSync(argv1);
10331
+ if (path25.isAbsolute(argv1) && fs20.existsSync(argv1)) {
10332
+ resolved = fs20.realpathSync(argv1);
9310
10333
  } else {
9311
- resolved = path21.resolve(process.cwd(), argv1);
10334
+ resolved = path25.resolve(process.cwd(), argv1);
9312
10335
  }
9313
- const fromArgv = tryFrom(path21.dirname(resolved));
10336
+ const fromArgv = tryFrom(path25.dirname(resolved));
9314
10337
  if (fromArgv) return fromArgv;
9315
10338
  } catch {
9316
10339
  }
@@ -9354,8 +10377,8 @@ Run: npm i -g ${BLUMA_PACKAGE_NAME} to update.`;
9354
10377
  }
9355
10378
 
9356
10379
  // src/app/ui/components/UpdateNotice.tsx
9357
- import { Box as Box15, Text as Text14 } from "ink";
9358
- import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
10380
+ import { Box as Box15, Text as Text15 } from "ink";
10381
+ import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
9359
10382
  function parseUpdateMessage(msg) {
9360
10383
  const lines = msg.split(/\r?\n/).map((l) => l.trim());
9361
10384
  const first = lines[0] || "";
@@ -9371,25 +10394,25 @@ function parseUpdateMessage(msg) {
9371
10394
  }
9372
10395
  var UpdateNotice = ({ message: message2 }) => {
9373
10396
  const { name, current, latest: latest2, hint } = parseUpdateMessage(message2);
9374
- return /* @__PURE__ */ jsx15(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingLeft: 2, children: [
9375
- name && current && latest2 ? /* @__PURE__ */ jsx15(Text14, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: name }) : null,
9376
- name && current && latest2 ? /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
10397
+ return /* @__PURE__ */ jsx15(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", paddingLeft: 2, children: [
10398
+ name && current && latest2 ? /* @__PURE__ */ jsx15(Text15, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: name }) : null,
10399
+ name && current && latest2 ? /* @__PURE__ */ jsxs15(Text15, { dimColor: true, children: [
9377
10400
  current,
9378
10401
  " \u2192 ",
9379
10402
  latest2
9380
- ] }) : /* @__PURE__ */ jsx15(Text14, { dimColor: true, children: message2 }),
9381
- hint ? /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text14, { dimColor: true, children: hint }) }) : null
10403
+ ] }) : /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: message2 }),
10404
+ hint ? /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: hint }) }) : null
9382
10405
  ] }) });
9383
10406
  };
9384
10407
  var UpdateNotice_default = UpdateNotice;
9385
10408
 
9386
10409
  // src/app/ui/components/ErrorMessage.tsx
9387
- import { Box as Box16, Text as Text15 } from "ink";
9388
- import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
9389
- var ErrorMessage = ({ message: message2, details, hint }) => /* @__PURE__ */ jsx16(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", paddingLeft: 2, children: [
9390
- /* @__PURE__ */ jsx16(Text15, { color: BLUMA_TERMINAL.err, children: message2 }),
9391
- details ? /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text15, { dimColor: true, children: details }) }) : null,
9392
- hint ? /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text15, { dimColor: true, children: [
10410
+ import { Box as Box16, Text as Text16 } from "ink";
10411
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
10412
+ var ErrorMessage = ({ message: message2, details, hint }) => /* @__PURE__ */ jsx16(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", paddingLeft: 2, children: [
10413
+ /* @__PURE__ */ jsx16(Text16, { color: BLUMA_TERMINAL.err, children: message2 }),
10414
+ details ? /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: details }) }) : null,
10415
+ hint ? /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
9393
10416
  "hint: ",
9394
10417
  hint
9395
10418
  ] }) }) : null
@@ -9398,8 +10421,8 @@ var ErrorMessage_default = ErrorMessage;
9398
10421
 
9399
10422
  // src/app/ui/components/ReasoningDisplay.tsx
9400
10423
  import { memo as memo9 } from "react";
9401
- import { Box as Box17, Text as Text16 } from "ink";
9402
- import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
10424
+ import { Box as Box17, Text as Text17 } from "ink";
10425
+ import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
9403
10426
  var ReasoningDisplayComponent = ({
9404
10427
  reasoning,
9405
10428
  collapsed = false
@@ -9411,9 +10434,9 @@ var ReasoningDisplayComponent = ({
9411
10434
  const lines = reasoning.split("\n");
9412
10435
  const displayLines = lines.slice(0, maxLines);
9413
10436
  const truncated = lines.length > maxLines;
9414
- return /* @__PURE__ */ jsx17(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", paddingLeft: 2, children: [
9415
- displayLines.map((line, i) => /* @__PURE__ */ jsx17(Text16, { dimColor: true, children: line }, i)),
9416
- truncated ? /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
10437
+ return /* @__PURE__ */ jsx17(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", paddingLeft: 2, children: [
10438
+ displayLines.map((line, i) => /* @__PURE__ */ jsx17(Text17, { dimColor: true, children: line }, i)),
10439
+ truncated ? /* @__PURE__ */ jsxs17(Text17, { dimColor: true, children: [
9417
10440
  "\u2026 +",
9418
10441
  lines.length - maxLines,
9419
10442
  " lines"
@@ -9424,8 +10447,8 @@ var ReasoningDisplay = memo9(ReasoningDisplayComponent);
9424
10447
 
9425
10448
  // src/app/ui/components/StreamingText.tsx
9426
10449
  import { useState as useState5, useEffect as useEffect6, useRef as useRef4, memo as memo10 } from "react";
9427
- import { Box as Box18, Text as Text17 } from "ink";
9428
- import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
10450
+ import { Box as Box18, Text as Text18 } from "ink";
10451
+ import { jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
9429
10452
  var StreamingTextComponent = ({
9430
10453
  eventBus,
9431
10454
  onReasoningComplete
@@ -9491,21 +10514,21 @@ var StreamingTextComponent = ({
9491
10514
  truncatedCount = lines.length - MAX_VISIBLE_LINES;
9492
10515
  displayLines = lines.slice(-MAX_VISIBLE_LINES);
9493
10516
  }
9494
- return /* @__PURE__ */ jsx18(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", paddingLeft: 2, children: [
9495
- truncatedCount > 0 ? /* @__PURE__ */ jsxs17(Text17, { dimColor: true, children: [
10517
+ return /* @__PURE__ */ jsx18(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", paddingLeft: 2, children: [
10518
+ truncatedCount > 0 ? /* @__PURE__ */ jsxs18(Text18, { dimColor: true, children: [
9496
10519
  "\u2026 ",
9497
10520
  truncatedCount,
9498
10521
  " lines above hidden"
9499
10522
  ] }) : null,
9500
- displayLines.map((line, i) => /* @__PURE__ */ jsx18(Text17, { dimColor: true, children: line }, i))
10523
+ displayLines.map((line, i) => /* @__PURE__ */ jsx18(Text18, { dimColor: true, children: line }, i))
9501
10524
  ] }) });
9502
10525
  };
9503
10526
  var StreamingText = memo10(StreamingTextComponent);
9504
10527
 
9505
10528
  // src/app/ui/components/ExpandedPreviewBlock.tsx
9506
10529
  import { memo as memo11 } from "react";
9507
- import { Box as Box19, Text as Text18 } from "ink";
9508
- import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
10530
+ import { Box as Box19, Text as Text19 } from "ink";
10531
+ import { jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
9509
10532
  function ExpandedPreviewBlockComponent({ data }) {
9510
10533
  const cols = typeof process.stdout?.columns === "number" ? process.stdout.columns : 80;
9511
10534
  const rule = TERMINAL_RULE_CHAR.repeat(Math.max(8, cols));
@@ -9513,50 +10536,35 @@ function ExpandedPreviewBlockComponent({ data }) {
9513
10536
  const cap = EXPAND_OVERLAY_MAX_LINES;
9514
10537
  const shown = lines.slice(0, cap);
9515
10538
  const rest = lines.length - cap;
9516
- return /* @__PURE__ */ jsxs18(ChatBlock, { marginBottom: 1, children: [
9517
- /* @__PURE__ */ jsx19(Text18, { color: "white", children: rule }),
9518
- /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", paddingLeft: 1, children: [
9519
- /* @__PURE__ */ jsx19(Text18, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: "expand (Ctrl+O)" }),
9520
- /* @__PURE__ */ jsx19(Text18, { dimColor: true, children: data.title }),
9521
- /* @__PURE__ */ jsxs18(Text18, { dimColor: true, children: [
10539
+ return /* @__PURE__ */ jsxs19(ChatBlock, { marginBottom: 1, children: [
10540
+ /* @__PURE__ */ jsx19(Text19, { color: "white", children: rule }),
10541
+ /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", paddingLeft: 1, children: [
10542
+ /* @__PURE__ */ jsx19(Text19, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: "expand (Ctrl+O)" }),
10543
+ /* @__PURE__ */ jsx19(Text19, { dimColor: true, children: data.title }),
10544
+ /* @__PURE__ */ jsxs19(Text19, { dimColor: true, children: [
9522
10545
  "+",
9523
10546
  data.linesHidden,
9524
10547
  " lines were clipped in chat \xB7 below: up to ",
9525
10548
  cap,
9526
10549
  " lines \xB7 use read_file_lines before edit_tool"
9527
10550
  ] }),
9528
- /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", marginTop: 1, children: [
9529
- shown.map((line, i) => /* @__PURE__ */ jsx19(Text18, { dimColor: true, children: line.slice(0, 200) }, i)),
9530
- rest > 0 ? /* @__PURE__ */ jsxs18(Text18, { dimColor: true, children: [
10551
+ /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", marginTop: 1, children: [
10552
+ shown.map((line, i) => /* @__PURE__ */ jsx19(Text19, { dimColor: true, children: line.slice(0, 200) }, i)),
10553
+ rest > 0 ? /* @__PURE__ */ jsxs19(Text19, { dimColor: true, children: [
9531
10554
  "\u2026 +",
9532
10555
  rest,
9533
10556
  " more lines in this chunk"
9534
10557
  ] }) : null
9535
10558
  ] })
9536
10559
  ] }),
9537
- /* @__PURE__ */ jsx19(Text18, { color: "white", children: rule })
10560
+ /* @__PURE__ */ jsx19(Text19, { color: "white", children: rule })
9538
10561
  ] });
9539
10562
  }
9540
10563
  var ExpandedPreviewBlock = memo11(ExpandedPreviewBlockComponent);
9541
10564
 
9542
10565
  // src/app/ui/App.tsx
9543
- import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
10566
+ import { jsx as jsx20, jsxs as jsxs20 } from "react/jsx-runtime";
9544
10567
  var blumaUpdateRegistryCheckStarted = false;
9545
- var SAFE_AUTO_APPROVE_TOOLS = [
9546
- // Comunicação/UI
9547
- "message",
9548
- "todo",
9549
- "task_boundary",
9550
- // Leitura de ficheiros (read-only)
9551
- "ls_tool",
9552
- "read_file_lines",
9553
- "count_file_lines",
9554
- "view_file_outline",
9555
- "find_by_name",
9556
- "grep_search",
9557
- // Status de comandos (read-only)
9558
- "command_status"
9559
- ];
9560
10568
  function trimRecentActivity(s, max = 72) {
9561
10569
  const t = String(s ?? "").replace(/\s+/g, " ").trim();
9562
10570
  if (!t) return "";
@@ -9579,7 +10587,7 @@ function UserMessageWithOptionalImages({
9579
10587
  caption: cap.length > 0 ? capDisp : null,
9580
10588
  captionDim: true
9581
10589
  }
9582
- ) : /* @__PURE__ */ jsx20(Text19, { dimColor: true, wrap: "wrap", children: fallbackDisp }) });
10590
+ ) : /* @__PURE__ */ jsx20(Text20, { dimColor: true, wrap: "wrap", children: fallbackDisp }) });
9583
10591
  }
9584
10592
  const displayRaw = raw.length > 1e4 ? `${raw.substring(0, 1e4)}...` : raw;
9585
10593
  const paths = collectImagePathStrings(displayRaw);
@@ -9590,7 +10598,7 @@ function UserMessageWithOptionalImages({
9590
10598
  imageCount: paths.length,
9591
10599
  caption: stripped.trim().length > 0 ? stripped : null
9592
10600
  }
9593
- ) : /* @__PURE__ */ jsx20(Text19, { wrap: "wrap", children: displayRaw }) });
10601
+ ) : /* @__PURE__ */ jsx20(Text20, { wrap: "wrap", children: displayRaw }) });
9594
10602
  }
9595
10603
  var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
9596
10604
  const agentInstance = useRef5(null);
@@ -9611,6 +10619,10 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
9611
10619
  );
9612
10620
  const [isInitAgentActive, setIsInitAgentActive] = useState6(false);
9613
10621
  const [recentActivityLine, setRecentActivityLine] = useState6(null);
10622
+ const [taskSummary, setTaskSummary] = useState6({
10623
+ activeTaskSummary: null,
10624
+ taskProgressSummary: null
10625
+ });
9614
10626
  const alwaysAcceptList = useRef5([]);
9615
10627
  const workdir = process.cwd();
9616
10628
  const turnStartedAtRef = useRef5(null);
@@ -9648,11 +10660,11 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
9648
10660
  void checkForUpdates().then((msg) => {
9649
10661
  if (!msg) return;
9650
10662
  setHistory((prev) => {
9651
- const nextId3 = prev.length === 0 ? 1 : Math.max(...prev.map((h) => h.id), HEADER_PANEL_HISTORY_ID) + 1;
10663
+ const nextId2 = prev.length === 0 ? 1 : Math.max(...prev.map((h) => h.id), HEADER_PANEL_HISTORY_ID) + 1;
9652
10664
  return [
9653
10665
  ...prev,
9654
10666
  {
9655
- id: nextId3,
10667
+ id: nextId2,
9656
10668
  component: /* @__PURE__ */ jsx20(UpdateNotice_default, { message: msg })
9657
10669
  }
9658
10670
  ];
@@ -9671,14 +10683,16 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
9671
10683
  sessionId,
9672
10684
  workdir,
9673
10685
  cliVersion,
9674
- recentActivitySummary: recentActivityLine
10686
+ recentActivitySummary: recentActivityLine,
10687
+ activeTaskSummary: taskSummary.activeTaskSummary,
10688
+ taskProgressSummary: taskSummary.taskProgressSummary
9675
10689
  }
9676
10690
  )
9677
10691
  },
9678
10692
  ...tail
9679
10693
  ];
9680
10694
  });
9681
- }, [sessionId, workdir, cliVersion, recentActivityLine]);
10695
+ }, [sessionId, workdir, cliVersion, recentActivityLine, taskSummary]);
9682
10696
  const handleInterrupt = useCallback3(() => {
9683
10697
  if (!isProcessing) return;
9684
10698
  eventBus.emit("user_interrupt");
@@ -9739,7 +10753,7 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
9739
10753
  ...prev,
9740
10754
  {
9741
10755
  id: prev.length,
9742
- component: /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsx20(Text19, { dimColor: true, children: text }) })
10756
+ component: /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsx20(Text20, { dimColor: true, children: text }) })
9743
10757
  },
9744
10758
  {
9745
10759
  id: prev.length + 1,
@@ -9768,7 +10782,7 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
9768
10782
  ...prev,
9769
10783
  {
9770
10784
  id: prev.length,
9771
- component: /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsxs19(Text19, { bold: true, color: "white", children: [
10785
+ component: /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsxs20(Text20, { bold: true, color: "white", children: [
9772
10786
  "$ !",
9773
10787
  command
9774
10788
  ] }) })
@@ -9791,7 +10805,7 @@ Please use command_status to check the result and report back to the user.`;
9791
10805
  ...prev,
9792
10806
  {
9793
10807
  id: prev.length,
9794
- component: /* @__PURE__ */ jsxs19(Text19, { color: "red", children: [
10808
+ component: /* @__PURE__ */ jsxs20(Text20, { color: "red", children: [
9795
10809
  "Failed to execute: ",
9796
10810
  result.error || result.message
9797
10811
  ] })
@@ -9805,7 +10819,7 @@ Please use command_status to check the result and report back to the user.`;
9805
10819
  ...prev,
9806
10820
  {
9807
10821
  id: prev.length,
9808
- component: /* @__PURE__ */ jsxs19(Text19, { color: "red", children: [
10822
+ component: /* @__PURE__ */ jsxs20(Text20, { color: "red", children: [
9809
10823
  "Error: ",
9810
10824
  err.message
9811
10825
  ] })
@@ -9871,6 +10885,16 @@ Please use command_status to check the result and report back to the user.`;
9871
10885
  };
9872
10886
  const handleBackendMessage = (parsed) => {
9873
10887
  try {
10888
+ const updateTaskSummaryFromResult = () => {
10889
+ const activeTask = parsed?.activeTask;
10890
+ const stats = parsed?.stats;
10891
+ if (activeTask || stats) {
10892
+ setTaskSummary({
10893
+ activeTaskSummary: activeTask ? `${String(activeTask.mode || "EXECUTION")} \xB7 ${String(activeTask.taskName || "")}${activeTask.status ? ` \xB7 ${String(activeTask.status)}` : ""}` : null,
10894
+ taskProgressSummary: stats && typeof stats.total === "number" ? `${stats.completed ?? 0}/${stats.total ?? 0} done \xB7 ${stats.pending ?? 0} pending \xB7 ${stats.inProgress ?? 0} in progress \xB7 ${stats.progress ?? 0}%` : null
10895
+ });
10896
+ }
10897
+ };
9874
10898
  const appendTurnDurationIfAny = () => {
9875
10899
  const t = turnStartedAtRef.current;
9876
10900
  if (t == null) return;
@@ -9893,7 +10917,8 @@ Please use command_status to check the result and report back to the user.`;
9893
10917
  }
9894
10918
  if (parsed.type === "confirmation_request") {
9895
10919
  const toolToConfirm = parsed.tool_calls[0].function.name;
9896
- if (SAFE_AUTO_APPROVE_TOOLS.includes(toolToConfirm)) {
10920
+ const policyAutoApprove = parsed.tool_policy?.autoApprove === true;
10921
+ if (policyAutoApprove) {
9897
10922
  handleConfirmation("accept", parsed.tool_calls);
9898
10923
  return;
9899
10924
  }
@@ -9929,9 +10954,9 @@ Please use command_status to check the result and report back to the user.`;
9929
10954
  if (parsed.type === "debug") {
9930
10955
  newComponent = /* @__PURE__ */ jsx20(ChatMeta, { children: parsed.message });
9931
10956
  } else if (parsed.type === "protocol_violation") {
9932
- newComponent = /* @__PURE__ */ jsx20(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", paddingLeft: 2, children: [
9933
- /* @__PURE__ */ jsx20(Text19, { dimColor: true, children: parsed.content }),
9934
- /* @__PURE__ */ jsx20(Text19, { dimColor: true, children: parsed.message })
10957
+ newComponent = /* @__PURE__ */ jsx20(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", paddingLeft: 2, children: [
10958
+ /* @__PURE__ */ jsx20(Text20, { dimColor: true, children: parsed.content }),
10959
+ /* @__PURE__ */ jsx20(Text20, { dimColor: true, children: parsed.message })
9935
10960
  ] }) });
9936
10961
  } else if (parsed.type === "error") {
9937
10962
  newComponent = /* @__PURE__ */ jsx20(
@@ -9951,10 +10976,12 @@ Please use command_status to check the result and report back to the user.`;
9951
10976
  {
9952
10977
  toolName: parsed.tool_name,
9953
10978
  args: parsed.arguments,
9954
- preview: parsed.preview
10979
+ preview: parsed.preview,
10980
+ toolPolicy: parsed.tool_policy
9955
10981
  }
9956
10982
  );
9957
10983
  } else if (parsed.type === "tool_result") {
10984
+ updateTaskSummaryFromResult();
9958
10985
  newComponent = /* @__PURE__ */ jsx20(
9959
10986
  ToolResultDisplay,
9960
10987
  {
@@ -9968,11 +10995,11 @@ Please use command_status to check the result and report back to the user.`;
9968
10995
  `Context: ${trimRecentActivity(String(parsed.payload))}`
9969
10996
  );
9970
10997
  }
9971
- newComponent = /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsx20(Text19, { dimColor: true, children: parsed.payload }) });
10998
+ newComponent = /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsx20(Text20, { dimColor: true, children: parsed.payload }) });
9972
10999
  } else if (parsed.type === "reasoning") {
9973
11000
  newComponent = /* @__PURE__ */ jsx20(ReasoningDisplay, { reasoning: parsed.content });
9974
11001
  } else if (parsed.type === "log") {
9975
- newComponent = /* @__PURE__ */ jsxs19(ChatMeta, { children: [
11002
+ newComponent = /* @__PURE__ */ jsxs20(ChatMeta, { children: [
9976
11003
  parsed.message,
9977
11004
  parsed.payload ? `: ${parsed.payload}` : ""
9978
11005
  ] });
@@ -10036,7 +11063,7 @@ Please use command_status to check the result and report back to the user.`;
10036
11063
  }
10037
11064
  );
10038
11065
  }
10039
- return /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", children: [
11066
+ return /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", children: [
10040
11067
  isProcessing && !pendingConfirmation && /* @__PURE__ */ jsx20(WorkingTimer, { eventBus }),
10041
11068
  /* @__PURE__ */ jsx20(
10042
11069
  InputPrompt,
@@ -10049,7 +11076,7 @@ Please use command_status to check the result and report back to the user.`;
10049
11076
  )
10050
11077
  ] });
10051
11078
  };
10052
- return /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", children: [
11079
+ return /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", children: [
10053
11080
  /* @__PURE__ */ jsx20(Static, { items: history, children: (item) => /* @__PURE__ */ jsx20(Box20, { children: item.component }, item.id) }),
10054
11081
  /* @__PURE__ */ jsx20(
10055
11082
  StreamingText,
@@ -10148,20 +11175,90 @@ function writeJsonl(event) {
10148
11175
  } catch {
10149
11176
  }
10150
11177
  }
11178
+ function writeAgentEvent(sessionId, event) {
11179
+ writeJsonl(event);
11180
+ if (sessionId) {
11181
+ appendSessionLog(sessionId, event);
11182
+ }
11183
+ }
11184
+ function finalizeSession(sessionId, status, metadata) {
11185
+ updateSession(sessionId, {
11186
+ status,
11187
+ pid: process.pid,
11188
+ metadata: metadata ? { ...getSession(sessionId)?.metadata, ...metadata } : getSession(sessionId)?.metadata
11189
+ });
11190
+ }
11191
+ function installSessionLifecycle(sessionId) {
11192
+ let finalized = false;
11193
+ const safeFinalize = (status, metadata) => {
11194
+ if (finalized) return;
11195
+ finalized = true;
11196
+ finalizeSession(sessionId, status, metadata);
11197
+ };
11198
+ process.once("exit", (code) => {
11199
+ safeFinalize(code === 0 ? "completed" : "error", { exitCode: code ?? 0 });
11200
+ });
11201
+ process.once("SIGINT", () => {
11202
+ safeFinalize("cancelled", { signal: "SIGINT" });
11203
+ process.exit(130);
11204
+ });
11205
+ process.once("SIGTERM", () => {
11206
+ safeFinalize("cancelled", { signal: "SIGTERM" });
11207
+ process.exit(143);
11208
+ });
11209
+ process.once("uncaughtException", (error) => {
11210
+ safeFinalize("error", { uncaughtException: error instanceof Error ? error.message : String(error) });
11211
+ throw error;
11212
+ });
11213
+ }
11214
+ function formatSessionLine(session) {
11215
+ return `${session.sessionId} ${session.kind} ${session.status} ${session.updatedAt} ${session.title}`;
11216
+ }
11217
+ function formatLogLine(line) {
11218
+ try {
11219
+ const parsed = JSON.parse(line);
11220
+ const ts = parsed.timestamp || (/* @__PURE__ */ new Date()).toISOString();
11221
+ if (parsed.event_type === "log") {
11222
+ return `[${ts}] ${parsed.level.toUpperCase()} ${parsed.message}`;
11223
+ }
11224
+ if (parsed.event_type === "backend_message") {
11225
+ return `[${ts}] EVENT ${parsed.backend_type}`;
11226
+ }
11227
+ if (parsed.event_type === "action_status") {
11228
+ return `[${ts}] STATUS ${JSON.stringify(parsed.payload)}`;
11229
+ }
11230
+ if (parsed.event_type === "result") {
11231
+ return `[${ts}] RESULT ${parsed.status}`;
11232
+ }
11233
+ } catch {
11234
+ }
11235
+ return line;
11236
+ }
11237
+ function isProcessAlive(pid) {
11238
+ if (!pid || pid <= 0) return false;
11239
+ try {
11240
+ process.kill(pid, 0);
11241
+ return true;
11242
+ } catch {
11243
+ return false;
11244
+ }
11245
+ }
10151
11246
  async function runAgentMode() {
10152
11247
  const args = process.argv.slice(2);
10153
11248
  const inputFileIndex = args.indexOf("--input-file");
10154
- const inputIndex = args.indexOf("--input");
11249
+ const backgroundWorker = args.includes("--background-worker");
11250
+ const registrySessionIndex = args.indexOf("--registry-session");
11251
+ const registrySessionId = registrySessionIndex !== -1 && args[registrySessionIndex + 1] ? args[registrySessionIndex + 1] : null;
10155
11252
  let rawPayload = "";
10156
11253
  try {
10157
11254
  if (inputFileIndex !== -1 && args[inputFileIndex + 1]) {
10158
11255
  const filePath = args[inputFileIndex + 1];
10159
- rawPayload = fs18.readFileSync(filePath, "utf-8");
11256
+ rawPayload = fs21.readFileSync(filePath, "utf-8");
10160
11257
  } else {
10161
- rawPayload = fs18.readFileSync(0, "utf-8");
11258
+ rawPayload = fs21.readFileSync(0, "utf-8");
10162
11259
  }
10163
11260
  } catch (err) {
10164
- writeJsonl({
11261
+ writeAgentEvent(registrySessionId, {
10165
11262
  event_type: "result",
10166
11263
  status: "error",
10167
11264
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -10177,7 +11274,7 @@ async function runAgentMode() {
10177
11274
  try {
10178
11275
  envelope = JSON.parse(rawPayload);
10179
11276
  } catch (err) {
10180
- writeJsonl({
11277
+ writeAgentEvent(registrySessionId, {
10181
11278
  event_type: "result",
10182
11279
  status: "error",
10183
11280
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -10196,7 +11293,9 @@ async function runAgentMode() {
10196
11293
  }
10197
11294
  }
10198
11295
  const eventBus = new EventEmitter3();
10199
- const sessionId = envelope.session_id || envelope.message_id || uuidv46();
11296
+ const sessionId = registrySessionId || envelope.session_id || envelope.message_id || uuidv47();
11297
+ process.env.BLUMA_SESSION_ID = sessionId;
11298
+ installSessionLifecycle(sessionId);
10200
11299
  const uc = envelope.user_context;
10201
11300
  const userContextInput = {
10202
11301
  sessionId,
@@ -10208,13 +11307,27 @@ async function runAgentMode() {
10208
11307
  companyId: uc?.companyId ?? null,
10209
11308
  companyName: uc?.companyName ?? null
10210
11309
  };
11310
+ registerSession({
11311
+ sessionId,
11312
+ kind: "agent",
11313
+ status: "running",
11314
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
11315
+ workdir: process.cwd(),
11316
+ title: backgroundWorker ? `background:${envelope.action || "unknown"}` : `agent:${envelope.action || "unknown"}`,
11317
+ pid: process.pid,
11318
+ metadata: {
11319
+ message_id: envelope.message_id || null,
11320
+ action: envelope.action || null,
11321
+ background: backgroundWorker
11322
+ }
11323
+ });
10211
11324
  let lastAssistantMessage = null;
10212
11325
  let reasoningBuffer = null;
10213
11326
  let lastAttachments = null;
10214
11327
  let resultEmitted = false;
10215
11328
  eventBus.on("backend_message", (payload) => {
10216
11329
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
10217
- writeJsonl({
11330
+ writeAgentEvent(sessionId, {
10218
11331
  event_type: "backend_message",
10219
11332
  backend_type: String(payload?.type || "unknown"),
10220
11333
  timestamp,
@@ -10245,7 +11358,8 @@ async function runAgentMode() {
10245
11358
  }
10246
11359
  if (!resultEmitted && payload?.type === "done") {
10247
11360
  resultEmitted = true;
10248
- writeJsonl({
11361
+ finalizeSession(sessionId, "completed", { finishedBy: "done-event" });
11362
+ writeAgentEvent(sessionId, {
10249
11363
  event_type: "result",
10250
11364
  status: "success",
10251
11365
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -10261,7 +11375,7 @@ async function runAgentMode() {
10261
11375
  }
10262
11376
  });
10263
11377
  eventBus.on("action_status", (payload) => {
10264
- writeJsonl({
11378
+ writeAgentEvent(sessionId, {
10265
11379
  event_type: "action_status",
10266
11380
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10267
11381
  payload
@@ -10270,7 +11384,7 @@ async function runAgentMode() {
10270
11384
  eventBus.on("stream_reasoning_chunk", (payload) => {
10271
11385
  reasoningBuffer = (reasoningBuffer || "") + (payload?.delta || "");
10272
11386
  });
10273
- writeJsonl({
11387
+ writeAgentEvent(sessionId, {
10274
11388
  event_type: "log",
10275
11389
  level: "info",
10276
11390
  message: "Starting agent mode execution",
@@ -10295,7 +11409,8 @@ async function runAgentMode() {
10295
11409
  await agent.processTurn({ content: userContent }, userContextInput);
10296
11410
  if (!resultEmitted) {
10297
11411
  resultEmitted = true;
10298
- writeJsonl({
11412
+ finalizeSession(sessionId, "completed", { finishedBy: "post-turn-fallback" });
11413
+ writeAgentEvent(sessionId, {
10299
11414
  event_type: "result",
10300
11415
  status: "success",
10301
11416
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -10311,7 +11426,8 @@ async function runAgentMode() {
10311
11426
  }
10312
11427
  } catch (err) {
10313
11428
  if (!resultEmitted) {
10314
- writeJsonl({
11429
+ finalizeSession(sessionId, "error", { finishedBy: "exception" });
11430
+ writeAgentEvent(sessionId, {
10315
11431
  event_type: "result",
10316
11432
  status: "error",
10317
11433
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -10326,29 +11442,177 @@ async function runAgentMode() {
10326
11442
  }
10327
11443
  function readCliPackageVersion() {
10328
11444
  try {
10329
- const base = path22.dirname(fileURLToPath5(import.meta.url));
10330
- const pkgPath = path22.join(base, "..", "package.json");
10331
- const j = JSON.parse(fs18.readFileSync(pkgPath, "utf8"));
11445
+ const base = path26.dirname(fileURLToPath5(import.meta.url));
11446
+ const pkgPath = path26.join(base, "..", "package.json");
11447
+ const j = JSON.parse(fs21.readFileSync(pkgPath, "utf8"));
10332
11448
  return String(j.version || "0.0.0");
10333
11449
  } catch {
10334
11450
  return "0.0.0";
10335
11451
  }
10336
11452
  }
10337
- function runCliMode() {
11453
+ function runCliMode(sessionId) {
10338
11454
  const BLUMA_TITLE = process.env.BLUMA_TITLE || "BluMa - NomadEngenuity";
10339
11455
  startTitleKeeper(BLUMA_TITLE);
10340
11456
  const eventBus = new EventEmitter3();
10341
- const sessionId = uuidv46();
11457
+ const resolvedSessionId = sessionId || uuidv47();
11458
+ process.env.BLUMA_SESSION_ID = resolvedSessionId;
11459
+ registerSession({
11460
+ sessionId: resolvedSessionId,
11461
+ kind: "interactive",
11462
+ status: "running",
11463
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
11464
+ workdir: process.cwd(),
11465
+ title: "interactive-cli",
11466
+ pid: process.pid,
11467
+ metadata: {}
11468
+ });
11469
+ installSessionLifecycle(resolvedSessionId);
10342
11470
  const props = {
10343
11471
  eventBus,
10344
- sessionId,
11472
+ sessionId: resolvedSessionId,
10345
11473
  cliVersion: readCliPackageVersion()
10346
11474
  };
10347
- render(React12.createElement(App_default, props));
11475
+ const instance = render(React12.createElement(App_default, props));
11476
+ void instance.waitUntilExit().then(() => {
11477
+ finalizeSession(resolvedSessionId, "completed", { finishedBy: "interactive-exit" });
11478
+ });
11479
+ }
11480
+ function printSessions() {
11481
+ const sessions = listSessions();
11482
+ if (sessions.length === 0) {
11483
+ console.log("No sessions registered.");
11484
+ return;
11485
+ }
11486
+ for (const session of sessions) {
11487
+ console.log(formatSessionLine(session));
11488
+ }
11489
+ }
11490
+ function printSessionStatus(sessionId) {
11491
+ const session = getSession(sessionId);
11492
+ if (!session) {
11493
+ console.error(`Unknown session: ${sessionId}`);
11494
+ process.exit(1);
11495
+ return;
11496
+ }
11497
+ const alive = session.status === "running" ? isProcessAlive(session.pid) : false;
11498
+ console.log(JSON.stringify({
11499
+ ...session,
11500
+ alive
11501
+ }, null, 2));
11502
+ }
11503
+ function printLogs(sessionId) {
11504
+ const session = getSession(sessionId);
11505
+ if (!session) {
11506
+ console.error(`Unknown session: ${sessionId}`);
11507
+ process.exit(1);
11508
+ }
11509
+ const lines = readSessionLog(sessionId);
11510
+ for (const line of lines) {
11511
+ console.log(formatLogLine(line));
11512
+ }
11513
+ }
11514
+ async function attachToSession(sessionId) {
11515
+ const session = getSession(sessionId);
11516
+ if (!session) {
11517
+ console.error(`Unknown session: ${sessionId}`);
11518
+ process.exit(1);
11519
+ return;
11520
+ }
11521
+ let cursor = 0;
11522
+ while (true) {
11523
+ const lines = readSessionLog(sessionId);
11524
+ const next = lines.slice(cursor);
11525
+ cursor = lines.length;
11526
+ for (const line of next) {
11527
+ console.log(formatLogLine(line));
11528
+ }
11529
+ const latest2 = getSession(sessionId);
11530
+ if (!latest2 || latest2.status !== "running") {
11531
+ return;
11532
+ }
11533
+ await new Promise((resolve2) => setTimeout(resolve2, 1e3));
11534
+ }
11535
+ }
11536
+ function killSession(sessionId) {
11537
+ const session = getSession(sessionId);
11538
+ if (!session) {
11539
+ console.error(`Unknown session: ${sessionId}`);
11540
+ process.exit(1);
11541
+ return;
11542
+ }
11543
+ if (session.status !== "running") {
11544
+ console.log(`Session ${sessionId} is already ${session.status}.`);
11545
+ return;
11546
+ }
11547
+ if (!session.pid || !isProcessAlive(session.pid)) {
11548
+ updateSession(sessionId, {
11549
+ status: "cancelled",
11550
+ metadata: { ...session.metadata, signal: "stale-process" }
11551
+ });
11552
+ console.log(`Session ${sessionId} was marked as cancelled because its process is not running.`);
11553
+ return;
11554
+ }
11555
+ process.kill(session.pid, "SIGTERM");
11556
+ updateSession(sessionId, {
11557
+ status: "cancelled",
11558
+ metadata: { ...session.metadata, signal: "SIGTERM" }
11559
+ });
11560
+ console.log(`Sent SIGTERM to session ${sessionId} (${session.pid}).`);
11561
+ }
11562
+ function startBackgroundAgent() {
11563
+ const args = process.argv.slice(2);
11564
+ const inputFileIndex = args.indexOf("--input-file");
11565
+ if (inputFileIndex === -1 || !args[inputFileIndex + 1]) {
11566
+ console.error("Background agent mode requires --input-file <path>.");
11567
+ process.exit(1);
11568
+ }
11569
+ const filePath = args[inputFileIndex + 1];
11570
+ const rawPayload = fs21.readFileSync(filePath, "utf-8");
11571
+ const envelope = JSON.parse(rawPayload);
11572
+ const sessionId = envelope.session_id || envelope.message_id || uuidv47();
11573
+ registerSession({
11574
+ sessionId,
11575
+ kind: "agent",
11576
+ status: "running",
11577
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
11578
+ workdir: process.cwd(),
11579
+ title: `background:${envelope.action || "unknown"}`,
11580
+ metadata: {
11581
+ action: envelope.action || null,
11582
+ message_id: envelope.message_id || null,
11583
+ background: true
11584
+ }
11585
+ });
11586
+ const childArgs = [process.argv[1], "agent", "--input-file", filePath, "--background-worker", "--registry-session", sessionId];
11587
+ const child = spawn4(process.execPath, childArgs, {
11588
+ detached: true,
11589
+ stdio: "ignore",
11590
+ cwd: process.cwd(),
11591
+ env: process.env
11592
+ });
11593
+ child.unref();
11594
+ updateSession(sessionId, { pid: child.pid });
11595
+ console.log(sessionId);
10348
11596
  }
10349
11597
  var argv = process.argv.slice(2);
10350
- if (argv[0] === "agent") {
11598
+ if (argv[0] === "agent" && argv.includes("--background")) {
11599
+ startBackgroundAgent();
11600
+ } else if (argv[0] === "agent") {
10351
11601
  runAgentMode();
11602
+ } else if (argv[0] === "sessions") {
11603
+ printSessions();
11604
+ } else if (argv[0] === "logs" && argv[1]) {
11605
+ printLogs(argv[1]);
11606
+ } else if (argv[0] === "status" && argv[1]) {
11607
+ printSessionStatus(argv[1]);
11608
+ } else if (argv[0] === "attach" && argv[1]) {
11609
+ attachToSession(argv[1]);
11610
+ } else if (argv[0] === "follow" && argv[1]) {
11611
+ attachToSession(argv[1]);
11612
+ } else if (argv[0] === "kill" && argv[1]) {
11613
+ killSession(argv[1]);
11614
+ } else if (argv[0] === "resume" && argv[1]) {
11615
+ runCliMode(argv[1]);
10352
11616
  } else {
10353
11617
  runCliMode();
10354
11618
  }