mergeable configs + cleanup (#8378)
This commit is contained in:
@@ -387,10 +387,7 @@ derive_utoipa!(IconTheme as IconThemeSchema);
|
|||||||
super::routes::status::system_info,
|
super::routes::status::system_info,
|
||||||
super::routes::status::diagnostics,
|
super::routes::status::diagnostics,
|
||||||
super::routes::mcp_ui_proxy::mcp_ui_proxy,
|
super::routes::mcp_ui_proxy::mcp_ui_proxy,
|
||||||
super::routes::config_management::backup_config,
|
|
||||||
super::routes::config_management::recover_config,
|
|
||||||
super::routes::config_management::validate_config,
|
super::routes::config_management::validate_config,
|
||||||
super::routes::config_management::init_config,
|
|
||||||
super::routes::config_management::upsert_config,
|
super::routes::config_management::upsert_config,
|
||||||
super::routes::config_management::remove_config,
|
super::routes::config_management::remove_config,
|
||||||
super::routes::config_management::read_config,
|
super::routes::config_management::read_config,
|
||||||
|
|||||||
@@ -515,33 +515,6 @@ pub async fn get_canonical_model_info(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[utoipa::path(
|
|
||||||
post,
|
|
||||||
path = "/config/init",
|
|
||||||
responses(
|
|
||||||
(status = 200, description = "Config initialization check completed", body = String),
|
|
||||||
(status = 500, description = "Internal server error")
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
pub async fn init_config() -> Result<Json<String>, ErrorResponse> {
|
|
||||||
let config = Config::global();
|
|
||||||
|
|
||||||
if config.exists() {
|
|
||||||
return Ok(Json("Config already exists".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the shared function to load init-config.yaml
|
|
||||||
match goose::config::base::load_init_config_from_workspace() {
|
|
||||||
Ok(init_values) => {
|
|
||||||
config.initialize_if_empty(init_values)?;
|
|
||||||
Ok(Json("Config initialized successfully".to_string()))
|
|
||||||
}
|
|
||||||
Err(_) => Ok(Json(
|
|
||||||
"No init-config.yaml found, using default configuration".to_string(),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
post,
|
post,
|
||||||
path = "/config/permissions",
|
path = "/config/permissions",
|
||||||
@@ -566,59 +539,6 @@ pub async fn upsert_permissions(
|
|||||||
Ok(Json("Permissions updated successfully".to_string()))
|
Ok(Json("Permissions updated successfully".to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[utoipa::path(
|
|
||||||
post,
|
|
||||||
path = "/config/backup",
|
|
||||||
responses(
|
|
||||||
(status = 200, description = "Config file backed up", body = String),
|
|
||||||
(status = 500, description = "Internal server error")
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
pub async fn backup_config() -> Result<Json<String>, ErrorResponse> {
|
|
||||||
let config_path = Paths::config_dir().join("config.yaml");
|
|
||||||
|
|
||||||
if !config_path.exists() {
|
|
||||||
return Err(ErrorResponse::not_found("Config file does not exist"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_name = config_path
|
|
||||||
.file_name()
|
|
||||||
.ok_or_else(|| ErrorResponse::internal("Invalid config file path"))?;
|
|
||||||
|
|
||||||
let mut backup_name = file_name.to_os_string();
|
|
||||||
backup_name.push(".bak");
|
|
||||||
|
|
||||||
let backup = config_path.with_file_name(backup_name);
|
|
||||||
std::fs::copy(&config_path, &backup)?;
|
|
||||||
Ok(Json(format!("Copied {:?} to {:?}", config_path, backup)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[utoipa::path(
|
|
||||||
post,
|
|
||||||
path = "/config/recover",
|
|
||||||
responses(
|
|
||||||
(status = 200, description = "Config recovery attempted", body = String),
|
|
||||||
(status = 500, description = "Internal server error")
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
pub async fn recover_config() -> Result<Json<String>, ErrorResponse> {
|
|
||||||
let config = Config::global();
|
|
||||||
|
|
||||||
// Force a reload which will trigger recovery if needed
|
|
||||||
let values = config.all_values()?;
|
|
||||||
let recovered_keys: Vec<String> = values.keys().cloned().collect();
|
|
||||||
|
|
||||||
if recovered_keys.is_empty() {
|
|
||||||
Ok(Json("Config recovery completed, but no data was recoverable. Starting with empty configuration.".to_string()))
|
|
||||||
} else {
|
|
||||||
Ok(Json(format!(
|
|
||||||
"Config recovery completed. Recovered {} keys: {}",
|
|
||||||
recovered_keys.len(),
|
|
||||||
recovered_keys.join(", ")
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
get,
|
get,
|
||||||
path = "/config/validate",
|
path = "/config/validate",
|
||||||
@@ -942,9 +862,6 @@ pub fn routes(state: Arc<AppState>) -> Router {
|
|||||||
"/config/canonical-model-info",
|
"/config/canonical-model-info",
|
||||||
post(get_canonical_model_info),
|
post(get_canonical_model_info),
|
||||||
)
|
)
|
||||||
.route("/config/init", post(init_config))
|
|
||||||
.route("/config/backup", post(backup_config))
|
|
||||||
.route("/config/recover", post(recover_config))
|
|
||||||
.route("/config/validate", get(validate_config))
|
.route("/config/validate", get(validate_config))
|
||||||
.route("/config/permissions", post(upsert_permissions))
|
.route("/config/permissions", post(upsert_permissions))
|
||||||
.route("/config/custom-providers", post(create_custom_provider))
|
.route("/config/custom-providers", post(create_custom_provider))
|
||||||
|
|||||||
@@ -77,29 +77,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/config/backup": {
|
|
||||||
"post": {
|
|
||||||
"tags": [
|
|
||||||
"super::routes::config_management"
|
|
||||||
],
|
|
||||||
"operationId": "backup_config",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Config file backed up",
|
|
||||||
"content": {
|
|
||||||
"text/plain": {
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal server error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/config/extensions": {
|
"/config/extensions": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
|
|||||||
+548
-380
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ pub mod signup_openrouter;
|
|||||||
pub mod signup_tetrate;
|
pub mod signup_tetrate;
|
||||||
|
|
||||||
pub use crate::agents::ExtensionConfig;
|
pub use crate::agents::ExtensionConfig;
|
||||||
pub use base::{Config, ConfigError};
|
pub use base::{merge_config_values, Config, ConfigError};
|
||||||
pub use declarative_providers::DeclarativeProviderConfig;
|
pub use declarative_providers::DeclarativeProviderConfig;
|
||||||
pub use experiments::ExperimentManager;
|
pub use experiments::ExperimentManager;
|
||||||
pub use extensions::{
|
pub use extensions::{
|
||||||
|
|||||||
@@ -747,29 +747,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/config/backup": {
|
|
||||||
"post": {
|
|
||||||
"tags": [
|
|
||||||
"super::routes::config_management"
|
|
||||||
],
|
|
||||||
"operationId": "backup_config",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Config file backed up",
|
|
||||||
"content": {
|
|
||||||
"text/plain": {
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal server error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/config/canonical-model-info": {
|
"/config/canonical-model-info": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -1065,29 +1042,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/config/init": {
|
|
||||||
"post": {
|
|
||||||
"tags": [
|
|
||||||
"super::routes::config_management"
|
|
||||||
],
|
|
||||||
"operationId": "init_config",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Config initialization check completed",
|
|
||||||
"content": {
|
|
||||||
"text/plain": {
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal server error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/config/permissions": {
|
"/config/permissions": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -1485,29 +1439,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/config/recover": {
|
|
||||||
"post": {
|
|
||||||
"tags": [
|
|
||||||
"super::routes::config_management"
|
|
||||||
],
|
|
||||||
"operationId": "recover_config",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Config recovery attempted",
|
|
||||||
"content": {
|
|
||||||
"text/plain": {
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal server error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/config/remove": {
|
"/config/remove": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": [
|
||||||
|
|||||||
@@ -273,29 +273,6 @@ describe('App Component - Brand New State', () => {
|
|||||||
expect(mockNavigate).not.toHaveBeenCalled();
|
expect(mockNavigate).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle config recovery gracefully', async () => {
|
|
||||||
// Mock config error that triggers recovery
|
|
||||||
const { readAllConfig, recoverConfig } = await import('./api');
|
|
||||||
console.log(recoverConfig);
|
|
||||||
vi.mocked(readAllConfig).mockRejectedValueOnce(new Error('Config read error'));
|
|
||||||
|
|
||||||
mockElectron.getConfig.mockReturnValue({
|
|
||||||
GOOSE_DEFAULT_PROVIDER: null,
|
|
||||||
GOOSE_DEFAULT_MODEL: null,
|
|
||||||
GOOSE_ALLOWLIST_WARNING: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
render(<AppInner />, { wrapper: IntlTestWrapper });
|
|
||||||
|
|
||||||
// Wait for initialization and recovery
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(mockElectron.reactReady).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
// App should still initialize without any navigation calls
|
|
||||||
expect(mockNavigate).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should seed recipe sessions with the recipe prompt when no initial message is provided', () => {
|
it('should seed recipe sessions with the recipe prompt when no initial message is provided', () => {
|
||||||
expect(
|
expect(
|
||||||
resolveSessionInitialMessage(
|
resolveSessionInitialMessage(
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2235,29 +2235,6 @@ export type ReadAllConfigResponses = {
|
|||||||
|
|
||||||
export type ReadAllConfigResponse = ReadAllConfigResponses[keyof ReadAllConfigResponses];
|
export type ReadAllConfigResponse = ReadAllConfigResponses[keyof ReadAllConfigResponses];
|
||||||
|
|
||||||
export type BackupConfigData = {
|
|
||||||
body?: never;
|
|
||||||
path?: never;
|
|
||||||
query?: never;
|
|
||||||
url: '/config/backup';
|
|
||||||
};
|
|
||||||
|
|
||||||
export type BackupConfigErrors = {
|
|
||||||
/**
|
|
||||||
* Internal server error
|
|
||||||
*/
|
|
||||||
500: unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type BackupConfigResponses = {
|
|
||||||
/**
|
|
||||||
* Config file backed up
|
|
||||||
*/
|
|
||||||
200: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type BackupConfigResponse = BackupConfigResponses[keyof BackupConfigResponses];
|
|
||||||
|
|
||||||
export type GetCanonicalModelInfoData = {
|
export type GetCanonicalModelInfoData = {
|
||||||
body: ModelInfoQuery;
|
body: ModelInfoQuery;
|
||||||
path?: never;
|
path?: never;
|
||||||
@@ -2478,29 +2455,6 @@ export type RemoveExtensionResponses = {
|
|||||||
|
|
||||||
export type RemoveExtensionResponse = RemoveExtensionResponses[keyof RemoveExtensionResponses];
|
export type RemoveExtensionResponse = RemoveExtensionResponses[keyof RemoveExtensionResponses];
|
||||||
|
|
||||||
export type InitConfigData = {
|
|
||||||
body?: never;
|
|
||||||
path?: never;
|
|
||||||
query?: never;
|
|
||||||
url: '/config/init';
|
|
||||||
};
|
|
||||||
|
|
||||||
export type InitConfigErrors = {
|
|
||||||
/**
|
|
||||||
* Internal server error
|
|
||||||
*/
|
|
||||||
500: unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type InitConfigResponses = {
|
|
||||||
/**
|
|
||||||
* Config initialization check completed
|
|
||||||
*/
|
|
||||||
200: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type InitConfigResponse = InitConfigResponses[keyof InitConfigResponses];
|
|
||||||
|
|
||||||
export type UpsertPermissionsData = {
|
export type UpsertPermissionsData = {
|
||||||
body: UpsertPermissionsQuery;
|
body: UpsertPermissionsQuery;
|
||||||
path?: never;
|
path?: never;
|
||||||
@@ -2815,29 +2769,6 @@ export type ReadConfigResponses = {
|
|||||||
200: unknown;
|
200: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RecoverConfigData = {
|
|
||||||
body?: never;
|
|
||||||
path?: never;
|
|
||||||
query?: never;
|
|
||||||
url: '/config/recover';
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RecoverConfigErrors = {
|
|
||||||
/**
|
|
||||||
* Internal server error
|
|
||||||
*/
|
|
||||||
500: unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RecoverConfigResponses = {
|
|
||||||
/**
|
|
||||||
* Config recovery attempted
|
|
||||||
*/
|
|
||||||
200: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RecoverConfigResponse = RecoverConfigResponses[keyof RecoverConfigResponses];
|
|
||||||
|
|
||||||
export type RemoveConfigData = {
|
export type RemoveConfigData = {
|
||||||
body: ConfigKeyQuery;
|
body: ConfigKeyQuery;
|
||||||
path?: never;
|
path?: never;
|
||||||
|
|||||||
Reference in New Issue
Block a user