cascade-ai 0.76.0 → 0.77.0

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/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- import dns from 'dns';
1
+ import dnsCallback from 'dns';
2
2
  import http, { createServer } from 'http';
3
3
  import https from 'https';
4
4
  import zlib from 'zlib';
@@ -7,8 +7,8 @@ import Anthropic from '@anthropic-ai/sdk';
7
7
  import OpenAI, { AzureOpenAI } from 'openai';
8
8
  import { HarmBlockThreshold, HarmCategory, GoogleGenAI } from '@google/genai';
9
9
  import Database2 from 'better-sqlite3';
10
- import path35, { dirname } from 'path';
11
- import fs32, { mkdirSync, writeFileSync, existsSync, readFileSync, promises } from 'fs';
10
+ import path36, { dirname } from 'path';
11
+ import fs33, { mkdirSync, writeFileSync, existsSync, readFileSync, promises } from 'fs';
12
12
  import os10 from 'os';
13
13
  import crypto, { randomUUID, randomBytes, webcrypto, timingSafeEqual, createHash } from 'crypto';
14
14
  import { render, Box, Text, useStdout, useApp, useInput, Static } from 'ink';
@@ -30,6 +30,7 @@ import { simpleGit } from 'simple-git';
30
30
  import PDFDocument from 'pdfkit';
31
31
  import dns2 from 'dns/promises';
32
32
  import net from 'net';
33
+ import { Agent } from 'undici';
33
34
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
34
35
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
35
36
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
@@ -1795,7 +1796,7 @@ var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SEC
1795
1796
  var init_constants = __esm({
1796
1797
  "src/constants.ts"() {
1797
1798
  init_pricing();
1798
- CASCADE_VERSION = "0.76.0";
1799
+ CASCADE_VERSION = "0.77.0";
1799
1800
  CASCADE_CONFIG_FILE = ".cascade/config.json";
1800
1801
  CASCADE_DB_FILE = ".cascade/memory.db";
1801
1802
  CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
@@ -1808,64 +1809,88 @@ var init_constants = __esm({
1808
1809
  DEFAULT_CONTEXT_LIMIT = 2e5;
1809
1810
  DEFAULT_AUTO_SUMMARIZE_AT = 15e4;
1810
1811
  MODELS = {
1811
- // Anthropic
1812
- "claude-opus-4": {
1813
- id: "claude-opus-4-5",
1814
- name: "Claude Opus 4",
1812
+ // Anthropic — active Claude API models (2026-08-27).
1813
+ "claude-fable-5": {
1814
+ id: "claude-fable-5",
1815
+ name: "Claude Fable 5",
1815
1816
  provider: "anthropic",
1816
- contextWindow: 2e5,
1817
+ contextWindow: 1e6,
1817
1818
  isVisionCapable: true,
1818
- inputCostPer1kTokens: 0.015,
1819
- outputCostPer1kTokens: 0.075,
1820
- maxOutputTokens: 32e3,
1819
+ inputCostPer1kTokens: 0.01,
1820
+ outputCostPer1kTokens: 0.05,
1821
+ maxOutputTokens: 128e3,
1821
1822
  supportsStreaming: true,
1822
1823
  isLocal: false
1823
1824
  },
1824
- "claude-sonnet-4": {
1825
- id: "claude-sonnet-4-5",
1826
- name: "Claude Sonnet 4",
1825
+ "claude-opus-5": {
1826
+ id: "claude-opus-5",
1827
+ name: "Claude Opus 5",
1827
1828
  provider: "anthropic",
1828
- contextWindow: 2e5,
1829
+ contextWindow: 1e6,
1829
1830
  isVisionCapable: true,
1830
- inputCostPer1kTokens: 3e-3,
1831
- outputCostPer1kTokens: 0.015,
1832
- maxOutputTokens: 16e3,
1831
+ inputCostPer1kTokens: 5e-3,
1832
+ outputCostPer1kTokens: 0.025,
1833
+ maxOutputTokens: 128e3,
1833
1834
  supportsStreaming: true,
1834
1835
  isLocal: false
1835
1836
  },
1836
- "claude-haiku-3-5": {
1837
- id: "claude-haiku-3-5-20251001",
1838
- name: "Claude Haiku 3.5",
1837
+ "claude-opus-4-8": {
1838
+ id: "claude-opus-4-8",
1839
+ name: "Claude Opus 4.8",
1839
1840
  provider: "anthropic",
1840
- contextWindow: 2e5,
1841
+ contextWindow: 1e6,
1841
1842
  isVisionCapable: true,
1842
- inputCostPer1kTokens: 8e-4,
1843
- outputCostPer1kTokens: 4e-3,
1844
- maxOutputTokens: 8e3,
1843
+ inputCostPer1kTokens: 5e-3,
1844
+ outputCostPer1kTokens: 0.025,
1845
+ maxOutputTokens: 128e3,
1845
1846
  supportsStreaming: true,
1846
1847
  isLocal: false
1847
1848
  },
1848
- "claude-haiku-4-5": {
1849
- id: "claude-haiku-4-5-20251001",
1850
- name: "Claude Haiku 4.5",
1849
+ "claude-opus-4-7": {
1850
+ id: "claude-opus-4-7",
1851
+ name: "Claude Opus 4.7",
1851
1852
  provider: "anthropic",
1852
- contextWindow: 2e5,
1853
+ contextWindow: 1e6,
1853
1854
  isVisionCapable: true,
1854
- inputCostPer1kTokens: 8e-4,
1855
- outputCostPer1kTokens: 4e-3,
1856
- maxOutputTokens: 8e3,
1855
+ inputCostPer1kTokens: 5e-3,
1856
+ outputCostPer1kTokens: 0.025,
1857
+ maxOutputTokens: 128e3,
1857
1858
  supportsStreaming: true,
1858
1859
  isLocal: false
1859
1860
  },
1860
- "claude-opus-4-8": {
1861
- id: "claude-opus-4-8",
1862
- name: "Claude Opus 4.8",
1861
+ "claude-opus-4-6": {
1862
+ id: "claude-opus-4-6",
1863
+ name: "Claude Opus 4.6",
1864
+ provider: "anthropic",
1865
+ contextWindow: 1e6,
1866
+ isVisionCapable: true,
1867
+ inputCostPer1kTokens: 5e-3,
1868
+ outputCostPer1kTokens: 0.025,
1869
+ maxOutputTokens: 128e3,
1870
+ supportsStreaming: true,
1871
+ isLocal: false
1872
+ },
1873
+ "claude-opus-4-5": {
1874
+ id: "claude-opus-4-5-20251101",
1875
+ name: "Claude Opus 4.5",
1863
1876
  provider: "anthropic",
1864
1877
  contextWindow: 2e5,
1865
1878
  isVisionCapable: true,
1866
- inputCostPer1kTokens: 0.015,
1867
- outputCostPer1kTokens: 0.075,
1868
- maxOutputTokens: 32e3,
1879
+ inputCostPer1kTokens: 5e-3,
1880
+ outputCostPer1kTokens: 0.025,
1881
+ maxOutputTokens: 64e3,
1882
+ supportsStreaming: true,
1883
+ isLocal: false
1884
+ },
1885
+ "claude-sonnet-5": {
1886
+ id: "claude-sonnet-5",
1887
+ name: "Claude Sonnet 5",
1888
+ provider: "anthropic",
1889
+ contextWindow: 1e6,
1890
+ isVisionCapable: true,
1891
+ inputCostPer1kTokens: 2e-3,
1892
+ outputCostPer1kTokens: 0.01,
1893
+ maxOutputTokens: 128e3,
1869
1894
  supportsStreaming: true,
1870
1895
  isLocal: false
1871
1896
  },
@@ -1873,76 +1898,135 @@ var init_constants = __esm({
1873
1898
  id: "claude-sonnet-4-6",
1874
1899
  name: "Claude Sonnet 4.6",
1875
1900
  provider: "anthropic",
1901
+ contextWindow: 1e6,
1902
+ isVisionCapable: true,
1903
+ inputCostPer1kTokens: 3e-3,
1904
+ outputCostPer1kTokens: 0.015,
1905
+ maxOutputTokens: 64e3,
1906
+ supportsStreaming: true,
1907
+ isLocal: false
1908
+ },
1909
+ "claude-sonnet-4-5": {
1910
+ id: "claude-sonnet-4-5-20250929",
1911
+ name: "Claude Sonnet 4.5",
1912
+ provider: "anthropic",
1876
1913
  contextWindow: 2e5,
1877
1914
  isVisionCapable: true,
1878
1915
  inputCostPer1kTokens: 3e-3,
1879
1916
  outputCostPer1kTokens: 0.015,
1880
- maxOutputTokens: 16e3,
1917
+ maxOutputTokens: 64e3,
1881
1918
  supportsStreaming: true,
1882
1919
  isLocal: false
1883
1920
  },
1884
- // OpenAI
1885
- "gpt-4o": {
1886
- id: "gpt-4o",
1887
- name: "GPT-4o",
1921
+ "claude-haiku-4-5": {
1922
+ id: "claude-haiku-4-5-20251001",
1923
+ name: "Claude Haiku 4.5",
1924
+ provider: "anthropic",
1925
+ contextWindow: 2e5,
1926
+ isVisionCapable: true,
1927
+ inputCostPer1kTokens: 1e-3,
1928
+ outputCostPer1kTokens: 5e-3,
1929
+ maxOutputTokens: 64e3,
1930
+ supportsStreaming: true,
1931
+ isLocal: false
1932
+ },
1933
+ // OpenAI — active chat-capable models used by Cascade's Chat Completions path.
1934
+ "gpt-5.6-sol": {
1935
+ id: "gpt-5.6-sol",
1936
+ name: "GPT-5.6 Sol",
1888
1937
  provider: "openai",
1889
- contextWindow: 128e3,
1938
+ contextWindow: 105e4,
1939
+ isVisionCapable: true,
1940
+ inputCostPer1kTokens: 4e-3,
1941
+ outputCostPer1kTokens: 0.02,
1942
+ maxOutputTokens: 128e3,
1943
+ supportsStreaming: true,
1944
+ isLocal: false
1945
+ },
1946
+ "gpt-5.6-terra": {
1947
+ id: "gpt-5.6-terra",
1948
+ name: "GPT-5.6 Terra",
1949
+ provider: "openai",
1950
+ contextWindow: 105e4,
1951
+ isVisionCapable: true,
1952
+ inputCostPer1kTokens: 2e-3,
1953
+ outputCostPer1kTokens: 0.012,
1954
+ maxOutputTokens: 128e3,
1955
+ supportsStreaming: true,
1956
+ isLocal: false
1957
+ },
1958
+ "gpt-5.6-luna": {
1959
+ id: "gpt-5.6-luna",
1960
+ name: "GPT-5.6 Luna",
1961
+ provider: "openai",
1962
+ contextWindow: 105e4,
1963
+ isVisionCapable: true,
1964
+ inputCostPer1kTokens: 2e-4,
1965
+ outputCostPer1kTokens: 12e-4,
1966
+ maxOutputTokens: 128e3,
1967
+ supportsStreaming: true,
1968
+ isLocal: false
1969
+ },
1970
+ "gpt-5.5": {
1971
+ id: "gpt-5.5",
1972
+ name: "GPT-5.5",
1973
+ provider: "openai",
1974
+ contextWindow: 105e4,
1890
1975
  isVisionCapable: true,
1891
1976
  inputCostPer1kTokens: 5e-3,
1892
- outputCostPer1kTokens: 0.015,
1893
- maxOutputTokens: 16e3,
1977
+ outputCostPer1kTokens: 0.03,
1978
+ maxOutputTokens: 128e3,
1894
1979
  supportsStreaming: true,
1895
1980
  isLocal: false
1896
1981
  },
1897
- "gpt-4o-mini": {
1898
- id: "gpt-4o-mini",
1899
- name: "GPT-4o Mini",
1982
+ "gpt-5.4": {
1983
+ id: "gpt-5.4",
1984
+ name: "GPT-5.4",
1900
1985
  provider: "openai",
1901
- contextWindow: 128e3,
1986
+ contextWindow: 105e4,
1902
1987
  isVisionCapable: true,
1903
- inputCostPer1kTokens: 15e-5,
1904
- outputCostPer1kTokens: 6e-4,
1905
- maxOutputTokens: 16e3,
1988
+ inputCostPer1kTokens: 25e-4,
1989
+ outputCostPer1kTokens: 0.015,
1990
+ maxOutputTokens: 128e3,
1906
1991
  supportsStreaming: true,
1907
1992
  isLocal: false
1908
1993
  },
1909
- "gpt-4.1": {
1910
- id: "gpt-4.1",
1911
- name: "GPT-4.1",
1994
+ "gpt-5.4-mini": {
1995
+ id: "gpt-5.4-mini",
1996
+ name: "GPT-5.4 Mini",
1912
1997
  provider: "openai",
1913
- contextWindow: 1047576,
1998
+ contextWindow: 4e5,
1914
1999
  isVisionCapable: true,
1915
- inputCostPer1kTokens: 2e-3,
1916
- outputCostPer1kTokens: 8e-3,
1917
- maxOutputTokens: 32768,
2000
+ inputCostPer1kTokens: 75e-5,
2001
+ outputCostPer1kTokens: 45e-4,
2002
+ maxOutputTokens: 128e3,
1918
2003
  supportsStreaming: true,
1919
2004
  isLocal: false
1920
2005
  },
1921
- "gpt-4.1-mini": {
1922
- id: "gpt-4.1-mini",
1923
- name: "GPT-4.1 Mini",
2006
+ "gpt-5.4-nano": {
2007
+ id: "gpt-5.4-nano",
2008
+ name: "GPT-5.4 Nano",
1924
2009
  provider: "openai",
1925
- contextWindow: 1047576,
2010
+ contextWindow: 4e5,
1926
2011
  isVisionCapable: true,
1927
- inputCostPer1kTokens: 4e-4,
1928
- outputCostPer1kTokens: 16e-4,
1929
- maxOutputTokens: 32768,
2012
+ inputCostPer1kTokens: 2e-4,
2013
+ outputCostPer1kTokens: 125e-5,
2014
+ maxOutputTokens: 128e3,
1930
2015
  supportsStreaming: true,
1931
2016
  isLocal: false
1932
2017
  },
1933
- "gpt-4.1-nano": {
1934
- id: "gpt-4.1-nano",
1935
- name: "GPT-4.1 Nano",
2018
+ "gpt-5.2": {
2019
+ id: "gpt-5.2",
2020
+ name: "GPT-5.2",
1936
2021
  provider: "openai",
1937
- contextWindow: 1047576,
2022
+ contextWindow: 4e5,
1938
2023
  isVisionCapable: true,
1939
- inputCostPer1kTokens: 1e-4,
1940
- outputCostPer1kTokens: 4e-4,
1941
- maxOutputTokens: 32768,
2024
+ inputCostPer1kTokens: 175e-5,
2025
+ outputCostPer1kTokens: 0.014,
2026
+ maxOutputTokens: 128e3,
1942
2027
  supportsStreaming: true,
1943
2028
  isLocal: false
1944
2029
  },
1945
- // GPT-5 family (offline baseline pricing; the live fetch keeps it current).
1946
2030
  "gpt-5": {
1947
2031
  id: "gpt-5",
1948
2032
  name: "GPT-5",
@@ -1979,79 +2063,149 @@ var init_constants = __esm({
1979
2063
  supportsStreaming: true,
1980
2064
  isLocal: false
1981
2065
  },
1982
- // GPT-5 point releases — genuinely distinct capability, so they route as their
1983
- // own families (not folded into gpt-5). Offline baseline pricing; live fetch
1984
- // keeps it current. gpt-5.5 "Spud" (Apr 2026) is the current SWE-bench leader.
1985
- "gpt-5.5": {
1986
- id: "gpt-5.5",
1987
- name: "GPT-5.5",
2066
+ "gpt-4.1": {
2067
+ id: "gpt-4.1",
2068
+ name: "GPT-4.1",
1988
2069
  provider: "openai",
1989
- contextWindow: 4e5,
2070
+ contextWindow: 1047576,
1990
2071
  isVisionCapable: true,
1991
- inputCostPer1kTokens: 25e-4,
1992
- outputCostPer1kTokens: 0.02,
1993
- maxOutputTokens: 128e3,
2072
+ inputCostPer1kTokens: 2e-3,
2073
+ outputCostPer1kTokens: 8e-3,
2074
+ maxOutputTokens: 32768,
1994
2075
  supportsStreaming: true,
1995
2076
  isLocal: false
1996
2077
  },
1997
- "gpt-5.4": {
1998
- id: "gpt-5.4",
1999
- name: "GPT-5.4",
2078
+ "gpt-4.1-mini": {
2079
+ id: "gpt-4.1-mini",
2080
+ name: "GPT-4.1 Mini",
2000
2081
  provider: "openai",
2001
- contextWindow: 4e5,
2082
+ contextWindow: 1047576,
2002
2083
  isVisionCapable: true,
2003
- inputCostPer1kTokens: 125e-5,
2084
+ inputCostPer1kTokens: 4e-4,
2085
+ outputCostPer1kTokens: 16e-4,
2086
+ maxOutputTokens: 32768,
2087
+ supportsStreaming: true,
2088
+ isLocal: false
2089
+ },
2090
+ "gpt-4.1-nano": {
2091
+ id: "gpt-4.1-nano",
2092
+ name: "GPT-4.1 Nano",
2093
+ provider: "openai",
2094
+ contextWindow: 1047576,
2095
+ isVisionCapable: true,
2096
+ inputCostPer1kTokens: 1e-4,
2097
+ outputCostPer1kTokens: 4e-4,
2098
+ maxOutputTokens: 32768,
2099
+ supportsStreaming: true,
2100
+ isLocal: false
2101
+ },
2102
+ "gpt-4o": {
2103
+ id: "gpt-4o",
2104
+ name: "GPT-4o",
2105
+ provider: "openai",
2106
+ contextWindow: 128e3,
2107
+ isVisionCapable: true,
2108
+ inputCostPer1kTokens: 25e-4,
2004
2109
  outputCostPer1kTokens: 0.01,
2005
- maxOutputTokens: 128e3,
2110
+ maxOutputTokens: 16e3,
2006
2111
  supportsStreaming: true,
2007
2112
  isLocal: false
2008
2113
  },
2009
- "gpt-5.4-mini": {
2010
- id: "gpt-5.4-mini",
2011
- name: "GPT-5.4 Mini",
2114
+ "gpt-4o-mini": {
2115
+ id: "gpt-4o-mini",
2116
+ name: "GPT-4o Mini",
2012
2117
  provider: "openai",
2013
- contextWindow: 4e5,
2118
+ contextWindow: 128e3,
2014
2119
  isVisionCapable: true,
2015
- inputCostPer1kTokens: 25e-5,
2016
- outputCostPer1kTokens: 2e-3,
2017
- maxOutputTokens: 128e3,
2120
+ inputCostPer1kTokens: 15e-5,
2121
+ outputCostPer1kTokens: 6e-4,
2122
+ maxOutputTokens: 16e3,
2018
2123
  supportsStreaming: true,
2019
2124
  isLocal: false
2020
2125
  },
2021
- // Google
2022
- "gemini-2.0-flash": {
2023
- id: "gemini-2.0-flash",
2024
- name: "Gemini 2.0 Flash",
2126
+ // Google Gemini — active GA + preview chat models. Gemini 2.0/1.5 are no
2127
+ // longer advertised because the Gemini API shut those generations down.
2128
+ "gemini-3.7-flash": {
2129
+ id: "gemini-3.7-flash",
2130
+ name: "Gemini 3.7 Flash",
2025
2131
  provider: "gemini",
2026
- contextWindow: 1e6,
2132
+ contextWindow: 1048576,
2027
2133
  isVisionCapable: true,
2028
- inputCostPer1kTokens: 1e-4,
2029
- outputCostPer1kTokens: 4e-4,
2030
- maxOutputTokens: 8e3,
2134
+ inputCostPer1kTokens: 75e-5,
2135
+ outputCostPer1kTokens: 375e-5,
2136
+ maxOutputTokens: 65536,
2031
2137
  supportsStreaming: true,
2032
2138
  isLocal: false
2033
2139
  },
2034
- "gemini-1.5-pro": {
2035
- id: "gemini-1.5-pro",
2036
- name: "Gemini 1.5 Pro",
2140
+ "gemini-3.6-flash": {
2141
+ id: "gemini-3.6-flash",
2142
+ name: "Gemini 3.6 Flash",
2037
2143
  provider: "gemini",
2038
- contextWindow: 1e6,
2144
+ contextWindow: 1048576,
2039
2145
  isVisionCapable: true,
2040
- inputCostPer1kTokens: 125e-5,
2041
- outputCostPer1kTokens: 5e-3,
2042
- maxOutputTokens: 8e3,
2146
+ inputCostPer1kTokens: 75e-5,
2147
+ outputCostPer1kTokens: 375e-5,
2148
+ maxOutputTokens: 65536,
2043
2149
  supportsStreaming: true,
2044
2150
  isLocal: false
2045
2151
  },
2046
- "gemini-2.0-flash-lite": {
2047
- id: "gemini-2.0-flash-lite",
2048
- name: "Gemini 2.0 Flash-Lite",
2152
+ "gemini-3.5-flash": {
2153
+ id: "gemini-3.5-flash",
2154
+ name: "Gemini 3.5 Flash",
2049
2155
  provider: "gemini",
2050
- contextWindow: 1e6,
2156
+ contextWindow: 1048576,
2051
2157
  isVisionCapable: true,
2052
- inputCostPer1kTokens: 5e-5,
2053
- outputCostPer1kTokens: 2e-4,
2054
- maxOutputTokens: 8e3,
2158
+ inputCostPer1kTokens: 15e-4,
2159
+ outputCostPer1kTokens: 9e-3,
2160
+ maxOutputTokens: 65536,
2161
+ supportsStreaming: true,
2162
+ isLocal: false
2163
+ },
2164
+ "gemini-3.5-flash-lite": {
2165
+ id: "gemini-3.5-flash-lite",
2166
+ name: "Gemini 3.5 Flash-Lite",
2167
+ provider: "gemini",
2168
+ contextWindow: 1048576,
2169
+ isVisionCapable: true,
2170
+ inputCostPer1kTokens: 3e-4,
2171
+ outputCostPer1kTokens: 25e-4,
2172
+ maxOutputTokens: 65536,
2173
+ supportsStreaming: true,
2174
+ isLocal: false
2175
+ },
2176
+ "gemini-3.1-flash-lite": {
2177
+ id: "gemini-3.1-flash-lite",
2178
+ name: "Gemini 3.1 Flash-Lite",
2179
+ provider: "gemini",
2180
+ contextWindow: 1048576,
2181
+ isVisionCapable: true,
2182
+ inputCostPer1kTokens: 25e-5,
2183
+ outputCostPer1kTokens: 15e-4,
2184
+ maxOutputTokens: 65536,
2185
+ supportsStreaming: true,
2186
+ isLocal: false
2187
+ },
2188
+ "gemini-3.1-pro-preview": {
2189
+ id: "gemini-3.1-pro-preview",
2190
+ name: "Gemini 3.1 Pro Preview",
2191
+ provider: "gemini",
2192
+ contextWindow: 1048576,
2193
+ isVisionCapable: true,
2194
+ inputCostPer1kTokens: 2e-3,
2195
+ outputCostPer1kTokens: 0.012,
2196
+ maxOutputTokens: 65536,
2197
+ supportsStreaming: true,
2198
+ isLocal: false
2199
+ },
2200
+ "gemini-3-flash-preview": {
2201
+ id: "gemini-3-flash-preview",
2202
+ name: "Gemini 3 Flash Preview",
2203
+ provider: "gemini",
2204
+ contextWindow: 1048576,
2205
+ isVisionCapable: true,
2206
+ inputCostPer1kTokens: 5e-4,
2207
+ outputCostPer1kTokens: 3e-3,
2208
+ maxOutputTokens: 65536,
2055
2209
  supportsStreaming: true,
2056
2210
  isLocal: false
2057
2211
  },
@@ -2059,7 +2213,7 @@ var init_constants = __esm({
2059
2213
  id: "gemini-2.5-pro",
2060
2214
  name: "Gemini 2.5 Pro",
2061
2215
  provider: "gemini",
2062
- contextWindow: 1e6,
2216
+ contextWindow: 1048576,
2063
2217
  isVisionCapable: true,
2064
2218
  inputCostPer1kTokens: 125e-5,
2065
2219
  outputCostPer1kTokens: 0.01,
@@ -2071,10 +2225,22 @@ var init_constants = __esm({
2071
2225
  id: "gemini-2.5-flash",
2072
2226
  name: "Gemini 2.5 Flash",
2073
2227
  provider: "gemini",
2074
- contextWindow: 1e6,
2228
+ contextWindow: 1048576,
2075
2229
  isVisionCapable: true,
2076
- inputCostPer1kTokens: 15e-5,
2077
- outputCostPer1kTokens: 6e-4,
2230
+ inputCostPer1kTokens: 3e-4,
2231
+ outputCostPer1kTokens: 25e-4,
2232
+ maxOutputTokens: 65536,
2233
+ supportsStreaming: true,
2234
+ isLocal: false
2235
+ },
2236
+ "gemini-2.5-flash-lite": {
2237
+ id: "gemini-2.5-flash-lite",
2238
+ name: "Gemini 2.5 Flash-Lite",
2239
+ provider: "gemini",
2240
+ contextWindow: 1048576,
2241
+ isVisionCapable: true,
2242
+ inputCostPer1kTokens: 1e-4,
2243
+ outputCostPer1kTokens: 4e-4,
2078
2244
  maxOutputTokens: 65536,
2079
2245
  supportsStreaming: true,
2080
2246
  isLocal: false
@@ -2144,43 +2310,57 @@ var init_constants = __esm({
2144
2310
  _m.pricingUnknown = false;
2145
2311
  }
2146
2312
  T1_MODEL_PRIORITY = [
2313
+ "gpt-5.6-sol",
2314
+ "claude-fable-5",
2315
+ "claude-opus-5",
2147
2316
  "gpt-5.5",
2148
2317
  "gpt-5.4",
2318
+ "gemini-3.1-pro-preview",
2319
+ "claude-opus-4-8",
2320
+ "claude-sonnet-5",
2321
+ "gemini-3.7-flash",
2322
+ "gpt-5.2",
2149
2323
  "gpt-5",
2150
- "claude-opus-4",
2151
- "claude-sonnet-4",
2152
2324
  "gemini-2.5-pro",
2153
2325
  "gpt-4.1",
2154
- "gpt-4o",
2155
- "gemini-1.5-pro"
2326
+ "gpt-4o"
2156
2327
  ];
2157
2328
  T2_MODEL_PRIORITY = [
2329
+ "gpt-5.6-terra",
2330
+ "claude-sonnet-5",
2331
+ "gemini-3.7-flash",
2332
+ "gemini-3.6-flash",
2158
2333
  "gpt-5.4-mini",
2159
2334
  "gpt-5-mini",
2160
- "claude-sonnet-4",
2335
+ "claude-sonnet-4-6",
2161
2336
  "claude-haiku-4-5",
2162
- "claude-haiku-3-5",
2337
+ "gemini-3.5-flash",
2163
2338
  "gemini-2.5-flash",
2164
2339
  "gpt-4.1-mini",
2165
2340
  "gpt-4o-mini",
2166
- "gemini-2.0-flash",
2167
2341
  "llama3:70b"
2168
2342
  ];
2169
2343
  T3_MODEL_PRIORITY = [
2344
+ "gpt-5.6-luna",
2345
+ "gpt-5.4-nano",
2346
+ "gemini-3.5-flash-lite",
2347
+ "gemini-3.1-flash-lite",
2348
+ "claude-haiku-4-5",
2170
2349
  "gpt-5-nano",
2350
+ "gemini-2.5-flash-lite",
2171
2351
  "llama3.2:3b",
2172
2352
  "mistral:7b",
2173
- "claude-haiku-4-5",
2174
- "claude-haiku-3-5",
2175
2353
  "gpt-4.1-nano",
2176
2354
  "gpt-4o-mini",
2177
- "gemini-2.5-flash",
2178
- "gemini-2.0-flash"
2355
+ "gemini-2.5-flash"
2179
2356
  ];
2180
2357
  VISION_MODEL_PRIORITY = [
2181
- "claude-sonnet-4",
2358
+ "gpt-5.6-sol",
2359
+ "claude-fable-5",
2360
+ "claude-sonnet-5",
2361
+ "gemini-3.7-flash",
2182
2362
  "gpt-4o",
2183
- "gemini-2.0-flash",
2363
+ "gemini-2.5-flash",
2184
2364
  "llava"
2185
2365
  ];
2186
2366
  COMPLEXITY_T2_COUNT = {
@@ -2237,8 +2417,8 @@ function normalizeEndpoint(url) {
2237
2417
  if (!raw) return "";
2238
2418
  try {
2239
2419
  const u = new URL(raw);
2240
- const path40 = stripTrailingSlashes(`${u.pathname}${u.search}`);
2241
- return `${u.protocol}//${u.host.toLowerCase()}${path40}`;
2420
+ const path41 = stripTrailingSlashes(`${u.pathname}${u.search}`);
2421
+ return `${u.protocol}//${u.host.toLowerCase()}${path41}`;
2242
2422
  } catch {
2243
2423
  return raw.toLowerCase();
2244
2424
  }
@@ -2370,7 +2550,7 @@ var CLIENT_VERSION_SEGMENT, REDIRECT_STATUSES, NULL_BODY_STATUSES, MAX_REDIRECTS
2370
2550
  var init_net = __esm({
2371
2551
  "src/utils/net.ts"() {
2372
2552
  try {
2373
- dns.setDefaultResultOrder("ipv4first");
2553
+ dnsCallback.setDefaultResultOrder("ipv4first");
2374
2554
  } catch {
2375
2555
  }
2376
2556
  CLIENT_VERSION_SEGMENT = {
@@ -3102,6 +3282,56 @@ var init_openai = __esm({
3102
3282
  }
3103
3283
  });
3104
3284
 
3285
+ // src/providers/cloud-model-catalog.json
3286
+ var cloud_model_catalog_default;
3287
+ var init_cloud_model_catalog = __esm({
3288
+ "src/providers/cloud-model-catalog.json"() {
3289
+ cloud_model_catalog_default = {
3290
+ schemaVersion: 1,
3291
+ verifiedAt: "2026-08-27",
3292
+ scope: "GA and Preview text/chat models that Cascade can route through its maintained cloud providers. Retired, shutdown, realtime/audio-only, embedding, image-only, video-only, and access-restricted models are excluded.",
3293
+ note: "Capabilities belong to the canonical model. Pricing deliberately does not live here: Cascade resolves price by model x provider x region in core/router/pricing-data.json. A model exposed by multiple providers may therefore have different prices without duplicating its capability profile.",
3294
+ models: [
3295
+ { id: "gpt-5.6-sol", name: "GPT-5.6 Sol", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 105e4, maxOutputTokens: 128e3, vision: true, toolUse: true },
3296
+ { id: "gpt-5.6-terra", name: "GPT-5.6 Terra", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 105e4, maxOutputTokens: 128e3, vision: true, toolUse: true },
3297
+ { id: "gpt-5.6-luna", name: "GPT-5.6 Luna", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 105e4, maxOutputTokens: 128e3, vision: true, toolUse: true },
3298
+ { id: "gpt-5.5", name: "GPT-5.5", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 105e4, maxOutputTokens: 128e3, vision: true, toolUse: true },
3299
+ { id: "gpt-5.4", name: "GPT-5.4", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 105e4, maxOutputTokens: 128e3, vision: true, toolUse: true },
3300
+ { id: "gpt-5.4-mini", name: "GPT-5.4 Mini", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 4e5, maxOutputTokens: 128e3, vision: true, toolUse: true },
3301
+ { id: "gpt-5.4-nano", name: "GPT-5.4 Nano", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 4e5, maxOutputTokens: 128e3, vision: true, toolUse: true },
3302
+ { id: "gpt-5", name: "GPT-5", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 4e5, maxOutputTokens: 128e3, vision: true, toolUse: true },
3303
+ { id: "gpt-5-mini", name: "GPT-5 Mini", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 4e5, maxOutputTokens: 128e3, vision: true, toolUse: true },
3304
+ { id: "gpt-5-nano", name: "GPT-5 Nano", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 4e5, maxOutputTokens: 128e3, vision: true, toolUse: true },
3305
+ { id: "gpt-4.1", name: "GPT-4.1", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 1047576, maxOutputTokens: 32768, vision: true, toolUse: true },
3306
+ { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 1047576, maxOutputTokens: 32768, vision: true, toolUse: true },
3307
+ { id: "gpt-4.1-nano", name: "GPT-4.1 Nano", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 1047576, maxOutputTokens: 32768, vision: true, toolUse: true },
3308
+ { id: "gpt-4o", name: "GPT-4o", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 128e3, maxOutputTokens: 16384, vision: true, toolUse: true },
3309
+ { id: "gpt-4o-mini", name: "GPT-4o Mini", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 128e3, maxOutputTokens: 16384, vision: true, toolUse: true },
3310
+ { id: "claude-fable-5", name: "Claude Fable 5", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, maxOutputTokens: 128e3, vision: true, toolUse: true },
3311
+ { id: "claude-opus-5", name: "Claude Opus 5", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, maxOutputTokens: 128e3, vision: true, toolUse: true },
3312
+ { id: "claude-opus-4-8", name: "Claude Opus 4.8", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, maxOutputTokens: 128e3, vision: true, toolUse: true },
3313
+ { id: "claude-opus-4-7", name: "Claude Opus 4.7", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, maxOutputTokens: 128e3, vision: true, toolUse: true },
3314
+ { id: "claude-opus-4-6", name: "Claude Opus 4.6", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, vision: true, toolUse: true },
3315
+ { id: "claude-opus-4-5-20251101", name: "Claude Opus 4.5", providers: ["anthropic"], lifecycle: "ga", contextWindow: 2e5, vision: true, toolUse: true },
3316
+ { id: "claude-sonnet-5", name: "Claude Sonnet 5", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, maxOutputTokens: 128e3, vision: true, toolUse: true },
3317
+ { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, maxOutputTokens: 128e3, vision: true, toolUse: true },
3318
+ { id: "claude-sonnet-4-5-20250929", name: "Claude Sonnet 4.5", providers: ["anthropic"], lifecycle: "ga", contextWindow: 2e5, vision: true, toolUse: true },
3319
+ { id: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5", providers: ["anthropic"], lifecycle: "ga", contextWindow: 2e5, maxOutputTokens: 64e3, vision: true, toolUse: true },
3320
+ { id: "gemini-3.7-flash", name: "Gemini 3.7 Flash", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
3321
+ { id: "gemini-3.6-flash", name: "Gemini 3.6 Flash", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
3322
+ { id: "gemini-3.5-flash", name: "Gemini 3.5 Flash", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
3323
+ { id: "gemini-3.5-flash-lite", name: "Gemini 3.5 Flash-Lite", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
3324
+ { id: "gemini-3.1-flash-lite", name: "Gemini 3.1 Flash-Lite", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
3325
+ { id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro", providers: ["gemini"], lifecycle: "preview", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
3326
+ { id: "gemini-3-flash-preview", name: "Gemini 3 Flash", providers: ["gemini"], lifecycle: "preview", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
3327
+ { id: "gemini-2.5-pro", name: "Gemini 2.5 Pro", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
3328
+ { id: "gemini-2.5-flash", name: "Gemini 2.5 Flash", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
3329
+ { id: "gemini-2.5-flash-lite", name: "Gemini 2.5 Flash-Lite", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true }
3330
+ ]
3331
+ };
3332
+ }
3333
+ });
3334
+
3105
3335
  // src/providers/azure.ts
3106
3336
  var azure_exports = {};
3107
3337
  __export(azure_exports, {
@@ -3110,36 +3340,81 @@ __export(azure_exports, {
3110
3340
  azureModelForDeployment: () => azureModelForDeployment,
3111
3341
  inferAzureBaseModel: () => inferAzureBaseModel
3112
3342
  });
3343
+ function compactModelId(id) {
3344
+ return id.toLowerCase().replace(/[^a-z0-9.]/g, "");
3345
+ }
3346
+ function catalogEntry(id) {
3347
+ const n = id.toLowerCase();
3348
+ return CLOUD_MODELS.find((m) => m.id.toLowerCase() === n);
3349
+ }
3113
3350
  function inferAzureBaseModel(deploymentName) {
3114
3351
  const n = deploymentName.toLowerCase();
3115
- for (const [re, base] of AZURE_BASE_MODEL_RULES) if (re.test(n)) return base;
3352
+ const compact = compactModelId(n);
3353
+ const hasBareGpt56Alias = compact.includes("gpt5.6");
3354
+ for (const model of AZURE_CATALOG) {
3355
+ if (hasBareGpt56Alias && model.id === "gpt-5") continue;
3356
+ if (n.includes(model.id.toLowerCase()) || compact.includes(compactModelId(model.id))) {
3357
+ return model.id;
3358
+ }
3359
+ }
3360
+ if (hasBareGpt56Alias) return "gpt-5.6-sol";
3361
+ const gpt5At = compact.indexOf("gpt5");
3362
+ if (gpt5At !== -1) {
3363
+ const suffix = compact.slice(gpt5At + "gpt5".length);
3364
+ if (suffix.includes("nano")) return "gpt-5-nano";
3365
+ if (suffix.includes("mini")) return "gpt-5-mini";
3366
+ return "gpt-5";
3367
+ }
3368
+ if (compact.includes("gpt4.1nano")) return "gpt-4.1-nano";
3369
+ if (compact.includes("gpt4.1mini")) return "gpt-4.1-mini";
3370
+ if (compact.includes("gpt4.1")) return "gpt-4.1";
3371
+ if (compact.includes("gpt4omini")) return "gpt-4o-mini";
3372
+ if (compact.includes("gpt4o")) return "gpt-4o";
3116
3373
  return null;
3117
3374
  }
3375
+ function catalogModelInfo(baseModelId) {
3376
+ const entry = catalogEntry(baseModelId);
3377
+ if (!entry?.contextWindow) return void 0;
3378
+ return {
3379
+ id: entry.id,
3380
+ name: entry.name,
3381
+ provider: "openai",
3382
+ contextWindow: entry.contextWindow,
3383
+ isVisionCapable: entry.vision ?? false,
3384
+ inputCostPer1kTokens: 0,
3385
+ outputCostPer1kTokens: 0,
3386
+ maxOutputTokens: entry.maxOutputTokens ?? 16e3,
3387
+ supportsStreaming: true,
3388
+ isLocal: false,
3389
+ supportsToolUse: entry.toolUse ?? true,
3390
+ pricingUnknown: true
3391
+ };
3392
+ }
3118
3393
  function azureModelForDeployment(cfg) {
3119
3394
  if (cfg.type !== "azure" || !cfg.deploymentName?.trim()) return null;
3120
3395
  const id = cfg.deploymentName.trim();
3121
3396
  const name = cfg.label?.trim() || id;
3122
3397
  const baseModelId = cfg.model?.trim() || inferAzureBaseModel(id) || void 0;
3123
- const base = baseModelId ? MODELS[baseModelId] : void 0;
3124
- if (base) {
3398
+ const base = baseModelId ? MODELS[baseModelId] ?? catalogModelInfo(baseModelId) : void 0;
3399
+ if (base && baseModelId) {
3125
3400
  const azurePrice = resolvePricing(
3126
3401
  { id, provider: "azure", isLocal: false, baseModelId },
3127
3402
  { region: cfg.region }
3128
3403
  );
3404
+ const exactAzurePrice = !azurePrice.unknown && !azurePrice.estimatedFromProvider;
3405
+ const nativeToolsOnThisTransport = /^gpt-5\.6-(?:sol|terra|luna)$/i.test(baseModelId) ? false : base.supportsToolUse ?? true;
3129
3406
  return {
3130
3407
  ...base,
3131
3408
  id,
3132
- // callable deployment name
3133
3409
  name,
3134
3410
  provider: "azure",
3135
3411
  baseModelId,
3136
- // real identity for benchmark + live pricing
3137
- supportsToolUse: base.supportsToolUse ?? true,
3138
- ...azurePrice.unknown ? {} : {
3412
+ supportsToolUse: nativeToolsOnThisTransport,
3413
+ ...!azurePrice.unknown ? {
3139
3414
  inputCostPer1kTokens: azurePrice.input,
3140
3415
  outputCostPer1kTokens: azurePrice.output,
3141
- pricingUnknown: false
3142
- }
3416
+ pricingUnknown: !exactAzurePrice
3417
+ } : { pricingUnknown: true }
3143
3418
  };
3144
3419
  }
3145
3420
  return {
@@ -3154,39 +3429,34 @@ function azureModelForDeployment(cfg) {
3154
3429
  maxOutputTokens: 16e3,
3155
3430
  supportsStreaming: true,
3156
3431
  isLocal: false,
3157
- supportsToolUse: true
3432
+ supportsToolUse: true,
3433
+ pricingUnknown: true
3158
3434
  };
3159
3435
  }
3160
- var DEFAULT_AZURE_API_VERSION, AZURE_BASE_MODEL_RULES, AZURE_BASE_MODELS, AzureOpenAIProvider;
3436
+ function stripTrailingSlashes3(value) {
3437
+ let end = value.length;
3438
+ while (end > 0 && value.charCodeAt(end - 1) === 47) end--;
3439
+ return value.slice(0, end);
3440
+ }
3441
+ var DEFAULT_AZURE_API_VERSION, CLOUD_MODELS, AZURE_CATALOG, AZURE_BASE_MODELS, AzureOpenAIProvider;
3161
3442
  var init_azure = __esm({
3162
3443
  "src/providers/azure.ts"() {
3163
3444
  init_constants();
3164
3445
  init_openai();
3165
3446
  init_pricing();
3447
+ init_cloud_model_catalog();
3166
3448
  DEFAULT_AZURE_API_VERSION = "2024-12-01-preview";
3167
- AZURE_BASE_MODEL_RULES = [
3168
- [/gpt-?5\.5/, "gpt-5.5"],
3169
- [/gpt-?5\.4.*mini/, "gpt-5.4-mini"],
3170
- [/gpt-?5\.4/, "gpt-5.4"],
3171
- [/gpt-?5.*nano/, "gpt-5-nano"],
3172
- [/gpt-?5.*mini/, "gpt-5-mini"],
3173
- [/gpt-?5/, "gpt-5"],
3174
- [/gpt-?4\.1-nano/, "gpt-4.1-nano"],
3175
- [/gpt-?4\.1-mini/, "gpt-4.1-mini"],
3176
- [/gpt-?4\.1/, "gpt-4.1"],
3177
- [/gpt-?4o-mini/, "gpt-4o-mini"],
3178
- [/gpt-?4o/, "gpt-4o"]
3179
- ];
3180
- AZURE_BASE_MODELS = AZURE_BASE_MODEL_RULES.map(([, id]) => id);
3449
+ CLOUD_MODELS = cloud_model_catalog_default.models;
3450
+ AZURE_CATALOG = CLOUD_MODELS.filter((m) => m.providers.includes("azure")).slice().sort((a, b) => b.id.length - a.id.length || a.id.localeCompare(b.id));
3451
+ AZURE_BASE_MODELS = AZURE_CATALOG.map((m) => m.id);
3181
3452
  AzureOpenAIProvider = class extends OpenAIProvider {
3182
3453
  constructor(config, model) {
3183
3454
  const rawUrl = config.baseUrl ?? AZURE_BASE_URL_TEMPLATE.replace("{resource}", "YOUR_RESOURCE");
3184
- const endpoint = rawUrl.replace(/\/+$/, "");
3455
+ const endpoint = stripTrailingSlashes3(rawUrl);
3185
3456
  super(
3186
3457
  {
3187
3458
  ...config,
3188
3459
  baseUrl: endpoint
3189
- // Kept for superclass compatibility if it reads it
3190
3460
  },
3191
3461
  model
3192
3462
  );
@@ -4170,16 +4440,16 @@ var require_isarray = __commonJS({
4170
4440
  }
4171
4441
  });
4172
4442
 
4173
- // node_modules/jszip/node_modules/readable-stream/lib/internal/streams/stream.js
4443
+ // node_modules/readable-stream/lib/internal/streams/stream.js
4174
4444
  var require_stream = __commonJS({
4175
- "node_modules/jszip/node_modules/readable-stream/lib/internal/streams/stream.js"(exports, module) {
4445
+ "node_modules/readable-stream/lib/internal/streams/stream.js"(exports, module) {
4176
4446
  module.exports = __require("stream");
4177
4447
  }
4178
4448
  });
4179
4449
 
4180
- // node_modules/jszip/node_modules/safe-buffer/index.js
4450
+ // node_modules/readable-stream/node_modules/safe-buffer/index.js
4181
4451
  var require_safe_buffer = __commonJS({
4182
- "node_modules/jszip/node_modules/safe-buffer/index.js"(exports, module) {
4452
+ "node_modules/readable-stream/node_modules/safe-buffer/index.js"(exports, module) {
4183
4453
  var buffer = __require("buffer");
4184
4454
  var Buffer2 = buffer.Buffer;
4185
4455
  function copyProps(src, dst) {
@@ -4297,7 +4567,7 @@ var require_util = __commonJS({
4297
4567
  typeof arg === "undefined";
4298
4568
  }
4299
4569
  exports.isPrimitive = isPrimitive;
4300
- exports.isBuffer = Buffer.isBuffer;
4570
+ exports.isBuffer = __require("buffer").Buffer.isBuffer;
4301
4571
  function objectToString(o) {
4302
4572
  return Object.prototype.toString.call(o);
4303
4573
  }
@@ -4350,9 +4620,9 @@ var require_inherits = __commonJS({
4350
4620
  }
4351
4621
  });
4352
4622
 
4353
- // node_modules/jszip/node_modules/readable-stream/lib/internal/streams/BufferList.js
4623
+ // node_modules/readable-stream/lib/internal/streams/BufferList.js
4354
4624
  var require_BufferList = __commonJS({
4355
- "node_modules/jszip/node_modules/readable-stream/lib/internal/streams/BufferList.js"(exports, module) {
4625
+ "node_modules/readable-stream/lib/internal/streams/BufferList.js"(exports, module) {
4356
4626
  function _classCallCheck(instance, Constructor) {
4357
4627
  if (!(instance instanceof Constructor)) {
4358
4628
  throw new TypeError("Cannot call a class as a function");
@@ -4427,9 +4697,9 @@ var require_BufferList = __commonJS({
4427
4697
  }
4428
4698
  });
4429
4699
 
4430
- // node_modules/jszip/node_modules/readable-stream/lib/internal/streams/destroy.js
4700
+ // node_modules/readable-stream/lib/internal/streams/destroy.js
4431
4701
  var require_destroy = __commonJS({
4432
- "node_modules/jszip/node_modules/readable-stream/lib/internal/streams/destroy.js"(exports, module) {
4702
+ "node_modules/readable-stream/lib/internal/streams/destroy.js"(exports, module) {
4433
4703
  var pna = require_process_nextick_args();
4434
4704
  function destroy(err, cb) {
4435
4705
  var _this = this;
@@ -4502,9 +4772,9 @@ var require_node = __commonJS({
4502
4772
  }
4503
4773
  });
4504
4774
 
4505
- // node_modules/jszip/node_modules/readable-stream/lib/_stream_writable.js
4775
+ // node_modules/readable-stream/lib/_stream_writable.js
4506
4776
  var require_stream_writable = __commonJS({
4507
- "node_modules/jszip/node_modules/readable-stream/lib/_stream_writable.js"(exports, module) {
4777
+ "node_modules/readable-stream/lib/_stream_writable.js"(exports, module) {
4508
4778
  var pna = require_process_nextick_args();
4509
4779
  module.exports = Writable;
4510
4780
  function CorkedRequest(state) {
@@ -4941,9 +5211,9 @@ var require_stream_writable = __commonJS({
4941
5211
  }
4942
5212
  });
4943
5213
 
4944
- // node_modules/jszip/node_modules/readable-stream/lib/_stream_duplex.js
5214
+ // node_modules/readable-stream/lib/_stream_duplex.js
4945
5215
  var require_stream_duplex = __commonJS({
4946
- "node_modules/jszip/node_modules/readable-stream/lib/_stream_duplex.js"(exports, module) {
5216
+ "node_modules/readable-stream/lib/_stream_duplex.js"(exports, module) {
4947
5217
  var pna = require_process_nextick_args();
4948
5218
  var objectKeys = Object.keys || function(obj) {
4949
5219
  var keys2 = [];
@@ -5017,10 +5287,67 @@ var require_stream_duplex = __commonJS({
5017
5287
  }
5018
5288
  });
5019
5289
 
5020
- // node_modules/jszip/node_modules/string_decoder/lib/string_decoder.js
5290
+ // node_modules/string_decoder/node_modules/safe-buffer/index.js
5291
+ var require_safe_buffer2 = __commonJS({
5292
+ "node_modules/string_decoder/node_modules/safe-buffer/index.js"(exports, module) {
5293
+ var buffer = __require("buffer");
5294
+ var Buffer2 = buffer.Buffer;
5295
+ function copyProps(src, dst) {
5296
+ for (var key in src) {
5297
+ dst[key] = src[key];
5298
+ }
5299
+ }
5300
+ if (Buffer2.from && Buffer2.alloc && Buffer2.allocUnsafe && Buffer2.allocUnsafeSlow) {
5301
+ module.exports = buffer;
5302
+ } else {
5303
+ copyProps(buffer, exports);
5304
+ exports.Buffer = SafeBuffer;
5305
+ }
5306
+ function SafeBuffer(arg, encodingOrOffset, length) {
5307
+ return Buffer2(arg, encodingOrOffset, length);
5308
+ }
5309
+ copyProps(Buffer2, SafeBuffer);
5310
+ SafeBuffer.from = function(arg, encodingOrOffset, length) {
5311
+ if (typeof arg === "number") {
5312
+ throw new TypeError("Argument must not be a number");
5313
+ }
5314
+ return Buffer2(arg, encodingOrOffset, length);
5315
+ };
5316
+ SafeBuffer.alloc = function(size, fill, encoding) {
5317
+ if (typeof size !== "number") {
5318
+ throw new TypeError("Argument must be a number");
5319
+ }
5320
+ var buf = Buffer2(size);
5321
+ if (fill !== void 0) {
5322
+ if (typeof encoding === "string") {
5323
+ buf.fill(fill, encoding);
5324
+ } else {
5325
+ buf.fill(fill);
5326
+ }
5327
+ } else {
5328
+ buf.fill(0);
5329
+ }
5330
+ return buf;
5331
+ };
5332
+ SafeBuffer.allocUnsafe = function(size) {
5333
+ if (typeof size !== "number") {
5334
+ throw new TypeError("Argument must be a number");
5335
+ }
5336
+ return Buffer2(size);
5337
+ };
5338
+ SafeBuffer.allocUnsafeSlow = function(size) {
5339
+ if (typeof size !== "number") {
5340
+ throw new TypeError("Argument must be a number");
5341
+ }
5342
+ return buffer.SlowBuffer(size);
5343
+ };
5344
+ }
5345
+ });
5346
+
5347
+ // node_modules/string_decoder/lib/string_decoder.js
5021
5348
  var require_string_decoder = __commonJS({
5022
- "node_modules/jszip/node_modules/string_decoder/lib/string_decoder.js"(exports) {
5023
- var Buffer2 = require_safe_buffer().Buffer;
5349
+ "node_modules/string_decoder/lib/string_decoder.js"(exports) {
5350
+ var Buffer2 = require_safe_buffer2().Buffer;
5024
5351
  var isEncoding = Buffer2.isEncoding || function(encoding) {
5025
5352
  encoding = "" + encoding;
5026
5353
  switch (encoding && encoding.toLowerCase()) {
@@ -5254,9 +5581,9 @@ var require_string_decoder = __commonJS({
5254
5581
  }
5255
5582
  });
5256
5583
 
5257
- // node_modules/jszip/node_modules/readable-stream/lib/_stream_readable.js
5584
+ // node_modules/readable-stream/lib/_stream_readable.js
5258
5585
  var require_stream_readable = __commonJS({
5259
- "node_modules/jszip/node_modules/readable-stream/lib/_stream_readable.js"(exports, module) {
5586
+ "node_modules/readable-stream/lib/_stream_readable.js"(exports, module) {
5260
5587
  var pna = require_process_nextick_args();
5261
5588
  module.exports = Readable2;
5262
5589
  var isArray = require_isarray();
@@ -5939,9 +6266,9 @@ var require_stream_readable = __commonJS({
5939
6266
  }
5940
6267
  });
5941
6268
 
5942
- // node_modules/jszip/node_modules/readable-stream/lib/_stream_transform.js
6269
+ // node_modules/readable-stream/lib/_stream_transform.js
5943
6270
  var require_stream_transform = __commonJS({
5944
- "node_modules/jszip/node_modules/readable-stream/lib/_stream_transform.js"(exports, module) {
6271
+ "node_modules/readable-stream/lib/_stream_transform.js"(exports, module) {
5945
6272
  module.exports = Transform;
5946
6273
  var Duplex = require_stream_duplex();
5947
6274
  var util = Object.create(require_util());
@@ -6038,9 +6365,9 @@ var require_stream_transform = __commonJS({
6038
6365
  }
6039
6366
  });
6040
6367
 
6041
- // node_modules/jszip/node_modules/readable-stream/lib/_stream_passthrough.js
6368
+ // node_modules/readable-stream/lib/_stream_passthrough.js
6042
6369
  var require_stream_passthrough = __commonJS({
6043
- "node_modules/jszip/node_modules/readable-stream/lib/_stream_passthrough.js"(exports, module) {
6370
+ "node_modules/readable-stream/lib/_stream_passthrough.js"(exports, module) {
6044
6371
  module.exports = PassThrough;
6045
6372
  var Transform = require_stream_transform();
6046
6373
  var util = Object.create(require_util());
@@ -6056,9 +6383,9 @@ var require_stream_passthrough = __commonJS({
6056
6383
  }
6057
6384
  });
6058
6385
 
6059
- // node_modules/jszip/node_modules/readable-stream/readable.js
6386
+ // node_modules/readable-stream/readable.js
6060
6387
  var require_readable = __commonJS({
6061
- "node_modules/jszip/node_modules/readable-stream/readable.js"(exports, module) {
6388
+ "node_modules/readable-stream/readable.js"(exports, module) {
6062
6389
  var Stream = __require("stream");
6063
6390
  if (process.env.READABLE_STREAM === "disable" && Stream) {
6064
6391
  module.exports = Stream;
@@ -6967,8 +7294,8 @@ var require_utils = __commonJS({
6967
7294
  var result = transform[inputType][outputType](input);
6968
7295
  return result;
6969
7296
  };
6970
- exports.resolve = function(path40) {
6971
- var parts = path40.split("/");
7297
+ exports.resolve = function(path41) {
7298
+ var parts = path41.split("/");
6972
7299
  var result = [];
6973
7300
  for (var index = 0; index < parts.length; index++) {
6974
7301
  var part = parts[index];
@@ -12783,18 +13110,18 @@ var require_object = __commonJS({
12783
13110
  var object = new ZipObject(name, zipObjectContent, o);
12784
13111
  this.files[name] = object;
12785
13112
  };
12786
- var parentFolder = function(path40) {
12787
- if (path40.slice(-1) === "/") {
12788
- path40 = path40.substring(0, path40.length - 1);
13113
+ var parentFolder = function(path41) {
13114
+ if (path41.slice(-1) === "/") {
13115
+ path41 = path41.substring(0, path41.length - 1);
12789
13116
  }
12790
- var lastSlash = path40.lastIndexOf("/");
12791
- return lastSlash > 0 ? path40.substring(0, lastSlash) : "";
13117
+ var lastSlash = path41.lastIndexOf("/");
13118
+ return lastSlash > 0 ? path41.substring(0, lastSlash) : "";
12792
13119
  };
12793
- var forceTrailingSlash = function(path40) {
12794
- if (path40.slice(-1) !== "/") {
12795
- path40 += "/";
13120
+ var forceTrailingSlash = function(path41) {
13121
+ if (path41.slice(-1) !== "/") {
13122
+ path41 += "/";
12796
13123
  }
12797
- return path40;
13124
+ return path41;
12798
13125
  };
12799
13126
  var folderAdd = function(name, createFolders) {
12800
13127
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
@@ -13793,12 +14120,12 @@ var init_audit_logger = __esm({
13793
14120
  constructor(workspacePath, debugMode = false) {
13794
14121
  this.workspacePath = workspacePath;
13795
14122
  this.debugMode = debugMode;
13796
- const cascadeDir = path35.join(workspacePath, ".cascade");
13797
- if (!fs32.existsSync(cascadeDir)) {
13798
- fs32.mkdirSync(cascadeDir, { recursive: true });
14123
+ const cascadeDir = path36.join(workspacePath, ".cascade");
14124
+ if (!fs33.existsSync(cascadeDir)) {
14125
+ fs33.mkdirSync(cascadeDir, { recursive: true });
13799
14126
  }
13800
- this.keyPath = path35.join(cascadeDir, "audit_log.key");
13801
- this.dbPath = path35.join(cascadeDir, "audit_log.db");
14127
+ this.keyPath = path36.join(cascadeDir, "audit_log.key");
14128
+ this.dbPath = path36.join(cascadeDir, "audit_log.db");
13802
14129
  this.initEncryptionKey();
13803
14130
  this.db = new Database2(this.dbPath);
13804
14131
  this.db.pragma("journal_mode = WAL");
@@ -13828,11 +14155,11 @@ var init_audit_logger = __esm({
13828
14155
  return crypto.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
13829
14156
  }
13830
14157
  initEncryptionKey() {
13831
- if (fs32.existsSync(this.keyPath)) {
13832
- this.encryptionKey = fs32.readFileSync(this.keyPath);
14158
+ if (fs33.existsSync(this.keyPath)) {
14159
+ this.encryptionKey = fs33.readFileSync(this.keyPath);
13833
14160
  } else {
13834
14161
  this.encryptionKey = crypto.randomBytes(32);
13835
- fs32.writeFileSync(this.keyPath, this.encryptionKey);
14162
+ fs33.writeFileSync(this.keyPath, this.encryptionKey);
13836
14163
  }
13837
14164
  }
13838
14165
  encrypt(text) {
@@ -13910,9 +14237,9 @@ var init_audit_logger = __esm({
13910
14237
  dumpDebugIfNeeded() {
13911
14238
  if (!this.debugMode) return;
13912
14239
  try {
13913
- const dumpPath = path35.join(os10.tmpdir(), "cascade_audit_logs_debug.json");
14240
+ const dumpPath = path36.join(os10.tmpdir(), "cascade_audit_logs_debug.json");
13914
14241
  const entries = this.getAllLogs();
13915
- fs32.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
14242
+ fs33.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
13916
14243
  } catch (err) {
13917
14244
  console.error("Failed to dump debug audit logs", err);
13918
14245
  }
@@ -13967,7 +14294,7 @@ var Keystore = class {
13967
14294
  const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
13968
14295
  this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
13969
14296
  this.backend = "keytar";
13970
- if (password && fs32.existsSync(this.storePath)) {
14297
+ if (password && fs33.existsSync(this.storePath)) {
13971
14298
  try {
13972
14299
  const fileEntries = this.decryptFile(password);
13973
14300
  for (const [k, v] of Object.entries(fileEntries)) {
@@ -13986,7 +14313,7 @@ var Keystore = class {
13986
14313
  "Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
13987
14314
  );
13988
14315
  }
13989
- if (!fs32.existsSync(this.storePath)) {
14316
+ if (!fs33.existsSync(this.storePath)) {
13990
14317
  const salt = crypto.randomBytes(SALT_LEN);
13991
14318
  this.masterKey = this.deriveKey(password, salt);
13992
14319
  this.writeWithSalt({}, salt);
@@ -14000,7 +14327,7 @@ var Keystore = class {
14000
14327
  }
14001
14328
  /** Synchronous legacy unlock kept for AES-only environments. */
14002
14329
  unlockSync(password) {
14003
- if (!fs32.existsSync(this.storePath)) {
14330
+ if (!fs33.existsSync(this.storePath)) {
14004
14331
  const salt = crypto.randomBytes(SALT_LEN);
14005
14332
  this.masterKey = this.deriveKey(password, salt);
14006
14333
  this.writeWithSalt({}, salt);
@@ -14058,7 +14385,7 @@ var Keystore = class {
14058
14385
  }
14059
14386
  }
14060
14387
  decryptFile(password, knownSalt) {
14061
- if (!fs32.existsSync(this.storePath)) return {};
14388
+ if (!fs33.existsSync(this.storePath)) return {};
14062
14389
  try {
14063
14390
  const { salt, ciphertext, iv, tag } = this.readRaw();
14064
14391
  const useSalt = knownSalt ?? salt;
@@ -14080,8 +14407,8 @@ var Keystore = class {
14080
14407
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
14081
14408
  const tag = cipher.getAuthTag();
14082
14409
  const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
14083
- fs32.mkdirSync(path35.dirname(this.storePath), { recursive: true });
14084
- fs32.writeFileSync(this.storePath, out, { mode: 384 });
14410
+ fs33.mkdirSync(path36.dirname(this.storePath), { recursive: true });
14411
+ fs33.writeFileSync(this.storePath, out, { mode: 384 });
14085
14412
  }
14086
14413
  writeWithSalt(data, salt) {
14087
14414
  if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
@@ -14091,11 +14418,11 @@ var Keystore = class {
14091
14418
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
14092
14419
  const tag = cipher.getAuthTag();
14093
14420
  const out = Buffer.concat([salt, iv, tag, ciphertext]);
14094
- fs32.mkdirSync(path35.dirname(this.storePath), { recursive: true });
14095
- fs32.writeFileSync(this.storePath, out, { mode: 384 });
14421
+ fs33.mkdirSync(path36.dirname(this.storePath), { recursive: true });
14422
+ fs33.writeFileSync(this.storePath, out, { mode: 384 });
14096
14423
  }
14097
14424
  readRaw() {
14098
- const buf = fs32.readFileSync(this.storePath);
14425
+ const buf = fs33.readFileSync(this.storePath);
14099
14426
  let offset = 0;
14100
14427
  const salt = buf.subarray(offset, offset + SALT_LEN);
14101
14428
  offset += SALT_LEN;
@@ -14128,7 +14455,7 @@ var CascadeIgnore = class {
14128
14455
  ]);
14129
14456
  }
14130
14457
  async load(workspacePath) {
14131
- const filePath = path35.join(workspacePath, ".cascadeignore");
14458
+ const filePath = path36.join(workspacePath, ".cascadeignore");
14132
14459
  try {
14133
14460
  const content = await fs11.readFile(filePath, "utf-8");
14134
14461
  const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
@@ -14139,7 +14466,7 @@ var CascadeIgnore = class {
14139
14466
  }
14140
14467
  isIgnored(filePath, workspacePath) {
14141
14468
  try {
14142
- const relative = workspacePath ? path35.relative(workspacePath, filePath) : filePath;
14469
+ const relative = workspacePath ? path36.relative(workspacePath, filePath) : filePath;
14143
14470
  return this.ig.ignores(relative);
14144
14471
  } catch {
14145
14472
  return false;
@@ -14150,7 +14477,7 @@ var CascadeIgnore = class {
14150
14477
  }
14151
14478
  };
14152
14479
  async function createDefaultIgnoreFile(workspacePath) {
14153
- const filePath = path35.join(workspacePath, ".cascadeignore");
14480
+ const filePath = path36.join(workspacePath, ".cascadeignore");
14154
14481
  const content = `# .cascadeignore \u2014 Files Cascade agents cannot read or modify
14155
14482
  # Syntax identical to .gitignore
14156
14483
 
@@ -14180,7 +14507,7 @@ Thumbs.db
14180
14507
  await fs11.writeFile(filePath, content, "utf-8");
14181
14508
  }
14182
14509
  async function loadCascadeMd(workspacePath) {
14183
- const filePath = path35.join(workspacePath, "CASCADE.md");
14510
+ const filePath = path36.join(workspacePath, "CASCADE.md");
14184
14511
  try {
14185
14512
  const raw = await fs11.readFile(filePath, "utf-8");
14186
14513
  return parseCascadeMd(raw);
@@ -14209,7 +14536,7 @@ ${raw.trim()}`;
14209
14536
  return { raw, sections, systemPrompt };
14210
14537
  }
14211
14538
  async function createDefaultCascadeMd(workspacePath) {
14212
- const filePath = path35.join(workspacePath, "CASCADE.md");
14539
+ const filePath = path36.join(workspacePath, "CASCADE.md");
14213
14540
  const content = `# Cascade Project Instructions
14214
14541
 
14215
14542
  This file contains project-specific instructions for the Cascade AI agent.
@@ -14244,7 +14571,7 @@ List any areas the agent should not touch.
14244
14571
  var MemoryStore = class _MemoryStore {
14245
14572
  db;
14246
14573
  constructor(dbPath) {
14247
- fs32.mkdirSync(path35.dirname(dbPath), { recursive: true });
14574
+ fs33.mkdirSync(path36.dirname(dbPath), { recursive: true });
14248
14575
  try {
14249
14576
  this.db = new Database2(dbPath, { timeout: 5e3 });
14250
14577
  this.db.pragma("journal_mode = WAL");
@@ -15142,7 +15469,18 @@ var WebSearchConfigSchema = z.object({
15142
15469
  /** Tavily API key — get one at https://tavily.com */
15143
15470
  tavilyApiKey: z.string().optional(),
15144
15471
  /** Max results per search (default 5) */
15145
- maxResults: z.number().default(5)
15472
+ maxResults: z.number().default(5),
15473
+ /**
15474
+ * Send `searxngUrl` through the SSRF guard. Off for a local run, where the
15475
+ * URL is the machine owner's own config and a self-hosted SearXNG is
15476
+ * routinely on a private address; on for a host that accepts it from a
15477
+ * request. See `src/tools/web-search.ts`.
15478
+ *
15479
+ * It has to be named HERE, not just on the TypeScript interfaces: the config
15480
+ * passes through `CascadeConfigSchema.parse()` on its way into a run, and
15481
+ * zod drops every key this object does not declare.
15482
+ */
15483
+ guardSearxngUrl: z.boolean().optional()
15146
15484
  });
15147
15485
  var ToolsConfigSchema = z.object({
15148
15486
  shellAllowlist: z.array(z.string()).default([]),
@@ -15551,7 +15889,7 @@ function credentialEndpointsConflict(type, rowEndpoint, credentialEndpoint) {
15551
15889
 
15552
15890
  // src/config/global-credentials.ts
15553
15891
  function credentialsPath(globalDir2) {
15554
- return path35.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
15892
+ return path36.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
15555
15893
  }
15556
15894
  function providerKey(p) {
15557
15895
  return p.type;
@@ -15581,7 +15919,7 @@ function isPersistable(p) {
15581
15919
  }
15582
15920
  function loadGlobalCredentials(globalDir2) {
15583
15921
  try {
15584
- const raw = fs32.readFileSync(credentialsPath(globalDir2), "utf-8");
15922
+ const raw = fs33.readFileSync(credentialsPath(globalDir2), "utf-8");
15585
15923
  const parsed = JSON.parse(raw);
15586
15924
  if (!Array.isArray(parsed.providers)) return [];
15587
15925
  return parsed.providers.filter(
@@ -15593,13 +15931,13 @@ function loadGlobalCredentials(globalDir2) {
15593
15931
  }
15594
15932
  function saveGlobalCredentials(globalDir2, providers) {
15595
15933
  const filePath = credentialsPath(globalDir2);
15596
- fs32.mkdirSync(globalDir2, { recursive: true, mode: 448 });
15934
+ fs33.mkdirSync(globalDir2, { recursive: true, mode: 448 });
15597
15935
  const body = { version: 1, providers: providers.filter(isPersistable) };
15598
15936
  const tmp = `${filePath}.tmp`;
15599
- fs32.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
15600
- fs32.renameSync(tmp, filePath);
15937
+ fs33.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
15938
+ fs33.renameSync(tmp, filePath);
15601
15939
  try {
15602
- fs32.chmodSync(filePath, 384);
15940
+ fs33.chmodSync(filePath, 384);
15603
15941
  } catch {
15604
15942
  }
15605
15943
  }
@@ -16004,7 +16342,7 @@ var ConfigManager = class {
16004
16342
  /** `globalDirOverride` exists for tests — never point it at the real home dir there. */
16005
16343
  constructor(workspacePath = process.cwd(), globalDirOverride) {
16006
16344
  this.workspacePath = workspacePath;
16007
- this.globalDir = globalDirOverride ?? path35.join(os10.homedir(), GLOBAL_CONFIG_DIR);
16345
+ this.globalDir = globalDirOverride ?? path36.join(os10.homedir(), GLOBAL_CONFIG_DIR);
16008
16346
  }
16009
16347
  async load() {
16010
16348
  this.config = await this.loadConfig();
@@ -16018,8 +16356,8 @@ var ConfigManager = class {
16018
16356
  this.ignore = new CascadeIgnore();
16019
16357
  await this.ignore.load(this.workspacePath);
16020
16358
  this.cascadeMd = await loadCascadeMd(this.workspacePath);
16021
- this.keystore = new Keystore(path35.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
16022
- this.store = new MemoryStore(path35.join(this.workspacePath, CASCADE_DB_FILE));
16359
+ this.keystore = new Keystore(path36.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
16360
+ this.store = new MemoryStore(path36.join(this.workspacePath, CASCADE_DB_FILE));
16023
16361
  const globalRevoked = stripRevokedCredentials(loadGlobalCredentials(this.globalDir));
16024
16362
  const globalCreds = filterRetiredCredentials(globalRevoked.kept);
16025
16363
  if (globalRevoked.removed > 0) this.revokedCredentials += globalRevoked.removed;
@@ -16092,8 +16430,8 @@ var ConfigManager = class {
16092
16430
  * never reached the disk (see `commitSettings`).
16093
16431
  */
16094
16432
  async save(config = this.config) {
16095
- const configPath = path35.join(this.workspacePath, CASCADE_CONFIG_FILE);
16096
- await fs11.mkdir(path35.dirname(configPath), { recursive: true });
16433
+ const configPath = path36.join(this.workspacePath, CASCADE_CONFIG_FILE);
16434
+ await fs11.mkdir(path36.dirname(configPath), { recursive: true });
16097
16435
  await fs11.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
16098
16436
  try {
16099
16437
  saveGlobalCredentials(this.globalDir, config.providers);
@@ -16143,7 +16481,7 @@ var ConfigManager = class {
16143
16481
  this.revokedCredentials = 0;
16144
16482
  this.revokedPins = [];
16145
16483
  this.revokedNotice = void 0;
16146
- const configPath = path35.join(this.workspacePath, CASCADE_CONFIG_FILE);
16484
+ const configPath = path36.join(this.workspacePath, CASCADE_CONFIG_FILE);
16147
16485
  try {
16148
16486
  const raw = await fs11.readFile(configPath, "utf-8");
16149
16487
  const parsed = JSON.parse(raw);
@@ -16237,7 +16575,7 @@ var ConfigManager = class {
16237
16575
  return onResource;
16238
16576
  }
16239
16577
  async persistClearedPins(tiers) {
16240
- const configPath = path35.join(this.workspacePath, CASCADE_CONFIG_FILE);
16578
+ const configPath = path36.join(this.workspacePath, CASCADE_CONFIG_FILE);
16241
16579
  try {
16242
16580
  const raw = JSON.parse(await fs11.readFile(configPath, "utf-8"));
16243
16581
  const models = raw["models"];
@@ -17961,7 +18299,7 @@ init_pricing();
17961
18299
  var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
17962
18300
  var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
17963
18301
  var FETCH_TIMEOUT_MS = 8e3;
17964
- var DEFAULT_CACHE_FILE = path35.join(os10.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
18302
+ var DEFAULT_CACHE_FILE = path36.join(os10.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
17965
18303
  function normalizeModelId2(id) {
17966
18304
  let s = id.toLowerCase();
17967
18305
  const slash = s.lastIndexOf("/");
@@ -18108,7 +18446,7 @@ var LiveDataProvider = class {
18108
18446
  }
18109
18447
  async saveCache() {
18110
18448
  try {
18111
- await fs11.mkdir(path35.dirname(this.opts.cacheFile), { recursive: true });
18449
+ await fs11.mkdir(path36.dirname(this.opts.cacheFile), { recursive: true });
18112
18450
  const cache = {
18113
18451
  fetchedAt: this.fetchedAt,
18114
18452
  snapshot: this.snapshot ?? void 0,
@@ -18215,9 +18553,15 @@ var FAMILY_BENCHMARKS = {
18215
18553
  "claude-opus": { code: 95, analysis: 92, creative: 90, data: 88 },
18216
18554
  "claude-sonnet": { code: 93, analysis: 88, creative: 87, data: 85 },
18217
18555
  "claude-haiku": { code: 80, analysis: 75, creative: 76, data: 72 },
18218
- // OpenAI GPT-5 family — current flagship; strongest all-round. Offline
18219
- // baseline (the live fetch refreshes these). Distinct point releases carry
18220
- // their own scores: gpt-5.5 "Spud" leads SWE-bench (88.7%); gpt-5.4 ~80%.
18556
+ // OpenAI GPT-5 family. GPT-5.6 is split by durable capability tier instead
18557
+ // of being folded into generic gpt-5: Sol, Terra and Luna have materially
18558
+ // different benchmark/cost envelopes and should compete independently.
18559
+ // Scores are the committed normalized snapshot; provenance is in
18560
+ // scripts/benchmarks/sources/openai-gpt-5.6-2026-08.json.
18561
+ "gpt-5.6-sol": { code: 100, analysis: 98, creative: 88, data: 88 },
18562
+ "gpt-5.6-terra": { code: 97, analysis: 92, creative: 82, data: 85 },
18563
+ "gpt-5.6-luna": { code: 93, analysis: 85, creative: 74, data: 60 },
18564
+ // Older GPT-5 point releases remain distinct where public data supports it.
18221
18565
  "gpt-5.5": { code: 97, analysis: 96, creative: 94, data: 95 },
18222
18566
  "gpt-5.4": { code: 94, analysis: 94, creative: 92, data: 92 },
18223
18567
  "gpt-5.4-mini": { code: 86, analysis: 85, creative: 85, data: 83 },
@@ -18253,13 +18597,19 @@ var FAMILY_BENCHMARKS = {
18253
18597
  "llama-small": { code: 55, analysis: 56, creative: 60, data: 54 }
18254
18598
  };
18255
18599
  var FAMILY_MATCHERS = [
18600
+ [/fable/i, "claude-opus"],
18256
18601
  [/opus/i, "claude-opus"],
18257
18602
  [/sonnet/i, "claude-sonnet"],
18258
18603
  [/haiku/i, "claude-haiku"],
18259
- // GPT-5 family — ordered most-specific first. Distinct point releases
18260
- // (gpt-5.5, gpt-5.4, gpt-5.4-mini) match their own family; nano/mini before
18261
- // the base; unrecognised gpt-5.x still fold into the gpt-5 base.
18604
+ // GPT-5 family — ordered most-specific first. GPT-5.6 durable tiers carry
18605
+ // their own profiles. 5.4 Nano intentionally maps to the nano class rather
18606
+ // than being swallowed by the generic 5.4 rule.
18607
+ [/gpt-?5\.6.*sol/i, "gpt-5.6-sol"],
18608
+ [/gpt-?5\.6.*terra/i, "gpt-5.6-terra"],
18609
+ [/gpt-?5\.6.*luna/i, "gpt-5.6-luna"],
18610
+ [/gpt-?5\.6(?:\s|$)/i, "gpt-5.6-sol"],
18262
18611
  [/gpt-?5\.5/i, "gpt-5.5"],
18612
+ [/gpt-?5\.4.*nano/i, "gpt-5-nano"],
18263
18613
  [/gpt-?5\.4.*mini/i, "gpt-5.4-mini"],
18264
18614
  [/gpt-?5\.4/i, "gpt-5.4"],
18265
18615
  [/gpt-?5.*nano/i, "gpt-5-nano"],
@@ -21319,8 +21669,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
21319
21669
  // Media generation can run for a minute; without this a cancelled run
21320
21670
  // still pays for an image nobody will see.
21321
21671
  ...this.signal ? { signal: this.signal } : {},
21322
- saveSnapshot: async (path40, content) => {
21323
- this.store?.addFileSnapshot(this.taskId, path40, content);
21672
+ saveSnapshot: async (path41, content) => {
21673
+ this.store?.addFileSnapshot(this.taskId, path41, content);
21324
21674
  },
21325
21675
  sendPeerSync: (to, syncType, content) => {
21326
21676
  this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
@@ -21523,7 +21873,7 @@ ${assignment.expectedOutput}`;
21523
21873
  const { promisify: promisify4 } = await import('util');
21524
21874
  const execFileAsync2 = promisify4(execFile3);
21525
21875
  for (const artifactPath of artifactPaths) {
21526
- const absolutePath = path35.resolve(this.artifactRoot(), artifactPath);
21876
+ const absolutePath = path36.resolve(this.artifactRoot(), artifactPath);
21527
21877
  try {
21528
21878
  const stat = await fs11.stat(absolutePath);
21529
21879
  if (!stat.isFile()) {
@@ -21558,7 +21908,7 @@ ${assignment.expectedOutput}`;
21558
21908
  issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
21559
21909
  continue;
21560
21910
  }
21561
- const ext = path35.extname(absolutePath).toLowerCase();
21911
+ const ext = path36.extname(absolutePath).toLowerCase();
21562
21912
  const check = ext === ".ts" || ext === ".tsx" ? { cmd: "npx", args: ["tsc", "--noEmit", absolutePath] } : ext === ".js" || ext === ".jsx" ? { cmd: process.execPath, args: ["--check", absolutePath] } : ext === ".py" ? { cmd: "python", args: ["-m", "py_compile", absolutePath] } : null;
21563
21913
  if (check) {
21564
21914
  try {
@@ -21650,7 +22000,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
21650
22000
  async checkAcceptance(assignment) {
21651
22001
  const criteria = assignment.acceptance ?? [];
21652
22002
  if (!criteria.length) return [];
21653
- const resolve2 = (target) => path35.resolve(this.artifactRoot(), target);
22003
+ const resolve2 = (target) => path36.resolve(this.artifactRoot(), target);
21654
22004
  const workerCanWriteFiles = canProduceFiles(this.tools.map((t) => t.name));
21655
22005
  return evaluateAcceptance(criteria, assignment.files ?? [], {
21656
22006
  stat: async (target) => {
@@ -24961,16 +25311,16 @@ function resolveInWorkspace(workspaceRoot, input) {
24961
25311
  if (typeof input !== "string" || input.length === 0) {
24962
25312
  throw new WorkspaceSandboxError(String(input), workspaceRoot);
24963
25313
  }
24964
- const root = path35.resolve(workspaceRoot);
24965
- const abs = path35.isAbsolute(input) ? path35.resolve(input) : path35.resolve(root, input);
24966
- const rel = path35.relative(root, abs);
24967
- if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path35.isAbsolute(rel)) {
25314
+ const root = path36.resolve(workspaceRoot);
25315
+ const abs = path36.isAbsolute(input) ? path36.resolve(input) : path36.resolve(root, input);
25316
+ const rel = path36.relative(root, abs);
25317
+ if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path36.isAbsolute(rel)) {
24968
25318
  throw new WorkspaceSandboxError(input, root);
24969
25319
  }
24970
25320
  try {
24971
- const real = fs32.realpathSync(abs);
24972
- const realRel = path35.relative(root, real);
24973
- if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path35.isAbsolute(realRel))) {
25321
+ const real = fs33.realpathSync(abs);
25322
+ const realRel = path36.relative(root, real);
25323
+ if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path36.isAbsolute(realRel))) {
24974
25324
  throw new WorkspaceSandboxError(input, root);
24975
25325
  }
24976
25326
  } catch (e) {
@@ -25519,9 +25869,9 @@ async function animatePptx(bytes, scheme) {
25519
25869
  const zip = await import_jszip.default.loadAsync(bytes);
25520
25870
  const slidePaths = Object.keys(zip.files).filter((n) => /^ppt\/slides\/slide\d+\.xml$/.test(n));
25521
25871
  if (slidePaths.length === 0) return bytes;
25522
- for (const path40 of slidePaths) {
25523
- const xml = await zip.file(path40).async("string");
25524
- zip.file(path40, animateSlideXml(xml, scheme));
25872
+ for (const path41 of slidePaths) {
25873
+ const xml = await zip.file(path41).async("string");
25874
+ zip.file(path41, animateSlideXml(xml, scheme));
25525
25875
  }
25526
25876
  return await zip.generateAsync({ type: "uint8array", compression: "DEFLATE" });
25527
25877
  } catch {
@@ -25902,7 +26252,7 @@ var GenerateDocumentTool = class extends BaseTool {
25902
26252
  }
25903
26253
  }
25904
26254
  const bytes = await renderDocument(ext, content, { name: filePath, loadImageBytes });
25905
- await fs11.mkdir(path35.dirname(absPath), { recursive: true });
26255
+ await fs11.mkdir(path36.dirname(absPath), { recursive: true });
25906
26256
  await fs11.writeFile(absPath, bytes);
25907
26257
  const charts = countCharts(content);
25908
26258
  return [
@@ -26123,7 +26473,7 @@ var FileWriteTool = class extends BaseTool {
26123
26473
  } catch {
26124
26474
  }
26125
26475
  }
26126
- await fs11.mkdir(path35.dirname(absPath), { recursive: true });
26476
+ await fs11.mkdir(path36.dirname(absPath), { recursive: true });
26127
26477
  await fs11.writeFile(absPath, content, "utf-8");
26128
26478
  return `Written ${content.length} characters to ${filePath}`;
26129
26479
  }
@@ -26481,12 +26831,10 @@ ${data.description}`;
26481
26831
  }
26482
26832
  }
26483
26833
  };
26484
-
26485
- // src/tools/browser.ts
26486
26834
  var BROWSER_LAUNCH_TIMEOUT_MS = 15e3;
26487
26835
  var BrowserTool = class extends BaseTool {
26488
26836
  name = "browser";
26489
- description = "Control a browser: navigate to URLs, click elements, fill forms, take screenshots. Only available with multimodal models.";
26837
+ description = "Control a browser: navigate to URLs, click elements, fill forms, take screenshots. Screenshots are saved to a file for image_analyze to read.";
26490
26838
  inputSchema = {
26491
26839
  type: "object",
26492
26840
  properties: {
@@ -26553,7 +26901,14 @@ var BrowserTool = class extends BaseTool {
26553
26901
  }
26554
26902
  case "screenshot": {
26555
26903
  const buf = await page2.screenshot({ type: "png" });
26556
- return `data:image/png;base64,${buf.toString("base64")}`;
26904
+ const rel = path36.join(".cascade", "screenshots", `screenshot-${Date.now()}-${randomUUID().slice(0, 8)}.png`);
26905
+ const abs = resolveInWorkspace(this.workspaceRoot, rel);
26906
+ await fs11.mkdir(path36.dirname(abs), { recursive: true });
26907
+ await fs11.writeFile(abs, buf);
26908
+ return [
26909
+ `Screenshot saved to ${abs} (${Math.round(buf.byteLength / 1024)} KB).`,
26910
+ `Call image_analyze with that path to see what is on it \u2014 that needs a vision-capable model.`
26911
+ ].join(" ");
26557
26912
  }
26558
26913
  case "evaluate": {
26559
26914
  const result = await page2.evaluate(input["script"]);
@@ -26621,7 +26976,7 @@ var ImageAnalyzeTool = class extends BaseTool {
26621
26976
  };
26622
26977
  async function fileToImageAttachment(filePath) {
26623
26978
  const data = await fs11.readFile(filePath);
26624
- const ext = path35.extname(filePath).toLowerCase();
26979
+ const ext = path36.extname(filePath).toLowerCase();
26625
26980
  const mimeMap = {
26626
26981
  ".jpg": "image/jpeg",
26627
26982
  ".jpeg": "image/jpeg",
@@ -26655,14 +27010,14 @@ var PDFCreateTool = class extends BaseTool {
26655
27010
  const filePath = input["path"];
26656
27011
  const content = input["content"];
26657
27012
  const title = input["title"];
26658
- const dir = path35.dirname(filePath);
26659
- if (!fs32.existsSync(dir)) {
26660
- fs32.mkdirSync(dir, { recursive: true });
27013
+ const dir = path36.dirname(filePath);
27014
+ if (!fs33.existsSync(dir)) {
27015
+ fs33.mkdirSync(dir, { recursive: true });
26661
27016
  }
26662
27017
  return new Promise((resolve2, reject) => {
26663
27018
  try {
26664
27019
  const doc = new PDFDocument({ margin: 50 });
26665
- const stream = fs32.createWriteStream(filePath);
27020
+ const stream = fs33.createWriteStream(filePath);
26666
27021
  doc.pipe(stream);
26667
27022
  if (title) {
26668
27023
  doc.info["Title"] = title;
@@ -26740,22 +27095,22 @@ var CodeInterpreterTool = class extends BaseTool {
26740
27095
  }
26741
27096
  cmdPrefix = NODE_CMD;
26742
27097
  }
26743
- const tmpDir = path35.join(this.workspaceRoot, ".cascade", "tmp");
26744
- if (!fs32.existsSync(tmpDir)) {
26745
- fs32.mkdirSync(tmpDir, { recursive: true });
27098
+ const tmpDir = path36.join(this.workspaceRoot, ".cascade", "tmp");
27099
+ if (!fs33.existsSync(tmpDir)) {
27100
+ fs33.mkdirSync(tmpDir, { recursive: true });
26746
27101
  }
26747
27102
  const extension = language === "python" ? "py" : "js";
26748
27103
  const fileName = `intp_${randomUUID().slice(0, 8)}.${extension}`;
26749
- const filePath = path35.join(tmpDir, fileName);
26750
- fs32.writeFileSync(filePath, code, "utf-8");
27104
+ const filePath = path36.join(tmpDir, fileName);
27105
+ fs33.writeFileSync(filePath, code, "utf-8");
26751
27106
  const execArgs = [filePath, ...args];
26752
27107
  return new Promise((resolve2) => {
26753
27108
  const startMs = Date.now();
26754
27109
  execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
26755
27110
  const duration = Date.now() - startMs;
26756
27111
  try {
26757
- if (fs32.existsSync(filePath)) {
26758
- fs32.unlinkSync(filePath);
27112
+ if (fs33.existsSync(filePath)) {
27113
+ fs33.unlinkSync(filePath);
26759
27114
  }
26760
27115
  } catch (cleanupErr) {
26761
27116
  console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
@@ -26835,18 +27190,178 @@ ${formatted}`;
26835
27190
  return `Unknown action: ${action}`;
26836
27191
  }
26837
27192
  };
27193
+ var SsrfBlockedError = class extends Error {
27194
+ constructor(message) {
27195
+ super(message);
27196
+ this.name = "SsrfBlockedError";
27197
+ }
27198
+ };
27199
+ var ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
27200
+ var MAX_REDIRECTS2 = 5;
27201
+ function allowLocal() {
27202
+ return process.env["CASCADE_ALLOW_LOCAL_FETCH"] === "1";
27203
+ }
27204
+ function isPrivateAddress(ip) {
27205
+ const type = net.isIP(ip);
27206
+ if (type === 4) return isPrivateIPv4(ip);
27207
+ if (type === 6) return isPrivateIPv6(ip);
27208
+ return true;
27209
+ }
27210
+ function isPrivateIPv4(ip) {
27211
+ const parts = ip.split(".").map((p) => Number(p));
27212
+ if (parts.length !== 4 || parts.some((p) => Number.isNaN(p) || p < 0 || p > 255)) return true;
27213
+ const [a, b] = parts;
27214
+ if (a === 0) return true;
27215
+ if (a === 10) return true;
27216
+ if (a === 127) return true;
27217
+ if (a === 169 && b === 254) return true;
27218
+ if (a === 172 && b >= 16 && b <= 31) return true;
27219
+ if (a === 192 && b === 168) return true;
27220
+ if (a === 100 && b >= 64 && b <= 127) return true;
27221
+ if (a >= 224) return true;
27222
+ return false;
27223
+ }
27224
+ function ipv6Groups(ip) {
27225
+ let s = ip.toLowerCase().replace(/^\[|\]$/g, "");
27226
+ const zone = s.indexOf("%");
27227
+ if (zone !== -1) s = s.slice(0, zone);
27228
+ const dotted = /^(.*:)(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(s);
27229
+ if (dotted) {
27230
+ const o = [dotted[2], dotted[3], dotted[4], dotted[5]].map((p) => Number(p));
27231
+ if (o.some((n) => !Number.isInteger(n) || n < 0 || n > 255)) return null;
27232
+ s = `${dotted[1]}${(o[0] << 8 | o[1]).toString(16)}:${(o[2] << 8 | o[3]).toString(16)}`;
27233
+ }
27234
+ const halves = s.split("::");
27235
+ if (halves.length > 2) return null;
27236
+ const split = (part) => part ? part.split(":") : [];
27237
+ const head = split(halves[0] ?? "");
27238
+ const tail = halves.length === 2 ? split(halves[1] ?? "") : [];
27239
+ const zeros = halves.length === 2 ? 8 - head.length - tail.length : 0;
27240
+ if (zeros < 0) return null;
27241
+ const groups = [...head, ...Array(zeros).fill("0"), ...tail];
27242
+ if (groups.length !== 8) return null;
27243
+ const out = [];
27244
+ for (const g of groups) {
27245
+ if (!/^[0-9a-f]{1,4}$/.test(g)) return null;
27246
+ out.push(parseInt(g, 16));
27247
+ }
27248
+ return out;
27249
+ }
27250
+ function embeddedIPv4(g) {
27251
+ const quad = (hi, lo) => `${hi >> 8}.${hi & 255}.${lo >> 8}.${lo & 255}`;
27252
+ const leadingZero = (n) => g.slice(0, n).every((x) => x === 0);
27253
+ if (leadingZero(5) && g[5] === 65535) return quad(g[6], g[7]);
27254
+ if (leadingZero(6)) return quad(g[6], g[7]);
27255
+ if (g[0] === 100 && g[1] === 65435) {
27256
+ if (g[2] === 1) {
27257
+ return quad(g[3], (g[4] & 255) << 8 | g[5] >> 8);
27258
+ }
27259
+ if (g[2] === 0 && g[3] === 0 && g[4] === 0 && g[5] === 0) return quad(g[6], g[7]);
27260
+ return null;
27261
+ }
27262
+ if (g[0] === 8194) return quad(g[1], g[2]);
27263
+ return null;
27264
+ }
27265
+ function isPrivateIPv6(ip) {
27266
+ const g = ipv6Groups(ip);
27267
+ if (!g) return true;
27268
+ const v4 = embeddedIPv4(g);
27269
+ if (v4) return isPrivateIPv4(v4);
27270
+ if (g[0] === 100 && g[1] === 65435) return true;
27271
+ if (g.every((x) => x === 0)) return true;
27272
+ if (leading7Zero(g) && g[7] === 1) return true;
27273
+ if ((g[0] & 65472) === 65152) return true;
27274
+ if ((g[0] & 65024) === 64512) return true;
27275
+ if ((g[0] & 65280) === 65280) return true;
27276
+ return false;
27277
+ }
27278
+ function leading7Zero(g) {
27279
+ return g.slice(0, 7).every((x) => x === 0);
27280
+ }
27281
+ async function assertPublicUrl(rawUrl) {
27282
+ let url;
27283
+ try {
27284
+ url = new URL(rawUrl);
27285
+ } catch {
27286
+ throw new SsrfBlockedError(`Invalid URL: ${rawUrl}`);
27287
+ }
27288
+ if (!ALLOWED_PROTOCOLS.has(url.protocol)) {
27289
+ throw new SsrfBlockedError(`Blocked URL scheme "${url.protocol}" \u2014 only http and https are allowed.`);
27290
+ }
27291
+ if (allowLocal()) return url;
27292
+ const host = url.hostname.replace(/^\[|\]$/g, "");
27293
+ if (net.isIP(host)) {
27294
+ if (isPrivateAddress(host)) {
27295
+ throw new SsrfBlockedError(`Blocked request to non-public address ${host}.`);
27296
+ }
27297
+ return url;
27298
+ }
27299
+ if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local")) {
27300
+ throw new SsrfBlockedError(`Blocked request to local hostname "${host}".`);
27301
+ }
27302
+ let addresses;
27303
+ try {
27304
+ const records = await dns2.lookup(host, { all: true });
27305
+ addresses = records.map((r) => r.address);
27306
+ } catch {
27307
+ throw new SsrfBlockedError(`Could not resolve host "${host}".`);
27308
+ }
27309
+ if (addresses.length === 0 || addresses.some((addr) => isPrivateAddress(addr))) {
27310
+ throw new SsrfBlockedError(`Blocked request to "${host}" \u2014 resolves to a non-public address.`);
27311
+ }
27312
+ return url;
27313
+ }
27314
+ var ssrfAgent = new Agent({
27315
+ connect: {
27316
+ lookup(hostname, options, callback) {
27317
+ if (allowLocal()) {
27318
+ dnsCallback.lookup(hostname, options, callback);
27319
+ return;
27320
+ }
27321
+ dnsCallback.lookup(hostname, { ...options, all: true }, (err, addresses) => {
27322
+ if (err) return callback(err, "", 0);
27323
+ const list = Array.isArray(addresses) ? addresses : [addresses];
27324
+ const bad = list.find((a) => isPrivateAddress(a.address));
27325
+ if (bad) {
27326
+ return callback(
27327
+ new SsrfBlockedError(`Blocked connection to "${hostname}" \u2014 resolved to non-public address ${bad.address}.`),
27328
+ "",
27329
+ 0
27330
+ );
27331
+ }
27332
+ if (options.all) return callback(null, list);
27333
+ const first = list[0];
27334
+ return callback(null, first.address, first.family);
27335
+ });
27336
+ }
27337
+ }
27338
+ });
27339
+ async function safeFetch(rawUrl, init = {}) {
27340
+ let currentUrl = (await assertPublicUrl(rawUrl)).toString();
27341
+ for (let i = 0; i <= MAX_REDIRECTS2; i++) {
27342
+ const resp = await fetch(currentUrl, { ...init, redirect: "manual", dispatcher: ssrfAgent });
27343
+ if (resp.status < 300 || resp.status >= 400) return resp;
27344
+ const location = resp.headers.get("location");
27345
+ if (!location) return resp;
27346
+ const next = new URL(location, currentUrl);
27347
+ await assertPublicUrl(next.toString());
27348
+ currentUrl = next.toString();
27349
+ }
27350
+ throw new SsrfBlockedError(`Too many redirects (>${MAX_REDIRECTS2}).`);
27351
+ }
26838
27352
 
26839
27353
  // src/tools/web-search.ts
26840
- async function searchSearXNG(query, baseUrl, maxResults) {
27354
+ async function searchSearXNG(query, baseUrl, maxResults, guardUrl = false) {
26841
27355
  const url = new URL("/search", baseUrl);
26842
27356
  url.searchParams.set("q", query);
26843
27357
  url.searchParams.set("format", "json");
26844
27358
  url.searchParams.set("categories", "general");
26845
27359
  url.searchParams.set("engines", "google,bing,duckduckgo");
26846
- const resp = await fetch(url.toString(), {
27360
+ const init = {
26847
27361
  headers: { "User-Agent": "Cascade-AI/1.0 WebSearchTool" },
26848
27362
  signal: AbortSignal.timeout(1e4)
26849
- });
27363
+ };
27364
+ const resp = guardUrl ? await safeFetch(url.toString(), init) : await fetch(url.toString(), init);
26850
27365
  if (!resp.ok) {
26851
27366
  throw new Error(`SearXNG returned HTTP ${resp.status}`);
26852
27367
  }
@@ -26975,7 +27490,11 @@ var WebSearchTool = class extends BaseTool {
26975
27490
  searxngUrl: config.searxngUrl ?? process.env["SEARXNG_URL"],
26976
27491
  braveApiKey: config.braveApiKey ?? process.env["BRAVE_SEARCH_API_KEY"],
26977
27492
  tavilyApiKey: config.tavilyApiKey ?? process.env["TAVILY_API_KEY"],
26978
- maxResults: config.maxResults ?? 5
27493
+ maxResults: config.maxResults ?? 5,
27494
+ // Note this reads only from `config`, never from the environment: the
27495
+ // guard tracks the trustworthiness of the URL the host passed in, and an
27496
+ // env var says nothing about that.
27497
+ guardSearxngUrl: config.guardSearxngUrl ?? false
26979
27498
  };
26980
27499
  }
26981
27500
  async execute(input, _options) {
@@ -26990,7 +27509,7 @@ var WebSearchTool = class extends BaseTool {
26990
27509
  let anyBackendReachable = false;
26991
27510
  if (this.config.searxngUrl) {
26992
27511
  try {
26993
- results = await searchSearXNG(query, this.config.searxngUrl, maxResults);
27512
+ results = await searchSearXNG(query, this.config.searxngUrl, maxResults, this.config.guardSearxngUrl);
26994
27513
  anyBackendReachable = true;
26995
27514
  if (results.length > 0) return this.formatResults(query, results);
26996
27515
  errors.push("SearXNG: returned 0 results");
@@ -27067,7 +27586,7 @@ var GlobTool = class extends BaseTool {
27067
27586
  };
27068
27587
  async execute(input, _options) {
27069
27588
  const pattern = input["pattern"];
27070
- const searchPath = input["path"] ? path35.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
27589
+ const searchPath = input["path"] ? path36.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
27071
27590
  const matches = await glob(pattern, {
27072
27591
  cwd: searchPath,
27073
27592
  ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
@@ -27080,7 +27599,7 @@ var GlobTool = class extends BaseTool {
27080
27599
  const withMtime = await Promise.all(
27081
27600
  matches.map(async (rel) => {
27082
27601
  try {
27083
- const stat = await fs11.stat(path35.join(searchPath, rel));
27602
+ const stat = await fs11.stat(path36.join(searchPath, rel));
27084
27603
  return { rel, mtime: stat.mtimeMs };
27085
27604
  } catch {
27086
27605
  return { rel, mtime: 0 };
@@ -27129,7 +27648,7 @@ var GrepTool = class extends BaseTool {
27129
27648
  };
27130
27649
  async execute(input, _options) {
27131
27650
  const pattern = input["pattern"];
27132
- const searchPath = input["path"] ? path35.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
27651
+ const searchPath = input["path"] ? path36.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
27133
27652
  const globPattern = input["glob"];
27134
27653
  const outputMode = input["output_mode"] ?? "content";
27135
27654
  const context = input["context"] ?? 0;
@@ -27183,12 +27702,12 @@ var GrepTool = class extends BaseTool {
27183
27702
  nodir: true
27184
27703
  });
27185
27704
  } catch {
27186
- files = [path35.relative(searchPath, searchPath) || "."];
27705
+ files = [path36.relative(searchPath, searchPath) || "."];
27187
27706
  }
27188
27707
  const results = [];
27189
27708
  let totalCount = 0;
27190
27709
  for (const rel of files) {
27191
- const abs = path35.join(searchPath, rel);
27710
+ const abs = path36.join(searchPath, rel);
27192
27711
  let content;
27193
27712
  try {
27194
27713
  content = await fs11.readFile(abs, "utf-8");
@@ -27229,92 +27748,6 @@ Total: ${totalCount} matches`);
27229
27748
  return results.join("\n");
27230
27749
  }
27231
27750
  };
27232
- var SsrfBlockedError = class extends Error {
27233
- constructor(message) {
27234
- super(message);
27235
- this.name = "SsrfBlockedError";
27236
- }
27237
- };
27238
- var ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
27239
- var MAX_REDIRECTS2 = 5;
27240
- function allowLocal() {
27241
- return process.env["CASCADE_ALLOW_LOCAL_FETCH"] === "1";
27242
- }
27243
- function isPrivateAddress(ip) {
27244
- const type = net.isIP(ip);
27245
- if (type === 4) return isPrivateIPv4(ip);
27246
- if (type === 6) return isPrivateIPv6(ip);
27247
- return true;
27248
- }
27249
- function isPrivateIPv4(ip) {
27250
- const parts = ip.split(".").map((p) => Number(p));
27251
- if (parts.length !== 4 || parts.some((p) => Number.isNaN(p) || p < 0 || p > 255)) return true;
27252
- const [a, b] = parts;
27253
- if (a === 0) return true;
27254
- if (a === 10) return true;
27255
- if (a === 127) return true;
27256
- if (a === 169 && b === 254) return true;
27257
- if (a === 172 && b >= 16 && b <= 31) return true;
27258
- if (a === 192 && b === 168) return true;
27259
- if (a === 100 && b >= 64 && b <= 127) return true;
27260
- if (a >= 224) return true;
27261
- return false;
27262
- }
27263
- function isPrivateIPv6(ip) {
27264
- const lower = ip.toLowerCase().replace(/^\[|\]$/g, "");
27265
- if (lower === "::1" || lower === "::") return true;
27266
- if (lower.startsWith("fe80")) return true;
27267
- if (lower.startsWith("fc") || lower.startsWith("fd")) return true;
27268
- const mapped = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(lower);
27269
- if (mapped) return isPrivateIPv4(mapped[1]);
27270
- return false;
27271
- }
27272
- async function assertPublicUrl(rawUrl) {
27273
- let url;
27274
- try {
27275
- url = new URL(rawUrl);
27276
- } catch {
27277
- throw new SsrfBlockedError(`Invalid URL: ${rawUrl}`);
27278
- }
27279
- if (!ALLOWED_PROTOCOLS.has(url.protocol)) {
27280
- throw new SsrfBlockedError(`Blocked URL scheme "${url.protocol}" \u2014 only http and https are allowed.`);
27281
- }
27282
- if (allowLocal()) return url;
27283
- const host = url.hostname.replace(/^\[|\]$/g, "");
27284
- if (net.isIP(host)) {
27285
- if (isPrivateAddress(host)) {
27286
- throw new SsrfBlockedError(`Blocked request to non-public address ${host}.`);
27287
- }
27288
- return url;
27289
- }
27290
- if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local")) {
27291
- throw new SsrfBlockedError(`Blocked request to local hostname "${host}".`);
27292
- }
27293
- let addresses;
27294
- try {
27295
- const records = await dns2.lookup(host, { all: true });
27296
- addresses = records.map((r) => r.address);
27297
- } catch {
27298
- throw new SsrfBlockedError(`Could not resolve host "${host}".`);
27299
- }
27300
- if (addresses.length === 0 || addresses.some((addr) => isPrivateAddress(addr))) {
27301
- throw new SsrfBlockedError(`Blocked request to "${host}" \u2014 resolves to a non-public address.`);
27302
- }
27303
- return url;
27304
- }
27305
- async function safeFetch(rawUrl, init = {}) {
27306
- let currentUrl = (await assertPublicUrl(rawUrl)).toString();
27307
- for (let i = 0; i <= MAX_REDIRECTS2; i++) {
27308
- const resp = await fetch(currentUrl, { ...init, redirect: "manual" });
27309
- if (resp.status < 300 || resp.status >= 400) return resp;
27310
- const location = resp.headers.get("location");
27311
- if (!location) return resp;
27312
- const next = new URL(location, currentUrl);
27313
- await assertPublicUrl(next.toString());
27314
- currentUrl = next.toString();
27315
- }
27316
- throw new SsrfBlockedError(`Too many redirects (>${MAX_REDIRECTS2}).`);
27317
- }
27318
27751
 
27319
27752
  // src/tools/web-fetch.ts
27320
27753
  var MAX_CHARS = 5e4;
@@ -27587,10 +28020,10 @@ var ToolRegistry = class extends EventEmitter {
27587
28020
  }
27588
28021
  isIgnored(filePath) {
27589
28022
  if (!filePath) return false;
27590
- const abs = path35.resolve(this.workspaceRoot, filePath);
27591
- const rel = path35.relative(this.workspaceRoot, abs);
27592
- if (!rel || rel.startsWith("..") || path35.isAbsolute(rel)) return true;
27593
- const posixRel = rel.split(path35.sep).join("/");
28023
+ const abs = path36.resolve(this.workspaceRoot, filePath);
28024
+ const rel = path36.relative(this.workspaceRoot, abs);
28025
+ if (!rel || rel.startsWith("..") || path36.isAbsolute(rel)) return true;
28026
+ const posixRel = rel.split(path36.sep).join("/");
27594
28027
  return this.ignoreMatcher.ignores(posixRel);
27595
28028
  }
27596
28029
  };
@@ -27824,22 +28257,22 @@ var FileMcpOAuthStore = class {
27824
28257
  filePath;
27825
28258
  load() {
27826
28259
  try {
27827
- return JSON.parse(fs32.readFileSync(this.filePath, "utf-8"));
28260
+ return JSON.parse(fs33.readFileSync(this.filePath, "utf-8"));
27828
28261
  } catch {
27829
28262
  return void 0;
27830
28263
  }
27831
28264
  }
27832
28265
  save(state) {
27833
- fs32.mkdirSync(path35.dirname(this.filePath), { recursive: true, mode: 448 });
27834
- fs32.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
28266
+ fs33.mkdirSync(path36.dirname(this.filePath), { recursive: true, mode: 448 });
28267
+ fs33.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
27835
28268
  try {
27836
- fs32.chmodSync(this.filePath, 384);
28269
+ fs33.chmodSync(this.filePath, 384);
27837
28270
  } catch {
27838
28271
  }
27839
28272
  }
27840
28273
  clear() {
27841
28274
  try {
27842
- fs32.rmSync(this.filePath);
28275
+ fs33.rmSync(this.filePath);
27843
28276
  } catch {
27844
28277
  }
27845
28278
  }
@@ -28659,7 +29092,7 @@ var TaskAnalyzer = class {
28659
29092
 
28660
29093
  // src/core/router/model-performance-tracker.ts
28661
29094
  init_pricing();
28662
- var DEFAULT_STATS_FILE = path35.join(os10.homedir(), ".cascade", "model-perf.json");
29095
+ var DEFAULT_STATS_FILE = path36.join(os10.homedir(), ".cascade", "model-perf.json");
28663
29096
  var ModelPerformanceTracker = class _ModelPerformanceTracker {
28664
29097
  stats = /* @__PURE__ */ new Map();
28665
29098
  featureStats = /* @__PURE__ */ new Map();
@@ -28699,7 +29132,7 @@ var ModelPerformanceTracker = class _ModelPerformanceTracker {
28699
29132
  async save() {
28700
29133
  if (this.readOnly) return;
28701
29134
  try {
28702
- await fs11.mkdir(path35.dirname(this.statsFile), { recursive: true });
29135
+ await fs11.mkdir(path36.dirname(this.statsFile), { recursive: true });
28703
29136
  const modelsObj = {};
28704
29137
  const featuresObj = {};
28705
29138
  for (const [key, stat] of this.stats) modelsObj[key] = stat;
@@ -29332,7 +29765,7 @@ Required capability: ${description.slice(0, 300)}`;
29332
29765
  * any dangerous action, so a silently-reloaded tool can't act without approval. */
29333
29766
  async loadPersistedTools() {
29334
29767
  if (!this.workspacePath || !this.persistEnabled) return;
29335
- const file = path35.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
29768
+ const file = path36.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
29336
29769
  try {
29337
29770
  const raw = await fs11.readFile(file, "utf-8");
29338
29771
  const specs = JSON.parse(raw);
@@ -29356,8 +29789,8 @@ Required capability: ${description.slice(0, 300)}`;
29356
29789
  }
29357
29790
  async persist() {
29358
29791
  if (!this.workspacePath || !this.persistEnabled) return;
29359
- const dir = path35.join(this.workspacePath, ".cascade");
29360
- const file = path35.join(dir, DYNAMIC_TOOLS_FILE);
29792
+ const dir = path36.join(this.workspacePath, ".cascade");
29793
+ const file = path36.join(dir, DYNAMIC_TOOLS_FILE);
29361
29794
  try {
29362
29795
  await fs11.mkdir(dir, { recursive: true });
29363
29796
  await fs11.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
@@ -29377,12 +29810,12 @@ var WorldStateDB = class {
29377
29810
  constructor(workspacePath, debugMode = false) {
29378
29811
  this.workspacePath = workspacePath;
29379
29812
  this.debugMode = debugMode;
29380
- const cascadeDir = path35.join(workspacePath, ".cascade");
29381
- if (!fs32.existsSync(cascadeDir)) {
29382
- fs32.mkdirSync(cascadeDir, { recursive: true });
29813
+ const cascadeDir = path36.join(workspacePath, ".cascade");
29814
+ if (!fs33.existsSync(cascadeDir)) {
29815
+ fs33.mkdirSync(cascadeDir, { recursive: true });
29383
29816
  }
29384
- this.keyPath = path35.join(cascadeDir, "world_state.key");
29385
- this.dbPath = path35.join(cascadeDir, "world_state.db");
29817
+ this.keyPath = path36.join(cascadeDir, "world_state.key");
29818
+ this.dbPath = path36.join(cascadeDir, "world_state.db");
29386
29819
  this.initEncryptionKey();
29387
29820
  this.db = new Database2(this.dbPath);
29388
29821
  this.db.pragma("journal_mode = WAL");
@@ -29439,11 +29872,11 @@ var WorldStateDB = class {
29439
29872
  ).run(e, r, row.encrypted_value, row.source_worker, row.timestamp, validTo, change);
29440
29873
  }
29441
29874
  initEncryptionKey() {
29442
- if (fs32.existsSync(this.keyPath)) {
29443
- this.encryptionKey = fs32.readFileSync(this.keyPath);
29875
+ if (fs33.existsSync(this.keyPath)) {
29876
+ this.encryptionKey = fs33.readFileSync(this.keyPath);
29444
29877
  } else {
29445
29878
  this.encryptionKey = crypto.randomBytes(32);
29446
- fs32.writeFileSync(this.keyPath, this.encryptionKey);
29879
+ fs33.writeFileSync(this.keyPath, this.encryptionKey);
29447
29880
  }
29448
29881
  }
29449
29882
  encrypt(text) {
@@ -29729,9 +30162,9 @@ var WorldStateDB = class {
29729
30162
  dumpDebugIfNeeded() {
29730
30163
  if (!this.debugMode) return;
29731
30164
  try {
29732
- const dumpPath = path35.join(os10.tmpdir(), "cascade_world_state_debug.json");
30165
+ const dumpPath = path36.join(os10.tmpdir(), "cascade_world_state_debug.json");
29733
30166
  const entries = this.getAllEntries();
29734
- fs32.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
30167
+ fs33.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
29735
30168
  } catch (err) {
29736
30169
  console.error("Failed to dump debug world state", err);
29737
30170
  }
@@ -29788,7 +30221,7 @@ var ResumeStore = class {
29788
30221
  createdAt: checkpoint.createdAt ?? new Date(timestamp).toISOString()
29789
30222
  };
29790
30223
  await promises.mkdir(this.dir, { recursive: true });
29791
- const finalPath = path35.join(this.dir, `${timestamp}-${safeId(record.taskId)}.json`);
30224
+ const finalPath = path36.join(this.dir, `${timestamp}-${safeId(record.taskId)}.json`);
29792
30225
  const tempPath = `${finalPath}.${process.pid}.tmp`;
29793
30226
  await promises.writeFile(tempPath, JSON.stringify(record, null, 2), "utf-8");
29794
30227
  await promises.rename(tempPath, finalPath);
@@ -29811,7 +30244,7 @@ var ResumeStore = class {
29811
30244
  const checkpoints = [];
29812
30245
  for (const candidate of candidates2) {
29813
30246
  try {
29814
- const raw = await promises.readFile(path35.join(this.dir, candidate.name), "utf-8");
30247
+ const raw = await promises.readFile(path36.join(this.dir, candidate.name), "utf-8");
29815
30248
  const parsed = JSON.parse(raw);
29816
30249
  if (isCheckpoint(parsed)) checkpoints.push(parsed);
29817
30250
  } catch {
@@ -29850,7 +30283,7 @@ var ResumeStore = class {
29850
30283
  const source = entries.find((name) => FILE_PATTERN.exec(name)?.[2] === wanted);
29851
30284
  if (!source) return null;
29852
30285
  const claimId = `${this.now()}-${safeId(target.taskId)}`;
29853
- await promises.rename(path35.join(this.dir, source), path35.join(this.dir, `${claimId}${CLAIM_SUFFIX}`));
30286
+ await promises.rename(path36.join(this.dir, source), path36.join(this.dir, `${claimId}${CLAIM_SUFFIX}`));
29854
30287
  return { checkpoint: target, claimId };
29855
30288
  } catch {
29856
30289
  return null;
@@ -29860,8 +30293,8 @@ var ResumeStore = class {
29860
30293
  async release(claimId) {
29861
30294
  try {
29862
30295
  await promises.rename(
29863
- path35.join(this.dir, `${claimId}${CLAIM_SUFFIX}`),
29864
- path35.join(this.dir, `${claimId}.json`)
30296
+ path36.join(this.dir, `${claimId}${CLAIM_SUFFIX}`),
30297
+ path36.join(this.dir, `${claimId}.json`)
29865
30298
  );
29866
30299
  } catch {
29867
30300
  }
@@ -29869,7 +30302,7 @@ var ResumeStore = class {
29869
30302
  /** Discard a claim for good — the resumed run has taken ownership. */
29870
30303
  async settle(claimId) {
29871
30304
  try {
29872
- await promises.rm(path35.join(this.dir, `${claimId}${CLAIM_SUFFIX}`), { force: true });
30305
+ await promises.rm(path36.join(this.dir, `${claimId}${CLAIM_SUFFIX}`), { force: true });
29873
30306
  } catch {
29874
30307
  }
29875
30308
  }
@@ -29898,8 +30331,8 @@ var ResumeStore = class {
29898
30331
  const stamp2 = Number(/^(\d+)-/.exec(name)?.[1]);
29899
30332
  if (!Number.isFinite(stamp2) || stamp2 >= cutoff) continue;
29900
30333
  await promises.rename(
29901
- path35.join(this.dir, name),
29902
- path35.join(this.dir, `${name.slice(0, -CLAIM_SUFFIX.length)}.json`)
30334
+ path36.join(this.dir, name),
30335
+ path36.join(this.dir, `${name.slice(0, -CLAIM_SUFFIX.length)}.json`)
29903
30336
  );
29904
30337
  reclaimed++;
29905
30338
  }
@@ -29915,7 +30348,7 @@ var ResumeStore = class {
29915
30348
  await Promise.all(entries.map(async (name) => {
29916
30349
  const match = FILE_PATTERN.exec(name);
29917
30350
  if (match?.[2] === wanted) {
29918
- await promises.rm(path35.join(this.dir, name), { force: true });
30351
+ await promises.rm(path36.join(this.dir, name), { force: true });
29919
30352
  }
29920
30353
  }));
29921
30354
  } catch {
@@ -29930,7 +30363,7 @@ var ResumeStore = class {
29930
30363
  const doomed = files.filter((f, index) => f.timestamp < cutoff || index >= this.maxCheckpoints);
29931
30364
  const staleTemps = entries.filter((name) => name.endsWith(".tmp")).filter((name) => (Number(FILE_PATTERN.exec(name.replace(/\.\d+\.tmp$/, ".json"))?.[1]) || 0) < cutoff);
29932
30365
  await Promise.all([...doomed.map((f) => f.name), ...staleTemps].map(
29933
- (name) => promises.rm(path35.join(this.dir, name), { force: true })
30366
+ (name) => promises.rm(path36.join(this.dir, name), { force: true })
29934
30367
  ));
29935
30368
  } catch {
29936
30369
  }
@@ -30307,7 +30740,7 @@ var WorkspaceIndex = class {
30307
30740
  isIgnored;
30308
30741
  chunker;
30309
30742
  constructor(opts) {
30310
- this.root = path35.resolve(opts.root);
30743
+ this.root = path36.resolve(opts.root);
30311
30744
  this.db = opts.db;
30312
30745
  this.namespace = opts.namespace ?? "code";
30313
30746
  this.extensions = new Set((opts.extensions ?? DEFAULT_EXTENSIONS).map((e) => e.replace(/^\./, "").toLowerCase()));
@@ -30339,14 +30772,14 @@ var WorkspaceIndex = class {
30339
30772
  return;
30340
30773
  }
30341
30774
  for (const entry of entries) {
30342
- const abs = path35.join(dir, entry.name);
30775
+ const abs = path36.join(dir, entry.name);
30343
30776
  if (entry.isDirectory()) {
30344
30777
  if (HARD_SKIP_DIRS.has(entry.name) || this.isIgnored(abs)) continue;
30345
30778
  await walk(abs);
30346
30779
  } else if (entry.isFile()) {
30347
- const ext = path35.extname(entry.name).slice(1).toLowerCase();
30780
+ const ext = path36.extname(entry.name).slice(1).toLowerCase();
30348
30781
  if (!this.extensions.has(ext) || this.isIgnored(abs)) continue;
30349
- out.push(path35.relative(this.root, abs));
30782
+ out.push(path36.relative(this.root, abs));
30350
30783
  }
30351
30784
  }
30352
30785
  };
@@ -30376,7 +30809,7 @@ var WorkspaceIndex = class {
30376
30809
  const entries = [];
30377
30810
  for (const rel of rels) {
30378
30811
  try {
30379
- const abs = path35.join(this.root, rel);
30812
+ const abs = path36.join(this.root, rel);
30380
30813
  const stat = await fs11.stat(abs);
30381
30814
  if (stat.size > this.maxFileBytes) continue;
30382
30815
  const buf = await fs11.readFile(abs);
@@ -30866,13 +31299,13 @@ var Cascade = class _Cascade extends EventEmitter {
30866
31299
  this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
30867
31300
  try {
30868
31301
  this.router.setDeadModelStore(new DeadModelStore(
30869
- fileDeadModelPersistence(path35.join(workspacePath, ".cascade", "dead-models.json"))
31302
+ fileDeadModelPersistence(path36.join(workspacePath, ".cascade", "dead-models.json"))
30870
31303
  ));
30871
31304
  } catch {
30872
31305
  }
30873
31306
  try {
30874
31307
  this.resumeStore = new ResumeStore({
30875
- dir: path35.join(workspacePath, ".cascade", "resume")
31308
+ dir: path36.join(workspacePath, ".cascade", "resume")
30876
31309
  });
30877
31310
  } catch {
30878
31311
  }
@@ -30960,8 +31393,8 @@ var Cascade = class _Cascade extends EventEmitter {
30960
31393
  this.emit("log", { level: "warn", message: "Code index enabled but no embeddings-capable provider is configured \u2014 skipping." });
30961
31394
  return;
30962
31395
  }
30963
- const dbPath = ci.dbPath || path35.join(this.workspacePath, ".cascade", "code-index.db");
30964
- fs32.mkdirSync(path35.dirname(dbPath), { recursive: true });
31396
+ const dbPath = ci.dbPath || path36.join(this.workspacePath, ".cascade", "code-index.db");
31397
+ fs33.mkdirSync(path36.dirname(dbPath), { recursive: true });
30965
31398
  const db = new Database2(dbPath);
30966
31399
  db.pragma("journal_mode = WAL");
30967
31400
  const complete = chatCompleterFromProviders(this.config.providers);
@@ -32429,14 +32862,14 @@ ${finalOutput}` : ""
32429
32862
  init_constants();
32430
32863
  var CLOUD_SESSION_FILE = "cloud-session.json";
32431
32864
  function globalDir(homeDir = os10.homedir()) {
32432
- return path35.join(homeDir, GLOBAL_CONFIG_DIR);
32865
+ return path36.join(homeDir, GLOBAL_CONFIG_DIR);
32433
32866
  }
32434
32867
  function cloudSessionPath(dir = globalDir()) {
32435
- return path35.join(dir, CLOUD_SESSION_FILE);
32868
+ return path36.join(dir, CLOUD_SESSION_FILE);
32436
32869
  }
32437
32870
  function loadCloudSession(dir = globalDir()) {
32438
32871
  try {
32439
- const raw = fs32.readFileSync(cloudSessionPath(dir), "utf-8");
32872
+ const raw = fs33.readFileSync(cloudSessionPath(dir), "utf-8");
32440
32873
  const parsed = JSON.parse(raw);
32441
32874
  if (!parsed?.refreshToken || !parsed?.serverUrl) return null;
32442
32875
  return parsed;
@@ -32445,17 +32878,17 @@ function loadCloudSession(dir = globalDir()) {
32445
32878
  }
32446
32879
  }
32447
32880
  function saveCloudSession(session, dir = globalDir()) {
32448
- fs32.mkdirSync(dir, { recursive: true, mode: 448 });
32881
+ fs33.mkdirSync(dir, { recursive: true, mode: 448 });
32449
32882
  const file = cloudSessionPath(dir);
32450
- fs32.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
32883
+ fs33.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
32451
32884
  try {
32452
- fs32.chmodSync(file, 384);
32885
+ fs33.chmodSync(file, 384);
32453
32886
  } catch {
32454
32887
  }
32455
32888
  }
32456
32889
  function clearCloudSession(dir = globalDir()) {
32457
32890
  try {
32458
- fs32.rmSync(cloudSessionPath(dir));
32891
+ fs33.rmSync(cloudSessionPath(dir));
32459
32892
  } catch {
32460
32893
  }
32461
32894
  }
@@ -32627,16 +33060,16 @@ var CloudClient = class _CloudClient {
32627
33060
  return (await this.refresh()).accessToken;
32628
33061
  }
32629
33062
  /** Authenticated request that refreshes once on a 401 (e.g. server-side revoke). */
32630
- async authed(path40, init = {}) {
32631
- const call = (token) => fetch(this.url(path40), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
33063
+ async authed(path41, init = {}) {
33064
+ const call = (token) => fetch(this.url(path41), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
32632
33065
  let res = await call(await this.accessToken());
32633
33066
  if (res.status === 401) res = await call((await this.refresh()).accessToken);
32634
33067
  if (!res.ok) throw new Error(`Request failed (${res.status}).`);
32635
33068
  const text = await res.text();
32636
33069
  return text ? JSON.parse(text) : {};
32637
33070
  }
32638
- authedGet(path40) {
32639
- return this.authed(path40);
33071
+ authedGet(path41) {
33072
+ return this.authed(path41);
32640
33073
  }
32641
33074
  async fetchMe(token) {
32642
33075
  const res = await fetch(this.url("/api/me"), { headers: { Authorization: `Bearer ${token}` } });
@@ -34410,9 +34843,9 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, s
34410
34843
  }
34411
34844
  } catch {
34412
34845
  }
34413
- const configPath = path35.join(workspacePath, ".cascade", "config.json");
34846
+ const configPath = path36.join(workspacePath, ".cascade", "config.json");
34414
34847
  try {
34415
- await fs11.mkdir(path35.dirname(configPath), { recursive: true });
34848
+ await fs11.mkdir(path36.dirname(configPath), { recursive: true });
34416
34849
  await fs11.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
34417
34850
  } catch (err) {
34418
34851
  const msg = err instanceof Error ? err.message : String(err);
@@ -34464,9 +34897,9 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, s
34464
34897
  if (msg.includes("non-text parts")) return;
34465
34898
  originalLog(...args);
34466
34899
  };
34467
- const store = new MemoryStore(path35.join(workspacePath, CASCADE_DB_FILE));
34900
+ const store = new MemoryStore(path36.join(workspacePath, CASCADE_DB_FILE));
34468
34901
  storeRef.current = store;
34469
- globalStoreRef.current = new MemoryStore(path35.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
34902
+ globalStoreRef.current = new MemoryStore(path36.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
34470
34903
  const identityRows = store.listIdentities().map((i) => ({ id: i.id, name: i.name, isDefault: i.isDefault }));
34471
34904
  setIdentities(identityRows);
34472
34905
  let initialIdentityId = config.defaultIdentityId ?? identityRows.find((i) => i.isDefault)?.id ?? identityRows[0]?.id;
@@ -34546,7 +34979,7 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, s
34546
34979
  onThemeChange: (name) => setTheme(getTheme(name)),
34547
34980
  onExport: async (fmt) => {
34548
34981
  const stamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
34549
- const exportPath = path35.join(workspacePath, `cascade-export-${stamp2}.${fmt === "json" ? "json" : "md"}`);
34982
+ const exportPath = path36.join(workspacePath, `cascade-export-${stamp2}.${fmt === "json" ? "json" : "md"}`);
34550
34983
  if (fmt === "json") {
34551
34984
  await fs11.writeFile(exportPath, JSON.stringify({ sessionId: sessionIdRef.current, messages: state.messages }, null, 2), "utf-8");
34552
34985
  } else {
@@ -35466,7 +35899,7 @@ function stringifySlashOutput(val) {
35466
35899
  }
35467
35900
  async function searchSessionsAndMessages(query, workspacePath) {
35468
35901
  if (!query) return "Usage: /search <query>";
35469
- const dbPath = path35.join(workspacePath, CASCADE_DB_FILE);
35902
+ const dbPath = path36.join(workspacePath, CASCADE_DB_FILE);
35470
35903
  try {
35471
35904
  await fs11.access(dbPath);
35472
35905
  } catch {
@@ -35500,7 +35933,7 @@ async function searchSessionsAndMessages(query, workspacePath) {
35500
35933
  async function diagnoseRuntime(config, workspacePath) {
35501
35934
  const providers = config.providers.map((p) => `${p.type}${p.apiKey ? " (key set)" : " (no key)"}`).join("\n");
35502
35935
  const models = [`T1: ${config.models.t1 ?? "default"}`, `T2: ${config.models.t2 ?? "default"}`, `T3: ${config.models.t3 ?? "default"}`].join("\n");
35503
- const store = new MemoryStore(path35.join(workspacePath, CASCADE_DB_FILE));
35936
+ const store = new MemoryStore(path36.join(workspacePath, CASCADE_DB_FILE));
35504
35937
  try {
35505
35938
  const sessions2 = store.listSessions(void 0, 3);
35506
35939
  return [
@@ -35519,7 +35952,7 @@ async function diagnoseRuntime(config, workspacePath) {
35519
35952
  }
35520
35953
  async function showRecentLogs(args, workspacePath) {
35521
35954
  const limit = Number.parseInt(args[0] ?? "10", 10) || 10;
35522
- const store = new MemoryStore(path35.join(workspacePath, CASCADE_DB_FILE));
35955
+ const store = new MemoryStore(path36.join(workspacePath, CASCADE_DB_FILE));
35523
35956
  try {
35524
35957
  const logs = store.listRuntimeNodeLogs(void 0, void 0, limit);
35525
35958
  if (!logs.length) return "No recent runtime logs.";
@@ -35531,7 +35964,7 @@ async function showRecentLogs(args, workspacePath) {
35531
35964
  async function loadSessionSnapshot(args, workspacePath) {
35532
35965
  const sessionId = args[0];
35533
35966
  if (!sessionId) return "Usage: /resume <sessionId>";
35534
- const store = new MemoryStore(path35.join(workspacePath, CASCADE_DB_FILE));
35967
+ const store = new MemoryStore(path36.join(workspacePath, CASCADE_DB_FILE));
35535
35968
  try {
35536
35969
  const session = store.getSession(sessionId);
35537
35970
  if (!session) return `Session not found: ${sessionId}`;
@@ -35951,9 +36384,9 @@ function SetupWizard({ workspacePath, onComplete }) {
35951
36384
  ...anyAuto ? { cascadeAuto: true } : {}
35952
36385
  };
35953
36386
  const config = CascadeConfigSchema.parse(rawConfig);
35954
- const configDir = path35.join(workspacePath, ".cascade");
36387
+ const configDir = path36.join(workspacePath, ".cascade");
35955
36388
  await fs11.mkdir(configDir, { recursive: true });
35956
- const configPath = path35.join(workspacePath, CASCADE_CONFIG_FILE);
36389
+ const configPath = path36.join(workspacePath, CASCADE_CONFIG_FILE);
35957
36390
  await fs11.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
35958
36391
  savedConfigRef.current = config;
35959
36392
  dispatchRef.current({ type: "GO_DONE" });
@@ -36294,14 +36727,14 @@ function printTelemetryBanner() {
36294
36727
  async function initCommand(workspacePath = process.cwd()) {
36295
36728
  const spin = ora({ text: "Initializing Cascade project\u2026", color: "magenta" }).start();
36296
36729
  try {
36297
- const configDir = path35.join(workspacePath, ".cascade");
36730
+ const configDir = path36.join(workspacePath, ".cascade");
36298
36731
  await fs11.mkdir(configDir, { recursive: true });
36299
- const mdPath = path35.join(workspacePath, "CASCADE.md");
36732
+ const mdPath = path36.join(workspacePath, "CASCADE.md");
36300
36733
  if (!await fileExists(mdPath)) {
36301
36734
  await createDefaultCascadeMd(workspacePath);
36302
36735
  spin.succeed(chalk8.green("Created CASCADE.md"));
36303
36736
  }
36304
- const ignorePath = path35.join(workspacePath, ".cascadeignore");
36737
+ const ignorePath = path36.join(workspacePath, ".cascadeignore");
36305
36738
  if (!await fileExists(ignorePath)) {
36306
36739
  await createDefaultIgnoreFile(workspacePath);
36307
36740
  spin.succeed(chalk8.green("Created .cascadeignore"));
@@ -36310,7 +36743,7 @@ async function initCommand(workspacePath = process.cwd()) {
36310
36743
  console.log();
36311
36744
  console.log(chalk8.magenta(" \u25C8 Cascade AI \u2014 Project initialized"));
36312
36745
  console.log();
36313
- const configPath = path35.join(workspacePath, CASCADE_CONFIG_FILE);
36746
+ const configPath = path36.join(workspacePath, CASCADE_CONFIG_FILE);
36314
36747
  if (await fileExists(configPath)) {
36315
36748
  console.log(chalk8.yellow(" .cascade/config.json already exists \u2014 launching wizard to reconfigure."));
36316
36749
  console.log();
@@ -36461,8 +36894,8 @@ function fromEnv(env) {
36461
36894
  return out;
36462
36895
  }
36463
36896
  async function fromClaudeCode(home, env) {
36464
- const dir = str(env["CLAUDE_CONFIG_DIR"]) ?? path35.join(home, ".claude");
36465
- const file = path35.join(dir, ".credentials.json");
36897
+ const dir = str(env["CLAUDE_CONFIG_DIR"]) ?? path36.join(home, ".claude");
36898
+ const file = path36.join(dir, ".credentials.json");
36466
36899
  const data = await readJson(file);
36467
36900
  if (!data) return [];
36468
36901
  const found = [];
@@ -36486,7 +36919,7 @@ async function fromClaudeCode(home, env) {
36486
36919
  return found;
36487
36920
  }
36488
36921
  async function fromCodex(home) {
36489
- const file = path35.join(home, ".codex", "auth.json");
36922
+ const file = path36.join(home, ".codex", "auth.json");
36490
36923
  const data = await readJson(file);
36491
36924
  if (!data) return [];
36492
36925
  const apiKey = str(data["OPENAI_API_KEY"]);
@@ -36509,7 +36942,7 @@ async function fromCodex(home) {
36509
36942
  return [];
36510
36943
  }
36511
36944
  async function fromGemini(home) {
36512
- const file = path35.join(home, ".gemini", "oauth_creds.json");
36945
+ const file = path36.join(home, ".gemini", "oauth_creds.json");
36513
36946
  const data = await readJson(file);
36514
36947
  const accessToken = str(data?.["access_token"]);
36515
36948
  if (!accessToken) return [];
@@ -36525,7 +36958,7 @@ async function fromGemini(home) {
36525
36958
  }
36526
36959
  async function fromCopilot(home) {
36527
36960
  for (const name of ["apps.json", "hosts.json"]) {
36528
- const file = path35.join(home, ".config", "github-copilot", name);
36961
+ const file = path36.join(home, ".config", "github-copilot", name);
36529
36962
  const data = await readJson(file);
36530
36963
  if (!data) continue;
36531
36964
  for (const value of Object.values(data)) {
@@ -36826,7 +37259,7 @@ async function doctorCommand() {
36826
37259
  checks.push({
36827
37260
  label: "Cascade config",
36828
37261
  ok: true,
36829
- detail: `Loaded ${path35.join(process.cwd(), CASCADE_CONFIG_FILE)}`
37262
+ detail: `Loaded ${path36.join(process.cwd(), CASCADE_CONFIG_FILE)}`
36830
37263
  });
36831
37264
  const providers = [
36832
37265
  { type: "anthropic", name: "Anthropic" },
@@ -36936,7 +37369,7 @@ async function checkEndpoint(url) {
36936
37369
  }
36937
37370
  }
36938
37371
  async function indexCommand(dirPath) {
36939
- const workspace = path35.resolve(dirPath || process.cwd());
37372
+ const workspace = path36.resolve(dirPath || process.cwd());
36940
37373
  const cm = new ConfigManager(workspace);
36941
37374
  await cm.load();
36942
37375
  const config = cm.getConfig();
@@ -36947,8 +37380,8 @@ async function indexCommand(dirPath) {
36947
37380
  process.exitCode = 1;
36948
37381
  return;
36949
37382
  }
36950
- const dbPath = config.codeIndex?.dbPath || path35.join(workspace, ".cascade", "code-index.db");
36951
- fs32.mkdirSync(path35.dirname(dbPath), { recursive: true });
37383
+ const dbPath = config.codeIndex?.dbPath || path36.join(workspace, ".cascade", "code-index.db");
37384
+ fs33.mkdirSync(path36.dirname(dbPath), { recursive: true });
36952
37385
  const db = new Database2(dbPath);
36953
37386
  db.pragma("journal_mode = WAL");
36954
37387
  const complete = chatCompleterFromProviders(config.providers);
@@ -37480,7 +37913,7 @@ function openBrowser2(url) {
37480
37913
  }
37481
37914
  function storePathFor(name) {
37482
37915
  const safe = name.replace(/[^a-z0-9._-]/gi, "_").slice(0, 64) || "server";
37483
- return path35.join(os10.homedir(), ".cascade-ai", "mcp-oauth", `${safe}.json`);
37916
+ return path36.join(os10.homedir(), ".cascade-ai", "mcp-oauth", `${safe}.json`);
37484
37917
  }
37485
37918
  function defaultName(url) {
37486
37919
  try {
@@ -37867,8 +38300,8 @@ function replaceInPlace(target, source) {
37867
38300
  }
37868
38301
  function writeConfigFile(configPath, config) {
37869
38302
  try {
37870
- fs32.mkdirSync(path35.dirname(configPath), { recursive: true });
37871
- fs32.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
38303
+ fs33.mkdirSync(path36.dirname(configPath), { recursive: true });
38304
+ fs33.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
37872
38305
  return { ok: true };
37873
38306
  } catch (err) {
37874
38307
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
@@ -38284,7 +38717,7 @@ function aggregateCostStats(sessions2, opts = {}) {
38284
38717
  }
38285
38718
 
38286
38719
  // src/dashboard/server.ts
38287
- var __dirname$1 = path35.dirname(fileURLToPath(import.meta.url));
38720
+ var __dirname$1 = path36.dirname(fileURLToPath(import.meta.url));
38288
38721
  var ORPHANED_ESCALATION_GRACE_MS = 3e4;
38289
38722
  var MASK = "***";
38290
38723
  function masked(key, value) {
@@ -38603,7 +39036,7 @@ var DashboardServer = class {
38603
39036
  * silently and still typecheck.
38604
39037
  */
38605
39038
  persistConfig(config) {
38606
- const result = writeConfigFile(path35.join(this.workspacePath, CASCADE_CONFIG_FILE), config);
39039
+ const result = writeConfigFile(path36.join(this.workspacePath, CASCADE_CONFIG_FILE), config);
38607
39040
  if (!result.ok) console.warn(`[dashboard] Failed to persist config: ${result.error}`);
38608
39041
  return result;
38609
39042
  }
@@ -38622,7 +39055,7 @@ var DashboardServer = class {
38622
39055
  */
38623
39056
  syncGlobalCredentials() {
38624
39057
  try {
38625
- saveGlobalCredentials(path35.join(os10.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
39058
+ saveGlobalCredentials(path36.join(os10.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
38626
39059
  } catch (err) {
38627
39060
  console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
38628
39061
  }
@@ -38637,15 +39070,15 @@ var DashboardServer = class {
38637
39070
  resolveDashboardSecret() {
38638
39071
  const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
38639
39072
  if (fromConfig) return fromConfig;
38640
- const secretPath = path35.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
39073
+ const secretPath = path36.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
38641
39074
  try {
38642
- if (fs32.existsSync(secretPath)) {
38643
- const existing = fs32.readFileSync(secretPath, "utf-8").trim();
39075
+ if (fs33.existsSync(secretPath)) {
39076
+ const existing = fs33.readFileSync(secretPath, "utf-8").trim();
38644
39077
  if (existing.length >= 16) return existing;
38645
39078
  }
38646
39079
  const generated = randomUUID();
38647
- fs32.mkdirSync(path35.dirname(secretPath), { recursive: true });
38648
- fs32.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
39080
+ fs33.mkdirSync(path36.dirname(secretPath), { recursive: true });
39081
+ fs33.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
38649
39082
  if (this.config.dashboard.auth) {
38650
39083
  console.warn(
38651
39084
  `Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
@@ -38672,7 +39105,7 @@ var DashboardServer = class {
38672
39105
  // ── Setup ─────────────────────────────────────
38673
39106
  getGlobalStore() {
38674
39107
  if (!this.globalStore) {
38675
- const globalDbPath = path35.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
39108
+ const globalDbPath = path36.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
38676
39109
  this.globalStore = new MemoryStore(globalDbPath);
38677
39110
  }
38678
39111
  return this.globalStore;
@@ -39052,12 +39485,12 @@ ${prompt}`;
39052
39485
  }
39053
39486
  }
39054
39487
  watchRuntimeChanges() {
39055
- const workspaceDbPath = path35.join(this.workspacePath, CASCADE_DB_FILE);
39056
- const globalDbPath = path35.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
39488
+ const workspaceDbPath = path36.join(this.workspacePath, CASCADE_DB_FILE);
39489
+ const globalDbPath = path36.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
39057
39490
  const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
39058
39491
  for (const watchPath of watchPaths) {
39059
- if (!fs32.existsSync(watchPath)) continue;
39060
- fs32.watchFile(watchPath, { interval: 3e3 }, () => {
39492
+ if (!fs33.existsSync(watchPath)) continue;
39493
+ fs33.watchFile(watchPath, { interval: 3e3 }, () => {
39061
39494
  this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
39062
39495
  });
39063
39496
  }
@@ -39179,7 +39612,7 @@ ${prompt}`;
39179
39612
  const sessionId = req.params.id;
39180
39613
  this.store.deleteSession(sessionId);
39181
39614
  this.store.deleteRuntimeSession(sessionId);
39182
- const globalDbPath = path35.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
39615
+ const globalDbPath = path36.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
39183
39616
  const globalStore = new MemoryStore(globalDbPath);
39184
39617
  try {
39185
39618
  globalStore.deleteRuntimeSession(sessionId);
@@ -39272,7 +39705,7 @@ ${prompt}`;
39272
39705
  });
39273
39706
  this.app.delete("/api/sessions", auth2, (req, res) => {
39274
39707
  const body = req.body;
39275
- const globalDbPath = path35.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
39708
+ const globalDbPath = path36.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
39276
39709
  if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
39277
39710
  const globalStore = new MemoryStore(globalDbPath);
39278
39711
  try {
@@ -39295,7 +39728,7 @@ ${prompt}`;
39295
39728
  });
39296
39729
  this.app.delete("/api/runtime", auth2, (_req, res) => {
39297
39730
  this.store.deleteAllRuntimeNodes();
39298
- const globalDbPath = path35.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
39731
+ const globalDbPath = path36.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
39299
39732
  const globalStore = new MemoryStore(globalDbPath);
39300
39733
  try {
39301
39734
  globalStore.deleteAllRuntimeNodes();
@@ -39380,12 +39813,12 @@ ${prompt}`;
39380
39813
  if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
39381
39814
  if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
39382
39815
  try {
39383
- const configPath = path35.join(this.workspacePath, CASCADE_CONFIG_FILE);
39384
- const existing = fs32.existsSync(configPath) ? JSON.parse(fs32.readFileSync(configPath, "utf-8")) : {};
39816
+ const configPath = path36.join(this.workspacePath, CASCADE_CONFIG_FILE);
39817
+ const existing = fs33.existsSync(configPath) ? JSON.parse(fs33.readFileSync(configPath, "utf-8")) : {};
39385
39818
  const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
39386
39819
  const tmp = configPath + ".tmp";
39387
- fs32.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
39388
- fs32.renameSync(tmp, configPath);
39820
+ fs33.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
39821
+ fs33.renameSync(tmp, configPath);
39389
39822
  res.json({ ok: true });
39390
39823
  } catch (err) {
39391
39824
  res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
@@ -39413,7 +39846,7 @@ ${prompt}`;
39413
39846
  this.app.get("/api/runtime", auth2, (req, res) => {
39414
39847
  const scope = req.query["scope"] ?? "workspace";
39415
39848
  if (scope === "global") {
39416
- const globalDbPath = path35.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
39849
+ const globalDbPath = path36.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
39417
39850
  const globalStore = new MemoryStore(globalDbPath);
39418
39851
  try {
39419
39852
  res.json({
@@ -39754,13 +40187,13 @@ ${prompt}`;
39754
40187
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
39755
40188
  }
39756
40189
  });
39757
- const prodPath = path35.resolve(__dirname$1, "../web/dist");
39758
- const devPath = path35.resolve(__dirname$1, "../../web/dist");
39759
- const webDistPath = fs32.existsSync(prodPath) ? prodPath : devPath;
39760
- if (fs32.existsSync(webDistPath)) {
40190
+ const prodPath = path36.resolve(__dirname$1, "../web/dist");
40191
+ const devPath = path36.resolve(__dirname$1, "../../web/dist");
40192
+ const webDistPath = fs33.existsSync(prodPath) ? prodPath : devPath;
40193
+ if (fs33.existsSync(webDistPath)) {
39761
40194
  this.app.use(express.static(webDistPath));
39762
40195
  this.app.get("*", (_req, res) => {
39763
- res.sendFile(path35.join(webDistPath, "index.html"));
40196
+ res.sendFile(path36.join(webDistPath, "index.html"));
39764
40197
  });
39765
40198
  } else {
39766
40199
  this.app.get("/", (_req, res) => {
@@ -39781,7 +40214,7 @@ init_constants();
39781
40214
  async function dashboardCommand(config, workspacePath = process.cwd()) {
39782
40215
  const port = config.dashboard.port ?? DEFAULT_DASHBOARD_PORT;
39783
40216
  const spin = ora({ text: `Starting dashboard on port ${port}\u2026`, color: "magenta" }).start();
39784
- const store = new MemoryStore(path35.join(workspacePath, CASCADE_DB_FILE));
40217
+ const store = new MemoryStore(path36.join(workspacePath, CASCADE_DB_FILE));
39785
40218
  const server = new DashboardServer(config, store, workspacePath);
39786
40219
  server.watchRuntimeChanges();
39787
40220
  const gThis = globalThis;
@@ -39813,7 +40246,7 @@ init_constants();
39813
40246
  function makeIdentityCommand(workspacePath = process.cwd()) {
39814
40247
  const identity = new Command("identity").alias("id").description("Manage Cascade identities");
39815
40248
  identity.command("list").description("List all available identities").action(() => {
39816
- const store = new MemoryStore(path35.join(workspacePath, CASCADE_DB_FILE));
40249
+ const store = new MemoryStore(path36.join(workspacePath, CASCADE_DB_FILE));
39817
40250
  try {
39818
40251
  const identities = store.listIdentities();
39819
40252
  console.log(chalk8.bold("\n Identities:"));
@@ -39833,7 +40266,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
39833
40266
  }
39834
40267
  });
39835
40268
  identity.command("create <name>").description("Create a new identity").option("-d, --desc <text>", "Description of the identity").option("-s, --system <text>", "System prompt").option("--default", "Set as default identity").action((name, options) => {
39836
- const store = new MemoryStore(path35.join(workspacePath, CASCADE_DB_FILE));
40269
+ const store = new MemoryStore(path36.join(workspacePath, CASCADE_DB_FILE));
39837
40270
  try {
39838
40271
  if (options.default) {
39839
40272
  const existingDefault = store.getDefaultIdentity();
@@ -39859,7 +40292,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
39859
40292
  }
39860
40293
  });
39861
40294
  identity.command("set-default <name>").description("Set an identity as default by name or ID").action((query) => {
39862
- const store = new MemoryStore(path35.join(workspacePath, CASCADE_DB_FILE));
40295
+ const store = new MemoryStore(path36.join(workspacePath, CASCADE_DB_FILE));
39863
40296
  try {
39864
40297
  const identities = store.listIdentities();
39865
40298
  const match = identities.find((i) => i.id === query || i.name.toLowerCase() === query.toLowerCase());
@@ -40008,8 +40441,8 @@ async function exportCommand(options = {}) {
40008
40441
  let store;
40009
40442
  try {
40010
40443
  const workspacePath = options.workspacePath ?? process.cwd();
40011
- const workspaceDbPath = path35.join(workspacePath, CASCADE_DB_FILE);
40012
- const globalDbPath = path35.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
40444
+ const workspaceDbPath = path36.join(workspacePath, CASCADE_DB_FILE);
40445
+ const globalDbPath = path36.join(os10.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
40013
40446
  let dbPath = globalDbPath;
40014
40447
  try {
40015
40448
  await fs11.access(workspaceDbPath);
@@ -40051,7 +40484,7 @@ async function exportCommand(options = {}) {
40051
40484
  const ext = format === "json" ? ".json" : ".md";
40052
40485
  const safeName = session.title.replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").slice(0, 60);
40053
40486
  const defaultFile = `cascade-export-${safeName}${ext}`;
40054
- const outPath = options.output ? path35.resolve(options.output) : path35.join(process.cwd(), defaultFile);
40487
+ const outPath = options.output ? path36.resolve(options.output) : path36.join(process.cwd(), defaultFile);
40055
40488
  await fs11.writeFile(outPath, content, "utf-8");
40056
40489
  spin.succeed(chalk8.green(`Exported to ${chalk8.white(outPath)}`));
40057
40490
  const messageCount = Array.isArray(session.messages) ? session.messages.length : 0;
@@ -40191,7 +40624,7 @@ var SHOW_CURSOR = "\x1B[?25h";
40191
40624
  function writeNow(stdout, text) {
40192
40625
  if (typeof stdout.fd === "number") {
40193
40626
  try {
40194
- fs32.writeSync(stdout.fd, text);
40627
+ fs33.writeSync(stdout.fd, text);
40195
40628
  return;
40196
40629
  } catch {
40197
40630
  }
@@ -40214,11 +40647,11 @@ function restoreTerminal(stdin, stdout) {
40214
40647
  dotenv.config();
40215
40648
  function warnIfBuildIsStale() {
40216
40649
  try {
40217
- const here = path35.dirname(fileURLToPath(import.meta.url));
40650
+ const here = path36.dirname(fileURLToPath(import.meta.url));
40218
40651
  for (const rel of ["..", "../.."]) {
40219
- const pkgPath = path35.join(here, rel, "package.json");
40220
- if (!fs32.existsSync(pkgPath)) continue;
40221
- const pkg = JSON.parse(fs32.readFileSync(pkgPath, "utf-8"));
40652
+ const pkgPath = path36.join(here, rel, "package.json");
40653
+ if (!fs33.existsSync(pkgPath)) continue;
40654
+ const pkg = JSON.parse(fs33.readFileSync(pkgPath, "utf-8"));
40222
40655
  if (pkg.name !== "cascade-ai") continue;
40223
40656
  if (pkg.version && pkg.version !== CASCADE_VERSION) {
40224
40657
  console.error(