From cda30803002f89e17301311d5c097e391db5c961 Mon Sep 17 00:00:00 2001 From: limityan Date: Tue, 2 Jun 2026 16:58:03 +0800 Subject: [PATCH 1/2] refactor(runtime): close product and tool owners --- AGENTS-CN.md | 13 +- AGENTS.md | 13 +- CONTRIBUTING.md | 126 +++------ CONTRIBUTING_CN.md | 99 ++----- MiniApp/Skills/miniapp-dev/SKILL.md | 20 +- MiniApp/Skills/miniapp-dev/design-playbook.md | 8 +- .../agent-runtime-services-design.md | 37 ++- docs/architecture/core-decomposition.md | 116 ++++---- docs/plans/core-decomposition-completed.md | 33 ++- docs/plans/core-decomposition-plan.md | 30 ++- scripts/check-core-boundaries.mjs | 64 +++-- src/apps/desktop/src/api/tool_api.rs | 14 +- src/crates/agent-stream/AGENTS.md | 27 ++ src/crates/agent-tools/AGENTS.md | 28 +- src/crates/api-layer/AGENTS.md | 28 ++ src/crates/core-types/AGENTS.md | 27 ++ src/crates/core/AGENTS-CN.md | 119 ++++---- src/crates/core/AGENTS.md | 254 +++++------------- .../agentic/tools/file_read_state_runtime.rs | 3 +- .../implementations/ask_user_question_tool.rs | 3 +- .../tools/implementations/bash_tool.rs | 15 +- .../tools/implementations/code_review_tool.rs | 3 +- .../tools/implementations/control_hub_tool.rs | 3 +- .../tools/implementations/cron_tool.rs | 3 +- .../tools/implementations/file_write_tool.rs | 31 +-- .../implementations/session_control_tool.rs | 3 +- .../implementations/session_message_tool.rs | 3 +- .../tools/implementations/skill_tool.rs | 24 +- .../tools/implementations/task_tool.rs | 16 +- .../tools/implementations/web_tools.rs | 3 +- .../src/agentic/tools/manifest_resolver.rs | 3 +- .../agentic/tools/pipeline/tool_pipeline.rs | 2 +- .../core/src/agentic/tools/product_runtime.rs | 91 +++---- .../agentic/tools/product_runtime/catalog.rs | 8 +- .../product_runtime/get_tool_spec_tool.rs | 3 +- .../tools/product_runtime/materialization.rs | 77 ++++++ .../tools/product_runtime/unlock_state.rs | 102 +++++-- .../src/agentic/tools/tool_context_runtime.rs | 93 ++++--- .../src/agentic/tools/tool_result_storage.rs | 3 +- src/crates/core/src/miniapp/builtin/mod.rs | 64 +---- src/crates/events/AGENTS.md | 27 ++ src/crates/product-domains/AGENTS-CN.md | 56 ++-- src/crates/product-domains/AGENTS.md | 65 ++--- .../product-domains/src/miniapp/builtin.rs | 94 ++++++- .../builtin/assets/coding-selfie/index.html | 0 .../builtin/assets/coding-selfie/meta.json | 0 .../builtin/assets/coding-selfie/style.css | 0 .../builtin/assets/coding-selfie/ui.js | 0 .../builtin/assets/coding-selfie/worker.js | 0 .../builtin/assets/divination/index.html | 0 .../builtin/assets/divination/meta.json | 0 .../builtin/assets/divination/style.css | 0 .../miniapp/builtin/assets/divination/ui.js | 0 .../builtin/assets/divination/worker.js | 0 .../miniapp/builtin/assets/gomoku/index.html | 0 .../miniapp/builtin/assets/gomoku/meta.json | 0 .../miniapp/builtin/assets/gomoku/style.css | 0 .../src/miniapp/builtin/assets/gomoku/ui.js | 0 .../miniapp/builtin/assets/gomoku/worker.js | 0 .../builtin/assets/pr-review/index.html | 0 .../builtin/assets/pr-review/meta.json | 0 .../builtin/assets/pr-review/style.css | 0 .../miniapp/builtin/assets/pr-review/ui.js | 0 .../builtin/assets/pr-review/worker.js | 0 .../assets/regex-playground/index.html | 0 .../builtin/assets/regex-playground/meta.json | 0 .../builtin/assets/regex-playground/style.css | 0 .../builtin/assets/regex-playground/ui.js | 0 .../builtin/assets/regex-playground/worker.js | 0 src/crates/runtime-ports/AGENTS.md | 27 ++ src/crates/runtime-ports/src/lib.rs | 85 ++++++ src/crates/runtime-services/AGENTS.md | 28 ++ src/crates/services-integrations/AGENTS.md | 27 +- src/crates/terminal/AGENTS.md | 28 ++ src/crates/tool-packs/AGENTS.md | 4 +- src/crates/tool-runtime/AGENTS.md | 29 ++ src/crates/transport/AGENTS.md | 27 ++ src/crates/webdriver/AGENTS.md | 24 ++ 78 files changed, 1189 insertions(+), 944 deletions(-) create mode 100644 src/crates/agent-stream/AGENTS.md create mode 100644 src/crates/api-layer/AGENTS.md create mode 100644 src/crates/core-types/AGENTS.md create mode 100644 src/crates/core/src/agentic/tools/product_runtime/materialization.rs create mode 100644 src/crates/events/AGENTS.md rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/coding-selfie/index.html (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/coding-selfie/meta.json (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/coding-selfie/style.css (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/coding-selfie/ui.js (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/coding-selfie/worker.js (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/divination/index.html (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/divination/meta.json (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/divination/style.css (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/divination/ui.js (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/divination/worker.js (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/gomoku/index.html (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/gomoku/meta.json (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/gomoku/style.css (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/gomoku/ui.js (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/gomoku/worker.js (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/pr-review/index.html (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/pr-review/meta.json (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/pr-review/style.css (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/pr-review/ui.js (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/pr-review/worker.js (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/regex-playground/index.html (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/regex-playground/meta.json (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/regex-playground/style.css (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/regex-playground/ui.js (100%) rename src/crates/{core => product-domains}/src/miniapp/builtin/assets/regex-playground/worker.js (100%) create mode 100644 src/crates/runtime-ports/AGENTS.md create mode 100644 src/crates/runtime-services/AGENTS.md create mode 100644 src/crates/terminal/AGENTS.md create mode 100644 src/crates/tool-runtime/AGENTS.md create mode 100644 src/crates/transport/AGENTS.md create mode 100644 src/crates/webdriver/AGENTS.md diff --git a/AGENTS-CN.md b/AGENTS-CN.md index f6ff5ebda..ac712f2f5 100644 --- a/AGENTS-CN.md +++ b/AGENTS-CN.md @@ -18,7 +18,13 @@ BitFun 是一个由 Rust workspace 与 React 前端组成的项目。 | 模块 | 路径 | Agent 文档 | |---|---|---| | 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 指南) | +| Core 共享 DTO | `src/crates/core-types` | [AGENTS.md](src/crates/core-types/AGENTS.md) | +| 事件契约 | `src/crates/events` | [AGENTS.md](src/crates/events/AGENTS.md) | +| Agent stream 归一化 | `src/crates/agent-stream` | [AGENTS.md](src/crates/agent-stream/AGENTS.md) | +| Runtime ports | `src/crates/runtime-ports` | [AGENTS.md](src/crates/runtime-ports/AGENTS.md) | +| Runtime services | `src/crates/runtime-services` | [AGENTS.md](src/crates/runtime-services/AGENTS.md) | +| Terminal 基础设施 | `src/crates/terminal` | [AGENTS.md](src/crates/terminal/AGENTS.md) | +| 底层 tool runtime | `src/crates/tool-runtime` | [AGENTS.md](src/crates/tool-runtime/AGENTS.md) | | Agent runtime owner crate | `src/crates/agent-runtime` | [AGENTS.md](src/crates/agent-runtime/AGENTS.md) | | Harness workflow contracts | `src/crates/harness` | [AGENTS.md](src/crates/harness/AGENTS.md) | | Service core owner crate | `src/crates/services-core` | [AGENTS.md](src/crates/services-core/AGENTS.md) | @@ -26,10 +32,11 @@ BitFun 是一个由 Rust workspace 与 React 前端组成的项目。 | Agent tool contracts | `src/crates/agent-tools` | [AGENTS.md](src/crates/agent-tools/AGENTS.md) | | Tool pack provider plan | `src/crates/tool-packs` | [AGENTS.md](src/crates/tool-packs/AGENTS.md) | | 产品领域 crate | `src/crates/product-domains` | [AGENTS.md](src/crates/product-domains/AGENTS.md) | -| Transport 适配层 | `src/crates/transport` | (使用 core 指南) | -| API layer | `src/crates/api-layer` | (使用 core 指南) | +| Transport 适配层 | `src/crates/transport` | [AGENTS.md](src/crates/transport/AGENTS.md) | +| API layer | `src/crates/api-layer` | [AGENTS.md](src/crates/api-layer/AGENTS.md) | | ACP 集成 | `src/crates/acp` | [AGENTS.md](src/crates/acp/AGENTS.md) | | AI adapters | `src/crates/ai-adapters` | [AGENTS.md](src/crates/ai-adapters/AGENTS.md) | +| 嵌入式 WebDriver | `src/crates/webdriver` | [AGENTS.md](src/crates/webdriver/AGENTS.md) | | 桌面应用 | `src/apps/desktop` | [AGENTS.md](src/apps/desktop/AGENTS.md) | | Server | `src/apps/server` | (使用 core 指南) | | CLI | `src/apps/cli` | (使用 core 指南) | diff --git a/AGENTS.md b/AGENTS.md index a92e25e32..73a1e777e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,7 +18,13 @@ Repository rule: **keep product logic platform-agnostic, then expose it through | Module | Path | Agent doc | |---|---|---| | 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) | +| Core shared DTOs | `src/crates/core-types` | [AGENTS.md](src/crates/core-types/AGENTS.md) | +| Event contracts | `src/crates/events` | [AGENTS.md](src/crates/events/AGENTS.md) | +| Agent stream normalization | `src/crates/agent-stream` | [AGENTS.md](src/crates/agent-stream/AGENTS.md) | +| Runtime ports | `src/crates/runtime-ports` | [AGENTS.md](src/crates/runtime-ports/AGENTS.md) | +| Runtime services | `src/crates/runtime-services` | [AGENTS.md](src/crates/runtime-services/AGENTS.md) | +| Terminal infrastructure | `src/crates/terminal` | [AGENTS.md](src/crates/terminal/AGENTS.md) | +| Low-level tool runtime | `src/crates/tool-runtime` | [AGENTS.md](src/crates/tool-runtime/AGENTS.md) | | Agent runtime owner crate | `src/crates/agent-runtime` | [AGENTS.md](src/crates/agent-runtime/AGENTS.md) | | Harness workflow contracts | `src/crates/harness` | [AGENTS.md](src/crates/harness/AGENTS.md) | | Service core owner crate | `src/crates/services-core` | [AGENTS.md](src/crates/services-core/AGENTS.md) | @@ -26,10 +32,11 @@ Repository rule: **keep product logic platform-agnostic, then expose it through | Agent tool contracts | `src/crates/agent-tools` | [AGENTS.md](src/crates/agent-tools/AGENTS.md) | | Tool pack provider plan | `src/crates/tool-packs` | [AGENTS.md](src/crates/tool-packs/AGENTS.md) | | Product domains | `src/crates/product-domains` | [AGENTS.md](src/crates/product-domains/AGENTS.md) | -| Transport adapters | `src/crates/transport` | (use core guide) | -| API layer | `src/crates/api-layer` | (use core guide) | +| Transport adapters | `src/crates/transport` | [AGENTS.md](src/crates/transport/AGENTS.md) | +| API layer | `src/crates/api-layer` | [AGENTS.md](src/crates/api-layer/AGENTS.md) | | ACP integration | `src/crates/acp` | [AGENTS.md](src/crates/acp/AGENTS.md) | | AI adapters | `src/crates/ai-adapters` | [AGENTS.md](src/crates/ai-adapters/AGENTS.md) | +| Embedded WebDriver | `src/crates/webdriver` | [AGENTS.md](src/crates/webdriver/AGENTS.md) | | Desktop app | `src/apps/desktop` | [AGENTS.md](src/apps/desktop/AGENTS.md) | | Server | `src/apps/server` | (use core guide) | | CLI | `src/apps/cli` | (use core guide) | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cf54bf786..324ebe14a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,81 +54,27 @@ pnpm run e2e:test ### Desktop debugging tools -When working on desktop UI/UX, the `devtools` Cargo feature provides additional debugging capabilities. It is automatically enabled in `dev` builds and `release-fast` profile builds, but never in `release` builds for end users. - -| Shortcut | Action | -|---|---| -| `Cmd/Ctrl + Shift + I` | Toggle element inspector — hover to highlight elements, click to capture metadata | -| `Cmd/Ctrl + Shift + J` | Open native webview DevTools window | - -The element inspector injects a lightweight script into the main webview. When you click an element, it captures: -- Tag, id, class, CSS selector path -- Computed styles and CSS variables -- Box model (margin, padding, border) -- Color values (text, background, border) -- Element attributes - -Captured data is logged as structured JSON under the `bitfun::devtools` target. +Desktop dev builds enable the `devtools` Cargo feature. Use +`Cmd/Ctrl + Shift + I` for the element inspector and `Cmd/Ctrl + Shift + J` for +native webview DevTools. These tools are disabled in end-user `release` builds. ## Code Standards and Architecture Constraints -### Logging - -- English only, avoid verbose logs -- Frontend: `createLogger('ModuleName')` -- Backend: `log::{info, debug, warn, error}` macros - -### Internationalization - -- Locale metadata lives in `src/shared/i18n/contract/locales.json`; run - `pnpm run i18n:generate` after editing it. -- Put stable cross-surface labels in `src/shared/i18n/resources/shared`; keep - workflow copy in the owning surface. -- Web UI route or feature copy should use `useI18n(namespace)`. Do not import - Web UI locale catalogs into mobile-web, installer, backend, or static pages. -- Static self-contained pages may use generated page-scoped shared-term files - instead of copying stable labels. -- User-visible dates, times, and numbers should use shared i18n formatting - helpers instead of direct `Intl.*` or `toLocale*` calls. -- `pnpm run i18n:audit` enforces key/placeholder parity, direct static key - existence, dynamic key source proofs, literal fallback and locale-format - no-growth baselines, shared-term/l10n governance baselines, non-blocking - same-text locale inventory, and the no-hardcoded-CJK source budget. - -### Platform-agnostic core - -Do not use platform-specific dependencies in `core`: - -- ❌ `tauri::AppHandle` -- ✅ `bitfun_events::EventEmitter` - -For `bitfun-core` decomposition or build-speed refactors, follow -[`docs/architecture/core-decomposition.md`](docs/architecture/core-decomposition.md) -and do not change product feature sets or release scripts as a side effect. - -For Deep Review / Code Review Team changes, keep -[`docs/architecture/deep-review.md`](docs/architecture/deep-review.md), -`src/crates/core/src/agentic/deep_review/CONTRIBUTING.md`, and -`src/web-ui/src/flow_chat/deep-review/CONTRIBUTING.md` aligned with the -implementation. - -### Tauri command conventions - -- Command names use `snake_case` -- Keep Rust and TypeScript naming aligned -- Always use structured request format: - -```rust -#[tauri::command] -pub async fn your_command( - state: State<'_, AppState>, - request: YourRequest, -) -> Result -``` - -```ts -await api.invoke("your_command", { request: { /* ... */ } }); -``` +Use [`AGENTS.md`](AGENTS.md) as the canonical source for architecture-sensitive +rules, module boundaries, and the verification matrix. In contributor-facing +terms: + +- Logs are English-only and should stay useful, not noisy. +- User-visible copy should use the project i18n flow; do not share Web UI + locale catalogs with smaller surfaces. +- Shared core must stay platform-agnostic. Desktop/Tauri details belong in app + adapters and flow back through transport/API layers. +- Tauri commands use `snake_case` command names and structured `request` + payloads. +- Core decomposition, feature-boundary, dependency-boundary, and build-speed + work must follow `docs/architecture/core-decomposition.md`. +- Deep Review / Code Review Team changes must keep the core and Web UI guidance + aligned with the implementation. ## Key Contribution Focus Areas @@ -185,29 +131,25 @@ Keep PRs small and focused. Avoid bundling unrelated changes. ## Testing and Verification -Run relevant tests for your change. You do not need to run every row below; choose the smallest set that matches the files and behavior you touched: - -CI covers full builds and broad test suites. Local prechecks should stay focused -unless the change directly touches build, packaging, or a path not covered by CI. +Run the smallest checks that match the changed files and behavior. CI covers +full builds and broad test suites; local prechecks should stay focused unless +the change affects build, packaging, release behavior, or a path CI cannot +protect. -For `/usage` UI copy changes, keep `en-US`, `zh-CN`, and `zh-TW` locale strings in sync. +Common local checks: -| Change type | Recommended verification | +| Change type | Typical verification | | --- | --- | -| Repository metadata, PR/issue templates, or GitHub workflows | `pnpm run check:repo-hygiene && pnpm run check:github-config && git diff --check` | -| Locale resource-only changes | `pnpm run i18n:audit` | -| Locale contract or shared terms | `pnpm run i18n:generate && pnpm run i18n:contract:test && pnpm run i18n:audit` | -| Web UI state, adapters, or runtime code | `pnpm run type-check:web`, plus the nearest focused test when behavior changed | -| Web UI i18n runtime or namespace-loading changes | `pnpm run i18n:contract:test && pnpm run type-check:web && pnpm --dir src/web-ui run test:run src/infrastructure/i18n/core/I18nService.test.ts` | -| Mobile web UI, pairing, reconnect, disconnect, or chat-flow changes | `pnpm --dir src/mobile-web run type-check` | -| Installer frontend or i18n runtime without packaging changes | `pnpm --dir BitFun-Installer run type-check` | -| Rust core, transport, API layer, services, or shared runtime logic | `cargo check --workspace`, plus the nearest focused `cargo test` when behavior changed | -| Desktop integration, Tauri APIs, or desktop-only behavior | `cargo check -p bitfun-desktop`, plus focused desktop tests when behavior changed | -| E2E-covered behavior | Run the closest focused E2E/smoke check; rely on CI for broad build/test coverage unless build behavior changed | - -For mobile-web pairing, reconnect, disconnect, or chat-flow changes, include manual verification steps and screenshots or a short recording when the UI changes. - -If you cannot run tests, explain why in the PR and provide manual verification steps. +| Repository metadata or GitHub config | `pnpm run check:repo-hygiene && pnpm run check:github-config && git diff --check` | +| Frontend runtime or UI | `pnpm run type-check:web`, plus the nearest focused test when behavior changed | +| Mobile web | `pnpm --dir src/mobile-web run type-check` | +| Rust shared runtime or services | `cargo check --workspace`, plus a focused `cargo test` when behavior changed | +| Desktop/Tauri integration | `cargo check -p bitfun-desktop` | +| i18n resources or contract | use the matching i18n row in `AGENTS.md` | + +For UI changes, include screenshots or a short recording when helpful. If you +cannot run a relevant check, explain why in the PR and provide a lower-risk +manual verification path. ## Security and Compliance diff --git a/CONTRIBUTING_CN.md b/CONTRIBUTING_CN.md index d022c9a75..b8e48353d 100644 --- a/CONTRIBUTING_CN.md +++ b/CONTRIBUTING_CN.md @@ -54,67 +54,20 @@ pnpm run e2e:test ### 桌面端调试工具 -开发桌面端 UI/UX 时,`devtools` Cargo feature 提供额外的调试能力。它在 `dev` 构建和 `release-fast` profile 构建中自动启用,但在面向最终用户的 `release` 构建中永不启用。 - -| 快捷键 | 功能 | -|---|---| -| `Cmd/Ctrl + Shift + I` | 切换元素检查器 — 悬停高亮元素,点击采集元数据 | -| `Cmd/Ctrl + Shift + J` | 打开原生 webview DevTools 窗口 | - -元素检查器向主 webview 注入一个轻量脚本。点击元素后会采集: -- 标签、id、class、CSS 选择器路径 -- Computed styles 和 CSS 变量 -- Box model(margin、padding、border) -- 颜色值(文本、背景、边框) -- 元素属性 - -采集的数据以结构化 JSON 形式输出到 `bitfun::devtools` 日志目标下。 +桌面端 dev 构建会启用 `devtools` Cargo feature。`Cmd/Ctrl + Shift + I` 打开元素检查器, +`Cmd/Ctrl + Shift + J` 打开原生 webview DevTools;面向最终用户的 `release` 构建不会启用这些工具。 ## 代码规范与架构约束 -### 日志规范 - -- 仅使用英文日志,避免冗长输出 -- 前端:`createLogger('ModuleName')` -- 后端:`log::{info, debug, warn, error}` 宏 - -### 国际化 - -- Locale 元数据统一维护在 `src/shared/i18n/contract/locales.json`;修改后运行 - `pnpm run i18n:generate`。 -- 跨形态稳定标签放在 `src/shared/i18n/resources/shared`;流程文案留在所属形态资源中。 -- Web UI 路由或功能文案使用 `useI18n(namespace)`。不要把 Web UI locale 资源导入 mobile-web、installer、backend 或静态页面。 -- `pnpm run i18n:audit` 会检查 key / 占位符一致性、直接静态 key 是否存在,以及 - source 中不再新增硬编码 CJK 文案。 - -### 平台无关核心 - -`core` 中禁止引入平台相关依赖: +架构敏感规则、模块边界和验证矩阵以 [`AGENTS.md`](AGENTS.md) 为准。面向贡献者只需把握: -- ❌ `tauri::AppHandle` -- ✅ `bitfun_events::EventEmitter` - -进行 `bitfun-core` 拆解或构建提速重构时,请遵循 -[`docs/architecture/core-decomposition.md`](docs/architecture/core-decomposition.md), -不要把产品 feature set 或 release 脚本变更作为顺手改动。 - -### Tauri 命令规范 - -- 命令名使用 `snake_case` -- Rust 与 TypeScript 命名保持一致 -- 必须使用结构化请求格式: - -```rust -#[tauri::command] -pub async fn your_command( - state: State<'_, AppState>, - request: YourRequest, -) -> Result -``` - -```ts -await api.invoke("your_command", { request: { /* ... */ } }); -``` +- 日志只使用英文,并保持必要、可读。 +- 用户可见文案走项目 i18n 流程;不要把 Web UI locale catalog 共享给较小产品形态。 +- shared core 必须保持平台无关;Desktop/Tauri 细节属于 app adapter,并通过 transport / API layer 回流。 +- Tauri command 使用 `snake_case` 命令名和结构化 `request` 参数。 +- core 拆解、feature 边界、依赖边界和构建提速重构必须遵循 + `docs/architecture/core-decomposition.md`。 +- Deep Review / 代码审核团队改动需要保持 core 与 Web UI 指南和实现一致。 ## 重点关注的贡献方向 @@ -171,29 +124,21 @@ UI 改动请附前后对比截图或短录屏,方便快速评审。 ## 测试与验证 -按改动范围运行相关测试;不需要跑完下方所有命令,只选择与本次改动文件和行为匹配的最小集合: - -完整构建和大范围测试由 CI 保护。本地预检应保持聚焦;只有改动直接触及构建、打包, -或 CI 不覆盖对应路径时才运行更重命令。 +按改动文件和行为选择最小检查。完整构建和大范围测试由 CI 保护;只有改动影响构建、打包、发布行为, +或 CI 无法覆盖对应路径时,才在本地运行更重命令。 -修改 `/usage` UI 文案时,请同步 `en-US`、`zh-CN`、`zh-TW` 多语言文本。 +常见本地检查: -| 改动类型 | 推荐验证 | +| 改动类型 | 常用验证 | | --- | --- | -| 仓库元信息、PR/Issue 模板或 GitHub workflow | `pnpm run check:repo-hygiene && pnpm run check:github-config && git diff --check` | -| 仅 locale 资源改动 | `pnpm run i18n:audit` | -| Locale contract 或 shared terms | `pnpm run i18n:generate && pnpm run i18n:contract:test && pnpm run i18n:audit` | -| Web UI 状态、适配层或运行时代码 | `pnpm run type-check:web`;行为变化时再加最近的 focused test | -| Web UI i18n runtime 或 namespace loading 改动 | `pnpm run i18n:contract:test && pnpm run type-check:web && pnpm --dir src/web-ui run test:run src/infrastructure/i18n/core/I18nService.test.ts` | -| Mobile web UI、配对、重连、断开或聊天流程 | `pnpm --dir src/mobile-web run type-check` | -| 不涉及打包的安装器前端或 i18n runtime | `pnpm --dir BitFun-Installer run type-check` | -| Rust core、transport、API layer、services 或共享运行时逻辑 | `cargo check --workspace`;行为变化时再加最近的 focused `cargo test` | -| 桌面端集成、Tauri API 或桌面端专属行为 | `cargo check -p bitfun-desktop`;行为变化时再加 focused desktop tests | -| E2E 覆盖的行为 | 运行最近的 focused E2E/smoke check;除非影响构建,否则 broad build/test 交给 CI | - -Mobile web 配对、重连、断开或聊天流程改动,需要在 PR 中补充手动验证步骤;涉及 UI 变化时,请附截图或短录屏。 - -如暂时无法运行测试,请在 PR 描述中说明原因,并提供手动验证步骤。 +| 仓库元信息或 GitHub 配置 | `pnpm run check:repo-hygiene && pnpm run check:github-config && git diff --check` | +| 前端运行时或 UI | `pnpm run type-check:web`;行为变化时再加最近的 focused test | +| Mobile web | `pnpm --dir src/mobile-web run type-check` | +| Rust 共享 runtime 或 services | `cargo check --workspace`;行为变化时再加 focused `cargo test` | +| Desktop/Tauri 集成 | `cargo check -p bitfun-desktop` | +| i18n 资源或契约 | 使用 `AGENTS.md` 中匹配的 i18n 验证行 | + +UI 改动在有帮助时附截图或短录屏。无法运行相关检查时,在 PR 中说明原因,并提供风险更低的手动验证路径。 ## 安全与合规 diff --git a/MiniApp/Skills/miniapp-dev/SKILL.md b/MiniApp/Skills/miniapp-dev/SKILL.md index 4c4a57d4c..08d81944a 100644 --- a/MiniApp/Skills/miniapp-dev/SKILL.md +++ b/MiniApp/Skills/miniapp-dev/SKILL.md @@ -18,7 +18,7 @@ description: Develops, maintains, and generates BitFun MiniApps (Zero-Dialect Ru ### 流程 1. **先问,再做**:用户的目标 / 受众 / 是否需要 node mode / 权限边界 / 是否需要 Tweaks 变体 / 是否多语言 / 是否有视觉参考——任何一项含糊就用 `AskUserQuestion` 问。**不要替用户决定**。 -2. **找设计上下文**:先读 `MiniApp/Demo/` 与 `src/crates/core/src/miniapp/builtin/assets/` 中**最贴近形态**的内置应用,复刻它的视觉语言(间距 / 圆角 / 卡片密度 / motif)。**从零 mock 是最后选择**。 +2. **找设计上下文**:先读 `MiniApp/Demo/` 与 `src/crates/product-domains/src/miniapp/builtin/assets/` 中**最贴近形态**的内置应用,复刻它的视觉语言(间距 / 圆角 / 卡片密度 / motif)。**从零 mock 是最后选择**。 3. **声明设计系统**:`style.css` 顶部用注释钉住 palette / typography / radius / motif(参见 playbook §1.3 模板),后续全应用复用。 4. **占位先行 → 早预览**:第一版用占位文本 / 占位图框 / fixture 数据,先在 Toolbox 里跑给用户看,再迭代。 5. **验证**:light/dark × zh/en 共 4 套截图都过;过 playbook §8 的 QA Checklist。 @@ -358,16 +358,16 @@ MiniApp 框架在 V2 之后内置 i18n 支持,开发者**必须**为多语言 ## 内置小应用(builtin/assets/*)维护规范 -内置小应用通过 `src/crates/core/src/miniapp/builtin/mod.rs` 中的 `BUILTIN_APPS` 数组以 `include_str!` 方式打包进 Rust 二进制;首次启动 / 升级时由 `seed_builtin_miniapps()` 把资源写入用户的 `miniapps_dir//`,并在该目录下写入 `.builtin-version` 标记文件。 +内置小应用通过 `src/crates/product-domains/src/miniapp/builtin.rs` 中的 `BUILTIN_APPS` 数组以 `include_str!` 方式打包进 Rust 二进制;首次启动 / 升级时由 `seed_builtin_miniapps()` 把资源写入用户的 `miniapps_dir//`,并在该目录下写入 `.builtin-manifest.json` 主标记文件,同时兼容写入 `.builtin-version` legacy 标记。 -**只有当 bundled `version` > on-disk 标记时才会重新 seed**,否则启动时会跳过、用户看到的还是旧版本。 +**只有当 bundled `version` / asset hash 与 on-disk `.builtin-manifest.json` 不一致时才会重新 seed**,否则启动时会跳过、用户看到的还是旧版本。 ### 修改流程(强制) -凡是修改了 `src/crates/core/src/miniapp/builtin/assets//` 下任何文件(`index.html` / `style.css` / `ui.js` / `worker.js` / `meta.json`),**都必须**同步在 `mod.rs` 的 `BUILTIN_APPS` 中把对应条目的 `version: N` → `N + 1`。 +凡是修改了 `src/crates/product-domains/src/miniapp/builtin/assets//` 下任何文件(`index.html` / `style.css` / `ui.js` / `worker.js` / `meta.json`),**都必须**同步在 `builtin.rs` 的 `BUILTIN_APPS` 中把对应条目的 `version: N` → `N + 1`。 ```rust -// src/crates/core/src/miniapp/builtin/mod.rs +// src/crates/product-domains/src/miniapp/builtin.rs BuiltinApp { id: "builtin-daily-divination", version: 14, // ← 改完资源就把这里 +1 @@ -376,20 +376,20 @@ BuiltinApp { ``` 未 bump 的后果: -- 已经体验过该小应用的用户(本地有 `.builtin-version` 标记)**不会**收到新版本,无法验证设计 / 修复 -- QA / Release 看到的还是旧文件,会误判"代码已合但效果没出来" +- asset hash 变化仍会触发 reseed,但 bundle version 和 `.builtin-version` legacy 标记无法体现升级批次 +- QA / Release 难以用版本号关联资源变更,容易误判资源是否已随包更新 ### 自检清单 - [ ] 改完 `assets//*` 任何文件 -- [ ] `mod.rs` 中对应 `BuiltinApp.version` 已 +1 -- [ ] 本地清掉 `~/.bitfun/miniapps//.builtin-version` 或直接整目录删,再启动验证 reseed 生效 +- [ ] `builtin.rs` 中对应 `BuiltinApp.version` 已 +1 +- [ ] 本地清掉 `~/.bitfun/miniapps//.builtin-manifest.json` 或直接整目录删,再启动验证 reseed 生效 - [ ] meta.json 中的 `version` 字段(用户可见的元数据版本)按需同步(与 reseed 无关,但展示用) ### 提示 - `meta.json` 里的 `version`(默认 1)是给用户看的版本号,**不**驱动 reseed -- 真正驱动 reseed 的是 `mod.rs` 中的 `BuiltinApp.version` 字段(u32) +- 真正驱动 reseed 的是 `builtin.rs` 中的 `BuiltinApp.version` 字段(u32)和 bundled asset hash - 二者最好语义一致:资源有重大更新时同步 bump,便于排查 ## 开发约定 diff --git a/MiniApp/Skills/miniapp-dev/design-playbook.md b/MiniApp/Skills/miniapp-dev/design-playbook.md index 5ffb4dbf3..c75bf9534 100644 --- a/MiniApp/Skills/miniapp-dev/design-playbook.md +++ b/MiniApp/Skills/miniapp-dev/design-playbook.md @@ -26,7 +26,7 @@ 按优先级取上下文: 1. 用户提供的截图 / 品牌资料 / 现成代码 -2. `MiniApp/Demo/` 与 `src/crates/core/src/miniapp/builtin/assets/` 中**最贴近形态**的内置应用——直接 `ls` + `Read` 拿到它的 `style.css`、`index.html`,识别它的视觉语言(间距、圆角、卡片密度、配色) +2. `MiniApp/Demo/` 与 `src/crates/product-domains/src/miniapp/builtin/assets/` 中**最贴近形态**的内置应用——直接 `ls` + `Read` 拿到它的 `style.css`、`index.html`,识别它的视觉语言(间距、圆角、卡片密度、配色) 3. `--bitfun-*` 主题变量(见 SKILL.md 的"主题集成"章节)——所有颜色都优先 `var(--bitfun-xxx, fallback)` **从零生成是最后选择**——它直接导致千篇一律的"AI 味"产出。 @@ -247,9 +247,9 @@ 完整体现以上原则的内置/示例小应用: -- `src/crates/core/src/miniapp/builtin/assets/regex-playground/` — 工具型,单 motif("/"包裹的 pattern row),克制配色 -- `src/crates/core/src/miniapp/builtin/assets/coding-selfie/` — 数据可视化,使用 worker,i18n 完整 -- `src/crates/core/src/miniapp/builtin/assets/gomoku/` — 交互型,主题切换 + i18n + 持久化范例 +- `src/crates/product-domains/src/miniapp/builtin/assets/regex-playground/` — 工具型,单 motif("/"包裹的 pattern row),克制配色 +- `src/crates/product-domains/src/miniapp/builtin/assets/coding-selfie/` — 数据可视化,使用 worker,i18n 完整 +- `src/crates/product-domains/src/miniapp/builtin/assets/gomoku/` — 交互型,主题切换 + i18n + 持久化范例 - `MiniApp/Demo/git-graph/` — 复杂应用拆模块的范例(`ui/components`, `ui/panels`, `ui/services`) - `MiniApp/Demo/icon-design-system/` — 设计系统型应用范例 diff --git a/docs/architecture/agent-runtime-services-design.md b/docs/architecture/agent-runtime-services-design.md index 25610365e..086392eea 100644 --- a/docs/architecture/agent-runtime-services-design.md +++ b/docs/architecture/agent-runtime-services-design.md @@ -1,11 +1,7 @@ # Agent Runtime SDK 与 Runtime Services 设计 本文是 [`core-decomposition.md`](core-decomposition.md) 的开发设计文档,描述目标模块、 -接口、crate 内部结构和迁移保护。本文不作为 PR 进度记录;已完成项、待执行项、PR 范围和 -issue 状态由 [`core-decomposition-plan.md`](../plans/core-decomposition-plan.md)、 -[`core-decomposition-completed.md`](../plans/core-decomposition-completed.md) 和跟踪 issue 维护。 - -除非发现目标分层、接口归属、行为边界或关键风险判断需要修正,后续 PR 不应修改本文。 +接口、crate 内部结构和行为保护。本文只记录设计约束,不记录实现过程或验证记录。 ## 1. 设计目标与边界 @@ -94,7 +90,7 @@ bitfun-runtime-services - `bitfun-runtime-services` 的扩展必须保持 typed builder、本地 service、remote service 和 fake provider 三类注入路径可测试。 - `bitfun-agent-runtime` 的扩展必须保持旧路径 facade、focused tests 和 boundary check,且不得吸收 concrete service、product surface 或平台实现。 - `bitfun-harness` 的扩展必须保持 descriptor / registry、旧路径兼容、focused tests 和 boundary check,且不得把 provider 注册误写成 concrete workflow execution。 -- 若目标 crate 只能承接单个 helper 或只能通过 `bitfun-core` 才能测试,继续留在迁移期 facade,不提前拆 crate。 +- 若目标 crate 只能承接单个 helper 或只能通过 `bitfun-core` 才能测试,应继续留在初始兼容 facade,不提前拆 crate。 ## 2. 稳定接口与运行时服务 @@ -252,14 +248,14 @@ Remote ports 的边界: - runtime events。 - post-turn processor。 -迁移期约束: +旧路径兼容约束: - `bitfun-agent-runtime` 只能依赖稳定契约、Tool Runtime、Runtime Services 接口和注入的 provider。 - concrete scheduler 生命周期、session metadata store、token subscriber、event delivery、product `Tool` handler、concrete prompt assembly、workspace / remote / config IO、custom subagent file IO 和平台 adapter 在行为等价未证明前不得下沉到 runtime kernel。 -- prompt、event、thread goal、scheduler 或 subagent 的纯事实可以先迁移,但每次迁移必须删除旧 owner - 实现主体,保留旧路径兼容,并补 focused contract test 与 boundary check。 +- prompt、event、thread goal、scheduler 或 subagent 的纯事实如果进入 Agent Runtime SDK,必须同时删除旧 owner + 实现主体,保留旧路径兼容,并具备 focused contract test 与 boundary check。 建议内部模块: @@ -435,12 +431,12 @@ pub struct ToolExecutionContext { - workspace service、path policy、runtime artifact reference、remote path containment 和 tool context facts 的 稳定 contract。 -迁移期约束: +旧路径兼容约束: - core 可以保留旧路径 facade、concrete tool adapter、state update、registry lookup、confirmation、actual - execution 和 filesystem persistence,直到对应 owner 迁移有等价测试保护。 + execution 和 filesystem persistence;目标状态要求只有在等价测试保护下才能移动这些行为。 - workspace file/shell contract 保留既有错误与取消语义;不得把错误分类、取消语义或产品 tool exposure - 变更混入 owner 迁移。 + 变更混入 owner 边界移动。 设计约束: @@ -526,15 +522,15 @@ pub struct HarnessExecutionContext { - harness 不直接访问 concrete filesystem / Git / terminal。 - 产品命令只映射到 harness capability,不把命令展示逻辑下沉。 - 新 harness 通过 provider 注册,不改 Agent Runtime SDK 内核。 -- descriptor-only / legacy-facade provider 只能表达 route plan;不得被描述为 concrete workflow execution - 已迁移。执行迁移必须单独证明行为等价。 +- descriptor-only / legacy-facade provider 只能表达 route plan;不得被描述为已经拥有 concrete workflow execution。 + 执行语义移动必须单独证明行为等价。 ## 4. 产品组装与扩展 ### 4.1 Product Assembly -Product Assembly 是 composition root。它可以位于 `bitfun-core` 迁移期 facade 内,也可以在 -后续拆成独立 Product Assembly crate。 +Product Assembly 是 composition root。初始状态可由 `bitfun-core` 兼容 facade 承载;目标状态可拆成独立 +Product Assembly crate。 职责: @@ -852,11 +848,10 @@ pub trait BeforeToolExecution: Send + Sync { - fork context 继续保留禁止字段和递归 subagent 保护。 - provider registry 构建后应尽量 immutable,避免 runtime 期间 materialization 漂移。 -### 5.2 计划边界 +### 5.2 设计边界 -本文只描述目标接口、crate 内部结构和行为保护要求,不定义 PR 顺序。Runtime Services、 -Tool Runtime、Agent Runtime SDK、Harness Layer 和 Product Assembly 的阶段边界、PR 范围和执行安排 -由 [`core-decomposition-plan.md`](../plans/core-decomposition-plan.md) 维护。 +本文只描述目标接口、crate 内部结构和行为保护要求。若验证发现目标接口、crate 归属、行为边界或风险判断不成立, +应先修正设计判断,再调整实现边界。 ### 5.3 测试策略 @@ -905,6 +900,6 @@ Product 测试: - `bitfun-runtime-services` 提供 typed service injection,并由 boundary check 保护。 - `tool-runtime` 承担 provider registry 和 execution pipeline,具体 tool 通过 provider 注入。 - `bitfun-harness` 支持工作流 provider 扩展。 -- `bitfun-core` 只作为迁移期 facade / product-full assembly。 +- `bitfun-core` 只作为兼容 facade / product-full assembly。 - 所有产品形态通过 Product Assembly 显式启用能力。 - 所有高风险行为有 snapshot、focused regression 或 product check 保护。 diff --git a/docs/architecture/core-decomposition.md b/docs/architecture/core-decomposition.md index b86edbabb..f8d699b5c 100644 --- a/docs/architecture/core-decomposition.md +++ b/docs/architecture/core-decomposition.md @@ -1,18 +1,17 @@ # BitFun Core 拆解架构 -本文描述 BitFun 当前 runtime 架构快照、目标分层形态和模块边界。它只回答“系统应该如何分层、 -每层负责什么、crate 与能力如何归属”;具体执行顺序、PR 范围和验证命令见 -[`core-decomposition-plan.md`](../plans/core-decomposition-plan.md)。详细接口、crate 内部模块和 -测试设计见 [`agent-runtime-services-design.md`](agent-runtime-services-design.md)。 +本文概括 BitFun core runtime 拆解的两个稳定设计维度:**初始状态**和**目标状态**。 +初始状态描述设计建立时的事实架构、耦合关系和主要问题;目标状态描述期望分层、稳定接口、 +实现归属、组装边界、依赖方向和风险约束。 -本文不作为 PR 进度记录。除目标分层、接口归属、行为边界或关键风险判断发生变化外,后续 PR -不应修改本文;完成状态由 plan、completed 归档和跟踪 issue 维护。 +本文聚焦设计结论。详细接口、crate 内部模块和测试设计见 +[`agent-runtime-services-design.md`](agent-runtime-services-design.md)。 ## 1. 背景与目标 -BitFun 当前已经从 `bitfun-core` 中抽出了若干 owner crate,但 `bitfun-core` 仍承担兼容 facade、 +设计建立时,BitFun 已经从 `bitfun-core` 中抽出了若干 owner crate,但 `bitfun-core` 仍承担兼容 facade、 完整产品 runtime 组装、agent loop、service 接线、tool materialization 和部分 product domain -adapter。这个形态在功能上可运行,但会让后续 runtime 迁移持续面临三个问题: +adapter。这个形态在功能上可运行,但会让 runtime 拆解持续面临三个问题: - 产品逻辑、平台接入和具体 service 实现边界不够稳定。 - Desktop、CLI、Server、Remote、ACP、Web 等产品形态容易被完整 `bitfun-core` 牵引。 @@ -22,7 +21,7 @@ adapter。这个形态在功能上可运行,但会让后续 runtime 迁移持 Agent Runtime SDK。稳定契约定义上层可依赖的接口,Product Assembly 负责注册具体实现, Runtime Services、Tool Runtime 和 Harness Layer 分别隔离 service、tool、工作流和产品形态差异。 -迁移期间不得改变产品行为、默认能力集合、权限语义、工具曝光、事件语义或 release 构建形态。 +目标状态必须保持产品行为、默认能力集合、权限语义、工具曝光、事件语义和 release 构建形态等价。 ## 2. 架构原则 @@ -31,15 +30,15 @@ Runtime Services、Tool Runtime 和 Harness Layer 分别隔离 service、tool、 具体实现属于 Product Assembly 或具体实现层。 - Product Surface 可以有差异,capability contract 必须收敛。不同产品入口可以选择不同能力集合, 但不能通过下沉 UI、命令或协议逻辑来换取复用。 -- `bitfun-core` 在迁移期保留 facade 和 `product-full` 组装点;新 owner crate 不得依赖回 +- `bitfun-core` 保留兼容 facade 和 `product-full` 组装边界;新 owner crate 不得依赖回 `bitfun-core`。 - Hook 是受控扩展点,Event 是事实通知。能改变行为的 hook 必须有顺序、timeout、错误策略和等价保护。 - feature group 是构建边界,CapabilitySet 是产品运行时能力边界;两者必须由 Product Assembly 显式映射。 -## 3. 现状逻辑视图 +## 3. 初始状态逻辑视图 -当前架构的核心事实是:多个 crate 已经承接了稳定类型、事件、stream、tool contract、部分 service +初始状态的核心事实是:多个 crate 已经承接了稳定类型、事件、stream、tool contract、部分 service helper 和 product domain 纯逻辑,但完整运行时仍以 `bitfun-core` 为中心。 ```mermaid @@ -85,13 +84,13 @@ flowchart TB Ai --> External ``` -当前主要模块范围: +初始状态主要模块范围: -| 模块 | 当前定位 | 架构影响 | +| 模块 | 初始定位 | 架构影响 | |---|---|---| -| `bitfun-core` | 兼容 facade、agent runtime、tool runtime 组装、service 接线和完整产品能力集合 | 仍是事实上的 runtime owner,迁移必须先保护行为等价 | +| `bitfun-core` | 兼容 facade、agent runtime、tool runtime 组装、service 接线和完整产品能力集合 | 仍是事实上的 runtime owner,拆解必须先保护行为等价 | | `bitfun-runtime-ports` | 面向 runtime/service 边界的 DTO 和 trait | 只定义 contract,不拥有 runtime 实现 | -| `bitfun-agent-tools` | provider-neutral tool DTO、manifest、path/result policy、catalog contract 和 deterministic execution admission gate | 已适合承接纯 tool runtime 策略,但不应拥有具体 IO tool | +| `bitfun-agent-tools` | provider-neutral tool DTO、manifest、path/result policy、catalog contract 和 deterministic execution admission gate | 适合承接纯 tool runtime 策略,但不应拥有具体 IO tool | | `tool-runtime` | 既有工具执行相关 crate | 目标是继续收敛 provider registry、permission gate 和 execution pipeline | | `bitfun-services-core` | 基础 service helper、本地 filesystem facade、部分通用 service 逻辑 | 适合作为本地基础 service owner,但不能吸收产品 runtime 语义 | | `bitfun-services-integrations` | MCP、Git、remote-connect、remote-SSH 等 integration helper | 适合拥有外部协议和重依赖 adapter,不应反向感知产品 surface | @@ -99,12 +98,12 @@ flowchart TB | `bitfun-acp` | ACP protocol 和 client integration | 应保持 external capability owner,不下沉到 Agent Runtime SDK | | `transport` / `api-layer` | surface 到 runtime 的 API/transport adapter | 应保持传输层,不拥有 runtime owner | -## 4. 当前主要问题 +## 4. 初始状态主要问题 ### 4.1 分层不清晰 同一能力经常同时包含 UI/command、runtime orchestration、tool execution、service IO 和 domain -decision。当前代码中这些部分仍大量通过 `bitfun-core` 串联,导致后续迁移时难以判断“移动的是接口、 +decision。初始状态代码中这些部分仍大量通过 `bitfun-core` 串联,导致拆解时难以判断“移动的是接口、 实现、组装逻辑还是产品行为”。 ### 4.2 接口与实现边界不稳定 @@ -115,16 +114,17 @@ core-owned context 或完整 product runtime snapshot。接口没有稳定到足 ### 4.3 产品形态被完整 core 牵引 -Desktop、CLI、Server、Remote、ACP 和 Web 的入口差异较大,但当前大多仍通过完整 `bitfun-core` +Desktop、CLI、Server、Remote、ACP 和 Web 的入口差异较大,但初始状态下大多仍通过完整 `bitfun-core` 获得能力。这会让轻量交付形态继承不必要的 tool、service、UI 或平台依赖。 ### 4.4 Tool contract 与 tool execution 混合 -provider-neutral manifest、path policy、result policy 已部分外移,但 concrete tool execution、 -`ToolUseContext` runtime handle、collapsed unlock persistence、runtime artifact persistence 和 product registry -materialization 仍在 core。 -工具迁移如果没有快照保护,容易改变 prompt-visible manifest、`GetToolSpec`、MCP/ACP catalog 或 -oversized result 行为。 +provider-neutral manifest、path policy、result policy、`ToolUseContext` runtime handle、collapsed unlock +lifecycle、runtime artifact persistence 和 product registry materialization 在初始状态下与 concrete tool +execution 交织在 core 及其兼容路径中。目标状态下,Tool Runtime 应拥有 provider-neutral manifest / +catalog / permission / result / artifact contract,core 或具体 integration 只保留实际 IO tool adapter、 +state update、旧路径 facade 和有等价保护的拆解边界。工具 owner 拆解如果没有快照保护,容易改变 +prompt-visible manifest、`GetToolSpec`、MCP/ACP catalog 或 oversized result 行为。 ### 4.5 Service、MCP、ACP 与 runtime kernel 容易交叉 @@ -140,13 +140,13 @@ product commands 都是扩展点,但目前没有统一表达它们分别属于 ### 4.7 feature graph 还不是产品能力矩阵 -`product-full` 当前是完整产品能力的安全网,不是最终按产品拆分的 feature matrix。直接减轻默认 feature +初始状态下,`product-full` 是完整产品能力的安全网,不是最终按产品拆分的 feature matrix。直接减轻默认 feature 或把 feature group 当成产品能力边界,都会引入构建形态和发布能力漂移。 ### 4.8 构建与测试牵引过大 重依赖和完整 runtime 聚合在 `bitfun-core` 周围,导致局部测试、owner crate 测试和轻量产品入口容易被 -不相关依赖拖入编译和链接路径。中间阶段不保证每个 PR 都变快,但目标架构必须让依赖收益可度量。 +不相关依赖拖入编译和链接路径。目标状态必须让依赖收益可度量,同时不能以牺牲功能等价换取构建收益。 ## 5. 对照分析 @@ -177,7 +177,7 @@ agents 分为 primary agents 和 subagents,可配置 prompt、model 与 tool a - Agent、Tool、MCP、Plugin/Hook、Skill 和 Product Surface 应该是互相连接的扩展面,而不是同一个模块内部的分支。 - 权限和工具可见性必须是 runtime 可观测的 contract,不能只存在于 UI 或 prompt 拼接中。 -- 多产品形态需要 Product Assembly 做 capability/provider 选择,而不是让 Agent Runtime SDK 判断当前是 +- 多产品形态需要 Product Assembly 做 capability/provider 选择,而不是让 Agent Runtime SDK 判断调用来自 Desktop、CLI、Remote 还是 ACP。 ## 6. 目标逻辑视图 @@ -230,15 +230,15 @@ flowchart TB ### 7.2 产品组装层(Product Assembly) 产品组装层(Product Assembly)是唯一的组装入口,负责选择产品能力、tool pack、harness pack、agent definition、 -command provider 和 service provider,并把具体实现注册到稳定接口。迁移期它可以留在 `bitfun-core` -facade 或产品入口中,目标形态可以收敛为独立 assembly crate 或清晰的 facade 模块。 +command provider 和 service provider,并把具体实现注册到稳定接口。初始状态可由 `bitfun-core` +facade 或产品入口承载组装职责;目标状态应收敛为独立 assembly crate 或清晰的 facade 模块。 ### 7.3 产品能力层(Product Capabilities) 产品能力层(Product Capabilities)描述 Code Agent、Deep Review、DeepResearch、MiniApp、Remote Control、MCP App、 Computer Use 等能力的组合边界。它负责定义一个产品能力需要哪些 agent、tool、harness、domain policy -和 service capability,不负责 UI,也不直接执行 IO。当前主要落在 `bitfun-product-domains` 和 -`bitfun-core` 的能力组装代码中,后续应收敛为 capability pack 和 domain policy。 +和 service capability,不负责 UI,也不直接执行 IO。初始状态下相关逻辑主要落在 `bitfun-product-domains` 和 +`bitfun-core` 的能力组装代码中;目标状态下应收敛为 capability pack 和 domain policy。 ### 7.4 工作流编排层(Harness Layer) @@ -246,7 +246,7 @@ Computer Use 等能力的组合边界。它负责定义一个产品能力需要 它可以调用 Agent Runtime SDK、Tool Runtime 和 Runtime Services,但不拥有 session manager 内部状态、 具体 filesystem/Git/terminal manager 或产品 UI。`bitfun-harness` 目标上承接 workflow descriptor、route plan 和 provider registry contract;descriptor-only / legacy-facade provider 只能表达 route,不代表 -concrete workflow execution 已迁移。 +目标状态已经具备 concrete workflow execution owner。 ### 7.5 Agent 运行时 SDK(Agent Runtime SDK) @@ -262,21 +262,22 @@ post-turn hook 在行为等价未证明前不得下沉。 工具运行时(Tool Runtime)负责工具 manifest、catalog、permission gate、execution pipeline、tool hook 和结果归一化。 它只消费 `ToolExecutionServices` 这类窄 service 视图,不直接创建 filesystem、Git、terminal、MCP 等具体实现。 -当前相关 crate 包括 `tool-runtime`、`bitfun-agent-tools`、`bitfun-tool-packs` 以及 `bitfun-core` +初始状态相关 crate 包括 `tool-runtime`、`bitfun-agent-tools`、`bitfun-tool-packs` 以及 `bitfun-core` 中的 tool materialization 代码。Tool Runtime 目标上承接 provider-neutral manifest、catalog、permission gate、execution admission、result presentation、artifact policy 和 `GetToolSpec` catalog / detail contract。 core 可以保留 concrete tool adapter、state update、registry lookup、input validation、confirmation、 -actual execution、hook 和旧路径 facade,直到对应迁移有等价测试保护。 +actual execution、hook 和旧路径 facade;目标状态要求只有在等价测试保护下才能移动这些行为。 ### 7.7 运行时服务层(Runtime Services) 运行时服务层(Runtime Services)是 runtime 可消费的 typed service bundle 和 capability availability 层。它提供 filesystem、workspace、session store、Git、terminal、network、MCP catalog、remote connection / projection 等端口,不执行产品命令, -不作为无类型 service locator,也不创建平台实现。当前相关 crate 包括 `bitfun-runtime-ports`、 +不作为无类型 service locator,也不创建平台实现。初始状态相关 crate 包括 `bitfun-runtime-ports`、 `bitfun-runtime-services`、`bitfun-services-core`、`bitfun-services-integrations` 和 `bitfun-core` 中的 service 接线代码。 workspace file/shell、remote connection / projection、session store、Git、terminal、network 和 MCP catalog -等接口应以 stable port 表达;core 可在迁移期保留旧路径 facade 和 concrete adapter。 +等接口应以 stable port 表达;初始状态可保留旧路径 facade 和 concrete adapter,目标状态应由 typed service bundle +消费这些端口。 ### 7.8 具体实现层(Concrete Integrations) @@ -288,12 +289,12 @@ connection、remote workspace projection 和 remote host capability。 Remote 不应作为 Agent Runtime SDK 的内部能力,也不应只按 Desktop/CLI 入口区分。它的稳定接口应拆为 remote connection、remote workspace、remote filesystem/terminal projection、remote capability facts 等 port; SSH、relay、本地隧道、远端 OS 差异和认证方式属于具体 Remote provider,由 Product Assembly 按产品形态注册。 -当前相关 crate 包括 `bitfun-services-*`、`bitfun-ai-adapters`、`terminal-core`、`bitfun-acp` 和 app adapters。 +初始状态相关 crate 包括 `bitfun-services-*`、`bitfun-ai-adapters`、`terminal-core`、`bitfun-acp` 和 app adapters。 ### 7.9 稳定契约层(Stable Contracts) 稳定契约层提供跨层共享的数据结构和接口语言,包括 DTO、event、permission facts、artifact refs、identity -和 port traits。它只描述事实和能力,不包含 IO、网络、进程、UI、runtime manager 或产品策略。当前相关 +和 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 应归入稳定契约;integration crate 可保留旧路径 re-export 以维持兼容。 @@ -335,13 +336,13 @@ flowchart TB 注册器与前文目标层级的对应关系如下: -| 注册器 / 组装点 | 所属目标层级 | 目标或迁移期模块 | 注册内容 | +| 注册器 / 组装点 | 所属目标层级 | 初始承载与目标承载 | 注册内容 | |---|---|---|---| -| `ProductAssembler` / `ProductAssemblyPlan` | 产品组装层(Product Assembly) | 迁移期在 `bitfun-core` facade 或产品入口;目标可收敛为 assembly owner | `DeliveryProfile`、`CapabilitySet`、feature group、provider 选择 | -| `RuntimeServicesBuilder` | 运行时服务层(Runtime Services) | `bitfun-runtime-services`;连接 `bitfun-runtime-ports`、`bitfun-services-*` 和迁移期 service wiring | filesystem、workspace、session store、Git、terminal、network、MCP catalog、remote connection / workspace / projection port | +| `ProductAssembler` / `ProductAssemblyPlan` | 产品组装层(Product Assembly) | 初始可在 `bitfun-core` facade 或产品入口;目标可收敛为 assembly owner | `DeliveryProfile`、`CapabilitySet`、feature group、provider 选择 | +| `RuntimeServicesBuilder` | 运行时服务层(Runtime Services) | 目标在 `bitfun-runtime-services`;连接 `bitfun-runtime-ports`、`bitfun-services-*` 和初始 service wiring | filesystem、workspace、session store、Git、terminal、network、MCP catalog、remote connection / workspace / projection port | | `ToolRuntimeBuilder` | 工具运行时(Tool Runtime) | `tool-runtime`、`bitfun-agent-tools`、`bitfun-tool-packs` | tool provider、tool pack、manifest、permission gate、tool hook | -| `HarnessRegistryBuilder` | 工作流编排层(Harness Layer) | `bitfun-harness`;迁移期由 `bitfun-core::agentic::harness` 注册 legacy-facade provider | SDD、Deep Review、DeepResearch、MiniApp 等 harness provider | -| `AgentDefinitionRegistry` | Agent 运行时 SDK(Agent Runtime SDK) | 目标 `bitfun-agent-runtime`;迁移期在 `bitfun-core` agent definition 代码中 | agent、subagent、prompt module、skill definition | +| `HarnessRegistryBuilder` | 工作流编排层(Harness Layer) | 目标在 `bitfun-harness`;初始可由 `bitfun-core::agentic::harness` 注册 legacy-facade provider | SDD、Deep Review、DeepResearch、MiniApp 等 harness provider | +| `AgentDefinitionRegistry` | Agent 运行时 SDK(Agent Runtime SDK) | 目标在 `bitfun-agent-runtime`;初始可由 `bitfun-core` agent definition 代码承载 | agent、subagent、prompt module、skill definition | | `ProductCommandRegistry` | 产品入口(Product Surfaces)与产品组装层(Product Assembly)的边界 | 产品入口或 assembly 模块 | 输入框命令、审核入口、MiniApp 入口到 capability / harness / runtime request 的映射 | | concrete provider set | 具体实现层(Concrete Integrations) | `bitfun-services-*`、`bitfun-ai-adapters`、`terminal-core`、`bitfun-acp`、app adapters | Tool、OS、Remote、Protocol 的具体 provider;Remote provider 内部继续区分 SSH、relay、本地隧道、远端 OS 支持 | @@ -366,22 +367,19 @@ flowchart TB | 平台实现泄漏到 Agent Runtime SDK、Tool Runtime 或 Harness | 依赖检查禁止 runtime owner 依赖 app crate、Tauri、CLI TUI、ACP protocol 和 concrete service crate | | core 拆分后仍隐式绑定 Tauri | Tauri 只允许在 Desktop provider / adapter;向下层传递 typed port、DTO、event fact 和 capability availability | | 不同产品形态能力矩阵漂移 | Product Assembly 维护 capability matrix;减少或替换能力时补产品入口验证和 unsupported 行为测试 | -| Tool、MCP、ACP 的 manifest、permission 或事件语义迁移后不等价 | 保留旧路径兼容 facade,增加 manifest snapshot、permission 决策和事件映射等价测试 | -| Harness provider 只做注册但被误认为已经迁移执行语义 | descriptor-only / legacy-facade provider 只能生成 route plan;执行迁移必须单独证明行为等价 | +| Tool、MCP、ACP 的 manifest、permission 或事件语义拆解后不等价 | 保留旧路径兼容 facade,增加 manifest snapshot、permission 决策和事件映射等价测试 | +| Harness provider 只做注册但被误认为已经拥有执行语义 | descriptor-only / legacy-facade provider 只能生成 route plan;执行语义移动必须单独证明行为等价 | | `bitfun-core` 只是改名为新的巨型 runtime crate | 新 owner crate 必须有单一职责和最小依赖;产品能力、harness、service 实现不得继续堆入 agent kernel | | 目标 crate 先行创建但没有真实 owner | 只有 owner 边界、旧路径兼容、focused tests、依赖收益和 boundary check 同时成立时才创建 crate;否则继续留在 facade | -## 10. 总结 - -- 当前架构影响:`bitfun-core` 从事实上的完整 runtime owner 收缩为兼容 facade 和迁移期组装点;Agent Runtime SDK、 - Tool Runtime、Runtime Services、Harness 和 Product Capabilities 分别成为可审查的 owner。 -- 接口与实现边界:稳定契约和各 runtime owner 定义接口,具体 Tool、OS、Remote、Protocol provider 留在具体实现层, - 由产品组装层(Product Assembly)通过 typed builder / registry 注册。 -- Tauri 脱离方向:Tauri 是 Desktop concrete provider / adapter 的实现细节,不是 core、runtime owner - 或 contract crate 的依赖来源;跨形态复用依赖 typed port 和 product assembly。 -- Remote 拆分方向:runtime 只依赖 remote connection、remote workspace、remote projection 和 capability facts 等 - port;SSH、relay、本地隧道、远端 OS 差异和认证方式属于具体 Remote provider。 -- 后续工作范围:抽出可独立构建的 runtime kernel;把 service、tool、harness 和 product capability 改为 typed - provider 注册;建立产品形态与 capability matrix 的对应关系。 -- 质量保护:用等价测试保护权限、工具曝光、事件、session、remote workspace 和构建形态不发生功能偏移。 -- 非目标:不改变默认产品能力、命令语义、权限策略或 release 构建形态。 +## 10. 目标状态判定 + +- `bitfun-core` 不再是事实上的完整 runtime owner,而是兼容 facade 和 `product-full` 组装边界。 +- Agent Runtime SDK、Tool Runtime、Runtime Services、Harness 和 Product Capabilities 分别拥有可审查的职责边界。 +- 稳定契约和各 runtime owner 定义接口;具体 Tool、OS、Remote、Protocol provider 留在具体实现层。 +- 产品组装层(Product Assembly)是唯一注册点,通过 typed builder / registry 连接接口和具体实现。 +- Tauri 只属于 Desktop concrete provider / adapter,不进入 core、runtime owner 或 contract crate。 +- runtime 只依赖 remote connection、remote workspace、remote projection 和 capability facts 等 port;SSH、relay、 + 本地隧道、远端 OS 差异和认证方式属于具体 Remote provider。 +- 产品形态差异通过 capability matrix 和 Product Assembly 表达,不通过下沉 UI、命令、协议或平台实现表达。 +- 权限、工具曝光、事件、session、remote workspace 和 release 构建形态必须保持功能等价。 diff --git a/docs/plans/core-decomposition-completed.md b/docs/plans/core-decomposition-completed.md index 419ed0320..607f48481 100644 --- a/docs/plans/core-decomposition-completed.md +++ b/docs/plans/core-decomposition-completed.md @@ -31,8 +31,8 @@ 明确未完成: - remote-SSH runtime、remote FS / terminal、workspace-root source、persistence / workspace service reads、`ImageContextData` concrete impl 仍未迁移。 -- `ToolUseContext` runtime handles、product registry materialization、collapsed unlock persistence、concrete tools 仍未迁移。 -- MiniApp filesystem IO / worker / host dispatch / builtin asset runtime、function-agent Git / AI concrete service 仍未迁移。 +- concrete tools 仍未迁移。 +- MiniApp filesystem IO / worker / host dispatch / builtin marker IO / seed 写盘、function-agent Git / AI concrete service 仍未迁移。 - agent definition loading / concrete scheduler lifecycle 仍未迁移。 ### 1.3 H1-H5 基线收口 @@ -47,7 +47,7 @@ - H5 不代表 per-product feature matrix、构建收益或 runtime owner 深迁移完成。 - `bitfun-core default = []` 仍是独立评估项,不能混入 runtime owner 迁移。 -- 具体 IO、scheduler 生命周期、workspace-root、persistence、MiniApp worker / host / builtin、function-agent Git / AI 仍需后续完整 owner PR。 +- 具体 IO、scheduler 生命周期、workspace-root、persistence、MiniApp worker / host / seed 写盘、function-agent Git / AI 仍需后续完整 owner PR。 ### 1.4 Runtime owner PR1-PR4:组装、remote、agent runtime 与 harness 边界 @@ -95,13 +95,28 @@ - `WorkspaceFileSystem`、`WorkspaceShell`、`WorkspaceServices`、workspace command / dir-entry contract 已归入 `bitfun-runtime-ports`;`bitfun-core::agentic::workspace` 只保留旧路径 re-export 和 local / remote concrete adapter。 为避免功能偏移,该 contract 暂时保留既有 `anyhow::Result` 和 `CancellationToken` 语义。 -- collapsed unlock 的 `GetToolSpec` observation adapter 已迁入 `product_runtime/unlock_state.rs`; - `ExecutionEngine` 不再直接解析 `GetToolSpec` tool result 或调用 generic collector。 +- `ToolRuntimeHandles` 已归入 `bitfun-runtime-ports`,承接 `ToolUseContext` 的 workspace services / + cancellation handle bundle;core 继续拥有 `ToolUseContext` 类型、runtime lookup、portable facts 投影和具体 tool 调用上下文。 +- product provider group plan 到 concrete tool 的 materialization 已迁入 `product_runtime/materialization.rs`; + provider order、tool name 和 registry exposure 由 focused test 保护。 +- collapsed unlock 的 message-derived lifecycle state 与 `GetToolSpec` observation adapter 已迁入 + `product_runtime/unlock_state.rs`;`ExecutionEngine` 不再直接解析 `GetToolSpec` tool result 或调用 generic collector。 明确未完成: -- `ToolUseContext` concrete service handles、product registry materialization、collapsed unlock persistence、 - 具体 IO tools 仍未迁移。 +- 具体 IO tools 仍未迁移;继续迁移必须先保护权限、filesystem/shell 行为、checkpoint hook 和产品 tool exposure。 + +### 1.6 Product-Domain builtin MiniApp bundle:asset owner 迁移 + +- 内置 MiniApp 的 bundle identity、版本和 embedded source assets 已归入 + `bitfun-product-domains::miniapp::builtin::BUILTIN_APPS`。 +- `bitfun-core::miniapp::builtin` 只保留旧路径 re-export、seed 写盘、marker IO、用户 `storage.json` 保留和 recompile。 +- 产品 seed 行为由既有 reseed/customization 回归和 product-domain bundle owner contract 保护。 + +明确未完成: + +- MiniApp worker process、host dispatch、permission execution、PathManager integration、builtin marker IO / + seed 写盘仍在 core;后续迁移必须单独证明权限与进程行为等价。 ## 2. 已建立保护 @@ -114,5 +129,7 @@ ## 3. 当前剩余结论 - 低风险准备项已经完成,不再新增零散小 PR。 -- 后续只按三段大 PR 推进:PR-B 的 Product-Domain + Tool Runtime owner closure,以及 PR-C 的 Harness / Capability / Build-Benefit closure;如要继续移动 Agent Runtime concrete scheduler、event delivery、permission handler 或 post-turn hook,必须先补等价保护并明确纳入后续大 PR,不能拆成零散 helper PR。 +- PR-B 已收敛 Product-Domain builtin asset owner 与 Tool Runtime owner closure。后续只剩 PR-C 的 Harness / + Capability / Build-Benefit closure,以及经等价保护后再评估的 MiniApp worker/host、function-agent Git/AI、 + 具体 IO tools、Agent Runtime concrete scheduler/event/permission/post-turn hook;不能拆成零散 helper PR。 - 缺陷修复、行为变更、冗余清理、三方库升级和构建脚本调整必须独立评估,不能伪装成 core decomposition 剩余里程碑。 diff --git a/docs/plans/core-decomposition-plan.md b/docs/plans/core-decomposition-plan.md index c81f18caa..d3febdb63 100644 --- a/docs/plans/core-decomposition-plan.md +++ b/docs/plans/core-decomposition-plan.md @@ -73,8 +73,8 @@ workspace build 证明没有行为或 feature 影响。 | PR | 主题 | 完整范围 | 不允许混入 | 合入门禁 | |---|---|---|---|---| | PR-A | Agent Runtime SDK Owner Closure | 承接 prompt cache policy / identity / DTO / in-memory store、shared mode profile / context policy、mode / subagent source presentation facts,并保持 core agent registry 与 session manager 旧路径兼容 | concrete scheduler 生命周期、event emitter、post-turn hook、permission `Tool` handler、custom subagent file IO、产品命令或默认 feature 变更 | `bitfun-agent-runtime` 独立测试、core agents / prompt-cache focused tests、boundary check、repo hygiene、`cargo check -p bitfun-core --features product-full` | -| PR-B | Product-Domain + Tool Runtime Owner Closure | 在 MiniApp worker / host / builtin asset、function-agent Git / AI、ToolUseContext concrete handles、product registry materialization、collapsed unlock persistence 与具体 IO tools 中迁移完整 owner 主题 | Agent Runtime scheduler / event 行为、Harness execution、feature matrix、UI 或产品语义变更 | MiniApp/function-agent/tool pipeline focused regressions,runtime/service port 边界清晰,产品 surface 不变 | -| PR-C | Harness / Capability / Build-Benefit Closure | 推进 Harness execution / Product Capability pack / service-tool orchestration,并评估 feature matrix、dependency profile、no-default 编译面、构建收益和可选 crate 目录分组 | runtime owner 主体迁移、默认 feature 副作用、未验证的构建脚本调整 | Harness workflow 等价、capability pack 注册可测、cargo metadata / cargo tree 证据,产品入口完整能力不变 | +| PR-B | Product-Domain + Tool Runtime Owner Closure | 完成 MiniApp builtin bundle asset owner、ToolUseContext runtime handle bundle、product registry materialization、collapsed unlock lifecycle state,并保持旧路径与产品 tool exposure 兼容 | Agent Runtime scheduler / event 行为、Harness execution、feature matrix、UI 或产品语义变更、MiniApp worker / host dispatch、function-agent Git / AI concrete service、具体 IO tool 行为 | MiniApp builtin seed regressions、function-agent facade regressions、tool pipeline focused regressions,runtime/service port 边界清晰,产品 surface 不变 | +| PR-C | Harness / Capability / Build-Benefit Closure | 推进 Harness execution / Product Capability pack / service-tool orchestration;评估 MiniApp worker/host、function-agent Git/AI、具体 IO tools 是否具备进一步外移保护;同时评估 feature matrix、dependency profile、no-default 编译面、构建收益和可选 crate 目录分组 | 默认 feature 副作用、未验证的构建脚本调整、未补等价保护的进程/权限/AI provider 迁移 | Harness workflow 等价、capability pack 注册可测、cargo metadata / cargo tree 证据,产品入口完整能力不变 | ### 4.1 PR-A 实施状态 @@ -88,6 +88,17 @@ PR-A 只迁移无 IO、无副作用、可由 contract test 证明等价的 Agent 不继续纳入 PR-A 的内容:concrete scheduler 生命周期、event delivery / post-turn hook、permission coordination 的 `Tool` handler 和 custom subagent file IO。这些路径直接连接事件发送、调度执行或文件/配置 IO,若迁移必须在 PR-B/PR-C 前单独补可观测行为等价保护,不能作为“纯 owner 事实迁移”处理。 +### 4.2 PR-B 实施状态 + +PR-B 收敛 Product-Domain 与 Tool Runtime 的真实 owner 逻辑,但不移动会改变进程、权限、Git/AI provider 或具体 IO 行为的实现。当前分支覆盖: + +1. `bitfun-product-domains::miniapp::builtin::BUILTIN_APPS` 承接内置 MiniApp bundle identity、版本和 embedded asset;`bitfun-core::miniapp::builtin` 保留旧路径 re-export、seed 写盘、marker IO、用户 storage 保留和 recompile。 +2. `bitfun-runtime-ports::ToolRuntimeHandles` 承接 tool execution context 的 workspace services 与 cancellation handle bundle;`ToolUseContext` 保留 core owner 类型、path/runtime lookup、portable facts 投影和具体 tool 调用上下文。 +3. `product_runtime/materialization.rs` 承接 product provider group plan 到 concrete tool 的 materialization,保持 provider order、tool name 和 registry exposure 不变。 +4. `product_runtime/unlock_state.rs` 承接 collapsed unlock 的 message-derived lifecycle state;`ExecutionEngine` 不再直接解析 `GetToolSpec` result。 + +不继续纳入 PR-B 的内容:MiniApp worker process / host dispatch、builtin marker IO / seed 写盘、function-agent Git / AI concrete service、具体 IO tools。这些路径连接进程执行、权限检查、文件系统、shell、Git、AI provider 或用户可见工具行为,必须在 PR-C 中先补等价保护后再评估是否外移。 + ## 5. 每类 PR 的保护重点 ### 5.1 Service / Agent Remote Runtime Owner @@ -109,7 +120,8 @@ PR-A 只迁移无 IO、无副作用、可由 contract test 证明等价的 Agent ### 5.3 Product-Domain Runtime Owner -- MiniApp 优先拆 storage/process/asset/Git/AI 的最小 port,避免把 PathManager、worker process、host dispatch、builtin marker IO 下沉到 domain crate。 +- MiniApp 已将 builtin bundle identity、版本和 embedded asset 放入 `bitfun-product-domains`;core 继续负责 seed 写盘、marker IO、用户 storage 保留、recompile、PathManager、worker process 和 host dispatch。 +- 后续若继续迁移 MiniApp worker / host,必须先拆清 process runtime、permission policy、host primitive dispatch、draft worker 与 active worker 的等价边界,不能把 PathManager 或 worker process 下沉到 domain crate。 - function-agent 保留 Git/AI provider acquisition、error mapping、no-HEAD diff fallback、非 Git workspace fallback、`analyzed_at` 时序。 - 验证 MiniApp import/sync/recompile/rollback/deps state、builtin seed marker、customized update metadata、function-agent prompt/response policy。 @@ -121,14 +133,16 @@ PR-A 只迁移无 IO、无副作用、可由 contract test 证明等价的 Agent - 已完成 manifest/catalog/snapshot owner closure;`manifest_resolver.rs` 只保留旧路径兼容 facade,product runtime 的 `catalog.rs` / `snapshot.rs` 管理 resolved manifest DTO、visible tools、readonly catalog、GetToolSpec catalog path 和 snapshot wrapper。 -- 本阶段继续完成两项 owner 收敛:`WorkspaceFileSystem`、`WorkspaceShell`、`WorkspaceServices` 等 workspace service +- 已完成 `WorkspaceFileSystem`、`WorkspaceShell`、`WorkspaceServices` 等 workspace service contract 归入 `bitfun-runtime-ports`,core `workspace.rs` 只保留旧路径 re-export 和 local/remote concrete adapter; - collapsed unlock 的 GetToolSpec observation adapter 归入 `product_runtime/unlock_state.rs`,`ExecutionEngine` 不再拥有 - GetToolSpec 结果解析细节。 + `ToolRuntimeHandles` 归入 `bitfun-runtime-ports`,承接 ToolUseContext 的 workspace services / cancellation handle bundle。 +- collapsed unlock 的 message-derived state 与 GetToolSpec observation adapter 已归入 `product_runtime/unlock_state.rs`, + `ExecutionEngine` 不再拥有 GetToolSpec 结果解析细节。 +- product provider group plan 到 concrete tool 的 materialization 已归入 `product_runtime/materialization.rs`, + `product_runtime.rs` 只保留 runtime 组装入口和旧路径兼容。 - workspace service contract 暂时保留既有 `anyhow::Result` 和 `CancellationToken` 语义,避免在 owner 迁移 PR 中同时改变 错误分类、取消语义或调用方边界;后续若要收敛为 portable `PortResult`,必须单独补错误映射等价测试。 -- 后续不直接搬全部 concrete tools。只在 collapsed unlock persistence、product registry materialization、 - `ToolUseContext` concrete service handles 或具体工具 IO 中选择能减少旧路径的完整 owner。 +- 后续不直接搬全部 concrete tools;具体 IO tools 只有在权限、filesystem/shell 行为和 checkpoint hook 均有等价保护时才允许进入 PR-C。 - 保留 tool name、schema、prompt stub、readonly/enabled/filtering、unlock state 生命周期。 - 验证 builtin tool list、provider order、expanded/collapsed exposure、dynamic provider metadata、Deep Review 修改类工具 checkpoint hook。 diff --git a/scripts/check-core-boundaries.mjs b/scripts/check-core-boundaries.mjs index 7475b75c9..23efe7dd2 100644 --- a/scripts/check-core-boundaries.mjs +++ b/scripts/check-core-boundaries.mjs @@ -4493,12 +4493,8 @@ const requiredContentRules = [ message: 'missing tool-pack provider group plan delegation', }, { - regex: /\bmaterialize_tool\b/, - message: 'missing core concrete tool materialization boundary', - }, - { - regex: /\bGetToolSpecTool::new\(\)/, - message: 'missing GetToolSpec registration anchor', + regex: /\bProductToolMaterializer\b/, + message: 'missing product tool materializer delegation', }, { regex: /\bToolRuntimeAssembly\b/, @@ -4512,6 +4508,29 @@ const requiredContentRules = [ regex: /\bproduct_tool_runtime_owner_preserves_registry_contract\b/, message: 'missing product runtime owner registry equivalence regression', }, + { + regex: /\bproduct_tool_materializer_preserves_provider_plan_order\b/, + message: 'missing product tool materializer order regression', + }, + ], + }, + { + path: 'src/crates/core/src/agentic/tools/product_runtime/materialization.rs', + reason: + 'product runtime materialization owns concrete tool construction from provider plans until concrete tools migrate', + patterns: [ + { + regex: /\bProductToolMaterializer\b/, + message: 'missing product tool materializer owner', + }, + { + regex: /\bmaterialize_tool\b/, + message: 'missing concrete tool materialization boundary', + }, + { + regex: /\bGetToolSpecTool::new\(\)/, + message: 'missing GetToolSpec registration anchor', + }, ], }, { @@ -5568,15 +5587,11 @@ const requiredContentRules = [ { path: 'src/crates/core/src/miniapp/builtin/mod.rs', reason: - 'core must continue owning built-in MiniApp asset includes, seeding IO, marker writes, and recompilation until builtin asset runtime migration is reviewed', + 'core must continue owning built-in MiniApp seeding IO, marker writes, and recompilation while product-domains owns bundle assets', patterns: [ - { - regex: /id: "builtin-pr-review"/, - message: 'missing built-in PR Review MiniApp anchor', - }, { regex: /\bBUILTIN_APPS\b/, - message: 'missing built-in MiniApp asset include owner', + message: 'missing product-domain built-in MiniApp bundle re-export/use', }, { regex: /\bbuiltin_content_hash\b/, @@ -6299,8 +6314,16 @@ const requiredContentRules = [ { path: 'src/crates/product-domains/src/miniapp/builtin.rs', reason: - 'product-domains owns pure built-in MiniApp bundle, marker, hash, and seed-decision contracts while core keeps asset seeding IO and recompilation', + 'product-domains owns built-in MiniApp bundle assets, marker, hash, and seed-decision contracts while core keeps asset seeding IO and recompilation', patterns: [ + { + regex: /id: "builtin-pr-review"/, + message: 'missing built-in PR Review MiniApp bundle anchor', + }, + { + regex: /\bpub const BUILTIN_APPS\b/, + message: 'missing built-in MiniApp bundle asset owner', + }, { regex: /\bpub struct BuiltinMiniAppBundle\b/, message: 'missing built-in MiniApp bundle contract', @@ -8148,8 +8171,7 @@ function runManifestParserSelfTest() { 'builtin_static_tool_providers', 'StaticToolProviderGroup', 'product_tool_provider_group_plan', - 'materialize_tool', - 'GetToolSpecTool', + 'ProductToolMaterializer', 'ToolRuntimeAssembly', 'create_registry_from_static_providers', 'wrap_tool_for_snapshot_tracking', @@ -8169,6 +8191,15 @@ function runManifestParserSelfTest() { 'product_catalog_provider_default_get_tool_spec_catalog_matches_registry', 'product_tool_runtime_owner_preserves_registry_contract', 'GetToolSpec requires agent type context', + 'product_tool_materializer_preserves_provider_plan_order', + ], + }, + { + path: 'src/crates/core/src/agentic/tools/product_runtime/materialization.rs', + contracts: [ + 'ProductToolMaterializer', + 'materialize_tool', + 'GetToolSpecTool', ], }, { @@ -8492,7 +8523,6 @@ function runManifestParserSelfTest() { { path: 'src/crates/core/src/miniapp/builtin/mod.rs', contracts: [ - 'builtin-pr-review', 'BUILTIN_APPS', 'builtin_content_hash', 'should_seed_builtin_app', @@ -8514,6 +8544,8 @@ function runManifestParserSelfTest() { { path: 'src/crates/product-domains/src/miniapp/builtin.rs', contracts: [ + 'builtin-pr-review', + 'BUILTIN_APPS', 'BuiltinMiniAppBundle', 'BuiltinInstallMarker', 'BUILTIN_INSTALL_MARKER', diff --git a/src/apps/desktop/src/api/tool_api.rs b/src/apps/desktop/src/api/tool_api.rs index d5bc2673c..171d06bf0 100644 --- a/src/apps/desktop/src/api/tool_api.rs +++ b/src/apps/desktop/src/api/tool_api.rs @@ -172,19 +172,7 @@ async fn build_tool_context(workspace_path: Option<&str>) -> ToolUseContext { None => None, }; - ToolUseContext { - tool_call_id: None, - agent_type: None, - session_id: None, - dialog_turn_id: None, - workspace, - unlocked_collapsed_tools: Vec::new(), - custom_data: HashMap::new(), - computer_use_host: None, - cancellation_token: None, - runtime_tool_restrictions: Default::default(), - workspace_services, - } + ToolUseContext::for_tool_listing(workspace, workspace_services) } fn to_dynamic_mcp_tool_info( diff --git a/src/crates/agent-stream/AGENTS.md b/src/crates/agent-stream/AGENTS.md new file mode 100644 index 000000000..8677bcb55 --- /dev/null +++ b/src/crates/agent-stream/AGENTS.md @@ -0,0 +1,27 @@ +# agent-stream Agent Guide + +Scope: this guide applies to `src/crates/agent-stream`. + +`bitfun-agent-stream` owns provider stream normalization and replayable stream +processing contracts. It should preserve provider wire behavior while exposing a +portable stream surface to higher layers. + +## Guardrails + +- Do not depend on `bitfun-core`, app crates, Tauri, concrete services, + transport adapters, terminal, tool-runtime, or product-domain implementations. +- Keep provider-specific parsing isolated to stream normalization. Do not add + session lifecycle, tool execution, prompt policy, or product orchestration + behavior here. +- Stream fixture changes must preserve ordering, tool-call reconstruction, + reasoning/thinking fields, usage accounting, and malformed-chunk handling. +- New provider quirks need fixture coverage rather than broad catch-all parsing. + +## Verification + +```bash +cargo test -p bitfun-agent-stream +node scripts/check-core-boundaries.mjs +``` + +For documentation-only changes, run `git diff --check`. diff --git a/src/crates/agent-tools/AGENTS.md b/src/crates/agent-tools/AGENTS.md index 7ae09bdfe..e0388aeb0 100644 --- a/src/crates/agent-tools/AGENTS.md +++ b/src/crates/agent-tools/AGENTS.md @@ -9,26 +9,18 @@ the product tool runtime. - Do not depend on `bitfun-core`, concrete service crates, `tool-packs`, app crates, Tauri, Git, MCP, network clients, or CLI UI dependencies. -- This crate may own `ToolResult`, validation DTOs, runtime restriction DTOs, - path-resolution DTOs, host path normalization, runtime artifact URI, - remote POSIX path pure contracts, provider-neutral path resolution / - absolute-path checks, runtime artifact reference assembly, file guidance - markers, file-read freshness comparison policy, oversized tool-result - preview/rendering policy, tool execution result/error/invalid-call presentation policy, deterministic - tool execution admission policy including loop detection, allowed-list, - runtime-restriction and collapsed-tool gates, generic/static/dynamic provider contracts, pure - manifest/exposure helpers, generic contextual prompt-manifest resolver - contracts, generic catalog snapshot provider contracts, generic GetToolSpec - catalog provider/detail/summary helpers, provider-backed GetToolSpec runtime - facades, and `ToolContextFacts` / `PortableToolContextProvider`. -- This crate may own generic provider containers such as - `StaticToolProviderGroup`, but concrete tool construction and product runtime - registration stay outside this crate until H1 explicitly moves an owner. +- This crate may own provider-neutral tool DTOs, validation/restriction facts, + path and artifact contracts, pure manifest/catalog/exposure helpers, result + presentation policy, deterministic admission policy, and portable tool context + facts. +- This crate may own generic provider contracts and containers, but concrete + tool construction and product runtime registration stay outside this crate + until a reviewed owner move proves behavior equivalence. - Do not move `ToolUseContext`, concrete tools, workspace services, cancellation tokens, session file-read state storage, tool-result filesystem writes, - state update side effects, snapshot decoration, collapsed unlock state, product registry - snapshot access, or concrete `GetToolSpecTool` execution here without H1 - approval and equivalence tests. + state update side effects, snapshot decoration, collapsed unlock state, + product registry snapshot access, or concrete `GetToolSpecTool` execution + here without an owner design and equivalence tests. - Provider-specific wire serialization belongs in AI adapters, not in these provider-neutral contracts. diff --git a/src/crates/api-layer/AGENTS.md b/src/crates/api-layer/AGENTS.md new file mode 100644 index 000000000..21abcf921 --- /dev/null +++ b/src/crates/api-layer/AGENTS.md @@ -0,0 +1,28 @@ +# api-layer Agent Guide + +Scope: this guide applies to `src/crates/api-layer`. + +`bitfun-api-layer` owns platform-agnostic API DTOs and handler coordination. It +is the stable boundary between app entrypoints and lower transport/runtime +layers. + +## Guardrails + +- Do not depend on `bitfun-core`, app crates, Tauri, desktop-only adapters, + concrete services, AI providers, terminal, or tool-runtime implementations. +- Keep request/response DTOs stable and explicit. Avoid catch-all payloads that + hide product or platform coupling. +- Handlers may coordinate ports and transport surfaces; they must not own + product policy, tool execution, session lifecycle, filesystem/process IO, or + platform-specific behavior. +- Preserve command/API compatibility when renaming fields, routes, or response + shapes. + +## Verification + +```bash +cargo check -p bitfun-api-layer +node scripts/check-core-boundaries.mjs +``` + +For documentation-only changes, run `git diff --check`. diff --git a/src/crates/core-types/AGENTS.md b/src/crates/core-types/AGENTS.md new file mode 100644 index 000000000..555d01817 --- /dev/null +++ b/src/crates/core-types/AGENTS.md @@ -0,0 +1,27 @@ +# core-types Agent Guide + +Scope: this guide applies to `src/crates/core-types`. + +`bitfun-core-types` owns low-level shared DTOs and error/session/surface +contracts. Keep it dependency-light and stable for cross-crate reuse. + +## Guardrails + +- Do not depend on `bitfun-core`, runtime owner crates, service crates, + transport adapters, app crates, Tauri, AI providers, Git, MCP, terminal, or + tool-runtime implementations. +- Keep additions limited to portable data shapes, serialization contracts, and + small pure helpers. +- Preserve persisted and cross-process wire compatibility. Any field rename, + enum variant change, or default change must be treated as a contract change. +- Product policy, runtime behavior, IO, process execution, and platform + integration belong in owner crates above this layer. + +## Verification + +```bash +cargo test -p bitfun-core-types +node scripts/check-core-boundaries.mjs +``` + +For documentation-only changes, run `git diff --check`. diff --git a/src/crates/core/AGENTS-CN.md b/src/crates/core/AGENTS-CN.md index 1cd983c8a..96080f50b 100644 --- a/src/crates/core/AGENTS-CN.md +++ b/src/crates/core/AGENTS-CN.md @@ -1,14 +1,16 @@ **中文** | [English](AGENTS.md) -# AGENTS-CN.md +# Core Agent 指南 ## 适用范围 -本文件适用于 `src/crates/core`。仓库级规则请看顶层 `AGENTS.md`。 +本文件适用于 `src/crates/core`。仓库级规则请看顶层 `AGENTS.md`;进入更具体目录后,优先遵循更近的局部指南。 -## 这里最重要的内容 +## 定位 -`bitfun-core` 是共享产品逻辑中心。 +`bitfun-core` 是共享产品 runtime facade。它仍承载兼容路径和 `product-full` 组装边界,但新的拆解工作应优先遵循 +`docs/architecture/core-decomposition.md` 与 +`docs/architecture/agent-runtime-services-design.md` 中定义的 owner crate 边界。 主要区域: @@ -19,81 +21,60 @@ Agent 运行时心智模型: ```text -SessionManager → Session → DialogTurn → ModelRound +SessionManager -> Session -> DialogTurn -> ModelRound ``` -## 本模块规则 - -- 共享 core 必须保持平台无关 -- 避免引入 `tauri::AppHandle` 等宿主 API -- 使用 `bitfun_events::EventEmitter` 等共享抽象 -- 桌面端专属集成应放在 `src/apps/desktop`,再通过 transport / API layer 连接回来 -- core 拆解期间,`bitfun-core` 是兼容 facade 与完整产品 runtime assembly 点;新模块优先放到 `docs/architecture/core-decomposition.md` 指定的 owner crate。 -- Harness workflow contract、descriptor provider、route plan 和 provider - registry 归属 `bitfun-harness`。迁移期 core 可以注册 Deep Review、 - DeepResearch、MiniApp 的 legacy-facade provider,但具体 workflow 执行继续 - 留在既有 core / product 路径,直到有评审过的迁移和等价测试。 -- Persisted thread goal 的 DTO、status、continuation plan 和 tool response - contract 归属 `bitfun-runtime-ports`。`ThreadGoalRuntime`、turn accounting、 - continuation planning、goal mutation decision 和 goal tool response assembly - 归属 `bitfun-agent-runtime`。core 只保留 session metadata store、token - subscriber、scheduler delivery adapter、event emission,以及 `get_goal` / - `create_goal` / `update_goal` 的 `Tool` handler。 -- Subagent query scope、visibility/availability decision、registry source/profile - fact、mode/subagent presentation fact、round-boundary yield/injection state 和 - turn-outcome queue decision 归属 `bitfun-agent-runtime`。core 保留 concrete - agent definition loading、custom subagent file IO/config adapter、desktop API - wiring、concrete scheduler lifecycle、submit execution 和 event delivery。 -- Prompt-loop 的 user-context policy 和 tool / skill / subagent listing - reminder ordering,以及 prompt-cache policy、identity、DTO、scope-key fact 和 - in-memory store 归属 `bitfun-agent-runtime`。core 保留具体 prompt assembly、 - workspace / remote / project-layout context IO、language/config lookup、 - prompt-cache persistence/restore 协调和旧路径兼容 re-export。 -- Finish-reason label、session-state event label 和 turn-outcome event fact - 归属 `bitfun-agent-runtime`。core 保留具体 event routing、event emission、 - session state storage 和旧路径兼容 re-export。 -- Tool 相关轻量 contract、portable tool context facts/provider、纯 manifest/exposure contract、generic registry / static-provider / dynamic-provider container、file guidance marker、file-read freshness 比较策略和 oversized tool-result preview/rendering 纯策略归属 `bitfun-agent-tools`;core tool runtime 通过 `product_runtime.rs` 统一负责产品工具组装、`dyn Tool` 适配、snapshot decoration、runtime manifest assembly / context filtering、按需工具说明发现(`GetToolSpec`)执行,以及 collapsed unlock observation source。 -- `ToolUseContext`、session file-read state storage、tool-result filesystem writes 与具体工具实现继续留在 core,除非已有评审过的 port/provider 方案和等价测试。 -- Tool 迁移必须保持 expanded/collapsed exposure、prompt 可见 manifest、`ToolUseContext.unlocked_collapsed_tools`,以及 desktop/MCP/ACP tool catalog 行为等价。 -- 不要把 OpenAI Responses / Codex ChatGPT flat tool schema 等 provider-specific 序列化行为写进 core tool contract;AI adapter 负责 provider 序列化,core 保持 provider-neutral manifest。 -- 调整 session/token usage 路径时,`cached_content_token_count` 必须继续表示 cache reads/hits,`cache_creation_token_count` 必须作为独立 provider fact 保留。 -- Function-agent commit-message 与 Startchat work-state orchestration 可以经由 - `bitfun-product-domains`;Git/AI service adapter、provider 获取、AI client - 调用和 transport error mapping 仍由 core 拥有。prompt template、JSON - extraction/repair、domain error mapping 与 domain JSON parsing policy 可以放在 - `bitfun-product-domains`。 -- MiniApp built-in bundle/hash/marker seed plan 与 marker wire helper 可以放在 - `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 与 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 拥有,直到有评审过的迁移和等价测试。 -- Workspace file/shell service contract 属于 `bitfun-runtime-ports`。`src/agentic/workspace.rs` - 可以保留旧路径 re-export 和 local / remote concrete adapter,但不能重新拥有 - `WorkspaceFileSystem`、`WorkspaceShell`、`WorkspaceServices` 或 workspace command DTO。 -- 不要在没有小型 port/interface 边界的情况下新增 `service` 到 `agentic` 的跨层引用。 -- 不要在 core 拆解中把平台专属逻辑、构建脚本行为或产品能力选择下沉到 shared core。 - -这里已经有更细粒度规则: +## 边界规则 + +- 共享 core 必须保持平台无关。避免引入 `tauri::AppHandle` 等宿主 API;优先使用 + `bitfun_events::EventEmitter` 等共享抽象。 +- 桌面端专属集成应放在 `src/apps/desktop`,再通过 transport / API layer 连接回来。 +- 不要在没有窄 port/interface 边界的情况下新增 `service` 到 `agentic` 的跨层引用。 +- 不要把平台专属逻辑、构建脚本行为、产品能力选择或 provider-specific AI 序列化写进 shared core。 +- owner 从 core 外移时,在下游调用点被有意迁移前,用 facade 或 re-export 保持旧 import path。 + +## 拆解规则 + +- 将 `bitfun-core` 视为兼容 facade 与完整产品组装点,而不是新稳定契约的默认归属。 +- 稳定 DTO、facts、ports 和纯决策应放到有明确边界的 owner crate;具体 manager、IO、平台 adapter 和产品执行在没有评审过的 + port/provider 设计与行为等价测试前继续留在 core。 +- Tool 改动必须保持 expanded/collapsed exposure、prompt-visible manifest、`GetToolSpec`、权限行为、 + `ToolUseContext` 语义,以及 desktop/MCP/ACP catalog 行为等价。 +- Product-domain 改动不得在没有明确 owner 设计和 focused regression 覆盖前,把 filesystem writes、worker/host execution、 + Git/AI concrete calls、marker IO 或 path-manager integration 移出 core。 +- Remote/service 改动必须保持 external protocol lifecycle、workspace projection、scheduler/session restore、 + terminal pre-warm 和 product execution 边界清晰。 +- Feature 改动必须保持 `product-full` 作为兼容产品组装边界;默认能力选择只有在单独的 product matrix review 后才能变化。 + +## 归属参考 + +归属细节放在下列文件中,不要继续扩写本指南: + +- `docs/architecture/core-decomposition.md` +- `docs/architecture/agent-runtime-services-design.md` +- `src/crates/agent-runtime/AGENTS.md` +- `src/crates/agent-tools/AGENTS.md` +- `src/crates/harness/AGENTS.md` +- `src/crates/product-domains/AGENTS.md` +- `src/crates/runtime-ports/` 与 `src/crates/runtime-services/` 源码说明 +- `src/crates/services-core/AGENTS.md` +- `src/crates/services-integrations/AGENTS.md` +- `src/crates/tool-packs/AGENTS.md` + +部分子目录已有更细指南: - `src/crates/ai-adapters/AGENTS.md` - `src/agentic/execution/AGENTS.md` - `src/agentic/deep_review/AGENTS.md` -## 命令 +## 验证 + +按触及行为选择最小检查: ```bash cargo check --workspace -cargo test --workspace cargo test -p bitfun-core -- --nocapture +node scripts/check-core-boundaries.mjs ``` -## 验证 - -```bash -cargo check --workspace && cargo test --workspace -``` +仅改文档时运行 `git diff --check`。 diff --git a/src/crates/core/AGENTS.md b/src/crates/core/AGENTS.md index b37b299ce..ade837f0f 100644 --- a/src/crates/core/AGENTS.md +++ b/src/crates/core/AGENTS.md @@ -1,14 +1,18 @@ [中文](AGENTS-CN.md) | **English** -# AGENTS.md +# Core Agent Guide ## Scope -This file applies to `src/crates/core`. Use the top-level `AGENTS.md` for repository-wide rules. +This file applies to `src/crates/core`. Use the top-level `AGENTS.md` for +repository-wide rules and the nearest narrower guide when one exists. -## What matters here +## Role -`bitfun-core` is the shared product-logic center. +`bitfun-core` is the shared product runtime facade. It still owns compatibility +paths and the `product-full` assembly boundary, but new decomposition work should +prefer the owner crates described in `docs/architecture/core-decomposition.md` +and `docs/architecture/agent-runtime-services-design.md`. Main areas: @@ -19,205 +23,73 @@ Main areas: Agent runtime mental model: ```text -SessionManager → Session → DialogTurn → ModelRound +SessionManager -> Session -> DialogTurn -> ModelRound ``` -## Local rules - -- Keep shared core platform-agnostic -- Avoid host-specific APIs such as `tauri::AppHandle` -- Use shared abstractions such as `bitfun_events::EventEmitter` -- Desktop-only integrations belong in `src/apps/desktop`, then flow through transport/API layers -- Backend locale ids, aliases, and fallback rules must stay aligned with - `src/shared/i18n/contract/locales.json`; run `pnpm run i18n:generate` when - changing supported locales. -- During core decomposition, `bitfun-core` is a compatibility facade and full - product runtime assembly point. New modules should prefer the extracted owner - crate listed in `docs/architecture/core-decomposition.md`. -- Harness workflow contracts, descriptor providers, route plans, and provider - registry logic belong in `bitfun-harness`. Core may register Deep Review, - DeepResearch, and MiniApp legacy-facade providers during migration, but - concrete workflow execution stays on existing core/product paths until a - reviewed migration proves equivalence. -- Persisted thread goal DTOs, statuses, continuation plans, and tool response - contracts belong in `bitfun-runtime-ports`. `ThreadGoalRuntime`, turn - accounting, continuation planning, goal mutation decisions, and goal tool - response assembly belong in `bitfun-agent-runtime`. Core keeps only the - session metadata store, token subscriber, scheduler delivery adapter, event - emission, and `get_goal` / `create_goal` / `update_goal` `Tool` handlers. -- Subagent query scope, visibility/availability decisions, registry source/profile - facts, mode/subagent presentation facts, round-boundary yield/injection state, - and turn-outcome queue decisions belong in - `bitfun-agent-runtime`. Core keeps concrete agent definition loading, custom - subagent file IO/config adapters, desktop API wiring, concrete scheduler - lifecycle, submit execution, and event delivery. -- Prompt-loop user-context policy and tool/skill/subagent listing reminder - ordering, plus prompt-cache policy, identity, DTOs, scope-key facts, and - in-memory store belong in `bitfun-agent-runtime`. Core keeps concrete prompt - assembly, workspace / remote / project-layout context IO, language/config - lookup, prompt-cache persistence/restore coordination, and old-path - compatibility re-exports. -- Finish-reason labels, session-state event labels, and turn-outcome event - facts belong in - `bitfun-agent-runtime`. Core keeps concrete event routing, event emission, - session state storage, and old-path compatibility re-exports. -- For tools, keep lightweight contracts, pure manifest/exposure contracts, - generic contextual prompt-manifest resolver contracts, generic catalog - snapshot provider contracts, generic GetToolSpec catalog provider/detail/ - summary/static tool surface/execution-plan/provider-backed runtime facade / execution-result/ - result-vector adapter / result-assembly helpers, and portable tool context facts/provider plus generic registry / static-provider / dynamic-provider container - contracts in `bitfun-agent-tools`. Provider-backed visible-tools / prompt-visible manifest / readonly catalog runtime facades, - generic decorator references, snapshot decorator adapters, static-provider runtime assembly, and readonly/enabled - registry-snapshot filtering belong in - `bitfun-agent-tools`; core tool runtime should materialize concrete tools from the `bitfun-tool-packs` - provider group plan through `product_runtime.rs`, adapt core `Tool` into - provider-neutral contracts through `tool_adapter.rs`, and keep product - registry snapshot access, product manifest / GetToolSpec facade wiring, - product snapshot wrapper adapter injection, on-demand spec discovery Tool - impl, and collapsed unlock observation source in `product_runtime.rs` / `product_runtime/` - for now. Keep manifest/catalog ownership in `product_runtime/catalog.rs` - and snapshot decoration in `product_runtime/snapshot.rs`; `manifest_resolver.rs` - should stay a compatibility facade. Do not move `GetToolSpecTool` ownership back into generic - concrete-tool implementations; a legacy re-export is only a compatibility - alias. - `bitfun-tool-packs` may expose planned - feature-group scaffold metadata, but it must not own concrete tools yet. -- Keep `ToolUseContext` and concrete tool implementations in core unless a - reviewed port/provider plan and equivalence tests exist. `ToolContextFacts` - / `PortableToolContextProvider` are only portable projections; they must not - carry runtime handles, workspace services, or cancellation tokens. -- Keep `ToolUseContext` owner type, portable facts projection, and - runtime/service bindings centralized in - `src/agentic/tools/tool_context_runtime.rs`. `framework.rs` should only keep - the tool trait and compatibility re-export, not own context shape, workspace - runtime lookup, path enforcement, pipeline/description context - materialization, cancellation wrapping, post-call hooks, or checkpoint - collection. -- Core runtime/adapter modules that need `ToolUseContext` should import it from - `tool_context_runtime`; the `framework.rs` re-export is only for legacy path - compatibility. -- Host path normalization, runtime artifact URI parsing/building, and remote - POSIX path containment are portable `bitfun-agent-tools` contracts. Core - keeps compatibility wrappers for `BitFunError`, workspace runtime-root - lookup, and `ToolUseContext` integration. -- Tool allowed-list and collapsed-tool direct execution gating delegate to - `bitfun-agent-tools`; product runtime owns the collapsed unlock observation - source and core maps gate results into pipeline failure state. -- Any tool migration must preserve expanded/collapsed exposure, prompt-visible - manifests, `ToolUseContext.unlocked_collapsed_tools`, and desktop/MCP/ACP - tool catalog behavior. -- Do not encode provider-specific OpenAI Responses / Codex ChatGPT flat tool - schema behavior in core tool contracts; AI adapters own provider - serialization while core keeps provider-neutral manifests. -- When touching session/token usage paths, keep `cached_content_token_count` - as cache reads/hits and `cache_creation_token_count` as a separate provider - fact. -- Function-agent commit-message and Startchat work-state orchestration may - route through `bitfun-product-domains`. Keep Git/AI service adapters, - provider acquisition, AI client calls, and transport error mapping core-owned; - prompt templates, JSON extraction/repair, domain error mapping, and domain - JSON parsing policy may live in `bitfun-product-domains`. -- MiniApp built-in bundle/hash/marker seed-plan and marker wire helpers may - live in `bitfun-product-domains`. MiniApp create/update/draft/apply pure state - transitions, imported metadata stamping, import runtime-state persistence - facade, and built-in seed meta timestamp policy may also live there; keep - 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 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`. -- Workspace file/shell service contracts belong in `bitfun-runtime-ports`. - `src/agentic/workspace.rs` may keep old-path re-exports and local / remote - concrete adapters, but must not re-own `WorkspaceFileSystem`, - `WorkspaceShell`, `WorkspaceServices`, or workspace command DTOs. -- Keep concrete remote SSH runtime code behind `ssh-remote`. No-default builds - may keep workspace identity helpers and explicit unsupported stubs, but must - not compile russh-backed SSH/SFTP/terminal/search runtime modules. -- Generic local filesystem operations, tree/search, listing, and filesystem DTOs - live in `bitfun-services-core::filesystem`. Core may keep compatibility - re-exports, remote workspace overlay, `BitFunError` mapping, MiniApp - filesystem IO, tool-result persistence, `PathManager` binding, and product - runtime wiring. -- Keep no-default `bitfun-core` as a runtime-surface-light facade, not a - claimed dependency-light build. Full product runtime modules such as agentic, - MiniApp/function-agent, Git/MCP, remote-connect, review-platform, snapshot, - token usage, and mode canonicalization stay behind `product-full` or their - owner feature group. -- Provider-neutral tool path resolution, effective absolute-path checks, - runtime artifact reference assembly, path policy root matching, and denial - text may live in `bitfun-agent-tools`; file guidance markers, file-read - freshness comparison policy, and oversized tool-result preview/rendering - policy may also live there as pure contracts. Provider-neutral tool result - assistant fallback text, error argument preview, invalid-call messages, and - steering-interrupted presentation may live there too. Keep workspace/runtime root lookup, - allowed-root resolution, local canonicalization, remote POSIX containment - callbacks, session file-read state storage, tool-result filesystem writes, - `BitFunError` category mapping, and `ToolUseContext` - runtime/service bindings in core unless a separate migration proves - equivalence. -- Product/runtime dependencies that are only used behind those feature gates - should stay optional in `bitfun-core` and be enabled by `product-full`, - `service-integrations`, or `ssh-remote`; do not treat that as permission to - lighten defaults or change product crate feature sets. Keep - `scripts/check-core-boundaries.mjs` updated so each optional runtime - dependency has an explicit feature owner. -- Product entry crates that depend on `bitfun-core` must keep - `default-features = false` and explicitly enable `product-full`; keep this - wired through product manifests rather than relying on core defaults. The - boundary script scans product entry manifests for new direct `bitfun-core` - dependencies and requires matching assembly rules. -- Keep `default = ["product-full"]` until a separate product matrix review - explicitly changes default capability selection. -- Keep `bitfun-core/product-full` explicitly wired to the current owner feature - groups: `ssh-remote`, `product-domains`, `service-integrations`, and - `tool-packs`. -- Owner crate feature graph guards keep `tool-packs`, `services-integrations`, - and `product-domains` default-light while allowing `product-full` to - explicitly aggregate current owner feature groups. When adding an owner - feature group, update `scripts/check-core-boundaries.mjs`; `product-full` - must not include undeclared feature groups or dependency shortcuts. Optional - runtime/domain dependencies in owner crates must stay owned by explicit - feature groups. -- `service-integrations` is not a standalone product shape in core yet; MCP, - remote-connect, and review-platform still depend on agentic/product runtime - owners through `product-full`. -- Do not add new cross-layer references from `service` to `agentic` without a - small port/interface boundary. -- Do not move platform-specific logic, build-script behavior, or product - capability selection into shared core as part of decomposition. +## Boundary Rules -Narrower rules already exist: +- Keep shared core platform-agnostic. Avoid host-specific APIs such as + `tauri::AppHandle`; use shared abstractions such as + `bitfun_events::EventEmitter`. +- Desktop-only integrations belong in `src/apps/desktop`, then flow through + transport/API layers. +- Do not add new cross-layer references from `service` to `agentic` without a + narrow port/interface boundary. +- Do not move platform-specific logic, build-script behavior, product capability + selection, or provider-specific AI serialization into shared core. +- When moving ownership out of core, preserve old import paths with facade or + re-export code until downstream call sites are intentionally migrated. + +## Decomposition Rules + +- Treat `bitfun-core` as a compatibility facade plus full product assembly point, + not as the preferred home for new stable contracts. +- Put stable DTOs, facts, ports, and pure decisions in the matching owner crate + where a clear owner exists. Keep concrete managers, IO, platform adapters, and + product execution in core until a reviewed port/provider design and behavior + equivalence tests exist. +- Tool changes must preserve expanded/collapsed exposure, prompt-visible + manifests, `GetToolSpec`, permission behavior, `ToolUseContext` semantics, and + desktop/MCP/ACP catalog behavior. +- Product-domain changes must not move filesystem writes, worker/host execution, + Git/AI concrete calls, marker IO, or path-manager integration out of core + without an explicit owner design and focused regression coverage. +- Remote/service changes must keep external protocol lifecycle, workspace + projection, scheduler/session restore, terminal pre-warm, and product + execution boundaries explicit. +- Feature work must keep `product-full` as the compatibility product assembly + boundary unless a separate product matrix review changes default capability + selection. + +## Owner References + +Use these files for ownership details instead of expanding this guide: + +- `docs/architecture/core-decomposition.md` +- `docs/architecture/agent-runtime-services-design.md` +- `src/crates/agent-runtime/AGENTS.md` +- `src/crates/agent-tools/AGENTS.md` +- `src/crates/harness/AGENTS.md` +- `src/crates/product-domains/AGENTS.md` +- `src/crates/runtime-ports/` and `src/crates/runtime-services/` source docs +- `src/crates/services-core/AGENTS.md` +- `src/crates/services-integrations/AGENTS.md` +- `src/crates/tool-packs/AGENTS.md` + +Narrower local guides already exist for some subtrees: - `src/crates/ai-adapters/AGENTS.md` - `src/agentic/execution/AGENTS.md` - `src/agentic/deep_review/AGENTS.md` -## DeepReview notes - -- Keep policy, manifest gate, queue state, Task adapter, and report enrichment - aligned when changing `src/agentic/deep_review*` or review agents. -- Keep reviewer subagents read-only; user-approved remediation is outside the - reviewer pass. +## Verification -## Commands +Use the smallest check that matches the touched behavior: ```bash cargo check --workspace -cargo test --workspace cargo test -p bitfun-core -- --nocapture +node scripts/check-core-boundaries.mjs ``` -## Verification - -```bash -cargo check --workspace && cargo test --workspace -``` +For documentation-only changes, run `git diff --check`. diff --git a/src/crates/core/src/agentic/tools/file_read_state_runtime.rs b/src/crates/core/src/agentic/tools/file_read_state_runtime.rs index 05199967d..0c3fa1ad7 100644 --- a/src/crates/core/src/agentic/tools/file_read_state_runtime.rs +++ b/src/crates/core/src/agentic/tools/file_read_state_runtime.rs @@ -373,9 +373,8 @@ mod tests { unlocked_collapsed_tools: Vec::new(), custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: Default::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), } } diff --git a/src/crates/core/src/agentic/tools/implementations/ask_user_question_tool.rs b/src/crates/core/src/agentic/tools/implementations/ask_user_question_tool.rs index acb6a72ba..f8e953d51 100644 --- a/src/crates/core/src/agentic/tools/implementations/ask_user_question_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/ask_user_question_tool.rs @@ -408,9 +408,8 @@ mod tests { unlocked_collapsed_tools: Vec::new(), custom_data, computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: Default::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), } } diff --git a/src/crates/core/src/agentic/tools/implementations/bash_tool.rs b/src/crates/core/src/agentic/tools/implementations/bash_tool.rs index 26112d2ea..30aceb6f4 100644 --- a/src/crates/core/src/agentic/tools/implementations/bash_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/bash_tool.rs @@ -431,8 +431,7 @@ impl BashTool { fn cancellation_requested(context: &ToolUseContext) -> bool { context - .cancellation_token - .as_ref() + .cancellation_token() .is_some_and(|token| token.is_cancelled()) } @@ -571,10 +570,10 @@ Before executing the command, please follow these steps: 2. Command Execution: - Always quote file paths that contain spaces with double quotes (e.g., cd "path with spaces/file.txt") - Examples of proper quoting: - - cd "/Users/name/My Documents" (correct) - - cd /Users/name/My Documents (incorrect - will fail) - - python "/path/with spaces/script.py" (correct) - - python /path/with spaces/script.py (incorrect - will fail) + - cd "My Documents" (correct) + - cd My Documents (incorrect - will fail) + - python "scripts/with spaces/script.py" (correct) + - python scripts/with spaces/script.py (incorrect - will fail) - After ensuring proper quoting, execute the command. - Capture the output of the command. @@ -917,7 +916,7 @@ Usage notes: &remote_command, WorkspaceCommandOptions { timeout_ms: Some(timeout_ms), - cancellation_token: context.cancellation_token.clone(), + cancellation_token: context.cancellation_token().cloned(), }, ) .await @@ -1132,7 +1131,7 @@ Usage notes: }; // Check cancellation request - if let Some(token) = &context.cancellation_token { + if let Some(token) = context.cancellation_token() { if token.is_cancelled() && !was_interrupted { debug!("Bash tool received cancellation request, sending interrupt signal, tool_id: {}", tool_use_id); was_interrupted = true; diff --git a/src/crates/core/src/agentic/tools/implementations/code_review_tool.rs b/src/crates/core/src/agentic/tools/implementations/code_review_tool.rs index 1a94f8fb2..8ea83d908 100644 --- a/src/crates/core/src/agentic/tools/implementations/code_review_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/code_review_tool.rs @@ -743,9 +743,8 @@ mod tests { unlocked_collapsed_tools: Vec::new(), custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: Default::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), } } diff --git a/src/crates/core/src/agentic/tools/implementations/control_hub_tool.rs b/src/crates/core/src/agentic/tools/implementations/control_hub_tool.rs index e457a8dd9..c37745200 100644 --- a/src/crates/core/src/agentic/tools/implementations/control_hub_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/control_hub_tool.rs @@ -1480,9 +1480,8 @@ mod control_hub_tests { unlocked_collapsed_tools: Vec::new(), custom_data: std::collections::HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: Default::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), } } diff --git a/src/crates/core/src/agentic/tools/implementations/cron_tool.rs b/src/crates/core/src/agentic/tools/implementations/cron_tool.rs index bae17671c..6279e5fc1 100644 --- a/src/crates/core/src/agentic/tools/implementations/cron_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/cron_tool.rs @@ -1167,9 +1167,8 @@ mod tests { unlocked_collapsed_tools: Vec::new(), custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: Default::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), } } diff --git a/src/crates/core/src/agentic/tools/implementations/file_write_tool.rs b/src/crates/core/src/agentic/tools/implementations/file_write_tool.rs index 7b5ee8c2c..3daaa17bf 100644 --- a/src/crates/core/src/agentic/tools/implementations/file_write_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/file_write_tool.rs @@ -252,9 +252,8 @@ mod tests { unlocked_collapsed_tools: Vec::new(), custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), } } @@ -489,19 +488,21 @@ impl Tool for FileWriteTool { } } - let large_write_warning = input - .get("content") - .and_then(|v| v.as_str()) - .and_then(|content| { - let line_count = content.lines().count(); - let byte_count = content.len(); - if line_count > LARGE_WRITE_SOFT_LINE_LIMIT || byte_count > LARGE_WRITE_SOFT_BYTE_LIMIT - { - Some((line_count, byte_count)) - } else { - None - } - }); + let large_write_warning = + input + .get("content") + .and_then(|v| v.as_str()) + .and_then(|content| { + let line_count = content.lines().count(); + let byte_count = content.len(); + if line_count > LARGE_WRITE_SOFT_LINE_LIMIT + || byte_count > LARGE_WRITE_SOFT_BYTE_LIMIT + { + Some((line_count, byte_count)) + } else { + None + } + }); if let Some(ctx) = context { if let Some(message) = Self::preflight_write_error(ctx, file_path).await { diff --git a/src/crates/core/src/agentic/tools/implementations/session_control_tool.rs b/src/crates/core/src/agentic/tools/implementations/session_control_tool.rs index 65f81801a..5b1ff2a76 100644 --- a/src/crates/core/src/agentic/tools/implementations/session_control_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/session_control_tool.rs @@ -793,9 +793,8 @@ mod tests { unlocked_collapsed_tools: Vec::new(), custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: Default::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), } } diff --git a/src/crates/core/src/agentic/tools/implementations/session_message_tool.rs b/src/crates/core/src/agentic/tools/implementations/session_message_tool.rs index 8eee627ef..f2479a2f0 100644 --- a/src/crates/core/src/agentic/tools/implementations/session_message_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/session_message_tool.rs @@ -605,9 +605,8 @@ mod tests { unlocked_collapsed_tools: Vec::new(), custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: Default::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), } } diff --git a/src/crates/core/src/agentic/tools/implementations/skill_tool.rs b/src/crates/core/src/agentic/tools/implementations/skill_tool.rs index 2fd4662db..a3bde63e6 100644 --- a/src/crates/core/src/agentic/tools/implementations/skill_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/skill_tool.rs @@ -383,12 +383,14 @@ Use the remote project skill. unlocked_collapsed_tools: Vec::new(), custom_data: Default::default(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: Default::default(), - workspace_services: Some(WorkspaceServices { - fs: Arc::new(FakeRemoteFs), - shell: Arc::new(FakeShell), - }), + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::new( + Some(WorkspaceServices { + fs: Arc::new(FakeRemoteFs), + shell: Arc::new(FakeShell), + }), + None, + ), }; let description = SkillTool::build_available_skills_context_section(Some(&context)) @@ -422,12 +424,14 @@ Use the remote project skill. unlocked_collapsed_tools: Vec::new(), custom_data: Default::default(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: Default::default(), - workspace_services: Some(WorkspaceServices { - fs: Arc::new(FakeRemoteFs), - shell: Arc::new(FakeShell), - }), + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::new( + Some(WorkspaceServices { + fs: Arc::new(FakeRemoteFs), + shell: Arc::new(FakeShell), + }), + None, + ), }; let results = SkillTool::new() diff --git a/src/crates/core/src/agentic/tools/implementations/task_tool.rs b/src/crates/core/src/agentic/tools/implementations/task_tool.rs index 06899009a..1a8965ed0 100644 --- a/src/crates/core/src/agentic/tools/implementations/task_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/task_tool.rs @@ -863,8 +863,7 @@ impl Tool for TaskTool { } if is_retry || requested_auto_retry || input.get("retry_coverage").is_some() { return Err(BitFunError::tool( - "DeepReview retry fields are not allowed when fork_context is true" - .to_string(), + "DeepReview retry fields are not allowed when fork_context is true".to_string(), )); } } @@ -1346,7 +1345,7 @@ impl Tool for TaskTool { context: subagent_context.clone().unwrap_or_default(), delegation_policy: context.delegation_policy().spawn_child(), }, - context.cancellation_token.as_ref(), + context.cancellation_token(), timeout_seconds, ) .await; @@ -1389,7 +1388,7 @@ impl Tool for TaskTool { Some(DeepReviewSubagentRole::Reviewer) ) && matches!(error, BitFunError::Cancelled(_)) && !context - .cancellation_token + .cancellation_token() .as_ref() .is_some_and(|token| token.is_cancelled()) { @@ -1890,9 +1889,8 @@ mod tests { ), ]), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), }; let error = TaskTool::new() @@ -2021,9 +2019,8 @@ mod tests { unlocked_collapsed_tools: Vec::new(), custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), }; let deep_review_context = ToolUseContext { agent_type: Some("DeepReview".to_string()), @@ -2057,9 +2054,8 @@ mod tests { unlocked_collapsed_tools: Vec::new(), custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), }; let builtin_a = "AAAPromptOrderBuiltin"; diff --git a/src/crates/core/src/agentic/tools/implementations/web_tools.rs b/src/crates/core/src/agentic/tools/implementations/web_tools.rs index dad3b4d86..276c96bc9 100644 --- a/src/crates/core/src/agentic/tools/implementations/web_tools.rs +++ b/src/crates/core/src/agentic/tools/implementations/web_tools.rs @@ -667,9 +667,8 @@ mod tests { unlocked_collapsed_tools: Vec::new(), custom_data: std::collections::HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: Default::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), } } diff --git a/src/crates/core/src/agentic/tools/manifest_resolver.rs b/src/crates/core/src/agentic/tools/manifest_resolver.rs index 0a8e9e518..218b59642 100644 --- a/src/crates/core/src/agentic/tools/manifest_resolver.rs +++ b/src/crates/core/src/agentic/tools/manifest_resolver.rs @@ -46,9 +46,8 @@ mod tests { unlocked_collapsed_tools: Vec::new(), custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), } } diff --git a/src/crates/core/src/agentic/tools/pipeline/tool_pipeline.rs b/src/crates/core/src/agentic/tools/pipeline/tool_pipeline.rs index 6ad55f439..55165a217 100644 --- a/src/crates/core/src/agentic/tools/pipeline/tool_pipeline.rs +++ b/src/crates/core/src/agentic/tools/pipeline/tool_pipeline.rs @@ -1641,7 +1641,7 @@ mod tests { assert_eq!(context.session_id.as_deref(), Some("session_1")); assert_eq!(context.dialog_turn_id.as_deref(), Some("turn_1")); assert_eq!(context.unlocked_collapsed_tools, vec!["WebFetch"]); - assert!(context.cancellation_token.is_some()); + assert!(context.cancellation_token().is_some()); assert!(context .runtime_tool_restrictions .is_tool_allowed("WebFetch")); diff --git a/src/crates/core/src/agentic/tools/product_runtime.rs b/src/crates/core/src/agentic/tools/product_runtime.rs index 3691f2cdb..827d2695a 100644 --- a/src/crates/core/src/agentic/tools/product_runtime.rs +++ b/src/crates/core/src/agentic/tools/product_runtime.rs @@ -7,16 +7,17 @@ mod catalog; mod get_tool_spec_tool; +mod materialization; mod snapshot; mod unlock_state; use crate::agentic::tools::framework::Tool; -use crate::agentic::tools::implementations::*; use crate::agentic::tools::registry::{ProductToolDecoratorRef, ToolRegistry}; #[cfg(test)] use bitfun_agent_tools::StaticToolProvider; use bitfun_agent_tools::{SnapshotToolDecorator, StaticToolProviderGroup, ToolRuntimeAssembly}; use bitfun_tool_packs::product_tool_provider_group_plan; +use materialization::ProductToolMaterializer; use snapshot::ProductSnapshotToolWrapper; use std::sync::Arc; @@ -77,69 +78,15 @@ impl ProductToolRuntime { } fn builtin_static_tool_providers() -> Vec> { - product_tool_provider_group_plan() - .iter() - .map(|group| { - StaticToolProviderGroup::new(group.provider_id(), materialize_tools(group.tool_names())) - }) - .collect() -} - -fn materialize_tools(tool_names: &[&str]) -> Vec> { - tool_names - .iter() - .map(|tool_name| materialize_tool(tool_name)) - .collect() -} - -fn materialize_tool(tool_name: &str) -> Arc { - match tool_name { - "LS" => Arc::new(LSTool::new()), - "Read" => Arc::new(FileReadTool::new()), - "Glob" => Arc::new(GlobTool::new()), - "Grep" => Arc::new(GrepTool::new()), - "Write" => Arc::new(FileWriteTool::new()), - "Edit" => Arc::new(FileEditTool::new()), - "Delete" => Arc::new(DeleteFileTool::new()), - "Bash" => Arc::new(BashTool::new()), - "Task" => Arc::new(TaskTool::new()), - "Skill" => Arc::new(SkillTool::new()), - "AskUserQuestion" => Arc::new(AskUserQuestionTool::new()), - "TodoWrite" => Arc::new(TodoWriteTool::new()), - "get_goal" => Arc::new(GetGoalTool::new()), - "create_goal" => Arc::new(CreateGoalTool::new()), - "update_goal" => Arc::new(UpdateGoalTool::new()), - "CreatePlan" => Arc::new(CreatePlanTool::new()), - "submit_code_review" => Arc::new(CodeReviewTool::new()), - "GetToolSpec" => Arc::new(GetToolSpecTool::new()), - "GetFileDiff" => Arc::new(GetFileDiffTool::new()), - "Log" => Arc::new(LogTool::new()), - "TerminalControl" => Arc::new(TerminalControlTool::new()), - "SessionControl" => Arc::new(SessionControlTool::new()), - "SessionMessage" => Arc::new(SessionMessageTool::new()), - "SessionHistory" => Arc::new(SessionHistoryTool::new()), - "Cron" => Arc::new(CronTool::new()), - "WebSearch" => Arc::new(WebSearchTool::new()), - "WebFetch" => Arc::new(WebFetchTool::new()), - "ListMCPResources" => Arc::new(ListMCPResourcesTool::new()), - "ReadMCPResource" => Arc::new(ReadMCPResourceTool::new()), - "ListMCPPrompts" => Arc::new(ListMCPPromptsTool::new()), - "GetMCPPrompt" => Arc::new(GetMCPPromptTool::new()), - "GenerativeUI" => Arc::new(GenerativeUITool::new()), - "Git" => Arc::new(GitTool::new()), - "ReviewPlatform" => Arc::new(ReviewPlatformTool::new()), - "InitMiniApp" => Arc::new(InitMiniAppTool::new()), - "ControlHub" => Arc::new(ControlHubTool::new()), - "ComputerUse" => Arc::new(ComputerUseTool::new()), - "Playbook" => Arc::new(PlaybookTool::new()), - _ => panic!("unknown product tool provider plan entry: {tool_name}"), - } + ProductToolMaterializer.materialize_provider_groups(product_tool_provider_group_plan()) } #[cfg(test)] mod tests { - use super::ProductToolRuntime; + use super::{materialization::ProductToolMaterializer, ProductToolRuntime}; use crate::agentic::tools::registry::create_tool_registry; + use bitfun_agent_tools::StaticToolProvider; + use bitfun_tool_packs::product_tool_provider_group_plan; #[test] fn product_tool_runtime_owner_preserves_registry_contract() { @@ -158,4 +105,30 @@ mod tests { "product tool runtime owner must preserve collapsed-tool exposure" ); } + + #[test] + fn product_tool_materializer_preserves_provider_plan_order() { + let materializer = ProductToolMaterializer::default(); + let providers = + materializer.materialize_provider_groups(product_tool_provider_group_plan()); + let provider_ids = providers + .iter() + .map(|provider| provider.provider_id()) + .collect::>(); + let planned_ids = product_tool_provider_group_plan() + .iter() + .map(|group| group.provider_id()) + .collect::>(); + + assert_eq!(provider_ids, planned_ids); + + let materialized_names = providers + .into_iter() + .flat_map(|provider| provider.tools()) + .map(|tool| tool.name().to_string()) + .collect::>(); + let registry_names = create_tool_registry().get_tool_names(); + + assert_eq!(materialized_names, registry_names); + } } diff --git a/src/crates/core/src/agentic/tools/product_runtime/catalog.rs b/src/crates/core/src/agentic/tools/product_runtime/catalog.rs index 24e2cec64..6deec5fac 100644 --- a/src/crates/core/src/agentic/tools/product_runtime/catalog.rs +++ b/src/crates/core/src/agentic/tools/product_runtime/catalog.rs @@ -234,9 +234,8 @@ mod tests { unlocked_collapsed_tools: Vec::new(), custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), } } @@ -439,7 +438,10 @@ mod tests { .find(|tool| tool.name == "Write") .expect("Write definition should exist"); - assert_eq!(write.parameters["required"], json!(["file_path", "content"])); + assert_eq!( + write.parameters["required"], + json!(["file_path", "content"]) + ); assert!(write.parameters["properties"].get("content").is_some()); assert!(write.description.contains("Read tool first")); } diff --git a/src/crates/core/src/agentic/tools/product_runtime/get_tool_spec_tool.rs b/src/crates/core/src/agentic/tools/product_runtime/get_tool_spec_tool.rs index ed600b98e..0d43b0ceb 100644 --- a/src/crates/core/src/agentic/tools/product_runtime/get_tool_spec_tool.rs +++ b/src/crates/core/src/agentic/tools/product_runtime/get_tool_spec_tool.rs @@ -165,9 +165,8 @@ mod tests { unlocked_collapsed_tools: vec!["WebFetch".to_string()], custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), }; let results = tool diff --git a/src/crates/core/src/agentic/tools/product_runtime/materialization.rs b/src/crates/core/src/agentic/tools/product_runtime/materialization.rs new file mode 100644 index 000000000..b71ccc72d --- /dev/null +++ b/src/crates/core/src/agentic/tools/product_runtime/materialization.rs @@ -0,0 +1,77 @@ +//! Product tool materialization owner. + +use crate::agentic::tools::framework::Tool; +use crate::agentic::tools::implementations::*; +use bitfun_agent_tools::StaticToolProviderGroup; +use bitfun_tool_packs::ToolProviderGroupPlan; +use std::sync::Arc; + +#[derive(Debug, Clone, Copy, Default)] +pub(in crate::agentic::tools) struct ProductToolMaterializer; + +impl ProductToolMaterializer { + pub(in crate::agentic::tools) fn materialize_provider_groups( + &self, + plan: &[ToolProviderGroupPlan], + ) -> Vec> { + plan.iter() + .map(|group| { + StaticToolProviderGroup::new( + group.provider_id(), + self.materialize_tools(group.tool_names()), + ) + }) + .collect() + } + + fn materialize_tools(&self, tool_names: &[&str]) -> Vec> { + tool_names + .iter() + .map(|tool_name| self.materialize_tool(tool_name)) + .collect() + } + + fn materialize_tool(&self, tool_name: &str) -> Arc { + match tool_name { + "LS" => Arc::new(LSTool::new()), + "Read" => Arc::new(FileReadTool::new()), + "Glob" => Arc::new(GlobTool::new()), + "Grep" => Arc::new(GrepTool::new()), + "Write" => Arc::new(FileWriteTool::new()), + "Edit" => Arc::new(FileEditTool::new()), + "Delete" => Arc::new(DeleteFileTool::new()), + "Bash" => Arc::new(BashTool::new()), + "Task" => Arc::new(TaskTool::new()), + "Skill" => Arc::new(SkillTool::new()), + "AskUserQuestion" => Arc::new(AskUserQuestionTool::new()), + "TodoWrite" => Arc::new(TodoWriteTool::new()), + "get_goal" => Arc::new(GetGoalTool::new()), + "create_goal" => Arc::new(CreateGoalTool::new()), + "update_goal" => Arc::new(UpdateGoalTool::new()), + "CreatePlan" => Arc::new(CreatePlanTool::new()), + "submit_code_review" => Arc::new(CodeReviewTool::new()), + "GetToolSpec" => Arc::new(GetToolSpecTool::new()), + "GetFileDiff" => Arc::new(GetFileDiffTool::new()), + "Log" => Arc::new(LogTool::new()), + "TerminalControl" => Arc::new(TerminalControlTool::new()), + "SessionControl" => Arc::new(SessionControlTool::new()), + "SessionMessage" => Arc::new(SessionMessageTool::new()), + "SessionHistory" => Arc::new(SessionHistoryTool::new()), + "Cron" => Arc::new(CronTool::new()), + "WebSearch" => Arc::new(WebSearchTool::new()), + "WebFetch" => Arc::new(WebFetchTool::new()), + "ListMCPResources" => Arc::new(ListMCPResourcesTool::new()), + "ReadMCPResource" => Arc::new(ReadMCPResourceTool::new()), + "ListMCPPrompts" => Arc::new(ListMCPPromptsTool::new()), + "GetMCPPrompt" => Arc::new(GetMCPPromptTool::new()), + "GenerativeUI" => Arc::new(GenerativeUITool::new()), + "Git" => Arc::new(GitTool::new()), + "ReviewPlatform" => Arc::new(ReviewPlatformTool::new()), + "InitMiniApp" => Arc::new(InitMiniAppTool::new()), + "ControlHub" => Arc::new(ControlHubTool::new()), + "ComputerUse" => Arc::new(ComputerUseTool::new()), + "Playbook" => Arc::new(PlaybookTool::new()), + _ => panic!("unknown product tool provider plan entry: {tool_name}"), + } + } +} diff --git a/src/crates/core/src/agentic/tools/product_runtime/unlock_state.rs b/src/crates/core/src/agentic/tools/product_runtime/unlock_state.rs index 921df3ff2..c01fd7c70 100644 --- a/src/crates/core/src/agentic/tools/product_runtime/unlock_state.rs +++ b/src/crates/core/src/agentic/tools/product_runtime/unlock_state.rs @@ -3,41 +3,67 @@ use crate::agentic::core::{Message, MessageContent}; use bitfun_agent_tools::{collect_loaded_collapsed_tool_names, GetToolSpecLoadObservation}; +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct ProductCollapsedUnlockState { + unlocked_tools: Vec, +} + +impl ProductCollapsedUnlockState { + pub(crate) fn from_messages(messages: &[Message], collapsed_tools: &[String]) -> Self { + let observations = messages + .iter() + .filter_map(get_tool_spec_load_observation) + .collect::>(); + + Self { + unlocked_tools: collect_loaded_collapsed_tool_names( + &observations, + collapsed_tools, + crate::agentic::tools::registry::GET_TOOL_SPEC_TOOL_NAME, + ), + } + } + + #[cfg(test)] + fn is_unlocked(&self, tool_name: &str) -> bool { + self.unlocked_tools + .iter() + .any(|unlocked_tool| unlocked_tool == tool_name) + } + + pub(crate) fn into_unlocked_tools(self) -> Vec { + self.unlocked_tools + } +} + pub(crate) fn collect_product_unlocked_collapsed_tools( messages: &[Message], collapsed_tools: &[String], ) -> Vec { - let observations = messages - .iter() - .filter_map(|message| { - let MessageContent::ToolResult { - tool_name, - result, - is_error, - .. - } = &message.content - else { - return None; - }; - - Some(GetToolSpecLoadObservation { - tool_name, - loaded_tool_name: result.get("tool_name").and_then(|v| v.as_str()), - is_error: *is_error, - }) - }) - .collect::>(); - - collect_loaded_collapsed_tool_names( - &observations, - collapsed_tools, - crate::agentic::tools::registry::GET_TOOL_SPEC_TOOL_NAME, - ) + ProductCollapsedUnlockState::from_messages(messages, collapsed_tools).into_unlocked_tools() +} + +fn get_tool_spec_load_observation(message: &Message) -> Option> { + let MessageContent::ToolResult { + tool_name, + result, + is_error, + .. + } = &message.content + else { + return None; + }; + + Some(GetToolSpecLoadObservation { + tool_name, + loaded_tool_name: result.get("tool_name").and_then(|v| v.as_str()), + is_error: *is_error, + }) } #[cfg(test)] mod tests { - use super::collect_product_unlocked_collapsed_tools; + use super::{collect_product_unlocked_collapsed_tools, ProductCollapsedUnlockState}; use crate::agentic::core::{Message, ToolResult}; use serde_json::json; @@ -180,4 +206,26 @@ mod tests { assert_eq!(unlocked, vec!["Git".to_string(), "WebFetch".to_string()]); } + + #[test] + fn product_collapsed_unlock_state_preserves_message_derived_lifecycle() { + let state = ProductCollapsedUnlockState::from_messages( + &[Message::tool_result(ToolResult { + tool_id: "tool-1".to_string(), + tool_name: "GetToolSpec".to_string(), + result: json!({ + "tool_name": "Git", + }), + result_for_assistant: None, + is_error: false, + duration_ms: Some(1), + image_attachments: None, + })], + &["Git".to_string(), "WebFetch".to_string()], + ); + + assert!(state.is_unlocked("Git")); + assert!(!state.is_unlocked("WebFetch")); + assert_eq!(state.into_unlocked_tools(), vec!["Git".to_string()]); + } } diff --git a/src/crates/core/src/agentic/tools/tool_context_runtime.rs b/src/crates/core/src/agentic/tools/tool_context_runtime.rs index f14c35d23..301dce20a 100644 --- a/src/crates/core/src/agentic/tools/tool_context_runtime.rs +++ b/src/crates/core/src/agentic/tools/tool_context_runtime.rs @@ -32,7 +32,7 @@ use crate::service::remote_ssh::workspace_state::remote_workspace_runtime_root; use crate::service::{get_workspace_runtime_service_arc, WorkspaceRuntimeContext}; use crate::util::errors::{BitFunError, BitFunResult}; use bitfun_agent_tools::{PortableToolContextProvider, ToolContextFacts, ToolWorkspaceKind}; -use bitfun_runtime_ports::DelegationPolicy; +use bitfun_runtime_ports::{DelegationPolicy, ToolRuntimeHandles}; use log::warn; use serde_json::Value; use sha2::{Digest, Sha256}; @@ -54,12 +54,9 @@ pub struct ToolUseContext { pub custom_data: HashMap, /// Desktop automation (Computer use); only set in BitFun desktop. pub computer_use_host: Option, - // Cancel tool execution more timely, especially for tools like TaskTool that need to run for a long time - pub cancellation_token: Option, pub runtime_tool_restrictions: ToolRuntimeRestrictions, - /// Workspace I/O services (filesystem + shell) - use these instead of - /// checking `get_remote_workspace_manager()` inside individual tools. - pub workspace_services: Option, + /// Runtime handles such as workspace I/O services and cancellation. + pub runtime_handles: ToolRuntimeHandles, } impl ToolUseContext { @@ -126,6 +123,32 @@ impl ToolUseContext { .and_then(|v| v.as_bool()) .unwrap_or(true) } + + pub fn cancellation_token(&self) -> Option<&CancellationToken> { + self.runtime_handles.cancellation_token() + } + + pub fn workspace_services(&self) -> Option<&WorkspaceServices> { + self.runtime_handles.workspace_services() + } + + pub fn for_tool_listing( + workspace: Option, + workspace_services: Option, + ) -> Self { + Self { + tool_call_id: None, + agent_type: None, + session_id: None, + dialog_turn_id: None, + workspace, + unlocked_collapsed_tools: Vec::new(), + custom_data: HashMap::new(), + computer_use_host: None, + runtime_tool_restrictions: ToolRuntimeRestrictions::default(), + runtime_handles: ToolRuntimeHandles::new(workspace_services, None), + } + } } impl PortableToolContextProvider for ToolUseContext { @@ -140,7 +163,7 @@ pub(crate) async fn call_with_tool_runtime_hooks( context: &ToolUseContext, call_impl: impl Future>>, ) -> BitFunResult> { - let result = if let Some(cancellation_token) = context.cancellation_token.as_ref() { + let result = if let Some(cancellation_token) = context.cancellation_token() { tokio::select! { result = call_impl => { result @@ -197,9 +220,11 @@ pub(crate) fn build_tool_use_context_for_execution_context( unlocked_collapsed_tools: context.unlocked_collapsed_tools.clone(), custom_data: build_tool_context_custom_data(context), computer_use_host, - cancellation_token: Some(cancellation_token), + runtime_handles: ToolRuntimeHandles::new( + context.workspace_services.clone(), + Some(cancellation_token), + ), runtime_tool_restrictions: context.runtime_tool_restrictions.clone(), - workspace_services: context.workspace_services.clone(), } } @@ -228,9 +253,8 @@ pub(crate) fn build_tool_description_context( unlocked_collapsed_tools: Vec::new(), custom_data, computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), - workspace_services: workspace_services.cloned(), + runtime_handles: ToolRuntimeHandles::new(workspace_services.cloned(), None), } } @@ -295,11 +319,11 @@ fn build_tool_context_custom_data(context: &ToolExecutionContext) -> HashMap Option<&dyn crate::agentic::workspace::WorkspaceFileSystem> { - self.workspace_services.as_ref().map(|s| s.fs.as_ref()) + self.workspace_services().map(|s| s.fs.as_ref()) } pub fn ws_shell(&self) -> Option<&dyn crate::agentic::workspace::WorkspaceShell> { - self.workspace_services.as_ref().map(|s| s.shell.as_ref()) + self.workspace_services().map(|s| s.shell.as_ref()) } pub async fn record_light_checkpoint( @@ -678,9 +702,8 @@ mod context_facts_tests { unlocked_collapsed_tools: Vec::new(), custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), } } @@ -695,14 +718,13 @@ mod context_facts_tests { unlocked_collapsed_tools: vec!["WebFetch".to_string()], custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: ToolRuntimeRestrictions { allowed_tool_names: BTreeSet::from(["Read".to_string()]), denied_tool_names: BTreeSet::from(["Bash".to_string()]), denied_tool_messages: Default::default(), path_policy: Default::default(), }, - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), }; let facts = context.to_tool_context_facts(); @@ -740,14 +762,16 @@ mod context_facts_tests { unlocked_collapsed_tools: vec!["WebFetch".to_string(), "Git".to_string()], custom_data, computer_use_host: None, - cancellation_token: Some(tokio_util::sync::CancellationToken::new()), runtime_tool_restrictions: ToolRuntimeRestrictions { allowed_tool_names: BTreeSet::from(["Read".to_string(), "GetToolSpec".to_string()]), denied_tool_names: BTreeSet::from(["Bash".to_string()]), denied_tool_messages: Default::default(), path_policy: Default::default(), }, - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::new( + None, + Some(tokio_util::sync::CancellationToken::new()), + ), }; let facts = PortableToolContextProvider::tool_context_facts(&context); @@ -799,9 +823,8 @@ mod context_facts_tests { unlocked_collapsed_tools: Vec::new(), custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), }; let facts = context.to_tool_context_facts(); @@ -851,9 +874,8 @@ mod path_resolution_tests { unlocked_collapsed_tools: Vec::new(), custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), } } @@ -875,9 +897,8 @@ mod path_resolution_tests { unlocked_collapsed_tools: Vec::new(), custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), } } @@ -901,9 +922,8 @@ mod path_resolution_tests { unlocked_collapsed_tools: Vec::new(), custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), } } @@ -1107,9 +1127,11 @@ mod call_runtime_tests { unlocked_collapsed_tools: Vec::new(), custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: Some(cancellation_token), runtime_tool_restrictions: ToolRuntimeRestrictions::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::new( + None, + Some(cancellation_token), + ), } } @@ -1141,9 +1163,8 @@ mod call_runtime_tests { unlocked_collapsed_tools: Vec::new(), custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), }; let result: BitFunResult> = @@ -1182,9 +1203,8 @@ mod call_runtime_tests { unlocked_collapsed_tools: Vec::new(), custom_data, computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), }; let tool = MeasurementReadTool; @@ -1230,15 +1250,14 @@ mod context_builder_tests { assert!(context.dialog_turn_id.is_none()); assert!(context.workspace.is_none()); assert!(context.unlocked_collapsed_tools.is_empty()); - assert!(context.cancellation_token.is_none()); - assert!(context.workspace_services.is_none()); + assert!(context.cancellation_token().is_none()); + assert!(context.workspace_services().is_none()); assert!(context.runtime_tool_restrictions.is_tool_allowed("Write")); assert_eq!( context.custom_data["primary_model_supports_image_understanding"], json!("false") ); } - } #[cfg(test)] @@ -1325,7 +1344,7 @@ mod task_context_tests { assert_eq!(context.session_id.as_deref(), Some("session_1")); assert_eq!(context.dialog_turn_id.as_deref(), Some("turn_1")); assert_eq!(context.unlocked_collapsed_tools, vec!["WebFetch"]); - assert!(context.cancellation_token.is_some()); + assert!(context.cancellation_token().is_some()); assert!(context .runtime_tool_restrictions .is_tool_allowed("WebFetch")); diff --git a/src/crates/core/src/agentic/tools/tool_result_storage.rs b/src/crates/core/src/agentic/tools/tool_result_storage.rs index dfeb5563d..dc51850a3 100644 --- a/src/crates/core/src/agentic/tools/tool_result_storage.rs +++ b/src/crates/core/src/agentic/tools/tool_result_storage.rs @@ -359,9 +359,8 @@ mod tests { unlocked_collapsed_tools: Vec::new(), custom_data: HashMap::new(), computer_use_host: None, - cancellation_token: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), - workspace_services: None, + runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), } } diff --git a/src/crates/core/src/miniapp/builtin/mod.rs b/src/crates/core/src/miniapp/builtin/mod.rs index 53dc8de86..d2b280c1f 100644 --- a/src/crates/core/src/miniapp/builtin/mod.rs +++ b/src/crates/core/src/miniapp/builtin/mod.rs @@ -11,70 +11,16 @@ use bitfun_product_domains::miniapp::builtin::{ build_builtin_package_json, build_builtin_seed_meta, builtin_source_files, parse_builtin_install_marker, preserved_builtin_created_at, resolve_builtin_seed_action, resolve_builtin_seed_check, serialize_builtin_install_marker, BuiltinInstallMarker, - BuiltinMiniAppBundle, BuiltinSeedAction, BuiltinSeedCheck, BUILTIN_INSTALL_MARKER, - BUILTIN_PLACEHOLDER_COMPILED_HTML, LEGACY_BUILTIN_VERSION_MARKER, + BuiltinSeedAction, BuiltinSeedCheck, BUILTIN_INSTALL_MARKER, BUILTIN_PLACEHOLDER_COMPILED_HTML, + LEGACY_BUILTIN_VERSION_MARKER, +}; +pub use bitfun_product_domains::miniapp::builtin::{ + BuiltinMiniAppBundle as BuiltinApp, BUILTIN_APPS, }; use chrono::Utc; use std::path::Path; use std::sync::Arc; -/// A built-in MiniApp bundled with the application binary. -pub type BuiltinApp = BuiltinMiniAppBundle; - -/// All built-in apps that ship with BitFun. -pub const BUILTIN_APPS: &[BuiltinApp] = &[ - BuiltinApp { - id: "builtin-gomoku", - version: 11, - meta_json: include_str!("assets/gomoku/meta.json"), - html: include_str!("assets/gomoku/index.html"), - css: include_str!("assets/gomoku/style.css"), - ui_js: include_str!("assets/gomoku/ui.js"), - worker_js: include_str!("assets/gomoku/worker.js"), - esm_dependencies_json: "[]", - }, - BuiltinApp { - id: "builtin-daily-divination", - version: 21, - meta_json: include_str!("assets/divination/meta.json"), - html: include_str!("assets/divination/index.html"), - css: include_str!("assets/divination/style.css"), - ui_js: include_str!("assets/divination/ui.js"), - worker_js: include_str!("assets/divination/worker.js"), - esm_dependencies_json: "[]", - }, - BuiltinApp { - id: "builtin-regex-playground", - version: 16, - meta_json: include_str!("assets/regex-playground/meta.json"), - html: include_str!("assets/regex-playground/index.html"), - css: include_str!("assets/regex-playground/style.css"), - ui_js: include_str!("assets/regex-playground/ui.js"), - worker_js: include_str!("assets/regex-playground/worker.js"), - esm_dependencies_json: "[]", - }, - BuiltinApp { - id: "builtin-coding-selfie", - version: 28, - meta_json: include_str!("assets/coding-selfie/meta.json"), - html: include_str!("assets/coding-selfie/index.html"), - css: include_str!("assets/coding-selfie/style.css"), - ui_js: include_str!("assets/coding-selfie/ui.js"), - worker_js: include_str!("assets/coding-selfie/worker.js"), - esm_dependencies_json: "[]", - }, - BuiltinApp { - id: "builtin-pr-review", - version: 3, - meta_json: include_str!("assets/pr-review/meta.json"), - html: include_str!("assets/pr-review/index.html"), - css: include_str!("assets/pr-review/style.css"), - ui_js: include_str!("assets/pr-review/ui.js"), - worker_js: include_str!("assets/pr-review/worker.js"), - esm_dependencies_json: "[]", - }, -]; - /// Seed all built-in MiniApps into the user data directory. Idempotent: skips apps /// whose on-disk marker hash matches the bundled content. User's `storage.json` /// is preserved across reseeds; source files & meta.json (without timestamps) are diff --git a/src/crates/events/AGENTS.md b/src/crates/events/AGENTS.md new file mode 100644 index 000000000..f01fc2578 --- /dev/null +++ b/src/crates/events/AGENTS.md @@ -0,0 +1,27 @@ +# events Agent Guide + +Scope: this guide applies to `src/crates/events`. + +`bitfun-events` owns platform-neutral event contracts and the emitter interface. +It describes events; it does not own event delivery or product decisions. + +## Guardrails + +- Keep event payloads serializable, stable, and platform-neutral. +- Do not depend on app crates, Tauri, transport adapters, concrete services, or + UI command logic. +- Preserve event names, payload fields, priority semantics, and ordering + assumptions when refactoring. +- Use shared DTOs from `bitfun-core-types` when event payloads need stable + identifiers or portable facts. +- Delivery, persistence, throttling, subscription routing, and remote sync + behavior belong in transport, services, or app adapters. + +## Verification + +```bash +cargo check -p bitfun-events +node scripts/check-core-boundaries.mjs +``` + +For documentation-only changes, run `git diff --check`. diff --git a/src/crates/product-domains/AGENTS-CN.md b/src/crates/product-domains/AGENTS-CN.md index b59369212..244cc7d9f 100644 --- a/src/crates/product-domains/AGENTS-CN.md +++ b/src/crates/product-domains/AGENTS-CN.md @@ -4,48 +4,26 @@ 适用范围:`src/crates/product-domains`。 -`bitfun-product-domains` 负责可以脱离完整 core runtime 编译的低风险产品领域契约。 -这里的抽取必须保持行为等价与平台无关;在所有下游调用点被有意迁移前, -`bitfun-core` 可以继续保留兼容 re-export 或 wrapper facade。 +`bitfun-product-domains` 承载可脱离完整 core runtime 编译的平台无关产品领域契约。这里应聚焦纯状态、DTO、策略和窄 +ports;具体 runtime 行为不属于本 crate。 ## 护栏 - 不要让 `bitfun-product-domains` 依赖 `bitfun-core`。 -- 保持 default feature 轻量。默认构建不应引入 runtime、service、desktop、 - network、process、AI 或 tool-runtime 依赖。 -- 本 crate 可以承载纯 DTO、枚举、序列化契约、搜索计划、命令选择决策、 - host-routing string rule、storage-shape parser、小型 helper,以及只依赖 `std` 或窄 feature 轻量依赖的 - 文件形态分析器。 -- 本 crate 可以定义面向后续 runtime 迁移的产品领域 port trait,但真正执行 IO、 - 进程、AI 调用、Git service 调用或平台集成的 concrete adapter 仍不能放进这里。 -- 不要在没有明确评审、port/provider 设计和等价性测试的情况下,把 runtime - 执行、文件系统写入、shell/network 行为、config/path manager、AI client、 - Git service 行为、tool manifest、`ToolUseContext`、tool exposure 或 - desktop/Tauri adapter 移到这里。 -- 在下游调用点被有意迁移前,用 re-export 或 wrapper facade 保持既有 core - import path。 -- 新增 feature-gated 依赖必须保持窄边界。`miniapp` 只放 MiniApp 专属依赖, - `function-agents` 只放 function-agent 专属依赖,`product-full` 只聚合已有 - 产品领域 feature 组。 - -## 当前归属 - -- `miniapp` 拥有 MiniApp DTO、compiler/bridge helper、storage/draft/import - 文件形态、fallback payload、runtime search plan、worker install 命令选择、 - lifecycle/revision 与 manager state-transition helper、host-routing string - policy、customization metadata policy、built-in update/decline 决策、 - built-in bundle/hash/marker seed plan 与 marker wire helper、built-in - source/placeholder payload contract、port trait,以及 storage-backed runtime - state facade。 -- `function-agents` 拥有纯 DTO、prompt template 与 assembly、commit prompt - preparation、AI response JSON extraction 与 domain error mapping policy、 - diff truncation policy、JSON string 到领域 DTO 的解析 helper、本地文件形态分析、 - Git/AI port trait,以及 port-backed runtime facade orchestration。 -- Core 仍拥有 MiniApp filesystem IO、worker process、host dispatch、built-in - asset include/seeding、marker IO、recompile orchestration、source-hash lookup、 - `PathManager` 集成、function-agent Git/AI service adapter、AI client 调用、 - provider acquisition 和 AI transport error mapping;core 侧 product-domain - runtime 绑定集中在 `src/crates/core/src/product_domain_runtime.rs`。 +- 保持 default feature 轻量。默认构建不得引入 runtime、service、desktop、network、process、AI 或 tool-runtime 依赖。 +- 本 crate 可以承载纯 DTO、枚举、序列化契约、搜索计划、命令选择决策、storage-shape parser、领域策略和产品领域 port trait。 +- 真正执行 IO、进程、AI 调用、Git service 调用、平台集成、tool exposure 或 desktop/Tauri 工作的 concrete adapter 属于本 crate 外部。 +- 在下游调用点被有意迁移前,用 re-export 或 wrapper facade 保持既有 core import path。 +- 新增 feature-gated 内容必须保持窄边界。`miniapp`、`function-agents` 和 `product-full` 只应启用已声明的产品领域 feature 组。 + +## 归属边界 + +- `miniapp` 可以拥有 MiniApp 数据形态、纯生命周期决策、metadata/import policy、built-in bundle identity、embedded source assets、 + seed-plan facts、marker wire format 和窄 port。 +- `function-agents` 可以拥有 function-agent DTO、prompt/domain policy、response parsing/repair rule、file-shape analysis + 和 Git/AI port trait。 +- Core 仍拥有 filesystem writes、marker IO、worker/host execution、compile orchestration、`PathManager` integration、 + concrete Git/AI service、provider acquisition 和 transport error mapping。 ## 验证 @@ -58,4 +36,4 @@ node scripts/check-core-boundaries.mjs cargo check -p bitfun-core --features product-full ``` -仅改文档时,也运行 `git diff --check`。 +仅改文档时运行 `git diff --check`。 diff --git a/src/crates/product-domains/AGENTS.md b/src/crates/product-domains/AGENTS.md index 58830cb0e..7e03bd0fe 100644 --- a/src/crates/product-domains/AGENTS.md +++ b/src/crates/product-domains/AGENTS.md @@ -4,59 +4,36 @@ Scope: this guide applies to `src/crates/product-domains`. -`bitfun-product-domains` owns low-risk product-domain contracts that can compile -without the full core runtime. Keep this crate behavior-preserving and -platform-agnostic; `bitfun-core` may keep compatibility facades while ownership -moves here gradually. +`bitfun-product-domains` owns platform-agnostic product-domain contracts that can +compile without the full core runtime. Keep it focused on pure state, DTOs, +policies, and narrow ports; concrete runtime behavior belongs outside this crate. ## Guardrails - Do not add a dependency from `bitfun-product-domains` to `bitfun-core`. -- Keep the default feature lightweight. Default builds should not pull runtime, +- Keep the default feature lightweight. Default builds must not pull runtime, service, desktop, network, process, AI, or tool-runtime dependencies. - This crate may own pure DTOs, enums, serialization contracts, search plans, - command-selection decisions, host-routing string rules, storage-shape parsers, - draft/metadata response shapes, small helpers, and file-shape analyzers that - use `std` or feature-gated lightweight dependencies only. -- This crate may define product-domain port traits for future runtime migration, - but concrete adapters that perform IO, process execution, AI calls, Git - service calls, or platform integration still belong outside this crate. -- Do not move runtime execution, filesystem writes, shell/network behavior, - config/path managers, AI clients, Git service behavior, tool manifests, - `ToolUseContext`, tool exposure, or desktop/Tauri adapters here without an - explicit review, a port/provider design, and equivalence tests. + command-selection decisions, storage-shape parsers, domain policies, and + product-domain port traits. +- Concrete adapters that perform IO, process execution, AI calls, Git service + calls, platform integration, tool exposure, or desktop/Tauri work belong + outside this crate. - Preserve existing core import paths with re-export or wrapper facades until downstream call sites are intentionally migrated. -- Feature-gated additions must remain narrow. `miniapp` may use MiniApp-only - dependencies, `function-agents` may use function-agent-only dependencies, and - `product-full` should only aggregate existing product-domain feature groups. - Boundary checks enforce the default-light profile and current `product-full` - feature-group list. +- Feature-gated additions must remain narrow. `miniapp`, `function-agents`, and + `product-full` should only enable their declared product-domain feature groups. -## Current owners +## Ownership Boundary -- `miniapp` owns MiniApp DTOs, compiler/bridge helpers, storage/draft/import - file shapes, import fallback payloads, runtime search-plan helpers, worker - install command selection, lifecycle/revision and manager state-transition - helpers including create/update/draft/apply/import state construction, - imported metadata identity/timestamp stamping, host-routing string policy, - customization metadata policy including built-in - update/decline decisions, built-in bundle/hash/marker seed plan, seed meta - timestamp policy, marker wire helpers, built-in source/placeholder payload - contracts, port traits, and storage-backed runtime state facade logic - including import runtime-state persistence. -- `function-agents` owns pure function-agent DTOs, prompt templates and - assembly helpers, commit prompt preparation, AI-response JSON extraction and - domain error-mapping policy, diff truncation policy, JSON-string-to-domain - parsing helpers, local file-shape analysis, Git/AI port traits, and - port-backed runtime facade orchestration, including the commit-message and - Startchat work-state facades used by core adapters. -- Core still owns MiniApp filesystem IO, worker process execution, host dispatch - execution, built-in asset includes/seeding, marker IO, recompile orchestration, - source-hash input lookup, `PathManager` integration, function-agent Git/AI - service adapters, AI client calls, provider acquisition, and AI transport - error mapping. Core product-domain runtime bindings are centralized in - `src/crates/core/src/product_domain_runtime.rs`. +- `miniapp` may own MiniApp data shapes, pure lifecycle decisions, metadata and + import policies, built-in bundle identity, embedded source assets, seed-plan + facts, marker wire formats, and narrow ports. +- `function-agents` may own function-agent DTOs, prompt/domain policies, + response parsing and repair rules, file-shape analysis, and Git/AI port traits. +- Core still owns filesystem writes, marker IO, worker/host execution, compile + orchestration, `PathManager` integration, concrete Git/AI services, provider + acquisition, and transport error mapping. ## Verification @@ -69,4 +46,4 @@ node scripts/check-core-boundaries.mjs cargo check -p bitfun-core --features product-full ``` -For documentation-only changes, also run `git diff --check`. +For documentation-only changes, run `git diff --check`. diff --git a/src/crates/product-domains/src/miniapp/builtin.rs b/src/crates/product-domains/src/miniapp/builtin.rs index 4bdf62afc..4fdde1364 100644 --- a/src/crates/product-domains/src/miniapp/builtin.rs +++ b/src/crates/product-domains/src/miniapp/builtin.rs @@ -38,7 +38,7 @@ pub enum BuiltinSeedAction { } /// Pure built-in MiniApp asset bundle shape. The owning runtime still decides -/// how assets are embedded, seeded, compiled, and persisted. +/// how bundles are seeded, compiled, and persisted. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct BuiltinMiniAppBundle { pub id: &'static str, @@ -51,6 +51,63 @@ pub struct BuiltinMiniAppBundle { pub esm_dependencies_json: &'static str, } +/// Built-in MiniApps that ship with the product-domain package. +/// +/// The concrete seeding runtime still lives in the app/core integration layer; +/// this list owns only the stable bundle identity and embedded source assets. +pub const BUILTIN_APPS: &[BuiltinMiniAppBundle] = &[ + BuiltinMiniAppBundle { + id: "builtin-gomoku", + version: 11, + meta_json: include_str!("builtin/assets/gomoku/meta.json"), + html: include_str!("builtin/assets/gomoku/index.html"), + css: include_str!("builtin/assets/gomoku/style.css"), + ui_js: include_str!("builtin/assets/gomoku/ui.js"), + worker_js: include_str!("builtin/assets/gomoku/worker.js"), + esm_dependencies_json: "[]", + }, + BuiltinMiniAppBundle { + id: "builtin-daily-divination", + version: 21, + meta_json: include_str!("builtin/assets/divination/meta.json"), + html: include_str!("builtin/assets/divination/index.html"), + css: include_str!("builtin/assets/divination/style.css"), + ui_js: include_str!("builtin/assets/divination/ui.js"), + worker_js: include_str!("builtin/assets/divination/worker.js"), + esm_dependencies_json: "[]", + }, + BuiltinMiniAppBundle { + id: "builtin-regex-playground", + version: 16, + meta_json: include_str!("builtin/assets/regex-playground/meta.json"), + html: include_str!("builtin/assets/regex-playground/index.html"), + css: include_str!("builtin/assets/regex-playground/style.css"), + ui_js: include_str!("builtin/assets/regex-playground/ui.js"), + worker_js: include_str!("builtin/assets/regex-playground/worker.js"), + esm_dependencies_json: "[]", + }, + BuiltinMiniAppBundle { + id: "builtin-coding-selfie", + version: 28, + meta_json: include_str!("builtin/assets/coding-selfie/meta.json"), + html: include_str!("builtin/assets/coding-selfie/index.html"), + css: include_str!("builtin/assets/coding-selfie/style.css"), + ui_js: include_str!("builtin/assets/coding-selfie/ui.js"), + worker_js: include_str!("builtin/assets/coding-selfie/worker.js"), + esm_dependencies_json: "[]", + }, + BuiltinMiniAppBundle { + id: "builtin-pr-review", + version: 3, + meta_json: include_str!("builtin/assets/pr-review/meta.json"), + html: include_str!("builtin/assets/pr-review/index.html"), + css: include_str!("builtin/assets/pr-review/style.css"), + ui_js: include_str!("builtin/assets/pr-review/ui.js"), + worker_js: include_str!("builtin/assets/pr-review/worker.js"), + esm_dependencies_json: "[]", + }, +]; + pub fn builtin_content_hash(app: &BuiltinMiniAppBundle) -> String { let mut hasher = Sha256::new(); hash_builtin_asset(&mut hasher, "meta.json", app.meta_json); @@ -182,3 +239,38 @@ fn hex_encode(bytes: &[u8]) -> String { } output } + +#[cfg(test)] +mod tests { + use super::{builtin_content_hash, BUILTIN_APPS}; + + #[test] + fn builtin_miniapp_bundles_keep_product_domain_asset_owner_contract() { + let ids = BUILTIN_APPS.iter().map(|app| app.id).collect::>(); + + assert_eq!( + ids, + vec![ + "builtin-gomoku", + "builtin-daily-divination", + "builtin-regex-playground", + "builtin-coding-selfie", + "builtin-pr-review", + ] + ); + assert_eq!(BUILTIN_APPS[0].version, 11); + assert_eq!(BUILTIN_APPS[1].version, 21); + assert_eq!(BUILTIN_APPS[2].version, 16); + assert_eq!(BUILTIN_APPS[3].version, 28); + assert_eq!(BUILTIN_APPS[4].version, 3); + + for app in BUILTIN_APPS { + assert!(!app.meta_json.trim().is_empty()); + assert!(!app.html.trim().is_empty()); + assert!(!app.css.trim().is_empty()); + assert!(!app.ui_js.trim().is_empty()); + assert!(!app.worker_js.trim().is_empty()); + assert!(builtin_content_hash(app).starts_with("sha256:")); + } + } +} diff --git a/src/crates/core/src/miniapp/builtin/assets/coding-selfie/index.html b/src/crates/product-domains/src/miniapp/builtin/assets/coding-selfie/index.html similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/coding-selfie/index.html rename to src/crates/product-domains/src/miniapp/builtin/assets/coding-selfie/index.html diff --git a/src/crates/core/src/miniapp/builtin/assets/coding-selfie/meta.json b/src/crates/product-domains/src/miniapp/builtin/assets/coding-selfie/meta.json similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/coding-selfie/meta.json rename to src/crates/product-domains/src/miniapp/builtin/assets/coding-selfie/meta.json diff --git a/src/crates/core/src/miniapp/builtin/assets/coding-selfie/style.css b/src/crates/product-domains/src/miniapp/builtin/assets/coding-selfie/style.css similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/coding-selfie/style.css rename to src/crates/product-domains/src/miniapp/builtin/assets/coding-selfie/style.css diff --git a/src/crates/core/src/miniapp/builtin/assets/coding-selfie/ui.js b/src/crates/product-domains/src/miniapp/builtin/assets/coding-selfie/ui.js similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/coding-selfie/ui.js rename to src/crates/product-domains/src/miniapp/builtin/assets/coding-selfie/ui.js diff --git a/src/crates/core/src/miniapp/builtin/assets/coding-selfie/worker.js b/src/crates/product-domains/src/miniapp/builtin/assets/coding-selfie/worker.js similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/coding-selfie/worker.js rename to src/crates/product-domains/src/miniapp/builtin/assets/coding-selfie/worker.js diff --git a/src/crates/core/src/miniapp/builtin/assets/divination/index.html b/src/crates/product-domains/src/miniapp/builtin/assets/divination/index.html similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/divination/index.html rename to src/crates/product-domains/src/miniapp/builtin/assets/divination/index.html diff --git a/src/crates/core/src/miniapp/builtin/assets/divination/meta.json b/src/crates/product-domains/src/miniapp/builtin/assets/divination/meta.json similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/divination/meta.json rename to src/crates/product-domains/src/miniapp/builtin/assets/divination/meta.json diff --git a/src/crates/core/src/miniapp/builtin/assets/divination/style.css b/src/crates/product-domains/src/miniapp/builtin/assets/divination/style.css similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/divination/style.css rename to src/crates/product-domains/src/miniapp/builtin/assets/divination/style.css diff --git a/src/crates/core/src/miniapp/builtin/assets/divination/ui.js b/src/crates/product-domains/src/miniapp/builtin/assets/divination/ui.js similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/divination/ui.js rename to src/crates/product-domains/src/miniapp/builtin/assets/divination/ui.js diff --git a/src/crates/core/src/miniapp/builtin/assets/divination/worker.js b/src/crates/product-domains/src/miniapp/builtin/assets/divination/worker.js similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/divination/worker.js rename to src/crates/product-domains/src/miniapp/builtin/assets/divination/worker.js diff --git a/src/crates/core/src/miniapp/builtin/assets/gomoku/index.html b/src/crates/product-domains/src/miniapp/builtin/assets/gomoku/index.html similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/gomoku/index.html rename to src/crates/product-domains/src/miniapp/builtin/assets/gomoku/index.html diff --git a/src/crates/core/src/miniapp/builtin/assets/gomoku/meta.json b/src/crates/product-domains/src/miniapp/builtin/assets/gomoku/meta.json similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/gomoku/meta.json rename to src/crates/product-domains/src/miniapp/builtin/assets/gomoku/meta.json diff --git a/src/crates/core/src/miniapp/builtin/assets/gomoku/style.css b/src/crates/product-domains/src/miniapp/builtin/assets/gomoku/style.css similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/gomoku/style.css rename to src/crates/product-domains/src/miniapp/builtin/assets/gomoku/style.css diff --git a/src/crates/core/src/miniapp/builtin/assets/gomoku/ui.js b/src/crates/product-domains/src/miniapp/builtin/assets/gomoku/ui.js similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/gomoku/ui.js rename to src/crates/product-domains/src/miniapp/builtin/assets/gomoku/ui.js diff --git a/src/crates/core/src/miniapp/builtin/assets/gomoku/worker.js b/src/crates/product-domains/src/miniapp/builtin/assets/gomoku/worker.js similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/gomoku/worker.js rename to src/crates/product-domains/src/miniapp/builtin/assets/gomoku/worker.js diff --git a/src/crates/core/src/miniapp/builtin/assets/pr-review/index.html b/src/crates/product-domains/src/miniapp/builtin/assets/pr-review/index.html similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/pr-review/index.html rename to src/crates/product-domains/src/miniapp/builtin/assets/pr-review/index.html diff --git a/src/crates/core/src/miniapp/builtin/assets/pr-review/meta.json b/src/crates/product-domains/src/miniapp/builtin/assets/pr-review/meta.json similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/pr-review/meta.json rename to src/crates/product-domains/src/miniapp/builtin/assets/pr-review/meta.json diff --git a/src/crates/core/src/miniapp/builtin/assets/pr-review/style.css b/src/crates/product-domains/src/miniapp/builtin/assets/pr-review/style.css similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/pr-review/style.css rename to src/crates/product-domains/src/miniapp/builtin/assets/pr-review/style.css diff --git a/src/crates/core/src/miniapp/builtin/assets/pr-review/ui.js b/src/crates/product-domains/src/miniapp/builtin/assets/pr-review/ui.js similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/pr-review/ui.js rename to src/crates/product-domains/src/miniapp/builtin/assets/pr-review/ui.js diff --git a/src/crates/core/src/miniapp/builtin/assets/pr-review/worker.js b/src/crates/product-domains/src/miniapp/builtin/assets/pr-review/worker.js similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/pr-review/worker.js rename to src/crates/product-domains/src/miniapp/builtin/assets/pr-review/worker.js diff --git a/src/crates/core/src/miniapp/builtin/assets/regex-playground/index.html b/src/crates/product-domains/src/miniapp/builtin/assets/regex-playground/index.html similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/regex-playground/index.html rename to src/crates/product-domains/src/miniapp/builtin/assets/regex-playground/index.html diff --git a/src/crates/core/src/miniapp/builtin/assets/regex-playground/meta.json b/src/crates/product-domains/src/miniapp/builtin/assets/regex-playground/meta.json similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/regex-playground/meta.json rename to src/crates/product-domains/src/miniapp/builtin/assets/regex-playground/meta.json diff --git a/src/crates/core/src/miniapp/builtin/assets/regex-playground/style.css b/src/crates/product-domains/src/miniapp/builtin/assets/regex-playground/style.css similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/regex-playground/style.css rename to src/crates/product-domains/src/miniapp/builtin/assets/regex-playground/style.css diff --git a/src/crates/core/src/miniapp/builtin/assets/regex-playground/ui.js b/src/crates/product-domains/src/miniapp/builtin/assets/regex-playground/ui.js similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/regex-playground/ui.js rename to src/crates/product-domains/src/miniapp/builtin/assets/regex-playground/ui.js diff --git a/src/crates/core/src/miniapp/builtin/assets/regex-playground/worker.js b/src/crates/product-domains/src/miniapp/builtin/assets/regex-playground/worker.js similarity index 100% rename from src/crates/core/src/miniapp/builtin/assets/regex-playground/worker.js rename to src/crates/product-domains/src/miniapp/builtin/assets/regex-playground/worker.js diff --git a/src/crates/runtime-ports/AGENTS.md b/src/crates/runtime-ports/AGENTS.md new file mode 100644 index 000000000..80fcc25fb --- /dev/null +++ b/src/crates/runtime-ports/AGENTS.md @@ -0,0 +1,27 @@ +# runtime-ports Agent Guide + +Scope: this guide applies to `src/crates/runtime-ports`. + +`bitfun-runtime-ports` owns stable runtime-facing ports, DTOs, and capability +facts. It is an interface crate, not a runtime implementation crate. + +## Guardrails + +- Do not depend on `bitfun-core`, app crates, Tauri, concrete service crates, + AI adapters, transport adapters, or tool implementations. +- Keep ports narrow and typed. Avoid untyped service locators, global registries, + or catch-all context structs. +- This crate may define portable request/response DTOs, runtime handles, + capability facts, cancellation surfaces, and service traits. +- Do not put filesystem writes, process execution, network clients, Git/AI/MCP + concrete behavior, product policy, or UI command logic here. +- Preserve serialization compatibility for persisted or cross-process DTOs. + +## Verification + +```bash +cargo test -p bitfun-runtime-ports +node scripts/check-core-boundaries.mjs +``` + +For documentation-only changes, run `git diff --check`. diff --git a/src/crates/runtime-ports/src/lib.rs b/src/crates/runtime-ports/src/lib.rs index 4b3ef6791..8495b6ca8 100644 --- a/src/crates/runtime-ports/src/lib.rs +++ b/src/crates/runtime-ports/src/lib.rs @@ -232,6 +232,58 @@ impl std::fmt::Debug for WorkspaceServices { } } +/// Runtime handles injected into tool execution contexts. +/// +/// This bundle is intentionally handle-only. Concrete local or remote +/// implementations are still assembled by product/runtime owners outside this +/// crate. +#[derive(Clone, Default)] +pub struct ToolRuntimeHandles { + workspace_services: Option, + cancellation_token: Option, +} + +impl ToolRuntimeHandles { + pub fn new( + workspace_services: Option, + cancellation_token: Option, + ) -> Self { + Self { + workspace_services, + cancellation_token, + } + } + + pub fn workspace_services(&self) -> Option<&WorkspaceServices> { + self.workspace_services.as_ref() + } + + pub fn cancellation_token(&self) -> Option<&CancellationToken> { + self.cancellation_token.as_ref() + } +} + +impl std::fmt::Debug for ToolRuntimeHandles { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ToolRuntimeHandles") + .field( + "workspace_services", + &self + .workspace_services + .as_ref() + .map(|_| ""), + ) + .field( + "cancellation_token", + &self + .cancellation_token + .as_ref() + .map(|_| ""), + ) + .finish() + } +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionRequest { @@ -1752,4 +1804,37 @@ mod tests { "WorkspaceServices { fs: \"\", shell: \"\" }" ); } + + #[test] + fn tool_runtime_handles_keep_workspace_services_and_cancellation_contracts() { + let cancellation_token = tokio_util::sync::CancellationToken::new(); + let services = WorkspaceServices { + fs: std::sync::Arc::new(FakeWorkspaceFileSystem), + shell: std::sync::Arc::new(FakeWorkspaceShell), + }; + + let handles = + ToolRuntimeHandles::new(Some(services.clone()), Some(cancellation_token.clone())); + + assert!(handles.cancellation_token().is_some()); + assert!(handles.workspace_services().is_some()); + assert!(std::sync::Arc::ptr_eq( + &services.fs, + &handles.workspace_services().expect("workspace services").fs + )); + + let cloned = handles.clone(); + assert!(cloned.cancellation_token().is_some()); + assert!(std::sync::Arc::ptr_eq( + &services.shell, + &cloned + .workspace_services() + .expect("workspace services") + .shell + )); + assert_eq!( + format!("{:?}", handles), + "ToolRuntimeHandles { workspace_services: Some(\"\"), cancellation_token: Some(\"\") }" + ); + } } diff --git a/src/crates/runtime-services/AGENTS.md b/src/crates/runtime-services/AGENTS.md new file mode 100644 index 000000000..88b398f92 --- /dev/null +++ b/src/crates/runtime-services/AGENTS.md @@ -0,0 +1,28 @@ +# runtime-services Agent Guide + +Scope: this guide applies to `src/crates/runtime-services`. + +`bitfun-runtime-services` owns typed runtime service assembly. It connects +runtime-facing ports to injected providers without becoming a concrete platform +implementation layer. + +## Guardrails + +- Depend on `bitfun-runtime-ports`; avoid dependencies on `bitfun-core`, app + crates, Tauri, concrete desktop adapters, or product UI. +- Builders should assemble explicit typed service bundles and capability + availability. Do not introduce untyped maps, global mutable registries, or + implicit service lookup. +- Fake/test providers may live here only when they protect port behavior without + pulling product runtime dependencies. +- Concrete filesystem, terminal, network, Git, MCP, remote, or AI provider + implementations belong in service/integration owner crates or app adapters. + +## Verification + +```bash +cargo test -p bitfun-runtime-services +node scripts/check-core-boundaries.mjs +``` + +For documentation-only changes, run `git diff --check`. diff --git a/src/crates/services-integrations/AGENTS.md b/src/crates/services-integrations/AGENTS.md index 65e1c5841..0c03cc2af 100644 --- a/src/crates/services-integrations/AGENTS.md +++ b/src/crates/services-integrations/AGENTS.md @@ -15,23 +15,20 @@ slices that are outside pure product logic but still platform-neutral. integration feature-group list. - MCP config/process/transport lifecycle and dynamic provider helpers may live 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, 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`. + execution, and concrete tool behavior remain outside this crate unless a + reviewed owner move proves behavior equivalence. +- Remote-connect contracts, dialog/cancel orchestration ports, image-context + adapter contracts, remote workspace helpers, and command/response assembly + may live here when they stay platform-neutral. +- Remote workspace facts, session metadata, file projection DTOs, and + workspace/projection host traits belong in `bitfun-runtime-ports`. +- Workspace-root source selection, persistence/workspace service reads, + concrete scheduler/session restore, terminal pre-warm adapters, and product + execution remain core-owned unless a reviewed port/provider moves them with + equivalence tests. - Remote-SSH path/session identity helpers may live here; SSH channels, SFTP, remote FS, remote terminal, and manager assembly remain core-owned unless a - later reviewed port/provider migration proves equivalence. + reviewed port/provider move proves equivalence. ## Verification diff --git a/src/crates/terminal/AGENTS.md b/src/crates/terminal/AGENTS.md new file mode 100644 index 000000000..7e3b85db4 --- /dev/null +++ b/src/crates/terminal/AGENTS.md @@ -0,0 +1,28 @@ +# terminal Agent Guide + +Scope: this guide applies to `src/crates/terminal`. + +`terminal-core` owns standalone terminal sessions, PTY process handling, shell +integration, and terminal event/config contracts. It is reusable infrastructure, +not a product command or UI layer. + +## Guardrails + +- Do not depend on `bitfun-core`, app crates, Tauri, product domains, AI + providers, Git, MCP, transport adapters, or tool-runtime implementations. +- Keep platform-specific behavior behind terminal abstractions and preserve + Windows, macOS, and Linux shell compatibility. +- Do not change command execution, PTY lifecycle, persistence, output + buffering, cancellation, or shell integration semantics as a side effect of + refactoring. +- Product-specific terminal policies, remote workspace routing, and UI command + wiring belong in higher layers. + +## Verification + +```bash +cargo check -p terminal-core +node scripts/check-core-boundaries.mjs +``` + +For documentation-only changes, run `git diff --check`. diff --git a/src/crates/tool-packs/AGENTS.md b/src/crates/tool-packs/AGENTS.md index 7b50bb114..8b3226126 100644 --- a/src/crates/tool-packs/AGENTS.md +++ b/src/crates/tool-packs/AGENTS.md @@ -11,8 +11,8 @@ tool provider group plan. It does not own concrete tool implementations yet. silently enable new runtime behavior. Boundary checks enforce the current feature-group list. - Do not depend on `bitfun-core`, concrete service crates, app crates, Tauri, - Git, MCP, network clients, or CLI UI dependencies unless H1 explicitly moves a - reviewed tool runtime owner here. + Git, MCP, network clients, or CLI UI dependencies unless a reviewed tool + runtime owner move explicitly changes this boundary. - Do not own manifest/exposure contracts, concrete runtime manifest assembly, `GetToolSpec` execution, collapsed unlock state, snapshot decoration, or `ToolUseContext`. Provider group plans may list group ids and tool names only. diff --git a/src/crates/tool-runtime/AGENTS.md b/src/crates/tool-runtime/AGENTS.md new file mode 100644 index 000000000..790545be2 --- /dev/null +++ b/src/crates/tool-runtime/AGENTS.md @@ -0,0 +1,29 @@ +# tool-runtime Agent Guide + +Scope: this guide applies to `src/crates/tool-runtime`. + +`tool-runtime` owns low-level reusable tool execution helpers such as filesystem +and search utilities. It is not the product tool registry, permission model, or +agent-facing tool surface. + +## Guardrails + +- Do not depend on `bitfun-core`, app crates, Tauri, product-domain crates, + transport adapters, or AI providers. +- Keep this crate focused on reusable execution primitives and pure utilities. + Product-specific tool exposure, prompt-visible manifests, `GetToolSpec`, + collapsed unlock state, and `ToolUseContext` stay outside this crate. +- Preserve existing filesystem/search behavior when moving helpers here. Do not + change path containment, encoding, cancellation, or result presentation + semantics as a side effect of refactoring. +- Provider-neutral contracts belong in `bitfun-agent-tools`; product provider + grouping belongs in `bitfun-tool-packs`. + +## Verification + +```bash +cargo test -p tool-runtime +node scripts/check-core-boundaries.mjs +``` + +For documentation-only changes, run `git diff --check`. diff --git a/src/crates/transport/AGENTS.md b/src/crates/transport/AGENTS.md new file mode 100644 index 000000000..1bafbc16c --- /dev/null +++ b/src/crates/transport/AGENTS.md @@ -0,0 +1,27 @@ +# transport Agent Guide + +Scope: this guide applies to `src/crates/transport`. + +`bitfun-transport` owns cross-platform communication contracts and adapters. It +bridges product/API events to concrete delivery channels without owning product +logic. + +## Guardrails + +- Do not depend on `bitfun-core`, API handlers, app crates, product domains, + concrete services, AI providers, terminal, or tool-runtime implementations. +- Keep adapter features explicit. Tauri, CLI, and websocket adapters must remain + feature-gated and must not change the default build surface. +- Transport may serialize and deliver events; it must not decide product policy, + session lifecycle, tool exposure, permissions, or remote workspace behavior. +- Preserve event names, payload compatibility, ordering assumptions, and + backpressure/error semantics when refactoring adapters. + +## Verification + +```bash +cargo check -p bitfun-transport +node scripts/check-core-boundaries.mjs +``` + +For documentation-only changes, run `git diff --check`. diff --git a/src/crates/webdriver/AGENTS.md b/src/crates/webdriver/AGENTS.md new file mode 100644 index 000000000..4046a65f4 --- /dev/null +++ b/src/crates/webdriver/AGENTS.md @@ -0,0 +1,24 @@ +# webdriver Agent Guide + +Scope: this guide applies to `src/crates/webdriver`. + +`bitfun-webdriver` owns the embedded desktop WebDriver bridge. It is a +platform-integration crate, not a product runtime or tool-policy owner. + +## Guardrails + +- Keep startup gated by the existing debug, feature, and environment checks. +- Platform capture, evaluation, and native WebView access may live here; product + policy, session lifecycle, tool exposure, and agent decisions must not. +- Preserve WebDriver protocol response shapes, session/window/element semantics, + and platform-specific capture/evaluation behavior. +- Do not expose this crate as a shared runtime contract; route product-facing + behavior through desktop/API/transport boundaries. + +## Verification + +```bash +cargo check -p bitfun-webdriver +``` + +For documentation-only changes, run `git diff --check`. From b5fbbe0b5450238c5a49545111bfa653905039b8 Mon Sep 17 00:00:00 2001 From: limityan Date: Tue, 2 Jun 2026 19:10:42 +0800 Subject: [PATCH 2/2] refactor(runtime): add product capability assembly --- AGENTS-CN.md | 1 + AGENTS.md | 1 + Cargo.toml | 1 + docs/plans/core-decomposition-completed.md | 24 +- docs/plans/core-decomposition-plan.md | 13 + scripts/check-core-boundaries.mjs | 102 ++++- src/crates/core/Cargo.toml | 5 + src/crates/core/src/agentic/harness.rs | 38 +- .../core/src/agentic/tools/product_runtime.rs | 12 +- src/crates/harness/AGENTS.md | 2 + src/crates/product-capabilities/AGENTS.md | 28 ++ src/crates/product-capabilities/Cargo.toml | 15 + src/crates/product-capabilities/src/lib.rs | 351 ++++++++++++++++++ .../tests/product_capabilities.rs | 123 ++++++ src/crates/tool-packs/AGENTS.md | 2 + 15 files changed, 671 insertions(+), 47 deletions(-) create mode 100644 src/crates/product-capabilities/AGENTS.md create mode 100644 src/crates/product-capabilities/Cargo.toml create mode 100644 src/crates/product-capabilities/src/lib.rs create mode 100644 src/crates/product-capabilities/tests/product_capabilities.rs diff --git a/AGENTS-CN.md b/AGENTS-CN.md index ac712f2f5..de0ef182b 100644 --- a/AGENTS-CN.md +++ b/AGENTS-CN.md @@ -32,6 +32,7 @@ BitFun 是一个由 Rust workspace 与 React 前端组成的项目。 | Agent tool contracts | `src/crates/agent-tools` | [AGENTS.md](src/crates/agent-tools/AGENTS.md) | | Tool pack provider plan | `src/crates/tool-packs` | [AGENTS.md](src/crates/tool-packs/AGENTS.md) | | 产品领域 crate | `src/crates/product-domains` | [AGENTS.md](src/crates/product-domains/AGENTS.md) | +| 产品能力包 | `src/crates/product-capabilities` | [AGENTS.md](src/crates/product-capabilities/AGENTS.md) | | Transport 适配层 | `src/crates/transport` | [AGENTS.md](src/crates/transport/AGENTS.md) | | API layer | `src/crates/api-layer` | [AGENTS.md](src/crates/api-layer/AGENTS.md) | | ACP 集成 | `src/crates/acp` | [AGENTS.md](src/crates/acp/AGENTS.md) | diff --git a/AGENTS.md b/AGENTS.md index 73a1e777e..e07c762ca 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,6 +32,7 @@ Repository rule: **keep product logic platform-agnostic, then expose it through | Agent tool contracts | `src/crates/agent-tools` | [AGENTS.md](src/crates/agent-tools/AGENTS.md) | | Tool pack provider plan | `src/crates/tool-packs` | [AGENTS.md](src/crates/tool-packs/AGENTS.md) | | Product domains | `src/crates/product-domains` | [AGENTS.md](src/crates/product-domains/AGENTS.md) | +| Product capabilities | `src/crates/product-capabilities` | [AGENTS.md](src/crates/product-capabilities/AGENTS.md) | | Transport adapters | `src/crates/transport` | [AGENTS.md](src/crates/transport/AGENTS.md) | | API layer | `src/crates/api-layer` | [AGENTS.md](src/crates/api-layer/AGENTS.md) | | ACP integration | `src/crates/acp` | [AGENTS.md](src/crates/acp/AGENTS.md) | diff --git a/Cargo.toml b/Cargo.toml index dd515824c..ae481bcc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "src/crates/agent-stream", "src/crates/agent-runtime", "src/crates/harness", + "src/crates/product-capabilities", "src/crates/runtime-ports", "src/crates/runtime-services", "src/crates/services-core", diff --git a/docs/plans/core-decomposition-completed.md b/docs/plans/core-decomposition-completed.md index 607f48481..0e8c855e4 100644 --- a/docs/plans/core-decomposition-completed.md +++ b/docs/plans/core-decomposition-completed.md @@ -118,18 +118,34 @@ - MiniApp worker process、host dispatch、permission execution、PathManager integration、builtin marker IO / seed 写盘仍在 core;后续迁移必须单独证明权限与进程行为等价。 +### 1.7 Product Capability pack:Harness / Tool / Service 组装闭环 + +- 新增 `bitfun-product-capabilities`,承接产品能力包 assembly facts:capability id、required runtime service + capability、tool provider group selection 和 harness provider descriptor。 +- `bitfun-core::agentic::harness` 改为消费 product capability owner 提供的默认 harness registry,core 不再硬编码 + Deep Review / DeepResearch / MiniApp provider descriptor。 +- `ProductToolRuntime` 改为通过 product capability owner 解析默认 tool provider group plan,默认产品 tool provider + order 保持不变。 +- capability pack 对未知 tool provider group 显式报错,避免配置错误被静默过滤成工具能力缺失。 +- boundary check 覆盖 product-capabilities:禁止依赖 core、product-domain implementation、tool-runtime、concrete + service crate、Tauri 和重 IO / protocol dependency。 +- cargo tree / metadata 证据显示 product-capabilities 只依赖 harness、runtime-ports、tool-packs;core + no-default 不选入 product owner deps,相关 owner 依赖保持 optional。 +- PR-C 只证明 capability / harness / tool provider 组装边界和 no-default / dependency profile 未扩大;不迁移缺少等价保护的 + concrete IO、MiniApp worker/host、function-agent Git/AI 或 scheduler/event/permission lifecycle。 + ## 2. 已建立保护 - 新 owner crate 不得依赖回 `bitfun-core`。 - `product-full` 是完整产品能力保护开关。 - 构建脚本和 installer 相关脚本不作为 core 拆解的一部分修改。 - boundary check 覆盖已外移 owner 的旧路径 facade-only / 禁止回流状态。 -- tool manifest、`GetToolSpec`、execution admission gate、MiniApp storage layout adapter、product-domain pure helper、remote workspace search fallback、MCP config / catalog / dynamic manifest、agent-runtime prompt cache 与 agent registry source/profile facts 等已有 focused baseline。 +- tool manifest、`GetToolSpec`、execution admission gate、MiniApp storage layout adapter、product-domain pure helper、remote workspace search fallback、MCP config / catalog / dynamic manifest、agent-runtime prompt cache、agent registry source/profile facts、product capability pack 和 harness/tool provider assembly 等已有 focused baseline。 ## 3. 当前剩余结论 - 低风险准备项已经完成,不再新增零散小 PR。 -- PR-B 已收敛 Product-Domain builtin asset owner 与 Tool Runtime owner closure。后续只剩 PR-C 的 Harness / - Capability / Build-Benefit closure,以及经等价保护后再评估的 MiniApp worker/host、function-agent Git/AI、 - 具体 IO tools、Agent Runtime concrete scheduler/event/permission/post-turn hook;不能拆成零散 helper PR。 +- PR-C 已收敛 Harness / Product Capability / Build-Benefit closure。后续不应继续拆零散 helper PR;若继续迁移 + MiniApp worker/host、function-agent Git/AI、具体 IO tools、Agent Runtime concrete scheduler/event/permission/post-turn + hook,必须先补端到端等价保护并作为新的完整 owner PR 评估。 - 缺陷修复、行为变更、冗余清理、三方库升级和构建脚本调整必须独立评估,不能伪装成 core decomposition 剩余里程碑。 diff --git a/docs/plans/core-decomposition-plan.md b/docs/plans/core-decomposition-plan.md index d3febdb63..4ee87704c 100644 --- a/docs/plans/core-decomposition-plan.md +++ b/docs/plans/core-decomposition-plan.md @@ -99,6 +99,19 @@ PR-B 收敛 Product-Domain 与 Tool Runtime 的真实 owner 逻辑,但不移 不继续纳入 PR-B 的内容:MiniApp worker process / host dispatch、builtin marker IO / seed 写盘、function-agent Git / AI concrete service、具体 IO tools。这些路径连接进程执行、权限检查、文件系统、shell、Git、AI provider 或用户可见工具行为,必须在 PR-C 中先补等价保护后再评估是否外移。 +### 4.3 PR-C 实施状态 + +PR-C 收敛 Harness / Product Capability / Build-Benefit closure,不把缺少等价保护的 concrete runtime 行为强行外移。当前分支覆盖: + +1. 新增 `bitfun-product-capabilities`,承接产品能力包 assembly facts:capability id、required runtime service capability、tool provider group selection 和 harness provider descriptor。 +2. `bitfun-core::agentic::harness` 改为消费 `bitfun-product-capabilities::default_product_harness_registry()`,core 不再硬编码 Deep Review / DeepResearch / MiniApp harness provider descriptor。 +3. `ProductToolRuntime` 改为通过 `default_product_tool_provider_group_plan()` 获取默认产品 tool provider group plan,tool group 选择由 Product Capability pack 统一表达。 +4. `scripts/check-core-boundaries.mjs` 增加 product-capabilities 防回流和 default-light 检查,确保它不依赖 core、product-domain implementation、tool-runtime、concrete services、Tauri 或重 IO / protocol dependency。 +5. capability pack 对未知 tool provider group 显式报错,避免配置错误被静默过滤成工具能力缺失。 +6. cargo tree / metadata 证据显示 `bitfun-product-capabilities` 只依赖 `bitfun-harness`、`bitfun-runtime-ports`、`bitfun-tool-packs`;`bitfun-core --no-default-features` 不选入 product owner deps,相关 owner 依赖保持 optional。 + +PR-C 评估后不继续迁移的内容:MiniApp worker process / host dispatch / builtin marker IO / seed 写盘、function-agent Git / AI concrete service、具体 IO tools、Agent Runtime concrete scheduler / event delivery / permission `Tool` handler / post-turn hook。这些路径仍缺少足够的端到端等价保护或会触及进程、权限、AI provider、filesystem/shell、checkpoint hook、session lifecycle 等用户可见行为,后续若迁移必须单独以完整 owner PR 处理。 + ## 5. 每类 PR 的保护重点 ### 5.1 Service / Agent Remote Runtime Owner diff --git a/scripts/check-core-boundaries.mjs b/scripts/check-core-boundaries.mjs index 23efe7dd2..b8cbc2034 100644 --- a/scripts/check-core-boundaries.mjs +++ b/scripts/check-core-boundaries.mjs @@ -15,6 +15,7 @@ const noCoreDependencyCrates = [ 'agent-stream', 'agent-runtime', 'harness', + 'product-capabilities', 'runtime-ports', 'runtime-services', 'services-core', @@ -166,6 +167,33 @@ const lightweightBoundaryRules = [ 'syntect-tui', ], }, + { + crateName: 'product-capabilities', + reason: + 'product-capabilities must own product capability assembly facts without concrete runtime implementations', + forbiddenDeps: [ + 'bitfun-core', + 'bitfun-ai-adapters', + 'bitfun-agent-tools', + 'bitfun-services-core', + 'bitfun-services-integrations', + 'bitfun-product-domains', + 'bitfun-transport', + 'terminal-core', + 'tool-runtime', + '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', @@ -202,6 +230,7 @@ const dependencyProfileRules = [ forbiddenNonOptionalDeps: [ 'aes', 'aes-gcm', + 'bitfun-product-capabilities', 'bitfun-product-domains', 'bitfun-relay-server', 'bitfun-tool-packs', @@ -358,6 +387,7 @@ const dependencyProfileRules = [ reason: 'agent-tools must stay a lightweight tool contract crate', forbiddenNonOptionalDeps: [ 'bitfun-ai-adapters', + 'bitfun-product-capabilities', 'reqwest', 'git2', 'rmcp', @@ -371,6 +401,33 @@ const dependencyProfileRules = [ 'syntect-tui', ], }, + { + crateName: 'product-capabilities', + profileName: 'default product capability profile', + reason: 'product-capabilities default profile must stay assembly-fact only', + forbiddenNonOptionalDeps: [ + 'bitfun-core', + 'bitfun-ai-adapters', + 'bitfun-agent-tools', + 'bitfun-services-core', + 'bitfun-services-integrations', + 'bitfun-product-domains', + 'bitfun-transport', + 'terminal-core', + 'tool-runtime', + 'tauri', + 'reqwest', + 'git2', + 'rmcp', + 'image', + 'tokio-tungstenite', + 'bitfun-cli', + 'ratatui', + 'crossterm', + 'arboard', + 'syntect-tui', + ], + }, { crateName: 'product-domains', profileName: 'default product domain profile', @@ -431,6 +488,7 @@ const optionalDependencyFeatureOwnerRules = [ dependencies: [ { depName: 'aes', ownerFeatures: ['service-integrations'] }, { depName: 'aes-gcm', ownerFeatures: ['service-integrations', 'ssh-remote'] }, + { depName: 'bitfun-product-capabilities', ownerFeatures: ['product-capabilities'] }, { depName: 'bitfun-product-domains', ownerFeatures: ['product-domains'] }, { depName: 'bitfun-relay-server', ownerFeatures: ['service-integrations'] }, { depName: 'bitfun-tool-packs', ownerFeatures: ['tool-packs'] }, @@ -531,7 +589,13 @@ const productCoreFeatureAssemblyScanRoots = ['src/apps', 'src/crates/acp']; const coreProductFullFeatureAssemblyRule = { manifestPath: 'src/crates/core/Cargo.toml', featureName: 'product-full', - requiredFeatureRefs: ['ssh-remote', 'product-domains', 'service-integrations', 'tool-packs'], + requiredFeatureRefs: [ + 'ssh-remote', + 'product-capabilities', + 'product-domains', + 'service-integrations', + 'tool-packs', + ], reason: 'bitfun-core product-full must explicitly assemble current owner feature groups', }; @@ -4489,8 +4553,8 @@ const requiredContentRules = [ message: 'missing generic static provider group contract use', }, { - regex: /\bproduct_tool_provider_group_plan\b/, - message: 'missing tool-pack provider group plan delegation', + regex: /\bdefault_product_tool_provider_group_plan\b/, + message: 'missing product capability provider group plan delegation', }, { regex: /\bProductToolMaterializer\b/, @@ -7007,7 +7071,13 @@ function runManifestParserSelfTest() { throw new Error(`${rule.manifestPath} must require bitfun-core product-full`); } } - for (const featureName of ['ssh-remote', 'product-domains', 'service-integrations', 'tool-packs']) { + for (const featureName of [ + 'ssh-remote', + 'product-capabilities', + 'product-domains', + 'service-integrations', + 'tool-packs', + ]) { if (!coreProductFullFeatureAssemblyRule.requiredFeatureRefs.includes(featureName)) { throw new Error(`core product-full assembly rule must require ${featureName}`); } @@ -7494,6 +7564,26 @@ function runManifestParserSelfTest() { if (!agentRuntimeProfile?.forbiddenNonOptionalDeps.includes('tauri')) { throw new Error('agent-runtime dependency profile must forbid product surface dependencies'); } + const productCapabilitiesRule = lightweightBoundaryRules.find( + (rule) => rule.crateName === 'product-capabilities', + ); + if (!productCapabilitiesRule?.forbiddenDeps.includes('bitfun-core')) { + throw new Error('product-capabilities lightweight boundary must forbid bitfun-core'); + } + if (!productCapabilitiesRule?.forbiddenDeps.includes('bitfun-product-domains')) { + throw new Error( + 'product-capabilities lightweight boundary must forbid product-domain implementations', + ); + } + if (!productCapabilitiesRule?.forbiddenDeps.includes('tool-runtime')) { + throw new Error('product-capabilities lightweight boundary must forbid tool-runtime'); + } + const productCapabilitiesProfile = dependencyProfileRules.find( + (rule) => rule.crateName === 'product-capabilities', + ); + if (!productCapabilitiesProfile?.forbiddenNonOptionalDeps.includes('bitfun-core')) { + throw new Error('product-capabilities dependency profile must forbid bitfun-core'); + } const agentToolsManifestRule = forbiddenContentUnderRules.find( (rule) => rule.path === 'src/crates/agent-tools/src', ); @@ -8170,7 +8260,7 @@ function runManifestParserSelfTest() { 'ProductSnapshotToolWrapper', 'builtin_static_tool_providers', 'StaticToolProviderGroup', - 'product_tool_provider_group_plan', + 'default_product_tool_provider_group_plan', 'ProductToolMaterializer', 'ToolRuntimeAssembly', 'create_registry_from_static_providers', @@ -8426,9 +8516,11 @@ function runManifestParserSelfTest() { { path: 'src/crates/core/Cargo.toml', contracts: [ + 'bitfun-product-capabilities = \\{ path = "\\.\\.\\/product-capabilities", default-features = false, optional = true \\}', 'bitfun-tool-packs = \\{ path = "\\.\\.\\/tool-packs", default-features = false, optional = true \\}', 'bitfun-services-integrations = \\{ path = "\\.\\.\\/services-integrations", default-features = false, features = \\["remote-ssh"\\] \\}', 'bitfun-product-domains = \\{ path = "\\.\\.\\/product-domains", default-features = false, optional = true \\}', + 'dep:bitfun-product-capabilities', 'dep:bitfun-tool-packs', 'bitfun-tool-packs\\/product-full', 'bitfun-services-integrations\\/product-full', diff --git a/src/crates/core/Cargo.toml b/src/crates/core/Cargo.toml index dee7d02f1..12a737180 100644 --- a/src/crates/core/Cargo.toml +++ b/src/crates/core/Cargo.toml @@ -87,6 +87,9 @@ bitfun-agent-runtime = { path = "../agent-runtime" } # Harness workflow contracts bitfun-harness = { path = "../harness" } +# Product capability pack contracts +bitfun-product-capabilities = { path = "../product-capabilities", default-features = false, optional = true } + # Agent tool contracts bitfun-agent-tools = { path = "../agent-tools" } @@ -180,10 +183,12 @@ product-full = [ "dep:similar", "dep:tool-runtime", "ssh-remote", + "product-capabilities", "product-domains", "service-integrations", "tool-packs", ] +product-capabilities = ["dep:bitfun-product-capabilities"] product-domains = ["dep:bitfun-product-domains", "bitfun-product-domains/product-full"] service-integrations = [ "dep:aes", diff --git a/src/crates/core/src/agentic/harness.rs b/src/crates/core/src/agentic/harness.rs index 50f7affe2..c6e409a94 100644 --- a/src/crates/core/src/agentic/harness.rs +++ b/src/crates/core/src/agentic/harness.rs @@ -1,43 +1,17 @@ -use bitfun_harness::{ - DescriptorHarnessProvider, HarnessCapability, HarnessRegistry, HarnessRegistryBuildError, - HarnessRegistryBuilder, HarnessWorkflow, +use bitfun_harness::{HarnessRegistry, HarnessRegistryBuildError}; +pub use bitfun_product_capabilities::{ + CORE_DEEP_RESEARCH_HARNESS_PROVIDER_ID, CORE_DEEP_REVIEW_HARNESS_PROVIDER_ID, + CORE_MINIAPP_HARNESS_PROVIDER_ID, }; -pub const CORE_DEEP_REVIEW_HARNESS_PROVIDER_ID: &str = "core.deep_review"; -pub const CORE_DEEP_RESEARCH_HARNESS_PROVIDER_ID: &str = "core.deep_research"; -pub const CORE_MINIAPP_HARNESS_PROVIDER_ID: &str = "core.miniapp"; - pub fn product_harness_registry() -> Result { - HarnessRegistryBuilder::new() - .install_provider(DescriptorHarnessProvider::legacy_facade( - CORE_DEEP_REVIEW_HARNESS_PROVIDER_ID, - HarnessWorkflow::DeepReview, - &[ - HarnessCapability::Plan, - HarnessCapability::ReviewGate, - HarnessCapability::PostProcessor, - ], - "bitfun-core::agentic::deep_review", - )) - .install_provider(DescriptorHarnessProvider::legacy_facade( - CORE_DEEP_RESEARCH_HARNESS_PROVIDER_ID, - HarnessWorkflow::DeepResearch, - &[HarnessCapability::Plan, HarnessCapability::PostProcessor], - "bitfun-core::agentic::agents::definitions::modes::deep_research", - )) - .install_provider(DescriptorHarnessProvider::legacy_facade( - CORE_MINIAPP_HARNESS_PROVIDER_ID, - HarnessWorkflow::MiniApp, - &[HarnessCapability::Plan, HarnessCapability::Artifact], - "bitfun-core::miniapp", - )) - .build() + bitfun_product_capabilities::default_product_harness_registry() } #[cfg(test)] mod tests { use super::*; - use bitfun_harness::{HarnessInput, HarnessStepKind}; + use bitfun_harness::{HarnessInput, HarnessStepKind, HarnessWorkflow}; #[test] fn product_harness_registry_registers_existing_workflow_facades() { diff --git a/src/crates/core/src/agentic/tools/product_runtime.rs b/src/crates/core/src/agentic/tools/product_runtime.rs index 827d2695a..a8e2b8e55 100644 --- a/src/crates/core/src/agentic/tools/product_runtime.rs +++ b/src/crates/core/src/agentic/tools/product_runtime.rs @@ -16,7 +16,6 @@ use crate::agentic::tools::registry::{ProductToolDecoratorRef, ToolRegistry}; #[cfg(test)] use bitfun_agent_tools::StaticToolProvider; use bitfun_agent_tools::{SnapshotToolDecorator, StaticToolProviderGroup, ToolRuntimeAssembly}; -use bitfun_tool_packs::product_tool_provider_group_plan; use materialization::ProductToolMaterializer; use snapshot::ProductSnapshotToolWrapper; use std::sync::Arc; @@ -78,7 +77,8 @@ impl ProductToolRuntime { } fn builtin_static_tool_providers() -> Vec> { - ProductToolMaterializer.materialize_provider_groups(product_tool_provider_group_plan()) + let plan = bitfun_product_capabilities::default_product_tool_provider_group_plan(); + ProductToolMaterializer.materialize_provider_groups(&plan) } #[cfg(test)] @@ -86,7 +86,7 @@ mod tests { use super::{materialization::ProductToolMaterializer, ProductToolRuntime}; use crate::agentic::tools::registry::create_tool_registry; use bitfun_agent_tools::StaticToolProvider; - use bitfun_tool_packs::product_tool_provider_group_plan; + use bitfun_product_capabilities::default_product_tool_provider_group_plan; #[test] fn product_tool_runtime_owner_preserves_registry_contract() { @@ -109,13 +109,13 @@ mod tests { #[test] fn product_tool_materializer_preserves_provider_plan_order() { let materializer = ProductToolMaterializer::default(); - let providers = - materializer.materialize_provider_groups(product_tool_provider_group_plan()); + let plan = default_product_tool_provider_group_plan(); + let providers = materializer.materialize_provider_groups(&plan); let provider_ids = providers .iter() .map(|provider| provider.provider_id()) .collect::>(); - let planned_ids = product_tool_provider_group_plan() + let planned_ids = plan .iter() .map(|group| group.provider_id()) .collect::>(); diff --git a/src/crates/harness/AGENTS.md b/src/crates/harness/AGENTS.md index 0006c9031..fce36616c 100644 --- a/src/crates/harness/AGENTS.md +++ b/src/crates/harness/AGENTS.md @@ -18,6 +18,8 @@ DeepResearch, MiniApp, and future SDD flows. internals, filesystem/Git/terminal managers, or UI command behavior. - Product Assembly should register providers through typed registries; avoid global mutable registries or untyped service locators. +- Product capability packs may own provider descriptors; `bitfun-harness` keeps + the provider-neutral contract and registry only. ## Verification diff --git a/src/crates/product-capabilities/AGENTS.md b/src/crates/product-capabilities/AGENTS.md new file mode 100644 index 000000000..391d046f0 --- /dev/null +++ b/src/crates/product-capabilities/AGENTS.md @@ -0,0 +1,28 @@ +# product-capabilities Agent Guide + +Scope: this guide applies to `src/crates/product-capabilities`. + +`bitfun-product-capabilities` owns product capability pack assembly facts: which +runtime services, tool provider groups, and harness providers a product +capability requires. It does not own concrete runtime execution. + +## Guardrails + +- Do not depend on `bitfun-core`, app crates, Tauri, product-domain + implementations, concrete service crates, AI adapters, transport adapters, + terminal, tool-runtime, or concrete tool implementations. +- Keep this crate limited to stable capability ids, service capability facts, + tool provider group selection, and harness provider descriptors. +- Do not encode product UI behavior, permission decisions, session lifecycle, + filesystem/process IO, Git/AI provider acquisition, or feature defaults here. +- Preserve default product tool provider order and legacy harness provider ids + when changing capability packs. + +## Verification + +```bash +cargo test -p bitfun-product-capabilities +node scripts/check-core-boundaries.mjs +``` + +For documentation-only changes, run `git diff --check`. diff --git a/src/crates/product-capabilities/Cargo.toml b/src/crates/product-capabilities/Cargo.toml new file mode 100644 index 000000000..a0e129e98 --- /dev/null +++ b/src/crates/product-capabilities/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "bitfun-product-capabilities" +version.workspace = true +authors.workspace = true +edition.workspace = true +description = "BitFun product capability pack contracts" + +[lib] +name = "bitfun_product_capabilities" +crate-type = ["rlib"] + +[dependencies] +bitfun-harness = { path = "../harness" } +bitfun-runtime-ports = { path = "../runtime-ports" } +bitfun-tool-packs = { path = "../tool-packs", default-features = false } diff --git a/src/crates/product-capabilities/src/lib.rs b/src/crates/product-capabilities/src/lib.rs new file mode 100644 index 000000000..1fd8d94a1 --- /dev/null +++ b/src/crates/product-capabilities/src/lib.rs @@ -0,0 +1,351 @@ +//! Product capability pack contracts. +//! +//! This crate owns provider-neutral product capability assembly facts. Concrete +//! workflow execution and tool implementations remain in their runtime owners. + +use std::collections::HashSet; +use std::fmt; + +use bitfun_harness::{ + DescriptorHarnessProvider, HarnessCapability, HarnessRegistry, HarnessRegistryBuildError, + HarnessRegistryBuilder, HarnessWorkflow, +}; +use bitfun_runtime_ports::RuntimeServiceCapability; +use bitfun_tool_packs::{product_tool_provider_group_plan, ToolProviderGroupPlan}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ProductCapabilityBuildError { + UnknownToolProviderGroup { provider_id: &'static str }, +} + +impl fmt::Display for ProductCapabilityBuildError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::UnknownToolProviderGroup { provider_id } => { + write!(f, "unknown tool provider group {provider_id}") + } + } + } +} + +impl std::error::Error for ProductCapabilityBuildError {} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum ProductCapabilityId { + CodeAgent, + DeepReview, + DeepResearch, + MiniApp, +} + +impl ProductCapabilityId { + pub const fn id(self) -> &'static str { + match self { + Self::CodeAgent => "code-agent", + Self::DeepReview => "deep-review", + Self::DeepResearch => "deep-research", + Self::MiniApp => "miniapp", + } + } +} + +impl fmt::Display for ProductCapabilityId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.id()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct HarnessProviderDescriptor { + provider_id: &'static str, + workflow: HarnessWorkflow, + capabilities: &'static [HarnessCapability], + legacy_target: &'static str, +} + +impl HarnessProviderDescriptor { + pub const fn legacy_facade( + provider_id: &'static str, + workflow: HarnessWorkflow, + capabilities: &'static [HarnessCapability], + legacy_target: &'static str, + ) -> Self { + Self { + provider_id, + workflow, + capabilities, + legacy_target, + } + } + + pub const fn provider_id(self) -> &'static str { + self.provider_id + } + + pub const fn workflow(self) -> HarnessWorkflow { + self.workflow + } + + pub const fn capabilities(self) -> &'static [HarnessCapability] { + self.capabilities + } + + pub const fn legacy_target(self) -> &'static str { + self.legacy_target + } + + pub fn into_provider(self) -> DescriptorHarnessProvider { + DescriptorHarnessProvider::legacy_facade( + self.provider_id, + self.workflow, + self.capabilities, + self.legacy_target, + ) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ProductCapabilityPack { + id: ProductCapabilityId, + required_services: &'static [RuntimeServiceCapability], + tool_provider_group_ids: &'static [&'static str], + harness_provider_descriptors: &'static [HarnessProviderDescriptor], +} + +impl ProductCapabilityPack { + pub const fn new( + id: ProductCapabilityId, + required_services: &'static [RuntimeServiceCapability], + tool_provider_group_ids: &'static [&'static str], + harness_provider_descriptors: &'static [HarnessProviderDescriptor], + ) -> Self { + Self { + id, + required_services, + tool_provider_group_ids, + harness_provider_descriptors, + } + } + + pub const fn id(self) -> ProductCapabilityId { + self.id + } + + pub const fn required_services(self) -> &'static [RuntimeServiceCapability] { + self.required_services + } + + pub const fn tool_provider_group_ids(self) -> &'static [&'static str] { + self.tool_provider_group_ids + } + + pub const fn harness_provider_descriptors(self) -> &'static [HarnessProviderDescriptor] { + self.harness_provider_descriptors + } +} + +#[derive(Debug, Clone, Copy)] +pub struct ProductCapabilityRegistry { + packs: &'static [ProductCapabilityPack], +} + +impl ProductCapabilityRegistry { + pub const fn new(packs: &'static [ProductCapabilityPack]) -> Self { + Self { packs } + } + + pub const fn packs(self) -> &'static [ProductCapabilityPack] { + self.packs + } + + pub fn capability_ids(self) -> Vec { + self.packs.iter().map(|pack| pack.id()).collect() + } + + pub fn required_service_capabilities(self) -> Vec { + let mut seen = HashSet::new(); + let mut capabilities = Vec::new(); + for pack in self.packs { + for capability in pack.required_services() { + if seen.insert(*capability) { + capabilities.push(*capability); + } + } + } + capabilities + } + + pub fn tool_provider_group_ids(self) -> Vec<&'static str> { + let mut seen = HashSet::new(); + let mut provider_ids = Vec::new(); + for pack in self.packs { + for provider_id in pack.tool_provider_group_ids() { + if seen.insert(*provider_id) { + provider_ids.push(*provider_id); + } + } + } + provider_ids + } + + pub fn try_tool_provider_group_plan( + self, + ) -> Result, ProductCapabilityBuildError> { + let provider_ids = self.tool_provider_group_ids(); + let requested_provider_ids = provider_ids.iter().copied().collect::>(); + let mut found_provider_ids = HashSet::new(); + let mut plan = Vec::new(); + + for group_plan in product_tool_provider_group_plan() { + if requested_provider_ids.contains(group_plan.provider_id()) { + found_provider_ids.insert(group_plan.provider_id()); + plan.push(*group_plan); + } + } + + for provider_id in provider_ids { + if !found_provider_ids.contains(provider_id) { + return Err(ProductCapabilityBuildError::UnknownToolProviderGroup { provider_id }); + } + } + + Ok(plan) + } + + pub fn tool_provider_group_plan(self) -> Vec { + self.try_tool_provider_group_plan() + .expect("product capability packs must reference known tool provider groups") + } + + pub fn harness_provider_descriptors(self) -> Vec { + let mut seen = HashSet::new(); + let mut descriptors = Vec::new(); + for pack in self.packs { + for descriptor in pack.harness_provider_descriptors() { + if seen.insert(descriptor.provider_id()) { + descriptors.push(*descriptor); + } + } + } + descriptors + } + + pub fn build_harness_registry(self) -> Result { + let mut builder = HarnessRegistryBuilder::new(); + for descriptor in self.harness_provider_descriptors() { + builder = builder.install_provider(descriptor.into_provider()); + } + builder.build() + } +} + +const CODE_AGENT_SERVICES: &[RuntimeServiceCapability] = &[ + RuntimeServiceCapability::FileSystem, + RuntimeServiceCapability::Workspace, + RuntimeServiceCapability::SessionStore, + RuntimeServiceCapability::Permission, + RuntimeServiceCapability::Events, + RuntimeServiceCapability::Clock, +]; +const DEEP_REVIEW_SERVICES: &[RuntimeServiceCapability] = &[ + RuntimeServiceCapability::Workspace, + RuntimeServiceCapability::Git, + RuntimeServiceCapability::Permission, + RuntimeServiceCapability::Events, +]; +const DEEP_RESEARCH_SERVICES: &[RuntimeServiceCapability] = &[ + RuntimeServiceCapability::Workspace, + RuntimeServiceCapability::Network, + RuntimeServiceCapability::Permission, + RuntimeServiceCapability::Events, +]; +const MINIAPP_SERVICES: &[RuntimeServiceCapability] = &[ + RuntimeServiceCapability::FileSystem, + RuntimeServiceCapability::Workspace, + RuntimeServiceCapability::Permission, + RuntimeServiceCapability::Events, +]; + +const CODE_AGENT_TOOL_GROUPS: &[&str] = &["core.basic", "core.agent", "core.session"]; +const INTEGRATION_TOOL_GROUPS: &[&str] = &["core.integration"]; + +const DEEP_REVIEW_HARNESS_CAPABILITIES: &[HarnessCapability] = &[ + HarnessCapability::Plan, + HarnessCapability::ReviewGate, + HarnessCapability::PostProcessor, +]; +const DEEP_RESEARCH_HARNESS_CAPABILITIES: &[HarnessCapability] = + &[HarnessCapability::Plan, HarnessCapability::PostProcessor]; +const MINIAPP_HARNESS_CAPABILITIES: &[HarnessCapability] = + &[HarnessCapability::Plan, HarnessCapability::Artifact]; + +pub const CORE_DEEP_REVIEW_HARNESS_PROVIDER_ID: &str = "core.deep_review"; +pub const CORE_DEEP_RESEARCH_HARNESS_PROVIDER_ID: &str = "core.deep_research"; +pub const CORE_MINIAPP_HARNESS_PROVIDER_ID: &str = "core.miniapp"; + +const DEEP_REVIEW_HARNESS_PROVIDER: HarnessProviderDescriptor = + HarnessProviderDescriptor::legacy_facade( + CORE_DEEP_REVIEW_HARNESS_PROVIDER_ID, + HarnessWorkflow::DeepReview, + DEEP_REVIEW_HARNESS_CAPABILITIES, + "bitfun-core::agentic::deep_review", + ); +const DEEP_RESEARCH_HARNESS_PROVIDER: HarnessProviderDescriptor = + HarnessProviderDescriptor::legacy_facade( + CORE_DEEP_RESEARCH_HARNESS_PROVIDER_ID, + HarnessWorkflow::DeepResearch, + DEEP_RESEARCH_HARNESS_CAPABILITIES, + "bitfun-core::agentic::agents::definitions::modes::deep_research", + ); +const MINIAPP_HARNESS_PROVIDER: HarnessProviderDescriptor = + HarnessProviderDescriptor::legacy_facade( + CORE_MINIAPP_HARNESS_PROVIDER_ID, + HarnessWorkflow::MiniApp, + MINIAPP_HARNESS_CAPABILITIES, + "bitfun-core::miniapp", + ); + +const NO_HARNESS_PROVIDERS: &[HarnessProviderDescriptor] = &[]; +const DEEP_REVIEW_HARNESS_PROVIDERS: &[HarnessProviderDescriptor] = &[DEEP_REVIEW_HARNESS_PROVIDER]; +const DEEP_RESEARCH_HARNESS_PROVIDERS: &[HarnessProviderDescriptor] = + &[DEEP_RESEARCH_HARNESS_PROVIDER]; +const MINIAPP_HARNESS_PROVIDERS: &[HarnessProviderDescriptor] = &[MINIAPP_HARNESS_PROVIDER]; + +const DEFAULT_PRODUCT_CAPABILITY_PACKS: &[ProductCapabilityPack] = &[ + ProductCapabilityPack::new( + ProductCapabilityId::CodeAgent, + CODE_AGENT_SERVICES, + CODE_AGENT_TOOL_GROUPS, + NO_HARNESS_PROVIDERS, + ), + ProductCapabilityPack::new( + ProductCapabilityId::DeepReview, + DEEP_REVIEW_SERVICES, + INTEGRATION_TOOL_GROUPS, + DEEP_REVIEW_HARNESS_PROVIDERS, + ), + ProductCapabilityPack::new( + ProductCapabilityId::DeepResearch, + DEEP_RESEARCH_SERVICES, + INTEGRATION_TOOL_GROUPS, + DEEP_RESEARCH_HARNESS_PROVIDERS, + ), + ProductCapabilityPack::new( + ProductCapabilityId::MiniApp, + MINIAPP_SERVICES, + INTEGRATION_TOOL_GROUPS, + MINIAPP_HARNESS_PROVIDERS, + ), +]; + +pub fn default_product_capability_registry() -> ProductCapabilityRegistry { + ProductCapabilityRegistry::new(DEFAULT_PRODUCT_CAPABILITY_PACKS) +} + +pub fn default_product_tool_provider_group_plan() -> Vec { + default_product_capability_registry().tool_provider_group_plan() +} + +pub fn default_product_harness_registry() -> Result { + default_product_capability_registry().build_harness_registry() +} diff --git a/src/crates/product-capabilities/tests/product_capabilities.rs b/src/crates/product-capabilities/tests/product_capabilities.rs new file mode 100644 index 000000000..c1090f17e --- /dev/null +++ b/src/crates/product-capabilities/tests/product_capabilities.rs @@ -0,0 +1,123 @@ +use bitfun_harness::{HarnessCapability, HarnessWorkflow}; +use bitfun_product_capabilities::{ + default_product_capability_registry, default_product_harness_registry, + default_product_tool_provider_group_plan, ProductCapabilityBuildError, ProductCapabilityId, + ProductCapabilityPack, ProductCapabilityRegistry, +}; +use bitfun_runtime_ports::RuntimeServiceCapability; + +#[test] +fn default_capability_registry_preserves_product_tool_provider_order() { + let provider_ids = default_product_tool_provider_group_plan() + .iter() + .map(|group| group.provider_id()) + .collect::>(); + + assert_eq!( + provider_ids, + vec![ + "core.basic", + "core.agent", + "core.session", + "core.integration", + ] + ); +} + +#[test] +fn default_capability_registry_preserves_legacy_harness_routes() { + let registry = default_product_harness_registry().expect("harness registry should build"); + + assert_eq!( + registry.provider_ids(), + vec!["core.deep_review", "core.deep_research", "core.miniapp"] + ); + assert_eq!( + registry.workflows(), + vec![ + HarnessWorkflow::DeepReview, + HarnessWorkflow::DeepResearch, + HarnessWorkflow::MiniApp, + ] + ); +} + +#[test] +fn capability_packs_describe_service_tool_and_harness_requirements() { + let registry = default_product_capability_registry(); + + let capability_ids = registry + .capability_ids() + .into_iter() + .map(ProductCapabilityId::id) + .collect::>(); + assert_eq!( + capability_ids, + vec!["code-agent", "deep-review", "deep-research", "miniapp"] + ); + + let service_capabilities = registry.required_service_capabilities(); + assert!(service_capabilities.contains(&RuntimeServiceCapability::FileSystem)); + assert!(service_capabilities.contains(&RuntimeServiceCapability::Workspace)); + assert!(service_capabilities.contains(&RuntimeServiceCapability::Permission)); + assert!(service_capabilities.contains(&RuntimeServiceCapability::Events)); + + let harness_capabilities = registry + .harness_provider_descriptors() + .into_iter() + .map(|descriptor| { + ( + descriptor.provider_id(), + descriptor.workflow(), + descriptor.capabilities().to_vec(), + ) + }) + .collect::>(); + + assert_eq!( + harness_capabilities, + vec![ + ( + "core.deep_review", + HarnessWorkflow::DeepReview, + vec![ + HarnessCapability::Plan, + HarnessCapability::ReviewGate, + HarnessCapability::PostProcessor, + ], + ), + ( + "core.deep_research", + HarnessWorkflow::DeepResearch, + vec![HarnessCapability::Plan, HarnessCapability::PostProcessor], + ), + ( + "core.miniapp", + HarnessWorkflow::MiniApp, + vec![HarnessCapability::Plan, HarnessCapability::Artifact], + ), + ] + ); +} + +#[test] +fn capability_registry_rejects_unknown_tool_provider_groups() { + static BROKEN_TOOL_GROUPS: &[&str] = &["core.missing"]; + static BROKEN_PACKS: &[ProductCapabilityPack] = &[ProductCapabilityPack::new( + ProductCapabilityId::CodeAgent, + &[], + BROKEN_TOOL_GROUPS, + &[], + )]; + + let error = ProductCapabilityRegistry::new(BROKEN_PACKS) + .try_tool_provider_group_plan() + .expect_err("unknown provider groups must not be silently dropped"); + + assert_eq!( + error, + ProductCapabilityBuildError::UnknownToolProviderGroup { + provider_id: "core.missing" + } + ); +} diff --git a/src/crates/tool-packs/AGENTS.md b/src/crates/tool-packs/AGENTS.md index 8b3226126..a4085f83e 100644 --- a/src/crates/tool-packs/AGENTS.md +++ b/src/crates/tool-packs/AGENTS.md @@ -16,6 +16,8 @@ tool provider group plan. It does not own concrete tool implementations yet. - Do not own manifest/exposure contracts, concrete runtime manifest assembly, `GetToolSpec` execution, collapsed unlock state, snapshot decoration, or `ToolUseContext`. Provider group plans may list group ids and tool names only. +- Product capability packs may select provider groups; this crate keeps only + tool-pack feature-group metadata and provider group plans. - Future concrete tool migration must preserve product registry order, expanded/collapsed exposure, prompt stubs, unlock state, cancellation, runtime restrictions, and Deep Review tool flow.