@tritard/waterbrother 0.8.2 → 0.8.4
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/package.json +1 -1
- package/src/cli.js +11 -0
- package/src/path-utils.js +7 -4
- package/src/tools.js +26 -8
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1682,6 +1682,14 @@ function extractExplicitReadOnlyRoots(line, cwd) {
|
|
|
1682
1682
|
return [...new Set(roots)];
|
|
1683
1683
|
}
|
|
1684
1684
|
|
|
1685
|
+
function extractExplicitWriteRoots(line, cwd) {
|
|
1686
|
+
const text = String(line || "").trim();
|
|
1687
|
+
if (!/\b(create|make|build|write|save|generate|scaffold|edit|update|rewrite|delete|remove|move|rename)\b/i.test(text)) {
|
|
1688
|
+
return [];
|
|
1689
|
+
}
|
|
1690
|
+
return extractExplicitReadOnlyRoots(text, cwd);
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1685
1693
|
function maybeRewriteExplicitInspectionPrompt(promptText, readOnlyRoots = []) {
|
|
1686
1694
|
const text = String(promptText || "").trim();
|
|
1687
1695
|
if (readOnlyRoots.length === 0) return text;
|
|
@@ -3308,8 +3316,10 @@ async function runTextTurnInteractive({
|
|
|
3308
3316
|
spinnerLabel = "thinking..."
|
|
3309
3317
|
}) {
|
|
3310
3318
|
const readOnlyRoots = extractExplicitReadOnlyRoots(promptText, context.cwd);
|
|
3319
|
+
const writeRoots = extractExplicitWriteRoots(promptText, context.cwd);
|
|
3311
3320
|
const effectivePromptText = maybeRewriteExplicitInspectionPrompt(promptText, readOnlyRoots);
|
|
3312
3321
|
agent.toolRuntime.setReadOnlyRoots(readOnlyRoots);
|
|
3322
|
+
agent.toolRuntime.setWriteRoots(writeRoots);
|
|
3313
3323
|
await maybeAutoCompactConversation({
|
|
3314
3324
|
agent,
|
|
3315
3325
|
currentSession,
|
|
@@ -3459,6 +3469,7 @@ async function runTextTurnInteractive({
|
|
|
3459
3469
|
detachInterruptListener();
|
|
3460
3470
|
spinner.stop();
|
|
3461
3471
|
agent.toolRuntime.setReadOnlyRoots([]);
|
|
3472
|
+
agent.toolRuntime.setWriteRoots([]);
|
|
3462
3473
|
}
|
|
3463
3474
|
|
|
3464
3475
|
context.lastUsage = response?.usage || null;
|
package/src/path-utils.js
CHANGED
|
@@ -39,8 +39,8 @@ function isAllowedReadOnlyHomePath(targetPath) {
|
|
|
39
39
|
return COMMON_READ_ONLY_HOME_ROOTS.some((rootPath) => isWithinPath(rootPath, targetPath));
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
function
|
|
43
|
-
return
|
|
42
|
+
function isAllowedExplicitPath(targetPath, allowedRoots = []) {
|
|
43
|
+
return allowedRoots.some((rootPath) => {
|
|
44
44
|
if (!rootPath) return false;
|
|
45
45
|
const resolvedRoot = canonicalizePath(String(rootPath));
|
|
46
46
|
return isWithinPath(resolvedRoot, targetPath);
|
|
@@ -65,11 +65,14 @@ export function resolveSandboxPath(baseDir, targetPath, allowOutsideCwd = false,
|
|
|
65
65
|
const rel = path.relative(baseReal, resolved);
|
|
66
66
|
const outside = rel.startsWith("..") || path.isAbsolute(rel);
|
|
67
67
|
const allowExplicitReadOnly =
|
|
68
|
-
Array.isArray(options.allowedReadOnlyRoots) &&
|
|
68
|
+
Array.isArray(options.allowedReadOnlyRoots) && isAllowedExplicitPath(resolved, options.allowedReadOnlyRoots);
|
|
69
|
+
const allowExplicitWrite =
|
|
70
|
+
Array.isArray(options.allowedWriteRoots) && isAllowedExplicitPath(resolved, options.allowedWriteRoots);
|
|
69
71
|
if (
|
|
70
72
|
outside &&
|
|
71
73
|
!(options.allowReadOnlyHome === true && isAllowedReadOnlyHomePath(resolved)) &&
|
|
72
|
-
!allowExplicitReadOnly
|
|
74
|
+
!allowExplicitReadOnly &&
|
|
75
|
+
!allowExplicitWrite
|
|
73
76
|
) {
|
|
74
77
|
throw new Error(`Path is outside cwd sandbox: ${targetPath}`);
|
|
75
78
|
}
|
package/src/tools.js
CHANGED
|
@@ -5,7 +5,7 @@ import crypto from "node:crypto";
|
|
|
5
5
|
import { exec } from "node:child_process";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
import { normalizeAutonomyMode, normalizeExperienceMode } from "./modes.js";
|
|
8
|
-
import { resolveSandboxPath } from "./path-utils.js";
|
|
8
|
+
import { expandHomePath, resolveSandboxPath } from "./path-utils.js";
|
|
9
9
|
import { promptKeyChoice, promptLine } from "./prompt.js";
|
|
10
10
|
import { McpManager } from "./mcp.js";
|
|
11
11
|
|
|
@@ -394,8 +394,8 @@ function globToRegExp(glob) {
|
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
function matchesPattern(value, pattern) {
|
|
397
|
-
const normalizedValue =
|
|
398
|
-
const normalizedPattern =
|
|
397
|
+
const normalizedValue = normalizePathPattern(value);
|
|
398
|
+
const normalizedPattern = normalizePathPattern(pattern);
|
|
399
399
|
return globToRegExp(normalizedPattern).test(normalizedValue);
|
|
400
400
|
}
|
|
401
401
|
|
|
@@ -409,7 +409,14 @@ function extractPatchPaths(patchText) {
|
|
|
409
409
|
}
|
|
410
410
|
|
|
411
411
|
function normalizePathList(values = []) {
|
|
412
|
-
return uniqueStrings(values).map((item) => item
|
|
412
|
+
return uniqueStrings(values).map((item) => normalizePathPattern(item));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function normalizePathPattern(value) {
|
|
416
|
+
const raw = String(value || "").trim();
|
|
417
|
+
if (!raw) return "";
|
|
418
|
+
const expanded = expandHomePath(raw);
|
|
419
|
+
return String(expanded).replace(/^\.\//, "").replace(/\\/g, "/");
|
|
413
420
|
}
|
|
414
421
|
|
|
415
422
|
function looksLikeMutatingShellCommand(command) {
|
|
@@ -891,6 +898,7 @@ export function createToolRuntime({
|
|
|
891
898
|
let checkpointCreatedThisTurn = false;
|
|
892
899
|
let currentTurn = buildDefaultTurnRecord();
|
|
893
900
|
let currentReadOnlyRoots = [];
|
|
901
|
+
let currentWriteRoots = [];
|
|
894
902
|
let currentExperienceMode = normalizeExperienceMode(experienceMode);
|
|
895
903
|
let currentAutonomyMode = normalizeAutonomyMode(autonomyMode);
|
|
896
904
|
let currentRequireTurnContracts = requireTurnContracts !== false;
|
|
@@ -1326,7 +1334,9 @@ ${clipped}`;
|
|
|
1326
1334
|
}
|
|
1327
1335
|
|
|
1328
1336
|
async function writeFile(args = {}) {
|
|
1329
|
-
const filePath = resolveSandboxPath(cwd, args.path, allowOutsideCwd
|
|
1337
|
+
const filePath = resolveSandboxPath(cwd, args.path, allowOutsideCwd, {
|
|
1338
|
+
allowedWriteRoots: currentWriteRoots
|
|
1339
|
+
});
|
|
1330
1340
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
1331
1341
|
if (args.mode === "append") {
|
|
1332
1342
|
await fs.appendFile(filePath, args.content, "utf8");
|
|
@@ -1383,7 +1393,9 @@ ${clipped}`;
|
|
|
1383
1393
|
}
|
|
1384
1394
|
|
|
1385
1395
|
async function replaceInFile(args = {}) {
|
|
1386
|
-
const filePath = resolveSandboxPath(cwd, args.path, allowOutsideCwd
|
|
1396
|
+
const filePath = resolveSandboxPath(cwd, args.path, allowOutsideCwd, {
|
|
1397
|
+
allowedWriteRoots: currentWriteRoots
|
|
1398
|
+
});
|
|
1387
1399
|
const source = await fs.readFile(filePath, "utf8");
|
|
1388
1400
|
|
|
1389
1401
|
let next;
|
|
@@ -1564,13 +1576,17 @@ ${clipped}`;
|
|
|
1564
1576
|
}
|
|
1565
1577
|
|
|
1566
1578
|
async function makeDirectory(args = {}) {
|
|
1567
|
-
const target = resolveSandboxPath(cwd, args.path, allowOutsideCwd
|
|
1579
|
+
const target = resolveSandboxPath(cwd, args.path, allowOutsideCwd, {
|
|
1580
|
+
allowedWriteRoots: currentWriteRoots
|
|
1581
|
+
});
|
|
1568
1582
|
await fs.mkdir(target, { recursive: true });
|
|
1569
1583
|
return pretty({ ok: true, path: args.path });
|
|
1570
1584
|
}
|
|
1571
1585
|
|
|
1572
1586
|
async function deletePath(args = {}) {
|
|
1573
|
-
const target = resolveSandboxPath(cwd, args.path, allowOutsideCwd
|
|
1587
|
+
const target = resolveSandboxPath(cwd, args.path, allowOutsideCwd, {
|
|
1588
|
+
allowedWriteRoots: currentWriteRoots
|
|
1589
|
+
});
|
|
1574
1590
|
if (target === cwd) throw new Error("Refusing to delete cwd root");
|
|
1575
1591
|
await fs.rm(target, { recursive: Boolean(args.recursive), force: false });
|
|
1576
1592
|
return pretty({ ok: true, path: args.path, recursive: Boolean(args.recursive) });
|
|
@@ -2181,6 +2197,8 @@ ${clipped}`;
|
|
|
2181
2197
|
getShellCwd,
|
|
2182
2198
|
setReadOnlyRoots(paths = []) { currentReadOnlyRoots = normalizePathList(paths); },
|
|
2183
2199
|
getReadOnlyRoots() { return [...currentReadOnlyRoots]; },
|
|
2200
|
+
setWriteRoots(paths = []) { currentWriteRoots = normalizePathList(paths); },
|
|
2201
|
+
getWriteRoots() { return [...currentWriteRoots]; },
|
|
2184
2202
|
setExperienceMode(mode) { currentExperienceMode = normalizeExperienceMode(mode); },
|
|
2185
2203
|
getExperienceMode() { return currentExperienceMode; },
|
|
2186
2204
|
setAutonomyMode(mode) { currentAutonomyMode = normalizeAutonomyMode(mode); },
|