> ## Documentation Index
> Fetch the complete documentation index at: https://openidea.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# 架构与沙箱

> 插件代码如何运行：WebAssembly 组件、WIT world、宿主调用、沙箱、资源限制，以及每个插件一个线程的模型。

代码插件是一个 WebAssembly **组件**（组件模型，WASI preview 2），在 IDE 内部由 [wasmtime](https://wasmtime.dev) 运行。它不能直接访问文件、网络、环境变量或进程。除纯计算外，它做的一切都要通过 IDE 提供的函数，而每个函数都会检查用户授予的[能力](https://openidea.sh/zh/plugins/capabilities.md)。

## 组成部分

| 部分 | 位置 | 作用 |
| - | - | - |
| 插件 API | [`wit/`](https://github.com/l0ng-ai/openidea/tree/main/wit) | 契约：WIT 包 `openidea:plugin@1.5.0`、其中的 interface 和 world |
| Rust SDK | [`sdk/rust/openidea-plugin`](https://github.com/l0ng-ai/openidea/tree/main/sdk/rust/openidea-plugin) | WIT 的类型化封装、`Plugin` trait、`register_plugin!`，以及给 `cargo test` 用的模拟宿主 |
| 插件包 | [`crates/ide-extension`](https://github.com/l0ng-ai/openidea/tree/main/crates/ide-extension) | 清单、能力及其风险等级、授权记录、贡献点、`.oiplugin` 包和插件市场索引 |
| 运行时 | [`crates/ide-plugin`](https://github.com/l0ng-ai/openidea/tree/main/crates/ide-plugin) | wasmtime 引擎、每个插件一个 `Store`、各项限制、每个活跃插件一个 actor 线程 |
| 宿主 | [`crates/ide-host/src/plugins`](https://github.com/l0ng-ai/openidea/tree/main/crates/ide-host/src/plugins) | 实现宿主 API，负责安装和监管插件；桌面应用和 `openidea-server` 运行的是同一份代码 |

宿主内部如何组织，见贡献者文档中的 [插件宿主](https://openidea.sh/zh/contribute/architecture/plugin-host.md)。

## World、导入与导出

插件 API 是一个 WIT 包。基础 world `plugin` 列出组件可以**导入**的宿主 interface（插件调用它们），以及组件必须**导出**的插件 interface（IDE 调用它们）：

* 导入：`log`、`ui`、`storage`、`fs`、`workspace`、`editor`、`decorations`、`invocation`、`vcs`、`commit`、`events`、`tasks`、`diagnostics`、`net`、`secrets`、`process`、`ipc`、`output`、`auth`、`command-runner`、`views`、`run`、`run-targets`、`database`、`mcp`、`settings`。
* 导出：`lifecycle`（activate、deactivate）、`commands`（执行命令）、`event-handler`（处理一批事件）。

可选功能各自是一个独立的 world：它 include `plugin` 并多一个导出。只有清单声明了对应的贡献时，插件才需要导出它：

| World | 新增导出 | 在清单中如何声明 | SDK `register_plugin!` 参数 |
| - | - | - | - |
| `plugin-syntax` | `syntax-provider` | 没有语法定义的语言 | `syntax` |
| `plugin-providers` | `providers` | `[[contributes.providers]]` | `providers` |
| `plugin-views` | `view-provider` | `views`、`customEditors` | `views` |
| `plugin-indexing` | `document-indexer` | `documentIndexers` | `indexing` |
| `plugin-semantic-tokens` | `semantic-tokens-provider` | provider kind `semantic-tokens` | `semantic_tokens` |
| `plugin-hover` | `hover-provider` | provider kind `hover` | `hover` |
| `plugin-inlay-hints` | `inlay-hints-provider` | provider kind `inlay-hints` | `inlay_hints` |
| `plugin-diagnostics` | `diagnostics-provider` | provider kind `diagnostics` | `diagnostics` |
| `plugin-file-actions` | `file-actions` | provider kind `code-actions` | `file_actions` |
| `plugin-details` | `detail-provider` | `location = "services"` 的视图 | `details` |
| `plugin-commit-checks` | `commit-checker` | `commitChecks` | `commit_checks` |
| `plugin-endpoints` | `endpoints-provider` | `endpoints` | `endpoints` |
| `plugin-run-configurations` | `run-configurations` | `runConfigurations` | `run_configurations` |
| `plugin-test-results` | `test-results` | `framework = "plugin"` 的运行检测器 | `test_results` |
| `plugin-language-features` | `language-features` | API 1.4 的 provider kind | `language_features` |
| `plugin-injections` | `injections` | provider kind `injections` | `injections` |
| `plugin-mcp` | `mcp-provider` | `mcpTools` | `mcp` |
| `plugin-run-target-validation` | `run-target-validation` | 无需声明，针对插件自己的运行目标调用（API 1.5） | `run_target_validation` |

在 Rust 里把参数列出来即可：`register_plugin!(MyPlugin, providers, views)`。每个可选 world 还有一个只含导出的孪生 world（`syntax`、`editor-providers`、`views-provider` 等），由组件工具与 `plugin` 合并。

## 双向调用

* **IDE → 插件**（导出）：用户执行命令时 IDE 调用 `commands.run`；有订阅的事件时调用 `event-handler.handle` 并传入一批事件；编辑器需要补全、悬停等时调用 provider 函数；绘制视图时调用 `view-provider.render`。
* **插件 → IDE**（导入）：在上述任一调用中，插件可以调用 `editor.text-editor.document`、`workspace.project.read-file`、`net.fetch` 等宿主函数。缺少能力时宿主函数返回 `error::denied("<capability>")`，插件继续运行，可以降级处理。

  1.x 的所有调用都是**同步**的：宿主调用返回前插件实例一直阻塞；编辑器 provider 每次调用都会收到完整的文档文本。位置坐标是从 0 开始的行号和 UTF-16 列，与编辑器和 LSP 一致（syntax provider 使用 UTF-8 字节偏移，由 IDE 转换）。

## 沙箱

每个插件都有自己的 wasmtime `Store`：

* 没有预打开的目录、没有环境变量、没有网络，也不继承 stdin/stdout；
* WASI 只提供时钟和随机数；
* stderr 会被捕获：`eprintln!` 的输出写进 IDE 日志，panic 的信息会随崩溃一起显示。

文件、网络、进程、套接字和密钥都只能通过宿主函数访问，每个函数都受某个能力约束。路径会先解析到真实位置再检查，所以符号链接无法逃出项目根目录或已授权的目录。

Web 视图和 Web 自定义编辑器运行在 `<iframe sandbox="allow-scripts">` 中，从 IDE 自己的 `openidea-plugin` URL scheme 加载：opaque origin，没有网络、Cookie 或存储，没有 IDE API，只能加载页面自己的文件。页面通过 `postMessage` 与插件代码通信。

## 限制

每次调用插件都有上限：

| 限制 | 值 |
| - | - |
| CPU | 每次调用约 5·10⁹ 条 wasm 指令的 fuel 预算 |
| 命令、事件、activate、deactivate | 5 秒墙钟时间 |
| 开启了任务（`tasks.begin`）的调用 | 60 秒；任务每次报告进度或检查取消时重新计时 |
| 编辑器 provider（语法、补全、代码操作、导航、行标记） | 超过 50 ms 的结果被丢弃；500 ms 时中断调用 |
| 内存 | 256 MiB 线性内存 |
| 空闲实例 | 5 分钟没有调用就回收 |

等待用户操作（选择器、输入框、确认框）的时间不计入时间限制。各个宿主函数还有自己的上限（文件大小、句柄数、响应大小等），详见 [`plugins/README.md`](https://github.com/l0ng-ai/openidea/blob/main/plugins/README.md)。

## 线程与崩溃

每个活跃插件运行在自己的线程上（一个持有该插件运行时的 actor）。调用方把任务排进队列并等待回复，所以同一实例内的调用永远不会并发。编辑器调用方可以在软截止时间放弃等待，而调用本身继续执行到硬截止时间；任何线程都可以取消调用。墙钟限制通过 wasmtime 的 epoch 中断实现。

panic、trap、权限违规或超出限制都会丢弃该实例。用户只会收到一次提示，下次需要时插件重新启动；连续崩溃时重启前的等待时间逐次翻倍（1 秒、2 秒……最长 60 秒）。连续崩溃三次后，插件在本次会话中被标记为 **失败**，**设置 › 插件** 中会显示原因；点 **重启**、关闭再打开，或 **从磁盘重新加载** 都能让它重新启动。只要有一次调用正常返回，计数就会清零。插件崩溃不会影响 IDE 本身。

**设置 › 插件** 会显示每个插件的激活次数、近期调用延迟（p95）、崩溃次数、超时的编辑器结果和空闲回收次数。

## 远程项目

插件代码在项目所在的机器上运行。窗口打开远程项目时，桌面端会把已启用的代码插件及其授权发送给 `openidea-server`，由它在文件旁边运行：文件读取、搜索、进程、网络请求和诊断都作用于远程机器，`process:` 程序也在那里解析。密钥始终留在桌面端的钥匙串中，服务器在请求需要时向桌面端索取。远程插件的视图、通知、状态栏项和终端照常显示在窗口中。

目前的限制：项目插件（`.openidea/plugins`）还不能在远程项目中运行；插件在服务器上的存储初始为空。

## 异步与 API 2.0

1.x API 有意设计为同步。WASI 0.3 异步组件计划在 API 2.0 引入，宿主中会有一个适配层让 1.x 插件继续运行。见 [API 版本与兼容](https://openidea.sh/zh/plugins/versioning.md)。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.