Skip to content

Commit 41f4af3

Browse files
committed
fix(plugin-host): gate execution and close lifecycle gaps
1 parent e9c09b0 commit 41f4af3

13 files changed

Lines changed: 279 additions & 62 deletions

File tree

.github/workflows/cli-package-manual.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,16 @@ jobs:
127127
with:
128128
ref: ${{ needs.prepare.outputs.checkout_ref }}
129129

130+
- name: Setup Bun
131+
uses: oven-sh/setup-bun@v2
132+
with:
133+
bun-version: "1.3.14"
134+
135+
- name: Build plugin Host resources
136+
run: |
137+
bun install --cwd src/apps/extension-host --frozen-lockfile
138+
bun run --cwd src/apps/extension-host build
139+
130140
- name: Install Linux system dependencies
131141
if: runner.os == 'Linux'
132142
shell: bash

.github/workflows/cli-package.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,16 @@ jobs:
105105
with:
106106
ref: ${{ needs.prepare.outputs.checkout_ref }}
107107

108+
- name: Setup Bun
109+
uses: oven-sh/setup-bun@v2
110+
with:
111+
bun-version: "1.3.14"
112+
113+
- name: Build plugin Host resources
114+
run: |
115+
bun install --cwd src/apps/extension-host --frozen-lockfile
116+
bun run --cwd src/apps/extension-host build
117+
108118
- name: Setup Rust toolchain
109119
uses: dtolnay/rust-toolchain@stable
110120
with:

.github/workflows/linux-binaries.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ jobs:
5757
with:
5858
ref: ${{ inputs.checkout_ref }}
5959

60+
- name: Setup Bun
61+
uses: oven-sh/setup-bun@v2
62+
with:
63+
bun-version: "1.3.14"
64+
65+
- name: Build plugin Host resources
66+
run: |
67+
bun install --cwd src/apps/extension-host --frozen-lockfile
68+
bun run --cwd src/apps/extension-host build
69+
6070
- name: Install Linux system dependencies
6171
shell: bash
6272
run: |

.github/workflows/nightly.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ jobs:
106106
steps:
107107
- uses: actions/checkout@v5
108108

109+
- name: Setup Bun
110+
uses: oven-sh/setup-bun@v2
111+
with:
112+
bun-version: "1.3.14"
113+
109114
- name: Install Linux system dependencies (Tauri bundler)
110115
if: runner.os == 'Linux'
111116
shell: bash
@@ -167,6 +172,11 @@ jobs:
167172
- name: Install dependencies
168173
run: pnpm install --frozen-lockfile
169174

175+
- name: Build plugin Host resources
176+
run: |
177+
bun install --cwd src/apps/extension-host --frozen-lockfile
178+
bun run --cwd src/apps/extension-host build
179+
170180
- name: Generate web API bindings
171181
run: pnpm --dir src/web-ui run gen:types
172182

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/architecture/extensions/plugin-runtime-design.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ workspace、project、session、turn、run 和 working directory 是不同事实
2626
决定 Plugin Host 进程数量。只有某项并发或权威状态确实要求单一实例时,负责该状态的归属模块才能把 workspace
2727
或其他身份加入自己的状态键,并说明清理与迁移语义。
2828

29-
Rust 主应用不执行第三方 JS/TS,也没有 Plugin Host 对象。它通过 `PluginRuntimeClient`、生态适配器和 services 层
30-
现有脚本执行实现向子进程发送类型明确的请求,并把结果交回 Tool、Config、Permission、Session、Event、TUI 等现有归属模块。
29+
当前产品运行路径不执行第三方 package 插件。仓库中的 Bun Host、RPC 和 OpenCode 适配代码是协议与进程隔离基础,
30+
由 fixture/mock 验证;Desktop 与 CLI 的自动启动策略保持关闭。在 contribution 归属、执行许可和故障恢复接入既有
31+
Tool、Config、Permission、Session、Event、TUI 等模块之前,不得把这套基础设施视为已交付的插件执行能力。
3132

3233
## 2. 职责
3334

@@ -256,6 +257,7 @@ flowchart LR
256257
subgraph Current["current implementation"]
257258
Manifest["Plugin manifest"] --> Static["Static preview"]
258259
Script["Standalone .js tool"] --> Worker["Dedicated worker"]
260+
Fixture["Protocol fixtures"] --> Foundation["Bun Host foundation"]
259261
end
260262
261263
subgraph Planned["planned runtime"]
@@ -272,6 +274,8 @@ flowchart LR
272274
当前受管 `bitfun.plugin.json` 链路仍只有来源校验、启停记录、CLI 诊断和 custom tool 静态预览,不执行 package
273275
plugin、Hook、完整 Client 或 TUI 插件入口。与其独立的 standalone `.js` Tool 端到端能力当前由
274276
`ScriptToolRuntime` 为每个脚本启动 Node worker;这是现有窄实现事实,不是目标 package-plugin 的进程模型。
277+
Bun Host 基础设施仅覆盖模块加载、RPC/HTTP 桥和进程树生命周期等隔离边界;配置插件时 CLI 会明确报告该执行链路
278+
尚未启用,不会静默导入或运行插件代码。
275279

276280
因此当前代码不得声称已经具备共享 Plugin Host、安全重启、通用进程级恢复或 Bun 兼容。目标实现应先用
277281
固定 OpenCode fixture 验证多个插件的顺序初始化、Hook 顺序、共享进程崩溃、安全重启和状态恢复,再替换现有

src/apps/cli/src/agent/runtime_client.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -967,7 +967,12 @@ impl CliAgentRuntimeClient {
967967
if matches!(&self.backend, CliAgentRuntimeBackend::Shared(_)) {
968968
return Ok(());
969969
}
970-
let binding = self.session_workspace_binding(session_id).await?;
970+
// Session creation holds session_id until activation has completed.
971+
// Resolve directly instead of re-locking that non-reentrant mutex.
972+
let project_workspace = self.project_workspace_path_buf();
973+
let binding = self
974+
.resolve_session_workspace_binding(session_id, &project_workspace)
975+
.await?;
971976
self.ensure_embedded_plugin_workspace_ready(&binding).await
972977
}
973978

src/apps/cli/src/main.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ use modes::chat::ChatMode;
5252
use modes::exec::{ExecApprovalMode, ExecOutputFormat};
5353

5454
pub(crate) const PLUGIN_HOST_LAUNCH_POLICY: bitfun_core::plugin_host::PluginHostLaunchPolicy =
55-
bitfun_core::plugin_host::PluginHostLaunchPolicy::Enabled;
55+
bitfun_core::plugin_host::PluginHostLaunchPolicy::Disabled;
5656

5757
// ======================== Global MCP Service ========================
5858

@@ -563,7 +563,10 @@ impl BootstrapProfile {
563563
}
564564

565565
const fn starts_plugin_host(self) -> bool {
566-
matches!(self, Self::Interactive | Self::Execution)
566+
matches!(
567+
PLUGIN_HOST_LAUNCH_POLICY,
568+
bitfun_core::plugin_host::PluginHostLaunchPolicy::Enabled
569+
) && matches!(self, Self::Interactive | Self::Execution)
567570
}
568571
}
569572

@@ -818,6 +821,12 @@ async fn initialize_core_services_for_deployment(
818821
.await
819822
.map_err(|error| anyhow!("Failed to initialize global config service: {error}"))?;
820823
tracing::info!("Global config service initialized");
824+
if matches!(
825+
bootstrap_profile,
826+
BootstrapProfile::Interactive | BootstrapProfile::Execution
827+
) {
828+
plugin_host_activation::ensure_configured_plugin_execution_supported().await?;
829+
}
821830
if bootstrap_profile.starts_plugin_host() {
822831
match bitfun_core::plugin_host::initialize_configured_plugin_host_with_log_file(
823832
PLUGIN_HOST_LAUNCH_POLICY,
@@ -1830,8 +1839,8 @@ mod bootstrap_profile_tests {
18301839
#[test]
18311840
fn profiles_start_only_their_requested_background_services() {
18321841
let cases = [
1833-
(BootstrapProfile::Interactive, true, true, true),
1834-
(BootstrapProfile::Execution, false, true, true),
1842+
(BootstrapProfile::Interactive, true, true, false),
1843+
(BootstrapProfile::Execution, false, true, false),
18351844
(BootstrapProfile::Management, false, false, false),
18361845
];
18371846

src/apps/cli/src/plugin_host_activation.rs

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::path::PathBuf;
22

3+
use bitfun_core::BitFunResult;
34
use bitfun_runtime_ports::AgentSessionWorkspaceBinding;
45

56
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -24,35 +25,42 @@ fn activation_target(
2425
})
2526
}
2627

27-
pub(crate) async fn ensure_plugin_workspace_ready(
28-
binding: &AgentSessionWorkspaceBinding,
29-
) -> bitfun_core::BitFunResult<()> {
28+
pub(crate) async fn ensure_configured_plugin_execution_supported() -> BitFunResult<bool> {
3029
// Plugin activation is optional. Isolated Runtime clients (including unit
3130
// tests) may create sessions before the process-level config service is
3231
// initialized; that must not make ordinary session creation fail.
3332
if !bitfun_core::service::config::GlobalConfigManager::is_initialized() {
34-
tracing::debug!(
35-
workspace_path = %binding.workspace_path,
36-
"Configured plugin host activation skipped before global config initialization"
37-
);
33+
return Ok(false);
34+
}
35+
36+
let config_service = bitfun_core::service::config::get_global_config_service().await?;
37+
let config: bitfun_core::service::config::GlobalConfig =
38+
config_service.get_config(None).await?;
39+
let has_configured_plugins = config.has_configured_plugins();
40+
if has_configured_plugins
41+
&& crate::PLUGIN_HOST_LAUNCH_POLICY
42+
== bitfun_core::plugin_host::PluginHostLaunchPolicy::Disabled
43+
{
44+
return Err(bitfun_core::BitFunError::NotImplemented(
45+
"Configured Plugin Host execution is not enabled; plugin definitions were not imported or executed"
46+
.to_string(),
47+
));
48+
}
49+
Ok(has_configured_plugins)
50+
}
51+
52+
pub(crate) async fn ensure_plugin_workspace_ready(
53+
binding: &AgentSessionWorkspaceBinding,
54+
) -> BitFunResult<()> {
55+
if !ensure_configured_plugin_execution_supported().await? {
3856
return Ok(());
3957
}
4058

4159
let Some(target) = activation_target(binding) else {
42-
let config_service = bitfun_core::service::config::get_global_config_service().await?;
43-
let config: bitfun_core::service::config::GlobalConfig =
44-
config_service.get_config(None).await?;
45-
if config.has_configured_plugins() {
46-
return Err(bitfun_core::BitFunError::NotImplemented(
47-
"Configured Plugin Host is unsupported for Remote CLI workspaces; no controller-local fallback was attempted"
48-
.to_string(),
49-
));
50-
}
51-
tracing::debug!(
52-
workspace_path = %binding.workspace_path,
53-
"Configured plugin host activation skipped for remote CLI workspace"
54-
);
55-
return Ok(());
60+
return Err(bitfun_core::BitFunError::NotImplemented(
61+
"Configured Plugin Host is unsupported for Remote CLI workspaces; no controller-local fallback was attempted"
62+
.to_string(),
63+
));
5664
};
5765

5866
bitfun_core::plugin_host::ensure_configured_plugin_instance(
@@ -102,4 +110,12 @@ mod tests {
102110

103111
assert_eq!(activation_target(&binding), None);
104112
}
113+
114+
#[test]
115+
fn cli_does_not_enable_unowned_plugin_execution() {
116+
assert_eq!(
117+
crate::PLUGIN_HOST_LAUNCH_POLICY,
118+
bitfun_core::plugin_host::PluginHostLaunchPolicy::Disabled
119+
);
120+
}
105121
}

0 commit comments

Comments
 (0)