From 96cf1310962943824fa824ffad904835232aabf6 Mon Sep 17 00:00:00 2001 From: limityan Date: Sat, 30 May 2026 19:46:16 +0800 Subject: [PATCH] refactor: add runtime owner boundaries --- AGENTS-CN.md | 1 + AGENTS.md | 1 + Cargo.toml | 1 + .../agent-runtime-services-design.md | 22 ++- docs/architecture/core-decomposition.md | 7 +- docs/plans/core-decomposition-plan.md | 43 +++++ scripts/check-core-boundaries.mjs | 111 ++++++++++- src/crates/agent-runtime/AGENTS.md | 24 +++ src/crates/agent-runtime/Cargo.toml | 13 ++ src/crates/agent-runtime/src/lib.rs | 6 + src/crates/agent-runtime/src/scheduler.rs | 52 ++++++ .../tests/scheduler_contracts.rs | 56 ++++++ src/crates/core/AGENTS-CN.md | 8 +- src/crates/core/AGENTS.md | 16 +- src/crates/core/Cargo.toml | 3 + .../src/agentic/coordination/scheduler.rs | 34 ++-- src/crates/core/src/service_agent_runtime.rs | 15 +- src/crates/runtime-ports/src/lib.rs | 176 +++++++++++++++++- .../runtime-services/src/test_support.rs | 53 +++++- .../tests/runtime_services_contracts.rs | 29 ++- src/crates/services-integrations/AGENTS.md | 17 +- .../src/remote_connect.rs | 133 ++----------- 22 files changed, 651 insertions(+), 170 deletions(-) create mode 100644 src/crates/agent-runtime/AGENTS.md create mode 100644 src/crates/agent-runtime/Cargo.toml create mode 100644 src/crates/agent-runtime/src/lib.rs create mode 100644 src/crates/agent-runtime/src/scheduler.rs create mode 100644 src/crates/agent-runtime/tests/scheduler_contracts.rs diff --git a/AGENTS-CN.md b/AGENTS-CN.md index 611112375..17ff2f6fd 100644 --- a/AGENTS-CN.md +++ b/AGENTS-CN.md @@ -19,6 +19,7 @@ BitFun 是一个由 Rust workspace 与 React 前端组成的项目。 |---|---|---| | Core(产品逻辑) | `src/crates/core` | [AGENTS.md](src/crates/core/AGENTS.md) | | 已拆出的 core 支撑 crate | `src/crates/{core-types,agent-stream,runtime-ports,runtime-services,terminal,tool-runtime}` | (使用 core 指南) | +| Agent runtime owner crate | `src/crates/agent-runtime` | [AGENTS.md](src/crates/agent-runtime/AGENTS.md) | | Service core owner crate | `src/crates/services-core` | [AGENTS.md](src/crates/services-core/AGENTS.md) | | Service integrations owner crate | `src/crates/services-integrations` | [AGENTS.md](src/crates/services-integrations/AGENTS.md) | | Agent tool contracts | `src/crates/agent-tools` | [AGENTS.md](src/crates/agent-tools/AGENTS.md) | diff --git a/AGENTS.md b/AGENTS.md index b130df399..c130b227b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,6 +19,7 @@ Repository rule: **keep product logic platform-agnostic, then expose it through |---|---|---| | Core (product logic) | `src/crates/core` | [AGENTS.md](src/crates/core/AGENTS.md) | | Extracted core support | `src/crates/{core-types,agent-stream,runtime-ports,runtime-services,terminal,tool-runtime}` | (use core guide) | +| Agent runtime owner crate | `src/crates/agent-runtime` | [AGENTS.md](src/crates/agent-runtime/AGENTS.md) | | Service core owner crate | `src/crates/services-core` | [AGENTS.md](src/crates/services-core/AGENTS.md) | | Service integrations owner crate | `src/crates/services-integrations` | [AGENTS.md](src/crates/services-integrations/AGENTS.md) | | Agent tool contracts | `src/crates/agent-tools` | [AGENTS.md](src/crates/agent-tools/AGENTS.md) | diff --git a/Cargo.toml b/Cargo.toml index 53c8cd58a..f3dc7699e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "src/crates/events", "src/crates/ai-adapters", "src/crates/agent-stream", + "src/crates/agent-runtime", "src/crates/runtime-ports", "src/crates/runtime-services", "src/crates/services-core", diff --git a/docs/architecture/agent-runtime-services-design.md b/docs/architecture/agent-runtime-services-design.md index 9fac82f35..592893d91 100644 --- a/docs/architecture/agent-runtime-services-design.md +++ b/docs/architecture/agent-runtime-services-design.md @@ -1,10 +1,10 @@ # Agent Runtime SDK 与 Runtime Services 设计 本文是 [`core-decomposition.md`](core-decomposition.md) 的开发设计文档,描述目标模块、 -接口、crate 内部结构和迁移保护。`bitfun-runtime-services` 已建立 PR1 基础壳层, -当前只承载 typed service bundle、builder、provider registry、capability availability 和 -fake provider;`bitfun-agent-runtime`、`bitfun-harness` 仍是目标 crate,在实际创建前不得把它们当作 -已完成事实。 +接口、crate 内部结构和迁移保护。`bitfun-runtime-services` 已建立 typed service bundle、 +builder、provider registry、capability availability 和 fake provider 基础;`bitfun-agent-runtime` +已创建并只承接可独立构建的 scheduler/background delivery 纯决策。`bitfun-harness` 仍是目标 crate; +未迁移的 session manager、prompt loop、subagent registry 和 concrete scheduler lifecycle 不得被描述为已完成。 ## 1. 设计目标与边界 @@ -24,7 +24,7 @@ bitfun-runtime-ports bitfun-runtime-services # PR1 基础壳层 bitfun-agent-tools tool-runtime -bitfun-agent-runtime # 目标 +bitfun-agent-runtime # 已创建,当前仅承接 scheduler/background delivery 决策 bitfun-harness # 目标 bitfun-services-core bitfun-services-integrations @@ -91,7 +91,7 @@ bitfun-runtime-services - 只有当 owner 边界、旧路径兼容、focused tests、依赖收益和 boundary check 都能同时落地时,才创建新的目标 crate。 - `bitfun-runtime-services` 已按该准入建立基础壳层;继续扩展时仍必须保持 typed builder、本地 service、remote service 和 fake provider 三类注入路径可测试。 -- `bitfun-agent-runtime` 的创建前提是 session / turn / scheduler / prompt loop 中至少一个 owner 可以脱离 `bitfun-core` 构建,并有旧路径 facade。 +- `bitfun-agent-runtime` 已通过 scheduler/background delivery 纯决策满足创建准入;继续扩展时仍必须保持旧路径 facade、focused tests 和 boundary check。 - `bitfun-harness` 的创建前提是至少两个 workflow 可以通过 provider contract 注册,例如 Deep Review 与 MiniApp / DeepResearch。 - 若目标 crate 只能承接单个 helper 或只能通过 `bitfun-core` 才能测试,继续留在迁移期 facade,不提前拆 crate。 @@ -235,7 +235,15 @@ Remote ports 的边界: ### 3.1 Agent Runtime SDK -目标 crate:`bitfun-agent-runtime`。 +当前 crate:`bitfun-agent-runtime`。 + +当前已承接范围: + +- background delivery 状态决策:Processing 注入当前运行 turn;Missing / Idle / Error 提交 agent-session follow-up turn。 + +仍留在 `bitfun-core` 的范围: + +- concrete scheduler 生命周期、session manager、turn id 生成、injection buffer、submit 执行、prompt loop、subagent registry 和 post-turn hook。 职责: diff --git a/docs/architecture/core-decomposition.md b/docs/architecture/core-decomposition.md index 7c048fec0..36ff48419 100644 --- a/docs/architecture/core-decomposition.md +++ b/docs/architecture/core-decomposition.md @@ -248,7 +248,8 @@ Computer Use 等能力的组合边界。它负责定义一个产品能力需要 Agent 运行时 SDK(Agent Runtime SDK)是可嵌入的 agent kernel,负责 session、turn、scheduler、prompt loop、subagent、 background task、permission coordination 和 runtime events。它只依赖稳定契约、tool runtime 和注入的 service ports,不感知 Desktop、CLI、Remote、ACP、Tauri 或 Web UI。当前主体仍在 `bitfun-core`, -目标归属是 `bitfun-agent-runtime`。 +但 `bitfun-agent-runtime` 已开始承接可独立构建的 scheduler/background delivery 纯决策;concrete +scheduler lifecycle、session manager、prompt loop 和 subagent registry 仍未外移。 ### 7.6 工具运行时(Tool Runtime) @@ -263,7 +264,7 @@ service ports,不感知 Desktop、CLI、Remote、ACP、Tauri 或 Web UI。当 filesystem、workspace、session store、Git、terminal、network、MCP catalog、remote connection / projection 等端口,不执行产品命令, 不作为无类型 service locator,也不创建平台实现。当前相关 crate 包括 `bitfun-runtime-ports`、 -`bitfun-services-core`、`bitfun-services-integrations` 和 `bitfun-core` 中的 service 接线代码。 +`bitfun-runtime-services`、`bitfun-services-core`、`bitfun-services-integrations` 和 `bitfun-core` 中的 service 接线代码。 ### 7.8 具体实现层(Concrete Integrations) @@ -282,6 +283,8 @@ SSH、relay、本地隧道、远端 OS 差异和认证方式属于具体 Remote 稳定契约层提供跨层共享的数据结构和接口语言,包括 DTO、event、permission facts、artifact refs、identity 和 port traits。它只描述事实和能力,不包含 IO、网络、进程、UI、runtime manager 或产品策略。当前相关 crate 包括 `bitfun-core-types`、`bitfun-events` 和 `bitfun-runtime-ports`。 +当前 remote workspace facts、remote session metadata、remote workspace file projection DTO 和 remote workspace/projection +host trait 已归入 `bitfun-runtime-ports`,`bitfun-services-integrations::remote_connect` 保留旧路径 re-export。 ## 8. 接口与实现关系 diff --git a/docs/plans/core-decomposition-plan.md b/docs/plans/core-decomposition-plan.md index 27021d481..d5fd986a6 100644 --- a/docs/plans/core-decomposition-plan.md +++ b/docs/plans/core-decomposition-plan.md @@ -86,6 +86,49 @@ PR1 是后续高风险迁移的前置门禁,目标是提供可测试的 typed PR1 不迁移任何 concrete service owner,因此预期不会修改产品行为、默认能力集合、权限语义、工具曝光、事件语义、session 生命周期或构建脚本。 +### 4.2 PR2 + PR3 合并实施计划 + +本次 PR 合并推进 PR2 和 PR3,但仍按两个 owner 主题顺序实施,避免把 remote provider、agent scheduler +和产品 surface 行为混在同一个迁移步骤中。若实现过程中发现必须改变用户可见行为、默认 feature、权限语义或构建形态, +应暂停并在 PR 描述中单独说明设计偏移原因、影响范围和回滚边界。 + +#### 4.2.1 PR2:Service / Agent Remote Runtime Owner + +目标是在不搬动 concrete SSH / relay / terminal / session restore 实现的前提下,把 remote workspace 与 projection +的稳定接口归入 `bitfun-runtime-ports`,并保留 `bitfun-services-integrations::remote_connect` 旧路径 re-export。 + +1. 在 `bitfun-runtime-ports` 中承接 remote workspace facts、remote session metadata、remote workspace file projection DTO + 和 `RemoteWorkspacePort` / `RemoteProjectionPort` owner trait。 +2. 在 `bitfun-services-integrations::remote_connect` 中删除重复 owner 定义,改为 re-export 新 owner crate 的类型和 trait, + 保持现有调用方 import 路径兼容。 +3. 让 core 侧 remote workspace / file adapter 继续作为具体 provider,实现新的 stable port;workspace-root、 + persistence、session restore、terminal pre-warm 和 scheduler submit 仍保留在 `bitfun-core`。 +4. 补充 focused tests,覆盖 remote workspace / file projection 类型通过旧路径与新 owner 路径保持等价,以及 + `RuntimeServicesBuilder` 能注册带方法的 remote workspace / projection provider。 +5. 更新 boundary check,防止 remote owner contract 回流到 `bitfun-core` 或 concrete service crate。 + +#### 4.2.2 PR3:Agent Runtime SDK Owner + +目标是创建有真实 owner 的 `bitfun-agent-runtime`,只迁移 scheduler/background delivery 这类可纯函数保护的运行时决策, +不外移 concrete scheduler 生命周期。 + +1. 新建 `bitfun-agent-runtime` crate,并加入 workspace;该 crate 只依赖 `bitfun-runtime-ports` 等稳定契约,不依赖 + `bitfun-core`、Tauri、CLI、ACP、Web UI 或 concrete service crate。 +2. 先把 background delivery 的状态决策抽为 `bitfun-agent-runtime` 的纯 contract:Processing 注入当前运行 turn, + Missing / Idle / Error 提交 agent-session follow-up turn。 +3. core scheduler 仅调用该决策结果,继续负责 injection buffer、submit、turn id、metadata 和实际生命周期执行。 +4. 补充 `bitfun-agent-runtime` focused tests 与 core scheduler 兼容验证,确保 background reply、cancel suppression、 + queue/preempt 和 DeepResearch/post-turn 相关语义没有漂移。 +5. 更新 `AGENTS.md` / `AGENTS-CN.md` 和设计文档中的 crate 状态,描述 `bitfun-agent-runtime` 已承接的范围以及仍未外移的 + scheduler lifecycle、session manager、prompt loop 和 subagent registry。 + +#### 4.2.3 本次 PR 验收 + +- 不修改产品命令、UI、默认 feature、release / fast build 脚本或产品能力集合。 +- 不新增反向依赖、无类型 service locator、全局 mutable registry 或重复 runtime materialization。 +- 必须通过 remote / runtime owner focused tests、boundary check、repo hygiene 和最小 Rust 编译验证。 +- 提交前从第三方视角审查功能偏移、性能劣化、跨产品形态遗漏、文档与代码不一致,并修复发现的问题。 + ## 5. 每类 PR 的保护重点 ### 5.1 Service / Agent Remote Runtime Owner diff --git a/scripts/check-core-boundaries.mjs b/scripts/check-core-boundaries.mjs index 8a4261e6b..648a8409d 100644 --- a/scripts/check-core-boundaries.mjs +++ b/scripts/check-core-boundaries.mjs @@ -13,6 +13,7 @@ const noCoreDependencyCrates = [ 'events', 'ai-adapters', 'agent-stream', + 'agent-runtime', 'runtime-ports', 'runtime-services', 'services-core', @@ -113,6 +114,31 @@ const lightweightBoundaryRules = [ 'syntect-tui', ], }, + { + crateName: 'agent-runtime', + reason: 'agent-runtime must own portable runtime decisions without concrete service or product implementations', + forbiddenDeps: [ + 'bitfun-core', + 'bitfun-ai-adapters', + 'bitfun-services-core', + 'bitfun-services-integrations', + 'bitfun-tool-packs', + 'bitfun-product-domains', + 'bitfun-transport', + 'terminal-core', + 'tauri', + 'reqwest', + 'git2', + 'rmcp', + 'image', + 'tokio-tungstenite', + 'bitfun-cli', + 'ratatui', + 'crossterm', + 'arboard', + 'syntect-tui', + ], + }, { crateName: 'agent-tools', reason: 'agent-tools must not depend on concrete service or product runtime implementations', @@ -273,6 +299,32 @@ const dependencyProfileRules = [ 'syntect-tui', ], }, + { + crateName: 'agent-runtime', + profileName: 'default agent runtime decision profile', + reason: 'agent-runtime default profile must not compile concrete services or product surfaces', + forbiddenNonOptionalDeps: [ + 'bitfun-core', + 'bitfun-ai-adapters', + 'bitfun-services-core', + 'bitfun-services-integrations', + 'bitfun-tool-packs', + 'bitfun-product-domains', + 'bitfun-transport', + 'terminal-core', + 'tauri', + 'reqwest', + 'git2', + 'rmcp', + 'image', + 'tokio-tungstenite', + 'bitfun-cli', + 'ratatui', + 'crossterm', + 'arboard', + 'syntect-tui', + ], + }, { crateName: 'agent-tools', profileName: 'tool contract-only profile', @@ -3027,9 +3079,14 @@ const requiredContentRules = [ }, { regex: - /use bitfun_runtime_ports::\{[\s\S]*DialogSessionStateFact[\s\S]*DialogSubmitQueueAction[\s\S]*DialogSubmitQueueFacts[\s\S]*DialogTurnOutcomeKind[\s\S]*resolve_dialog_submit_queue_action[\s\S]*should_skip_agent_session_reply_contract[\s\S]*should_suppress_agent_session_cancelled_reply_contract[\s\S]*\};/, + /use bitfun_runtime_ports::\{(?=[\s\S]*DialogSessionStateFact)(?=[\s\S]*DialogSubmitQueueAction)(?=[\s\S]*DialogSubmitQueueFacts)(?=[\s\S]*DialogTurnOutcomeKind)(?=[\s\S]*resolve_dialog_submit_queue_action)(?=[\s\S]*should_skip_agent_session_reply_contract)(?=[\s\S]*should_suppress_agent_session_cancelled_reply_contract)[\s\S]*\};/, message: 'missing dialog scheduler decision contract import', }, + { + regex: + /use bitfun_agent_runtime::scheduler::\{(?=[\s\S]*BackgroundDeliveryAction)(?=[\s\S]*BackgroundDeliveryFacts)(?=[\s\S]*resolve_background_delivery_action)[\s\S]*\};/, + message: 'missing agent-runtime background delivery decision import', + }, ], }, { @@ -3276,7 +3333,7 @@ const requiredContentRules = [ { path: 'src/crates/services-integrations/src/remote_connect.rs', reason: - 'services-integrations must own remote-connect wire, tracker, dialog, file, and image adapter contracts', + 'services-integrations must own remote-connect wire/response assembly and preserve remote owner compatibility re-exports', patterns: [ { regex: /\bpub struct RemoteSessionStateTracker\b/, @@ -3407,7 +3464,7 @@ const requiredContentRules = [ message: 'missing remote workspace file-info reader', }, { - regex: /\bpub trait RemoteWorkspaceFileRuntimeHost\b/, + regex: /\bRemoteWorkspaceFileRuntimeHost\b/, message: 'missing remote workspace file runtime host contract', }, { @@ -3443,11 +3500,11 @@ const requiredContentRules = [ message: 'missing remote answer response assembly helper', }, { - regex: /\bpub struct RemoteWorkspaceFacts\b/, + regex: /\bRemoteWorkspaceFacts\b/, message: 'missing remote workspace response facts DTO', }, { - regex: /\bpub struct RemoteSessionMetadata\b/, + regex: /\bRemoteSessionMetadata\b/, message: 'missing remote session response metadata DTO', }, { @@ -6803,6 +6860,21 @@ function runManifestParserSelfTest() { if (!runtimeServicesProfile?.forbiddenNonOptionalDeps.includes('tool-runtime')) { throw new Error('runtime-services dependency profile must forbid tool runtime implementations'); } + const agentRuntimeRule = lightweightBoundaryRules.find( + (rule) => rule.crateName === 'agent-runtime', + ); + if (!agentRuntimeRule?.forbiddenDeps.includes('bitfun-core')) { + throw new Error('agent-runtime lightweight boundary must forbid bitfun-core'); + } + if (!agentRuntimeRule?.forbiddenDeps.includes('bitfun-services-integrations')) { + throw new Error('agent-runtime lightweight boundary must forbid concrete service integrations'); + } + const agentRuntimeProfile = dependencyProfileRules.find( + (rule) => rule.crateName === 'agent-runtime', + ); + if (!agentRuntimeProfile?.forbiddenNonOptionalDeps.includes('tauri')) { + throw new Error('agent-runtime dependency profile must forbid product surface dependencies'); + } const agentToolsManifestRule = forbiddenContentUnderRules.find( (rule) => rule.path === 'src/crates/agent-tools/src', ); @@ -6852,6 +6924,14 @@ function runManifestParserSelfTest() { 'AgentTurnCancellationPort', 'RemoteControlStatePort', 'RuntimeEventSink', + 'RemoteWorkspaceFacts', + 'RemoteWorkspaceRuntimeHost', + 'RemoteWorkspacePort', + 'RemoteWorkspaceFileRuntimeHost', + 'RemoteProjectionPort', + 'RemoteInitialSyncRuntimeHost', + 'remote_workspace_contracts_preserve_workspace_and_session_facts', + 'remote_projection_contract_preserves_file_chunk_identity', 'remote_image', 'DialogTriggerSource', 'dialog_trigger_source_reuses_agent_submission_source_contract', @@ -6920,6 +7000,27 @@ function runManifestParserSelfTest() { 'missing_optional_capability_returns_typed_unsupported_error', 'capability_availability_reports_optional_service_status_without_side_effects', 'builder_rejects_port_registered_under_the_wrong_capability', + 'registered_remote_ports_expose_owner_contract_methods', + ], + }, + { + path: 'src/crates/agent-runtime/src/scheduler.rs', + contracts: [ + 'BackgroundDeliveryFacts', + 'BackgroundDeliveryAction', + 'resolve_background_delivery_action', + 'follow_up_submission_policy', + 'SubmitAgentSessionFollowUp', + 'InjectIntoRunningTurn', + ], + }, + { + path: 'src/crates/agent-runtime/tests/scheduler_contracts.rs', + contracts: [ + 'background_delivery_injects_when_session_is_processing', + 'background_delivery_starts_agent_session_follow_up_when_session_is_not_processing', + 'background_delivery_follow_up_uses_agent_session_source_semantics', + 'background_delivery_injection_does_not_expose_follow_up_policy', ], }, { diff --git a/src/crates/agent-runtime/AGENTS.md b/src/crates/agent-runtime/AGENTS.md new file mode 100644 index 000000000..8b1e5fd5a --- /dev/null +++ b/src/crates/agent-runtime/AGENTS.md @@ -0,0 +1,24 @@ +# agent-runtime Agent Guide + +Scope: this guide applies to `src/crates/agent-runtime`. + +`bitfun-agent-runtime` owns portable agent runtime decisions that can be built +and tested without `bitfun-core`. + +## Guardrails + +- Do not depend on `bitfun-core`, app crates, Tauri, ACP protocol, web UI, + concrete service crates, or product-domain implementations. +- Keep concrete scheduler/session lifecycle execution in `bitfun-core` until a + reviewed owner migration proves behavior equivalence. +- Prefer pure facts and decisions first: queue policy, background delivery, + cancellation routing, runtime event facts, and registry visibility. +- Add focused tests before moving any runtime decision into this crate. + +## Verification + +```bash +cargo test -p bitfun-agent-runtime +node scripts/check-core-boundaries.mjs +cargo check -p bitfun-core --features product-full +``` diff --git a/src/crates/agent-runtime/Cargo.toml b/src/crates/agent-runtime/Cargo.toml new file mode 100644 index 000000000..6d50a0078 --- /dev/null +++ b/src/crates/agent-runtime/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "bitfun-agent-runtime" +version.workspace = true +authors.workspace = true +edition.workspace = true +description = "Agent runtime contracts and owner decisions for BitFun" + +[lib] +name = "bitfun_agent_runtime" +crate-type = ["rlib"] + +[dependencies] +bitfun-runtime-ports = { path = "../runtime-ports" } diff --git a/src/crates/agent-runtime/src/lib.rs b/src/crates/agent-runtime/src/lib.rs new file mode 100644 index 000000000..f44269455 --- /dev/null +++ b/src/crates/agent-runtime/src/lib.rs @@ -0,0 +1,6 @@ +//! Agent runtime owner contracts. +//! +//! This crate owns runtime decisions that can be built and tested without +//! depending on `bitfun-core` concrete session or scheduler lifecycle. + +pub mod scheduler; diff --git a/src/crates/agent-runtime/src/scheduler.rs b/src/crates/agent-runtime/src/scheduler.rs new file mode 100644 index 000000000..ff7d55235 --- /dev/null +++ b/src/crates/agent-runtime/src/scheduler.rs @@ -0,0 +1,52 @@ +//! Scheduler owner decisions. + +use bitfun_runtime_ports::{ + DialogQueuePriority, DialogSessionStateFact, DialogSubmissionPolicy, DialogTriggerSource, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct BackgroundDeliveryFacts { + pub session_state: DialogSessionStateFact, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BackgroundDeliveryAction { + InjectIntoRunningTurn, + SubmitAgentSessionFollowUp { + queue_priority: DialogQueuePriority, + skip_tool_confirmation: bool, + }, +} + +impl BackgroundDeliveryAction { + pub const fn follow_up_submission_policy(self) -> Option { + match self { + Self::InjectIntoRunningTurn => None, + Self::SubmitAgentSessionFollowUp { + queue_priority, + skip_tool_confirmation, + } => Some(DialogSubmissionPolicy::new( + DialogTriggerSource::AgentSession, + queue_priority, + skip_tool_confirmation, + )), + } + } +} + +pub const fn resolve_background_delivery_action( + facts: BackgroundDeliveryFacts, +) -> BackgroundDeliveryAction { + match facts.session_state { + DialogSessionStateFact::Processing => BackgroundDeliveryAction::InjectIntoRunningTurn, + DialogSessionStateFact::Missing + | DialogSessionStateFact::Idle + | DialogSessionStateFact::Error => { + let policy = DialogSubmissionPolicy::for_source(DialogTriggerSource::AgentSession); + BackgroundDeliveryAction::SubmitAgentSessionFollowUp { + queue_priority: policy.queue_priority, + skip_tool_confirmation: policy.skip_tool_confirmation, + } + } + } +} diff --git a/src/crates/agent-runtime/tests/scheduler_contracts.rs b/src/crates/agent-runtime/tests/scheduler_contracts.rs new file mode 100644 index 000000000..6aa7f3702 --- /dev/null +++ b/src/crates/agent-runtime/tests/scheduler_contracts.rs @@ -0,0 +1,56 @@ +use bitfun_agent_runtime::scheduler::{ + resolve_background_delivery_action, BackgroundDeliveryAction, BackgroundDeliveryFacts, +}; +use bitfun_runtime_ports::{DialogQueuePriority, DialogSessionStateFact, DialogTriggerSource}; + +#[test] +fn background_delivery_injects_when_session_is_processing() { + let action = resolve_background_delivery_action(BackgroundDeliveryFacts { + session_state: DialogSessionStateFact::Processing, + }); + + assert_eq!(action, BackgroundDeliveryAction::InjectIntoRunningTurn); +} + +#[test] +fn background_delivery_starts_agent_session_follow_up_when_session_is_not_processing() { + for session_state in [ + DialogSessionStateFact::Missing, + DialogSessionStateFact::Idle, + DialogSessionStateFact::Error, + ] { + let action = resolve_background_delivery_action(BackgroundDeliveryFacts { session_state }); + + assert_eq!( + action, + BackgroundDeliveryAction::SubmitAgentSessionFollowUp { + queue_priority: DialogQueuePriority::Low, + skip_tool_confirmation: true, + } + ); + } +} + +#[test] +fn background_delivery_follow_up_uses_agent_session_source_semantics() { + let action = resolve_background_delivery_action(BackgroundDeliveryFacts { + session_state: DialogSessionStateFact::Missing, + }); + + let policy = action + .follow_up_submission_policy() + .expect("follow-up action should expose submission policy"); + + assert_eq!(policy.trigger_source, DialogTriggerSource::AgentSession); + assert_eq!(policy.queue_priority, DialogQueuePriority::Low); + assert!(policy.skip_tool_confirmation); +} + +#[test] +fn background_delivery_injection_does_not_expose_follow_up_policy() { + let action = resolve_background_delivery_action(BackgroundDeliveryFacts { + session_state: DialogSessionStateFact::Processing, + }); + + assert_eq!(action.follow_up_submission_policy(), None); +} diff --git a/src/crates/core/AGENTS-CN.md b/src/crates/core/AGENTS-CN.md index bc697ffd5..45827289a 100644 --- a/src/crates/core/AGENTS-CN.md +++ b/src/crates/core/AGENTS-CN.md @@ -43,9 +43,11 @@ SessionManager → Session → DialogTurn → ModelRound `bitfun-product-domains`;bundled asset include、filesystem writes、marker IO、 customization metadata IO、recompile orchestration、worker process runtime 和 host dispatch execution 仍由 core 拥有,直到有评审过的迁移和等价测试。 -- Remote-connect wire/tracker/dialog orchestration 与 portable file/image contract - 可以放在 `bitfun-services-integrations`;workspace-root source selection、 - response wrapping、concrete scheduler/session restore、terminal pre-warm adapter +- Remote-connect wire/tracker/dialog orchestration 与 response wrapping 可以放在 + `bitfun-services-integrations`;remote workspace facts、session metadata、 + file projection DTO 和 remote workspace/projection host trait 属于 + `bitfun-runtime-ports`,`remote_connect` 只保留旧路径 re-export。workspace-root + source selection、concrete scheduler/session restore、terminal pre-warm adapter 和 product execution 仍由 core 拥有,直到有评审过的迁移和等价测试。 - 不要在没有小型 port/interface 边界的情况下新增 `service` 到 `agentic` 的跨层引用。 - 不要在 core 拆解中把平台专属逻辑、构建脚本行为或产品能力选择下沉到 shared core。 diff --git a/src/crates/core/AGENTS.md b/src/crates/core/AGENTS.md index a0e1cf0e5..ab9b27c1e 100644 --- a/src/crates/core/AGENTS.md +++ b/src/crates/core/AGENTS.md @@ -92,14 +92,14 @@ SessionManager → Session → DialogTurn → ModelRound bundled asset includes, filesystem writes, marker IO, customization metadata IO, source reads, compile orchestration, worker process runtime, and host dispatch execution core-owned until a reviewed migration proves equivalence. -- Remote-connect wire/tracker/dialog and cancel orchestration plus portable - file/image contracts, remote file command/response assembly, - dialog/cancel/interaction response helpers, and workspace/session response - assembly helpers may live in `bitfun-services-integrations`; keep - workspace-root source selection, persistence/workspace service reads, - concrete scheduler/session restore, terminal pre-warm adapters, and product - execution core-owned until a reviewed migration proves equivalence. Core - remote dialog/cancel/file/tracker adapters, remote model catalog/session-model +- Remote-connect wire/tracker/dialog and cancel orchestration plus response + assembly helpers may live in `bitfun-services-integrations`; remote workspace + facts, session metadata, file projection DTOs, and remote workspace/projection + host traits belong in `bitfun-runtime-ports` with old-path re-exports from + `remote_connect`. Keep workspace-root source selection, persistence/workspace + service reads, concrete scheduler/session restore, terminal pre-warm adapters, + and product execution core-owned until a reviewed migration proves equivalence. + Core remote dialog/cancel/file/tracker adapters, remote model catalog/session-model selection adapters, remote chat history persistence/message conversion adapters, and service/agent runtime bindings are centralized in `src/crates/core/src/service_agent_runtime.rs`. diff --git a/src/crates/core/Cargo.toml b/src/crates/core/Cargo.toml index 925cf8439..90c3f3b0c 100644 --- a/src/crates/core/Cargo.toml +++ b/src/crates/core/Cargo.toml @@ -81,6 +81,9 @@ bitfun-ai-adapters = { path = "../ai-adapters" } # Lightweight agent stream processing bitfun-agent-stream = { path = "../agent-stream" } +# Agent runtime owner contracts +bitfun-agent-runtime = { path = "../agent-runtime" } + # Agent tool contracts bitfun-agent-tools = { path = "../agent-tools" } diff --git a/src/crates/core/src/agentic/coordination/scheduler.rs b/src/crates/core/src/agentic/coordination/scheduler.rs index 4355054fb..57ce1cd27 100644 --- a/src/crates/core/src/agentic/coordination/scheduler.rs +++ b/src/crates/core/src/agentic/coordination/scheduler.rs @@ -32,15 +32,18 @@ use uuid::Uuid; const MAX_QUEUE_DEPTH: usize = 20; -pub use bitfun_runtime_ports::{ - AgentSessionReplyRoute, DialogQueuePriority, DialogSteerOutcome, DialogSubmissionPolicy, - DialogSubmitOutcome, +use bitfun_agent_runtime::scheduler::{ + resolve_background_delivery_action, BackgroundDeliveryAction, BackgroundDeliveryFacts, }; use bitfun_runtime_ports::{ - DialogSessionStateFact, DialogSubmitQueueAction, DialogSubmitQueueFacts, DialogTurnOutcomeKind, resolve_dialog_submit_queue_action, should_skip_agent_session_reply as should_skip_agent_session_reply_contract, should_suppress_agent_session_cancelled_reply as should_suppress_agent_session_cancelled_reply_contract, + DialogSessionStateFact, DialogSubmitQueueAction, DialogSubmitQueueFacts, DialogTurnOutcomeKind, +}; +pub use bitfun_runtime_ports::{ + AgentSessionReplyRoute, DialogQueuePriority, DialogSteerOutcome, DialogSubmissionPolicy, + DialogSubmitOutcome, }; #[derive(Debug, Clone)] @@ -256,8 +259,10 @@ impl DialogScheduler { .get_session(&session_id) .map(|s| s.state.clone()); - match state { - Some(SessionState::Processing { .. }) => { + match resolve_background_delivery_action(BackgroundDeliveryFacts { + session_state: Self::session_state_fact(state.as_ref()), + }) { + BackgroundDeliveryAction::InjectIntoRunningTurn => { let injection_id = Uuid::new_v4().to_string(); self.round_injection_buffer.push( &session_id, @@ -272,7 +277,10 @@ impl DialogScheduler { ); Ok(()) } - _ => self + BackgroundDeliveryAction::SubmitAgentSessionFollowUp { + queue_priority, + skip_tool_confirmation, + } => self .submit( session_id, content, @@ -280,7 +288,11 @@ impl DialogScheduler { None, agent_type, workspace_path, - DialogSubmissionPolicy::for_source(DialogTriggerSource::AgentSession), + DialogSubmissionPolicy::new( + DialogTriggerSource::AgentSession, + queue_priority, + skip_tool_confirmation, + ), None, user_message_metadata, None, @@ -998,10 +1010,8 @@ mod tests { assert!( DialogScheduler::goal_verification_observation_text(&cancelled).contains("cancelled") ); - assert!( - DialogScheduler::goal_verification_observation_text(&failed) - .contains("network offline") - ); + assert!(DialogScheduler::goal_verification_observation_text(&failed) + .contains("network offline")); } #[test] diff --git a/src/crates/core/src/service_agent_runtime.rs b/src/crates/core/src/service_agent_runtime.rs index 8dfcb5c4e..df74e2fdf 100644 --- a/src/crates/core/src/service_agent_runtime.rs +++ b/src/crates/core/src/service_agent_runtime.rs @@ -8,7 +8,8 @@ use bitfun_runtime_ports::{ AgentSessionCreateRequest, AgentSubmissionPort, AgentSubmissionSource, AgentTurnCancellationPort, AgentTurnCancellationRequest, RemoteControlStatePort, - RemoteControlStateRequest, RemoteControlStateSnapshot, + RemoteControlStateRequest, RemoteControlStateSnapshot, RuntimeServiceCapability, + RuntimeServicePort, }; use bitfun_services_integrations::remote_connect::{ build_remote_chat_messages, build_remote_model_catalog, @@ -716,6 +717,18 @@ impl CoreRemoteWorkspaceRuntimeHost { } } +impl RuntimeServicePort for CoreRemoteWorkspaceFileRuntimeHost { + fn capability(&self) -> RuntimeServiceCapability { + RuntimeServiceCapability::RemoteProjection + } +} + +impl RuntimeServicePort for CoreRemoteWorkspaceRuntimeHost { + fn capability(&self) -> RuntimeServiceCapability { + RuntimeServiceCapability::RemoteWorkspace + } +} + pub(crate) struct CoreRemoteSessionRuntimeHost { coordinator: Arc, } diff --git a/src/crates/runtime-ports/src/lib.rs b/src/crates/runtime-ports/src/lib.rs index ce73b2291..3224b1614 100644 --- a/src/crates/runtime-ports/src/lib.rs +++ b/src/crates/runtime-ports/src/lib.rs @@ -5,6 +5,7 @@ //! on concrete managers, platform adapters, `bitfun-core`, or app crates. use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; pub type PortResult = Result; @@ -142,11 +143,143 @@ pub trait McpCatalogPort: RuntimeServicePort {} /// lifecycle methods once behavior-equivalence tests are in place. pub trait RemoteConnectionPort: RuntimeServicePort {} +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum RemoteWorkspaceKind { + Normal, + Assistant, + Remote, +} + +impl RemoteWorkspaceKind { + pub const fn as_wire_str(self) -> &'static str { + match self { + Self::Normal => "normal", + Self::Assistant => "assistant", + Self::Remote => "remote", + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RemoteWorkspaceFacts { + pub path: String, + pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub git_branch: Option, + pub kind: RemoteWorkspaceKind, + #[serde(skip_serializing_if = "Option::is_none")] + pub assistant_id: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RemoteRecentWorkspaceFacts { + pub path: String, + pub name: String, + pub last_opened: String, + pub kind: RemoteWorkspaceKind, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RemoteAssistantWorkspaceFacts { + pub path: String, + pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub assistant_id: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RemoteWorkspaceUpdate { + pub path: String, + pub name: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RemoteSessionMetadata { + pub session_id: String, + pub name: String, + pub agent_type: String, + pub created_at_ms: u64, + pub last_active_at_ms: u64, + pub turn_count: usize, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RemoteWorkspaceFileContent { + pub name: String, + pub bytes: Vec, + pub mime_type: &'static str, + pub size: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RemoteWorkspaceFileChunk { + pub name: String, + pub bytes: Vec, + pub offset: u64, + pub chunk_size: u64, + pub total_size: u64, + pub mime_type: &'static str, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RemoteWorkspaceFileInfo { + pub name: String, + pub size: u64, + pub mime_type: &'static str, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct RemoteFileChunkRange { + pub start: usize, + pub end: usize, + pub chunk_size: u64, +} + +/// Old remote-connect host compatibility trait for workspace commands. +#[async_trait::async_trait] +pub trait RemoteWorkspaceRuntimeHost: Send + Sync { + async fn current_workspace(&self) -> Option; + async fn recent_workspaces(&self) -> Vec; + async fn open_workspace(&self, path: &str) -> Result; + async fn assistant_workspaces(&self) -> Vec; + async fn open_assistant_workspace(&self, path: &str) -> Result; +} + /// Typed registration boundary for remote workspace providers. -pub trait RemoteWorkspacePort: RuntimeServicePort {} +pub trait RemoteWorkspacePort: RuntimeServicePort + RemoteWorkspaceRuntimeHost {} + +impl RemoteWorkspacePort for T where T: RuntimeServicePort + RemoteWorkspaceRuntimeHost + ?Sized {} + +/// Old remote-connect host compatibility trait for initial sync. +#[async_trait::async_trait] +pub trait RemoteInitialSyncRuntimeHost: Send + Sync { + async fn current_workspace(&self) -> Option; + async fn list_session_metadata( + &self, + workspace_path: &Path, + ) -> Result, String>; +} + +/// Old remote-connect host compatibility trait for remote file projection. +#[async_trait::async_trait] +pub trait RemoteWorkspaceFileRuntimeHost: Send + Sync { + async fn resolve_remote_file_workspace_root(&self, session_id: Option<&str>) + -> Option; +} /// Typed registration boundary for remote filesystem/terminal/image projection providers. -pub trait RemoteProjectionPort: RuntimeServicePort {} +pub trait RemoteProjectionPort: RuntimeServicePort + RemoteWorkspaceFileRuntimeHost {} + +impl RemoteProjectionPort for T where + T: RuntimeServicePort + RemoteWorkspaceFileRuntimeHost + ?Sized +{ +} /// Typed registration boundary for remote host capability facts. pub trait RemoteCapabilityPort: RuntimeServicePort {} @@ -1071,6 +1204,45 @@ mod tests { assert_eq!(route.source_workspace_path, "/workspace/requester"); } + #[test] + fn remote_workspace_contracts_preserve_workspace_and_session_facts() { + let workspace = RemoteWorkspaceFacts { + path: "/workspace/project".to_string(), + name: "project".to_string(), + git_branch: Some("main".to_string()), + kind: RemoteWorkspaceKind::Remote, + assistant_id: Some("assistant_1".to_string()), + }; + let session = RemoteSessionMetadata { + session_id: "session_1".to_string(), + name: "Research".to_string(), + agent_type: "CodeAgent".to_string(), + created_at_ms: 10, + last_active_at_ms: 20, + turn_count: 3, + }; + + assert_eq!(workspace.kind.as_wire_str(), "remote"); + assert_eq!(workspace.assistant_id.as_deref(), Some("assistant_1")); + assert_eq!(session.turn_count, 3); + } + + #[test] + fn remote_projection_contract_preserves_file_chunk_identity() { + let chunk = RemoteWorkspaceFileChunk { + name: "report.md".to_string(), + bytes: b"chunk".to_vec(), + offset: 6, + chunk_size: 5, + total_size: 11, + mime_type: "text/markdown", + }; + + assert_eq!(chunk.name, "report.md"); + assert_eq!(chunk.bytes, b"chunk"); + assert_eq!(chunk.offset + chunk.chunk_size, chunk.total_size); + } + #[test] fn dialog_steer_outcome_preserves_buffered_fields() { let outcome = DialogSteerOutcome::Buffered { diff --git a/src/crates/runtime-services/src/test_support.rs b/src/crates/runtime-services/src/test_support.rs index 7e52357ea..b0a690cc2 100644 --- a/src/crates/runtime-services/src/test_support.rs +++ b/src/crates/runtime-services/src/test_support.rs @@ -2,8 +2,10 @@ use std::sync::Arc; use bitfun_runtime_ports::{ ClockPort, FileSystemPort, GitPort, McpCatalogPort, NetworkPort, PermissionDecision, - PermissionPort, PermissionRequest, PortResult, RemoteCapabilityPort, RemoteConnectionPort, - RemoteProjectionPort, RemoteWorkspacePort, RuntimeEventEnvelope, RuntimeEventSink, + PermissionPort, PermissionRequest, PortResult, RemoteAssistantWorkspaceFacts, + RemoteCapabilityPort, RemoteConnectionPort, RemoteProjectionPort, RemoteRecentWorkspaceFacts, + RemoteWorkspaceFacts, RemoteWorkspaceFileRuntimeHost, RemoteWorkspaceKind, RemoteWorkspacePort, + RemoteWorkspaceRuntimeHost, RemoteWorkspaceUpdate, RuntimeEventEnvelope, RuntimeEventSink, RuntimeServiceCapability, RuntimeServicePort, SessionStorePort, TerminalPort, WorkspacePort, }; @@ -36,10 +38,53 @@ impl NetworkPort for FakeRuntimePort {} impl GitPort for FakeRuntimePort {} impl McpCatalogPort for FakeRuntimePort {} impl RemoteConnectionPort for FakeRuntimePort {} -impl RemoteWorkspacePort for FakeRuntimePort {} -impl RemoteProjectionPort for FakeRuntimePort {} impl RemoteCapabilityPort for FakeRuntimePort {} +#[async_trait::async_trait] +impl RemoteWorkspaceRuntimeHost for FakeRuntimePort { + async fn current_workspace(&self) -> Option { + Some(RemoteWorkspaceFacts { + path: "/remote/project".to_string(), + name: "project".to_string(), + git_branch: Some("main".to_string()), + kind: RemoteWorkspaceKind::Remote, + assistant_id: None, + }) + } + + async fn recent_workspaces(&self) -> Vec { + Vec::new() + } + + async fn open_workspace(&self, path: &str) -> Result { + Ok(RemoteWorkspaceUpdate { + path: path.to_string(), + name: "project".to_string(), + }) + } + + async fn assistant_workspaces(&self) -> Vec { + Vec::new() + } + + async fn open_assistant_workspace(&self, path: &str) -> Result { + Ok(RemoteWorkspaceUpdate { + path: path.to_string(), + name: "assistant".to_string(), + }) + } +} + +#[async_trait::async_trait] +impl RemoteWorkspaceFileRuntimeHost for FakeRuntimePort { + async fn resolve_remote_file_workspace_root( + &self, + _session_id: Option<&str>, + ) -> Option { + Some(std::path::PathBuf::from("/remote/project")) + } +} + #[async_trait::async_trait] impl PermissionPort for FakeRuntimePort { async fn request_permission( diff --git a/src/crates/runtime-services/tests/runtime_services_contracts.rs b/src/crates/runtime-services/tests/runtime_services_contracts.rs index e079a5db4..b37527fad 100644 --- a/src/crates/runtime-services/tests/runtime_services_contracts.rs +++ b/src/crates/runtime-services/tests/runtime_services_contracts.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use bitfun_runtime_ports::FileSystemPort; -use bitfun_runtime_ports::RuntimeServiceCapability; +use bitfun_runtime_ports::{RemoteWorkspaceKind, RuntimeServiceCapability}; use bitfun_runtime_services::test_support::{FakeRuntimePort, FakeRuntimeServicesProvider}; use bitfun_runtime_services::{ CapabilityAvailability, RuntimeServicesBuilder, RuntimeServicesError, RuntimeServicesProvider, @@ -98,3 +98,30 @@ fn builder_rejects_port_registered_under_the_wrong_capability() { } ); } + +#[tokio::test] +async fn registered_remote_ports_expose_owner_contract_methods() { + let services = FakeRuntimeServicesProvider::with_all_required() + .with_all_remote() + .build_services() + .expect("remote fake services should build"); + + let workspace = services + .remote_workspace + .as_ref() + .expect("remote workspace port") + .current_workspace() + .await + .expect("fake remote workspace facts"); + let projection_root = services + .remote_projection + .as_ref() + .expect("remote projection port") + .resolve_remote_file_workspace_root(Some("session_1")) + .await + .expect("fake remote projection root"); + + assert_eq!(workspace.kind, RemoteWorkspaceKind::Remote); + assert_eq!(workspace.path, "/remote/project"); + assert_eq!(projection_root.to_string_lossy(), "/remote/project"); +} diff --git a/src/crates/services-integrations/AGENTS.md b/src/crates/services-integrations/AGENTS.md index afb8cdf7d..65e1c5841 100644 --- a/src/crates/services-integrations/AGENTS.md +++ b/src/crates/services-integrations/AGENTS.md @@ -17,14 +17,15 @@ slices that are outside pure product logic but still platform-neutral. here; product tool registry assembly, manifest filtering, `GetToolSpec` execution, and concrete tool behavior remain core-owned until H1. - Remote-connect tracker/wire/pure-policy contracts, dialog submission and - cancel-task orchestration ports, image-context adapter contracts, portable - workspace-file path/read/chunk/info helpers, file command/response assembly, - dialog/cancel/interaction response helpers, and workspace/session response - assembly helpers may live here. Workspace-root source selection, - persistence/workspace service reads, concrete scheduler submission, concrete - session restore / terminal pre-warm adapters, and product execution remain - core-owned unless a later reviewed port/provider moves them with equivalence - tests. Core remote dialog/cancel/file/tracker, remote model + cancel-task orchestration ports, image-context adapter contracts, remote + workspace file helpers, and command/response assembly may live here. Remote + workspace facts, session metadata, file projection DTOs, and + workspace/projection host traits are owned by `bitfun-runtime-ports` and + re-exported from `remote_connect` for compatibility. Workspace-root source + selection, persistence/workspace service reads, concrete scheduler submission, + concrete session restore / terminal pre-warm adapters, and product execution + remain core-owned unless a later reviewed port/provider moves them with + equivalence tests. Core remote dialog/cancel/file/tracker, remote model catalog/session-model selection, and remote chat history persistence/message conversion adapter bindings are centralized in `src/crates/core/src/service_agent_runtime.rs`. diff --git a/src/crates/services-integrations/src/remote_connect.rs b/src/crates/services-integrations/src/remote_connect.rs index c0f5d3921..0b7317125 100644 --- a/src/crates/services-integrations/src/remote_connect.rs +++ b/src/crates/services-integrations/src/remote_connect.rs @@ -1,14 +1,22 @@ //! Remote-connect integration contracts. //! -//! This module owns stable remote-facing DTOs, runtime-port request -//! construction, and remote session tracker state. Network lifecycle and -//! product assembly stay in `bitfun-core` until their ports are explicit. +//! This module owns remote-connect wire assembly, runtime-port request +//! construction, compatibility re-exports, and remote session tracker state. +//! Network lifecycle and product assembly stay in `bitfun-core` until their +//! ports are explicit. use bitfun_events::AgenticEvent; use bitfun_runtime_ports::{ AgentInputAttachment, AgentSessionCreateRequest, AgentSubmissionRequest, AgentSubmissionSource, RemoteControlStateSnapshot, }; +pub use bitfun_runtime_ports::{ + RemoteAssistantWorkspaceFacts, RemoteFileChunkRange, RemoteInitialSyncRuntimeHost, + RemoteProjectionPort, RemoteRecentWorkspaceFacts, RemoteSessionMetadata, RemoteWorkspaceFacts, + RemoteWorkspaceFileChunk, RemoteWorkspaceFileContent, RemoteWorkspaceFileInfo, + RemoteWorkspaceFileRuntimeHost, RemoteWorkspaceKind, RemoteWorkspacePort, + RemoteWorkspaceRuntimeHost, RemoteWorkspaceUpdate, +}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::{Path, PathBuf}; @@ -424,38 +432,6 @@ where pub const REMOTE_FILE_MAX_READ_BYTES: u64 = 30 * 1024 * 1024; pub const REMOTE_FILE_MAX_CHUNK_BYTES: u64 = 3 * 1024 * 1024; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RemoteWorkspaceFileContent { - pub name: String, - pub bytes: Vec, - pub mime_type: &'static str, - pub size: u64, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RemoteWorkspaceFileChunk { - pub name: String, - pub bytes: Vec, - pub offset: u64, - pub chunk_size: u64, - pub total_size: u64, - pub mime_type: &'static str, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RemoteWorkspaceFileInfo { - pub name: String, - pub size: u64, - pub mime_type: &'static str, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct RemoteFileChunkRange { - pub start: usize, - pub end: usize, - pub chunk_size: u64, -} - pub fn resolve_remote_file_chunk_range( file_len: usize, offset: u64, @@ -650,12 +626,6 @@ pub async fn read_remote_workspace_file_info( }) } -#[async_trait::async_trait] -pub trait RemoteWorkspaceFileRuntimeHost: Send + Sync { - async fn resolve_remote_file_workspace_root(&self, session_id: Option<&str>) - -> Option; -} - pub fn remote_file_content_response( result: Result, ) -> RemoteResponse { @@ -971,15 +941,6 @@ pub fn remote_initial_sync_response( } } -#[async_trait::async_trait] -pub trait RemoteWorkspaceRuntimeHost: Send + Sync { - async fn current_workspace(&self) -> Option; - async fn recent_workspaces(&self) -> Vec; - async fn open_workspace(&self, path: &str) -> Result; - async fn assistant_workspaces(&self) -> Vec; - async fn open_assistant_workspace(&self, path: &str) -> Result; -} - pub async fn handle_remote_workspace_command(host: &H, command: &RemoteCommand) -> RemoteResponse where H: RemoteWorkspaceRuntimeHost + ?Sized, @@ -1006,15 +967,6 @@ where } } -#[async_trait::async_trait] -pub trait RemoteInitialSyncRuntimeHost: Send + Sync { - async fn current_workspace(&self) -> Option; - async fn list_session_metadata( - &self, - workspace_path: &Path, - ) -> Result, String>; -} - pub async fn generate_remote_initial_sync( host: &H, authenticated_user_id: Option, @@ -1900,63 +1852,6 @@ pub struct AssistantEntry { pub assistant_id: Option, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum RemoteWorkspaceKind { - Normal, - Assistant, - Remote, -} - -impl RemoteWorkspaceKind { - pub const fn as_wire_str(self) -> &'static str { - match self { - RemoteWorkspaceKind::Normal => "normal", - RemoteWorkspaceKind::Assistant => "assistant", - RemoteWorkspaceKind::Remote => "remote", - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RemoteWorkspaceFacts { - pub path: String, - pub name: String, - pub git_branch: Option, - pub kind: RemoteWorkspaceKind, - pub assistant_id: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RemoteRecentWorkspaceFacts { - pub path: String, - pub name: String, - pub last_opened: String, - pub kind: RemoteWorkspaceKind, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RemoteAssistantWorkspaceFacts { - pub path: String, - pub name: String, - pub assistant_id: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RemoteWorkspaceUpdate { - pub path: String, - pub name: String, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RemoteSessionMetadata { - pub session_id: String, - pub name: String, - pub agent_type: String, - pub created_at_ms: u64, - pub last_active_at_ms: u64, - pub turn_count: usize, -} - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ActiveTurnSnapshot { pub turn_id: String, @@ -3027,7 +2922,11 @@ pub fn remote_persisted_poll_response( } fn non_empty_title(title: String) -> Option { - if title.is_empty() { None } else { Some(title) } + if title.is_empty() { + None + } else { + Some(title) + } } #[cfg(test)]