@letrquan/book 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2747 -40
- package/README.md +132 -1311
- package/dist/additional-roots-H7UNAEHS.js +31 -0
- package/dist/{app-MOXXWEOY.js → app-GE4OHJOC.js} +5740 -4045
- package/dist/app-GE4OHJOC.js.map +1 -0
- package/dist/{chunk-5GDZ22YP.js → chunk-224FJZLH.js} +99 -108
- package/dist/chunk-224FJZLH.js.map +1 -0
- package/dist/chunk-2AAOF5B5.js +28 -0
- package/dist/chunk-2AAOF5B5.js.map +1 -0
- package/dist/{chunk-CAGOB2N4.js → chunk-4AS7LKKQ.js} +6 -4
- package/dist/chunk-4AS7LKKQ.js.map +1 -0
- package/dist/chunk-525CAZPY.js +120 -0
- package/dist/chunk-525CAZPY.js.map +1 -0
- package/dist/{chunk-URLQAXTR.js → chunk-56SIEMDX.js} +2 -2
- package/dist/chunk-56SIEMDX.js.map +1 -0
- package/dist/{chunk-XIFFTATU.js → chunk-5YHTKWJN.js} +5420 -4031
- package/dist/chunk-5YHTKWJN.js.map +1 -0
- package/dist/chunk-62QL5QUP.js +283 -0
- package/dist/chunk-62QL5QUP.js.map +1 -0
- package/dist/{chunk-KWHQ2XXL.js → chunk-67UZEWWF.js} +3 -3
- package/dist/chunk-67UZEWWF.js.map +1 -0
- package/dist/chunk-6OQQOMZR.js +785 -0
- package/dist/chunk-6OQQOMZR.js.map +1 -0
- package/dist/{chunk-GTVB4HEM.js → chunk-AQEQX4FF.js} +344 -315
- package/dist/chunk-AQEQX4FF.js.map +1 -0
- package/dist/chunk-C7ADUNPR.js +273 -0
- package/dist/chunk-C7ADUNPR.js.map +1 -0
- package/dist/{chunk-E75IPST5.js → chunk-EFJ2NH6E.js} +12 -58
- package/dist/chunk-EFJ2NH6E.js.map +1 -0
- package/dist/{chunk-5KLVH3PY.js → chunk-HQI7NYGX.js} +5 -3
- package/dist/chunk-HQI7NYGX.js.map +1 -0
- package/dist/chunk-IME7R2UI.js +6541 -0
- package/dist/chunk-IME7R2UI.js.map +1 -0
- package/dist/chunk-J4F2QLM2.js +273 -0
- package/dist/chunk-J4F2QLM2.js.map +1 -0
- package/dist/{chunk-MAQXCUR4.js → chunk-J62VZYOB.js} +444 -112
- package/dist/chunk-J62VZYOB.js.map +1 -0
- package/dist/chunk-JTPOI3KU.js +826 -0
- package/dist/chunk-JTPOI3KU.js.map +1 -0
- package/dist/{chunk-WKBO5R4O.js → chunk-KBQS35OV.js} +2 -2
- package/dist/chunk-KBQS35OV.js.map +1 -0
- package/dist/chunk-KXAYUIKP.js +90 -0
- package/dist/chunk-KXAYUIKP.js.map +1 -0
- package/dist/{chunk-VZQXX3WW.js → chunk-QYBD22H5.js} +1 -1
- package/dist/chunk-QYBD22H5.js.map +1 -0
- package/dist/{chunk-V4BOYR52.js → chunk-RFJ6Q6LE.js} +763 -198
- package/dist/chunk-RFJ6Q6LE.js.map +1 -0
- package/dist/{chunk-7N4J557C.js → chunk-RT5SATF7.js} +1 -1
- package/dist/chunk-RT5SATF7.js.map +1 -0
- package/dist/{chunk-C7D4NJ2D.js → chunk-S4JMID4G.js} +68 -26
- package/dist/chunk-S4JMID4G.js.map +1 -0
- package/dist/{chunk-ANSJPYGI.js → chunk-SOW4WRQP.js} +3 -3
- package/dist/chunk-SOW4WRQP.js.map +1 -0
- package/dist/{chunk-J7MIZ5CA.js → chunk-TLX3H2CL.js} +518 -79
- package/dist/chunk-TLX3H2CL.js.map +1 -0
- package/dist/{chunk-DGMQEI6Y.js → chunk-VPNLCMVR.js} +22 -9
- package/dist/chunk-VPNLCMVR.js.map +1 -0
- package/dist/{chunk-BTZZUCLI.js → chunk-W3XFHEF4.js} +173 -29
- package/dist/chunk-W3XFHEF4.js.map +1 -0
- package/dist/{chunk-574C6MQR.js → chunk-X4NJT436.js} +130 -60
- package/dist/chunk-X4NJT436.js.map +1 -0
- package/dist/chunk-X66I2NQO.js +137 -0
- package/dist/chunk-X66I2NQO.js.map +1 -0
- package/dist/chunk-XHAO3K7I.js +18 -0
- package/dist/chunk-XHAO3K7I.js.map +1 -0
- package/dist/{chunk-3U5IBM24.js → chunk-Y2VHN7TC.js} +2 -2
- package/dist/chunk-Y2VHN7TC.js.map +1 -0
- package/dist/chunk-YC6W4EJH.js +947 -0
- package/dist/chunk-YC6W4EJH.js.map +1 -0
- package/dist/{chunk-24AXE6SP.js → chunk-ZPSZZUWF.js} +1 -1
- package/dist/chunk-ZPSZZUWF.js.map +1 -0
- package/dist/{command-approvals-6Y57VCWZ.js → command-approvals-VBIU6BLM.js} +6 -5
- package/dist/context-CBOBDY5Q.js +38 -0
- package/dist/{hook-approvals-CTUFKEJT.js → hook-approvals-ZVT4V7IM.js} +5 -5
- package/dist/index.js +996 -155
- package/dist/index.js.map +1 -1
- package/dist/{interactive-assets-CCAAWVKH.js → interactive-assets-T4I4SEHU.js} +13 -9
- package/dist/interactive-assets-T4I4SEHU.js.map +1 -0
- package/dist/job-runner.js +159 -111
- package/dist/job-runner.js.map +1 -1
- package/dist/job-supervisor.d.ts +2 -0
- package/dist/job-supervisor.js +112 -0
- package/dist/job-supervisor.js.map +1 -0
- package/dist/loader-S77OVTHH.js +18 -0
- package/dist/{mcp-approvals-WNPH65BK.js → mcp-approvals-HJ2VDYMC.js} +5 -5
- package/dist/{mcp-config-YIJIES2C.js → mcp-config-RNB5LEVE.js} +3 -3
- package/dist/memory-store-DZUT5HUH.js +75 -0
- package/dist/{model-window-store-YFVNXCIP.js → model-window-store-MTSQ5JDJ.js} +8 -4
- package/dist/openai-compatible-2OB54YST.js +14 -0
- package/dist/{permission-approvals-76PVREFD.js → permission-approvals-YLV6UC5W.js} +5 -5
- package/dist/permissions-DHOKF3W2.js +45 -0
- package/dist/sandbox-WRYTZH3D.js +41 -0
- package/dist/{scrollback-LUKLIBVI.js → scrollback-35ME2GFX.js} +32 -22
- package/dist/scrollback-35ME2GFX.js.map +1 -0
- package/dist/sdk.d.ts +2219 -2361
- package/dist/sdk.js +53 -38
- package/dist/sdk.js.map +1 -1
- package/dist/settings-loader-JUNPGAAM.js +35 -0
- package/dist/settings-loader-JUNPGAAM.js.map +1 -0
- package/dist/{shell-selection-V6QAYXPX.js → shell-selection-CDHGJ4GC.js} +2 -2
- package/dist/shell-selection-CDHGJ4GC.js.map +1 -0
- package/package.json +23 -22
- package/dist/app-MOXXWEOY.js.map +0 -1
- package/dist/chunk-24AXE6SP.js.map +0 -1
- package/dist/chunk-3U5IBM24.js.map +0 -1
- package/dist/chunk-574C6MQR.js.map +0 -1
- package/dist/chunk-5GDZ22YP.js.map +0 -1
- package/dist/chunk-5KLVH3PY.js.map +0 -1
- package/dist/chunk-7N4J557C.js.map +0 -1
- package/dist/chunk-A7JLBK2Y.js +0 -3506
- package/dist/chunk-A7JLBK2Y.js.map +0 -1
- package/dist/chunk-ANSJPYGI.js.map +0 -1
- package/dist/chunk-BTZZUCLI.js.map +0 -1
- package/dist/chunk-C7D4NJ2D.js.map +0 -1
- package/dist/chunk-CAGOB2N4.js.map +0 -1
- package/dist/chunk-DGMQEI6Y.js.map +0 -1
- package/dist/chunk-E75IPST5.js.map +0 -1
- package/dist/chunk-EYGD6X2T.js +0 -180
- package/dist/chunk-EYGD6X2T.js.map +0 -1
- package/dist/chunk-GTVB4HEM.js.map +0 -1
- package/dist/chunk-J7MIZ5CA.js.map +0 -1
- package/dist/chunk-KWHQ2XXL.js.map +0 -1
- package/dist/chunk-L6LK2QIQ.js +0 -657
- package/dist/chunk-L6LK2QIQ.js.map +0 -1
- package/dist/chunk-MAQXCUR4.js.map +0 -1
- package/dist/chunk-S4XL7HOM.js +0 -402
- package/dist/chunk-S4XL7HOM.js.map +0 -1
- package/dist/chunk-URLQAXTR.js.map +0 -1
- package/dist/chunk-V4BOYR52.js.map +0 -1
- package/dist/chunk-VZQXX3WW.js.map +0 -1
- package/dist/chunk-WKBO5R4O.js.map +0 -1
- package/dist/chunk-XIFFTATU.js.map +0 -1
- package/dist/context-ZC5IFEAX.js +0 -25
- package/dist/interactive-assets-CCAAWVKH.js.map +0 -1
- package/dist/loader-4IDS2RIE.js +0 -15
- package/dist/openai-compatible-RHZUBHV6.js +0 -10
- package/dist/permissions-CHCKTFQQ.js +0 -28
- package/dist/sandbox-CNVAQDA7.js +0 -23
- package/dist/scrollback-LUKLIBVI.js.map +0 -1
- package/dist/settings-loader-M2QP5TX2.js +0 -21
- package/patches/ink+6.8.0.patch +0 -13
- package/scripts/apply-ink-patch.mjs +0 -29
- package/scripts/ink-patch.mjs +0 -59
- package/scripts/verify-ink-patch.mjs +0 -11
- /package/dist/{command-approvals-6Y57VCWZ.js.map → additional-roots-H7UNAEHS.js.map} +0 -0
- /package/dist/{context-ZC5IFEAX.js.map → command-approvals-VBIU6BLM.js.map} +0 -0
- /package/dist/{hook-approvals-CTUFKEJT.js.map → context-CBOBDY5Q.js.map} +0 -0
- /package/dist/{loader-4IDS2RIE.js.map → hook-approvals-ZVT4V7IM.js.map} +0 -0
- /package/dist/{mcp-approvals-WNPH65BK.js.map → loader-S77OVTHH.js.map} +0 -0
- /package/dist/{mcp-config-YIJIES2C.js.map → mcp-approvals-HJ2VDYMC.js.map} +0 -0
- /package/dist/{model-window-store-YFVNXCIP.js.map → mcp-config-RNB5LEVE.js.map} +0 -0
- /package/dist/{openai-compatible-RHZUBHV6.js.map → memory-store-DZUT5HUH.js.map} +0 -0
- /package/dist/{permission-approvals-76PVREFD.js.map → model-window-store-MTSQ5JDJ.js.map} +0 -0
- /package/dist/{permissions-CHCKTFQQ.js.map → openai-compatible-2OB54YST.js.map} +0 -0
- /package/dist/{sandbox-CNVAQDA7.js.map → permission-approvals-YLV6UC5W.js.map} +0 -0
- /package/dist/{settings-loader-M2QP5TX2.js.map → permissions-DHOKF3W2.js.map} +0 -0
- /package/dist/{shell-selection-V6QAYXPX.js.map → sandbox-WRYTZH3D.js.map} +0 -0
|
@@ -1,15 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
globToRegex,
|
|
3
|
+
globWalkPlan,
|
|
4
|
+
sandboxCoverage
|
|
5
|
+
} from "./chunk-JTPOI3KU.js";
|
|
6
|
+
import {
|
|
7
|
+
containsPath,
|
|
8
|
+
pathHoldsHome,
|
|
9
|
+
rootHoldsHome
|
|
10
|
+
} from "./chunk-525CAZPY.js";
|
|
11
|
+
import {
|
|
12
|
+
canonicalizePath,
|
|
13
|
+
isBookLocalSettingsPath,
|
|
14
|
+
isUnderRoot,
|
|
15
|
+
pathOutsideWorkspaceResult,
|
|
16
|
+
resolveMutationPath,
|
|
17
|
+
resolveReadablePathDetail,
|
|
18
|
+
resolveWorkspacePath
|
|
19
|
+
} from "./chunk-X66I2NQO.js";
|
|
1
20
|
import {
|
|
2
21
|
canonicalToolName,
|
|
3
22
|
getPrimaryArg,
|
|
4
|
-
observeFile,
|
|
5
|
-
requireFreshObservation,
|
|
6
23
|
toolFailure,
|
|
7
24
|
toolSuccess
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from "
|
|
25
|
+
} from "./chunk-224FJZLH.js";
|
|
26
|
+
|
|
27
|
+
// src/permissions.ts
|
|
28
|
+
import { realpathSync } from "fs";
|
|
29
|
+
import { isAbsolute, relative as relative2, sep } from "path";
|
|
13
30
|
|
|
14
31
|
// src/async.ts
|
|
15
32
|
function throwIfAborted(signal) {
|
|
@@ -122,7 +139,9 @@ function hunkBounds(ops, context) {
|
|
|
122
139
|
e++;
|
|
123
140
|
ctxAfter++;
|
|
124
141
|
}
|
|
125
|
-
bounds.
|
|
142
|
+
const previous = bounds.at(-1);
|
|
143
|
+
if (previous && s + 1 <= previous[1]) previous[1] = e;
|
|
144
|
+
else bounds.push([s + 1, e]);
|
|
126
145
|
k = e;
|
|
127
146
|
}
|
|
128
147
|
return bounds;
|
|
@@ -203,52 +222,109 @@ async function renderDiffWithStatsAsync(oldText, newText, context = 3, signal) {
|
|
|
203
222
|
return { diff: out.join("\n"), stats: diffStatsFromHunks(hunks) };
|
|
204
223
|
}
|
|
205
224
|
|
|
206
|
-
// src/tools/
|
|
207
|
-
import {
|
|
208
|
-
import {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return
|
|
221
|
-
}
|
|
222
|
-
function
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
225
|
+
// src/tools/file-provenance.ts
|
|
226
|
+
import { createHash } from "crypto";
|
|
227
|
+
import { readFile, stat } from "fs/promises";
|
|
228
|
+
import { normalize, relative, resolve } from "path";
|
|
229
|
+
function workspaceIdentity(workspaceRoot) {
|
|
230
|
+
const root = normalize(resolve(workspaceRoot));
|
|
231
|
+
const stable = process.platform === "win32" ? root.toLowerCase() : root;
|
|
232
|
+
return createHash("sha256").update(stable).digest("hex").slice(0, 24);
|
|
233
|
+
}
|
|
234
|
+
function normalizeObservedPath(path) {
|
|
235
|
+
const normalized = path.replace(/\\/g, "/");
|
|
236
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
237
|
+
}
|
|
238
|
+
function observationKey(workspaceId, path) {
|
|
239
|
+
return `${workspaceId}:${normalizeObservedPath(path)}`;
|
|
240
|
+
}
|
|
241
|
+
function observationPathFor(workspaceRoot, absolutePath) {
|
|
242
|
+
const root = canonicalizePath(resolve(workspaceRoot));
|
|
243
|
+
return relative(root, canonicalizePath(absolutePath)).replace(/\\/g, "/");
|
|
244
|
+
}
|
|
245
|
+
function isOutline(observation) {
|
|
246
|
+
return observation.operation === "outline";
|
|
247
|
+
}
|
|
248
|
+
function mayReplaceObservation(current, next) {
|
|
249
|
+
return !current || !isOutline(next) || isOutline(current);
|
|
250
|
+
}
|
|
251
|
+
function supersedesObservation(current, next) {
|
|
252
|
+
if (!current) return true;
|
|
253
|
+
if (isOutline(current) !== isOutline(next)) return isOutline(current);
|
|
254
|
+
return current.timestamp <= next.timestamp;
|
|
255
|
+
}
|
|
256
|
+
function seedObservationLedger(ledger, messages) {
|
|
257
|
+
for (const message of messages) {
|
|
258
|
+
for (const observation of message.fileObservations ?? []) {
|
|
259
|
+
const key = observationKey(observation.workspaceId, observation.path);
|
|
260
|
+
if (supersedesObservation(ledger.get(key), observation)) ledger.set(key, observation);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return ledger;
|
|
264
|
+
}
|
|
265
|
+
async function observeFile(ctx, absolutePath, operation, coverage) {
|
|
266
|
+
const bytes = await readFile(absolutePath);
|
|
267
|
+
const workspaceId = workspaceIdentity(ctx.workspaceRoot);
|
|
268
|
+
const path = observationPathFor(ctx.workspaceRoot, absolutePath);
|
|
269
|
+
const observation = {
|
|
270
|
+
path,
|
|
271
|
+
workspaceId,
|
|
272
|
+
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
273
|
+
byteSize: bytes.byteLength,
|
|
274
|
+
lineStart: coverage?.lineStart,
|
|
275
|
+
lineEnd: coverage?.lineEnd,
|
|
276
|
+
operation,
|
|
277
|
+
sourceRef: ctx.currentToolTraceId ?? "runtime-tool",
|
|
278
|
+
timestamp: Date.now()
|
|
279
|
+
};
|
|
280
|
+
const key = observationKey(workspaceId, path);
|
|
281
|
+
const ledger = ctx.fileObservationLedger;
|
|
282
|
+
if (ledger && mayReplaceObservation(ledger.get(key), observation)) ledger.set(key, observation);
|
|
283
|
+
return observation;
|
|
284
|
+
}
|
|
285
|
+
async function requireFreshObservation(ctx, absolutePath, relativePath) {
|
|
286
|
+
const workspaceId = workspaceIdentity(ctx.workspaceRoot);
|
|
287
|
+
const normalizedPath = relativePath.replace(/\\/g, "/");
|
|
288
|
+
const remembered = ctx.fileObservationLedger?.get(
|
|
289
|
+
observationKey(workspaceId, observationPathFor(ctx.workspaceRoot, absolutePath))
|
|
290
|
+
);
|
|
291
|
+
if (!remembered || isOutline(remembered)) return void 0;
|
|
230
292
|
try {
|
|
231
|
-
|
|
232
|
-
|
|
293
|
+
const info = await stat(absolutePath);
|
|
294
|
+
if (!info.isFile()) return staleMessage(normalizedPath);
|
|
295
|
+
const bytes = await readFile(absolutePath);
|
|
296
|
+
const currentHash = createHash("sha256").update(bytes).digest("hex");
|
|
297
|
+
return currentHash === remembered.sha256 ? void 0 : staleMessage(normalizedPath);
|
|
233
298
|
} catch {
|
|
234
|
-
return
|
|
299
|
+
return staleMessage(normalizedPath);
|
|
235
300
|
}
|
|
236
|
-
if (isOutside(realRoot, realExisting)) return null;
|
|
237
|
-
const canonicalPath = resolve(realExisting, relative(existingPath, filePath));
|
|
238
|
-
if (isOutside(realRoot, canonicalPath)) return null;
|
|
239
|
-
const rel = relative(lexicalRoot, filePath);
|
|
240
|
-
return { filePath, canonicalPath, relativePath: rel.replace(/\\/g, "/") };
|
|
241
301
|
}
|
|
242
|
-
function
|
|
243
|
-
return
|
|
244
|
-
|
|
245
|
-
|
|
302
|
+
function staleMessage(path) {
|
|
303
|
+
return `SKIPPED: ${path} changed or disappeared since it was last shown to the model. Call Read (or mention the file again) before modifying it.`;
|
|
304
|
+
}
|
|
305
|
+
function requireObservationForMutation(ctx, absolutePath, relativePath, retryVerb) {
|
|
306
|
+
const ledger = ctx.fileObservationLedger;
|
|
307
|
+
if (!ledger) return void 0;
|
|
308
|
+
const workspaceId = workspaceIdentity(ctx.workspaceRoot);
|
|
309
|
+
const normalizedPath = relativePath.replace(/\\/g, "/");
|
|
310
|
+
const remembered = ledger.get(
|
|
311
|
+
observationKey(workspaceId, observationPathFor(ctx.workspaceRoot, absolutePath))
|
|
312
|
+
);
|
|
313
|
+
if (remembered && !isOutline(remembered)) return void 0;
|
|
314
|
+
const seen = remembered ? "has only been outlined in this session, and an outline is not the file's content" : "has not been read in this session";
|
|
315
|
+
return toolFailure(
|
|
316
|
+
`SKIPPED: ${normalizedPath} ${seen}. Call Read (or mention the file) before modifying it.`,
|
|
317
|
+
{
|
|
318
|
+
code: "file_not_observed",
|
|
319
|
+
remediation: `Read the file first, then retry the ${retryVerb}.`
|
|
320
|
+
}
|
|
321
|
+
);
|
|
246
322
|
}
|
|
247
323
|
|
|
248
324
|
// src/tools/mutation.ts
|
|
249
325
|
import { randomUUID } from "crypto";
|
|
250
|
-
import { mkdir, readFile, rename, rm, stat, writeFile } from "fs/promises";
|
|
251
|
-
import { dirname
|
|
326
|
+
import { mkdir, readFile as readFile2, rename, rm, stat as stat2, writeFile } from "fs/promises";
|
|
327
|
+
import { dirname } from "path";
|
|
252
328
|
var pathLocks = /* @__PURE__ */ new Map();
|
|
253
329
|
function looksBinary(bytes) {
|
|
254
330
|
const sample = bytes.subarray(0, 8192);
|
|
@@ -262,7 +338,7 @@ function looksBinary(bytes) {
|
|
|
262
338
|
async function readTextSnapshot(filePath, allowMissing = false) {
|
|
263
339
|
let bytes;
|
|
264
340
|
try {
|
|
265
|
-
bytes = await
|
|
341
|
+
bytes = await readFile2(filePath);
|
|
266
342
|
} catch (error) {
|
|
267
343
|
if (allowMissing && error.code === "ENOENT") {
|
|
268
344
|
return {
|
|
@@ -292,7 +368,7 @@ async function readTextSnapshot(filePath, allowMissing = false) {
|
|
|
292
368
|
const bareLfCount = (decoded.replace(/\r\n/g, "").match(/\n/g) ?? []).length;
|
|
293
369
|
const bareCrCount = (decoded.replace(/\r\n/g, "").match(/\r/g) ?? []).length;
|
|
294
370
|
const mixedLineEndings = crlfCount > 0 && bareLfCount > 0 || bareCrCount > 0;
|
|
295
|
-
const mode = (await
|
|
371
|
+
const mode = (await stat2(filePath)).mode & 511;
|
|
296
372
|
return {
|
|
297
373
|
exists: true,
|
|
298
374
|
bytes,
|
|
@@ -310,7 +386,7 @@ function restoreTextEncoding(text, snapshot) {
|
|
|
310
386
|
}
|
|
311
387
|
async function writeFileAtomically(filePath, bytes, signal, mode) {
|
|
312
388
|
throwIfAborted(signal);
|
|
313
|
-
await mkdir(
|
|
389
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
314
390
|
const temporary = `${filePath}.book-tmp-${randomUUID()}`;
|
|
315
391
|
try {
|
|
316
392
|
await writeFile(temporary, bytes, { flag: "wx", mode });
|
|
@@ -378,6 +454,8 @@ function parsePatchUnsafe(patch) {
|
|
|
378
454
|
if (lines.at(-1) === "") lines.pop();
|
|
379
455
|
if (lines[0] !== "*** Begin Patch" || lines.at(-1) !== "*** End Patch")
|
|
380
456
|
return invalid("Patch must start with *** Begin Patch and end with *** End Patch");
|
|
457
|
+
while (lines.length > 2 && lines[1] === "*** Begin Patch") lines.splice(1, 1);
|
|
458
|
+
while (lines.length > 2 && lines.at(-2) === "*** End Patch") lines.splice(-2, 1);
|
|
381
459
|
const operations = [];
|
|
382
460
|
let index = 1;
|
|
383
461
|
let hunkCount = 0;
|
|
@@ -462,17 +540,13 @@ function linesOf(text) {
|
|
|
462
540
|
if (lines.at(-1) === "") lines.pop();
|
|
463
541
|
return lines;
|
|
464
542
|
}
|
|
465
|
-
function
|
|
466
|
-
if (needle.length === 0) return
|
|
467
|
-
|
|
468
|
-
let count = 0;
|
|
543
|
+
function sequenceMatches(haystack, needle) {
|
|
544
|
+
if (needle.length === 0) return [];
|
|
545
|
+
const matches = [];
|
|
469
546
|
for (let start = 0; start <= haystack.length - needle.length; start++) {
|
|
470
|
-
if (needle.every((line, offset) => haystack[start + offset] === line))
|
|
471
|
-
index = start;
|
|
472
|
-
count++;
|
|
473
|
-
}
|
|
547
|
+
if (needle.every((line, offset) => haystack[start + offset] === line)) matches.push(start);
|
|
474
548
|
}
|
|
475
|
-
return
|
|
549
|
+
return matches;
|
|
476
550
|
}
|
|
477
551
|
function candidateLine(haystack, line) {
|
|
478
552
|
const matches = haystack.reduce((found, value, index) => {
|
|
@@ -484,12 +558,15 @@ function candidateLine(haystack, line) {
|
|
|
484
558
|
function applyHunks(text, hunks) {
|
|
485
559
|
let lines = linesOf(text);
|
|
486
560
|
let trailingNewline = text.endsWith("\n");
|
|
561
|
+
let cursor = 0;
|
|
487
562
|
for (let hunkIndex = 0; hunkIndex < hunks.length; hunkIndex++) {
|
|
488
563
|
const hunk = hunks[hunkIndex];
|
|
489
564
|
const oldLines = hunk.lines.filter((line) => line.kind !== "add").map((line) => line.text);
|
|
490
565
|
const newLines = hunk.lines.filter((line) => line.kind !== "remove").map((line) => line.text);
|
|
491
|
-
const
|
|
492
|
-
|
|
566
|
+
const matches = sequenceMatches(lines, oldLines);
|
|
567
|
+
const afterPrevious = matches.filter((start) => start >= cursor);
|
|
568
|
+
const index = matches.length === 1 ? matches[0] : afterPrevious.length === 1 ? afterPrevious[0] : -1;
|
|
569
|
+
if (matches.length === 0)
|
|
493
570
|
return {
|
|
494
571
|
text,
|
|
495
572
|
mismatch: toolFailure(`Hunk ${hunkIndex + 1}: patch context not found`, {
|
|
@@ -507,21 +584,23 @@ function applyHunks(text, hunks) {
|
|
|
507
584
|
}
|
|
508
585
|
})
|
|
509
586
|
};
|
|
510
|
-
if (
|
|
587
|
+
if (index < 0)
|
|
511
588
|
return {
|
|
512
589
|
text,
|
|
513
590
|
mismatch: toolFailure(`Hunk ${hunkIndex + 1}: patch context is ambiguous`, {
|
|
514
591
|
code: "ambiguous_patch_context",
|
|
515
|
-
remediation: "
|
|
516
|
-
details: {
|
|
592
|
+
remediation: "Add surrounding lines, such as the enclosing function signature, until the hunk occurs once in the file or once after the previous hunk, then regenerate the patch.",
|
|
593
|
+
details: {
|
|
594
|
+
hunkIndex: hunkIndex + 1,
|
|
595
|
+
header: hunk.header,
|
|
596
|
+
matches: matches.length,
|
|
597
|
+
matchesAfterPreviousHunk: afterPrevious.length
|
|
598
|
+
}
|
|
517
599
|
})
|
|
518
600
|
};
|
|
519
|
-
const touchesEndOfFile =
|
|
520
|
-
lines = [
|
|
521
|
-
|
|
522
|
-
...newLines,
|
|
523
|
-
...lines.slice(match.index + oldLines.length)
|
|
524
|
-
];
|
|
601
|
+
const touchesEndOfFile = index + oldLines.length === lines.length;
|
|
602
|
+
lines = [...lines.slice(0, index), ...newLines, ...lines.slice(index + oldLines.length)];
|
|
603
|
+
cursor = index + newLines.length;
|
|
525
604
|
if (touchesEndOfFile) {
|
|
526
605
|
const oldNoNewline = hunk.lines.some((line) => line.kind !== "add" && line.noNewline);
|
|
527
606
|
const newNoNewline = hunk.lines.some((line) => line.kind !== "remove" && line.noNewline);
|
|
@@ -547,7 +626,7 @@ async function applyPatch(args, ctx) {
|
|
|
547
626
|
if (!("operations" in parsed)) return parsed;
|
|
548
627
|
const paths = parsed.operations.map((operation) => operation.path);
|
|
549
628
|
const resolved = parsed.operations.map((operation) => {
|
|
550
|
-
const result =
|
|
629
|
+
const result = resolveMutationPath(ctx, operation.path);
|
|
551
630
|
return result ? { operation, ...result } : null;
|
|
552
631
|
});
|
|
553
632
|
if (resolved.some((entry) => !entry))
|
|
@@ -770,7 +849,13 @@ ${diff}`);
|
|
|
770
849
|
}
|
|
771
850
|
}
|
|
772
851
|
);
|
|
773
|
-
return result;
|
|
852
|
+
if (!staged.every((file) => file.mutation.kind === "create")) return result;
|
|
853
|
+
return {
|
|
854
|
+
...result,
|
|
855
|
+
maskedPlaceholder: `${staged.map(
|
|
856
|
+
(file, order) => `${order === 0 ? "Created" : "created"} ${file.relativePath} (${file.mutation.addedLines} lines, ${file.afterBytes?.byteLength ?? 0} bytes)`
|
|
857
|
+
).join("; ")}.`
|
|
858
|
+
};
|
|
774
859
|
} catch (error) {
|
|
775
860
|
const code = errorCodeFor(error);
|
|
776
861
|
return toolFailure(error instanceof Error ? error.message : String(error), {
|
|
@@ -809,6 +894,32 @@ var patchTools = [
|
|
|
809
894
|
];
|
|
810
895
|
|
|
811
896
|
// src/permissions.ts
|
|
897
|
+
function isUnreadablePattern(target) {
|
|
898
|
+
return typeof target === "object" && target !== null && target.target === "invalid";
|
|
899
|
+
}
|
|
900
|
+
var WORKSPACE_READ_TOOLS = /* @__PURE__ */ new Set(["Read", "Glob", "Grep"]);
|
|
901
|
+
var PATH_RULE_TOOLS = /* @__PURE__ */ new Set([
|
|
902
|
+
"Read",
|
|
903
|
+
"Write",
|
|
904
|
+
"Edit",
|
|
905
|
+
"MultiEdit",
|
|
906
|
+
"NotebookEdit"
|
|
907
|
+
]);
|
|
908
|
+
var WORKSPACE_READ_JUDGING_MODES = /* @__PURE__ */ new Set([
|
|
909
|
+
"default",
|
|
910
|
+
"accept-edits",
|
|
911
|
+
"plan"
|
|
912
|
+
]);
|
|
913
|
+
var WORKSPACE_READ_REFUSAL_MODES = /* @__PURE__ */ new Set([
|
|
914
|
+
...WORKSPACE_READ_JUDGING_MODES,
|
|
915
|
+
"dontAsk"
|
|
916
|
+
]);
|
|
917
|
+
var WORKSPACE_READ_ONLY_GIT_TOOLS = /* @__PURE__ */ new Set([
|
|
918
|
+
"GitStatus",
|
|
919
|
+
"GitDiff",
|
|
920
|
+
"GitLog",
|
|
921
|
+
"GitBranch"
|
|
922
|
+
]);
|
|
812
923
|
function parseRule(rule) {
|
|
813
924
|
const parenIdx = rule.indexOf("(");
|
|
814
925
|
if (parenIdx === -1) {
|
|
@@ -834,12 +945,19 @@ function toolNameMatchesRule(ruleToolName, toolName) {
|
|
|
834
945
|
serverName && !serverName.includes("__") && toolName.startsWith(`${ruleToolName}__`)
|
|
835
946
|
);
|
|
836
947
|
}
|
|
837
|
-
|
|
948
|
+
var caseFoldingPathRule = (value) => value.toLowerCase();
|
|
949
|
+
function ruleMatchesCandidate(rule, toolName, primaryArg, fold) {
|
|
838
950
|
if (!toolNameMatchesRule(rule.toolName, toolName)) return false;
|
|
839
951
|
if (rule.pattern === null) return true;
|
|
840
952
|
const normalizedArg = normalizePathArg(primaryArg);
|
|
841
953
|
const normalizedPattern = rule.pattern.startsWith("./") ? rule.pattern.slice(2) : rule.pattern;
|
|
842
|
-
return globToRegex(normalizedPattern).test(normalizedArg);
|
|
954
|
+
if (!fold) return globToRegex(normalizedPattern).test(normalizedArg);
|
|
955
|
+
return globToRegex(caseFoldingPathRule(normalizedPattern)).test(
|
|
956
|
+
caseFoldingPathRule(normalizedArg)
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
function ruleMatches(rule, toolName, primaryArg) {
|
|
960
|
+
return ruleMatchesCandidate(rule, toolName, primaryArg, false);
|
|
843
961
|
}
|
|
844
962
|
function permissionRuleForToolCall(call) {
|
|
845
963
|
const toolName = canonicalToolName(call.name);
|
|
@@ -852,7 +970,8 @@ function permissionRuleForToolCall(call) {
|
|
|
852
970
|
} catch {
|
|
853
971
|
}
|
|
854
972
|
}
|
|
855
|
-
|
|
973
|
+
if (!primaryArg) return void 0;
|
|
974
|
+
return `${toolName}(${primaryArg})`;
|
|
856
975
|
}
|
|
857
976
|
function permissionResultOf(decision) {
|
|
858
977
|
return typeof decision === "string" ? decision : decision.result;
|
|
@@ -860,10 +979,14 @@ function permissionResultOf(decision) {
|
|
|
860
979
|
function permissionRuleOf(decision) {
|
|
861
980
|
return typeof decision === "string" ? void 0 : decision.rule;
|
|
862
981
|
}
|
|
982
|
+
function permissionReasonOf(decision) {
|
|
983
|
+
return typeof decision === "string" ? void 0 : decision.reason;
|
|
984
|
+
}
|
|
863
985
|
var RULE_LADDER_MAX = 3;
|
|
864
986
|
var SHELL_CHAINING = /[;&|><`$(){}\n]/;
|
|
865
987
|
function permissionRuleLadder(call) {
|
|
866
988
|
const exact = permissionRuleForToolCall(call);
|
|
989
|
+
if (!exact) return [];
|
|
867
990
|
if (canonicalToolName(call.name) !== "Bash") return [exact];
|
|
868
991
|
const command = getPrimaryArg(call.arguments).trim();
|
|
869
992
|
if (!command || SHELL_CHAINING.test(command)) return [exact];
|
|
@@ -890,8 +1013,8 @@ function patchOperations(args) {
|
|
|
890
1013
|
const parsed = parsePatch(args.patch);
|
|
891
1014
|
return "operations" in parsed ? parsed.operations : [];
|
|
892
1015
|
}
|
|
893
|
-
function ruleMatchesPatch(rule,
|
|
894
|
-
return
|
|
1016
|
+
function ruleMatchesPatch(rule, paths, fold) {
|
|
1017
|
+
return paths.some((path) => ruleMatchesCandidate(rule, rule.toolName, path, fold));
|
|
895
1018
|
}
|
|
896
1019
|
function patchRuleSupportsOperation(rule, operation) {
|
|
897
1020
|
if (rule.toolName === "ApplyPatch") return true;
|
|
@@ -899,11 +1022,11 @@ function patchRuleSupportsOperation(rule, operation) {
|
|
|
899
1022
|
if (rule.toolName === "Write") return operation.kind === "update" || operation.kind === "add";
|
|
900
1023
|
return false;
|
|
901
1024
|
}
|
|
902
|
-
function compatiblePatchRuleMatches(rule, operations) {
|
|
1025
|
+
function compatiblePatchRuleMatches(rule, operations, spellingsOf, fold) {
|
|
903
1026
|
if (!["ApplyPatch", "Edit", "Write"].includes(rule.toolName)) return false;
|
|
904
1027
|
if (rule.toolName === "ApplyPatch" && rule.pattern === null) return true;
|
|
905
1028
|
return operations.some(
|
|
906
|
-
(operation) => patchRuleSupportsOperation(rule, operation) && (rule.pattern === null || ruleMatchesPatch(rule, operation.path))
|
|
1029
|
+
(operation) => patchRuleSupportsOperation(rule, operation) && (rule.pattern === null || ruleMatchesPatch(rule, spellingsOf(operation.path, fold), fold))
|
|
907
1030
|
);
|
|
908
1031
|
}
|
|
909
1032
|
function primaryArgForRule(_toolName, args) {
|
|
@@ -920,63 +1043,260 @@ function sandboxAutoAllows(toolName, args, settings, options) {
|
|
|
920
1043
|
if (!command) return false;
|
|
921
1044
|
return sandboxCoverage(command, settings.sandbox, options.sandboxBackendAvailable).sandboxed;
|
|
922
1045
|
}
|
|
1046
|
+
var toPosix = (value) => value.replace(/\\/g, "/");
|
|
1047
|
+
function realRootOf(scope) {
|
|
1048
|
+
if (scope.realRoot) return scope.realRoot;
|
|
1049
|
+
try {
|
|
1050
|
+
return realpathSync.native(scope.root);
|
|
1051
|
+
} catch {
|
|
1052
|
+
return void 0;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
function pathArgument(toolName, args) {
|
|
1056
|
+
const value = toolName === "NotebookEdit" ? args.notebook_path : args.filePath;
|
|
1057
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
1058
|
+
}
|
|
1059
|
+
function rulesName(lists, toolName) {
|
|
1060
|
+
return [...lists.deny, ...lists.ask, ...lists.allow].some(
|
|
1061
|
+
({ parsed }) => parsed.toolName === toolName
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
function resolvedPathScope(scope) {
|
|
1065
|
+
const detailCache = /* @__PURE__ */ new Map();
|
|
1066
|
+
const rootsFor = (includeReadOnly) => ({
|
|
1067
|
+
workspaceRoot: scope.root,
|
|
1068
|
+
readOnlyRoots: includeReadOnly ? scope.readOnlyRoots : void 0,
|
|
1069
|
+
additionalRoots: scope.additionalRoots
|
|
1070
|
+
});
|
|
1071
|
+
const holdsHomeByRoot = /* @__PURE__ */ new Map();
|
|
1072
|
+
const holdsHome = (root) => {
|
|
1073
|
+
let known = holdsHomeByRoot.get(root);
|
|
1074
|
+
if (known === void 0) {
|
|
1075
|
+
known = rootHoldsHome(root, scope.homeGuards ?? []);
|
|
1076
|
+
holdsHomeByRoot.set(root, known);
|
|
1077
|
+
}
|
|
1078
|
+
return known;
|
|
1079
|
+
};
|
|
1080
|
+
const guardedRoots = [scope.root, ...scope.additionalRoots ?? []].filter(holdsHome);
|
|
1081
|
+
const servingRootOf = (canonicalPath) => [realRootOf(scope), ...scope.additionalRoots ?? []].find(
|
|
1082
|
+
(root) => typeof root === "string" && root.length > 0 && isUnderRoot(canonicalPath, root)
|
|
1083
|
+
);
|
|
1084
|
+
return {
|
|
1085
|
+
scope,
|
|
1086
|
+
roots: rootsFor,
|
|
1087
|
+
detail: (raw, includeReadOnly) => {
|
|
1088
|
+
const key = `${includeReadOnly ? "r" : "w"}\0${raw}`;
|
|
1089
|
+
let known = detailCache.get(key);
|
|
1090
|
+
if (!known) {
|
|
1091
|
+
known = resolveReadablePathDetail(rootsFor(includeReadOnly), raw);
|
|
1092
|
+
detailCache.set(key, known);
|
|
1093
|
+
}
|
|
1094
|
+
return known;
|
|
1095
|
+
},
|
|
1096
|
+
isGuarded: (canonicalPath) => {
|
|
1097
|
+
const roots = [realRootOf(scope), ...scope.additionalRoots ?? []].filter(
|
|
1098
|
+
(root) => typeof root === "string" && root.length > 0
|
|
1099
|
+
);
|
|
1100
|
+
if (roots.some((root) => isBookLocalSettingsPath(canonicalPath, root, { directory: true })))
|
|
1101
|
+
return true;
|
|
1102
|
+
if (guardedRoots.length === 0) return false;
|
|
1103
|
+
if (!pathHoldsHome(canonicalPath, scope.homeGuards ?? [])) return false;
|
|
1104
|
+
const serving = servingRootOf(canonicalPath);
|
|
1105
|
+
return serving !== void 0 && holdsHome(serving);
|
|
1106
|
+
},
|
|
1107
|
+
/**
|
|
1108
|
+
* Whether a Grep or Glob *scope* can reach a guarded home. A scope is a subtree, not a file,
|
|
1109
|
+
* so it is guarded when the guarded home is inside it as well as when it is inside the home:
|
|
1110
|
+
* `Grep {path: "/home"}` reads `~/.ssh` without naming a single file under it (PR #334
|
|
1111
|
+
* finding 4). Compared canonically, both ways, because either side may be reached by a link.
|
|
1112
|
+
*/
|
|
1113
|
+
scopeReachesHome: (canonicalScope) => {
|
|
1114
|
+
const homes = scope.homeGuards ?? [];
|
|
1115
|
+
if (homes.some((home) => containsPath(canonicalScope, home))) return true;
|
|
1116
|
+
return guardedRoots.some((root) => containsPath(root, canonicalScope));
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
function spellingsOfPath(paths, raw, includeReadOnly, restrictive) {
|
|
1121
|
+
const spellings = [];
|
|
1122
|
+
const detail = paths.detail(raw, includeReadOnly);
|
|
1123
|
+
if (!("path" in detail)) return [];
|
|
1124
|
+
const resolved = detail.path;
|
|
1125
|
+
const inWorkspace = resolveWorkspacePath(paths.scope.root, raw) !== null;
|
|
1126
|
+
if (inWorkspace) {
|
|
1127
|
+
spellings.push(
|
|
1128
|
+
resolved.relativePath,
|
|
1129
|
+
toPosix(resolved.filePath),
|
|
1130
|
+
toPosix(resolved.canonicalPath)
|
|
1131
|
+
);
|
|
1132
|
+
const realRoot = realRootOf(paths.scope);
|
|
1133
|
+
if (realRoot) spellings.push(toPosix(relative2(realRoot, resolved.canonicalPath)));
|
|
1134
|
+
} else {
|
|
1135
|
+
spellings.push(toPosix(resolved.filePath), toPosix(resolved.canonicalPath));
|
|
1136
|
+
if (restrictive) {
|
|
1137
|
+
for (const root of paths.scope.additionalRoots ?? []) {
|
|
1138
|
+
const fromRoot = relative2(canonicalizePath(root), resolved.canonicalPath);
|
|
1139
|
+
if (!fromRoot || isAbsolute(fromRoot) || fromRoot === ".." || fromRoot.startsWith(`..${sep}`)) {
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
1142
|
+
spellings.push(toPosix(fromRoot));
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return [...new Set(spellings)].filter((spelling) => spelling !== "" && spelling !== raw);
|
|
1147
|
+
}
|
|
1148
|
+
function pathRuleSpellings(paths, toolName, args, restrictive) {
|
|
1149
|
+
const raw = pathArgument(toolName, args);
|
|
1150
|
+
return raw ? spellingsOfPath(paths, raw, toolName === "Read", restrictive) : [];
|
|
1151
|
+
}
|
|
1152
|
+
function globTarget(pattern, paths) {
|
|
1153
|
+
const { refusal, scopes } = globWalkPlan(pattern, paths.scope.root);
|
|
1154
|
+
if (refusal) return { target: "invalid", reason: refusal };
|
|
1155
|
+
if (scopes === null) return "outside";
|
|
1156
|
+
let guarded = false;
|
|
1157
|
+
for (const scope of scopes) {
|
|
1158
|
+
const detail = paths.detail(scope, false);
|
|
1159
|
+
if (!("path" in detail)) return "outside";
|
|
1160
|
+
if (paths.scopeReachesHome(detail.path.canonicalPath)) guarded = true;
|
|
1161
|
+
}
|
|
1162
|
+
return guarded ? "guarded" : "servable";
|
|
1163
|
+
}
|
|
1164
|
+
function readToolTarget(toolName, args, paths) {
|
|
1165
|
+
if (toolName === "Read") {
|
|
1166
|
+
const raw2 = pathArgument(toolName, args);
|
|
1167
|
+
if (!raw2) return "none";
|
|
1168
|
+
const detail2 = paths.detail(raw2, true);
|
|
1169
|
+
if (!("path" in detail2)) return detail2.reason === "excluded" ? "hidden" : "outside";
|
|
1170
|
+
return paths.isGuarded(detail2.path.canonicalPath) ? "guarded" : "servable";
|
|
1171
|
+
}
|
|
1172
|
+
if (toolName === "Glob") {
|
|
1173
|
+
const pattern = typeof args.pattern === "string" ? args.pattern.trim() : "";
|
|
1174
|
+
if (!pattern) return "none";
|
|
1175
|
+
return globTarget(pattern, paths);
|
|
1176
|
+
}
|
|
1177
|
+
const raw = typeof args.path === "string" ? args.path.trim() : "";
|
|
1178
|
+
if (raw === "" || raw === ".") return "servable";
|
|
1179
|
+
const detail = paths.detail(raw, false);
|
|
1180
|
+
if (!("path" in detail)) return detail.reason === "excluded" ? "hidden" : "outside";
|
|
1181
|
+
if (paths.isGuarded(detail.path.canonicalPath)) return "guarded";
|
|
1182
|
+
return paths.scopeReachesHome(detail.path.canonicalPath) ? "guarded" : "servable";
|
|
1183
|
+
}
|
|
1184
|
+
function parseRuleLists(settings) {
|
|
1185
|
+
const list = (texts) => texts.map((text) => ({ text, parsed: parseRule(text) }));
|
|
1186
|
+
return {
|
|
1187
|
+
deny: list(settings.permissions.deny),
|
|
1188
|
+
ask: list(settings.permissions.ask),
|
|
1189
|
+
allow: list(settings.permissions.allow)
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
function hasReadAdjudication(lists) {
|
|
1193
|
+
return [...lists.deny, ...lists.ask].some(
|
|
1194
|
+
({ parsed }) => WORKSPACE_READ_TOOLS.has(parsed.toolName)
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
923
1197
|
function evaluatePermission(toolName, args, settings, options = {}) {
|
|
924
1198
|
return evaluatePermissionDetail(toolName, args, settings, options).decision;
|
|
925
1199
|
}
|
|
926
1200
|
function evaluatePermissionDetail(toolName, args, settings, options = {}) {
|
|
927
|
-
const
|
|
1201
|
+
const scope = options.workspace;
|
|
1202
|
+
const paths = scope ? resolvedPathScope(scope) : void 0;
|
|
1203
|
+
const parsed = parseRuleLists(settings);
|
|
928
1204
|
if (toolName === "ApplyPatch") {
|
|
929
1205
|
const operations = patchOperations(args);
|
|
930
|
-
const
|
|
931
|
-
|
|
932
|
-
|
|
1206
|
+
const spellings = /* @__PURE__ */ new Map();
|
|
1207
|
+
const restrictiveSpellings = /* @__PURE__ */ new Map();
|
|
1208
|
+
const spellingsOf = (path, restrictive) => {
|
|
1209
|
+
const cache = restrictive ? restrictiveSpellings : spellings;
|
|
1210
|
+
let known = cache.get(path);
|
|
1211
|
+
if (!known) {
|
|
1212
|
+
known = paths ? [path, ...spellingsOfPath(paths, path, false, restrictive)] : [path];
|
|
1213
|
+
cache.set(path, known);
|
|
1214
|
+
}
|
|
1215
|
+
return known;
|
|
933
1216
|
};
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
const
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
const rule = parseRule(ruleStr);
|
|
943
|
-
if (compatiblePatchRuleMatches(rule, operations))
|
|
944
|
-
return { decision: "ask", matchedRule: ruleStr, source: "ask" };
|
|
1217
|
+
const isCompatible = (rule) => rule.toolName === "ApplyPatch" || rule.toolName === "Edit" || rule.toolName === "Write";
|
|
1218
|
+
for (const list of ["deny", "ask"]) {
|
|
1219
|
+
for (const entry of parsed[list]) {
|
|
1220
|
+
if (!isCompatible(entry.parsed)) continue;
|
|
1221
|
+
if (compatiblePatchRuleMatches(entry.parsed, operations, spellingsOf, true)) {
|
|
1222
|
+
return { decision: list, matchedRule: entry.text, source: list };
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
945
1225
|
}
|
|
946
|
-
const allowRules = allow.filter(
|
|
1226
|
+
const allowRules = parsed.allow.filter(({ parsed: rule }) => isCompatible(rule));
|
|
1227
|
+
const firstCompatible = allowRules[0]?.text;
|
|
947
1228
|
if (operations.length > 0 && operations.every(
|
|
948
1229
|
(operation) => allowRules.some(
|
|
949
|
-
(rule) => patchRuleSupportsOperation(rule, operation) && (rule.pattern === null || ruleMatchesPatch(rule, operation.path))
|
|
1230
|
+
({ parsed: rule }) => patchRuleSupportsOperation(rule, operation) && (rule.pattern === null || ruleMatchesPatch(rule, spellingsOf(operation.path, false), false))
|
|
950
1231
|
)
|
|
951
1232
|
)) {
|
|
952
|
-
return { decision: "allow", matchedRule:
|
|
1233
|
+
return { decision: "allow", matchedRule: firstCompatible, source: "allow" };
|
|
953
1234
|
}
|
|
954
|
-
if (allowRules.some((rule) => rule.toolName === "ApplyPatch" && rule.pattern === null)) {
|
|
955
|
-
return { decision: "allow", matchedRule:
|
|
1235
|
+
if (allowRules.some(({ parsed: rule }) => rule.toolName === "ApplyPatch" && rule.pattern === null)) {
|
|
1236
|
+
return { decision: "allow", matchedRule: firstCompatible, source: "allow" };
|
|
956
1237
|
}
|
|
957
1238
|
return { decision: "ask", source: "default" };
|
|
958
1239
|
}
|
|
959
|
-
const
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1240
|
+
const tool = canonicalToolName(toolName);
|
|
1241
|
+
const candidates = [getPrimaryArg(args)];
|
|
1242
|
+
let restrictiveCandidates = candidates;
|
|
1243
|
+
if (scope && paths && PATH_RULE_TOOLS.has(tool) && rulesName(parsed, tool)) {
|
|
1244
|
+
restrictiveCandidates = [...candidates, ...pathRuleSpellings(paths, tool, args, true)];
|
|
1245
|
+
candidates.push(...pathRuleSpellings(paths, tool, args, false));
|
|
1246
|
+
}
|
|
1247
|
+
const matchesRule = (rule, fold) => {
|
|
1248
|
+
const pool = fold ? restrictiveCandidates : candidates;
|
|
1249
|
+
if (tool === "WebFetch") {
|
|
1250
|
+
try {
|
|
1251
|
+
const url = new URL(candidates[0]).toString();
|
|
1252
|
+
return ruleMatchesCandidate(rule, tool, url, false);
|
|
1253
|
+
} catch {
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
return pool.some((candidate) => ruleMatchesCandidate(rule, tool, candidate, fold));
|
|
1257
|
+
};
|
|
1258
|
+
const readTarget = scope && paths && (scope.judgeReads || scope.refuseUnreachableReads) && WORKSPACE_READ_TOOLS.has(tool) ? readToolTarget(tool, args, paths) : void 0;
|
|
1259
|
+
const unreadablePattern = readTarget !== void 0 && isUnreadablePattern(readTarget) ? readTarget.reason : void 0;
|
|
1260
|
+
const outside = readTarget === "outside" ? { outsideWorkspace: true } : {};
|
|
1261
|
+
for (const entry of parsed.deny) {
|
|
1262
|
+
if (matchesRule(entry.parsed, true)) {
|
|
1263
|
+
return { decision: "deny", matchedRule: entry.text, source: "deny" };
|
|
963
1264
|
}
|
|
964
1265
|
}
|
|
965
|
-
for (const
|
|
966
|
-
if (
|
|
967
|
-
return { decision: "ask", matchedRule:
|
|
1266
|
+
for (const entry of parsed.ask) {
|
|
1267
|
+
if (matchesRule(entry.parsed, true)) {
|
|
1268
|
+
return { decision: "ask", matchedRule: entry.text, source: "ask", ...outside };
|
|
968
1269
|
}
|
|
969
1270
|
}
|
|
970
|
-
for (const
|
|
971
|
-
if (
|
|
972
|
-
return { decision: "allow", matchedRule:
|
|
1271
|
+
for (const entry of parsed.allow) {
|
|
1272
|
+
if (matchesRule(entry.parsed, false)) {
|
|
1273
|
+
return { decision: "allow", matchedRule: entry.text, source: "allow" };
|
|
973
1274
|
}
|
|
974
1275
|
}
|
|
975
1276
|
if (sandboxAutoAllows(toolName, args, settings, options)) {
|
|
976
1277
|
return { decision: "allow", source: "sandbox" };
|
|
977
1278
|
}
|
|
1279
|
+
if (scope?.autoAllowReads && (readTarget === "none" || readTarget === "servable" && (tool === "Read" || !hasReadAdjudication(parsed)))) {
|
|
1280
|
+
return { decision: "allow", source: "workspace" };
|
|
1281
|
+
}
|
|
1282
|
+
if (scope?.autoAllowReads && WORKSPACE_READ_ONLY_GIT_TOOLS.has(tool)) {
|
|
1283
|
+
return { decision: "allow", source: "workspace" };
|
|
1284
|
+
}
|
|
1285
|
+
if (ALWAYS_ALLOWED_TOOLS.has(tool)) {
|
|
1286
|
+
return { decision: "allow", source: "default" };
|
|
1287
|
+
}
|
|
1288
|
+
if (unreadablePattern !== void 0) {
|
|
1289
|
+
return { decision: "refuse", source: "default", unreadablePattern };
|
|
1290
|
+
}
|
|
1291
|
+
if (readTarget === "outside") {
|
|
1292
|
+
return { decision: "refuse", source: "default", outsideWorkspace: true };
|
|
1293
|
+
}
|
|
1294
|
+
if (readTarget === "hidden") {
|
|
1295
|
+
return { decision: "refuse", source: "default", excludedPath: true };
|
|
1296
|
+
}
|
|
978
1297
|
return { decision: "ask", source: "default" };
|
|
979
1298
|
}
|
|
1299
|
+
var ALWAYS_ALLOWED_TOOLS = /* @__PURE__ */ new Set(["MemorySave", "TodoWrite"]);
|
|
980
1300
|
|
|
981
1301
|
export {
|
|
982
1302
|
throwIfAborted,
|
|
@@ -984,8 +1304,14 @@ export {
|
|
|
984
1304
|
delay,
|
|
985
1305
|
yieldToEventLoop,
|
|
986
1306
|
renderDiffWithStatsAsync,
|
|
987
|
-
|
|
988
|
-
|
|
1307
|
+
workspaceIdentity,
|
|
1308
|
+
normalizeObservedPath,
|
|
1309
|
+
observationKey,
|
|
1310
|
+
supersedesObservation,
|
|
1311
|
+
seedObservationLedger,
|
|
1312
|
+
observeFile,
|
|
1313
|
+
requireFreshObservation,
|
|
1314
|
+
requireObservationForMutation,
|
|
989
1315
|
readTextSnapshot,
|
|
990
1316
|
restoreTextEncoding,
|
|
991
1317
|
writeFileAtomically,
|
|
@@ -993,15 +1319,21 @@ export {
|
|
|
993
1319
|
parsePatch,
|
|
994
1320
|
applyHunks,
|
|
995
1321
|
patchTools,
|
|
1322
|
+
WORKSPACE_READ_TOOLS,
|
|
1323
|
+
WORKSPACE_READ_JUDGING_MODES,
|
|
1324
|
+
WORKSPACE_READ_REFUSAL_MODES,
|
|
1325
|
+
WORKSPACE_READ_ONLY_GIT_TOOLS,
|
|
996
1326
|
parseRule,
|
|
997
1327
|
permissionRuleForToolCall,
|
|
998
1328
|
permissionResultOf,
|
|
999
1329
|
permissionRuleOf,
|
|
1330
|
+
permissionReasonOf,
|
|
1000
1331
|
permissionRuleLadder,
|
|
1001
1332
|
permissionRuleMatchesCall,
|
|
1002
1333
|
primaryArgForRule,
|
|
1003
1334
|
hasAdjudicationPolicy,
|
|
1004
1335
|
evaluatePermission,
|
|
1005
|
-
evaluatePermissionDetail
|
|
1336
|
+
evaluatePermissionDetail,
|
|
1337
|
+
ALWAYS_ALLOWED_TOOLS
|
|
1006
1338
|
};
|
|
1007
|
-
//# sourceMappingURL=chunk-
|
|
1339
|
+
//# sourceMappingURL=chunk-J62VZYOB.js.map
|