@bike4mind/cli 0.2.11 → 0.2.12-cli-todo-list.17408
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/{ImageStore-RQZ7OF7P.js → ImageStore-MMUOUPI2.js} +0 -1
- package/dist/{artifactExtractor-F2VLARCK.js → artifactExtractor-URGQJDMT.js} +0 -1
- package/dist/{chunk-RJFNIMVH.js → chunk-7N7AZPLK.js} +1 -1
- package/dist/chunk-BDQBOLYG.js +0 -0
- package/dist/chunk-BPFEGDC7.js +0 -0
- package/dist/{chunk-BU5TP3BH.js → chunk-INYINU6X.js} +1 -1
- package/dist/{chunk-ID4NS6KF.js → chunk-JXROCVSM.js} +1 -1
- package/dist/{chunk-AMDXHL6S.js → chunk-OCYRD7D6.js} +1 -1
- package/dist/chunk-OHR7UCTC.js +0 -0
- package/dist/{chunk-ZHCX7BLB.js → chunk-XM52K32A.js} +1 -1
- package/dist/{create-MXBWYABO.js → create-IGUAAYLC.js} +3 -4
- package/dist/{formatConverter-I7EIUVDY.js → formatConverter-5QEJDW24.js} +0 -1
- package/dist/index.js +560 -303
- package/dist/{llmMarkdownGenerator-AVPOXVE5.js → llmMarkdownGenerator-QLIULZ4H.js} +0 -1
- package/dist/{markdownGenerator-FBXMTP3B.js → markdownGenerator-WE5PLZRI.js} +0 -1
- package/dist/{mementoService-4WLVEWPZ.js → mementoService-OGW5IEJX.js} +3 -4
- package/dist/{notificationDeduplicator-UW7D75QS.js → notificationDeduplicator-UTHJHMSZ.js} +1 -2
- package/dist/{src-YU4VN5YG.js → src-557ODV2Y.js} +2 -3
- package/dist/{src-SYE7WXBJ.js → src-7JPWO5SS.js} +0 -1
- package/dist/{subtractCredits-FGISXF2D.js → subtractCredits-QSGEUH6U.js} +3 -4
- package/dist/{utils-JPMDGUBL.js → utils-JCHWDM4Z.js} +0 -1
- package/package.json +20 -16
- package/dist/chunk-PDX44BCA.js +0 -11
package/dist/index.js
CHANGED
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
getEffectiveApiKey,
|
|
5
5
|
getOpenWeatherKey,
|
|
6
6
|
getSerperKey
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-XM52K32A.js";
|
|
8
|
+
import "./chunk-JXROCVSM.js";
|
|
9
|
+
import "./chunk-7N7AZPLK.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-
|
|
18
|
+
} from "./chunk-INYINU6X.js";
|
|
19
19
|
import {
|
|
20
20
|
Logger
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-OCYRD7D6.js";
|
|
22
22
|
import "./chunk-BPFEGDC7.js";
|
|
23
23
|
import {
|
|
24
24
|
AiEvents,
|
|
@@ -74,9 +74,6 @@ import {
|
|
|
74
74
|
b4mLLMTools,
|
|
75
75
|
getMcpProviderMetadata
|
|
76
76
|
} from "./chunk-OHR7UCTC.js";
|
|
77
|
-
import {
|
|
78
|
-
__require
|
|
79
|
-
} from "./chunk-PDX44BCA.js";
|
|
80
77
|
|
|
81
78
|
// src/index.tsx
|
|
82
79
|
import React17, { useState as useState8, useEffect as useEffect3, useCallback, useRef as useRef2 } from "react";
|
|
@@ -182,11 +179,11 @@ function CustomTextInput({
|
|
|
182
179
|
setCursorOffset(Math.min(value.length, cursorOffset + 1));
|
|
183
180
|
return;
|
|
184
181
|
}
|
|
185
|
-
if (!key.ctrl && !key.meta && input.length
|
|
182
|
+
if (!key.ctrl && !key.meta && input.length > 0) {
|
|
186
183
|
const sanitizedInput = input === "\r" ? "\n" : input;
|
|
187
184
|
const newValue = value.slice(0, cursorOffset) + sanitizedInput + value.slice(cursorOffset);
|
|
188
185
|
onChange(newValue);
|
|
189
|
-
setCursorOffset(cursorOffset +
|
|
186
|
+
setCursorOffset(cursorOffset + sanitizedInput.length);
|
|
190
187
|
}
|
|
191
188
|
},
|
|
192
189
|
{ isActive: !disabled }
|
|
@@ -713,7 +710,7 @@ var ImageInputDetector = class {
|
|
|
713
710
|
if (!this.looksLikeFilePath(input)) return null;
|
|
714
711
|
let filepath = input.trim();
|
|
715
712
|
filepath = filepath.replace(/^["']|["']$/g, "");
|
|
716
|
-
filepath = filepath.replace(
|
|
713
|
+
filepath = filepath.replace(/\\(.)/g, "$1");
|
|
717
714
|
if (!existsSync(filepath)) return null;
|
|
718
715
|
const stats = statSync2(filepath);
|
|
719
716
|
if (!stats.isFile()) return null;
|
|
@@ -740,7 +737,15 @@ var ImageInputDetector = class {
|
|
|
740
737
|
*/
|
|
741
738
|
static looksLikeFilePath(input) {
|
|
742
739
|
const trimmed = input.trim();
|
|
743
|
-
if (trimmed.startsWith("/")
|
|
740
|
+
if (trimmed.startsWith("/")) {
|
|
741
|
+
const hasMultipleSegments = trimmed.indexOf("/", 1) !== -1;
|
|
742
|
+
const hasImageExtension = this.SUPPORTED_EXTENSIONS.some((ext) => trimmed.toLowerCase().endsWith(ext));
|
|
743
|
+
if (hasMultipleSegments || hasImageExtension) {
|
|
744
|
+
return true;
|
|
745
|
+
}
|
|
746
|
+
return false;
|
|
747
|
+
}
|
|
748
|
+
if (trimmed.match(/^[a-zA-Z]:\\/)) {
|
|
744
749
|
return true;
|
|
745
750
|
}
|
|
746
751
|
if (trimmed.startsWith("~")) {
|
|
@@ -769,6 +774,16 @@ var ImageInputDetector = class {
|
|
|
769
774
|
};
|
|
770
775
|
|
|
771
776
|
// src/components/InputPrompt.tsx
|
|
777
|
+
function looksLikeFilePath(input) {
|
|
778
|
+
const trimmed = input.trim();
|
|
779
|
+
if (trimmed.startsWith("/")) {
|
|
780
|
+
const hasMultipleSegments = trimmed.indexOf("/", 1) !== -1;
|
|
781
|
+
if (hasMultipleSegments) return true;
|
|
782
|
+
}
|
|
783
|
+
if (trimmed.startsWith("~")) return true;
|
|
784
|
+
if (trimmed.match(/^[a-zA-Z]:\\/)) return true;
|
|
785
|
+
return false;
|
|
786
|
+
}
|
|
772
787
|
function findAtTrigger(value) {
|
|
773
788
|
for (let i = value.length - 1; i >= 0; i--) {
|
|
774
789
|
if (value[i] === "@") {
|
|
@@ -812,7 +827,7 @@ function InputPrompt({
|
|
|
812
827
|
useEffect(() => {
|
|
813
828
|
onBashModeChange?.(isBashMode);
|
|
814
829
|
}, [isBashMode, onBashModeChange]);
|
|
815
|
-
const shouldShowCommandAutocomplete = value.startsWith("/") && !disabled && !fileAutocomplete?.active;
|
|
830
|
+
const shouldShowCommandAutocomplete = value.startsWith("/") && !disabled && !fileAutocomplete?.active && !looksLikeFilePath(value);
|
|
816
831
|
const commandQuery = shouldShowCommandAutocomplete ? value.slice(1) : "";
|
|
817
832
|
const filteredCommands = useMemo(() => {
|
|
818
833
|
if (!shouldShowCommandAutocomplete) return [];
|
|
@@ -1358,7 +1373,7 @@ function isNameSuffix(value) {
|
|
|
1358
1373
|
|
|
1359
1374
|
// src/utils/processFileReferences.ts
|
|
1360
1375
|
var FILE_REFERENCE_REGEX = /(?:^|\s)@([^\s@]+)/g;
|
|
1361
|
-
function
|
|
1376
|
+
function looksLikeFilePath2(ref) {
|
|
1362
1377
|
if (ref.includes("/") || ref.includes(path2.sep)) {
|
|
1363
1378
|
return true;
|
|
1364
1379
|
}
|
|
@@ -1381,7 +1396,7 @@ function extractFileReferences(message) {
|
|
|
1381
1396
|
let match;
|
|
1382
1397
|
while ((match = FILE_REFERENCE_REGEX.exec(message)) !== null) {
|
|
1383
1398
|
const ref = match[1];
|
|
1384
|
-
if (
|
|
1399
|
+
if (looksLikeFilePath2(ref)) {
|
|
1385
1400
|
references.push(ref);
|
|
1386
1401
|
}
|
|
1387
1402
|
}
|
|
@@ -3093,6 +3108,21 @@ ${options.context}` : this.getSystemPrompt()
|
|
|
3093
3108
|
let finalAnswer = "";
|
|
3094
3109
|
let reachedMaxIterations = false;
|
|
3095
3110
|
while (iterations < maxIterations) {
|
|
3111
|
+
if (options.signal?.aborted) {
|
|
3112
|
+
this.context.logger.info("[ReActAgent] Operation aborted by user");
|
|
3113
|
+
const result2 = {
|
|
3114
|
+
finalAnswer: "Interrupted",
|
|
3115
|
+
steps: this.steps,
|
|
3116
|
+
completionInfo: {
|
|
3117
|
+
totalTokens: this.totalTokens,
|
|
3118
|
+
iterations,
|
|
3119
|
+
toolCalls: this.toolCallCount,
|
|
3120
|
+
reachedMaxIterations: false
|
|
3121
|
+
}
|
|
3122
|
+
};
|
|
3123
|
+
this.emit("complete", result2);
|
|
3124
|
+
return result2;
|
|
3125
|
+
}
|
|
3096
3126
|
iterations++;
|
|
3097
3127
|
this.context.logger.debug(`[ReActAgent] Starting iteration ${iterations}/${maxIterations}`);
|
|
3098
3128
|
let iterationComplete = false;
|
|
@@ -3106,7 +3136,8 @@ ${options.context}` : this.getSystemPrompt()
|
|
|
3106
3136
|
stream: false,
|
|
3107
3137
|
tools: this.context.tools,
|
|
3108
3138
|
maxTokens,
|
|
3109
|
-
temperature
|
|
3139
|
+
temperature,
|
|
3140
|
+
abortSignal: options.signal
|
|
3110
3141
|
},
|
|
3111
3142
|
async (texts, completionInfo) => {
|
|
3112
3143
|
for (const text of texts) {
|
|
@@ -3249,6 +3280,9 @@ ${options.context}` : this.getSystemPrompt()
|
|
|
3249
3280
|
this.emit("final_answer", finalStep);
|
|
3250
3281
|
}
|
|
3251
3282
|
}
|
|
3283
|
+
if (options.signal?.aborted && !finalAnswer) {
|
|
3284
|
+
finalAnswer = "Interrupted";
|
|
3285
|
+
}
|
|
3252
3286
|
const result = {
|
|
3253
3287
|
finalAnswer,
|
|
3254
3288
|
steps: this.steps,
|
|
@@ -3356,6 +3390,70 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
|
|
|
3356
3390
|
}
|
|
3357
3391
|
};
|
|
3358
3392
|
|
|
3393
|
+
// src/core/prompts.ts
|
|
3394
|
+
var TOOL_GREP_SEARCH = "grep_search";
|
|
3395
|
+
var TOOL_GLOB_FILES = "glob_files";
|
|
3396
|
+
var TOOL_FILE_READ = "file_read";
|
|
3397
|
+
var TOOL_EDIT_LOCAL_FILE = "edit_local_file";
|
|
3398
|
+
var TOOL_CREATE_FILE = "create_file";
|
|
3399
|
+
var TOOL_BASH_EXECUTE = "bash_execute";
|
|
3400
|
+
var TOOL_SUBAGENT_DELEGATE = "subagent_delegate";
|
|
3401
|
+
var TOOL_WRITE_TODOS = "write_todos";
|
|
3402
|
+
var EXPLORE_SUBAGENT_TYPE = "explore";
|
|
3403
|
+
function buildCoreSystemPrompt(contextSection = "") {
|
|
3404
|
+
return `You are an autonomous AI assistant with access to tools. Your job is to help users by taking action and solving problems proactively.
|
|
3405
|
+
|
|
3406
|
+
CORE BEHAVIOR:
|
|
3407
|
+
- Be proactive: Take action instead of asking for permission or clarification
|
|
3408
|
+
- Make smart assumptions: If unclear, choose the most reasonable interpretation
|
|
3409
|
+
- Use conversation history: Reference previous exchanges to understand context
|
|
3410
|
+
- Complete tasks fully: Don't just show what to do - actually do it
|
|
3411
|
+
|
|
3412
|
+
FOR SOFTWARE ENGINEERING TASKS:
|
|
3413
|
+
When requested to perform tasks like fixing bugs, adding features, refactoring, or explaining code, follow this sequence:
|
|
3414
|
+
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}'.
|
|
3415
|
+
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.
|
|
3416
|
+
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.
|
|
3417
|
+
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.
|
|
3418
|
+
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.
|
|
3419
|
+
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.
|
|
3420
|
+
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.
|
|
3421
|
+
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.
|
|
3422
|
+
|
|
3423
|
+
SUBAGENT DELEGATION:
|
|
3424
|
+
- You have access to specialized subagents via the ${TOOL_SUBAGENT_DELEGATE} tool
|
|
3425
|
+
- Use subagents for focused exploration, planning, or review tasks:
|
|
3426
|
+
* explore: Fast read-only codebase search (e.g., "find all auth files", "locate API endpoints")
|
|
3427
|
+
* plan: Break down complex tasks into actionable steps
|
|
3428
|
+
* review: Analyze code quality and identify issues
|
|
3429
|
+
- Subagents keep the main conversation clean and run faster with optimized models
|
|
3430
|
+
- Delegate when you need to search extensively or analyze code structure
|
|
3431
|
+
|
|
3432
|
+
FOR GENERAL TASKS:
|
|
3433
|
+
- Use available tools to get information (weather, web search, calculations, etc.)
|
|
3434
|
+
- When user asks follow-up questions, use conversation context to understand what they're referring to
|
|
3435
|
+
- If user asks "how about X?" after a previous question, apply the same question type to X
|
|
3436
|
+
|
|
3437
|
+
## Shell tool output token efficiency:
|
|
3438
|
+
IT IS CRITICAL TO FOLLOW THESE GUIDELINES TO AVOID EXCESSIVE TOKEN CONSUMPTION.
|
|
3439
|
+
|
|
3440
|
+
- Always prefer command flags that reduce output verbosity when using '${TOOL_BASH_EXECUTE}'.
|
|
3441
|
+
- Aim to minimize tool output tokens while still capturing necessary information.
|
|
3442
|
+
- If a command is expected to produce a lot of output, use quiet or silent flags where available and appropriate.
|
|
3443
|
+
- 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.
|
|
3444
|
+
- If a command does not have quiet/silent flags or for commands with potentially long output that may not be useful, redirect stdout and stderr to temp files in the project's temporary directory. For example: 'command > <temp_dir>/out.log 2> <temp_dir>/err.log'.
|
|
3445
|
+
- After the command runs, inspect the temp files (e.g. '<temp_dir>/out.log' and '<temp_dir>/err.log') using commands like 'grep', 'tail', 'head', ... (or platform equivalents). Remove the temp files when done.
|
|
3446
|
+
|
|
3447
|
+
EXAMPLES:
|
|
3448
|
+
- "what should I wear in Texas?" \u2192 use weather tool for Texas
|
|
3449
|
+
- "how about Japan?" \u2192 use weather tool for Japan (applying same question from context)
|
|
3450
|
+
- "enhance README" \u2192 ${TOOL_FILE_READ} \u2192 generate \u2192 ${TOOL_EDIT_LOCAL_FILE}
|
|
3451
|
+
- "what packages installed?" \u2192 ${TOOL_GLOB_FILES} "**/package.json" \u2192 ${TOOL_FILE_READ}
|
|
3452
|
+
- "find all components using React hooks" \u2192 ${TOOL_SUBAGENT_DELEGATE}(task="find all components using React hooks", type="explore")
|
|
3453
|
+
|
|
3454
|
+
Remember: Use context from previous messages to understand follow-up questions.${contextSection}`;
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3359
3457
|
// ../../b4m-core/packages/services/dist/src/referService/generateCodes.js
|
|
3360
3458
|
import { randomBytes } from "crypto";
|
|
3361
3459
|
import range from "lodash/range.js";
|
|
@@ -8707,98 +8805,80 @@ var createFileTool = {
|
|
|
8707
8805
|
};
|
|
8708
8806
|
|
|
8709
8807
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/globFiles/index.js
|
|
8710
|
-
import {
|
|
8711
|
-
import {
|
|
8808
|
+
import { glob } from "glob";
|
|
8809
|
+
import { stat } from "fs/promises";
|
|
8712
8810
|
import path9 from "path";
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
8726
|
-
|
|
8727
|
-
if (!includeHidden && entry.name.startsWith(".")) {
|
|
8728
|
-
continue;
|
|
8729
|
-
}
|
|
8730
|
-
const relativePath = basePath ? path9.join(basePath, entry.name) : entry.name;
|
|
8731
|
-
const fullPath = path9.join(dir, entry.name);
|
|
8732
|
-
if (matchGlob(relativePath, pattern)) {
|
|
8733
|
-
matches.push(relativePath);
|
|
8734
|
-
}
|
|
8735
|
-
if (entry.isDirectory() && (pattern.includes("**") || pattern.includes("/"))) {
|
|
8736
|
-
const subMatches = await walkDir(fullPath, pattern, includeHidden, maxResults - matches.length, relativePath);
|
|
8737
|
-
matches.push(...subMatches);
|
|
8738
|
-
}
|
|
8739
|
-
}
|
|
8740
|
-
} catch (error) {
|
|
8741
|
-
}
|
|
8742
|
-
return matches;
|
|
8743
|
-
}
|
|
8744
|
-
async function listFiles(params) {
|
|
8745
|
-
const { pattern, cwd: relativeCwd, includeHidden = false, maxResults = 1e3 } = params;
|
|
8811
|
+
var DEFAULT_IGNORE_PATTERNS2 = [
|
|
8812
|
+
"**/node_modules/**",
|
|
8813
|
+
"**/.git/**",
|
|
8814
|
+
"**/dist/**",
|
|
8815
|
+
"**/build/**",
|
|
8816
|
+
"**/.next/**",
|
|
8817
|
+
"**/coverage/**",
|
|
8818
|
+
"**/.turbo/**",
|
|
8819
|
+
"**/.sst/**",
|
|
8820
|
+
"**/*.min.js",
|
|
8821
|
+
"**/*.min.css"
|
|
8822
|
+
];
|
|
8823
|
+
async function findFiles(params) {
|
|
8824
|
+
const { pattern, dir_path, case_sensitive = true, respect_git_ignore = true } = params;
|
|
8746
8825
|
const baseCwd = process.cwd();
|
|
8747
|
-
const
|
|
8748
|
-
if (!
|
|
8749
|
-
throw new Error(`Access denied: Cannot
|
|
8750
|
-
}
|
|
8751
|
-
const
|
|
8826
|
+
const targetDir = dir_path ? path9.resolve(baseCwd, path9.normalize(dir_path)) : baseCwd;
|
|
8827
|
+
if (!targetDir.startsWith(baseCwd)) {
|
|
8828
|
+
throw new Error(`Access denied: Cannot search outside of current working directory`);
|
|
8829
|
+
}
|
|
8830
|
+
const ignorePatterns = respect_git_ignore ? DEFAULT_IGNORE_PATTERNS2 : [];
|
|
8831
|
+
const matches = await glob(pattern, {
|
|
8832
|
+
cwd: targetDir,
|
|
8833
|
+
dot: false,
|
|
8834
|
+
// Don't match hidden files by default
|
|
8835
|
+
ignore: ignorePatterns,
|
|
8836
|
+
absolute: true,
|
|
8837
|
+
nodir: true,
|
|
8838
|
+
// Only return files, not directories
|
|
8839
|
+
nocase: !case_sensitive,
|
|
8840
|
+
// Case-insensitive if requested
|
|
8841
|
+
maxDepth: 20
|
|
8842
|
+
// Reasonable depth limit
|
|
8843
|
+
});
|
|
8752
8844
|
if (matches.length === 0) {
|
|
8753
|
-
return `No files found matching pattern: ${pattern}`;
|
|
8845
|
+
return `No files found matching pattern: ${pattern}${dir_path ? ` in ${dir_path}` : ""}`;
|
|
8754
8846
|
}
|
|
8755
|
-
const
|
|
8756
|
-
for (const
|
|
8847
|
+
const filesWithStats = [];
|
|
8848
|
+
for (const filePath of matches) {
|
|
8757
8849
|
try {
|
|
8758
|
-
const
|
|
8759
|
-
|
|
8760
|
-
|
|
8761
|
-
|
|
8762
|
-
size: stats.size,
|
|
8763
|
-
isDirectory: stats.isDirectory(),
|
|
8764
|
-
modified: stats.mtime.toISOString()
|
|
8850
|
+
const stats = await stat(filePath);
|
|
8851
|
+
filesWithStats.push({
|
|
8852
|
+
path: filePath,
|
|
8853
|
+
mtime: stats.mtimeMs
|
|
8765
8854
|
});
|
|
8766
|
-
} catch
|
|
8855
|
+
} catch {
|
|
8767
8856
|
continue;
|
|
8768
8857
|
}
|
|
8769
8858
|
}
|
|
8770
|
-
|
|
8771
|
-
const
|
|
8772
|
-
|
|
8773
|
-
const
|
|
8774
|
-
` : ""
|
|
8775
|
-
const
|
|
8776
|
-
|
|
8777
|
-
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
return `${summary}${cwdInfo}
|
|
8859
|
+
filesWithStats.sort((a, b) => b.mtime - a.mtime);
|
|
8860
|
+
const MAX_RESULTS = 500;
|
|
8861
|
+
const truncated = filesWithStats.length > MAX_RESULTS;
|
|
8862
|
+
const results = truncated ? filesWithStats.slice(0, MAX_RESULTS) : filesWithStats;
|
|
8863
|
+
const summary = `Found ${filesWithStats.length} file(s)${truncated ? ` (showing first ${MAX_RESULTS})` : ""} matching: ${pattern}`;
|
|
8864
|
+
const dirInfo = dir_path ? `
|
|
8865
|
+
Directory: ${dir_path}` : "";
|
|
8866
|
+
const filesList = results.map((file) => path9.relative(baseCwd, file.path)).join("\n");
|
|
8867
|
+
return `${summary}${dirInfo}
|
|
8868
|
+
|
|
8781
8869
|
${filesList}`;
|
|
8782
8870
|
}
|
|
8783
|
-
function formatBytes(bytes) {
|
|
8784
|
-
if (bytes === 0)
|
|
8785
|
-
return "0 B";
|
|
8786
|
-
const k = 1024;
|
|
8787
|
-
const sizes = ["B", "KB", "MB", "GB"];
|
|
8788
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
8789
|
-
return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`;
|
|
8790
|
-
}
|
|
8791
8871
|
var globFilesTool = {
|
|
8792
8872
|
name: "glob_files",
|
|
8793
8873
|
implementation: (context) => ({
|
|
8794
8874
|
toolFn: async (value) => {
|
|
8795
8875
|
const params = value;
|
|
8796
|
-
context.logger.info("\u{1F50D} GlobFiles:
|
|
8876
|
+
context.logger.info("\u{1F50D} GlobFiles: Finding files", {
|
|
8797
8877
|
pattern: params.pattern,
|
|
8798
|
-
|
|
8878
|
+
dir_path: params.dir_path || "."
|
|
8799
8879
|
});
|
|
8800
8880
|
try {
|
|
8801
|
-
const result = await
|
|
8881
|
+
const result = await findFiles(params);
|
|
8802
8882
|
context.logger.info("\u2705 GlobFiles: Success");
|
|
8803
8883
|
return result;
|
|
8804
8884
|
} catch (error) {
|
|
@@ -8808,25 +8888,25 @@ var globFilesTool = {
|
|
|
8808
8888
|
},
|
|
8809
8889
|
toolSchema: {
|
|
8810
8890
|
name: "glob_files",
|
|
8811
|
-
description: "
|
|
8891
|
+
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.",
|
|
8812
8892
|
parameters: {
|
|
8813
8893
|
type: "object",
|
|
8814
8894
|
properties: {
|
|
8815
8895
|
pattern: {
|
|
8816
8896
|
type: "string",
|
|
8817
|
-
description: '
|
|
8897
|
+
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.'
|
|
8818
8898
|
},
|
|
8819
|
-
|
|
8899
|
+
dir_path: {
|
|
8820
8900
|
type: "string",
|
|
8821
|
-
description: "
|
|
8901
|
+
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."
|
|
8822
8902
|
},
|
|
8823
|
-
|
|
8903
|
+
case_sensitive: {
|
|
8824
8904
|
type: "boolean",
|
|
8825
|
-
description: "
|
|
8905
|
+
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."
|
|
8826
8906
|
},
|
|
8827
|
-
|
|
8828
|
-
type: "
|
|
8829
|
-
description: "
|
|
8907
|
+
respect_git_ignore: {
|
|
8908
|
+
type: "boolean",
|
|
8909
|
+
description: "Optional: Whether to respect common ignore patterns (node_modules, .git, dist, build, etc.). Defaults to true. Set to false to include all files."
|
|
8830
8910
|
}
|
|
8831
8911
|
},
|
|
8832
8912
|
required: ["pattern"]
|
|
@@ -8836,158 +8916,144 @@ var globFilesTool = {
|
|
|
8836
8916
|
};
|
|
8837
8917
|
|
|
8838
8918
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/grepSearch/index.js
|
|
8839
|
-
import {
|
|
8840
|
-
import {
|
|
8919
|
+
import { globStream } from "glob";
|
|
8920
|
+
import { readFile, stat as stat2 } from "fs/promises";
|
|
8841
8921
|
import path10 from "path";
|
|
8842
8922
|
var MAX_FILE_SIZE3 = 10 * 1024 * 1024;
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8923
|
+
var DEFAULT_IGNORE_PATTERNS3 = [
|
|
8924
|
+
"**/node_modules/**",
|
|
8925
|
+
"**/.git/**",
|
|
8926
|
+
"**/dist/**",
|
|
8927
|
+
"**/build/**",
|
|
8928
|
+
"**/.next/**",
|
|
8929
|
+
"**/coverage/**",
|
|
8930
|
+
"**/*.min.js",
|
|
8931
|
+
"**/*.min.css",
|
|
8932
|
+
"**/package-lock.json",
|
|
8933
|
+
"**/pnpm-lock.yaml",
|
|
8934
|
+
"**/yarn.lock"
|
|
8935
|
+
];
|
|
8936
|
+
async function isBinaryFile2(filePath) {
|
|
8850
8937
|
try {
|
|
8851
|
-
const
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
|
|
8855
|
-
|
|
8856
|
-
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
const fullPath = path10.join(dir, entry.name);
|
|
8861
|
-
if (matchGlob2(relativePath, pattern)) {
|
|
8862
|
-
matches.push(relativePath);
|
|
8863
|
-
}
|
|
8864
|
-
if (entry.isDirectory() && (pattern.includes("**") || pattern.includes("/"))) {
|
|
8865
|
-
const subMatches = await walkDir2(fullPath, pattern, maxResults - matches.length, relativePath);
|
|
8866
|
-
matches.push(...subMatches);
|
|
8867
|
-
}
|
|
8868
|
-
}
|
|
8869
|
-
} catch (error) {
|
|
8938
|
+
const { openSync, readSync, closeSync } = await import("fs");
|
|
8939
|
+
const buffer = Buffer.alloc(8192);
|
|
8940
|
+
const fd = openSync(filePath, "r");
|
|
8941
|
+
const bytesRead = readSync(fd, buffer, 0, 8192, 0);
|
|
8942
|
+
closeSync(fd);
|
|
8943
|
+
const chunk = buffer.slice(0, bytesRead);
|
|
8944
|
+
return chunk.includes(0);
|
|
8945
|
+
} catch {
|
|
8946
|
+
return true;
|
|
8870
8947
|
}
|
|
8871
|
-
|
|
8948
|
+
}
|
|
8949
|
+
function isPathWithinWorkspace(targetPath, baseCwd) {
|
|
8950
|
+
const resolvedTarget = path10.resolve(targetPath);
|
|
8951
|
+
const resolvedBase = path10.resolve(baseCwd);
|
|
8952
|
+
return resolvedTarget.startsWith(resolvedBase);
|
|
8872
8953
|
}
|
|
8873
8954
|
async function searchFiles2(params) {
|
|
8874
|
-
const { pattern,
|
|
8955
|
+
const { pattern, dir_path, include } = params;
|
|
8875
8956
|
const baseCwd = process.cwd();
|
|
8876
|
-
const
|
|
8877
|
-
if (!
|
|
8878
|
-
throw new Error(`
|
|
8957
|
+
const targetDir = dir_path ? path10.resolve(baseCwd, dir_path) : baseCwd;
|
|
8958
|
+
if (!isPathWithinWorkspace(targetDir, baseCwd)) {
|
|
8959
|
+
throw new Error(`Path validation failed: "${dir_path}" resolves outside the allowed workspace directory`);
|
|
8960
|
+
}
|
|
8961
|
+
try {
|
|
8962
|
+
const stats = await stat2(targetDir);
|
|
8963
|
+
if (!stats.isDirectory()) {
|
|
8964
|
+
throw new Error(`Path is not a directory: ${dir_path}`);
|
|
8965
|
+
}
|
|
8966
|
+
} catch (error) {
|
|
8967
|
+
if (error.code === "ENOENT") {
|
|
8968
|
+
throw new Error(`Path does not exist: ${dir_path}`);
|
|
8969
|
+
}
|
|
8970
|
+
throw error;
|
|
8879
8971
|
}
|
|
8880
8972
|
let searchRegex;
|
|
8881
8973
|
try {
|
|
8882
|
-
|
|
8883
|
-
searchRegex = new RegExp(pattern, flags);
|
|
8974
|
+
searchRegex = new RegExp(pattern, "i");
|
|
8884
8975
|
} catch (error) {
|
|
8885
8976
|
throw new Error(`Invalid regex pattern: ${pattern}`);
|
|
8886
8977
|
}
|
|
8887
|
-
const
|
|
8888
|
-
const
|
|
8889
|
-
|
|
8890
|
-
|
|
8978
|
+
const globPattern = include || "**/*";
|
|
8979
|
+
const filesStream = globStream(globPattern, {
|
|
8980
|
+
cwd: targetDir,
|
|
8981
|
+
dot: false,
|
|
8982
|
+
// Skip hidden files
|
|
8983
|
+
ignore: DEFAULT_IGNORE_PATTERNS3,
|
|
8984
|
+
absolute: true,
|
|
8985
|
+
nodir: true
|
|
8986
|
+
// Only match files, not directories
|
|
8987
|
+
});
|
|
8988
|
+
const allMatches = [];
|
|
8989
|
+
let filesSearched = 0;
|
|
8990
|
+
const maxMatches = 500;
|
|
8991
|
+
for await (const filePath of filesStream) {
|
|
8992
|
+
if (allMatches.length >= maxMatches) {
|
|
8993
|
+
break;
|
|
8994
|
+
}
|
|
8995
|
+
const fileAbsPath = filePath;
|
|
8891
8996
|
try {
|
|
8892
|
-
const stats =
|
|
8893
|
-
if (stats.
|
|
8997
|
+
const stats = await stat2(fileAbsPath);
|
|
8998
|
+
if (stats.size > MAX_FILE_SIZE3) {
|
|
8894
8999
|
continue;
|
|
8895
9000
|
}
|
|
8896
|
-
if (isBinaryFile2(
|
|
9001
|
+
if (await isBinaryFile2(fileAbsPath)) {
|
|
8897
9002
|
continue;
|
|
8898
9003
|
}
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
for (const file of filteredFiles) {
|
|
8910
|
-
if (matches.length >= maxResults) {
|
|
8911
|
-
break;
|
|
8912
|
-
}
|
|
8913
|
-
filesSearched++;
|
|
8914
|
-
const fullPath = path10.join(targetCwd, file);
|
|
8915
|
-
try {
|
|
8916
|
-
const content = readFileSync3(fullPath, "utf-8");
|
|
8917
|
-
const lines = content.split("\n");
|
|
8918
|
-
for (let i = 0; i < lines.length; i++) {
|
|
8919
|
-
if (matches.length >= maxResults) {
|
|
8920
|
-
break;
|
|
8921
|
-
}
|
|
8922
|
-
const line = lines[i];
|
|
8923
|
-
if (searchRegex.test(line)) {
|
|
8924
|
-
const match = {
|
|
8925
|
-
file,
|
|
8926
|
-
line: i + 1,
|
|
8927
|
-
// 1-indexed line numbers
|
|
8928
|
-
content: line.trim()
|
|
8929
|
-
};
|
|
8930
|
-
if (contextLines > 0) {
|
|
8931
|
-
const before = [];
|
|
8932
|
-
const after = [];
|
|
8933
|
-
for (let j = Math.max(0, i - contextLines); j < i; j++) {
|
|
8934
|
-
before.push(lines[j]);
|
|
8935
|
-
}
|
|
8936
|
-
for (let j = i + 1; j <= Math.min(lines.length - 1, i + contextLines); j++) {
|
|
8937
|
-
after.push(lines[j]);
|
|
8938
|
-
}
|
|
8939
|
-
match.context = { before, after };
|
|
8940
|
-
}
|
|
8941
|
-
matches.push(match);
|
|
9004
|
+
filesSearched++;
|
|
9005
|
+
const content = await readFile(fileAbsPath, "utf8");
|
|
9006
|
+
const lines = content.split(/\r?\n/);
|
|
9007
|
+
lines.forEach((line, index) => {
|
|
9008
|
+
if (allMatches.length < maxMatches && searchRegex.test(line)) {
|
|
9009
|
+
allMatches.push({
|
|
9010
|
+
filePath: path10.relative(targetDir, fileAbsPath) || path10.basename(fileAbsPath),
|
|
9011
|
+
lineNumber: index + 1,
|
|
9012
|
+
line
|
|
9013
|
+
});
|
|
8942
9014
|
}
|
|
8943
|
-
|
|
9015
|
+
});
|
|
9016
|
+
} catch (readError) {
|
|
9017
|
+
const errCode = readError.code;
|
|
9018
|
+
if (errCode !== "ENOENT" && errCode !== "EACCES") {
|
|
9019
|
+
console.debug(`Could not read ${fileAbsPath}: ${readError.message}`);
|
|
8944
9020
|
}
|
|
8945
|
-
} catch {
|
|
8946
|
-
continue;
|
|
8947
9021
|
}
|
|
8948
9022
|
}
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
9023
|
+
const searchDirDisplay = dir_path || ".";
|
|
9024
|
+
const filterInfo = include ? ` (filter: "${include}")` : "";
|
|
9025
|
+
if (allMatches.length === 0) {
|
|
9026
|
+
return `No matches found for pattern "${pattern}" in "${searchDirDisplay}"${filterInfo}.
|
|
9027
|
+
Searched ${filesSearched} file(s).`;
|
|
8952
9028
|
}
|
|
8953
|
-
const
|
|
8954
|
-
|
|
9029
|
+
const matchesByFile = allMatches.reduce((acc, match) => {
|
|
9030
|
+
if (!acc[match.filePath]) {
|
|
9031
|
+
acc[match.filePath] = [];
|
|
9032
|
+
}
|
|
9033
|
+
acc[match.filePath].push(match);
|
|
9034
|
+
acc[match.filePath].sort((a, b) => a.lineNumber - b.lineNumber);
|
|
9035
|
+
return acc;
|
|
9036
|
+
}, {});
|
|
9037
|
+
const matchCount = allMatches.length;
|
|
9038
|
+
const matchTerm = matchCount === 1 ? "match" : "matches";
|
|
9039
|
+
const truncated = matchCount >= maxMatches;
|
|
9040
|
+
let result = `Found ${matchCount} ${matchTerm}${truncated ? " (truncated)" : ""} for pattern "${pattern}" in "${searchDirDisplay}"${filterInfo}:
|
|
8955
9041
|
`;
|
|
8956
|
-
|
|
9042
|
+
result += `Searched ${filesSearched} file(s)
|
|
9043
|
+
---
|
|
8957
9044
|
`;
|
|
8958
|
-
const
|
|
8959
|
-
`
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
${match.context.before.map((l) => ` ${l}`).join("\n")}`;
|
|
8969
|
-
}
|
|
8970
|
-
if (match.context.after.length > 0) {
|
|
8971
|
-
result = `${result}
|
|
8972
|
-
Context after:
|
|
8973
|
-
${match.context.after.map((l) => ` ${l}`).join("\n")}`;
|
|
8974
|
-
}
|
|
8975
|
-
}
|
|
8976
|
-
return result;
|
|
8977
|
-
}).join("\n");
|
|
8978
|
-
return `${summary}${patternInfo}${cwdInfo}${matchesList}`;
|
|
8979
|
-
}
|
|
8980
|
-
function isBinaryFile2(filePath) {
|
|
8981
|
-
try {
|
|
8982
|
-
const buffer = Buffer.alloc(8192);
|
|
8983
|
-
const fd = __require("fs").openSync(filePath, "r");
|
|
8984
|
-
const bytesRead = __require("fs").readSync(fd, buffer, 0, 8192, 0);
|
|
8985
|
-
__require("fs").closeSync(fd);
|
|
8986
|
-
const chunk = buffer.slice(0, bytesRead);
|
|
8987
|
-
return chunk.includes(0);
|
|
8988
|
-
} catch {
|
|
8989
|
-
return true;
|
|
9045
|
+
for (const filePath in matchesByFile) {
|
|
9046
|
+
result += `File: ${filePath}
|
|
9047
|
+
`;
|
|
9048
|
+
matchesByFile[filePath].forEach((match) => {
|
|
9049
|
+
const trimmedLine = match.line.trim();
|
|
9050
|
+
const displayLine = trimmedLine.length > 200 ? trimmedLine.slice(0, 200) + "..." : trimmedLine;
|
|
9051
|
+
result += `L${match.lineNumber}: ${displayLine}
|
|
9052
|
+
`;
|
|
9053
|
+
});
|
|
9054
|
+
result += "---\n";
|
|
8990
9055
|
}
|
|
9056
|
+
return result.trim();
|
|
8991
9057
|
}
|
|
8992
9058
|
var grepSearchTool = {
|
|
8993
9059
|
name: "grep_search",
|
|
@@ -8996,8 +9062,8 @@ var grepSearchTool = {
|
|
|
8996
9062
|
const params = value;
|
|
8997
9063
|
context.logger.info("\u{1F50D} GrepSearch: Searching files", {
|
|
8998
9064
|
pattern: params.pattern,
|
|
8999
|
-
|
|
9000
|
-
|
|
9065
|
+
dir_path: params.dir_path || ".",
|
|
9066
|
+
include: params.include || "**/*"
|
|
9001
9067
|
});
|
|
9002
9068
|
try {
|
|
9003
9069
|
const result = await searchFiles2(params);
|
|
@@ -9010,33 +9076,21 @@ var grepSearchTool = {
|
|
|
9010
9076
|
},
|
|
9011
9077
|
toolSchema: {
|
|
9012
9078
|
name: "grep_search",
|
|
9013
|
-
description: "
|
|
9079
|
+
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.",
|
|
9014
9080
|
parameters: {
|
|
9015
9081
|
type: "object",
|
|
9016
9082
|
properties: {
|
|
9017
9083
|
pattern: {
|
|
9018
9084
|
type: "string",
|
|
9019
|
-
description: "
|
|
9085
|
+
description: "The regular expression (regex) pattern to search for within file contents (e.g., 'function\\s+myFunction', 'import\\s+\\{.*\\}\\s+from\\s+.*')."
|
|
9020
9086
|
},
|
|
9021
|
-
|
|
9087
|
+
dir_path: {
|
|
9022
9088
|
type: "string",
|
|
9023
|
-
description:
|
|
9089
|
+
description: "Optional: The absolute path to the directory to search within. If omitted, searches the current working directory."
|
|
9024
9090
|
},
|
|
9025
|
-
|
|
9091
|
+
include: {
|
|
9026
9092
|
type: "string",
|
|
9027
|
-
description: "
|
|
9028
|
-
},
|
|
9029
|
-
caseSensitive: {
|
|
9030
|
-
type: "boolean",
|
|
9031
|
-
description: "Whether the search should be case-sensitive (default: false)"
|
|
9032
|
-
},
|
|
9033
|
-
maxResults: {
|
|
9034
|
-
type: "number",
|
|
9035
|
-
description: "Maximum number of matches to return (default: 100). Prevents overwhelming output."
|
|
9036
|
-
},
|
|
9037
|
-
contextLines: {
|
|
9038
|
-
type: "number",
|
|
9039
|
-
description: "Number of context lines to show before and after each match (default: 0). Similar to grep -C option."
|
|
9093
|
+
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)."
|
|
9040
9094
|
}
|
|
9041
9095
|
},
|
|
9042
9096
|
required: ["pattern"]
|
|
@@ -9047,7 +9101,7 @@ var grepSearchTool = {
|
|
|
9047
9101
|
|
|
9048
9102
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/deleteFile/index.js
|
|
9049
9103
|
import { promises as fs10 } from "fs";
|
|
9050
|
-
import { existsSync as existsSync6, statSync as
|
|
9104
|
+
import { existsSync as existsSync6, statSync as statSync5 } from "fs";
|
|
9051
9105
|
import path11 from "path";
|
|
9052
9106
|
async function deleteFile(params) {
|
|
9053
9107
|
const { path: filePath, recursive = false } = params;
|
|
@@ -9060,7 +9114,7 @@ async function deleteFile(params) {
|
|
|
9060
9114
|
if (!existsSync6(resolvedPath)) {
|
|
9061
9115
|
throw new Error(`File or directory not found: ${filePath}`);
|
|
9062
9116
|
}
|
|
9063
|
-
const stats =
|
|
9117
|
+
const stats = statSync5(resolvedPath);
|
|
9064
9118
|
const isDirectory = stats.isDirectory();
|
|
9065
9119
|
const size = stats.size;
|
|
9066
9120
|
if (isDirectory && !recursive) {
|
|
@@ -9081,7 +9135,7 @@ var deleteFileTool = {
|
|
|
9081
9135
|
toolFn: async (value) => {
|
|
9082
9136
|
const params = value;
|
|
9083
9137
|
const resolvedPath = path11.resolve(process.cwd(), path11.normalize(params.path));
|
|
9084
|
-
const isDirectory = existsSync6(resolvedPath) &&
|
|
9138
|
+
const isDirectory = existsSync6(resolvedPath) && statSync5(resolvedPath).isDirectory();
|
|
9085
9139
|
context.logger.info(`\u{1F5D1}\uFE0F DeleteFile: Deleting ${isDirectory ? "directory" : "file"}`, {
|
|
9086
9140
|
path: params.path,
|
|
9087
9141
|
recursive: params.recursive
|
|
@@ -9857,7 +9911,7 @@ var ToolErrorType;
|
|
|
9857
9911
|
|
|
9858
9912
|
// src/utils/diffPreview.ts
|
|
9859
9913
|
import * as Diff from "diff";
|
|
9860
|
-
import { readFile } from "fs/promises";
|
|
9914
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
9861
9915
|
import { existsSync as existsSync8 } from "fs";
|
|
9862
9916
|
async function generateFileDiffPreview(args) {
|
|
9863
9917
|
try {
|
|
@@ -9872,7 +9926,7 @@ ${preview}${hasMore ? `
|
|
|
9872
9926
|
|
|
9873
9927
|
... (${lines2.length - 20} more lines)` : ""}`;
|
|
9874
9928
|
}
|
|
9875
|
-
const currentContent = await
|
|
9929
|
+
const currentContent = await readFile2(args.path, "utf-8");
|
|
9876
9930
|
const patch = Diff.createPatch(
|
|
9877
9931
|
args.path,
|
|
9878
9932
|
currentContent,
|
|
@@ -11248,6 +11302,10 @@ var ServerLlmBackend = class {
|
|
|
11248
11302
|
*/
|
|
11249
11303
|
async complete(model, messages, options, callback) {
|
|
11250
11304
|
logger.debug(`[ServerLlmBackend] Starting complete() with model: ${model}`);
|
|
11305
|
+
if (options.abortSignal?.aborted) {
|
|
11306
|
+
logger.debug("[ServerLlmBackend] Request aborted before start");
|
|
11307
|
+
return;
|
|
11308
|
+
}
|
|
11251
11309
|
return new Promise(async (resolve3, reject) => {
|
|
11252
11310
|
try {
|
|
11253
11311
|
logger.debug("[ServerLlmBackend] Making streaming request...");
|
|
@@ -11348,7 +11406,25 @@ var ServerLlmBackend = class {
|
|
|
11348
11406
|
}
|
|
11349
11407
|
}
|
|
11350
11408
|
});
|
|
11409
|
+
if (options.abortSignal) {
|
|
11410
|
+
const abortHandler = () => {
|
|
11411
|
+
logger.debug("[ServerLlmBackend] Abort signal received, destroying stream");
|
|
11412
|
+
response.data.destroy();
|
|
11413
|
+
resolve3();
|
|
11414
|
+
};
|
|
11415
|
+
if (options.abortSignal.aborted) {
|
|
11416
|
+
abortHandler();
|
|
11417
|
+
return;
|
|
11418
|
+
}
|
|
11419
|
+
options.abortSignal.addEventListener("abort", abortHandler, { once: true });
|
|
11420
|
+
response.data.on("close", () => {
|
|
11421
|
+
options.abortSignal?.removeEventListener("abort", abortHandler);
|
|
11422
|
+
});
|
|
11423
|
+
}
|
|
11351
11424
|
response.data.on("data", (chunk) => {
|
|
11425
|
+
if (options.abortSignal?.aborted) {
|
|
11426
|
+
return;
|
|
11427
|
+
}
|
|
11352
11428
|
parser.feed(chunk.toString());
|
|
11353
11429
|
});
|
|
11354
11430
|
response.data.on("end", () => {
|
|
@@ -11360,9 +11436,23 @@ var ServerLlmBackend = class {
|
|
|
11360
11436
|
}
|
|
11361
11437
|
});
|
|
11362
11438
|
response.data.on("error", (error) => {
|
|
11439
|
+
if (options.abortSignal?.aborted) {
|
|
11440
|
+
resolve3();
|
|
11441
|
+
return;
|
|
11442
|
+
}
|
|
11363
11443
|
reject(error);
|
|
11364
11444
|
});
|
|
11365
11445
|
} catch (error) {
|
|
11446
|
+
if (options.abortSignal?.aborted) {
|
|
11447
|
+
logger.debug("[ServerLlmBackend] Request was aborted, resolving gracefully");
|
|
11448
|
+
resolve3();
|
|
11449
|
+
return;
|
|
11450
|
+
}
|
|
11451
|
+
if (isAxiosError(error) && error.code === "ERR_CANCELED") {
|
|
11452
|
+
logger.debug("[ServerLlmBackend] Request was canceled, resolving gracefully");
|
|
11453
|
+
resolve3();
|
|
11454
|
+
return;
|
|
11455
|
+
}
|
|
11366
11456
|
logger.error("LLM completion failed", error);
|
|
11367
11457
|
if (isAxiosError(error)) {
|
|
11368
11458
|
logger.debug(
|
|
@@ -11513,8 +11603,10 @@ var ServerLlmBackend = class {
|
|
|
11513
11603
|
logger.debug(` Body: ${logger.formatBytes(bodySize)}`);
|
|
11514
11604
|
logger.debug(` Preview: ${bodyStr.substring(0, 200)}`);
|
|
11515
11605
|
const response = await axiosInstance.post(this.completionsEndpoint, requestBody, {
|
|
11516
|
-
responseType: "stream"
|
|
11606
|
+
responseType: "stream",
|
|
11517
11607
|
// Auth header is automatically injected by ApiClient interceptor
|
|
11608
|
+
// Pass abort signal to cancel request if user presses ESC
|
|
11609
|
+
signal: options.abortSignal
|
|
11518
11610
|
});
|
|
11519
11611
|
logger.debug(`\u2190 ${response.status} ${response.statusText}`);
|
|
11520
11612
|
return response;
|
|
@@ -11658,7 +11750,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
|
|
|
11658
11750
|
// package.json
|
|
11659
11751
|
var package_default = {
|
|
11660
11752
|
name: "@bike4mind/cli",
|
|
11661
|
-
version: "0.2.
|
|
11753
|
+
version: "0.2.12-cli-todo-list.17408+244ee96b6",
|
|
11662
11754
|
type: "module",
|
|
11663
11755
|
description: "Interactive CLI tool for Bike4Mind with ReAct agents",
|
|
11664
11756
|
license: "UNLICENSED",
|
|
@@ -11695,7 +11787,8 @@ var package_default = {
|
|
|
11695
11787
|
test: "vitest run",
|
|
11696
11788
|
"test:watch": "vitest",
|
|
11697
11789
|
start: "node dist/index.js",
|
|
11698
|
-
prepublishOnly: "pnpm build"
|
|
11790
|
+
prepublishOnly: "pnpm build",
|
|
11791
|
+
postinstall: `node -e "try { require('better-sqlite3') } catch(e) { if(e.message.includes('bindings')) { console.log('\\n\u26A0\uFE0F Rebuilding better-sqlite3 native bindings...'); require('child_process').execSync('pnpm rebuild better-sqlite3', {stdio:'inherit'}) } }"`
|
|
11699
11792
|
},
|
|
11700
11793
|
dependencies: {
|
|
11701
11794
|
"@anthropic-ai/sdk": "^0.22.0",
|
|
@@ -11728,6 +11821,7 @@ var package_default = {
|
|
|
11728
11821
|
"eventsource-parser": "^3.0.6",
|
|
11729
11822
|
"file-type": "^18.7.0",
|
|
11730
11823
|
"fuse.js": "^7.1.0",
|
|
11824
|
+
glob: "^13.0.0",
|
|
11731
11825
|
"gray-matter": "^4.0.3",
|
|
11732
11826
|
ink: "^6.5.1",
|
|
11733
11827
|
"ink-select-input": "^6.2.0",
|
|
@@ -11761,11 +11855,11 @@ var package_default = {
|
|
|
11761
11855
|
zustand: "^4.5.4"
|
|
11762
11856
|
},
|
|
11763
11857
|
devDependencies: {
|
|
11764
|
-
"@bike4mind/agents": "
|
|
11765
|
-
"@bike4mind/common": "
|
|
11766
|
-
"@bike4mind/mcp": "
|
|
11767
|
-
"@bike4mind/services": "
|
|
11768
|
-
"@bike4mind/utils": "
|
|
11858
|
+
"@bike4mind/agents": "0.1.0",
|
|
11859
|
+
"@bike4mind/common": "2.41.1-cli-todo-list.17408+244ee96b6",
|
|
11860
|
+
"@bike4mind/mcp": "1.21.1-cli-todo-list.17408+244ee96b6",
|
|
11861
|
+
"@bike4mind/services": "2.36.1-cli-todo-list.17408+244ee96b6",
|
|
11862
|
+
"@bike4mind/utils": "2.1.6-cli-todo-list.17408+244ee96b6",
|
|
11769
11863
|
"@types/better-sqlite3": "^7.6.13",
|
|
11770
11864
|
"@types/diff": "^5.0.9",
|
|
11771
11865
|
"@types/jsonwebtoken": "^9.0.4",
|
|
@@ -11777,7 +11871,8 @@ var package_default = {
|
|
|
11777
11871
|
tsx: "^4.21.0",
|
|
11778
11872
|
typescript: "^5.9.3",
|
|
11779
11873
|
vitest: "^3.2.4"
|
|
11780
|
-
}
|
|
11874
|
+
},
|
|
11875
|
+
gitHead: "244ee96b6f743aeff0513fc84f573050b624d4e7"
|
|
11781
11876
|
};
|
|
11782
11877
|
|
|
11783
11878
|
// src/config/constants.ts
|
|
@@ -12133,6 +12228,131 @@ function createSubagentDelegateTool(orchestrator, parentSessionId) {
|
|
|
12133
12228
|
};
|
|
12134
12229
|
}
|
|
12135
12230
|
|
|
12231
|
+
// src/tools/writeTodosTool.ts
|
|
12232
|
+
var VALID_STATUSES = ["pending", "in_progress", "completed", "cancelled"];
|
|
12233
|
+
function validateTodos(todos) {
|
|
12234
|
+
if (!Array.isArray(todos)) {
|
|
12235
|
+
throw new Error("write_todos: todos must be an array");
|
|
12236
|
+
}
|
|
12237
|
+
if (todos.length === 0) {
|
|
12238
|
+
throw new Error("write_todos: todos array cannot be empty");
|
|
12239
|
+
}
|
|
12240
|
+
let inProgressCount = 0;
|
|
12241
|
+
const validatedTodos = todos.map((item, index) => {
|
|
12242
|
+
if (!item || typeof item !== "object") {
|
|
12243
|
+
throw new Error(`write_todos: item at index ${index} must be an object`);
|
|
12244
|
+
}
|
|
12245
|
+
const { description, status } = item;
|
|
12246
|
+
if (typeof description !== "string" || description.trim() === "") {
|
|
12247
|
+
throw new Error(`write_todos: item at index ${index} must have a non-empty description`);
|
|
12248
|
+
}
|
|
12249
|
+
if (typeof status !== "string" || !VALID_STATUSES.includes(status)) {
|
|
12250
|
+
throw new Error(
|
|
12251
|
+
`write_todos: item at index ${index} has invalid status "${status}". Must be one of: ${VALID_STATUSES.join(", ")}`
|
|
12252
|
+
);
|
|
12253
|
+
}
|
|
12254
|
+
if (status === "in_progress") {
|
|
12255
|
+
inProgressCount++;
|
|
12256
|
+
}
|
|
12257
|
+
return {
|
|
12258
|
+
description: description.trim(),
|
|
12259
|
+
status
|
|
12260
|
+
};
|
|
12261
|
+
});
|
|
12262
|
+
if (inProgressCount > 1) {
|
|
12263
|
+
throw new Error(`write_todos: only one task can be 'in_progress' at a time, but found ${inProgressCount}`);
|
|
12264
|
+
}
|
|
12265
|
+
return validatedTodos;
|
|
12266
|
+
}
|
|
12267
|
+
function formatTodosOutput(todos) {
|
|
12268
|
+
const statusEmoji = {
|
|
12269
|
+
pending: "\u2B1C",
|
|
12270
|
+
in_progress: "\u{1F504}",
|
|
12271
|
+
completed: "\u2705",
|
|
12272
|
+
cancelled: "\u274C"
|
|
12273
|
+
};
|
|
12274
|
+
const lines = todos.map((todo, index) => {
|
|
12275
|
+
const emoji = statusEmoji[todo.status];
|
|
12276
|
+
return `${index + 1}. ${emoji} [${todo.status}] ${todo.description}`;
|
|
12277
|
+
});
|
|
12278
|
+
return lines.join("\n");
|
|
12279
|
+
}
|
|
12280
|
+
function createWriteTodosTool(todoStore) {
|
|
12281
|
+
return {
|
|
12282
|
+
toolFn: async (args) => {
|
|
12283
|
+
const params = args;
|
|
12284
|
+
const validatedTodos = validateTodos(params.todos);
|
|
12285
|
+
todoStore.todos = validatedTodos;
|
|
12286
|
+
if (todoStore.onUpdate) {
|
|
12287
|
+
todoStore.onUpdate(validatedTodos);
|
|
12288
|
+
}
|
|
12289
|
+
const output = formatTodosOutput(validatedTodos);
|
|
12290
|
+
return `Todo list updated successfully:
|
|
12291
|
+
|
|
12292
|
+
${output}`;
|
|
12293
|
+
},
|
|
12294
|
+
toolSchema: {
|
|
12295
|
+
name: "write_todos",
|
|
12296
|
+
description: `Create or update a list of subtasks for complex multi-step requests.
|
|
12297
|
+
|
|
12298
|
+
**When to use this tool:**
|
|
12299
|
+
- When handling complex requests that require multiple steps
|
|
12300
|
+
- To break down a large task into smaller, manageable subtasks
|
|
12301
|
+
- To track progress through a multi-step workflow
|
|
12302
|
+
|
|
12303
|
+
**Important guidelines:**
|
|
12304
|
+
- Call this tool early when receiving complex requests
|
|
12305
|
+
- Update the list immediately when starting, completing, or cancelling tasks
|
|
12306
|
+
- Only ONE task should be 'in_progress' at a time
|
|
12307
|
+
- Never batch updates - update as soon as state changes
|
|
12308
|
+
|
|
12309
|
+
**Status values:**
|
|
12310
|
+
- pending: Task not yet started
|
|
12311
|
+
- in_progress: Currently working on this task (only ONE allowed)
|
|
12312
|
+
- completed: Task finished successfully
|
|
12313
|
+
- cancelled: Task will not be completed
|
|
12314
|
+
|
|
12315
|
+
**Example:**
|
|
12316
|
+
If asked to "create a new React component with tests", break it down:
|
|
12317
|
+
1. Create component file \u2192 in_progress
|
|
12318
|
+
2. Add component logic \u2192 pending
|
|
12319
|
+
3. Create test file \u2192 pending
|
|
12320
|
+
4. Write unit tests \u2192 pending`,
|
|
12321
|
+
parameters: {
|
|
12322
|
+
type: "object",
|
|
12323
|
+
properties: {
|
|
12324
|
+
todos: {
|
|
12325
|
+
type: "array",
|
|
12326
|
+
description: "Complete list of todo items. This replaces any existing list. Each item needs a description and status.",
|
|
12327
|
+
items: {
|
|
12328
|
+
type: "object",
|
|
12329
|
+
properties: {
|
|
12330
|
+
description: {
|
|
12331
|
+
type: "string",
|
|
12332
|
+
description: "Clear, concise description of the task"
|
|
12333
|
+
},
|
|
12334
|
+
status: {
|
|
12335
|
+
type: "string",
|
|
12336
|
+
enum: ["pending", "in_progress", "completed", "cancelled"],
|
|
12337
|
+
description: "Current status of the task. Only ONE task can be 'in_progress' at a time."
|
|
12338
|
+
}
|
|
12339
|
+
},
|
|
12340
|
+
required: ["description", "status"]
|
|
12341
|
+
}
|
|
12342
|
+
}
|
|
12343
|
+
},
|
|
12344
|
+
required: ["todos"]
|
|
12345
|
+
}
|
|
12346
|
+
}
|
|
12347
|
+
};
|
|
12348
|
+
}
|
|
12349
|
+
function createTodoStore(onUpdate) {
|
|
12350
|
+
return {
|
|
12351
|
+
todos: [],
|
|
12352
|
+
onUpdate
|
|
12353
|
+
};
|
|
12354
|
+
}
|
|
12355
|
+
|
|
12136
12356
|
// src/index.tsx
|
|
12137
12357
|
process.removeAllListeners("warning");
|
|
12138
12358
|
process.on("warning", (warning) => {
|
|
@@ -12164,7 +12384,8 @@ function CliApp() {
|
|
|
12164
12384
|
trustLocationSelector: null,
|
|
12165
12385
|
rewindSelector: null,
|
|
12166
12386
|
sessionSelector: null,
|
|
12167
|
-
orchestrator: null
|
|
12387
|
+
orchestrator: null,
|
|
12388
|
+
abortController: null
|
|
12168
12389
|
});
|
|
12169
12390
|
const [isInitialized, setIsInitialized] = useState8(false);
|
|
12170
12391
|
const [initError, setInitError] = useState8(null);
|
|
@@ -12210,6 +12431,15 @@ function CliApp() {
|
|
|
12210
12431
|
}, 100);
|
|
12211
12432
|
}, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore]);
|
|
12212
12433
|
useInput6((input, key) => {
|
|
12434
|
+
if (key.escape) {
|
|
12435
|
+
if (state.abortController) {
|
|
12436
|
+
logger.debug("[ABORT] ESC pressed - aborting current operation...");
|
|
12437
|
+
state.abortController.abort();
|
|
12438
|
+
setState((prev) => ({ ...prev, abortController: null }));
|
|
12439
|
+
useCliStore.getState().setIsThinking(false);
|
|
12440
|
+
}
|
|
12441
|
+
return;
|
|
12442
|
+
}
|
|
12213
12443
|
if (key.ctrl && input === "c") {
|
|
12214
12444
|
const now = Date.now();
|
|
12215
12445
|
if (exitTimestamp && now - exitTimestamp < EXIT_TIMEOUT_MS) {
|
|
@@ -12407,7 +12637,9 @@ function CliApp() {
|
|
|
12407
12637
|
subagentConfigs
|
|
12408
12638
|
});
|
|
12409
12639
|
const subagentDelegateTool = createSubagentDelegateTool(orchestrator, newSession.id);
|
|
12410
|
-
const
|
|
12640
|
+
const todoStore = createTodoStore();
|
|
12641
|
+
const writeTodosTool = createWriteTodosTool(todoStore);
|
|
12642
|
+
const allTools = [...b4mTools, ...mcpTools, subagentDelegateTool, writeTodosTool];
|
|
12411
12643
|
console.log(`\u{1F916} Subagent delegation enabled (explore, plan, review)`);
|
|
12412
12644
|
const projectDir = state.configStore.getProjectConfigDir();
|
|
12413
12645
|
const contextResult = await loadContextFiles(projectDir);
|
|
@@ -12427,46 +12659,7 @@ function CliApp() {
|
|
|
12427
12659
|
Follow these project-specific instructions:
|
|
12428
12660
|
|
|
12429
12661
|
${contextResult.mergedContent}` : "";
|
|
12430
|
-
const cliSystemPrompt =
|
|
12431
|
-
|
|
12432
|
-
CORE BEHAVIOR:
|
|
12433
|
-
- Be proactive: Take action instead of asking for permission or clarification
|
|
12434
|
-
- Make smart assumptions: If unclear, choose the most reasonable interpretation
|
|
12435
|
-
- Use conversation history: Reference previous exchanges to understand context
|
|
12436
|
-
- Complete tasks fully: Don't just show what to do - actually do it
|
|
12437
|
-
|
|
12438
|
-
FOR CODING TASKS:
|
|
12439
|
-
- You have file system access (file_read, create_file, glob_files, grep_search, delete_file)
|
|
12440
|
-
- When user says "enhance", "update", "fix", "improve" a file:
|
|
12441
|
-
* Use file_read to get current content
|
|
12442
|
-
* Generate the improved version
|
|
12443
|
-
* Use create_file to write it back
|
|
12444
|
-
* Don't just show suggestions - make the changes
|
|
12445
|
-
- When searching for code: Use grep_search or glob_files to find relevant files
|
|
12446
|
-
- Permission system will ask for approval before any file writes
|
|
12447
|
-
|
|
12448
|
-
SUBAGENT DELEGATION:
|
|
12449
|
-
- You have access to specialized subagents via the subagent_delegate tool
|
|
12450
|
-
- Use subagents for focused exploration, planning, or review tasks:
|
|
12451
|
-
* explore: Fast read-only codebase search (e.g., "find all auth files", "locate API endpoints")
|
|
12452
|
-
* plan: Break down complex tasks into actionable steps
|
|
12453
|
-
* review: Analyze code quality and identify issues
|
|
12454
|
-
- Subagents keep the main conversation clean and run faster with optimized models
|
|
12455
|
-
- Delegate when you need to search extensively or analyze code structure
|
|
12456
|
-
|
|
12457
|
-
FOR GENERAL TASKS:
|
|
12458
|
-
- Use available tools to get information (weather, web search, calculations, etc.)
|
|
12459
|
-
- When user asks follow-up questions, use conversation context to understand what they're referring to
|
|
12460
|
-
- If user asks "how about X?" after a previous question, apply the same question type to X
|
|
12461
|
-
|
|
12462
|
-
EXAMPLES:
|
|
12463
|
-
- "what should I wear in Texas?" \u2192 use weather tool for Texas
|
|
12464
|
-
- "how about Japan?" \u2192 use weather tool for Japan (applying same question from context)
|
|
12465
|
-
- "enhance README" \u2192 file_read \u2192 generate \u2192 create_file
|
|
12466
|
-
- "what packages installed?" \u2192 glob_files "**/package.json" \u2192 file_read
|
|
12467
|
-
- "find all components using React hooks" \u2192 subagent_delegate(task="find all components using React hooks", type="explore")
|
|
12468
|
-
|
|
12469
|
-
Remember: Use context from previous messages to understand follow-up questions.${contextSection}`;
|
|
12662
|
+
const cliSystemPrompt = buildCoreSystemPrompt(contextSection);
|
|
12470
12663
|
const maxIterations = config.preferences.maxIterations === null ? 999999 : config.preferences.maxIterations;
|
|
12471
12664
|
const agent = new ReActAgent({
|
|
12472
12665
|
userId: config.userId,
|
|
@@ -12511,13 +12704,16 @@ Remember: Use context from previous messages to understand follow-up questions.$
|
|
|
12511
12704
|
}
|
|
12512
12705
|
};
|
|
12513
12706
|
agent.on("thought", stepHandler);
|
|
12707
|
+
agent.on("observation", stepHandler);
|
|
12514
12708
|
agent.on("action", stepHandler);
|
|
12515
12709
|
orchestrator.setBeforeRunCallback((subagent, _subagentType) => {
|
|
12516
12710
|
subagent.on("thought", stepHandler);
|
|
12711
|
+
subagent.on("observation", stepHandler);
|
|
12517
12712
|
subagent.on("action", stepHandler);
|
|
12518
12713
|
});
|
|
12519
12714
|
orchestrator.setAfterRunCallback((subagent, _subagentType) => {
|
|
12520
12715
|
subagent.off("thought", stepHandler);
|
|
12716
|
+
subagent.off("observation", stepHandler);
|
|
12521
12717
|
subagent.off("action", stepHandler);
|
|
12522
12718
|
});
|
|
12523
12719
|
setState((prev) => ({
|
|
@@ -12564,6 +12760,8 @@ Remember: Use context from previous messages to understand follow-up questions.$
|
|
|
12564
12760
|
return;
|
|
12565
12761
|
}
|
|
12566
12762
|
useCliStore.getState().setIsThinking(true);
|
|
12763
|
+
const abortController = new AbortController();
|
|
12764
|
+
setState((prev) => ({ ...prev, abortController }));
|
|
12567
12765
|
const currentSteps = [];
|
|
12568
12766
|
const stepHandler = (step) => {
|
|
12569
12767
|
currentSteps.push(step);
|
|
@@ -12623,7 +12821,8 @@ Remember: Use context from previous messages to understand follow-up questions.$
|
|
|
12623
12821
|
content: msg.content
|
|
12624
12822
|
}));
|
|
12625
12823
|
const result = await state.agent.run(messageContent, {
|
|
12626
|
-
previousMessages: previousMessages.length > 0 ? previousMessages : void 0
|
|
12824
|
+
previousMessages: previousMessages.length > 0 ? previousMessages : void 0,
|
|
12825
|
+
signal: abortController.signal
|
|
12627
12826
|
});
|
|
12628
12827
|
const permissionDenied = result.finalAnswer.startsWith("Permission denied for tool");
|
|
12629
12828
|
if (permissionDenied) {
|
|
@@ -12668,6 +12867,33 @@ Remember: Use context from previous messages to understand follow-up questions.$
|
|
|
12668
12867
|
useCliStore.getState().setIsThinking(false);
|
|
12669
12868
|
} catch (error) {
|
|
12670
12869
|
useCliStore.getState().setIsThinking(false);
|
|
12870
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
12871
|
+
logger.debug("[ABORT] Custom command aborted by user");
|
|
12872
|
+
const currentSession2 = useCliStore.getState().session;
|
|
12873
|
+
if (currentSession2) {
|
|
12874
|
+
const messages2 = [...currentSession2.messages];
|
|
12875
|
+
const lastMessage2 = messages2[messages2.length - 1];
|
|
12876
|
+
if (lastMessage2 && lastMessage2.role === "assistant") {
|
|
12877
|
+
messages2[messages2.length - 1] = {
|
|
12878
|
+
...lastMessage2,
|
|
12879
|
+
content: "\u26A0\uFE0F Operation cancelled by user",
|
|
12880
|
+
metadata: {
|
|
12881
|
+
...lastMessage2.metadata,
|
|
12882
|
+
cancelled: true
|
|
12883
|
+
}
|
|
12884
|
+
};
|
|
12885
|
+
}
|
|
12886
|
+
const sessionWithCancel = {
|
|
12887
|
+
...currentSession2,
|
|
12888
|
+
messages: messages2,
|
|
12889
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12890
|
+
};
|
|
12891
|
+
setState((prev) => ({ ...prev, session: sessionWithCancel }));
|
|
12892
|
+
setStoreSession(sessionWithCancel);
|
|
12893
|
+
await state.sessionStore.save(sessionWithCancel);
|
|
12894
|
+
}
|
|
12895
|
+
return;
|
|
12896
|
+
}
|
|
12671
12897
|
if (error?.message?.includes("Permission denied")) {
|
|
12672
12898
|
console.log("\n\u26A0\uFE0F Action blocked by permission settings");
|
|
12673
12899
|
const currentSession2 = useCliStore.getState().session;
|
|
@@ -12708,6 +12934,7 @@ Remember: Use context from previous messages to understand follow-up questions.$
|
|
|
12708
12934
|
setState((prev) => ({ ...prev, session: sessionWithError }));
|
|
12709
12935
|
setStoreSession(sessionWithError);
|
|
12710
12936
|
} finally {
|
|
12937
|
+
setState((prev) => ({ ...prev, abortController: null }));
|
|
12711
12938
|
state.agent.off("thought", stepHandler);
|
|
12712
12939
|
state.agent.off("action", stepHandler);
|
|
12713
12940
|
}
|
|
@@ -12727,6 +12954,8 @@ Remember: Use context from previous messages to understand follow-up questions.$
|
|
|
12727
12954
|
return;
|
|
12728
12955
|
}
|
|
12729
12956
|
useCliStore.getState().setIsThinking(true);
|
|
12957
|
+
const abortController = new AbortController();
|
|
12958
|
+
setState((prev) => ({ ...prev, abortController }));
|
|
12730
12959
|
try {
|
|
12731
12960
|
let messageContent = message;
|
|
12732
12961
|
let userMessageContent = message;
|
|
@@ -12764,7 +12993,8 @@ Remember: Use context from previous messages to understand follow-up questions.$
|
|
|
12764
12993
|
content: msg.content
|
|
12765
12994
|
}));
|
|
12766
12995
|
const result = await state.agent.run(messageContent, {
|
|
12767
|
-
previousMessages: previousMessages.length > 0 ? previousMessages : void 0
|
|
12996
|
+
previousMessages: previousMessages.length > 0 ? previousMessages : void 0,
|
|
12997
|
+
signal: abortController.signal
|
|
12768
12998
|
});
|
|
12769
12999
|
const permissionDenied = result.finalAnswer.startsWith("Permission denied for tool");
|
|
12770
13000
|
if (permissionDenied) {
|
|
@@ -12804,6 +13034,30 @@ Remember: Use context from previous messages to understand follow-up questions.$
|
|
|
12804
13034
|
await state.sessionStore.save(updatedSession);
|
|
12805
13035
|
} catch (error) {
|
|
12806
13036
|
useCliStore.getState().clearPendingMessages();
|
|
13037
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
13038
|
+
logger.debug("[ABORT] Operation aborted by user");
|
|
13039
|
+
const currentSession = useCliStore.getState().session;
|
|
13040
|
+
if (currentSession) {
|
|
13041
|
+
const cancelMessage = {
|
|
13042
|
+
id: uuidv410(),
|
|
13043
|
+
role: "assistant",
|
|
13044
|
+
content: "\u26A0\uFE0F Operation cancelled by user",
|
|
13045
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13046
|
+
metadata: {
|
|
13047
|
+
cancelled: true
|
|
13048
|
+
}
|
|
13049
|
+
};
|
|
13050
|
+
const sessionWithCancel = {
|
|
13051
|
+
...currentSession,
|
|
13052
|
+
messages: [...currentSession.messages, cancelMessage],
|
|
13053
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13054
|
+
};
|
|
13055
|
+
setState((prev) => ({ ...prev, session: sessionWithCancel }));
|
|
13056
|
+
setStoreSession(sessionWithCancel);
|
|
13057
|
+
await state.sessionStore.save(sessionWithCancel);
|
|
13058
|
+
}
|
|
13059
|
+
return;
|
|
13060
|
+
}
|
|
12807
13061
|
if (error instanceof Error) {
|
|
12808
13062
|
if (error.message.includes("Authentication failed") || error.message.includes("Authentication expired")) {
|
|
12809
13063
|
console.log("\n\u274C Authentication failed");
|
|
@@ -12812,6 +13066,9 @@ Remember: Use context from previous messages to understand follow-up questions.$
|
|
|
12812
13066
|
}
|
|
12813
13067
|
}
|
|
12814
13068
|
console.error("Error processing message:", error);
|
|
13069
|
+
} finally {
|
|
13070
|
+
setState((prev) => ({ ...prev, abortController: null }));
|
|
13071
|
+
useCliStore.getState().setIsThinking(false);
|
|
12815
13072
|
}
|
|
12816
13073
|
};
|
|
12817
13074
|
const handleBashCommand = useCallback(
|
|
@@ -12862,7 +13119,7 @@ ${output}` : output.trim() || "(no output)",
|
|
|
12862
13119
|
if (!imageStore) {
|
|
12863
13120
|
if (!imageStoreInitPromise.current) {
|
|
12864
13121
|
imageStoreInitPromise.current = (async () => {
|
|
12865
|
-
const { ImageStore: ImageStoreClass } = await import("./ImageStore-
|
|
13122
|
+
const { ImageStore: ImageStoreClass } = await import("./ImageStore-MMUOUPI2.js");
|
|
12866
13123
|
const newImageStore = new ImageStoreClass();
|
|
12867
13124
|
setState((prev) => ({
|
|
12868
13125
|
...prev,
|