Skip to content
Open
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,4 @@ ASSETS_LICENSES.md
external/
/.bitfun/search/flashgrep-index/
.agents/
/.flashgrep-index-engine/
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ qrcode = "0.14"
# WebSocket client
tokio-tungstenite = { version = "0.29", features = ["rustls-tls-native-roots"] }

# Local speech recognition
sherpa-onnx = "1.13.4"

# MCP and remote runtimes
rmcp = { version = "1.7", default-features = false, features = [
"auth",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Relay Deploy: Live Logs + Docker Cache Invalidation

**Date:** 2026-07-19
**Date:** 2026-07-19
**Status:** Approved for implementation

## Problems
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Desktop Streaming Typewriter Smoothness

**Date:** 2026-07-20
**Date:** 2026-07-20
**Status:** Approved for implementation (user authorized direct design + implement)

## Problem
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Mobile-Web 账号配对门槛与设备列表跟随设计

Date: 2026-07-20
Date: 2026-07-20
Scope: `src/mobile-web`, `src/crates/services/services-integrations` (pairing/QR/account), `src/crates/assembly/core` (remote_connect), `src/apps/desktop` (account verifier wiring)

## 背景
Expand Down
12 changes: 12 additions & 0 deletions scripts/core-boundaries/rules/crate-rules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ export const noCoreDependencyCrates = [
];

export const forbiddenManifestDependencyRules = [
{
dependencyNames: ['sherpa-onnx'],
scanRoots: ['src/apps', 'src/crates', 'BitFun-Installer/src-tauri'],
forbidWorkspaceAliases: false,
allowManifestPaths: [
'src/crates/services/services-integrations/Cargo.toml',
],
reason:
'speech recognition engines are concrete integration service dependencies',
message:
'sherpa-onnx must stay in services-integrations and be injected by a product composition root',
},
{
dependencyNames: ['bitfun-opencode-adapter'],
scanRoots: ['src/apps', 'src/crates', 'BitFun-Installer/src-tauri'],
Expand Down
21 changes: 13 additions & 8 deletions scripts/core-boundaries/rules/feature-rules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -55,24 +55,27 @@ export const optionalDependencyFeatureOwnerRules = [
{ depName: 'anyhow', ownerFeatures: ['browser-control', 'debug-log', 'mcp', 'remote-connect', 'remote-ssh', 'remote-ssh-concrete'] },
{
depName: 'async-trait',
ownerFeatures: ['mcp', 'remote-connect', 'remote-ssh', 'remote-ssh-concrete', 'review-platform', 'script-tool-runtime', 'workspace-search'],
ownerFeatures: ['mcp', 'remote-connect', 'remote-ssh', 'remote-ssh-concrete', 'review-platform', 'script-tool-runtime', 'speech', 'workspace-search'],
},
{
depName: 'base64',
ownerFeatures: ['mcp', 'miniapp-runtime', 'remote-connect', 'remote-ssh-concrete'],
ownerFeatures: ['mcp', 'miniapp-runtime', 'remote-connect', 'remote-ssh-concrete', 'speech'],
},
{ depName: 'bitfun-agent-runtime', ownerFeatures: ['deep-research'] },
{ depName: 'bitfun-core-types', ownerFeatures: ['speech'] },
{ depName: 'bitfun-product-domains', ownerFeatures: ['canvas-runtime', 'function-agents', 'miniapp-runtime', 'plugin-source'] },
{ depName: 'bitfun-runtime-ports', ownerFeatures: ['remote-connect', 'remote-ssh', 'remote-ssh-concrete', 'script-tool-runtime'] },
{
depName: 'bitfun-services-core',
ownerFeatures: ['browser-control', 'git', 'mcp', 'miniapp-runtime', 'process-tree', 'remote-connect', 'review-platform', 'workspace-search'],
},
{ depName: 'chrono', ownerFeatures: ['debug-log', 'git', 'remote-connect', 'remote-ssh-concrete', 'review-platform'] },
{ depName: 'bzip2', ownerFeatures: ['speech'] },
{ depName: 'chrono', ownerFeatures: ['debug-log', 'git', 'remote-connect', 'remote-ssh-concrete', 'review-platform', 'speech'] },
{ depName: 'dirs', ownerFeatures: ['browser-control', 'miniapp-runtime', 'remote-connect', 'remote-ssh-concrete'] },
{ depName: 'dunce', ownerFeatures: ['plugin-source', 'remote-ssh', 'workspace-search'] },
{ depName: 'fs2', ownerFeatures: ['plugin-source'] },
{ depName: 'futures', ownerFeatures: ['mcp', 'remote-connect', 'review-platform'] },
{ depName: 'futures-util', ownerFeatures: ['speech'] },
{ depName: 'git2', ownerFeatures: ['git'] },
{ depName: 'hex', ownerFeatures: ['plugin-source', 'remote-connect'] },
{ depName: 'hostname', ownerFeatures: ['remote-connect'] },
Expand All @@ -85,24 +88,26 @@ export const optionalDependencyFeatureOwnerRules = [
{ depName: 'oxc', ownerFeatures: ['canvas-runtime'] },
{ depName: 'qrcode', ownerFeatures: ['remote-connect'] },
{ depName: 'rand', ownerFeatures: ['mcp', 'remote-connect', 'remote-ssh-concrete'] },
{ depName: 'reqwest', ownerFeatures: ['announcement', 'browser-control', 'debug-log', 'mcp', 'miniapp-runtime', 'remote-connect', 'review-platform', 'web-tools'] },
{ depName: 'reqwest', ownerFeatures: ['announcement', 'browser-control', 'debug-log', 'mcp', 'miniapp-runtime', 'remote-connect', 'review-platform', 'speech', 'web-tools'] },
{ depName: 'rmcp', ownerFeatures: ['mcp'] },
{ depName: 'russh', ownerFeatures: ['remote-ssh-concrete'] },
{ depName: 'russh-keys', ownerFeatures: ['remote-ssh-concrete'] },
{ depName: 'russh-sftp', ownerFeatures: ['remote-ssh-concrete'] },
{ depName: 'rustls', ownerFeatures: ['remote-connect'] },
{ depName: 'rustls-native-certs', ownerFeatures: ['remote-connect'] },
{ depName: 'schannel', ownerFeatures: ['remote-connect'] },
{ depName: 'sha2', ownerFeatures: ['canvas-runtime', 'plugin-source', 'remote-connect', 'remote-ssh', 'review-platform'] },
{ depName: 'sha2', ownerFeatures: ['canvas-runtime', 'plugin-source', 'remote-connect', 'remote-ssh', 'review-platform', 'speech'] },
{ depName: 'sherpa-onnx', ownerFeatures: ['speech'] },
{ depName: 'shellexpand', ownerFeatures: ['remote-ssh-concrete'] },
{ depName: 'sse-stream', ownerFeatures: ['mcp'] },
{ depName: 'ssh_config', ownerFeatures: ['remote-ssh-concrete', 'ssh_config'] },
{ depName: 'terminal-core', ownerFeatures: ['remote-ssh', 'remote-ssh-concrete'] },
{ depName: 'thiserror', ownerFeatures: ['browser-control', 'git', 'plugin-source', 'remote-ssh', 'remote-ssh-concrete', 'review-platform', 'web-tools', 'workspace-search'] },
{ depName: 'tar', ownerFeatures: ['speech'] },
{ depName: 'thiserror', ownerFeatures: ['browser-control', 'git', 'plugin-source', 'remote-ssh', 'remote-ssh-concrete', 'review-platform', 'speech', 'web-tools', 'workspace-search'] },
{ depName: 'tokio-tungstenite', ownerFeatures: ['remote-connect'] },
{ depName: 'tokio-util', ownerFeatures: ['remote-ssh'] },
{ depName: 'tokio-util', ownerFeatures: ['remote-ssh', 'speech'] },
{ depName: 'urlencoding', ownerFeatures: ['canvas-runtime', 'remote-connect', 'review-platform'] },
{ depName: 'uuid', ownerFeatures: ['canvas-runtime', 'debug-log', 'miniapp-runtime', 'plugin-source', 'remote-connect', 'remote-ssh-concrete'] },
{ depName: 'uuid', ownerFeatures: ['canvas-runtime', 'debug-log', 'miniapp-runtime', 'plugin-source', 'remote-connect', 'remote-ssh-concrete', 'speech'] },
{ depName: 'which', ownerFeatures: ['miniapp-runtime', 'remote-connect', 'script-tool-runtime', 'workspace-search'] },
{ depName: 'windows', ownerFeatures: ['plugin-source', 'review-platform'] },
{ depName: 'x25519-dalek', ownerFeatures: ['remote-connect'] },
Expand Down
17 changes: 17 additions & 0 deletions scripts/core-boundaries/rules/source/forbidden-rules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4140,6 +4140,23 @@ export const forbiddenContentUnderRules = [
},
],
},
{
path: 'src/crates/assembly/core/src/service',
reason:
'concrete speech runtime ownership belongs in services-integrations',
patterns: [
{
regex: /\bpub\s+mod\s+speech\s*;/,
message:
'core must not declare a speech service owner; use stable core-types contracts and compose the services-integrations provider at the app boundary',
},
{
regex: /\bpub\s+use\s+(?:self::)?speech(?:::|\s*::)/,
message:
'core must not re-export the concrete speech service provider',
},
],
},
{
path: 'src/crates/assembly/core/src',
reason:
Expand Down
48 changes: 48 additions & 0 deletions scripts/core-boundaries/self-test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,28 @@ export function runManifestParserSelfTest({
throw new Error(`services-integrations review-platform must own optional dependency ${dep}`);
}
}
for (const dep of [
'async-trait',
'base64',
'bitfun-core-types',
'bzip2',
'chrono',
'futures-util',
'reqwest',
'sha2',
'sherpa-onnx',
'tar',
'thiserror',
'tokio-util',
'uuid',
]) {
const owner = servicesOptionalOwnerRule?.dependencies.find(
(dependency) => dependency.depName === dep,
);
if (!owner?.ownerFeatures.includes('speech')) {
throw new Error(`services-integrations speech must own optional dependency ${dep}`);
}
}
for (const dep of ['bitfun-services-core']) {
const owner = servicesOptionalOwnerRule?.dependencies.find(
(dependency) => dependency.depName === dep,
Expand Down Expand Up @@ -1130,6 +1152,32 @@ export function runManifestParserSelfTest({
if (!opencodeManifestRule) {
throw new Error('OpenCode adapter must have a forbidden manifest dependency rule');
}
const speechEngineManifestRule = forbiddenManifestDependencyRules.find((rule) =>
rule.dependencyNames?.includes('sherpa-onnx'),
);
if (!speechEngineManifestRule) {
throw new Error('speech engine must have a forbidden manifest dependency rule');
}
if (
!speechEngineManifestRule.allowManifestPaths?.includes(
'src/crates/services/services-integrations/Cargo.toml',
)
) {
throw new Error('speech engine manifest guard must allow only its integration service owner');
}
const coreSpeechOwnerRule = forbiddenContentUnderRules.find(
(rule) => rule.path === 'src/crates/assembly/core/src/service',
);
if (!coreSpeechOwnerRule) {
throw new Error('core service tree must have a speech ownership guard');
}
if (
!coreSpeechOwnerRule.patterns.some((pattern) =>
pattern.regex.source.includes('mod\\s+speech'),
)
) {
throw new Error('core speech ownership guard must forbid a speech service module');
}
for (const scanRoot of ['src/apps', 'src/crates', 'BitFun-Installer/src-tauri']) {
if (!opencodeManifestRule.scanRoots?.includes(scanRoot)) {
throw new Error(`OpenCode adapter manifest guard must scan ${scanRoot}`);
Expand Down
3 changes: 2 additions & 1 deletion src/apps/desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ bitfun-relay-service = { path = "../../crates/services/relay-service" }
bitfun-agent-runtime = { path = "../../crates/execution/agent-runtime" }
bitfun-runtime-ports = { path = "../../crates/contracts/runtime-ports" }
bitfun-product-domains = { path = "../../crates/contracts/product-domains", default-features = false }
bitfun-services-integrations = { path = "../../crates/services/services-integrations", default-features = false, features = ["canvas-runtime"] }
bitfun-services-integrations = { path = "../../crates/services/services-integrations", default-features = false, features = ["canvas-runtime", "speech"] }
bitfun-core-types = { path = "../../crates/contracts/core-types" }
bitfun-agent-tools = { path = "../../crates/execution/tool-contracts" }
bitfun-transport = { path = "../../crates/adapters/transport", features = ["tauri-adapter"] }
bitfun-events = { path = "../../crates/contracts/events" }
Expand Down
17 changes: 17 additions & 0 deletions src/apps/desktop/src/api/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use bitfun_core::service::remote_ssh::{
};
use bitfun_core::service::{announcement, config, filesystem, mcp, search, token_usage, workspace};
use bitfun_core::util::errors::*;
use bitfun_services_integrations::speech::{SpeechService, SpeechStoragePaths};

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
Expand Down Expand Up @@ -69,6 +70,7 @@ pub struct AppState {
pub config_service: Arc<config::ConfigService>,
pub filesystem_service: Arc<filesystem::FileSystemService>,
pub workspace_search_service: Arc<search::WorkspaceSearchService>,
pub speech_service: Arc<SpeechService>,
pub agent_registry: Arc<agents::AgentRegistry>,
pub mcp_service: Option<Arc<mcp::MCPService>>,
pub acp_client_service: Option<Arc<bitfun_acp::AcpClientService>>,
Expand Down Expand Up @@ -186,6 +188,20 @@ impl AppState {
std::path::PathBuf::from("worker_host.js")
}
};
let speech_service = Arc::new(SpeechService::new(SpeechStoragePaths::new(
path_manager.speech_models_dir(),
path_manager.speech_model_downloads_dir(),
path_manager.speech_input_temp_dir(),
)));
match speech_service.cleanup_orphaned_input_files().await {
Ok(removed) if removed > 0 => {
log::info!("Removed {removed} orphaned speech input audio files");
}
Ok(_) => {}
Err(error) => {
log::warn!("Failed to clean orphaned speech input audio files: {error}");
}
}
let js_worker_pool = JsWorkerPool::new(path_manager, worker_host_path)
.ok()
.map(Arc::new);
Expand Down Expand Up @@ -294,6 +310,7 @@ impl AppState {
config_service,
filesystem_service,
workspace_search_service,
speech_service,
agent_registry,
mcp_service,
acp_client_service,
Expand Down
1 change: 1 addition & 0 deletions src/apps/desktop/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub mod session_api;
pub mod session_storage_path;
pub mod skill_api;
pub mod snapshot_service;
pub mod speech_api;
pub mod ssh_api;
pub mod startchat_agent_api;
pub mod storage_commands;
Expand Down
27 changes: 27 additions & 0 deletions src/apps/desktop/src/api/remote_workspace_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,33 @@ pub const REMOTE_WORKSPACE_COMMAND_POLICIES: &[(&str, RemoteWorkspacePolicy)] =
RemoteWorkspacePolicy::LocalOnly,
),
("show_main_window", RemoteWorkspacePolicy::LocalOnly),
(
"speech_append_audio_chunk",
RemoteWorkspacePolicy::LocalOnly,
),
(
"speech_cancel_input_session",
RemoteWorkspacePolicy::LocalOnly,
),
(
"speech_cancel_model_download",
RemoteWorkspacePolicy::LocalOnly,
),
("speech_delete_model", RemoteWorkspacePolicy::LocalOnly),
(
"speech_download_model",
RemoteWorkspacePolicy::LocalOnly,
),
(
"speech_finish_input_session",
RemoteWorkspacePolicy::LocalOnly,
),
("speech_list_models", RemoteWorkspacePolicy::LocalOnly),
(
"speech_start_input_session",
RemoteWorkspacePolicy::LocalOnly,
),
("speech_verify_model", RemoteWorkspacePolicy::LocalOnly),
("ssh_connect", RemoteWorkspacePolicy::WorkspaceAgnostic),
(
"ssh_delete_connection",
Expand Down
Loading
Loading