@sema-agent/core
Version:
Stateless, task-oriented AI agent core
345 lines (344 loc) • 17.7 kB
JavaScript
import { uuidv7 } from "../internal/harness.js";
import { summarizeWorkflowRun } from "./workflow-run-store.js";
import { delimitUntrusted } from "./untrusted-text.js";
import { boundedRedactedSummary } from "./untrusted-egress.js";
import { clipWithFilePointer, OUTPUT_BUDGET_MIN_CHARS } from "./tool-errors.js";
import { resolveEnvBudget } from "./env-budget.js";
export function mintCompletionId(target) {
if (target.completionId === undefined)
target.completionId = uuidv7();
return target.completionId;
}
export const defaultMonitorTimers = {
setInterval: (fn, ms) => {
const h = setInterval(() => void fn(), ms);
h.unref?.();
return h;
},
clearInterval: (h) => clearInterval(h),
now: () => Date.now(),
};
const BLOCK_POLL_INTERVAL_MS = 400;
export const sleepPollStep = (deadline, signal) => sleepMs(deadline === undefined ? BLOCK_POLL_INTERVAL_MS : Math.max(1, Math.min(BLOCK_POLL_INTERVAL_MS, deadline - Date.now())), signal);
export const sleepMs = (ms, signal) => new Promise((resolve) => {
const t = setTimeout(done, ms);
function done() {
clearTimeout(t);
signal?.removeEventListener("abort", done);
resolve();
}
signal?.addEventListener("abort", done, { once: true });
});
export const MONITOR_BATCH_WINDOW_MS = 200;
export const MONITOR_DEFAULT_TIMEOUT_MS = 300_000;
export const MONITOR_MAX_TIMEOUT_MS = 3_600_000;
export const MONITOR_MAX_BATCHES_PER_MINUTE = 50;
export const MONITOR_STORM_BURST = 10;
export const MONITOR_STORM_KILL_AFTER_MS = 30_000;
const MONITOR_EVENT_RESULT_CAP = 2_000;
const MONITOR_EVENT_LINE_CAP = 500;
export const MONITOR_LINE_BUF_CAP = 1_048_576;
export function clipMonitorEvent(s) {
return s.length <= MONITOR_EVENT_RESULT_CAP ? s : `${s.slice(0, MONITOR_EVENT_RESULT_CAP)}\n…[+${s.length - MONITOR_EVENT_RESULT_CAP} chars truncated]`;
}
export function clipMonitorLine(s) {
return s.length <= MONITOR_EVENT_LINE_CAP ? s : `${s.slice(0, MONITOR_EVENT_LINE_CAP)}...(truncated)`;
}
export function terminalTaskSummary(kind, label, status, exitCode) {
if (kind === "monitor") {
if (status === "completed")
return `Monitor "${label}" stream ended`;
if (status === "failed")
return `Monitor "${label}" script failed${exitCode !== undefined ? ` (exit ${exitCode})` : ""}`;
return `Monitor "${label}" stopped`;
}
if (status === "completed")
return `Background command "${label}" completed${exitCode !== undefined ? ` (exit code ${exitCode})` : ""}`;
if (status === "failed")
return `Background command "${label}" failed${exitCode !== undefined ? ` with exit code ${exitCode}` : ""}`;
return `Background command "${label}" was stopped`;
}
const TASK_OUTPUT_DEFAULT_CHARS = 32_000;
export const TASK_OUTPUT_MAX_CHARS = 160_000;
function taskMaxOutputChars() {
return resolveEnvBudget(process.env.TASK_MAX_OUTPUT_LENGTH, { name: "TASK_MAX_OUTPUT_LENGTH", unit: "characters", default: TASK_OUTPUT_DEFAULT_CHARS, min: OUTPUT_BUDGET_MIN_CHARS, max: TASK_OUTPUT_MAX_CHARS });
}
export function clipTaskOutput(s, fullOutputPath) {
return clipWithFilePointer(s, taskMaxOutputChars(), fullOutputPath);
}
export function statusFromBackground(status, exitCode) {
if (status === "running")
return "running";
if (status === "killed")
return "killed";
if (status === "failed")
return "failed";
if (status === "exited")
return exitCode === 0 ? "completed" : "failed";
return "failed";
}
export function rollSpoolText(spool, s, cap, onDrop) {
if (s.length <= cap)
return s;
const half = Math.floor(cap / 2);
const dropped = s.slice(half, s.length - half);
spool.rolledChars += dropped.length;
onDrop?.(dropped);
return s.slice(0, half) + s.slice(s.length - half);
}
export const MONITOR_SPILL_CAP_CHARS = 64 * 1024 * 1024;
export function accountDroppedBytes(spool, poll) {
const d = poll.bytesDroppedBeforeCursor ?? 0;
if (d > 0)
spool.droppedBytes = (spool.droppedBytes ?? 0) + d;
else if (poll.truncated === true)
spool.dropUnknown = true;
}
export function spoolDropNote(acct) {
if ((acct.droppedBytes ?? 0) > 0)
return `[!] ${acct.droppedBytes} earlier byte(s) permanently dropped before this window (8MB buffer evicted)\n`;
if (acct.dropUnknown === true)
return "[!] an unknown amount of earlier output was permanently dropped before this window (buffer evicted)\n";
return "";
}
export const TASK_OUTPUT_REPLAY_KINDS = Object.freeze(["full", "cursor"]);
export const REPLAY_FACE = {
full: { stdout: "stdout (full, re-readable)", stdoutWhenGapped: "stdout (re-readable spool — INCOMPLETE, see note above)", stderr: "stderr" },
cursor: { stdout: "new stdout", stdoutWhenGapped: undefined, stderr: "new stderr" },
};
export const SPOOL_REPLAY = "full";
export const INCREMENT_REPLAY = "cursor";
export function stdoutHeadingOf(replay, gapDisclosed) {
const face = REPLAY_FACE[replay];
return gapDisclosed ? (face.stdoutWhenGapped ?? face.stdout) : face.stdout;
}
export function renderSpoolBody(spool, opts) {
const timedOutClause = spool.timedOut === true ? ` (timed out after ${spool.timeoutSec !== undefined ? `${spool.timeoutSec}s` : "its background time budget"})` : "";
const displayStatus = (spool.exitCode !== undefined ? `exited(code ${spool.exitCode})` : opts.running ? "running" : opts.handleStatus) + timedOutClause;
const rolledNote = spool.rolledChars > 0 ? `[!] ${spool.rolledChars} earlier char(s) dropped from the spool (memory bound)${opts.rolledExtraClause}\n` : "";
const droppedNote = spoolDropNote(spool);
const stdoutHeading = stdoutHeadingOf(SPOOL_REPLAY, droppedNote !== "" || opts.drainNote !== "");
return (`status: ${displayStatus}\n${rolledNote}${droppedNote}${opts.drainNote}--- ${stdoutHeading} ---\n${opts.shape(spool.stdout) || "(none)"}\n` +
`--- ${REPLAY_FACE[SPOOL_REPLAY].stderr} ---\n${opts.shape(spool.stderr) || "(none)"}`);
}
export function droppedGapNote(spool) {
if ((spool.droppedBytes ?? 0) > 0)
return ` [!] ${spool.droppedBytes} earlier byte(s) of output were permanently dropped before this window (execution-environment tail buffer evicted) — the event stream and this spool are INCOMPLETE.`;
if (spool.dropUnknown === true)
return " [!] An unknown amount of earlier output was permanently dropped before this window (execution-environment tail buffer evicted) — the event stream and this spool are INCOMPLETE.";
return "";
}
export function firstDropNote(spool) {
if (spool.dropNotified === true)
return "";
const note = droppedGapNote(spool);
if (note !== "")
spool.dropNotified = true;
return note;
}
export function alreadyTerminalStopNote(id, status) {
return `${id} already ${status} — nothing to stop; poll TaskOutput for its final state.`;
}
export function assertOwnership(input, site) {
if (input.owner === undefined || input.scope === undefined) {
throw new Error(`TaskRegistry.${site}: owner AND scope are both required (access is default-deny on both axes; use scope "default" for a single-tenant deployment)`);
}
}
export function canAccessWorkflowRun(run, access) {
if (access.scope === undefined || run.scope !== access.scope)
return false;
if (access.owner !== undefined && run.sourceTaskId !== undefined && run.sourceTaskId === access.owner)
return true;
if (access.sessionId !== undefined && run.originatingSessionId !== undefined && run.originatingSessionId === access.sessionId)
return true;
return false;
}
const WORKFLOW_PARTIAL_RESULTS_MAX = 5;
const WORKFLOW_AGENT_ROWS_MAX = 60;
function workflowAgentRow(a, ordinal) {
return {
ordinal,
label: a.label,
...(a.phase !== undefined ? { phase: a.phase } : {}),
status: a.status,
...(a.errorCode !== undefined ? { errorCode: a.errorCode } : {}),
...(a.errorMessage !== undefined
? { error: delimitUntrusted(`workflow agent ${a.label} error`, boundedRedactedSummary(a.errorMessage, 200)) }
: {}),
...(a.startedAt !== undefined && a.endedAt !== undefined ? { elapsedMs: a.endedAt - a.startedAt } : {}),
...(a.replayed === true ? { replayed: true } : {}),
};
}
function budgetOvershootNote(overshoot) {
if (overshoot === undefined)
return "";
const unsettled = overshoot.unsettledTokens ?? 0;
const total = overshoot.spentTokens + unsettled;
return (` token budget OVERSHOT: this run spent ${total.toLocaleString()} total tokens against a ${overshoot.budgetTokens.toLocaleString()} ceiling ` +
`(over by ${(total - overshoot.budgetTokens).toLocaleString()}` +
`${unsettled > 0 ? `, of which ${unsettled.toLocaleString()} was observed on agents still in flight at the terminal and never settled` : ""}).`);
}
function timeoutInterruptionNote(interruption) {
if (interruption === undefined)
return "";
return (` INTERRUPTED by the workflow's total timeout (${interruption.timeoutMs.toLocaleString()}ms): when the deadline fired ` +
`${interruption.agentsCompleted} agent(s) had completed, ${interruption.agentsFailed} had failed, and ` +
`${interruption.agentsInFlight} were still in flight — the failed run status is the deadline's, not a verdict on those agents.` +
(interruption.approvalWaitedMs !== undefined
? ` The longest single agent wait on a human approval reported for this run was ${interruption.approvalWaitedMs.toLocaleString()}ms` +
(interruption.approvalWaitedMs > interruption.timeoutMs
? ` — MORE than the ${interruption.timeoutMs.toLocaleString()}ms window itself, so the deployment's approval clock is` +
` counting time this run did not (an earlier leg's wait carried forward, or a mis-scaled figure). Read it as` +
` "waiting dominated", not as a slice of this window.`
: ` — that much of the ${interruption.timeoutMs.toLocaleString()}ms window was one agent BLOCKED on a person` +
` rather than working (the longest single wait, not a total).`) +
` Raising the timeout only helps if that approval gets answered.`
: ""));
}
export function formatWorkflowRun(run) {
const summary = summarizeWorkflowRun(run);
const done = run.agents.filter((a) => a.status === "completed" || a.status === "failed").length;
const failed = run.agents.filter((a) => a.status === "failed").length;
const allRows = run.agents.map((a, i) => ({ a, i }));
const overCap = allRows.length > WORKFLOW_AGENT_ROWS_MAX;
const keptRows = overCap
? (() => {
const urgent = allRows.filter((r) => r.a.status !== "completed");
const budget = Math.max(0, WORKFLOW_AGENT_ROWS_MAX - urgent.length);
const fill = allRows.filter((r) => r.a.status === "completed").slice(0, budget);
return [...urgent, ...fill].sort((x, y) => x.i - y.i);
})()
: allRows;
const agentRows = keptRows.map((r) => workflowAgentRow(r.a, r.i));
const finishedAgents = run.status === "failed" ? run.agents.filter((a) => a.status === "completed" && a.output !== undefined && a.output !== "") : [];
return {
content: JSON.stringify({
task_id: run.id,
runId: run.id,
type: "workflow",
status: run.status,
...(summary.name ? { name: summary.name } : {}),
...(summary.currentPhase ? { currentPhase: summary.currentPhase } : {}),
agents: { done, total: run.agents.length, failed },
agent_runs: agentRows,
...(overCap ? { agent_runs_note: `showing ${agentRows.length} of ${run.agents.length} agent rows (every non-completed row kept; completed rows trimmed).` } : {}),
startedAt: run.startedAt,
...(run.endedAt !== undefined ? { endedAt: run.endedAt, elapsedMs: run.endedAt - run.startedAt } : {}),
...(run.resume !== undefined
? {
resume: {
from_run_id: run.resume.fromRunId,
journal_entries: run.resume.journalEntries,
replayed: run.resume.replayed,
...(run.resume.divergedAtOrdinal !== undefined
? { replay_stopped_at: run.resume.divergedAtOrdinal, replay_stopped_reason: run.resume.divergedReason }
: {}),
},
}
: {}),
...(run.journalSkips !== undefined && run.journalSkips > 0 ? { journal_skipped: run.journalSkips } : {}),
...(run.status === "failed" && run.error ? { error: delimitUntrusted("workflow error", boundedRedactedSummary(run.error, 500)) } : {}),
...(run.errorCode !== undefined ? { errorCode: run.errorCode } : {}),
...(run.status === "completed" && run.result !== undefined
? { result: delimitUntrusted("workflow result", run.resultFull ?? run.result) }
: {}),
...(finishedAgents.length > 0
? {
partial_results: finishedAgents.slice(0, WORKFLOW_PARTIAL_RESULTS_MAX).map((a) => ({
agent: a.label,
output: delimitUntrusted(`workflow agent ${a.label} output`, a.output),
})),
...(finishedAgents.length > WORKFLOW_PARTIAL_RESULTS_MAX
? { partial_results_note: `showing ${WORKFLOW_PARTIAL_RESULTS_MAX} of ${finishedAgents.length} completed agent outputs; the full set remains retrievable from the workflow run record (getWorkflowRun / the workflow journal).` }
: {}),
}
: {}),
note: (run.status === "running"
? "still running — poll again shortly."
: run.status === "completed"
? run.result !== undefined
? "completed — `result` above is the workflow's return value."
: "completed — this run predates result persistence; its result was delivered on the completion notification."
: finishedAgents.length > 0
? `failed — see error. ${finishedAgents.length} agent(s) had already completed before the run ended; their outputs are inlined under \`partial_results\`.`
: "failed — see error.") +
timeoutInterruptionNote(summary.timeoutInterruption) +
budgetOvershootNote(summary.budgetOvershoot),
}),
details: {
task_id: run.id,
type: "workflow",
status: run.status,
retrieval_status: "success",
...(run.completionId !== undefined ? { completionId: run.completionId } : {}),
...(run.errorCode !== undefined ? { errorCode: run.errorCode } : {}),
details: summary,
},
};
}
export const DURABLE_AGENT_HEARTBEAT_MS = 60_000;
export const DURABLE_AGENT_HANDLE_RE = /^a[0-9a-f]{6,32}$/;
export const BG_AGENT_REAP_STOP_ERROR = "stopped (parent task ended or TaskStop)";
export function normalizeAgentName(s) {
return s
.normalize("NFKC")
.replace(/[\p{Cc}\p{Cf}]/gu, (t) => (/\s/.test(t) ? t : ""))
.trim()
.toLowerCase()
.replace(/\s+/g, "-");
}
export function editDistance(a, b) {
if (a === b)
return 0;
const m = a.length;
const n = b.length;
const d = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0)));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
}
}
}
return d[m][n];
}
export function closestName(query, candidates) {
let best;
let bestDist = 3;
const seen = new Set();
for (const c of candidates) {
if (seen.has(c))
continue;
seen.add(c);
if (Math.abs(c.length - query.length) > 2)
continue;
const dist = editDistance(query, c);
if (dist <= 2 && dist < bestDist) {
bestDist = dist;
best = c;
}
}
return best;
}
export function canAccess(handle, access) {
if (handle.scope === undefined || access.scope === undefined || access.scope !== handle.scope)
return false;
if (handle.owner === undefined)
return false;
if (access.owner === handle.owner)
return true;
if (handle.sessionScoped && access.sessionId !== undefined && access.sessionId === handle.owner)
return true;
if (handle.type === "workflow" && access.sessionId !== undefined && handle.originatingSessionId === access.sessionId)
return true;
if (handle.type === "background_agent" && access.sessionId !== undefined && handle.rootSessionId === access.sessionId)
return true;
return false;
}
export function commitCompletionIdIfEmpty(target, value) {
if (target.completionId === undefined)
target.completionId = value;
return target.completionId;
}