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

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);
@@ -9470,7 +9456,7 @@ async function generateFileDeletePreview(args) {
9470
9456
  if (!existsSync7(args.path)) {
9471
9457
  return `[File does not exist: ${args.path}]`;
9472
9458
  }
9473
- const stats = await import("fs/promises").then((fs14) => fs14.stat(args.path));
9459
+ const stats = await import("fs/promises").then((fs12) => fs12.stat(args.path));
9474
9460
  return `[File will be deleted]
9475
9461
 
9476
9462
  Path: ${args.path}
@@ -9815,330 +9801,26 @@ async function executeTool(toolName, input, apiClient, localToolFn) {
9815
9801
  }
9816
9802
  }
9817
9803
 
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
9804
  // src/utils/toolsAdapter.ts
10123
9805
  var NoOpStorage = class extends BaseStorage {
10124
9806
  async upload(input, destination, options) {
10125
9807
  return `/tmp/${destination}`;
10126
9808
  }
10127
- async download(path17) {
9809
+ async download(path15) {
10128
9810
  throw new Error("Download not supported in CLI");
10129
9811
  }
10130
- async delete(path17) {
9812
+ async delete(path15) {
10131
9813
  }
10132
- async getSignedUrl(path17) {
10133
- return `/tmp/${path17}`;
9814
+ async getSignedUrl(path15) {
9815
+ return `/tmp/${path15}`;
10134
9816
  }
10135
- getPublicUrl(path17) {
10136
- return `/tmp/${path17}`;
9817
+ getPublicUrl(path15) {
9818
+ return `/tmp/${path15}`;
10137
9819
  }
10138
- async getPreview(path17) {
10139
- return `/tmp/${path17}`;
9820
+ async getPreview(path15) {
9821
+ return `/tmp/${path15}`;
10140
9822
  }
10141
- async getMetadata(path17) {
9823
+ async getMetadata(path15) {
10142
9824
  return { size: 0, contentType: "application/octet-stream" };
10143
9825
  }
10144
9826
  };
@@ -10232,7 +9914,31 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
10232
9914
  }
10233
9915
  };
10234
9916
  }
10235
- function generateCliTools(userId, llm, model, permissionManager, workspaceManager, showPermissionPrompt, agentContext, configStore, apiClient) {
9917
+ var TOOL_NAME_MAPPING = {
9918
+ // Claude Code -> B4M
9919
+ read: "file_read",
9920
+ write: "create_file",
9921
+ edit: "edit_file",
9922
+ delete: "delete_file",
9923
+ glob: "glob_files",
9924
+ grep: "grep_search",
9925
+ bash: "bash_execute",
9926
+ // B4M -> Claude Code (reverse mapping)
9927
+ file_read: "read",
9928
+ create_file: "write",
9929
+ edit_file: "edit",
9930
+ delete_file: "delete",
9931
+ glob_files: "glob",
9932
+ grep_search: "grep",
9933
+ bash_execute: "bash"
9934
+ };
9935
+ function normalizeToolName(toolName) {
9936
+ if (toolName.includes("_")) {
9937
+ return toolName;
9938
+ }
9939
+ return TOOL_NAME_MAPPING[toolName] || toolName;
9940
+ }
9941
+ function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter) {
10236
9942
  const logger2 = new CliLogger();
10237
9943
  const storage = new NoOpStorage();
10238
9944
  const user = {
@@ -10302,32 +10008,25 @@ function generateCliTools(userId, llm, model, permissionManager, workspaceManage
10302
10008
  toolConfig,
10303
10009
  model
10304
10010
  );
10305
- const tools2 = Object.entries(toolsMap).filter(([key]) => toolConfig[key] !== void 0).map(
10011
+ let tools2 = Object.entries(toolsMap).filter(([key]) => toolConfig[key] !== void 0).map(
10306
10012
  ([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient)
10307
10013
  );
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 };
10014
+ if (toolFilter) {
10015
+ const { allowedTools, deniedTools } = toolFilter;
10016
+ const normalizedAllowed = allowedTools?.map(normalizeToolName);
10017
+ const normalizedDenied = deniedTools?.map(normalizeToolName);
10018
+ tools2 = tools2.filter((tool) => {
10019
+ const toolName = tool.toolSchema.name;
10020
+ if (normalizedDenied && normalizedDenied.includes(toolName)) {
10021
+ return false;
10022
+ }
10023
+ if (normalizedAllowed && normalizedAllowed.length > 0) {
10024
+ return normalizedAllowed.includes(toolName);
10025
+ }
10026
+ return true;
10027
+ });
10028
+ }
10029
+ return { tools: tools2, agentContext };
10331
10030
  }
10332
10031
 
10333
10032
  // src/config/toolSafety.ts
@@ -10506,174 +10205,6 @@ var PermissionManager = class {
10506
10205
  }
10507
10206
  };
10508
10207
 
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
10208
  // src/utils/apiUrl.ts
10678
10209
  var BIKE4MIND_URL = "https://app.bike4mind.com";
10679
10210
  function getApiUrl(configApiConfig) {
@@ -10690,8 +10221,8 @@ function getEnvironmentName(configApiConfig) {
10690
10221
  }
10691
10222
 
10692
10223
  // src/utils/contextLoader.ts
10693
- import * as fs13 from "fs";
10694
- import * as path15 from "path";
10224
+ import * as fs11 from "fs";
10225
+ import * as path13 from "path";
10695
10226
  import { homedir as homedir4 } from "os";
10696
10227
  var CONTEXT_FILE_SIZE_LIMIT = 100 * 1024;
10697
10228
  var PROJECT_CONTEXT_FILES = [
@@ -10712,9 +10243,9 @@ function formatFileSize2(bytes) {
10712
10243
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
10713
10244
  }
10714
10245
  function tryReadContextFile(dir, filename, source) {
10715
- const filePath = path15.join(dir, filename);
10246
+ const filePath = path13.join(dir, filename);
10716
10247
  try {
10717
- const stats = fs13.lstatSync(filePath);
10248
+ const stats = fs11.lstatSync(filePath);
10718
10249
  if (stats.isDirectory()) {
10719
10250
  return null;
10720
10251
  }
@@ -10728,7 +10259,7 @@ function tryReadContextFile(dir, filename, source) {
10728
10259
  error: `${source === "global" ? "Global" : "Project"} ${filename} exceeds 100KB limit (${formatFileSize2(stats.size)})`
10729
10260
  };
10730
10261
  }
10731
- const content = fs13.readFileSync(filePath, "utf-8");
10262
+ const content = fs11.readFileSync(filePath, "utf-8");
10732
10263
  return {
10733
10264
  filename,
10734
10265
  content,
@@ -10780,7 +10311,7 @@ ${project.content}`;
10780
10311
  }
10781
10312
  async function loadContextFiles(projectDir) {
10782
10313
  const errors = [];
10783
- const globalDir = path15.join(homedir4(), ".bike4mind");
10314
+ const globalDir = path13.join(homedir4(), ".bike4mind");
10784
10315
  const projectDirectory = projectDir || process.cwd();
10785
10316
  const [globalResult, projectResult] = await Promise.all([
10786
10317
  Promise.resolve(findContextFile(globalDir, GLOBAL_CONTEXT_FILES, "global")),
@@ -10804,8 +10335,8 @@ async function loadContextFiles(projectDir) {
10804
10335
  // ../../b4m-core/packages/mcp/dist/src/client.js
10805
10336
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
10806
10337
  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";
10338
+ import path14 from "path";
10339
+ import { existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
10809
10340
  var MCPClient = class {
10810
10341
  // Note: This class handles MCP server communication with repository filtering
10811
10342
  mcp;
@@ -10845,18 +10376,18 @@ var MCPClient = class {
10845
10376
  const root = process.env.INIT_CWD || process.cwd();
10846
10377
  const candidatePaths = [
10847
10378
  // When running from SST Lambda with node_modules structure (copyFiles)
10848
- path16.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
10379
+ path14.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
10849
10380
  // When running from SST Lambda deployed environment (/var/task)
10850
- path16.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10381
+ path14.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10851
10382
  // 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`),
10383
+ path14.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10853
10384
  // 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`),
10385
+ path14.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10855
10386
  // 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")
10387
+ path14.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10388
+ path14.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
10858
10389
  ];
10859
- const serverScriptPath = candidatePaths.find((p) => existsSync9(p));
10390
+ const serverScriptPath = candidatePaths.find((p) => existsSync8(p));
10860
10391
  if (!serverScriptPath) {
10861
10392
  const getDirectories = (source) => readdirSync3(source, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
10862
10393
  console.error(`[MCP] Server script not found. Tried paths:`, candidatePaths);
@@ -11658,7 +11189,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
11658
11189
  // package.json
11659
11190
  var package_default = {
11660
11191
  name: "@bike4mind/cli",
11661
- version: "0.2.10-feat-cli-multi-directory-workspace.17245+b0d0fbabe",
11192
+ version: "0.2.10-feat-explore-subagent-implementation.17245+d73e88b84",
11662
11193
  type: "module",
11663
11194
  description: "Interactive CLI tool for Bike4Mind with ReAct agents",
11664
11195
  license: "UNLICENSED",
@@ -11726,7 +11257,6 @@ var package_default = {
11726
11257
  diff: "^8.0.2",
11727
11258
  dotenv: "^16.3.1",
11728
11259
  "eventsource-parser": "^3.0.6",
11729
- "fast-glob": "^3.3.3",
11730
11260
  "file-type": "^18.7.0",
11731
11261
  "fuse.js": "^7.1.0",
11732
11262
  ink: "^6.5.1",
@@ -11762,10 +11292,10 @@ var package_default = {
11762
11292
  },
11763
11293
  devDependencies: {
11764
11294
  "@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",
11295
+ "@bike4mind/common": "2.39.1-feat-explore-subagent-implementation.17245+d73e88b84",
11296
+ "@bike4mind/mcp": "1.20.4-feat-explore-subagent-implementation.17245+d73e88b84",
11297
+ "@bike4mind/services": "2.34.2-feat-explore-subagent-implementation.17245+d73e88b84",
11298
+ "@bike4mind/utils": "2.1.4-feat-explore-subagent-implementation.17245+d73e88b84",
11769
11299
  "@types/better-sqlite3": "^7.6.13",
11770
11300
  "@types/diff": "^5.0.9",
11771
11301
  "@types/jsonwebtoken": "^9.0.4",
@@ -11778,7 +11308,7 @@ var package_default = {
11778
11308
  typescript: "^5.9.3",
11779
11309
  vitest: "^3.2.4"
11780
11310
  },
11781
- gitHead: "b0d0fbabeacd5799168626cb093b2573301f0e84"
11311
+ gitHead: "d73e88b84cc88ea33861cf5270ee1e23eb953ed1"
11782
11312
  };
11783
11313
 
11784
11314
  // src/config/constants.ts
@@ -11786,6 +11316,331 @@ var USAGE_DAYS = 30;
11786
11316
  var MODEL_NAME_COLUMN_WIDTH = 18;
11787
11317
  var USAGE_CACHE_TTL = 5 * 60 * 1e3;
11788
11318
 
11319
+ // src/agents/SubagentOrchestrator.ts
11320
+ var SubagentOrchestrator = class {
11321
+ constructor(deps) {
11322
+ this.deps = deps;
11323
+ }
11324
+ /**
11325
+ * Delegate a task to a specialized subagent
11326
+ *
11327
+ * @param options - Configuration for subagent execution
11328
+ * @returns Subagent result with summary
11329
+ */
11330
+ async delegateToSubagent(options) {
11331
+ const { task, type, thoroughness, parentSessionId, config: configOverride } = options;
11332
+ const baseConfig = this.deps.subagentConfigs.get(type);
11333
+ if (!baseConfig) {
11334
+ throw new Error(`No configuration found for subagent type: ${type}`);
11335
+ }
11336
+ const config = {
11337
+ ...baseConfig,
11338
+ ...configOverride,
11339
+ type
11340
+ };
11341
+ const model = config.model || "claude-3-5-haiku-20241022";
11342
+ const maxIterations = this.getMaxIterations(config, thoroughness);
11343
+ const toolFilter = {
11344
+ allowedTools: config.allowedTools,
11345
+ deniedTools: config.deniedTools
11346
+ };
11347
+ const agentContext = {
11348
+ currentAgent: null,
11349
+ observationQueue: []
11350
+ };
11351
+ const { tools: tools2, agentContext: updatedContext } = generateCliTools(
11352
+ this.deps.userId,
11353
+ this.deps.llm,
11354
+ model,
11355
+ this.deps.permissionManager,
11356
+ this.deps.showPermissionPrompt,
11357
+ agentContext,
11358
+ this.deps.configStore,
11359
+ this.deps.apiClient,
11360
+ toolFilter
11361
+ );
11362
+ this.deps.logger.debug(`Spawning ${type} subagent with ${tools2.length} tools, thoroughness: ${thoroughness}`);
11363
+ const subagent = new ReActAgent({
11364
+ userId: this.deps.userId,
11365
+ logger: this.deps.logger,
11366
+ llm: this.deps.llm,
11367
+ model,
11368
+ tools: tools2,
11369
+ maxIterations,
11370
+ systemPrompt: config.systemPrompt || this.getDefaultSystemPrompt(type)
11371
+ });
11372
+ updatedContext.currentAgent = subagent;
11373
+ const startTime = Date.now();
11374
+ const result = await subagent.run(task, {
11375
+ maxIterations
11376
+ });
11377
+ const duration = Date.now() - startTime;
11378
+ this.deps.logger.debug(
11379
+ `Subagent completed in ${duration}ms, ${result.completionInfo.iterations} iterations, ${result.completionInfo.totalTokens} tokens`
11380
+ );
11381
+ const summary = this.summarizeResult(result, type);
11382
+ const subagentResult = {
11383
+ ...result,
11384
+ subagentType: type,
11385
+ thoroughness,
11386
+ summary,
11387
+ parentSessionId
11388
+ };
11389
+ return subagentResult;
11390
+ }
11391
+ /**
11392
+ * Get max iterations based on thoroughness and config
11393
+ */
11394
+ getMaxIterations(config, thoroughness) {
11395
+ const defaults = {
11396
+ quick: 2,
11397
+ medium: 5,
11398
+ very_thorough: 10
11399
+ };
11400
+ const configIterations = config.maxIterations || defaults;
11401
+ return configIterations[thoroughness];
11402
+ }
11403
+ /**
11404
+ * Get default system prompt for subagent type
11405
+ */
11406
+ getDefaultSystemPrompt(type) {
11407
+ switch (type) {
11408
+ case "explore":
11409
+ return `You are a code exploration specialist. Your job is to search and analyze codebases efficiently.
11410
+
11411
+ Focus on:
11412
+ - Finding relevant files and functions
11413
+ - Understanding code structure and patterns
11414
+ - Providing clear, concise summaries
11415
+
11416
+ You have read-only access. Use file_read, grep_search, and glob_files to explore.
11417
+
11418
+ When you find what you're looking for, provide a clear summary including:
11419
+ 1. What you found (files, functions, patterns)
11420
+ 2. Key insights or observations
11421
+ 3. Relevant code locations
11422
+
11423
+ Be thorough but concise. Your summary will be used by the main agent.`;
11424
+ case "plan":
11425
+ return `You are a task planning specialist. Your job is to break down complex tasks into clear, actionable steps.
11426
+
11427
+ Focus on:
11428
+ - Identifying dependencies and blockers
11429
+ - Creating logical sequence of steps
11430
+ - Estimating scope and priorities
11431
+
11432
+ Provide a structured plan that the main agent can execute.`;
11433
+ case "review":
11434
+ return `You are a code review specialist. Your job is to analyze code quality and identify issues.
11435
+
11436
+ Focus on:
11437
+ - Code quality and best practices
11438
+ - Potential bugs and edge cases
11439
+ - Performance and security considerations
11440
+
11441
+ Provide actionable feedback with specific file and line references.`;
11442
+ default:
11443
+ return "You are a helpful AI assistant.";
11444
+ }
11445
+ }
11446
+ /**
11447
+ * Summarize subagent result for parent agent
11448
+ */
11449
+ summarizeResult(result, type) {
11450
+ const { finalAnswer, steps, completionInfo } = result;
11451
+ const toolCalls = steps.filter((s) => s.type === "action");
11452
+ const filesRead = toolCalls.filter((s) => s.metadata?.toolName === "file_read").length;
11453
+ const searches = toolCalls.filter((s) => s.metadata?.toolName === "grep_search").length;
11454
+ const globs = toolCalls.filter((s) => s.metadata?.toolName === "glob_files").length;
11455
+ let summary = `**${type.charAt(0).toUpperCase() + type.slice(1)} Subagent Results**
11456
+
11457
+ `;
11458
+ summary += `*Execution: ${completionInfo.iterations} iterations, ${completionInfo.toolCalls} tool calls*
11459
+
11460
+ `;
11461
+ if (type === "explore") {
11462
+ summary += `*Exploration: ${filesRead} files read, ${searches} searches, ${globs} glob patterns*
11463
+
11464
+ `;
11465
+ }
11466
+ const maxLength = 1e3;
11467
+ if (finalAnswer.length > maxLength) {
11468
+ summary += finalAnswer.slice(0, maxLength) + "\n\n...(truncated)";
11469
+ } else {
11470
+ summary += finalAnswer;
11471
+ }
11472
+ return summary;
11473
+ }
11474
+ };
11475
+
11476
+ // src/agents/configs.ts
11477
+ var EXPLORE_CONFIG = {
11478
+ type: "explore",
11479
+ model: "claude-3-5-haiku-20241022",
11480
+ systemPrompt: `You are a code exploration specialist. Your job is to search and analyze codebases efficiently.
11481
+
11482
+ Focus on:
11483
+ - Finding relevant files and functions
11484
+ - Understanding code structure and patterns
11485
+ - Providing clear, concise summaries
11486
+
11487
+ You have read-only access. Use file_read, grep_search, and glob_files to explore.
11488
+
11489
+ When you find what you're looking for, provide a clear summary including:
11490
+ 1. What you found (files, functions, patterns)
11491
+ 2. Key insights or observations
11492
+ 3. Relevant code locations
11493
+
11494
+ Be thorough but concise. Your summary will be used by the main agent.`,
11495
+ allowedTools: [
11496
+ "file_read",
11497
+ "grep_search",
11498
+ "glob_files",
11499
+ "bash_execute",
11500
+ // Only for read-only commands like ls, cat, find
11501
+ "current_datetime",
11502
+ "math_evaluate"
11503
+ ],
11504
+ deniedTools: [
11505
+ "create_file",
11506
+ "edit_file",
11507
+ "delete_file",
11508
+ "web_search",
11509
+ "weather_info",
11510
+ "blog_publish",
11511
+ "blog_edit",
11512
+ "blog_draft"
11513
+ ],
11514
+ maxIterations: {
11515
+ quick: 2,
11516
+ medium: 5,
11517
+ very_thorough: 10
11518
+ },
11519
+ defaultThoroughness: "medium"
11520
+ };
11521
+ var PLAN_CONFIG = {
11522
+ type: "plan",
11523
+ model: "claude-3-5-haiku-20241022",
11524
+ systemPrompt: `You are a task planning specialist. Your job is to break down complex tasks into clear, actionable steps.
11525
+
11526
+ Focus on:
11527
+ - Identifying dependencies and blockers
11528
+ - Creating logical sequence of steps
11529
+ - Estimating scope and priorities
11530
+
11531
+ You can explore the codebase to understand the current architecture before planning.
11532
+
11533
+ Provide a structured plan that the main agent can execute.`,
11534
+ allowedTools: ["file_read", "grep_search", "glob_files", "bash_execute", "current_datetime", "math_evaluate"],
11535
+ deniedTools: ["create_file", "edit_file", "delete_file", "web_search", "weather_info"],
11536
+ maxIterations: {
11537
+ quick: 3,
11538
+ medium: 7,
11539
+ very_thorough: 12
11540
+ },
11541
+ defaultThoroughness: "medium"
11542
+ };
11543
+ var REVIEW_CONFIG = {
11544
+ type: "review",
11545
+ model: "claude-3-5-sonnet-20241022",
11546
+ // Use Sonnet for better reasoning
11547
+ systemPrompt: `You are a code review specialist. Your job is to analyze code quality and identify issues.
11548
+
11549
+ Focus on:
11550
+ - Code quality and best practices
11551
+ - Potential bugs and edge cases
11552
+ - Performance and security considerations
11553
+
11554
+ You have read-only access to analyze code.
11555
+
11556
+ Provide actionable feedback with specific file and line references.`,
11557
+ allowedTools: ["file_read", "grep_search", "glob_files", "bash_execute", "current_datetime"],
11558
+ deniedTools: ["create_file", "edit_file", "delete_file", "web_search", "weather_info"],
11559
+ maxIterations: {
11560
+ quick: 3,
11561
+ medium: 8,
11562
+ very_thorough: 15
11563
+ },
11564
+ defaultThoroughness: "medium"
11565
+ };
11566
+ function getDefaultSubagentConfigs() {
11567
+ return /* @__PURE__ */ new Map([
11568
+ ["explore", EXPLORE_CONFIG],
11569
+ ["plan", PLAN_CONFIG],
11570
+ ["review", REVIEW_CONFIG]
11571
+ ]);
11572
+ }
11573
+
11574
+ // src/agents/delegateTool.ts
11575
+ function createSubagentDelegateTool(orchestrator, parentSessionId) {
11576
+ return {
11577
+ toolFn: async (args) => {
11578
+ const params = args;
11579
+ if (!params.task) {
11580
+ throw new Error("subagent_delegate: task parameter is required");
11581
+ }
11582
+ if (!params.type) {
11583
+ throw new Error("subagent_delegate: type parameter is required");
11584
+ }
11585
+ const thoroughness = params.thoroughness || "medium";
11586
+ const type = params.type;
11587
+ const result = await orchestrator.delegateToSubagent({
11588
+ task: params.task,
11589
+ type,
11590
+ thoroughness,
11591
+ parentSessionId
11592
+ });
11593
+ return result.summary;
11594
+ },
11595
+ toolSchema: {
11596
+ name: "subagent_delegate",
11597
+ description: `Delegate a task to a specialized subagent for focused work.
11598
+
11599
+ **When to use this tool:**
11600
+ - **Explore**: When you need to search through the codebase, find files, or understand code structure (read-only)
11601
+ - **Plan**: When you need to break down a complex task into actionable steps
11602
+ - **Review**: When you need to analyze code quality and identify potential issues
11603
+
11604
+ **Benefits:**
11605
+ - Keeps main conversation focused and clean
11606
+ - Uses specialized prompts optimized for each task type
11607
+ - Faster execution with appropriate models (Haiku for explore/plan, Sonnet for review)
11608
+
11609
+ **Example uses:**
11610
+ - "Find all files that use the authentication system" \u2192 explore
11611
+ - "Search for components that handle user input" \u2192 explore
11612
+ - "Break down implementing a new feature into steps" \u2192 plan
11613
+ - "Review this module for potential bugs" \u2192 review`,
11614
+ parameters: {
11615
+ type: "object",
11616
+ properties: {
11617
+ task: {
11618
+ type: "string",
11619
+ 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."
11620
+ },
11621
+ type: {
11622
+ type: "string",
11623
+ enum: ["explore", "plan", "review"],
11624
+ description: `Type of subagent to use:
11625
+ - explore: Read-only codebase exploration and search (fast, uses Haiku)
11626
+ - plan: Task breakdown and planning (uses Haiku)
11627
+ - review: Code quality analysis and review (uses Sonnet for better reasoning)`
11628
+ },
11629
+ thoroughness: {
11630
+ type: "string",
11631
+ enum: ["quick", "medium", "very_thorough"],
11632
+ description: `How thoroughly to execute:
11633
+ - quick: Fast lookup, 1-2 iterations
11634
+ - medium: Balanced exploration, 3-5 iterations (default)
11635
+ - very_thorough: Comprehensive analysis, 8-10 iterations`
11636
+ }
11637
+ },
11638
+ required: ["task", "type"]
11639
+ }
11640
+ }
11641
+ };
11642
+ }
11643
+
11789
11644
  // src/index.tsx
11790
11645
  process.removeAllListeners("warning");
11791
11646
  process.on("warning", (warning) => {
@@ -11800,19 +11655,6 @@ var usageCache = null;
11800
11655
  function CliApp() {
11801
11656
  const { exit } = useApp();
11802
11657
  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
11658
  const [state, setState] = useState8({
11817
11659
  session: null,
11818
11660
  sessionStore: new SessionStore(),
@@ -11825,7 +11667,6 @@ function CliApp() {
11825
11667
  agent: null,
11826
11668
  mcpManager: null,
11827
11669
  permissionManager: null,
11828
- workspaceManager: new WorkspaceManager(getInitialWorkspaces()),
11829
11670
  permissionPrompt: null,
11830
11671
  trustLocationSelector: null,
11831
11672
  rewindSelector: null,
@@ -11930,8 +11771,7 @@ function CliApp() {
11930
11771
  totalTokens: 0,
11931
11772
  totalCost: 0,
11932
11773
  toolCallCount: 0
11933
- },
11934
- workingDirectories: state.workspaceManager.getWorkspaces()
11774
+ }
11935
11775
  };
11936
11776
  setState((prev) => ({
11937
11777
  ...prev,
@@ -11976,8 +11816,7 @@ function CliApp() {
11976
11816
  totalTokens: 0,
11977
11817
  totalCost: 0,
11978
11818
  toolCallCount: 0
11979
- },
11980
- workingDirectories: state.workspaceManager.getWorkspaces()
11819
+ }
11981
11820
  };
11982
11821
  await logger.initialize(newSession.id);
11983
11822
  logger.setVerbose(process.env.B4M_VERBOSE === "1");
@@ -12024,7 +11863,6 @@ function CliApp() {
12024
11863
  llm,
12025
11864
  modelInfo.id,
12026
11865
  permissionManager,
12027
- state.workspaceManager,
12028
11866
  promptFn,
12029
11867
  agentContext,
12030
11868
  state.configStore,
@@ -12040,7 +11878,34 @@ function CliApp() {
12040
11878
  console.log(` \u{1F4E1} ${serverName}: ${count} tool(s)`);
12041
11879
  });
12042
11880
  }
12043
- const allTools = [...b4mTools, ...mcpTools];
11881
+ const subagentConfigs = getDefaultSubagentConfigs();
11882
+ if (config.subagents) {
11883
+ if (config.subagents.explore) {
11884
+ const currentConfig = subagentConfigs.get("explore");
11885
+ subagentConfigs.set("explore", { ...currentConfig, ...config.subagents.explore });
11886
+ }
11887
+ if (config.subagents.plan) {
11888
+ const currentConfig = subagentConfigs.get("plan");
11889
+ subagentConfigs.set("plan", { ...currentConfig, ...config.subagents.plan });
11890
+ }
11891
+ if (config.subagents.review) {
11892
+ const currentConfig = subagentConfigs.get("review");
11893
+ subagentConfigs.set("review", { ...currentConfig, ...config.subagents.review });
11894
+ }
11895
+ }
11896
+ const orchestrator = new SubagentOrchestrator({
11897
+ userId: config.userId,
11898
+ llm,
11899
+ logger: silentLogger,
11900
+ permissionManager,
11901
+ showPermissionPrompt: promptFn,
11902
+ configStore: state.configStore,
11903
+ apiClient,
11904
+ subagentConfigs
11905
+ });
11906
+ const subagentDelegateTool = createSubagentDelegateTool(orchestrator, newSession.id);
11907
+ const allTools = [...b4mTools, ...mcpTools, subagentDelegateTool];
11908
+ console.log(`\u{1F916} Subagent delegation enabled (explore, plan, review)`);
12044
11909
  const projectDir = state.configStore.getProjectConfigDir();
12045
11910
  const contextResult = await loadContextFiles(projectDir);
12046
11911
  if (contextResult.globalContext) {
@@ -12077,6 +11942,15 @@ FOR CODING TASKS:
12077
11942
  - When searching for code: Use grep_search or glob_files to find relevant files
12078
11943
  - Permission system will ask for approval before any file writes
12079
11944
 
11945
+ SUBAGENT DELEGATION:
11946
+ - You have access to specialized subagents via the subagent_delegate tool
11947
+ - Use subagents for focused exploration, planning, or review tasks:
11948
+ * explore: Fast read-only codebase search (e.g., "find all auth files", "locate API endpoints")
11949
+ * plan: Break down complex tasks into actionable steps
11950
+ * review: Analyze code quality and identify issues
11951
+ - Subagents keep the main conversation clean and run faster with optimized models
11952
+ - Delegate when you need to search extensively or analyze code structure
11953
+
12080
11954
  FOR GENERAL TASKS:
12081
11955
  - Use available tools to get information (weather, web search, calculations, etc.)
12082
11956
  - When user asks follow-up questions, use conversation context to understand what they're referring to
@@ -12087,6 +11961,7 @@ EXAMPLES:
12087
11961
  - "how about Japan?" \u2192 use weather tool for Japan (applying same question from context)
12088
11962
  - "enhance README" \u2192 file_read \u2192 generate \u2192 create_file
12089
11963
  - "what packages installed?" \u2192 glob_files "**/package.json" \u2192 file_read
11964
+ - "find all components using React hooks" \u2192 subagent_delegate(task="find all components using React hooks", type="explore")
12090
11965
 
12091
11966
  Remember: Use context from previous messages to understand follow-up questions.${contextSection}`;
12092
11967
  const maxIterations = config.preferences.maxIterations === null ? 999999 : config.preferences.maxIterations;
@@ -12365,11 +12240,6 @@ Tool Permissions:
12365
12240
  /untrust <tool-name> - Remove tool from trusted list
12366
12241
  /trusted - List all trusted tools
12367
12242
 
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
12243
  Project Configuration:
12374
12244
  /project-config - Show merged project configuration
12375
12245
  `);
@@ -12411,24 +12281,7 @@ Project Configuration:
12411
12281
  }
12412
12282
  await logger.initialize(loadedSession.id);
12413
12283
  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
- }
12284
+ setState((prev) => ({ ...prev, session: loadedSession }));
12432
12285
  setStoreSession(loadedSession);
12433
12286
  useCliStore.getState().clearAgentSteps();
12434
12287
  useCliStore.getState().clearPendingMessages();
@@ -12624,74 +12477,6 @@ Project Configuration:
12624
12477
  }
12625
12478
  console.log("");
12626
12479
  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
12480
  case "login":
12696
12481
  setState((prev) => ({ ...prev, showLoginFlow: true }));
12697
12482
  break;
@@ -12728,8 +12513,7 @@ Project Configuration:
12728
12513
  totalTokens: 0,
12729
12514
  totalCost: 0,
12730
12515
  toolCallCount: 0
12731
- },
12732
- workingDirectories: state.workspaceManager.getWorkspaces()
12516
+ }
12733
12517
  };
12734
12518
  await logger.initialize(newSession.id);
12735
12519
  logger.debug("=== New Session Started via /clear ===");