Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions scripts/check-core-boundaries.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1853,6 +1853,14 @@ test('services-core capability profiles keep heavy owners out of the empty profi
'dep:sha2',
'tokio/fs',
]);
assert.deepEqual(profiles.get('json-io'), [
'dep:fs2',
'dep:windows',
'tokio/fs',
'tokio/sync',
'windows/Win32_Foundation',
'windows/Win32_Storage_FileSystem',
]);
assert.deepEqual(profiles.get('local-storage'), [
'dep:bitfun-core-types',
'dep:bitfun-events',
Expand Down Expand Up @@ -1967,6 +1975,7 @@ test('services-core Tokio capabilities stay owner-scoped', () => {
],
features: {
filesystem: [],
'json-io': [],
'local-storage': [],
'process-runtime': [],
'workspace-instructions': [],
Expand Down
1 change: 1 addition & 0 deletions scripts/core-boundaries/cargo-dependency-boundaries.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ const SERVICES_INTEGRATIONS_TOKIO_FEATURES = new Map([

const SERVICES_CORE_TOKIO_FEATURES = new Map([
['filesystem', ['fs']],
['json-io', ['fs', 'sync']],
['local-storage', ['fs', 'sync']],
['process-runtime', ['io-util', 'process']],
['workspace-instructions', ['fs', 'io-util']],
Expand Down
18 changes: 16 additions & 2 deletions scripts/core-boundaries/rules/feature-rules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const optionalDependencyFeatureOwnerRules = [
{ depName: 'bitfun-runtime-ports', ownerFeatures: ['permission', 'workspace-runtime'] },
{ depName: 'chrono', ownerFeatures: ['filesystem', 'local-storage'] },
{ depName: 'dunce', ownerFeatures: ['runtime-ownership', 'workspace-identity', 'workspace-runtime'] },
{ depName: 'fs2', ownerFeatures: ['local-storage', 'runtime-ownership'] },
{ depName: 'fs2', ownerFeatures: ['json-io', 'local-storage', 'runtime-ownership'] },
{ depName: 'git2', ownerFeatures: ['session-git'] },
{ depName: 'globset', ownerFeatures: ['workspace-instructions'] },
{ depName: 'ignore', ownerFeatures: ['filesystem'] },
Expand All @@ -49,7 +49,7 @@ export const optionalDependencyFeatureOwnerRules = [
},
{ depName: 'which', ownerFeatures: ['process-runtime'] },
{ depName: 'win32job', ownerFeatures: ['process-runtime'] },
{ depName: 'windows', ownerFeatures: ['local-storage', 'process-runtime'] },
{ depName: 'windows', ownerFeatures: ['json-io', 'local-storage', 'process-runtime'] },
{ depName: 'zip', ownerFeatures: ['lsp'] },
],
},
Expand Down Expand Up @@ -396,6 +396,20 @@ export const coreClosedFeatureProfileRules = [
exact: true,
reason: 'services-core filesystem must own only local file operations and recursive search dependencies',
},
{
manifestPath: 'src/crates/services/services-core/Cargo.toml',
featureName: 'json-io',
requiredFeatureRefs: [
'dep:fs2',
'dep:windows',
'tokio/fs',
'tokio/sync',
'windows/Win32_Foundation',
'windows/Win32_Storage_FileSystem',
],
exact: true,
reason: 'services-core json-io must own only generic locked and atomic JSON file IO',
},
{
manifestPath: 'src/crates/services/services-core/Cargo.toml',
featureName: 'local-storage',
Expand Down
4 changes: 2 additions & 2 deletions scripts/core-boundaries/rules/source/required-rules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ export const requiredContentRules = [
message: 'missing filesystem capability source gate',
},
{
regex: /#\[cfg\(feature = "local-storage"\)\]\s*pub mod json_store;/,
message: 'missing local-storage JSON owner source gate',
regex: /#\[cfg\(any\(feature = "json-io", feature = "local-storage"\)\)\]\s*pub mod json_store;/,
message: 'missing json-io/local-storage JSON owner source gate',
},
{
regex: /#\[cfg\(feature = "local-storage"\)\]\s*pub mod persistence;/,
Expand Down
4 changes: 2 additions & 2 deletions scripts/core-boundaries/self-test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -937,7 +937,7 @@ export function runManifestParserSelfTest({
['bitfun-core-types', ['local-storage', 'lsp']],
['bitfun-events', ['local-storage']],
['chrono', ['filesystem', 'local-storage']],
['fs2', ['local-storage', 'runtime-ownership']],
['fs2', ['json-io', 'local-storage', 'runtime-ownership']],
['git2', ['session-git']],
['globset', ['workspace-instructions']],
['ignore', ['filesystem']],
Expand All @@ -957,7 +957,7 @@ export function runManifestParserSelfTest({
],
['which', ['process-runtime']],
['win32job', ['process-runtime']],
['windows', ['local-storage', 'process-runtime']],
['windows', ['json-io', 'local-storage', 'process-runtime']],
['zip', ['lsp']],
]);
for (const [dependencyName, ownerFeatures] of expectedServicesCoreOwners) {
Expand Down
5 changes: 5 additions & 0 deletions src/apps/desktop/build.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
fn main() {
// The Windows primary thread keeps the Tauri event loop and native window
// creation stack. Reserve the same headroom as the Tokio workers so a
// large debug invoke dispatcher cannot exhaust the default 1 MiB stack.
#[cfg(target_os = "windows")]
println!("cargo:rustc-link-arg-bins=/STACK:8388608");
tauri_build::build();
}
26 changes: 26 additions & 0 deletions src/apps/desktop/src/api/config_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use crate::api::app_state::AppState;
use crate::startup_trace::DesktopStartupTrace;
use bitfun_core::service::config::{SaveCloudSpeechConfigRequest, SaveCloudSpeechConfigResult};
use bitfun_core::util::errors::BitFunError;
use log::{error, info};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -224,6 +225,31 @@ pub async fn set_config(
result
}

#[tauri::command]
pub async fn save_cloud_speech_config(
state: State<'_, AppState>,
request: SaveCloudSpeechConfigRequest,
) -> Result<SaveCloudSpeechConfigResult, String> {
match state.config_service.save_cloud_speech_config(request).await {
Ok(result) => {
state.ai_client_factory.invalidate_cache();
crate::api::remote_connect_api::notify_settings_changed();
info!(
"Cloud speech configuration saved atomically: model_id={}, created={}",
result.model_id, result.created
);
Ok(result)
}
Err(error) => {
error!("Failed to save cloud speech configuration: {}", error);
Err(format!(
"Failed to save cloud speech configuration: {}",
error
))
}
}
}

#[tauri::command]
pub async fn reset_config(
state: State<'_, AppState>,
Expand Down
4 changes: 4 additions & 0 deletions src/apps/desktop/src/api/remote_workspace_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1638,6 +1638,10 @@ pub const REMOTE_WORKSPACE_COMMAND_POLICIES: &[(&str, RemoteWorkspacePolicy)] =
RemoteWorkspacePolicy::LegacyUnaudited,
),
("save_canvas_state", RemoteWorkspacePolicy::LegacyUnaudited),
(
"save_cloud_speech_config",
RemoteWorkspacePolicy::WorkspaceAgnostic,
),
(
"save_git_repo_history",
RemoteWorkspacePolicy::LegacyUnaudited,
Expand Down
42 changes: 42 additions & 0 deletions src/apps/desktop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,34 @@ static MAIN_WINDOW_CLOSE_PENDING_ON_MACOS: AtomicBool = AtomicBool::new(false);

const MAIN_WINDOW_CLOSE_REQUESTED_EVENT: &str = "bitfun_main_window_close_requested";
const BROWSER_WEBVIEW_PAGE_LOAD_EVENT: &str = "browser-webview-page-load";

#[cfg(target_os = "windows")]
fn show_fatal_startup_error(message: &str) {
use windows::core::PCWSTR;
use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_ICONERROR, MB_OK};

let title = "BitFun startup error"
.encode_utf16()
.chain(std::iter::once(0))
.collect::<Vec<_>>();
let message = message
.encode_utf16()
.chain(std::iter::once(0))
.collect::<Vec<_>>();
unsafe {
let _ = MessageBoxW(
None,
PCWSTR(message.as_ptr()),
PCWSTR(title.as_ptr()),
MB_OK | MB_ICONERROR,
);
}
}

#[cfg(not(target_os = "windows"))]
fn show_fatal_startup_error(message: &str) {
eprintln!("BitFun startup error: {message}");
}
const CRON_DESKTOP_START_FALLBACK_DELAY: Duration = Duration::from_secs(120);
pub(crate) const MAIN_WINDOW_DEFAULT_WIDTH: f64 = 1200.0;
pub(crate) const MAIN_WINDOW_DEFAULT_HEIGHT: f64 = 800.0;
Expand Down Expand Up @@ -468,8 +496,21 @@ pub async fn run() {
let step_started = Instant::now();
if let Err(e) = bitfun_core::service::config::initialize_global_config().await {
log::error!("Failed to initialize global config service: {}", e);
show_fatal_startup_error(&format!(
"BitFun could not initialize its configuration and cannot continue.\n\n{e}\n\nSee early-startup.log for details."
));
return;
}
if let Ok(config_service) = bitfun_core::service::config::get_global_config_service().await {
for diagnostic in config_service.load_diagnostics().await {
log::warn!(
"Startup configuration diagnostic: code={}, path={}, recoverability={:?}",
diagnostic.code,
diagnostic.path,
diagnostic.recoverability
);
}
}
startup_timings.record_elapsed("initialize_global_config", step_started);
startup_trace.record_elapsed_step("native_pre_tauri", "initialize_global_config", step_started);

Expand Down Expand Up @@ -1291,6 +1332,7 @@ pub async fn run() {
computer_use_request_permissions,
computer_use_open_system_settings,
set_config,
save_cloud_speech_config,
reset_config,
export_config,
import_config,
Expand Down
14 changes: 10 additions & 4 deletions src/apps/desktop/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@
// plugin redirects them back to the existing desktop process.
#![cfg_attr(target_os = "windows", windows_subsystem = "windows")]

#[tokio::main(flavor = "multi_thread", worker_threads = 4)]
async fn main() {
std::env::set_var("RUST_MIN_STACK", "8388608"); // 8MB
bitfun_desktop_lib::run().await
fn main() {
// Tokio reads this value while creating its worker threads. Setting it in
// the async body is too late, because the runtime has already been built.
std::env::set_var("RUST_MIN_STACK", "8388608"); // 8 MiB worker stacks
let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(4)
.enable_all()
.build()
.expect("failed to build Tokio runtime");
runtime.block_on(bitfun_desktop_lib::run());
}
2 changes: 1 addition & 1 deletion src/crates/assembly/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ bitfun-agent-tools = { path = "../../execution/tool-contracts" }
bitfun-tool-packs = { path = "../../execution/tool-provider-groups", default-features = false, optional = true }

# Core service owner crate
bitfun-services-core = { path = "../../services/services-core", default-features = false }
bitfun-services-core = { path = "../../services/services-core", default-features = false, features = ["json-io"] }

# Integration service owner crate
bitfun-services-integrations = { path = "../../services/services-integrations", default-features = false, optional = true }
Expand Down
Loading