import { Type } from "typebox";
import type { ExecutionEnv } from "../../internal/harness-types.js";
import { type FsViolation, type ReadFileState } from "./safety.js";
import { type DecodedTextFile } from "./encoding.js";
import { type HomeViewsCarrier } from "./read-deny-home.js";
import { type ImageDownsampler } from "../../core/mcp.js";
import { type EngineNotice } from "../../core/types.js";
/**
 * The "hand" tool band — built per task over an injected {@link ExecutionEnv} and a per-task
 * {@link ReadFileState}, closure-captured (the codebase tool idiom; no `ToolExecuteContext` growth).
 * Every edit/write enforces the CC-ported safety invariants (read-before-edit, content-hash staleness,
 * edit uniqueness, write-back) plus rootPath containment. The band is the file tools
 * read/edit/write/grep/glob plus a shell: `bash` (effect:write, full shell) for normal tasks, or
 * `bash_readonly` (effect:read, allowlisted) under the verifier read-only boundary.
 */
/** Byte cap on a single read (CC `MAX_OUTPUT_SIZE = 0.25*1024*1024`). A DEFAULT whole-file read over
 *  this is refused with guidance; an explicit offset/limit slice is served, which keeps
 *  read-before-edit satisfiable on large files. */
export declare const MAX_READ_BYTES: number;
/** Hard MEMORY upper bound on a sliced (explicit offset/limit) read. The offset/limit
 *  escape hatch bypasses the whole-file byte cap above, but the reader still loads the ENTIRE file into
 *  memory (`readBinaryFile`) before slicing by line — so without this cap, an explicit `offset:1,limit:1`
 *  on a multi-GB file is an OOM lever the model can pull. This bounds the bytes loaded into MEMORY, not
 *  the output (the output stays bounded by the token cap): files over this are refused even with an
 *  explicit slice, with guidance to stream a portion via bash (`sed -n` / `head -c`) instead, which
 *  never loads the whole file. */
export declare const SLICED_READ_MAX_BYTES: number;
/** The byte cap Edit refuses ABOVE (CC's own constant, verbatim). CC stats the target before touching
 *  it and refuses with a formatted-size message when it exceeds this. Without the gate, Edit goes
 *  straight to a whole-file `readBinaryFile` (twice: the `old_string:""` full-overwrite branch and the
 *  normal find/replace branch), so a multi-GB target is an unguarded OOM lever an ordinary Edit call
 *  can pull. The shape is Read's: a pre-read `env.fileInfo` stat plus a post-read TOCTOU recheck
 *  ({@link SLICED_READ_MAX_BYTES} above). */
export declare const MAX_EDIT_BYTES: number;
/** `Edit`'s human-readable byte-count formatter (CC `pl()` verbatim: bytes below 1KB, else
 *  KB/MB/GB with one decimal, trailing ".0" stripped). */
export declare function formatByteSize(n: number): string;
/** Node's own string-length ceiling (`buffer.constants.MAX_STRING_LENGTH` — ~512MiB on V8/Node 24)
 *  sits BELOW `MAX_EDIT_BYTES` (1GiB, CC's constant, which was never cross-checked against a host
 *  engine's actual limit). A file whose size lands between the two thresholds passes Edit's byte-count
 *  gate (both the pre-read `fileInfo` stat and the post-read TOCTOU recheck) and still crashes
 *  `decodeTextBytes`'s `Buffer#toString` with an uncaught `RangeError` — no race needed, reachable
 *  through the `old_string:""` branch's documented "no read-before-edit gate" path. The model would
 *  then see a bare, unactionable "Cannot create a string longer than…" instead of the "too large to
 *  edit" guidance the byte gate exists to provide. A numeric threshold pinned to today's engine
 *  constant would be fragile across Node/V8 versions (and would do nothing for Read's own decode call
 *  if a future engine lowered the ceiling further), so this catches ANY decode failure, at whatever
 *  byte count actually broke, and reports it exactly like the byte-count gate would have. */
export declare function decodeEditBytes(bytes: Uint8Array, path: string): {
    ok: true;
    value: DecodedTextFile;
} | {
    ok: false;
    message: string;
};
/** The edit size-ceiling refusal. One definition for every gate that can hit it (the pre-read stat, the
 *  post-read TOCTOU recheck, the binary-body recheck, and the decode failure above): a size refusal the
 *  model reads differently depending on which gate fired reads as several different limits. */
export declare function tooLargeToEditMessage(path: string, bytes: number): string;
/** The truncated-UTF-16 repair instruction, parameterized by the tool that refused. The condition is one
 *  condition — an odd-length UTF-16 body cannot be decoded — reached from Edit, Write and NotebookEdit. */
export declare function truncatedUtf16BodyMessage(tool: string, path: string): string;
/** Why a bash_readonly refusal is terminal rather than escalated. Appended by every arm of that refusal
 *  family; the arms differ in what they found, never in what the model can do about it. */
export declare const BASH_READONLY_CONFINEMENT_NOTE = "bash_readonly is confined to the workspace roots; it has no approval path, so the call is refused rather than escalated.";
/** The readFileState hash coordinate for a file that was JUST WRITTEN via
 *  `encodeTextForFile`: decode the EXACT payload handed to `env.writeFile`, i.e. what the next
 *  re-read's `decodeTextBytes` will produce. A blanket `normalizeFileText` is WRONG on the
 *  encode-preserving Edit lanes: a leading U+FEFF in the new text is stripped by the next read only
 *  when the target is plain no-BOM utf8 (it becomes the file's BOM); behind a preserved utf8-BOM or
 *  the always-emitted utf16le BOM it SURVIVES as body text, so stripping it here would invert the
 *  false-stale defect for those targets. Round-tripping the real bytes sidesteps the case analysis
 *  and stays correct if the encoding rules ever change. */
export declare function persistedTextOf(encoded: string | Uint8Array): string;
/**
 * One APPLIED edit as the Edit tool's `details` card records it: the RESOLVED old/new strings
 * (post quote/escape resolution, post deletion-newline widening) in the normalized originalFile
 * coordinate. This is the card shape a transcript replay must re-apply, so the application step is a
 * single source shared by the Edit tool's own apply loop and the replay — two spellings of "apply"
 * could silently drift (e.g. one expanding `$&` and one not) and the replay's recomputed hash would
 * refuse edits the tool actually made.
 */
export interface RecordedEdit {
    oldString: string;
    newString: string;
    replaceAll: boolean;
}
/**
 * Apply ONE recorded edit mechanically (no matching layers, no validation: the recording site
 * already resolved and validated the strings; a replay consumer that cannot prove `oldString` is
 * present must fall back to retracting its record, never guess here).
 *
 * Two load-bearing details, both shared with the Edit tool's historical inline apply:
 *  - Function-form replacement: the callback's return value is NOT interpreted as a replacement
 *    pattern, so `$&`/`$$`/`` $` ``/`$'` in `newString` are written literally (the replaceAll
 *    split/join path is literal by construction).
 *  - `oldString === ""` is the whole-content OVERWRITE shape: the Edit tool's whitespace-only-file
 *    overwrite lane records `{oldString: "", newString: <full new text>}` after REPLACING the file's
 *    content wholesale (fs-write.ts, the `old_string:""` existing-file branch). The tool's own apply
 *    loop never reaches this arm (checkEditMatch refuses an empty old_string before application), so
 *    it exists for the replay consumer alone — a plain `replace("", ...)` would PREPEND instead.
 */
export declare function applyRecordedEdit(text: string, edit: RecordedEdit): string;
/** Render the read-before-edit refusal, appending the slice/bash escape hatch when the target is over
 *  the Read byte cap — a bare "read it first" is un-followable there (a default whole-file Read
 *  refuses such files). Best-effort: an un-stattable file keeps the bare CC-verbatim refusal.
 *
 *  `fallbackHint` is a caller-supplied parenthetical for the case NO branch below diagnosed anything
 *  (all three writing tools pass {@link READ_REFUSED_ESCAPE_HINT} — the Read-refuses-this-file
 *  deadlock, which by construction leaves nothing to stat). It is NOT appended beside a sharper hint:
 *  both sharper shapes describe files Read handled successfully, so the encoding-deadlock sentence
 *  would be false there — and stacking two escape routes on one refusal buries the one that applies. */
export declare function notReadRefusalText(env: ExecutionEnv, toolName: string, key: string, v: Pick<FsViolation, "code" | "message" | "partialView">, signal?: AbortSignal, fallbackHint?: string): Promise<string>;
/** The no-downsampler cap (`MAX_IMAGE_READ_BYTES`, core/image-admission.ts) is a DELIBERATE bound and does not move — but its refusal must not
 *  read like a hard ceiling. Whether the higher cap is reachable depends on an OPTIONAL dependency the
 *  caller can install, and only this package knows that, so the refusal names the move instead of
 *  leaving the caller to guess that the limit is negotiable. Appended ONLY on the no-downsampler
 *  branch: with sharp already resolved the hint would be false advice. */
export declare const NO_DOWNSAMPLER_IMAGE_CAP_HINT = "Install the optional \"sharp\" dependency (declared in this package's optionalDependencies) to read images up to 67108864 bytes \u2014 it downsamples them to fit the API image limit.";
/** Read-image downsampler seam: `undefined` → auto-detect sharp; `false` → force-disabled (deterministic
 *  no-sharp path, used by tests + envs that must not touch native deps); a function → injected. */
export type ReadImageDownsamplerOption = ImageDownsampler | false | undefined;
/** Resolve (and memoize) the auto-detected sharp downsampler, process-wide. Shared accessor for the two
 *  consumers of the module-private {@link autoDownsampler} cache. */
export declare function resolveAutoDownsampler(): Promise<ImageDownsampler | undefined>;
/** Output-token cap as a char proxy (CC's 25000, at ~4 chars/token). Caps the shown body so 2000 very
 *  long lines cannot blow the context budget.
 *
 *  Only the 25000 NUMBER is CC's. CC does NOT use a flat chars/4 proxy — it pre-filters with a
 *  PER-EXTENSION chars-per-token coefficient (json/jsonl = 2, half the 4 assumed here) and then
 *  confirms with a REAL tokenizer; its budget is also overridable per call and by a feature gate,
 *  neither of which has a sema lane. Consequence, stated rather than hidden: on token-dense text
 *  (JSON/CJK/base64) this proxy UNDER-counts, so sema serves a page CC would have cut further; on
 *  token-sparse text it over-counts and paginates earlier than CC. A real tokenizer here would need a
 *  provider-neutral counting seam (BYOM — no single vocabulary), which is why the proxy stands; the
 *  divergence is a fidelity delta, not a correctness one (the partial-view marker always states the
 *  served window truthfully). */
export declare const MAX_READ_OUTPUT_CHARS = 100000;
export declare const BASH_DEFAULT_TIMEOUT_SEC = 120;
export declare const BASH_MAX_TIMEOUT_SEC = 600;
export declare const BASH_DEFAULT_TIMEOUT_MS: number;
export declare const BASH_MAX_TIMEOUT_MS: number;
/**
 * The smallest timeout that can be asked for in ms without the request being destroyed on the way in.
 *
 * The shell's internal unit is SECONDS while every face of this tool speaks milliseconds, so each ms
 * value passes through `Math.max(1, Math.round(ms / 1000))`. That expression has a PLATEAU: every input
 * from 1ms up to 1499ms — plus zero and every negative number — comes out as the same 1-second budget.
 * Inside the plateau the number a caller wrote is not approximated, it is erased, and nothing said so:
 * an operator who wrote 120 meaning two minutes got a shell where every command died after one second,
 * and the timeout text then reported `timeout limit: 1s` in the same voice it uses for a real budget.
 * A model reading that concludes the environment imposes a one-second limit and abandons every real
 * command it was asked to run.
 *
 * So the plateau is refused rather than entered silently, and the refusal splits by cause: a positive
 * value under a second is almost certainly a SECONDS number in a milliseconds slot (the units are three
 * orders of magnitude apart), while a value between one and one-and-a-half seconds is a real
 * milliseconds request whose remainder simply cannot survive — it must be spelled as the exact 1000 it
 * would become, or raised past the plateau. Zero and negatives have no reading at all.
 */
export declare const MIN_BASH_TIMEOUT_MS = 1000;
/** The first value ABOVE the collapse plateau: `Math.round(1500 / 1000)` is the first result that is not
 *  the 1-second floor. Between {@link MIN_BASH_TIMEOUT_MS} (exclusive) and this, every request lands on
 *  the floor with its remainder discarded. */
export declare const BASH_TIMEOUT_PLATEAU_END_MS = 1500;
/**
 * A host-supplied failure `message` is untrusted as a CARRIER OF FACTS: an execution env whose adapter
 * hands back an empty (or whitespace-only) string turns any text built as `Error (X): ${message}` into a
 * bare `Error (X):` — a card with a red badge and nothing to act on, which is exactly what a model (and
 * a person) reads as "the engine broke and won't say how". Use this wherever the message IS the whole
 * fact; where it only decorates a sentence the engine wrote itself, interpolate it directly.
 */
export declare function envErrorDetail(message: string | undefined): string;
/**
 * The `timeout` parameter's model-facing description, rendered from the SAME caps the runtime enforces
 * and the SAME plateau the refusal applies — one text for both shell legs so a boundary can never be
 * stated on one card and enforced on the other. `withDefault` includes the default budget (the
 * read-only leg's historical wording; the full `bash` leg states its default in the tool description).
 */
export declare function bashTimeoutParamDescription(caps: {
    defaultMs: number;
    maxMs: number;
}, withDefault: boolean): string;
/**
 * The model-facing `timeout` argument's validity rule — returns the refusal text when the value cannot
 * be honored, `undefined` when it can. Shared by both shell legs (`bash` and `bash_readonly`: one rule,
 * both entry points). Absent is always fine — that is the request to use the configured default.
 *
 * A refusal rather than a clamp: clamping a plateau request produced a one-second budget the caller
 * never asked for and then reported it back as the caller's own limit (see {@link MIN_BASH_TIMEOUT_MS}).
 */
export declare function bashTimeoutArgRefusal(timeoutMs: number | undefined, caps: {
    defaultMs: number;
    maxMs: number;
}): string | undefined;
/**
 * The SINGLE authority for the Bash `timeout` parameter's two numbers: the default budget applied
 * when the model omits `timeout`, and the engine ceiling requests are clamped to. The 120s/600s
 * constants themselves are CC-same; what this resolver adds on top is configurability.
 *
 * Resolution, per leg (options over env over constant — the standing sema discipline: "deployments
 * configure the tool, not the process"; env stays as the CC-shaped fallback for process-level tuning):
 *   defaultMs = opts.bashDefaultTimeoutMs ?? env BASH_DEFAULT_TIMEOUT_MS ?? {@link BASH_DEFAULT_TIMEOUT_MS}
 *   maxMs     = Math.max(opts.bashMaxTimeoutMs ?? env BASH_MAX_TIMEOUT_MS ?? {@link BASH_MAX_TIMEOUT_MS}, defaultMs)
 * The outer `Math.max` is CC's own only-widen semantic (its env max resolves through the same form): a
 * ceiling below the resolved default is a NARROWING intent = invalid — it is ignored and the resolved
 * default floors the cap. Invalid values (non-finite / <= 0) are discarded at every leg, options
 * included, exactly like CC's env read.
 *
 * Readable seam: a shell/host rendering its OWN copy of the Bash tool description MUST
 * read the true values from here instead of holding a sibling constant — the engine's description/param
 * text and its runtime clamp both interpolate this function's output (resolved once per tool instance),
 * so a self-held "600s" only matches by coincidence and forks silently the moment a deployment
 * configures either knob.
 */
export declare function resolveBashTimeoutCaps(opts?: {
    bashDefaultTimeoutMs?: number;
    bashMaxTimeoutMs?: number;
    /** Structured seat for the discard announcements below — wired, it REPLACES their `console.warn`
     *  (same ledger, same lines as `message`); absent, the console line prints verbatim. Threaded from
     *  the toolkit assembly (ultimately `RunnerDeps.onNotice`); never a global. */
    onNotice?: (n: EngineNotice) => void;
}): {
    defaultMs: number;
    maxMs: number;
};
/** Test seam: the announcement de-dupes per sink (console arm per process), so a test that asserts
 *  the line must be able to re-arm the ledger. Re-arms BOTH arms (a WeakMap has no clear — it is
 *  re-minted). Never called by production code. */
export declare function __resetBashTimeoutAnnouncements(): void;
/** The seconds view of a resolved {@link resolveBashTimeoutCaps} pair — the shell's INTERNAL unit
 *  stays seconds while every model-facing surface stays ms. One rounding
 *  point, same Math.round form as the model-supplied timeout's own ms→s conversion, floored at 1s. */
export declare function bashTimeoutCapsSec(caps: {
    defaultMs: number;
    maxMs: number;
}): {
    defaultSec: number;
    maxSec: number;
};
/** Read per call (a deployment steers it without a restart); the one shared resolver holds the grammar,
 *  the `[OUTPUT_BUDGET_MIN_CHARS, ceiling]` range and the loud outlet for a value it could not honor. */
export declare function bashMaxOutputChars(): number;
export declare const FILE_PATH_PARAMS: {
    file_path: Type.TString;
};
/**
 * Write's pre-validation argument repair — CC 2.1.296 `orr` (the Write tool's `coerceInput`, cli296.js:247143),
 * the two rules that matter under a non-strict schema: `path` is read as `file_path` when `file_path` is absent
 * and `path` is a string; exactly one of `file_text` / `file_content` is read as `content` when `content` is
 * absent and the value is a string. Anything else — both content aliases, a present `file_path`, a non-string
 * value — is left to the schema, which refuses it by naming the parameter. The repaired key REPLACES the alias
 * (it is deleted), so no second spelling rides on into the gates. Idempotent, and the very same object for a
 * call that needs no repair (the loop's "prepared arguments changed" test is a reference test).
 */
export declare function coerceWriteArguments(args: unknown): unknown;
/** Honest head+tail truncation to {@link bashMaxOutputChars}: the middle is dropped with a real-total
 *  marker, because the head AND tail both matter for a shell. Kept as the inline shape even beside the
 *  file-backed recovery below — unlike a background task's poll-for-the-latest-tail interaction, a
 *  foreground command's head can carry status a bare tail-keep would drop. Shared by runShell
 *  (foreground) and TaskOutput (background poll). Delegates its arithmetic to the shared
 *  {@link clipWithFilePointer} but never exercises its pointer arm — no caller here passes a path. */
export declare function clipShellOutput(s: string): string;
/**
 * A foreground command's captured stdout/stderr are ALREADY fully in memory here (the env delivered
 * them whole — only `clipShellOutput` above decides how much of that the model's turn actually sees).
 * What this closes is that the omitted middle would otherwise be discarded, forcing a blind full
 * re-run to recover output that was, in fact, captured. "Whole" means whatever THIS layer received,
 * not a guarantee about what the process originally wrote: the env's own rolling-tail cap
 * (`MAX_EXEC_OUTPUT_BYTES`, exec-output-tail.ts) can already have dropped the head before delivery on
 * a truly enormous single command — this file is a faithful copy
 * of the received bytes either way, and still carries that cap's own `markTruncated()` marker when it
 * fired, so nothing here overclaims completeness beyond what the text itself already discloses. Purely
 * ADDITIVE: the inline head+tail framing above is untouched; this only gives the model a second, explicit
 * route to the bytes that framing dropped. Writes BOTH streams (not just the one that overflowed) to ONE
 * combined file — same shape TaskOutput's A5 mechanism uses (`stdout + stderr`, single pointer) — so the
 * model gets one path regardless of which stream tripped the clip. Uses `env.createTempFile`/`writeFile`/
 * `canonicalPath` — the standard `FileSystem` primitives every {@link ExecutionEnv} backend implements —
 * NOT the Node env's internal exec spool, so this works uniformly across backends, not just the Node adapter.
 *
 * Deliberately NOT wired into the Read-tool root-fence exemption (unlike TaskOutput's A5 file): that
 * exemption is keyed off a TaskRegistry `background_bash` row with a live lifecycle (owner/scope/session
 * visibility, membership dies with the row) — a one-shot foreground command has no such row, and building
 * one purely to carry a read-exemption would be a parallel, un-owned lifecycle with no natural teardown.
 * `bash` itself is NOT root-fenced at the command-text level (only Read/Edit/Write/Grep/Glob enforce path
 * containment on their explicit path arguments), so the model already has a direct route to the file: a
 * later `bash` call — the message text says so explicitly rather than implying Read will work, and picks
 * an example command the CALLING tool variant can actually run (see {@link shellRecoveryHint}: `sed -n`
 * for full `bash`, an allowlisted `tail -c` for `bash_readonly`). Creation/write failure degrades
 * silently to no file (the inline clip is unaffected either way), matching the background lane's own
 * degrade posture.
 */
export declare function writeShellOverflowFile(env: ExecutionEnv, stdout: string, stderr: string): Promise<string | undefined>;
/**
 * Recognise the engine's OWN shell overflow recovery files, so `bash_readonly` can keep honouring the
 * `tail -c … <file>` command its result trailer advertises.
 *
 * NOT a `Set` of the exact paths one tool instance minted: the trailer lives in the transcript, not in
 * the instance, and the hands toolkit is rebuilt on every task preparation — a later run replaying the
 * advertised command would meet a tool with no memory of it and be refused, i.e. the engine refusing
 * its own advice, and (in the read-only band, whose Read tool is fenced by the same roots) the only
 * route to the truncated output.
 *
 * Judged by SHAPE instead, which survives any rebuild, with the grant kept pinned to the engine's own
 * temp area on three counts that must ALL hold:
 *   1. the basename is the engine's mint spelling ({@link isShellOverflowFileName}) — carrying the
 *      backend's unguessable unique token, so another session's recovery file cannot be named blind;
 *   2. the file sits inside the area the backend hands out temp paths from, learned by minting a probe
 *      temp directory through the SAME backend (`createTempDir`) and taking its parent, then removing the
 *      probe. Learned rather than assumed because the area is the backend's business — this file must not
 *      grow an `os.tmpdir()` assumption that a non-Node {@link ExecutionEnv} would not honour;
 *   3. its depth under that area is the mint's own — the area itself, or one directory below it (the Node
 *      backend puts each temp file in a fresh `mkdtemp` directory). So a deep path that merely happens to
 *      live under the temp area is not covered either.
 * Every failure to learn the area (backend refuses, probe unresolvable) yields `false`: no exemption, i.e.
 * the refusal stands. The probe runs at most once per fence and only for a path that already passed (1),
 * so an ordinary refusal never touches the filesystem.
 *
 * What this does NOT grant: any path outside the temp area (a `/etc/passwd`, a user file, a sibling
 * project) — those fail (2) no matter what they are named; and any neighbour inside the temp area that is
 * not named like a mint — those fail (1). The narrowing this trades away against the exact-path Set is
 * precisely "a file in the engine's temp area, named with the engine's prefix AND a valid unique token
 * the caller would have to already know".
 */
export declare function createShellOverflowSpoolFence(env: ExecutionEnv): (path: string) => Promise<boolean>;
/**
 * The recovery-file trailer's example command must be one the
 * CALLING tool can actually run. `sed` is not in {@link import("./bash-readonly-classifier.js").BASH_READONLY_DEFAULT_ALLOW} and `bash_readonly`
 * forbids the operators a pipe/redirect would need, so a `bash_readonly` command that overflows gets a
 * single-command, allowlisted alternative instead (`tail -c`, byte-bounded so it doesn't depend on line
 * shape) — strictly less precise than `sed -n 'START,ENDp'` (no arbitrary mid-file range), but actually
 * executable. Full `bash` keeps the precise form.
 */
export declare function shellRecoveryHint(path: string, readOnly: boolean | undefined): string;
/** CC 2.1.198 live-verbatim Edit/Write success trailer — appended to every Edit/Write success message
 *  (NOT NotebookEdit — CC ships it without the trailer). */
export declare const FILE_STATE_TRAILER = " (file state is current in your context \u2014 no need to Read it back)";
export declare const CWD_SENTINEL = "__cc_cwd_9f2c1b__";
/**
 * Take the engine's own cwd epilogue out of a finished stderr, and report the working directory the
 * command left behind (the payload of the LAST span).
 *
 * EVERY span, and the LAST one — not the first, and not just one. A command that turns on shell tracing
 * produces more than one (the shell echoes the epilogue's own source, marker and all, before running
 * it), and under a one-pair rule the FIRST pair was that echo, whose payload is the unexpanded
 * `' "$(pwd)" '`; a `cd` that plainly worked was then dropped while the real record stayed in the stderr
 * a person reads. An opener with no close is NOT ours to eat — the command's own text is left exactly
 * as it arrived.
 *
 * TWO CALLERS, ONE RULE, which is why the marker is optional. The foreground result path KNOWS the
 * marker it minted and passes it (exact, unambiguous). A backgrounded command's spool is assembled by a
 * different lane entirely — the shell row outlives the run that launched it and never had the marker —
 * so it passes none and the marker is recovered per span from the sentinel plus its hex suffix.
 */
export declare function stripCwdSentinel(stderr: string, marker?: string): {
    stderr: string;
    cwd: string | undefined;
};
/** Convert a model-supplied millisecond timeout to our internal seconds, clamped. Exported for the
 *  cc-parity unit (model sees ms, internal stays seconds — no ×1000 error).
 *  ⚠️ This returns the EFFECTIVE budget, not the request — capping at a call site destroys the
 *  caller's number before it can be disclosed. The Bash legs deliberately do NOT use it (they pass the
 *  uncapped conversion into runShell, which caps once and reports the true request); use this only
 *  where the post-ceiling value is what you mean.
 *  The clamp bounds are not the bare constants — pass the caller's resolved caps
 *  ({@link resolveBashTimeoutCaps}) to clamp against a configured deployment; omitted ⇒ resolve fresh
 *  (zero-config = the historical 120s/600s, byte-compat).
 *  ⚠️ This is the RAW converter and NOT the authority on what the tool accepts: it still folds the whole
 *  collapse plateau (see {@link MIN_BASH_TIMEOUT_MS}) onto a 1-second budget, while the shell legs refuse
 *  those inputs outright via {@link bashTimeoutArgRefusal}. A host rendering its own view of what a given
 *  request would cost must consult the refusal first, or it will advertise a budget the engine declines
 *  to run. No production caller inside the engine reaches this function. */
export declare function msTimeoutToSec(timeoutMs: number | undefined, caps?: {
    defaultMs: number;
    maxMs: number;
}): number;
/** `.ipynb` redirect (CC FileEditTool parity): a raw text edit/overwrite corrupts the
 *  notebook JSON, so the text-edit face refuses and redirects to the NotebookEdit tool (built in
 *  src/tools/fs/fs-write.ts, mounted with the rest of the hands band). */
export declare function ipynbRedirect(toolName: string, path: string): string | undefined;
/** Count the lines of text content. `split("\n")` yields a trailing "" for newline-terminated content
 *  (e.g. "a\nb\n" → ["a","b",""]); that empty fragment is NOT a real line, so drop one when the content
 *  ends in "\n". Behavior is identical for content WITHOUT a trailing newline. Shared by read_file's
 *  `total`, edit_file/write_file's "N lines" report, and the persisted ReadFileState.totalLines. */
export declare function countLines(s: string): number;
/** CC 2.1.204 VERBATIM: the reminder served when a DEFAULT whole-file Read hits a startup-seeded,
 *  unchanged file. `filePath` is the CANONICAL key (CC passes the resolved full path, matching the
 *  `Contents of <path>` header its context seeding emits — deployments seeding files should title the
 *  injected block the same way so the back-reference lands). Locked verbatim by test: the BODY and
 *  close tag stay CC-verbatim; the open tag alone is rendered by the mint home and carries the session
 *  mark when one is threaded (the recorded wire-form divergence). */
export declare function seededFileUnchangedReminder(filePath: string, mark?: string): string;
/**
 * True when `resultText` is one of the Read tool's TWO dedup markers (the ordinary
 * unchanged-since-last-read stub below, or {@link seededFileUnchangedReminder}) rather than a genuine
 * content transmission. A consumer deciding "is this file's real content present in THIS message"
 * (e.g. auto-compaction's kept-tail visibility scan) must treat a stub hit as absence — the marker
 * POINTS AT an earlier read that may itself no longer be in context.
 *
 * Both emitters ALSO carry a structured marker (`details.type === "file_unchanged"`,
 * fs-read.ts `fileUnchangedResult`) — consumers with access to the tool result's `details` should
 * branch on that instead of this prose match (which can false-positive on file CONTENT that quotes
 * either sentence). This predicate stays for text-only surfaces (compaction sees rendered text).
 */
export declare function isReadDedupStubResult(resultText: string): boolean;
/** Pre-seed the hands' read state for a file whose FULL, disk-verbatim text was injected into the
 *  model's context at startup (CC 2.1.204 `seededFromContext` seeding of the memory preload; sema:
 *  ProjectMemoryLoad.seededFiles → prepare-task).
 *  `content` MUST be the file's exact disk text at load time — the Read tool's seeded dedup treats a
 *  hash match as "unchanged"; a truncated/annotated variant must NOT be seeded (CC exempts those via
 *  `isPartialView`; sema's contract is simply "don't seed partials"). `key` is the CANONICAL path
 *  (resolveKey output), so it collides correctly with real Read/Edit entries. Note the CC-faithful
 *  side effect: a seeded file passes read-before-edit (the model legitimately has the full text). */
export declare function seedReadFileStateFromContext(state: ReadFileState, key: string, content: string): void;
/**
 * Seed the hands' read state from a WHOLE-file content the session transcript recorded on an earlier
 * turn (the selection rules live in core/runner/session-file-state-replay.ts).
 *
 * Same minting as {@link seedReadFileStateFromContext} and the same normalization reason, with three
 * deliberate differences, each of which is a claim this seed cannot honestly make:
 *  - `lastReadAt` is the RECORDED moment, not now. It is what the changed-files detector compares an
 *    mtime against, so an honest old stamp keeps a between-turns modification detectable; stamping
 *    "now" would hide exactly the change the model needs told about.
 *  - no `view`, so the Read dedup stub can never fire off this entry. The dedup answers a re-read
 *    with "unchanged since you last read it" — true only while the earlier read's body is still IN
 *    the context, which a continuation (or a compaction inside it) cannot promise.
 *  - no `seededFromContext`: that flag means the text lives in the system-prompt lane and survives
 *    compaction. This text lives in the transcript and does not.
 */
export declare function seedReadFileStateFromTranscript(state: ReadFileState, key: string, content: string, lastReadAt: number): void;
/**
 * Copy a SESSION-inherited read-file state into a new task's map, under the new task's
 * own containment judgment (CC parity: readFileState is session-scoped — minted once per REPL session,
 * mutated across turns, with no turn-boundary or abort clear site; sema's per-task map inherits the
 * previous turn's live state instead).
 *
 * Two rules, each a claim inheritance cannot honestly skip:
 *  - **Every key passes the CURRENT task's own key judgment** (`judgeKey` = the same resolveKey
 *    containment the transcript-replay seed uses: write roots ∪ read roots, resolved read face). A
 *    root or face narrowed between the turns must narrow what the state remembers, exactly as the
 *    replay leg already judges — an unjudged copy would smuggle out-of-face entries into the task
 *    (and into its checkpoints).
 *  - **`seededFromContext` entries do not inherit.** That flag claims the file's text lives in the
 *    system-prompt lane OF THIS TASK — a claim only this task's own startup seeding can make.
 *    A still-declared file is re-seeded the same turn (no loss); a no-longer-declared one must not
 *    keep answering reads with an "already in your context" reminder that is no longer true.
 *
 * Entries are copied by REFERENCE (ReadEntry values are never mutated in place — every write-back
 * `set`s a fresh object), so recorded facts (view / isPartialView / lastReadAt) ride verbatim: the
 * dedup stub and the partial-view refusal keep working across turns exactly as inside one task.
 */
export declare function inheritSessionReadFileState(target: ReadFileState, inherited: ReadFileState, judgeKey: (recordedKey: string) => Promise<string | undefined>): Promise<void>;
/**
 * CC parity: a landed compaction summarizes old Read results OUT of the context, so every non-seeded
 * read-state entry must be dropped — CC snapshots then
 * `readFileState.clear()`s at the same spot. Without this, the Read dedup stub above answers a re-read of
 * an unchanged file with "content omitted to save context" pointing at content that no longer exists
 * anywhere in the context (the model works from hallucinated memory or has to vary offset/limit to escape).
 * Two deliberate divergences from CC's bare clear(), each from sema's own structure:
 *  - SEEDED entries survive: their content lives in the system-prompt lane, which compaction never touches,
 *    so their dedup remains CORRECT (CC re-seeds the same family right after its clear — equivalent end state).
 *  - Files re-attached WHOLE into the summary re-register (content genuinely re-entered the context — same
 *    principle as CC re-registering restored file attachments); a TRUNCATED attachment does not (a partial
 *    view must not answer a whole-file read with a stub).
 * The read-before-edit gate tightens as a consequence, matching CC: after a compaction the model must
 * re-Read a summarized-away file before editing it, instead of blind-editing from a stale recollection.
 *
 * A THIRD category besides seeded/re-attached: a file whose most recent Read is still visible
 * verbatim in the compaction's KEPT tail is deliberately left OUT of
 * `attachedComplete` (re-attaching it would just duplicate content already in context), but its
 * existing read-state entry must survive unmodified for the SAME reason a seeded entry does: the
 * model's view of it did not change. Wiping it breaks two things for exactly these files — the
 * read-before-edit gate would reject an Edit the model could visibly justify from its own kept-tail
 * Read, and a next Read would miss the dedup stub and retransmit the whole file, recreating the exact
 * waste this dedup exists to remove. `preserveKeys` is the caller's kept-tail set
 * (see `maybeCompact`'s `candidateFiles` computation) — passed as keys already in `state`'s own
 * coordinate (canonicalized the same way the Read/Edit/Write tools populate it), so a plain lookup
 * suffices; no re-hash/re-register needed since the entry's content genuinely has not changed.
 */
/**
 * The SECOND way a transmission leaves the model's view, and why this function is named for context
 * SHRINK rather than compaction: the request-layer stale-result pass replaces an old tool result's
 * content with a cleared marker — no summary, no re-attachment, nothing changed in the session — and
 * for a Read result the effect on the entry is identical: the body its dedup stub points at is no
 * longer anywhere the model can see, so the entry must retire with it.
 *
 * Selection is by the TRANSMISSION, not by path — an entry a LATER Read re-transmitted carries that
 * later call's id and so survives a clear of the earlier one, which a path-keyed retirement could not
 * tell apart. KNOWN IMPRECISION: a call id is not unique over a run (the clear ledger keys occurrences
 * by id + message timestamp + ordinal for that reason) and the finer key is not reachable at the Read
 * mint, which does not know the timestamp of the message its result will land in. A reused id can
 * therefore retire one entry too many, costing a re-transmission and a tightened read-before-edit gate
 * — the same tightening the summarized arm accepts wholesale. The converse is not reachable: an entry
 * carries the id of the last Read that transmitted it, so if that result was cleared its id is in the list.
 */
export type ReadFileStateShrink = 
/** A landed compaction: `attachedComplete` re-entered the context whole; `preserveKeys` stayed visible in the kept tail. */
{
    kind: "summarized";
    attachedComplete: ReadonlyArray<{
        path: string;
        content: string;
    }>;
    preserveKeys?: ReadonlyArray<string>;
}
/** A request-layer stale-result clear: these tool calls' results are markers now. Nothing re-entered. */
 | {
    kind: "tool-results-cleared";
    clearedToolCallIds: ReadonlyArray<string>;
};
export declare function applyContextShrinkToReadFileState(state: ReadFileState, shrink: ReadFileStateShrink): void;
/** CC 2.1.198 verbatim skeleton: the ENOENT error body for a
 *  missing Read/Edit target — `File does not exist. Note: your current working directory is <cwd>.`
 *  plus a `Did you mean <sibling>?` correction when the parent directory holds a near-name (case
 *  variant / same stem different extension — the high-frequency self-heal path). Best-effort: a
 *  listDir failure just omits the suggestion. */
export declare function enoentMessage(env: ExecutionEnv, key: string, cwd: string, signal?: AbortSignal, readDeny?: {
    matchPath(path: string): {
        pattern: string;
    } | null;
} & HomeViewsCarrier): Promise<string>;
/** Per-task mutable working directory shared by the shell and the path-taking fs tools.
 *  Holds the RAW path (never canonicalized): bash `cd` updates `current`, and the fs tools resolve relative
 *  paths against it. Containment is still enforced per-op by resolveKey (canonicalize + within), so a `cd`
 *  through a symlink out of root cannot carry across a relative fs path outside. */
export interface CwdRef {
    current: string;
}
