@mortz.prk/opencode-auto-mode 0.1.3 → 1.0.1

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.
@@ -1,38 +0,0 @@
1
- import { parentPort, workerData } from "node:worker_threads";
2
- import { compileExpression, type RuleBindings } from "./rules.js";
3
- import type { UserRule } from "./config.js";
4
- import { shellWords } from "./shell.js";
5
-
6
- const { rules, resources, bindings, hasResources } = workerData as {
7
- rules: UserRule[]; resources: string[]; bindings: RuleBindings; hasResources: boolean;
8
- };
9
- function evaluate() {
10
- const results = rules.map(() => ({ any: false, coverage: hasResources ? resources.map(() => false) : [] }));
11
- for (const decision of ["deny", "ask", "allow"]) {
12
- let failed = false;
13
- for (const [index, rule] of rules.entries()) {
14
- if (rule.decision !== decision) continue;
15
- try {
16
- const predicate = compileExpression(rule.expression);
17
- const matches = resources.map(() => false);
18
- for (const [resourceIndex, resource] of resources.entries()) {
19
- try {
20
- const words = bindings.action === "shell" ? shellWords(resource) ?? [] : [];
21
- const result = predicate({ ...bindings, resource, command: words[0] ?? "", args: words.slice(1) });
22
- if (typeof result !== "boolean") throw new Error("CEL rules must return a boolean");
23
- matches[resourceIndex] = result;
24
- if (result && decision !== "allow") {
25
- results[index] = { any: true, coverage: hasResources ? matches : [] };
26
- return results;
27
- }
28
- } catch { failed = true; }
29
- }
30
- results[index] = { any: matches.some(Boolean), coverage: hasResources ? matches : [] };
31
- } catch { failed = true; }
32
- }
33
- if (failed) return null;
34
- }
35
- return results;
36
- }
37
-
38
- parentPort!.postMessage(evaluate());
package/src/rules.ts DELETED
@@ -1,79 +0,0 @@
1
- import { celEnv, CelScalar, listType, parse, plan } from "@bufbuild/cel";
2
- import { Worker } from "node:worker_threads";
3
- import type { UserRule } from "./config.js";
4
- import type { PermissionRequest } from "./reviewer.js";
5
-
6
- const environment = celEnv({ variables: {
7
- action: CelScalar.STRING, resource: CelScalar.STRING, agent: CelScalar.STRING,
8
- projectDir: CelScalar.STRING, isDelegation: CelScalar.BOOL,
9
- command: CelScalar.STRING, args: listType(CelScalar.STRING),
10
- } });
11
- let activeWorkers = 0;
12
-
13
- export function compileExpression(expression: string) {
14
- if (typeof expression !== "string" || !expression.trim() || expression.length > 4096)
15
- throw new Error("Invalid CEL expression");
16
- const parsed = parse(expression);
17
- type Expr = NonNullable<typeof parsed.expr>;
18
- const pending: { expr: Expr; depth: number }[] = [];
19
- if (parsed.expr) pending.push({ expr: parsed.expr, depth: 1 });
20
- let count = 0;
21
- while (pending.length) {
22
- const { expr, depth } = pending.pop()!;
23
- if (++count > 512 || depth > 32) throw new Error("CEL expression is too complex");
24
- const kind = expr.exprKind;
25
- const children: Expr[] = [];
26
- if (kind.case === "comprehensionExpr") throw new Error("CEL collection macros are disabled");
27
- if (kind.case === "callExpr") {
28
- children.push(...kind.value.args);
29
- if (kind.value.target) children.push(kind.value.target);
30
- } else if (kind.case === "selectExpr" && kind.value.operand) children.push(kind.value.operand);
31
- else if (kind.case === "listExpr") children.push(...kind.value.elements);
32
- else if (kind.case === "structExpr") {
33
- for (const entry of kind.value.entries) {
34
- if (entry.keyKind.case === "mapKey") children.push(entry.keyKind.value);
35
- if (entry.value) children.push(entry.value);
36
- }
37
- }
38
- for (const child of children) pending.push({ expr: child, depth: depth + 1 });
39
- }
40
- return plan(environment, parsed);
41
- }
42
-
43
- export type RuleMatch = { rule: UserRule; any: boolean; coverage: boolean[] };
44
- export type RuleBindings = { action: string; agent: string; projectDir: string; isDelegation: boolean };
45
-
46
- export async function evaluateRules(rules: UserRule[], request: PermissionRequest): Promise<RuleMatch[]> {
47
- if (!rules.length) return [];
48
- const resources = request.resources.length ? request.resources : [""];
49
- const bindings: RuleBindings = { action: request.action, agent: request.agent ?? "",
50
- projectDir: request.projectDir ?? "", isDelegation: request.isDelegation === true };
51
- const strings = [...resources, bindings.action, bindings.agent, bindings.projectDir];
52
- if (resources.length > 256 || rules.length * resources.length > 4096 ||
53
- strings.some((value) => typeof value !== "string" || value.length > 4096) ||
54
- strings.reduce((sum, value) => sum + Buffer.byteLength(value), 0) > 65_536)
55
- throw new Error("CEL request exceeds evaluation limits");
56
- if (activeWorkers >= 4) throw new Error("CEL evaluator is busy");
57
- const worker = new Worker(new URL("./rules-worker.ts", import.meta.url), {
58
- workerData: { rules, resources, bindings, hasResources: request.resources.length > 0 },
59
- });
60
- activeWorkers++;
61
- try {
62
- const matches = await new Promise<{ any: boolean; coverage: boolean[] }[]>((resolve, reject) => {
63
- const timer = setTimeout(() => reject(new Error("CEL evaluation timed out")), 1000);
64
- const fail = () => { clearTimeout(timer); reject(new Error("CEL evaluation failed")); };
65
- worker.once("error", fail);
66
- worker.once("exit", fail);
67
- worker.once("message", (value: unknown) => {
68
- clearTimeout(timer);
69
- if (!Array.isArray(value) || value.length !== rules.length || value.some((match) =>
70
- !match || typeof match.any !== "boolean" || !Array.isArray(match.coverage) ||
71
- match.coverage.length !== request.resources.length || match.coverage.some((item: unknown) => typeof item !== "boolean"))) { fail(); return; }
72
- resolve(value);
73
- });
74
- });
75
- return matches.map((match, index) => ({ ...match, rule: rules[index] }));
76
- } finally {
77
- try { await worker.terminate(); } finally { activeWorkers--; }
78
- }
79
- }