UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

70 lines (69 loc) • 4.47 kB
import { readMailboxPeerMeta } from "../core/mailbox-store.js"; import { buildCrossSessionEnvelope, encodePeerAddress } from "./cross-session-envelope.js"; import { PRINCIPAL_INBOX_HANDLE } from "./cross-session-ref.js"; import { describeRejectedValue } from "../core/tool-search-setting.js"; export const PRINCIPAL_INBOX_CONSUMER_INVALID_CODE = "config.principal_inbox_consumer_invalid"; export function principalInboxConsumerOf(deps) { const raw = deps.principalInboxConsumer; if (raw === undefined) return { kind: "none" }; if (raw === "terminals") { throw consumerRefusal(`RunnerDeps.principalInboxConsumer "terminals" is not available yet: the shared held decision ledger is not yet available, so a letter a person refused in one terminal could be delivered by another. Declare { assistant: "<agent handle>" } or leave the seat absent.`, "terminals_unsupported"); } if (raw !== null && typeof raw === "object" && !Array.isArray(raw) && Object.getPrototypeOf(raw) === Object.prototype) { const keys = Reflect.ownKeys(raw); const handle = raw.assistant; if (keys.length === 1 && keys[0] === "assistant" && typeof handle === "string" && handle.trim() !== "") return { kind: "assistant", handle }; } throw consumerRefusal(`RunnerDeps.principalInboxConsumer must be absent (no principal-inbox consumer), "terminals" (the scope's terminal sessions drain it beside their own box) or { assistant: "<agent handle>" } ` + `(got ${describeRejectedValue(raw)}) — a malformed value is refused rather than read as "terminals", which would let terminal sessions take cross-principal letters past the assistant`, "malformed"); } function consumerRefusal(message, reason) { const e = new Error(message); e.code = PRINCIPAL_INBOX_CONSUMER_INVALID_CODE; e.detail = { reason }; return e; } export const ORG_DELIVERY_REFUSAL_CODES = Object.freeze(["org_delivery.not_member", "org_delivery.rate_limited", "org_delivery.inbox_full", "org_delivery.recipient_tombstoned"]); export function isOrgDeliveryRefusalCode(value) { return typeof value === "string" && ORG_DELIVERY_REFUSAL_CODES.includes(value); } export function mintCrossPrincipalRecord(input) { const { from, body, receiptId, sentAt, consumer } = input; if (consumer?.kind !== "terminals" && consumer?.kind !== "assistant") { throw consumerRefusal(`an organization delivery gate is minting a principal-inbox letter, but the deployment declares no principal-inbox consumer (RunnerDeps.principalInboxConsumer is absent): nobody would ever read it. Declare { assistant: "<agent handle>" }.`, "no_consumer"); } if (typeof body.content !== "string") throw new TypeError("mintCrossPrincipalRecord: body.content must be a string"); if (!Number.isFinite(sentAt)) throw new TypeError("mintCrossPrincipalRecord: sentAt must be a finite epoch-ms number"); const peerMeta = readMailboxPeerMeta({ kind: "peer_message", fromScope: from.scope, gateReceiptId: receiptId, ...(body.urgent === true ? { urgent: true } : {}) }); const name = typeof from.name === "string" && from.name !== "" ? from.name : undefined; buildCrossSessionEnvelope({ from: encodePeerAddress(name ?? "unknown"), ...(name !== undefined ? { fromName: name } : {}), fromScope: from.scope }, body.content); return { ...(name !== undefined ? { from: name } : {}), content: body.content, sentAt, peerMeta }; } export function isGateMintedRecord(meta) { return meta !== undefined && meta.kind === "peer_message" && meta.fromScope !== undefined && meta.gateReceiptId !== undefined; } export const PRINCIPAL_INBOX_TRIAGE_POLICIES = Object.freeze(["triage_readonly", "draft_for_approval", "decline"]); export function principalInboxTriagePolicy(verdict) { switch (verdict.verdict) { case "deliver": return "triage_readonly"; case "hold": return "draft_for_approval"; case "refuse": return "decline"; default: { const _exhaustive = verdict; void _exhaustive; throw new Error(`unreachable verdict ${JSON.stringify(verdict)}`); } } } export function principalInboxSideEffectKey(seq) { if (!Number.isInteger(seq) || seq < 1) throw new RangeError(`principalInboxSideEffectKey: seq must be a positive integer (got ${String(seq)})`); return `${PRINCIPAL_INBOX_HANDLE}#${seq}`; }