@sema-agent/core
Version:
Stateless, task-oriented AI agent core
65 lines (64 loc) • 5.2 kB
JavaScript
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");
}