@tritard/waterbrother 0.8.6 → 0.8.8
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/path-utils.js +16 -2
- package/src/tools.js +12 -3
package/package.json
CHANGED
package/src/path-utils.js
CHANGED
|
@@ -8,6 +8,11 @@ const COMMON_HOME_ALIASES = new Map([
|
|
|
8
8
|
["/documents", path.join(os.homedir(), "Documents")]
|
|
9
9
|
]);
|
|
10
10
|
|
|
11
|
+
const COMMON_HOME_PATHS = ["Desktop", "Downloads", "Documents"].map((name) => ({
|
|
12
|
+
lower: path.join(os.homedir(), name).toLowerCase(),
|
|
13
|
+
actual: path.join(os.homedir(), name)
|
|
14
|
+
}));
|
|
15
|
+
|
|
11
16
|
const COMMON_READ_ONLY_HOME_ROOTS = ["Desktop", "Downloads", "Documents"].map((name) => path.join(os.homedir(), name));
|
|
12
17
|
|
|
13
18
|
function isWithinPath(rootPath, targetPath) {
|
|
@@ -35,6 +40,10 @@ function canonicalizePath(targetPath) {
|
|
|
35
40
|
return path.join(canonicalParent, path.basename(resolved));
|
|
36
41
|
}
|
|
37
42
|
|
|
43
|
+
export function canonicalizeLoosePath(targetPath) {
|
|
44
|
+
return canonicalizePath(expandHomePath(targetPath || "."));
|
|
45
|
+
}
|
|
46
|
+
|
|
38
47
|
function isAllowedReadOnlyHomePath(targetPath) {
|
|
39
48
|
return COMMON_READ_ONLY_HOME_ROOTS.some((rootPath) => isWithinPath(rootPath, targetPath));
|
|
40
49
|
}
|
|
@@ -58,15 +67,20 @@ export function expandHomePath(inputPath) {
|
|
|
58
67
|
return path.join(replacement, raw.slice(prefix.length + 1));
|
|
59
68
|
}
|
|
60
69
|
}
|
|
70
|
+
for (const { lower, actual } of COMMON_HOME_PATHS) {
|
|
71
|
+
if (lowered === lower) return actual;
|
|
72
|
+
if (lowered.startsWith(`${lower}/`)) {
|
|
73
|
+
return path.join(actual, raw.slice(lower.length + 1));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
61
76
|
if (raw === "~") return os.homedir();
|
|
62
77
|
if (raw.startsWith("~/")) return path.join(os.homedir(), raw.slice(2));
|
|
63
78
|
return raw;
|
|
64
79
|
}
|
|
65
80
|
|
|
66
81
|
export function resolveSandboxPath(baseDir, targetPath, allowOutsideCwd = false, options = {}) {
|
|
67
|
-
const expanded = expandHomePath(targetPath || ".");
|
|
68
82
|
const baseReal = canonicalizePath(baseDir);
|
|
69
|
-
const resolved = canonicalizePath(path.resolve(baseReal,
|
|
83
|
+
const resolved = canonicalizePath(path.resolve(baseReal, expandHomePath(targetPath || ".")));
|
|
70
84
|
if (!allowOutsideCwd) {
|
|
71
85
|
const rel = path.relative(baseReal, resolved);
|
|
72
86
|
const outside = rel.startsWith("..") || path.isAbsolute(rel);
|
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 { expandHomePath, resolveSandboxPath } from "./path-utils.js";
|
|
8
|
+
import { canonicalizeLoosePath, expandHomePath, resolveSandboxPath } from "./path-utils.js";
|
|
9
9
|
import { promptKeyChoice, promptLine } from "./prompt.js";
|
|
10
10
|
import { McpManager } from "./mcp.js";
|
|
11
11
|
|
|
@@ -416,7 +416,11 @@ function normalizePathPattern(value) {
|
|
|
416
416
|
const raw = String(value || "").trim();
|
|
417
417
|
if (!raw) return "";
|
|
418
418
|
const expanded = expandHomePath(raw);
|
|
419
|
-
|
|
419
|
+
const text = String(expanded);
|
|
420
|
+
const normalized = (text.startsWith("/") || raw.startsWith("~"))
|
|
421
|
+
? canonicalizeLoosePath(text)
|
|
422
|
+
: text;
|
|
423
|
+
return String(normalized).replace(/^\.\//, "").replace(/\\/g, "/");
|
|
420
424
|
}
|
|
421
425
|
|
|
422
426
|
function looksLikeMutatingShellCommand(command) {
|
|
@@ -1036,7 +1040,11 @@ export function createToolRuntime({
|
|
|
1036
1040
|
function contractAllows(toolName, args = {}) {
|
|
1037
1041
|
if (!currentTurn.contract) return { ok: !toolRequiresContract(toolName, args), reason: "No active contract" };
|
|
1038
1042
|
const touchedPaths = getTouchedPathsForTool(toolName, args);
|
|
1039
|
-
|
|
1043
|
+
const allowedPaths = normalizePathList([
|
|
1044
|
+
...(Array.isArray(currentTurn.contract.paths) ? currentTurn.contract.paths : []),
|
|
1045
|
+
...getCurrentWriteRoots()
|
|
1046
|
+
]);
|
|
1047
|
+
if (touchedPaths.length > 0 && !touchedPaths.every((item) => matchesAnyPattern(item, allowedPaths))) {
|
|
1040
1048
|
return { ok: false, reason: `Action outside declared contract scope: ${touchedPaths.join(", ")}` };
|
|
1041
1049
|
}
|
|
1042
1050
|
if (toolName === "run_shell") {
|
|
@@ -2201,6 +2209,7 @@ ${clipped}`;
|
|
|
2201
2209
|
function beginTurn() {
|
|
2202
2210
|
checkpointCreatedThisTurn = false;
|
|
2203
2211
|
currentReadOnlyRoots = [];
|
|
2212
|
+
currentWriteRoots = [];
|
|
2204
2213
|
currentTurn = buildDefaultTurnRecord();
|
|
2205
2214
|
}
|
|
2206
2215
|
|