OMO(oh-my-openagents)插件在OpenCode Desktop v1.4.33以上版本失效问题研究

1 根因分析

根本原因 : oh-my-openagent 插件使用 bun build --target bun 构建,产物包含大量 Bun 专有 API 调用(Bun.file()Bun.spawn()Bun.which() 等)。OpenCode Desktop 使用 Node.js/Electron 运行时,不认识 Bun 全局变量,导致插件加载失败。

问题链:

scss 复制代码
插件源码使用 Bun API
    ↓
bun build --target bun 内联 Bun 调用
    ↓
OpenCode Desktop (Node.js) 加载插件
    ↓
ReferenceError: Bun is not defined
    ↓
插件加载失败 → Agents 不注册

2 解决方案

核心思路: 在插件加载前注入 Bun polyfill,提供 Node.js 等价实现。

完整修复脚本 (script/patch-bun-polyfill.ts):

dart 复制代码
#!/usr/bin/env bun

import { readFileSync, writeFileSync } from "node:fs"
import { dirname, join } from "node:path"
import { fileURLToPath } from "node:url"

const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url))
const DIST_PATH = join(SCRIPT_DIR, "..", "dist", "index.js")

const POLYFILL_MARKER = "// __BUN_POLYFILL_INJECTED__"
const POLYFILL = `
${POLYFILL_MARKER}
if (typeof Bun === 'undefined') {
  const fs = __require('fs');
  const path = __require('path');
  const { spawn, spawnSync, execSync } = __require('child_process');
  const { createHash } = __require('crypto');
  const { Readable } = __require('stream');

  globalThis.Bun = {
    file: (filePath) => ({
      text: () => fs.promises.readFile(filePath, 'utf-8'),
      arrayBuffer: () => fs.promises.readFile(filePath).then(b => b.buffer),
      exists: () => fs.promises.access(filePath).then(() => true, () => false),
      delete: () => fs.promises.unlink(filePath),
      size: fs.statSync(filePath).size,
    }),
    write: (filePath, data) => fs.promises.writeFile(filePath, data),
    spawn: (cmd, opts) => {
      const args = Array.isArray(cmd) ? cmd : [cmd];
      const proc = spawn(args[0], args.slice(1), { 
        ...opts, 
        stdio: opts?.stdio || ['ignore', 'pipe', 'pipe'] 
      });
      return {
        exited: new Promise((resolve, reject) => {
          proc.on('close', (code) => resolve(code));
          proc.on('error', reject);
        }),
        stdout: proc.stdout,
        stderr: proc.stderr,
        pid: proc.pid,
        kill: (signal) => proc.kill(signal),
      };
    },
    spawnSync: (cmd, opts) => {
      const args = Array.isArray(cmd) ? cmd : [cmd];
      const result = spawnSync(args[0], args.slice(1), { 
        ...opts, 
        stdio: opts?.stdio || ['ignore', 'pipe', 'pipe'] 
      });
      return {
        stdout: result.stdout,
        stderr: result.stderr,
        exitCode: result.status ?? 1,
        pid: result.pid ?? -1,
      };
    },
    which: (cmd) => {
      try {
        const result = process.platform === 'win32'
          ? execSync('where ' + cmd, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] })
              .trim().split('\\n')[0]
          : execSync('which ' + cmd, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] })
              .trim();
        return result || null;
      } catch { return null; }
    },
    hash: {
      xxHash32: (data, seed) => {
        const h = createHash('md5')
          .update(typeof data === 'string' ? data : Buffer.from(data))
          .digest();
        return h.readUInt32LE(0) ^ (seed || 0);
      },
    },
    serve: () => { throw new Error('Bun.serve not available in Node.js'); },
    readableStreamToText: async (stream) => {
      if (stream instanceof Readable) {
        const chunks = [];
        for await (const chunk of stream) chunks.push(chunk);
        return Buffer.concat(chunks).toString('utf-8');
      }
      return new Response(stream).text();
    },
    sleep: (ms) => new Promise(r => setTimeout(r, ms)),
  };
}
`

const original = readFileSync(DIST_PATH, "utf-8")

if (original.includes(POLYFILL_MARKER)) {
  console.log("Bun polyfill already present, skipping.")
  process.exit(0)
}

// Find the __require definition line and insert polyfill after it
const lines = original.split("\n")
const requireLineIndex = lines.findIndex(
  l => l.includes('var __require = typeof import.meta.require')
)
if (requireLineIndex === -1) {
  throw new Error("Could not find __require definition line")
}
lines.splice(requireLineIndex + 1, 0, POLYFILL)
const patched = lines.join("\n")

writeFileSync(DIST_PATH, patched, "utf-8")
console.log("Injected Bun polyfill for Node.js/Electron compatibility")

3 一键修复命令

bash 复制代码
# 1. 进入源码目录
cd ~\oh-my-openagent

# 2. 安装依赖
bun install

# 3. 构建(自动应用 node-require-shim)
bun run build

# 4. 注入 Bun polyfill
bun run script/patch-bun-polyfill.ts

# 5. 部署到缓存目录
copy /Y dist\index.js "%USERPROFILE%\.cache\opencode\packages\oh-my-openagent@3.17.15\node_modules\oh-my-openagent\dist\index.js"

# 6. 部署到配置目录
copy /Y dist\index.js "%USERPROFILE%\.config\opencode\node_modules\oh-my-openagent\dist\index.js"
相关推荐
码流怪侠2 小时前
【GitHub】TextGen:开源本地大模型运行平台的终极解决方案
python·程序员·github
小雨青年3 小时前
GitHub Copilot Commit Message 生成与自定义配置优化指南
人工智能·github·copilot
无限进步_3 小时前
【C++】AVL树完全解析:从平衡因子到四种旋转
c语言·开发语言·数据结构·c++·后端·算法·github
华万通信king4 小时前
2026 年 GitHub AI 趋势周报:Skills 生态崛起,Agent 框架去中心化
人工智能·去中心化·github
逛逛GitHub4 小时前
GitHub 上 3.7 万的 Star,终端里浏览文件的开源工具。
github
GISer_Jing4 小时前
基于 GitHub Actions 端到端工程化落地——AI全栈项目实战案例
人工智能·github
YuePeng19 小时前
我用 30 行 Java 注解,做出了别人花三周写的管理后台,还顺手接入了 DeepSeek
后端·github
无心水21 小时前
【Hermes:MCP 与工具实战】28、GitHub MCP 深度实战:PR 审查、Issue、自动汇报全搞定
人工智能·github·issue·openclaw·养龙虾·hermes·honcho
逛逛GitHub1 天前
GitHub 上狂揽 1.8 万 Star!开源平替的 Claude Design。
github