UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

118 lines (117 loc) • 5.93 kB
import { type PromptEpochArtifact, type PublishedPromptArtifactEnvelope } from "./artifact.js"; export interface PromptArtifactStore { /** Verified read by digest — undefined on miss OR on any verification failure of stored bytes. */ get(artifactDigest: string, opts: { engineVersion: string; }): Promise<PublishedPromptArtifactEnvelope | undefined>; /** Idempotent immutable write (same digest ⇒ same bytes; the caller verified before putting). */ put(envelope: PublishedPromptArtifactEnvelope): Promise<void>; } export declare class MemoryPromptArtifactStore implements PromptArtifactStore { private readonly byDigest; get(artifactDigest: string, opts: { engineVersion: string; }): Promise<PublishedPromptArtifactEnvelope | undefined>; put(envelope: PublishedPromptArtifactEnvelope): Promise<void>; } /** `artifacts/<hex>.json` under a caller-owned directory; atomic tmp+rename writes (§9.2). */ export declare class FilePromptArtifactStore implements PromptArtifactStore { private readonly dir; constructor(dir: string); private fileFor; get(artifactDigest: string, opts: { engineVersion: string; }): Promise<PublishedPromptArtifactEnvelope | undefined>; put(envelope: PublishedPromptArtifactEnvelope): Promise<void>; } export type PromptSourceState = { state: "active"; artifactDigest: string; sourceRevision: string; } | { state: "disabled"; sourceRevision: string; }; export interface PromptSourceStateStore { load(): Promise<PromptSourceState | undefined>; save(state: PromptSourceState): Promise<void>; } export declare class MemoryPromptSourceStateStore implements PromptSourceStateStore { private state; load(): Promise<PromptSourceState | undefined>; save(state: PromptSourceState): Promise<void>; } /** `source-state.json` sibling of the artifact dir; atomic tmp+rename; malformed file = undefined * (stateless boot — conservative: no candidate until the next verified response). */ export declare class FilePromptSourceStateStore implements PromptSourceStateStore { private readonly file; constructor(dir: string); load(): Promise<PromptSourceState | undefined>; save(state: PromptSourceState): Promise<void>; } /** The narrow face the Runner consumes (RunnerDeps.promptSource) — resolution only, no publish. */ export interface PromptArtifactSource { /** The current validated candidate for NEW sessions, with its publish provenance * (undefined = bundled-only, today's shape). */ current(): { artifact: PromptEpochArtifact; sourceRevision: string; } | undefined; /** Resolve a PINNED digest (resume path). Undefined = unavailable — the caller fails loud. */ get(artifactDigest: string): Promise<PromptEpochArtifact | undefined>; /** RB-31 — the tri-state the adoption boundary keys on: `"disabled"` = an EXPLICIT * validated revocation (applyDisabled) ⇒ pinned sessions roll back to bundled at the next * compaction; `"active"` = a validated candidate is live; `"unavailable"` = no verified state * (never-configured face, store-miss boot) ⇒ conservative restate — an outage must never strip * policy, only an explicit revocation does. Optional (older sources read as "unavailable"). */ sourceState?(): "active" | "disabled" | "unavailable"; } /** * The candidate state machine (§9.2 full pipeline). Publish-side entry points: * - `applySnapshot(raw)` — a fetched effective-snapshot body: verify → store.put → persist * active state → atomic in-memory swap. Any verification failure leaves EVERYTHING unchanged. * - `applyDisabled(sourceRevision)` — an explicit, successful disable/delete response: persist * disabled state + drop the candidate. (Network failure/304 = simply don't call anything.) * - `restore()` — boot: re-resolve persisted active state through the verified store; a miss or * verify failure boots stateless (no candidate) without touching the persisted record. */ export declare class CenterPromptSource implements PromptArtifactSource { private readonly store; private readonly stateStore; private readonly engineVersion; private candidate; private state; /** Hardening 2026-07-31 — serialization tail for the three STATE-MUTATING entry points. Each of * them is a multi-await sequence ending in a synchronous swap of (candidate, state); with two in * flight, an interleaving where B's `stateStore.save` resolves and swaps before A's leaves the * persisted record naming B's digest while the in-memory candidate names A's — `current()` then * serves A to every new session until the next `restore()` silently switches to B. `CenterPromptSource` * is a public export driven by a caller's poll loop, so two overlapping applies are an ordinary * schedule, not an exotic race. Serializing costs nothing (these are publish-rate operations) and * makes the durable record and the in-memory candidate agree by construction. */ private tail; /** Run `fn` after every previously-queued mutation settles (success OR failure — a failed apply * must not wedge the queue). */ private exclusive; constructor(opts: { store: PromptArtifactStore; stateStore?: PromptSourceStateStore; engineVersion: string; }); current(): { artifact: PromptEpochArtifact; sourceRevision: string; } | undefined; get(artifactDigest: string): Promise<PromptEpochArtifact | undefined>; restore(): Promise<void>; sourceState(): "active" | "disabled" | "unavailable"; applySnapshot(raw: unknown): Promise<{ ok: true; artifactDigest: string; } | { ok: false; code: string; reason: string; }>; applyDisabled(sourceRevision: string): Promise<void>; }