UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

47 lines (46 loc) • 3.08 kB
import { createHash } from "node:crypto"; import { createAutoModeDecider } from "./auto-mode.js"; import { buildAutoModePrompt, renderAutoModeAction, renderAutoModeWindow } from "./auto-mode-prompt.js"; import { foldAutoModeArming, } from "./auto-mode-arming.js"; import { CROSS_SESSION_CLASSIFIER_RULE } from "../agents/cross-session-envelope.js"; export function rebuildAutoModeDecider(opts) { const folded = foldAutoModeArming(opts.recorded, opts.current); if (!folded.ok) return folded; const effective = folded.effective; const promptOptions = { ...(effective.rules !== undefined ? { rules: effective.rules } : {}), ...(effective.settingsDenyRules !== undefined ? { settingsDenyRules: effective.settingsDenyRules } : {}), ...(effective.derivedDenyRules !== undefined ? { derivedDenyRules: effective.derivedDenyRules } : {}), ...(effective.sessionContext !== undefined ? { sessionContext: effective.sessionContext } : {}), ...(effective.crossSessionMessagesRule === true ? { crossSessionMessagesRule: CROSS_SESSION_CLASSIFIER_RULE } : {}), }; const systemPrompt = buildAutoModePrompt(promptOptions); const assembledDigest = `apv1:${createHash("sha256").update(systemPrompt).digest("hex")}`; if (folded.recorded.promptDigest !== assembledDigest) { return { ok: false, reason: "prompt_assets_moved", message: folded.recorded.promptDigest === undefined ? "the recorded arming carries no classifier-prompt digest, so this build cannot establish that the prompt it assembles is " + "the one the ancestor enforced (the recipe records overrides; the base shell and permissions document come from the build's " + "own assets) — refusing to rebuild rather than enforce criteria that may not be the ancestor's" : "this build assembles a DIFFERENT classifier prompt than the recorded arming did (prompt-asset version skew across the fleet: " + `recorded ${folded.recorded.promptDigest}, assembled here ${assembledDigest}) — the overrides match but the default rules they ` + "merge into do not, so the rebuilt classifier would enforce criteria the ancestor never ran; refusing to rebuild", }; } const decider = createAutoModeDecider({ ...(effective.timeoutMs !== undefined ? { timeoutMs: effective.timeoutMs } : {}), assemble: async (input) => { const messages = await opts.transcript(); return renderAutoModeWindow(messages, effective.window) + renderAutoModeAction(input); }, classify: async (_input, signal, _rung, userPrompt) => { if (userPrompt === undefined) throw new Error("the rebuilt auto-mode classifier was asked with no assembled question"); return opts.complete({ systemPrompt, userPrompt }, signal); }, }); return { ok: true, decider, arming: folded.recorded, effective, tightened: folded.tightened }; }