UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

71 lines (70 loc) • 3.46 kB
import { combinePolicies } from "./tool-policy.js"; import { clampPermissionMode } from "./ask-origin.js"; import { CLAMP_SEATS, composeClampSeat, loosensClampSeat } from "./child-clamp-seats.js"; export class TaskSpecTightenError extends Error { constructor(message) { super(message); this.name = "TaskSpecTightenError"; } } export function tightenTaskSpec(base, overrides) { const merged = { ...base, ...overrides }; if (base.toolPolicy && overrides.toolPolicy) { merged.toolPolicy = combinePolicies(base.toolPolicy, overrides.toolPolicy); } else { merged.toolPolicy = overrides.toolPolicy ?? base.toolPolicy; } for (const field of ["excludeTools", "deferTools"]) { const b = base[field]; const o = overrides[field]; if (b !== undefined || o !== undefined) { merged[field] = [...new Set([...(b ?? []), ...(o ?? [])])]; } } { const b = base.allowTools; const o = overrides.allowTools; if (b !== undefined && o !== undefined && !b.includes("*")) { const wider = o.filter((entry) => !b.includes(entry)); if (wider.length > 0) throw new TaskSpecTightenError(`tightenTaskSpec: cannot widen allowTools beyond the base list (override names ${JSON.stringify(wider)}; the override may only narrow)`); } const kept = o ?? b; if (kept !== undefined) merged.allowTools = [...kept]; else delete merged.allowTools; } for (const seat of CLAMP_SEATS) { const b = base[seat]; const o = overrides[seat]; if (loosensClampSeat(seat, b, o)) { throw new TaskSpecTightenError(`tightenTaskSpec: cannot loosen ${seat} from ${JSON.stringify(b)} to ${JSON.stringify(o)} (override may only tighten)`); } if (b !== undefined || o !== undefined) Object.assign(merged, { [seat]: composeClampSeat(seat, b, o) }); } if (base.permissionMode !== undefined && overrides.permissionMode !== undefined) { const { effective, clamped } = clampPermissionMode(overrides.permissionMode, base.permissionMode); if (clamped) { throw new TaskSpecTightenError(`tightenTaskSpec: permissionMode override "${overrides.permissionMode}" would loosen the base "${base.permissionMode}" (the override may only tighten; a bypass override needs a bypass base, acceptEdits and auto never adopt each other)`); } merged.permissionMode = effective; } else if (base.permissionMode !== undefined || overrides.permissionMode !== undefined) { merged.permissionMode = overrides.permissionMode ?? base.permissionMode; } if (base.shellGate !== undefined || overrides.shellGate !== undefined) { merged.shellGate = base.shellGate ?? overrides.shellGate; } if (base.onAsk !== undefined && overrides.onAsk !== undefined && base.onAsk !== overrides.onAsk) { throw new TaskSpecTightenError("tightenTaskSpec: conflicting onAsk on base and overrides — onAsk cannot be merged automatically " + "(the approver must be coordinated with the policy); set it in exactly one of base/overrides"); } merged.onAsk = base.onAsk ?? overrides.onAsk; if (base.mods !== undefined || overrides.mods !== undefined) { merged.mods = [...(base.mods ?? []), ...(overrides.mods ?? [])]; } return merged; }