feat: add orchestrator policy inheritance
This commit is contained in:
parent
92ebedc703
commit
fa5006ed98
|
|
@ -37,3 +37,6 @@ export type { SubAgentSpec, MetaAgentConfig, MetaAgentResult } from "./meta-agen
|
|||
|
||||
export { AgentTeams } from "./agent-teams.js";
|
||||
export type { TeamRole, TeamAgent, TeamConfig, TeamResult } from "./agent-teams.js";
|
||||
|
||||
export { isActionDenied, mergeToolPolicies } from "./orchestrator-policy.js";
|
||||
export type { ToolPolicy, EffectiveToolPolicy } from "./orchestrator-policy.js";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { isActionDenied, mergeToolPolicies } from "./orchestrator-policy.js";
|
||||
|
||||
describe("orchestrator tool policy", () => {
|
||||
it("cascades parent denies over child allows", () => {
|
||||
const effective = mergeToolPolicies(
|
||||
{ deny: ["git push --force"] },
|
||||
{ allow: ["shell", "git push --force"] },
|
||||
);
|
||||
|
||||
expect(effective.allow).toContain("git push --force");
|
||||
expect(effective.deny).toContain("git push --force");
|
||||
expect(isActionDenied("git push origin main --force", effective)).toBe(true);
|
||||
expect(isActionDenied("git push origin main --force-with-lease", effective)).toBe(true);
|
||||
});
|
||||
|
||||
it("does not block normal push when only force push is denied", () => {
|
||||
expect(isActionDenied("git push origin main", { deny: ["git push --force"] })).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
export interface ToolPolicy {
|
||||
allow?: string[];
|
||||
deny?: string[];
|
||||
}
|
||||
|
||||
export interface EffectiveToolPolicy {
|
||||
allow: string[];
|
||||
deny: string[];
|
||||
}
|
||||
|
||||
const uniq = (items: string[] = []): string[] => [...new Set(items.map((s) => s.trim()).filter(Boolean))];
|
||||
|
||||
export function mergeToolPolicies(parent?: ToolPolicy, child?: ToolPolicy): EffectiveToolPolicy {
|
||||
return {
|
||||
allow: uniq([...(parent?.allow ?? []), ...(child?.allow ?? [])]),
|
||||
// ponytail: deny wins; add richer policy language when real agent configs need it.
|
||||
deny: uniq([...(parent?.deny ?? []), ...(child?.deny ?? [])]),
|
||||
};
|
||||
}
|
||||
|
||||
export function isActionDenied(action: string, policy?: ToolPolicy): boolean {
|
||||
const normalized = action.toLowerCase().replace(/\s+/g, " ").trim();
|
||||
return mergeToolPolicies(policy).deny.some((rule) => matchesRule(normalized, rule));
|
||||
}
|
||||
|
||||
function matchesRule(action: string, rule: string): boolean {
|
||||
const normalizedRule = rule.toLowerCase().replace(/\s+/g, " ").trim();
|
||||
if (!normalizedRule) return false;
|
||||
if (normalizedRule === "git push --force") {
|
||||
return /\bgit\s+push\b/.test(action) && /(?:\s|^)(?:--force(?:\s|=|$)|-f(?:\s|$)|--force-with-lease(?:\s|=|$))/.test(action);
|
||||
}
|
||||
return action.includes(normalizedRule);
|
||||
}
|
||||
Loading…
Reference in New Issue