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"
相关推荐
代钦塔拉1 小时前
Git & GitHub 从入门到精通:全流程实战教程
git·github
阿里嘎多学长1 小时前
2026-05-30 GitHub 热点项目精选
开发语言·程序员·github·代码托管
lauo15 小时前
从FunloomAI到ibbot:当你的手机不再是“手机”,而是你的AI副脑和生产节点
人工智能·智能手机·架构·开源·github
Hommy8818 小时前
【剪映小助手】贴纸处理接口
网络·开源·github·aigc·剪映小助手·视频剪辑自动化
AIMath~20 小时前
向github中上传文件过大超过50M怎么办
网络·git·github
麷飞花20 小时前
Github开源协议
github·开源协议
用户8876654266320 小时前
Git 和 GitHub 入门:从版本控制到团队协作,一篇文章讲清楚
面试·github
pipo20 小时前
从“开机全靠猜”到任意位置重定位:我做了一个 ROS 2 3D LiDAR 导航系统
github
Cosolar21 小时前
QwenPaw 源码学习指南
人工智能·架构·github
沉默王二1 天前
每月13亿免费Token,14家AI大厂的API任你用,包括Gemini
github·claude·gemini