@sema-agent/core
Version:
Stateless, task-oriented AI agent core
438 lines (437 loc) • 21.2 kB
JavaScript
import { types } from "node:util";
import { resolveModel } from "../roles.js";
import { brainToRuntime } from "../runtime.js";
import { deliverEngineNotice } from "../engine-notice.js";
import { inlineUntrusted } from "../untrusted-text.js";
import { extensionWords } from "../untrusted-egress.js";
import { askApproverIdentity, describeThrown, ownArgs } from "../tool-policy.js";
import { admitMods, expandModPattern } from "./kernel/index.js";
import { validateOverlay } from "../../prompt-assembly/overlay.js";
import { modsInvalid } from "./kernel/registration.js";
import { isPlainRecord } from "./kernel/receive.js";
import { engineMethodBottoms } from "./dollar.js";
import { modSeatsOf, modsReportToOnError, registrationRefusedNotice } from "./host.js";
export const ENGINE_CORE_ANSWER = Object.freeze({});
export function engineCore() {
return ENGINE_CORE_ANSWER;
}
export function agentIdOf(identity) {
return identity?.isDelegatedChild === true ? identity.taskId : undefined;
}
export function stopEventOf(identity) {
return identity?.isDelegatedChild === true ? "classic.SubagentStop" : "classic.Stop";
}
export function judgedInputOf(args) {
const owned = ownArgs(args);
return owned.ok ? owned.value : args;
}
export function admittedChainOf(bus, spec, deps) {
const offered = new Map();
for (const r of [...(deps.mods ?? []), ...(spec.mods ?? [])])
offered.set(r.name, r);
const hung = bus.mounted();
const links = [];
for (const rec of bus.records) {
if ("refusedBy" in rec)
continue;
const registration = offered.get(rec.name);
if (registration !== undefined)
links.push(Object.freeze({ registration, handlers: Object.freeze(hung.filter((h) => h.plugin === rec.name)) }));
}
return Object.freeze(links);
}
const ADMITTED_CHAINS = new WeakMap();
function recordAdmittedChain(bus, seats) {
const chain = admittedChainOf(bus, { ...(seats.task !== undefined ? { mods: [...seats.task] } : {}) }, { ...(seats.deployment !== undefined ? { mods: [...seats.deployment] } : {}) });
ADMITTED_CHAINS.set(bus, chain);
return chain;
}
export function chainOfBus(bus) {
return ADMITTED_CHAINS.get(bus);
}
const sameMatcher = (a, b) => {
if (a === undefined || b === undefined)
return a === b;
const ka = Object.keys(a).sort();
const kb = Object.keys(b).sort();
return ka.length === kb.length && ka.every((k, i) => k === kb[i] && a[k] === b[k]);
};
const sameHandler = (a, b) => a.seat === b.seat && a.pattern === b.pattern && a.observe === b.observe && a.caught === b.caught && sameMatcher(a.matcher, b.matcher);
function sameModLink(a, b) {
return a.registration === b.registration && a.handlers.length === b.handlers.length && a.handlers.every((h, j) => sameHandler(h, b.handlers[j]));
}
export function sameModChain(a, b) {
return a.length === b.length && a.every((l, i) => sameModLink(l, b[i]));
}
const judgesCall = (p) => !p.observe;
export function chainJudges(bus) {
return bus.hasHandlers("classic.PreToolUse", judgesCall) || bus.hasHandlers("tool.check", judgesCall);
}
const SCREENING_CHAINS = new WeakMap();
export function recordScreeningChain(policy, chain) {
SCREENING_CHAINS.set(policy, chain);
}
export function screeningChainOf(policy) {
return SCREENING_CHAINS.get(policy);
}
export function sameInstallation(pc, leg) {
const bus = screeningChainOf(pc.policy);
const chain = bus !== undefined ? chainOfBus(bus) : undefined;
return (chain !== undefined &&
sameModChain(chain, leg.chain) &&
askApproverIdentity(pc.onAsk) === askApproverIdentity(leg.onAsk) &&
(pc.durableMandate === true) === leg.durableMandate &&
(pc.contentMandate === true) === leg.contentMandate &&
pc.hookEnv === leg.hookEnv &&
pc.autoMode?.decider === leg.decider);
}
export function standsInFor(pc, leg, legChainJudges) {
return legChainJudges && sameInstallation(pc, { ...leg, durableMandate: false, contentMandate: false });
}
const judgesToolCheck = (h) => {
const selected = expandModPattern(h.pattern);
return !h.observe && "names" in selected && selected.names.includes("tool.check");
};
export function screeningInstallationsOf(entries) {
return entries.flatMap((pc) => {
const bus = screeningChainOf(pc.policy);
return bus !== undefined || pc.mods !== undefined ? [{ bus, chain: pc.mods }] : [];
});
}
export function unraisableGuardsOf(installations) {
const guards = installations.flatMap((i) => (i.bus !== undefined ? [] : (i.chain ?? []).filter((l) => l.handlers.some(judgesToolCheck)).map((l) => `"${inlineUntrusted(l.registration.name, 80)}"`)));
return guards.length === 0 ? undefined : `an inherited screening entry names the tool.check guard of ${guards.join(", ")}, but its policy is not one createPreToolUseConstraintPolicy minted, so the guard has no chain to judge this leg's calls on — hand the minted policy back as it is`;
}
const judges = (p) => judgesCall(p);
export function inheritedToolChecks(entries, standsIn, settleOf) {
const unraisable = unraisableGuardsOf(screeningInstallationsOf(entries));
if (unraisable !== undefined)
throw modsInvalid(unraisable);
const checks = [];
for (const pc of entries) {
const bus = screeningChainOf(pc.policy);
if (bus === undefined || standsIn(pc) || !bus.hasHandlers("tool.check", judges))
continue;
checks.push(Object.freeze({ bus, judges, settle: settleOf(pc) }));
}
return Object.freeze(checks);
}
export function classicEventBase(identity, permissionMode) {
const agentId = agentIdOf(identity);
return {
...(identity !== undefined ? { identity } : {}),
...(agentId !== undefined ? { agentId } : {}),
...(permissionMode !== undefined ? { permissionMode } : {}),
};
}
export async function observeClassic(bus, event, e, signal) {
if (!bus.hasHandlers(event))
return;
try {
await bus.dispatch(event, e, { bottom: engineCore, ...(signal !== undefined ? { signal } : {}) });
}
catch {
}
}
export async function dispatchClassic(bus, event, e, options) {
if (!bus.hasHandlers(event))
return undefined;
const check = options.checkAnswer;
return await bus.dispatch(event, e, {
bottom: engineCore,
...(options.signal !== undefined ? { signal: options.signal } : {}),
...(check !== undefined ? { checkAnswer: (a) => (a === ENGINE_CORE_ANSWER ? undefined : check(a)) } : {}),
});
}
export function stringMembersAnswer(event, members) {
return (answer) => {
if (answer === null || answer === false)
return undefined;
if (typeof answer !== "object")
return `answered ${typeof answer}; ${event} takes { ${members.map((m) => `${m}?`).join(", ")} }`;
for (const m of members) {
const v = Reflect.get(answer, m);
if (v !== undefined && typeof v !== "string")
return `answered a ${typeof v} "${m}"; it is a string`;
}
return undefined;
};
}
export function stringMembersOf(answer, members) {
const out = {};
if (typeof answer !== "object" || answer === null)
return out;
for (const m of members) {
const v = Reflect.get(answer, m);
if (typeof v === "string")
out[m] = v;
}
return out;
}
const FAILURE_CLAUSE = Object.freeze({
threw: (who, why) => `crashed (extension "${who}" ${why})`,
timeout: (who, why) => `did not answer (extension "${who}" ${why})`,
violation: (who, why) => `gave an answer the station cannot take (extension "${who}" ${why})`,
reentry: (who, why) => `was skipped on re-entry and its .catch did not answer (extension "${who}" ${why})`,
});
export function failedHandlerClause(f) {
return FAILURE_CLAUSE[f.cause](extensionWords(f.registration, 80), extensionWords(f.reason, 200));
}
export async function observePermissionDenied(input) {
const { payload, hostObserver } = input;
const e = { ...payload, ...classicEventBase(payload.identity, payload.permissionMode) };
if (input.legBus?.hasHandlers("classic.PermissionDenied") === true)
await observeClassic(input.legBus, "classic.PermissionDenied", e, input.signal);
if (hostObserver === undefined)
return;
const own = await admitModChain({
deployment: [
{
name: "toolGate.permissionDenied",
tier: "user",
uses: { events: ["classic.PermissionDenied"], calls: [] },
register: (on) => on("classic.PermissionDenied", async () => void (await hostObserver(payload)), { observe: true }),
},
],
}, input.onHostObserverFailure);
await observeClassic(own, "classic.PermissionDenied", e, input.signal);
}
export async function screenUserPrompt(bus, e, signal) {
let outcome;
try {
outcome = await dispatchClassic(bus, "classic.UserPromptSubmit", e, {
...(signal !== undefined ? { signal } : {}),
checkAnswer: stringMembersAnswer("classic.UserPromptSubmit", ["block", "additionalContext"]),
});
}
catch (err) {
return { kind: "failed", registration: "engine", cause: "threw", reason: `could not raise the screen: ${describeThrown(err)}`, skipped: [] };
}
if (outcome === undefined)
return undefined;
if (outcome.kind !== "answered")
return outcome;
return { kind: "answered", ...stringMembersOf(outcome.answer, ["block", "additionalContext"]) };
}
export const TOOL_CHECK_DECISIONS = Object.freeze(["allow", "ask", "deny"]);
const isToolCheckDecision = (v) => TOOL_CHECK_DECISIONS.some((d) => d === v);
function readToolCheckAnswer(answer) {
if (typeof answer !== "object" || answer === null)
return `answered ${answer === null ? "null" : typeof answer}; tool.check takes { decision, reason? }`;
const decision = Reflect.get(answer, "decision");
const reason = Reflect.get(answer, "reason");
if (!isToolCheckDecision(decision))
return `no { decision } (one of ${TOOL_CHECK_DECISIONS.join(", ")})`;
if (reason !== undefined && typeof reason !== "string")
return `answered a ${typeof reason} "reason"; it is a string`;
return { decision, ...(reason !== undefined ? { reason } : {}) };
}
export async function raiseToolCheck(bus, e, projection, signal, judges) {
if (!bus.hasHandlers("tool.check", judges))
return undefined;
let outcome;
try {
outcome = await bus.dispatch("tool.check", e, {
bottom: () => projection,
...(judges !== undefined ? { participant: judges } : {}),
...(signal !== undefined ? { signal } : {}),
checkAnswer: (a) => {
const read = readToolCheckAnswer(a);
return typeof read === "string" ? read : undefined;
},
});
}
catch (err) {
return { kind: "failed", registration: "engine", cause: "threw", reason: `could not raise the check: ${describeThrown(err)}`, skipped: [] };
}
if (outcome.kind !== "answered")
return outcome;
const read = readToolCheckAnswer(outcome.answer);
if (typeof read === "string")
return { kind: "failed", registration: outcome.answeredBy ?? "engine", cause: "violation", reason: read, skipped: outcome.skipped };
return { kind: "answered", ...read, ...(outcome.answeredBy !== undefined ? { by: outcome.answeredBy } : {}) };
}
const NO_COMPOSE = Object.freeze({ deploy: Object.freeze([]), task: Object.freeze([]), warnings: Object.freeze([]) });
function composeOverlaysOf(answer) {
if (!isPlainRecord(answer)) {
return `answered ${answer === null ? "null" : Array.isArray(answer) ? "an array" : typeof answer === "object" ? "an object that is not a plain record" : typeof answer}; prompt.compose takes a plain { overlays }`;
}
const stray = Object.keys(answer).filter((k) => k !== "overlays");
if (stray.length > 0)
return `the answer carries ${stray.slice(0, 5).map((k) => JSON.stringify(extensionWords(k, 40))).join(", ")} (overlays only)`;
const overlays = answer["overlays"];
return Array.isArray(overlays) && Object.getPrototypeOf(overlays) === Array.prototype ? overlays : "the answer's overlays is not an ordinary array";
}
export async function raisePromptCompose(bus, e, options) {
if (!bus.hasHandlers("prompt.compose"))
return NO_COMPOSE;
const sourceOf = (seat) => (seat === "deployment" ? "deploy" : "task");
const bottom = Object.freeze({ overlays: Object.freeze([]) });
const taken = new WeakMap([[bottom, Object.freeze([])]]);
const HEAD = "the answer does not carry what next() returned unchanged at its head (a handler adds overlays; it never rewrites or removes another's)";
let outcome;
try {
outcome = await bus.dispatch("prompt.compose", e, {
bottom: () => bottom,
...(options.signal !== undefined ? { signal: options.signal } : {}),
checkAnswer: (answer, _e, by, below) => {
if (typeof answer === "object" && answer !== null && taken.has(answer))
return undefined;
const mine = composeOverlaysOf(answer);
if (typeof mine === "string")
return mine;
const returned = below[below.length - 1];
const lower = typeof returned === "object" && returned !== null ? taken.get(returned) : undefined;
if (lower === undefined) {
return below.length === 0
? "it answered without next(e) — prompt.compose collects: a handler awaits next(e) and adds after what it returned"
: "next() is still running; prompt.compose answers after it";
}
if (mine.length < lower.length)
return HEAD;
const out = [...lower];
for (let i = 0; i < mine.length; i++) {
const under = lower[i];
const v = validateOverlay(mine[i], { source: sourceOf(under?.by.seat ?? by.seat), event: "prompt.compose", lowerAdds: options.lowerAdds });
if (under !== undefined) {
if ("violation" in v || JSON.stringify(v.overlay) !== JSON.stringify(under.overlay))
return HEAD;
continue;
}
if ("violation" in v)
return v.violation;
out.push(Object.freeze({ overlay: v.overlay, by: Object.freeze({ plugin: by.plugin, seat: by.seat }) }));
}
taken.set(answer, Object.freeze(out));
return undefined;
},
});
}
catch (err) {
return { deploy: [], task: [], warnings: [`prompt.compose could not be raised (${describeThrown(err)}); no mods overlay applies this run`] };
}
const warnings = (outcome.kind === "aborted" ? [] : outcome.skipped).map((s) => `prompt.compose: extension "${extensionWords(s.registration, 80)}" ${s.cause === "violation" ? "gave an answer the station cannot take" : s.cause}: ${extensionWords(s.reason, 300)} — that answer is skipped; what is below it stands`);
if (outcome.kind !== "answered")
return { deploy: [], task: [], warnings };
const answer = outcome.answer;
const contributions = typeof answer === "object" && answer !== null ? taken.get(answer) : undefined;
if (contributions === undefined)
return { deploy: [], task: [], warnings: [...warnings, "prompt.compose: the chain's answer is not one the station took; no mods overlay applies this run"] };
const blocks = [];
for (const { overlay, by } of contributions) {
const item = { overlay, origin: { source: sourceOf(by.seat), by: by.plugin } };
const last = blocks[blocks.length - 1];
if (last !== undefined && last.by.plugin === by.plugin && last.by.seat === by.seat)
last.items.push(item);
else
blocks.push({ by, items: [item] });
}
const inChainOrder = blocks.reverse().flatMap((b) => b.items);
return {
deploy: Object.freeze(inChainOrder.filter((o) => o.origin.source === "deploy")),
task: Object.freeze(inChainOrder.filter((o) => o.origin.source === "task")),
warnings,
};
}
function lateView() {
let seats;
const need = (method) => {
if (seats === undefined)
throw new Error(`${method}: the engine facade is not available before the leg is prepared`);
return seats;
};
const view = {
env: { exec: (command, options) => need("process.run").exec(command, options) },
complete: (request, signal) => need("model.complete").complete(request, signal),
book: (reply) => need("model.complete").book(reply),
session: {
id: () => need("session.id").sessionId,
cwd: () => need("session.cwd").cwd(),
root: () => need("session.root").root(),
model: () => need("session.model").model(),
messages: () => need("session.messages").messages(),
},
now: () => need("clock.now").now(),
tools: () => need("tool.list").tools(),
agents: () => need("agent.list").agents(),
};
return {
view,
bind: (s) => {
seats = s;
},
};
}
export async function admitLegMods(input) {
const late = lateView();
const seats = modSeatsOf(input.spec, input.deps);
const bus = await admitMods({
side: "engine",
seats,
bottoms: engineMethodBottoms(late.view),
report: modsReportToOnError(input.deps.onError, input.sessionId),
describeThrown,
isProxy: types.isProxy,
});
for (const record of bus.records) {
if ("refusedBy" in record)
deliverEngineNotice(input.deps.onNotice, registrationRefusedNotice(record, input.sessionId));
}
return { bus, chain: recordAdmittedChain(bus, seats), bindFacade: late.bind };
}
function replyText(reply) {
return reply.content.flatMap((c) => (c.type === "text" ? [c.text] : [])).join("");
}
export function legFacadeSeats(input) {
const runtime = brainToRuntime(input.deps.brain);
const answeredBy = new Map();
const byUsage = new WeakMap();
return {
exec: (command, options) => input.executionEnv.exec(command, options),
complete: async (request, signal) => {
const model = request.model !== undefined ? resolveModel(request.model, input.deps.models) : input.model;
answeredBy.set(model.id, model);
const auth = await input.spec.getApiKeyAndHeaders?.(model);
const reply = await runtime.completeSimple(model, { ...request.context, messages: [...request.context.messages] }, { signal, maxTokens: request.maxTokens, ...(auth?.apiKey !== undefined ? { apiKey: auth.apiKey } : {}), ...(auth?.headers !== undefined ? { headers: auth.headers } : {}) });
if (reply.stopReason === "error" || reply.stopReason === "aborted")
throw new Error(`model.complete: ${reply.errorMessage ?? reply.stopReason}`);
if (reply.usage !== undefined)
byUsage.set(reply.usage, { model, ...(reply.diagnostics !== undefined ? { diagnostics: reply.diagnostics } : {}) });
return Object.freeze({ text: replyText(reply), model: model.id, ...(reply.usage !== undefined ? { usage: reply.usage } : {}) });
},
sessionId: input.sessionId,
book: (reply) => {
const seen = reply.usage !== undefined ? byUsage.get(reply.usage) : undefined;
const record = seen !== undefined && seen.model.id === reply.model ? seen : undefined;
const model = record?.model ?? answeredBy.get(reply.model);
if (model === undefined)
throw new Error(`model.complete: the reply names model "${reply.model}", which no call of this leg resolved; it is not booked`);
input.book(model, { usage: reply.usage, ...(record?.diagnostics !== undefined ? { diagnostics: record.diagnostics } : {}) });
},
cwd: () => input.cwdRef?.current ?? input.taskRoot,
root: () => input.taskRoot,
model: () => input.model.id,
messages: () => input.lastBrainContext()?.messages ?? [],
now: () => (input.deps.now ?? Date.now)(),
tools: () => [...input.activeTools],
agents: () => (input.agentListing?.entries ?? []).map((e) => e.name),
};
}
export async function admitModChain(seats, onError) {
const bus = await admitMods({
side: "engine",
seats,
bottoms: engineMethodBottoms(lateView().view),
report: (err) => {
try {
onError?.(err);
}
catch {
}
},
describeThrown,
isProxy: types.isProxy,
});
recordAdmittedChain(bus, seats);
return bus;
}