UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

65 lines (64 loc) • 5.2 kB
function presentAt(at, steps) { if (steps.length === 0) return at !== undefined; if (at === null || (typeof at !== "object" && typeof at !== "function")) return false; const [step, ...rest] = steps; if (!step.endsWith("[]")) return presentAt(Reflect.get(at, step), rest); const items = Reflect.get(at, step.slice(0, -2)); return Array.isArray(items) && items.some((item) => presentAt(item, rest)); } export function retiredKeyOf(bag, table) { for (const [key, replacement] of Object.entries(table)) { if (presentAt(bag, key.split("."))) return { key, replacement }; } return undefined; } export function assertNoRetiredKeys(bag, table, owner) { const hit = retiredKeyOf(bag, table); if (hit === undefined) return; const e = new Error(`${owner}.${hit.key} is retired and is not read by this engine — ${hit.replacement}. Refused rather than ignored: a seat that is silently dropped reads to the deployment as wired.`); e.code = owner === "TaskSpec" ? "config.spec_retired_key" : "config.deps_retired_key"; throw e; } export const RETIRED_HOOKS_REPLACEMENT = 'the callback seats are `mods` registrations now — register the extension on `RunnerDeps.mods` (a deployment\'s) or `TaskSpec.mods` (a task\'s), hung on the `classic.*` events (`classic.PreToolUse`, `classic.PostToolUse`, `classic.PostToolBatch`, `classic.UserPromptSubmit`, `classic.Stop`, `classic.SubagentStop`, …); a callback that denied is a handler answering `{ action: "deny", message }`'; export const RETIRED_TASK_SPEC_KEYS = Object.freeze({ hooks: RETIRED_HOOKS_REPLACEMENT, autoModeRequested: 'the permission mode is one key now — write `permissionMode: "auto"`', approverPosture: 'a posture never authorized anything; drop the key (the engine renders the posture from the effective mode). Releasing asks is a separate decision: `permissionMode: "bypassPermissions"`', "attachments.planModeReminder": "the read-only planning posture is stated whenever the leg's mode is `plan` (first turn, every 5 turns, and an exit frame when it ends) — drop the key", "skills[].manifest": "a skill's tools are its `allowedTools` / `disallowedTools` now — an allow-rule and a deny-rule source for the run the skill is activated in, never a fence; move `manifest.allowTools` to `allowedTools`. To fence tools write deny rules (or a delegated agent definition's `allowTools`); to fence write paths use session `allowDirs`", promptProvider: "a task changes prompt text through overlays: its role text is `systemPrompt` (it replaces the role base `promptRole` selects), its added block `appendSystemPrompt`, the minimal form `promptForm: \"minimal\"`; anything else is a `prompt.compose` registration on the task's own `mods` answering `{ overlays }` (an already-assembled prompt is refused, never passed through)", }); export const RETIRED_TOOL_POLICY_KEYS = Object.freeze({ writeTargetGuard: "the guarded write targets are stated as rows now — `ToolPolicy.writeGuardRows`, an async provider of the one path table's `PathGuardRow`s the leg's ONE write judgment reads for every writer the policy cannot see as a call (the shell's file commands). A wrapper carries it through `wrapToolPolicy`, which reads every member a policy declares", }); export const RETIRED_SHELL_READ_BOUNDARY_KEYS = Object.freeze({ writeProtectionJudge: 'the station asks ONE write judgment, `ShellReadBoundary.writeTargetJudge`, over the write-protection table and the deployment\'s guarded write rows together — it answers `"protected"`, `"guarded"` or `undefined`, not a boolean', writeTargetGuard: 'the deployment\'s guarded write rows join the same one judgment, `ShellReadBoundary.writeTargetJudge` (`"guarded"` for a target a `ToolPolicy.writeGuardRows` row covers) — the boundary has no second write seat', }); export const RETIRED_DECISION_MEMBERS = Object.freeze({ reason: 'rename it to "message" (the one text field a decision carries)', guard: 'write `guards: ["<word>"]` instead (the same five words, as a list: one ask may stand on several integrity guards)', }); export function assertNoRetiredToolPolicyKeys(policy) { if (policy === undefined || policy === null) return; const isRecord = typeof policy === "object" && !Array.isArray(policy); if (isRecord) assertNoRetiredKeys(policy, RETIRED_TOOL_POLICY_KEYS, "ToolPolicy"); const check = isRecord ? policy.check : undefined; if (typeof check !== "function") { const got = typeof policy !== "object" ? typeof policy : Array.isArray(policy) ? "an array" : `an object whose check is ${check === null ? "null" : typeof check}`; const e = new Error(`ToolPolicy must be an object whose \`check\` is a function (got ${got}) — a policy the engine cannot call is refused rather than run as no policy; omit the seat for none.`); e.code = "config.invalid_argument"; e.detail = { field: "ToolPolicy" }; throw e; } } export function assertNoRetiredShellReadBoundaryKeys(boundary) { assertNoRetiredKeys(boundary, RETIRED_SHELL_READ_BOUNDARY_KEYS, "ShellReadBoundary"); }