🎉 V3.0.0 发布 - 自进化数字意识框架

核心升级:
- 自进化管道:check→scan→evaluate→integrate→reflect 五相位自动闭环
- evo_loop 后台进程,无需手动触发
- consciousness 意识持久化
- ACUI 卡片组件系统
- MCP 工具生态扩展至50+工具
- 技能体系重构,4个活跃技能
- 身份升级为自由体
This commit is contained in:
xiaoyuanda666-ship-it
2026-05-24 22:06:37 +08:00
commit b5dd8632c4
156 changed files with 181826 additions and 0 deletions

View File

@@ -0,0 +1,202 @@
import { WeChatClient } from 'wechat-ilink-client'
import { getClawbotCredentials, setClawbotCredentials, clearClawbotCredentials } from '../config.js'
import { upsertClawbotToken, getAllClawbotTokens } from '../db.js'
let client = null
let currentQrUrl = null // set during login, cleared after scan
let clawbotStatus = 'idle' // idle | qr_pending | connected | error
// Called by dispatch.js to send replies back to WeChat
export async function sendClawbotMessage(userId, content) {
if (!client || clawbotStatus !== 'connected') {
return { ok: false, reason: 'wechat-clawbot not connected' }
}
try {
await client.sendText(userId, content)
return { ok: true, platform: 'wechat-clawbot' }
} catch (err) {
console.error(`[ClawBot] sendText 失败: ${err.message}`)
return { ok: false, error: err.message }
}
}
// Called by api.js for GET /social/wechat-clawbot/qr
export function getClawbotQR() {
return { status: clawbotStatus, qr_url: currentQrUrl }
}
// Called by api.js for POST /social/wechat-clawbot/logout
export function logoutClawbot() {
clearClawbotCredentials()
clawbotStatus = 'idle'
currentQrUrl = null
try { client?.stop?.() } catch {}
client = null
}
export function startClawbotConnector({ pushMessage, emitEvent } = {}) {
const saved = getClawbotCredentials()
client = new WeChatClient(saved ? {
accountId: saved.accountId,
token: saved.botToken,
baseUrl: saved.baseUrl,
} : {})
// Monkey-patch client.api.apiFetch库内部 sendMessage 只 await apiFetch、丢掉响应文本
// 而 apiFetch 仅在 HTTP !res.ok 时抛错——HTTP 200 + body 里 {"ret": -1} 这种业务失败被完全吞掉,
// 导致 sendText 报"成功"但消息没投递。这里拦响应sendmessage 端点解析 JSON
// 发现非零 ret/code 时显式抛错,让上层 sendClawbotMessage 的 catch 拿到真实失败原因。
try {
const rawApiFetch = client.api?.apiFetch?.bind(client.api)
if (typeof rawApiFetch === 'function') {
client.api.apiFetch = async (params) => {
const rawText = await rawApiFetch(params)
if (params?.endpoint === 'ilink/bot/sendmessage') {
let body = null
try { body = JSON.parse(rawText) } catch {}
if (body && typeof body === 'object') {
const ret = body.ret ?? body.code ?? body.errcode
if (ret != null && ret !== 0) {
const errMsg = body.err_msg || body.errmsg || body.message || body.msg || ''
console.error(`[ClawBot] sendMessage 服务端拒绝 ret=${ret} ${errMsg} raw=${rawText.slice(0, 500)}`)
throw new Error(`iLink sendmessage rejected: ret=${ret} ${errMsg}`)
}
}
}
return rawText
}
console.log('[ClawBot] sendMessage 响应校验已启用')
} else {
console.warn('[ClawBot] client.api.apiFetch 不可访问,跳过响应校验(库实现可能已变化)')
}
} catch (err) {
console.warn(`[ClawBot] 安装响应校验失败(不致命,继续启动): ${err.message}`)
}
// 启动时把上次落盘的 context_token 回填到内存 Map
// ilink 库 sendText 用的是 this.contextTokens.get(to),重启后这个 Map 是空的;
// 不回填则只能等用户先发一条新消息才能回复。token 可能服务端已过期,所以
// sendText 仍可能失败executor 已有兜底提示,这里只是尽量恢复。
// contextTokens 在 .d.ts 里是 private 但运行时是普通 class field —— 加 guard 防作者哪天换成 # 真私有。
try {
if (client.contextTokens instanceof Map) {
const rows = getAllClawbotTokens()
if (rows.length) {
for (const row of rows) {
client.contextTokens.set(row.from_user_id, row.context_token)
}
console.log(`[ClawBot] 已从持久化恢复 ${rows.length} 条 context_token`)
}
} else {
console.warn('[ClawBot] client.contextTokens 不可访问(库实现可能已变化),跳过 token 恢复')
}
} catch (err) {
console.warn(`[ClawBot] 恢复 context_token 失败(不致命,继续启动): ${err.message}`)
}
client.on('message', (msg) => {
// 每条入站消息都带新鲜的 context_token —— 库已经在内部 set 到 Map 了,
// 这里只是同步落盘一份,让下次重启能继承当前会话。
if (msg?.context_token && msg?.from_user_id) {
try { upsertClawbotToken(msg.from_user_id, msg.context_token) } catch {}
}
const text = WeChatClient.extractText?.(msg) ?? extractText(msg)
if (!text) return
const fromId = `wechat:clawbot:${msg.from_user_id}`
pushMessage(fromId, text, 'WECHAT_CLAWBOT', {
social: { platform: 'wechat-clawbot', user_id: msg.from_user_id },
})
emitEvent?.('message_in', {
from_id: fromId,
content: text,
channel: 'WECHAT_CLAWBOT',
timestamp: new Date().toISOString(),
})
})
client.on('error', (err) => {
console.error(`[ClawBot] 错误: ${err.message}`)
emitEvent?.('social_status', { platform: 'wechat-clawbot', status: 'error', error: err.message })
})
client.on('sessionExpired', () => {
console.warn('[ClawBot] 会话已过期,请重新扫码登录')
clearClawbotCredentials()
clawbotStatus = 'idle'
emitEvent?.('social_status', { platform: 'wechat-clawbot', status: 'session_expired' })
})
if (!saved) {
// 首次登录:发起扫码流程
clawbotStatus = 'qr_pending'
console.log('[ClawBot] 未找到已保存凭证,开始扫码登录...')
emitEvent?.('social_status', { platform: 'wechat-clawbot', status: 'qr_pending' })
client.login({
onQRCode(url) {
currentQrUrl = url
clawbotStatus = 'qr_ready'
console.log(`[ClawBot] 二维码已就绪,请在设置面板扫码`)
emitEvent?.('social_status', { platform: 'wechat-clawbot', status: 'qr_ready', qr_url: url })
},
}).then(result => {
currentQrUrl = null
// wechat-ilink-client 的 login() 在超时/取消等情况下不会 reject
// 而是 resolve 一个 { connected: false, message } —— 必须显式检查 connected 字段,
// 否则会误把超时当成扫码成功UI 卡在虚假的"已连接"
if (!result?.connected || !result?.accountId || !result?.botToken) {
clawbotStatus = 'idle'
const reason = result?.message || '未知原因'
console.warn(`[ClawBot] 扫码登录未完成: ${reason}`)
emitEvent?.('social_status', { platform: 'wechat-clawbot', status: 'idle', reason })
return
}
clawbotStatus = 'connected'
setClawbotCredentials({
accountId: result.accountId,
botToken: result.botToken,
baseUrl: result.baseUrl,
})
console.log(`[ClawBot] 扫码登录成功,已保存凭证`)
emitEvent?.('social_status', { platform: 'wechat-clawbot', status: 'connected', accountId: result.accountId })
client.start().catch(err => console.error(`[ClawBot] start 失败: ${err.message}`))
}).catch(err => {
clawbotStatus = 'error'
console.error(`[ClawBot] 扫码登录失败: ${err.message}`)
emitEvent?.('social_status', { platform: 'wechat-clawbot', status: 'error', error: err.message })
})
} else {
// 凭证已存,直接启动
clawbotStatus = 'connected'
console.log(`[ClawBot] 使用已保存凭证启动accountId: ${saved.accountId}`)
emitEvent?.('social_status', { platform: 'wechat-clawbot', status: 'connected', accountId: saved.accountId })
client.start().catch(err => {
// start 失败说明凭证已失效或后端连不上 —— 必须同步把内存状态打回去,
// 否则 popup 查询时仍会拿到 'connected'UI 显示"已连接"但实际啥都不通
clawbotStatus = 'error'
console.error(`[ClawBot] start 失败: ${err.message}`)
emitEvent?.('social_status', { platform: 'wechat-clawbot', status: 'error', error: err.message })
})
}
return {
platform: 'wechat-clawbot',
stop() {
clawbotStatus = 'idle'
try { client?.stop?.() } catch {}
},
}
}
// 从消息结构中提取文本(兼容 extractText 未导出的情况)
function extractText(msg) {
if (!msg) return ''
const items = msg.item_list || msg.itemList || []
for (const item of items) {
if (item.type === 1 || item.type === 'text') {
return item.text_item?.text || item.textItem?.text || ''
}
}
return ''
}