@sema-agent/core
Version:
Stateless, task-oriented AI agent core
85 lines (84 loc) • 5.45 kB
TypeScript
/**
* 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;