UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

123 lines (122 loc) • 7.44 kB
import { type MailboxAppendMessage } from "../core/mailbox-store.js"; import type { CrossSessionInboundVerdict } from "./cross-session-judge.js"; import type { RunnerDeps } from "../core/runner-deps.js"; /** The prepare-time refusal code of a malformed {@link RunnerDeps.principalInboxConsumer} (and of a gate * minting in a deployment that declared no consumer — {@link mintCrossPrincipalRecord}). */ export declare const PRINCIPAL_INBOX_CONSUMER_INVALID_CODE = "config.principal_inbox_consumer_invalid"; /** Who consumes a scope's principal inbox. `none` = the deployment declared no consumer (it mounts no * delivery gate): no lane anywhere. */ export type PrincipalInboxConsumer = { kind: "none"; } | { kind: "terminals"; } | { kind: "assistant"; handle: string; }; /** * THE consumer arbitration of a scope's principal inbox ({@link PRINCIPAL_INBOX_HANDLE}), * read off {@link RunnerDeps.principalInboxConsumer}: absent ⇒ `none` (no principal-inbox lane: a * deployment without a delivery gate claims no extra box, byte for byte as before); `"terminals"` ⇒ the * scope's terminal sessions, each draining it as a second box beside its own; `{ assistant: "<agent * handle>" }` ⇒ that assistant alone, and no terminal session drains the box (it does not claim and yield — * it never mints the lane). ANY other value is refused with {@link PRINCIPAL_INBOX_CONSUMER_INVALID_CODE}: * a malformed assistant configuration must never be read as "terminals", which would let terminal sessions * take the letters past the assistant. The one judgment every reader calls: the drain binding (which * lanes), the `ReadInbox` mount (its origin) and the record mint (a gate with no consumer). */ export declare function principalInboxConsumerOf(deps: Pick<RunnerDeps, "principalInboxConsumer">): PrincipalInboxConsumer; /** * The sender as the deployment's AUTHENTICATION context states it — an object the gate implementation * receives from its own auth layer, never a string a caller passes in. `scope` is the authenticated * principal's storage scope (it becomes the record's `fromScope`); `name` is the display label the * recipient sees. */ export interface OrgAuthenticatedSender { readonly scope: string; readonly name?: string; } /** What a sender asks the gate to carry. */ export interface CrossPrincipalBody { readonly content: string; /** The sender marked it urgent: the recipient presents it first; it never changes WHEN the recipient reads. */ readonly urgent?: boolean; } /** * The gate's refusal words (closed set). `not_member` — the two principals do not share an organization * (or the deployment cannot say they do); `rate_limited` — the cross-principal quota of the sender pair * or of the recipient is spent; `inbox_full` — the recipient's principal inbox is at its bound; * `recipient_tombstoned` — the recipient principal is being deleted (the store's own pre-delete code, * relayed). A refused delivery appends nothing anywhere. */ export declare const ORG_DELIVERY_REFUSAL_CODES: readonly ["org_delivery.not_member", "org_delivery.rate_limited", "org_delivery.inbox_full", "org_delivery.recipient_tombstoned"]; export type OrgDeliveryRefusalCode = (typeof ORG_DELIVERY_REFUSAL_CODES)[number]; export declare function isOrgDeliveryRefusalCode(value: unknown): value is OrgDeliveryRefusalCode; /** The gate's synchronous answer: a receipt naming the appended record, or a coded refusal. */ export type OrgDeliveryOutcome = { ok: true; receipt: { receiptId: string; seq: number; }; } | { ok: false; code: OrgDeliveryRefusalCode; message: string; }; /** * THE contract a deployment implements. `toPrincipal` is the recipient as the deployment addresses * principals (its own directory resolves it to the recipient's scope); an unknown recipient and one the * sender may not reach answer the same refusal bytes (`not_member`), so the gate is never an existence * probe. */ export interface OrgDeliveryGate { deliverCrossPrincipal(authedFrom: OrgAuthenticatedSender, toPrincipal: string, body: CrossPrincipalBody): Promise<OrgDeliveryOutcome>; } /** * The record the gate appends into `(recipientScope, PRINCIPAL_INBOX_HANDLE)` — the ONE mint, so every * gate implementation writes the same typed shape. Pure. Writes `peerMeta { kind: "peer_message", * fromScope, gateReceiptId }` (+ `urgent`) and the display `from`; writes NO `fromSession` (a sender's * session id addresses nothing in the recipient's scope), NO `fromMode` (the principal arm reads no * parity) and NO `hopChain` (the org gate owns the cross-principal quota; the same-principal admission * gate's chain does not apply). Throws when the anchors cannot be stored or the sender's scope cannot be * spelled on the envelope (the gate maps that to its own failure; nothing has been appended yet). * * `consumer` is the deployment's declared principal-inbox consumer ({@link principalInboxConsumerOf} over * the same `RunnerDeps` the deployment runs its legs with). A gate in a deployment that declared NONE is a * half-configured deployment: the append would hand the sender a durable receipt for a letter no leg will * ever read — so the mint REFUSES it with `config.principal_inbox_consumer_invalid`, at the first delivery, * before anything is appended (the same coded refusal a malformed declaration gets; core holds no seat for * the gate itself, so the delivery is the first moment both facts meet). */ export declare function mintCrossPrincipalRecord(input: { from: OrgAuthenticatedSender; body: CrossPrincipalBody; receiptId: string; sentAt: number; consumer: PrincipalInboxConsumer; }): MailboxAppendMessage; /** Does this stored record carry the gate's two anchors as a peer message — i.e. was it minted by * {@link mintCrossPrincipalRecord}? The principal inbox serves nothing else. */ export declare function isGateMintedRecord(meta: { kind?: string; fromScope?: string; gateReceiptId?: string; } | undefined): boolean; /** * How a scope's assistant treats one principal-inbox letter, from the recipient's judgment of it. * `triage_readonly` — read and triage it, read-only tools only (replies, writes and spending still wait * for the owner); `draft_for_approval` — triage and DRAFT, the draft goes to the owner with the letter * and nothing leaves until the owner approves; `decline` — do not read it, decline on the owner's behalf. * An unset setting never reaches `triage_readonly` for a cross-principal letter: the judge holds it. */ export declare const PRINCIPAL_INBOX_TRIAGE_POLICIES: readonly ["triage_readonly", "draft_for_approval", "decline"]; export type PrincipalInboxTriagePolicy = (typeof PRINCIPAL_INBOX_TRIAGE_POLICIES)[number]; export declare function principalInboxTriagePolicy(verdict: CrossSessionInboundVerdict): PrincipalInboxTriagePolicy; /** * The idempotency key of every side effect an assistant takes for one letter (a draft, a decline, an * audit row). The principal inbox is consumed at-least-once — a lease that lapses before its ack serves * the letter again — so a consumer that finds an effect under this key already written skips it. The key * is the box plus the letter's seq: seq is stable across re-leases for the life of the box. */ export declare function principalInboxSideEffectKey(seq: number): string;