@tritard/waterbrother 0.8.3 → 0.8.5
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 +42 -4
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
|
@@ -517,6 +517,27 @@ function getTouchedPathsForTool(toolName, args = {}) {
|
|
|
517
517
|
return [];
|
|
518
518
|
}
|
|
519
519
|
|
|
520
|
+
function inferContractForTool(toolName, args = {}) {
|
|
521
|
+
const paths = getTouchedPathsForTool(toolName, args);
|
|
522
|
+
if (paths.length === 0) return null;
|
|
523
|
+
if (toolName === "run_shell" || toolName === "restore_checkpoint") return null;
|
|
524
|
+
const summaries = {
|
|
525
|
+
write_file: `Write ${paths.join(", ")}`,
|
|
526
|
+
replace_in_file: `Edit ${paths.join(", ")}`,
|
|
527
|
+
make_directory: `Create ${paths.join(", ")}`,
|
|
528
|
+
delete_path: `Delete ${paths.join(", ")}`,
|
|
529
|
+
apply_patch: `Patch ${paths.join(", ")}`
|
|
530
|
+
};
|
|
531
|
+
return {
|
|
532
|
+
summary: summaries[toolName] || `Mutate ${paths.join(", ")}`,
|
|
533
|
+
paths,
|
|
534
|
+
commands: [],
|
|
535
|
+
verification: [],
|
|
536
|
+
risk: toolName === "delete_path" ? "high" : "medium",
|
|
537
|
+
inferred: true
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
520
541
|
function buildDefaultTurnRecord() {
|
|
521
542
|
return {
|
|
522
543
|
startedAt: new Date().toISOString(),
|
|
@@ -898,6 +919,7 @@ export function createToolRuntime({
|
|
|
898
919
|
let checkpointCreatedThisTurn = false;
|
|
899
920
|
let currentTurn = buildDefaultTurnRecord();
|
|
900
921
|
let currentReadOnlyRoots = [];
|
|
922
|
+
let currentWriteRoots = [];
|
|
901
923
|
let currentExperienceMode = normalizeExperienceMode(experienceMode);
|
|
902
924
|
let currentAutonomyMode = normalizeAutonomyMode(autonomyMode);
|
|
903
925
|
let currentRequireTurnContracts = requireTurnContracts !== false;
|
|
@@ -1333,7 +1355,9 @@ ${clipped}`;
|
|
|
1333
1355
|
}
|
|
1334
1356
|
|
|
1335
1357
|
async function writeFile(args = {}) {
|
|
1336
|
-
const filePath = resolveSandboxPath(cwd, args.path, allowOutsideCwd
|
|
1358
|
+
const filePath = resolveSandboxPath(cwd, args.path, allowOutsideCwd, {
|
|
1359
|
+
allowedWriteRoots: currentWriteRoots
|
|
1360
|
+
});
|
|
1337
1361
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
1338
1362
|
if (args.mode === "append") {
|
|
1339
1363
|
await fs.appendFile(filePath, args.content, "utf8");
|
|
@@ -1390,7 +1414,9 @@ ${clipped}`;
|
|
|
1390
1414
|
}
|
|
1391
1415
|
|
|
1392
1416
|
async function replaceInFile(args = {}) {
|
|
1393
|
-
const filePath = resolveSandboxPath(cwd, args.path, allowOutsideCwd
|
|
1417
|
+
const filePath = resolveSandboxPath(cwd, args.path, allowOutsideCwd, {
|
|
1418
|
+
allowedWriteRoots: currentWriteRoots
|
|
1419
|
+
});
|
|
1394
1420
|
const source = await fs.readFile(filePath, "utf8");
|
|
1395
1421
|
|
|
1396
1422
|
let next;
|
|
@@ -1571,13 +1597,17 @@ ${clipped}`;
|
|
|
1571
1597
|
}
|
|
1572
1598
|
|
|
1573
1599
|
async function makeDirectory(args = {}) {
|
|
1574
|
-
const target = resolveSandboxPath(cwd, args.path, allowOutsideCwd
|
|
1600
|
+
const target = resolveSandboxPath(cwd, args.path, allowOutsideCwd, {
|
|
1601
|
+
allowedWriteRoots: currentWriteRoots
|
|
1602
|
+
});
|
|
1575
1603
|
await fs.mkdir(target, { recursive: true });
|
|
1576
1604
|
return pretty({ ok: true, path: args.path });
|
|
1577
1605
|
}
|
|
1578
1606
|
|
|
1579
1607
|
async function deletePath(args = {}) {
|
|
1580
|
-
const target = resolveSandboxPath(cwd, args.path, allowOutsideCwd
|
|
1608
|
+
const target = resolveSandboxPath(cwd, args.path, allowOutsideCwd, {
|
|
1609
|
+
allowedWriteRoots: currentWriteRoots
|
|
1610
|
+
});
|
|
1581
1611
|
if (target === cwd) throw new Error("Refusing to delete cwd root");
|
|
1582
1612
|
await fs.rm(target, { recursive: Boolean(args.recursive), force: false });
|
|
1583
1613
|
return pretty({ ok: true, path: args.path, recursive: Boolean(args.recursive) });
|
|
@@ -2078,6 +2108,12 @@ ${clipped}`;
|
|
|
2078
2108
|
}
|
|
2079
2109
|
|
|
2080
2110
|
if (currentRequireTurnContracts && toolRequiresContract(name, args)) {
|
|
2111
|
+
if (!currentTurn.contract) {
|
|
2112
|
+
const inferred = inferContractForTool(name, args);
|
|
2113
|
+
if (inferred) {
|
|
2114
|
+
currentTurn.contract = inferred;
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2081
2117
|
const contractDecision = contractAllows(name, args);
|
|
2082
2118
|
if (!contractDecision.ok) {
|
|
2083
2119
|
return pretty({
|
|
@@ -2188,6 +2224,8 @@ ${clipped}`;
|
|
|
2188
2224
|
getShellCwd,
|
|
2189
2225
|
setReadOnlyRoots(paths = []) { currentReadOnlyRoots = normalizePathList(paths); },
|
|
2190
2226
|
getReadOnlyRoots() { return [...currentReadOnlyRoots]; },
|
|
2227
|
+
setWriteRoots(paths = []) { currentWriteRoots = normalizePathList(paths); },
|
|
2228
|
+
getWriteRoots() { return [...currentWriteRoots]; },
|
|
2191
2229
|
setExperienceMode(mode) { currentExperienceMode = normalizeExperienceMode(mode); },
|
|
2192
2230
|
getExperienceMode() { return currentExperienceMode; },
|
|
2193
2231
|
setAutonomyMode(mode) { currentAutonomyMode = normalizeAutonomyMode(mode); },
|