@bike4mind/cli 0.6.1 → 0.8.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.
@@ -862,9 +862,27 @@ const GenericCreditDeductTransaction = BaseCreditTransaction.extend({
862
862
  */
863
863
  userId: z.string().optional(),
864
864
  /**
865
- * Optional reason/source for the transaction (e.g., 'admin_adjustment', 'legacy_usage', 'manual_deduction')
865
+ * Reason/source for the transaction. Use GenericDeductReasons for new callers.
866
866
  */
867
- reason: z.string().optional()
867
+ reason: z.enum([
868
+ "dispute_clawback",
869
+ "refund_clawback",
870
+ "payment_failed_clawback",
871
+ "notebook_curation",
872
+ "manual",
873
+ "admin_adjustment",
874
+ "refund_adjustment"
875
+ ]).optional(),
876
+ /**
877
+ * Stripe dispute ID — set for dispute clawback transactions.
878
+ * Used for idempotency (unique sparse index prevents duplicate clawbacks).
879
+ */
880
+ stripeDisputeId: z.string().optional(),
881
+ /**
882
+ * Stripe refund ID — set for refund clawback transactions.
883
+ * Used for idempotency (unique sparse index prevents duplicate clawbacks).
884
+ */
885
+ stripeRefundId: z.string().optional()
868
886
  });
869
887
  const TextGenerationUsageTransaction = BaseCreditTransaction.extend({
870
888
  type: z.literal("text_generation_usage"),
@@ -8829,7 +8847,14 @@ const CliConfigSchema = z.object({
8829
8847
  maxIterations: z.number().nullable().prefault(10),
8830
8848
  enableSkillTool: z.boolean().optional().prefault(true),
8831
8849
  enableDynamicAgentCreation: z.boolean().optional().prefault(false),
8832
- enableCoordinatorMode: z.boolean().optional().prefault(false)
8850
+ enableCoordinatorMode: z.boolean().optional().prefault(false),
8851
+ /**
8852
+ * System-prompt variant. 'current' uses the elaborate behavioral-scaffolding
8853
+ * prompt; 'minimal' uses a pi-style short prompt. See apps/cli/src/core/prompts.ts.
8854
+ * Defaults to 'current' for backward compatibility; switch via /config or by
8855
+ * editing the config file directly.
8856
+ */
8857
+ promptVariant: z.enum(["current", "minimal"]).optional().prefault("current")
8833
8858
  }),
8834
8859
  tools: z.object({
8835
8860
  enabled: z.array(z.string()),
@@ -8862,7 +8887,8 @@ const ProjectConfigSchema = z.object({
8862
8887
  exportFormat: z.enum(["markdown", "json"]).optional(),
8863
8888
  enableSkillTool: z.boolean().optional(),
8864
8889
  enableDynamicAgentCreation: z.boolean().optional(),
8865
- enableCoordinatorMode: z.boolean().optional()
8890
+ enableCoordinatorMode: z.boolean().optional(),
8891
+ promptVariant: z.enum(["current", "minimal"]).optional()
8866
8892
  }).optional(),
8867
8893
  sandbox: PartialSandboxConfigSchema,
8868
8894
  additionalDirectories: z.array(z.string()).optional()
@@ -8912,7 +8938,8 @@ const DEFAULT_CONFIG = {
8912
8938
  maxIterations: 10,
8913
8939
  enableSkillTool: true,
8914
8940
  enableDynamicAgentCreation: false,
8915
- enableCoordinatorMode: false
8941
+ enableCoordinatorMode: false,
8942
+ promptVariant: "current"
8916
8943
  },
8917
8944
  tools: {
8918
8945
  enabled: [],
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as version, n as compareSemver, r as fetchLatestVersion } from "../updateChecker-D-xoVcN3.mjs";
2
+ import { a as version, n as compareSemver, r as fetchLatestVersion } from "../updateChecker-Bbkc_8IL.mjs";
3
3
  import { execSync } from "child_process";
4
4
  import { constants, existsSync, promises } from "fs";
5
5
  import { homedir } from "os";
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { 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-BUs_OkJc.mjs";
3
- import { n as logger, t as ConfigStore } from "../ConfigStore-Dt6utdSA.mjs";
2
+ import { C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as PermissionManager, I as generateCliTools, M as loadContextFiles, N as getApiUrl, O as McpManager, Q as CheckpointStore, S as ApiClient, T as FallbackLlmBackend, W as setWebSocketToolExecutor, X as ReActAgent, Y as isReadOnlyTool, Z as CustomCommandStore, _ as createAgentDelegateTool, b as createSkillTool, d as createFindDefinitionTool, et as SessionStore, f as createTodoStore, g as BackgroundAgentManager, h as createBackgroundAgentTools, m as createCoordinateTaskTool, p as createWriteTodosTool, q as buildSystemPrompt, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, y as SubagentOrchestrator } from "../tools-Dg1HL5PO.mjs";
3
+ import { n as logger, t as ConfigStore } from "../ConfigStore-Bj1IOvWn.mjs";
4
4
  import { t as DEFAULT_SANDBOX_CONFIG } from "../types-DBEjF9YS.mjs";
5
5
  import { t as createSandboxRuntime } from "../SandboxRuntimeAdapter-C1B4t20N.mjs";
6
6
  import { t as SandboxOrchestrator } from "../SandboxOrchestrator-BEW3rqYi.mjs";
@@ -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-Bj1IOvWn.mjs";
3
3
  //#region src/commands/mcpCommand.ts
4
4
  /**
5
5
  * External MCP commands (b4m mcp list, b4m mcp add, etc.)
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as version, i as forceCheckForUpdate } from "../updateChecker-D-xoVcN3.mjs";
2
+ import { a as version, i as forceCheckForUpdate } from "../updateChecker-Bbkc_8IL.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-BUs_OkJc.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 { a as version, t as checkForUpdate } from "./updateChecker-D-xoVcN3.mjs";
2
+ import { n as useCliStore, t as selectActiveBackgroundAgents } from "./store-B0ImnWR4.mjs";
3
+ import { $ as CommandHistoryStore, A as formatStep, B as DEFAULT_RETRY_CONFIG, C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as PermissionManager, G as OllamaBackend, H as clearFeatureModuleTools, I as generateCliTools, J as buildSkillsPromptSection, K as getPlanModeFilePath, L as ALWAYS_DENIED_FOR_AGENTS, M as loadContextFiles, N as getApiUrl, O as McpManager, P as getEnvironmentName, Q as CheckpointStore, R as DEFAULT_AGENT_MODEL, S as ApiClient, T as FallbackLlmBackend, U as registerFeatureModuleTools, V as DEFAULT_THOROUGHNESS, W as setWebSocketToolExecutor, X as ReActAgent, Y as isReadOnlyTool, Z as CustomCommandStore, _ as createAgentDelegateTool, a as createBlockerTools, at as mergeCommands, b as createSkillTool, c as createDecisionStore, ct as warmFileCache, d as createFindDefinitionTool, et as SessionStore, f as createTodoStore, g as BackgroundAgentManager, h as createBackgroundAgentTools, i as createBlockerStore, it as searchCommands, j as extractCompactInstructions, k as substituteArguments, l as formatDecisionsOutput, m as createCoordinateTaskTool, n as createReviewGateTool, nt as hasFileReferences, o as formatBlockersOutput, ot as formatFileSize, p as createWriteTodosTool, q as buildSystemPrompt, r as formatReviewGatesOutput, rt as processFileReferences, s as createDecisionLogTool, st as searchFiles, t as createReviewGateStore, tt as OAuthClient, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, x as parseAgentConfig, y as SubagentOrchestrator, z as DEFAULT_MAX_ITERATIONS } from "./tools-Dg1HL5PO.mjs";
4
+ import { Mt as validateNotebookPath$1, g as ChatModels, jt as validateJupyterKernelName, m as CREDIT_DEDUCT_TRANSACTION_TYPES, n as logger, t as ConfigStore } from "./ConfigStore-Bj1IOvWn.mjs";
5
+ import { a as version, t as checkForUpdate } from "./updateChecker-Bbkc_8IL.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";
@@ -25,16 +25,19 @@ import { get_encoding } from "tiktoken";
25
25
  import WsWebSocket from "ws";
26
26
  //#region src/components/StatusBar.tsx
27
27
  const StatusBar = React.memo(function StatusBar({ sessionName, model, tokenUsage, creditsUsage }) {
28
- const autoAcceptEdits = useCliStore((state) => state.autoAcceptEdits);
28
+ const interactionMode = useCliStore((state) => state.interactionMode);
29
29
  return /* @__PURE__ */ React.createElement(Box, {
30
30
  flexDirection: "row",
31
31
  justifyContent: "space-between",
32
32
  width: "100%",
33
33
  paddingX: 1
34
- }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, sessionName), /* @__PURE__ */ React.createElement(Box, { gap: 2 }, autoAcceptEdits && /* @__PURE__ */ React.createElement(Text, {
34
+ }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, sessionName), /* @__PURE__ */ React.createElement(Box, { gap: 2 }, interactionMode === "auto-accept" && /* @__PURE__ */ React.createElement(Text, {
35
35
  color: "green",
36
36
  bold: true
37
- }, "AUTO ACCEPT: Edits"), tokenUsage > 0 && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, tokenUsage.toLocaleString(), " tokens"), creditsUsage !== void 0 && creditsUsage > 0 && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, creditsUsage.toLocaleString(), " ", creditsUsage === 1 ? "credit" : "credits"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, model)));
37
+ }, "AUTO ACCEPT: Edits"), interactionMode === "plan" && /* @__PURE__ */ React.createElement(Text, {
38
+ color: "yellow",
39
+ bold: true
40
+ }, "PLAN MODE"), tokenUsage > 0 && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, tokenUsage.toLocaleString(), " tokens"), creditsUsage !== void 0 && creditsUsage > 0 && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, creditsUsage.toLocaleString(), " ", creditsUsage === 1 ? "credit" : "credits"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, model)));
38
41
  });
39
42
  /**
40
43
  * Maximum paste size in characters (~500KB) to prevent memory issues
@@ -1274,6 +1277,138 @@ function UserQuestionPrompt({ payload, onResponse }) {
1274
1277
  })), /* @__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
1278
  }
1276
1279
  //#endregion
1280
+ //#region src/components/ReviewGatePrompt.tsx
1281
+ const ITEMS = [
1282
+ {
1283
+ label: "✓ Approve",
1284
+ decision: "approved",
1285
+ withNote: false
1286
+ },
1287
+ {
1288
+ label: "✓ Approve with note...",
1289
+ decision: "approved",
1290
+ withNote: true
1291
+ },
1292
+ {
1293
+ label: "✗ Reject",
1294
+ decision: "rejected",
1295
+ withNote: false
1296
+ },
1297
+ {
1298
+ label: "✗ Reject with note...",
1299
+ decision: "rejected",
1300
+ withNote: true
1301
+ }
1302
+ ];
1303
+ /**
1304
+ * Review gate prompt component.
1305
+ *
1306
+ * Pauses the agent and asks the user to explicitly approve or reject a
1307
+ * significant decision. An optional free-text note can be attached to either
1308
+ * decision via the dedicated "...with note..." actions.
1309
+ *
1310
+ * Keyboard:
1311
+ * - 1–4: shortcut for each action
1312
+ * - y: Approve (no note); n: Reject (no note)
1313
+ * - ↑/↓: navigate; Enter: confirm selection
1314
+ * - When a "with note..." action is selected, an inline text input appears.
1315
+ * Type the note and press Enter to submit. Use ↑/↓ to switch to a no-note
1316
+ * action without losing the typed note.
1317
+ */
1318
+ function ReviewGatePrompt({ description, options, recommendation, onResponse }) {
1319
+ const [selectedIndex, setSelectedIndex] = useState(0);
1320
+ const [note, setNote] = useState("");
1321
+ const [responded, setResponded] = useState(false);
1322
+ const submit = useCallback((item, noteText) => {
1323
+ if (responded) return;
1324
+ setResponded(true);
1325
+ const trimmed = noteText.trim();
1326
+ onResponse({
1327
+ decision: item.decision,
1328
+ note: trimmed.length > 0 ? trimmed : void 0
1329
+ });
1330
+ }, [responded, onResponse]);
1331
+ const selectedItem = ITEMS[selectedIndex];
1332
+ const noteMode = selectedItem.withNote;
1333
+ const handleConfirm = useCallback(() => {
1334
+ if (selectedItem.withNote && note.trim().length === 0) return;
1335
+ submit(selectedItem, note);
1336
+ }, [
1337
+ selectedItem,
1338
+ note,
1339
+ submit
1340
+ ]);
1341
+ useInput((input, key) => {
1342
+ if (responded) return;
1343
+ if (key.upArrow) {
1344
+ setSelectedIndex((i) => i > 0 ? i - 1 : ITEMS.length - 1);
1345
+ return;
1346
+ }
1347
+ if (key.downArrow) {
1348
+ setSelectedIndex((i) => i < ITEMS.length - 1 ? i + 1 : 0);
1349
+ return;
1350
+ }
1351
+ if (noteMode) return;
1352
+ const num = parseInt(input, 10);
1353
+ if (num >= 1 && num <= ITEMS.length) {
1354
+ const item = ITEMS[num - 1];
1355
+ setSelectedIndex(num - 1);
1356
+ if (!item.withNote) submit(item, "");
1357
+ return;
1358
+ }
1359
+ if (input.toLowerCase() === "y") {
1360
+ submit(ITEMS[0], "");
1361
+ return;
1362
+ }
1363
+ if (input.toLowerCase() === "n") {
1364
+ submit(ITEMS[2], "");
1365
+ return;
1366
+ }
1367
+ if (key.return) handleConfirm();
1368
+ }, { isActive: !responded });
1369
+ if (responded) return null;
1370
+ return /* @__PURE__ */ React.createElement(Box, {
1371
+ flexDirection: "column",
1372
+ borderStyle: "bold",
1373
+ borderColor: "magenta",
1374
+ padding: 1,
1375
+ marginY: 1
1376
+ }, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, {
1377
+ bold: true,
1378
+ color: "magenta"
1379
+ }, "🛑 Review Gate")), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, description)), recommendation && /* @__PURE__ */ React.createElement(Box, {
1380
+ marginTop: 1,
1381
+ flexDirection: "column"
1382
+ }, /* @__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, {
1383
+ marginTop: 1,
1384
+ flexDirection: "column"
1385
+ }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Options:"), /* @__PURE__ */ React.createElement(Box, {
1386
+ paddingLeft: 2,
1387
+ flexDirection: "column"
1388
+ }, options.map((opt, idx) => /* @__PURE__ */ React.createElement(Text, {
1389
+ key: idx,
1390
+ dimColor: true
1391
+ }, "• ", opt)))), /* @__PURE__ */ React.createElement(Box, {
1392
+ marginTop: 1,
1393
+ flexDirection: "column"
1394
+ }, ITEMS.map((item, index) => {
1395
+ const isHighlighted = index === selectedIndex;
1396
+ const color = item.decision === "approved" ? "green" : "red";
1397
+ return /* @__PURE__ */ React.createElement(Box, { key: item.label }, /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, index + 1, "."), /* @__PURE__ */ React.createElement(Text, {
1398
+ color: isHighlighted ? color : void 0,
1399
+ bold: isHighlighted
1400
+ }, isHighlighted ? " ❯ " : " ", item.label));
1401
+ })), noteMode && /* @__PURE__ */ React.createElement(Box, {
1402
+ marginTop: 1,
1403
+ flexDirection: "column"
1404
+ }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Note:"), /* @__PURE__ */ React.createElement(Box, { paddingLeft: 2 }, /* @__PURE__ */ React.createElement(TextInput, {
1405
+ value: note,
1406
+ onChange: setNote,
1407
+ onSubmit: handleConfirm,
1408
+ placeholder: "Type a note and press Enter…"
1409
+ }))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, noteMode ? note.trim().length === 0 ? "Note required — type a note + Enter to submit, ↑↓ to switch action" : "Type note + Enter to submit, ↑↓ to switch action" : "Press 1-4, y/n, or ↑↓ + Enter")));
1410
+ }
1411
+ //#endregion
1277
1412
  //#region src/components/ConfigEditor.tsx
1278
1413
  /**
1279
1414
  * Max iterations options: 10, 20, 30, 40, 50, Infinite (null)
@@ -1886,7 +2021,7 @@ const MessageItem = React.memo(function MessageItem({ message }) {
1886
2021
  });
1887
2022
  //#endregion
1888
2023
  //#region src/components/App.tsx
1889
- function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPermissionResponse, onUserQuestionResponse, onImageDetected, commandHistory = [], commands = [], config, availableModels = [], onSaveConfig, prefillInput, onPrefillConsumed, mcpManager }) {
2024
+ function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPermissionResponse, onUserQuestionResponse, onReviewGateResponse, onImageDetected, commandHistory = [], commands = [], config, availableModels = [], onSaveConfig, prefillInput, onPrefillConsumed, mcpManager }) {
1890
2025
  const messages = useCliStore((state) => state.session?.messages || []);
1891
2026
  const pendingMessages = useCliStore((state) => state.pendingMessages);
1892
2027
  const sessionName = useCliStore((state) => state.session?.name || "New Session");
@@ -1896,6 +2031,7 @@ function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPe
1896
2031
  const isThinking = useCliStore((state) => state.isThinking);
1897
2032
  const permissionPrompt = useCliStore((state) => state.permissionPrompt);
1898
2033
  const userQuestionPrompt = useCliStore((state) => state.userQuestionPrompt);
2034
+ const reviewGatePrompt = useCliStore((state) => state.reviewGatePrompt);
1899
2035
  const showConfigEditor = useCliStore((state) => state.showConfigEditor);
1900
2036
  const setShowConfigEditor = useCliStore((state) => state.setShowConfigEditor);
1901
2037
  const showMcpViewer = useCliStore((state) => state.showMcpViewer);
@@ -1915,9 +2051,9 @@ function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPe
1915
2051
  setPendingBackgroundTrigger,
1916
2052
  onBackgroundCompletion
1917
2053
  ]);
1918
- const toggleAutoAcceptEdits = useCliStore((state) => state.toggleAutoAcceptEdits);
2054
+ const cycleInteractionMode = useCliStore((state) => state.cycleInteractionMode);
1919
2055
  useInput((_input, key) => {
1920
- if (key.tab && key.shift) toggleAutoAcceptEdits();
2056
+ if (key.tab && key.shift) cycleInteractionMode();
1921
2057
  });
1922
2058
  const [isBashMode, setIsBashMode] = useState(false);
1923
2059
  const handleSubmit = React.useCallback(async (input) => {
@@ -1988,7 +2124,16 @@ function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPe
1988
2124
  }, /* @__PURE__ */ React.createElement(UserQuestionPrompt, {
1989
2125
  payload: userQuestionPrompt.payload,
1990
2126
  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, {
2127
+ })), reviewGatePrompt && /* @__PURE__ */ React.createElement(Box, {
2128
+ key: reviewGatePrompt.id,
2129
+ flexDirection: "column",
2130
+ paddingX: 1
2131
+ }, /* @__PURE__ */ React.createElement(ReviewGatePrompt, {
2132
+ description: reviewGatePrompt.description,
2133
+ options: reviewGatePrompt.options,
2134
+ recommendation: reviewGatePrompt.recommendation,
2135
+ onResponse: (response) => onReviewGateResponse(response, reviewGatePrompt.id)
2136
+ })), !permissionPrompt && !userQuestionPrompt && !reviewGatePrompt && /* @__PURE__ */ React.createElement(AgentThinking, null), /* @__PURE__ */ React.createElement(BackgroundAgentStatus, null), /* @__PURE__ */ React.createElement(CompletedGroupNotification, null), exitRequested && /* @__PURE__ */ React.createElement(Box, {
1992
2137
  paddingX: 1,
1993
2138
  marginBottom: 1
1994
2139
  }, /* @__PURE__ */ React.createElement(Text, {
@@ -2002,7 +2147,7 @@ function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPe
2002
2147
  onSubmit: handleSubmit,
2003
2148
  onBashCommand,
2004
2149
  onImageDetected,
2005
- disabled: isThinking || !!permissionPrompt || !!userQuestionPrompt,
2150
+ disabled: isThinking || !!permissionPrompt || !!userQuestionPrompt || !!reviewGatePrompt,
2006
2151
  history: commandHistory,
2007
2152
  commands,
2008
2153
  prefillInput,
@@ -2469,7 +2614,7 @@ function buildCompactionPrompt(messages, options = {}) {
2469
2614
  function createCompactedSession(originalSession, summary, preservedMessages) {
2470
2615
  const summaryMessage = {
2471
2616
  id: v4(),
2472
- role: "system",
2617
+ role: "user",
2473
2618
  content: `[Previous conversation summary]\n\n${summary}`,
2474
2619
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2475
2620
  };
@@ -5235,10 +5380,13 @@ function CliApp() {
5235
5380
  const imageStoreInitPromise = useRef(null);
5236
5381
  const decisionStoreRef = useRef(createDecisionStore());
5237
5382
  const blockerStoreRef = useRef(createBlockerStore());
5383
+ const reviewGateStoreRef = useRef(createReviewGateStore());
5238
5384
  const setStoreSession = useCliStore((state) => state.setSession);
5239
5385
  const enqueuePermissionPrompt = useCliStore((state) => state.enqueuePermissionPrompt);
5240
5386
  const enqueueUserQuestionPrompt = useCliStore((state) => state.enqueueUserQuestionPrompt);
5241
5387
  const dequeueUserQuestionPrompt = useCliStore((state) => state.dequeueUserQuestionPrompt);
5388
+ const enqueueReviewGatePrompt = useCliStore((state) => state.enqueueReviewGatePrompt);
5389
+ const dequeueReviewGatePrompt = useCliStore((state) => state.dequeueReviewGatePrompt);
5242
5390
  const setShowConfigEditor = useCliStore((state) => state.setShowConfigEditor);
5243
5391
  const setShowMcpViewer = useCliStore((state) => state.setShowMcpViewer);
5244
5392
  const setExitRequested = useCliStore((state) => state.setExitRequested);
@@ -5628,6 +5776,22 @@ function CliApp() {
5628
5776
  });
5629
5777
  });
5630
5778
  };
5779
+ const reviewGateFn = (params) => {
5780
+ return new Promise((resolve) => {
5781
+ enqueueReviewGatePrompt({
5782
+ id: params.id,
5783
+ description: params.description,
5784
+ options: params.options,
5785
+ recommendation: params.recommendation,
5786
+ resolve
5787
+ });
5788
+ bridgePresence.emitEvent({
5789
+ type: "status",
5790
+ status: "awaiting_input",
5791
+ text: `Review gate: ${params.description}`.slice(0, 240)
5792
+ });
5793
+ });
5794
+ };
5631
5795
  const agentContext = {
5632
5796
  currentAgent: null,
5633
5797
  observationQueue: []
@@ -5681,12 +5845,15 @@ function CliApp() {
5681
5845
  const writeTodosTool = createWriteTodosTool(createTodoStore());
5682
5846
  const decisionLogTool = createDecisionLogTool(decisionStoreRef.current);
5683
5847
  const blockerTools = createBlockerTools(blockerStoreRef.current);
5848
+ const reviewGateTool = createReviewGateTool(reviewGateStoreRef.current, reviewGateFn);
5684
5849
  if (newSession.metadata.workflow) {
5685
5850
  decisionStoreRef.current.decisions = [...newSession.metadata.workflow.decisions];
5686
5851
  blockerStoreRef.current.blockers = [...newSession.metadata.workflow.blockers];
5852
+ reviewGateStoreRef.current.reviewGates = [...newSession.metadata.workflow.reviewGates ?? []];
5687
5853
  } else {
5688
5854
  decisionStoreRef.current.decisions = [];
5689
5855
  blockerStoreRef.current.blockers = [];
5856
+ reviewGateStoreRef.current.reviewGates = [];
5690
5857
  }
5691
5858
  const enableSkillTool = config.preferences.enableSkillTool !== false;
5692
5859
  const skillTool = enableSkillTool ? createSkillTool({
@@ -5707,6 +5874,7 @@ function CliApp() {
5707
5874
  writeTodosTool,
5708
5875
  decisionLogTool,
5709
5876
  ...blockerTools,
5877
+ reviewGateTool,
5710
5878
  findDefinitionTool,
5711
5879
  getFileStructureTool
5712
5880
  ];
@@ -5741,15 +5909,18 @@ function CliApp() {
5741
5909
  if (contextResult.projectContext) startupLog.push(`📄 Project context: ${contextResult.projectContext.filename}`);
5742
5910
  for (const error of contextResult.errors) startupLog.push(`⚠️ Context file error: ${error}`);
5743
5911
  const featureModulePrompts = featureRegistry.getSystemPromptSections();
5744
- const cliSystemPrompt = buildCoreSystemPrompt({
5912
+ const promptVariant = config.preferences.promptVariant ?? "current";
5913
+ const buildPromptForMode = (mode) => buildSystemPrompt(promptVariant, {
5745
5914
  contextContent: contextResult.mergedContent,
5746
5915
  agentStore,
5747
5916
  customCommands: state.customCommandStore.getAllCommands(),
5748
5917
  enableSkillTool,
5749
5918
  enableDynamicAgentCreation: config.preferences.enableDynamicAgentCreation === true,
5750
5919
  additionalDirectories,
5751
- featureModulePrompts: featureModulePrompts || void 0
5920
+ featureModulePrompts: featureModulePrompts || void 0,
5921
+ planModeFilePath: mode === "plan" ? getPlanModeFilePath(newSession.id) : void 0
5752
5922
  });
5923
+ const cliSystemPrompt = buildPromptForMode(useCliStore.getState().interactionMode);
5753
5924
  const maxIterations = config.preferences.maxIterations === null ? 999999 : config.preferences.maxIterations;
5754
5925
  const agent = new ReActAgent({
5755
5926
  userId: config.userId,
@@ -5763,6 +5934,14 @@ function CliApp() {
5763
5934
  systemPrompt: cliSystemPrompt
5764
5935
  });
5765
5936
  agentContext.currentAgent = agent;
5937
+ const agentInternalContext = agent.context;
5938
+ let lastInteractionMode = useCliStore.getState().interactionMode;
5939
+ useCliStore.subscribe((s) => {
5940
+ if (s.interactionMode === lastInteractionMode) return;
5941
+ lastInteractionMode = s.interactionMode;
5942
+ if (agentContext.currentAgent !== agent) return;
5943
+ agentInternalContext.systemPrompt = buildPromptForMode(s.interactionMode);
5944
+ });
5766
5945
  agent.observationQueue = agentContext.observationQueue;
5767
5946
  const stepHandler = (step) => {
5768
5947
  const { pendingMessages, updatePendingMessage } = useCliStore.getState();
@@ -5980,6 +6159,14 @@ function CliApp() {
5980
6159
  });
5981
6160
  return;
5982
6161
  }
6162
+ if (s.reviewGatePrompt) {
6163
+ bridgePresence.emitEvent({
6164
+ type: "status",
6165
+ status: "awaiting_input",
6166
+ text: `Review gate: ${s.reviewGatePrompt.description}`.slice(0, 240)
6167
+ });
6168
+ return;
6169
+ }
5983
6170
  const abort = abortControllerRef.current;
5984
6171
  if (!abort || abort.signal.aborted) return;
5985
6172
  bridgePresence.emitEvent({
@@ -6137,11 +6324,11 @@ function CliApp() {
6137
6324
  totalTokens: currentSession.metadata.totalTokens + result.completionInfo.totalTokens,
6138
6325
  totalCredits: (currentSession.metadata.totalCredits || 0) + (result.completionInfo.totalCredits || 0),
6139
6326
  toolCallCount: currentSession.metadata.toolCallCount + successfulToolCalls,
6140
- workflow: decisionStoreRef.current.decisions.length > 0 || blockerStoreRef.current.blockers.length > 0 ? {
6327
+ workflow: decisionStoreRef.current.decisions.length > 0 || blockerStoreRef.current.blockers.length > 0 || reviewGateStoreRef.current.reviewGates.length > 0 ? {
6141
6328
  decisions: decisionStoreRef.current.decisions,
6142
6329
  blockers: blockerStoreRef.current.blockers,
6143
6330
  handoff: currentSession.metadata.workflow?.handoff,
6144
- reviewGates: currentSession.metadata.workflow?.reviewGates
6331
+ reviewGates: reviewGateStoreRef.current.reviewGates
6145
6332
  } : currentSession.metadata.workflow
6146
6333
  }
6147
6334
  };
@@ -6255,7 +6442,7 @@ function CliApp() {
6255
6442
  if (config?.preferences.autoCompact !== false && activeSession.messages.length >= 6) {
6256
6443
  const tokenCounter = getTokenCounter();
6257
6444
  const threshold = tokenCounter.getContextWindow(activeSession.model, state.availableModels) * .8;
6258
- const systemPrompt = buildCoreSystemPrompt({
6445
+ const systemPrompt = buildSystemPrompt(config?.preferences.promptVariant ?? "current", {
6259
6446
  contextContent: state.contextContent,
6260
6447
  agentStore: state.agentStore || void 0,
6261
6448
  customCommands: state.customCommandStore.getAllCommands(),
@@ -6653,7 +6840,7 @@ function CliApp() {
6653
6840
  decisions: decisionStoreRef.current.decisions,
6654
6841
  blockers: blockerStoreRef.current.blockers,
6655
6842
  handoff,
6656
- reviewGates: session.metadata.workflow?.reviewGates
6843
+ reviewGates: reviewGateStoreRef.current.reviewGates
6657
6844
  };
6658
6845
  return handoff;
6659
6846
  } catch (err) {
@@ -6798,11 +6985,11 @@ Multi-line Input:
6798
6985
  }
6799
6986
  const sessionName = args.join(" ") || state.session.name;
6800
6987
  state.session.name = sessionName;
6801
- if (decisionStoreRef.current.decisions.length > 0 || blockerStoreRef.current.blockers.length > 0) state.session.metadata.workflow = {
6988
+ if (decisionStoreRef.current.decisions.length > 0 || blockerStoreRef.current.blockers.length > 0 || reviewGateStoreRef.current.reviewGates.length > 0) state.session.metadata.workflow = {
6802
6989
  decisions: decisionStoreRef.current.decisions,
6803
6990
  blockers: blockerStoreRef.current.blockers,
6804
6991
  handoff: state.session.metadata.workflow?.handoff,
6805
- reviewGates: state.session.metadata.workflow?.reviewGates
6992
+ reviewGates: reviewGateStoreRef.current.reviewGates
6806
6993
  };
6807
6994
  const handoff = await generateHandoff(state.session);
6808
6995
  await state.sessionStore.save(state.session);
@@ -7084,6 +7271,17 @@ Multi-line Input:
7084
7271
  }));
7085
7272
  setStoreSession(newSession);
7086
7273
  useCliStore.getState().clearPendingMessages();
7274
+ const staleGate = useCliStore.getState().reviewGatePrompt;
7275
+ if (staleGate) {
7276
+ dequeueReviewGatePrompt();
7277
+ staleGate.resolve({
7278
+ decision: "rejected",
7279
+ note: "Session cleared."
7280
+ });
7281
+ }
7282
+ queueMicrotask(() => {
7283
+ reviewGateStoreRef.current.reviewGates = [];
7284
+ });
7087
7285
  usageCache = null;
7088
7286
  console.log("New session started.");
7089
7287
  console.log(`\n📝 Session: ${newSession.name} | 🤖 Model: ${newSession.model} | 📊 Tokens: 0\n`);
@@ -7309,7 +7507,8 @@ Multi-line Input:
7309
7507
  }
7310
7508
  const tokenCounter = getTokenCounter();
7311
7509
  const contextWindow = tokenCounter.getContextWindow(state.session.model, state.availableModels);
7312
- const corePromptTokens = tokenCounter.countTokens(buildCoreSystemPrompt());
7510
+ const variantForCount = state.config?.preferences.promptVariant ?? "current";
7511
+ const corePromptTokens = tokenCounter.countTokens(buildSystemPrompt(variantForCount));
7313
7512
  const projectContextTokens = state.contextContent ? tokenCounter.countTokens(state.contextContent) : 0;
7314
7513
  const commands = state.customCommandStore.getAllCommands();
7315
7514
  const skillsSection = buildSkillsPromptSection(commands);
@@ -7318,7 +7517,7 @@ Multi-line Input:
7318
7517
  const mcpTools = state.mcpManager?.getTools() || [];
7319
7518
  const mcpToolsTokens = tokenCounter.countToolSchemaTokens(mcpTools);
7320
7519
  const mcpToolCount = state.mcpManager?.getToolCount() || [];
7321
- const systemPrompt = buildCoreSystemPrompt({
7520
+ const systemPrompt = buildSystemPrompt(variantForCount, {
7322
7521
  contextContent: state.contextContent,
7323
7522
  agentStore: state.agentStore || void 0,
7324
7523
  customCommands: commands,
@@ -7778,6 +7977,11 @@ Multi-line Input:
7778
7977
  console.log(formatBlockersOutput(blockerStoreRef.current.blockers));
7779
7978
  console.log("");
7780
7979
  break;
7980
+ case "review-gates":
7981
+ console.log("\n🛑 Review Gates\n");
7982
+ console.log(formatReviewGatesOutput(reviewGateStoreRef.current.reviewGates));
7983
+ console.log("");
7984
+ break;
7781
7985
  case "handoff": {
7782
7986
  if (!state.session) {
7783
7987
  console.log("No active session");
@@ -7891,14 +8095,16 @@ Multi-line Input:
7891
8095
  const newFeatureTools = newFeatureRegistry.getAllTools();
7892
8096
  agentContext.tools = [...baseTools, ...newFeatureTools];
7893
8097
  const newFeaturePrompts = newFeatureRegistry.getSystemPromptSections();
7894
- agentContext.systemPrompt = buildCoreSystemPrompt({
8098
+ const planFilePathForRebuild = useCliStore.getState().interactionMode === "plan" && state.session ? getPlanModeFilePath(state.session.id) : void 0;
8099
+ agentContext.systemPrompt = buildSystemPrompt(updatedConfig.preferences.promptVariant ?? "current", {
7895
8100
  contextContent: state.contextContent,
7896
8101
  agentStore: state.agentStore || void 0,
7897
8102
  customCommands: state.customCommandStore.getAllCommands(),
7898
8103
  enableSkillTool: updatedConfig.preferences.enableSkillTool !== false,
7899
8104
  enableDynamicAgentCreation: updatedConfig.preferences.enableDynamicAgentCreation === true,
7900
8105
  additionalDirectories: state.additionalDirectories,
7901
- featureModulePrompts: newFeaturePrompts || void 0
8106
+ featureModulePrompts: newFeaturePrompts || void 0,
8107
+ planModeFilePath: planFilePathForRebuild
7902
8108
  });
7903
8109
  const moduleNames = newFeatureRegistry.getModuleNames();
7904
8110
  if (moduleNames.length > 0) console.error(`\n\x1b[36m🏰 Feature modules hot-reloaded: ${moduleNames.join(", ")}\x1b[0m`);
@@ -8037,8 +8243,15 @@ Multi-line Input:
8037
8243
  onUserQuestionResponse: (response, promptId) => {
8038
8244
  const currentPrompt = useCliStore.getState().userQuestionPrompt;
8039
8245
  if (currentPrompt?.id !== promptId) return;
8040
- currentPrompt.resolve(response);
8041
8246
  dequeueUserQuestionPrompt();
8247
+ currentPrompt.resolve(response);
8248
+ emitNextAwaitingStatus();
8249
+ },
8250
+ onReviewGateResponse: (response, promptId) => {
8251
+ const currentPrompt = useCliStore.getState().reviewGatePrompt;
8252
+ if (currentPrompt?.id !== promptId) return;
8253
+ dequeueReviewGatePrompt();
8254
+ currentPrompt.resolve(response);
8042
8255
  emitNextAwaitingStatus();
8043
8256
  }
8044
8257
  });
@@ -8049,6 +8262,9 @@ try {
8049
8262
  } catch {}
8050
8263
  if (import.meta.url.includes("/src/") || process.env.NODE_ENV === "development") logger.debug("🔧 Running in development mode (using TypeScript source)\n");
8051
8264
  warmFileCache();
8052
- render(/* @__PURE__ */ React.createElement(CliApp, null), { exitOnCtrlC: false });
8265
+ render(/* @__PURE__ */ React.createElement(CliApp, null), {
8266
+ exitOnCtrlC: false,
8267
+ alternateScreen: true
8268
+ });
8053
8269
  //#endregion
8054
8270
  export {};
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { n as useCliStore } from "./store-B0ImnWR4.mjs";
3
+ export { useCliStore };
@@ -3,6 +3,14 @@ import { create } from "zustand";
3
3
  //#region src/store/index.ts
4
4
  /** Active job statuses (jobs still in progress) */
5
5
  const ACTIVE_STATUSES = new Set(["running", "queued"]);
6
+ const INTERACTION_MODE_CYCLE = [
7
+ "normal",
8
+ "auto-accept",
9
+ "plan"
10
+ ];
11
+ function nextInteractionMode(current) {
12
+ return INTERACTION_MODE_CYCLE[(INTERACTION_MODE_CYCLE.indexOf(current) + 1) % INTERACTION_MODE_CYCLE.length];
13
+ }
6
14
  /** Check if a job status is active (running or queued) */
7
15
  function isActiveStatus(status) {
8
16
  return ACTIVE_STATUSES.has(status);
@@ -105,12 +113,26 @@ const useCliStore = create((set) => ({
105
113
  userQuestionQueue: rest
106
114
  };
107
115
  }),
116
+ reviewGatePrompt: null,
117
+ reviewGateQueue: [],
118
+ enqueueReviewGatePrompt: (prompt) => set((state) => {
119
+ if (!state.reviewGatePrompt) return { reviewGatePrompt: prompt };
120
+ return { reviewGateQueue: [...state.reviewGateQueue, prompt] };
121
+ }),
122
+ dequeueReviewGatePrompt: () => set((state) => {
123
+ const [next, ...rest] = state.reviewGateQueue;
124
+ return {
125
+ reviewGatePrompt: next ?? null,
126
+ reviewGateQueue: rest
127
+ };
128
+ }),
108
129
  showConfigEditor: false,
109
130
  setShowConfigEditor: (show) => set({ showConfigEditor: show }),
110
131
  showMcpViewer: false,
111
132
  setShowMcpViewer: (show) => set({ showMcpViewer: show }),
112
- autoAcceptEdits: false,
113
- toggleAutoAcceptEdits: () => set((state) => ({ autoAcceptEdits: !state.autoAcceptEdits })),
133
+ interactionMode: "normal",
134
+ cycleInteractionMode: () => set((state) => ({ interactionMode: nextInteractionMode(state.interactionMode) })),
135
+ setInteractionMode: (mode) => set({ interactionMode: mode }),
114
136
  exitRequested: false,
115
137
  setExitRequested: (requested) => set({ exitRequested: requested }),
116
138
  backgroundAgents: [],