UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

209 lines (208 loc) • 16.7 kB
import type { ExecutionEnv } from "../../internal/harness-types.js"; import { type PathFamily } from "./safety.js"; import { type ReadDenyMatcher, type WriteDenyMatcher } from "./read-deny.js"; /** The one bound every probe of an AUXILIARY home shares within one placement (see the module head): long enough for a * local directory's canonical spelling, its chain of identities and its rows' heads, short enough that a hung home * costs a placement seconds, not the gate's bound. */ export declare const AUXILIARY_HOME_PROBE_MS = 2000; /** {@link declaredReadDenyHomesAnswered}'s anchor list. */ export declare function declaredReadDenyHomes(env: Pick<ExecutionEnv, "homeDir">): string[]; /** The declared home and the account home ({@link declaredReadDenyHomes}) and, where the environment's canonical * spelling of one differs (a home reached through a symlink), that spelling too: the faces judge canonical keys beside * the spelled views, so a row placed only at the spelled home would never meet a canonical key. A home the * environment cannot resolve keeps its spelling — the account home's probe waits at most {@link AUXILIARY_HOME_PROBE_MS}. * `[]` — the rows hold their run at every location — where the environment declares no home, where the account lookup * gave no answer, or where the declared home's own probe did not answer (ask again later). */ export declare function readDenyHomesOf(env: Pick<ExecutionEnv, "homeDir" | "canonicalPath">, signal?: AbortSignal): Promise<string[]>; /** * Where a table's deny rows LEAD past the places their own runs anchor them, keyed by the row's pattern — the `linked` * argument of `compileReadDeny`. ONE constructor for every anchor ({@link ReadDenyRowAnchor}): the rows the anchor * places, each row's LITERAL HEAD ({@link literalHeadOf}) spelled under each of the anchor's places and resolved the way the * read faces mint their keys (`canonicalizeTarget`: the longest existing prefix resolved, the rest rejoined); a location * that differs from the spellings the row is already anchored at is where one of the head's segments is a link (`~/.sema` * into a dotfiles directory, `/app` a link to `/data/app`), and the row is placed there too. * · a head that resolves to its own spelling at an anchored place adds nothing (the row is already placed there); * · a head under a spelling that is no directory (a segment of it is a file, a symlink to a file, a device node or a * socket — the environment answers `not_directory` for the head) has nothing below it: an answer that adds nothing; * · any other head that cannot be resolved (an unanswered or refused probe — a directory the probe may not enter — a * probe that throws) adds nothing, and the answer is not WHOLE (`whole: false`): the row stays at the places its run * anchors until the mount places again; * · the heads are resolved together, so one placement waits at most one probe bound — or, with `deadline` (an absolute * time), each head until then: a head still unresolved at the deadline is a head that did not answer; * · read per placement — a link created afterwards is followed from the next placement on. */ export declare function readDenyLinksOf(env: ExecutionEnv, entries: readonly { readonly pattern: string; readonly caseSensitive?: boolean; }[], anchor: ReadDenyRowAnchor, signal?: AbortSignal, deadline?: number): Promise<{ readonly linked: Map<string, string[]>; readonly whole: boolean; }>; /** * Which rows one placement reads, and where their heads are spelled: * · `home` — the home-anchored rows (`~/…`), under each home; the row's run anchors it there; * · `root` — the ROOTED rows (`/app/config-lkg.json`, {@link isRootedReadDenyPattern}), under the filesystem root; the run * anchors it there. A root is no home: read whether or not a home answered; * · `unanchored` — the rows that open with the configuration directory's name (`~/.sema/…`, {@link agentConfigDirTail}) * under each spelling of the root the host declares for that directory (`RunnerDeps.agentConfigDir`): the head BELOW * the directory name, a head that ends at it (`~/.sema/*.json`, `~/.sema`) the root itself. The row's run does not * anchor it there (it is anchored at the homes), so every location — the spelling itself and where it leads — is one * the row is placed at in addition; the row's name in every refusal is unchanged. * A home or rooted row whose head is empty (`~/.sema.*`, `/*.json`) has no location to resolve and adds nothing. */ export type ReadDenyRowAnchor = { readonly at: "home"; readonly homes: readonly string[]; } | { readonly at: "root"; } | { readonly at: "unanchored"; readonly roots: readonly string[]; }; /** Where an absolute location LEADS in one environment — resolved the way the read and write faces mint their keys * (`canonicalizeTarget`: the longest existing prefix resolved, the rest rejoined). `undefined` when the environment * does not answer (a refused or failed probe, one that throws). The one resolution a deny row's placement reads: a * deny row's head ({@link readDenyLinksOf}) and a rule row's head ({@link whereItsAnsweredPrefixLeads}). */ export declare function whereItLeads(env: ExecutionEnv, at: string, family?: PathFamily, signal?: AbortSignal): Promise<string | undefined>; /** {@link whereItLeads} with a floor: where the environment does not answer for `at` (a probe that fails), the longest * prefix it does answer for is resolved and the rest rejoined — a rule row's head below a base that resolves is never * placed narrower than that base's own canonical spelling. The whole walk waits at most one bounded-await deadline. * `undefined` when no prefix answers in time. */ export declare function whereItsAnsweredPrefixLeads(env: ExecutionEnv, at: string, family?: PathFamily, signal?: AbortSignal): Promise<string | undefined>; /** The environment surface a home view reads. */ export type HomeViewEnv = Pick<ExecutionEnv, "homeDir" | "canonicalPath" | "fileIdentity">; /** * The HOME VIEWS of one placement — the one reader every face that judges a deny row by location uses (the structured * read and write tools' target judgment, the shell station's establishment, the search walker, the ripgrep leg's output * check and its scope choice). A judge carries the source its placement made ({@link HomeViewsCarrier}); a judgment * takes one {@link HomeViews} from it ({@link homeViewsOf}). * * The view of a path is the path itself when it IS a directory on a home's chain (each home's canonical spelling and * each of its ancestors, matched by {@link ExecutionEnv.fileIdentity}) — that directory's canonical spelling — and * otherwise its parent's view with its own name rejoined. One identity keeps ONE spelling, and a row is placed at a * HOME's spelling: a directory that IS a home (its own identity answered) keeps the home's own spelling over an * ancestor's (a declared home below a mount of the account home meets the account home's identity at an ancestor); a * home whose own identity did not answer has no directory to claim. Homes in order: the declared home, then the * auxiliary homes. The homes are the placement's: * · the DECLARED home (`env.homeDir`, no lookup) is always among them — whatever the account lookup answered — and its * chain is read by the first judgment that asks, kept once every level answered, and asked again by the next * judgment otherwise (a home that does not exist yet is read once it does); * · each AUXILIARY home's chain is the one its placement read, once ({@link AUXILIARY_HOME_PROBE_MS}); a chain that did * not answer stays unanswered for that placement. */ export interface HomeViewSource { /** The home views of one judgment (a tool call, a command's establishment, one search): a view per directory is read * once and shared by every path below it. */ views(signal?: AbortSignal, env?: HomeViewEnv): HomeViews; } /** A judgment's views. `of` answers one path's {@link HomeView}: no view where there is nothing to read (no `fileIdentity` * seat, no home, a seat that declares the surface absent — the path is judged as spelled); the view where it differs from * the path as spelled; and `unread` wherever a lookup the view stands on gave no answer. A level that does not exist (a * write's missing tail) takes its parent's view. `leaf: "file"` skips the path's own identity (a file is never a directory * on the chain). */ export interface HomeViews { of(path: string, opts?: { readonly leaf?: "file"; }): Promise<HomeView>; } /** One path's home view as a judge reads it: * · `view` — the path respelled through a home's own directory chain, present only where it differs from the spelling; * · `unread` — the first lookup the view stands on that gave no answer (an identity lookup that threw, timed out or failed * with an error that is no conclusive "not there": a level of a home's chain — the index of every path is then partial — * or a level of the path itself), present wherever one did, and inherited by every path below it. * An `unread` answer is no reading: the path's view is not "none", it is unknown, so a judge that places the home-anchored * rows by the view has not finished — every judge refuses to vouch past it ({@link homeViewUnreadSentence}). */ export interface HomeView { readonly view?: string; readonly unread?: string; } /** The one sentence a judgment that met an unread home view ({@link HomeView.unread}) refuses with — `subject` the path the * call judged, `face` the deny table it was judged against. */ export declare function homeViewUnreadSentence(subject: string, unread: string, face?: "read" | "write"): string; /** The refusal a traversal raises when an entry it walks met an unread home view: the whole call is refused (what the walk * would serve was not judged), with the one sentence. */ export declare class HomeViewUnreadError extends Error { constructor(subject: string, unread: string); } /** A judge that carries the home views of the placement its rows were compiled at. */ export interface HomeViewsCarrier { readonly homeViews?: HomeViewSource; } /** A read deny judge with its placement's home views. */ export type PlacedReadDenyMatcher = ReadDenyMatcher & { readonly homeViews: HomeViewSource; }; /** The Runner's read face as its tool band takes it: the run's read and write deny tables — each judgment reads the run's * latest, `forCall` hands a call its own — and the read face's root set, read per judgment (a call's own through * `forCall`). A band handed one judges with it and compiles no table of its own. */ export interface CarriedReadFace { readonly readDeny: PlacedReadDenyMatcher; readonly writeDeny: WriteDenyMatcher & { readonly homeViews: HomeViewSource; }; readonly readRoots: () => readonly string[]; } /** The views of the placement that returned `homes` — the very list object, as the engine hands it on (the Runner's * tool band). `undefined` for any other list: a copy, or a host's own. A mount that was handed the engine's placement * and cannot find its views has lost it: an engine defect, which the mount names ({@link sayPlacementLost}) and answers * by holding its home-anchored rows at every location — never by reading the homes a second time. */ export declare function placementViewsOf(homes: readonly string[]): HomeViewSource | undefined; /** The run's read face a home list carries ({@link carryReadFace}); `undefined` for any other list. */ export declare function carriedReadFaceOf(homes: readonly string[]): CarriedReadFace | undefined; /** A home list (a fresh frozen copy of `homes`) that hands the Runner's read face to its band: the band judges with the * face's tables and roots instead of compiling its own from the rows it is handed. */ export declare function carryReadFace(homes: readonly string[], face: CarriedReadFace): readonly string[]; /** A judge that reads `current` at every judgment — the table a placement in flight will replace, or the run's tables of * which each judgment takes the latest — with the home views `homeViews` (default: the current judge's). */ export declare function readDenyOver(current: () => PlacedReadDenyMatcher, homeViews?: HomeViewSource): PlacedReadDenyMatcher; /** The line a mount says when the engine handed it a home list that is not its placement's (an engine defect, said every * time: no latch). */ export declare function sayPlacementLost(): void; /** The home views over a home list fixed now — a mount that places nothing itself (a toolkit handed its placement, or * one whose table has no home-anchored row) and the public shell judgment (the `readDenyHomes` its boundary's rows were * compiled under): the declared home, and every entry of `homes` that is not the declared home as an auxiliary home, * read once, at once. */ export declare function homeViewsAt(env: HomeViewEnv, homes: readonly string[]): HomeViewSource; /** The home views of one judgment, off the judge it judges with: the views of the judge's placement. A judge that * carries none (one built outside a mount) reads the declared home alone. */ export declare function homeViewsOf(env: HomeViewEnv, judge: HomeViewsCarrier | undefined, signal?: AbortSignal): HomeViews; /** A judge with the home views `source` gives it. */ export declare function carryHomeViews<T extends object>(judge: T, source: HomeViewSource): T & { readonly homeViews: HomeViewSource; }; /** One mount's read-deny judge with its home-anchored rows placed, and the home views of that placement: * · the HOME SET — `homes` when the mount carries a list (the account is not asked and no home is added), else the * declared home and the account lookup's answer ({@link declaredReadDenyHomes}); every home of the set is anchored at * its spelling and, where the environment answered one, its canonical spelling — one rule for both forms; * · the DECLARED home's canonical spelling under the tree's gate bound, and its rows' heads ({@link readDenyLinksOf}): * a declared home, or a head under it, that did not answer leaves the placement not WHOLE — and an account lookup * with no answer, or a declared home that did not answer, places nothing: the rows are held anywhere (`compile([])`) * rather than at a spelling that may miss the home's canonical one; * · each AUXILIARY home (see the module head) read once under {@link AUXILIARY_HOME_PROBE_MS} — its spellings, its * chain, its rows' heads — at the same time; whatever of it did not answer costs that home alone and never `whole`; * · the ROOTED rows' heads ({@link readDenyLinksOf}), read whether or not a home answered: a head that did not answer * leaves the placement not WHOLE, and a placement that held the rows anywhere still places them. * `compile` is the mount's own compile; the rows' patterns are read off its compile at no home. */ export declare function placeReadDenyAtHome(env: ExecutionEnv, compile: (homes: readonly string[], linked?: ReadonlyMap<string, readonly string[]>) => ReadDenyMatcher, homes?: readonly string[], signal?: AbortSignal, configRoot?: string): Promise<{ readonly matcher: PlacedReadDenyMatcher; readonly homes: readonly string[]; readonly linked: ReadonlyMap<string, readonly string[]>; readonly whole: boolean; }>; /** * A judge for a mount that is built before its placement settles (the hands band is built synchronously): the table as * `unplaced` compiles it — home-anchored rows held anywhere, the rule for a row that cannot be placed — until a * placement settles, then the placed table. `place` starts at once; `ready()` settles when the latest placement has * (either way) and, while no placement has come back WHOLE (a failed one, or one with a head that did not answer), * places again first — an unanswered lookup is never kept as the answer. A failed placement leaves the table it * replaced (the rows held anywhere, or an earlier placement), never unplaced-and-narrow. The mount holds its read faces * until `ready()`. The judge's home views are the latest settled placement's: a judgment that asks for one waits for the * placement in flight (it never starts one). */ export declare function readDenyPlacedLater(unplaced: PlacedReadDenyMatcher, place: () => Promise<{ readonly matcher: PlacedReadDenyMatcher; readonly whole: boolean; }>): { readonly matcher: PlacedReadDenyMatcher; readonly ready: () => Promise<void>; };