UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

69 lines (68 loc) • 4.1 kB
import type { BackgroundAgentRecord } from "./background-agent-store.js"; /** * The protocol's task states, as ONE list — the type is derived from it, so a value and its type can * never drift apart, and a consumer validating wire input iterates the same source the mapping does. */ export declare const A2A_TASK_STATES: readonly ["submitted", "working", "input-required", "completed", "canceled", "failed", "rejected", "auth-required", "unknown"]; /** One A2A task state (see {@link A2A_TASK_STATES}). */ export type A2ATaskState = (typeof A2A_TASK_STATES)[number]; /** * Durable row → protocol state. TOTAL and pure: every (status, stoppedBy) pair has an answer, and the * answer depends on nothing else. * * The four direct arms: * - `running` → `working`; * - `completed` → `completed`; * - `failed` → `failed`; * - `parked` → `input-required`. This engine's `parked` is durably suspended ON AN APPROVAL CHECKPOINT * — a NARROWING of the protocol's "waiting for the caller to supply something": the something is an * approval decision, and it is redeemed through the durable approval inbox, not by a message reply. * * `killed` splits on the attribution, because the protocol distinguishes what this engine records in a * second field: a stop by `user` or `parent` is a deliberate cancellation (`canceled`), while a * `system` stop, an unrecognized source, or NO attribution at all is not — those are terminations the * requester never asked for, and `canceled` would assert an intent nobody expressed. `failed` is the * honest terminal for them. * * FOUR PROTOCOL STATES HAVE NO ARM HERE, as a fact of the type rather than an omission: * - `submitted`, `rejected`, `auth-required` describe moments BEFORE a durable row exists (registration * and start are one step in this engine; rejection and an authorization demand are admission-time * outcomes of the serving layer, which has no row to project yet); * - `unknown` belongs to the READ-PROJECTION layer one level up — the poll face's `not_local` arm, * where a `running` row was written by ANOTHER engine instance and this instance genuinely cannot * speak to its outcome. That arm has no record-level answer to give and never calls this function; a * caller holding a record, by contrast, always has the state. Keeping the two apart is the boundary * this function is drawn on. * The switch is exhaustive over `status` with NO default arm, so tsc — not this comment — is what fails * when a sixth durable status is added. */ export declare function toA2ATaskState(record: Pick<BackgroundAgentRecord, "status" | "stoppedBy">): A2ATaskState; /** A reversal that keeps everything the protocol state carried. */ export interface A2ATaskStateReversalFaithful { lossy: false; status: BackgroundAgentRecord["status"]; } /** * A reversal that DROPPED something. `note` is required on this arm by construction: the whole purpose * of the discriminant is that a consumer which branches on `lossy` cannot reach the lossy case without * text describing what it lost. */ export interface A2ATaskStateReversalLossy { lossy: true; status: BackgroundAgentRecord["status"]; note: string; } /** The result of {@link fromA2ATaskState}: discriminated on `lossy`. */ export type A2ATaskStateReversal = A2ATaskStateReversalFaithful | A2ATaskStateReversalLossy; /** * Protocol state → durable row status. Nine onto five, so five arms lose information; each of those * says so (`lossy: true`) and names what it dropped, rather than returning a bare status that reads as * an exact translation. * * `lossy: false` holds EXACTLY for the states that survive `toA2ATaskState(fromA2ATaskState(s))` * unchanged — that biconditional is pinned as a property test (`test/a2a-task-state.test.ts`), so the * flag is tied to an observable fact and not to an author's intent. * * The exhaustive switch (no default arm) makes a newly-declared protocol state a compile error here. */ export declare function fromA2ATaskState(state: A2ATaskState): A2ATaskStateReversal;