@sema-agent/core
Version:
Stateless, task-oriented AI agent core
105 lines (104 loc) • 7.74 kB
TypeScript
/**
* PrepareTask's QUESTION-FACE phase, verbatim from the driver: the leg's ONE checkpoint-store
* read, the durable mandate as it binds at the question seat, the content-ask binding table (with the leak
* bound the routing lane checks against — the constant below), the stranded-answer disclosure and the leg-end
* settle, the three-way mount decision (live face / durable face / override), and the AskUserQuestion mount
* with its redeemed-approval, posture, fallback and synthetic-continuation arms. The interface is the
* dependency list the segment had implicitly.
*
* SYNCHRONOUS FUNCTION, SYNCHRONOUS CALL: the stretch has no await, so the phase adds no yield between the
* offload-reader mount before it and the scheduler mount after it (a stretch with no await
* is extracted as a sync function — an `async` wrapper would open a microtask window). Read-stability of the
* host handles for the call: {@link RunInternals} (`@contract prepare.deps-read-stable`).
*
* One seat is a LIVE GETTER: the inherited-unavailable marker set is declared by the driver's
* gate-fold section AFTER this phase and read per call at RUN time, so it enters as `() => ReadonlySet` — a
* read before the driver declares it throws (temporal dead zone), which is the fail-loud an uninitialized
* live read owes.
*/
import type { AgentTool } from "../../internal/harness.js";
import { type OnQuestion } from "../ask-question.js";
import type { CheckpointStore } from "../checkpoint-store.js";
import type { RunnerDeps, RuntimeCaps, TaskSpec } from "../types.js";
import type { PrepareResume } from "./contracts.js";
import { type ContentAskBinding } from "./content-ask-bindings.js";
import { RosterBuilder } from "../tool-roster.js";
export { CONTENT_ASK_BINDING_CAP, type ContentAskBinding } from "./content-ask-bindings.js";
export interface PrepareQuestionFaceInput {
/** borrowed-readonly — the run's frozen question seat (spec > deps), resolved once at the top of prepare;
* the mount predicate and the live-face derivation both read this one value. */
frozenOnQuestion: OnQuestion | undefined;
/** borrowed-readonly — the REBOUND spec. Read: `durableApproval` (the durable face's opt-in), `interactiveTools`
* (the override), `principal` (the tool's source identity), `interactiveQuestionFallback` (restores the
* continuation under a resolved posture). Never mutated. */
spec: Pick<TaskSpec, "durableApproval" | "interactiveTools" | "principal" | "interactiveQuestionFallback">;
/** borrowed-readonly — the deployment seat `onError` (the stranded-answer and synthetic-continuation operator
* lines; swallow-guarded). */
deps: Pick<RunnerDeps, "onError">;
/** borrowed-readonly — THE leg's checkpoint seat (minted once by the driver after the config doors); the mount face, the routability predicate, the manifest and suspendAsk all read this one value through the result. */
checkpointStore: CheckpointStore | undefined;
/** borrowed-readonly — the principal's resolved runtime caps as of this point (`forceDurableGate` is the
* mandate that outranks a live face only where a store exists to park the question). */
runtimeCaps: Pick<RuntimeCaps, "forceDurableGate"> | undefined;
/** borrowed-readonly — the resolved interaction posture (the door's value), threaded to the tool so both
* no-answer arms harden under it. */
resolvedInteractionPosture: "interactive" | "headless" | undefined;
/** borrowed-readonly — the durable-resume carrier; only the redeemed content-ask call id and its questions
* hash are read (the one call an operator's approval was spent on). */
resume: Pick<PrepareResume, "redeemedContentAskCallId" | "redeemedContentAskQuestionsHash"> | undefined;
/** borrowed-readonly — the acquired session id (the tool's source task id; operator-line context). */
sessionId: string;
/** borrowed-mutable — the run's roster builder (identity is the contract). Writer here: ONE mount (the question
* tool) when the mount predicate holds; `contentAskRoutable` reads `includes` per call against the LIVE
* read face (the roster is edited in place after the mount). */
roster: RosterBuilder;
/** borrowed-readonly — LIVE GETTER over the inherited-unavailable marker set (declared by the driver's
* gate-fold section after this phase; `has` only, per call at run time). See the module header. */
inheritedUnavailableAsks: () => ReadonlySet<string>;
}
export interface PrepareQuestionFaceResult {
/** borrowed-readonly — THE leg's store snapshot (`resolveCheckpointStore(spec, deps)`): the mount face, the
* routability predicate, the manifest and suspendAsk all read this one value. */
checkpointStore: CheckpointStore | undefined;
/** borrowed-readonly — the run's live question face through the shared predicate (the reserved resume
* placeholder is a function and is NOT a live face), or undefined. */
liveQuestionFace: OnQuestion | undefined;
/** borrowed-mutable — the one-shot answer binding table for questions the GATE routed (scope = this leg).
* Writers (R-3): the routing lane (`set`, cap-checked against {@link CONTENT_ASK_BINDING_CAP} before every
* insertion), the mounted face here (`delete`, one-shot redemption or a mismatched-batch drop), the settle
* here (`clear`). Identity is the contract (the routing lane and the face share this exact map). */
contentAskBindings: Map<string, ContentAskBinding>;
/** owned — THE routability predicate for a content ask (per call, against the live roster and the marker set). */
contentAskRoutable: (toolCallId: string) => boolean;
/** borrowed-mutable — late answers noted before the leg settles. Writer: the routing lane's delivery-race
* watcher (`push`); reader: the settle here (merged into the result disclosure by delivery id). */
lateStrandedAnswers: Array<{
deliveryId: string;
toolCallId: string;
}>;
/** owned — THE disclosure lane for an answer a person gave that no call could collect (swallow-guarded). */
discloseStrandedAnswers: (records: ReadonlyArray<{
deliveryId: string;
toolCallId: string;
}>, why: string) => void;
/** owned — the leg-end settle (`Prepared.settleContentAskBindings`): discloses surviving answers, clears the
* table, returns the records the result carries. */
settleContentAskBindings: () => ReadonlyArray<{
deliveryId: string;
toolCallId: string;
}>;
/** owned — whether a durable delivery face exists (store wired ∧ (durableApproval ∨ forceDurableGate));
* the manifest's composition-lie detector reuses this exact value. */
durableQuestionFace: boolean;
/** owned — the face the tool was mounted with: the gate-bound answer for THIS call id if one exists (one-shot),
* else the live face itself; the raw frozen seat when no live face exists. */
mountedQuestionFace: OnQuestion | undefined;
/** owned — whether THIS prepare mounted the first-party question tool (the routing lane keys on it, not on
* the reserved name). */
questionToolMounted: boolean;
/** borrowed-readonly — the mounted tool object itself (the exact roster member), so the routing lane can
* validate a batch against the schema the executing call is re-checked against; undefined when not mounted. */
mountedQuestionTool: AgentTool | undefined;
}
/** The M7 phase body — prepareTask's question-face stretch, verbatim (see the module header). */
export declare function prepareQuestionFace(input: PrepareQuestionFaceInput): PrepareQuestionFaceResult;