@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 +98 -5
- package/dist/{ConfigStore-Dt6utdSA.mjs → ConfigStore-Bu_plzvP.mjs} +12 -3
- package/dist/commands/doctorCommand.mjs +19 -19
- package/dist/commands/headlessCommand.mjs +3 -3
- package/dist/commands/mcpCommand.mjs +1 -1
- package/dist/commands/updateCommand.mjs +1 -1
- package/dist/index.mjs +209 -20
- package/dist/store-D2zh6DFz.mjs +3 -0
- package/dist/{store-B7-LLvvx.mjs → store-FkY4K-0M.mjs} +13 -0
- package/dist/{tools-dIVxi-6-.mjs → tools-DY-JzNoY.mjs} +293 -27
- package/dist/{updateChecker-DZXWZfKF.mjs → updateChecker-Dn-Ri8zw.mjs} +3 -3
- package/package.json +8 -7
- package/dist/store-B_ILRSdP.mjs +0 -3
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
|
-
#
|
|
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
|
|
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
|
-
|
|
393
|
-
pnpm build
|
|
437
|
+
### Editing workspace dependencies (`b4m-core/*`)
|
|
394
438
|
|
|
395
|
-
|
|
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 {
|
|
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)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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 {
|
|
3
|
-
import { n as logger, t as ConfigStore } from "../ConfigStore-
|
|
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 =
|
|
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 {
|
|
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-
|
|
3
|
-
import { $ as
|
|
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-
|
|
5
|
-
import {
|
|
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
|
-
})),
|
|
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: "
|
|
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 =
|
|
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:
|
|
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 =
|
|
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:
|
|
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:
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
}
|
|
@@ -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-
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 = "
|
|
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",
|
|
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
|
|
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-
|
|
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 `
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
122
|
-
"@bike4mind/common": "
|
|
123
|
-
"@bike4mind/
|
|
124
|
-
"@bike4mind/
|
|
125
|
-
"@bike4mind/
|
|
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
|
}
|
package/dist/store-B_ILRSdP.mjs
DELETED