UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

189 lines (188 loc) • 11 kB
import { uuidv7 } from "../../internal/harness.js"; import { snapshotActorAssertion } from "../../internal/llm.js"; import { MAX_STEER_INPUT_ID_CHARS } from "../checkpoint-store.js"; import { formatHookFeedback, hookSeatExpiredError, runHookSeat } from "../hooks.js"; import { buildHumanInputEvent, frameMidTurnUserInput, projectHumanInput } from "../human-input-projection.js"; import { isSystemInjectionPriority, steerPriorityOf, SYSTEM_INJECTION_PRIORITIES } from "../task-notification.js"; import { deliverEngineNotice } from "../types.js"; import { inlineUntrusted, sanitizeUntrustedText } from "../untrusted-text.js"; import { announceAcceptance, armAcceptance, nextHumanInputSeq, sameAcceptedSteerInput } from "./steer-admission.js"; import { asThrownError } from "../thrown-value.js"; export function streamSteerVerb(input) { const { spec, internals, queue, acceptedSteerInputs, live, ready, orTimeout, readyTimeoutMs: READY_TIMEOUT_MS, steeringError, runner } = input; let steerChain = Promise.resolve(); const steer = async (text, options) => { const trusted = options?.trusted ? true : false; if (trusted && sanitizeUntrustedText(text) !== text) { throw steeringError("trusted steering text must not contain a </system-reminder> tag", "steering.invalid_content"); } const inputId = options?.inputId; if (inputId !== undefined) { if (typeof inputId !== "string") { throw steeringError("inputId must be a string when supplied", "steering.invalid_content"); } if (inputId === "" || inputId.length > MAX_STEER_INPUT_ID_CHARS) { throw steeringError(`inputId must be a non-empty string of at most ${MAX_STEER_INPUT_ID_CHARS} characters`, "steering.invalid_content"); } } const priorityIn = options?.priority; if (priorityIn !== undefined && !isSystemInjectionPriority(priorityIn)) { throw steeringError(`priority must be one of ${SYSTEM_INJECTION_PRIORITIES.join("/")} when supplied`, "steering.invalid_content"); } const priority = steerPriorityOf({ priority: priorityIn }); const actorIn = options?.actor; const actor = actorIn === undefined ? undefined : snapshotActorAssertion(actorIn); const projected = projectHumanInput({ text, actor, source: "steer" }); const effectiveInputId = typeof inputId === "string" ? inputId : uuidv7(); const parkRecord = { text, trusted, inputId: effectiveInputId, ...(priorityIn !== undefined ? { priority } : {}), ...(actor !== undefined ? { actor } : {}), }; let payload; let mintsAFrame; let replay; const publishQueued = (h) => { const identity = internals?.parentToolCallId !== undefined ? { parentToolCallId: internals.parentToolCallId, ...(spec.taskId !== undefined ? { sourceTaskId: spec.taskId } : {}) } : {}; queue.push({ ...buildHumanInputEvent({ carrier: "steer", source: "steer", delivery: "queued", sessionSeq: nextHumanInputSeq(h.harness), inputId: effectiveInputId, ...(actor !== undefined ? { actor } : {}), ...(actor?.issuer !== undefined ? { issuer: actor.issuer } : {}), ...(spec.principal !== undefined ? { principal: spec.principal } : {}), }), eventId: uuidv7(), ...identity, }); return identity; }; const noteAccepted = () => { if (!mintsAFrame) return; if (typeof inputId === "string") acceptedSteerInputs.set(inputId, replay); announceAcceptance(parkRecord); }; const deliver = async () => { if (live.resultValue) throw steeringError("the task has already finished"); const h = live.handle ?? (await orTimeout(ready)); if (!h) throw steeringError("the task is not running"); payload = trusted ? formatHookFeedback(projected, h.reminderMark) : frameMidTurnUserInput(projected); mintsAFrame = payload.trim().length !== 0; replay = { payload, trusted, priority, ...(actor !== undefined ? { actor } : {}) }; if (typeof inputId === "string") { const prior = acceptedSteerInputs.get(inputId); if (prior !== undefined) { if (live.resultValue !== undefined || h.loop.ended) throw steeringError("the task is no longer running"); if (!sameAcceptedSteerInput(prior, replay)) { throw steeringError("a different steering instruction was already accepted under this inputId — re-issue this one with a fresh inputId " + "(an identical payload would have been an idempotent retry)", "steering.duplicate_input_id"); } return; } } if (live.resultValue !== undefined || h.loop.ended) throw steeringError("the task is no longer running"); if (mintsAFrame) { const screen = (spec.hooks ?? runner.deps.hooks)?.userPromptSubmit; if (screen !== undefined) { let decision; try { const seat = await runHookSeat("userPromptSubmit", { timeoutMs: h.hookTimeoutMs, signal: h.abortController.signal, abortEnds: true }, (sig) => screen(text, { identity: h.hookIdentity, permissionMode: h.permissionMode.observed, signal: sig, source: "steer", inputId: effectiveInputId, ...(actor !== undefined ? { actor: snapshotActorAssertion(actor) } : {}) })); if (seat.expired) { if (seat.cause === "timeout") { try { runner.deps.onError?.(hookSeatExpiredError("userPromptSubmit", h.hookTimeoutMs, seat.cause, "the steering input was NOT accepted (fail-closed) and the caller was refused typed"), { phase: "hook", sessionId: h.sessionId }); } catch { } } throw steeringError(seat.cause === "timeout" ? `the deployment's userPromptSubmit hook did not answer within its ${h.hookTimeoutMs}ms bound while screening this steering input; the input was NOT accepted (fail-closed)` : `the task was cancelled while the deployment's userPromptSubmit hook was still screening this steering input; the input was NOT accepted (fail-closed)`, "steering.blocked_by_hook"); } decision = seat.value; } catch (hookErr) { if (hookErr instanceof Error && hookErr.code === "steering.blocked_by_hook") throw hookErr; const err = asThrownError(hookErr); try { runner.deps.onError?.(err, { phase: "hook", sessionId: h.sessionId }); } catch { } throw steeringError(`the deployment's userPromptSubmit hook crashed while screening this steering input (${inlineUntrusted(err.message)}); the input was NOT accepted (fail-closed)`, "steering.blocked_by_hook"); } if (decision?.block !== undefined && decision.block !== "") { throw steeringError(`the deployment's userPromptSubmit hook blocked this steering input: ${inlineUntrusted(decision.block)}`, "steering.blocked_by_hook"); } if (decision?.additionalContext !== undefined && decision.additionalContext !== "") { payload = `${formatHookFeedback(decision.additionalContext, h.reminderMark)}\n\n${payload}`; } } } const injectFramed = async () => { const noteOptions = { provenance: "engine-note", callerAuthored: true, parkRecord, ...(actor !== undefined ? { actor } : {}) }; if (mintsAFrame) armAcceptance(parkRecord, () => publishQueued(h)); if (priority === "later") { await h.harness.followUp(payload, noteOptions); noteAccepted(); return; } const frame = await h.harness.steer(payload, { ...noteOptions, ...(priority === "now" ? { immediate: true } : {}) }); noteAccepted(); if (priority === "now" && frame !== undefined && h.harness.interruptTurn(frame)) { deliverEngineNotice(runner.deps.onNotice, { code: "task.turn_interrupted", message: "a caller-provenance steer with priority \"now\" interrupted the running turn: in-flight work was cut at " + "a manufactured boundary (finished tool calls keep their real results; never-started ones settle as " + "interrupted) and the run continues with the steer at the queue head.", detail: { inputId: effectiveInputId, sessionId: h.sessionId, runId: h.runId, ...(actor?.id !== undefined ? { actorId: actor.id } : {}), ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), }, }); } }; try { await injectFramed(); return; } catch (e) { if (!(e instanceof Error && e.code === "invalid_state")) throw e; } const birthDeadline = Date.now() + READY_TIMEOUT_MS; while (live.resultValue === undefined && !h.loop.ended && Date.now() < birthDeadline) { try { await injectFramed(); return; } catch (e2) { if (!(e2 instanceof Error && e2.code === "invalid_state")) throw e2; } await new Promise((r) => setTimeout(r, 10)); } throw steeringError("the task is no longer running"); }; const p = steerChain.then(deliver); steerChain = p.then(() => undefined, () => undefined); return p; }; return { steer }; }