@bike4mind/cli 0.2.10-feat-cli-multi-directory-workspace.17245 → 0.2.10-feat-explore-subagent-implementation.17246

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -91,13 +91,8 @@ import { Box as Box10, Text as Text10, Static } from "ink";
91
91
  // src/components/StatusBar.tsx
92
92
  import React from "react";
93
93
  import { Box, Text } from "ink";
94
- var StatusBar = React.memo(function StatusBar2({
95
- sessionName,
96
- model,
97
- tokenUsage,
98
- workspaceCount
99
- }) {
100
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "row", justifyContent: "space-between", width: "100%", paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, sessionName), /* @__PURE__ */ React.createElement(Box, { gap: 2 }, workspaceCount && workspaceCount > 1 && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u{1F4C1} ", workspaceCount, " dirs"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, tokenUsage.toLocaleString(), " tokens"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, model)));
94
+ var StatusBar = React.memo(function StatusBar2({ sessionName, model, tokenUsage }) {
95
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "row", justifyContent: "space-between", width: "100%", paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, sessionName), /* @__PURE__ */ React.createElement(Box, { gap: 2 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, tokenUsage.toLocaleString(), " tokens"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, model)));
101
96
  });
102
97
 
103
98
  // src/components/InputPrompt.tsx
@@ -1430,7 +1425,6 @@ function App({
1430
1425
  const sessionName = useCliStore((state) => state.session?.name || "New Session");
1431
1426
  const currentModel = useCliStore((state) => state.session?.model || "claude-sonnet-4-5-20250929");
1432
1427
  const totalTokens = useCliStore((state) => state.session?.metadata.totalTokens || 0);
1433
- const workspaceCount = useCliStore((state) => state.session?.workingDirectories?.length || 1);
1434
1428
  const isThinking = useCliStore((state) => state.isThinking);
1435
1429
  const permissionPrompt = useCliStore((state) => state.permissionPrompt);
1436
1430
  const showConfigEditor = useCliStore((state) => state.showConfigEditor);
@@ -1496,15 +1490,7 @@ ${errorBlock}`;
1496
1490
  onPrefillConsumed,
1497
1491
  onBashModeChange: setIsBashMode
1498
1492
  }
1499
- )), /* @__PURE__ */ React11.createElement(
1500
- StatusBar,
1501
- {
1502
- sessionName,
1503
- model: currentModel,
1504
- tokenUsage: totalTokens,
1505
- workspaceCount
1506
- }
1507
- )));
1493
+ )), /* @__PURE__ */ React11.createElement(StatusBar, { sessionName, model: currentModel, tokenUsage: totalTokens })));
1508
1494
  }
1509
1495
 
1510
1496
  // src/components/MessageList.tsx
@@ -5165,8 +5151,8 @@ async function processAndStoreImages(images, context) {
5165
5151
  const buffer = await downloadImage(image);
5166
5152
  const fileType = await fileTypeFromBuffer2(buffer);
5167
5153
  const filename = `${uuidv45()}.${fileType?.ext}`;
5168
- const path17 = await context.imageGenerateStorage.upload(buffer, filename, {});
5169
- return path17;
5154
+ const path15 = await context.imageGenerateStorage.upload(buffer, filename, {});
5155
+ return path15;
5170
5156
  }));
5171
5157
  }
5172
5158
  async function updateQuestAndReturnMarkdown(storedImageUrls, context) {
@@ -6378,8 +6364,8 @@ async function processAndStoreImage(imageUrl, context) {
6378
6364
  const buffer = await downloadImage2(imageUrl);
6379
6365
  const fileType = await fileTypeFromBuffer3(buffer);
6380
6366
  const filename = `${uuidv46()}.${fileType?.ext}`;
6381
- const path17 = await context.imageGenerateStorage.upload(buffer, filename, {});
6382
- return path17;
6367
+ const path15 = await context.imageGenerateStorage.upload(buffer, filename, {});
6368
+ return path15;
6383
6369
  }
6384
6370
  async function updateQuestAndReturnMarkdown2(storedImagePath, context) {
6385
6371
  await context.onFinish?.("edit_image", storedImagePath);
@@ -8283,7 +8269,8 @@ var fileReadTool = {
8283
8269
  return content;
8284
8270
  } catch (error) {
8285
8271
  context.logger.error("\u274C FileRead: Failed", error);
8286
- throw error;
8272
+ const errorMessage = error instanceof Error ? error.message : String(error);
8273
+ return `Error reading file: ${errorMessage}`;
8287
8274
  }
8288
8275
  },
8289
8276
  toolSchema: {
@@ -9470,7 +9457,7 @@ async function generateFileDeletePreview(args) {
9470
9457
  if (!existsSync7(args.path)) {
9471
9458
  return `[File does not exist: ${args.path}]`;
9472
9459
  }
9473
- const stats = await import("fs/promises").then((fs14) => fs14.stat(args.path));
9460
+ const stats = await import("fs/promises").then((fs12) => fs12.stat(args.path));
9474
9461
  return `[File will be deleted]
9475
9462
 
9476
9463
  Path: ${args.path}
@@ -9815,330 +9802,26 @@ async function executeTool(toolName, input, apiClient, localToolFn) {
9815
9802
  }
9816
9803
  }
9817
9804
 
9818
- // src/tools/workspaceAwareFileTools.ts
9819
- import path13 from "path";
9820
- import fs11 from "fs/promises";
9821
- import { existsSync as existsSync8 } from "fs";
9822
- function validatePathInWorkspace(filePath, workspaceManager) {
9823
- try {
9824
- return workspaceManager.resolvePath(filePath);
9825
- } catch (error) {
9826
- throw error;
9827
- }
9828
- }
9829
- function createWorkspaceAwareFileRead(workspaceManager) {
9830
- return {
9831
- toolSchema: {
9832
- name: "file_read",
9833
- description: "Read contents of a file from the workspace. Returns file contents as a string.",
9834
- parameters: {
9835
- type: "object",
9836
- properties: {
9837
- path: {
9838
- type: "string",
9839
- description: "Path to the file to read (relative to workspace or absolute)"
9840
- }
9841
- },
9842
- required: ["path"]
9843
- }
9844
- },
9845
- toolFn: async (parameters) => {
9846
- const args = parameters;
9847
- const { path: filePath } = args;
9848
- const resolvedPath = validatePathInWorkspace(filePath, workspaceManager);
9849
- if (!existsSync8(resolvedPath)) {
9850
- throw new Error(`File not found: ${filePath}`);
9851
- }
9852
- const content = await fs11.readFile(resolvedPath, "utf-8");
9853
- return content;
9854
- }
9855
- };
9856
- }
9857
- function createWorkspaceAwareCreateFile(workspaceManager) {
9858
- return {
9859
- toolSchema: {
9860
- name: "create_file",
9861
- description: "Create a new file with the given content in the workspace.",
9862
- parameters: {
9863
- type: "object",
9864
- properties: {
9865
- path: {
9866
- type: "string",
9867
- description: "Path where the file should be created (relative to workspace or absolute)"
9868
- },
9869
- content: {
9870
- type: "string",
9871
- description: "Content to write to the file"
9872
- }
9873
- },
9874
- required: ["path", "content"]
9875
- }
9876
- },
9877
- toolFn: async (parameters) => {
9878
- const args = parameters;
9879
- const { path: filePath, content } = args;
9880
- const resolvedPath = validatePathInWorkspace(filePath, workspaceManager);
9881
- if (existsSync8(resolvedPath)) {
9882
- throw new Error(`File already exists: ${filePath}. Use edit_file to modify existing files.`);
9883
- }
9884
- const parentDir = path13.dirname(resolvedPath);
9885
- await fs11.mkdir(parentDir, { recursive: true });
9886
- await fs11.writeFile(resolvedPath, content, "utf-8");
9887
- return `File created successfully: ${filePath}`;
9888
- }
9889
- };
9890
- }
9891
- function createWorkspaceAwareEditFile(workspaceManager) {
9892
- return {
9893
- toolSchema: {
9894
- name: "edit_file",
9895
- description: "Edit an existing file by replacing its entire content.",
9896
- parameters: {
9897
- type: "object",
9898
- properties: {
9899
- path: {
9900
- type: "string",
9901
- description: "Path to the file to edit (relative to workspace or absolute)"
9902
- },
9903
- content: {
9904
- type: "string",
9905
- description: "New content for the file"
9906
- }
9907
- },
9908
- required: ["path", "content"]
9909
- }
9910
- },
9911
- toolFn: async (parameters) => {
9912
- const args = parameters;
9913
- const { path: filePath, content } = args;
9914
- const resolvedPath = validatePathInWorkspace(filePath, workspaceManager);
9915
- if (!existsSync8(resolvedPath)) {
9916
- throw new Error(`File not found: ${filePath}. Use create_file to create new files.`);
9917
- }
9918
- await fs11.writeFile(resolvedPath, content, "utf-8");
9919
- return `File edited successfully: ${filePath}`;
9920
- }
9921
- };
9922
- }
9923
- function createWorkspaceAwareDeleteFile(workspaceManager) {
9924
- return {
9925
- toolSchema: {
9926
- name: "delete_file",
9927
- description: "Delete a file from the workspace.",
9928
- parameters: {
9929
- type: "object",
9930
- properties: {
9931
- path: {
9932
- type: "string",
9933
- description: "Path to the file to delete (relative to workspace or absolute)"
9934
- }
9935
- },
9936
- required: ["path"]
9937
- }
9938
- },
9939
- toolFn: async (parameters) => {
9940
- const args = parameters;
9941
- const { path: filePath } = args;
9942
- const resolvedPath = validatePathInWorkspace(filePath, workspaceManager);
9943
- if (!existsSync8(resolvedPath)) {
9944
- throw new Error(`File not found: ${filePath}`);
9945
- }
9946
- await fs11.unlink(resolvedPath);
9947
- return `File deleted successfully: ${filePath}`;
9948
- }
9949
- };
9950
- }
9951
- function createWorkspaceAwareGlobFiles(workspaceManager) {
9952
- return {
9953
- toolSchema: {
9954
- name: "glob_files",
9955
- description: "Search for files matching a glob pattern in the workspace.",
9956
- parameters: {
9957
- type: "object",
9958
- properties: {
9959
- pattern: {
9960
- type: "string",
9961
- description: 'Glob pattern to match files (e.g., "**/*.ts", "src/**/*.js")'
9962
- },
9963
- cwd: {
9964
- type: "string",
9965
- description: "Directory to search from (relative to primary workspace or absolute)"
9966
- }
9967
- },
9968
- required: ["pattern"]
9969
- }
9970
- },
9971
- toolFn: async (parameters) => {
9972
- const args = parameters;
9973
- const { pattern, cwd: relativeCwd } = args;
9974
- const primaryWorkspace = workspaceManager.getPrimaryWorkspace();
9975
- let searchDir = primaryWorkspace;
9976
- if (relativeCwd) {
9977
- searchDir = validatePathInWorkspace(relativeCwd, workspaceManager);
9978
- }
9979
- const glob = (await import("fast-glob")).default;
9980
- const files = await glob(pattern, {
9981
- cwd: searchDir,
9982
- absolute: false,
9983
- onlyFiles: true
9984
- });
9985
- const result = {
9986
- pattern,
9987
- cwd: searchDir,
9988
- files: files.map((f) => path13.join(searchDir, f)),
9989
- count: files.length
9990
- };
9991
- return JSON.stringify(result);
9992
- }
9993
- };
9994
- }
9995
- function createWorkspaceAwareGrepSearch(workspaceManager) {
9996
- return {
9997
- toolSchema: {
9998
- name: "grep_search",
9999
- description: "Search for text patterns in files within the workspace using grep.",
10000
- parameters: {
10001
- type: "object",
10002
- properties: {
10003
- pattern: {
10004
- type: "string",
10005
- description: "Regular expression pattern to search for"
10006
- },
10007
- path: {
10008
- type: "string",
10009
- description: "Directory or file to search (relative to primary workspace or absolute)"
10010
- },
10011
- filePattern: {
10012
- type: "string",
10013
- description: 'File pattern to filter files (e.g., "*.ts", "*.js")'
10014
- }
10015
- },
10016
- required: ["pattern"]
10017
- }
10018
- },
10019
- toolFn: async (parameters) => {
10020
- const args = parameters;
10021
- const { pattern, path: searchPath, filePattern } = args;
10022
- const primaryWorkspace = workspaceManager.getPrimaryWorkspace();
10023
- let searchDir = primaryWorkspace;
10024
- if (searchPath) {
10025
- searchDir = validatePathInWorkspace(searchPath, workspaceManager);
10026
- }
10027
- const glob = (await import("fast-glob")).default;
10028
- const globPattern = filePattern || "**/*";
10029
- const files = await glob(globPattern, {
10030
- cwd: searchDir,
10031
- absolute: true,
10032
- onlyFiles: true
10033
- });
10034
- const matches = [];
10035
- const regex = new RegExp(pattern, "gi");
10036
- for (const file of files) {
10037
- try {
10038
- const content = await fs11.readFile(file, "utf-8");
10039
- const lines = content.split("\n");
10040
- lines.forEach((line, index) => {
10041
- if (regex.test(line)) {
10042
- matches.push({
10043
- file: path13.relative(primaryWorkspace, file),
10044
- line: index + 1,
10045
- content: line.trim()
10046
- });
10047
- }
10048
- });
10049
- } catch (error) {
10050
- }
10051
- }
10052
- const result = {
10053
- pattern,
10054
- searchDir,
10055
- matches,
10056
- totalMatches: matches.length
10057
- };
10058
- return JSON.stringify(result);
10059
- }
10060
- };
10061
- }
10062
- function createWorkspaceAwareBashExecute(workspaceManager) {
10063
- return {
10064
- toolSchema: {
10065
- name: "bash_execute",
10066
- description: "Execute a bash command in the workspace. Use with caution.",
10067
- parameters: {
10068
- type: "object",
10069
- properties: {
10070
- command: {
10071
- type: "string",
10072
- description: "Bash command to execute"
10073
- },
10074
- cwd: {
10075
- type: "string",
10076
- description: "Working directory for command execution (relative to primary workspace or absolute)"
10077
- },
10078
- timeout: {
10079
- type: "number",
10080
- description: "Timeout in milliseconds (default: 30000)"
10081
- }
10082
- },
10083
- required: ["command"]
10084
- }
10085
- },
10086
- toolFn: async (parameters) => {
10087
- const args = parameters;
10088
- const { command, cwd: relativeCwd, timeout = 3e4 } = args;
10089
- const primaryWorkspace = workspaceManager.getPrimaryWorkspace();
10090
- let execDir = primaryWorkspace;
10091
- if (relativeCwd) {
10092
- execDir = path13.resolve(primaryWorkspace, relativeCwd);
10093
- }
10094
- const { execSync: execSync2 } = await import("child_process");
10095
- try {
10096
- const output = execSync2(command, {
10097
- cwd: execDir,
10098
- encoding: "utf-8",
10099
- timeout,
10100
- maxBuffer: 10 * 1024 * 1024
10101
- // 10MB
10102
- });
10103
- const result = {
10104
- success: true,
10105
- output: output.toString(),
10106
- exitCode: 0
10107
- };
10108
- return JSON.stringify(result);
10109
- } catch (error) {
10110
- const result = {
10111
- success: false,
10112
- output: error.stdout?.toString() || "",
10113
- error: error.stderr?.toString() || error.message,
10114
- exitCode: error.status || 1
10115
- };
10116
- return JSON.stringify(result);
10117
- }
10118
- }
10119
- };
10120
- }
10121
-
10122
9805
  // src/utils/toolsAdapter.ts
10123
9806
  var NoOpStorage = class extends BaseStorage {
10124
9807
  async upload(input, destination, options) {
10125
9808
  return `/tmp/${destination}`;
10126
9809
  }
10127
- async download(path17) {
9810
+ async download(path15) {
10128
9811
  throw new Error("Download not supported in CLI");
10129
9812
  }
10130
- async delete(path17) {
9813
+ async delete(path15) {
10131
9814
  }
10132
- async getSignedUrl(path17) {
10133
- return `/tmp/${path17}`;
9815
+ async getSignedUrl(path15) {
9816
+ return `/tmp/${path15}`;
10134
9817
  }
10135
- getPublicUrl(path17) {
10136
- return `/tmp/${path17}`;
9818
+ getPublicUrl(path15) {
9819
+ return `/tmp/${path15}`;
10137
9820
  }
10138
- async getPreview(path17) {
10139
- return `/tmp/${path17}`;
9821
+ async getPreview(path15) {
9822
+ return `/tmp/${path15}`;
10140
9823
  }
10141
- async getMetadata(path17) {
9824
+ async getMetadata(path15) {
10142
9825
  return { size: 0, contentType: "application/octet-stream" };
10143
9826
  }
10144
9827
  };
@@ -10232,7 +9915,31 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
10232
9915
  }
10233
9916
  };
10234
9917
  }
10235
- function generateCliTools(userId, llm, model, permissionManager, workspaceManager, showPermissionPrompt, agentContext, configStore, apiClient) {
9918
+ var TOOL_NAME_MAPPING = {
9919
+ // Claude Code -> B4M
9920
+ read: "file_read",
9921
+ write: "create_file",
9922
+ edit: "edit_file",
9923
+ delete: "delete_file",
9924
+ glob: "glob_files",
9925
+ grep: "grep_search",
9926
+ bash: "bash_execute",
9927
+ // B4M -> Claude Code (reverse mapping)
9928
+ file_read: "read",
9929
+ create_file: "write",
9930
+ edit_file: "edit",
9931
+ delete_file: "delete",
9932
+ glob_files: "glob",
9933
+ grep_search: "grep",
9934
+ bash_execute: "bash"
9935
+ };
9936
+ function normalizeToolName(toolName) {
9937
+ if (toolName.includes("_")) {
9938
+ return toolName;
9939
+ }
9940
+ return TOOL_NAME_MAPPING[toolName] || toolName;
9941
+ }
9942
+ function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter) {
10236
9943
  const logger2 = new CliLogger();
10237
9944
  const storage = new NoOpStorage();
10238
9945
  const user = {
@@ -10302,32 +10009,25 @@ function generateCliTools(userId, llm, model, permissionManager, workspaceManage
10302
10009
  toolConfig,
10303
10010
  model
10304
10011
  );
10305
- const tools2 = Object.entries(toolsMap).filter(([key]) => toolConfig[key] !== void 0).map(
10012
+ let tools2 = Object.entries(toolsMap).filter(([key]) => toolConfig[key] !== void 0).map(
10306
10013
  ([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient)
10307
10014
  );
10308
- const fileToolNames = [
10309
- "file_read",
10310
- "create_file",
10311
- "edit_file",
10312
- "delete_file",
10313
- "glob_files",
10314
- "grep_search",
10315
- "bash_execute"
10316
- ];
10317
- const workspaceAwareTools = [
10318
- createWorkspaceAwareFileRead(workspaceManager),
10319
- createWorkspaceAwareCreateFile(workspaceManager),
10320
- createWorkspaceAwareEditFile(workspaceManager),
10321
- createWorkspaceAwareDeleteFile(workspaceManager),
10322
- createWorkspaceAwareGlobFiles(workspaceManager),
10323
- createWorkspaceAwareGrepSearch(workspaceManager),
10324
- createWorkspaceAwareBashExecute(workspaceManager)
10325
- ];
10326
- const filteredTools = tools2.filter((tool) => !fileToolNames.includes(tool.toolSchema.name));
10327
- const wrappedWorkspaceTools = workspaceAwareTools.map(
10328
- (tool) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient)
10329
- );
10330
- return { tools: [...filteredTools, ...wrappedWorkspaceTools], agentContext };
10015
+ if (toolFilter) {
10016
+ const { allowedTools, deniedTools } = toolFilter;
10017
+ const normalizedAllowed = allowedTools?.map(normalizeToolName);
10018
+ const normalizedDenied = deniedTools?.map(normalizeToolName);
10019
+ tools2 = tools2.filter((tool) => {
10020
+ const toolName = tool.toolSchema.name;
10021
+ if (normalizedDenied && normalizedDenied.includes(toolName)) {
10022
+ return false;
10023
+ }
10024
+ if (normalizedAllowed && normalizedAllowed.length > 0) {
10025
+ return normalizedAllowed.includes(toolName);
10026
+ }
10027
+ return true;
10028
+ });
10029
+ }
10030
+ return { tools: tools2, agentContext };
10331
10031
  }
10332
10032
 
10333
10033
  // src/config/toolSafety.ts
@@ -10506,174 +10206,6 @@ var PermissionManager = class {
10506
10206
  }
10507
10207
  };
10508
10208
 
10509
- // src/utils/WorkspaceManager.ts
10510
- import path14 from "path";
10511
- import fs12 from "fs";
10512
- var WorkspaceManager = class _WorkspaceManager {
10513
- constructor(initialWorkspaces = [process.cwd()]) {
10514
- this.workspaces = [];
10515
- this.workspaces = initialWorkspaces.length > 0 ? initialWorkspaces.map((dir) => path14.resolve(dir)) : [path14.resolve(process.cwd())];
10516
- }
10517
- static {
10518
- /**
10519
- * System directories that should never be added as workspaces
10520
- */
10521
- this.FORBIDDEN_PATHS = [
10522
- "/",
10523
- "/bin",
10524
- "/boot",
10525
- "/dev",
10526
- "/etc",
10527
- "/lib",
10528
- "/lib64",
10529
- "/proc",
10530
- "/root",
10531
- "/sbin",
10532
- "/sys",
10533
- "/usr",
10534
- "/var",
10535
- "/tmp",
10536
- "/System",
10537
- "/Library",
10538
- "/Applications",
10539
- "/Windows",
10540
- "/Program Files",
10541
- "/Program Files (x86)"
10542
- ];
10543
- }
10544
- /**
10545
- * Get all workspace directories
10546
- */
10547
- getWorkspaces() {
10548
- return [...this.workspaces];
10549
- }
10550
- /**
10551
- * Get the primary workspace (first in list, used as default)
10552
- */
10553
- getPrimaryWorkspace() {
10554
- return this.workspaces[0] || process.cwd();
10555
- }
10556
- /**
10557
- * Add a new workspace directory
10558
- * @throws Error if directory is invalid, doesn't exist, or is forbidden
10559
- */
10560
- addWorkspace(directory) {
10561
- const resolvedPath = path14.resolve(directory);
10562
- if (!fs12.existsSync(resolvedPath)) {
10563
- throw new Error(`Directory does not exist: ${resolvedPath}`);
10564
- }
10565
- const stat = fs12.statSync(resolvedPath);
10566
- if (!stat.isDirectory()) {
10567
- throw new Error(`Path is not a directory: ${resolvedPath}`);
10568
- }
10569
- if (_WorkspaceManager.isForbiddenPath(resolvedPath)) {
10570
- throw new Error(
10571
- `Cannot add system directory as workspace: ${resolvedPath}
10572
- Adding system directories like /, /usr, /etc is not allowed for security reasons.`
10573
- );
10574
- }
10575
- if (this.workspaces.includes(resolvedPath)) {
10576
- throw new Error(`Directory already in workspace: ${resolvedPath}`);
10577
- }
10578
- this.workspaces.push(resolvedPath);
10579
- }
10580
- /**
10581
- * Remove a workspace directory
10582
- * @throws Error if trying to remove the last/primary workspace
10583
- */
10584
- removeWorkspace(directory) {
10585
- const resolvedPath = path14.resolve(directory);
10586
- const index = this.workspaces.indexOf(resolvedPath);
10587
- if (index === -1) {
10588
- throw new Error(`Directory not in workspace: ${resolvedPath}`);
10589
- }
10590
- if (this.workspaces.length === 1) {
10591
- throw new Error("Cannot remove the last workspace directory");
10592
- }
10593
- if (index === 0) {
10594
- throw new Error(
10595
- "Cannot remove the primary workspace. Remove other directories first, or use /clear to start a new session."
10596
- );
10597
- }
10598
- this.workspaces.splice(index, 1);
10599
- }
10600
- /**
10601
- * Check if a path is within any workspace
10602
- * @param filePath Absolute or relative path to check
10603
- * @returns true if path is within an allowed workspace
10604
- */
10605
- isPathInWorkspace(filePath) {
10606
- const absolutePath = path14.isAbsolute(filePath) ? filePath : path14.resolve(this.getPrimaryWorkspace(), filePath);
10607
- return this.workspaces.some(
10608
- (workspace) => absolutePath.startsWith(workspace + path14.sep) || absolutePath === workspace
10609
- );
10610
- }
10611
- /**
10612
- * Resolve a file path across all workspaces
10613
- * For relative paths: searches all workspaces and returns first match
10614
- * For absolute paths: validates it's within a workspace
10615
- * @throws Error if path is outside all workspaces
10616
- */
10617
- resolvePath(filePath) {
10618
- if (path14.isAbsolute(filePath)) {
10619
- if (!this.isPathInWorkspace(filePath)) {
10620
- throw new Error(
10621
- `Access denied: Path is outside all workspace directories.
10622
- Path: ${filePath}
10623
- Workspaces: ${this.workspaces.join(", ")}
10624
- Use '/directory add <path>' to add the directory to your workspace.`
10625
- );
10626
- }
10627
- return filePath;
10628
- }
10629
- const primaryResolved = path14.resolve(this.getPrimaryWorkspace(), filePath);
10630
- if (fs12.existsSync(primaryResolved)) {
10631
- return primaryResolved;
10632
- }
10633
- for (let i = 1; i < this.workspaces.length; i++) {
10634
- const candidatePath = path14.resolve(this.workspaces[i], filePath);
10635
- if (fs12.existsSync(candidatePath)) {
10636
- return candidatePath;
10637
- }
10638
- }
10639
- return primaryResolved;
10640
- }
10641
- /**
10642
- * Get the workspace that contains a given path
10643
- * @returns The workspace directory, or null if not in any workspace
10644
- */
10645
- getWorkspaceForPath(filePath) {
10646
- const absolutePath = path14.isAbsolute(filePath) ? filePath : path14.resolve(this.getPrimaryWorkspace(), filePath);
10647
- for (const workspace of this.workspaces) {
10648
- if (absolutePath.startsWith(workspace + path14.sep) || absolutePath === workspace) {
10649
- return workspace;
10650
- }
10651
- }
10652
- return null;
10653
- }
10654
- /**
10655
- * Check if a path is a forbidden system directory
10656
- */
10657
- static isForbiddenPath(resolvedPath) {
10658
- if (_WorkspaceManager.FORBIDDEN_PATHS.includes(resolvedPath)) {
10659
- return true;
10660
- }
10661
- return _WorkspaceManager.FORBIDDEN_PATHS.some((forbidden) => resolvedPath.startsWith(forbidden + path14.sep));
10662
- }
10663
- /**
10664
- * Serialize workspace state for storage in session
10665
- */
10666
- toJSON() {
10667
- return this.getWorkspaces();
10668
- }
10669
- /**
10670
- * Create WorkspaceManager from serialized state
10671
- */
10672
- static fromJSON(workspaces) {
10673
- return new _WorkspaceManager(workspaces);
10674
- }
10675
- };
10676
-
10677
10209
  // src/utils/apiUrl.ts
10678
10210
  var BIKE4MIND_URL = "https://app.bike4mind.com";
10679
10211
  function getApiUrl(configApiConfig) {
@@ -10690,8 +10222,8 @@ function getEnvironmentName(configApiConfig) {
10690
10222
  }
10691
10223
 
10692
10224
  // src/utils/contextLoader.ts
10693
- import * as fs13 from "fs";
10694
- import * as path15 from "path";
10225
+ import * as fs11 from "fs";
10226
+ import * as path13 from "path";
10695
10227
  import { homedir as homedir4 } from "os";
10696
10228
  var CONTEXT_FILE_SIZE_LIMIT = 100 * 1024;
10697
10229
  var PROJECT_CONTEXT_FILES = [
@@ -10712,9 +10244,9 @@ function formatFileSize2(bytes) {
10712
10244
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
10713
10245
  }
10714
10246
  function tryReadContextFile(dir, filename, source) {
10715
- const filePath = path15.join(dir, filename);
10247
+ const filePath = path13.join(dir, filename);
10716
10248
  try {
10717
- const stats = fs13.lstatSync(filePath);
10249
+ const stats = fs11.lstatSync(filePath);
10718
10250
  if (stats.isDirectory()) {
10719
10251
  return null;
10720
10252
  }
@@ -10728,7 +10260,7 @@ function tryReadContextFile(dir, filename, source) {
10728
10260
  error: `${source === "global" ? "Global" : "Project"} ${filename} exceeds 100KB limit (${formatFileSize2(stats.size)})`
10729
10261
  };
10730
10262
  }
10731
- const content = fs13.readFileSync(filePath, "utf-8");
10263
+ const content = fs11.readFileSync(filePath, "utf-8");
10732
10264
  return {
10733
10265
  filename,
10734
10266
  content,
@@ -10780,7 +10312,7 @@ ${project.content}`;
10780
10312
  }
10781
10313
  async function loadContextFiles(projectDir) {
10782
10314
  const errors = [];
10783
- const globalDir = path15.join(homedir4(), ".bike4mind");
10315
+ const globalDir = path13.join(homedir4(), ".bike4mind");
10784
10316
  const projectDirectory = projectDir || process.cwd();
10785
10317
  const [globalResult, projectResult] = await Promise.all([
10786
10318
  Promise.resolve(findContextFile(globalDir, GLOBAL_CONTEXT_FILES, "global")),
@@ -10804,8 +10336,8 @@ async function loadContextFiles(projectDir) {
10804
10336
  // ../../b4m-core/packages/mcp/dist/src/client.js
10805
10337
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
10806
10338
  import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
10807
- import path16 from "path";
10808
- import { existsSync as existsSync9, readdirSync as readdirSync3 } from "fs";
10339
+ import path14 from "path";
10340
+ import { existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
10809
10341
  var MCPClient = class {
10810
10342
  // Note: This class handles MCP server communication with repository filtering
10811
10343
  mcp;
@@ -10845,18 +10377,18 @@ var MCPClient = class {
10845
10377
  const root = process.env.INIT_CWD || process.cwd();
10846
10378
  const candidatePaths = [
10847
10379
  // When running from SST Lambda with node_modules structure (copyFiles)
10848
- path16.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
10380
+ path14.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
10849
10381
  // When running from SST Lambda deployed environment (/var/task)
10850
- path16.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10382
+ path14.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10851
10383
  // When running from SST Lambda (.sst/artifacts/mcpHandler-dev), navigate to monorepo root (3 levels up)
10852
- path16.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10384
+ path14.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10853
10385
  // When running from packages/client (Next.js app), navigate to monorepo root (2 levels up)
10854
- path16.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10386
+ path14.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10855
10387
  // Original paths (backward compatibility)
10856
- path16.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10857
- path16.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
10388
+ path14.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10389
+ path14.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
10858
10390
  ];
10859
- const serverScriptPath = candidatePaths.find((p) => existsSync9(p));
10391
+ const serverScriptPath = candidatePaths.find((p) => existsSync8(p));
10860
10392
  if (!serverScriptPath) {
10861
10393
  const getDirectories = (source) => readdirSync3(source, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
10862
10394
  console.error(`[MCP] Server script not found. Tried paths:`, candidatePaths);
@@ -11658,7 +11190,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
11658
11190
  // package.json
11659
11191
  var package_default = {
11660
11192
  name: "@bike4mind/cli",
11661
- version: "0.2.10-feat-cli-multi-directory-workspace.17245+b0d0fbabe",
11193
+ version: "0.2.10-feat-explore-subagent-implementation.17246+5c3defa5d",
11662
11194
  type: "module",
11663
11195
  description: "Interactive CLI tool for Bike4Mind with ReAct agents",
11664
11196
  license: "UNLICENSED",
@@ -11726,7 +11258,6 @@ var package_default = {
11726
11258
  diff: "^8.0.2",
11727
11259
  dotenv: "^16.3.1",
11728
11260
  "eventsource-parser": "^3.0.6",
11729
- "fast-glob": "^3.3.3",
11730
11261
  "file-type": "^18.7.0",
11731
11262
  "fuse.js": "^7.1.0",
11732
11263
  ink: "^6.5.1",
@@ -11762,10 +11293,10 @@ var package_default = {
11762
11293
  },
11763
11294
  devDependencies: {
11764
11295
  "@bike4mind/agents": "0.1.0",
11765
- "@bike4mind/common": "2.39.1-feat-cli-multi-directory-workspace.17245+b0d0fbabe",
11766
- "@bike4mind/mcp": "1.20.4-feat-cli-multi-directory-workspace.17245+b0d0fbabe",
11767
- "@bike4mind/services": "2.34.2-feat-cli-multi-directory-workspace.17245+b0d0fbabe",
11768
- "@bike4mind/utils": "2.1.4-feat-cli-multi-directory-workspace.17245+b0d0fbabe",
11296
+ "@bike4mind/common": "2.39.1-feat-explore-subagent-implementation.17246+5c3defa5d",
11297
+ "@bike4mind/mcp": "1.20.4-feat-explore-subagent-implementation.17246+5c3defa5d",
11298
+ "@bike4mind/services": "2.34.2-feat-explore-subagent-implementation.17246+5c3defa5d",
11299
+ "@bike4mind/utils": "2.1.4-feat-explore-subagent-implementation.17246+5c3defa5d",
11769
11300
  "@types/better-sqlite3": "^7.6.13",
11770
11301
  "@types/diff": "^5.0.9",
11771
11302
  "@types/jsonwebtoken": "^9.0.4",
@@ -11778,7 +11309,7 @@ var package_default = {
11778
11309
  typescript: "^5.9.3",
11779
11310
  vitest: "^3.2.4"
11780
11311
  },
11781
- gitHead: "b0d0fbabeacd5799168626cb093b2573301f0e84"
11312
+ gitHead: "5c3defa5da2bec9450d7b030dafc850c05863d68"
11782
11313
  };
11783
11314
 
11784
11315
  // src/config/constants.ts
@@ -11786,6 +11317,331 @@ var USAGE_DAYS = 30;
11786
11317
  var MODEL_NAME_COLUMN_WIDTH = 18;
11787
11318
  var USAGE_CACHE_TTL = 5 * 60 * 1e3;
11788
11319
 
11320
+ // src/agents/SubagentOrchestrator.ts
11321
+ var SubagentOrchestrator = class {
11322
+ constructor(deps) {
11323
+ this.deps = deps;
11324
+ }
11325
+ /**
11326
+ * Delegate a task to a specialized subagent
11327
+ *
11328
+ * @param options - Configuration for subagent execution
11329
+ * @returns Subagent result with summary
11330
+ */
11331
+ async delegateToSubagent(options) {
11332
+ const { task, type, thoroughness, parentSessionId, config: configOverride } = options;
11333
+ const baseConfig = this.deps.subagentConfigs.get(type);
11334
+ if (!baseConfig) {
11335
+ throw new Error(`No configuration found for subagent type: ${type}`);
11336
+ }
11337
+ const config = {
11338
+ ...baseConfig,
11339
+ ...configOverride,
11340
+ type
11341
+ };
11342
+ const model = config.model || "claude-3-5-haiku-20241022";
11343
+ const maxIterations = this.getMaxIterations(config, thoroughness);
11344
+ const toolFilter = {
11345
+ allowedTools: config.allowedTools,
11346
+ deniedTools: config.deniedTools
11347
+ };
11348
+ const agentContext = {
11349
+ currentAgent: null,
11350
+ observationQueue: []
11351
+ };
11352
+ const { tools: tools2, agentContext: updatedContext } = generateCliTools(
11353
+ this.deps.userId,
11354
+ this.deps.llm,
11355
+ model,
11356
+ this.deps.permissionManager,
11357
+ this.deps.showPermissionPrompt,
11358
+ agentContext,
11359
+ this.deps.configStore,
11360
+ this.deps.apiClient,
11361
+ toolFilter
11362
+ );
11363
+ this.deps.logger.debug(`Spawning ${type} subagent with ${tools2.length} tools, thoroughness: ${thoroughness}`);
11364
+ const subagent = new ReActAgent({
11365
+ userId: this.deps.userId,
11366
+ logger: this.deps.logger,
11367
+ llm: this.deps.llm,
11368
+ model,
11369
+ tools: tools2,
11370
+ maxIterations,
11371
+ systemPrompt: config.systemPrompt || this.getDefaultSystemPrompt(type)
11372
+ });
11373
+ updatedContext.currentAgent = subagent;
11374
+ const startTime = Date.now();
11375
+ const result = await subagent.run(task, {
11376
+ maxIterations
11377
+ });
11378
+ const duration = Date.now() - startTime;
11379
+ this.deps.logger.debug(
11380
+ `Subagent completed in ${duration}ms, ${result.completionInfo.iterations} iterations, ${result.completionInfo.totalTokens} tokens`
11381
+ );
11382
+ const summary = this.summarizeResult(result, type);
11383
+ const subagentResult = {
11384
+ ...result,
11385
+ subagentType: type,
11386
+ thoroughness,
11387
+ summary,
11388
+ parentSessionId
11389
+ };
11390
+ return subagentResult;
11391
+ }
11392
+ /**
11393
+ * Get max iterations based on thoroughness and config
11394
+ */
11395
+ getMaxIterations(config, thoroughness) {
11396
+ const defaults = {
11397
+ quick: 2,
11398
+ medium: 5,
11399
+ very_thorough: 10
11400
+ };
11401
+ const configIterations = config.maxIterations || defaults;
11402
+ return configIterations[thoroughness];
11403
+ }
11404
+ /**
11405
+ * Get default system prompt for subagent type
11406
+ */
11407
+ getDefaultSystemPrompt(type) {
11408
+ switch (type) {
11409
+ case "explore":
11410
+ return `You are a code exploration specialist. Your job is to search and analyze codebases efficiently.
11411
+
11412
+ Focus on:
11413
+ - Finding relevant files and functions
11414
+ - Understanding code structure and patterns
11415
+ - Providing clear, concise summaries
11416
+
11417
+ You have read-only access. Use file_read, grep_search, and glob_files to explore.
11418
+
11419
+ When you find what you're looking for, provide a clear summary including:
11420
+ 1. What you found (files, functions, patterns)
11421
+ 2. Key insights or observations
11422
+ 3. Relevant code locations
11423
+
11424
+ Be thorough but concise. Your summary will be used by the main agent.`;
11425
+ case "plan":
11426
+ return `You are a task planning specialist. Your job is to break down complex tasks into clear, actionable steps.
11427
+
11428
+ Focus on:
11429
+ - Identifying dependencies and blockers
11430
+ - Creating logical sequence of steps
11431
+ - Estimating scope and priorities
11432
+
11433
+ Provide a structured plan that the main agent can execute.`;
11434
+ case "review":
11435
+ return `You are a code review specialist. Your job is to analyze code quality and identify issues.
11436
+
11437
+ Focus on:
11438
+ - Code quality and best practices
11439
+ - Potential bugs and edge cases
11440
+ - Performance and security considerations
11441
+
11442
+ Provide actionable feedback with specific file and line references.`;
11443
+ default:
11444
+ return "You are a helpful AI assistant.";
11445
+ }
11446
+ }
11447
+ /**
11448
+ * Summarize subagent result for parent agent
11449
+ */
11450
+ summarizeResult(result, type) {
11451
+ const { finalAnswer, steps, completionInfo } = result;
11452
+ const toolCalls = steps.filter((s) => s.type === "action");
11453
+ const filesRead = toolCalls.filter((s) => s.metadata?.toolName === "file_read").length;
11454
+ const searches = toolCalls.filter((s) => s.metadata?.toolName === "grep_search").length;
11455
+ const globs = toolCalls.filter((s) => s.metadata?.toolName === "glob_files").length;
11456
+ let summary = `**${type.charAt(0).toUpperCase() + type.slice(1)} Subagent Results**
11457
+
11458
+ `;
11459
+ summary += `*Execution: ${completionInfo.iterations} iterations, ${completionInfo.toolCalls} tool calls*
11460
+
11461
+ `;
11462
+ if (type === "explore") {
11463
+ summary += `*Exploration: ${filesRead} files read, ${searches} searches, ${globs} glob patterns*
11464
+
11465
+ `;
11466
+ }
11467
+ const maxLength = 1e3;
11468
+ if (finalAnswer.length > maxLength) {
11469
+ summary += finalAnswer.slice(0, maxLength) + "\n\n...(truncated)";
11470
+ } else {
11471
+ summary += finalAnswer;
11472
+ }
11473
+ return summary;
11474
+ }
11475
+ };
11476
+
11477
+ // src/agents/configs.ts
11478
+ var EXPLORE_CONFIG = {
11479
+ type: "explore",
11480
+ model: "claude-3-5-haiku-20241022",
11481
+ systemPrompt: `You are a code exploration specialist. Your job is to search and analyze codebases efficiently.
11482
+
11483
+ Focus on:
11484
+ - Finding relevant files and functions
11485
+ - Understanding code structure and patterns
11486
+ - Providing clear, concise summaries
11487
+
11488
+ You have read-only access. Use file_read, grep_search, and glob_files to explore.
11489
+
11490
+ When you find what you're looking for, provide a clear summary including:
11491
+ 1. What you found (files, functions, patterns)
11492
+ 2. Key insights or observations
11493
+ 3. Relevant code locations
11494
+
11495
+ Be thorough but concise. Your summary will be used by the main agent.`,
11496
+ allowedTools: [
11497
+ "file_read",
11498
+ "grep_search",
11499
+ "glob_files",
11500
+ "bash_execute",
11501
+ // Only for read-only commands like ls, cat, find
11502
+ "current_datetime",
11503
+ "math_evaluate"
11504
+ ],
11505
+ deniedTools: [
11506
+ "create_file",
11507
+ "edit_file",
11508
+ "delete_file",
11509
+ "web_search",
11510
+ "weather_info",
11511
+ "blog_publish",
11512
+ "blog_edit",
11513
+ "blog_draft"
11514
+ ],
11515
+ maxIterations: {
11516
+ quick: 2,
11517
+ medium: 5,
11518
+ very_thorough: 10
11519
+ },
11520
+ defaultThoroughness: "medium"
11521
+ };
11522
+ var PLAN_CONFIG = {
11523
+ type: "plan",
11524
+ model: "claude-3-5-haiku-20241022",
11525
+ systemPrompt: `You are a task planning specialist. Your job is to break down complex tasks into clear, actionable steps.
11526
+
11527
+ Focus on:
11528
+ - Identifying dependencies and blockers
11529
+ - Creating logical sequence of steps
11530
+ - Estimating scope and priorities
11531
+
11532
+ You can explore the codebase to understand the current architecture before planning.
11533
+
11534
+ Provide a structured plan that the main agent can execute.`,
11535
+ allowedTools: ["file_read", "grep_search", "glob_files", "bash_execute", "current_datetime", "math_evaluate"],
11536
+ deniedTools: ["create_file", "edit_file", "delete_file", "web_search", "weather_info"],
11537
+ maxIterations: {
11538
+ quick: 3,
11539
+ medium: 7,
11540
+ very_thorough: 12
11541
+ },
11542
+ defaultThoroughness: "medium"
11543
+ };
11544
+ var REVIEW_CONFIG = {
11545
+ type: "review",
11546
+ model: "claude-3-5-sonnet-20241022",
11547
+ // Use Sonnet for better reasoning
11548
+ systemPrompt: `You are a code review specialist. Your job is to analyze code quality and identify issues.
11549
+
11550
+ Focus on:
11551
+ - Code quality and best practices
11552
+ - Potential bugs and edge cases
11553
+ - Performance and security considerations
11554
+
11555
+ You have read-only access to analyze code.
11556
+
11557
+ Provide actionable feedback with specific file and line references.`,
11558
+ allowedTools: ["file_read", "grep_search", "glob_files", "bash_execute", "current_datetime"],
11559
+ deniedTools: ["create_file", "edit_file", "delete_file", "web_search", "weather_info"],
11560
+ maxIterations: {
11561
+ quick: 3,
11562
+ medium: 8,
11563
+ very_thorough: 15
11564
+ },
11565
+ defaultThoroughness: "medium"
11566
+ };
11567
+ function getDefaultSubagentConfigs() {
11568
+ return /* @__PURE__ */ new Map([
11569
+ ["explore", EXPLORE_CONFIG],
11570
+ ["plan", PLAN_CONFIG],
11571
+ ["review", REVIEW_CONFIG]
11572
+ ]);
11573
+ }
11574
+
11575
+ // src/agents/delegateTool.ts
11576
+ function createSubagentDelegateTool(orchestrator, parentSessionId) {
11577
+ return {
11578
+ toolFn: async (args) => {
11579
+ const params = args;
11580
+ if (!params.task) {
11581
+ throw new Error("subagent_delegate: task parameter is required");
11582
+ }
11583
+ if (!params.type) {
11584
+ throw new Error("subagent_delegate: type parameter is required");
11585
+ }
11586
+ const thoroughness = params.thoroughness || "medium";
11587
+ const type = params.type;
11588
+ const result = await orchestrator.delegateToSubagent({
11589
+ task: params.task,
11590
+ type,
11591
+ thoroughness,
11592
+ parentSessionId
11593
+ });
11594
+ return result.summary;
11595
+ },
11596
+ toolSchema: {
11597
+ name: "subagent_delegate",
11598
+ description: `Delegate a task to a specialized subagent for focused work.
11599
+
11600
+ **When to use this tool:**
11601
+ - **Explore**: When you need to search through the codebase, find files, or understand code structure (read-only)
11602
+ - **Plan**: When you need to break down a complex task into actionable steps
11603
+ - **Review**: When you need to analyze code quality and identify potential issues
11604
+
11605
+ **Benefits:**
11606
+ - Keeps main conversation focused and clean
11607
+ - Uses specialized prompts optimized for each task type
11608
+ - Faster execution with appropriate models (Haiku for explore/plan, Sonnet for review)
11609
+
11610
+ **Example uses:**
11611
+ - "Find all files that use the authentication system" \u2192 explore
11612
+ - "Search for components that handle user input" \u2192 explore
11613
+ - "Break down implementing a new feature into steps" \u2192 plan
11614
+ - "Review this module for potential bugs" \u2192 review`,
11615
+ parameters: {
11616
+ type: "object",
11617
+ properties: {
11618
+ task: {
11619
+ type: "string",
11620
+ description: "Clear description of what you want the subagent to do. Be specific about what you're looking for or what needs to be accomplished."
11621
+ },
11622
+ type: {
11623
+ type: "string",
11624
+ enum: ["explore", "plan", "review"],
11625
+ description: `Type of subagent to use:
11626
+ - explore: Read-only codebase exploration and search (fast, uses Haiku)
11627
+ - plan: Task breakdown and planning (uses Haiku)
11628
+ - review: Code quality analysis and review (uses Sonnet for better reasoning)`
11629
+ },
11630
+ thoroughness: {
11631
+ type: "string",
11632
+ enum: ["quick", "medium", "very_thorough"],
11633
+ description: `How thoroughly to execute:
11634
+ - quick: Fast lookup, 1-2 iterations
11635
+ - medium: Balanced exploration, 3-5 iterations (default)
11636
+ - very_thorough: Comprehensive analysis, 8-10 iterations`
11637
+ }
11638
+ },
11639
+ required: ["task", "type"]
11640
+ }
11641
+ }
11642
+ };
11643
+ }
11644
+
11789
11645
  // src/index.tsx
11790
11646
  process.removeAllListeners("warning");
11791
11647
  process.on("warning", (warning) => {
@@ -11800,19 +11656,6 @@ var usageCache = null;
11800
11656
  function CliApp() {
11801
11657
  const { exit } = useApp();
11802
11658
  const imageRenderer = new ImageRenderer();
11803
- const getInitialWorkspaces = () => {
11804
- const workspaces = [process.cwd()];
11805
- const addDirsEnv = process.env.B4M_ADD_DIRS;
11806
- if (addDirsEnv) {
11807
- try {
11808
- const additionalDirs = JSON.parse(addDirsEnv);
11809
- workspaces.push(...additionalDirs);
11810
- } catch (error) {
11811
- logger.warn(`Failed to parse B4M_ADD_DIRS environment variable: ${error}`);
11812
- }
11813
- }
11814
- return workspaces;
11815
- };
11816
11659
  const [state, setState] = useState8({
11817
11660
  session: null,
11818
11661
  sessionStore: new SessionStore(),
@@ -11825,7 +11668,6 @@ function CliApp() {
11825
11668
  agent: null,
11826
11669
  mcpManager: null,
11827
11670
  permissionManager: null,
11828
- workspaceManager: new WorkspaceManager(getInitialWorkspaces()),
11829
11671
  permissionPrompt: null,
11830
11672
  trustLocationSelector: null,
11831
11673
  rewindSelector: null,
@@ -11930,8 +11772,7 @@ function CliApp() {
11930
11772
  totalTokens: 0,
11931
11773
  totalCost: 0,
11932
11774
  toolCallCount: 0
11933
- },
11934
- workingDirectories: state.workspaceManager.getWorkspaces()
11775
+ }
11935
11776
  };
11936
11777
  setState((prev) => ({
11937
11778
  ...prev,
@@ -11976,8 +11817,7 @@ function CliApp() {
11976
11817
  totalTokens: 0,
11977
11818
  totalCost: 0,
11978
11819
  toolCallCount: 0
11979
- },
11980
- workingDirectories: state.workspaceManager.getWorkspaces()
11820
+ }
11981
11821
  };
11982
11822
  await logger.initialize(newSession.id);
11983
11823
  logger.setVerbose(process.env.B4M_VERBOSE === "1");
@@ -12024,7 +11864,6 @@ function CliApp() {
12024
11864
  llm,
12025
11865
  modelInfo.id,
12026
11866
  permissionManager,
12027
- state.workspaceManager,
12028
11867
  promptFn,
12029
11868
  agentContext,
12030
11869
  state.configStore,
@@ -12040,7 +11879,34 @@ function CliApp() {
12040
11879
  console.log(` \u{1F4E1} ${serverName}: ${count} tool(s)`);
12041
11880
  });
12042
11881
  }
12043
- const allTools = [...b4mTools, ...mcpTools];
11882
+ const subagentConfigs = getDefaultSubagentConfigs();
11883
+ if (config.subagents) {
11884
+ if (config.subagents.explore) {
11885
+ const currentConfig = subagentConfigs.get("explore");
11886
+ subagentConfigs.set("explore", { ...currentConfig, ...config.subagents.explore });
11887
+ }
11888
+ if (config.subagents.plan) {
11889
+ const currentConfig = subagentConfigs.get("plan");
11890
+ subagentConfigs.set("plan", { ...currentConfig, ...config.subagents.plan });
11891
+ }
11892
+ if (config.subagents.review) {
11893
+ const currentConfig = subagentConfigs.get("review");
11894
+ subagentConfigs.set("review", { ...currentConfig, ...config.subagents.review });
11895
+ }
11896
+ }
11897
+ const orchestrator = new SubagentOrchestrator({
11898
+ userId: config.userId,
11899
+ llm,
11900
+ logger: silentLogger,
11901
+ permissionManager,
11902
+ showPermissionPrompt: promptFn,
11903
+ configStore: state.configStore,
11904
+ apiClient,
11905
+ subagentConfigs
11906
+ });
11907
+ const subagentDelegateTool = createSubagentDelegateTool(orchestrator, newSession.id);
11908
+ const allTools = [...b4mTools, ...mcpTools, subagentDelegateTool];
11909
+ console.log(`\u{1F916} Subagent delegation enabled (explore, plan, review)`);
12044
11910
  const projectDir = state.configStore.getProjectConfigDir();
12045
11911
  const contextResult = await loadContextFiles(projectDir);
12046
11912
  if (contextResult.globalContext) {
@@ -12077,6 +11943,15 @@ FOR CODING TASKS:
12077
11943
  - When searching for code: Use grep_search or glob_files to find relevant files
12078
11944
  - Permission system will ask for approval before any file writes
12079
11945
 
11946
+ SUBAGENT DELEGATION:
11947
+ - You have access to specialized subagents via the subagent_delegate tool
11948
+ - Use subagents for focused exploration, planning, or review tasks:
11949
+ * explore: Fast read-only codebase search (e.g., "find all auth files", "locate API endpoints")
11950
+ * plan: Break down complex tasks into actionable steps
11951
+ * review: Analyze code quality and identify issues
11952
+ - Subagents keep the main conversation clean and run faster with optimized models
11953
+ - Delegate when you need to search extensively or analyze code structure
11954
+
12080
11955
  FOR GENERAL TASKS:
12081
11956
  - Use available tools to get information (weather, web search, calculations, etc.)
12082
11957
  - When user asks follow-up questions, use conversation context to understand what they're referring to
@@ -12087,6 +11962,7 @@ EXAMPLES:
12087
11962
  - "how about Japan?" \u2192 use weather tool for Japan (applying same question from context)
12088
11963
  - "enhance README" \u2192 file_read \u2192 generate \u2192 create_file
12089
11964
  - "what packages installed?" \u2192 glob_files "**/package.json" \u2192 file_read
11965
+ - "find all components using React hooks" \u2192 subagent_delegate(task="find all components using React hooks", type="explore")
12090
11966
 
12091
11967
  Remember: Use context from previous messages to understand follow-up questions.${contextSection}`;
12092
11968
  const maxIterations = config.preferences.maxIterations === null ? 999999 : config.preferences.maxIterations;
@@ -12365,11 +12241,6 @@ Tool Permissions:
12365
12241
  /untrust <tool-name> - Remove tool from trusted list
12366
12242
  /trusted - List all trusted tools
12367
12243
 
12368
- Workspace Management:
12369
- /directory show - Show all workspace directories
12370
- /directory add <path> - Add directory to workspace
12371
- /directory remove <path> - Remove directory from workspace
12372
-
12373
12244
  Project Configuration:
12374
12245
  /project-config - Show merged project configuration
12375
12246
  `);
@@ -12411,24 +12282,7 @@ Project Configuration:
12411
12282
  }
12412
12283
  await logger.initialize(loadedSession.id);
12413
12284
  logger.debug("=== Session Resumed ===");
12414
- if (loadedSession.workingDirectories && loadedSession.workingDirectories.length > 0) {
12415
- try {
12416
- const restoredWorkspaceManager = WorkspaceManager.fromJSON(loadedSession.workingDirectories);
12417
- setState((prev) => ({
12418
- ...prev,
12419
- session: loadedSession,
12420
- workspaceManager: restoredWorkspaceManager
12421
- }));
12422
- console.log(
12423
- `\u{1F4C1} Restored ${loadedSession.workingDirectories.length} workspace director${loadedSession.workingDirectories.length !== 1 ? "ies" : "y"}`
12424
- );
12425
- } catch (error) {
12426
- logger.warn(`Failed to restore workspace directories from session: ${error}`);
12427
- setState((prev) => ({ ...prev, session: loadedSession }));
12428
- }
12429
- } else {
12430
- setState((prev) => ({ ...prev, session: loadedSession }));
12431
- }
12285
+ setState((prev) => ({ ...prev, session: loadedSession }));
12432
12286
  setStoreSession(loadedSession);
12433
12287
  useCliStore.getState().clearAgentSteps();
12434
12288
  useCliStore.getState().clearPendingMessages();
@@ -12624,74 +12478,6 @@ Project Configuration:
12624
12478
  }
12625
12479
  console.log("");
12626
12480
  break;
12627
- case "directory": {
12628
- const subcommand = args[0];
12629
- if (!subcommand || subcommand === "show") {
12630
- const workspaces = state.workspaceManager.getWorkspaces();
12631
- console.log("\n\u{1F4C1} Workspace Directories:\n");
12632
- workspaces.forEach((dir, index) => {
12633
- const label = index === 0 ? " (primary)" : "";
12634
- console.log(` ${index + 1}. ${dir}${label}`);
12635
- });
12636
- console.log("");
12637
- break;
12638
- }
12639
- if (subcommand === "add") {
12640
- const dirPath = args[1];
12641
- if (!dirPath) {
12642
- console.log("Usage: /directory add <path>");
12643
- console.log("Example: /directory add ../other-project");
12644
- break;
12645
- }
12646
- try {
12647
- state.workspaceManager.addWorkspace(dirPath);
12648
- if (state.session) {
12649
- const updatedSession = {
12650
- ...state.session,
12651
- workingDirectories: state.workspaceManager.getWorkspaces()
12652
- };
12653
- setState((prev) => ({ ...prev, session: updatedSession }));
12654
- await state.sessionStore.save(updatedSession);
12655
- }
12656
- console.log(`\u2705 Added directory to workspace: ${dirPath}`);
12657
- console.log(`\u{1F4C1} Workspace now has ${state.workspaceManager.getWorkspaces().length} directories`);
12658
- } catch (error) {
12659
- console.log(`\u274C Failed to add directory: ${error.message}`);
12660
- }
12661
- break;
12662
- }
12663
- if (subcommand === "remove") {
12664
- const dirPath = args[1];
12665
- if (!dirPath) {
12666
- console.log("Usage: /directory remove <path>");
12667
- console.log("Example: /directory remove ../other-project");
12668
- break;
12669
- }
12670
- try {
12671
- state.workspaceManager.removeWorkspace(dirPath);
12672
- if (state.session) {
12673
- const updatedSession = {
12674
- ...state.session,
12675
- workingDirectories: state.workspaceManager.getWorkspaces()
12676
- };
12677
- setState((prev) => ({ ...prev, session: updatedSession }));
12678
- await state.sessionStore.save(updatedSession);
12679
- }
12680
- console.log(`\u2705 Removed directory from workspace: ${dirPath}`);
12681
- console.log(
12682
- `\u{1F4C1} Workspace now has ${state.workspaceManager.getWorkspaces().length} director${state.workspaceManager.getWorkspaces().length !== 1 ? "ies" : "y"}`
12683
- );
12684
- } catch (error) {
12685
- console.log(`\u274C Failed to remove directory: ${error.message}`);
12686
- }
12687
- break;
12688
- }
12689
- console.log("Unknown subcommand. Usage:");
12690
- console.log(" /directory show - Show all workspace directories");
12691
- console.log(" /directory add <path> - Add directory to workspace");
12692
- console.log(" /directory remove <path> - Remove directory from workspace");
12693
- break;
12694
- }
12695
12481
  case "login":
12696
12482
  setState((prev) => ({ ...prev, showLoginFlow: true }));
12697
12483
  break;
@@ -12728,8 +12514,7 @@ Project Configuration:
12728
12514
  totalTokens: 0,
12729
12515
  totalCost: 0,
12730
12516
  toolCallCount: 0
12731
- },
12732
- workingDirectories: state.workspaceManager.getWorkspaces()
12517
+ }
12733
12518
  };
12734
12519
  await logger.initialize(newSession.id);
12735
12520
  logger.debug("=== New Session Started via /clear ===");