UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

105 lines (104 loc) • 7.74 kB
/** * 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;