@bike4mind/cli 0.9.3 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +13 -3
  2. package/bin/bike4mind-cli.mjs +44 -1
  3. package/dist/{BubblewrapRuntime-CUD3bsgG.mjs → BubblewrapRuntime-CkL9-gnG.mjs} +1 -1
  4. package/dist/{ConfigStore-BauEpjvT.mjs → ConfigStore-aeJGqjKm.mjs} +116 -15
  5. package/dist/ProxyManager-ByuAHFMq.mjs +3 -0
  6. package/dist/{SandboxOrchestrator-C4oDqltp.mjs → SandboxOrchestrator-BS6gALNq.mjs} +1 -1
  7. package/dist/{SandboxOrchestrator-B4GcZdBc.mjs → SandboxOrchestrator-BoINxbX4.mjs} +1 -1
  8. package/dist/{SandboxRuntimeAdapter-DXa3nFOw.mjs → SandboxRuntimeAdapter-CKelGICD.mjs} +1 -1
  9. package/dist/{SandboxRuntimeAdapter-D1RUReNL.mjs → SandboxRuntimeAdapter-ChGlxSGQ.mjs} +2 -2
  10. package/dist/{SeatbeltRuntime-CTElMR9Q.mjs → SeatbeltRuntime-Qqt19cAN.mjs} +1 -1
  11. package/dist/{bashExecute-pYljpfPn-BZXHMQEl.mjs → bashExecute-B1N1lMOS-TZVDbcQ4.mjs} +1 -1
  12. package/dist/commands/apiCommand.mjs +45 -0
  13. package/dist/commands/doctorCommand.mjs +2 -2
  14. package/dist/commands/headlessCommand.mjs +6 -6
  15. package/dist/commands/mcpCommand.mjs +1 -1
  16. package/dist/commands/updateCommand.mjs +2 -2
  17. package/dist/{createFile-C1JoeuYh-metInFKd.mjs → createFile-DPv180yF-BnWFIxey.mjs} +2 -2
  18. package/dist/{deleteFile-BTberNGj-CW922hRM.mjs → deleteFile-BdjUwUQF-B3XOJmg3.mjs} +2 -2
  19. package/dist/{globFiles-Bez8QCbS-DZb6McbJ.mjs → globFiles-DjfDGaUK-CNR8pMRC.mjs} +2 -2
  20. package/dist/{grepSearch-BxucZWO8-lPRv6R6F.mjs → grepSearch-DJs-cubo-Bm0Y8oS3.mjs} +2 -2
  21. package/dist/index.mjs +258 -39
  22. package/dist/{pathValidation-CIytuhr3-Dt5dntLx.mjs → pathValidation-D8tjkQXE-1HwvsuYT.mjs} +1 -1
  23. package/dist/store-DgzCTRkN.mjs +3 -0
  24. package/dist/{tools-CpWE3Qif.mjs → tools-RdGu37Lw.mjs} +11217 -10240
  25. package/dist/types-CqscS34o.mjs +3 -0
  26. package/dist/{updateChecker-DhWcEKAu.mjs → updateChecker-CP_jeER9.mjs} +1 -1
  27. package/dist/utils-BGtSXfce.mjs +3 -0
  28. package/dist/utils-PpNti-tY.mjs +146 -0
  29. package/package.json +31 -31
  30. package/dist/ProxyManager-DIAAw902.mjs +0 -3
  31. package/dist/store-5PXzE9DM.mjs +0 -3
  32. package/dist/types-DK3P88Px.mjs +0 -3
  33. /package/dist/{ImageStore-BFp_d12J.mjs → ImageStore-BVmEG1xc.mjs} +0 -0
  34. /package/dist/{ProxyManager-BsCoxpns.mjs → ProxyManager-CV94yZUW.mjs} +0 -0
  35. /package/dist/{StderrViolationParser-BFP4bo7I.mjs → StderrViolationParser-CS43a-TP.mjs} +0 -0
  36. /package/dist/{ViolationLogStore-Dp6HF0nz.mjs → ViolationLogStore-B-plqJfn.mjs} +0 -0
  37. /package/dist/{ripgrepCheck-DIu4apVE.mjs → ripgrepCheck-BmkyTK2i.mjs} +0 -0
  38. /package/dist/{store-BonrwrMi.mjs → store-DV5s-qni.mjs} +0 -0
  39. /package/dist/{terminalSetup-DxloCowq.mjs → terminalSetup-BbJt04ZG.mjs} +0 -0
  40. /package/dist/{treeSitterEngine-Cw2LbVZT.mjs → treeSitterEngine-BRbQ9b7I.mjs} +0 -0
  41. /package/dist/{types-DBEjF9YS.mjs → types-LyRNHOiS.mjs} +0 -0
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { $ as CommandHistoryStore, A as formatStep, B as DEFAULT_RETRY_CONFIG, C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as PermissionManager, G as OllamaBackend, H as clearFeatureModuleTools, I as generateCliTools, J as buildSkillsPromptSection, K as getPlanModeFilePath, L as ALWAYS_DENIED_FOR_AGENTS, M as loadContextFiles, N as getApiUrl, O as McpManager, P as getEnvironmentName, Q as CheckpointStore, R as DEFAULT_AGENT_MODEL, S as ApiClient, T as FallbackLlmBackend, U as registerFeatureModuleTools, V as DEFAULT_THOROUGHNESS, W as setWebSocketToolExecutor, X as ReActAgent, Y as isReadOnlyTool, Z as CustomCommandStore, _ as createAgentDelegateTool, a as createBlockerTools, at as mergeCommands, b as createSkillTool, c as createDecisionStore, ct as warmFileCache, d as createFindDefinitionTool, et as SessionStore, f as createTodoStore, g as BackgroundAgentManager, h as createBackgroundAgentTools, i as createBlockerStore, it as searchCommands, j as extractCompactInstructions, k as substituteArguments, l as formatDecisionsOutput, m as createCoordinateTaskTool, n as createReviewGateTool, nt as hasFileReferences, o as formatBlockersOutput, ot as formatFileSize, p as createWriteTodosTool, q as buildSystemPrompt, r as formatReviewGatesOutput, rt as processFileReferences, s as createDecisionLogTool, st as searchFiles, t as createReviewGateStore, tt as OAuthClient, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, x as parseAgentConfig, y as SubagentOrchestrator, z as DEFAULT_MAX_ITERATIONS } from "./tools-CpWE3Qif.mjs";
3
- import { n as useCliStore, t as selectActiveBackgroundAgents } from "./store-BonrwrMi.mjs";
4
- import { Xt as validateNotebookPath$1, Yt as validateJupyterKernelName, g as CREDIT_DEDUCT_TRANSACTION_TYPES, n as logger, t as ConfigStore, v as ChatModels } from "./ConfigStore-BauEpjvT.mjs";
5
- import { a as version, t as checkForUpdate } from "./updateChecker-DhWcEKAu.mjs";
2
+ import { $ as CommandHistoryStore, A as formatStep, B as DEFAULT_RETRY_CONFIG, C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as PermissionManager, G as OllamaBackend, H as clearFeatureModuleTools, I as generateCliTools, J as buildSkillsPromptSection, K as getPlanModeFilePath, L as ALWAYS_DENIED_FOR_AGENTS, M as loadContextFiles, N as getApiUrl, O as McpManager, P as getEnvironmentName, Q as CheckpointStore, R as DEFAULT_AGENT_MODEL, S as ApiClient, T as FallbackLlmBackend, U as registerFeatureModuleTools, V as DEFAULT_THOROUGHNESS, W as setWebSocketToolExecutor, X as ReActAgent, Y as isReadOnlyTool, Z as CustomCommandStore, _ as createAgentDelegateTool, a as createBlockerTools, at as mergeCommands, b as createSkillTool, c as createDecisionStore, ct as warmFileCache, d as createFindDefinitionTool, et as SessionStore, f as createTodoStore, g as BackgroundAgentManager, h as createBackgroundAgentTools, i as createBlockerStore, it as searchCommands, j as extractCompactInstructions, k as substituteArguments, l as formatDecisionsOutput, m as createCoordinateTaskTool, n as createReviewGateTool, nt as hasFileReferences, o as formatBlockersOutput, ot as formatFileSize, p as createWriteTodosTool, q as buildSystemPrompt, r as formatReviewGatesOutput, rt as processFileReferences, s as createDecisionLogTool, st as searchFiles, t as createReviewGateStore, tt as OAuthClient, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, x as parseAgentConfig, y as SubagentOrchestrator, z as DEFAULT_MAX_ITERATIONS } from "./tools-RdGu37Lw.mjs";
3
+ import { n as useCliStore, t as selectActiveBackgroundAgents } from "./store-DV5s-qni.mjs";
4
+ import { Xt as validateNotebookPath$1, Yt as validateJupyterKernelName, g as CREDIT_DEDUCT_TRANSACTION_TYPES, n as logger, t as ConfigStore, v as ChatModels } from "./ConfigStore-aeJGqjKm.mjs";
5
+ import { a as version, t as checkForUpdate } from "./updateChecker-CP_jeER9.mjs";
6
6
  import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
7
7
  import { Box, Static, Text, render, useApp, useInput, usePaste, useStdout } from "ink";
8
8
  import { execSync } from "child_process";
@@ -287,8 +287,7 @@ function CommandAutocomplete({ commands, selectedIndex }) {
287
287
  let startIndex = 0;
288
288
  let endIndex = Math.min(VIEWPORT_SIZE, totalCommands);
289
289
  if (totalCommands > VIEWPORT_SIZE) {
290
- const halfViewport = Math.floor(VIEWPORT_SIZE / 2);
291
- startIndex = Math.max(0, selectedIndex - halfViewport);
290
+ startIndex = Math.max(0, selectedIndex - Math.floor(VIEWPORT_SIZE / 2));
292
291
  endIndex = Math.min(totalCommands, startIndex + VIEWPORT_SIZE);
293
292
  if (endIndex === totalCommands) startIndex = Math.max(0, totalCommands - VIEWPORT_SIZE);
294
293
  }
@@ -329,8 +328,7 @@ function FileAutocomplete({ files, selectedIndex, query }) {
329
328
  let startIndex = 0;
330
329
  let endIndex = Math.min(VIEWPORT_SIZE, totalFiles);
331
330
  if (totalFiles > VIEWPORT_SIZE) {
332
- const halfViewport = Math.floor(VIEWPORT_SIZE / 2);
333
- startIndex = Math.max(0, selectedIndex - halfViewport);
331
+ startIndex = Math.max(0, selectedIndex - Math.floor(VIEWPORT_SIZE / 2));
334
332
  endIndex = Math.min(totalFiles, startIndex + VIEWPORT_SIZE);
335
333
  if (endIndex === totalFiles) startIndex = Math.max(0, totalFiles - VIEWPORT_SIZE);
336
334
  }
@@ -2164,6 +2162,10 @@ function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPe
2164
2162
  await onCommand(command, args);
2165
2163
  return;
2166
2164
  }
2165
+ if (trimmed === "exit" || trimmed === "quit") {
2166
+ await onCommand(trimmed, []);
2167
+ return;
2168
+ }
2167
2169
  let messageToSend = trimmed;
2168
2170
  if (hasFileReferences(trimmed)) {
2169
2171
  const processed = await processFileReferences(trimmed);
@@ -2668,7 +2670,7 @@ const ROLE_LABELS = {
2668
2670
  function buildHandoffPrompt(session) {
2669
2671
  if (session.messages.length < 4) return "";
2670
2672
  const filtered = session.messages.filter((m) => !isInjectedHandoff(m));
2671
- const conversation = filtered.length > MAX_CONVERSATION_MESSAGES ? filtered.slice(-MAX_CONVERSATION_MESSAGES) : filtered;
2673
+ const conversation = filtered.length > MAX_CONVERSATION_MESSAGES ? filtered.slice(-50) : filtered;
2672
2674
  let prompt = `You are generating a structured session handoff so the next session (or another agent) can pick up seamlessly without re-reading the full chat history.
2673
2675
 
2674
2676
  Output a single JSON object — no prose, no markdown fences — with exactly these fields:
@@ -3759,6 +3761,196 @@ Unlike agent_delegate (which uses pre-defined agents), this tool lets you compos
3759
3761
  };
3760
3762
  }
3761
3763
  //#endregion
3764
+ //#region src/tools/deferredToolRegistry.ts
3765
+ /**
3766
+ * Registry of tool schemas that are NOT loaded into the model's initial tool
3767
+ * list. The model sees only the names (via the system prompt directory) and
3768
+ * must call the `tool_search` meta-tool to load schemas on demand.
3769
+ *
3770
+ * This mirrors Claude Code's deferred-tool pattern. The win is large for
3771
+ * heavy MCP integrations (e.g. 41 GitHub MCP tools at ~250-350 tokens of
3772
+ * JSONSchema each = ~10-15k tokens per turn that's now ~1-1.5k of names).
3773
+ */
3774
+ var DeferredToolRegistry = class {
3775
+ constructor() {
3776
+ this.byName = /* @__PURE__ */ new Map();
3777
+ }
3778
+ /** Replace registry contents with the supplied tools. Idempotent. */
3779
+ register(tools) {
3780
+ this.byName.clear();
3781
+ for (const tool of tools) this.byName.set(tool.toolSchema.name, tool);
3782
+ logger.debug(`[DeferredToolRegistry] Registered ${tools.length} deferred tool(s)`);
3783
+ }
3784
+ clear() {
3785
+ this.byName.clear();
3786
+ }
3787
+ size() {
3788
+ return this.byName.size;
3789
+ }
3790
+ has(name) {
3791
+ return this.byName.has(name);
3792
+ }
3793
+ get(name) {
3794
+ return this.byName.get(name);
3795
+ }
3796
+ getAll() {
3797
+ return Array.from(this.byName.values());
3798
+ }
3799
+ /** Return tools whose names appear in the supplied list, in input order. */
3800
+ getByNames(names) {
3801
+ const found = [];
3802
+ for (const name of names) {
3803
+ const tool = this.byName.get(name);
3804
+ if (tool) found.push(tool);
3805
+ }
3806
+ return found;
3807
+ }
3808
+ /**
3809
+ * Rank-search deferred tools by query terms. Name matches outrank
3810
+ * description matches; exact substring on name wins ties.
3811
+ */
3812
+ searchByKeywords(query, maxResults) {
3813
+ const terms = query.toLowerCase().split(/\s+/).filter((t) => t.length > 0);
3814
+ if (terms.length === 0) return [];
3815
+ const scored = [];
3816
+ for (const tool of this.byName.values()) {
3817
+ const name = tool.toolSchema.name.toLowerCase();
3818
+ const desc = (tool.toolSchema.description || "").toLowerCase();
3819
+ let score = 0;
3820
+ for (const term of terms) {
3821
+ if (name.includes(term)) score += 10;
3822
+ if (desc.includes(term)) score += 1;
3823
+ }
3824
+ if (score > 0) scored.push({
3825
+ tool,
3826
+ score
3827
+ });
3828
+ }
3829
+ scored.sort((a, b) => b.score - a.score);
3830
+ return scored.slice(0, maxResults).map((s) => s.tool);
3831
+ }
3832
+ /** Return the directory entries used to render the system-prompt reminder. */
3833
+ getDirectoryNames() {
3834
+ return Array.from(this.byName.keys()).sort();
3835
+ }
3836
+ };
3837
+ const deferredToolRegistry = new DeferredToolRegistry();
3838
+ //#endregion
3839
+ //#region src/tools/toolSearchTool.ts
3840
+ /**
3841
+ * Default number of tools returned for a keyword search. Matches Claude
3842
+ * Code's ToolSearch convention. 5 keeps the response payload small while
3843
+ * surfacing enough alternatives for the model to refine its query.
3844
+ */
3845
+ const DEFAULT_MAX_RESULTS = 5;
3846
+ /**
3847
+ * Runtime validation for tool_search params. The LLM produces these
3848
+ * values, so we validate at this boundary rather than trusting the
3849
+ * shape. Coerces `max_results` from string→number for models that emit
3850
+ * numeric-looking strings.
3851
+ */
3852
+ const ToolSearchParamsSchema = z.object({
3853
+ query: z.string().min(1, "query must be a non-empty string"),
3854
+ max_results: z.coerce.number().int().min(1).max(20).optional()
3855
+ });
3856
+ /**
3857
+ * Parse the query string. Two forms:
3858
+ * - `select:name1,name2,...` — exact-name selection
3859
+ * - free text — keyword search across name + description
3860
+ */
3861
+ function parseQuery(query) {
3862
+ const trimmed = query.trim();
3863
+ const selectMatch = trimmed.match(/^select:(.+)$/i);
3864
+ if (selectMatch) return {
3865
+ mode: "select",
3866
+ names: selectMatch[1].split(",").map((n) => n.trim()).filter((n) => n.length > 0)
3867
+ };
3868
+ return {
3869
+ mode: "search",
3870
+ text: trimmed
3871
+ };
3872
+ }
3873
+ /**
3874
+ * Format the loaded-tools response. Mirrors Claude Code's convention:
3875
+ * one <function>{...}</function> line per matched tool. The model has
3876
+ * already seen this format in its tool-registration system messages, so
3877
+ * it parses without additional explanation.
3878
+ *
3879
+ * Note: the schemas are *also* injected into context.tools by the caller,
3880
+ * so on the next iteration the model gets them as native tool definitions.
3881
+ * The text response here is for in-turn awareness and audit trail.
3882
+ */
3883
+ function renderToolsBlock(tools) {
3884
+ if (tools.length === 0) return "";
3885
+ return `<functions>\n${tools.map((tool) => {
3886
+ const schema = {
3887
+ description: tool.toolSchema.description,
3888
+ name: tool.toolSchema.name,
3889
+ parameters: tool.toolSchema.parameters
3890
+ };
3891
+ return `<function>${JSON.stringify(schema)}</function>`;
3892
+ }).join("\n")}\n</functions>`;
3893
+ }
3894
+ /**
3895
+ * Build the tool_search meta-tool. The returned tool has a closure over
3896
+ * the supplied `toolListAccessor`, which it uses to push newly-resolved
3897
+ * tool schemas into the live agent context.
3898
+ *
3899
+ * Idempotent: re-loading a tool that's already in the context is a no-op.
3900
+ */
3901
+ function createToolSearchTool(toolListAccessor) {
3902
+ return {
3903
+ toolSchema: {
3904
+ name: "tool_search",
3905
+ description: "Fetches full schema definitions for deferred tools so they can be called. Deferred tools appear by name only in a system reminder; their parameter schemas are NOT loaded by default. Use this tool to load schemas on demand. Query forms: 'select:name1,name2' for exact selection, or free-text keywords to search by name and description. Once a tool's schema is returned, it becomes callable in subsequent turns.",
3906
+ parameters: {
3907
+ type: "object",
3908
+ properties: {
3909
+ query: {
3910
+ type: "string",
3911
+ description: "Either 'select:<comma-separated names>' to fetch specific tools, or free-text keywords (e.g. 'github pull request') to rank-search deferred tools."
3912
+ },
3913
+ max_results: {
3914
+ type: "number",
3915
+ description: `Maximum number of tools to return for keyword search. Defaults to ${DEFAULT_MAX_RESULTS}. Ignored for 'select:' queries.`
3916
+ }
3917
+ },
3918
+ required: ["query"]
3919
+ }
3920
+ },
3921
+ toolFn: async (params) => {
3922
+ const parsedParams = ToolSearchParamsSchema.safeParse(params ?? {});
3923
+ if (!parsedParams.success) {
3924
+ const issue = parsedParams.error.issues[0];
3925
+ return `tool_search: invalid parameters — ${issue.path.join(".") || "params"}: ${issue.message}`;
3926
+ }
3927
+ const { query, max_results } = parsedParams.data;
3928
+ const parsed = parseQuery(query);
3929
+ let matched;
3930
+ let unmatched = [];
3931
+ if (parsed.mode === "select") {
3932
+ matched = deferredToolRegistry.getByNames(parsed.names);
3933
+ const foundNames = new Set(matched.map((t) => t.toolSchema.name));
3934
+ unmatched = parsed.names.filter((n) => !foundNames.has(n));
3935
+ } else {
3936
+ const max = max_results ?? DEFAULT_MAX_RESULTS;
3937
+ matched = deferredToolRegistry.searchByKeywords(parsed.text, max);
3938
+ }
3939
+ if (matched.length === 0) return parsed.mode === "select" ? `tool_search: no deferred tools matched ${parsed.names.join(", ")}. Use a free-text query to search.` : `tool_search: no deferred tools matched query "${parsed.text}".`;
3940
+ const liveTools = toolListAccessor();
3941
+ const liveNames = new Set(liveTools.map((t) => t.toolSchema.name));
3942
+ let added = 0;
3943
+ for (const tool of matched) if (!liveNames.has(tool.toolSchema.name)) {
3944
+ liveTools.push(tool);
3945
+ added++;
3946
+ }
3947
+ logger.debug(`[tool_search] query="${query}" matched=${matched.length} added=${added} alreadyLoaded=${matched.length - added}`);
3948
+ const block = renderToolsBlock(matched);
3949
+ return `${`Loaded ${added} new tool schema(s)${added < matched.length ? ` (${matched.length - added} already loaded)` : ""}. These are now callable in your next message.${unmatched.length > 0 ? `\n\nNot found: ${unmatched.join(", ")}` : ""}`}\n\n${block}`;
3950
+ }
3951
+ };
3952
+ }
3953
+ //#endregion
3762
3954
  //#region src/features/FeatureModuleRegistry.ts
3763
3955
  /**
3764
3956
  * Manages the lifecycle of opt-in CLI feature modules.
@@ -5854,11 +6046,11 @@ function CliApp() {
5854
6046
  };
5855
6047
  const permissionManager = new PermissionManager(config.trustedTools || [], void 0, config.tools.disabled || []);
5856
6048
  const [{ createSandboxRuntime }, { SandboxOrchestrator }, { DEFAULT_SANDBOX_CONFIG }, { ProxyManager }, { ViolationLogStore }] = await Promise.all([
5857
- import("./SandboxRuntimeAdapter-DXa3nFOw.mjs"),
5858
- import("./SandboxOrchestrator-C4oDqltp.mjs"),
5859
- import("./types-DK3P88Px.mjs"),
5860
- import("./ProxyManager-DIAAw902.mjs"),
5861
- import("./ViolationLogStore-Dp6HF0nz.mjs")
6049
+ import("./SandboxRuntimeAdapter-CKelGICD.mjs"),
6050
+ import("./SandboxOrchestrator-BS6gALNq.mjs"),
6051
+ import("./types-CqscS34o.mjs"),
6052
+ import("./ProxyManager-ByuAHFMq.mjs"),
6053
+ import("./ViolationLogStore-B-plqJfn.mjs")
5862
6054
  ]);
5863
6055
  const sandboxConfig = config.sandbox ?? DEFAULT_SANDBOX_CONFIG;
5864
6056
  const checkpointStore = new CheckpointStore(state.configStore.getProjectConfigDir() || process.cwd());
@@ -5974,10 +6166,23 @@ function CliApp() {
5974
6166
  loadContextFiles(agentProjectDir)
5975
6167
  ]);
5976
6168
  const mcpTools = mcpManager.getTools();
6169
+ const deferredB4mToolNames = new Set([
6170
+ "math_evaluate",
6171
+ "dice_roll",
6172
+ "current_datetime",
6173
+ "recent_changes",
6174
+ "prompt_enhancement"
6175
+ ]);
6176
+ const deferredB4mTools = b4mTools.filter((t) => deferredB4mToolNames.has(t.toolSchema.name));
6177
+ const loadedB4mTools = b4mTools.filter((t) => !deferredB4mToolNames.has(t.toolSchema.name));
6178
+ deferredToolRegistry.register([...mcpTools, ...deferredB4mTools]);
5977
6179
  if (mcpTools.length > 0) {
5978
6180
  const serverSummaries = mcpManager.getToolCount().map((s) => `${s.serverName} (${s.count})`).join(", ");
5979
- startupLog.push(`🛠️ Loaded ${b4mTools.length} B4M + ${mcpTools.length} MCP tool(s): ${serverSummaries}`);
5980
- } else startupLog.push(`🛠️ Loaded ${b4mTools.length} B4M tool(s), no MCP tools`);
6181
+ startupLog.push(`🛠️ Loaded ${loadedB4mTools.length} B4M + ${mcpTools.length} MCP tool(s, ${deferredB4mTools.length + mcpTools.length} deferred): ${serverSummaries}`);
6182
+ } else {
6183
+ const suffix = deferredB4mTools.length > 0 ? ` (${deferredB4mTools.length} deferred)` : "";
6184
+ startupLog.push(`🛠️ Loaded ${loadedB4mTools.length} B4M tool(s)${suffix}, no MCP tools`);
6185
+ }
5981
6186
  const agentSummary = agentStore.getSummary();
5982
6187
  startupLog.push(`🤖 Loaded ${agentSummary.total} agent(s): ${agentSummary.builtin} built-in, ${agentSummary.global} global, ${agentSummary.project} project`);
5983
6188
  const orchestrator = new SubagentOrchestrator({
@@ -6051,9 +6256,14 @@ function CliApp() {
6051
6256
  cliTools.push(coordinateTaskTool);
6052
6257
  }
6053
6258
  const featureTools = featureRegistry.getAllTools();
6259
+ const agentToolsRef = { current: null };
6260
+ const toolSearchTool = deferredToolRegistry.size() > 0 ? createToolSearchTool(() => {
6261
+ if (!agentToolsRef.current) throw new Error("tool_search invoked before agent context was wired");
6262
+ return agentToolsRef.current;
6263
+ }) : null;
6054
6264
  const allTools = [
6055
- ...b4mTools,
6056
- ...mcpTools,
6265
+ ...loadedB4mTools,
6266
+ ...toolSearchTool ? [toolSearchTool] : [],
6057
6267
  ...cliTools,
6058
6268
  ...featureTools
6059
6269
  ];
@@ -6068,7 +6278,7 @@ function CliApp() {
6068
6278
  const moduleNames = featureRegistry.getModuleNames().join(", ");
6069
6279
  startupLog.push(`🏰 Feature modules: ${moduleNames} (${featureTools.length} tools)`);
6070
6280
  }
6071
- logger.debug(`Total tools available to agent: ${allTools.length} (${b4mTools.length} B4M + ${mcpTools.length} MCP + ${cliTools.length} CLI + ${featureTools.length} feature)`);
6281
+ logger.debug(`Total tools available to agent: ${allTools.length} (${loadedB4mTools.length} B4M loaded + ${cliTools.length} CLI + ${featureTools.length} feature + ${toolSearchTool ? 1 : 0} tool_search, ${deferredB4mTools.length} B4M + ${mcpTools.length} MCP deferred)`);
6072
6282
  if (contextResult.globalContext) startupLog.push(`📄 Global context: ${contextResult.globalContext.filename}`);
6073
6283
  if (contextResult.projectContext) startupLog.push(`📄 Project context: ${contextResult.projectContext.filename}`);
6074
6284
  for (const error of contextResult.errors) startupLog.push(`⚠️ Context file error: ${error}`);
@@ -6082,7 +6292,8 @@ function CliApp() {
6082
6292
  enableDynamicAgentCreation: config.preferences.enableDynamicAgentCreation === true,
6083
6293
  additionalDirectories,
6084
6294
  featureModulePrompts: featureModulePrompts || void 0,
6085
- planModeFilePath: mode === "plan" ? getPlanModeFilePath(newSession.id) : void 0
6295
+ planModeFilePath: mode === "plan" ? getPlanModeFilePath(newSession.id) : void 0,
6296
+ deferredToolNames: deferredToolRegistry.getDirectoryNames()
6086
6297
  });
6087
6298
  const cliSystemPrompt = buildPromptForMode(useCliStore.getState().interactionMode);
6088
6299
  const maxIterations = config.preferences.maxIterations === null ? 999999 : config.preferences.maxIterations;
@@ -6095,16 +6306,17 @@ function CliApp() {
6095
6306
  maxIterations,
6096
6307
  maxTokens: config.preferences.maxTokens,
6097
6308
  temperature: config.preferences.temperature,
6098
- systemPrompt: cliSystemPrompt
6309
+ systemPrompt: cliSystemPrompt,
6310
+ unknownToolResolver: async (toolName) => deferredToolRegistry.get(toolName) ?? null
6099
6311
  });
6312
+ agentToolsRef.current = agent.getTools();
6100
6313
  agentContext.currentAgent = agent;
6101
- const agentInternalContext = agent.context;
6102
6314
  let lastInteractionMode = useCliStore.getState().interactionMode;
6103
6315
  useCliStore.subscribe((s) => {
6104
6316
  if (s.interactionMode === lastInteractionMode) return;
6105
6317
  lastInteractionMode = s.interactionMode;
6106
6318
  if (agentContext.currentAgent !== agent) return;
6107
- agentInternalContext.systemPrompt = buildPromptForMode(s.interactionMode);
6319
+ agent.setSystemPrompt(buildPromptForMode(s.interactionMode));
6108
6320
  });
6109
6321
  agent.observationQueue = agentContext.observationQueue;
6110
6322
  const stepHandler = (step) => {
@@ -6560,7 +6772,8 @@ function CliApp() {
6560
6772
  customCommands: state.customCommandStore.getAllCommands(),
6561
6773
  enableSkillTool: config?.preferences.enableSkillTool !== false,
6562
6774
  additionalDirectories: state.additionalDirectories,
6563
- featureModulePrompts: state.featureRegistry?.getSystemPromptSections() || void 0
6775
+ featureModulePrompts: state.featureRegistry?.getSystemPromptSections() || void 0,
6776
+ deferredToolNames: deferredToolRegistry.getDirectoryNames()
6564
6777
  });
6565
6778
  if (tokenCounter.countSessionTokens(activeSession, systemPrompt).totalTokens >= threshold) {
6566
6779
  console.log("\n⚠️ Context window 80% full. Auto-compacting...\n");
@@ -6875,7 +7088,7 @@ function CliApp() {
6875
7088
  let imageStore = state.imageStore;
6876
7089
  if (!imageStore) {
6877
7090
  if (!imageStoreInitPromise.current) imageStoreInitPromise.current = (async () => {
6878
- const { ImageStore: ImageStoreClass } = await import("./ImageStore-BFp_d12J.mjs");
7091
+ const { ImageStore: ImageStoreClass } = await import("./ImageStore-BVmEG1xc.mjs");
6879
7092
  const newImageStore = new ImageStoreClass();
6880
7093
  setState((prev) => ({
6881
7094
  ...prev,
@@ -6924,8 +7137,7 @@ function CliApp() {
6924
7137
  console.log(`${emoji} ${label}:`);
6925
7138
  const terminalWidth = process.stdout.columns || 80;
6926
7139
  const nameWidth = 25;
6927
- const prefixWidth = 2 + nameWidth + 3;
6928
- const maxDescLength = Math.max(20, terminalWidth - prefixWidth);
7140
+ const maxDescLength = Math.max(20, terminalWidth - 30);
6929
7141
  agents.forEach((agent) => {
6930
7142
  const desc = agent.description.length > maxDescLength ? agent.description.slice(0, maxDescLength - 3) + "..." : agent.description;
6931
7143
  console.log(` ${agent.name.padEnd(nameWidth)} - ${desc}`);
@@ -7752,7 +7964,7 @@ Multi-line Input:
7752
7964
  const skillsTokens = skillsSection ? tokenCounter.countTokens(skillsSection) : 0;
7753
7965
  const agentDirectoryTokens = state.agentStore ? tokenCounter.countTokens(state.agentStore.getDirectoryContext()) : 0;
7754
7966
  const mcpTools = state.mcpManager?.getTools() || [];
7755
- const mcpToolsTokens = tokenCounter.countToolSchemaTokens(mcpTools);
7967
+ const deferredNames = deferredToolRegistry.getDirectoryNames();
7756
7968
  const mcpToolCount = state.mcpManager?.getToolCount() || [];
7757
7969
  const systemPrompt = buildSystemPrompt(variantForCount, {
7758
7970
  contextContent: state.contextContent,
@@ -7760,10 +7972,13 @@ Multi-line Input:
7760
7972
  customCommands: commands,
7761
7973
  enableSkillTool: state.config?.preferences.enableSkillTool !== false,
7762
7974
  additionalDirectories: state.additionalDirectories,
7763
- featureModulePrompts: state.featureRegistry?.getSystemPromptSections() || void 0
7975
+ featureModulePrompts: state.featureRegistry?.getSystemPromptSections() || void 0,
7976
+ deferredToolNames: deferredNames
7764
7977
  });
7765
7978
  const usage = tokenCounter.countSessionTokens(state.session, systemPrompt);
7766
- const totalWithTools = usage.totalTokens + mcpToolsTokens;
7979
+ const agentTools = state.agent?.getTools() ?? [];
7980
+ const agentToolsTokens = tokenCounter.countToolSchemaTokens(agentTools);
7981
+ const totalWithTools = usage.totalTokens + agentToolsTokens;
7767
7982
  const usagePercent = totalWithTools / contextWindow * 100;
7768
7983
  const BAR_WIDTH = 40;
7769
7984
  const filledWidth = Math.min(Math.round(usagePercent / 100 * BAR_WIDTH), BAR_WIDTH);
@@ -7780,10 +7995,14 @@ Multi-line Input:
7780
7995
  console.log(` Agent Directory: ${agentDirectoryTokens.toLocaleString()} tokens (${agentCount} agents)`);
7781
7996
  }
7782
7997
  if (mcpTools.length > 0) {
7783
- console.log("\nMCP Tools:");
7784
- console.log(` Total: ${mcpToolsTokens.toLocaleString()} tokens (${mcpTools.length} tools)`);
7998
+ console.log("\nMCP Tools (deferred):");
7999
+ console.log(` Schemas: load on demand via tool_search (${mcpTools.length} tools available)`);
7785
8000
  for (const { serverName, count } of mcpToolCount) console.log(` ${serverName}: ${count} tools`);
7786
8001
  }
8002
+ if (agentTools.length > 0) {
8003
+ console.log("\nLoaded Tool Schemas:");
8004
+ console.log(` Schemas: ${agentToolsTokens.toLocaleString()} tokens (${agentTools.length} tools active)`);
8005
+ }
7787
8006
  console.log("\nConversation:");
7788
8007
  console.log(` Messages: ${usage.messageTokens.toLocaleString()} tokens (${state.session.messages.length} messages)`);
7789
8008
  if (usagePercent >= 80) {
@@ -8154,7 +8373,7 @@ Multi-line Input:
8154
8373
  break;
8155
8374
  }
8156
8375
  case "terminal-setup": {
8157
- const { runTerminalSetup } = await import("./terminalSetup-DxloCowq.mjs");
8376
+ const { runTerminalSetup } = await import("./terminalSetup-BbJt04ZG.mjs");
8158
8377
  await runTerminalSetup();
8159
8378
  break;
8160
8379
  }
@@ -8319,14 +8538,13 @@ Multi-line Input:
8319
8538
  const newToolNames = newFeatureRegistry.getAllToolNames();
8320
8539
  if (newToolNames.length > 0) registerFeatureModuleTools(newToolNames);
8321
8540
  if (state.wsManager && newFeatureRegistry.hasModules) newFeatureRegistry.registerAllWsHandlers(state.wsManager);
8322
- const agentContext = state.agent.context;
8323
8541
  const oldFeatureToolNames = new Set(state.featureRegistry?.getAllToolNames() ?? []);
8324
- const baseTools = agentContext.tools.filter((t) => !oldFeatureToolNames.has(t.toolSchema.name));
8542
+ const baseTools = state.agent.getTools().filter((t) => !oldFeatureToolNames.has(t.toolSchema.name));
8325
8543
  const newFeatureTools = newFeatureRegistry.getAllTools();
8326
- agentContext.tools = [...baseTools, ...newFeatureTools];
8544
+ state.agent.setTools([...baseTools, ...newFeatureTools]);
8327
8545
  const newFeaturePrompts = newFeatureRegistry.getSystemPromptSections();
8328
8546
  const planFilePathForRebuild = useCliStore.getState().interactionMode === "plan" && state.session ? getPlanModeFilePath(state.session.id) : void 0;
8329
- agentContext.systemPrompt = buildSystemPrompt(updatedConfig.preferences.promptVariant ?? "current", {
8547
+ state.agent.setSystemPrompt(buildSystemPrompt(updatedConfig.preferences.promptVariant ?? "current", {
8330
8548
  contextContent: state.contextContent,
8331
8549
  agentStore: state.agentStore || void 0,
8332
8550
  customCommands: state.customCommandStore.getAllCommands(),
@@ -8334,8 +8552,9 @@ Multi-line Input:
8334
8552
  enableDynamicAgentCreation: updatedConfig.preferences.enableDynamicAgentCreation === true,
8335
8553
  additionalDirectories: state.additionalDirectories,
8336
8554
  featureModulePrompts: newFeaturePrompts || void 0,
8337
- planModeFilePath: planFilePathForRebuild
8338
- });
8555
+ planModeFilePath: planFilePathForRebuild,
8556
+ deferredToolNames: deferredToolRegistry.getDirectoryNames()
8557
+ }));
8339
8558
  const moduleNames = newFeatureRegistry.getModuleNames();
8340
8559
  if (moduleNames.length > 0) console.error(`\n\x1b[36m🏰 Feature modules hot-reloaded: ${moduleNames.join(", ")}\x1b[0m`);
8341
8560
  else console.error(`\n\x1b[36m🏰 Feature modules disabled\x1b[0m`);
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { realpathSync } from "fs";
3
3
  import path from "path";
4
- //#region ../../b4m-core/services/dist/pathValidation-CIytuhr3.mjs
4
+ //#region ../../b4m-core/services/dist/pathValidation-D8tjkQXE.mjs
5
5
  /**
6
6
  * Resolves a path to its canonical form, following symlinks.
7
7
  * If the path doesn't exist (e.g., file being created), resolves the
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { n as useCliStore } from "./store-DV5s-qni.mjs";
3
+ export { useCliStore };