Files
BaiLongma/src/capabilities/marketplace/index.js
xiaoyuanda666-ship-it b5dd8632c4 🎉 V3.0.0 发布 - 自进化数字意识框架
核心升级:
- 自进化管道:check→scan→evaluate→integrate→reflect 五相位自动闭环
- evo_loop 后台进程,无需手动触发
- consciousness 意识持久化
- ACUI 卡片组件系统
- MCP 工具生态扩展至50+工具
- 技能体系重构,4个活跃技能
- 身份升级为自由体
2026-05-24 22:06:37 +08:00

200 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import fs from 'fs'
import path from 'path'
import { execSync, execFileSync } from 'child_process'
import { paths } from '../../paths.js'
const IS_WIN = process.platform === 'win32'
const TOOLS_DIR = path.join(paths.sandboxDir, 'installed_tools')
// 运行时注册表name → { schema, execute }
const registry = new Map()
// 不允许覆盖的内置工具名(关键工具保护)
const BUILTIN_NAMES = new Set([
'express', 'send_message', 'read_file', 'list_dir', 'write_file', 'delete_file',
'make_dir', 'exec_command', 'kill_process', 'list_processes', 'web_search',
'fetch_url', 'browser_read', 'search_memory', 'upsert_memory', 'skip_recognition',
'speak', 'generate_lyrics', 'generate_music', 'generate_image', 'set_tick_interval',
'media_mode', 'hotspot_mode', 'open_doc_panel', 'person_card_mode', 'music',
'manage_reminder', 'schedule_reminder', 'manage_prefetch_task', 'ui_show', 'ui_update',
'ui_hide', 'ui_patch', 'manage_app', 'ui_register', 'focus_banner',
'set_location', 'delegate_to_agent', 'grant_agent_delegation', 'recall_memory',
'complete_startup_self_check', 'set_task', 'complete_task', 'update_task_step',
'install_tool', 'uninstall_tool', 'list_tools', 'set_security', 'connect_wechat',
])
function ensureToolsDir() {
fs.mkdirSync(TOOLS_DIR, { recursive: true })
}
function buildSchema(name, description, parameters) {
return {
type: 'function',
function: { name, description, parameters },
}
}
// helpers 暴露给已安装工具代码使用的受控能力
function buildHelpers() {
return {
fetch: (...args) => globalThis.fetch(...args),
exec: (command, opts = {}) => {
try {
const execOpts = {
encoding: 'utf-8',
timeout: opts.timeout ?? 30_000,
maxBuffer: 2 * 1024 * 1024,
windowsHide: true,
}
if (IS_WIN) {
// 走 PowerShell 显式调用,并三层同步 UTF-8 编码chcp + OutputEncoding + InputEncoding
// 避免中文 Windows 默认 GBK 代码页让原生命令输出被按 UTF-8 解码成乱码。
const wrapped =
`chcp 65001 > $null; ` +
`[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; ` +
`[Console]::InputEncoding=[System.Text.Encoding]::UTF8; ` +
`$OutputEncoding=[System.Text.Encoding]::UTF8; ` +
command
return execFileSync('powershell.exe', ['-NoLogo', '-NoProfile', '-Command', wrapped], execOpts)
}
return execSync(command, execOpts)
} catch (err) {
return `Error: ${err.message}`
}
},
log: (msg) => console.log('[installed_tool]', msg),
}
}
// 把工具代码字符串编译为可调用的 async 函数
// 代码是函数体(不含 function 声明可用变量args, helpers
function compileExecute(name, code) {
let fn
try {
// AsyncFunction 构造器接受参数名列表 + 函数体
// eslint-disable-next-line no-new-func
fn = new Function('args', 'helpers', `"use strict";\nreturn (async () => {\n${code}\n})()`)
} catch (err) {
throw new Error(`工具 "${name}" 代码语法错误:${err.message}`)
}
return async (args) => {
const helpers = buildHelpers()
return await fn(args ?? {}, helpers)
}
}
function validateName(name) {
if (!name || typeof name !== 'string') throw new Error('工具名称不能为空')
if (!/^[a-z][a-z0-9_]{1,49}$/.test(name)) {
throw new Error('工具名称只能含小写字母、数字、下划线,长度 2-50且以字母开头')
}
if (BUILTIN_NAMES.has(name)) throw new Error(`"${name}" 是保留名称,不允许覆盖`)
}
function validateParameters(parameters) {
if (!parameters || typeof parameters !== 'object') throw new Error('parameters_schema 必须是对象')
if (parameters.type !== 'object') throw new Error('parameters_schema.type 必须是 "object"')
if (!parameters.properties || typeof parameters.properties !== 'object') {
throw new Error('parameters_schema.properties 必须是对象')
}
}
// ─── 对外 API ────────────────────────────────────────────────────────────────
export async function installTool({ name, description, parameters, code }) {
validateName(name)
if (!description || typeof description !== 'string') throw new Error('description 不能为空')
validateParameters(parameters)
if (!code || typeof code !== 'string') throw new Error('code 不能为空')
// 先编译,语法错误立即报告
const executeFn = compileExecute(name, code)
ensureToolsDir()
const meta = {
name,
description,
parameters,
code,
installed_at: new Date().toISOString(),
}
fs.writeFileSync(
path.join(TOOLS_DIR, `${name}.json`),
JSON.stringify(meta, null, 2),
'utf-8',
)
registry.set(name, { schema: buildSchema(name, description, parameters), execute: executeFn })
console.log(`[marketplace] 工具 "${name}" 安装完成`)
return `工具 "${name}" 安装成功。下一轮对话起即可调用。`
}
export function uninstallTool({ name }) {
if (!name) throw new Error('name 不能为空')
if (!registry.has(name)) return `工具 "${name}" 未安装。`
registry.delete(name)
const filePath = path.join(TOOLS_DIR, `${name}.json`)
if (fs.existsSync(filePath)) fs.unlinkSync(filePath)
console.log(`[marketplace] 工具 "${name}" 已卸载`)
return `工具 "${name}" 已卸载。`
}
export function listInstalledTools() {
return [...registry.entries()].map(([name, { schema }]) => ({
name,
description: schema.function.description,
source: 'installed',
}))
}
export function getInstalledToolNames() {
return [...registry.keys()]
}
export function getInstalledToolSchema(name) {
return registry.get(name)?.schema ?? null
}
export function isInstalledTool(name) {
return registry.has(name)
}
export async function executeInstalledTool(name, args) {
const tool = registry.get(name)
if (!tool) throw new Error(`已安装工具 "${name}" 不存在`)
const result = await tool.execute(args)
if (result === undefined || result === null) return `工具 "${name}" 执行完成(无返回值)`
return typeof result === 'string' ? result : JSON.stringify(result)
}
// 启动时从磁盘加载所有已安装工具
export async function loadInstalledTools() {
ensureToolsDir()
const files = fs.readdirSync(TOOLS_DIR).filter(f => f.endsWith('.json'))
let loaded = 0
for (const file of files) {
const filePath = path.join(TOOLS_DIR, file)
try {
const meta = JSON.parse(fs.readFileSync(filePath, 'utf-8'))
const { name, description, parameters, code } = meta
if (!name || !code) {
console.warn(`[marketplace] 跳过无效工具文件 ${file}`)
continue
}
const executeFn = compileExecute(name, code)
registry.set(name, { schema: buildSchema(name, description, parameters), execute: executeFn })
loaded++
} catch (err) {
console.warn(`[marketplace] 加载工具 ${file} 失败:${err.message}`)
}
}
if (loaded > 0) console.log(`[marketplace] 已加载 ${loaded} 个已安装工具`)
}