import { type GuardRoutingConfig } from "#src/guard-config"; import { type SandboxPolicyResolution } from "#src/sandbox/policy"; import { type StyleConfig } from "#src/widget-style"; /** * Model routing configuration reader. * * Reads tmux-pilot.config.yaml from the pi config directory and resolves * model/thinking entries for a given agent type. */ export interface ModelEntry { model: string; provider?: string; thinking?: string; harness?: string; } export interface ResolvedEntry { model: string; provider?: string; thinking?: string; harness?: string; } export interface RoleConfig { thinking?: string; provider?: string; models?: Array>; /** Optional path to an agent definition markdown file. */ agentDef?: string; /** Harness name (e.g. "pi", "cmd"). Defaults to "pi" when absent. */ harness?: string; /** Guard configuration for turn limits and steer prompts. */ guards?: Record; /** Whether worktree isolation is enabled for this role. Resolved via 3-layer chain. */ newWorktree?: boolean; /** Raw YAML key for worktree isolation; coerced into `newWorktree` (accepts true/false, yes/no). */ "new-worktree"?: boolean | string; /** Widget style overrides for this role (merged over the global style). */ style?: Record; /** * Per-role config-dir override (a path + an optional mode). Object form * `{ dir, mode }` or a bare string (dir, mode defaults "layer"). Resolved by * {@link resolveHarnessConfigDir}. See the `harness-config-dir` capability. */ "config-dir"?: ConfigDirField; /** * Ms to wait after a terminal state before physical cleanup (window/config/ * temp/worktree). Role > harness-global > subagent-global > 0. Does NOT * disable cleanup — only delays it. See {@link resolveCleanupLingerMs}. */ cleanupLingerMs?: number; /** * Raw canonical `sandbox` block (kebab-case keys, kept verbatim by the * loader conversion). Resolved per-leaf against the global block by * `resolveSandboxPolicy` (`src/sandbox/policy.ts`). */ sandbox?: Record; } /** Raw YAML shape of a config-dir field: object `{ dir, mode }` or a bare path. */ export type ConfigDirField = string | { dir?: string; mode?: string; } | null | undefined; /** * Resolved config-dir selection for a spawn. `dir` undefined ⇒ no override * (harness runs as today). `mode` defaults to "layer". */ export interface ConfigDirSelection { dir?: string; mode: "layer" | "replace"; } export interface SubagentGlobalDefaults { thinking?: string; harness?: string; provider?: string; agentDef?: string; agentDefFolder?: string; models?: Array>; guards?: Record; /** Whether worktree isolation is enabled by default for all roles. */ newWorktree?: boolean; /** Raw YAML key for worktree isolation; coerced into `newWorktree` (accepts true/false, yes/no). */ "new-worktree"?: boolean | string; /** Global widget style defaults + overrides. */ style?: Record; /** * Global default cleanup linger (ms) after terminal before physical teardown. * Overridden by harness-global and role. See {@link resolveCleanupLingerMs}. */ cleanupLingerMs?: number; /** Raw canonical `sandbox` defaults block (see {@link RoleConfig.sandbox}). */ sandbox?: Record; } /** * Per-harness defaults under top-level `harness-global-defaults`. * Holds harness-scoped fields that used to live under `harnessSettings` / * `subagent-global-defaults.config-dir`. */ export interface HarnessGlobalDefaults { stopKeyCombo?: string; "config-dir"?: ConfigDirField; /** Per-harness cleanup linger (ms). See {@link resolveCleanupLingerMs}. */ cleanupLingerMs?: number; } /** * Git worktree settings from the top-level `git-worktree-settings` key. * Controls worktree behavior globally, not per-role. */ export interface GitWorktreeSettings { /** * When false (default), all worktrees are ephemeral and removed on agent completion. * When true, explicitly-named worktrees (via worktree_branch param) persist. */ enablePersistentWorktrees?: boolean; } /** * Presentation settings for the subagent frames widget, read from the * `tmuxPilotSettings` section (shared with the settings modal's keys). */ export interface WidgetSettings { /** Layout mode: hybrid (`auto`), party-frames-only, or raid-cells-only. */ displayMode: "auto" | "party" | "raid"; /** * pi widget region (design D6 rename — the values name pi's REAL regions): * `aboveEditor` = between chat and editor, `belowEditor` = between editor * and footer. pi has no fixed above-chat / top-of-terminal region. */ placement: "aboveEditor" | "belowEditor"; /** Keyboard shortcut (pi KeyId string) for the visibility toggle. */ toggleKey: string; /** * Responsive party-frame width band (design D6): the minimum a frame may * pack to (to admit one more member into the party) and the maximum/resting * width it may grow to. Invalid or min > max → the defaults (16/24). */ partyFrameMinWidth: number; partyFrameMaxWidth: number; /** * Raid grid density (tui-frames-polish D5): the maximum number of raid rows * and the maximum raid cells per row. The widget height budget derives from * these rather than a fixed line cap; a narrow terminal may render fewer * cells per row than `raidPerRow`. Invalid/non-positive → the defaults (3/10). */ raidMaxRows: number; raidPerRow: number; } /** Defaults applied when settings keys are absent or invalid. */ export declare const DEFAULT_WIDGET_SETTINGS: WidgetSettings; export declare const WIDGET_DISPLAY_MODES: readonly ["auto", "party", "raid"]; export declare const WIDGET_PLACEMENTS: readonly ["aboveEditor", "belowEditor"]; /** * Plausible-KeyId shape check (task 6.1): optional `ctrl+`/`shift+`/`alt+`/ * `super+` modifiers followed by a single base key (letter, digit, symbol, * or a named special key). Mirrors pi-tui's KeyId grammar without importing * pi types (this module is core). */ export declare function isPlausibleKeyId(value: string): boolean; export interface ModelRoutingConfig { "subagent-roles"?: Record; "subagent-global-defaults"?: SubagentGlobalDefaults; /** * Per-harness defaults (stop combo, config-dir, cleanup linger), keyed by * harness id. Preferred home for harness-scoped settings. */ "harness-global-defaults"?: Record; "git-worktree-settings"?: GitWorktreeSettings; /** * Internal bridge for widget/execution settings — populated by the config * conversion from canonical `subagent-widget` / `subagent-execution` paths. * Not a public key; public schema uses `subagent-widget` and * `subagent-execution.tmux-server`. */ tmuxPilotSettings?: Record; /** * Internal bridge — populated by the config conversion from canonical * `subagent-execution.cleanup-enabled`. Not a public key. */ enableSubagentCleanup?: boolean; /** Internal bridge from `web-configuration-console`. */ webConfigurationConsole?: { port?: unknown; }; } /** Resolve the loopback console port, falling back safely for invalid YAML. */ export declare function resolveWebConfigurationConsolePort(config: ModelRoutingConfig | undefined): number; /** * Get the list of available agent types (role keys) from the config. * Returns an empty array if the config doesn't exist or has no roles. */ export declare function getAvailableAgentTypes(configDir: string): string[]; /** * Resolve agentDefFolder path against config directory. * Handles tilde expansion, absolute paths, relative-to-configDir resolution, * and undefined fallback to configDir. */ export declare function resolveAgentDefFolder(agentDefFolder: string | undefined | null, configDir: string): string; /** * Resolve global defaults from the routing config, with safe defaults for missing keys. * Performs type coercion so malformed YAML values don't leak through as wrong types. */ export declare function resolveGlobalDefaults(routing: ModelRoutingConfig): SubagentGlobalDefaults; /** * Resolve an agentDef path against the config directory or agentDefFolder. * Absolute paths are returned as-is; relative paths are resolved against * agentDefFolder (if provided) or configDir. * Tilde (`~`) is expanded to the user's home directory. */ export declare function resolveAgentDefPath(agentDef: string, configDir: string, agentDefFolder?: string): string; /** * Parse YAML frontmatter from a markdown file. * Frontmatter is the content between `---` delimiters at the start of the file. * Returns the parsed frontmatter object, or undefined if the file doesn't exist, * has no frontmatter, or the frontmatter is malformed. */ export declare function parseMarkdownFrontmatter(filePath: string): Record | undefined; /** * Enrich role keys with descriptions sourced from their agentDef markdown files. * * For each role key, if the role has an `agentDef` path, the function resolves * the path, parses the markdown frontmatter, and extracts the `description` field. * All errors are handled gracefully — the key is returned without a description. */ export declare function enrichAgentTypes(configDir: string, roleKeys: string[], routing: ModelRoutingConfig): Array<{ key: string; description?: string; }>; /** Read and compose tmux-pilot.config.yaml from the given config directory. */ export declare function readModelRouting(configDir: string): ModelRoutingConfig | undefined; /** * Resolve models for a given agent type from the routing config. * Case-insensitive matching. * * Three-layer resolution: model-level override ?? role-level default ?? global default. * Harness additionally falls back to "pi" hardcoded default. * Models list falls back to global models when role has none. */ export declare function resolveModelsForType(agentType: string, routing: ModelRoutingConfig): ResolvedEntry[] | undefined; /** * Resolve the full guard routing config from the routing config. * * Implements a 3-layer guard merge chain: * DEFAULT_GUARD_CONFIG ← subagent-global-defaults.guards ← role.guards * * Global guards are extracted from `subagent-global-defaults` and parsed into a * GuardConfig using DEFAULT_GUARD_CONFIG as the base. Each role's guards are * then parsed with the global GuardConfig as the `baseGuards` parameter, giving * role-level fields precedence over global defaults. */ export declare function resolveGuardRoutingConfig(routing: ModelRoutingConfig): GuardRoutingConfig; /** * Resolve new-worktree configuration using a 3-layer chain: * hardcoded(false) ← subagent-global-defaults.new-worktree ← role.new-worktree * * Returns true if worktree isolation should be enabled for the given role, * false otherwise. The role-level setting takes precedence over global, * which takes precedence over the hardcoded default. * * If the agent type is not found in the roles configuration, returns false * regardless of global defaults (worktree isolation is role-specific). */ export declare function resolveNewWorktree(routing: ModelRoutingConfig, agentType: string): boolean; /** * Resolve the sandbox policy for one role (`hardcoded ← global ← role`). * Role lookup is case-insensitive, matching the other role resolvers; an * unknown role resolves against the global block alone. */ export declare function resolveSandboxPolicyForRole(routing: ModelRoutingConfig | undefined, agentType: string | undefined): SandboxPolicyResolution; /** * Resolve a per-agent config-dir selection on the `role > harness-global > * unset` chain (mirrors {@link resolveNewWorktree}). Returns * `{ dir?: string, mode: "layer" | "replace" }`: * - Layer 1 (hardcoded): `{ dir: undefined, mode: "layer" }`. * - Layer 2 (harness-global): `harness-global-defaults[harnessId].config-dir` * (already renamed from canonical by config conversion). * - Layer 3 (role): the role's own `config-dir` * (already renamed from canonical by config conversion). * * The role's `dir` wins over the harness-global `dir`; likewise for `mode`. * Paths support tilde / relative resolution (relative → against host storage, * as `agentDefFolder` resolves against the config dir). An invalid `mode` * warns and falls back to `"layer"`. Never throws on malformed input. * * NOTE: this is pure routing resolution. Warn-and-ignore for a harness whose * instance has no `configProfile` is applied separately by * {@link applyConfigProfileSupport} (which has the harness instance). */ export declare function resolveHarnessConfigDir(routing: ModelRoutingConfig, agentType: string, harnessId: string): ConfigDirSelection; /** * Resolve cleanup linger (ms after terminal before physical teardown). * Chain: `role > harness-global-defaults[harness] > subagent-global-defaults > 0`. * `0` (default) = clean up immediately when `enableSubagentCleanup` is true. * Does not disable cleanup — only delays it. Manual GC ignores linger. */ export declare function resolveCleanupLingerMs(routing: ModelRoutingConfig, agentType: string, harnessId: string): number; /** * Drop a config-dir selection the harness cannot honor. A harness WITHOUT a * `configProfile` does not support a config-dir override (D2): if one is set, * warn once and ignore it (the harness runs as today). `hasConfigProfile` is * passed by the caller (the spawner) which holds the harness instance. */ export declare function applyConfigProfileSupport(selection: ConfigDirSelection, hasConfigProfile: boolean, harnessId: string): ConfigDirSelection; /** * Resolve git worktree settings from the top-level `git-worktree-settings` key. * * Returns a GitWorktreeSettings object with defaults applied: * - enablePersistentWorktrees defaults to false if not set or not a boolean */ export declare function resolveGitWorktreeSettings(routing: ModelRoutingConfig): GitWorktreeSettings; /** * Resolve the top-level `enableSubagentCleanup` flag. Defaults to `true` * (subagents auto-clean on terminal state, optionally after * {@link resolveCleanupLingerMs}). Only an explicit `false` disables * cleanup; any non-boolean value falls back to the default. */ export declare function resolveEnableSubagentCleanup(routing: ModelRoutingConfig): boolean; /** * Resolve the frames-widget presentation settings from `tmuxPilotSettings`. * Absent, non-string, or out-of-enum values fall back to defaults — * a malformed key never breaks widget construction. */ export declare function resolveWidgetSettings(routing: ModelRoutingConfig | undefined): WidgetSettings; /** * Subagent tmux topology (subagent-tmux-topology capability): * `current-session` = subagent windows live in a `pilot-` session on * the user's OWN tmux server — natively visible, no `-S` socket (default); * `isolated` = the pre-existing per-parent dedicated server (ADR 0003), * now opt-in. */ export type SubagentTmuxServer = "current-session" | "isolated"; export declare const SUBAGENT_TMUX_SERVERS: readonly ["current-session", "isolated"]; export declare const DEFAULT_SUBAGENT_TMUX_SERVER: SubagentTmuxServer; /** * Resolve the `subagentTmuxServer` topology from `tmuxPilotSettings`. * Global-only, resolved once at extension bootstrap (mirrors * `resolveWidgetSettings`, not the per-role 3-layer merge). Absent → * default; invalid → warn and default, never throw. */ export declare function resolveSubagentTmuxServer(routing: ModelRoutingConfig | undefined): SubagentTmuxServer; /** Resolved widget styles: the global layer plus per-role merged styles. */ export interface StyleRoutingConfig { /** Validated global `style:` (subagent-global-defaults). */ globalStyle: StyleConfig; /** Per-role (lowercased key) styles, each merged global ← role. */ roleStyles: Record; } /** * Parse and validate ONE scope's `style:` object (task 4.1). Invalid values * are ignored with a warning so resolution falls through to the next layer * down — a malformed style section never prevents spawning. */ export declare function parseStyleConfig(raw: unknown, scope: string): StyleConfig; /** * Resolve the widget style config (task 4.1): the global * `subagent-global-defaults.style` layer plus, per role, the shallow * per-leaf-group merge `global style ← role style`. (The hardcoded-palette * layer is scheme-dependent, so it is applied at RENDER time by * `applyStyleConfig` — resolved configs carry only config-provided values.) */ export declare function resolveStyleConfig(routing: ModelRoutingConfig | undefined): StyleRoutingConfig; //# sourceMappingURL=model-routing.d.ts.map