@bike4mind/cli 0.2.12-ja-fix-slack-github-repo-matching.17428 → 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-SUX7CM3M.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-TSL6XWBP.js → chunk-K44CDTQN.js} +2 -2
  8. package/dist/{chunk-AMDXHL6S.js → chunk-OCYRD7D6.js} +1 -1
  9. package/dist/{chunk-KRVKVP5N.js → chunk-PMY7IPOC.js} +18 -2
  10. package/dist/{chunk-KKYUZIAR.js → chunk-SZXO7HOF.js} +2 -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 +434 -279
  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-X53U3ZW5.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-IQITRVUM.js +0 -13
  25. package/dist/mementoService-3C2I4CHP.js +0 -13
  26. package/dist/subtractCredits-6LARUXMI.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-SUX7CM3M.js";
8
- import "./chunk-KRVKVP5N.js";
9
- import "./chunk-TSL6XWBP.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-KKYUZIAR.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";
@@ -3395,6 +3393,15 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
3395
3393
  };
3396
3394
 
3397
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";
3398
3405
  function buildCoreSystemPrompt(contextSection = "") {
3399
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.
3400
3407
 
@@ -3406,15 +3413,17 @@ CORE BEHAVIOR:
3406
3413
 
3407
3414
  FOR SOFTWARE ENGINEERING TASKS:
3408
3415
  When requested to perform tasks like fixing bugs, adding features, refactoring, or explaining code, follow this sequence:
3409
- 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.
3410
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.
3411
- 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.
3412
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.
3413
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.
3414
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.
3415
3424
 
3416
3425
  SUBAGENT DELEGATION:
3417
- - You have access to specialized subagents via the subagent_delegate tool
3426
+ - You have access to specialized subagents via the ${TOOL_SUBAGENT_DELEGATE} tool
3418
3427
  - Use subagents for focused exploration, planning, or review tasks:
3419
3428
  * explore: Fast read-only codebase search (e.g., "find all auth files", "locate API endpoints")
3420
3429
  * plan: Break down complex tasks into actionable steps
@@ -3430,7 +3439,7 @@ FOR GENERAL TASKS:
3430
3439
  ## Shell tool output token efficiency:
3431
3440
  IT IS CRITICAL TO FOLLOW THESE GUIDELINES TO AVOID EXCESSIVE TOKEN CONSUMPTION.
3432
3441
 
3433
- - 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}'.
3434
3443
  - Aim to minimize tool output tokens while still capturing necessary information.
3435
3444
  - If a command is expected to produce a lot of output, use quiet or silent flags where available and appropriate.
3436
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.
@@ -3440,9 +3449,9 @@ IT IS CRITICAL TO FOLLOW THESE GUIDELINES TO AVOID EXCESSIVE TOKEN CONSUMPTION.
3440
3449
  EXAMPLES:
3441
3450
  - "what should I wear in Texas?" \u2192 use weather tool for Texas
3442
3451
  - "how about Japan?" \u2192 use weather tool for Japan (applying same question from context)
3443
- - "enhance README" \u2192 file_read \u2192 generate \u2192 create_file
3444
- - "what packages installed?" \u2192 glob_files "**/package.json" \u2192 file_read
3445
- - "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")
3446
3455
 
3447
3456
  Remember: Use context from previous messages to understand follow-up questions.${contextSection}`;
3448
3457
  }
@@ -8798,98 +8807,80 @@ var createFileTool = {
8798
8807
  };
8799
8808
 
8800
8809
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/globFiles/index.js
8801
- import { readdir } from "fs/promises";
8802
- import { statSync as statSync5 } from "fs";
8810
+ import { glob } from "glob";
8811
+ import { stat } from "fs/promises";
8803
8812
  import path9 from "path";
8804
- function matchGlob(filePath, pattern) {
8805
- let regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, ".").replace(/\{([^}]+)\}/g, (_, options) => `(${options.split(",").join("|")})`).replace(/\[([^\]]+)\]/g, "[$1]");
8806
- regexPattern = regexPattern.replace(/\{\{GLOBSTAR\}\}\//g, "(.*/)?").replace(/\/\{\{GLOBSTAR\}\}/g, "(/.*)?").replace(/\{\{GLOBSTAR\}\}/g, ".*");
8807
- const regex = new RegExp(`^${regexPattern}$`);
8808
- return regex.test(filePath);
8809
- }
8810
- async function walkDir(dir, pattern, includeHidden, maxResults, basePath = "") {
8811
- const matches = [];
8812
- try {
8813
- const entries = await readdir(dir, { withFileTypes: true });
8814
- for (const entry of entries) {
8815
- if (matches.length >= maxResults) {
8816
- break;
8817
- }
8818
- if (!includeHidden && entry.name.startsWith(".")) {
8819
- continue;
8820
- }
8821
- const relativePath = basePath ? path9.join(basePath, entry.name) : entry.name;
8822
- const fullPath = path9.join(dir, entry.name);
8823
- if (matchGlob(relativePath, pattern)) {
8824
- matches.push(relativePath);
8825
- }
8826
- if (entry.isDirectory() && (pattern.includes("**") || pattern.includes("/"))) {
8827
- const subMatches = await walkDir(fullPath, pattern, includeHidden, maxResults - matches.length, relativePath);
8828
- matches.push(...subMatches);
8829
- }
8830
- }
8831
- } catch (error) {
8832
- }
8833
- return matches;
8834
- }
8835
- async function listFiles(params) {
8836
- 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;
8837
8827
  const baseCwd = process.cwd();
8838
- const targetCwd = relativeCwd ? path9.resolve(baseCwd, path9.normalize(relativeCwd)) : baseCwd;
8839
- if (!targetCwd.startsWith(baseCwd)) {
8840
- throw new Error(`Access denied: Cannot list files outside of current working directory`);
8841
- }
8842
- 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
+ });
8843
8846
  if (matches.length === 0) {
8844
- return `No files found matching pattern: ${pattern}`;
8847
+ return `No files found matching pattern: ${pattern}${dir_path ? ` in ${dir_path}` : ""}`;
8845
8848
  }
8846
- const filesInfo = [];
8847
- for (const match of matches) {
8849
+ const filesWithStats = [];
8850
+ for (const filePath of matches) {
8848
8851
  try {
8849
- const fullPath = path9.join(targetCwd, match);
8850
- const stats = statSync5(fullPath);
8851
- filesInfo.push({
8852
- path: match,
8853
- size: stats.size,
8854
- isDirectory: stats.isDirectory(),
8855
- modified: stats.mtime.toISOString()
8852
+ const stats = await stat(filePath);
8853
+ filesWithStats.push({
8854
+ path: filePath,
8855
+ mtime: stats.mtimeMs
8856
8856
  });
8857
- } catch (error) {
8857
+ } catch {
8858
8858
  continue;
8859
8859
  }
8860
8860
  }
8861
- const truncated = matches.length >= maxResults;
8862
- const summary = `Found ${filesInfo.length} file(s)${truncated ? ` (truncated to ${maxResults})` : ""} matching: ${pattern}
8863
- `;
8864
- const cwdInfo = relativeCwd ? `Directory: ${relativeCwd}
8865
- ` : "";
8866
- const filesList = filesInfo.map((file) => {
8867
- const typeIcon = file.isDirectory ? "\u{1F4C1}" : "\u{1F4C4}";
8868
- const sizeStr = file.isDirectory ? "" : ` (${formatBytes(file.size)})`;
8869
- return `${typeIcon} ${file.path}${sizeStr}`;
8870
- }).join("\n");
8871
- 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
+
8872
8871
  ${filesList}`;
8873
8872
  }
8874
- function formatBytes(bytes) {
8875
- if (bytes === 0)
8876
- return "0 B";
8877
- const k = 1024;
8878
- const sizes = ["B", "KB", "MB", "GB"];
8879
- const i = Math.floor(Math.log(bytes) / Math.log(k));
8880
- return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`;
8881
- }
8882
8873
  var globFilesTool = {
8883
8874
  name: "glob_files",
8884
8875
  implementation: (context) => ({
8885
8876
  toolFn: async (value) => {
8886
8877
  const params = value;
8887
- context.logger.info("\u{1F50D} GlobFiles: Listing files", {
8878
+ context.logger.info("\u{1F50D} GlobFiles: Finding files", {
8888
8879
  pattern: params.pattern,
8889
- cwd: params.cwd || "."
8880
+ dir_path: params.dir_path || "."
8890
8881
  });
8891
8882
  try {
8892
- const result = await listFiles(params);
8883
+ const result = await findFiles(params);
8893
8884
  context.logger.info("\u2705 GlobFiles: Success");
8894
8885
  return result;
8895
8886
  } catch (error) {
@@ -8899,25 +8890,25 @@ var globFilesTool = {
8899
8890
  },
8900
8891
  toolSchema: {
8901
8892
  name: "glob_files",
8902
- 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.",
8903
8894
  parameters: {
8904
8895
  type: "object",
8905
8896
  properties: {
8906
8897
  pattern: {
8907
8898
  type: "string",
8908
- 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.'
8909
8900
  },
8910
- cwd: {
8901
+ dir_path: {
8911
8902
  type: "string",
8912
- 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."
8913
8904
  },
8914
- includeHidden: {
8905
+ case_sensitive: {
8915
8906
  type: "boolean",
8916
- 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."
8917
8908
  },
8918
- maxResults: {
8919
- type: "number",
8920
- 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."
8921
8912
  }
8922
8913
  },
8923
8914
  required: ["pattern"]
@@ -8927,158 +8918,144 @@ var globFilesTool = {
8927
8918
  };
8928
8919
 
8929
8920
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/grepSearch/index.js
8930
- import { readdir as readdir2 } from "fs/promises";
8931
- import { readFileSync as readFileSync3, statSync as statSync6 } from "fs";
8921
+ import { globStream } from "glob";
8922
+ import { readFile, stat as stat2 } from "fs/promises";
8932
8923
  import path10 from "path";
8933
8924
  var MAX_FILE_SIZE3 = 10 * 1024 * 1024;
8934
- function matchGlob2(filePath, pattern) {
8935
- const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*").replace(/\?/g, ".").replace(/\{([^}]+)\}/g, (_, options) => `(${options.split(",").join("|")})`).replace(/\[([^\]]+)\]/g, "[$1]");
8936
- const regex = new RegExp(`^${regexPattern}$`);
8937
- return regex.test(filePath);
8938
- }
8939
- async function walkDir2(dir, pattern, maxResults, basePath = "") {
8940
- 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) {
8941
8939
  try {
8942
- const entries = await readdir2(dir, { withFileTypes: true });
8943
- for (const entry of entries) {
8944
- if (matches.length >= maxResults) {
8945
- break;
8946
- }
8947
- if (entry.name.startsWith(".")) {
8948
- continue;
8949
- }
8950
- const relativePath = basePath ? path10.join(basePath, entry.name) : entry.name;
8951
- const fullPath = path10.join(dir, entry.name);
8952
- if (matchGlob2(relativePath, pattern)) {
8953
- matches.push(relativePath);
8954
- }
8955
- if (entry.isDirectory() && (pattern.includes("**") || pattern.includes("/"))) {
8956
- const subMatches = await walkDir2(fullPath, pattern, maxResults - matches.length, relativePath);
8957
- matches.push(...subMatches);
8958
- }
8959
- }
8960
- } 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;
8961
8949
  }
8962
- 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);
8963
8955
  }
8964
8956
  async function searchFiles2(params) {
8965
- const { pattern, filePattern = "**/*", cwd: relativeCwd, caseSensitive = false, maxResults = 100, contextLines = 0 } = params;
8957
+ const { pattern, dir_path, include } = params;
8966
8958
  const baseCwd = process.cwd();
8967
- const targetCwd = relativeCwd ? path10.resolve(baseCwd, path10.normalize(relativeCwd)) : baseCwd;
8968
- if (!targetCwd.startsWith(baseCwd)) {
8969
- 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;
8970
8973
  }
8971
8974
  let searchRegex;
8972
8975
  try {
8973
- const flags = caseSensitive ? "g" : "gi";
8974
- searchRegex = new RegExp(pattern, flags);
8976
+ searchRegex = new RegExp(pattern, "i");
8975
8977
  } catch (error) {
8976
8978
  throw new Error(`Invalid regex pattern: ${pattern}`);
8977
8979
  }
8978
- const filesToSearch = await walkDir2(targetCwd, filePattern, 1e4);
8979
- const filteredFiles = [];
8980
- for (const match of filesToSearch) {
8981
- 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;
8982
8998
  try {
8983
- const stats = statSync6(fullPath);
8984
- if (stats.isDirectory() || stats.size > MAX_FILE_SIZE3) {
8999
+ const stats = await stat2(fileAbsPath);
9000
+ if (stats.size > MAX_FILE_SIZE3) {
8985
9001
  continue;
8986
9002
  }
8987
- if (isBinaryFile2(fullPath)) {
9003
+ if (await isBinaryFile2(fileAbsPath)) {
8988
9004
  continue;
8989
9005
  }
8990
- filteredFiles.push(match);
8991
- } catch {
8992
- continue;
8993
- }
8994
- }
8995
- if (filteredFiles.length === 0) {
8996
- return `No files found matching pattern: ${filePattern}`;
8997
- }
8998
- const matches = [];
8999
- let filesSearched = 0;
9000
- for (const file of filteredFiles) {
9001
- if (matches.length >= maxResults) {
9002
- break;
9003
- }
9004
- filesSearched++;
9005
- const fullPath = path10.join(targetCwd, file);
9006
- try {
9007
- const content = readFileSync3(fullPath, "utf-8");
9008
- const lines = content.split("\n");
9009
- for (let i = 0; i < lines.length; i++) {
9010
- if (matches.length >= maxResults) {
9011
- break;
9012
- }
9013
- const line = lines[i];
9014
- if (searchRegex.test(line)) {
9015
- const match = {
9016
- file,
9017
- line: i + 1,
9018
- // 1-indexed line numbers
9019
- content: line.trim()
9020
- };
9021
- if (contextLines > 0) {
9022
- const before = [];
9023
- const after = [];
9024
- for (let j = Math.max(0, i - contextLines); j < i; j++) {
9025
- before.push(lines[j]);
9026
- }
9027
- for (let j = i + 1; j <= Math.min(lines.length - 1, i + contextLines); j++) {
9028
- after.push(lines[j]);
9029
- }
9030
- match.context = { before, after };
9031
- }
9032
- 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
+ });
9033
9016
  }
9034
- 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}`);
9035
9022
  }
9036
- } catch {
9037
- continue;
9038
9023
  }
9039
9024
  }
9040
- if (matches.length === 0) {
9041
- return `No matches found for pattern: ${pattern}
9042
- 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).`;
9043
9030
  }
9044
- const truncated = matches.length >= maxResults;
9045
- 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}:
9046
9043
  `;
9047
- const patternInfo = `Pattern: ${pattern}${caseSensitive ? "" : " (case-insensitive)"}
9044
+ result += `Searched ${filesSearched} file(s)
9045
+ ---
9048
9046
  `;
9049
- const cwdInfo = relativeCwd ? `Directory: ${relativeCwd}
9050
- ` : "";
9051
- const matchesList = matches.map((match) => {
9052
- let result = `
9053
- \u{1F4C4} ${match.file}:${match.line}
9054
- ${match.content}`;
9055
- if (match.context) {
9056
- if (match.context.before.length > 0) {
9057
- result = `${result}
9058
- Context before:
9059
- ${match.context.before.map((l) => ` ${l}`).join("\n")}`;
9060
- }
9061
- if (match.context.after.length > 0) {
9062
- result = `${result}
9063
- Context after:
9064
- ${match.context.after.map((l) => ` ${l}`).join("\n")}`;
9065
- }
9066
- }
9067
- return result;
9068
- }).join("\n");
9069
- return `${summary}${patternInfo}${cwdInfo}${matchesList}`;
9070
- }
9071
- function isBinaryFile2(filePath) {
9072
- try {
9073
- const buffer = Buffer.alloc(8192);
9074
- const fd = __require("fs").openSync(filePath, "r");
9075
- const bytesRead = __require("fs").readSync(fd, buffer, 0, 8192, 0);
9076
- __require("fs").closeSync(fd);
9077
- const chunk = buffer.slice(0, bytesRead);
9078
- return chunk.includes(0);
9079
- } catch {
9080
- 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";
9081
9057
  }
9058
+ return result.trim();
9082
9059
  }
9083
9060
  var grepSearchTool = {
9084
9061
  name: "grep_search",
@@ -9087,8 +9064,8 @@ var grepSearchTool = {
9087
9064
  const params = value;
9088
9065
  context.logger.info("\u{1F50D} GrepSearch: Searching files", {
9089
9066
  pattern: params.pattern,
9090
- filePattern: params.filePattern || "**/*",
9091
- cwd: params.cwd || "."
9067
+ dir_path: params.dir_path || ".",
9068
+ include: params.include || "**/*"
9092
9069
  });
9093
9070
  try {
9094
9071
  const result = await searchFiles2(params);
@@ -9101,33 +9078,21 @@ var grepSearchTool = {
9101
9078
  },
9102
9079
  toolSchema: {
9103
9080
  name: "grep_search",
9104
- 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.",
9105
9082
  parameters: {
9106
9083
  type: "object",
9107
9084
  properties: {
9108
9085
  pattern: {
9109
9086
  type: "string",
9110
- 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+.*')."
9111
9088
  },
9112
- filePattern: {
9089
+ dir_path: {
9113
9090
  type: "string",
9114
- 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."
9115
9092
  },
9116
- cwd: {
9093
+ include: {
9117
9094
  type: "string",
9118
- description: "Directory to search in, relative to current working directory (optional). Defaults to current directory."
9119
- },
9120
- caseSensitive: {
9121
- type: "boolean",
9122
- description: "Whether the search should be case-sensitive (default: false)"
9123
- },
9124
- maxResults: {
9125
- type: "number",
9126
- description: "Maximum number of matches to return (default: 100). Prevents overwhelming output."
9127
- },
9128
- contextLines: {
9129
- type: "number",
9130
- 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)."
9131
9096
  }
9132
9097
  },
9133
9098
  required: ["pattern"]
@@ -9138,7 +9103,7 @@ var grepSearchTool = {
9138
9103
 
9139
9104
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/deleteFile/index.js
9140
9105
  import { promises as fs10 } from "fs";
9141
- import { existsSync as existsSync6, statSync as statSync7 } from "fs";
9106
+ import { existsSync as existsSync6, statSync as statSync5 } from "fs";
9142
9107
  import path11 from "path";
9143
9108
  async function deleteFile(params) {
9144
9109
  const { path: filePath, recursive = false } = params;
@@ -9151,7 +9116,7 @@ async function deleteFile(params) {
9151
9116
  if (!existsSync6(resolvedPath)) {
9152
9117
  throw new Error(`File or directory not found: ${filePath}`);
9153
9118
  }
9154
- const stats = statSync7(resolvedPath);
9119
+ const stats = statSync5(resolvedPath);
9155
9120
  const isDirectory = stats.isDirectory();
9156
9121
  const size = stats.size;
9157
9122
  if (isDirectory && !recursive) {
@@ -9172,7 +9137,7 @@ var deleteFileTool = {
9172
9137
  toolFn: async (value) => {
9173
9138
  const params = value;
9174
9139
  const resolvedPath = path11.resolve(process.cwd(), path11.normalize(params.path));
9175
- const isDirectory = existsSync6(resolvedPath) && statSync7(resolvedPath).isDirectory();
9140
+ const isDirectory = existsSync6(resolvedPath) && statSync5(resolvedPath).isDirectory();
9176
9141
  context.logger.info(`\u{1F5D1}\uFE0F DeleteFile: Deleting ${isDirectory ? "directory" : "file"}`, {
9177
9142
  path: params.path,
9178
9143
  recursive: params.recursive
@@ -9948,7 +9913,7 @@ var ToolErrorType;
9948
9913
 
9949
9914
  // src/utils/diffPreview.ts
9950
9915
  import * as Diff from "diff";
9951
- import { readFile } from "fs/promises";
9916
+ import { readFile as readFile2 } from "fs/promises";
9952
9917
  import { existsSync as existsSync8 } from "fs";
9953
9918
  async function generateFileDiffPreview(args) {
9954
9919
  try {
@@ -9963,7 +9928,7 @@ ${preview}${hasMore ? `
9963
9928
 
9964
9929
  ... (${lines2.length - 20} more lines)` : ""}`;
9965
9930
  }
9966
- const currentContent = await readFile(args.path, "utf-8");
9931
+ const currentContent = await readFile2(args.path, "utf-8");
9967
9932
  const patch = Diff.createPatch(
9968
9933
  args.path,
9969
9934
  currentContent,
@@ -10872,6 +10837,82 @@ async function loadContextFiles(projectDir) {
10872
10837
  };
10873
10838
  }
10874
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
+
10875
10916
  // src/utils/argumentSubstitution.ts
10876
10917
  function substituteArguments(template, args) {
10877
10918
  let result = template;
@@ -11787,7 +11828,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
11787
11828
  // package.json
11788
11829
  var package_default = {
11789
11830
  name: "@bike4mind/cli",
11790
- version: "0.2.12-ja-fix-slack-github-repo-matching.17428+e58dba2ec",
11831
+ version: "0.2.13",
11791
11832
  type: "module",
11792
11833
  description: "Interactive CLI tool for Bike4Mind with ReAct agents",
11793
11834
  license: "UNLICENSED",
@@ -11858,6 +11899,7 @@ var package_default = {
11858
11899
  "eventsource-parser": "^3.0.6",
11859
11900
  "file-type": "^18.7.0",
11860
11901
  "fuse.js": "^7.1.0",
11902
+ glob: "^13.0.0",
11861
11903
  "gray-matter": "^4.0.3",
11862
11904
  ink: "^6.5.1",
11863
11905
  "ink-select-input": "^6.2.0",
@@ -11891,11 +11933,11 @@ var package_default = {
11891
11933
  zustand: "^4.5.4"
11892
11934
  },
11893
11935
  devDependencies: {
11894
- "@bike4mind/agents": "0.1.0",
11895
- "@bike4mind/common": "2.41.1-ja-fix-slack-github-repo-matching.17428+e58dba2ec",
11896
- "@bike4mind/mcp": "1.21.1-ja-fix-slack-github-repo-matching.17428+e58dba2ec",
11897
- "@bike4mind/services": "2.36.1-ja-fix-slack-github-repo-matching.17428+e58dba2ec",
11898
- "@bike4mind/utils": "2.1.6-ja-fix-slack-github-repo-matching.17428+e58dba2ec",
11936
+ "@bike4mind/agents": "workspace:*",
11937
+ "@bike4mind/common": "workspace:*",
11938
+ "@bike4mind/mcp": "workspace:*",
11939
+ "@bike4mind/services": "workspace:*",
11940
+ "@bike4mind/utils": "workspace:*",
11899
11941
  "@types/better-sqlite3": "^7.6.13",
11900
11942
  "@types/diff": "^5.0.9",
11901
11943
  "@types/jsonwebtoken": "^9.0.4",
@@ -11903,12 +11945,12 @@ var package_default = {
11903
11945
  "@types/react": "^19.2.7",
11904
11946
  "@types/uuid": "^9.0.7",
11905
11947
  "@types/yargs": "^17.0.32",
11948
+ "ink-testing-library": "^4.0.0",
11906
11949
  tsup: "^8.5.1",
11907
11950
  tsx: "^4.21.0",
11908
11951
  typescript: "^5.9.3",
11909
11952
  vitest: "^3.2.4"
11910
- },
11911
- gitHead: "e58dba2ec406a51c01acee154913c40c02c8ebf2"
11953
+ }
11912
11954
  };
11913
11955
 
11914
11956
  // src/config/constants.ts
@@ -12264,6 +12306,131 @@ function createSubagentDelegateTool(orchestrator, parentSessionId) {
12264
12306
  };
12265
12307
  }
12266
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
+
12267
12434
  // src/index.tsx
12268
12435
  process.removeAllListeners("warning");
12269
12436
  process.on("warning", (warning) => {
@@ -12548,7 +12715,9 @@ function CliApp() {
12548
12715
  subagentConfigs
12549
12716
  });
12550
12717
  const subagentDelegateTool = createSubagentDelegateTool(orchestrator, newSession.id);
12551
- const allTools = [...b4mTools, ...mcpTools, subagentDelegateTool];
12718
+ const todoStore = createTodoStore();
12719
+ const writeTodosTool = createWriteTodosTool(todoStore);
12720
+ const allTools = [...b4mTools, ...mcpTools, subagentDelegateTool, writeTodosTool];
12552
12721
  console.log(`\u{1F916} Subagent delegation enabled (explore, plan, review)`);
12553
12722
  const projectDir = state.configStore.getProjectConfigDir();
12554
12723
  const contextResult = await loadContextFiles(projectDir);
@@ -12588,26 +12757,12 @@ ${contextResult.mergedContent}` : "";
12588
12757
  const lastIdx = pendingMessages.length - 1;
12589
12758
  if (lastIdx >= 0 && pendingMessages[lastIdx].role === "assistant") {
12590
12759
  const existingSteps = pendingMessages[lastIdx].metadata?.steps || [];
12591
- const MAX_INPUT_LENGTH = 500;
12592
- let truncatedStep = step;
12593
- if (step.type === "action" && step.metadata?.toolInput) {
12594
- const inputStr = typeof step.metadata.toolInput === "string" ? step.metadata.toolInput : JSON.stringify(step.metadata.toolInput);
12595
- if (inputStr.length > MAX_INPUT_LENGTH) {
12596
- const truncatedInput = inputStr.slice(0, MAX_INPUT_LENGTH) + `... (${inputStr.length - MAX_INPUT_LENGTH} more chars)`;
12597
- truncatedStep = {
12598
- ...step,
12599
- metadata: {
12600
- ...step.metadata,
12601
- toolInput: truncatedInput
12602
- }
12603
- };
12604
- }
12605
- }
12760
+ const formattedStep = formatStep(step);
12606
12761
  updatePendingMessage(lastIdx, {
12607
12762
  ...pendingMessages[lastIdx],
12608
12763
  metadata: {
12609
12764
  ...pendingMessages[lastIdx].metadata,
12610
- steps: [...existingSteps, truncatedStep]
12765
+ steps: [...existingSteps, formattedStep]
12611
12766
  }
12612
12767
  });
12613
12768
  }
@@ -12749,7 +12904,7 @@ ${contextResult.mergedContent}` : "";
12749
12904
  content: result.finalAnswer,
12750
12905
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12751
12906
  metadata: {
12752
- steps: result.steps,
12907
+ steps: result.steps.map(formatStep),
12753
12908
  tokenUsage: {
12754
12909
  prompt: 0,
12755
12910
  completion: 0,
@@ -12922,7 +13077,7 @@ ${contextResult.mergedContent}` : "";
12922
13077
  completion: 0,
12923
13078
  total: result.completionInfo.totalTokens
12924
13079
  },
12925
- steps: result.steps,
13080
+ steps: result.steps.map(formatStep),
12926
13081
  // Complete history: thoughts, actions, observations
12927
13082
  permissionDenied
12928
13083
  }
@@ -13028,7 +13183,7 @@ ${output}` : output.trim() || "(no output)",
13028
13183
  if (!imageStore) {
13029
13184
  if (!imageStoreInitPromise.current) {
13030
13185
  imageStoreInitPromise.current = (async () => {
13031
- const { ImageStore: ImageStoreClass } = await import("./ImageStore-RQZ7OF7P.js");
13186
+ const { ImageStore: ImageStoreClass } = await import("./ImageStore-MMUOUPI2.js");
13032
13187
  const newImageStore = new ImageStoreClass();
13033
13188
  setState((prev) => ({
13034
13189
  ...prev,