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"
相关推荐
天衍四九12 小时前
Git从0到实战(四):冲突解决与版本回退 —— 别怕,出错了也能救
github
大刚测试开发实战13 小时前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
uhakadotcom1 天前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github
Avan_菜菜2 天前
AI 能写代码了,为什么我反而开始要求它先写文档?
前端·github·ai编程
逛逛GitHub2 天前
这个爆红的 GitHub 项目让 token 直接省 60–95%。
github
iccb10132 天前
5年,一个程序员是如何把私有化在线客服系统做到第一名的
前端·后端·github
蝎子莱莱爱打怪2 天前
AI Agent 相关知识扫盲:16 个概念+11张图+38个开源项目推荐
人工智能·github·agent
用户317723070363 天前
Pydub:用 Python 处理音频,不写废话
github
张居邪3 天前
GitHub Actions + 阿里云 OSS:OIDC 免密同步构建产物
后端·github
张居斜3 天前
GitHub Actions + 阿里云 OSS:OIDC 免密同步构建产物
github·oss·llm-wiki