@sema-agent/core
Version:
Stateless, task-oriented AI agent core
211 lines (210 loc) • 8.86 kB
JavaScript
export const KERNEL_CONTRACT_VERSION = 2;
export const MOD_TIERS = Object.freeze(["prepend", "user", "append", "builtin", "core"]);
export const MOD_SEATS = Object.freeze(["deployment", "task"]);
export const MOD_EVENT_SIDES = Object.freeze(["engine", "surface", "both"]);
export const MOD_FAILURE_ANSWERS = Object.freeze(["refuse", "skip"]);
export const MOD_ABORT_POSTURES = Object.freeze(["immediate", "grace", "caller"]);
export const NEXT_TO_TABLE = Object.freeze({
prepend: Object.freeze(["append", "builtin", "core"]),
user: Object.freeze([]),
append: Object.freeze(["core"]),
builtin: Object.freeze([]),
core: Object.freeze([]),
});
const CONTINUABLE = NEXT_TO_TABLE.prepend;
export function nextToRefusal(from, to) {
const allowed = NEXT_TO_TABLE[from];
const named = CONTINUABLE.find((t) => t === to);
if (named === undefined)
return `next.to names ${describe(to)}, which is not a tier a dispatch continues at (${CONTINUABLE.join(", ")})`;
if (allowed.length === 0)
return `next.to is available to prepend and append handlers only, not to a ${from} handler`;
if (!allowed.includes(named))
return `next.to("${named}") skips nothing from ${from}; a ${from} handler may continue at ${allowed.join(", ")}`;
return undefined;
}
export const SEAT_TIER_TABLE = Object.freeze({
deployment: Object.freeze({ declared: "required", tiers: Object.freeze(["prepend", "user", "append"]) }),
task: Object.freeze({ declared: "optional", tiers: Object.freeze(["user"]) }),
});
const station = (failure, abort, pinned = []) => Object.freeze({ failure, abort, pinned: Object.freeze([...pinned]) });
const method = (pausesCallerBudget) => Object.freeze({ failure: "skip", abort: "caller", pinned: Object.freeze([]), method: Object.freeze({ pausesCallerBudget }) });
const engine = (s) => Object.freeze(s === undefined ? { side: "engine" } : { side: "engine", station: s });
const surface = Object.freeze({ side: "surface" });
export const MOD_EVENT_TABLE = Object.freeze({
"classic.PreToolUse": engine(station("refuse", "immediate", ["tool", "tool_use_id", "agentId", "input"])),
"classic.PostToolUse": engine(station("skip", "grace")),
"classic.PostToolUseFailure": engine(station("skip", "grace")),
"classic.PostToolBatch": engine(station("skip", "grace")),
"classic.Notification": engine(),
"classic.UserPromptSubmit": engine(station("refuse", "immediate", ["source", "inputId", "actor", "prompt"])),
"classic.UserPromptExpansion": engine(),
"classic.SessionStart": engine(),
"classic.SessionEnd": engine(),
"classic.Stop": engine(station("skip", "immediate")),
"classic.StopFailure": engine(station("skip", "grace")),
"classic.SubagentStart": engine(),
"classic.SubagentStop": engine(station("skip", "immediate")),
"classic.PreCompact": engine(station("skip", "grace")),
"classic.PostCompact": engine(station("skip", "grace")),
"classic.PreModelSwitch": engine(),
"classic.PostModelSwitch": engine(),
"classic.PermissionRequest": engine(),
"classic.PermissionDenied": engine(station("skip", "grace")),
"classic.Setup": engine(),
"classic.TeammateIdle": engine(),
"classic.TaskCreated": engine(),
"classic.TaskCompleted": engine(),
"classic.Elicitation": engine(),
"classic.ElicitationResult": engine(),
"classic.ConfigChange": engine(),
"classic.WorktreeCreate": engine(),
"classic.WorktreeRemove": engine(),
"classic.InstructionsLoaded": engine(),
"classic.CwdChanged": engine(),
"classic.FileChanged": engine(),
"classic.DirectoryAdded": engine(),
"classic.MessageDisplay": engine(),
"tool.call": engine(),
"tool.check": engine(station("refuse", "immediate", ["tool", "input", "tool_use_id", "agentId", "ceiling"])),
"ui.render": surface,
"ui.resolve": surface,
"ui.press": surface,
"ui.input": surface,
"ui.select": surface,
"ui.message": surface,
"ui.fault": surface,
"ui.scroll": surface,
"ui.focus": surface,
"agent.offer": engine(),
"agent.spawn": engine(),
"prompt.submit": engine(),
"prompt.fill": surface,
"prompt.suggest": surface,
"prompt.edit": surface,
"prompt.autocomplete": surface,
"prompt.section": engine(station("skip", "grace", ["name", "key", "mountedTools"])),
"prompt.context": engine(),
"prompt.attachment": engine(),
"prompt.mention": surface,
"prompt.compose": engine(station("skip", "grace", ["model", "catalog", "selectors"])),
"tool.describe": engine(),
"command.run": surface,
"command.describe": surface,
"config.set": surface,
"config.describe": surface,
"telemetry.log": engine(),
"telemetry.mark": engine(),
"skill.prompt": engine(),
"attribution.text": engine(),
"session.start": engine(),
"session.receive": engine(),
"session.append": engine(),
"session.send": engine(),
"session.compact": engine(),
"session.attach": engine(),
"session.detach": engine(),
"session.measure": engine(),
"session.end": engine(),
"plugin.register": Object.freeze({ side: "both", station: station("skip", "immediate", ["name", "seat", "tier", "uses"]) }),
"turn.start": engine(),
"turn.step": engine(),
"turn.complete": engine(),
"engine.create": engine(),
"model.complete": engine(method(true)),
"model.classify": engine(),
"model.fork": engine(),
"audio.play": surface,
"audio.speak": surface,
"mcp.call": engine(),
"mcp.connect": engine(),
"session.cwd": engine(method(true)),
"session.root": engine(method(true)),
"session.model": engine(method(true)),
"session.turns": engine(),
"session.id": engine(method(true)),
"session.messages": engine(method(true)),
"session.repo": engine(),
"session.surface": engine(),
"session.surfaces": engine(),
"session.authorize": engine(),
"session.usage": engine(),
"session.version": engine(),
"turn.abort": engine(),
"prompt.read": surface,
"flag.value": engine(),
"tool.list": engine(method(true)),
"tool.register": engine(),
"command.list": surface,
"command.register": surface,
"config.list": surface,
"agent.list": engine(method(true)),
"agent.register": engine(),
"ui.toast": surface,
"ui.status": surface,
"ui.log": surface,
"ui.notice": surface,
"ui.invalidate": surface,
"ui.open": surface,
"ui.close": surface,
"ui.panes": surface,
"ui.selection": surface,
"ui.copy": surface,
"ui.notify": surface,
"ui.blit": surface,
"fs.read": engine(),
"fs.write": engine(),
"fs.list": engine(),
"fs.exists": engine(),
"fs.stat": engine(),
"fs.ancestors": engine(),
"store.get": engine(),
"store.set": engine(),
"store.delete": engine(),
"store.keys": engine(),
"state.get": engine(),
"state.set": engine(),
"clock.now": engine(method(false)),
"clock.sleep": engine(method(false)),
"clock.after": engine(),
"clock.every": engine(),
"http.fetch": engine(),
"process.run": engine(method(true)),
"process.spawn": engine(),
"settings.read": engine(),
"env.get": engine(),
"env.set": engine(),
});
const EVENT_NAMES = Object.freeze(Object.keys(MOD_EVENT_TABLE).filter(isModEventName));
export function isModEventName(name) {
return typeof name === "string" && Object.prototype.hasOwnProperty.call(MOD_EVENT_TABLE, name);
}
export function stationOn(name, side) {
const row = MOD_EVENT_TABLE[name];
return row.side === side || row.side === "both" ? row.station : undefined;
}
export function readOnSide(name, side) {
const row = MOD_EVENT_TABLE[name];
return row.side === side || row.side === "both";
}
export function expandModPattern(pattern) {
if (typeof pattern !== "string" || pattern === "")
return { refusal: `${describe(pattern)} is not an event name or a "<prefix>.*" pattern` };
if (pattern.endsWith(".*")) {
const prefix = pattern.slice(0, -2);
if (prefix === "" || prefix.includes("*"))
return { refusal: `"${pattern}" is not an event name or a "<prefix>.*" pattern` };
const names = EVENT_NAMES.filter((n) => n.startsWith(`${prefix}.`));
return names.length > 0 ? { names } : { refusal: `"${pattern}" selects no event` };
}
return isModEventName(pattern) ? { names: [pattern] } : { refusal: `"${pattern}" is not an event` };
}
export function describe(value) {
if (typeof value === "string")
return `"${value}"`;
if (typeof value === "number" || typeof value === "boolean")
return String(value);
if (value === null)
return "null";
return typeof value === "object" ? (Array.isArray(value) ? "an array" : "an object") : typeof value;
}