@zaly/agent 0.0.3

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.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/dist/agent-BaD2kax9.mjs +68 -0
  4. package/dist/agent-BaD2kax9.mjs.map +1 -0
  5. package/dist/agent-GDLdDc2w.mjs +902 -0
  6. package/dist/agent-GDLdDc2w.mjs.map +1 -0
  7. package/dist/bash-QNMTQ1PD.mjs +142 -0
  8. package/dist/bash-QNMTQ1PD.mjs.map +1 -0
  9. package/dist/compactions-3AjPqjeZ.mjs +190 -0
  10. package/dist/compactions-3AjPqjeZ.mjs.map +1 -0
  11. package/dist/edit-Qxl4w1C2.mjs +170 -0
  12. package/dist/edit-Qxl4w1C2.mjs.map +1 -0
  13. package/dist/env-UUFv5Mye.mjs +30 -0
  14. package/dist/env-UUFv5Mye.mjs.map +1 -0
  15. package/dist/fetch-D8cmmA3E.mjs +111 -0
  16. package/dist/fetch-D8cmmA3E.mjs.map +1 -0
  17. package/dist/find-Bah9SKWX.mjs +87 -0
  18. package/dist/find-Bah9SKWX.mjs.map +1 -0
  19. package/dist/grep-B2Ax7ewy.mjs +244 -0
  20. package/dist/grep-B2Ax7ewy.mjs.map +1 -0
  21. package/dist/index-node-esm-D6YQRKEz.mjs +1537 -0
  22. package/dist/index-node-esm-D6YQRKEz.mjs.map +1 -0
  23. package/dist/index.d.mts +1605 -0
  24. package/dist/index.d.mts.map +1 -0
  25. package/dist/index.mjs +948 -0
  26. package/dist/index.mjs.map +1 -0
  27. package/dist/jsonl-Dw_VBp46.mjs +134 -0
  28. package/dist/jsonl-Dw_VBp46.mjs.map +1 -0
  29. package/dist/manager-D-lWBTHG.mjs +271 -0
  30. package/dist/manager-D-lWBTHG.mjs.map +1 -0
  31. package/dist/markdown-Bv4J8cg3.mjs +53 -0
  32. package/dist/markdown-Bv4J8cg3.mjs.map +1 -0
  33. package/dist/masker-Bv9KjkDF.mjs +396 -0
  34. package/dist/masker-Bv9KjkDF.mjs.map +1 -0
  35. package/dist/model-_3ZA2xqm.mjs +27 -0
  36. package/dist/model-_3ZA2xqm.mjs.map +1 -0
  37. package/dist/notify-DpG_m9iP.mjs +96 -0
  38. package/dist/notify-DpG_m9iP.mjs.map +1 -0
  39. package/dist/read-Djsx2NZl.mjs +205 -0
  40. package/dist/read-Djsx2NZl.mjs.map +1 -0
  41. package/dist/registry-CFmR5G3J.mjs +564 -0
  42. package/dist/registry-CFmR5G3J.mjs.map +1 -0
  43. package/dist/search-CRw247ES.mjs +121 -0
  44. package/dist/search-CRw247ES.mjs.map +1 -0
  45. package/dist/session/claude.d.mts +59 -0
  46. package/dist/session/claude.d.mts.map +1 -0
  47. package/dist/session/claude.mjs +455 -0
  48. package/dist/session/claude.mjs.map +1 -0
  49. package/dist/session-CUUdWFC5.mjs +450 -0
  50. package/dist/session-CUUdWFC5.mjs.map +1 -0
  51. package/dist/session-Ds5nJz5A.d.mts +346 -0
  52. package/dist/session-Ds5nJz5A.d.mts.map +1 -0
  53. package/dist/session.d.mts +37 -0
  54. package/dist/session.d.mts.map +1 -0
  55. package/dist/session.mjs +106 -0
  56. package/dist/session.mjs.map +1 -0
  57. package/dist/subagent-baqzjRMc.mjs +136 -0
  58. package/dist/subagent-baqzjRMc.mjs.map +1 -0
  59. package/dist/swarm-BJ3gQA1Z.mjs +194 -0
  60. package/dist/swarm-BJ3gQA1Z.mjs.map +1 -0
  61. package/dist/swarm-BuFx_xeY.mjs +109 -0
  62. package/dist/swarm-BuFx_xeY.mjs.map +1 -0
  63. package/dist/tasks-D8rARHZ1.mjs +65 -0
  64. package/dist/tasks-D8rARHZ1.mjs.map +1 -0
  65. package/dist/tasks-WmC270X2.mjs +615 -0
  66. package/dist/tasks-WmC270X2.mjs.map +1 -0
  67. package/dist/tools-DW1dbcCz.mjs +695 -0
  68. package/dist/tools-DW1dbcCz.mjs.map +1 -0
  69. package/dist/truncate-BSMXgiXj.mjs +102 -0
  70. package/dist/truncate-BSMXgiXj.mjs.map +1 -0
  71. package/dist/uuid-ByXP5SO5.mjs +59 -0
  72. package/dist/uuid-ByXP5SO5.mjs.map +1 -0
  73. package/dist/wakeup-BYqTnfSp.mjs +29 -0
  74. package/dist/wakeup-BYqTnfSp.mjs.map +1 -0
  75. package/dist/write-D3L-92vj.mjs +89 -0
  76. package/dist/write-D3L-92vj.mjs.map +1 -0
  77. package/package.json +63 -0
@@ -0,0 +1,564 @@
1
+ import { a as __toESM, n as parseBash, r as __commonJSMin, t as TOOLS } from "./tools-DW1dbcCz.mjs";
2
+ import { normPath, prettyPath } from "@zaly/shared";
3
+ import { createRegistry } from "@zaly/shared/registry";
4
+ import { dirname, isAbsolute, relative } from "pathe";
5
+ //#region src/permissions/handlers/bash.ts
6
+ /**
7
+ * Handler for `Rule<"bash">` — evaluates a bash command line against
8
+ * pattern rules, then delegates each segment's inferred and explicit
9
+ * file paths to the file handler via `ctx.validate("read"|"write", …)`.
10
+ *
11
+ * Composition keeps file-rule logic in one place: the bash handler
12
+ * doesn't know about workspaces, sensitive files, or path-pattern
13
+ * matching — it just asks the registered handler for those scopes.
14
+ *
15
+ * Sources of file paths per segment:
16
+ * - `seg.reads` / `seg.writes` — explicit `<` / `>` / `>>` redirects
17
+ * extracted by the parser.
18
+ * - `TOOLS[cmd].reads/writes(args)` — inferred from built-in tool
19
+ * specs (e.g. `cat foo.txt` reads `foo.txt`, `tee out` writes `out`).
20
+ *
21
+ * Suggestions and reasons from inner `validate` calls bubble up so the
22
+ * prompt UI can offer "allow `Bash(cat:*)`" alongside "add ~/Documents
23
+ * as workspace" in a single ask.
24
+ */
25
+ const bashHandler = { validate(input, ctx) {
26
+ const parsed = parseBash(input);
27
+ if (!parsed.ok) return {
28
+ ask: `Allow this bash command? (${parsed.reason})`,
29
+ reason: parsed.reason,
30
+ verdict: "ask"
31
+ };
32
+ let verdict = "allow";
33
+ let reason;
34
+ const suggestions = [];
35
+ for (const seg of parsed.segments) {
36
+ const cmdVerdict = resolveRules(ctx.rules, seg) ?? "ask";
37
+ if (seg.hasCommandSubst && cmdVerdict !== "allow") return {
38
+ ask: `Allow command substitution in \`${seg.cmd}\`?`,
39
+ reason: "command substitution not allowed",
40
+ verdict: "ask"
41
+ };
42
+ const spec = TOOLS[seg.cmd];
43
+ if (spec?.unsafe?.(seg.args) && cmdVerdict !== "allow") return {
44
+ ask: `Allow invocation of \`${seg.cmd}\`? (unsafe)`,
45
+ reason: `${seg.cmd}: invocation mode not safely modelled`,
46
+ verdict: "ask"
47
+ };
48
+ let segVerdict = cmdVerdict;
49
+ let segReason = cmdVerdict === "allow" ? void 0 : `segment "${seg.cmd}" → ${cmdVerdict}`;
50
+ if (cmdVerdict === "ask") suggestions.push({
51
+ kind: "rule",
52
+ pattern: seg.args.length > 0 ? `${seg.cmd}:*` : seg.cmd,
53
+ scope: "bash"
54
+ });
55
+ const reads = [...spec?.reads?.(seg.args) ?? [], ...seg.reads];
56
+ const writes = [...spec?.writes?.(seg.args) ?? [], ...seg.writes.map((w) => w.path)];
57
+ for (const path of reads) {
58
+ if (seg.dyn?.has(path)) {
59
+ segVerdict = combine(segVerdict, "ask");
60
+ segReason ??= `${seg.cmd}: read ${path}: dynamic path`;
61
+ suggestions.push({
62
+ kind: "rule",
63
+ pattern: `${seg.cmd}:*`,
64
+ scope: "bash"
65
+ });
66
+ continue;
67
+ }
68
+ const r = ctx.validate("read", path);
69
+ segVerdict = combine(segVerdict, r.verdict);
70
+ if (r.verdict !== "allow") {
71
+ segReason ??= `${seg.cmd}: read ${path}: ${r.reason}`;
72
+ if (r.suggestions) suggestions.push(...r.suggestions);
73
+ }
74
+ if (segVerdict === "deny") break;
75
+ }
76
+ if (segVerdict !== "deny") for (const path of writes) {
77
+ if (seg.dyn?.has(path)) {
78
+ segVerdict = combine(segVerdict, "ask");
79
+ segReason ??= `${seg.cmd}: write ${path}: dynamic path`;
80
+ suggestions.push({
81
+ kind: "rule",
82
+ pattern: `${seg.cmd}:*`,
83
+ scope: "bash"
84
+ });
85
+ continue;
86
+ }
87
+ const w = ctx.validate("write", path);
88
+ segVerdict = combine(segVerdict, w.verdict);
89
+ if (w.verdict !== "allow") {
90
+ segReason ??= `${seg.cmd}: write ${path}: ${w.reason}`;
91
+ if (w.suggestions) suggestions.push(...w.suggestions);
92
+ }
93
+ if (segVerdict === "deny") break;
94
+ }
95
+ verdict = combine(verdict, segVerdict);
96
+ if (verdict !== "allow" && reason === void 0) reason = segReason;
97
+ if (verdict === "deny") break;
98
+ }
99
+ return verdict === "allow" ? { verdict: "allow" } : {
100
+ ask: `Allow this bash command?`,
101
+ reason: reason ?? "no rule matched",
102
+ suggestions: suggestions.length > 0 ? suggestions : void 0,
103
+ verdict
104
+ };
105
+ } };
106
+ /** Combine two verdicts: deny dominates, ask beats allow. */
107
+ function combine(a, b) {
108
+ if (a === "deny" || b === "deny") return "deny";
109
+ if (a === "ask" || b === "ask") return "ask";
110
+ return "allow";
111
+ }
112
+ const patternCache = /* @__PURE__ */ new Map();
113
+ function parsePattern(pattern) {
114
+ const cached = patternCache.get(pattern);
115
+ if (cached) return cached;
116
+ let body = pattern.trim();
117
+ let trailingWildcard = false;
118
+ if (body.endsWith(":*")) {
119
+ trailingWildcard = true;
120
+ body = body.slice(0, -2);
121
+ }
122
+ const tokens = body.split(/\s+/).filter((s) => s.length > 0);
123
+ const result = {
124
+ cmd: tokens[0] ?? "",
125
+ parts: tokens.slice(1),
126
+ trailingWildcard
127
+ };
128
+ patternCache.set(pattern, result);
129
+ return result;
130
+ }
131
+ function matchPart(part, value) {
132
+ if (part.endsWith("*")) return value.startsWith(part.slice(0, -1));
133
+ return part === value;
134
+ }
135
+ /** Test whether a parsed segment matches a rule pattern. */
136
+ function matchRule(rule, seg) {
137
+ const p = parsePattern(rule.pattern);
138
+ if (p.cmd === "*" && p.parts.length === 0) return true;
139
+ if (p.cmd !== seg.cmd) return false;
140
+ if (seg.args.length < p.parts.length) return false;
141
+ for (let i = 0; i < p.parts.length; i++) if (!matchPart(p.parts[i], seg.args[i])) return false;
142
+ if (p.trailingWildcard) return true;
143
+ return seg.args.length === p.parts.length;
144
+ }
145
+ /** First-match-wins resolution. Returns `undefined` if no rule matches. */
146
+ function resolveRules(rules, seg) {
147
+ for (const r of rules) if (matchRule(r, seg)) return r.policy;
148
+ }
149
+ //#endregion
150
+ //#region src/permissions/handlers/files.ts
151
+ var import_ignore = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
152
+ function makeArray(subject) {
153
+ return Array.isArray(subject) ? subject : [subject];
154
+ }
155
+ const UNDEFINED = void 0;
156
+ const EMPTY = "";
157
+ const SPACE = " ";
158
+ const ESCAPE = "\\";
159
+ const REGEX_TEST_BLANK_LINE = /^\s+$/;
160
+ const REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
161
+ const REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
162
+ const REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
163
+ const REGEX_SPLITALL_CRLF = /\r?\n/g;
164
+ const REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/;
165
+ const REGEX_TEST_TRAILING_SLASH = /\/$/;
166
+ const SLASH = "/";
167
+ let TMP_KEY_IGNORE = "node-ignore";
168
+ /* istanbul ignore else */
169
+ if (typeof Symbol !== "undefined") TMP_KEY_IGNORE = Symbol.for("node-ignore");
170
+ const KEY_IGNORE = TMP_KEY_IGNORE;
171
+ const define = (object, key, value) => {
172
+ Object.defineProperty(object, key, { value });
173
+ return value;
174
+ };
175
+ const REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
176
+ const RETURN_FALSE = () => false;
177
+ const sanitizeRange = (range) => range.replace(REGEX_REGEXP_RANGE, (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) ? match : EMPTY);
178
+ const cleanRangeBackSlash = (slashes) => {
179
+ const { length } = slashes;
180
+ return slashes.slice(0, length - length % 2);
181
+ };
182
+ const REPLACERS = [
183
+ [/^\uFEFF/, () => EMPTY],
184
+ [/((?:\\\\)*?)(\\?\s+)$/, (_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)],
185
+ [/(\\+?)\s/g, (_, m1) => {
186
+ const { length } = m1;
187
+ return m1.slice(0, length - length % 2) + SPACE;
188
+ }],
189
+ [/[\\$.|*+(){^]/g, (match) => `\\${match}`],
190
+ [/(?!\\)\?/g, () => "[^/]"],
191
+ [/^\//, () => "^"],
192
+ [/\//g, () => "\\/"],
193
+ [/^\^*\\\*\\\*\\\//, () => "^(?:.*\\/)?"],
194
+ [/^(?=[^^])/, function startingReplacer() {
195
+ return !/\/(?!$)/.test(this) ? "(?:^|\\/)" : "^";
196
+ }],
197
+ [/\\\/\\\*\\\*(?=\\\/|$)/g, (_, index, str) => index + 6 < str.length ? "(?:\\/[^\\/]+)*" : "\\/.+"],
198
+ [/(^|[^\\]+)(\\\*)+(?=.+)/g, (_, p1, p2) => {
199
+ return p1 + p2.replace(/\\\*/g, "[^\\/]*");
200
+ }],
201
+ [/\\\\\\(?=[$.|*+(){^])/g, () => ESCAPE],
202
+ [/\\\\/g, () => ESCAPE],
203
+ [/(\\)?\[([^\]/]*?)(\\*)($|\])/g, (match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}` : close === "]" ? endEscape.length % 2 === 0 ? `[${sanitizeRange(range)}${endEscape}]` : "[]" : "[]"],
204
+ [/(?:[^*])$/, (match) => /\/$/.test(match) ? `${match}$` : `${match}(?=$|\\/$)`]
205
+ ];
206
+ const REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\\\*$/;
207
+ const MODE_IGNORE = "regex";
208
+ const MODE_CHECK_IGNORE = "checkRegex";
209
+ const TRAILING_WILD_CARD_REPLACERS = {
210
+ [MODE_IGNORE](_, p1) {
211
+ return `${p1 ? `${p1}[^/]+` : "[^/]*"}(?=$|\\/$)`;
212
+ },
213
+ [MODE_CHECK_IGNORE](_, p1) {
214
+ return `${p1 ? `${p1}[^/]*` : "[^/]*"}(?=$|\\/$)`;
215
+ }
216
+ };
217
+ const makeRegexPrefix = (pattern) => REPLACERS.reduce((prev, [matcher, replacer]) => prev.replace(matcher, replacer.bind(pattern)), pattern);
218
+ const isString = (subject) => typeof subject === "string";
219
+ const checkPattern = (pattern) => pattern && isString(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0;
220
+ const splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF).filter(Boolean);
221
+ var IgnoreRule = class {
222
+ constructor(pattern, mark, body, ignoreCase, negative, prefix) {
223
+ this.pattern = pattern;
224
+ this.mark = mark;
225
+ this.negative = negative;
226
+ define(this, "body", body);
227
+ define(this, "ignoreCase", ignoreCase);
228
+ define(this, "regexPrefix", prefix);
229
+ }
230
+ get regex() {
231
+ const key = "_regex";
232
+ if (this[key]) return this[key];
233
+ return this._make(MODE_IGNORE, key);
234
+ }
235
+ get checkRegex() {
236
+ const key = "_checkRegex";
237
+ if (this[key]) return this[key];
238
+ return this._make(MODE_CHECK_IGNORE, key);
239
+ }
240
+ _make(mode, key) {
241
+ const str = this.regexPrefix.replace(REGEX_REPLACE_TRAILING_WILDCARD, TRAILING_WILD_CARD_REPLACERS[mode]);
242
+ const regex = this.ignoreCase ? new RegExp(str, "i") : new RegExp(str);
243
+ return define(this, key, regex);
244
+ }
245
+ };
246
+ const createRule = ({ pattern, mark }, ignoreCase) => {
247
+ let negative = false;
248
+ let body = pattern;
249
+ if (body.indexOf("!") === 0) {
250
+ negative = true;
251
+ body = body.substr(1);
252
+ }
253
+ body = body.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
254
+ const regexPrefix = makeRegexPrefix(body);
255
+ return new IgnoreRule(pattern, mark, body, ignoreCase, negative, regexPrefix);
256
+ };
257
+ var RuleManager = class {
258
+ constructor(ignoreCase) {
259
+ this._ignoreCase = ignoreCase;
260
+ this._rules = [];
261
+ }
262
+ _add(pattern) {
263
+ if (pattern && pattern[KEY_IGNORE]) {
264
+ this._rules = this._rules.concat(pattern._rules._rules);
265
+ this._added = true;
266
+ return;
267
+ }
268
+ if (isString(pattern)) pattern = { pattern };
269
+ if (checkPattern(pattern.pattern)) {
270
+ const rule = createRule(pattern, this._ignoreCase);
271
+ this._added = true;
272
+ this._rules.push(rule);
273
+ }
274
+ }
275
+ add(pattern) {
276
+ this._added = false;
277
+ makeArray(isString(pattern) ? splitPattern(pattern) : pattern).forEach(this._add, this);
278
+ return this._added;
279
+ }
280
+ test(path, checkUnignored, mode) {
281
+ let ignored = false;
282
+ let unignored = false;
283
+ let matchedRule;
284
+ this._rules.forEach((rule) => {
285
+ const { negative } = rule;
286
+ if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) return;
287
+ if (!rule[mode].test(path)) return;
288
+ ignored = !negative;
289
+ unignored = negative;
290
+ matchedRule = negative ? UNDEFINED : rule;
291
+ });
292
+ const ret = {
293
+ ignored,
294
+ unignored
295
+ };
296
+ if (matchedRule) ret.rule = matchedRule;
297
+ return ret;
298
+ }
299
+ };
300
+ const throwError = (message, Ctor) => {
301
+ throw new Ctor(message);
302
+ };
303
+ const checkPath = (path, originalPath, doThrow) => {
304
+ if (!isString(path)) return doThrow(`path must be a string, but got \`${originalPath}\``, TypeError);
305
+ if (!path) return doThrow(`path must not be empty`, TypeError);
306
+ if (checkPath.isNotRelative(path)) return doThrow(`path should be a \`path.relative()\`d string, but got "${originalPath}"`, RangeError);
307
+ return true;
308
+ };
309
+ const isNotRelative = (path) => REGEX_TEST_INVALID_PATH.test(path);
310
+ checkPath.isNotRelative = isNotRelative;
311
+ /* istanbul ignore next */
312
+ checkPath.convert = (p) => p;
313
+ var Ignore = class {
314
+ constructor({ ignorecase = true, ignoreCase = ignorecase, allowRelativePaths = false } = {}) {
315
+ define(this, KEY_IGNORE, true);
316
+ this._rules = new RuleManager(ignoreCase);
317
+ this._strictPathCheck = !allowRelativePaths;
318
+ this._initCache();
319
+ }
320
+ _initCache() {
321
+ this._ignoreCache = Object.create(null);
322
+ this._testCache = Object.create(null);
323
+ }
324
+ add(pattern) {
325
+ if (this._rules.add(pattern)) this._initCache();
326
+ return this;
327
+ }
328
+ addPattern(pattern) {
329
+ return this.add(pattern);
330
+ }
331
+ _test(originalPath, cache, checkUnignored, slices) {
332
+ const path = originalPath && checkPath.convert(originalPath);
333
+ checkPath(path, originalPath, this._strictPathCheck ? throwError : RETURN_FALSE);
334
+ return this._t(path, cache, checkUnignored, slices);
335
+ }
336
+ checkIgnore(path) {
337
+ if (!REGEX_TEST_TRAILING_SLASH.test(path)) return this.test(path);
338
+ const slices = path.split(SLASH).filter(Boolean);
339
+ slices.pop();
340
+ if (slices.length) {
341
+ const parent = this._t(slices.join(SLASH) + SLASH, this._testCache, true, slices);
342
+ if (parent.ignored) return parent;
343
+ }
344
+ return this._rules.test(path, false, MODE_CHECK_IGNORE);
345
+ }
346
+ _t(path, cache, checkUnignored, slices) {
347
+ if (path in cache) return cache[path];
348
+ if (!slices) slices = path.split(SLASH).filter(Boolean);
349
+ slices.pop();
350
+ if (!slices.length) return cache[path] = this._rules.test(path, checkUnignored, MODE_IGNORE);
351
+ const parent = this._t(slices.join(SLASH) + SLASH, cache, checkUnignored, slices);
352
+ return cache[path] = parent.ignored ? parent : this._rules.test(path, checkUnignored, MODE_IGNORE);
353
+ }
354
+ ignores(path) {
355
+ return this._test(path, this._ignoreCache, false).ignored;
356
+ }
357
+ createFilter() {
358
+ return (path) => !this.ignores(path);
359
+ }
360
+ filter(paths) {
361
+ return makeArray(paths).filter(this.createFilter());
362
+ }
363
+ test(path) {
364
+ return this._test(path, this._testCache, true);
365
+ }
366
+ };
367
+ const factory = (options) => new Ignore(options);
368
+ const isPathValid = (path) => checkPath(path && checkPath.convert(path), path, RETURN_FALSE);
369
+ /* istanbul ignore next */
370
+ const setupWindows = () => {
371
+ const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
372
+ checkPath.convert = makePosix;
373
+ const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
374
+ checkPath.isNotRelative = (path) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path) || isNotRelative(path);
375
+ };
376
+ /* istanbul ignore next */
377
+ if (typeof process !== "undefined" && process.platform === "win32") setupWindows();
378
+ module.exports = factory;
379
+ factory.default = factory;
380
+ module.exports.isPathValid = isPathValid;
381
+ define(module.exports, Symbol.for("setupWindows"), setupWindows);
382
+ })))(), 1);
383
+ /**
384
+ * Handler for `Rule<"read" | "write">` — one impl serves both scopes
385
+ * because they share the same matcher and the same workspace state.
386
+ *
387
+ * Algorithm:
388
+ * 1. Normalize candidate to an absolute path.
389
+ * 2. Sensitive-file check (env, ssh keys, credentials) → deny.
390
+ * 3. Find the longest-prefix workspace containing the path. None →
391
+ * ask, with a `kind: "workspace"` suggestion for the containing
392
+ * directory.
393
+ * 4. Inside a workspace: resolve the first matching rule against the
394
+ * workspace-relative path. Earlier rules win.
395
+ * 5. Default when no rule matches but the path is inside a workspace:
396
+ * `allow` for reads (workspace = trust signal), `ask` for writes
397
+ * (mutating ops escalate by default; users add `Write(...)` rules
398
+ * to whitelist).
399
+ *
400
+ * Pattern grammar (per Claude-Code conventions):
401
+ * //abs/path absolute filesystem path
402
+ * ~/path path under home directory
403
+ * /path or path workspace-relative (passed through to gitignore)
404
+ */
405
+ const fileHandler = { validate(input, ctx) {
406
+ const abs = normPath(ctx.cwd, input);
407
+ if (isSensitiveFile(abs)) return {
408
+ reason: `${abs}: sensitive file`,
409
+ verdict: "deny"
410
+ };
411
+ const ws = longestWorkspace(abs, ctx.workspaces);
412
+ const inWorkspace = ws !== void 0;
413
+ if (inWorkspace && abs === ws) return { verdict: "allow" };
414
+ const base = ws ?? "/";
415
+ const rel = relative(base, abs);
416
+ const verb = ctx.scope === "read" ? "reading" : "writing";
417
+ const rule = resolveRule$1(base, rel, ctx.rules);
418
+ if (rule?.policy === "deny") return {
419
+ reason: `${abs}: denied by rule`,
420
+ verdict: "deny"
421
+ };
422
+ if (rule?.policy === "ask") return {
423
+ ask: `Allow ${verb} ${prettyPath(abs)}?`,
424
+ reason: `${abs}: rule requires confirmation`,
425
+ suggestions: [{
426
+ kind: "rule",
427
+ pattern: `/${rel}`,
428
+ scope: ctx.scope
429
+ }],
430
+ verdict: "ask"
431
+ };
432
+ if (rule?.policy === "allow") return { verdict: "allow" };
433
+ if (!inWorkspace) {
434
+ const parent = dirname(abs);
435
+ return {
436
+ ask: `Allow ${verb} ${prettyPath(abs)}? (not in any workspace)`,
437
+ reason: `${abs}: outside any workspace`,
438
+ suggestions: [{
439
+ description: `add ${parent} as workspace`,
440
+ kind: "workspace",
441
+ path: parent
442
+ }],
443
+ verdict: "ask"
444
+ };
445
+ }
446
+ if (ctx.scope === "read") return { verdict: "allow" };
447
+ return {
448
+ ask: `Allow ${verb} ${prettyPath(abs)}?`,
449
+ reason: `${abs}: write requires confirmation`,
450
+ suggestions: [{
451
+ kind: "rule",
452
+ pattern: `/${rel}`,
453
+ scope: "write"
454
+ }],
455
+ verdict: "ask"
456
+ };
457
+ } };
458
+ /** True if `path` looks like a credentials/keys file we shouldn't
459
+ * expose to the model by default. Conservative — false positives are
460
+ * fine here, and users can override via `allow` rules. */
461
+ function isSensitiveFile(path) {
462
+ return SENSITIVE_PATTERNS.some((re) => re.test(path));
463
+ }
464
+ /** Common locations for credentials, keys, and other sensitive data.
465
+ * Matched against the path string directly — covers both absolute and
466
+ * relative inputs. Add to this list as new gotchas surface. */
467
+ const SENSITIVE_PATTERNS = [
468
+ /(?:^|\/)\.env(?:\.|$)/,
469
+ /(?:^|\/)\.git\/(?!hooks\/)/,
470
+ /(?:^|\/)\.ssh\//,
471
+ /(?:^|\/)\.aws\/credentials/,
472
+ /(?:^|\/)\.netrc$/,
473
+ /(?:^|\/)id_(?:rsa|dsa|ed25519|ecdsa)(?:\.pub)?$/,
474
+ /(?:^|\/)\.npmrc$/,
475
+ /(?:^|\/)\.pypirc$/,
476
+ /(?:^|\/)secrets?\//,
477
+ /(?:^|\/)credentials?(?:\.|\/)/
478
+ ];
479
+ /** Longest-prefix match against the workspace list — returns the
480
+ * containing workspace's absolute path, or undefined if none contain
481
+ * the candidate. Workspaces arrive pre-normalized from the manager. */
482
+ function longestWorkspace(abs, workspaces) {
483
+ let best;
484
+ for (const root of workspaces) if (abs === root || abs.startsWith(root.endsWith("/") ? root : `${root}/`)) {
485
+ if (best === void 0 || root.length > best.length) best = root;
486
+ }
487
+ return best;
488
+ }
489
+ /** Resolve the first matching rule.
490
+ * Patterns get normalized (`//abs`, `~/`, `/rel`, `rel`) into
491
+ * workspace-rooted gitignore syntax; patterns that resolve outside the
492
+ * candidate's workspace are dropped (they don't apply here). */
493
+ function resolveRule$1(workspace, rel, rules) {
494
+ if (rel === "") return void 0;
495
+ for (const rule of rules) {
496
+ const norm = normalizePattern(rule.pattern, workspace);
497
+ if (norm === void 0) continue;
498
+ try {
499
+ if ((0, import_ignore.default)().add(norm).ignores(rel)) return rule;
500
+ } catch {}
501
+ }
502
+ }
503
+ function normalizePattern(pattern, workspace) {
504
+ if (pattern.startsWith("//")) return relativizeToWorkspace(pattern.slice(1), workspace);
505
+ if (pattern.startsWith("~/")) return relativizeToWorkspace(normPath(pattern), workspace);
506
+ return pattern;
507
+ }
508
+ function relativizeToWorkspace(absPattern, workspace) {
509
+ const rel = relative(workspace, absPattern);
510
+ if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) return void 0;
511
+ return `/${rel}`;
512
+ }
513
+ //#endregion
514
+ //#region src/permissions/handlers/tool.ts
515
+ /**
516
+ * Generic tool-scope handler.
517
+ *
518
+ * Used by `Tasks` to gate every tool dispatch on the tool name, and
519
+ * available to tools themselves for richer checks (e.g. fetch passing
520
+ * `fetch:example.com` so users can ask-for-domain).
521
+ *
522
+ * Pattern grammar is gitignore-style — reuses the `ignore` package so
523
+ * the syntax matches `read` / `write` patterns. Common shapes:
524
+ *
525
+ * "tool" → bare scope; matches every input (default-allow)
526
+ * "tool(bash)" → exact match for `"bash"`
527
+ * "tool(fetch:*)" → glob match `"fetch:..."`
528
+ * "tool(task_*)" → glob match `"task_..."`
529
+ *
530
+ * Resolution is first-match-wins. Default verdict when no rule matches is
531
+ * `allow` — an unconfigured `tool` scope should not surprise users by
532
+ * gating ordinary tool calls.
533
+ */
534
+ const toolHandler = { validate(input, ctx) {
535
+ const rule = resolveRule(input, ctx.rules);
536
+ if (rule?.policy === "deny") return {
537
+ reason: `tool ${input}: denied by rule`,
538
+ verdict: "deny"
539
+ };
540
+ if (rule?.policy === "ask") return {
541
+ ask: `Allow tool ${input}?`,
542
+ reason: `tool ${input}: rule requires confirmation`,
543
+ suggestions: [{
544
+ kind: "rule",
545
+ pattern: input,
546
+ scope: "tool"
547
+ }],
548
+ verdict: "ask"
549
+ };
550
+ return { verdict: "allow" };
551
+ } };
552
+ function resolveRule(input, rules) {
553
+ for (const rule of rules) if ((0, import_ignore.default)().add(rule.pattern).ignores(input)) return rule;
554
+ }
555
+ const handlerRegistry = createRegistry("scope").from({
556
+ bash: () => bashHandler,
557
+ read: () => fileHandler,
558
+ tool: () => toolHandler,
559
+ write: () => fileHandler
560
+ });
561
+ //#endregion
562
+ export { handlerRegistry as t };
563
+
564
+ //# sourceMappingURL=registry-CFmR5G3J.mjs.map