import type { AgentTool, ExecutionEnv } from "../../internal/harness.js"; import type { BeforeWriteHook, ToolEffect } from "../../core/types.js"; import { type TaskRegistry } from "../../core/task-registry.js"; import { type ReadFileState } from "./safety.js"; import { type ImageDownsampler } from "../../core/mcp.js"; import { type PdfModelCapabilities } from "./pdf.js"; export { pdfModelCapabilitiesOf, type PdfModelCapabilities } from "./pdf.js"; /** 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; /** Default git commit-message attribution trailer (design/64 §8.1D — our release convention; configurable). */ export declare const DEFAULT_COMMIT_COAUTHOR = "Claude Opus 4.8 "; /** Convert a model-supplied millisecond timeout to our internal seconds, clamped (design/64 §8.1C). Exported * for the cc-parity TC-8.2 unit (model sees ms, internal stays seconds — no ×1000 error). */ export declare function msTimeoutToSec(timeoutMs: number | undefined): number; /** parity-204 — CC 2.1.204 VERBATIM (constants `qfc`+`Wfc`, cc204-bundle @9438179/@9440917; 198 zero * hits): the reminder served when a DEFAULT whole-file Read hits a startup-seeded, unchanged file. * CC: ``Wfc(e) = `${qfc} (see "Contents of ${e}" above) and has not changed on disk. Use that content * instead of re-reading.``` with `qfc = 'This file is already in * your context'`. `filePath` is the CANONICAL key (CC passes the resolved full path `f`, matching the * `Contents of ` 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 (逐字常量锁). */ export declare function seededFileUnchangedReminder(filePath: string): string; /** parity-204 — 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:!0` seeding, cc204-bundle * @17917159: CLAUDE.md/nested-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; /** Per-task mutable working directory shared by the shell and the path-taking fs tools (design/64 §16.3). * 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 smuggle a relative fs path outside. */ export interface CwdRef { current: string; } export declare function makeReadFileTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], imageDownsampler?: ReadImageDownsamplerOption, pdfCapabilities?: PdfModelCapabilities): AgentTool; /** * design/138 S2-C — the `beforeWrite` CONTENT hook (C-F2: mounted here, not in ToolPolicy, because * only this band sees the FINAL text an Edit/NotebookEdit produces — the applied old→new full text * is what actually lands on disk). Called before EVERY `env.writeFile` in Write/Edit/NotebookEdit * with the resolved containment key and the exact text about to be written. Returning `{ ok:false }` * makes the tool fail with a structured error and NOTHING is written. The Runner wires this to the * MemoryEngine's write gate (`MemoryEngine.gateWrite`) — a non-memory path passes with one string * prefix comparison there (零开销直通). Absent hook ⇒ byte-identical behavior. */ export type { BeforeWriteRequest, BeforeWriteResult, BeforeWriteHook } from "../../core/types.js"; export declare function makeEditFileTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], beforeWrite?: BeforeWriteHook): AgentTool; export declare function makeWriteFileTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], beforeWrite?: BeforeWriteHook): AgentTool; /** * design v1.163 — NotebookEdit: replace/insert/delete a single cell in a .ipynb. CC-parity tool over the SAME hand-band * safety skeleton as Edit/Write (resolveKey containment → requireRead read-before-edit → checkStale content-hash * freshness → mutate → writeFile → state.set), with object ops swapped for ipynb-JSON cell mutation. Read already reads * .ipynb as UTF-8 JSON (NOT refused) — that read-before-edit is what this tool's freshness check depends on. */ export declare function makeNotebookEditTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], beforeWrite?: BeforeWriteHook): AgentTool; export declare function makeGrepTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: readonly string[]): AgentTool; export declare function makeGlobTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: readonly string[]): AgentTool; /** Static side-effect class of every hand tool, by name (design/44 §3). Used by prepare-task to (a) feed * wake/resume reconciliation and (b) drive the verifier read-only boundary. `write_file` is `idempotent` * (a full-file overwrite re-applied is a no-op, S3); `bash` is `write` (a command can do anything); * `bash_readonly` is `read` (so it survives the verifier boundary — but still goes through the policy * gate, council #7: effect:read is a redo-safety class, never a policy bypass). */ export declare const HAND_TOOL_EFFECTS: Readonly>; /** * Bare command names `bash_readonly` permits out of the box, ALSO the default reversible set for the * design/80 D-2 {@link bashReversibilityProbe} classifier. **Coarse first filter, NOT a security * boundary**: {@link coarseReadonlyCheck} matches only the command NAME, so a listed command with a * writing flag (e.g. `find -delete`, `sort -o`, `tee`) would still write. Such commands are therefore * kept OFF this default — the list is curated to commands with NO write/mutation mode under ANY args, so * the `effect:"read"` declaration (relied on by wake/resume reconcile + the verifier read-only boundary) * AND the classifier's "reversible" promise both stay truthful. The deployment's tool-policy gate is the * authoritative control (design/44 §5, council blocker #1); anything that can mutate state or run arbitrary * code belongs on the full `bash` (effect:write, gated), not here. A deployment may widen this list, * accepting that responsibility. * * design/80 D-2 final-council MAJOR: `date` (`-s`/`--set` → CLOCK_SETTIME), `hostname` (`` → kernel * hostname), and `file` (`-C -m` → compiles/writes a magic file) were REMOVED — each is read-only by NAME * but state-MUTATING with args, which an argv[0]-only filter cannot tell apart. Leaving them in defeated * both the `effect:read` truthfulness here and the classifier's irreversibility promise (a `date -s` would * auto-allow an irreversible clock jump under `shellGate:"classify"`). */ export declare const BASH_READONLY_DEFAULT_ALLOW: readonly string[]; /** * The SINGLE fail-closed simple-command parser shared by `bash_readonly` ({@link coarseReadonlyCheck}), the * `bash` reversibility classifier ({@link bashReversibilityProbe}), and the coarse command-name policy * ({@link import("../../core/tool-policy.js").createCoarseCommandNamePolicy}). It extracts the leading * `argv[0]` command NAME of a SINGLE simple command, rejecting anything that could chain past or escape an * argv[0]-name filter: shell operators (pipes / redirects / `;` / `&&` / `$(…)` / subshells / backticks / * newlines / backslash), a path-prefixed command (`/usr/bin/foo`), or a leading env-assignment (`FOO=bar cmd`). * * Returns `{ name }` for a parseable single bare command, or `{ reject }` with a human reason otherwise. It * does NOT inspect ARGUMENTS for write flags or consult any allowlist — that is the caller's job (the * allowlist for `bash_readonly`, the allow/deny lists for the coarse policy). Keeping ONE parser is the whole * point: a second argv[0] parser would drift from this one and silently open a bypass. */ export declare function parseLeadingCommandName(command: string): { name: string; } | { reject: string; }; /** * design/80 D-2 (part-1): a parsed-command classifier for the `bash` tool, exposed as a * `ToolSpec.reversibilityProbe`. A full shell is treated as egress+irreversible by DEFAULT; this probe is the * "real parsed classifier" that lets a deployment safely auto-allow the provably-benign subset (the §4-OQ4 * doctrine: shell⇒always-gate UNLESS a parsed classifier is wired). It reuses {@link coarseReadonlyCheck}: * a command is reversible ONLY if it is a SINGLE bare command, with NO shell operators (so it can't chain to * `curl`/`git push`/`rm`), whose `argv[0]` is in the reversible allowlist (default * {@link BASH_READONLY_DEFAULT_ALLOW} — `ls`/`cat`/`grep`/…, curated to commands with NO write/mutation mode * under ANY args; the final-council MAJOR removed `date`/`hostname`/`file`, which mutate with args). EVERYTHING * else (a write command, an egress command, an operator/pipe, a path-prefixed or env-assigned command, a * non-string arg) is NOT reversible → the gate tightens it to an `irreversible_ask` durable suspend. Fail-closed * by construction: the allowlist is the only path to `reversible: true`. It is an argv[0]-NAME filter, so the * remaining residual is the OTHER axis — a listed reader with crafted args can still READ a secret (a DATA side * channel, NOT irreversibility/egress); a deployment that widens `allow` owns that (and the mutation tradeoff). * * Wire it on the `bash` tool with `irreversibility: "maybe"` (or via `TaskSpec.shellGate: "classify"`); without * a classifier a deployment should mark `bash` `irreversibility: "always"` (`shellGate: "always"`) — fail-closed. */ export declare function bashReversibilityProbe(allow?: readonly string[]): (args: unknown) => { reversible: boolean; }; /** * design/130 P2b — soft tool-deadline clamp option. `deadlineMs()` returns the ABSOLUTE (ms epoch) * soft execution deadline for a foreground shell command, or undefined when inert (no wall-clock * budget / graceful finalize off / resource-slice task). When the requested/default timeout would * run past it, the timeout is clamped down and `onClamp` fires (telemetry — * `stats.mechanisms.toolClamps`). */ export interface ExecClampOption { deadlineMs: () => number | undefined; onClamp?: (requestedSec: number, clampedSec: number) => void; } /** The CC-verbatim exit-1 interpretation for `command`, or undefined when exit 1 means a real error. * Conservative parse: last `;`/`&&`/`||`/newline statement → last `|` pipeline segment → leading * command name (env-assignments skipped, path prefix stripped); `git grep`/`git diff` special-cased * (CC cLp). Exported for the 批④ unit tests. */ export declare function bashExitOneInterpretation(command: string): string | undefined; /** * `bash` (effect:write) — a full shell. ⚠️ It runs with `rootCanonical` as the initial cwd but is NOT * sandboxed: a command can `cd` out, read/write/delete any path the process can reach, and use the * network. `rootPath` is a file-tool guard rail, NOT a bash sandbox (design/44 §5, DESIGN#6) — real * isolation is the deployment's job (inject a chroot/container `ExecutionEnv`). Every call still goes * through the design/37 policy gate, which a multi-tenant deployment MUST wire to constrain it. The cwd * persists across calls (design/64 §8.1A): a per-task `cwdRef` starts at `rootCanonical` and is updated * from the shell's final pwd after each command. */ export declare function makeBashTool(env: ExecutionEnv, rootCanonical: string, coAuthor?: string | false, cwdRef?: CwdRef, taskOpts?: { taskRegistry?: TaskRegistry; taskOwner?: string; taskScope?: string; /** 黑板 [636]① (design/129, mirrors MonitorToolOptions.sessionId): when the task runs INSIDE a session, * a background command registers session-resident (owner = sessionId, sessionScoped flag) — it * survives the run teardown like CC's bg shells and is reaped at the session terminal. Absent ⇒ * run-scoped registration exactly as before (killed-with-receipt at teardown). */ sessionId?: string; /** design/116 §7 G2b: completion-notification sink — a finished background command fires ONE * task-notification (priority "next": boundary interrupt, CC LocalShellTask posture). */ onTaskNotification?: (n: import("../../core/task-notification.js").TaskNotificationPayload, opts?: { priority?: "now" | "next" | "later"; }) => void; /** design/116 detach: per-tool-call detach hub — a fired signal adopts the running command as background. */ detachHub?: import("../../core/tool-detach.js").ToolDetachHub; /** design/130 P2b: soft tool-deadline clamp (see {@link HandsToolkitOptions.execClamp}). */ execClamp?: ExecClampOption; }): AgentTool; /** * `bash_readonly` (effect:read) — a restricted shell for the verifier read-only boundary (design/44 M2): * a single allowlisted, bare command with no shell operators. effect:read lets it survive the verifier's * read-only filter, but it is NOT a policy bypass — it still goes through the design/37 gate (council #7: * a read can still be a side channel, e.g. dumping a secret file). The allowlist is a coarse pre-filter; * the policy gate is the authoritative control. */ export declare function makeBashReadonlyTool(env: ExecutionEnv, rootCanonical: string, allow: ReadonlySet, execClamp?: ExecClampOption): AgentTool; /** * design/115 P0 `TaskOutput` (legacy aliases: BashOutput/AgentOutput*) — read a background shell's NEW output * since the last call (cursor), by task_id. Optional legacy `filter` regex is applied BEFORE the per-poll truncation so a watched line survives even when it * falls in a high-throughput middle window. Untrusted process output is fenced (delimitUntrusted) — observe-only, * never re-fed as instructions. */ export declare function makeBashOutputTool(env: ExecutionEnv): AgentTool; /** design/115 P0 `TaskStop` (legacy aliases: KillShell/KillBash) — terminate a background shell by task_id. * * design/134 KNOWN-ISSUES close-out: this band kills ENV-DIRECT (it mounts precisely when the toolkit has * no registry — the createHandsToolkit ternary routes registry deployments to createTaskStopTool), so a * row for the SAME shell in the process-local {@link defaultTaskRegistry} (a Runner-mounted run sharing * this env) used to settle via the watcher's no-claimant floor as stoppedBy:"system". The tool now marks * the initiation site ("parent") on `registry` (default: the process-local {@link defaultTaskRegistry}) * BEFORE the kill — attribution only; the kill path and the model-facing receipt text are unchanged. */ export declare function makeKillShellTool(env: ExecutionEnv, registry?: TaskRegistry): AgentTool; /** Options for {@link createHandsToolkit}. */ export interface HandsToolkitOptions { /** design/119 (CC --add-dir parity): extra allowed containment roots (canonical). Widens file-tool * containment only; the primary root keeps the cwd/base role. Does NOT constrain bash. */ additionalRoots?: readonly string[]; /** Mount a shell tool. Pass false for an env without a real shell (e.g. `StubExecutionEnv`) so a bash * tool that can only error never reaches the model (design/44 §9 S1). Default false. */ includeShell?: boolean; /** Verifier read-only boundary (design/44 §6): mount ONLY effect:read hand tools — read_file/grep/glob * (+ `bash_readonly` when `includeShell`). Drops edit_file/write_file and the full `bash`. Default false. */ readOnly?: boolean; /** Override the `bash_readonly` command allowlist (default {@link BASH_READONLY_DEFAULT_ALLOW}). */ bashReadonlyAllow?: readonly string[]; /** Commit-message attribution trailer for the `bash` git protocol (design/64 §8.1D). Default * {@link DEFAULT_COMMIT_COAUTHOR}; pass `false` to omit the Co-Authored-By trailer entirely. */ commitCoAuthor?: string | false; /** design/99 §E13 — the caller's own {@link CwdRef} to track this task's logical cwd, instead of one * created internally. The Runner passes its own so it can OBSERVE `cd` moves (`cwdRef.current` changes) * and emit a `workspace_changed` event. Ignored in `readOnly` mode (no `cd`). Default: a fresh ref at root. */ cwdRef?: CwdRef; /** Optional process-local registry for unified task_id (`b*`) handles. */ taskRegistry?: TaskRegistry; /** Runner-owned owner/scope fallback used when a tool execute context is unavailable in tests. */ taskOwner?: string; taskScope?: string; /** 黑板 [636]① — see makeBashTool's taskOpts.sessionId: session-resident background commands. */ sessionId?: string; /** Mount TaskOutput/TaskStop directly from this band. Runner sets false and mounts the unified dispatcher once. */ mountBackgroundTaskTools?: boolean; /** design/116 §7 G2b: completion-notification sink for finished background commands (threaded to * makeBashTool's onTaskNotification; the Runner wires the run-local injection lane here). */ taskNotification?: (n: import("../../core/task-notification.js").TaskNotificationPayload, opts?: { priority?: "now" | "next" | "later"; }) => void; /** design/116 detach: the run-local per-tool-call detach hub (mid-flight ctrl+b → adopt as background). */ detachHub?: import("../../core/tool-detach.js").ToolDetachHub; /** design/130 P2b: soft tool-deadline clamp for foreground shell timeouts (see {@link ExecClampOption}). * Wired by the Runner over its wall-clock state; absent ⇒ no clamping (byte-compat). */ execClamp?: ExecClampOption; /** 批③: Read-image downsampler seam ({@link ReadImageDownsamplerOption}). Default undefined = auto-detect * the optional sharp dependency once per process; `false` disables it (deterministic no-sharp behavior). */ readImageDownsampler?: ReadImageDownsamplerOption; /** PDF degradation chain v2: the SERVING model's PDF capability profile (prepare-task derives it via * {@link pdfModelCapabilitiesOf}). A model without native document input gets pdftotext text extraction → * rendered page images (vision) → an honest placeholder, instead of a brain-level placeholder. Default * undefined = fully capable (byte-compat: native document block). */ pdfModelCapabilities?: PdfModelCapabilities; /** design/138 S2-C — write-time content gate for Write/Edit/NotebookEdit (see {@link BeforeWriteHook}). * The Runner wires the MemoryEngine's memory-domain scan here; absent ⇒ byte-identical behavior. */ beforeWrite?: BeforeWriteHook; } /** * Build the per-task hand tool band over an injected env + fresh per-task read state (design/44 §11 A). * `rootCanonical` is the already-canonicalized containment root (prepare-task resolves it once). Normal * mode mounts read/edit/write/grep/glob (+`bash` when `includeShell`); `readOnly` mode mounts only the * effect:read tools read/grep/glob (+`bash_readonly` when `includeShell`) — the verifier boundary. Subset * selection beyond that is the caller's job via the existing design/38 tool-filter. */ export declare function createHandsToolkit(env: ExecutionEnv, readFileState: ReadFileState, rootCanonical: string, opts?: HandsToolkitOptions): AgentTool[]; //# sourceMappingURL=index.d.ts.map