cascade-ai 0.20.0 → 0.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,5 +1,9 @@
1
1
  import EventEmitter from 'node:events';
2
+ import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
2
3
  import { Server } from 'node:http';
4
+ import { OAuthClientInformationMixed, OAuthTokens, OAuthClientMetadata, OAuthClientInformationFull } from '@modelcontextprotocol/sdk/shared/auth.js';
5
+ export { OAuthClientInformationMixed, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
6
+ import Database from 'better-sqlite3';
3
7
 
4
8
  type ProviderType = 'anthropic' | 'openai' | 'gemini' | 'azure' | 'openai-compatible' | 'ollama';
5
9
  interface ModelInfo {
@@ -18,6 +22,13 @@ interface ModelInfo {
18
22
  supportsToolUse?: boolean;
19
23
  /** Self-declared or API-sourced specialization categories. */
20
24
  specializations?: string[];
25
+ /**
26
+ * Canonical base-model id this entry stands for, when `id` isn't itself
27
+ * canonical. Azure deployments are addressed by an arbitrary deployment name
28
+ * (the `id`), but back a real model (e.g. gpt-5) — `baseModelId` carries that
29
+ * real identity so benchmark scoring and live pricing resolve correctly.
30
+ */
31
+ baseModelId?: string;
21
32
  }
22
33
  interface ProviderConfig {
23
34
  type: ProviderType;
@@ -104,9 +115,14 @@ interface ImageAttachment {
104
115
  }
105
116
  interface McpServerConfig$1 {
106
117
  name: string;
107
- command: string;
118
+ command?: string;
108
119
  args?: string[];
109
120
  env?: Record<string, string>;
121
+ url?: string;
122
+ headers?: Record<string, string>;
123
+ /** Path to a JSON store of this server's OAuth state (desktop/CLI). When set,
124
+ * the run attaches an auto-refreshing OAuth token instead of a static header. */
125
+ oauthStore?: string;
110
126
  }
111
127
  interface ToolDefinition {
112
128
  name: string;
@@ -390,6 +406,8 @@ interface CascadeConfig {
390
406
  budget: BudgetConfig;
391
407
  theme: string;
392
408
  workspace: WorkspaceConfig;
409
+ /** Extended context: compact history/input that exceeds the model's window. */
410
+ extendedContext?: ExtendedContextConfig;
393
411
  cascadeAuto?: boolean;
394
412
  /** Cascade Auto trade-off bias when picking a model. Default: 'balanced'. */
395
413
  autoBias?: 'balanced' | 'quality' | 'cost';
@@ -402,6 +420,12 @@ interface CascadeConfig {
402
420
  knowledge?: {
403
421
  factsExtraction?: boolean;
404
422
  };
423
+ /** Workspace code index (Phase 3). Opt-in; registers a `code_search` tool. */
424
+ codeIndex?: {
425
+ enabled?: boolean;
426
+ autoRefresh?: boolean;
427
+ dbPath?: string;
428
+ };
405
429
  plugins?: string[];
406
430
  localConcurrency?: number;
407
431
  localInferenceTimeoutMs?: number;
@@ -425,6 +449,10 @@ interface CascadeConfig {
425
449
  enabled?: boolean;
426
450
  maxRounds?: number;
427
451
  };
452
+ /** Fast answer behaviour. `autoSimple`: pure small talk skips orchestration for a direct single-model reply. Default: true. */
453
+ fastAnswer?: {
454
+ autoSimple?: boolean;
455
+ };
428
456
  /** T3 wave execution: 'auto' (sequential for local, parallel for cloud), or force one. Default: 'auto'. */
429
457
  t3Execution?: 'auto' | 'parallel' | 'sequential';
430
458
  /** T3→T2 reinforcement: let a worker ask its manager to spawn sibling workers. Off by default. */
@@ -451,6 +479,10 @@ interface CascadeConfig {
451
479
  */
452
480
  routing?: {
453
481
  forceTier?: 'auto' | 'T1' | 'T2' | 'T3';
482
+ /** Override the model-performance stats file (cloud → persistent volume). */
483
+ perfStatsPath?: string;
484
+ /** Read shared scores but don't record this run's outcomes when false. Default true. */
485
+ learnFromOutcomes?: boolean;
454
486
  };
455
487
  /** Render the TUI in the alternate screen buffer (vim-style). Default: false. */
456
488
  altScreen?: boolean;
@@ -478,6 +510,10 @@ interface BenchmarksConfig {
478
510
  sourceUrl?: string;
479
511
  /** Fetch current per-token prices from OpenRouter (free, no key). Default: true. */
480
512
  pricingLive?: boolean;
513
+ /** Where the fetched live snapshot is cached. Cloud → the persistent volume,
514
+ * so live benchmark scores survive redeploys and are shared across requests
515
+ * instead of re-fetched on every fresh Cascade. Defaults to ~/.cascade. */
516
+ cacheFile?: string;
481
517
  }
482
518
  interface ToolsConfig {
483
519
  shellAllowlist: string[];
@@ -538,11 +574,30 @@ interface MemoryConfig {
538
574
  maxSessionMessages: number;
539
575
  autoSummarizeAt: number;
540
576
  retentionDays: number;
577
+ /**
578
+ * Opt-in: after a run completes, distill durable facts from the session into
579
+ * the project knowledge store (undoable per-fact from the Knowledge tab).
580
+ * Off by default — session content stays out of long-term memory unless the
581
+ * user chooses otherwise.
582
+ */
583
+ rememberSessions?: boolean;
584
+ }
585
+ interface ExtendedContextConfig {
586
+ /** Off by default — when on, oversized history/input is compacted to fit. */
587
+ enabled: boolean;
588
+ /** How far past the model's native window an input may go before it's
589
+ * truncated (via chunk + map-reduce). 2 = up to 2× the window. */
590
+ maxMultiplier: number;
541
591
  }
542
592
  interface TierLimits {
543
593
  t1MaxTokens?: number;
544
594
  t2MaxTokens?: number;
545
595
  t3MaxTokens?: number;
596
+ /** Per-tier sampling temperature (0–2). Applied only when a call doesn't set
597
+ * its own temperature — deterministic internal calls (temperature: 0) win. */
598
+ t1Temperature?: number;
599
+ t2Temperature?: number;
600
+ t3Temperature?: number;
546
601
  }
547
602
  interface BudgetConfig {
548
603
  dailyBudgetUsd?: number;
@@ -583,7 +638,7 @@ interface ThemeColors {
583
638
  t2Color: string;
584
639
  t3Color: string;
585
640
  }
586
- type CascadeEventType = 'task:start' | 'task:complete' | 'task:error' | 'tier:status' | 'tier:result' | 'tier:root' | 'stream:token' | 'stream:done' | 'tool:approval-request' | 'tool:approval-response' | 'tool:execute' | 'tool:result' | 'tool:call' | 'cost:update' | 'session:save' | 'escalation' | 'peer:sync' | 'peer:message' | 'plan' | 'log' | 'run:cancelled' | 'budget:warning' | 'budget:exceeded' | 'permission:user-required' | 'mcp:approval-required' | 'plan:approval-required';
641
+ type CascadeEventType = 'task:start' | 'task:complete' | 'task:error' | 'tier:status' | 'tier:result' | 'tier:root' | 'stream:token' | 'stream:done' | 'tool:approval-request' | 'tool:approval-response' | 'tool:execute' | 'tool:result' | 'tool:call' | 'cost:update' | 'session:save' | 'escalation' | 'peer:sync' | 'peer:message' | 'plan' | 'log' | 'run:cancelled' | 'budget:warning' | 'budget:exceeded' | 'permission:user-required' | 'mcp:approval-required' | 'plan:approval-required' | 'context:approval-required' | 'context:compacted';
587
642
  interface CascadeEvent<T = unknown> {
588
643
  type: CascadeEventType;
589
644
  taskId?: string;
@@ -717,6 +772,36 @@ interface CascadeRunOptions {
717
772
  * controller.abort();
718
773
  */
719
774
  signal?: AbortSignal;
775
+ /**
776
+ * An optional, pre-computed complexity verdict for this run — e.g. from a
777
+ * cheap on-device classifier the caller ran first. When provided (and no tier
778
+ * is manually forced), the orchestrator uses it as the starting verdict and
779
+ * skips its own classifier LLM call, saving that round-trip. The heuristic
780
+ * complexity floors and mid-run escalation still apply, so an under-estimate
781
+ * can't strand genuinely complex work on a cheap tier. Only the first three
782
+ * levels are accepted as a hint; 'Highly Complex' is derived server-side.
783
+ */
784
+ complexityHint?: Exclude<TaskComplexity, 'Highly Complex'>;
785
+ /**
786
+ * "Fast answer" — bypass the whole T1/T2/T3 orchestration and reply with a
787
+ * single mid-tier model, no tools and no artifact verification. For quick
788
+ * questions and small edits where the multi-agent machinery just adds latency
789
+ * and cost. The reply still streams and is persisted like a normal turn.
790
+ */
791
+ fastAnswer?: boolean;
792
+ /**
793
+ * The user's ACTUAL message, for routing decisions only, when `prompt` has
794
+ * been augmented by the host (system guidance, memories, documents). The
795
+ * complexity heuristics/classifier and task-type analysis read this instead
796
+ * of the augmented prompt, so a bare "hi" still routes as small talk. The
797
+ * model still receives the full `prompt`. Defaults to `prompt` when unset.
798
+ */
799
+ routingPrompt?: string;
800
+ /**
801
+ * Optional model id to use for a fast answer (e.g. 'gpt-4o-mini'). When unset,
802
+ * a validated mid-tier model is auto-selected. Ignored unless `fastAnswer`.
803
+ */
804
+ fastAnswerModel?: string;
720
805
  }
721
806
  interface CascadeRunResult {
722
807
  output: string;
@@ -738,8 +823,23 @@ interface CascadeRunResult {
738
823
  declare class ModelSelector {
739
824
  private availableProviders;
740
825
  private availableModels;
826
+ /**
827
+ * Per-provider set of model ids the provider's own API confirmed it serves
828
+ * (from listModels discovery). When present for a provider, AUTO selection
829
+ * skips that provider's bundled catalog ids that aren't in the set — so a
830
+ * stale/access-gated catalog id is never picked, then 404'd, then failed over.
831
+ * Absent for a provider ⇒ no validation ran ⇒ behave exactly as before.
832
+ */
833
+ private validatedIds;
741
834
  constructor(availableProviders: Set<ProviderType>);
742
835
  addDynamicModel(model: ModelInfo): void;
836
+ /** Record the ids a provider's API actually serves (discovery). Empty ⇒ ignored. */
837
+ setValidatedModels(provider: ProviderType, ids: string[]): void;
838
+ /**
839
+ * A model is usable for AUTO selection when its provider is available AND —
840
+ * if that provider was validated — the id is one the provider confirmed.
841
+ */
842
+ private isUsable;
743
843
  /**
744
844
  * Permanently drop a model from the available set for this session. Used by
745
845
  * the router's 404 / "model not found" self-heal so a dead id is never
@@ -777,6 +877,11 @@ interface ModelStat {
777
877
  totalRetries: number;
778
878
  totalCostUsd: number;
779
879
  sampleCount: number;
880
+ /** Sum of input/context tokens across samples — so we can see whether a
881
+ * model tends to fail on larger contexts and route less to it accordingly. */
882
+ totalContextTokens: number;
883
+ /** Sum of context tokens on the FAILED samples only. */
884
+ failureContextTokens: number;
780
885
  }
781
886
  interface FeatureStat {
782
887
  totalCostUsd: number;
@@ -786,11 +891,19 @@ declare class ModelPerformanceTracker {
786
891
  private stats;
787
892
  private featureStats;
788
893
  private readonly statsFile;
894
+ private readonly readOnly;
789
895
  private loaded;
790
- constructor(statsFile?: string);
896
+ /**
897
+ * @param statsFile where stats persist (cloud → the persistent volume).
898
+ * @param options.readOnly consume the shared scores but don't record/save this
899
+ * run's outcomes — the opt-out path for users who declined to contribute.
900
+ */
901
+ constructor(statsFile?: string, options?: {
902
+ readOnly?: boolean;
903
+ });
791
904
  load(): Promise<void>;
792
905
  save(): Promise<void>;
793
- record(modelId: string, taskType: TaskType, outcome: 'success' | 'failure', retries?: number, costUsd?: number): void;
906
+ record(modelId: string, taskType: TaskType, outcome: 'success' | 'failure', retries?: number, costUsd?: number, contextTokens?: number): void;
794
907
  recordFeatureCost(featureTag: string, costUsd: number): void;
795
908
  /**
796
909
  * Record an explicit user rating (good/bad). Counts as 3 automatic samples
@@ -856,7 +969,7 @@ declare class TaskAnalyzer {
856
969
  * Record the outcome of a completed run across all tiers that were selected
857
970
  * during this session and persist stats to disk.
858
971
  */
859
- recordRunOutcome(outcome: 'success' | 'failure', costByTier: Record<string, number>): void;
972
+ recordRunOutcome(outcome: 'success' | 'failure', costByTier: Record<string, number>, contextTokens?: number): void;
860
973
  /**
861
974
  * Record an explicit user rating (good/bad) for the last run's selected models.
862
975
  * Explicit ratings carry 3× the weight of auto-detected outcomes.
@@ -1080,6 +1193,12 @@ declare class WorldStateDB {
1080
1193
  private dbPath;
1081
1194
  private encryptionKey;
1082
1195
  constructor(workspacePath: string, debugMode?: boolean);
1196
+ /**
1197
+ * Append the CURRENTLY-stored fact for (e, r) to the history ledger before it
1198
+ * is overwritten/deleted, timestamped [its recorded time → now]. No-op when
1199
+ * there is no current row. The value stays encrypted with the same key.
1200
+ */
1201
+ private archiveCurrentFact;
1083
1202
  private initEncryptionKey;
1084
1203
  private encrypt;
1085
1204
  private decrypt;
@@ -1117,8 +1236,27 @@ declare class WorldStateDB {
1117
1236
  * delete — users can prune what the planner remembers about their project.
1118
1237
  */
1119
1238
  deleteFact(entity: string, relation: string): boolean;
1120
- /** Delete every fact. Returns how many were removed. */
1239
+ /** Delete every fact. Returns how many were removed. History is preserved. */
1121
1240
  clearFacts(): number;
1241
+ /**
1242
+ * Prior values for one (entity, relation), newest-first — the durable trail of
1243
+ * what this fact used to be before it was overwritten or deleted. Decrypted
1244
+ * here (the ciphertext never leaves this process).
1245
+ */
1246
+ getFactHistory(entity: string, relation: string): Array<{
1247
+ value: string;
1248
+ sourceWorker: string;
1249
+ validFrom: string;
1250
+ validTo: string;
1251
+ change: string;
1252
+ }>;
1253
+ /**
1254
+ * Restore a historical value for (entity, relation) as the current fact. The
1255
+ * value currently in place (if any) is itself archived first, so restore is
1256
+ * undoable. When `validFrom` is given, restores that exact historical entry;
1257
+ * otherwise restores the most recent one. Returns whether anything was restored.
1258
+ */
1259
+ restoreFact(entity: string, relation: string, validFrom?: string): boolean;
1122
1260
  exportKnowledge(): {
1123
1261
  facts: WorldFact[];
1124
1262
  worldLog: WorldStateEntry[];
@@ -1272,6 +1410,15 @@ declare class CascadeRouter extends EventEmitter {
1272
1410
  refreshLiveData(): Promise<void>;
1273
1411
  /** Returns the live-data provider once refreshLiveData has run (UX/insight). */
1274
1412
  getLiveData(): LiveDataProvider | undefined;
1413
+ /**
1414
+ * Validate the official cloud providers (anthropic/openai/gemini) against
1415
+ * their own /models list at init time so AUTO selection only picks a model the
1416
+ * key actually serves. This is the fix for "a non-existent catalog model gets
1417
+ * selected, then the run fails over through several models before one works".
1418
+ * Cached per key (TTL) so a per-request router doesn't re-discover; fully
1419
+ * best-effort + time-boxed, so offline/error simply keeps the bundled catalog.
1420
+ */
1421
+ private validateCloudProviderModels;
1275
1422
  /**
1276
1423
  * Query each available cloud provider's live model list and register the
1277
1424
  * results. Confirms catalog ids still exist and surfaces newly released
@@ -1322,6 +1469,12 @@ declare class CascadeRouter extends EventEmitter {
1322
1469
  /** Set (or clear) the current run's abort signal for instant cancellation. */
1323
1470
  setRunSignal(signal: AbortSignal | undefined): void;
1324
1471
  getSelector(): ModelSelector;
1472
+ /**
1473
+ * The tightest context window (in tokens) across the resolved tier models, or
1474
+ * undefined if none are resolved yet. Extended-context compaction budgets
1475
+ * against this so the compacted context fits whichever tier handles the run.
1476
+ */
1477
+ getReferenceContextWindow(): number | undefined;
1325
1478
  /** Wire the Cascade Auto task analyzer used for per-subtask model routing. */
1326
1479
  setTaskAnalyzer(analyzer: TaskAnalyzer): void;
1327
1480
  setWorldStateDB(db: WorldStateDB | undefined): void;
@@ -1399,6 +1552,8 @@ declare class CascadeRouter extends EventEmitter {
1399
1552
  * Used to populate the desktop model pickers with the user's real models.
1400
1553
  */
1401
1554
  getAvailableModels(): ModelInfo[];
1555
+ /** Logs why a configured provider failed its availability probe. */
1556
+ private emitProbeFailure;
1402
1557
  private detectAvailableProviders;
1403
1558
  private discoverOllamaModels;
1404
1559
  /** Returns true when at least one real model was discovered from the endpoint. */
@@ -1451,9 +1606,11 @@ declare abstract class BaseTool {
1451
1606
 
1452
1607
  interface McpServerConfig {
1453
1608
  name: string;
1454
- command: string;
1609
+ command?: string;
1455
1610
  args?: string[];
1456
1611
  env?: Record<string, string>;
1612
+ url?: string;
1613
+ headers?: Record<string, string>;
1457
1614
  }
1458
1615
  /**
1459
1616
  * Gate called before each MCP server is spawned. Lets the caller (REPL or
@@ -1487,7 +1644,16 @@ declare class McpClient {
1487
1644
  private approvalCallback;
1488
1645
  private onWarn;
1489
1646
  constructor(options?: McpClientOptions);
1490
- connect(server: McpServerConfig): Promise<void>;
1647
+ connect(server: McpServerConfig, opts?: {
1648
+ authProvider?: OAuthClientProvider;
1649
+ }): Promise<void>;
1650
+ /**
1651
+ * Connect to a remote MCP server. Tries Streamable HTTP first (the current
1652
+ * transport); if that fails (a legacy server that only speaks SSE), retries
1653
+ * once over SSE. Auth headers ride on every request. Returns the live
1654
+ * transport so the caller can register it.
1655
+ */
1656
+ private connectRemote;
1491
1657
  disconnect(serverName: string): Promise<void>;
1492
1658
  disconnectAll(): Promise<void>;
1493
1659
  callTool(serverName: string, toolName: string, input: Record<string, unknown>): Promise<string>;
@@ -1813,13 +1979,14 @@ declare class T1Administrator extends BaseTier {
1813
1979
  /** One entry in the per-run orchestration decision trail (see /why). */
1814
1980
  interface DecisionLogEntry {
1815
1981
  at: string;
1816
- kind: 'complexity' | 'model' | 'failover' | 'escalation';
1982
+ kind: 'complexity' | 'model' | 'failover' | 'escalation' | 'context';
1817
1983
  detail: string;
1818
1984
  }
1819
1985
  declare class Cascade extends EventEmitter {
1820
1986
  private router;
1821
1987
  private toolRegistry;
1822
1988
  private mcpClient;
1989
+ private codeIndex?;
1823
1990
  private config;
1824
1991
  /** Orchestration decisions for the CURRENT run — cleared on each run(). */
1825
1992
  private decisionLog;
@@ -1855,6 +2022,13 @@ declare class Cascade extends EventEmitter {
1855
2022
  */
1856
2023
  private pendingMcpApprovals;
1857
2024
  private requestMcpApproval;
2025
+ /**
2026
+ * Build the workspace code index and register the `code_search` tool. Uses the
2027
+ * user's own provider key for embeddings (and, if chat-capable, reranking).
2028
+ * The index persists to a per-workspace SQLite file; `autoRefresh` brings it
2029
+ * up to date at init (incremental — only changed files re-embed).
2030
+ */
2031
+ private initCodeIndex;
1858
2032
  private recordDecision;
1859
2033
  /**
1860
2034
  * The orchestration decision trail for the most recent run: complexity
@@ -1872,6 +2046,18 @@ declare class Cascade extends EventEmitter {
1872
2046
  * directly (no re-decompose).
1873
2047
  */
1874
2048
  resolvePlanApproval(approved: boolean, note?: string, editedPlan?: TaskPlan): void;
2049
+ private pendingContextApproval?;
2050
+ /** Resolve a pending extended-context confirmation from a UI listener. */
2051
+ resolveContextApproval(approved: boolean): void;
2052
+ private requestContextApproval;
2053
+ /**
2054
+ * Extended context: when enabled, compact an over-budget conversation history
2055
+ * (rolling summary — cheap, automatic) and/or a single oversized input (chunk
2056
+ * + map-reduce — confirm-gated) so the run fits the model's window. Returns a
2057
+ * new options object with the compacted prompt/history; a no-op when disabled,
2058
+ * when nothing overflows, or when the model window isn't known yet.
2059
+ */
2060
+ private applyExtendedContext;
1875
2061
  /**
1876
2062
  * Autonomy control (used by the /auto command). 'auto' makes the next run
1877
2063
  * hands-off: the plan gate auto-approves and non-dangerous tools auto-approve,
@@ -1919,6 +2105,22 @@ declare class Cascade extends EventEmitter {
1919
2105
  private estimatePlanCost;
1920
2106
  init(): Promise<void>;
1921
2107
  private isCasualGreeting;
2108
+ /**
2109
+ * Pure small talk that a single tool-less model answers best: greetings,
2110
+ * acknowledgements and self-identity questions. Deliberately NARROWER than
2111
+ * looksLikeConversational — "what is X" / "show me Y" / "list Z" need tools
2112
+ * or real lookup and stay on the worker path. Bare confirmations ("yes",
2113
+ * "ok") in an ongoing conversation are task input, not small talk.
2114
+ */
2115
+ private looksLikeSmallTalk;
2116
+ /**
2117
+ * A terse option/menu selection ("3", "b)", "option 2") replying to an
2118
+ * ongoing conversation. These inherit the prior turn's intent — they are
2119
+ * usually an ACTION choice, so they must not be short-circuited to a
2120
+ * tool-less direct answer, and a context-free on-device hint about them
2121
+ * is noise (the hint classifier never saw the conversation).
2122
+ */
2123
+ private looksLikeTerseOptionReply;
1922
2124
  private looksLikeSimpleArtifactTask;
1923
2125
  private looksLikeConversational;
1924
2126
  /**
@@ -1951,8 +2153,31 @@ declare class Cascade extends EventEmitter {
1951
2153
  private looksLikeModerateBuild;
1952
2154
  private static globCache;
1953
2155
  private countWorkspaceFiles;
2156
+ /**
2157
+ * Raises a raw complexity verdict (from the classifier LLM or a caller hint)
2158
+ * to the tier the prompt's signals demand. A small model that under-rates
2159
+ * clearly multi-step build work can't strand it below where it belongs:
2160
+ * explicit build+scale signals floor it to Complex (full T1→T2→T3), and a
2161
+ * single-deliverable build floors a "Simple" up to Moderate (one manager).
2162
+ * Conservative on purpose — the source is free to go higher, and a manual
2163
+ * tier override is the escape hatch. Records the decision it makes.
2164
+ */
2165
+ private floorComplexity;
1954
2166
  private determineComplexity;
1955
2167
  run(options: CascadeRunOptions): Promise<CascadeRunResult>;
2168
+ /**
2169
+ * Direct single-model reply — the "fast answer" path. No planning, no workers,
2170
+ * no tools, no artifact verification: the user's message (with recent history)
2171
+ * answered by one mid-tier model, streamed as the primary output and shaped
2172
+ * into the normal CascadeRunResult so persistence + the run receipt still work.
2173
+ */
2174
+ private runFastAnswer;
2175
+ /**
2176
+ * Distill a completed session into durable facts and store them in the
2177
+ * project knowledge graph (opt-in via memory.rememberSessions). Best-effort:
2178
+ * skips trivial exchanges, uses one cheap T3 call, and swallows all errors.
2179
+ */
2180
+ private rememberSession;
1956
2181
  getRouter(): CascadeRouter;
1957
2182
  getToolRegistry(): ToolRegistry;
1958
2183
  /**
@@ -2589,6 +2814,519 @@ interface HookResult {
2589
2814
  error?: string;
2590
2815
  }
2591
2816
 
2817
+ /** The persisted state of one MCP server's OAuth: DCR result, tokens, PKCE verifier. */
2818
+ interface McpOAuthState {
2819
+ clientInformation?: OAuthClientInformationMixed;
2820
+ tokens?: OAuthTokens;
2821
+ codeVerifier?: string;
2822
+ }
2823
+ /** Pluggable persistence for a single server's OAuth state (differs per surface). */
2824
+ interface McpOAuthStore {
2825
+ load(): McpOAuthState | undefined | Promise<McpOAuthState | undefined>;
2826
+ save(state: McpOAuthState): void | Promise<void>;
2827
+ clear?(): void | Promise<void>;
2828
+ }
2829
+ interface McpOAuthProviderOptions {
2830
+ /** Where the authorization server redirects back (our callback / loopback). */
2831
+ redirectUrl: string;
2832
+ store: McpOAuthStore;
2833
+ /** Send the user to the authorization URL (open a browser / hand it to the web). */
2834
+ redirect: (authorizationUrl: URL) => void | Promise<void>;
2835
+ clientName?: string;
2836
+ scope?: string;
2837
+ /** Fixed OAuth `state` (e.g. the cloud pending-flow key). Random if omitted. */
2838
+ state?: string;
2839
+ }
2840
+ /**
2841
+ * Our `OAuthClientProvider`: storage + redirect for the MCP SDK's OAuth client.
2842
+ * The SDK calls these hooks while it runs discovery/DCR/PKCE/refresh for us.
2843
+ */
2844
+ declare class McpOAuthProvider implements OAuthClientProvider {
2845
+ private readonly opts;
2846
+ private cache;
2847
+ private readonly _state;
2848
+ constructor(opts: McpOAuthProviderOptions);
2849
+ /** The `state` this provider uses — callers validate it on the callback. */
2850
+ get oauthState(): string;
2851
+ private loaded;
2852
+ private persist;
2853
+ get redirectUrl(): string;
2854
+ get clientMetadata(): OAuthClientMetadata;
2855
+ state(): string;
2856
+ clientInformation(): Promise<OAuthClientInformationMixed | undefined>;
2857
+ saveClientInformation(info: OAuthClientInformationFull): Promise<void>;
2858
+ tokens(): Promise<OAuthTokens | undefined>;
2859
+ saveTokens(tokens: OAuthTokens): Promise<void>;
2860
+ redirectToAuthorization(authorizationUrl: URL): Promise<void>;
2861
+ saveCodeVerifier(codeVerifier: string): Promise<void>;
2862
+ codeVerifier(): Promise<string>;
2863
+ invalidateCredentials(scope: 'all' | 'client' | 'tokens' | 'verifier' | 'discovery'): Promise<void>;
2864
+ }
2865
+ /** A 0600 JSON file store for one server's OAuth state (desktop/CLI). */
2866
+ declare class FileMcpOAuthStore implements McpOAuthStore {
2867
+ private readonly filePath;
2868
+ constructor(filePath: string);
2869
+ load(): McpOAuthState | undefined;
2870
+ save(state: McpOAuthState): void;
2871
+ clear(): void;
2872
+ }
2873
+ /**
2874
+ * Build a refresh-only provider over a file store, for a run connecting an
2875
+ * already-authorized OAuth MCP server. Refresh is silent; if the refresh token
2876
+ * is dead the transport would need a browser redirect, which a headless run
2877
+ * can't do — so `redirect` throws a clear "reconnect" message instead.
2878
+ */
2879
+ declare function fileOAuthProvider(storePath: string): McpOAuthProvider;
2880
+ /** Drive the OAuth flow to the point of a browser redirect (or already-authorized). */
2881
+ declare function beginMcpOAuth(provider: OAuthClientProvider, serverUrl: string): Promise<'AUTHORIZED' | 'REDIRECT'>;
2882
+ /** Complete the flow with the authorization code (saves tokens via the provider). */
2883
+ declare function completeMcpOAuth(provider: OAuthClientProvider, serverUrl: string, authorizationCode: string): Promise<void>;
2884
+ /** The authorization server (issuer) protecting an MCP server, if it advertises one. */
2885
+ declare function discoverMcpAuthServer(serverUrl: string): Promise<string | undefined>;
2886
+ /** Refresh an access token from a stored refresh token + client registration. */
2887
+ declare function refreshMcpToken(input: {
2888
+ authorizationServerUrl: string;
2889
+ clientInformation: OAuthClientInformationMixed;
2890
+ refreshToken: string;
2891
+ }): Promise<OAuthTokens>;
2892
+ interface LoopbackOAuthResult {
2893
+ tokens: OAuthTokens;
2894
+ clientInformation?: OAuthClientInformationMixed;
2895
+ }
2896
+ /**
2897
+ * Run the full OAuth connect for a rich native client: open a one-shot
2898
+ * `127.0.0.1` listener, let the MCP SDK drive discovery/DCR/PKCE, open the
2899
+ * system browser via `openUrl`, catch the code on the loopback, finish the
2900
+ * exchange, and return the tokens (also persisted through `store`).
2901
+ */
2902
+ declare function connectMcpWithLoopbackOAuth(opts: {
2903
+ serverUrl: string;
2904
+ store: McpOAuthStore;
2905
+ openUrl: (url: string) => void | Promise<void>;
2906
+ clientName?: string;
2907
+ scope?: string;
2908
+ signal?: AbortSignal;
2909
+ timeoutMs?: number;
2910
+ }): Promise<LoopbackOAuthResult>;
2911
+
2912
+ /** A unit of indexed text. `sourceId` groups chunks from one document/file. */
2913
+ interface Chunk {
2914
+ id: string;
2915
+ text: string;
2916
+ sourceId: string;
2917
+ /** Order of this chunk within its source (0-based). */
2918
+ ord: number;
2919
+ meta?: Record<string, unknown>;
2920
+ }
2921
+ /** A chunk with a relevance score (higher = more relevant). */
2922
+ interface ScoredChunk extends Chunk {
2923
+ score: number;
2924
+ }
2925
+ /** Turns text into embedding vectors. Implementations wrap a provider API or a
2926
+ * local model. `model` + `dims` are recorded with every vector so a future
2927
+ * model change forces a clean re-index rather than silent quality rot. */
2928
+ interface Embedder {
2929
+ readonly model: string;
2930
+ /** Vector dimension. 0 until the first embed call resolves it. */
2931
+ readonly dims: number;
2932
+ /** Embed a batch of texts → one vector per input (same order). */
2933
+ embed(texts: string[]): Promise<number[][]>;
2934
+ }
2935
+ interface SearchOptions {
2936
+ namespace: string;
2937
+ k?: number;
2938
+ /** Restrict results to these source ids (e.g. the docs attached to a run). */
2939
+ sourceIds?: string[];
2940
+ }
2941
+ /** Persists chunk vectors and answers the two first-stage queries of hybrid
2942
+ * retrieval. The Phase-1 implementation is SQLite-backed (BLOB vectors +
2943
+ * FTS5), swappable later for a dedicated ANN index behind this interface. */
2944
+ interface VectorStore {
2945
+ upsert(records: Array<{
2946
+ chunk: Chunk;
2947
+ vector: number[];
2948
+ }>, embedModel: string): void;
2949
+ /** True once this source has chunks for the given embed model (skip re-embed). */
2950
+ hasSource(namespace: string, sourceId: string, embedModel: string): boolean;
2951
+ lexicalSearch(query: string, opts: SearchOptions): ScoredChunk[];
2952
+ denseSearch(queryVector: number[], opts: SearchOptions): ScoredChunk[];
2953
+ deleteSource(namespace: string, sourceId: string): void;
2954
+ }
2955
+
2956
+ interface ChunkOptions {
2957
+ /** Target chunk size in characters (~512 tokens by default). */
2958
+ targetChars?: number;
2959
+ /** Overlap carried from the end of one chunk into the next. */
2960
+ overlapChars?: number;
2961
+ /** Drop a trailing chunk smaller than this unless it's the only one. */
2962
+ minChars?: number;
2963
+ }
2964
+ /**
2965
+ * Chunk prose/markdown into overlapping windows. Returns `{ text, ord }` in
2966
+ * document order. Deterministic and dependency-free.
2967
+ */
2968
+ declare function chunkText(input: string, options?: ChunkOptions): Array<{
2969
+ text: string;
2970
+ ord: number;
2971
+ }>;
2972
+
2973
+ interface OpenAIEmbedderOptions {
2974
+ apiKey?: string;
2975
+ baseURL?: string;
2976
+ model: string;
2977
+ /** Matryoshka truncation for text-embedding-3-* (e.g. 512). Omit for native. */
2978
+ dimensions?: number;
2979
+ }
2980
+ /**
2981
+ * Embedder backed by an OpenAI-compatible `/v1/embeddings` endpoint. Works
2982
+ * against OpenAI, OpenAI-compatible gateways, and Ollama (which exposes the same
2983
+ * route) — so the cloud uses a provider API and desktop/CLI can point at a local
2984
+ * Ollama with no separate runtime.
2985
+ */
2986
+ declare class OpenAICompatibleEmbedder implements Embedder {
2987
+ readonly model: string;
2988
+ private client;
2989
+ private dimensions;
2990
+ private resolvedDims;
2991
+ constructor(opts: OpenAIEmbedderOptions);
2992
+ get dims(): number;
2993
+ embed(texts: string[]): Promise<number[][]>;
2994
+ }
2995
+ /**
2996
+ * Pick an embeddings-capable provider from the user's configured providers and
2997
+ * build an Embedder, or return null when none can embed (e.g. an Anthropic-only
2998
+ * setup). Prefers a hosted OpenAI/compatible key over a local Ollama.
2999
+ */
3000
+ declare function embedderFromProviders(providers: ProviderConfig[], opts?: {
3001
+ model?: string;
3002
+ dimensions?: number;
3003
+ }): Embedder | null;
3004
+
3005
+ declare class SqliteVectorStore implements VectorStore {
3006
+ private db;
3007
+ constructor(db: Database.Database);
3008
+ private migrate;
3009
+ upsert(records: Array<{
3010
+ chunk: Chunk;
3011
+ vector: number[];
3012
+ }>, embedModel: string): void;
3013
+ hasSource(namespace: string, sourceId: string, embedModel: string): boolean;
3014
+ deleteSource(namespace: string, sourceId: string): void;
3015
+ lexicalSearch(query: string, opts: SearchOptions): ScoredChunk[];
3016
+ denseSearch(queryVector: number[], opts: SearchOptions): ScoredChunk[];
3017
+ private sourceFilter;
3018
+ }
3019
+
3020
+ interface Reranker {
3021
+ readonly name: string;
3022
+ /** Reorder candidates for a query; return the best `topK`, best-first. */
3023
+ rerank(query: string, candidates: ScoredChunk[], topK: number): Promise<ScoredChunk[]>;
3024
+ }
3025
+ /** A minimal text-completion function (prompt in, text out). */
3026
+ type CompleteFn = (prompt: string) => Promise<string>;
3027
+ /** Parse a model's "3,1,4" answer into 0-based indices, keeping only valid,
3028
+ * in-range, non-duplicate entries. Returns null when nothing parses. */
3029
+ declare function parseRankOrder(reply: string, count: number): number[] | null;
3030
+ declare class LLMReranker implements Reranker {
3031
+ readonly name = "llm-listwise";
3032
+ private complete;
3033
+ /** Cap on candidates sent to the model (keeps the prompt + cost bounded). */
3034
+ private maxCandidates;
3035
+ constructor(opts: {
3036
+ complete: CompleteFn;
3037
+ maxCandidates?: number;
3038
+ });
3039
+ rerank(query: string, candidates: ScoredChunk[], topK: number): Promise<ScoredChunk[]>;
3040
+ }
3041
+ /**
3042
+ * Build a completion function from the user's providers using an OpenAI-
3043
+ * compatible `/chat/completions` endpoint (OpenAI, compatible gateways, Ollama).
3044
+ * Returns null when no such provider is configured (reranking then no-ops and
3045
+ * the fused order stands). Mirrors embedderFromProviders so the same key powers
3046
+ * both stages.
3047
+ */
3048
+ declare function chatCompleterFromProviders(providers: ProviderConfig[], opts?: {
3049
+ model?: string;
3050
+ }): CompleteFn | null;
3051
+
3052
+ interface RetrieverSearchOptions {
3053
+ namespace: string;
3054
+ /** Final number of chunks to return. */
3055
+ k?: number;
3056
+ /** Restrict to these source ids (e.g. only this run's attached docs). */
3057
+ sourceIds?: string[];
3058
+ /** Candidates to pull from each first stage before fusion. */
3059
+ candidates?: number;
3060
+ /** RRF damping constant (higher = flatter). Standard default is 60. */
3061
+ rrfK?: number;
3062
+ /** Rerank the fused candidates when a reranker is configured (default true). */
3063
+ rerank?: boolean;
3064
+ }
3065
+ /**
3066
+ * Fuse several ranked lists into one by Reciprocal Rank Fusion. Each list
3067
+ * contributes 1/(rrfK + rank) to an item's score; identical items across lists
3068
+ * accumulate. Returns items best-first.
3069
+ */
3070
+ declare function reciprocalRankFusion(lists: ScoredChunk[][], rrfK?: number): ScoredChunk[];
3071
+ declare class Retriever {
3072
+ private readonly embedder;
3073
+ private readonly store;
3074
+ /** Optional second-stage reranker (Phase 2). When set, search reranks the
3075
+ * fused candidates before returning the top-k. */
3076
+ private readonly reranker?;
3077
+ constructor(embedder: Embedder, store: VectorStore,
3078
+ /** Optional second-stage reranker (Phase 2). When set, search reranks the
3079
+ * fused candidates before returning the top-k. */
3080
+ reranker?: Reranker | undefined);
3081
+ /** Chunk model this retriever embeds with (for cache/version keys). */
3082
+ get embedModel(): string;
3083
+ /** True when this source is already indexed under the current embed model. */
3084
+ isIndexed(namespace: string, sourceId: string): boolean;
3085
+ /**
3086
+ * Embed and store a source's chunks. Skips work when the source is already
3087
+ * indexed under the current embed model (so re-runs don't re-embed). Returns
3088
+ * the number of chunks newly indexed.
3089
+ */
3090
+ index(namespace: string, sourceId: string, chunks: Array<{
3091
+ text: string;
3092
+ ord: number;
3093
+ }>): Promise<number>;
3094
+ /** Hybrid search: lexical ∪ dense, fused with RRF, then optionally reranked. */
3095
+ search(query: string, opts: RetrieverSearchOptions): Promise<ScoredChunk[]>;
3096
+ }
3097
+
3098
+ type RetrievalMode = 'none' | 'cag' | 'rag' | 'graph' | 'code';
3099
+ interface RetrievalContext {
3100
+ /** Retrievable sources attached to this run (e.g. documents). */
3101
+ sourceCount: number;
3102
+ /** Total characters across those sources. */
3103
+ totalChars: number;
3104
+ /** Below this size, sources are injected in full (cache-augmented). */
3105
+ cagCharBudget: number;
3106
+ /** A single direct model call — no orchestration/retrieval machinery. */
3107
+ fastAnswer?: boolean;
3108
+ }
3109
+ interface RetrievalPlan {
3110
+ mode: RetrievalMode;
3111
+ /** Human-readable rationale (surfaced in notices / decision logs). */
3112
+ reason: string;
3113
+ }
3114
+ /** Rough characters-per-token, the standard ~4:1 approximation for English text. */
3115
+ declare const CHARS_PER_TOKEN = 4;
3116
+ /**
3117
+ * The character budget below which attached documents are injected in full
3118
+ * (CAG) rather than retrieved (RAG) — derived from the model context window
3119
+ * instead of a fixed byte cliff. Reserve most of the window for the system
3120
+ * prompt, conversation history, and the model's own output, and let attached
3121
+ * documents occupy up to `docFraction` of what the window can hold. A larger
3122
+ * window therefore admits larger documents in full; retrieval only kicks in
3123
+ * for corpora that genuinely wouldn't fit. (A 52 KB doc is ~13k tokens — it
3124
+ * fits comfortably in any modern window, so it should never be forced to RAG.)
3125
+ */
3126
+ declare function cagCharBudget(contextWindowTokens: number, opts?: {
3127
+ docFraction?: number;
3128
+ charsPerToken?: number;
3129
+ }): number;
3130
+ /**
3131
+ * Choose the retrieval mode for a run. Pure and deterministic so it's unit-
3132
+ * testable and reusable across cloud, desktop, and CLI.
3133
+ */
3134
+ declare function planRetrieval(ctx: RetrievalContext): RetrievalPlan;
3135
+
3136
+ interface CodeChunker {
3137
+ chunk(text: string, opts?: {
3138
+ filename?: string;
3139
+ }): Array<{
3140
+ text: string;
3141
+ ord: number;
3142
+ }>;
3143
+ }
3144
+ interface CodeChunkOptions {
3145
+ targetChars?: number;
3146
+ /** Lines of trailing context carried into the next chunk when a segment is
3147
+ * hard-split (0 for whole-definition segments, which don't need it). */
3148
+ overlapLines?: number;
3149
+ filename?: string;
3150
+ }
3151
+ /**
3152
+ * Chunk source code into definition-aligned, size-bounded pieces. Returns
3153
+ * `{ text, ord }` in file order. Deterministic and dependency-free.
3154
+ */
3155
+ declare function chunkCode(text: string, opts?: CodeChunkOptions): Array<{
3156
+ text: string;
3157
+ ord: number;
3158
+ }>;
3159
+ /** Default heuristic implementation of the CodeChunker seam. */
3160
+ declare const heuristicCodeChunker: CodeChunker;
3161
+
3162
+ interface FileManifest {
3163
+ /** repo-relative path → content hash (hex). */
3164
+ files: Record<string, string>;
3165
+ /** Fingerprint over the whole (sorted) manifest — equal roots ⇒ no changes. */
3166
+ root: string;
3167
+ }
3168
+ interface ManifestDiff {
3169
+ added: string[];
3170
+ changed: string[];
3171
+ removed: string[];
3172
+ unchanged: string[];
3173
+ }
3174
+ declare function hashContent(content: string | Buffer): string;
3175
+ declare function buildManifest(entries: Array<{
3176
+ path: string;
3177
+ hash: string;
3178
+ }>): FileManifest;
3179
+ declare function diffManifest(oldManifest: FileManifest | null, next: FileManifest): ManifestDiff;
3180
+
3181
+ interface WorkspaceIndexOptions {
3182
+ root: string;
3183
+ /** better-sqlite3 DB hosting vectors + the manifest. */
3184
+ db: Database.Database;
3185
+ embedder: Embedder;
3186
+ reranker?: Reranker;
3187
+ /** Predicate for paths to skip (e.g. CascadeIgnore.isIgnored bound to root). */
3188
+ isIgnored?: (absPath: string) => boolean;
3189
+ namespace?: string;
3190
+ extensions?: string[];
3191
+ maxFileBytes?: number;
3192
+ chunker?: CodeChunker;
3193
+ }
3194
+ interface RefreshResult {
3195
+ filesIndexed: number;
3196
+ filesRemoved: number;
3197
+ filesUnchanged: number;
3198
+ chunks: number;
3199
+ root: string;
3200
+ }
3201
+ declare class WorkspaceIndex {
3202
+ private readonly root;
3203
+ private readonly db;
3204
+ private readonly store;
3205
+ private readonly retriever;
3206
+ private readonly namespace;
3207
+ private readonly extensions;
3208
+ private readonly maxFileBytes;
3209
+ private readonly isIgnored;
3210
+ private readonly chunker;
3211
+ constructor(opts: WorkspaceIndexOptions);
3212
+ private migrate;
3213
+ /** Recursively collect indexable files under root (relative paths). */
3214
+ private collectFiles;
3215
+ private loadStoredManifest;
3216
+ private saveManifest;
3217
+ /**
3218
+ * Scan the workspace and bring the index up to date, re-embedding only files
3219
+ * whose contents changed since the last refresh. Returns what changed.
3220
+ */
3221
+ refresh(onFile?: (rel: string) => void): Promise<RefreshResult>;
3222
+ /** Hybrid + reranked search over the indexed codebase. */
3223
+ search(query: string, k?: number): Promise<ScoredChunk[]>;
3224
+ }
3225
+
3226
+ /** The slice of world-state this retriever needs — so it's testable in isolation. */
3227
+ interface GraphFactSource {
3228
+ getAllFacts(): WorldFact[];
3229
+ getFactsForEntities(entities: string[]): WorldFact[];
3230
+ }
3231
+ interface GraphSearchOptions {
3232
+ /** How many hops to expand from the seed entities (default 1). */
3233
+ hops?: number;
3234
+ /** Max facts to return. */
3235
+ limit?: number;
3236
+ }
3237
+ interface RankedFact extends WorldFact {
3238
+ /** Query-overlap score (higher = more relevant); 0 for pure hop-neighbors. */
3239
+ score: number;
3240
+ /** Hop distance from a seed entity (0 = seed). */
3241
+ hop: number;
3242
+ }
3243
+ declare class GraphRetriever {
3244
+ private readonly source;
3245
+ constructor(source: GraphFactSource);
3246
+ /**
3247
+ * Retrieve facts relevant to a query, expanding the graph a few hops from the
3248
+ * entities the query mentions. Returns facts ranked by relevance.
3249
+ */
3250
+ search(query: string, opts?: GraphSearchOptions): RankedFact[];
3251
+ /** Human-readable "- entity relation value" lines for injection into a run. */
3252
+ format(facts: RankedFact[]): string;
3253
+ }
3254
+
3255
+ /**
3256
+ * Lets a worker search the indexed workspace codebase by meaning + keywords and
3257
+ * get back the most relevant passages with their file paths. Backed by the
3258
+ * hybrid + reranked WorkspaceIndex, so it finds code by concept, not just exact
3259
+ * string match.
3260
+ */
3261
+ declare class CodeSearchTool extends BaseTool {
3262
+ private readonly index;
3263
+ readonly name = "code_search";
3264
+ readonly description = "Search the indexed workspace codebase for code relevant to a query (semantic + keyword). Returns the most relevant snippets with their file paths. Use to find where something is implemented or how an API is used.";
3265
+ readonly inputSchema: {
3266
+ type: string;
3267
+ properties: {
3268
+ query: {
3269
+ type: string;
3270
+ description: string;
3271
+ };
3272
+ k: {
3273
+ type: string;
3274
+ description: string;
3275
+ };
3276
+ };
3277
+ required: string[];
3278
+ };
3279
+ constructor(index: WorkspaceIndex);
3280
+ execute(input: Record<string, unknown>, _options: ToolExecuteOptions): Promise<string>;
3281
+ }
3282
+
3283
+ /**
3284
+ * Lets a worker query the project knowledge graph (world-state) for facts
3285
+ * related to entities in the query, following relationships a couple of hops.
3286
+ * Best for relational / multi-hop questions ("what depends on X?", "which
3287
+ * component owns Y?") that vector search answers poorly.
3288
+ */
3289
+ declare class GraphSearchTool extends BaseTool {
3290
+ readonly name = "knowledge_graph_search";
3291
+ readonly description = "Search the project knowledge graph (learned entity\u2192relation\u2192value facts) for information related to a query, expanding a couple of hops across relationships. Use for relational or multi-hop questions about the project.";
3292
+ readonly inputSchema: {
3293
+ type: string;
3294
+ properties: {
3295
+ query: {
3296
+ type: string;
3297
+ description: string;
3298
+ };
3299
+ hops: {
3300
+ type: string;
3301
+ description: string;
3302
+ };
3303
+ };
3304
+ required: string[];
3305
+ };
3306
+ private retriever;
3307
+ constructor(source: GraphFactSource);
3308
+ execute(input: Record<string, unknown>, _options: ToolExecuteOptions): Promise<string>;
3309
+ }
3310
+
3311
+ interface DistilledFact {
3312
+ entity: string;
3313
+ relation: string;
3314
+ value: string;
3315
+ }
3316
+ /** A run is worth distilling only if the conversation carried real content. */
3317
+ declare function sessionWorthRemembering(history: ConversationMessage[], latestPrompt: string, output: string): boolean;
3318
+ /**
3319
+ * Build the transcript the distiller reads: recent history + the final turn,
3320
+ * capped so a long session doesn't blow the distiller's budget.
3321
+ */
3322
+ declare function buildSessionTranscript(history: ConversationMessage[], latestPrompt: string, output: string, maxChars?: number): string;
3323
+ /**
3324
+ * Distill durable facts from a session transcript via one cheap model call.
3325
+ * `generate` returns the model's raw text; parsing is defensive (a JSON array of
3326
+ * triples anywhere in the reply). Never throws — returns [] on any problem.
3327
+ */
3328
+ declare function distillSessionFacts(transcript: string, generate: (prompt: string) => Promise<string>): Promise<DistilledFact[]>;
3329
+
2592
3330
  /** Structured log levels for audit entries */
2593
3331
  type LogLevel = 'debug' | 'info' | 'warn' | 'error';
2594
3332
  /** Extended audit entry with log level */
@@ -2783,4 +3521,211 @@ declare class CascadeToolError extends Error {
2783
3521
  declare function preferIpv4Host(url: string | undefined): string | undefined;
2784
3522
  declare function nodeHttpFetch(input: string | URL | Request, init?: RequestInit, redirectCount?: number): Promise<Response>;
2785
3523
 
2786
- export { AZURE_BASE_URL_TEMPLATE, type ApprovalRequest, type ApprovalResponse, type AuditEntry, AuditLogger, type BenchmarksConfig, type BudgetConfig, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, type CascadeConfig, type CascadeEvent, type CascadeEventType, CascadeIgnore, type CascadeMessage, CascadeRouter, type CascadeRunOptions, type CascadeRunResult, type CascadeThemeName, CascadeToolError, ConfigManager, type ConversationMessage, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, type DashboardConfig, DashboardServer, type EscalationPayload, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, type GenerateOptions, type GenerateResult, type HookDefinition, type HooksConfig, HooksRunner, type Identity, type ImageAttachment, Keystore, LM_STUDIO_BASE_URL, type LegacyThemeName, MODELS, McpClient, type McpServerConfig$1 as McpServerConfig, type MemoryConfig, MemoryStore, type Message, type MessageContent, type MessagePayload, type MessageStatus, type MessageType, type ModelInfo, type ModelOverrides, OLLAMA_BASE_URL, PROVIDER_DISPLAY_NAMES, type PeerMessage, type PeerMessageEvent, type PeerSyncPayload, type PeerSyncType, type PermissionDecision, type PermissionDecisionPayload, type PermissionRequest, type PermissionTrailEntry, type PlanReviewConfig, type ProviderConfig, type ProviderType, type RuntimeNode, type RuntimeNodeLog, type RuntimeRefreshPayload, type RuntimeScope, type RuntimeSession, type RuntimeSnapshotPayload, type ScheduledTask, type Session, type SessionCheckpoint, type SessionMetadata, type SessionSubscriptionPayload, type StatusUpdate, type StoredMessage, type StreamChunk, T1Administrator, type T1ToT2Assignment, T1_MODEL_PRIORITY, T2Manager, type T2Result, type T2ToT3Assignment, T2_MODEL_PRIORITY, type T3Result, type T3ResultPayload, type T3SubtaskSpec, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, type TaskComplexity, TaskScheduler, Telemetry, type TelemetryConfig, type Theme, type ThemeColors, type ThemeName, type TierConfig, type TierLimits, type TierRole, type TierStatus, type TokenUsage, type ToolCall, type ToolDefinition, type ToolExecuteOptions, ToolRegistry, type ToolResult, type ToolsConfig, VISION_MODEL_PRIORITY, type WebSearchConfig, type WebhookConfig, type WorkspaceConfig, createCascade, nodeHttpFetch, preferIpv4Host, runCascade, streamCascade };
3524
+ /**
3525
+ * Best-effort guess of the canonical base model an Azure deployment backs, from
3526
+ * its (arbitrary) deployment name. Ordered most-specific → least so "gpt-5-mini"
3527
+ * doesn't match the "gpt-5" base. Distinct point releases (gpt-5.5, gpt-5.4,
3528
+ * gpt-5.4-mini) resolve to their OWN base so their real economics + benchmark
3529
+ * scores apply — only unrecognised gpt-5.x fold into the gpt-5 base.
3530
+ * Returns null when the name gives no signal (e.g. "prod-fast") — the caller
3531
+ * then keeps neutral defaults, or the user can set an explicit base model.
3532
+ */
3533
+ declare function inferAzureBaseModel(deploymentName: string): string | null;
3534
+ /**
3535
+ * The ModelInfo for one configured Azure deployment. On Azure the deployment IS
3536
+ * the model (you address it by deployment name, and which base model backs it is
3537
+ * opaque to the API) — so each `providers[]` entry with a deploymentName becomes
3538
+ * one selectable model.
3539
+ *
3540
+ * Which base model it serves drives correct benchmark scoring + pricing, so we
3541
+ * resolve it: an explicit `cfg.model` (user override) wins, else we infer it from
3542
+ * the deployment name. When resolved to a known catalog model, this deployment
3543
+ * INHERITS that model's real economics (context window, pricing, vision, output
3544
+ * cap) while keeping the deployment name as its callable `id` and carrying the
3545
+ * base identity in `baseModelId`. Unresolved deployments fall back to neutral
3546
+ * GPT-4o-class defaults (an estimate, not $0), exactly as before.
3547
+ */
3548
+ declare function azureModelForDeployment(cfg: ProviderConfig): ModelInfo | null;
3549
+
3550
+ interface CloudUser {
3551
+ id: string;
3552
+ email: string | null;
3553
+ name: string | null;
3554
+ plan?: string;
3555
+ }
3556
+ interface CloudSession {
3557
+ serverUrl: string;
3558
+ accessToken: string;
3559
+ /** Epoch ms when the access token expires (refresh before this). */
3560
+ accessExpiresAt: number;
3561
+ refreshToken: string;
3562
+ user: CloudUser;
3563
+ }
3564
+
3565
+ interface EncryptedBlob {
3566
+ ciphertext: string;
3567
+ salt: string;
3568
+ iv: string;
3569
+ }
3570
+ declare function encryptJSON(data: unknown, passphrase: string): Promise<EncryptedBlob>;
3571
+ /** Throws (AES-GCM auth-tag check fails) on a wrong passphrase or tampered ciphertext. */
3572
+ declare function decryptJSON<T>(blob: EncryptedBlob, passphrase: string): Promise<T>;
3573
+
3574
+ declare const DEFAULT_CLOUD_URL = "https://app.cascadeai.in";
3575
+ /** Providers the loopback flow can start (must match the server's `/auth/native/:provider`). */
3576
+ type NativeProvider = 'google' | 'github';
3577
+ interface DeviceStart {
3578
+ deviceCode: string;
3579
+ userCode: string;
3580
+ verificationUri: string;
3581
+ expiresIn: number;
3582
+ interval: number;
3583
+ }
3584
+ interface CloudConversation {
3585
+ id: string;
3586
+ title: string;
3587
+ /** Branching: leaf of the currently-selected path (null for an empty chat). */
3588
+ activeLeafId?: string | null;
3589
+ skillId?: string | null;
3590
+ createdAt?: number;
3591
+ updatedAt?: number;
3592
+ }
3593
+ interface CloudMessage {
3594
+ /** Present on messages read from the server; absent on minimal echoes. */
3595
+ id?: string;
3596
+ /** Branching: the message this replies to (null = a root turn). */
3597
+ parentId?: string | null;
3598
+ role: string;
3599
+ content: string;
3600
+ tier?: string | null;
3601
+ model?: string | null;
3602
+ costUsd?: number | null;
3603
+ /** JSON-encoded run-explorer report, or null. */
3604
+ why?: string | null;
3605
+ /** Branching: ids of this message + its siblings, oldest first (for < n/m >). */
3606
+ siblingIds?: string[];
3607
+ }
3608
+ /** A locally-executed turn to persist into the cloud conversation tree. */
3609
+ interface CloudTurnInput {
3610
+ userContent: string;
3611
+ assistant: {
3612
+ content: string;
3613
+ tier?: string | null;
3614
+ model?: string | null;
3615
+ costUsd?: number | null;
3616
+ why?: string | null;
3617
+ };
3618
+ /** Fork a sibling of this edited user turn (server derives the parent). */
3619
+ editOfMessageId?: string;
3620
+ /** Re-answer this user turn (reply saved as a sibling; no new user message). */
3621
+ regenerateFromUserMessageId?: string;
3622
+ }
3623
+ type DevicePollResult = 'pending' | 'slow_down' | 'expired' | {
3624
+ session: CloudSession;
3625
+ };
3626
+ /**
3627
+ * Where the signed-in session persists. The CLI uses the default file store
3628
+ * (`~/.cascade-ai/cloud-session.json`, 0600); the desktop injects a
3629
+ * `safeStorage`-backed one. Kept tiny so any host can implement it.
3630
+ */
3631
+ interface CloudSessionStore {
3632
+ load(): CloudSession | null;
3633
+ save(session: CloudSession): void;
3634
+ clear(): void;
3635
+ }
3636
+ declare class CloudClient {
3637
+ private readonly serverUrl;
3638
+ private readonly store;
3639
+ constructor(serverUrl: string, dir?: string, store?: CloudSessionStore);
3640
+ /** Build a client from a stored session, or null if not signed in. */
3641
+ static fromSession(dir?: string, store?: CloudSessionStore): CloudClient | null;
3642
+ private url;
3643
+ startDevice(): Promise<DeviceStart>;
3644
+ /** Single poll of the device grant. On approval, persists + returns the session. */
3645
+ pollDevice(deviceCode: string): Promise<DevicePollResult>;
3646
+ /**
3647
+ * Run the full device flow: start it, hand the code to `onCode`, then poll at
3648
+ * the server's interval until approved or expired.
3649
+ */
3650
+ runDeviceLogin(onCode: (d: DeviceStart) => void, opts?: {
3651
+ signal?: AbortSignal;
3652
+ }): Promise<CloudSession>;
3653
+ /**
3654
+ * Run the loopback flow for a rich native client (the desktop). Opens a
3655
+ * one-shot listener on `http://127.0.0.1:<random>`, sends the user to the
3656
+ * provider in the system browser via `openUrl`, catches the one-time code on
3657
+ * the loopback redirect, and exchanges it (+ the PKCE verifier) for tokens.
3658
+ *
3659
+ * No secret is involved: PKCE S256 proves the client that started the flow is
3660
+ * the one redeeming the code. Storage-agnostic — persists through the same
3661
+ * session store as the device flow, so callers get a ready `CloudSession`.
3662
+ */
3663
+ runLoopbackLogin(openUrl: (url: string) => void | Promise<void>, opts?: {
3664
+ provider?: NativeProvider;
3665
+ signal?: AbortSignal;
3666
+ timeoutMs?: number;
3667
+ }): Promise<CloudSession>;
3668
+ /** Exchange a fresh token response for a stored session (fetches the user). */
3669
+ private finishLogin;
3670
+ private session;
3671
+ private refresh;
3672
+ private accessToken;
3673
+ /** Authenticated request that refreshes once on a 401 (e.g. server-side revoke). */
3674
+ private authed;
3675
+ private authedGet;
3676
+ private fetchMe;
3677
+ me(): Promise<CloudUser>;
3678
+ listConversations(): Promise<CloudConversation[]>;
3679
+ getMessages(conversationId: string): Promise<CloudMessage[]>;
3680
+ /** Start a new cloud conversation and return it. */
3681
+ createConversation(title?: string): Promise<CloudConversation>;
3682
+ /** Persist a locally-executed turn; returns the new active path (with siblings). */
3683
+ appendTurn(conversationId: string, turn: CloudTurnInput): Promise<CloudMessage[]>;
3684
+ /** Branching: switch the active path to a sibling; returns the new path. */
3685
+ selectBranch(conversationId: string, messageId: string): Promise<CloudMessage[]>;
3686
+ /** Branching: delete a message and its subtree; returns the new path. */
3687
+ deleteMessage(conversationId: string, messageId: string): Promise<CloudMessage[]>;
3688
+ renameConversation(conversationId: string, title: string): Promise<void>;
3689
+ deleteConversation(conversationId: string): Promise<void>;
3690
+ /** Fetch the stored encrypted settings envelope (ciphertext only), or null. */
3691
+ pullSecrets(): Promise<{
3692
+ blob: EncryptedBlob | null;
3693
+ version?: number;
3694
+ updatedAt?: number;
3695
+ }>;
3696
+ /** Store/replace the encrypted settings envelope. The server never decrypts it. */
3697
+ pushSecrets(blob: EncryptedBlob): Promise<{
3698
+ version: number;
3699
+ updatedAt: number;
3700
+ }>;
3701
+ /** Forget the stored envelope on the server. */
3702
+ deleteSecrets(): Promise<void>;
3703
+ logout(): Promise<void>;
3704
+ }
3705
+
3706
+ interface SyncBundle {
3707
+ v: 1;
3708
+ /** LLM provider keys / endpoints. */
3709
+ providers?: ProviderConfig[];
3710
+ /** Web-search backend keys (Brave / Tavily) + SearXNG URL. */
3711
+ webSearch?: WebSearchConfig;
3712
+ /** MCP servers / connectors (headers carry auth). */
3713
+ mcpServers?: McpServerConfig$1[];
3714
+ /** Per-tier model overrides (non-secret). */
3715
+ models?: ModelOverrides;
3716
+ /** Budget caps (non-secret). */
3717
+ budget?: BudgetConfig;
3718
+ /** Assorted non-secret preferences. */
3719
+ prefs?: {
3720
+ autoBias?: 'balanced' | 'quality' | 'cost';
3721
+ cascadeAuto?: boolean;
3722
+ extendedContext?: ExtendedContextConfig;
3723
+ autonomy?: 'manual' | 'auto';
3724
+ };
3725
+ }
3726
+ /** Pluck the portable subset of a config into a bundle (undefined fields omitted). */
3727
+ declare function gatherSyncBundle(config: CascadeConfig): SyncBundle;
3728
+ /** Merge a pulled bundle into a config, returning the updated config to persist. */
3729
+ declare function applySyncBundle(bundle: SyncBundle, config: CascadeConfig): CascadeConfig;
3730
+
3731
+ export { AZURE_BASE_URL_TEMPLATE, type ApprovalRequest, type ApprovalResponse, type AuditEntry, AuditLogger, type BenchmarksConfig, type BudgetConfig, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, CHARS_PER_TOKEN, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, type CascadeConfig, type CascadeEvent, type CascadeEventType, CascadeIgnore, type CascadeMessage, CascadeRouter, type CascadeRunOptions, type CascadeRunResult, type CascadeThemeName, CascadeToolError, type Chunk, type ChunkOptions, CloudClient, type CloudConversation, type CloudMessage, type CloudSession, type CloudSessionStore, type CloudTurnInput, type CloudUser, type CodeChunkOptions, type CodeChunker, CodeSearchTool, type CompleteFn, ConfigManager, type ConversationMessage, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CLOUD_URL, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, type DashboardConfig, DashboardServer, type DeviceStart, type DistilledFact, type Embedder, type EncryptedBlob, type EscalationPayload, type ExtendedContextConfig, type FileManifest, FileMcpOAuthStore, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, type GenerateOptions, type GenerateResult, type GraphFactSource, GraphRetriever, type GraphSearchOptions, GraphSearchTool, type HookDefinition, type HooksConfig, HooksRunner, type Identity, type ImageAttachment, Keystore, LLMReranker, LM_STUDIO_BASE_URL, type LegacyThemeName, MODELS, type ManifestDiff, McpClient, McpOAuthProvider, type McpOAuthState, type McpOAuthStore, type McpServerConfig$1 as McpServerConfig, type MemoryConfig, MemoryStore, type Message, type MessageContent, type MessagePayload, type MessageStatus, type MessageType, type ModelInfo, type ModelOverrides, type NativeProvider, OLLAMA_BASE_URL, OpenAICompatibleEmbedder, type OpenAIEmbedderOptions, PROVIDER_DISPLAY_NAMES, type PeerMessage, type PeerMessageEvent, type PeerSyncPayload, type PeerSyncType, type PermissionDecision, type PermissionDecisionPayload, type PermissionRequest, type PermissionTrailEntry, type PlanReviewConfig, type ProviderConfig, type ProviderType, type RankedFact, type RefreshResult, type Reranker, type RetrievalContext, type RetrievalMode, type RetrievalPlan, Retriever, type RetrieverSearchOptions, type RuntimeNode, type RuntimeNodeLog, type RuntimeRefreshPayload, type RuntimeScope, type RuntimeSession, type RuntimeSnapshotPayload, type ScheduledTask, type ScoredChunk, type SearchOptions, type Session, type SessionCheckpoint, type SessionMetadata, type SessionSubscriptionPayload, SqliteVectorStore, type StatusUpdate, type StoredMessage, type StreamChunk, type SyncBundle, T1Administrator, type T1ToT2Assignment, T1_MODEL_PRIORITY, T2Manager, type T2Result, type T2ToT3Assignment, T2_MODEL_PRIORITY, type T3Result, type T3ResultPayload, type T3SubtaskSpec, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, type TaskComplexity, TaskScheduler, Telemetry, type TelemetryConfig, type Theme, type ThemeColors, type ThemeName, type TierConfig, type TierLimits, type TierRole, type TierStatus, type TokenUsage, type ToolCall, type ToolDefinition, type ToolExecuteOptions, ToolRegistry, type ToolResult, type ToolsConfig, VISION_MODEL_PRIORITY, type VectorStore, type WebSearchConfig, type WebhookConfig, type WorkspaceConfig, WorkspaceIndex, type WorkspaceIndexOptions, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildManifest, buildSessionTranscript, cagCharBudget, chatCompleterFromProviders, chunkCode, chunkText, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, decryptJSON as decryptSyncBlob, diffManifest, discoverMcpAuthServer, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, fileOAuthProvider, gatherSyncBundle, hashContent, heuristicCodeChunker, inferAzureBaseModel, nodeHttpFetch, parseRankOrder, planRetrieval, preferIpv4Host, reciprocalRankFusion, refreshMcpToken, runCascade, sessionWorthRemembering, streamCascade };