crow-central-agency 0.26.12 → 0.26.14
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/dist/_shared/auto-approve/auto-approve-rule-set.js +30 -0
- package/dist/_shared/auto-approve/auto-approve-rule.types.js +2 -0
- package/dist/_shared/auto-approve/command-decomposition.js +238 -0
- package/dist/_shared/auto-approve/rule-format.js +79 -0
- package/dist/_shared/auto-approve/rule-strategies.js +72 -0
- package/dist/_shared/auto-approve/rule-strategy-registry.js +7 -0
- package/dist/_shared/index.js +6 -1
- package/dist/_shared/schemas/artifact.schema.js +24 -3
- package/dist/bootstrap.js +2 -0
- package/dist/feed/feed-search-index.js +211 -0
- package/dist/feed/feed-search-index.types.js +2 -0
- package/dist/feed/simply-feed-manager.js +92 -58
- package/dist/mcp/agents/invoke-agent.js +10 -7
- package/dist/mcp/gmail/create-gmail-draft.js +9 -3
- package/dist/mcp/gmail/reply-to-gmail-message.js +8 -5
- package/dist/mcp/gmail/send-gmail-message.js +8 -5
- package/dist/mcp/gmail/update-gmail-draft.js +10 -1
- package/dist/mcp/tasks/get-task-result.js +4 -3
- package/dist/mcp/tasks/get-task.js +1 -1
- package/dist/mcp/tasks/tasks-mcp-server-utils.js +25 -26
- package/dist/mcp/tasks/tasks-mcp-server.js +1 -1
- package/dist/public/assets/{architectureDiagram-3BPJPVTR-CRUuaJq5.js → architectureDiagram-3BPJPVTR-9PO2Zi5j.js} +1 -1
- package/dist/public/assets/{chunk-727SXJPM-3bCxa2Zq.js → chunk-727SXJPM-BmaCn-6B.js} +1 -1
- package/dist/public/assets/{chunk-AQP2D5EJ-Dom7ZjX5.js → chunk-AQP2D5EJ-Cca-zKhj.js} +1 -1
- package/dist/public/assets/{classDiagram-v2-Q7XG4LA2-BX_yaOCo.js → classDiagram-4FO5ZUOK-B_zdrMg0.js} +1 -1
- package/dist/public/assets/{classDiagram-4FO5ZUOK-BX_yaOCo.js → classDiagram-v2-Q7XG4LA2-B_zdrMg0.js} +1 -1
- package/dist/public/assets/{dagre-BM42HDAG-KKbmsGYT.js → dagre-BM42HDAG-CRiqaSzz.js} +1 -1
- package/dist/public/assets/{diagram-2AECGRRQ-BY_4cf4J.js → diagram-2AECGRRQ-BRpLjqbq.js} +1 -1
- package/dist/public/assets/{diagram-5GNKFQAL-CS0SCo68.js → diagram-5GNKFQAL-Bc9v-jIT.js} +1 -1
- package/dist/public/assets/{diagram-LMA3HP47-DWPp9bbA.js → diagram-LMA3HP47-BML6fr2x.js} +1 -1
- package/dist/public/assets/{diagram-OG6HWLK6-BLYKu1nE.js → diagram-OG6HWLK6-Czbcayu-.js} +1 -1
- package/dist/public/assets/{erDiagram-TEJ5UH35--mqF2IH-.js → erDiagram-TEJ5UH35-DWxHYHOI.js} +1 -1
- package/dist/public/assets/{flowDiagram-I6XJVG4X-BplDzcPA.js → flowDiagram-I6XJVG4X-ZS40gCP9.js} +1 -1
- package/dist/public/assets/{index-C0F6g385.js → index-Bf-eiYs6.js} +69 -68
- package/dist/public/assets/index-DOeMoLVc.css +1 -0
- package/dist/public/assets/{infoDiagram-5YYISTIA-77oXRmwj.js → infoDiagram-5YYISTIA-cREQjSEN.js} +1 -1
- package/dist/public/assets/{ishikawaDiagram-YF4QCWOH-DVFwKp8q.js → ishikawaDiagram-YF4QCWOH-BGHPisrV.js} +1 -1
- package/dist/public/assets/{kanban-definition-UN3LZRKU-DY8-KN2p.js → kanban-definition-UN3LZRKU-C-2qMHn6.js} +1 -1
- package/dist/public/assets/{mindmap-definition-RKZ34NQL-DCtZO5l6.js → mindmap-definition-RKZ34NQL-gLns4iF7.js} +1 -1
- package/dist/public/assets/{pieDiagram-4H26LBE5-B4uOAUWj.js → pieDiagram-4H26LBE5-Da06yraI.js} +1 -1
- package/dist/public/assets/{requirementDiagram-4Y6WPE33-DYN368AR.js → requirementDiagram-4Y6WPE33-BsSvQc8s.js} +1 -1
- package/dist/public/assets/{sequenceDiagram-3UESZ5HK-BbfY7AeW.js → sequenceDiagram-3UESZ5HK-CxdEFAWz.js} +1 -1
- package/dist/public/assets/{stateDiagram-AJRCARHV-B_BzBqT7.js → stateDiagram-AJRCARHV-itM-qY8o.js} +1 -1
- package/dist/public/assets/{stateDiagram-v2-BHNVJYJU-DZ4qVD8V.js → stateDiagram-v2-BHNVJYJU-CSRZkocn.js} +1 -1
- package/dist/public/assets/{timeline-definition-PNZ67QCA-Ba8WPvkl.js → timeline-definition-PNZ67QCA-BUxAMoDy.js} +1 -1
- package/dist/public/assets/{vennDiagram-CIIHVFJN-wcxj086h.js → vennDiagram-CIIHVFJN-CtO4Fn4j.js} +1 -1
- package/dist/public/assets/{wardleyDiagram-YWT4CUSO-CfpyXdPF.js → wardleyDiagram-YWT4CUSO-BewTtRo4.js} +1 -1
- package/dist/public/assets/{xychartDiagram-2RQKCTM6-EgqIcFwJ.js → xychartDiagram-2RQKCTM6-DjHvRmZM.js} +1 -1
- package/dist/public/index.html +2 -2
- package/dist/routes/artifact.routes.js +24 -19
- package/dist/routines/inter-agent-task-routine.js +8 -11
- package/dist/runner/claude-code-agent-runner.js +18 -10
- package/dist/runner/github-copilot-agent-runner.js +21 -23
- package/dist/services/agent-registry.js +15 -5
- package/dist/services/artifact/artifact-manager.js +3 -0
- package/dist/services/google/google-client.js +15 -10
- package/dist/services/google/google-client.types.js +4 -0
- package/dist/services/google/resolve-email-body-parts.js +15 -0
- package/dist/services/runtime/agent-runtime-manager.js +2 -2
- package/package.json +5 -2
- package/dist/public/assets/index-DwcUOyrO.css +0 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { parseRule } from "./rule-format.js";
|
|
2
|
+
import { getRuleStrategy } from "./rule-strategy-registry.js";
|
|
3
|
+
/**
|
|
4
|
+
* A mutable set of auto-approve rule strings that parses each rule once and caches the result, so
|
|
5
|
+
* permission checks match against the pre-parsed rules instead of re-parsing the whole list per call.
|
|
6
|
+
* Raw strings are deduped; malformed rules are dropped on add (same as `parseRules`).
|
|
7
|
+
*/
|
|
8
|
+
export class AutoApproveRuleSet {
|
|
9
|
+
raw = new Set();
|
|
10
|
+
parsed = [];
|
|
11
|
+
constructor(initialRules = []) {
|
|
12
|
+
this.add(initialRules);
|
|
13
|
+
}
|
|
14
|
+
add(rules) {
|
|
15
|
+
for (const rule of rules) {
|
|
16
|
+
if (this.raw.has(rule)) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
this.raw.add(rule);
|
|
20
|
+
const parsedRule = parseRule(rule);
|
|
21
|
+
if (parsedRule !== undefined) {
|
|
22
|
+
this.parsed.push(parsedRule);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
matches(toolName, input) {
|
|
27
|
+
return getRuleStrategy(toolName).matches(toolName, input, this.parsed);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=auto-approve-rule-set.js.map
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { matchesSpecifier, WORD_BOUNDARY_SUFFIX } from "./rule-format.js";
|
|
2
|
+
/** Prefix depth for derived rules (first N tokens of a subcommand). */
|
|
3
|
+
export const DEFAULT_PREFIX_DEPTH = 2;
|
|
4
|
+
/** Maximum number of rules derived from a single compound command, mirroring the SDK cap. */
|
|
5
|
+
export const MAX_DERIVED_RULES = 5;
|
|
6
|
+
/**
|
|
7
|
+
* Shell separators that decompose a compound command. Two-character separators are matched before
|
|
8
|
+
* the single-character ones so `&&`/`||`/`|&` win over `&`/`|`.
|
|
9
|
+
*/
|
|
10
|
+
export const TWO_CHAR_SEPARATORS = ["&&", "||", "|&"];
|
|
11
|
+
export const SINGLE_CHAR_SEPARATORS = [";", "|", "&", "\n", "\r"];
|
|
12
|
+
/**
|
|
13
|
+
* Process wrappers stripped from the front of a subcommand before matching. `timeout`/`nice` also
|
|
14
|
+
* consume a leading numeric argument (duration / priority).
|
|
15
|
+
*/
|
|
16
|
+
export const PROCESS_WRAPPERS = new Set(["timeout", "time", "nice", "nohup", "stdbuf"]);
|
|
17
|
+
const WRAPPERS_WITH_NUMERIC_ARG = new Set(["timeout", "nice"]);
|
|
18
|
+
const XARGS_WRAPPER = "xargs";
|
|
19
|
+
/**
|
|
20
|
+
* Read-only shell builtins that run with no prompt. `cd` and `git` are handled separately
|
|
21
|
+
* (in-cwd `cd` only; read-only `git` subcommands only).
|
|
22
|
+
*/
|
|
23
|
+
export const READ_ONLY_BUILTINS = new Set([
|
|
24
|
+
"ls",
|
|
25
|
+
"cat",
|
|
26
|
+
"echo",
|
|
27
|
+
"pwd",
|
|
28
|
+
"head",
|
|
29
|
+
"tail",
|
|
30
|
+
"grep",
|
|
31
|
+
"find",
|
|
32
|
+
"wc",
|
|
33
|
+
"which",
|
|
34
|
+
"diff",
|
|
35
|
+
"stat",
|
|
36
|
+
"du",
|
|
37
|
+
]);
|
|
38
|
+
const CD_COMMAND = "cd";
|
|
39
|
+
const IN_CWD_CD_ARGS = new Set([".", "./"]);
|
|
40
|
+
const GIT_COMMAND = "git";
|
|
41
|
+
const READ_ONLY_GIT_SUBCOMMANDS = new Set(["status", "log", "diff", "show"]);
|
|
42
|
+
const SINGLE_QUOTE = "'";
|
|
43
|
+
const DOUBLE_QUOTE = '"';
|
|
44
|
+
const REDIRECT_CHAR = ">";
|
|
45
|
+
/** Split a command string into whitespace-delimited tokens, respecting single/double quotes. */
|
|
46
|
+
function tokenize(command) {
|
|
47
|
+
const tokens = [];
|
|
48
|
+
let current = "";
|
|
49
|
+
let hasToken = false;
|
|
50
|
+
let quote;
|
|
51
|
+
for (const char of command) {
|
|
52
|
+
if (quote !== undefined) {
|
|
53
|
+
if (char === quote) {
|
|
54
|
+
quote = undefined;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
current += char;
|
|
58
|
+
}
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (char === SINGLE_QUOTE || char === DOUBLE_QUOTE) {
|
|
62
|
+
quote = char;
|
|
63
|
+
hasToken = true;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (char === " " || char === "\t") {
|
|
67
|
+
if (hasToken) {
|
|
68
|
+
tokens.push(current);
|
|
69
|
+
current = "";
|
|
70
|
+
hasToken = false;
|
|
71
|
+
}
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
current += char;
|
|
75
|
+
hasToken = true;
|
|
76
|
+
}
|
|
77
|
+
if (hasToken) {
|
|
78
|
+
tokens.push(current);
|
|
79
|
+
}
|
|
80
|
+
return tokens;
|
|
81
|
+
}
|
|
82
|
+
/** Return the shell separator starting at `index`, or `undefined` if none. */
|
|
83
|
+
function separatorAt(command, index) {
|
|
84
|
+
const twoChar = command.slice(index, index + 2);
|
|
85
|
+
for (const separator of TWO_CHAR_SEPARATORS) {
|
|
86
|
+
if (twoChar === separator) {
|
|
87
|
+
return separator;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const char = command[index];
|
|
91
|
+
if (char === "&") {
|
|
92
|
+
// Guard shell redirects (`2>&1`, `&>file`) from being read as a background separator.
|
|
93
|
+
if (command[index - 1] === REDIRECT_CHAR || command[index + 1] === REDIRECT_CHAR) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
for (const separator of SINGLE_CHAR_SEPARATORS) {
|
|
98
|
+
if (char === separator) {
|
|
99
|
+
return separator;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
/** Strip leading process wrappers (and their option/numeric args) from a token list. */
|
|
105
|
+
function stripWrappers(tokens) {
|
|
106
|
+
let remaining = tokens;
|
|
107
|
+
while (remaining.length > 0) {
|
|
108
|
+
const head = remaining[0];
|
|
109
|
+
if (head === XARGS_WRAPPER) {
|
|
110
|
+
// Only bare `xargs` (no flags) is transparent; flagged xargs stays and simply won't match.
|
|
111
|
+
const next = remaining[1];
|
|
112
|
+
if (next !== undefined && next.startsWith("-")) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
remaining = remaining.slice(1);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!PROCESS_WRAPPERS.has(head)) {
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
remaining = remaining.slice(1);
|
|
122
|
+
while (remaining.length > 0 && remaining[0].startsWith("-")) {
|
|
123
|
+
remaining = remaining.slice(1);
|
|
124
|
+
}
|
|
125
|
+
if (WRAPPERS_WITH_NUMERIC_ARG.has(head) && remaining.length > 0 && /^[0-9]/.test(remaining[0])) {
|
|
126
|
+
remaining = remaining.slice(1);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return remaining;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Split a compound command into its normalized subcommands. Splits on shell separators outside
|
|
133
|
+
* quotes, strips process wrappers, and drops empty segments. The returned strings are re-joined
|
|
134
|
+
* from parsed tokens (quotes removed, whitespace collapsed) for stable matching.
|
|
135
|
+
*/
|
|
136
|
+
export function splitSubcommands(command) {
|
|
137
|
+
const rawSegments = [];
|
|
138
|
+
let current = "";
|
|
139
|
+
let quote;
|
|
140
|
+
let index = 0;
|
|
141
|
+
while (index < command.length) {
|
|
142
|
+
const char = command[index];
|
|
143
|
+
if (quote !== undefined) {
|
|
144
|
+
current += char;
|
|
145
|
+
if (char === quote) {
|
|
146
|
+
quote = undefined;
|
|
147
|
+
}
|
|
148
|
+
index += 1;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (char === SINGLE_QUOTE || char === DOUBLE_QUOTE) {
|
|
152
|
+
quote = char;
|
|
153
|
+
current += char;
|
|
154
|
+
index += 1;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const separator = separatorAt(command, index);
|
|
158
|
+
if (separator !== undefined) {
|
|
159
|
+
rawSegments.push(current);
|
|
160
|
+
current = "";
|
|
161
|
+
index += separator.length;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
current += char;
|
|
165
|
+
index += 1;
|
|
166
|
+
}
|
|
167
|
+
rawSegments.push(current);
|
|
168
|
+
const subcommands = [];
|
|
169
|
+
for (const segment of rawSegments) {
|
|
170
|
+
const tokens = stripWrappers(tokenize(segment));
|
|
171
|
+
if (tokens.length > 0) {
|
|
172
|
+
subcommands.push(tokens.join(" "));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return subcommands;
|
|
176
|
+
}
|
|
177
|
+
/** Whether a normalized subcommand is a read-only builtin, in-cwd `cd`, or read-only `git`. */
|
|
178
|
+
export function isReadOnlyCommand(subcommand) {
|
|
179
|
+
const tokens = tokenize(subcommand);
|
|
180
|
+
const head = tokens[0];
|
|
181
|
+
if (head === undefined) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
if (head === CD_COMMAND) {
|
|
185
|
+
const target = tokens[1];
|
|
186
|
+
return target === undefined || IN_CWD_CD_ARGS.has(target);
|
|
187
|
+
}
|
|
188
|
+
if (head === GIT_COMMAND) {
|
|
189
|
+
const gitSubcommand = tokens[1];
|
|
190
|
+
return gitSubcommand !== undefined && READ_ONLY_GIT_SUBCOMMANDS.has(gitSubcommand);
|
|
191
|
+
}
|
|
192
|
+
return READ_ONLY_BUILTINS.has(head);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Capture side: derive one prefix-`depth` specifier per non-read-only subcommand (in-cwd `cd` and
|
|
196
|
+
* read-only builtins contribute nothing), deduped and capped at {@link MAX_DERIVED_RULES}.
|
|
197
|
+
*/
|
|
198
|
+
export function deriveRules(command, depth = DEFAULT_PREFIX_DEPTH) {
|
|
199
|
+
const specifiers = [];
|
|
200
|
+
for (const subcommand of splitSubcommands(command)) {
|
|
201
|
+
if (specifiers.length >= MAX_DERIVED_RULES) {
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
if (isReadOnlyCommand(subcommand)) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const tokens = tokenize(subcommand);
|
|
208
|
+
if (tokens.length === 0) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const prefix = tokens.slice(0, depth).join(" ");
|
|
212
|
+
const specifier = `${prefix}${WORD_BOUNDARY_SUFFIX}`;
|
|
213
|
+
if (!specifiers.includes(specifier)) {
|
|
214
|
+
specifiers.push(specifier);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return specifiers;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Match side: fails closed. Every subcommand must be a read-only builtin or match one of the
|
|
221
|
+
* specifiers. Empty/unparseable input yields `false`.
|
|
222
|
+
*/
|
|
223
|
+
export function matchesRules(command, specifiers) {
|
|
224
|
+
const subcommands = splitSubcommands(command);
|
|
225
|
+
if (subcommands.length === 0) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
for (const subcommand of subcommands) {
|
|
229
|
+
if (isReadOnlyCommand(subcommand)) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (!specifiers.some((specifier) => matchesSpecifier(subcommand, specifier))) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=command-decomposition.js.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Glob wildcard used in rule specifiers. */
|
|
2
|
+
export const GLOB_STAR = "*";
|
|
3
|
+
/**
|
|
4
|
+
* A space before a trailing `*` enforces a word boundary: `ls *` matches `ls` and `ls -la`,
|
|
5
|
+
* but never `lsof`.
|
|
6
|
+
*/
|
|
7
|
+
export const WORD_BOUNDARY_SUFFIX = " *";
|
|
8
|
+
/** Claude's `:*` prefix suffix, treated as equivalent to a trailing ` *`. */
|
|
9
|
+
export const PREFIX_STAR_SUFFIX = ":*";
|
|
10
|
+
const RULE_PATTERN = /^([^()]+)(?:\((.*)\))?$/;
|
|
11
|
+
const REGEXP_SPECIAL_CHARS = /[.+?^${}()|[\]\\]/g;
|
|
12
|
+
/**
|
|
13
|
+
* Parse a rule string into its tool + optional specifier. Fails closed: any malformed rule
|
|
14
|
+
* (unbalanced parentheses, empty tool, stray `(` in the tool name) yields `undefined`.
|
|
15
|
+
*/
|
|
16
|
+
export function parseRule(rule) {
|
|
17
|
+
const trimmed = rule.trim();
|
|
18
|
+
if (trimmed.length === 0) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
const match = RULE_PATTERN.exec(trimmed);
|
|
22
|
+
if (!match) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
const tool = match[1];
|
|
26
|
+
const specifier = match[2]?.trim();
|
|
27
|
+
if (tool.length === 0) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
return specifier === undefined ? { tool } : { tool, specifier };
|
|
31
|
+
}
|
|
32
|
+
/** Parse a list of rule strings, discarding any that fail to parse. */
|
|
33
|
+
export function parseRules(rules) {
|
|
34
|
+
const parsed = [];
|
|
35
|
+
for (const rule of rules) {
|
|
36
|
+
const result = parseRule(rule);
|
|
37
|
+
if (result !== undefined) {
|
|
38
|
+
parsed.push(result);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return parsed;
|
|
42
|
+
}
|
|
43
|
+
/** Format a parsed rule back into its canonical string form. */
|
|
44
|
+
export function formatRule(rule) {
|
|
45
|
+
return rule.specifier === undefined ? rule.tool : `${rule.tool}(${rule.specifier})`;
|
|
46
|
+
}
|
|
47
|
+
/** Normalize Claude's `:*` prefix suffix into the equivalent word-boundary ` *` form. */
|
|
48
|
+
function normalizeSpecifier(specifier) {
|
|
49
|
+
if (specifier.endsWith(PREFIX_STAR_SUFFIX)) {
|
|
50
|
+
return `${specifier.slice(0, -PREFIX_STAR_SUFFIX.length)}${WORD_BOUNDARY_SUFFIX}`;
|
|
51
|
+
}
|
|
52
|
+
return specifier;
|
|
53
|
+
}
|
|
54
|
+
function globToRegExp(pattern) {
|
|
55
|
+
const source = pattern
|
|
56
|
+
.split(GLOB_STAR)
|
|
57
|
+
.map((segment) => segment.replace(REGEXP_SPECIAL_CHARS, "\\$&"))
|
|
58
|
+
.join(".*");
|
|
59
|
+
return new RegExp(`^${source}$`);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Whether a single command/value matches a rule specifier under glob semantics. A specifier
|
|
63
|
+
* ending in a word-boundary ` *` (or `:*`) matches the exact prefix or the prefix followed by a
|
|
64
|
+
* space; any other `*` is a general wildcard. No wildcard means an exact match.
|
|
65
|
+
*/
|
|
66
|
+
export function matchesSpecifier(value, specifier) {
|
|
67
|
+
const normalized = normalizeSpecifier(specifier);
|
|
68
|
+
if (normalized.endsWith(WORD_BOUNDARY_SUFFIX)) {
|
|
69
|
+
const base = normalized.slice(0, -WORD_BOUNDARY_SUFFIX.length);
|
|
70
|
+
if (!base.includes(GLOB_STAR)) {
|
|
71
|
+
return value === base || value.startsWith(`${base} `);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (normalized.includes(GLOB_STAR)) {
|
|
75
|
+
return globToRegExp(normalized).test(value);
|
|
76
|
+
}
|
|
77
|
+
return value === normalized;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=rule-format.js.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { CLAUDE_CODE_TOOL } from "../schemas/agent.schema.js";
|
|
2
|
+
import { deriveRules as deriveCommandRules, matchesRules as matchesCommandRules } from "./command-decomposition.js";
|
|
3
|
+
import { formatRule, GLOB_STAR } from "./rule-format.js";
|
|
4
|
+
/** Input field carrying the shell command for Bash/PowerShell tools. */
|
|
5
|
+
const COMMAND_INPUT_KEY = "command";
|
|
6
|
+
/**
|
|
7
|
+
* Command-scoped tools, compared case-insensitively to cover Claude (`Bash`, `PowerShell`) and
|
|
8
|
+
* GitHub Copilot (`bash`, `powershell`).
|
|
9
|
+
*/
|
|
10
|
+
const COMMAND_TOOL_BASE_NAMES = new Set([
|
|
11
|
+
CLAUDE_CODE_TOOL.BASH.toLowerCase(),
|
|
12
|
+
CLAUDE_CODE_TOOL.POWER_SHELL.toLowerCase(),
|
|
13
|
+
]);
|
|
14
|
+
/**
|
|
15
|
+
* Whole-tool matching, preserving the legacy `isToolAutoApproved` behavior: exact tool-name match,
|
|
16
|
+
* plus a trailing `*` (including a bare `*` and MCP prefixes like `mcp__crow-artifacts__*`) as a
|
|
17
|
+
* prefix match.
|
|
18
|
+
*/
|
|
19
|
+
function wholeToolMatches(toolName, rules) {
|
|
20
|
+
for (const rule of rules) {
|
|
21
|
+
if (rule.specifier !== undefined) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const pattern = rule.tool;
|
|
25
|
+
if (pattern === toolName) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
if (pattern.endsWith(GLOB_STAR) && toolName.startsWith(pattern.slice(0, -GLOB_STAR.length))) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
function extractCommand(input) {
|
|
35
|
+
const command = input[COMMAND_INPUT_KEY];
|
|
36
|
+
return typeof command === "string" ? command : undefined;
|
|
37
|
+
}
|
|
38
|
+
/** Default strategy: today's whole-tool behavior, used for any tool without a specialization. */
|
|
39
|
+
export const defaultRuleStrategy = {
|
|
40
|
+
appliesTo: () => true,
|
|
41
|
+
deriveRules: (toolName) => [toolName],
|
|
42
|
+
matches: (toolName, _input, rules) => wholeToolMatches(toolName, rules),
|
|
43
|
+
};
|
|
44
|
+
/** Command strategy: Bash/PowerShell decomposition on top of the whole-tool behavior. */
|
|
45
|
+
export const commandRuleStrategy = {
|
|
46
|
+
appliesTo: (toolName) => COMMAND_TOOL_BASE_NAMES.has(toolName.toLowerCase()),
|
|
47
|
+
deriveRules: (toolName, input) => {
|
|
48
|
+
const command = extractCommand(input);
|
|
49
|
+
if (command === undefined) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
return deriveCommandRules(command).map((specifier) => formatRule({ tool: toolName, specifier }));
|
|
53
|
+
},
|
|
54
|
+
matches: (toolName, input, rules) => {
|
|
55
|
+
if (wholeToolMatches(toolName, rules)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
const command = extractCommand(input);
|
|
59
|
+
if (command === undefined) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const targetTool = toolName.toLowerCase();
|
|
63
|
+
const specifiers = [];
|
|
64
|
+
for (const rule of rules) {
|
|
65
|
+
if (rule.specifier !== undefined && rule.tool.toLowerCase() === targetTool) {
|
|
66
|
+
specifiers.push(rule.specifier);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return matchesCommandRules(command, specifiers);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=rule-strategies.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { commandRuleStrategy, defaultRuleStrategy } from "./rule-strategies.js";
|
|
2
|
+
const REGISTERED_STRATEGIES = [commandRuleStrategy];
|
|
3
|
+
/** Resolve the auto-approve strategy for a tool, falling back to the default whole-tool strategy. */
|
|
4
|
+
export function getRuleStrategy(toolName) {
|
|
5
|
+
return REGISTERED_STRATEGIES.find((strategy) => strategy.appliesTo(toolName)) ?? defaultRuleStrategy;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=rule-strategy-registry.js.map
|
package/dist/_shared/index.js
CHANGED
|
@@ -5,7 +5,7 @@ export { SessionUsageSchema, PendingPermissionInfoSchema, PendingInstructionRemi
|
|
|
5
5
|
export { ClientMessageSchema, ServerMessageSchema, SendMessageSchema, InjectMessageSchema, PermissionResponseWsSchema, CommandMessageSchema, AgentTextWsMessageSchema, AgentActivityWsMessageSchema, AgentResultWsMessageSchema, AgentStatusWsMessageSchema, AgentCreatedWsMessageSchema, AgentUpdatedWsMessageSchema, AgentDeletedWsMessageSchema, AgentUsageWsMessageSchema, PermissionRequestWsMessageSchema, PermissionCancelledWsMessageSchema, ErrorWsMessageSchema, AgentMessageWsMessageSchema, AgentToolProgressWsMessageSchema, TaskAddedWsMessageSchema, TaskUpdatedWsMessageSchema, TaskAssignedWsMessageSchema, TaskStateChangedWsMessageSchema, TaskDeletedWsMessageSchema, CircleCreatedWsMessageSchema, CircleUpdatedWsMessageSchema, CircleDeletedWsMessageSchema, RelationshipCreatedWsMessageSchema, RelationshipDeletedWsMessageSchema, AgentBuilderDraftUpdatedWsMessageSchema, isAgentServerMessage, CLIENT_MESSAGE_TYPE, SERVER_MESSAGE_TYPE, } from "./schemas/websocket.schema.js";
|
|
6
6
|
export { PERMISSION_DECISION, PermissionRequestSchema, PermissionResponseSchema, } from "./schemas/permission.schema.js";
|
|
7
7
|
export { AgentMessageSchema, MessageAnnotationSchema, AGENT_MESSAGE_ROLE, AGENT_MESSAGE_TYPE, } from "./schemas/agent-message.schema.js";
|
|
8
|
-
export { ARTIFACT_TYPE, ARTIFACT_CONTENT_TYPE, ArtifactTypeSchema, ArtifactContentTypeSchema, ArtifactMetadataSchema,
|
|
8
|
+
export { ARTIFACT_TYPE, ARTIFACT_CONTENT_TYPE, ArtifactTypeSchema, ArtifactContentTypeSchema, ArtifactMetadataSchema, ArtifactUpdateSchema, } from "./schemas/artifact.schema.js";
|
|
9
9
|
export { AGENT_TASK_STATE, AGENT_TASK_SOURCE_TYPE, AgentTaskStateSchema, AgentTaskSourceSchema, AgentTaskItemSchema, AgentTaskDatabaseSchema, CreateTaskInputSchema, UpdateTaskInputSchema, UpdateTaskStateInputSchema, AssignTaskInputSchema, } from "./schemas/agent-task.schema.js";
|
|
10
10
|
export { MCP_CONFIG_TYPE, InternalMcpConfigSchema, McpServerConfigSchema, CreateMcpConfigInputSchema, UpdateMcpConfigInputSchema, } from "./schemas/mcp-config.schema.js";
|
|
11
11
|
export { SensorInfoSchema } from "./schemas/sensor.schema.js";
|
|
@@ -24,4 +24,9 @@ export { MESSAGE_SOURCE_TYPE, MessageSourceSchema, } from "./schemas/message-sou
|
|
|
24
24
|
export { AGENT_COMMAND, AgentCommandSchema } from "./schemas/agent-command.schema.js";
|
|
25
25
|
export { GraphNodeSchema, GraphEdgeSchema, GraphDataSchema, } from "./schemas/graph.schema.js";
|
|
26
26
|
export { ConnectorConnectionSchema, ConnectorInfoSchema, ConnectConnectorResponseSchema, } from "./schemas/connector.schema.js";
|
|
27
|
+
export { parseRule, parseRules, formatRule, matchesSpecifier, GLOB_STAR, WORD_BOUNDARY_SUFFIX, PREFIX_STAR_SUFFIX, } from "./auto-approve/rule-format.js";
|
|
28
|
+
export { splitSubcommands, deriveRules, matchesRules, isReadOnlyCommand, DEFAULT_PREFIX_DEPTH, MAX_DERIVED_RULES, READ_ONLY_BUILTINS, PROCESS_WRAPPERS, } from "./auto-approve/command-decomposition.js";
|
|
29
|
+
export { defaultRuleStrategy, commandRuleStrategy } from "./auto-approve/rule-strategies.js";
|
|
30
|
+
export { getRuleStrategy } from "./auto-approve/rule-strategy-registry.js";
|
|
31
|
+
export { AutoApproveRuleSet } from "./auto-approve/auto-approve-rule-set.js";
|
|
27
32
|
//# sourceMappingURL=index.js.map
|
|
@@ -49,11 +49,32 @@ export const ArtifactMetadataSchema = z.object({
|
|
|
49
49
|
createdBy: AgentTaskSourceSchema,
|
|
50
50
|
});
|
|
51
51
|
/**
|
|
52
|
-
* Request body for
|
|
53
|
-
*
|
|
52
|
+
* Request body for a PATCH artifact update. Tag changes apply a remove-then-add delta to the current
|
|
53
|
+
* set; `content` replaces the raw text. `expectedUpdatedTimestamp` guards against clobbering a
|
|
54
|
+
* concurrent edit — the update is rejected with a conflict when it no longer matches the stored
|
|
55
|
+
* timestamp. At least one of addTags/removeTags/content must be present, and a content replacement
|
|
56
|
+
* must carry the expected timestamp.
|
|
54
57
|
*/
|
|
55
|
-
export const
|
|
58
|
+
export const ArtifactUpdateSchema = z
|
|
59
|
+
.object({
|
|
56
60
|
addTags: z.array(z.string()).optional(),
|
|
57
61
|
removeTags: z.array(z.string()).optional(),
|
|
62
|
+
content: z.string().optional(),
|
|
63
|
+
expectedUpdatedTimestamp: z.number().optional(),
|
|
64
|
+
})
|
|
65
|
+
.superRefine((update, ctx) => {
|
|
66
|
+
if ((update.addTags?.length ?? 0) === 0 && (update.removeTags?.length ?? 0) === 0 && update.content === undefined) {
|
|
67
|
+
ctx.addIssue({
|
|
68
|
+
code: "custom",
|
|
69
|
+
message: "At least one of addTags, removeTags, or content is required",
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
if (update.content !== undefined && update.expectedUpdatedTimestamp === undefined) {
|
|
73
|
+
ctx.addIssue({
|
|
74
|
+
code: "custom",
|
|
75
|
+
message: "expectedUpdatedTimestamp is required when content is provided",
|
|
76
|
+
path: ["expectedUpdatedTimestamp"],
|
|
77
|
+
});
|
|
78
|
+
}
|
|
58
79
|
});
|
|
59
80
|
//# sourceMappingURL=artifact.schema.js.map
|
package/dist/bootstrap.js
CHANGED
|
@@ -91,6 +91,7 @@ export async function bootstrap(options) {
|
|
|
91
91
|
const taskManager = new AgentTaskManager(storeProvider, broadcaster, circleManager);
|
|
92
92
|
await taskManager.initialize();
|
|
93
93
|
const feedManager = new SimplyFeedManager(storeProvider, folderFileProvider, crowScheduler);
|
|
94
|
+
await feedManager.initialize();
|
|
94
95
|
const artifactManager = new ArtifactManager(storeProvider, registry, circleManager);
|
|
95
96
|
await artifactManager.initialize();
|
|
96
97
|
const documentSearchService = new DocumentSearchService(artifactManager, taskManager, registry, circleManager);
|
|
@@ -175,6 +176,7 @@ export async function bootstrap(options) {
|
|
|
175
176
|
const shutdown = async () => {
|
|
176
177
|
logger.info("Shutting down...");
|
|
177
178
|
crowScheduler.stop();
|
|
179
|
+
await feedManager.dispose();
|
|
178
180
|
await discordBotManager.destroy();
|
|
179
181
|
await server.close();
|
|
180
182
|
await copilotClientManager.dispose();
|