Skip to content

Commit 12079c6

Browse files
committed
refactor(services): move integration runtime helpers
1 parent 03d6eba commit 12079c6

11 files changed

Lines changed: 541 additions & 198 deletions

File tree

docs/architecture/core-decomposition.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ owner 边界,否则不要把一个 feature group 继续拆成更小的 crate
128128
或任何产品 crate feature set 调整。
129129
- 高风险项不能作为 P2/P3 普通收尾任务顺带执行,必须先有等价性测试、port/provider 设计、
130130
旧路径兼容策略和用户确认。
131+
- 为减少 PR 次数,剩余 runtime 迁移可以压缩为 5 个主题 PR,但每个 PR 仍必须保持单一
132+
owner 主题:`services-integrations` runtime 收口、MCP runtime/dynamic tools、
133+
remote-connect runtime、agent tools + `tool-packs` owner 化、`product-domains`
134+
runtime + core facade finalization。`bitfun-core default = []` 和 per-product feature
135+
matrix 仍是上述 5 个 PR 之后的独立评估。
131136

132137
## Feature 安全规则
133138

docs/plans/core-decomposition-plan.md

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,6 +1531,7 @@ cargo check --workspace
15311531
- 已将 `ToolImageAttachment` 提升到 `bitfun-core-types`,并由 `bitfun-ai-adapters``bitfun-agent-tools``bitfun-core::util::types` 保留旧路径兼容;`bitfun-agent-tools` 不再依赖 `bitfun-ai-adapters`
15321532
- 已将 `product-domains``dirs` 依赖限制到 `miniapp` feature,默认 profile 保持轻量。
15331533
- 已补充 `ToolResult` image attachment 序列化 round-trip 测试,以及 tool registry readonly 工具清单快照测试;后续迁移 `ToolUseContext`、registry/provider 或 concrete tool implementation 前必须保持这些基线。
1534+
- PR 1 已开始执行:remote-SSH workspace registry / ambiguous root resolution / legacy state snapshot 已迁入 `bitfun-services-integrations::remote_ssh::RemoteWorkspaceRegistry`,core 仅保留 local assistant path guard 与 SSH manager / file service / terminal manager 组装;announcement state persistence 已迁入 `bitfun-services-integrations::announcement::AnnouncementStateStore`,core 旧 `PathManager` 构造 API 继续委托并映射原错误类型。
15341535
- 本批 dependency profile 基线已验证:
15351536
- `cargo tree -p bitfun-core-types --depth 1 --edges features` 运行时依赖仅显示 `serde`,测试依赖显示 `serde_json`
15361537
- `cargo tree -p bitfun-runtime-ports --depth 1 --edges features` 仅显示 `async-trait``serde``serde_json`
@@ -1562,6 +1563,16 @@ cargo check --workspace
15621563
- tool registry 迁移后工具清单无法证明等价。
15631564
- 新 owner crate 反向依赖 core。
15641565

1566+
**剩余工作压缩为 5 个 PR(2026-05-13):**
1567+
1568+
1. `services-integrations` runtime 收口:迁移 remote-SSH 中不直接持有 SSH channel / SFTP / terminal handle 的 workspace registry、session mirror 与轻量 runtime helper;继续保留 SSH manager / remote FS / remote terminal 的 core-owned assembly,直到 port/provider 合约明确。`file-watch` 已由 `services-integrations` 拥有,只做 contract 复核;announcement 只迁移不依赖 config service / embedded content / remote fetch 的 state 或 eligibility helper。验收重点是 owner crate contract test、旧路径 facade、boundary check、workspace check/test。
1569+
2. MCP runtime 与 dynamic tools:迁移 MCP config service orchestration、server process / transport lifecycle、adapter、dynamic tool/resource/prompt provider;不混入 remote-connect 或 tool registry owner 化。验收重点是 MCP wire shape、auth/config merge、dynamic manifest 快照和 core registry 集成等价。
1570+
3. remote-connect runtime:通过 `AgentSubmissionPort``SessionTranscriptReader``EventSink` 等 port 合约迁移 remote-connect / relay-facing runtime。验收重点是 message contract、session submission、remote control 同步与无真实网络依赖的 integration test。
1571+
4. agent tools + `tool-packs` owner 化:`ToolUseContext` port 化、tool registry/provider 化、concrete tool implementation 按 feature group 迁移;必须保持 builtin/readonly/dynamic manifest、snapshot wrapping、runtime restrictions、cancellation 与 Deep Review tool flow 等价。
1572+
5. `product-domains` runtime + core facade finalization:迁移 miniapp runtime/compiler/builtin 与 function-agent 运行逻辑,最后把 `bitfun-core` 收敛为 facade + product runtime assembly;不在本 PR 中修改 `bitfun-core default = []` 或 per-product feature matrix。
1573+
1574+
`bitfun-core default = []`、per-product feature set、构建矩阵和 release 能力调整仍作为重构完成后的独立评估,不计入上述 5 个 PR。
1575+
15651576
### 里程碑三:facade 收敛、边界强制与可选默认轻量化评估
15661577

15671578
**覆盖计划:**
@@ -1640,20 +1651,21 @@ git diff -- package.json scripts/dev.cjs scripts/desktop-tauri-build.mjs scripts
16401651

16411652
## 11. 推荐 PR 顺序
16421653

1643-
1. 文档与基线护栏。
1644-
2. `product-full` feature 安全网,不改变 default 行为。
1645-
3. 移动 nested `terminal-core``tool-runtime` 到 workspace 顶层。
1646-
4. 抽取 `bitfun-core-types`,先放错误和第一批稳定 DTO。
1647-
5. 抽取 `bitfun-agent-stream`,迁移 stream processor 测试。
1648-
6. 引入 runtime ports 初始边界;后续在 service 迁移中逐步打断 `service <-> agentic` concrete 循环。
1649-
7. 抽取 `bitfun-services-core`
1650-
8. 抽取 `bitfun-services-integrations` 的低风险 feature group 和纯 helper,先闭环 `git`、remote-SSH contract/helper、MCP 纯 protocol/config/auth helper;不把 remote-connect 或 MCP dynamic runtime 顺带迁入。
1651-
9. 前移低风险保护项:dependency profile / feature graph 基线、轻量 contract crate 依赖瘦身、feature group 说明、boundary check 扩展、迁移前快照测试。
1652-
10. 拆解 agent tools 为 `bitfun-agent-tools``bitfun-tool-packs` 的 contract/provider 前置层;先锁定工具清单、dynamic provider metadata、snapshot wrapper 等价,再评估 `ToolUseContext` 和具体工具实现外移。
1653-
11. 抽取 `bitfun-product-domains` 的纯 DTO / policy / builder 层;miniapp runtime/compiler/builtin 和 function-agent 运行逻辑必须作为单独高风险迁移评审。
1654-
12. 分批迁移高风险 runtime:remote-SSH runtime、MCP runtime、remote-connect、tool registry/provider、miniapp/function-agent runtime、agent registry。每批只处理一个 owner,先补 port/provider 设计和等价测试。
1655-
13.`bitfun-core` 收敛为 facade + product runtime assembly,并验证它不再承载新的业务实现。
1656-
14. 只有在全产品显式启用完整 runtime、dependency profile 证明收益、完整门禁通过后,单独评估 `bitfun-core default = []` 或 per-product feature set。
1654+
1. 已完成:文档与基线护栏。
1655+
2. 已完成:`product-full` feature 安全网,不改变 default 行为。
1656+
3. 已完成:移动 nested `terminal-core``tool-runtime` 到 workspace 顶层。
1657+
4. 已完成:抽取 `bitfun-core-types`,先放错误和第一批稳定 DTO。
1658+
5. 已完成:抽取 `bitfun-agent-stream`,迁移 stream processor 测试。
1659+
6. 已完成:引入 runtime ports 初始边界;后续在 service 迁移中逐步打断 `service <-> agentic` concrete 循环。
1660+
7. 已完成:抽取 `bitfun-services-core`
1661+
8. 已完成:抽取 `bitfun-services-integrations` 的低风险 feature group 和纯 helper,闭环 `git`、remote-SSH contract/helper、MCP 纯 protocol/config/auth helper;未把 remote-connect 或 MCP dynamic runtime 顺带迁入。
1662+
9. 已完成:前移低风险保护项:dependency profile / feature graph 基线、轻量 contract crate 依赖瘦身、feature group 说明、boundary check 扩展、迁移前快照测试。
1663+
10. 进行中:PR 1 `services-integrations` runtime 收口,先处理 remote-SSH workspace registry / session mirror helper 和已迁移 file-watch 的 contract 复核;announcement 仅迁移无 config/content/remote fetch 依赖的 helper。
1664+
11. 待执行:PR 2 MCP runtime 与 dynamic tools。
1665+
12. 待执行:PR 3 remote-connect runtime。
1666+
13. 待执行:PR 4 agent tools + `tool-packs` owner 化。
1667+
14. 待执行:PR 5 `product-domains` runtime + core facade finalization。
1668+
15. 后续独立评估:`bitfun-core default = []` 或 per-product feature set。
16571669

16581670
冗余清理 PR 不进入上述主线序号。只有在满足 `0A.6` 的绝对等价要求时,才可以插入到相邻里程碑之间,并且不得与主线拆分 PR 混合。
16591671

scripts/check-core-boundaries.mjs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,39 @@ const forbiddenContentRules = [
583583
regex: /\bpub fn unresolved_remote_session_storage_key\b/,
584584
message: 'core remote SSH workspace runtime must not redefine unresolved session keys; use the integrations contract',
585585
},
586+
{
587+
regex: /\bstruct RegisteredRemoteWorkspace\b/,
588+
message: 'core remote SSH workspace runtime must not own workspace registrations; use the integrations registry',
589+
},
590+
{
591+
regex: /\bpub struct RemoteWorkspaceEntry\b/,
592+
message: 'core remote SSH workspace runtime must not redefine workspace entries; use the integrations registry',
593+
},
594+
{
595+
regex: /\bpub struct RemoteWorkspaceState\b/,
596+
message: 'core remote SSH workspace runtime must not redefine legacy workspace state; use the integrations registry',
597+
},
598+
{
599+
regex: /\bregistration_matches_path\b/,
600+
message: 'core remote SSH workspace runtime must not own path-to-registration matching; use the integrations registry',
601+
},
602+
],
603+
},
604+
{
605+
path: 'src/crates/core/src/service/announcement/state_store.rs',
606+
patterns: [
607+
{
608+
regex: /\btokio::fs\b/,
609+
message: 'core announcement state store facade must not own filesystem persistence; use the integrations state store',
610+
},
611+
{
612+
regex: /\bserde_json::to_string_pretty\b/,
613+
message: 'core announcement state store facade must not own state serialization; use the integrations state store',
614+
},
615+
{
616+
regex: /\bserde_json::from_str\b/,
617+
message: 'core announcement state store facade must not own state deserialization; use the integrations state store',
618+
},
586619
],
587620
},
588621
];
@@ -854,6 +887,10 @@ function runManifestParserSelfTest() {
854887
'local_workspace_stable_storage_id',
855888
'remote_workspace_stable_id',
856889
'unresolved_remote_session_storage_key',
890+
'RegisteredRemoteWorkspace',
891+
'RemoteWorkspaceEntry',
892+
'RemoteWorkspaceState',
893+
'registration_matches_path',
857894
];
858895
const ruleText = remoteWorkspaceRule.patterns.map((pattern) => pattern.regex.source).join('\n');
859896
for (const helper of remoteWorkspaceHelpers) {
@@ -862,6 +899,13 @@ function runManifestParserSelfTest() {
862899
}
863900
}
864901

902+
const announcementStateStoreRule = forbiddenContentRules.find(
903+
(rule) => rule.path === 'src/crates/core/src/service/announcement/state_store.rs',
904+
);
905+
if (!announcementStateStoreRule) {
906+
throw new Error('missing announcement state store boundary rule');
907+
}
908+
865909
const mcpProcessRule = forbiddenContentRules.find(
866910
(rule) => rule.path === 'src/crates/core/src/service/mcp/server/process.rs',
867911
);
Lines changed: 21 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,41 @@
1-
//! Persistent state store for the announcement system.
2-
//!
3-
//! Reads and writes `announcement-state.json` in the user config directory.
4-
51
use super::types::AnnouncementState;
62
use crate::infrastructure::app_paths::PathManager;
7-
use crate::util::errors::BitFunResult;
8-
use log::{debug, warn};
9-
use std::path::PathBuf;
3+
use crate::util::errors::{BitFunError, BitFunResult};
104
use std::sync::Arc;
11-
use tokio::fs;
125

136
pub struct AnnouncementStateStore {
14-
state_file: PathBuf,
7+
inner: bitfun_services_integrations::announcement::AnnouncementStateStore,
158
}
169

1710
impl AnnouncementStateStore {
1811
pub fn new(path_manager: &Arc<PathManager>) -> Self {
19-
let state_file = path_manager
20-
.user_config_dir()
21-
.join("announcement-state.json");
22-
Self { state_file }
12+
Self {
13+
inner: bitfun_services_integrations::announcement::AnnouncementStateStore::new(
14+
path_manager.user_config_dir(),
15+
),
16+
}
2317
}
2418

2519
/// Load state from disk. Returns a default state if the file does not exist.
2620
pub async fn load(&self) -> BitFunResult<AnnouncementState> {
27-
match fs::read_to_string(&self.state_file).await {
28-
Ok(content) => {
29-
let state =
30-
serde_json::from_str::<AnnouncementState>(&content).unwrap_or_else(|e| {
31-
warn!("Failed to parse announcement state, using default: {}", e);
32-
AnnouncementState::default()
33-
});
34-
debug!("Loaded announcement state from {:?}", self.state_file);
35-
Ok(state)
36-
}
37-
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
38-
debug!("Announcement state file not found, using default");
39-
Ok(AnnouncementState::default())
40-
}
41-
Err(e) => {
42-
warn!("Failed to read announcement state file: {}", e);
43-
Ok(AnnouncementState::default())
44-
}
45-
}
21+
self.inner.load().await.map_err(map_state_store_error)
4622
}
4723

4824
/// Persist state to disk.
4925
pub async fn save(&self, state: &AnnouncementState) -> BitFunResult<()> {
50-
if let Some(parent) = self.state_file.parent() {
51-
if !parent.exists() {
52-
fs::create_dir_all(parent).await?;
53-
}
26+
self.inner.save(state).await.map_err(map_state_store_error)
27+
}
28+
}
29+
30+
fn map_state_store_error(
31+
err: bitfun_services_integrations::announcement::AnnouncementStateStoreError,
32+
) -> BitFunError {
33+
match err {
34+
bitfun_services_integrations::announcement::AnnouncementStateStoreError::Io(err) => {
35+
BitFunError::Io(err)
5436
}
55-
let content = serde_json::to_string_pretty(state)?;
56-
fs::write(&self.state_file, content).await?;
57-
debug!("Saved announcement state to {:?}", self.state_file);
58-
Ok(())
37+
bitfun_services_integrations::announcement::AnnouncementStateStoreError::Serialization(
38+
err,
39+
) => BitFunError::Serialization(err),
5940
}
6041
}

0 commit comments

Comments
 (0)