@bike4mind/cli 0.2.29-feat-cli-websocket-streaming.18881 → 0.2.29-feat-cli-sandbox.18927
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/dist/BubblewrapRuntime-PMIOLWKR.js +71 -0
- package/dist/ProxyManager-4ZZEYTD6.js +249 -0
- package/dist/SandboxOrchestrator-MYBNKKWL.js +162 -0
- package/dist/SandboxRuntimeAdapter-FQ56MAB2.js +13 -0
- package/dist/SeatbeltRuntime-EE3TTLEP.js +98 -0
- package/dist/StderrViolationParser-7OYPM2DJ.js +59 -0
- package/dist/ViolationLogStore-RIIUVURH.js +104 -0
- package/dist/{artifactExtractor-SNWW3EVH.js → artifactExtractor-5KWVJRK7.js} +1 -1
- package/dist/{chunk-QQL3OBKL.js → chunk-2OADXKGK.js} +2 -2
- package/dist/chunk-4BIBE3J7.js +48 -0
- package/dist/{chunk-EC2VEDD2.js → chunk-5JTIPGUF.js} +2 -2
- package/dist/{chunk-BXCARBUQ.js → chunk-CLA2MUX4.js} +2 -2
- package/dist/{chunk-KPOK6V6E.js → chunk-FKJTU7KU.js} +8 -67
- package/dist/{chunk-LBTTUQJM.js → chunk-M2AJGZSX.js} +87 -3
- package/dist/{chunk-TY6Z2UV2.js → chunk-Q7CVAY4M.js} +6 -15
- package/dist/chunk-QWB6ZYY4.js +48 -0
- package/dist/commands/mcpCommand.js +2 -1
- package/dist/{create-QOJPAIOR.js → create-BDV4MI3R.js} +3 -3
- package/dist/index.js +965 -839
- package/dist/{llmMarkdownGenerator-JPDNDGE4.js → llmMarkdownGenerator-UIJD4IKA.js} +1 -1
- package/dist/{markdownGenerator-NHN4MNEA.js → markdownGenerator-UBZZJ3YM.js} +1 -1
- package/dist/{mementoService-EN7FZK7B.js → mementoService-UP7777VB.js} +3 -3
- package/dist/{src-36AL3BJR.js → src-DOG6MRCB.js} +1 -13
- package/dist/{src-JZN5O6F3.js → src-G5MW2D7G.js} +2 -2
- package/dist/{subtractCredits-FCBMFPIP.js → subtractCredits-XGBHF6DI.js} +3 -3
- package/dist/types-KB5NP6T4.js +7 -0
- package/package.json +11 -6
package/dist/index.js
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import "./chunk-5JTIPGUF.js";
|
|
2
3
|
import "./chunk-BPFEGDC7.js";
|
|
3
4
|
import "./chunk-BDQBOLYG.js";
|
|
4
5
|
import {
|
|
5
6
|
getEffectiveApiKey,
|
|
6
7
|
getOpenWeatherKey,
|
|
7
8
|
getSerperKey
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-2OADXKGK.js";
|
|
9
10
|
import {
|
|
10
11
|
ConfigStore,
|
|
11
12
|
logger
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-M2AJGZSX.js";
|
|
13
14
|
import {
|
|
14
15
|
selectActiveBackgroundAgents,
|
|
15
16
|
useCliStore
|
|
16
17
|
} from "./chunk-TVW4ZESU.js";
|
|
17
|
-
import "./chunk-
|
|
18
|
-
import "./chunk-
|
|
18
|
+
import "./chunk-4BIBE3J7.js";
|
|
19
|
+
import "./chunk-CLA2MUX4.js";
|
|
19
20
|
import {
|
|
20
21
|
BFLImageService,
|
|
21
22
|
BaseStorage,
|
|
@@ -27,7 +28,7 @@ import {
|
|
|
27
28
|
OpenAIBackend,
|
|
28
29
|
OpenAIImageService,
|
|
29
30
|
XAIImageService
|
|
30
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-Q7CVAY4M.js";
|
|
31
32
|
import {
|
|
32
33
|
AiEvents,
|
|
33
34
|
ApiKeyEvents,
|
|
@@ -83,32 +84,37 @@ import {
|
|
|
83
84
|
XAI_IMAGE_MODELS,
|
|
84
85
|
b4mLLMTools,
|
|
85
86
|
getMcpProviderMetadata
|
|
86
|
-
} from "./chunk-
|
|
87
|
+
} from "./chunk-FKJTU7KU.js";
|
|
87
88
|
import {
|
|
88
89
|
Logger
|
|
89
90
|
} from "./chunk-OCYRD7D6.js";
|
|
90
91
|
|
|
91
92
|
// src/index.tsx
|
|
92
|
-
import React21, { useState as
|
|
93
|
+
import React21, { useState as useState10, useEffect as useEffect7, useCallback as useCallback2, useRef as useRef3 } from "react";
|
|
93
94
|
import { render, Box as Box20, Text as Text20, useApp, useInput as useInput9 } from "ink";
|
|
94
95
|
import { execSync } from "child_process";
|
|
95
96
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
96
|
-
import { v4 as
|
|
97
|
+
import { v4 as uuidv411 } from "uuid";
|
|
97
98
|
|
|
98
99
|
// src/components/App.tsx
|
|
99
|
-
import React15, { useState as
|
|
100
|
+
import React15, { useState as useState6, useEffect as useEffect5 } from "react";
|
|
100
101
|
import { Box as Box14, Text as Text14, Static, useInput as useInput6 } from "ink";
|
|
101
102
|
|
|
102
103
|
// src/components/StatusBar.tsx
|
|
103
104
|
import React from "react";
|
|
104
105
|
import { Box, Text } from "ink";
|
|
105
|
-
var StatusBar = React.memo(function StatusBar2({
|
|
106
|
+
var StatusBar = React.memo(function StatusBar2({
|
|
107
|
+
sessionName,
|
|
108
|
+
model,
|
|
109
|
+
tokenUsage,
|
|
110
|
+
creditsUsage
|
|
111
|
+
}) {
|
|
106
112
|
const autoAcceptEdits = useCliStore((state) => state.autoAcceptEdits);
|
|
107
|
-
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "row", justifyContent: "space-between", width: "100%", paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, sessionName), /* @__PURE__ */ React.createElement(Box, { gap: 2 }, autoAcceptEdits && /* @__PURE__ */ React.createElement(Text, { color: "green", bold: true }, "AUTO ACCEPT: Edits"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, tokenUsage.toLocaleString(), " tokens"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, model)));
|
|
113
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "row", justifyContent: "space-between", width: "100%", paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, sessionName), /* @__PURE__ */ React.createElement(Box, { gap: 2 }, autoAcceptEdits && /* @__PURE__ */ React.createElement(Text, { color: "green", bold: true }, "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)));
|
|
108
114
|
});
|
|
109
115
|
|
|
110
116
|
// src/components/InputPrompt.tsx
|
|
111
|
-
import React5, { useState as
|
|
117
|
+
import React5, { useState as useState3, useMemo, useEffect as useEffect3, useRef as useRef2 } from "react";
|
|
112
118
|
import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
|
|
113
119
|
|
|
114
120
|
// src/components/CustomTextInput.tsx
|
|
@@ -321,65 +327,53 @@ function CommandAutocomplete({ commands, selectedIndex }) {
|
|
|
321
327
|
// src/components/FileAutocomplete.tsx
|
|
322
328
|
import React4 from "react";
|
|
323
329
|
import { Box as Box3, Text as Text4 } from "ink";
|
|
330
|
+
import * as path2 from "path";
|
|
324
331
|
|
|
325
332
|
// src/utils/fileSearch.ts
|
|
326
333
|
import * as fs from "fs";
|
|
327
334
|
import * as path from "path";
|
|
328
|
-
import
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
".
|
|
334
|
-
"
|
|
335
|
-
|
|
336
|
-
".cache",
|
|
337
|
-
"coverage",
|
|
338
|
-
".DS_Store",
|
|
339
|
-
".env",
|
|
340
|
-
".env.local",
|
|
341
|
-
".env.secrets"
|
|
342
|
-
];
|
|
343
|
-
function walkDirectory(basePath = process.cwd(), maxDepth = 5, ignorePatterns = DEFAULT_IGNORE_PATTERNS) {
|
|
344
|
-
const results = [];
|
|
345
|
-
const normalizedBase = path.resolve(basePath);
|
|
346
|
-
function walk(currentPath, depth) {
|
|
347
|
-
if (depth > maxDepth) return;
|
|
335
|
+
import { AsyncFzf } from "fzf";
|
|
336
|
+
import { fdir } from "fdir";
|
|
337
|
+
import ignore from "ignore";
|
|
338
|
+
function loadIgnoreRules(projectRoot) {
|
|
339
|
+
const ig = ignore();
|
|
340
|
+
ig.add(".git/");
|
|
341
|
+
const gitignorePath = path.join(projectRoot, ".gitignore");
|
|
342
|
+
if (fs.existsSync(gitignorePath)) {
|
|
348
343
|
try {
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
if (ignorePatterns.some((pattern) => entry.name === pattern || entry.name.startsWith(pattern))) {
|
|
352
|
-
continue;
|
|
353
|
-
}
|
|
354
|
-
const fullPath = path.join(currentPath, entry.name);
|
|
355
|
-
const relativePath = path.relative(normalizedBase, fullPath);
|
|
356
|
-
if (entry.isDirectory()) {
|
|
357
|
-
results.push({
|
|
358
|
-
path: relativePath,
|
|
359
|
-
isDirectory: true
|
|
360
|
-
});
|
|
361
|
-
walk(fullPath, depth + 1);
|
|
362
|
-
} else if (entry.isFile()) {
|
|
363
|
-
try {
|
|
364
|
-
const stats = fs.statSync(fullPath);
|
|
365
|
-
results.push({
|
|
366
|
-
path: relativePath,
|
|
367
|
-
isDirectory: false,
|
|
368
|
-
size: stats.size
|
|
369
|
-
});
|
|
370
|
-
} catch {
|
|
371
|
-
results.push({
|
|
372
|
-
path: relativePath,
|
|
373
|
-
isDirectory: false
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
344
|
+
const gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
|
|
345
|
+
ig.add(gitignoreContent);
|
|
378
346
|
} catch {
|
|
379
347
|
}
|
|
380
348
|
}
|
|
381
|
-
|
|
382
|
-
|
|
349
|
+
return ig;
|
|
350
|
+
}
|
|
351
|
+
function crawlDirectory(projectRoot, maxDepth = 10, maxFiles = 2e4, ig) {
|
|
352
|
+
let fileCount = 0;
|
|
353
|
+
const crawler = new fdir().withRelativePaths().withDirs().withPathSeparator("/").exclude((dirPath) => {
|
|
354
|
+
const relativePath = path.posix.relative(projectRoot, dirPath);
|
|
355
|
+
if (!relativePath || relativePath === ".") {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
const pathWithSlash = `${relativePath}/`;
|
|
359
|
+
return ig.ignores(pathWithSlash);
|
|
360
|
+
}).filter(() => {
|
|
361
|
+
if (fileCount >= maxFiles) {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
fileCount++;
|
|
365
|
+
return true;
|
|
366
|
+
});
|
|
367
|
+
if (maxDepth !== void 0) {
|
|
368
|
+
crawler.withMaxDepth(maxDepth);
|
|
369
|
+
}
|
|
370
|
+
const paths = crawler.crawl(projectRoot).sync();
|
|
371
|
+
return paths.filter((p) => {
|
|
372
|
+
if (!p || p === ".") {
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
return !ig.ignores(p);
|
|
376
|
+
});
|
|
383
377
|
}
|
|
384
378
|
function formatFileSize(bytes) {
|
|
385
379
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -390,42 +384,112 @@ function formatFileSize(bytes) {
|
|
|
390
384
|
var cachedFiles = null;
|
|
391
385
|
var cacheTimestamp = 0;
|
|
392
386
|
var CACHE_TTL_MS = 3e4;
|
|
393
|
-
function getCachedFiles() {
|
|
387
|
+
function getCachedFiles(projectRoot = process.cwd()) {
|
|
394
388
|
const now = Date.now();
|
|
395
389
|
if (!cachedFiles || now - cacheTimestamp > CACHE_TTL_MS) {
|
|
396
|
-
|
|
390
|
+
const normalizedBase = path.resolve(projectRoot);
|
|
391
|
+
const ig = loadIgnoreRules(normalizedBase);
|
|
392
|
+
cachedFiles = crawlDirectory(normalizedBase, 10, 2e4, ig);
|
|
397
393
|
cacheTimestamp = now;
|
|
398
394
|
}
|
|
399
395
|
return cachedFiles;
|
|
400
396
|
}
|
|
401
|
-
function
|
|
402
|
-
|
|
397
|
+
function warmFileCache() {
|
|
398
|
+
if (!cachedFiles || Date.now() - cacheTimestamp > CACHE_TTL_MS) {
|
|
399
|
+
setImmediate(() => {
|
|
400
|
+
getCachedFiles();
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function listAbsoluteDirectory(absolutePath, filterQuery) {
|
|
405
|
+
try {
|
|
406
|
+
const normalizedPath = path.normalize(absolutePath);
|
|
407
|
+
if (!fs.existsSync(normalizedPath)) {
|
|
408
|
+
return [];
|
|
409
|
+
}
|
|
410
|
+
const stats = fs.statSync(normalizedPath);
|
|
411
|
+
if (!stats.isDirectory()) {
|
|
412
|
+
return [];
|
|
413
|
+
}
|
|
414
|
+
const entries = fs.readdirSync(normalizedPath, { withFileTypes: true });
|
|
415
|
+
let filteredEntries = entries;
|
|
416
|
+
if (filterQuery && filterQuery.length > 0) {
|
|
417
|
+
const lowerQuery = filterQuery.toLowerCase();
|
|
418
|
+
filteredEntries = entries.filter((entry) => entry.name.toLowerCase().includes(lowerQuery));
|
|
419
|
+
}
|
|
420
|
+
return filteredEntries.slice(0, 15).map((entry) => {
|
|
421
|
+
const fullPath = path.join(normalizedPath, entry.name);
|
|
422
|
+
const result = {
|
|
423
|
+
path: fullPath,
|
|
424
|
+
isDirectory: entry.isDirectory()
|
|
425
|
+
};
|
|
426
|
+
if (entry.isFile()) {
|
|
427
|
+
try {
|
|
428
|
+
const fileStats = fs.statSync(fullPath);
|
|
429
|
+
result.size = fileStats.size;
|
|
430
|
+
} catch {
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return result;
|
|
434
|
+
});
|
|
435
|
+
} catch {
|
|
436
|
+
return [];
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
async function searchFiles(query, maxResults = 20) {
|
|
440
|
+
const projectRoot = process.cwd();
|
|
441
|
+
if (path.isAbsolute(query)) {
|
|
442
|
+
try {
|
|
443
|
+
const stats = fs.statSync(query);
|
|
444
|
+
if (stats.isDirectory()) {
|
|
445
|
+
return listAbsoluteDirectory(query);
|
|
446
|
+
}
|
|
447
|
+
} catch {
|
|
448
|
+
}
|
|
449
|
+
let dirToList;
|
|
450
|
+
let filterQuery;
|
|
451
|
+
if (query.endsWith("/") || query.endsWith(path.sep)) {
|
|
452
|
+
dirToList = query;
|
|
453
|
+
filterQuery = void 0;
|
|
454
|
+
} else {
|
|
455
|
+
dirToList = path.dirname(query);
|
|
456
|
+
filterQuery = path.basename(query);
|
|
457
|
+
}
|
|
458
|
+
return listAbsoluteDirectory(dirToList, filterQuery);
|
|
459
|
+
}
|
|
460
|
+
const files = getCachedFiles(projectRoot);
|
|
403
461
|
if (!query || query.trim() === "") {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
462
|
+
const rootFiles = files.filter((f) => !f.includes("/")).slice(0, maxResults);
|
|
463
|
+
return rootFiles.map((p) => {
|
|
464
|
+
const fullPath = path.join(projectRoot, p);
|
|
465
|
+
try {
|
|
466
|
+
const stats = fs.statSync(fullPath);
|
|
467
|
+
return {
|
|
468
|
+
path: p,
|
|
469
|
+
isDirectory: stats.isDirectory(),
|
|
470
|
+
size: stats.isFile() ? stats.size : void 0
|
|
471
|
+
};
|
|
472
|
+
} catch {
|
|
473
|
+
return { path: p, isDirectory: false };
|
|
474
|
+
}
|
|
413
475
|
});
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
476
|
+
}
|
|
477
|
+
const fzf = new AsyncFzf(files);
|
|
478
|
+
const results = await fzf.find(query);
|
|
479
|
+
return results.slice(0, maxResults).map((result) => {
|
|
480
|
+
const p = result.item;
|
|
481
|
+
const fullPath = path.join(projectRoot, p);
|
|
482
|
+
try {
|
|
483
|
+
const stats = fs.statSync(fullPath);
|
|
484
|
+
return {
|
|
485
|
+
path: p,
|
|
486
|
+
isDirectory: stats.isDirectory(),
|
|
487
|
+
size: stats.isFile() ? stats.size : void 0
|
|
488
|
+
};
|
|
489
|
+
} catch {
|
|
490
|
+
return { path: p, isDirectory: false };
|
|
491
|
+
}
|
|
426
492
|
});
|
|
427
|
-
const results = fuse.search(query);
|
|
428
|
-
return results.slice(0, 20).map((result) => result.item);
|
|
429
493
|
}
|
|
430
494
|
function isPathWithinCwd(filePath) {
|
|
431
495
|
const cwd = path.resolve(process.cwd());
|
|
@@ -509,10 +573,27 @@ var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
|
509
573
|
// src/components/FileAutocomplete.tsx
|
|
510
574
|
function FileAutocomplete({ files, selectedIndex, query }) {
|
|
511
575
|
if (files.length === 0) {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
576
|
+
if (path2.isAbsolute(query)) {
|
|
577
|
+
return /* @__PURE__ */ React4.createElement(Box3, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "No items in "), /* @__PURE__ */ React4.createElement(Text4, { color: "cyan" }, query), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, " (or path does not exist)"));
|
|
578
|
+
}
|
|
579
|
+
return /* @__PURE__ */ React4.createElement(Box3, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "No matching files", query ? ` for "${query}"` : " in root directory"));
|
|
580
|
+
}
|
|
581
|
+
const VIEWPORT_SIZE = 6;
|
|
582
|
+
const totalFiles = files.length;
|
|
583
|
+
let startIndex = 0;
|
|
584
|
+
let endIndex = Math.min(VIEWPORT_SIZE, totalFiles);
|
|
585
|
+
if (totalFiles > VIEWPORT_SIZE) {
|
|
586
|
+
const halfViewport = Math.floor(VIEWPORT_SIZE / 2);
|
|
587
|
+
startIndex = Math.max(0, selectedIndex - halfViewport);
|
|
588
|
+
endIndex = Math.min(totalFiles, startIndex + VIEWPORT_SIZE);
|
|
589
|
+
if (endIndex === totalFiles) {
|
|
590
|
+
startIndex = Math.max(0, totalFiles - VIEWPORT_SIZE);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
const visibleFiles = files.slice(startIndex, endIndex);
|
|
594
|
+
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, { marginBottom: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, totalFiles === 1 ? "1 match" : `${totalFiles} matches`, totalFiles > VIEWPORT_SIZE && ` (${selectedIndex + 1}/${totalFiles})`, " - Use up/down to navigate, Tab to select")), visibleFiles.map((file, viewportIndex) => {
|
|
595
|
+
const actualIndex = startIndex + viewportIndex;
|
|
596
|
+
const isSelected = actualIndex === selectedIndex;
|
|
516
597
|
const icon = file.isDirectory ? "[folder]" : "[file] ";
|
|
517
598
|
const sizeDisplay = file.isDirectory ? "" : file.size !== void 0 ? ` (${formatFileSize(file.size)})` : "";
|
|
518
599
|
const pathDisplay = file.isDirectory ? `${file.path}/` : file.path;
|
|
@@ -521,7 +602,7 @@ function FileAutocomplete({ files, selectedIndex, query }) {
|
|
|
521
602
|
}
|
|
522
603
|
|
|
523
604
|
// src/utils/fuzzySearch.ts
|
|
524
|
-
import
|
|
605
|
+
import Fuse from "fuse.js";
|
|
525
606
|
|
|
526
607
|
// src/config/commands.ts
|
|
527
608
|
var COMMANDS = [
|
|
@@ -645,6 +726,42 @@ var COMMANDS = [
|
|
|
645
726
|
name: "compact",
|
|
646
727
|
description: "Compact conversation into new session",
|
|
647
728
|
args: "[instructions]"
|
|
729
|
+
},
|
|
730
|
+
// Sandbox commands
|
|
731
|
+
{
|
|
732
|
+
name: "sandbox",
|
|
733
|
+
description: "Show sandbox status and configuration"
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
name: "sandbox:enable",
|
|
737
|
+
description: "Enable OS-level sandbox for bash commands"
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
name: "sandbox:disable",
|
|
741
|
+
description: "Disable sandbox mode"
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
name: "sandbox:mode",
|
|
745
|
+
description: "Set sandbox mode (auto-allow or permissions)",
|
|
746
|
+
args: "<auto-allow|permissions>"
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
name: "sandbox:trust-domain",
|
|
750
|
+
description: "Add domain(s) to the network proxy allowlist",
|
|
751
|
+
args: "<domain> [...]"
|
|
752
|
+
},
|
|
753
|
+
{
|
|
754
|
+
name: "sandbox:domains",
|
|
755
|
+
description: "Show network proxy allowed domains"
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
name: "sandbox:violations",
|
|
759
|
+
description: "Show recent sandbox violations",
|
|
760
|
+
args: "[count]"
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
name: "sandbox:violations:clear",
|
|
764
|
+
description: "Clear all recorded sandbox violations"
|
|
648
765
|
}
|
|
649
766
|
];
|
|
650
767
|
function getAllCommandNames() {
|
|
@@ -698,13 +815,13 @@ function searchCommands(query, commands = COMMANDS) {
|
|
|
698
815
|
if (!query || query.trim() === "") {
|
|
699
816
|
return commands;
|
|
700
817
|
}
|
|
701
|
-
const fuse = new
|
|
818
|
+
const fuse = new Fuse(commands, fuseOptions);
|
|
702
819
|
const results = fuse.search(query);
|
|
703
820
|
return results.map((result) => result.item);
|
|
704
821
|
}
|
|
705
822
|
|
|
706
823
|
// src/utils/imageDetector.ts
|
|
707
|
-
import { readFileSync, existsSync, statSync as statSync2 } from "fs";
|
|
824
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
708
825
|
import { extname as extname2 } from "path";
|
|
709
826
|
var ImageInputDetector = class {
|
|
710
827
|
static {
|
|
@@ -845,14 +962,14 @@ var ImageInputDetector = class {
|
|
|
845
962
|
let filepath = input.trim();
|
|
846
963
|
filepath = filepath.replace(/^["']|["']$/g, "");
|
|
847
964
|
filepath = filepath.replace(/\\(.)/g, "$1");
|
|
848
|
-
if (!
|
|
965
|
+
if (!existsSync2(filepath)) return null;
|
|
849
966
|
const stats = statSync2(filepath);
|
|
850
967
|
if (!stats.isFile()) return null;
|
|
851
968
|
const ext = extname2(filepath).toLowerCase();
|
|
852
969
|
if (!this.SUPPORTED_EXTENSIONS.includes(ext)) return null;
|
|
853
970
|
if (stats.size > this.MAX_IMAGE_SIZE) return null;
|
|
854
971
|
try {
|
|
855
|
-
const data =
|
|
972
|
+
const data = readFileSync2(filepath);
|
|
856
973
|
const format = ext.substring(1) === "jpeg" ? "jpg" : ext.substring(1);
|
|
857
974
|
const filename = filepath.split("/").pop() || "image";
|
|
858
975
|
return {
|
|
@@ -907,6 +1024,21 @@ var ImageInputDetector = class {
|
|
|
907
1024
|
}
|
|
908
1025
|
};
|
|
909
1026
|
|
|
1027
|
+
// src/hooks/useDebounce.ts
|
|
1028
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
1029
|
+
function useDebounce(value, delay = 300) {
|
|
1030
|
+
const [debouncedValue, setDebouncedValue] = useState2(value);
|
|
1031
|
+
useEffect2(() => {
|
|
1032
|
+
const timer = setTimeout(() => {
|
|
1033
|
+
setDebouncedValue(value);
|
|
1034
|
+
}, delay);
|
|
1035
|
+
return () => {
|
|
1036
|
+
clearTimeout(timer);
|
|
1037
|
+
};
|
|
1038
|
+
}, [value, delay]);
|
|
1039
|
+
return debouncedValue;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
910
1042
|
// src/components/InputPrompt.tsx
|
|
911
1043
|
function looksLikeFilePath(input) {
|
|
912
1044
|
const trimmed = input.trim();
|
|
@@ -947,20 +1079,20 @@ function InputPrompt({
|
|
|
947
1079
|
const value = useCliStore((state) => state.inputValue);
|
|
948
1080
|
const setInputValue = useCliStore((state) => state.setInputValue);
|
|
949
1081
|
const setValue = setInputValue;
|
|
950
|
-
const [selectedIndex, setSelectedIndex] =
|
|
951
|
-
const [historyIndex, setHistoryIndex] =
|
|
952
|
-
const [tempInput, setTempInput] =
|
|
1082
|
+
const [selectedIndex, setSelectedIndex] = useState3(0);
|
|
1083
|
+
const [historyIndex, setHistoryIndex] = useState3(-1);
|
|
1084
|
+
const [tempInput, setTempInput] = useState3("");
|
|
953
1085
|
const inputKey = useRef2(0);
|
|
954
|
-
|
|
1086
|
+
useEffect3(() => {
|
|
955
1087
|
if (prefillInput) {
|
|
956
1088
|
setValue(prefillInput);
|
|
957
1089
|
onPrefillConsumed?.();
|
|
958
1090
|
}
|
|
959
1091
|
}, [prefillInput, onPrefillConsumed]);
|
|
960
|
-
const [fileAutocomplete, setFileAutocomplete] =
|
|
961
|
-
const [fileSelectedIndex, setFileSelectedIndex] =
|
|
1092
|
+
const [fileAutocomplete, setFileAutocomplete] = useState3(null);
|
|
1093
|
+
const [fileSelectedIndex, setFileSelectedIndex] = useState3(0);
|
|
962
1094
|
const isBashMode = value.startsWith("!");
|
|
963
|
-
|
|
1095
|
+
useEffect3(() => {
|
|
964
1096
|
onBashModeChange?.(isBashMode);
|
|
965
1097
|
}, [isBashMode, onBashModeChange]);
|
|
966
1098
|
const shouldShowCommandAutocomplete = value.startsWith("/") && !disabled && !fileAutocomplete?.active && !looksLikeFilePath(value);
|
|
@@ -969,14 +1101,32 @@ function InputPrompt({
|
|
|
969
1101
|
if (!shouldShowCommandAutocomplete) return [];
|
|
970
1102
|
return searchCommands(commandQuery, commands);
|
|
971
1103
|
}, [shouldShowCommandAutocomplete, commandQuery, commands]);
|
|
972
|
-
const
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1104
|
+
const debouncedFileQuery = useDebounce(fileAutocomplete?.query ?? "", 200);
|
|
1105
|
+
const [filteredFiles, setFilteredFiles] = useState3([]);
|
|
1106
|
+
useEffect3(() => {
|
|
1107
|
+
if (!fileAutocomplete?.active) {
|
|
1108
|
+
setFilteredFiles([]);
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
let cancelled = false;
|
|
1112
|
+
searchFiles(debouncedFileQuery).then((results) => {
|
|
1113
|
+
if (!cancelled) {
|
|
1114
|
+
setFilteredFiles(results);
|
|
1115
|
+
}
|
|
1116
|
+
}).catch((error) => {
|
|
1117
|
+
if (!cancelled) {
|
|
1118
|
+
console.error("File search error:", error);
|
|
1119
|
+
setFilteredFiles([]);
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
return () => {
|
|
1123
|
+
cancelled = true;
|
|
1124
|
+
};
|
|
1125
|
+
}, [fileAutocomplete?.active, debouncedFileQuery]);
|
|
1126
|
+
useEffect3(() => {
|
|
977
1127
|
setSelectedIndex(0);
|
|
978
1128
|
}, [filteredCommands]);
|
|
979
|
-
|
|
1129
|
+
useEffect3(() => {
|
|
980
1130
|
setFileSelectedIndex(0);
|
|
981
1131
|
}, [filteredFiles]);
|
|
982
1132
|
useInput2(
|
|
@@ -1214,13 +1364,13 @@ function BackgroundAgentStatus() {
|
|
|
1214
1364
|
}
|
|
1215
1365
|
|
|
1216
1366
|
// src/components/CompletedGroupNotification.tsx
|
|
1217
|
-
import React9, { useEffect as
|
|
1367
|
+
import React9, { useEffect as useEffect4 } from "react";
|
|
1218
1368
|
import { Box as Box8, Text as Text8 } from "ink";
|
|
1219
1369
|
var NOTIFICATION_DISPLAY_DURATION_MS = 3e3;
|
|
1220
1370
|
function CompletedGroupNotification() {
|
|
1221
1371
|
const notifications = useCliStore((state) => state.completedGroupNotifications);
|
|
1222
1372
|
const clearNotifications = useCliStore((state) => state.clearCompletedGroupNotifications);
|
|
1223
|
-
|
|
1373
|
+
useEffect4(() => {
|
|
1224
1374
|
if (notifications.length > 0) {
|
|
1225
1375
|
const timer = setTimeout(() => {
|
|
1226
1376
|
clearNotifications();
|
|
@@ -1233,14 +1383,20 @@ function CompletedGroupNotification() {
|
|
|
1233
1383
|
}
|
|
1234
1384
|
|
|
1235
1385
|
// src/components/PermissionPrompt.tsx
|
|
1236
|
-
import React10, { useState as
|
|
1386
|
+
import React10, { useState as useState4, useCallback } from "react";
|
|
1237
1387
|
import { Box as Box9, Text as Text9, useInput as useInput3 } from "ink";
|
|
1238
1388
|
function renderDiffPreview(preview) {
|
|
1239
1389
|
const lines = preview.split("\n");
|
|
1240
1390
|
return lines.map((line, index) => {
|
|
1391
|
+
if (line.startsWith("\u{1F6D1}")) {
|
|
1392
|
+
return /* @__PURE__ */ React10.createElement(Text9, { key: index, bold: true, color: "red" }, line);
|
|
1393
|
+
}
|
|
1241
1394
|
if (line.startsWith("+")) {
|
|
1242
1395
|
return /* @__PURE__ */ React10.createElement(Text9, { key: index, color: "green" }, line);
|
|
1243
1396
|
}
|
|
1397
|
+
if (line.startsWith("- ")) {
|
|
1398
|
+
return /* @__PURE__ */ React10.createElement(Text9, { key: index, color: "yellow" }, line);
|
|
1399
|
+
}
|
|
1244
1400
|
if (line.startsWith("-")) {
|
|
1245
1401
|
return /* @__PURE__ */ React10.createElement(Text9, { key: index, color: "red" }, line);
|
|
1246
1402
|
}
|
|
@@ -1260,14 +1416,16 @@ function PermissionPrompt({
|
|
|
1260
1416
|
}) {
|
|
1261
1417
|
const items = canBeTrusted ? [
|
|
1262
1418
|
{ label: "\u2713 Allow once", value: "allow-once" },
|
|
1419
|
+
{ label: "\u2713 Allow for this session", value: "allow-session" },
|
|
1263
1420
|
{ label: "\u2713 Always allow (trust this tool)", value: "allow-always" },
|
|
1264
1421
|
{ label: "\u2717 Deny", value: "deny" }
|
|
1265
1422
|
] : [
|
|
1266
1423
|
{ label: "\u2713 Allow once", value: "allow-once" },
|
|
1424
|
+
{ label: "\u2713 Allow for this session", value: "allow-session" },
|
|
1267
1425
|
{ label: "\u2717 Deny", value: "deny" }
|
|
1268
1426
|
];
|
|
1269
|
-
const [selectedIndex, setSelectedIndex] =
|
|
1270
|
-
const [responded, setResponded] =
|
|
1427
|
+
const [selectedIndex, setSelectedIndex] = useState4(0);
|
|
1428
|
+
const [responded, setResponded] = useState4(false);
|
|
1271
1429
|
const handleSelect = useCallback(() => {
|
|
1272
1430
|
if (responded) return;
|
|
1273
1431
|
setResponded(true);
|
|
@@ -1311,11 +1469,15 @@ function PermissionPrompt({
|
|
|
1311
1469
|
const rawArgsString = typeof args === "string" ? args : JSON.stringify(args, null, 2);
|
|
1312
1470
|
const argsString = rawArgsString.length > MAX_ARGS_LENGTH ? rawArgsString.slice(0, MAX_ARGS_LENGTH) + `
|
|
1313
1471
|
... (${rawArgsString.length - MAX_ARGS_LENGTH} more chars)` : rawArgsString;
|
|
1314
|
-
|
|
1472
|
+
const isSandboxBlock = preview?.startsWith("\u{1F6D1}");
|
|
1473
|
+
const borderColor = isSandboxBlock ? "red" : "yellow";
|
|
1474
|
+
const headerColor = isSandboxBlock ? "red" : "yellow";
|
|
1475
|
+
const headerText = isSandboxBlock ? "\u{1F6D1} Sandbox Blocked" : "\u26A0\uFE0F Permission Required";
|
|
1476
|
+
return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", borderStyle: "bold", borderColor, padding: 1, marginY: 1 }, /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: headerColor }, headerText)), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Tool: "), /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: "cyan" }, toolName)), toolDescription && /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Action: "), /* @__PURE__ */ React10.createElement(Text9, null, toolDescription)), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "Arguments:"), /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, argsString))), preview && /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "Preview:"), /* @__PURE__ */ React10.createElement(Box9, { borderStyle: "single", borderColor: "gray", paddingX: 1, flexDirection: "column" }, renderDiffPreview(preview))), !canBeTrusted && /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "red", dimColor: true }, "Note: This tool cannot be trusted due to its dangerous nature.")), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1, flexDirection: "column" }, items.map((item, index) => /* @__PURE__ */ React10.createElement(Box9, { key: item.value }, /* @__PURE__ */ React10.createElement(Text9, { color: "cyan" }, index + 1, "."), /* @__PURE__ */ React10.createElement(Text9, { color: index === selectedIndex ? "cyan" : void 0, bold: index === selectedIndex }, index === selectedIndex ? " \u276F " : " ", item.label)))), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Press 1-", items.length, ", y/n, or \u2191\u2193 + Enter")));
|
|
1315
1477
|
}
|
|
1316
1478
|
|
|
1317
1479
|
// src/components/ConfigEditor.tsx
|
|
1318
|
-
import React11, { useState as
|
|
1480
|
+
import React11, { useState as useState5, useMemo as useMemo3 } from "react";
|
|
1319
1481
|
import { Box as Box10, Text as Text10, useInput as useInput4 } from "ink";
|
|
1320
1482
|
var MAX_ITERATIONS_OPTIONS = [
|
|
1321
1483
|
{ label: "10", value: 10 },
|
|
@@ -1460,10 +1622,10 @@ function buildConfigItems(availableModels) {
|
|
|
1460
1622
|
return items;
|
|
1461
1623
|
}
|
|
1462
1624
|
function ConfigEditor({ config, availableModels, onSave, onClose }) {
|
|
1463
|
-
const [selectedIndex, setSelectedIndex] =
|
|
1464
|
-
const [editedConfig, setEditedConfig] =
|
|
1465
|
-
const [saveError, setSaveError] =
|
|
1466
|
-
const [isSaving, setIsSaving] =
|
|
1625
|
+
const [selectedIndex, setSelectedIndex] = useState5(0);
|
|
1626
|
+
const [editedConfig, setEditedConfig] = useState5(config);
|
|
1627
|
+
const [saveError, setSaveError] = useState5(null);
|
|
1628
|
+
const [isSaving, setIsSaving] = useState5(false);
|
|
1467
1629
|
const configItems = useMemo3(() => buildConfigItems(availableModels), [availableModels]);
|
|
1468
1630
|
const hasChanges = useMemo3(() => {
|
|
1469
1631
|
return JSON.stringify(config.preferences) !== JSON.stringify(editedConfig.preferences) || config.defaultModel !== editedConfig.defaultModel;
|
|
@@ -1744,12 +1906,12 @@ var MessageItem = React14.memo(function MessageItem2({ message }) {
|
|
|
1744
1906
|
return /* @__PURE__ */ React14.createElement(Box13, { key: idx, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React14.createElement(Text13, { color: "yellow" }, "\u{1F527} ", toolName), toolInput && /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, ` Input: ${truncateValue(toolInput, 100)}`), result && /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, ` Result: ${truncateValue(result, 200)}`));
|
|
1745
1907
|
}
|
|
1746
1908
|
return null;
|
|
1747
|
-
}).filter(Boolean)), !isUser && message.content !== "..." && /* @__PURE__ */ React14.createElement(Box13, { paddingLeft: 2, marginBottom: 1 }, message.metadata?.permissionDenied ? /* @__PURE__ */ React14.createElement(Text13, { color: "yellow" }, "\u26A0\uFE0F ", message.content) : /* @__PURE__ */ React14.createElement(MarkdownRenderer, { content: message.content })),
|
|
1909
|
+
}).filter(Boolean)), !isUser && message.content !== "..." && /* @__PURE__ */ React14.createElement(Box13, { paddingLeft: 2, marginBottom: 1 }, message.metadata?.permissionDenied ? /* @__PURE__ */ React14.createElement(Text13, { color: "yellow" }, "\u26A0\uFE0F ", message.content) : /* @__PURE__ */ React14.createElement(MarkdownRenderer, { content: message.content })), !isUser && message.content !== "..." && message.metadata && /* @__PURE__ */ React14.createElement(Box13, { paddingLeft: 2, marginBottom: 1 }, (message.metadata.tokenUsage?.total || message.metadata.creditsUsed) && /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "(", message.metadata.tokenUsage?.total ? `${message.metadata.tokenUsage.total.toLocaleString()} tokens` : "", message.metadata.creditsUsed && message.metadata.creditsUsed > 0 ? (message.metadata.tokenUsage?.total ? " \u2022 " : "") + `used ${message.metadata.creditsUsed.toLocaleString()} ${message.metadata.creditsUsed === 1 ? "credit" : "credits"}` : "", ")")));
|
|
1748
1910
|
});
|
|
1749
1911
|
|
|
1750
1912
|
// src/utils/processFileReferences.ts
|
|
1751
1913
|
import * as fs2 from "fs";
|
|
1752
|
-
import * as
|
|
1914
|
+
import * as path3 from "path";
|
|
1753
1915
|
|
|
1754
1916
|
// src/utils/constants.ts
|
|
1755
1917
|
var NAME_SUFFIXES = ["jr", "sr", "ii", "iii", "iv", "v", "phd", "md", "esq"];
|
|
@@ -1760,7 +1922,7 @@ function isNameSuffix(value) {
|
|
|
1760
1922
|
// src/utils/processFileReferences.ts
|
|
1761
1923
|
var FILE_REFERENCE_REGEX = /(?:^|\s)@([^\s@]+)/g;
|
|
1762
1924
|
function looksLikeFilePath2(ref) {
|
|
1763
|
-
if (ref.includes("/") || ref.includes(
|
|
1925
|
+
if (ref.includes("/") || ref.includes(path3.sep)) {
|
|
1764
1926
|
return true;
|
|
1765
1927
|
}
|
|
1766
1928
|
const extensionMatch = /\.(\w+)$/.exec(ref);
|
|
@@ -1790,9 +1952,13 @@ function extractFileReferences(message) {
|
|
|
1790
1952
|
}
|
|
1791
1953
|
function readFileContents(filePath) {
|
|
1792
1954
|
const cwd = process.cwd();
|
|
1793
|
-
const
|
|
1794
|
-
if (
|
|
1795
|
-
return { error: `Security: Path "${filePath}"
|
|
1955
|
+
const isAbsolutePath = path3.isAbsolute(filePath);
|
|
1956
|
+
if (filePath.includes("..")) {
|
|
1957
|
+
return { error: `Security: Path traversal detected in "${filePath}"` };
|
|
1958
|
+
}
|
|
1959
|
+
const absolutePath = isAbsolutePath ? path3.normalize(filePath) : path3.resolve(cwd, filePath);
|
|
1960
|
+
if (!isAbsolutePath && !isPathWithinCwd(filePath)) {
|
|
1961
|
+
return { error: `Security: Relative path "${filePath}" escapes the current working directory` };
|
|
1796
1962
|
}
|
|
1797
1963
|
if (!fs2.existsSync(absolutePath)) {
|
|
1798
1964
|
return { error: `File not found: "${filePath}"` };
|
|
@@ -1883,6 +2049,7 @@ function App({
|
|
|
1883
2049
|
const sessionName = useCliStore((state) => state.session?.name || "New Session");
|
|
1884
2050
|
const currentModel = useCliStore((state) => state.session?.model || "claude-sonnet-4-5-20250929");
|
|
1885
2051
|
const totalTokens = useCliStore((state) => state.session?.metadata.totalTokens || 0);
|
|
2052
|
+
const totalCredits = useCliStore((state) => state.session?.metadata.totalCredits);
|
|
1886
2053
|
const isThinking = useCliStore((state) => state.isThinking);
|
|
1887
2054
|
const permissionPrompt = useCliStore((state) => state.permissionPrompt);
|
|
1888
2055
|
const showConfigEditor = useCliStore((state) => state.showConfigEditor);
|
|
@@ -1893,7 +2060,7 @@ function App({
|
|
|
1893
2060
|
const setIsThinking = useCliStore((state) => state.setIsThinking);
|
|
1894
2061
|
const pendingBackgroundTrigger = useCliStore((state) => state.pendingBackgroundTrigger);
|
|
1895
2062
|
const setPendingBackgroundTrigger = useCliStore((state) => state.setPendingBackgroundTrigger);
|
|
1896
|
-
|
|
2063
|
+
useEffect5(() => {
|
|
1897
2064
|
if (pendingBackgroundTrigger && !isThinking && onBackgroundCompletion) {
|
|
1898
2065
|
setPendingBackgroundTrigger(false);
|
|
1899
2066
|
onBackgroundCompletion();
|
|
@@ -1905,7 +2072,7 @@ function App({
|
|
|
1905
2072
|
toggleAutoAcceptEdits();
|
|
1906
2073
|
}
|
|
1907
2074
|
});
|
|
1908
|
-
const [isBashMode, setIsBashMode] =
|
|
2075
|
+
const [isBashMode, setIsBashMode] = useState6(false);
|
|
1909
2076
|
const handleSubmit = React15.useCallback(
|
|
1910
2077
|
async (input) => {
|
|
1911
2078
|
const trimmed = input.trim();
|
|
@@ -1968,7 +2135,15 @@ ${errorBlock}`;
|
|
|
1968
2135
|
onPrefillConsumed,
|
|
1969
2136
|
onBashModeChange: setIsBashMode
|
|
1970
2137
|
}
|
|
1971
|
-
)), /* @__PURE__ */ React15.createElement(
|
|
2138
|
+
)), /* @__PURE__ */ React15.createElement(
|
|
2139
|
+
StatusBar,
|
|
2140
|
+
{
|
|
2141
|
+
sessionName,
|
|
2142
|
+
model: currentModel,
|
|
2143
|
+
tokenUsage: totalTokens,
|
|
2144
|
+
creditsUsage: totalCredits
|
|
2145
|
+
}
|
|
2146
|
+
)));
|
|
1972
2147
|
}
|
|
1973
2148
|
|
|
1974
2149
|
// src/components/MessageList.tsx
|
|
@@ -2044,12 +2219,12 @@ function TrustLocationSelector({ inProject, onSelect, onCancel }) {
|
|
|
2044
2219
|
}
|
|
2045
2220
|
|
|
2046
2221
|
// src/components/RewindSelector.tsx
|
|
2047
|
-
import React18, { useState as
|
|
2222
|
+
import React18, { useState as useState7 } from "react";
|
|
2048
2223
|
import { Box as Box17, Text as Text17, useInput as useInput7 } from "ink";
|
|
2049
2224
|
import SelectInput2 from "ink-select-input";
|
|
2050
2225
|
function RewindSelector({ messages, onSelect, onCancel }) {
|
|
2051
|
-
const [step, setStep] =
|
|
2052
|
-
const [selectedMessageIndex, setSelectedMessageIndex] =
|
|
2226
|
+
const [step, setStep] = useState7("selection");
|
|
2227
|
+
const [selectedMessageIndex, setSelectedMessageIndex] = useState7(null);
|
|
2053
2228
|
useInput7((input, key) => {
|
|
2054
2229
|
if (key.escape) {
|
|
2055
2230
|
if (step === "confirmation") {
|
|
@@ -2113,12 +2288,12 @@ function RewindSelector({ messages, onSelect, onCancel }) {
|
|
|
2113
2288
|
}
|
|
2114
2289
|
|
|
2115
2290
|
// src/components/SessionSelector.tsx
|
|
2116
|
-
import React19, { useState as
|
|
2291
|
+
import React19, { useState as useState8 } from "react";
|
|
2117
2292
|
import { Box as Box18, Text as Text18, useInput as useInput8 } from "ink";
|
|
2118
2293
|
import SelectInput3 from "ink-select-input";
|
|
2119
2294
|
function SessionSelector({ sessions, currentSession, onSelect, onCancel }) {
|
|
2120
|
-
const [step, setStep] =
|
|
2121
|
-
const [selectedSession, setSelectedSession] =
|
|
2295
|
+
const [step, setStep] = useState8("selection");
|
|
2296
|
+
const [selectedSession, setSelectedSession] = useState8(null);
|
|
2122
2297
|
useInput8((_input, key) => {
|
|
2123
2298
|
if (key.escape) {
|
|
2124
2299
|
if (step === "confirmation") {
|
|
@@ -2193,7 +2368,7 @@ function SessionSelector({ sessions, currentSession, onSelect, onCancel }) {
|
|
|
2193
2368
|
}
|
|
2194
2369
|
|
|
2195
2370
|
// src/components/LoginFlow.tsx
|
|
2196
|
-
import React20, { useState as
|
|
2371
|
+
import React20, { useState as useState9, useEffect as useEffect6 } from "react";
|
|
2197
2372
|
import { Box as Box19, Text as Text19 } from "ink";
|
|
2198
2373
|
import Spinner3 from "ink-spinner";
|
|
2199
2374
|
import jwt from "jsonwebtoken";
|
|
@@ -2310,11 +2485,11 @@ var OAuthClient = class {
|
|
|
2310
2485
|
|
|
2311
2486
|
// src/components/LoginFlow.tsx
|
|
2312
2487
|
function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, onError }) {
|
|
2313
|
-
const [status, setStatus] =
|
|
2314
|
-
const [deviceFlow, setDeviceFlow] =
|
|
2315
|
-
const [statusMessage, setStatusMessage] =
|
|
2316
|
-
const [error, setError] =
|
|
2317
|
-
|
|
2488
|
+
const [status, setStatus] = useState9("initiating");
|
|
2489
|
+
const [deviceFlow, setDeviceFlow] = useState9(null);
|
|
2490
|
+
const [statusMessage, setStatusMessage] = useState9("Initiating device authorization...");
|
|
2491
|
+
const [error, setError] = useState9(null);
|
|
2492
|
+
useEffect6(() => {
|
|
2318
2493
|
const runLoginFlow = async () => {
|
|
2319
2494
|
const oauth = new OAuthClient(apiUrl);
|
|
2320
2495
|
try {
|
|
@@ -2349,7 +2524,7 @@ function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, o
|
|
|
2349
2524
|
};
|
|
2350
2525
|
runLoginFlow();
|
|
2351
2526
|
}, [apiUrl, configStore, onSuccess, onError]);
|
|
2352
|
-
|
|
2527
|
+
useEffect6(() => {
|
|
2353
2528
|
if (deviceFlow && status === "waiting") {
|
|
2354
2529
|
open(deviceFlow.verification_uri_complete).catch((err) => {
|
|
2355
2530
|
console.error("Failed to auto-open browser:", err);
|
|
@@ -2370,12 +2545,12 @@ function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, o
|
|
|
2370
2545
|
|
|
2371
2546
|
// src/storage/SessionStore.ts
|
|
2372
2547
|
import { promises as fs3 } from "fs";
|
|
2373
|
-
import
|
|
2548
|
+
import path4 from "path";
|
|
2374
2549
|
import { homedir } from "os";
|
|
2375
2550
|
import { v4 as uuidv4 } from "uuid";
|
|
2376
2551
|
var SessionStore = class {
|
|
2377
2552
|
constructor(basePath) {
|
|
2378
|
-
this.basePath = basePath ||
|
|
2553
|
+
this.basePath = basePath || path4.join(homedir(), ".bike4mind", "sessions");
|
|
2379
2554
|
}
|
|
2380
2555
|
/**
|
|
2381
2556
|
* Initialize storage directory
|
|
@@ -2396,7 +2571,7 @@ var SessionStore = class {
|
|
|
2396
2571
|
throw new Error("Cannot save session with no messages");
|
|
2397
2572
|
}
|
|
2398
2573
|
await this.init();
|
|
2399
|
-
const filePath =
|
|
2574
|
+
const filePath = path4.join(this.basePath, `${session.id}.json`);
|
|
2400
2575
|
try {
|
|
2401
2576
|
await fs3.writeFile(filePath, JSON.stringify(session, null, 2), "utf-8");
|
|
2402
2577
|
} catch (error) {
|
|
@@ -2408,7 +2583,7 @@ var SessionStore = class {
|
|
|
2408
2583
|
* Load a session from disk by ID
|
|
2409
2584
|
*/
|
|
2410
2585
|
async load(id) {
|
|
2411
|
-
const filePath =
|
|
2586
|
+
const filePath = path4.join(this.basePath, `${id}.json`);
|
|
2412
2587
|
try {
|
|
2413
2588
|
const data = await fs3.readFile(filePath, "utf-8");
|
|
2414
2589
|
const session = JSON.parse(data);
|
|
@@ -2446,7 +2621,7 @@ var SessionStore = class {
|
|
|
2446
2621
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
2447
2622
|
const sessionsWithFiles = await Promise.all(
|
|
2448
2623
|
jsonFiles.map(async (file) => {
|
|
2449
|
-
const filePath =
|
|
2624
|
+
const filePath = path4.join(this.basePath, file);
|
|
2450
2625
|
const data = await fs3.readFile(filePath, "utf-8");
|
|
2451
2626
|
const session = JSON.parse(data);
|
|
2452
2627
|
session.messages = session.messages.map((msg) => {
|
|
@@ -2481,7 +2656,7 @@ var SessionStore = class {
|
|
|
2481
2656
|
* Delete a session
|
|
2482
2657
|
*/
|
|
2483
2658
|
async delete(id) {
|
|
2484
|
-
const filePath =
|
|
2659
|
+
const filePath = path4.join(this.basePath, `${id}.json`);
|
|
2485
2660
|
try {
|
|
2486
2661
|
await fs3.unlink(filePath);
|
|
2487
2662
|
return true;
|
|
@@ -2520,19 +2695,19 @@ var SessionStore = class {
|
|
|
2520
2695
|
|
|
2521
2696
|
// src/storage/CommandHistoryStore.ts
|
|
2522
2697
|
import { promises as fs4 } from "fs";
|
|
2523
|
-
import
|
|
2698
|
+
import path5 from "path";
|
|
2524
2699
|
import { homedir as homedir2 } from "os";
|
|
2525
2700
|
var MAX_HISTORY_ENTRIES = 1e3;
|
|
2526
2701
|
var CommandHistoryStore = class {
|
|
2527
2702
|
constructor(historyPath) {
|
|
2528
2703
|
this.history = null;
|
|
2529
|
-
this.historyPath = historyPath ||
|
|
2704
|
+
this.historyPath = historyPath || path5.join(homedir2(), ".bike4mind", "history.jsonl");
|
|
2530
2705
|
}
|
|
2531
2706
|
/**
|
|
2532
2707
|
* Initialize history directory
|
|
2533
2708
|
*/
|
|
2534
2709
|
async init() {
|
|
2535
|
-
const dir =
|
|
2710
|
+
const dir = path5.dirname(this.historyPath);
|
|
2536
2711
|
try {
|
|
2537
2712
|
await fs4.mkdir(dir, { recursive: true });
|
|
2538
2713
|
} catch (error) {
|
|
@@ -2635,7 +2810,7 @@ var CommandHistoryStore = class {
|
|
|
2635
2810
|
|
|
2636
2811
|
// src/storage/CustomCommandStore.ts
|
|
2637
2812
|
import fs5 from "fs/promises";
|
|
2638
|
-
import
|
|
2813
|
+
import path6 from "path";
|
|
2639
2814
|
import os from "os";
|
|
2640
2815
|
|
|
2641
2816
|
// src/utils/commandParser.ts
|
|
@@ -2798,14 +2973,14 @@ var CustomCommandStore = class {
|
|
|
2798
2973
|
const home = os.homedir();
|
|
2799
2974
|
const root = projectRoot || process.cwd();
|
|
2800
2975
|
this.globalCommandsDirs = [
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2976
|
+
path6.join(home, ".bike4mind", "commands"),
|
|
2977
|
+
path6.join(home, ".claude", "commands"),
|
|
2978
|
+
path6.join(home, ".claude", "skills")
|
|
2804
2979
|
];
|
|
2805
2980
|
this.projectCommandsDirs = [
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2981
|
+
path6.join(root, ".bike4mind", "commands"),
|
|
2982
|
+
path6.join(root, ".claude", "commands"),
|
|
2983
|
+
path6.join(root, ".claude", "skills")
|
|
2809
2984
|
];
|
|
2810
2985
|
}
|
|
2811
2986
|
/**
|
|
@@ -2865,7 +3040,7 @@ var CustomCommandStore = class {
|
|
|
2865
3040
|
try {
|
|
2866
3041
|
const entries = await fs5.readdir(directory, { withFileTypes: true });
|
|
2867
3042
|
for (const entry of entries) {
|
|
2868
|
-
const fullPath =
|
|
3043
|
+
const fullPath = path6.join(directory, entry.name);
|
|
2869
3044
|
if (entry.isDirectory()) {
|
|
2870
3045
|
const subFiles = await this.findCommandFiles(fullPath);
|
|
2871
3046
|
files.push(...subFiles);
|
|
@@ -2885,7 +3060,7 @@ var CustomCommandStore = class {
|
|
|
2885
3060
|
* @param source - Source identifier ('global' or 'project')
|
|
2886
3061
|
*/
|
|
2887
3062
|
async loadCommandFile(filePath, source) {
|
|
2888
|
-
const filename =
|
|
3063
|
+
const filename = path6.basename(filePath);
|
|
2889
3064
|
const isSkillFile = filename.toLowerCase() === "skill.md";
|
|
2890
3065
|
const commandName = isSkillFile ? this.extractSkillName(filePath) : extractCommandName(filename);
|
|
2891
3066
|
if (!commandName) {
|
|
@@ -2904,7 +3079,7 @@ var CustomCommandStore = class {
|
|
|
2904
3079
|
* @returns Skill name or null if invalid
|
|
2905
3080
|
*/
|
|
2906
3081
|
extractSkillName(filePath) {
|
|
2907
|
-
const parentDir =
|
|
3082
|
+
const parentDir = path6.basename(path6.dirname(filePath));
|
|
2908
3083
|
return parentDir && parentDir !== "skills" ? parentDir : null;
|
|
2909
3084
|
}
|
|
2910
3085
|
/**
|
|
@@ -2966,7 +3141,7 @@ var CustomCommandStore = class {
|
|
|
2966
3141
|
*/
|
|
2967
3142
|
async createCommandFile(name, isGlobal = false) {
|
|
2968
3143
|
const targetDir = isGlobal ? this.globalCommandsDirs[0] : this.projectCommandsDirs[0];
|
|
2969
|
-
const filePath =
|
|
3144
|
+
const filePath = path6.join(targetDir, `${name}.md`);
|
|
2970
3145
|
const fileExists = await fs5.access(filePath).then(
|
|
2971
3146
|
() => true,
|
|
2972
3147
|
() => false
|
|
@@ -3122,6 +3297,7 @@ var ReActAgent = class extends EventEmitter {
|
|
|
3122
3297
|
super();
|
|
3123
3298
|
this.steps = [];
|
|
3124
3299
|
this.totalTokens = 0;
|
|
3300
|
+
this.totalCredits = 0;
|
|
3125
3301
|
this.toolCallCount = 0;
|
|
3126
3302
|
this.observationQueue = [];
|
|
3127
3303
|
this.context = {
|
|
@@ -3141,6 +3317,7 @@ var ReActAgent = class extends EventEmitter {
|
|
|
3141
3317
|
async run(query, options = {}) {
|
|
3142
3318
|
this.steps = [];
|
|
3143
3319
|
this.totalTokens = 0;
|
|
3320
|
+
this.totalCredits = 0;
|
|
3144
3321
|
this.toolCallCount = 0;
|
|
3145
3322
|
const maxIterations = options.maxIterations ?? this.context.maxIterations ?? 5;
|
|
3146
3323
|
const temperature = options.temperature ?? this.context.temperature ?? 0.7;
|
|
@@ -3177,6 +3354,7 @@ ${options.context}` : this.getSystemPrompt()
|
|
|
3177
3354
|
steps: this.steps,
|
|
3178
3355
|
completionInfo: {
|
|
3179
3356
|
totalTokens: this.totalTokens,
|
|
3357
|
+
totalCredits: this.totalCredits > 0 ? this.totalCredits : void 0,
|
|
3180
3358
|
iterations,
|
|
3181
3359
|
toolCalls: this.toolCallCount,
|
|
3182
3360
|
reachedMaxIterations: false
|
|
@@ -3212,6 +3390,9 @@ ${options.context}` : this.getSystemPrompt()
|
|
|
3212
3390
|
const inputTokens = completionInfo.inputTokens || 0;
|
|
3213
3391
|
const outputTokens = completionInfo.outputTokens || 0;
|
|
3214
3392
|
this.totalTokens += inputTokens + outputTokens;
|
|
3393
|
+
if (completionInfo.creditsUsed) {
|
|
3394
|
+
this.totalCredits += completionInfo.creditsUsed;
|
|
3395
|
+
}
|
|
3215
3396
|
if (currentText.trim() && completionInfo.toolsUsed?.length) {
|
|
3216
3397
|
const thoughtStep = {
|
|
3217
3398
|
type: "thought",
|
|
@@ -3325,6 +3506,7 @@ ${options.context}` : this.getSystemPrompt()
|
|
|
3325
3506
|
steps: this.steps,
|
|
3326
3507
|
completionInfo: {
|
|
3327
3508
|
totalTokens: this.totalTokens,
|
|
3509
|
+
totalCredits: this.totalCredits > 0 ? this.totalCredits : void 0,
|
|
3328
3510
|
iterations,
|
|
3329
3511
|
toolCalls: this.toolCallCount,
|
|
3330
3512
|
reachedMaxIterations
|
|
@@ -3342,6 +3524,7 @@ ${options.context}` : this.getSystemPrompt()
|
|
|
3342
3524
|
steps: this.steps,
|
|
3343
3525
|
completionInfo: {
|
|
3344
3526
|
totalTokens: this.totalTokens,
|
|
3527
|
+
totalCredits: this.totalCredits > 0 ? this.totalCredits : void 0,
|
|
3345
3528
|
iterations,
|
|
3346
3529
|
toolCalls: this.toolCallCount,
|
|
3347
3530
|
reachedMaxIterations: false
|
|
@@ -3763,6 +3946,10 @@ EXAMPLES:
|
|
|
3763
3946
|
Remember: Use context from previous messages to understand follow-up questions.${projectContextSection}${skillsSection}`;
|
|
3764
3947
|
}
|
|
3765
3948
|
|
|
3949
|
+
// src/utils/toolsAdapter.ts
|
|
3950
|
+
import { rmSync } from "fs";
|
|
3951
|
+
import path14 from "path";
|
|
3952
|
+
|
|
3766
3953
|
// ../../b4m-core/packages/services/dist/src/referService/generateCodes.js
|
|
3767
3954
|
import { randomBytes } from "crypto";
|
|
3768
3955
|
import range from "lodash/range.js";
|
|
@@ -5889,8 +6076,8 @@ async function processAndStoreImages(images, context) {
|
|
|
5889
6076
|
const buffer = await downloadImage(image);
|
|
5890
6077
|
const fileType = await fileTypeFromBuffer2(buffer);
|
|
5891
6078
|
const filename = `${uuidv45()}.${fileType?.ext}`;
|
|
5892
|
-
const
|
|
5893
|
-
return
|
|
6079
|
+
const path20 = await context.imageGenerateStorage.upload(buffer, filename, {});
|
|
6080
|
+
return path20;
|
|
5894
6081
|
}));
|
|
5895
6082
|
}
|
|
5896
6083
|
async function updateQuestAndReturnMarkdown(storedImageUrls, context) {
|
|
@@ -7216,8 +7403,8 @@ async function processAndStoreImage(imageUrl, context) {
|
|
|
7216
7403
|
const buffer = await downloadImage2(imageUrl);
|
|
7217
7404
|
const fileType = await fileTypeFromBuffer3(buffer);
|
|
7218
7405
|
const filename = `${uuidv46()}.${fileType?.ext}`;
|
|
7219
|
-
const
|
|
7220
|
-
return
|
|
7406
|
+
const path20 = await context.imageGenerateStorage.upload(buffer, filename, {});
|
|
7407
|
+
return path20;
|
|
7221
7408
|
}
|
|
7222
7409
|
async function generateFullMask(imageBuffer) {
|
|
7223
7410
|
const sharp = (await import("sharp")).default;
|
|
@@ -9159,18 +9346,18 @@ var planetVisibilityTool = {
|
|
|
9159
9346
|
|
|
9160
9347
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/fileRead/index.js
|
|
9161
9348
|
import { promises as fs7 } from "fs";
|
|
9162
|
-
import { existsSync as
|
|
9163
|
-
import
|
|
9349
|
+
import { existsSync as existsSync4, statSync as statSync4 } from "fs";
|
|
9350
|
+
import path7 from "path";
|
|
9164
9351
|
var MAX_FILE_SIZE2 = 10 * 1024 * 1024;
|
|
9165
9352
|
async function readFileContent(params) {
|
|
9166
9353
|
const { path: filePath, encoding = "utf-8", offset = 0, limit } = params;
|
|
9167
|
-
const normalizedPath =
|
|
9168
|
-
const resolvedPath =
|
|
9169
|
-
const cwd =
|
|
9354
|
+
const normalizedPath = path7.normalize(filePath);
|
|
9355
|
+
const resolvedPath = path7.resolve(process.cwd(), normalizedPath);
|
|
9356
|
+
const cwd = path7.resolve(process.cwd());
|
|
9170
9357
|
if (!resolvedPath.startsWith(cwd)) {
|
|
9171
9358
|
throw new Error(`Access denied: Cannot read files outside of current working directory`);
|
|
9172
9359
|
}
|
|
9173
|
-
if (!
|
|
9360
|
+
if (!existsSync4(resolvedPath)) {
|
|
9174
9361
|
throw new Error(`File not found: ${filePath}`);
|
|
9175
9362
|
}
|
|
9176
9363
|
const stats = statSync4(resolvedPath);
|
|
@@ -9239,7 +9426,7 @@ var fileReadTool = {
|
|
|
9239
9426
|
context.logger.info("\u{1F4C4} FileRead: Reading file", { path: params.path });
|
|
9240
9427
|
try {
|
|
9241
9428
|
const content = await readFileContent(params);
|
|
9242
|
-
const stats = statSync4(
|
|
9429
|
+
const stats = statSync4(path7.resolve(process.cwd(), path7.normalize(params.path)));
|
|
9243
9430
|
context.logger.info("\u2705 FileRead: Success", {
|
|
9244
9431
|
path: params.path,
|
|
9245
9432
|
size: stats.size,
|
|
@@ -9284,20 +9471,20 @@ var fileReadTool = {
|
|
|
9284
9471
|
|
|
9285
9472
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/createFile/index.js
|
|
9286
9473
|
import { promises as fs8 } from "fs";
|
|
9287
|
-
import { existsSync as
|
|
9288
|
-
import
|
|
9474
|
+
import { existsSync as existsSync5 } from "fs";
|
|
9475
|
+
import path8 from "path";
|
|
9289
9476
|
async function createFile(params) {
|
|
9290
9477
|
const { path: filePath, content, createDirectories = true } = params;
|
|
9291
|
-
const normalizedPath =
|
|
9292
|
-
const resolvedPath =
|
|
9293
|
-
const cwd =
|
|
9478
|
+
const normalizedPath = path8.normalize(filePath);
|
|
9479
|
+
const resolvedPath = path8.resolve(process.cwd(), normalizedPath);
|
|
9480
|
+
const cwd = path8.resolve(process.cwd());
|
|
9294
9481
|
if (!resolvedPath.startsWith(cwd)) {
|
|
9295
9482
|
throw new Error(`Access denied: Cannot create files outside of current working directory`);
|
|
9296
9483
|
}
|
|
9297
|
-
const fileExists =
|
|
9484
|
+
const fileExists = existsSync5(resolvedPath);
|
|
9298
9485
|
const action = fileExists ? "overwritten" : "created";
|
|
9299
9486
|
if (createDirectories) {
|
|
9300
|
-
const dir =
|
|
9487
|
+
const dir = path8.dirname(resolvedPath);
|
|
9301
9488
|
await fs8.mkdir(dir, { recursive: true });
|
|
9302
9489
|
}
|
|
9303
9490
|
await fs8.writeFile(resolvedPath, content, "utf-8");
|
|
@@ -9312,7 +9499,7 @@ var createFileTool = {
|
|
|
9312
9499
|
implementation: (context) => ({
|
|
9313
9500
|
toolFn: async (value) => {
|
|
9314
9501
|
const params = value;
|
|
9315
|
-
const fileExists =
|
|
9502
|
+
const fileExists = existsSync5(path8.resolve(process.cwd(), path8.normalize(params.path)));
|
|
9316
9503
|
context.logger.info(`\u{1F4DD} CreateFile: ${fileExists ? "Overwriting" : "Creating"} file`, {
|
|
9317
9504
|
path: params.path,
|
|
9318
9505
|
size: params.content.length
|
|
@@ -9354,8 +9541,8 @@ var createFileTool = {
|
|
|
9354
9541
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/globFiles/index.js
|
|
9355
9542
|
import { glob } from "glob";
|
|
9356
9543
|
import { stat } from "fs/promises";
|
|
9357
|
-
import
|
|
9358
|
-
var
|
|
9544
|
+
import path9 from "path";
|
|
9545
|
+
var DEFAULT_IGNORE_PATTERNS = [
|
|
9359
9546
|
"**/node_modules/**",
|
|
9360
9547
|
"**/.git/**",
|
|
9361
9548
|
"**/dist/**",
|
|
@@ -9370,11 +9557,11 @@ var DEFAULT_IGNORE_PATTERNS2 = [
|
|
|
9370
9557
|
async function findFiles(params) {
|
|
9371
9558
|
const { pattern, dir_path, case_sensitive = true, respect_git_ignore = true } = params;
|
|
9372
9559
|
const baseCwd = process.cwd();
|
|
9373
|
-
const targetDir = dir_path ?
|
|
9560
|
+
const targetDir = dir_path ? path9.resolve(baseCwd, path9.normalize(dir_path)) : baseCwd;
|
|
9374
9561
|
if (!targetDir.startsWith(baseCwd)) {
|
|
9375
9562
|
throw new Error(`Access denied: Cannot search outside of current working directory`);
|
|
9376
9563
|
}
|
|
9377
|
-
const ignorePatterns = respect_git_ignore ?
|
|
9564
|
+
const ignorePatterns = respect_git_ignore ? DEFAULT_IGNORE_PATTERNS : [];
|
|
9378
9565
|
const matches = await glob(pattern, {
|
|
9379
9566
|
cwd: targetDir,
|
|
9380
9567
|
dot: false,
|
|
@@ -9410,7 +9597,7 @@ async function findFiles(params) {
|
|
|
9410
9597
|
const summary = `Found ${filesWithStats.length} file(s)${truncated ? ` (showing first ${MAX_RESULTS})` : ""} matching: ${pattern}`;
|
|
9411
9598
|
const dirInfo = dir_path ? `
|
|
9412
9599
|
Directory: ${dir_path}` : "";
|
|
9413
|
-
const filesList = results.map((file) =>
|
|
9600
|
+
const filesList = results.map((file) => path9.relative(baseCwd, file.path)).join("\n");
|
|
9414
9601
|
return `${summary}${dirInfo}
|
|
9415
9602
|
|
|
9416
9603
|
${filesList}`;
|
|
@@ -9464,7 +9651,7 @@ var globFilesTool = {
|
|
|
9464
9651
|
|
|
9465
9652
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/grepSearch/index.js
|
|
9466
9653
|
import { stat as stat2 } from "fs/promises";
|
|
9467
|
-
import
|
|
9654
|
+
import path10 from "path";
|
|
9468
9655
|
import { execFile } from "child_process";
|
|
9469
9656
|
import { promisify } from "util";
|
|
9470
9657
|
import { createRequire } from "module";
|
|
@@ -9473,18 +9660,18 @@ var require2 = createRequire(import.meta.url);
|
|
|
9473
9660
|
function getRipgrepPath() {
|
|
9474
9661
|
try {
|
|
9475
9662
|
const ripgrepPath = require2.resolve("@vscode/ripgrep");
|
|
9476
|
-
const ripgrepDir =
|
|
9477
|
-
const rgBinary =
|
|
9663
|
+
const ripgrepDir = path10.dirname(ripgrepPath);
|
|
9664
|
+
const rgBinary = path10.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
|
|
9478
9665
|
return rgBinary;
|
|
9479
9666
|
} catch (error) {
|
|
9480
9667
|
throw new Error("ripgrep is not available. Please install @vscode/ripgrep: pnpm add @vscode/ripgrep --filter @bike4mind/services");
|
|
9481
9668
|
}
|
|
9482
9669
|
}
|
|
9483
9670
|
function isPathWithinWorkspace(targetPath, baseCwd) {
|
|
9484
|
-
const resolvedTarget =
|
|
9485
|
-
const resolvedBase =
|
|
9486
|
-
const relativePath =
|
|
9487
|
-
return !relativePath.startsWith("..") && !
|
|
9671
|
+
const resolvedTarget = path10.resolve(targetPath);
|
|
9672
|
+
const resolvedBase = path10.resolve(baseCwd);
|
|
9673
|
+
const relativePath = path10.relative(resolvedBase, resolvedTarget);
|
|
9674
|
+
return !relativePath.startsWith("..") && !path10.isAbsolute(relativePath);
|
|
9488
9675
|
}
|
|
9489
9676
|
function convertGlobToRipgrepGlobs(globPattern) {
|
|
9490
9677
|
if (!globPattern || globPattern === "**/*") {
|
|
@@ -9500,7 +9687,7 @@ function convertGlobToRipgrepGlobs(globPattern) {
|
|
|
9500
9687
|
async function searchFiles2(params) {
|
|
9501
9688
|
const { pattern, dir_path, include } = params;
|
|
9502
9689
|
const baseCwd = process.cwd();
|
|
9503
|
-
const targetDir = dir_path ?
|
|
9690
|
+
const targetDir = dir_path ? path10.resolve(baseCwd, dir_path) : baseCwd;
|
|
9504
9691
|
if (!isPathWithinWorkspace(targetDir, baseCwd)) {
|
|
9505
9692
|
throw new Error(`Path validation failed: "${dir_path}" resolves outside the allowed workspace directory`);
|
|
9506
9693
|
}
|
|
@@ -9562,7 +9749,7 @@ async function searchFiles2(params) {
|
|
|
9562
9749
|
if (item.type === "match") {
|
|
9563
9750
|
const match = item;
|
|
9564
9751
|
allMatches.push({
|
|
9565
|
-
filePath:
|
|
9752
|
+
filePath: path10.relative(targetDir, match.data.path.text) || path10.basename(match.data.path.text),
|
|
9566
9753
|
lineNumber: match.data.line_number,
|
|
9567
9754
|
line: match.data.lines.text.trimEnd()
|
|
9568
9755
|
// Remove trailing newline
|
|
@@ -9656,17 +9843,17 @@ var grepSearchTool = {
|
|
|
9656
9843
|
|
|
9657
9844
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/deleteFile/index.js
|
|
9658
9845
|
import { promises as fs9 } from "fs";
|
|
9659
|
-
import { existsSync as
|
|
9660
|
-
import
|
|
9846
|
+
import { existsSync as existsSync6, statSync as statSync5 } from "fs";
|
|
9847
|
+
import path11 from "path";
|
|
9661
9848
|
async function deleteFile(params) {
|
|
9662
9849
|
const { path: filePath, recursive = false } = params;
|
|
9663
|
-
const normalizedPath =
|
|
9664
|
-
const resolvedPath =
|
|
9665
|
-
const cwd =
|
|
9850
|
+
const normalizedPath = path11.normalize(filePath);
|
|
9851
|
+
const resolvedPath = path11.resolve(process.cwd(), normalizedPath);
|
|
9852
|
+
const cwd = path11.resolve(process.cwd());
|
|
9666
9853
|
if (!resolvedPath.startsWith(cwd)) {
|
|
9667
9854
|
throw new Error(`Access denied: Cannot delete files outside of current working directory`);
|
|
9668
9855
|
}
|
|
9669
|
-
if (!
|
|
9856
|
+
if (!existsSync6(resolvedPath)) {
|
|
9670
9857
|
throw new Error(`File or directory not found: ${filePath}`);
|
|
9671
9858
|
}
|
|
9672
9859
|
const stats = statSync5(resolvedPath);
|
|
@@ -9689,8 +9876,8 @@ var deleteFileTool = {
|
|
|
9689
9876
|
implementation: (context) => ({
|
|
9690
9877
|
toolFn: async (value) => {
|
|
9691
9878
|
const params = value;
|
|
9692
|
-
const resolvedPath =
|
|
9693
|
-
const isDirectory =
|
|
9879
|
+
const resolvedPath = path11.resolve(process.cwd(), path11.normalize(params.path));
|
|
9880
|
+
const isDirectory = existsSync6(resolvedPath) && statSync5(resolvedPath).isDirectory();
|
|
9694
9881
|
context.logger.info(`\u{1F5D1}\uFE0F DeleteFile: Deleting ${isDirectory ? "directory" : "file"}`, {
|
|
9695
9882
|
path: params.path,
|
|
9696
9883
|
recursive: params.recursive
|
|
@@ -9815,7 +10002,7 @@ var knowledgeBaseSearchTool = {
|
|
|
9815
10002
|
|
|
9816
10003
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/bashExecute/index.js
|
|
9817
10004
|
import { spawn } from "child_process";
|
|
9818
|
-
import
|
|
10005
|
+
import path12 from "path";
|
|
9819
10006
|
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
9820
10007
|
var MAX_OUTPUT_SIZE = 100 * 1024;
|
|
9821
10008
|
var DANGEROUS_PATTERNS = [
|
|
@@ -9999,7 +10186,7 @@ async function executeBashCommand(params) {
|
|
|
9999
10186
|
};
|
|
10000
10187
|
}
|
|
10001
10188
|
const baseCwd = process.cwd();
|
|
10002
|
-
const targetCwd = relativeCwd ?
|
|
10189
|
+
const targetCwd = relativeCwd ? path12.resolve(baseCwd, relativeCwd) : baseCwd;
|
|
10003
10190
|
const effectiveTimeout = Math.min(timeout, 5 * 60 * 1e3);
|
|
10004
10191
|
return new Promise((resolve3) => {
|
|
10005
10192
|
let stdout = "";
|
|
@@ -10190,8 +10377,8 @@ BLOCKED OPERATIONS:
|
|
|
10190
10377
|
|
|
10191
10378
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/editLocalFile/index.js
|
|
10192
10379
|
import { promises as fs10 } from "fs";
|
|
10193
|
-
import { existsSync as
|
|
10194
|
-
import
|
|
10380
|
+
import { existsSync as existsSync7 } from "fs";
|
|
10381
|
+
import path13 from "path";
|
|
10195
10382
|
import { diffLines as diffLines3 } from "diff";
|
|
10196
10383
|
function generateDiff(original, modified) {
|
|
10197
10384
|
const differences = diffLines3(original, modified);
|
|
@@ -10215,13 +10402,13 @@ function generateDiff(original, modified) {
|
|
|
10215
10402
|
}
|
|
10216
10403
|
async function editLocalFile(params) {
|
|
10217
10404
|
const { path: filePath, old_string, new_string } = params;
|
|
10218
|
-
const normalizedPath =
|
|
10219
|
-
const resolvedPath =
|
|
10220
|
-
const cwd =
|
|
10405
|
+
const normalizedPath = path13.normalize(filePath);
|
|
10406
|
+
const resolvedPath = path13.resolve(process.cwd(), normalizedPath);
|
|
10407
|
+
const cwd = path13.resolve(process.cwd());
|
|
10221
10408
|
if (!resolvedPath.startsWith(cwd)) {
|
|
10222
10409
|
throw new Error(`Access denied: Cannot edit files outside of current working directory`);
|
|
10223
10410
|
}
|
|
10224
|
-
if (!
|
|
10411
|
+
if (!existsSync7(resolvedPath)) {
|
|
10225
10412
|
throw new Error(`File not found: ${filePath}`);
|
|
10226
10413
|
}
|
|
10227
10414
|
const currentContent = await fs10.readFile(resolvedPath, "utf-8");
|
|
@@ -11412,10 +11599,10 @@ var ToolErrorType;
|
|
|
11412
11599
|
// src/utils/diffPreview.ts
|
|
11413
11600
|
import * as Diff from "diff";
|
|
11414
11601
|
import { readFile } from "fs/promises";
|
|
11415
|
-
import { existsSync as
|
|
11602
|
+
import { existsSync as existsSync8 } from "fs";
|
|
11416
11603
|
async function generateFileDiffPreview(args) {
|
|
11417
11604
|
try {
|
|
11418
|
-
if (!
|
|
11605
|
+
if (!existsSync8(args.path)) {
|
|
11419
11606
|
const lines2 = args.content.split("\n");
|
|
11420
11607
|
const preview = lines2.slice(0, 20).join("\n");
|
|
11421
11608
|
const hasMore = lines2.length > 20;
|
|
@@ -11453,7 +11640,7 @@ ${diffLines4.join("\n")}`;
|
|
|
11453
11640
|
}
|
|
11454
11641
|
async function generateFileDeletePreview(args) {
|
|
11455
11642
|
try {
|
|
11456
|
-
if (!
|
|
11643
|
+
if (!existsSync8(args.path)) {
|
|
11457
11644
|
return `[File does not exist: ${args.path}]`;
|
|
11458
11645
|
}
|
|
11459
11646
|
const stats = await import("fs/promises").then((fs14) => fs14.stat(args.path));
|
|
@@ -11509,10 +11696,6 @@ var ServerToolExecutor = class {
|
|
|
11509
11696
|
};
|
|
11510
11697
|
|
|
11511
11698
|
// src/llm/ToolRouter.ts
|
|
11512
|
-
var wsToolExecutor = null;
|
|
11513
|
-
function setWebSocketToolExecutor(executor) {
|
|
11514
|
-
wsToolExecutor = executor;
|
|
11515
|
-
}
|
|
11516
11699
|
var SERVER_TOOLS = ["weather_info", "web_search", "web_fetch"];
|
|
11517
11700
|
var LOCAL_TOOLS = [
|
|
11518
11701
|
"file_read",
|
|
@@ -11535,15 +11718,7 @@ function isLocalTool(toolName) {
|
|
|
11535
11718
|
}
|
|
11536
11719
|
async function executeTool(toolName, input, apiClient, localToolFn) {
|
|
11537
11720
|
if (isServerTool(toolName)) {
|
|
11538
|
-
|
|
11539
|
-
logger.debug(`[ToolRouter] Routing ${toolName} to server via WebSocket`);
|
|
11540
|
-
const result = await wsToolExecutor.execute(toolName, input);
|
|
11541
|
-
if (!result.success) {
|
|
11542
|
-
return `Error executing ${toolName}: ${result.error || "Tool execution failed"}`;
|
|
11543
|
-
}
|
|
11544
|
-
return typeof result.content === "string" ? result.content : JSON.stringify(result.content ?? "");
|
|
11545
|
-
}
|
|
11546
|
-
logger.debug(`[ToolRouter] Routing ${toolName} to server via HTTP`);
|
|
11721
|
+
logger.debug(`[ToolRouter] Routing ${toolName} to server`);
|
|
11547
11722
|
const executor = new ServerToolExecutor(apiClient);
|
|
11548
11723
|
return await executor.executeTool(toolName, input);
|
|
11549
11724
|
} else if (isLocalTool(toolName)) {
|
|
@@ -11759,21 +11934,21 @@ var NoOpStorage = class extends BaseStorage {
|
|
|
11759
11934
|
async upload(input, destination, options) {
|
|
11760
11935
|
return `/tmp/${destination}`;
|
|
11761
11936
|
}
|
|
11762
|
-
async download(
|
|
11937
|
+
async download(path20) {
|
|
11763
11938
|
throw new Error("Download not supported in CLI");
|
|
11764
11939
|
}
|
|
11765
|
-
async delete(
|
|
11940
|
+
async delete(path20) {
|
|
11766
11941
|
}
|
|
11767
|
-
async getSignedUrl(
|
|
11768
|
-
return `/tmp/${
|
|
11942
|
+
async getSignedUrl(path20) {
|
|
11943
|
+
return `/tmp/${path20}`;
|
|
11769
11944
|
}
|
|
11770
|
-
getPublicUrl(
|
|
11771
|
-
return `/tmp/${
|
|
11945
|
+
getPublicUrl(path20) {
|
|
11946
|
+
return `/tmp/${path20}`;
|
|
11772
11947
|
}
|
|
11773
|
-
async getPreview(
|
|
11774
|
-
return `/tmp/${
|
|
11948
|
+
async getPreview(path20) {
|
|
11949
|
+
return `/tmp/${path20}`;
|
|
11775
11950
|
}
|
|
11776
|
-
async getMetadata(
|
|
11951
|
+
async getMetadata(path20) {
|
|
11777
11952
|
return { size: 0, contentType: "application/octet-stream" };
|
|
11778
11953
|
}
|
|
11779
11954
|
};
|
|
@@ -11793,87 +11968,179 @@ var CliLogger = class extends Logger {
|
|
|
11793
11968
|
log(...args) {
|
|
11794
11969
|
}
|
|
11795
11970
|
};
|
|
11796
|
-
function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient) {
|
|
11971
|
+
function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, sandboxOrchestrator) {
|
|
11797
11972
|
const originalFn = tool.toolFn;
|
|
11798
11973
|
const toolName = tool.toolSchema.name;
|
|
11799
11974
|
return {
|
|
11800
11975
|
...tool,
|
|
11801
11976
|
toolFn: async (args) => {
|
|
11802
|
-
|
|
11803
|
-
|
|
11804
|
-
|
|
11805
|
-
|
|
11806
|
-
|
|
11807
|
-
|
|
11808
|
-
|
|
11809
|
-
|
|
11810
|
-
|
|
11811
|
-
|
|
11812
|
-
|
|
11813
|
-
|
|
11814
|
-
|
|
11815
|
-
|
|
11816
|
-
preview = generateEditLocalFilePreview({
|
|
11817
|
-
path: args.path,
|
|
11818
|
-
old_string: args.old_string,
|
|
11819
|
-
new_string: args.new_string
|
|
11820
|
-
});
|
|
11821
|
-
} catch (error) {
|
|
11822
|
-
preview = `[Could not generate preview: ${error instanceof Error ? error.message : "Unknown error"}]`;
|
|
11823
|
-
}
|
|
11824
|
-
} else if (toolName === "create_file" && args?.path && args?.content) {
|
|
11825
|
-
try {
|
|
11826
|
-
preview = await generateFileDiffPreview({
|
|
11827
|
-
path: args.path,
|
|
11828
|
-
content: args.content
|
|
11977
|
+
let isSandboxed = false;
|
|
11978
|
+
let sandboxedArgs = args;
|
|
11979
|
+
if (toolName === "bash_execute" && args?.command && sandboxOrchestrator) {
|
|
11980
|
+
const cwd = args.cwd ? path14.resolve(process.cwd(), args.cwd) : process.cwd();
|
|
11981
|
+
const decision = sandboxOrchestrator.shouldSandbox(args.command, cwd);
|
|
11982
|
+
if (decision.type === "blocked") {
|
|
11983
|
+
sandboxOrchestrator.recordBlocked();
|
|
11984
|
+
sandboxOrchestrator.recordViolation({
|
|
11985
|
+
type: "filesystem",
|
|
11986
|
+
command: args.command,
|
|
11987
|
+
blockedBy: "config",
|
|
11988
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
11989
|
+
detail: decision.reason
|
|
11990
|
+
}).catch(() => {
|
|
11829
11991
|
});
|
|
11830
|
-
|
|
11831
|
-
|
|
11992
|
+
console.error(
|
|
11993
|
+
`
|
|
11994
|
+
\x1B[41m\x1B[97m BLOCKED \x1B[0m \x1B[31mSandbox denied this command:\x1B[0m ${decision.reason}
|
|
11995
|
+
`
|
|
11996
|
+
);
|
|
11997
|
+
return `Command blocked by sandbox: ${decision.reason}`;
|
|
11832
11998
|
}
|
|
11833
|
-
|
|
11834
|
-
|
|
11835
|
-
|
|
11836
|
-
|
|
11837
|
-
|
|
11838
|
-
|
|
11839
|
-
|
|
11999
|
+
if (decision.type === "sandbox") {
|
|
12000
|
+
sandboxOrchestrator.recordSandboxed();
|
|
12001
|
+
isSandboxed = true;
|
|
12002
|
+
sandboxedArgs = {
|
|
12003
|
+
...args,
|
|
12004
|
+
command: decision.wrappedCommand.commandString,
|
|
12005
|
+
_sandboxCleanup: decision.wrappedCommand.cleanupPaths
|
|
12006
|
+
};
|
|
12007
|
+
} else if (decision.type === "unsandboxed") {
|
|
12008
|
+
sandboxOrchestrator.recordUnsandboxed();
|
|
11840
12009
|
}
|
|
11841
|
-
} else if (toolName === "bash_execute" && args?.command) {
|
|
11842
|
-
const cwd = args.cwd ? ` (in ${args.cwd})` : "";
|
|
11843
|
-
const timeout = args.timeout ? ` [timeout: ${args.timeout}ms]` : "";
|
|
11844
|
-
preview = `$ ${args.command}${cwd}${timeout}`;
|
|
11845
12010
|
}
|
|
11846
|
-
const
|
|
12011
|
+
const effectiveArgs = isSandboxed ? sandboxedArgs : args;
|
|
12012
|
+
async function executeAndRecord() {
|
|
12013
|
+
let result = await executeTool(toolName, effectiveArgs, apiClient, originalFn);
|
|
12014
|
+
cleanupSandboxFiles(effectiveArgs?._sandboxCleanup);
|
|
12015
|
+
await captureViolations(isSandboxed, result, args?.command, sandboxOrchestrator);
|
|
12016
|
+
result = await retrySandboxFailure(
|
|
12017
|
+
isSandboxed,
|
|
12018
|
+
result,
|
|
12019
|
+
toolName,
|
|
12020
|
+
args,
|
|
12021
|
+
apiClient,
|
|
12022
|
+
originalFn,
|
|
12023
|
+
showPermissionPrompt
|
|
12024
|
+
);
|
|
12025
|
+
agentContext.observationQueue.push({ toolName, result });
|
|
12026
|
+
return result;
|
|
12027
|
+
}
|
|
12028
|
+
if (!permissionManager.needsPermission(toolName, { isSandboxed })) {
|
|
12029
|
+
return executeAndRecord();
|
|
12030
|
+
}
|
|
12031
|
+
const { useCliStore: useCliStore2 } = await import("./store-FU6NDC2W.js");
|
|
12032
|
+
if (useCliStore2.getState().autoAcceptEdits) {
|
|
12033
|
+
return executeAndRecord();
|
|
12034
|
+
}
|
|
12035
|
+
const preview = await generateToolPreview(toolName, args, isSandboxed);
|
|
12036
|
+
const response = await showPermissionPrompt(toolName, effectiveArgs, preview);
|
|
11847
12037
|
if (response.action === "deny") {
|
|
11848
12038
|
throw new PermissionDeniedError(toolName, args);
|
|
11849
12039
|
}
|
|
12040
|
+
if (response.action === "allow-session") {
|
|
12041
|
+
permissionManager.trustToolForSession(toolName);
|
|
12042
|
+
}
|
|
11850
12043
|
if (response.action === "allow-always") {
|
|
11851
|
-
|
|
11852
|
-
if (canTrust) {
|
|
11853
|
-
const projectDir = configStore.getProjectConfigDir();
|
|
11854
|
-
if (projectDir) {
|
|
11855
|
-
try {
|
|
11856
|
-
await configStore.initProjectConfig();
|
|
11857
|
-
const existingLocal = await configStore.loadRawProjectLocalConfig() || {};
|
|
11858
|
-
const updatedLocal = {
|
|
11859
|
-
...existingLocal,
|
|
11860
|
-
trustedTools: [...existingLocal.trustedTools || [], toolName]
|
|
11861
|
-
};
|
|
11862
|
-
await configStore.saveProjectLocalConfig(updatedLocal);
|
|
11863
|
-
} catch (error) {
|
|
11864
|
-
await configStore.trustTool(toolName);
|
|
11865
|
-
}
|
|
11866
|
-
} else {
|
|
11867
|
-
await configStore.trustTool(toolName);
|
|
11868
|
-
}
|
|
11869
|
-
}
|
|
12044
|
+
await persistToolTrust(toolName, permissionManager, configStore);
|
|
11870
12045
|
}
|
|
11871
|
-
|
|
11872
|
-
agentContext.observationQueue.push({ toolName, result });
|
|
11873
|
-
return result;
|
|
12046
|
+
return executeAndRecord();
|
|
11874
12047
|
}
|
|
11875
12048
|
};
|
|
11876
12049
|
}
|
|
12050
|
+
function isSandboxFailure(isSandboxed, result) {
|
|
12051
|
+
if (!isSandboxed) return false;
|
|
12052
|
+
return result.includes("sandbox-exec:") || result.includes("bwrap:") || result.includes("Operation not permitted");
|
|
12053
|
+
}
|
|
12054
|
+
async function retrySandboxFailure(isSandboxed, result, toolName, originalArgs, apiClient, originalFn, showPermissionPrompt) {
|
|
12055
|
+
if (!isSandboxFailure(isSandboxed, result)) return result;
|
|
12056
|
+
const errorSnippet = result.slice(0, 200);
|
|
12057
|
+
const retryResponse = await showPermissionPrompt(
|
|
12058
|
+
toolName,
|
|
12059
|
+
originalArgs,
|
|
12060
|
+
`\u{1F6D1} SANDBOX BLOCKED \u2014 This command was denied by the OS sandbox.
|
|
12061
|
+
|
|
12062
|
+
- The sandbox prevented this operation because it violates filesystem restrictions.
|
|
12063
|
+
- You can retry without the sandbox, but the command will run with full system access.
|
|
12064
|
+
|
|
12065
|
+
@@Error Details@@
|
|
12066
|
+
${errorSnippet}`
|
|
12067
|
+
);
|
|
12068
|
+
if (retryResponse.action !== "deny") {
|
|
12069
|
+
return executeTool(toolName, originalArgs, apiClient, originalFn);
|
|
12070
|
+
}
|
|
12071
|
+
return result;
|
|
12072
|
+
}
|
|
12073
|
+
function cleanupSandboxFiles(paths) {
|
|
12074
|
+
if (!paths || paths.length === 0) return;
|
|
12075
|
+
for (const p of paths) {
|
|
12076
|
+
try {
|
|
12077
|
+
rmSync(p, { recursive: true, force: true });
|
|
12078
|
+
} catch {
|
|
12079
|
+
}
|
|
12080
|
+
}
|
|
12081
|
+
}
|
|
12082
|
+
async function captureViolations(isSandboxed, result, command, orchestrator) {
|
|
12083
|
+
if (!isSandboxed || !orchestrator || !command) return;
|
|
12084
|
+
const { parseSandboxStderr, toSandboxViolations } = await import("./StderrViolationParser-7OYPM2DJ.js");
|
|
12085
|
+
const parsed = parseSandboxStderr(result);
|
|
12086
|
+
if (parsed.length > 0) {
|
|
12087
|
+
const violations = toSandboxViolations(parsed, command);
|
|
12088
|
+
for (const v of violations) {
|
|
12089
|
+
await orchestrator.recordViolation(v).catch(() => {
|
|
12090
|
+
});
|
|
12091
|
+
}
|
|
12092
|
+
}
|
|
12093
|
+
}
|
|
12094
|
+
async function generateToolPreview(toolName, args, isSandboxed) {
|
|
12095
|
+
try {
|
|
12096
|
+
if (toolName === "edit_local_file" && args?.path && args?.old_string && typeof args?.new_string === "string") {
|
|
12097
|
+
return generateEditLocalFilePreview({
|
|
12098
|
+
path: args.path,
|
|
12099
|
+
old_string: args.old_string,
|
|
12100
|
+
new_string: args.new_string
|
|
12101
|
+
});
|
|
12102
|
+
}
|
|
12103
|
+
if (toolName === "create_file" && args?.path && args?.content) {
|
|
12104
|
+
return await generateFileDiffPreview({
|
|
12105
|
+
path: args.path,
|
|
12106
|
+
content: args.content
|
|
12107
|
+
});
|
|
12108
|
+
}
|
|
12109
|
+
if (toolName === "delete_file" && args?.path) {
|
|
12110
|
+
return await generateFileDeletePreview({
|
|
12111
|
+
path: args.path
|
|
12112
|
+
});
|
|
12113
|
+
}
|
|
12114
|
+
if (toolName === "bash_execute" && args?.command) {
|
|
12115
|
+
const cwd = args.cwd ? ` (in ${args.cwd})` : "";
|
|
12116
|
+
const timeout = args.timeout ? ` [timeout: ${args.timeout}ms]` : "";
|
|
12117
|
+
const sandboxLabel = isSandboxed ? " [sandboxed]" : "";
|
|
12118
|
+
return `$ ${args.command}${cwd}${timeout}${sandboxLabel}`;
|
|
12119
|
+
}
|
|
12120
|
+
} catch (error) {
|
|
12121
|
+
return `[Could not generate preview: ${error instanceof Error ? error.message : "Unknown error"}]`;
|
|
12122
|
+
}
|
|
12123
|
+
return void 0;
|
|
12124
|
+
}
|
|
12125
|
+
async function persistToolTrust(toolName, permissionManager, configStore) {
|
|
12126
|
+
const canTrust = permissionManager.trustTool(toolName);
|
|
12127
|
+
if (!canTrust) return;
|
|
12128
|
+
const projectDir = configStore.getProjectConfigDir();
|
|
12129
|
+
if (projectDir) {
|
|
12130
|
+
try {
|
|
12131
|
+
await configStore.initProjectConfig();
|
|
12132
|
+
const existingLocal = await configStore.loadRawProjectLocalConfig() || {};
|
|
12133
|
+
await configStore.saveProjectLocalConfig({
|
|
12134
|
+
...existingLocal,
|
|
12135
|
+
trustedTools: [...existingLocal.trustedTools || [], toolName]
|
|
12136
|
+
});
|
|
12137
|
+
} catch {
|
|
12138
|
+
await configStore.trustTool(toolName);
|
|
12139
|
+
}
|
|
12140
|
+
} else {
|
|
12141
|
+
await configStore.trustTool(toolName);
|
|
12142
|
+
}
|
|
12143
|
+
}
|
|
11877
12144
|
function wrapToolWithHooks(tool, hooks, hookContext) {
|
|
11878
12145
|
const hasToolHooks = hooks?.PreToolUse || hooks?.PostToolUse || hooks?.PostToolUseFailure;
|
|
11879
12146
|
if (!hasToolHooks) {
|
|
@@ -11967,7 +12234,7 @@ function normalizeToolName(toolName) {
|
|
|
11967
12234
|
}
|
|
11968
12235
|
return TOOL_NAME_MAPPING[toolName] || toolName;
|
|
11969
12236
|
}
|
|
11970
|
-
function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter) {
|
|
12237
|
+
function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter, sandboxOrchestrator) {
|
|
11971
12238
|
const logger2 = new CliLogger();
|
|
11972
12239
|
const storage = new NoOpStorage();
|
|
11973
12240
|
const user = {
|
|
@@ -12027,7 +12294,15 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
|
|
|
12027
12294
|
tools_to_generate
|
|
12028
12295
|
);
|
|
12029
12296
|
let tools = Object.entries(toolsMap).map(
|
|
12030
|
-
([_, tool]) => wrapToolWithPermission(
|
|
12297
|
+
([_, tool]) => wrapToolWithPermission(
|
|
12298
|
+
tool,
|
|
12299
|
+
permissionManager,
|
|
12300
|
+
showPermissionPrompt,
|
|
12301
|
+
agentContext,
|
|
12302
|
+
configStore,
|
|
12303
|
+
apiClient,
|
|
12304
|
+
sandboxOrchestrator
|
|
12305
|
+
)
|
|
12031
12306
|
);
|
|
12032
12307
|
if (toolFilter) {
|
|
12033
12308
|
const { allowedTools, deniedTools } = toolFilter;
|
|
@@ -12051,11 +12326,23 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
|
|
|
12051
12326
|
var PermissionManager = class {
|
|
12052
12327
|
constructor(trustedTools = [], customCategories, deniedTools) {
|
|
12053
12328
|
this.trustedTools = /* @__PURE__ */ new Set();
|
|
12329
|
+
this.sessionTrustedTools = /* @__PURE__ */ new Set();
|
|
12054
12330
|
this.deniedTools = /* @__PURE__ */ new Set();
|
|
12331
|
+
this._sandboxMode = "disabled";
|
|
12332
|
+
this._sandboxActive = false;
|
|
12055
12333
|
this.trustedTools = new Set(trustedTools);
|
|
12056
12334
|
this.customCategories = new Map(Object.entries(customCategories || {}));
|
|
12057
12335
|
this.deniedTools = new Set(deniedTools || []);
|
|
12058
12336
|
}
|
|
12337
|
+
/** Update sandbox state from the orchestrator */
|
|
12338
|
+
setSandboxState(mode, active) {
|
|
12339
|
+
this._sandboxMode = mode;
|
|
12340
|
+
this._sandboxActive = active;
|
|
12341
|
+
}
|
|
12342
|
+
/** Check if sandbox auto-allow is active */
|
|
12343
|
+
isSandboxAutoAllow() {
|
|
12344
|
+
return this._sandboxMode === "auto-allow" && this._sandboxActive;
|
|
12345
|
+
}
|
|
12059
12346
|
/**
|
|
12060
12347
|
* Check if a tool needs permission before execution
|
|
12061
12348
|
*
|
|
@@ -12066,8 +12353,13 @@ var PermissionManager = class {
|
|
|
12066
12353
|
*
|
|
12067
12354
|
* Note: prompt_always tools CANNOT be trusted, so they always need permission
|
|
12068
12355
|
* Note: denied tools from project config ALWAYS need permission (cannot be overridden locally)
|
|
12356
|
+
*
|
|
12357
|
+
* @param toolName - The tool being checked
|
|
12358
|
+
* @param options.isSandboxed - If true, the command will run inside the OS-level sandbox.
|
|
12359
|
+
* In auto-allow mode, sandboxed bash_execute commands skip the permission prompt
|
|
12360
|
+
* because the sandbox provides the security boundary.
|
|
12069
12361
|
*/
|
|
12070
|
-
needsPermission(toolName) {
|
|
12362
|
+
needsPermission(toolName, options) {
|
|
12071
12363
|
const categoryMap = Object.fromEntries(this.customCategories);
|
|
12072
12364
|
const category = getToolCategory(toolName, categoryMap);
|
|
12073
12365
|
if (this.deniedTools.has(toolName)) {
|
|
@@ -12076,6 +12368,12 @@ var PermissionManager = class {
|
|
|
12076
12368
|
if (category === "auto_approve") {
|
|
12077
12369
|
return false;
|
|
12078
12370
|
}
|
|
12371
|
+
if (options?.isSandboxed && toolName === "bash_execute" && this.isSandboxAutoAllow()) {
|
|
12372
|
+
return false;
|
|
12373
|
+
}
|
|
12374
|
+
if (this.sessionTrustedTools.has(toolName)) {
|
|
12375
|
+
return false;
|
|
12376
|
+
}
|
|
12079
12377
|
if (category === "prompt_always") {
|
|
12080
12378
|
return true;
|
|
12081
12379
|
}
|
|
@@ -12143,6 +12441,29 @@ var PermissionManager = class {
|
|
|
12143
12441
|
getDeniedTools() {
|
|
12144
12442
|
return Array.from(this.deniedTools).sort();
|
|
12145
12443
|
}
|
|
12444
|
+
/**
|
|
12445
|
+
* Trust a tool for the current session only (in-memory, no persistence)
|
|
12446
|
+
* Works for all tool categories including prompt_always, but NOT project-denied tools
|
|
12447
|
+
*/
|
|
12448
|
+
trustToolForSession(toolName) {
|
|
12449
|
+
if (this.deniedTools.has(toolName)) {
|
|
12450
|
+
return false;
|
|
12451
|
+
}
|
|
12452
|
+
this.sessionTrustedTools.add(toolName);
|
|
12453
|
+
return true;
|
|
12454
|
+
}
|
|
12455
|
+
/**
|
|
12456
|
+
* Check if a tool is trusted for the current session
|
|
12457
|
+
*/
|
|
12458
|
+
isSessionTrusted(toolName) {
|
|
12459
|
+
return this.sessionTrustedTools.has(toolName);
|
|
12460
|
+
}
|
|
12461
|
+
/**
|
|
12462
|
+
* Clear all session-scoped trust (called on exit or session reset)
|
|
12463
|
+
*/
|
|
12464
|
+
clearSessionTrust() {
|
|
12465
|
+
this.sessionTrustedTools.clear();
|
|
12466
|
+
}
|
|
12146
12467
|
/**
|
|
12147
12468
|
* Clear all trusted tools
|
|
12148
12469
|
*/
|
|
@@ -12180,7 +12501,7 @@ function getEnvironmentName(configApiConfig) {
|
|
|
12180
12501
|
|
|
12181
12502
|
// src/utils/contextLoader.ts
|
|
12182
12503
|
import * as fs11 from "fs";
|
|
12183
|
-
import * as
|
|
12504
|
+
import * as path15 from "path";
|
|
12184
12505
|
import { homedir as homedir3 } from "os";
|
|
12185
12506
|
var CONTEXT_FILE_SIZE_LIMIT = 100 * 1024;
|
|
12186
12507
|
var PROJECT_CONTEXT_FILES = [
|
|
@@ -12201,7 +12522,7 @@ function formatFileSize2(bytes) {
|
|
|
12201
12522
|
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
12202
12523
|
}
|
|
12203
12524
|
function tryReadContextFile(dir, filename, source) {
|
|
12204
|
-
const filePath =
|
|
12525
|
+
const filePath = path15.join(dir, filename);
|
|
12205
12526
|
try {
|
|
12206
12527
|
const stats = fs11.lstatSync(filePath);
|
|
12207
12528
|
if (stats.isDirectory()) {
|
|
@@ -12269,7 +12590,7 @@ ${project.content}`;
|
|
|
12269
12590
|
}
|
|
12270
12591
|
async function loadContextFiles(projectDir) {
|
|
12271
12592
|
const errors = [];
|
|
12272
|
-
const globalDir =
|
|
12593
|
+
const globalDir = path15.join(homedir3(), ".bike4mind");
|
|
12273
12594
|
const projectDirectory = projectDir || process.cwd();
|
|
12274
12595
|
const [globalResult, projectResult] = await Promise.all([
|
|
12275
12596
|
Promise.resolve(findContextFile(globalDir, GLOBAL_CONTEXT_FILES, "global")),
|
|
@@ -12540,8 +12861,8 @@ function substituteArguments(template, args) {
|
|
|
12540
12861
|
// ../../b4m-core/packages/mcp/dist/src/client.js
|
|
12541
12862
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
12542
12863
|
import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
|
|
12543
|
-
import
|
|
12544
|
-
import { existsSync as
|
|
12864
|
+
import path16 from "path";
|
|
12865
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3 } from "fs";
|
|
12545
12866
|
var MCPClient = class {
|
|
12546
12867
|
// Note: This class handles MCP server communication with repository filtering
|
|
12547
12868
|
mcp;
|
|
@@ -12582,18 +12903,18 @@ var MCPClient = class {
|
|
|
12582
12903
|
const root = process.env.INIT_CWD || process.cwd();
|
|
12583
12904
|
const candidatePaths = [
|
|
12584
12905
|
// When running from SST Lambda with node_modules structure (copyFiles)
|
|
12585
|
-
|
|
12906
|
+
path16.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
|
|
12586
12907
|
// When running from SST Lambda deployed environment (/var/task)
|
|
12587
|
-
|
|
12908
|
+
path16.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
12588
12909
|
// When running from SST Lambda (.sst/artifacts/mcpHandler-dev), navigate to monorepo root (3 levels up)
|
|
12589
|
-
|
|
12910
|
+
path16.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
12590
12911
|
// When running from packages/client (Next.js app), navigate to monorepo root (2 levels up)
|
|
12591
|
-
|
|
12912
|
+
path16.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
12592
12913
|
// Original paths (backward compatibility)
|
|
12593
|
-
|
|
12594
|
-
|
|
12914
|
+
path16.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
12915
|
+
path16.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
|
|
12595
12916
|
];
|
|
12596
|
-
const serverScriptPath = candidatePaths.find((p) =>
|
|
12917
|
+
const serverScriptPath = candidatePaths.find((p) => existsSync9(p));
|
|
12597
12918
|
if (!serverScriptPath) {
|
|
12598
12919
|
const getDirectories = (source) => readdirSync3(source, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
|
|
12599
12920
|
console.error(`[MCP] Server script not found. Tried paths:`, candidatePaths);
|
|
@@ -13153,6 +13474,14 @@ ${totalText}`);
|
|
|
13153
13474
|
function stripThinkingBlocks(text) {
|
|
13154
13475
|
return text.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
|
|
13155
13476
|
}
|
|
13477
|
+
function extractUsageInfo(parsed) {
|
|
13478
|
+
return {
|
|
13479
|
+
inputTokens: parsed.usage?.inputTokens,
|
|
13480
|
+
outputTokens: parsed.usage?.outputTokens,
|
|
13481
|
+
creditsUsed: parsed.credits?.used,
|
|
13482
|
+
usdCost: parsed.credits?.usdCost
|
|
13483
|
+
};
|
|
13484
|
+
}
|
|
13156
13485
|
var ServerLlmBackend = class {
|
|
13157
13486
|
constructor(options) {
|
|
13158
13487
|
this.completionsEndpoint = "/api/ai/v1/completions";
|
|
@@ -13304,11 +13633,8 @@ var ServerLlmBackend = class {
|
|
|
13304
13633
|
if (parsed.type === "content") {
|
|
13305
13634
|
const textChunk = parsed.text || "";
|
|
13306
13635
|
accumulatedText += textChunk;
|
|
13307
|
-
if (parsed.usage) {
|
|
13308
|
-
lastUsageInfo =
|
|
13309
|
-
inputTokens: parsed.usage.inputTokens,
|
|
13310
|
-
outputTokens: parsed.usage.outputTokens
|
|
13311
|
-
};
|
|
13636
|
+
if (parsed.usage || parsed.credits) {
|
|
13637
|
+
lastUsageInfo = extractUsageInfo(parsed);
|
|
13312
13638
|
}
|
|
13313
13639
|
streamLogger.onContent(eventCount, textChunk, accumulatedText);
|
|
13314
13640
|
} else if (parsed.type === "tool_use") {
|
|
@@ -13335,11 +13661,8 @@ var ServerLlmBackend = class {
|
|
|
13335
13661
|
thinkingBlocks = parsed.thinking;
|
|
13336
13662
|
streamLogger.onCriticalEvent(eventCount, "THINKING", `${thinkingBlocks.length} thinking blocks`);
|
|
13337
13663
|
}
|
|
13338
|
-
if (parsed.usage) {
|
|
13339
|
-
lastUsageInfo =
|
|
13340
|
-
inputTokens: parsed.usage.inputTokens,
|
|
13341
|
-
outputTokens: parsed.usage.outputTokens
|
|
13342
|
-
};
|
|
13664
|
+
if (parsed.usage || parsed.credits) {
|
|
13665
|
+
lastUsageInfo = extractUsageInfo(parsed);
|
|
13343
13666
|
}
|
|
13344
13667
|
}
|
|
13345
13668
|
} catch (parseError) {
|
|
@@ -13473,173 +13796,6 @@ var ServerLlmBackend = class {
|
|
|
13473
13796
|
}
|
|
13474
13797
|
};
|
|
13475
13798
|
|
|
13476
|
-
// src/llm/WebSocketLlmBackend.ts
|
|
13477
|
-
import { v4 as uuidv411 } from "uuid";
|
|
13478
|
-
function stripThinkingBlocks2(text) {
|
|
13479
|
-
return text.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
|
|
13480
|
-
}
|
|
13481
|
-
var WebSocketLlmBackend = class {
|
|
13482
|
-
constructor(options) {
|
|
13483
|
-
this.wsManager = options.wsManager;
|
|
13484
|
-
this.apiClient = options.apiClient;
|
|
13485
|
-
this.currentModel = options.model;
|
|
13486
|
-
this.tokenGetter = options.tokenGetter;
|
|
13487
|
-
this.wsCompletionUrl = options.wsCompletionUrl;
|
|
13488
|
-
}
|
|
13489
|
-
/**
|
|
13490
|
-
* Send completion request via HTTP POST, receive streaming response via WebSocket.
|
|
13491
|
-
* Collects all streamed chunks, then calls callback once at completion
|
|
13492
|
-
* with the full accumulated content.
|
|
13493
|
-
*/
|
|
13494
|
-
async complete(model, messages, options, callback) {
|
|
13495
|
-
logger.debug(`[WebSocketLlmBackend] Starting complete() with model: ${model}`);
|
|
13496
|
-
if (options.abortSignal?.aborted) {
|
|
13497
|
-
logger.debug("[WebSocketLlmBackend] Request aborted before start");
|
|
13498
|
-
return;
|
|
13499
|
-
}
|
|
13500
|
-
if (!this.wsManager.isConnected) {
|
|
13501
|
-
throw new Error("WebSocket is not connected");
|
|
13502
|
-
}
|
|
13503
|
-
const requestId = uuidv411();
|
|
13504
|
-
return new Promise((resolve3, reject) => {
|
|
13505
|
-
const isVerbose = process.env.B4M_VERBOSE === "1";
|
|
13506
|
-
const isUltraVerbose = process.env.B4M_DEBUG_STREAM === "1";
|
|
13507
|
-
const streamLogger = new StreamLogger(logger, "WebSocketLlmBackend", isVerbose, isUltraVerbose);
|
|
13508
|
-
streamLogger.streamStart();
|
|
13509
|
-
let eventCount = 0;
|
|
13510
|
-
let accumulatedText = "";
|
|
13511
|
-
let lastUsageInfo = {};
|
|
13512
|
-
let toolsUsed = [];
|
|
13513
|
-
let thinkingBlocks = [];
|
|
13514
|
-
let settled = false;
|
|
13515
|
-
const settle = (action) => {
|
|
13516
|
-
if (settled) return;
|
|
13517
|
-
settled = true;
|
|
13518
|
-
this.wsManager.offRequest(requestId);
|
|
13519
|
-
this.wsManager.offDisconnect(onDisconnect);
|
|
13520
|
-
action();
|
|
13521
|
-
};
|
|
13522
|
-
const settleResolve = () => settle(() => resolve3());
|
|
13523
|
-
const settleReject = (err) => settle(() => reject(err));
|
|
13524
|
-
const onDisconnect = () => {
|
|
13525
|
-
logger.debug("[WebSocketLlmBackend] Connection dropped during completion");
|
|
13526
|
-
settleReject(new Error("WebSocket connection lost during completion"));
|
|
13527
|
-
};
|
|
13528
|
-
this.wsManager.onDisconnect(onDisconnect);
|
|
13529
|
-
if (options.abortSignal) {
|
|
13530
|
-
if (options.abortSignal.aborted) {
|
|
13531
|
-
settleResolve();
|
|
13532
|
-
return;
|
|
13533
|
-
}
|
|
13534
|
-
options.abortSignal.addEventListener(
|
|
13535
|
-
"abort",
|
|
13536
|
-
() => {
|
|
13537
|
-
logger.debug("[WebSocketLlmBackend] Abort signal received");
|
|
13538
|
-
settleResolve();
|
|
13539
|
-
},
|
|
13540
|
-
{ once: true }
|
|
13541
|
-
);
|
|
13542
|
-
}
|
|
13543
|
-
const updateUsage = (usage) => {
|
|
13544
|
-
if (usage) {
|
|
13545
|
-
lastUsageInfo = { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens };
|
|
13546
|
-
}
|
|
13547
|
-
};
|
|
13548
|
-
this.wsManager.onRequest(requestId, (message) => {
|
|
13549
|
-
if (options.abortSignal?.aborted) return;
|
|
13550
|
-
const action = message.action;
|
|
13551
|
-
if (action === "cli_completion_chunk") {
|
|
13552
|
-
eventCount++;
|
|
13553
|
-
const chunk = message.chunk;
|
|
13554
|
-
streamLogger.onEvent(eventCount, JSON.stringify(chunk));
|
|
13555
|
-
const textChunk = chunk.text || "";
|
|
13556
|
-
if (textChunk) accumulatedText += textChunk;
|
|
13557
|
-
updateUsage(chunk.usage);
|
|
13558
|
-
if (chunk.type === "content") {
|
|
13559
|
-
streamLogger.onContent(eventCount, textChunk, accumulatedText);
|
|
13560
|
-
} else if (chunk.type === "tool_use") {
|
|
13561
|
-
streamLogger.onCriticalEvent(eventCount, "TOOL_USE", `tools: ${chunk.tools?.length}`);
|
|
13562
|
-
if (chunk.tools && chunk.tools.length > 0) toolsUsed = chunk.tools;
|
|
13563
|
-
if (chunk.thinking && chunk.thinking.length > 0) thinkingBlocks = chunk.thinking;
|
|
13564
|
-
}
|
|
13565
|
-
} else if (action === "cli_completion_done") {
|
|
13566
|
-
streamLogger.streamComplete(accumulatedText);
|
|
13567
|
-
const cleanedText = stripThinkingBlocks2(accumulatedText);
|
|
13568
|
-
if (!cleanedText && toolsUsed.length === 0) {
|
|
13569
|
-
settleResolve();
|
|
13570
|
-
return;
|
|
13571
|
-
}
|
|
13572
|
-
const info = {
|
|
13573
|
-
...lastUsageInfo,
|
|
13574
|
-
...toolsUsed.length > 0 && { toolsUsed },
|
|
13575
|
-
...thinkingBlocks.length > 0 && { thinking: thinkingBlocks }
|
|
13576
|
-
};
|
|
13577
|
-
callback([cleanedText], info).then(() => settleResolve()).catch((err) => settleReject(err));
|
|
13578
|
-
} else if (action === "cli_completion_error") {
|
|
13579
|
-
const errorMsg = message.error || "Server error";
|
|
13580
|
-
streamLogger.onCriticalEvent(eventCount, "ERROR", errorMsg);
|
|
13581
|
-
settleReject(new Error(errorMsg));
|
|
13582
|
-
}
|
|
13583
|
-
});
|
|
13584
|
-
const axiosInstance = this.apiClient.getAxiosInstance();
|
|
13585
|
-
axiosInstance.post(
|
|
13586
|
-
this.wsCompletionUrl,
|
|
13587
|
-
{
|
|
13588
|
-
requestId,
|
|
13589
|
-
model,
|
|
13590
|
-
messages,
|
|
13591
|
-
options: {
|
|
13592
|
-
temperature: options.temperature,
|
|
13593
|
-
maxTokens: options.maxTokens,
|
|
13594
|
-
stream: true,
|
|
13595
|
-
tools: options.tools || []
|
|
13596
|
-
}
|
|
13597
|
-
},
|
|
13598
|
-
{ signal: options.abortSignal }
|
|
13599
|
-
).catch((err) => {
|
|
13600
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
13601
|
-
settleReject(new Error(`HTTP request failed: ${msg}`));
|
|
13602
|
-
});
|
|
13603
|
-
});
|
|
13604
|
-
}
|
|
13605
|
-
/**
|
|
13606
|
-
* Get available models from server (REST call, not streaming).
|
|
13607
|
-
* Delegates to ApiClient -- same as ServerLlmBackend.
|
|
13608
|
-
*/
|
|
13609
|
-
async getModelInfo() {
|
|
13610
|
-
try {
|
|
13611
|
-
logger.debug("[WebSocketLlmBackend] Fetching models from /api/models");
|
|
13612
|
-
const response = await this.apiClient.get("/api/models");
|
|
13613
|
-
if (!response || typeof response !== "object" || !Array.isArray(response.models)) {
|
|
13614
|
-
logger.warn("[WebSocketLlmBackend] Invalid API response format, using fallback models");
|
|
13615
|
-
return this.getFallbackModels();
|
|
13616
|
-
}
|
|
13617
|
-
const filteredModels = response.models.filter(
|
|
13618
|
-
(model) => model.type === "text" && model.supportsTools === true
|
|
13619
|
-
);
|
|
13620
|
-
if (filteredModels.length === 0) {
|
|
13621
|
-
logger.warn("[WebSocketLlmBackend] No CLI-compatible models found, using fallback");
|
|
13622
|
-
return this.getFallbackModels();
|
|
13623
|
-
}
|
|
13624
|
-
logger.debug(`[WebSocketLlmBackend] Loaded ${filteredModels.length} models`);
|
|
13625
|
-
return filteredModels;
|
|
13626
|
-
} catch (error) {
|
|
13627
|
-
logger.warn(
|
|
13628
|
-
`[WebSocketLlmBackend] Failed to fetch models: ${error instanceof Error ? error.message : String(error)}`
|
|
13629
|
-
);
|
|
13630
|
-
return this.getFallbackModels();
|
|
13631
|
-
}
|
|
13632
|
-
}
|
|
13633
|
-
getFallbackModels() {
|
|
13634
|
-
return [
|
|
13635
|
-
{ id: "claude-sonnet-4-5-20250929", name: "Claude 4.5 Sonnet" },
|
|
13636
|
-
{ id: "claude-3-5-haiku-20241022", name: "Claude 3.5 Haiku" },
|
|
13637
|
-
{ id: "gpt-4o", name: "GPT-4o" },
|
|
13638
|
-
{ id: "gpt-4o-mini", name: "GPT-4o Mini" }
|
|
13639
|
-
];
|
|
13640
|
-
}
|
|
13641
|
-
};
|
|
13642
|
-
|
|
13643
13799
|
// src/llm/NotifyingLlmBackend.ts
|
|
13644
13800
|
var NotifyingLlmBackend = class {
|
|
13645
13801
|
constructor(inner, backgroundManager) {
|
|
@@ -13674,253 +13830,6 @@ Please acknowledge these background agent results and incorporate them into your
|
|
|
13674
13830
|
}
|
|
13675
13831
|
};
|
|
13676
13832
|
|
|
13677
|
-
// src/ws/WebSocketConnectionManager.ts
|
|
13678
|
-
var WebSocketConnectionManager = class {
|
|
13679
|
-
constructor(wsUrl, getToken) {
|
|
13680
|
-
this.ws = null;
|
|
13681
|
-
this.heartbeatInterval = null;
|
|
13682
|
-
this.reconnectAttempts = 0;
|
|
13683
|
-
this.maxReconnectDelay = 3e4;
|
|
13684
|
-
this.handlers = /* @__PURE__ */ new Map();
|
|
13685
|
-
this.disconnectHandlers = /* @__PURE__ */ new Set();
|
|
13686
|
-
this.reconnectTimer = null;
|
|
13687
|
-
this.connected = false;
|
|
13688
|
-
this.connecting = false;
|
|
13689
|
-
this.closed = false;
|
|
13690
|
-
this.wsUrl = wsUrl;
|
|
13691
|
-
this.getToken = getToken;
|
|
13692
|
-
}
|
|
13693
|
-
/**
|
|
13694
|
-
* Connect to the WebSocket server.
|
|
13695
|
-
* Resolves when connection is established, rejects on failure.
|
|
13696
|
-
*/
|
|
13697
|
-
async connect() {
|
|
13698
|
-
if (this.connected || this.connecting) return;
|
|
13699
|
-
this.connecting = true;
|
|
13700
|
-
const token = await this.getToken();
|
|
13701
|
-
if (!token) {
|
|
13702
|
-
this.connecting = false;
|
|
13703
|
-
throw new Error("No access token available for WebSocket connection");
|
|
13704
|
-
}
|
|
13705
|
-
return new Promise((resolve3, reject) => {
|
|
13706
|
-
logger.debug(`[WS] Connecting to ${this.wsUrl}...`);
|
|
13707
|
-
this.ws = new WebSocket(this.wsUrl, [`access_token.${token}`]);
|
|
13708
|
-
this.ws.onopen = () => {
|
|
13709
|
-
logger.debug("[WS] Connected");
|
|
13710
|
-
this.connected = true;
|
|
13711
|
-
this.connecting = false;
|
|
13712
|
-
this.reconnectAttempts = 0;
|
|
13713
|
-
this.startHeartbeat();
|
|
13714
|
-
resolve3();
|
|
13715
|
-
};
|
|
13716
|
-
this.ws.onmessage = (event) => {
|
|
13717
|
-
try {
|
|
13718
|
-
const data = typeof event.data === "string" ? event.data : event.data.toString();
|
|
13719
|
-
const message = JSON.parse(data);
|
|
13720
|
-
const requestId = message.requestId;
|
|
13721
|
-
if (requestId && this.handlers.has(requestId)) {
|
|
13722
|
-
this.handlers.get(requestId)(message);
|
|
13723
|
-
} else {
|
|
13724
|
-
logger.debug(`[WS] Unhandled message: ${message.action || "unknown"}`);
|
|
13725
|
-
}
|
|
13726
|
-
} catch (err) {
|
|
13727
|
-
logger.debug(`[WS] Failed to parse message: ${err}`);
|
|
13728
|
-
}
|
|
13729
|
-
};
|
|
13730
|
-
this.ws.onclose = () => {
|
|
13731
|
-
logger.debug("[WS] Connection closed");
|
|
13732
|
-
this.cleanup();
|
|
13733
|
-
this.notifyDisconnect();
|
|
13734
|
-
if (!this.closed) {
|
|
13735
|
-
this.scheduleReconnect();
|
|
13736
|
-
}
|
|
13737
|
-
};
|
|
13738
|
-
this.ws.onerror = (err) => {
|
|
13739
|
-
logger.debug(`[WS] Error: ${err}`);
|
|
13740
|
-
if (this.connecting) {
|
|
13741
|
-
this.connecting = false;
|
|
13742
|
-
this.connected = false;
|
|
13743
|
-
reject(new Error("WebSocket connection failed"));
|
|
13744
|
-
}
|
|
13745
|
-
};
|
|
13746
|
-
});
|
|
13747
|
-
}
|
|
13748
|
-
/** Whether the connection is currently established */
|
|
13749
|
-
get isConnected() {
|
|
13750
|
-
return this.connected;
|
|
13751
|
-
}
|
|
13752
|
-
/**
|
|
13753
|
-
* Send a JSON message over the WebSocket connection.
|
|
13754
|
-
*/
|
|
13755
|
-
send(data) {
|
|
13756
|
-
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
13757
|
-
throw new Error("WebSocket is not connected");
|
|
13758
|
-
}
|
|
13759
|
-
const payload = JSON.stringify(data);
|
|
13760
|
-
const sizeKB = (payload.length / 1024).toFixed(1);
|
|
13761
|
-
logger.debug(`[WS] Sending ${sizeKB} KB (action: ${data.action})`);
|
|
13762
|
-
if (payload.length > 32e3) {
|
|
13763
|
-
logger.warn(`[WS] Payload ${sizeKB} KB exceeds API Gateway 32 KB frame limit \u2014 connection will be closed`);
|
|
13764
|
-
}
|
|
13765
|
-
this.ws.send(payload);
|
|
13766
|
-
}
|
|
13767
|
-
/**
|
|
13768
|
-
* Register a handler for messages matching a specific requestId.
|
|
13769
|
-
*/
|
|
13770
|
-
onRequest(requestId, handler) {
|
|
13771
|
-
this.handlers.set(requestId, handler);
|
|
13772
|
-
}
|
|
13773
|
-
/**
|
|
13774
|
-
* Remove a handler for a specific requestId.
|
|
13775
|
-
*/
|
|
13776
|
-
offRequest(requestId) {
|
|
13777
|
-
this.handlers.delete(requestId);
|
|
13778
|
-
}
|
|
13779
|
-
/**
|
|
13780
|
-
* Register a handler that fires when the connection drops.
|
|
13781
|
-
*/
|
|
13782
|
-
onDisconnect(handler) {
|
|
13783
|
-
this.disconnectHandlers.add(handler);
|
|
13784
|
-
}
|
|
13785
|
-
/**
|
|
13786
|
-
* Remove a disconnect handler.
|
|
13787
|
-
*/
|
|
13788
|
-
offDisconnect(handler) {
|
|
13789
|
-
this.disconnectHandlers.delete(handler);
|
|
13790
|
-
}
|
|
13791
|
-
/**
|
|
13792
|
-
* Close the connection and stop all heartbeat/reconnect logic.
|
|
13793
|
-
*/
|
|
13794
|
-
disconnect() {
|
|
13795
|
-
this.closed = true;
|
|
13796
|
-
this.cleanup();
|
|
13797
|
-
if (this.ws) {
|
|
13798
|
-
this.ws.close();
|
|
13799
|
-
this.ws = null;
|
|
13800
|
-
}
|
|
13801
|
-
this.handlers.clear();
|
|
13802
|
-
this.disconnectHandlers.clear();
|
|
13803
|
-
}
|
|
13804
|
-
startHeartbeat() {
|
|
13805
|
-
this.stopHeartbeat();
|
|
13806
|
-
this.heartbeatInterval = setInterval(
|
|
13807
|
-
() => {
|
|
13808
|
-
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
13809
|
-
this.ws.send(JSON.stringify({ action: "heartbeat" }));
|
|
13810
|
-
logger.debug("[WS] Heartbeat sent");
|
|
13811
|
-
}
|
|
13812
|
-
},
|
|
13813
|
-
5 * 60 * 1e3
|
|
13814
|
-
);
|
|
13815
|
-
}
|
|
13816
|
-
stopHeartbeat() {
|
|
13817
|
-
if (this.heartbeatInterval) {
|
|
13818
|
-
clearInterval(this.heartbeatInterval);
|
|
13819
|
-
this.heartbeatInterval = null;
|
|
13820
|
-
}
|
|
13821
|
-
}
|
|
13822
|
-
cleanup() {
|
|
13823
|
-
this.connected = false;
|
|
13824
|
-
this.connecting = false;
|
|
13825
|
-
this.stopHeartbeat();
|
|
13826
|
-
if (this.reconnectTimer) {
|
|
13827
|
-
clearTimeout(this.reconnectTimer);
|
|
13828
|
-
this.reconnectTimer = null;
|
|
13829
|
-
}
|
|
13830
|
-
}
|
|
13831
|
-
notifyDisconnect() {
|
|
13832
|
-
for (const handler of this.disconnectHandlers) {
|
|
13833
|
-
try {
|
|
13834
|
-
handler();
|
|
13835
|
-
} catch {
|
|
13836
|
-
}
|
|
13837
|
-
}
|
|
13838
|
-
}
|
|
13839
|
-
scheduleReconnect() {
|
|
13840
|
-
if (this.closed) return;
|
|
13841
|
-
this.reconnectAttempts++;
|
|
13842
|
-
const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts - 1), this.maxReconnectDelay);
|
|
13843
|
-
logger.debug(`[WS] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
|
|
13844
|
-
this.reconnectTimer = setTimeout(async () => {
|
|
13845
|
-
this.reconnectTimer = null;
|
|
13846
|
-
if (this.closed) return;
|
|
13847
|
-
try {
|
|
13848
|
-
await this.connect();
|
|
13849
|
-
} catch {
|
|
13850
|
-
logger.debug("[WS] Reconnection failed");
|
|
13851
|
-
}
|
|
13852
|
-
}, delay);
|
|
13853
|
-
}
|
|
13854
|
-
};
|
|
13855
|
-
|
|
13856
|
-
// src/ws/WebSocketToolExecutor.ts
|
|
13857
|
-
import { v4 as uuidv412 } from "uuid";
|
|
13858
|
-
var WebSocketToolExecutor = class {
|
|
13859
|
-
constructor(wsManager, tokenGetter) {
|
|
13860
|
-
this.wsManager = wsManager;
|
|
13861
|
-
this.tokenGetter = tokenGetter;
|
|
13862
|
-
}
|
|
13863
|
-
/**
|
|
13864
|
-
* Execute a server-side tool via WebSocket.
|
|
13865
|
-
* Returns the tool result or throws on error.
|
|
13866
|
-
*/
|
|
13867
|
-
async execute(toolName, input, abortSignal) {
|
|
13868
|
-
if (!this.wsManager.isConnected) {
|
|
13869
|
-
throw new Error("WebSocket is not connected");
|
|
13870
|
-
}
|
|
13871
|
-
const token = await this.tokenGetter();
|
|
13872
|
-
if (!token) {
|
|
13873
|
-
throw new Error("No access token available");
|
|
13874
|
-
}
|
|
13875
|
-
const requestId = uuidv412();
|
|
13876
|
-
return new Promise((resolve3, reject) => {
|
|
13877
|
-
let settled = false;
|
|
13878
|
-
const settle = (action) => {
|
|
13879
|
-
if (settled) return;
|
|
13880
|
-
settled = true;
|
|
13881
|
-
this.wsManager.offRequest(requestId);
|
|
13882
|
-
this.wsManager.offDisconnect(onDisconnect);
|
|
13883
|
-
action();
|
|
13884
|
-
};
|
|
13885
|
-
const settleResolve = (result) => settle(() => resolve3(result));
|
|
13886
|
-
const settleReject = (err) => settle(() => reject(err));
|
|
13887
|
-
const onDisconnect = () => {
|
|
13888
|
-
settleReject(new Error("WebSocket connection lost during tool execution"));
|
|
13889
|
-
};
|
|
13890
|
-
this.wsManager.onDisconnect(onDisconnect);
|
|
13891
|
-
if (abortSignal) {
|
|
13892
|
-
if (abortSignal.aborted) {
|
|
13893
|
-
settleReject(new Error("Tool execution aborted"));
|
|
13894
|
-
return;
|
|
13895
|
-
}
|
|
13896
|
-
abortSignal.addEventListener("abort", () => settleReject(new Error("Tool execution aborted")), {
|
|
13897
|
-
once: true
|
|
13898
|
-
});
|
|
13899
|
-
}
|
|
13900
|
-
this.wsManager.onRequest(requestId, (message) => {
|
|
13901
|
-
if (message.action === "cli_tool_response") {
|
|
13902
|
-
settleResolve({
|
|
13903
|
-
success: message.success,
|
|
13904
|
-
content: message.content,
|
|
13905
|
-
error: message.error
|
|
13906
|
-
});
|
|
13907
|
-
}
|
|
13908
|
-
});
|
|
13909
|
-
try {
|
|
13910
|
-
this.wsManager.send({
|
|
13911
|
-
action: "cli_tool_request",
|
|
13912
|
-
accessToken: token,
|
|
13913
|
-
requestId,
|
|
13914
|
-
toolName,
|
|
13915
|
-
input
|
|
13916
|
-
});
|
|
13917
|
-
} catch (err) {
|
|
13918
|
-
settleReject(err instanceof Error ? err : new Error(String(err)));
|
|
13919
|
-
}
|
|
13920
|
-
});
|
|
13921
|
-
}
|
|
13922
|
-
};
|
|
13923
|
-
|
|
13924
13833
|
// src/auth/ApiClient.ts
|
|
13925
13834
|
import axios11 from "axios";
|
|
13926
13835
|
var ApiClient = class {
|
|
@@ -14058,7 +13967,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
|
|
|
14058
13967
|
// package.json
|
|
14059
13968
|
var package_default = {
|
|
14060
13969
|
name: "@bike4mind/cli",
|
|
14061
|
-
version: "0.2.29-feat-cli-
|
|
13970
|
+
version: "0.2.29-feat-cli-sandbox.18927+ef8f5870a",
|
|
14062
13971
|
type: "module",
|
|
14063
13972
|
description: "Interactive CLI tool for Bike4Mind with ReAct agents",
|
|
14064
13973
|
license: "UNLICENSED",
|
|
@@ -14129,10 +14038,13 @@ var package_default = {
|
|
|
14129
14038
|
diff: "^8.0.2",
|
|
14130
14039
|
dotenv: "^16.3.1",
|
|
14131
14040
|
"eventsource-parser": "^3.0.6",
|
|
14041
|
+
fdir: "^6.5.0",
|
|
14132
14042
|
"file-type": "^18.7.0",
|
|
14133
14043
|
"fuse.js": "^7.1.0",
|
|
14044
|
+
fzf: "^0.5.2",
|
|
14134
14045
|
glob: "^13.0.0",
|
|
14135
14046
|
"gray-matter": "^4.0.3",
|
|
14047
|
+
ignore: "^7.0.5",
|
|
14136
14048
|
ink: "^6.5.1",
|
|
14137
14049
|
"ink-select-input": "^6.2.0",
|
|
14138
14050
|
"ink-spinner": "^5.0.0",
|
|
@@ -14148,6 +14060,7 @@ var package_default = {
|
|
|
14148
14060
|
open: "^11.0.0",
|
|
14149
14061
|
openai: "^6.18.0",
|
|
14150
14062
|
"p-limit": "^6.2.0",
|
|
14063
|
+
picomatch: "^4.0.3",
|
|
14151
14064
|
qrcode: "^1.5.4",
|
|
14152
14065
|
react: "^19.2.3",
|
|
14153
14066
|
sharp: "^0.34.5",
|
|
@@ -14168,14 +14081,15 @@ var package_default = {
|
|
|
14168
14081
|
},
|
|
14169
14082
|
devDependencies: {
|
|
14170
14083
|
"@bike4mind/agents": "0.1.0",
|
|
14171
|
-
"@bike4mind/common": "2.50.1-feat-cli-
|
|
14172
|
-
"@bike4mind/mcp": "1.29.1-feat-cli-
|
|
14173
|
-
"@bike4mind/services": "2.48.1-feat-cli-
|
|
14174
|
-
"@bike4mind/utils": "2.5.1-feat-cli-
|
|
14084
|
+
"@bike4mind/common": "2.50.1-feat-cli-sandbox.18927+ef8f5870a",
|
|
14085
|
+
"@bike4mind/mcp": "1.29.1-feat-cli-sandbox.18927+ef8f5870a",
|
|
14086
|
+
"@bike4mind/services": "2.48.1-feat-cli-sandbox.18927+ef8f5870a",
|
|
14087
|
+
"@bike4mind/utils": "2.5.1-feat-cli-sandbox.18927+ef8f5870a",
|
|
14175
14088
|
"@types/better-sqlite3": "^7.6.13",
|
|
14176
14089
|
"@types/diff": "^5.0.9",
|
|
14177
14090
|
"@types/jsonwebtoken": "^9.0.4",
|
|
14178
14091
|
"@types/node": "^22.9.0",
|
|
14092
|
+
"@types/picomatch": "^4.0.2",
|
|
14179
14093
|
"@types/react": "^19.2.7",
|
|
14180
14094
|
"@types/uuid": "^9.0.7",
|
|
14181
14095
|
"@types/yargs": "^17.0.32",
|
|
@@ -14188,7 +14102,7 @@ var package_default = {
|
|
|
14188
14102
|
optionalDependencies: {
|
|
14189
14103
|
"@vscode/ripgrep": "^1.17.0"
|
|
14190
14104
|
},
|
|
14191
|
-
gitHead: "
|
|
14105
|
+
gitHead: "ef8f5870abfa559f03d5af462d888986a7b6cd31"
|
|
14192
14106
|
};
|
|
14193
14107
|
|
|
14194
14108
|
// src/config/constants.ts
|
|
@@ -14638,7 +14552,7 @@ var SubagentOrchestrator = class {
|
|
|
14638
14552
|
|
|
14639
14553
|
// src/agents/AgentStore.ts
|
|
14640
14554
|
import fs12 from "fs/promises";
|
|
14641
|
-
import
|
|
14555
|
+
import path17 from "path";
|
|
14642
14556
|
import os2 from "os";
|
|
14643
14557
|
import matter2 from "gray-matter";
|
|
14644
14558
|
var FULL_MODEL_ID_PREFIXES = [
|
|
@@ -14787,10 +14701,10 @@ var AgentStore = class {
|
|
|
14787
14701
|
const root = projectRoot || process.cwd();
|
|
14788
14702
|
const home = os2.homedir();
|
|
14789
14703
|
this.builtinAgentsDir = builtinDir;
|
|
14790
|
-
this.globalB4MAgentsDir =
|
|
14791
|
-
this.globalClaudeAgentsDir =
|
|
14792
|
-
this.projectB4MAgentsDir =
|
|
14793
|
-
this.projectClaudeAgentsDir =
|
|
14704
|
+
this.globalB4MAgentsDir = path17.join(home, ".bike4mind", "agents");
|
|
14705
|
+
this.globalClaudeAgentsDir = path17.join(home, ".claude", "agents");
|
|
14706
|
+
this.projectB4MAgentsDir = path17.join(root, ".bike4mind", "agents");
|
|
14707
|
+
this.projectClaudeAgentsDir = path17.join(root, ".claude", "agents");
|
|
14794
14708
|
}
|
|
14795
14709
|
/**
|
|
14796
14710
|
* Load all agents from all directories
|
|
@@ -14844,7 +14758,7 @@ var AgentStore = class {
|
|
|
14844
14758
|
try {
|
|
14845
14759
|
const entries = await fs12.readdir(directory, { withFileTypes: true });
|
|
14846
14760
|
for (const entry of entries) {
|
|
14847
|
-
const fullPath =
|
|
14761
|
+
const fullPath = path17.join(directory, entry.name);
|
|
14848
14762
|
if (entry.isDirectory()) {
|
|
14849
14763
|
const subFiles = await this.findAgentFiles(fullPath);
|
|
14850
14764
|
files.push(...subFiles);
|
|
@@ -14864,7 +14778,7 @@ var AgentStore = class {
|
|
|
14864
14778
|
const content = await fs12.readFile(filePath, "utf-8");
|
|
14865
14779
|
const { data: frontmatter, content: body } = matter2(content);
|
|
14866
14780
|
const parsed = AgentFrontmatterSchema.parse(frontmatter);
|
|
14867
|
-
const name =
|
|
14781
|
+
const name = path17.basename(filePath, ".md");
|
|
14868
14782
|
const modelInput = parsed.model || DEFAULT_AGENT_MODEL;
|
|
14869
14783
|
const resolution = resolveModelAlias(modelInput, name, filePath);
|
|
14870
14784
|
if (!resolution.resolved && resolution.warning) {
|
|
@@ -14944,7 +14858,7 @@ var AgentStore = class {
|
|
|
14944
14858
|
*/
|
|
14945
14859
|
async createAgentFile(name, isGlobal = false, useClaude = true) {
|
|
14946
14860
|
const targetDir = isGlobal ? useClaude ? this.globalClaudeAgentsDir : this.globalB4MAgentsDir : useClaude ? this.projectClaudeAgentsDir : this.projectB4MAgentsDir;
|
|
14947
|
-
const filePath =
|
|
14861
|
+
const filePath = path17.join(targetDir, `${name}.md`);
|
|
14948
14862
|
try {
|
|
14949
14863
|
await fs12.access(filePath);
|
|
14950
14864
|
throw new Error(`Agent file already exists: ${filePath}`);
|
|
@@ -15641,7 +15555,7 @@ function createTodoStore(onUpdate) {
|
|
|
15641
15555
|
|
|
15642
15556
|
// src/tools/findDefinitionTool.ts
|
|
15643
15557
|
import { stat as stat3 } from "fs/promises";
|
|
15644
|
-
import
|
|
15558
|
+
import path18 from "path";
|
|
15645
15559
|
import { execFile as execFile2 } from "child_process";
|
|
15646
15560
|
import { promisify as promisify2 } from "util";
|
|
15647
15561
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -15662,8 +15576,8 @@ var ALL_KEYWORDS = Object.values(KIND_KEYWORDS).flat();
|
|
|
15662
15576
|
function getRipgrepPath2() {
|
|
15663
15577
|
try {
|
|
15664
15578
|
const ripgrepPath = require3.resolve("@vscode/ripgrep");
|
|
15665
|
-
const ripgrepDir =
|
|
15666
|
-
return
|
|
15579
|
+
const ripgrepDir = path18.dirname(ripgrepPath);
|
|
15580
|
+
return path18.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
|
|
15667
15581
|
} catch {
|
|
15668
15582
|
throw new Error(
|
|
15669
15583
|
"ripgrep is not available. Please install @vscode/ripgrep: pnpm add @vscode/ripgrep --filter @bike4mind/services"
|
|
@@ -15671,10 +15585,10 @@ function getRipgrepPath2() {
|
|
|
15671
15585
|
}
|
|
15672
15586
|
}
|
|
15673
15587
|
function isPathWithinWorkspace2(targetPath, baseCwd) {
|
|
15674
|
-
const resolvedTarget =
|
|
15675
|
-
const resolvedBase =
|
|
15676
|
-
const relativePath =
|
|
15677
|
-
return !relativePath.startsWith("..") && !
|
|
15588
|
+
const resolvedTarget = path18.resolve(targetPath);
|
|
15589
|
+
const resolvedBase = path18.resolve(baseCwd);
|
|
15590
|
+
const relativePath = path18.relative(resolvedBase, resolvedTarget);
|
|
15591
|
+
return !relativePath.startsWith("..") && !path18.isAbsolute(relativePath);
|
|
15678
15592
|
}
|
|
15679
15593
|
function escapeRegex(str) {
|
|
15680
15594
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -15707,7 +15621,7 @@ async function findDefinitions(params) {
|
|
|
15707
15621
|
throw new Error("symbol_name is required");
|
|
15708
15622
|
}
|
|
15709
15623
|
const baseCwd = process.cwd();
|
|
15710
|
-
const targetDir = search_path ?
|
|
15624
|
+
const targetDir = search_path ? path18.resolve(baseCwd, search_path) : baseCwd;
|
|
15711
15625
|
if (!isPathWithinWorkspace2(targetDir, baseCwd)) {
|
|
15712
15626
|
throw new Error(`Path validation failed: "${search_path}" resolves outside the allowed workspace directory`);
|
|
15713
15627
|
}
|
|
@@ -15762,7 +15676,7 @@ async function findDefinitions(params) {
|
|
|
15762
15676
|
const lineText = match.data.lines.text.trimEnd();
|
|
15763
15677
|
if (isLikelyDefinition(lineText)) {
|
|
15764
15678
|
allMatches.push({
|
|
15765
|
-
filePath:
|
|
15679
|
+
filePath: path18.relative(targetDir, match.data.path.text) || path18.basename(match.data.path.text),
|
|
15766
15680
|
lineNumber: match.data.line_number,
|
|
15767
15681
|
line: lineText
|
|
15768
15682
|
});
|
|
@@ -15840,8 +15754,8 @@ function createFindDefinitionTool() {
|
|
|
15840
15754
|
}
|
|
15841
15755
|
|
|
15842
15756
|
// src/tools/getFileStructure/index.ts
|
|
15843
|
-
import { existsSync as
|
|
15844
|
-
import
|
|
15757
|
+
import { existsSync as existsSync10, promises as fs13, statSync as statSync6 } from "fs";
|
|
15758
|
+
import path19 from "path";
|
|
15845
15759
|
|
|
15846
15760
|
// src/tools/getFileStructure/formatter.ts
|
|
15847
15761
|
var SECTIONS = [
|
|
@@ -15885,11 +15799,11 @@ function createGetFileStructureTool() {
|
|
|
15885
15799
|
const params = value;
|
|
15886
15800
|
try {
|
|
15887
15801
|
const cwd = process.cwd();
|
|
15888
|
-
const resolvedPath =
|
|
15802
|
+
const resolvedPath = path19.resolve(cwd, params.path);
|
|
15889
15803
|
if (!resolvedPath.startsWith(cwd)) {
|
|
15890
15804
|
return "Error: Access denied - cannot read files outside of current working directory";
|
|
15891
15805
|
}
|
|
15892
|
-
if (!
|
|
15806
|
+
if (!existsSync10(resolvedPath)) {
|
|
15893
15807
|
return `Error: File not found: ${params.path}`;
|
|
15894
15808
|
}
|
|
15895
15809
|
const stats = statSync6(resolvedPath);
|
|
@@ -15899,7 +15813,7 @@ function createGetFileStructureTool() {
|
|
|
15899
15813
|
if (stats.size > MAX_FILE_SIZE3) {
|
|
15900
15814
|
return `Error: File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Max: ${MAX_FILE_SIZE3 / 1024 / 1024}MB`;
|
|
15901
15815
|
}
|
|
15902
|
-
const ext =
|
|
15816
|
+
const ext = path19.extname(resolvedPath).toLowerCase();
|
|
15903
15817
|
const { getLanguageForExtension, parseFileStructure, getSupportedLanguages } = await import("./treeSitterEngine-4SGFQDY3.js");
|
|
15904
15818
|
const languageId = getLanguageForExtension(ext);
|
|
15905
15819
|
if (!languageId) {
|
|
@@ -15946,7 +15860,7 @@ var usageCache = null;
|
|
|
15946
15860
|
function CliApp() {
|
|
15947
15861
|
const { exit } = useApp();
|
|
15948
15862
|
const imageRenderer = new ImageRenderer();
|
|
15949
|
-
const [state, setState] =
|
|
15863
|
+
const [state, setState] = useState10({
|
|
15950
15864
|
session: null,
|
|
15951
15865
|
sessionStore: new SessionStore(),
|
|
15952
15866
|
configStore: new ConfigStore(),
|
|
@@ -15968,11 +15882,11 @@ function CliApp() {
|
|
|
15968
15882
|
abortController: null,
|
|
15969
15883
|
contextContent: "",
|
|
15970
15884
|
backgroundManager: null,
|
|
15971
|
-
|
|
15885
|
+
sandboxOrchestrator: null
|
|
15972
15886
|
});
|
|
15973
|
-
const [isInitialized, setIsInitialized] =
|
|
15974
|
-
const [initError, setInitError] =
|
|
15975
|
-
const [commandHistory, setCommandHistory] =
|
|
15887
|
+
const [isInitialized, setIsInitialized] = useState10(false);
|
|
15888
|
+
const [initError, setInitError] = useState10(null);
|
|
15889
|
+
const [commandHistory, setCommandHistory] = useState10([]);
|
|
15976
15890
|
const imageStoreInitPromise = useRef3(null);
|
|
15977
15891
|
const setStoreSession = useCliStore((state2) => state2.setSession);
|
|
15978
15892
|
const enqueuePermissionPrompt = useCliStore((state2) => state2.enqueuePermissionPrompt);
|
|
@@ -15996,10 +15910,6 @@ function CliApp() {
|
|
|
15996
15910
|
})
|
|
15997
15911
|
);
|
|
15998
15912
|
}
|
|
15999
|
-
if (state.wsManager) {
|
|
16000
|
-
state.wsManager.disconnect();
|
|
16001
|
-
setWebSocketToolExecutor(null);
|
|
16002
|
-
}
|
|
16003
15913
|
if (state.agent) {
|
|
16004
15914
|
state.agent.removeAllListeners();
|
|
16005
15915
|
}
|
|
@@ -16018,7 +15928,7 @@ function CliApp() {
|
|
|
16018
15928
|
setTimeout(() => {
|
|
16019
15929
|
process.exit(0);
|
|
16020
15930
|
}, 100);
|
|
16021
|
-
}, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore
|
|
15931
|
+
}, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore]);
|
|
16022
15932
|
useInput9((input, key) => {
|
|
16023
15933
|
if (key.escape) {
|
|
16024
15934
|
if (state.abortController) {
|
|
@@ -16088,7 +15998,7 @@ function CliApp() {
|
|
|
16088
15998
|
if (!isAuthenticated) {
|
|
16089
15999
|
console.log("\u2139\uFE0F AI features disabled. Available commands: /login, /help, /config\n");
|
|
16090
16000
|
const minimalSession = {
|
|
16091
|
-
id:
|
|
16001
|
+
id: uuidv411(),
|
|
16092
16002
|
name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
16093
16003
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16094
16004
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -16116,45 +16026,10 @@ function CliApp() {
|
|
|
16116
16026
|
console.log(`\u{1F30D} API Environment: ${envName} (${apiBaseURL})`);
|
|
16117
16027
|
}
|
|
16118
16028
|
const apiClient = new ApiClient(apiBaseURL, state.configStore);
|
|
16119
|
-
const
|
|
16120
|
-
|
|
16121
|
-
|
|
16122
|
-
};
|
|
16123
|
-
let wsManager = null;
|
|
16124
|
-
let llm;
|
|
16125
|
-
try {
|
|
16126
|
-
const serverConfig = await apiClient.get(
|
|
16127
|
-
"/api/settings/serverConfig"
|
|
16128
|
-
);
|
|
16129
|
-
const wsUrl = serverConfig?.websocketUrl;
|
|
16130
|
-
const wsCompletionUrl = serverConfig?.wsCompletionUrl;
|
|
16131
|
-
if (wsUrl && wsCompletionUrl) {
|
|
16132
|
-
wsManager = new WebSocketConnectionManager(wsUrl, tokenGetter);
|
|
16133
|
-
await wsManager.connect();
|
|
16134
|
-
const wsToolExecutor2 = new WebSocketToolExecutor(wsManager, tokenGetter);
|
|
16135
|
-
setWebSocketToolExecutor(wsToolExecutor2);
|
|
16136
|
-
llm = new WebSocketLlmBackend({
|
|
16137
|
-
wsManager,
|
|
16138
|
-
apiClient,
|
|
16139
|
-
model: config.defaultModel,
|
|
16140
|
-
tokenGetter,
|
|
16141
|
-
wsCompletionUrl
|
|
16142
|
-
});
|
|
16143
|
-
logger.debug("\u{1F50C} Using WebSocket transport (bypasses CloudFront timeout)");
|
|
16144
|
-
} else {
|
|
16145
|
-
throw new Error("No websocketUrl or wsCompletionUrl in server config");
|
|
16146
|
-
}
|
|
16147
|
-
} catch (wsError) {
|
|
16148
|
-
logger.debug(
|
|
16149
|
-
`[WS] WebSocket unavailable, using SSE fallback: ${wsError instanceof Error ? wsError.message : String(wsError)}`
|
|
16150
|
-
);
|
|
16151
|
-
wsManager = null;
|
|
16152
|
-
setWebSocketToolExecutor(null);
|
|
16153
|
-
llm = new ServerLlmBackend({
|
|
16154
|
-
apiClient,
|
|
16155
|
-
model: config.defaultModel
|
|
16156
|
-
});
|
|
16157
|
-
}
|
|
16029
|
+
const llm = new ServerLlmBackend({
|
|
16030
|
+
apiClient,
|
|
16031
|
+
model: config.defaultModel
|
|
16032
|
+
});
|
|
16158
16033
|
const models = await llm.getModelInfo();
|
|
16159
16034
|
if (models.length === 0) {
|
|
16160
16035
|
throw new Error("No models available from server.");
|
|
@@ -16167,7 +16042,7 @@ function CliApp() {
|
|
|
16167
16042
|
}
|
|
16168
16043
|
llm.currentModel = modelInfo.id;
|
|
16169
16044
|
const newSession = {
|
|
16170
|
-
id:
|
|
16045
|
+
id: uuidv411(),
|
|
16171
16046
|
name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
16172
16047
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16173
16048
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -16204,6 +16079,52 @@ function CliApp() {
|
|
|
16204
16079
|
config.tools.disabled || []
|
|
16205
16080
|
// denied tools from project config
|
|
16206
16081
|
);
|
|
16082
|
+
const { createSandboxRuntime } = await import("./SandboxRuntimeAdapter-FQ56MAB2.js");
|
|
16083
|
+
const { SandboxOrchestrator } = await import("./SandboxOrchestrator-MYBNKKWL.js");
|
|
16084
|
+
const { DEFAULT_SANDBOX_CONFIG } = await import("./types-KB5NP6T4.js");
|
|
16085
|
+
const { ProxyManager } = await import("./ProxyManager-4ZZEYTD6.js");
|
|
16086
|
+
const sandboxRuntime = await createSandboxRuntime();
|
|
16087
|
+
const sandboxConfig = config.sandbox ?? DEFAULT_SANDBOX_CONFIG;
|
|
16088
|
+
const proxyManager = new ProxyManager(sandboxConfig.network);
|
|
16089
|
+
const sandboxOrchestrator = new SandboxOrchestrator(sandboxConfig, sandboxRuntime, proxyManager);
|
|
16090
|
+
proxyManager.onEvent((event) => {
|
|
16091
|
+
if (event.type === "blocked") {
|
|
16092
|
+
console.error(
|
|
16093
|
+
`
|
|
16094
|
+
\x1B[41m\x1B[97m BLOCKED \x1B[0m \x1B[31mNetwork proxy denied connection to\x1B[0m \x1B[1m${event.domain}\x1B[0m \x1B[90m(${event.method})\x1B[0m`
|
|
16095
|
+
);
|
|
16096
|
+
console.error(`\x1B[90m Tip: /sandbox:trust-domain ${event.domain}\x1B[0m
|
|
16097
|
+
`);
|
|
16098
|
+
sandboxOrchestrator.recordViolation({
|
|
16099
|
+
type: "network",
|
|
16100
|
+
domain: event.domain,
|
|
16101
|
+
command: `[network] ${event.method} ${event.domain}`,
|
|
16102
|
+
blockedBy: "proxy",
|
|
16103
|
+
timestamp: event.timestamp,
|
|
16104
|
+
detail: `Blocked ${event.method} to ${event.domain}`
|
|
16105
|
+
}).catch(() => {
|
|
16106
|
+
});
|
|
16107
|
+
}
|
|
16108
|
+
});
|
|
16109
|
+
const { ViolationLogStore } = await import("./ViolationLogStore-RIIUVURH.js");
|
|
16110
|
+
const violationStore = new ViolationLogStore();
|
|
16111
|
+
sandboxOrchestrator.setViolationStore(violationStore);
|
|
16112
|
+
permissionManager.setSandboxState(sandboxConfig.mode, sandboxOrchestrator.isActive());
|
|
16113
|
+
if (sandboxConfig.enabled && sandboxConfig.mode !== "disabled") {
|
|
16114
|
+
if (sandboxRuntime) {
|
|
16115
|
+
console.log(`\u{1F512} Sandbox: ${sandboxConfig.mode} (${sandboxRuntime.name})`);
|
|
16116
|
+
} else {
|
|
16117
|
+
console.log("\u26A0\uFE0F Sandbox: enabled but runtime not available on this platform");
|
|
16118
|
+
}
|
|
16119
|
+
if (sandboxConfig.network.enabled) {
|
|
16120
|
+
await proxyManager.start();
|
|
16121
|
+
if (proxyManager.isRunning()) {
|
|
16122
|
+
console.log(
|
|
16123
|
+
`\u{1F310} Network proxy: filtering on port ${proxyManager.getPort()} (${sandboxConfig.network.allowedDomains.length} domains)`
|
|
16124
|
+
);
|
|
16125
|
+
}
|
|
16126
|
+
}
|
|
16127
|
+
}
|
|
16207
16128
|
let permissionPromptCounter = 0;
|
|
16208
16129
|
const promptFn = (toolName, args, preview) => {
|
|
16209
16130
|
return new Promise((resolve3) => {
|
|
@@ -16229,7 +16150,10 @@ function CliApp() {
|
|
|
16229
16150
|
promptFn,
|
|
16230
16151
|
agentContext,
|
|
16231
16152
|
state.configStore,
|
|
16232
|
-
apiClient
|
|
16153
|
+
apiClient,
|
|
16154
|
+
void 0,
|
|
16155
|
+
// toolFilter
|
|
16156
|
+
sandboxOrchestrator
|
|
16233
16157
|
);
|
|
16234
16158
|
startupLog.push(`\u{1F6E0}\uFE0F Loaded ${b4mTools2.length} B4M tool(s)`);
|
|
16235
16159
|
const mcpManager = new McpManager(config);
|
|
@@ -16383,8 +16307,8 @@ function CliApp() {
|
|
|
16383
16307
|
// Store raw context for compact instructions
|
|
16384
16308
|
backgroundManager,
|
|
16385
16309
|
// Store for grouped notification turn tracking
|
|
16386
|
-
|
|
16387
|
-
//
|
|
16310
|
+
sandboxOrchestrator
|
|
16311
|
+
// Store sandbox orchestrator for /sandbox commands
|
|
16388
16312
|
}));
|
|
16389
16313
|
setStoreSession(newSession);
|
|
16390
16314
|
const bannerLines = [
|
|
@@ -16430,7 +16354,7 @@ function CliApp() {
|
|
|
16430
16354
|
setStoreSession,
|
|
16431
16355
|
setState
|
|
16432
16356
|
]);
|
|
16433
|
-
|
|
16357
|
+
useEffect7(() => {
|
|
16434
16358
|
init();
|
|
16435
16359
|
}, [init]);
|
|
16436
16360
|
const handleCustomCommandMessage = async (fullTemplate, displayMessage) => {
|
|
@@ -16479,13 +16403,13 @@ function CliApp() {
|
|
|
16479
16403
|
messageContent = multimodalMessage.content;
|
|
16480
16404
|
}
|
|
16481
16405
|
const userMessage = {
|
|
16482
|
-
id:
|
|
16406
|
+
id: uuidv411(),
|
|
16483
16407
|
role: "user",
|
|
16484
16408
|
content: userMessageContent,
|
|
16485
16409
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
16486
16410
|
};
|
|
16487
16411
|
const pendingAssistantMessage = {
|
|
16488
|
-
id:
|
|
16412
|
+
id: uuidv411(),
|
|
16489
16413
|
role: "assistant",
|
|
16490
16414
|
content: "...",
|
|
16491
16415
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -16541,6 +16465,7 @@ function CliApp() {
|
|
|
16541
16465
|
completion: 0,
|
|
16542
16466
|
total: result.completionInfo.totalTokens
|
|
16543
16467
|
},
|
|
16468
|
+
creditsUsed: result.completionInfo.totalCredits,
|
|
16544
16469
|
model: state.session.model,
|
|
16545
16470
|
permissionDenied
|
|
16546
16471
|
}
|
|
@@ -16552,6 +16477,7 @@ function CliApp() {
|
|
|
16552
16477
|
metadata: {
|
|
16553
16478
|
...currentSession.metadata,
|
|
16554
16479
|
totalTokens: currentSession.metadata.totalTokens + result.completionInfo.totalTokens,
|
|
16480
|
+
totalCredits: (currentSession.metadata.totalCredits || 0) + (result.completionInfo.totalCredits || 0),
|
|
16555
16481
|
toolCallCount: currentSession.metadata.toolCallCount + successfulToolCalls
|
|
16556
16482
|
}
|
|
16557
16483
|
};
|
|
@@ -16696,13 +16622,13 @@ function CliApp() {
|
|
|
16696
16622
|
userMessageContent = message;
|
|
16697
16623
|
}
|
|
16698
16624
|
const userMessage = {
|
|
16699
|
-
id:
|
|
16625
|
+
id: uuidv411(),
|
|
16700
16626
|
role: "user",
|
|
16701
16627
|
content: userMessageContent,
|
|
16702
16628
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
16703
16629
|
};
|
|
16704
16630
|
const pendingAssistantMessage = {
|
|
16705
|
-
id:
|
|
16631
|
+
id: uuidv411(),
|
|
16706
16632
|
role: "assistant",
|
|
16707
16633
|
content: "...",
|
|
16708
16634
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -16754,6 +16680,7 @@ function CliApp() {
|
|
|
16754
16680
|
completion: 0,
|
|
16755
16681
|
total: result.completionInfo.totalTokens
|
|
16756
16682
|
},
|
|
16683
|
+
creditsUsed: result.completionInfo.totalCredits,
|
|
16757
16684
|
steps: result.steps.map(formatStep),
|
|
16758
16685
|
// Complete history: thoughts, actions, observations
|
|
16759
16686
|
permissionDenied
|
|
@@ -16767,6 +16694,7 @@ function CliApp() {
|
|
|
16767
16694
|
metadata: {
|
|
16768
16695
|
...currentSession.metadata,
|
|
16769
16696
|
totalTokens: currentSession.metadata.totalTokens + result.completionInfo.totalTokens,
|
|
16697
|
+
totalCredits: (currentSession.metadata.totalCredits || 0) + (result.completionInfo.totalCredits || 0),
|
|
16770
16698
|
toolCallCount: currentSession.metadata.toolCallCount + successfulToolCalls
|
|
16771
16699
|
}
|
|
16772
16700
|
};
|
|
@@ -16780,7 +16708,7 @@ function CliApp() {
|
|
|
16780
16708
|
const currentSession = useCliStore.getState().session;
|
|
16781
16709
|
if (currentSession) {
|
|
16782
16710
|
const cancelMessage = {
|
|
16783
|
-
id:
|
|
16711
|
+
id: uuidv411(),
|
|
16784
16712
|
role: "assistant",
|
|
16785
16713
|
content: "\u26A0\uFE0F Operation cancelled by user",
|
|
16786
16714
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -16825,7 +16753,7 @@ function CliApp() {
|
|
|
16825
16753
|
setState((prev) => ({ ...prev, abortController }));
|
|
16826
16754
|
try {
|
|
16827
16755
|
const pendingAssistantMessage = {
|
|
16828
|
-
id:
|
|
16756
|
+
id: uuidv411(),
|
|
16829
16757
|
role: "assistant",
|
|
16830
16758
|
content: "...",
|
|
16831
16759
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -16853,7 +16781,7 @@ function CliApp() {
|
|
|
16853
16781
|
const currentSession = useCliStore.getState().session;
|
|
16854
16782
|
if (!currentSession) return;
|
|
16855
16783
|
const continuationMessage = {
|
|
16856
|
-
id:
|
|
16784
|
+
id: uuidv411(),
|
|
16857
16785
|
role: "assistant",
|
|
16858
16786
|
content: "---\n\n**Background Agent Results:**\n\n" + result.finalAnswer,
|
|
16859
16787
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -16865,7 +16793,8 @@ function CliApp() {
|
|
|
16865
16793
|
prompt: 0,
|
|
16866
16794
|
completion: 0,
|
|
16867
16795
|
total: result.completionInfo.totalTokens
|
|
16868
|
-
}
|
|
16796
|
+
},
|
|
16797
|
+
creditsUsed: result.completionInfo.totalCredits
|
|
16869
16798
|
}
|
|
16870
16799
|
};
|
|
16871
16800
|
const updatedSession = {
|
|
@@ -16875,6 +16804,7 @@ function CliApp() {
|
|
|
16875
16804
|
metadata: {
|
|
16876
16805
|
...currentSession.metadata,
|
|
16877
16806
|
totalTokens: currentSession.metadata.totalTokens + result.completionInfo.totalTokens,
|
|
16807
|
+
totalCredits: (currentSession.metadata.totalCredits || 0) + (result.completionInfo.totalCredits || 0),
|
|
16878
16808
|
toolCallCount: currentSession.metadata.toolCallCount + successfulToolCalls
|
|
16879
16809
|
}
|
|
16880
16810
|
};
|
|
@@ -16912,13 +16842,13 @@ function CliApp() {
|
|
|
16912
16842
|
isError = true;
|
|
16913
16843
|
}
|
|
16914
16844
|
const userMessage = {
|
|
16915
|
-
id:
|
|
16845
|
+
id: uuidv411(),
|
|
16916
16846
|
role: "user",
|
|
16917
16847
|
content: `$ ${command}`,
|
|
16918
16848
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
16919
16849
|
};
|
|
16920
16850
|
const assistantMessage = {
|
|
16921
|
-
id:
|
|
16851
|
+
id: uuidv411(),
|
|
16922
16852
|
role: "assistant",
|
|
16923
16853
|
content: isError ? `\u274C Error:
|
|
16924
16854
|
${output}` : output.trim() || "(no output)",
|
|
@@ -17387,7 +17317,7 @@ Keyboard Shortcuts:
|
|
|
17387
17317
|
console.clear();
|
|
17388
17318
|
const model = state.session?.model || state.config?.defaultModel || "claude-sonnet";
|
|
17389
17319
|
const newSession = {
|
|
17390
|
-
id:
|
|
17320
|
+
id: uuidv411(),
|
|
17391
17321
|
name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
17392
17322
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17393
17323
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -17868,6 +17798,201 @@ No usage data available for the last ${USAGE_DAYS} days.`);
|
|
|
17868
17798
|
}
|
|
17869
17799
|
break;
|
|
17870
17800
|
}
|
|
17801
|
+
// --- Sandbox commands ---
|
|
17802
|
+
case "sandbox": {
|
|
17803
|
+
if (!state.sandboxOrchestrator) {
|
|
17804
|
+
console.log("\nSandbox: Not initialized");
|
|
17805
|
+
break;
|
|
17806
|
+
}
|
|
17807
|
+
const status = state.sandboxOrchestrator.getStatus();
|
|
17808
|
+
console.log("\nSandbox Status:");
|
|
17809
|
+
console.log(` Enabled: ${status.enabled ? "Yes" : "No"}`);
|
|
17810
|
+
console.log(` Mode: ${status.mode}`);
|
|
17811
|
+
console.log(` Platform: ${status.platform ?? "N/A"}`);
|
|
17812
|
+
console.log(` Runtime: ${status.runtimeName ?? "N/A"}`);
|
|
17813
|
+
console.log(` Available: ${status.runtimeAvailable ? "Yes" : "No"}`);
|
|
17814
|
+
console.log("");
|
|
17815
|
+
console.log("Filesystem Config:");
|
|
17816
|
+
console.log(` Write only to CWD: ${status.config.filesystem.writeOnlyToWorkingDir}`);
|
|
17817
|
+
console.log(` Allowed reads: ${status.config.filesystem.allowedReadPaths.join(", ") || "(none)"}`);
|
|
17818
|
+
console.log(` Denied paths: ${status.config.filesystem.deniedPaths.join(", ") || "(none)"}`);
|
|
17819
|
+
console.log(` Excluded cmds: ${status.config.excludedCommands.join(", ") || "(none)"}`);
|
|
17820
|
+
console.log("");
|
|
17821
|
+
console.log("Network Filtering:");
|
|
17822
|
+
console.log(` Enabled: ${status.config.network.enabled ? "Yes" : "No"}`);
|
|
17823
|
+
console.log(` Proxy: ${status.proxyRunning ? `running on port ${status.proxyPort}` : "stopped"}`);
|
|
17824
|
+
console.log(` Domains: ${status.config.network.allowedDomains.length} allowed`);
|
|
17825
|
+
console.log("");
|
|
17826
|
+
if (status.stats) {
|
|
17827
|
+
const total = status.stats.sandboxed + status.stats.unsandboxed + status.stats.blocked;
|
|
17828
|
+
console.log("Session Stats:");
|
|
17829
|
+
console.log(` Total commands: ${total}`);
|
|
17830
|
+
console.log(` Sandboxed: ${status.stats.sandboxed}`);
|
|
17831
|
+
console.log(` Unsandboxed: ${status.stats.unsandboxed}`);
|
|
17832
|
+
console.log(` Blocked: ${status.stats.blocked}`);
|
|
17833
|
+
console.log(` Violations: ${status.stats.violations}`);
|
|
17834
|
+
console.log("");
|
|
17835
|
+
}
|
|
17836
|
+
break;
|
|
17837
|
+
}
|
|
17838
|
+
case "sandbox:enable": {
|
|
17839
|
+
if (!state.sandboxOrchestrator) {
|
|
17840
|
+
console.log("Sandbox not initialized");
|
|
17841
|
+
break;
|
|
17842
|
+
}
|
|
17843
|
+
if (!state.sandboxOrchestrator.isAvailable()) {
|
|
17844
|
+
console.log("Sandbox runtime not available on this platform");
|
|
17845
|
+
break;
|
|
17846
|
+
}
|
|
17847
|
+
state.sandboxOrchestrator.setMode("auto-allow");
|
|
17848
|
+
state.permissionManager?.setSandboxState("auto-allow", state.sandboxOrchestrator.isActive());
|
|
17849
|
+
const sandboxCfg = state.sandboxOrchestrator.getConfig();
|
|
17850
|
+
if (sandboxCfg.network.enabled) {
|
|
17851
|
+
await state.sandboxOrchestrator.startProxy();
|
|
17852
|
+
const pm = state.sandboxOrchestrator.getProxyManager();
|
|
17853
|
+
if (pm?.isRunning()) {
|
|
17854
|
+
console.log(`\u{1F310} Network proxy started on port ${pm.getPort()}`);
|
|
17855
|
+
}
|
|
17856
|
+
}
|
|
17857
|
+
const config = await state.configStore.get();
|
|
17858
|
+
await state.configStore.save({
|
|
17859
|
+
...config,
|
|
17860
|
+
sandbox: { ...state.sandboxOrchestrator.getConfig() }
|
|
17861
|
+
});
|
|
17862
|
+
console.log("Sandbox enabled (auto-allow mode)");
|
|
17863
|
+
break;
|
|
17864
|
+
}
|
|
17865
|
+
case "sandbox:disable": {
|
|
17866
|
+
if (!state.sandboxOrchestrator) {
|
|
17867
|
+
console.log("Sandbox not initialized");
|
|
17868
|
+
break;
|
|
17869
|
+
}
|
|
17870
|
+
await state.sandboxOrchestrator.stopProxy();
|
|
17871
|
+
state.sandboxOrchestrator.setMode("disabled");
|
|
17872
|
+
state.permissionManager?.setSandboxState("disabled", false);
|
|
17873
|
+
const disableConfig = await state.configStore.get();
|
|
17874
|
+
await state.configStore.save({
|
|
17875
|
+
...disableConfig,
|
|
17876
|
+
sandbox: { ...state.sandboxOrchestrator.getConfig() }
|
|
17877
|
+
});
|
|
17878
|
+
console.log("Sandbox disabled");
|
|
17879
|
+
break;
|
|
17880
|
+
}
|
|
17881
|
+
case "sandbox:mode": {
|
|
17882
|
+
if (!state.sandboxOrchestrator) {
|
|
17883
|
+
console.log("Sandbox not initialized");
|
|
17884
|
+
break;
|
|
17885
|
+
}
|
|
17886
|
+
const modeArg = args[0];
|
|
17887
|
+
if (modeArg !== "auto-allow" && modeArg !== "permissions") {
|
|
17888
|
+
console.log("Usage: /sandbox:mode <auto-allow|permissions>");
|
|
17889
|
+
break;
|
|
17890
|
+
}
|
|
17891
|
+
if (!state.sandboxOrchestrator.isAvailable()) {
|
|
17892
|
+
console.log("Sandbox runtime not available on this platform");
|
|
17893
|
+
break;
|
|
17894
|
+
}
|
|
17895
|
+
state.sandboxOrchestrator.setMode(modeArg);
|
|
17896
|
+
state.permissionManager?.setSandboxState(modeArg, state.sandboxOrchestrator.isActive());
|
|
17897
|
+
const modeConfig = await state.configStore.get();
|
|
17898
|
+
await state.configStore.save({
|
|
17899
|
+
...modeConfig,
|
|
17900
|
+
sandbox: { ...state.sandboxOrchestrator.getConfig() }
|
|
17901
|
+
});
|
|
17902
|
+
console.log(`Sandbox mode set to: ${modeArg}`);
|
|
17903
|
+
break;
|
|
17904
|
+
}
|
|
17905
|
+
case "sandbox:trust-domain": {
|
|
17906
|
+
if (!state.sandboxOrchestrator) {
|
|
17907
|
+
console.log("Sandbox not initialized");
|
|
17908
|
+
break;
|
|
17909
|
+
}
|
|
17910
|
+
if (args.length === 0) {
|
|
17911
|
+
console.log("Usage: /sandbox:trust-domain <domain> [...]");
|
|
17912
|
+
break;
|
|
17913
|
+
}
|
|
17914
|
+
const proxyMgr = state.sandboxOrchestrator.getProxyManager();
|
|
17915
|
+
if (!proxyMgr) {
|
|
17916
|
+
console.log("Network proxy not initialized");
|
|
17917
|
+
break;
|
|
17918
|
+
}
|
|
17919
|
+
for (const domain of args) {
|
|
17920
|
+
proxyMgr.addAllowedDomain(domain);
|
|
17921
|
+
console.log(` Added: ${domain}`);
|
|
17922
|
+
}
|
|
17923
|
+
const trustDomainConfig = await state.configStore.get();
|
|
17924
|
+
const currentSandboxConfig = state.sandboxOrchestrator.getConfig();
|
|
17925
|
+
await state.configStore.save({
|
|
17926
|
+
...trustDomainConfig,
|
|
17927
|
+
sandbox: {
|
|
17928
|
+
...currentSandboxConfig,
|
|
17929
|
+
network: {
|
|
17930
|
+
...currentSandboxConfig.network,
|
|
17931
|
+
allowedDomains: proxyMgr.getAllowedDomains()
|
|
17932
|
+
}
|
|
17933
|
+
}
|
|
17934
|
+
});
|
|
17935
|
+
console.log(`Trusted ${args.length} domain(s)`);
|
|
17936
|
+
break;
|
|
17937
|
+
}
|
|
17938
|
+
case "sandbox:domains": {
|
|
17939
|
+
if (!state.sandboxOrchestrator) {
|
|
17940
|
+
console.log("Sandbox not initialized");
|
|
17941
|
+
break;
|
|
17942
|
+
}
|
|
17943
|
+
const domainProxyMgr = state.sandboxOrchestrator.getProxyManager();
|
|
17944
|
+
if (!domainProxyMgr) {
|
|
17945
|
+
console.log("Network proxy not initialized");
|
|
17946
|
+
break;
|
|
17947
|
+
}
|
|
17948
|
+
const domains = domainProxyMgr.getAllowedDomains().sort();
|
|
17949
|
+
console.log(`
|
|
17950
|
+
Allowed domains (${domains.length}):`);
|
|
17951
|
+
for (const d of domains) {
|
|
17952
|
+
console.log(` ${d}`);
|
|
17953
|
+
}
|
|
17954
|
+
console.log("");
|
|
17955
|
+
break;
|
|
17956
|
+
}
|
|
17957
|
+
case "sandbox:violations": {
|
|
17958
|
+
const vStore = state.sandboxOrchestrator?.getViolationStore();
|
|
17959
|
+
if (!vStore) {
|
|
17960
|
+
console.log("Violation tracking not initialized");
|
|
17961
|
+
break;
|
|
17962
|
+
}
|
|
17963
|
+
const requestedCount = parseInt(args[0], 10);
|
|
17964
|
+
const violations = await vStore.getRecent(Number.isNaN(requestedCount) ? 20 : requestedCount);
|
|
17965
|
+
if (violations.length === 0) {
|
|
17966
|
+
console.log("\nNo sandbox violations recorded.\n");
|
|
17967
|
+
break;
|
|
17968
|
+
}
|
|
17969
|
+
console.log(`
|
|
17970
|
+
\x1B[1mRecent Sandbox Violations\x1B[0m (${violations.length}):
|
|
17971
|
+
`);
|
|
17972
|
+
for (const v of violations) {
|
|
17973
|
+
const time = new Date(v.timestamp).toLocaleTimeString();
|
|
17974
|
+
const typeColor = v.type === "filesystem" ? "\x1B[33m" : "\x1B[36m";
|
|
17975
|
+
const typeLabel = v.type === "filesystem" ? "FS " : "NET";
|
|
17976
|
+
const target = v.path ?? v.domain ?? "";
|
|
17977
|
+
console.log(` ${typeColor}${typeLabel}\x1B[0m ${time} \x1B[90m${v.blockedBy}\x1B[0m ${target}`);
|
|
17978
|
+
if (v.detail) {
|
|
17979
|
+
console.log(` \x1B[90m${v.detail.slice(0, 120)}\x1B[0m`);
|
|
17980
|
+
}
|
|
17981
|
+
}
|
|
17982
|
+
console.log("");
|
|
17983
|
+
break;
|
|
17984
|
+
}
|
|
17985
|
+
case "sandbox:violations:clear": {
|
|
17986
|
+
const clearStore = state.sandboxOrchestrator?.getViolationStore();
|
|
17987
|
+
if (!clearStore) {
|
|
17988
|
+
console.log("Violation tracking not initialized");
|
|
17989
|
+
break;
|
|
17990
|
+
}
|
|
17991
|
+
await clearStore.clear();
|
|
17992
|
+
state.sandboxOrchestrator?.resetStats();
|
|
17993
|
+
console.log("Violation log cleared.");
|
|
17994
|
+
break;
|
|
17995
|
+
}
|
|
17871
17996
|
default:
|
|
17872
17997
|
console.log(`Unknown command: /${command}`);
|
|
17873
17998
|
console.log("Type /help for available commands");
|
|
@@ -17892,9 +18017,9 @@ No usage data available for the last ${USAGE_DAYS} days.`);
|
|
|
17892
18017
|
return { ...prev, config: updatedConfig };
|
|
17893
18018
|
});
|
|
17894
18019
|
if (modelChanged && state.agent) {
|
|
17895
|
-
const
|
|
17896
|
-
if (
|
|
17897
|
-
|
|
18020
|
+
const llm = state.agent.context.llm;
|
|
18021
|
+
if (llm) {
|
|
18022
|
+
llm.currentModel = updatedConfig.defaultModel;
|
|
17898
18023
|
}
|
|
17899
18024
|
}
|
|
17900
18025
|
};
|
|
@@ -18018,6 +18143,7 @@ var isDevMode = import.meta.url.includes("/src/") || process.env.NODE_ENV === "d
|
|
|
18018
18143
|
if (isDevMode) {
|
|
18019
18144
|
logger.debug("\u{1F527} Running in development mode (using TypeScript source)\n");
|
|
18020
18145
|
}
|
|
18146
|
+
warmFileCache();
|
|
18021
18147
|
render(/* @__PURE__ */ React21.createElement(CliApp, null), {
|
|
18022
18148
|
exitOnCtrlC: false
|
|
18023
18149
|
});
|