@sema-agent/core
Version:
Stateless, task-oriented AI agent core
123 lines (122 loc) • 7.44 kB
TypeScript
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;