@amaster.ai/dsh-policy 0.1.34 → 0.1.35
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 +3 -3
- package/lib/index.js +2 -3
- package/lib/rule.js +7 -6
- package/lib/shell.js +208 -12
- package/lib/types/shell.d.ts +9 -6
- package/package.json +19 -19
package/README.md
CHANGED
|
@@ -73,9 +73,9 @@ Plugin-level fields: `enabled` (master switch) and `commandKeys` (argument keys
|
|
|
73
73
|
### Matching semantics
|
|
74
74
|
|
|
75
75
|
- **Every condition on a rule must hold** for the rule to match (AND). A rule with no conditions beyond `tool` matches every call of that tool.
|
|
76
|
-
- **Shell commands are checked segment by segment.** Compound commands (`a && b | c`, newlines, background `&`) are split, quote-aware, and `$( )` /
|
|
76
|
+
- **Shell commands are checked segment by segment.** Compound commands (`a && b | c`, newlines, background `&`) are split, quote-aware, and the executable command word is normalized through quotes, escapes and line continuations. Bash `$( )`, backtick and `<()` / `>()` substitutions are extracted as their own segments; here-doc bodies are data, except for real substitutions in unquoted bodies. PowerShell backticks are escapes, not Bash-style substitutions.
|
|
77
77
|
- **Priority resolves competition within one segment**; across segments the aggregation is conservative: any segment's `deny` denies the whole call, then any `ask` escalates, and `allow` requires every segment decided allow. A broad deny is still overridable by a specific allow because both compete on the same segment (`bun run lint` at 300 vs `bun run` at 200).
|
|
78
|
-
- **No rule matches → the call passes through** to the rest of the `tools/pre-execute` chain (`next()`), so dsh's own gates and other plugins keep their say. Invalid regexes are config errors and fail the plugin load; a runtime evaluation failure is logged with the `[dsh-policy]` prefix and
|
|
78
|
+
- **No rule matches → the call passes through** to the rest of the `tools/pre-execute` chain (`next()`), so dsh's own gates and other plugins keep their say. Invalid regexes are config errors and fail the plugin load; a runtime evaluation failure is logged with the `[dsh-policy]` prefix and denied (fail-closed) without breaking the agent loop.
|
|
79
79
|
|
|
80
80
|
### What the gate covers
|
|
81
81
|
|
|
@@ -111,7 +111,7 @@ A policy plugin is advisory gating, not containment: deny rules keep a well-beha
|
|
|
111
111
|
|
|
112
112
|
| @amaster.ai/dsh-policy | dsh | cordis |
|
|
113
113
|
| --- | --- | --- |
|
|
114
|
-
| 0.1.x | `>=0.1.5-rc.1 || >=0.1.6-alpha.1 || >=0.1.7-alpha.1 || >=0.2.0-rc.1 || >=0.2.1-rc.1` (tested `0.2.1-rc.
|
|
114
|
+
| 0.1.x | `>=0.1.5-rc.1 || >=0.1.6-alpha.1 || >=0.1.7-alpha.1 || >=0.2.0-rc.1 || >=0.2.1-rc.1` (tested `0.2.1-rc.5`) | `^4.0.1` (tested `4.0.5`) |
|
|
115
115
|
|
|
116
116
|
Peer dependency: `@deepseek-ai/dsh-tools` (the `tools/pre-execute` gate). The approval seam (`@deepseek-ai/dsh-user-approval`) is optional and only involved in `ask` decisions.
|
|
117
117
|
|
package/lib/index.js
CHANGED
|
@@ -73,10 +73,9 @@ export function apply(ctx, config) {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
catch (error) {
|
|
76
|
-
//
|
|
77
|
-
// delegates to the rest of the chain (dsh's own gates keep their say).
|
|
76
|
+
// Keep the agent loop alive without turning a broken policy into an allow.
|
|
78
77
|
console.error('[dsh-policy] evaluation failed:', error);
|
|
79
|
-
return
|
|
78
|
+
return { kind: 'deny', reason: 'policy evaluation failed' };
|
|
80
79
|
}
|
|
81
80
|
});
|
|
82
81
|
}
|
package/lib/rule.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* - No matching rule at all → `undefined` → the plugin delegates to the rest
|
|
18
18
|
* of the `tools/pre-execute` chain (dsh's own gates keep their say).
|
|
19
19
|
*/
|
|
20
|
-
import { matchesPrefix, splitSegments } from './shell.js';
|
|
20
|
+
import { matchesPrefix, normalizeCommandWord, splitSegments } from './shell.js';
|
|
21
21
|
/** Fail-closed tie-break within one priority tier. */
|
|
22
22
|
const SEVERITY = { allow: 0, ask: 1, deny: 2 };
|
|
23
23
|
const prefer = (a, b) => {
|
|
@@ -101,13 +101,13 @@ function argsMatch(rule, args, argsJson) {
|
|
|
101
101
|
return cond.res.some((re) => re.test(text));
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
|
-
function matchesSegment(rule, segment) {
|
|
104
|
+
function matchesSegment(rule, segment, dialect) {
|
|
105
105
|
const hasCommandCondition = rule.commandPrefixes !== undefined || rule.commandRes !== undefined;
|
|
106
106
|
// No command string on this call: command conditions cannot be satisfied.
|
|
107
107
|
if (segment === undefined)
|
|
108
108
|
return !hasCommandCondition;
|
|
109
|
-
return (anyMatch(rule.commandPrefixes, (prefix) => matchesPrefix(segment, prefix)) &&
|
|
110
|
-
anyMatch(rule.commandRes, (re) => re.test(segment)));
|
|
109
|
+
return (anyMatch(rule.commandPrefixes, (prefix) => matchesPrefix(segment, prefix, dialect)) &&
|
|
110
|
+
anyMatch(rule.commandRes, (re) => re.test(normalizeCommandWord(segment, dialect))));
|
|
111
111
|
}
|
|
112
112
|
export function evaluate(rules, toolName, args, commandKeys) {
|
|
113
113
|
const candidates = rules.filter((rule) => rule.tools.includes('*') || rule.tools.includes(toolName));
|
|
@@ -117,7 +117,8 @@ export function evaluate(rules, toolName, args, commandKeys) {
|
|
|
117
117
|
const command = extractCommand(args, commandKeys);
|
|
118
118
|
// A call without a shell command is one virtual whole-call segment — only
|
|
119
119
|
// rules without command conditions can match it.
|
|
120
|
-
const
|
|
120
|
+
const dialect = toolName === 'pwsh' ? 'pwsh' : 'bash';
|
|
121
|
+
const segments = command === undefined ? [undefined] : splitSegments(command, dialect);
|
|
121
122
|
let undecided = false;
|
|
122
123
|
const best = {};
|
|
123
124
|
for (const segment of segments) {
|
|
@@ -125,7 +126,7 @@ export function evaluate(rules, toolName, args, commandKeys) {
|
|
|
125
126
|
for (const rule of candidates) {
|
|
126
127
|
if (!argsMatch(rule, args, argsJson))
|
|
127
128
|
continue;
|
|
128
|
-
if (!matchesSegment(rule, segment))
|
|
129
|
+
if (!matchesSegment(rule, segment, dialect))
|
|
129
130
|
continue;
|
|
130
131
|
winner = winner === undefined ? rule : prefer(winner, rule);
|
|
131
132
|
}
|
package/lib/shell.js
CHANGED
|
@@ -3,18 +3,76 @@
|
|
|
3
3
|
*
|
|
4
4
|
* dsh's bash/pwsh tools receive one raw command string, so a prefix rule that
|
|
5
5
|
* only examined the whole string would miss `cd /tmp && npm install`. The
|
|
6
|
-
* splitter is quote-aware
|
|
7
|
-
* their own segments (recursively)
|
|
8
|
-
*
|
|
9
|
-
* `commandPrefix` / `commandRegex` rules match against
|
|
6
|
+
* splitter is quote-aware, normalizes the executable command word, and pulls
|
|
7
|
+
* command/process substitutions out as their own segments (recursively).
|
|
8
|
+
* Here-doc bodies stay data except for real substitutions in unquoted bodies;
|
|
9
|
+
* `commandPrefix` / `commandRegex` rules match against the resulting segments.
|
|
10
10
|
*/
|
|
11
11
|
const isWordBoundary = (ch) => ch === undefined || /\s/.test(ch);
|
|
12
|
+
/** Remove shell quoting and escapes from the first word, leaving its arguments untouched. */
|
|
13
|
+
export function normalizeCommandWord(segment, dialect = 'bash') {
|
|
14
|
+
let i = 0;
|
|
15
|
+
while (/\s/.test(segment[i] ?? ''))
|
|
16
|
+
i += 1;
|
|
17
|
+
const start = i;
|
|
18
|
+
let word = '';
|
|
19
|
+
let quote;
|
|
20
|
+
while (i < segment.length) {
|
|
21
|
+
const ch = segment[i];
|
|
22
|
+
if (quote === undefined && /\s/.test(ch))
|
|
23
|
+
break;
|
|
24
|
+
if (quote !== undefined) {
|
|
25
|
+
if (ch === quote) {
|
|
26
|
+
quote = undefined;
|
|
27
|
+
i += 1;
|
|
28
|
+
}
|
|
29
|
+
else if (dialect === 'pwsh' && ch === '`' && quote !== "'" && i + 1 < segment.length) {
|
|
30
|
+
word += segment[i + 1];
|
|
31
|
+
i += 2;
|
|
32
|
+
}
|
|
33
|
+
else if (dialect === 'bash' && ch === '\\' && quote !== "'" && i + 1 < segment.length) {
|
|
34
|
+
const next = segment[i + 1];
|
|
35
|
+
if (next === '\n')
|
|
36
|
+
i += 2;
|
|
37
|
+
else {
|
|
38
|
+
if ('$`"\\'.includes(next))
|
|
39
|
+
word += next;
|
|
40
|
+
else
|
|
41
|
+
word += ch + next;
|
|
42
|
+
i += 2;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
word += ch;
|
|
47
|
+
i += 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else if (ch === "'" || ch === '"') {
|
|
51
|
+
quote = ch;
|
|
52
|
+
i += 1;
|
|
53
|
+
}
|
|
54
|
+
else if (dialect === 'pwsh' && ch === '`' && i + 1 < segment.length) {
|
|
55
|
+
word += segment[i + 1];
|
|
56
|
+
i += 2;
|
|
57
|
+
}
|
|
58
|
+
else if (dialect === 'bash' && ch === '\\' && i + 1 < segment.length) {
|
|
59
|
+
if (segment[i + 1] !== '\n')
|
|
60
|
+
word += segment[i + 1];
|
|
61
|
+
i += 2;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
word += ch;
|
|
65
|
+
i += 1;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return segment.slice(0, start) + word + segment.slice(i);
|
|
69
|
+
}
|
|
12
70
|
/**
|
|
13
71
|
* A prefix matches a segment only at a word boundary: `npm` matches
|
|
14
72
|
* `npm install` and `npm` itself, never `npmx` or `npm-cache`.
|
|
15
73
|
*/
|
|
16
|
-
export function matchesPrefix(segment, prefix) {
|
|
17
|
-
const trimmed = segment.trimStart();
|
|
74
|
+
export function matchesPrefix(segment, prefix, dialect = 'bash') {
|
|
75
|
+
const trimmed = normalizeCommandWord(segment, dialect).trimStart();
|
|
18
76
|
return trimmed.startsWith(prefix) && isWordBoundary(trimmed[prefix.length]);
|
|
19
77
|
}
|
|
20
78
|
/** Read a `$(`...`)` body starting just after the opening paren. */
|
|
@@ -45,13 +103,122 @@ function readParenSubshell(text, start) {
|
|
|
45
103
|
}
|
|
46
104
|
return { body: text.slice(start), end: text.length }; // unbalanced: take the rest
|
|
47
105
|
}
|
|
106
|
+
/** Read a simple here-doc, including a shell-chain tail on its declaration line. */
|
|
107
|
+
function readHereDoc(text, start) {
|
|
108
|
+
// Bash here-strings (<<<) are ordinary input, not here-doc declarations.
|
|
109
|
+
// Check both sides because the splitter also offers the second '<' as a start.
|
|
110
|
+
if (text[start + 2] === '<' || (start > 0 && text[start - 1] === '<'))
|
|
111
|
+
return;
|
|
112
|
+
const match = /^<<(-)?[ \t]*(?:'([^']*)'|"([^"]*)"|([^\s]+))/.exec(text.slice(start));
|
|
113
|
+
if (!match)
|
|
114
|
+
return;
|
|
115
|
+
const rawDelimiter = match[0].replace(/^<<-?[ \t]*/, '');
|
|
116
|
+
const delimiter = normalizeCommandWord(rawDelimiter);
|
|
117
|
+
// Any quote/escape in the delimiter suppresses body expansion, including mixed forms.
|
|
118
|
+
const quoted = delimiter !== rawDelimiter;
|
|
119
|
+
let cursor = start + match[0].length;
|
|
120
|
+
while (text[cursor] === ' ' || text[cursor] === '\t')
|
|
121
|
+
cursor += 1;
|
|
122
|
+
const tailStart = cursor;
|
|
123
|
+
const declarationEnd = text.indexOf('\n', cursor);
|
|
124
|
+
if (declarationEnd === -1)
|
|
125
|
+
return;
|
|
126
|
+
const rawTail = text.slice(tailStart, declarationEnd).replace(/\r$/, '');
|
|
127
|
+
const outputRedirect = /^\s*>>?\s+\S+\s*$/.test(rawTail);
|
|
128
|
+
// Keep unsupported declaration tails conservative instead of guessing shell grammar.
|
|
129
|
+
if (rawTail.trim() && !outputRedirect && !/^(?:&&|\|\||[;|&])/.test(rawTail.trimStart()))
|
|
130
|
+
return;
|
|
131
|
+
const declaration = text
|
|
132
|
+
.slice(start, outputRedirect ? declarationEnd : tailStart)
|
|
133
|
+
.replace(/\r$/, '');
|
|
134
|
+
const tail = rawTail;
|
|
135
|
+
const bodyStart = declarationEnd + 1;
|
|
136
|
+
let lineStart = bodyStart;
|
|
137
|
+
for (;;) {
|
|
138
|
+
const newline = text.indexOf('\n', lineStart);
|
|
139
|
+
const lineEnd = newline === -1 ? text.length : newline;
|
|
140
|
+
let line = text.slice(lineStart, lineEnd);
|
|
141
|
+
if (line.endsWith('\r'))
|
|
142
|
+
line = line.slice(0, -1);
|
|
143
|
+
const candidate = match[1] ? line.replace(/^\t+/, '') : line;
|
|
144
|
+
if (candidate === delimiter) {
|
|
145
|
+
return {
|
|
146
|
+
declaration,
|
|
147
|
+
tail,
|
|
148
|
+
outputRedirect,
|
|
149
|
+
body: text.slice(bodyStart, lineStart),
|
|
150
|
+
quoted,
|
|
151
|
+
end: newline === -1 ? text.length : lineEnd + 1,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (newline === -1) {
|
|
155
|
+
return {
|
|
156
|
+
declaration,
|
|
157
|
+
tail,
|
|
158
|
+
outputRedirect,
|
|
159
|
+
body: text.slice(bodyStart),
|
|
160
|
+
quoted,
|
|
161
|
+
end: text.length,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
lineStart = lineEnd + 1;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/** Extract real substitutions; shell command-line tails optionally honor single/double quotes. */
|
|
168
|
+
function hereDocCommands(body, respectQuotes = false) {
|
|
169
|
+
const commands = [];
|
|
170
|
+
let quote;
|
|
171
|
+
let i = 0;
|
|
172
|
+
while (i < body.length) {
|
|
173
|
+
const ch = body[i];
|
|
174
|
+
if (respectQuotes && quote === "'") {
|
|
175
|
+
if (ch === "'")
|
|
176
|
+
quote = undefined;
|
|
177
|
+
i += 1;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (ch === '\\' && i + 1 < body.length) {
|
|
181
|
+
i += 2;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (respectQuotes) {
|
|
185
|
+
if (ch === quote) {
|
|
186
|
+
quote = undefined;
|
|
187
|
+
i += 1;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (quote === undefined && (ch === "'" || ch === '"')) {
|
|
191
|
+
quote = ch;
|
|
192
|
+
i += 1;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (ch === '$' && body[i + 1] === '(') {
|
|
197
|
+
const { body: inner, end } = readParenSubshell(body, i + 2);
|
|
198
|
+
commands.push(...splitSegments(inner));
|
|
199
|
+
i = end;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (ch === '`') {
|
|
203
|
+
let close = i + 1;
|
|
204
|
+
while (close < body.length && body[close] !== '`') {
|
|
205
|
+
close += body[close] === '\\' ? 2 : 1;
|
|
206
|
+
}
|
|
207
|
+
commands.push(...splitSegments(body.slice(i + 1, close)));
|
|
208
|
+
i = close < body.length ? close + 1 : body.length;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
i += 1;
|
|
212
|
+
}
|
|
213
|
+
return commands;
|
|
214
|
+
}
|
|
48
215
|
/**
|
|
49
216
|
* Split a compound command into the segments a shell would run: pipelines,
|
|
50
217
|
* `&&`/`||`/`;`/newline chains, background `&`, plus the interiors of
|
|
51
218
|
* `$( )` and backtick substitutions (which execute even inside double
|
|
52
219
|
* quotes). Quoted operator characters stay literal.
|
|
53
220
|
*/
|
|
54
|
-
export function splitSegments(command) {
|
|
221
|
+
export function splitSegments(command, dialect = 'bash') {
|
|
55
222
|
const segments = [];
|
|
56
223
|
let buf = '';
|
|
57
224
|
let quote;
|
|
@@ -64,25 +231,54 @@ export function splitSegments(command) {
|
|
|
64
231
|
let i = 0;
|
|
65
232
|
while (i < command.length) {
|
|
66
233
|
const ch = command[i];
|
|
234
|
+
if (dialect === 'pwsh' && ch === '`' && quote !== "'" && i + 1 < command.length) {
|
|
235
|
+
buf += ch + command[i + 1];
|
|
236
|
+
i += 2;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (dialect === 'bash' && quote === undefined && ch === '<' && command[i + 1] === '<') {
|
|
240
|
+
const doc = readHereDoc(command, i);
|
|
241
|
+
if (doc) {
|
|
242
|
+
buf += doc.declaration;
|
|
243
|
+
push();
|
|
244
|
+
if (!doc.quoted)
|
|
245
|
+
segments.push(...hereDocCommands(doc.body));
|
|
246
|
+
segments.push(...(doc.outputRedirect
|
|
247
|
+
? hereDocCommands(doc.tail, true)
|
|
248
|
+
: splitSegments(doc.tail, dialect)));
|
|
249
|
+
i = doc.end;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Process substitution is unquoted Bash syntax; its interior executes too.
|
|
254
|
+
if (dialect === 'bash' &&
|
|
255
|
+
quote === undefined &&
|
|
256
|
+
(ch === '<' || ch === '>') &&
|
|
257
|
+
command[i + 1] === '(') {
|
|
258
|
+
const { body, end } = readParenSubshell(command, i + 2);
|
|
259
|
+
segments.push(...splitSegments(body, dialect));
|
|
260
|
+
i = end;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
67
263
|
// Substitution contents execute inside double quotes but are literal in
|
|
68
264
|
// single quotes — extract them from everywhere except '...'.
|
|
69
265
|
if (quote !== "'") {
|
|
70
266
|
if (ch === '$' && command[i + 1] === '(') {
|
|
71
267
|
const { body, end } = readParenSubshell(command, i + 2);
|
|
72
|
-
segments.push(...splitSegments(body));
|
|
268
|
+
segments.push(...splitSegments(body, dialect));
|
|
73
269
|
i = end;
|
|
74
270
|
continue;
|
|
75
271
|
}
|
|
76
|
-
if (ch === '`') {
|
|
272
|
+
if (dialect === 'bash' && ch === '`') {
|
|
77
273
|
const close = command.indexOf('`', i + 1);
|
|
78
|
-
segments.push(...splitSegments(close === -1 ? command.slice(i + 1) : command.slice(i + 1, close)));
|
|
274
|
+
segments.push(...splitSegments(close === -1 ? command.slice(i + 1) : command.slice(i + 1, close), dialect));
|
|
79
275
|
i = close === -1 ? command.length : close + 1;
|
|
80
276
|
continue;
|
|
81
277
|
}
|
|
82
278
|
}
|
|
83
279
|
if (quote !== undefined) {
|
|
84
280
|
buf += ch;
|
|
85
|
-
if (ch === '\\' && quote !== "'" && i + 1 < command.length) {
|
|
281
|
+
if (dialect === 'bash' && ch === '\\' && quote !== "'" && i + 1 < command.length) {
|
|
86
282
|
buf += command[i + 1];
|
|
87
283
|
i += 2;
|
|
88
284
|
continue;
|
|
@@ -98,7 +294,7 @@ export function splitSegments(command) {
|
|
|
98
294
|
i += 1;
|
|
99
295
|
continue;
|
|
100
296
|
}
|
|
101
|
-
if (ch === '\\' && i + 1 < command.length) {
|
|
297
|
+
if (dialect === 'bash' && ch === '\\' && i + 1 < command.length) {
|
|
102
298
|
buf += ch + command[i + 1];
|
|
103
299
|
i += 2;
|
|
104
300
|
continue;
|
package/lib/types/shell.d.ts
CHANGED
|
@@ -3,20 +3,23 @@
|
|
|
3
3
|
*
|
|
4
4
|
* dsh's bash/pwsh tools receive one raw command string, so a prefix rule that
|
|
5
5
|
* only examined the whole string would miss `cd /tmp && npm install`. The
|
|
6
|
-
* splitter is quote-aware
|
|
7
|
-
* their own segments (recursively)
|
|
8
|
-
*
|
|
9
|
-
* `commandPrefix` / `commandRegex` rules match against
|
|
6
|
+
* splitter is quote-aware, normalizes the executable command word, and pulls
|
|
7
|
+
* command/process substitutions out as their own segments (recursively).
|
|
8
|
+
* Here-doc bodies stay data except for real substitutions in unquoted bodies;
|
|
9
|
+
* `commandPrefix` / `commandRegex` rules match against the resulting segments.
|
|
10
10
|
*/
|
|
11
|
+
export type ShellDialect = 'bash' | 'pwsh';
|
|
12
|
+
/** Remove shell quoting and escapes from the first word, leaving its arguments untouched. */
|
|
13
|
+
export declare function normalizeCommandWord(segment: string, dialect?: ShellDialect): string;
|
|
11
14
|
/**
|
|
12
15
|
* A prefix matches a segment only at a word boundary: `npm` matches
|
|
13
16
|
* `npm install` and `npm` itself, never `npmx` or `npm-cache`.
|
|
14
17
|
*/
|
|
15
|
-
export declare function matchesPrefix(segment: string, prefix: string): boolean;
|
|
18
|
+
export declare function matchesPrefix(segment: string, prefix: string, dialect?: ShellDialect): boolean;
|
|
16
19
|
/**
|
|
17
20
|
* Split a compound command into the segments a shell would run: pipelines,
|
|
18
21
|
* `&&`/`||`/`;`/newline chains, background `&`, plus the interiors of
|
|
19
22
|
* `$( )` and backtick substitutions (which execute even inside double
|
|
20
23
|
* quotes). Quoted operator characters stay literal.
|
|
21
24
|
*/
|
|
22
|
-
export declare function splitSegments(command: string): string[];
|
|
25
|
+
export declare function splitSegments(command: string, dialect?: ShellDialect): string[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amaster.ai/dsh-policy",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.35",
|
|
4
4
|
"description": "Declarative tool-call policy for DeepSeek Harness: config-driven allow/deny/ask rules (tool name, args pattern, shell command prefix/regex, priority) on the tools/pre-execute gate — Gemini CLI-style policy files expressed as plain plugin config.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
35
|
+
"@deepseek-ai/schemastery": "^3.18.5"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
@@ -40,23 +40,23 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@deepseek-ai/cordis": "4.0.5",
|
|
43
|
-
"@deepseek-ai/dsh-agent": "0.2.1-rc.
|
|
44
|
-
"@deepseek-ai/dsh-home-paths": "0.2.1-rc.
|
|
45
|
-
"@deepseek-ai/dsh-http-proxy": "0.2.1-rc.
|
|
46
|
-
"@deepseek-ai/dsh-jobs": "0.2.1-rc.
|
|
47
|
-
"@deepseek-ai/dsh-llm": "0.2.1-rc.
|
|
48
|
-
"@deepseek-ai/dsh-ptc-runtime": "0.2.1-rc.
|
|
49
|
-
"@deepseek-ai/dsh-sandbox": "0.2.1-rc.
|
|
50
|
-
"@deepseek-ai/dsh-sandbox-policy": "0.2.1-rc.
|
|
51
|
-
"@deepseek-ai/dsh-scope": "0.2.1-rc.
|
|
52
|
-
"@deepseek-ai/dsh-session-projection": "0.2.1-rc.
|
|
53
|
-
"@deepseek-ai/dsh-shell": "0.2.1-rc.
|
|
54
|
-
"@deepseek-ai/dsh-shell-env": "0.2.1-rc.
|
|
55
|
-
"@deepseek-ai/dsh-subprocess": "0.2.1-rc.
|
|
56
|
-
"@deepseek-ai/dsh-system-prompt": "0.2.1-rc.
|
|
57
|
-
"@deepseek-ai/dsh-tool-bash": "0.2.1-rc.
|
|
58
|
-
"@deepseek-ai/dsh-tools": "0.2.1-rc.
|
|
59
|
-
"@deepseek-ai/dsh-user-approval": "0.2.1-rc.
|
|
43
|
+
"@deepseek-ai/dsh-agent": "0.2.1-rc.5",
|
|
44
|
+
"@deepseek-ai/dsh-home-paths": "0.2.1-rc.5",
|
|
45
|
+
"@deepseek-ai/dsh-http-proxy": "0.2.1-rc.5",
|
|
46
|
+
"@deepseek-ai/dsh-jobs": "0.2.1-rc.5",
|
|
47
|
+
"@deepseek-ai/dsh-llm": "0.2.1-rc.5",
|
|
48
|
+
"@deepseek-ai/dsh-ptc-runtime": "0.2.1-rc.5",
|
|
49
|
+
"@deepseek-ai/dsh-sandbox": "0.2.1-rc.5",
|
|
50
|
+
"@deepseek-ai/dsh-sandbox-policy": "0.2.1-rc.5",
|
|
51
|
+
"@deepseek-ai/dsh-scope": "0.2.1-rc.5",
|
|
52
|
+
"@deepseek-ai/dsh-session-projection": "0.2.1-rc.5",
|
|
53
|
+
"@deepseek-ai/dsh-shell": "0.2.1-rc.5",
|
|
54
|
+
"@deepseek-ai/dsh-shell-env": "0.2.1-rc.5",
|
|
55
|
+
"@deepseek-ai/dsh-subprocess": "0.2.1-rc.5",
|
|
56
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.1-rc.5",
|
|
57
|
+
"@deepseek-ai/dsh-tool-bash": "0.2.1-rc.5",
|
|
58
|
+
"@deepseek-ai/dsh-tools": "0.2.1-rc.5",
|
|
59
|
+
"@deepseek-ai/dsh-user-approval": "0.2.1-rc.5",
|
|
60
60
|
"@types/node": "^22.0.0",
|
|
61
61
|
"typescript": "^5.6.3",
|
|
62
62
|
"vitest": "^4.1.11"
|