@bike4mind/cli 0.6.0 → 0.7.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
@@ -384,18 +384,111 @@ tail -f ~/.bike4mind/debug/[session-id].txt
384
384
 
385
385
  ## Development
386
386
 
387
+ This section covers the contributor workflow for hacking on the CLI from a checkout of the monorepo. For end-user installation see [Installation](#installation) above.
388
+
389
+ ### How the bin resolves source vs. built code
390
+
391
+ `apps/cli/bin/bike4mind-cli.mjs` auto-detects which mode to run in:
392
+
393
+ - If `apps/cli/dist/index.mjs` exists → runs the bundled production build
394
+ - Otherwise → falls back to `tsx` and imports `src/index.tsx` directly
395
+
396
+ This means **you do not need to rebuild the CLI itself between source edits** — `tsx` reads `src/` live on every invocation. The rebuild burden is only on the workspace packages that the CLI imports (see below).
397
+
398
+ ### Quick start
399
+
400
+ From the repo root:
401
+
387
402
  ```bash
388
- # Run in development mode
403
+ # 1. Install all workspace dependencies
404
+ pnpm install
405
+
406
+ # 2. Build the @bike4mind/* core packages so the CLI can import their dist/ outputs
407
+ pnpm turbo:core:build
408
+
409
+ # 3. Make the `b4m` and `bike4mind` commands point at this checkout
389
410
  cd apps/cli
390
- pnpm dev
411
+ pnpm link --global
412
+
413
+ # 4. Verify
414
+ which b4m
415
+ b4m --version
416
+ ```
417
+
418
+ After step 3, running `b4m` anywhere on your system executes this working tree. Re-run `pnpm link --global` if you move or rename the repo.
419
+
420
+ ### Editing CLI source (`apps/cli/src/`)
421
+
422
+ Just run `b4m`. The bin's `tsx` fallback picks up your edits on the next invocation — no build step needed.
423
+
424
+ If you want to test the bundled production path (the same code an end user would run after `npm install -g @bike4mind/cli`):
425
+
426
+ ```bash
427
+ pnpm --filter @bike4mind/cli build
428
+ b4m # now uses dist/index.mjs
429
+ ```
430
+
431
+ To drop back into source mode, delete `dist/`:
432
+
433
+ ```bash
434
+ rm -rf apps/cli/dist
435
+ ```
391
436
 
392
- # Build for production
393
- pnpm build
437
+ ### Editing workspace dependencies (`b4m-core/*`)
394
438
 
395
- # Type check
439
+ The CLI imports `@bike4mind/agents`, `@bike4mind/services`, `@bike4mind/utils`, `@bike4mind/mcp`, and `@bike4mind/common` via pnpm symlinks that resolve to each package's `dist/` (per its `exports` field). Source edits in those packages require a rebuild before `b4m` will see them.
440
+
441
+ Rebuild only the package you touched:
442
+
443
+ ```bash
444
+ pnpm --filter @bike4mind/agents build
445
+ ```
446
+
447
+ Or rebuild the whole core graph (cached, near-instant if only one package changed):
448
+
449
+ ```bash
450
+ pnpm turbo:core:build
451
+ ```
452
+
453
+ The next `b4m` invocation picks up the change. The CLI itself does not need to be rebuilt.
454
+
455
+ ### Watch mode (optional)
456
+
457
+ Each core package exposes `"dev": "tsdown --watch"`. In a separate terminal:
458
+
459
+ ```bash
460
+ pnpm --filter @bike4mind/agents dev
461
+ ```
462
+
463
+ `dist/` rebuilds on save, so the next `b4m` invocation sees changes without a manual `build`.
464
+
465
+ Caveats:
466
+ - This does **not** hot-reload an already-running interactive `b4m` session — you still exit and re-run.
467
+ - Running multiple watchers in parallel (one per package you're touching) hasn't been load-tested in this repo. If watch seems to miss changes or feels unreliable, fall back to manual `pnpm --filter <pkg> build`.
468
+
469
+ ### Verification commands
470
+
471
+ ```bash
472
+ # Inside apps/cli
396
473
  pnpm typecheck
474
+ pnpm test
475
+ pnpm test:watch
476
+
477
+ # From repo root (cached, recommended)
478
+ pnpm turbo:typecheck
479
+ pnpm turbo:test
480
+
481
+ # Run with debug logs
482
+ b4m --verbose
397
483
  ```
398
484
 
485
+ ### Common pitfalls
486
+
487
+ - **Don't `pnpm --filter @bike4mind/cli build` after every CLI source edit.** It's wasted work — the `tsx` fallback already runs your source live.
488
+ - **Don't use `npm link`.** This repo is pnpm-only; mixing tools breaks symlink resolution.
489
+ - **A stale `apps/cli/dist/` will mask your source changes.** If `b4m` is showing old behavior, check whether `dist/index.mjs` exists — the bin will prefer it over `src/`. Delete `dist/` to drop back to source mode.
490
+ - **First-run native module errors** (`better-sqlite3`, `sharp`): see [Build Requirements](#build-requirements) above. The postinstall hook handles most cases automatically.
491
+
399
492
  ## Architecture
400
493
 
401
494
  ```
@@ -8829,7 +8829,14 @@ const CliConfigSchema = z.object({
8829
8829
  maxIterations: z.number().nullable().prefault(10),
8830
8830
  enableSkillTool: z.boolean().optional().prefault(true),
8831
8831
  enableDynamicAgentCreation: z.boolean().optional().prefault(false),
8832
- enableCoordinatorMode: z.boolean().optional().prefault(false)
8832
+ enableCoordinatorMode: z.boolean().optional().prefault(false),
8833
+ /**
8834
+ * System-prompt variant. 'current' uses the elaborate behavioral-scaffolding
8835
+ * prompt; 'minimal' uses a pi-style short prompt. See apps/cli/src/core/prompts.ts.
8836
+ * Defaults to 'current' for backward compatibility; switch via /config or by
8837
+ * editing the config file directly.
8838
+ */
8839
+ promptVariant: z.enum(["current", "minimal"]).optional().prefault("current")
8833
8840
  }),
8834
8841
  tools: z.object({
8835
8842
  enabled: z.array(z.string()),
@@ -8862,7 +8869,8 @@ const ProjectConfigSchema = z.object({
8862
8869
  exportFormat: z.enum(["markdown", "json"]).optional(),
8863
8870
  enableSkillTool: z.boolean().optional(),
8864
8871
  enableDynamicAgentCreation: z.boolean().optional(),
8865
- enableCoordinatorMode: z.boolean().optional()
8872
+ enableCoordinatorMode: z.boolean().optional(),
8873
+ promptVariant: z.enum(["current", "minimal"]).optional()
8866
8874
  }).optional(),
8867
8875
  sandbox: PartialSandboxConfigSchema,
8868
8876
  additionalDirectories: z.array(z.string()).optional()
@@ -8912,7 +8920,8 @@ const DEFAULT_CONFIG = {
8912
8920
  maxIterations: 10,
8913
8921
  enableSkillTool: true,
8914
8922
  enableDynamicAgentCreation: false,
8915
- enableCoordinatorMode: false
8923
+ enableCoordinatorMode: false,
8924
+ promptVariant: "current"
8916
8925
  },
8917
8926
  tools: {
8918
8927
  enabled: [],
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as version, n as fetchLatestVersion, r as forceCheckForUpdate } from "../updateChecker-DZXWZfKF.mjs";
2
+ import { a as version, n as compareSemver, r as fetchLatestVersion } from "../updateChecker-Dn-Ri8zw.mjs";
3
3
  import { execSync } from "child_process";
4
4
  import { constants, existsSync, promises } from "fs";
5
5
  import { homedir } from "os";
@@ -25,29 +25,29 @@ async function handleDoctorCommand() {
25
25
  status: "fail",
26
26
  message: `${nodeVersion} (>= 18 required, please upgrade)`
27
27
  });
28
+ const currentVersion = version;
28
29
  const latestVersion = await fetchLatestVersion();
29
- if (latestVersion) results.push({
30
- name: "NPM registry",
31
- status: "pass",
32
- message: `Accessible (latest: v${latestVersion})`
33
- });
34
- else results.push({
30
+ if (latestVersion) {
31
+ results.push({
32
+ name: "NPM registry",
33
+ status: "pass",
34
+ message: `Accessible (latest: v${latestVersion})`
35
+ });
36
+ if (compareSemver(latestVersion, currentVersion) > 0) results.push({
37
+ name: "Version",
38
+ status: "warn",
39
+ message: `v${currentVersion} installed, v${latestVersion} available. Run: b4m update`
40
+ });
41
+ else results.push({
42
+ name: "Version",
43
+ status: "pass",
44
+ message: `v${currentVersion} (latest)`
45
+ });
46
+ } else results.push({
35
47
  name: "NPM registry",
36
48
  status: "fail",
37
49
  message: "Not accessible — check your internet connection"
38
50
  });
39
- const currentVersion = version;
40
- const updateResult = await forceCheckForUpdate(currentVersion);
41
- if (updateResult) if (updateResult.updateAvailable) results.push({
42
- name: "Version",
43
- status: "warn",
44
- message: `v${currentVersion} installed, v${updateResult.latestVersion} available. Run: b4m update`
45
- });
46
- else results.push({
47
- name: "Version",
48
- status: "pass",
49
- message: `v${currentVersion} (latest)`
50
- });
51
51
  try {
52
52
  const npmPrefix = execSync("npm config get prefix", {
53
53
  encoding: "utf-8",
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { A as getApiUrl, C as WebSocketLlmBackend, G as isReadOnlyTool, J as CheckpointStore, K as ReActAgent, M as PermissionManager, N as generateCliTools, S as FallbackLlmBackend, T as McpManager, U as buildCoreSystemPrompt, V as setWebSocketToolExecutor, X as SessionStore, _ as createSkillTool, b as WebSocketToolExecutor, c as createFindDefinitionTool, d as createCoordinateTaskTool, f as createBackgroundAgentTools, g as SubagentOrchestrator, h as AgentStore, k as loadContextFiles, l as createTodoStore, m as createAgentDelegateTool, p as BackgroundAgentManager, q as CustomCommandStore, s as createGetFileStructureTool, u as createWriteTodosTool, w as ServerLlmBackend, x as WebSocketConnectionManager, y as ApiClient } from "../tools-dIVxi-6-.mjs";
3
- import { n as logger, t as ConfigStore } from "../ConfigStore-Dt6utdSA.mjs";
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";
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";
@@ -193,7 +193,7 @@ async function handleHeadlessCommand(options) {
193
193
  ...mcpTools,
194
194
  ...cliTools
195
195
  ];
196
- const systemPrompt = buildCoreSystemPrompt({
196
+ const systemPrompt = buildSystemPrompt(config.preferences.promptVariant ?? "current", {
197
197
  contextContent: contextResult.mergedContent,
198
198
  agentStore,
199
199
  customCommands: customCommandStore.getAllCommands(),
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as ConfigStore } from "../ConfigStore-Dt6utdSA.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-Bu_plzvP.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 { i as version, r as forceCheckForUpdate } from "../updateChecker-DZXWZfKF.mjs";
2
+ import { a as version, i as forceCheckForUpdate } from "../updateChecker-Dn-Ri8zw.mjs";
3
3
  import { execSync } from "child_process";
4
4
  //#region src/commands/updateCommand.ts
5
5
  /**
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { n as useCliStore, t as selectActiveBackgroundAgents } from "./store-B7-LLvvx.mjs";
3
- import { $ as processFileReferences, A as getApiUrl, B as registerFeatureModuleTools, C as WebSocketLlmBackend, D as formatStep, E as substituteArguments, F as DEFAULT_AGENT_MODEL, G as isReadOnlyTool, H as OllamaBackend, I as DEFAULT_MAX_ITERATIONS, J as CheckpointStore, K as ReActAgent, L as DEFAULT_RETRY_CONFIG, M as PermissionManager, N as generateCliTools, O as extractCompactInstructions, P as ALWAYS_DENIED_FOR_AGENTS, Q as hasFileReferences, R as DEFAULT_THOROUGHNESS, S as FallbackLlmBackend, T as McpManager, U as buildCoreSystemPrompt, V as setWebSocketToolExecutor, W as buildSkillsPromptSection, X as SessionStore, Y as CommandHistoryStore, Z as OAuthClient, _ as createSkillTool, a as createDecisionStore, b as WebSocketToolExecutor, c as createFindDefinitionTool, d as createCoordinateTaskTool, et as searchCommands, f as createBackgroundAgentTools, g as SubagentOrchestrator, h as AgentStore, i as createDecisionLogTool, it as warmFileCache, j as getEnvironmentName, k as loadContextFiles, l as createTodoStore, m as createAgentDelegateTool, n as createBlockerTools, nt as formatFileSize, o as formatDecisionsOutput, p as BackgroundAgentManager, q as CustomCommandStore, r as formatBlockersOutput, rt as searchFiles, s as createGetFileStructureTool, t as createBlockerStore, tt as mergeCommands, u as createWriteTodosTool, v as parseAgentConfig, w as ServerLlmBackend, x as WebSocketConnectionManager, y as ApiClient, z as clearFeatureModuleTools } from "./tools-dIVxi-6-.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-Dt6utdSA.mjs";
5
- import { i as version, t as checkForUpdate } from "./updateChecker-DZXWZfKF.mjs";
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";
6
6
  import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
7
7
  import { Box, Static, Text, render, useApp, useInput } from "ink";
8
8
  import { execSync } from "child_process";
@@ -1274,6 +1274,138 @@ function UserQuestionPrompt({ payload, onResponse }) {
1274
1274
  })), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, isOnOther ? "Type your answer, Enter to submit, or arrow keys to go back" : isMulti ? "Press 1-" + options.length + ", Space to toggle, Enter to confirm" : "Press 1-" + options.length + ", or arrow keys + Enter")));
1275
1275
  }
1276
1276
  //#endregion
1277
+ //#region src/components/ReviewGatePrompt.tsx
1278
+ const ITEMS = [
1279
+ {
1280
+ label: "✓ Approve",
1281
+ decision: "approved",
1282
+ withNote: false
1283
+ },
1284
+ {
1285
+ label: "✓ Approve with note...",
1286
+ decision: "approved",
1287
+ withNote: true
1288
+ },
1289
+ {
1290
+ label: "✗ Reject",
1291
+ decision: "rejected",
1292
+ withNote: false
1293
+ },
1294
+ {
1295
+ label: "✗ Reject with note...",
1296
+ decision: "rejected",
1297
+ withNote: true
1298
+ }
1299
+ ];
1300
+ /**
1301
+ * Review gate prompt component.
1302
+ *
1303
+ * Pauses the agent and asks the user to explicitly approve or reject a
1304
+ * significant decision. An optional free-text note can be attached to either
1305
+ * decision via the dedicated "...with note..." actions.
1306
+ *
1307
+ * Keyboard:
1308
+ * - 1–4: shortcut for each action
1309
+ * - y: Approve (no note); n: Reject (no note)
1310
+ * - ↑/↓: navigate; Enter: confirm selection
1311
+ * - When a "with note..." action is selected, an inline text input appears.
1312
+ * Type the note and press Enter to submit. Use ↑/↓ to switch to a no-note
1313
+ * action without losing the typed note.
1314
+ */
1315
+ function ReviewGatePrompt({ description, options, recommendation, onResponse }) {
1316
+ const [selectedIndex, setSelectedIndex] = useState(0);
1317
+ const [note, setNote] = useState("");
1318
+ const [responded, setResponded] = useState(false);
1319
+ const submit = useCallback((item, noteText) => {
1320
+ if (responded) return;
1321
+ setResponded(true);
1322
+ const trimmed = noteText.trim();
1323
+ onResponse({
1324
+ decision: item.decision,
1325
+ note: trimmed.length > 0 ? trimmed : void 0
1326
+ });
1327
+ }, [responded, onResponse]);
1328
+ const selectedItem = ITEMS[selectedIndex];
1329
+ const noteMode = selectedItem.withNote;
1330
+ const handleConfirm = useCallback(() => {
1331
+ if (selectedItem.withNote && note.trim().length === 0) return;
1332
+ submit(selectedItem, note);
1333
+ }, [
1334
+ selectedItem,
1335
+ note,
1336
+ submit
1337
+ ]);
1338
+ useInput((input, key) => {
1339
+ if (responded) return;
1340
+ if (key.upArrow) {
1341
+ setSelectedIndex((i) => i > 0 ? i - 1 : ITEMS.length - 1);
1342
+ return;
1343
+ }
1344
+ if (key.downArrow) {
1345
+ setSelectedIndex((i) => i < ITEMS.length - 1 ? i + 1 : 0);
1346
+ return;
1347
+ }
1348
+ if (noteMode) return;
1349
+ const num = parseInt(input, 10);
1350
+ if (num >= 1 && num <= ITEMS.length) {
1351
+ const item = ITEMS[num - 1];
1352
+ setSelectedIndex(num - 1);
1353
+ if (!item.withNote) submit(item, "");
1354
+ return;
1355
+ }
1356
+ if (input.toLowerCase() === "y") {
1357
+ submit(ITEMS[0], "");
1358
+ return;
1359
+ }
1360
+ if (input.toLowerCase() === "n") {
1361
+ submit(ITEMS[2], "");
1362
+ return;
1363
+ }
1364
+ if (key.return) handleConfirm();
1365
+ }, { isActive: !responded });
1366
+ if (responded) return null;
1367
+ return /* @__PURE__ */ React.createElement(Box, {
1368
+ flexDirection: "column",
1369
+ borderStyle: "bold",
1370
+ borderColor: "magenta",
1371
+ padding: 1,
1372
+ marginY: 1
1373
+ }, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, {
1374
+ bold: true,
1375
+ color: "magenta"
1376
+ }, "🛑 Review Gate")), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, description)), recommendation && /* @__PURE__ */ React.createElement(Box, {
1377
+ marginTop: 1,
1378
+ flexDirection: "column"
1379
+ }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Recommendation:"), /* @__PURE__ */ React.createElement(Box, { paddingLeft: 2 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, recommendation))), options && options.length > 0 && /* @__PURE__ */ React.createElement(Box, {
1380
+ marginTop: 1,
1381
+ flexDirection: "column"
1382
+ }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Options:"), /* @__PURE__ */ React.createElement(Box, {
1383
+ paddingLeft: 2,
1384
+ flexDirection: "column"
1385
+ }, options.map((opt, idx) => /* @__PURE__ */ React.createElement(Text, {
1386
+ key: idx,
1387
+ dimColor: true
1388
+ }, "• ", opt)))), /* @__PURE__ */ React.createElement(Box, {
1389
+ marginTop: 1,
1390
+ flexDirection: "column"
1391
+ }, ITEMS.map((item, index) => {
1392
+ const isHighlighted = index === selectedIndex;
1393
+ const color = item.decision === "approved" ? "green" : "red";
1394
+ return /* @__PURE__ */ React.createElement(Box, { key: item.label }, /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, index + 1, "."), /* @__PURE__ */ React.createElement(Text, {
1395
+ color: isHighlighted ? color : void 0,
1396
+ bold: isHighlighted
1397
+ }, isHighlighted ? " ❯ " : " ", item.label));
1398
+ })), noteMode && /* @__PURE__ */ React.createElement(Box, {
1399
+ marginTop: 1,
1400
+ flexDirection: "column"
1401
+ }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Note:"), /* @__PURE__ */ React.createElement(Box, { paddingLeft: 2 }, /* @__PURE__ */ React.createElement(TextInput, {
1402
+ value: note,
1403
+ onChange: setNote,
1404
+ onSubmit: handleConfirm,
1405
+ 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")));
1407
+ }
1408
+ //#endregion
1277
1409
  //#region src/components/ConfigEditor.tsx
1278
1410
  /**
1279
1411
  * Max iterations options: 10, 20, 30, 40, 50, Infinite (null)
@@ -1886,7 +2018,7 @@ const MessageItem = React.memo(function MessageItem({ message }) {
1886
2018
  });
1887
2019
  //#endregion
1888
2020
  //#region src/components/App.tsx
1889
- function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPermissionResponse, onUserQuestionResponse, onImageDetected, commandHistory = [], commands = [], config, availableModels = [], onSaveConfig, prefillInput, onPrefillConsumed, mcpManager }) {
2021
+ function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPermissionResponse, onUserQuestionResponse, onReviewGateResponse, onImageDetected, commandHistory = [], commands = [], config, availableModels = [], onSaveConfig, prefillInput, onPrefillConsumed, mcpManager }) {
1890
2022
  const messages = useCliStore((state) => state.session?.messages || []);
1891
2023
  const pendingMessages = useCliStore((state) => state.pendingMessages);
1892
2024
  const sessionName = useCliStore((state) => state.session?.name || "New Session");
@@ -1896,6 +2028,7 @@ function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPe
1896
2028
  const isThinking = useCliStore((state) => state.isThinking);
1897
2029
  const permissionPrompt = useCliStore((state) => state.permissionPrompt);
1898
2030
  const userQuestionPrompt = useCliStore((state) => state.userQuestionPrompt);
2031
+ const reviewGatePrompt = useCliStore((state) => state.reviewGatePrompt);
1899
2032
  const showConfigEditor = useCliStore((state) => state.showConfigEditor);
1900
2033
  const setShowConfigEditor = useCliStore((state) => state.setShowConfigEditor);
1901
2034
  const showMcpViewer = useCliStore((state) => state.showMcpViewer);
@@ -1988,7 +2121,16 @@ function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPe
1988
2121
  }, /* @__PURE__ */ React.createElement(UserQuestionPrompt, {
1989
2122
  payload: userQuestionPrompt.payload,
1990
2123
  onResponse: (response) => onUserQuestionResponse(response, userQuestionPrompt.id)
1991
- })), !permissionPrompt && !userQuestionPrompt && /* @__PURE__ */ React.createElement(AgentThinking, null), /* @__PURE__ */ React.createElement(BackgroundAgentStatus, null), /* @__PURE__ */ React.createElement(CompletedGroupNotification, null), exitRequested && /* @__PURE__ */ React.createElement(Box, {
2124
+ })), reviewGatePrompt && /* @__PURE__ */ React.createElement(Box, {
2125
+ key: reviewGatePrompt.id,
2126
+ flexDirection: "column",
2127
+ paddingX: 1
2128
+ }, /* @__PURE__ */ React.createElement(ReviewGatePrompt, {
2129
+ description: reviewGatePrompt.description,
2130
+ options: reviewGatePrompt.options,
2131
+ recommendation: reviewGatePrompt.recommendation,
2132
+ onResponse: (response) => onReviewGateResponse(response, reviewGatePrompt.id)
2133
+ })), !permissionPrompt && !userQuestionPrompt && !reviewGatePrompt && /* @__PURE__ */ React.createElement(AgentThinking, null), /* @__PURE__ */ React.createElement(BackgroundAgentStatus, null), /* @__PURE__ */ React.createElement(CompletedGroupNotification, null), exitRequested && /* @__PURE__ */ React.createElement(Box, {
1992
2134
  paddingX: 1,
1993
2135
  marginBottom: 1
1994
2136
  }, /* @__PURE__ */ React.createElement(Text, {
@@ -2002,7 +2144,7 @@ function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPe
2002
2144
  onSubmit: handleSubmit,
2003
2145
  onBashCommand,
2004
2146
  onImageDetected,
2005
- disabled: isThinking || !!permissionPrompt || !!userQuestionPrompt,
2147
+ disabled: isThinking || !!permissionPrompt || !!userQuestionPrompt || !!reviewGatePrompt,
2006
2148
  history: commandHistory,
2007
2149
  commands,
2008
2150
  prefillInput,
@@ -2469,7 +2611,7 @@ function buildCompactionPrompt(messages, options = {}) {
2469
2611
  function createCompactedSession(originalSession, summary, preservedMessages) {
2470
2612
  const summaryMessage = {
2471
2613
  id: v4(),
2472
- role: "system",
2614
+ role: "user",
2473
2615
  content: `[Previous conversation summary]\n\n${summary}`,
2474
2616
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2475
2617
  };
@@ -5235,10 +5377,13 @@ function CliApp() {
5235
5377
  const imageStoreInitPromise = useRef(null);
5236
5378
  const decisionStoreRef = useRef(createDecisionStore());
5237
5379
  const blockerStoreRef = useRef(createBlockerStore());
5380
+ const reviewGateStoreRef = useRef(createReviewGateStore());
5238
5381
  const setStoreSession = useCliStore((state) => state.setSession);
5239
5382
  const enqueuePermissionPrompt = useCliStore((state) => state.enqueuePermissionPrompt);
5240
5383
  const enqueueUserQuestionPrompt = useCliStore((state) => state.enqueueUserQuestionPrompt);
5241
5384
  const dequeueUserQuestionPrompt = useCliStore((state) => state.dequeueUserQuestionPrompt);
5385
+ const enqueueReviewGatePrompt = useCliStore((state) => state.enqueueReviewGatePrompt);
5386
+ const dequeueReviewGatePrompt = useCliStore((state) => state.dequeueReviewGatePrompt);
5242
5387
  const setShowConfigEditor = useCliStore((state) => state.setShowConfigEditor);
5243
5388
  const setShowMcpViewer = useCliStore((state) => state.setShowMcpViewer);
5244
5389
  const setExitRequested = useCliStore((state) => state.setExitRequested);
@@ -5335,7 +5480,8 @@ function CliApp() {
5335
5480
  console.log("\n🔐 Welcome to B4M CLI! Authentication is required to get started.\n");
5336
5481
  setState((prev) => ({
5337
5482
  ...prev,
5338
- showLoginFlow: true
5483
+ showLoginFlow: true,
5484
+ config
5339
5485
  }));
5340
5486
  return;
5341
5487
  }
@@ -5345,7 +5491,8 @@ function CliApp() {
5345
5491
  await state.configStore.clearAuthTokens();
5346
5492
  setState((prev) => ({
5347
5493
  ...prev,
5348
- showLoginFlow: true
5494
+ showLoginFlow: true,
5495
+ config
5349
5496
  }));
5350
5497
  return;
5351
5498
  }
@@ -5626,6 +5773,22 @@ function CliApp() {
5626
5773
  });
5627
5774
  });
5628
5775
  };
5776
+ const reviewGateFn = (params) => {
5777
+ return new Promise((resolve) => {
5778
+ enqueueReviewGatePrompt({
5779
+ id: params.id,
5780
+ description: params.description,
5781
+ options: params.options,
5782
+ recommendation: params.recommendation,
5783
+ resolve
5784
+ });
5785
+ bridgePresence.emitEvent({
5786
+ type: "status",
5787
+ status: "awaiting_input",
5788
+ text: `Review gate: ${params.description}`.slice(0, 240)
5789
+ });
5790
+ });
5791
+ };
5629
5792
  const agentContext = {
5630
5793
  currentAgent: null,
5631
5794
  observationQueue: []
@@ -5679,12 +5842,15 @@ function CliApp() {
5679
5842
  const writeTodosTool = createWriteTodosTool(createTodoStore());
5680
5843
  const decisionLogTool = createDecisionLogTool(decisionStoreRef.current);
5681
5844
  const blockerTools = createBlockerTools(blockerStoreRef.current);
5845
+ const reviewGateTool = createReviewGateTool(reviewGateStoreRef.current, reviewGateFn);
5682
5846
  if (newSession.metadata.workflow) {
5683
5847
  decisionStoreRef.current.decisions = [...newSession.metadata.workflow.decisions];
5684
5848
  blockerStoreRef.current.blockers = [...newSession.metadata.workflow.blockers];
5849
+ reviewGateStoreRef.current.reviewGates = [...newSession.metadata.workflow.reviewGates ?? []];
5685
5850
  } else {
5686
5851
  decisionStoreRef.current.decisions = [];
5687
5852
  blockerStoreRef.current.blockers = [];
5853
+ reviewGateStoreRef.current.reviewGates = [];
5688
5854
  }
5689
5855
  const enableSkillTool = config.preferences.enableSkillTool !== false;
5690
5856
  const skillTool = enableSkillTool ? createSkillTool({
@@ -5705,6 +5871,7 @@ function CliApp() {
5705
5871
  writeTodosTool,
5706
5872
  decisionLogTool,
5707
5873
  ...blockerTools,
5874
+ reviewGateTool,
5708
5875
  findDefinitionTool,
5709
5876
  getFileStructureTool
5710
5877
  ];
@@ -5739,7 +5906,7 @@ function CliApp() {
5739
5906
  if (contextResult.projectContext) startupLog.push(`📄 Project context: ${contextResult.projectContext.filename}`);
5740
5907
  for (const error of contextResult.errors) startupLog.push(`⚠️ Context file error: ${error}`);
5741
5908
  const featureModulePrompts = featureRegistry.getSystemPromptSections();
5742
- const cliSystemPrompt = buildCoreSystemPrompt({
5909
+ const cliSystemPrompt = buildSystemPrompt(config.preferences.promptVariant ?? "current", {
5743
5910
  contextContent: contextResult.mergedContent,
5744
5911
  agentStore,
5745
5912
  customCommands: state.customCommandStore.getAllCommands(),
@@ -5978,6 +6145,14 @@ function CliApp() {
5978
6145
  });
5979
6146
  return;
5980
6147
  }
6148
+ if (s.reviewGatePrompt) {
6149
+ bridgePresence.emitEvent({
6150
+ type: "status",
6151
+ status: "awaiting_input",
6152
+ text: `Review gate: ${s.reviewGatePrompt.description}`.slice(0, 240)
6153
+ });
6154
+ return;
6155
+ }
5981
6156
  const abort = abortControllerRef.current;
5982
6157
  if (!abort || abort.signal.aborted) return;
5983
6158
  bridgePresence.emitEvent({
@@ -6135,11 +6310,11 @@ function CliApp() {
6135
6310
  totalTokens: currentSession.metadata.totalTokens + result.completionInfo.totalTokens,
6136
6311
  totalCredits: (currentSession.metadata.totalCredits || 0) + (result.completionInfo.totalCredits || 0),
6137
6312
  toolCallCount: currentSession.metadata.toolCallCount + successfulToolCalls,
6138
- workflow: decisionStoreRef.current.decisions.length > 0 || blockerStoreRef.current.blockers.length > 0 ? {
6313
+ workflow: decisionStoreRef.current.decisions.length > 0 || blockerStoreRef.current.blockers.length > 0 || reviewGateStoreRef.current.reviewGates.length > 0 ? {
6139
6314
  decisions: decisionStoreRef.current.decisions,
6140
6315
  blockers: blockerStoreRef.current.blockers,
6141
6316
  handoff: currentSession.metadata.workflow?.handoff,
6142
- reviewGates: currentSession.metadata.workflow?.reviewGates
6317
+ reviewGates: reviewGateStoreRef.current.reviewGates
6143
6318
  } : currentSession.metadata.workflow
6144
6319
  }
6145
6320
  };
@@ -6253,7 +6428,7 @@ function CliApp() {
6253
6428
  if (config?.preferences.autoCompact !== false && activeSession.messages.length >= 6) {
6254
6429
  const tokenCounter = getTokenCounter();
6255
6430
  const threshold = tokenCounter.getContextWindow(activeSession.model, state.availableModels) * .8;
6256
- const systemPrompt = buildCoreSystemPrompt({
6431
+ const systemPrompt = buildSystemPrompt(config?.preferences.promptVariant ?? "current", {
6257
6432
  contextContent: state.contextContent,
6258
6433
  agentStore: state.agentStore || void 0,
6259
6434
  customCommands: state.customCommandStore.getAllCommands(),
@@ -6651,7 +6826,7 @@ function CliApp() {
6651
6826
  decisions: decisionStoreRef.current.decisions,
6652
6827
  blockers: blockerStoreRef.current.blockers,
6653
6828
  handoff,
6654
- reviewGates: session.metadata.workflow?.reviewGates
6829
+ reviewGates: reviewGateStoreRef.current.reviewGates
6655
6830
  };
6656
6831
  return handoff;
6657
6832
  } catch (err) {
@@ -6796,11 +6971,11 @@ Multi-line Input:
6796
6971
  }
6797
6972
  const sessionName = args.join(" ") || state.session.name;
6798
6973
  state.session.name = sessionName;
6799
- if (decisionStoreRef.current.decisions.length > 0 || blockerStoreRef.current.blockers.length > 0) state.session.metadata.workflow = {
6974
+ if (decisionStoreRef.current.decisions.length > 0 || blockerStoreRef.current.blockers.length > 0 || reviewGateStoreRef.current.reviewGates.length > 0) state.session.metadata.workflow = {
6800
6975
  decisions: decisionStoreRef.current.decisions,
6801
6976
  blockers: blockerStoreRef.current.blockers,
6802
6977
  handoff: state.session.metadata.workflow?.handoff,
6803
- reviewGates: state.session.metadata.workflow?.reviewGates
6978
+ reviewGates: reviewGateStoreRef.current.reviewGates
6804
6979
  };
6805
6980
  const handoff = await generateHandoff(state.session);
6806
6981
  await state.sessionStore.save(state.session);
@@ -7075,6 +7250,7 @@ Multi-line Input:
7075
7250
  logger.debug("=== New Session Started via /clear ===");
7076
7251
  decisionStoreRef.current.decisions = [];
7077
7252
  blockerStoreRef.current.blockers = [];
7253
+ reviewGateStoreRef.current.reviewGates = [];
7078
7254
  if (state.checkpointStore) state.checkpointStore.setSessionId(newSession.id);
7079
7255
  setState((prev) => ({
7080
7256
  ...prev,
@@ -7307,7 +7483,8 @@ Multi-line Input:
7307
7483
  }
7308
7484
  const tokenCounter = getTokenCounter();
7309
7485
  const contextWindow = tokenCounter.getContextWindow(state.session.model, state.availableModels);
7310
- const corePromptTokens = tokenCounter.countTokens(buildCoreSystemPrompt());
7486
+ const variantForCount = state.config?.preferences.promptVariant ?? "current";
7487
+ const corePromptTokens = tokenCounter.countTokens(buildSystemPrompt(variantForCount));
7311
7488
  const projectContextTokens = state.contextContent ? tokenCounter.countTokens(state.contextContent) : 0;
7312
7489
  const commands = state.customCommandStore.getAllCommands();
7313
7490
  const skillsSection = buildSkillsPromptSection(commands);
@@ -7316,7 +7493,7 @@ Multi-line Input:
7316
7493
  const mcpTools = state.mcpManager?.getTools() || [];
7317
7494
  const mcpToolsTokens = tokenCounter.countToolSchemaTokens(mcpTools);
7318
7495
  const mcpToolCount = state.mcpManager?.getToolCount() || [];
7319
- const systemPrompt = buildCoreSystemPrompt({
7496
+ const systemPrompt = buildSystemPrompt(variantForCount, {
7320
7497
  contextContent: state.contextContent,
7321
7498
  agentStore: state.agentStore || void 0,
7322
7499
  customCommands: commands,
@@ -7776,6 +7953,11 @@ Multi-line Input:
7776
7953
  console.log(formatBlockersOutput(blockerStoreRef.current.blockers));
7777
7954
  console.log("");
7778
7955
  break;
7956
+ case "review-gates":
7957
+ console.log("\n🛑 Review Gates\n");
7958
+ console.log(formatReviewGatesOutput(reviewGateStoreRef.current.reviewGates));
7959
+ console.log("");
7960
+ break;
7779
7961
  case "handoff": {
7780
7962
  if (!state.session) {
7781
7963
  console.log("No active session");
@@ -7889,7 +8071,7 @@ Multi-line Input:
7889
8071
  const newFeatureTools = newFeatureRegistry.getAllTools();
7890
8072
  agentContext.tools = [...baseTools, ...newFeatureTools];
7891
8073
  const newFeaturePrompts = newFeatureRegistry.getSystemPromptSections();
7892
- agentContext.systemPrompt = buildCoreSystemPrompt({
8074
+ agentContext.systemPrompt = buildSystemPrompt(updatedConfig.preferences.promptVariant ?? "current", {
7893
8075
  contextContent: state.contextContent,
7894
8076
  agentStore: state.agentStore || void 0,
7895
8077
  customCommands: state.customCommandStore.getAllCommands(),
@@ -8038,6 +8220,13 @@ Multi-line Input:
8038
8220
  currentPrompt.resolve(response);
8039
8221
  dequeueUserQuestionPrompt();
8040
8222
  emitNextAwaitingStatus();
8223
+ },
8224
+ onReviewGateResponse: (response, promptId) => {
8225
+ const currentPrompt = useCliStore.getState().reviewGatePrompt;
8226
+ if (currentPrompt?.id !== promptId) return;
8227
+ currentPrompt.resolve(response);
8228
+ dequeueReviewGatePrompt();
8229
+ emitNextAwaitingStatus();
8041
8230
  }
8042
8231
  });
8043
8232
  }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { n as useCliStore } from "./store-FkY4K-0M.mjs";
3
+ export { useCliStore };
@@ -105,6 +105,19 @@ const useCliStore = create((set) => ({
105
105
  userQuestionQueue: rest
106
106
  };
107
107
  }),
108
+ reviewGatePrompt: null,
109
+ reviewGateQueue: [],
110
+ enqueueReviewGatePrompt: (prompt) => set((state) => {
111
+ if (!state.reviewGatePrompt) return { reviewGatePrompt: prompt };
112
+ return { reviewGateQueue: [...state.reviewGateQueue, prompt] };
113
+ }),
114
+ dequeueReviewGatePrompt: () => set((state) => {
115
+ const [next, ...rest] = state.reviewGateQueue;
116
+ return {
117
+ reviewGatePrompt: next ?? null,
118
+ reviewGateQueue: rest
119
+ };
120
+ }),
108
121
  showConfigEditor: false,
109
122
  setShowConfigEditor: (show) => set({ showConfigEditor: show }),
110
123
  showMcpViewer: false,
@@ -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-Dt6utdSA.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-Bu_plzvP.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";
@@ -1906,11 +1906,17 @@ var ReActAgent = class extends EventEmitter {
1906
1906
  iterations = 0;
1907
1907
  /** Whether runIteration() has been initialized (messages built, state reset) */
1908
1908
  iterationInitialized = false;
1909
+ /**
1910
+ * Length of the initial messages array (system + previousMessages + user query)
1911
+ * before any ReAct iteration messages are appended. Used by trimConversationHistory
1912
+ * to protect the conversation prefix from being mistaken for iteration nudges.
1913
+ */
1914
+ initialMessageCount = 0;
1909
1915
  constructor(context) {
1910
1916
  super();
1911
1917
  this.context = {
1912
1918
  ...context,
1913
- maxIterations: context.maxIterations ?? 5,
1919
+ maxIterations: context.maxIterations ?? 50,
1914
1920
  maxTokens: context.maxTokens ?? 4096,
1915
1921
  temperature: context.temperature ?? .7
1916
1922
  };
@@ -1933,11 +1939,13 @@ var ReActAgent = class extends EventEmitter {
1933
1939
  this.toolCallCount = 0;
1934
1940
  this.confidenceLog = [];
1935
1941
  this.iterationConfidences = [];
1936
- const maxIterations = options.maxIterations ?? this.context.maxIterations ?? 5;
1942
+ const maxIterations = options.maxIterations ?? this.context.maxIterations ?? 50;
1937
1943
  const temperature = options.temperature ?? this.context.temperature ?? .7;
1938
1944
  const maxTokens = options.maxTokens ?? this.context.maxTokens ?? 4096;
1945
+ const maxTotalTokens = options.maxTotalTokens ?? this.context.maxTotalTokens;
1939
1946
  const maxHistoryIterations = options.maxHistoryIterations ?? 4;
1940
1947
  let iterations = 0;
1948
+ let reachedMaxTotalTokens = false;
1941
1949
  try {
1942
1950
  const messages = [
1943
1951
  {
@@ -1951,6 +1959,7 @@ var ReActAgent = class extends EventEmitter {
1951
1959
  }
1952
1960
  ];
1953
1961
  this.messages = messages;
1962
+ this.initialMessageCount = messages.length;
1954
1963
  messages.forEach((msg, i) => {
1955
1964
  const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
1956
1965
  this.context.logger.debug(` [${i}] ${msg.role}: ${content.substring(0, 150)}...`);
@@ -1987,7 +1996,7 @@ var ReActAgent = class extends EventEmitter {
1987
1996
  const processedToolIds = /* @__PURE__ */ new Set();
1988
1997
  let hadToolCalls = false;
1989
1998
  if (maxHistoryIterations > 0 && iterations > 1) {
1990
- trimConversationHistory(messages, maxHistoryIterations);
1999
+ trimConversationHistory(messages, maxHistoryIterations, this.initialMessageCount);
1991
2000
  trimSteps(this.steps, maxHistoryIterations);
1992
2001
  }
1993
2002
  const cacheStrategy = options.enableCaching ? {
@@ -2119,6 +2128,18 @@ var ReActAgent = class extends EventEmitter {
2119
2128
  role: "user",
2120
2129
  content: `Based on the tool results above, please provide a complete answer. If I asked for multiple things, make sure to address all of them.`
2121
2130
  });
2131
+ if (maxTotalTokens !== void 0 && this.totalTokens >= maxTotalTokens && !iterationComplete) {
2132
+ reachedMaxTotalTokens = true;
2133
+ finalAnswer = currentText.trim() || `I stopped after reaching the cumulative token ceiling (${this.totalTokens}/${maxTotalTokens}) without arriving at a final answer.`;
2134
+ const finalStep = {
2135
+ type: "final_answer",
2136
+ content: finalAnswer,
2137
+ metadata: { timestamp: Date.now() }
2138
+ };
2139
+ this.steps.push(finalStep);
2140
+ this.emit("final_answer", finalStep);
2141
+ break;
2142
+ }
2122
2143
  if (iterations >= maxIterations) {
2123
2144
  reachedMaxIterations = true;
2124
2145
  finalAnswer = currentText.trim() || `I reached the maximum number of iterations (${iterations}/${maxIterations}) without arriving at a final answer.`;
@@ -2147,6 +2168,7 @@ var ReActAgent = class extends EventEmitter {
2147
2168
  iterations,
2148
2169
  toolCalls: this.toolCallCount,
2149
2170
  reachedMaxIterations,
2171
+ reachedMaxTotalTokens: reachedMaxTotalTokens || void 0,
2150
2172
  averageConfidence: avgConfidence,
2151
2173
  minConfidence,
2152
2174
  confidenceLog: this.confidenceLog.length > 0 ? this.confidenceLog : void 0
@@ -2265,7 +2287,8 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
2265
2287
  totalCredits: this.totalCredits,
2266
2288
  toolCallCount: this.toolCallCount,
2267
2289
  confidenceLog: structuredClone(this.confidenceLog),
2268
- iterationConfidences: [...this.iterationConfidences]
2290
+ iterationConfidences: [...this.iterationConfidences],
2291
+ initialMessageCount: this.initialMessageCount
2269
2292
  };
2270
2293
  }
2271
2294
  /**
@@ -2290,6 +2313,7 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
2290
2313
  this.toolCallCount = checkpoint.toolCallCount;
2291
2314
  this.confidenceLog = structuredClone(checkpoint.confidenceLog);
2292
2315
  this.iterationConfidences = [...checkpoint.iterationConfidences];
2316
+ this.initialMessageCount = checkpoint.initialMessageCount ?? this.messages.length;
2293
2317
  this.observationQueue = [];
2294
2318
  this.iterationInitialized = true;
2295
2319
  }
@@ -2325,9 +2349,10 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
2325
2349
  * regressing the battle-tested run() path in this PR.
2326
2350
  */
2327
2351
  async runIteration(query, options = {}) {
2328
- const maxIterations = options.maxIterations ?? this.context.maxIterations ?? 5;
2352
+ const maxIterations = options.maxIterations ?? this.context.maxIterations ?? 50;
2329
2353
  const temperature = options.temperature ?? this.context.temperature ?? .7;
2330
2354
  const maxTokens = options.maxTokens ?? this.context.maxTokens ?? 4096;
2355
+ const maxTotalTokens = options.maxTotalTokens ?? this.context.maxTotalTokens;
2331
2356
  if (!this.iterationInitialized) {
2332
2357
  if (!query) throw new Error("query is required on the first call to runIteration(). Pass the user query, or call fromCheckpoint() first to resume.");
2333
2358
  this.steps = [];
@@ -2352,6 +2377,7 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
2352
2377
  content: query
2353
2378
  }
2354
2379
  ];
2380
+ this.initialMessageCount = this.messages.length;
2355
2381
  this.iterationInitialized = true;
2356
2382
  }
2357
2383
  if (this.iterations >= maxIterations) {
@@ -2393,7 +2419,7 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
2393
2419
  this.context.logger.debug(`[ReActAgent] Starting iteration ${this.iterations}/${maxIterations}`);
2394
2420
  const maxHistoryIterations = options.maxHistoryIterations ?? 4;
2395
2421
  if (maxHistoryIterations > 0 && this.iterations > 1) {
2396
- trimConversationHistory(this.messages, maxHistoryIterations);
2422
+ trimConversationHistory(this.messages, maxHistoryIterations, this.initialMessageCount);
2397
2423
  trimSteps(this.steps, maxHistoryIterations);
2398
2424
  }
2399
2425
  let iterationComplete = false;
@@ -2540,6 +2566,25 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
2540
2566
  role: "user",
2541
2567
  content: "Based on the tool results above, please provide a complete answer. If I asked for multiple things, make sure to address all of them."
2542
2568
  });
2569
+ if (!iterationComplete && maxTotalTokens !== void 0 && this.totalTokens >= maxTotalTokens) {
2570
+ finalAnswer = currentText.trim() || `I stopped after reaching the cumulative token ceiling (${this.totalTokens}/${maxTotalTokens}) without arriving at a final answer.`;
2571
+ const ceilingStep = {
2572
+ type: "final_answer",
2573
+ content: finalAnswer,
2574
+ metadata: { timestamp: Date.now() }
2575
+ };
2576
+ this.steps.push(ceilingStep);
2577
+ iterationSteps.push(ceilingStep);
2578
+ this.emit("final_answer", ceilingStep);
2579
+ return {
2580
+ step: ceilingStep,
2581
+ allSteps: iterationSteps,
2582
+ isComplete: true,
2583
+ reachedMaxIterations: false,
2584
+ reachedMaxTotalTokens: true,
2585
+ checkpoint: this.toCheckpoint()
2586
+ };
2587
+ }
2543
2588
  if (!iterationComplete && this.iterations >= maxIterations) {
2544
2589
  finalAnswer = currentText.trim() || `I reached the maximum number of iterations (${this.iterations}/${maxIterations}) without arriving at a final answer.`;
2545
2590
  const maxStep = {
@@ -2671,10 +2716,19 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
2671
2716
  return .7;
2672
2717
  }
2673
2718
  /**
2674
- * Parse tool arguments, handling both string and object forms
2719
+ * Parse tool arguments, handling both string and object forms.
2720
+ *
2721
+ * Recovers from common malformed-JSON patterns models emit before failing:
2722
+ * - Trailing commas before `]` or `}` (frequent across providers)
2723
+ * - Markdown code fences wrapping the JSON (`` ```json ... ``` ``)
2724
+ *
2725
+ * If recovery succeeds, logs at debug level so provider-specific patterns
2726
+ * can be tracked. If recovery fails, re-throws the original parse error
2727
+ * so the failure mode stays visible.
2675
2728
  */
2676
2729
  parseToolArguments(args) {
2677
- return typeof args === "string" ? JSON.parse(args) : args;
2730
+ if (typeof args !== "string") return args;
2731
+ return parseToolArgsLenient(args, this.context.logger);
2678
2732
  }
2679
2733
  /**
2680
2734
  * Execute a tool and return the result as a string.
@@ -2715,6 +2769,64 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
2715
2769
  }
2716
2770
  };
2717
2771
  /**
2772
+ * Parse JSON tool arguments with recovery for common LLM mistakes.
2773
+ *
2774
+ * Strategy: try standard parse first (the happy path, no allocation). On
2775
+ * failure, attempt targeted fixes one at a time and retry. Each recovery
2776
+ * step is conservative — it only handles patterns that have a low risk of
2777
+ * silently corrupting valid input.
2778
+ *
2779
+ * Exported for direct use and unit testing.
2780
+ */
2781
+ function parseToolArgsLenient(args, logger) {
2782
+ try {
2783
+ return JSON.parse(args);
2784
+ } catch (originalError) {
2785
+ const recoveries = [
2786
+ {
2787
+ name: "strip-code-fence",
2788
+ transform: stripCodeFence
2789
+ },
2790
+ {
2791
+ name: "strip-trailing-commas",
2792
+ transform: stripTrailingCommas
2793
+ },
2794
+ {
2795
+ name: "strip-code-fence+strip-trailing-commas",
2796
+ transform: (s) => stripTrailingCommas(stripCodeFence(s))
2797
+ }
2798
+ ];
2799
+ for (const { name, transform } of recoveries) {
2800
+ const candidate = transform(args);
2801
+ if (candidate === args) continue;
2802
+ try {
2803
+ const result = JSON.parse(candidate);
2804
+ logger?.debug?.(`[ReActAgent] Recovered malformed tool args via ${name}`);
2805
+ return result;
2806
+ } catch {}
2807
+ }
2808
+ throw originalError;
2809
+ }
2810
+ }
2811
+ /**
2812
+ * Remove a wrapping markdown code fence, e.g. `` ```json\n{...}\n``` ``.
2813
+ * Preserves input if no recognizable fence is found.
2814
+ */
2815
+ function stripCodeFence(input) {
2816
+ const fenceMatch = input.trim().match(/^```(?:json|JSON)?\s*\n?([\s\S]*?)\n?```$/);
2817
+ return fenceMatch ? fenceMatch[1].trim() : input;
2818
+ }
2819
+ /**
2820
+ * Strip commas that immediately precede `}` or `]` (with optional whitespace).
2821
+ * Conservative: doesn't touch commas inside strings because the regex only
2822
+ * matches commas followed by whitespace and a closing bracket.
2823
+ *
2824
+ * Preserves input if no trailing-comma pattern is found.
2825
+ */
2826
+ function stripTrailingCommas(input) {
2827
+ return input.replace(/,(\s*[}\]])/g, "$1");
2828
+ }
2829
+ /**
2718
2830
  * Trim the steps array to keep only the last N iterations worth of steps.
2719
2831
  *
2720
2832
  * Each iteration produces 1-5 steps (thought, action(s), observation(s), final_answer).
@@ -2731,27 +2843,27 @@ function trimSteps(steps, maxIterations) {
2731
2843
  }
2732
2844
  }
2733
2845
  /**
2734
- * Trim conversation history to keep only the initial messages (system + user query)
2735
- * plus the last N iterations of assistant/tool/user messages.
2846
+ * Trim conversation history to keep the protected prefix (system + previousMessages
2847
+ * + current user query) plus the last N iterations of assistant/tool/user messages.
2736
2848
  *
2737
2849
  * Each ReAct iteration adds ~3-5 messages (assistant with tool calls, tool results,
2738
2850
  * user nudge). Keeping all iterations causes input tokens to grow quadratically.
2739
2851
  * Trimming to the last N iterations keeps the agent grounded in recent context
2740
2852
  * while dramatically reducing token accumulation.
2853
+ *
2854
+ * @param protectedPrefixCount - Length of the initial messages array before any
2855
+ * ReAct iteration messages were appended. Required when previousMessages is
2856
+ * used, otherwise prior-turn user messages get mistaken for iteration nudges
2857
+ * and trimmed away (including, eventually, the current user query itself).
2741
2858
  */
2742
- function trimConversationHistory(messages, maxIterations) {
2743
- let dynamicStart = 0;
2744
- for (let i = 0; i < messages.length; i++) if (messages[i].role === "user") {
2745
- dynamicStart = i + 1;
2746
- break;
2747
- }
2859
+ function trimConversationHistory(messages, maxIterations, protectedPrefixCount) {
2860
+ const dynamicStart = protectedPrefixCount;
2748
2861
  const dynamicMessages = messages.slice(dynamicStart);
2749
2862
  if (dynamicMessages.length === 0) return;
2750
2863
  const nudgeIndices = [];
2751
2864
  for (let i = 0; i < dynamicMessages.length; i++) if (dynamicMessages[i].role === "user" && typeof dynamicMessages[i].content === "string") nudgeIndices.push(i);
2752
2865
  if (nudgeIndices.length <= maxIterations) return;
2753
- const cutoffNudgeIndex = nudgeIndices[nudgeIndices.length - maxIterations];
2754
- const keepFrom = dynamicStart + cutoffNudgeIndex;
2866
+ const keepFrom = dynamicStart + nudgeIndices[nudgeIndices.length - maxIterations];
2755
2867
  messages.splice(dynamicStart, keepFrom - dynamicStart);
2756
2868
  }
2757
2869
  //#endregion
@@ -2906,7 +3018,7 @@ const TOOL_FILE_READ = "file_read";
2906
3018
  const TOOL_EDIT_LOCAL_FILE = "edit_local_file";
2907
3019
  const TOOL_CREATE_FILE = "create_file";
2908
3020
  const TOOL_BASH_EXECUTE = "bash_execute";
2909
- const TOOL_SUBAGENT_DELEGATE = "subagent_delegate";
3021
+ const TOOL_SUBAGENT_DELEGATE = "agent_delegate";
2910
3022
  const TOOL_WRITE_TODOS = "write_todos";
2911
3023
  const TOOL_CREATE_DYNAMIC_AGENT = "create_dynamic_agent";
2912
3024
  const EXPLORE_SUBAGENT_TYPE = "explore";
@@ -3025,12 +3137,12 @@ EXAMPLES:
3025
3137
  - "how about Japan?" → use weather tool for Japan (applying same question from context)
3026
3138
  - "enhance README" → ${TOOL_FILE_READ} → generate → ${TOOL_EDIT_LOCAL_FILE}
3027
3139
  - "what packages installed?" → ${TOOL_GLOB_FILES} "**/package.json" → ${TOOL_FILE_READ}
3028
- - "find all components using React hooks" → ${TOOL_SUBAGENT_DELEGATE}(task="find all components using React hooks", type="explore")
3140
+ - "find all components using React hooks" → ${TOOL_SUBAGENT_DELEGATE}(task="find all components using React hooks", agent="explore")
3029
3141
 
3030
3142
  Remember: Use context from previous messages to understand follow-up questions.
3031
3143
 
3032
3144
  DURABLE WORKFLOW TRACKING:
3033
- You have tools for tracking decisions and blockers during your work. These create an audit trail that persists across sessions, enabling anyone to understand why things were done and what's still outstanding.
3145
+ You have tools for tracking decisions, blockers, and human review gates during your work. These create an audit trail that persists across sessions, enabling anyone to understand why things were done and what's still outstanding.
3034
3146
 
3035
3147
  - log_decision: When you make a significant decision (architecture choice, scope narrowing, interpretation of ambiguous requirements, trade-off between alternatives), log it with rationale. Do NOT log trivial decisions. Log decisions that would matter if someone needed to understand WHY you did something or if they needed to resume this work.
3036
3148
 
@@ -3038,9 +3150,40 @@ You have tools for tracking decisions and blockers during your work. These creat
3038
3150
 
3039
3151
  - resolve_blocker: When a blocker is cleared, record how it was resolved. Use the blocker ID from the track_blocker output.
3040
3152
 
3153
+ - 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
+
3041
3155
  These tools are lightweight — use them naturally as part of your work, not as a ceremony.${directoriesSection}${projectContextSection}${skillsSection}${featureModulesSection}`;
3042
3156
  }
3043
3157
  /**
3158
+ * Build the minimal-variant system prompt. Reuses the project context,
3159
+ * skills, additional-directories, and feature-module sections from
3160
+ * `buildCoreSystemPrompt` — those carry user-specific information the
3161
+ * model needs and are independent of the behavioral scaffolding.
3162
+ */
3163
+ function buildMinimalSystemPrompt(config = {}) {
3164
+ let projectContextSection = "";
3165
+ let skillsSection = "";
3166
+ let directoriesSection = "";
3167
+ let featureModulesSection = "";
3168
+ if (config.contextContent) projectContextSection = `\n\n## Project Context\n\nFollow these project-specific instructions:\n\n${config.contextContent}`;
3169
+ if (config.enableSkillTool !== false && config.customCommands && config.customCommands.length > 0) skillsSection = buildSkillsPromptSection(config.customCommands);
3170
+ 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
+ if (config.featureModulePrompts) featureModulesSection = config.featureModulePrompts;
3172
+ 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
+
3174
+ Guidelines:
3175
+ - Be concise in your responses.
3176
+ - 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}`;
3178
+ }
3179
+ /**
3180
+ * Pick a system prompt by variant. The dispatch point for the
3181
+ * production CLI's `promptVariant` preference flag.
3182
+ */
3183
+ function buildSystemPrompt(variant, config = {}) {
3184
+ return variant === "minimal" ? buildMinimalSystemPrompt(config) : buildCoreSystemPrompt(config);
3185
+ }
3186
+ /**
3044
3187
  * Build the dynamic agent creation prompt section
3045
3188
  * Injected into the system prompt when enableDynamicAgentCreation is true
3046
3189
  */
@@ -18828,7 +18971,7 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
18828
18971
  return result;
18829
18972
  }
18830
18973
  if (!permissionManager.needsPermission(toolName, { isSandboxed })) return executeAndRecord();
18831
- const { useCliStore } = await import("./store-B_ILRSdP.mjs");
18974
+ const { useCliStore } = await import("./store-D2zh6DFz.mjs");
18832
18975
  if (useCliStore.getState().autoAcceptEdits) return executeAndRecord();
18833
18976
  const response = await showPermissionPrompt(toolName, effectiveArgs, await generateToolPreview(toolName, args, isSandboxed));
18834
18977
  if (response.action === "deny") throw new PermissionDeniedError(toolName, args);
@@ -21995,7 +22138,7 @@ Describe the expected output format here.
21995
22138
  */
21996
22139
  getDirectoryContext() {
21997
22140
  if (this.agents.size === 0) return "No sub-agents are currently available.";
21998
- let context = "Use `subagent_delegate` for complex tasks requiring specialized analysis.\n";
22141
+ let context = "Use `agent_delegate` for complex tasks requiring specialized analysis.\n";
21999
22142
  for (const [name, def] of this.agents) context += ` - **${name}**: ${def.description}\n`;
22000
22143
  return context;
22001
22144
  }
@@ -23620,7 +23763,7 @@ function createGetFileStructureTool() {
23620
23763
  * Validate log_decision parameters
23621
23764
  * @throws Error if validation fails
23622
23765
  */
23623
- function validateParams(args) {
23766
+ function validateParams$1(args) {
23624
23767
  const params = args;
23625
23768
  if (typeof params.summary !== "string" || params.summary.trim() === "") throw new Error("log_decision: summary must be a non-empty string");
23626
23769
  if (typeof params.rationale !== "string" || params.rationale.trim() === "") throw new Error("log_decision: rationale must be a non-empty string");
@@ -23660,7 +23803,7 @@ function formatDecisionsOutput(decisions) {
23660
23803
  function createDecisionLogTool(store) {
23661
23804
  return {
23662
23805
  toolFn: async (args) => {
23663
- const params = validateParams(args);
23806
+ const params = validateParams$1(args);
23664
23807
  const decision = {
23665
23808
  id: v4(),
23666
23809
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -23845,4 +23988,127 @@ function createBlockerStore(onUpdate) {
23845
23988
  };
23846
23989
  }
23847
23990
  //#endregion
23848
- export { processFileReferences as $, getApiUrl as A, registerFeatureModuleTools as B, WebSocketLlmBackend as C, formatStep as D, substituteArguments as E, DEFAULT_AGENT_MODEL as F, isReadOnlyTool as G, OllamaBackend as H, DEFAULT_MAX_ITERATIONS as I, CheckpointStore as J, ReActAgent as K, DEFAULT_RETRY_CONFIG as L, PermissionManager as M, generateCliTools as N, extractCompactInstructions as O, ALWAYS_DENIED_FOR_AGENTS as P, hasFileReferences as Q, DEFAULT_THOROUGHNESS as R, FallbackLlmBackend as S, McpManager as T, buildCoreSystemPrompt as U, setWebSocketToolExecutor as V, buildSkillsPromptSection as W, SessionStore as X, CommandHistoryStore as Y, OAuthClient as Z, createSkillTool as _, createDecisionStore as a, WebSocketToolExecutor as b, createFindDefinitionTool as c, createCoordinateTaskTool as d, searchCommands as et, createBackgroundAgentTools as f, SubagentOrchestrator as g, AgentStore as h, createDecisionLogTool as i, warmFileCache as it, getEnvironmentName as j, loadContextFiles as k, createTodoStore as l, createAgentDelegateTool as m, createBlockerTools as n, formatFileSize$1 as nt, formatDecisionsOutput as o, BackgroundAgentManager as p, CustomCommandStore as q, formatBlockersOutput as r, searchFiles as rt, createGetFileStructureTool as s, createBlockerStore as t, mergeCommands as tt, createWriteTodosTool as u, parseAgentConfig as v, ServerLlmBackend as w, WebSocketConnectionManager as x, ApiClient as y, clearFeatureModuleTools as z };
23991
+ //#region src/tools/reviewGateTool.ts
23992
+ /**
23993
+ * Validate request_review_gate parameters
23994
+ * @throws Error if validation fails
23995
+ */
23996
+ function validateParams(args) {
23997
+ const params = args;
23998
+ if (typeof params.description !== "string" || params.description.trim() === "") throw new Error("request_review_gate: description must be a non-empty string");
23999
+ if (params.options !== void 0) {
24000
+ 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");
24002
+ }
24003
+ if (params.recommendation !== void 0 && typeof params.recommendation !== "string") throw new Error("request_review_gate: recommendation must be a string");
24004
+ const options = params.options?.map((o) => o.trim()).filter((o) => o.length > 0);
24005
+ return {
24006
+ description: params.description.trim(),
24007
+ options: options && options.length > 0 ? options : void 0,
24008
+ recommendation: typeof params.recommendation === "string" ? params.recommendation.trim() : void 0
24009
+ };
24010
+ }
24011
+ /**
24012
+ * Format review gates for display output
24013
+ */
24014
+ function formatReviewGatesOutput(gates) {
24015
+ if (gates.length === 0) return "No review gates recorded in this session.";
24016
+ return gates.map((gate, index) => {
24017
+ const lines = [`${index + 1}. **${gate.description}**`, ` Status: ${gate.status}`];
24018
+ if (gate.userNote) lines.push(` Note: ${gate.userNote}`);
24019
+ const requested = new Date(gate.timestamp).toLocaleTimeString();
24020
+ lines.push(` Requested at: ${requested}`);
24021
+ if (gate.resolvedAt) {
24022
+ const resolved = new Date(gate.resolvedAt).toLocaleTimeString();
24023
+ lines.push(` Resolved at: ${resolved}`);
24024
+ }
24025
+ return lines.join("\n");
24026
+ }).join("\n\n");
24027
+ }
24028
+ /**
24029
+ * Create the request_review_gate tool.
24030
+ *
24031
+ * Pauses agent execution and prompts the user for explicit approval at a
24032
+ * significant decision point. The agent halts until the user responds.
24033
+ *
24034
+ * Decisions are persisted in the session's workflow state (`reviewGates`)
24035
+ * for audit trail and cross-session continuity.
24036
+ */
24037
+ function createReviewGateTool(store, requestReviewFn) {
24038
+ return {
24039
+ toolFn: async (args) => {
24040
+ const params = validateParams(args);
24041
+ const id = v4();
24042
+ const requestedAt = (/* @__PURE__ */ new Date()).toISOString();
24043
+ const response = await requestReviewFn({
24044
+ id,
24045
+ description: params.description,
24046
+ options: params.options,
24047
+ recommendation: params.recommendation
24048
+ });
24049
+ const trimmedNote = response.note?.trim();
24050
+ const entry = {
24051
+ id,
24052
+ timestamp: requestedAt,
24053
+ description: params.description,
24054
+ status: response.decision,
24055
+ resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
24056
+ userNote: trimmedNote && trimmedNote.length > 0 ? trimmedNote : void 0
24057
+ };
24058
+ store.reviewGates.push(entry);
24059
+ if (store.onUpdate) store.onUpdate(store.reviewGates);
24060
+ const verdict = response.decision === "approved" ? "APPROVED" : "REJECTED";
24061
+ const noteText = entry.userNote ? `\nUser note: ${entry.userNote}` : "";
24062
+ return `Review gate ${verdict} [${id.slice(0, 8)}]: ${params.description}${noteText}`;
24063
+ },
24064
+ toolSchema: {
24065
+ name: "request_review_gate",
24066
+ description: `Pause execution and request explicit human approval at a significant decision point.
24067
+
24068
+ Review gates protect meaning. Stop before crossing decisions that affect interpretation, evidence, cost, credentials, platform, or public commitment.
24069
+
24070
+ **When to use:**
24071
+ - Synthesizing findings before narrowing research scope
24072
+ - Hard-to-reverse decisions (refactors, architectural pivots, dependency swaps)
24073
+ - Decisions affecting cost, credentials, or external commitments
24074
+ - Major direction changes after exploration
24075
+
24076
+ **When NOT to use:**
24077
+ - Routine operations (reading files, running tests, listing directories)
24078
+ - Operations already covered by the standard permission system (file edits, bash commands)
24079
+ - Trivial choices that wouldn't matter to someone resuming this work
24080
+
24081
+ The agent will pause until the user explicitly approves or rejects. The user may attach an optional note to their decision (e.g., to redirect or clarify scope). Treat a rejection as a hard stop — re-plan rather than retry.`,
24082
+ parameters: {
24083
+ type: "object",
24084
+ properties: {
24085
+ description: {
24086
+ type: "string",
24087
+ description: "Clear explanation of what the user is being asked to approve, including relevant context"
24088
+ },
24089
+ options: {
24090
+ type: "array",
24091
+ items: { type: "string" },
24092
+ description: "Optional list of alternatives the user can choose between"
24093
+ },
24094
+ recommendation: {
24095
+ type: "string",
24096
+ description: "Optional recommendation from the AI on the preferred path and why"
24097
+ }
24098
+ },
24099
+ required: ["description"]
24100
+ }
24101
+ }
24102
+ };
24103
+ }
24104
+ /**
24105
+ * Create a new empty ReviewGateStore
24106
+ */
24107
+ function createReviewGateStore(onUpdate) {
24108
+ return {
24109
+ reviewGates: [],
24110
+ onUpdate
24111
+ };
24112
+ }
24113
+ //#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 };
@@ -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.6.0";
7
+ var version = "0.7.0";
8
8
  //#endregion
9
9
  //#region src/utils/updateChecker.ts
10
10
  /**
@@ -53,7 +53,7 @@ async function readCache() {
53
53
  try {
54
54
  const data = await promises.readFile(CACHE_FILE, "utf-8");
55
55
  const parsed = JSON.parse(data);
56
- if (parsed && typeof parsed.lastChecked === "string" && typeof parsed.latestVersion === "string") return parsed;
56
+ if (parsed && typeof parsed.lastChecked === "string" && typeof parsed.latestVersion === "string" && typeof parsed.currentVersion === "string") return parsed;
57
57
  return null;
58
58
  } catch {
59
59
  return null;
@@ -117,4 +117,4 @@ async function forceCheckForUpdate(currentVersion) {
117
117
  };
118
118
  }
119
119
  //#endregion
120
- export { version as i, fetchLatestVersion as n, forceCheckForUpdate as r, checkForUpdate as t };
120
+ export { version as a, forceCheckForUpdate as i, compareSemver as n, fetchLatestVersion as r, checkForUpdate as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bike4mind/cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "description": "Interactive CLI tool for Bike4Mind with ReAct agents",
6
6
  "license": "UNLICENSED",
@@ -103,7 +103,7 @@
103
103
  "yauzl": "^3.3.0",
104
104
  "zod": "^4.3.6",
105
105
  "zod-validation-error": "^5.0.0",
106
- "zustand": "^5.0.13"
106
+ "zustand": "^4.5.4"
107
107
  },
108
108
  "devDependencies": {
109
109
  "@types/better-sqlite3": "^7.6.13",
@@ -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.0.0-changeset-release-main-20260505221459",
122
- "@bike4mind/common": "0.0.0-changeset-release-main-20260505221459",
123
- "@bike4mind/mcp": "0.0.0-changeset-release-main-20260505221459",
124
- "@bike4mind/services": "0.0.0-changeset-release-main-20260505221459",
125
- "@bike4mind/utils": "0.0.0-changeset-release-main-20260505221459"
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"
126
126
  },
127
127
  "optionalDependencies": {
128
128
  "@vscode/ripgrep": "^1.17.1"
@@ -134,6 +134,7 @@
134
134
  "test": "vitest run",
135
135
  "test:watch": "vitest",
136
136
  "start": "node dist/index.mjs",
137
+ "eval:run": "tsx src/evals/cli.ts",
137
138
  "postinstall": "node -e \"try { require('better-sqlite3') } catch(e) { if(e.message.includes('bindings')) { console.log('\\n⚠️ Rebuilding better-sqlite3 native bindings...'); require('child_process').execSync('pnpm rebuild better-sqlite3', {stdio:'inherit'}) } }\""
138
139
  }
139
140
  }
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- import { n as useCliStore } from "./store-B7-LLvvx.mjs";
3
- export { useCliStore };