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