UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

116 lines (115 loc) • 7.31 kB
/** * The ONE resolution of a call's EFFECTIVE PATH TARGET — the absolute path a path-targeting tool will * actually open — derived from the tool's OWN declaration ({@link ToolCallPathTarget}'s `base` / `absent` / * `patternParam`) rather than guessed, separately, by every fence. * * ## Why this module exists * * The permission-rule lanes resolved a call's path with one hard-coded rule (`liveCwd ?? root` for a * relative spelling, "no verdict" for an absent slot) while each tool resolved its own target its own way. * The three shapes that rule got wrong were all silent — a rule that names the very directory the tool * reads simply never fires: * * · Grep/Glob with NO `path` search the whole ROOT; the lanes read the absent slot as "no path", so a * deny on the root reached nothing; * · Glob with an ABSOLUTE `pattern` and no `path` searches the base the pattern carries * (`/proj/secret/**` ⇒ `/proj/secret`); the lanes never looked at `pattern`; * · Grep/Glob resolve a RELATIVE `path` against the ROOT (their describes say so, and both tools hand * `rootCanonical` to the resolver), while the lanes resolved it against the live `cd` — so after a * `cd` into a sibling directory the lane judged a path the tool would never open. * * The fix is not a fourth arm in the lanes: it is that "which base does this tool resolve against, and what * does it target when the slot is empty" is a FACT OF THE TOOL, so the tool declares it once and both * sides read the declaration. {@link pathTargetBaseOf} is the shared SELECTION (which base), and each side * then applies its own identity to the selected base — lexical-normal here (the rule family's one path * identity, zero IO), canonical (symlink-resolving, IO) inside the tool. * * ## What it does NOT decide * * Nothing here judges anything: it answers "which absolute path is this call about". Containment, the * sensitive-path deny face and the rule precedence walk are unchanged and still run where they ran. * `undefined` means "this call has no path this reading can locate" — the documented fail-toward-asking * posture of both lanes (no refusal on the tightening side, no grant on the allow side). * * RepoMap shares the search tools' root-relative `path` semantics but declares NO path target at all (no * rule family reaches it, and the rule grammar is derived from the catalog's path targets), so it has no * declaration to read; the day it declares one, it reads the same selector as everything else. */ import { type PathFamily, type PathIdentity, type Win32NamespaceRefusal } from "../tools/fs/safety.js"; import type { ToolCallPathTarget } from "./tool-face.js"; import { type ToolCallLike } from "./tool-registry.js"; /** The bases of ONE CALL a target resolves against: the task root, and the live tracked working directory * (absent when no tracker moved or the caller keeps none). Facts of the call, never of the tool. The * tree's FAMILY is read from them once per call ({@link pathFamilyOf}). */ export interface ToolPathBases { readonly root?: string | undefined; readonly cwd?: string | undefined; } /** * WHICH base this target's relative spellings resolve against, as SPELLED by the caller (no normalization: * the tool hands this straight to `canonicalizeTarget`, which resolves win-form and symlinked spellings the * fences must not pre-fold). `undefined` ⇒ the deployment gave no such base, so a relative spelling in this * slot names nothing this reader can locate. */ export declare function pathTargetBaseOf(pt: Pick<ToolCallPathTarget, "base"> | undefined, bases: ToolPathBases): string | undefined; /** * design/116 批④ #8 (CC 2.1.198 RWp, bundle :332987) — decompose an ABSOLUTE glob pattern into a base * directory + relative pattern: cut at the last separator before the first glob metachar (`* ? [ {`); a * pattern with no metachar splits dirname/basename. `undefined` for a relative pattern (the caller keeps it * as-is) or when no usable base can be derived; a namespace refusal ({@link win32NamespaceScreen}, run * BEFORE the metachar scan so `\\?\` is a prefix, never a `?` wildcard) is answered by its word. * Absoluteness and the separator set are the FAMILY's: on a POSIX tree `C:\\x\\*` is a relative pattern. * * Lives HERE rather than beside the search engine because two readers need the identical cut: Glob's own * scope resolution and the path fences that must know which directory an absolute pattern really searches. */ export declare function splitAbsoluteGlobPattern(pattern: string, family: PathFamily | undefined): { baseDir: string; relativePattern: string; } | { refused: Win32NamespaceRefusal; } | undefined; /** * The reading of a call's path slot under ONE declaration: `path` = the absolute target's identity; `unreadable` * = the slot spells a target but the base it needs is not a usable base of this tree (fed to TIGHTENING rows * only, as the ask a person clears — never to the allow arm); `refused` = a namespace refusal of the * spelling itself (a rule speaks about no such spelling: no verdict, no ask — the fs throat delivers the * refusal with the same word); `undefined` = no target in this reading. */ export type PathTargetReading = { readonly kind: "path"; readonly identity: PathIdentity; } | { readonly kind: "unreadable"; readonly reason: string; } | { readonly kind: "refused"; readonly reason: Win32NamespaceRefusal; } | undefined; /** * The absolute target this call is about under ONE reading of its path slot, as a {@link PathTargetReading}. * `pt` is the reading (design/388 B6/B17: the PROTECTIVE target for a tightening fence, the DECLARED target * for a grant) — this function never picks between the two. * * The order is the tool's own: * ① a spelling in the slot wins — fully qualified as spelled, otherwise against {@link pathTargetBaseOf}'s base; * ② otherwise, when the declaration says an absent slot means `none`, the call is about no path; * ③ otherwise a `patternParam`'s ABSOLUTE pattern supplies its own base directory; * ④ otherwise the base itself (the tool searches all of it). * * A base the deployment did not supply yields `undefined`: a relative spelling with nothing to resolve * against names no file, and claiming it names `/<spelling>` was a fabrication no fence should judge. */ export declare function effectivePathTargetOf(call: ToolCallLike, pt: ToolCallPathTarget | undefined, bases: ToolPathBases): PathTargetReading; /** * The SELECTION half of {@link effectivePathTargetOf}, before any identity is applied: the spelling the tool opens and * the base it resolves that spelling against — the slot's own value, or an absolute pattern's base directory — or, * with `spelled` absent, the base itself (the tool searches all of it). A caller that resolves canonically (the tool, * the read face's own station) reads this; the rule lanes read the lexical identity above. Same order as above. */ export declare function effectivePathSpellingOf(call: ToolCallLike, pt: ToolCallPathTarget | undefined, bases: ToolPathBases): { readonly spelled?: string; readonly base: string | undefined; } | { readonly refused: Win32NamespaceRefusal; } | undefined;