@bike4mind/cli 0.15.3 → 0.16.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/README.md CHANGED
@@ -578,6 +578,55 @@ Coming soon:
578
578
  ⏳ Session search and management
579
579
  ⏳ Tool execution monitoring
580
580
 
581
+ ## Running as a `claude` engine inside a host app
582
+
583
+ The CLI can be driven by a host app (e.g. a kanban-style agent pipeline) as a
584
+ drop-in replacement for `claude` — running a pipeline **stage** or a **board AI /
585
+ YAML** pane with full parity (board tools over HTTP MCP, the 3-layer brief
586
+ injected, the spec auto-fired on turn 1, and the live-card `processing` /
587
+ `action_required` / `ready` signal).
588
+
589
+ A host that only launches an engine whose executable basename is exactly `claude`
590
+ can be pointed at a **symlink** named `claude`:
591
+
592
+ ```bash
593
+ # Create a claude-named symlink to the CLI entry point somewhere on disk:
594
+ ln -s "$(pwd)/apps/cli/bin/bike4mind-cli.mjs" /usr/local/bin/claude-b4m/claude
595
+
596
+ # In the host, set the engine's `terminal_command` to that symlink path:
597
+ # /usr/local/bin/claude-b4m/claude
598
+ ```
599
+
600
+ Use a **direct symlink** whose basename is `claude` — not a `package.json` bin
601
+ alias (it would shadow the real `claude`) and not a wrapper shell script (argv
602
+ gets mangled). No other setup is required; the CLI replicates claude's launch-flag
603
+ and lifecycle-hook contract.
604
+
605
+ ### Supported claude-compatible flags
606
+
607
+ These are parsed in `bin/bike4mind-cli.mjs` into `B4M_*` env vars consumed by the
608
+ runtime. Unknown flags are tolerated (never hard-error) and the interactive TUI is
609
+ preserved (a positional prompt seeds **and** submits turn 1 without switching to
610
+ headless `-p`):
611
+
612
+ | Flag | Effect |
613
+ | --- | --- |
614
+ | `--mcp-config <file>` | Inject MCP servers from a claude-shape JSON (`{ "mcpServers": {...} }`). Supports HTTP transport (`type: "http"`, `url`, `headers`) with per-launch Bearer auth. |
615
+ | `--strict-mcp-config` | Use ONLY `--mcp-config` servers; ignore file-config and `.mcp.json` (board YAML pane). |
616
+ | `--append-system-prompt <text>` | Append text verbatim to the end of the system prompt (the 3-layer brief). |
617
+ | `--allowedTools <patterns>` | Auto-approve matching tools without a permission prompt (glob `mcp__manifold__*` or a space-separated explicit list). |
618
+ | `--settings <json>` | Inline JSON; the `hooks` subset drives lifecycle hooks (see below). Malformed JSON is ignored, never fatal. |
619
+ | `--session-id <uuid>` / `--resume <uuid>` | Pin / resume a session (board pane). `/clear` and `/compact` keep the pinned uuid; a bad `--resume` prints `No conversation found with session ID <uuid>` and exits non-zero so the host can self-heal the pane. |
620
+ | `<positional prompt>` | Seeds and auto-fires turn 1 while staying interactive. |
621
+
622
+ ### Lifecycle hooks (`--settings.hooks`)
623
+
624
+ Mirrors claude's hook contract for the host's `action_required` signal: a
625
+ `Notification`/`permission_prompt` command writes a sentinel file while an
626
+ interactive permission prompt blocks; `PostToolUse` / `Stop` / `UserPromptSubmit`
627
+ commands remove it. Each hook command runs via a real shell with its stdin piped
628
+ then closed (EOF), so a blocking `cat > <file>` hook returns immediately.
629
+
581
630
  ## License
582
631
 
583
632
  Private - Bike4Mind
@@ -111,6 +111,39 @@ const argv = await yargs(hideBin(process.argv))
111
111
  type: 'string',
112
112
  description: 'Add local Ollama models to the model picker (e.g. http://localhost:11434)',
113
113
  })
114
+ // ─── claude-compatible flags (host drop-in masquerade) ──────────────────────
115
+ // Declared so a host's claude launch flags parse into named options instead of
116
+ // leaking into the positional `argv._` task. See apps/cli README "host app".
117
+ .option('mcp-config', {
118
+ type: 'string',
119
+ description: 'Path to a JSON file of MCP servers to inject ({ "mcpServers": {...} })',
120
+ })
121
+ .option('strict-mcp-config', {
122
+ type: 'boolean',
123
+ description: 'Use ONLY --mcp-config servers; ignore file-config and .mcp.json',
124
+ default: false,
125
+ })
126
+ .option('append-system-prompt', {
127
+ type: 'string',
128
+ description: 'Text appended verbatim to the end of the composed system prompt',
129
+ })
130
+ .option('allowedTools', {
131
+ type: 'array',
132
+ string: true,
133
+ description: 'Tool names/globs auto-approved without a permission prompt (e.g. mcp__manifold__*)',
134
+ })
135
+ .option('settings', {
136
+ type: 'string',
137
+ description: 'Inline JSON settings (currently: lifecycle hooks) merged over user config',
138
+ })
139
+ .option('session-id', {
140
+ type: 'string',
141
+ description: 'Pin this session to a fixed uuid (first launch of a resumable pane)',
142
+ })
143
+ .option('resume', {
144
+ type: 'string',
145
+ description: 'Resume an existing session by uuid',
146
+ })
114
147
  .option('api-url', {
115
148
  type: 'string',
116
149
  description: 'Set a custom API URL (self-hosted instance) and clear auth tokens, then exit',
@@ -197,6 +230,39 @@ if (argv['ollama-host']) {
197
230
  process.env.B4M_OLLAMA_HOST = argv['ollama-host'];
198
231
  }
199
232
 
233
+ // ─── claude-compatible flags → B4M_* env (read by src/index.tsx init) ─────────
234
+ // Mirrors the established bin→env→init channel (cf. --add-dir → B4M_ADDITIONAL_DIRS).
235
+ if (argv['mcp-config']) {
236
+ process.env.B4M_MCP_CONFIG_FILE = resolve(argv['mcp-config']);
237
+ }
238
+ if (argv['strict-mcp-config']) {
239
+ process.env.B4M_STRICT_MCP_CONFIG = '1';
240
+ }
241
+ if (argv['append-system-prompt']) {
242
+ process.env.B4M_APPEND_SYSTEM_PROMPT = argv['append-system-prompt'];
243
+ }
244
+ if (argv.allowedTools && argv.allowedTools.length > 0) {
245
+ // Each element may itself be a space-separated list (the host's board pane passes
246
+ // "a b c" as a single argv token); flatten on whitespace into discrete patterns.
247
+ const patterns = argv.allowedTools.flatMap((s) => String(s).split(/\s+/)).filter(Boolean);
248
+ process.env.B4M_ALLOWED_TOOLS = JSON.stringify(patterns);
249
+ }
250
+ if (argv.settings) {
251
+ process.env.B4M_SETTINGS_JSON = argv.settings;
252
+ }
253
+ if (argv['session-id']) {
254
+ process.env.B4M_SESSION_ID = argv['session-id'];
255
+ }
256
+ if (argv.resume) {
257
+ process.env.B4M_RESUME_ID = argv.resume;
258
+ }
259
+ // Positional task (claude `<prompt>` form): seeds AND submits turn 1, stays interactive.
260
+ // Only when it's not a known subcommand and headless -p wasn't used.
261
+ const KNOWN_SUBCOMMANDS = new Set(['mcp', 'update', 'doctor']);
262
+ if (argv.prompt === undefined && argv._.length > 0 && !KNOWN_SUBCOMMANDS.has(String(argv._[0]))) {
263
+ process.env.B4M_INITIAL_PROMPT = String(argv._[0]);
264
+ }
265
+
200
266
  // Auto-detect environment: prefer production mode when dist exists
201
267
  const distPath = join(__dirname, '../dist/index.mjs');
202
268
  const srcPath = join(__dirname, '../src/index.tsx');
@@ -291,9 +291,6 @@ let ImageModels = /* @__PURE__ */ function(ImageModels) {
291
291
  ImageModels["FLUX_PRO_1_1"] = "flux-pro-1.1";
292
292
  ImageModels["FLUX_PRO_ULTRA"] = "flux-pro-1.1-ultra";
293
293
  ImageModels["FLUX_PRO_FILL"] = "flux-pro-1.0-fill";
294
- ImageModels["FLUX_PRO_CANNY"] = "flux-pro-1.0-canny";
295
- ImageModels["FLUX_PRO_DEPTH"] = "flux-pro-1.0-depth";
296
- ImageModels["FLUX_DEV"] = "flux-dev";
297
294
  ImageModels["FLUX_KONTEXT_PRO"] = "flux-kontext-pro";
298
295
  ImageModels["FLUX_KONTEXT_MAX"] = "flux-kontext-max";
299
296
  ImageModels["GROK_IMAGINE_IMAGE_QUALITY"] = "grok-imagine-image-quality";
@@ -2663,7 +2660,8 @@ const AgentCompletedAction = z.object({
2663
2660
  executionId: z.string(),
2664
2661
  answer: z.string().optional(),
2665
2662
  totalIterations: z.number(),
2666
- totalCreditsUsed: z.number()
2663
+ totalCreditsUsed: z.number(),
2664
+ mementoIds: z.array(z.string()).optional()
2667
2665
  });
2668
2666
  const AgentFailedAction = z.object({
2669
2667
  action: z.literal("failed"),
@@ -3615,10 +3613,26 @@ const OpenAIImageStyleSchema = z.enum(["vivid", "natural"]);
3615
3613
  /**
3616
3614
  * Maps legacy/removed image model IDs to their current replacements.
3617
3615
  * Prevents Zod validation failures when clients send stale persisted model names.
3616
+ *
3617
+ * Values are constrained to `ALL_IMAGE_MODELS` so a remap target can never point at
3618
+ * a model that the schema would itself reject. If a target model is later retired,
3619
+ * re-point its entry to the current replacement rather than deleting it — clients may
3620
+ * still hold the legacy key in persisted state. When adding an entry, bump the
3621
+ * `llm-settings` persist `version` in LLMContext so existing clients re-run the remap.
3622
+ *
3623
+ * Only `flux-dev` is aliased among the recently removed Flux ids. It was a general
3624
+ * text-to-image model with a confirmed stale client (#8853), so remapping to the
3625
+ * current BFL standard is safe and faithful. `flux-pro-1.0-canny` and
3626
+ * `flux-pro-1.0-depth` are intentionally left as hard validation errors: they were
3627
+ * never UI-selectable (nothing persists them, and no alerts show callers sending them)
3628
+ * and they are structural-control models — silently remapping them to a plain
3629
+ * text-to-image model would drop the control image and return the wrong kind of result,
3630
+ * so a clear "unsupported model" error is the more honest response.
3618
3631
  */
3619
3632
  const LEGACY_IMAGE_MODEL_MAP = {
3620
3633
  "dall-e-3": "gpt-image-1",
3621
- "dall-e-2": "gpt-image-1"
3634
+ "dall-e-2": "gpt-image-1",
3635
+ "flux-dev": "flux-pro-1.1"
3622
3636
  };
3623
3637
  const OpenAIImageGenerationInput = z.object({
3624
3638
  prompt: z.string(),
@@ -3829,6 +3843,8 @@ z.enum([
3829
3843
  "logoSettings",
3830
3844
  "enforceCredits",
3831
3845
  "enableTeamPlan",
3846
+ "allowOpenRegistration",
3847
+ "defaultFreeCredits",
3832
3848
  "enableGoogleCalendar",
3833
3849
  "googleCalendarServiceAccountEmail",
3834
3850
  "googleCalendarServiceAccountSecret",
@@ -5765,6 +5781,22 @@ const settingsMap = {
5765
5781
  group: API_SERVICE_GROUPS.CREDITS.id,
5766
5782
  category: "Users"
5767
5783
  }),
5784
+ allowOpenRegistration: makeBooleanSetting({
5785
+ key: "allowOpenRegistration",
5786
+ name: "Allow Open Registration",
5787
+ defaultValue: false,
5788
+ description: "Master switch for self-serve signup. When OFF (default), a valid invite code is required to register. When ON, users may register without an invite code; the Default Free Credits grant is then applied after they verify their email (anti-spam — see Default Free Credits). Safe to enable: the pre-request credit reservation caps every free user at the credits they are granted.",
5789
+ group: API_SERVICE_GROUPS.CREDITS.id,
5790
+ category: "Users"
5791
+ }),
5792
+ defaultFreeCredits: makeNumberSetting({
5793
+ key: "defaultFreeCredits",
5794
+ name: "Default Free Credits",
5795
+ defaultValue: 0,
5796
+ description: "Credits granted to a user who registers WITHOUT an invite code (only applies when Allow Open Registration is ON). Granted after the user verifies their email, NOT at signup — an unverified throwaway account gets 0 credits (anti-spam). A free user can never spend more than this — their hard ceiling of real model cost is roughly credits ÷ 1500 USD.",
5797
+ group: API_SERVICE_GROUPS.CREDITS.id,
5798
+ category: "Users"
5799
+ }),
5768
5800
  FacebookLink: makeStringSetting({
5769
5801
  key: "FacebookLink",
5770
5802
  name: "Facebook Link",
@@ -10238,11 +10270,32 @@ const ApiConfigSchema = z.object({ customUrl: z.url().optional() });
10238
10270
  */
10239
10271
  const McpServerSchema = z.object({
10240
10272
  name: z.string(),
10273
+ type: z.enum(["stdio", "http"]).optional(),
10241
10274
  command: z.string().optional(),
10242
10275
  args: z.array(z.string()).optional(),
10276
+ url: z.string().optional(),
10277
+ headers: z.record(z.string(), z.string()).optional(),
10243
10278
  env: z.record(z.string(), z.string()).prefault({}),
10244
10279
  enabled: z.boolean().prefault(true)
10245
- });
10280
+ }).superRefine(mcpServerTransportRefine);
10281
+ /**
10282
+ * A valid MCP server is exactly one of:
10283
+ * - stdio: has `command` (spawned child process)
10284
+ * - http: has `url` (streamable-HTTP endpoint)
10285
+ * Rejecting malformed configs at parse time (not connect time) surfaces errors early.
10286
+ */
10287
+ function mcpServerTransportRefine(server, ctx) {
10288
+ const hasCommand = typeof server.command === "string" && server.command.trim() !== "";
10289
+ const hasUrl = typeof server.url === "string" && server.url.trim() !== "";
10290
+ if (hasCommand && hasUrl) ctx.addIssue({
10291
+ code: "custom",
10292
+ message: "MCP server cannot set both \"command\" (stdio) and \"url\" (http)"
10293
+ });
10294
+ else if (!hasCommand && !hasUrl) ctx.addIssue({
10295
+ code: "custom",
10296
+ message: "MCP server must set either \"command\" (stdio) or \"url\" (http)"
10297
+ });
10298
+ }
10246
10299
  /**
10247
10300
  * MCP Servers can be specified in two formats:
10248
10301
  * 1. Array format (B4M native): [{ "name": "...", "command": "...", ... }]
@@ -10251,11 +10304,14 @@ const McpServerSchema = z.object({
10251
10304
  * Both formats are supported for maximum flexibility and compatibility.
10252
10305
  */
10253
10306
  const McpServersSchema = z.union([z.array(McpServerSchema), z.record(z.string(), z.object({
10307
+ type: z.enum(["stdio", "http"]).optional(),
10254
10308
  command: z.string().optional(),
10255
10309
  args: z.array(z.string()).optional(),
10310
+ url: z.string().optional(),
10311
+ headers: z.record(z.string(), z.string()).optional(),
10256
10312
  env: z.record(z.string(), z.string()).optional(),
10257
10313
  enabled: z.boolean().optional()
10258
- }))]);
10314
+ }).superRefine(mcpServerTransportRefine))]);
10259
10315
  /**
10260
10316
  * Normalize MCP servers to internal array format
10261
10317
  * Accepts both array and object formats
@@ -10263,15 +10319,21 @@ const McpServersSchema = z.union([z.array(McpServerSchema), z.record(z.string(),
10263
10319
  function normalizeMcpServers(servers) {
10264
10320
  if (Array.isArray(servers)) return servers.map((server) => ({
10265
10321
  name: server.name,
10322
+ type: server.type,
10266
10323
  command: server.command,
10267
10324
  args: server.args,
10325
+ url: server.url,
10326
+ headers: server.headers,
10268
10327
  env: server.env || {},
10269
10328
  enabled: server.enabled ?? true
10270
10329
  }));
10271
10330
  else return Object.entries(servers).map(([name, config]) => ({
10272
10331
  name,
10332
+ type: config.type,
10273
10333
  command: config.command,
10274
10334
  args: config.args,
10335
+ url: config.url,
10336
+ headers: config.headers,
10275
10337
  env: config.env || {},
10276
10338
  enabled: config.enabled ?? true
10277
10339
  }));
@@ -10503,6 +10565,29 @@ async function loadMcpJsonConfig(projectDir) {
10503
10565
  }
10504
10566
  }
10505
10567
  /**
10568
+ * Load an explicit `--mcp-config <file>` (claude shape: `{ "mcpServers": {...} }`).
10569
+ * Unlike `.mcp.json` this is an absolute path passed at launch, not project-relative.
10570
+ * Returns null on missing/malformed file (a bad config must not brick the launch).
10571
+ */
10572
+ async function loadMcpConfigFile(filePath) {
10573
+ try {
10574
+ const data = await promises.readFile(filePath, "utf-8");
10575
+ const rawConfig = JSON.parse(data);
10576
+ return normalizeMcpServers(McpJsonConfigSchema.parse(rawConfig).mcpServers);
10577
+ } catch (error) {
10578
+ if (error.code === "ENOENT") {
10579
+ console.error(`--mcp-config file not found: ${filePath}`);
10580
+ return null;
10581
+ }
10582
+ if (error instanceof z.ZodError) {
10583
+ console.error("--mcp-config validation error:", error.issues);
10584
+ return null;
10585
+ }
10586
+ console.error("Failed to load --mcp-config file:", error);
10587
+ return null;
10588
+ }
10589
+ }
10590
+ /**
10506
10591
  * Merge MCP servers from multiple configs
10507
10592
  * Later configs can override earlier ones by name
10508
10593
  */
@@ -10684,6 +10769,12 @@ var ConfigStore = class {
10684
10769
  } else this.projectConfigDir = null;
10685
10770
  const mergedConfig = mergeConfigs(globalConfig, projectConfig, projectLocalConfig);
10686
10771
  if (mcpJsonServers && mcpJsonServers.length > 0) mergedConfig.mcpServers = mergeMcpServers(mcpJsonServers, mergedConfig.mcpServers);
10772
+ const mcpConfigFile = process.env.B4M_MCP_CONFIG_FILE;
10773
+ if (mcpConfigFile) {
10774
+ const injected = await loadMcpConfigFile(mcpConfigFile);
10775
+ if (process.env.B4M_STRICT_MCP_CONFIG === "1") mergedConfig.mcpServers = injected ?? [];
10776
+ else if (injected) mergedConfig.mcpServers = mergeMcpServers(mergedConfig.mcpServers, injected);
10777
+ }
10687
10778
  this.config = mergedConfig;
10688
10779
  return this.config;
10689
10780
  } catch (error) {
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as ConfigStore } from "../ConfigStore-D4gALtkZ.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-CtGc4fF2.mjs";
3
3
  //#region src/commands/apiCommand.ts
4
4
  /**
5
5
  * External API config command (--api-url / --reset-api)
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as version } from "../package-CfGv2cC5.mjs";
2
+ import { t as version } from "../package-3pouvthv.mjs";
3
3
  import { a as fetchLatestVersion, c as isNpmPrefixWritable, i as compareSemver } from "../updateChecker-C8xsNY2L.mjs";
4
4
  import { t as checkRipgrep } from "../ripgrepCheck-BmkyTK2i.mjs";
5
5
  import { execSync } from "child_process";
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as ConfigStore } from "../ConfigStore-D4gALtkZ.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-CtGc4fF2.mjs";
3
3
  //#region src/commands/envCommand.ts
4
4
  /**
5
5
  * Environment switching for the `--dev` / `--prod` launch flags.
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as generateCliTools, G as buildSystemPrompt, J as ReActAgent, N as loadContextFiles, P as PermissionManager, Q as SessionStore, S as ApiClient, T as FallbackLlmBackend, U as setWebSocketToolExecutor, X as CheckpointStore, Y as CustomCommandStore, _ as createAgentDelegateTool, b as createSkillTool, d as createFindDefinitionTool, f as createTodoStore, g as BackgroundAgentManager, h as createBackgroundAgentTools, k as McpManager, m as createCoordinateTaskTool, p as createWriteTodosTool, q as isReadOnlyTool, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, y as SubagentOrchestrator } from "../tools-GgAy5rmD.mjs";
3
- import { n as logger, r as getApiUrl, t as ConfigStore } from "../ConfigStore-D4gALtkZ.mjs";
2
+ import { $ as SessionStore, C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as generateCliTools, J as isReadOnlyTool, K as buildSystemPrompt, N as loadContextFiles, P as PermissionManager, 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, k as McpManager, m as createCoordinateTaskTool, p as createWriteTodosTool, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, y as SubagentOrchestrator } from "../tools-D9eSRR7Q.mjs";
3
+ import { n as logger, r as getApiUrl, t as ConfigStore } from "../ConfigStore-CtGc4fF2.mjs";
4
4
  import { t as DEFAULT_SANDBOX_CONFIG } from "../types-LyRNHOiS.mjs";
5
5
  import { t as createSandboxRuntime } from "../SandboxRuntimeAdapter-ChGlxSGQ.mjs";
6
6
  import { t as SandboxOrchestrator } from "../SandboxOrchestrator-BoINxbX4.mjs";
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as ConfigStore } from "../ConfigStore-D4gALtkZ.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-CtGc4fF2.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 { t as version } from "../package-CfGv2cC5.mjs";
2
+ import { t as version } from "../package-3pouvthv.mjs";
3
3
  import { c as isNpmPrefixWritable, l as setAutoUpdatePreference, n as REEXEC_GUARD_ENV, o as forceCheckForUpdate, r as checkForUpdate, s as getAutoUpdatePreference, t as INSTALL_CMD, u as shouldAttemptAutoUpdate } from "../updateChecker-C8xsNY2L.mjs";
4
4
  import { t as checkRipgrep } from "../ripgrepCheck-BmkyTK2i.mjs";
5
5
  import { execSync, spawnSync } from "child_process";
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { $ as OAuthClient, A as substituteArguments, B as DEFAULT_THOROUGHNESS, C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as generateCliTools, G as buildSystemPrompt, H as registerFeatureModuleTools, I as ALWAYS_DENIED_FOR_AGENTS, J as ReActAgent, K as buildSkillsPromptSection, L as DEFAULT_AGENT_MODEL, M as extractCompactInstructions, N as loadContextFiles, O as isTransientNetworkError, P as PermissionManager, Q as SessionStore, R as DEFAULT_MAX_ITERATIONS, S as ApiClient, T as FallbackLlmBackend, U as setWebSocketToolExecutor, V as clearFeatureModuleTools, W as getPlanModeFilePath, X as CheckpointStore, Y as CustomCommandStore, Z as CommandHistoryStore, _ as createAgentDelegateTool, a as createBlockerTools, at as searchFiles, b as createSkillTool, c as createDecisionStore, d as createFindDefinitionTool, et as hasFileReferences, f as createTodoStore, g as BackgroundAgentManager, h as createBackgroundAgentTools, i as createBlockerStore, it as formatFileSize, j as formatStep, k as McpManager, l as formatDecisionsOutput, m as createCoordinateTaskTool, n as createReviewGateTool, nt as searchCommands, o as formatBlockersOutput, ot as warmFileCache, p as createWriteTodosTool, q as isReadOnlyTool, r as formatReviewGatesOutput, rt as mergeCommands, s as createDecisionLogTool, t as createReviewGateStore, tt as processFileReferences, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, x as parseAgentConfig, y as SubagentOrchestrator, z as DEFAULT_RETRY_CONFIG } from "./tools-GgAy5rmD.mjs";
2
+ import { $ as SessionStore, A as substituteArguments, B as DEFAULT_RETRY_CONFIG, C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as generateCliTools, G as getPlanModeFilePath, H as clearFeatureModuleTools, I as getProcessHooks, J as isReadOnlyTool, K as buildSystemPrompt, L as ALWAYS_DENIED_FOR_AGENTS, M as extractCompactInstructions, N as loadContextFiles, O as isTransientNetworkError, P as PermissionManager, 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 formatStep, k as McpManager, 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-D9eSRR7Q.mjs";
3
3
  import { n as useCliStore, t as selectActiveBackgroundAgents } from "./store-DV5s-qni.mjs";
4
- import { Gt as validateNotebookPath$1, Wt as validateJupyterKernelName, g as CREDIT_DEDUCT_TRANSACTION_TYPES, i as getEnvironmentName, n as logger, r as getApiUrl, t as ConfigStore, v as ChatModels } from "./ConfigStore-D4gALtkZ.mjs";
5
- import { t as version } from "./package-CfGv2cC5.mjs";
4
+ import { Gt as validateNotebookPath$1, Wt as validateJupyterKernelName, g as CREDIT_DEDUCT_TRANSACTION_TYPES, i as getEnvironmentName, n as logger, r as getApiUrl, t as ConfigStore, v as ChatModels } from "./ConfigStore-CtGc4fF2.mjs";
5
+ import { t as version } from "./package-3pouvthv.mjs";
6
6
  import { r as checkForUpdate } from "./updateChecker-C8xsNY2L.mjs";
7
7
  import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
8
8
  import { Box, Static, Text, render, useApp, useInput, usePaste, useStdout } from "ink";
@@ -3083,7 +3083,7 @@ function buildCompactionPrompt(messages, options = {}) {
3083
3083
  * @param preservedMessages - Recent messages to keep verbatim
3084
3084
  * @returns A new session with compacted context
3085
3085
  */
3086
- function createCompactedSession(originalSession, summary, preservedMessages) {
3086
+ function createCompactedSession(originalSession, summary, preservedMessages, preserveId = false) {
3087
3087
  const summaryMessage = {
3088
3088
  id: v4(),
3089
3089
  role: "user",
@@ -3103,7 +3103,7 @@ function createCompactedSession(originalSession, summary, preservedMessages) {
3103
3103
  ...preservedMessages
3104
3104
  ] : [summaryMessage, ...preservedMessages];
3105
3105
  return {
3106
- id: v4(),
3106
+ id: preserveId ? originalSession.id : v4(),
3107
3107
  name: `${originalSession.name} (compacted)`,
3108
3108
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3109
3109
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -6327,8 +6327,18 @@ function CliApp() {
6327
6327
  logger.warn(`🤖 Using fallback model: ${modelInfo.id}`);
6328
6328
  }
6329
6329
  llm.currentModel = modelInfo.id;
6330
- const newSession = {
6331
- id: v4(),
6330
+ const pinnedSessionId = process.env.B4M_SESSION_ID;
6331
+ const resumeSessionId = process.env.B4M_RESUME_ID;
6332
+ let newSession;
6333
+ if (resumeSessionId) {
6334
+ const resumed = await state.sessionStore.load(resumeSessionId);
6335
+ if (!resumed) {
6336
+ console.error(`No conversation found with session ID ${resumeSessionId}`);
6337
+ process.exit(1);
6338
+ }
6339
+ newSession = resumed;
6340
+ } else newSession = {
6341
+ id: pinnedSessionId || v4(),
6332
6342
  name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
6333
6343
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6334
6344
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -6392,6 +6402,7 @@ function CliApp() {
6392
6402
  let permissionPromptCounter = 0;
6393
6403
  const promptFn = (toolName, args, preview) => {
6394
6404
  return new Promise((resolve) => {
6405
+ getProcessHooks()?.fireNotificationPermissionPrompt(toolName);
6395
6406
  const canBeTrusted = permissionManager.canBeTrusted(toolName);
6396
6407
  const id = `perm-${++permissionPromptCounter}`;
6397
6408
  const prompt = {
@@ -6600,6 +6611,7 @@ function CliApp() {
6600
6611
  additionalDirectories,
6601
6612
  featureModulePrompts: featureModulePrompts || void 0,
6602
6613
  planModeFilePath: mode === "plan" ? getPlanModeFilePath(newSession.id) : void 0,
6614
+ appendSystemPrompt: process.env.B4M_APPEND_SYSTEM_PROMPT,
6603
6615
  deferredToolNames: deferredToolRegistry.getDirectoryNames()
6604
6616
  });
6605
6617
  const cliSystemPrompt = buildPromptForMode(useCliStore.getState().interactionMode);
@@ -6778,6 +6790,16 @@ function CliApp() {
6778
6790
  const handleMessageRef = useRef(null);
6779
6791
  const abortControllerRef = useRef(state.abortController);
6780
6792
  abortControllerRef.current = state.abortController;
6793
+ const autoFiredRef = useRef(false);
6794
+ useEffect(() => {
6795
+ if (!isInitialized || autoFiredRef.current) return;
6796
+ const initialPrompt = process.env.B4M_INITIAL_PROMPT;
6797
+ if (!initialPrompt) return;
6798
+ autoFiredRef.current = true;
6799
+ setImmediate(() => {
6800
+ handleMessageRef.current?.(initialPrompt);
6801
+ });
6802
+ }, [isInitialized]);
6781
6803
  const emitNextAwaitingStatus = () => {
6782
6804
  const s = useCliStore.getState();
6783
6805
  if (s.permissionPrompt) {
@@ -7068,6 +7090,7 @@ function CliApp() {
7068
7090
  console.error("❌ CLI failed to initialize. Try restarting b4m.\n");
7069
7091
  return;
7070
7092
  }
7093
+ getProcessHooks()?.fireUserPromptSubmit();
7071
7094
  bridgePresence.emitEvent({
7072
7095
  type: "message",
7073
7096
  role: "user",
@@ -7102,7 +7125,7 @@ function CliApp() {
7102
7125
  if (compactionPrompt) {
7103
7126
  const result = await state.agent.run(compactionPrompt, { maxIterations: 1 });
7104
7127
  await state.sessionStore.save(activeSession);
7105
- const newSession = createCompactedSession(activeSession, result.finalAnswer, preservedMessages);
7128
+ const newSession = createCompactedSession(activeSession, result.finalAnswer, preservedMessages, !!(process.env.B4M_SESSION_ID || process.env.B4M_RESUME_ID));
7106
7129
  await logger.initialize(newSession.id);
7107
7130
  setState((prev) => ({
7108
7131
  ...prev,
@@ -7260,6 +7283,7 @@ function CliApp() {
7260
7283
  abortController: null
7261
7284
  }));
7262
7285
  useCliStore.getState().setIsThinking(false);
7286
+ getProcessHooks()?.fireStop();
7263
7287
  bridgePresence.emitEvent({
7264
7288
  type: "status",
7265
7289
  status: wasAborted ? "idle" : "awaiting_input"
@@ -8118,8 +8142,9 @@ Multi-line Input:
8118
8142
  console.clear();
8119
8143
  renderBanner();
8120
8144
  const model = state.session?.model || state.config?.defaultModel || ChatModels.CLAUDE_4_5_SONNET;
8145
+ const clearPinnedId = process.env.B4M_SESSION_ID || process.env.B4M_RESUME_ID;
8121
8146
  const newSession = {
8122
- id: v4(),
8147
+ id: clearPinnedId ? state.session?.id ?? clearPinnedId : v4(),
8123
8148
  name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
8124
8149
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
8125
8150
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -8458,7 +8483,7 @@ Multi-line Input:
8458
8483
  const summary = (await state.agent.run(compactionPrompt, { maxIterations: 1 })).finalAnswer;
8459
8484
  await state.sessionStore.save(state.session);
8460
8485
  const oldSessionName = state.session.name;
8461
- const newSession = createCompactedSession(state.session, summary, preservedMessages);
8486
+ const newSession = createCompactedSession(state.session, summary, preservedMessages, !!(process.env.B4M_SESSION_ID || process.env.B4M_RESUME_ID));
8462
8487
  await logger.initialize(newSession.id);
8463
8488
  setState((prev) => ({
8464
8489
  ...prev,
@@ -8975,6 +9000,7 @@ Multi-line Input:
8975
9000
  additionalDirectories: state.additionalDirectories,
8976
9001
  featureModulePrompts: newFeaturePrompts || void 0,
8977
9002
  planModeFilePath: planFilePathForRebuild,
9003
+ appendSystemPrompt: process.env.B4M_APPEND_SYSTEM_PROMPT,
8978
9004
  deferredToolNames: deferredToolRegistry.getDirectoryNames()
8979
9005
  }));
8980
9006
  const moduleNames = newFeatureRegistry.getModuleNames();
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  //#region package.json
3
- var version = "0.15.3";
3
+ var version = "0.16.0";
4
4
  //#endregion
5
5
  export { version as t };
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { $ as ProjectEvents, A as GenerateImageToolCallSchema, At as dayjsConfig_default, B as InviteEvents, Bt as resolveNavigationIntents, C as ElabsEvents, Ct as UnauthorizedError, D as ForbiddenError, Dt as VideoModels, E as FileEvents, Et as VideoGenerationUsageTransaction, F as ImageEditUsageTransaction, Ft as isGPTImage2Model, G as ModalEvents, H as KnowledgeType, Ht as secureParameters, I as ImageGenerationUsageTransaction, It as isGPTImageModel, J as OpenAIEmbeddingModel, Jt as isNearLimit, K as ModelBackend, Kt as buildRateLimitLogEntry, L as ImageModels, Lt as isSupportedEmbeddingModel, M as GenericCreditDeductTransaction, Mt as getDataLakeTags, N as HTTPError, Nt as getMcpProviderMetadata, O as FriendshipEvents, Ot as XAI_IMAGE_MODELS, P as HttpStatus, Pt as getViewById, Q as ProfileEvents, R as InboxEvents, Rt as isZodError, S as DashboardParamsSchema, St as UiNavigationEvents, T as FeedbackEvents, Tt as VIDEO_SIZE_CONSTRAINTS, U as LLMEvents, Ut as settingsMap, V as InviteType, Vt as sanitizeTelemetryError, W as MiscEvents, X as Permission, Xt as CollectionType, Y as OpenAIImageGenerationInput, Yt as parseRateLimitHeaders, Z as PermissionDeniedError, _ as ChatCompletionCreateInputSchema, _t as TaskScheduleHandler, a as ALERT_THRESHOLDS, at as ReceivedCreditTransaction, b as CompletionApiUsageTransaction, bt as ToolUsageTransaction, c as ApiKeyScope, ct as ResearchModeParamsSchema, d as ArtifactTypeSchema, dt as ResearchTaskType, et as PromptIntentSchema, f as AuthEvents, ft as SessionEvents, gt as TagType, h as BadRequestError, ht as SupportedFabFileMimeTypes, it as RealtimeVoiceUsageTransaction, j as GenericCreditAddTransaction, jt as getAccessibleDataLakes, k as GEMINI_IMAGE_MODELS, kt as b4mLLMTools, l as ApiKeyType, lt as ResearchTaskExecutionType, m as BFL_SAFETY_TOLERANCE, mt as SubscriptionCreditTransaction, n as logger, nt as PurchaseTransaction, o as AiEvents, ot as RechartsChartTypeList, p as BFL_IMAGE_MODELS, pt as SpeechToTextUsageTransaction, q as NotFoundError, qt as extractSnippetMeta, rt as QuestMasterParamsSchema, s as ApiKeyEvents, st as RegInviteEvents, t as ConfigStore, tt as PromptMetaZodSchema, u as AppFileEvents, ut as ResearchTaskPeriodicFrequencyType, v as ChatModels, vt as TextGenerationUsageTransaction, w as FavoriteDocumentType, wt as UnprocessableEntityError, x as CorruptedFileError, xt as TransferCreditTransaction, y as ClaudeArtifactMimeTypes, yt as TooManyRequestsError, z as InternalServerError, zt as obfuscateApiKey } from "./ConfigStore-D4gALtkZ.mjs";
2
+ import { $ as ProjectEvents, A as GenerateImageToolCallSchema, At as dayjsConfig_default, B as InviteEvents, Bt as resolveNavigationIntents, C as ElabsEvents, Ct as UnauthorizedError, D as ForbiddenError, Dt as VideoModels, E as FileEvents, Et as VideoGenerationUsageTransaction, F as ImageEditUsageTransaction, Ft as isGPTImage2Model, G as ModalEvents, H as KnowledgeType, Ht as secureParameters, I as ImageGenerationUsageTransaction, It as isGPTImageModel, J as OpenAIEmbeddingModel, Jt as isNearLimit, K as ModelBackend, Kt as buildRateLimitLogEntry, L as ImageModels, Lt as isSupportedEmbeddingModel, M as GenericCreditDeductTransaction, Mt as getDataLakeTags, N as HTTPError, Nt as getMcpProviderMetadata, O as FriendshipEvents, Ot as XAI_IMAGE_MODELS, P as HttpStatus, Pt as getViewById, Q as ProfileEvents, R as InboxEvents, Rt as isZodError, S as DashboardParamsSchema, St as UiNavigationEvents, T as FeedbackEvents, Tt as VIDEO_SIZE_CONSTRAINTS, U as LLMEvents, Ut as settingsMap, V as InviteType, Vt as sanitizeTelemetryError, W as MiscEvents, X as Permission, Xt as CollectionType, Y as OpenAIImageGenerationInput, Yt as parseRateLimitHeaders, Z as PermissionDeniedError, _ as ChatCompletionCreateInputSchema, _t as TaskScheduleHandler, a as ALERT_THRESHOLDS, at as ReceivedCreditTransaction, b as CompletionApiUsageTransaction, bt as ToolUsageTransaction, c as ApiKeyScope, ct as ResearchModeParamsSchema, d as ArtifactTypeSchema, dt as ResearchTaskType, et as PromptIntentSchema, f as AuthEvents, ft as SessionEvents, gt as TagType, h as BadRequestError, ht as SupportedFabFileMimeTypes, it as RealtimeVoiceUsageTransaction, j as GenericCreditAddTransaction, jt as getAccessibleDataLakes, k as GEMINI_IMAGE_MODELS, kt as b4mLLMTools, l as ApiKeyType, lt as ResearchTaskExecutionType, m as BFL_SAFETY_TOLERANCE, mt as SubscriptionCreditTransaction, n as logger, nt as PurchaseTransaction, o as AiEvents, ot as RechartsChartTypeList, p as BFL_IMAGE_MODELS, pt as SpeechToTextUsageTransaction, q as NotFoundError, qt as extractSnippetMeta, rt as QuestMasterParamsSchema, s as ApiKeyEvents, st as RegInviteEvents, t as ConfigStore, tt as PromptMetaZodSchema, u as AppFileEvents, ut as ResearchTaskPeriodicFrequencyType, v as ChatModels, vt as TextGenerationUsageTransaction, w as FavoriteDocumentType, wt as UnprocessableEntityError, x as CorruptedFileError, xt as TransferCreditTransaction, y as ClaudeArtifactMimeTypes, yt as TooManyRequestsError, z as InternalServerError, zt as obfuscateApiKey } from "./ConfigStore-CtGc4fF2.mjs";
3
3
  import { a as isUserLockedOut, c as userCanDisableMFA, d as userRequiresMFA, f as verifyBackupCode, i as getLockoutTimeRemaining, l as userEligibleForMFA, n as generateBackupCodes, o as recordFailedAttempt, p as verifyTOTPToken, r as generateTOTPSetup, s as shouldResetFailedAttempts, t as clearFailedAttempts, u as userHasMFAConfigured } from "./utils-PpNti-tY.mjs";
4
4
  import { n as isPathAllowed, t as assertPathAllowed } from "./pathValidation-D8tjkQXE-1HwvsuYT.mjs";
5
- import { t as version } from "./package-CfGv2cC5.mjs";
5
+ import { t as version } from "./package-3pouvthv.mjs";
6
6
  import { execFile, execFileSync, spawn } from "child_process";
7
7
  import crypto, { createHash, randomBytes, randomUUID } from "crypto";
8
8
  import { existsSync, promises, readFileSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync } from "fs";
@@ -70,6 +70,7 @@ import { Mutex } from "async-mutex";
70
70
  import "zod-validation-error";
71
71
  import { homedir as homedir$1 } from "node:os";
72
72
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
73
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
73
74
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
74
75
  import { fileURLToPath } from "url";
75
76
  import WsWebSocket from "ws";
@@ -1902,6 +1903,18 @@ function shouldUseParallelExecution(toolsUsed, isReadOnly = defaultIsReadOnlyToo
1902
1903
  return toolsUsed.filter((tool) => isReadOnly(tool.name)).length >= 2;
1903
1904
  }
1904
1905
  /**
1906
+ * True when an error is an abort/cancellation (user stop or execution timeout)
1907
+ * rather than a real failure. Aborts are benign and must NOT be logged at error
1908
+ * severity — error logs trip the CloudWatch ERROR→LiveOps/Slack alert path and
1909
+ * page on routine cancellations (#8947, #8669).
1910
+ */
1911
+ function isAbortError(error, signal) {
1912
+ if (signal?.aborted) return true;
1913
+ if (!(error instanceof Error)) return false;
1914
+ if (error.name === "AbortError") return true;
1915
+ return error.message.toLowerCase().includes("aborted");
1916
+ }
1917
+ /**
1905
1918
  * ReAct (Reasoning and Acting) Agent
1906
1919
  *
1907
1920
  * This agent uses the ReAct pattern to solve problems by:
@@ -2051,6 +2064,9 @@ var ReActAgent = class extends EventEmitter {
2051
2064
  let currentText = "";
2052
2065
  const processedToolIds = /* @__PURE__ */ new Set();
2053
2066
  let hadToolCalls = false;
2067
+ let thoughtEmitted = false;
2068
+ const iterStartInputTokens = this.totalInputTokens;
2069
+ const iterStartOutputTokens = this.totalOutputTokens;
2054
2070
  if (maxHistoryIterations > 0 && iterations > 1) {
2055
2071
  trimConversationHistory(messages, maxHistoryIterations, this.initialMessageCount);
2056
2072
  trimSteps(this.steps, maxHistoryIterations);
@@ -2092,35 +2108,18 @@ var ReActAgent = class extends EventEmitter {
2092
2108
  this.totalCacheWriteTokens += completionInfo.cacheStats.cacheWriteTokens || 0;
2093
2109
  }
2094
2110
  if (completionInfo.creditsUsed) this.totalCredits += completionInfo.creditsUsed;
2095
- if (currentText.trim() && completionInfo.toolsUsed?.length) {
2096
- const thoughtStep = {
2097
- type: "thought",
2098
- content: currentText.trim(),
2099
- metadata: { timestamp: Date.now() }
2100
- };
2101
- this.steps.push(thoughtStep);
2102
- this.emit("thought", thoughtStep);
2103
- }
2104
- if (!completionInfo.toolsUsed?.length && currentText.trim()) {
2105
- finalAnswer = currentText.trim();
2106
- const finalStep = {
2107
- type: "final_answer",
2108
- content: finalAnswer,
2109
- metadata: {
2110
- timestamp: Date.now(),
2111
- tokenUsage: {
2112
- prompt: inputTokens,
2113
- completion: outputTokens,
2114
- total: inputTokens + outputTokens
2115
- }
2116
- }
2117
- };
2118
- this.steps.push(finalStep);
2119
- this.emit("final_answer", finalStep);
2120
- iterationComplete = true;
2121
- }
2122
2111
  if (completionInfo.toolsUsed && completionInfo.toolsUsed.length > 0) {
2123
2112
  hadToolCalls = true;
2113
+ if (!thoughtEmitted && currentText.trim()) {
2114
+ const thoughtStep = {
2115
+ type: "thought",
2116
+ content: currentText.trim(),
2117
+ metadata: { timestamp: Date.now() }
2118
+ };
2119
+ this.steps.push(thoughtStep);
2120
+ this.emit("thought", thoughtStep);
2121
+ thoughtEmitted = true;
2122
+ }
2124
2123
  const thinkingBlocks = completionInfo.thinking || [];
2125
2124
  const unprocessedTools = [];
2126
2125
  for (const toolUse of completionInfo.toolsUsed) {
@@ -2159,6 +2158,26 @@ var ReActAgent = class extends EventEmitter {
2159
2158
  }
2160
2159
  }
2161
2160
  });
2161
+ if (!hadToolCalls && currentText.trim()) {
2162
+ finalAnswer = currentText.trim();
2163
+ const iterInputTokens = this.totalInputTokens - iterStartInputTokens;
2164
+ const iterOutputTokens = this.totalOutputTokens - iterStartOutputTokens;
2165
+ const finalStep = {
2166
+ type: "final_answer",
2167
+ content: finalAnswer,
2168
+ metadata: {
2169
+ timestamp: Date.now(),
2170
+ tokenUsage: {
2171
+ prompt: iterInputTokens,
2172
+ completion: iterOutputTokens,
2173
+ total: iterInputTokens + iterOutputTokens
2174
+ }
2175
+ }
2176
+ };
2177
+ this.steps.push(finalStep);
2178
+ this.emit("final_answer", finalStep);
2179
+ iterationComplete = true;
2180
+ }
2162
2181
  if (iterationComplete && finalAnswer) break;
2163
2182
  if (hadToolCalls && options.confidenceGate && this.iterationConfidences.length > 0) {
2164
2183
  const iterAvg = this.iterationConfidences.reduce((a, b) => a + b, 0) / this.iterationConfidences.length;
@@ -2258,7 +2277,8 @@ var ReActAgent = class extends EventEmitter {
2258
2277
  this.emit("complete", result);
2259
2278
  return result;
2260
2279
  }
2261
- this.context.logger.error("[ReActAgent] Error during execution:", error);
2280
+ if (isAbortError(error, options.signal)) this.context.logger.warn("[ReActAgent] Execution aborted (user cancel or timeout):", error);
2281
+ else this.context.logger.error("[ReActAgent] Error during execution:", error);
2262
2282
  this.emit("error", error);
2263
2283
  throw error;
2264
2284
  }
@@ -2530,6 +2550,9 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
2530
2550
  let finalAnswer = "";
2531
2551
  const processedToolIds = /* @__PURE__ */ new Set();
2532
2552
  let hadToolCalls = false;
2553
+ let thoughtEmitted = false;
2554
+ const iterStartInputTokens = this.totalInputTokens;
2555
+ const iterStartOutputTokens = this.totalOutputTokens;
2533
2556
  const cacheStrategy = options.enableCaching ? {
2534
2557
  enableCaching: true,
2535
2558
  cacheSystemPrompt: true,
@@ -2567,41 +2590,22 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
2567
2590
  this.totalCacheWriteTokens += completionInfo.cacheStats.cacheWriteTokens || 0;
2568
2591
  }
2569
2592
  if (completionInfo.creditsUsed) this.totalCredits += completionInfo.creditsUsed;
2570
- if (currentText.trim() && completionInfo.toolsUsed?.length) {
2571
- const thoughtStep = {
2572
- type: "thought",
2573
- content: currentText.trim(),
2574
- metadata: {
2575
- timestamp: Date.now(),
2576
- iteration: this.iterations - 1
2577
- }
2578
- };
2579
- this.steps.push(thoughtStep);
2580
- iterationSteps.push(thoughtStep);
2581
- this.emit("thought", thoughtStep);
2582
- }
2583
- if (!completionInfo.toolsUsed?.length && currentText.trim()) {
2584
- finalAnswer = currentText.trim();
2585
- const finalStep = {
2586
- type: "final_answer",
2587
- content: finalAnswer,
2588
- metadata: {
2589
- timestamp: Date.now(),
2590
- iteration: this.iterations - 1,
2591
- tokenUsage: {
2592
- prompt: inputTokens,
2593
- completion: outputTokens,
2594
- total: inputTokens + outputTokens
2595
- }
2596
- }
2597
- };
2598
- this.steps.push(finalStep);
2599
- iterationSteps.push(finalStep);
2600
- this.emit("final_answer", finalStep);
2601
- iterationComplete = true;
2602
- }
2603
2593
  if (completionInfo.toolsUsed && completionInfo.toolsUsed.length > 0) {
2604
2594
  hadToolCalls = true;
2595
+ if (!thoughtEmitted && currentText.trim()) {
2596
+ const thoughtStep = {
2597
+ type: "thought",
2598
+ content: currentText.trim(),
2599
+ metadata: {
2600
+ timestamp: Date.now(),
2601
+ iteration: this.iterations - 1
2602
+ }
2603
+ };
2604
+ this.steps.push(thoughtStep);
2605
+ iterationSteps.push(thoughtStep);
2606
+ this.emit("thought", thoughtStep);
2607
+ thoughtEmitted = true;
2608
+ }
2605
2609
  const thinkingBlocks = completionInfo.thinking || [];
2606
2610
  const unprocessedTools = [];
2607
2611
  for (const toolUse of completionInfo.toolsUsed) {
@@ -2645,6 +2649,28 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
2645
2649
  }
2646
2650
  }
2647
2651
  });
2652
+ if (!hadToolCalls && currentText.trim()) {
2653
+ finalAnswer = currentText.trim();
2654
+ const iterInputTokens = this.totalInputTokens - iterStartInputTokens;
2655
+ const iterOutputTokens = this.totalOutputTokens - iterStartOutputTokens;
2656
+ const finalStep = {
2657
+ type: "final_answer",
2658
+ content: finalAnswer,
2659
+ metadata: {
2660
+ timestamp: Date.now(),
2661
+ iteration: this.iterations - 1,
2662
+ tokenUsage: {
2663
+ prompt: iterInputTokens,
2664
+ completion: iterOutputTokens,
2665
+ total: iterInputTokens + iterOutputTokens
2666
+ }
2667
+ }
2668
+ };
2669
+ this.steps.push(finalStep);
2670
+ iterationSteps.push(finalStep);
2671
+ this.emit("final_answer", finalStep);
2672
+ iterationComplete = true;
2673
+ }
2648
2674
  if (hadToolCalls && options.confidenceGate && this.iterationConfidences.length > 0) {
2649
2675
  const iterAvg = this.iterationConfidences.reduce((a, b) => a + b, 0) / this.iterationConfidences.length;
2650
2676
  const decision = options.confidenceGate(iterAvg, this.iterations);
@@ -2761,7 +2787,8 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
2761
2787
  checkpoint: this.toCheckpoint()
2762
2788
  };
2763
2789
  }
2764
- this.context.logger.error("[ReActAgent] Error during runIteration:", error);
2790
+ if (isAbortError(error, options.signal)) this.context.logger.warn("[ReActAgent] Iteration aborted (user cancel or timeout):", error);
2791
+ else this.context.logger.error("[ReActAgent] Error during runIteration:", error);
2765
2792
  this.emit("error", error);
2766
2793
  throw error;
2767
2794
  }
@@ -4117,7 +4144,7 @@ These tools are lightweight — use them naturally as part of your work, not as
4117
4144
 
4118
4145
  ## Working Directory
4119
4146
 
4120
- The current working directory is \`${process.cwd()}\`. All relative paths in tool calls resolve from here. When using \`${TOOL_GLOB_FILES}\` or \`${TOOL_GREP_SEARCH}\` without an explicit \`dir_path\`, they search from this directory.${directoriesSection}${projectContextSection}${skillsSection}${featureModulesSection}${planModeSection}${deferredToolSection}`;
4147
+ The current working directory is \`${process.cwd()}\`. All relative paths in tool calls resolve from here. When using \`${TOOL_GLOB_FILES}\` or \`${TOOL_GREP_SEARCH}\` without an explicit \`dir_path\`, they search from this directory.${directoriesSection}${projectContextSection}${skillsSection}${featureModulesSection}${planModeSection}${deferredToolSection}${config?.appendSystemPrompt ? `\n\n${config.appendSystemPrompt}` : ""}`;
4121
4148
  }
4122
4149
  /**
4123
4150
  * Build the minimal-variant system prompt. Reuses the project context,
@@ -4147,7 +4174,7 @@ Guidelines:
4147
4174
 
4148
4175
  ## Working Directory
4149
4176
 
4150
- The current working directory is \`${process.cwd()}\`. All relative paths in tool calls resolve from here. When using \`${TOOL_GLOB_FILES}\` or \`${TOOL_GREP_SEARCH}\` without an explicit \`dir_path\`, they search from this directory.${directoriesSection}${projectContextSection}${skillsSection}${featureModulesSection}${planModeSection}${deferredToolSection}`;
4177
+ The current working directory is \`${process.cwd()}\`. All relative paths in tool calls resolve from here. When using \`${TOOL_GLOB_FILES}\` or \`${TOOL_GREP_SEARCH}\` without an explicit \`dir_path\`, they search from this directory.${directoriesSection}${projectContextSection}${skillsSection}${featureModulesSection}${planModeSection}${deferredToolSection}${config?.appendSystemPrompt ? `\n\n${config.appendSystemPrompt}` : ""}`;
4151
4178
  }
4152
4179
  /**
4153
4180
  * Pick a system prompt by variant. The dispatch point for the
@@ -8477,7 +8504,8 @@ var GeminiImageService = class extends AIImageService {
8477
8504
  async generateImageViaContent(prompt, model = ImageModels.GEMINI_2_5_FLASH_IMAGE) {
8478
8505
  const response = await this.genAI.models.generateContent({
8479
8506
  model,
8480
- contents: [{ text: prompt }]
8507
+ contents: [{ text: prompt }],
8508
+ config: { responseModalities: ["IMAGE", "TEXT"] }
8481
8509
  });
8482
8510
  if (!response.candidates || response.candidates.length === 0) throw new Error("No candidates returned from Gemini");
8483
8511
  const candidate = response.candidates[0];
@@ -8568,7 +8596,8 @@ var GeminiImageService = class extends AIImageService {
8568
8596
  });
8569
8597
  const response = await this.genAI.models.generateContent({
8570
8598
  model,
8571
- contents
8599
+ contents,
8600
+ config: { responseModalities: ["IMAGE", "TEXT"] }
8572
8601
  });
8573
8602
  if (!response.candidates || response.candidates.length === 0) throw new Error("No candidates returned from Gemini");
8574
8603
  const candidate = response.candidates[0];
@@ -9769,8 +9798,9 @@ function registerLambdaErrorHandlers(logger) {
9769
9798
  } else log.error("[Lambda] Unhandled promise rejection", logEntry);
9770
9799
  });
9771
9800
  process.on("uncaughtException", (error) => {
9772
- const logEntry = buildLogEntry(error, "uncaught_exception");
9773
- log.error("[Lambda] Uncaught exception", logEntry);
9801
+ const category = classifyError(error);
9802
+ if (category.startsWith("network_")) log.warn("[Lambda] Network error (uncaught exception)", buildLogEntry(error, category));
9803
+ else log.error("[Lambda] Uncaught exception", buildLogEntry(error, "uncaught_exception"));
9774
9804
  });
9775
9805
  }
9776
9806
  /**
@@ -19745,13 +19775,19 @@ const updateUserSchema = z.object({
19745
19775
  scrollbarWidth: z.number().optional(),
19746
19776
  experimentalFeatures: z.record(z.string(), z.boolean()).optional(),
19747
19777
  rechartsDisplayMode: z.enum(["inline", "artifact"]).optional(),
19778
+ toolsCatalogCollapsed: z.boolean().optional(),
19748
19779
  docxTemplateFileId: z.string().nullable().optional(),
19749
19780
  contextTelemetryLevel: z.enum([
19750
19781
  "none",
19751
19782
  "basic",
19752
19783
  "enhanced"
19753
19784
  ]).optional(),
19754
- contextTelemetryConsentedAt: z.date().optional()
19785
+ contextTelemetryConsentedAt: z.date().optional(),
19786
+ agentModeDefault: z.enum([
19787
+ "off",
19788
+ "auto",
19789
+ "on"
19790
+ ]).optional()
19755
19791
  }).nullable().optional()
19756
19792
  });
19757
19793
  z.object({
@@ -19794,7 +19830,7 @@ z.object({
19794
19830
  username: z.string(),
19795
19831
  email: z.string(),
19796
19832
  name: z.string(),
19797
- inviteCode: z.string(),
19833
+ inviteCode: z.string().optional(),
19798
19834
  password: z.string(),
19799
19835
  metadata: z.object({
19800
19836
  loginTime: z.date(),
@@ -23523,8 +23559,175 @@ const DEFAULT_AGENT_MODEL = ChatModels.CLAUDE_4_5_HAIKU;
23523
23559
  */
23524
23560
  const DEFAULT_THOROUGHNESS = "medium";
23525
23561
  //#endregion
23562
+ //#region src/agents/toolFilter.ts
23563
+ /**
23564
+ * Check if a tool name matches a pattern
23565
+ *
23566
+ * Supports wildcards (*) that match any sequence of characters.
23567
+ *
23568
+ * @param toolName - The actual tool name to check
23569
+ * @param pattern - The pattern to match against (may include * wildcards)
23570
+ * @returns true if the tool name matches the pattern
23571
+ *
23572
+ * @example
23573
+ * matchesToolPattern('mcp__github__create_issue', 'mcp__github__*') // true
23574
+ * matchesToolPattern('mcp__github__delete_repo', 'mcp__*__delete_*') // true
23575
+ * matchesToolPattern('file_read', 'file_read') // true
23576
+ * matchesToolPattern('file_read', 'file_*') // true
23577
+ * matchesToolPattern('file_read', '*_read') // true
23578
+ * matchesToolPattern('bash_execute', 'file_*') // false
23579
+ */
23580
+ function matchesToolPattern(toolName, pattern) {
23581
+ const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
23582
+ return new RegExp(`^${regexPattern}$`).test(toolName);
23583
+ }
23584
+ /**
23585
+ * Check if a tool name matches any pattern in a list
23586
+ *
23587
+ * @param toolName - The tool name to check
23588
+ * @param patterns - Array of patterns to match against
23589
+ * @returns true if the tool matches any pattern
23590
+ */
23591
+ function matchesAnyPattern(toolName, patterns) {
23592
+ return patterns.some((pattern) => matchesToolPattern(toolName, pattern));
23593
+ }
23594
+ /**
23595
+ * Filter tools based on allowed and denied patterns
23596
+ *
23597
+ * Rules:
23598
+ * 1. Denied patterns take precedence over allowed patterns
23599
+ * 2. If no allowed patterns specified, all tools are allowed (minus denied)
23600
+ * 3. If allowed patterns specified, only matching tools are allowed
23601
+ * 4. Supports wildcards in both allowed and denied patterns
23602
+ *
23603
+ * @param allTools - Complete list of available tools
23604
+ * @param allowedPatterns - Whitelist patterns (optional)
23605
+ * @param deniedPatterns - Blacklist patterns (optional)
23606
+ * @returns Filtered list of tools
23607
+ *
23608
+ * @example
23609
+ * // Allow only specific tools
23610
+ * filterToolsByPatterns(tools, ['file_read', 'grep_search'])
23611
+ *
23612
+ * // Allow all GitHub MCP tools except delete operations
23613
+ * filterToolsByPatterns(tools, ['mcp__github__*'], ['mcp__github__delete_*'])
23614
+ *
23615
+ * // Deny specific tools, allow everything else
23616
+ * filterToolsByPatterns(tools, undefined, ['create_file', 'edit_file'])
23617
+ */
23618
+ function filterToolsByPatterns(allTools, allowedPatterns, deniedPatterns) {
23619
+ return allTools.filter((tool) => {
23620
+ const toolName = tool.toolSchema.name;
23621
+ if (deniedPatterns && deniedPatterns.length > 0) {
23622
+ if (matchesAnyPattern(toolName, deniedPatterns)) return false;
23623
+ }
23624
+ if (!allowedPatterns || allowedPatterns.length === 0) return true;
23625
+ return matchesAnyPattern(toolName, allowedPatterns);
23626
+ });
23627
+ }
23628
+ //#endregion
23629
+ //#region src/utils/processHooks.ts
23630
+ const HOOK_TIMEOUT_MS = 5e3;
23631
+ /** Does a group's matcher match the event's matcher value? `*`/empty/undefined match anything. */
23632
+ function matcherMatches(groupMatcher, eventMatcher) {
23633
+ if (groupMatcher === void 0 || groupMatcher === "" || groupMatcher === "*") return true;
23634
+ return groupMatcher === eventMatcher;
23635
+ }
23636
+ var ProcessHooks = class {
23637
+ constructor(hooks) {
23638
+ this.hooks = hooks;
23639
+ }
23640
+ /**
23641
+ * Run every command registered for `event` whose matcher matches `eventMatcher`.
23642
+ * `payload` is written to each command's stdin (then EOF). Best-effort: a failing
23643
+ * command never throws or blocks the caller's lifecycle.
23644
+ */
23645
+ async fire(event, eventMatcher, payload) {
23646
+ const groups = this.hooks[event];
23647
+ if (!groups || groups.length === 0) return;
23648
+ const stdin = JSON.stringify({
23649
+ hook_event_name: event,
23650
+ ...eventMatcher ? { matcher: eventMatcher } : {},
23651
+ ...payload
23652
+ });
23653
+ const runs = [];
23654
+ for (const group of groups) {
23655
+ if (!matcherMatches(group.matcher, eventMatcher)) continue;
23656
+ for (const hook of group.hooks ?? []) {
23657
+ if (hook.type !== "command" || !hook.command) continue;
23658
+ runs.push(runShellCommand({
23659
+ command: hook.command,
23660
+ cwd: process.cwd(),
23661
+ timeoutMs: HOOK_TIMEOUT_MS,
23662
+ stdin
23663
+ }).catch(() => void 0));
23664
+ }
23665
+ }
23666
+ await Promise.all(runs);
23667
+ }
23668
+ /** Fired when an interactive permission prompt begins blocking. */
23669
+ fireNotificationPermissionPrompt(toolName) {
23670
+ return this.fire("Notification", "permission_prompt", { tool_name: toolName });
23671
+ }
23672
+ /** Fired after any tool completes. */
23673
+ firePostToolUse(toolName) {
23674
+ return this.fire("PostToolUse", void 0, { tool_name: toolName });
23675
+ }
23676
+ /** Fired at the end of every agent turn. */
23677
+ fireStop() {
23678
+ return this.fire("Stop");
23679
+ }
23680
+ /** Fired when the user submits a new prompt. */
23681
+ fireUserPromptSubmit() {
23682
+ return this.fire("UserPromptSubmit");
23683
+ }
23684
+ };
23685
+ /**
23686
+ * Parse a `--settings` JSON string into a {@link SettingsHooks}. Defensive:
23687
+ * malformed JSON or a missing/!-object `hooks` field yields `null` (no hooks),
23688
+ * never throws — a bad `--settings` must not brick the launch.
23689
+ */
23690
+ function parseSettingsHooks(settingsJson) {
23691
+ if (!settingsJson) return null;
23692
+ try {
23693
+ const parsed = JSON.parse(settingsJson);
23694
+ if (!parsed || typeof parsed !== "object") return null;
23695
+ const hooks = parsed.hooks;
23696
+ if (!hooks || typeof hooks !== "object") return null;
23697
+ return hooks;
23698
+ } catch {
23699
+ return null;
23700
+ }
23701
+ }
23702
+ let cached;
23703
+ /**
23704
+ * Lazily-built process-hook singleton from `B4M_SETTINGS_JSON`. Returns null when
23705
+ * no hooks are configured, so call sites can `void getProcessHooks()?.fireStop()`.
23706
+ */
23707
+ function getProcessHooks() {
23708
+ if (cached !== void 0) return cached;
23709
+ const hooks = parseSettingsHooks(process.env.B4M_SETTINGS_JSON);
23710
+ cached = hooks ? new ProcessHooks(hooks) : null;
23711
+ return cached;
23712
+ }
23713
+ //#endregion
23526
23714
  //#region src/utils/toolsAdapter.ts
23527
23715
  /**
23716
+ * Tool-name patterns auto-approved without a permission prompt, from `--allowedTools`
23717
+ * (claude-compat; e.g. `mcp__manifold__*`). Parsed once from B4M_ALLOWED_TOOLS — set by
23718
+ * bin/bike4mind-cli.mjs. Returns [] when unset so behaviour is unchanged off-host.
23719
+ */
23720
+ let cachedAllowedTools;
23721
+ function getAllowedToolPatterns() {
23722
+ if (cachedAllowedTools === void 0) try {
23723
+ const raw = process.env.B4M_ALLOWED_TOOLS;
23724
+ cachedAllowedTools = raw ? JSON.parse(raw) : [];
23725
+ } catch {
23726
+ cachedAllowedTools = [];
23727
+ }
23728
+ return cachedAllowedTools;
23729
+ }
23730
+ /**
23528
23731
  * Simple CLI-friendly storage adapter
23529
23732
  * Most methods won't be called for CLI-only tools
23530
23733
  * Silent to avoid interfering with Ink rendering
@@ -23633,6 +23836,7 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
23633
23836
  toolName,
23634
23837
  result
23635
23838
  });
23839
+ getProcessHooks()?.firePostToolUse(toolName);
23636
23840
  return result;
23637
23841
  }
23638
23842
  const { useCliStore } = await import("./store-DgzCTRkN.mjs");
@@ -23645,6 +23849,8 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
23645
23849
  });
23646
23850
  return result;
23647
23851
  }
23852
+ const allowedPatterns = getAllowedToolPatterns();
23853
+ if (allowedPatterns.length > 0 && matchesAnyPattern(toolName, allowedPatterns)) return executeAndRecord();
23648
23854
  if (!permissionManager.needsPermission(toolName, { isSandboxed })) return executeAndRecord();
23649
23855
  if (interactionMode === "auto-accept") return executeAndRecord();
23650
23856
  const response = await showPermissionPrompt(toolName, effectiveArgs, await generateToolPreview(toolName, args, isSandboxed));
@@ -24370,9 +24576,11 @@ var MCPClient = class {
24370
24576
  customArgs;
24371
24577
  suppressStderr;
24372
24578
  onStderrLine;
24579
+ url;
24580
+ headers;
24373
24581
  tools = [];
24374
24582
  serverName;
24375
- constructor({ envVariables, name, command, args, suppressStderr = false, onStderrLine }) {
24583
+ constructor({ envVariables, name, command, args, suppressStderr = false, onStderrLine, url, headers }) {
24376
24584
  this.mcp = new Client({
24377
24585
  name: "mcp-client-cli",
24378
24586
  version: "1.0.0"
@@ -24383,9 +24591,26 @@ var MCPClient = class {
24383
24591
  this.customArgs = args;
24384
24592
  this.suppressStderr = suppressStderr;
24385
24593
  this.onStderrLine = onStderrLine;
24594
+ this.url = url;
24595
+ this.headers = headers;
24386
24596
  }
24387
24597
  async connectToServer() {
24388
24598
  try {
24599
+ if (this.url) {
24600
+ const transport = new StreamableHTTPClientTransport(new URL(this.url), { requestInit: this.headers ? { headers: this.headers } : void 0 });
24601
+ transport.onerror = (error) => {
24602
+ console.error(`[MCP] Transport error for ${this.serverName}:`, error);
24603
+ };
24604
+ this.transport = transport;
24605
+ await this.mcp.connect(transport);
24606
+ const toolsResult = await this.mcp.listTools();
24607
+ this.tools = toolsResult.tools.map((tool) => ({
24608
+ name: tool.name,
24609
+ description: tool.description,
24610
+ input_schema: tool.inputSchema
24611
+ }));
24612
+ return;
24613
+ }
24389
24614
  const envVarsObject = this.envVariables.reduce((acc, env) => ({
24390
24615
  ...acc,
24391
24616
  [env.key]: env.value
@@ -24410,7 +24635,7 @@ var MCPClient = class {
24410
24635
  console.log(`[MCP] Using server: ${this.serverName} at ${serverScriptPath}`);
24411
24636
  }
24412
24637
  const stderrMode = this.suppressStderr ? "ignore" : this.onStderrLine ? "pipe" : void 0;
24413
- const transportConfig = {
24638
+ const stdioTransport = new StdioClientTransport({
24414
24639
  command,
24415
24640
  args,
24416
24641
  env: {
@@ -24418,13 +24643,13 @@ var MCPClient = class {
24418
24643
  ...envVarsObject
24419
24644
  },
24420
24645
  ...stderrMode && { stderr: stderrMode }
24421
- };
24422
- this.transport = new StdioClientTransport(transportConfig);
24423
- this.transport.onerror = (error) => {
24646
+ });
24647
+ this.transport = stdioTransport;
24648
+ stdioTransport.onerror = (error) => {
24424
24649
  console.error(`[MCP] Transport error for ${this.serverName}:`, error);
24425
24650
  };
24426
- await this.mcp.connect(this.transport);
24427
- if (this.onStderrLine && this.transport.stderr) this.readStderr(this.transport.stderr);
24651
+ await this.mcp.connect(stdioTransport);
24652
+ if (this.onStderrLine && stdioTransport.stderr) this.readStderr(stdioTransport.stderr);
24428
24653
  await new Promise((resolve) => setTimeout(resolve, 100));
24429
24654
  const toolsResult = await this.mcp.listTools();
24430
24655
  this.tools = toolsResult.tools.map((tool) => {
@@ -24646,6 +24871,8 @@ var McpManager = class {
24646
24871
  name: serverConfig.name,
24647
24872
  command: serverConfig.command,
24648
24873
  args: serverConfig.args,
24874
+ url: serverConfig.url,
24875
+ headers: serverConfig.headers,
24649
24876
  suppressStderr: true
24650
24877
  });
24651
24878
  await client.connectToServer();
@@ -24703,6 +24930,7 @@ var McpManager = class {
24703
24930
  const key = JSON.stringify({
24704
24931
  command: serverConfig.command,
24705
24932
  args: serverConfig.args,
24933
+ url: serverConfig.url,
24706
24934
  env: serverConfig.env
24707
24935
  });
24708
24936
  return createHash("sha256").update(key).digest("hex").slice(0, 16);
@@ -25945,73 +26173,6 @@ var ApiClient = class {
25945
26173
  }
25946
26174
  };
25947
26175
  //#endregion
25948
- //#region src/agents/toolFilter.ts
25949
- /**
25950
- * Check if a tool name matches a pattern
25951
- *
25952
- * Supports wildcards (*) that match any sequence of characters.
25953
- *
25954
- * @param toolName - The actual tool name to check
25955
- * @param pattern - The pattern to match against (may include * wildcards)
25956
- * @returns true if the tool name matches the pattern
25957
- *
25958
- * @example
25959
- * matchesToolPattern('mcp__github__create_issue', 'mcp__github__*') // true
25960
- * matchesToolPattern('mcp__github__delete_repo', 'mcp__*__delete_*') // true
25961
- * matchesToolPattern('file_read', 'file_read') // true
25962
- * matchesToolPattern('file_read', 'file_*') // true
25963
- * matchesToolPattern('file_read', '*_read') // true
25964
- * matchesToolPattern('bash_execute', 'file_*') // false
25965
- */
25966
- function matchesToolPattern(toolName, pattern) {
25967
- const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
25968
- return new RegExp(`^${regexPattern}$`).test(toolName);
25969
- }
25970
- /**
25971
- * Check if a tool name matches any pattern in a list
25972
- *
25973
- * @param toolName - The tool name to check
25974
- * @param patterns - Array of patterns to match against
25975
- * @returns true if the tool matches any pattern
25976
- */
25977
- function matchesAnyPattern(toolName, patterns) {
25978
- return patterns.some((pattern) => matchesToolPattern(toolName, pattern));
25979
- }
25980
- /**
25981
- * Filter tools based on allowed and denied patterns
25982
- *
25983
- * Rules:
25984
- * 1. Denied patterns take precedence over allowed patterns
25985
- * 2. If no allowed patterns specified, all tools are allowed (minus denied)
25986
- * 3. If allowed patterns specified, only matching tools are allowed
25987
- * 4. Supports wildcards in both allowed and denied patterns
25988
- *
25989
- * @param allTools - Complete list of available tools
25990
- * @param allowedPatterns - Whitelist patterns (optional)
25991
- * @param deniedPatterns - Blacklist patterns (optional)
25992
- * @returns Filtered list of tools
25993
- *
25994
- * @example
25995
- * // Allow only specific tools
25996
- * filterToolsByPatterns(tools, ['file_read', 'grep_search'])
25997
- *
25998
- * // Allow all GitHub MCP tools except delete operations
25999
- * filterToolsByPatterns(tools, ['mcp__github__*'], ['mcp__github__delete_*'])
26000
- *
26001
- * // Deny specific tools, allow everything else
26002
- * filterToolsByPatterns(tools, undefined, ['create_file', 'edit_file'])
26003
- */
26004
- function filterToolsByPatterns(allTools, allowedPatterns, deniedPatterns) {
26005
- return allTools.filter((tool) => {
26006
- const toolName = tool.toolSchema.name;
26007
- if (deniedPatterns && deniedPatterns.length > 0) {
26008
- if (matchesAnyPattern(toolName, deniedPatterns)) return false;
26009
- }
26010
- if (!allowedPatterns || allowedPatterns.length === 0) return true;
26011
- return matchesAnyPattern(toolName, allowedPatterns);
26012
- });
26013
- }
26014
- //#endregion
26015
26176
  //#region src/tools/skillTool.ts
26016
26177
  /**
26017
26178
  * Execute a lifecycle hook script
@@ -28751,4 +28912,4 @@ function createReviewGateStore(onUpdate) {
28751
28912
  };
28752
28913
  }
28753
28914
  //#endregion
28754
- export { OAuthClient as $, substituteArguments as A, DEFAULT_THOROUGHNESS as B, WebSocketToolExecutor as C, ServerLlmBackend as D, WebSocketLlmBackend as E, generateCliTools as F, buildSystemPrompt as G, registerFeatureModuleTools as H, ALWAYS_DENIED_FOR_AGENTS as I, ReActAgent as J, buildSkillsPromptSection as K, DEFAULT_AGENT_MODEL as L, extractCompactInstructions as M, loadContextFiles as N, isTransientNetworkError as O, PermissionManager as P, SessionStore as Q, DEFAULT_MAX_ITERATIONS as R, ApiClient as S, FallbackLlmBackend as T, setWebSocketToolExecutor as U, clearFeatureModuleTools as V, getPlanModeFilePath as W, CheckpointStore as X, CustomCommandStore as Y, CommandHistoryStore as Z, createAgentDelegateTool as _, createBlockerTools as a, searchFiles as at, createSkillTool as b, createDecisionStore as c, createFindDefinitionTool as d, hasFileReferences as et, createTodoStore as f, BackgroundAgentManager as g, createBackgroundAgentTools as h, createBlockerStore as i, formatFileSize$1 as it, formatStep as j, McpManager as k, formatDecisionsOutput as l, createCoordinateTaskTool as m, createReviewGateTool as n, searchCommands as nt, formatBlockersOutput as o, warmFileCache as ot, createWriteTodosTool as p, isReadOnlyTool as q, formatReviewGatesOutput as r, mergeCommands as rt, createDecisionLogTool as s, createReviewGateStore as t, processFileReferences as tt, createGetFileStructureTool as u, AgentStore as v, WebSocketConnectionManager as w, parseAgentConfig as x, SubagentOrchestrator as y, DEFAULT_RETRY_CONFIG as z };
28915
+ export { SessionStore as $, substituteArguments as A, DEFAULT_RETRY_CONFIG as B, WebSocketToolExecutor as C, ServerLlmBackend as D, WebSocketLlmBackend as E, generateCliTools as F, getPlanModeFilePath as G, clearFeatureModuleTools as H, getProcessHooks as I, isReadOnlyTool as J, buildSystemPrompt as K, ALWAYS_DENIED_FOR_AGENTS as L, extractCompactInstructions as M, loadContextFiles as N, isTransientNetworkError as O, PermissionManager 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, formatStep as j, McpManager 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bike4mind/cli",
3
- "version": "0.15.3",
3
+ "version": "0.16.0",
4
4
  "type": "module",
5
5
  "description": "Interactive CLI tool for Bike4Mind with ReAct agents",
6
6
  "license": "UNLICENSED",
@@ -107,9 +107,9 @@
107
107
  "zod": "^4.4.3",
108
108
  "zod-validation-error": "^5.0.0",
109
109
  "zustand": "^5.0.13",
110
- "@bike4mind/fab-pipeline": "0.3.3",
111
- "@bike4mind/llm-adapters": "0.4.3",
112
- "@bike4mind/observability": "0.1.0"
110
+ "@bike4mind/fab-pipeline": "0.3.4",
111
+ "@bike4mind/llm-adapters": "0.4.4",
112
+ "@bike4mind/observability": "0.1.1"
113
113
  },
114
114
  "devDependencies": {
115
115
  "@types/better-sqlite3": "^7.6.13",
@@ -124,11 +124,11 @@
124
124
  "tsx": "^4.22.3",
125
125
  "typescript": "^5.9.3",
126
126
  "vitest": "^4.1.9",
127
- "@bike4mind/agents": "0.15.0",
128
- "@bike4mind/common": "2.110.0",
129
- "@bike4mind/mcp": "1.37.27",
130
- "@bike4mind/services": "2.95.0",
131
- "@bike4mind/utils": "2.24.3"
127
+ "@bike4mind/agents": "0.15.1",
128
+ "@bike4mind/common": "2.111.0",
129
+ "@bike4mind/mcp": "1.38.0",
130
+ "@bike4mind/services": "2.96.0",
131
+ "@bike4mind/utils": "2.24.4"
132
132
  },
133
133
  "optionalDependencies": {
134
134
  "@vscode/ripgrep": "^1.18.0"