UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

111 lines (110 loc) • 8.01 kB
/** * The run body's LEG (R13), verbatim from `Runner.runLocked`: the one `harness.prompt` of the leg and * everything that decides whether it is issued. Two legs behind one try / catch / finally: the RESUME leg (the walltime * and governance-window entrance claims, the gated decision's application, the exhaustion arms, the continuation prompt's * assembly — orphaned background tasks, the git frame, the parked steer queue and the wake message each under its own trust * framing and redelivery screen — and the prompt itself) and the OBJECTIVE leg (the UserPromptSubmit screen, the git * frame's primary delivery, the first-frame listings, the pre-call budget / token / entry-governance gates, the image * conversion, the objective's human-input frame and the prompt itself). The resumed decision's application * (`applyResumeDecision`) and the gated call's resolver (`resolvePendingCall` — the post-CAS belts, the execution record) * are the lane's own module-private functions since S7, verbatim from the Runner's private methods: the resume * leg is their one caller, and a sibling file would be a lane-to-lane reach (the S6 legs' reading). The tail captures the abort facts; the catch * re-throws the resume-equivalent env failures after a bounded teardown and adopts everything else as the leg's throw; * the finally releases the timer, the diagnostics, the signal, the content-ask bindings, flips the notification lane * and releases the four subscriptions in their order. * * Async (twenty first-level awaits, the same awaits at the same positions). It ends by entering the terminal adoption * (`next`) in its LAST continuation — the adoption's first read sits on the tick the finally ran on, as it did in the * one-function body — and returns what it returns. The two legs stay ONE function on purpose: splitting them into two * async functions (the design's "internal split") costs a microtask on the leg → tail edge, and the tail's first reads * there — `abortController.signal.aborted`, `loopLatch.userInterrupted` — are host-mutable attribution facts a late read * would mis-sign (the adversarial r2 HIGH-1 finding recorded on them); the forwarded-frame depth ratchet measured the * extra hop (58 → 59) when the split was tried. */ import type { AssistantMessage } from "../../internal/llm.js"; import { type ActorAssertion } from "../checkpoint-store.js"; import type { PushQueue } from "../push-queue.js"; import { type TaskEvent, type TaskSpec } from "../types.js"; import { type WriteFamily } from "./turn-attachments.js"; import type { NotificationLaneBindings, Prepared, ResumeRun, RunnerDepsSeat, RunState } from "./contracts.js"; export interface RunLegInput<R> { /** borrowed-readonly — the task spec: the objective, the images, the hooks slot, the principal, the task id. */ spec: TaskSpec; /** borrowed-mutable — the run's event queue: the human-input frames, the git echo frames, the listing echo frames. */ queue: PushQueue<TaskEvent>; /** borrowed-mutable — the leg's prepared seat: the harness is prompted, the blocked / git / listing refs written, the abort controller aborted and the signal released in the finally. */ prepared: Prepared; /** borrowed-readonly — the resume plan when this leg resumes a checkpoint: chooses the resume leg; its delivery bits are written. */ resume: ResumeRun | undefined; /** borrowed-mutable — the run's mutable state: the limit flags and the platform terminal are written by the entrance gates. */ rs: RunState; /** borrowed-readonly — the trusted spawn-side fact the rejection boundary reads (a delegated child's bare refusal halts no batch). */ isDelegatedChild: boolean; /** borrowed-readonly — the API-boundary attribution snapshot of the objective's actor (the objective leg's speaker envelope). */ entryActor: ActorAssertion | undefined; /** borrowed-readonly — the effective walltime window (undefined = unbounded): `<= 0` refuses the first turn. */ effectiveTimeoutMs: number | undefined; /** borrowed-mutable — the hard walltime timer: claimed (`fired`) and cleared by the exhausted arms and the finally. */ timeout: { fired: boolean; clear: () => void; }; /** borrowed-readonly — the content push (the resumed batch's frames ride it with the leg's identity). */ pushContent: (e: TaskEvent) => void; /** borrowed-readonly — the run's event-identity mint. */ ident: () => { eventId: string; parentToolCallId?: string; sourceTaskId?: string; }; /** borrowed-readonly — the `message_committed` mint (the resumed batch's re-applied tool results). */ emitCommitted: (entryId: string, role: "user" | "assistant" | "toolResult", toolCallId?: string) => void; /** borrowed-readonly — the write-family resolver (the resume-executed gated write's attachment anchor). */ writeFamilyOf: (name: string) => WriteFamily | undefined; /** borrowed-mutable — the observed tool-start ids: the resume-executed gated call registers itself here. */ startedToolCallIds: Set<string>; /** borrowed-readonly — the harness-handlers lane's workspace-move announcer (the approved `cd`'s frame). */ announceWorkspaceMove: () => void; /** borrowed-readonly — the brain-sinks composition the leg's prompt runs under. */ withBrainSinks: <T>(fn: () => Promise<T>) => Promise<T>; /** borrowed-readonly — the git lane's parked-mirror flush, settled after the prompt (single-turn legs reach no boundary). */ flushGitMirror: () => Promise<void>; /** borrowed-mutable — the loop-liveness latch: `ended` flips when the prompt settles (or a no-prompt / throw arm is taken); `userInterrupted` is snapshotted. */ loopLatch: { ended: boolean; readonly userInterrupted: boolean; }; /** borrowed-mutable — the notification lane's bindings seat: `live` flips false in the finally (every later notification parks). */ notificationLane: NotificationLaneBindings; /** borrowed-readonly — the notification lane's subscription release, called in the finally right after the flip. */ unsubscribeTaskNotifications: () => void; /** borrowed-readonly — the git lane's owed-frame retry release, called in the finally first of the three. */ unsubGitRetry: () => void; /** borrowed-readonly — the git lane's boundary-handler release, called second. */ unsubBoundary: () => void; /** borrowed-readonly — the git lane's harness-event release, called third. */ unsub: () => void; /** borrowed-readonly — the Runner's deployment deps, read LIVE (`onError`, `hooks`, `onNotice`, `allowImageUrl`; the resumed * decision's resolver files its execution record's word through `onNotice` too); the one home of why it is a seat and not a * captured object is {@link RunnerDepsSeat}. */ runner: RunnerDepsSeat; /** borrowed-readonly — the terminal adoption, entered in this lane's last continuation with the leg's facts. */ next: (leg: RunLegResult) => Promise<R>; } /** The leg's facts — what the terminal adoption and the settle read. */ export interface RunLegResult { /** The final assistant message; undefined on every no-prompt arm (blocked, refused, exhausted). */ final: AssistantMessage | undefined; /** What the leg threw (undefined on a clean end); the terminal adoption may lift a typed cause over it. */ threw: unknown; /** The live-abort flag, captured before the finally's unconditional release-abort. */ abortedLive: boolean; /** The snapshot of `loopLatch.userInterrupted`, taken with `abortedLive`. */ userInterruptedLive: boolean; /** The answered-but-never-collected deliveries the finally settled. */ strandedHumanAnswers: ReadonlyArray<{ deliveryId: string; toolCallId: string; }>; } export declare function runLeg<R>(input: RunLegInput<R>): Promise<R>;