Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions docs/architecture/extensions/external-ai-work-sources-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@ native ID、disposition 和稳定 reason code,不包含 command arguments、UR

environment 值或引用、header/authorization、cwd、未知字段和其他 transport 不猜测、不复制、不记录。导入条目始终为
`enabled: false` 与 `autoStart: false`;local 条目不继承完整父进程环境,只保留 MCP runtime owner 提供的安全环境。
Codex 的 legacy `name` 是上游忽略的展示字段,不进入导入结果或行为版本;`startup_timeout_sec`、`tool_timeout_sec`、
`enabled_tools`、`disabled_tools`、approval、environment/scopes/OAuth 与并行调用等运行敏感字段仍按不支持处理,不能因
静态发现成功而丢弃语义后导入。
Codex 的 legacy `name` 是上游忽略的展示字段,不进入导入结果或行为版本;`startup_timeout_sec`(含旧
`startup_timeout_ms`)和 `tool_timeout_sec` 可进入受审批保护的兼容运行投影,但当前原生快照格式不能无损保留它们,
因此仍会阻断 C0a 导入。`enabled_tools`、`disabled_tools`、approval、environment/scopes/OAuth 与并行调用等运行敏感字段
仍按不支持处理,不能因静态发现成功而丢弃语义后导入。
Codex 未显式声明 cwd 时,其兼容运行投影仍会把当前 workspace 作为 effective cwd;现有原生快照格式不会保留这项隐式
语义,因此 workspace 场景的 local 声明返回“需要设置”,不能以“没有 cwd 字段”为由导入后继承 BitFun 进程目录。

Expand Down Expand Up @@ -628,8 +629,12 @@ Command;明确缺失且未被标记失败的 Command 是稳定删除。产品
扩展。删除项的一次性用户通知/有界墓碑尚未实现,当前行会在稳定重扫后消失;该展示增强
留待后续 PR,不能改变“先撤 route、禁止新调用”的运行语义。
8. 外部本地进程不继承 BitFun 的完整父进程环境,只保留启动所需的系统基线和配置显式声明的变量;这仍不是 OS
沙箱,进程继续拥有当前用户的文件、网络和子进程权限。Remote 执行域、OpenCode OAuth client 配置、SSE、完整
`timeout`/Agent 范围和通用凭据归属模块明确延后。
沙箱,进程继续拥有当前用户的文件、网络和子进程权限。OpenCode V1 标量 `timeout`、Codex 启动/工具 timeout 与
Claude Code 单服务器执行 timeout 已映射为统一的启动、目录读取、执行阶段事实;Codex `startup_timeout_sec` 同时约束
初始化和首次工具目录请求,`tool_timeout_sec` 只约束工具执行。只有来源显式声明时才覆盖现有运行行为;当前使用每次请求的
硬期限,不因 progress 重置,超时只停止
BitFun 的当前等待,不承诺服务端工作已经取消,也不触发自动重放或重启。Remote 执行域、OpenCode OAuth client 配置、SSE、OpenCode V2 分阶段 timeout
配置格式、Agent 范围和通用凭据归属模块明确延后。
9. 本阶段只把外部 MCP 的 Tool 目录接入 Agent Tool 归属模块。通用 Resource/Prompt/MCP App Desktop 接口不接受无工作区
上下文的外部 runtime id;外部服务器发起的 roots、sampling 和 elicitation 请求也一律拒绝,防止跨工作区读取或借用
BitFun 宿主能力。后续若接入这些能力,必须先补独立契约、工作区路由与权限交互,不能复用全局连接绕过当前边界。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ OpenCode adapter 在来源发现、解析和审批前不 import module、不读
| Skills | `.opencode/.claude/.agents` 项目与用户根、`SKILL.md`、`skills.paths/urls` | OpenCode adapter 只由 `bitfun-core/external_sources` 组合并投影有序本地配置根;Skill 归属模块负责有界递归、解析、覆盖与按需加载 | 标准根及 V1 `skills.paths`/当前本地字符串数组可用;项目配置限项目根,用户配置限项目根或用户目录;配置根最多 64 个、每根 512 个 Skill、单文件 256 KiB、可选策略 64 KiB,实际加载再次执行有界非链接读取;配置根在同 scope 覆盖标准 OpenCode 根,但不重排更早的 BitFun/Claude/Codex/Cursor 来源 | URL、下载/缓存、脚本与外部依赖不加载;无效根不影响标准 Skill。 |
| References | `references` / 旧 `reference`,本地 path 或 Git repository/branch/description/hidden | OpenCode adapter 输出来源无关的 Reference provider snapshot;Product Assembly 生命周期协调器与 BitFun 原生关联目录合成唯一有效引用目录;关联目录视图和既有目录选择器消费 | 当前支持本地声明路径、description/hidden、异步刷新和 `@alias` 展示;原生关联目录始终在 OpenCode 引用之前,外部引用只读、不自动进入 Prompt 且不改变权限 | Git 引用、Remote 发现和下载/缓存不实现;无效高优先级 entry 阻断同 alias 的旧值并给出诊断,不回退到更宽松来源。 |
| Commands | JSON/JSONC、Markdown、`$ARGUMENTS`、位置参数、`@file`、`!shell`、agent/model/variant/subtask | Prompt Command 专属契约;adapter 提取静态文件引用与 shell 计划,Product Assembly 负责审批指纹和装配,Terminal owner 负责进程执行 | prompt 与静态 workspace 相对 UTF-8 `@file` 可发送;`!shell` 展示精确命令、工作目录与绝对 shell 路径,经重新校验后以不加载 profile 的隔离式 argv 执行,并仅把 stdout 按模板顺序加入 Prompt。为保持 OpenCode 语义,正常退出后的非零退出码仍使用 stdout。静态计划可记住,参数相关计划仅可单次运行;显式 agent 加缺省/true subtask 可走 approved fresh Subagent,其余 agent/model/variant/subtask 组合以及 shell 与委派的组合整体受限 | 任一文件读取、进程启动、超时或超限失败时不发送部分 Prompt;进程副作用不可回滚。最多 8 文件、单文件 64 KiB、文件总量 128 KiB;最多 8 条 shell 指令、单条 64 KiB、总计 128 KiB、每条 stdout 256 Ki 字符、30 秒;最终命令 1 MiB。安全模式禁用,Remote 不回退到本机。 |
| MCP | local 的 command/environment/cwd/timeout,remote 的 URL/headers/oauth/timeout,Agent 选择 | MCP 归属模块创建兼容配置视图 | 当前支持 local stdio 和 HTTPS remote 的静态发现、首次/行为变化审批、冲突选择与 workspace 隔离的运行期接纳;C0a 快照导入只复制无 env/cwd 的 local command/args 或无 header/query/fragment 的 HTTPS remote,并保持 disabled | `{env:NAME}` 当前只允许用于运行期兼容来源的 environment/Header 值,不进入 C0a 快照;SSE、OpenCode OAuth client 配置、完整 timeout/Agent 范围与 Remote 执行域保持明确不支持;凭据或网络失败只影响单个 Server。 |
| MCP | local 的 command/environment/cwd/timeout,remote 的 URL/headers/oauth/timeout,Agent 选择 | MCP 归属模块创建兼容配置视图 | 当前支持 local stdio 和 HTTPS remote 的静态发现、首次/行为变化审批、冲突选择与 workspace 隔离的运行期接纳;显式 V1 `timeout` 作为毫秒值约束启动、目录读取和执行,并在 GUI/TUI 审阅详情中可见;C0a 快照导入只复制无 env/cwd/timeout 的 local command/args 或无 header/query/fragment/timeout 的 HTTPS remote,并保持 disabled | `{env:NAME}` 当前只允许用于运行期兼容来源的 environment/Header 值,不进入 C0a 快照;SSE、OpenCode OAuth client 配置、Agent 范围、Remote 执行域与 V2 分阶段 timeout 配置格式保持明确不支持;凭据、超时或网络失败只影响单个 Server。 |
| LSP | command、extensions、env、initialization | LSP 归属模块注册兼容实例 | 首次确认外部进程和使用范围后按文件类型启动 | 自定义 Server 缺少 extensions 或启动失败时只禁用该项。 |
| Formatters | command、environment、extensions、`$FILE` | **基础能力缺失**:先补文件写入后的 Formatter 执行消费点,再做格式转换 | 首次确认命令后执行匹配 Formatter | 超时后标记未格式化,文件写入结果保留。 |
| Themes | builtin/user/project/cwd JSON | **部分已有**:GUI Theme 已有;TUI 主题消费边界在终端阶段补齐 | 保留覆盖顺序和语义角色 | 颜色能力不支持时做可见降级。 |
Expand Down Expand Up @@ -384,8 +384,10 @@ OpenCode 生态内部仍按其规则覆盖同名内置命令,但跨独立 prov
`enabled`,并在批准后按 workspace 交给现有 MCP 归属模块;工具在调用前复核 workspace route,Remote 不回退到本机实例。
远端静态摘要只展示 HTTPS origin,环境引用只展示变量名;为避免审批后通过环境变量改变已经确认的运行条件,`{env:NAME}` 仅
支持 environment/Header 值,展开后重新校验大小和协议。未配置 `cwd` 时遵循 OpenCode,使用当前 workspace。
外部本地进程默认不继承 BitFun 的完整父进程环境。SSE、OpenCode
`clientId/clientSecret/scope/callbackPort/redirectUri`、完整超时和 Agent 范围仍需后续接入,不能静默忽略。
外部本地进程默认不继承 BitFun 的完整父进程环境。显式 V1 `timeout` 通过现有 MCP runtime owner 分别约束启动、目录读取
和执行;缺省值继续使用 BitFun 既有行为。当前是每次请求的硬期限,不因 progress 重置;超时只停止 BitFun 的当前等待,
不承诺服务端工作已经取消,也不自动重放或重启。SSE、OpenCode
`clientId/clientSecret/scope/callbackPort/redirectUri`、V2 分阶段 timeout 配置格式和 Agent 范围仍需后续接入,不能静默忽略。
- LSP 必须覆盖 initialization、扩展名匹配、环境变量和工作区生命周期。
- Formatter 必须覆盖写入后时机、`$FILE` 替换、`environment`、多个 Formatter 顺序和失败行为。

Expand Down
2 changes: 2 additions & 0 deletions scripts/core-boundaries/rules/source/public-api-rules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,8 @@ export const externalSourceContractPublicApiEntries = [
'SourceQualifiedMcpServerId',
'ExternalMcpTransportKind',
'ExternalMcpStaticStatus',
'ExternalMcpTimeouts',
'MAX_EXTERNAL_MCP_TIMEOUT_MS',
'ExternalMcpServerDefinition',
'ExternalMcpActivationState',
'ExternalMcpCatalogEntry',
Expand Down
2 changes: 2 additions & 0 deletions scripts/core-boundaries/self-test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1454,6 +1454,8 @@ export function runManifestParserSelfTest({
}
}
for (const requiredSymbol of [
'ExternalMcpTimeouts',
'MAX_EXTERNAL_MCP_TIMEOUT_MS',
'ExternalMcpServerDefinition',
'ExternalMcpSourceProvider',
'PreparedExternalMcpServer',
Expand Down
40 changes: 40 additions & 0 deletions src/apps/cli/src/modes/chat/mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ fn bounded_mcp_terminal_text(value: &str) -> String {
}
}

fn external_mcp_timeout_detail(
timeouts: &bitfun_product_domains::external_sources::ExternalMcpTimeouts,
) -> Option<String> {
let phases = [
("startup", timeouts.startup_ms),
("catalog", timeouts.catalog_ms),
("execution", timeouts.execution_ms),
]
.into_iter()
.filter_map(|(phase, timeout)| timeout.map(|timeout| format!("{phase} {timeout} ms")))
.collect::<Vec<_>>();
(!phases.is_empty()).then(|| format!("timeouts: {}", phases.join(", ")))
}

fn external_mcp_state_label(
state: &bitfun_core::external_sources::ExternalMcpActivationState,
) -> &'static str {
Expand Down Expand Up @@ -306,6 +320,12 @@ impl ChatMode {
),
_ => "unsupported external MCP transport".to_string(),
};
if let Some(timeouts) =
external_mcp_timeout_detail(&entry.definition.timeouts)
{
detail.push_str("; ");
detail.push_str(&timeouts);
}
if let bitfun_core::external_sources::ExternalMcpActivationState::RuntimeUnavailable { reason } = &entry.activation_state {
detail.push_str(&format!(
"; unavailable reason: {}; next step: disable this server, fix its source configuration or authentication, then enable it",
Expand Down Expand Up @@ -772,6 +792,7 @@ impl ChatMode {
.get("xaa")
.cloned()
.and_then(|value| serde_json::from_value(value).ok()),
timeouts: Default::default(),
};

mcp_service.server_manager().add_server(config).await?;
Expand Down Expand Up @@ -897,3 +918,22 @@ impl ChatMode {
}
}
}

#[cfg(test)]
mod external_mcp_timeout_tests {
use super::external_mcp_timeout_detail;
use bitfun_product_domains::external_sources::ExternalMcpTimeouts;

#[test]
fn external_mcp_timeout_detail_lists_only_explicit_phases() {
let detail = external_mcp_timeout_detail(&ExternalMcpTimeouts {
startup_ms: Some(1_000),
catalog_ms: None,
execution_ms: Some(30_000),
})
.expect("explicit timeouts should be visible");

assert_eq!(detail, "timeouts: startup 1000 ms, execution 30000 ms");
assert!(external_mcp_timeout_detail(&ExternalMcpTimeouts::default()).is_none());
}
}
Loading