@sema-agent/core
Version:
Stateless, task-oriented AI agent core
83 lines (82 loc) • 7.97 kB
JavaScript
import { createHash } from "node:crypto";
import { PRESENT_PLAN_TOOL_NAME } from "./present-plan-tool.js";
import { engineRegionTextsOf, markedReminderBodiesOf } from "./reminder-mint.js";
export const AUTO_MODE_DIRECTIVE = "## Auto Mode Active\n\nBias toward working without stopping for clarifying questions — when you'd normally pause to check, make the reasonable call and keep going; they'll redirect you if needed. If the user, a skill, or the shape of the task suggests they want you to ask (with AskUserQuestion or otherwise), do so. And even absent that signal, it's still fine to stop when you're genuinely blocked — unclear direction, missing input, a decision only they can make.\n\nBefore any command that could discard uncommitted work — `git checkout`/`restore`/`reset`/`clean`, `rm -rf` in the repo, restoring from a snapshot — run `git status` first and stash (with `-u` for untracked) or commit anything that's there. When staging or committing, review what's included (`git status` after a broad `git add`), and if you see anything suspicious that might reveal secrets — even if the filename looks innocuous — double-check the file's contents before pushing.";
export const BASH_FIRST_DIRECTIVE = "Do your work through the Bash tool wherever it can accomplish the job: read files with cat, head, or sed -n, search with grep and find, and make file changes with sed, heredocs, or short scripts, rather than using the dedicated Read, Edit, or Write tools. Fall back to a dedicated tool only when Bash genuinely cannot do the job.";
export const BYPASS_POSTURE_DIRECTIVE = `While bypass permissions mode is active:\n\n${BASH_FIRST_DIRECTIVE}`;
export const AUTO_MODE_EXIT_DIRECTIVE = "## Exited Auto Mode\n\nYou have exited auto mode. The user may now want to interact more directly. You should ask clarifying questions when the approach is ambiguous rather than making assumptions.";
export const BYPASS_POSTURE_EXIT_DIRECTIVE = "## Exited Auto Mode\n\nYou have exited auto mode. Resume using the dedicated tools for file reads, searches, and edits.";
export const PLAN_MODE_DIRECTIVE = `Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits, run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system — every such call asks the user first, and is refused where nobody can answer. This supercedes any other instructions you have received. Instead, you should: research and analyze with read-only tools, and when you have formed a concrete plan, present it with the ${PRESENT_PLAN_TOOL_NAME} tool for approval. That tool inherently requests plan approval — never ask for approval via plain text.`;
export const PLAN_MODE_SPARSE_DIRECTIVE = `Plan mode still active (see full instructions earlier in conversation). Read-only: no edits, commits, or config changes. When your plan is ready, present it with the ${PRESENT_PLAN_TOOL_NAME} tool for approval. Never ask about plan approval via plain text.`;
export const PLAN_MODE_NO_EXIT_DIRECTIVE = "Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits, run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system — every such call asks the user first, and is refused where nobody can answer. This supercedes any other instructions you have received. Instead, you should: research and analyze with read-only tools, and when you have formed a concrete plan, give it as your final answer and stop.";
export const PLAN_MODE_NO_EXIT_SPARSE_DIRECTIVE = "Plan mode still active (see full instructions earlier in conversation). Read-only: no edits, commits, or config changes. When your plan is ready, give it as your final answer and stop.";
export const PLAN_MODE_CHILD_DIRECTIVE = "Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits, run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system — every such call asks the user first, and is refused where nobody can answer. This supercedes any other instructions you have received. Instead, you should: research and analyze with read-only tools, and answer the request comprehensively — your final answer, carrying the plan, goes back to the agent that delegated to you.";
export const PLAN_MODE_EXIT_DIRECTIVE = "## Exited Plan Mode\n\nYou have exited plan mode. You can now make edits, run tools, and take actions.";
const PLAN_CADENCE = { everyTurns: 5, fullEvery: 5, sparse: PLAN_MODE_SPARSE_DIRECTIVE };
export const POSTURE_TABLE = Object.freeze([
{ family: "plan", owedBy: (f) => f.mode === "plan" && f.isChildLeg, frame: PLAN_MODE_CHILD_DIRECTIVE, exit: PLAN_MODE_EXIT_DIRECTIVE, cadence: { ...PLAN_CADENCE, sparse: PLAN_MODE_CHILD_DIRECTIVE } },
{ family: "plan", owedBy: (f) => f.mode === "plan" && f.exitToolMounted, frame: PLAN_MODE_DIRECTIVE, exit: PLAN_MODE_EXIT_DIRECTIVE, cadence: PLAN_CADENCE },
{ family: "plan", owedBy: (f) => f.mode === "plan", frame: PLAN_MODE_NO_EXIT_DIRECTIVE, exit: PLAN_MODE_EXIT_DIRECTIVE, cadence: { ...PLAN_CADENCE, sparse: PLAN_MODE_NO_EXIT_SPARSE_DIRECTIVE } },
{ family: "auto", owedBy: (f) => f.mode === "bypassPermissions" || (f.mode !== "dontAsk" && f.askEffective === "auto_allow"), frame: BYPASS_POSTURE_DIRECTIVE, exit: BYPASS_POSTURE_EXIT_DIRECTIVE },
{ family: "auto", owedBy: (f) => f.mode === "auto", frame: AUTO_MODE_DIRECTIVE, exit: AUTO_MODE_EXIT_DIRECTIVE },
]);
export function owedPostureRow(facts) {
return POSTURE_TABLE.find((row) => row.owedBy(facts));
}
const POSTURE_VOCABULARY = POSTURE_TABLE.flatMap((row) => [
[row.frame, { row, exit: false }],
[row.exit, { row, exit: true }],
...(row.cadence !== undefined ? [[row.cadence.sparse, { row, exit: false }]] : []),
]);
function postureItemsOf(body) {
const out = [];
let rest = body;
while (rest.length > 0) {
const hit = POSTURE_VOCABULARY.find(([text]) => rest === text || rest.startsWith(`${text}\n\n`));
if (hit === undefined)
return undefined;
out.push(hit[1]);
rest = rest === hit[0] ? "" : rest.slice(hit[0].length + 2);
}
return out.length > 0 ? out : undefined;
}
export function postureStandingOf(messages, reminderMark) {
const standing = new Map();
for (const m of messages) {
if (m.role === "assistant") {
for (const [family, s] of standing)
standing.set(family, { ...s, turnsSince: s.turnsSince + 1 });
continue;
}
for (const region of engineRegionTextsOf([m])) {
for (const body of markedReminderBodiesOf(region, reminderMark)) {
for (const item of postureItemsOf(body) ?? []) {
if (item.exit)
standing.delete(item.row.family);
else
standing.set(item.row.family, { row: item.row, statements: (standing.get(item.row.family)?.statements ?? 0) + 1, turnsSince: 0 });
}
}
}
}
return standing;
}
export function composePostureFrames(facts, standing) {
const owed = owedPostureRow(facts);
const frames = [];
for (const [family, s] of standing)
if (family !== owed?.family)
frames.push(s.row.exit);
if (owed === undefined)
return frames;
const s = standing.get(owed.family);
if (s === undefined || s.row !== owed)
frames.push(owed.frame);
else if (owed.cadence !== undefined && s.turnsSince >= owed.cadence.everyTurns) {
frames.push((s.statements + 1) % owed.cadence.fullEvery === 1 ? owed.frame : owed.cadence.sparse);
}
return frames;
}
export function directiveDigest(body) {
return createHash("sha256").update(body, "utf8").digest("hex");
}