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 { AgentTeams } from "./agent-teams.js";
|
||||||
export type { TeamRole, TeamAgent, TeamConfig, TeamResult } 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