@@ -32,12 +32,16 @@ const LEGACY_DEV_TOKENS = new Set([
3232 'my-secure-token-123' ,
3333] ) ;
3434
35+ export type ThemeOption = 'zinc' | 'slate' | 'neutral' ;
36+ export type DensityOption = 'compact' | 'comfortable' ;
37+ export type BackdropThemeOption = 'cyan' | 'emerald' | 'nebula' | 'slate' | 'amber' ;
38+
3539export interface Tadpole_Settings {
3640 tadpole_os_url : string ;
3741 tadpole_os_api_key : string ;
38- theme : string ;
39- density : string ;
40- backdrop_theme : string ;
42+ theme : ThemeOption ;
43+ density : DensityOption ;
44+ backdrop_theme : BackdropThemeOption ;
4145 default_model : string ;
4246 default_temperature : number ;
4347 auto_approve_safe_skills : boolean ;
@@ -58,11 +62,12 @@ interface Settings_State {
5862 settings : Tadpole_Settings ;
5963 save_settings : ( new_settings : Tadpole_Settings ) => string | null ;
6064 update_setting : < K extends keyof Tadpole_Settings > ( key : K , value : Tadpole_Settings [ K ] ) => void ;
65+ reset_to_defaults : ( ) => void ;
6166}
6267
63- const get_base_url = ( ) : string => {
68+ export const get_base_url = ( ) : string => {
6469 if ( import . meta. env . VITE_TADPOLE_OS_URL ) {
65- return import . meta. env . VITE_TADPOLE_OS_URL ;
70+ return import . meta. env . VITE_TADPOLE_OS_URL . trim ( ) . replace ( / \/ + $ / , '' ) ;
6671 }
6772 // Dynamically align loopback URL with current window origin if available
6873 if ( typeof window !== 'undefined' && window . location ?. hostname ) {
@@ -76,19 +81,20 @@ const get_base_url = (): string => {
7681
7782const normalize_loopback_url = ( url : string | undefined ) : string => {
7883 if ( ! url ) return get_base_url ( ) ;
79- if ( url . toLowerCase ( ) . includes ( 'tauri' ) ) {
84+ const cleaned = url . trim ( ) . replace ( / \/ + $ / , '' ) ;
85+ if ( cleaned . toLowerCase ( ) . includes ( 'tauri' ) ) {
8086 return get_base_url ( ) ;
8187 }
8288 if ( typeof window !== 'undefined' && window . location ?. hostname ) {
8389 const current_host = window . location . hostname ;
84- if ( current_host === 'localhost' && url . includes ( '127.0.0.1:8000' ) ) {
85- return url . replace ( '127.0.0.1:8000' , 'localhost:8000' ) ;
90+ if ( current_host === 'localhost' && cleaned . includes ( '127.0.0.1:8000' ) ) {
91+ return cleaned . replace ( '127.0.0.1:8000' , 'localhost:8000' ) ;
8692 }
87- if ( current_host === '127.0.0.1' && url . includes ( 'localhost:8000' ) ) {
88- return url . replace ( 'localhost:8000' , '127.0.0.1:8000' ) ;
93+ if ( current_host === '127.0.0.1' && cleaned . includes ( 'localhost:8000' ) ) {
94+ return cleaned . replace ( 'localhost:8000' , '127.0.0.1:8000' ) ;
8995 }
9096 }
91- return url ;
97+ return cleaned ;
9298} ;
9399
94100const sanitize_api_key = ( value : string ) : string => {
@@ -102,7 +108,28 @@ const sanitize_settings = (settings: Tadpole_Settings): Tadpole_Settings => ({
102108 tadpole_os_api_key : sanitize_api_key ( settings . tadpole_os_api_key || '' ) ,
103109} ) ;
104110
105-
111+ /** Canonical default configuration state */
112+ export const get_default_settings = ( ) : Tadpole_Settings => ( {
113+ tadpole_os_url : get_base_url ( ) ,
114+ tadpole_os_api_key : import . meta. env . VITE_NEURAL_TOKEN || '' ,
115+ theme : 'zinc' ,
116+ density : 'compact' ,
117+ backdrop_theme : 'cyan' ,
118+ default_model : 'GPT-4o' ,
119+ default_temperature : 0.7 ,
120+ auto_approve_safe_skills : true ,
121+ max_agents : 50 ,
122+ max_clusters : 10 ,
123+ max_swarm_depth : 5 ,
124+ max_task_length : 32768 ,
125+ default_budget_usd : 1.0 ,
126+ is_safe_mode : true , // Default to safe mode for stabilization
127+ privacy_mode : false ,
128+ browser_specialist_model_id : 'HuggingFaceTB/SmolLM-360M-Instruct' ,
129+ computer_architect_url : 'http://localhost:11434' ,
130+ enable_neural_handoff : true ,
131+ sentinel_mode : false ,
132+ } ) ;
106133
107134/** is_valid_url - Validates a URL string for HTTP/HTTPS protocols. */
108135export function is_valid_url ( url : string ) : boolean {
@@ -127,27 +154,7 @@ export function is_valid_api_key(api_key: string): boolean {
127154export const use_settings_store = create < Settings_State > ( ) (
128155 persist (
129156 ( set , get ) => ( {
130- settings : {
131- tadpole_os_url : get_base_url ( ) ,
132- tadpole_os_api_key : import . meta. env . VITE_NEURAL_TOKEN || '' ,
133- theme : 'zinc' ,
134- density : 'compact' ,
135- backdrop_theme : 'cyan' ,
136- default_model : 'GPT-4o' ,
137- default_temperature : 0.7 ,
138- auto_approve_safe_skills : true ,
139- max_agents : 50 ,
140- max_clusters : 10 ,
141- max_swarm_depth : 5 ,
142- max_task_length : 32768 ,
143- default_budget_usd : 1.0 ,
144- is_safe_mode : true , // Default to safe mode for stabilization
145- privacy_mode : false ,
146- browser_specialist_model_id : 'HuggingFaceTB/SmolLM-360M-Instruct' ,
147- computer_architect_url : 'http://localhost:11434' ,
148- enable_neural_handoff : true ,
149- sentinel_mode : false ,
150- } as unknown as Tadpole_Settings ,
157+ settings : get_default_settings ( ) ,
151158
152159 save_settings : ( new_settings ) => {
153160 // NUCLEAR PROTECTION: Never allow internal tauri URIs to be explicitly saved
@@ -161,11 +168,24 @@ export const use_settings_store = create<Settings_State>()(
161168 if ( ! is_valid_url ( clean_url ) ) {
162169 return 'Invalid URL. Must start with http:// or https://' ;
163170 }
171+
172+ // Numeric Invariant Clamping
173+ const clamped_temperature = Math . min ( 2.0 , Math . max ( 0.0 , Number ( new_settings . default_temperature ) || 0.7 ) ) ;
174+ const clamped_agents = Math . min ( 100 , Math . max ( 1 , Math . floor ( Number ( new_settings . max_agents ) || 50 ) ) ) ;
175+ const clamped_clusters = Math . min ( 20 , Math . max ( 1 , Math . floor ( Number ( new_settings . max_clusters ) || 10 ) ) ) ;
176+ const clamped_swarm_depth = Math . min ( 10 , Math . max ( 1 , Math . floor ( Number ( new_settings . max_swarm_depth ) || 5 ) ) ) ;
177+ const clamped_budget = Math . max ( 0 , Number ( new_settings . default_budget_usd ) || 0 ) ;
178+
164179 set ( {
165180 settings : {
166181 ...new_settings ,
167182 tadpole_os_url : clean_url ,
168- tadpole_os_api_key : sanitize_api_key ( new_settings . tadpole_os_api_key ) ,
183+ tadpole_os_api_key : sanitize_api_key ( new_settings . tadpole_os_api_key || '' ) ,
184+ default_temperature : clamped_temperature ,
185+ max_agents : clamped_agents ,
186+ max_clusters : clamped_clusters ,
187+ max_swarm_depth : clamped_swarm_depth ,
188+ default_budget_usd : clamped_budget ,
169189 }
170190 } ) ;
171191 return null ;
@@ -182,9 +202,19 @@ export const use_settings_store = create<Settings_State>()(
182202 } else {
183203 final_value = normalize_loopback_url ( value ) as unknown as Tadpole_Settings [ K ] ;
184204 }
205+ } else if ( key === 'tadpole_os_api_key' && typeof value === 'string' ) {
206+ final_value = sanitize_api_key ( value ) as unknown as Tadpole_Settings [ K ] ;
207+ } else if ( key === 'default_temperature' && typeof value === 'number' ) {
208+ final_value = Math . min ( 2.0 , Math . max ( 0.0 , value ) ) as unknown as Tadpole_Settings [ K ] ;
209+ } else if ( key === 'max_agents' && typeof value === 'number' ) {
210+ final_value = Math . min ( 100 , Math . max ( 1 , Math . floor ( value ) ) ) as unknown as Tadpole_Settings [ K ] ;
185211 }
186212
187213 set ( { settings : { ...current , [ key ] : final_value } } ) ;
214+ } ,
215+
216+ reset_to_defaults : ( ) => {
217+ set ( { settings : get_default_settings ( ) } ) ;
188218 }
189219 } ) ,
190220 {
@@ -233,6 +263,7 @@ export const use_settings_store = create<Settings_State>()(
233263// Backward compatibility helpers for non-reactive code
234264export const get_settings = ( ) : Tadpole_Settings => use_settings_store . getState ( ) . settings ;
235265export const save_settings = ( s : Tadpole_Settings ) : string | null => use_settings_store . getState ( ) . save_settings ( s ) ;
266+ export const reset_settings = ( ) : void => use_settings_store . getState ( ) . reset_to_defaults ( ) ;
236267
237268
238269
0 commit comments