UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

177 lines (176 loc) • 13.3 kB
/** * 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[]; };