@nexrall/code-core 1.4.63 → 1.4.66
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/README.md +2 -2
- package/dist/agent/agentRegistry.d.ts +14 -3
- package/dist/agent/agentRegistry.d.ts.map +1 -1
- package/dist/agent/agentRegistry.js +136 -6
- package/dist/agent/agentTypes.d.ts +53 -3
- package/dist/agent/agentTypes.d.ts.map +1 -1
- package/dist/agent/agentTypes.js +200 -153
- package/dist/agent/backgroundAgents.d.ts +66 -0
- package/dist/agent/backgroundAgents.d.ts.map +1 -0
- package/dist/agent/backgroundAgents.js +145 -0
- package/dist/agent/loop.d.ts +44 -7
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +946 -263
- package/dist/agent/memory.d.ts +11 -0
- package/dist/agent/memory.d.ts.map +1 -1
- package/dist/agent/memory.js +23 -3
- package/dist/agent/modelCatalogue.d.ts +15 -0
- package/dist/agent/modelCatalogue.d.ts.map +1 -1
- package/dist/agent/modelCatalogue.js +46 -0
- package/dist/agent/planMode.d.ts +14 -0
- package/dist/agent/planMode.d.ts.map +1 -1
- package/dist/agent/planMode.js +145 -17
- package/dist/agent/readDedupe.d.ts +15 -0
- package/dist/agent/readDedupe.d.ts.map +1 -0
- package/dist/agent/readDedupe.js +146 -0
- package/dist/agent/securityLint.js +2 -2
- package/dist/agent/toolPrefetch.d.ts +44 -0
- package/dist/agent/toolPrefetch.d.ts.map +1 -0
- package/dist/agent/toolPrefetch.js +101 -0
- package/dist/agent/trust.d.ts +0 -5
- package/dist/agent/trust.d.ts.map +1 -1
- package/dist/agent/trust.js +41 -0
- package/dist/api/client.d.ts +15 -1
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +88 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/permissions/destructive.d.ts +9 -7
- package/dist/permissions/destructive.d.ts.map +1 -1
- package/dist/permissions/destructive.js +55 -8
- package/dist/permissions/destructiveTokens.d.ts +29 -0
- package/dist/permissions/destructiveTokens.d.ts.map +1 -0
- package/dist/permissions/destructiveTokens.js +469 -0
- package/dist/permissions/modePolicy.d.ts +22 -8
- package/dist/permissions/modePolicy.d.ts.map +1 -1
- package/dist/permissions/modePolicy.js +25 -10
- package/dist/tools/executor.d.ts +36 -0
- package/dist/tools/executor.d.ts.map +1 -1
- package/dist/tools/executor.js +408 -85
- package/dist/tools/tsLangService.d.ts.map +1 -1
- package/dist/tools/tsLangService.js +107 -21
- package/dist/types.d.ts +101 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +12 -1
- package/dist/util/miniYaml.d.ts +10 -0
- package/dist/util/miniYaml.d.ts.map +1 -0
- package/dist/util/miniYaml.js +149 -0
- package/package.json +2 -10
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Token-level half of the destructive-command classifier.
|
|
4
|
+
*
|
|
5
|
+
* destructive.ts started life as a pile of regexes run over the command STRING. That
|
|
6
|
+
* missed most real-world spellings, because a regex does not understand shell:
|
|
7
|
+
* - global options between the tool and its verb: git -C repo reset --hard
|
|
8
|
+
* - combined short flags: git push -fu origin main
|
|
9
|
+
* - refspec force syntax: git push origin +main
|
|
10
|
+
* - wrappers that hide the real command: sudo / env X=1 / xargs / nohup
|
|
11
|
+
* - interpreters that take the command as a string: bash -c "…", eval "…", ssh host "…"
|
|
12
|
+
* - containers that run it elsewhere: docker exec db psql … / kubectl exec -- …
|
|
13
|
+
* and it could not express "rm -rf node_modules is fine, rm -rf src is not".
|
|
14
|
+
*
|
|
15
|
+
* So this module splits the command into simple commands the way a shell would (honouring
|
|
16
|
+
* quotes, so a commit message that MENTIONS `git push --force` is one argument of
|
|
17
|
+
* `git commit`, never a command), peels wrappers off, and judges (binary, args).
|
|
18
|
+
*
|
|
19
|
+
* It is a heuristic, not a shell: no variable expansion, no aliases, no functions. When it
|
|
20
|
+
* cannot tell (an `rm -rf $DIR`), it errs toward asking — the cost of a false positive is
|
|
21
|
+
* one confirmation, the cost of a false negative is unrecoverable.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.tokenize = tokenize;
|
|
25
|
+
exports.classifyTokens = classifyTokens;
|
|
26
|
+
const hit = (category, reason) => ({ category, reason });
|
|
27
|
+
/** Split `src` into simple commands, each a list of unquoted words. */
|
|
28
|
+
function tokenize(src) {
|
|
29
|
+
const cmds = [];
|
|
30
|
+
let cur = [];
|
|
31
|
+
let word = '';
|
|
32
|
+
let has = false;
|
|
33
|
+
let q = null;
|
|
34
|
+
const pushWord = () => { if (has)
|
|
35
|
+
cur.push(word); word = ''; has = false; };
|
|
36
|
+
const pushCmd = () => { pushWord(); if (cur.length)
|
|
37
|
+
cmds.push(cur); cur = []; };
|
|
38
|
+
for (let i = 0; i < src.length; i++) {
|
|
39
|
+
const ch = src[i];
|
|
40
|
+
if (q === "'") {
|
|
41
|
+
if (ch === "'")
|
|
42
|
+
q = null;
|
|
43
|
+
else
|
|
44
|
+
word += ch;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (q === '"') {
|
|
48
|
+
if (ch === '\\' && i + 1 < src.length) {
|
|
49
|
+
word += src[++i];
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (ch === '"') {
|
|
53
|
+
q = null;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
word += ch;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (ch === "'" || ch === '"') {
|
|
60
|
+
q = ch;
|
|
61
|
+
has = true;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (ch === '\\') {
|
|
65
|
+
if (i + 1 < src.length) {
|
|
66
|
+
word += src[++i];
|
|
67
|
+
has = true;
|
|
68
|
+
}
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
// Command separators. `(` `)` and backtick make $(…) / `…` bodies their own commands.
|
|
72
|
+
if (ch === ';' || ch === '&' || ch === '|' || ch === '\n' || ch === '(' || ch === ')' || ch === '`') {
|
|
73
|
+
pushCmd();
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (ch === ' ' || ch === '\t') {
|
|
77
|
+
pushWord();
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
word += ch;
|
|
81
|
+
has = true;
|
|
82
|
+
}
|
|
83
|
+
pushCmd();
|
|
84
|
+
return cmds;
|
|
85
|
+
}
|
|
86
|
+
const ENV_ASSIGN = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
87
|
+
const CONTROL_WORDS = new Set(['{', '}', '!', 'if', 'then', 'else', 'elif', 'do', 'while', 'until', 'fi', 'done']);
|
|
88
|
+
const SHELLS = new Set(['bash', 'sh', 'zsh', 'dash', 'ksh']);
|
|
89
|
+
const base = (w) => {
|
|
90
|
+
const b = w.includes('/') ? w.slice(w.lastIndexOf('/') + 1) : w;
|
|
91
|
+
return b;
|
|
92
|
+
};
|
|
93
|
+
/** Drop leading options from `args`, consuming a value for those named in `withArg`. */
|
|
94
|
+
function dropOpts(args, withArg = new Set()) {
|
|
95
|
+
let i = 0;
|
|
96
|
+
while (i < args.length && args[i].startsWith('-') && args[i] !== '--') {
|
|
97
|
+
i += withArg.has(args[i]) ? 2 : 1;
|
|
98
|
+
}
|
|
99
|
+
if (args[i] === '--')
|
|
100
|
+
i++;
|
|
101
|
+
return args.slice(i);
|
|
102
|
+
}
|
|
103
|
+
/** True if `args` carries the long flag (or `--flag=value`) or a short-flag bundle containing `short`. */
|
|
104
|
+
function has(args, long, short) {
|
|
105
|
+
return args.some((a) => (long !== null && (a === long || a.startsWith(long + '='))) ||
|
|
106
|
+
(short !== null && /^-[A-Za-z]+$/.test(a) && a.includes(short)));
|
|
107
|
+
}
|
|
108
|
+
const BROAD = new Set(['.', './', '*', './*', ':/', ':/', '..', '../', '/', '~', '~/', '$HOME', '${HOME}', '.*']);
|
|
109
|
+
/** Directories that are regenerated by a build/install and are routine to delete. */
|
|
110
|
+
const DISPOSABLE = new Set([
|
|
111
|
+
'node_modules', 'dist', 'build', 'out', '.next', '.nuxt', '.svelte-kit', '.turbo', '.cache', '.parcel-cache',
|
|
112
|
+
'coverage', '.nyc_output', 'target', '__pycache__', '.pytest_cache', '.mypy_cache', '.ruff_cache', '.tox',
|
|
113
|
+
'venv', '.venv', '.gradle', '.dart_tool', 'tmp', '.tmp', '.vite', '.angular', '.expo', 'DerivedData',
|
|
114
|
+
]);
|
|
115
|
+
const SCRATCH_PREFIXES = ['/tmp/', '/private/tmp/', '/var/folders/', '/private/var/folders/'];
|
|
116
|
+
function isDisposableTarget(raw) {
|
|
117
|
+
let t = raw;
|
|
118
|
+
if (t === '')
|
|
119
|
+
return false;
|
|
120
|
+
// Anything we cannot resolve statically is treated as unsafe.
|
|
121
|
+
if (/[$~`]/.test(t) || t.includes('..'))
|
|
122
|
+
return false;
|
|
123
|
+
if (t.startsWith('/'))
|
|
124
|
+
return SCRATCH_PREFIXES.some((p) => t.startsWith(p) && t.length > p.length);
|
|
125
|
+
t = t.replace(/^(?:\.\/)+/, '').replace(/\/+$/, '');
|
|
126
|
+
if (t === '' || t === '.')
|
|
127
|
+
return false;
|
|
128
|
+
const segs = t.split('/').filter(Boolean);
|
|
129
|
+
if (segs.length === 0)
|
|
130
|
+
return false;
|
|
131
|
+
return DISPOSABLE.has(segs[0]) || DISPOSABLE.has(segs[segs.length - 1]);
|
|
132
|
+
}
|
|
133
|
+
function checkGit(argsIn) {
|
|
134
|
+
// Skip git's GLOBAL options so `git -C repo reset --hard` is seen as `reset --hard`.
|
|
135
|
+
let i = 0;
|
|
136
|
+
while (i < argsIn.length && argsIn[i].startsWith('-')) {
|
|
137
|
+
const a = argsIn[i];
|
|
138
|
+
i += ['-C', '-c', '--git-dir', '--work-tree', '--namespace', '--exec-path'].includes(a) ? 2 : 1;
|
|
139
|
+
}
|
|
140
|
+
const sub = argsIn[i];
|
|
141
|
+
const args = argsIn.slice(i + 1);
|
|
142
|
+
const operands = args.filter((a) => !a.startsWith('-'));
|
|
143
|
+
switch (sub) {
|
|
144
|
+
case 'push': {
|
|
145
|
+
if (has(args, '--force', 'f') || operands.some((a) => a.startsWith('+'))) {
|
|
146
|
+
return hit('git-history', 'force-pushes (can overwrite/destroy remote history)');
|
|
147
|
+
}
|
|
148
|
+
if (has(args, '--delete', 'd') || operands.some((a) => a.startsWith(':'))) {
|
|
149
|
+
return hit('git-history', 'deletes a remote branch or tag');
|
|
150
|
+
}
|
|
151
|
+
if (has(args, '--mirror', null)) {
|
|
152
|
+
return hit('git-history', 'mirror-pushes (overwrites and deletes remote refs to match local)');
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
case 'reset':
|
|
157
|
+
if (has(args, '--hard', null) || has(args, '--keep', null) || has(args, '--merge', null)) {
|
|
158
|
+
return hit('git-history', 'hard-resets the working tree (discards uncommitted work)');
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
case 'clean':
|
|
162
|
+
if (has(args, '--force', 'f') && !has(args, '--dry-run', 'n')) {
|
|
163
|
+
return hit('git-history', 'deletes untracked files (git clean)');
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
case 'branch':
|
|
167
|
+
if (args.some((a) => /^-[A-Za-z]*D/.test(a)) ||
|
|
168
|
+
((has(args, '--delete', 'd')) && has(args, '--force', 'f'))) {
|
|
169
|
+
return hit('git-history', 'force-deletes a git branch');
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
case 'checkout':
|
|
173
|
+
if (has(args, '--force', 'f'))
|
|
174
|
+
return hit('git-history', 'git checkout --force discards local changes');
|
|
175
|
+
if (args.some((a) => BROAD.has(a)))
|
|
176
|
+
return hit('git-history', 'discards every uncommitted change in the working tree');
|
|
177
|
+
return null;
|
|
178
|
+
case 'restore': {
|
|
179
|
+
const stagedOnly = has(args, '--staged', 'S') && !has(args, '--worktree', 'W');
|
|
180
|
+
if (!stagedOnly && args.some((a) => BROAD.has(a))) {
|
|
181
|
+
return hit('git-history', 'discards every uncommitted change in the working tree');
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
case 'stash':
|
|
186
|
+
if (args[0] === 'drop' || args[0] === 'clear')
|
|
187
|
+
return hit('git-history', 'drops stashed work (not recoverable without the commit hash)');
|
|
188
|
+
return null;
|
|
189
|
+
case 'update-ref':
|
|
190
|
+
if (has(args, '-d', 'd') || has(args, '--delete', null))
|
|
191
|
+
return hit('git-history', 'deletes a git ref');
|
|
192
|
+
return null;
|
|
193
|
+
case 'reflog':
|
|
194
|
+
if (args[0] === 'delete' || args[0] === 'expire')
|
|
195
|
+
return hit('git-history', 'reflog expire / gc prune (permanently drops recoverable commits)');
|
|
196
|
+
return null;
|
|
197
|
+
case 'gc':
|
|
198
|
+
if (args.some((a) => /^--prune=(?:now|all)$/.test(a)))
|
|
199
|
+
return hit('git-history', 'reflog expire / gc prune (permanently drops recoverable commits)');
|
|
200
|
+
return null;
|
|
201
|
+
case 'worktree':
|
|
202
|
+
if (args[0] === 'remove' && has(args, '--force', 'f'))
|
|
203
|
+
return hit('git-history', 'force-removes a worktree (discards its uncommitted work)');
|
|
204
|
+
return null;
|
|
205
|
+
case 'filter-branch':
|
|
206
|
+
case 'filter-repo':
|
|
207
|
+
return hit('git-history', 'rewrites git history (filter-branch/filter-repo)');
|
|
208
|
+
case 'rm':
|
|
209
|
+
// `git rm -r <dir>` is routine and recoverable from HEAD; only a bare `.`/`*` is a wipe.
|
|
210
|
+
if (operands.some((a) => BROAD.has(a))) {
|
|
211
|
+
return hit('git-history', 'removes every tracked file from the working tree and index');
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
default:
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function checkRm(args) {
|
|
219
|
+
const targets = [];
|
|
220
|
+
let recursive = false;
|
|
221
|
+
let endOpts = false;
|
|
222
|
+
for (const a of args) {
|
|
223
|
+
if (!endOpts && a === '--') {
|
|
224
|
+
endOpts = true;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (!endOpts && a.startsWith('-')) {
|
|
228
|
+
if (a === '--recursive' || /^-[A-Za-z]*[rR]/.test(a))
|
|
229
|
+
recursive = true;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
targets.push(a);
|
|
233
|
+
}
|
|
234
|
+
if (targets.some((t) => BROAD.has(t)))
|
|
235
|
+
return hit('filesystem', 'deletes the current directory, home, or everything matching a bare glob');
|
|
236
|
+
if (recursive && targets.some((t) => !isDisposableTarget(t))) {
|
|
237
|
+
return hit('filesystem', 'recursively deletes files or directories that are not a known build artefact');
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
function checkFind(args) {
|
|
242
|
+
if (args.includes('-delete'))
|
|
243
|
+
return hit('filesystem', 'find -delete removes every match');
|
|
244
|
+
for (let i = 0; i < args.length - 1; i++) {
|
|
245
|
+
if ((args[i] === '-exec' || args[i] === '-execdir' || args[i] === '-ok') && ['rm', 'shred', 'unlink'].includes(base(args[i + 1]))) {
|
|
246
|
+
return hit('filesystem', 'find -exec rm removes every match');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
/** Judge one already-unwrapped command. `recurse` re-enters the string classifier. */
|
|
252
|
+
function judge(bin, args) {
|
|
253
|
+
switch (bin) {
|
|
254
|
+
case 'git': return checkGit(args);
|
|
255
|
+
case 'rm':
|
|
256
|
+
case 'unlink': return checkRm(args);
|
|
257
|
+
case 'find': return checkFind(args);
|
|
258
|
+
case 'rsync':
|
|
259
|
+
if (args.some((a) => a === '--del' || /^--delete(?:-[a-z]+)?$/.test(a)))
|
|
260
|
+
return hit('filesystem', 'rsync --delete removes destination files that are absent from the source');
|
|
261
|
+
return null;
|
|
262
|
+
case 'truncate': return hit('filesystem', 'truncate cuts a file (contents lost)');
|
|
263
|
+
case 'shred': return hit('filesystem', 'shred irrecoverably overwrites a file');
|
|
264
|
+
case 'dd':
|
|
265
|
+
if (args.some((a) => /^of=\/dev\//.test(a)))
|
|
266
|
+
return hit('filesystem', 'dd writing to a device (overwrites the disk)');
|
|
267
|
+
return null;
|
|
268
|
+
case 'diskutil':
|
|
269
|
+
if (/^(?:erase|secureErase|zeroDisk|randomDisk|partitionDisk|reformat)/i.test(args[0] ?? ''))
|
|
270
|
+
return hit('filesystem', 'diskutil erases or repartitions a disk');
|
|
271
|
+
return null;
|
|
272
|
+
case 'chmod': {
|
|
273
|
+
if (!has(args, '--recursive', 'R'))
|
|
274
|
+
return null;
|
|
275
|
+
const operands = args.filter((a) => !a.startsWith('-'));
|
|
276
|
+
if (operands.slice(1).some((t) => BROAD.has(t)))
|
|
277
|
+
return hit('filesystem', 'recursive chmod over the current directory / home / root');
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
case 'terraform':
|
|
281
|
+
case 'tofu':
|
|
282
|
+
case 'terragrunt':
|
|
283
|
+
if (args.includes('destroy') || (args.includes('apply') && (args.includes('-destroy') || args.includes('-auto-approve')))) {
|
|
284
|
+
return hit('infra', 'destroys or applies Terraform-managed infrastructure without a plan review');
|
|
285
|
+
}
|
|
286
|
+
return null;
|
|
287
|
+
case 'kubectl':
|
|
288
|
+
case 'oc': {
|
|
289
|
+
const a = dropOpts(args, new Set(['-n', '--namespace', '--context', '--cluster', '--kubeconfig', '-s', '--server', '--user']));
|
|
290
|
+
if (a[0] === 'delete')
|
|
291
|
+
return hit('infra', 'deletes Kubernetes resources');
|
|
292
|
+
if (a[0] === 'drain')
|
|
293
|
+
return hit('infra', 'drains a node (evicts every pod)');
|
|
294
|
+
if (a[0] === 'replace' && has(a, '--force', null))
|
|
295
|
+
return hit('infra', 'kubectl replace --force deletes and recreates the resource');
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
case 'helm': {
|
|
299
|
+
const a = dropOpts(args, new Set(['-n', '--namespace', '--kube-context', '--kubeconfig']));
|
|
300
|
+
if (a[0] === 'uninstall' || a[0] === 'delete')
|
|
301
|
+
return hit('infra', 'helm uninstall / delete (removes a release)');
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
case 'aws':
|
|
305
|
+
if (args[0] === 's3' && args[1] === 'sync' && has(args, '--delete', null))
|
|
306
|
+
return hit('cloud', 'aws s3 sync --delete removes bucket objects missing locally');
|
|
307
|
+
return null;
|
|
308
|
+
case 'vercel':
|
|
309
|
+
if (args[0] === 'remove' || args[0] === 'rm')
|
|
310
|
+
return hit('cloud', 'removes a Vercel project or deployment');
|
|
311
|
+
return null;
|
|
312
|
+
case 'fly':
|
|
313
|
+
case 'flyctl':
|
|
314
|
+
if (args.includes('destroy'))
|
|
315
|
+
return hit('cloud', 'destroys a Fly.io app, volume or machine');
|
|
316
|
+
return null;
|
|
317
|
+
case 'heroku':
|
|
318
|
+
if (args.some((a) => /:destroy$/.test(a) || a === 'pg:reset'))
|
|
319
|
+
return hit('cloud', 'destroys a Heroku app or resets its database');
|
|
320
|
+
return null;
|
|
321
|
+
case 'supabase':
|
|
322
|
+
if ((args[0] === 'db' && args[1] === 'reset') || (args[0] === 'projects' && args[1] === 'delete'))
|
|
323
|
+
return hit('cloud', 'resets a Supabase database or deletes a project');
|
|
324
|
+
return null;
|
|
325
|
+
case 'firebase':
|
|
326
|
+
if (args.some((a) => /:delete$/.test(a)))
|
|
327
|
+
return hit('cloud', 'deletes Firebase data or a project');
|
|
328
|
+
return null;
|
|
329
|
+
case 'netlify':
|
|
330
|
+
if (args.some((a) => /:delete$/.test(a)))
|
|
331
|
+
return hit('cloud', 'deletes a Netlify site');
|
|
332
|
+
return null;
|
|
333
|
+
case 'npm':
|
|
334
|
+
case 'pnpm':
|
|
335
|
+
case 'yarn':
|
|
336
|
+
if (args[0] === 'unpublish')
|
|
337
|
+
return hit('publish', 'unpublishes a package version');
|
|
338
|
+
if (args[0] === 'dist-tag' && (args[1] === 'rm' || args[1] === 'remove'))
|
|
339
|
+
return hit('publish', 'removes an npm dist-tag (can break `npm i pkg@latest`)');
|
|
340
|
+
return null;
|
|
341
|
+
case 'gh': {
|
|
342
|
+
if ((args[0] === 'repo' || args[0] === 'release') && args[1] === 'delete')
|
|
343
|
+
return hit('cloud', 'deletes a GitHub repository or release');
|
|
344
|
+
if (args[0] === 'api') {
|
|
345
|
+
const m = args.findIndex((a) => a === '-X' || a === '--method');
|
|
346
|
+
const inline = args.find((a) => /^(?:-X|--method=)DELETE$/i.test(a));
|
|
347
|
+
if ((m >= 0 && /^delete$/i.test(args[m + 1] ?? '')) || inline)
|
|
348
|
+
return hit('cloud', 'sends an HTTP DELETE to the GitHub API');
|
|
349
|
+
}
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
case 'docker':
|
|
353
|
+
case 'docker-compose':
|
|
354
|
+
case 'podman': {
|
|
355
|
+
const a = bin === 'docker-compose' ? ['compose', ...args] : dropOpts(args, new Set(['-H', '--host', '--context', '-c']));
|
|
356
|
+
const verb = a[0] === 'container' ? a[1] : a[0];
|
|
357
|
+
const rest = a.slice(a[0] === 'container' ? 2 : 1);
|
|
358
|
+
if (verb === 'rm' && has(rest, '--force', 'f') && rest.some((x) => x.startsWith('$')))
|
|
359
|
+
return hit('container', 'force-removes many containers at once');
|
|
360
|
+
if (a[0] === 'compose' && rest.includes('down') && (has(rest, '--volumes', 'v')))
|
|
361
|
+
return hit('container', 'docker compose down with -v (deletes named volumes)');
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
case 'dropdb':
|
|
365
|
+
case 'dropuser': return hit('database', 'drops a Postgres database or role');
|
|
366
|
+
default:
|
|
367
|
+
if (bin.startsWith('mkfs'))
|
|
368
|
+
return hit('filesystem', 'mkfs formats a filesystem (destroys its contents)');
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Peel wrappers off one simple command and judge what is really being run.
|
|
374
|
+
* `recurse` classifies a nested command STRING (bash -c "…", ssh host "…", eval …).
|
|
375
|
+
*/
|
|
376
|
+
const DB_BINS = new Set([
|
|
377
|
+
'psql', 'mysql', 'mariadb', 'mongo', 'mongosh', 'sqlite3', 'cockroach', 'clickhouse-client', 'pgcli', 'mycli', 'usql', 'sqlcmd', 'redis-cli',
|
|
378
|
+
]);
|
|
379
|
+
function classifyTokens(tokens, recurse, rawSql) {
|
|
380
|
+
let t = tokens.slice();
|
|
381
|
+
for (let guard = 0; guard < 12; guard++) {
|
|
382
|
+
while (t.length && (ENV_ASSIGN.test(t[0]) || CONTROL_WORDS.has(t[0])))
|
|
383
|
+
t = t.slice(1);
|
|
384
|
+
if (t.length === 0)
|
|
385
|
+
return null;
|
|
386
|
+
// A bare redirect with no command (`> file`, `: > file`, `true > file`) truncates the file.
|
|
387
|
+
{
|
|
388
|
+
const r = /^>(?![>&])/.test(t[0]) ? t : ((t[0] === ':' || t[0] === 'true') && /^>(?![>&])/.test(t[1] ?? '')) ? t.slice(1) : null;
|
|
389
|
+
if (r) {
|
|
390
|
+
const target = r[0].length > 1 ? r[0].slice(1) : (r[1] ?? '');
|
|
391
|
+
if (!target.startsWith('/dev/'))
|
|
392
|
+
return hit('filesystem', 'a bare `>` redirect truncates the file (contents lost)');
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
const bin = base(t[0]);
|
|
397
|
+
const args = t.slice(1);
|
|
398
|
+
// Real SQL lives inside quotes, which the tokenizer has already unwrapped; hand it back
|
|
399
|
+
// to the quote-preserving SQL rules as a normal `client "arg" "arg"` string.
|
|
400
|
+
if (DB_BINS.has(bin)) {
|
|
401
|
+
return rawSql(bin + ' ' + args.map((a) => JSON.stringify(a)).join(' '));
|
|
402
|
+
}
|
|
403
|
+
if (bin === 'sudo' || bin === 'doas') {
|
|
404
|
+
t = dropOpts(args, new Set(['-u', '-g', '-h', '-p', '-C', '-D', '-R', '-T', '-U']));
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if (bin === 'env') {
|
|
408
|
+
t = dropOpts(args, new Set(['-u', '-C', '-S']));
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (bin === 'nice') {
|
|
412
|
+
t = dropOpts(args, new Set(['-n']));
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (bin === 'ionice') {
|
|
416
|
+
t = dropOpts(args, new Set(['-c', '-n', '-p']));
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
if (bin === 'timeout') {
|
|
420
|
+
t = dropOpts(args, new Set(['-s', '-k'])).slice(1);
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
if (bin === 'xargs') {
|
|
424
|
+
t = dropOpts(args, new Set(['-I', '-n', '-P', '-L', '-s', '-d', '-E', '-a', '-J']));
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (['command', 'builtin', 'nohup', 'exec', 'time', 'setsid', 'caffeinate', 'stdbuf', 'unbuffer'].includes(bin)) {
|
|
428
|
+
t = dropOpts(args);
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (SHELLS.has(bin)) {
|
|
432
|
+
const i = args.findIndex((a) => /^-[A-Za-z]*c[A-Za-z]*$/.test(a));
|
|
433
|
+
if (i >= 0 && args[i + 1] !== undefined)
|
|
434
|
+
return recurse(args[i + 1]);
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
if (bin === 'eval')
|
|
438
|
+
return recurse(args.join(' '));
|
|
439
|
+
if (bin === 'ssh') {
|
|
440
|
+
const a = dropOpts(args, new Set(['-p', '-i', '-l', '-o', '-F', '-J', '-L', '-R', '-D', '-b', '-c', '-E', '-e', '-I', '-m', '-O', '-Q', '-S', '-w', '-W']));
|
|
441
|
+
return a.length > 1 ? recurse(a.slice(1).join(' ')) : null;
|
|
442
|
+
}
|
|
443
|
+
if (bin === 'docker' || bin === 'podman' || bin === 'docker-compose') {
|
|
444
|
+
const a0 = bin === 'docker-compose' ? ['compose', ...args] : dropOpts(args, new Set(['-H', '--host', '--context', '-c']));
|
|
445
|
+
const isExec = a0[0] === 'exec' || (a0[0] === 'compose' && dropOpts(a0.slice(1), new Set(['-f', '-p', '--project-name', '--file', '--profile']))[0] === 'exec');
|
|
446
|
+
if (isExec) {
|
|
447
|
+
const afterVerb = a0[0] === 'exec'
|
|
448
|
+
? a0.slice(1)
|
|
449
|
+
: dropOpts(a0.slice(1), new Set(['-f', '-p', '--project-name', '--file', '--profile'])).slice(1);
|
|
450
|
+
const inner = dropOpts(afterVerb, new Set(['-e', '-u', '-w', '--env', '--user', '--workdir', '--env-file', '--detach-keys', '--index']));
|
|
451
|
+
if (inner.length > 1) {
|
|
452
|
+
t = inner.slice(1);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (bin === 'kubectl' || bin === 'oc') {
|
|
459
|
+
const i = args.indexOf('--');
|
|
460
|
+
if (i >= 0 && args.includes('exec') && i + 1 < args.length) {
|
|
461
|
+
t = args.slice(i + 1);
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return judge(bin, args);
|
|
466
|
+
}
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
//# sourceMappingURL=destructiveTokens.js.map
|
|
@@ -18,6 +18,16 @@ export interface PolicyInput {
|
|
|
18
18
|
* in two windows. Note `memory_write` is NOT here even though the CLI's old list had it
|
|
19
19
|
* — it mutates durable cross-session state, and listing it under "read-only / safe"
|
|
20
20
|
* meant it silently bypassed plan mode's read-only guarantee.
|
|
21
|
+
*
|
|
22
|
+
* `task` IS here on purpose, to stay in lockstep with `planMode.ts`'s own
|
|
23
|
+
* READ_ONLY_TOOLS: that module already lets `task` through its plan-mode lock
|
|
24
|
+
* because delegating research to a sub-agent is safe — `runAgentLoop` passes
|
|
25
|
+
* `planMode` down, so the sub-agent is under the identical read-only lock and
|
|
26
|
+
* its own write tools are refused one level deeper. Without `task` here too,
|
|
27
|
+
* step 3 of `decide()` (`mode === 'plan' → deny`) would silently re-block a
|
|
28
|
+
* call that `checkPlanMode()` had already deliberately allowed through — the
|
|
29
|
+
* exact "two copies that drift" failure this module exists to prevent, just
|
|
30
|
+
* hitting `task` specifically instead of the read-only tools it was written for.
|
|
21
31
|
*/
|
|
22
32
|
export declare const READ_ONLY_TOOLS: Set<string>;
|
|
23
33
|
/** Tools that write to the filesystem. */
|
|
@@ -27,16 +37,20 @@ export declare const FILE_MUTATE_TOOLS: Set<string>;
|
|
|
27
37
|
/** Tools that run a shell command. */
|
|
28
38
|
export declare const BASH_TOOLS: Set<string>;
|
|
29
39
|
/**
|
|
30
|
-
* Is this bash command read-only?
|
|
40
|
+
* Is this bash command read-only — i.e. safe to run WITHOUT asking?
|
|
31
41
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* (
|
|
35
|
-
*
|
|
42
|
+
* History: this used to be `!bashNeedsRepoLock(command)`. That classifier answers a
|
|
43
|
+
* different question — "does this need the repo-state LOCK?" — by listing the ~30
|
|
44
|
+
* build/VCS verbs that take one (git, npm, make …). Everything NOT on that list was
|
|
45
|
+
* therefore "read-only", so `rm file`, `mv`, `echo x > f`, `sed -i`, `kill` and
|
|
46
|
+
* `curl … | sh` all ran with no prompt in `ask` and `edit` mode. Measured through the
|
|
47
|
+
* real `requestPermission`: prompted=0 for each. A denylist of mutators can never be
|
|
48
|
+
* complete; the gate needs an ALLOWLIST of readers, which plan mode already had.
|
|
36
49
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
50
|
+
* It now delegates to `isReadOnlyPipeline` (agent/planMode.ts), the same "provably
|
|
51
|
+
* read-only" classifier the plan-mode lock uses, so "read-only" means one thing in
|
|
52
|
+
* both places. `bashNeedsRepoLock` is unchanged and still correct for what it is for:
|
|
53
|
+
* deciding which calls must not run concurrently.
|
|
40
54
|
*/
|
|
41
55
|
export declare function isReadOnlyBash(command: string): boolean;
|
|
42
56
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modePolicy.d.ts","sourceRoot":"","sources":["../../src/permissions/modePolicy.ts"],"names":[],"mappings":"AAsBA,yDAAyD;AACzD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9D,kDAAkD;AAClD,MAAM,MAAM,cAAc,GACtB,OAAO,GACP,KAAK,GACL,MAAM,GACN,SAAS,CAAC;AAEd,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,EAAE,cAAc,CAAC;IACrB,oFAAoF;IACpF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED
|
|
1
|
+
{"version":3,"file":"modePolicy.d.ts","sourceRoot":"","sources":["../../src/permissions/modePolicy.ts"],"names":[],"mappings":"AAsBA,yDAAyD;AACzD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9D,kDAAkD;AAClD,MAAM,MAAM,cAAc,GACtB,OAAO,GACP,KAAK,GACL,MAAM,GACN,SAAS,CAAC;AAEd,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,EAAE,cAAc,CAAC;IACrB,oFAAoF;IACpF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,eAAe,aAW1B,CAAC;AAEH,0CAA0C;AAC1C,eAAO,MAAM,gBAAgB,aAG3B,CAAC;AAEH,oEAAoE;AACpE,eAAO,MAAM,iBAAiB,aAE5B,CAAC;AAEH,sCAAsC;AACtC,eAAO,MAAM,UAAU,aAAsD,CAAC;AAE9E;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,WAAW,GAAG,cAAc,CAwDvD;AAED,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAOzD;AAED,gFAAgF;AAChF,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,CAGtD"}
|
|
@@ -5,7 +5,7 @@ exports.isReadOnlyBash = isReadOnlyBash;
|
|
|
5
5
|
exports.decide = decide;
|
|
6
6
|
exports.describeMode = describeMode;
|
|
7
7
|
exports.parseMode = parseMode;
|
|
8
|
-
const
|
|
8
|
+
const planMode_1 = require("../agent/planMode");
|
|
9
9
|
/**
|
|
10
10
|
* Tools that only ever READ. Always allowed, in every mode including plan.
|
|
11
11
|
*
|
|
@@ -13,6 +13,16 @@ const bashClassify_1 = require("./bashClassify");
|
|
|
13
13
|
* in two windows. Note `memory_write` is NOT here even though the CLI's old list had it
|
|
14
14
|
* — it mutates durable cross-session state, and listing it under "read-only / safe"
|
|
15
15
|
* meant it silently bypassed plan mode's read-only guarantee.
|
|
16
|
+
*
|
|
17
|
+
* `task` IS here on purpose, to stay in lockstep with `planMode.ts`'s own
|
|
18
|
+
* READ_ONLY_TOOLS: that module already lets `task` through its plan-mode lock
|
|
19
|
+
* because delegating research to a sub-agent is safe — `runAgentLoop` passes
|
|
20
|
+
* `planMode` down, so the sub-agent is under the identical read-only lock and
|
|
21
|
+
* its own write tools are refused one level deeper. Without `task` here too,
|
|
22
|
+
* step 3 of `decide()` (`mode === 'plan' → deny`) would silently re-block a
|
|
23
|
+
* call that `checkPlanMode()` had already deliberately allowed through — the
|
|
24
|
+
* exact "two copies that drift" failure this module exists to prevent, just
|
|
25
|
+
* hitting `task` specifically instead of the read-only tools it was written for.
|
|
16
26
|
*/
|
|
17
27
|
exports.READ_ONLY_TOOLS = new Set([
|
|
18
28
|
'read_file', 'list_directory', 'search_files', 'glob', 'grep',
|
|
@@ -24,6 +34,7 @@ exports.READ_ONLY_TOOLS = new Set([
|
|
|
24
34
|
'notebook_read',
|
|
25
35
|
'get_diagnostics', 'go_to_definition', 'find_references',
|
|
26
36
|
'get_symbols', 'get_workspace_symbols', 'get_hover',
|
|
37
|
+
'task',
|
|
27
38
|
]);
|
|
28
39
|
/** Tools that write to the filesystem. */
|
|
29
40
|
exports.FILE_WRITE_TOOLS = new Set([
|
|
@@ -37,19 +48,23 @@ exports.FILE_MUTATE_TOOLS = new Set([
|
|
|
37
48
|
/** Tools that run a shell command. */
|
|
38
49
|
exports.BASH_TOOLS = new Set(['bash', 'run_command', 'execute_command']);
|
|
39
50
|
/**
|
|
40
|
-
* Is this bash command read-only?
|
|
51
|
+
* Is this bash command read-only — i.e. safe to run WITHOUT asking?
|
|
41
52
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* (
|
|
45
|
-
*
|
|
53
|
+
* History: this used to be `!bashNeedsRepoLock(command)`. That classifier answers a
|
|
54
|
+
* different question — "does this need the repo-state LOCK?" — by listing the ~30
|
|
55
|
+
* build/VCS verbs that take one (git, npm, make …). Everything NOT on that list was
|
|
56
|
+
* therefore "read-only", so `rm file`, `mv`, `echo x > f`, `sed -i`, `kill` and
|
|
57
|
+
* `curl … | sh` all ran with no prompt in `ask` and `edit` mode. Measured through the
|
|
58
|
+
* real `requestPermission`: prompted=0 for each. A denylist of mutators can never be
|
|
59
|
+
* complete; the gate needs an ALLOWLIST of readers, which plan mode already had.
|
|
46
60
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
61
|
+
* It now delegates to `isReadOnlyPipeline` (agent/planMode.ts), the same "provably
|
|
62
|
+
* read-only" classifier the plan-mode lock uses, so "read-only" means one thing in
|
|
63
|
+
* both places. `bashNeedsRepoLock` is unchanged and still correct for what it is for:
|
|
64
|
+
* deciding which calls must not run concurrently.
|
|
50
65
|
*/
|
|
51
66
|
function isReadOnlyBash(command) {
|
|
52
|
-
return
|
|
67
|
+
return (0, planMode_1.isReadOnlyPipeline)(command);
|
|
53
68
|
}
|
|
54
69
|
/**
|
|
55
70
|
* The single source of truth for "should we prompt?".
|
package/dist/tools/executor.d.ts
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
import type { ToolResult } from '../types';
|
|
2
2
|
import { type SandboxConfig } from './sandbox';
|
|
3
|
+
/**
|
|
4
|
+
* Cap output from a tool we do not control (MCP servers). Built-in tools all cap their
|
|
5
|
+
* own output, but an MCP result went into the conversation verbatim: one call to a
|
|
6
|
+
* DB/browser/log server could inline megabytes, which is then re-sent (and re-billed as
|
|
7
|
+
* cache writes) on every later round until compaction. Keep HEAD+TAIL inline and spill
|
|
8
|
+
* the full text to a temp file the model can read_file with offset/limit — the same
|
|
9
|
+
* contract as bash's GAP B spill.
|
|
10
|
+
*/
|
|
11
|
+
export declare function capExternalOutput(output: string, label: string, maxChars?: number): string;
|
|
12
|
+
/**
|
|
13
|
+
* Async drop-in for the subset of `spawnSync` the search tools use. spawnSync blocked
|
|
14
|
+
* the Node event loop for the whole search: several search_files calls issued in one
|
|
15
|
+
* parallel tool batch ran strictly one after another, and a search started by the
|
|
16
|
+
* mid-stream prefetcher froze SSE parsing and UI rendering until it finished. Result
|
|
17
|
+
* shape mirrors spawnSync's (`status`, `signal`, `error` with code ETIMEDOUT / ENOBUFS)
|
|
18
|
+
* so the callers' existing failure handling is unchanged.
|
|
19
|
+
*/
|
|
20
|
+
export interface CaptureResult {
|
|
21
|
+
stdout: string;
|
|
22
|
+
stderr: string;
|
|
23
|
+
status: number | null;
|
|
24
|
+
signal: NodeJS.Signals | null;
|
|
25
|
+
error?: NodeJS.ErrnoException;
|
|
26
|
+
}
|
|
27
|
+
export declare function runCapture(cmd: string, args: string[], opts: {
|
|
28
|
+
cwd?: string;
|
|
29
|
+
timeout: number;
|
|
30
|
+
maxBuffer: number;
|
|
31
|
+
}): Promise<CaptureResult>;
|
|
32
|
+
/**
|
|
33
|
+
* Glob → anchored RegExp. Scans the pattern once, so the `*` / `?` rules can never
|
|
34
|
+
* rewrite the regex emitted for `**` (the old chained .replace() calls turned
|
|
35
|
+
* `**\/` into `([^/]:[^/]+/)` — every `**` pattern silently matched nothing).
|
|
36
|
+
* Supports `**`, `*`, `?`, `{a,b}` and `[...]` classes.
|
|
37
|
+
*/
|
|
38
|
+
export declare function globToRegex(pattern: string): RegExp;
|
|
3
39
|
export declare function executeTool(name: string, input: Record<string, unknown>, abortSignal?: {
|
|
4
40
|
aborted: boolean;
|
|
5
41
|
}, sandbox?: SandboxConfig, workDir?: string, agentScope?: string, onStream?: (chunk: string) => void, selfPeer?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/tools/executor.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAyB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/tools/executor.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAyB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAokCtE;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,SAAmB,GAAG,MAAM,CAcpG;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;CAC/B;AACD,wBAAgB,UAAU,CACxB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACzD,OAAO,CAAC,aAAa,CAAC,CA0CxB;AAsoCD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAqCnD;AAwwBD,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,WAAW,CAAC,EAAE;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,EAClC,OAAO,CAAC,EAAE,aAAa,EACvB,OAAO,CAAC,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EAMlC,QAAQ,CAAC,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACtC,OAAO,CAAC,UAAU,CAAC,CAkCrB"}
|