@bike4mind/cli 0.7.0 → 0.8.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.
@@ -862,9 +862,27 @@ const GenericCreditDeductTransaction = BaseCreditTransaction.extend({
862
862
  */
863
863
  userId: z.string().optional(),
864
864
  /**
865
- * Optional reason/source for the transaction (e.g., 'admin_adjustment', 'legacy_usage', 'manual_deduction')
865
+ * Reason/source for the transaction. Use GenericDeductReasons for new callers.
866
866
  */
867
- reason: z.string().optional()
867
+ reason: z.enum([
868
+ "dispute_clawback",
869
+ "refund_clawback",
870
+ "payment_failed_clawback",
871
+ "notebook_curation",
872
+ "manual",
873
+ "admin_adjustment",
874
+ "refund_adjustment"
875
+ ]).optional(),
876
+ /**
877
+ * Stripe dispute ID — set for dispute clawback transactions.
878
+ * Used for idempotency (unique sparse index prevents duplicate clawbacks).
879
+ */
880
+ stripeDisputeId: z.string().optional(),
881
+ /**
882
+ * Stripe refund ID — set for refund clawback transactions.
883
+ * Used for idempotency (unique sparse index prevents duplicate clawbacks).
884
+ */
885
+ stripeRefundId: z.string().optional()
868
886
  });
869
887
  const TextGenerationUsageTransaction = BaseCreditTransaction.extend({
870
888
  type: z.literal("text_generation_usage"),
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as version, n as compareSemver, r as fetchLatestVersion } from "../updateChecker-Dn-Ri8zw.mjs";
2
+ import { a as version, n as compareSemver, r as fetchLatestVersion } from "../updateChecker-BVKr0OXs.mjs";
3
3
  import { execSync } from "child_process";
4
4
  import { constants, existsSync, promises } from "fs";
5
5
  import { homedir } from "os";
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { $ as SessionStore, C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as PermissionManager, I as generateCliTools, J as isReadOnlyTool, K as buildSystemPrompt, M as loadContextFiles, N as getApiUrl, O as McpManager, S as ApiClient, T as FallbackLlmBackend, W as setWebSocketToolExecutor, X as CustomCommandStore, Y as ReActAgent, Z as CheckpointStore, _ as createAgentDelegateTool, b as createSkillTool, d as createFindDefinitionTool, f as createTodoStore, g as BackgroundAgentManager, h as createBackgroundAgentTools, m as createCoordinateTaskTool, p as createWriteTodosTool, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, y as SubagentOrchestrator } from "../tools-DY-JzNoY.mjs";
3
- import { n as logger, t as ConfigStore } from "../ConfigStore-Bu_plzvP.mjs";
2
+ import { C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as PermissionManager, I as generateCliTools, M as loadContextFiles, N as getApiUrl, O as McpManager, Q as CheckpointStore, S as ApiClient, T as FallbackLlmBackend, W as setWebSocketToolExecutor, X as ReActAgent, Y as isReadOnlyTool, Z as CustomCommandStore, _ as createAgentDelegateTool, b as createSkillTool, d as createFindDefinitionTool, et as SessionStore, f as createTodoStore, g as BackgroundAgentManager, h as createBackgroundAgentTools, m as createCoordinateTaskTool, p as createWriteTodosTool, q as buildSystemPrompt, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, y as SubagentOrchestrator } from "../tools-DDoiKdgk.mjs";
3
+ import { n as logger, t as ConfigStore } from "../ConfigStore-Bj1IOvWn.mjs";
4
4
  import { t as DEFAULT_SANDBOX_CONFIG } from "../types-DBEjF9YS.mjs";
5
5
  import { t as createSandboxRuntime } from "../SandboxRuntimeAdapter-C1B4t20N.mjs";
6
6
  import { t as SandboxOrchestrator } from "../SandboxOrchestrator-BEW3rqYi.mjs";
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as ConfigStore } from "../ConfigStore-Bu_plzvP.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-Bj1IOvWn.mjs";
3
3
  //#region src/commands/mcpCommand.ts
4
4
  /**
5
5
  * External MCP commands (b4m mcp list, b4m mcp add, etc.)
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as version, i as forceCheckForUpdate } from "../updateChecker-Dn-Ri8zw.mjs";
2
+ import { a as version, i as forceCheckForUpdate } from "../updateChecker-BVKr0OXs.mjs";
3
3
  import { execSync } from "child_process";
4
4
  //#region src/commands/updateCommand.ts
5
5
  /**
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { n as useCliStore, t as selectActiveBackgroundAgents } from "./store-FkY4K-0M.mjs";
3
- import { $ as SessionStore, 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 isReadOnlyTool, K as buildSystemPrompt, L as ALWAYS_DENIED_FOR_AGENTS, M as loadContextFiles, N as getApiUrl, O as McpManager, P as getEnvironmentName, Q as CommandHistoryStore, R as DEFAULT_AGENT_MODEL, S as ApiClient, T as FallbackLlmBackend, U as registerFeatureModuleTools, V as DEFAULT_THOROUGHNESS, W as setWebSocketToolExecutor, X as CustomCommandStore, Y as ReActAgent, Z as CheckpointStore, _ as createAgentDelegateTool, a as createBlockerTools, at as formatFileSize, b as createSkillTool, c as createDecisionStore, d as createFindDefinitionTool, et as OAuthClient, f as createTodoStore, g as BackgroundAgentManager, h as createBackgroundAgentTools, i as createBlockerStore, it as mergeCommands, j as extractCompactInstructions, k as substituteArguments, l as formatDecisionsOutput, m as createCoordinateTaskTool, n as createReviewGateTool, nt as processFileReferences, o as formatBlockersOutput, ot as searchFiles, p as createWriteTodosTool, q as buildSkillsPromptSection, r as formatReviewGatesOutput, rt as searchCommands, s as createDecisionLogTool, st as warmFileCache, t as createReviewGateStore, tt as hasFileReferences, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, x as parseAgentConfig, y as SubagentOrchestrator, z as DEFAULT_MAX_ITERATIONS } from "./tools-DY-JzNoY.mjs";
4
- import { Mt as validateNotebookPath$1, g as ChatModels, jt as validateJupyterKernelName, m as CREDIT_DEDUCT_TRANSACTION_TYPES, n as logger, t as ConfigStore } from "./ConfigStore-Bu_plzvP.mjs";
5
- import { a as version, t as checkForUpdate } from "./updateChecker-Dn-Ri8zw.mjs";
2
+ import { n as useCliStore, t as selectActiveBackgroundAgents } from "./store-B0ImnWR4.mjs";
3
+ 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-DDoiKdgk.mjs";
4
+ import { Mt as validateNotebookPath$1, g as ChatModels, jt as validateJupyterKernelName, m as CREDIT_DEDUCT_TRANSACTION_TYPES, n as logger, t as ConfigStore } from "./ConfigStore-Bj1IOvWn.mjs";
5
+ import { a as version, t as checkForUpdate } from "./updateChecker-BVKr0OXs.mjs";
6
6
  import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
7
- import { Box, Static, Text, render, useApp, useInput } from "ink";
7
+ import { Box, Static, Text, render, useApp, useInput, usePaste } from "ink";
8
8
  import { execSync } from "child_process";
9
9
  import { randomBytes, randomUUID } from "crypto";
10
10
  import { existsSync, promises, readFileSync, statSync } from "fs";
@@ -25,16 +25,19 @@ import { get_encoding } from "tiktoken";
25
25
  import WsWebSocket from "ws";
26
26
  //#region src/components/StatusBar.tsx
27
27
  const StatusBar = React.memo(function StatusBar({ sessionName, model, tokenUsage, creditsUsage }) {
28
- const autoAcceptEdits = useCliStore((state) => state.autoAcceptEdits);
28
+ const interactionMode = useCliStore((state) => state.interactionMode);
29
29
  return /* @__PURE__ */ React.createElement(Box, {
30
30
  flexDirection: "row",
31
31
  justifyContent: "space-between",
32
32
  width: "100%",
33
33
  paddingX: 1
34
- }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, sessionName), /* @__PURE__ */ React.createElement(Box, { gap: 2 }, autoAcceptEdits && /* @__PURE__ */ React.createElement(Text, {
34
+ }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, sessionName), /* @__PURE__ */ React.createElement(Box, { gap: 2 }, interactionMode === "auto-accept" && /* @__PURE__ */ React.createElement(Text, {
35
35
  color: "green",
36
36
  bold: true
37
- }, "AUTO ACCEPT: Edits"), tokenUsage > 0 && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, tokenUsage.toLocaleString(), " tokens"), creditsUsage !== void 0 && creditsUsage > 0 && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, creditsUsage.toLocaleString(), " ", creditsUsage === 1 ? "credit" : "credits"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, model)));
37
+ }, "AUTO ACCEPT: Edits"), interactionMode === "plan" && /* @__PURE__ */ React.createElement(Text, {
38
+ color: "yellow",
39
+ bold: true
40
+ }, "PLAN MODE"), tokenUsage > 0 && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, tokenUsage.toLocaleString(), " tokens"), creditsUsage !== void 0 && creditsUsage > 0 && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, creditsUsage.toLocaleString(), " ", creditsUsage === 1 ? "credit" : "credits"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, model)));
38
41
  });
39
42
  /**
40
43
  * Maximum paste size in characters (~500KB) to prevent memory issues
@@ -74,6 +77,20 @@ function CustomTextInput({ value, onChange, onSubmit, onPaste, pasteIndicator, p
74
77
  while (pos < text.length && /\s/.test(text[pos])) pos++;
75
78
  return pos;
76
79
  };
80
+ usePaste((text) => {
81
+ const normalized = (text.length > 5e5 ? text.slice(0, MAX_PASTE_SIZE) : text).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
82
+ if (normalized.split("\n").length >= 5 && onPaste) {
83
+ onPaste(normalized);
84
+ return;
85
+ }
86
+ if (pasteIndicator) {
87
+ emitChange(normalized);
88
+ setCursorOffset(normalized.length);
89
+ return;
90
+ }
91
+ emitChange(value.slice(0, cursorOffset) + normalized + value.slice(cursorOffset));
92
+ setCursorOffset(cursorOffset + normalized.length);
93
+ }, { isActive: !disabled });
77
94
  useInput((input, key) => {
78
95
  if (key.return && !key.meta && !key.shift) {
79
96
  if (value.length > 0 && value[cursorOffset - 1] === "\\") {
@@ -98,7 +115,7 @@ function CustomTextInput({ value, onChange, onSubmit, onPaste, pasteIndicator, p
98
115
  setCursorOffset(findNextWordBoundary(value, cursorOffset));
99
116
  return;
100
117
  }
101
- if (key.backspace || key.delete) {
118
+ if (key.backspace) {
102
119
  const beforeCursor = value.slice(0, cursorOffset);
103
120
  const afterCursor = value.slice(cursorOffset);
104
121
  const newPos = findPreviousWordBoundary(beforeCursor, beforeCursor.length);
@@ -106,6 +123,13 @@ function CustomTextInput({ value, onChange, onSubmit, onPaste, pasteIndicator, p
106
123
  setCursorOffset(newPos);
107
124
  return;
108
125
  }
126
+ if (key.delete) {
127
+ const beforeCursor = value.slice(0, cursorOffset);
128
+ const afterCursor = value.slice(cursorOffset);
129
+ const newPos = findNextWordBoundary(afterCursor, 0);
130
+ emitChange(beforeCursor + afterCursor.slice(newPos));
131
+ return;
132
+ }
109
133
  } else {
110
134
  if (key.leftArrow) {
111
135
  setCursorOffset(0);
@@ -115,11 +139,15 @@ function CustomTextInput({ value, onChange, onSubmit, onPaste, pasteIndicator, p
115
139
  setCursorOffset(value.length);
116
140
  return;
117
141
  }
118
- if (key.backspace || key.delete) {
142
+ if (key.backspace) {
119
143
  emitChange(value.slice(cursorOffset));
120
144
  setCursorOffset(0);
121
145
  return;
122
146
  }
147
+ if (key.delete) {
148
+ emitChange(value.slice(0, cursorOffset));
149
+ return;
150
+ }
123
151
  }
124
152
  if (key.home) {
125
153
  setCursorOffset(0);
@@ -188,7 +216,7 @@ function CustomTextInput({ value, onChange, onSubmit, onPaste, pasteIndicator, p
188
216
  }
189
217
  return;
190
218
  }
191
- if (key.backspace || key.delete) {
219
+ if (key.backspace) {
192
220
  if (pasteIndicator) {
193
221
  emitChange("");
194
222
  setCursorOffset(0);
@@ -200,6 +228,15 @@ function CustomTextInput({ value, onChange, onSubmit, onPaste, pasteIndicator, p
200
228
  }
201
229
  return;
202
230
  }
231
+ if (key.delete) {
232
+ if (pasteIndicator) {
233
+ emitChange("");
234
+ setCursorOffset(0);
235
+ return;
236
+ }
237
+ if (cursorOffset < value.length) emitChange(value.slice(0, cursorOffset) + value.slice(cursorOffset + 1));
238
+ return;
239
+ }
203
240
  if (key.leftArrow && !key.meta && !key.ctrl) {
204
241
  setCursorOffset(Math.max(0, cursorOffset - 1));
205
242
  return;
@@ -721,10 +758,9 @@ function InputPrompt({ onSubmit, onBashCommand, onImageDetected, disabled = fals
721
758
  setFileAutocomplete(null);
722
759
  };
723
760
  const handlePaste = (content) => {
724
- const truncated = content.length > 5e5 ? content.slice(0, MAX_PASTE_SIZE) : content;
725
- const lineCount = truncated.split("\n").length;
761
+ const lineCount = content.split("\n").length;
726
762
  const prefix = value.trim();
727
- setPastedContent(prefix ? `${prefix}\n${truncated}` : truncated, lineCount);
763
+ setPastedContent(prefix ? `${prefix}\n${content}` : content, lineCount);
728
764
  };
729
765
  const handleChange = async (newValue) => {
730
766
  if (pastedContent) clearPaste();
@@ -1403,7 +1439,7 @@ function ReviewGatePrompt({ description, options, recommendation, onResponse })
1403
1439
  onChange: setNote,
1404
1440
  onSubmit: handleConfirm,
1405
1441
  placeholder: "Type a note and press Enter…"
1406
- }))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, noteMode ? "Type note + Enter to submit, ↑↓ to switch action" : "Press 1-4, y/n, or ↑↓ + Enter")));
1442
+ }))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, noteMode ? note.trim().length === 0 ? "Note required — type a note + Enter to submit, ↑↓ to switch action" : "Type note + Enter to submit, ↑↓ to switch action" : "Press 1-4, y/n, or ↑↓ + Enter")));
1407
1443
  }
1408
1444
  //#endregion
1409
1445
  //#region src/components/ConfigEditor.tsx
@@ -1856,21 +1892,22 @@ function McpViewer({ config, mcpManager, onClose }) {
1856
1892
  }
1857
1893
  //#endregion
1858
1894
  //#region src/components/MarkdownRenderer.tsx
1859
- function MarkdownRenderer({ content }) {
1895
+ function MarkdownRenderer({ content, columns: columnsProp }) {
1896
+ const columns = columnsProp ?? process.stdout.columns ?? 80;
1860
1897
  const tokens = marked.lexer(content);
1861
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, tokens.map((token, idx) => renderToken(token, idx)));
1898
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, tokens.map((token, idx) => renderToken(token, idx, columns)));
1862
1899
  }
1863
- function renderToken(token, idx) {
1900
+ function renderToken(token, idx, columns) {
1864
1901
  switch (token.type) {
1865
1902
  case "heading": return renderHeading(token, idx);
1866
1903
  case "code": return renderCodeBlock(token, idx);
1867
1904
  case "paragraph": return renderParagraph(token, idx);
1868
1905
  case "list": return renderList(token, idx);
1869
- case "blockquote": return renderBlockquote(token, idx);
1906
+ case "blockquote": return renderBlockquote(token, idx, columns);
1870
1907
  case "hr": return /* @__PURE__ */ React.createElement(Text, {
1871
1908
  key: idx,
1872
1909
  dimColor: true
1873
- }, "─".repeat(50));
1910
+ }, "─".repeat(Math.max(1, columns - 4)));
1874
1911
  case "space": return null;
1875
1912
  default:
1876
1913
  if ("text" in token) return /* @__PURE__ */ React.createElement(Text, { key: idx }, token.text);
@@ -1929,14 +1966,14 @@ function renderListItem(item, idx, ordered, number) {
1929
1966
  paddingLeft: 2
1930
1967
  }, /* @__PURE__ */ React.createElement(Text, null, bullet, " ", parseInlineText(item.text)));
1931
1968
  }
1932
- function renderBlockquote(token, idx) {
1969
+ function renderBlockquote(token, idx, columns) {
1933
1970
  return /* @__PURE__ */ React.createElement(Box, {
1934
1971
  key: idx,
1935
1972
  paddingLeft: 2,
1936
1973
  borderStyle: "single",
1937
1974
  borderLeft: true,
1938
1975
  borderColor: "gray"
1939
- }, token.tokens.map((t, i) => renderToken(t, i)));
1976
+ }, token.tokens.map((t, i) => renderToken(t, i, columns)));
1940
1977
  }
1941
1978
  /**
1942
1979
  * Parse inline markdown formatting (bold, italic, code, links)
@@ -2048,9 +2085,9 @@ function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPe
2048
2085
  setPendingBackgroundTrigger,
2049
2086
  onBackgroundCompletion
2050
2087
  ]);
2051
- const toggleAutoAcceptEdits = useCliStore((state) => state.toggleAutoAcceptEdits);
2088
+ const cycleInteractionMode = useCliStore((state) => state.cycleInteractionMode);
2052
2089
  useInput((_input, key) => {
2053
- if (key.tab && key.shift) toggleAutoAcceptEdits();
2090
+ if (key.tab && key.shift) cycleInteractionMode();
2054
2091
  });
2055
2092
  const [isBashMode, setIsBashMode] = useState(false);
2056
2093
  const handleSubmit = React.useCallback(async (input) => {
@@ -5906,15 +5943,18 @@ function CliApp() {
5906
5943
  if (contextResult.projectContext) startupLog.push(`📄 Project context: ${contextResult.projectContext.filename}`);
5907
5944
  for (const error of contextResult.errors) startupLog.push(`⚠️ Context file error: ${error}`);
5908
5945
  const featureModulePrompts = featureRegistry.getSystemPromptSections();
5909
- const cliSystemPrompt = buildSystemPrompt(config.preferences.promptVariant ?? "current", {
5946
+ const promptVariant = config.preferences.promptVariant ?? "current";
5947
+ const buildPromptForMode = (mode) => buildSystemPrompt(promptVariant, {
5910
5948
  contextContent: contextResult.mergedContent,
5911
5949
  agentStore,
5912
5950
  customCommands: state.customCommandStore.getAllCommands(),
5913
5951
  enableSkillTool,
5914
5952
  enableDynamicAgentCreation: config.preferences.enableDynamicAgentCreation === true,
5915
5953
  additionalDirectories,
5916
- featureModulePrompts: featureModulePrompts || void 0
5954
+ featureModulePrompts: featureModulePrompts || void 0,
5955
+ planModeFilePath: mode === "plan" ? getPlanModeFilePath(newSession.id) : void 0
5917
5956
  });
5957
+ const cliSystemPrompt = buildPromptForMode(useCliStore.getState().interactionMode);
5918
5958
  const maxIterations = config.preferences.maxIterations === null ? 999999 : config.preferences.maxIterations;
5919
5959
  const agent = new ReActAgent({
5920
5960
  userId: config.userId,
@@ -5928,6 +5968,14 @@ function CliApp() {
5928
5968
  systemPrompt: cliSystemPrompt
5929
5969
  });
5930
5970
  agentContext.currentAgent = agent;
5971
+ const agentInternalContext = agent.context;
5972
+ let lastInteractionMode = useCliStore.getState().interactionMode;
5973
+ useCliStore.subscribe((s) => {
5974
+ if (s.interactionMode === lastInteractionMode) return;
5975
+ lastInteractionMode = s.interactionMode;
5976
+ if (agentContext.currentAgent !== agent) return;
5977
+ agentInternalContext.systemPrompt = buildPromptForMode(s.interactionMode);
5978
+ });
5931
5979
  agent.observationQueue = agentContext.observationQueue;
5932
5980
  const stepHandler = (step) => {
5933
5981
  const { pendingMessages, updatePendingMessage } = useCliStore.getState();
@@ -7250,7 +7298,6 @@ Multi-line Input:
7250
7298
  logger.debug("=== New Session Started via /clear ===");
7251
7299
  decisionStoreRef.current.decisions = [];
7252
7300
  blockerStoreRef.current.blockers = [];
7253
- reviewGateStoreRef.current.reviewGates = [];
7254
7301
  if (state.checkpointStore) state.checkpointStore.setSessionId(newSession.id);
7255
7302
  setState((prev) => ({
7256
7303
  ...prev,
@@ -7258,6 +7305,17 @@ Multi-line Input:
7258
7305
  }));
7259
7306
  setStoreSession(newSession);
7260
7307
  useCliStore.getState().clearPendingMessages();
7308
+ const staleGate = useCliStore.getState().reviewGatePrompt;
7309
+ if (staleGate) {
7310
+ dequeueReviewGatePrompt();
7311
+ staleGate.resolve({
7312
+ decision: "rejected",
7313
+ note: "Session cleared."
7314
+ });
7315
+ }
7316
+ queueMicrotask(() => {
7317
+ reviewGateStoreRef.current.reviewGates = [];
7318
+ });
7261
7319
  usageCache = null;
7262
7320
  console.log("New session started.");
7263
7321
  console.log(`\n📝 Session: ${newSession.name} | 🤖 Model: ${newSession.model} | 📊 Tokens: 0\n`);
@@ -8071,6 +8129,7 @@ Multi-line Input:
8071
8129
  const newFeatureTools = newFeatureRegistry.getAllTools();
8072
8130
  agentContext.tools = [...baseTools, ...newFeatureTools];
8073
8131
  const newFeaturePrompts = newFeatureRegistry.getSystemPromptSections();
8132
+ const planFilePathForRebuild = useCliStore.getState().interactionMode === "plan" && state.session ? getPlanModeFilePath(state.session.id) : void 0;
8074
8133
  agentContext.systemPrompt = buildSystemPrompt(updatedConfig.preferences.promptVariant ?? "current", {
8075
8134
  contextContent: state.contextContent,
8076
8135
  agentStore: state.agentStore || void 0,
@@ -8078,7 +8137,8 @@ Multi-line Input:
8078
8137
  enableSkillTool: updatedConfig.preferences.enableSkillTool !== false,
8079
8138
  enableDynamicAgentCreation: updatedConfig.preferences.enableDynamicAgentCreation === true,
8080
8139
  additionalDirectories: state.additionalDirectories,
8081
- featureModulePrompts: newFeaturePrompts || void 0
8140
+ featureModulePrompts: newFeaturePrompts || void 0,
8141
+ planModeFilePath: planFilePathForRebuild
8082
8142
  });
8083
8143
  const moduleNames = newFeatureRegistry.getModuleNames();
8084
8144
  if (moduleNames.length > 0) console.error(`\n\x1b[36m🏰 Feature modules hot-reloaded: ${moduleNames.join(", ")}\x1b[0m`);
@@ -8217,15 +8277,15 @@ Multi-line Input:
8217
8277
  onUserQuestionResponse: (response, promptId) => {
8218
8278
  const currentPrompt = useCliStore.getState().userQuestionPrompt;
8219
8279
  if (currentPrompt?.id !== promptId) return;
8220
- currentPrompt.resolve(response);
8221
8280
  dequeueUserQuestionPrompt();
8281
+ currentPrompt.resolve(response);
8222
8282
  emitNextAwaitingStatus();
8223
8283
  },
8224
8284
  onReviewGateResponse: (response, promptId) => {
8225
8285
  const currentPrompt = useCliStore.getState().reviewGatePrompt;
8226
8286
  if (currentPrompt?.id !== promptId) return;
8227
- currentPrompt.resolve(response);
8228
8287
  dequeueReviewGatePrompt();
8288
+ currentPrompt.resolve(response);
8229
8289
  emitNextAwaitingStatus();
8230
8290
  }
8231
8291
  });
@@ -8236,6 +8296,9 @@ try {
8236
8296
  } catch {}
8237
8297
  if (import.meta.url.includes("/src/") || process.env.NODE_ENV === "development") logger.debug("🔧 Running in development mode (using TypeScript source)\n");
8238
8298
  warmFileCache();
8239
- render(/* @__PURE__ */ React.createElement(CliApp, null), { exitOnCtrlC: false });
8299
+ render(/* @__PURE__ */ React.createElement(CliApp, null), {
8300
+ exitOnCtrlC: false,
8301
+ alternateScreen: true
8302
+ });
8240
8303
  //#endregion
8241
8304
  export {};
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { n as useCliStore } from "./store-B0ImnWR4.mjs";
3
+ export { useCliStore };
@@ -3,6 +3,14 @@ import { create } from "zustand";
3
3
  //#region src/store/index.ts
4
4
  /** Active job statuses (jobs still in progress) */
5
5
  const ACTIVE_STATUSES = new Set(["running", "queued"]);
6
+ const INTERACTION_MODE_CYCLE = [
7
+ "normal",
8
+ "auto-accept",
9
+ "plan"
10
+ ];
11
+ function nextInteractionMode(current) {
12
+ return INTERACTION_MODE_CYCLE[(INTERACTION_MODE_CYCLE.indexOf(current) + 1) % INTERACTION_MODE_CYCLE.length];
13
+ }
6
14
  /** Check if a job status is active (running or queued) */
7
15
  function isActiveStatus(status) {
8
16
  return ACTIVE_STATUSES.has(status);
@@ -122,8 +130,9 @@ const useCliStore = create((set) => ({
122
130
  setShowConfigEditor: (show) => set({ showConfigEditor: show }),
123
131
  showMcpViewer: false,
124
132
  setShowMcpViewer: (show) => set({ showMcpViewer: show }),
125
- autoAcceptEdits: false,
126
- toggleAutoAcceptEdits: () => set((state) => ({ autoAcceptEdits: !state.autoAcceptEdits })),
133
+ interactionMode: "normal",
134
+ cycleInteractionMode: () => set((state) => ({ interactionMode: nextInteractionMode(state.interactionMode) })),
135
+ setInteractionMode: (mode) => set({ interactionMode: mode }),
127
136
  exitRequested: false,
128
137
  setExitRequested: (requested) => set({ exitRequested: requested }),
129
138
  backgroundAgents: [],
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { $ as RechartsChartTypeList, A as ImageGenerationUsageTransaction, At as secureParameters, B as NotFoundError, C as FileEvents, Ct as getMcpProviderMetadata, D as GenericCreditAddTransaction, Dt as obfuscateApiKey, E as GenerateImageToolCallSchema, Et as isGPTImageModel, F as KnowledgeType, G as ProfileEvents, H as OpenAIImageGenerationInput, I as LLMEvents, J as PurchaseTransaction, K as ProjectEvents, L as MiscEvents, M as InboxEvents, N as InviteEvents, Nt as CollectionType, O as GenericCreditDeductTransaction, Ot as resolveNavigationIntents, P as InviteType, Q as ReceivedCreditTransaction, R as ModalEvents, S as FeedbackEvents, St as getDataLakeTags, T as GEMINI_IMAGE_MODELS, Tt as isGPTImage2Model, U as Permission, V as OpenAIEmbeddingModel, W as PermissionDeniedError, X as REASONING_SUPPORTED_MODELS, Y as QuestMasterParamsSchema, Z as RealtimeVoiceUsageTransaction, _ as CompletionApiUsageTransaction, _t as VideoModels, a as ApiKeyEvents, at as SessionEvents, b as FIXED_TEMPERATURE_MODELS, bt as b4mLLMTools, c as AppFileEvents, ct as SupportedFabFileMimeTypes, d as BFL_IMAGE_MODELS, dt as TextGenerationUsageTransaction, et as RegInviteEvents, f as BFL_SAFETY_TOLERANCE, ft as ToolUsageTransaction, g as ChatModels, gt as VideoGenerationUsageTransaction, h as ChatCompletionCreateInputSchema, ht as VIDEO_SIZE_CONSTRAINTS, i as AiEvents, it as ResearchTaskType, j as ImageModels, k as ImageEditUsageTransaction, kt as sanitizeTelemetryError, l as ArtifactTypeSchema, lt as TagType, mt as UiNavigationEvents, n as logger, nt as ResearchTaskExecutionType, o as ApiKeyScope, ot as SpeechToTextModels, p as BedrockEmbeddingModel, pt as TransferCreditTransaction, q as PromptMetaZodSchema, r as ALERT_THRESHOLDS, rt as ResearchTaskPeriodicFrequencyType, s as ApiKeyType, st as SubscriptionCreditTransaction, t as ConfigStore, tt as ResearchModeParamsSchema, u as AuthEvents, ut as TaskScheduleHandler, v as DashboardParamsSchema, vt as VoyageAIEmbeddingModel, w as FriendshipEvents, wt as getViewById, x as FavoriteDocumentType, xt as getAccessibleDataLakes, y as ElabsEvents, yt as XAI_IMAGE_MODELS, z as ModelBackend } from "./ConfigStore-Bu_plzvP.mjs";
2
+ import { $ as RechartsChartTypeList, A as ImageGenerationUsageTransaction, At as secureParameters, B as NotFoundError, C as FileEvents, Ct as getMcpProviderMetadata, D as GenericCreditAddTransaction, Dt as obfuscateApiKey, E as GenerateImageToolCallSchema, Et as isGPTImageModel, F as KnowledgeType, G as ProfileEvents, H as OpenAIImageGenerationInput, I as LLMEvents, J as PurchaseTransaction, K as ProjectEvents, L as MiscEvents, M as InboxEvents, N as InviteEvents, Nt as CollectionType, O as GenericCreditDeductTransaction, Ot as resolveNavigationIntents, P as InviteType, Q as ReceivedCreditTransaction, R as ModalEvents, S as FeedbackEvents, St as getDataLakeTags, T as GEMINI_IMAGE_MODELS, Tt as isGPTImage2Model, U as Permission, V as OpenAIEmbeddingModel, W as PermissionDeniedError, X as REASONING_SUPPORTED_MODELS, Y as QuestMasterParamsSchema, Z as RealtimeVoiceUsageTransaction, _ as CompletionApiUsageTransaction, _t as VideoModels, a as ApiKeyEvents, at as SessionEvents, b as FIXED_TEMPERATURE_MODELS, bt as b4mLLMTools, c as AppFileEvents, ct as SupportedFabFileMimeTypes, d as BFL_IMAGE_MODELS, dt as TextGenerationUsageTransaction, et as RegInviteEvents, f as BFL_SAFETY_TOLERANCE, ft as ToolUsageTransaction, g as ChatModels, gt as VideoGenerationUsageTransaction, h as ChatCompletionCreateInputSchema, ht as VIDEO_SIZE_CONSTRAINTS, i as AiEvents, it as ResearchTaskType, j as ImageModels, k as ImageEditUsageTransaction, kt as sanitizeTelemetryError, l as ArtifactTypeSchema, lt as TagType, mt as UiNavigationEvents, n as logger, nt as ResearchTaskExecutionType, o as ApiKeyScope, ot as SpeechToTextModels, p as BedrockEmbeddingModel, pt as TransferCreditTransaction, q as PromptMetaZodSchema, r as ALERT_THRESHOLDS, rt as ResearchTaskPeriodicFrequencyType, s as ApiKeyType, st as SubscriptionCreditTransaction, t as ConfigStore, tt as ResearchModeParamsSchema, u as AuthEvents, ut as TaskScheduleHandler, v as DashboardParamsSchema, vt as VoyageAIEmbeddingModel, w as FriendshipEvents, wt as getViewById, x as FavoriteDocumentType, xt as getAccessibleDataLakes, y as ElabsEvents, yt as XAI_IMAGE_MODELS, z as ModelBackend } from "./ConfigStore-Bj1IOvWn.mjs";
3
3
  import { n as isPathAllowed, t as assertPathAllowed } from "./pathValidation-CIytuhr3-Dt5dntLx.mjs";
4
4
  import { execFile, execFileSync, spawn } from "child_process";
5
5
  import { createHash, randomBytes } from "crypto";
@@ -2268,6 +2268,15 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
2268
2268
  return this.toolCallCount;
2269
2269
  }
2270
2270
  /**
2271
+ * Get current iteration count.
2272
+ * Cheap accessor for hot paths (e.g., per-step event listeners) — prefer this
2273
+ * over `toCheckpoint().iteration`, which deep-clones messages, steps, and
2274
+ * confidence log on every call.
2275
+ */
2276
+ getIteration() {
2277
+ return this.iterations;
2278
+ }
2279
+ /**
2271
2280
  * Serialize the agent's current execution state for persistence.
2272
2281
  *
2273
2282
  * Call this after each iteration to create a durable checkpoint that can
@@ -3023,6 +3032,84 @@ const TOOL_WRITE_TODOS = "write_todos";
3023
3032
  const TOOL_CREATE_DYNAMIC_AGENT = "create_dynamic_agent";
3024
3033
  const EXPLORE_SUBAGENT_TYPE = "explore";
3025
3034
  /**
3035
+ * Plan-mode section: tells the model that write tools are blocked and where to put the plan.
3036
+ * Appended dynamically when the user cycles into plan mode via Shift+Tab.
3037
+ *
3038
+ * The phased workflow (understand → clarify → design → present) mirrors Claude Code's
3039
+ * plan-mode prompting: research before designing, ask the user before assuming, and only
3040
+ * write the plan after ambiguities are resolved.
3041
+ */
3042
+ function buildPlanModePromptSection(planModeFilePath) {
3043
+ return `
3044
+
3045
+ ## PLAN MODE ACTIVE
3046
+
3047
+ The user has cycled into plan mode (Shift+Tab). Plan mode restricts WRITING, not READING. You still have a complete read toolkit — use it.
3048
+
3049
+ **Tools available in plan mode (use these aggressively):**
3050
+ - \`grep_search\` — find symbols, strings, patterns across files
3051
+ - \`glob_files\` — list files by pattern (use this instead of \`ls\` / \`find\`)
3052
+ - \`file_read\` — read file contents
3053
+ - \`find_definition\` — locate where a symbol is defined
3054
+ - \`get_file_structure\` — AST overview of a file
3055
+ - \`agent_delegate\` — delegate to read-only subagents (e.g. 'explore', 'plan')
3056
+ - \`ask_user_question\` — ask the user clarifying questions
3057
+ - \`current_datetime\`, \`math_evaluate\`, \`web_search\`, \`web_fetch\` — also fine
3058
+
3059
+ **Tools blocked in plan mode:**
3060
+ - \`bash_execute\`, \`edit_local_file\`, \`create_file\`, \`delete_file\` — and any other tool that mutates state.
3061
+ - Exception: \`create_file\` / \`edit_local_file\` targeting paths under \`${planModeFilePath.replace(/\/plan-[^/]+\.md$/, "/")}\` are allowed (that's where you write the plan).
3062
+
3063
+ **Forbidden responses:**
3064
+ - ❌ "I can't explore the directory because plan mode blocks shell commands."
3065
+ - ❌ "I'd need to run bash to check this."
3066
+ - ❌ Any variant of "plan mode prevents me from researching."
3067
+ - ✅ Instead: use \`glob_files\`, \`grep_search\`, \`file_read\`, or delegate to the \`explore\` subagent. Bash is not the only way to investigate code.
3068
+
3069
+ Ground every claim in files you have actually read. Do not write pseudocode in chat as a substitute for reading the real code.
3070
+
3071
+ Follow this phased workflow. Do not skip phases.
3072
+
3073
+ ### Phase 1 — Understand
3074
+ - Read the relevant code before forming opinions. Use grep_search / glob_files / file_read directly for narrow lookups.
3075
+ - For broad or uncertain scope, delegate to the 'explore' subagent via agent_delegate so the main context stays focused.
3076
+ - Identify what already exists vs. what needs to be built. Reuse existing functions and patterns rather than proposing new ones.
3077
+
3078
+ ### Phase 2 — Clarify (REQUIRED if anything is ambiguous)
3079
+ - If requirements are unclear, if there are multiple reasonable approaches, or if you would otherwise be guessing about user intent, **call the ask_user_question tool BEFORE writing the plan**.
3080
+ - Ask about: trade-offs the user should pick between, scope (which files / how broad), behavior on edge cases, naming, dependencies you would add.
3081
+ - Do NOT ask "is the plan ready?" or "should I proceed?" — those are decided by the user pressing Shift+Tab. Only ask substantive design questions.
3082
+ - If the request is fully unambiguous, skip this phase. Do not invent questions.
3083
+
3084
+ ### Phase 3 — Design
3085
+ - For complex tasks, delegate to the 'plan' subagent via agent_delegate to get a structured implementation plan, then critique its output.
3086
+ - For simple tasks, design directly.
3087
+
3088
+ ### Phase 4 — Write the plan
3089
+ Write the plan to \`${planModeFilePath}\` (writes to this path are permitted in plan mode). Build it incrementally — append sections as you research, do not wait until the end. Structure it as:
3090
+
3091
+ \`\`\`markdown
3092
+ ## Context
3093
+ Why this change is being made — the problem, what prompted it, the intended outcome.
3094
+
3095
+ ## Approach
3096
+ The chosen approach in 1-3 sentences. Mention rejected alternatives only if non-obvious.
3097
+
3098
+ ## Files to change
3099
+ - path/to/file.ts — what changes and why
3100
+ - path/to/other.ts — what changes and why
3101
+
3102
+ ## Reused existing code
3103
+ - function/module path — how it will be reused
3104
+
3105
+ ## Verification
3106
+ How to confirm the change works end-to-end (commands, tests, manual steps).
3107
+ \`\`\`
3108
+
3109
+ ### Phase 5 — Hand off
3110
+ Tell the user the plan is ready and where it lives (\`${planModeFilePath}\`). Summarize in 1-2 sentences. Do not ask for approval — the user will press Shift+Tab to exit plan mode and authorize execution.`;
3111
+ }
3112
+ /**
3026
3113
  * Build the CLI system prompt with optional project context
3027
3114
  * @param contextSection - Optional project-specific context to append (legacy string) or config object
3028
3115
  * @param config - Configuration object for building context sections (when first param is string)
@@ -3034,6 +3121,7 @@ function buildCoreSystemPrompt(contextSection, config) {
3034
3121
  let dynamicAgentSection = "";
3035
3122
  let directoriesSection = "";
3036
3123
  let featureModulesSection = "";
3124
+ let planModeSection = "";
3037
3125
  if (typeof contextSection === "string") {
3038
3126
  projectContextSection = contextSection;
3039
3127
  if (config) {
@@ -3041,6 +3129,7 @@ function buildCoreSystemPrompt(contextSection, config) {
3041
3129
  if (config.agentStore) agentDirectoryContext = config.agentStore.getDirectoryContext();
3042
3130
  if (config.enableDynamicAgentCreation) dynamicAgentSection = buildDynamicAgentPromptSection();
3043
3131
  if (config.featureModulePrompts) featureModulesSection = config.featureModulePrompts;
3132
+ if (config.planModeFilePath) planModeSection = buildPlanModePromptSection(config.planModeFilePath);
3044
3133
  }
3045
3134
  } else if (contextSection && typeof contextSection === "object") {
3046
3135
  config = contextSection;
@@ -3050,6 +3139,7 @@ function buildCoreSystemPrompt(contextSection, config) {
3050
3139
  if (config.enableDynamicAgentCreation) dynamicAgentSection = buildDynamicAgentPromptSection();
3051
3140
  if (config.additionalDirectories && config.additionalDirectories.length > 0) directoriesSection = `\n\n## Additional Allowed Directories\n\nIn addition to the working directory (${process.cwd()}), you have read/write access to these directories:\n${config.additionalDirectories.map((d) => `- ${d}`).join("\n")}\n\nTo access files in additional directories, pass the full path to the 'dir_path' parameter of file tools:\n- ${TOOL_GREP_SEARCH}(pattern="...", dir_path="/path/to/additional/dir")\n- ${TOOL_GLOB_FILES}(pattern="**/*.ts", dir_path="/path/to/additional/dir")\n- ${TOOL_FILE_READ}(path="/path/to/additional/dir/file.ts")\n\nWhen the user asks about content in an additional directory, search there first using the dir_path parameter.`;
3052
3141
  if (config.featureModulePrompts) featureModulesSection = config.featureModulePrompts;
3142
+ if (config.planModeFilePath) planModeSection = buildPlanModePromptSection(config.planModeFilePath);
3053
3143
  }
3054
3144
  return `You are an autonomous AI assistant with access to tools. Your job is to help users by taking action and solving problems proactively.
3055
3145
 
@@ -3152,7 +3242,7 @@ You have tools for tracking decisions, blockers, and human review gates during y
3152
3242
 
3153
3243
  - request_review_gate: Pause for explicit human approval before crossing a significant decision point — one that affects interpretation, evidence, cost, credentials, platform, or public commitment (e.g., narrowing research scope after synthesis, hard-to-reverse refactors, architectural pivots, dependency swaps). Do NOT use for routine operations or actions already covered by the standard permission system (file edits, bash commands). Treat a rejection as a hard stop — re-plan, do not retry.
3154
3244
 
3155
- These tools are lightweight — use them naturally as part of your work, not as a ceremony.${directoriesSection}${projectContextSection}${skillsSection}${featureModulesSection}`;
3245
+ These tools are lightweight — use them naturally as part of your work, not as a ceremony.${directoriesSection}${projectContextSection}${skillsSection}${featureModulesSection}${planModeSection}`;
3156
3246
  }
3157
3247
  /**
3158
3248
  * Build the minimal-variant system prompt. Reuses the project context,
@@ -3165,16 +3255,18 @@ function buildMinimalSystemPrompt(config = {}) {
3165
3255
  let skillsSection = "";
3166
3256
  let directoriesSection = "";
3167
3257
  let featureModulesSection = "";
3258
+ let planModeSection = "";
3168
3259
  if (config.contextContent) projectContextSection = `\n\n## Project Context\n\nFollow these project-specific instructions:\n\n${config.contextContent}`;
3169
3260
  if (config.enableSkillTool !== false && config.customCommands && config.customCommands.length > 0) skillsSection = buildSkillsPromptSection(config.customCommands);
3170
3261
  if (config.additionalDirectories && config.additionalDirectories.length > 0) directoriesSection = `\n\n## Additional Allowed Directories\n\nIn addition to the working directory (${process.cwd()}), you have read/write access to these directories:\n${config.additionalDirectories.map((d) => `- ${d}`).join("\n")}\n\nPass full paths to file tools' \`dir_path\` parameter to access these directories.`;
3171
3262
  if (config.featureModulePrompts) featureModulesSection = config.featureModulePrompts;
3263
+ if (config.planModeFilePath) planModeSection = buildPlanModePromptSection(config.planModeFilePath);
3172
3264
  return `You are an expert coding assistant. You help users by reading files, executing commands, editing code, and writing new files using the tools available to you.
3173
3265
 
3174
3266
  Guidelines:
3175
3267
  - Be concise in your responses.
3176
3268
  - Show file paths clearly when working with files.
3177
- - When the task is done, give the user a direct answer — no recap of steps already visible in the tool history.${directoriesSection}${projectContextSection}${skillsSection}${featureModulesSection}`;
3269
+ - When the task is done, give the user a direct answer — no recap of steps already visible in the tool history.${directoriesSection}${projectContextSection}${skillsSection}${featureModulesSection}${planModeSection}`;
3178
3270
  }
3179
3271
  /**
3180
3272
  * Pick a system prompt by variant. The dispatch point for the
@@ -3234,6 +3326,41 @@ You have access to the '${TOOL_CREATE_DYNAMIC_AGENT}' tool, which lets you creat
3234
3326
  thoroughness: "quick"`;
3235
3327
  }
3236
3328
  //#endregion
3329
+ //#region src/utils/planMode.ts
3330
+ const PLAN_MODE_DIR = ".b4m-cli/plans";
3331
+ const PATH_TOOLS = new Set([
3332
+ "create_file",
3333
+ "edit_local_file",
3334
+ "delete_file"
3335
+ ]);
3336
+ /**
3337
+ * Directory plan-mode artifacts are written to (under the working directory).
3338
+ * Writes to paths under this directory are permitted while plan mode is active.
3339
+ */
3340
+ function getPlanModeFileDir(cwd = process.cwd()) {
3341
+ return path.resolve(cwd, PLAN_MODE_DIR);
3342
+ }
3343
+ /**
3344
+ * Default plan file for the current session. Sessions can override this if needed.
3345
+ */
3346
+ function getPlanModeFilePath(sessionId, cwd = process.cwd()) {
3347
+ const safeId = sessionId.replace(/[^a-zA-Z0-9_-]/g, "_");
3348
+ return path.join(getPlanModeFileDir(cwd), `plan-${safeId}.md`);
3349
+ }
3350
+ /**
3351
+ * Whether a write/edit/delete tool call targets a path inside the plan-mode directory.
3352
+ * Used to allow incremental plan-file writes while plan mode blocks other writes.
3353
+ */
3354
+ function isWriteTargetingPlanFile(toolName, args, cwd = process.cwd()) {
3355
+ if (!PATH_TOOLS.has(toolName)) return false;
3356
+ const argPath = args?.path;
3357
+ if (typeof argPath !== "string" || argPath.length === 0) return false;
3358
+ const resolved = path.resolve(cwd, argPath);
3359
+ const planDir = getPlanModeFileDir(cwd);
3360
+ const rel = path.relative(planDir, resolved);
3361
+ return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
3362
+ }
3363
+ //#endregion
3237
3364
  //#region ../../b4m-core/utils/dist/imageGeneration/AIImageService.mjs
3238
3365
  /**
3239
3366
  * Abstract class for AI Image Service
@@ -5723,7 +5850,7 @@ z.object({
5723
5850
  * @see https://platform.openai.com/docs/guides/function-calling
5724
5851
  */
5725
5852
  const MAX_GOAL_LENGTH = 1e3;
5726
- const MAX_DESCRIPTION_LENGTH = 2e3;
5853
+ const MAX_DESCRIPTION_LENGTH$1 = 2e3;
5727
5854
  ChatModels.GPT5, ChatModels.GPT5_MINI, ChatModels.GPT5_NANO, ChatModels.GPT5_1, ChatModels.GPT5_2, ChatModels.GPT5_4, ChatModels.GPT5_4_MINI, ChatModels.GPT5_4_NANO, ChatModels.GPT5_5;
5728
5855
  /**
5729
5856
  * Invokes the image processor Lambda to convert and resize images
@@ -15204,7 +15331,8 @@ updateUserSchema.extend({
15204
15331
  note: z.string(),
15205
15332
  userName: z.string()
15206
15333
  })).optional(),
15207
- numReferralsAvailable: z.number().optional()
15334
+ numReferralsAvailable: z.number().optional(),
15335
+ disputePending: z.boolean().optional()
15208
15336
  });
15209
15337
  z.object({
15210
15338
  username: z.string(),
@@ -15907,7 +16035,7 @@ z.object({
15907
16035
  const questSchema = z.object({
15908
16036
  id: z.string(),
15909
16037
  title: z.string().min(1).max(255),
15910
- description: z.string().max(MAX_DESCRIPTION_LENGTH),
16038
+ description: z.string().max(MAX_DESCRIPTION_LENGTH$1),
15911
16039
  status: z.enum([
15912
16040
  "not_started",
15913
16041
  "in_progress",
@@ -15930,7 +16058,7 @@ const questResourceSchema = z.object({
15930
16058
  });
15931
16059
  z.object({
15932
16060
  title: z.string().min(1).max(255),
15933
- description: z.string().max(MAX_DESCRIPTION_LENGTH).optional(),
16061
+ description: z.string().max(MAX_DESCRIPTION_LENGTH$1).optional(),
15934
16062
  goal: z.string().min(1).max(MAX_GOAL_LENGTH),
15935
16063
  complexity: z.enum([
15936
16064
  "beginner",
@@ -18970,9 +19098,18 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
18970
19098
  });
18971
19099
  return result;
18972
19100
  }
19101
+ const { useCliStore } = await import("./store-44C_Fvdb.mjs");
19102
+ const interactionMode = useCliStore.getState().interactionMode;
19103
+ if (interactionMode === "plan" && !isReadOnlyTool(toolName) && !isWriteTargetingPlanFile(toolName, args)) {
19104
+ const result = `Tool "${toolName}" is blocked while plan mode is active. Plan mode is read-only — research the codebase, then write your plan to a file under ${getPlanModeFileDir()}/. The user will press Shift+Tab to exit plan mode and authorize execution.`;
19105
+ agentContext.observationQueue.push({
19106
+ toolName,
19107
+ result
19108
+ });
19109
+ return result;
19110
+ }
18973
19111
  if (!permissionManager.needsPermission(toolName, { isSandboxed })) return executeAndRecord();
18974
- const { useCliStore } = await import("./store-D2zh6DFz.mjs");
18975
- if (useCliStore.getState().autoAcceptEdits) return executeAndRecord();
19112
+ if (interactionMode === "auto-accept") return executeAndRecord();
18976
19113
  const response = await showPermissionPrompt(toolName, effectiveArgs, await generateToolPreview(toolName, args, isSandboxed));
18977
19114
  if (response.action === "deny") throw new PermissionDeniedError(toolName, args);
18978
19115
  if (response.action === "allow-session") permissionManager.trustToolForSession(toolName);
@@ -23989,6 +24126,10 @@ function createBlockerStore(onUpdate) {
23989
24126
  }
23990
24127
  //#endregion
23991
24128
  //#region src/tools/reviewGateTool.ts
24129
+ const MAX_DESCRIPTION_LENGTH = 2e3;
24130
+ const MAX_RECOMMENDATION_LENGTH = 1e3;
24131
+ const MAX_OPTION_LENGTH = 500;
24132
+ const MAX_OPTIONS_COUNT = 10;
23992
24133
  /**
23993
24134
  * Validate request_review_gate parameters
23994
24135
  * @throws Error if validation fails
@@ -23996,11 +24137,19 @@ function createBlockerStore(onUpdate) {
23996
24137
  function validateParams(args) {
23997
24138
  const params = args;
23998
24139
  if (typeof params.description !== "string" || params.description.trim() === "") throw new Error("request_review_gate: description must be a non-empty string");
24140
+ if (params.description.length > MAX_DESCRIPTION_LENGTH) throw new Error(`request_review_gate: description must be ${MAX_DESCRIPTION_LENGTH} characters or fewer`);
23999
24141
  if (params.options !== void 0) {
24000
24142
  if (!Array.isArray(params.options)) throw new Error("request_review_gate: options must be an array of strings");
24001
- for (const opt of params.options) if (typeof opt !== "string") throw new Error("request_review_gate: each option must be a string");
24143
+ if (params.options.length > MAX_OPTIONS_COUNT) throw new Error(`request_review_gate: options must contain ${MAX_OPTIONS_COUNT} entries or fewer`);
24144
+ for (const opt of params.options) {
24145
+ if (typeof opt !== "string") throw new Error("request_review_gate: each option must be a string");
24146
+ if (opt.length > MAX_OPTION_LENGTH) throw new Error(`request_review_gate: each option must be ${MAX_OPTION_LENGTH} characters or fewer`);
24147
+ }
24148
+ }
24149
+ if (params.recommendation !== void 0) {
24150
+ if (typeof params.recommendation !== "string") throw new Error("request_review_gate: recommendation must be a string");
24151
+ if (params.recommendation.length > MAX_RECOMMENDATION_LENGTH) throw new Error(`request_review_gate: recommendation must be ${MAX_RECOMMENDATION_LENGTH} characters or fewer`);
24002
24152
  }
24003
- if (params.recommendation !== void 0 && typeof params.recommendation !== "string") throw new Error("request_review_gate: recommendation must be a string");
24004
24153
  const options = params.options?.map((o) => o.trim()).filter((o) => o.length > 0);
24005
24154
  return {
24006
24155
  description: params.description.trim(),
@@ -24015,6 +24164,11 @@ function formatReviewGatesOutput(gates) {
24015
24164
  if (gates.length === 0) return "No review gates recorded in this session.";
24016
24165
  return gates.map((gate, index) => {
24017
24166
  const lines = [`${index + 1}. **${gate.description}**`, ` Status: ${gate.status}`];
24167
+ if (gate.recommendation) lines.push(` Recommendation: ${gate.recommendation}`);
24168
+ if (gate.options && gate.options.length > 0) {
24169
+ lines.push(" Options:");
24170
+ for (const opt of gate.options) lines.push(` • ${opt}`);
24171
+ }
24018
24172
  if (gate.userNote) lines.push(` Note: ${gate.userNote}`);
24019
24173
  const requested = new Date(gate.timestamp).toLocaleTimeString();
24020
24174
  lines.push(` Requested at: ${requested}`);
@@ -24053,7 +24207,9 @@ function createReviewGateTool(store, requestReviewFn) {
24053
24207
  description: params.description,
24054
24208
  status: response.decision,
24055
24209
  resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
24056
- userNote: trimmedNote && trimmedNote.length > 0 ? trimmedNote : void 0
24210
+ userNote: trimmedNote && trimmedNote.length > 0 ? trimmedNote : void 0,
24211
+ options: params.options,
24212
+ recommendation: params.recommendation
24057
24213
  };
24058
24214
  store.reviewGates.push(entry);
24059
24215
  if (store.onUpdate) store.onUpdate(store.reviewGates);
@@ -24111,4 +24267,4 @@ function createReviewGateStore(onUpdate) {
24111
24267
  };
24112
24268
  }
24113
24269
  //#endregion
24114
- export { SessionStore as $, formatStep as A, DEFAULT_RETRY_CONFIG as B, WebSocketToolExecutor as C, ServerLlmBackend as D, WebSocketLlmBackend as E, PermissionManager as F, OllamaBackend as G, clearFeatureModuleTools as H, generateCliTools as I, isReadOnlyTool as J, buildSystemPrompt as K, ALWAYS_DENIED_FOR_AGENTS as L, loadContextFiles as M, getApiUrl as N, McpManager as O, getEnvironmentName as P, CommandHistoryStore as Q, DEFAULT_AGENT_MODEL as R, ApiClient as S, FallbackLlmBackend as T, registerFeatureModuleTools as U, DEFAULT_THOROUGHNESS as V, setWebSocketToolExecutor as W, CustomCommandStore as X, ReActAgent as Y, CheckpointStore as Z, createAgentDelegateTool as _, createBlockerTools as a, formatFileSize$1 as at, createSkillTool as b, createDecisionStore as c, createFindDefinitionTool as d, OAuthClient as et, createTodoStore as f, BackgroundAgentManager as g, createBackgroundAgentTools as h, createBlockerStore as i, mergeCommands as it, extractCompactInstructions as j, substituteArguments as k, formatDecisionsOutput as l, createCoordinateTaskTool as m, createReviewGateTool as n, processFileReferences as nt, formatBlockersOutput as o, searchFiles as ot, createWriteTodosTool as p, buildSkillsPromptSection as q, formatReviewGatesOutput as r, searchCommands as rt, createDecisionLogTool as s, warmFileCache as st, createReviewGateStore as t, hasFileReferences as tt, createGetFileStructureTool as u, AgentStore as v, WebSocketConnectionManager as w, parseAgentConfig as x, SubagentOrchestrator as y, DEFAULT_MAX_ITERATIONS as z };
24270
+ export { CommandHistoryStore as $, formatStep as A, DEFAULT_RETRY_CONFIG as B, WebSocketToolExecutor as C, ServerLlmBackend as D, WebSocketLlmBackend as E, PermissionManager as F, OllamaBackend as G, clearFeatureModuleTools as H, generateCliTools as I, buildSkillsPromptSection as J, getPlanModeFilePath as K, ALWAYS_DENIED_FOR_AGENTS as L, loadContextFiles as M, getApiUrl as N, McpManager as O, getEnvironmentName as P, CheckpointStore as Q, DEFAULT_AGENT_MODEL as R, ApiClient as S, FallbackLlmBackend as T, registerFeatureModuleTools as U, DEFAULT_THOROUGHNESS as V, setWebSocketToolExecutor as W, ReActAgent as X, isReadOnlyTool as Y, CustomCommandStore as Z, createAgentDelegateTool as _, createBlockerTools as a, mergeCommands as at, createSkillTool as b, createDecisionStore as c, warmFileCache as ct, createFindDefinitionTool as d, SessionStore as et, createTodoStore as f, BackgroundAgentManager as g, createBackgroundAgentTools as h, createBlockerStore as i, searchCommands as it, extractCompactInstructions as j, substituteArguments as k, formatDecisionsOutput as l, createCoordinateTaskTool as m, createReviewGateTool as n, hasFileReferences as nt, formatBlockersOutput as o, formatFileSize$1 as ot, createWriteTodosTool as p, buildSystemPrompt as q, formatReviewGatesOutput as r, processFileReferences as rt, createDecisionLogTool as s, searchFiles as st, createReviewGateStore as t, OAuthClient as tt, createGetFileStructureTool as u, AgentStore as v, WebSocketConnectionManager as w, parseAgentConfig as x, SubagentOrchestrator as y, DEFAULT_MAX_ITERATIONS as z };
@@ -4,7 +4,7 @@ import { homedir } from "os";
4
4
  import path from "path";
5
5
  import axios from "axios";
6
6
  //#region package.json
7
- var version = "0.7.0";
7
+ var version = "0.8.1";
8
8
  //#endregion
9
9
  //#region src/utils/updateChecker.ts
10
10
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bike4mind/cli",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "type": "module",
5
5
  "description": "Interactive CLI tool for Bike4Mind with ReAct agents",
6
6
  "license": "UNLICENSED",
@@ -118,11 +118,11 @@
118
118
  "tsx": "^4.21.0",
119
119
  "typescript": "^5.9.3",
120
120
  "vitest": "^4.1.2",
121
- "@bike4mind/agents": "0.7.0",
122
- "@bike4mind/common": "2.92.1",
123
- "@bike4mind/services": "2.79.4",
124
- "@bike4mind/utils": "2.19.3",
125
- "@bike4mind/mcp": "1.37.3"
121
+ "@bike4mind/agents": "0.8.0",
122
+ "@bike4mind/common": "2.92.2",
123
+ "@bike4mind/mcp": "1.37.4",
124
+ "@bike4mind/utils": "2.19.4",
125
+ "@bike4mind/services": "2.80.0"
126
126
  },
127
127
  "optionalDependencies": {
128
128
  "@vscode/ripgrep": "^1.17.1"
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- import { n as useCliStore } from "./store-FkY4K-0M.mjs";
3
- export { useCliStore };