import type { RunHomeBuildContext } from "./build.js"; import { type RunHomeBrand } from "./types.js"; /** * The top-level keys of the pinned runtime's `Settings` interface (`sdk.d.ts`, 0.3.250), verbatim. * Anything else is not the official runtime's to read and is not written. A drift test re-derives * this list from the installed declaration file (`test/run-home/settings.test.ts`). */ export declare const CLAUDE_SETTINGS_KEYS: readonly string[]; /** * Keys the pinned runtime will not take from a repository tier, so the router drops them from that tier * before the merge promotes it to the user tier (F17, extended with the keys below): * * * F17's list — `skipDangerousModePermissionPrompt` (project), `processWrapper`, the credential * helpers (`apiKeyHelper`, `awsAuthRefresh`, `awsCredentialExport`, `gcpAuthRefresh`, * `proxyAuthHelper`, `otelHeadersHelper`), `footerLinksRegexes`, `spellcheck`; * * the runtime's trusted-source-only readers (policy, flag, user): `askUserQuestionTimeout`, * `autoContinueAtUsageLimit`, `desktopSessionCleanupPeriodDays`, `dialogExpiry`, `feedbackDrafts`, * `modelProposedGoals`, `vimInsertModeRemaps`, `modelPicker`, `pluginConfigs`; * * keys the runtime warns are repo-controllable: `autoMode` ("only user/flag/managed settings may * set classifier rules"), `crossSessionInbound` (a repository may only tighten it), `remote` * (a `ccpool_` environment only from a trusted source), `enableArtifact`/`enableWorkflows` (an * enable key is honoured from a trusted layer only), `spinnerTipsOverride`, and * `syncClaudeAiSkills`/`syncClaudeAiPlugins`/`skipWorkflowUsageWarning` (not read from the project); * * `claudeMd` — honoured from managed settings only. * * `permissions.defaultMode` is handled beside these (a value, not a key): `REFUSED_DEFAULT_MODES`. */ export declare const PROJECT_TIER_REFUSED_KEYS: { readonly project: readonly string[]; readonly local: readonly string[]; }; /** * MODEL ROUTING (R.3, M3 — ruled Important): Winter never switches to another leg, provider or model * silently, and a repository never chooses models. Dropped and REPORTED (`droppedRules`, the key's name). * * * `fallbackModel` — from EVERY tier, the user's included: claude honours it from settings on * overload, which is exactly a silent switch to another model. * * `modelOverrides` — from EVERY tier too (Touch 4 add-on). The runtime's schema: "Override mapping from * Anthropic model ID … to provider-specific model ID"; measured by the daemon reviewer on the real * child, a USER-tier remap of haiku to opus made claude report haiku at `system/init` while sending * `claude-opus-5` on the wire — a switch no startup model check can see. It became reachable in WS-21 * through `settingSources: ["user"]`. * * `model`, `availableModels`, `advisorModel`, `enforceAvailableModels` — from the project and local * tiers. The user tier keeps them: the host's `Options.model` — forwarded to the official child as * `--model` (Touch 4, F1) — outranks any settings `model`, and the rest are the user's own choices. * `enforceAvailableModels` (R.3 touch) is a switch too: the runtime's schema text says that with it * "Default resolves to the first allowed availableModels entry". */ export declare const EVERY_TIER_REFUSED_MODEL_KEYS: readonly string[]; export declare const REPOSITORY_TIER_REFUSED_MODEL_KEYS: readonly string[]; /** * EFFORT AND THINKING (WS-24): a repository never sets how hard the session's model works either. Dropped * from the project and local tiers and REPORTED, the same way as the model keys; the user tier keeps them. * * * `effortLevel` — the session's reasoning effort; * * `modelSettings` — per-model settings, a per-model `effortLevel` among them; * * `ultracode` — the maximum-effort coding switch; * * `alwaysThinkingEnabled` — extended thinking on every turn. * * DIVERGENCE, deliberate: claude honours these from a project's settings. Winter's session effort is the * daemon's (the host's `Options.effort`, per session and per message), and each of these keys changes what * a turn costs, so a repository's copy is refused rather than promoted to the user tier by the merge. The * Winter runtime reads none of them from a settings file today (agent SDK 0.0.27/0.0.28); every one is a * `Settings` key, though, so without this the run folder would carry a repository's value to any runtime * that starts reading it. */ export declare const REPOSITORY_TIER_REFUSED_EFFORT_KEYS: readonly string[]; /** * THE OUTPUT STYLE (WS-24): a repository never picks it — not from the project tier, not from the local * one. A style replaces part of the system prompt, and the Winter runtime lets a PROJECT-tier style add to * the prompt but never replace it; merged into the user tier, a repository's `outputStyle` naming its own * style would skip that rule. Dropped and REPORTED. (A project style the USER selects is still loaded — as * a copy with `keep-coding-instructions: true`, see `items.ts`.) */ export declare const REPOSITORY_TIER_REFUSED_STYLE_KEYS: readonly string[]; /** * `permissions.defaultMode` values a repository tier may not set (R.3, I1), dropped and REPORTED. * * * project — every ESCALATING mode: `bypassPermissions`, `auto`, `acceptEdits`. That is claude's own * trust-tier filter (`filterEscalatingDefaultMode`, `sdk.d.ts`: those three modes, applied to the * project tier only). Once the router has merged a tier into the user tier that filter never fires, * and the router sets no `Options.permissionMode` on the official child, so a repository's * `"acceptEdits"` would have the child approve every in-cwd write without asking. * * local — `auto` only, as before (claude takes `auto` from user, flag or managed settings only); the * project-tier filter does not name the local tier. EXCEPT a local file git TRACKS (WS-24): that one * arrived with the repository, so it is filtered as the project tier — every escalating mode, and every * key `PROJECT_TIER_REFUSED_KEYS.project` names (see `localTierShippedByRepository`). * * DIVERGENCE, deliberate: claude drops the EFFECTIVE mode when the highest tier that sets one is the * project, so a user's own mode under a project's escalating one becomes no mode at all (`default`). * Filtering per tier keeps the user's value instead. The result is therefore always the user's own * tier's mode (or none): the repository never sets or changes it. That is not always the narrower of the * two — a user's own `acceptEdits` under a project's `bypassPermissions` stays `acceptEdits`, where * claude would fall to `default` — but it is exactly what the user chose. */ export declare const REFUSED_DEFAULT_MODES: { readonly project: ReadonlySet; readonly local: ReadonlySet; }; /** * The variable names the official runtime refuses from a project or local tier's `env` block (matched * case-insensitively, as it matches them). */ export declare const CLAUDE_PROJECT_TIER_ENV_DENY: readonly string[]; /** The variable names the official runtime refuses from EVERY tier's `env` block. */ export declare const CLAUDE_EVERY_TIER_ENV_DENY: readonly string[]; /** The official-leg variables the ROUTER sets for a run home (spec §3.1). */ export declare const ROUTER_SET_OFFICIAL_VARIABLES: readonly string[]; /** The Winter-leg twins the router sets, spelled from the brand (spec §3.1, §6.3 items 11/13). */ export declare function routerSetWinterVariables(brand: Pick): string[]; /** * Every variable a settings `env` block may never set, on any tier: the runtime's every-tier set, the * variables the router sets itself on either leg (config dir, plugin root, host-managed provider, cron, * the Winter twins, the transcript key and temp root, the traffic opt-outs, the credentials), and the * router's refused execution-indirection list. */ export declare function everyTierEnvRefused(name: string, brand: Pick): boolean; /** * WS-24: did the REPOSITORY ship `//settings.local.json`? A LOCAL settings file is the * user's own only while nothing says otherwise; a shipped one is filtered exactly as the project tier * (`buildEffectiveSettings`). `root` is the git root (or the cwd git is asked from, see there). * * SHIPPED when any of these holds — each is checked, and every one fails closed: * * 1. the dot-dir or the file is a symbolic link, or the file's real path is not exactly * `//settings.local.json` (another spelling on a case-insensitive volume, a * linked directory) — what the name resolves to is then not the plain local file; * 2. git lists an index entry, matched case-insensitively, AT the file, or AT the dot-dir itself (only a * link or a submodule can be one — either way the directory is the repository's). "In the index" is * committed, or staged and not yet committed; * 3. git cannot answer: no git executable, a non-zero exit (not a repository, a repository git refuses to * read for its owner, a broken index), a timeout. * * The probe reads the index and nothing else: `ls-files -s` with `:(literal,icase)` pathspecs, * `core.fsmonitor` off so no configured monitor command runs, and every `GIT_*` variable removed from its * environment. * * WHAT THIS CANNOT PROVE: a repository delivered together with its `.git` controls its own index, so * "untracked" is git's word about that index, never proof of where the file came from. It closes the * ordinary case — a local file committed to the repository — not every delivery. */ export declare function localTierShippedByRepository(root: string, dirName: string, git?: string): Promise; export declare function repositoryPlansDirectoryProblem(value: unknown): string | undefined; /** * `Tool(/x)` → `Tool(///x)` for a path tool; every other form is returned as written. * * THE ANCHOR IS ESCAPED (`escapeRulePath`) ON BOTH LEGS. The router writes the anchor part, so it must * spell it in the rule grammar, and both runtimes now read claude's gitignore-style grammar: claude * always has (a root named `[wip] app` is a character class unless escaped — MEASURED, a re-anchored * project `ask` rule under such a root never fired), and the Winter runtime since `ws21/sdk`@57e7fef * (SV-6 — measured the same way on the Winter leg: unescaped, its re-anchored ask and deny rules under a * `[wip] app` root never matched). Since the escape-table round the spelling is two layers — claude's * rule-content escape over the gitignore escape (see `escapeRulePath`) — because both legs unescape a * rule's content once before matching (the Winter runtime since `ws21/sdk`@6170adb). The author's own * part of the pattern is already in that grammar and is passed through as written. */ export declare function anchorRule(rule: string, anchor: string): string; /** * A path spelled for CLAUDE'S SANDBOX grammar (review N-1 minor), which is not the rule grammar — the * grammar BOTH runtimes read a `sandbox.filesystem` entry in (the Winter runtime since `ws21/sdk` round * 11, which routes every deny entry through claude's glob-shape check). * * MEASURED on the pinned runtime (claude 2.1.250, macOS): a `sandbox.filesystem` entry holding any of * `* ? [ ]` is a GLOB — the runtime renders it as a seatbelt `(regex …)` instead of `(subpath …)` — and * its glob-to-regex step escapes a backslash into a LITERAL backslash, so `escapeRulePath`'s spelling * would make the entry name a path that does not exist (a deny that covers nothing). The one escape that * grammar honours is a character class: `[` → `[[]` (a lone `]` is already literal). Under a root named * `[wip] app`, a re-anchored `denyWrite` spelled raw did NOT stop a sandboxed write (the class matched * `w app`, not the literal root); spelled `[[]wip] app/…` it did. `*` and `?` have no spelling there * (the regex step rewrites every one of them), so they stay raw: a deny is then wider — stricter — and an * allow is dropped by the caller. * * ON AN ALLOW ENTRY (`allowWrite`, `allowRead`) — for a host calling this directly: the result is still a * glob, and claude renders a glob allow as an EXACT-path match (its trailing `/**` is stripped first), so * a path holding `[` covers that path itself and NOT what is under it (measured: a write inside it was * refused, raw or escaped); and a `*` or `?` left in the path WIDENS the allow to sibling paths. The router * drops such allows itself (`droppedRules`); a host must decide the same for its own. */ export declare function escapeSandboxGlobPath(path: string): string; /** F17's merge: `higher` over `lower`. */ export declare function mergeSettings(lower: Record, higher: Record, path?: readonly string[]): Record; /** Builds `/settings.json` and returns exactly what it holds. */ export declare function buildEffectiveSettings(context: RunHomeBuildContext): Promise>; /** * Spec §3.2's per-mode stripping. Outside code mode the permission grants, hooks, env, plugins and * output style are removed and native auto-memory is OFF (chat and dispatch keep the daemon's own * `_assistant` injection, spec §3.7 r3). A dispatch child loses its output style. */ export declare function stripForMode(settings: Record, mode: RunHomeBuildContext["input"]["mode"], dispatchChild: boolean): Record;