@sema-agent/core
Version:
Stateless, task-oriented AI agent core
110 lines (109 loc) • 5.33 kB
TypeScript
import type { MailboxPeerFromMode } from "../core/mailbox-store.js";
import type { CrossSessionInboundVerdict } from "./cross-session-judge.js";
import { type PeerIdleNoticeKind } from "./peer-notices.js";
import { type PeerClock } from "./peer-held-queue.js";
export interface PeerIdleSubscriber {
/** The requester's session id (reply routing). */
readonly requester: string;
/** The requester's display name as its record carried it (display only). */
readonly requesterName?: string;
/** The requester's self-attested mode class (gates the detail line through the parity judge). */
readonly fromMode?: MailboxPeerFromMode;
readonly selfSent: boolean;
readonly requestedAt: number;
}
export interface PeerIdleTargetNotice {
kind: Exclude<PeerIdleNoticeKind, "expired">;
finishedAt?: number;
/** `idle` only, and only when the parity judge DELIVERS for that subscriber (CC gates it on a
* pid-verified registry match + parity; sema has parity alone, §7). */
detail?: string;
}
export interface PeerIdleTargetSink {
/** Route one notice to one subscriber (the binding appends it to the requester's box). */
notify: (sub: PeerIdleSubscriber, notice: PeerIdleTargetNotice) => void;
/** The parity judge over the subscriber's attestation (detail gate, CC `p(d)`). */
judge: (sub: PeerIdleSubscriber) => CrossSessionInboundVerdict;
/** The held buffer's size: while > 0 the session is NOT idle to a subscriber (CC `c0e`). */
heldCount?: () => number;
/** The last turn's text, for the one-line detail (CC `getLastTurnText`). */
lastTurnText?: () => string | undefined;
/** Host announce: a subscription was recorded/refreshed (CC `onSubscribed`). */
onSubscribed?: (sub: PeerIdleSubscriber, outcome: "recorded" | "refreshed", live: number) => void;
}
export type PeerIdleSubscribeOutcome = "recorded" | "refreshed" | "full";
export interface PeerIdleTarget {
configure(sink: PeerIdleTargetSink): void;
configured(): boolean;
/** CC `tYt` (sans pid classes): same requester ⇒ refresh; table full ⇒ `"full"` (the caller answers
* `unavailable`). Already idle ⇒ the fire is (re)scheduled so a late subscriber hears it. */
subscribe(sub: PeerIdleSubscriber): PeerIdleSubscribeOutcome;
/** CC `A`: drop subscribers older than the TTL, silently. Returns how many. */
sweep(now?: number): number;
/** The session started working (a run began): cancel a pending fire. */
busy(): void;
/** The session's run ended with nothing queued: schedule the one-shot `idle` fire (debounced; parked
* while the held buffer is non-empty and re-checked). `finishedAt` = when the turn ended. */
idle(finishedAt: number): void;
/** A graceful session end: fire `exited` (or `idle`, when the session was in fact idle — CC `$Rt`). */
exited(finishedAt?: number): void;
list(): readonly PeerIdleSubscriber[];
size(): number;
}
export declare function createPeerIdleTarget(opts?: {
clock?: PeerClock;
tableCap?: number;
ttlMs?: number;
}): PeerIdleTarget;
export interface PeerIdleOutstanding {
/** The target's session id. */
readonly target: string;
/** The target's label as the requester knew it at request time (bounded by the producer). */
readonly label: string;
readonly requestedAt: number;
}
export interface PeerIdleRequesterSink {
/** The 12 h timer fired with no newer ask to the same target: the requester answers itself `expired`. */
expired: (o: PeerIdleOutstanding) => void;
}
export interface PeerIdleRequester {
configure(sink: PeerIdleRequesterSink): void;
configured(): boolean;
/** CC `rYt`: record an outstanding ask (keeps the last 3 per target; the table caps at 32 across
* targets — `cap` is the degraded send verdict). */
request(target: string, label: string): {
ok: true;
priors: number;
} | {
ok: false;
reason: "cap";
};
/** CC `oYt`: a notice arrived for a target — `idle`/`exited`/`expired` clear every ask to it,
* `unavailable` clears the oldest one. Returns the label the requester knew, for rendering. */
settle(target: string, kind: PeerIdleNoticeKind): {
cleared: number;
label?: string;
};
labelFor(target: string): string | undefined;
list(): readonly PeerIdleOutstanding[];
size(): number;
}
export declare function createPeerIdleRequester(opts?: {
clock?: PeerClock;
cap?: number;
priorsKept?: number;
ttlMs?: number;
}): PeerIdleRequester;
export interface PeerIdleMachine {
readonly key: string;
readonly target: PeerIdleTarget;
readonly requester: PeerIdleRequester;
}
export declare function peerIdleMachineKey(scope: string, sessionId: string): string;
/** The idle machine of a session (created on first use; `clock` honored at creation). */
export declare function peerIdleMachineFor(scope: string, sessionId: string, opts?: {
clock?: PeerClock;
}): PeerIdleMachine;
export declare function peerIdleMachineIfAny(scope: string, sessionId: string): PeerIdleMachine | undefined;
/** Test seam: forget every machine (timers are NOT settled — the crash form). Never called by production code. */
export declare function resetPeerIdleMachinesForTests(): void;