@sema-agent/core
Version:
Stateless, task-oriented AI agent core
177 lines (176 loc) • 13.3 kB
TypeScript
/**
* PrepareTask's PARK CLOSURE and CONTENT-ASK ROUTING closure, verbatim from the driver's
* gate-machinery block: `resolveContentAsk` (the question face's delivery race, the binding table's cap and redemption
* identity, the stranded-answer disclosure) and `suspendAsk` (the sync-first decline and the park-priority mandates, the
* args capture / fidelity projection / re-adjudication steps, the pause-then-cut fence, the gate-kind choice, the row
* mint, the commit through the saga and the post-commit publication). The one module-scope helper only this segment
* threw (`ParkRefusal`) moved with it. The interface is the dependency list the segment had implicitly
* — the two closures used to capture every seat here off the driver's scope, which is the capture surface this
* decomposition exists to make explicit.
*
* The thirteen-argument park closure is unchanged: twelve positional seats then the structural {@link AskCarry}, the
* shape the gate's one invocation site (`parkWith`) holds. The ask's origin word, class pair and denial-limit fallback
* ride the carry onto the row — this phase mints no origin or class, changes no word, adds no arm to the closed set.
*
* SYNCHRONOUS FUNCTION, SYNCHRONOUS CALL: the stretch has no await of its own (both closures run per tool call), so the
* phase adds no yield between the boundary parks before it and the gate station after it.
* Read-stability of the host handles for the call: {@link RunInternals} (`@contract prepare.deps-read-stable`).
*
* Both closures exist exactly when the ask lane and the saga do (the same predicate) — an absent lane answers two absent
* closures, so the orchestrator grows no branch.
*/
import type { AgentHarness, AgentMessage, AgentTool, ExecutionEnv } from "../../internal/harness.js";
import { type OnQuestion } from "../ask-question.js";
import { type CheckpointStore } from "../checkpoint-store.js";
import type { SessionStore } from "../session.js";
import { type OnAsk, type ToolCallRequest, type ToolPolicy } from "../tool-policy.js";
import { type ToolResultStore } from "../tool-result-store.js";
import type { RunnerDeps, RuntimeCaps, ShellGateDoctrine, TaskSpec } from "../types.js";
import type { CwdRef } from "../../tools/fs/fs-shared.js";
import type { AdvertisedWritableDirs } from "./advertised-writable-dirs.js";
import { type ContentAskBinding } from "./content-ask-bindings.js";
import type { AskLane, IrreversibilityTier, ParkAsk, Prepared, SuspendSaga } from "./contracts.js";
import type { createRuleOffersOf } from "./permission-rule-lanes.js";
import { type ToolRosterDeltaSeat } from "../tool-roster.js";
import { type SkillGrantSource } from "../skill-grant.js";
export interface PrepareParkAskInput {
/** borrowed-readonly — the in-stream ask lane, or undefined when the gate machinery is inactive (then both closures are
* absent). Read: `composedCallSignal` (the fence and the delivery race), `approvalPreviewOf` (the row's preview). */
askLane: AskLane | undefined;
/** borrowed-readonly — the durable settlement saga, or undefined (same law). Read: `suspendableEnv` / `parkOnlyRemoteEnv` /
* `parkOnlyHandle` (the pause target), `suspendLoopCapHit`, `serializeCheckpointState`, `inFlightSpendMicroUsd`,
* `compensatePausedVM` (the pause-then-cut arm), `commitSuspendSaga` (the point of no return). */
saga: SuspendSaga | undefined;
/** borrowed-readonly — the REBOUND spec. Read: `principal` (the question's source identity, the unattended re-scope, the
* row's identity record), `retainBackgroundProcesses` (the park-only sweep gate). Never mutated. */
spec: Pick<TaskSpec, "principal" | "retainBackgroundProcesses">;
/** borrowed-readonly — the deployment's error face, as a Pick over the SAME `deps` object (receiver preserved for
* `deps.onError?.()`): the park refusals' disclosure, the pin failure. */
deps: Pick<RunnerDeps, "onError">;
/** borrowed-readonly — the acquired session id (the question's `sourceTaskId`, the row's session and source, every tag). */
sessionId: string;
/** borrowed-readonly — the leg's checkpoint store, or undefined (then `suspendAsk` is absent — the park predicate's store
* term). Read: the declared fidelity; the saga writes to it. */
checkpointStore: CheckpointStore | undefined;
/** borrowed-readonly — the leg's delta seat (B18): the park reads the gated tool's identity off its
* CURRENT roster snapshot so the row carries who the action was minted against. */
toolRosterDeltas: ToolRosterDeltaSeat;
/** borrowed-readonly — the wiring-manifest phase's park predicate (`parkLane.effective`): arms `suspendAsk`. */
parkLaneArmed: boolean;
/** borrowed-readonly — the question-face phase's routability predicate (the mounted question tool is the engine's own,
* resolved against the LIVE roster per call). */
contentAskRoutable: (toolCallId: string) => boolean;
/** borrowed-readonly — the live question face, or undefined (then a content ask answers `unavailable`). */
liveQuestionFace: OnQuestion | undefined;
/** borrowed-readonly — the mounted AskUserQuestion tool, or undefined; only its `parameters` schema is read (the pre-delivery
* validation). */
mountedQuestionTool: AgentTool | undefined;
/** borrowed-mutable — the content-ask binding table (the question-face phase mints it). Writer here: `bindOutcome`
* (`set`, cap-checked before every insertion); the mounted tool and the leg-end settle consume. */
contentAskBindings: Map<string, ContentAskBinding>;
/** borrowed-mutable — the stranded late answers (the question-face phase mints it). Writer here: the delivery race's
* aborted arm (`push`) when an answer lands after the race. */
lateStrandedAnswers: Array<{
deliveryId: string;
toolCallId: string;
}>;
/** borrowed-readonly — the question-face phase's stranded-answer disclosure (called on the same late arm). */
discloseStrandedAnswers: (records: ReadonlyArray<{
deliveryId: string;
toolCallId: string;
}>, why: string) => void;
/** borrowed-readonly — the run's own approver seat, or undefined (the sync-first decline's live-approver test). */
onAsk: OnAsk | undefined;
/** borrowed-readonly — the resolved per-principal entitlements, or undefined (`forceDurableGate` keeps park priority). */
runtimeCaps: RuntimeCaps | undefined;
/** borrowed-readonly — the inherited-unavailable marker set; read only here (`has`: park priority and the row's
* `inheritedUnresolved`). The ask lane consumes, the gate station sweeps. */
inheritedUnavailableAsks: ReadonlySet<string>;
/** borrowed-readonly — the policy the projection re-adjudication consults when the codec moved the value, or undefined
* (then a moved projection refuses the park). Called at park time. */
basePolicyForResumeEdit: ToolPolicy | undefined;
/** borrowed-readonly — the per-leg-immutable budget snapshot the re-adjudication carries (same object as the main gate). */
budgetSnapshot: NonNullable<ToolCallRequest["budget"]>;
/** borrowed-readonly — the hand's live tracked cwd, or undefined; `current` is read at park time for the re-adjudication.
* Not written here. */
handsCwdRef: CwdRef | undefined;
/** borrowed-readonly — the SAME seat the live gate stamps from ({@link import("./prepare-ask-lane.js").PrepareAskLaneInput.advertisedWritableDirs}): a parked call's projection re-adjudication asks one question about one call, so it must ask it against the same admitted list. Read at park time, not written here. */
advertisedWritableDirs: Pick<AdvertisedWritableDirs, "roots"> | undefined;
/** borrowed-readonly — the run's offload store, or undefined (a volatile store holding results refuses the park). */
offloadStore: ToolResultStore | undefined;
/** borrowed-readonly — the per-task owned env, or undefined (a non-remote per-task env refuses the park). Never destroyed. */
ownedEnv: ExecutionEnv | undefined;
/** borrowed-readonly — the restore-incomplete adapter's missing members, or undefined (names the actual gap in the refusal). */
incompleteSuspendAdapter: readonly ("resumeVM" | "postResumeInit")[] | undefined;
/** borrowed-readonly — the acquired session; `getLeafId` (the resume point) and `buildContext` (the batch position) are read. */
session: Pick<Prepared["session"], "getLeafId" | "buildContext">;
/** borrowed-readonly — the session store; only the optional `pin` is called (best-effort, after commit). */
sessions: Pick<SessionStore, "pin">;
/** borrowed-readonly — the restart-loop chain base (the row's `suspendCount`, the cap's count). */
suspendChainBase: () => number;
/** borrowed-readonly — the restart-loop cap. */
maxSuspends: number;
/** borrowed-mutable — the run-scoped remote-lifecycle failure log. Writer here: a refused `suspendVM` (`push`). */
remoteEnvFailures: Prepared["remoteEnvFailures"];
/** borrowed-readonly — the shell-seat provenance set: the shell-running tools whose tier is the engine's shell seat's, not a
* deployment mark (the risk descriptor's `shellGated` is the membership of the name the call's tier is read under). */
shellGatedTools: ReadonlySet<string>;
/** borrowed-readonly — the per-tool tier, read only for the name the call's tier seat is read under (as at the gate). */
irreversibilityTier: IrreversibilityTier;
/** borrowed-readonly — who marked each name's seat (the descriptor's `shellGated` reads it as the gate does). */
toolMarkSources: ReadonlyMap<string, ReadonlySet<import("../ask-origin.js").ToolMarkSource>>;
/** borrowed-readonly — the operator's shell floor (`"always"`), persisted on a shell-gated descriptor; undefined = no floor. */
effectiveShellGate: ShellGateDoctrine | undefined;
/** borrowed-readonly — the EFFECTIVE durable-approval opt-in (the caller's, or the synthesized mandate scope): picks the
* gate kind, the scope and the ttl. */
durableApproval: TaskSpec["durableApproval"];
/** borrowed-readonly — the cross-slice ledger carried on the resumed checkpoint, or undefined. */
priorLedger: Prepared["resourceLedger"];
/** borrowed-readonly — the loop's live spend seat; `.get` is read at mint time. */
liveSpendRef: Prepared["liveSpendRef"];
/** borrowed-readonly — the human total the first slice seeds the ledger from, or undefined. */
resourceTotal: {
totalBudgetMicroUsd?: number;
totalTokens?: number;
} | undefined;
/** borrowed-readonly — the inherited-gate phase's three version-stamp predicates (face / F-012 / org admission). */
faceCheckpointState: () => boolean;
/** borrowed-readonly — see `faceCheckpointState`. */
f012CheckpointState: () => boolean;
/** borrowed-readonly — see `faceCheckpointState`. */
orgAdmissionCheckpointState: () => boolean;
/** borrowed-readonly — the policy-chain phase's rule-offer factory (the row's suggestion members). */
ruleOffersOf: ReturnType<typeof createRuleOffersOf>;
/** borrowed-readonly — the leg's loaded skills (what its `Skill` tool serves); a parked `Skill` call's row binds the named
* skill — its name and its lists — from them (`boundGrantDigest`). Empty when the leg loads none. */
skillSpecs: readonly SkillGrantSource[];
/** borrowed-readonly — the injectable wall clock (the row's `suspendedAt`). */
now: () => number;
/** borrowed-readonly — the per-task human-review accumulator; snapshotted onto the row (never written here). */
humanReviewRef: Prepared["humanReviewRef"];
/** borrowed-mutable — prepare's abort seat. Writers here: `abort()` after a committed park and on a failed pause
* compensation; `.signal` is composed into the fence. */
abortController: {
readonly signal: AbortSignal;
abort(): void;
};
/** borrowed-mutable — the built harness. Writer here: `abort()` (release the gate, then stop the loop). */
harness: Pick<AgentHarness, "abort">;
/** borrowed-mutable — the run's committed-pause holder (`Prepared.pausedRef`). Writer here: the commit-side publication. */
pausedRef: Prepared["pausedRef"];
}
export interface PrepareParkAskResult {
/** owned — the two closures as ONE value, or undefined exactly when the ask lane (and so the saga) is absent. */
parkAsk: ParkAsk | undefined;
}
/** The M19 park-ask phase body — prepareTask's park closure and content-ask routing stretch, verbatim (see the module header). */
export declare function prepareParkAsk(input: PrepareParkAskInput): PrepareParkAskResult;
/**
* From the resumed/active transcript, the batch position of `currentId`: the tool-call
* ids of the assistant message that issued it (the batch), and the subset already resolved (executed
* before the suspend — #1..k-1). ID-based, not positional (council Question #1): immune to reordering.
*/
export declare function batchContextAt(messages: AgentMessage[], currentId: string): {
batchToolCallIds: string[];
completedCallIds: string[];
};