diff --git a/AGENTS-CN.md b/AGENTS-CN.md index 4ce7bef07b..8b22ab7dbd 100644 --- a/AGENTS-CN.md +++ b/AGENTS-CN.md @@ -12,6 +12,7 @@ BitFun 是一个由 Rust workspace 与 React 前端组成的项目。 2. 桌面端开发优先使用 `pnpm run desktop:dev` — 提供完整热更新(Vite HMR + Rust 自动重编译并重启)。仅在需要更快冷启动且只迭代前端时使用 `pnpm run desktop:preview:debug`(Rust 改动不会自动重编译)。 3. 修改 Rust 文件后,优先使用 `pnpm run fmt:rs`,只格式化已改动或已暂存的 `.rs` 文件。只有在你明确需要更大范围格式化时才使用 `cargo fmt`。 4. 改完后按下方表格执行与改动范围匹配的最小验证。 +5. Rust workspace 依赖应在根清单中统一版本,而由消费 crate 按自身职责声明所需 feature;仅测试所需的 feature 应放入 `dev-dependencies`,受 crate feature 控制的服务能力应只在对应 feature 中启用。禁止使用 `tokio/full` 绕过依赖边界设计。 ## 分层模块索引 diff --git a/AGENTS.md b/AGENTS.md index cc848b2a56..cd8d9e60f5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,6 +12,11 @@ Repository rule: **keep product logic platform-agnostic, then expose it through 2. For desktop development, prefer `pnpm run desktop:dev` — it provides full hot-reload (Vite HMR + Rust auto-rebuild & restart). Use `pnpm run desktop:preview:debug` only when you need a faster cold-start for frontend-only iteration (Rust changes are not auto-rebuilt). 3. After Rust file changes, prefer `pnpm run fmt:rs` to format only changed or staged `.rs` files. Use `cargo fmt` only when you intentionally want broader formatting coverage. 4. After changes, run the smallest matching verification from the table below. +5. Workspace Rust dependencies own compatible versions, not broad capability + unions. Each crate must select the dependency features it actually uses; + keep test-only features in dev-dependencies and attach feature-gated service + capabilities to the owning crate feature. `tokio/full` is forbidden in the + root workspace and workspace members. ## Layered Module Index diff --git a/Cargo.toml b/Cargo.toml index 96bde7351a..15dc95ee16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,7 @@ undocumented_unsafe_blocks = "warn" # Shared dependency versions to keep all crates aligned [workspace.dependencies] # Async runtime -tokio = { version = "1.52", features = ["full"] } +tokio = { version = "1.52", default-features = false } tokio-stream = "0.1.18" tokio-util = "0.7.18" async-trait = "0.1.89" diff --git a/scripts/check-core-boundaries.test.mjs b/scripts/check-core-boundaries.test.mjs index b82f75c898..14a34b8465 100644 --- a/scripts/check-core-boundaries.test.mjs +++ b/scripts/check-core-boundaries.test.mjs @@ -1,6 +1,7 @@ import { access, readFile } from 'node:fs/promises'; import { spawnSync } from 'node:child_process'; import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; import test from 'node:test'; import assert from 'node:assert/strict'; @@ -10,6 +11,8 @@ import { findCargoLayerViolations, findFeatureGatedTestTargetViolations, findProductEntrypointCoreFeatureViolations, + findServicesIntegrationsTokioFeatureViolations, + findTokioDependencyFeatureViolations, } from './core-boundaries/cargo-dependency-boundaries.mjs'; import { crateLayoutRules } from './core-boundaries/rules/crate-layout.mjs'; @@ -30,6 +33,31 @@ const MODULES = [ const TEST_ROOT = join('C:', 'repo'); +function parseManifestFeatures(manifest) { + const section = manifest.match(/^\[features\]\s*$([\s\S]*?)(?=^\[|(?![\s\S]))/m)?.[1] ?? ''; + const features = {}; + + for (const match of section.matchAll(/^([a-zA-Z0-9_-]+)\s*=\s*\[([\s\S]*?)\]/gm)) { + features[match[1]] = [...match[2].matchAll(/["']([^"']+)["']/g)].map((value) => value[1]); + } + + return features; +} + +function removeFeatureValue(manifest, feature, value) { + const featurePattern = new RegExp(`^${feature}\\s*=\\s*\\[([\\s\\S]*?)\\]`, 'm'); + return manifest.replace(featurePattern, (definition) => + definition.replace(new RegExp(`\\s*["']${value}["'],?`), '')); +} + +function servicesIntegrationsPackage(manifest) { + return { + name: 'bitfun-services-integrations', + manifest_path: join(TEST_ROOT, 'src', 'crates', 'services', 'services-integrations', 'Cargo.toml'), + features: parseManifestFeatures(manifest), + }; +} + function packageAt(name, repoManifestPath, dependencies = []) { return { id: name, @@ -289,6 +317,55 @@ test('cargo layer checker rejects reverse edges across dependency kinds', () => assert.match(violations[5].message, /contract.*contracts.*->.*service.*services.*build dependency/); }); +test('workspace Tokio capabilities stay crate-owned', async () => { + const repositoryRoot = fileURLToPath(new URL('..', import.meta.url)); + const workspaceManifest = await readFile(new URL('../Cargo.toml', import.meta.url), 'utf8'); + const workspaceTokio = workspaceManifest.match(/^tokio\s*=\s*\{[^}]+\}/m)?.[0]; + + assert.ok(workspaceTokio, 'workspace dependencies must declare Tokio once'); + assert.match(workspaceTokio, /default-features\s*=\s*false/); + assert.doesNotMatch(workspaceTokio, /(?:^|,\s*)features\s*=/); + const packages = collectCargoMetadataPackages({ root: repositoryRoot }); + assert.deepEqual(findTokioDependencyFeatureViolations(packages), []); +}); + +test('services integrations Tokio owner contracts reject feature-union masking', async () => { + const manifest = await readFile( + new URL('../src/crates/services/services-integrations/Cargo.toml', import.meta.url), + 'utf8', + ); + const mutations = [ + ['plugin-source', 'tokio/time', /plugin-source missing effective Tokio capabilities: time/], + ['mcp', 'tokio/process', /mcp missing effective Tokio capabilities: process/], + ['miniapp-market', 'miniapp-runtime', /miniapp-market missing effective Tokio capabilities: fs/], + ['function-agents', 'git', /function-agents missing effective Tokio capabilities: fs/], + ['remote-ssh-concrete', 'remote-ssh', /remote-ssh-concrete missing effective Tokio capabilities: fs/], + ]; + + for (const [feature, value, expected] of mutations) { + const mutated = removeFeatureValue(manifest, feature, value); + assert.notEqual(mutated, manifest, `${feature} must own ${value} in the fixture`); + const messages = findServicesIntegrationsTokioFeatureViolations( + servicesIntegrationsPackage(mutated), + ).map((violation) => violation.message).join('\n'); + assert.match(messages, expected); + } +}); + +test('Cargo metadata Tokio policy catches table-style and renamed full dependencies', () => { + const pkg = packageAt('table-style', 'src/crates/services/table-style/Cargo.toml', [{ + name: 'tokio', + rename: 'async_runtime', + kind: null, + optional: false, + features: ['full'], + }]); + const violations = findTokioDependencyFeatureViolations([pkg]); + + assert.equal(violations.length, 1); + assert.match(violations[0].message, /table-style must not enable tokio\/full/); +}); + test('cargo layer checker allows documented downward and peer dependencies', () => { const packages = [ packageAt('entry', 'src/apps/example/Cargo.toml', [ diff --git a/scripts/core-boundaries/cargo-dependency-boundaries.mjs b/scripts/core-boundaries/cargo-dependency-boundaries.mjs index e3d32958c6..cb42ac30e5 100644 --- a/scripts/core-boundaries/cargo-dependency-boundaries.mjs +++ b/scripts/core-boundaries/cargo-dependency-boundaries.mjs @@ -57,6 +57,143 @@ function dependencyDescription(dependency) { return `${kind}${optional} dependency${target}`; } +const SERVICES_INTEGRATIONS_TOKIO_FEATURES = new Map([ + ['announcement', ['fs', 'sync']], + ['browser-control', ['time']], + ['canvas-runtime', ['fs']], + ['debug-log', ['rt']], + ['deep-research', ['fs']], + ['git', ['fs', 'io-util', 'macros', 'rt', 'time']], + ['file-watch', ['rt', 'sync']], + ['function-agents', ['fs', 'io-util', 'macros', 'rt', 'time']], + ['mcp', ['fs', 'io-util', 'net', 'process', 'rt', 'sync', 'time']], + ['miniapp-runtime', ['fs', 'io-util', 'net', 'process', 'rt', 'sync', 'time']], + ['miniapp-market', ['fs', 'io-util', 'net', 'process', 'rt', 'sync', 'time']], + ['plugin-source', ['fs', 'rt', 'sync', 'time']], + ['hook-import', ['fs', 'sync']], + ['remote-connect', ['fs', 'io-util', 'net', 'process', 'rt', 'sync', 'time']], + ['remote-ssh', ['fs', 'io-util', 'macros', 'net', 'process', 'rt', 'sync', 'time']], + ['remote-ssh-concrete', ['fs', 'io-util', 'macros', 'net', 'process', 'rt', 'sync', 'time']], + ['review-platform', ['fs', 'io-util', 'sync']], + ['speech', ['fs', 'io-util', 'macros', 'rt', 'sync']], + ['workspace-search', ['io-util', 'rt', 'sync', 'time']], + ['script-tool-runtime', ['io-util', 'process', 'rt', 'sync', 'time']], +]); + +// The installer is an excluded standalone workspace with its own Rust checks +// and packaging lifecycle; this policy governs the root product workspace. +const TOKIO_DEPENDENCY_POLICY_EXCLUDED_PACKAGES = new Set(['bitfun-installer']); + +function effectiveTokioCapabilities(feature, featureGraph, visiting = new Set()) { + if (visiting.has(feature)) { + return new Set(); + } + visiting.add(feature); + + const capabilities = new Set(); + for (const value of featureGraph[feature] ?? []) { + if (value.startsWith('tokio/')) { + capabilities.add(value.slice('tokio/'.length)); + } else if (Object.hasOwn(featureGraph, value)) { + for (const capability of effectiveTokioCapabilities(value, featureGraph, visiting)) { + capabilities.add(capability); + } + } + } + + visiting.delete(feature); + return capabilities; +} + +export function findServicesIntegrationsTokioFeatureViolations(pkg) { + const violations = []; + const featureGraph = pkg.features ?? {}; + + for (const [feature, expectedCapabilities] of SERVICES_INTEGRATIONS_TOKIO_FEATURES) { + if (!Object.hasOwn(featureGraph, feature)) { + violations.push({ + path: pkg.manifest_path, + line: 1, + message: `${pkg.name}:${feature} governed Tokio feature is missing`, + }); + continue; + } + + const actualCapabilities = [...effectiveTokioCapabilities(feature, featureGraph)].sort(); + const expected = [...expectedCapabilities].sort(); + const missing = expected.filter((capability) => !actualCapabilities.includes(capability)); + const unexpected = actualCapabilities.filter((capability) => !expected.includes(capability)); + if (missing.length > 0) { + violations.push({ + path: pkg.manifest_path, + line: 1, + message: `${pkg.name}:${feature} missing effective Tokio capabilities: ${missing.join(', ')}`, + }); + } + if (unexpected.length > 0) { + violations.push({ + path: pkg.manifest_path, + line: 1, + message: `${pkg.name}:${feature} has unexpected effective Tokio capabilities: ${unexpected.join(', ')}`, + }); + } + } + + for (const [feature, values] of Object.entries(featureGraph)) { + if (SERVICES_INTEGRATIONS_TOKIO_FEATURES.has(feature)) { + continue; + } + if (values.some((value) => value.startsWith('tokio/'))) { + violations.push({ + path: pkg.manifest_path, + line: 1, + message: `${pkg.name}:${feature} Tokio capabilities require an explicit owner contract`, + }); + } + } + + return violations; +} + +export function findTokioDependencyFeatureViolations(packages) { + const violations = []; + + for (const pkg of packages) { + if (TOKIO_DEPENDENCY_POLICY_EXCLUDED_PACKAGES.has(pkg.name)) { + continue; + } + for (const dependency of pkg.dependencies ?? []) { + if (dependency.name !== 'tokio') { + continue; + } + const features = dependency.features ?? []; + if (features.includes('full')) { + violations.push({ + path: pkg.manifest_path, + line: 1, + message: `${pkg.name} must not enable tokio/full for its ${dependencyDescription(dependency)}`, + }); + } + const featureOwnedIntegrationRuntime = + pkg.name === 'bitfun-services-integrations' + && (dependency.kind ?? null) === null; + if (features.length === 0 && !featureOwnedIntegrationRuntime) { + violations.push({ + path: pkg.manifest_path, + line: 1, + message: `${pkg.name} must declare explicit Tokio capabilities for its ${dependencyDescription(dependency)}`, + }); + } + } + + if (pkg.name === 'bitfun-services-integrations') { + violations.push(...findServicesIntegrationsTokioFeatureViolations(pkg)); + } + } + + return violations; +} + export function findCargoLayerViolations( packages, { root, crateLayoutRules }, @@ -590,6 +727,7 @@ export function checkCargoDependencyBoundaries({ root, crateLayoutRules }) { { root, crateLayoutRules }, ), ...findFeatureGatedTestTargetViolations(packages), + ...findTokioDependencyFeatureViolations(packages), ]; } diff --git a/scripts/core-boundaries/rules/source/required-rules.mjs b/scripts/core-boundaries/rules/source/required-rules.mjs index ba484e5097..eadde1c5c5 100644 --- a/scripts/core-boundaries/rules/source/required-rules.mjs +++ b/scripts/core-boundaries/rules/source/required-rules.mjs @@ -3879,6 +3879,10 @@ export const requiredContentRules = [ regex: /#\[cfg\(feature = "product-full"\)\]\s*pub mod agentic\b/s, message: 'agentic runtime must stay behind product-full for no-default builds', }, + { + regex: /#\[cfg\(feature = "product-full"\)\]\s*mod external_subagents\b/s, + message: 'external subagent product assembly must stay behind product-full', + }, { regex: /#\[cfg\(feature = "product-domains"\)\]\s*pub mod function_agents\b/s, message: 'function-agent product domain facade must stay behind product-domains', @@ -3893,6 +3897,23 @@ export const requiredContentRules = [ }, ], }, + { + path: 'src/crates/assembly/core/src/service/dispatch/mod.rs', + reason: + 'no-default dispatch cleanup must retain claimed records when the product worktree owner is unavailable', + patterns: [ + { + regex: + /#\[cfg\(feature = "product-full"\)\]\s*async fn release_baseline_claim\b/s, + message: 'worktree-backed dispatch claim release must stay behind product-full', + }, + { + regex: + /#\[cfg\(not\(feature = "product-full"\)\)\]\s*async fn release_baseline_claim\([^)]*\)\s*->\s*Result<\(\), DispatchStoreError>\s*\{\s*Err\(\s*DispatchStoreError::ClaimRelease\([\s\S]*?\)\s*\)\s*\}/s, + message: 'no-default dispatch claim release must fail closed', + }, + ], + }, { path: 'src/crates/assembly/core/src/infrastructure/mod.rs', reason: 'concrete AI adapter runtime and debug ingest HTTP server must stay out of no-default core builds', @@ -3982,7 +4003,7 @@ export const requiredContentRules = [ message: 'worktree topology owner import must stay gated for no-default builds', }, { - regex: /#\[cfg\(not\(feature = "service-integrations"\)\)\]\s*\{\s*let _ = workspace_root;\s*return None;\s*\}/s, + regex: /#\[cfg\(not\(feature = "service-integrations"\)\)\]\s*\{\s*let _ = \(workspace_root, freshness\);\s*return None;\s*\}/s, message: 'no-default worktree enrichment fallback must remain explicit', }, ], diff --git a/scripts/core-boundaries/self-test.mjs b/scripts/core-boundaries/self-test.mjs index ca1342fbbd..9c8a8eef73 100644 --- a/scripts/core-boundaries/self-test.mjs +++ b/scripts/core-boundaries/self-test.mjs @@ -3785,6 +3785,7 @@ export function runManifestParserSelfTest({ contracts: [ 'feature = "product-full"', 'pub mod agentic', + 'mod external_subagents', 'feature = "product-domains"', 'pub mod function_agents', 'pub mod miniapp', @@ -3847,6 +3848,15 @@ export function runManifestParserSelfTest({ 'session_store_migration_error', ], }, + { + path: 'src/crates/assembly/core/src/service/dispatch/mod.rs', + contracts: [ + 'feature = "product-full"', + 'not\\(feature = "product-full"\\)', + 'release_baseline_claim', + 'DispatchStoreError::ClaimRelease', + ], + }, { path: 'src/crates/interfaces/acp/src/client/manager.rs', contracts: ['CLIENT_STARTUP_TIMEOUT_SECS', 'startup_timeout_error_message', 'formats_startup_timeout_error_message'], @@ -4419,6 +4429,30 @@ export function runManifestParserSelfTest({ } } + const dispatchClaimReleaseRule = requiredContentRules + .find((rule) => rule.path === 'src/crates/assembly/core/src/service/dispatch/mod.rs') + ?.patterns.find((pattern) => pattern.message === 'no-default dispatch claim release must fail closed'); + if (!dispatchClaimReleaseRule) { + throw new Error('missing no-default dispatch claim release boundary rule'); + } + const failClosedDispatchRelease = ` +#[cfg(not(feature = "product-full"))] +async fn release_baseline_claim(release: BaselineClaimRelease) -> Result<(), DispatchStoreError> { + Err(DispatchStoreError::ClaimRelease(format!("job_id={}", release.job_id))) +}`; + const unsafeDispatchRelease = ` +#[cfg(not(feature = "product-full"))] +async fn release_baseline_claim(release: BaselineClaimRelease) -> Result<(), DispatchStoreError> { + let _ignored = DispatchStoreError::ClaimRelease(format!("job_id={}", release.job_id)); + Ok(()) +}`; + if (!dispatchClaimReleaseRule.regex.test(failClosedDispatchRelease)) { + throw new Error('no-default dispatch claim release rule must accept a direct fail-closed return'); + } + if (dispatchClaimReleaseRule.regex.test(unsafeDispatchRelease)) { + throw new Error('no-default dispatch claim release rule must reject a discarded error followed by success'); + } + const sessionControlRuleText = forbiddenRuleTextForPath( 'src/crates/assembly/core/src/agentic/tools/implementations/session_control_tool.rs', ); diff --git a/src/apps/cli/Cargo.toml b/src/apps/cli/Cargo.toml index 5bf22c9b90..d4d9bd4b57 100644 --- a/src/apps/cli/Cargo.toml +++ b/src/apps/cli/Cargo.toml @@ -73,7 +73,7 @@ libc = { workspace = true } arboard = { workspace = true } # Inherited from workspace -tokio = { workspace = true } +tokio = { workspace = true, features = ["macros", "process", "rt-multi-thread", "signal", "sync", "time"] } serde = { workspace = true } serde_json = { workspace = true } anyhow = { workspace = true } @@ -97,6 +97,7 @@ shlex = { workspace = true } [dev-dependencies] hex = { workspace = true } +tokio = { workspace = true, features = ["io-util", "net"] } [target.'cfg(windows)'.dependencies] windows = { workspace = true, features = ["Win32_Foundation", "Win32_System_Console"] } diff --git a/src/apps/desktop/Cargo.toml b/src/apps/desktop/Cargo.toml index 9c9a6c1b6e..bdc9ad00eb 100644 --- a/src/apps/desktop/Cargo.toml +++ b/src/apps/desktop/Cargo.toml @@ -52,7 +52,7 @@ alloc-no-stdlib = { workspace = true } alloc-stdlib = { workspace = true } # Inherited from workspace -tokio = { workspace = true } +tokio = { workspace = true, features = ["fs", "io-util", "macros", "net", "process", "rt-multi-thread", "sync", "time"] } serde = { workspace = true } serde_json = { workspace = true } anyhow = { workspace = true } diff --git a/src/apps/miniapp-market-server/Cargo.toml b/src/apps/miniapp-market-server/Cargo.toml index 3edc9a3520..a3071d25b6 100644 --- a/src/apps/miniapp-market-server/Cargo.toml +++ b/src/apps/miniapp-market-server/Cargo.toml @@ -13,7 +13,7 @@ path = "src/main.rs" anyhow = { workspace = true } axum = { workspace = true } bitfun-miniapp-market-service = { path = "../../crates/services/miniapp-market-service" } -tokio = { workspace = true } +tokio = { workspace = true, features = ["macros", "net", "rt-multi-thread", "signal"] } tracing = { workspace = true } tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } diff --git a/src/apps/sdk-host/Cargo.toml b/src/apps/sdk-host/Cargo.toml index f72eca53c7..bf80751689 100644 --- a/src/apps/sdk-host/Cargo.toml +++ b/src/apps/sdk-host/Cargo.toml @@ -18,7 +18,7 @@ bitfun-core = { path = "../../crates/assembly/core", default-features = false, f bitfun-sdk-host = { path = "../../crates/interfaces/sdk-host" } futures-util = { workspace = true } serde_json = { workspace = true } -tokio = { workspace = true } +tokio = { workspace = true, features = ["io-std", "io-util", "macros", "rt-multi-thread", "sync", "time"] } tokio-util = { workspace = true, features = ["codec"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } @@ -26,6 +26,7 @@ tracing-subscriber = { workspace = true } [dev-dependencies] rustls = { workspace = true } tempfile = "3" +tokio = { workspace = true, features = ["process"] } [lints] workspace = true diff --git a/src/apps/server/Cargo.toml b/src/apps/server/Cargo.toml index f2db9afe74..17ada21df9 100644 --- a/src/apps/server/Cargo.toml +++ b/src/apps/server/Cargo.toml @@ -17,7 +17,7 @@ axum = { workspace = true } tower-http = { workspace = true } # Inherited from workspace -tokio = { workspace = true, features = ["full"] } +tokio = { workspace = true, features = ["macros", "net", "rt-multi-thread", "sync"] } serde = { workspace = true } serde_json = { workspace = true } anyhow = { workspace = true } diff --git a/src/crates/adapters/agent-runtime-ipc/Cargo.toml b/src/crates/adapters/agent-runtime-ipc/Cargo.toml index 60bca6c02a..e1d2209288 100644 --- a/src/crates/adapters/agent-runtime-ipc/Cargo.toml +++ b/src/crates/adapters/agent-runtime-ipc/Cargo.toml @@ -21,7 +21,7 @@ serde = { workspace = true } serde_json = { workspace = true } sha2 = { workspace = true } thiserror = { workspace = true } -tokio = { workspace = true } +tokio = { workspace = true, features = ["io-util", "macros", "net", "rt", "sync", "time"] } uuid = { workspace = true } tempfile = { workspace = true } diff --git a/src/crates/adapters/ai-adapters/Cargo.toml b/src/crates/adapters/ai-adapters/Cargo.toml index 51e478d352..eca4915a41 100644 --- a/src/crates/adapters/ai-adapters/Cargo.toml +++ b/src/crates/adapters/ai-adapters/Cargo.toml @@ -28,7 +28,7 @@ reqwest = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } sha2 = { workspace = true, optional = true } -tokio = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt", "sync", "time"] } tokio-stream = { workspace = true } tokio-util = { workspace = true } urlencoding = { workspace = true } @@ -53,6 +53,9 @@ subscription-auth = [ "dep:keyring-core", "dep:libc", "dep:sha2", + "tokio/fs", + "tokio/io-util", + "tokio/net", "dep:uuid", "dep:windows-native-keyring-store", "dep:zbus-secret-service-keyring-store", @@ -61,6 +64,7 @@ subscription-auth = [ [dev-dependencies] axum = { workspace = true } bitfun-events = { path = "../../contracts/events" } +tokio = { workspace = true, features = ["io-util", "net", "rt-multi-thread"] } [lints] workspace = true diff --git a/src/crates/adapters/opencode-adapter/Cargo.toml b/src/crates/adapters/opencode-adapter/Cargo.toml index 85adc36789..804fd1e03e 100644 --- a/src/crates/adapters/opencode-adapter/Cargo.toml +++ b/src/crates/adapters/opencode-adapter/Cargo.toml @@ -32,7 +32,7 @@ url = { workspace = true } [dev-dependencies] bitfun-services-integrations = { path = "../../services/services-integrations", default-features = false, features = ["plugin-source", "script-tool-runtime"] } -tokio = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt"] } tempfile = { workspace = true } [lints] diff --git a/src/crates/adapters/webdriver/Cargo.toml b/src/crates/adapters/webdriver/Cargo.toml index 5be7c15d41..615fcd4908 100644 --- a/src/crates/adapters/webdriver/Cargo.toml +++ b/src/crates/adapters/webdriver/Cargo.toml @@ -12,7 +12,7 @@ embedded = [] [dependencies] anyhow = { workspace = true } axum = { workspace = true } -tokio = { workspace = true } +tokio = { workspace = true, features = ["net", "rt", "sync", "time"] } serde = { workspace = true } serde_json = { workspace = true } log = { workspace = true } diff --git a/src/crates/assembly/core/AGENTS-CN.md b/src/crates/assembly/core/AGENTS-CN.md index 80e92dd7b3..6d47c35959 100644 --- a/src/crates/assembly/core/AGENTS-CN.md +++ b/src/crates/assembly/core/AGENTS-CN.md @@ -50,6 +50,8 @@ SessionManager -> Session -> DialogTurn -> ModelRound - Remote/service 改动必须保持 external protocol lifecycle、workspace projection、scheduler/session restore、 terminal pre-warm 和 product execution 边界清晰。 - Feature 改动必须保持 `product-full` 作为兼容产品组装边界;默认能力选择只有在单独的 product matrix review 后才能变化。 +- 保持 `cargo check -p bitfun-core --no-default-features` 可用。产品专属模块必须由 owner feature 控制;轻量 facade + 操作在缺少产品 owner 时若无法安全完成,应明确 fail-closed 并保留持久化恢复状态,不得隐式启用 `product-full`。 ## 归属参考 @@ -78,7 +80,8 @@ SessionManager -> Session -> DialogTurn -> ModelRound ```bash cargo check --workspace -cargo test -p bitfun-core -- --nocapture +cargo check -p bitfun-core --no-default-features +cargo test -p bitfun-core --lib -- --nocapture node scripts/check-core-boundaries.mjs ``` diff --git a/src/crates/assembly/core/AGENTS.md b/src/crates/assembly/core/AGENTS.md index 43e467b22e..e0a5fa2486 100644 --- a/src/crates/assembly/core/AGENTS.md +++ b/src/crates/assembly/core/AGENTS.md @@ -79,6 +79,10 @@ SessionManager -> Session -> DialogTurn -> ModelRound - Feature work must keep `product-full` as the compatibility product assembly boundary unless a separate product matrix review changes default capability selection. +- Keep `cargo check -p bitfun-core --no-default-features` viable. Gate + product-only modules at their owner feature; if a light facade operation + cannot safely complete without a product owner, fail closed and preserve any + durable recovery state instead of enabling `product-full` implicitly. ## Owner References @@ -107,7 +111,8 @@ Use the smallest check that matches the touched behavior: ```bash cargo check --workspace -cargo test -p bitfun-core -- --nocapture +cargo check -p bitfun-core --no-default-features +cargo test -p bitfun-core --lib -- --nocapture node scripts/check-core-boundaries.mjs ``` diff --git a/src/crates/assembly/core/Cargo.toml b/src/crates/assembly/core/Cargo.toml index f2b1272dd3..0644fa2eec 100644 --- a/src/crates/assembly/core/Cargo.toml +++ b/src/crates/assembly/core/Cargo.toml @@ -11,7 +11,7 @@ crate-type = ["rlib"] [dependencies] # Inherit shared dependencies from workspace -tokio = { workspace = true } +tokio = { workspace = true, features = ["fs", "io-util", "macros", "net", "rt", "sync", "time"] } tokio-stream = { workspace = true } tokio-util = { workspace = true } async-trait = { workspace = true } @@ -241,6 +241,7 @@ ssh-remote = [ [dev-dependencies] tempfile = { workspace = true } +tokio = { workspace = true, features = ["rt-multi-thread"] } [[test]] name = "context_profile" @@ -258,6 +259,10 @@ required-features = ["product-full"] name = "remote_mcp_streamable_http" required-features = ["service-integrations"] +[[test]] +name = "remote_connect_host_boundary" +required-features = ["service-integrations"] + [build-dependencies] sha2 = { workspace = true } diff --git a/src/crates/assembly/core/src/lib.rs b/src/crates/assembly/core/src/lib.rs index a5eca6660e..a93fe70c94 100644 --- a/src/crates/assembly/core/src/lib.rs +++ b/src/crates/assembly/core/src/lib.rs @@ -21,6 +21,7 @@ pub mod external_mcp_import; mod external_mcp_tests; #[cfg(feature = "product-full")] pub mod external_sources; +#[cfg(feature = "product-full")] mod external_subagents; #[cfg(feature = "product-full")] mod external_tools; diff --git a/src/crates/assembly/core/src/service/config/service.rs b/src/crates/assembly/core/src/service/config/service.rs index 99486e0e16..1d340248fa 100644 --- a/src/crates/assembly/core/src/service/config/service.rs +++ b/src/crates/assembly/core/src/service/config/service.rs @@ -120,6 +120,7 @@ impl ConfigService { /// Atomically replaces one JSON configuration value when its current value /// still matches the caller's snapshot. The read, comparison, and persisted /// write share the existing manager write lock. + #[cfg(any(test, feature = "service-integrations"))] pub(crate) async fn compare_and_set_json_config( &self, path: &str, diff --git a/src/crates/assembly/core/src/service/dispatch/mod.rs b/src/crates/assembly/core/src/service/dispatch/mod.rs index a21cd4d121..4353d3b86d 100644 --- a/src/crates/assembly/core/src/service/dispatch/mod.rs +++ b/src/crates/assembly/core/src/service/dispatch/mod.rs @@ -51,6 +51,7 @@ const PROMPT_PREVIEW_CHARS: usize = 160; /// controller's baseline worktree. pub(super) const OUTBOUND_RESULTS_DIR: &str = ".results"; /// Where base bundles are built before being uploaded to a target. +#[cfg(feature = "ssh-remote")] const OUTBOUND_BUNDLES_DIR: &str = ".bundles"; /// Where the renderer's observer transcript cache lives. const OUTBOUND_TRANSCRIPTS_DIR: &str = ".transcripts"; @@ -64,6 +65,7 @@ const MAX_OUTBOUND_TRANSCRIPT_BYTES: usize = 8 * 1024 * 1024; #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg(feature = "ssh-remote")] struct DispatchTargetJobEntry { job_id: String, session_id: String, @@ -487,6 +489,7 @@ impl OutboundDispatchStore { /// /// Bundles hold repository contents, so they get the same private treatment /// as everything else the controller writes here. + #[cfg(feature = "ssh-remote")] pub(crate) async fn bundles_dir(&self) -> anyhow::Result { let bundles = self.root.join(OUTBOUND_BUNDLES_DIR); fs::create_dir_all(&bundles).await?; @@ -495,6 +498,7 @@ impl OutboundDispatchStore { } /// Owner-only staging directory for bundles fetched back from a target. + #[cfg(feature = "ssh-remote")] pub(crate) async fn results_dir(&self) -> anyhow::Result { let results = self.root.join(OUTBOUND_RESULTS_DIR); fs::create_dir_all(&results).await?; @@ -629,6 +633,7 @@ impl BaselineClaimRelease { /// The caller intentionally keeps the durable outbound record until this /// succeeds, so a moved repository or temporary registry error remains /// observable and retryable instead of silently stranding a claim. +#[cfg(feature = "product-full")] async fn release_baseline_claim(release: BaselineClaimRelease) -> Result<(), DispatchStoreError> { crate::service::worktree::WorktreeService::release_claim_for_worktree( &release.project_workspace_path, @@ -642,6 +647,14 @@ async fn release_baseline_claim(release: BaselineClaimRelease) -> Result<(), Dis }) } +#[cfg(not(feature = "product-full"))] +async fn release_baseline_claim(release: BaselineClaimRelease) -> Result<(), DispatchStoreError> { + Err(DispatchStoreError::ClaimRelease(format!( + "job_id={} error=product-full is required to release the baseline worktree claim", + release.job_id + ))) +} + /// Claim string a dispatch job holds on its baseline worktree. pub fn baseline_claim(job_id: &str) -> String { format!("dispatch:{job_id}") @@ -655,6 +668,7 @@ async fn remove_file_if_present(path: &Path) -> anyhow::Result<()> { } } +#[cfg(feature = "ssh-remote")] async fn adopt_target_jobs( store: &OutboundDispatchStore, target: &DispatchTarget, @@ -749,6 +763,7 @@ async fn adopt_target_jobs( /// Validate a path returned by the target without applying the controller /// process's host path semantics. The target may run POSIX while the controller /// runs Windows, or vice versa. +#[cfg(feature = "ssh-remote")] fn target_workspace_path_is_absolute(path: &str) -> bool { let path = path.trim(); if path.starts_with('/') { @@ -773,6 +788,7 @@ fn target_workspace_path_is_absolute(path: &str) -> bool { components.next().is_some() && components.next().is_some() } +#[cfg(feature = "ssh-remote")] fn same_target_identity_for_store(left: &DispatchTarget, right: &DispatchTarget) -> bool { match (left, right) { ( @@ -1046,6 +1062,43 @@ mod tests { .is_none()); } + #[cfg(not(feature = "product-full"))] + #[tokio::test] + async fn removing_a_claimed_record_without_product_full_fails_closed() { + let temp = tempfile::tempdir().expect("tempdir"); + let store = OutboundDispatchStore::new_in_root_for_tests(temp.path().to_path_buf()); + let mut record = OutboundDispatchRecord::new( + "job-no-product-full".to_string(), + target(), + "session-1".to_string(), + "/srv/app".to_string(), + "Summarize the repository", + "succeeded", + ) + .expect("record") + .with_source_workspace(Some("/linked/repo".to_string()), None); + record.baseline_worktree_id = Some("wt-baseline".to_string()); + record.baseline_project_workspace_path = Some("/stable/repo".to_string()); + store.bind_if_absent(&record).await.expect("persist"); + + let error = store + .remove("job-no-product-full") + .await + .expect_err("claim cleanup without the product owner must fail closed"); + let DispatchStoreError::ClaimRelease(message) = error else { + panic!("unexpected dispatch cleanup error: {error}"); + }; + assert!(message.contains("product-full")); + assert!( + store + .get("job-no-product-full") + .await + .expect("read retained record") + .is_some(), + "the durable record must remain available for a product-full retry" + ); + } + #[tokio::test] async fn expired_jobs_do_not_strand_their_result_bundles() { let temp = tempfile::tempdir().expect("temp dir"); @@ -1317,6 +1370,7 @@ mod tests { assert_eq!(record.prompt_preview.chars().count(), PROMPT_PREVIEW_CHARS); } + #[cfg(feature = "ssh-remote")] #[test] fn target_workspace_paths_use_target_platform_semantics() { assert!(target_workspace_path_is_absolute("/srv/app")); @@ -1331,6 +1385,7 @@ mod tests { assert!(!target_workspace_path_is_absolute(r"\\server")); } + #[cfg(feature = "ssh-remote")] #[tokio::test] async fn listing_a_target_adopts_observer_records_without_runtime_ownership() { let temp = tempfile::tempdir().expect("tempdir"); diff --git a/src/crates/assembly/core/src/service/workspace/manager.rs b/src/crates/assembly/core/src/service/workspace/manager.rs index f806b55396..66b58241ee 100644 --- a/src/crates/assembly/core/src/service/workspace/manager.rs +++ b/src/crates/assembly/core/src/service/workspace/manager.rs @@ -452,7 +452,7 @@ impl WorkspaceInfo { ) -> Option { #[cfg(not(feature = "service-integrations"))] { - let _ = workspace_root; + let _ = (workspace_root, freshness); return None; } diff --git a/src/crates/assembly/core/src/service/workspace/service.rs b/src/crates/assembly/core/src/service/workspace/service.rs index 1181d88d13..7483187bab 100644 --- a/src/crates/assembly/core/src/service/workspace/service.rs +++ b/src/crates/assembly/core/src/service/workspace/service.rs @@ -284,7 +284,7 @@ impl WorkspaceService { Ok(service) } - #[cfg(test)] + #[cfg(all(test, feature = "product-full"))] pub(crate) async fn new_for_test_path_manager(path_manager: Arc) -> Self { path_manager .initialize_user_directories() diff --git a/src/crates/assembly/core/tests/remote_connect_host_boundary.rs b/src/crates/assembly/core/tests/remote_connect_host_boundary.rs index dcfc199fe4..938d0baf3e 100644 --- a/src/crates/assembly/core/tests/remote_connect_host_boundary.rs +++ b/src/crates/assembly/core/tests/remote_connect_host_boundary.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "service-integrations")] + use bitfun_core::service::remote_connect::embedded_relay_host::EmbeddedRelayHost; use bitfun_core::service::remote_connect::{ ConnectionMethod, RemoteConnectConfig, RemoteConnectService, diff --git a/src/crates/assembly/external-sources/Cargo.toml b/src/crates/assembly/external-sources/Cargo.toml index 8debb8fa32..3dcdc619c8 100644 --- a/src/crates/assembly/external-sources/Cargo.toml +++ b/src/crates/assembly/external-sources/Cargo.toml @@ -13,7 +13,10 @@ crate-type = ["rlib"] bitfun-product-domains = { path = "../../contracts/product-domains", default-features = false, features = ["external-sources"] } futures = { workspace = true } log = { workspace = true } -tokio = { workspace = true } +tokio = { workspace = true, features = ["rt", "sync", "time"] } + +[dev-dependencies] +tokio = { workspace = true, features = ["macros"] } [lints] workspace = true diff --git a/src/crates/assembly/product-capabilities/Cargo.toml b/src/crates/assembly/product-capabilities/Cargo.toml index aa18d8a35c..3e32fcb59c 100644 --- a/src/crates/assembly/product-capabilities/Cargo.toml +++ b/src/crates/assembly/product-capabilities/Cargo.toml @@ -18,7 +18,7 @@ bitfun-tool-packs = { path = "../../execution/tool-provider-groups", default-fea [dev-dependencies] async-trait = { workspace = true } bitfun-agent-runtime = { path = "../../execution/agent-runtime" } -tokio = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt"] } [lints] workspace = true diff --git a/src/crates/contracts/product-domains/Cargo.toml b/src/crates/contracts/product-domains/Cargo.toml index a79519b0a8..9c2656e9c5 100644 --- a/src/crates/contracts/product-domains/Cargo.toml +++ b/src/crates/contracts/product-domains/Cargo.toml @@ -62,7 +62,7 @@ external-sources = ["hex", "hmac", "sha2", "url"] product-full = ["plugin-source", "miniapp", "function-agents", "external-sources"] [dev-dependencies] -tokio = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt"] } [lints] workspace = true diff --git a/src/crates/contracts/runtime-ports/Cargo.toml b/src/crates/contracts/runtime-ports/Cargo.toml index 4d9d67f974..186e213668 100644 --- a/src/crates/contracts/runtime-ports/Cargo.toml +++ b/src/crates/contracts/runtime-ports/Cargo.toml @@ -16,7 +16,7 @@ bitfun-product-domains = { path = "../product-domains", default-features = false bitfun-core-types = { path = "../core-types" } serde = { workspace = true } serde_json = { workspace = true } -tokio = { workspace = true } +tokio = { workspace = true, features = ["sync"] } tokio-util = { workspace = true } [features] @@ -24,7 +24,7 @@ default = [] permission = ["dep:bitfun-product-domains"] [dev-dependencies] -tokio = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt"] } [lints] workspace = true diff --git a/src/crates/execution/agent-runtime/Cargo.toml b/src/crates/execution/agent-runtime/Cargo.toml index 6898f58a24..689aab3e59 100644 --- a/src/crates/execution/agent-runtime/Cargo.toml +++ b/src/crates/execution/agent-runtime/Cargo.toml @@ -31,10 +31,11 @@ serde_yaml = { workspace = true } sha2 = { workspace = true } thiserror = { workspace = true } uuid = { workspace = true } -tokio = { workspace = true } +tokio = { workspace = true, features = ["io-util", "macros", "process", "rt", "sync", "time"] } tokio-util = { workspace = true } [dev-dependencies] +tokio = { workspace = true, features = ["rt-multi-thread"] } [lints] workspace = true diff --git a/src/crates/execution/agent-stream/Cargo.toml b/src/crates/execution/agent-stream/Cargo.toml index f8c4771232..47245168bb 100644 --- a/src/crates/execution/agent-stream/Cargo.toml +++ b/src/crates/execution/agent-stream/Cargo.toml @@ -19,7 +19,7 @@ bitfun-tool-call-jsonrepair = { path = "../tool-call-jsonrepair" } log = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -tokio = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt", "sync", "time"] } tokio-util = { workspace = true } uuid = { workspace = true } diff --git a/src/crates/execution/harness/Cargo.toml b/src/crates/execution/harness/Cargo.toml index d711d7f0c9..68bc17f749 100644 --- a/src/crates/execution/harness/Cargo.toml +++ b/src/crates/execution/harness/Cargo.toml @@ -14,7 +14,7 @@ async-trait = { workspace = true } thiserror = { workspace = true } [dev-dependencies] -tokio = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt"] } [lints] workspace = true diff --git a/src/crates/execution/plugin-runtime-client/Cargo.toml b/src/crates/execution/plugin-runtime-client/Cargo.toml index 83615c3796..2bb8dbab52 100644 --- a/src/crates/execution/plugin-runtime-client/Cargo.toml +++ b/src/crates/execution/plugin-runtime-client/Cargo.toml @@ -12,7 +12,10 @@ crate-type = ["rlib"] [dependencies] async-trait = { workspace = true } bitfun-runtime-ports = { path = "../../contracts/runtime-ports" } -tokio = { workspace = true } +tokio = { workspace = true, features = ["sync", "time"] } + +[dev-dependencies] +tokio = { workspace = true, features = ["macros", "rt"] } [lints] workspace = true diff --git a/src/crates/execution/runtime-services/Cargo.toml b/src/crates/execution/runtime-services/Cargo.toml index 355caf6798..b94728d281 100644 --- a/src/crates/execution/runtime-services/Cargo.toml +++ b/src/crates/execution/runtime-services/Cargo.toml @@ -18,9 +18,10 @@ log = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } -tokio = { workspace = true } +tokio = { workspace = true, features = ["sync"] } [dev-dependencies] +tokio = { workspace = true, features = ["macros", "rt"] } [lints] workspace = true diff --git a/src/crates/execution/tool-contracts/Cargo.toml b/src/crates/execution/tool-contracts/Cargo.toml index f4f6f32625..5a1f594ec2 100644 --- a/src/crates/execution/tool-contracts/Cargo.toml +++ b/src/crates/execution/tool-contracts/Cargo.toml @@ -18,7 +18,7 @@ async-trait = { workspace = true } indexmap = { workspace = true } [dev-dependencies] -tokio = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt"] } [lints] workspace = true diff --git a/src/crates/execution/tool-execution/Cargo.toml b/src/crates/execution/tool-execution/Cargo.toml index 9bba33531c..74dec05264 100644 --- a/src/crates/execution/tool-execution/Cargo.toml +++ b/src/crates/execution/tool-execution/Cargo.toml @@ -23,7 +23,7 @@ log = { workspace = true } regex = { workspace = true, optional = true } serde = { workspace = true } serde_json = { workspace = true } -tokio = { workspace = true } +tokio = { workspace = true, features = ["rt", "sync", "time"] } tokio-util = { workspace = true } vte = { workspace = true, features = ["ansi"] } @@ -36,5 +36,8 @@ windows = { workspace = true, features = [ [target.'cfg(not(target_env = "ohos"))'.dependencies] readability-js = { version = "0.1.5", optional = true } +[dev-dependencies] +tokio = { workspace = true, features = ["macros"] } + [lints] workspace = true diff --git a/src/crates/interfaces/acp/Cargo.toml b/src/crates/interfaces/acp/Cargo.toml index 519fe4d5b4..62d389c768 100644 --- a/src/crates/interfaces/acp/Cargo.toml +++ b/src/crates/interfaces/acp/Cargo.toml @@ -16,7 +16,7 @@ bitfun-events = { path = "../../contracts/events" } bitfun-core-types = { path = "../../contracts/core-types" } agent-client-protocol = { workspace = true } -tokio = { workspace = true } +tokio = { workspace = true, features = ["fs", "io-std", "io-util", "macros", "process", "rt", "sync", "time"] } tokio-util = { workspace = true, features = ["compat"] } futures = { workspace = true } async-trait = { workspace = true } @@ -28,5 +28,8 @@ log = { workspace = true } uuid = { workspace = true } sha2 = { workspace = true } +[dev-dependencies] +tokio = { workspace = true, features = ["rt-multi-thread"] } + [lints] workspace = true diff --git a/src/crates/interfaces/sdk-host/Cargo.toml b/src/crates/interfaces/sdk-host/Cargo.toml index c3672d6f7a..355f9b03f9 100644 --- a/src/crates/interfaces/sdk-host/Cargo.toml +++ b/src/crates/interfaces/sdk-host/Cargo.toml @@ -17,7 +17,7 @@ bitfun-runtime-ports = { path = "../../contracts/runtime-ports" } futures-util = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -tokio = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt", "sync", "time"] } tokio-util = { workspace = true, features = ["codec"] } tracing = { workspace = true } uuid = { workspace = true } diff --git a/src/crates/services/miniapp-market-service/Cargo.toml b/src/crates/services/miniapp-market-service/Cargo.toml index 42dbcf9f82..a37003b8ae 100644 --- a/src/crates/services/miniapp-market-service/Cargo.toml +++ b/src/crates/services/miniapp-market-service/Cargo.toml @@ -25,7 +25,7 @@ sha2 = { workspace = true } similar = { workspace = true } sqlx = { version = "0.8", default-features = false, features = ["runtime-tokio", "sqlite"] } thiserror = { workspace = true } -tokio = { workspace = true } +tokio = { workspace = true, features = ["fs", "rt", "time"] } tower-http = { version = "0.6.11", features = ["fs", "set-header", "trace"] } tracing = { workspace = true } url = { workspace = true } @@ -35,6 +35,7 @@ zip = { workspace = true } [dev-dependencies] tempfile = { workspace = true } +tokio = { workspace = true, features = ["macros"] } tower = { version = "0.5", features = ["util"] } [lints] diff --git a/src/crates/services/services-core/Cargo.toml b/src/crates/services/services-core/Cargo.toml index 9b9aaac164..6681b52132 100644 --- a/src/crates/services/services-core/Cargo.toml +++ b/src/crates/services/services-core/Cargo.toml @@ -15,7 +15,7 @@ async-trait = { workspace = true, optional = true } bitfun-core-types = { path = "../../contracts/core-types" } bitfun-events = { path = "../../contracts/events" } bitfun-runtime-ports = { path = "../../contracts/runtime-ports", optional = true } -tokio = { workspace = true } +tokio = { workspace = true, features = ["fs", "io-util", "process", "rt", "sync", "time"] } serde = { workspace = true } serde_json = { workspace = true } serde_yaml = { workspace = true, optional = true } @@ -64,6 +64,7 @@ dispatch-workspace = ["dep:anyhow"] [dev-dependencies] filetime = { workspace = true } tempfile = { workspace = true } +tokio = { workspace = true, features = ["macros"] } [[test]] name = "markdown_owner_contracts" diff --git a/src/crates/services/services-integrations/Cargo.toml b/src/crates/services/services-integrations/Cargo.toml index 1dbb9a450e..84fc89f9df 100644 --- a/src/crates/services/services-integrations/Cargo.toml +++ b/src/crates/services/services-integrations/Cargo.toml @@ -94,17 +94,18 @@ zbus-secret-service-keyring-store = { workspace = true, optional = true } [features] default = [] -announcement = ["reqwest"] -browser-control = ["anyhow", "bitfun-services-core", "dirs", "reqwest", "thiserror"] +announcement = ["reqwest", "tokio/fs", "tokio/sync"] +browser-control = ["anyhow", "bitfun-services-core", "dirs", "reqwest", "thiserror", "tokio/time"] canvas-runtime = [ "dep:bitfun-product-domains", "oxc", "sha2", + "tokio/fs", "urlencoding", "uuid", ] -debug-log = ["anyhow", "chrono", "reqwest", "uuid"] -deep-research = ["bitfun-agent-runtime"] +debug-log = ["anyhow", "chrono", "reqwest", "tokio/rt", "uuid"] +deep-research = ["bitfun-agent-runtime", "tokio/fs"] git = [ "async-trait", "bitfun-runtime-ports", @@ -112,8 +113,13 @@ git = [ "chrono", "git2", "thiserror", + "tokio/fs", + "tokio/io-util", + "tokio/macros", + "tokio/rt", + "tokio/time", ] -file-watch = ["notify"] +file-watch = ["notify", "tokio/rt", "tokio/sync"] function-agents = [ "bitfun-product-domains/function-agents", "dep:bitfun-product-domains", @@ -134,6 +140,13 @@ mcp = [ "rmcp/transport-streamable-http-client-reqwest", "sha2", "sse-stream", + "tokio/fs", + "tokio/io-util", + "tokio/net", + "tokio/process", + "tokio/rt", + "tokio/sync", + "tokio/time", "url", "process-tree", ] @@ -144,6 +157,13 @@ miniapp-runtime = [ "dep:bitfun-product-domains", "dirs", "reqwest", + "tokio/fs", + "tokio/io-util", + "tokio/net", + "tokio/process", + "tokio/rt", + "tokio/sync", + "tokio/time", "uuid", "which", ] @@ -171,6 +191,10 @@ plugin-source = [ "libc", "sha2", "thiserror", + "tokio/fs", + "tokio/rt", + "tokio/sync", + "tokio/time", "uuid", "windows", ] @@ -182,6 +206,8 @@ hook-import = [ "hex", "sha2", "thiserror", + "tokio/fs", + "tokio/sync", "uuid", ] remote-connect = [ @@ -210,6 +236,13 @@ remote-connect = [ "rustls-native-certs", "schannel", "sha2", + "tokio/fs", + "tokio/io-util", + "tokio/net", + "tokio/process", + "tokio/rt", + "tokio/sync", + "tokio/time", "tokio-tungstenite", "urlencoding", "uuid", @@ -224,6 +257,14 @@ remote-ssh = [ "sha2", "terminal-core", "thiserror", + "tokio/fs", + "tokio/io-util", + "tokio/macros", + "tokio/net", + "tokio/process", + "tokio/rt", + "tokio/sync", + "tokio/time", "tokio-util", ] remote-ssh-concrete = [ @@ -256,6 +297,9 @@ review-platform = [ "reqwest", "sha2", "thiserror", + "tokio/fs", + "tokio/io-util", + "tokio/sync", "urlencoding", "windows", ] @@ -271,6 +315,11 @@ speech = [ "sherpa-onnx", "tar", "thiserror", + "tokio/fs", + "tokio/io-util", + "tokio/macros", + "tokio/rt", + "tokio/sync", "tokio-util", "uuid", ] @@ -279,10 +328,24 @@ workspace-search = [ "bitfun-services-core", "dunce", "thiserror", + "tokio/io-util", + "tokio/rt", + "tokio/sync", + "tokio/time", "which", ] process-tree = ["bitfun-services-core"] -script-tool-runtime = ["async-trait", "bitfun-runtime-ports", "process-tree", "which"] +script-tool-runtime = [ + "async-trait", + "bitfun-runtime-ports", + "process-tree", + "tokio/io-util", + "tokio/process", + "tokio/rt", + "tokio/sync", + "tokio/time", + "which", +] web-tools = ["reqwest", "thiserror"] product-full = [ "announcement", @@ -309,7 +372,7 @@ ssh_config = ["dep:ssh_config"] [dev-dependencies] tempfile = { workspace = true } -tokio = { workspace = true, features = ["test-util"] } +tokio = { workspace = true, features = ["macros", "rt", "test-util"] } [[test]] name = "debug_log_owner_contracts" diff --git a/src/crates/services/services-integrations/src/miniapp/host_dispatch.rs b/src/crates/services/services-integrations/src/miniapp/host_dispatch.rs index 9916c082b6..b05152b9b6 100644 --- a/src/crates/services/services-integrations/src/miniapp/host_dispatch.rs +++ b/src/crates/services/services-integrations/src/miniapp/host_dispatch.rs @@ -781,7 +781,7 @@ mod tests { )); } - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + #[tokio::test] async fn host_shell_exec_runs_git_with_workspace_cwd() { let workspace_dir = Path::new(env!("CARGO_MANIFEST_DIR")); let perms = MiniAppPermissions { diff --git a/src/crates/services/services-integrations/src/plugin_source.rs b/src/crates/services/services-integrations/src/plugin_source.rs index daa0587edf..fa50a80c20 100644 --- a/src/crates/services/services-integrations/src/plugin_source.rs +++ b/src/crates/services/services-integrations/src/plugin_source.rs @@ -3154,7 +3154,7 @@ fn declared_parent_metadata_issue_code(kind: ErrorKind) -> PluginSourceIssueCode mod tests { use super::{ build_snapshot, charge_scanned_read, declared_parent_metadata_issue_code, - map_activation_store_error, map_load_store_error, + map_activation_store_error, map_load_store_error, native_path_identity, persist_trust_bytes_with_parent_sync, read_bounded_reader, read_scanned_file, replace_file_atomically, trust_file_identity, trust_store_issue_code, workspace_scope, ManagedPluginSourceError, ManagedPluginSourceService, OperationScanBudget, diff --git a/src/crates/services/terminal/Cargo.toml b/src/crates/services/terminal/Cargo.toml index c0f1c39340..3d2ea05120 100644 --- a/src/crates/services/terminal/Cargo.toml +++ b/src/crates/services/terminal/Cargo.toml @@ -13,7 +13,7 @@ path = "src/lib.rs" bitfun-runtime-ports = { path = "../../contracts/runtime-ports" } # Async runtime -tokio = { workspace = true } +tokio = { workspace = true, features = ["io-util", "macros", "process", "rt", "sync", "time"] } tokio-stream = { workspace = true } futures = { workspace = true }