@bike4mind/cli 0.2.12-refactor-github-mcp.17405 → 0.2.13

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.
Files changed (26) hide show
  1. package/dist/{ImageStore-RQZ7OF7P.js → ImageStore-MMUOUPI2.js} +0 -1
  2. package/dist/{artifactExtractor-F2VLARCK.js → artifactExtractor-BRHUIR7I.js} +1 -2
  3. package/dist/{chunk-ZHCX7BLB.js → chunk-3MT5HI3G.js} +2 -2
  4. package/dist/{chunk-OHR7UCTC.js → chunk-A7POWM75.js} +13 -0
  5. package/dist/chunk-BDQBOLYG.js +0 -0
  6. package/dist/chunk-BPFEGDC7.js +0 -0
  7. package/dist/{chunk-RJFNIMVH.js → chunk-K44CDTQN.js} +2 -2
  8. package/dist/{chunk-AMDXHL6S.js → chunk-OCYRD7D6.js} +1 -1
  9. package/dist/{chunk-ID4NS6KF.js → chunk-PMY7IPOC.js} +18 -2
  10. package/dist/{chunk-BU5TP3BH.js → chunk-SZXO7HOF.js} +19 -2
  11. package/dist/create-X2GOA457.js +12 -0
  12. package/dist/{formatConverter-I7EIUVDY.js → formatConverter-5QEJDW24.js} +0 -1
  13. package/dist/index.js +436 -280
  14. package/dist/{llmMarkdownGenerator-AVPOXVE5.js → llmMarkdownGenerator-AHTZFV2T.js} +1 -2
  15. package/dist/{markdownGenerator-FBXMTP3B.js → markdownGenerator-BLXDOZWQ.js} +1 -2
  16. package/dist/mementoService-ZAFUWL4U.js +12 -0
  17. package/dist/{notificationDeduplicator-UW7D75QS.js → notificationDeduplicator-UTHJHMSZ.js} +1 -2
  18. package/dist/{src-SYE7WXBJ.js → src-SIU2KHTA.js} +3 -2
  19. package/dist/{src-YU4VN5YG.js → src-STUEUNUB.js} +3 -4
  20. package/dist/subtractCredits-KRFWXMZH.js +12 -0
  21. package/dist/{utils-JPMDGUBL.js → utils-JCHWDM4Z.js} +0 -1
  22. package/package.json +19 -19
  23. package/dist/chunk-PDX44BCA.js +0 -11
  24. package/dist/create-MXBWYABO.js +0 -13
  25. package/dist/mementoService-4WLVEWPZ.js +0 -13
  26. package/dist/subtractCredits-FGISXF2D.js +0 -13
package/dist/index.js CHANGED
@@ -4,9 +4,9 @@ import {
4
4
  getEffectiveApiKey,
5
5
  getOpenWeatherKey,
6
6
  getSerperKey
7
- } from "./chunk-ZHCX7BLB.js";
8
- import "./chunk-ID4NS6KF.js";
9
- import "./chunk-RJFNIMVH.js";
7
+ } from "./chunk-3MT5HI3G.js";
8
+ import "./chunk-PMY7IPOC.js";
9
+ import "./chunk-K44CDTQN.js";
10
10
  import {
11
11
  BFLImageService,
12
12
  BaseStorage,
@@ -15,10 +15,10 @@ import {
15
15
  OpenAIBackend,
16
16
  OpenAIImageService,
17
17
  XAIImageService
18
- } from "./chunk-BU5TP3BH.js";
18
+ } from "./chunk-SZXO7HOF.js";
19
19
  import {
20
20
  Logger
21
- } from "./chunk-AMDXHL6S.js";
21
+ } from "./chunk-OCYRD7D6.js";
22
22
  import "./chunk-BPFEGDC7.js";
23
23
  import {
24
24
  AiEvents,
@@ -73,10 +73,7 @@ import {
73
73
  XAI_IMAGE_MODELS,
74
74
  b4mLLMTools,
75
75
  getMcpProviderMetadata
76
- } from "./chunk-OHR7UCTC.js";
77
- import {
78
- __require
79
- } from "./chunk-PDX44BCA.js";
76
+ } from "./chunk-A7POWM75.js";
80
77
 
81
78
  // src/index.tsx
82
79
  import React17, { useState as useState8, useEffect as useEffect3, useCallback, useRef as useRef2 } from "react";
@@ -869,10 +866,11 @@ function InputPrompt({
869
866
  if (shouldShowCommandAutocomplete && filteredCommands.length > 0) {
870
867
  if (key.upArrow) {
871
868
  setSelectedIndex((prev) => prev > 0 ? prev - 1 : filteredCommands.length - 1);
869
+ return;
872
870
  } else if (key.downArrow) {
873
871
  setSelectedIndex((prev) => prev < filteredCommands.length - 1 ? prev + 1 : 0);
872
+ return;
874
873
  }
875
- return;
876
874
  }
877
875
  if (!shouldShowCommandAutocomplete && !fileAutocomplete?.active && history.length > 0) {
878
876
  if (key.upArrow) {
@@ -1351,7 +1349,7 @@ function MessageItem({ message }) {
1351
1349
  const isUser = message.role === "user";
1352
1350
  return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: isUser ? "cyan" : "green" }, isUser ? "\u{1F464} You" : "\u{1F916} Assistant"), isUser && /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2022 ", new Date(message.timestamp).toLocaleTimeString())), isUser && message.content && /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2 }, /* @__PURE__ */ React10.createElement(Text9, { backgroundColor: "whiteBright", color: "black" }, " ", message.content, " ")), !isUser && message.metadata?.steps && message.metadata.steps.length > 0 && /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true, bold: true }, "\u{1F527} Agent Reasoning Trace (", message.metadata.steps.filter((s) => s.type === "action").length, " ", "tools used, ", message.metadata.steps.length, " total steps)", message.metadata.tokenUsage && ` \u2022 ${message.metadata.tokenUsage.total} tokens`), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Step types: ", message.metadata.steps.map((s) => s.type).join(", ")), message.metadata.steps.map((step, idx) => {
1353
1351
  if (step.type === "thought") {
1354
- return /* @__PURE__ */ React10.createElement(Box9, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { color: "blue" }, "\u{1F4AD} Thought:"), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, ` ${step.content.slice(0, 200)}${step.content.length > 200 ? "..." : ""}`));
1352
+ return /* @__PURE__ */ React10.createElement(Box9, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { color: "blue" }, "\u{1F4AD} Thought:"), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, ` ${step.content}`));
1355
1353
  }
1356
1354
  if (step.type === "action") {
1357
1355
  const toolName = step.metadata?.toolName || "unknown";
@@ -3140,7 +3138,8 @@ ${options.context}` : this.getSystemPrompt()
3140
3138
  tools: this.context.tools,
3141
3139
  maxTokens,
3142
3140
  temperature,
3143
- abortSignal: options.signal
3141
+ abortSignal: options.signal,
3142
+ tool_choice: this.context.toolChoice
3144
3143
  },
3145
3144
  async (texts, completionInfo) => {
3146
3145
  for (const text of texts) {
@@ -3394,6 +3393,15 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
3394
3393
  };
3395
3394
 
3396
3395
  // src/core/prompts.ts
3396
+ var TOOL_GREP_SEARCH = "grep_search";
3397
+ var TOOL_GLOB_FILES = "glob_files";
3398
+ var TOOL_FILE_READ = "file_read";
3399
+ var TOOL_EDIT_LOCAL_FILE = "edit_local_file";
3400
+ var TOOL_CREATE_FILE = "create_file";
3401
+ var TOOL_BASH_EXECUTE = "bash_execute";
3402
+ var TOOL_SUBAGENT_DELEGATE = "subagent_delegate";
3403
+ var TOOL_WRITE_TODOS = "write_todos";
3404
+ var EXPLORE_SUBAGENT_TYPE = "explore";
3397
3405
  function buildCoreSystemPrompt(contextSection = "") {
3398
3406
  return `You are an autonomous AI assistant with access to tools. Your job is to help users by taking action and solving problems proactively.
3399
3407
 
@@ -3405,15 +3413,17 @@ CORE BEHAVIOR:
3405
3413
 
3406
3414
  FOR SOFTWARE ENGINEERING TASKS:
3407
3415
  When requested to perform tasks like fixing bugs, adding features, refactoring, or explaining code, follow this sequence:
3408
- 1. **Understand:** Think about the user's request and the relevant codebase context. Use 'grep_search' and 'glob_files' search tools extensively (in parallel if independent) to understand file structures, existing code patterns, and conventions. Use 'file_read' to understand context and validate any assumptions you may have. If you need to read multiple files, you should make multiple parallel calls to 'file_read'.
3416
+ 1. **Understand:** Think about the user's request and the relevant codebase context. Use '${TOOL_GREP_SEARCH}' and '${TOOL_GLOB_FILES}' search tools extensively (in parallel if independent) to understand file structures, existing code patterns, and conventions. Use '${TOOL_FILE_READ}' to understand context and validate any assumptions you may have. If you need to read multiple files, you should make multiple parallel calls to '${TOOL_FILE_READ}'.
3417
+ When the task involves **complex refactoring, codebase exploration or system-wide analysis**, your **first and primary action** must be to delegate to the '${EXPLORE_SUBAGENT_TYPE}' agent using the '${TOOL_SUBAGENT_DELEGATE}' tool. Use it to build a comprehensive understanding of the code, its structure, and dependencies. For **simple, targeted searches** (like finding a specific function name, file path, or variable declaration), you should use '${TOOL_GREP_SEARCH}' or '${TOOL_GLOB_FILES}' directly.
3409
3418
  2. **Plan:** Build a coherent and grounded (based on the understanding in step 1) plan for how you intend to resolve the user's task. Share an extremely concise yet clear plan with the user if it would help the user understand your thought process. As part of the plan, you should use an iterative development process that includes writing unit tests to verify your changes. Use output logs or debug statements as part of this process to arrive at a solution.
3410
- 3. **Implement:** Use the available tools (e.g., 'edit_local_file', 'create_file', 'bash_execute' ...) to act on the plan, strictly adhering to the project's established conventions.
3419
+ If '${EXPLORE_SUBAGENT_TYPE}' subagent was used, do not ignore the output of the agent, you must use it as the foundation of your plan. For complex tasks, break them down into smaller, manageable subtasks and use the \`${TOOL_WRITE_TODOS}\` tool to track your progress. Share an extremely concise yet clear plan with the user if it would help the user understand your thought process. As part of the plan, you should use an iterative development process that includes writing unit tests to verify your changes. Use output logs or debug statements as part of this process to arrive at a solution.
3420
+ 3. **Implement:** Use the available tools (e.g., '${TOOL_EDIT_LOCAL_FILE}', '${TOOL_CREATE_FILE}', '${TOOL_BASH_EXECUTE}' ...) to act on the plan, strictly adhering to the project's established conventions.
3411
3421
  4. **Verify (Tests):** If applicable and feasible, verify the changes using the project's testing procedures. Identify the correct test commands and frameworks by examining 'README' files, build/package configuration (e.g., 'package.json'), or existing test execution patterns. NEVER assume standard test commands. When executing test commands, prefer "run once" or "CI" modes to ensure the command terminates after completion.
3412
3422
  5. **Verify (Standards):** VERY IMPORTANT: After making code changes, execute the project-specific build, linting and type-checking commands (e.g., 'tsc', 'npm run lint', 'ruff check .') that you have identified for this project (or obtained from the user). This ensures code quality and adherence to standards.
3413
3423
  6. **Finalize:** After all verification passes, consider the task complete. Do not remove or revert any changes or created files (like tests). Await the user's next instruction.
3414
3424
 
3415
3425
  SUBAGENT DELEGATION:
3416
- - You have access to specialized subagents via the subagent_delegate tool
3426
+ - You have access to specialized subagents via the ${TOOL_SUBAGENT_DELEGATE} tool
3417
3427
  - Use subagents for focused exploration, planning, or review tasks:
3418
3428
  * explore: Fast read-only codebase search (e.g., "find all auth files", "locate API endpoints")
3419
3429
  * plan: Break down complex tasks into actionable steps
@@ -3429,7 +3439,7 @@ FOR GENERAL TASKS:
3429
3439
  ## Shell tool output token efficiency:
3430
3440
  IT IS CRITICAL TO FOLLOW THESE GUIDELINES TO AVOID EXCESSIVE TOKEN CONSUMPTION.
3431
3441
 
3432
- - Always prefer command flags that reduce output verbosity when using 'bash_execute'.
3442
+ - Always prefer command flags that reduce output verbosity when using '${TOOL_BASH_EXECUTE}'.
3433
3443
  - Aim to minimize tool output tokens while still capturing necessary information.
3434
3444
  - If a command is expected to produce a lot of output, use quiet or silent flags where available and appropriate.
3435
3445
  - Always consider the trade-off between output verbosity and the need for information. If a command's full output is essential for understanding the result, avoid overly aggressive quieting that might obscure important details.
@@ -3439,9 +3449,9 @@ IT IS CRITICAL TO FOLLOW THESE GUIDELINES TO AVOID EXCESSIVE TOKEN CONSUMPTION.
3439
3449
  EXAMPLES:
3440
3450
  - "what should I wear in Texas?" \u2192 use weather tool for Texas
3441
3451
  - "how about Japan?" \u2192 use weather tool for Japan (applying same question from context)
3442
- - "enhance README" \u2192 file_read \u2192 generate \u2192 create_file
3443
- - "what packages installed?" \u2192 glob_files "**/package.json" \u2192 file_read
3444
- - "find all components using React hooks" \u2192 subagent_delegate(task="find all components using React hooks", type="explore")
3452
+ - "enhance README" \u2192 ${TOOL_FILE_READ} \u2192 generate \u2192 ${TOOL_EDIT_LOCAL_FILE}
3453
+ - "what packages installed?" \u2192 ${TOOL_GLOB_FILES} "**/package.json" \u2192 ${TOOL_FILE_READ}
3454
+ - "find all components using React hooks" \u2192 ${TOOL_SUBAGENT_DELEGATE}(task="find all components using React hooks", type="explore")
3445
3455
 
3446
3456
  Remember: Use context from previous messages to understand follow-up questions.${contextSection}`;
3447
3457
  }
@@ -8797,98 +8807,80 @@ var createFileTool = {
8797
8807
  };
8798
8808
 
8799
8809
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/globFiles/index.js
8800
- import { readdir } from "fs/promises";
8801
- import { statSync as statSync5 } from "fs";
8810
+ import { glob } from "glob";
8811
+ import { stat } from "fs/promises";
8802
8812
  import path9 from "path";
8803
- function matchGlob(filePath, pattern) {
8804
- let regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, ".").replace(/\{([^}]+)\}/g, (_, options) => `(${options.split(",").join("|")})`).replace(/\[([^\]]+)\]/g, "[$1]");
8805
- regexPattern = regexPattern.replace(/\{\{GLOBSTAR\}\}\//g, "(.*/)?").replace(/\/\{\{GLOBSTAR\}\}/g, "(/.*)?").replace(/\{\{GLOBSTAR\}\}/g, ".*");
8806
- const regex = new RegExp(`^${regexPattern}$`);
8807
- return regex.test(filePath);
8808
- }
8809
- async function walkDir(dir, pattern, includeHidden, maxResults, basePath = "") {
8810
- const matches = [];
8811
- try {
8812
- const entries = await readdir(dir, { withFileTypes: true });
8813
- for (const entry of entries) {
8814
- if (matches.length >= maxResults) {
8815
- break;
8816
- }
8817
- if (!includeHidden && entry.name.startsWith(".")) {
8818
- continue;
8819
- }
8820
- const relativePath = basePath ? path9.join(basePath, entry.name) : entry.name;
8821
- const fullPath = path9.join(dir, entry.name);
8822
- if (matchGlob(relativePath, pattern)) {
8823
- matches.push(relativePath);
8824
- }
8825
- if (entry.isDirectory() && (pattern.includes("**") || pattern.includes("/"))) {
8826
- const subMatches = await walkDir(fullPath, pattern, includeHidden, maxResults - matches.length, relativePath);
8827
- matches.push(...subMatches);
8828
- }
8829
- }
8830
- } catch (error) {
8831
- }
8832
- return matches;
8833
- }
8834
- async function listFiles(params) {
8835
- const { pattern, cwd: relativeCwd, includeHidden = false, maxResults = 1e3 } = params;
8813
+ var DEFAULT_IGNORE_PATTERNS2 = [
8814
+ "**/node_modules/**",
8815
+ "**/.git/**",
8816
+ "**/dist/**",
8817
+ "**/build/**",
8818
+ "**/.next/**",
8819
+ "**/coverage/**",
8820
+ "**/.turbo/**",
8821
+ "**/.sst/**",
8822
+ "**/*.min.js",
8823
+ "**/*.min.css"
8824
+ ];
8825
+ async function findFiles(params) {
8826
+ const { pattern, dir_path, case_sensitive = true, respect_git_ignore = true } = params;
8836
8827
  const baseCwd = process.cwd();
8837
- const targetCwd = relativeCwd ? path9.resolve(baseCwd, path9.normalize(relativeCwd)) : baseCwd;
8838
- if (!targetCwd.startsWith(baseCwd)) {
8839
- throw new Error(`Access denied: Cannot list files outside of current working directory`);
8840
- }
8841
- const matches = await walkDir(targetCwd, pattern, includeHidden, maxResults);
8828
+ const targetDir = dir_path ? path9.resolve(baseCwd, path9.normalize(dir_path)) : baseCwd;
8829
+ if (!targetDir.startsWith(baseCwd)) {
8830
+ throw new Error(`Access denied: Cannot search outside of current working directory`);
8831
+ }
8832
+ const ignorePatterns = respect_git_ignore ? DEFAULT_IGNORE_PATTERNS2 : [];
8833
+ const matches = await glob(pattern, {
8834
+ cwd: targetDir,
8835
+ dot: false,
8836
+ // Don't match hidden files by default
8837
+ ignore: ignorePatterns,
8838
+ absolute: true,
8839
+ nodir: true,
8840
+ // Only return files, not directories
8841
+ nocase: !case_sensitive,
8842
+ // Case-insensitive if requested
8843
+ maxDepth: 20
8844
+ // Reasonable depth limit
8845
+ });
8842
8846
  if (matches.length === 0) {
8843
- return `No files found matching pattern: ${pattern}`;
8847
+ return `No files found matching pattern: ${pattern}${dir_path ? ` in ${dir_path}` : ""}`;
8844
8848
  }
8845
- const filesInfo = [];
8846
- for (const match of matches) {
8849
+ const filesWithStats = [];
8850
+ for (const filePath of matches) {
8847
8851
  try {
8848
- const fullPath = path9.join(targetCwd, match);
8849
- const stats = statSync5(fullPath);
8850
- filesInfo.push({
8851
- path: match,
8852
- size: stats.size,
8853
- isDirectory: stats.isDirectory(),
8854
- modified: stats.mtime.toISOString()
8852
+ const stats = await stat(filePath);
8853
+ filesWithStats.push({
8854
+ path: filePath,
8855
+ mtime: stats.mtimeMs
8855
8856
  });
8856
- } catch (error) {
8857
+ } catch {
8857
8858
  continue;
8858
8859
  }
8859
8860
  }
8860
- const truncated = matches.length >= maxResults;
8861
- const summary = `Found ${filesInfo.length} file(s)${truncated ? ` (truncated to ${maxResults})` : ""} matching: ${pattern}
8862
- `;
8863
- const cwdInfo = relativeCwd ? `Directory: ${relativeCwd}
8864
- ` : "";
8865
- const filesList = filesInfo.map((file) => {
8866
- const typeIcon = file.isDirectory ? "\u{1F4C1}" : "\u{1F4C4}";
8867
- const sizeStr = file.isDirectory ? "" : ` (${formatBytes(file.size)})`;
8868
- return `${typeIcon} ${file.path}${sizeStr}`;
8869
- }).join("\n");
8870
- return `${summary}${cwdInfo}
8861
+ filesWithStats.sort((a, b) => b.mtime - a.mtime);
8862
+ const MAX_RESULTS = 500;
8863
+ const truncated = filesWithStats.length > MAX_RESULTS;
8864
+ const results = truncated ? filesWithStats.slice(0, MAX_RESULTS) : filesWithStats;
8865
+ const summary = `Found ${filesWithStats.length} file(s)${truncated ? ` (showing first ${MAX_RESULTS})` : ""} matching: ${pattern}`;
8866
+ const dirInfo = dir_path ? `
8867
+ Directory: ${dir_path}` : "";
8868
+ const filesList = results.map((file) => path9.relative(baseCwd, file.path)).join("\n");
8869
+ return `${summary}${dirInfo}
8870
+
8871
8871
  ${filesList}`;
8872
8872
  }
8873
- function formatBytes(bytes) {
8874
- if (bytes === 0)
8875
- return "0 B";
8876
- const k = 1024;
8877
- const sizes = ["B", "KB", "MB", "GB"];
8878
- const i = Math.floor(Math.log(bytes) / Math.log(k));
8879
- return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`;
8880
- }
8881
8873
  var globFilesTool = {
8882
8874
  name: "glob_files",
8883
8875
  implementation: (context) => ({
8884
8876
  toolFn: async (value) => {
8885
8877
  const params = value;
8886
- context.logger.info("\u{1F50D} GlobFiles: Listing files", {
8878
+ context.logger.info("\u{1F50D} GlobFiles: Finding files", {
8887
8879
  pattern: params.pattern,
8888
- cwd: params.cwd || "."
8880
+ dir_path: params.dir_path || "."
8889
8881
  });
8890
8882
  try {
8891
- const result = await listFiles(params);
8883
+ const result = await findFiles(params);
8892
8884
  context.logger.info("\u2705 GlobFiles: Success");
8893
8885
  return result;
8894
8886
  } catch (error) {
@@ -8898,25 +8890,25 @@ var globFilesTool = {
8898
8890
  },
8899
8891
  toolSchema: {
8900
8892
  name: "glob_files",
8901
- description: "List files and directories matching a glob pattern. Supports wildcards like *, **, ?, and character ranges. Restricted to current working directory for security.",
8893
+ description: "Efficiently finds files matching specific glob patterns (e.g., `src/**/*.ts`, `**/*.md`), returning absolute paths sorted by modification time (newest first). Use this to locate files by name or extension when you need to discover or explore files in a project.",
8902
8894
  parameters: {
8903
8895
  type: "object",
8904
8896
  properties: {
8905
8897
  pattern: {
8906
8898
  type: "string",
8907
- description: 'Glob pattern to match files (e.g., "*.ts", "src/**/*.tsx", "test/*.{js,ts}"). Use ** for recursive directory search.'
8899
+ description: 'The glob pattern to match against file paths. Supports wildcards (*, **), character ranges ([abc]), and brace expansion ({ts,tsx}). Examples: "*.ts" for TypeScript files in current dir, "src/**/*.tsx" for all TSX files in src tree, "**/*.{js,ts}" for all JS/TS files.'
8908
8900
  },
8909
- cwd: {
8901
+ dir_path: {
8910
8902
  type: "string",
8911
- description: "Directory to search in, relative to current working directory (optional). Defaults to current directory."
8903
+ description: "Optional: The absolute path to the directory to search in. If not specified, uses the current working directory. Must be within the current working directory."
8912
8904
  },
8913
- includeHidden: {
8905
+ case_sensitive: {
8914
8906
  type: "boolean",
8915
- description: "Include hidden files and directories (starting with .). Defaults to false."
8907
+ description: "Optional: Whether the pattern matching should be case-sensitive. Defaults to true. Set to false for case-insensitive matching on case-sensitive file systems."
8916
8908
  },
8917
- maxResults: {
8918
- type: "number",
8919
- description: "Maximum number of results to return (default: 1000). Prevents overwhelming output."
8909
+ respect_git_ignore: {
8910
+ type: "boolean",
8911
+ description: "Optional: Whether to respect common ignore patterns (node_modules, .git, dist, build, etc.). Defaults to true. Set to false to include all files."
8920
8912
  }
8921
8913
  },
8922
8914
  required: ["pattern"]
@@ -8926,158 +8918,144 @@ var globFilesTool = {
8926
8918
  };
8927
8919
 
8928
8920
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/grepSearch/index.js
8929
- import { readdir as readdir2 } from "fs/promises";
8930
- import { readFileSync as readFileSync3, statSync as statSync6 } from "fs";
8921
+ import { globStream } from "glob";
8922
+ import { readFile, stat as stat2 } from "fs/promises";
8931
8923
  import path10 from "path";
8932
8924
  var MAX_FILE_SIZE3 = 10 * 1024 * 1024;
8933
- function matchGlob2(filePath, pattern) {
8934
- const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*").replace(/\?/g, ".").replace(/\{([^}]+)\}/g, (_, options) => `(${options.split(",").join("|")})`).replace(/\[([^\]]+)\]/g, "[$1]");
8935
- const regex = new RegExp(`^${regexPattern}$`);
8936
- return regex.test(filePath);
8937
- }
8938
- async function walkDir2(dir, pattern, maxResults, basePath = "") {
8939
- const matches = [];
8925
+ var DEFAULT_IGNORE_PATTERNS3 = [
8926
+ "**/node_modules/**",
8927
+ "**/.git/**",
8928
+ "**/dist/**",
8929
+ "**/build/**",
8930
+ "**/.next/**",
8931
+ "**/coverage/**",
8932
+ "**/*.min.js",
8933
+ "**/*.min.css",
8934
+ "**/package-lock.json",
8935
+ "**/pnpm-lock.yaml",
8936
+ "**/yarn.lock"
8937
+ ];
8938
+ async function isBinaryFile2(filePath) {
8940
8939
  try {
8941
- const entries = await readdir2(dir, { withFileTypes: true });
8942
- for (const entry of entries) {
8943
- if (matches.length >= maxResults) {
8944
- break;
8945
- }
8946
- if (entry.name.startsWith(".")) {
8947
- continue;
8948
- }
8949
- const relativePath = basePath ? path10.join(basePath, entry.name) : entry.name;
8950
- const fullPath = path10.join(dir, entry.name);
8951
- if (matchGlob2(relativePath, pattern)) {
8952
- matches.push(relativePath);
8953
- }
8954
- if (entry.isDirectory() && (pattern.includes("**") || pattern.includes("/"))) {
8955
- const subMatches = await walkDir2(fullPath, pattern, maxResults - matches.length, relativePath);
8956
- matches.push(...subMatches);
8957
- }
8958
- }
8959
- } catch (error) {
8940
+ const { openSync, readSync, closeSync } = await import("fs");
8941
+ const buffer = Buffer.alloc(8192);
8942
+ const fd = openSync(filePath, "r");
8943
+ const bytesRead = readSync(fd, buffer, 0, 8192, 0);
8944
+ closeSync(fd);
8945
+ const chunk = buffer.slice(0, bytesRead);
8946
+ return chunk.includes(0);
8947
+ } catch {
8948
+ return true;
8960
8949
  }
8961
- return matches;
8950
+ }
8951
+ function isPathWithinWorkspace(targetPath, baseCwd) {
8952
+ const resolvedTarget = path10.resolve(targetPath);
8953
+ const resolvedBase = path10.resolve(baseCwd);
8954
+ return resolvedTarget.startsWith(resolvedBase);
8962
8955
  }
8963
8956
  async function searchFiles2(params) {
8964
- const { pattern, filePattern = "**/*", cwd: relativeCwd, caseSensitive = false, maxResults = 100, contextLines = 0 } = params;
8957
+ const { pattern, dir_path, include } = params;
8965
8958
  const baseCwd = process.cwd();
8966
- const targetCwd = relativeCwd ? path10.resolve(baseCwd, path10.normalize(relativeCwd)) : baseCwd;
8967
- if (!targetCwd.startsWith(baseCwd)) {
8968
- throw new Error(`Access denied: Cannot search files outside of current working directory`);
8959
+ const targetDir = dir_path ? path10.resolve(baseCwd, dir_path) : baseCwd;
8960
+ if (!isPathWithinWorkspace(targetDir, baseCwd)) {
8961
+ throw new Error(`Path validation failed: "${dir_path}" resolves outside the allowed workspace directory`);
8962
+ }
8963
+ try {
8964
+ const stats = await stat2(targetDir);
8965
+ if (!stats.isDirectory()) {
8966
+ throw new Error(`Path is not a directory: ${dir_path}`);
8967
+ }
8968
+ } catch (error) {
8969
+ if (error.code === "ENOENT") {
8970
+ throw new Error(`Path does not exist: ${dir_path}`);
8971
+ }
8972
+ throw error;
8969
8973
  }
8970
8974
  let searchRegex;
8971
8975
  try {
8972
- const flags = caseSensitive ? "g" : "gi";
8973
- searchRegex = new RegExp(pattern, flags);
8976
+ searchRegex = new RegExp(pattern, "i");
8974
8977
  } catch (error) {
8975
8978
  throw new Error(`Invalid regex pattern: ${pattern}`);
8976
8979
  }
8977
- const filesToSearch = await walkDir2(targetCwd, filePattern, 1e4);
8978
- const filteredFiles = [];
8979
- for (const match of filesToSearch) {
8980
- const fullPath = path10.join(targetCwd, match);
8980
+ const globPattern = include || "**/*";
8981
+ const filesStream = globStream(globPattern, {
8982
+ cwd: targetDir,
8983
+ dot: false,
8984
+ // Skip hidden files
8985
+ ignore: DEFAULT_IGNORE_PATTERNS3,
8986
+ absolute: true,
8987
+ nodir: true
8988
+ // Only match files, not directories
8989
+ });
8990
+ const allMatches = [];
8991
+ let filesSearched = 0;
8992
+ const maxMatches = 500;
8993
+ for await (const filePath of filesStream) {
8994
+ if (allMatches.length >= maxMatches) {
8995
+ break;
8996
+ }
8997
+ const fileAbsPath = filePath;
8981
8998
  try {
8982
- const stats = statSync6(fullPath);
8983
- if (stats.isDirectory() || stats.size > MAX_FILE_SIZE3) {
8999
+ const stats = await stat2(fileAbsPath);
9000
+ if (stats.size > MAX_FILE_SIZE3) {
8984
9001
  continue;
8985
9002
  }
8986
- if (isBinaryFile2(fullPath)) {
9003
+ if (await isBinaryFile2(fileAbsPath)) {
8987
9004
  continue;
8988
9005
  }
8989
- filteredFiles.push(match);
8990
- } catch {
8991
- continue;
8992
- }
8993
- }
8994
- if (filteredFiles.length === 0) {
8995
- return `No files found matching pattern: ${filePattern}`;
8996
- }
8997
- const matches = [];
8998
- let filesSearched = 0;
8999
- for (const file of filteredFiles) {
9000
- if (matches.length >= maxResults) {
9001
- break;
9002
- }
9003
- filesSearched++;
9004
- const fullPath = path10.join(targetCwd, file);
9005
- try {
9006
- const content = readFileSync3(fullPath, "utf-8");
9007
- const lines = content.split("\n");
9008
- for (let i = 0; i < lines.length; i++) {
9009
- if (matches.length >= maxResults) {
9010
- break;
9011
- }
9012
- const line = lines[i];
9013
- if (searchRegex.test(line)) {
9014
- const match = {
9015
- file,
9016
- line: i + 1,
9017
- // 1-indexed line numbers
9018
- content: line.trim()
9019
- };
9020
- if (contextLines > 0) {
9021
- const before = [];
9022
- const after = [];
9023
- for (let j = Math.max(0, i - contextLines); j < i; j++) {
9024
- before.push(lines[j]);
9025
- }
9026
- for (let j = i + 1; j <= Math.min(lines.length - 1, i + contextLines); j++) {
9027
- after.push(lines[j]);
9028
- }
9029
- match.context = { before, after };
9030
- }
9031
- matches.push(match);
9006
+ filesSearched++;
9007
+ const content = await readFile(fileAbsPath, "utf8");
9008
+ const lines = content.split(/\r?\n/);
9009
+ lines.forEach((line, index) => {
9010
+ if (allMatches.length < maxMatches && searchRegex.test(line)) {
9011
+ allMatches.push({
9012
+ filePath: path10.relative(targetDir, fileAbsPath) || path10.basename(fileAbsPath),
9013
+ lineNumber: index + 1,
9014
+ line
9015
+ });
9032
9016
  }
9033
- searchRegex.lastIndex = 0;
9017
+ });
9018
+ } catch (readError) {
9019
+ const errCode = readError.code;
9020
+ if (errCode !== "ENOENT" && errCode !== "EACCES") {
9021
+ console.debug(`Could not read ${fileAbsPath}: ${readError.message}`);
9034
9022
  }
9035
- } catch {
9036
- continue;
9037
9023
  }
9038
9024
  }
9039
- if (matches.length === 0) {
9040
- return `No matches found for pattern: ${pattern}
9041
- Searched ${filesSearched} file(s)`;
9025
+ const searchDirDisplay = dir_path || ".";
9026
+ const filterInfo = include ? ` (filter: "${include}")` : "";
9027
+ if (allMatches.length === 0) {
9028
+ return `No matches found for pattern "${pattern}" in "${searchDirDisplay}"${filterInfo}.
9029
+ Searched ${filesSearched} file(s).`;
9042
9030
  }
9043
- const truncated = matches.length >= maxResults;
9044
- const summary = `Found ${matches.length} match(es)${truncated ? ` (truncated to ${maxResults})` : ""} in ${filesSearched} file(s)
9031
+ const matchesByFile = allMatches.reduce((acc, match) => {
9032
+ if (!acc[match.filePath]) {
9033
+ acc[match.filePath] = [];
9034
+ }
9035
+ acc[match.filePath].push(match);
9036
+ acc[match.filePath].sort((a, b) => a.lineNumber - b.lineNumber);
9037
+ return acc;
9038
+ }, {});
9039
+ const matchCount = allMatches.length;
9040
+ const matchTerm = matchCount === 1 ? "match" : "matches";
9041
+ const truncated = matchCount >= maxMatches;
9042
+ let result = `Found ${matchCount} ${matchTerm}${truncated ? " (truncated)" : ""} for pattern "${pattern}" in "${searchDirDisplay}"${filterInfo}:
9045
9043
  `;
9046
- const patternInfo = `Pattern: ${pattern}${caseSensitive ? "" : " (case-insensitive)"}
9044
+ result += `Searched ${filesSearched} file(s)
9045
+ ---
9047
9046
  `;
9048
- const cwdInfo = relativeCwd ? `Directory: ${relativeCwd}
9049
- ` : "";
9050
- const matchesList = matches.map((match) => {
9051
- let result = `
9052
- \u{1F4C4} ${match.file}:${match.line}
9053
- ${match.content}`;
9054
- if (match.context) {
9055
- if (match.context.before.length > 0) {
9056
- result = `${result}
9057
- Context before:
9058
- ${match.context.before.map((l) => ` ${l}`).join("\n")}`;
9059
- }
9060
- if (match.context.after.length > 0) {
9061
- result = `${result}
9062
- Context after:
9063
- ${match.context.after.map((l) => ` ${l}`).join("\n")}`;
9064
- }
9065
- }
9066
- return result;
9067
- }).join("\n");
9068
- return `${summary}${patternInfo}${cwdInfo}${matchesList}`;
9069
- }
9070
- function isBinaryFile2(filePath) {
9071
- try {
9072
- const buffer = Buffer.alloc(8192);
9073
- const fd = __require("fs").openSync(filePath, "r");
9074
- const bytesRead = __require("fs").readSync(fd, buffer, 0, 8192, 0);
9075
- __require("fs").closeSync(fd);
9076
- const chunk = buffer.slice(0, bytesRead);
9077
- return chunk.includes(0);
9078
- } catch {
9079
- return true;
9047
+ for (const filePath in matchesByFile) {
9048
+ result += `File: ${filePath}
9049
+ `;
9050
+ matchesByFile[filePath].forEach((match) => {
9051
+ const trimmedLine = match.line.trim();
9052
+ const displayLine = trimmedLine.length > 200 ? trimmedLine.slice(0, 200) + "..." : trimmedLine;
9053
+ result += `L${match.lineNumber}: ${displayLine}
9054
+ `;
9055
+ });
9056
+ result += "---\n";
9080
9057
  }
9058
+ return result.trim();
9081
9059
  }
9082
9060
  var grepSearchTool = {
9083
9061
  name: "grep_search",
@@ -9086,8 +9064,8 @@ var grepSearchTool = {
9086
9064
  const params = value;
9087
9065
  context.logger.info("\u{1F50D} GrepSearch: Searching files", {
9088
9066
  pattern: params.pattern,
9089
- filePattern: params.filePattern || "**/*",
9090
- cwd: params.cwd || "."
9067
+ dir_path: params.dir_path || ".",
9068
+ include: params.include || "**/*"
9091
9069
  });
9092
9070
  try {
9093
9071
  const result = await searchFiles2(params);
@@ -9100,33 +9078,21 @@ var grepSearchTool = {
9100
9078
  },
9101
9079
  toolSchema: {
9102
9080
  name: "grep_search",
9103
- description: "Search for a regex pattern in file contents. Similar to grep command. Searches text files only, excluding binary files. Restricted to current working directory for security.",
9081
+ description: "Searches for a regular expression pattern within the content of files in a specified directory (or current working directory). Can filter files by a glob pattern. Returns the lines containing matches, along with their file paths and line numbers.",
9104
9082
  parameters: {
9105
9083
  type: "object",
9106
9084
  properties: {
9107
9085
  pattern: {
9108
9086
  type: "string",
9109
- description: "Regular expression pattern to search for in file contents"
9087
+ description: "The regular expression (regex) pattern to search for within file contents (e.g., 'function\\s+myFunction', 'import\\s+\\{.*\\}\\s+from\\s+.*')."
9110
9088
  },
9111
- filePattern: {
9089
+ dir_path: {
9112
9090
  type: "string",
9113
- description: 'Glob pattern to filter which files to search (optional, default: "**/*"). Example: "src/**/*.ts" to search only TypeScript files in src directory.'
9091
+ description: "Optional: The absolute path to the directory to search within. If omitted, searches the current working directory."
9114
9092
  },
9115
- cwd: {
9093
+ include: {
9116
9094
  type: "string",
9117
- description: "Directory to search in, relative to current working directory (optional). Defaults to current directory."
9118
- },
9119
- caseSensitive: {
9120
- type: "boolean",
9121
- description: "Whether the search should be case-sensitive (default: false)"
9122
- },
9123
- maxResults: {
9124
- type: "number",
9125
- description: "Maximum number of matches to return (default: 100). Prevents overwhelming output."
9126
- },
9127
- contextLines: {
9128
- type: "number",
9129
- description: "Number of context lines to show before and after each match (default: 0). Similar to grep -C option."
9095
+ description: "Optional: A glob pattern to filter which files are searched (e.g., '*.js', '*.{ts,tsx}', 'src/**'). If omitted, searches all files (respecting potential global ignores)."
9130
9096
  }
9131
9097
  },
9132
9098
  required: ["pattern"]
@@ -9137,7 +9103,7 @@ var grepSearchTool = {
9137
9103
 
9138
9104
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/deleteFile/index.js
9139
9105
  import { promises as fs10 } from "fs";
9140
- import { existsSync as existsSync6, statSync as statSync7 } from "fs";
9106
+ import { existsSync as existsSync6, statSync as statSync5 } from "fs";
9141
9107
  import path11 from "path";
9142
9108
  async function deleteFile(params) {
9143
9109
  const { path: filePath, recursive = false } = params;
@@ -9150,7 +9116,7 @@ async function deleteFile(params) {
9150
9116
  if (!existsSync6(resolvedPath)) {
9151
9117
  throw new Error(`File or directory not found: ${filePath}`);
9152
9118
  }
9153
- const stats = statSync7(resolvedPath);
9119
+ const stats = statSync5(resolvedPath);
9154
9120
  const isDirectory = stats.isDirectory();
9155
9121
  const size = stats.size;
9156
9122
  if (isDirectory && !recursive) {
@@ -9171,7 +9137,7 @@ var deleteFileTool = {
9171
9137
  toolFn: async (value) => {
9172
9138
  const params = value;
9173
9139
  const resolvedPath = path11.resolve(process.cwd(), path11.normalize(params.path));
9174
- const isDirectory = existsSync6(resolvedPath) && statSync7(resolvedPath).isDirectory();
9140
+ const isDirectory = existsSync6(resolvedPath) && statSync5(resolvedPath).isDirectory();
9175
9141
  context.logger.info(`\u{1F5D1}\uFE0F DeleteFile: Deleting ${isDirectory ? "directory" : "file"}`, {
9176
9142
  path: params.path,
9177
9143
  recursive: params.recursive
@@ -9947,7 +9913,7 @@ var ToolErrorType;
9947
9913
 
9948
9914
  // src/utils/diffPreview.ts
9949
9915
  import * as Diff from "diff";
9950
- import { readFile } from "fs/promises";
9916
+ import { readFile as readFile2 } from "fs/promises";
9951
9917
  import { existsSync as existsSync8 } from "fs";
9952
9918
  async function generateFileDiffPreview(args) {
9953
9919
  try {
@@ -9962,7 +9928,7 @@ ${preview}${hasMore ? `
9962
9928
 
9963
9929
  ... (${lines2.length - 20} more lines)` : ""}`;
9964
9930
  }
9965
- const currentContent = await readFile(args.path, "utf-8");
9931
+ const currentContent = await readFile2(args.path, "utf-8");
9966
9932
  const patch = Diff.createPatch(
9967
9933
  args.path,
9968
9934
  currentContent,
@@ -10871,6 +10837,82 @@ async function loadContextFiles(projectDir) {
10871
10837
  };
10872
10838
  }
10873
10839
 
10840
+ // src/utils/formatStep.ts
10841
+ var MAX_INPUT_LENGTH = 500;
10842
+ var formatStep = (step) => {
10843
+ if (step.type === "action" && step.metadata?.toolInput) {
10844
+ let parsedInput = step.metadata.toolInput;
10845
+ if (typeof parsedInput === "string") {
10846
+ try {
10847
+ parsedInput = JSON.parse(parsedInput);
10848
+ } catch {
10849
+ }
10850
+ }
10851
+ if (step.metadata.toolName === "edit_local_file") {
10852
+ const pathOnly = typeof parsedInput === "object" && parsedInput !== null && "path" in parsedInput ? { path: parsedInput.path } : parsedInput;
10853
+ return {
10854
+ ...step,
10855
+ metadata: {
10856
+ ...step.metadata,
10857
+ toolInput: pathOnly
10858
+ }
10859
+ };
10860
+ }
10861
+ if (step.metadata.toolName === "write_todos") {
10862
+ const todos = typeof parsedInput === "object" && parsedInput !== null && "todos" in parsedInput ? parsedInput.todos : null;
10863
+ const count = Array.isArray(todos) ? todos.length : 0;
10864
+ return {
10865
+ ...step,
10866
+ metadata: {
10867
+ ...step.metadata,
10868
+ toolInput: `${count} todo${count !== 1 ? "s" : ""}`
10869
+ }
10870
+ };
10871
+ }
10872
+ const inputStr = typeof parsedInput === "string" ? parsedInput : JSON.stringify(parsedInput);
10873
+ if (inputStr.length > MAX_INPUT_LENGTH) {
10874
+ const truncatedInput = inputStr.slice(0, MAX_INPUT_LENGTH) + `... (${inputStr.length - MAX_INPUT_LENGTH} more chars)`;
10875
+ return {
10876
+ ...step,
10877
+ metadata: {
10878
+ ...step.metadata,
10879
+ toolInput: truncatedInput
10880
+ }
10881
+ };
10882
+ }
10883
+ }
10884
+ if (step.type === "observation" && step.content) {
10885
+ if (step.metadata?.toolName === "file_read") {
10886
+ const lineCount = step.content.split("\n").length;
10887
+ return {
10888
+ ...step,
10889
+ content: `Read ${lineCount} line${lineCount !== 1 ? "s" : ""}`
10890
+ };
10891
+ }
10892
+ if (step.metadata?.toolName === "grep_search") {
10893
+ const match = step.content.match(/^Found (\d+) file\(s\)/);
10894
+ if (match) {
10895
+ const count = parseInt(match[1], 10);
10896
+ return {
10897
+ ...step,
10898
+ content: `Found ${count} file${count !== 1 ? "s" : ""}`
10899
+ };
10900
+ }
10901
+ }
10902
+ if (step.metadata?.toolName === "glob_files") {
10903
+ const match = step.content.match(/^Found (\d+) file\(s\)/);
10904
+ if (match) {
10905
+ const count = parseInt(match[1], 10);
10906
+ return {
10907
+ ...step,
10908
+ content: `Found ${count} file${count !== 1 ? "s" : ""}`
10909
+ };
10910
+ }
10911
+ }
10912
+ }
10913
+ return step;
10914
+ };
10915
+
10874
10916
  // src/utils/argumentSubstitution.ts
10875
10917
  function substituteArguments(template, args) {
10876
10918
  let result = template;
@@ -11786,7 +11828,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
11786
11828
  // package.json
11787
11829
  var package_default = {
11788
11830
  name: "@bike4mind/cli",
11789
- version: "0.2.12-refactor-github-mcp.17405+ae8801bbe",
11831
+ version: "0.2.13",
11790
11832
  type: "module",
11791
11833
  description: "Interactive CLI tool for Bike4Mind with ReAct agents",
11792
11834
  license: "UNLICENSED",
@@ -11857,6 +11899,7 @@ var package_default = {
11857
11899
  "eventsource-parser": "^3.0.6",
11858
11900
  "file-type": "^18.7.0",
11859
11901
  "fuse.js": "^7.1.0",
11902
+ glob: "^13.0.0",
11860
11903
  "gray-matter": "^4.0.3",
11861
11904
  ink: "^6.5.1",
11862
11905
  "ink-select-input": "^6.2.0",
@@ -11890,11 +11933,11 @@ var package_default = {
11890
11933
  zustand: "^4.5.4"
11891
11934
  },
11892
11935
  devDependencies: {
11893
- "@bike4mind/agents": "0.1.0",
11894
- "@bike4mind/common": "2.41.1-refactor-github-mcp.17405+ae8801bbe",
11895
- "@bike4mind/mcp": "1.21.1-refactor-github-mcp.17405+ae8801bbe",
11896
- "@bike4mind/services": "2.36.1-refactor-github-mcp.17405+ae8801bbe",
11897
- "@bike4mind/utils": "2.1.6-refactor-github-mcp.17405+ae8801bbe",
11936
+ "@bike4mind/agents": "workspace:*",
11937
+ "@bike4mind/common": "workspace:*",
11938
+ "@bike4mind/mcp": "workspace:*",
11939
+ "@bike4mind/services": "workspace:*",
11940
+ "@bike4mind/utils": "workspace:*",
11898
11941
  "@types/better-sqlite3": "^7.6.13",
11899
11942
  "@types/diff": "^5.0.9",
11900
11943
  "@types/jsonwebtoken": "^9.0.4",
@@ -11902,12 +11945,12 @@ var package_default = {
11902
11945
  "@types/react": "^19.2.7",
11903
11946
  "@types/uuid": "^9.0.7",
11904
11947
  "@types/yargs": "^17.0.32",
11948
+ "ink-testing-library": "^4.0.0",
11905
11949
  tsup: "^8.5.1",
11906
11950
  tsx: "^4.21.0",
11907
11951
  typescript: "^5.9.3",
11908
11952
  vitest: "^3.2.4"
11909
- },
11910
- gitHead: "ae8801bbe418b5047a42104f8aa2bb56c198e4b5"
11953
+ }
11911
11954
  };
11912
11955
 
11913
11956
  // src/config/constants.ts
@@ -12263,6 +12306,131 @@ function createSubagentDelegateTool(orchestrator, parentSessionId) {
12263
12306
  };
12264
12307
  }
12265
12308
 
12309
+ // src/tools/writeTodosTool.ts
12310
+ var VALID_STATUSES = ["pending", "in_progress", "completed", "cancelled"];
12311
+ function validateTodos(todos) {
12312
+ if (!Array.isArray(todos)) {
12313
+ throw new Error("write_todos: todos must be an array");
12314
+ }
12315
+ if (todos.length === 0) {
12316
+ throw new Error("write_todos: todos array cannot be empty");
12317
+ }
12318
+ let inProgressCount = 0;
12319
+ const validatedTodos = todos.map((item, index) => {
12320
+ if (!item || typeof item !== "object") {
12321
+ throw new Error(`write_todos: item at index ${index} must be an object`);
12322
+ }
12323
+ const { description, status } = item;
12324
+ if (typeof description !== "string" || description.trim() === "") {
12325
+ throw new Error(`write_todos: item at index ${index} must have a non-empty description`);
12326
+ }
12327
+ if (typeof status !== "string" || !VALID_STATUSES.includes(status)) {
12328
+ throw new Error(
12329
+ `write_todos: item at index ${index} has invalid status "${status}". Must be one of: ${VALID_STATUSES.join(", ")}`
12330
+ );
12331
+ }
12332
+ if (status === "in_progress") {
12333
+ inProgressCount++;
12334
+ }
12335
+ return {
12336
+ description: description.trim(),
12337
+ status
12338
+ };
12339
+ });
12340
+ if (inProgressCount > 1) {
12341
+ throw new Error(`write_todos: only one task can be 'in_progress' at a time, but found ${inProgressCount}`);
12342
+ }
12343
+ return validatedTodos;
12344
+ }
12345
+ function formatTodosOutput(todos) {
12346
+ const statusEmoji = {
12347
+ pending: "\u2B1C",
12348
+ in_progress: "\u{1F504}",
12349
+ completed: "\u2705",
12350
+ cancelled: "\u274C"
12351
+ };
12352
+ const lines = todos.map((todo, index) => {
12353
+ const emoji = statusEmoji[todo.status];
12354
+ return `${index + 1}. ${emoji} [${todo.status}] ${todo.description}`;
12355
+ });
12356
+ return lines.join("\n");
12357
+ }
12358
+ function createWriteTodosTool(todoStore) {
12359
+ return {
12360
+ toolFn: async (args) => {
12361
+ const params = args;
12362
+ const validatedTodos = validateTodos(params.todos);
12363
+ todoStore.todos = validatedTodos;
12364
+ if (todoStore.onUpdate) {
12365
+ todoStore.onUpdate(validatedTodos);
12366
+ }
12367
+ const output = formatTodosOutput(validatedTodos);
12368
+ return `Todo list updated successfully:
12369
+
12370
+ ${output}`;
12371
+ },
12372
+ toolSchema: {
12373
+ name: "write_todos",
12374
+ description: `Create or update a list of subtasks for complex multi-step requests.
12375
+
12376
+ **When to use this tool:**
12377
+ - When handling complex requests that require multiple steps
12378
+ - To break down a large task into smaller, manageable subtasks
12379
+ - To track progress through a multi-step workflow
12380
+
12381
+ **Important guidelines:**
12382
+ - Call this tool early when receiving complex requests
12383
+ - Update the list immediately when starting, completing, or cancelling tasks
12384
+ - Only ONE task should be 'in_progress' at a time
12385
+ - Never batch updates - update as soon as state changes
12386
+
12387
+ **Status values:**
12388
+ - pending: Task not yet started
12389
+ - in_progress: Currently working on this task (only ONE allowed)
12390
+ - completed: Task finished successfully
12391
+ - cancelled: Task will not be completed
12392
+
12393
+ **Example:**
12394
+ If asked to "create a new React component with tests", break it down:
12395
+ 1. Create component file \u2192 in_progress
12396
+ 2. Add component logic \u2192 pending
12397
+ 3. Create test file \u2192 pending
12398
+ 4. Write unit tests \u2192 pending`,
12399
+ parameters: {
12400
+ type: "object",
12401
+ properties: {
12402
+ todos: {
12403
+ type: "array",
12404
+ description: "Complete list of todo items. This replaces any existing list. Each item needs a description and status.",
12405
+ items: {
12406
+ type: "object",
12407
+ properties: {
12408
+ description: {
12409
+ type: "string",
12410
+ description: "Clear, concise description of the task"
12411
+ },
12412
+ status: {
12413
+ type: "string",
12414
+ enum: ["pending", "in_progress", "completed", "cancelled"],
12415
+ description: "Current status of the task. Only ONE task can be 'in_progress' at a time."
12416
+ }
12417
+ },
12418
+ required: ["description", "status"]
12419
+ }
12420
+ }
12421
+ },
12422
+ required: ["todos"]
12423
+ }
12424
+ }
12425
+ };
12426
+ }
12427
+ function createTodoStore(onUpdate) {
12428
+ return {
12429
+ todos: [],
12430
+ onUpdate
12431
+ };
12432
+ }
12433
+
12266
12434
  // src/index.tsx
12267
12435
  process.removeAllListeners("warning");
12268
12436
  process.on("warning", (warning) => {
@@ -12547,7 +12715,9 @@ function CliApp() {
12547
12715
  subagentConfigs
12548
12716
  });
12549
12717
  const subagentDelegateTool = createSubagentDelegateTool(orchestrator, newSession.id);
12550
- const allTools = [...b4mTools, ...mcpTools, subagentDelegateTool];
12718
+ const todoStore = createTodoStore();
12719
+ const writeTodosTool = createWriteTodosTool(todoStore);
12720
+ const allTools = [...b4mTools, ...mcpTools, subagentDelegateTool, writeTodosTool];
12551
12721
  console.log(`\u{1F916} Subagent delegation enabled (explore, plan, review)`);
12552
12722
  const projectDir = state.configStore.getProjectConfigDir();
12553
12723
  const contextResult = await loadContextFiles(projectDir);
@@ -12587,26 +12757,12 @@ ${contextResult.mergedContent}` : "";
12587
12757
  const lastIdx = pendingMessages.length - 1;
12588
12758
  if (lastIdx >= 0 && pendingMessages[lastIdx].role === "assistant") {
12589
12759
  const existingSteps = pendingMessages[lastIdx].metadata?.steps || [];
12590
- const MAX_INPUT_LENGTH = 500;
12591
- let truncatedStep = step;
12592
- if (step.type === "action" && step.metadata?.toolInput) {
12593
- const inputStr = typeof step.metadata.toolInput === "string" ? step.metadata.toolInput : JSON.stringify(step.metadata.toolInput);
12594
- if (inputStr.length > MAX_INPUT_LENGTH) {
12595
- const truncatedInput = inputStr.slice(0, MAX_INPUT_LENGTH) + `... (${inputStr.length - MAX_INPUT_LENGTH} more chars)`;
12596
- truncatedStep = {
12597
- ...step,
12598
- metadata: {
12599
- ...step.metadata,
12600
- toolInput: truncatedInput
12601
- }
12602
- };
12603
- }
12604
- }
12760
+ const formattedStep = formatStep(step);
12605
12761
  updatePendingMessage(lastIdx, {
12606
12762
  ...pendingMessages[lastIdx],
12607
12763
  metadata: {
12608
12764
  ...pendingMessages[lastIdx].metadata,
12609
- steps: [...existingSteps, truncatedStep]
12765
+ steps: [...existingSteps, formattedStep]
12610
12766
  }
12611
12767
  });
12612
12768
  }
@@ -12748,7 +12904,7 @@ ${contextResult.mergedContent}` : "";
12748
12904
  content: result.finalAnswer,
12749
12905
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12750
12906
  metadata: {
12751
- steps: result.steps,
12907
+ steps: result.steps.map(formatStep),
12752
12908
  tokenUsage: {
12753
12909
  prompt: 0,
12754
12910
  completion: 0,
@@ -12921,7 +13077,7 @@ ${contextResult.mergedContent}` : "";
12921
13077
  completion: 0,
12922
13078
  total: result.completionInfo.totalTokens
12923
13079
  },
12924
- steps: result.steps,
13080
+ steps: result.steps.map(formatStep),
12925
13081
  // Complete history: thoughts, actions, observations
12926
13082
  permissionDenied
12927
13083
  }
@@ -13027,7 +13183,7 @@ ${output}` : output.trim() || "(no output)",
13027
13183
  if (!imageStore) {
13028
13184
  if (!imageStoreInitPromise.current) {
13029
13185
  imageStoreInitPromise.current = (async () => {
13030
- const { ImageStore: ImageStoreClass } = await import("./ImageStore-RQZ7OF7P.js");
13186
+ const { ImageStore: ImageStoreClass } = await import("./ImageStore-MMUOUPI2.js");
13031
13187
  const newImageStore = new ImageStoreClass();
13032
13188
  setState((prev) => ({
13033
13189
  ...prev,