@bike4mind/cli 3.3.0 → 4.0.0

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.mjs CHANGED
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import { $ as CustomCommandStore, A as createAgentDelegateTool, B as DEFAULT_THOROUGHNESS, C as createTodoStore, D as createCoordinateTaskTool, E as parseAgentConfig, G as setWebSocketToolExecutor, H as buildSkillsPromptSection, I as ALWAYS_DENIED_FOR_AGENTS, L as DEFAULT_AGENT_MODEL, M as substituteArguments, N as PermissionManager, O as createResumeAgentTool, P as buildCliToolList, Q as RemoteSkillSource, R as DEFAULT_MAX_ITERATIONS, S as createFindDefinitionTool, T as createSkillTool, V as buildSystemPrompt, W as getProcessHooks, X as createToolSearchTool, Z as findIterationBoundary, _ as createDecisionLogTool, at as mergeCommands, b as createGetFileStructureTool, ct as searchFiles, d as createReviewGateStore, dt as COMPACTION_SUMMARY_MARKER, et as processFileReferences, f as createReviewGateTool, g as formatBlockersOutput, h as createBlockerTools, it as searchCommands, j as FallbackLlmBackend, k as createBackgroundAgentTools, lt as warmFileCache, m as createBlockerStore, nt as CheckpointStore, ot as rewireReservedNames, p as formatReviewGatesOutput, pt as MAX_PASTE_SIZE, q as getPlanModeFilePath, rt as SessionStore, s as buildSandbox, st as formatFileSize, tt as hasFileReferences, u as isConnectionDrop, v as createDecisionStore, w as createWriteTodosTool, x as createWorkItemTools, y as formatDecisionsOutput, z as DEFAULT_RETRY_CONFIG } from "./AgentHistoryStore-CLidbOHG.mjs";
3
- import { a as selectLiveSubagentTokens, i as selectLiveSubagentCredits, n as selectActiveBackgroundShells, o as useCliStore, r as selectCompletedBackgroundShells, t as selectActiveBackgroundAgents } from "./store-BxmPEI3v.mjs";
4
- import { Bt as DELEGATION_PAYLOAD_SCHEMA_NAME, Gt as selfClaimedActorKindSchema, Ht as actorKindMarker, L as CREDIT_DEDUCT_TRANSACTION_TYPES, Pt as tokenEstimateMultiplier, R as ChatModels, S as isValidSessionId, Ut as actorKindSchema, Vt as actorColorIndex, Wt as delegationPayloadSchema, _ as LOCAL_DEV_URL, b as logger, bt as isEarlyStop, d as getCreditsUrl, f as getEnvironmentName, i as ToolCatalog, it as SUBQUEST_STATUS_VALUES, m as resolveApiEndpoint, n as extractCompactInstructions, p as requireApiUrl, t as ConfigStore, tt as REVIEW_GATE_STATUS_VALUES, u as ApiEndpointUnconfiguredError, x as SESSION_ID_PATTERN, y as parseApiUrl, zt as ACTOR_COLOR_SLOTS } from "./ConfigStore-DUATzLxB.mjs";
2
+ import { $ as processFileReferences, A as createAgentDelegateTool, B as DEFAULT_THOROUGHNESS, C as createTodoStore, D as createCoordinateTaskTool, E as parseAgentConfig, H as buildSkillsPromptSection, I as ALWAYS_DENIED_FOR_AGENTS, J as getProcessHooks, K as findIterationBoundary, L as DEFAULT_AGENT_MODEL, M as substituteArguments, N as PermissionManager, O as createResumeAgentTool, P as buildCliToolList, Q as CustomCommandStore, R as DEFAULT_MAX_ITERATIONS, S as createFindDefinitionTool, T as createSkillTool, V as buildSystemPrompt, Y as setWebSocketToolExecutor, Z as getPlanModeFilePath, _ as createDecisionLogTool, at as searchFiles, b as createGetFileStructureTool, ct as COMPACTION_SUMMARY_MARKER, d as createReviewGateStore, et as hasFileReferences, f as createReviewGateTool, g as formatBlockersOutput, h as createBlockerTools, it as formatFileSize, j as FallbackLlmBackend, k as createBackgroundAgentTools, m as createBlockerStore, nt as SessionStore, ot as warmFileCache, p as formatReviewGatesOutput, rt as searchCommands, s as buildSandbox, tt as CheckpointStore, u as isConnectionDrop, ut as MAX_PASTE_SIZE, v as createDecisionStore, w as createWriteTodosTool, x as createWorkItemTools, y as formatDecisionsOutput, z as DEFAULT_RETRY_CONFIG } from "./AgentHistoryStore-DgqmcVLN.mjs";
3
+ import { a as selectLiveSubagentTokens, i as selectLiveSubagentCredits, n as selectActiveBackgroundShells, o as useCliStore, r as selectCompletedBackgroundShells, t as selectActiveBackgroundAgents } from "./store-BbvSi3KK.mjs";
4
+ import { $t as selfClaimedActorKindSchema, B as ChatModels, Dt as isEarlyStop, Jt as DELEGATION_PAYLOAD_SCHEMA_NAME, Qt as delegationPayloadSchema, S as isValidSessionId, Vt as tokenEstimateMultiplier, Xt as actorKindMarker, Yt as actorColorIndex, Zt as actorKindSchema, _ as LOCAL_DEV_URL, at as REVIEW_GATE_STATUS_VALUES, b as logger, ct as SUBQUEST_STATUS_VALUES, d as getCreditsUrl, f as getEnvironmentName, i as ToolCatalog, m as resolveApiEndpoint, n as extractCompactInstructions, p as requireApiUrl, qt as ACTOR_COLOR_SLOTS, t as ConfigStore, u as ApiEndpointUnconfiguredError, x as SESSION_ID_PATTERN, y as parseApiUrl, z as CREDIT_DEDUCT_TRANSACTION_TYPES } from "./ConfigStore-BwqsQ0U9.mjs";
5
+ import { a as rewireReservedNames, i as mergeCommands, t as RemoteSkillSource } from "./RemoteSkillSource-qTvRkaNA.mjs";
5
6
  import { r as isFeatureEnabled, t as PluginStore } from "./PluginStore-DwvOJ-G3.mjs";
6
- import { i as getTokenCounter, n as ConversationContext, r as reconstructTurnBlocks, t as WorkItemsClient } from "./WorkItemsClient-BSoOblGp.mjs";
7
- import { a as NotifyingLlmBackend, i as deferredToolRegistry, n as buildSupportingStores, r as buildLlmBackend, t as buildAgent } from "./buildAgent-DyZWK0LW.mjs";
7
+ import { i as getTokenCounter, n as ConversationContext, r as reconstructTurnBlocks, t as WorkItemsClient } from "./WorkItemsClient-CERTDrhT.mjs";
8
+ import { a as deferredToolRegistry, i as buildLlmBackend, n as buildAgent, o as NotifyingLlmBackend, r as buildSupportingStores, t as buildToolSearch } from "./buildToolSearch-D9GEK91u.mjs";
8
9
  import { t as getShellSessionManager } from "./ShellSessionManager-6o8KZzl1-vrbPAUTq.mjs";
9
- import { a as ApiClient, s as OAuthClient } from "./dist-DPP7UmK9.mjs";
10
- import { t as version } from "./package-DYPVrada.mjs";
10
+ import { r as OAuthClient, t as ApiClient } from "./ApiClient-_JrM_xWw.mjs";
11
+ import { t as version } from "./package-CQaam4Dq.mjs";
11
12
  import { s as getStartupUpdateStatus } from "./updateChecker-CCLVVdH0.mjs";
12
13
  import React, { useCallback, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore } from "react";
13
14
  import { Box, Static, Text, measureElement, render, useApp, useInput, usePaste, useStdout } from "ink";
@@ -627,6 +628,7 @@ function InputPrompt({ onSubmit, onBashCommand, onImageDetected, disabled = fals
627
628
  const pastedLineCount = useCliStore((state) => state.pastedLineCount);
628
629
  const setPastedContent = useCliStore((state) => state.setPastedContent);
629
630
  const clearPaste = useCliStore((state) => state.clearPaste);
631
+ const turnBusy = useCliStore((state) => state.isThinking || state.turnInFlight);
630
632
  const [selectedIndex, setSelectedIndex] = useState(0);
631
633
  const [historyIndex, setHistoryIndex] = useState(-1);
632
634
  const [tempInput, setTempInput] = useState("");
@@ -705,6 +707,15 @@ function InputPrompt({ onSubmit, onBashCommand, onImageDetected, disabled = fals
705
707
  return;
706
708
  }
707
709
  }
710
+ if (key.tab && !key.shift && turnBusy && !isBashMode && !shouldShowCommandAutocomplete && !fileAutocomplete?.active) {
711
+ const content = pastedContent ?? value;
712
+ if (!content.trim()) return;
713
+ if (pastedContent) clearPaste();
714
+ onSubmit(content, { followUp: true });
715
+ setValue("");
716
+ setHistoryIndex(-1);
717
+ return;
718
+ }
708
719
  if (!shouldShowCommandAutocomplete && !fileAutocomplete?.active && history.length > 0) {
709
720
  if (key.upArrow) {
710
721
  if (historyIndex === -1) {
@@ -2706,6 +2717,8 @@ const MessageItem = React.memo(function MessageItem({ message, showThoughts = tr
2706
2717
  * agent/shell status blocks render nothing when idle.
2707
2718
  */
2708
2719
  const LIVE_CHROME_ROWS = 10;
2720
+ /** Shown under the queued-message rows; mirrors the routing in handleSubmit and the ESC handler in index.tsx. */
2721
+ const QUEUE_HINT = "Enter steers this turn · Tab queues for next turn · Esc cancels and restores to input";
2709
2722
  function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPermissionResponse, onUserQuestionResponse, onReviewGateResponse, onImageDetected, commandHistory = [], commands = [], config, availableModels = [], pluginDescriptors = [], onSaveConfig, prefillInput, onPrefillConsumed, mcpManager }) {
2710
2723
  const messages = useCliStore(useShallow((state) => state.session?.messages || []));
2711
2724
  const pendingMessages = useCliStore((state) => state.pendingMessages);
@@ -2729,15 +2742,17 @@ function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPe
2729
2742
  const exitRequested = useCliStore((state) => state.exitRequested);
2730
2743
  const setIsThinking = useCliStore((state) => state.setIsThinking);
2731
2744
  const pendingBackgroundTrigger = useCliStore((state) => state.pendingBackgroundTrigger);
2745
+ const turnInFlight = useCliStore((state) => state.turnInFlight);
2732
2746
  const setPendingBackgroundTrigger = useCliStore((state) => state.setPendingBackgroundTrigger);
2733
2747
  useEffect(() => {
2734
- if (pendingBackgroundTrigger && !isThinking && onBackgroundCompletion) {
2748
+ if (pendingBackgroundTrigger && !isThinking && !turnInFlight && onBackgroundCompletion) {
2735
2749
  setPendingBackgroundTrigger(false);
2736
2750
  onBackgroundCompletion();
2737
2751
  }
2738
2752
  }, [
2739
2753
  pendingBackgroundTrigger,
2740
2754
  isThinking,
2755
+ turnInFlight,
2741
2756
  setPendingBackgroundTrigger,
2742
2757
  onBackgroundCompletion
2743
2758
  ]);
@@ -2767,8 +2782,9 @@ function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPe
2767
2782
  useInput(() => setPromptInputTick((tick) => tick + 1), { isActive: promptKey !== "" });
2768
2783
  const promptRows = promptKey === "" ? 0 : measuredPrompt?.key === promptKey ? measuredPrompt.rows : Infinity;
2769
2784
  const [terminalCols, terminalRows] = useStdoutDimensions();
2770
- const liveRows = Math.max(0, terminalRows - LIVE_CHROME_ROWS - messageQueue.length - promptRows);
2771
- const handleSubmit = React.useCallback(async (input) => {
2785
+ const queueRows = messageQueue.length > 0 ? messageQueue.length + 1 : 0;
2786
+ const liveRows = Math.max(0, terminalRows - LIVE_CHROME_ROWS - queueRows - promptRows);
2787
+ const handleSubmit = React.useCallback(async (input, options) => {
2772
2788
  const trimmed = input.trim();
2773
2789
  if (!trimmed) return;
2774
2790
  if (trimmed.startsWith("/") && !trimmed.match(/^\/[A-Z]/)) {
@@ -2789,8 +2805,10 @@ function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPe
2789
2805
  messageToSend = `${messageToSend}\n\n${errorBlock}`;
2790
2806
  }
2791
2807
  }
2792
- if (useCliStore.getState().isThinking) {
2793
- useCliStore.getState().enqueueMessage(messageToSend);
2808
+ const store = useCliStore.getState();
2809
+ if (store.isThinking || store.turnInFlight) {
2810
+ const kind = options?.followUp || !store.turnInFlight ? "followUp" : "steer";
2811
+ store.enqueueMessage(messageToSend, kind, trimmed);
2794
2812
  return;
2795
2813
  }
2796
2814
  setIsThinking(true);
@@ -2888,7 +2906,9 @@ function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPe
2888
2906
  paddingX: 1,
2889
2907
  marginBottom: 1
2890
2908
  }, messageQueue.map((queuedMessage, idx) => {
2891
- const rowText = `❯ ${queuedMessage}`;
2909
+ const label = queuedMessage.kind === "followUp" ? "[next turn] " : "";
2910
+ const [firstLine, ...moreLines] = (queuedMessage.typed ?? queuedMessage.text).split("\n");
2911
+ const rowText = `\u276F ${label}${firstLine}${moreLines.length > 0 ? " ..." : ""}`;
2892
2912
  const padded = rowText.length >= terminalCols - 2 ? rowText : rowText.padEnd(terminalCols - 2);
2893
2913
  return /* @__PURE__ */ React.createElement(Text, {
2894
2914
  key: idx,
@@ -2896,7 +2916,10 @@ function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPe
2896
2916
  color: "white",
2897
2917
  wrap: "truncate-end"
2898
2918
  }, padded);
2899
- })), /* @__PURE__ */ React.createElement(Box, {
2919
+ }), /* @__PURE__ */ React.createElement(Text, {
2920
+ dimColor: true,
2921
+ wrap: "truncate-end"
2922
+ }, QUEUE_HINT)), /* @__PURE__ */ React.createElement(Box, {
2900
2923
  borderStyle: "single",
2901
2924
  borderColor: isBashMode ? "yellow" : "cyan",
2902
2925
  borderTop: true,
@@ -3049,7 +3072,7 @@ function RewindSelector({ messages, onSelect, onCancel }) {
3049
3072
  const items = messages.map((msg, idx) => ({
3050
3073
  msg,
3051
3074
  originalIndex: idx
3052
- })).filter(({ msg }) => msg.role === "user").map(({ msg, originalIndex }, index) => {
3075
+ })).filter(({ msg }) => msg.role === "user" && !msg.metadata?.steered).map(({ msg, originalIndex }, index) => {
3053
3076
  const preview = msg.content.length > 50 ? msg.content.substring(0, 50) + "..." : msg.content;
3054
3077
  const timestamp = new Date(msg.timestamp).toLocaleString();
3055
3078
  return {
@@ -8207,7 +8230,9 @@ Terminal Setup:
8207
8230
 
8208
8231
  Keyboard Shortcuts:
8209
8232
  Ctrl+C - Press twice to exit
8210
- Esc - Abort current operation
8233
+ Esc - Abort current operation (queued messages return to the input)
8234
+ Enter (busy) - Steer the running turn with the message
8235
+ Tab (busy) - Queue the message to run as the next turn
8211
8236
  Shift+Tab - Toggle auto-accept edits
8212
8237
  Ctrl+U - Clear current line
8213
8238
  Ctrl+K - Clear from cursor to end of line
@@ -8394,6 +8419,57 @@ async function handleSandboxTrustDomainCommand(deps, domains) {
8394
8419
  return lines.join("\n");
8395
8420
  }
8396
8421
  //#endregion
8422
+ //#region src/session/steering.ts
8423
+ /**
8424
+ * Steering: user input submitted while a turn runs joins that turn at its next
8425
+ * model request instead of waiting for the turn to end.
8426
+ *
8427
+ * Delivery rides the agent's `drainFeedback` hook (see AgentRunOptions), which
8428
+ * fires after a tool batch resolves or when the model answers with iterations
8429
+ * left, and is never called once the run is aborted. A drain call is therefore
8430
+ * the delivery point: the steers leave the queue and enter the session in one
8431
+ * synchronous step, so each one is delivered exactly once - to the model and
8432
+ * the transcript together - or stays queued for the host to run as a follow-up
8433
+ * turn (turnController.ts) or restore to the input on cancel (index.tsx ESC).
8434
+ */
8435
+ const STEER_PREAMBLE = "The user sent the following while you were working. Take it into account before you continue, and change course if it asks you to.";
8436
+ /** The feedback text the model sees for `steers`, or null when there are none. */
8437
+ function formatSteerFeedback(steers) {
8438
+ if (steers.length === 0) return null;
8439
+ return [STEER_PREAMBLE, ...steers].join("\n\n");
8440
+ }
8441
+ /**
8442
+ * Take every queued steer and record each as a user message on the session.
8443
+ * Returns the feedback for the agent, or null when nothing is queued or the
8444
+ * turn was cancelled (the steers then stay queued).
8445
+ */
8446
+ function deliverSteers(signal) {
8447
+ if (signal.aborted) return null;
8448
+ const store = useCliStore.getState();
8449
+ const session = store.session;
8450
+ if (!session) return null;
8451
+ const steers = store.takeSteerMessages();
8452
+ const feedback = formatSteerFeedback(steers);
8453
+ if (!feedback) return null;
8454
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
8455
+ const steerMessages = steers.map((text) => ({
8456
+ id: v4(),
8457
+ role: "user",
8458
+ content: text,
8459
+ timestamp,
8460
+ metadata: { steered: true }
8461
+ }));
8462
+ store.setSession({
8463
+ ...session,
8464
+ messages: [...session.messages, ...steerMessages],
8465
+ updatedAt: timestamp
8466
+ });
8467
+ return {
8468
+ feedback,
8469
+ steers
8470
+ };
8471
+ }
8472
+ //#endregion
8397
8473
  //#region src/session/turnController.ts
8398
8474
  /**
8399
8475
  * Turn lifecycle (issue #228, phase 2).
@@ -8407,16 +8483,53 @@ async function handleSandboxTrustDomainCommand(deps, domains) {
8407
8483
  * keeps only a thin wrapper that builds the context from its render state.
8408
8484
  */
8409
8485
  /**
8410
- * Run one user turn to completion. Never throws: transport, auth, and abort
8411
- * errors are handled and surfaced to the console; the queue-drain in `finally`
8412
- * recurses with the same context for messages submitted mid-turn.
8486
+ * Run one user turn to completion, then each follow-up queued behind it. Never
8487
+ * throws: transport, auth, and abort errors are handled and surfaced to the
8488
+ * console.
8489
+ *
8490
+ * Only one runTurn turn is in flight at a time. A call that arrives while one
8491
+ * is (a tavern prompt, a bash-command drain, a submit during an ESC unwind) is
8492
+ * queued as a follow-up rather than run as a second agent loop over the same
8493
+ * agent. Other agent runs (custom commands, background results) gate on
8494
+ * `turnInFlight` themselves. After
8495
+ * each turn settles - completed, failed, or cancelled - everything still queued
8496
+ * runs as one combined next turn: follow-ups, steers that never reached a
8497
+ * request boundary, and input submitted after a cancel (ESC moves what was
8498
+ * queued before it back to the input, see index.tsx).
8413
8499
  */
8414
8500
  async function runTurn(message, ctx) {
8415
- const releaseSession = useCliStore.getState().holdSession();
8501
+ const store = useCliStore.getState();
8502
+ if (store.turnInFlight) {
8503
+ store.enqueueMessage(message, "followUp");
8504
+ return;
8505
+ }
8506
+ store.setTurnInFlight(true);
8416
8507
  try {
8417
- await runHeldTurn(message, ctx);
8508
+ let next = message;
8509
+ while (next !== null) {
8510
+ const releaseSession = useCliStore.getState().holdSession();
8511
+ try {
8512
+ await runHeldTurn(next, ctx);
8513
+ } catch (error) {
8514
+ console.error(`\n\u274C ${error instanceof Error ? error.message : String(error)}\n`);
8515
+ logger.debug(`Turn failed outside its error handling: ${error instanceof Error ? error.stack : String(error)}`);
8516
+ } finally {
8517
+ releaseSession();
8518
+ }
8519
+ const queued = useCliStore.getState().dequeueAllMessages();
8520
+ next = queued.length > 0 ? queued.join("\n\n") : null;
8521
+ }
8418
8522
  } finally {
8419
- releaseSession();
8523
+ useCliStore.getState().setTurnInFlight(false);
8524
+ }
8525
+ }
8526
+ /** Not awaited by the steer drain, so a failed history write is logged rather than left as an unhandled rejection. */
8527
+ async function recordSteerHistory(steers, commandHistoryStore, setCommandHistory) {
8528
+ try {
8529
+ for (const text of steers) await commandHistoryStore.add(text);
8530
+ setCommandHistory(await commandHistoryStore.list());
8531
+ } catch (error) {
8532
+ logger.debug(`[STEER] History write failed: ${error instanceof Error ? error.message : String(error)}`);
8420
8533
  }
8421
8534
  }
8422
8535
  async function runHeldTurn(message, ctx) {
@@ -8540,7 +8653,19 @@ async function runHeldTurn(message, ctx) {
8540
8653
  parallelExecution: cliConfig.preferences.enableParallelToolExecution === true,
8541
8654
  isReadOnlyTool: (toolName) => toolCatalog.isReadOnly(toolName),
8542
8655
  workflowReminder,
8543
- drainFeedback: diagnostics ? (phase) => diagnostics.drain(DRAIN_BUDGET_MS[phase], abortController.signal) : void 0,
8656
+ drainFeedback: async (phase) => {
8657
+ const diagnosticsFeedback = diagnostics ? await diagnostics.drain(DRAIN_BUDGET_MS[phase], abortController.signal) : null;
8658
+ const steered = deliverSteers(abortController.signal);
8659
+ if (steered) {
8660
+ for (const text of steered.steers) bridgePresence.emitEvent({
8661
+ type: "message",
8662
+ role: "user",
8663
+ text: text.slice(0, 4e3)
8664
+ });
8665
+ recordSteerHistory(steered.steers, commandHistoryStore, setCommandHistory);
8666
+ }
8667
+ return [steered?.feedback, diagnosticsFeedback].filter(Boolean).join("\n\n") || null;
8668
+ },
8544
8669
  onContextLimit: createReactiveCompactionHandler(agent, activeSession)
8545
8670
  });
8546
8671
  } finally {
@@ -8635,15 +8760,6 @@ async function runHeldTurn(message, ctx) {
8635
8760
  type: "status",
8636
8761
  status: wasAborted ? "idle" : "awaiting_input"
8637
8762
  });
8638
- if (!wasAborted) {
8639
- const queued = useCliStore.getState().dequeueAllMessages();
8640
- if (queued.length > 0) {
8641
- const combined = queued.join("\n\n");
8642
- setImmediate(() => {
8643
- runTurn(combined, ctx);
8644
- });
8645
- }
8646
- }
8647
8763
  }
8648
8764
  }
8649
8765
  //#endregion
@@ -9064,6 +9180,7 @@ function CliApp() {
9064
9180
  store.clearPaste();
9065
9181
  return;
9066
9182
  }
9183
+ store.restoreQueueToInput();
9067
9184
  if (state.abortController) {
9068
9185
  logger.debug("[ABORT] ESC pressed - aborting current operation...");
9069
9186
  state.abortController.abort();
@@ -9071,9 +9188,8 @@ function CliApp() {
9071
9188
  ...prev,
9072
9189
  abortController: null
9073
9190
  }));
9074
- useCliStore.getState().setIsThinking(false);
9075
- useCliStore.getState().clearMessageQueue();
9076
- } else useCliStore.getState().clearMessageQueue();
9191
+ store.setIsThinking(false);
9192
+ }
9077
9193
  return;
9078
9194
  }
9079
9195
  if (key.ctrl && input === "c") {
@@ -9350,9 +9466,17 @@ function CliApp() {
9350
9466
  const dynamicAgentTool = config.preferences.enableDynamicAgentCreation === true ? createDynamicAgentTool(orchestrator, newSession.id, backgroundManager) : null;
9351
9467
  const backgroundTools = createBackgroundAgentTools(backgroundManager);
9352
9468
  const resumeAgentTool = createResumeAgentTool(orchestrator, historyStore, backgroundManager);
9353
- const llmWithFallback = config.fallbackModels && config.fallbackModels.length > 0 ? new FallbackLlmBackend(llm, config.fallbackModels, (fromModel, toModel, error) => {
9469
+ const agentToolsRef = { current: null };
9470
+ const { llm: toolSearchLlm, toolSearchTool } = buildToolSearch({
9471
+ llm,
9472
+ models,
9473
+ registry: deferredToolRegistry,
9474
+ logger,
9475
+ agentToolsRef
9476
+ });
9477
+ const llmWithFallback = config.fallbackModels && config.fallbackModels.length > 0 ? new FallbackLlmBackend(toolSearchLlm, config.fallbackModels, (fromModel, toModel, error) => {
9354
9478
  logger.warn(`⚠️ Model "${fromModel}" failed (${error.message}) — falling back to "${toModel}"`);
9355
- }) : llm;
9479
+ }) : toolSearchLlm;
9356
9480
  const notifyingLlm = new NotifyingLlmBackend(llmWithFallback, backgroundManager);
9357
9481
  const todoStore = todoStoreRef.current;
9358
9482
  todoStore.todos = [];
@@ -9390,15 +9514,6 @@ function CliApp() {
9390
9514
  allowedDirectories: additionalDirectories
9391
9515
  }) : null;
9392
9516
  const workItemTools = config.preferences.enableWorkItemTools ? createWorkItemTools(new WorkItemsClient(apiClient)) : [];
9393
- const agentToolsRef = { current: null };
9394
- const toolSearchTool = deferredToolRegistry.size() > 0 ? createToolSearchTool({
9395
- registry: deferredToolRegistry,
9396
- logger,
9397
- toolListAccessor: () => {
9398
- if (!agentToolsRef.current) throw new Error("tool_search invoked before agent context was wired");
9399
- return agentToolsRef.current;
9400
- }
9401
- }) : null;
9402
9517
  const cliTools = buildCliToolList({
9403
9518
  toolSearchTool,
9404
9519
  agentDelegateTool,
@@ -9421,7 +9536,11 @@ function CliApp() {
9421
9536
  descriptors: pluginDescriptors,
9422
9537
  config,
9423
9538
  logger,
9424
- reservedToolNames: [...loadedB4mTools, ...cliTools].map((t) => t.toolSchema.name).concat("tool_search")
9539
+ reservedToolNames: [
9540
+ ...[...loadedB4mTools, ...cliTools].map((t) => t.toolSchema.name),
9541
+ ...deferredToolRegistry.getDirectoryNames(),
9542
+ "tool_search"
9543
+ ]
9425
9544
  });
9426
9545
  rewireReservedNames(state, featureRegistry);
9427
9546
  if (wsManager && featureRegistry.hasModules) featureRegistry.registerAllWsHandlers(wsManager);
@@ -11351,7 +11470,11 @@ function CliApp() {
11351
11470
  descriptors: state.pluginDescriptors,
11352
11471
  config: updatedConfig,
11353
11472
  logger,
11354
- reservedToolNames: baseTools.map((t) => t.toolSchema.name)
11473
+ reservedToolNames: [
11474
+ ...baseTools.map((t) => t.toolSchema.name),
11475
+ ...deferredToolRegistry.getDirectoryNames(),
11476
+ "tool_search"
11477
+ ]
11355
11478
  });
11356
11479
  const rebuiltRegistry = rebuilt.registry;
11357
11480
  newFeatureRegistry = rebuiltRegistry;
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  //#region package.json
3
- var version = "3.3.0";
3
+ var version = "4.0.0";
4
4
  //#endregion
5
5
  export { version as t };