import type { MigrationHarness } from "./migration.js"; export interface ResolvedAftConfigPaths { userConfigPath: string; projectConfigPath: string; } export interface LegacyAftConfigSource { path: string; label: string; /** Which harness this legacy config belonged to (drives operating-harness-wins). */ harness?: MigrationHarness; } export declare function resolveCortexKitUserConfigPath(): string; export declare function resolveCortexKitProjectConfigPath(projectDirectory: string): string; export declare function resolveCortexKitConfigPaths(projectDirectory: string): ResolvedAftConfigPaths; export declare function resolveLegacyAftConfigSources(projectDirectory: string): { user: LegacyAftConfigSource[]; project: LegacyAftConfigSource[]; }; export declare function resolveHarnessStoragePath(storageRoot: string, harness: MigrationHarness, ...segments: string[]): string; export declare function repairRootScopedStorageFile(storageRoot: string, harness: MigrationHarness, fileName: string): string; /** * Decides whether to surface the version-specific announcement dialog/toast. * * Three cases, all driven off the persisted `last_announced_version` file: * * 1. **Existing user, same version** — file matches `currentVersion`. Skip. * * 2. **Existing user, upgrade** — file holds a *different* non-empty version. * Show the dialog so the user sees what's new in their upgrade. After the * dialog is dismissed, the host calls `markAnnouncementSeen` to record * `currentVersion`. * * 3. **Fresh install or ephemeral sandbox** — file does not exist OR holds * only whitespace. We deliberately do NOT show changelog bullets to a * first-time user (no context to interpret them), AND we don't pester * Docker/CI/disposable-VM users whose storage gets wiped every boot. * Instead we silently **seed** the file with `currentVersion` so the very * next launch behaves like case 1. Future upgrades still trigger case 2. * * Failures to read/write the marker file are non-fatal: we never let a * filesystem hiccup spam an announcement. On any I/O error the function * returns `false` and the host treats this turn as already-announced. * * Returns: * - `true` → the caller should render the announcement and then call * `markAnnouncementSeen(...)` once the user has seen it. * - `false` → skip rendering. (File was already up-to-date, OR this was a * fresh-install seed and the file has now been written so the * next launch will also skip.) */ export declare function shouldShowAnnouncement(storageRoot: string, harness: MigrationHarness, currentVersion: string): boolean; /** * Records that the user has seen `currentVersion`'s announcement. Best-effort * filesystem write — failures are silently swallowed because the worst case * is repeating the announcement once, not a broken plugin. */ export declare function markAnnouncementSeen(storageRoot: string, harness: MigrationHarness, currentVersion: string): void; /** * Decode an RFC 8089 `file:` URL to a local path. Models routinely spell * local targets as file:///path (or file:/path, file://localhost/path); * rejecting them only produces failed tool calls. MUST match the Rust * decoder (subc_translate.rs decode_file_url) so plugin-side permission * gates judge the same target the server resolves. Returns the input * unchanged when it is not a decodable local file URL. * * The percent-decode is byte-wise and tolerant, matching Rust EXACTLY: * each valid %HH decodes independently; malformed escapes stay literal. * decodeURIComponent is all-or-nothing (throws on one malformed %ZZ, * leaving VALID escapes like %2e%2e encoded too) — that divergence lets a * crafted URL read as in-project at the permission gate while the server * resolves the decoded ../ out of the project. */ export declare function decodeFileUrl(target: string): string; //# sourceMappingURL=paths.d.ts.map