UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

250 lines (249 loc) • 17.7 kB
/** * PrepareTask's two GATE STATIONS, verbatim from the driver: the `tool_call` station (the final-roster * effect sync, the write-protection judge compiled once per task, the arming predicate, the compliance and plan-mode * short-circuits, the `runToolGate` call with every seat it is handed, the lifecycle sweep, the deferred sandbox-admission * disclosure, the PreToolUse context stash, the settlement sideband write and the bare-human-rejection boundary) and the * `tool_result` station's registration (its body, the PostToolUse / PostToolUseFailure seats and the PreToolUse context * drain, lives in call-hook-stations.ts). The two module-scope * helpers only these stations called moved with them (the settlement screen, the rejection-boundary judgment). The * interface is the dependency list the stations had implicitly. * * The stations REGISTER handlers on the harness (the side effect this phase exists for) and report only two observation * seats back; the order they register in — after the file-history boundary lease the driver registers, and in this order * (call before result) — is the order handlers run in. * * SYNCHRONOUS FUNCTION, SYNCHRONOUS CALL: the stretch has no await of its own (the handlers run per tool call / per tool * result), so the phase adds no yield between the park closure before it and the context lane after it. * Read-stability of the host handles for the call: {@link RunInternals} (`@contract prepare.deps-read-stable`). * * The `tool_call` station is built exactly when the ask lane and the park closure are (the same predicate as the whole * gate-machinery block, kept as the block's one guard here); the `tool_result` station keys off the hook record alone, as * it always did. * * Every station this file holds is a `deniedBy` / settlement WRITE site the decomposition's next slice maps onto one * gate outcome; nothing here changes a word, a source or an order — the stations are moved, not re-adjudicated. */ import type { AgentHarness, AgentTool } from "../../internal/harness.js"; import type { AskClass } from "../ask-class.js"; import type { Arbitration, ToolMarkSource } from "../ask-origin.js"; import { type PermissionModeController } from "../mode-policy.js"; import type { AutoModeDecider, AutoModeDenialTracker } from "../auto-mode.js"; import { type ComplianceCapability } from "../compliance.js"; import { type HookEnvCapabilities, type HookInvocationIdentity, type Hooks } from "../hooks.js"; import type { ToolPolicy } from "../tool-policy.js"; import { type GateOutcome } from "../gate-outcome.js"; import type { RunnerDeps, TaskSpec, ToolEffect } from "../types.js"; import { type WiringManifest } from "../wiring-manifest.js"; import type { WriteProtectionCheck } from "../write-protect.js"; import type { ShellReadCheck } from "../../tools/fs/shell-read-check.js"; import type { CwdRef } from "../../tools/fs/fs-shared.js"; import type { AskLane, GateSeatInputsFor, IrreversibilityTier, ParkAsk, Prepared, ReversibilityProbes, StopForDenialLimit } from "./contracts.js"; import type { PermissionRuleLanes } from "./permission-rule-lanes.js"; import { type ToolRosterDeltaSeat } from "../tool-roster.js"; import type { ToolCallLike } from "../tool-registry.js"; import type { FrameSettlement } from "../frame-settlement.js"; import { type ApprovedCallScreen } from "./call-hook-stations.js"; /** * The parent-thread human-rejection boundary — judged ONCE here, from the pass's own record (CC parity: * the reference CLI's reject path aborts exactly when there is no feedback and the run is not a * subagent; a rejection WITH the decider's note, and every delegated child — forks included — keep the * continue / adapt-or-report posture): the final block IS a person's refusal (`deniedBy: ask_resolution` * and a `human_refused` settlement — not a later fold layer's deny that merely followed a human * interaction), the refusal carried NO note (`settlement.note` absent — a fact composed at the refusal, * never re-derived from message text), and the run is not a delegated child (the raw trusted delegation * facet — the same fact that selects the refusal text's posture, so the boundary and the words the model * was told always agree). Policy/hook/classifier denies and window/abort settlements never satisfy it. * * Returns the loop directive that settles every never-started same-batch sibling: honest * known-not-executed wording plus the machine mark (`error`/`code` twin — the wire errorCode lift * reads `details.code`; `details.error` is the in-band mark result consumers grep) naming the * rejected call. `undefined` = no boundary (the block stands alone, current behavior). */ export declare function maybeHumanRejectionHalt(input: { gate: GateOutcome | undefined; isDelegatedChild: boolean; toolName: string; toolCallId: string; }): { reason: string; details: Record<string, unknown>; } | undefined; export interface PrepareGateStationsInput { /** borrowed-readonly — the in-stream ask lane, or undefined when the gate machinery is inactive (then no `tool_call` * station is built). Read: `adjudicate`, `resolveAskBound`, `notifyPermissionDenied`, `notifyHookError`. */ askLane: AskLane | undefined; /** borrowed-readonly — the park closure and the content-ask routing closure, or undefined (same law). Handed to the gate. */ parkAsk: ParkAsk | undefined; /** borrowed-readonly — the FINAL mount roster: iterated once for the effect sync and the write-protection arming. Never * mutated here. */ tools: readonly AgentTool[]; /** borrowed-readonly — the leg's delta seat: its current roster is the face source for every gated call. */ toolRosterDeltas: ToolRosterDeltaSeat; /** borrowed-mutable — the effect collection. Writer here: the final-roster sync fills MISSING entries from each * first-party tool's declared effect (never overrides a scanned one); the plan-mode deny reads it per call. */ toolEffects: Map<string, ToolEffect>; /** borrowed-readonly — the deployment seats these stations read, as a Pick over the SAME `deps` object (receiver * preserved for `deps.onError?.()`): the write-protection table and its data root, the tracer, the error face, and * the notice sink (the gate's unresolvable-ask exit delivers `delegation.ask_unresolvable` through it). */ deps: Pick<RunnerDeps, "writeProtectedPaths" | "memoryEngineDir" | "tracer" | "onError" | "onNotice">; /** borrowed-mutable — the protocol-tools phase's arming belt. Writer here: `armed` ← the registration predicate, the * one write; the refresh seam reads it. */ toolCallGateArmedRef: { armed: boolean; }; /** borrowed-readonly — the policy chain's effective policy, or undefined; presence only (the arming predicate). */ effectivePolicy: ToolPolicy | undefined; /** borrowed-readonly — the resolved hook record: `preToolUse` presence (arming), the two post seats and the failure seat * (the `tool_result` station), called through the record so a class-instance keeps its receiver. */ hooks: Hooks | undefined; /** borrowed-readonly — the egress vocabulary: arming, and the per-call `egress` flag handed to the gate. */ egressTools: ReadonlySet<string>; /** borrowed-readonly — the irreversible vocabulary: arming only. */ irreversibleTools: ReadonlySet<string>; /** borrowed-readonly — the REBOUND spec: `enablePlanMode` (arming) and `signal` (the failure seat's interrupt fact). */ spec: Pick<TaskSpec, "enablePlanMode" | "signal">; /** borrowed-readonly — the compliance denials (the web_fetch veto's call-time lock, and its arming term). */ complianceDenies: ReadonlySet<ComplianceCapability>; /** borrowed-mutable — the built harness. Writer here: `on("tool_call")` / `on("tool_result")` — the registrations this * phase exists for, in this order, after the driver's boundary-lease handler. */ harness: Pick<AgentHarness, "on">; /** borrowed-mutable — the blocked/suspended call ids (`Prepared.blockedToolCalls`). Writers here: the head-of-evaluation * hygiene `delete`, the two short-circuits' and the gate outcome's `add`, the `tool_result` station's one-shot `delete`. */ blockedToolCalls: Set<string>; /** borrowed-mutable — the duplicate-frame ask-grant record. Writer here: the head-of-evaluation and lifecycle-end `delete`. */ inheritedAskGrants: Map<string, unknown>; /** borrowed-mutable — the inherited-unavailable marker set. Writer here: the lifecycle-end `delete`; `has` is handed to * the gate as the read-only `isMarkedUnresolvable`. */ inheritedUnavailableAsks: Set<string>; /** borrowed-mutable — the per-call ask-class record the sandbox admission reads. Writer here: the lifecycle-end `delete`. */ foldAskClasses: Map<string, Array<{ cls: AskClass; layer: string; }>>; /** borrowed-mutable — the deferred ancestor sandbox admissions. Writer here: the unconditional read-and-`delete` after the * fold settles. */ ancestorSandboxAdmissions: Map<string, Array<{ cls: AskClass; layer: string; }>>; /** borrowed-mutable — PreToolUse `additionalContext` per call id. Writers here: the `tool_call` station's stash (`set`, only * for calls that will execute) and the `tool_result` station's one-shot drain (`delete`). */ preToolContexts: Map<string, string[]>; /** borrowed-mutable — the gate-outcome sideband (`Prepared.gateOutcomes`). Writer here: the single screened write per * gated call (the two short-circuits and the gate's terminal exits); the frame mint reads and deletes. */ gateOutcomes: Prepared["gateOutcomes"]; /** borrowed-mutable — the turn halt fact (`Prepared.batchHaltRef`). Writer here: the bare-rejection boundary (`current`). */ batchHaltRef: Prepared["batchHaltRef"]; /** borrowed-readonly — whether blocked/suspended ids are tracked at all (the post-tool callbacks exist). */ blockedTracked: boolean; /** borrowed-readonly — the leg's identity envelope every HookToolContext and PermissionDeniedPayload carries. */ hookIdentity: HookInvocationIdentity; /** borrowed-readonly — the leg's reminder provenance mark (every model-facing deny reason and hook context carries it). */ reminderMark: string; /** borrowed-readonly — the per-task identity (the trace rows' taskId). */ hostTaskId: string; /** borrowed-readonly — the acquired session id (the settlement defects' error tag). */ sessionId: string; /** borrowed-readonly — the screening face as the gate must see it (the observational wrapper or the face itself). */ ownGatePreToolUse: Hooks["preToolUse"] | undefined; /** borrowed-readonly — the validated per-prepare hook seat bound. */ hookTimeoutMs: number; /** borrowed-readonly — the hand's live tracked cwd, or undefined; `current` is read per screening / per post seat. Not * written here. */ handsCwdRef: CwdRef | undefined; /** borrowed-readonly — the leg's read-deny table in force, read at a release: the active built-in tiers and the rows * beside them (the deployment's and the task's patterns, the read-face rule rows). Empty ⇒ no table. */ readDenyInForce: () => { readonly tiers: readonly string[]; readonly rows: readonly string[]; }; /** borrowed-readonly — the read-only env capability face every hook context carries, or undefined. */ hookEnvFace: HookEnvCapabilities | undefined; /** borrowed-readonly — the leg's write-protection gate input, compiled ONCE by the wiring-manifest phase * (which runs first and has to report the judge's own target view), or undefined when the deployment * emptied the table: no judge, and no tighten mounted at all. Carried rather than compiled a second time * here — two compiles are two judges, free to disagree about the seats they were handed. */ writeProtectionCheck: WriteProtectionCheck | undefined; /** borrowed-readonly — the leg's shell READ STATION (`ToolGateInput.shellReadCheck`), mounted by the hands phase, or * undefined with no real shell: no station, nothing judged. */ shellReadCheck: ShellReadCheck | undefined; /** borrowed-readonly — the per-tool irreversibility tier handed to the gate per call. */ irreversibilityTier: IrreversibilityTier; /** borrowed-readonly — the per-tool reversibility probes handed to the gate per call. */ reversibilityProbes: ReversibilityProbes; /** borrowed-readonly — prepare's abort seat; only `.signal` is read (the gate's probe bound, the post seats' bound, the * failure seat's interrupt fact). */ abortController: { readonly signal: AbortSignal; }; /** 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 gate's `shellGated` attribution is the membership of the name the call's tier was read under). */ shellGatedTools: ReadonlySet<string>; /** borrowed-readonly — the armed classifier's live seat (read per call: a leg steered into `auto` arms after prepare). */ autoModeDecider: { readonly current: AutoModeDecider | undefined; }; /** borrowed-readonly — the denial tracker's live seat beside the decider's. */ autoModeDenialTracking: { readonly current: AutoModeDenialTracker | undefined; }; /** borrowed-readonly — the leg's ONE ask derivation (`wiring_manifest.ask`: form + effective), read for the classifier's seat. */ askRoute: WiringManifest["ask"]; /** borrowed-readonly — the leg's live permission-mode seat: the gate's arbitration point reads `current` per call. */ permissionMode: PermissionModeController; /** borrowed-readonly — WHO marked each tool's seat (the safety scan's map, the protocol fold's rows); read per call into * the gate's class derivation. */ toolMarkSources: ReadonlyMap<string, ReadonlySet<ToolMarkSource>>; /** borrowed-readonly — the deployment's A2A isolation switch (`RunnerDeps.a2a.isolatePeers`), when set. */ a2aIsolatePeers?: true; /** borrowed-readonly — the run's invocation id (the released-ask account's detail). */ runId: string; /** borrowed-mutable — the inherited stations' mode arbitrations, keyed by tool call (the policy-chain phase writes; the * gate's exit reads and this station deletes at the call's end, the fold-record lifecycle). */ ancestorArbitrations: Map<string, Exclude<Arbitration, { kind: "ask"; }>>; /** borrowed-readonly — the headless denial-limit stop seat (wired unconditionally). */ stopForDenialLimit: StopForDenialLimit; /** borrowed-readonly — the persisted allow-rule lane, or undefined (absent entirely with no store). */ permissionRuleLane: PermissionRuleLanes["personal"]; /** borrowed-readonly — the call's seat inputs: the permission seat (static, or the `Skill` tool's per-call reader) and the run's skill rows. */ seatInputsFor: GateSeatInputsFor; /** borrowed-readonly — the org rule lane, or undefined (absent on an ungoverned deployment). */ permissionRuleOrgLane: PermissionRuleLanes["org"]; /** borrowed-readonly — whether the ENGINE's question tool is what is mounted under the reserved name (the org lane's * content-ask exemption predicate). */ questionToolMounted: boolean; /** borrowed-readonly — whether sandbox admission is armed (isolated env + park prerequisites). */ sandboxAdmissionArmed: boolean; /** borrowed-readonly — the per-call boundary-capability fact the sandbox admission reads (the call's wire name and names). */ sandboxBoundaryCapable: (call: Pick<ToolCallLike, "toolName" | "face">) => boolean; /** borrowed-readonly — the sandbox-admission trace sink (the deferred ancestor disclosure and the gate's own). */ emitSandboxAdmitted: (info: { toolCallId: string; toolName: string; askClasses: ReadonlyArray<{ cls: AskClass; layer: string; }>; boundary: "sandbox_internal"; }) => void; /** borrowed-readonly — the effective delegation facts (the rejection boundary's isDelegatedChild). */ delegation: { isDelegatedChild: boolean; }; /** borrowed-readonly — the own hook-crash reporter (an expired post seat is reported through it). */ notifyOwnHookCrash: (err: unknown) => void; /** borrowed-mutable — the leg's settlement seam. Writer here: the `tool_result` station marks a carrier whose output * a host hook rewrote (`noteHostRewrite`), so a frame the rewrite removed settles as the host's decision. */ frameSettlement: FrameSettlement; } export interface PrepareGateStationsResult { /** owned — whether the `tool_call` station registered (the arming predicate's value; observation seat only). */ toolCallStationArmed: boolean; /** owned — whether the `tool_result` station registered (a post or screening hook is wired; observation seat only). */ toolResultStationArmed: boolean; /** owned — the approved call's PreToolUse re-screen (the gate's face and seat), present exactly when a face is wired. */ approvedCallScreen?: ApprovedCallScreen; } /** The M20 phase body — prepareTask's two gate stations, verbatim (see the module header). */ export declare function prepareGateStations(input: PrepareGateStationsInput): PrepareGateStationsResult;