UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

85 lines (84 loc) • 5.45 kB
/** * The ONE exit for an auto-mode classification that did not RULE ({@link rungDidNotRule}: the classifier leg failed or its * budget ran out — `error` / `timeout` — or it replied with no verdict at all — `parse_error`) — shared by both classifier * stations, the leg's gate and an ancestor's frozen classifier in the policy fold, so the rule is written once. * * ONE fork, on the APPROVAL ROUTE the station states (`ApprovalRoute`, the one seat decision — `live_approver`, * `durable_park` or `none`): with a person on the route, the ask goes on carrying why (`classifierNoRuling`, its text saying it too), and a person decides — never * counted (a person's refusal of it is a person's); without one, an unavailable classification is refused with CC's `x1t` * sentence (its FORM, `unavailable`, attested on the deny) and is never counted, while a reply with no verdict goes back to * the station's ruling path (`counted`): refused with its own sentence and counted on the denial limit, so the bound falls * back to a person (CC: `Cr` excludes unavailable; failureMode is counted). The handed-on ask carries `requiresRealApproval`: * no automatic lane may answer it in the classifier's stead — not a blanket `onAsk:"allow"`, not the sandbox admission, not a * budget resolver over its park. Asking where a person can be asked is a declared departure (CC refuses). * * The observer hears every such classification with where the call went, except while the call is being aborted — a halt, * or the run's end. It is observe-only: a throw never changes the arm. */ import { type AutoModeClassifierRound, type AutoModeDenialTracker, type AutoModeFallbackCause, type UnarmedDenialLimitFallback } from "./auto-mode.js"; import { type PermissionResult, type ResolvedAsk, type ToolCallRequest } from "./tool-policy.js"; import type { Settlement } from "./gate-outcome.js"; import type { ApprovalRoute } from "./wiring-manifest.js"; /** Where a classification that did not rule sent the call: `asked` = on to the approval seat, `denied` = refused — the * projection of the route ({@link classifierUnavailableDispositionOf}), never a second reading of it. */ export type ClassifierUnavailableDisposition = "asked" | "denied"; /** The disposition a route projects: a route with a person on it is `asked`, `none` is `denied`. */ export declare function classifierUnavailableDispositionOf(route: ApprovalRoute): ClassifierUnavailableDisposition; /** What the stations' observer receives — the facts the `classifier.unavailable` notice is composed from. `cause` is why * the classification did not rule ({@link AutoModeFallbackCause}: `error` / `timeout`, or `parse_error` for a reply with no * verdict); `route` is where the station sent the call (the disposition the notice's detail carries is its projection). */ export interface ClassifierUnavailableInfo { toolName: string; toolCallId: string; cause: AutoModeFallbackCause; route: ApprovalRoute; } export type ClassifierUnavailableObserver = (info: ClassifierUnavailableInfo) => void; /** The station's two facts for the fork: the route an ask it hands on takes (`approvalRouteOf` / `frozenApproverRoute` — * a value outside the two person routes, or none stated, is `none`), and who hears it. */ export interface ClassifierUnavailableSeat { route?: ApprovalRoute; onUnavailable?: ClassifierUnavailableObserver; } /** The arm the fork chose: the ask to hand on, the refusal, or — a reply with no verdict and no seat — the station's own * counted ruling path. */ export type ClassifierNoRulingArm<A> = { kind: "asked"; ask: A; } | { kind: "denied"; deny: PermissionResult; } | { kind: "counted"; }; export declare function classifierNoRulingArm<A extends Extract<PermissionResult, { action: "ask"; }>>(args: { ask: A; req: ToolCallRequest; cause: AutoModeFallbackCause; /** The ladder's attribution, for the refusal's sentence (it names both rungs when the ladder moved). */ attribution?: AutoModeClassifierRound; seat: ClassifierUnavailableSeat | undefined; /** The call is being aborted (its per-call signal — a halt, or the run's end): the observer is not told (the arm is chosen * as ever). */ aborted: boolean; }): ClassifierNoRulingArm<A>; /** * The no-ruling card's settlement (both stations): a card THIS station's no-ruling arm handed on for this call (`mintedHere` — * a policy's own `classifierNoRuling`, or a card another station minted, is not this station's classification and counts * nothing here) that the seat's deployment POLICY refused (settlement `policy_refused` — a machine settled it, not a person) * counts on the denial limit like a ruling refusal, because "handed to a person" means a person settled it. Returns the fallback the count minted when it reached the * bound — the station then puts the same call to the same seat as the denial-limit fallback, and a deny nobody judged there * stops the run through the existing headless arm. A person's answer (allow or refusal), a fallback card, and every other * settlement count nothing. */ export declare function settleNoRulingAsk(opts: { ask: PermissionResult; mintedHere: boolean; resolved: { action: ResolvedAsk["action"]; settlement?: Settlement; }; tracker: AutoModeDenialTracker | undefined; }): UnarmedDenialLimitFallback | undefined;