UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

254 lines (253 loc) • 17.6 kB
/** * PrepareTask's POLICY-CHAIN phase, verbatim from the driver: the caller policy * and its name audit (advisory once per deployment; the two hard refusals), the session / ancestor / frozen-projection / * skill-scope deny-narrowing layers and their fold, the resume-edit base policy, the hook record with its timeout seat * and observational half, the same-instance arbitration machinery (the caller slot's first-decision record), the * inherited-unavailable marker channel, the grant record, the sandbox-admission mode's arming and its three * sideband maps, the source-identity and risk-axes factories, the two permission-rule lanes and the rule-offer * factory, the approved-edit re-check, the inherited-constraint wrappers (the same-instance adapter and the true * wrapper), the rewrite-emitter index and the instrumented chain, the integrity guards, the fold, and the final-form * re-check that is the effective policy. The advised-names dedupe pair only this section reads moved with it. * The interface is the dependency list the segment had implicitly. * * ASYNC PHASE: the stretch awaits the session-policy store (the task's own rules and each ancestor's live re-read) * exactly where the segment did; the closures it builds await at tool-call time. The phase boundary adds one * continuation between its last await and the driver's next read — the accepted class under {@link RunInternals} * `@contract prepare.deps-read-stable`. * * NOT CHANGED HERE: the ask-origin vocabulary. This phase is a mint site for `AskOrigin` words (through the inherited * arms' `judgeInheritedClassifier` / `inheritedAskCarry`), and it carries them verbatim — the closed set, its fence and * every spelling are exactly the driver's. */ import type { AgentTool } from "../../internal/harness.js"; import type { ExecutionEnv } from "../../internal/harness.js"; import { type AskClass } from "../ask-class.js"; import type { AutoModeDecider } from "../auto-mode.js"; import { type Hooks } from "../hooks.js"; import type { LockedPreflight } from "../locked-config.js"; import type { SessionPermissionRules } from "../session-policy-store.js"; import { type OnAsk, type ToolPolicy } from "../tool-policy.js"; import type { RunnerDeps, RuntimeCaps, TaskSpec, ToolEffect } from "../types.js"; import type { CwdRef } from "../../tools/fs/fs-shared.js"; import type { ReadDenyMatcher } from "../../tools/fs/read-deny.js"; import type { SkillGrantLane, SkillGrantTable } from "../skill-grant.js"; import { type AllowEntryLane, type AllowEntryTable } from "../allow-entry.js"; import type { InheritedGate, IrreversibilityTier, PrepareResume, RunInternals, StopForDenialLimit } from "./contracts.js"; import { type Arbitration } from "../ask-origin.js"; import { type ModeGateSeats, type PermissionModeController } from "../mode-policy.js"; import { type InheritedAskGrant } from "./inherited-ask-grants.js"; import { createRuleOffersOf, type PermissionRuleLanes } from "./permission-rule-lanes.js"; import type { ToolCallLike } from "../tool-registry.js"; export interface PreparePolicyChainInput { /** borrowed-readonly — the REBOUND spec. Read: `sessionId` / `principal` (the session-rule read and the lanes' identity), * `hooks` (spec over deps), `durableApproval` / `checkpointStore` (the mandate-park facility test and the sandbox arming's park prerequisites), `taskId` (the late-ask observer's detail). * Never mutated. */ spec: Pick<TaskSpec, "sessionId" | "principal" | "hooks" | "durableApproval" | "taskId" | "checkpointStore">; /** borrowed-readonly — the deployment seats: `sessionPolicyStore` (the rule reads), `hooks` (the fallback record), * `permissionRuleStore` / `localOwnerRules` (the lanes and the offer factory), `onError` / `onNotice` (the advisories, * the refusal disclosures, the late-ask observer), `tracer` (the admission and rule-store frames), `checkpointStore` (the park facility). Receiver preserved; * the advisory dedupe is KEYED on this object's identity. */ deps: Pick<RunnerDeps, "sessionPolicyStore" | "hooks" | "permissionRuleStore" | "localOwnerRules" | "onError" | "onNotice" | "tracer" | "checkpointStore" | "a2a">; /** borrowed-readonly — the trusted spawn-side channel: `parentTaskId` (the from-subagent bit), `explicitAgentName` / * `agentName` (the source agent name) — the ask source identity's local, trusted facts. */ internals: Pick<RunInternals, "parentTaskId" | "explicitAgentName" | "agentName"> | undefined; /** borrowed-readonly — the acquired session id (every operator line's attribution; the ask source's task id). */ sessionId: string; /** borrowed-readonly — the run's invocation id (the late-ask observer's detail). */ runId: string; /** borrowed-readonly — the per-task identity (the trace frames' taskId; the rule-store disclosure). */ hostTaskId: string; /** borrowed-readonly — the FINAL task root (path rules resolve against it; the lanes' and the offers' root). */ taskRootFinal: string; /** borrowed-readonly — the leg's canonical write roots beyond the task root (`additionalDirectories` + advertised writable * dirs): the `acceptEdits` mode gate allows writes inside the root and these, and asks outside them all. */ additionalRootsCanonical: readonly string[]; /** borrowed-readonly — the run's resolved env (path rules resolve in it; the sandbox declaration is read off it). */ executionEnv: ExecutionEnv; /** borrowed-readonly — the frozen preflight: `toolPolicy` (the caller slot) and `basePolicyForResumeEdit` (the lock-aware * resume-edit override). */ lockedPreflight: Pick<LockedPreflight, "toolPolicy" | "basePolicyForResumeEdit">; /** borrowed-readonly — the run's ONE tool roster, read only here (the name audit's universe: names + aliases). */ tools: ReadonlyArray<AgentTool>; /** borrowed-readonly — the harness list (placeholders keep their deferred names), the audit universe's second half. */ harnessTools: ReadonlyArray<AgentTool>; /** borrowed-readonly — the effect collection, read by the narrowing layers (path-confinable write tools). */ toolEffects: ReadonlyMap<string, ToolEffect>; /** borrowed-readonly — the egress mark set (the risk axes, the boundary predicate, the offer factory). */ egressTools: ReadonlySet<string>; /** borrowed-readonly — the folded irreversibility tiers (the risk axes, the offer factory). */ irreversibilityTier: IrreversibilityTier; /** borrowed-readonly — the explicit negative declarations per tool (the risk axes' "engine judged it false"). */ axisExplicitNegatives: ReadonlyMap<string, { irreversible?: false; egress?: false; }>; /** borrowed-readonly — the ENGINE HAND roster: every tool whose effect dispatches through the task's execution env, * membership minted at the mount sites (the sandbox boundary predicate's one containment fact). */ envHandToolNames: ReadonlySet<string>; /** borrowed-readonly — the shell-seat provenance set (the offer factory's `shellGated` mandate term, read per call). */ shellGatedTools: ReadonlySet<string>; /** borrowed-readonly — who marked each name's seat (the offer factory's `shellGated` term reads it as the gate does). */ toolMarkSources: ReadonlyMap<string, ReadonlySet<import("../ask-origin.js").ToolMarkSource>>; /** borrowed-readonly — the tracked-cwd seat, read per call by the lanes and the offer factory (never written here). */ handsCwdRef: CwdRef | undefined; /** borrowed-readonly — the compiled read-deny judge, or undefined (the offer factory's directory negative control). */ readDenyMatcher: ReadDenyMatcher | undefined; /** borrowed-readonly — the run's one notice for a person's rule store that could not be read (`ruleStoreUnreadableNotice`). */ announceRuleStoreSkipped: (at: "prepare" | "call") => void; /** borrowed-readonly — whether the question tool mounted (the lanes' question-tool exclusion). */ questionToolMounted: boolean; /** borrowed-readonly — the resolved per-principal entitlements; only `forceDurableGate` (the mandate-park facility and * the sandbox arming's prerequisite). */ runtimeCaps: Pick<RuntimeCaps, "forceDurableGate"> | undefined; /** borrowed-readonly — the ancestors' session-rule snapshots (each re-read live-preferring, floor kept). * Never written: an entry is some other leg's or some host's object — see `ancestorSessionRulesRef`. */ inheritedAncestorRules: InheritedGate["ancestorRules"]; /** borrowed-mutable — THIS leg's record of what each ancestor layer now ENFORCES, keyed by the entry's * POSITION (see the seat's own declaration in `prepare-inherited-gate.ts` for why position and not * identity). Writer: this phase, through each ancestor layer's `onEnforced`. */ ancestorSessionRulesRef: { current: Map<number, { sessionId: string; principal?: string; rev: number; gen?: number; rules: SessionPermissionRules; }>; }; /** borrowed-readonly — the ancestors' caller-policy constraints, ancestors first (one wrapper each). */ inheritedParentConstraints: NonNullable<InheritedGate["parentConstraints"]>[number][] | undefined; /** borrowed-readonly — the resumed leg's SEED half; only `constraintChain` (the frozen deny projections). */ seedInheritedGate: Pick<NonNullable<PrepareResume["seed"]["inheritedGate"]>, "constraintChain"> | undefined; /** borrowed-readonly — the run's skill grant table; this phase binds its lane to the call facts the person's rule lane * reads (root, session, live cwd, the execution environment's home). Not written here. */ skillGrants: SkillGrantTable; /** borrowed-readonly — the request's and the deployment's allow-entry tables (prepare seeded them); this phase binds * their lanes to the same call facts. Not written here. */ allowedToolTables: { readonly request: AllowEntryTable; readonly deployment: AllowEntryTable; }; /** borrowed-readonly — the FROZEN approver seat (the pass-through test's approver identity). */ frozenOnAsk: OnAsk | undefined; /** borrowed-readonly — the identity of this task's hook-env source (the pass-through test's environment term). */ hookEnvSource: unknown; /** borrowed-readonly — the own-hook crash reporter (the timeout seat's bad-value disclosure). */ notifyOwnHookCrash: (err: unknown) => void; /** borrowed-readonly — LATE-BOUND: this task's own classifier instance (the pass-through test's decider term), read at chain * assembly (the arming runs after this phase, and again on a steer into `auto`). */ autoModeDecider: () => AutoModeDecider | undefined; /** borrowed-readonly — the leg's live permission-mode seat: the mode gate policy layer and the inherited stations' arbitration * read `current` per call. */ permissionMode: PermissionModeController; /** borrowed-readonly — the task's scratchpad directory (the mode write gate's one exemption), or undefined. */ scratchpadDir: string | undefined; /** borrowed-readonly — the leg's live roster seat; the mode write gate reads `current` per call. */ toolRosterDeltas: NonNullable<ModeGateSeats["roster"]>; /** borrowed-readonly — the run's ONE typed stop for a denial-limit bound (the fallback settlements call it). */ stopForDenialLimit: StopForDenialLimit; /** borrowed-readonly — the effective delegation facts; only `isDelegatedChild` (the ask source identity). */ delegation: { isDelegatedChild: boolean; }; /** borrowed-readonly — the run's abort controller; only `signal` (the default call signal of every closure here). */ abortController: Pick<AbortController, "signal">; /** borrowed-readonly — the injectable wall-clock (the inherited arms' human-wait timing). */ now: () => number; /** borrowed-mutable — the own session-rules snapshot seat (the inherited-gate phase mints it; `inheritedGateForChildren` * reads it late). Writer: THIS phase, once, when the store returns this task's own rules. */ ownSessionRulesRef: { current?: { sessionId: string; principal?: string; rev: number; gen?: number; rules: SessionPermissionRules; }; }; } export interface PreparePolicyChainResult { /** owned — the resolved hook record (`spec.hooks ?? deps.hooks`), or undefined. */ hooks: Hooks | undefined; /** owned — the per-leg hook timeout, resolved and validated ONCE (`Prepared.hookTimeoutMs`). */ hookTimeoutMs: number; /** owned — whether the deployment declared its screening face observational. */ preToolUseObservational: boolean; /** owned — the screening face this task's OWN gate runs in phase 1 (the observational wrapper when declared). */ ownGatePreToolUse: Hooks["preToolUse"] | undefined; /** owned — the resume-edit base policy (`Prepared.basePolicyForResumeEdit`), or undefined. */ basePolicyForResumeEdit: ToolPolicy | undefined; /** owned — the deny-narrowing fold (`Prepared.denyNarrowingPolicy`), or undefined when no layer exists. */ denyNarrowingPolicy: ToolPolicy | undefined; /** owned — the skill grant lane (`Prepared.skillGrants`; the gate's skill member and deny rows read it). */ skillGrantLane: SkillGrantLane; /** owned — the request's and the deployment's allow-entry lanes (the gate's two entry-list members). */ allowEntryLanes: { readonly request: AllowEntryLane; readonly deployment: AllowEntryLane; }; /** owned — the caller-facing policy layers (the wiring manifest reads their COUNT). */ policyLayers: ToolPolicy[]; /** owned — THE effective policy the gate station adjudicates with (the fold plus its final-form re-check), or undefined. */ effectivePolicy: ToolPolicy | undefined; /** borrowed-mutable — the inherited-unavailable marker channel. Writers: the inherited arms here (mark); the gate station * (the lifecycle-end sweep) and the park lane's consumers (delete on consumption). */ inheritedUnavailableAsks: Set<string>; /** owned — the inherited stations' MODE ARBITRATIONS by tool call (a release or a refusal made inside the fold, * where an ancestor's ask is arbitrated before its frozen approver). Writers: the three inherited stations here; the gate's * exit reads it onto the disposition and the gate station deletes it at the call's end. */ ancestorArbitrations: Map<string, Exclude<Arbitration, { kind: "ask"; }>>; /** borrowed-mutable — the grant record. Writers: the inherited arms here (record a clean allow at a live approver); * the gate station (consume on reuse, sweep at settlement). */ inheritedAskGrants: Map<string, InheritedAskGrant>; /** owned — the SINGLE AskRequest source-identity factory every resolveAsk construction site spreads. */ askSourceIdentity: () => { principal?: string; sourceTaskId?: string; fromSubagent?: true; sourceAgentName?: string; isDelegatedChild?: true; }; /** owned — the engine-judged risk axes for an ask, from the same resolved sources the safety tightens use. */ riskAxesOf: (call: Pick<ToolCallLike, "toolName" | "face">) => { riskAxes?: { irreversible?: boolean; egress?: boolean; }; }; /** owned — the ONE rule-offer factory for all five ask-mint sites. */ ruleOffersOf: ReturnType<typeof createRuleOffersOf>; /** owned — the personal permission-rule lane, or undefined. */ permissionRuleLane: PermissionRuleLanes["personal"]; /** owned — the org permission-rule lane (`Prepared.permissionRuleOrg`), or undefined. */ permissionRuleOrgLane: PermissionRuleLanes["org"]; /** owned — whether the sandbox-admission mode armed (declared isolation with its park prerequisites). */ sandboxAdmissionArmed: boolean; /** owned — the NOT-CONTAINED classification: a call the declared sandbox does not contain. */ sandboxBoundaryCapable: (call: Pick<ToolCallLike, "toolName" | "face">) => boolean; /** owned — the admission disclosure's trace emitter (the gate station emits it once the fold settles). */ emitSandboxAdmitted: (info: { toolCallId: string; toolName: string; askClasses: ReadonlyArray<{ cls: AskClass; layer: string; }>; boundary: "sandbox_internal"; }) => void; /** borrowed-mutable — the per-call fold ask-class record. Writers: the instrumented chain here (record); the gate station * (read for strict dominance, then sweep). */ foldAskClasses: Map<string, Array<{ cls: AskClass; layer: string; }>>; /** borrowed-mutable — the ancestor arms' deferred sandbox admissions. Writers: the inherited arms here (record); the gate * station (emit once the fold settles, then sweep). */ ancestorSandboxAdmissions: Map<string, Array<{ cls: AskClass; layer: string; }>>; } /** The M17 phase body — prepareTask's policy-chain stretch, verbatim (see the module header). */ export declare function preparePolicyChain(input: PreparePolicyChainInput): Promise<PreparePolicyChainResult>;